@orkestrel/scaffold 0.0.103 → 0.0.104

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.
Files changed (88) hide show
  1. package/dist/agents/skills/orkestrel-dispatch/scripts/helpers.js +84 -1
  2. package/dist/agents/skills/orkestrel-dispatch/scripts/launch.js +21 -6
  3. package/dist/agents/skills/orkestrel-harden/scripts/titles.js +338 -0
  4. package/dist/agents/skills/orkestrel-publish/scripts/swap.js +3 -39
  5. package/dist/agents/skills/orkestrel-publish/scripts/wave.js +433 -47
  6. package/dist/agents/skills/orkestrel-publish/scripts/window.js +2 -1
  7. package/dist/bin/main.js +16 -9
  8. package/dist/bin/main.js.map +1 -1
  9. package/dist/host/agents/orchestration.md +3 -3
  10. package/dist/host/agents/skills/enterprise-bootstrap/references/components.md +2 -2
  11. package/dist/host/agents/skills/enterprise-bootstrap/references/inspection.md +1 -1
  12. package/dist/host/agents/skills/orkestrel-build/SKILL.md +2 -2
  13. package/dist/host/agents/skills/orkestrel-debrief/SKILL.md +2 -2
  14. package/dist/host/agents/skills/orkestrel-debrief/references/field-testing.md +1 -1
  15. package/dist/host/agents/skills/orkestrel-debrief/references/instruction-audit.md +3 -3
  16. package/dist/host/agents/skills/orkestrel-dispatch/SKILL.md +10 -10
  17. package/dist/host/agents/skills/orkestrel-dispatch/references/launch.md +1 -0
  18. package/dist/host/agents/skills/orkestrel-dispatch/scripts/helpers.ts +100 -1
  19. package/dist/host/agents/skills/orkestrel-dispatch/scripts/launch.ts +27 -6
  20. package/dist/host/agents/skills/orkestrel-falsify/SKILL.md +4 -4
  21. package/dist/host/agents/skills/orkestrel-falsify/agents/openai.yaml +1 -1
  22. package/dist/host/agents/skills/orkestrel-falsify/references/brief.md +8 -7
  23. package/dist/host/agents/skills/orkestrel-falsify/references/reconcile.md +6 -6
  24. package/dist/host/agents/skills/orkestrel-harden/SKILL.md +6 -1
  25. package/dist/host/agents/skills/orkestrel-harden/references/bindings.md +1700 -0
  26. package/dist/host/agents/skills/orkestrel-harden/references/contract.md +2 -2
  27. package/dist/host/agents/skills/orkestrel-harden/references/equivalence.md +48 -0
  28. package/dist/host/agents/skills/orkestrel-harden/references/hardening.md +4 -2
  29. package/dist/host/agents/skills/orkestrel-harden/scripts/titles.ts +425 -0
  30. package/dist/host/agents/skills/orkestrel-journey/SKILL.md +2 -2
  31. package/dist/host/agents/skills/orkestrel-journey/references/captures.md +9 -8
  32. package/dist/host/agents/skills/orkestrel-polish/SKILL.md +3 -3
  33. package/dist/host/agents/skills/orkestrel-publish/SKILL.md +2 -2
  34. package/dist/host/agents/skills/orkestrel-publish/references/release.md +1 -0
  35. package/dist/host/agents/skills/orkestrel-publish/references/wave.md +56 -13
  36. package/dist/host/agents/skills/orkestrel-publish/references/window.md +6 -6
  37. package/dist/host/agents/skills/orkestrel-publish/scripts/swap.ts +5 -49
  38. package/dist/host/agents/skills/orkestrel-publish/scripts/wave.ts +646 -49
  39. package/dist/host/agents/skills/orkestrel-publish/scripts/window.ts +2 -1
  40. package/dist/host/agents/transports/codex.md +1 -1
  41. package/dist/host/claude/agents/orkestrel.md +54 -53
  42. package/dist/host/claude/agents/reviewer.md +4 -4
  43. package/dist/host/claude/rules/documentation.md +2 -1
  44. package/dist/host/claude/rules/patterns.md +1 -1
  45. package/dist/host/claude/rules/quality.md +2 -2
  46. package/dist/host/claude/rules/styles.md +1 -1
  47. package/dist/host/claude/rules/tests.md +34 -8
  48. package/dist/host/claude/rules/typescript.md +29 -0
  49. package/dist/host/claude/rules/workspace.md +32 -22
  50. package/dist/host/claude/rules/writing.md +34 -26
  51. package/dist/host/claude/settings.json +2 -0
  52. package/dist/host/claude/skills/orkestrel-debrief/SKILL.md +1 -1
  53. package/dist/host/claude/skills/orkestrel-falsify/SKILL.md +1 -1
  54. package/dist/host/codex/agents/analyst.toml +3 -3
  55. package/dist/host/codex/agents/reviewer.toml +4 -4
  56. package/dist/host/configs/policy.ts +13 -2
  57. package/dist/host/dotfiles/editorconfig +0 -1
  58. package/dist/host/dotfiles/gitattributes +0 -1
  59. package/dist/host/dotfiles/gitignore +0 -1
  60. package/dist/host/dotfiles/oxlintrc.json +24 -0
  61. package/dist/host/guides/agent.md +1638 -598
  62. package/dist/host/guides/budget.md +89 -29
  63. package/dist/host/guides/form.md +477 -91
  64. package/dist/host/guides/mcp.md +14 -2
  65. package/dist/host/guides/ollama.md +97 -73
  66. package/dist/host/guides/probe.md +1 -1
  67. package/dist/host/guides/scaffold.md +158 -101
  68. package/dist/host/guides/study.md +2635 -0
  69. package/dist/host/guides/table.md +225 -15
  70. package/dist/host/guides/terminal.md +22 -20
  71. package/dist/host/guides/test.md +689 -255
  72. package/dist/host/manifest.json +87 -63
  73. package/dist/host/tests/config.test.ts +136 -82
  74. package/dist/host/tests/policy.test.ts +39 -1
  75. package/dist/host/tests/setupPolicy.ts +86 -28
  76. package/dist/src/core/index.cjs +1387 -611
  77. package/dist/src/core/index.cjs.map +1 -1
  78. package/dist/src/core/index.d.cts +35 -20
  79. package/dist/src/core/index.d.ts +35 -20
  80. package/dist/src/core/index.js +1385 -612
  81. package/dist/src/core/index.js.map +1 -1
  82. package/dist/src/server/index.cjs +12 -12
  83. package/dist/src/server/index.cjs.map +1 -1
  84. package/dist/src/server/index.d.cts +12 -12
  85. package/dist/src/server/index.d.ts +12 -12
  86. package/dist/src/server/index.js +12 -12
  87. package/dist/src/server/index.js.map +1 -1
  88. package/package.json +4 -4
