@orkestrel/scaffold 0.0.5 → 0.0.6
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 +57 -22
- package/dist/bin/scaffold.js.map +1 -1
- package/dist/host/CLAUDE.md +18 -4
- package/dist/host/claude/rules/workspace.md +8 -8
- package/dist/host/guides/src/scaffold.md +105 -37
- package/dist/host/scripts/codex.sh +5 -0
- package/dist/host/tests/setupPolicy.ts +178 -4
- package/dist/src/core/index.cjs +120 -37
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +52 -13
- package/dist/src/core/index.d.ts +52 -13
- package/dist/src/core/index.js +119 -38
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +11 -5
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +4 -2
- package/dist/src/server/index.d.ts +4 -2
- package/dist/src/server/index.js +12 -6
- package/dist/src/server/index.js.map +1 -1
- package/package.json +5 -5
package/dist/host/CLAUDE.md
CHANGED
|
@@ -276,7 +276,13 @@ journal head as the recovery handle.
|
|
|
276
276
|
`tmp/codex/<unit>-brief.md`, one `codex exec --json` streaming to `tmp/codex/<unit>.jsonl`
|
|
277
277
|
with `--output-last-message`, foreground when it fits the shell cap, backgrounded with the
|
|
278
278
|
turn ended when it may not — the harness re-invocation is the wait; placeholder loops and
|
|
279
|
-
wait-promise reports are deviations.
|
|
279
|
+
wait-promise reports are deviations. Every exec names its working directory with `-C`, and an
|
|
280
|
+
exec rooted outside a trusted git repository dies at launch unless `--skip-git-repo-check` is
|
|
281
|
+
passed, so cross-repo and fleet-container work rooted outside a checkout always passes it. A
|
|
282
|
+
launch is not a launch until the journal grows past its header: the bridge confirms the event
|
|
283
|
+
stream advanced beyond the session-configured head
|
|
284
|
+
before it reports the exec started, and treats an instantly-dead journal as a failed launch
|
|
285
|
+
whose tail is the evidence. Recovery ladder on interruption: persisted-id
|
|
280
286
|
`codex-reply` re-emission → fresh CLI session with the same brief file → for an interrupted
|
|
281
287
|
CLI exec, the journal survives and the Orchestrator chooses resume or fresh.
|
|
282
288
|
- **The inverse bridge exists too:** Claude Code exposes `claude mcp serve`, registered in
|
|
@@ -297,9 +303,17 @@ journal head as the recovery handle.
|
|
|
297
303
|
- At the start of each live Cloud session the user runs `codex login --device-auth` and
|
|
298
304
|
completes ChatGPT approval in the browser. `scripts/codex.sh` only reports readiness; it
|
|
299
305
|
never installs, authenticates, logs out, reads the auth cache, or performs a model call.
|
|
300
|
-
-
|
|
301
|
-
|
|
302
|
-
|
|
306
|
+
- A probe that finds the binary present but authentication unavailable starts recovery in the
|
|
307
|
+
same turn instead of recording the bench dark and waiting: the Orchestrator backgrounds
|
|
308
|
+
`codex login --device-auth` with its output captured to `tmp/codex/login.log`, surfaces the
|
|
309
|
+
verification URL and one-time code to the user the moment they appear there, arms a watcher
|
|
310
|
+
on completion, and re-probes `codex login status` when it fires. The bench comes live
|
|
311
|
+
mid-session with no restart; a session that sits dark until the user asks for the login has
|
|
312
|
+
failed the probe, not the bench.
|
|
313
|
+
- If that recovery cannot complete — device login unavailable, declined, or expired — the
|
|
314
|
+
Codex bench is dark. Fall back to `planner`/`reviewer` (Opus 5) and `builder`, and say so.
|
|
315
|
+
Never substitute an API key, access token, copied `auth.json`, or another login flow unless
|
|
316
|
+
the user changes this policy.
|
|
303
317
|
|
|
304
318
|
Codex environment defaults:
|
|
305
319
|
|
|
@@ -123,14 +123,14 @@ then runs the configured scoped checks that prove environment isolation.
|
|
|
123
123
|
- Lint is a separate complementary gate; neither lint nor root checking replaces
|
|
124
124
|
environment-isolation checks.
|
|
125
125
|
|
|
126
|
-
| Scope | `lib` | `types` | Permitted host globals
|
|
127
|
-
| ---------------------------- | --------------------------------- | ----------------- |
|
|
128
|
-
| `src:core`, `app:core` | `["ESNext"]`
|
|
129
|
-
| `src:browser`, `app:browser` | `["ESNext","DOM","DOM.Iterable"]` | default | DOM; no Node
|
|
130
|
-
| `src:server`, `app:server` | `["ESNext"]` | `["node"]` | Node; no DOM
|
|
131
|
-
| `src:styles` | `["ESNext"]` | `["vite/client"]` | Vite SCSS module declaration only
|
|
132
|
-
|
|
133
|
-
Strict core is load-bearing. A host-dependent helper belongs in its host environment; for example, a `generateId`
|
|
126
|
+
| Scope | `lib` | `types` | Permitted host globals |
|
|
127
|
+
| ---------------------------- | --------------------------------- | ----------------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
|
|
128
|
+
| `src:core`, `app:core` | `["ESNext","WebWorker"]` | `[]` | WHATWG web interop: fetch family, streams, URL, Abort, encoders, crypto, timers, console, DOMException, structuredClone; no DOM, no Node |
|
|
129
|
+
| `src:browser`, `app:browser` | `["ESNext","DOM","DOM.Iterable"]` | default | DOM; no Node |
|
|
130
|
+
| `src:server`, `app:server` | `["ESNext"]` | `["node"]` | Node; no DOM |
|
|
131
|
+
| `src:styles` | `["ESNext"]` | `["vite/client"]` | Vite SCSS module declaration only |
|
|
132
|
+
|
|
133
|
+
Strict core is load-bearing. A host-dependent helper belongs in its host environment; for example, a `generateId` reading `node:crypto` belongs in server, not core. The worker-only globals — `name`, `onrtctransform`, `close`, `postMessage`, `dispatchEvent`, `location`, `onerror`, `onlanguagechange`, `onoffline`, `ononline`, `onrejectionhandled`, `onunhandledrejection`, `self`, `importScripts`, `fonts`, `caches`, `crossOriginIsolated`, `indexedDB`, `isSecureContext`, `origin`, `scheduler`, `createImageBitmap`, `reportError`, `cancelAnimationFrame`, `requestAnimationFrame`, `onmessage`, `onmessageerror`, `addEventListener`, and `removeEventListener` — are policy-fenced out of core sources, so the `WebWorker` declarations widen the interop surface without admitting a worker host.
|
|
134
134
|
|
|
135
135
|
Build/check config alignment:
|
|
136
136
|
|
|
@@ -29,7 +29,7 @@ that projection, expressed as data. Rendered defaults ship as versioned package
|
|
|
29
29
|
here rather than a hand edit in every workspace.
|
|
30
30
|
|
|
31
31
|
The module is mechanism, never product policy. The judgment calls — the name, the description, the
|
|
32
|
-
keywords, which src and app environments, which dependencies, any
|
|
32
|
+
keywords, which src and app environments, which dependencies, any artifact override —
|
|
33
33
|
belong to the caller. What this module supplies is the closed vocabularies, the variant matrix as
|
|
34
34
|
data, exact-record validation, a fail-closed gate, a deterministic pin, and lossless projections.
|
|
35
35
|
|
|
@@ -370,7 +370,9 @@ value lists behind their literal unions. `SRC_MATRIX` is the `src` environment m
|
|
|
370
370
|
data — each environment's `configs/src` files, test-project label, `exports` subpath, and build
|
|
371
371
|
formats. `APP_MATRIX` is its application sibling, adding the runtime entry where an environment produces
|
|
372
372
|
one (`app/browser/index.html`, `app/server/main.ts`). `HOST_PATHS` is the ordered list of
|
|
373
|
-
byte-copied host artifacts
|
|
373
|
+
byte-copied host artifacts, and it is the staging manifest rather than the per-plan carried set:
|
|
374
|
+
`stageHost` vendors every path on it, while each plan carries the subset `selectHostPaths` selects
|
|
375
|
+
for that one workspace.
|
|
374
376
|
|
|
375
377
|
The bounds are public because they are part of the contract, not implementation trivia.
|
|
376
378
|
`MAX_ARTIFACT_BYTES` caps one artifact at 5 MiB and `MAX_TOTAL_ARTIFACT_BYTES` caps one blueprint,
|
|
@@ -544,7 +546,8 @@ entity allocation.
|
|
|
544
546
|
then checks every override against the exact artifact set the plan would write. An override whose
|
|
545
547
|
`path` matches no planned artifact, targets a `host`-origin artifact, or targets the
|
|
546
548
|
blueprint-owned `package.json` publication boundary is a blocking question rather than a silent
|
|
547
|
-
no-op.
|
|
549
|
+
no-op. An override that clears all three lands a `warnings` entry naming the path it replaces — the
|
|
550
|
+
declaration is accepted, and it is never accepted silently.
|
|
548
551
|
|
|
549
552
|
### Validators — server
|
|
550
553
|
|
|
@@ -736,11 +739,13 @@ From [`helpers.ts`](../../src/core/helpers.ts).
|
|
|
736
739
|
| `contentByteLength` | function |
|
|
737
740
|
| `contentToHex` | function |
|
|
738
741
|
| `snapshotOf` | function |
|
|
742
|
+
| `selectHostPaths` | function |
|
|
739
743
|
| `findPathConflict` | function |
|
|
740
744
|
| `findFileConflict` | function |
|
|
741
745
|
| `validateDependencyArray` | function |
|
|
742
746
|
| `validateBlueprint` | function |
|
|
743
747
|
| `manifestToDependencies` | function |
|
|
748
|
+
| `manifestToName` | function |
|
|
744
749
|
| `rangeToFreshness` | function |
|
|
745
750
|
| `computeHash` | function |
|
|
746
751
|
| `stableStringify` | function |
|
|
@@ -786,14 +791,20 @@ report projections count with.
|
|
|
786
791
|
`snapshotOf`, `contentToHex`, `contentToBytes`, `contentByteLength`, `contentCodePoint`, and
|
|
787
792
|
`bytesToHex` are the host-independent byte leaves that make exact comparison possible without a
|
|
788
793
|
host encoder or buffer; an unpaired surrogate encodes as `U+FFFD` rather than throwing.
|
|
789
|
-
`
|
|
790
|
-
|
|
794
|
+
`selectHostPaths` is the one-owner filter plan assembly applies before it carries anything: it
|
|
795
|
+
returns the host paths in input order minus `guides/src/<name>.md`, so a workspace never plans a
|
|
796
|
+
vendored mirror of the guide it writes itself. `findPathConflict` finds the first exact or
|
|
797
|
+
case-insensitive collision in a path list, and `findFileConflict` additionally rejects a file that
|
|
798
|
+
would sit inside another planned path — the loud backstop behind that selection.
|
|
791
799
|
|
|
792
800
|
`validateBlueprint` and `validateDependencyArray` are the semantic pass. The array validator is
|
|
793
801
|
pure — it returns its questions and the set of names it saw, so the caller can apply the
|
|
794
802
|
cross-array overlap rules on top. `manifestToDependencies` reads a manifest's `dependencies`,
|
|
795
803
|
`devDependencies`, and `peerDependencies` in that order, keeps only own data sections and scoped
|
|
796
|
-
names, deduplicates, and never throws. `
|
|
804
|
+
names, deduplicates, and never throws. `manifestToName` is its self-reading sibling over the same
|
|
805
|
+
text: the manifest's own string `name`, or `undefined` when the text is oversized, malformed,
|
|
806
|
+
rootless, or nameless — the projection that lets a target recognize itself in its own declared
|
|
807
|
+
dependencies. `rangeToFreshness` applies the exact-pin comparison; the `missing` and `failed`
|
|
797
808
|
verdicts come from the fetch layer, never from this pure comparison.
|
|
798
809
|
|
|
799
810
|
`computeHash` is a deterministic FNV-1a digest and `stableStringify` a key-order-independent
|
|
@@ -904,7 +915,9 @@ dependency-less stand-in. Environments come from `src/<environment>/` and `app/<
|
|
|
904
915
|
scoped entries, with an optional peer recovered from `peerDependenciesMeta`; and `extras` is every
|
|
905
916
|
development dependency minus the generated baseline and minus anything already declared as a
|
|
906
917
|
dependency or peer, so a hand-added development dependency round-trips and stays audit-clean.
|
|
907
|
-
`overrides
|
|
918
|
+
Derivation yields no `overrides`: they are caller-time inputs, not repository state. A computed
|
|
919
|
+
artifact that must differ reveals a gap in the canon; the blueprint grows an axis for that
|
|
920
|
+
distinction rather than the repository forking the file.
|
|
908
921
|
|
|
909
922
|
`storagePath`, `stageHost`, `readHostManifest`, `locateHostSource`, `remapArtifactPath`, and
|
|
910
923
|
`hydratePlan` are the vendored-host path. `storagePath` maps a repo-relative path to its un-dotted
|
|
@@ -926,7 +939,9 @@ anything.
|
|
|
926
939
|
`consumeCatalogAllowance` decrements the single shared entry allowance and throws `TARGET` before an
|
|
927
940
|
over-budget traversal continues. `discoverPackages` requires a real, unlinked root and lists its
|
|
928
941
|
immediate child directories whose bounded manifest names a scoped package, skipping anything else
|
|
929
|
-
silently.
|
|
942
|
+
silently. A control-bearing child directory fails closed before its manifest is read and the
|
|
943
|
+
untrusted name is never reflected in the diagnostic. `catalogPackages` applies one allowance across
|
|
944
|
+
every root and directory rather than
|
|
930
945
|
resetting a per-root budget, then draws each description from the first paragraph of the first
|
|
931
946
|
blockquote of that package's own bounded guide via `guideToDescription`; a missing guide, an
|
|
932
947
|
unreadable or oversized one, or one with no blockquote yields an empty description rather than an
|
|
@@ -994,7 +1009,13 @@ optional metadata, and engines.
|
|
|
994
1009
|
|
|
995
1010
|
`rootTsconfig` emits the root compiler options and one path alias per declared environment;
|
|
996
1011
|
`coreTsconfig`, `srcTsconfig`, and `appTsconfig` emit the scoped configurations that remove the
|
|
997
|
-
wrong host's globals from each environment.
|
|
1012
|
+
wrong host's globals from each environment. A core scope is the interesting one: `lib` is
|
|
1013
|
+
`["ESNext", "WebWorker"]` and `types` stays `[]`, which declares the WHATWG surface that is
|
|
1014
|
+
identical across Node, browsers, and workers — `fetch` and its request/response/header types,
|
|
1015
|
+
streams, `URL`, `AbortController`, the text encoders, `crypto`, timers, `console`, `DOMException`,
|
|
1016
|
+
`structuredClone` — while leaving `document`, `window`, and every `node:*` type unresolvable. That
|
|
1017
|
+
is one declaration set for a host-independent module, not a host. `viteHeader` renders the shared
|
|
1018
|
+
header — the alias block
|
|
998
1019
|
derived from the tsconfig paths, plus the environment-boundary plugin — and `viteMachinery` is the
|
|
999
1020
|
one place the header's axes are derived, read by `rootViteConfig`, `singleSrcViteConfig`,
|
|
1000
1021
|
`applicationViteConfig`, and `configArtifacts` alike so no caller can invent a fourth answer.
|
|
@@ -1125,8 +1146,9 @@ The public methods of each behavioral interface, one table per type.
|
|
|
1125
1146
|
`Scaffolding`; the optional group selection scopes the plan to those artifact groups.
|
|
1126
1147
|
`audit(blueprint, current, groups?)` compiles and then diffs the resulting plan against the
|
|
1127
1148
|
caller-supplied current content; a gated blueprint returns `complete: false` with the gate's
|
|
1128
|
-
blocking questions and zero findings
|
|
1129
|
-
exposes the readonly
|
|
1149
|
+
blocking questions and zero findings, and a complete one carries the gate's advisories on that same
|
|
1150
|
+
`questions` field. `destroy()` is idempotent teardown. The interface also exposes the readonly
|
|
1151
|
+
`emitter`.
|
|
1130
1152
|
|
|
1131
1153
|
#### `PlanManagerInterface`
|
|
1132
1154
|
|
|
@@ -1190,12 +1212,14 @@ in-flight request. The interface also exposes the readonly `emitter`.
|
|
|
1190
1212
|
`compile` runs three stages in fixed order and records each as a `CompileRecord` carrying its input,
|
|
1191
1213
|
its output, whether it failed, and any error text.
|
|
1192
1214
|
|
|
1193
|
-
1. **draft** — `blueprintToPlan` selects the covered groups, drafts each group's artifacts,
|
|
1194
|
-
the host set
|
|
1215
|
+
1. **draft** — `blueprintToPlan` selects the covered groups, drafts each group's artifacts, carries
|
|
1216
|
+
the selected host set — every vendored host path except the workspace's own guide — applies
|
|
1217
|
+
overrides, and pins the draft. A throw here records a `draft` failure coded
|
|
1195
1218
|
`INVALID`, emits `error`, marks the remaining two stages skipped, and returns incomplete.
|
|
1196
1219
|
2. **gate** — `validatePlan` runs the semantic pass over the blueprint and checks every override
|
|
1197
|
-
against the drafted artifact set. Blocking questions fail the stage;
|
|
1198
|
-
vendored guide set
|
|
1220
|
+
against the drafted artifact set. Blocking questions fail the stage; an accepted override and a
|
|
1221
|
+
dependency outside the vendored guide set each contribute a non-blocking advisory question
|
|
1222
|
+
instead.
|
|
1199
1223
|
3. **pin** — a host-origin pointer artifact is appended for each non-vendored dependency, and
|
|
1200
1224
|
`pinPlan` fills `trace` and `hash` from the plan's own content.
|
|
1201
1225
|
|
|
@@ -1227,7 +1251,8 @@ how it is audited, and whether it may ever be overwritten.
|
|
|
1227
1251
|
- **`host`** — byte-copied from the vendored data root. These are the shared files a whole fleet
|
|
1228
1252
|
keeps identical: the root instruction documents and licence, the agent, rule, and skill
|
|
1229
1253
|
directories, the session scripts, the repository coding-law policy module, the byte-identical root
|
|
1230
|
-
dotfiles, and the two guide mirrors
|
|
1254
|
+
dotfiles, and the two line guide mirrors a workspace carries for contracts other than its own.
|
|
1255
|
+
`HOST_PATHS` is the exact vendored list; what a given plan carries is `selectHostPaths` of it.
|
|
1231
1256
|
- **`template`** — filled from a frozen template definition by a pure fill engine. These are
|
|
1232
1257
|
starter files: source stubs, test stubs, the starter guide, the README.
|
|
1233
1258
|
- **`computed`** — derived by this package's own combination logic. These are the structural files:
|
|
@@ -1256,18 +1281,31 @@ anything, so a mature workspace's hand-written source, tests, guides, and manife
|
|
|
1256
1281
|
overwritten with a stub. A consequence worth stating plainly: the generated
|
|
1257
1282
|
`.github/workflows/ci.yml` is a **computed** artifact, so **user-owned CI is never repaired**. Once
|
|
1258
1283
|
a workspace has its own workflow, that copy stands, and any change to it is an ordinary edit in that
|
|
1259
|
-
workspace.
|
|
1284
|
+
workspace. Audit still compares it because computed artifacts are content-aware canon. A legitimate
|
|
1285
|
+
difference that the blueprint cannot express is a canon gap: add the missing blueprint axis rather
|
|
1286
|
+
than forking the computed file in one repository.
|
|
1260
1287
|
|
|
1261
1288
|
Overrides respect the same boundary from the other direction. `applyOverrides` never replaces a
|
|
1262
1289
|
host-origin artifact and never replaces `package.json`; the gate turns either attempt — and an
|
|
1263
1290
|
override matching no planned artifact at all — into a blocking question rather than a silent no-op.
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1291
|
+
What survives those three refusals is applied and announced: the gate carries a non-blocking
|
|
1292
|
+
advisory naming each replaced path, and that advisory rides the `Scaffolding` and the `Audit` all
|
|
1293
|
+
the way through the library result.
|
|
1294
|
+
|
|
1295
|
+
Guide mirrors are the one place ownership is conditional, and the law is one owner per guide path.
|
|
1296
|
+
**A workspace mirrors every line guide except its own.** When the name matches — the guide package
|
|
1297
|
+
on `guides/src/guide.md`, this package on `guides/src/scaffold.md` — the workspace itself is the
|
|
1298
|
+
owner, keeping that path as its **template**-origin starter guide, and `selectHostPaths` drops the
|
|
1299
|
+
vendored mirror so the path is contributed exactly once. For every other contract the mirror is the
|
|
1300
|
+
owner: a dependency this package vendors a byte-identical mirror for gets a real host-origin copy of
|
|
1301
|
+
`guides/src/<short>.md`, contributed once whether it arrives through the host set or through the
|
|
1302
|
+
dependency, so a package depending on `@orkestrel/guide` plans one `guides/src/guide.md` rather than
|
|
1303
|
+
two. Any other dependency gets a host-origin _pointer_ artifact plus a non-blocking question, never
|
|
1304
|
+
a fabricated mirror; on materialization that pointer degrades to a short stub, and `scaffold pull`
|
|
1305
|
+
fetches the real thing. That degrade is scoped exactly to guide pointers: any other missing manifest
|
|
1306
|
+
entry means a corrupt or truncated vendored manifest, and fails closed. Selection is the law and
|
|
1307
|
+
`findFileConflict` is its backstop: two artifacts at one path refuse the plan rather than racing to
|
|
1308
|
+
be the last writer.
|
|
1271
1309
|
|
|
1272
1310
|
## Audit, repair, and prune
|
|
1273
1311
|
|
|
@@ -1317,10 +1355,15 @@ because there is nothing to explain.
|
|
|
1317
1355
|
`pull` is the target-aware composition. It reads the target's declared scoped dependencies from its
|
|
1318
1356
|
manifest, rejects any explicit selection the target does not declare, builds the reference map from
|
|
1319
1357
|
the target's own `guides/src/<short>.md` mirrors, fetches guides and versions under one shared
|
|
1320
|
-
allowance, and assembles a report whose `clean` flag requires both no drift and no failures.
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1358
|
+
allowance, and assembles a report whose `clean` flag requires both no drift and no failures. A
|
|
1359
|
+
target that declares itself is the one asymmetry, and it follows the same single-owner law: the
|
|
1360
|
+
guide pass drops the self dependency, so `pull` never fetches or writes a workspace's own contract
|
|
1361
|
+
guide over the copy that workspace owns, while the version pass keeps it and still reports its
|
|
1362
|
+
freshness. A `--live` audit reads upstream through the same two passes and applies the same
|
|
1363
|
+
self-exclusion, so the freshness a workspace reports about itself never depends on which verb asked.
|
|
1364
|
+
`write` then commits only the `behind` guides — never `current`, `missing`, or `failed`,
|
|
1365
|
+
none of which carry trustworthy content — under the same containment and precondition law
|
|
1366
|
+
`Materializer` enforces, including a baseline digest check against what is actually on disk.
|
|
1324
1367
|
|
|
1325
1368
|
`catalog` builds the fleet package catalog from three reads per entry. The registry's exact
|
|
1326
1369
|
organization package list is authoritative and unconditionally required. Each package's own registry
|
|
@@ -1362,10 +1405,20 @@ a fixed, interleaved order so aggregates sit immediately before their per-enviro
|
|
|
1362
1405
|
live generated-consumer integration gate for the scaffold engine itself
|
|
1363
1406
|
|
|
1364
1407
|
**Environment isolation.** Scoped TypeScript projects remove the wrong host's globals from each
|
|
1365
|
-
environment: core scopes carry
|
|
1366
|
-
and no Node; server scopes carry Node and no DOM.
|
|
1367
|
-
|
|
1368
|
-
|
|
1408
|
+
environment: core scopes carry the WHATWG web-interop surface and no host at all — no DOM, no Node,
|
|
1409
|
+
no `vite/client`; browser scopes carry DOM and no Node; server scopes carry Node and no DOM. The
|
|
1410
|
+
worker-only globals the `WebWorker` declarations would otherwise admit — `name`, `onrtctransform`,
|
|
1411
|
+
`close`, `postMessage`, `dispatchEvent`, `location`, `onerror`, `onlanguagechange`, `onoffline`,
|
|
1412
|
+
`ononline`, `onrejectionhandled`, `onunhandledrejection`, `self`, `importScripts`, `fonts`, `caches`,
|
|
1413
|
+
`crossOriginIsolated`, `indexedDB`, `isSecureContext`, `origin`, `scheduler`, `createImageBitmap`,
|
|
1414
|
+
`reportError`, `cancelAnimationFrame`, `requestAnimationFrame`, `onmessage`, `onmessageerror`,
|
|
1415
|
+
`addEventListener`, and `removeEventListener` — are fenced out of `src/core` and `app/core` sources
|
|
1416
|
+
by the policy suite, so the declarations widen what a host-independent module may call without
|
|
1417
|
+
widening where it may run. On every TypeScript bump, derive this list from the module-scope
|
|
1418
|
+
global-object `declare var` and `declare function` declarations in the installed
|
|
1419
|
+
`lib.webworker.d.ts`, then subtract values supplied by `lib.esnext*` or current Node globals. Lint
|
|
1420
|
+
restricts declared package, alias, and conventional relative imports in the same directions.
|
|
1421
|
+
Neither replaces the other, and neither replaces the build.
|
|
1369
1422
|
|
|
1370
1423
|
**The generated build boundary.** The emitted configuration carries an environment-boundary plugin
|
|
1371
1424
|
that resolves the real module graph rather than re-implementing a parser. **TypeScript and
|
|
@@ -1485,7 +1538,8 @@ cannot express — that a centralized module exports every top-level declaration
|
|
|
1485
1538
|
implementation files hold one class and no stray module-scope declaration, that no function is
|
|
1486
1539
|
declared inside another function outside a directly-passed callback, that interface properties are
|
|
1487
1540
|
readonly, that privacy is a runtime `#` field rather than a TypeScript modifier, that a barrel
|
|
1488
|
-
re-exports only through `export *`,
|
|
1541
|
+
re-exports only through `export *`, that a core source never names a worker-only global the
|
|
1542
|
+
`WebWorker` declarations expose, and that a computed dynamic import cannot smuggle a
|
|
1489
1543
|
cross-environment dependency past the declared import rules. Vue components are inspected for the
|
|
1490
1544
|
same evasions. It is a complement to lint and typecheck, never a second type system, and it is not a
|
|
1491
1545
|
general-purpose source analyzer. Generated workspaces receive the same exported policy module as a
|
|
@@ -1495,7 +1549,11 @@ host-origin file and run it as a dedicated Node-only `policy` test project over
|
|
|
1495
1549
|
**Real browser capability.** Browser test projects are gated on the real executable: the generated
|
|
1496
1550
|
configuration and the generated policy test both probe `existsSync(chromium.executablePath())`. A
|
|
1497
1551
|
browser suite runs when a real Chromium is installed and is skipped honestly when it is not, rather
|
|
1498
|
-
than being faked.
|
|
1552
|
+
than being faked. The gate is applied at registration, not only inside the project: without a
|
|
1553
|
+
Chromium the browser project is left out of the emitted `projects` list entirely, so the runner
|
|
1554
|
+
never has to reconcile a registered project whose include set resolves to nothing, and one printed
|
|
1555
|
+
warning names every omitted project label. A machine with a browser runs the browser suite; a
|
|
1556
|
+
machine without one runs the remaining projects and says so.
|
|
1499
1557
|
|
|
1500
1558
|
**Continuous integration.** The generated workflow runs on push and pull request, on
|
|
1501
1559
|
`ubuntu-latest`, with read-only contents permission, a 60-minute timeout, and a matrix that **tests
|
|
@@ -1903,6 +1961,7 @@ import {
|
|
|
1903
1961
|
hostGroup,
|
|
1904
1962
|
packageManifest,
|
|
1905
1963
|
paritySpecifiers,
|
|
1964
|
+
selectHostPaths,
|
|
1906
1965
|
sourceArtifacts,
|
|
1907
1966
|
srcVariant,
|
|
1908
1967
|
testArtifacts,
|
|
@@ -1912,6 +1971,7 @@ const spec = blueprint('router', { src: ['core'], app: ['core', 'server'] })
|
|
|
1912
1971
|
const members = blueprintToMembers(spec)
|
|
1913
1972
|
|
|
1914
1973
|
hostGroup('AGENTS.md') // 'docs'
|
|
1974
|
+
selectHostPaths(['guides/src/router.md', 'LICENSE'], spec.name) // ['LICENSE'] — never its own guide
|
|
1915
1975
|
srcVariant(['core', 'server']) // 'multi'
|
|
1916
1976
|
entryFields(['browser']).main // './dist/src/browser/index.js'
|
|
1917
1977
|
dualCondition('./dist/src/core/index')
|
|
@@ -1979,7 +2039,12 @@ applicationViteConfig([], ['core', 'server']).includes('appServer') // true
|
|
|
1979
2039
|
### Reading declared dependencies and comparing freshness
|
|
1980
2040
|
|
|
1981
2041
|
```ts
|
|
1982
|
-
import {
|
|
2042
|
+
import {
|
|
2043
|
+
isBehind,
|
|
2044
|
+
manifestToDependencies,
|
|
2045
|
+
manifestToName,
|
|
2046
|
+
rangeToFreshness,
|
|
2047
|
+
} from '@orkestrel/scaffold'
|
|
1983
2048
|
import {
|
|
1984
2049
|
guideStub,
|
|
1985
2050
|
packageShortName,
|
|
@@ -1988,6 +2053,7 @@ import {
|
|
|
1988
2053
|
} from '@orkestrel/scaffold/server'
|
|
1989
2054
|
|
|
1990
2055
|
manifestToDependencies('{"dependencies":{"@orkestrel/contract":"^0.0.7"}}')
|
|
2056
|
+
manifestToName('{"name":"@orkestrel/router"}') // '@orkestrel/router' — the target's own name
|
|
1991
2057
|
rangeToFreshness('^0.0.7', '0.0.7') // 'current'
|
|
1992
2058
|
isBehind(rangeToFreshness('^0.0.7', '0.0.9')) // true
|
|
1993
2059
|
|
|
@@ -2220,7 +2286,8 @@ isMissingPathError(caught) // true only for an ENOENT error
|
|
|
2220
2286
|
## Tests
|
|
2221
2287
|
|
|
2222
2288
|
- [`tests/src/core/helpers.test.ts`](../../tests/src/core/helpers.test.ts) — the pure leaves: table
|
|
2223
|
-
alignment, byte encoding, snapshots, conflicts, projections, hashing, and
|
|
2289
|
+
alignment, byte encoding, snapshots, host selection, conflicts, projections, hashing, and
|
|
2290
|
+
format-stable JSON.
|
|
2224
2291
|
- [`tests/src/core/builders.test.ts`](../../tests/src/core/builders.test.ts) — the blueprint,
|
|
2225
2292
|
dependency, override, and member builders, including optional-field omission.
|
|
2226
2293
|
- [`tests/src/core/validators.test.ts`](../../tests/src/core/validators.test.ts) — every guard and
|
|
@@ -2228,7 +2295,8 @@ isMissingPathError(caught) // true only for an ENOENT error
|
|
|
2228
2295
|
- [`tests/src/core/shapers.test.ts`](../../tests/src/core/shapers.test.ts) — per-shape guard
|
|
2229
2296
|
exactness, schema essentials, seeded generation, and parse round-trips.
|
|
2230
2297
|
- [`tests/src/core/compilers.test.ts`](../../tests/src/core/compilers.test.ts) — every drafted
|
|
2231
|
-
group, the manifest and exports combination rules,
|
|
2298
|
+
group, the manifest and exports combination rules, the one-owner guide law for a workspace that
|
|
2299
|
+
names a line guide, and the emitted configuration text.
|
|
2232
2300
|
- [`tests/src/core/Compiler.test.ts`](../../tests/src/core/Compiler.test.ts) — the three-stage
|
|
2233
2301
|
pipeline, the fail-closed gate, the emission sequences, and post-destroy behavior.
|
|
2234
2302
|
- [`tests/src/core/PlanManager.test.ts`](../../tests/src/core/PlanManager.test.ts) — content-hash
|
|
@@ -32,8 +32,10 @@ valid_effort() {
|
|
|
32
32
|
|
|
33
33
|
if codex login status >/dev/null 2>&1; then
|
|
34
34
|
auth="authentication ready"
|
|
35
|
+
recovery=""
|
|
35
36
|
else
|
|
36
37
|
auth="authentication unavailable"
|
|
38
|
+
recovery='codex login --device-auth'
|
|
37
39
|
fi
|
|
38
40
|
|
|
39
41
|
if valid_model "$analyst_model" &&
|
|
@@ -46,4 +48,7 @@ else
|
|
|
46
48
|
fi
|
|
47
49
|
|
|
48
50
|
echo "codex.sh: bench detected; ${auth}; ${routes}."
|
|
51
|
+
if [ -n "$recovery" ]; then
|
|
52
|
+
echo "codex.sh: to recover, run \`${recovery}\`."
|
|
53
|
+
fi
|
|
49
54
|
exit 0
|
|
@@ -55,6 +55,42 @@ export const DATA_SOURCE_FILES: readonly string[] = Object.freeze([
|
|
|
55
55
|
'validators.ts',
|
|
56
56
|
])
|
|
57
57
|
|
|
58
|
+
/** Worker-only value globals that WebWorker typing must not expose to core implementations. */
|
|
59
|
+
export const WORKER_SCOPE_VALUE_GLOBALS: readonly string[] = Object.freeze([
|
|
60
|
+
'name',
|
|
61
|
+
'onrtctransform',
|
|
62
|
+
'close',
|
|
63
|
+
'postMessage',
|
|
64
|
+
'dispatchEvent',
|
|
65
|
+
'location',
|
|
66
|
+
'onerror',
|
|
67
|
+
'onlanguagechange',
|
|
68
|
+
'onoffline',
|
|
69
|
+
'ononline',
|
|
70
|
+
'onrejectionhandled',
|
|
71
|
+
'onunhandledrejection',
|
|
72
|
+
'self',
|
|
73
|
+
'importScripts',
|
|
74
|
+
'fonts',
|
|
75
|
+
'caches',
|
|
76
|
+
'crossOriginIsolated',
|
|
77
|
+
'indexedDB',
|
|
78
|
+
'isSecureContext',
|
|
79
|
+
'origin',
|
|
80
|
+
'scheduler',
|
|
81
|
+
'createImageBitmap',
|
|
82
|
+
'reportError',
|
|
83
|
+
'cancelAnimationFrame',
|
|
84
|
+
'requestAnimationFrame',
|
|
85
|
+
'onmessage',
|
|
86
|
+
'onmessageerror',
|
|
87
|
+
'addEventListener',
|
|
88
|
+
'removeEventListener',
|
|
89
|
+
])
|
|
90
|
+
|
|
91
|
+
/** Virtual source text used while binding one policy-inspected module. */
|
|
92
|
+
export const POLICY_SOURCE_TEXTS: Map<string, string> = new Map()
|
|
93
|
+
|
|
58
94
|
/** One script block extracted from a Vue SFC by the official compiler. */
|
|
59
95
|
export interface VueScriptBlockInterface {
|
|
60
96
|
readonly content: string
|
|
@@ -162,6 +198,126 @@ export function hasAllowedTripleSlashReference(path: string, source: ts.SourceFi
|
|
|
162
198
|
)
|
|
163
199
|
}
|
|
164
200
|
|
|
201
|
+
/**
|
|
202
|
+
* Whether the policy compiler can read one source path.
|
|
203
|
+
*
|
|
204
|
+
* @param path - The source path to inspect
|
|
205
|
+
* @returns `true` when the virtual or physical source exists
|
|
206
|
+
*/
|
|
207
|
+
export function hasPolicySource(path: string): boolean {
|
|
208
|
+
return POLICY_SOURCE_TEXTS.has(path) || ts.sys.fileExists(path)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/**
|
|
212
|
+
* Read one virtual or physical policy source.
|
|
213
|
+
*
|
|
214
|
+
* @param path - The source path to read
|
|
215
|
+
* @returns The source text when present
|
|
216
|
+
*/
|
|
217
|
+
export function readPolicySource(path: string): string | undefined {
|
|
218
|
+
return POLICY_SOURCE_TEXTS.get(path) ?? ts.sys.readFile(path)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
/**
|
|
222
|
+
* Parse one virtual or physical policy source for the compiler host.
|
|
223
|
+
*
|
|
224
|
+
* @param path - The source path to parse
|
|
225
|
+
* @param language - The requested TypeScript language target
|
|
226
|
+
* @returns The parsed source file when present
|
|
227
|
+
*/
|
|
228
|
+
export function createPolicySource(
|
|
229
|
+
path: string,
|
|
230
|
+
language: ts.ScriptTarget | ts.CreateSourceFileOptions,
|
|
231
|
+
): ts.SourceFile | undefined {
|
|
232
|
+
const content = readPolicySource(path)
|
|
233
|
+
return content === undefined ? undefined : ts.createSourceFile(path, content, language, true)
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Bind one policy-inspected module without loading ambient host declarations.
|
|
238
|
+
*
|
|
239
|
+
* @param path - The source path used in diagnostics
|
|
240
|
+
* @param content - The TypeScript source text to bind
|
|
241
|
+
* @returns A one-file TypeScript program whose checker resolves lexical bindings
|
|
242
|
+
*/
|
|
243
|
+
export function createPolicyProgram(path: string, content: string): ts.Program {
|
|
244
|
+
const options: ts.CompilerOptions = {
|
|
245
|
+
allowJs: true,
|
|
246
|
+
noLib: true,
|
|
247
|
+
noResolve: true,
|
|
248
|
+
target: ts.ScriptTarget.Latest,
|
|
249
|
+
types: [],
|
|
250
|
+
}
|
|
251
|
+
POLICY_SOURCE_TEXTS.set(path, content)
|
|
252
|
+
const host = ts.createCompilerHost(options)
|
|
253
|
+
host.fileExists = hasPolicySource
|
|
254
|
+
host.readFile = readPolicySource
|
|
255
|
+
host.getSourceFile = createPolicySource
|
|
256
|
+
const program = ts.createProgram([path], options, host)
|
|
257
|
+
POLICY_SOURCE_TEXTS.delete(path)
|
|
258
|
+
return program
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Whether an identifier is a standalone runtime value reference.
|
|
263
|
+
*
|
|
264
|
+
* @param node - The identifier occurrence to classify
|
|
265
|
+
* @param checker - The binder used to distinguish lexical values from ambient globals
|
|
266
|
+
* @returns `true` only when the occurrence reads or writes a runtime value
|
|
267
|
+
*/
|
|
268
|
+
export function isValueReferenceIdentifier(node: ts.Identifier, checker: ts.TypeChecker): boolean {
|
|
269
|
+
if (ts.isPartOfTypeNode(node)) return false
|
|
270
|
+
|
|
271
|
+
let ancestor: ts.Node = node.parent
|
|
272
|
+
while (!ts.isSourceFile(ancestor) && !ts.isStatement(ancestor)) {
|
|
273
|
+
if (ts.isTypeQueryNode(ancestor)) return false
|
|
274
|
+
if (ts.isComputedPropertyName(ancestor) && ts.isTypeElement(ancestor.parent)) return false
|
|
275
|
+
ancestor = ancestor.parent
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const parent = node.parent
|
|
279
|
+
if (
|
|
280
|
+
(ts.isPropertyAccessExpression(parent) && parent.name === node) ||
|
|
281
|
+
(ts.isPropertyAssignment(parent) && parent.name === node) ||
|
|
282
|
+
(ts.isPropertyDeclaration(parent) && parent.name === node) ||
|
|
283
|
+
(ts.isPropertySignature(parent) && parent.name === node) ||
|
|
284
|
+
(ts.isMethodDeclaration(parent) && parent.name === node) ||
|
|
285
|
+
(ts.isMethodSignature(parent) && parent.name === node) ||
|
|
286
|
+
(ts.isGetAccessorDeclaration(parent) && parent.name === node) ||
|
|
287
|
+
(ts.isSetAccessorDeclaration(parent) && parent.name === node) ||
|
|
288
|
+
(ts.isVariableDeclaration(parent) && parent.name === node) ||
|
|
289
|
+
(ts.isParameter(parent) && parent.name === node) ||
|
|
290
|
+
(ts.isBindingElement(parent) && (parent.name === node || parent.propertyName === node)) ||
|
|
291
|
+
(ts.isFunctionDeclaration(parent) && parent.name === node) ||
|
|
292
|
+
(ts.isFunctionExpression(parent) && parent.name === node) ||
|
|
293
|
+
(ts.isClassDeclaration(parent) && parent.name === node) ||
|
|
294
|
+
(ts.isClassExpression(parent) && parent.name === node) ||
|
|
295
|
+
(ts.isInterfaceDeclaration(parent) && parent.name === node) ||
|
|
296
|
+
(ts.isTypeAliasDeclaration(parent) && parent.name === node) ||
|
|
297
|
+
(ts.isTypeParameterDeclaration(parent) && parent.name === node) ||
|
|
298
|
+
(ts.isEnumDeclaration(parent) && parent.name === node) ||
|
|
299
|
+
(ts.isEnumMember(parent) && parent.name === node) ||
|
|
300
|
+
(ts.isModuleDeclaration(parent) && parent.name === node) ||
|
|
301
|
+
ts.isImportClause(parent) ||
|
|
302
|
+
ts.isImportSpecifier(parent) ||
|
|
303
|
+
ts.isNamespaceImport(parent) ||
|
|
304
|
+
ts.isImportEqualsDeclaration(parent) ||
|
|
305
|
+
ts.isExportSpecifier(parent) ||
|
|
306
|
+
ts.isNamespaceExport(parent) ||
|
|
307
|
+
ts.isNamespaceExportDeclaration(parent) ||
|
|
308
|
+
(ts.isLabeledStatement(parent) && parent.label === node) ||
|
|
309
|
+
(ts.isBreakOrContinueStatement(parent) && parent.label === node) ||
|
|
310
|
+
(ts.isJsxAttribute(parent) && parent.name === node)
|
|
311
|
+
) {
|
|
312
|
+
return false
|
|
313
|
+
}
|
|
314
|
+
return (
|
|
315
|
+
(ts.isShorthandPropertyAssignment(parent)
|
|
316
|
+
? checker.getShorthandAssignmentValueSymbol(parent)
|
|
317
|
+
: checker.getSymbolAtLocation(node)) === undefined
|
|
318
|
+
)
|
|
319
|
+
}
|
|
320
|
+
|
|
165
321
|
/** Inspect a Vue single-file component for syntax that can bypass declared import policy. */
|
|
166
322
|
export function inspectVueCodingLaw(
|
|
167
323
|
path: string,
|
|
@@ -187,7 +343,22 @@ export function inspectVueCodingLaw(
|
|
|
187
343
|
}
|
|
188
344
|
|
|
189
345
|
/** Add syntax-wide coding-law violations while traversing one source tree. */
|
|
190
|
-
export function inspectCodingNode(
|
|
346
|
+
export function inspectCodingNode(
|
|
347
|
+
path: string,
|
|
348
|
+
node: ts.Node,
|
|
349
|
+
violations: string[],
|
|
350
|
+
checker: ts.TypeChecker,
|
|
351
|
+
): void {
|
|
352
|
+
if (
|
|
353
|
+
/^(?:app|src)[\\/]core[\\/]/u.test(path) &&
|
|
354
|
+
ts.isIdentifier(node) &&
|
|
355
|
+
WORKER_SCOPE_VALUE_GLOBALS.includes(node.text) &&
|
|
356
|
+
isValueReferenceIdentifier(node, checker)
|
|
357
|
+
) {
|
|
358
|
+
violations.push(
|
|
359
|
+
`${path}:${formatPolicyPosition(node)} forbids worker-scope global ${node.text} in core`,
|
|
360
|
+
)
|
|
361
|
+
}
|
|
191
362
|
if (
|
|
192
363
|
ts.isAsExpression(node) ||
|
|
193
364
|
ts.isTypeAssertionExpression(node) ||
|
|
@@ -242,13 +413,16 @@ export function inspectCodingNode(path: string, node: ts.Node, violations: strin
|
|
|
242
413
|
) {
|
|
243
414
|
violations.push(`${path}:${formatPolicyPosition(node)} forbids hidden function assignments`)
|
|
244
415
|
}
|
|
245
|
-
ts.forEachChild(node, (child) => inspectCodingNode(path, child, violations))
|
|
416
|
+
ts.forEachChild(node, (child) => inspectCodingNode(path, child, violations, checker))
|
|
246
417
|
}
|
|
247
418
|
|
|
248
419
|
/** Inspect one TypeScript source module for repository coding-law violations. */
|
|
249
420
|
export function inspectCodingLaw(path: string, content: string): readonly string[] {
|
|
250
421
|
const violations: string[] = []
|
|
251
|
-
const
|
|
422
|
+
const program = createPolicyProgram(path, content)
|
|
423
|
+
const source = program.getSourceFile(path)
|
|
424
|
+
if (source === undefined) throw new Error(`Policy source was not bound at ${path}`)
|
|
425
|
+
const checker = program.getTypeChecker()
|
|
252
426
|
const file = basename(path)
|
|
253
427
|
|
|
254
428
|
if (/\.[cm]?jsx?$/u.test(path)) {
|
|
@@ -362,7 +536,7 @@ export function inspectCodingLaw(path: string, content: string): readonly string
|
|
|
362
536
|
}
|
|
363
537
|
}
|
|
364
538
|
|
|
365
|
-
inspectCodingNode(path, source, violations)
|
|
539
|
+
inspectCodingNode(path, source, violations, checker)
|
|
366
540
|
return violations
|
|
367
541
|
}
|
|
368
542
|
|