@orkestrel/scaffold 0.0.107 → 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.
- package/dist/agents/skills/orkestrel-dispatch/scripts/sweep.js +124 -4
- package/dist/bin/main.js +1 -1
- package/dist/bin/main.js.map +1 -1
- package/dist/host/AGENTS.md +5 -1
- package/dist/host/agents/orchestration.md +60 -45
- package/dist/host/agents/platforms/posix.md +15 -0
- package/dist/host/agents/platforms/windows.md +37 -0
- package/dist/host/agents/skills/orkestrel-debrief/references/retention.md +10 -10
- package/dist/host/agents/skills/orkestrel-dispatch/SKILL.md +6 -5
- package/dist/host/agents/skills/orkestrel-dispatch/references/launch.md +12 -8
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/sweep.ts +144 -4
- package/dist/host/agents/skills/orkestrel-parallel/SKILL.md +67 -10
- package/dist/host/agents/skills/orkestrel-parallel/agents/openai.yaml +2 -2
- package/dist/host/agents/skills/orkestrel-parallel/references/claude.md +32 -0
- package/dist/host/agents/skills/orkestrel-parallel/references/codex.md +28 -0
- package/dist/host/agents/skills/orkestrel-parallel/references/cursor.md +29 -0
- package/dist/host/agents/skills/orkestrel-parallel/references/t3.md +31 -0
- package/dist/host/agents/skills/orkestrel-publish/references/wave.md +2 -1
- package/dist/host/agents/transports/codex.md +1 -1
- package/dist/host/claude/AGENTS.md +6 -9
- package/dist/host/claude/rules/campaign.md +55 -0
- package/dist/host/claude/rules/documentation.md +2 -0
- package/dist/host/claude/rules/portability.md +1 -2
- package/dist/host/claude/rules/typescript.md +1 -1
- package/dist/host/claude/rules/writing.md +5 -2
- package/dist/host/claude/skills/orkestrel-parallel/SKILL.md +1 -1
- package/dist/host/codex/config.toml +3 -3
- package/dist/host/configs/policy.ts +106 -1
- package/dist/host/cursor/rules/orchestration.mdc +1 -0
- package/dist/host/dotfiles/oxlintrc.json +4 -2
- package/dist/host/guides/scaffold.md +56 -1
- package/dist/host/manifest.json +64 -27
- package/dist/host/tests/config.test.ts +560 -5
- package/dist/host/tests/setupPolicy.ts +496 -6
- package/dist/src/core/index.cjs +26 -2
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +1 -1
- package/dist/src/core/index.d.ts +1 -1
- package/dist/src/core/index.js +26 -2
- package/dist/src/core/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/host/agents/skills/orkestrel-parallel/references/harnesses.md +0 -19
|
@@ -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
|
-
|
|
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
|
-
|
|
2994
|
-
['../browser?x',
|
|
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:
|
|
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:
|
|
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',
|