@orkestrel/scaffold 0.0.106 → 0.0.108

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 (46) hide show
  1. package/dist/agents/skills/orkestrel-dispatch/scripts/sweep.js +124 -4
  2. package/dist/agents/templates/brief.md +4 -2
  3. package/dist/bin/main.js +1 -1
  4. package/dist/bin/main.js.map +1 -1
  5. package/dist/host/AGENTS.md +5 -1
  6. package/dist/host/agents/orchestration.md +81 -51
  7. package/dist/host/agents/platforms/posix.md +15 -0
  8. package/dist/host/agents/platforms/windows.md +37 -0
  9. package/dist/host/agents/skills/orkestrel-debrief/references/retention.md +10 -10
  10. package/dist/host/agents/skills/orkestrel-dispatch/SKILL.md +8 -6
  11. package/dist/host/agents/skills/orkestrel-dispatch/references/launch.md +12 -8
  12. package/dist/host/agents/skills/orkestrel-dispatch/scripts/sweep.ts +144 -4
  13. package/dist/host/agents/skills/orkestrel-parallel/SKILL.md +118 -51
  14. package/dist/host/agents/skills/orkestrel-parallel/agents/openai.yaml +2 -2
  15. package/dist/host/agents/skills/orkestrel-parallel/references/claude.md +32 -0
  16. package/dist/host/agents/skills/orkestrel-parallel/references/codex.md +28 -0
  17. package/dist/host/agents/skills/orkestrel-parallel/references/cursor.md +29 -0
  18. package/dist/host/agents/skills/orkestrel-parallel/references/t3.md +31 -0
  19. package/dist/host/agents/skills/orkestrel-publish/references/wave.md +2 -1
  20. package/dist/host/agents/templates/brief.md +4 -2
  21. package/dist/host/agents/transports/codex.md +1 -1
  22. package/dist/host/claude/AGENTS.md +9 -11
  23. package/dist/host/claude/agents/verifier.md +5 -5
  24. package/dist/host/claude/rules/campaign.md +55 -0
  25. package/dist/host/claude/rules/documentation.md +2 -0
  26. package/dist/host/claude/rules/portability.md +1 -2
  27. package/dist/host/claude/rules/typescript.md +1 -1
  28. package/dist/host/claude/rules/writing.md +5 -2
  29. package/dist/host/claude/skills/orkestrel-parallel/SKILL.md +1 -1
  30. package/dist/host/codex/agents/verifier.toml +13 -9
  31. package/dist/host/codex/config.toml +4 -6
  32. package/dist/host/configs/policy.ts +106 -1
  33. package/dist/host/cursor/rules/orchestration.mdc +1 -0
  34. package/dist/host/dotfiles/oxlintrc.json +4 -2
  35. package/dist/host/guides/scaffold.md +56 -1
  36. package/dist/host/manifest.json +67 -30
  37. package/dist/host/tests/config.test.ts +560 -5
  38. package/dist/host/tests/setupPolicy.ts +496 -6
  39. package/dist/src/core/index.cjs +26 -2
  40. package/dist/src/core/index.cjs.map +1 -1
  41. package/dist/src/core/index.d.cts +1 -1
  42. package/dist/src/core/index.d.ts +1 -1
  43. package/dist/src/core/index.js +26 -2
  44. package/dist/src/core/index.js.map +1 -1
  45. package/package.json +1 -1
  46. package/dist/host/agents/skills/orkestrel-parallel/references/harnesses.md +0 -12
