@orkestrel/scaffold 0.0.31 → 0.0.32

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.
@@ -10,8 +10,15 @@ paths:
10
10
 
11
11
  ## Test contract
12
12
 
13
- - Mirror source/application structure:
14
- `tests/{src,app}/[environment]/[domain]/[source].test.ts`.
13
+ - Mirror module/application structure:
14
+ `tests/{src,app}/[environment]/[domain]/[module].test.ts`.
15
+ - The mirrored population is `src` and `app` alone. `configs/` is a source directory and is
16
+ deliberately not a mirrored root: its leaves produce the workspace's configuration rather than ship
17
+ in it, and they are proved from `tests/config.test.ts` beside the configuration they produce. Do
18
+ not add `tests/configs/`.
19
+ - Resolve a mirrored module through `.ts`, `.tsx`, `.mts`, `.cts`, `.vue`, `.scss`, or `.css`.
20
+ - Resolve a Sass or CSS partial through the module's leading underscore.
21
+ - Resolve a `setup*` module test against its sibling `setup*.ts` module inside `tests/`.
15
22
  - Prefer test filenames matching entrypoints: `index.test.ts` for `index.ts`, `main.test.ts` for `main.ts`.
16
23
  - Tests are deterministic: identical inputs produce identical results.
17
24
  - Keep default suites fast: timers normally use 10–50 ms and tests make no network calls.
@@ -37,16 +44,20 @@ paths:
37
44
  A proof that covers the workspace instead of one module has a fixed location, so no package invents
38
45
  its own:
39
46
 
40
- | Path | Proves |
41
- | ---------------------------- | ------------------------------------------------------------------- |
42
- | `tests/policy.test.ts` | Every source file obeys the syntactic coding and placement law |
43
- | `tests/config.test.ts` | Root configuration resolves its aliases, projects, and outputs |
44
- | `tests/guides.test.ts` | Every documented API exists and every public API is documented |
45
- | `tests/conformance.test.ts` | Where this package drifts from the official tooling it tracks |
46
- | `tests/integration.test.ts` | The package's features work together end to end across environments |
47
- | `tests/service/**/*.test.ts` | The live external services this package drives, driven for real |
47
+ | Path | Proves |
48
+ | ---------------------------- | ----------------------------------------------------------------------------------------------------- |
49
+ | `tests/policy.test.ts` | Every source file obeys the syntactic coding and placement law |
50
+ | `tests/config.test.ts` | Root configuration resolves its aliases, projects, and outputs, and the `configs/` leaves behind them |
51
+ | `tests/guides.test.ts` | Every documented API exists and every public API is documented |
52
+ | `tests/conformance.test.ts` | Where this package drifts from the official tooling it tracks |
53
+ | `tests/distribution.test.ts` | The packed package installs and resolves through its public exports |
54
+ | `tests/integration.test.ts` | The package's features work together end to end across environments |
55
+ | `tests/service/**/*.test.ts` | The live external services this package drives, driven for real |
48
56
 
49
57
  - `.claude/rules/workspace.md` names the Vitest project each location belongs to.
58
+ - The `guides` project runs in Node with the browser disabled. Its subject is
59
+ documented-name-to-real-export. A proof that renders a component and compares it against a
60
+ definition is a composition and belongs in an `integration.test.ts` scoped to its directory.
50
61
  - `integration.test.ts` is a reserved filename at any level. It names a scope rather than a module,
51
62
  so the mirror rule does not reach it; its scope is the directory it sits in.
52
63
  - An integration test is an end-to-end test: it composes the package's own features and drives them
@@ -58,9 +69,13 @@ its own:
58
69
  - Do not put a packaging, install, or distribution check in an integration test. What the tarball
59
70
  contains is a different question from whether the features compose.
60
71
  - A test the mirror rule flags is a misplaced test until its placement is checked. Move it to the
61
- location its scope names. Never widen the rule to accept it.
62
- - Give every nested `integration.test.ts` its own exact-path project entry. A glob such as
63
- `tests/src/**/integration.test.ts` double-claims a file another project already owns.
72
+ location its scope names. Widen the rule only when that check shows the test already sits at the
73
+ location its scope names and the rule's population omits a module this ruleset mandates, and then
74
+ widen the population rather than admitting the individual case.
75
+ - A nested `tests/{src,app}/<environment>/**/integration.test.ts` runs in that environment's project,
76
+ whose existing glob collects it exactly once. Give it a separate exact-path project entry only when
77
+ the proof needs different setup or a different runtime, and exclude that exact path from the
78
+ environment project when you do.
64
79
 
65
80
  ## Probes
66
81
 
@@ -96,7 +111,8 @@ Live external services/models are the deliberate exception to fast hermetic defa
96
111
  - Put them in the `service` project, under `tests/service/`, with `tests/setupService.ts` for setup
97
112
  and a longer timeout. That module's presence is what registers the project, so a live proof with
98
113
  no readiness setup is a project nothing configures.
99
- - Keep them out of the default run.
114
+ - `.claude/rules/workspace.md` fixes which gate runs the `service` project, and that gate is not the
115
+ same one in a publishing and a `private: true` workspace.
100
116
  - Warm and verify service readiness in `tests/setupService.ts`.
101
117
  - Hard-require readiness: throw loudly; never silently skip.
102
118
  - Verify service-dependent logic through that service's project, not unrelated module tests or scattered conditional skips.
