@orkestrel/scaffold 0.0.44 → 0.0.45

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.
Files changed (66) hide show
  1. package/README.md +10 -10
  2. package/dist/bin/main.js +31 -31
  3. package/dist/bin/main.js.map +1 -1
  4. package/dist/host/AGENTS.md +7 -2
  5. package/dist/host/agents/orchestration.md +232 -56
  6. package/dist/host/agents/skills/enterprise-bootstrap/SKILL.md +7 -7
  7. package/dist/host/agents/skills/enterprise-bootstrap/references/bootstrap-reference.md +11 -9
  8. package/dist/host/agents/skills/enterprise-bootstrap/references/components.md +3 -3
  9. package/dist/host/agents/skills/enterprise-bootstrap/references/frontend-design.md +3 -3
  10. package/dist/host/agents/skills/enterprise-bootstrap/references/utilities.md +1 -1
  11. package/dist/host/agents/skills/orkestrel-align-packages/SKILL.md +1 -1
  12. package/dist/host/agents/skills/orkestrel-build-application/SKILL.md +2 -2
  13. package/dist/host/agents/skills/orkestrel-debrief/SKILL.md +1 -1
  14. package/dist/host/agents/skills/orkestrel-debrief/references/field-testing.md +2 -2
  15. package/dist/host/agents/skills/orkestrel-debrief/references/instruction-audit.md +8 -8
  16. package/dist/host/agents/skills/orkestrel-falsify/SKILL.md +16 -14
  17. package/dist/host/agents/skills/orkestrel-falsify/references/brief.md +3 -3
  18. package/dist/host/agents/skills/orkestrel-falsify/references/reconcile.md +14 -14
  19. package/dist/host/agents/skills/orkestrel-harden-package/SKILL.md +1 -1
  20. package/dist/host/agents/skills/orkestrel-human-journey/SKILL.md +3 -3
  21. package/dist/host/agents/skills/orkestrel-human-journey/references/captures.md +3 -3
  22. package/dist/host/agents/skills/orkestrel-human-journey/references/layer.md +3 -3
  23. package/dist/host/agents/skills/orkestrel-polish-surface/SKILL.md +3 -3
  24. package/dist/host/claude/agents/analyst.md +2 -2
  25. package/dist/host/claude/agents/checker.md +2 -2
  26. package/dist/host/claude/agents/codex.md +5 -5
  27. package/dist/host/claude/agents/orkestrel.md +9 -8
  28. package/dist/host/claude/agents/planner.md +1 -1
  29. package/dist/host/claude/agents/researcher.md +2 -2
  30. package/dist/host/claude/agents/reviewer.md +1 -1
  31. package/dist/host/claude/agents/scout.md +2 -2
  32. package/dist/host/claude/agents/sol.md +3 -3
  33. package/dist/host/claude/agents/verifier.md +8 -0
  34. package/dist/host/claude/rules/application.md +7 -7
  35. package/dist/host/claude/rules/architecture.md +6 -6
  36. package/dist/host/claude/rules/documentation.md +1 -0
  37. package/dist/host/claude/rules/patterns.md +3 -3
  38. package/dist/host/claude/rules/quality.md +3 -3
  39. package/dist/host/claude/rules/tests.md +9 -2
  40. package/dist/host/claude/rules/workspace.md +7 -7
  41. package/dist/host/claude/rules/writing.md +12 -2
  42. package/dist/host/codex/agents/planner.toml +1 -1
  43. package/dist/host/configs/helpers.ts +21 -1
  44. package/dist/host/cursor/rules/orchestration.mdc +1 -1
  45. package/dist/host/dotfiles/gitignore +4 -1
  46. package/dist/host/guides/scaffold.md +134 -119
  47. package/dist/host/scripts/codex.sh +0 -0
  48. package/dist/host/scripts/cursor.sh +0 -0
  49. package/dist/host/scripts/deps.sh +0 -0
  50. package/dist/host/scripts/ollama.sh +0 -0
  51. package/dist/host/tests/config.test.ts +40 -1
  52. package/dist/host/tests/policy.test.ts +2 -2
  53. package/dist/host/tests/setupPolicy.ts +8 -5
  54. package/dist/src/core/index.cjs +143 -149
  55. package/dist/src/core/index.cjs.map +1 -1
  56. package/dist/src/core/index.d.cts +1624 -1643
  57. package/dist/src/core/index.d.ts +1624 -1643
  58. package/dist/src/core/index.js +144 -149
  59. package/dist/src/core/index.js.map +1 -1
  60. package/dist/src/server/index.cjs +82 -97
  61. package/dist/src/server/index.cjs.map +1 -1
  62. package/dist/src/server/index.d.cts +1844 -1871
  63. package/dist/src/server/index.d.ts +1844 -1871
  64. package/dist/src/server/index.js +83 -96
  65. package/dist/src/server/index.js.map +1 -1
  66. package/package.json +3 -3
@@ -1,26 +1,28 @@
1
1
  # Scaffold
2
2
 
3
3
  > Scaffold compiles a workspace specification into an ordered list of files, compares that list to a
4
- > real directory, and writes the difference. It ships one executable, `scaffold`, and two library
4
+ > real directory, and writes the difference. It ships one executable, `scaffold`, and library
5
5
  > entry points: `@orkestrel/scaffold` is the pure compiler and its data contracts, and
6
6
  > `@orkestrel/scaffold/server` is the filesystem writer and the network reader. Source:
7
7
  > [`src/core/index.ts`](../src/core/index.ts) and [`src/server/index.ts`](../src/server/index.ts).
8
8
 
9
9
  The package exists because every `@orkestrel` repository shares the same toolchain, the same agent
10
- instructions, and the same root dotfiles. Keeping thirty copies of those files in agreement by hand
10
+ instructions, and the same root dotfiles. Keeping every copy of those files in agreement by hand
11
11
  does not work. Scaffold makes the shared set data — a vendored data root shipped inside the package
