feat(cli)!: retire os create example; the refusal names os init (#16483) - #16665
Conversation
…16483) The `example` template emitted a subset of what `os init` writes plus one README, the only template-level duplication #15531 found between the two scaffolder families. Removed under the #15531 ruling (batch #66, option B) with no alias and no deprecation window. The template is not merely deleted: `os create example` still answers, exits 1 and names `os init`, rather than falling through to `Unknown type:` and printing only the surviving roster. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YFY46JydE1gMxQG1TqBcMZ
📓 Docs Drift CheckThis PR changes 1 package(s): 12 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 1 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 22 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 23a5d6ff450c8f71999e37b62c0bd72c86f8fe0b && git checkout 23a5d6ff450c8f71999e37b62c0bd72c86f8fe0b
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin d5c40222f3a4a36823e663aa25cd16e801d55d49 91cf99066af9943e0d3168e0cf4e98edfe4193b1 && git checkout -B drift-repro d5c40222f3a4a36823e663aa25cd16e801d55d49 && git merge --no-ff 91cf99066af9943e0d3168e0cf4e98edfe4193b1
node scripts/docs-audit/affected-docs.mjs --json d5c40222f3a4a36823e663aa25cd16e801d55d49
|
os-sales
left a comment
There was a problem hiding this comment.
Contract review (clause ②) — verdict: PASS-WITH-FINDINGS
At-tier contract review dispatched by the domain:cli execution PM seat (#6024). Read on head 250ccd2a2f against merge-base 4fe00b80e7 (the stated base 0a61db1f5 is main's tip after the branch point; the three-dot diff is what was judged). This is a review verdict, not an approval: the queue and the maintainer decide.
What was verified against primary artifacts (not the PR's paraphrase)
- #15531 ruling — faithful. Comment 5564757555 on #15531 (director seat, decision batch #66, 2026-09-07, option B) says verbatim: "Retire
os create example(a weakeros initplus a README) — #16483." Ledger #12708's batch #66 row (2026-09-07T03:50Z) matches. - Changeset grade
minor— right for this repo, and the carrier is sufficient. Read.changeset/os-create-example-retired.mdat head:"@objectstack/cli": minor, a**BREAKING**banner, a "What to run instead" FROM → TO block, the no-alias statement and theos create plugincarve-out. The written rule:scripts/check-changeset-no-major.mjsheader ("During the launch window we ship breaking changes asminor… the bump level tells a consumer nothing … the mandatory carriers are the BREAKING banner and the ADR-0087 disposition"; expires at GA, #14043) and.github/workflows/pr-automation.yml:667-682"WHICH LEVEL" (maintainer 2026-09-04, batch #35 on #15294: "breaking-ness is carried by the BREAKING banner plus the ADR-0087 disposition, not by the level"). 0majorchangesets on the head tree. Closest precedent is the same shape, not merely the same clause:.changeset/retire-agent-generator.md(commit15b63e85a8,os g agentretired behind a signpost) shipped@objectstack/cli: minorwith a BREAKING banner. The!marker is one of the gate's three recognised breaking signals, sofeat(cli)!:+minoris consistent, ascompliance-families-retired.mdalready does. Level axis (#16055) satisfied — clause ② yes and notpatch.Check Changesetis green on this head (both runs). - ADR-0087
not-required (no-migration-prescription)— correct, and the honest answer to the gate that demanded a marker. The category is in the closed vocabulary (ADR-0087 addendum 2026-08-13). The reasoning is the D7 shape "one surface over" — no Zod schema, nopackages/specdeclaration, no storedsys_metadatashape, nothing forobjectstack migrate metato rewrite, the CLI itself carrying the notice — and it is word-for-word the disposition the shippedos g agentprecedent carried. The gate'sfindMigrationPrescriptionreads FROM/TO-labelled lines and framed## Migration/## 迁移sections or tables; a bold "What to run instead" line over a bash fence is not that shape, so the marker is not contradicted by the body. - Rosters are derived.
packages/cli/src/commands/create.tsat head:availableTypes()(l.454) isObject.keys(templates).join(', ')and is what theUnknown type:branch prints (l.506); thetypearg description (l.462-466) is built fromObject.keys(templates)too. Swept the head tree for a survivingplugin, exampleliteral: none inpackages/cli/src,packages/cli/README.md,scripts/,.github/;scripts/create-scaffold-smoke.shderivesTEMPLATE_KEYSfrom the built CLI and the "Scaffold outside the monorepo, install, build" job is green on this head; no tracked oclif manifest;cli.mdx's--in-repoprose no longer offersexamples/. The one remaining mention outside this PR's own files,.changeset/init-generate-emit-service-object-annotation.md:14, is a past-tense measurement record — fine. - Refusal ordering and carve-out.
RETIRED_TEMPLATESis consulted at l.493, ahead ofUnknown type:(l.505) andProject name is required(l.511); exit 1; whole block on stderr. "os create plugin NAMEscaffolds the kernel codePlugincontract, whichos initdoes not emit" is true per the audit's §5.1 and the routing table atcontent/docs/deployment/cli.mdx:91-94, which is the "scaffolder table" the message points at; that pointer is generic enough to survive #16484's rewording. - Contention with #16654 — the PR's reading is right. #16654's hunk
@@ -64,6 +76,7 @@inscaffold-emission-policy.e2e.test.tsinsertsSCAFFOLD_PNPM_RANGE,on the line directly above theSCAFFOLD_TSX_RANGE,this PR deletes: a one-line conflict, and keep-both is correct because #16654's appended block (l.302+) never usesSCAFFOLD_TSX_RANGE.init.tshunks (282-312 vs 332-338) do not touch. content/docs/releases/v17.mdx— audited, read-only, nothing to file. The drift bot listed it only via the generic anchorspackage.json(a string literal on a changed line) andos init(command). None of this diff's real symbols (os create,SCAFFOLD_*,renderScaffoldTsconfig,create.ts) appear on that page; nothing there is falsified.
Findings (none blocking; each names the surface and the evidence)
F1 — The "maintainer 2026-08-27: no alias, no deprecation window" ruling could not be located; maintainer to confirm. It appears in the card body (#16483, director seat) and everywhere downstream — PR body, create.ts:13-14, the e2e header, the changeset, the docs callout, and the runtime message ("There is no alias and no deprecation window"). Searched: all six comments on #15531 (the batch #66 ruling names only "retire os create example"); all 200 comments on ledger #12708 (its earliest comment is 2026-08-30, so it cannot carry an 08-27 ruling, and the batch #66 row has no alias/window language); and the head tree (AGENTS.md, docs/, .claude/, scripts/pm, .changeset/, packages/cli/src, content/docs) for "deprecation window" and "2026-08-27" — every 2026-08-27 ruling found is about something else (#12473, #12509, #12579, #12599, #12667, #12278, #11982, objectui#6262). Two internal tensions: 08-27 predates the measurement that first identified the duplicate (PR #15797, 2026-09-05); and create.ts:13-14 and the e2e header say the maintainer "ruled the same week" as #15531 (2026-09-07), which 08-27 is not — one of those two statements is wrong whichever way the ruling resolves. The substance is consistent with the shipped os g agent precedent (exit 1, no alias, no window) and with ADR-0087's "timeliness is never load-bearing", so this does not block; it is a citation the maintainer should confirm, after which the "same week" prose in create.ts and create-example-retired.e2e.test.ts should be corrected to the real date. ⛔ Not decided here.
F2 — Runtime message and changeset overstate "the same objectstack.config.ts". create.ts:429-430 ("it writes the same objectstack.config.ts and tsconfig.json") and the changeset ("os init writes the same objectstack.config.ts and tsconfig.json the retired template did"). The audit this PR cites (docs/audits/2026-09-os-create-os-init-convergence-measurement.md §4) has tsconfig.json byte-identical (045d4931833c93b0 on both) but objectstack.config.ts different (8b033a6e95c2185c vs 1f58ea648813bf3e; its table reads "different manifests, both ManifestSchema-valid"). Concretely, the removed template wrote name: '<name>', description: '<name> example application' and commented-out barrel imports with objects: [] / apps: []; os init -t empty writes name: toTitleCase(name), description: '' and no barrels. Same skeleton, not the same file. Suggested: "the same tsconfig.json and an equivalent objectstack.config.ts". Low — the direction a reader is sent is right.
F3 — "os init NAME -t empty → objectstack.config.ts only" is loose. Runtime message, changeset and docs callout. -t empty writes five files (.gitignore, objectstack.config.ts, package.json, pnpm-workspace.yaml, tsconfig.json — audit §4) and runs the install. It mirrors init.ts:679's own template description ("Minimal project with just a config file"), so it is an inherited gloss rather than a new falsehood. If reworded: "config only, no src/objects". Very low.
F4 — Two shipped-text inaccuracies in the changeset body (it becomes CHANGELOG.md). (a) It says the e2e file "holds the four public doc pages to the same promise"; the docs pin is the separate create-example-retired-docs-parity.test.ts, by this PR's own tier design. (b) "os create's flags, its --in-repo placement … are unchanged" holds for plugin only: the --in-repo examples/NAME placement is removed with the template and no replacement is named. Platform-work-only, and no contributor doc referenced it (none in scripts/, .github/, AGENTS.md, docs/), so one sentence suffices. Low.
F5 — SCAFFOLD_TSX_RANGE: leaving it is right, the stated reason is not, and it should be filed rather than "noted, not filed". packages/cli/package.json exports is ., ./console, ./hook-body, ./package.json; src/index.ts re-exports only the InitCommand / CreateCommand classes; there is no packages/cli/api-surface baseline. So the constant is a module export inside the package, not a published surface, and removing it would not be a second surface removal. Leaving it in this PR is still correct — init.ts is held by #16654 and the review should not widen — but its doc comment (init.ts:305, "The tsx range a scaffolded project declares when its scripts need it") is now false on the tree, which is the statement-outlives-removal class this card is about, and #16654 does not give it a consumer (its sync script harvests only SCAFFOLD_PNPM_RANGE and SCAFFOLD_TYPESCRIPT_RANGE). Recommend a domain:cli follow-up card to retire it (or give it a consumer) once #16654 lands. Not a contract problem.
CI on this head at review time
Every completed job green (Check Changeset ×2, all four Type Check jobs, Governed Surface Queue Guard, Scaffold outside the monorepo, Dogfood Verify CLI, Build Docs, Check Documentation Links, Test Core 1/2/3/6); Test Core 4/6 and 5/6, Lint & Repo Gates and Dogfood Regression 1/3 were still running.
🤖 Generated with Claude Code
https://claude.ai/code/session_01YFY46JydE1gMxQG1TqBcMZ
Generated by Claude Code
…16483) Contract review on PR #16665, findings F1-F4. F1 — the "no alias, no deprecation window" ruling could not be located by the review: not in #15531's comments, not in ledger #12708 (whose earliest comment postdates the attributed date), not in the tree. The removal itself IS verified by the #15531 batch #66 entry, so create.ts and the e2e header now cite that and record the alias/window terms as recorded on card #16483, pending maintainer confirmation. The runtime message is deliberately unchanged: it describes what the code does, which is true whoever ruled it. F2 — `os init` does NOT write "the same objectstack.config.ts". The audit this card cites measures tsconfig.json byte-identical and the two manifests DIFFERENT (both ManifestSchema-valid). Runtime message and changeset now say "the same tsconfig.json and an equivalent objectstack.config.ts", and the changeset states the difference. F3 — `-t empty` writes five files and runs the install, so "objectstack.config.ts only" was wrong in the runtime message, the changeset and the doc callout this PR added. All three now read "config only, no src/objects". F4 — the changeset said the e2e file holds the doc pages (the docs pin is the separate queue-tier file) and that `--in-repo` is unchanged (its examples/ placement goes with the template). Both corrected. F5 is deliberately NOT acted on here: SCAFFOLD_TSX_RANGE stays, init.ts is held by #16654. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YFY46JydE1gMxQG1TqBcMZ
|
Contract-review findings applied in one push: F1 — citation made honest, ruling NOT resolved. F2 — the byte-equality claim is fixed. The runtime message and the changeset now say F3 — applied, in all three places, not just the two you named. F4 — both changeset inaccuracies corrected. (a) the docs promise is credited to the separate queue-tier pin; (b) F5 — code untouched, my stated reason corrected. Re-verification and the two NOT-MEASURED gates are in the PR body. Generated by Claude Code |
Brings in #16654 (scripts/sync-scaffold-emission-policy.mjs and its `check:scaffold-emission-policy` gate), which landed while this PR was in contract review. Merged rather than left to the queue because that gate's declared inputs include packages/cli/src/commands/init.ts and the bundled templates, and this PR touches init.ts (comment-only) and removes a template — close enough that it should be measured on the PR instead of first discovered in a dequeue. ⛔ No code change for the `allSixManifests()` drift this merge creates (#16654's helper is named for five CLI emissions plus the on-ramp, and this PR takes the CLI side to four). It is main's code now, a card of its own, and repairing it here would widen this PR past its own. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YFY46JydE1gMxQG1TqBcMZ
Fixes #16483
Retires
os create exampleunder the #15531 ruling (director seat, decision batch 66, option B — do not merge the command families; fix the measured problems).create.ts, the e2e pin header and the changeset. ⛔ I did not decide which of the two internal statements was wrong, did not guess a date, and did not go hunting for a ruling to make it true; the PM is filing it. The runtime message is deliberately unchanged — it describes what the code does, which is true whoever ruled it, and it matches the shippedos g agentprecedent.#15531 rendered and hashed both scaffolder families' real emission. The only template-level duplication left was this one template:
os create examplewrote a subset of whatos initwrites, plus one README. Emission policy is already unified through four shared exports (PR #15974), so the remaining duplicate was the template itself.The removal is a signpost, not a deletion
⛔ Deleting the template and letting
os create examplefall through toUnknown type:is not what the ruling asked for. That branch prints the surviving roster and nothing else, so a reader arriving from an old doc page, an older tutorial or a CI script would learn only that their spelling is off the list — and would go hunting for the right spelling of something that no longer exists.So
os create examplestill answers. It exits 1 and its message namesos init:NAMEappears above. GitHub's body sanitizer deletes tag-shaped fragments and fences do not protect them, so the placeholder is re-spelled here and nowhere else.)The registry lives in
RETIRED_TEMPLATES(packages/cli/src/commands/create.ts), checked ahead of the roster lookup and ahead of the "project name is required" branch —os create examplewith no name at all must reach the signpost, not be told to supply an argument to a command that no longer exists. The shape follows theos g agentretirement (RETIRED_GENERATORSingenerate.ts) rather than inventing a second one.What changed
packages/cli/src/commands/create.ts— theexampletemplate is gone;RETIRED_TEMPLATEScarries the refusal; theAvailable types:roster and thetypearg description are now DERIVED from the live map (they were literals readingplugin, example, so a removal could have outlived them); the--in-repoflag text no longer offersexamples/.packages/cli/test/create-example-retired.e2e.test.ts— new pin, driving a real child process (nightly tier; see the tier table below).packages/cli/test/create-example-retired-docs-parity.test.ts— new pin, the docs half, queue tier so it reddens on the PR.packages/cli/test/create.test.ts— the sweep's roster assertion, plus a new case holding BOTH halves:exampleoff the template map AND on the retired map.packages/cli/test/scaffold-manifest-schema.test.ts—os createnow contributes no config-emitting scaffold. Asserted as the roster the filter selects from, not as a bare count: an empty harvest and a broken filter produce the same count.packages/cli/test/scaffold-emission-policy.e2e.test.ts— four emissions instead of five.tsxleaves the policy table because the retired template was its only declarer.content/docs/deployment/cli.mdx— theos createsection, the Scaffolding roster row, the--in-repooption text, plus a retirement callout pointing atos init..changeset/os-create-example-retired.md—minor, with the break stated in the body and an ADR-0087not-required (no-migration-prescription)disposition: a CLI subcommand is not authored metadata, soobjectstack migrate metahas nothing to rewrite.packages/cli/src/commands/init.ts— comment only: one paragraph ofrenderScaffoldTsconfig's doc block namedos create exampleas a live emitter.scripts/cross-package-test-inputs.mjs— one declaration row.turbo.jsonhas carriedscripts/nightly-tiers.mjsin this task'sinputsall along; the row here was missing, and nothing forced it until the new docs-parity pin named the path in prose. Noturbo.jsonedit:check:cross-package-test-inputsreports it already hashes every declared glob.Verification
All exit codes captured before any pipe (
cmd > log 2>&1; EXIT=$?), on head3fd6424f35unless stated.The refusal, driven
bin/run-dev.js(the SOURCE entry — it runssrc/through tsx, so nodist/is in the loop),NO_COLOR=1, streams captured separately, in an empty temp directory:os initos create example my-appos create example(no name)os create definitely-not-a-template my-appUnknown type:+Available types: pluginos create plugin my-app./plugin-my-app/Row 3 is the discriminating control: it is why "asserts non-zero" cannot be mistaken here for "asserts the signpost".
Ablation — the RED direction the card turns on
Implementation committed first (
3fd6424f35), then mutated: the 7 occurrences ofos initinside theRETIRED_TEMPLATESliteral replaced with a name that does not exist.The mutation reddens exactly the two assertions that read the signpost, and nothing else:
The other nine — non-zero exit,
was retired, noUnknown type:, the ruling's terms,os create pluginsurviving, writes-nothing, the generic-branch control, the roster, the survivor scaffolding — stayed green. A pin asserting only non-zero would have been green on this whole run, which is the outcome the ruling exists to prevent.Restore is proved by state (blob hash equal to the HEAD blob,
git diff HEADempty), never by an exit code, under anEXIT/INT/TERMtrap using absolute paths. No rebuild is in the loop because the subject resolves throughsrc/, notdist/—bin/run-dev.jssays so in its own header.Which tier each pin runs in — measured, not chosen
*.e2e.test.*is a NAME-decided nightly tier (scripts/nightly-tiers.mjs, #16455), so it is excluded from the per-PR and merge-queue population. Measured withintegrationTestFiles()/unitTestFiles()under both settings:OS_TEST_TIERSunset (per-PR)OS_TEST_TIERS=nightlycreate-example-retired.e2e.test.tsintegrationcreate-example-retired-docs-parity.test.tsunitcreate-refuses-invalid-project-name.e2e.test.ts(existing sibling)integrationgenerate-agent-retired.e2e.test.ts(the precedent)integrationThat is why the pin is two files. The spawning half sits in the nightly lane its two closest siblings already occupy — four cold
tsxstarts is what #16455 moved off the queue. The docs half spawns nothing, so it is queue-tier and reddens on the PR that reintroducesos create examplein a page, not the following night.Suites
pnpm --filter @objectstack/cli typecheckunitproject, 9 files this diff touches or newly builtTest Files 9 passed (9)·Tests 136 passed | 6 expected fail (142)integrationproject, the 3 spawning filesTest Files 3 passed (3)·Tests 23 passed (23)packages/cliunittier (earlier head)183 files, 1 failure since fixed, 2 suites refused for a missingpackages/cli/dist— both green afterpnpm --filter @objectstack/cli buildDependency closure built first (
pnpm --filter '@objectstack/cli^...' build,VERDICT command-exit 0), then@objectstack/cliand@objectstack/client-reactfor the gates that refuse to judge an unbuilt tree.Gate union
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands, run on head250ccd2a2f, then reconciled with--ran:104 derived, 104 run, 0 UNRUN, and every one exit 0. Seventeen of them only appeared once the diff grew a
scripts/path, which is why the reconciliation was re-derived rather than trusted from the first pass.Three gates refused rather than judged on an earlier pass, each naming what it needed, and all three are green above once it was supplied — none of them was ever read as a pass:
check:i18n,check:i18n-coverage,check:i18n-walk-paritypackages/cli/distcheck:skill-examplespackages/client-react/distcheck:dual-build-cjs-loadsdist/Re-verification after the contract-review findings (head
fd31e93f51)Every exit code captured before any pipe.
pnpm --filter @objectstack/cli typecheckunitproject, 8 filesTest Files 8 passed (8)·Tests 162 passed | 6 expected fail (168)integrationproject, 3 spawning filesTest Files 3 passed (3)·Tests 23 passed (23)published-subpath-*pins (needpackages/cli/dist)Test Files 2 passed (2)·Tests 29 passed (29)104 derived, 104 run, 0 UNRUN— 102 measured exit 0@objectstack/cli's dependency closure, which this diff cannot reach and which a full repo build would be needed to supply:check:dual-build-cjs-loads(exit 3) — named 11 missingdist/:studio, fourconnector-*,embedder-openai, twoknowledge-*, and 3 more.check:i18n-coverage(exit 3) —examples/app-showcase's config could not load:@objectstack/connector-mcphas no build output.Nine others refused earlier for prerequisites this round could supply and are green above once it did —
check:docs,check:skill-examples,check:doc-formula-expressions,check:doc-security-posture,check:docs-transcript-drift,check:dts-closure,check:i18n,check:i18n-walk-parity,check:type-check-debt. None was ever read as a pass while it refused.The merge of
origin/main(head91cf99066a)origin/mainwas merged into this branch rather than left to the queue, because#16654 landed while this PR was in contract review and brought a gate,
check:scaffold-emission-policy, whose declared inputs includepackages/cli/src/commands/init.tsand the bundled templates — both of which thisPR touches. A merge commit, no rebase, no force-push.
91cf99066afd31e93f51origin/main)2539f4aec6git rev-list --left-right --count origin/main...HEAD0 3— nothing left behind on maingit diff --stat origin/main HEAD⛔ No code change was made for the
allSixManifests()drift the merge creates.It is
main's code now and the PM is filing it as its own card; the flag stays below.The silent-drop trap was checked, not assumed.
git check-attr mergeon all 11 ofthis PR's paths reports
unspecifiedfor every one — none is markedmerge=os-regen,so no regeneration is owed and nothing could have been dropped by a custom driver.
The one place the two diffs met resolved as predicted: the import block of
scaffold-emission-policy.e2e.test.tsnow carries #16654'sSCAFFOLD_PNPM_RANGEandthis PR's removal of
SCAFFOLD_TSX_RANGE— both edits kept.Re-verification on the merged head
91cf99066aEvery exit code captured before any pipe (
cmd > log 2>&1; EXIT=$?), never across one.Gate union, re-derived on this head — 105 families (was 104; the STALE TREE warning
that the pre-merge derivation carried is gone, and the diff against it is exactly one
added line,
pnpm check:scaffold-emission-policy).Twelve families first refused rather than judged, each naming its own unmet
prerequisite. The build then supplied what ten of them named and they measure green. The
run record claims the remaining two as
NOT-MEASUREDwith the prerequisite they name,rather than recording them as run — so the reconciliation prints the honest split itself:
N derived, N run, 0 NOT-MEASUREDwould be the runner's own accounting, not independentverification — the tool says outright it "did not measure them and cannot verify the reason".
That is why the two refusals are claimed rather than counted as run: ⛔ neither is a pass, and
CI builds the whole repo and will measure both. A whole-repo build was deliberately not run
locally for them — four agents share this container.
The ten that refused first and are green after the build:
check:doc-formula-expressions,check:doc-security-posture,check:docs,check:skill-examples,check:docs-transcript-drift,check:dts-closure,check:i18n,check:i18n-walk-parity,check:sourcemap-no-sources-content,check:type-check-debt. ⛔ None was read as a pass while it refused.check:scaffold-emission-policy— green, and what that green does not sayThis is the gate the merge was performed for, so its scope is worth stating exactly rather
than resting on the tick. It exits 0 on the merged head:
packages/create-objectstack/src/templates/, not the CLI'sos createtemplate map, so its green says the merge introduced no drift and not that the removal was
measured by it. The "1 bundled template" is the on-ramp's
blank, which this PR does nottouch; a reader who takes this tick as coverage of the retirement would be taking a green
whose scope is narrower than the reassurance it appears to give.
What rules out the other reading — that it is vacuously green — is its own
--self-test,which runs in the same command and prints 27 assertions over temp fixtures: a clean corpus is
observed reached and left byte-identical, drift is observed reddening
--checkbefore therewrite and green after it, and a renamed policy constant, a template omitting a stamped key,
an unparseable template and an empty templates directory each exit 1 naming the path. So the
gate demonstrably can fail; it simply is not looking at what this PR changed. The pins that
do look at the retirement are the two suites below.
Build and suites on the merged head
All under the shared verify lock (⚠️ shared-box seconds, not idle-box figures), each exit code captured before any pipe:
VERDICT command-exit 0 · held the lock 687s · waited 403s—
pnpm --filter '@objectstack/cli...' build(package and its dependency closure)BUILD_CLI_CLOSURE_EXIT=0cli dist: yes·create-objectstack dist: yespnpm --filter @objectstack/client-react buildBUILD_CLIENT_REACT_EXIT=0check:skill-examplespnpm --filter @objectstack/cli typecheckTYPECHECK_EXIT=0check:test-typecheck: OK — @objectstack/cli's test layer compiles under tsconfig.test.jsonunitproject, 10 filesQUEUE_UNIT_EXIT=0Test Files 10 passed (10)·Tests 191 passed | 6 expected fail (197)integrationproject, 3 spawning filesNIGHTLY_EXIT=0Test Files 3 passed (3)·Tests 27 passed (27)The queue-tier run includes the two
published-subpath-*pins, which needpackages/cli/distand therefore could only be run after the build landed.
验收备注
Found while working here, deliberately not fixed in this PR:
SCAFFOLD_TSX_RANGE(packages/cli/src/commands/init.ts) now reaches no scaffold at all — the retiredexampletemplate was its only consumer. Left in place, butpackages/cli'sexportsare.,./console,./hook-bodyand./package.json, andsrc/index.tsre-exports only the command classes, soSCAFFOLD_TSX_RANGEis a module export inside the package and removing it would not have been a second surface removal. The reason that does hold is the remaining one:init.tsis held by PR fix(create-objectstack): generate the on-ramp's version pins from the shared emission policy #16654. The review also notes its doc comment atinit.ts:305is now false on the tree and that fix(create-objectstack): generate the on-ramp's version pins from the shared emission policy #16654 gives it no consumer; the PM is filing the follow-up card, and I have left the code alone.os create's other refusals split their streams (headline on stderr, hint on stdout viaconsole.log). The retirement block above is entirely on stderr. The pre-existing split is not widened and not repaired here. Noted, not filed.docs/audits/2026-09-os-create-os-init-convergence-measurement.mdstill spellsos create examplethroughout. It is the record of a measurement taken when the command existed; rewriting it would rewrite the measurement. Left as taken. Noted, not filed.Contention with #16654 — resolved by the merge
#16654 has landed on
mainand is merged into this branch at91cf99066a. What wasa prediction last round is now a measurement:
packages/cli/src/commands/init.ts— comment-only here (one paragraph ofrenderScaffoldTsconfig's doc block that namedos create exampleas a live emitter).My hunk was at 335-338, fix(create-objectstack): generate the on-ramp's version pins from the shared emission policy #16654's at 282-312: no context overlap, and the merge took both
without a conflict.
packages/cli/test/scaffold-emission-policy.e2e.test.ts— the predicted one-line meetingin the import block resolved keeping both edits, as called:
SCAFFOLD_PNPM_RANGEisimported,
SCAFFOLD_TSX_RANGEis not.allSixManifests()is named for five CLI emissions plus the on-ramp, and this PR takes theCLI side to four, so the name and its comment read one too many. That is
main's code, notthis PR's; the PM is filing it as its own card and no code here changes for it.
🤖 Generated with Claude Code
https://claude.ai/code/session_01YFY46JydE1gMxQG1TqBcMZ
Generated by Claude Code