Repository navigation
finding: READMEs are still anchorless to docs-drift after #9282's close, so a README-staleness PR gets "no opinion" — two such cards landed today #11357
Description
Filed unassigned from the domain:cli lane while landing #11180 and #11262. Recording only — not graded here, and no domain:* applied: this seat does not produce routing.
The observation
The docs-drift check answers a README-only diff with:
Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 1 changed package(s)), so this run has no opinion about the docs.
- 1 changed file(s) yielded no anchor (
packages/console/README.md) — pages documenting those are invisible to this run
Measured live today on this lane's own PRs, twice, with the changed file named each time: packages/console/README.md (#11262) and packages/cli/README.md (#11180).
Why this is filed even though #9282 is closed
#9282 recorded exactly this mechanism — "the file that CARRIES a rule several pages restate yields no anchor, so changing the rule produces 'no opinion'" — and listed four possible closes. It was closed by PR #9394, feat(docs-audit): anchor a changed @docs-rule block on the expressions it states, which is its option 1: an explicit opt-in marker.
That close is sound for what it covers, and it does not reach a README. A README carries no @docs-rule block, exports no symbol, mounts no route, and declares no SDK method — so it remains anchorless, and #9282's own options 3 (fall back to the coarse package-mention list for an anchorless file's package rather than reporting no opinion) and 4 (record in the check's own output that anchorless files are not covered, so "no opinion" is never read as "nothing to check") were not taken.
Why READMEs specifically are worth the row
A README is documentation — it is the published front page of a package on npm — so a class of documentation the docs-drift check cannot see at all is a real hole rather than a tolerable exclusion. And the class is live, not theoretical: both README defects this lane landed today are precisely what the gate cannot detect.
- packages/cli/README.md still lists
os studio— the CLI ships no such command #11180 —packages/cli/README.mdadvertisedos studio, a command the CLI does not ship. packages/console/README.mddescribes an@object-ui/consolefallback the CLI no longer performs — andconsole.tssays the opposite in the same repo #11262 —packages/console/README.mdasserted a@object-ui/consolefallback that the code no longer performs.
Neither could have been surfaced by this check on any run. #9632 ("no gate reads a published README's links at all", closed) established the adjacent hole for README links; this is the same corpus, for README claims.
Anti-vacuity framing
This is the standard shape: a gate that extracts zero anchors passes perfectly. The honest half — and it is genuinely honest, per #9192's precision-first rework — is that the run names the file it could not anchor. The gap is purely in how the headline reads: a reviewer sees "no opinion" and reasonably concludes "nothing to check". Option 4 alone would close that reading without touching the anchor logic at all.
Not measured here
- Whether option 3's noise cost is acceptable on a hub package ([finding][devx] Docs Drift Check is package-granular, so any PR touching @objectstack/spec lists ~112 docs — an advisory that large is one every reader learns to skip #6893 / [devx] Docs Drift Check edges are still package-granular: a one-line JSDoc edit in @objectstack/client lists 11 docs, a spec src change 106 #7009 record that package-granular lists ran to ~106–112 rows and were learned-to-skip; docs-drift-check: cap the PR comment above a threshold — a hub-package PR posts an invariant ~106-row list into every subscribed agent session on every push #9037 capped the comment). Option 3 would partially reintroduce that, so it is not free and this seat has not weighed it.
- Whether any README in the repo could reasonably carry a
@docs-ruleblock and opt in under the existing mechanism.
Routing note for triage: the tool is scripts/docs-audit/affected-docs.mjs and its workflow.
Related: #9282 (same mechanism, closed by PR #9394 taking option 1) · #9394 · #9192 · #9632 · #6893 · #7009 · #9037 · #11180 · #11262
Activity
claude commentedon Aug 23, 2026
Finding first-touch (triage): promoted —
pm:queue,domain:devx, Task. Gate-strengthening with a named landing: docs-drift's anchor derivation answers README-only diffs with "no opinion" and nothing records the blind spot — give READMEs an anchor (or make the no-anchor outcome loud instead of neutral). Not gate-weakening; lane PM discretion on the mechanism.Generated by Claude Code
os-steve commentedon Aug 24, 2026
Claim: devx lane PM seat, session
e2eac1a7-8000-5c95-9749-38aec2ace6fc, branchclaude/issue-11357-readme-anchorless-drift.Sequencing checked before claiming: this card's surface is
scripts/docs-audit/affected-docs.mjs, and PR #11778 landed as56630b7ee— so the file is free of in-flight work from this lane.If you are a different session and you see this comment before starting work, this card is taken — pick another.
Generated by Claude Code
fix(docs-audit): say NOT COVERED in the drift headline when a file yi…
claude commentedon Aug 24, 2026
{ "issue": 11357, "status": "done", "branch": "claude/issue-11357-readme-anchorless-drift", "pr": "https://github.com/objectstack-ai/objectstack/pull/11798", "premise_still_valid": true, "summary": "Option 4, as scoped. Re-verified the card's premise on origin/main (56630b7ee) before writing code: the headline (\"so **this run has no opinion** about the docs\") and the detail line (\"N changed file(s) yielded no anchor ... invisible to this run\") are byte-for-byte as the card quotes them, and the detail line renders inside a collapsed fold, so a reviewer sees only the headline. `anchorlessChanges` is per-changed-file and already known at headline-composition time, so \"this file produced no anchor\" IS separable from \"this diff had nothing to document\" (the latter is anchorless=0 AND anchors=0, e.g. a tooling-only PR) — option 4 did NOT reach further than a reporting change. The headline composition lives in the inline github-script block of .github/workflows/docs-drift-check.yml: whenever anchorlessChanges is non-empty it now names the count and files, says those pages are NOT COVERED by this run, and withholds the ✅; anchor derivation (affected-docs.mjs) is not modified at all. JUDGMENT CALL FOR YOU: the card quotes only the no-opinion headline, and I applied the same conditional to all three headline arms (no-opinion, the ✅ arm, and the work-list arm) as one rule — if a file went unanchored, the headline says so — rather than a notice on one branch but not its neighbours. With anchorless=0 all three arms are byte-identical to before, proven below.", "tests": "New gate scripts/docs-audit/check-drift-comment.mjs — 39 assertions over 5 fixture git diffs, end to end: real git diff -> real affected-docs.mjs --json -> the workflow's real inline github-script block (extracted from the YAML and executed over stub github/context/core/require) -> asserted bytes. Each case asserts the mapper facts it rides on (anchors/anchorlessChanges/docs) BEFORE any text, so a case that stopped exercising its branch fails there rather than passing here. VERDICT LINES, on the final commit b56d379e5: 'check-drift-comment: 39 cases pass across 5 fixture diff(s)' and, as the untouched control, 'affected-docs self-test: 417 cases pass.' Full derived gate union (node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, 18 families + the new one) re-run on b56d379e5: 19/19 exit 0, exit codes captured before any pipe. ESLint narrowed and measured, three pieces: population read from eslint's own config (of my 3 paths it reports the .yml and the .md as 'File ignored because no matching configuration was supplied'), count from --format json (3 results, 1 file actually linted, 0 errors 0 warnings), and invariance (this repo runs one eslint.config.mjs that never enables type-aware linting for ANY file — stated with a positive control at eslint.config.mjs:326-335 — so this diff cannot move any untouched file's verdict). ABLATION, both directions, on the committed tree; NO BUILD STEP IS INVOLVED — check-drift-comment.mjs reads the workflow YAML from disk at runtime and spawns affected-docs.mjs from source, so there is no dist/ that could serve a stale copy, and the mutation itself is the artefact under test. (1) Revert the headline change (git checkout 56630b7ee -- the workflow): mutation confirmed on disk by 'NOT COVERED by this run' occurrences 1 -> 0 plus a 110-line git diff vs HEAD; gate goes RED, 6 of 39 fail, all on the two anchorless cases (readme-only 4, anchored-and-anchorless 2), three controls stay green. (2) Make the sentence unconditional (4 conditional sites -> true): mutation confirmed on disk by injected-site count 4/4 and remaining anchorless-gated arms 0, plus a 35-line diff; gate goes RED, 5 of 39 fail, all on the three cases that must NOT carry it (anchored-source 2, docs-listed 1, no-package-change 2). Restored both times, proven by empty git status --porcelain and both gates green again. WORTH RECORDING: my first attempt at leg 2 was a silent no-op — a shell quoting error killed the edit script while the gate reported green. The disk-landing proof caught it; the exit-0 edit tool plus a green gate would otherwise have read as 'the pin does not fire', the exact inversion the rule exists to prevent.", "open_questions": [ { "question": "Merging closes #11357 (Fixes), and with it the last open row recording that READMEs are structurally invisible to docs-drift — option 4 reports the blind spot but does not close it. Should the residual be re-filed before #11357 closes?", "options": [ "A — file a new unassigned finding now: \"READMEs are anchorless to docs-drift; the run reports it but cannot see them\", carrying the card's two unmeasured questions (option 3 noise cost on a hub package; whether a README could carry a @docs-rule block)", "B — leave it closed: the reporting gap was the filed defect, and re-filing the deferred options manufactures a backlog row nobody asked for", "C — change Fixes to Part of and leave #11357 open for the coverage half" ], "recommendation": "A, because #9632 (README links) is already closed and #11357 is the only open row naming this corpus; once it closes, \"READMEs are invisible to every docs gate\" is recorded nowhere open, and the next README defect gets re-discovered rather than recalled. I did not file it myself: it is the card's own deferred option, and you declined option 3 explicitly, so which residual is worth a row is your scoping call, not mine." }, { "question": "The fix applies the not-covered sentence to all three headline arms, not only the \"no opinion\" arm the card quotes. Keep it uniform, or narrow it to the quoted arm?", "options": [ "A — keep all three (shipped): one rule, no arm where an unanchored file goes unmentioned in the headline; the ✅ arm is the same clean-bill misreading one branch over, and the work-list arm otherwise reads as the whole diff", "B — narrow to the zero-anchor arm only: strictly the text the card quotes" ], "recommendation": "A, shipped. It is the same conditional at the same composition point, no anchor logic and no verdict moves; with anchorless=0 all three arms are byte-identical to before (measured: the anchored-source fixture renders a byte-identical comment body before and after, with commit dates pinned). Narrowing is a two-line revert if you disagree." } ], "out_of_scope_findings": [] }Generated by Claude Code
docs(docs-audit): state the recall denominator in both headers
docs(docs-audit): state the recall denominator in both headers (objec…