@orkestrel/scaffold 0.0.30 → 0.0.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/main.js +144 -23
- package/dist/bin/main.js.map +1 -1
- package/dist/host/agents/orchestration.md +48 -6
- package/dist/host/agents/skills/orkestrel-falsify/SKILL.md +12 -0
- package/dist/host/claude/agents/codex.md +10 -0
- package/dist/host/claude/agents/grok.md +19 -8
- package/dist/host/claude/rules/architecture.md +25 -0
- package/dist/host/claude/rules/documentation.md +2 -1
- package/dist/host/claude/rules/tests.md +58 -21
- package/dist/host/claude/rules/workspace.md +25 -20
- package/dist/host/dotfiles/oxlintrc.json +1 -0
- package/dist/host/guides/scaffold.md +142 -67
- package/dist/host/tests/config.test.ts +45 -17
- package/dist/host/tests/policy.test.ts +254 -2
- package/dist/host/tests/setupPolicy.ts +297 -34
- package/dist/src/core/index.cjs +176 -179
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +90 -33
- package/dist/src/core/index.d.ts +90 -33
- package/dist/src/core/index.js +171 -180
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +13 -1
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.js +14 -2
- package/dist/src/server/index.js.map +1 -1
- package/package.json +6 -5
|
@@ -71,8 +71,8 @@ Exported from `@orkestrel/scaffold`, and reachable from
|
|
|
71
71
|
| `HostArtifact` | interface | A file byte-copied from the vendored data root, planned before its bytes are read. |
|
|
72
72
|
| `HydratedArtifact` | interface | A vendored file whose exact bytes have been read, so its content can be compared. |
|
|
73
73
|
| `Override` | interface | One artifact override. |
|
|
74
|
-
| `PlanSummary` | interface | The tally of one plan by artifact origin. |
|
|
75
74
|
| `Plan` | interface | The compiled, ordered artifact list and the selection it covers. |
|
|
75
|
+
| `PlanSummary` | interface | The tally of one plan by artifact origin. |
|
|
76
76
|
| `Question` | interface | One validation issue raised against a blueprint or a plan. |
|
|
77
77
|
| `Scaffolding` | interface | The replayable outcome of one compile. |
|
|
78
78
|
| `SrcDefinition` | interface | The build and export settings one published `src` environment contributes. |
|
|
@@ -94,20 +94,22 @@ Exported from `@orkestrel/scaffold`, and reachable from
|
|
|
94
94
|
| `CONFIG_TEMPLATES` | const | Formatter-stable template text for every configuration artifact. |
|
|
95
95
|
| `CONFORMANCE_TEST_PATH` | const | The official-tooling drift proof whose presence makes a workspace `conformance`. |
|
|
96
96
|
| `CONTROL_CHARACTER_PATTERN` | const | Unicode controls, formatting controls, and line and paragraph separators rejected in text. |
|
|
97
|
+
| `DECLARATION_DEV_DEPENDENCIES` | const | The development dependencies that emit declarations for published source or an executable. |
|
|
97
98
|
| `DEFAULT_ENGINES` | const | The `engines.node` range a workspace starts with. |
|
|
98
99
|
| `DEFAULT_VERSION` | const | The version a workspace starts at. |
|
|
99
100
|
| `DEPENDENCY_NAME_PATTERN` | const | The runtime dependency name syntax: the `@orkestrel` scope and a bare name. |
|
|
101
|
+
| `DISTRIBUTION_TEST_PATH` | const | The packed-package proof whose presence makes a workspace `distribution`. |
|
|
100
102
|
| `ENGINES_PATTERN` | const | The minimum-Node engine syntax a blueprint declares. |
|
|
101
103
|
| `ENVIRONMENTS` | const | The three `Environment` values, frozen. |
|
|
104
|
+
| `EXECUTABLE_PATHS` | const | The vendored paths a target receives with its executable bit set, frozen. |
|
|
102
105
|
| `EXTRA_NAME_PATTERN` | const | The development extra name syntax: any valid npm package name. |
|
|
103
106
|
| `EXTRA_RANGE_PATTERN` | const | The registry-only semver subset accepted for a development extra's range. |
|
|
104
107
|
| `GLOBAL_SETUP_PATH` | const | The shared Vitest global-setup module whose presence makes a workspace `global`. |
|
|
105
|
-
| `GUIDES_TEST_PATH` | const | The guide-parity proof whose physical file selects the fixed `guides` project. |
|
|
106
|
-
| `EXECUTABLE_PATHS` | const | The vendored paths a target receives with its executable bit set, frozen. |
|
|
107
108
|
| `GROUPS` | const | The seven `Group` values in plan order, frozen. |
|
|
109
|
+
| `GUIDES_TEST_PATH` | const | The guide-parity proof whose presence selects the planned `guides` project. |
|
|
108
110
|
| `HEX_PATTERN` | const | Exact lowercase hexadecimal bytes: two digits per byte, and empty content is valid. |
|
|
109
111
|
| `HOST_PATHS` | const | The paths byte-copied from the vendored data root, frozen. |
|
|
110
|
-
| `INTEGRATION_TEST_PATH` | const | The
|
|
112
|
+
| `INTEGRATION_TEST_PATH` | const | The cross-environment composition proof whose presence makes a workspace `integration`. |
|
|
111
113
|
| `INVALID_PATH_CHARACTER_PATTERN` | const | Visible characters a target-relative path and a Markdown path cell both forbid. |
|
|
112
114
|
| `MAX_ARTIFACT_BYTES` | const | Maximum bytes accepted for one artifact. |
|
|
113
115
|
| `MAX_ARTIFACT_HEX_LENGTH` | const | Maximum length of the hexadecimal string carrying one artifact's bytes. |
|
|
@@ -124,6 +126,7 @@ Exported from `@orkestrel/scaffold`, and reachable from
|
|
|
124
126
|
| `ORCHESTRATION_PATH_NAMES` | const | The exact root filenames that wire an agent bench rather than the toolchain, frozen. |
|
|
125
127
|
| `ORCHESTRATION_PATH_PREFIXES` | const | The path prefixes whose contents instruct or wire an agent, frozen. |
|
|
126
128
|
| `ORKESTREL_RANGE_PATTERN` | const | The exact caret-pinned pre-1.0 range accepted for an `@orkestrel/*` runtime dependency. |
|
|
129
|
+
| `PRINT_WIDTH` | const | Columns one emitted line may occupy, matching `printWidth` in `.oxfmtrc.json`. |
|
|
127
130
|
| `SERVICE_SCRIPT_PATH` | const | The provisioner skeleton a workspace with declared service vendors is given once. |
|
|
128
131
|
| `SERVICE_SETUP_PATH` | const | The live-service readiness module whose presence makes a workspace `service`. |
|
|
129
132
|
| `SERVICE_TEST_INCLUDE` | const | The include the live-service project covers, which is a directory rather than one proof. |
|
|
@@ -131,7 +134,9 @@ Exported from `@orkestrel/scaffold`, and reachable from
|
|
|
131
134
|
| `SHOWCASE_DEV_DEPENDENCIES` | const | The development dependency used only by the optional single-file showcase build. |
|
|
132
135
|
| `SOURCE_BROWSER_DEV_DEPENDENCIES` | const | The development dependencies a published browser `src` environment adds. |
|
|
133
136
|
| `SRC_MATRIX` | const | The build and export settings each published `src` environment contributes, frozen. |
|
|
137
|
+
| `TAB_WIDTH` | const | Columns one tab occupies when the formatter measures a line, matching `tabWidth`. |
|
|
134
138
|
| `VERSION_PATTERN` | const | The exact three-component version syntax a blueprint declares. |
|
|
139
|
+
| `WORKSPACE_OWNED_PATHS` | const | The vendored paths whose present bytes belong to each workspace, frozen. |
|
|
135
140
|
|
|
136
141
|
#### Guards
|
|
137
142
|
|
|
@@ -145,8 +150,8 @@ Exported from `@orkestrel/scaffold`, and reachable from
|
|
|
145
150
|
| `isCompilerHooks` | const | Narrow a value to the compiler's initial listener record. |
|
|
146
151
|
| `isCompilerOptions` | const | Narrow a value to `CompilerOptions`. |
|
|
147
152
|
| `isContent` | const | Narrow a value to text this package will accept as one artifact's content. |
|
|
148
|
-
| `isDependencyName` | const | Narrow a value to the scoped package name a runtime dependency carries. |
|
|
149
153
|
| `isDependency` | const | Narrow a value to a `Dependency`. |
|
|
154
|
+
| `isDependencyName` | const | Narrow a value to the scoped package name a runtime dependency carries. |
|
|
150
155
|
| `isEnvironment` | const | Narrow a value to one `Environment` a workspace may select. |
|
|
151
156
|
| `isFinding` | const | Narrow a value to a `Finding`. |
|
|
152
157
|
| `isGroup` | const | Narrow a value to one `Group` a plan selects over. |
|
|
@@ -189,9 +194,10 @@ Exported from `@orkestrel/scaffold`, and reachable from
|
|
|
189
194
|
| `matchesDriftReachability` | function | Test whether `inferDrift` could have produced a finding for an ownership. |
|
|
190
195
|
| `matchesEngines` | function | Test whether a declared engines floor is at or above the supported minimum. |
|
|
191
196
|
| `matchesOrchestrationPath` | function | Test whether a path instructs or wires an agent rather than the toolchain. |
|
|
197
|
+
| `matchesPrintWidth` | function | Test whether one emitted line fits the vendored formatter width. |
|
|
192
198
|
| `matchesRange` | function | Test whether a declared range already admits a published version. |
|
|
193
199
|
| `nameToGuide` | function | Derive the guide mirror path a package name answers for. |
|
|
194
|
-
| `nameToRewrite` | function | Derive the declaration rewrite a published face
|
|
200
|
+
| `nameToRewrite` | function | Derive the declaration rewrite a published face's `beforeWriteFile` applies. |
|
|
195
201
|
| `planToSummary` | function | Project a plan into its tally by artifact origin. |
|
|
196
202
|
| `selectGroups` | function | Select the groups a compile covers, in plan order. |
|
|
197
203
|
| `selectHostPaths` | function | Select the host paths a named workspace vendors. |
|
|
@@ -458,9 +464,10 @@ An option a verb does not list is refused by name rather than parsed and ignored
|
|
|
458
464
|
the registry, so `new` fails when the registry names no release for a package it was given: the
|
|
459
465
|
workspace would otherwise declare a dependency that does not resolve.
|
|
460
466
|
|
|
461
|
-
`new --bin` creates the executable entry, its test, and its scoped Vite and TypeScript wrappers.
|
|
462
|
-
|
|
463
|
-
`
|
|
467
|
+
`new --bin` creates the executable entry, its test, and its scoped Vite and TypeScript wrappers. The
|
|
468
|
+
other structural facts do not need creation flags. Add `tests/guides.test.ts` for `guides`,
|
|
469
|
+
`tests/distribution.test.ts` for `distribution`, `tests/integration.test.ts` for `integration`,
|
|
470
|
+
`tests/conformance.test.ts` for `conformance`, `tests/setupService.ts` for `service`,
|
|
464
471
|
`tests/setupGlobal.ts` for `global`, and `configs/app/vite.showcase.config.ts` for `showcase`;
|
|
465
472
|
reading verbs detect each exact-case file and register its fixed machinery. Add `scripts/service.sh`
|
|
466
473
|
for `vendors`. Reading verbs preserve and protect that birth-owned script, but do not infer its
|
|
@@ -471,8 +478,9 @@ vendor list from edited text.
|
|
|
471
478
|
`audit`, `repair`, `catalog`, and `overwrite` derive the blueprint from the target itself. The name
|
|
472
479
|
and the declared `@orkestrel/*` packages come from `package.json`. The two environment axes come
|
|
473
480
|
from the directories the target actually ships, because a directory is the fact and a declaration
|
|
474
|
-
beside it could disagree.
|
|
475
|
-
`bin`, `tests/
|
|
481
|
+
beside it could disagree. Eight more facts come from exact-case files: `src/bin/main.ts` selects
|
|
482
|
+
`bin`, `tests/guides.test.ts` selects `guides`, `tests/distribution.test.ts` selects `distribution`,
|
|
483
|
+
`tests/integration.test.ts` selects `integration`, `tests/conformance.test.ts` selects
|
|
476
484
|
`conformance`, `tests/setupService.ts` selects `service`, `tests/setupGlobal.ts` selects `global`,
|
|
477
485
|
and `configs/app/vite.showcase.config.ts` selects `showcase`. A containing directory does not select
|
|
478
486
|
the fact by itself.
|
|
@@ -488,27 +496,43 @@ cannot recover would leave every such workspace unplannable. `tests/setupService
|
|
|
488
496
|
is the module the root configuration names by path, and is what a live proof needs anyway, so it
|
|
489
497
|
carries the fact and the vendor list keeps its own separate job.
|
|
490
498
|
|
|
491
|
-
The root Vite configuration
|
|
492
|
-
|
|
493
|
-
directory or a case-folded spelling does not select it. A
|
|
494
|
-
guides project. A
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
The three plan-reading verbs compare
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
499
|
+
The root Vite configuration defines and registers the fixed `guides` project only when the derived
|
|
500
|
+
blueprint carries `guides`. Reading verbs set that fact only when `tests/guides.test.ts` is a
|
|
501
|
+
physical file with that exact path case. A directory or a case-folded spelling does not select it. A
|
|
502
|
+
fresh workspace therefore carries no guides project or script. A developer who adds the proof must
|
|
503
|
+
also add the exact `test:guides` script line that the plan reports; the manifest remains
|
|
504
|
+
birth-owned.
|
|
505
|
+
|
|
506
|
+
The three plan-reading verbs compare the Vitest project set named by the target manifest with the
|
|
507
|
+
project set the planned root configuration registers. Every planned proof project must also be
|
|
508
|
+
reachable from the manifest's `test` chain. A target whose manifest does not set `private: true` may
|
|
509
|
+
also reach it from `prepublishOnly`. A private target cannot use that chain, because npm refuses the
|
|
510
|
+
package before a publish lifecycle script runs, so crediting it would report a dead gate as a live
|
|
511
|
+
one. Generated integration runs from `test`. One shell-token pass reads quoted and unquoted
|
|
512
|
+
`--project value` and `--project=value` forms and follows literal `npm run` calls. A shell expansion
|
|
513
|
+
or malformed quote that prevents a project or script name from being resolved statically produces a
|
|
514
|
+
question instead of licensing a write. The classifier is deliberately bounded to manifest script
|
|
515
|
+
text that names `vitest`; an external wrapper whose name does not identify its runner supplies no
|
|
516
|
+
static Vitest fact to infer.
|
|
504
517
|
|
|
505
518
|
`audit` still completes the comparison and reports one non-blocking `projects` question. For a
|
|
506
519
|
literal absent project, its advisory tells the developer to register the project or remove the
|
|
507
|
-
script.
|
|
520
|
+
script. For a planned project absent from both gate chains, the advisory gives the exact direct
|
|
521
|
+
script line to add to `package.json`. `repair` and `overwrite` refuse either mismatch and do not
|
|
522
|
+
write the manifest or configuration. Their absent-project refusal tells the developer to remove the
|
|
508
523
|
script or not use scaffold writing verbs for a workspace that needs custom Vitest projects. It does
|
|
509
524
|
not recommend editing the content-owned configuration that the refusing verb would restore. An
|
|
510
525
|
advisory alone does not make an aligned target drift.
|
|
511
526
|
|
|
527
|
+
The same three plan-reading verbs compare the tooling set the derived blueprint plans against
|
|
528
|
+
`dependencies` and `devDependencies` together. A missing planned package produces one non-blocking
|
|
529
|
+
`dependencies` question naming every missing package and the exact manifest lines to add, in stable
|
|
530
|
+
order. The comparison measures membership only: range differences and workspace-owned extras are
|
|
531
|
+
outside it, and a planned tool may live in either section. A present section that is not an object
|
|
532
|
+
produces a question instead of a crash. `audit` reports the question without changing its exit
|
|
533
|
+
semantics. `repair` and `overwrite` refuse before writing configuration, and no verb edits the
|
|
534
|
+
birth-owned `package.json`.
|
|
535
|
+
|
|
512
536
|
### Exit codes
|
|
513
537
|
|
|
514
538
|
`0` means the target matched its plan and every step completed. `1` means the target drifted or a
|
|
@@ -572,22 +596,50 @@ because the shape is chosen once and read afterwards: `new` refuses the advisory
|
|
|
572
596
|
`repair` need the plan to describe and restore a target that already has that shape. A library
|
|
573
597
|
caller creating a workspace holds the same refusal, and the Compile section below states it.
|
|
574
598
|
|
|
575
|
-
`bin`, `integration`, `conformance`, `service`, `vendors`, `global`, and
|
|
576
|
-
facts. Each is set only when the workspace physically ships the directory
|
|
577
|
-
defines it, never because of the workspace's name and never because a
|
|
599
|
+
`bin`, `guides`, `distribution`, `integration`, `conformance`, `service`, `vendors`, `global`, and
|
|
600
|
+
`showcase` are structural facts. Each is set only when the workspace physically ships the directory
|
|
601
|
+
or exact-case file that defines it, never because of the workspace's name and never because a
|
|
602
|
+
sibling fact is set.
|
|
603
|
+
|
|
604
|
+
A structural fact is read when a verb runs, not when the file appears. Writing
|
|
605
|
+
`tests/integration.test.ts` into a workspace sets the fact, but the root configuration on disk was
|
|
606
|
+
generated before that file existed and still registers no `integration` project, so `test:config`
|
|
607
|
+
fails with `integration has no project factory or configuration` until a plan-writing verb
|
|
608
|
+
regenerates it.
|
|
609
|
+
|
|
610
|
+
`repair` alone does not close that, and refusing is correct rather than a gap. `package.json` is
|
|
611
|
+
birth-owned, so the verb cannot add the project's script, and it will not register a project the
|
|
612
|
+
manifest reaches from no gate. It exits 1 naming the target and writes nothing.
|
|
613
|
+
|
|
614
|
+
Adding a structural proof is therefore three steps, in order: write the file; declare its
|
|
615
|
+
`test:<project>` script and invoke that script from a gate chain; then run `repair`, which
|
|
616
|
+
regenerates the root configuration and registers the project. `audit` reports whichever piece is
|
|
617
|
+
still outstanding at each step.
|
|
618
|
+
|
|
619
|
+
`distribution` projects only when the workspace also publishes at least one `src` environment. It
|
|
620
|
+
packs and installs the published artifact, so without that axis there is nothing to pack, and the
|
|
621
|
+
declared flag alone adds no project, no `test:distribution` script, and no gate entry.
|
|
578
622
|
|
|
579
623
|
`service` says the workspace runs a live-service Vitest project over `tests/service`, and it alone
|
|
580
624
|
registers that project, its `test:service` script, and the `tests/setupService.ts` readiness module
|
|
581
|
-
the project names.
|
|
582
|
-
`
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
625
|
+
the project names. A publishing workspace invokes it from `prepublishOnly`; a `private: true`
|
|
626
|
+
workspace invokes it from `test`, which is the only gate it has. Its longer timeouts and disabled
|
|
627
|
+
file parallelism are the same in both. `vendors` names each external service the workspace drives
|
|
628
|
+
and emits `scripts/service.sh`, the provisioner that starts them. Neither is derivable from the
|
|
629
|
+
other: a workspace may declare vendors before it writes a suite, and a suite may drive a service the
|
|
630
|
+
skeleton does not start.
|
|
631
|
+
|
|
632
|
+
`integration` projects a cross-environment composition proof for any workspace, independently of
|
|
633
|
+
whether it has a published `src`. Its generated seed imports every selected `src` and `app`
|
|
634
|
+
environment through its public barrel. It starts no process and does not pack or install the
|
|
635
|
+
workspace. The proof composes across environments, so when `src` and `app` together declare fewer
|
|
636
|
+
than two the gate reports a non-blocking `integration` question: the project, the script, and the
|
|
637
|
+
`test` entry are all still registered, and the advisory reports that the seed composes nothing
|
|
638
|
+
rather than withholding it.
|
|
639
|
+
|
|
640
|
+
`showcase` projects only when the browser `app` environment exists. Without that axis the flag adds
|
|
641
|
+
no artifact, configuration, script, or dependency, and the gate reports a non-blocking question on
|
|
642
|
+
that field so the caller who set it learns it emitted nothing.
|
|
591
643
|
|
|
592
644
|
`createBlueprint` enforces shape only. Whether the name is a name, the version a version, and the
|
|
593
645
|
axis combination one this package can generate are the gate's laws, and the gate answers them with
|
|
@@ -688,15 +740,21 @@ content is produced. `Ownership` says what scaffold claims at the path.
|
|
|
688
740
|
| `presence` | Existence only | Restore an absent file, never touch present bytes |
|
|
689
741
|
| `birth` | Nothing | Create the file only while it is absent |
|
|
690
742
|
|
|
743
|
+
Presence ownership has two separate mechanisms, and a reader needs to know which applies:
|
|
744
|
+
|
|
745
|
+
| Mechanism | Paths | Bytes belong to | Cost |
|
|
746
|
+
| --------------- | ------------------------------------------------- | --------------------- | ------------------------------------------------------- |
|
|
747
|
+
| Verb-owned | `CATALOG_AGENT_PATH` and dependency guide mirrors | `catalog` or `mirror` | The owning verb is the only route for a later update. |
|
|
748
|
+
| Workspace-owned | `WORKSPACE_OWNED_PATHS`, currently `.gitignore` | The target workspace | Present bytes receive no later canonical ignore update. |
|
|
749
|
+
|
|
691
750
|
Birth ownership is what makes a generated workspace the consumer's. `package.json`, the source
|
|
692
751
|
barrels, the tests, `README.md`, and `guides/README.md` are written once and are never rewritten by
|
|
693
752
|
a later verb.
|
|
694
753
|
|
|
695
754
|
Content ownership does not preserve an arbitrary custom Vitest project. Fixed optional proofs are
|
|
696
|
-
selected by their defining paths, as `guides`, `
|
|
697
|
-
needs other local configuration
|
|
698
|
-
|
|
699
|
-
project set.
|
|
755
|
+
selected by their defining paths, as `guides`, `distribution`, `integration`, `conformance`, and
|
|
756
|
+
`service` are. A workspace that needs other local configuration must keep those edits outside a
|
|
757
|
+
content-owned file; `repair` restores that file to the canonical project set.
|
|
700
758
|
|
|
701
759
|
An audit reports one `Finding` per planned path, followed by any foreign path beneath the groups
|
|
702
760
|
the plan covers. Every planned finding carries its artifact's `ownership`. A foreign finding has
|
|
@@ -840,7 +898,10 @@ except the manifest.
|
|
|
840
898
|
selection declares the `playwright` this module imports.
|
|
841
899
|
- One template artifact per source and test file the selection needs: an `index.ts` barrel per
|
|
842
900
|
selected environment, `main.ts` and `index.html` for an application browser, `tests/setup.ts`
|
|
843
|
-
plus the host setup modules the selection reaches, and one entry test per axis project.
|
|
901
|
+
plus the host setup modules the selection reaches, and one entry test per axis project. An
|
|
902
|
+
integration selection also emits a birth-owned `tests/integration.test.ts` seed that imports each
|
|
903
|
+
selected public barrel and records its initial empty exports for the consumer to replace with an
|
|
904
|
+
observable cross-environment flow.
|
|
844
905
|
- One template artifact each for `README.md` and `guides/README.md`.
|
|
845
906
|
- One host artifact per vendored path the workspace selects. A vendored directory is one planned
|
|
846
907
|
path that expands into the files the data root stores beneath it.
|
|
@@ -998,7 +1059,7 @@ proves that every fence imports only real exports of the two barrels, and that e
|
|
|
998
1059
|
in this file resolves to one. It neither runs a fence nor typechecks one, so a trailing `// value`
|
|
999
1060
|
comment inside a fence states what this guide claims rather than what the build answered. The
|
|
1000
1061
|
verdicts that are measured are the ones a consumer hovers:
|
|
1001
|
-
[`tests/
|
|
1062
|
+
[`tests/distribution.test.ts`](../tests/distribution.test.ts) drives every `@example` the built
|
|
1002
1063
|
declarations print against the installed package, scores each verdict it can read as a value, and
|
|
1003
1064
|
names exactly the ones it cannot. Fences are not added to that instrument, because most of them
|
|
1004
1065
|
cannot be run: several declare an ambient value that has no runtime, and several write to a
|
|
@@ -1043,29 +1104,43 @@ nothing. This is deliberate: a generated sample entity is repeatedly mistaken fo
|
|
|
1043
1104
|
implementation. What a consumer does first is write the module's `types.ts`, then the
|
|
1044
1105
|
implementation that conforms to it, then export both from the barrel — the order `AGENTS.md` fixes.
|
|
1045
1106
|
|
|
1046
|
-
**A conformance or live-service proof is registered, but
|
|
1047
|
-
registers
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
`
|
|
1107
|
+
**A selected distribution, conformance, or live-service proof is registered, but none is written for
|
|
1108
|
+
you.** Scaffold registers `conformance` and `service` when their structural facts are set, and
|
|
1109
|
+
registers `distribution` only when the workspace also publishes `src`. In a publishing workspace,
|
|
1110
|
+
`distribution` and `service` run from `prepublishOnly` and `conformance` stays in `test`. In a
|
|
1111
|
+
`private: true` workspace, `distribution` is absent, `service` runs from `test`, and there is no
|
|
1112
|
+
`prepublishOnly` at all. Scaffold emits no proof into any registered project, because each names
|
|
1113
|
+
something only the package knows: the behavior its own packed artifact must hold once installed, the
|
|
1114
|
+
official artifact a conformance check measures against, and the service a live proof drives. A
|
|
1115
|
+
generated placeholder would read as a proof while measuring nothing, so the file a consumer writes
|
|
1116
|
+
is the file that selects the project.
|
|
1117
|
+
|
|
1118
|
+
A distribution proof carries one contract scaffold does enforce from the outside. The generated
|
|
1119
|
+
`prepublishOnly` invokes it as `npm run test:distribution -- --mode release`, and a proof that reads
|
|
1120
|
+
`import.meta.env.MODE === 'release'` must **fail** on an unreachable registry rather than skip. An
|
|
1121
|
+
ordinary local run may skip that case, because a developer offline is not a defect; a release run
|
|
1122
|
+
may not, because skipping there passes the publish gate without ever proving the artifact installs.
|
|
1123
|
+
Scaffold writes no proof, so honouring the flag is the consumer's, and a proof that ignores it
|
|
1124
|
+
reports green on exactly the runs that matter.
|
|
1125
|
+
|
|
1126
|
+
The consequence is one empty-project case per registered proof. A publishing blueprint carrying
|
|
1127
|
+
`distribution` with no `tests/distribution.test.ts`, or any blueprint carrying `conformance` with no
|
|
1128
|
+
`tests/conformance.test.ts`, registers a project whose include resolves to nothing, and Vitest exits
|
|
1129
|
+
non-zero on it. A blueprint carrying `service` gets `tests/setupService.ts` — the root configuration
|
|
1130
|
+
names that module by path, so an absent one fails the project's load rather than its run — and still
|
|
1131
|
+
no suite beneath `tests/service`, so `test:service` reports no test files until the consumer writes
|
|
1132
|
+
the first one. Every case is visible the first time the script runs, which is why none is silent.
|
|
1133
|
+
|
|
1134
|
+
None of the three folds into `integration`, which measures a different axis rather than a smaller
|
|
1135
|
+
one: the workspace's selected environments compose through their public barrels. The generated seed
|
|
1136
|
+
proves only that those barrels load together and expose the initial empty surfaces; the consumer
|
|
1137
|
+
replaces it with an observable cross-environment flow. The seed starts no process and does not pack
|
|
1138
|
+
or install the workspace, so the project stays in `test`. Two fleet packages hold the distinction.
|
|
1139
|
+
`@orkestrel/ollama` drives a real Ollama daemon through a `service` project, so a real service
|
|
1140
|
+
answers it and it runs from `prepublishOnly`. `@orkestrel/mcp` measures its server against the
|
|
1141
|
+
specification's own runner, `@modelcontextprotocol/conformance`, through a `conformance` project. It pins that runner as a development dependency and resolves it out
|
|
1142
|
+
of `node_modules`, and the server the runner drives is one the fixture starts itself on a loopback
|
|
1143
|
+
port, so the run drives nothing external and stays in `test`.
|
|
1069
1144
|
|
|
1070
1145
|
## Tests
|
|
1071
1146
|
|
|
@@ -42,7 +42,7 @@ describe('root configuration', () => {
|
|
|
42
42
|
for (const key of required.keys()) {
|
|
43
43
|
if (!absent.has(key)) throw new Error('The alias population carries no required entry')
|
|
44
44
|
}
|
|
45
|
-
}).
|
|
45
|
+
}).toThrow('The alias population carries no required entry')
|
|
46
46
|
for (const [key, expected] of required) {
|
|
47
47
|
const values = declared.get(key)
|
|
48
48
|
if (values === undefined) throw new Error(`${key} is not declared`)
|
|
@@ -102,7 +102,14 @@ describe('root configuration', () => {
|
|
|
102
102
|
setup: ['./tests/setup.ts', './tests/setupServer.ts'],
|
|
103
103
|
})
|
|
104
104
|
}
|
|
105
|
-
for (const label of [
|
|
105
|
+
for (const label of [
|
|
106
|
+
'policy',
|
|
107
|
+
'config',
|
|
108
|
+
'guides',
|
|
109
|
+
'conformance',
|
|
110
|
+
'distribution',
|
|
111
|
+
'integration',
|
|
112
|
+
]) {
|
|
106
113
|
if (!existsSync(resolve(root, `tests/${label}.test.ts`))) continue
|
|
107
114
|
expected.set(label, {
|
|
108
115
|
include: `tests/${label}.test.ts`,
|
|
@@ -218,7 +225,7 @@ describe('root configuration', () => {
|
|
|
218
225
|
expect(missing.delete('probe')).toBe(true)
|
|
219
226
|
expect(() => {
|
|
220
227
|
for (const [label, project] of expected) expect(missing.get(label)).toStrictEqual(project)
|
|
221
|
-
}).
|
|
228
|
+
}).toThrow(/strictly equal/u)
|
|
222
229
|
|
|
223
230
|
const misconfigured = new Map(configured)
|
|
224
231
|
const probe = misconfigured.get('probe')
|
|
@@ -227,7 +234,7 @@ describe('root configuration', () => {
|
|
|
227
234
|
expect(() => {
|
|
228
235
|
for (const [label, project] of expected)
|
|
229
236
|
expect(misconfigured.get(label)).toStrictEqual(project)
|
|
230
|
-
}).
|
|
237
|
+
}).toThrow(/strictly equal/u)
|
|
231
238
|
})
|
|
232
239
|
|
|
233
240
|
it('requires and validates every selected target wrapper', async () => {
|
|
@@ -344,10 +351,10 @@ describe('root configuration', () => {
|
|
|
344
351
|
const controlFound = ['configs/app/vite.server.config.ts']
|
|
345
352
|
expect(() => {
|
|
346
353
|
for (const wrapper of controlRequired) expect(controlFound).toContain(wrapper)
|
|
347
|
-
}).
|
|
348
|
-
expect(() =>
|
|
349
|
-
|
|
350
|
-
)
|
|
354
|
+
}).toThrow(/vite\.browser/u)
|
|
355
|
+
expect(() => expect(resolve(root, 'dist/actual')).toBe(resolve(root, 'dist/control'))).toThrow(
|
|
356
|
+
/expected/u,
|
|
357
|
+
)
|
|
351
358
|
})
|
|
352
359
|
|
|
353
360
|
it('registers proof scripts in the correct gate', () => {
|
|
@@ -359,8 +366,10 @@ describe('root configuration', () => {
|
|
|
359
366
|
if (typeof scripts !== 'object' || scripts === null) {
|
|
360
367
|
throw new Error('The package manifest carries no scripts')
|
|
361
368
|
}
|
|
369
|
+
const publishes = Object.getOwnPropertyDescriptor(manifest, 'private')?.value !== true
|
|
362
370
|
const test = Object.getOwnPropertyDescriptor(scripts, 'test')?.value
|
|
363
371
|
const config = Object.getOwnPropertyDescriptor(scripts, 'test:config')?.value
|
|
372
|
+
const distribution = Object.getOwnPropertyDescriptor(scripts, 'test:distribution')?.value
|
|
364
373
|
const integration = Object.getOwnPropertyDescriptor(scripts, 'test:integration')?.value
|
|
365
374
|
const conformance = Object.getOwnPropertyDescriptor(scripts, 'test:conformance')?.value
|
|
366
375
|
const service = Object.getOwnPropertyDescriptor(scripts, 'test:service')?.value
|
|
@@ -390,24 +399,39 @@ describe('root configuration', () => {
|
|
|
390
399
|
expect(registered.has('config')).toBe(true)
|
|
391
400
|
expect(registered.has('control')).toBe(false)
|
|
392
401
|
const hasConformance = registered.has('conformance')
|
|
402
|
+
const hasDistribution = registered.has('distribution')
|
|
393
403
|
const hasService = registered.has('service')
|
|
394
404
|
expect(config).toBe(
|
|
395
405
|
'vitest run --config vite.config.ts --no-cache --reporter=dot --project config',
|
|
396
406
|
)
|
|
397
407
|
expect(typeof test === 'string' && test.includes('npm run test:config')).toBe(true)
|
|
408
|
+
expect(distribution).toBe(
|
|
409
|
+
hasDistribution
|
|
410
|
+
? 'vitest run --config vite.config.ts --no-cache --reporter=dot --project distribution'
|
|
411
|
+
: undefined,
|
|
412
|
+
)
|
|
413
|
+
expect(typeof test === 'string' && test.includes('test:distribution')).toBe(false)
|
|
414
|
+
expect(typeof publish === 'string' && publish.includes('npm run test:distribution')).toBe(
|
|
415
|
+
hasDistribution && publishes,
|
|
416
|
+
)
|
|
417
|
+
expect(typeof publish === 'string').toBe(publishes)
|
|
398
418
|
expect(integration).toBe(
|
|
399
419
|
hasIntegration
|
|
400
420
|
? 'vitest run --config vite.config.ts --no-cache --reporter=dot --project integration'
|
|
401
421
|
: undefined,
|
|
402
422
|
)
|
|
403
|
-
|
|
404
|
-
|
|
423
|
+
// The integration seed composes barrels and starts no process, so it runs in
|
|
424
|
+
// `test` like any other hermetic proof. `prepublishOnly` reaches it through
|
|
425
|
+
// `npm test` rather than through a second direct invocation.
|
|
426
|
+
expect(typeof test === 'string' && test.includes('npm run test:integration')).toBe(
|
|
405
427
|
hasIntegration,
|
|
406
428
|
)
|
|
429
|
+
expect(typeof publish === 'string' && publish.includes('npm run test:integration')).toBe(false)
|
|
407
430
|
// A registered project no gate runs is a proof that never executes, and it
|
|
408
431
|
// never fails, so the suite reports green while carrying it. Conformance is
|
|
409
|
-
// hermetic and belongs to `test
|
|
410
|
-
// service
|
|
432
|
+
// hermetic and belongs to `test`. A publishing workspace isolates the
|
|
433
|
+
// live-service project in `prepublishOnly`; a private workspace reaches it
|
|
434
|
+
// from `test`, because npm never runs a private package's publish lifecycle.
|
|
411
435
|
expect(conformance).toBe(
|
|
412
436
|
hasConformance
|
|
413
437
|
? 'vitest run --config vite.config.ts --no-cache --reporter=dot --project conformance'
|
|
@@ -421,8 +445,12 @@ describe('root configuration', () => {
|
|
|
421
445
|
? 'vitest run --config vite.config.ts --no-cache --reporter=dot --project service'
|
|
422
446
|
: undefined,
|
|
423
447
|
)
|
|
424
|
-
expect(typeof test === 'string' && test.includes('test:service')).toBe(
|
|
425
|
-
|
|
448
|
+
expect(typeof test === 'string' && test.includes('npm run test:service')).toBe(
|
|
449
|
+
hasService && !publishes,
|
|
450
|
+
)
|
|
451
|
+
expect(typeof publish === 'string' && publish.includes('npm run test:service')).toBe(
|
|
452
|
+
hasService && publishes,
|
|
453
|
+
)
|
|
426
454
|
})
|
|
427
455
|
})
|
|
428
456
|
|
|
@@ -562,9 +590,9 @@ describe('configuration helpers', () => {
|
|
|
562
590
|
expect(() => configHelpers.enforceOutputPath(expected, expected)).not.toThrow()
|
|
563
591
|
expect(() =>
|
|
564
592
|
configHelpers.enforceOutputPath(resolve(root, 'dist/outside-control'), expected),
|
|
565
|
-
).
|
|
593
|
+
).toThrow('Build output must use its exact configured workspace directory')
|
|
566
594
|
const outside = resolve(dirname(root), 'outside-config-control')
|
|
567
|
-
expect(() => configHelpers.enforceOutputPath(outside, outside)).
|
|
595
|
+
expect(() => configHelpers.enforceOutputPath(outside, outside)).toThrow(
|
|
568
596
|
'Build output must remain inside the workspace',
|
|
569
597
|
)
|
|
570
598
|
})
|
|
@@ -611,7 +639,7 @@ describe('configuration helpers', () => {
|
|
|
611
639
|
error: expect.unreachable,
|
|
612
640
|
resolve: Promise.resolve.bind(Promise, { id: source }),
|
|
613
641
|
}
|
|
614
|
-
await expect(Reflect.apply(hook, context, ['@src/server', source])).rejects.
|
|
642
|
+
await expect(Reflect.apply(hook, context, ['@src/server', source])).rejects.toThrow(
|
|
615
643
|
'Browser modules cannot depend on Node or server-only modules',
|
|
616
644
|
)
|
|
617
645
|
await expect(Reflect.apply(hook, context, ['@src/core', source])).resolves.toBeNull()
|