Uh oh!
There was an error while loading. Please reload this page.
fix(spec): file-description skips doc blocks the lazify codemod detached from their symbols - #13336
Merged
Merged
Conversation
…hed from their symbols
…htened selection rule
…export-doc-block # Conflicts: # skills/objectstack-ui/references/_index.md
Contributor
📓 Docs Drift CheckNothing 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
Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
os-zhuang
approved these changes
Aug 30, 2026
os-zhuang
marked this pull request as ready for review
August 30, 2026 10:43
os-zhuang
enabled auto-merge
August 30, 2026 10:43
Uh oh!
There was an error while loading. Please reload this page.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes#13263
ui/view.zod.ts, already corrected at source by #13262 and gone from the tree), not the mechanism. The mechanism is an import injected by the lazify codemod between a doc block and the declaration it documents. Everything below reports what was measured; the title is the dispatch's and is the PM's to change.The reading came first — the triage fence, honoured
Triage was explicit that the candidate list is not a verdict and that the route choice is not available until the candidates are read, one module at a time. So they were, before a line of the rule was touched.
Premise reproduced verbatim on
origin/mainat3322527f, with the card's own one-level globpackages/spec/src/*/*.zod.ts:What the 29 actually are — and the line-5 threshold is not the predicate
Reading them individually turned up something the card's framing did not predict. In 27 of the 29 the line directly under the block is not a re-export at all; it is
import { lazySchema } from '…/shared/lazy-schema';, and the block is the doc of the declaration on the other side of that import:The cause is mechanical, not editorial.
packages/spec/scripts/lazify-schemas.ts→ensureLazyImportinserts its import at the end of the file's leading run of comments, blank lines and imports, and the regex for that run counts a doc block among the comments (its comment alternative is slash-star, any run, star-slash). So a block written directly above a schema came out of the codemod with an import wedged in. Nothing about the block changed; only the detector's view of it did. That also means the attachment is broken for editors, not just for this generator — hoveringPageRegionSchematoday shows nothing, because the JSDoc is attached to the import.The other 2 of the 29 are genuine module headers:
shared/mapping.zod.ts("This module provides the canonical field mapping schema used across ObjectStack") andsystem/cache.zod.ts("Application Cache (system/cache.zod.ts) - This File").And "starts after line 5" is an arbitrary cut. The predicate that actually separates the class is does a plumbing line precede the block — i.e. is the block inside the import block or above it. Measured, that is 39 of the 69, not 29; the extra 10 (
api/events,data/datasource,data/document,data/seed,kernel/execution-context,shared/identifiers,system/auth-config,system/license,system/search-engine,ui/notification) sit at line 5 after a singleimport { z } from 'zod';and every one of them was read as a symbol doc too. The card's 29 undercounts by construction.The route chosen, and why the carve-out is measured rather than assumed
Direction 1 (tighten the rule), in the one form the reading supports. Condition 3 now sees plumbing the way condition 2 already does: a declaration reachable across nothing but blank lines and import / re-export lines is still the block's subject. Two limbs bound it, and dropping either was measured to cost real pages:
api/error-code-ledger,cloud/template-manifestandsystem/doc— three real headers whose imports happen to be followed straight by a declaration — go blank.// ===banner or the next schema's own JSDoc means the block was not against the declaration before the codemod ran either.api/analytics(banner) andsystem/cache(next schema's JSDoc) keep their headers through exactly this clause.⛔ Neither the marker route nor a corpus pass is taken here: triage reserved the first as its own dispatch, and the second would edit
packages/spec/src/**/*.zod.ts, which this dispatch fenced off.Measured delta over the real corpus
origin/main'sfindModuleDocBlockversus this one, both run over the same tree (208 sources, recursive — the card's one-level glob misseskernel/events/*.zod.tsandsrc/stack.zod.ts):All 35 open with a schema's name. Zero legitimate module headers are lost — the three above-the-imports headers and both in-import-block headers are in the 154. 34 reference pages change (the 35th,
src/stack.zod.ts, has no category and so no page).The published skill indexes are where this stops being a docs-site matter. Each row now tells the truth instead of naming one schema:
Every added line in the regenerated products is one of those
Exports:fallback rows — checked, not assumed:git diff -- content/docs/references skills | grep '^+[^+]'yields 28 lines and all 28 have that shape.Reverse verification
Direction predicted before running: red, on the new cases only. The fix was committed first, so the restore leg has a real restore point.
Mutation (
if (insideImportBlock && …)guarded tofalse) proven on disk by anchor count and blob hash, not by the editor's exit code: injected marker count 1, blobb16f90ef→cc4053c3. Nodistleg exists — vitest imports./lib/file-descriptionfrom source, andtsxruns the generator from source.The three
keepscases stayed green under the ablation, which is the asymmetry that says the pins measure the tightening and not the selector at large. Restored withgit checkout HEAD -- ...: blob back tob16f90ef,git diff HEADempty,git status --porcelainempty for the path.One existing test asserted the opposite, and is corrected
keeps a header the lazify codemod separated from the importspinned exactly this shape with the comment "It is still a header." Its cited real file isapi/analytics.zod.ts— which carries a// ===banner between the block and the declaration, and therefore is not in this class at all. The reduction had dropped the one line carrying the verdict. The case is rewritten to the file it names, banner included, and still assertsAnalytics API Protocol; the bannerless shape it used to assert now sits directly below it assertingnull, so the difference is visible in one screen.Residue, filed rather than absorbed
10 modules still put a block inside their import list with a comment on the far side. 8 publish a schema's doc; 2 (
shared/mapping,system/cache) are genuine headers. They are structurally identical — line number, imports-before count, block length and whether the next declaration has its own JSDoc all fail to separate them, each measured — so only the prose differs, and a first-sentence pattern check is the approachfile-description.ts's own header rejects with the measurement behind the rejection. Recorded module by module in #13334, with the two composable routes and their real costs.#13335 records a second finding from the merge:
skills/*/references/_index.mdis generator-owned and gated bycheck:skill-refs, but is in neither.gitattributes'smerge=os-regenblock norregen-artifacts.mjs'sNOT_DRIVER_MANAGEDroster, so two branches that regenerate it text-merge. It conflicted for real on this merge.Published-skills readings
Required whole-file and whole-package numbers, measured against merge base
56c093c4:references/_index.md, linesskills/**/SKILL.md, lines (0 files touched)skills/**, linesskills/**, bytesNo SKILL.md is touched and no row is added or removed — 8 index rows are rewritten in place. The byte growth is entirely the generator substituting an
Exports:list for a description it no longer has; it is not authored expansion, and it is the price of the row being true.skills/**is a governed surface (Prime Directive #14), so this PR stays draft and is the maintainer's to land. No AI seat merges, queues, or arms auto-merge on it.Changeset
None, deliberately; the
skip-changesetlabel declares it. This PR releases nothing from any package, checked on all three parts of the diff rather than on the first:packages/spec'sfilesarray is["dist","json-schema","liveness","prompts","llms.txt","README.md","src/**/*.zod.ts","CHANGELOG.md","api-surface","spec-changes.json"]—scriptsis absent, so neither edited source ships;apps/docsisprivate: true, socontent/docs/**ships nowhere; and no package manifest in the workspace namesskillsin itsfiles(checked over every non-private trackedpackage.json). Precedents #13202 and #13199, both landed on the same argument.Difference from those two, stated rather than glossed: they carried no regenerated products, this one does. The products are still unpublished by the test above, so the criterion the
Check Changesetjob states — such a PR releases nothing — holds the same way.Verification
Union re-run after the final commit, at
b48f9574.Gate family derived mechanically with
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, no hand-fed paths — change set44 path(s) vs merge base 56c093c4d, three-dot. Re-derived after the merge and byte-identical to the pre-merge derivation.All exit codes captured before any pipe, all 0:
Verdict lines quoted from the gates themselves, not from
$?:✓ All 14 generated artifacts are up to date.—check:generated, which covers both regenerated artifacts, so the hand-resolved merge conflict inskills/objectstack-ui/references/_index.mdis proven generator-exact rather than hand-merged into placecheck:test-typecheck: OK — @objectstack/spec's test layer compiles under packages/spec/tsconfig.test.jsonpnpm --filter @objectstack/spec test—Test Files 443 passed (443),Tests 11784 passed (11784).pnpm --filter '@objectstack/spec^...' buildmatched no projects. That zero match is real and not a mistyped filter:@objectstack/specdeclares no workspace dependencies, so there is nothing to build ahead of it.Declared narrowing. Repo-wide
pnpm lintwas not run: the container caps a foreground command at ~10 minutes and killed one union batch at that boundary already (exit 143), withos-verify-lockqueue waits of 2–6 minutes on top. CI runs the farm regardless, and the derived family above is what this diff can move.check:type-check-debt --re-measure,check:dev-prereqsandcheck:dual-build-cjs-loadsare NOT MEASURED here for the reason their own text gives — each refuses on an unbuilt workspace closure rather than failing — and CI builds first and runs all three.Generated by Claude Code