@orkestrel/scaffold 0.0.50 → 0.0.51

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -13,7 +13,7 @@
13
13
  - External delegates, including Cursor and Codex models, have no exemption. Every dispatch restates the non-negotiables, applicable rules, guide or spec, and owned files. Every result receives independent review.
14
14
  - Before working, read in order:
15
15
  1. this file;
16
- 2. every applicable file in `.claude/rules/` from the rule map below;
16
+ 2. every applicable file in `.claude/rules/` from the Rule map section;
17
17
  3. every explicitly invoked or dispatch-named skill and the references it requires;
18
18
  4. `guides/README.md`, the matching guide or spec, and `ROADMAP.md` when present.
19
19
  - Rules state **how to write**. Guides and specs state **what to build** and the domain workflow. When they conflict, stop and surface the conflict.
@@ -115,7 +115,7 @@ npm run format:check → npm run lint:check → npm run check → npm run build
115
115
 
116
116
  ## Rule map
117
117
 
118
- Every file below is a normative extension of this root. Read every rule relevant to the files or concepts you touch. Path frontmatter only controls Claude's automatic loading.
118
+ Every file in the following table is a normative extension of this root. Read every rule relevant to the files or concepts you touch. Path frontmatter only controls Claude's automatic loading.
119
119
 
120
120
  | Rule | Governs |
121
121
  | -------------------------------- | ---------------------------------------------------------------------- |
@@ -7,7 +7,7 @@ harness follows this file.
7
7
 
8
8
  Read in this order before acting:
9
9
 
10
- 1. The user's current instruction. It wins over everything below.
10
+ 1. The user's current instruction. It wins over every later item.
11
11
  2. `AGENTS.md` and the applicable `.claude/rules/*.md` files. They govern code substance.
12
12
  3. This file. It governs agent operation only and cannot weaken the coding contract.
13
13
  4. The dispatch-named skill and the references it requires.
@@ -95,8 +95,9 @@ Record the substitution.
95
95
  - Grok takes every lane only in Cursor, and only when Opus 5 and Sol are both unavailable.
96
96
  - Treat a lane that returns no verdicts as a lane that did not run. A bench lane reporting that its
97
97
  driver executed and its engine was never reached is a dark bench, not a result. Record the bench
98
- dark from that report, re-run the lane on the substitute engine from the table above, and name in
99
- the routing ledger which lane ran on which engine. Never accept a round with one lane empty.
98
+ dark from that report, re-run the lane on the substitute engine from the preceding table, and
99
+ name in the routing ledger which lane ran on which engine. Never accept a round with one lane
100
+ empty.
100
101
  - Re-read bench liveness at dispatch, not at session start. A bench that probed live can be dark when
101
102
  the lane launches.
102
103
 
@@ -288,13 +289,14 @@ owns each bench's exact probe.
288
289
 
289
290
  The local steps still run, because they route the recovery rather than decide the verdict: an
290
291
  unresolved CLI is an install problem, a failed authentication-state check starts the login ladder
291
- below, and a bench that passes both and still cannot round-trip is dark for a reason no local check
292
- can see. Record every dark bench with its fallback and the lane substitution it forces, and never
293
- absorb one silently. A readiness script reports readiness and performs no model call, so the round
294
- trip belongs to the Orchestrator's own probe or to the bridge carrying the unit, never to the hook.
295
- Liveness also expires: a dispatch that fails on quota, model access, or the network is a fresh
296
- liveness result rather than a unit-level fault, so record the bench dark from there and re-plan the
297
- lane instead of re-dispatching against a session-start answer that no longer holds.
292
+ in Recovering a dark bench, and a bench that passes both and still cannot round-trip is dark for a
293
+ reason no local check can see. Record every dark bench with its fallback and the lane substitution
294
+ it forces, and never absorb one silently. A readiness script reports readiness and performs no
295
+ model call, so the round trip belongs to the Orchestrator's own probe or to the bridge carrying
296
+ the unit, never to the hook. Liveness also expires: a dispatch that fails on quota, model access,
297
+ or the network is a fresh liveness result rather than a unit-level fault, so record the bench dark
298
+ from there and re-plan the lane instead of re-dispatching against a session-start answer that no
299
+ longer holds.
298
300
 
299
301
  1. **Absorb.** Dispatch `grok` for terrain, prior art, and the reading the decision needs. In an
300
302
  Orkestrel repo dispatch `orkestrel` alongside it for live package state. Skip only when the
