Skip to content

fix(cli): os init / os create emit overrides.vitest so new projects install on npm 10 - #23850

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-23805-scaffold-vitest-override
Oct 16, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-23805-scaffold-vitest-override

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #23805

Clause-②: no

os init (all three templates) and os create plugin now emit "overrides": { "vitest": "~4.0.0" }, built from SCAFFOLD_VITEST_RANGE through one new export, SCAFFOLD_NPM_OVERRIDES. The scaffolded project now installs on npm 10, the npm that Node 22 bundles. scripts/sync-scaffold-emission-policy.mjs gains an overrides.vitest POLICY_STAMPS row, so the blank template's override is now generated from the same constant instead of being kept by hand. The template file's bytes do not change, because it already reads ~4.0.0.

Upstream re-measured first, as the card asks

Read 2026-10-16T06:46Z with npm view:

  • vitest dist-tags: V4 = 4.1.12, latest = 5.0.5.
  • vitest@4.1.12 peers "@vitest/browser-webdriverio": "^4.1.11" (optional). Every other @vitest/* peer is pinned to exactly 4.1.12.
  • @vitest/browser-webdriverio has no 4.1.12. Its newest 4.x is 4.1.11, which peers "vitest": "4.1.11".
  • So the contradiction is still live upstream. The range itself is not changed here.

Every emission site, with its verdict

Measured 2026-10-16T06:57Z. Each manifest was installed in an empty directory with a fresh --cache, against the published 18.0.0-next.2. "Before" is today's main emission.

Site declares vitest directly before, npm 10.9.9 verdict
os init -t app (init.ts, renderScaffoldPackageJson) no exit 1, edgesOut in scope: emits the override
os init -t plugin (same renderer) ~4.0.0 exit 1, edgesOut in scope: emits the override
os init -t empty (same renderer) no exit 1, edgesOut in scope: emits the override
os create plugin standalone (create.ts) ~4.0.0 exit 0 emits the override (card criterion; keeps it equal to the declaration)
os create plugin --in-repo ~4.0.0 n/a (pnpm workspace member) no override: npm reads overrides only from the root manifest and pnpm never reads it, so here it would do nothing
create-objectstack blank template ~4.0.0 already overridden (landed earlier) now stamped by the new POLICY_STAMPS row

os create has one template (plugin), and os init has three (app, plugin, empty), all rendered through renderScaffoldPackageJson. No other packages/cli code writes a scaffold package.json.

Why -t app and -t empty too, against the card's plugin-only criterion

The dispatch assumed that a site emitting no vitest needs no override. The measurement above shows otherwise: -t app and -t empty crash the same way, because what all three templates share is @objectstack/cli in devDependencies, and its closure carries optional vitest peers (@objectstack/metadata-core peers ^4.0.0, better-auth peers ^2 || ^3 || ^4). -t app is also the docs.objectos.ai quickstart's command, measured failing 3 of 3 in the card's thread. os create plugin has no @objectstack/cli in its closure, which is why it installed without the override.

The installs after the change

Each emitted manifest was produced by driving the real CLI (tsx bin/run-dev.js init NAME -t T --no-install and create plugin NAME), then installed in an empty directory with a fresh --cache:

manifest npm 10.9.9 npm 11.21.1 vitest in the tree
os init -t app exit 0 exit 0 none
os init -t plugin exit 0 exit 0 one copy, 4.0.18
os init -t empty exit 0 exit 0 none
os create plugin exit 0 exit 0 one copy, 4.0.18

Cost, measured

On an emitted -t app with the override (npm 10.9.9):

  • A later bare npm install -D vitest is refused with EOVERRIDE: Override for vitest@* conflicts with direct dependency.
  • npm install -D vitest@~4.0.0 installs (vitest 4.0.18).

The refusal is loud and names the conflict; without the override the project could not install at all. The changeset gives the one-line fix. "$vitest" is not used: it needs a direct dependency, and two of the three os init templates have none.

The stamp row

POLICY_STAMPS gains { key: 'overrides.vitest', block: 'overrides', field: 'vitest', constant: 'SCAFFOLD_VITEST_RANGE' }. The engine already locates any "BLOCK": { … } object and one field inside it, so no engine change was needed. The self-test:

  • Every fixture template carries overrides.vitest.
  • Battery B now parses both vitest stamps, instead of a substring check that one stale copy could still pass.
  • New battery J drifts the override alone while devDependencies.vitest agrees. It checks that --check exits 1 naming overrides.vitest, that the agreeing key is not blamed, that the rewrite touches only that span byte for byte, and that a template dropping the overrides block exits 1 naming it.
  • Roster floor 9 → 10.

--check on the real template, before and after a mutation (through scripts/ablation-replace.mjs, which restores and checks the blob hash):

  • The blank template's overrides.vitest was mutated to ^4.1.0. --check then exits 1 with packages/create-objectstack/src/templates/blank/package.json: overrides.vitest is "^4.1.0" but the policy declares "~4.0.0".
  • Restored: blob 1bbbff43ce0f equals HEAD, and git diff HEAD is empty.
  • Control: the same mutation under main's script (1ecb08a1c, no row) exits 0 (2 carrier(s) emit the shared policy). So on main, an override drift that npm refuses with EOVERRIDE passed the gate.

Pins

packages/cli/test/scaffold-npm-overrides.test.ts is a unit-tier file. Every expectation is derived from the exported constants, with no literal range:

  • every os init template and os create plugin (standalone) emit overrides equal to SCAFFOLD_NPM_OVERRIDES, whose vitest is SCAFFOLD_VITEST_RANGE;
  • every override that names a direct dependency equals that dependency's spec (npm EOVERRIDE);
  • --in-repo emits no overrides;
  • a control checks that the harvest is non-empty and that the equality case ranges over the two manifests that declare vitest.

Ablation, through scripts/ablation-replace.mjs. Each leg mutates, runs the file, restores, and proves the restored blob equals HEAD:

  • Deleting the override from renderScaffoldPackageJson fails the 3 os init cases (3 failed / 8 passed).
  • Deleting it from create.ts fails os create plugin (1 failed / 10 passed).
  • Forcing it on in-repo fails the in-repo case (1 failed / 10 passed).

Verification

All results below are at head f8d18b092 unless a line says otherwise. Exit codes were captured before any pipe.

  • Gate union: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack names 85 commands, derived from merge base 43567a8fc. Run at f8d18b092: 85 of 85 exit 0. --ran reconciliation: exit 0, 85 derived, 85 run, 0 NOT-MEASURED, 0 UNRUN.
  • An earlier pass of the union, at e521b5709, found one real red: check:cross-package-test-inputs exit 1. A comment in template-consistency.test.ts spelled the stamp script's repo path, which the gate reads as a cross-package input. The comment was reworded in f8d18b092.
  • node scripts/sync-scaffold-emission-policy.mjs --self-test: exit 0, 60 assertions, 10 batteries. --check: exit 0.
  • pnpm --filter @objectstack/cli exec vitest run --project unit --maxWorkers=2 over the 23 non-e2e test files that import commands/init or commands/create, the new pin included: exit 0, 22 files / 501 tests passed, at d8a9a54f3. The later commits touch no packages/cli file.
    • test/generate-stack-reach.test.ts is in the integration project. That tier and the nightly scaffold-emission-policy.e2e.test.ts are declared to CI.
  • pnpm --filter @objectstack/cli typecheck: exit 0 (tsc --noEmit plus check:test-typecheck OK), at d8a9a54f3.
  • pnpm --filter create-objectstack test: exit 0, 18 files / 275 tests. typecheck: exit 0.
  • pnpm exec eslint --no-inline-config over the five changed source and test files: exit 0. The repo-wide lint run belongs to the Lint & Repo Gates check-run.

Acceptance notes


Generated by Claude Code

…nstalls on npm 10

vitest 4.1.12 peers @vitest/browser-webdriverio ^4.1.11, whose only
release peers vitest 4.1.11 exactly. npm 10 reaches that contradiction
through optional vitest peers in the @objectstack/cli closure and
crashes (edgesOut of null) on every os init template, app and empty
included.

Every standalone os init / os create manifest now emits
overrides.vitest from SCAFFOLD_VITEST_RANGE (SCAFFOLD_NPM_OVERRIDES),
and the stamp script gains an overrides.vitest POLICY_STAMPS row so the
blank template's override is generated rather than hand-kept.

Claude-Session: https://claude.ai/code/session_018t6a5rvqPRvEDMCVVW9kYT
Co-authored-by: Claude <noreply@anthropic.com>
…po path

check:cross-package-test-inputs reads a quoted repo-relative path in a
test source as an input the test reads; the comment only describes where
the override is generated, so it names the script without spelling the
path.

Claude-Session: https://claude.ai/code/session_018t6a5rvqPRvEDMCVVW9kYT
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Oct 16, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

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

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

  • content/docs/deployment/cli.mdx (via os create (command, read off packages/cli/src/commands/create.ts), os init (command, read off packages/cli/src/commands/init.ts))
  • content/docs/getting-started/examples.mdx (via os init (command, read off packages/cli/src/commands/init.ts))
  • content/docs/getting-started/your-first-project.mdx (via os create (command, read off packages/cli/src/commands/create.ts), os init (command, read off packages/cli/src/commands/init.ts))
  • content/docs/plugins/index.mdx (via os create (command, read off packages/cli/src/commands/create.ts), os init (command, read off packages/cli/src/commands/init.ts))
  • content/docs/protocol/kernel/index.mdx (via os create (command, read off packages/cli/src/commands/create.ts), os init (command, read off packages/cli/src/commands/init.ts))
  • content/docs/protocol/kernel/plugin-spec.mdx (via os create (command, read off packages/cli/src/commands/create.ts))

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

  • content/docs/releases/v17/17-1.mdx (via os init (command, read off packages/cli/src/commands/init.ts))
  • content/docs/releases/v17/17-4.mdx (via os create (command, read off packages/cli/src/commands/create.ts), os init (command, read off packages/cli/src/commands/init.ts))
  • content/docs/releases/v17/17-5.mdx (via os init (command, read off packages/cli/src/commands/init.ts))
  • content/docs/releases/v17/17-7.mdx (via 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
  • 2 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 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.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 28 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 3c080a65c8c17af97403fed1c26cddac05f43a6a → packageMentionDocs.

Which tree this was computed on

This run read content/docs from f44475186840ba7a85fe06d09d3eed603f794c56 — the merge of head f8d18b09257057d12893525766d1d4b292e1c020 into base 3c080a65c8c17af97403fed1c26cddac05f43a6a, 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 f44475186840ba7a85fe06d09d3eed603f794c56 && git checkout f44475186840ba7a85fe06d09d3eed603f794c56
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 3c080a65c8c17af97403fed1c26cddac05f43a6a f8d18b09257057d12893525766d1d4b292e1c020 && git checkout -B drift-repro 3c080a65c8c17af97403fed1c26cddac05f43a6a && git merge --no-ff f8d18b09257057d12893525766d1d4b292e1c020

node scripts/docs-audit/affected-docs.mjs --json 3c080a65c8c17af97403fed1c26cddac05f43a6a

⚠️ 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 3c080a65c8c17af97403fed1c26cddac05f43a6a → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对

ACCEPT · #23805 · domain:cli seat 2 · os-tesla · session session_018t6a5rvqPRvEDMCVVW9kYT · 2026-10-16T08:51Z · reviewed head f8d18b09257057d12893525766d1d4b292e1c020, against GitHub and the branch, not against the report's prose.

  1. Shape. Draft, base main, first line Fixes #23805; the only closing keyword in the body is that one (release: the Docker image job fails at "Build and push" since Release #11838 (npm 18.0.0-next.2 published fine): npm install -g @objectstack/cli crashes in npm's peer resolution ("Cannot read properties of null (reading 'edgesOut')") #23801, finding: existing projects cannot npm install @objectstack/plugin-auth or @objectstack/metadata-core on npm 10 since vitest@4.1.12 (edgesOut); metadata-core's own optional vitest peer is one path in #23806 and release(next): the Version Packages PR #23481 is red — npm 10 crashes with edgesOut on two lockfile-less installs (validate-deps vendor-export resolve, scaffold-e2e); fix on main, never on the generated branch #23788 appear only in non-closing sentences). Clause-②: no at line start in the body and in the changeset, matching claim 6209758755. The new export SCAFFOLD_NPM_OVERRIDES lives in packages/cli/src/commands/init.ts, and the package's public entry (src/index.ts) re-exports only default as InitCommand from that module, so the published surface does not widen and the no stands. check-governed-merges --pr 23850: 0 of 6 paths governed; 295 changed lines.
  2. Premise, measured. The report re-measured upstream first (vitest@4.1.12 still peers @vitest/browser-webdriverio ^4.1.11, which has no 4.1.12), so the range stays ~4.0.0. It also falsified the PM's assumption 2: before the change, on npm 10.9.9, os init -t app, -t plugin and -t empty all exited 1 with edgesOut, because what they share is @objectstack/cli; os create plugin exited 0. So the override goes on every standalone template. That is the card's ask ("os init / os create emit the same overrides.vitest = SCAFFOLD_VITEST_RANGE"); for app and empty, the criterion's "equal to their devDependencies.vitest" is vacuous, since they declare no vitest. Read in the diff: renderScaffoldPackageJson emits overrides: { ...SCAFFOLD_NPM_OVERRIDES } for every os init template; create.ts emits it only when standalone, so --in-repo gets none (npm reads overrides from the root manifest only, and pnpm never reads it). The constant is built from SCAFFOLD_VITEST_RANGE, so the two cannot disagree.
  3. Changeset, sentence by sentence. @objectstack/cli patch, published. "every template: app, plugin, empty" and "os create plugin": the diff. "npm 11 installs the same tree, and pnpm ignores the field": the report's 4/4 on npm 11.21.1. "a bare npm install -D vitest is refused with EOVERRIDE … npm install -D vitest@~4.0.0": measured on an emitted -t app. "os create plugin --in-repo is unchanged": the standalone guard. No create-objectstack changeset is owed: the blank template's bytes do not change (the new POLICY_STAMPS row stamps the value it already holds), and scripts/ is not published.
  4. Gates, pinned to the head. f8d18b0925 check-runs read at review: 40; 24 success, 7 skipped, 9 in_progress (Test Core 1–6/6, two Type Check lanes, Lint · PM dispatch-gates). Six skips are EXPECTED_SKIPS rows; the seventh, Registry canary: ${{ matrix.template }}, is a schedule / workflow_dispatch-only job (scaffold-e2e.yml), the non-PR-event class the tool derives. Landing waits for every check green or an expected skip; this record is not a green reading. The dev's local subset: 85 of 85 derived gate commands exit 0 at the head (--ran: 0 NOT-MEASURED, 0 UNRUN); the cli unit files importing init / create (22 files, 501 tests) and typecheck ran at d8a9a54f3 (later commits touch no packages/cli file); create-objectstack test and typecheck at the head; the stamp script's --self-test (battery J added, floor 9 to 10) and --check; ablations on three legs and a stamp-red mutation with a control under main's script; real installs 4/4 on npm 10.9.9 and 4/4 on npm 11.21.1 (baseline at 1ecb08a1c: 3 of 4 exit 1).
  5. Accepted deviations. (1) The scope widening in item 2, measured. (2) packages/create-objectstack/src/template-consistency.test.ts, outside the claim's listed files: a comment-only change. Its comment said the template's override "is not" stamped, which this PR's POLICY_STAMPS row makes false, and its earlier wording spelled a path that check:cross-package-test-inputs read as a cross-package input. A sentence this PR made false, corrected in the same PR; no assertion changed. (3) A merge of origin/main (43567a8fc, scripts/ci only) before the PR opened. (4) A container restart killed the first unit run, which was re-run in full.
  6. Out of scope. One class: a finding: the published skill's copy-paste manifest (skills/objectstack-platform/references/bootstrap.md, the "Complete Working Example" package.json fence) fails npm install on npm 10.9.9 with edgesOut and installs with overrides.vitest added (measured). This is the user-remedy-text family that docs: npm 10 cannot install @objectstack/plugin-auth or @objectstack/cli (edgesOut through better-auth's optional vitest peer); the install and self-hosting pages name the npm 11 / overrides.vitest remedy #23849 carries, so it goes to that card as an enumerated location, not as a new card: pointer comment there. skills/** is a Tier H governed surface; the routing is triage's.

Landing: once every check on the head is green or an expected skip, this seat flips the PR ready and arms auto-merge through the relay, then follows it to MERGED.


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 16, 2026 09:05
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 16, 2026 09:05
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 16, 2026
Merged via the queue into main with commit a31ff2e Oct 16, 2026
44 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-23805-scaffold-vitest-override branch October 16, 2026 09:24
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/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

cli: os init / os create emit manifests npm 10 cannot install since vitest@4.1.12 (edgesOut); the CLI half of #23788's template override

2 participants