@@ -55,8 +55,10 @@ import policyPlugin, {
55
55
  TERM_RULE,
56
56
  TYPE_RULE,
57
57
  VOICE_RULE,
58
+ VUE_IMPORT_RULE,
58
59
  blankPolicyText,
59
60
  commentToPolicyParagraph,
61
+ computePolicyTarget,
60
62
  isPolicyVoiced,
61
63
  nodeToPolicyOuter,
62
64
  paragraphToPolicyOpener,
@@ -82,6 +84,7 @@ import {
82
84
  SHEET_POLICY_BARREL_PATTERN,
83
85
  SHEET_POLICY_ORDER_PATTERN,
84
86
  readImportDiagnostics,
87
+ readNativeDiagnostics,
85
88
  createPolicyLazyCases,
86
89
  createPolicyScratch,
87
90
  createPolicySparse,
@@ -92,6 +95,22 @@ import {
92
95
  POLICY_LAZY_SHAPES,
93
96
  POLICY_PUBLISHES,
94
97
  POLICY_ROOT,
98
+ POLICY_VUE_COMPOSABLE_SOURCE,
99
+ POLICY_VUE_RELATIVE_SOURCES,
100
+ POLICY_VUE_TARGET_CASES,
101
+ POLICY_VUE_FACES,
102
+ POLICY_VUE_UNCLASSIFIED_FILES,
103
+ POLICY_VUE_IMPORT_FORMS,
104
+ POLICY_VUE_POPULATIONS,
105
+ POLICY_VUE_IMPORT_CASES,
106
+ POLICY_VUE_BOUNDARY_CASES,
107
+ POLICY_VUE_COMPUTED_CASES,
108
+ POLICY_VUE_COMPUTED_FORMS,
109
+ POLICY_VUE_PARSER_ROWS,
110
+ POLICY_VUE_PARSER_CASES,
111
+ POLICY_VUE_PARSED_CASES,
112
+ POLICY_VUE_ORIGINAL_RELATIVES,
113
+ POLICY_VUE_DOMAIN_CASES,
95
114
  } from './setupPolicy.js'
96
115
  import { describe, expect, it } from 'vitest'
97
116
 
@@ -1282,6 +1301,85 @@ describe('root configuration', () => {
1282
1301
  ).toThrow(/expected/u)
1283
1302
  })
1284
1303
 
1304
+ // A target with a server application builds it to one `main.cjs`, so a
1305
+ // single-executable embed has one file to take. The real wrapper's configuration
1306
+ // builds a scratch entry that dynamically imports a scratch module, once as the
1307
+ // wrapper declares it and once with splitting forced on. The second build is the
1308
+ // control: it shows the single chunk comes from the wrapper's `codeSplitting` option
1309
+ // and not from the entry's shape. Evaluation order of the inlined module is proved
1310
+ // once, by executing the emitted file, in the template proof that stages a server
1311
+ // root. A workspace without an `app/server` has no wrapper to build, so this case
1312
+ // reports nothing about the server application of a target that has one: the
1313
+ // assertion below only classifies the absence, and each target with a server
1314
+ // application establishes this proof when its own configuration test runs.
1315
+ it('builds the application server wrapper to one entry and splits it under the control', async () => {
1316
+ const wrapper = resolve(POLICY_ROOT, 'configs/app/vite.server.config.ts')
1317
+ const present = existsSync(wrapper)
1318
+ expect(present || !existsSync(resolve(POLICY_ROOT, 'app/server'))).toBe(true)
1319
+ if (!present) return
1320
+ const scratch = createPolicyScratch({
1321
+ parent: resolve(POLICY_ROOT, 'app/server'),
1322
+ prefix: 'config-server-entry-',
1323
+ })
1324
+ try {
1325
+ scratch.write(
1326
+ 'index.ts',
1327
+ "process.stdout.write('entry\\n')\nvoid import('./lazy.js').then(({ lazy }) => process.stdout.write(lazy()))\nprocess.stdout.write('after\\n')\n",
1328
+ )
1329
+ scratch.write(
1330
+ 'lazy.ts',
1331
+ "process.stdout.write('lazy\\n')\nexport function lazy(): string {\n\treturn String(process.argv.length)\n}\n",
1332
+ )
1333
+ const rosters: Array<readonly string[]> = []
1334
+ for (const splitting of [false, true]) {
1335
+ const loaded = await loadConfigFromFile(
1336
+ { command: 'build', mode: 'production', isSsrBuild: false, isPreview: false },
1337
+ wrapper,
1338
+ POLICY_ROOT,
1339
+ 'silent',
1340
+ )
1341
+ if (loaded === null) throw new Error('The server wrapper did not load')
1342
+ const rolldown = loaded.config.build?.rolldownOptions
1343
+ const output: unknown = rolldown?.output
1344
+ if (typeof output !== 'object' || output === null || Array.isArray(output)) {
1345
+ throw new Error('The server wrapper declares no output options')
1346
+ }
1347
+ expect(Object.getOwnPropertyDescriptor(output, 'codeSplitting')?.value).toBe(false)
1348
+ const built = await build({
1349
+ ...loaded.config,
1350
+ root: POLICY_ROOT,
1351
+ configFile: false,
1352
+ logLevel: 'silent',
1353
+ build: {
1354
+ ...loaded.config.build,
1355
+ write: false,
1356
+ lib: {
1357
+ entry: resolve(scratch.path, 'index.ts'),
1358
+ formats: ['cjs'],
1359
+ fileName: () => 'main.cjs',
1360
+ },
1361
+ rolldownOptions: splitting
1362
+ ? { ...rolldown, output: { codeSplitting: true } }
1363
+ : { ...rolldown },
1364
+ },
1365
+ })
1366
+ // A library build returns one result per format, an array even for one format.
1367
+ const results = Array.isArray(built) ? built : [built]
1368
+ expect(results).toHaveLength(1)
1369
+ for (const result of results) {
1370
+ if (!('output' in result)) throw new Error('The server build returned no output')
1371
+ rosters.push(result.output.map(({ fileName }) => fileName).toSorted())
1372
+ }
1373
+ }
1374
+ const [single, split] = rosters
1375
+ expect(single).toStrictEqual(['main.cjs'])
1376
+ expect(split).toContain('main.cjs')
1377
+ expect(split?.length).toBeGreaterThan(1)
1378
+ } finally {
1379
+ scratch.destroy()
1380
+ }
1381
+ })
1382
+
1285
1383
  it('registers proof scripts in the correct gate', () => {
1286
1384
  const manifest: unknown = JSON.parse(readFileSync(resolve(POLICY_ROOT, 'package.json'), 'utf8'))
1287
1385
  if (typeof manifest !== 'object' || manifest === null) {
@@ -1562,6 +1660,35 @@ describe('policy plugin', () => {
1562
1660
  RuleTester.it = it
1563
1661
 
1564
1662
  const tester = new RuleTester({ languageOptions: { parserOptions: { lang: 'ts' } } })
1663
+ tester.run('no-cross-vue-face', VUE_IMPORT_RULE, {
1664
+ valid: [...POLICY_VUE_IMPORT_CASES, ...POLICY_VUE_BOUNDARY_CASES, ...POLICY_VUE_COMPUTED_CASES]
1665
+ .filter((source) => source.path.endsWith('.ts') && !source.boundary)
1666
+ .map((source) => ({
1667
+ name: `accepts the native relative boundary in ${source.path}`,
1668
+ filename: resolve(POLICY_ROOT, source.path),
1669
+ cwd: POLICY_ROOT,
1670
+ code: source.content,
1671
+ })),
1672
+ invalid: [
1673
+ ...POLICY_VUE_IMPORT_CASES,
1674
+ ...POLICY_VUE_BOUNDARY_CASES,
1675
+ ...POLICY_VUE_UNCLASSIFIED_FILES.map((path) => ({
1676
+ path,
1677
+ content: "import './entry.js'\ndebugger\n",
1678
+ boundary: true,
1679
+ restricted: false,
1680
+ })),
1681
+ ]
1682
+ .filter((source) => source.path.endsWith('.ts') && source.boundary)
1683
+ .map((source) => ({
1684
+ name: `rejects the native relative boundary in ${source.path} [membership: literal relative import or export outside permitted Vue destinations]`,
1685
+ filename: resolve(POLICY_ROOT, source.path),
1686
+ cwd: POLICY_ROOT,
1687
+ code: source.content,
1688
+ errors: [{ messageId: 'vue' }],
1689
+ })),
1690
+ })
1691
+
1565
1692
  tester.run('no-mocking', MOCKING_RULE, {
1566
1693
  valid: [
1567
1694
  { name: 'accepts recorders', code: 'createRecorder()' },
@@ -1931,6 +2058,12 @@ createSomething({
1931
2058
 
1932
2059
  tester.run('no-misplaced-function', FUNCTION_RULE, {
1933
2060
  valid: [
2061
+ ...POLICY_VUE_DOMAIN_CASES.filter((source) => !source.misplaced).map((source) => ({
2062
+ name: `accepts Vue function placement in ${source.path}`,
2063
+ filename: resolve(POLICY_ROOT, source.path),
2064
+ cwd: POLICY_ROOT,
2065
+ code: source.content,
2066
+ })),
1934
2067
  {
1935
2068
  name: 'accepts a function in a function-kind file',
1936
2069
  filename: 'src/worker/helpers.ts',
@@ -1979,6 +2112,13 @@ createSomething({
1979
2112
  code: 'export function runTask(): void {}',
1980
2113
  errors: [{ messageId: 'function' }],
1981
2114
  },
2115
+ ...POLICY_VUE_DOMAIN_CASES.filter((source) => source.misplaced).map((source) => ({
2116
+ name: `rejects Vue function placement in ${source.path} [membership: modules outside the exact direct function register]`,
2117
+ filename: resolve(POLICY_ROOT, source.path),
2118
+ cwd: POLICY_ROOT,
2119
+ code: source.content,
2120
+ errors: [{ messageId: 'function' }],
2121
+ })),
1982
2122
  {
1983
2123
  name: 'rejects a property-held arrow in a route table [membership: module function syntax whose file is absent from the function register]',
1984
2124
  filename: 'src/worker/routes.ts',
@@ -2281,6 +2421,12 @@ createSomething({
2281
2421
 
2282
2422
  tester.run('no-malformed-domain', DOMAIN_RULE, {
2283
2423
  valid: [
2424
+ ...POLICY_VUE_DOMAIN_CASES.filter((source) => !source.malformed).map((source) => ({
2425
+ name: `accepts Vue domain shape in ${source.path}`,
2426
+ filename: resolve(POLICY_ROOT, source.path),
2427
+ cwd: POLICY_ROOT,
2428
+ code: source.content,
2429
+ })),
2284
2430
  {
2285
2431
  name: 'accepts a registered function module carrying imports and one named export',
2286
2432
  filename: 'app/browser/composables/useTheme.ts',
@@ -2306,6 +2452,13 @@ createSomething({
2306
2452
  code: 'export function useMode(): void {}',
2307
2453
  errors: [{ messageId: 'module' }],
2308
2454
  },
2455
+ ...POLICY_VUE_DOMAIN_CASES.filter((source) => source.malformed).map((source) => ({
2456
+ name: `rejects Vue domain shape in ${source.path} [membership: direct camelCase modules in the Vue function domain]`,
2457
+ filename: resolve(POLICY_ROOT, source.path),
2458
+ cwd: POLICY_ROOT,
2459
+ code: source.content,
2460
+ errors: [{ messageId: 'module' }],
2461
+ })),
2309
2462
  {
2310
2463
  name: 'rejects a hidden domain function [membership: direct camelCase modules in a registered function-domain folder]',
2311
2464
  filename: 'app/browser/composables/useTheme.ts',
@@ -2923,6 +3076,400 @@ createSomething({
2923
3076
  }
2924
3077
  })
2925
3078
 
3079
+ it('computes Vue targets from lexical importer and workspace boundaries', () => {
3080
+ expect(POLICY_VUE_TARGET_CASES.length).toBeGreaterThan(30)
3081
+ for (const [filename, cwd, source, expected] of POLICY_VUE_TARGET_CASES) {
3082
+ expect({ filename, cwd, source, target: computePolicyTarget(filename, cwd, source) }).toEqual(
3083
+ {
3084
+ filename,
3085
+ cwd,
3086
+ source,
3087
+ target: expected,
3088
+ },
3089
+ )
3090
+ }
3091
+ })
3092
+
3093
+ it('partitions every Vue syntax input between parsed and native parser-refused coverage', () => {
3094
+ const originals = [...POLICY_VUE_IMPORT_CASES, ...POLICY_VUE_COMPUTED_CASES]
3095
+ const partition = [...POLICY_VUE_PARSED_CASES, ...POLICY_VUE_PARSER_CASES]
3096
+ expect(originals).toHaveLength(312)
3097
+ expect(new Set(originals.map(({ path }) => path)).size).toBe(originals.length)
3098
+ expect(POLICY_VUE_PARSED_CASES).toHaveLength(298)
3099
+ expect(POLICY_VUE_PARSER_CASES).toHaveLength(14)
3100
+ expect(new Set(partition.map(({ path }) => path)).size).toBe(partition.length)
3101
+ expect(partition.map(({ path }) => path).toSorted()).toEqual(
3102
+ originals.map(({ path }) => path).toSorted(),
3103
+ )
3104
+ for (const original of originals) {
3105
+ expect(partition.find((source) => source.path === original.path)).toMatchObject(original)
3106
+ }
3107
+ expect(POLICY_VUE_PARSER_CASES.map(({ path }) => path).toSorted()).toEqual(
3108
+ POLICY_VUE_FACES.flatMap((face) =>
3109
+ POLICY_VUE_PARSER_ROWS.map(([file]) => `${face}/${file}`),
3110
+ ).toSorted(),
3111
+ )
3112
+ expect(POLICY_VUE_PARSER_CASES.every(({ path }) => path.endsWith('.cjs'))).toBe(true)
3113
+ expect([...new Set(POLICY_VUE_POPULATIONS.map(({ extension }) => extension))]).toEqual([
3114
+ 'cjs',
3115
+ 'cts',
3116
+ 'js',
3117
+ 'jsx',
3118
+ 'mjs',
3119
+ 'mts',
3120
+ 'ts',
3121
+ 'tsx',
3122
+ 'vue',
3123
+ ])
3124
+ for (const face of POLICY_VUE_FACES) {
3125
+ const sources = originals.filter((source) => source.path.startsWith(face + '/'))
3126
+ expect(sources).toHaveLength(156)
3127
+ for (const population of POLICY_VUE_POPULATIONS) {
3128
+ for (const form of POLICY_VUE_IMPORT_FORMS.filter(
3129
+ (item) => !item.typed || population.typed,
3130
+ )) {
3131
+ expect(sources).toContainEqual(
3132
+ expect.objectContaining({
3133
+ path: `${face}/${population.name}${form.name}.${population.extension}`,
3134
+ boundary: true,
3135
+ }),
3136
+ )
3137
+ expect(sources).toContainEqual(
3138
+ expect.objectContaining({
3139
+ path: `${face}/features/editor/${population.name}${form.name}.${population.extension}`,
3140
+ boundary: false,
3141
+ }),
3142
+ )
3143
+ }
3144
+ for (const index of POLICY_VUE_COMPUTED_FORMS.keys()) {
3145
+ expect(sources).toContainEqual(
3146
+ expect.objectContaining({
3147
+ path: `${face}/computed${population.name}${index}.${population.extension}`,
3148
+ boundary: false,
3149
+ }),
3150
+ )
3151
+ }
3152
+ }
3153
+ }
3154
+ })
3155
+
3156
+ it('checks Vue import syntax and populations through the canonical policy', () => {
3157
+ const fixture = createPolicyScratch({ prefix: 'policy-vue-syntax-' })
3158
+ try {
3159
+ fixture.write('.oxlintrc.json', readFileSync(resolve(POLICY_ROOT, '.oxlintrc.json'), 'utf8'))
3160
+ fixture.write(
3161
+ 'configs/policy.ts',
3162
+ readFileSync(resolve(POLICY_ROOT, 'configs/policy.ts'), 'utf8'),
3163
+ )
3164
+ for (const face of POLICY_VUE_FACES) {
3165
+ const sources = POLICY_VUE_PARSED_CASES.filter((source) =>
3166
+ source.path.startsWith(face + '/'),
3167
+ )
3168
+ expect(sources).toHaveLength(149)
3169
+ expect(sources).toContainEqual(
3170
+ expect.objectContaining({ path: `${face}/cjsdynamic.cjs`, boundary: true }),
3171
+ )
3172
+ expect(sources).toContainEqual(
3173
+ expect.objectContaining({
3174
+ path: `${face}/features/editor/cjsdynamic.cjs`,
3175
+ boundary: false,
3176
+ }),
3177
+ )
3178
+ for (const source of sources) fixture.write(source.path, source.content)
3179
+ const codes = readImportDiagnostics(
3180
+ fixture.path,
3181
+ sources.map(({ path }) => path),
3182
+ )
3183
+ expect(
3184
+ sources.map(({ path }) => ({
3185
+ path,
3186
+ boundary: codes.has('policy(no-cross-vue-face) ' + path),
3187
+ restricted: codes.has('eslint(no-restricted-imports) ' + path),
3188
+ })),
3189
+ ).toEqual(sources.map(({ path, boundary, restricted }) => ({ path, boundary, restricted })))
3190
+ }
3191
+ } finally {
3192
+ fixture.destroy()
3193
+ }
3194
+ })
3195
+
3196
+ it('retains exact native parser refusals for CommonJS Vue inputs on both faces', () => {
3197
+ const fixture = createPolicyScratch({ prefix: 'policy-vue-parser-' })
3198
+ try {
3199
+ fixture.write('.oxlintrc.json', readFileSync(resolve(POLICY_ROOT, '.oxlintrc.json'), 'utf8'))
3200
+ fixture.write(
3201
+ 'configs/policy.ts',
3202
+ readFileSync(resolve(POLICY_ROOT, 'configs/policy.ts'), 'utf8'),
3203
+ )
3204
+ const sources = POLICY_VUE_PARSER_CASES
3205
+ const paths = sources.map(({ path }) => path)
3206
+ expect(paths).toHaveLength(14)
3207
+ for (const source of sources) fixture.write(source.path, source.content)
3208
+ const diagnostics = readNativeDiagnostics(fixture.path, paths)
3209
+ expect(diagnostics).toHaveLength(paths.length)
3210
+ const comparisonRoot = realpathSync.native(fixture.path)
3211
+ const observed = diagnostics.map((diagnostic) => {
3212
+ if (typeof diagnostic.filename !== 'string')
3213
+ throw new Error('Missing parser diagnostic filename: ' + JSON.stringify(diagnostic))
3214
+ const path = normalizePolicyFilename(comparisonRoot, diagnostic.filename)
3215
+ const source = sources.find((candidate) => candidate.path === path)
3216
+ if (source === undefined)
3217
+ throw new Error('Unexpected parser diagnostic: ' + JSON.stringify(diagnostic))
3218
+ expect(diagnostic).toEqual({
3219
+ message: source.message,
3220
+ severity: 'error',
3221
+ filename: diagnostic.filename,
3222
+ labels: [{ span: source.span }],
3223
+ })
3224
+ expect(
3225
+ source.content.slice(source.span.offset, source.span.offset + source.span.length),
3226
+ ).toBe(source.keyword)
3227
+ return path
3228
+ })
3229
+ expect(observed.toSorted()).toEqual(paths.toSorted())
3230
+ } finally {
3231
+ fixture.destroy()
3232
+ }
3233
+ })
3234
+
3235
+ it('retains Vue independent restrictions beside exact relative diagnostics', () => {
3236
+ const fixture = createPolicyScratch({ prefix: 'policy-vue-boundaries-' })
3237
+ try {
3238
+ fixture.write('.oxlintrc.json', readFileSync(resolve(POLICY_ROOT, '.oxlintrc.json'), 'utf8'))
3239
+ fixture.write(
3240
+ 'configs/policy.ts',
3241
+ readFileSync(resolve(POLICY_ROOT, 'configs/policy.ts'), 'utf8'),
3242
+ )
3243
+ const sources = POLICY_VUE_BOUNDARY_CASES
3244
+ expect(sources.length).toBeGreaterThan(100)
3245
+ for (const source of sources) fixture.write(source.path, source.content)
3246
+ const codes = readImportDiagnostics(
3247
+ fixture.path,
3248
+ sources.map(({ path }) => path),
3249
+ )
3250
+ expect(
3251
+ sources.map(({ path }) => ({
3252
+ path,
3253
+ boundary: codes.has('policy(no-cross-vue-face) ' + path),
3254
+ restricted: codes.has('eslint(no-restricted-imports) ' + path),
3255
+ })),
3256
+ ).toEqual(sources.map(({ path, boundary, restricted }) => ({ path, boundary, restricted })))
3257
+ } finally {
3258
+ fixture.destroy()
3259
+ }
3260
+ })
3261
+
3262
+ it('detects suppressed Vue reporting in a scratch policy without weakening independent bans', () => {
3263
+ const fixture = createPolicyScratch({ prefix: 'policy-vue-report-control-' })
3264
+ try {
3265
+ fixture.write('.oxlintrc.json', readFileSync(resolve(POLICY_ROOT, '.oxlintrc.json'), 'utf8'))
3266
+ const plugin = readFileSync(resolve(POLICY_ROOT, 'configs/policy.ts'), 'utf8')
3267
+ const marker = "context.report({ node, messageId: 'vue' })"
3268
+ expect(plugin.split(marker)).toHaveLength(2)
3269
+ fixture.write('configs/policy.ts', plugin)
3270
+ const sources = POLICY_VUE_BOUNDARY_CASES
3271
+ for (const source of sources) fixture.write(source.path, source.content)
3272
+ const paths = sources.map(({ path }) => path)
3273
+ const enabled = readImportDiagnostics(fixture.path, paths)
3274
+ const expected = sources.filter(({ boundary }) => boundary).map(({ path }) => path)
3275
+ expect(expected).toContain('src/vue/boundary0.ts')
3276
+ expect(expected).toContain('app/vue/boundary0.ts')
3277
+ expect(paths.filter((path) => enabled.has('policy(no-cross-vue-face) ' + path))).toEqual(
3278
+ expected,
3279
+ )
3280
+ fixture.write('configs/policy.ts', plugin.replace(marker, 'void node'))
3281
+ const suppressed = readImportDiagnostics(fixture.path, paths)
3282
+ const actual = paths.filter((path) => suppressed.has('policy(no-cross-vue-face) ' + path))
3283
+ expect(actual).toEqual([])
3284
+ expect(() => expect(actual).toEqual(expected)).toThrow('deeply equal')
3285
+ for (const { path, restricted } of sources) {
3286
+ expect({ path, enabled: enabled.has('eslint(no-restricted-imports) ' + path) }).toEqual({
3287
+ path,
3288
+ enabled: restricted,
3289
+ })
3290
+ expect({
3291
+ path,
3292
+ suppressed: suppressed.has('eslint(no-restricted-imports) ' + path),
3293
+ }).toEqual({ path, suppressed: restricted })
3294
+ }
3295
+ } finally {
3296
+ fixture.destroy()
3297
+ }
3298
+ })
3299
+
3300
+ it('detects restored Vue relative regexes against nested canonical imports', () => {
3301
+ const fixture = createPolicyScratch({ prefix: 'policy-vue-regex-control-' })
3302
+ try {
3303
+ const text = readFileSync(resolve(POLICY_ROOT, '.oxlintrc.json'), 'utf8')
3304
+ fixture.write('.oxlintrc.json', text)
3305
+ fixture.write(
3306
+ 'configs/policy.ts',
3307
+ readFileSync(resolve(POLICY_ROOT, 'configs/policy.ts'), 'utf8'),
3308
+ )
3309
+ for (const source of POLICY_VUE_RELATIVE_SOURCES) fixture.write(source.path, source.content)
3310
+ const paths = POLICY_VUE_RELATIVE_SOURCES.map(({ path }) => path)
3311
+ const enabled = readImportDiagnostics(fixture.path, paths)
3312
+ expect(paths.filter((path) => enabled.has('eslint(no-restricted-imports) ' + path))).toEqual(
3313
+ [],
3314
+ )
3315
+ expect(paths.filter((path) => enabled.has('policy(no-cross-vue-face) ' + path))).toEqual([])
3316
+ const restoredConfiguration = readConfigRecord(JSON.parse(text))
3317
+ if (!Array.isArray(restoredConfiguration.overrides)) throw new Error('Missing lint overrides')
3318
+ const restored = restoredConfiguration.overrides.map((value: unknown) => {
3319
+ const block = readConfigRecord(value)
3320
+ const files = block.files
3321
+ const owner = POLICY_VUE_FACES.find(
3322
+ (face) =>
3323
+ Array.isArray(files) &&
3324
+ files.some((file: unknown) => typeof file === 'string' && file.startsWith(face + '/')),
3325
+ )
3326
+ if (owner === undefined) return block
3327
+ const original = POLICY_VUE_ORIGINAL_RELATIVES[owner]
3328
+ if (original === undefined) throw new Error('Missing original Vue relative pattern')
3329
+ const rules = readConfigRecord(block.rules)
3330
+ const rule = rules['no-restricted-imports']
3331
+ if (!Array.isArray(rule)) throw new Error('Missing Vue import restrictions')
3332
+ const options = readConfigRecord(rule[1])
3333
+ if (!Array.isArray(options.patterns)) throw new Error('Missing Vue import patterns')
3334
+ const patterns = options.patterns.map((patternValue: unknown): Record<string, unknown> => {
3335
+ const pattern = readConfigRecord(patternValue)
3336
+ if (pattern.message !== 'Vue faces must not depend on sibling sheet faces') return pattern
3337
+ if (typeof pattern.regex !== 'string' || !pattern.regex.endsWith(')'))
3338
+ throw new Error('Missing Vue sibling alias pattern')
3339
+ return { ...pattern, regex: pattern.regex.slice(0, -1) + '|' + original + ')' }
3340
+ })
3341
+ expect(
3342
+ patterns.filter(
3343
+ (pattern) => pattern.message === 'Vue faces must not depend on sibling sheet faces',
3344
+ ),
3345
+ ).toHaveLength(1)
3346
+ return {
3347
+ ...block,
3348
+ rules: { ...rules, 'no-restricted-imports': [rule[0], { ...options, patterns }] },
3349
+ }
3350
+ })
3351
+ expect(
3352
+ restored.filter(
3353
+ (block) => readConfigRecord(block.rules)['policy/no-cross-vue-face'] === 'error',
3354
+ ),
3355
+ ).toHaveLength(2)
3356
+ fixture.write(
3357
+ '.oxlintrc.json',
3358
+ JSON.stringify({ ...restoredConfiguration, overrides: restored }),
3359
+ )
3360
+ const control = readImportDiagnostics(fixture.path, paths)
3361
+ const refused = paths.filter((path) => control.has('eslint(no-restricted-imports) ' + path))
3362
+ expect(refused).toEqual(paths)
3363
+ expect(() => expect(refused).toEqual([])).toThrow('deeply equal')
3364
+ expect(paths.filter((path) => control.has('policy(no-cross-vue-face) ' + path))).toEqual([])
3365
+ } finally {
3366
+ fixture.destroy()
3367
+ }
3368
+ })
3369
+
3370
+ it('checks exact Vue composable membership and malformed modules through the canonical policy', () => {
3371
+ const fixture = createPolicyScratch({ prefix: 'policy-vue-domain-' })
3372
+ try {
3373
+ fixture.write('.oxlintrc.json', readFileSync(resolve(POLICY_ROOT, '.oxlintrc.json'), 'utf8'))
3374
+ fixture.write(
3375
+ 'configs/policy.ts',
3376
+ readFileSync(resolve(POLICY_ROOT, 'configs/policy.ts'), 'utf8'),
3377
+ )
3378
+ const sources = POLICY_VUE_DOMAIN_CASES
3379
+ expect(sources).toContainEqual({
3380
+ ...POLICY_VUE_COMPOSABLE_SOURCE,
3381
+ misplaced: false,
3382
+ malformed: false,
3383
+ })
3384
+ for (const source of sources) fixture.write(source.path, source.content)
3385
+ const codes = readImportDiagnostics(
3386
+ fixture.path,
3387
+ sources.map(({ path }) => path),
3388
+ )
3389
+ expect(
3390
+ sources.map(({ path }) => ({
3391
+ path,
3392
+ misplaced: codes.has('policy(no-misplaced-function) ' + path),
3393
+ malformed: codes.has('policy(no-malformed-domain) ' + path),
3394
+ })),
3395
+ ).toEqual(sources.map(({ path, misplaced, malformed }) => ({ path, misplaced, malformed })))
3396
+ } finally {
3397
+ fixture.destroy()
3398
+ }
3399
+ })
3400
+
3401
+ it('detects a removed Vue composable registration in a scratch policy', () => {
3402
+ const fixture = createPolicyScratch({ prefix: 'policy-vue-domain-control-' })
3403
+ try {
3404
+ fixture.write('.oxlintrc.json', readFileSync(resolve(POLICY_ROOT, '.oxlintrc.json'), 'utf8'))
3405
+ const plugin = readFileSync(resolve(POLICY_ROOT, 'configs/policy.ts'), 'utf8')
3406
+ const registration = /^\t'app\/vue\/composables',\r?\n/gmu
3407
+ expect(plugin.match(registration)).toHaveLength(1)
3408
+ fixture.write('configs/policy.ts', plugin)
3409
+ const source = POLICY_VUE_COMPOSABLE_SOURCE
3410
+ fixture.write(source.path, source.content)
3411
+ const enabled = readImportDiagnostics(fixture.path, [source.path])
3412
+ expect(enabled.has('policy(no-misplaced-function) ' + source.path)).toBe(false)
3413
+ expect(enabled.has('policy(no-malformed-domain) ' + source.path)).toBe(false)
3414
+ fixture.write('configs/policy.ts', plugin.replace(registration, ''))
3415
+ const control = readImportDiagnostics(fixture.path, [source.path])
3416
+ expect(control.has('policy(no-misplaced-function) ' + source.path)).toBe(true)
3417
+ expect(control.has('policy(no-malformed-domain) ' + source.path)).toBe(false)
3418
+ expect(() =>
3419
+ expect(control.has('policy(no-misplaced-function) ' + source.path)).toBe(false),
3420
+ ).toThrow('expected')
3421
+ } finally {
3422
+ fixture.destroy()
3423
+ }
3424
+ })
3425
+
3426
+ it('admits nested same-face Vue relative imports through the canonical policy', () => {
3427
+ const fixture = createPolicyScratch({ prefix: 'policy-vue-relative-' })
3428
+ try {
3429
+ fixture.write('.oxlintrc.json', readFileSync(resolve(POLICY_ROOT, '.oxlintrc.json'), 'utf8'))
3430
+ fixture.write(
3431
+ 'configs/policy.ts',
3432
+ readFileSync(resolve(POLICY_ROOT, 'configs/policy.ts'), 'utf8'),
3433
+ )
3434
+ for (const source of POLICY_VUE_RELATIVE_SOURCES) {
3435
+ fixture.write(source.path, source.content)
3436
+ }
3437
+ const paths = POLICY_VUE_RELATIVE_SOURCES.map(({ path }) => path)
3438
+ expect(paths).toContain('app/vue/components/Panel.vue')
3439
+ expect(paths).toContain('src/vue/features/helpers.ts')
3440
+ const codes = readImportDiagnostics(fixture.path, paths)
3441
+ const refused = paths.filter(
3442
+ (path) =>
3443
+ codes.has('eslint(no-restricted-imports) ' + path) ||
3444
+ codes.has('policy(no-cross-vue-face) ' + path),
3445
+ )
3446
+ expect(refused).toEqual([])
3447
+ } finally {
3448
+ fixture.destroy()
3449
+ }
3450
+ })
3451
+
3452
+ it('admits a valid direct application Vue composable through the canonical policy', () => {
3453
+ const fixture = createPolicyScratch({ prefix: 'policy-vue-composable-' })
3454
+ try {
3455
+ fixture.write('.oxlintrc.json', readFileSync(resolve(POLICY_ROOT, '.oxlintrc.json'), 'utf8'))
3456
+ fixture.write(
3457
+ 'configs/policy.ts',
3458
+ readFileSync(resolve(POLICY_ROOT, 'configs/policy.ts'), 'utf8'),
3459
+ )
3460
+ const source = POLICY_VUE_COMPOSABLE_SOURCE
3461
+ expect(source.path).toBe('app/vue/composables/useTheme.ts')
3462
+ fixture.write(source.path, source.content)
3463
+ const codes = readImportDiagnostics(fixture.path, [source.path])
3464
+ expect({
3465
+ misplaced: codes.has('policy(no-misplaced-function) ' + source.path),
3466
+ malformed: codes.has('policy(no-malformed-domain) ' + source.path),
3467
+ }).toEqual({ misplaced: false, malformed: false })
3468
+ } finally {
3469
+ fixture.destroy()
3470
+ }
3471
+ })
3472
+
2926
3473
  it('matches every isolated import pattern with refused and admitted fixtures', () => {
2927
3474
  const fixture = createPolicyScratch({ prefix: 'propagation-patterns-' })
2928
3475
  const scratch = fixture.path
@@ -2982,7 +3529,7 @@ createSomething({
2982
3529
  : message.includes('sibling sheet')
2983
3530
  ? [
2984
3531
  ['@src/print', true],
2985
- ['../print/index.js', true],
3532
+ // Relative refusals now belong to the canonical filename-based matrix.
2986
3533
  ['@src/core', false],
2987
3534
  ['@src/browser', false],
2988
3535
  ['@src/vue', false],
@@ -2990,8 +3537,8 @@ createSomething({
2990
3537
  ['@app/browser', false],
2991
3538
  ['@src/corex', true],
2992
3539
  ['@src/', true],
2993
- ['../src/print/index.js', true],
2994
- ['../browser?x', true],
3540
+ // The app browser query is admitted there; src retains its alias refusal.
3541
+ ['../browser?x', false],
2995
3542
  ['../print.ext/index.js', false],
2996
3543
  ...(Array.isArray(block.files) &&
2997
3544
  block.files.some(
@@ -3302,13 +3849,17 @@ createSomething({
3302
3849
  const readings = [...expected].map(([path, refusal]) => ({
3303
3850
  path,
3304
3851
  expected: refusal,
3305
- actual: enabled.has('eslint(no-restricted-imports) ' + path),
3852
+ actual:
3853
+ enabled.has('eslint(no-restricted-imports) ' + path) ||
3854
+ enabled.has('policy(no-cross-vue-face) ' + path),
3306
3855
  }))
3307
3856
  expect(readings.filter((reading) => reading.actual !== reading.expected)).toEqual([])
3308
3857
  const control = [...expected].map(([path, refusal]) => ({
3309
3858
  path,
3310
3859
  expected: refusal,
3311
- actual: disabled.has('eslint(no-restricted-imports) ' + path),
3860
+ actual:
3861
+ disabled.has('eslint(no-restricted-imports) ' + path) ||
3862
+ disabled.has('policy(no-cross-vue-face) ' + path),
3312
3863
  }))
3313
3864
  expect(() =>
3314
3865
  expect(control.filter((reading) => reading.actual !== reading.expected)).toEqual([]),
@@ -3329,6 +3880,7 @@ createSomething({
3329
3880
  const declared = Object.getOwnPropertyNames(rules)
3330
3881
  expect(declared).toContain('no-misplaced-function')
3331
3882
  expect(declared).toContain('no-host-line-endings')
3883
+ expect(declared).toContain('no-cross-vue-face')
3332
3884
  expect(
3333
3885
  inspectPolicyWiring(
3334
3886
  parsed,
@@ -3380,6 +3932,7 @@ createSomething({
3380
3932
  scratch.write('src/violations/constants.ts', 'export const values = []\n')
3381
3933
  scratch.write('src/violations/composables.ts', "export const READY = 'yes'\n")
3382
3934
  scratch.write('app/browser/composables/useTheme.ts', 'export function useMode(): void {}\n')
3935
+ scratch.write('app/vue/helpers.ts', "export * from '../print/index.js'\ndebugger\n")
3383
3936
  // The line-ending population reaches src, app, and configs alone, so this module
3384
3937
  // outside them carries the same defect and must draw no diagnostic. The `debugger`
3385
3938
  // statement is the arrival control the root `no-debugger` rule reports, which proves
@@ -3482,6 +4035,8 @@ createSomething({
3482
4035
  { code: 'policy(no-misnamed-plugin)', filename: 'src/violations/plugins.ts' },
3483
4036
  { code: 'policy(no-malformed-constant)', filename: 'src/violations/constants.ts' },
3484
4037
  { code: 'policy(no-malformed-domain)', filename: 'src/violations/composables.ts' },
4038
+ { code: 'policy(no-cross-vue-face)', filename: 'app/vue/helpers.ts' },
4039
+ { code: 'eslint(no-debugger)', filename: 'app/vue/helpers.ts' },
3485
4040
  {
3486
4041
  code: 'policy(no-malformed-domain)',
3487
4042
  filename: 'app/browser/composables/useTheme.ts',