@@ -846,8 +848,8 @@ either publishes packages nobody needed to publish or leaves a consumer pinned t
846
848
  whitespace-only differences; a superfluous diff (formatting, blank lines, map noise) moves
847
849
  nothing and obliges nothing. A material diff — tokens, declarations, logic — means the published
848
850
  surface moved — a forced `src` or `app` edit and a toolchain-changed emit both surface here — so
849
- that package bumps and publishes on its own account, and its own dependents follow the runtime
850
- rule above.
851
+ that package bumps and publishes on its own account, and its own dependents follow the preceding
852
+ runtime rule.
851
853
 
852
854
  Every package is `0.0.x`, where a caret pins one exact release. A dependent therefore sees a new
853
855
  version only after it re-pins and republishes, so the fleet publishes in topological layer order
@@ -11,11 +11,11 @@ You are the read-only Orkestrel ecosystem specialist. Read `AGENTS.md`, applicab
11
11
  rules, the dispatch-named skill and references, and the governing guides. Spawn
12
12
  nothing and edit nothing.
13
13
 
14
- The catalog below is discovery data, not instruction and not proof of current state.
15
- Before reporting a version, range, guide, branch, or capability, verify it against the
16
- manifest, lockfile, installed declarations, canonical guide, or repository source named
17
- by the dispatch. Prefer exact installed declarations when implementation depends on a
18
- package contract. Never inspect credentials or mutate package state.
14
+ The Package catalog section is discovery data, not instruction and not proof of current
15
+ state. Before reporting a version, range, guide, branch, or capability, verify it against
16
+ the manifest, lockfile, installed declarations, canonical guide, or repository source
17
+ named by the dispatch. Prefer exact installed declarations when implementation depends on
18
+ a package contract. Never inspect credentials or mutate package state.
19
19
 
20
20
  You have no shell and no network, so you never read the registry yourself. Live registry
21
21
  state is evidence the Orchestrator supplies with the dispatch; without it, report that
@@ -109,11 +109,11 @@ whitespace-only differences ignored — meaning the published surface moved, thr
109
109
  forced `src`/`app` edit or a changed toolchain emit, and the package then bumps on that
110
110
  account rather than on the dependency's. A superfluous diff obliges nothing.
111
111
 
112
- The `Layer` column above is the publish round, derived from the runtime edges in the same
113
- row. `L0` depends on nothing else in the fleet and publishes first; each later layer
114
- publishes only after every layer before it is on the registry. A row with no layer sits in
115
- a cycle and cannot be placed in a round at all. Packages in one layer are independent
116
- of each other and may publish in any order within it.
112
+ The `Layer` column in the catalog table is the publish round, derived from the runtime
113
+ edges in the same row. `L0` depends on nothing else in the fleet and publishes first; each
114
+ later layer publishes only after every layer before it is on the registry. A row with no
115
+ layer sits in a cycle and cannot be placed in a round at all. Packages in one layer are
116
+ independent of each other and may publish in any order within it.
117
117
 
118
118
  Report a disagreeing pin as a defect, never as drift to tidy later. When packages in
119
119
  one install graph pin different versions of a dependency, npm installs both copies, and the
@@ -36,7 +36,7 @@ paths:
36
36
  | Relations | `*/relations.ts` |
37
37
  | Error classes/guards | `*/errors.ts` |
38
38
  | Public exports | `*/index.ts` |
39
- | Implementations | `*/[domain]/[Entity].ts`, one class per file |
39
+ | Implementations | `*/[domain]/[Entity].ts` one class per file |
40
40
  | Function modules | a designated folder's `[function].ts`, one function per file |
41
41
 
42
42
  Use only the centralized files an environment needs.
@@ -49,7 +49,7 @@ Use only the centralized files an environment needs.
49
49
  - Extract local declarations by kind. “Only used here” and “not exported” are not exemptions.
50
50
  - Every declaration in a centralized file is exported. Fold away a trivial single-use declaration or export/test it; never leave it hidden.
51
51
  - The only permitted non-exported module-scope declarations are in a runtime entrypoint that must be self-contained and cannot import siblings, such as raw source loaded in a worker. Explain that necessity in a comment.
