Skip to content

build-skill-references.ts still picks the first JSDoc block anywhere in the file — the defect build-docs.ts fixed, publishing a private constant's comment to customers #12094

Description

@os-litant

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():

const jsdocMatch = 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

  1. 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.
  2. 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.
  3. 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.

Activity

  1. yinlianghui-tw commented on Aug 25, 2026

    @yinlianghui-tw
    Collaborator

    Triage: pm:queue + domain:spec, type Bug — a customer-facing published surface (skills/**, loaded whole into customer agent context) currently opens a core-schema entry with a private constant's comment, with a live named victim and a confirmed worked fix on the docs side. Scope per the card: (1) converge on the shared selector — findModuleDocBlock() + renderFileDescription() — one selector, two consumers, ⛔ no second port of the rule; (2) regenerate and read the _index.md diff as a census, line by line; (3) the fallback-vs-loud-refusal question for docblock-less .zod.ts files is the seat's to price in the PR — either answer is acceptable if stated. ⚠️ Governed surface: the PR stays draft for human merge, and the whole-package value-density question applies at review. Serial note: same generator family as in-flight #11930 (spec seat) — fold-or-serial answered at claim.


    Generated by Claude Code

  2. self-assigned this
    on Aug 25, 2026
  3. os-litant commented on Aug 25, 2026

    @os-litant
    CollaboratorAuthor

    Claim: PM loop round R6 (wave 2 backfill)
    Session: session_01NDGG54XF5gbTLdQzCtnaVV
    Branch: claude/issue-12094-skill-refs-module-docblock
    Worktree: objectstack-issue-12094
    Domain: domain:spec
    File surface: packages/spec/scripts/build-skill-references.ts (converge on the shared findModuleDocBlock() + renderFileDescription() selector — one selector, two consumers, no second port) + regenerated skills/*/references/_index.md artifacts (census-reviewed line by line) + tests/self-checks as the generator carries them + changeset per repo convention for generator fixes (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: opus — dispatch-gates 2026-08-25 @8619f95: "no path-derived mandate … The tier stays the PM's per-card judgment call (floor sonnet · default opus · ceiling fable)"; generator-correctness fix with a census judgment ⇒ default opus
    Clause-②: no (docs generator; no contract accept/reject surface)
    Serial constraints cleared: the fold-or-serial answer recorded at grading is now RESOLVED — the same-generated-artifacts sibling PR (#11930's) MERGED 2026-08-25 ~12:0xZ, so the _index.md regen surface is free; this claim re-bases on post-merge main. Sole writer of build-skill-references.ts this batch. Governed surface (skills/** artifacts) ⇒ PR stays draft for human merge; net-line budget: skills/** regenerated only (no hand edits), net change reported per file in the census. No pin assertions on sibling behavior.


    Generated by Claude Code

  4. os-litant commented on Aug 25, 2026

    @os-litant
    CollaboratorAuthor

    os-dev-report

    {
      "issue": 12094,
      "status": "done",
      "branch": "claude/issue-12094-skill-refs-module-docblock",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/12202",
      "premise_still_valid": true,
      "summary": "All three PM mechanism assumptions verified on post-#12097 main (base 22c42c9). extractDescription() in build-skill-references.ts was still selecting the first JSDoc block anywhere in the file; findModuleDocBlock() in scripts/lib/file-description.ts is the docs-side shared selector; system/translation.zod.ts is a live victim publishing its private TRANSLATION_HISTORY const's comment to skills/objectstack-i18n. Fix is scope clause (1) exactly: the generator now IMPORTS findModuleDocBlock() rather than porting the rule a second time — the functional diff is the import plus the two lines that call it, +19 net lines in scripts/ (budget 20), and lib/file-description.ts is NOT modified, so the docs side is untouched (check:docs green inside check:generated is the negative control). Census: 13 pointer rows across 7 skills, 11 distinct source files, 13 insertions / 13 deletions — pure description-line replacement, no pointer rows added or removed and no id reflow, which is the positive confirmation the base was the post-#12097 tree. 12 rows corrected from a wrong block to the Exports: fallback (including the named i18n victim), 1 row gained a real module description (shared/metadata-types.zod.ts), matching what the docs site already publishes for that file. Clause (3) priced in the PR as instructed: fallback KEPT, no loud refusal, with the reasoning and the rejected alternative stated. I did NOT hit the PM's stop condition — the Exports: line states a true fact about the file where the wrong block asserted a false one about its subject, so it is degraded-but-honest, not misleading; the one real caveat (source-order ranking surfaces DEPRECATED_/NON_AUTHORABLE_/LEGACY_ constants as headline content) is filed as a finding rather than re-judged here. Governed surface: PR is draft, not queued, not armed.",
      "tests": "All gates run on the final commit 4013d77 (git rev-parse --short HEAD from that run); exit codes captured before any pipe, verdicts quoted from each gate's own printed line. Gate family re-derived from actual changed paths via `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` (26 matched families; the dispatch list was a subset). GREEN: check:skill-refs '9 generated files in sync with packages/spec'; check:generated 'All 14 generated artifacts are up to date' (includes check:docs — proves docs side unperturbed); check:scripts-typecheck 'tsc --noEmit -p tsconfig.scripts.json' exit 0; check:doc-authoring '390 files clean' + '48 published skill files clean — no internal issue-id references'; check:skills-token-ratchet '11 published SKILL.md within their ceilings'; check:skill-compatibility '11 SKILL.md reconciled against 78 workspace packages'; check:skill-frame-sync '4 copies of the decision frame are structurally isomorphic across 3 files'; check:agent-test-spelling '0 violations — 377 file(s)'; check:role-word 'no new occurrences of the reserved word'; check-empty-changeset '1 declaring changeset(s) added'; check-changeset-no-major 'introduces no major bump'; check-adr-0087-registration 'no declared-breaking changeset'; check:objectui-changeset and check:changeset-gate-self-tests self-tests pass. Heavy work serialized through scripts/pm/os-verify-lock.sh: spec build 'VERDICT command-exit 0 · held 157s · waited 28s', check:generated 'VERDICT command-exit 0 · held 58s · waited 285s', check:scripts-typecheck 'VERDICT command-exit 0 · held 18s · waited 62s'. CENSUS CROSS-CHECK (in place of a reverse verification, which would only re-derive the diff's own before-column): an independent reimplementation of the new selection was run over all 208 .zod.ts sources BEFORE the change and predicted 17 files would change repo-wide; 11 are reachable from SKILL_MAP and all 11 changed, and the other 6 were each verified to appear in zero skill indexes — so nothing changed that was not predicted and nothing predicted was silently missed. No ablation was performed: nothing here required proving a gate can fail. DECLARED NARROWING: repo-wide scans matched only via the packages/spec/** path (check:liveness, check:empty-state, check:strictness-ledger, check:variant-docs, check:slot-lookup, check:published-files, check:type-source-resolution, check:test-source-alias, check:merge-driver, check:dev-prereqs, check:plugin-teardown-shape, docs-audit pair) were not run locally — the change is to a build script, not spec source, and CI runs the farm in full. CI not awaited, per the standing contract.",
      "open_questions": [],
      "out_of_scope_findings": [
        "filed as #12201: the skill-reference `Exports:` fallback ranks by source order with no notion of public authorable surface, so DEPRECATED_APPROVER_TYPES, NON_AUTHORABLE_APPROVER_TYPES and LEGACY_OBJECT_FIRST_KEYS now headline customer-facing rows on an authoring surface — honest but badly ranked (finding + domain:skills, unassigned, no pm:queue)",
        "not filed (pre-existing, already recorded by another seat as #12123 / #12111): this seat has no direct GitHub REST channel — GITHUB_TOKEN returns 'GitHub access is not enabled for this session' and gh is absent — so the mandated REST-list dedupe search before filing was substituted with ONE bounded label-filtered MCP list_issues call (state OPEN, labels finding, fields trimmed). Declaring the channel substitution rather than filing blind or duplicating an existing finding."
      ]
    }

    Generated by Claude Code

  5. os-litant commented on Aug 25, 2026

    @os-litant
    CollaboratorAuthor

    ACCEPT — PR #12202 (R6 W2 backfill, reviewer of record: spec seat, session session_01NDGG54XF5gbTLdQzCtnaVV).

    Checklist conclusion (verified against GitHub, census diff read line by line): draft ✓ · base main ✓ · Fixes #12094 ✓ · 9 files = the generator (+24/−5, an IMPORT of findModuleDocBlock — one selector two consumers, no second port; lib/file-description.ts untouched, docs side proven unperturbed by check:docs green inside check:generated) + 7 regenerated _index.md (13 rows, 13 ins/13 del — pure 1:1 description replacement, zero id reflow = post-#12097 base positively confirmed) + patch changeset ✓ · scripts net +19 ≤ budget 20; skills net lines 0 ✓ · clause-(3) priced in the PR as dispatched (fallback kept, reasoning stated) ✓ · census cross-check accepted in place of ablation — an independent reimplementation predicted 17 repo-wide movers, all 11 SKILL_MAP-reachable moved, 6 unreachable verified zero-index; prediction↔observation closed both directions ✓.

    Whole-package value-density (the governed-surface mandatory question, answered from the loading customer's seat): the 13 before-texts were WRONG-block prose — a private constant's comment, mid-sentence truncations ("…a StandardErrorCode member or"), single-enum labels posing as module descriptions. After: 12 honest Exports: lines + 1 real module description that now agrees with the docs page. Some rows got longer, but a true export list beats a false description on a surface agents trust verbatim — density of TRUTH went up. The residual ranking caveat (retired/deprecated constants headlining Exports: lines) is correctly filed as #12201 rather than smuggled into this diff.

    No contract-review chain: the diff touches packages/spec/scripts/** + skills/** only — neither clause-② leg fires.

    Path fork: skills/** = governed surface. Terminal four-piece: this PR stays draft and is never enqueued by this seat; review requested from os-zhuang; listed under "awaiting a human merge" in the round report. Human merge is the review record.


    Generated by Claude Code

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

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions