Repository navigation
[finding] @example and @category tag lines render verbatim on 13 published reference pages — a tag WITH a payload needs a rewrite, not the @module drop #14455
Description
Activity
- addedbugSomething isn't workingSomething isn't workingdocumentationImprovements or additions to documentationImprovements or additions to documentation
on Sep 4, 2026 Triage: lands in
packages/spec/scripts/lib/file-description.ts⇒domain:spec(the lane ownspackages/spec/scripts/**and the toolchain that turns on the spec contract). GradedBug·priority:p3.Bugby the mechanical boundary test: nothing widens, a renderer emits raw tag text where reader prose was intended.priority:p3— 13 customer-facing reference pages show literal@example/@categorytext. Visible and embarrassing, ⛔ not harmful: no contract is wrong, nothing fails, and the surrounding prose still reads.⛔ Not sent to the decision inbox, and the reason matters for the shape of the work
The card says both tags need a decision about the rendered shape before anyone writes code. Read against the escalation threshold, only half of that is a decision, and it is the smaller half:
@example CAPTIONis mechanical. The payload is the caption of the fence directly beneath it — the card establishes that, and it is checkable on the page. It renders as a caption; the two bare@examplelines with no payload (studio/plugin,studio/object-designer) render as nothing, since a tag with no payload is the@modulecase the renderer already drops. ⛔ No judgement is owed here.@category Securityis the open one — reader-facing prose, page frontmatter, or machinery to drop. Four pages, allSecurity.
⇒ Routed as work rather than as a decision. The spec seat picks the
@categoryshape as part of doing it, and escalates to the inbox only if it judges the classification a product-surface call rather than a rendering one. Putting a four-line docs-rendering question in the maintainer's inbox ahead of its owner having looked would be premature.What the card already establishes and should not be re-derived
⭐ The right instrument is one line away and named:
renderProsealready rewrites@see pathintoSee also: pathrather than dropping it. That is the precedent for a tag with a payload, and it is in the same file.⛔ A blanket
^@\w+line filter is the wrong instrument and the card is right to rule it out — it would take reader prose off the page and orphan the fences.⚠️ check:docsis structurally blind to this class: it compares the artifact against the source, and the artifact reproduces the tag faithfully. ⇒ Assert on the rendered fragment, ⛔ not on the emitted.mdx, or the pin will pass while the page stays wrong. Unit pins go inpackages/spec/scripts/file-description.test.tswith a corpus assertion re-derived frompackages/spec/src, matching the neighbouring cases in that file.⚠️ The 18 cited lines are from 2026-09-02. Re-run the card's own repro before editing (grep -rn '^@example\|^@category' content/docs/references/) — it is the acceptance instrument, so a stale baseline makes the count unprovable.
Generated by Claude Code
Claim: PM loop round R1
Session:session_01G4138K1EG7kQ81FNba5Kp4
Branch:claude/issue-14455-doc-tag-payload-render
Worktree:objectstack-issue-14455
Domain:domain:spec
File surface:packages/spec/scripts/lib/file-description.ts·packages/spec/scripts/file-description.test.ts· the regeneratedcontent/docs/references/**pages that follow from the renderer change (stop on breach; explain in the report)
Container & model:M,mode:subagent,model: opus—node scripts/pm/dispatch-gates.mjs --tier packages/spec/scripts/lib/file-description.tsrun at 2026-09-04T15:12Z against the tree at097a01c: "no path-derived mandate", tier is the PM's per-card call (floor sonnet · default opus · ceiling fable).opusbecause the card carries a rendered-shape judgement, not a mechanical substitution.
Clause-②: no — the diff changes how a module docblock is RENDERED into published prose. It touches nopackages/spec/src/**contract file, changes no accept/reject behaviour and widens no public surface.packages/spec/scripts/**is this lane's toolchain surface, not the contract surface.
Serial constraints cleared:packages/spec/scripts/lib/file-description.tsandpackages/spec/scripts/file-description.test.tsare unreserved on this seat's hot-file queue at 2026-09-04T15:12Z; the skill-references generator was released by445a25f6and is a different file. In-flight/queued PRs at 15:12Z: PR #15386 (#14788) and PR #15281 (#15229) — both disjoint from this surface.⚠️ Shared generated corpus with the same-round dispatch of #14079: this card regeneratescontent/docs/references/**wholesale (the renderer moves), #14079 regenerates only its own followers. Source surfaces disjoint; the generated corpus lands serially — whichever PR is enqueued second mergesmainthroughscripts/pm/os-regen-merge.shand re-regenerates before it may enqueue.
Generated by Claude Code
Claim: os-dev, dispatched by the
domain:specPM seat, round R1.
Session:session_01G4138K1EG7kQ81FNba5Kp4
Branch:claude/issue-14455-doc-tag-payload-render
Worktree:objectstack-issue-14455(base52d5a52)
Clause-2: no — this changes how a module docblock is RENDERED into published prose. It touches nopackages/spec/src/**contract file, changes no accept/reject behaviour and widens no public surface.Working to the triage grading comment
5542169855:@example CAPTIONis mechanical (caption of the fence beneath it); the@categoryrendered shape is this seat's call. Pins assert on the RENDERED fragment, not on the emitted.mdx.
Generated by Claude Code
os-dev-report
{ "issue": 14455, "status": "done", "branch": "claude/issue-14455-doc-tag-payload-render", "pr": "https://github.com/objectstack-ai/objectstack/pull/15443", "premise_still_valid": true, "summary": "Premise holds, with one correction to the card's arithmetic. Re-ran the card's own repro on current main before editing (H2): still 18 lines, the identical set (14 `@example` + 4 `@category`) — but the affected page sets are disjoint 10 + 4, so it is 14 pages, not the 13 the card body and title say. H1 verified: `renderProse` really does rewrite `@see path` into `See also: path`, in the same file, and that is the shape the fix follows. H3 verified: `renderFileDescription` is in `packages/spec/scripts/lib/file-description.ts`; the producer is that renderer, so no consumer-side patch. H4 measured: `gen:docs` regenerated 230 files and exactly 14 changed — the 14 the repro names, nothing else, no unexpected page moved. Implementation: `@example CAPTION` (12 lines / 10 pages) is REWRITTEN to a bold caption line beside the `@see` precedent; a bare `@example` (2 lines) and `@category VALUE` (4 lines) are DROPPED before classification alongside the existing marker drops. Bold rather than a heading because heading renumbering has already run by then, so an emitted heading would carry a level chosen blind of the page, add 12 TOC entries across 8 pages and reach `check:docs-single-h1`. R2's overturn measurement came out NEGATIVE — no consumer of `@category` exists anywhere in the repo: no typedoc or api-extractor (neither is a dependency here), no search index, no gate; the only matches outside `packages/spec/src` were the pin asserting the defect, the renderer's own comment and a CHANGELOG line. So R2 holds and the drop stands. The tags stay in the sources; only the rendered page drops them. Clause-2 stayed `no`: no `packages/spec/src/**` contract file was touched and no accept/reject behaviour moved. A changeset was added (patch to @objectstack/spec) rather than the `skip-changeset` label, following #13796 — the same class of change — which shipped one.", "tests": "Union re-run on final head 954d10c76, after merging main via scripts/pm/os-regen-merge.sh and re-regenerating the whole chain (gen:schema + gen:docs came back byte-identical, so the os-regen merge driver dropped nothing). (1) `pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2` under scripts/pm/os-verify-lock.sh — VERDICT command-exit 0, `Test Files 470 passed | 1 skipped (471)`, `Tests 12649 passed | 1 skipped (12650)`, 0 FAIL lines. (2) `pnpm --filter @objectstack/spec typecheck` — VERDICT command-exit 0; NOT-MEASURED trap checked: `tsc -p tsconfig.scripts.json --listFiles` counts 1 (not 0) for each of the two edited files, so the green verdict really covers them, and 0 `error TS` lines. (3) `pnpm lint` (repo-wide `eslint . --no-inline-config`) — VERDICT command-exit 0, so no narrowing and no narrowing-evidence is claimed. (4) `pnpm --filter @objectstack/spec check:docs` — its own verdict line: `230 generated files in sync with packages/spec`. (5) Gate family re-derived with `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` (no paths passed) on both heads: 17 paths, identical family across the merge. All implicated families exit 0: check:skill-refs, check:skill-docs, check:docs-single-h1, check:doc-anchors, check:doc-authoring, check:docs-redirects, check:corpus-claim-drift, check:quick-reference-counts, check:page-declaration-shape, check:docs-audit-scope, check:nul-bytes, check:cross-package-test-inputs, check:test-source-alias, check:changeset-gate-self-tests, check:published-files. Every exit code captured by redirect-then-capture, never through a pipe. (6) ACCEPTANCE: the card's repro `grep -rn '^@example|^@category' content/docs/references/` now returns 0 hits (was 18); no `^@` tag line of any kind remains in the tree. (7) ABLATION — predicted direction RED, observed RED. Restoring origin/main's file-description.ts turns 8 cases red: 5 of the 7 new #14455 unit cases, both new corpus assertions, and the retriaged #10924 case. The 2 that stay green assert the ABSENCE of over-reach (a tag inside a fence, a mid-sentence mention), which the pre-fix renderer also satisfied by doing nothing — that asymmetry is expected, not a gap. NO REBUILD WAS NEEDED OR DONE and that is a property of the pair, not an omission: the test imports './lib/file-description' by relative path inside the same package, so no dist and no vitest alias sits between subject and test. MUTATION CONFIRMED ON DISK: `git hash-object` of the path = 2464af9a (HEAD blob 73c63999, non-empty and different), and grep counts of EXAMPLE_CAPTION / CATEGORY_MARKER on disk both fell to 0. RESTORE CONFIRMED: trap-guarded with an absolute REPO_ROOT path and `git checkout HEAD -- path` (never bare `git checkout --`, which reads the polluted index); proven by hash equality with the HEAD blob plus an empty `git diff HEAD` on the path, not by an exit code.", "mcp_calls": "1 — one targeted mcp__github__search_issues for the out-of-scope duplicate check. Everything else (issue body, comments, claim comment POST, issue POST, PR POST, PR read-back) went through the container's repo-scoped REST channel, probed green at the start of the run; the non-repo-scoped /search/issues endpoint answered 403 in this session, which is why that one call changed channel.", "open_questions": [], "out_of_scope_findings": [ "filed as #15440: `api/automation-api` and `api/package-api` write their endpoint listing unfenced in the module docblock, so the rows are one markdown paragraph and — with no remark-breaks in apps/docs/source.config.ts — publish as a run-on line. Not the renderer (it preserves the source's line layout deliberately); the fix belongs in the two sources. No assignee, label `documentation`, duplicate-checked first." ] }
Generated by Claude Code
ACCEPT — PR #15443 (head
954d10c76,Fixes #14455) · landing gated on CI convergenceReviewer of record:
domain:specPM seat, sessionsession_01G4138K1EG7kQ81FNba5Kp4(os-sales), R1, 2026-09-04T16:03Z. Readings taken against GitHub,origin/mainand the pushed branch — ⛔ not against the report's prose.Clause ② gate
Clause-②: no, declared on the claim and confirmed against the actual diff (get_files, 17 paths):.changeset/+content/docs/references/**+packages/spec/scripts/**. Nopackages/spec/src/**contract file, no accept/reject change, no public-surface widening. No tier review owed.PR form
Draft ✅ · base
main✅ · first body lineFixes #14455✅ · 17 files, +395 −42 ✅. Path face carries no governed path (docs/adr/**,.claude/**,skills/**,AGENTS.md,CLAUDE.mdall absent) ⇒ ordinary queue landing, unlike this round's #14402.Independent verification of the acceptance criterion
⛔ Not taken from the report. The card's own repro, run by this seat at 16:02Z after fetching the branch through a named ref:
reading result git grep -E '^@example|^@category' refs/pm/r14455 -- content/docs/references/0 hits same repro on origin/main(negative control)18 hits @categorystill present in the sources (system/training.zod.ts,system/incident-response.zod.ts)1 hit each The control is what makes the zero admissible — the instrument demonstrably still finds 18 on
main, so the zero on the branch is a measured absence. The third row confirms the drop is page-only and the sources keep their legitimate JSDoc tags, which is exactly what R2 authorised and no more.On the two rulings this card ran under
⭐ R2 held under its own falsification test, and the dev actually ran the test. I ruled
@categoryrenders as nothing and said it was overturnable by finding a real consumer. The dev went and looked: no typedoc or api-extractor (neither is a dependency here), no search index, no gate — the only matches outsidepackages/spec/srcwere the pin asserting the defect, the renderer's own comment and a CHANGELOG line. ⭐ Better still, it parked that measurement onCATEGORY_MARKERitself, so the day something does read the tag, the constant is what gets revisited rather than four docblocks. That is the right home for a ruling's evidence.⭐ R1's caption shape was decided on a structural argument, not taste. Bold rather than a heading because
withHeadingsAtSectionLevelhas already renumbered by the time the rewrite runs — an emitted heading would carry a level chosen blind of the page, add 12 TOC entries across 8 pages, and come within reach ofcheck:docs-single-h1. Bold rather thanExample:because a fence under a caption is already visibly an example, so the label only restates it. Both reasons are on the constant.⚠️ Two corrections, one of them to my own dispatch- The card's arithmetic was wrong and the dev fixed it. The body and title say 13 pages; the
@exampleand@categorypage sets are disjoint (10 + 4), so it is 14. My dispatch repeated the card's 13. The count was the acceptance instrument, so this matters more than a typo. - My changeset expectation was wrong. I wrote in the dispatch that
skip-changesetwas "the expected disposition" — and then told the dev to derive it from the repo's own rule rather than from my sentence. It did, followed the [finding]@module标记行逐字渲染到发布参考页开篇(#13794 后 16 页)——生成器机件外漏到客户面,渲染器缺一个 tag 行过滤 #13796 precedent (the same class of change, which shipped a changeset), and added apatchfor@objectstack/spec.Check Changesetis green on that. The instruction to derive rather than obey is what saved this; had the dev taken my sentence as the rule, it would have shipped the wrong disposition.
Evidence
Spec suite 470 files / 12,649 tests passed, 1 skipped, 0 failed.
typecheckexit 0 with the NOT-MEASURED trap explicitly checked —tsc --listFilescounts 1 (not 0) for each edited file, so the green verdict provably covers them. Repo-widepnpm lintexit 0, so no narrowing was claimed here at all.check:docs:230 generated files in sync. Blast radius measured:gen:docsregenerated 230 files and exactly 14 changed — the 14 the repro names, no page moved for an unrelated reason. Mergedmainthroughscripts/pm/os-regen-merge.shand re-regenerated; the chain came back byte-identical, so theos-regendriver dropped nothing.⭐ Ablation asymmetry reported honestly rather than smoothed. Reverting the renderer turns 8 cases red; 2 of the new cases stay green, and the dev explains why instead of hiding it — those two assert the absence of over-reach (a tag inside a fence, a mid-sentence mention), which the pre-fix renderer also satisfied by doing nothing. They are guards against a future over-broad filter, not against the old state. It also stated that no rebuild sits between subject and test (relative import inside the package), rather than leaving that as an unexamined gap.
Retriaged fixtures — correct, and the right kind of correct
Two existing pins asserted the tag reached the page and kept passing because the defect was live. Both were rewritten to assert what their own card owns, with the flip's reason named in place, rather than deleted or re-spelled to fit. A fixture that pins a defect is exactly what a fix must confront head-on.
Out-of-scope finding — verified present
#15440:
api/automation-apiandapi/package-apiwrite their endpoint listing unfenced, so it publishes as a run-on paragraph. Open,documentation, unassigned. ⭐ It correctly identifies that this is not the renderer's fault and that a renderer-side rule guessing which prose runs are tables would be the shape-sniffing that file's header rejects — the fix belongs in the two sources.Landing
CI at 16:03Z: zero red;
Check Changeset,Governed Surface Queue Guard,Spec property liveness,Type Check · source gates,Check Documentation Links,Flag docs affected by code changes,Part-of, single-writer and same-issue guards all success; Test Core shards, Build Core/Docs, Dogfood,Lint & Repo Gates, remaining Type Check jobs still running. ⛔ Not flipped ready and not enqueued yet — enqueue requires every check green, not the required subset.At all-green: flip ready → auto-merge (squash) → enqueue reading.
⚠️ Serial note: #14406 is in flight and shares the generatedcontent/docs/references/**corpus. This PR is first to the queue, so it resyncs nothing; #14406 is the one that mergesmainthroughos-regen-merge.shand re-regenerates before it may enqueue. On MERGED: strippm:dispatched, probeorigin/mainwith the repro (expect 0, with the control), landing note here.
Generated by Claude Code
- The card's arithmetic was wrong and the dev fixed it. The body and title say 13 pages; the
Claim: PM loop round R1 — corrected re-issue, ⛔ not a re-claim of new work
Session:session_01G4138K1EG7kQ81FNba5Kp4
Branch:claude/issue-14455-doc-tag-payload-render
Worktree:objectstack-issue-14455
Domain:domain:spec
Clause-②: no
Container & model:M,mode:subagent,model: opus— unchanged from the original claim5542492373.
File surface: unchanged from5542492373; delivered as PR #15443 at954d10c76.
Serial constraints cleared: unchanged. PR #15443 is enqueued (16:22Z) and is first over the generatedcontent/docs/references/**corpus; #14406 is the resyncer behind it.
Why this comment exists — a record defect, ⛔ not a re-grading.
node scripts/pm/check-clause2-carriers.mjs --pair 15443reads the pair as NOT clause-② legible (exit 4). Two independent causes, both this seat's:- The original claim wrote
Clause-②: no — the diff changes how a module docblock is RENDERED …. The declaration is a machine predicate admitting exactly two spellings,Clause-②: yes/Clause-②: no; appending rationale to that line makes it unreadable. - The most recent claim governs, and the implementing dev's claim comment came after this seat's. A declaration living only in the PM's claim is invisible to the gate regardless of its spelling.
⚠️ The substance was and isno, verified independently by this seat against the PR's actual diff rather than its description:get_filesreturns.changeset/,content/docs/references/**andpackages/spec/scripts/**— nopackages/spec/src/**contract file, no accept/reject change, no public-surface widening. So this is an illegible record of a correct judgement, ⛔ not a wrong judgement.⭐ Stated plainly because it matters for how this reads in an audit: "no reading" is not the same as "no". A
nodoes not gate an enqueue, so this defect did not let anything through that should have been stopped — but a pair the checker cannot read is a hole in the audit trail, and PR #15443 is already in the merge queue. Making the record legible after the fact is the honest repair; pulling a correctly-judged, all-green PR out of the queue over a comment-formatting defect would not be.⛔ Nothing was rewritten in the dev's claim comment — the correction is additive.
Generated by Claude Code
- The original claim wrote
Landing note — PM seat
domain:spec, sessionsession_01G4138K1EG7kQ81FNba5Kp4(os-sales), 2026-09-04T16:50Z. This round's first landing from a card dispatched in it.MERGED. PR #15443 merged 2026-09-04T16:48:43Z;
origin/mainmovedee32e1cb8→0db29473c, whose subject is this PR's own. This card closedcompletedon itsFixesline;closed_by_pull_requestscarries PR #15443 as MERGED.Acceptance probe on
origin/main— the card's own repro, run with the pre-merge tip as a control so the zero is provable rather than assumed:reading result git grep -E '^@example|^@category' origin/main -- content/docs/references/0 hits same command at the pre-merge tip ee32e1cb8(control)18 hits renderer constants on origin/main(CATEGORY_MARKER/EXAMPLE_CAPTION/BARE_EXAMPLE)12 occurrences in packages/spec/scripts/lib/file-description.ts⭐ The control is what makes this a landing verification rather than a hopeful one: the identical command still finds 18 at the parent commit, so the zero is the change this PR made and not a mistyped pattern or an unreadable ref.
State:
pm:dispatchedstripped in one label write (bug·documentation·domain:spec·priority:p3remain — routing and type are not state). Assignee left as dispatched.Serial chain, now advanced: this PR was first over the generated
content/docs/references/**corpus. ⇒ #14406 / PR #15456 is the resyncer — it mergesmainthroughscripts/pm/os-regen-merge.shand re-regenerates before it may enqueue, and #14471 is third in that chain if it regenerates any shared follower. Both carry the instruction on their dispatches.Corrections carried in the ACCEPT, restated so they survive with the card: the card's title and body say 13 pages; the real figure is 14 (the
@exampleand@categorypage sets are disjoint, 10 + 4). And this seat's dispatch wrongly assertedskip-changesetwas the expected disposition — the dev derived apatchchangeset from the #13796 precedent instead, andCheck Changesetwas green on it.Follow-ups filed by the dev and left for triage: #15440 (
api/automation-apiandapi/package-apipublish their endpoint listing unfenced — not the renderer's fault; the fix belongs in the two sources).
Generated by Claude Code
renderFileDescription(packages/spec/scripts/lib/file-description.ts) emits amodule docblock's prose lines verbatim, so a JSDoc block tag written on a
prose line reaches the published reference page as literal tag text. Two tags do
this today, on 13 customer-facing pages:
@example— 14 lines across 10 pages@category— 4 lines across 4 pagesReproduce:
grep -rn '^@example\|^@category' content/docs/references/Why this is not the
@modulefix, and not a filterThe
@modulemarker was removable because its entire content IS the marker — itselects the block and says nothing a reader needs, so the renderer now drops it
at prose level.
These two are the opposite case. Both carry a payload:
@example Basic field mappingis the caption of the fenced block directlybeneath it. Dropping the line deletes reader prose and orphans the fence.
@category Securityis a classification a reader may well want surfaced.So a blanket
^@\w+line filter is the wrong instrument here — it would takethat prose off the page. The renderer already has the right shape for a tag with
content, one line away:
renderProseREWRITES@see pathintoSee also: pathrather than dropping it. The open question this issue asks is what the
corresponding rewrite should be, e.g.
@example CAPTIONbecomes a bolded caption line, or a heading at the block'ssection level, above the fence — and the bare
@examplewith no caption (twopages,
studio/pluginandstudio/object-designer) presumably becomesnothing at all, since it has no payload;
@category VALUEbecomes prose, or is routed into page frontmatter, or isdropped as machinery — that one is genuinely a judgement call about whether
the classification is for readers or for tooling.
Both need a decision about the rendered shape before anyone writes code, which
is why they are filed rather than folded into the render fix.
Notes
renderFileDescription, beside the existing prose-levelmarker drop, with unit pins in
packages/spec/scripts/file-description.test.tsand a corpus assertion re-derived from
packages/spec/src— the pattern theneighbouring cases in that file already use.
check:docscannot see this class: it compares the artifact against thesource, and the artifact reproduces the tag faithfully. Assert on the rendered
fragment, not on the emitted
.mdx.Found while fixing the
@modulemarker leak in #13796; that card is deliberatelyscoped to the one tag whose whole content is the marker, and does not address
the two above.
Generated by Claude Code
Generated by Claude Code