Skip to content

mongodb-labs/scas

Repository files navigation

SCAS: Source Code Algebraic System

Kevin Pulo, MongoDB Research


⚠️⚠️⚠️DISCLAIMER⚠️⚠️⚠️

This is not an official MongoDB product. There is no support or warranty of any kind, including for any security-related issues, and this software is excluded from the MongoDB Bug Bounty program.


Source Code Algebraic System (SCAS) is a novel approach to programming. Instead of text editing source code files, the programmer applies a series of logical transformations to the codebase. These transformations operate on the entire codebase, and each operation makes all the changes necessary for a single semantic change. Operations move the codebase to a new valid state, and can easily be composed, with higher level operations built up out of lower level ones. Operations can also often be language-agnostic, allowing a level of meta-programming.

This approach is expected to improve coding productivity and accuracy, especially once ML models are layered on top (instead of their current textual approaches). That is, instead of having ML models (such as LLMs) which directly output the modified source code for some desired change, they can instead output the necessary logical transformation operations that are required to achieve the change. This allows the model to operate at a higher level of abstraction, much closer to the planning that is "actual programming", and freeing it from wrangling low level text and syntactic elements.

Very basic overview

The general flow is:

  1. Parse the codebase and import the syntax tree into MongoDB.

  2. Enrich the syntax tree.

  3. Apply operation(s) to the enriched syntax tree, regenerating the source code after each.

Dependencies

Development is being done on Ubuntu 22.04, so YMMV in other environments.

  1. Python + PyPI packages:

    sudo apt-get install python3.10
    python3 -m pip install pymongo tree-sitter-language-pack==0.13.0 frozendict httpx==0.28.1 pydantic==2.12.5
    
  2. MongoDB 8.0+ database instance

  3. mongosh: https://www.mongodb.com/docs/mongodb-shell/install/

  4. MongoDB Tools (mongoimport and mongoexport): https://www.mongodb.com/docs/database-tools/

  5. bash 5.3+ with ${;cmd;} patch installed in $PATH as bash-scas:

    git clone https://https.git.savannah.gnu.org/git/bash.git
    cd bash
    patch -p1 < /path/to/scas/bash-trailing_newline_nofork_comsub.patch
    ./configure && make -j$(nproc)
    ln -nsf $(pwd)/bash ~/bin/bash-scas  # or anywhere that's in $PATH
    
  6. jq

    sudo apt-get install jq
    
  7. podman (preferred), or docker:

    sudo apt-get install podman
    

    or

    sudo apt-get install docker.io
    

Optional Dependencies

  1. Python MCP SDK (recommended, only needed for the mcp-server operation):

    python3 -m pip install mcp
    
  2. Java (not necessary, only if re-enabling the legacy enrich-java):

    sudo apt-get install openjdk-21-jdk maven
    

Build

  1. The default enricher enrich-lsp needs lsproxy to be built:

    lsproxy/scripts/build.sh
    
  2. The legacy enrich-java enricher (not enabled by default) needs java-spoon to be built:

    cd java-spoon/scas-java-spoon
    mvn package
    

    After building, ensure that there is a copy or symlink of the resulting scas-java-spoon-1.0-SNAPSHOT-jar-with-dependencies.jar file as libexec/scas/scas-java-spoon.jar.

Test suite

  • Test suite dependencies (beyond the above):

    1. Git
  • To run the test suite:

    ./run_tests
    

    This will use the MongoDB instance/cluster specified by the connection string in $SCAS_DB, defaulting to mongodb://localhost/scas.

  • To run the unit tests:

    ./run_unittests
    

Detailed usage

Setup

  1. Checkout the repo:

    git clone https://github.com/mongodb-labs/scas
    cd scas
    
  2. Decide where to install SCAS.

    1. If you choose the checked-out git repo, then you only need to add the bin dir to $PATH:

      export PATH="$(pwd)/bin:$PATH"
      

      Consider adding this to the end of your ~/.profile file.

    2. If another location, then ensure that the bin/scas program can be found via $PATH. Then, you need to install the contents of the libexec/scas dir to a place where it can be found, one of:

      • /etc/xdg/scas/libexec (or scas/libexec under an entry in $XDG_CONFIG_DIRS)
      • libexec/scas or ../libexec/scas (relative to where bin/scas has been installed)
      • /usr/libexec/scas
  3. If bash-scas hasn't already been installed, you can do so now:

    git clone https://https.git.savannah.gnu.org/git/bash.git
    cd bash
    patch -p1 < ../bash-trailing_newline_nofork_comsub.patch
    ./configure && make -j$(nproc)
    cd ..
    

    If you installed SCAS:

    1. To the checked-out git repo, then:

      ln -nsf ../bash/bash bin/bash-scas
      
    2. To another location, then place the bash-scas symlink (or copy the bash binary as bash-scas) somewhere in the $PATH, ideally alongside the scas symlink/script.

  4. (Optional) Initialise scas in each shell where it will be used (this enables the scas -c and scas -d commands):

    source <(scas _shellinit bash)
    

    Consider adding this to the end of your ~/.profile file.

  5. Set the connection string of the MongoDB instance/cluster that will be used (including DB name). Defaults to mongodb://localhost/scas.

    # If step 2 above was done:
    scas -d 'mongodb://localhost/scas'
    
    # Otherwise:
    export SCAS_DB='mongodb://localhost/scas'
    
  6. Set the "name" for your repo. This will be the collection name under the db (if it already exists, it will be dropped).

    # If step 2 above was done:
    scas -c my_repo_name
    
    # Otherwise:
    export SCAS_COLL=my_repo_name
    
  7. Parse your repo's source code.

    scas parse /path/to/my/repo my_repo_parsed.json
    
  8. Import the parsed syntax tree into MongoDB.

    scas import my_repo_parsed.json
    
  9. Enrich the syntax tree. This re-parses the original source code, which must not have changed.

    scas enrich /path/to/my/repo
    