12
- — and gives it three verbs: create a workspace from it, report how a workspace differs from it, and
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. Nothing runs one, so a trailing `// value` comment inside a
16
- fence is this guide's claim rather than a measured answer; the driven examples are the ones the
17
- shipped declarations print. Limits states what that leaves unproven and what covers it instead.
15
+ Every code fence below is illustrative. [`tests/guides.test.ts`](../tests/guides.test.ts)
16
+ transcribes the pure blueprint-default, compile-refusal, and error-narrowing fences and checks their
17
+ literal verdicts. A trailing comment in another fence is this guide's claim rather than a measured
18
+ answer; the driven examples are the ones the shipped declarations print. Limits states what that
19
+ leaves unproven and what covers it instead.
18
20
 
19
21
  ```sh
20
22
  npm install --save-dev @orkestrel/scaffold
21
23
  ```
22
24
 
23
- The executable needs Node 22.12 or newer. Run it through `npx` without installing:
25
+ The executable needs Node 22.12 or later. Run it through `npx` without installing:
24
26
 
25
27
  ```sh
26
28
  npx @orkestrel/scaffold --help
@@ -40,7 +42,7 @@ Exported from `@orkestrel/scaffold`, and reachable from
40
42
  | `Artifact` | type | One file in a plan, discriminated by how its content is produced and what scaffold claims of it. |
41
43
  | `BuildFormat` | type | One module format a published library environment builds. |
42
44
  | `CatalogEntry` | type | One package row of the fleet catalog. |
43
- | `CompileStage` | type | The three compile phases, in the order they run. |
45
+ | `CompileStage` | type | The compile phases, in the order they run. |
44
46
  | `CompilerEventMap` | type | The compiler's observation channel. |
45
47
  | `Drift` | type | How one target path compares to the artifact planned for it. |
46
48
  | `Environment` | type | One environment a generated workspace selects on its `src` or `app` axis. |
@@ -100,13 +102,13 @@ Exported from `@orkestrel/scaffold`, and reachable from
100
102
  | `DEPENDENCY_NAME_PATTERN` | const | The runtime dependency name syntax: the `@orkestrel` scope and a bare name. |
101
103
  | `DISTRIBUTION_TEST_PATH` | const | The packed-package proof whose presence makes a workspace `distribution`. |
102
104
  | `ENGINES_PATTERN` | const | The minimum-Node engine syntax a blueprint declares. |
103
- | `ENVIRONMENTS` | const | The three `Environment` values, frozen. |
105
+ | `ENVIRONMENTS` | const | The `Environment` values, frozen. |
104
106
  | `EXECUTABLE_PATHS` | const | The vendored paths a target receives with its executable bit set, frozen. |
105
107
  | `EXTRA_RANGE_PATTERN` | const | The registry-only semver subset accepted for a development extra's range. |
106
- | `FLOOR_RANGE_PATTERN` | const | The exact three-component floor accepted for a foreign peer's range. |
108
+ | `FLOOR_RANGE_PATTERN` | const | The exact `major.minor.patch` floor accepted for a foreign peer's range. |
107
109
  | `FOREIGN_NAME_PATTERN` | const | The package name syntax for a dependency this package does not publish. |
108
110
  | `GLOBAL_SETUP_PATH` | const | The shared Vitest global-setup module whose presence makes a workspace `global`. |
109
- | `GROUPS` | const | The seven `Group` values in plan order, frozen. |
111
+ | `GROUPS` | const | The `Group` values in plan order, frozen. |
110
112
  | `GUIDES_TEST_PATH` | const | The guide-parity proof whose presence selects the planned `guides` project. |
111
113
  | `HEX_PATTERN` | const | Exact lowercase hexadecimal bytes: two digits per byte, and empty content is valid. |
112
114
  | `HOST_PATHS` | const | The paths byte-copied from the vendored data root, frozen. |
@@ -136,7 +138,7 @@ Exported from `@orkestrel/scaffold`, and reachable from
136
138
  | `SOURCE_BROWSER_DEV_DEPENDENCIES` | const | The development dependencies a published browser `src` environment adds. |
137
139
  | `SRC_MATRIX` | const | The build and export settings each published `src` environment contributes, frozen. |
138
140
  | `TAB_WIDTH` | const | Columns one tab occupies when the formatter measures a line, matching `tabWidth`. |
139
- | `VERSION_PATTERN` | const | The exact three-component version syntax a blueprint declares. |
141
+ | `VERSION_PATTERN` | const | The exact `major.minor.patch` version syntax a blueprint declares. |
140
142
  | `WORKSPACE_OWNED_PATHS` | const | The vendored paths whose present bytes belong to each workspace, frozen. |
141
143
 
142
144
  #### Guards
@@ -187,7 +189,7 @@ Exported from `@orkestrel/scaffold`, and reachable from
187
189
  | `computeBytes` | function | Count the UTF-8 bytes text encodes to. |
188
190
  | `computeHash` | function | Compute the deterministic content identity of text. |
189
191
  | `contentToHex` | function | Encode text as the exact lowercase hexadecimal form of its UTF-8 bytes. |
190
- | `extractVersion` | function | Extract the three numeric components of an exact version. |
192
+ | `extractVersion` | function | Extract the major, minor, and patch components of an exact version. |
191
193
  | `inferDrift` | function | Infer how one target path compares to the artifact planned for it. |
192
194
  | `inferGroup` | function | Infer the `Group` a path belongs to. |
193
195
  | `manifestToDependencies` | function | Project a package manifest's text to the `@orkestrel/*` packages it declares. |
@@ -239,7 +241,6 @@ Exported from `@orkestrel/scaffold`, and reachable from
239
241
  | Name | Kind | Summary |
240
242
  | ----------------- | -------- | --------------------------------------------------------------------------------- |
241
243
  | `createBlueprint` | function | Construct a `Blueprint` from a name and the fields that differ from the defaults. |
242
- | `createCompiler` | function | Construct a `Compiler`. |
243
244
 
244
245
  #### Classes
245
246
 
@@ -351,13 +352,6 @@ Exported from `@orkestrel/scaffold/server`, and reachable from
351
352
  | `resolveRealPath` | function | Resolve a path through the real filesystem, keeping the part that does not exist yet. |
352
353
  | `stageHost` | function | Stage a vendored host root from a real checkout. |
353
354
 
354
- #### Factories
355
-
356
- | Name | Kind | Summary |
357
- | -------------------- | -------- | --------------------------- |
358
- | `createMaterializer` | function | Construct a `Materializer`. |
359
- | `createUpstream` | function | Construct an `Upstream`. |
360
-
361
355
  #### Classes
362
356
 
363
357
  | Name | Kind | Summary |
@@ -391,7 +385,7 @@ no interface and is documented directly.
391
385
  | `mirror` | Write fetched dependency guides to their local mirrors. |
392
386
  | `catalog` | Rewrite the marker-bounded package table in the target's catalog agent file. |
393
387
  | `declare` | Rewrite the `@orkestrel/*` range set in the target's manifest. |
394
- | `remove` | Delete the files the plan does not own. |
388
+ | `remove` | Re-derive and delete the tracked files the plan does not own. |
395
389
  | `destroy` | Tear the materializer down. Every later call throws, and teardown is idempotent. |
396
390
 
397
391
  #### `UpstreamInterface`
@@ -409,14 +403,14 @@ no interface and is documented directly.
409
403
  | ----------- | ---------------------------------------------------------------------------------- |
410
404
  | `write` | Stage one text file. |
411
405
  | `copy` | Stage one byte-for-byte copy in executable or non-executable destination mode. |
412
- | `directory` | Establish one directory inside the target, one segment at a time. |
406
+ | `establish` | Establish one directory inside the target, one segment at a time. |
413
407
  | `remove` | Mark one file for deletion at commit. |
414
408
  | `commit` | Promote every staged file and take every marked file, or roll the whole call back. |
415
409
  | `discard` | Abandon the transaction and remove everything it created. |
416
410
 
417
411
  ## Command line
418
412
 
419
- Five verbs. Authority is the verb's: every verb except `audit` writes when it is typed, and no
413
+ Authority is the verb's: every verb except `audit` writes when it is typed, and no
420
414
  option grants a write.
421
415
 
422
416
  | Verb | Writes |
@@ -449,7 +443,7 @@ options
449
443
  --bin scaffold a command-line executable at src/bin/main.ts
450
444
  --deps <list> the @orkestrel/* packages the workspace depends on
451
445
  --groups <list> the artifact groups to cover; every group when absent
452
- --all fetch a guide for every package the organization publishes, not just the declared ones
446
+ --all fetch a guide for every package the organization publishes, not the declared ones alone
453
447
  --dirty delete from a tree carrying uncommitted changes
454
448
  --from <path> read the data root from a local path instead of the bundled one; catalog alone accepts it more than once
455
449
  --target <path> the directory the verb operates on; the working directory when absent
@@ -461,9 +455,12 @@ exit codes
461
455
  2 usage error
462
456
  ```
463
457
 
464
- An option a verb does not list is refused by name rather than parsed and ignored. `--deps` reaches
465
- the registry, so `new` fails when the registry names no release for a package it was given: the
466
- workspace would otherwise declare a dependency that does not resolve.
458
+ An option a verb does not list is refused by name rather than parsed and ignored. `--help` is the
459
+ one exception, because it replaces the run rather than modifying it: a command line carrying
460
+ `--help` anywhere prints the whole reference and exits `0` before the line is read as a command, so
461
+ no verb has to list it. `--deps` reaches the registry, so `new` fails when the registry names no
462
+ release for a package it was given: the workspace would otherwise declare a dependency that does not
463
+ resolve.
467
464
 
468
465
  `new --bin` creates the executable entry, its test, and its scoped Vite and TypeScript wrappers. The
469
466
  other structural facts do not need creation flags. Add a root `tests/setup*.test.ts` proof for
@@ -478,9 +475,9 @@ vendor list from edited text.
478
475
  ### Reading a target
479
476
 
480
477
  `audit`, `repair`, `catalog`, and `overwrite` derive the blueprint from the target itself. The name
481
- and the declared `@orkestrel/*` packages come from `package.json`. The two environment axes come
478
+ and the declared `@orkestrel/*` packages come from `package.json`. The environment axes come
482
479
  from the directories the target actually ships, because a directory is the fact and a declaration
483
- beside it could disagree. Nine more facts come from exact-case files: `src/bin/main.ts` selects
480
+ beside it could disagree. The remaining facts come from exact-case files: `src/bin/main.ts` selects
484
481
  `bin`, each root `tests/setup*.test.ts` match selects `setup`, `tests/guides.test.ts` selects
485
482
  `guides`, `tests/distribution.test.ts` selects `distribution`,
486
483
  `tests/integration.test.ts` selects `integration`, `tests/conformance.test.ts` selects
@@ -506,7 +503,7 @@ fresh workspace therefore carries no guides project or script. A developer who a
506
503
  also add the exact `test:guides` script line that the plan reports; the manifest remains
507
504
  birth-owned.
508
505
 
509
- The three plan-reading verbs compare the Vitest project set named by the target manifest with the
506
+ The plan-reading verbs compare the Vitest project set named by the target manifest with the
510
507
  project set the planned root configuration registers. Every planned proof project must also be
511
508
  reachable from the manifest's `test` chain. A target whose manifest does not set `private: true` may
512
509
  also reach it from `prepublishOnly`. A private target cannot use that chain, because npm refuses the
@@ -520,14 +517,16 @@ static Vitest fact to infer.
520
517
 
521
518
  `audit` still completes the comparison and reports one non-blocking `projects` question. For a
522
519
  literal absent project, its advisory tells the developer to register the project or remove the
523
- script. For a planned project absent from both gate chains, the advisory gives the exact direct
524
- script line to add to `package.json`. `repair` and `overwrite` refuse either mismatch and do not
525
- write the manifest or configuration. Their absent-project refusal tells the developer to remove the
526
- script or not use scaffold writing verbs for a workspace that needs custom Vitest projects. It does
527
- not recommend editing the content-owned configuration that the refusing verb would restore. An
528
- advisory alone does not make an aligned target drift.
529
-
530
- The same three plan-reading verbs compare the tooling set the derived blueprint plans against
520
+ script. For a planned project absent from the gate chains, the advisory checks the direct
521
+ `test:<project>` script. When the script is absent, the advisory gives the exact line to add to
522
+ `package.json`. When the script is declared but ungated, the advisory names the script and the gate
523
+ chain that must invoke it, without repeating a script line. `repair` and `overwrite` refuse either
524
+ mismatch and do not write the manifest or configuration. Their absent-project refusal tells the
525
+ developer to remove the script or not use scaffold writing verbs for a workspace that needs custom
526
+ Vitest projects. It does not recommend editing the content-owned configuration that the refusing
527
+ verb would restore. An advisory alone does not make an aligned target drift.
528
+
529
+ The same plan-reading verbs compare the tooling set the derived blueprint plans against
531
530
  `dependencies` and `devDependencies` together. A missing planned package produces one non-blocking
532
531
  `dependencies` question naming every missing package and the exact manifest lines to add, in stable
533
532
  order. The comparison measures membership only: range differences and workspace-owned extras are
@@ -548,7 +547,7 @@ remove it.
548
547
  `overwrite` is the only verb that reads git, and it needs a repository. It asks git for the tracked
549
548
  set and the dirty set, deletes only tracked paths, and refuses a tree carrying uncommitted changes
550
549
  unless `--dirty` waives that refusal. A target that is not a git repository is refused under
551
- `TARGET`, because deletion there would have no recovery mechanism. The other four verbs never ask.
550
+ `TARGET`, because deletion there would have no recovery mechanism. The other verbs never ask.
552
551
 
553
552
  ### Machine-readable output
554
553
 
@@ -566,7 +565,8 @@ standard error, so a piped value is never polluted.
566
565
  Every failure reports the same envelope instead: `{ "error": { "code": …, "message": … } }`. The
567
566
  code is a `ScaffoldErrorCode`, or `USAGE` for a command line that never became a command, or
568
567
  `FAILED` for a raised value that published no code of its own. A command line that never became a
569
- command carries no `--json`, so its refusal is always prose.
568
+ command is refused in prose even when the line carries `--json`, because the flag is read from the
569
+ command and no command was read.
570
570
 
571
571
  ## Blueprint
572
572
 
@@ -587,10 +587,10 @@ blueprint.engines // '>=22.12.0'
587
587
  ```
588
588
 
589
589
  `src` selects published library environments and `app` selects private application environments.
590
- The two axes are independent, so a library-only, an application-only, and a mixed workspace are all
590
+ The axes are independent, so a library-only, an application-only, and a mixed workspace are all
591
591
  first class. `dependencies` are runtime `@orkestrel/*` packages. A peer in the `@orkestrel` scope is
592
592
  a fleet pin; every other peer is a floor. `extras` are development dependencies and may carry any
593
- valid npm name. A peer reaches the generated workspace through two representations:
593
+ valid npm name. A peer reaches the generated workspace through separate representations:
594
594
  `Blueprint.peers` validates the scope rule and compiles the manifest declarations, while the
595
595
  `peers` binding in the generated `vite.config.ts` derives from the target's live
596
596
  `peerDependencies`. Each published build face — core, browser, server, and `bin` — externalizes
@@ -624,7 +624,7 @@ regenerates it.
624
624
  birth-owned, so the verb cannot add the project's script, and it will not register a project the
625
625
  manifest reaches from no gate. It exits 1 naming the target and writes nothing.
626
626
 
627
- Adding a structural proof is therefore three steps, in order: write the file; declare its
627
+ Adding a structural proof is therefore these steps, in order: write the file; declare its
628
628
  `test:<project>` script and invoke that script from a gate chain; then run `repair`, which
629
629
  regenerates the root configuration and registers the project. `audit` reports whichever piece is
630
630
  still outstanding at each step.
@@ -660,7 +660,7 @@ questions. A blueprint the gate will refuse is still constructible, so one law l
660
660
 
661
661
  ## Compile
662
662
 
663
- The compiler is pure, synchronous, and host-independent. It runs three stages in order.
663
+ The compiler is pure, synchronous, and host-independent. It runs its stages in order.
664
664
 
665
665
  | Stage | Does |
666
666
  | ------- | ------------------------------------------------------------------------ |
@@ -669,9 +669,9 @@ The compiler is pure, synchronous, and host-independent. It runs three stages in
669
669
  | `pin` | Gives the plan its content identity |
670
670
 
671
671
  ```ts
672
- import { createBlueprint, createCompiler } from '@orkestrel/scaffold'
672
+ import { Compiler, createBlueprint } from '@orkestrel/scaffold'
673
673
 
674
- const compiler = createCompiler()
674
+ const compiler = new Compiler()
675
675
  const scaffolding = compiler.compile(createBlueprint('router', { src: ['core'] }))
676
676
 
677
677
  scaffolding.plan?.artifacts // every planned file, in group order
@@ -684,18 +684,18 @@ refused blueprint is answered rather than raised, so a caller reads the refusal
684
684
  asked for. Each stage records its input and its output, a failed stage records the coded reason
685
685
  beside them, and the stages after a failed one never run.
686
686
 
687
- A plan says the blueprint can be built. It does not say the blueprint should be created. Every
688
- question beside the plan is advice the compile could not settle, and the caller that chose the shape
689
- is the one that answers it. So `new` refuses on any question, blocking or not, before it writes,
690
- while `audit` and `repair` carry the same questions through, because a target that already has that
691
- shape still has to be described and restored.
687
+ A plan says the blueprint can be built. It does not decide whether to create it. Every question
688
+ beside the plan is advice the compile could not settle, and the caller that chose the shape is the
689
+ one that answers it. So `new` refuses on any question, blocking or not, before it writes, while
690
+ `audit` and `repair` carry the same questions through, because a target that already has that shape
691
+ still has to be described and restored.
692
692
 
693
693
  A library caller creating a fresh workspace applies `new`'s rule itself:
694
694
 
695
695
  ```ts
696
- import { createBlueprint, createCompiler } from '@orkestrel/scaffold'
696
+ import { Compiler, createBlueprint } from '@orkestrel/scaffold'
697
697
 
698
- const compiler = createCompiler()
698
+ const compiler = new Compiler()
699
699
  const scaffolding = compiler.compile(createBlueprint('router', { src: ['browser', 'server'] }))
700
700
 
701
701
  scaffolding.plan === undefined || scaffolding.questions.length > 0 // true — do not write this shape
@@ -711,8 +711,8 @@ and the questions are where the package says what it thinks of the choice. That
711
711
  place, and the caller that picked the shape is the one holding it.
712
712
 
713
713
  Off-contract input is different. A value that is not the exact shape raises `ScaffoldError` coded
714
- `INVALID`, because it is not a question anyone can answer. Both entry points snapshot the caller's
715
- value first and then guard the snapshot, so a property backed by an accessor is refused rather than
714
+ `INVALID`, because it is not a question anyone can answer. Each entry point snapshots the caller's
715
+ value first and then guards the snapshot, so a property backed by an accessor is refused rather than
716
716
  read.
717
717
 
718
718
  Overrides replace a drafted artifact's content whole. The gate checks each override against the
@@ -723,8 +723,8 @@ than a silent no-op.
723
723
 
724
724
  ### Groups
725
725
 
726
- A plan selects over seven groups, and a compile that names none covers all of them. The order below
727
- is the order a plan lists its artifacts in.
726
+ A plan selects over the following groups, and a compile that names none covers all of them. Their
727
+ order is the order a plan lists its artifacts in.
728
728
 
729
729
  | Group | Holds |
730
730
  | --------------- | ------------------------------------------------------------------ |
@@ -738,7 +738,7 @@ is the order a plan lists its artifacts in.
738
738
 
739
739
  ## Ownership and drift
740
740
 
741
- Two axes describe every planned file, and they answer different questions. `Origin` says how the
741
+ `Origin` and `Ownership` describe every planned file, and they answer different questions. `Origin` says how the
742
742
  content is produced. `Ownership` says what scaffold claims at the path.
743
743
 
744
744
  | `Origin` | Content comes from |
@@ -751,18 +751,29 @@ content is produced. `Ownership` says what scaffold claims at the path.
751
751
  | ----------- | -------------- | ------------------------------------------------- |
752
752
  | `content` | The bytes | Restore a missing file, replace a stale one |
753
753
  | `presence` | Existence only | Restore an absent file, never touch present bytes |
754
- | `birth` | Nothing | Create the file only while it is absent |
754
+ | `birth` | Nothing | Create the file only during initial materialize |
755
755
 
756
- Presence ownership has two separate mechanisms, and a reader needs to know which applies:
756
+ Presence ownership has separate mechanisms, and a reader needs to know which applies:
757
757
 
758
758
  | Mechanism | Paths | Bytes belong to | Cost |
759
759
  | --------------- | ------------------------------------------------- | --------------------- | ------------------------------------------------------- |
760
760
  | Verb-owned | `CATALOG_AGENT_PATH` and dependency guide mirrors | `catalog` or `mirror` | The owning verb is the only route for a later update. |
761
- | Workspace-owned | `WORKSPACE_OWNED_PATHS`, currently `.gitignore` | The target workspace | Present bytes receive no later canonical ignore update. |
762
-
763
- Birth ownership is what makes a generated workspace the consumer's. `package.json`, the source
764
- barrels, the tests, `README.md`, and `guides/README.md` are written once and are never rewritten by
765
- a later verb.
761
+ | Workspace-owned | `WORKSPACE_OWNED_PATHS`, which holds `.gitignore` | The target workspace | Present bytes receive no later canonical ignore update. |
762
+
763
+ Birth ownership is what makes a generated workspace the consumer's. `materialize` writes a
764
+ birth-owned path into a vacant target. A later `repair` or `overwrite` call treats that path as
765
+ aligned whether it is present or absent, so it neither restores missing bytes nor replaces present
766
+ bytes.
767
+
768
+ You own `tests/setup.ts`, the selected `tests/setupBrowser.ts`, `tests/setupServer.ts`,
769
+ `tests/setupService.ts`, and `tests/setupGlobal.ts` modules, each root `tests/setup*.test.ts` proof,
770
+ the selected environment entry tests under `tests/src` and `tests/app`, the
771
+ `tests/src/bin/main.test.ts` file, and the `tests/integration.test.ts` seed. Scaffold writes those
772
+ planned files only during materialize and leaves later edits or deletions alone. You also own the `tests/guides.test.ts`,
773
+ `tests/distribution.test.ts`, `tests/conformance.test.ts`, and `tests/service/**/*.test.ts` proof
774
+ files that you add to select their projects. Scaffold content-owns `tests/setupPolicy.ts`,
775
+ `tests/policy.test.ts`, and `tests/config.test.ts`; `repair` and `overwrite` restore those files when
776
+ their bytes drift or the files are missing.
766
777
 
767
778
  Content ownership does not preserve an arbitrary custom Vitest project. Fixed optional proofs are
768
779
  selected by their defining paths, as `guides`, `distribution`, `integration`, `conformance`, and
@@ -812,13 +823,13 @@ is refused with a coded `INVALID` failure rather than accepted and partly unders
812
823
  finding carries `ownership`, which findings made before that field existed do not. `remove` acts
813
824
  only on foreign findings, which never carried ownership, but the guard reads every finding, so one
814
825
  older planned finding refuses that call too. Take a fresh audit rather than replaying a stored one:
815
- a stored audit records what a target looked like then, and both verbs bind their writes to what a
816
- target holds now. The refusal is deliberate at `0.0.x` and there is no migration path.
826
+ a stored audit records what a target looked like then, and each verb binds its writes to what a
827
+ target holds. The refusal is deliberate at `0.0.x` and there is no migration path.
817
828
 
818
829
  ## Fleet catalog
819
830
 
820
831
  `catalog` rewrites one marker-bounded region in `CATALOG_AGENT_PATH` and nothing else in that file.
821
- The region holds a table with four columns:
832
+ The region holds a table with these columns:
822
833
 
823
834
  | Column | Content |
824
835
  | ---------------------- | -------------------------------------------------------------------------- |
@@ -827,13 +838,13 @@ The region holds a table with four columns:
827
838
  | `Layer` | The publish round the edges place the package in, as `L0`, `L1`, … |
828
839
  | `Runtime dependencies` | Each declared runtime edge, as name and range |
829
840
 
830
- Both edge-bearing columns come from the same abbreviated packument the version came from, so a
841
+ The edge-bearing columns come from the same abbreviated packument the version came from, so a
831
842
  catalog costs one request per package and no more. Only `dependencies` is read. `devDependencies`
832
843
  reaches no consumer of the published package, so it constrains nothing about publish order, and
833
844
  reading it would place packages in rounds that do not exist.
834
845
 
835
846
  The layer is not stored on a row. `catalogToLayers` derives it from the rows' own edges, in the same
836
- call that writes them, so the two cannot disagree:
847
+ call that writes them, so the layer and the rows cannot disagree:
837
848
 
838
849
  ```ts