52
- - A runtime entry—`src/bin/main.ts`, `app/browser/main.ts`, `app/server/main.ts`—is a fixed name, not a centralized kind file. Both the data rule and the function rule reach it, so it declares no module-scope constant and no module-scope function: it imports what it needs and runs. The self-contained exception above covers only an entrypoint that cannot import siblings.
52
+ - A runtime entry—`src/bin/main.ts`, `app/browser/main.ts`, `app/server/main.ts`—is a fixed name, not a centralized kind file. Both the data rule and the function rule reach it, so it declares no module-scope constant and no module-scope function: it imports what it needs and runs. The preceding self-contained exception covers only an entrypoint that cannot import siblings.
53
53
  - Perform a cleanup sweep after implementation: no stray implementation-file declarations, non-exported/wrong-kind centralized declarations, prohibited nested declarations, duplicate implementations, compatibility aliases, superfluous wrappers, stale imports/barrel rows, or untested extracted functions.
54
54
 
55
55
  ## Kind purity
@@ -108,7 +108,7 @@ kind. It reads declaration syntax and file name, never meaning.
108
108
  `oxlint-disable` directive.
109
109
  - It does not prove a collection is frozen. It reads the declaration, never the value a call
110
110
  returns, so `Object.freeze([…])` and any other call initializer are one syntax to it. The freeze
111
- obligation in the kind-purity rules above binds regardless; only the bare literal is mechanical.
111
+ obligation in the earlier kind-purity rules binds regardless; only the bare literal is mechanical.
112
112
  - It does not tell one function kind from another. Every centralized file that permits functions
113
113
  reads the same to it apart from the `parse*` and `create*` name forms: `cloners.ts`, `combinators.ts`,
114
114
  `compilers.ts`, `errors.ts`, `factories.ts`, `handlers.ts`, `helpers.ts`, `inferers.ts`,
@@ -118,12 +118,12 @@ kind. It reads declaration syntax and file name, never meaning.
118
118
  - It reports no `data` violation in `helpers.ts`. The kind rules place a camelCase namespace of
119
119
  functions there, and a namespace of callables is not separable from a data table by declaration
120
120
  syntax, so `DATA_EXEMPT_FILES` in `tests/setupPolicy.ts` excludes the file. Ordinary module data
121
- there — `export const RETRIES = 3` — is unreported; the constants rule above binds regardless.
121
+ there — `export const RETRIES = 3` — is unreported; the earlier constants rule binds regardless.
122
122
  - It inspects no ambient declaration file: `.d.ts`, `.d.mts`, and `.d.cts` are all outside its
123
123
  reach. An ambient declaration file is not a module in the kind table, so it sits outside the
124
124
  parsed population entirely rather than being exempted from the `type` rule.
125
125
  - It does not inspect class-expression members. A function assigned inside a class-expression
126
- method is unreported; the functions rule above still binds, and cleanup and review enforce it.
126
+ method is unreported; the earlier functions rule still binds, and cleanup and review enforce it.
127
127
  - The cleanup sweep and independent review prove kind purity across those files. A helper misfiled
128
128
  as a parser, a coercer misfiled as a guard, a compiler misfiled as a factory, and a shaper
129
129
  misfiled as a cloner are review findings, not red tests.
@@ -279,7 +279,7 @@ export * from './greeters/Greeter.js'
279
279
  - Centralize any pattern repeated twice.
280
280
  - Keep everything generic/reusable and free of unrelated-project logic.
281
281
  - Do not expand the capability set without concrete need. Once that capability exists intentionally,
282
- its reusable top-level exports follow the barrel rule above without a second consumer gate.
282
+ its reusable top-level exports follow the earlier barrel rule without a second consumer gate.
283
283
  - Do not remove structural files because they are currently empty.
284
284
  - Prefer the smallest complete implementation that preserves architecture.
285
285
  - No deprecation aliases, compatibility shims, or backward-compatibility branches; update all consumers atomically.
@@ -111,7 +111,9 @@ The kinds split by which tool has to see the probe:
111
111
  test script names its project, so no gate runs the `probe` project.
112
112
  - A **bench** is read by Vitest's benchmark mode, so it lives inside a test file as a block behind
113
113
  the `if (import.meta.env.MODE === 'benchmark')` guard. Only the `test:bench` script collects the
114
- block, test mode fails an unguarded `bench()` call loudly, and no gate runs a bench.
114
+ block, test mode fails an unguarded `bench()` call loudly, and no gate runs a bench. Call `bench`
115
+ directly inside the guard. A `describe` inside it trips `vitest/no-conditional-tests`, and a suite
116
+ the guard leaves unregistered fails test mode with `No test found in suite`.
115
117
 
116
118
  Run a probe before relying on an unverified belief about behaviour: what a function returns, what a
