@orkestrel/scaffold 0.0.85 → 0.0.87
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/dist/agents/skills/orkestrel-publish/scripts/wave.js +16 -2
- package/dist/bin/main.js +173 -23
- package/dist/bin/main.js.map +1 -1
- package/dist/host/agents/skills/orkestrel-publish/SKILL.md +6 -6
- package/dist/host/agents/skills/orkestrel-publish/references/wave.md +3 -2
- package/dist/host/agents/skills/orkestrel-publish/scripts/wave.ts +20 -7
- package/dist/host/claude/agents/orkestrel.md +2 -2
- package/dist/host/claude/rules/architecture.md +15 -6
- package/dist/host/claude/rules/names.md +13 -11
- package/dist/host/configs/policy.ts +86 -27
- package/dist/host/dotfiles/oxlintrc.json +1 -0
- package/dist/host/guides/guide.md +13 -8
- package/dist/host/guides/scaffold.md +32 -7
- package/dist/host/manifest.json +12 -12
- package/dist/host/scripts/codex.sh +0 -0
- package/dist/host/scripts/cursor.sh +0 -0
- package/dist/host/scripts/deps.sh +0 -0
- package/dist/host/scripts/ollama.sh +0 -0
- package/dist/host/tests/config.test.ts +142 -0
- package/dist/src/core/index.cjs +182 -2
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +39 -0
- package/dist/src/core/index.d.ts +39 -0
- package/dist/src/core/index.js +182 -3
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +31 -7
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +19 -6
- package/dist/src/server/index.d.ts +19 -6
- package/dist/src/server/index.js +32 -8
- package/dist/src/server/index.js.map +1 -1
- package/package.json +2 -2
|
@@ -20,12 +20,12 @@ description: Run an Orkestrel release from layer order to registry confirmation.
|
|
|
20
20
|
|
|
21
21
|
## Scripts
|
|
22
22
|
|
|
23
|
-
| Script | Does
|
|
24
|
-
| -------------------- |
|
|
25
|
-
| `scripts/compare.ts` | `[--package NAME] [--version V] [--tarball PATH] [--dist dist] [--json]`, run from the package root after its build: fetches the published tarball and lists the material differences from the rebuilt `dist/` (sourcemaps excluded, whitespace-only differences ignored). Exit 0 when nothing differs, 3 when something does, 2 when the tarball cannot be read, 64 on usage (no package name, no `dist/`, or `--tarball` with no path).
|
|
26
|
-
| `scripts/pins.ts` | `--version PRIOR [--range PRIOR_RANGE ...] [--paths src,tests] [--json]`, run from the package root: lists every line under the paths carrying the prior version literal or a prior range literal, once per line, sweeping a one-comparator range such as `^8.3.0` for its bare version in any form. Exit 0 with no hits, 3 with hits, 64 on usage (no literal, or a flag with no value).
|
|
27
|
-
| `scripts/wave.ts` | `--visit [--target DIR] [--offline] [--from STEP] [--to STEP] [--prior VERSION] [--dry-run] [--json] [--out FILE]` runs one repository's visit in the order [wave.md](references/wave.md) § Visit a repository fixes (pin, commit, overwrite, verify, install, pins, format, gates, compare), stops at the first failing step, and prints each step's exit, the bump ruling (the rebuilt dist against the published tarball, and the final runtime dependency set against the published manifest), and a row for `.orkestrel/release.md`; `--prior` names the version a pre-ruled bump replaced so the self-pin sweep finds it; `--plan` prints the packages by layer from the catalog table. Exit 0 when every step passed, 1 when one failed, 2 when a reading failed, 64 on usage. |
|
|
28
|
-
| `scripts/window.ts` | `--whoami [--wait SECONDS]` reads or polls `npm whoami`; `--login` prints the operator's login command and polls `whoami` until it answers; `--publish DIR... --otp CODE` uploads back to back with `npm publish --ignore-scripts --otp`, journals each upload under `tmp/units/`, stops at the first refusal naming where to resume, and confirms each accepted version against the registry; `--confirm NAME@VERSION...` re-reads the registry until it serves the version. Exit 0 on success, 3 when not.
|
|
23
|
+
| Script | Does |
|
|
24
|
+
| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
25
|
+
| `scripts/compare.ts` | `[--package NAME] [--version V] [--tarball PATH] [--dist dist] [--json]`, run from the package root after its build: fetches the published tarball and lists the material differences from the rebuilt `dist/` (sourcemaps excluded, whitespace-only differences ignored). Exit 0 when nothing differs, 3 when something does, 2 when the tarball cannot be read, 64 on usage (no package name, no `dist/`, or `--tarball` with no path). |
|
|
26
|
+
| `scripts/pins.ts` | `--version PRIOR [--range PRIOR_RANGE ...] [--paths src,tests] [--json]`, run from the package root: lists every line under the paths carrying the prior version literal or a prior range literal, once per line, sweeping a one-comparator range such as `^8.3.0` for its bare version in any form. Exit 0 with no hits, 3 with hits, 64 on usage (no literal, or a flag with no value). |
|
|
27
|
+
| `scripts/wave.ts` | `--visit [--target DIR] [--offline] [--from STEP] [--to STEP] [--prior VERSION] [--dry-run] [--json] [--out FILE]` runs one repository's visit in the order [wave.md](references/wave.md) § Visit a repository fixes (pin, commit, overwrite, verify, install, pins, format, gates, compare), stops at the first failing step, and prints each step's exit (a failed step names the `code: message` refusal or the `note` a JSON verb writes on stdout, else the last stderr lines), the bump ruling (the rebuilt dist against the published tarball, and the final runtime dependency set against the published manifest), and a row for `.orkestrel/release.md`; `--prior` names the version a pre-ruled bump replaced so the self-pin sweep finds it; `--plan` prints the packages by layer from the catalog table. Exit 0 when every step passed, 1 when one failed, 2 when a reading failed, 64 on usage. |
|
|
28
|
+
| `scripts/window.ts` | `--whoami [--wait SECONDS]` reads or polls `npm whoami`; `--login` prints the operator's login command and polls `whoami` until it answers; `--publish DIR... --otp CODE` uploads back to back with `npm publish --ignore-scripts --otp`, journals each upload under `tmp/units/`, stops at the first refusal naming where to resume, and confirms each accepted version against the registry; `--confirm NAME@VERSION...` re-reads the registry until it serves the version. Exit 0 on success, 3 when not. |
|
|
29
29
|
|
|
30
30
|
The user's current instruction wins. `references/release.md` owns the credential
|
|
31
31
|
and authorization law; nothing here weakens it.
|
|
@@ -35,8 +35,9 @@ by hand only to repair it, then resume with `--from <step>`.
|
|
|
35
35
|
again. Keep a local MCP server registration outside the repository rather than at `.mcp.json`.
|
|
36
36
|
4. Force-verify every `@orkestrel` range against a registry sweep taken after the previous layer
|
|
37
37
|
published.
|
|
38
|
-
5. Run the full install. The overwrite re-declares the toolchain ranges
|
|
39
|
-
install regenerated no longer matches the
|
|
38
|
+
5. Run the full install. The overwrite re-declares the toolchain ranges and declares each planned
|
|
39
|
+
dependency the manifest lacks, so the lockfile the first install regenerated no longer matches the
|
|
40
|
+
manifest.
|
|
40
41
|
6. Sweep the self-pins, per § Sweep the self-pins: the re-pin moves the snapshot class.
|
|
41
42
|
7. Run the mutating `format` script to converge generated writes.
|
|
42
43
|
8. Run the quality gates.
|
|
@@ -14,11 +14,12 @@
|
|
|
14
14
|
// fatal), format, gates (format:check, lint:check, check, build, test), and compare (the rebuilt
|
|
15
15
|
// dist against the published tarball, and the final runtime dependency set against the published
|
|
16
16
|
// manifest `npm view NAME --json` serves; both readings are reported, not fatal). The summary
|
|
17
|
-
// carries each step's exit and duration,
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
//
|
|
21
|
-
//
|
|
17
|
+
// carries each step's exit and duration, a failed step's refusal (the `code: message` envelope or
|
|
18
|
+
// the `note` a JSON verb prints on stdout, else the last stderr lines), the bump ruling, and a
|
|
19
|
+
// Markdown row for `.orkestrel/release.md`. --dry-run prints the commands and runs nothing, so its
|
|
20
|
+
// ruling is unanswered. --plan reads the marker-bounded catalog table in
|
|
21
|
+
// `.claude/agents/orkestrel.md` and prints the packages by layer. Exit 0 when every step passed, 1
|
|
22
|
+
// when a step failed, 2 when a reading failed, 64 on usage.
|
|
22
23
|
import type { SpawnSyncReturns } from 'node:child_process'
|
|
23
24
|
import { spawnSync } from 'node:child_process'
|
|
24
25
|
import { existsSync, readFileSync, mkdirSync, writeFileSync } from 'node:fs'
|
|
@@ -158,8 +159,20 @@ function describeCommand(file: string, args: readonly string[]): string {
|
|
|
158
159
|
return [file === process.execPath ? 'node' : file, ...args].join(' ')
|
|
159
160
|
}
|
|
160
161
|
|
|
162
|
+
// A JSON verb writes its refusal envelope or its partial-run note on stdout, so the stderr tail
|
|
163
|
+
// alone reads empty for exactly the failures the operator needs named.
|
|
161
164
|
function describeFailure(result: SpawnSyncReturns<string>): string | undefined {
|
|
162
165
|
if (result.status === 0) return undefined
|
|
166
|
+
const value = readJsonObject(result.stdout)
|
|
167
|
+
const error = value?.error
|
|
168
|
+
if (typeof error === 'object' && error !== null && !Array.isArray(error)) {
|
|
169
|
+
const envelope = Object.fromEntries(Object.entries(error))
|
|
170
|
+
const code = readString(envelope, 'code')
|
|
171
|
+
const message = readString(envelope, 'message')
|
|
172
|
+
if (code !== undefined && message !== undefined) return `${code}: ${message}`
|
|
173
|
+
}
|
|
174
|
+
const note = readString(value, 'note')
|
|
175
|
+
if (note !== undefined) return note
|
|
163
176
|
return result.stderr
|
|
164
177
|
.trim()
|
|
165
178
|
.split(/\r\n|\n/)
|
|
@@ -341,11 +354,11 @@ function runOverwrite(
|
|
|
341
354
|
runner.target,
|
|
342
355
|
)
|
|
343
356
|
if (runner.offline && written !== undefined && written.status === 1) {
|
|
344
|
-
const note = readString(readJsonObject(written.stdout)
|
|
357
|
+
const note = readString(readJsonObject(written.stdout), 'note')
|
|
345
358
|
if (note !== undefined && note.includes(OFFLINE_REFUSAL)) {
|
|
346
359
|
replaceLastStep(runner, 0, 'offline overwrite skipped the catalog step by design')
|
|
347
360
|
} else {
|
|
348
|
-
replaceLastStep(runner, 1,
|
|
361
|
+
replaceLastStep(runner, 1, describeFailure(written))
|
|
349
362
|
}
|
|
350
363
|
}
|
|
351
364
|
if (!failed(runner)) {
|
|
@@ -57,7 +57,7 @@ so network-controlled descriptions never enter agent instruction context.
|
|
|
57
57
|
| `@orkestrel/database` | `0.0.16` | L2 | `@orkestrel/sqlite` `^0.0.13`, `@orkestrel/emitter` `^0.0.11`, `@orkestrel/contract` `^0.0.18`, `@orkestrel/indexeddb` `^0.0.13` | |
|
|
58
58
|
| `@orkestrel/emitter` | `0.0.11` | L1 | `@orkestrel/contract` `^0.0.18` | |
|
|
59
59
|
| `@orkestrel/form` | `0.0.8` | L2 | `@orkestrel/emitter` `^0.0.11`, `@orkestrel/contract` `^0.0.18` | |
|
|
60
|
-
| `@orkestrel/guide` | `0.0.
|
|
60
|
+
| `@orkestrel/guide` | `0.0.24` | L3 | `@orkestrel/contract` `^0.0.19`, `@orkestrel/markdown` `^0.0.17` | |
|
|
61
61
|
| `@orkestrel/html` | `0.0.12` | L1 | `@orkestrel/contract` `^0.0.19` | |
|
|
62
62
|
| `@orkestrel/indexeddb` | `0.0.13` | L1 | `@orkestrel/contract` `^0.0.18` | |
|
|
63
63
|
| `@orkestrel/interpret` | `0.0.15` | L3 | `@orkestrel/reason` `^0.0.12`, `@orkestrel/emitter` `^0.0.11`, `@orkestrel/contract` `^0.0.18`, `@orkestrel/template` `^0.0.9` | |
|
|
@@ -78,7 +78,7 @@ so network-controlled descriptions never enter agent instruction context.
|
|
|
78
78
|
| `@orkestrel/reason` | `0.0.12` | L2 | `@orkestrel/emitter` `^0.0.11`, `@orkestrel/contract` `^0.0.18` | |
|
|
79
79
|
| `@orkestrel/relation` | `0.0.14` | L3 | `@orkestrel/emitter` `^0.0.11`, `@orkestrel/contract` `^0.0.18`, `@orkestrel/database` `^0.0.16` | |
|
|
80
80
|
| `@orkestrel/router` | `0.0.16` | L2 | `@orkestrel/abort` `^0.0.12`, `@orkestrel/emitter` `^0.0.11`, `@orkestrel/contract` `^0.0.18` | |
|
|
81
|
-
| `@orkestrel/scaffold` | `0.0.
|
|
81
|
+
| `@orkestrel/scaffold` | `0.0.86` | L3 | `@orkestrel/console` `^0.0.15`, `@orkestrel/emitter` `^0.0.11`, `@orkestrel/process` `^0.0.14`, `@orkestrel/contract` `^0.0.19`, `@orkestrel/markdown` `^0.0.17`, `@orkestrel/template` `^0.0.9` | |
|
|
82
82
|
| `@orkestrel/sea` | `0.0.18` | L3 | `@orkestrel/emitter` `^0.0.11`, `@orkestrel/process` `^0.0.14`, `@orkestrel/contract` `^0.0.18` | |
|
|
83
83
|
| `@orkestrel/server` | `0.0.22` | L3 | `@orkestrel/abort` `^0.0.12`, `@orkestrel/codec` `^0.0.5`, `@orkestrel/router` `^0.0.16`, `@orkestrel/emitter` `^0.0.11`, `@orkestrel/timeout` `^0.0.12`, `@orkestrel/contract` `^0.0.19` | |
|
|
84
84
|
| `@orkestrel/sqlite` | `0.0.13` | L1 | `@orkestrel/contract` `^0.0.18` | |
|
|
@@ -28,6 +28,7 @@ paths:
|
|
|
28
28
|
| Shape/algorithm compilers | `*/compilers.ts` |
|
|
29
29
|
| Entity/value factories | `*/factories.ts` |
|
|
30
30
|
| Middleware factories | `*/middlewares.ts` |
|
|
31
|
+
| Plugin factories | `*/plugins.ts` |
|
|
31
32
|
| Request handlers | `*/handlers.ts` |
|
|
32
33
|
| Route tables | `*/routes.ts` |
|
|
33
34
|
| Seeders | `*/seeders.ts` |
|
|
@@ -63,7 +64,8 @@ Use only the centralized files an environment needs.
|
|
|
63
64
|
- non-trivial or reusable → extract, export, unit-test, and route every duplicate through it.
|
|
64
65
|
- `factories.ts`, `compilers.ts`, and `parsers.ts` are centralized files, not hiding places. Factory glue extracts to `helpers.ts`; pure compiler/parser recursion remains exported in its own kind file.
|
|
65
66
|
- Every exported function in `parsers.ts` is named `parse*`. Every exported function in
|
|
66
|
-
`factories.ts` is named `create*`.
|
|
67
|
+
`factories.ts` is named `create*`. Every exported function in `plugins.ts` is named
|
|
68
|
+
`create{Entity}Plugin`, or `create{Name}Plugins` when it returns a collection.
|
|
67
69
|
- Those name forms are one-directional. A name does not place a function: `createWriteDirectory`
|
|
68
70
|
creates a directory rather than an entity and `isVacant` is a predicate rather than a `Guard<T>`,
|
|
69
71
|
so both stay in `helpers.ts`. Placement follows what the function is; the name form follows
|
|
@@ -86,7 +88,7 @@ Use only the centralized files an environment needs.
|
|
|
86
88
|
- Keep the leaf pair class-free. `helpers.ts` and `validators.ts` sit at the bottom of a module's
|
|
87
89
|
graph: they import types, constants, errors, and each other, and they import no implementation
|
|
88
90
|
class. Every file that constructs or drives a class — `cloners.ts`, `compilers.ts`, `factories.ts`,
|
|
89
|
-
`shapers.ts` — sits above them, consumes them, and is never consumed by them. One cycle between
|
|
91
|
+
`plugins.ts`, `shapers.ts` — sits above them, consumes them, and is never consumed by them. One cycle between
|
|
90
92
|
the leaves is the shape this produces and is acceptable; an edge running downward from a
|
|
91
93
|
class-importing file into the leaf pair is not.
|
|
92
94
|
- `templates.ts` and `contracts.ts` hold data only — shipped template definitions and compiled
|
|
@@ -126,10 +128,10 @@ neither reads meaning.
|
|
|
126
128
|
freeze obligation in the earlier kind-purity rules binds regardless; only the bare literal is
|
|
127
129
|
mechanical.
|
|
128
130
|
- The plugin does not tell one function kind from another. Every centralized file that permits
|
|
129
|
-
functions reads the same to it apart from the `parse
|
|
130
|
-
`combinators.ts`, `compilers.ts`, `errors.ts`, `factories.ts`, `handlers.ts`,
|
|
131
|
-
`
|
|
132
|
-
`shapers.ts`, and `validators.ts`. That list is exhaustive, a new function kind joins it, and no
|
|
131
|
+
functions reads the same to it apart from the `parse*`, `create*`, `create*Plugin`, and `create*Plugins` name
|
|
132
|
+
forms: `cloners.ts`, `combinators.ts`, `compilers.ts`, `errors.ts`, `factories.ts`, `handlers.ts`,
|
|
133
|
+
`helpers.ts`, `inferers.ts`, `middlewares.ts`, `parsers.ts`, `plugins.ts`, `relations.ts`,
|
|
134
|
+
`schemas.ts`, `seeders.ts`, `shapers.ts`, and `validators.ts`. That list is exhaustive, a new function kind joins it, and no
|
|
133
135
|
later version of the plugin claims more.
|
|
134
136
|
- The plugin reports no `data` violation in `helpers.ts`. The kind rules place a camelCase
|
|
135
137
|
namespace of functions there, and a namespace of callables is not separable from a data table by
|
|
@@ -202,6 +204,13 @@ Store child managers in `#` fields and expose readonly getters typed as their in
|
|
|
202
204
|
- Extract reusable cross-middleware machinery to helpers/classes.
|
|
203
205
|
- `MiddlewareManager` is the sole manager of the middleware composition chain; do not create one manager per middleware.
|
|
204
206
|
|
|
207
|
+
## Plugins
|
|
208
|
+
|
|
209
|
+
- A plugin is a value a host composes at creation: the binding of one entity class to the host's extension seam. The entity class stays unaware of it.
|
|
210
|
+
- Place plugin factories in `plugins.ts` as `create{Entity}Plugin(options?)`, and a factory of a host's collection as `create{Name}Plugins()`; keep them distinct from the entity factories in `factories.ts`.
|
|
211
|
+
- A plugin factory builds and returns the value; it never registers, installs, or boots anything. The host's creation options take the plugin list, and registration happens there.
|
|
212
|
+
- A plugin drives its entity through the entity's public interface. When the host needs behavior the interface lacks, add it to the interface as a real entity capability; never reach a private member from the plugin.
|
|
213
|
+
|
|
205
214
|
## Environment/module placement
|
|
206
215
|
|
|
207
216
|
- Shared cross-environment logic belongs in the central core/shared layer. Other environments import core; core imports neither browser nor server.
|
|
@@ -169,17 +169,18 @@ Keep canonical case:
|
|
|
169
169
|
|
|
170
170
|
## Value-level identifiers
|
|
171
171
|
|
|
172
|
-
| Kind | Required form
|
|
173
|
-
| -------------- |
|
|
174
|
-
| Class | PascalCase `{Entity}`
|
|
175
|
-
| Manager class | PascalCase `{Entity}Manager`
|
|
176
|
-
| Factory | camelCase `create{Entity}`
|
|
177
|
-
|
|
|
178
|
-
|
|
|
179
|
-
|
|
|
180
|
-
|
|
|
181
|
-
|
|
|
182
|
-
|
|
|
172
|
+
| Kind | Required form |
|
|
173
|
+
| -------------- | -------------------------------------------------------------------- |
|
|
174
|
+
| Class | PascalCase `{Entity}` |
|
|
175
|
+
| Manager class | PascalCase `{Entity}Manager` |
|
|
176
|
+
| Factory | camelCase `create{Entity}` |
|
|
177
|
+
| Plugin factory | camelCase `create{Entity}Plugin`; a collection `create{Name}Plugins` |
|
|
178
|
+
| Guard | camelCase `is{Condition}` |
|
|
179
|
+
| Helper | camelCase `{verb}{Noun}` |
|
|
180
|
+
| Constant | UPPER_SNAKE_CASE `{QUALIFIER}_{NOUN}` |
|
|
181
|
+
| Property/field | camelCase bare noun |
|
|
182
|
+
| Method | camelCase bare verb |
|
|
183
|
+
| Boolean | camelCase adjective/past participle |
|
|
183
184
|
|
|
184
185
|
## Fixed derivation/construction forms
|
|
185
186
|
|
|
@@ -187,6 +188,7 @@ Keep canonical case:
|
|
|
187
188
|
- `is*`: total `Guard<T>`; never throws; returns false off-shape.
|
|
188
189
|
- `parse*`: coercion producing `T | undefined`; cross-type conversion never belongs in a guard.
|
|
189
190
|
- `create*`: the factory form; `.claude/rules/architecture.md` § Kind purity states what a factory is and where it lives.
|
|
191
|
+
- `create*Plugin` and `create*Plugins`: the plugin-factory forms; `.claude/rules/architecture.md` § Plugins owns what a plugin is and what its factory does. Never name a plugin factory `register*` or `install*`.
|
|
190
192
|
- `*Of`: combinator named for its constituents, combining them into a container/guard/value, such as `arrayOf(guard)` or `boundsOf(min, max)`.
|
|
191
193
|
- `{noun}To{Noun}`: projection from a whole to a derived view, such as `definitionToSnapshot`.
|
|
192
194
|
- `*Shape`: `ContractShape` value/JSON-Schema blueprint, not a function or type.
|
|
@@ -31,6 +31,13 @@ export interface PolicyExpression extends PolicyNode {
|
|
|
31
31
|
readonly source?: PolicyExpression
|
|
32
32
|
readonly specifiers?: readonly PolicyExpression[]
|
|
33
33
|
readonly imported?: PolicyExpression
|
|
34
|
+
readonly exported?: PolicyExpression
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** Pairs a name-form kind file's name pattern with the message id a misnamed export reports. */
|
|
38
|
+
export interface PolicyNameForm {
|
|
39
|
+
readonly pattern: RegExp
|
|
40
|
+
readonly messageId: string
|
|
34
41
|
}
|
|
35
42
|
|
|
36
43
|
/** Pairs one declared module function with the name a prefix rule reads. */
|
|
@@ -145,6 +152,7 @@ export const CENTRAL_SOURCE_FILES: readonly string[] = Object.freeze([
|
|
|
145
152
|
'inferers.ts',
|
|
146
153
|
'middlewares.ts',
|
|
147
154
|
'parsers.ts',
|
|
155
|
+
'plugins.ts',
|
|
148
156
|
'relations.ts',
|
|
149
157
|
'routes.ts',
|
|
150
158
|
'schemas.ts',
|
|
@@ -167,6 +175,7 @@ export const FUNCTION_SOURCE_FILES: readonly string[] = Object.freeze([
|
|
|
167
175
|
'inferers.ts',
|
|
168
176
|
'middlewares.ts',
|
|
169
177
|
'parsers.ts',
|
|
178
|
+
'plugins.ts',
|
|
170
179
|
'relations.ts',
|
|
171
180
|
'schemas.ts',
|
|
172
181
|
'seeders.ts',
|
|
@@ -274,6 +283,13 @@ export const POLICY_TAG_PATTERN = /\{@(?:linkcode|linkplain|link|inheritDoc)\b[^
|
|
|
274
283
|
/** Matches a URL, whose segments are an address rather than prose. */
|
|
275
284
|
export const POLICY_URL_PATTERN = /https?:\/\/\S+/gu
|
|
276
285
|
|
|
286
|
+
/** Maps each name-form kind file to the form every function it exports must take. */
|
|
287
|
+
export const POLICY_NAME_FORMS: Readonly<Record<string, PolicyNameForm>> = Object.freeze({
|
|
288
|
+
'parsers.ts': Object.freeze({ pattern: /^parse/u, messageId: 'parser' }),
|
|
289
|
+
'factories.ts': Object.freeze({ pattern: /^create/u, messageId: 'factory' }),
|
|
290
|
+
'plugins.ts': Object.freeze({ pattern: /^create[A-Z]\w*Plugins?$/u, messageId: 'plugin' }),
|
|
291
|
+
})
|
|
292
|
+
|
|
277
293
|
/**
|
|
278
294
|
* Lists the words ending in `s` that open a sentence without being a third-person verb.
|
|
279
295
|
*
|
|
@@ -992,24 +1008,37 @@ export function reportConstant(context: PolicyContext, node: PolicyExpression):
|
|
|
992
1008
|
}
|
|
993
1009
|
}
|
|
994
1010
|
|
|
995
|
-
/**
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1011
|
+
/**
|
|
1012
|
+
* Reports a function in the named kind file whose name breaks that file's form, an export specifier
|
|
1013
|
+
* there that publishes a name outside it, and a star re-export there, whose names the form cannot
|
|
1014
|
+
* read.
|
|
1015
|
+
*/
|
|
1016
|
+
export function reportName(context: PolicyContext, node: PolicyExpression, file: string): void {
|
|
1017
|
+
const form = POLICY_NAME_FORMS[file]
|
|
1018
|
+
if (form === undefined || pathToPolicyFile(context.filename) !== file) return
|
|
1019
|
+
if (node.type === 'ExportAllDeclaration') {
|
|
1020
|
+
context.report({ node, messageId: form.messageId })
|
|
1021
|
+
return
|
|
1022
|
+
}
|
|
1023
|
+
if (node.type === 'ExportNamedDeclaration') {
|
|
1024
|
+
for (const specifier of node.specifiers ?? []) {
|
|
1025
|
+
const exported = specifier.exported
|
|
1026
|
+
const name =
|
|
1027
|
+
typeof exported?.name === 'string'
|
|
1028
|
+
? exported.name
|
|
1029
|
+
: typeof exported?.value === 'string'
|
|
1030
|
+
? exported.value
|
|
1031
|
+
: undefined
|
|
1032
|
+
if (name === undefined || !form.pattern.test(name)) {
|
|
1033
|
+
context.report({ node: specifier, messageId: form.messageId })
|
|
1034
|
+
}
|
|
1002
1035
|
}
|
|
1036
|
+
return
|
|
1003
1037
|
}
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
/** Reports a factories.ts function whose name lacks the create prefix. */
|
|
1007
|
-
export function reportFactory(context: PolicyContext, node: PolicyExpression): void {
|
|
1008
|
-
if (isPolicyAmbient(context.filename) || !isPolicyTop(node)) return
|
|
1009
|
-
if (pathToPolicyFile(context.filename) !== 'factories.ts') return
|
|
1038
|
+
if (!isPolicyTop(node)) return
|
|
1010
1039
|
for (const binding of statementToPolicyBindings(node)) {
|
|
1011
|
-
if (binding.name === undefined || !binding.name
|
|
1012
|
-
context.report({ node: binding.node, messageId:
|
|
1040
|
+
if (binding.name === undefined || !form.pattern.test(binding.name)) {
|
|
1041
|
+
context.report({ node: binding.node, messageId: form.messageId })
|
|
1013
1042
|
}
|
|
1014
1043
|
}
|
|
1015
1044
|
}
|
|
@@ -1252,44 +1281,73 @@ export const CONSTANT_RULE: PolicyRuleInterface = {
|
|
|
1252
1281
|
},
|
|
1253
1282
|
}
|
|
1254
1283
|
|
|
1255
|
-
/** Bans a parsers.ts function whose name lacks the parse prefix. */
|
|
1284
|
+
/** Bans a parsers.ts function or export whose name lacks the parse prefix. */
|
|
1256
1285
|
export const PARSER_RULE: PolicyRuleInterface = {
|
|
1257
1286
|
meta: {
|
|
1258
1287
|
type: 'problem',
|
|
1259
1288
|
docs: {
|
|
1260
|
-
description: 'Disallow a parsers.ts function whose name does not start with parse.',
|
|
1289
|
+
description: 'Disallow a parsers.ts function or export whose name does not start with parse.',
|
|
1261
1290
|
},
|
|
1262
1291
|
messages: {
|
|
1263
1292
|
parser:
|
|
1264
|
-
'Name this parsers.ts function with the parse prefix, or move it to its own kind file.',
|
|
1293
|
+
'Name this parsers.ts function or export with the parse prefix, or move it to its own kind file.',
|
|
1265
1294
|
},
|
|
1266
1295
|
},
|
|
1267
1296
|
create(context) {
|
|
1268
1297
|
return {
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1298
|
+
ExportAllDeclaration: (node) => reportName(context, node, 'parsers.ts'),
|
|
1299
|
+
ExportNamedDeclaration: (node) => reportName(context, node, 'parsers.ts'),
|
|
1300
|
+
FunctionDeclaration: (node) => reportName(context, node, 'parsers.ts'),
|
|
1301
|
+
TSDeclareFunction: (node) => reportName(context, node, 'parsers.ts'),
|
|
1302
|
+
VariableDeclaration: (node) => reportName(context, node, 'parsers.ts'),
|
|
1272
1303
|
}
|
|
1273
1304
|
},
|
|
1274
1305
|
}
|
|
1275
1306
|
|
|
1276
|
-
/** Bans a factories.ts function whose name lacks the create prefix. */
|
|
1307
|
+
/** Bans a factories.ts function or export whose name lacks the create prefix. */
|
|
1277
1308
|
export const FACTORY_RULE: PolicyRuleInterface = {
|
|
1278
1309
|
meta: {
|
|
1279
1310
|
type: 'problem',
|
|
1280
1311
|
docs: {
|
|
1281
|
-
description:
|
|
1312
|
+
description:
|
|
1313
|
+
'Disallow a factories.ts function or export whose name does not start with create.',
|
|
1282
1314
|
},
|
|
1283
1315
|
messages: {
|
|
1284
1316
|
factory:
|
|
1285
|
-
'Name this factories.ts function with the create prefix, or move it to its own kind file.',
|
|
1317
|
+
'Name this factories.ts function or export with the create prefix, or move it to its own kind file.',
|
|
1318
|
+
},
|
|
1319
|
+
},
|
|
1320
|
+
create(context) {
|
|
1321
|
+
return {
|
|
1322
|
+
ExportAllDeclaration: (node) => reportName(context, node, 'factories.ts'),
|
|
1323
|
+
ExportNamedDeclaration: (node) => reportName(context, node, 'factories.ts'),
|
|
1324
|
+
FunctionDeclaration: (node) => reportName(context, node, 'factories.ts'),
|
|
1325
|
+
TSDeclareFunction: (node) => reportName(context, node, 'factories.ts'),
|
|
1326
|
+
VariableDeclaration: (node) => reportName(context, node, 'factories.ts'),
|
|
1327
|
+
}
|
|
1328
|
+
},
|
|
1329
|
+
}
|
|
1330
|
+
|
|
1331
|
+
/** Bans a plugins.ts function or export whose name is not a create-prefixed plugin or plugins form. */
|
|
1332
|
+
export const PLUGIN_RULE: PolicyRuleInterface = {
|
|
1333
|
+
meta: {
|
|
1334
|
+
type: 'problem',
|
|
1335
|
+
docs: {
|
|
1336
|
+
description:
|
|
1337
|
+
'Disallow a plugins.ts function or export whose name is not create…Plugin or create…Plugins.',
|
|
1338
|
+
},
|
|
1339
|
+
messages: {
|
|
1340
|
+
plugin:
|
|
1341
|
+
'Name this plugins.ts function or export create…Plugin, or create…Plugins for a collection, or move it to its own kind file.',
|
|
1286
1342
|
},
|
|
1287
1343
|
},
|
|
1288
1344
|
create(context) {
|
|
1289
1345
|
return {
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1346
|
+
ExportAllDeclaration: (node) => reportName(context, node, 'plugins.ts'),
|
|
1347
|
+
ExportNamedDeclaration: (node) => reportName(context, node, 'plugins.ts'),
|
|
1348
|
+
FunctionDeclaration: (node) => reportName(context, node, 'plugins.ts'),
|
|
1349
|
+
TSDeclareFunction: (node) => reportName(context, node, 'plugins.ts'),
|
|
1350
|
+
VariableDeclaration: (node) => reportName(context, node, 'plugins.ts'),
|
|
1293
1351
|
}
|
|
1294
1352
|
},
|
|
1295
1353
|
}
|
|
@@ -1392,6 +1450,7 @@ export default {
|
|
|
1392
1450
|
'no-malformed-constant': CONSTANT_RULE,
|
|
1393
1451
|
'no-misnamed-parser': PARSER_RULE,
|
|
1394
1452
|
'no-misnamed-factory': FACTORY_RULE,
|
|
1453
|
+
'no-misnamed-plugin': PLUGIN_RULE,
|
|
1395
1454
|
'no-malformed-domain': DOMAIN_RULE,
|
|
1396
1455
|
'no-host-line-endings': ENDING_RULE,
|
|
1397
1456
|
'no-malformed-summary': VOICE_RULE,
|
|
@@ -129,7 +129,7 @@ directly.
|
|
|
129
129
|
| `extractSourceLines` | function | `(source: string) => readonly SourceLine[]` | Extracts aligned physical source-line records in one character traversal. Real line and block comments and complete template tokens become spaces in `SourceLine.code`, while ordinary code, quoted strings, and recognized regex literals retain their columns. Genuine JSDoc opened from reflection code is retained span by span at its exact physical column in `SourceLine.jsdoc`; faux openers in comments and templates are excluded. Membership remains each consumer's separate anchored grammar. |
|
|
130
130
|
| `extractExports` | function | `(source: string) => readonly SurfaceSymbol[]` | Extracts the module-scope exports declared in one file's source text — the declaration keys `collectKeys` reports, each split back into the keyword and name that built it, deduped by (keyword, name). |
|
|
131
131
|
| `extractHidden` | function | `(source: string) => readonly SurfaceSymbol[]` | Extracts the module-scope declarations lacking the `export` keyword in one file's source text — the mirror image of `extractExports`'s grammar, anchored the same way (column 0, so an indented inner declaration never matches). |
|
|
132
|
-
| `joinHead` | function | `(lines: readonly string[], start: number) => DeclarationHead \| undefined` | Joins the declaration head starting at `start` into one space-separated line, consuming lines until the first that ends with `{`.
|
|
132
|
+
| `joinHead` | function | `(lines: readonly string[], start: number) => DeclarationHead \| undefined` | Joins the declaration head starting at `start` into one space-separated line, consuming lines until the first that opens a body: one that ends with `{`, or one that closes an empty body with `{}` or `{ }`. |
|
|
133
133
|
| `escapeRegExp` | function | `(value: string) => string` | Escapes every regex metacharacter in a literal string so it reads as text inside a larger `RegExp` source rather than as syntax. |
|
|
134
134
|
| `collectDeclarations` | function | `(source: string) => ReadonlyMap<string, Declaration>` | Collects every `export class` / `export interface` declaration one file's source text declares, each keyed `${keyword} ${name}` and carrying the body lines and the base identifiers read from its own head, so a body and a heritage clause always come from the same declaration. |
|
|
135
135
|
| `extractDeclaration` | function | `(source: string, keyword: DeclarationKeyword, name: string) => Declaration \| undefined` | Locates the named `export class` / `export interface` declaration in one file's source text and returns its body lines and its base identifiers read from that one head, so a body and a heritage clause always come from the same declaration, or `undefined` when the file declares no such head. |
|
|
@@ -577,7 +577,9 @@ keyed `${keyword} ${name}`. One collector is what keeps a body and a heritage cl
|
|
|
577
577
|
declaration. The identifier is the head's own run up to its generic parameter list or its heritage
|
|
578
578
|
clause, so it enters the key as literal text: a name carrying a regex metacharacter reaches no
|
|
579
579
|
`RegExp`, and a lookup of that name matches the character rather than a wildcard. A head that opens
|
|
580
|
-
no column-zero close records nothing,
|
|
580
|
+
no column-zero close records nothing, a head whose last line closes its own body with `{}` or
|
|
581
|
+
`{ }` records an empty body, and a later head of a key already collected adds nothing unless the
|
|
582
|
+
earlier head recorded neither a body nor bases, which the later head then replaces.
|
|
581
583
|
`extractDeclaration` is the named lookup over that map: it spells the `${keyword} ${name}` key so a
|
|
582
584
|
consumer reading one name never writes that convention, and it collects the file afresh on every
|
|
583
585
|
call. A consumer reading many names from one file calls `collectDeclarations` once and reads the
|
|
@@ -592,12 +594,15 @@ reads as `Base`, and a class's `implements` clause is excluded. `Source.methods(
|
|
|
592
594
|
located declaration's own members with those of every declaration it extends, following each base
|
|
593
595
|
through the same module scope and keeping the keyword it started from — an interface chain resolves
|
|
594
596
|
through interfaces, a class chain through classes, so an interface extending a name only a class
|
|
595
|
-
declares gets nothing from it. One declaration answers
|
|
596
|
-
in sorted key order, and the first one whose located head has a body or has bases supplies
|
|
597
|
-
members and the bases; a head with neither a body nor bases does not count as declared, so
|
|
598
|
-
`export interface X {}` is skipped and the scan continues to a later file
|
|
599
|
-
|
|
600
|
-
|
|
597
|
+
declares gets nothing from it. One declaration answers under each keyword: the module scope's files
|
|
598
|
+
are read in sorted key order, and the first one whose located head has a body or has bases supplies
|
|
599
|
+
both the members and the bases; a head with neither a body nor bases does not count as declared, so
|
|
600
|
+
an empty `export interface X {}` is skipped and the scan continues to a later file, and a second
|
|
601
|
+
file declaring the same name under that keyword adds nothing. The interface answers for a name, and
|
|
602
|
+
the class answers when no interface declares it. Where one file declares a name as both an interface
|
|
603
|
+
and a class, which TypeScript merges into one type, both answer: each shape's own members come ahead
|
|
604
|
+
of inherited ones, the interface's own first. Shapes from two files stay separate declarations, and
|
|
605
|
+
Parity lets the class of a merge the barrel exports answer for its interface half. The inventory is the further bound: a base the selected directories do not declare, whether it is
|
|
601
606
|
imported from another package or written as a qualified name such as `external.Store`, contributes
|
|
602
607
|
no members and is not an error, and one visited set per call collapses a cycle and a diamond to a
|
|
603
608
|
single visit. `Source.examples(name)` is deliberately asymmetric with it — it reads only the named
|
|
@@ -306,6 +306,7 @@ Exported from `@orkestrel/scaffold`, and reachable from
|
|
|
306
306
|
| `blueprintToTestArtifacts` | function | Compiles every artifact in the `tests` group that is not vendored from the host. |
|
|
307
307
|
| `blueprintToWritableScripts` | function | Projects a blueprint into the manifest scripts a region write may replace. |
|
|
308
308
|
| `dependenciesToQuestions` | function | Measures one declared package list against the name and range syntax it accepts. |
|
|
309
|
+
| `insertManifestDependencies` | function | Inserts dependencies a package manifest does not declare into the section each one names. |
|
|
309
310
|
| `overridesToQuestions` | function | Measures a blueprint's overrides against the artifacts drafted for it. |
|
|
310
311
|
| `pathToCondition` | function | Builds one `exports` condition block for a built environment. |
|
|
311
312
|
| `planToFindings` | function | Compares a plan against a target's current content. |
|
|
@@ -539,7 +540,7 @@ option grants a write.
|
|
|
539
540
|
| `audit` | Nothing |
|
|
540
541
|
| `repair` | Each planned path the target is missing or has let drift, and the range and script regions |
|
|
541
542
|
| `catalog` | The package table, the guide mirrors, and the range region |
|
|
542
|
-
| `overwrite` | Everything `repair` and `catalog` write,
|
|
543
|
+
| `overwrite` | Everything `repair` and `catalog` write, deletions, and missing planned dependencies |
|
|
543
544
|
|
|
544
545
|
### Baselines
|
|
545
546
|
|
|
@@ -589,7 +590,7 @@ scaffold <verb> [options]
|
|
|
589
590
|
scaffold catalog [--all] [--from <path>] [--target <path>] [--json]
|
|
590
591
|
regenerate the package table and refresh the guide mirrors
|
|
591
592
|
scaffold overwrite [--groups <list>] [--dirty] [--offline] [--from <path>] [--target <path>] [--json]
|
|
592
|
-
do everything repair and catalog do, then delete what the plan does not own
|
|
593
|
+
do everything repair and catalog do, then delete what the plan does not own, re-declare the dependency ranges, and declare each planned dependency the manifest lacks
|
|
593
594
|
|
|
594
595
|
options
|
|
595
596
|
--src <list> the published library environments to build: core, browser, server
|
|
@@ -802,9 +803,20 @@ live in either section, and how current a declared range is belongs to the regis
|
|
|
802
803
|
Dependency floors describes rather than to this question. A present section that is not an object
|
|
803
804
|
produces a question instead of a crash. This question belongs to `configs` and `tests`. `audit`
|
|
804
805
|
reports it only when its selection includes either group, without changing its exit semantics.
|
|
805
|
-
`repair`
|
|
806
|
-
|
|
807
|
-
|
|
806
|
+
`repair` refuses before writing a selected `configs` or `tests` group, and a selection that
|
|
807
|
+
excludes those groups proceeds: `package.json` is birth-owned, and `repair` rewrites only its range
|
|
808
|
+
and script regions. `overwrite` declares each missing package instead, in the `devDependencies` map
|
|
809
|
+
the plan assigns it, at its planned range, before the first declared key that sorts after it. A
|
|
810
|
+
manifest with no `devDependencies` map gets one: `overwrite` creates it as one top-level key after
|
|
811
|
+
`dependencies`, or last in the manifest object when `dependencies` is absent too, in the indentation
|
|
812
|
+
of the manifest's first key. The declaration lands with the repair, before the catalog step, so a
|
|
813
|
+
partial run keeps it. The JSON result names each declaration in `additions`, and the human report
|
|
814
|
+
prints one `Declared "<name>": "<range>" in devDependencies. Run npm install to install it.` line
|
|
815
|
+
per declaration, because the lockfile does not carry the package until the next install. A
|
|
816
|
+
`devDependencies` value that is not an object, or an entry in that map whose value is not a version
|
|
817
|
+
string, leaves `overwrite` no map to declare in. `overwrite` refuses that manifest before any write,
|
|
818
|
+
whatever `--groups` selects, and names the section or each malformed entry. A malformed section
|
|
819
|
+
refuses `repair` as well.
|
|
808
820
|
|
|
809
821
|
`audit` reports a further non-blocking question, on the `setup` field.
|
|
810
822
|
|
|
@@ -844,6 +856,17 @@ write: a writing verb reports it in the terminal audit it prints, because refusi
|
|
|
844
856
|
gap no write can close would block every write. Run across a fleet, the question is the list of
|
|
845
857
|
packages carrying a filled, exporting setup module that no proof covers.
|
|
846
858
|
|
|
859
|
+
`audit` reports a non-blocking question on the `tests` field for each planned sheet test the target
|
|
860
|
+
holds that imports by a root-relative specifier. A sheet test is birth-owned, so a target born
|
|
861
|
+
before the template imported the built sheet by a relative path keeps the
|
|
862
|
+
`'/dist/src/<name>/index.css?raw'` import, and the content-owned `.oxlintrc.json` refuses it under
|
|
863
|
+
`--deny-warnings` through `import/no-absolute-path`. The message names the file and each
|
|
864
|
+
root-relative specifier beside the relative one to write: one `../` per directory between the file
|
|
865
|
+
and the workspace root, so `tests/src/styles/index.test.ts` writes
|
|
866
|
+
`'../../../dist/src/styles/index.css?raw'`. Scaffold never rewrites the file. The question belongs to
|
|
867
|
+
the `tests` group, and `repair` and `overwrite` report it in their terminal audit without
|
|
868
|
+
refusing a write.
|
|
869
|
+
|
|
847
870
|
`audit` reads the instruction canon as findings rather than as a question. Each `CANON_PATHS` member
|
|
848
871
|
the target holds enters the comparison, by file where the member is a directory, and a path the plan
|
|
849
872
|
does not claim there reports `foreign`. Ownership and drift states that population, and Vendored data
|
|
@@ -886,12 +909,14 @@ standard error, so a piped value is never polluted.
|
|
|
886
909
|
| `audit` | `Audit` — `findings` and `questions` — plus `releases` and `provenance`; findings carry `ownership` |
|
|
887
910
|
| `repair` | `MaterializeResult` plus `audit`, the terminal audit taken after the write, `releases`, and `provenance` |
|
|
888
911
|
| `catalog` | `MaterializeResult` plus `mirrors`, `provenance`, optional `membership` with `entries`, `dropped`, and `releases`, and an explanatory `note` on a partial run |
|
|
889
|
-
| `overwrite` | The `catalog` value plus `audit
|
|
912
|
+
| `overwrite` | The `catalog` value plus `audit`, top-level `releases` from its version read, and `additions`; `note` explains a partial run |
|
|
890
913
|
|
|
891
914
|
The `membership` entity is present only when the catalog read completes. Its `entries` holds the
|
|
892
915
|
package table, `dropped` names packages the preceding table carried that the registry no longer
|
|
893
916
|
lists, and `releases` measures declared fleet ranges against the catalog read. The `overwrite`
|
|
894
|
-
result also retains top-level `releases` from its separate version read, including foreign tools
|
|
917
|
+
result also retains top-level `releases` from its separate version read, including foreign tools,
|
|
918
|
+
and `additions`, the `DependencyPinSet` of planned dependencies it declared, empty when the
|
|
919
|
+
manifest lacked none.
|
|
895
920
|
An absent `membership` identifies an incomplete catalog read; `note` explains the cause.
|
|
896
921
|
|
|
897
922
|
The following JSON excerpt shows the membership evidence in a completed catalog result:
|