@@ -323,16 +323,27 @@ describe('selected faces', () => {
323
323
  }
324
324
  const script =
325
325
  'vite build --config configs/src/vite.styles.config.ts && vite build --config configs/src/vite.themes.config.ts'
326
- expect(inspectSheetConfiguration(project, script)).toEqual([])
327
- expect(
328
- inspectSheetConfiguration(
329
- { test: { ...project.test, setupFiles: ['./tests/setup.ts', './tests/setupBrowser.ts'] } },
330
- script,
331
- ),
332
- ).toContain('./tests/setupStyles.ts')
333
- expect(
334
- inspectSheetConfiguration(project, script.split(' && ').reverse().join(' && ')),
335
- ).toContain('themes order')
326
+ const scratch = createPolicyScratch({ prefix: 'config-sheet-setup-' })
327
+ const withoutStyles = {
328
+ test: { ...project.test, setupFiles: ['./tests/setup.ts', './tests/setupBrowser.ts'] },
329
+ }
330
+ try {
331
+ expect(inspectSheetConfiguration(withoutStyles, script, scratch.path)).toEqual([])
332
+ scratch.write('tests/setupStyles.ts', 'export {}\n')
333
+ expect(inspectSheetConfiguration(project, script, scratch.path)).toEqual([])
334
+ expect(inspectSheetConfiguration(withoutStyles, script, scratch.path)).toContain(
335
+ './tests/setupStyles.ts',
336
+ )
337
+ expect(
338
+ inspectSheetConfiguration(
339
+ project,
340
+ script.split(' && ').reverse().join(' && '),
341
+ scratch.path,
342
+ ),
343
+ ).toContain('themes order')
344
+ } finally {
345
+ scratch.destroy()
346
+ }
336
347
  })
337
348
 