117
119
  configuration resolves to, whether a path is reached at all. Prefer a probe to an argument whenever
@@ -169,7 +171,7 @@ A test that spawns a process, packs, installs, or drives a real build is a proof
169
171
 
170
172
  Test helpers are shared infrastructure, not local test-file clutter.
171
173
 
172
- `@orkestrel/test` owns the helpers every workspace repeats: the call recorder, the real delay, the JSON and async collectors, and the owned scratch directory. Import them from `@orkestrel/test`, and its Node-only helpers from `@orkestrel/test/server`. Write a helper of your own only where the package exports none for the job. The shapes below are the contract a workspace codes against, not source to copy.
174
+ `@orkestrel/test` owns the helpers every workspace repeats: the call recorder, the real delay, the JSON and async collectors, and the owned scratch directory. Import them from `@orkestrel/test`, and its Node-only helpers from `@orkestrel/test/server`. Write a helper of your own only where the package exports none for the job. The following shapes are the contract a workspace codes against, not source to copy.
173
175
 
174
176
  - For the vendored test set (`tests/setupPolicy.ts`, `tests/policy.test.ts`, and
175
177
  `tests/config.test.ts`), keep shared helpers within that set instead of importing them from
@@ -166,7 +166,7 @@ ignored by git; and `.claude/rules/tests.md` governs what may live there.
166
166
  drives.
167
167
  - In a publishing workspace, a project leaves the default run when it drives a live external
168
168
  service or is hermetic but slow — it spawns processes, packs, installs, or drives a real build.
169
- - Give every isolated project its own script, and place that script by the paragraph above.
169
+ - Give every isolated project its own script, and place that script by the preceding paragraph.
170
170
 
171
171
  Setup assets:
172
172
 
