Skip to content
tim-janikPublic

About

Code Motion Diff: Matches code blocks across languages (functions, classes, JSX, string templates) and diffs per block.

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

cmscout

cmscout compares two versions of a source file and shows what changed, one code block at a time. It recognizes functions, classes, methods, imports, JSX elements and other building blocks, so a renamed or moved function stays a single changed unit instead of a delete plus an add.

Parsing uses tree-sitter, then a staged hierarchical matcher pairs blocks by name, kind, text similarity and scope ancestry. The output is deterministic: the same input always produces the same report.

Usage

cmscout old.ts new.ts                # semantic block diff
cmscout --summary old.ts new.ts      # counts only
cmscout --simple-diff a.txt b.txt    # plain whole-file line diff, any file type

One side can be - to read from stdin. cmscout --help lists the remaining options.

When to use it

Use cmscout for stable block diffs across code motion: a block moves within a file, or a rewrite reidentifies the same constructs in another language.

Line diffs (diff -u) treat every line independently. When a method moves, reorders, changes parameters, or alters its body, the change drowns in added and removed lines. cmscout pairs the blocks first, re-identifies the method's before and after versions, then renders line or word diffs for just the parts that changed.

cmscout matches structural blocks in TS/TSX, JS/JSX, Go, Bash, C, and C++ and renders the change per block: similarity percentages and [changed]/[moved]/[converted]/[whitespace] tags where applicable. Pass --word-diff to highlight changed words inside a line.

How it works

The pipeline has several stages (detailed in doc/pipeline.md):

  • AST parsing via tree-sitter
  • Extraction of SemanticBlocks like functions, methods, classes, interfaces, JSX elements, string templates, imports, etc.
  • Matching of semantic blocks in hierarchical stages by name and kind, comment text, distance tables
  • Collapsing replaces matched children inside matched parents with canonical reference comments (// [matched: method foo]), so the parent's diff stays small. See doc/canonical-references.md.
  • Prefix attachment merges doc-comment runs directly before a component into that component's diff. See doc/prefix-comments.md and doc/enclosed-comments.md.
  • Word diff splits each changed line into words, diffs them at word granularity, and merges long adjacent runs into spans. Pass --word-diff to enable it. See doc/word-diff.md.
  • Reporting renders one section per kind (imports, constants, classes, functions, methods, lifecycle, JSX, templates, comments) and a summary.
  • Each block header shows a similarity percentage and the source code, with [changed], [moved], [converted], and [whitespace] tags where they apply. See doc/whitespace-classification.md.

Supported languages

Detection is extension-based and case-insensitive: .ts, .tsx, .js, .mjs, .cjs, .jsx, .go, .sh, .bash, .c, and the C++ family .cc, .cpp, .cxx, .c++, .hh, .hpp, .hxx, .h++, .tcc. Legacy .C maps to C++. For .h files, both C and C++ grammars run and the parse with fewer errors wins, so pure-C headers use C while C++ headers stay correct. C and C++ split function-like macros into their own block kind.

Line coverage

Every non-empty line of the before and after input files appears in the report output, unless --skip-unchanged drops unchanged blocks (they stay in the summary). Files in unsupported languages or with no semantic blocks fall back to a whole-file line diff; --simple-diff forces that fallback.

Output example

Real output, excerpted (Imports, Methods, Lifecycle, Templates, and Other sections omitted):

diff --cmscout testdata/old/knob.tsx testdata/new/knob.tsx
Constants
@@ -4,1 +4,1 @@  VERSION  98% similarity  [changed]
-export const VERSION = '1.0.0';
+export const VERSION = '1.0.1';

Classes
@@ -6,37 +6,38 @@  Knob  93% similarity  [changed]
 export class Knob extends LitElement {
   static styles = css`
-    .knob { width: 100px; height: 100px; }
+    .knob { width: 120px; height: 120px; }
   `;
   private last_ = 0;
   private delta = 0;
   private enabled = true;
+  private snapped = false;
   // [matched: lifecycle connectedCallback]
   // [matched: lifecycle disconnectedCallback]
-  updated() {
-    this.updateDelta();
+  connectedCallbackUpdated() {
+    this.handleUpdate();
   }
   // [matched: method handleUpdate]
   // [matched: method notifyValueChanged]
   // [matched: method render]
 }

Summary
  Added:     1
  Removed:   1
  Renamed:   2
  Changed:   4
  Matched:   10
  Unchanged: 6

Install

Release builds provide Linux x86-64 binary archives from Ubuntu 24.04. When a release is published, download cmscout-VERSION-linux-amd64.tar.xz from GitHub Releases, unpack it, and put cmscout on your PATH. The Git wrapper can stay beside the binary. Releases also provide the source archive and a SHA256SUMS file, and cmscout --version confirms the installed build.

To build from source, Go 1.25+ and a C compiler are required. Tree-sitter grammars are C libraries, so builds need CGO_ENABLED=1, which the Makefile sets.

git clone https://github.com/tim-janik/cmscout
cd cmscout
make build		# produces ./cmscout
make test		# full test suite
make run		# runs cmscout on testdata/ fixture files
make vet

or from the clone:

CGO_ENABLED=1 go install ./cmd/cmscout

Git integration

Use the included git-diff-wrapper.sh to replace git's default diff for a file:

GIT_EXTERNAL_DIFF=/path/to/git-diff-wrapper.sh git diff -- <file>
git -c diff.external=/path/to/git-diff-wrapper.sh log --ext-diff -p

The wrapper respects NO_COLOR, CMSCOUT_WORD_DIFF, CMSCOUT_ADDED_STYLE, CMSCOUT_REMOVED_STYLE, and CMSCOUT_KEEP_UNCHANGED environment variables. By default it passes --ignore-all-space and --skip-unchanged, so unchanged blocks stay hidden; set CMSCOUT_WORD_DIFF=1 for word highlights and CMSCOUT_KEEP_UNCHANGED=1 to show unchanged blocks. See the script for details.

License

MPL-2.0, see LICENSE.

About

Code Motion Diff: Matches code blocks across languages (functions, classes, JSX, string templates) and diffs per block.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages