@orkestrel/scaffold 0.0.46 → 0.0.48
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/main.js +179 -44
- package/dist/bin/main.js.map +1 -1
- package/dist/host/AGENTS.md +1 -0
- package/dist/host/agents/orchestration.md +3 -1
- package/dist/host/claude/agents/codex.md +4 -4
- package/dist/host/claude/agents/orkestrel.md +4 -4
- package/dist/host/claude/rules/documentation.md +2 -1
- package/dist/host/claude/rules/quality.md +3 -0
- package/dist/host/claude/rules/tests.md +19 -0
- package/dist/host/claude/rules/workspace.md +28 -21
- package/dist/host/claude/settings.json +781 -2
- package/dist/host/configs/policy.ts +170 -10
- package/dist/host/dotfiles/oxlintrc.json +9 -0
- package/dist/host/guides/scaffold.md +143 -44
- 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 +149 -11
- package/dist/host/tests/policy.test.ts +7 -0
- package/dist/host/tests/setupPolicy.ts +222 -8
- package/dist/src/core/index.cjs +420 -24
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +102 -9
- package/dist/src/core/index.d.ts +102 -9
- package/dist/src/core/index.js +416 -25
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +41 -49
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.d.cts +9 -8
- package/dist/src/server/index.d.ts +9 -8
- package/dist/src/server/index.js +42 -50
- package/dist/src/server/index.js.map +1 -1
- package/package.json +9 -7
|
@@ -1,13 +1,26 @@
|
|
|
1
|
-
/** The
|
|
2
|
-
export interface
|
|
1
|
+
/** The syntax-node fields supplied to every policy visitor. */
|
|
2
|
+
export interface PolicyNode {
|
|
3
3
|
readonly type: string
|
|
4
4
|
readonly range: [number, number]
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
/** The expression fields inspected by the policy rules. */
|
|
8
|
+
export interface PolicyExpression extends PolicyNode {
|
|
9
|
+
readonly parent?: PolicyExpression | null
|
|
5
10
|
readonly name?: unknown
|
|
6
11
|
readonly value?: unknown
|
|
12
|
+
readonly key?: PolicyExpression
|
|
13
|
+
readonly id?: unknown
|
|
14
|
+
readonly method?: boolean
|
|
15
|
+
readonly kind?: 'get' | 'init' | 'set'
|
|
16
|
+
readonly expression?: boolean
|
|
7
17
|
readonly object?: PolicyExpression
|
|
8
18
|
readonly property?: PolicyExpression
|
|
9
19
|
readonly computed?: boolean
|
|
10
20
|
readonly callee?: PolicyExpression
|
|
21
|
+
readonly argument?: PolicyExpression | null
|
|
22
|
+
readonly arguments?: readonly PolicyExpression[]
|
|
23
|
+
readonly body?: PolicyExpression
|
|
11
24
|
readonly quasis?: readonly PolicyExpression[]
|
|
12
25
|
readonly expressions?: readonly PolicyExpression[]
|
|
13
26
|
readonly accessibility?: 'private' | 'protected' | 'public' | null
|
|
@@ -40,14 +53,17 @@ export interface PolicyMeta {
|
|
|
40
53
|
|
|
41
54
|
/** The Oxlint visitor entries used by the policy rules. */
|
|
42
55
|
export interface PolicyVisitor {
|
|
43
|
-
readonly [key: string]: ((node:
|
|
44
|
-
readonly CallExpression?: (node:
|
|
45
|
-
readonly
|
|
46
|
-
readonly
|
|
47
|
-
readonly
|
|
48
|
-
readonly
|
|
49
|
-
readonly
|
|
50
|
-
readonly
|
|
56
|
+
readonly [key: string]: ((node: PolicyNode) => void) | undefined
|
|
57
|
+
readonly CallExpression?: (node: PolicyNode) => void
|
|
58
|
+
readonly FunctionDeclaration?: (node: PolicyNode) => void
|
|
59
|
+
readonly FunctionExpression?: (node: PolicyNode) => void
|
|
60
|
+
readonly ArrowFunctionExpression?: (node: PolicyNode) => void
|
|
61
|
+
readonly MethodDefinition?: (node: PolicyNode) => void
|
|
62
|
+
readonly PropertyDefinition?: (node: PolicyNode) => void
|
|
63
|
+
readonly AccessorProperty?: (node: PolicyNode) => void
|
|
64
|
+
readonly TSAbstractMethodDefinition?: (node: PolicyNode) => void
|
|
65
|
+
readonly TSAbstractPropertyDefinition?: (node: PolicyNode) => void
|
|
66
|
+
readonly TSAbstractAccessorProperty?: (node: PolicyNode) => void
|
|
51
67
|
}
|
|
52
68
|
|
|
53
69
|
/** The complete behavior exposed by one policy rule. */
|
|
@@ -56,6 +72,128 @@ export interface PolicyRuleInterface {
|
|
|
56
72
|
create(context: PolicyContext): PolicyVisitor
|
|
57
73
|
}
|
|
58
74
|
|
|
75
|
+
/** Whether a policy expression is runtime function syntax. */
|
|
76
|
+
export function isPolicyFunction(node: PolicyExpression): boolean {
|
|
77
|
+
return (
|
|
78
|
+
node.type === 'FunctionDeclaration' ||
|
|
79
|
+
node.type === 'FunctionExpression' ||
|
|
80
|
+
node.type === 'ArrowFunctionExpression'
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Whether a policy function is anonymous. */
|
|
85
|
+
export function isPolicyAnonymous(node: PolicyExpression): boolean {
|
|
86
|
+
return node.type === 'ArrowFunctionExpression' || node.id === null
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Return the outermost parenthesized expression holding a policy function. */
|
|
90
|
+
export function functionToPolicyPosition(node: PolicyExpression): PolicyExpression {
|
|
91
|
+
let position = node
|
|
92
|
+
while (position.parent?.type === 'ParenthesizedExpression') {
|
|
93
|
+
position = position.parent
|
|
94
|
+
}
|
|
95
|
+
return position
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Whether a policy function is an anonymous callback passed directly as an argument. */
|
|
99
|
+
export function isPolicyCallback(node: PolicyExpression): boolean {
|
|
100
|
+
if (!isPolicyAnonymous(node)) return false
|
|
101
|
+
const position = functionToPolicyPosition(node)
|
|
102
|
+
const parent = position.parent
|
|
103
|
+
return (
|
|
104
|
+
(parent?.type === 'CallExpression' || parent?.type === 'NewExpression') &&
|
|
105
|
+
parent.arguments?.includes(position) === true
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** Whether a policy function is an anonymous function returned directly as a result. */
|
|
110
|
+
export function isPolicyResult(node: PolicyExpression): boolean {
|
|
111
|
+
if (!isPolicyAnonymous(node)) return false
|
|
112
|
+
const position = functionToPolicyPosition(node)
|
|
113
|
+
const parent = position.parent
|
|
114
|
+
return (
|
|
115
|
+
(parent?.type === 'ReturnStatement' && parent.argument === position) ||
|
|
116
|
+
(parent?.type === 'ArrowFunctionExpression' && parent.body === position)
|
|
117
|
+
)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Whether an Oxlint function expression represents method syntax. */
|
|
121
|
+
export function isPolicyMethod(node: PolicyExpression): boolean {
|
|
122
|
+
const parent = node.parent
|
|
123
|
+
return (
|
|
124
|
+
node.type === 'FunctionExpression' &&
|
|
125
|
+
parent?.value === node &&
|
|
126
|
+
(parent.type === 'MethodDefinition' ||
|
|
127
|
+
(parent.type === 'Property' &&
|
|
128
|
+
(parent.method === true || parent.kind === 'get' || parent.kind === 'set')))
|
|
129
|
+
)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/** Whether a policy function sits inside another function before any class-expression boundary. */
|
|
133
|
+
export function hasPolicyFunctionAncestor(node: PolicyExpression): boolean {
|
|
134
|
+
let parent = node.parent
|
|
135
|
+
let method = false
|
|
136
|
+
while (parent !== undefined && parent !== null) {
|
|
137
|
+
if (parent.type === 'ClassExpression') return false
|
|
138
|
+
if (parent.type === 'ClassDeclaration' && method) return true
|
|
139
|
+
if (isPolicyFunction(parent)) {
|
|
140
|
+
if (!isPolicyMethod(parent)) return true
|
|
141
|
+
method = true
|
|
142
|
+
}
|
|
143
|
+
parent = parent.parent
|
|
144
|
+
}
|
|
145
|
+
return method
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/** Whether an arrow is the policy plugin's sanctioned visitor-table delegation. */
|
|
149
|
+
export function isPolicyVisitor(node: PolicyExpression): boolean {
|
|
150
|
+
if (
|
|
151
|
+
node.type !== 'ArrowFunctionExpression' ||
|
|
152
|
+
node.expression !== true ||
|
|
153
|
+
node.body?.type !== 'CallExpression' ||
|
|
154
|
+
node.body.callee?.type !== 'Identifier' ||
|
|
155
|
+
typeof node.body.callee.name !== 'string' ||
|
|
156
|
+
!node.body.callee.name.startsWith('report')
|
|
157
|
+
) {
|
|
158
|
+
return false
|
|
159
|
+
}
|
|
160
|
+
const property = node.parent
|
|
161
|
+
const object = property?.parent
|
|
162
|
+
const returned = object?.parent
|
|
163
|
+
const block = returned?.parent
|
|
164
|
+
const create = block?.parent
|
|
165
|
+
const definition = create?.parent
|
|
166
|
+
return (
|
|
167
|
+
property?.type === 'Property' &&
|
|
168
|
+
property.method === false &&
|
|
169
|
+
property.value === node &&
|
|
170
|
+
object?.type === 'ObjectExpression' &&
|
|
171
|
+
returned?.type === 'ReturnStatement' &&
|
|
172
|
+
returned.argument === object &&
|
|
173
|
+
block?.type === 'BlockStatement' &&
|
|
174
|
+
create?.type === 'FunctionExpression' &&
|
|
175
|
+
definition?.type === 'Property' &&
|
|
176
|
+
definition.method === true &&
|
|
177
|
+
definition.value === create &&
|
|
178
|
+
definition.key?.type === 'Identifier' &&
|
|
179
|
+
definition.key.name === 'create'
|
|
180
|
+
)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/** Report function syntax nested inside another function body. */
|
|
184
|
+
export function reportNested(context: PolicyContext, node: PolicyExpression): void {
|
|
185
|
+
if (
|
|
186
|
+
!hasPolicyFunctionAncestor(node) ||
|
|
187
|
+
isPolicyMethod(node) ||
|
|
188
|
+
isPolicyCallback(node) ||
|
|
189
|
+
isPolicyResult(node) ||
|
|
190
|
+
isPolicyVisitor(node)
|
|
191
|
+
) {
|
|
192
|
+
return
|
|
193
|
+
}
|
|
194
|
+
context.report({ node, messageId: 'nested' })
|
|
195
|
+
}
|
|
196
|
+
|
|
59
197
|
/** Report banned calls on the named Vitest and Jest framework objects. */
|
|
60
198
|
export function reportMocking(context: PolicyContext, node: PolicyExpression): void {
|
|
61
199
|
const callee = node.callee
|
|
@@ -128,6 +266,27 @@ export function reportPrivacy(context: PolicyContext, node: PolicyExpression): v
|
|
|
128
266
|
}
|
|
129
267
|
}
|
|
130
268
|
|
|
269
|
+
/** Ban function declarations and assignments inside another function body. */
|
|
270
|
+
export const NESTED_RULE: PolicyRuleInterface = {
|
|
271
|
+
meta: {
|
|
272
|
+
type: 'problem',
|
|
273
|
+
docs: {
|
|
274
|
+
description: 'Disallow function declarations and assignments inside another function body.',
|
|
275
|
+
},
|
|
276
|
+
messages: {
|
|
277
|
+
nested:
|
|
278
|
+
'Extract the function to module scope or make instance-bound work a method; only direct anonymous callbacks and returned anonymous functions may stay in a function body.',
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
create(context) {
|
|
282
|
+
return {
|
|
283
|
+
FunctionDeclaration: (node) => reportNested(context, node),
|
|
284
|
+
FunctionExpression: (node) => reportNested(context, node),
|
|
285
|
+
ArrowFunctionExpression: (node) => reportNested(context, node),
|
|
286
|
+
}
|
|
287
|
+
},
|
|
288
|
+
}
|
|
289
|
+
|
|
131
290
|
/** Ban framework mocking, spying, fake clocks, and global or environment stubs. */
|
|
132
291
|
export const MOCKING_RULE: PolicyRuleInterface = {
|
|
133
292
|
meta: {
|
|
@@ -181,5 +340,6 @@ export default {
|
|
|
181
340
|
rules: {
|
|
182
341
|
'no-mocking': MOCKING_RULE,
|
|
183
342
|
'no-keyword-privacy': PRIVACY_RULE,
|
|
343
|
+
'no-nested-functions': NESTED_RULE,
|
|
184
344
|
},
|
|
185
345
|
}
|
|
@@ -79,6 +79,15 @@
|
|
|
79
79
|
"import/no-default-export": "off"
|
|
80
80
|
}
|
|
81
81
|
},
|
|
82
|
+
{
|
|
83
|
+
"files": [
|
|
84
|
+
"src/**/*.{cjs,cts,js,jsx,mjs,mts,ts,tsx,vue}",
|
|
85
|
+
"app/**/*.{cjs,cts,js,jsx,mjs,mts,ts,tsx,vue}"
|
|
86
|
+
],
|
|
87
|
+
"rules": {
|
|
88
|
+
"policy/no-nested-functions": "error"
|
|
89
|
+
}
|
|
90
|
+
},
|
|
82
91
|
{
|
|
83
92
|
"files": ["*.vue"],
|
|
84
93
|
"rules": {
|
|
@@ -52,7 +52,7 @@ Exported from `@orkestrel/scaffold`, and reachable from
|
|
|
52
52
|
| `Mirror` | type | One dependency guide fetched from upstream, beside the local mirror it answers for. |
|
|
53
53
|
| `Origin` | type | How an artifact's content is produced. |
|
|
54
54
|
| `Ownership` | type | What scaffold claims at an artifact's path. |
|
|
55
|
-
| `Release` | type | One declared dependency range measured against
|
|
55
|
+
| `Release` | type | One declared dependency range measured against a registry release. |
|
|
56
56
|
| `ScaffoldErrorCode` | type | The coded reasons a scaffold error is raised. |
|
|
57
57
|
| `Snapshot` | type | Exact lowercase hexadecimal target bytes keyed by artifact-relative path. |
|
|
58
58
|
|
|
@@ -123,7 +123,9 @@ Exported from `@orkestrel/scaffold`, and reachable from
|
|
|
123
123
|
| `MAX_NAME_LENGTH` | const | Maximum bare workspace name length. |
|
|
124
124
|
| `MAX_PATH_LENGTH` | const | Maximum length of one path, matching the longest a supported filesystem accepts. |
|
|
125
125
|
| `MAX_RANGE_LENGTH` | const | Maximum length of one declared package range. |
|
|
126
|
+
| `MAX_REGISTRY_BYTES` | const | Maximum decoded bytes accepted from one registry response. |
|
|
126
127
|
| `MAX_TOTAL_ARTIFACT_BYTES` | const | Maximum bytes retained across one whole plan or audit. |
|
|
128
|
+
| `MAX_TOTAL_REGISTRY_BYTES` | const | Maximum decoded bytes accepted across one registry-reading call. |
|
|
127
129
|
| `MINIMUM_NODE_VERSION` | const | The oldest Node version the generated toolchain supports. |
|
|
128
130
|
| `NAME_PATTERN` | const | The bare workspace name syntax: lowercase alphanumeric with hyphens, letter first. |
|
|
129
131
|
| `ORCHESTRATION_PATH_NAMES` | const | The exact root filenames that wire an agent bench rather than the toolchain, frozen. |
|
|
@@ -189,6 +191,7 @@ Exported from `@orkestrel/scaffold`, and reachable from
|
|
|
189
191
|
| `computeBytes` | function | Count the UTF-8 bytes text encodes to. |
|
|
190
192
|
| `computeHash` | function | Compute the deterministic content identity of text. |
|
|
191
193
|
| `contentToHex` | function | Encode text as the exact lowercase hexadecimal form of its UTF-8 bytes. |
|
|
194
|
+
| `extractRangeMajor` | function | Extract the major component of an admitted dependency range. |
|
|
192
195
|
| `extractVersion` | function | Extract the major, minor, and patch components of an exact version. |
|
|
193
196
|
| `inferDrift` | function | Infer how one target path compares to the artifact planned for it. |
|
|
194
197
|
| `inferGroup` | function | Infer the `Group` a path belongs to. |
|
|
@@ -232,6 +235,8 @@ Exported from `@orkestrel/scaffold`, and reachable from
|
|
|
232
235
|
| `pathToCondition` | function | Build one `exports` condition block for a built environment. |
|
|
233
236
|
| `planToFindings` | function | Compare a plan against a target's current content. |
|
|
234
237
|
| `planToHash` | function | Compute a plan's content identity. |
|
|
238
|
+
| `replaceManifestRanges` | function | Replace declared dependency ranges in package manifest text. |
|
|
239
|
+
| `replacePlanRanges` | function | Replace dependency ranges in a plan's manifest and recompute its identity. |
|
|
235
240
|
| `srcToEntry` | function | Project a published selection into the manifest's entry fields. |
|
|
236
241
|
| `srcToExports` | function | Project a published selection into the manifest's `exports` map. |
|
|
237
242
|
| `srcToRoot` | function | Select the single published environment a package root points at. |
|
|
@@ -392,7 +397,7 @@ no interface and is documented directly.
|
|
|
392
397
|
|
|
393
398
|
| Method | Summary |
|
|
394
399
|
| --------- | -------------------------------------------------------------------------- |
|
|
395
|
-
| `lookup` | Look up the
|
|
400
|
+
| `lookup` | Look up the newest release each declared range admits. |
|
|
396
401
|
| `fetch` | Fetch each named package's guide, beside the local mirror it answers for. |
|
|
397
402
|
| `catalog` | Catalog the published fleet from the registry's organization package list. |
|
|
398
403
|
| `destroy` | Tear the reader down, aborting every request in flight. |
|
|
@@ -413,13 +418,13 @@ no interface and is documented directly.
|
|
|
413
418
|
Authority is the verb's: every verb except `audit` writes when it is typed, and no
|
|
414
419
|
option grants a write.
|
|
415
420
|
|
|
416
|
-
| Verb | Writes
|
|
417
|
-
| ----------- |
|
|
418
|
-
| `new` | A whole workspace, into a target that holds nothing the plan would collide with
|
|
419
|
-
| `audit` | Nothing
|
|
420
|
-
| `repair` | Each planned path the target is missing or has let drift
|
|
421
|
-
| `catalog` | The package table
|
|
422
|
-
| `overwrite` | Everything `repair` and `catalog` write, plus deletions
|
|
421
|
+
| Verb | Writes |
|
|
422
|
+
| ----------- | ------------------------------------------------------------------------------- |
|
|
423
|
+
| `new` | A whole workspace, into a target that holds nothing the plan would collide with |
|
|
424
|
+
| `audit` | Nothing |
|
|
425
|
+
| `repair` | Each planned path the target is missing or has let drift, and the ranges |
|
|
426
|
+
| `catalog` | The package table, the guide mirrors, and the ranges |
|
|
427
|
+
| `overwrite` | Everything `repair` and `catalog` write, plus deletions |
|
|
423
428
|
|
|
424
429
|
`scaffold --help` prints the whole reference:
|
|
425
430
|
|
|
@@ -448,6 +453,7 @@ options
|
|
|
448
453
|
--from <path> read the data root from a local path instead of the bundled one; catalog alone accepts it more than once
|
|
449
454
|
--target <path> the directory the verb operates on; the working directory when absent
|
|
450
455
|
--json emit one machine-readable value instead of a report
|
|
456
|
+
ORKESTREL_SCAFFOLD_REGISTRY the registry base mapped to upstream.registry.base
|
|
451
457
|
|
|
452
458
|
exit codes
|
|
453
459
|
0 clean
|
|
@@ -458,9 +464,13 @@ exit codes
|
|
|
458
464
|
An option a verb does not list is refused by name rather than parsed and ignored. `--help` is the
|
|
459
465
|
one exception, because it replaces the run rather than modifying it: a command line carrying
|
|
460
466
|
`--help` anywhere prints the whole reference and exits `0` before the line is read as a command, so
|
|
461
|
-
no verb has to list it.
|
|
462
|
-
|
|
463
|
-
|
|
467
|
+
no verb has to list it. Every verb reaches the registry, and none of them invents a range when the
|
|
468
|
+
read produces no answer. Dependency floors states what each verb reads and what it does then.
|
|
469
|
+
|
|
470
|
+
Every read addresses the published registry. Set `ORKESTREL_SCAFFOLD_REGISTRY` to address a loopback
|
|
471
|
+
or private one instead. The process entry maps it to `upstream.registry.base` and nothing else, so
|
|
472
|
+
the seam changes which host answers a read and grants no verb any write authority it did not already
|
|
473
|
+
have.
|
|
464
474
|
|
|
465
475
|
`new --bin` creates the executable entry, its test, and its scoped Vite and TypeScript wrappers. The
|
|
466
476
|
other structural facts do not need creation flags. Add a root `tests/setup*.test.ts` proof for
|
|
@@ -529,8 +539,9 @@ verb would restore. An advisory alone does not make an aligned target drift.
|
|
|
529
539
|
The same plan-reading verbs compare the tooling set the derived blueprint plans against
|
|
530
540
|
`dependencies` and `devDependencies` together. A missing planned package produces one non-blocking
|
|
531
541
|
`dependencies` question naming every missing package and the exact manifest lines to add, in stable
|
|
532
|
-
order. The comparison measures membership
|
|
533
|
-
|
|
542
|
+
order. The comparison measures membership: a workspace-owned extra is outside it, a planned tool may
|
|
543
|
+
live in either section, and how current a declared range is belongs to the registry evidence
|
|
544
|
+
Dependency floors describes rather than to this question. A present section that is not an object
|
|
534
545
|
produces a question instead of a crash. `audit` reports the question without changing its exit
|
|
535
546
|
semantics. `repair` and `overwrite` refuse before writing configuration, and no verb edits the
|
|
536
547
|
birth-owned `package.json`.
|
|
@@ -554,13 +565,13 @@ unless `--dirty` waives that refusal. A target that is not a git repository is r
|
|
|
554
565
|
`--json` replaces the report with one JSON value on standard output. Warnings and refusals go to
|
|
555
566
|
standard error, so a piped value is never polluted.
|
|
556
567
|
|
|
557
|
-
| Verb | Value
|
|
558
|
-
| ----------- |
|
|
559
|
-
| `new` | `MaterializeResult` — `target`, `written`, `skipped`, `removed`
|
|
560
|
-
| `audit` | `Audit` — `findings` and `questions`;
|
|
561
|
-
| `repair` | `MaterializeResult` plus `audit`, the terminal audit taken after the write |
|
|
562
|
-
| `catalog` | `MaterializeResult` plus `entries`, `mirrors`, and `
|
|
563
|
-
| `overwrite` | The `catalog` value plus `audit
|
|
568
|
+
| Verb | Value |
|
|
569
|
+
| ----------- | ------------------------------------------------------------------------------------------ |
|
|
570
|
+
| `new` | `MaterializeResult` — `target`, `written`, `skipped`, `removed` |
|
|
571
|
+
| `audit` | `Audit` — `findings` and `questions` — plus `releases`; findings carry `ownership` |
|
|
572
|
+
| `repair` | `MaterializeResult` plus `audit`, the terminal audit taken after the write, and `releases` |
|
|
573
|
+
| `catalog` | `MaterializeResult` plus `entries`, `mirrors`, `dropped`, and `releases` |
|
|
574
|
+
| `overwrite` | The `catalog` value plus `audit` and `note` on a partial run |
|
|
564
575
|
|
|
565
576
|
Every failure reports the same envelope instead: `{ "error": { "code": …, "message": … } }`. The
|
|
566
577
|
code is a `ScaffoldErrorCode`, or `USAGE` for a command line that never became a command, or
|
|
@@ -597,6 +608,25 @@ valid npm name. A peer reaches the generated workspace through separate represen
|
|
|
597
608
|
every name in that binding, so a peer the workspace declares by hand, such as `vitest`, is left as
|
|
598
609
|
an import in the emitted bundle rather than inlined into it.
|
|
599
610
|
|
|
611
|
+
A range is admitted by the shape and refused by the gate, and a `file:` specifier is where a
|
|
612
|
+
consumer meets that split. `isDependency` reads `range` as a non-empty string bounded at
|
|
613
|
+
`MAX_RANGE_LENGTH` and nothing more, because which ranges a blueprint may declare is a gate law
|
|
614
|
+
that reports its accepted candidates rather than a bare `false`. So a blueprint naming
|
|
615
|
+
`file:vendor/orkestrel-form-0.0.1.tgz` is a valid `Dependency` and reaches the gate.
|
|
616
|
+
`dependenciesToQuestions` then tests every declared range against the pattern its own field
|
|
617
|
+
accepts — `ORKESTREL_RANGE_PATTERN` for a runtime dependency and a fleet peer,
|
|
618
|
+
`FLOOR_RANGE_PATTERN` for a foreign peer, `EXTRA_RANGE_PATTERN` for a development extra — and none
|
|
619
|
+
of them admits a `file:` specifier. The question is blocking, so `audit` reports it and compares no
|
|
620
|
+
path:
|
|
621
|
+
|
|
622
|
+
```text
|
|
623
|
+
dependencies: @orkestrel/form declares the range file:vendor/orkestrel-form-0.0.1.tgz, which dependencies does not accept.
|
|
624
|
+
Audit did not compare the target because the blueprint was refused.
|
|
625
|
+
```
|
|
626
|
+
|
|
627
|
+
A workspace pinned to a committed tarball therefore has no drift detection until it re-pins to a
|
|
628
|
+
registry range. Read that audit as unavailable, not as clean.
|
|
629
|
+
|
|
600
630
|
One published environment owns the package root directly. Several published environments require
|
|
601
631
|
`core`, which owns that root while each other environment keeps its subpath. A multi-environment
|
|
602
632
|
`src` selection without `core` therefore emits entry fields naming a `core` build the workspace
|
|
@@ -807,15 +837,10 @@ removes a foreign file each fail when the destination no longer matches what the
|
|
|
807
837
|
The requirement sits in the type rather than in prose, because a deletion that cannot bind to what
|
|
808
838
|
the audit showed is the one thing the destructive verb must never do.
|
|
809
839
|
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
deliberate: restating the comparison's case analysis in the type would be a second copy of it, able
|
|
815
|
-
to disagree with the one that decides. `isFinding` proves the shape a reader may destructure and
|
|
816
|
-
nothing about whether the verdict is one an audit could have reached. `repair` and `remove`
|
|
817
|
-
re-derive every verdict themselves and act only on what they derived, so a verdict the comparison
|
|
818
|
-
could not have produced is refused by name rather than acted on.
|
|
840
|
+
`isFinding` proves the shape a reader may destructure and nothing about whether the verdict is one
|
|
841
|
+
an audit could have reached. `repair` and `remove` re-derive every verdict themselves and act only
|
|
842
|
+
on what they derived, so a verdict the comparison could not have produced is refused by name rather
|
|
843
|
+
than acted on.
|
|
819
844
|
|
|
820
845
|
That shape is versioned, and the guard runs at runtime. `repair` and `remove` guard the whole audit
|
|
821
846
|
before reading any of it, so an audit persisted or built against an earlier version of this package
|
|
@@ -868,6 +893,69 @@ in an order that would be wrong. It also omits each row whose `lookup` field is
|
|
|
868
893
|
inspect an omitted row's `lookup` field: an omitted `found` row belongs to a cycle, while another
|
|
869
894
|
lookup verdict records why the registry row could not enter a layer.
|
|
870
895
|
|
|
896
|
+
## Dependency floors
|
|
897
|
+
|
|
898
|
+
Every scaffold-owned range from its dependency tables is a floor: a caret over a whole
|
|
899
|
+
`major.minor.patch` version. The triple is the newest release the registry served when that floor
|
|
900
|
+
was last raised, so a workspace generated with no network still receives the latest floor scaffold
|
|
901
|
+
knew rather than a bare `major.0.0`. Caller extras and peers pass through unchanged. Extras follow
|
|
902
|
+
`EXTRA_RANGE_PATTERN`; fleet peers follow `ORKESTREL_RANGE_PATTERN`; foreign peers follow
|
|
903
|
+
`FLOOR_RANGE_PATTERN`.
|
|
904
|
+
|
|
905
|
+
The floors live in scaffold's own `package.json`. `BASE_DEV_DEPENDENCIES` and the tables beside it
|
|
906
|
+
derive each row scaffold installs from that manifest, and the self-pin from its `version` field, so
|
|
907
|
+
the toolchain a generated workspace receives is the toolchain scaffold runs. The rows scaffold does
|
|
908
|
+
not install are seeds — `@vitejs/plugin-vue`, `vue`, `vue-tsc`, `vite-plugin-singlefile`, and the
|
|
909
|
+
application-server fleet packages — and each carries the newest triple its supported major served
|
|
910
|
+
when it was written.
|
|
911
|
+
[`tests/src/core/constants.test.ts`](../tests/src/core/constants.test.ts) names that seeded set, so a
|
|
912
|
+
row entering or leaving the manifest moves a test rather than passing unnoticed.
|
|
913
|
+
|
|
914
|
+
A newer major is never crossed for you. `audit` reports one as a non-blocking `dependencies`
|
|
915
|
+
question, and a person decides whether the generated toolchain supports it. Inside the declared
|
|
916
|
+
major the verbs raise the floor themselves, which is what makes the caret's own width beside the
|
|
917
|
+
point: `^0.64.0` admits no `0.65.0`, and `repair` rewrites the range to `^0.65.0` rather than
|
|
918
|
+
widening it.
|
|
919
|
+
|
|
920
|
+
Compatibility and staleness are separate questions, so they are read by separate helpers.
|
|
921
|
+
`extractRangeMajor` answers which major a range names, which is what a compatibility bound is
|
|
922
|
+
measured against. `matchesRange` answers whether a published version satisfies a range, which is
|
|
923
|
+
admission rather than currency: a drift check that asked it would read a raised floor and a stale one
|
|
924
|
+
alike.
|
|
925
|
+
|
|
926
|
+
### What each verb reads
|
|
927
|
+
|
|
928
|
+
| Verb | Reads | Writes on a complete answer | With no answer |
|
|
929
|
+
| ----------- | ----------------------------------------------------------- | ----------------------------------------------- | -------------------------------------------------------- |
|
|
930
|
+
| `new` | Every `@orkestrel/*` row the compiled manifest declares | The plan's ranges, before the target is opened | `FETCH`, exit `1`, nothing written |
|
|
931
|
+
| `audit` | Every declared fleet row and planned foreign row | Nothing | Failed verdicts reported, exit `1`, nothing written |
|
|
932
|
+
| `repair` | Every declared fleet row and planned foreign row | The manifest's ranges, beside the repair | `FETCH`, exit `1`, nothing written |
|
|
933
|
+
| `catalog` | The organization package list and every packument behind it | The manifest's ranges, beside the package table | `FETCH`, exit `1`, nothing written |
|
|
934
|
+
| `overwrite` | Everything `repair` and `catalog` read | Everything `repair` and `catalog` write | The offline half stands, `note` names the step, exit `1` |
|
|
935
|
+
|
|
936
|
+
Each verb resolves the whole set before it opens a write transaction, so a partial answer never
|
|
937
|
+
becomes a partial pin set. `overwrite` is the exception, and deliberately: its offline repair and
|
|
938
|
+
deletion have already landed by the time the network half runs, so a step that produces no answer is
|
|
939
|
+
collected into `note` and reported rather than discarding work that succeeded.
|
|
940
|
+
|
|
941
|
+
A fleet row is compared exactly — `^0.1.0` is stale the moment the registry serves `0.1.2` — and
|
|
942
|
+
that inequality alone raises `audit` to exit `1`. A foreign row is compared inside its declared
|
|
943
|
+
major, and each verdict is a non-blocking question: one for a floor below the newest release that
|
|
944
|
+
major serves, and one for a newer major the registry publishes.
|
|
945
|
+
|
|
946
|
+
### Raising the floor before a release
|
|
947
|
+
|
|
948
|
+
The floors ship inside the package, so a release carrying stale ones propagates them to every
|
|
949
|
+
workspace generated from it until the next release. Raise them as the opening step of a release,
|
|
950
|
+
while the registry is reachable:
|
|
951
|
+
|
|
952
|
+
1. Run `scaffold audit` against this repository and read its `dependencies` questions.
|
|
953
|
+
2. Run `scaffold repair`, or `npm update` followed by the same audit, until no question remains.
|
|
954
|
+
3. Run the gate chain, then bump the version and publish.
|
|
955
|
+
|
|
956
|
+
That cycle is what keeps every release shipping the then-latest floor, and it is what a consumer
|
|
957
|
+
generating a workspace with no network receives.
|
|
958
|
+
|
|
871
959
|
## Vendored data root
|
|
872
960
|
|
|
873
961
|
The vendored data root is the shared file set, staged into the published package as plain data. It
|
|
@@ -876,6 +964,13 @@ directories, the bench scripts, the shared policy register, the byte-identical r
|
|
|
876
964
|
the guide mirrors a generated workspace starts from. `HOST_PATHS` is the candidate list; a plan
|
|
877
965
|
carries the subset its target selects, because a workspace never mirrors its own guide.
|
|
878
966
|
|
|
967
|
+
`.claude/settings.json` is in that set, and the artifact planned for it is content-owned. `repair`
|
|
968
|
+
and `overwrite` restore its bytes, so an edit made to it inside a target is reverted at the next
|
|
969
|
+
visit and reported as drift until then. Put an operator grant in `.claude/settings.local.json`
|
|
970
|
+
instead. That path is outside `HOST_PATHS` and matches the vendoring deny-list
|
|
971
|
+
`matchesSensitivePath` reads, so `stageHost` never copies it into a host root and no plan carries
|
|
972
|
+
it.
|
|
973
|
+
|
|
879
974
|
`stageHost` fills the root from a real checkout at build time:
|
|
880
975
|
|
|
881
976
|
```ts
|
|
@@ -907,7 +1002,10 @@ A workspace's file set is a function of its axes plus its structural facts. Noth
|
|
|
907
1002
|
except the manifest.
|
|
908
1003
|
|
|
909
1004
|
- One computed artifact: `package.json`, with the entry points, `exports` map, scripts, and
|
|
910
|
-
development dependencies its selection implies.
|
|
1005
|
+
development dependencies its selection implies. A publishing manifest carries
|
|
1006
|
+
`"prepack": "npm run build"` so a publish rebuilds `dist/` and cannot ship a stale artifact;
|
|
1007
|
+
the hook is publish-time only, and every generated distribution proof passes
|
|
1008
|
+
`--ignore-scripts` to `npm pack` so a suite never re-runs the build it already gates.
|
|
911
1009
|
- One template artifact per configuration file the selection needs: the root `tsconfig.json` and
|
|
912
1010
|
`vite.config.ts`, plus a Vite config and a scoped TypeScript config per selected environment and
|
|
913
1011
|
for `bin` when it is set.
|
|
@@ -1016,12 +1114,17 @@ verdict carrying its cause rather than thrown, so one unreachable package never
|
|
|
1016
1114
|
rest of the answer. The organization package list is the exception, because without it there is no
|
|
1017
1115
|
fleet to report.
|
|
1018
1116
|
|
|
1117
|
+
A found verdict carries the newest version the declared range admits, not whatever `dist-tags.latest`
|
|
1118
|
+
names. `lookup` reads the packument's version map, selects across it before any collection bound can
|
|
1119
|
+
truncate the map, and falls back to the latest tag only when that tag is itself admitted. A range of
|
|
1120
|
+
`*` admits every version, which is how a caller asks for the newest release outright.
|
|
1121
|
+
|
|
1019
1122
|
Each bound counts decoded bytes, and a version lookup asks the registry for the abbreviated
|
|
1020
1123
|
packument — `dist-tags` and a trimmed version map, rather than the full per-version metadata no
|
|
1021
|
-
verdict reads. That is the smallest form the registry publishes
|
|
1022
|
-
`
|
|
1023
|
-
|
|
1024
|
-
statement about the package.
|
|
1124
|
+
verdict reads. That is the smallest form the registry publishes. The default response limit is
|
|
1125
|
+
`MAX_REGISTRY_BYTES`, and the default call budget is `MAX_TOTAL_REGISTRY_BYTES`. A package that
|
|
1126
|
+
passes the response limit comes back as a `failed` verdict naming the limit, which is this reader's
|
|
1127
|
+
bound and not a statement about the package.
|
|
1025
1128
|
|
|
1026
1129
|
A status that carries no representation — a `204` or a `205` — is a `failed` verdict naming the
|
|
1027
1130
|
status, never a `found` answer holding no bytes. A genuinely empty file arrives as a `200` and does
|
|
@@ -1096,13 +1199,8 @@ ambient value plus isolated filesystem and network drivers for the mutating exam
|
|
|
1096
1199
|
drivers is separate test capability rather than name-resolution parity.
|
|
1097
1200
|
|
|
1098
1201
|
**The library does not enforce the creating verb's policy.** `new` refuses a blueprint carrying any
|
|
1099
|
-
question, and `materialize` writes any plan into any vacant target.
|
|
1100
|
-
|
|
1101
|
-
library, and its manifest names a `core` build the workspace never runs — which is exactly what the
|
|
1102
|
-
advisory said. The refusal lives in the verb that chose the shape because that verb is the only one
|
|
1103
|
-
holding the advice: `compile` returns `questions` beside `plan`, and `materialize` receives the plan
|
|
1104
|
-
alone, so it has nothing to refuse on. The Compile section states the rule a library caller applies
|
|
1105
|
-
in its place.
|
|
1202
|
+
question, and `materialize` writes any plan into any vacant target. The Compile section states the
|
|
1203
|
+
rule a library caller applies in its place.
|
|
1106
1204
|
|
|
1107
1205
|
**`isPath` does not prove host portability.** It proves bounded target-relative syntax and rejects
|
|
1108
1206
|
traversal, separators, controls, and reserved syntax characters. It deliberately admits host-specific
|
|
@@ -1188,8 +1286,9 @@ port, so the run drives nothing external and stays in `test`.
|
|
|
1188
1286
|
taken from a hostile value.
|
|
1189
1287
|
- [`tests/src/core/templates.test.ts`](../tests/src/core/templates.test.ts) — the frozen template
|
|
1190
1288
|
definitions.
|
|
1191
|
-
- [`tests/src/core/constants.test.ts`](../tests/src/core/constants.test.ts) — the
|
|
1192
|
-
|
|
1289
|
+
- [`tests/src/core/constants.test.ts`](../tests/src/core/constants.test.ts) — the seeded rows named
|
|
1290
|
+
as a set, the floor form every shared table and this manifest carry, and the emitted TypeScript
|
|
1291
|
+
bound.
|
|
1193
1292
|
- [`tests/src/server/Materializer.test.ts`](../tests/src/server/Materializer.test.ts) — every
|
|
1194
1293
|
mutation verb against a real temporary target and a real vendored root.
|
|
1195
1294
|
- [`tests/src/server/WriteTransaction.test.ts`](../tests/src/server/WriteTransaction.test.ts) —
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|