839
850
  import { catalogToLayers } from '@orkestrel/scaffold'
@@ -847,20 +858,22 @@ edge to a row that found no version each constrain nothing, so neither holds its
847
858
 
848
859
  The order is load-bearing because these packages are `0.0.x`, where a caret pins one exact release.
849
860
  A dependent sees a new dependency version only after the dependent re-pins and republishes, so
850
- publishing a dependent before its dependency leaves the dependent pinned to the older release. Two
851
- ranges that disagree install two copies of one package, and the compiler reads those copies as two
861
+ publishing a dependent before its dependency leaves the dependent pinned to the older release.
862
+ Ranges that disagree install duplicate copies of one package, and the compiler reads those copies as
852
863
  distinct types.
853
864
 
854
865
  A cycle cannot be published in rounds. `catalogToLayers` omits its members rather than placing them
855
- in an order that would be wrong, and their rows carry no layer cell. An absent name is the report:
856
- compare the returned names against the catalog to find one.
866
+ in an order that would be wrong. It also omits each row whose `lookup` field is `missing` or
867
+ `failed`, because no published version supplied dependencies to place. To distinguish the causes,
868
+ inspect an omitted row's `lookup` field: an omitted `found` row belongs to a cycle, while another
869
+ lookup verdict records why the registry row could not enter a layer.
857
870
 
