Skip to content

fix(check-doc-anchors): spell the heading-ids remedy so it cannot be read as a watch-hint declaration - #13775

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-13449-doc-anchors-remedy-literal
Aug 31, 2026
Merged

fix(check-doc-anchors): spell the heading-ids remedy so it cannot be read as a watch-hint declaration#13775
os-project-manager merged 1 commit into
mainfrom
claude/issue-13449-doc-anchors-remedy-literal

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#13449

check:doc-anchors printed a remedy command whose module specifier was a quoted
relative literal. The dispatch watch-hint extractor reads quoted path-shaped literals
out of a gate's module body and resolves the relative ones against the directory of the
file that wrote them — so this gate, which lives in scripts/, declared a path with its
own prefix doubled. That lead named a file that has never existed on this tree, and it
was pasted into every dispatch prompt whose file surface brushed the family.

Neither half was wrong on its own: the remedy is correct for its reader, the extractor is
correct about a declaration. The fabrication came from one being read as the other, so the
repair is local to the producer — spell the path so it cannot be read as a declaration.
Per the triage ruling, the extractor is not touched (upstream measured the tightening
at 8.6 real populations deleted per fabrication removed), and nothing in this delivery
touches scripts/pm/dispatch-gates.mjs.

The change

One line in scripts/check-doc-anchors.mjs, plus the rationale comment above it:

- " node -e \"import('./scripts/check-doc-anchors.mjs').then(m=>…+ " node -e \"import(process.cwd()+'/scripts/check-doc-anchors.mjs').then(m=>…