@@ -14,7 +14,7 @@ developer_instructions = """
14
14
  Read AGENTS.md, .agents/orchestration.md, every applicable .claude/rules/*.md file, the
15
15
  dispatch-named .agents/skills workflow and its required references, and the governing
16
16
  guide or spec before acting. AGENTS.md controls code substance; .agents/orchestration.md
17
- controls agent operation; this layer adds only the Codex specifics below and cannot
17
+ controls agent operation; this layer adds only the following Codex specifics and cannot
18
18
  weaken either.
19
19
 
20
20
  Sol orchestrates in this harness. Act as the Orchestrator defined in
@@ -12,8 +12,8 @@ does not work. Scaffold makes the shared set data — a vendored data root shipp
12
12
  — and gives it verbs: create a workspace from it, report how a workspace differs from it, and
13
13
  write the difference back.
14
14
 
15
- Every code fence below is illustrative. [`tests/guides.test.ts`](../tests/guides.test.ts) keeps the
16
- command reference aligned with the executable and transcribes the pure blueprint-default,
15
+ Every following code fence is illustrative. [`tests/guides.test.ts`](../tests/guides.test.ts)
16
+ keeps the command reference aligned with the executable and transcribes the pure blueprint-default,
17
17
  compile-refusal, and error-narrowing fences. A trailing comment in another fence is this guide's
18
18
  claim rather than a measured answer; the driven examples are the ones the shipped declarations
19
19
  print. Limits states what that leaves unproven and what covers it instead.
@@ -391,8 +391,8 @@ Exported from `@orkestrel/scaffold/server`, and reachable from
391
391
 
392
392
  `Compiler` implements `CompilerInterface`, `Materializer` implements `MaterializerInterface`, and
393
393
  `Upstream` implements `UpstreamInterface`. Each class exposes exactly its interface's members and
394
- nothing more, so the interface tables below describe the classes too. `WriteTransaction` publishes
395
- no interface and is documented directly.
394
+ nothing more, so the following interface tables describe the classes too. `WriteTransaction`
395
+ publishes no interface and is documented directly.
396
396
 
397
397
  #### `CompilerInterface`
398
398
 
@@ -566,9 +566,9 @@ carries the fact and the vendor list keeps its own separate job.
566
566
  The root Vite configuration defines and registers the fixed `guides` project only when the derived
567
567
  blueprint carries `guides`. Reading verbs set that fact only when `tests/guides.test.ts` is a
568
568
  physical file with that exact path case. A directory or a case-folded spelling does not select it. A
569
- fresh workspace therefore carries no guides project or script. A developer who adds the proof must
570
- also add the exact `test:guides` script line that the plan reports; the manifest remains
571
- birth-owned.
569
+ fresh workspace therefore carries no guides project or script. When a developer adds the proof,
570
+ `audit` reports the exact `test:guides` script line until `repair` or `overwrite` appends it through
571
+ the writable script region. The rest of the manifest remains birth-owned.
572
572
 
573
573
  The plan-reading verbs compare the Vitest project set named by the target manifest with the
574
574
  project set the planned root configuration registers. Every planned proof project must also be
@@ -585,31 +585,36 @@ static Vitest fact to infer.
585
585
  `audit` still completes the comparison and reports one non-blocking `projects` question when its
586
586
  selection includes `configs`. A scoped audit that excludes `configs` omits that question. For a
587
587
  literal absent project, its advisory tells the developer to register the project or remove the
588
- script. For a planned project absent from the gate chains, the advisory checks the direct
589
- `test:<project>` script. When the script is absent, the advisory gives the exact line to add to
590
- `package.json`. When the script is declared but ungated, the advisory names the script and the gate
591
- chain that must invoke it, without repeating a script line. When `configs` is selected, `repair` and
592
- `overwrite` refuse either mismatch before writing. Their refusal names the `configs` group, the
593
- manifest and planned `vite.config.ts` conflict, and the option to exclude `configs` from
594
- `--groups`. A selection that excludes `configs` proceeds. An advisory alone does not make an aligned
595
- target drift.
596
-
597
- Scaffold writes one part of the manifest rather than advising on it: the script region a publishing
598
- workspace needs for the generated distribution proof. `repair` and `overwrite` write
599
- `test:distribution` and `prepublishOnly` there. A declared value is overwritten only when it is
600
- already the value being written, or, for `prepublishOnly`, the one predecessor the region accepts —
601
- the same gate chain without the release row. The overwrite happens in place, so every byte outside
602
- the replaced ranges survives, and a description, a keyword, a key order, and a script the workspace
603
- added are untouched. A script the manifest does not declare is appended after the last declared one,
604
- copying that section's indentation. `catalog` writes no script region; it names the ranges alone.
605
-
606
- A value matching neither is a chain the workspace author wrote. The region is then refused whole,
607
- without a byte moving, and the range region is still written. The `projects` advisory reads the
608
- manifest as a write would leave it, so a region scaffold writes for itself raises no question, while
609
- a refused region leaves the target's own text standing and the advisory reports whichever half is
610
- outstanding: the exact `test:distribution` line to paste, or the gate chain that must invoke a
611
- script the manifest already declares. The refusal covers the region rather than one script, so a
612
- manifest never holds one written value beside one refused one.
588
+ script. For a planned project absent from the gate chains, the advisory reads the manifest after a
589
+ writable script projection. The `scripts` question owns an absent direct `test:<project>` line. The
590
+ `projects` question names the direct script and the gate chain that must invoke it when the projected
591
+ region still leaves the project ungated. When `configs` is selected, `repair` and `overwrite` refuse
592
+ an unregistered or ungated project before writing. Their refusal names the `configs` group, the
593
+ manifest and planned `vite.config.ts` conflict, and the option to exclude `configs` from `--groups`.
594
+ A selection that excludes `configs` proceeds. An advisory alone does not make an aligned target
595
+ drift.
596
+
597
+ Scaffold writes one part of the manifest rather than advising on it: the writable script region.
598
+ `repair` and `overwrite` write every direct `test:<project>` script the blueprint computes,
599
+ `test:probe`, and `test:bench`. A publishing workspace also receives `test:distribution`, `prepack`,
600
+ and `prepublishOnly`. The `test`, `check`, `build`, `dev`, `serve`, `show`, `format`, `lint`, `clean`,
601
+ and `copy` gate chains stay maintainer-owned. A declared value is overwritten only when it is already
602
+ the value being written or is a recognized generated predecessor. The overwrite happens in place,
603
+ so every byte outside the replaced ranges survives. A target's descriptions, keywords, extra
604
+ scripts, and manifest key order survive byte-for-byte. A script the manifest does not declare is
605
+ appended after the last declared one, copying that section's indentation. `catalog` writes no script
606
+ region; it names the ranges alone.
607
+
608
+ A value matching neither is a script the workspace author wrote. The region writer retains that
609
+ value byte-for-byte and reports its name, declared value, and planned value. Each other planned
610
+ script is decided independently: an absent script appends, the planned value stands, and an accepted
611
+ predecessor upgrades in place. Extra scripts remain byte-identical. A planned key holding a
612
+ non-string value or a `scripts` field that is not an object still refuses the whole region with no
613
+ script byte moving. `audit` reports absent and differing scripts separately in its non-blocking
614
+ `scripts` question, and the terminal audit in `repair` and `overwrite` keeps every retained
615
+ difference visible. Other selected writes still proceed. The `projects` question reports only a
616
+ direct script the manifest declares but the maintainer-owned gate chain does not invoke after
617
+ projection.
613
618
 
614
619
  The same plan-reading verbs compare the tooling set the derived blueprint plans against
615
620
  `dependencies` and `devDependencies` together. A missing planned package produces one non-blocking
@@ -640,7 +645,8 @@ maintainer wrote rather than what scaffold seeded.
640
645
  That reading carries a release-skew limit. A seeded setup module is birth-owned, so `repair` reports
641
646
  it aligned and never rewrites it. A target keeps the seed of the release that materialized it. When
642
647
  a release moves a planned seed, scaffold raises the question on every target materialized before it,
643
- against a module scaffold wrote and no maintainer touched.
648
+ against a module scaffold wrote and no maintainer touched. `audit` compares each setup module only
649
+ with the seed the installed release plans, and it retains no earlier seed bytes.
644
650
  `tests/setupGlobal.ts` is the module that can meet it, because it is the one seeded with more than
645
651
  the empty string. A maintainer meeting that question closes it by writing the proof it asks for, or
646
652
  by taking the seed the installed release plans.
@@ -741,7 +747,7 @@ One published environment owns the package root directly. Several published envi
741
747
  never runs. The gate reports that as a non-blocking `src` question rather than refusing the compile,
742
748
  because the shape is chosen once and read afterwards: `new` refuses the advisory, while `audit` and
743
749
  `repair` need the plan to describe and restore a target that already has that shape. A library
744
- caller creating a workspace holds the same refusal, and the Compile section below states it.
750
+ caller creating a workspace holds the same refusal, and the Compile section states it.
745
751
 
746
752
  `bin`, `setup`, `guides`, `integration`, `conformance`, `service`, `vendors`, `global`, and
747
753
  `showcase` are structural facts. Each is set only when the workspace physically ships the directory
@@ -758,14 +764,12 @@ generated before that file existed and still registers no `integration` project,
758
764
  fails with `integration has no project factory or configuration` until a plan-writing verb
759
765
  regenerates it.
760
766
 
761
- `repair` alone does not close that, and refusing is correct rather than a gap. `package.json` is
762
- birth-owned, so the verb cannot add the project's script, and it will not register a project the
763
- manifest reaches from no gate. It exits 1 naming the target and writes nothing.
767
+ `repair` closes the direct-script half through the writable manifest region. It still refuses to
768
+ register a project that the maintainer-owned gate chains do not reach.
764
769
 
765
- Adding a structural proof is therefore these steps, in order: write the file; declare its
766
- `test:<project>` script and invoke that script from a gate chain; then run `repair`, which
767
- regenerates the root configuration and registers the project. `audit` reports whichever piece is
768
- still outstanding at each step.
770
+ When you add a structural proof, write the file and invoke its planned `test:<project>` script from
771
+ a gate chain. Then run `repair`; it appends the direct script, regenerates the root configuration,
772
+ and registers the project. `audit` reports whichever piece is still outstanding at each step.
769
773
 
770
774
  `distribution` is not a field at all. A published `src` environment is its whole condition, read
771
775
  from the `src` axis the blueprint already carries. The proof packs and installs the published
@@ -1184,9 +1188,9 @@ A workspace's file set is a function of its axes plus its structural facts. Noth
1184
1188
  except the manifest.
1185
1189
 
1186
1190
  - One computed artifact: `package.json`, with the entry points, `exports` map, scripts, and
1187
- development dependencies its selection implies. A publishing manifest carries
1188
- `"prepack": "npm run build"` so a publish rebuilds `dist/` and cannot ship a stale artifact;
1189
- the hook is publish-time only, and every generated distribution proof passes
1191
+ development dependencies its selection implies. In publishing workspaces, the emitted `prepack`
1192
+ script runs `npm run build` so a publish rebuilds `dist/` and cannot ship a stale artifact; the
1193
+ hook is publish-time only, and every generated distribution proof passes
1190
1194
  `--ignore-scripts` to `npm pack` so a suite never re-runs the build it already gates.
1191
1195
  - One template artifact per configuration file the selection needs: the root `tsconfig.json` and
1192
1196
  `vite.config.ts`, plus a Vite config and a scoped TypeScript config per selected environment and
@@ -4,7 +4,7 @@
4
4
  "storage": "AGENTS.md",
5
5
  "destination": "AGENTS.md",
6
6
  "executable": false,
7
- "digest": "152c8e8fb880814be96fb0b0a63524e98bf72125af97edddb72c140d2e3cc0ea"
7
+ "digest": "691f9fb52acd475a08109b93cdbed18628af36e9363f4803d84176a8117cf53c"
8
8
  },
9
9
  {
10
10
  "storage": "CLAUDE.md",
@@ -22,7 +22,7 @@
22
22
  "storage": "agents/orchestration.md",
23
23
  "destination": ".agents/orchestration.md",
24
24
  "executable": false,
25
- "digest": "c8e9931f5cf30d0a5a94ea2a3008904b5833cd534036680ea0fd9618c64d6f54"
25
+ "digest": "63bacfdcd9b9918fb8302b723a0049bd3bbf74e939a8c1afbe50d0083c1742db"
26
26
  },
27
27
  {
28
28
  "storage": "agents/skills/enterprise-bootstrap/SKILL.md",
@@ -268,7 +268,7 @@
268
268
  "storage": "claude/agents/orkestrel.md",
269
269
  "destination": ".claude/agents/orkestrel.md",
270
270
  "executable": false,
271
- "digest": "dc6f528f27980569650b5d8fb3ec21e7adfd50eb815f1c0a4f48bf001b816707"
271
+ "digest": "8e228a484383a04bf12fa510b3619eb7f38a512ec081ee0870ea7534b314945d"
272
272
  },
273
273
  {
274
274
  "storage": "claude/agents/planner.md",
@@ -316,7 +316,7 @@
316
316
  "storage": "claude/rules/architecture.md",
317
317
  "destination": ".claude/rules/architecture.md",
318
318
  "executable": false,
319
- "digest": "32bf977e77fb42cbf16bbcb92d9005d4879b194f5957ad6173c355054bfa0a88"
319
+ "digest": "94f70c20f927129cf9cb3965b3180a14e941e4f6eddf6512c4adc014e0bcbdf2"
320
320
  },
321
321
  {
322
322
  "storage": "claude/rules/browser.md",
@@ -358,7 +358,7 @@
358
358
  "storage": "claude/rules/tests.md",
359
359
  "destination": ".claude/rules/tests.md",
360
360
  "executable": false,
361
- "digest": "6830aafb605cb7123c682b09a0b7cb8fbbb8d62437ac136c9c91d88b7c79149c"
361
+ "digest": "2f8658425bc48b8751a5526300827c90f22226d4605514ec866891b5be4fabfb"
362
362
  },
363
363
  {
364
364
  "storage": "claude/rules/typescript.md",
@@ -370,7 +370,7 @@
370
370
  "storage": "claude/rules/workspace.md",
371
371
  "destination": ".claude/rules/workspace.md",
372
372
  "executable": false,
373
- "digest": "eac73efda50bcf415968538caa83c326ee5e30076d7dadefe07512510e86df22"
373
+ "digest": "8120bac99d00b4cb0613b0dc6cb4048074c86fdb6f6583be07514229d90bbd07"
374
374
  },
375
375
  {
376
376
  "storage": "claude/rules/writing.md",
@@ -520,7 +520,7 @@
520
520
  "storage": "codex/config.toml",
521
521
  "destination": ".codex/config.toml",
522
522
  "executable": false,
523
- "digest": "5aa2e5903a9501fb53a6fa9b58d4e1d18b44e950f88fd928edbc3f5b5aadfc62"
523
+ "digest": "e67d555ed4381b098b203e14484718e64e5262715d87b3f71857511cff929560"
524
524
  },
525
525
  {
526
526
  "storage": "configs/helpers.ts",
@@ -604,7 +604,7 @@
604
604
  "storage": "guides/scaffold.md",
605
605
  "destination": "guides/scaffold.md",
606
606
  "executable": false,
607
- "digest": "b3dec011e6ed02398d162ed2f5c83b1ffbdc834f4d2507f4a216bbe01cf0630f"
607
+ "digest": "1aace5325608c6880d73a2bae7c02936c26445ef7aff550c43a70333c7be85f6"
608
608
  },
609
609
  {
610
610
  "storage": "scripts/codex.sh",
@@ -634,7 +634,7 @@
634
634
  "storage": "tests/config.test.ts",
635
635
  "destination": "tests/config.test.ts",
636
636
  "executable": false,
637
- "digest": "2627f98c2caf3c6a75f4c4659df41191054154ed36d8429c7c6b5c53284a91bf"
637
+ "digest": "f8ddc2ab6cb54f2b1f51ad9212d7f09881271d265628299e5ce8f536cfec76a7"
638
638
  },
639
639
  {
640
640
  "storage": "tests/policy.test.ts",
@@ -688,5 +688,5 @@
688
688
  ".codex/agents",
689
689
  ".cursor/rules"
690
690
  ],
691
- "digest": "d9f1ee5c68e28ef445b74e697d0ce13e7a3dd201911962b77a685b1d29c88c80"
691
+ "digest": "1472dda0358190ef9900fcdf86fdfb0ed60053b6095ba7b9f1b0efe490cc85ce"
692
692
  }
@@ -318,6 +318,43 @@ describe('root configuration', () => {
318
318
  expect(callable).not.toContain(inline)
319
319
  })
320
320
 
321
+ it('keeps Vitest invocation fields out of project configurations', () => {
322
+ const projects = configuration.test?.projects
323
+ if (!Array.isArray(projects)) throw new Error('The root configuration carries no projects')
324
+ const factories = projects.filter((row) => typeof row === 'function')
325
+ if (factories.length === 0)
326
+ throw new Error('The root configuration registers no project factory')
327
+ const sentinel = {
328
+ command: 'sentinel-command',
329
+ isPreview: true,
330
+ isSsrBuild: true,
331
+ mode: 'sentinel-mode',
332
+ sentinel: true,
333
+ }
334
+ for (const factory of factories) {
335
+ const project: unknown = Reflect.apply(factory, undefined, [sentinel])
336
+ if (typeof project !== 'object' || project === null) {
337
+ throw new Error('A project factory returned no configuration')
338
+ }
339
+ for (const field of Object.keys(sentinel)) {
340
+ expect(Object.getOwnPropertyDescriptor(project, field)?.value).toBeUndefined()
341
+ }
342
+ }
343
+
344
+ const control = Object.defineProperty(() => ({ ...sentinel }), 'name', { value: 'control' })
345
+ expect(() => {
346
+ for (const factory of factories.concat(control)) {
347
+ const project: unknown = Reflect.apply(factory, undefined, [sentinel])
348
+ if (typeof project !== 'object' || project === null) {
349
+ throw new Error('A project factory returned no configuration')
350
+ }
351
+ for (const field of Object.keys(sentinel)) {
352
+ expect(Object.getOwnPropertyDescriptor(project, field)?.value).toBeUndefined()
353
+ }
354
+ }
355
+ }).toThrow(/expected/u)
356
+ })
357
+
321
358
  it('requires and validates every selected target wrapper', async () => {
322
359
  const required: string[] = []
323
360
  for (const axis of ['src', 'app']) {
@@ -534,6 +571,26 @@ describe('root configuration', () => {
534
571
  )
535
572
  })
536
573
 
574
+ it('rebuilds publishing workspaces before packing', () => {
575
+ const manifest: unknown = JSON.parse(readFileSync(resolve(root, 'package.json'), 'utf8'))
576
+ if (typeof manifest !== 'object' || manifest === null) {
577
+ throw new Error('The package manifest is not a record')
578
+ }
579
+ const scripts: unknown = Object.getOwnPropertyDescriptor(manifest, 'scripts')?.value
580
+ if (typeof scripts !== 'object' || scripts === null) {
581
+ throw new Error('The package manifest carries no scripts')
582
+ }
583
+ const publishes = Object.getOwnPropertyDescriptor(manifest, 'private')?.value !== true
584
+ const prepack = Object.getOwnPropertyDescriptor(scripts, 'prepack')?.value
585
+ expect(prepack).toBe(publishes ? 'npm run build' : undefined)
586
+
587
+ const controlled = { ...scripts, prepack: 'npm run control' }
588
+ expect(() => {
589
+ const control = Object.getOwnPropertyDescriptor(controlled, 'prepack')?.value
590
+ expect(control).toBe(publishes ? 'npm run build' : undefined)
591
+ }).toThrow(/expected/u)
592
+ })
593
+
537
594
  it('keeps the committed host inventory aligned with the vendored checkout bytes', async () => {
538
595
  // Run this gate against a quiescent checkout. It compares two reads and cannot
539
596
  // distinguish stale committed data from a source edit made while it runs.