cmscout matches code blocks across languages (functions, classes, methods, JSX elements, string templates, imports, constants) and diffs per block. A renamed method shows as one changed unit instead of a delete plus an add; a moved function keeps its block identity.
Parsing uses tree-sitter. A staged hierarchical matcher then pairs blocks by name, kind, text similarity, and scope ancestry. The output is deterministic: the same input always produces the same report.
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, word-level intra-line changes, and [changed]/[moved]/[converted]/[whitespace] tags where applicable.
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. See doc/word-diff.md.
- Reporting renders one section per kind (imports, constants, classes, methods, functions, lifecycle, jsx, 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.
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.
Every non-empty line of the before and after input files appears in the report output. Files in unsupported languages or with no semantic blocks fall back to a whole-file line diff.
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
There are no prebuilt binaries; build from source.
Requires Go 1.25+ and a C compiler.
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 vetor from the clone:
CGO_ENABLED=1 go install ./cmd/cmscoutUse 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 -pThe wrapper respects NO_COLOR, CMCSOUT_WORD_DIFF, CMCSOUT_ADDED_STYLE, CMCSOUT_REMOVED_STYLE, and CMCSOUT_KEEP_UNCHANGED environment variables. See the script for details.
MPL-2.0, see LICENSE.