858
871
  ## Vendored data root
859
872
 
860
873
  The vendored data root is the shared file set, staged into the published package as plain data. It
861
- holds the root instruction documents, the licence, the orchestration contract, the four harness
874
+ holds the root instruction documents, the licence, the orchestration contract, the harness
862
875
  directories, the bench scripts, the shared policy register, the byte-identical root dotfiles, and
863
- the two guide mirrors a generated workspace starts from. `HOST_PATHS` is the candidate list; a plan
876
+ the guide mirrors a generated workspace starts from. `HOST_PATHS` is the candidate list; a plan
864
877
  carries the subset its target selects, because a workspace never mirrors its own guide.
865
878
 
866
879
  `stageHost` fills the root from a real checkout at build time:
@@ -890,14 +903,14 @@ one to one.
890
903
 
891
904
  ## Generated workspace
892
905
 
893
- A workspace's file set is a function of its two axes plus its structural facts. Nothing is fixed
906
+ A workspace's file set is a function of its axes plus its structural facts. Nothing is fixed
894
907
  except the manifest.
895
908
 
896
909
  - One computed artifact: `package.json`, with the entry points, `exports` map, scripts, and
897
910
  development dependencies its selection implies.
898
911
  - One template artifact per configuration file the selection needs: the root `tsconfig.json` and
