Relationships
Branches 1
Context
The first real run of refrakt stale over this repository put two pages near the top of the report that are not stale in any sense worth acting on:
site/content/docs/migration/v0.14.0.md— a migration guide for a version already shipped, scoring 12 againstpackages/lumina/src/tokens.ts. It is a record of what was true at v0.14.0. Updating it as the tokens move would destroy the only thing it is for.site/content/releases.md— generated byscripts/generate-changelog.mjson every release, and the target of a described link from the contributing guide scoring 39. "Commits since the page last changed" here measures the release cadence, not a divergence between claim and code.
The first instinct was to special-case docs/migration/ and blog/ in the extractor. That is wrong for a reason that has nothing to do with these two pages: every project using refrakt has a different folder structure, and a tool that compiles in one repository's conventions is a tool built for that repository.
So the exclusion has to be project configuration. The open question is its shape — and SPEC-136, plus WORK-595's Notes, warn specifically against the obvious answer: "an opt-out is both the easy answer and the easy way to make the feature disappear one page at a time."
Decision
Option 4. refrakt.config.json grows an optional stale section:
"stale": { "archival": ["site/content/docs/migration/**", "site/content/releases.md"], "generated": ["CHANGELOG.md", "site/content/releases.md"] }
Both lists default to empty, so an unconfigured project excludes nothing. The content roots the report scans come from the sites the project already declares, so the scan needs no configuration at all in the common case.
Two further pieces make the split hold rather than decay into option 3:
- Whatever the lists remove is counted in the report footer and in both the JSON and MCP payloads (
excluded: { archivalPages, generatedTargets }). An exclusion list that hides its own effect is how the report goes quiet. - An archival page is skipped before extraction, not filtered after it, so it also stops answering the
touchingquery. "What documents this file" must not answer with a record of what was once true.
Rationale
The two lists exclude opposite sides of an edge, for opposite reasons, and neither reason is "this finding is noisy":
archivalnames pages that are historical records — correct as written, and wrong to update when the code moves on. An edge out of one can only ever be a false positive, for the whole life of the page.generatednames targets that change by construction. The commit count against them is not noisy, it is meaningless: it measures a generator.
That is the property option 3 lacks. A single exclude list asks "should this be hidden?", which has an easy yes for anything irritating. Two named lists ask "which kind of structurally wrong thing is this?", which has no answer at all for a page that is merely noisy — and a page that is merely noisy has two real remedies that stay available: fix the extraction rule that found the edge, or narrow the page's subject with documents: in its frontmatter, which replaces every inferred edge for that page with a declared one.
This does not reopen WORK-595's "do not add a frontmatter opt-out yet". That note is about suppressing the prose class on a page the class got right; these two entries are pages the class got structurally wrong, which is the "genuinely miscategorised" case the same note reserves.
The glob subset is deliberately small — *, **, ?, and a trailing / for a directory — with no brace expansion and no negation. A pattern language that can express "everything except" invites the exclusion list to grow into a second content model.
Consequences
StaleConfigjoins@refrakt-md/types,refrakt.config.schema.jsonand the generated configuration reference; the schema drift guard covers it.refrakt stalenow refuses rather than guessing when there is norefrakt.config.json— it reads the file to learn where content lives. "I could not look" and "nothing to report" stay different answers.- The MCP tool reads the same settings, so an agent's
touchingquery and a maintainer's survey exclude the same things. No second source of truth. - Removing the
site/contentdefault means a project whose content lives elsewhere works without a flag, and a multi-site project is scanned whole. - The counted-exclusions footer is the thing to watch. If those numbers climb while the report shrinks, the lists have become option 3 by another name.
References
- SPEC-136 — the staleness measure and its failure modes
- WORK-595 — the Notes this decision answers
- WORK-596 — the
touchingquery an archival page must not answer packages/content/src/edges/exclude.ts— the implementation