Skip to content

feat(cli)!: retire os create example; the refusal names os init (#16483) - #16665

Merged
os-sales merged 3 commits into
mainfrom
claude/issue-16483-retire-os-create-example
Sep 7, 2026
Merged

feat(cli)!: retire os create example; the refusal names os init (#16483)#16665
os-sales merged 3 commits into
mainfrom
claude/issue-16483-retire-os-create-example

Conversation

@claude

@claude claude Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Fixes #16483

Retires os create example under the #15531 ruling (director seat, decision batch 66, option B — do not merge the command families; fix the measured problems).

⚠️ Provenance correction (review finding F1). That batch entry is the whole of what is verified, and it settles the removal only. The terms this PR implements on top of it — no alias, no deprecation window — were attributed here to a maintainer ruling the contract review could not locate, so they are now recorded as on card #16483, pending maintainer confirmation, in 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 shipped os g agent precedent.

#15531 rendered and hashed both scaffolder families' real emission. The only template-level duplication left was this one template: os create example wrote a subset of what os init writes, 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 example fall through to Unknown 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 example still answers. It exits 1 and its message names os init:

❌ `os create example` was retired — it was a weaker `os init` (#16483).

  It emitted a SUBSET of what `os init` writes, plus one README. The two
  command families were measured file by file and hashed: there was no
  shape this template produced that `os init` does not.

  Use `os init` instead — it writes the same tsconfig.json and an
  equivalent objectstack.config.ts, and adds src/objects, a .gitignore and
  the dependency install this template never had:

      os init NAME             ->  a full application project
      os init NAME -t empty    ->  config only, no src/objects

  There is no alias and no deprecation window: `os create example` will not
  come back, so change the command rather than pinning an older CLI.

  `os create plugin NAME` is unaffected. It scaffolds the kernel code
  `Plugin` contract, which `os init` does not emit — see the scaffolder
  table on https://objectstack.ai/docs/deployment/cli

⚠️ Two lines of this block changed with review findings F2 and F3, so it is now a RE-QUOTE from the message definition rather than the terminal capture the rest of it was; the re-driven capture is pending the shared verify lock (see Verification). (The command really prints an angle-bracket placeholder where NAME appears 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 example with 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 the os g agent retirement (RETIRED_GENERATORS in generate.ts) rather than inventing a second one.

What changed

  • packages/cli/src/commands/create.ts — the example template is gone; RETIRED_TEMPLATES carries the refusal; the Available types: roster and the type arg description are now DERIVED from the live map (they were literals reading plugin, example, so a removal could have outlived them); the --in-repo flag text no longer offers examples/.
  • 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: example off the template map AND on the retired map.
  • packages/cli/test/scaffold-manifest-schema.test.tsos create now 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. tsx leaves the policy table because the retired template was its only declarer.
  • content/docs/deployment/cli.mdx — the os create section, the Scaffolding roster row, the --in-repo option text, plus a retirement callout pointing at os init.
  • .changeset/os-create-example-retired.mdminor, with the break stated in the body and an ADR-0087 not-required (no-migration-prescription) disposition: a CLI subcommand is not authored metadata, so objectstack migrate meta has nothing to rewrite.
  • packages/cli/src/commands/init.ts — comment only: one paragraph of renderScaffoldTsconfig's doc block named os create example as a live emitter.
  • scripts/cross-package-test-inputs.mjs — one declaration row. turbo.json has carried scripts/nightly-tiers.mjs in this task's inputs all along; the row here was missing, and nothing forced it until the new docs-parity pin named the path in prose. No turbo.json edit: check:cross-package-test-inputs reports it already hashes every declared glob.

Verification

All exit codes captured before any pipe (cmd > log 2>&1; EXIT=$?), on head 3fd6424f35 unless stated.

The refusal, driven

bin/run-dev.js (the SOURCE entry — it runs src/ through tsx, so no dist/ is in the loop), NO_COLOR=1, streams captured separately, in an empty temp directory:

command exit says os init wrote
os create example my-app 1 yes (stderr) nothing
os create example (no name) 1 yes (stderr) nothing
os create definitely-not-a-template my-app 1 noUnknown type: + Available types: plugin nothing
os create plugin my-app 0 n/a ./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 of os init inside the RETIRED_TEMPLATES literal replaced with a name that does not exist.

HEAD blob        : 963d6d79942ef562647fcf71fbe56647d31ef21f
worktree pre     : 963d6d79942ef562647fcf71fbe56647d31ef21f
MUTATION: replaced 7 occurrence(s) of `os init` inside RETIRED_TEMPLATES
injected text count (os scaffold): 7
deleted  text count (os init in RETIRED_TEMPLATES): 0
worktree mutated : fc1ffe74ef4e825ce92feeb89cd4fa3d1571387d      (on disk, by hash)
ABLATED vitest exit: 1
worktree post    : 963d6d79942ef562647fcf71fbe56647d31ef21f      (restored, by hash)
RESTORE PROVED   : blob identical to HEAD, git diff HEAD empty
RESTORED vitest exit: 0

The mutation reddens exactly the two assertions that read the signpost, and nothing else:

 × NAMES `os init` — the half a bare removal would drop
 × answers the same with no project name at all
 Test Files  1 failed (1)
      Tests  2 failed | 9 passed (11)

AssertionError: expected '\n❌ `os create example` was retired —…' to contain 'os init'
- os init
+ ❌ `os create example` was retired — it was a weaker `os scaffold`.

The other nine — non-zero exit, was retired, no Unknown type:, the ruling's terms, os create plugin surviving, 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 HEAD empty), never by an exit code, under an EXIT/INT/TERM trap using absolute paths. No rebuild is in the loop because the subject resolves through src/, not dist/bin/run-dev.js says 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 with integrationTestFiles() / unitTestFiles() under both settings:

file OS_TEST_TIERS unset (per-PR) OS_TEST_TIERS=nightly
create-example-retired.e2e.test.ts not in population integration
create-example-retired-docs-parity.test.ts unit not in population
create-refuses-invalid-project-name.e2e.test.ts (existing sibling) not in population integration
generate-agent-retired.e2e.test.ts (the precedent) not in population integration

That is why the pin is two files. The spawning half sits in the nightly lane its two closest siblings already occupy — four cold tsx starts is what #16455 moved off the queue. The docs half spawns nothing, so it is queue-tier and reddens on the PR that reintroduces os create example in a page, not the following night.

Suites

run result
pnpm --filter @objectstack/cli typecheck exit 0
queue tier, unit project, 9 files this diff touches or newly built Test Files 9 passed (9) · Tests 136 passed | 6 expected fail (142)
nightly tier, integration project, the 3 spawning files Test Files 3 passed (3) · Tests 23 passed (23)
whole packages/cli unit tier (earlier head) 183 files, 1 failure since fixed, 2 suites refused for a missing packages/cli/dist — both green after pnpm --filter @objectstack/cli build

Dependency closure built first (pnpm --filter '@objectstack/cli^...' build, VERDICT command-exit 0), then @objectstack/cli and @objectstack/client-react for the gates that refuse to judge an unbuilt tree.

Gate union

node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands, run on head 250ccd2a2f, then reconciled with --ran:

Run reconciliation — 104 derived, 104 run, 0 NOT-MEASURED, 0 UNRUN.
✓ dispatch-gates --ran: 104 derived famil(ies) accounted for — 104 run, 0 NOT-MEASURED.

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:

gate needed after
check:i18n, check:i18n-coverage, check:i18n-walk-parity packages/cli/dist exit 0
check:skill-examples packages/client-react/dist exit 0
check:dual-build-cjs-loads twelve packages' dist/ exit 0, and it measured: 104 require entry points across 67 packages

Re-verification after the contract-review findings (head fd31e93f51)

Every exit code captured before any pipe.

run result
pnpm --filter @objectstack/cli typecheck exit 0
queue tier, unit project, 8 files Test Files 8 passed (8) · Tests 162 passed | 6 expected fail (168)
nightly tier, integration project, 3 spawning files Test Files 3 passed (3) · Tests 23 passed (23)
published-subpath-* pins (need packages/cli/dist) Test Files 2 passed (2) · Tests 29 passed (29)
gate union, re-derived on this head 104 derived, 104 run, 0 UNRUN102 measured exit 0

⚠️ Two of the 104 are NOT MEASURED, not passes, and both refused for the same reason — they read built output of packages outside @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 missing dist/: studio, four connector-*, embedder-openai, two knowledge-*, and 3 more.
  • check:i18n-coverage (exit 3) — examples/app-showcase's config could not load: @objectstack/connector-mcp has 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 (head 91cf99066a)

origin/main was 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 include
packages/cli/src/commands/init.ts and the bundled templates — both of which this
PR touches. A merge commit, no rebase, no force-push.

merge commit 91cf99066a
first parent (this branch) fd31e93f51
second parent (origin/main) 2539f4aec6
conflicts none
git rev-list --left-right --count origin/main...HEAD 0 3 — nothing left behind on main
git diff --stat origin/main HEAD the same 11 files, +601 / -164 — the merge dropped nothing of this PR

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 merge on all 11 of
this PR's paths reports unspecified for every one — none is marked merge=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.ts now carries #16654's SCAFFOLD_PNPM_RANGE and
this PR's removal of SCAFFOLD_TSX_RANGE — both edits kept.

Re-verification on the merged head 91cf99066a

Every 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-MEASURED with the prerequisite they name,
rather than recording them as run — so the reconciliation prints the honest split itself:

Run reconciliation — 105 derived, 103 run, 2 NOT-MEASURED, 0 UNRUN.
  NOT-MEASURED (2) — the RUNNER's claim, recorded with a reason. ⛔ This tool did not measure them and cannot verify the reason:
    - pnpm check:dual-build-cjs-loads   [PREREQUISITE NOT MET (exit 3) — reads built output; 11 packages outside @objectstack/cli's dependency closure have no dist/]
    - pnpm check:i18n-coverage   [COULD NOT MEASURE (exit 3) — examples/app-showcase cannot load: @objectstack/connector-mcp has no build output in this worktree]
✓ dispatch-gates --ran: 105 derived famil(ies) accounted for — 103 run, 2 NOT-MEASURED.

⚠️ N derived, N run, 0 NOT-MEASURED would be the runner's own accounting, not independent
verification — 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 say

This 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:

✓ check:scaffold-emission-policy: 1 bundled template(s) emit the shared policy (SCAFFOLD_PNPM_RANGE=>=10.15, SCAFFOLD_TYPESCRIPT_RANGE=^5.3.0).

⚠️ The gate walks packages/create-objectstack/src/templates/, not the CLI's os create
template 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 not
touch; 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 --check before the
rewrite 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 (VERDICT command-exit 0 · held the lock 687s · waited 403s
⚠️ shared-box seconds, not idle-box figures), each exit code captured before any pipe:

run exit evidence
pnpm --filter '@objectstack/cli...' build (package and its dependency closure) BUILD_CLI_CLOSURE_EXIT=0 cli dist: yes · create-objectstack dist: yes
pnpm --filter @objectstack/client-react build BUILD_CLIENT_REACT_EXIT=0 prerequisite for check:skill-examples
pnpm --filter @objectstack/cli typecheck TYPECHECK_EXIT=0 check:test-typecheck: OK — @objectstack/cli's test layer compiles under tsconfig.test.json
queue tier, unit project, 10 files QUEUE_UNIT_EXIT=0 Test Files 10 passed (10) · Tests 191 passed | 6 expected fail (197)
nightly tier, integration project, 3 spawning files NIGHTLY_EXIT=0 Test Files 3 passed (3) · Tests 27 passed (27)

The queue-tier run includes the two published-subpath-* pins, which need packages/cli/dist
and 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 retired example template was its only consumer. Left in place, but ⚠️ my stated reason was wrong and the review corrected it (F5): it is not a published surface. packages/cli's exports are ., ./console, ./hook-body and ./package.json, and src/index.ts re-exports only the command classes, so SCAFFOLD_TSX_RANGE is 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.ts is 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 at init.ts:305 is 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 via console.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.md still spells os create example throughout. 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 main and is merged into this branch at 91cf99066a. What was
a prediction last round is now a measurement:

  • packages/cli/src/commands/init.tscomment-only here (one paragraph of
    renderScaffoldTsconfig's doc block that named os create example as 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 meeting
    in the import block resolved keeping both edits, as called: SCAFFOLD_PNPM_RANGE is
    imported, SCAFFOLD_TSX_RANGE is not.

⚠️ One piece of drift survives the merge and is left alone on purpose: #16654's helper
allSixManifests() is named for five CLI emissions plus the on-ramp, and this PR takes the
CLI side to four, so the name and its comment read one too many. That is main's code, not
this 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

…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
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling labels Sep 7, 2026
@github-actions

github-actions Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/cli, touching 7 documentable anchor(s).

12 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/deployment/cli.mdx (via package.json (literal, a string literal on a changed line), tsconfig.json (literal, a string literal on a changed line), os create (command, read off packages/cli/src/commands/create.ts), os init (command, read off packages/cli/src/commands/init.ts))
  • content/docs/deployment/self-hosting.mdx (via package.json (literal, a string literal on a changed line))
  • content/docs/deployment/tenancy-modes.mdx (via package.json (literal, a string literal on a changed line))
  • content/docs/deployment/troubleshooting.mdx (via package.json (literal, a string literal on a changed line))
  • content/docs/getting-started/examples.mdx (via package.json (literal, a string literal on a changed line), tsconfig.json (literal, a string literal on a changed line), os init (command, read off packages/cli/src/commands/init.ts))
  • content/docs/getting-started/your-first-project.mdx (via package.json (literal, a string literal on a changed line), tsconfig.json (literal, a string literal on a changed line), os init (command, read off packages/cli/src/commands/init.ts))
  • content/docs/plugins/development.mdx (via package.json (literal, a string literal on a changed line), tsconfig.json (literal, a string literal on a changed line))
  • content/docs/plugins/index.mdx (via package.json (literal, a string literal on a changed line), tsconfig.json (literal, a string literal on a changed line), os create (command, read off packages/cli/src/commands/create.ts))
  • content/docs/protocol/kernel/index.mdx (via package.json (literal, a string literal on a changed line), tsconfig.json (literal, a string literal on a changed line), os create (command, read off packages/cli/src/commands/create.ts))
  • content/docs/protocol/kernel/plugin-spec.mdx (via package.json (literal, a string literal on a changed line), tsconfig.json (literal, a string literal on a changed line), os create (command, read off packages/cli/src/commands/create.ts))
  • content/docs/protocol/objectql/schema.mdx (via package.json (literal, a string literal on a changed line))
  • content/docs/upgrading.mdx (via package.json (literal, a string literal on a changed line))

1 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17.mdx (via package.json (literal, a string literal on a changed line), os init (command, read off packages/cli/src/commands/init.ts))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 1 anchor(s) matched too much of the corpus to be a work list: objectstack.config.ts (literal, 32 pages)
  • 5 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 61 of 219 client-bound route-ledger rows — the other 158 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 158: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 56 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 102 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 22 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json d5c40222f3a4a36823e663aa25cd16e801d55d49packageMentionDocs.

Which tree this was computed on

This run read content/docs from 23a5d6ff450c8f71999e37b62c0bd72c86f8fe0b — the merge of head 91cf99066af9943e0d3168e0cf4e98edfe4193b1 into base d5c40222f3a4a36823e663aa25cd16e801d55d49, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# 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

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs d5c40222f3a4a36823e663aa25cd16e801d55d49 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@os-sales os-sales left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 weaker os init plus 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.md at head: "@objectstack/cli": minor, a **BREAKING** banner, a "What to run instead" FROM → TO block, the no-alias statement and the os create plugin carve-out. The written rule: scripts/check-changeset-no-major.mjs header ("During the launch window we ship breaking changes as minor … 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"). 0 major changesets on the head tree. Closest precedent is the same shape, not merely the same clause: .changeset/retire-agent-generator.md (commit 15b63e85a8, os g agent retired behind a signpost) shipped @objectstack/cli: minor with a BREAKING banner. The ! marker is one of the gate's three recognised breaking signals, so feat(cli)!: + minor is consistent, as compliance-families-retired.md already does. Level axis (#16055) satisfied — clause ② yes and not patch. Check Changeset is 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, no packages/spec declaration, no stored sys_metadata shape, nothing for objectstack migrate meta to rewrite, the CLI itself carrying the notice — and it is word-for-word the disposition the shipped os g agent precedent carried. The gate's findMigrationPrescription reads 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.ts at head: availableTypes() (l.454) is Object.keys(templates).join(', ') and is what the Unknown type: branch prints (l.506); the type arg description (l.462-466) is built from Object.keys(templates) too. Swept the head tree for a surviving plugin, example literal: none in packages/cli/src, packages/cli/README.md, scripts/, .github/; scripts/create-scaffold-smoke.sh derives TEMPLATE_KEYS from 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-repo prose no longer offers examples/. 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_TEMPLATES is consulted at l.493, ahead of Unknown type: (l.505) and Project name is required (l.511); exit 1; whole block on stderr. "os create plugin NAME scaffolds the kernel code Plugin contract, which os init does not emit" is true per the audit's §5.1 and the routing table at content/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 @@ in scaffold-emission-policy.e2e.test.ts inserts SCAFFOLD_PNPM_RANGE, on the line directly above the SCAFFOLD_TSX_RANGE, this PR deletes: a one-line conflict, and keep-both is correct because #16654's appended block (l.302+) never uses SCAFFOLD_TSX_RANGE. init.ts hunks (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 anchors package.json (a string literal on a changed line) and os 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
@claude

claude Bot commented Sep 7, 2026

Copy link
Copy Markdown
Contributor Author

Contract-review findings applied in one push: 250ccd2a2f..fd31e93f51, fast-forward, no force.

F1 — citation made honest, ruling NOT resolved. create.ts and the e2e pin header now cite the #15531 batch 66 entry (the half the review verified) and record the no-alias / no-deprecation-window terms as recorded on card #16483, pending maintainer confirmation. The 2026-08-27 date and the "ruled the same week" phrasing are both gone, since neither can be supported today. I did not decide which of the two internal statements was wrong, did not guess a date, and did not hunt for a ruling. ⛔ The runtime message is unchanged: it describes what the code does, which is true whoever ruled it.

F2 — the byte-equality claim is fixed. The runtime message and the changeset now say os init writes "the same tsconfig.json and an equivalent objectstack.config.ts"; the changeset states the difference the audit measured (title-cased name, empty description, no barrels), verified against init.tss empty template on this tree rather than copied from the review.

F3 — applied, in all three places, not just the two you named. -t empty now reads "config only, no src/objects" in the runtime message, in the changeset, and in the doc callout this PR itself added (content/docs/deployment/cli.mdx). The pre-existing gloss at init.ts:679 is untouched.

F4 — both changeset inaccuracies corrected. (a) the docs promise is credited to the separate queue-tier pin; (b) --in-repo is described as narrowed rather than unchanged, naming that its examples/ placement goes with the template and gets no replacement.

F5 — code untouched, my stated reason corrected. SCAFFOLD_TSX_RANGE stays. The PR body no longer claims removing it would have been a second surface removal; it now records the measured reason (a module export inside the package, not a published one) and that the surviving reason is #16654 holding init.ts.

Re-verification and the two NOT-MEASURED gates are in the PR body. ⚠️ One thing that changed under this PR while the review ran: #16654 has landed on main. A local trial merge is conflict-free, but its appended allSixManifests() helper reads one emission too many once this PR takes the CLI side from five to four, and its new check:scaffold-emission-policy gate exists only on main so it could not run here.


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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

cli: retire os create example — it is a weaker os init plus a README; docs point at os init (item 2 of #15531)

1 participant