repocli diff describes what changed and which files may be affected. It
reports source files, changed declarations, and import dependency paths. It does
not execute project commands or decide what a caller should do with the result.
-
affectedFiles: reached existing files in the analyzed workset, plus changed existing files. Each entry includespath, snapshotversion,seed, pathconfidence, dependencydistance,dependencyPathand rawrelations. -
seeds: automatic before/after query entries, withid,path,version,granularity(symbol, module, file or package) and the selectionbasis. -
sourceFiles: changed Go, Python, JavaScript, and TypeScript source paths. This includes changed test sources and deleted sources. A rename uses its new path; the old path and status remain inchanges. -
testFiles: existing test files under the requested directories that may be affected through a known-target dependency path, including inferred edges, or are themselves changed. Unknown-target relationships and deleted tests are omitted. -
changes: all changed files, their status, changed line ranges, and declarations intersecting those ranges in the before/after snapshots. Symbols includequalifiedNameto distinguish owners such asA.workandB.work. -
reasons: dependency paths explaining the affected test files, with typedrelationsand source locations where available.versionidentifies the before/after snapshot supplying the evidence. -
fallbackReasons: retained wire name for reasons the analysis is incomplete; repocli does not fill the test list or choose an execution fallback. -
observations: non-blocking gaps and metadata-only changes. These retain reporting context without creating dependency edges. -
repositoryandcomponents: repository identity, component roots and languages, declared product memberships, and per-component source/test lists.
For example, if source.ts exports a and b, changing the body of a selects
a test importing { a as alias }, even if it never calls alias. A test importing
only { b } is not selected by that direct dependency. Namespace, default and
side-effect imports are treated at module granularity. Go imports are treated at
package granularity. Transitive propagation is deliberately broader: once an
importing file is affected, its downstream importers may also be affected.
This is a static import-based estimate, not proof that other tests are
unaffected. It follows the static calls supplied by CodeGraph, preserving cross-file
targets and candidate evidence, so a helper change can reach tests importing its caller. It does not
check actual use of imports in tests or infer dynamic method dispatch, general value
references, runtime side effects, reflection, arbitrary resource access, or framework-specific
implicit setup dependencies beyond recognized configuration files (such as
conftest.py).
Requires Go 1.26+ to build and Git to read repositories. No Node or Python runtime is needed for analysis.
make build
./bin/repocli diff --repo /path/to/repo --base main --test-dir tests --jsonRepeat --test-dir for multiple directories. Use --test-dir . for colocated
tests throughout a repository. Test directories only limit candidate discovery;
sources and imported helpers inside them still use ordinary dependency analysis.
Paths in the report and test-directory arguments
are relative to the repository root. Without --test-dir, the command reports
affected files using all supported source files as candidate roots. With test directories,
affectedFiles covers the changeset/testset dependency workset. Both forms are bounded
by 2,000 parsed files and depth 32 per snapshot; no result promises a complete closure.
--base defaults to HEAD and must resolve to a commit. The comparison is that
exact commit versus the current working tree, including staged, unstaged, and
non-ignored untracked files. It does not implicitly find a merge base.
You can also supply a Git patch, including via stdin:
git diff --binary main > /tmp/change.patch
./bin/repocli diff --base main --file /tmp/change.patch --test-dir tests --json
git diff --binary HEAD | ./bin/repocli diff --file - --test-dir tests --jsonPatch mode reconstructs the postimage from --base in memory, independently of
the working tree. The base must match the patch preimage. Use --binary when a
patch includes binary changes. This mode does not include untracked files unless
the patch itself contains them.
The default output is readable text; --json writes one JSON object to stdout.
Text groups local gaps by reason and subject, showing counts and up to three examples;
JSON retains every recorded location.
Execution errors go to stderr; analysis diagnostics are included in the result. An abbreviated result looks like:
{
"schemaVersion": 3,
"complete": true,
"diagnostics": [],
"sourceFiles": ["src/source.ts"],
"testFiles": ["tests/source.test.ts"],
"scope": "focused",
"reasons": [
{
"testFile": "tests/source.test.ts",
"kind": "import",
"dependencyPath": ["tests/source.test.ts", "symbol:src/source.ts#a"]
}
],
"fallbackReasons": []
}scope describes the requested impact analysis:
| Value | Meaning |
|---|---|
focused |
Known static paths determined the returned files. |
partial |
Known paths are returned; relevant input, workset or configuration gaps remain. |
An empty testFiles is only meaningful together with scope and
complete and diagnostics. A partial empty list does not mean no tests were
affected. A successful exit means analysis ran, possibly with gaps; it says
nothing about project correctness. The caller owns any execution fallback. Exit code 1
indicates analysis/input failure and 2 indicates command-line usage errors.
Test discovery recognizes _test.go, test_*.py, *_test.py, JS/TS
*.test.* / *.spec.*, and JS/TS sources inside __tests__. Custom test filename
conventions are not discovered. Dependencies outside test directories are still
read so indirect imports can be followed.
- JS/TS: relative imports, named imports and aliases, re-exports, plain-string
requireandimport(), and declared external package dependencies. Local JSONtsconfig extends(including arrays) and explicit workspaceexports,main, andmodulesource entrypoints are resolved. Referenced submodule JSON configs are read from the selected snapshot without discovering child sources or tests. Captured conditional export alternatives become weak edges. Custom TS aliases, package-basedextends, JSONC config syntax, export patterns/arrays and missing generated entrypoints have no resolved target. Configuration parsing failures remain diagnostics. - Python: relative imports, named imports and package initialization. Absolute
imports resolve against proven search-path prefixes; catalog-only source-root
matches remain weak inference, even when unique. File-local variables, import
aliases, small literal loops, and roots built from
Path(__file__),resolve(),parent/parents[n], and/literal suffixes are recognized. Context follows statement order and respects binding shadowing.sys.path.insertcan establish a known prefix; append, conditional roots and deferred function bodies do not establish precedence over unknown paths. Known-stringimportlib.import_module()/__import__()targets are supported. Cwd-relative strings, external roots, unsupported path mutations invalidate known precedence. Unknown dynamic targets are omitted; unavailable captured roots and evaluation budget exhaustion remain diagnostics. Absolute imports with no repository match are treated as external. Import hooks, module caches, arbitrary function execution and cross-module side effects are outside this static model. - Go: repository module imports and implicit same-package dependencies, across
all source files regardless of build tags. Local
replacedirectives cause diagnostics. Results remain file paths, including for Go tests.
Configuration changes and unsupported resources retain scoped diagnostics. README Markdown/reStructuredText changes and changes limited to descriptive package fields are observations; package versions, names, exports, scripts and unknown fields are not exempt. Local parse, declaration and call-resolution gaps remain observations, including their snapshot, subject, location and outline counters where available. Overlapping declaration gaps broaden changed seeds to files; they do not make all candidates partial. Unresolved targets stay unknown. These gaps never add tests. Both old and new import graphs participate, preserving resolved dependencies removed by the diff.
Untracked embedded repositories (including linked worktrees) are outside the parent
repository input; ordinary untracked files remain included. Git-ignored untracked
files are excluded. Symlink, submodule and large-file content
identity follows snapshot. A submodule remains one gitlink entry in
changes; its sources, components and tests are not recursively included. Parent
imports into a gitlink root depend on that entry as an external package. A gitlink
change can select parent consumers, but never dependency-owned tests. Unchanged
non-source links/assets do not independently create analysis gaps; imports of
unavailable targets, source symlinks and changed resources remain diagnosed.
Files over 2 MiB are hashed without syntax parsing. Capture limits are 10,000 files
or 128 MiB of regular-file contents per repository; exceeding them fails rather
than silently truncating. The default deadline is
two minutes, configurable with --timeout. Syntax facts for identical content
are reused within a run; there is no persistent cache.
See the analysis design for implementation boundaries.
diff uses the shared repository preparation for each captured
version, including patch postimages. It reports the entire current component
catalog, even components with empty change lists. Removed components that own
changed source files retain snapshot: "before" metadata.
Each entry includes component (shared identity and language), root, products,
and detected packageTools, plus analysis-specific source/test lists, scope,
complete, and optional fallbackReasons. Component roots describe ownership;
cross-component consumers still participate in the dependency graph.
--base REF is an exact commit, never an implicit merge base. The target is the
working tree by default; --staged selects the index and --head REF selects a
commit. These target modes and --file are mutually exclusive. Working-tree
analysis includes non-ignored untracked files. No mode writes to the index.
Repeat --changed-file PATH to restrict changed seeds (matching either rename
side). The complete before/after snapshots and dependency graph remain available,
so unchanged consumers in another component can still appear in testFiles.
Granularity is automatic; there is no --impact option. Edits within known declarations
use symbol seeds plus a whole-module-import seed. Module-level edits, changed tests,
added/renamed files, and changes overlapping declaration gaps use file seeds. Go uses
package propagation. seed.basis explains the choice; seed precision is separate from
path confidence.
Path confidence follows the weakest edge: exact, strong, then weak. Native CodeGraph
candidate edges rank as weak while retaining their original value in relations.
Stronger paths win before shorter paths. Distance counts imports, calls and config
inheritance; ownership, package membership and export aliases count as zero. Distance
and confidence are ranking signals, not probabilities. Before/after edges are never
combined into one path. Changed existing files have an exact, zero-distance direct
change explanation; deleted files remain seeds, but are absent from affectedFiles.
JSON includes schemaVersion: 3, resolved base/head, input,
snapshot (SHA-256 of observed file contents), complete, and diagnostics.
Diagnostic codes are snapshot_incomplete, snapshot_changed, and
impact_uncertain; message describes the gap and path is optional. Impact
diagnostics additionally expose reason (such as missing_config,
boundary_unavailable, expansion_limit or configuration_change), relation,
version and line where available. Relation confidence is omitted for definite
static evidence, strong for strong inference, or weak for weak inference; basis
explains the inference. Native CodeGraph calls retain exact / candidate, relation ID and full location.
All known-target tiers participate in recommendations; unknown targets create no edge.
complete means no blocking input, workset or repository-resolution gaps in the requested
best-effort analysis. Local extraction gaps remain in observations without changing it.
It does not guarantee dependency coverage: even a focused, complete report with an
empty test list cannot prove that no tests are affected. Snapshot gaps are reported
even without test directories or changed files. Callers decide whether to run more tests.
Working tree/index contents are compared before and after analysis to detect concurrent changes. The digest is content identity, not an atomic snapshot. Callers must ensure the checked tree still matches the analyzed input.
Use repocli --version for build identity. Tagged releases publish fixed-version
macOS/Linux amd64/arm64 archives and SHA-256 checksums. Consumers should install
once outside validation, keep a pinned version, and check the JSON schema before
using a result. A missing CLI or unsupported schema is an analysis failure, never
evidence that no files were affected.
For complete regular-file snapshots, snapshot hashes paths in lexicographic order.
Each entry contributes UTF-8 byte length, :, path bytes, decimal content byte length,
:, then content bytes. Prefix the SHA-256 hex digest with sha256:. This framing
lets a consumer compare execution input without rerunning dependency analysis.
Incomplete snapshots additionally hash their sorted issue strings and must never
be treated as a complete regular-file identity.
Execution logging is shared by all commands; see execution logs.
Snapshot completeness and dependency-analysis completeness are separate: an internal
symlink or initialized dirty submodule can have a reliable content identity while
parent-repository expansion failures can still yield impact_uncertain, and parse failures
remain local observations. Patch reconstruction with symlinks,
submodules or streamed large files in the base is unsupported; use working-tree,
index or commit comparison instead. Imports without a captured target, including runtime built-ins and unknown package
specifiers, create neither edges nor unresolved-package diagnostics.