Skip to content

fix(devx): give check:llms-txt the population it re-derives, not the artifact it guards - #13302

Merged
os-project-manager merged 1 commit into
mainfrom
claude/issue-13207-llms-txt-population
Aug 30, 2026
Merged

fix(devx): give check:llms-txt the population it re-derives, not the artifact it guards#13302
os-project-manager merged 1 commit into
mainfrom
claude/issue-13207-llms-txt-population

Conversation

@claude

@claudeclaudeBot commented Aug 29, 2026

Copy link
Copy Markdown
Contributor

Fixes#13207

check:llms-txt re-derives the claims of the hand-kept packages/spec/llms.txt against trees elsewhere — the *.zod.ts counts under packages/spec/src, the api-surface/ shards, the manifest exports keys, and the non-private @objectstack/* workspace set. But it reached every one of them through join(PKG, ...), so the only repo-relative literal it spelled was llms.txt itself.

scripts/pm/dispatch-gates.mjs derives a card's gate family by scanning each gate's source for path literals. So the derivation could name this gate only after the artifact had already been edited, while every edit that actually falsifies it lands somewhere else. That is under-matching, the silent direction: an over-matching gate costs a wasted run, this one omitted itself and the tool's output gave no signal.

Reproduce-first, on this branch, before the fix

Two rounds, same commit, family derived mechanically each time:

$ node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack \
packages/spec/src/kernel/cluster.zod.ts
-> check:llms-txt ABSENT
$ node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack \
packages/spec/src/kernel/cluster.zod.ts packages/spec/llms.txt
-> pnpm --filter @objectstack/spec run check:llms-txt [lint.yml]
matched via packages/spec/llms.txt ... gate source 'packages/spec/llms.txt'

One route, and it is the artifact. That reproduces the card's measurement exactly.

After

Each input is spelled repo-relative and joined onto ROOT, so the literals stay load-bearing — the file opens exactly the paths it declares, which is what keeps the declaration from drifting from what the gate really reads. The repo-root workspace file and the per-package manifests cannot be spelled as literals this file opens, so they are declared in the established FILE/** form that check-doc-anchors and the pm line ratchet already use.

changed pathnames check:llms-txt?route
packages/spec/src/kernel/cluster.zod.tsyesgate source 'packages/spec/src'
packages/spec/api-surface/data.jsonyesgate source 'packages/spec/api-surface'
packages/spec/package.jsonyesgate source 'packages/spec/package.json'
pnpm-workspace.yamlyesgate source 'pnpm-workspace.yaml/**'
packages/drivers/driver-mongodb/package.jsonyesgate source 'packages/**/package.json'
packages/spec/llms.txtyesunchanged
packages/spec/docs/anything.mdno
packages/rest/src/...no
content/docs/deployment/cli.mdxno
apps/docs/components/ui/card.tsxno
examples/app-crm/src/objects/lead.object.tsno

The acceptance criterion flips: a diff deleting a src/ schema module now names the gate, with llms.txt untouched.

Priced, not assumed