338
349
  it('resolves showcase and journey modes for every occupied application', async () => {
@@ -707,19 +718,24 @@ describe('root configuration', () => {
707
718
  )
708
719
  .filter(([, path]) => existsSync(resolve(POLICY_ROOT, path)))
709
720
  .map(([label, , include, setup]): [string, PolicyProject] => [label, { include, setup }]),
710
- ...['policy', 'config', 'guides', 'conformance', 'distribution', 'integration']
711
- .filter((label) => existsSync(resolve(POLICY_ROOT, `tests/${label}.test.ts`)))
721
+ ...['policy', 'config', 'guides', 'conformance', 'tree', 'distribution', 'integration']
722
+ .filter((label) => readdirSync(resolve(POLICY_ROOT, 'tests')).includes(`${label}.test.ts`))
712
723
  .map((label): [string, PolicyProject] => [
713
724
  label,
714
725
  {
715
726
  include: `tests/${label}.test.ts`,
716
727
  setup: [
717
728
  './tests/setup.ts',
718
- ...(label === 'conformance' ? ['./tests/setupServer.ts'] : []),
729
+ ...(label === 'conformance' || label === 'tree' ? ['./tests/setupServer.ts'] : []),
719
730
  ...(label === 'integration' &&
720
731
  (collectSheets(POLICY_ROOT).length > 0 ||
721
732
  existsSync(resolve(POLICY_ROOT, 'src/styles/themes/index.scss')))
722
- ? ['./tests/setupBrowser.ts', './tests/setupStyles.ts']
733
+ ? [
734
+ './tests/setupBrowser.ts',
735
+ ...(existsSync(resolve(POLICY_ROOT, 'tests/setupStyles.ts'))
736
+ ? ['./tests/setupStyles.ts']
737
+ : []),
738
+ ]
723
739
  : []),
724
740
  ],
725
741
  },
@@ -910,8 +926,8 @@ describe('root configuration', () => {
910
926
  // before its result is read. Extra factories are ignored without validating their result shape.
911
927
  expect(extraLoads).toStrictEqual([])
912
928
  for (const [label, project] of expected) expect(configured.get(label)).toStrictEqual(project)
913
- for (const label of ['setup', 'setup:browser']) {
914
- const name = label === 'setup' ? 'setup' : 'setupBrowser'
929
+ for (const label of ['setup', 'setup:browser', 'tree']) {
930
+ const name = label === 'setup:browser' ? 'setupBrowser' : label
915
931
  expect(projects.some((entry) => typeof entry === 'function' && entry.name === name)).toBe(
916
932
  expected.has(label),
917
933
  )
@@ -1281,6 +1297,7 @@ describe('root configuration', () => {
1281
1297
  const distribution = Object.getOwnPropertyDescriptor(scripts, 'test:distribution')?.value
1282
1298
  const integration = Object.getOwnPropertyDescriptor(scripts, 'test:integration')?.value
1283
1299
  const conformance = Object.getOwnPropertyDescriptor(scripts, 'test:conformance')?.value
1300
+ const tree = Object.getOwnPropertyDescriptor(scripts, 'test:tree')?.value
1284
1301
  const service = Object.getOwnPropertyDescriptor(scripts, 'test:service')?.value
1285
1302
  const studies = Object.getOwnPropertyDescriptor(scripts, 'test:studies')?.value
1286
1303
  const publish = Object.getOwnPropertyDescriptor(scripts, 'prepublishOnly')?.value
@@ -1307,6 +1324,7 @@ describe('root configuration', () => {
1307
1324
  expect(registered.has('config')).toBe(true)
1308
1325
  expect(registered.has('control')).toBe(false)
1309
1326
  const hasConformance = registered.has('conformance')
1327
+ const hasTree = registered.has('tree')
1310
1328
  const hasDistribution = registered.has('distribution')
1311
1329
  const hasService = registered.has('service')
1312
1330
  const hasStudies = registered.has('studies')
@@ -1349,6 +1367,13 @@ describe('root configuration', () => {
1349
1367
  expect(typeof test === 'string' && test.includes('npm run test:conformance')).toBe(
1350
1368
  hasConformance,
1351
1369
  )
1370
+ expect(tree).toBe(
1371
+ hasTree
1372
+ ? 'vitest run --config vite.config.ts --no-cache --reporter=dot --project tree'
1373
+ : undefined,
1374
+ )
1375
+ expect(typeof test === 'string' && test.includes('npm run test:tree')).toBe(hasTree)
1376
+ expect(typeof publish === 'string' && publish.includes('npm run test:tree')).toBe(false)
1352
1377
  expect(service).toBe(
1353
1378
  hasService
1354
1379
  ? 'vitest run --config vite.config.ts --no-cache --reporter=dot --project service'
@@ -2916,72 +2941,89 @@ createSomething({
2916
2941
  const pattern = readConfigRecord(patternValue)
2917
2942
  const message = pattern.message
2918
2943
  if (typeof message !== 'string') throw new Error('Missing pattern message')
2919
- const listed: ReadonlyArray<readonly [string, boolean]> = message.includes('URL schemes')
2920
- ? [
2921
- ['https://host/x', true],
2922
- ['data:text/plain,x', true],
2923
- ['node:fs', false],
2924
- ['n:entry', true],
2925
- ['no:entry', true],
2926
- ['nod:entry', true],
2927
- ['nodea:entry', true],
2928
- ['1bad:entry', false],
2929
- ['./data:entry', false],
2930
- ]
2931
- : message.includes('noncanonical dot')
2944
+ const alias = /@src\/(core|browser|server) alias/u.exec(message)?.[1]
2945
+ const listed: ReadonlyArray<readonly [string, boolean]> =
2946
+ alias !== undefined
2932
2947
  ? [
2933
- ['../core/../server/index.js', true],
2934
- ['../core/./index.js', true],
2935
- ['../core/index.js', false],
2936
- ['./index.js', false],
2937
- ['../../core/index.js', false],
2938
- ['.../../index.js', true],
2939
- ['.name/../index.js', true],
2948
+ [`../${alias}/index.js`, true],
2949
+ [`../../${alias}/types.js`, true],
2950
+ [`../../src/${alias}/index.js`, true],
2951
+ [`../${alias}`, true],
2952
+ [`../${alias}/`, true],
2953
+ [`../${alias}?raw`, true],
2954
+ [`../${alias}#fragment`, true],
2955
+ [`@src/${alias}`, false],
2956
+ [`./${alias}/index.js`, false],
2957
+ [`../${alias}x/index.js`, false],
2958
+ [`../${alias}.ext/index.js`, false],
2940
2959
  ]
2941
- : message.includes('sibling sheet')
2960
+ : message.includes('URL schemes')
2942
2961
  ? [
2943
- ['@src/print', true],
2944
- ['../print/index.js', true],
2945
- ['@src/core', false],
2946
- ['@src/browser', false],
2947
- ['@src/vue', false],
2948
- ['@app/core', false],
2949
- ['@app/browser', false],
2950
- ['@src/corex', true],
2951
- ['@src/', true],
2952
- ['../src/print/index.js', true],
2953
- ['../browser?x', true],
2954
- ['../print.ext/index.js', false],
2955
- ...(Array.isArray(block.files) &&
2956
- block.files.some(
2957
- (file: unknown) => typeof file === 'string' && file.startsWith('app/vue/'),
2958
- )
2959
- ? [['@app/print', true] as const]
2960
- : []),
2961
- ]
2962
- : [
2963
- [
2964
- message.includes('absolute paths')
2965
- ? '/machine/module.js'
2966
- : message.includes('normalized traversal')
2967
- ? './../core/index.js'
2968
- : message.includes('forward slashes')
2969
- ? '..\\core\\index.js'
2970
- : message.includes('compiler API')
2971
- ? 'typescript'
2972
- : message.includes('private app')
2973
- ? '@app/core'
2974
- : message.includes('stylesheets')
2975
- ? './index.css'
2976
- : message.includes('host-independent') ||
2977
- message.includes('Node or server')
2978
- ? 'node:fs'
2979
- : message.includes('Vue extension')
2980
- ? '@src/vue'
2981
- : '@src/browser',
2982
- true,
2983
- ],
2962
+ ['https://host/x', true],
2963
+ ['data:text/plain,x', true],
2964
+ ['node:fs', false],
2965
+ ['n:entry', true],
2966
+ ['no:entry', true],
2967
+ ['nod:entry', true],
2968
+ ['nodea:entry', true],
2969
+ ['1bad:entry', false],
2970
+ ['./data:entry', false],
2984
2971
  ]
2972
+ : message.includes('noncanonical dot')
2973
+ ? [
2974
+ ['../core/../server/index.js', true],
2975
+ ['../core/./index.js', true],
2976
+ ['../core/index.js', false],
2977
+ ['./index.js', false],
2978
+ ['../../core/index.js', false],
2979
+ ['.../../index.js', true],
2980
+ ['.name/../index.js', true],
2981
+ ]
2982
+ : message.includes('sibling sheet')
2983
+ ? [
2984
+ ['@src/print', true],
2985
+ ['../print/index.js', true],
2986
+ ['@src/core', false],
2987
+ ['@src/browser', false],
2988
+ ['@src/vue', false],
2989
+ ['@app/core', false],
2990
+ ['@app/browser', false],
2991
+ ['@src/corex', true],
2992
+ ['@src/', true],
2993
+ ['../src/print/index.js', true],
2994
+ ['../browser?x', true],
2995
+ ['../print.ext/index.js', false],
2996
+ ...(Array.isArray(block.files) &&
2997
+ block.files.some(
2998
+ (file: unknown) =>
2999
+ typeof file === 'string' && file.startsWith('app/vue/'),
3000
+ )
3001
+ ? [['@app/print', true] as const]
3002
+ : []),
3003
+ ]
3004
+ : [
3005
+ [
3006
+ message.includes('absolute paths')
3007
+ ? '/machine/module.js'
3008
+ : message.includes('normalized traversal')
3009
+ ? './../core/index.js'
3010
+ : message.includes('forward slashes')
3011
+ ? '..\\core\\index.js'
3012
+ : message.includes('compiler API')
3013
+ ? 'typescript'
3014
+ : message.includes('private app')
3015
+ ? '@app/core'
3016
+ : message.includes('stylesheets')
3017
+ ? './index.css'
3018
+ : message.includes('host-independent') ||
3019
+ message.includes('Node or server')
3020
+ ? 'node:fs'
3021
+ : message.includes('Vue extension')
3022
+ ? '@src/vue'
3023
+ : '@src/browser',
3024
+ true,
3025
+ ],
3026
+ ]
2985
3027
  const face =
2986
3028
  message.includes('another styles face') && typeof pattern.regex === 'string'
2987
3029
  ? /@src\/(bootstrap|tailwindcss|styles)\(/u.exec(pattern.regex)?.[1]
@@ -3135,21 +3177,34 @@ createSomething({
3135
3177
  ]),
3136
3178
  ]
3137
3179
  : []),
3180
+ ...(['src/browser', 'src/server', 'src/vue', 'src/bin'].includes(owner)
3181
+ ? ['../core/index.js', '../../src/core/index.js']
3182
+ : []),
3183
+ ...(owner === 'src/vue' ? ['../browser/index.js', '../../src/browser/index.js'] : []),
3184
+ ...(owner === 'src/bin' ? ['../server/index.js', '../../src/server/index.js'] : []),
3138
3185
  ]
3139
3186
  const admitted = [
3140
3187
  ...(owner.endsWith('/vue')
3141
3188
  ? [
3142
3189
  '@src/core',
3143
3190
  '@src/browser',
3144
- '../core/index.js',
3145
- '../browser/index.js',
3146
3191
  'vue',
3147
3192
  '@vue/runtime-core',
3148
3193
  ...(owner.startsWith('app/')
3149
- ? ['@app/core', '@app/browser', '@src/vue', '../../src/vue/index.js']
3194
+ ? [
3195
+ '@app/core',
3196
+ '@app/browser',
3197
+ '@src/vue',
3198
+ '../../src/vue/index.js',
3199
+ '../browser/index.js',
3200
+ ]
3150
3201
  : []),
3151
3202
  ]
3152
3203
  : ['@src/core']),
3204
+ ...(['src/browser', 'src/server', 'src/vue', 'src/bin'].includes(owner)
3205
+ ? []
3206
+ : ['../core/index.js']),
3207
+ ...(owner === 'src/bin' ? ['@src/server'] : []),
3153
3208
  ...(sheet
3154
3209
  ? [
3155
3210
  ...faces
@@ -3177,7 +3232,6 @@ createSomething({
3177
3232
  [
3178
3233
  ...admitted,
3179
3234
  './index.js',
3180
- '../core/index.js',
3181
3235
  ...(owner.endsWith('/server') || owner === 'src/bin' ? ['node:fs'] : []),
3182
3236
  ],
3183
3237
  false,
@@ -88,10 +88,23 @@ describe('surface policy controls', () => {
88
88
  }
89
89
  })
90
90
 
91
- it('refuses a target catalog row absent from the installed guides', () => {
91
+ it('ignores a target catalog row the installed scaffold does not stage', () => {
92
92
  const scratch = createPolicySurfaceFixture()
93
93
  try {
94
94
  scratch.write(POLICY_CATALOG_FILE, createPolicyCatalog(['other', 'sample', 'uncovered']))
95
+ expect(inspectPolicyWorkspace(scratch.path)).toEqual([])
96
+ } finally {
97
+ scratch.destroy()
98
+ }
99
+ })
100
+
101
+ it('refuses a staged catalog row absent from the installed guides', () => {
102
+ const scratch = createPolicySurfaceFixture()
103
+ try {
104
+ scratch.write(
105
+ `${POLICY_SURFACE_HOST}/${POLICY_SURFACE_CATALOG}`,
106
+ createPolicyCatalog(['other', 'sample', 'uncovered']),
107
+ )
95
108
  expect(inspectPolicyWorkspace(scratch.path)).toEqual([
96
109
  {
97
110
  rule: 'surface',
@@ -212,6 +225,31 @@ describe('surface policy controls', () => {
212
225
  }
213
226
  })
214
227
 
228
+ it('excludes the distribution harness in an adopter while reporting target-owned collisions', () => {
229
+ const scratch = createPolicySurfaceFixture()
230
+ try {
231
+ scratch.write('tests/setupDistribution.ts', 'export function readShared() {}\n')
232
+ expect(inspectPolicyWorkspace(scratch.path)).toEqual([])
233
+ scratch.write('tests/setupExtra.ts', 'export function readShared() {}\n')
234
+ expect(inspectPolicyWorkspace(scratch.path)).toEqual([
235
+ {
236
+ rule: 'mirror',
237
+ path: 'tests/setupExtra.ts',
238
+ message:
239
+ 'exporting setup module requires its sibling proof or an import from tests/setup.test.ts',
240
+ },
241
+ {
242
+ rule: 'surface',
243
+ path: 'tests/setupExtra.ts',
244
+ line: 1,
245
+ message: 'surface name belongs to one package: readShared (other)',
246
+ },
247
+ ])
248
+ } finally {
249
+ scratch.destroy()
250
+ }
251
+ })
252
+
215
253
  it('refuses a barrel statement outside the relative star-export population', () => {
216
254
  const scratch = createPolicySurfaceFixture()
217
255
  try {
@@ -5,7 +5,7 @@ import {
5
5
  hasCanonicalSegments,
6
6
  resolveLink,
7
7
  } from '@orkestrel/guide'
8
- import { BASE_DEV_DEPENDENCIES, HOST_PATHS } from '@orkestrel/scaffold'
8
+ import { BASE_DEV_DEPENDENCIES, DISTRIBUTION_SETUP_PATH, HOST_PATHS } from '@orkestrel/scaffold'
9
9
  import { spawnSync } from 'node:child_process'
10
10
  import { createRequire } from 'node:module'
11
11
  import {
@@ -172,7 +172,7 @@ export interface PolicyProject {
172
172
  readonly setup: readonly string[]
173
173
  }
174
174
 
175
- /** Describes one physical negative control, including the population boundary it attacks. */
175
+ /** Describes one physical negative control, including the population boundary it tests. */
176
176
  export interface PolicyControl {
177
177
  readonly label: string
178
178
  readonly membership: string
@@ -1104,9 +1104,28 @@ export function inspectPolicyMirrors(root: string): readonly PolicyViolation[] {
1104
1104
  return [...inspectPolicyMirrorPaths(tests, modules), ...inspectPolicySetup(root)]
1105
1105
  }
1106
1106
 
1107
+ /**
1108
+ * Checks whether a normalized path belongs to a scaffold-owned setup module.
1109
+ * @param path - The workspace-relative module path.
1110
+ * @param root - The workspace root whose manifest determines harness ownership.
1111
+ * @returns True if scaffold owns the module; false otherwise.
1112
+ */
1113
+ export function matchesScaffoldSetup(path: string, root: string): boolean {
1114
+ const normalized = normalizePolicyPath(path)
1115
+ const manifest = readPolicyJSON(join(root, 'package.json'))
1116
+ return (
1117
+ (normalized === DISTRIBUTION_SETUP_PATH &&
1118
+ (!isPolicyRecord(manifest) || manifest.private !== true)) ||
1119
+ HOST_PATHS.some(
1120
+ (vendored) =>
1121
+ normalized === normalizePolicyPath(vendored) ||
1122
+ normalized.startsWith(normalizePolicyPath(vendored) + '/'),
1123
+ )
1124
+ )
1125
+ }
1126
+
1107
1127
  /**
1108
1128
  * Inspects root setup modules and their proofs in both directions.
1109
- *
1110
1129
  * @param root - The workspace root to inspect.
1111
1130
  * @returns Missing modules and uncovered exporting setup modules.
1112
1131
  */
@@ -1125,14 +1144,7 @@ export function inspectPolicySetup(root: string): readonly PolicyViolation[] {
1125
1144
  )
1126
1145
  return [...paths].sort().flatMap((path) => {
1127
1146
  const module = path.endsWith('.test.ts') ? path.replace(/\.test\.ts$/u, '.ts') : path
1128
- if (
1129
- HOST_PATHS.some(
1130
- (vendored) =>
1131
- module === normalizePolicyPath(vendored) ||
1132
- module.startsWith(normalizePolicyPath(vendored) + '/'),
1133
- )
1134
- )
1135
- return []
1147
+ if (matchesScaffoldSetup(module, root)) return []
1136
1148
  if (path.endsWith('.test.ts')) {
1137
1149
  return paths.has(module)
1138
1150
  ? []
@@ -3075,7 +3087,9 @@ export function readPolicySurface(root: string): PolicySurfacePopulation {
3075
3087
  *
3076
3088
  * @remarks
3077
3089
  * Source names claimed by the target's own hosted guide are grandfathered. Setup names have no
3078
- * grandfather. An absent installed host falls back to checkout guides only with catalog coverage.
3090
+ * grandfather. Catalog rows come from beside the guides: when reading installed guides, from the
3091
+ * staged catalog and never from the target's own table, which can list a package without a guide in
3092
+ * that host. An absent installed host falls back to checkout guides only with catalog coverage.
3079
3093
  * Missing comparison evidence and unread barrel statements produce surface violations.
3080
3094
  *
3081
3095
  * @param root - The workspace root to inspect.
@@ -3088,7 +3102,7 @@ export function inspectPolicySurface(root: string): readonly PolicyViolation[] {
3088
3102
  const host = hosted ? installed : root
3089
3103
  const catalog = hosted ? POLICY_SURFACE_CATALOG : POLICY_CATALOG_FILE
3090
3104
  const directory = hosted ? `${POLICY_SURFACE_HOST}/guides` : 'guides'
3091
- const names = new Set([...readPolicyCatalog(host, catalog), ...readPolicyCatalog(root)])
3105
+ const names = new Set(readPolicyCatalog(host, catalog))
3092
3106
  if (!existsSync(join(host, 'guides')) || names.size === 0) {
3093
3107
  return [
3094
3108
  createPolicyViolation(
@@ -3153,12 +3167,7 @@ export function inspectPolicySurface(root: string): readonly PolicyViolation[] {
3153
3167
  .map(normalizePolicyPath)
3154
3168
  .sort()
3155
3169
  .flatMap((path) =>
3156
- path.endsWith('.test.ts') ||
3157
- HOST_PATHS.some(
3158
- (vendored) =>
3159
- path === normalizePolicyPath(vendored) ||
3160
- path.startsWith(`${normalizePolicyPath(vendored)}/`),
3161
- )
3170
+ path.endsWith('.test.ts') || matchesScaffoldSetup(path, root)
3162
3171
  ? []
3163
3172
  : [collectPolicyDeclarations(root, path, readFileSync(join(root, path), 'utf8'))],
3164
3173
  )
@@ -3392,6 +3401,13 @@ export const SETUP_POLICY_CONTROLS: readonly PolicyControl[] = Object.freeze([
3392
3401
  },
3393
3402
  ],
3394
3403
  },
3404
+ {
3405
+ label: 'admits the distribution harness without a sibling proof',
3406
+ membership: 'content-owned distribution setup module outside the mirror population',
3407
+ rule: 'mirror',
3408
+ files: [{ path: 'tests/setupDistribution.ts', content: 'export const DISTRIBUTION = 1\n' }],
3409
+ violations: [],
3410
+ },
3395
3411
  {
3396
3412
  label: 'admits an inventory-vendored setup module without proof',
3397
3413
  membership: 'host-inventory setup modules outside the mirror population',
@@ -3399,12 +3415,30 @@ export const SETUP_POLICY_CONTROLS: readonly PolicyControl[] = Object.freeze([
3399
3415
  files: [{ path: 'tests/setupPolicy.ts', content: 'export const POLICY = 1\n' }],
3400
3416
  violations: [],
3401
3417
  },
3418
+ {
3419
+ label: 'requires a proof for a private workspace distribution module',
3420
+ membership: 'target-owned distribution setup module in a private workspace',
3421
+ rule: 'mirror',
3422
+ files: [
3423
+ { path: 'package.json', content: '{"private":true}\n' },
3424
+ { path: 'tests/setupDistribution.ts', content: 'export const DISTRIBUTION = 1\n' },
3425
+ ],
3426
+ violations: [
3427
+ {
3428
+ rule: 'mirror',
3429
+ path: 'tests/setupDistribution.ts',
3430
+ message:
3431
+ 'exporting setup module requires its sibling proof or an import from tests/setup.test.ts',
3432
+ },
3433
+ ],
3434
+ },
3402
3435
  ])
3403
3436
 
3404
3437
  /** Lists standalone sheet selections and their core-bearing control for the vendored proof. */
3405
3438
  export const SHEET_POLICY_SELECTIONS: ReadonlyArray<
3406
3439
  Pick<Blueprint, 'src' | 'styles' | 'themes'> & {
3407
3440
  readonly conformance?: boolean
3441
+ readonly tree?: boolean
3408
3442
  readonly integration?: boolean
3409
3443
  readonly files?: readonly PolicySource[]
3410
3444
  readonly control?: {
@@ -3416,8 +3450,25 @@ export const SHEET_POLICY_SELECTIONS: ReadonlyArray<
3416
3450
  > = Object.freeze([
3417
3451
  { src: [], styles: true, themes: true },
3418
3452
  { src: [], styles: false, themes: true },
3419
- { src: [], styles: false, themes: true, integration: true },
3453
+ {
3454
+ src: [],
3455
+ styles: false,
3456
+ themes: true,
3457
+ integration: true,
3458
+ control: {
3459
+ before: "existsSync(resolveWorkspacePath('tests/setupStyles.ts'))",
3460
+ after: 'true',
3461
+ failure: './tests/setupStyles.ts',
3462
+ },
3463
+ },
3420
3464
  { src: ['core'], styles: true, themes: true },
3465
+ {
3466
+ src: ['core'],
3467
+ styles: false,
3468
+ themes: false,
3469
+ tree: true,
3470
+ files: [{ path: 'tests/tree.test.ts', content: 'export {}\n' }],
3471
+ },
3421
3472
  {
3422
3473
  src: ['core', 'browser'],
3423
3474
  styles: true,
@@ -3457,10 +3508,10 @@ export const SHEET_POLICY_SELECTIONS: ReadonlyArray<
3457
3508
  styles: true,
3458
3509
  themes: false,
3459
3510
  integration: true,
3511
+ files: [{ path: 'tests/setupStyles.ts', content: 'export {}\n' }],
3460
3512
  control: {
3461
- before:
3462
- "setupFiles: ['./tests/setup.ts', './tests/setupBrowser.ts', './tests/setupStyles.ts']",
3463
- after: "setupFiles: ['./tests/setup.ts', './tests/setupBrowser.ts']",
3513
+ before: "? ['./tests/setupStyles.ts']",
3514
+ after: '? []',
3464
3515
  failure: './tests/setupStyles.ts',
3465
3516
  },
3466
3517
  },
@@ -4283,7 +4334,7 @@ export const PROSE_POLICY_MANIFEST = '{\n\t"name": "@orkestrel/sample"\n}\n'
4283
4334
  * Lists the physical controls for every prose-population boundary the workspace route reaches.
4284
4335
  *
4285
4336
  * @remarks
4286
- * Each control that attacks an exclusion also writes the arrival file, whose different term proves
4337
+ * Each control that tests an exclusion also writes the arrival file, whose different term proves
4287
4338
  * the sweep ran over the control workspace rather than reporting nothing because it found nothing.
4288
4339
  */
4289
4340
  export const PROSE_POLICY_CONTROLS: readonly PolicyControl[] = Object.freeze([
@@ -4559,15 +4610,22 @@ export function collectFaceWrappers(directory: string): readonly string[] {
4559
4610
  * Inspects sheet setup membership and themes ordering from configuration data.
4560
4611
  * @param project - The resolved sheet project.
4561
4612
  * @param script - The optional chained styles and themes build command.
4613
+ * @param directory - The target workspace root; defaults to the policy root.
4562
4614
  * @returns Missing setup paths and invalid isolation or ordering fields.
4563
4615
  */
4564
- export function inspectSheetConfiguration(project: unknown, script?: string): readonly string[] {
4616
+ export function inspectSheetConfiguration(
4617
+ project: unknown,
4618
+ script?: string,
4619
+ directory: string = POLICY_ROOT,
4620
+ ): readonly string[] {
4565
4621
  const test = readConfigRecord(readConfigRecord(project).test)
4566
4622
  const setup = test.setupFiles
4567
4623
  return [
4568
- ...['./tests/setup.ts', './tests/setupBrowser.ts', './tests/setupStyles.ts'].filter(
4569
- (path) => !Array.isArray(setup) || !setup.includes(path),
4570
- ),
4624
+ ...[
4625
+ './tests/setup.ts',
4626
+ './tests/setupBrowser.ts',
4627
+ ...(existsSync(resolve(directory, 'tests/setupStyles.ts')) ? ['./tests/setupStyles.ts'] : []),
4628
+ ].filter((path) => !Array.isArray(setup) || !setup.includes(path)),
4571
4629
  ...(test.isolate !== false ? ['isolate'] : []),
4572
4630
  ...(script !== undefined &&
4573
4631
  (!script.includes('vite.styles.config.ts') ||