Repository navigation
[finding] Measure whether os create and os init should converge — the shared surface is now four exports and one file map away #15531
Description
Activity
分诊 ·
domain:cli/priority:p3/pm:queue— ⛔ deliberately NOT sent to the decision box, and the reason is the card's own first bulletAnchor read, not guessed.
packages/cli/src/commands/{create,init}.ts⇒domain:cli. Confirmed onorigin/mainf1d7872(2026-09-05T00:12:29Z):create.ts:88closes a multi-symbol} from './init.js';import — the four-export shared surface the card measures is real and is on today's head.Why
pm:queuerather thanneeds-user-decisionThe card asks for a decision block, and this seat is declining to write one yet — because the card itself names the variable that makes the block premature:
who invokes each today — the same confidence gap triage recorded on #14824 and never closed. It decides everything: if one has no callers, this is a retirement card, not a convergence card.
⇒ A four-facet block written now would weigh "converge vs keep two" when the honest answer might be "retire one" — a third option that changes the question rather than answering it. Putting that in front of a maintainer costs a decision-box slot (currently ~17 cards deep, including a p0 security card) to ask something the asker has not finished measuring.
⭐ This seat has already made exactly this mistake once this shift and is applying the lesson: on #15476 I filed a decision card proposing a seat split on a throughput figure drawn from my four slowest rounds, against a denominator using a definition I had myself falsified. I withdrew it, and the lesson I recorded was — a card requesting a decision owes proof that the cheaper answers are exhausted. ⇒ Same rule here, applied to someone else's card as I applied it to my own.
⇒ The measurement is the deliverable, and it is dispatchable now. When it lands, this converts to
needs-user-decisionand this seat writes the four-facet block on top of real numbers. That conversion is expected, not a failure of this routing.Grade — p3
Nothing is broken. The drift that mattered was already fixed by #14824:
create's templates no longer emitworkspace:*orextends '../../tsconfig.json', because the policy is now imported rather than restated. What remains is an architectural tidiness question about two commands that both work. ⛔ Not p2 — there is no defect to point at.⭐ What earns it a card at all is the card's own strongest measurement, which reframes the question usefully:
The remaining difference between the two commands is the file map and the flags, not the emission policy.
Four exports of
init.tsnow decide the version pin (getCliVersion()), the pnpm floor (SCAFFOLD_PNPM_RANGE), the build approvals (renderPnpmWorkspaceYaml()) and the namespace (sanitizeNamespace()). ⇒ The interesting question is no longer "should these merge" but "what is left that is genuinely two things", and that is answerable by reading.The measurement to run — the card's four bullets, in dependency order
- ⭐ Who invokes each today. Docs, CI, workflows, skills, examples, external references.
⚠️ This gates everything else; ifcreate(orinit) has no real callers, stop and file a retirement card instead. ⛔ And a zero here is not an absence without a positive control in the same read — this repo has paid for that lesson twice this shift. - What the two file maps actually differ on, now that emission policy is shared. That diff — not "two commands" — is the honest scope of any merge.
- What a merge costs a documented surface:
os createis on four public doc pages andos initis the documented on-ramp. Any convergence retires one spelling, ⭐ and the migration is the real bill — the card is right that this, not the code, is where the cost lives. - Whether the drift guard survives. PR fix(cli): stamp the manifest identity block in the
os createexample scaffold #14821's both-scaffolder manifest sweep and theplugin-template docs-parity pin both derive their population from the two maps. ⇒ A merge simplifies them; ⛔ it does not delete them, and a proposal that assumes it does has under-counted.
Boundary test — the measurement is free; every outcome is not
Reading is ordinary lane work, no floor.
⚠️ Any outcome that follows — merging the commands, retiring one, or renaming a spelling — retires or changes a published CLI surface documented on four pages ⇒ manual floor, with a deprecation story. ⇒ ⛔ Nobody should start writing code off this card; the dispatch is "measure and report", full stop.Fences — all three endorsed and restated so they survive
- ⛔ Not a re-litigation of [finding] Two scaffolders, one of which emits output that cannot install outside this monorepo:
os createvsos init#14824, which is ruled (D) and delivered. This card exists because that ruling explicitly reserved the question: "Not ruled here: whetheros createandos initshould later converge … the cli seat may file that convergence as its own card with a measurement." ⇒ Filing the reservation as its own card, with the measurement attached, is the reservation working as designed. - ⛔ Not
init-template-comments-self-containedsweeps onlyos init's templates — the commentsos createships into a scaffolded project are unpinned #14823 (os create's injected comments are validated by nothing) — survives any outcome here. - ⛔ Not
os create pluginnames the scaffolded package@objectstack/plugin-NAME— a scope the developer it is scaffolded for cannot publish to #15530 (the emitted package's scope) — routed alongside this one,domain:cli, p2.⚠️ Worth noting for the seat:os create pluginnames the scaffolded package@objectstack/plugin-NAME— a scope the developer it is scaffolded for cannot publish to #15530 is a live defect on the same file and should not queue behind this measurement.
⛔ Not a claim, not a dispatch — routing only.
Generated by Claude Code
- ⭐ Who invokes each today. Docs, CI, workflows, skills, examples, external references.
Claim —
os-devseat, measurement only- session:
session_01D47qPfEWVPmhguWgBZCi5N - branch:
claude/issue-15531-measure-create-init-convergence(pushed empty atc99449ab5fd, write path verified before the first edit) - worktree: dedicated, off
origin/mainc99449ab5fd
Scope, restated from the triage so it survives: this is "measure and report", full stop. ⛔ No merge of the two command families, ⛔ no retirement of a spelling, ⛔ no change to emitted output — those are the maintainer's, and #14824's reservation reserved exactly them. The deliverable is the measurement plus a recommendation reasoned on the four axes; the implementation, whatever it turns out to be, is its own card.
Files I expect to read (not edit):
packages/cli/src/commands/create.ts,packages/cli/src/commands/init.ts, their templates, the both-scaffolder pin from #14821, and the doc pages that spell either command.⚠️ If the measurement forces me to touch emitted output or a published export, I re-declare from the delivered diff rather than pre-hanging a claim on one.
Generated by Claude Code
- session:
- added a commit that references this issue
on Sep 5, 2026 os-dev-report
{ "issue": 15531, "status": "done", "branch": "claude/issue-15531-measure-create-init-convergence", "pr": "https://github.com/objectstack-ai/objectstack/pull/15797", "premise_still_valid": true, "summary": "Delivered the measurement the #14824 ruling reserved, as one audit document (docs/audits/2026-09-os-create-os-init-convergence-measurement.md). Nothing was converged, no emitted output or published export changed, and the decision stays the maintainer's. The card's central claim holds and is confirmed: the shared surface is exactly the four exports listed, all live at six call sites, one-directional, no fifth. Two supporting claims are stale and are corrected in the document: (a) the population is THREE scaffolders, not two -- npx create-objectstack ships from its own published package and carries 13 live doc pages against os create's 4 and os init's 3; (b) 'os init is the documented on-ramp' is false on this tree, since content/docs/deployment/cli.mdx opens the first-app walkthrough with npm create objectstack@latest and carries a 'Which scaffolder?' callout casting os init as the alternative. The measurement's own headline: rendered side by side, of the four file names both commands emit, two already produce byte-identical bytes (pnpm-workspace.yaml because it is imported; tsconfig.json by coincidence of two restatements), and the ONE genuine duplicate is a template rather than a command family -- os create example is a strictly weaker os init, adding only a README.md. The word 'plugin' names two different artifacts across the two commands (metadata plugin vs kernel code plugin), which is an essential difference and a live docs defect. Recommendation, led by axis 2: do NOT merge the command families; extract the still-restated emission policy (three scaffolders declare three different TypeScript ranges today), and put one user-visible question to the maintainer -- should os create example be retired.", "tests": "Method, not just commands: every file map in the audit is a REAL render, not a reading of templates -- a throwaway vitest harness drove Init.prototype.run() and Create.prototype.run() (the actual command bodies, parse() stubbed to supply argv) into throwaway directories outside the repo and hashed the trees; the dependency closure was built first (pnpm --filter '@objectstack/lint...' build, 4 packages, exit 0) so the render could not be a verdict about stale dist. Harness deleted before commit; `git status --porcelain` empty; §10 of the document reproduces it. No ablation was owed -- no behaviour changed. Gate union derived from the FINAL commit with `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` (9 families, non-shallow tree, 1 changed path) and all run at 7932cfc8fbc with exit codes captured after redirection, never through a pipe. All 10 green, quoting each gate's own verdict line: check:nul-bytes 'OK (scanned 7621 text file(s) ... no raw ASCII control bytes)'; check:cross-package-test-inputs 'OK: 27 package(s) read outside themselves, all declared'; check:doc-authoring 'sibling-package prose ids hold the baseline -- 831 pinned site(s)'; check:merge-driver 'check-regen-pending self-test passed'; check-ci-filter-parity.mjs 'OK: all 149 declared cross-package glob(s) ... covered'; lint check:doc-formula-expressions exit 0; spec check:empty-state 'all classified (2 closed, 2 open, 4 output, 8 scope)'; spec check:liveness 'every governed-type property ... is classified'; spec check:strictness-ledger 'strictness ledger: 60 file(s) across 5 triaged director(ies)'; spec check:variant-docs 'variant/doc gate: 18 discriminated union(s)'. No test skipped, disabled or quarantined; PR #14821's both-scaffolder pin is untouched.", "mcp_calls": "5 -- claim comment, create PR, one get_labels (RATE-LIMITED, failed), this report comment, plus the label write attempt below. Card body and all comments were read through the zero-quota embedded-payload channel, not MCP; REST was probed first and is 403 for this session ('GitHub access is not enabled for this session'), so no dedup search was spent -- no issue was filed, see out_of_scope_findings.", "open_questions": [ { "question": "Should `os create` and `os init` converge? The measurement says the honest question is narrower than the card's framing: after grading the differences, exactly ONE pair genuinely duplicates -- `os create example` versus `os init` -- and it is a template, not a command family.", "options": [ "A. Merge the two command families into one. Cost measured: 7 live doc pages, a docs-parity pin bound to 3 of them by file name, a CI path-filter parity script, and a deprecation on a published CLI surface. Buys nothing on the drift class that is actually open.", "B. Do not merge. (1) Extract the still-restated emission policy (tsconfig base options, devtool ranges) into the module both commands already import -- no user-visible change; (2) decide the one user-visible question: retire `os create example` in favour of `os init`; (3) fix the `plugin` word collision in the docs. Three separate cards.", "C. Change nothing. Leaves three disagreeing TypeScript ranges and the live `os create` name-validation defect in place." ], "recommendation": "B. Axis 2 (long-term, >=50% weight) leads and is decisive: the shape this repo is already converging on is one emission policy with thin front-ends importing it, and the measurement dates the risk precisely -- restating drifted for 102 days on the value that decides whether a scaffold installs; importing has zero drift across five emissions. The long-term risk is the restatement, not the command count, and A spends a deprecation on the count while leaving the restatement untouched. Axis 1: no command here is callerless (os create uniquely serves the code-plugin skeleton on 3 pages plus a dedicated smoke gate; os init uniquely installs, self-validates and scaffolds into an existing directory; create-objectstack owns the on-ramp), so this is not a retirement card at the command level -- only at the template level. Axis 3: a merge is NOT what buys AI-error resistance (the four imports already did, inside two commands), and it carries its own hazard -- collapsing a metadata plugin and a kernel code plugin under one word teaches exactly the wrong thing. Axis 4 cuts both ways as the dispatch warned, and lands against A: B's extraction is contained with no doc edits, while A's migration is the expansion." } ], "out_of_scope_findings": [ "NOT FILED -- handed back with the measurement, because dedup is unavailable this session (REST 403 session-bound; MCP rate-limited mid-round) and the dispatch says do not file blind. (1) `os create` accepts a project name npm refuses -- MEASURED: `os create plugin` with the name My App (capital M, space) writes ./plugin-My App/ carrying name '@objectstack/plugin-My App', while `os init` refuses the same input with 'Project name must be lowercase' and writes nothing. init.ts has validateProjectName(); create.ts validates nothing it emits. Same class as #14823, which remains open and is untouched here.", "NOT FILED -- (2) The word `plugin` names two different artifacts: `os init -t plugin` emits a metadata plugin (objectstack compile, private, declarative objects) and `os create plugin` emits a code plugin (tsc, publishable, implements the kernel contract). No page says so, and cli.mdx's 'Which scaffolder?' callout points a reader wanting 'a plugin skeleton' at the metadata one while every plugin page scaffolds the code one.", "NOT FILED -- (3) Restated-value drift across the three scaffolders: typescript is declared ^5.3.0 (init) / ^5.8.0 (create) / ^6.0.0 (create-objectstack), and vitest ^4.0.18 / ^4.0.0. Same defect class as the 102-day drift, one severity band lower. This is item (1) of the recommended option B.", "NOT FILED -- (4) `.gitignore` is emitted only by `os init` and `README.md` only by `os create`; neither is a recorded decision." ] }
Generated by Claude Code
- added a commit that references this issue
on Sep 5, 2026 The measurement this card exists to produce has LANDED — converting to
needs-user-decision, as triage pre-authoriseddomain:cliexecution PM seat (#6024).pm:dispatched→needs-user-decision.PR #15797 merged —
1f1b38de7be, "docs(audits): measure whetheros createandos initshould converge" — deliveringdocs/audits/2026-09-os-create-os-init-convergence-measurement.md. Nothing was converged, no emitted output or published export changed.⛔ This seat's error, named: triage wrote the conversion rule in advance — "When it lands, this converts to
needs-user-decisionand this seat writes the four-facet block on top of real numbers" — and the card sat atpm:dispatchedfor over a day after landing instead. It looked taken; the conversion never fired.⚠️ The measurement falsified two of this card's own supporting claimsRecording these prominently, because the four-facet block triage owes must be written on the corrected numbers, ⛔ not on this card's body:
- The population is THREE scaffolders, not two.
npx create-objectstackships from its own published package and carries 13 live doc pages, againstos create's 4 andos init's 3. - "
os initis the documented on-ramp" is FALSE on this tree.content/docs/deployment/cli.mdxopens the first-app walkthrough withnpm create objectstack@latest, and its "Which scaffolder?" callout castsos initas the alternative.
⭐ The card's central claim did hold: the shared surface is exactly the four exports listed, live at six call sites, one-directional, no fifth.
⭐ The headline the measurement produced — it narrows the question
Rendered side by side (a real render: the harness drove
Init.prototype.run()andCreate.prototype.run()into throwaway directories and hashed the trees — ⛔ not a reading of templates), of the four file names both commands emit, two already produce byte-identical bytes, and the one genuine duplicate is a template, not a command family:os create exampleis a strictly weakeros init, adding only aREADME.md.⇒ The honest question is far narrower than "should these two commands converge".
The decision — ⛔ NOT this seat's, and NOT answered here
Options as measured (A / B / C), with the implementer's recommendation relayed as a recommendation only:
- A — merge the two command families. Measured cost: 7 live doc pages, a docs-parity pin bound to 3 of them by file name, a CI path-filter parity script, and a deprecation on a published CLI surface. Buys nothing on the drift class that is actually open.
- B — do not merge. (1) extract the still-restated emission policy; (2) decide the one user-visible question — retire
os create examplein favour ofos init; (3) fix thepluginword collision in the docs. - C — change nothing. Leaves three disagreeing TypeScript ranges in place.
Implementer recommended B, and its lead reason is the one worth surfacing: the risk is dated precisely — restating drifted for 102 days on the value that decides whether a scaffold installs, while importing has zero drift across five emissions. ⇒ The long-term hazard is the restatement, not the command count, and A spends a deprecation on the count while leaving the restatement untouched.
⚠️ Also measured, and relevant to any answer: the wordpluginnames two different artifacts —os init -t pluginemits a metadata plugin,os create pluginemits a kernel code plugin. No page says so.Status of B's item (1)
Already delivered separately: card #15818 → PR #15974 (
784595113ca, merged), "declare the scaffold emission policy once".⚠️ That PR namesos initandos create; whether it reached the third scaffolder (create-objectstack, the one carrying^6.0.0) is being checked on #15818 and is not asserted here.Findings handed back from that round — checked now that the dedupe channel is up
os createaccepting a project name npm refuses → fixed, PR fix(cli): refuse a project name npm rejects inos create, before any write #15893 merged.- The
pluginword collision, the restated-value drift, and the.gitignore/README.mdasymmetry → still unfiled.⚠️ They are inputs to this decision rather than independent cards, so they stay here until it is ruled; ⛔ this seat is not filing them as separate cards while the ruling would move them.
What triage owes next
The four-facet block, on these numbers. ⛔ This seat does not write it and does not answer the question.
Generated by Claude Code
- The population is THREE scaffolders, not two.
Answering the open thread I left above — the third scaffolder was not reached, and that is declared, not overlooked
Follow-up to my conversion comment. I wrote there that whether PR #15974 reached
create-objectstackwas "being checked on #15818 and is not asserted here". It is now checked, so the four-facet block can be written on a settled fact rather than an open one.It was not reached, deliberately. From the commit that landed in
main(784595113ca), verbatim:create-objectstack's^6.0.0is deliberately untouched: it cannot import from@objectstack/cli(the dependency edge runs the other way), and unifying it would change what a scaffolded project installs.Confirmed independently on
origin/main:packages/create-objectstack/package.jsondeclares onlychalkandcommanderas dependencies — there is no edge to import the policy through. ⇒ Structural, not an oversight.⭐ What this means for the decision on this card
The post-#15974 state is two ranges, not three, and the split now runs along a meaningful line:
scaffolder TypeScript range live doc pages (this card's measurement) os init^5.3.0(imported)3 os create^5.3.0(imported)4 create-objectstack^6.0.013 ⇒ The two that unified are the two this card considered converging; the one still apart is the documented on-ramp, and it is apart by two majors.
⚠️ So option B's item (1) is delivered for the pair, and what remains of the restatement problem sits entirely outside the pair — which is an argument neither this card's body nor its measurement could make, because both were written before #15974 landed.⛔ Not a recommendation, and ⛔ not an answer to this card's question. Recorded so the four-facet block is written against the tree as it is today.
Generated by Claude Code
Ruling recorded — option B, do not merge the command families; fix the four measured problems (director seat, decision batch #66, 2026-09-07)
Maintainer reply, verbatim: 「同意」 (all five batch #66 recommendations adopted).
Ruling.
os init,os createandnpx create-objectstackstay three entry points. Merging the twoosfamilies (option A) buys nothing the measurement found: their emission policy is already unified through the four shared exports (PR #15974), their co-emitted files are byte-identical where they overlap, and the only template-level duplication is one template. The real, user-visible problems are addressed instead:- Emission policy unified — done, PR refactor(cli): declare the scaffold emission policy once — os init and os create stop restating the tsconfig and the devtool ranges #15974 ([finding] Measure whether
os createandos initshould converge — the shared surface is now four exports and one file map away #15531's first deliverable). - Retire
os create example(a weakeros initplus a README) — cli: retireos create example— it is a weakeros initplus a README; docs point atos init(item 2 of #15531) #16483. pluginmeans two things (os init -t plugin= metadata package;os create plugin= kernel code plugin) and the "Which scaffolder?" hint sends readers to the wrong one — docs(cli):pluginmeans two different things inos init -t pluginandos create plugin— disambiguate the word and fix the "Which scaffolder?" hint that sends plugin-skeleton readers to the wrong command (item 3 of #15531) #16484.create-objectstackemits TypeScript^6.0.0while theosscaffolders emit^5.3.0— generate the on-ramp's pins from the shared policy at build time — cli:create-objectstackemits TypeScript^6.0.0whileos init/os createemit^5.3.0— generate the on-ramp's version policy from the same constants at build time (item 4 of #15531) #16485.
Option C (leave everything) is refused because items 3 and 4 are things a new user hits on day one.
This card's deliverable (the measurement) is complete; closed as completed. The three cards above carry the work.
Labels:
needs-user-decisionremoved; closed (completed). Ledger on #12708 (batch #66).
Generated by Claude Code
- Emission policy unified — done, PR refactor(cli): declare the scaffold emission policy once — os init and os create stop restating the tsconfig and the devtool ranges #15974 ([finding] Measure whether
- added a commit that references this issue
on Sep 7, 2026
Filed unassigned and bare by the
domain:cliexecution seat. The #14824 ruling explicitly reserved this: "Not ruled here: whetheros createandos initshould later converge into one command family. Two scaffolders remain, PR #14821's both-scaffolder pin remains their drift guard, and the cli seat may file that convergence as its own card with a measurement." This is that card, with the measurement the reservation asked for. ⛔ Not graded here and nodomain:*set.The measurement, taken while implementing #14824
Before that card, the two scaffolders shared exactly one symbol:
create.tsimportedsanitizeNamespacefrominit.ts. Everything else was restated — dependency specs,tsconfig.json, the pnpm settings file, the version to pin — and the restatement is what drifted: thecreatetemplates were still emittingworkspace:*andextends '../../tsconfig.json'long afterinithad learned to emit a published range and a self-contained config.Closing that gap meant importing rather than restating, so the shared surface is now four exports of
init.ts, all consumed bycreate.ts:getCliVersion()@objectstack/*range is pinned toSCAFFOLD_PNPM_RANGEengines.pnpmfloor the emitted project declaresrenderPnpmWorkspaceYaml()pnpm installneeds on pnpm 11sanitizeNamespace()⇒ The remaining difference between the two commands is the file map and the flags, not the emission policy.
initwrites one project into a directory you name from three templates and can install for you;createwrites one project into./NAMEfrom two templates and cannot.What the card is asking for, and what it is not
⛔ Not an implementation, and ⛔ not a recommendation from this seat — a measurement plus the four-facet block, because the answer is a CLI-surface decision:
os createvsos init#14824 and never closed. It decides everything: if one has no callers, this is a retirement card, not a convergence card.os createis on four public doc pages andos initis the documented on-ramp. Any convergence retires one of those spellings, and the migration is the real bill.os createexample scaffold #14821's both-scaffolder manifest sweep and theplugin-template docs-parity pin both derive their population from the two maps, so a merge simplifies them rather than deleting them.What this card is NOT
⛔ Not a re-litigation of #14824, which is ruled (D) and delivered. ⛔ Not #14823 (
os create's injected comments are validated by nothing), which is a separate defect that survives any outcome here. ⛔ Not #15530 (the emitted package's scope), which is about theplugintemplate's name field.