899
- `vite.config.ts`, plus a Vite config and a scoped TypeScript config per selected environment, and
900
- two more when `bin` is set.
912
+ `vite.config.ts`, plus a Vite config and a scoped TypeScript config per selected environment and
913
+ for `bin` when it is set.
901
914
  - One template artifact, `configs/browsers.ts`, for a workspace selecting `browser` on either axis.
902
915
  It resolves the Chromium the Playwright provider launches, and the root `vite.config.ts` calls it
903
916
  once into `browserOptions` and passes that to every `playwright()` provider it configures. The
@@ -922,9 +935,9 @@ except the manifest.
922
935
  `planToSummary` reports the tally rather than a number written down here:
923
936
 
924
937
  ```ts
925
- import { createBlueprint, createCompiler, planToSummary } from '@orkestrel/scaffold'
938
+ import { Compiler, createBlueprint, planToSummary } from '@orkestrel/scaffold'
926
939
 
927
- const compiler = createCompiler()
940
+ const compiler = new Compiler()
928
941
  const scaffolding = compiler.compile(createBlueprint('router', { src: ['core', 'server'] }))
929
942
  const summary = scaffolding.plan === undefined ? undefined : planToSummary(scaffolding.plan)
930
943
 
@@ -936,7 +949,7 @@ compiler.destroy()
936
949
 
937
950
  ## Library
938
951
 
939
- The two entry points split by host. `@orkestrel/scaffold` is host-independent: it compiles, gates,
952
+ The entry points split by host. `@orkestrel/scaffold` is host-independent: it compiles, gates,
940
953
  and compares, and it touches neither the filesystem nor the network.
941
954
  `@orkestrel/scaffold/server` is Node-only and holds everything that does.
942
955
 
@@ -944,12 +957,12 @@ Compare a plan against bytes a caller already read:
944
957
 
945
958
  ```ts
