@orkestrel/scaffold 0.0.31 → 0.0.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/main.js +144 -23
- package/dist/bin/main.js.map +1 -1
- package/dist/host/agents/orchestration.md +20 -1
- package/dist/host/claude/agents/grok.md +17 -6
- package/dist/host/claude/rules/architecture.md +12 -0
- package/dist/host/claude/rules/documentation.md +2 -1
- package/dist/host/claude/rules/tests.md +57 -21
- package/dist/host/claude/rules/workspace.md +25 -20
- package/dist/host/dotfiles/oxlintrc.json +1 -0
- package/dist/host/guides/scaffold.md +142 -67
- package/dist/host/tests/config.test.ts +45 -17
- package/dist/host/tests/policy.test.ts +254 -2
- package/dist/host/tests/setupPolicy.ts +297 -34
- package/dist/src/core/index.cjs +176 -179
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +90 -33
- package/dist/src/core/index.d.ts +90 -33
- package/dist/src/core/index.js +171 -180
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +13 -1
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.js +14 -2
- package/dist/src/server/index.js.map +1 -1
- package/package.json +4 -4
|
@@ -42,7 +42,7 @@ describe('root configuration', () => {
|
|
|
42
42
|
for (const key of required.keys()) {
|
|
43
43
|
if (!absent.has(key)) throw new Error('The alias population carries no required entry')
|
|
44
44
|
}
|
|
45
|
-
}).
|
|
45
|
+
}).toThrow('The alias population carries no required entry')
|
|
46
46
|
for (const [key, expected] of required) {
|
|
47
47
|
const values = declared.get(key)
|
|
48
48
|
if (values === undefined) throw new Error(`${key} is not declared`)
|
|
@@ -102,7 +102,14 @@ describe('root configuration', () => {
|
|
|
102
102
|
setup: ['./tests/setup.ts', './tests/setupServer.ts'],
|
|
103
103
|
})
|
|
104
104
|
}
|
|
105
|
-
for (const label of [
|
|
105
|
+
for (const label of [
|
|
106
|
+
'policy',
|
|
107
|
+
'config',
|
|
108
|
+
'guides',
|
|
109
|
+
'conformance',
|
|
110
|
+
'distribution',
|
|
111
|
+
'integration',
|
|
112
|
+
]) {
|
|
106
113
|
if (!existsSync(resolve(root, `tests/${label}.test.ts`))) continue
|
|
107
114
|
expected.set(label, {
|
|
108
115
|
include: `tests/${label}.test.ts`,
|
|
@@ -218,7 +225,7 @@ describe('root configuration', () => {
|
|
|
218
225
|
expect(missing.delete('probe')).toBe(true)
|
|
219
226
|
expect(() => {
|
|
220
227
|
for (const [label, project] of expected) expect(missing.get(label)).toStrictEqual(project)
|
|
221
|
-
}).
|
|
228
|
+
}).toThrow(/strictly equal/u)
|
|
222
229
|
|
|
223
230
|
const misconfigured = new Map(configured)
|
|
224
231
|
const probe = misconfigured.get('probe')
|
|
@@ -227,7 +234,7 @@ describe('root configuration', () => {
|
|
|
227
234
|
expect(() => {
|
|
228
235
|
for (const [label, project] of expected)
|
|
229
236
|
expect(misconfigured.get(label)).toStrictEqual(project)
|
|
230
|
-
}).
|
|
237
|
+
}).toThrow(/strictly equal/u)
|
|
231
238
|
})
|
|
232
239
|
|
|
233
240
|
it('requires and validates every selected target wrapper', async () => {
|
|
@@ -344,10 +351,10 @@ describe('root configuration', () => {
|
|
|
344
351
|
const controlFound = ['configs/app/vite.server.config.ts']
|
|
345
352
|
expect(() => {
|
|
346
353
|
for (const wrapper of controlRequired) expect(controlFound).toContain(wrapper)
|
|
347
|
-
}).
|
|
348
|
-
expect(() =>
|
|
349
|
-
|
|
350
|
-
)
|
|
354
|
+
}).toThrow(/vite\.browser/u)
|
|
355
|
+
expect(() => expect(resolve(root, 'dist/actual')).toBe(resolve(root, 'dist/control'))).toThrow(
|
|
356
|
+
/expected/u,
|
|
357
|
+
)
|
|
351
358
|
})
|
|
352
359
|
|
|
353
360
|
it('registers proof scripts in the correct gate', () => {
|
|
@@ -359,8 +366,10 @@ describe('root configuration', () => {
|
|
|
359
366
|
if (typeof scripts !== 'object' || scripts === null) {
|
|
360
367
|
throw new Error('The package manifest carries no scripts')
|
|
361
368
|
}
|
|
369
|
+
const publishes = Object.getOwnPropertyDescriptor(manifest, 'private')?.value !== true
|
|
362
370
|
const test = Object.getOwnPropertyDescriptor(scripts, 'test')?.value
|
|
363
371
|
const config = Object.getOwnPropertyDescriptor(scripts, 'test:config')?.value
|
|
372
|
+
const distribution = Object.getOwnPropertyDescriptor(scripts, 'test:distribution')?.value
|
|
364
373
|
const integration = Object.getOwnPropertyDescriptor(scripts, 'test:integration')?.value
|
|
365
374
|
const conformance = Object.getOwnPropertyDescriptor(scripts, 'test:conformance')?.value
|
|
366
375
|
const service = Object.getOwnPropertyDescriptor(scripts, 'test:service')?.value
|
|
@@ -390,24 +399,39 @@ describe('root configuration', () => {
|
|
|
390
399
|
expect(registered.has('config')).toBe(true)
|
|
391
400
|
expect(registered.has('control')).toBe(false)
|
|
392
401
|
const hasConformance = registered.has('conformance')
|
|
402
|
+
const hasDistribution = registered.has('distribution')
|
|
393
403
|
const hasService = registered.has('service')
|
|
394
404
|
expect(config).toBe(
|
|
395
405
|
'vitest run --config vite.config.ts --no-cache --reporter=dot --project config',
|
|
396
406
|
)
|
|
397
407
|
expect(typeof test === 'string' && test.includes('npm run test:config')).toBe(true)
|
|
408
|
+
expect(distribution).toBe(
|
|
409
|
+
hasDistribution
|
|
410
|
+
? 'vitest run --config vite.config.ts --no-cache --reporter=dot --project distribution'
|
|
411
|
+
: undefined,
|
|
412
|
+
)
|
|
413
|
+
expect(typeof test === 'string' && test.includes('test:distribution')).toBe(false)
|
|
414
|
+
expect(typeof publish === 'string' && publish.includes('npm run test:distribution')).toBe(
|
|
415
|
+
hasDistribution && publishes,
|
|
416
|
+
)
|
|
417
|
+
expect(typeof publish === 'string').toBe(publishes)
|
|
398
418
|
expect(integration).toBe(
|
|
399
419
|
hasIntegration
|
|
400
420
|
? 'vitest run --config vite.config.ts --no-cache --reporter=dot --project integration'
|
|
401
421
|
: undefined,
|
|
402
422
|
)
|
|
403
|
-
|
|
404
|
-
|
|
423
|
+
// The integration seed composes barrels and starts no process, so it runs in
|
|
424
|
+
// `test` like any other hermetic proof. `prepublishOnly` reaches it through
|
|
425
|
+
// `npm test` rather than through a second direct invocation.
|
|
426
|
+
expect(typeof test === 'string' && test.includes('npm run test:integration')).toBe(
|
|
405
427
|
hasIntegration,
|
|
406
428
|
)
|
|
429
|
+
expect(typeof publish === 'string' && publish.includes('npm run test:integration')).toBe(false)
|
|
407
430
|
// A registered project no gate runs is a proof that never executes, and it
|
|
408
431
|
// never fails, so the suite reports green while carrying it. Conformance is
|
|
409
|
-
// hermetic and belongs to `test
|
|
410
|
-
// service
|
|
432
|
+
// hermetic and belongs to `test`. A publishing workspace isolates the
|
|
433
|
+
// live-service project in `prepublishOnly`; a private workspace reaches it
|
|
434
|
+
// from `test`, because npm never runs a private package's publish lifecycle.
|
|
411
435
|
expect(conformance).toBe(
|
|
412
436
|
hasConformance
|
|
413
437
|
? 'vitest run --config vite.config.ts --no-cache --reporter=dot --project conformance'
|
|
@@ -421,8 +445,12 @@ describe('root configuration', () => {
|
|
|
421
445
|
? 'vitest run --config vite.config.ts --no-cache --reporter=dot --project service'
|
|
422
446
|
: undefined,
|
|
423
447
|
)
|
|
424
|
-
expect(typeof test === 'string' && test.includes('test:service')).toBe(
|
|
425
|
-
|
|
448
|
+
expect(typeof test === 'string' && test.includes('npm run test:service')).toBe(
|
|
449
|
+
hasService && !publishes,
|
|
450
|
+
)
|
|
451
|
+
expect(typeof publish === 'string' && publish.includes('npm run test:service')).toBe(
|
|
452
|
+
hasService && publishes,
|
|
453
|
+
)
|
|
426
454
|
})
|
|
427
455
|
})
|
|
428
456
|
|
|
@@ -562,9 +590,9 @@ describe('configuration helpers', () => {
|
|
|
562
590
|
expect(() => configHelpers.enforceOutputPath(expected, expected)).not.toThrow()
|
|
563
591
|
expect(() =>
|
|
564
592
|
configHelpers.enforceOutputPath(resolve(root, 'dist/outside-control'), expected),
|
|
565
|
-
).
|
|
593
|
+
).toThrow('Build output must use its exact configured workspace directory')
|
|
566
594
|
const outside = resolve(dirname(root), 'outside-config-control')
|
|
567
|
-
expect(() => configHelpers.enforceOutputPath(outside, outside)).
|
|
595
|
+
expect(() => configHelpers.enforceOutputPath(outside, outside)).toThrow(
|
|
568
596
|
'Build output must remain inside the workspace',
|
|
569
597
|
)
|
|
570
598
|
})
|
|
@@ -611,7 +639,7 @@ describe('configuration helpers', () => {
|
|
|
611
639
|
error: expect.unreachable,
|
|
612
640
|
resolve: Promise.resolve.bind(Promise, { id: source }),
|
|
613
641
|
}
|
|
614
|
-
await expect(Reflect.apply(hook, context, ['@src/server', source])).rejects.
|
|
642
|
+
await expect(Reflect.apply(hook, context, ['@src/server', source])).rejects.toThrow(
|
|
615
643
|
'Browser modules cannot depend on Node or server-only modules',
|
|
616
644
|
)
|
|
617
645
|
await expect(Reflect.apply(hook, context, ['@src/core', source])).resolves.toBeNull()
|
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
inspectPolicySources,
|
|
8
8
|
inspectPolicyWorkspace,
|
|
9
9
|
POLICY_CONTROLS,
|
|
10
|
+
stemToPolicyCandidates,
|
|
11
|
+
testToPolicyStem,
|
|
10
12
|
} from './setupPolicy.js'
|
|
11
13
|
|
|
12
14
|
describe('fleet policy register', () => {
|
|
@@ -25,8 +27,258 @@ describe('fleet policy register', () => {
|
|
|
25
27
|
'tests/app/browser/routes.test.ts',
|
|
26
28
|
'tests/app/edge/deep/integration.test.ts',
|
|
27
29
|
]
|
|
28
|
-
const
|
|
29
|
-
expect(inspectPolicyMirrorPaths(tests,
|
|
30
|
+
const modules = new Set(['src/worker/Worker.ts', 'app/browser/routes.ts'])
|
|
31
|
+
expect(inspectPolicyMirrorPaths(tests, modules)).toEqual([])
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
it('derives mirror candidates in module resolution order', () => {
|
|
35
|
+
const stem = testToPolicyStem('tests/app/core/setupBrowser.test.ts')
|
|
36
|
+
expect(stem).toBe('app/core/setupBrowser')
|
|
37
|
+
expect(stemToPolicyCandidates('app/core/setupBrowser')).toEqual([
|
|
38
|
+
'app/core/setupBrowser.cts',
|
|
39
|
+
'app/core/setupBrowser.mts',
|
|
40
|
+
'app/core/setupBrowser.ts',
|
|
41
|
+
'app/core/setupBrowser.tsx',
|
|
42
|
+
'app/core/setupBrowser.vue',
|
|
43
|
+
'app/core/setupBrowser.scss',
|
|
44
|
+
'app/core/setupBrowser.css',
|
|
45
|
+
'app/core/_setupBrowser.scss',
|
|
46
|
+
'app/core/_setupBrowser.css',
|
|
47
|
+
'tests/app/core/setupBrowser.ts',
|
|
48
|
+
])
|
|
49
|
+
expect(testToPolicyStem('tests/app/core/integration.test.ts')).toBeUndefined()
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
describe('policy population controls', () => {
|
|
54
|
+
it('accepts a Vue module mirror', () => {
|
|
55
|
+
expect(
|
|
56
|
+
inspectPolicyControl({
|
|
57
|
+
label: 'accepts a Vue module mirror',
|
|
58
|
+
membership: 'registered module extensions with an exact test stem',
|
|
59
|
+
rule: 'mirror',
|
|
60
|
+
files: [
|
|
61
|
+
{ path: 'app/browser/Widget.vue', content: '<template></template>\n' },
|
|
62
|
+
{ path: 'tests/app/browser/Widget.test.ts', content: '' },
|
|
63
|
+
],
|
|
64
|
+
}),
|
|
65
|
+
).toEqual([])
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
it('accepts a TSX module mirror', () => {
|
|
69
|
+
expect(
|
|
70
|
+
inspectPolicyControl({
|
|
71
|
+
label: 'accepts a TSX module mirror',
|
|
72
|
+
membership: 'registered module extensions with an exact test stem',
|
|
73
|
+
rule: 'mirror',
|
|
74
|
+
files: [
|
|
75
|
+
{ path: 'app/browser/Widget.tsx', content: '' },
|
|
76
|
+
{ path: 'tests/app/browser/Widget.test.ts', content: '' },
|
|
77
|
+
],
|
|
78
|
+
}),
|
|
79
|
+
).toEqual([])
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
it('accepts an MTS module mirror', () => {
|
|
83
|
+
expect(
|
|
84
|
+
inspectPolicyControl({
|
|
85
|
+
label: 'accepts an MTS module mirror',
|
|
86
|
+
membership: 'registered module extensions with an exact test stem',
|
|
87
|
+
rule: 'mirror',
|
|
88
|
+
files: [
|
|
89
|
+
{ path: 'app/browser/Widget.mts', content: '' },
|
|
90
|
+
{ path: 'tests/app/browser/Widget.test.ts', content: '' },
|
|
91
|
+
],
|
|
92
|
+
}),
|
|
93
|
+
).toEqual([])
|
|
94
|
+
})
|
|
95
|
+
|
|
96
|
+
it('accepts a CTS module mirror', () => {
|
|
97
|
+
expect(
|
|
98
|
+
inspectPolicyControl({
|
|
99
|
+
label: 'accepts a CTS module mirror',
|
|
100
|
+
membership: 'registered module extensions with an exact test stem',
|
|
101
|
+
rule: 'mirror',
|
|
102
|
+
files: [
|
|
103
|
+
{ path: 'app/browser/Widget.cts', content: '' },
|
|
104
|
+
{ path: 'tests/app/browser/Widget.test.ts', content: '' },
|
|
105
|
+
],
|
|
106
|
+
}),
|
|
107
|
+
).toEqual([])
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('accepts a Sass partial module mirror', () => {
|
|
111
|
+
expect(
|
|
112
|
+
inspectPolicyControl({
|
|
113
|
+
label: 'accepts a Sass partial module mirror',
|
|
114
|
+
membership: 'registered partial extensions with an exact underscore-free test stem',
|
|
115
|
+
rule: 'mirror',
|
|
116
|
+
files: [
|
|
117
|
+
{ path: 'app/browser/styles/_tokens.scss', content: '' },
|
|
118
|
+
{ path: 'tests/app/browser/styles/tokens.test.ts', content: '' },
|
|
119
|
+
],
|
|
120
|
+
}),
|
|
121
|
+
).toEqual([])
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it('accepts a setup module mirror inside tests', () => {
|
|
125
|
+
expect(
|
|
126
|
+
inspectPolicyControl({
|
|
127
|
+
label: 'accepts a setup module mirror inside tests',
|
|
128
|
+
membership: 'tests-axis setup modules with an exact test stem',
|
|
129
|
+
rule: 'mirror',
|
|
130
|
+
files: [
|
|
131
|
+
{ path: 'tests/app/core/setup.ts', content: '' },
|
|
132
|
+
{ path: 'tests/app/core/setup.test.ts', content: '' },
|
|
133
|
+
],
|
|
134
|
+
}),
|
|
135
|
+
).toEqual([])
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('excludes a .d.ts ambient declaration from placement', () => {
|
|
139
|
+
expect(
|
|
140
|
+
inspectPolicyControl({
|
|
141
|
+
label: 'excludes a .d.ts ambient declaration from placement',
|
|
142
|
+
membership: 'ambient TypeScript declarations',
|
|
143
|
+
rule: 'type',
|
|
144
|
+
files: [
|
|
145
|
+
{
|
|
146
|
+
path: 'app/browser/env.d.ts',
|
|
147
|
+
content: 'export interface EnvironmentInterface {}\n',
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
}),
|
|
151
|
+
).toEqual([])
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
it('excludes a .d.mts ambient declaration from placement', () => {
|
|
155
|
+
expect(
|
|
156
|
+
inspectPolicyControl({
|
|
157
|
+
label: 'excludes a .d.mts ambient declaration from placement',
|
|
158
|
+
membership: 'ambient TypeScript declarations',
|
|
159
|
+
rule: 'type',
|
|
160
|
+
files: [
|
|
161
|
+
{
|
|
162
|
+
path: 'app/browser/env.d.mts',
|
|
163
|
+
content: 'export interface EnvironmentInterface {}\n',
|
|
164
|
+
},
|
|
165
|
+
],
|
|
166
|
+
}),
|
|
167
|
+
).toEqual([])
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
it('excludes a .d.cts ambient declaration from placement', () => {
|
|
171
|
+
expect(
|
|
172
|
+
inspectPolicyControl({
|
|
173
|
+
label: 'excludes a .d.cts ambient declaration from placement',
|
|
174
|
+
membership: 'ambient TypeScript declarations',
|
|
175
|
+
rule: 'type',
|
|
176
|
+
files: [
|
|
177
|
+
{
|
|
178
|
+
path: 'app/browser/env.d.cts',
|
|
179
|
+
content: 'export interface EnvironmentInterface {}\n',
|
|
180
|
+
},
|
|
181
|
+
],
|
|
182
|
+
}),
|
|
183
|
+
).toEqual([])
|
|
184
|
+
})
|
|
185
|
+
|
|
186
|
+
it('accepts a direct callback argument in constants.ts', () => {
|
|
187
|
+
expect(
|
|
188
|
+
inspectPolicyControl({
|
|
189
|
+
label: 'accepts a direct callback argument in constants.ts',
|
|
190
|
+
membership: 'anonymous callbacks passed directly as call arguments',
|
|
191
|
+
rule: 'function',
|
|
192
|
+
files: [
|
|
193
|
+
{
|
|
194
|
+
path: 'app/edge/constants.ts',
|
|
195
|
+
content: 'export const LABELS = Object.freeze(COLUMNS.map((column) => column.label))\n',
|
|
196
|
+
},
|
|
197
|
+
],
|
|
198
|
+
}),
|
|
199
|
+
).toEqual([])
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
it('accepts a helper namespace in helpers.ts', () => {
|
|
203
|
+
expect(
|
|
204
|
+
inspectPolicyControl({
|
|
205
|
+
label: 'accepts a helper namespace in helpers.ts',
|
|
206
|
+
membership: 'camelCase helper namespaces containing function behavior',
|
|
207
|
+
rule: 'data',
|
|
208
|
+
files: [
|
|
209
|
+
{
|
|
210
|
+
path: 'app/edge/helpers.ts',
|
|
211
|
+
content: 'export const formatters = Object.freeze({ money: build(fmt) })\n',
|
|
212
|
+
},
|
|
213
|
+
],
|
|
214
|
+
}),
|
|
215
|
+
).toEqual([])
|
|
216
|
+
})
|
|
217
|
+
|
|
218
|
+
it('accepts a concise-body direct return in constants.ts', () => {
|
|
219
|
+
expect(
|
|
220
|
+
inspectPolicyControl({
|
|
221
|
+
label: 'accepts a concise-body direct return in constants.ts',
|
|
222
|
+
membership: 'anonymous functions returned directly through a concise arrow body',
|
|
223
|
+
rule: 'function',
|
|
224
|
+
files: [
|
|
225
|
+
{
|
|
226
|
+
path: 'app/edge/constants.ts',
|
|
227
|
+
content: 'export const WRAPPED = wrap(() => () => 1)\n',
|
|
228
|
+
},
|
|
229
|
+
],
|
|
230
|
+
}),
|
|
231
|
+
).toEqual([])
|
|
232
|
+
})
|
|
233
|
+
|
|
234
|
+
it('accepts a returned function inside a direct callback', () => {
|
|
235
|
+
expect(
|
|
236
|
+
inspectPolicyControl({
|
|
237
|
+
label: 'accepts a returned function inside a direct callback',
|
|
238
|
+
membership: 'anonymous functions returned directly by a return statement',
|
|
239
|
+
rule: 'function',
|
|
240
|
+
files: [
|
|
241
|
+
{
|
|
242
|
+
path: 'app/edge/constants.ts',
|
|
243
|
+
content:
|
|
244
|
+
'export const LABELS = Object.freeze(COLUMNS.map((column) => { return () => column.label }))\n',
|
|
245
|
+
},
|
|
246
|
+
],
|
|
247
|
+
}),
|
|
248
|
+
).toEqual([])
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
it('accepts directly returned functions through callback control flow', () => {
|
|
252
|
+
expect(
|
|
253
|
+
inspectPolicyControl({
|
|
254
|
+
label: 'accepts directly returned functions through callback control flow',
|
|
255
|
+
membership: 'anonymous functions returned directly through control-flow branches',
|
|
256
|
+
rule: 'function',
|
|
257
|
+
files: [
|
|
258
|
+
{
|
|
259
|
+
path: 'app/edge/constants.ts',
|
|
260
|
+
content:
|
|
261
|
+
'export const VALUES = Object.freeze(C.map((c) => { if (c) return () => 1; return () => 2 }))\n',
|
|
262
|
+
},
|
|
263
|
+
],
|
|
264
|
+
}),
|
|
265
|
+
).toEqual([])
|
|
266
|
+
})
|
|
267
|
+
|
|
268
|
+
it('excludes Vue text from the placement population', () => {
|
|
269
|
+
expect(
|
|
270
|
+
inspectPolicyControl({
|
|
271
|
+
label: 'excludes Vue text from the placement population',
|
|
272
|
+
membership: 'files outside the declared TypeScript source extensions',
|
|
273
|
+
rule: 'type',
|
|
274
|
+
files: [
|
|
275
|
+
{
|
|
276
|
+
path: 'app/browser/Panel.vue',
|
|
277
|
+
content: '<script setup lang="ts">\nexport interface PanelInterface {}\n</script>\n',
|
|
278
|
+
},
|
|
279
|
+
],
|
|
280
|
+
}),
|
|
281
|
+
).toEqual([])
|
|
30
282
|
})
|
|
31
283
|
})
|
|
32
284
|
|