@orkestrel/scaffold 0.0.20 → 0.0.21
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/scaffold.js +15 -9
- package/dist/bin/scaffold.js.map +1 -1
- package/dist/host/CLAUDE.md +42 -21
- package/dist/host/agents/skills/orkestrel-debrief/references/field-testing.md +2 -2
- package/dist/host/agents/skills/orkestrel-debrief/references/instruction-audit.md +12 -0
- package/dist/host/claude/agents/codex.md +7 -0
- package/dist/host/claude/agents/orkestrel.md +3 -3
- package/dist/host/claude/agents/reviewer.md +5 -0
- package/dist/host/claude/rules/quality.md +10 -0
- package/dist/host/claude/rules/tests.md +10 -0
- package/dist/host/codex/agents/analyst.toml +5 -1
- package/dist/host/codex/agents/reviewer.toml +3 -1
- package/dist/host/codex/config.toml +10 -4
- package/dist/host/guides/src/scaffold.md +120 -29
- package/dist/host/tests/setupPolicy.ts +49 -11
- package/dist/src/core/index.cjs +1661 -856
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +84 -7
- package/dist/src/core/index.d.ts +84 -7
- package/dist/src/core/index.js +1656 -857
- package/dist/src/core/index.js.map +1 -1
- package/package.json +4 -2
package/dist/host/CLAUDE.md
CHANGED
|
@@ -163,10 +163,12 @@ absorbed.
|
|
|
163
163
|
patches serially; route cross-cutting findings.
|
|
164
164
|
5. **Audit adversarially.** Every nontrivial implementation gets `reviewer` (Opus 5, design
|
|
165
165
|
fit) and `analyst` (Sol, correctness and constraints) independently, plus `checker` for
|
|
166
|
-
mechanical conformance.
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
166
|
+
mechanical conformance. An audit brief states its subject as numbered falsifiable claims
|
|
167
|
+
and requires per-claim CONFIRMED/BROKEN with evidence, per the Falsification law in
|
|
168
|
+
`.claude/rules/quality.md`. In a fix round the unit's auditor is an engine that did not
|
|
169
|
+
write it. Multi-round audits use one fixed verdict shape with a single terminal line,
|
|
170
|
+
defined by the campaign skill. Reconcile their evidence; a finding neither engine can
|
|
171
|
+
substantiate against the evidence is dropped on the record.
|
|
170
172
|
6. **Verify.** One independent `verifier` runs the authoritative gates.
|
|
171
173
|
7. **Accept.** The Orchestrator decides and reports concise outcomes, decisions, evidence, and
|
|
172
174
|
remaining risk.
|
|
@@ -198,13 +200,34 @@ Workflow failures use the same ladder; do not absorb their raw logs into the mai
|
|
|
198
200
|
writing nodes — never two concurrent writers in the tree.
|
|
199
201
|
- Every node names a role and its engine.
|
|
200
202
|
|
|
203
|
+
Every dispatch is a file before it is a launch:
|
|
204
|
+
|
|
205
|
+
- The brief is written to a file under `tmp/`, named for its unit, before the unit is
|
|
206
|
+
launched, whatever engine executes it. A brief composed only inside a launch argument
|
|
207
|
+
cannot be corrected, resumed, or re-run once that call ends.
|
|
208
|
+
- The unit's returned report is captured to a file beside its brief under the same unit name,
|
|
209
|
+
so a unit's instruction and its outcome are one pair on disk.
|
|
210
|
+
- A re-run amends its brief instead of restating it: a mid-campaign correction produces a
|
|
211
|
+
successor file recording what changed and why, and the original stays. A fix round's brief
|
|
212
|
+
names the findings it carries and where each came from.
|
|
213
|
+
- Brief and report files are unit evidence, not deliverables. They are never committed, and
|
|
214
|
+
they are swept when the campaign that produced them is accepted.
|
|
215
|
+
- Anything in a brief or a report that must outlive the campaign is promoted into a durable
|
|
216
|
+
artifact — a commit message, a guide, a rule, a retrospective — before the sweep. What is
|
|
217
|
+
only in a swept file did not survive.
|
|
218
|
+
|
|
201
219
|
Every dispatch contains:
|
|
202
220
|
|
|
203
221
|
- **Role/engine** — named role and explicit engine.
|
|
204
222
|
- **Objective** — one concrete outcome.
|
|
205
223
|
- **Context** — the evidence slice, paths, decisions, `AGENTS.md`, applicable rules, the
|
|
206
224
|
skill name and required references (or explicit none), and the guide/spec.
|
|
225
|
+
- **Unknowns** — what the Orchestrator does not yet know that the unit needs, named as
|
|
226
|
+
unknown, with how the unit reports back on it. A brief that cannot be fully specified says
|
|
227
|
+
so instead of shipping a guess the executor would have to invent an answer around.
|
|
207
228
|
- **Scope** — owned files, shared and off-limits files, allowed tools, permission limits.
|
|
229
|
+
- **Execution** — the executor performs the assignment directly and spawns nothing. Every
|
|
230
|
+
brief states it; an executor deep in a task does not re-read this contract.
|
|
208
231
|
- **Output** — the exact distilled return shape; no process diary.
|
|
209
232
|
- **Deviation contract** — required stop/report behaviour for writers.
|
|
210
233
|
- **Acceptance criteria** — independently checkable completion conditions.
|
|
@@ -230,22 +253,20 @@ Four bench laws apply to every external engine:
|
|
|
230
253
|
transport where one exists. Long-running work — audits, implementation units, anything
|
|
231
254
|
multi-minute — uses the journaled CLI and never MCP: an interrupted MCP call loses its
|
|
232
255
|
session invisibly, while a journal survives any client-side failure.
|
|
233
|
-
- **Journal first.** Every bench invocation leaves a tailable on-disk record
|
|
234
|
-
`tmp/<bench>/` (`tmp/codex/`, `tmp/cursor/`): the
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
the recovery handle. Briefs never travel as fragile shell arguments.
|
|
256
|
+
- **Journal first.** Every bench invocation leaves a tailable on-disk record beside its brief
|
|
257
|
+
under `tmp/<bench>/` (`tmp/codex/`, `tmp/cursor/`): the event stream or output log and the
|
|
258
|
+
final answer. Every long exec also carries exactly one Monitor on its journal — a filtered
|
|
259
|
+
tail that emits milestones (commands run, files changed, agent messages, terminal states)
|
|
260
|
+
and never the raw event firehose — so progress arrives in the conversation while the
|
|
261
|
+
journal stays tailable for depth. The filter exits on the exec's terminal event, so the
|
|
262
|
+
monitor's lifecycle matches the exec's and no watcher outlives its subject. The journal's
|
|
263
|
+
mtime is the liveness signal; the session id in the journal head is the recovery handle.
|
|
242
264
|
- **Tracked, never loose.** Every bench unit is registered in the session task registry at
|
|
243
265
|
launch — subject, journal path, session id — and completed there at acceptance, so "what is
|
|
244
266
|
running" always has a first-class answer instead of a recollection of a command.
|
|
245
|
-
- **Ephemeral journals.**
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
its campaign is residue.
|
|
267
|
+
- **Ephemeral journals.** Bridges never delete journals; the Orchestrator sweeps them with
|
|
268
|
+
the campaign's other unit evidence at acceptance, after the final gate evidence is
|
|
269
|
+
recorded. A journal surviving past its campaign is residue.
|
|
249
270
|
|
|
250
271
|
Every long bench exec is launched by the Orchestrator as a harness-tracked background command
|
|
251
272
|
under a hard time cap, never detached from inside a bridge agent: the harness owns the
|
|
@@ -261,12 +282,12 @@ unit.
|
|
|
261
282
|
### Cursor Grok
|
|
262
283
|
|
|
263
284
|
- Reached only through the `grok` role, in ask mode:
|
|
264
|
-
`<agent-cli> -p --trust --mode=ask --model "$CURSOR_GROK_MODEL" "<
|
|
285
|
+
`<agent-cli> -p --trust --mode=ask --model "$CURSOR_GROK_MODEL" "<prompt>" | tee tmp/cursor/<unit>.log`.
|
|
265
286
|
`<agent-cli>` resolves as bare `agent`, then `agent.cmd` (Windows installs ship only
|
|
266
287
|
`.cmd`/`.ps1` shims, so bare `agent` does not resolve in Bash), then
|
|
267
|
-
`"$LOCALAPPDATA/cursor-agent/agent.cmd"` — verified with `--version` before first use.
|
|
268
|
-
|
|
269
|
-
|
|
288
|
+
`"$LOCALAPPDATA/cursor-agent/agent.cmd"` — verified with `--version` before first use. The
|
|
289
|
+
prompt points at the unit's brief file, `tmp/cursor/<unit>-brief.md`. The tee'd log is the
|
|
290
|
+
bench's journal.
|
|
270
291
|
- A long ask-mode run obeys the same launch, stream, and ledger discipline as a Codex exec:
|
|
271
292
|
the Orchestrator starts it as a harness-tracked background command under a time cap,
|
|
272
293
|
registers the unit in the task registry, and arms one Monitor on the tee'd log for
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Field testing an agent-facing surface
|
|
2
2
|
|
|
3
|
-
The method for producing the live evidence a debrief judges by. Proven across
|
|
4
|
-
|
|
3
|
+
The method for producing the live evidence a debrief judges by. Proven across a five-model
|
|
4
|
+
roster and portable to any surface a model consumes.
|
|
5
5
|
|
|
6
6
|
## The tier ladder
|
|
7
7
|
|
|
@@ -63,6 +63,18 @@ Findings land as one of:
|
|
|
63
63
|
probe-first, journal-first, verdict shapes, launch ownership) land in the operating
|
|
64
64
|
contract, mirrored across providers.
|
|
65
65
|
|
|
66
|
+
## Three questions every round asks
|
|
67
|
+
|
|
68
|
+
- **Which findings came from falsification rather than diff reading**, and is the brief
|
|
69
|
+
shape that produced them doctrine yet? Route to the Falsification law in
|
|
70
|
+
`.claude/rules/quality.md`.
|
|
71
|
+
- **Which shipped gaps were accepted as untestable**, and was each one genuinely
|
|
72
|
+
irreducible or a missing seam? Route to the missing-seam law in
|
|
73
|
+
`.claude/rules/tests.md`.
|
|
74
|
+
- **Which units could not be re-run from their own recorded brief**, and why? A unit whose
|
|
75
|
+
instruction never became a file, or whose correction left its brief behind, is a defect in
|
|
76
|
+
the dispatch. Route to the brief-and-report laws in the operating contract.
|
|
77
|
+
|
|
66
78
|
## Mirror discipline
|
|
67
79
|
|
|
68
80
|
Every roster or contract change lands on all provider surfaces in the same round: the
|
|
@@ -77,6 +77,13 @@ argument, diagnosis, correctness/security audit, and constraint review. Capture
|
|
|
77
77
|
repository status before and after. Require evidence for every claim and return
|
|
78
78
|
unsupported claims as dropped.
|
|
79
79
|
|
|
80
|
+
An audit brief states its subject as a numbered list of falsifiable claims rather
|
|
81
|
+
than a diff to read, and requires Sol to attempt refutation: each claim comes back
|
|
82
|
+
CONFIRMED with the evidence that convinced it, or BROKEN with the exact failing
|
|
83
|
+
input, state, or interleaving. The Falsification section of
|
|
84
|
+
`.claude/rules/quality.md` owns that method — point the brief at it instead of
|
|
85
|
+
restating it.
|
|
86
|
+
|
|
80
87
|
## Implementer
|
|
81
88
|
|
|
82
89
|
Sandbox `workspace-write`, main checkout, sole serial writer from a clean
|
|
@@ -44,11 +44,11 @@ descriptions never enter agent instruction context.
|
|
|
44
44
|
| @orkestrel/database | 0.0.7 |
|
|
45
45
|
| @orkestrel/emitter | 0.0.5 |
|
|
46
46
|
| @orkestrel/guide | 0.0.8 |
|
|
47
|
-
| @orkestrel/html | 0.0.
|
|
47
|
+
| @orkestrel/html | 0.0.2 |
|
|
48
48
|
| @orkestrel/indexeddb | 0.0.6 |
|
|
49
49
|
| @orkestrel/interpret | 0.0.7 |
|
|
50
50
|
| @orkestrel/markdown | 0.0.7 |
|
|
51
|
-
| @orkestrel/mcp | 0.0.
|
|
51
|
+
| @orkestrel/mcp | 0.0.12 |
|
|
52
52
|
| @orkestrel/middleware | 0.0.9 |
|
|
53
53
|
| @orkestrel/msg | 0.0.5 |
|
|
54
54
|
| @orkestrel/ndjson | 0.0.5 |
|
|
@@ -61,7 +61,7 @@ descriptions never enter agent instruction context.
|
|
|
61
61
|
| @orkestrel/reason | 0.0.4 |
|
|
62
62
|
| @orkestrel/relation | 0.0.7 |
|
|
63
63
|
| @orkestrel/router | 0.0.8 |
|
|
64
|
-
| @orkestrel/scaffold | 0.0.
|
|
64
|
+
| @orkestrel/scaffold | 0.0.20 |
|
|
65
65
|
| @orkestrel/sea | 0.0.5 |
|
|
66
66
|
| @orkestrel/server | 0.0.10 |
|
|
67
67
|
| @orkestrel/sqlite | 0.0.6 |
|
|
@@ -33,6 +33,11 @@ Audit the changed work only through Opus 5's subjective and creative lens:
|
|
|
33
33
|
5. **Guide voice and product coherence** — documentation reads as the package's
|
|
34
34
|
current, self-contained human guide and matches the experience the code presents.
|
|
35
35
|
|
|
36
|
+
Test a design claim by asking whether the shipped artifact still matches it — a
|
|
37
|
+
guide, charter, or name that described the work two revisions ago is drift, and
|
|
38
|
+
that question is what finds it. Anything you cannot settle on subjective grounds
|
|
39
|
+
becomes an Analyst referral rather than a verdict of yours.
|
|
40
|
+
|
|
36
41
|
For a rendered or externally driven surface, the supplied capture portfolio is the
|
|
37
42
|
primary evidence and source is corroboration only: cite a capture for every rendered
|
|
38
43
|
claim, mark what the portfolio cannot show as NOT-EVIDENCED instead of inferring it,
|
|
@@ -21,6 +21,16 @@ paths:
|
|
|
21
21
|
- Treat existing code, tests, `old/`, branches, and copied projects as evidence rather than authority.
|
|
22
22
|
- Build a capability/defect matrix before a broad API or production-readiness change. Every row ends as implement, repair, retain, or intentionally exclude with evidence.
|
|
23
23
|
|
|
24
|
+
## Falsification
|
|
25
|
+
|
|
26
|
+
A review that reads a diff finds what the diff shows; a review that tries to break named claims finds what the diff hides. Code that has already passed diff review several times can still carry a defect nobody has yet tried to trigger.
|
|
27
|
+
|
|
28
|
+
- State an audit's subject as a numbered list of the claims the work makes, never as “review this diff”. Each claim is falsifiable: a property some concrete input, state, or interleaving could show false.
|
|
29
|
+
- Instruct the auditor to attempt refutation rather than confirmation. A claim it cannot break is reported CONFIRMED with the evidence that convinced it; a claim it breaks is reported BROKEN with the exact failing input, state, or interleaving, plus the smallest correct fix.
|
|
30
|
+
- Derive claims from what the change asserts under adverse conditions: cancellation, restart, concurrency, partial failure, hostile input, resource exhaustion, and the orderings a happy path never reaches.
|
|
31
|
+
- Read the installed declaration or implementation of every substrate a claim depends on. A claim about `stop()` is unfalsifiable until you know what `stop()` does when the status is not the one the caller assumed.
|
|
32
|
+
- An audit returning only confirmations has either audited trivial claims or has not tried. Name the claims you could not break, so the next round knows what has already been attacked.
|
|
33
|
+
|
|
24
34
|
## Ecosystem reuse
|
|
25
35
|
|
|
26
36
|
The root laws on inspecting declared `@orkestrel/*` capabilities, reusing a matching primitive, and updating every consumer without shims bind here without restatement. They leave this file the judgment calls:
|
|
@@ -111,6 +111,16 @@ Keep Vitest/provider configuration minimal:
|
|
|
111
111
|
- Remove exploratory settings after fixing the cause.
|
|
112
112
|
- Config comments explain the current reason, not the history of failed experiments.
|
|
113
113
|
|
|
114
|
+
## Untestable usually means missing seam
|
|
115
|
+
|
|
116
|
+
Before accepting that a behavior cannot be tested, look for the seam that would make it testable.
|
|
117
|
+
|
|
118
|
+
- A collaborator reached through a hard-coded global — a stream, a clock source, a spawn, a fetch — is a missing injection point, not an untestable truth. An injected collaborator with a real minimal implementation is a sanctioned boundary stub, not a mock of project-owned behavior.
|
|
119
|
+
- Prefer adding the seam over shipping the gap whenever the seam is one the design would welcome anyway, and whenever a sibling collaborator is already injected.
|
|
120
|
+
- When a gap is genuinely irreducible, record it where a reader meets it: what is unproven, why it cannot be driven, and what would change that. A silent untested guard reads exactly like a tested one.
|
|
121
|
+
|
|
122
|
+
Coverage reporting is a discovery input, never evidence of proof. It answers one cheap mechanical question — which code no test even executed — and that question reliably finds forgotten branches and rules nothing calls. It cannot tell you whether an executed line is asserted, so a fully covered file can still be entirely unproven. Read a coverage report to find candidates for the adequacy audit; never cite it as the audit's result, and never let a percentage become the target.
|
|
123
|
+
|
|
114
124
|
## Discovery and adequacy audit
|
|
115
125
|
|
|
116
126
|
Before acceptance:
|
|
@@ -9,7 +9,11 @@ governing guide/spec, and the exact scope. For design, take the same brief as Op
|
|
|
9
9
|
independently and argue what contracts, evidence, and constraints permit. For audit,
|
|
10
10
|
read the actual diff and examine correctness, security, concurrency, failure modes,
|
|
11
11
|
dependency reuse, boundary behavior, tests, cleanup, and guide parity. Require
|
|
12
|
-
file:line evidence and drop unsupported claims.
|
|
12
|
+
file:line evidence and drop unsupported claims. When the brief states its subject as
|
|
13
|
+
numbered falsifiable claims, attempt refutation rather than confirmation and return
|
|
14
|
+
each claim CONFIRMED with the evidence that convinced you or BROKEN with the exact
|
|
15
|
+
failing input, state, or interleaving, per the Falsification section of
|
|
16
|
+
.claude/rules/quality.md. For a rendered or externally driven
|
|
13
17
|
surface, the supplied capture portfolio is primary evidence and source is
|
|
14
18
|
corroboration; mark a claim the portfolio cannot show NOT-EVIDENCED rather than
|
|
15
19
|
inferring it from source. Never edit, implement, reconcile, accept, or spawn. Return
|
|
@@ -15,7 +15,9 @@ Verify that the `claude` CLI is available before dispatch. If it is absent or th
|
|
|
15
15
|
dispatch fails, return the failure immediately so the Sol main session can run the
|
|
16
16
|
design-audit pass itself and record that Opus was unavailable for this round.
|
|
17
17
|
Require PASS/FAIL, design-fit blockers with file:line evidence, nonblocking
|
|
18
|
-
advisories, and acceptance-criterion confirmations.
|
|
18
|
+
advisories, and acceptance-criterion confirmations. Require each design claim to be
|
|
19
|
+
tested by asking whether the shipped artifact still matches it, and anything not
|
|
20
|
+
settleable on subjective grounds to come back as an Analyst referral. For a rendered or externally
|
|
19
21
|
driven surface, require capture citations as primary evidence and the verdict shape
|
|
20
22
|
of the dispatch-named skill. Forbid edits, commands, orchestration, reconciliation,
|
|
21
23
|
and acceptance. Never invoke Fable, read credentials,
|
|
@@ -53,10 +53,16 @@ concurrently.
|
|
|
53
53
|
|
|
54
54
|
Every dispatch includes objective, context, AGENTS.md, applicable rules, governing
|
|
55
55
|
skill name/path and required references (or explicit none), guide/spec, owned and
|
|
56
|
-
off-limits files, permissions, exact output, deviation behavior,
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
56
|
+
off-limits files, permissions, exact output, deviation behavior, declared unknowns
|
|
57
|
+
with how the unit reports them, the requirement that the executor spawns nothing,
|
|
58
|
+
and independently checkable acceptance criteria. Every brief is a file under tmp/
|
|
59
|
+
written before its unit launches, with the unit's report captured beside it; a
|
|
60
|
+
re-run amends the brief in a successor file rather than restating it; both are
|
|
61
|
+
swept at campaign acceptance, so anything that must outlive the campaign is
|
|
62
|
+
promoted into a durable artifact first. Writers stop when reality diverges; they do
|
|
63
|
+
not re-plan or debug beyond scope. After reconciling findings into briefs, every
|
|
64
|
+
retained finding names the brief item that carries it; a finding with no carrier is
|
|
65
|
+
a dropped finding.
|
|
60
66
|
|
|
61
67
|
A claim about a rendered or externally driven surface is evidenced by its capture
|
|
62
68
|
portfolio or a real foreign client driving it, never by source alone; the campaign
|
|
@@ -67,11 +67,41 @@ than aspirational:
|
|
|
67
67
|
- `app/browser` reaches server behavior only through shared `app/core` contracts and transports,
|
|
68
68
|
never through a server implementation import.
|
|
69
69
|
|
|
70
|
-
A generated `app/server` owns strict `
|
|
71
|
-
|
|
70
|
+
A generated `app/server` owns strict grouped `server.host`, `server.port`, and `server.timeout`
|
|
71
|
+
options plus the `APP_HOST`, `APP_PORT`, and `APP_START_TIMEOUT` environment boundaries. It
|
|
72
|
+
composes the installed router, server, and boundary/security/deadline middleware substrates around
|
|
73
|
+
a fresh `GET /health` dispatcher from `createApplicationDispatcher`, supports repeated start/stop
|
|
74
|
+
cycles and terminal destroy of both the server and its owned dispatcher, and writes exactly one
|
|
75
|
+
`[READY] <name> <url>` diagnostic after process-owned readiness. The process runner owns an emitter
|
|
76
|
+
whose `ApplicationServerRunnerEventMap` publishes `ready(url)` and `fail(error)`; initial
|
|
77
|
+
`ApplicationServerRunnerOptions.on` hooks run before the runner's own announcement and reporting
|
|
78
|
+
listeners; a synchronous fail hook sees an otherwise-unset `process.exitCode` as `undefined` before
|
|
79
|
+
the default reporter sets it to `1`. Concurrent stops join one substrate shutdown. In-process tests park on those events,
|
|
80
|
+
while child-process tests still observe the readiness line across the process boundary. Its exported
|
|
72
81
|
`reportApplicationServerError` handler writes only a stable configuration, lifecycle, or unknown
|
|
73
|
-
failure code; process-owned
|
|
74
|
-
other error context.
|
|
82
|
+
failure code; process-owned failures never serialize a rejected value, nested cause, stack,
|
|
83
|
+
secret, or other error context. `ApplicationState` extends middleware's `IdentifierState` and adds
|
|
84
|
+
only the connection fact. `ApplicationServer.url` is `undefined` until a real port is bound and
|
|
85
|
+
again after stop or destroy; the redundant `listening` projection is not part of the generated
|
|
86
|
+
interface. The runner narrows the post-start URL before writing `[READY]`, so it never announces a
|
|
87
|
+
stale or unbound address, and it stops the server as part of failing that narrowing rather than
|
|
88
|
+
leaving a bound listener without a shutdown owner. It also serializes every start and stop on one
|
|
89
|
+
lifecycle queue, so a stop waits for the startup it aborted to settle before closing the server,
|
|
90
|
+
and a restart issued during that shutdown is honoured after it rather than lost.
|
|
91
|
+
|
|
92
|
+
The health contract belongs to whichever layer both hosts can reach. While the server alone reads
|
|
93
|
+
it, `ApplicationRecord`, `APP_HEALTH_METHOD`, and `APP_HEALTH_PATH` stay declared in `app/server`.
|
|
94
|
+
The moment a blueprint declares `app/browser` beside `app/server` — a combination that already
|
|
95
|
+
requires `app/core` — those three declarations move to `app/core` and gain `APP_HEALTH_TIMEOUT`,
|
|
96
|
+
the `isApplicationRecord` guard, and `readApplicationHealth`. That one asynchronous read is the
|
|
97
|
+
whole browser/server boundary: it fetches the running server's health route, reads the body as
|
|
98
|
+
`unknown`, narrows it with the shared guard, and returns the shared `Application` identity or
|
|
99
|
+
`undefined` for an unreachable, slow, or off-contract answer. Nothing is duplicated by the move —
|
|
100
|
+
`app/server` imports the relocated contract from `@app/core`, and `app/browser` still never imports
|
|
101
|
+
a server module. The generated browser entry then mounts `mountBrowserApplication`, which performs
|
|
102
|
+
that single read before mounting and falls back to the locally configured identity when the
|
|
103
|
+
boundary yields `undefined`. A rejected mount reports the context-free
|
|
104
|
+
`[ERROR] Browser application failed`, the browser twin of that server-side discipline.
|
|
75
105
|
|
|
76
106
|
Every environment barrel is an export-star barrel: `index.ts` contains only `export * from './x.js'`
|
|
77
107
|
rows and nothing else. Named, default, namespace, and type-only barrel rows are absent by design,
|
|
@@ -416,7 +446,9 @@ From [`constants.ts`](../../src/core/constants.ts).
|
|
|
416
446
|
| `SCAFFOLD_RANGE` | const |
|
|
417
447
|
| `BASE_DEV_DEPENDENCIES` | const |
|
|
418
448
|
| `SOURCE_BROWSER_DEV_DEPENDENCIES` | const |
|
|
449
|
+
| `APP_DEV_DEPENDENCIES` | const |
|
|
419
450
|
| `APP_BROWSER_DEV_DEPENDENCIES` | const |
|
|
451
|
+
| `APP_SERVER_DEV_DEPENDENCIES` | const |
|
|
420
452
|
| `CHECKOUT_ACTION_SHA` | const |
|
|
421
453
|
| `SETUP_NODE_ACTION_SHA` | const |
|
|
422
454
|
| `COMPILER_ID` | const |
|
|
@@ -464,8 +496,11 @@ form. `HEX_PATTERN` requires whole lowercase byte pairs, and `SYNC_BASELINE_PATT
|
|
|
464
496
|
`MINIMUM_NODE_VERSION` is `22.12.0`, `DEFAULT_ENGINES` derives from it, and `DEFAULT_VERSION` is
|
|
465
497
|
`0.0.1`. `BASE_DEV_DEPENDENCIES` is the host-neutral tooling baseline every generated workspace
|
|
466
498
|
gets; `SOURCE_BROWSER_DEV_DEPENDENCIES` adds the real browser providers a published browser environment
|
|
467
|
-
needs
|
|
468
|
-
|
|
499
|
+
needs; `APP_DEV_DEPENDENCIES` is the baseline every private application environment gets;
|
|
500
|
+
`APP_BROWSER_DEV_DEPENDENCIES` adds the Vue toolchain and `@orkestrel/html` start-tag parser a
|
|
501
|
+
private browser application needs;
|
|
502
|
+
and `APP_SERVER_DEV_DEPENDENCIES` adds the emitter, middleware, router, and server packages a private
|
|
503
|
+
server application needs. Vite is minor-pinned at `~8.2.0`: the generated boundary consumes the reviewed
|
|
469
504
|
8.2 `CSSOptions`, `preprocessCSS`, and `isCSSRequest` surface, while the selected
|
|
470
505
|
`css.transformer` / `lightningcss` path is experimental and must not float into an unreviewed minor.
|
|
471
506
|
`SCAFFOLD_RANGE` is the range generated workspaces pin this package at.
|
|
@@ -788,6 +823,8 @@ From [`helpers.ts`](../../src/core/helpers.ts).
|
|
|
788
823
|
| `pascalCase` | function |
|
|
789
824
|
| `escapeHtmlText` | function |
|
|
790
825
|
| `serializeTypeScriptString` | function |
|
|
826
|
+
| `hasApplicationBoundary` | function |
|
|
827
|
+
| `hasApplicationShowcase` | function |
|
|
791
828
|
| `blueprintToMembers` | function |
|
|
792
829
|
| `catalogNames` | function |
|
|
793
830
|
| `alignTable` | function |
|
|
@@ -824,6 +861,7 @@ From [`helpers.ts`](../../src/core/helpers.ts).
|
|
|
824
861
|
| `renderArray` | function |
|
|
825
862
|
| `renderObject` | function |
|
|
826
863
|
| `renderValue` | function |
|
|
864
|
+
| `renderStringArray` | function |
|
|
827
865
|
| `formatJson` | function |
|
|
828
866
|
| `pinPlan` | function |
|
|
829
867
|
|
|
@@ -836,7 +874,9 @@ guard. `blueprint` fills the defaults: `version` and `engines` from their consta
|
|
|
836
874
|
derives the entity name from a lowercase-hyphen package name, and `blueprintToMembers` derives the
|
|
837
875
|
declared public `Member[]` — a full entity, options type, interface, and factory per published
|
|
838
876
|
environment, plus the exact declaration inventory each selected application environment
|
|
839
|
-
contributes.
|
|
877
|
+
contributes. `hasApplicationBoundary` recognizes exactly app/core + app/browser + app/server,
|
|
878
|
+
while `hasApplicationShowcase` requires showcase intent beside app/browser; plan assembly, tests,
|
|
879
|
+
guides, and member inventory share those predicates.
|
|
840
880
|
|
|
841
881
|
`escapeHtmlText` and `serializeTypeScriptString` are the two escaping leaves used when a
|
|
842
882
|
caller-supplied name reaches generated HTML or generated TypeScript source; the latter preserves
|
|
@@ -888,7 +928,12 @@ id is already registered: an identical plan is idempotent, while a distinct payl
|
|
|
888
928
|
`formatJson` and its leaves — `renderValue`,
|
|
889
929
|
`renderArray`, `renderObject`, `computeColumnWidth`, and `fitsPrintWidth` — emit JSON that matches the fleet
|
|
890
930
|
formatter byte for byte, collapsing a short array onto one line and breaking a long one, so
|
|
891
|
-
computed configuration JSON is format-stable by construction.
|
|
931
|
+
computed configuration JSON is format-stable by construction. `renderStringArray` applies the same
|
|
932
|
+
inline-or-broken width rule to single-quoted TypeScript string-array literals — with a trailing
|
|
933
|
+
comma on every broken line, matching `oxfmt`'s `trailingComma: "all"` for non-JSON files — so
|
|
934
|
+
generated TypeScript configuration is format-stable too. It serializes every string element through
|
|
935
|
+
`serializeTypeScriptString`, so quotes, backslashes, controls, and line separators remain inert in
|
|
936
|
+
both layouts.
|
|
892
937
|
|
|
893
938
|
### Helpers — server
|
|
894
939
|
|
|
@@ -1049,6 +1094,7 @@ From [`compilers.ts`](../../src/core/compilers.ts).
|
|
|
1049
1094
|
| `renderViteTest` | function |
|
|
1050
1095
|
| `viteHeader` | function |
|
|
1051
1096
|
| `policyViteProject` | function |
|
|
1097
|
+
| `configViteProject` | function |
|
|
1052
1098
|
| `guidesViteProject` | function |
|
|
1053
1099
|
| `binViteProject` | function |
|
|
1054
1100
|
| `integrationViteProject` | function |
|
|
@@ -1136,7 +1182,7 @@ formatter's 100-column fixed point: a complete registration-array line, includin
|
|
|
1136
1182
|
its trailing comma, stays collapsed when it fits and expands one entry per line otherwise.
|
|
1137
1183
|
`viteProjectRegistrations` is the one registration derivation every root shape consumes: it derives
|
|
1138
1184
|
the selected source and application projects from the canonical environment order, then appends
|
|
1139
|
-
`policy`, `guides`, and the optional `srcBin`, `integration`, and `service` projects.
|
|
1185
|
+
`policy`, `config`, `guides`, and the optional `srcBin`, `integration`, and `service` projects.
|
|
1140
1186
|
`viteProjectDefinitions` renders the standalone proof and structural-fact definitions in that same
|
|
1141
1187
|
order with one blank line between declarations. Both consume `ViteFacts`, so each optional project
|
|
1142
1188
|
is controlled only by its matching `bin`, `integration`, or `service` blueprint fact; the same
|
|
@@ -1149,8 +1195,9 @@ the root `srcCore` factory and its wrapper stay host-independent even when the w
|
|
|
1149
1195
|
browser target. `binTsconfig` emits the executable declaration scope; `rootViteConfig`,
|
|
1150
1196
|
`singleSrcViteConfig`, and `applicationViteConfig` emit the root configuration for a library-only,
|
|
1151
1197
|
single non-core `src` environment, and application-bearing workspace respectively; and
|
|
1152
|
-
`policyViteProject`, `guidesViteProject`, `integrationViteProject`, and
|
|
1153
|
-
the standalone Node proof projects, with `binViteProject` the single
|
|
1198
|
+
`policyViteProject`, `configViteProject`, `guidesViteProject`, `integrationViteProject`, and
|
|
1199
|
+
`serviceViteProject` emit the standalone Node proof projects, with `binViteProject` the single
|
|
1200
|
+
executable-project emitter. A
|
|
1154
1201
|
proof project is structurally derived from the directory holding its tests and never wraps a source
|
|
1155
1202
|
or application environment project. The guides project therefore uses only `tests/setup.ts`, never
|
|
1156
1203
|
`setupServer.ts`, `setupBrowser.ts`, or `setupService.ts`; and its `tests/src/**/*.test.ts` and
|
|
@@ -1540,13 +1587,13 @@ a fixed, interleaved order so aggregates sit immediately before their per-enviro
|
|
|
1540
1587
|
- `format`, `format:check`, `lint:check`
|
|
1541
1588
|
- `test`, then `test:src` and its per-environment scopes, the optional `test:integration`,
|
|
1542
1589
|
`test:equivalence`, and `test:service` proofs, `test:app` and its per-environment scopes, then
|
|
1543
|
-
`test:policy` and `test:guides`
|
|
1590
|
+
`test:policy`, `test:config`, and `test:guides`
|
|
1544
1591
|
- `build`, then `build:src` and its per-environment targets, `build:app` and its runtime targets, and
|
|
1545
1592
|
`build:host` for a bin workspace
|
|
1546
1593
|
- `dev` when a browser application is selected; `serve` and `serve:build` when a server application
|
|
1547
1594
|
is selected
|
|
1548
1595
|
- `showcase`, `build:showcase`, and `show` only when the physical showcase wrapper is present;
|
|
1549
|
-
`show` builds
|
|
1596
|
+
`show` formats, then builds, then copies `dist/showcase/index.html` to `demo/showcase.html`
|
|
1550
1597
|
- `prepublishOnly` chaining `format:check → lint:check → check → build → test`, followed by
|
|
1551
1598
|
`test:integration` when the integration axis is selected
|
|
1552
1599
|
|
|
@@ -1560,8 +1607,9 @@ only where `bin` and `integration` are both set:
|
|
|
1560
1607
|
| `test:equivalence` | no | no | no |
|
|
1561
1608
|
| `test:service` | no | never | after `scripts/service.sh` |
|
|
1562
1609
|
|
|
1563
|
-
No proof joins the default chain: `npm test` runs the source, application, policy,
|
|
1564
|
-
projects, and nothing there needs a build artifact or a foreign process.
|
|
1610
|
+
No opt-in proof joins the default chain: `npm test` runs the source, application, policy,
|
|
1611
|
+
configuration, and guide projects, and nothing there needs a build artifact or a foreign process.
|
|
1612
|
+
Publication is the one
|
|
1565
1613
|
asymmetry — `prepublishOnly` appends `test:integration`, because a package about to be published
|
|
1566
1614
|
should prove itself against its own built output, while `test:service` is never in that chain.
|
|
1567
1615
|
Neither default testing nor publication starts or requires a foreign process.
|
|
@@ -1633,10 +1681,16 @@ comments, text, raw blocks, attributes, adjacent tokens, casing, and user-author
|
|
|
1633
1681
|
byte-stable. The trusted preparation hook owns the final pre-parse phase; inline proxy code is
|
|
1634
1682
|
restored before module analysis, and the first normal post-parse hook restores the original HTML
|
|
1635
1683
|
spelling. The browser entry begins with a generated, byte-stable security prologue: the doctype,
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1684
|
+
head opening, and `Content-Security-Policy` meta markup, ordering, and indentation are exact. The
|
|
1685
|
+
opening `html` start tag is parsed by `@orkestrel/html`'s fail-closed `parseStartTag` boundary,
|
|
1686
|
+
so ASCII case and well-formed attributes such as `lang`, `data-bs-theme`, and `data-bs-core`
|
|
1687
|
+
may vary without weakening the position of the following head and policy. A malformed, incomplete,
|
|
1688
|
+
duplicate-attribute, wrong-name, or syntactically slashed root still fails closed. Preparation owns
|
|
1689
|
+
that positional check while the document is still generated bytes; the final trusted post-hook
|
|
1690
|
+
checks only that the exact
|
|
1691
|
+
policy survived because Vite may legitimately inject into the head. CRLF and LF files are both
|
|
1692
|
+
accepted. Vite's
|
|
1693
|
+
`%ENV%` HTML substitution is rejected
|
|
1640
1694
|
before parsing because Vite performs that expansion after every plugin pre-hook, where it could
|
|
1641
1695
|
otherwise create a late control attribute. The guard walks the exact left-to-right `%(\S+?)%`
|
|
1642
1696
|
tokens Vite recognizes instead of performing a substring search, and each preparation plugin owns
|
|
@@ -1685,15 +1739,31 @@ still rejects public directories, browser asset inlining, and output path overri
|
|
|
1685
1739
|
post-factory composition as defense in depth; that narrow check is not a general extension seam.
|
|
1686
1740
|
|
|
1687
1741
|
When the showcase fact is present, the generated root also exports closed
|
|
1688
|
-
`appShowcase(...config: never[])`; both factories reject every argument at runtime
|
|
1689
|
-
|
|
1742
|
+
`appShowcase(...config: never[])`; both factories reject every argument at runtime. The
|
|
1743
|
+
ordinary factory retains its strict
|
|
1690
1744
|
`script-src 'self'` policy, external asset auditing, and `dist/app/browser` output. The showcase
|
|
1691
|
-
factory
|
|
1745
|
+
factory is a standalone configuration with `base: './'`, unlimited asset inlining, and
|
|
1746
|
+
`dist/showcase` output. It applies `viteSingleFile` with
|
|
1692
1747
|
`removeViteModuleLoader: true` and `useRecommendedBuildConfig: true`, uses Oxc and Lightning CSS
|
|
1693
|
-
minification for an `esnext` build without source maps or module preload, and inserts a
|
|
1694
|
-
`build-id`
|
|
1695
|
-
|
|
1696
|
-
|
|
1748
|
+
minification for an `esnext` build without source maps or module preload, and inserts a SHA-256
|
|
1749
|
+
`build-id` derived from the secured, fully inlined document. An unchanged document therefore keeps
|
|
1750
|
+
the same id, while any changed byte changes it. The showcase development CSP keeps scripts
|
|
1751
|
+
same-origin and permits Vue's injected inline styles. Its built CSP swaps that script permission to
|
|
1752
|
+
inline and admits only inline styles plus data images and fonts, while both policies retain
|
|
1753
|
+
`default-src 'none'`, `script-src-attr 'none'`, `object-src 'none'`, and `base-uri 'none'`.
|
|
1754
|
+
|
|
1755
|
+
The showcase fact also emits its own entry pair, `app/browser/showcase.html` and
|
|
1756
|
+
`app/browser/showcase.ts`, beside the application's `index.html` and `main.ts`. Both HTML entries
|
|
1757
|
+
open with a generated security prologue: the application carries the ordinary strict policy and the
|
|
1758
|
+
showcase carries its development policy. The boundary plugins select and validate the matching
|
|
1759
|
+
prologue; the showcase build alone swaps in the self-contained policy before hashing and renames its
|
|
1760
|
+
single HTML output to `index.html`, which is what `show` copies to `demo/showcase.html`. The showcase entry
|
|
1761
|
+
mounts `mountShowcaseApplication`, and `app/browser/seeders.ts` exports exactly one seeder,
|
|
1762
|
+
`seedApplication`, returning a frozen identity of the same shape the shipped root view receives.
|
|
1763
|
+
The two mount factories differ in the seed expression alone. Both explicitly pass
|
|
1764
|
+
`{ name: seed.name }` to the same `createBrowserApplication` root: the showcase seed comes from
|
|
1765
|
+
`seedApplication()`, while the shipped application seed comes from `readApplicationHealth` with
|
|
1766
|
+
the configured identity as its fallback.
|
|
1697
1767
|
|
|
1698
1768
|
The browser development server applies the same trust boundary before Vite's internal middleware.
|
|
1699
1769
|
Its explicit filesystem allowlist contains only browser/core source roots, browser tests, their
|
|
@@ -1756,12 +1826,26 @@ it is not a general-purpose source analyzer. Generated workspaces receive the sa
|
|
|
1756
1826
|
module as a host-origin file and run it as a dedicated Node-only `policy` test project over
|
|
1757
1827
|
`tests/policy.test.ts`.
|
|
1758
1828
|
|
|
1829
|
+
**The configuration suite.** Policy reads source, the `config` project exercises the root
|
|
1830
|
+
configuration, and integration builds for real. Every generated workspace therefore receives a
|
|
1831
|
+
universal Node-only
|
|
1832
|
+
`config` project over `tests/config/**/*.test.ts`. Its base cases execute the root module's physical
|
|
1833
|
+
workspace containment and environment-direction helpers; conditional cases exercise output
|
|
1834
|
+
containment when the workspace builds, managed/system browser discovery when a browser environment
|
|
1835
|
+
exists, and the HTML/CSP boundary only for an application browser. Those cases import the generated
|
|
1836
|
+
root `vite.config.ts` itself, so a failure is repaired in the generator rather than patched into a
|
|
1837
|
+
consumer. The generated-consumer integration matrix remains the fidelity boundary for real builds;
|
|
1838
|
+
the configuration suite supplies deterministic edge coverage without duplicating build orchestration.
|
|
1839
|
+
When scaffold changes a generated configuration invariant, an existing consumer's `vite.config.ts`
|
|
1840
|
+
is intentionally reported stale until that consumer accepts the regenerated configuration and its
|
|
1841
|
+
matching config test.
|
|
1842
|
+
|
|
1759
1843
|
**Real browser capability.** Browser test projects are gated on one centralized discovery chain:
|
|
1760
1844
|
Playwright's pinned Chromium executable first, then a managed Chromium alias or cached revision,
|
|
1761
1845
|
then stable system Chrome, then stable system Edge. Managed candidates must be executable regular
|
|
1762
1846
|
files. System channels are selected only when their executable exists at Playwright's standard
|
|
1763
1847
|
Linux, macOS, or Windows installation location; custom installations are not guessed. The generated
|
|
1764
|
-
|
|
1848
|
+
configuration test consumes the same discovery helpers and accepts either an executable managed path or the
|
|
1765
1849
|
stable `chrome` / `msedge` channel, so it does not maintain a second heuristic.
|
|
1766
1850
|
|
|
1767
1851
|
A browser suite runs when any one of those real browser capabilities is available and is skipped
|
|
@@ -1900,7 +1984,10 @@ renderer behind the table and blockquote work; the template engine behind every
|
|
|
1900
1984
|
artifact; and, consumed only at the executable boundary, the terminal prompt toolkit and the console
|
|
1901
1985
|
reporter. The core face uses the first four and stays pure; the server face adds only `node:*`
|
|
1902
1986
|
builtins. Development dependencies are the shared tooling baseline plus the guide-parity toolkit
|
|
1903
|
-
that drives [`parity.test.ts`](../../tests/guides/src/parity.test.ts)
|
|
1987
|
+
that drives [`parity.test.ts`](../../tests/guides/src/parity.test.ts) and `@orkestrel/html`,
|
|
1988
|
+
which this package's real emitted-configuration tests execute. Generated manifests keep that HTML
|
|
1989
|
+
dependency scoped to `app/browser`; source-only, `app/core`, and `app/server` workspaces do not
|
|
1990
|
+
receive it. The engines floor is Node
|
|
1904
1991
|
`>=22.12.0`, and the build emits ES and CJS for both library faces plus an ES executable.
|
|
1905
1992
|
|
|
1906
1993
|
## Patterns
|
|
@@ -2245,6 +2332,7 @@ import {
|
|
|
2245
2332
|
appViteConfig,
|
|
2246
2333
|
applicationViteConfig,
|
|
2247
2334
|
binViteProject,
|
|
2335
|
+
configViteProject,
|
|
2248
2336
|
coreTsconfig,
|
|
2249
2337
|
coreViteConfig,
|
|
2250
2338
|
guidesViteProject,
|
|
@@ -2276,13 +2364,14 @@ coreViteConfig()
|
|
|
2276
2364
|
srcViteConfig('browser')
|
|
2277
2365
|
appViteConfig('server')
|
|
2278
2366
|
policyViteProject()
|
|
2367
|
+
configViteProject()
|
|
2279
2368
|
guidesViteProject()
|
|
2280
2369
|
binViteProject()
|
|
2281
2370
|
integrationViteProject({ bin: true, integration: true, global: true })
|
|
2282
2371
|
serviceViteProject()
|
|
2283
2372
|
viteProjectDefinitions({ integration: true }).includes('export const integration =') // true
|
|
2284
2373
|
viteProjectRegistrations(['core'], [], { integration: true }).map(({ project }) => project)
|
|
2285
|
-
// ['srcCore', 'policy', 'guides', 'integration']
|
|
2374
|
+
// ['srcCore', 'policy', 'config', 'guides', 'integration']
|
|
2286
2375
|
|
|
2287
2376
|
rootViteConfig(['core', 'server'], { bin: true })
|
|
2288
2377
|
singleSrcViteConfig('server').includes('srcServer') // true
|
|
@@ -2576,6 +2665,8 @@ isMissingPathError(caught) // true only for an ENOENT error
|
|
|
2576
2665
|
ids, the batch-overload semantics, and all-or-nothing list removal.
|
|
2577
2666
|
- [`tests/src/core/policy.test.ts`](../../tests/src/core/policy.test.ts) — the repository coding-law
|
|
2578
2667
|
policy module against this workspace and against deliberately hostile fixtures.
|
|
2668
|
+
- [`tests/config/vite.test.ts`](../../tests/config/vite.test.ts) — the executable root Vite
|
|
2669
|
+
invariants for workspace, environment, and output containment.
|
|
2579
2670
|
- [`tests/src/server/helpers.test.ts`](../../tests/src/server/helpers.test.ts) — containment,
|
|
2580
2671
|
digests, host staging, hydration, derivation, prune scanning, and the local catalog.
|
|
2581
2672
|
- [`tests/src/server/validators.test.ts`](../../tests/src/server/validators.test.ts) — the portable
|