The leading / inside the quotes is the whole mechanism. extractWatchHints' admission
test is /^[\w.@][\w.@/*-]*$/ — a literal must begin with a word character, . or @
so an absolute path is refused outright, before the module-relative resolve can ever run.
process.cwd() is what keeps the command runnable verbatim from the repo root.

A2.1 — the defect was still live at my ref

Re-confirmed at c42bc8ee6 (my branch point), not inherited from the dispatch order:

git ls-files 'scripts/scripts/*' -> 0
git ls-files 'scripts/*' -> 299 (209 at the top level) <- the control

The zero is a true zero, so #13312's closing PR did not absorb this. The card correctly
gets a PR rather than a close.

A2.2 — census: 1 site, and it is the only one

Two independent sweeps, both run through the real extractor rather than by eye.

Fleet sweep — every tracked gate/tooling script under a scripts/ directory
(333 files), classifying each extracted hint that reaches nothing in the tree, and
flagging the doubled-prefix signature (a dead hint that becomes a real path when one copy
of the writer's own directory is stripped):

scripts scanned: 333
hints total: 1500 dead (reach nothing): 736 DOUBLED-PREFIX class: 1
scripts/check-doc-anchors.mjs
fabricated: scripts/scripts/check-doc-anchors.mjs
real path : scripts/check-doc-anchors.mjs

Same-file sweep — every quoted module-relative literal in check-doc-anchors.mjs:
4 of them, and the other 3 are genuine sibling import specifiers
(./import-prerequisite.mjs, ./check-adr-links.mjs, ./invoked-as.mjs), which the
extractor already refuses as single-segment siblings. Line 459 was the only remedy string.

1 hit, fixed; 0 identical second sites left behind. After the change the same fleet
sweep reads hints total: 1499 · dead: 735 · DOUBLED-PREFIX class: 0 — exactly one
hint removed and nothing else moved.

The remaining 735 dead hints are the fixture-constant class #13312 owns, a different
cause; they are untouched here.

A2.3 — reverse verification: the extractor emitted it before and does not after

Run against the committed implementation, mutating the tree back to the pre-fix spelling
and restoring it, with the mutation proved on disk by anchored grep -c on both the
injected and the removed text, and the restore proved by observed state (empty
git diff HEAD plus a blob-hash comparison against the HEAD blob) rather than by an exit
code. The extractor is a pure string function over the source file as read from disk, so
there is no build artifact between the mutation and the reading.

HEAD blob for target: cb30a9f975574aa65d251be2f52f33c6c1eac4a9
=== LEG 1: MUTATE (pre-fix relative spelling put back) ===
injected import('./scripts/... : 1
removed process.cwd()+'/... : 0
blob hash now vs HEAD blob : 4d12eae58b70… (HEAD cb30a9f97557…)
extractor reading on the MUTATED tree:
HINT COUNT: 4
HINT "ARCHITECTURE.md/**"
HINT "README.md/**"
HINT "content/**"
HINT "scripts/scripts/check-doc-anchors.mjs"
FABRICATED PRESENT: true
=== LEG 2: RESTORE (git checkout HEAD -- ABSOLUTE_PATH) ===
git diff HEAD (must be empty) : EMPTY
blob hash == HEAD blob : cb30a9f97557… == cb30a9f97557…
injected import('./scripts/... : 0
restored process.cwd()+'/... : 1
extractor reading on the RESTORED tree:
HINT COUNT: 3
HINT "ARCHITECTURE.md/**"
HINT "README.md/**"
HINT "content/**"
FABRICATED PRESENT: false

Direction observed: the fabricated hint disappears and the three real declarations stay
content/**, README.md/**, ARCHITECTURE.md/** are byte-identical before and after,
and nothing new is added. That last half matters: a spelling that merely moved the literal
(say, a bare scripts/check-doc-anchors.mjs) would have been admitted as a true hint
and quietly given this gate a population it does not read. This one declares nothing.

The same result is confirmed by the tool's own suite: dispatch-gates.mjs --self-test
reads this file live and asserts that the doc-anchors family still reaches
content/docs/deployment/cli.mdx, still claims README.md and ARCHITECTURE.md, and
still claims neither instruction file. 1017 cases pass.

One more observed side-effect, in the right direction: the rationale comment added above
the line spells the relative path in prose, and the module-body sweep confirms comment
masking removes it — the file's RAW text has 4 module-relative literals, its masked module
body has 3.

A2.4 — the remedy still runs verbatim from the repo root

The gate was driven to its failure branch against a throwaway fixture root so the remedy
is the text a human actually sees:

 The heading id is computed with `github-slugger`, the same package
fumadocs-core uses to render the page, so what this gate says the anchor is
is what the site says it is. Print a page's real ids with:
node -e "import(process.cwd()+'/scripts/check-doc-anchors.mjs').then(m=>console.log(m.headingIds(require('node:fs').readFileSync('FILE','utf8')).join('\n')))"

(FILE above stands for the literal placeholder the gate prints, which the reader replaces.)

Pasted verbatim at the repo root with the placeholder filled in:

$ node -e "import(process.cwd()+'/scripts/check-doc-anchors.mjs').then(m=>console.log(m.headingIds(require('node:fs').readFileSync('content/docs/index.mdx','utf8')).join('\n')))"
objectstack-documentation
start-here
platform-modules
protocol--reference
EXIT=0

Byte-identical to the same run under the old spelling — diff of the two captures is
empty. Readability does not regress: the command is one token longer and still a single
paste.

Gates

Family derived after the final commit with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (no paths passed —
the script takes its own change set; it reported 1 path(s) vs merge base c42bc8ee6).
Every exit code below was captured before any pipe, and each verdict is the gate's own
printed line. All runs are on the final commit fd2ea143f.

Path-derived family (13):

gateexitverdict
pnpm check:agent-test-spelling0GREEN
pnpm check:bash32-floor0GREEN
pnpm check:cli-command-ids0GREEN
pnpm check:cross-package-test-inputs0GREEN
pnpm check:doc-anchors0GREEN — check-doc-anchors: 287 internal #fragment link(s) across 409 source file(s) all resolve to a real heading
pnpm check:entry-guard0GREEN
pnpm check:parse-guard0GREEN
pnpm check:pnpm-filter-targets0GREEN
pnpm check:watch-hint-literal0GREEN — 34 declaration(s) across 4 rostered name(s) … no unrostered spelling of the idiom in the tree
node scripts/check-ci-filter-parity.mjs0GREEN
node scripts/check-cross-package-test-inputs.mjs0GREEN
node scripts/check-shard-attestation.mjs0GREEN
node scripts/check-test-completeness.mjs3NOT MEASUREDPREREQUISITE NOT MET; it grades a saved turbo run test log and none was named. Its own text says exit 3 is not a finding.

Convention-triggered (editing a gate script), both named by the derivation:

gateexitverdict
node scripts/pm/bare-root-worklist.mjs --self-test0GREEN — 51 live row(s), 43 unreachable as spelled, 43 recorded verdict(s) — none stale, none missing, none contradicted
pnpm check:pm-dispatch-gates0GREEN — dispatch-gates self-test: 1017 cases pass.

Beyond the derived list, the gate script's own suite plus the consumers that read this
file by name (the "which tests test this script" half the path derivation cannot answer):

gateexitverdict
pnpm check:nul-bytes0GREEN — scanned 7578 text file(s) … no raw ASCII control bytes
pnpm check:published-readme-links0GREEN (imports headingIds from the edited file)
pnpm check:docs-single-h10GREEN
pnpm check:docs-image-tag0GREEN
node scripts/check-self-test-wired.mjs (+ --self-test)0GREEN
node scripts/check-self-test-workflow-commands.mjs (+ --self-test)0GREEN
pnpm lint (repo-wide eslint . --no-inline-config, not narrowed)0GREEN — 2m02s

Repo-wide ESLint was run in full rather than narrowed, so no narrowing is being declared.

Changeset

skip-changeset, applied at PR creation. Check Changeset is path-blind — it counts the
changesets a PR adds against the merge base and fails on zero; its only exemptions are
the skip-changeset label and the Changesets release branch. This PR publishes nothing:
the root manifest is private, and no published package's files field escapes its own
directory, so a repo-root scripts/ file ships in no tarball.

Out of scope, deliberately


Generated by Claude Code

…read as a watch-hint declaration
The remedy line printed for a broken anchor is a command for a human to paste
at the repo root, and it carried the module specifier as a quoted relative
literal `'./scripts/check-doc-anchors.mjs'`. The dispatch watch-hint extractor
reads quoted path-shaped literals in a gate's module body and resolves the
relative ones against the writer's own directory, so this gate — which lives in
scripts/ — declared a path that has never existed on this tree, and that lead
was pasted into every dispatch prompt the family matched.
Neither half was wrong on its own; the fabrication came from prose being read as
a declaration. Tightening the reader was measured and refused upstream, so the
producer spells the path absolutely instead: process.cwd() + a leading-slash
literal, which the extractor's admission test refuses outright while the command
stays runnable verbatim from the repo root.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
fix(check-doc-anchors): spell the heading-ids remedy so it cannot be read as a watch-hint declaration by os-project-manager · Pull Request #13775 · objectstack-ai/objectstack · GitHub
Skip to content

fix(check-doc-anchors): spell the heading-ids remedy so it cannot be read as a watch-hint declaration - #13775

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-13449-doc-anchors-remedy-literal
Aug 31, 2026
Merged

fix(check-doc-anchors): spell the heading-ids remedy so it cannot be read as a watch-hint declaration#13775
os-project-manager merged 1 commit into
mainfrom
claude/issue-13449-doc-anchors-remedy-literal

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#13449

check:doc-anchors printed a remedy command whose module specifier was a quoted
relative literal. The dispatch watch-hint extractor reads quoted path-shaped literals
out of a gate's module body and resolves the relative ones against the directory of the
file that wrote them — so this gate, which lives in scripts/, declared a path with its
own prefix doubled. That lead named a file that has never existed on this tree, and it
was pasted into every dispatch prompt whose file surface brushed the family.

Neither half was wrong on its own: the remedy is correct for its reader, the extractor is
correct about a declaration. The fabrication came from one being read as the other, so the
repair is local to the producer — spell the path so it cannot be read as a declaration.
Per the triage ruling, the extractor is not touched (upstream measured the tightening
at 8.6 real populations deleted per fabrication removed), and nothing in this delivery
touches scripts/pm/dispatch-gates.mjs.

The change

One line in scripts/check-doc-anchors.mjs, plus the rationale comment above it:

- " node -e \"import('./scripts/check-doc-anchors.mjs').then(m=>…+ " node -e \"import(process.cwd()+'/scripts/check-doc-anchors.mjs').then(m=>…

The leading / inside the quotes is the whole mechanism. extractWatchHints' admission
test is /^[\w.@][\w.@/*-]*$/ — a literal must begin with a word character, . or @
so an absolute path is refused outright, before the module-relative resolve can ever run.
process.cwd() is what keeps the command runnable verbatim from the repo root.

A2.1 — the defect was still live at my ref

Re-confirmed at c42bc8ee6 (my branch point), not inherited from the dispatch order:

git ls-files 'scripts/scripts/*' -> 0
git ls-files 'scripts/*' -> 299 (209 at the top level) <- the control

The zero is a true zero, so #13312's closing PR did not absorb this. The card correctly
gets a PR rather than a close.

A2.2 — census: 1 site, and it is the only one

Two independent sweeps, both run through the real extractor rather than by eye.

Fleet sweep — every tracked gate/tooling script under a scripts/ directory
(333 files), classifying each extracted hint that reaches nothing in the tree, and
flagging the doubled-prefix signature (a dead hint that becomes a real path when one copy
of the writer's own directory is stripped):

scripts scanned: 333
hints total: 1500 dead (reach nothing): 736 DOUBLED-PREFIX class: 1
scripts/check-doc-anchors.mjs
fabricated: scripts/scripts/check-doc-anchors.mjs
real path : scripts/check-doc-anchors.mjs

Same-file sweep — every quoted module-relative literal in check-doc-anchors.mjs:
4 of them, and the other 3 are genuine sibling import specifiers
(./import-prerequisite.mjs, ./check-adr-links.mjs, ./invoked-as.mjs), which the
extractor already refuses as single-segment siblings. Line 459 was the only remedy string.

1 hit, fixed; 0 identical second sites left behind. After the change the same fleet
sweep reads hints total: 1499 · dead: 735 · DOUBLED-PREFIX class: 0 — exactly one
hint removed and nothing else moved.

The remaining 735 dead hints are the fixture-constant class #13312 owns, a different
cause; they are untouched here.

A2.3 — reverse verification: the extractor emitted it before and does not after

Run against the committed implementation, mutating the tree back to the pre-fix spelling
and restoring it, with the mutation proved on disk by anchored grep -c on both the
injected and the removed text, and the restore proved by observed state (empty
git diff HEAD plus a blob-hash comparison against the HEAD blob) rather than by an exit
code. The extractor is a pure string function over the source file as read from disk, so
there is no build artifact between the mutation and the reading.

HEAD blob for target: cb30a9f975574aa65d251be2f52f33c6c1eac4a9
=== LEG 1: MUTATE (pre-fix relative spelling put back) ===
injected import('./scripts/... : 1
removed process.cwd()+'/... : 0
blob hash now vs HEAD blob : 4d12eae58b70… (HEAD cb30a9f97557…)
extractor reading on the MUTATED tree:
HINT COUNT: 4
HINT "ARCHITECTURE.md/**"
HINT "README.md/**"
HINT "content/**"
HINT "scripts/scripts/check-doc-anchors.mjs"
FABRICATED PRESENT: true
=== LEG 2: RESTORE (git checkout HEAD -- ABSOLUTE_PATH) ===
git diff HEAD (must be empty) : EMPTY
blob hash == HEAD blob : cb30a9f97557… == cb30a9f97557…
injected import('./scripts/... : 0
restored process.cwd()+'/... : 1
extractor reading on the RESTORED tree:
HINT COUNT: 3
HINT "ARCHITECTURE.md/**"
HINT "README.md/**"
HINT "content/**"
FABRICATED PRESENT: false

Direction observed: the fabricated hint disappears and the three real declarations stay
content/**, README.md/**, ARCHITECTURE.md/** are byte-identical before and after,
and nothing new is added. That last half matters: a spelling that merely moved the literal
(say, a bare scripts/check-doc-anchors.mjs) would have been admitted as a true hint
and quietly given this gate a population it does not read. This one declares nothing.

The same result is confirmed by the tool's own suite: dispatch-gates.mjs --self-test
reads this file live and asserts that the doc-anchors family still reaches
content/docs/deployment/cli.mdx, still claims README.md and ARCHITECTURE.md, and
still claims neither instruction file. 1017 cases pass.

One more observed side-effect, in the right direction: the rationale comment added above
the line spells the relative path in prose, and the module-body sweep confirms comment
masking removes it — the file's RAW text has 4 module-relative literals, its masked module
body has 3.

A2.4 — the remedy still runs verbatim from the repo root

The gate was driven to its failure branch against a throwaway fixture root so the remedy
is the text a human actually sees:

 The heading id is computed with `github-slugger`, the same package
fumadocs-core uses to render the page, so what this gate says the anchor is
is what the site says it is. Print a page's real ids with:
node -e "import(process.cwd()+'/scripts/check-doc-anchors.mjs').then(m=>console.log(m.headingIds(require('node:fs').readFileSync('FILE','utf8')).join('\n')))"

(FILE above stands for the literal placeholder the gate prints, which the reader replaces.)

Pasted verbatim at the repo root with the placeholder filled in:

$ node -e "import(process.cwd()+'/scripts/check-doc-anchors.mjs').then(m=>console.log(m.headingIds(require('node:fs').readFileSync('content/docs/index.mdx','utf8')).join('\n')))"
objectstack-documentation
start-here
platform-modules
protocol--reference
EXIT=0

Byte-identical to the same run under the old spelling — diff of the two captures is
empty. Readability does not regress: the command is one token longer and still a single
paste.

Gates

Family derived after the final commit with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (no paths passed —
the script takes its own change set; it reported 1 path(s) vs merge base c42bc8ee6).
Every exit code below was captured before any pipe, and each verdict is the gate's own
printed line. All runs are on the final commit fd2ea143f.

Path-derived family (13):

gateexitverdict
pnpm check:agent-test-spelling0GREEN
pnpm check:bash32-floor0GREEN
pnpm check:cli-command-ids0GREEN
pnpm check:cross-package-test-inputs0GREEN
pnpm check:doc-anchors0GREEN — check-doc-anchors: 287 internal #fragment link(s) across 409 source file(s) all resolve to a real heading
pnpm check:entry-guard0GREEN
pnpm check:parse-guard0GREEN
pnpm check:pnpm-filter-targets0GREEN
pnpm check:watch-hint-literal0GREEN — 34 declaration(s) across 4 rostered name(s) … no unrostered spelling of the idiom in the tree
node scripts/check-ci-filter-parity.mjs0GREEN
node scripts/check-cross-package-test-inputs.mjs0GREEN
node scripts/check-shard-attestation.mjs0GREEN
node scripts/check-test-completeness.mjs3NOT MEASUREDPREREQUISITE NOT MET; it grades a saved turbo run test log and none was named. Its own text says exit 3 is not a finding.

Convention-triggered (editing a gate script), both named by the derivation:

gateexitverdict
node scripts/pm/bare-root-worklist.mjs --self-test0GREEN — 51 live row(s), 43 unreachable as spelled, 43 recorded verdict(s) — none stale, none missing, none contradicted
pnpm check:pm-dispatch-gates0GREEN — dispatch-gates self-test: 1017 cases pass.

Beyond the derived list, the gate script's own suite plus the consumers that read this
file by name (the "which tests test this script" half the path derivation cannot answer):

gateexitverdict
pnpm check:nul-bytes0GREEN — scanned 7578 text file(s) … no raw ASCII control bytes
pnpm check:published-readme-links0GREEN (imports headingIds from the edited file)
pnpm check:docs-single-h10GREEN
pnpm check:docs-image-tag0GREEN
node scripts/check-self-test-wired.mjs (+ --self-test)0GREEN
node scripts/check-self-test-workflow-commands.mjs (+ --self-test)0GREEN
pnpm lint (repo-wide eslint . --no-inline-config, not narrowed)0GREEN — 2m02s

Repo-wide ESLint was run in full rather than narrowed, so no narrowing is being declared.

Changeset

skip-changeset, applied at PR creation. Check Changeset is path-blind — it counts the
changesets a PR adds against the merge base and fails on zero; its only exemptions are
the skip-changeset label and the Changesets release branch. This PR publishes nothing:
the root manifest is private, and no published package's files field escapes its own
directory, so a repo-root scripts/ file ships in no tarball.

Out of scope, deliberately


Generated by Claude Code

…read as a watch-hint declaration
The remedy line printed for a broken anchor is a command for a human to paste
at the repo root, and it carried the module specifier as a quoted relative
literal `'./scripts/check-doc-anchors.mjs'`. The dispatch watch-hint extractor
reads quoted path-shaped literals in a gate's module body and resolves the
relative ones against the writer's own directory, so this gate — which lives in
scripts/ — declared a path that has never existed on this tree, and that lead
was pasted into every dispatch prompt the family matched.
Neither half was wrong on its own; the fabrication came from prose being read as
a declaration. Tightening the reader was measured and refused upstream, so the
producer spells the path absolutely instead: process.cwd() + a leading-slash
literal, which the extractor's admission test refuses outright while the command
stays runnable verbatim from the repo root.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(check-doc-anchors): spell the heading-ids remedy so it cannot be read as a watch-hint declaration by os-project-manager · Pull Request #13775 · objectstack-ai/objectstack · GitHub
Skip to content

fix(check-doc-anchors): spell the heading-ids remedy so it cannot be read as a watch-hint declaration - #13775

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-13449-doc-anchors-remedy-literal
Aug 31, 2026
Merged

fix(check-doc-anchors): spell the heading-ids remedy so it cannot be read as a watch-hint declaration#13775
os-project-manager merged 1 commit into
mainfrom
claude/issue-13449-doc-anchors-remedy-literal

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#13449

check:doc-anchors printed a remedy command whose module specifier was a quoted
relative literal. The dispatch watch-hint extractor reads quoted path-shaped literals
out of a gate's module body and resolves the relative ones against the directory of the
file that wrote them — so this gate, which lives in scripts/, declared a path with its
own prefix doubled. That lead named a file that has never existed on this tree, and it
was pasted into every dispatch prompt whose file surface brushed the family.

Neither half was wrong on its own: the remedy is correct for its reader, the extractor is
correct about a declaration. The fabrication came from one being read as the other, so the
repair is local to the producer — spell the path so it cannot be read as a declaration.
Per the triage ruling, the extractor is not touched (upstream measured the tightening
at 8.6 real populations deleted per fabrication removed), and nothing in this delivery
touches scripts/pm/dispatch-gates.mjs.

The change

One line in scripts/check-doc-anchors.mjs, plus the rationale comment above it:

- " node -e \"import('./scripts/check-doc-anchors.mjs').then(m=>…+ " node -e \"import(process.cwd()+'/scripts/check-doc-anchors.mjs').then(m=>…

The leading / inside the quotes is the whole mechanism. extractWatchHints' admission
test is /^[\w.@][\w.@/*-]*$/ — a literal must begin with a word character, . or @
so an absolute path is refused outright, before the module-relative resolve can ever run.
process.cwd() is what keeps the command runnable verbatim from the repo root.

A2.1 — the defect was still live at my ref

Re-confirmed at c42bc8ee6 (my branch point), not inherited from the dispatch order:

git ls-files 'scripts/scripts/*' -> 0
git ls-files 'scripts/*' -> 299 (209 at the top level) <- the control

The zero is a true zero, so #13312's closing PR did not absorb this. The card correctly
gets a PR rather than a close.

A2.2 — census: 1 site, and it is the only one

Two independent sweeps, both run through the real extractor rather than by eye.

Fleet sweep — every tracked gate/tooling script under a scripts/ directory
(333 files), classifying each extracted hint that reaches nothing in the tree, and
flagging the doubled-prefix signature (a dead hint that becomes a real path when one copy
of the writer's own directory is stripped):

scripts scanned: 333
hints total: 1500 dead (reach nothing): 736 DOUBLED-PREFIX class: 1
scripts/check-doc-anchors.mjs
fabricated: scripts/scripts/check-doc-anchors.mjs
real path : scripts/check-doc-anchors.mjs

Same-file sweep — every quoted module-relative literal in check-doc-anchors.mjs:
4 of them, and the other 3 are genuine sibling import specifiers
(./import-prerequisite.mjs, ./check-adr-links.mjs, ./invoked-as.mjs), which the
extractor already refuses as single-segment siblings. Line 459 was the only remedy string.

1 hit, fixed; 0 identical second sites left behind. After the change the same fleet
sweep reads hints total: 1499 · dead: 735 · DOUBLED-PREFIX class: 0 — exactly one
hint removed and nothing else moved.

The remaining 735 dead hints are the fixture-constant class #13312 owns, a different
cause; they are untouched here.

A2.3 — reverse verification: the extractor emitted it before and does not after

Run against the committed implementation, mutating the tree back to the pre-fix spelling
and restoring it, with the mutation proved on disk by anchored grep -c on both the
injected and the removed text, and the restore proved by observed state (empty
git diff HEAD plus a blob-hash comparison against the HEAD blob) rather than by an exit
code. The extractor is a pure string function over the source file as read from disk, so
there is no build artifact between the mutation and the reading.

HEAD blob for target: cb30a9f975574aa65d251be2f52f33c6c1eac4a9
=== LEG 1: MUTATE (pre-fix relative spelling put back) ===
injected import('./scripts/... : 1
removed process.cwd()+'/... : 0
blob hash now vs HEAD blob : 4d12eae58b70… (HEAD cb30a9f97557…)
extractor reading on the MUTATED tree:
HINT COUNT: 4
HINT "ARCHITECTURE.md/**"
HINT "README.md/**"
HINT "content/**"
HINT "scripts/scripts/check-doc-anchors.mjs"
FABRICATED PRESENT: true
=== LEG 2: RESTORE (git checkout HEAD -- ABSOLUTE_PATH) ===
git diff HEAD (must be empty) : EMPTY
blob hash == HEAD blob : cb30a9f97557… == cb30a9f97557…
injected import('./scripts/... : 0
restored process.cwd()+'/... : 1
extractor reading on the RESTORED tree:
HINT COUNT: 3
HINT "ARCHITECTURE.md/**"
HINT "README.md/**"
HINT "content/**"
FABRICATED PRESENT: false

Direction observed: the fabricated hint disappears and the three real declarations stay
content/**, README.md/**, ARCHITECTURE.md/** are byte-identical before and after,
and nothing new is added. That last half matters: a spelling that merely moved the literal
(say, a bare scripts/check-doc-anchors.mjs) would have been admitted as a true hint
and quietly given this gate a population it does not read. This one declares nothing.

The same result is confirmed by the tool's own suite: dispatch-gates.mjs --self-test
reads this file live and asserts that the doc-anchors family still reaches
content/docs/deployment/cli.mdx, still claims README.md and ARCHITECTURE.md, and
still claims neither instruction file. 1017 cases pass.

One more observed side-effect, in the right direction: the rationale comment added above
the line spells the relative path in prose, and the module-body sweep confirms comment
masking removes it — the file's RAW text has 4 module-relative literals, its masked module
body has 3.

A2.4 — the remedy still runs verbatim from the repo root

The gate was driven to its failure branch against a throwaway fixture root so the remedy
is the text a human actually sees:

 The heading id is computed with `github-slugger`, the same package
fumadocs-core uses to render the page, so what this gate says the anchor is
is what the site says it is. Print a page's real ids with:
node -e "import(process.cwd()+'/scripts/check-doc-anchors.mjs').then(m=>console.log(m.headingIds(require('node:fs').readFileSync('FILE','utf8')).join('\n')))"

(FILE above stands for the literal placeholder the gate prints, which the reader replaces.)

Pasted verbatim at the repo root with the placeholder filled in:

$ node -e "import(process.cwd()+'/scripts/check-doc-anchors.mjs').then(m=>console.log(m.headingIds(require('node:fs').readFileSync('content/docs/index.mdx','utf8')).join('\n')))"
objectstack-documentation
start-here
platform-modules
protocol--reference
EXIT=0

Byte-identical to the same run under the old spelling — diff of the two captures is
empty. Readability does not regress: the command is one token longer and still a single
paste.

Gates

Family derived after the final commit with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (no paths passed —
the script takes its own change set; it reported 1 path(s) vs merge base c42bc8ee6).
Every exit code below was captured before any pipe, and each verdict is the gate's own
printed line. All runs are on the final commit fd2ea143f.

Path-derived family (13):

gateexitverdict
pnpm check:agent-test-spelling0GREEN
pnpm check:bash32-floor0GREEN
pnpm check:cli-command-ids0GREEN
pnpm check:cross-package-test-inputs0GREEN
pnpm check:doc-anchors0GREEN — check-doc-anchors: 287 internal #fragment link(s) across 409 source file(s) all resolve to a real heading
pnpm check:entry-guard0GREEN
pnpm check:parse-guard0GREEN
pnpm check:pnpm-filter-targets0GREEN
pnpm check:watch-hint-literal0GREEN — 34 declaration(s) across 4 rostered name(s) … no unrostered spelling of the idiom in the tree
node scripts/check-ci-filter-parity.mjs0GREEN
node scripts/check-cross-package-test-inputs.mjs0GREEN
node scripts/check-shard-attestation.mjs0GREEN
node scripts/check-test-completeness.mjs3NOT MEASUREDPREREQUISITE NOT MET; it grades a saved turbo run test log and none was named. Its own text says exit 3 is not a finding.

Convention-triggered (editing a gate script), both named by the derivation:

gateexitverdict
node scripts/pm/bare-root-worklist.mjs --self-test0GREEN — 51 live row(s), 43 unreachable as spelled, 43 recorded verdict(s) — none stale, none missing, none contradicted
pnpm check:pm-dispatch-gates0GREEN — dispatch-gates self-test: 1017 cases pass.

Beyond the derived list, the gate script's own suite plus the consumers that read this
file by name (the "which tests test this script" half the path derivation cannot answer):

gateexitverdict
pnpm check:nul-bytes0GREEN — scanned 7578 text file(s) … no raw ASCII control bytes
pnpm check:published-readme-links0GREEN (imports headingIds from the edited file)
pnpm check:docs-single-h10GREEN
pnpm check:docs-image-tag0GREEN
node scripts/check-self-test-wired.mjs (+ --self-test)0GREEN
node scripts/check-self-test-workflow-commands.mjs (+ --self-test)0GREEN
pnpm lint (repo-wide eslint . --no-inline-config, not narrowed)0GREEN — 2m02s

Repo-wide ESLint was run in full rather than narrowed, so no narrowing is being declared.

Changeset

skip-changeset, applied at PR creation. Check Changeset is path-blind — it counts the
changesets a PR adds against the merge base and fails on zero; its only exemptions are
the skip-changeset label and the Changesets release branch. This PR publishes nothing:
the root manifest is private, and no published package's files field escapes its own
directory, so a repo-root scripts/ file ships in no tarball.

Out of scope, deliberately


Generated by Claude Code

…read as a watch-hint declaration
The remedy line printed for a broken anchor is a command for a human to paste
at the repo root, and it carried the module specifier as a quoted relative
literal `'./scripts/check-doc-anchors.mjs'`. The dispatch watch-hint extractor
reads quoted path-shaped literals in a gate's module body and resolves the
relative ones against the writer's own directory, so this gate — which lives in
scripts/ — declared a path that has never existed on this tree, and that lead
was pasted into every dispatch prompt the family matched.
Neither half was wrong on its own; the fabrication came from prose being read as
a declaration. Tightening the reader was measured and refused upstream, so the
producer spells the path absolutely instead: process.cwd() + a leading-slash
literal, which the extractor's admission test refuses outright while the command
stays runnable verbatim from the repo root.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(check-doc-anchors): spell the heading-ids remedy so it cannot be read as a watch-hint declaration by os-project-manager · Pull Request #13775 · objectstack-ai/objectstack · GitHub
Skip to content

fix(check-doc-anchors): spell the heading-ids remedy so it cannot be read as a watch-hint declaration - #13775

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-13449-doc-anchors-remedy-literal
Aug 31, 2026
Merged

fix(check-doc-anchors): spell the heading-ids remedy so it cannot be read as a watch-hint declaration#13775
os-project-manager merged 1 commit into
mainfrom
claude/issue-13449-doc-anchors-remedy-literal

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#13449

check:doc-anchors printed a remedy command whose module specifier was a quoted
relative literal. The dispatch watch-hint extractor reads quoted path-shaped literals
out of a gate's module body and resolves the relative ones against the directory of the
file that wrote them — so this gate, which lives in scripts/, declared a path with its
own prefix doubled. That lead named a file that has never existed on this tree, and it
was pasted into every dispatch prompt whose file surface brushed the family.

Neither half was wrong on its own: the remedy is correct for its reader, the extractor is
correct about a declaration. The fabrication came from one being read as the other, so the
repair is local to the producer — spell the path so it cannot be read as a declaration.
Per the triage ruling, the extractor is not touched (upstream measured the tightening
at 8.6 real populations deleted per fabrication removed), and nothing in this delivery
touches scripts/pm/dispatch-gates.mjs.

The change

One line in scripts/check-doc-anchors.mjs, plus the rationale comment above it:

- " node -e \"import('./scripts/check-doc-anchors.mjs').then(m=>…+ " node -e \"import(process.cwd()+'/scripts/check-doc-anchors.mjs').then(m=>…

The leading / inside the quotes is the whole mechanism. extractWatchHints' admission
test is /^[\w.@][\w.@/*-]*$/ — a literal must begin with a word character, . or @
so an absolute path is refused outright, before the module-relative resolve can ever run.
process.cwd() is what keeps the command runnable verbatim from the repo root.

A2.1 — the defect was still live at my ref

Re-confirmed at c42bc8ee6 (my branch point), not inherited from the dispatch order:

git ls-files 'scripts/scripts/*' -> 0
git ls-files 'scripts/*' -> 299 (209 at the top level) <- the control

The zero is a true zero, so #13312's closing PR did not absorb this. The card correctly
gets a PR rather than a close.

A2.2 — census: 1 site, and it is the only one

Two independent sweeps, both run through the real extractor rather than by eye.

Fleet sweep — every tracked gate/tooling script under a scripts/ directory
(333 files), classifying each extracted hint that reaches nothing in the tree, and
flagging the doubled-prefix signature (a dead hint that becomes a real path when one copy
of the writer's own directory is stripped):

scripts scanned: 333
hints total: 1500 dead (reach nothing): 736 DOUBLED-PREFIX class: 1
scripts/check-doc-anchors.mjs
fabricated: scripts/scripts/check-doc-anchors.mjs
real path : scripts/check-doc-anchors.mjs

Same-file sweep — every quoted module-relative literal in check-doc-anchors.mjs:
4 of them, and the other 3 are genuine sibling import specifiers
(./import-prerequisite.mjs, ./check-adr-links.mjs, ./invoked-as.mjs), which the
extractor already refuses as single-segment siblings. Line 459 was the only remedy string.

1 hit, fixed; 0 identical second sites left behind. After the change the same fleet
sweep reads hints total: 1499 · dead: 735 · DOUBLED-PREFIX class: 0 — exactly one
hint removed and nothing else moved.

The remaining 735 dead hints are the fixture-constant class #13312 owns, a different
cause; they are untouched here.

A2.3 — reverse verification: the extractor emitted it before and does not after

Run against the committed implementation, mutating the tree back to the pre-fix spelling
and restoring it, with the mutation proved on disk by anchored grep -c on both the
injected and the removed text, and the restore proved by observed state (empty
git diff HEAD plus a blob-hash comparison against the HEAD blob) rather than by an exit
code. The extractor is a pure string function over the source file as read from disk, so
there is no build artifact between the mutation and the reading.

HEAD blob for target: cb30a9f975574aa65d251be2f52f33c6c1eac4a9
=== LEG 1: MUTATE (pre-fix relative spelling put back) ===
injected import('./scripts/... : 1
removed process.cwd()+'/... : 0
blob hash now vs HEAD blob : 4d12eae58b70… (HEAD cb30a9f97557…)
extractor reading on the MUTATED tree:
HINT COUNT: 4
HINT "ARCHITECTURE.md/**"
HINT "README.md/**"
HINT "content/**"
HINT "scripts/scripts/check-doc-anchors.mjs"
FABRICATED PRESENT: true
=== LEG 2: RESTORE (git checkout HEAD -- ABSOLUTE_PATH) ===
git diff HEAD (must be empty) : EMPTY
blob hash == HEAD blob : cb30a9f97557… == cb30a9f97557…
injected import('./scripts/... : 0
restored process.cwd()+'/... : 1
extractor reading on the RESTORED tree:
HINT COUNT: 3
HINT "ARCHITECTURE.md/**"
HINT "README.md/**"
HINT "content/**"
FABRICATED PRESENT: false

Direction observed: the fabricated hint disappears and the three real declarations stay
content/**, README.md/**, ARCHITECTURE.md/** are byte-identical before and after,
and nothing new is added. That last half matters: a spelling that merely moved the literal
(say, a bare scripts/check-doc-anchors.mjs) would have been admitted as a true hint
and quietly given this gate a population it does not read. This one declares nothing.

The same result is confirmed by the tool's own suite: dispatch-gates.mjs --self-test
reads this file live and asserts that the doc-anchors family still reaches
content/docs/deployment/cli.mdx, still claims README.md and ARCHITECTURE.md, and
still claims neither instruction file. 1017 cases pass.

One more observed side-effect, in the right direction: the rationale comment added above
the line spells the relative path in prose, and the module-body sweep confirms comment
masking removes it — the file's RAW text has 4 module-relative literals, its masked module
body has 3.

A2.4 — the remedy still runs verbatim from the repo root

The gate was driven to its failure branch against a throwaway fixture root so the remedy
is the text a human actually sees:

 The heading id is computed with `github-slugger`, the same package
fumadocs-core uses to render the page, so what this gate says the anchor is
is what the site says it is. Print a page's real ids with:
node -e "import(process.cwd()+'/scripts/check-doc-anchors.mjs').then(m=>console.log(m.headingIds(require('node:fs').readFileSync('FILE','utf8')).join('\n')))"

(FILE above stands for the literal placeholder the gate prints, which the reader replaces.)

Pasted verbatim at the repo root with the placeholder filled in:

$ node -e "import(process.cwd()+'/scripts/check-doc-anchors.mjs').then(m=>console.log(m.headingIds(require('node:fs').readFileSync('content/docs/index.mdx','utf8')).join('\n')))"
objectstack-documentation
start-here
platform-modules
protocol--reference
EXIT=0

Byte-identical to the same run under the old spelling — diff of the two captures is
empty. Readability does not regress: the command is one token longer and still a single
paste.

Gates

Family derived after the final commit with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (no paths passed —
the script takes its own change set; it reported 1 path(s) vs merge base c42bc8ee6).
Every exit code below was captured before any pipe, and each verdict is the gate's own
printed line. All runs are on the final commit fd2ea143f.

Path-derived family (13):

gateexitverdict
pnpm check:agent-test-spelling0GREEN
pnpm check:bash32-floor0GREEN
pnpm check:cli-command-ids0GREEN
pnpm check:cross-package-test-inputs0GREEN
pnpm check:doc-anchors0GREEN — check-doc-anchors: 287 internal #fragment link(s) across 409 source file(s) all resolve to a real heading
pnpm check:entry-guard0GREEN
pnpm check:parse-guard0GREEN
pnpm check:pnpm-filter-targets0GREEN
pnpm check:watch-hint-literal0GREEN — 34 declaration(s) across 4 rostered name(s) … no unrostered spelling of the idiom in the tree
node scripts/check-ci-filter-parity.mjs0GREEN
node scripts/check-cross-package-test-inputs.mjs0GREEN
node scripts/check-shard-attestation.mjs0GREEN
node scripts/check-test-completeness.mjs3NOT MEASUREDPREREQUISITE NOT MET; it grades a saved turbo run test log and none was named. Its own text says exit 3 is not a finding.

Convention-triggered (editing a gate script), both named by the derivation:

gateexitverdict
node scripts/pm/bare-root-worklist.mjs --self-test0GREEN — 51 live row(s), 43 unreachable as spelled, 43 recorded verdict(s) — none stale, none missing, none contradicted
pnpm check:pm-dispatch-gates0GREEN — dispatch-gates self-test: 1017 cases pass.

Beyond the derived list, the gate script's own suite plus the consumers that read this
file by name (the "which tests test this script" half the path derivation cannot answer):

gateexitverdict
pnpm check:nul-bytes0GREEN — scanned 7578 text file(s) … no raw ASCII control bytes
pnpm check:published-readme-links0GREEN (imports headingIds from the edited file)
pnpm check:docs-single-h10GREEN
pnpm check:docs-image-tag0GREEN
node scripts/check-self-test-wired.mjs (+ --self-test)0GREEN
node scripts/check-self-test-workflow-commands.mjs (+ --self-test)0GREEN
pnpm lint (repo-wide eslint . --no-inline-config, not narrowed)0GREEN — 2m02s

Repo-wide ESLint was run in full rather than narrowed, so no narrowing is being declared.

Changeset

skip-changeset, applied at PR creation. Check Changeset is path-blind — it counts the
changesets a PR adds against the merge base and fails on zero; its only exemptions are
the skip-changeset label and the Changesets release branch. This PR publishes nothing:
the root manifest is private, and no published package's files field escapes its own
directory, so a repo-root scripts/ file ships in no tarball.

Out of scope, deliberately


Generated by Claude Code

…read as a watch-hint declaration
The remedy line printed for a broken anchor is a command for a human to paste
at the repo root, and it carried the module specifier as a quoted relative
literal `'./scripts/check-doc-anchors.mjs'`. The dispatch watch-hint extractor
reads quoted path-shaped literals in a gate's module body and resolves the
relative ones against the writer's own directory, so this gate — which lives in
scripts/ — declared a path that has never existed on this tree, and that lead
was pasted into every dispatch prompt the family matched.
Neither half was wrong on its own; the fabrication came from prose being read as
a declaration. Tightening the reader was measured and refused upstream, so the
producer spells the path absolutely instead: process.cwd() + a leading-slash
literal, which the extractor's admission test refuses outright while the command
stays runnable verbatim from the repo root.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' fix(check-doc-anchors): spell the heading-ids remedy so it cannot be read as a watch-hint declaration by os-project-manager · Pull Request #13775 · objectstack-ai/objectstack · GitHub
Skip to content

fix(check-doc-anchors): spell the heading-ids remedy so it cannot be read as a watch-hint declaration - #13775

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-13449-doc-anchors-remedy-literal
Aug 31, 2026
Merged

fix(check-doc-anchors): spell the heading-ids remedy so it cannot be read as a watch-hint declaration#13775
os-project-manager merged 1 commit into
mainfrom
claude/issue-13449-doc-anchors-remedy-literal

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#13449

check:doc-anchors printed a remedy command whose module specifier was a quoted
relative literal. The dispatch watch-hint extractor reads quoted path-shaped literals
out of a gate's module body and resolves the relative ones against the directory of the
file that wrote them — so this gate, which lives in scripts/, declared a path with its
own prefix doubled. That lead named a file that has never existed on this tree, and it
was pasted into every dispatch prompt whose file surface brushed the family.

Neither half was wrong on its own: the remedy is correct for its reader, the extractor is
correct about a declaration. The fabrication came from one being read as the other, so the
repair is local to the producer — spell the path so it cannot be read as a declaration.
Per the triage ruling, the extractor is not touched (upstream measured the tightening
at 8.6 real populations deleted per fabrication removed), and nothing in this delivery
touches scripts/pm/dispatch-gates.mjs.

The change

One line in scripts/check-doc-anchors.mjs, plus the rationale comment above it:

- " node -e \"import('./scripts/check-doc-anchors.mjs').then(m=>…+ " node -e \"import(process.cwd()+'/scripts/check-doc-anchors.mjs').then(m=>…

The leading / inside the quotes is the whole mechanism. extractWatchHints' admission
test is /^[\w.@][\w.@/*-]*$/ — a literal must begin with a word character, . or @
so an absolute path is refused outright, before the module-relative resolve can ever run.
process.cwd() is what keeps the command runnable verbatim from the repo root.

A2.1 — the defect was still live at my ref

Re-confirmed at c42bc8ee6 (my branch point), not inherited from the dispatch order:

git ls-files 'scripts/scripts/*' -> 0
git ls-files 'scripts/*' -> 299 (209 at the top level) <- the control

The zero is a true zero, so #13312's closing PR did not absorb this. The card correctly
gets a PR rather than a close.

A2.2 — census: 1 site, and it is the only one

Two independent sweeps, both run through the real extractor rather than by eye.

Fleet sweep — every tracked gate/tooling script under a scripts/ directory
(333 files), classifying each extracted hint that reaches nothing in the tree, and
flagging the doubled-prefix signature (a dead hint that becomes a real path when one copy
of the writer's own directory is stripped):

scripts scanned: 333
hints total: 1500 dead (reach nothing): 736 DOUBLED-PREFIX class: 1
scripts/check-doc-anchors.mjs
fabricated: scripts/scripts/check-doc-anchors.mjs
real path : scripts/check-doc-anchors.mjs

Same-file sweep — every quoted module-relative literal in check-doc-anchors.mjs:
4 of them, and the other 3 are genuine sibling import specifiers
(./import-prerequisite.mjs, ./check-adr-links.mjs, ./invoked-as.mjs), which the
extractor already refuses as single-segment siblings. Line 459 was the only remedy string.

1 hit, fixed; 0 identical second sites left behind. After the change the same fleet
sweep reads hints total: 1499 · dead: 735 · DOUBLED-PREFIX class: 0 — exactly one
hint removed and nothing else moved.

The remaining 735 dead hints are the fixture-constant class #13312 owns, a different
cause; they are untouched here.

A2.3 — reverse verification: the extractor emitted it before and does not after

Run against the committed implementation, mutating the tree back to the pre-fix spelling
and restoring it, with the mutation proved on disk by anchored grep -c on both the
injected and the removed text, and the restore proved by observed state (empty
git diff HEAD plus a blob-hash comparison against the HEAD blob) rather than by an exit
code. The extractor is a pure string function over the source file as read from disk, so
there is no build artifact between the mutation and the reading.

HEAD blob for target: cb30a9f975574aa65d251be2f52f33c6c1eac4a9
=== LEG 1: MUTATE (pre-fix relative spelling put back) ===
injected import('./scripts/... : 1
removed process.cwd()+'/... : 0
blob hash now vs HEAD blob : 4d12eae58b70… (HEAD cb30a9f97557…)
extractor reading on the MUTATED tree:
HINT COUNT: 4
HINT "ARCHITECTURE.md/**"
HINT "README.md/**"
HINT "content/**"
HINT "scripts/scripts/check-doc-anchors.mjs"
FABRICATED PRESENT: true
=== LEG 2: RESTORE (git checkout HEAD -- ABSOLUTE_PATH) ===
git diff HEAD (must be empty) : EMPTY
blob hash == HEAD blob : cb30a9f97557… == cb30a9f97557…
injected import('./scripts/... : 0
restored process.cwd()+'/... : 1
extractor reading on the RESTORED tree:
HINT COUNT: 3
HINT "ARCHITECTURE.md/**"
HINT "README.md/**"
HINT "content/**"
FABRICATED PRESENT: false

Direction observed: the fabricated hint disappears and the three real declarations stay
content/**, README.md/**, ARCHITECTURE.md/** are byte-identical before and after,
and nothing new is added. That last half matters: a spelling that merely moved the literal
(say, a bare scripts/check-doc-anchors.mjs) would have been admitted as a true hint
and quietly given this gate a population it does not read. This one declares nothing.

The same result is confirmed by the tool's own suite: dispatch-gates.mjs --self-test
reads this file live and asserts that the doc-anchors family still reaches
content/docs/deployment/cli.mdx, still claims README.md and ARCHITECTURE.md, and
still claims neither instruction file. 1017 cases pass.

One more observed side-effect, in the right direction: the rationale comment added above
the line spells the relative path in prose, and the module-body sweep confirms comment
masking removes it — the file's RAW text has 4 module-relative literals, its masked module
body has 3.

A2.4 — the remedy still runs verbatim from the repo root

The gate was driven to its failure branch against a throwaway fixture root so the remedy
is the text a human actually sees:

 The heading id is computed with `github-slugger`, the same package
fumadocs-core uses to render the page, so what this gate says the anchor is
is what the site says it is. Print a page's real ids with:
node -e "import(process.cwd()+'/scripts/check-doc-anchors.mjs').then(m=>console.log(m.headingIds(require('node:fs').readFileSync('FILE','utf8')).join('\n')))"

(FILE above stands for the literal placeholder the gate prints, which the reader replaces.)

Pasted verbatim at the repo root with the placeholder filled in:

$ node -e "import(process.cwd()+'/scripts/check-doc-anchors.mjs').then(m=>console.log(m.headingIds(require('node:fs').readFileSync('content/docs/index.mdx','utf8')).join('\n')))"
objectstack-documentation
start-here
platform-modules
protocol--reference
EXIT=0

Byte-identical to the same run under the old spelling — diff of the two captures is
empty. Readability does not regress: the command is one token longer and still a single
paste.

Gates

Family derived after the final commit with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (no paths passed —
the script takes its own change set; it reported 1 path(s) vs merge base c42bc8ee6).
Every exit code below was captured before any pipe, and each verdict is the gate's own
printed line. All runs are on the final commit fd2ea143f.

Path-derived family (13):

gateexitverdict
pnpm check:agent-test-spelling0GREEN
pnpm check:bash32-floor0GREEN
pnpm check:cli-command-ids0GREEN
pnpm check:cross-package-test-inputs0GREEN
pnpm check:doc-anchors0GREEN — check-doc-anchors: 287 internal #fragment link(s) across 409 source file(s) all resolve to a real heading
pnpm check:entry-guard0GREEN
pnpm check:parse-guard0GREEN
pnpm check:pnpm-filter-targets0GREEN
pnpm check:watch-hint-literal0GREEN — 34 declaration(s) across 4 rostered name(s) … no unrostered spelling of the idiom in the tree
node scripts/check-ci-filter-parity.mjs0GREEN
node scripts/check-cross-package-test-inputs.mjs0GREEN
node scripts/check-shard-attestation.mjs0GREEN
node scripts/check-test-completeness.mjs3NOT MEASUREDPREREQUISITE NOT MET; it grades a saved turbo run test log and none was named. Its own text says exit 3 is not a finding.

Convention-triggered (editing a gate script), both named by the derivation:

gateexitverdict
node scripts/pm/bare-root-worklist.mjs --self-test0GREEN — 51 live row(s), 43 unreachable as spelled, 43 recorded verdict(s) — none stale, none missing, none contradicted
pnpm check:pm-dispatch-gates0GREEN — dispatch-gates self-test: 1017 cases pass.

Beyond the derived list, the gate script's own suite plus the consumers that read this
file by name (the "which tests test this script" half the path derivation cannot answer):

gateexitverdict
pnpm check:nul-bytes0GREEN — scanned 7578 text file(s) … no raw ASCII control bytes
pnpm check:published-readme-links0GREEN (imports headingIds from the edited file)
pnpm check:docs-single-h10GREEN
pnpm check:docs-image-tag0GREEN
node scripts/check-self-test-wired.mjs (+ --self-test)0GREEN
node scripts/check-self-test-workflow-commands.mjs (+ --self-test)0GREEN
pnpm lint (repo-wide eslint . --no-inline-config, not narrowed)0GREEN — 2m02s

Repo-wide ESLint was run in full rather than narrowed, so no narrowing is being declared.

Changeset

skip-changeset, applied at PR creation. Check Changeset is path-blind — it counts the
changesets a PR adds against the merge base and fails on zero; its only exemptions are
the skip-changeset label and the Changesets release branch. This PR publishes nothing:
the root manifest is private, and no published package's files field escapes its own
directory, so a repo-root scripts/ file ships in no tarball.

Out of scope, deliberately


Generated by Claude Code

…read as a watch-hint declaration
The remedy line printed for a broken anchor is a command for a human to paste
at the repo root, and it carried the module specifier as a quoted relative
literal `'./scripts/check-doc-anchors.mjs'`. The dispatch watch-hint extractor
reads quoted path-shaped literals in a gate's module body and resolves the
relative ones against the writer's own directory, so this gate — which lives in
scripts/ — declared a path that has never existed on this tree, and that lead
was pasted into every dispatch prompt the family matched.
Neither half was wrong on its own; the fabrication came from prose being read as
a declaration. Tightening the reader was measured and refused upstream, so the
producer spells the path absolutely instead: process.cwd() + a leading-slash
literal, which the extractor's admission test refuses outright while the command
stays runnable verbatim from the repo root.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(check-doc-anchors): spell the heading-ids remedy so it cannot be read as a watch-hint declaration by os-project-manager · Pull Request #13775 · objectstack-ai/objectstack · GitHub
Skip to content

fix(check-doc-anchors): spell the heading-ids remedy so it cannot be read as a watch-hint declaration - #13775

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-13449-doc-anchors-remedy-literal
Aug 31, 2026
Merged

fix(check-doc-anchors): spell the heading-ids remedy so it cannot be read as a watch-hint declaration#13775
os-project-manager merged 1 commit into
mainfrom
claude/issue-13449-doc-anchors-remedy-literal

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#13449

check:doc-anchors printed a remedy command whose module specifier was a quoted
relative literal. The dispatch watch-hint extractor reads quoted path-shaped literals
out of a gate's module body and resolves the relative ones against the directory of the
file that wrote them — so this gate, which lives in scripts/, declared a path with its
own prefix doubled. That lead named a file that has never existed on this tree, and it
was pasted into every dispatch prompt whose file surface brushed the family.

Neither half was wrong on its own: the remedy is correct for its reader, the extractor is
correct about a declaration. The fabrication came from one being read as the other, so the
repair is local to the producer — spell the path so it cannot be read as a declaration.
Per the triage ruling, the extractor is not touched (upstream measured the tightening
at 8.6 real populations deleted per fabrication removed), and nothing in this delivery
touches scripts/pm/dispatch-gates.mjs.

The change

One line in scripts/check-doc-anchors.mjs, plus the rationale comment above it:

- " node -e \"import('./scripts/check-doc-anchors.mjs').then(m=>…+ " node -e \"import(process.cwd()+'/scripts/check-doc-anchors.mjs').then(m=>…

The leading / inside the quotes is the whole mechanism. extractWatchHints' admission
test is /^[\w.@][\w.@/*-]*$/ — a literal must begin with a word character, . or @
so an absolute path is refused outright, before the module-relative resolve can ever run.
process.cwd() is what keeps the command runnable verbatim from the repo root.

A2.1 — the defect was still live at my ref

Re-confirmed at c42bc8ee6 (my branch point), not inherited from the dispatch order:

git ls-files 'scripts/scripts/*' -> 0
git ls-files 'scripts/*' -> 299 (209 at the top level) <- the control

The zero is a true zero, so #13312's closing PR did not absorb this. The card correctly
gets a PR rather than a close.

A2.2 — census: 1 site, and it is the only one

Two independent sweeps, both run through the real extractor rather than by eye.

Fleet sweep — every tracked gate/tooling script under a scripts/ directory
(333 files), classifying each extracted hint that reaches nothing in the tree, and
flagging the doubled-prefix signature (a dead hint that becomes a real path when one copy
of the writer's own directory is stripped):

scripts scanned: 333
hints total: 1500 dead (reach nothing): 736 DOUBLED-PREFIX class: 1
scripts/check-doc-anchors.mjs
fabricated: scripts/scripts/check-doc-anchors.mjs
real path : scripts/check-doc-anchors.mjs

Same-file sweep — every quoted module-relative literal in check-doc-anchors.mjs:
4 of them, and the other 3 are genuine sibling import specifiers
(./import-prerequisite.mjs, ./check-adr-links.mjs, ./invoked-as.mjs), which the
extractor already refuses as single-segment siblings. Line 459 was the only remedy string.

1 hit, fixed; 0 identical second sites left behind. After the change the same fleet
sweep reads hints total: 1499 · dead: 735 · DOUBLED-PREFIX class: 0 — exactly one
hint removed and nothing else moved.

The remaining 735 dead hints are the fixture-constant class #13312 owns, a different
cause; they are untouched here.

A2.3 — reverse verification: the extractor emitted it before and does not after

Run against the committed implementation, mutating the tree back to the pre-fix spelling
and restoring it, with the mutation proved on disk by anchored grep -c on both the
injected and the removed text, and the restore proved by observed state (empty
git diff HEAD plus a blob-hash comparison against the HEAD blob) rather than by an exit
code. The extractor is a pure string function over the source file as read from disk, so
there is no build artifact between the mutation and the reading.

HEAD blob for target: cb30a9f975574aa65d251be2f52f33c6c1eac4a9
=== LEG 1: MUTATE (pre-fix relative spelling put back) ===
injected import('./scripts/... : 1
removed process.cwd()+'/... : 0
blob hash now vs HEAD blob : 4d12eae58b70… (HEAD cb30a9f97557…)
extractor reading on the MUTATED tree:
HINT COUNT: 4
HINT "ARCHITECTURE.md/**"
HINT "README.md/**"
HINT "content/**"
HINT "scripts/scripts/check-doc-anchors.mjs"
FABRICATED PRESENT: true
=== LEG 2: RESTORE (git checkout HEAD -- ABSOLUTE_PATH) ===
git diff HEAD (must be empty) : EMPTY
blob hash == HEAD blob : cb30a9f97557… == cb30a9f97557…
injected import('./scripts/... : 0
restored process.cwd()+'/... : 1
extractor reading on the RESTORED tree:
HINT COUNT: 3
HINT "ARCHITECTURE.md/**"
HINT "README.md/**"
HINT "content/**"
FABRICATED PRESENT: false

Direction observed: the fabricated hint disappears and the three real declarations stay
content/**, README.md/**, ARCHITECTURE.md/** are byte-identical before and after,
and nothing new is added. That last half matters: a spelling that merely moved the literal
(say, a bare scripts/check-doc-anchors.mjs) would have been admitted as a true hint
and quietly given this gate a population it does not read. This one declares nothing.

The same result is confirmed by the tool's own suite: dispatch-gates.mjs --self-test
reads this file live and asserts that the doc-anchors family still reaches
content/docs/deployment/cli.mdx, still claims README.md and ARCHITECTURE.md, and
still claims neither instruction file. 1017 cases pass.

One more observed side-effect, in the right direction: the rationale comment added above
the line spells the relative path in prose, and the module-body sweep confirms comment
masking removes it — the file's RAW text has 4 module-relative literals, its masked module
body has 3.

A2.4 — the remedy still runs verbatim from the repo root

The gate was driven to its failure branch against a throwaway fixture root so the remedy
is the text a human actually sees:

 The heading id is computed with `github-slugger`, the same package
fumadocs-core uses to render the page, so what this gate says the anchor is
is what the site says it is. Print a page's real ids with:
node -e "import(process.cwd()+'/scripts/check-doc-anchors.mjs').then(m=>console.log(m.headingIds(require('node:fs').readFileSync('FILE','utf8')).join('\n')))"

(FILE above stands for the literal placeholder the gate prints, which the reader replaces.)

Pasted verbatim at the repo root with the placeholder filled in:

$ node -e "import(process.cwd()+'/scripts/check-doc-anchors.mjs').then(m=>console.log(m.headingIds(require('node:fs').readFileSync('content/docs/index.mdx','utf8')).join('\n')))"
objectstack-documentation
start-here
platform-modules
protocol--reference
EXIT=0

Byte-identical to the same run under the old spelling — diff of the two captures is
empty. Readability does not regress: the command is one token longer and still a single
paste.

Gates

Family derived after the final commit with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (no paths passed —
the script takes its own change set; it reported 1 path(s) vs merge base c42bc8ee6).
Every exit code below was captured before any pipe, and each verdict is the gate's own
printed line. All runs are on the final commit fd2ea143f.

Path-derived family (13):

gateexitverdict
pnpm check:agent-test-spelling0GREEN
pnpm check:bash32-floor0GREEN
pnpm check:cli-command-ids0GREEN
pnpm check:cross-package-test-inputs0GREEN
pnpm check:doc-anchors0GREEN — check-doc-anchors: 287 internal #fragment link(s) across 409 source file(s) all resolve to a real heading
pnpm check:entry-guard0GREEN
pnpm check:parse-guard0GREEN
pnpm check:pnpm-filter-targets0GREEN
pnpm check:watch-hint-literal0GREEN — 34 declaration(s) across 4 rostered name(s) … no unrostered spelling of the idiom in the tree
node scripts/check-ci-filter-parity.mjs0GREEN
node scripts/check-cross-package-test-inputs.mjs0GREEN
node scripts/check-shard-attestation.mjs0GREEN
node scripts/check-test-completeness.mjs3NOT MEASUREDPREREQUISITE NOT MET; it grades a saved turbo run test log and none was named. Its own text says exit 3 is not a finding.

Convention-triggered (editing a gate script), both named by the derivation:

gateexitverdict
node scripts/pm/bare-root-worklist.mjs --self-test0GREEN — 51 live row(s), 43 unreachable as spelled, 43 recorded verdict(s) — none stale, none missing, none contradicted
pnpm check:pm-dispatch-gates0GREEN — dispatch-gates self-test: 1017 cases pass.

Beyond the derived list, the gate script's own suite plus the consumers that read this
file by name (the "which tests test this script" half the path derivation cannot answer):

gateexitverdict
pnpm check:nul-bytes0GREEN — scanned 7578 text file(s) … no raw ASCII control bytes
pnpm check:published-readme-links0GREEN (imports headingIds from the edited file)
pnpm check:docs-single-h10GREEN
pnpm check:docs-image-tag0GREEN
node scripts/check-self-test-wired.mjs (+ --self-test)0GREEN
node scripts/check-self-test-workflow-commands.mjs (+ --self-test)0GREEN
pnpm lint (repo-wide eslint . --no-inline-config, not narrowed)0GREEN — 2m02s

Repo-wide ESLint was run in full rather than narrowed, so no narrowing is being declared.

Changeset

skip-changeset, applied at PR creation. Check Changeset is path-blind — it counts the
changesets a PR adds against the merge base and fails on zero; its only exemptions are
the skip-changeset label and the Changesets release branch. This PR publishes nothing:
the root manifest is private, and no published package's files field escapes its own
directory, so a repo-root scripts/ file ships in no tarball.

Out of scope, deliberately


Generated by Claude Code

…read as a watch-hint declaration
The remedy line printed for a broken anchor is a command for a human to paste
at the repo root, and it carried the module specifier as a quoted relative
literal `'./scripts/check-doc-anchors.mjs'`. The dispatch watch-hint extractor
reads quoted path-shaped literals in a gate's module body and resolves the
relative ones against the writer's own directory, so this gate — which lives in
scripts/ — declared a path that has never existed on this tree, and that lead
was pasted into every dispatch prompt the family matched.
Neither half was wrong on its own; the fabrication came from prose being read as
a declaration. Tightening the reader was measured and refused upstream, so the
producer spells the path absolutely instead: process.cwd() + a leading-slash
literal, which the extractor's admission test refuses outright while the command
stays runnable verbatim from the repo root.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' fix(check-doc-anchors): spell the heading-ids remedy so it cannot be read as a watch-hint declaration by os-project-manager · Pull Request #13775 · objectstack-ai/objectstack · GitHub
Skip to content

fix(check-doc-anchors): spell the heading-ids remedy so it cannot be read as a watch-hint declaration - #13775

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-13449-doc-anchors-remedy-literal
Aug 31, 2026
Merged

fix(check-doc-anchors): spell the heading-ids remedy so it cannot be read as a watch-hint declaration#13775
os-project-manager merged 1 commit into
mainfrom
claude/issue-13449-doc-anchors-remedy-literal

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#13449

check:doc-anchors printed a remedy command whose module specifier was a quoted
relative literal. The dispatch watch-hint extractor reads quoted path-shaped literals
out of a gate's module body and resolves the relative ones against the directory of the
file that wrote them — so this gate, which lives in scripts/, declared a path with its
own prefix doubled. That lead named a file that has never existed on this tree, and it
was pasted into every dispatch prompt whose file surface brushed the family.

Neither half was wrong on its own: the remedy is correct for its reader, the extractor is
correct about a declaration. The fabrication came from one being read as the other, so the
repair is local to the producer — spell the path so it cannot be read as a declaration.
Per the triage ruling, the extractor is not touched (upstream measured the tightening
at 8.6 real populations deleted per fabrication removed), and nothing in this delivery
touches scripts/pm/dispatch-gates.mjs.

The change

One line in scripts/check-doc-anchors.mjs, plus the rationale comment above it:

- " node -e \"import('./scripts/check-doc-anchors.mjs').then(m=>…+ " node -e \"import(process.cwd()+'/scripts/check-doc-anchors.mjs').then(m=>…

The leading / inside the quotes is the whole mechanism. extractWatchHints' admission
test is /^[\w.@][\w.@/*-]*$/ — a literal must begin with a word character, . or @
so an absolute path is refused outright, before the module-relative resolve can ever run.
process.cwd() is what keeps the command runnable verbatim from the repo root.

A2.1 — the defect was still live at my ref

Re-confirmed at c42bc8ee6 (my branch point), not inherited from the dispatch order:

git ls-files 'scripts/scripts/*' -> 0
git ls-files 'scripts/*' -> 299 (209 at the top level) <- the control

The zero is a true zero, so #13312's closing PR did not absorb this. The card correctly
gets a PR rather than a close.

A2.2 — census: 1 site, and it is the only one

Two independent sweeps, both run through the real extractor rather than by eye.

Fleet sweep — every tracked gate/tooling script under a scripts/ directory
(333 files), classifying each extracted hint that reaches nothing in the tree, and
flagging the doubled-prefix signature (a dead hint that becomes a real path when one copy
of the writer's own directory is stripped):

scripts scanned: 333
hints total: 1500 dead (reach nothing): 736 DOUBLED-PREFIX class: 1
scripts/check-doc-anchors.mjs
fabricated: scripts/scripts/check-doc-anchors.mjs
real path : scripts/check-doc-anchors.mjs

Same-file sweep — every quoted module-relative literal in check-doc-anchors.mjs:
4 of them, and the other 3 are genuine sibling import specifiers
(./import-prerequisite.mjs, ./check-adr-links.mjs, ./invoked-as.mjs), which the
extractor already refuses as single-segment siblings. Line 459 was the only remedy string.

1 hit, fixed; 0 identical second sites left behind. After the change the same fleet
sweep reads hints total: 1499 · dead: 735 · DOUBLED-PREFIX class: 0 — exactly one
hint removed and nothing else moved.

The remaining 735 dead hints are the fixture-constant class #13312 owns, a different
cause; they are untouched here.

A2.3 — reverse verification: the extractor emitted it before and does not after

Run against the committed implementation, mutating the tree back to the pre-fix spelling
and restoring it, with the mutation proved on disk by anchored grep -c on both the
injected and the removed text, and the restore proved by observed state (empty
git diff HEAD plus a blob-hash comparison against the HEAD blob) rather than by an exit
code. The extractor is a pure string function over the source file as read from disk, so
there is no build artifact between the mutation and the reading.

HEAD blob for target: cb30a9f975574aa65d251be2f52f33c6c1eac4a9
=== LEG 1: MUTATE (pre-fix relative spelling put back) ===
injected import('./scripts/... : 1
removed process.cwd()+'/... : 0
blob hash now vs HEAD blob : 4d12eae58b70… (HEAD cb30a9f97557…)
extractor reading on the MUTATED tree:
HINT COUNT: 4
HINT "ARCHITECTURE.md/**"
HINT "README.md/**"
HINT "content/**"
HINT "scripts/scripts/check-doc-anchors.mjs"
FABRICATED PRESENT: true
=== LEG 2: RESTORE (git checkout HEAD -- ABSOLUTE_PATH) ===
git diff HEAD (must be empty) : EMPTY
blob hash == HEAD blob : cb30a9f97557… == cb30a9f97557…
injected import('./scripts/... : 0
restored process.cwd()+'/... : 1
extractor reading on the RESTORED tree:
HINT COUNT: 3
HINT "ARCHITECTURE.md/**"
HINT "README.md/**"
HINT "content/**"
FABRICATED PRESENT: false

Direction observed: the fabricated hint disappears and the three real declarations stay
content/**, README.md/**, ARCHITECTURE.md/** are byte-identical before and after,
and nothing new is added. That last half matters: a spelling that merely moved the literal
(say, a bare scripts/check-doc-anchors.mjs) would have been admitted as a true hint
and quietly given this gate a population it does not read. This one declares nothing.

The same result is confirmed by the tool's own suite: dispatch-gates.mjs --self-test
reads this file live and asserts that the doc-anchors family still reaches
content/docs/deployment/cli.mdx, still claims README.md and ARCHITECTURE.md, and
still claims neither instruction file. 1017 cases pass.

One more observed side-effect, in the right direction: the rationale comment added above
the line spells the relative path in prose, and the module-body sweep confirms comment
masking removes it — the file's RAW text has 4 module-relative literals, its masked module
body has 3.

A2.4 — the remedy still runs verbatim from the repo root

The gate was driven to its failure branch against a throwaway fixture root so the remedy
is the text a human actually sees:

 The heading id is computed with `github-slugger`, the same package
fumadocs-core uses to render the page, so what this gate says the anchor is
is what the site says it is. Print a page's real ids with:
node -e "import(process.cwd()+'/scripts/check-doc-anchors.mjs').then(m=>console.log(m.headingIds(require('node:fs').readFileSync('FILE','utf8')).join('\n')))"

(FILE above stands for the literal placeholder the gate prints, which the reader replaces.)

Pasted verbatim at the repo root with the placeholder filled in:

$ node -e "import(process.cwd()+'/scripts/check-doc-anchors.mjs').then(m=>console.log(m.headingIds(require('node:fs').readFileSync('content/docs/index.mdx','utf8')).join('\n')))"
objectstack-documentation
start-here
platform-modules
protocol--reference
EXIT=0

Byte-identical to the same run under the old spelling — diff of the two captures is
empty. Readability does not regress: the command is one token longer and still a single
paste.

Gates

Family derived after the final commit with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (no paths passed —
the script takes its own change set; it reported 1 path(s) vs merge base c42bc8ee6).
Every exit code below was captured before any pipe, and each verdict is the gate's own
printed line. All runs are on the final commit fd2ea143f.

Path-derived family (13):

gateexitverdict
pnpm check:agent-test-spelling0GREEN
pnpm check:bash32-floor0GREEN
pnpm check:cli-command-ids0GREEN
pnpm check:cross-package-test-inputs0GREEN
pnpm check:doc-anchors0GREEN — check-doc-anchors: 287 internal #fragment link(s) across 409 source file(s) all resolve to a real heading
pnpm check:entry-guard0GREEN
pnpm check:parse-guard0GREEN
pnpm check:pnpm-filter-targets0GREEN
pnpm check:watch-hint-literal0GREEN — 34 declaration(s) across 4 rostered name(s) … no unrostered spelling of the idiom in the tree
node scripts/check-ci-filter-parity.mjs0GREEN
node scripts/check-cross-package-test-inputs.mjs0GREEN
node scripts/check-shard-attestation.mjs0GREEN
node scripts/check-test-completeness.mjs3NOT MEASUREDPREREQUISITE NOT MET; it grades a saved turbo run test log and none was named. Its own text says exit 3 is not a finding.

Convention-triggered (editing a gate script), both named by the derivation:

gateexitverdict
node scripts/pm/bare-root-worklist.mjs --self-test0GREEN — 51 live row(s), 43 unreachable as spelled, 43 recorded verdict(s) — none stale, none missing, none contradicted
pnpm check:pm-dispatch-gates0GREEN — dispatch-gates self-test: 1017 cases pass.

Beyond the derived list, the gate script's own suite plus the consumers that read this
file by name (the "which tests test this script" half the path derivation cannot answer):

gateexitverdict
pnpm check:nul-bytes0GREEN — scanned 7578 text file(s) … no raw ASCII control bytes
pnpm check:published-readme-links0GREEN (imports headingIds from the edited file)
pnpm check:docs-single-h10GREEN
pnpm check:docs-image-tag0GREEN
node scripts/check-self-test-wired.mjs (+ --self-test)0GREEN
node scripts/check-self-test-workflow-commands.mjs (+ --self-test)0GREEN
pnpm lint (repo-wide eslint . --no-inline-config, not narrowed)0GREEN — 2m02s

Repo-wide ESLint was run in full rather than narrowed, so no narrowing is being declared.

Changeset

skip-changeset, applied at PR creation. Check Changeset is path-blind — it counts the
changesets a PR adds against the merge base and fails on zero; its only exemptions are
the skip-changeset label and the Changesets release branch. This PR publishes nothing:
the root manifest is private, and no published package's files field escapes its own
directory, so a repo-root scripts/ file ships in no tarball.

Out of scope, deliberately


Generated by Claude Code

…read as a watch-hint declaration
The remedy line printed for a broken anchor is a command for a human to paste
at the repo root, and it carried the module specifier as a quoted relative
literal `'./scripts/check-doc-anchors.mjs'`. The dispatch watch-hint extractor
reads quoted path-shaped literals in a gate's module body and resolves the
relative ones against the writer's own directory, so this gate — which lives in
scripts/ — declared a path that has never existed on this tree, and that lead
was pasted into every dispatch prompt the family matched.
Neither half was wrong on its own; the fabrication came from prose being read as
a declaration. Tightening the reader was measured and refused upstream, so the
producer spells the path absolutely instead: process.cwd() + a leading-slash
literal, which the extractor's admission test refuses outright while the command
stays runnable verbatim from the repo root.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-project-manager@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); fix(check-doc-anchors): spell the heading-ids remedy so it cannot be read as a watch-hint declaration by os-project-manager · Pull Request #13775 · objectstack-ai/objectstack · GitHub
Skip to content

fix(check-doc-anchors): spell the heading-ids remedy so it cannot be read as a watch-hint declaration - #13775

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-13449-doc-anchors-remedy-literal
Aug 31, 2026
Merged

fix(check-doc-anchors): spell the heading-ids remedy so it cannot be read as a watch-hint declaration#13775
os-project-manager merged 1 commit into
mainfrom
claude/issue-13449-doc-anchors-remedy-literal

Conversation

@os-project-manager

Copy link
Copy Markdown
Collaborator

Fixes#13449

check:doc-anchors printed a remedy command whose module specifier was a quoted
relative literal. The dispatch watch-hint extractor reads quoted path-shaped literals
out of a gate's module body and resolves the relative ones against the directory of the
file that wrote them — so this gate, which lives in scripts/, declared a path with its
own prefix doubled. That lead named a file that has never existed on this tree, and it
was pasted into every dispatch prompt whose file surface brushed the family.

Neither half was wrong on its own: the remedy is correct for its reader, the extractor is
correct about a declaration. The fabrication came from one being read as the other, so the
repair is local to the producer — spell the path so it cannot be read as a declaration.
Per the triage ruling, the extractor is not touched (upstream measured the tightening
at 8.6 real populations deleted per fabrication removed), and nothing in this delivery
touches scripts/pm/dispatch-gates.mjs.

The change

One line in scripts/check-doc-anchors.mjs, plus the rationale comment above it:

- " node -e \"import('./scripts/check-doc-anchors.mjs').then(m=>…+ " node -e \"import(process.cwd()+'/scripts/check-doc-anchors.mjs').then(m=>…

The leading / inside the quotes is the whole mechanism. extractWatchHints' admission
test is /^[\w.@][\w.@/*-]*$/ — a literal must begin with a word character, . or @
so an absolute path is refused outright, before the module-relative resolve can ever run.
process.cwd() is what keeps the command runnable verbatim from the repo root.

A2.1 — the defect was still live at my ref

Re-confirmed at c42bc8ee6 (my branch point), not inherited from the dispatch order:

git ls-files 'scripts/scripts/*' -> 0
git ls-files 'scripts/*' -> 299 (209 at the top level) <- the control

The zero is a true zero, so #13312's closing PR did not absorb this. The card correctly
gets a PR rather than a close.

A2.2 — census: 1 site, and it is the only one

Two independent sweeps, both run through the real extractor rather than by eye.

Fleet sweep — every tracked gate/tooling script under a scripts/ directory
(333 files), classifying each extracted hint that reaches nothing in the tree, and
flagging the doubled-prefix signature (a dead hint that becomes a real path when one copy
of the writer's own directory is stripped):

scripts scanned: 333
hints total: 1500 dead (reach nothing): 736 DOUBLED-PREFIX class: 1
scripts/check-doc-anchors.mjs
fabricated: scripts/scripts/check-doc-anchors.mjs
real path : scripts/check-doc-anchors.mjs

Same-file sweep — every quoted module-relative literal in check-doc-anchors.mjs:
4 of them, and the other 3 are genuine sibling import specifiers
(./import-prerequisite.mjs, ./check-adr-links.mjs, ./invoked-as.mjs), which the
extractor already refuses as single-segment siblings. Line 459 was the only remedy string.

1 hit, fixed; 0 identical second sites left behind. After the change the same fleet
sweep reads hints total: 1499 · dead: 735 · DOUBLED-PREFIX class: 0 — exactly one
hint removed and nothing else moved.

The remaining 735 dead hints are the fixture-constant class #13312 owns, a different
cause; they are untouched here.

A2.3 — reverse verification: the extractor emitted it before and does not after

Run against the committed implementation, mutating the tree back to the pre-fix spelling
and restoring it, with the mutation proved on disk by anchored grep -c on both the
injected and the removed text, and the restore proved by observed state (empty
git diff HEAD plus a blob-hash comparison against the HEAD blob) rather than by an exit
code. The extractor is a pure string function over the source file as read from disk, so
there is no build artifact between the mutation and the reading.

HEAD blob for target: cb30a9f975574aa65d251be2f52f33c6c1eac4a9
=== LEG 1: MUTATE (pre-fix relative spelling put back) ===
injected import('./scripts/... : 1
removed process.cwd()+'/... : 0
blob hash now vs HEAD blob : 4d12eae58b70… (HEAD cb30a9f97557…)
extractor reading on the MUTATED tree:
HINT COUNT: 4
HINT "ARCHITECTURE.md/**"
HINT "README.md/**"
HINT "content/**"
HINT "scripts/scripts/check-doc-anchors.mjs"
FABRICATED PRESENT: true
=== LEG 2: RESTORE (git checkout HEAD -- ABSOLUTE_PATH) ===
git diff HEAD (must be empty) : EMPTY
blob hash == HEAD blob : cb30a9f97557… == cb30a9f97557…
injected import('./scripts/... : 0
restored process.cwd()+'/... : 1
extractor reading on the RESTORED tree:
HINT COUNT: 3
HINT "ARCHITECTURE.md/**"
HINT "README.md/**"
HINT "content/**"
FABRICATED PRESENT: false

Direction observed: the fabricated hint disappears and the three real declarations stay
content/**, README.md/**, ARCHITECTURE.md/** are byte-identical before and after,
and nothing new is added. That last half matters: a spelling that merely moved the literal
(say, a bare scripts/check-doc-anchors.mjs) would have been admitted as a true hint
and quietly given this gate a population it does not read. This one declares nothing.

The same result is confirmed by the tool's own suite: dispatch-gates.mjs --self-test
reads this file live and asserts that the doc-anchors family still reaches
content/docs/deployment/cli.mdx, still claims README.md and ARCHITECTURE.md, and
still claims neither instruction file. 1017 cases pass.

One more observed side-effect, in the right direction: the rationale comment added above
the line spells the relative path in prose, and the module-body sweep confirms comment
masking removes it — the file's RAW text has 4 module-relative literals, its masked module
body has 3.

A2.4 — the remedy still runs verbatim from the repo root

The gate was driven to its failure branch against a throwaway fixture root so the remedy
is the text a human actually sees:

 The heading id is computed with `github-slugger`, the same package
fumadocs-core uses to render the page, so what this gate says the anchor is
is what the site says it is. Print a page's real ids with:
node -e "import(process.cwd()+'/scripts/check-doc-anchors.mjs').then(m=>console.log(m.headingIds(require('node:fs').readFileSync('FILE','utf8')).join('\n')))"

(FILE above stands for the literal placeholder the gate prints, which the reader replaces.)

Pasted verbatim at the repo root with the placeholder filled in:

$ node -e "import(process.cwd()+'/scripts/check-doc-anchors.mjs').then(m=>console.log(m.headingIds(require('node:fs').readFileSync('content/docs/index.mdx','utf8')).join('\n')))"
objectstack-documentation
start-here
platform-modules
protocol--reference
EXIT=0

Byte-identical to the same run under the old spelling — diff of the two captures is
empty. Readability does not regress: the command is one token longer and still a single
paste.

Gates

Family derived after the final commit with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (no paths passed —
the script takes its own change set; it reported 1 path(s) vs merge base c42bc8ee6).
Every exit code below was captured before any pipe, and each verdict is the gate's own
printed line. All runs are on the final commit fd2ea143f.

Path-derived family (13):

gateexitverdict
pnpm check:agent-test-spelling0GREEN
pnpm check:bash32-floor0GREEN
pnpm check:cli-command-ids0GREEN
pnpm check:cross-package-test-inputs0GREEN
pnpm check:doc-anchors0GREEN — check-doc-anchors: 287 internal #fragment link(s) across 409 source file(s) all resolve to a real heading
pnpm check:entry-guard0GREEN
pnpm check:parse-guard0GREEN
pnpm check:pnpm-filter-targets0GREEN
pnpm check:watch-hint-literal0GREEN — 34 declaration(s) across 4 rostered name(s) … no unrostered spelling of the idiom in the tree
node scripts/check-ci-filter-parity.mjs0GREEN
node scripts/check-cross-package-test-inputs.mjs0GREEN
node scripts/check-shard-attestation.mjs0GREEN
node scripts/check-test-completeness.mjs3NOT MEASUREDPREREQUISITE NOT MET; it grades a saved turbo run test log and none was named. Its own text says exit 3 is not a finding.

Convention-triggered (editing a gate script), both named by the derivation:

gateexitverdict
node scripts/pm/bare-root-worklist.mjs --self-test0GREEN — 51 live row(s), 43 unreachable as spelled, 43 recorded verdict(s) — none stale, none missing, none contradicted
pnpm check:pm-dispatch-gates0GREEN — dispatch-gates self-test: 1017 cases pass.

Beyond the derived list, the gate script's own suite plus the consumers that read this
file by name (the "which tests test this script" half the path derivation cannot answer):

gateexitverdict
pnpm check:nul-bytes0GREEN — scanned 7578 text file(s) … no raw ASCII control bytes
pnpm check:published-readme-links0GREEN (imports headingIds from the edited file)
pnpm check:docs-single-h10GREEN
pnpm check:docs-image-tag0GREEN
node scripts/check-self-test-wired.mjs (+ --self-test)0GREEN
node scripts/check-self-test-workflow-commands.mjs (+ --self-test)0GREEN
pnpm lint (repo-wide eslint . --no-inline-config, not narrowed)0GREEN — 2m02s

Repo-wide ESLint was run in full rather than narrowed, so no narrowing is being declared.

Changeset

skip-changeset, applied at PR creation. Check Changeset is path-blind — it counts the
changesets a PR adds against the merge base and fails on zero; its only exemptions are
the skip-changeset label and the Changesets release branch. This PR publishes nothing:
the root manifest is private, and no published package's files field escapes its own
directory, so a repo-root scripts/ file ships in no tarball.

Out of scope, deliberately


Generated by Claude Code

…read as a watch-hint declaration
The remedy line printed for a broken anchor is a command for a human to paste
at the repo root, and it carried the module specifier as a quoted relative
literal `'./scripts/check-doc-anchors.mjs'`. The dispatch watch-hint extractor
reads quoted path-shaped literals in a gate's module body and resolves the
relative ones against the writer's own directory, so this gate — which lives in
scripts/ — declared a path that has never existed on this tree, and that lead
was pasted into every dispatch prompt the family matched.
Neither half was wrong on its own; the fabrication came from prose being read as
a declaration. Tightening the reader was measured and refused upstream, so the
producer spells the path absolutely instead: process.cwd() + a leading-slash
literal, which the extractor's admission test refuses outright while the command
stays runnable verbatim from the repo root.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Pk26oZ12t5N1hwGW1m1MgC
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-project-manager@claude