946
959
  import type { Blueprint, Snapshot } from '@orkestrel/scaffold'
947
- import { createCompiler } from '@orkestrel/scaffold'
960
+ import { Compiler } from '@orkestrel/scaffold'
948
961
 
949
962
  declare const blueprint: Blueprint
950
963
  declare const current: Snapshot
951
964
 
952
- const compiler = createCompiler()
965
+ const compiler = new Compiler()
953
966
  const audit = compiler.audit(blueprint, current)
954
967
 
955
968
  audit.findings.filter(({ drift }) => drift !== 'aligned')
@@ -960,11 +973,11 @@ Write a compiled plan into a real directory:
960
973
 
961
974
  ```ts
962
975
  import type { Plan } from '@orkestrel/scaffold'
963
- import { createMaterializer } from '@orkestrel/scaffold/server'
976
+ import { Materializer } from '@orkestrel/scaffold/server'
964
977
 
965
978
  declare const plan: Plan
966
979
 
967
- const materializer = createMaterializer({ host: './dist/host' })
980
+ const materializer = new Materializer({ host: './dist/host' })
968
981
  const result = materializer.materialize(plan, './packages/router')
969
982
 
970
983
  result.written // every path created
@@ -973,10 +986,11 @@ materializer.destroy()
973
986
 
974
987
  `resolveContainedPath` refuses a lexical escape, a physical link out of the root, and a dangling
975
988
  link whose raw target contains a `..` segment. It returns the lexical join of `root` and `path` — an
976
- absolute path under `root`, which is what its shipped example prints after checking the namespace,
977
- not an open filesystem handle. Its contract therefore excludes a concurrent rename or link swap
978
- during the check or before the caller finishes using that path. A caller that admits hostile
979
- concurrent namespace mutation needs a handle-bound operation instead.
989
+ absolute path under `root` after checking the namespace, not an open filesystem handle. Its
990
+ shipped example tests that answer's suffix rather than printing the whole path. Its contract
991
+ therefore excludes a concurrent rename or link swap during the check or before the caller finishes
992
+ using that path. A caller that admits hostile concurrent namespace mutation needs a handle-bound
993
+ operation instead.
980
994
 
981
995
  `resolveRealPath` answers the caller's own text collapsed lexically, then resolved through every link
982
996
  in what survives that collapse. A `..` the caller wrote cancels the segment before it as text, so
@@ -987,9 +1001,9 @@ way; the answer is a lexical location resolved through links, not a physical one
987
1001
  Read the registry and the guide host:
988
1002
 
989
1003
  ```ts
