@orkestrel/scaffold 0.0.55 → 0.0.57
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 +21 -6
- package/dist/bin/main.js +2 -2
- package/dist/bin/main.js.map +1 -1
- package/dist/host/agents/orchestration.md +6 -3
- package/dist/host/agents/skills/orkestrel-publish/SKILL.md +4 -2
- package/dist/host/agents/skills/orkestrel-publish/references/wave.md +28 -2
- package/dist/host/agents/skills/orkestrel-publish/references/window.md +73 -19
- package/dist/host/claude/agents/orkestrel.md +6 -2
- package/dist/host/claude/rules/quality.md +1 -0
- package/dist/host/guides/scaffold.md +187 -68
- package/dist/host/manifest.json +9 -9
- package/dist/host/scripts/codex.sh +0 -0
- package/dist/host/scripts/cursor.sh +0 -0
- package/dist/host/scripts/deps.sh +0 -0
- package/dist/host/scripts/ollama.sh +0 -0
- package/dist/host/tests/policy.test.ts +71 -4
- package/dist/src/core/index.cjs +196 -40
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +113 -12
- package/dist/src/core/index.d.ts +113 -12
- package/dist/src/core/index.js +195 -41
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +140 -12
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +108 -9
- package/dist/src/server/index.d.ts +108 -9
- package/dist/src/server/index.js +141 -15
- package/dist/src/server/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -12,6 +12,17 @@ does not work. Scaffold makes the shared set data — a vendored data root shipp
|
|
|
12
12
|
— and gives it verbs: create a workspace from it, report how a workspace differs from it, and
|
|
13
13
|
write the difference back.
|
|
14
14
|
|
|
15
|
+
That root stages the vendored set and the instruction canon, and a target meets them differently.
|
|
16
|
+
`HOST_PATHS` names the vendored set — the toolchain, the policy proofs, the bench scripts, and the
|
|
17
|
+
harness permission file — and every target carries its own copy, which the verbs write and compare.
|
|
18
|
+
`CANON_PATHS` names the instruction canon — the coding and orchestration contracts, the rules, the
|
|
19
|
+
skills, the templates, the transport contracts, the agent roles, the bench configuration, and the
|
|
20
|
+
MCP registrations — which stays in one place and is published for reading. A target carries the
|
|
21
|
+
`AGENTS.md` and `CLAUDE.md` pointers that name where a reader finds it, and the catalog agent file
|
|
22
|
+
the `catalog` verb rewrites. It carries nothing else at a canon path: a file found at one is a
|
|
23
|
+
superseded copy, and `overwrite` deletes it. Vendored data root states how each set is staged and
|
|
24
|
+
how a pointer resolves.
|
|
25
|
+
|
|
15
26
|
Every following code fence is illustrative. [`tests/guides.test.ts`](../tests/guides.test.ts)
|
|
16
27
|
keeps the command reference aligned with the executable and transcribes the pure blueprint-default,
|
|
17
28
|
compile-refusal, and error-narrowing fences. A trailing comment in another fence is this guide's
|
|
@@ -97,6 +108,7 @@ Exported from `@orkestrel/scaffold`, and reachable from
|
|
|
97
108
|
| `BASE_DEV_DEPENDENCIES` | const | The tooling versions scaffold and every generated workspace share. |
|
|
98
109
|
| `BIN_CONFIGS` | const | The configuration files a workspace that ships its own executable adds, frozen. |
|
|
99
110
|
| `BIN_ENTRY_PATH` | const | The executable entry whose presence makes a workspace `bin`. |
|
|
111
|
+
| `CANON_PATHS` | const | The instruction-canon paths staged for reading rather than for a target, frozen. |
|
|
100
112
|
| `CATALOG_AGENT_PATH` | const | The agent file whose marker-bounded package table the catalog verb alone owns. |
|
|
101
113
|
| `CONFIG_TEMPLATES` | const | Formatter-stable template text for every configuration artifact. |
|
|
102
114
|
| `CONFORMANCE_TEST_PATH` | const | The official-tooling drift proof whose presence makes a workspace `conformance`. |
|
|
@@ -116,7 +128,7 @@ Exported from `@orkestrel/scaffold`, and reachable from
|
|
|
116
128
|
| `GROUPS` | const | The `Group` values in plan order, frozen. |
|
|
117
129
|
| `GUIDES_TEST_PATH` | const | The guide-parity proof whose presence selects the planned `guides` project. |
|
|
118
130
|
| `HEX_PATTERN` | const | Exact lowercase hexadecimal bytes: two digits per byte, and empty content is valid. |
|
|
119
|
-
| `HOST_PATHS` | const | The paths
|
|
131
|
+
| `HOST_PATHS` | const | The paths a target receives from the vendored data root, frozen. |
|
|
120
132
|
| `HOST_INVENTORY_PATH` | const | The repository-relative path where the committed vendored-file inventory is served. |
|
|
121
133
|
| `INTEGRATION_TEST_PATH` | const | The cross-environment composition proof whose presence makes a workspace `integration`. |
|
|
122
134
|
| `INVALID_PATH_CHARACTER_PATTERN` | const | Visible characters a target-relative path and a Markdown path cell both forbid. |
|
|
@@ -205,6 +217,7 @@ Exported from `@orkestrel/scaffold`, and reachable from
|
|
|
205
217
|
| `extractVersion` | function | Extract the major, minor, and patch components of an exact version. |
|
|
206
218
|
| `inferDrift` | function | Infer how one target path compares to the artifact planned for it. |
|
|
207
219
|
| `inferGroup` | function | Infer the `Group` a path belongs to. |
|
|
220
|
+
| `isCanonPath` | function | Test whether a path belongs to the instruction canon a target reads. |
|
|
208
221
|
| `isDeferredPath` | function | Test whether another surface owns the vendored bytes at a path. |
|
|
209
222
|
| `manifestToDependencies` | function | Project a manifest's `@orkestrel/*` declarations into separate section lists. |
|
|
210
223
|
| `manifestToName` | function | Project a package manifest's text to its own name. |
|
|
@@ -343,41 +356,43 @@ Exported from `@orkestrel/scaffold/server`, and reachable from
|
|
|
343
356
|
|
|
344
357
|
#### Helpers
|
|
345
358
|
|
|
346
|
-
| Name
|
|
347
|
-
|
|
|
348
|
-
| `computeDigest`
|
|
349
|
-
| `computeFileDigest`
|
|
350
|
-
| `computeManifestDigest`
|
|
351
|
-
| `filesToHost`
|
|
352
|
-
| `hexToDigest`
|
|
353
|
-
| `isExactCaseFile`
|
|
354
|
-
| `isPhysicalDirectory`
|
|
355
|
-
| `isPhysicalFile`
|
|
356
|
-
| `isVacant`
|
|
357
|
-
| `
|
|
358
|
-
| `
|
|
359
|
-
| `
|
|
360
|
-
| `
|
|
361
|
-
| `
|
|
362
|
-
| `
|
|
363
|
-
| `
|
|
364
|
-
| `
|
|
365
|
-
| `
|
|
366
|
-
| `
|
|
367
|
-
| `
|
|
368
|
-
| `
|
|
369
|
-
| `
|
|
370
|
-
| `
|
|
371
|
-
| `
|
|
372
|
-
| `
|
|
373
|
-
| `
|
|
374
|
-
| `
|
|
375
|
-
| `
|
|
376
|
-
| `
|
|
377
|
-
| `
|
|
378
|
-
| `
|
|
379
|
-
| `
|
|
380
|
-
| `
|
|
359
|
+
| Name | Kind | Summary |
|
|
360
|
+
| ------------------------- | -------- | ------------------------------------------------------------------------------------- |
|
|
361
|
+
| `computeDigest` | function | Compute the SHA-256 digest of text. |
|
|
362
|
+
| `computeFileDigest` | function | Compute the SHA-256 digest of one file's exact bytes. |
|
|
363
|
+
| `computeManifestDigest` | function | Compute the digest of a vendored host's declared membership. |
|
|
364
|
+
| `filesToHost` | function | Overlay host-owned live files onto the installed vendored floor. |
|
|
365
|
+
| `hexToDigest` | function | Project exact bytes stated in hexadecimal to their SHA-256 digest. |
|
|
366
|
+
| `isExactCaseFile` | function | Test whether a physical file's path matches every on-disk segment exactly. |
|
|
367
|
+
| `isPhysicalDirectory` | function | Test whether a path is a physical directory this package will read or write into. |
|
|
368
|
+
| `isPhysicalFile` | function | Test whether a path is a physical file this package will read or replace. |
|
|
369
|
+
| `isVacant` | function | Test whether a target is safe to write a fresh workspace into. |
|
|
370
|
+
| `listCanonPaths` | function | Lists the canon paths a target holds, filtered to a plan's groups. |
|
|
371
|
+
| `listDirectories` | function | List a directory's descendant directories as sorted root-relative paths. |
|
|
372
|
+
| `listFiles` | function | List a directory's files as sorted root-relative paths. |
|
|
373
|
+
| `matchesAnchor` | function | Test whether a captured directory is still the same directory. |
|
|
374
|
+
| `matchesExecutablePath` | function | Test whether a vendored path is one a target receives executable. |
|
|
375
|
+
| `matchesExpectation` | function | Test whether a destination still holds what was captured of it. |
|
|
376
|
+
| `matchesGitPath` | function | Test whether a path addresses a target's own repository metadata. |
|
|
377
|
+
| `matchesMissingPath` | function | Test whether a caught filesystem error reports an absent path. |
|
|
378
|
+
| `matchesPrecondition` | function | Test whether a destination still matches the narrower state a caller observed. |
|
|
379
|
+
| `matchesProtectedPath` | function | Test whether a target-relative path is one no verb may delete. |
|
|
380
|
+
| `matchesSensitivePath` | function | Test whether a path names local configuration or a credential. |
|
|
381
|
+
| `pathToStorage` | function | Project a target-relative path to the storage name a vendored host holds it under. |
|
|
382
|
+
| `pruneEmptiedDirectories` | function | Removes every directory one set of deletions emptied. |
|
|
383
|
+
| `readAnchor` | function | Capture one directory's physical identity. |
|
|
384
|
+
| `readExpectation` | function | Capture what one destination holds before a write. |
|
|
385
|
+
| `readFileHex` | function | Read one contained file as its exact bytes in lowercase hexadecimal. |
|
|
386
|
+
| `readFileText` | function | Read one contained file as bounded UTF-8 text. |
|
|
387
|
+
| `readHostFloor` | function | Read the installed vendored host floor as a verified value. |
|
|
388
|
+
| `readHostManifest` | function | Read a vendored host's manifest, when it carries one. |
|
|
389
|
+
| `readManifestEntry` | function | Derive one vendored-host manifest entry from a file in a checkout. |
|
|
390
|
+
| `readSnapshot` | function | Read a target's current bytes at the paths a plan claims. |
|
|
391
|
+
| `resolveContainedPath` | function | Resolve a root-relative path and refuse one that leaves its root. |
|
|
392
|
+
| `resolveRealPath` | function | Resolve a path through the real filesystem, keeping the part that does not exist yet. |
|
|
393
|
+
| `stageBytes` | function | Stage the named destinations of a value host into a private root. |
|
|
394
|
+
| `stageHost` | function | Stage a vendored host root from a real checkout. |
|
|
395
|
+
| `stageInventory` | function | Stage the committed vendored-file inventory from a real checkout. |
|
|
381
396
|
|
|
382
397
|
#### Classes
|
|
383
398
|
|
|
@@ -643,11 +658,12 @@ reports it only when its selection includes either group, without changing its e
|
|
|
643
658
|
that excludes those groups proceeds, and no verb adds the declaration for you: `package.json` is
|
|
644
659
|
birth-owned, and the range and script regions are the only parts of it a verb rewrites.
|
|
645
660
|
|
|
646
|
-
`audit` reports
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
661
|
+
`audit` reports a further non-blocking question, on the `setup` field.
|
|
662
|
+
|
|
663
|
+
The `setup` question fires when the target carries a filled root `tests/setup*.ts` module that is
|
|
664
|
+
neither a proof itself nor one of the vendored modules every target receives, while no proof of the
|
|
665
|
+
same stem covers it. A module counts as filled when its text differs from the seed this blueprint
|
|
666
|
+
plans at that same path.
|
|
651
667
|
|
|
652
668
|
The comparison reads the module and the seed trimmed, so surrounding whitespace decides nothing: a
|
|
653
669
|
trailing newline is not authorship, and a module holding whitespace alone reads as empty rather than
|
|
@@ -670,24 +686,34 @@ Coverage is read per module: `tests/<name>.ts` is covered by `tests/<name>.test.
|
|
|
670
686
|
else, which is the pairing the vendored policy proof resolves. Writing one proof retires that module
|
|
671
687
|
and leaves every other uncovered module named, and the message pairs each module it names with the
|
|
672
688
|
proof that module wants. The question belongs to the `tests` group, so a scoped audit that excludes
|
|
673
|
-
`tests` omits it. Scaffold does not write the proof it asks for, and
|
|
674
|
-
|
|
675
|
-
|
|
689
|
+
`tests` omits it. Scaffold does not write the proof it asks for, and the question never refuses a
|
|
690
|
+
write: a writing verb reports it in the terminal audit it prints, because refusing `repair` over a
|
|
691
|
+
gap no write can close would block every write. Run across a fleet, the question is the list of
|
|
676
692
|
packages carrying a filled setup module that no proof covers.
|
|
677
693
|
|
|
694
|
+
`audit` reads the instruction canon as findings rather than as a question. Each `CANON_PATHS` member
|
|
695
|
+
the target holds enters the comparison, by file where the member is a directory, and a path the plan
|
|
696
|
+
does not claim there reports `foreign`. Ownership and drift states that population, and Vendored data
|
|
697
|
+
root states what `overwrite` does with it.
|
|
698
|
+
|
|
678
699
|
### Exit codes
|
|
679
700
|
|
|
680
701
|
`0` means the target matched its plan and every step completed. `1` means the target drifted or a
|
|
681
702
|
step failed. `2` means the command line was not a command. A foreign file counts as drift: the
|
|
682
703
|
target holds something the plan does not own, whether or not the verb that found it was allowed to
|
|
683
|
-
remove it.
|
|
704
|
+
remove it. A superseded instruction copy is such a file, so a target generated before the canon
|
|
705
|
+
split exits `1` until the copy goes.
|
|
684
706
|
|
|
685
707
|
### Git
|
|
686
708
|
|
|
687
709
|
`overwrite` is the only verb that reads git, and it needs a repository. It asks git for the tracked
|
|
688
710
|
set and the dirty set, deletes only tracked paths, and refuses a tree carrying uncommitted changes
|
|
689
711
|
unless `--dirty` waives that refusal. A target that is not a git repository is refused under
|
|
690
|
-
`TARGET`, because deletion there would have no recovery mechanism. The other verbs never ask.
|
|
712
|
+
`TARGET`, because deletion there would have no recovery mechanism. The other verbs never ask. A
|
|
713
|
+
git-ignored file sits outside each reading: it never makes the tree dirty and it is never deleted.
|
|
714
|
+
Limits states what that costs a target that keeps one at a canon path. The sweep prunes the
|
|
715
|
+
directories its deletions emptied, so a swept target does not keep the shape of the set it no longer
|
|
716
|
+
holds, and git records no directory to report that shape with.
|
|
691
717
|
|
|
692
718
|
### Machine-readable output
|
|
693
719
|
|
|
@@ -887,15 +913,24 @@ than a silent no-op.
|
|
|
887
913
|
A plan selects over the following groups, and a compile that names none covers all of them. Their
|
|
888
914
|
order is the order a plan lists its artifacts in.
|
|
889
915
|
|
|
890
|
-
| Group | Holds
|
|
891
|
-
| --------------- |
|
|
892
|
-
| `manifest` | `package.json`
|
|
893
|
-
| `configs` | The root and per-target build configuration, and the root dotfiles
|
|
894
|
-
| `source` | The selected environment barrels and entries
|
|
895
|
-
| `tests` | The shared setup modules, the entry tests, and the policy sweep
|
|
896
|
-
| `guides` | The guide index and the vendored guide mirrors
|
|
897
|
-
| `docs` | `README.md`
|
|
898
|
-
| `orchestration` | The harness
|
|
916
|
+
| Group | Holds |
|
|
917
|
+
| --------------- | -------------------------------------------------------------------------- |
|
|
918
|
+
| `manifest` | `package.json` |
|
|
919
|
+
| `configs` | The root and per-target build configuration, and the root dotfiles |
|
|
920
|
+
| `source` | The selected environment barrels and entries |
|
|
921
|
+
| `tests` | The shared setup modules, the entry tests, and the policy sweep |
|
|
922
|
+
| `guides` | The guide index and the vendored guide mirrors |
|
|
923
|
+
| `docs` | `README.md` beside the `AGENTS.md` and `CLAUDE.md` pointers |
|
|
924
|
+
| `orchestration` | The harness permission file, the bench scripts, and the catalog agent file |
|
|
925
|
+
|
|
926
|
+
The plan claims paths inside the instruction canon deliberately, and each has a reason. The `docs`
|
|
927
|
+
group carries the `AGENTS.md` and `CLAUDE.md` pointers that name where each contract is read,
|
|
928
|
+
planned at those canon destinations as this package's own template content. The `orchestration`
|
|
929
|
+
group carries `CATALOG_AGENT_PATH`, the host-origin artifact at a canon path, because the `catalog`
|
|
930
|
+
verb refuses a target that lacks the file. Every other canon path is staged for reading, so no group
|
|
931
|
+
selection copies a contract into a target, and a copy a target holds at one of them is foreign drift
|
|
932
|
+
in the group `inferGroup` gives it. A scoped audit reads the canon through that same selection, so a
|
|
933
|
+
run excluding a group reports nothing there.
|
|
899
934
|
|
|
900
935
|
## Ownership and drift
|
|
901
936
|
|
|
@@ -927,13 +962,19 @@ The unhydrated row is the one a core-only caller meets most, and reading it as a
|
|
|
927
962
|
path is the mistake it invites. `Compiler` runs in the pure core face, which cannot read the
|
|
928
963
|
vendored data root, so every host artifact it plans carries `presence`: a claim over bytes nobody
|
|
929
964
|
has read is a claim no comparison could check. A `src: ['core']` plan therefore reports `presence`
|
|
930
|
-
for
|
|
931
|
-
a consumer concluding from that reading that scaffold never replaces those bytes is wrong.
|
|
965
|
+
for `.claude/settings.json`, `scripts/codex.sh`, `tests/policy.test.ts`, and every other vendored
|
|
966
|
+
path, and a consumer concluding from that reading that scaffold never replaces those bytes is wrong.
|
|
932
967
|
`Materializer` hydrates the plan before it audits or writes: hydration reads the vendored root and
|
|
933
968
|
turns each path scaffold owns the bytes of into a content-owned artifact, leaving `presence` on the
|
|
934
969
|
workspace-owned paths and the mirror pointers the preceding rows name. What a verb claims at a
|
|
935
970
|
vendored path is the hydrated ownership, and `HostArtifact` carries the same narrowing on the type.
|
|
936
971
|
|
|
972
|
+
The `AGENTS.md` and `CLAUDE.md` pointers sit outside that row. Their bytes come from a frozen
|
|
973
|
+
template rather than from the data root, so the pure core face already claims them content-owned and
|
|
974
|
+
hydration leaves them alone. `repair` and `overwrite` restore a missing pointer and replace a drifted
|
|
975
|
+
one, in every face, which is what keeps a target's resolution instructions in agreement with the
|
|
976
|
+
release it installed.
|
|
977
|
+
|
|
937
978
|
Birth ownership is what makes a generated workspace the consumer's. `materialize` writes a
|
|
938
979
|
birth-owned path into a vacant target. A later `repair` or `overwrite` call treats that path as
|
|
939
980
|
aligned whether it is present or absent, so it neither restores missing bytes nor replaces present
|
|
@@ -967,9 +1008,11 @@ paths, and `distribution` by a published `src` environment. A workspace that nee
|
|
|
967
1008
|
configuration must keep those edits outside a content-owned file; `repair` restores that file to the
|
|
968
1009
|
canonical project set.
|
|
969
1010
|
|
|
970
|
-
An audit reports one `Finding` per planned path, followed by
|
|
971
|
-
|
|
972
|
-
|
|
1011
|
+
An audit reports one `Finding` per planned path, followed by every foreign path in the groups the
|
|
1012
|
+
plan covers. That second list draws on a file beneath a vendored directory the plan expands and on a
|
|
1013
|
+
file the target holds at a canon path the plan does not claim. Every planned finding carries its
|
|
1014
|
+
artifact's `ownership`. A foreign finding has no ownership because no artifact was planned for its
|
|
1015
|
+
path. `Ownership` says what scaffold claims at
|
|
973
1016
|
a path, not what one run did there. Counting planned findings by `content`, `presence`, and `birth`
|
|
974
1017
|
therefore says what audit is entitled to compare and stays the same against a vacant target and a
|
|
975
1018
|
repaired one. What one run compared comes from `ownership`, `drift`, and `observed` together. A
|
|
@@ -1126,11 +1169,36 @@ generating a workspace with no network receives.
|
|
|
1126
1169
|
|
|
1127
1170
|
## Vendored data root
|
|
1128
1171
|
|
|
1129
|
-
The vendored data root is the shared file set, staged into the published package as plain data.
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
the
|
|
1133
|
-
|
|
1172
|
+
The vendored data root is the shared file set, staged into the published package as plain data.
|
|
1173
|
+
Staging walks `HOST_PATHS` and `CANON_PATHS`, and a release ships what both name.
|
|
1174
|
+
|
|
1175
|
+
`HOST_PATHS` is the vendored set, and a target receives a copy of each path it selects: the licence,
|
|
1176
|
+
the harness permission file, the bench scripts, the shared policy register, the byte-identical root
|
|
1177
|
+
dotfiles, and the guide mirrors a generated workspace starts from. It is a candidate list rather than
|
|
1178
|
+
a plan, because a workspace never mirrors its own guide.
|
|
1179
|
+
|
|
1180
|
+
`CANON_PATHS` is the instruction canon, staged for reading instead: the `AGENTS.md` coding contract,
|
|
1181
|
+
the `CLAUDE.md` harness bridge, the `.agents/orchestration.md` agent-operation contract, the rules
|
|
1182
|
+
under `.claude/rules/` and `.cursor/rules/`, the skills under `.agents/skills/` and `.claude/skills/`,
|
|
1183
|
+
the templates under `.agents/templates/`, the transport contracts under `.agents/transports/`, the
|
|
1184
|
+
agent roles under `.claude/agents/` and `.codex/agents/`, the `.codex/config.toml` bench
|
|
1185
|
+
configuration, and the `.mcp.json` and `.cursor/mcp.json` server registrations. A release stages
|
|
1186
|
+
every one of them, and a target receives a copy only where the plan claims the path. At the
|
|
1187
|
+
`AGENTS.md` and `CLAUDE.md` destinations it receives the pointers: different content at the same
|
|
1188
|
+
paths, planned as this package's own template content. At `CATALOG_AGENT_PATH` it receives the staged
|
|
1189
|
+
bytes themselves, because the `catalog` verb refuses a target that lacks the file. Everywhere else in
|
|
1190
|
+
the canon a target holds nothing, and a reader reaches the contracts from a scaffold checkout sitting
|
|
1191
|
+
beside the repository, or from the `node_modules/@orkestrel/scaffold/dist/host/` root inside the
|
|
1192
|
+
installed package, which is what the `AGENTS.md` pointer scaffold plans into a target names.
|
|
1193
|
+
|
|
1194
|
+
`HOST_PATHS` and `CANON_PATHS` are disjoint by prefix in either direction: no member of one equals or
|
|
1195
|
+
sits beneath a member of the other. Staging depends on that, because the walk covers the union and a
|
|
1196
|
+
path it discovers twice claims one storage name twice, which refuses the stage. `isCanonPath` is the
|
|
1197
|
+
one reading of canon membership, matching a member and anything beneath a member that is a directory,
|
|
1198
|
+
so staging, the live overlay, and the executable's fetch list never disagree about what a path is.
|
|
1199
|
+
Membership says where a path's bytes are staged, not whether a plan claims it: `nameToHostArtifacts`
|
|
1200
|
+
appends `CATALOG_AGENT_PATH` to what `HOST_PATHS` selects rather than listing it there, which is what
|
|
1201
|
+
keeps the file planned without putting a canon path in the vendored list.
|
|
1134
1202
|
|
|
1135
1203
|
The `host.json` file at the repository root is the committed live inventory. Each entry carries the
|
|
1136
1204
|
SHA-256 digest of its file content, and the inventory carries a membership digest over its declared
|
|
@@ -1143,12 +1211,29 @@ those paths; it can neither introduce a path nor delete one. A path added upstre
|
|
|
1143
1211
|
until a release adds it to the installed manifest. Remove a vendored path in the same change that
|
|
1144
1212
|
ships the release which removes it from that manifest.
|
|
1145
1213
|
|
|
1214
|
+
Moving a path from `HOST_PATHS` to `CANON_PATHS` is not that removal. The path stays staged, stays in
|
|
1215
|
+
the installed manifest, and keeps being published; what changes is that no host artifact claims it. A
|
|
1216
|
+
target generated before the move still holds the copy it received, and that copy sits at a path the
|
|
1217
|
+
plan does not own, so `audit` reports it `foreign` and exits `1`. `overwrite` deletes it in the run
|
|
1218
|
+
that repairs the pointers — one candidate list and one transaction, whether the file is a stray
|
|
1219
|
+
beneath a vendored directory or a superseded copy inside the canon. Membership decides that, never
|
|
1220
|
+
byte identity: a copy a release behind no longer matches the bytes the canon stages, and matching
|
|
1221
|
+
bytes is exactly how such a copy would be spared.
|
|
1222
|
+
|
|
1146
1223
|
At the default `UpstreamOptions.retries` value, an aligned target spends one request on `host.json`,
|
|
1147
1224
|
and each installed path whose live digest differs from the target adds one request for its bytes. A
|
|
1148
1225
|
positive `retries` value can repeat a request after a transport fault. Raw-host propagation lag after
|
|
1149
1226
|
a commit is a property of the content host. Scaffold neither creates that lag nor presents a stale
|
|
1150
1227
|
response as fresher than the host served it.
|
|
1151
1228
|
|
|
1229
|
+
A canon destination costs no request. The fetch list drops every canon destination and `filesToHost`
|
|
1230
|
+
keeps the installed floor bytes for each one, claimed or not. The rule covers the destinations a plan
|
|
1231
|
+
does claim as well: the `AGENTS.md` and `CLAUDE.md` pointers are written from this package's own
|
|
1232
|
+
templates, and the catalog agent file is claimed by presence, so no byte a target holds is taken from
|
|
1233
|
+
a fetched canon path. A fill carrying no row for a canon path is complete rather than spoiled, which
|
|
1234
|
+
is what lets one `Host` carry live bytes beside floor bytes without mixing baselines within a
|
|
1235
|
+
surface.
|
|
1236
|
+
|
|
1152
1237
|
`.claude/settings.json` is in that set, and the artifact planned for it is content-owned. `repair`
|
|
1153
1238
|
and `overwrite` restore its bytes, so an edit made to it inside a target is reverted at the next
|
|
1154
1239
|
visit and reported as drift until then. Put an operator grant in `.claude/settings.local.json`
|
|
@@ -1164,7 +1249,7 @@ import { stageHost } from '@orkestrel/scaffold/server'
|
|
|
1164
1249
|
stageHost(process.cwd(), 'dist/host') // one ManifestEntry per file staged
|
|
1165
1250
|
```
|
|
1166
1251
|
|
|
1167
|
-
Each
|
|
1252
|
+
Each staged path is copied to a storage name, and every dot that opens a segment comes off,
|
|
1168
1253
|
because npm's own ignore rules would drop a leading-dot entry from the tarball. A dotted file at the
|
|
1169
1254
|
root moves under `dotfiles/` so it cannot collide with an undotted sibling. `manifest.json` is
|
|
1170
1255
|
written last and declares the whole membership: one entry per file with a digest computed from the
|
|
@@ -1172,7 +1257,15 @@ staged destination after its copy, the sorted directory inventory, and a SHA-256
|
|
|
1172
1257
|
The membership digest detects an edit that did not update the manifest, and the directory inventory
|
|
1173
1258
|
makes a declared empty directory survive a file walk.
|
|
1174
1259
|
|
|
1175
|
-
|
|
1260
|
+
The pointer reads that spelling back. The installed branch of a target's `AGENTS.md` names the
|
|
1261
|
+
`node_modules/@orkestrel/scaffold/dist/host/AGENTS.md` file, the
|
|
1262
|
+
`node_modules/@orkestrel/scaffold/dist/host/agents/orchestration.md` file, and the
|
|
1263
|
+
`node_modules/@orkestrel/scaffold/dist/host/claude/rules/` directory, which are where the dot-stripped
|
|
1264
|
+
storage names of `AGENTS.md`, `.agents/orchestration.md`, and `.claude/rules/` land. A reader
|
|
1265
|
+
following a canon path through the installed package types the storage spelling, not the repository
|
|
1266
|
+
one.
|
|
1267
|
+
|
|
1268
|
+
A missing staged path is refused rather than staged around, and the refusal names every missing
|
|
1176
1269
|
path at once. That is why `guides/scaffold.md` — this file — must exist before `npm run build`
|
|
1177
1270
|
completes.
|
|
1178
1271
|
|
|
@@ -1234,6 +1327,12 @@ except the manifest.
|
|
|
1234
1327
|
`configs/browsers.ts` resolution, serves the bundle over a loopback server, and drives it in
|
|
1235
1328
|
Playwright Chromium.
|
|
1236
1329
|
- One template artifact each for `README.md` and `guides/README.md`.
|
|
1330
|
+
- One template artifact each for `AGENTS.md` and `CLAUDE.md`. They are pointers rather than contracts:
|
|
1331
|
+
`AGENTS.md` names the coding contract, the orchestration contract, the rules, and the skills, and
|
|
1332
|
+
resolves each against a sibling scaffold checkout or the installed package; `CLAUDE.md` names the
|
|
1333
|
+
`AGENTS.md` file beside it and imports nothing, because an `@path` import inlines the imported file
|
|
1334
|
+
into every context that loads it. Scaffold owns their bytes, so a release that moves the wording
|
|
1335
|
+
moves every target's copy at its next `repair`.
|
|
1237
1336
|
- One host artifact per vendored path the workspace selects. A vendored directory is one planned
|
|
1238
1337
|
path that expands into the files the data root stores beneath it.
|
|
1239
1338
|
|
|
@@ -1413,6 +1512,26 @@ drivers is separate test capability rather than name-resolution parity.
|
|
|
1413
1512
|
question, and `materialize` writes any plan into any vacant target. The Compile section states the
|
|
1414
1513
|
rule a library caller applies in its place.
|
|
1415
1514
|
|
|
1515
|
+
**A file a target keeps at a canon path never reports clean.** `overwrite` deletes a superseded
|
|
1516
|
+
instruction copy in the run that repairs the pointers, and it deletes only what git tracks, from a
|
|
1517
|
+
tree carrying no uncommitted work. An untracked copy is left standing, and a git-ignored one sits
|
|
1518
|
+
outside the dirty reading as well, so a target can carry its own file at a canon path through every
|
|
1519
|
+
visit. The audit reads canon membership by path, so such a copy stays a `foreign` finding and that
|
|
1520
|
+
target exits `1` on every run. `repair` never closes it either: that verb writes the planned paths a
|
|
1521
|
+
target is missing or has let drift and deletes nothing, so it restores the `AGENTS.md` and
|
|
1522
|
+
`CLAUDE.md` pointers and leaves every other copy where it is. A maintainer who wants a local MCP
|
|
1523
|
+
server registration keeps it outside the repository, in the harness's own local or user scope, rather
|
|
1524
|
+
than at `.mcp.json`, where the file is drift whoever wrote it.
|
|
1525
|
+
|
|
1526
|
+
**A target holds no dispatchable role beyond the catalog agent.** The canon is staged for reading, so
|
|
1527
|
+
a target receives the `AGENTS.md` and `CLAUDE.md` pointers and `.claude/agents/orkestrel.md`, and
|
|
1528
|
+
nothing else a harness reads: no other agent role, no bench configuration, and no MCP registration. A
|
|
1529
|
+
harness running in a target loads none of those from `node_modules` either, so a role, a bench, or a
|
|
1530
|
+
server that target needs is defined in the harness's own local or user scope — the seam the preceding
|
|
1531
|
+
registration entry already names. Fleet targets are not orchestration hosts. A session that
|
|
1532
|
+
dispatches roles starts on scaffold, where `.agents/orchestration.md` and the role files sit, and
|
|
1533
|
+
attaches the target it is working on.
|
|
1534
|
+
|
|
1416
1535
|
**`isPath` does not prove host portability.** It proves bounded target-relative syntax and rejects
|
|
1417
1536
|
traversal, separators, controls, and reserved syntax characters. It deliberately admits host-specific
|
|
1418
1537
|
segment spellings such as a Windows device name, a trailing dot or space, and a segment beyond a
|
|
@@ -1648,4 +1767,4 @@ port, so the run drives nothing external and stays in `test`.
|
|
|
1648
1767
|
|
|
1649
1768
|
- [`guides/README.md`](README.md) — the concept and directory index.
|
|
1650
1769
|
- [`README.md`](../README.md) — the package front page.
|
|
1651
|
-
- [`AGENTS.md`](../AGENTS.md) — the coding contract every generated workspace
|
|
1770
|
+
- [`AGENTS.md`](../AGENTS.md) — the coding contract every generated workspace points at.
|
package/dist/host/manifest.json
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"storage": "agents/orchestration.md",
|
|
23
23
|
"destination": ".agents/orchestration.md",
|
|
24
24
|
"executable": false,
|
|
25
|
-
"digest": "
|
|
25
|
+
"digest": "601036308ef2144a5e6e0aeb9af58127c43b25a007d194a1cfbc501f9fade1d7"
|
|
26
26
|
},
|
|
27
27
|
{
|
|
28
28
|
"storage": "agents/skills/enterprise-bootstrap/SKILL.md",
|
|
@@ -232,7 +232,7 @@
|
|
|
232
232
|
"storage": "agents/skills/orkestrel-publish/SKILL.md",
|
|
233
233
|
"destination": ".agents/skills/orkestrel-publish/SKILL.md",
|
|
234
234
|
"executable": false,
|
|
235
|
-
"digest": "
|
|
235
|
+
"digest": "7c259c5945838d0fedfd6eaf453bd912f6af802e26e74aebac13810018218c8c"
|
|
236
236
|
},
|
|
237
237
|
{
|
|
238
238
|
"storage": "agents/skills/orkestrel-publish/agents/openai.yaml",
|
|
@@ -244,13 +244,13 @@
|
|
|
244
244
|
"storage": "agents/skills/orkestrel-publish/references/wave.md",
|
|
245
245
|
"destination": ".agents/skills/orkestrel-publish/references/wave.md",
|
|
246
246
|
"executable": false,
|
|
247
|
-
"digest": "
|
|
247
|
+
"digest": "9f8dbb293bccffbc87751d70b828a8a7e6e6f09fb60ae9fc9f66aeb9f135e906"
|
|
248
248
|
},
|
|
249
249
|
{
|
|
250
250
|
"storage": "agents/skills/orkestrel-publish/references/window.md",
|
|
251
251
|
"destination": ".agents/skills/orkestrel-publish/references/window.md",
|
|
252
252
|
"executable": false,
|
|
253
|
-
"digest": "
|
|
253
|
+
"digest": "db102eb706708e887d8abf0d1f1bdd1132850e4dced735fb8c8378595c819ffd"
|
|
254
254
|
},
|
|
255
255
|
{
|
|
256
256
|
"storage": "agents/templates/brief.md",
|
|
@@ -310,7 +310,7 @@
|
|
|
310
310
|
"storage": "claude/agents/orkestrel.md",
|
|
311
311
|
"destination": ".claude/agents/orkestrel.md",
|
|
312
312
|
"executable": false,
|
|
313
|
-
"digest": "
|
|
313
|
+
"digest": "c48480a671f6288d9acf70366804f2386540bc16d711cf8cc29b67a3947bcfe5"
|
|
314
314
|
},
|
|
315
315
|
{
|
|
316
316
|
"storage": "claude/agents/planner.md",
|
|
@@ -394,7 +394,7 @@
|
|
|
394
394
|
"storage": "claude/rules/quality.md",
|
|
395
395
|
"destination": ".claude/rules/quality.md",
|
|
396
396
|
"executable": false,
|
|
397
|
-
"digest": "
|
|
397
|
+
"digest": "bed49d37c2f97cb1b1640ee36e45a1c2a79a95fd7263513369ee53b999aa0d85"
|
|
398
398
|
},
|
|
399
399
|
{
|
|
400
400
|
"storage": "claude/rules/styles.md",
|
|
@@ -652,7 +652,7 @@
|
|
|
652
652
|
"storage": "guides/scaffold.md",
|
|
653
653
|
"destination": "guides/scaffold.md",
|
|
654
654
|
"executable": false,
|
|
655
|
-
"digest": "
|
|
655
|
+
"digest": "168273a80a9bd44046d1afe472c238858b92deae528e46d73b8ff4b4b4fe73bd"
|
|
656
656
|
},
|
|
657
657
|
{
|
|
658
658
|
"storage": "scripts/codex.sh",
|
|
@@ -688,7 +688,7 @@
|
|
|
688
688
|
"storage": "tests/policy.test.ts",
|
|
689
689
|
"destination": "tests/policy.test.ts",
|
|
690
690
|
"executable": false,
|
|
691
|
-
"digest": "
|
|
691
|
+
"digest": "f1f9ad779a090e9921695c743068025c387ccd610a09fee411eda809475a7367"
|
|
692
692
|
},
|
|
693
693
|
{
|
|
694
694
|
"storage": "tests/setupPolicy.ts",
|
|
@@ -742,5 +742,5 @@
|
|
|
742
742
|
".codex/agents",
|
|
743
743
|
".cursor/rules"
|
|
744
744
|
],
|
|
745
|
-
"digest": "
|
|
745
|
+
"digest": "21424b0a9f13e34fb838f565808372d07ec5b4a49a7b558196e48b63cd883a67"
|
|
746
746
|
}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { existsSync, readdirSync } from 'node:fs'
|
|
2
|
+
import { join } from 'node:path'
|
|
1
3
|
import { describe, expect, it } from 'vitest'
|
|
2
4
|
import {
|
|
3
5
|
BRIDGE_POLICY_CONTROLS,
|
|
4
6
|
createPolicyScratch,
|
|
7
|
+
createSkillMetadata,
|
|
5
8
|
FUNCTION_SOURCE_FILES,
|
|
6
9
|
GENERIC_POLICY_SOURCES,
|
|
7
10
|
inspectPolicyControl,
|
|
@@ -23,6 +26,8 @@ import {
|
|
|
23
26
|
readPolicyPaths,
|
|
24
27
|
readSkillFamily,
|
|
25
28
|
RULES_POLICY_CONTROLS,
|
|
29
|
+
SKILL_BRIDGE_ROOT,
|
|
30
|
+
SKILL_FAMILY_ROOT,
|
|
26
31
|
SKILL_POLICY_APOSTROPHE,
|
|
27
32
|
SKILL_POLICY_BACKTICKED,
|
|
28
33
|
SKILL_POLICY_CONTROLS,
|
|
@@ -30,6 +35,7 @@ import {
|
|
|
30
35
|
SKILL_POLICY_FENCED,
|
|
31
36
|
SKILL_POLICY_FOLDED,
|
|
32
37
|
SKILL_POLICY_PARAGRAPHS,
|
|
38
|
+
SKILL_POLICY_TEXT,
|
|
33
39
|
stemToPolicyCandidates,
|
|
34
40
|
testToPolicyStem,
|
|
35
41
|
} from './setupPolicy.js'
|
|
@@ -345,10 +351,29 @@ describe('instrument negative controls', () => {
|
|
|
345
351
|
})
|
|
346
352
|
|
|
347
353
|
describe('skill family policy', () => {
|
|
348
|
-
|
|
354
|
+
// The family is read from the workspace it runs in, so a membership literal would
|
|
355
|
+
// bind this file to one workspace. The relationship binds in every workspace: a
|
|
356
|
+
// direct `node:fs` read of the canonical root is a second mechanism that reports
|
|
357
|
+
// the same directories, and reports none where the root is absent.
|
|
358
|
+
//
|
|
359
|
+
// The root is spelled here as literal segments rather than read from
|
|
360
|
+
// `SKILL_FAMILY_ROOT`, and that literal is what makes this read a second
|
|
361
|
+
// mechanism. Both sides reading the constant would move together when it drifts,
|
|
362
|
+
// so the case would stay green for every value the constant ever holds. Against
|
|
363
|
+
// the literal, a drifted constant desyncs the sides and reddens this case in a
|
|
364
|
+
// workspace that has the tree, while a workspace without one still passes on
|
|
365
|
+
// both readings being empty.
|
|
366
|
+
it('discovers exactly the directories the canonical skill root holds', () => {
|
|
367
|
+
const root = join(process.cwd(), '.agents', 'skills')
|
|
368
|
+
const held = existsSync(root)
|
|
369
|
+
? readdirSync(root, { withFileTypes: true })
|
|
370
|
+
.filter((entry) => entry.isDirectory())
|
|
371
|
+
.map((entry) => entry.name)
|
|
372
|
+
.sort()
|
|
373
|
+
: []
|
|
349
374
|
const family = readSkillFamily(process.cwd())
|
|
350
|
-
expect(family.length).
|
|
351
|
-
expect(family).
|
|
375
|
+
expect(family.length > 0).toBe(held.length > 0)
|
|
376
|
+
expect([...family]).toEqual(held)
|
|
352
377
|
})
|
|
353
378
|
|
|
354
379
|
it('requires every discovered skill file, metadata token, and reference', () => {
|
|
@@ -490,10 +515,52 @@ describe('repository policy', () => {
|
|
|
490
515
|
expect(inspectPolicyWorkspace(process.cwd())).toEqual([])
|
|
491
516
|
})
|
|
492
517
|
|
|
518
|
+
// A target reads the canon from the installed package, so its tree carries the
|
|
519
|
+
// pointer pair and no `.agents/` directory, no rule map, and no skill bridges.
|
|
520
|
+
// This vendored suite runs there, and every inspector it routes through has to
|
|
521
|
+
// stay silent on that shape.
|
|
522
|
+
it('accepts a target holding the pointer pair and no canon tree', () => {
|
|
523
|
+
const scratch = createPolicyScratch({ prefix: 'orkestrel-policy-pointer-' })
|
|
524
|
+
try {
|
|
525
|
+
scratch.write(
|
|
526
|
+
'AGENTS.md',
|
|
527
|
+
'# AGENTS.md\n\nRead `node_modules/@orkestrel/scaffold/dist/host/AGENTS.md` for the canon.\n',
|
|
528
|
+
)
|
|
529
|
+
scratch.write(
|
|
530
|
+
'CLAUDE.md',
|
|
531
|
+
'# Claude Code bridge\n\nRead the `AGENTS.md` file beside this one first.\n',
|
|
532
|
+
)
|
|
533
|
+
scratch.write('.claude/settings.json', '{\n\t"permissions": {\n\t\t"allow": []\n\t}\n}\n')
|
|
534
|
+
scratch.write(
|
|
535
|
+
'.claude/agents/orkestrel.md',
|
|
536
|
+
'# Orkestrel\n\nThe agent carrying the package catalog.\n',
|
|
537
|
+
)
|
|
538
|
+
scratch.write(
|
|
539
|
+
'package.json',
|
|
540
|
+
'{\n\t"name": "target",\n\t"private": true,\n\t"scripts": {\n\t\t"test": "vitest run"\n\t}\n}\n',
|
|
541
|
+
)
|
|
542
|
+
expect(inspectPolicyWorkspace(scratch.path)).toEqual([])
|
|
543
|
+
// The control: the same workspace with one canonical skill planted and no
|
|
544
|
+
// bridge beside it reports the twin violation, so the empty result above is a
|
|
545
|
+
// sweep that ran rather than a sweep with nothing it could report.
|
|
546
|
+
scratch.write(`${SKILL_FAMILY_ROOT}/sample/SKILL.md`, SKILL_POLICY_TEXT)
|
|
547
|
+
scratch.write(`${SKILL_FAMILY_ROOT}/sample/agents/openai.yaml`, createSkillMetadata('sample'))
|
|
548
|
+
expect(inspectPolicyWorkspace(scratch.path)).toEqual([
|
|
549
|
+
{
|
|
550
|
+
rule: 'bridge',
|
|
551
|
+
path: `${SKILL_BRIDGE_ROOT}/sample`,
|
|
552
|
+
message: 'canonical skill has a matching provider bridge directory',
|
|
553
|
+
},
|
|
554
|
+
])
|
|
555
|
+
} finally {
|
|
556
|
+
scratch.destroy()
|
|
557
|
+
}
|
|
558
|
+
})
|
|
559
|
+
|
|
493
560
|
it('reaches every branch of the workspace-authored path population', () => {
|
|
494
561
|
const paths = readPolicyPaths(process.cwd())
|
|
495
562
|
expect(paths).toContain('tests/setupPolicy.ts')
|
|
496
|
-
expect(paths).toContain('.claude/
|
|
563
|
+
expect(paths).toContain('.claude/settings.json')
|
|
497
564
|
expect(paths).toContain('package.json')
|
|
498
565
|
expect(paths).toContain('.gitattributes')
|
|
499
566
|
})
|