@@ -108,7 +124,13 @@ Live external services/models are the deliberate exception to fast hermetic defa
108
124
  A test that spawns a process, packs, installs, or drives a real build is a proof, not a unit test.
109
125
 
110
126
  - Give it its own Vitest project with its own setup and timeout.
111
- - Keep it out of the default run and require it in `prepublishOnly`.
127
+ - Keep it out of the default run where the workspace has a gate that can hold it.
128
+ `.claude/rules/workspace.md` fixes which gate each isolated project runs from, and that placement
129
+ differs between a publishing and a `private: true` workspace.
130
+ - The fixed expensive-proof projects are `distribution` and `service`.
131
+ - A `distribution` proof reads `import.meta.env.MODE` and fails, rather than skips, on an unreachable
132
+ registry under `--mode release`. The publish gate invokes it that way, so a proof that skips there
133
+ passes the gate without ever proving the artifact installs. An ordinary local run may still skip.
112
134
  - Slow and hermetic is reason enough to isolate a proof; it need not touch an external service.
113
135
  - Where such a proof stays in a shared project, size its budget from a full contended run rather than from an isolated one. A budget that clears the isolated cost by a thin margin turns contention into a red gate reporting a timeout, which carries no diagnostic about the code and costs a full investigation to dismiss.
114
136
 
@@ -116,6 +138,8 @@ A test that spawns a process, packs, installs, or drives a real build is a proof
116
138
 
117
139
  Test helpers are shared infrastructure, not local test-file clutter.
118
140
 
141
+ `@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.
142
+
119
143
  - Extract a fixture, recorder, event factory, async wait, renderer, scenario/data builder, protocol fixture, or DOM builder as soon as it could serve another test.
120
144
  - Any duplicate or near-duplicate helper is a defect; consolidate it into one general form.
121
145
  - Export every reusable helper, fixture type, factory, constant, and guard from setup files.
@@ -124,7 +148,7 @@ Test helpers are shared infrastructure, not local test-file clutter.
124
148
  - Test files import shared infrastructure rather than declaring local fixture factories.
125
149
  - Never reimplement a framework helper in tests or fixtures; import the real parser, signer, flattener, or other helper.
126
150
  - Prefer small customizable factories/stubs that seed inert data for a real scenario over repeated inline setup.
127
- - Helper names follow module-helper naming: `createRecorder`, `buildElement`, `appendItems`, `renderRows`, `waitForDelay`, `extractDetail`.
151
+ - Helper names follow module-helper naming: `createFixtureServer`, `buildElement`, `appendItems`, `renderRows`, `waitForReady`, `extractDetail`.
128
152
 
129
153
  Place helpers by environment:
130
154
 
@@ -135,10 +159,10 @@ Place helpers by environment:
135
159
 
136
160
  ### Recorder
137
161
 
138
- Use a real recorder callback instead of a framework spy when only calls/arguments matter:
162
+ Import `createRecorder` from `@orkestrel/test` instead of a framework spy when only calls and arguments matter. It returns:
139
163
 
140
164
  ```ts