990
- import { createUpstream } from '@orkestrel/scaffold/server'
1004
+ import { Upstream } from '@orkestrel/scaffold/server'
991
1005
 
992
- const upstream = createUpstream({ registry: { timeout: 5_000 } })
1006
+ const upstream = new Upstream({ registry: { timeout: 5_000 } })
993
1007
  const releases = await upstream.lookup([{ name: '@orkestrel/emitter', range: '^0.0.5' }])
994
1008
 
995
1009
  releases.filter((release) => release.lookup === 'found')
@@ -1002,7 +1016,7 @@ verdict carrying its cause rather than thrown, so one unreachable package never
1002
1016
  rest of the answer. The organization package list is the exception, because without it there is no
1003
1017
  fleet to report.
1004
1018
 
1005
- Both bounds count decoded bytes, and a version lookup asks the registry for the abbreviated
1019
+ Each bound counts decoded bytes, and a version lookup asks the registry for the abbreviated
1006
1020
  packument — `dist-tags` and a trimmed version map, rather than the full per-version metadata no
1007
1021
  verdict reads. That is the smallest form the registry publishes, and `limit` is capped at
1008
1022
  `MAX_ARTIFACT_BYTES`, so a package with enough published releases to pass it cannot be looked up at
@@ -1030,7 +1044,7 @@ try {
1030
1044
  A transaction owns a private root beside the target on the same volume, so every promotion is a
1031
1045
  rename rather than a copy. A failure part way through commit restores every destination it already
1032
1046
  promoted and removes every directory it created. No destination ever receives half-written bytes.
1033
- It is not a journal: a process killed between two promotions leaves a mixed target.
1047
+ It is not a journal: a process killed between promotions leaves a mixed target.
1034
1048
  The transaction binds each directory's location rather than its lifetime, so an ancestor swapped
1035
1049
  for another path, a file, a symlink, or nothing is refused, and one deleted and recreated in place
1036
1050
  may not be.
@@ -1047,11 +1061,12 @@ try {
1047
1061
  }
1048
1062
  ```
1049
1063
 
1050
- `INVALID` is off-contract input, `BLOCKED` is a refused blueprint, `TARGET` is a destination that is
1051
- not what the caller's observation said it was, `WRITE` is a mutation that could not be completed, and
1052
- `FETCH` is an upstream read that produced no answer.
1064
+ `INVALID` is off-contract input, `BLOCKED` is a refused blueprint, `DESTROYED` is a call after an
1065
+ entity's teardown, `TARGET` is a destination that is not what the caller's observation said it was,
1066
+ `WRITE` is a mutation that could not be completed, and `FETCH` is an upstream read that produced no
1067
+ answer.
1053
1068
 
1054
- `BLOCKED` covers both refusals a blueprint can meet, because they are one fact — this blueprint will
1069
+ `BLOCKED` covers every refusal a blueprint can meet, because they are one fact — this blueprint will
1055
1070
  not be built — and the questions say which. The compiler answers its refusal rather than throwing it:
1056
1071
  the gate fails closed and records `BLOCKED` on its stage, so a caller reads that refusal from the
1057
1072
  value it asked for. A verb that creates a workspace throws it, because it chose the shape and has
@@ -1065,18 +1080,20 @@ are thrown, so an observer sees a refusal even where the caller catches it.
1065
1080
 
1066
1081
  ## Limits
1067
1082
 
1068
- Seven things a reader will look for and not find.
1083
+ What a reader will look for and not find.
1069
1084
 
1070
- **A code fence in this guide is unverified.** [`tests/guides.test.ts`](../tests/guides.test.ts)
1071
- proves that every fence imports only real exports of the two barrels, and that every backticked name
1072
- in this file resolves to one. It neither runs a fence nor typechecks one, so a trailing `// value`
1073
- comment inside a fence states what this guide claims rather than what the build answered. The
1074
- verdicts that are measured are the ones a consumer hovers:
1085
+ **Guide parity has a bounded reach.** [`tests/guides.test.ts`](../tests/guides.test.ts) proves that
1086
+ the Surface tables match the core and server barrels in each direction, the method tables match the
1087
+ behavioral declarations, relative links resolve, and named imports in TypeScript fences resolve. It
1088
+ does not resolve arbitrary backticked prose spans or typecheck a whole fence. The same suite
1089
+ executes the transcribed pure examples for blueprint defaults, compile refusal, and error-code
1090
+ narrowing. Other trailing comments remain guide claims rather than build answers. The verdicts that
1091
+ are measured are the ones a consumer hovers:
1075
1092
  [`tests/distribution.test.ts`](../tests/distribution.test.ts) drives every `@example` the built
1076
1093
  declarations print against the installed package, scores each verdict it can read as a value, and
1077
- names exactly the ones it cannot. Fences are not added to that instrument, because most of them
1078
- cannot be run: several declare an ambient value that has no runtime, and several write to a
1079
- directory or read the network, so executing them would be a mutation rather than a check.
1094
+ names exactly the ones it cannot. Executing the remaining fences would require fixtures for each
1095
+ ambient value plus isolated filesystem and network drivers for the mutating examples; adding those
1096
+ drivers is separate test capability rather than name-resolution parity.
1080
1097
 
1081
1098
  **The library does not enforce the creating verb's policy.** `new` refuses a blueprint carrying any
1082
1099
  question, and `materialize` writes any plan into any vacant target. A workspace of several published
@@ -1102,7 +1119,7 @@ host-dependent second one.
1102
1119
  **Scaffold emits no styles axis.** `SRC_MATRIX` is exactly `core`, `browser`, and `server`, and
1103
1120
  `Blueprint` carries no styles field. A workspace that needs `src/styles/` adds the directory, its
1104
1121
  configuration, and its Vitest project by hand. `.claude/rules/workspace.md` describes styles as an
1105
- environment because the fleet has one; scaffold simply does not generate it.
1122
+ environment because the fleet has one; scaffold does not generate it.
1106
1123
 
1107
1124
  **No host path is normalized before it is guarded.** `isFilesystemPath` refuses an empty segment, so
1108
1125
  `packages//router` is off contract. A trailing separator does not produce one: it terminates a
@@ -1123,10 +1140,10 @@ registers `distribution` only when the workspace also publishes `src`. In a publ
1123
1140
  `distribution` and `service` run from `prepublishOnly` and `conformance` stays in `test`. In a
1124
1141
  `private: true` workspace, `distribution` is absent, `service` runs from `test`, and there is no
1125
1142
  `prepublishOnly` at all. Scaffold emits no proof into any registered project, because each names
1126
- something only the package knows: the behavior its own packed artifact must hold once installed, the
1127
- official artifact a conformance check measures against, and the service a live proof drives. A
1128
- generated placeholder would read as a proof while measuring nothing, so the file a consumer writes
1129
- is the file that selects the project.
1143
+ something only the package knows: the behavior its own packed artifact must hold after it is
1144
+ installed, the official artifact a conformance check measures against, and the service a live proof
1145
+ drives. A generated placeholder would read as a proof while measuring nothing, so the file a
1146
+ consumer writes is the file that selects the project.
1130
1147
 
1131
1148
  A distribution proof carries one contract scaffold does enforce from the outside. The generated
1132
1149
  `prepublishOnly` invokes it as `npm run test:distribution -- --mode release`, and a proof that reads
@@ -1144,11 +1161,11 @@ names that module by path, so an absent one fails the project's load rather than
1144
1161
  no suite beneath `tests/service`, so `test:service` reports no test files until the consumer writes
1145
1162
  the first one. Every case is visible the first time the script runs, which is why none is silent.
1146
1163
 
1147
- None of the three folds into `integration`, which measures a different axis rather than a smaller
1164
+ None of those folds into `integration`, which measures a different axis rather than a smaller
1148
1165
  one: the workspace's selected environments compose through their public barrels. The generated seed
1149
1166
  proves only that those barrels load together and expose the initial empty surfaces; the consumer
1150
1167
  replaces it with an observable cross-environment flow. The seed starts no process and does not pack
1151
- or install the workspace, so the project stays in `test`. Two fleet packages hold the distinction.
1168
+ or install the workspace, so the project stays in `test`. These fleet packages hold the distinction.
1152
1169
  `@orkestrel/ollama` drives a real Ollama daemon through a `service` project, so a real service
1153
1170
  answers it and it runs from `prepublishOnly`. `@orkestrel/mcp` measures its server against the
1154
1171
  specification's own runner, `@modelcontextprotocol/conformance`, through a `conformance` project. It pins that runner as a development dependency and resolves it out
@@ -1157,7 +1174,7 @@ port, so the run drives nothing external and stays in `test`.
1157
1174
 
1158
1175
  ## Tests
1159
1176
 
1160
- - [`tests/src/core/Compiler.test.ts`](../tests/src/core/Compiler.test.ts) — the three stages, the
1177
+ - [`tests/src/core/Compiler.test.ts`](../tests/src/core/Compiler.test.ts) — the compile stages, the
1161
1178
  fail-closed rule, off-contract input, and teardown.
1162
1179
  - [`tests/src/core/compilers.test.ts`](../tests/src/core/compilers.test.ts) — every projection from
1163
1180
  a blueprint to an artifact, and every gate law.
@@ -1183,8 +1200,6 @@ port, so the run drives nothing external and stays in `test`.
1183
1200
  digests, inventories, and the staging producer.
1184
1201
  - [`tests/src/server/validators.test.ts`](../tests/src/server/validators.test.ts) — the host-path
1185
1202
  law and every server guard's boundary values.
1186
- - [`tests/src/server/factories.test.ts`](../tests/src/server/factories.test.ts) — the two server
1187
- factories.
1188
1203
  - [`tests/src/bin/CLI.test.ts`](../tests/src/bin/CLI.test.ts) — every verb driven in process
1189
1204
  through recording output handlers.
1190
1205
  - [`tests/src/bin/helpers.test.ts`](../tests/src/bin/helpers.test.ts) — command-line reading, usage
@@ -1192,7 +1207,7 @@ port, so the run drives nothing external and stays in `test`.
1192
1207
  - [`tests/src/bin/main.test.ts`](../tests/src/bin/main.test.ts) — the process entry point.
1193
1208
  - [`tests/policy.test.ts`](../tests/policy.test.ts) — the syntactic coding and placement law over
1194
1209
  every source file.
1195
- - [`tests/guides.test.ts`](../tests/guides.test.ts) — this guide's bijection with the two barrels.
1210
+ - [`tests/guides.test.ts`](../tests/guides.test.ts) — this guide's bijection with the barrels.
1196
1211
 
1197
1212
  ## See also
1198
1213