Over the 232 commits this checkout holds: packages/**/package.json matches 8 (3.4%), and the whole declared population matches 47 (20.3%). A blanket packages/spec or packages/** would have bought the flip too — and the "22 leads is the same as none" failure the derivation's own header refuses. The negative rows above are the pins that hold that line.

The anti-drift case earned its keep immediately

Case 21 of the gate's own self-test binds the declaration to the reads. The first draft declared only packages/, and the case went red: pnpm-workspace.yaml also lists apps/* and examples/*, and this gate opens every manifest under them to build the package-ecosystem denominator. Neither root holds a non-private @objectstack/* package today and neither is touched by any of the 232 commits, so declaring them costs nothing measurable and closes the same hole one root over. The case now derives the roots from the real pnpm-workspace.yaml instead of repeating a list, so the next new workspace root fails at the gate that reads it.

Verification

Union re-run at final commit 6fd7443a:

  • pnpm check:pm-dispatch-gatesdispatch-gates self-test: 892 cases pass (879 on base, +13 pinned here)
  • pnpm --filter @objectstack/spec check:llms-txt21 cases pass, then 97 claim(s) re-derived ... (14 domains, 207 schemas) ... (68 published)
  • pnpm --filter @objectstack/spec typecheck — exit 0; confirmed non-vacuous with tsc -p tsconfig.scripts.json --listFiles, which lists the edited file (1 hit)
  • pnpm lint (repo-wide eslint . --no-inline-config) — exit 0, clean
  • pnpm check:nul-bytes — 7359 files, no raw control bytes
  • pnpm check:watch-hint-literal, check-self-test-wired, and the rest of the derived family: 39 of 43 pass; the other 4 are self-declared NOT MEASURED / PREREQUISITE NOT MET on an unbuilt tree or without a real GitHub token, not reds. This diff provably cannot move them: every tsup entry is under src/, and packages/spec/scripts/ is not in the package files array, so nothing here reaches any dist/.

Ablation (no build or dist/ is involved — the self-test reads the gate source directly). Mutation applied to a committed tree, restore leg pinned to HEAD with an absolute path and an EXIT INT TERM trap:

  • on-disk confirmation: the deleted text const SRC_DIR = 'packages/spec/src'; went to 0 occurrences and the blob hash moved 916a9990 -> 4948e188
  • ablated, the derivation stops naming the gate for a src-only diff — the hole reopens
  • ablated, the register self-test goes red: 2 of 892 case(s) failed, precisely the two src cases
  • restored, git diff HEAD is empty

Landing

node scripts/pm/check-governed-merges.mjs --test on the final file list: 0 of 2 path(s) hit the registerNOT governed, ordinary queue landing. No changeset: the diff publishes nothing (packages/spec/scripts/ is not in files, scripts/pm/ ships from no package), so skip-changeset applies.

Scope

Disposition 1 only. Disposition 2 is #13300 in the skills lane and is not addressed here.

The card's follow-up question — does any other gate declare a population equal to its own guarded artifact — was run as a census over the register and is reported to the PM seat as a reading, not acted on: 33 of 183 discovered families have a non-self population of two files or fewer. Separately, #13301 was filed for check:watch-hint-literal guarding only ROOT_DIR_WATCH_HINTS while 41 ROOT_FILE_WATCH_HINTS mentions carry the same silent-drop mechanism.


Generated by Claude Code

…artifact it guards
check:llms-txt verifies packages/spec/llms.txt against trees elsewhere - the
*.zod.ts counts under packages/spec/src, the api-surface/ shards, the manifest
exports keys, and the non-private @objectstack/* workspace set - but reached
every one of them through join(PKG, ...), so the only repo-relative literal it
spelled was llms.txt itself.
scripts/pm/dispatch-gates.mjs derives a card's gate family by scanning each
gate's source for path literals, so it could name this gate only AFTER the
artifact had been edited, while every edit that falsifies it lands somewhere
else. Measured on PR #13186 across two rounds of one branch: deleting a src/
schema module moved src/kernel/ 32 -> 31 and the summed total 208 -> 207, the
derived family did not contain the gate, and the red reached CI instead of the
local sweep. The direction is the bad one - over-matching costs a wasted run,
this under-matched silently and the tool's output gave no signal.
Each input is now spelled repo-relative and joined onto ROOT, so the literals
stay load-bearing: the file opens exactly the paths it declares. The repo-root
workspace file and the per-package manifests, which cannot be spelled as
literals this file opens, are declared in the established <file>/** form that
check-doc-anchors and the pm line ratchet already use.
Case 21 of the gate's own self-test binds the declaration to the reads, and
earned that immediately: the first draft declared only packages/, and the case
caught apps/* and examples/* being workspace roots this gate also opens. It
derives the roots from the real pnpm-workspace.yaml rather than repeating a
list, so the next new root fails at the gate that reads it.
Thirteen pinned cases in the register's self-test hold both halves - the four
trees are reached, and nothing else is. Priced over the 232 commits this
checkout holds: the whole declared population matches 47 of them (20.3%), where
a blanket packages/** would have bought the "22 leads is the same as none"
failure the derivation's own header refuses.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KX8wnyjStaZcuMyAMNsy3N
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

What this run could not see
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 74049254d47bd0edd2a2fcd732dcc01c91504f10packageMentionDocs.

Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/mskip-changesetPR has no user-facing published change; bypasses the changeset gatetooling

Projects

None yet

2 participants

@os-project-manager@claude