AnchorMap flags docs-to-code drift in TypeScript PRs before merge.
You do not need to install anything to react to the preview.
Start here:
- New unmapped anchor: fstepho/anchormap-h3-demo#3
- Passing baseline: fstepho/anchormap-h3-demo#2
- Stale mapping: fstepho/anchormap-h3-demo#4
- Degraded analysis: fstepho/anchormap-h3-demo#5
- Feedback issue: #5
If you only open one link, start with the new unmapped anchor PR. It is the shortest example of the review question: a spec-like statement appears, but no mapping exists for it yet. Use the passing baseline to compare that report with a clean policy pass.
- New unmapped anchor: a spec-like statement appears without a mapping.
- Passing baseline: the mapped docs/code slice passes policy.
- Stale mapping: a human mapping points to an anchor that is no longer observed.
- Degraded analysis: the report still renders, but analysis trust is reduced.
Useful reaction:
- Did you understand the problem?
- Did the PR report make sense?
- Would you try this on a TypeScript repo?
- What confused you?
Negative feedback is useful when it names the blocker.
In many TypeScript projects, product docs, API docs, specs, and code change separately.
During review, it is hard to see whether:
- a new requirement-like statement was added without code mapping;
- an old mapping points to something that no longer exists;
- a PR reduced traceability coverage;
- the report is still reliable enough to trust.
AnchorMap makes those cases visible in CI as local artifacts and a PR-readable Markdown report.
AnchorMap is a local deterministic CLI for surfacing docs-to-code drift between Markdown/YAML spec anchors and supported TypeScript repositories.
It shows what is observed in specs, what has been mapped explicitly by a human, what is structurally covered by supported local TypeScript edges, and whether the analysis is clean or degraded. AnchorMap is not a pruning system and is not a deletion-safety system.
A reference demo is available at fstepho/anchormap-h3-demo.
It applies AnchorMap to the src/ tree of
h3 and shows the core mapping and scan flow:
init, scaffold, selective anchor promotion, map, and scan --json.
The demo includes a 308-anchor scaffold draft, 3 promoted active anchors, 3 explicit mappings, analysis health with no degrading findings, a pretty-printed full scan output, and a short scan brief with the same status, coverage, anchors, findings, and interpretation vocabulary used by the reference runbooks.
The same demo repository also hosts the GitHub Action PR preview set: a merged
workflow base plus draft passing-policy, unmapped-anchor, stale-mapping, and
degraded-analysis scenario PRs. The preview uses
fstepho/anchormap-action@v0-preview.4 with anchormap@1.2.2 and uploads
GitHub workflow artifacts only. It does not create PR comments or upload source
to an AnchorMap service.
The public scenario workflows use the recommended CI gate mode:
fail-on-policy: "true". PR #2 should produce a successful GitHub check with
Policy decision: pass; PRs #3, #4, and #5 should produce failed GitHub checks
after the PR body summary, job summary, and workflow artifacts are available.
The PR body summary and job summary are the primary reading path. Logs and
artifacts are optional verification context.
AnchorMap is distributed as the public npm package anchormap.
npm install -g anchormapThe installed command is anchormap. The current support matrix is:
- Linux x86_64
- macOS arm64
- Node.js 22 or newer
Other platforms are outside the supported release contract.
Start with one existing TypeScript slice that contains at least one supported
public export. Replace src with the directory you want AnchorMap to inspect,
such as app or server:
mkdir -p .specify/specs
anchormap init --root src --spec-root .specify/specs
anchormap scaffold --output .specify/specs/scaffold.generated.md
anchormap scan --json > anchormap.scan.jsonThe first scan is a local traceability artifact. It does not read Git history, CI metadata, caches, network data, clocks, or environment variables as product truth.
The generated scaffold is scanned as draft spec content; promote selected anchors into active specs before treating them as approved traceability.
The npm package provides the anchormap CLI. The GitHub Action installs and
orchestrates that CLI in a workflow.
For preview testing, use the current pinned versions:
uses: fstepho/anchormap-action@v0-preview.4
with:
anchormap-version: "1.2.2"The Action preview uploads GitHub workflow artifacts only. It does not upload source code to an AnchorMap service, require a SaaS account, or create PR comments by default. See the GitHub Action setup guide and the copyable workflow examples in docs/examples/github-actions.
AnchorMap is intentionally narrow:
- one repository root, which is the current working directory where the command starts;
- one TypeScript product tree under
product_root; - one or more spec roots containing Markdown or YAML specs;
- product files are
.tsand.tsxfiles underproduct_root, excluding.d.ts; - supported local graph edges come from static TypeScript
importandexportdeclarations with relative string-literal specifiers or supported deterministic local alias specifiers from./tsconfig.json.
.tsx files are treated as syntax-only TypeScript product files. JSX is
accepted only as parser syntax in .tsx; AnchorMap does not interpret React,
component ownership, JSX runtime behavior, or framework conventions.
AnchorMap automatically reads ./tsconfig.json when it is present. The
supported alias subset is deliberately small: local relative extends, optional
compilerOptions.baseUrl, and compilerOptions.paths entries with exactly one
terminal /* key and exactly one terminal /* target under the repository
root. Aliases that target product_root are public local aliases and are
reported in scan --json as config.local_aliases; aliases that target the
repository root but leave product_root are used only to classify references
that leave the selected slice.
With that config, static import and export declarations such as
import { checkReadme } from "@/rules/readme" are treated as supported local
edges. A missing ./tsconfig.json, or a supported config chain without
effective paths, preserves relative-only graph behavior. A present
./tsconfig.json that is unreadable, invalid, cyclic, non-local, or outside
the supported deterministic alias subset makes scan and map fail with
repository error code 3.
The following are outside the current support boundary: monorepos, .js and
.jsx product files, declaration files as product files, TypeScript aliases
beyond the deterministic local subset above, package specifier resolution,
dynamic imports, require, project references, Node resolver conditions,
framework semantics, and repository-wide inference beyond the configured roots.
Run AnchorMap on an unmodified existing TypeScript codebase slice. No framework setup, no build integration, no tsconfig edits. AnchorMap reports what is deterministically traceable inside the slice, and marks references that leave that scope.
A slice is one configured product_root inside a larger repository. Common
examples are app, src, or server.
Start at the existing repository root:
mkdir -p .specify/specs
anchormap init --root app --spec-root .specify/specs
anchormap scan --json > anchormap.scan.jsonKeep the repository's original ./tsconfig.json. AnchorMap reads only the root
./tsconfig.json and local relative extends files. It does not read
framework configuration, package exports, project references, workspace
metadata, Git state, caches, network data, clocks, or environment variables as
resolver truth.
Interpret the first report as a slice boundary report:
files[*].supported_local_targetslists supported static references traced insideproduct_root;out_of_scope_static_edgemeans a supported static reference points to an existing file outside the selected slice;unresolved_static_edgemeans a supported local or alias reference could not be resolved inside the deterministic subset;- aliases outside
product_rootare not rendered inconfig.local_aliases, even when they are used internally to classify an outgoing reference.
These findings are analysis results, not proof of dead code or ownership. To
try a different slice, run init in a fresh checkout or replace
anchormap.yaml intentionally with a new product_root.
For a reference runbook against an existing codebase slice, see
demos/outline-reference. It uses
Outline's original tsconfig.json and presents the result as a degraded slice
boundary report, not as the primary no-install demo.
Start in a TypeScript mono-package repository:
.
├── .specify/
│ └── specs/
│ └── requirements.md
└── src/
├── index.ts
└── rules/
└── readme.ts
Example spec:
# DOC.README.PRESENT README presentExample product files:
// src/index.ts
import { checkReadme } from "./rules/readme";
export { checkReadme };// src/rules/readme.ts
export function checkReadme(path: string): boolean {
return path.endsWith("README.md");
}Initialize AnchorMap:
anchormap init --root src --spec-root .specify/specsThis creates anchormap.yaml:
version: 1
product_root: 'src'
spec_roots:
- '.specify/specs'
mappings: {}Add a human-approved mapping from an anchor to one or more seed files:
anchormap map --anchor DOC.README.PRESENT --seed src/index.tsRun the machine-readable scan:
anchormap scan --jsonscan --json writes one canonical JSON object to stdout on success. The root
keys are schema_version, config, analysis_health, observed_anchors,
stored_mappings, files, traceability_metrics, and findings.
Formatted for readability, the example above reports:
{
"schema_version": 5,
"config": {
"version": 1,
"product_root": "src",
"spec_roots": [".specify/specs"],
"ignore_roots": [],
"tsconfig_path": null,
"local_aliases": []
},
"analysis_health": "clean",
"observed_anchors": {
"DOC.README.PRESENT": {
"spec_path": ".specify/specs/requirements.md",
"mapping_state": "usable",
"source": {
"kind": "markdown_atx_heading",
"line": 1,
"column": 3,
"heading_level": 1
}
}
},
"stored_mappings": {
"DOC.README.PRESENT": {
"state": "usable",
"seed_files": ["src/index.ts"],
"reached_files": ["src/index.ts", "src/rules/readme.ts"]
}
},
"files": {
"src/index.ts": {
"covering_anchor_ids": ["DOC.README.PRESENT"],
"supported_local_targets": ["src/rules/readme.ts"]
},
"src/rules/readme.ts": {
"covering_anchor_ids": ["DOC.README.PRESENT"],
"supported_local_targets": []
}
},
"traceability_metrics": {
"summary": {
"product_file_count": 2,
"stored_mapping_count": 1,
"usable_mapping_count": 1,
"observed_anchor_count": 1,
"active_anchor_count": 1,
"draft_anchor_count": 0,
"covered_product_file_count": 2,
"uncovered_product_file_count": 0,
"directly_seeded_product_file_count": 1,
"single_cover_product_file_count": 2,
"multi_cover_product_file_count": 0
},
"anchors": {
"DOC.README.PRESENT": {
"seed_file_count": 1,
"direct_seed_file_count": 1,
"reached_file_count": 2,
"transitive_reached_file_count": 1,
"unique_reached_file_count": 2,
"shared_reached_file_count": 0
}
}
},
"findings": []
}For an existing TypeScript repo, scaffold can create a draft Markdown spec
from public exports in supported .ts and .tsx product files:
anchormap scaffold --output .specify/specs/scaffold.generated.mdThe generated file starts with:
<!-- anchormap: draft -->Draft anchors are visible in scan --json with mapping_state: "draft", but
they do not emit unmapped_anchor findings and they cannot be mapped. To
promote an anchor, add the human intent, remove the draft marker from the file
or move the selected anchor to an active spec file, then run anchormap map.
When scaffold is run again, anchors already present in current specs are
skipped and only new draft sections are written.
scan --json is the source artifact for local CI and PR workflows:
anchormap scan --json > anchormap.scan.jsonCurrent scans use schema v5, which adds closed source-location metadata for observed spec anchors. Source locations contain line and column numbers only; they do not include source text or snippets. Artifact commands that consume scan files accept supported schema v4 and v5 inputs.
Use check to evaluate a local policy against either a live scan or an
explicit scan artifact:
anchormap check --policy anchormap.policy.yaml --json
anchormap check --scan anchormap.scan.json --policy anchormap.policy.yaml --jsonThe supported policy file is a closed YAML object with version: 1 and
optional fail_on and thresholds sections:
version: 1
fail_on:
analysis_health: degraded
finding_kinds:
- untraced_product_file
thresholds:
min_covered_product_file_percent: 80
max_untraced_product_files: 0Policy failures are not technical errors. With --json, check writes a
stable PolicyResult JSON artifact to stdout and exits with code 5.
Compare two scan artifacts without Git:
anchormap diff --base base.scan.json --head head.scan.json --json > anchormap.diff.jsonExplain one anchor or one file from a scan artifact alone:
anchormap explain --anchor DOC.README.PRESENT --scan anchormap.scan.json --json
anchormap explain --file src/index.ts --scan anchormap.scan.json --jsonRender a stable Markdown report from explicit artifacts:
anchormap report --scan anchormap.scan.json --format markdown > anchormap.report.md
anchormap report --scan anchormap.scan.json --check anchormap.check.json --diff anchormap.diff.json --format markdown > anchormap.report.mdRender CI-native reports from explicit artifacts:
anchormap report --check anchormap.check.json --format junit > anchormap.junit.xml
anchormap report --scan anchormap.scan.json --format sarif > anchormap.sarif.jsonFor future SaaS ingestion, bundle can assemble explicit scan, check, diff,
and metadata inputs into one local JSON artifact without uploading anything.
diff, explain, report, and bundle are artifact-only. They do not read
Git, anchormap.yaml, repository source files, CI variables, network data,
caches, clocks, or environment variables as product truth. report serializes
the artifacts it is given; bundle validates and embeds the artifacts and the
explicit metadata it is given. Neither command proves that the inputs came from
the same run.
The current artifact workflow is deliberately local. AnchorMap does not provide upload, dashboard, GitHub App, source snippets, symbol observation, call graph, or implicit CI metadata inference.
AnchorMap exposes these commands:
anchormap init --root <path> --spec-root <path> [--spec-root <path> ...] [--ignore-root <path> ...]anchormap map --anchor <anchor_id> --seed <path> [--seed <path> ...] [--replace]anchormap scan [--json]anchormap scaffold --output <path>anchormap check --policy <path> [--scan <scan.json>] [--json]anchormap diff --base <base.scan.json> --head <head.scan.json> [--json]anchormap explain (--anchor <anchor_id> | --file <path>) --scan <scan.json> [--json]anchormap report --scan <scan.json> [--check <check.json>] [--diff <diff.json>] --format markdownanchormap report --check <check.json> --format junitanchormap report --scan <scan.json> [--check <check.json>] [--diff <diff.json>] --format sarifanchormap bundle --scan <scan.json> --check <check.json> --diff <diff.json> --metadata <metadata.json> --json
init creates ./anchormap.yaml once. map creates or replaces explicit human
mappings in ./anchormap.yaml. scan reads ./anchormap.yaml and the
configured repository inputs; it never writes to disk. scaffold creates one
draft Markdown file and never mutates ./anchormap.yaml. check applies a
local policy to either a live scan or an explicit scan artifact. diff
compares two explicit scan artifacts. explain reconstructs one anchor or file
view from a scan artifact. report renders Markdown, JUnit XML, or SARIF JSON
from explicit artifacts. bundle assembles explicit scan, check, diff, and
metadata inputs into one local JSON artifact.
Human-readable terminal output is not a stable contract. Use scan --json,
check --json, diff --json, explain --json, report --format markdown,
report --format junit, report --format sarif, and bundle --json for
stable outputs.
Exit-code overview:
0: success, including completed analyses that emit findings in JSON;1: write failure or internal failure;2:anchormap.yamlis missing, unreadable, invalid, or violates config rules;3: repository inputs outsideanchormap.yamlcannot be read, decoded, parsed, indexed, or otherwise inspected as required;4: unsupported command, option, option combination, policy, artifact path, artifact JSON, or artifact schema;5:checkpolicy failure after all technical preconditions succeeded.
For scan --json, check --json, diff --json, explain --json,
report --format sarif, and bundle --json, success writes JSON to stdout and
leaves stderr empty. For report --format markdown, success writes Markdown to
stdout and leaves stderr empty. For report --format junit, success writes XML
to stdout and leaves stderr empty. check --json with a policy failure also
writes JSON to stdout and exits 5. On technical exit codes 1 through 4,
stdout is empty and no machine result is emitted.
For scan and map, an invalid present ./tsconfig.json is a repository
input failure and exits with code 3. Argument errors still have priority over
repository inspection, and anchormap.yaml config errors still have priority
over other repository input errors.
anchormap.yaml is the only AnchorMap-owned persistent source of truth. It
stores stable config and explicit human mappings only; it does not store caches,
derived graph data, candidates, classifications, history, metrics, or
tsconfig.json aliases. Alias state from ./tsconfig.json is an observed input
rendered in scan --json as config.tsconfig_path and
config.local_aliases.
covering_anchor_ids reports structural reachability from usable mappings under
the supported local TypeScript rules. It is not ownership proof.
untraced_product_file means a product file was not reached by any usable
mapping when the analysis was clean enough to emit that finding. It does not
mean dead code, and it does not authorize removing the file.
analysis_health = clean means no degrading findings were emitted. It does not
mean every product file is mapped.
For the full machine contract, see the repository contract.
{ "compilerOptions": { "baseUrl": ".", "paths": { "@/*": ["src/*"] } } }