@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.
- package/README.md +84 -99
- package/dist/bin/main.js +1094 -0
- package/dist/bin/main.js.map +1 -0
- package/dist/host/CLAUDE.md +3 -1
- package/dist/host/agents/orchestration.md +61 -4
- package/dist/host/agents/skills/orkestrel-align-packages/SKILL.md +1 -1
- package/dist/host/agents/skills/orkestrel-falsify/SKILL.md +7 -5
- package/dist/host/agents/skills/orkestrel-harden-package/SKILL.md +1 -1
- package/dist/host/agents/skills/orkestrel-harden-package/references/contract.md +1 -1
- package/dist/host/claude/agents/orkestrel.md +9 -9
- package/dist/host/claude/rules/architecture.md +45 -3
- package/dist/host/claude/rules/quality.md +4 -0
- package/dist/host/claude/rules/tests.md +57 -1
- package/dist/host/claude/rules/workspace.md +50 -17
- package/dist/host/codex/agents/orkestrel.toml +1 -1
- package/dist/host/configs/helpers.ts +762 -0
- package/dist/host/dotfiles/oxlintrc.json +2 -1
- package/dist/host/guides/scaffold.md +862 -0
- package/dist/host/manifest.json +40 -33
- package/dist/host/tests/config.test.ts +544 -0
- package/dist/host/tests/policy.test.ts +46 -0
- package/dist/host/tests/setupPolicy.ts +557 -602
- package/dist/src/core/index.cjs +3569 -10510
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +2361 -2789
- package/dist/src/core/index.d.ts +2361 -2789
- package/dist/src/core/index.js +3513 -10374
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +2855 -3765
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +1920 -1335
- package/dist/src/server/index.d.ts +1920 -1335
- package/dist/src/server/index.js +2812 -3680
- package/dist/src/server/index.js.map +1 -1
- package/package.json +16 -23
- package/dist/bin/scaffold.js +0 -1896
- package/dist/bin/scaffold.js.map +0 -1
- package/dist/host/guides/src/scaffold.md +0 -2886
- /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.
|