This track is independent of SPEC-131
Worth stating because the milestone otherwise reads as one long chain. Phase 1 extracts edges from path= attributes, which exist today. symbol= → resolved line range is only needed for --precise, which is not in this milestone. So this item can start on day one, in parallel with WORK-597.
Almost nothing new is being built
| Piece | State |
|---|
Single-pass git log --format=%at --name-only scan | packages/content/src/timestamps.ts:55 — the exact scan shape |
| Shallow-clone detection | timestamps.ts:37 — isShallowClone, already written |
| Git-root-relative path prefixing | timestamps.ts:123 — getGitRelativePrefix |
| Embedded-source edge extraction | the shared reader, read-file.ts |
A ranked report with --format json | plan status, the pattern to follow |
The refusals are load-bearing from the first commit (D5)
A shallow clone reports an empty result that is indistinguishable from a clean corpus. That is the failure this command cannot afford, because its output is already "nothing is very wrong" most of the time.
This phase will find almost nothing here, and that is the right order
The spec is explicit: embedded-source edges are the class whose extraction is already trustworthy, so they are the right one to establish the index, the scan and the output shape against. WORK-595 is where the corpus is actually reached.
Acceptance Criteria
- A
stale command is registered in packages/cli/src/commands/ — no such command exists today refrakt stale reports a ranked list of edges, ordered by commits to the target since the referrer last changed- The git scan is a single
git log --name-only pass over the repository, not one git log invocation per edge - The scan runs with
--full-history and no pathspec, so history simplification cannot drop commits from a file's history - A test pins a file whose simplified and full histories differ, asserting the scan reports the full one
- Embedded-source edges are extracted from
snippet, file-ref and expand path= attributes - An edge whose referrer is newer than every change to its target scores zero and is omitted
- Output is bounded by
--top, defaulting to 10, and each entry lists commit subjects for the target's changes --class, --min and --format json behave as specifiedrefrakt stale exits zero whatever it finds, including when every edge in the corpus is stale- A refusal (shallow clone, non-git tree) exits non-zero, distinguishing "could not measure" from "measured, nothing wrong"
- A shallow clone is detected and refused with a message naming
fetch-depth: 0 - A non-git working tree is refused with its own message, not treated as a clean corpus
- The report footer states each class's base rate — how many edges of that class were non-zero out of how many scanned
- The edge index is a shared module, with ranking as one reader of it and no query owning it
- A test fixture reproduces the measured case: a page referencing a file that has since taken N commits ranks above one referencing an unchanged file
- Docs state that a zero score is the absence of evidence of staleness, not evidence of freshness
site/content/docs/cli/cli-overview.md gains a row for stale — the page WORK-595 cites as its motivating instance of a stale command table
Approach
Adapt timestamps.ts's scan rather than rewriting it; the shallow-clone and git-root handling there are the parts most easily got wrong.
Pass --full-history, and do not take the existing call shape on trust. Found while measuring SPEC-136 D16's base rate, and it is the single easiest way to ship this item with quietly wrong numbers:
git log --name-only -- site/content/docs/cli/cli-overview.md
→ one commit, 2026-07-24
git log --full-history --name-only (no pathspec)
→ five commits, most recent 2026-09-10
A pathspec makes git apply history simplification. Both outputs are correct git answering different questions, but this command does arithmetic on those timestamps — the referrer's last-changed time is one half of every score — so a simplified history inflates every edge out of that file, and deflates every edge into it.
The failure is invisible: the report still renders, still ranks, still looks plausible. It is the spec's own subject turned on the spec's own instrument, which is why it gets a pinned test rather than a comment.
The commit subjects are the report's whole value. "11 commits" is a number; "generate breadcrumb and timeline positions from a declared index" is a person recognising that the page they wrote does not mention declared indexes. Do not economise on them.
Notes
This ranks. It never fails (D1). No exit code on findings, no build failure, no required check, not even an opt-in flag to make it failing.
The failure mode of getting that wrong is specific and fatal: the cheapest way to turn any edge green is to edit the referring page, so a gate would train people to make trivial documentation edits to clear it — actively destroying the signal it measures. The measure only survives if nothing depends on it being zero.
Two edge classes are rejected, not deferred: plan source= edges (99% base rate) and derived rune pages (96%). Both are in the spec's table with their measurements; do not re-add them.
References
- SPEC-136 — the measure, D1 (ranks, never fails), D5 (refusals), D9 (
--top and commit subjects), D12 (the index as a shared module) - WORK-595 — the classes that reach the rest of the corpus
- WORK-596 — the
touching query this index must be able to answer packages/content/src/timestamps.ts — the single-pass scan and isShallowClone, both adapted rather than rewrittenpackages/runes/src/lib/read-file.ts — the shared reader carrying path=
Resolution
Completed: 2026-09-24
Branch: claude/v0-37-0-review-vqpl41 PR: refrakt-md/refrakt#648 (batched with WORK-590)
What was done
packages/content/src/edges/ (new) — extract.ts, git-scan.ts and index.ts. The index is addressable in both directions; ranking is one reader of it.packages/cli/src/commands/stale.ts (new) — refrakt stale, registered in bin.ts with --top, --class, --min, --format.packages/content/test/edges.test.ts — 21 tests.site/content/docs/cli/cli-overview.md gains the row.
Notes
- Built for
touching even though WORK-596 ships it. A walk-score-print function cannot answer a query that runs target → referrer and without git. edgesTouching already exists and is tested, including for a path with no commit history at all. - The
--full-history test needed a real divergence. My first fixture reported 2 commits both ways — it would have passed while proving nothing. A merge using -s ours (discarding the side's version) is the construction that diverges: simplified 2, scan 3. Worth knowing if this fixture is ever edited. - The shallow-clone refusal was exercised for real. This container is a shallow clone, so the first live run refused with
fetch-depth: 0 before the test existed. git fetch --unshallow was needed to see any ranking at all. - Fenced invocations must be excluded. Counting
{% snippet %} inside a ```markdoc fence inflated this repo's index from 6 real edges to 10. Reuses WORK-588's Markdown masker rather than re-deriving fence tracking. - Test fixtures need a fake clock. Commits created in the same wall-clock second make the measure — which counts commits strictly after the referrer's change — see nothing. Four tests failed on this before the clock was added.
- This phase finds almost nothing, as predicted. One edge, base rate 1/6. Embedded source reaches a fraction of the corpus; WORK-595 is where it is actually reached.
Verification
4858 tests pass. Live run against this repository produces one genuine finding.