@orkestrel/scaffold 0.0.22 → 0.0.24

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 (39) hide show
  1. package/README.md +84 -99
  2. package/dist/bin/main.js +1094 -0
  3. package/dist/bin/main.js.map +1 -0
  4. package/dist/host/CLAUDE.md +3 -1
  5. package/dist/host/agents/orchestration.md +61 -4
  6. package/dist/host/agents/skills/orkestrel-align-packages/SKILL.md +1 -1
  7. package/dist/host/agents/skills/orkestrel-falsify/SKILL.md +7 -5
  8. package/dist/host/agents/skills/orkestrel-harden-package/SKILL.md +1 -1
  9. package/dist/host/agents/skills/orkestrel-harden-package/references/contract.md +1 -1
  10. package/dist/host/claude/agents/orkestrel.md +9 -9
  11. package/dist/host/claude/rules/architecture.md +45 -3
  12. package/dist/host/claude/rules/quality.md +4 -0
  13. package/dist/host/claude/rules/tests.md +57 -1
  14. package/dist/host/claude/rules/workspace.md +50 -17
  15. package/dist/host/codex/agents/orkestrel.toml +1 -1
  16. package/dist/host/configs/helpers.ts +762 -0
  17. package/dist/host/dotfiles/oxlintrc.json +2 -1
  18. package/dist/host/guides/scaffold.md +862 -0
  19. package/dist/host/manifest.json +40 -33
  20. package/dist/host/tests/config.test.ts +544 -0
  21. package/dist/host/tests/policy.test.ts +46 -0
  22. package/dist/host/tests/setupPolicy.ts +557 -602
  23. package/dist/src/core/index.cjs +3569 -10510
  24. package/dist/src/core/index.cjs.map +1 -1
  25. package/dist/src/core/index.d.cts +2361 -2789
  26. package/dist/src/core/index.d.ts +2361 -2789
  27. package/dist/src/core/index.js +3513 -10374
  28. package/dist/src/core/index.js.map +1 -1
  29. package/dist/src/server/index.cjs +2855 -3765
  30. package/dist/src/server/index.cjs.map +1 -1
  31. package/dist/src/server/index.d.cts +1920 -1335
  32. package/dist/src/server/index.d.ts +1920 -1335
  33. package/dist/src/server/index.js +2812 -3680
  34. package/dist/src/server/index.js.map +1 -1
  35. package/package.json +16 -23
  36. package/dist/bin/scaffold.js +0 -1896
  37. package/dist/bin/scaffold.js.map +0 -1
  38. package/dist/host/guides/src/scaffold.md +0 -2886
  39. /package/dist/host/guides/{src/guide.md → guide.md} +0 -0
@@ -0,0 +1,862 @@
1
+ # Scaffold
2
+
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
5
+ > entry points: `@orkestrel/scaffold` is the pure compiler and its data contracts, and
6
+ > `@orkestrel/scaffold/server` is the filesystem writer and the network reader. Source:
7
+ > [`src/core/index.ts`](../src/core/index.ts) and [`src/server/index.ts`](../src/server/index.ts).
8
+
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
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
13
+ write the difference back.
14
+
15
+ ```sh
16
+ npm install --save-dev @orkestrel/scaffold
17
+ ```
18
+
19
+ The executable needs Node 22.12 or newer. Run it through `npx` without installing:
20
+
21
+ ```sh
22
+ npx @orkestrel/scaffold --help
23
+ ```
24
+
25
+ ## Surface
26
+
27
+ ### Core
28
+
29
+ Exported from `@orkestrel/scaffold`, and reachable from
30
+ [`src/core/index.ts`](../src/core/index.ts).
31
+
32
+ #### Types
33
+
34
+ | Name | Kind | Summary |
35
+ | ------------------- | ---- | ------------------------------------------------------------------------------------------------ |
36
+ | `Artifact` | type | One file in a plan, discriminated by how its content is produced and what scaffold claims of it. |
37
+ | `BuildFormat` | type | One module format a published library environment builds. |
38
+ | `CatalogEntry` | type | One package row of the fleet catalog. |
39
+ | `CompileStage` | type | The three compile phases, in the order they run. |
40
+ | `CompilerEventMap` | type | The compiler's observation channel. |
41
+ | `Drift` | type | How one target path compares to the artifact planned for it. |
42
+ | `Environment` | type | One environment a generated workspace selects on its `src` or `app` axis. |
43
+ | `Finding` | type | One drift verdict against a target path. |
44
+ | `Group` | type | The artifact group a plan selects over. |
45
+ | `Lookup` | type | Whether an upstream lookup produced an answer. |
46
+ | `Mirror` | type | One dependency guide fetched from upstream, beside the local mirror it answers for. |
47
+ | `Origin` | type | How an artifact's content is produced. |
48
+ | `Ownership` | type | What scaffold claims at an artifact's path. |
49
+ | `Release` | type | One declared dependency range measured against the registry's latest release. |
50
+ | `ScaffoldErrorCode` | type | The coded reasons a scaffold error is raised. |
51
+ | `Snapshot` | type | Exact lowercase hexadecimal target bytes keyed by artifact-relative path. |
52
+
53
+ #### Interfaces
54
+
55
+ | Name | Kind | Summary |
56
+ | ------------------- | --------- | --------------------------------------------------------------------------------------- |
57
+ | `AppDefinition` | interface | The configuration and runtime-entry settings one private `app` environment contributes. |
58
+ | `ArtifactBase` | interface | The fields every planned file carries. |
59
+ | `Audit` | interface | The whole comparison of a plan against a target's current content. |
60
+ | `Blueprint` | interface | The closed, JSON-serializable workspace specification. |
61
+ | `CompileFailure` | interface | The coded reason one compile stage failed. |
62
+ | `CompileRecord` | interface | The input and output snapshot of one compile stage. |
63
+ | `CompilerInterface` | interface | The compilation contract: pure, synchronous, and host-independent. |
64
+ | `CompilerOptions` | interface | Options for the compiler. |
65
+ | `ContentArtifact` | interface | A text file produced by the template or computed compilation path. |
66
+ | `Dependency` | interface | One runtime `@orkestrel/*` dependency of a generated workspace. |
67
+ | `HostArtifact` | interface | A file byte-copied from the vendored data root, planned before its bytes are read. |
68
+ | `HydratedArtifact` | interface | A vendored file whose exact bytes have been read, so its content can be compared. |
69
+ | `Override` | interface | One artifact override. |
70
+ | `PlanSummary` | interface | The tally of one plan by artifact origin. |
71
+ | `Plan` | interface | The compiled, ordered artifact list and the selection it covers. |
72
+ | `Question` | interface | One validation issue raised against a blueprint or a plan. |
73
+ | `Scaffolding` | interface | The replayable outcome of one compile. |
74
+ | `SrcDefinition` | interface | The build and export settings one published `src` environment contributes. |
75
+ | `ViteMachinery` | interface | Which host-specific pipelines a generated root Vite configuration carries. |
76
+
77
+ #### Constants
78
+
79
+ | Name | Kind | Summary |
80
+ | --------------------------------- | ----- | ------------------------------------------------------------------------------------------------ |
81
+ | `APP_BROWSER_DEV_DEPENDENCIES` | const | The development dependencies a private Vue browser application adds. |
82
+ | `APP_DEV_DEPENDENCIES` | const | The development dependency every private `app` environment adds. |
83
+ | `APP_MATRIX` | const | The configuration and runtime-entry settings each private `app` environment contributes, frozen. |
84
+ | `APP_SERVER_DEV_DEPENDENCIES` | const | The development dependencies a private server application adds. |
85
+ | `ARTIFACT_TEMPLATES` | const | Formatter-stable template text for source, test, document, guide, and service artifacts. |
86
+ | `BASE_DEV_DEPENDENCIES` | const | The tooling versions scaffold and every generated workspace share. |
87
+ | `BIN_CONFIGS` | const | The configuration files a workspace that ships its own executable adds, frozen. |
88
+ | `BIN_ENTRY_PATH` | const | The executable entry whose presence makes a workspace `bin`. |
89
+ | `CATALOG_AGENT_PATH` | const | The agent file whose marker-bounded package table the catalog verb alone owns. |
90
+ | `CONFIG_TEMPLATES` | const | Formatter-stable template text for every configuration artifact. |
91
+ | `CONTROL_CHARACTER_PATTERN` | const | Unicode controls, formatting controls, and line and paragraph separators rejected in text. |
92
+ | `DEFAULT_ENGINES` | const | The `engines.node` range a workspace starts with. |
93
+ | `DEFAULT_VERSION` | const | The version a workspace starts at. |
94
+ | `DEPENDENCY_NAME_PATTERN` | const | The runtime dependency name syntax: the `@orkestrel` scope and a bare name. |
95
+ | `ENGINES_PATTERN` | const | The minimum-Node engine syntax a blueprint declares. |
96
+ | `ENVIRONMENTS` | const | The three `Environment` values, frozen. |
97
+ | `EXTRA_NAME_PATTERN` | const | The development extra name syntax: any valid npm package name. |
98
+ | `EXTRA_RANGE_PATTERN` | const | The registry-only semver subset accepted for a development extra's range. |
99
+ | `GLOBAL_SETUP_PATH` | const | The shared Vitest global-setup module whose presence makes a workspace `global`. |
100
+ | `GUIDES_TEST_PATH` | const | The guide-parity proof whose physical file selects the fixed `guides` project. |
101
+ | `GROUPS` | const | The seven `Group` values in plan order, frozen. |
102
+ | `HEX_PATTERN` | const | Exact lowercase hexadecimal bytes: two digits per byte, and empty content is valid. |
103
+ | `HOST_PATHS` | const | The paths byte-copied from the vendored data root, frozen. |
104
+ | `INTEGRATION_TEST_PATH` | const | The installed-package proof whose presence makes a workspace `integration`. |
105
+ | `INVALID_PATH_CHARACTER_PATTERN` | const | Visible characters a portable path and a Markdown path cell both forbid. |
106
+ | `MAX_ARTIFACT_BYTES` | const | Maximum bytes accepted for one artifact. |
107
+ | `MAX_ARTIFACT_HEX_LENGTH` | const | Maximum length of the hexadecimal string carrying one artifact's bytes. |
108
+ | `MAX_COLLECTION_ITEMS` | const | Maximum items accepted in one public collection. |
109
+ | `MAX_DEPENDENCY_NAME_LENGTH` | const | Maximum dependency package name length, scope included, as the registry caps it. |
110
+ | `MAX_MANIFEST_BYTES` | const | Maximum bytes accepted for one package or vendored-host manifest. |
111
+ | `MAX_NAME_LENGTH` | const | Maximum bare workspace name length. |
112
+ | `MAX_PATH_LENGTH` | const | Maximum length of one path, matching the longest a supported filesystem accepts. |
113
+ | `MAX_RANGE_LENGTH` | const | Maximum length of one declared package range. |
114
+ | `MAX_TOTAL_ARTIFACT_BYTES` | const | Maximum bytes retained across one whole plan or audit. |
115
+ | `MINIMUM_NODE_VERSION` | const | The oldest Node version the generated toolchain supports. |
116
+ | `NAME_PATTERN` | const | The bare workspace name syntax: lowercase alphanumeric with hyphens, letter first. |
117
+ | `ORCHESTRATION_PATH_NAMES` | const | The exact root filenames that wire an agent bench rather than the toolchain, frozen. |
118
+ | `ORCHESTRATION_PATH_PREFIXES` | const | The path prefixes whose contents instruct or wire an agent, frozen. |
119
+ | `ORKESTREL_RANGE_PATTERN` | const | The exact caret-pinned pre-1.0 range accepted for an `@orkestrel/*` runtime dependency. |
120
+ | `SERVICE_SCRIPT_PATH` | const | The provisioner skeleton a workspace with declared service vendors is given once. |
121
+ | `SHOWCASE_CONFIG_PATH` | const | The Vite wrapper whose presence makes a workspace `showcase`. |
122
+ | `SHOWCASE_DEV_DEPENDENCIES` | const | The development dependency used only by the optional single-file showcase build. |
123
+ | `SOURCE_BROWSER_DEV_DEPENDENCIES` | const | The development dependencies a published browser `src` environment adds. |
124
+ | `SRC_MATRIX` | const | The build and export settings each published `src` environment contributes, frozen. |
125
+ | `VERSION_PATTERN` | const | The exact three-component version syntax a blueprint declares. |
126
+
127
+ #### Guards
128
+
129
+ | Name | Kind | Summary |
130
+ | ------------------- | -------- | -------------------------------------------------------------------------------- |
131
+ | `isArtifact` | const | Narrow a value to an `Artifact`. |
132
+ | `isAudit` | const | Narrow a value to an `Audit`. |
133
+ | `isBlueprint` | const | Narrow a value to a `Blueprint`. |
134
+ | `isCatalogEntry` | const | Narrow a value to a `CatalogEntry`. |
135
+ | `isCollection` | function | Narrow a value to an array within the limit one public collection accepts. |
136
+ | `isCompilerHooks` | const | Narrow a value to the compiler's initial listener record. |
137
+ | `isCompilerOptions` | const | Narrow a value to `CompilerOptions`. |
138
+ | `isContent` | const | Narrow a value to text this package will accept as one artifact's content. |
139
+ | `isDependencyName` | const | Narrow a value to the scoped package name a runtime dependency carries. |
140
+ | `isDependency` | const | Narrow a value to a `Dependency`. |
141
+ | `isEnvironment` | const | Narrow a value to one `Environment` a workspace may select. |
142
+ | `isFinding` | const | Narrow a value to a `Finding`. |
143
+ | `isGroup` | const | Narrow a value to one `Group` a plan selects over. |
144
+ | `isGroups` | const | Narrow a value to a bounded group selection. |
145
+ | `isHex` | const | Narrow a value to exact lowercase hexadecimal bytes within one artifact's limit. |
146
+ | `isMirror` | const | Narrow a value to a `Mirror`. |
147
+ | `isOverride` | const | Narrow a value to an `Override`. |
148
+ | `isPath` | function | Narrow a value to a portable target-relative path. |
149
+ | `isPlan` | const | Narrow a value to a `Plan`. |
150
+ | `isQuestion` | const | Narrow a value to a `Question`. |
151
+ | `isScaffoldError` | function | Narrow a caught value to a `ScaffoldError`. |
152
+ | `isSnapshot` | function | Narrow a value to a `Snapshot`. |
153
+
154
+ #### Parsers
155
+
156
+ | Name | Kind | Summary |
157
+ | ---------------------- | -------- | ----------------------------------------------- |
158
+ | `parseBlueprint` | function | Coerce an untrusted value to a `Blueprint`. |
159
+ | `parseCompilerOptions` | function | Coerce an untrusted value to `CompilerOptions`. |
160
+ | `parseGroups` | function | Coerce an untrusted value to a group selection. |
161
+ | `parseSnapshot` | function | Coerce an untrusted value to a `Snapshot`. |
162
+
163
+ #### Helpers
164
+
165
+ | Name | Kind | Summary |
166
+ | --------------------------- | -------- | ----------------------------------------------------------------------------- |
167
+ | `artifactToHex` | function | Project an artifact to the exact bytes it claims, as hexadecimal. |
168
+ | `bytesToHex` | function | Encode bytes as exact lowercase hexadecimal text. |
169
+ | `cloneValue` | function | Snapshot an untrusted value into exact JSON data the caller owns. |
170
+ | `compareVersions` | function | Compare two versions by their numeric components. |
171
+ | `computeBytes` | function | Count the UTF-8 bytes text encodes to. |
172
+ | `computeHash` | function | Compute the deterministic content identity of text. |
173
+ | `contentToHex` | function | Encode text as the exact lowercase hexadecimal form of its UTF-8 bytes. |
174
+ | `extractVersion` | function | Extract the three numeric components of an exact version. |
175
+ | `inferDrift` | function | Infer how one target path compares to the artifact planned for it. |
176
+ | `inferGroup` | function | Infer the `Group` a path belongs to. |
177
+ | `manifestToDependencies` | function | Project a package manifest's text to the `@orkestrel/*` packages it declares. |
178
+ | `manifestToName` | function | Project a package manifest's text to its own name. |
179
+ | `matchesEngines` | function | Test whether a declared engines floor is at or above the supported minimum. |
180
+ | `matchesOrchestrationPath` | function | Test whether a path instructs or wires an agent rather than the toolchain. |
181
+ | `matchesRange` | function | Test whether a declared range already admits a published version. |
182
+ | `nameToGuide` | function | Derive the guide mirror path a package name answers for. |
183
+ | `planToSummary` | function | Project a plan into its tally by artifact origin. |
184
+ | `selectGroups` | function | Select the groups a compile covers, in plan order. |
185
+ | `selectHostPaths` | function | Select the host paths a named workspace vendors. |
186
+ | `serializeTypeScriptString` | function | Serialize one string as a single-quoted TypeScript literal. |
187
+
188
+ #### Compilers
189
+
190
+ | Name | Kind | Summary |
191
+ | ----------------------------------- | -------- | ------------------------------------------------------------------------------- |
192
+ | `applyOverrides` | function | Replace the content of every drafted artifact an override names. |
193
+ | `artifactToFinding` | function | Project one planned artifact and the bytes found at its path into a verdict. |
194
+ | `artifactsToQuestions` | function | Measure a drafted artifact list against the laws a whole plan decides. |
195
+ | `blueprintToConfigArtifacts` | function | Compile every artifact in the `configs` group. |
196
+ | `blueprintToDevDependencies` | function | Project a blueprint into the development dependencies its manifest declares. |
197
+ | `blueprintToDocumentArtifacts` | function | Compile the generated workspace's root documentation. |
198
+ | `blueprintToGuideArtifacts` | function | Compile the generated workspace's guide index. |
199
+ | `blueprintToMachinery` | function | Derive the host-specific machinery a generated root Vite configuration carries. |
200
+ | `blueprintToManifest` | function | Compile a blueprint into its `package.json` content. |
201
+ | `blueprintToOrchestrationArtifacts` | function | Compile the blueprint-dependent orchestration artifacts. |
202
+ | `blueprintToQuestions` | function | Measure a blueprint against every law its own fields decide. |
203
+ | `blueprintToRootTsconfig` | function | Compile the root TypeScript configuration for a blueprint. |
204
+ | `blueprintToRootVite` | function | Compile the root Vite and Vitest configuration for a blueprint. |
205
+ | `blueprintToScripts` | function | Project a blueprint into the scripts its manifest declares. |
206
+ | `blueprintToSourceArtifacts` | function | Compile every artifact in the `source` group. |
207
+ | `blueprintToTestArtifacts` | function | Compile every artifact in the `tests` group that is not vendored from the host. |
208
+ | `dependenciesToQuestions` | function | Measure one declared package list against the name and range syntax it accepts. |
209
+ | `nameToHostArtifacts` | function | Compile the vendored host artifacts a named workspace plans. |
210
+ | `overridesToQuestions` | function | Measure a blueprint's overrides against the artifacts drafted for it. |
211
+ | `pathToCondition` | function | Build one `exports` condition block for a built environment. |
212
+ | `planToFindings` | function | Compare a plan against a target's current content. |
213
+ | `planToHash` | function | Compute a plan's content identity. |
214
+ | `srcToEntry` | function | Project a published selection into the manifest's entry fields. |
215
+ | `srcToExports` | function | Project a published selection into the manifest's `exports` map. |
216
+ | `srcToRoot` | function | Select the single published environment a package root points at. |
217
+
218
+ #### Factories
219
+
220
+ | Name | Kind | Summary |
221
+ | ----------------- | -------- | --------------------------------------------------------------------------------- |
222
+ | `createBlueprint` | function | Construct a `Blueprint` from a name and the fields that differ from the defaults. |
223
+ | `createCompiler` | function | Construct a `Compiler`. |
224
+
225
+ #### Classes
226
+
227
+ | Name | Kind | Summary |
228
+ | --------------- | ----- | --------------------------------------------------------------------------- |
229
+ | `Compiler` | class | The compile spine: draft, gate, pin, run in that order over a blueprint. |
230
+ | `ScaffoldError` | class | The one error this package throws, carrying the coded reason it was raised. |
231
+
232
+ ### Server
233
+
234
+ Exported from `@orkestrel/scaffold/server`, and reachable from
235
+ [`src/server/index.ts`](../src/server/index.ts).
236
+
237
+ #### Types
238
+
239
+ | Name | Kind | Summary |
240
+ | ---------------------- | ---- | ------------------------------------------ |
241
+ | `MaterializerEventMap` | type | The materializer's observation channel. |
242
+ | `UpstreamEventMap` | type | The upstream reader's observation channel. |
243
+
244
+ #### Interfaces
245
+
246
+ | Name | Kind | Summary |
247
+ | ----------------------- | --------- | ------------------------------------------------------------------------------------ |
248
+ | `HostManifest` | interface | The complete vendored-host inventory. |
249
+ | `ManifestEntry` | interface | One file record of the vendored host's manifest. |
250
+ | `MaterializeResult` | interface | The outcome of one mutation of a target. |
251
+ | `MaterializerInterface` | interface | The mutation contract: the package's only filesystem writer. |
252
+ | `MaterializerOptions` | interface | Options for the materializer. |
253
+ | `Repository` | interface | What git reports about a target's working tree. |
254
+ | `UpstreamInterface` | interface | The upstream contract: the package's only network reader, and it never writes. |
255
+ | `UpstreamOptions` | interface | Options for the upstream reader. |
256
+ | `WriteAnchor` | interface | One physical directory identity captured across a write transaction. |
257
+ | `WriteDirectoryResult` | interface | The final directory anchor of a write transaction and the subset one call created. |
258
+ | `WriteExpectation` | interface | One destination snapshot captured before a write and required to survive it. |
259
+ | `WritePrecondition` | interface | The narrower caller-observed destination state a write transaction must still match. |
260
+
261
+ #### Constants
262
+
263
+ | Name | Kind | Summary |
264
+ | ----------------------------------- | ----- | ---------------------------------------------------------------------------------------- |
265
+ | `BRANCH_PATTERN` | const | The Git branch syntax the guide endpoint accepts. |
266
+ | `DIGEST_PATTERN` | const | The exact SHA-256 syntax a digest is stated in: sixty-four lowercase hexadecimal digits. |
267
+ | `DRIVE_PATTERN` | const | The drive prefix a Windows host path may open with. |
268
+ | `INVALID_SEGMENT_CHARACTER_PATTERN` | const | Visible characters no host path segment may carry. |
269
+ | `MANIFEST_NAME` | const | The reserved metadata name a staged vendored host writes at its own root. |
270
+ | `MAX_BRANCH_LENGTH` | const | Maximum characters one guide branch may carry. |
271
+ | `MAX_ENDPOINT_LENGTH` | const | Maximum characters one caller-supplied upstream endpoint may carry. |
272
+ | `MAX_INVENTORY_PATHS` | const | Maximum paths one target's working-tree inventory may report. |
273
+ | `MAX_PATH_DEPTH` | const | Maximum segments one host path may carry. |
274
+ | `MAX_PATH_SEGMENT_BYTES` | const | Maximum UTF-8 bytes one host path segment may encode to. |
275
+ | `MAX_UPSTREAM_CONCURRENCY` | const | Maximum simultaneous upstream requests. |
276
+ | `MAX_UPSTREAM_RETRIES` | const | Maximum retries one upstream request may be given after a transport fault. |
277
+ | `MAX_UPSTREAM_TIMEOUT` | const | Maximum timeout one upstream request may be given, in milliseconds. |
278
+ | `RESERVED_SEGMENT_PATTERN` | const | The Windows device names that stay reserved even when an extension follows. |
279
+
280
+ #### Guards
281
+
282
+ | Name | Kind | Summary |
283
+ | ----------------------- | -------- | ---------------------------------------------------------------------------------- |
284
+ | `isBranch` | const | Narrow a value to a Git branch the guide endpoint accepts. |
285
+ | `isCatalogEntries` | const | Narrow a value to a bounded list of fleet catalog rows. |
286
+ | `isDependencies` | const | Narrow a value to a bounded list of declared runtime dependencies. |
287
+ | `isDependencyNames` | const | Narrow a value to a bounded list of `@orkestrel` package names. |
288
+ | `isDigest` | const | Narrow a value to one exact SHA-256 digest. |
289
+ | `isEndpoint` | const | Narrow a value to a bounded upstream endpoint. |
290
+ | `isFilesystemPath` | function | Narrow a value to a path naming a location on this host. |
291
+ | `isHostManifest` | const | Narrow a value to one `HostManifest`. |
292
+ | `isInventory` | function | Narrow a value to a working-tree inventory within the limit one target may report. |
293
+ | `isManifestEntry` | const | Narrow a value to one `ManifestEntry`. |
294
+ | `isMaterializerHooks` | const | Narrow a value to the materializer's initial listener record. |
295
+ | `isMaterializerOptions` | const | Narrow a value to `MaterializerOptions`. |
296
+ | `isMirrors` | const | Narrow a value to a bounded list of fetched guide mirrors. |
297
+ | `isRepository` | const | Narrow a value to a `Repository`. |
298
+ | `isTimeout` | const | Narrow a value to a per-request timeout in milliseconds. |
299
+ | `isUpstreamHooks` | const | Narrow a value to the upstream reader's initial listener record. |
300
+ | `isUpstreamOptions` | const | Narrow a value to `UpstreamOptions`. |
301
+
302
+ #### Helpers
303
+
304
+ | Name | Kind | Summary |
305
+ | ----------------------- | -------- | ------------------------------------------------------------------------------------- |
306
+ | `computeDigest` | function | Compute the SHA-256 digest of text. |
307
+ | `computeFileDigest` | function | Compute the SHA-256 digest of one file's exact bytes. |
308
+ | `computeManifestDigest` | function | Compute the digest of a vendored host's declared membership. |
309
+ | `isExactCaseFile` | function | Test whether a physical file's path matches every on-disk segment exactly. |
310
+ | `isPhysicalDirectory` | function | Test whether a path is a physical directory this package will read or write into. |
311
+ | `isPhysicalFile` | function | Test whether a path is a physical file this package will read or replace. |
312
+ | `isVacant` | function | Test whether a target is safe to write a fresh workspace into. |
313
+ | `listDirectories` | function | List a directory's descendant directories as sorted root-relative paths. |
314
+ | `listFiles` | function | List a directory's files as sorted root-relative paths. |
315
+ | `matchesAnchor` | function | Test whether a captured directory is still the same directory. |
316
+ | `matchesExpectation` | function | Test whether a destination still holds what was captured of it. |
317
+ | `matchesGitPath` | function | Test whether a path addresses a target's own repository metadata. |
318
+ | `matchesMissingPath` | function | Test whether a caught filesystem error reports an absent path. |
319
+ | `matchesPrecondition` | function | Test whether a destination still matches the narrower state a caller observed. |
320
+ | `matchesProtectedPath` | function | Test whether a target-relative path is one no verb may delete. |
321
+ | `matchesSensitivePath` | function | Test whether a path names local configuration or a credential. |
322
+ | `pathToStorage` | function | Project a target-relative path to the storage name a vendored host holds it under. |
323
+ | `readAnchor` | function | Capture one directory's physical identity. |
324
+ | `readExpectation` | function | Capture what one destination holds before a write. |
325
+ | `readFileHex` | function | Read one contained file as its exact bytes in lowercase hexadecimal. |
326
+ | `readFileText` | function | Read one contained file as bounded UTF-8 text. |
327
+ | `readHostManifest` | function | Read a vendored host's manifest, when it carries one. |
328
+ | `readManifestEntry` | function | Derive one vendored-host manifest entry from a file in a checkout. |
329
+ | `readSnapshot` | function | Read a target's current bytes at the paths a plan claims. |
330
+ | `resolveContainedPath` | function | Resolve a root-relative path and refuse one that leaves its root. |
331
+ | `resolveRealPath` | function | Resolve a path through the real filesystem, keeping the part that does not exist yet. |
332
+ | `stageHost` | function | Stage a vendored host root from a real checkout. |
333
+
334
+ #### Factories
335
+
336
+ | Name | Kind | Summary |
337
+ | -------------------- | -------- | --------------------------- |
338
+ | `createMaterializer` | function | Construct a `Materializer`. |
339
+ | `createUpstream` | function | Construct an `Upstream`. |
340
+
341
+ #### Classes
342
+
343
+ | Name | Kind | Summary |
344
+ | ------------------ | ----- | ---------------------------------------------------------------------------------- |
345
+ | `Materializer` | class | The mutation spine: read the vendored host, re-derive the target, stage, swap. |
346
+ | `Upstream` | class | The reading spine: one bounded, unauthenticated, redirect-free request per answer. |
347
+ | `WriteTransaction` | class | One staged, reversible mutation of one target directory. |
348
+
349
+ ## Methods
350
+
351
+ `Compiler` implements `CompilerInterface`, `Materializer` implements `MaterializerInterface`, and
352
+ `Upstream` implements `UpstreamInterface`. Each class exposes exactly its interface's members and
353
+ nothing more, so the interface tables below describe the classes too. `WriteTransaction` publishes
354
+ no interface and is documented directly.
355
+
356
+ #### `CompilerInterface`
357
+
358
+ | Method | Summary |
359
+ | --------- | ---------------------------------------------------------------------------- |
360
+ | `compile` | Compile a blueprint into a plan through the draft, gate, and pin stages. |
361
+ | `audit` | Compile a blueprint and compare its plan to a target's current content. |
362
+ | `destroy` | Tear the compiler down. Every later call throws, and teardown is idempotent. |
363
+
364
+ #### `MaterializerInterface`
365
+
366
+ | Method | Summary |
367
+ | ------------- | -------------------------------------------------------------------------------- |
368
+ | `audit` | Compare a plan with a target through the vendored host that will repair it. |
369
+ | `materialize` | Write a plan into a vacant target. |
370
+ | `repair` | Write a plan into an existing target, guided by an audit of it. |
371
+ | `mirror` | Write fetched dependency guides to their local mirrors. |
372
+ | `catalog` | Rewrite the marker-bounded package table in the target's catalog agent file. |
373
+ | `declare` | Rewrite the `@orkestrel/*` range set in the target's manifest. |
374
+ | `remove` | Delete the files the plan does not own. |
375
+ | `destroy` | Tear the materializer down. Every later call throws, and teardown is idempotent. |
376
+
377
+ #### `UpstreamInterface`
378
+
379
+ | Method | Summary |
380
+ | --------- | -------------------------------------------------------------------------- |
381
+ | `lookup` | Look up the registry's latest release for each declared dependency. |
382
+ | `fetch` | Fetch each named package's guide, beside the local mirror it answers for. |
383
+ | `catalog` | Catalog the published fleet from the registry's organization package list. |
384
+ | `destroy` | Tear the reader down, aborting every request in flight. |
385
+
386
+ #### `WriteTransaction`
387
+
388
+ | Method | Summary |
389
+ | ----------- | ---------------------------------------------------------------------------------- |
390
+ | `write` | Stage one text file. |
391
+ | `copy` | Stage one byte-for-byte copy of a file that already exists on this host. |
392
+ | `directory` | Establish one directory inside the target, one segment at a time. |
393
+ | `remove` | Mark one file for deletion at commit. |
394
+ | `commit` | Promote every staged file and take every marked file, or roll the whole call back. |
395
+ | `discard` | Abandon the transaction and remove everything it created. |
396
+
397
+ ## Command line
398
+
399
+ Five verbs. Authority is the verb's: every verb except `audit` writes when it is typed, and no
400
+ option grants a write.
401
+
402
+ | Verb | Writes |
403
+ | ----------- | --------------------------------------------------------------------------------- |
404
+ | `new` | A whole workspace, into a target that holds nothing the plan would collide with |
405
+ | `audit` | Nothing |
406
+ | `repair` | Each planned path the target is missing or has let drift |
407
+ | `catalog` | The package table and the guide mirrors |
408
+ | `overwrite` | Everything `repair` and `catalog` write, plus deletions and the dependency ranges |
409
+
410
+ `scaffold --help` prints the whole reference:
411
+
412
+ ```text
413
+ scaffold <verb> [options]
414
+
415
+ scaffold new <name> [--src <list>] [--app <list>] [--bin] [--deps <list>] [--from <path>] [--target <path>] [--json]
416
+ scaffold a workspace
417
+ scaffold audit [--groups <list>] [--from <path>] [--target <path>] [--json]
418
+ report how the target compares to its plan, writing nothing
419
+ scaffold repair [--groups <list>] [--from <path>] [--target <path>] [--json]
420
+ write each planned path the target is missing or has let drift
421
+ scaffold catalog [--all] [--from <path>] [--target <path>] [--json]
422
+ regenerate the package table and refresh the guide mirrors
423
+ scaffold overwrite [--groups <list>] [--dirty] [--from <path>] [--target <path>] [--json]
424
+ do everything repair and catalog do, then delete what the plan does not own and re-declare the dependency ranges
425
+
426
+ options
427
+ --src <list> the published library environments to build: core, browser, server
428
+ --app <list> the private application environments to build: core, browser, server
429
+ --bin scaffold a command-line executable at src/bin/main.ts
430
+ --deps <list> the @orkestrel/* packages the workspace depends on
431
+ --groups <list> the artifact groups to cover; every group when absent
432
+ --all fetch a guide for every package the organization publishes, not just the declared ones
433
+ --dirty delete from a tree carrying uncommitted changes
434
+ --from <path> read the data root from a local path instead of the bundled one; catalog alone accepts it more than once
435
+ --target <path> the directory the verb operates on; the working directory when absent
436
+ --json emit one machine-readable value instead of a report
437
+
438
+ exit codes
439
+ 0 clean
440
+ 1 drift or failure
441
+ 2 usage error
442
+ ```
443
+
444
+ An option a verb does not list is refused by name rather than parsed and ignored. `--deps` reaches
445
+ the registry, so `new` fails when the registry names no release for a package it was given: the
446
+ workspace would otherwise declare a dependency that does not resolve.
447
+
448
+ `new --bin` creates the executable entry, its test, and its scoped Vite and TypeScript wrappers.
449
+ The other structural facts do not need creation flags. Add `tests/integration.test.ts` for
450
+ `integration`, `tests/setupGlobal.ts` for `global`, and
451
+ `configs/app/vite.showcase.config.ts` for `showcase`; reading verbs detect each exact-case file and
452
+ register its fixed machinery. Add `scripts/service.sh` for `services`. Reading verbs preserve and
453
+ protect that birth-owned script, but do not infer its service list from edited text.
454
+
455
+ ### Reading a target
456
+
457
+ `audit`, `repair`, `catalog`, and `overwrite` derive the blueprint from the target itself. The name
458
+ and the declared `@orkestrel/*` packages come from `package.json`. The two environment axes come
459
+ from the directories the target actually ships, because a directory is the fact and a declaration
460
+ beside it could disagree. Four more facts come from exact-case files: `src/bin/main.ts` selects
461
+ `bin`, `tests/integration.test.ts` selects `integration`, `tests/setupGlobal.ts` selects `global`,
462
+ and `configs/app/vite.showcase.config.ts` selects `showcase`. A containing directory does not select
463
+ the fact by itself.
464
+
465
+ `services` is not reconstructed. Its only artifact, `scripts/service.sh`, is birth-owned, so edited
466
+ script text is not a trustworthy declaration of a service list. A present script remains in the
467
+ target and remains protected from deletion through the owned scripts inventory, but a reading verb
468
+ does not infer services from it.
469
+
470
+ The root Vite configuration always defines the fixed `guides` project and selects it at
471
+ configuration load only when `tests/guides.test.ts` is a physical file with that exact path case. A
472
+ directory or a case-folded spelling does not select it. A fresh workspace therefore carries no
473
+ guides project. A workspace that later adds the proof keeps the project through `repair`. The
474
+ birth-owned manifest still gains no `test:guides` script automatically; the developer who adds the
475
+ proof adds that script too.
476
+
477
+ The three plan-reading verbs compare every Vitest project named by the target manifest's scripts
478
+ with the project set the planned root configuration will load. One shell-token pass reads quoted and
479
+ unquoted `--project value` and `--project=value` forms. A shell expansion or malformed quote that
480
+ prevents a project value from being resolved statically produces a question instead of licensing a
481
+ write. The classifier is deliberately bounded to manifest script text that names `vitest`; an
482
+ external wrapper whose name does not identify its runner supplies no static Vitest fact to infer.
483
+
484
+ `audit` still completes the comparison and reports one non-blocking `projects` question. For a
485
+ literal absent project, its advisory tells the developer to register the project or remove the
486
+ script. `repair` and `overwrite` refuse instead. Their refusal tells the developer to remove the
487
+ script or not use scaffold writing verbs for a workspace that needs custom Vitest projects. It does
488
+ not recommend editing the content-owned configuration that the refusing verb would restore. An
489
+ advisory alone does not make an aligned target drift.
490
+
491
+ ### Exit codes
492
+
493
+ `0` means the target matched its plan and every step completed. `1` means the target drifted or a
494
+ step failed. `2` means the command line was not a command. A foreign file counts as drift: the
495
+ target holds something the plan does not own, whether or not the verb that found it was allowed to
496
+ remove it.
497
+
498
+ ### Git
499
+
500
+ `overwrite` is the only verb that reads git, and it needs a repository. It asks git for the tracked
501
+ set and the dirty set, deletes only tracked paths, and refuses a tree carrying uncommitted changes
502
+ unless `--dirty` waives that refusal. A target that is not a git repository is refused under
503
+ `TARGET`, because deletion there would have no recovery mechanism. The other four verbs never ask.
504
+
505
+ ### Machine-readable output
506
+
507
+ `--json` replaces the report with one JSON value on standard output. Warnings and refusals go to
508
+ standard error, so a piped value is never polluted.
509
+
510
+ | Verb | Value |
511
+ | ----------- | -------------------------------------------------------------------------- |
512
+ | `new` | `MaterializeResult` — `target`, `written`, `skipped`, `removed` |
513
+ | `audit` | `Audit` — `findings` and `questions` |
514
+ | `repair` | `MaterializeResult` plus `audit`, the terminal audit taken after the write |
515
+ | `catalog` | `MaterializeResult` plus `entries`, `mirrors`, and `dropped` |
516
+ | `overwrite` | The `catalog` value plus `audit`, `releases`, and `note` on a partial run |
517
+
518
+ Every failure reports the same envelope instead: `{ "error": { "code": …, "message": … } }`. The
519
+ code is a `ScaffoldErrorCode`, or `USAGE` for a command line that never became a command, or
520
+ `FAILED` for a raised value that published no code of its own. A command line that never became a
521
+ command carries no `--json`, so its refusal is always prose.
522
+
523
+ ## Blueprint
524
+
525
+ A `Blueprint` is the closed, JSON-serializable specification of one workspace. `createBlueprint`
526
+ fills every field a caller does not state:
527
+
528
+ ```ts
529
+ import { createBlueprint } from '@orkestrel/scaffold'
530
+
531
+ const blueprint = createBlueprint('router', {
532
+ src: ['core', 'server'],
533
+ dependencies: [{ name: '@orkestrel/emitter', range: '^0.0.5' }],
534
+ bin: true,
535
+ })
536
+
537
+ blueprint.version // '0.0.1'
538
+ blueprint.engines // '>=22.12.0'
539
+ ```
540
+
541
+ `src` selects published library environments and `app` selects private application environments.
542
+ The two axes are independent, so a library-only, an application-only, and a mixed workspace are all
543
+ first class. `dependencies` and `peers` are runtime `@orkestrel/*` packages; `extras` are
544
+ development dependencies and may carry any valid npm name.
545
+
546
+ `bin`, `integration`, `services`, `global`, and `showcase` are structural facts. Each is set only
547
+ when the workspace physically ships the directory or exact-case file that defines it, never because
548
+ of the workspace's name and never because a sibling fact is set.
549
+
550
+ `createBlueprint` enforces shape only. Whether the name is a name, the version a version, and the
551
+ axis combination one this package can generate are the gate's laws, and the gate answers them with
552
+ questions. A blueprint the gate will refuse is still constructible, so one law lives in one place.
553
+
554
+ ## Compile
555
+
556
+ The compiler is pure, synchronous, and host-independent. It runs three stages in order.
557
+
558
+ | Stage | Does |
559
+ | ------- | ------------------------------------------------------------------------ |
560
+ | `draft` | Assembles the artifacts the selected groups cover, and applies overrides |
561
+ | `gate` | Measures the blueprint, its overrides, and the drafted artifacts |
562
+ | `pin` | Gives the plan its content identity |
563
+
564
+ ```ts
565
+ import { createBlueprint, createCompiler } from '@orkestrel/scaffold'
566
+
567
+ const compiler = createCompiler()
568
+ const scaffolding = compiler.compile(createBlueprint('router', { src: ['core'] }))
569
+
570
+ scaffolding.plan?.artifacts // every planned file, in group order
571
+ scaffolding.stages // one CompileRecord per stage that ran
572
+ compiler.destroy()
573
+ ```
574
+
575
+ One rule decides the outcome: a `Scaffolding` carries a plan exactly when no question blocks. A
576
+ refused blueprint is answered rather than raised, so a caller reads the refusal from the value it
577
+ asked for. Each stage records its input and its output, a failed stage records the coded reason
578
+ beside them, and the stages after a failed one never run.
579
+
580
+ Off-contract input is different. A value that is not the exact shape raises `ScaffoldError` coded
581
+ `INVALID`, because it is not a question anyone can answer. Both entry points snapshot the caller's
582
+ value first and then guard the snapshot, so a property backed by an accessor is refused rather than
583
+ read.
584
+
585
+ Overrides replace a drafted artifact's content whole. An override that matches no planned artifact,
586
+ that targets a host-origin artifact, or that targets the manifest is a blocking question rather
587
+ than a silent no-op.
588
+
589
+ ### Groups
590
+
591
+ A plan selects over seven groups, and a compile that names none covers all of them. The order below
592
+ is the order a plan lists its artifacts in.
593
+
594
+ | Group | Holds |
595
+ | --------------- | ------------------------------------------------------------------ |
596
+ | `manifest` | `package.json` |
597
+ | `configs` | The root and per-target build configuration, and the root dotfiles |
598
+ | `source` | The selected environment barrels and entries |
599
+ | `tests` | The shared setup modules, the entry tests, and the policy sweep |
600
+ | `guides` | The guide index and the vendored guide mirrors |
601
+ | `docs` | `README.md` and the root instruction documents |
602
+ | `orchestration` | The harness directories, the bench scripts, and `.mcp.json` |
603
+
604
+ ## Ownership and drift
605
+
606
+ Two axes describe every planned file, and they answer different questions. `Origin` says how the
607
+ content is produced. `Ownership` says what scaffold claims at the path.
608
+
609
+ | `Origin` | Content comes from |
610
+ | ---------- | ------------------------------------------- |
611
+ | `host` | Byte-copied from the vendored data root |
612
+ | `template` | Filled from a frozen template definition |
613
+ | `computed` | Derived by this package's combination logic |
614
+
615
+ | `Ownership` | Audit compares | A write does |
616
+ | ----------- | -------------- | ------------------------------------------------- |
617
+ | `content` | The bytes | Restore a missing file, replace a stale one |
618
+ | `presence` | Existence only | Restore an absent file, never touch present bytes |
619
+ | `birth` | Nothing | Create the file only while it is absent |
620
+
621
+ Birth ownership is what makes a generated workspace the consumer's. `package.json`, the source
622
+ barrels, the tests, `README.md`, and `guides/README.md` are written once and are never rewritten by
623
+ a later verb.
624
+
625
+ Content ownership does not preserve an arbitrary custom Vitest project. Fixed optional proofs are
626
+ selected by their defining paths, as `guides` is. A workspace that needs other local configuration
627
+ must keep those edits outside a content-owned file; `repair` restores that file to the canonical
628
+ project set.
629
+
630
+ An audit reports one `Finding` per compared path.
631
+
632
+ | `Drift` | Means |
633
+ | --------- | ------------------------------------- |
634
+ | `aligned` | The target matches the plan |
635
+ | `stale` | The target holds different bytes |
636
+ | `missing` | The target holds no file at that path |
637
+ | `foreign` | The plan does not own the path at all |
638
+
639
+ A stale or foreign finding carries the destination's exact bytes in `observed`, and that record is
640
+ the precondition the mutation is held to. A write that replaces stale bytes and a deletion that
641
+ removes a foreign file each fail when the destination no longer matches what the finding recorded.
642
+ The requirement sits in the type rather than in prose, because a deletion that cannot bind to what
643
+ the audit showed is the one thing the destructive verb must never do.
644
+
645
+ ## Vendored data root
646
+
647
+ The vendored data root is the shared file set, staged into the published package as plain data. It
648
+ holds the root instruction documents, the licence, the orchestration contract, the four harness
649
+ directories, the bench scripts, the shared policy register, the byte-identical root dotfiles, and
650
+ the two guide mirrors a generated workspace starts from. `HOST_PATHS` is the candidate list; a plan
651
+ carries the subset its target selects, because a workspace never mirrors its own guide.
652
+
653
+ `stageHost` fills the root from a real checkout at build time:
654
+
655
+ ```ts
656
+ import { stageHost } from '@orkestrel/scaffold/server'
657
+
658
+ stageHost(process.cwd(), 'dist/host') // one ManifestEntry per file staged
659
+ ```
660
+
661
+ Each vendored path is copied to a storage name, and every dot that opens a segment comes off,
662
+ because npm's own ignore rules would drop a leading-dot entry from the tarball. A dotted file at the
663
+ root moves under `dotfiles/` so it cannot collide with an undotted sibling. `manifest.json` is
664
+ written last and declares the whole membership: one entry per file, the sorted directory inventory,
665
+ and a SHA-256 digest over both. The digest is what detects a membership edit that did not update
666
+ it, and the directory inventory is what makes a declared empty directory survive a file walk.
667
+
668
+ A missing vendored path is refused rather than staged around, and the refusal names every missing
669
+ path at once. That is why `guides/scaffold.md` — this file — must exist before `npm run build`
670
+ completes.
671
+
672
+ The `Materializer` reads the root once, at construction, and cross-checks the manifest against the
673
+ files actually stored. It defaults to the root inside the installed package, resolved from the
674
+ module's own location rather than from the caller's working directory. `--from` points it somewhere
675
+ else. A root carrying no manifest at all is read as a raw checkout, and artifact paths map onto it
676
+ one to one.
677
+
678
+ ## Generated workspace
679
+
680
+ A workspace's file set is a function of its two axes plus its structural facts. Nothing is fixed
681
+ except the manifest.
682
+
683
+ - One computed artifact: `package.json`, with the entry points, `exports` map, scripts, and
684
+ development dependencies its selection implies.
685
+ - One template artifact per configuration file the selection needs: the root `tsconfig.json` and
686
+ `vite.config.ts`, plus a Vite config and a scoped TypeScript config per selected environment, and
687
+ two more when `bin` is set.
688
+ - One template artifact per source and test file the selection needs: an `index.ts` barrel per
689
+ selected environment, `main.ts` and `index.html` for an application browser, `tests/setup.ts`
690
+ plus the host setup modules the selection reaches, and one entry test per axis project.
691
+ - One template artifact each for `README.md` and `guides/README.md`.
692
+ - One host artifact per vendored path the workspace selects. A vendored directory is one planned
693
+ path that expands into the files the data root stores beneath it.
694
+
695
+ `planToSummary` reports the tally rather than a number written down here:
696
+
697
+ ```ts
698
+ import { createBlueprint, createCompiler, planToSummary } from '@orkestrel/scaffold'
699
+
700
+ const compiler = createCompiler()
701
+ const scaffolding = compiler.compile(createBlueprint('router', { src: ['core', 'server'] }))
702
+ const summary = scaffolding.plan === undefined ? undefined : planToSummary(scaffolding.plan)
703
+
704
+ summary?.host // vendored artifacts
705
+ summary?.template // filled artifacts
706
+ summary?.computed // the manifest
707
+ compiler.destroy()
708
+ ```
709
+
710
+ ## Library
711
+
712
+ The two entry points split by host. `@orkestrel/scaffold` is host-independent: it compiles, gates,
713
+ and compares, and it touches neither the filesystem nor the network.
714
+ `@orkestrel/scaffold/server` is Node-only and holds everything that does.
715
+
716
+ Compare a plan against bytes a caller already read:
717
+
718
+ ```ts
719
+ import type { Blueprint, Snapshot } from '@orkestrel/scaffold'
720
+ import { createCompiler } from '@orkestrel/scaffold'
721
+
722
+ declare const blueprint: Blueprint
723
+ declare const current: Snapshot
724
+
725
+ const compiler = createCompiler()
726
+ const audit = compiler.audit(blueprint, current)
727
+
728
+ audit.findings.filter(({ drift }) => drift !== 'aligned')
729
+ compiler.destroy()
730
+ ```
731
+
732
+ Write a compiled plan into a real directory:
733
+
734
+ ```ts
735
+ import type { Plan } from '@orkestrel/scaffold'
736
+ import { createMaterializer } from '@orkestrel/scaffold/server'
737
+
738
+ declare const plan: Plan
739
+
740
+ const materializer = createMaterializer({ host: './dist/host' })
741
+ const result = materializer.materialize(plan, './packages/router')
742
+
743
+ result.written // every path created
744
+ materializer.destroy()
745
+ ```
746
+
747
+ Read the registry and the guide host:
748
+
749
+ ```ts
750
+ import { createUpstream } from '@orkestrel/scaffold/server'
751
+
752
+ const upstream = createUpstream({ registry: { timeout: 5_000 } })
753
+ const releases = await upstream.lookup([{ name: '@orkestrel/emitter', range: '^0.0.5' }])
754
+
755
+ releases.filter((release) => release.lookup === 'found')
756
+ upstream.destroy()
757
+ ```
758
+
759
+ Every request is unauthenticated, follows no redirect, and is bounded twice: `limit` refuses one
760
+ oversized answer and `budget` refuses many small ones. A per-package failure is collected as a
761
+ verdict carrying its cause rather than thrown, so one unreachable package never costs the caller the
762
+ rest of the answer. The organization package list is the exception, because without it there is no
763
+ fleet to report.
764
+
765
+ Stage and swap a set of files yourself:
766
+
767
+ ```ts
768
+ import { WriteTransaction } from '@orkestrel/scaffold/server'
769
+
770
+ const transaction = new WriteTransaction('./packages/router', ['AGENTS.md'])
771
+ try {
772
+ transaction.write('AGENTS.md', '# Agents\n')
773
+ transaction.commit() // ['AGENTS.md']
774
+ } finally {
775
+ transaction.discard()
776
+ }
777
+ ```
778
+
779
+ A transaction owns a private root beside the target on the same volume, so every promotion is a
780
+ rename rather than a copy. A failure part way through commit restores every destination it already
781
+ promoted and removes every directory it created. No destination ever receives half-written bytes.
782
+ It is not a journal: a process killed between two promotions leaves a mixed target.
783
+
784
+ Narrow a refusal by its code:
785
+
786
+ ```ts
787
+ import { isScaffoldError, ScaffoldError } from '@orkestrel/scaffold'
788
+
789
+ try {
790
+ throw new ScaffoldError('TARGET', 'The target carries no readable manifest.')
791
+ } catch (error) {
792
+ if (isScaffoldError(error)) error.code // 'TARGET'
793
+ }
794
+ ```
795
+
796
+ `INVALID` is off-contract input, `BLOCKED` is a refused blueprint recorded on a compile stage,
797
+ `TARGET` is a destination that is not what the caller's observation said it was, `WRITE` is a
798
+ mutation that could not be completed, and `FETCH` is an upstream read that produced no answer.
799
+
800
+ Every entity publishes an emitter. The compiler emits `compile`, `audit`, `block`, `error`, and
801
+ `destroy`; the materializer emits `write`, `remove`, `finish`, `error`, and `destroy`; the upstream
802
+ reader emits `release`, `mirror`, `error`, and `destroy`. Errors are emitted immediately before they
803
+ are thrown, so an observer sees a refusal even where the caller catches it.
804
+
805
+ ## Limits
806
+
807
+ Two things a reader will look for and not find.
808
+
809
+ **Scaffold emits no styles axis.** `SRC_MATRIX` is exactly `core`, `browser`, and `server`, and
810
+ `Blueprint` carries no styles field. A workspace that needs `src/styles/` adds the directory, its
811
+ configuration, and its Vitest project by hand. `.claude/rules/workspace.md` describes styles as an
812
+ environment because the fleet has one; scaffold simply does not generate it.
813
+
814
+ **A generated workspace has empty barrels and no starter entity.** Every emitted `index.ts` exports
815
+ nothing. This is deliberate: a generated sample entity is repeatedly mistaken for real
816
+ implementation. What a consumer does first is write the module's `types.ts`, then the
817
+ implementation that conforms to it, then export both from the barrel — the order `AGENTS.md` fixes.
818
+
819
+ ## Tests
820
+
821
+ - [`tests/src/core/Compiler.test.ts`](../tests/src/core/Compiler.test.ts) — the three stages, the
822
+ fail-closed rule, off-contract input, and teardown.
823
+ - [`tests/src/core/compilers.test.ts`](../tests/src/core/compilers.test.ts) — every projection from
824
+ a blueprint to an artifact, and every gate law.
825
+ - [`tests/src/core/helpers.test.ts`](../tests/src/core/helpers.test.ts) — the pure leaves, their
826
+ determinism, and their input independence.
827
+ - [`tests/src/core/validators.test.ts`](../tests/src/core/validators.test.ts) — every guard against
828
+ the adversarial matrix.
829
+ - [`tests/src/core/parsers.test.ts`](../tests/src/core/parsers.test.ts) — parse and guard soundness
830
+ in both directions.
831
+ - [`tests/src/core/cloners.test.ts`](../tests/src/core/cloners.test.ts) — ownership of a snapshot
832
+ taken from a hostile value.
833
+ - [`tests/src/core/templates.test.ts`](../tests/src/core/templates.test.ts) — the frozen template
834
+ definitions.
835
+ - [`tests/src/core/constants.test.ts`](../tests/src/core/constants.test.ts) — the scaffold pin every
836
+ generated workspace inherits, held to the version this manifest declares.
837
+ - [`tests/src/server/Materializer.test.ts`](../tests/src/server/Materializer.test.ts) — every
838
+ mutation verb against a real temporary target and a real vendored root.
839
+ - [`tests/src/server/WriteTransaction.test.ts`](../tests/src/server/WriteTransaction.test.ts) —
840
+ staging, promotion, and rollback after a driven failure.
841
+ - [`tests/src/server/Upstream.test.ts`](../tests/src/server/Upstream.test.ts) — the bounds, the
842
+ verdicts, and cancellation, against a protocol-faithful loopback server.
843
+ - [`tests/src/server/helpers.test.ts`](../tests/src/server/helpers.test.ts) — path containment,
844
+ digests, inventories, and the staging producer.
845
+ - [`tests/src/server/validators.test.ts`](../tests/src/server/validators.test.ts) — the host-path
846
+ law and every server guard's boundary values.
847
+ - [`tests/src/server/factories.test.ts`](../tests/src/server/factories.test.ts) — the two server
848
+ factories.
849
+ - [`tests/src/bin/CLI.test.ts`](../tests/src/bin/CLI.test.ts) — every verb driven in process
850
+ through recording output handlers.
851
+ - [`tests/src/bin/helpers.test.ts`](../tests/src/bin/helpers.test.ts) — command-line reading, usage
852
+ rendering, and the failure envelope.
853
+ - [`tests/src/bin/main.test.ts`](../tests/src/bin/main.test.ts) — the process entry point.
854
+ - [`tests/policy.test.ts`](../tests/policy.test.ts) — the syntactic coding and placement law over
855
+ every source file.
856
+ - [`tests/guides.test.ts`](../tests/guides.test.ts) — this guide's bijection with the two barrels.
857
+
858
+ ## See also
859
+
860
+ - [`guides/README.md`](README.md) — the concept and directory index.
861
+ - [`README.md`](../README.md) — the package front page.
862
+ - [`AGENTS.md`](../AGENTS.md) — the coding contract every generated workspace inherits.