Usage - command line

  1. Run each operation with the run operation, passing in the source location of interest. Note that run expects "normal" 1-indexed line/col numbers. Alternatively you can directly pass in node ids. eg:
    scas run ChangeLiteral 0.15 <<<test1.java:7:20
    
    scas run Rename discountNew <<<test1.java:23:20
    

Usage - editor integration

The only editor integration so far is vim (may not work yet in neovim, YMMV).

vim

  1. Source the scas.vim file from your ~/.vimrc (or otherwise source/plugin manager it).

  2. Open the source files from the base dir of your repo (ie. from the /path/to/my/repo that was passed to scas parse and scas enrich in the earlier steps). This is necessary so that the relative filenames match, and SCAS is able to properly apply the operations to the correct syntax tree nodes. (This restriction will eventually be removed.)

  3. To run a SCAS operation on the node targeted by the current cursor position, press Meta-s (Alt-s). This will open the vim command line with :Scas pre-filled. You can now enter your SCAS operation and args, and press enter to execute it.

    The Scas vim command will take care of automatically re-reading any open files that were modified (without modifying your autoread setting). If the SCAS operation outputs any messages or errors, then those will be shown with a "Press Enter" prompt. Otherwise, the file is silently updated with the new contents.

The Meta-s (Alt-s) shortcut works in both normal and insert mode, but when using SCAS you shouldn't really ever be in insert mode, since you shouldn't modify the files except via SCAS. If you do modify the source files outside of SCAS, then you'll need to re-run the parse + import + enrich flow from scratch (steps 5-7 above).

Usage - MCP server (for AI agents)

The mcp-server operation runs an MCP (Model Context Protocol) server over stdio, exposing scas as a tool for AI agents. It requires the mcp Python package (pip install mcp).

  1. Ensure that $SCAS_COLL is set (and optionally $SCAS_DB), and that the codebase has been parsed, imported, and enriched (steps 3-7 above).

  2. Configure your MCP client to launch the scas MCP server. For example, in Claude Desktop's claude_desktop_config.json:

    {
      "mcpServers": {
        "scas": {
          "command": "scas",
          "args": ["mcp-server"],
          "env": {
            "SCAS_COLL": "my_repo_name"
          }
        }
      }
    }

    or in opencode.jsonc:

    {
      "mcp": {
        "scas": {
          "type": "local",
          "command": ["scas", "mcp-server"],
          "enabled": true,
          //"environment": {
          //  "SCAS_COLL": "my_repo_name",  // or just allow the value set in the environment, before loading opencode, to pass through
          //}
        }
      },
    }

    The server exposes a single scas tool that accepts a list of string arguments (equivalent to command-line arguments to the scas program). The tool returns structured output with fields rc (exit code), stdout, and stderr.

Usage - low level

Note that the run operation (described above) should be preferred to manually running operations low-level like this. The run operation automatically takes care of calling _FindNode, regenerate, etc.

  1. Run an operation. See below for the operation API/protocol. Note that line/col numbers in SCAS are zero-indexed. eg:

    scas _FindNode <<<test1.java:6:19 | scas _FirstNode | scas ChangeLiteral 0.15
    
  2. Regenerate the source code files, and update the "locs" in the database.

    scas regenerate --locs --files /path/to/output/repo
    
  3. Rinse and repeat for the next operation.

Debug

  1. To print the syntax tree stored in a collection, run:
    scas regenerate --tree | less
    

Operation API

Operations are programs, written in any language. They take arguments on the command line, and input on stdin. They output on stdout, and errors go to stderr and cause a non-zero exit code.

The input and output varies by operation, but is usually comprised of id‍s for syntax tree nodes, with each line containing a (possibly empty) list of whitespace-separated ids. Each line is considered to be a semantically separate set of ids. Each id is 40 hex digits (similar to git commit hashes). Operations may be read-only, or may modify the syntax tree. In either case, they expect $SCAS_COLL to be set (and $SCAS_DB, except that will default to mongodb://localhost/scas if unset).

_FindNode is a special operation. It takes loc‍s as input, each of which specifies a location or range in the source code text files. The format is filename:start_line:start_col[-end_line:end_col], where filename is the name of the file (relative to the base of the repo, as passed to parse and enrich), and the start/end line/col numbers are zero-indexed (unlike the usual convention in text editors, compilers, etc). The end line/col numbers are optional, and if present, specify a range within the given source file (with the start line/col being inclusive, and the end line/col being exclusive). Otherwise, the start line/col numbers indicate an exact position in the source file. The _FindNode operation will query for syntax tree nodes which include the input loc, and output them from the most specific node (lowest level of the tree) up to the root node for that file. This serves as the most useful and convenient way of getting the ids of nodes that the programmer is interested in.

MongoDB Schema

The syntax tree is stored as one document per tree node. Each node has an immutable _id (computed at parse time), with references to the ids of its parent and children nodes.

Any operation can connect to the database and query/update the syntax nodes, however, in general, if there is an existing operation which achieves the same result, then it is preferable to instead call that operation.

(TODO: describe the schema)

About

Source Code Algebraic System

Resources

License

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors