Filed unassigned, measured while stripping the projected issue-ids in #11930 (out of that card's scope — that one strips ids, this is about which doc block gets published at all).
What
packages/spec/scripts/build-skill-references.ts derives each _index.md entry's description with its own extractDescription():
constjsdocMatch=content.match(/\/\*\*\s*\n([\s\S]*?)\*\//);
That is the first JSDoc block anywhere in the file, verbatim — a rule about ordering, not about descriptions. Whichever declaration happens to sit nearest the top donates its comment to a customer-facing page.
This is the same defect that was measured and fixed on the docs-site side. build-docs.ts no longer does this: it calls findModuleDocBlock() from packages/spec/scripts/lib/file-description.ts, which follows TSDoc's own rule read back — the block must start at column 0 and precede the first declaration. packages/spec/CHANGELOG.md records the original measurement (six victim pages) and the reasoning:
getFileDescription() took the first doc block anywhere in a *.zod.ts file, verbatim, and published it as the page's opening paragraph. That is not a rule about descriptions — it is a rule about ordering […] Adding a helper above the first schema silently rewrote a published page, and no gate could see it.
The fix landed in the docs generator only. The skill-references generator was never converted, and it publishes to a surface with a strictly higher cost curve: skills/** is loaded whole into customer agent context windows.
Live victim
packages/spec/src/system/translation.zod.ts has no module docblock. Its first JSDoc block documents a private constant, TRANSLATION_HISTORY. So skills/objectstack-i18n/references/_index.md currently opens its only core-schema entry with:
- `node_modules/@objectstack/spec/src/system/translation.zod.ts` — Shared history sentence for every shape in this file.
"Shared history sentence for every shape in this file" describes an internal string constant used to build .describe() text. It is not a description of the Translation protocol, and it is meaningless to the customer reading it. The docs site does not have this problem for the same file — findModuleDocBlock() correctly declines that block — which is the cleanest possible demonstration that the two generators disagree.
Note this is exactly the file the CHANGELOG names as a victim of the original defect ("The Translation protocol reference opened with Shared history sentence for every shape in this file"), so the docs-side repair is confirmed and the skills-side one is confirmed missing.
Why no gate catches it
check:skill-refs compares the generated file against what the generator produces — it reproduces the wrong block faithfully, so there is no drift to report. Same green-through-the-defect shape the docs side had.
What a card here would do
- Replace
extractDescription() in build-skill-references.ts with the shared findModuleDocBlock() + renderFileDescription() path, rather than porting the rule a second time — one selector, two consumers. - Regenerate and read the diff as a census: every
_index.md line that changes is a file that was donating the wrong block. Some will fall through to the Exports: … fallback, which is a separate authoring question (does that file want a real module docblock?) and may want its own follow-up. - Decide whether the fallback chain is right for a customer-facing surface at all, or whether a
.zod.ts with no module docblock should be a loud refusal instead of a quiet Exports: line.
⚠️skills/** is a governed surface — whatever lands here is draft + human merge, and the regeneration diff should be reviewed line by line rather than accepted wholesale.
Filed unassigned, measured while stripping the projected issue-ids in #11930 (out of that card's scope — that one strips ids, this is about which doc block gets published at all).
What
packages/spec/scripts/build-skill-references.tsderives each_index.mdentry's description with its ownextractDescription():That is the first JSDoc block anywhere in the file, verbatim — a rule about ordering, not about descriptions. Whichever declaration happens to sit nearest the top donates its comment to a customer-facing page.
This is the same defect that was measured and fixed on the docs-site side.
build-docs.tsno longer does this: it callsfindModuleDocBlock()frompackages/spec/scripts/lib/file-description.ts, which follows TSDoc's own rule read back — the block must start at column 0 and precede the first declaration.packages/spec/CHANGELOG.mdrecords the original measurement (six victim pages) and the reasoning:The fix landed in the docs generator only. The skill-references generator was never converted, and it publishes to a surface with a strictly higher cost curve:
skills/**is loaded whole into customer agent context windows.Live victim
packages/spec/src/system/translation.zod.tshas no module docblock. Its first JSDoc block documents a private constant,TRANSLATION_HISTORY. Soskills/objectstack-i18n/references/_index.mdcurrently opens its only core-schema entry with:"Shared history sentence for every shape in this file" describes an internal string constant used to build
.describe()text. It is not a description of the Translation protocol, and it is meaningless to the customer reading it. The docs site does not have this problem for the same file —findModuleDocBlock()correctly declines that block — which is the cleanest possible demonstration that the two generators disagree.Note this is exactly the file the CHANGELOG names as a victim of the original defect ("The Translation protocol reference opened with
Shared history sentence for every shape in this file"), so the docs-side repair is confirmed and the skills-side one is confirmed missing.Why no gate catches it
check:skill-refscompares the generated file against what the generator produces — it reproduces the wrong block faithfully, so there is no drift to report. Same green-through-the-defect shape the docs side had.What a card here would do
extractDescription()inbuild-skill-references.tswith the sharedfindModuleDocBlock()+renderFileDescription()path, rather than porting the rule a second time — one selector, two consumers._index.mdline that changes is a file that was donating the wrong block. Some will fall through to theExports: …fallback, which is a separate authoring question (does that file want a real module docblock?) and may want its own follow-up..zod.tswith no module docblock should be a loud refusal instead of a quietExports:line.skills/**is a governed surface — whatever lands here is draft + human merge, and the regeneration diff should be reviewed line by line rather than accepted wholesale.