141
- interface TestRecorderInterface<TArgs extends readonly unknown[]> {
165
+ interface RecorderInterface<TArgs extends readonly unknown[]> {
142
166
  readonly calls: readonly TArgs[]
143
167
  readonly count: number
144
168
  readonly handler: (...args: TArgs) => void
@@ -148,11 +172,23 @@ interface TestRecorderInterface<TArgs extends readonly unknown[]> {
148
172
 
149
173
  ### Delay
150
174
 
151
- Use the shared delay helper; never repeat inline timeout promises:
175
+ Import `waitForDelay` from `@orkestrel/test`; never repeat an inline timeout promise. It waits for one host timer and defaults to `0`:
176
+
177
+ ```ts
178
+ function waitForDelay(ms?: number): Promise<void>
179
+ ```
180
+
181
+ ### Scratch
182
+
183
+ Import `createScratch` from `@orkestrel/test/server` when a proof needs real files. It allocates a temporary directory it owns, contains every path against escape, and removes the directory on `destroy`:
152
184
 
153
185
  ```ts
154
- export function waitForDelay(ms = 0): Promise<void> {
155
- return new Promise((resolve) => setTimeout(resolve, ms))
186
+ interface ScratchInterface {
187
+ readonly path: string
188
+ write(target: string, text: string): void
189
+ read(target: string): string | undefined
190
+ exists(target: string): boolean
191
+ destroy(): void
156
192
  }
157
193
  ```
158
194
 
@@ -114,21 +114,26 @@ environment:
114
114
  The second axis is cross-cutting workspace proofs. Each one covers the whole workspace rather than
115
115
  one environment, so each is its own project:
116
116
 
117
- | Project | Files | Proves | In `test` |
118
- | ------------- | ---------------------------- | ------------------------------------------------------------------- | --------- |
119
- | `policy` | `tests/policy.test.ts` | Every source file obeys the syntactic coding and placement law | Yes |
120
- | `config` | `tests/config.test.ts` | Root configuration resolves its aliases, projects, and outputs | Yes |
121
- | `guides` | `tests/guides.test.ts` | Every documented API exists and every public API is documented | Yes |
122
- | `conformance` | `tests/conformance.test.ts` | Where this package drifts from the official tooling it tracks | Yes |
123
- | `integration` | `tests/integration.test.ts` | The package's features work together end to end across environments | No |
124
- | `service` | `tests/service/**/*.test.ts` | The live external services this package drives, driven for real | No |
125
-
126
- `conformance` and `service` are two subjects, not two names for one. `conformance` measures this
127
- package against an official artifact it stays compatible with and drives nothing external: the
128
- tooling it measures against is installed, and any server it drives is one the proof starts itself.
129
- That is what makes it hermetic and keeps it in `test`. `service` drives the real thing, so it takes
130
- `tests/setupService.ts` for readiness, longer timeouts, and no file parallelism, and it leaves
131
- `test` for `prepublishOnly`.
117
+ | Project | Files | Proves | Gate |
118
+ | -------------- | ---------------------------- | ------------------------------------------------------------------- | ------------------------------------- |
119
+ | `policy` | `tests/policy.test.ts` | Every source file obeys the syntactic coding and placement law | `test` |
120
+ | `config` | `tests/config.test.ts` | Root configuration resolves its aliases, projects, and outputs | `test` |
121
+ | `guides` | `tests/guides.test.ts` | Every documented API exists and every public API is documented | `test` |
122
+ | `conformance` | `tests/conformance.test.ts` | Where this package drifts from the official tooling it tracks | `test` |
123
+ | `distribution` | `tests/distribution.test.ts` | The packed package installs and resolves through its public exports | `prepublishOnly`; absent when private |
124
+ | `integration` | `tests/integration.test.ts` | The package's features work together end to end across environments | `test` |
125
+ | `service` | `tests/service/**/*.test.ts` | The live external services this package drives, driven for real | `prepublishOnly`; `test` when private |
126
+
127
+ `conformance`, `integration`, `distribution`, and `service` are four subjects, not four names for
128
+ one.
129
+ Keep `conformance` in `test`: measure this package against an installed official artifact, and start
130
+ any server the proof drives itself. Keep `integration` in `test`: compose the workspace's public
131
+ surfaces without packing, installing, or driving an external service. In a publishing workspace,
132
+ run `distribution` and `service` from `prepublishOnly`: pack and install the package in
133
+ `distribution`, and drive the real service with `tests/setupService.ts`, longer timeouts, and no file
134
+ parallelism in `service`. In a `private: true` workspace, never declare `prepublishOnly`; omit
135
+ `distribution`, reach `service` from `test`, and retain the service project's isolated
136
+ configuration.
132
137
 
133
138
  One project sits on neither axis. `probe` includes `tmp/probe/**/*.test.ts` so an agent can run a
134
139
  throwaway instrument against real sources, aliases and setup. Declare no proof there. Every test
@@ -139,9 +144,9 @@ script names its project, so no gate runs it; its directory is ignored by git; a
139
144
  - A live-service project is the fifth kind. It is the `service` project above, `scripts/service.sh`
140
145
  provisions what it drives, and `.claude/rules/tests.md` governs it. Name it `service` whatever it
141
146
  drives.
142
- - A project leaves the default run for one of two reasons: it drives a live external service, or it
143
- is hermetic but slow — it spawns processes, packs, installs, or drives a real build.
144
- - Every isolated project has its own script, is excluded from `test`, and runs in `prepublishOnly`.
147
+ - In a publishing workspace, a project leaves the default run when it drives a live external
148
+ service or is hermetic but slow — it spawns processes, packs, installs, or drives a real build.
149
+ - Give every isolated project its own script, and place that script by the paragraph above.
145
150
 
146
151
  Setup assets:
147
152
 
@@ -151,7 +156,7 @@ Setup assets:
151
156
 
152
157
  Scope with `test:src`, `test:src:core`, `test:app`, `test:app:server`, and equivalent scripts. Each
153
158
  cross-cutting project has its own script too: `test:policy`, `test:config`, `test:guides`,
154
- `test:conformance`, `test:integration`, `test:service`.
159
+ `test:conformance`, `test:distribution`, `test:integration`, `test:service`.
155
160
 
156
161
  ## Typechecking and environment isolation
157
162
 
@@ -206,7 +211,7 @@ Build/check config alignment:
206
211
  | `test` | Environment projects plus non-isolated cross-cutting proofs |
207
212
  | `clean` | Remove `dist/` |
208
213
  | `copy <from> <to>` | Copy while creating parent directories |
209
- | `prepublishOnly` | The gate chain in `AGENTS.md`, then every isolated project |
214
+ | `prepublishOnly` | Publishing workspaces only: the gate chain, then isolated proofs |
210
215
 
211
216
  Run `show` only **after** formatting. The committed `demo/showcase.html` is generated/minified; formatting after generation would expand its inlined bundle.
212
217
 
@@ -49,6 +49,7 @@
49
49
  "no-debugger": "error",
50
50
  "no-underscore-dangle": "warn",
51
51
 
52
+ "vitest/no-alias-methods": "error",
52
53
  "vitest/warn-todo": "off"
53
54
  },
54
55
  "categories": {
@@ -71,8 +71,8 @@ Exported from `@orkestrel/scaffold`, and reachable from
71
71
  | `HostArtifact` | interface | A file byte-copied from the vendored data root, planned before its bytes are read. |
72
72
  | `HydratedArtifact` | interface | A vendored file whose exact bytes have been read, so its content can be compared. |
73
73
  | `Override` | interface | One artifact override. |
74
- | `PlanSummary` | interface | The tally of one plan by artifact origin. |
75
74
  | `Plan` | interface | The compiled, ordered artifact list and the selection it covers. |
75
+ | `PlanSummary` | interface | The tally of one plan by artifact origin. |
76
76
  | `Question` | interface | One validation issue raised against a blueprint or a plan. |
77
77
  | `Scaffolding` | interface | The replayable outcome of one compile. |
78
78
  | `SrcDefinition` | interface | The build and export settings one published `src` environment contributes. |
@@ -94,20 +94,22 @@ Exported from `@orkestrel/scaffold`, and reachable from
94
94
  | `CONFIG_TEMPLATES` | const | Formatter-stable template text for every configuration artifact. |
95
95
  | `CONFORMANCE_TEST_PATH` | const | The official-tooling drift proof whose presence makes a workspace `conformance`. |
96
96
  | `CONTROL_CHARACTER_PATTERN` | const | Unicode controls, formatting controls, and line and paragraph separators rejected in text. |
97
+ | `DECLARATION_DEV_DEPENDENCIES` | const | The development dependencies that emit declarations for published source or an executable. |
97
98
  | `DEFAULT_ENGINES` | const | The `engines.node` range a workspace starts with. |
98
99
  | `DEFAULT_VERSION` | const | The version a workspace starts at. |
99
100
  | `DEPENDENCY_NAME_PATTERN` | const | The runtime dependency name syntax: the `@orkestrel` scope and a bare name. |
101
+ | `DISTRIBUTION_TEST_PATH` | const | The packed-package proof whose presence makes a workspace `distribution`. |
100
102
  | `ENGINES_PATTERN` | const | The minimum-Node engine syntax a blueprint declares. |
101
103
  | `ENVIRONMENTS` | const | The three `Environment` values, frozen. |
104
+ | `EXECUTABLE_PATHS` | const | The vendored paths a target receives with its executable bit set, frozen. |
102
105
  | `EXTRA_NAME_PATTERN` | const | The development extra name syntax: any valid npm package name. |
103
106
  | `EXTRA_RANGE_PATTERN` | const | The registry-only semver subset accepted for a development extra's range. |
104
107
  | `GLOBAL_SETUP_PATH` | const | The shared Vitest global-setup module whose presence makes a workspace `global`. |
105
- | `GUIDES_TEST_PATH` | const | The guide-parity proof whose physical file selects the fixed `guides` project. |
106
- | `EXECUTABLE_PATHS` | const | The vendored paths a target receives with its executable bit set, frozen. |
107
108
  | `GROUPS` | const | The seven `Group` values in plan order, frozen. |
109
+ | `GUIDES_TEST_PATH` | const | The guide-parity proof whose presence selects the planned `guides` project. |
108
110
  | `HEX_PATTERN` | const | Exact lowercase hexadecimal bytes: two digits per byte, and empty content is valid. |
109
111
  | `HOST_PATHS` | const | The paths byte-copied from the vendored data root, frozen. |
110
- | `INTEGRATION_TEST_PATH` | const | The installed-package proof whose presence makes a workspace `integration`. |
112
+ | `INTEGRATION_TEST_PATH` | const | The cross-environment composition proof whose presence makes a workspace `integration`. |
111
113
  | `INVALID_PATH_CHARACTER_PATTERN` | const | Visible characters a target-relative path and a Markdown path cell both forbid. |
112
114
  | `MAX_ARTIFACT_BYTES` | const | Maximum bytes accepted for one artifact. |
113
115
  | `MAX_ARTIFACT_HEX_LENGTH` | const | Maximum length of the hexadecimal string carrying one artifact's bytes. |
@@ -124,6 +126,7 @@ Exported from `@orkestrel/scaffold`, and reachable from
124
126
  | `ORCHESTRATION_PATH_NAMES` | const | The exact root filenames that wire an agent bench rather than the toolchain, frozen. |
125
127
  | `ORCHESTRATION_PATH_PREFIXES` | const | The path prefixes whose contents instruct or wire an agent, frozen. |
126
128
  | `ORKESTREL_RANGE_PATTERN` | const | The exact caret-pinned pre-1.0 range accepted for an `@orkestrel/*` runtime dependency. |
129
+ | `PRINT_WIDTH` | const | Columns one emitted line may occupy, matching `printWidth` in `.oxfmtrc.json`. |
127
130
  | `SERVICE_SCRIPT_PATH` | const | The provisioner skeleton a workspace with declared service vendors is given once. |
128
131
  | `SERVICE_SETUP_PATH` | const | The live-service readiness module whose presence makes a workspace `service`. |
129
132
  | `SERVICE_TEST_INCLUDE` | const | The include the live-service project covers, which is a directory rather than one proof. |
@@ -131,7 +134,9 @@ Exported from `@orkestrel/scaffold`, and reachable from
131
134
  | `SHOWCASE_DEV_DEPENDENCIES` | const | The development dependency used only by the optional single-file showcase build. |
132
135
  | `SOURCE_BROWSER_DEV_DEPENDENCIES` | const | The development dependencies a published browser `src` environment adds. |
133
136
  | `SRC_MATRIX` | const | The build and export settings each published `src` environment contributes, frozen. |
137
+ | `TAB_WIDTH` | const | Columns one tab occupies when the formatter measures a line, matching `tabWidth`. |
134
138
  | `VERSION_PATTERN` | const | The exact three-component version syntax a blueprint declares. |
139
+ | `WORKSPACE_OWNED_PATHS` | const | The vendored paths whose present bytes belong to each workspace, frozen. |
135
140
 
136
141
  #### Guards
137
142
 
@@ -145,8 +150,8 @@ Exported from `@orkestrel/scaffold`, and reachable from
145
150
  | `isCompilerHooks` | const | Narrow a value to the compiler's initial listener record. |
146
151
  | `isCompilerOptions` | const | Narrow a value to `CompilerOptions`. |
147
152
  | `isContent` | const | Narrow a value to text this package will accept as one artifact's content. |
148
- | `isDependencyName` | const | Narrow a value to the scoped package name a runtime dependency carries. |
149
153
  | `isDependency` | const | Narrow a value to a `Dependency`. |
154
+ | `isDependencyName` | const | Narrow a value to the scoped package name a runtime dependency carries. |
150
155
  | `isEnvironment` | const | Narrow a value to one `Environment` a workspace may select. |
151
156
  | `isFinding` | const | Narrow a value to a `Finding`. |
152
157
  | `isGroup` | const | Narrow a value to one `Group` a plan selects over. |
@@ -189,9 +194,10 @@ Exported from `@orkestrel/scaffold`, and reachable from
189
194
  | `matchesDriftReachability` | function | Test whether `inferDrift` could have produced a finding for an ownership. |
190
195
  | `matchesEngines` | function | Test whether a declared engines floor is at or above the supported minimum. |
191
196
  | `matchesOrchestrationPath` | function | Test whether a path instructs or wires an agent rather than the toolchain. |
197
+ | `matchesPrintWidth` | function | Test whether one emitted line fits the vendored formatter width. |
192
198
  | `matchesRange` | function | Test whether a declared range already admits a published version. |
193
199
  | `nameToGuide` | function | Derive the guide mirror path a package name answers for. |
194
- | `nameToRewrite` | function | Derive the declaration rewrite a published face roll-up applies. |
200
+ | `nameToRewrite` | function | Derive the declaration rewrite a published face's `beforeWriteFile` applies. |
195
201
  | `planToSummary` | function | Project a plan into its tally by artifact origin. |
196
202
  | `selectGroups` | function | Select the groups a compile covers, in plan order. |
197
203
  | `selectHostPaths` | function | Select the host paths a named workspace vendors. |
@@ -458,9 +464,10 @@ An option a verb does not list is refused by name rather than parsed and ignored
458
464
  the registry, so `new` fails when the registry names no release for a package it was given: the
459
465
  workspace would otherwise declare a dependency that does not resolve.
460
466
 
461
- `new --bin` creates the executable entry, its test, and its scoped Vite and TypeScript wrappers.
462
- The other structural facts do not need creation flags. Add `tests/integration.test.ts` for
463
- `integration`, `tests/conformance.test.ts` for `conformance`, `tests/setupService.ts` for `service`,
467
+ `new --bin` creates the executable entry, its test, and its scoped Vite and TypeScript wrappers. The
468
+ other structural facts do not need creation flags. Add `tests/guides.test.ts` for `guides`,
469
+ `tests/distribution.test.ts` for `distribution`, `tests/integration.test.ts` for `integration`,
470
+ `tests/conformance.test.ts` for `conformance`, `tests/setupService.ts` for `service`,
464
471
  `tests/setupGlobal.ts` for `global`, and `configs/app/vite.showcase.config.ts` for `showcase`;
465
472
  reading verbs detect each exact-case file and register its fixed machinery. Add `scripts/service.sh`
466
473
  for `vendors`. Reading verbs preserve and protect that birth-owned script, but do not infer its
@@ -471,8 +478,9 @@ vendor list from edited text.
471
478
  `audit`, `repair`, `catalog`, and `overwrite` derive the blueprint from the target itself. The name
472
479
  and the declared `@orkestrel/*` packages come from `package.json`. The two environment axes come
473
480
  from the directories the target actually ships, because a directory is the fact and a declaration
474
- beside it could disagree. Six more facts come from exact-case files: `src/bin/main.ts` selects
475
- `bin`, `tests/integration.test.ts` selects `integration`, `tests/conformance.test.ts` selects
481
+ beside it could disagree. Eight more facts come from exact-case files: `src/bin/main.ts` selects
482
+ `bin`, `tests/guides.test.ts` selects `guides`, `tests/distribution.test.ts` selects `distribution`,
483
+ `tests/integration.test.ts` selects `integration`, `tests/conformance.test.ts` selects
476
484
  `conformance`, `tests/setupService.ts` selects `service`, `tests/setupGlobal.ts` selects `global`,
477
485
  and `configs/app/vite.showcase.config.ts` selects `showcase`. A containing directory does not select
478
486
  the fact by itself.
@@ -488,27 +496,43 @@ cannot recover would leave every such workspace unplannable. `tests/setupService
488
496
  is the module the root configuration names by path, and is what a live proof needs anyway, so it
489
497
  carries the fact and the vendor list keeps its own separate job.
490
498
 
491
- The root Vite configuration always defines the fixed `guides` project and selects it at
492
- configuration load only when `tests/guides.test.ts` is a physical file with that exact path case. A
493
- directory or a case-folded spelling does not select it. A fresh workspace therefore carries no
494
- guides project. A workspace that later adds the proof keeps the project through `repair`. The
495
- birth-owned manifest still gains no `test:guides` script automatically; the developer who adds the
496
- proof adds that script too.
497
-
498
- The three plan-reading verbs compare every Vitest project named by the target manifest's scripts
499
- with the project set the planned root configuration will load. One shell-token pass reads quoted and
500
- unquoted `--project value` and `--project=value` forms. A shell expansion or malformed quote that
501
- prevents a project value from being resolved statically produces a question instead of licensing a
502
- write. The classifier is deliberately bounded to manifest script text that names `vitest`; an
503
- external wrapper whose name does not identify its runner supplies no static Vitest fact to infer.
499
+ The root Vite configuration defines and registers the fixed `guides` project only when the derived
500
+ blueprint carries `guides`. Reading verbs set that fact only when `tests/guides.test.ts` is a
501
+ physical file with that exact path case. A directory or a case-folded spelling does not select it. A
502
+ fresh workspace therefore carries no guides project or script. A developer who adds the proof must
503
+ also add the exact `test:guides` script line that the plan reports; the manifest remains
504
+ birth-owned.
505
+
506
+ The three plan-reading verbs compare the Vitest project set named by the target manifest with the
507
+ project set the planned root configuration registers. Every planned proof project must also be
508
+ reachable from the manifest's `test` chain. A target whose manifest does not set `private: true` may
509
+ also reach it from `prepublishOnly`. A private target cannot use that chain, because npm refuses the
510
+ package before a publish lifecycle script runs, so crediting it would report a dead gate as a live
511
+ one. Generated integration runs from `test`. One shell-token pass reads quoted and unquoted
512
+ `--project value` and `--project=value` forms and follows literal `npm run` calls. A shell expansion
513
+ or malformed quote that prevents a project or script name from being resolved statically produces a
514
+ question instead of licensing a write. The classifier is deliberately bounded to manifest script
515
+ text that names `vitest`; an external wrapper whose name does not identify its runner supplies no
516
+ static Vitest fact to infer.
504
517
 
505
518
  `audit` still completes the comparison and reports one non-blocking `projects` question. For a
506
519
  literal absent project, its advisory tells the developer to register the project or remove the
507
- script. `repair` and `overwrite` refuse instead. Their refusal tells the developer to remove the
520
+ script. For a planned project absent from both gate chains, the advisory gives the exact direct
521
+ script line to add to `package.json`. `repair` and `overwrite` refuse either mismatch and do not
522
+ write the manifest or configuration. Their absent-project refusal tells the developer to remove the
508
523
  script or not use scaffold writing verbs for a workspace that needs custom Vitest projects. It does
509
524
  not recommend editing the content-owned configuration that the refusing verb would restore. An
510
525
  advisory alone does not make an aligned target drift.
511
526
 
527
+ The same three plan-reading verbs compare the tooling set the derived blueprint plans against
528
+ `dependencies` and `devDependencies` together. A missing planned package produces one non-blocking
529
+ `dependencies` question naming every missing package and the exact manifest lines to add, in stable
530
+ order. The comparison measures membership only: range differences and workspace-owned extras are
531
+ outside it, and a planned tool may live in either section. A present section that is not an object
532
+ produces a question instead of a crash. `audit` reports the question without changing its exit
533
+ semantics. `repair` and `overwrite` refuse before writing configuration, and no verb edits the
534
+ birth-owned `package.json`.
535
+
512
536
  ### Exit codes
513
537
 
514
538
  `0` means the target matched its plan and every step completed. `1` means the target drifted or a
@@ -572,22 +596,50 @@ because the shape is chosen once and read afterwards: `new` refuses the advisory
572
596
  `repair` need the plan to describe and restore a target that already has that shape. A library
573
597
  caller creating a workspace holds the same refusal, and the Compile section below states it.
574
598
 
575
- `bin`, `integration`, `conformance`, `service`, `vendors`, `global`, and `showcase` are structural
576
- facts. Each is set only when the workspace physically ships the directory or exact-case file that
577
- defines it, never because of the workspace's name and never because a sibling fact is set.
599
+ `bin`, `guides`, `distribution`, `integration`, `conformance`, `service`, `vendors`, `global`, and
600
+ `showcase` are structural facts. Each is set only when the workspace physically ships the directory
601
+ or exact-case file that defines it, never because of the workspace's name and never because a
602
+ sibling fact is set.
603
+
604
+ A structural fact is read when a verb runs, not when the file appears. Writing
605
+ `tests/integration.test.ts` into a workspace sets the fact, but the root configuration on disk was
606
+ generated before that file existed and still registers no `integration` project, so `test:config`
607
+ fails with `integration has no project factory or configuration` until a plan-writing verb
608
+ regenerates it.
609
+
610
+ `repair` alone does not close that, and refusing is correct rather than a gap. `package.json` is
611
+ birth-owned, so the verb cannot add the project's script, and it will not register a project the
612
+ manifest reaches from no gate. It exits 1 naming the target and writes nothing.
613
+
614
+ Adding a structural proof is therefore three steps, in order: write the file; declare its
615
+ `test:<project>` script and invoke that script from a gate chain; then run `repair`, which
616
+ regenerates the root configuration and registers the project. `audit` reports whichever piece is
617
+ still outstanding at each step.
618
+
619
+ `distribution` projects only when the workspace also publishes at least one `src` environment. It
620
+ packs and installs the published artifact, so without that axis there is nothing to pack, and the
621
+ declared flag alone adds no project, no `test:distribution` script, and no gate entry.
578
622
 
579
623
  `service` says the workspace runs a live-service Vitest project over `tests/service`, and it alone
580
624
  registers that project, its `test:service` script, and the `tests/setupService.ts` readiness module
581
- the project names. `vendors` names each external service the workspace drives and emits
582
- `scripts/service.sh`, the provisioner that starts them. Neither is derivable from the other: a
583
- workspace may declare vendors before it writes a suite, and a suite may drive a service the skeleton
584
- does not start.
585
-
586
- An axis-dependent structural fact projects only when its required axis exists. `integration`
587
- projects a published `src`, and `showcase` projects the browser `app` environment. When that axis is
588
- absent the flag adds no artifact, configuration, script, or dependency, and the gate reports a
589
- non-blocking question on that field so the caller who set it learns it emitted nothing. The same
590
- rule applies to both facts.
625
+ the project names. A publishing workspace invokes it from `prepublishOnly`; a `private: true`
626
+ workspace invokes it from `test`, which is the only gate it has. Its longer timeouts and disabled
627
+ file parallelism are the same in both. `vendors` names each external service the workspace drives
628
+ and emits `scripts/service.sh`, the provisioner that starts them. Neither is derivable from the
629
+ other: a workspace may declare vendors before it writes a suite, and a suite may drive a service the
630
+ skeleton does not start.
631
+
632
+ `integration` projects a cross-environment composition proof for any workspace, independently of
633
+ whether it has a published `src`. Its generated seed imports every selected `src` and `app`
634
+ environment through its public barrel. It starts no process and does not pack or install the
635
+ workspace. The proof composes across environments, so when `src` and `app` together declare fewer
636
+ than two the gate reports a non-blocking `integration` question: the project, the script, and the
637
+ `test` entry are all still registered, and the advisory reports that the seed composes nothing
638
+ rather than withholding it.
639
+
640
+ `showcase` projects only when the browser `app` environment exists. Without that axis the flag adds
641
+ no artifact, configuration, script, or dependency, and the gate reports a non-blocking question on
642
+ that field so the caller who set it learns it emitted nothing.
591
643
 
592
644
  `createBlueprint` enforces shape only. Whether the name is a name, the version a version, and the
593
645
  axis combination one this package can generate are the gate's laws, and the gate answers them with
@@ -688,15 +740,21 @@ content is produced. `Ownership` says what scaffold claims at the path.
688
740
  | `presence` | Existence only | Restore an absent file, never touch present bytes |
689
741
  | `birth` | Nothing | Create the file only while it is absent |
690
742
 
743
+ Presence ownership has two separate mechanisms, and a reader needs to know which applies:
744
+
745
+ | Mechanism | Paths | Bytes belong to | Cost |
746
+ | --------------- | ------------------------------------------------- | --------------------- | ------------------------------------------------------- |
747
+ | Verb-owned | `CATALOG_AGENT_PATH` and dependency guide mirrors | `catalog` or `mirror` | The owning verb is the only route for a later update. |
748
+ | Workspace-owned | `WORKSPACE_OWNED_PATHS`, currently `.gitignore` | The target workspace | Present bytes receive no later canonical ignore update. |
749
+
691
750
  Birth ownership is what makes a generated workspace the consumer's. `package.json`, the source
692
751
  barrels, the tests, `README.md`, and `guides/README.md` are written once and are never rewritten by
693
752
  a later verb.
694
753
 
695
754
  Content ownership does not preserve an arbitrary custom Vitest project. Fixed optional proofs are
696
- selected by their defining paths, as `guides`, `conformance`, and `service` are. A workspace that
697
- needs other local configuration
698
- must keep those edits outside a content-owned file; `repair` restores that file to the canonical
699
- project set.
755
+ selected by their defining paths, as `guides`, `distribution`, `integration`, `conformance`, and
756
+ `service` are. A workspace that needs other local configuration must keep those edits outside a
757
+ content-owned file; `repair` restores that file to the canonical project set.
700
758
 
701
759
  An audit reports one `Finding` per planned path, followed by any foreign path beneath the groups
702
760
  the plan covers. Every planned finding carries its artifact's `ownership`. A foreign finding has
@@ -840,7 +898,10 @@ except the manifest.
840
898
  selection declares the `playwright` this module imports.
841
899
  - One template artifact per source and test file the selection needs: an `index.ts` barrel per
842
900
  selected environment, `main.ts` and `index.html` for an application browser, `tests/setup.ts`
843
- plus the host setup modules the selection reaches, and one entry test per axis project.
901
+ plus the host setup modules the selection reaches, and one entry test per axis project. An
902
+ integration selection also emits a birth-owned `tests/integration.test.ts` seed that imports each
903
+ selected public barrel and records its initial empty exports for the consumer to replace with an
904
+ observable cross-environment flow.
844
905
  - One template artifact each for `README.md` and `guides/README.md`.
845
906
  - One host artifact per vendored path the workspace selects. A vendored directory is one planned
846
907
  path that expands into the files the data root stores beneath it.
@@ -998,7 +1059,7 @@ proves that every fence imports only real exports of the two barrels, and that e
998
1059
  in this file resolves to one. It neither runs a fence nor typechecks one, so a trailing `// value`
999
1060
  comment inside a fence states what this guide claims rather than what the build answered. The
1000
1061
  verdicts that are measured are the ones a consumer hovers:
1001
- [`tests/integration.test.ts`](../tests/integration.test.ts) drives every `@example` the built
1062
+ [`tests/distribution.test.ts`](../tests/distribution.test.ts) drives every `@example` the built
1002
1063
  declarations print against the installed package, scores each verdict it can read as a value, and
1003
1064
  names exactly the ones it cannot. Fences are not added to that instrument, because most of them
1004
1065
  cannot be run: several declare an ambient value that has no runtime, and several write to a
@@ -1043,29 +1104,43 @@ nothing. This is deliberate: a generated sample entity is repeatedly mistaken fo
1043
1104
  implementation. What a consumer does first is write the module's `types.ts`, then the
1044
1105
  implementation that conforms to it, then export both from the barrel — the order `AGENTS.md` fixes.
1045
1106
 
1046
- **A conformance or live-service proof is registered, but neither is written for you.** Scaffold
1047
- registers the `conformance` and `service` projects, their scripts, and the gate each belongs to. It
1048
- emits no proof into either, because both name something only the package knows: the official
1049
- artifact a conformance check measures against, and the service a live proof drives. A generated
1050
- placeholder would read as a proof while measuring nothing, so the file a consumer writes is the file
1051
- that selects the project.
1052
-
1053
- The consequence is one empty-project case in each direction. A blueprint carrying `conformance` with
1054
- no `tests/conformance.test.ts` registers a project whose include resolves to nothing, and Vitest
1055
- exits non-zero on it. A blueprint carrying `service` gets `tests/setupService.ts` — the root
1056
- configuration names that module by path, so an absent one fails the project's load rather than its
1057
- run and still no suite beneath `tests/service`, so `test:service` reports no test files until the
1058
- consumer writes the first one. Both cases are visible the first time the script runs, which is why
1059
- neither is silent.
1060
-
1061
- Neither project folds into `integration`, which means something narrower: the built package works
1062
- when installed and driven from outside. Two fleet packages hold the distinction. `@orkestrel/ollama`
1063
- drives a real Ollama daemon through a `service` project, so a real service answers it and it runs
1064
- from `prepublishOnly`. `@orkestrel/mcp` measures its server against the specification's own runner,
1065
- `@modelcontextprotocol/conformance`, through a `conformance` project. It pins that runner as a
1066
- development dependency and resolves it out of `node_modules`, and the server the runner drives is
1067
- one the fixture starts itself on a loopback port, so the run drives nothing external and stays in
1068
- `test`.
1107
+ **A selected distribution, conformance, or live-service proof is registered, but none is written for
1108
+ you.** Scaffold registers `conformance` and `service` when their structural facts are set, and
1109
+ registers `distribution` only when the workspace also publishes `src`. In a publishing workspace,
1110
+ `distribution` and `service` run from `prepublishOnly` and `conformance` stays in `test`. In a
1111
+ `private: true` workspace, `distribution` is absent, `service` runs from `test`, and there is no
1112
+ `prepublishOnly` at all. Scaffold emits no proof into any registered project, because each names
1113
+ something only the package knows: the behavior its own packed artifact must hold once installed, the
1114
+ official artifact a conformance check measures against, and the service a live proof drives. A
1115
+ generated placeholder would read as a proof while measuring nothing, so the file a consumer writes
1116
+ is the file that selects the project.
1117
+
1118
+ A distribution proof carries one contract scaffold does enforce from the outside. The generated
1119
+ `prepublishOnly` invokes it as `npm run test:distribution -- --mode release`, and a proof that reads
1120
+ `import.meta.env.MODE === 'release'` must **fail** on an unreachable registry rather than skip. An
1121
+ ordinary local run may skip that case, because a developer offline is not a defect; a release run
1122
+ may not, because skipping there passes the publish gate without ever proving the artifact installs.
1123
+ Scaffold writes no proof, so honouring the flag is the consumer's, and a proof that ignores it
1124
+ reports green on exactly the runs that matter.
1125
+
1126
+ The consequence is one empty-project case per registered proof. A publishing blueprint carrying
1127
+ `distribution` with no `tests/distribution.test.ts`, or any blueprint carrying `conformance` with no
1128
+ `tests/conformance.test.ts`, registers a project whose include resolves to nothing, and Vitest exits
1129
+ non-zero on it. A blueprint carrying `service` gets `tests/setupService.ts` — the root configuration
1130
+ names that module by path, so an absent one fails the project's load rather than its run — and still
1131
+ no suite beneath `tests/service`, so `test:service` reports no test files until the consumer writes
1132
+ the first one. Every case is visible the first time the script runs, which is why none is silent.
1133
+
1134
+ None of the three folds into `integration`, which measures a different axis rather than a smaller
1135
+ one: the workspace's selected environments compose through their public barrels. The generated seed
1136
+ proves only that those barrels load together and expose the initial empty surfaces; the consumer
1137
+ replaces it with an observable cross-environment flow. The seed starts no process and does not pack
1138
+ or install the workspace, so the project stays in `test`. Two fleet packages hold the distinction.
1139
+ `@orkestrel/ollama` drives a real Ollama daemon through a `service` project, so a real service
1140
+ answers it and it runs from `prepublishOnly`. `@orkestrel/mcp` measures its server against the
1141
+ specification's own runner, `@modelcontextprotocol/conformance`, through a `conformance` project. It pins that runner as a development dependency and resolves it out
1142
+ of `node_modules`, and the server the runner drives is one the fixture starts itself on a loopback
1143
+ port, so the run drives nothing external and stays in `test`.
1069
1144
 
1070
1145
  ## Tests
1071
1146