@orkestrel/scaffold 0.0.108 → 0.0.109
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/bench.js +326 -91
- package/dist/agents/skills/orkestrel-dispatch/scripts/brief.js +110 -14
- package/dist/agents/skills/orkestrel-dispatch/scripts/chain.js +280 -0
- package/dist/agents/skills/orkestrel-dispatch/scripts/cite.js +188 -26
- package/dist/agents/skills/orkestrel-dispatch/scripts/launch.js +142 -43
- package/dist/agents/skills/orkestrel-dispatch/scripts/login.js +240 -62
- package/dist/agents/skills/orkestrel-dispatch/scripts/patch.js +323 -0
- package/dist/agents/skills/orkestrel-dispatch/scripts/result.js +169 -32
- package/dist/agents/skills/orkestrel-dispatch/scripts/sweep.js +290 -69
- package/dist/agents/skills/orkestrel-falsify/scripts/mutate.js +560 -0
- package/dist/agents/skills/orkestrel-harden/scripts/discovery.js +1052 -205
- package/dist/agents/skills/orkestrel-harden/scripts/measure.js +361 -0
- package/dist/agents/skills/orkestrel-harden/scripts/titles.js +455 -80
- package/dist/agents/skills/orkestrel-parallel/scripts/cases.js +327 -0
- package/dist/agents/skills/orkestrel-publish/scripts/compare.js +298 -51
- package/dist/agents/skills/orkestrel-publish/scripts/pins.js +170 -13
- package/dist/agents/skills/orkestrel-publish/scripts/swap.js +956 -191
- package/dist/agents/skills/orkestrel-publish/scripts/wave.js +707 -112
- package/dist/agents/skills/orkestrel-publish/scripts/window.js +235 -41
- package/dist/agents/skills/orkestrel-scout/scripts/map.js +341 -54
- package/dist/agents/templates/brief.md +1 -1
- package/dist/bin/main.js +51 -46
- package/dist/bin/main.js.map +1 -1
- package/dist/host/AGENTS.md +23 -21
- package/dist/host/agents/orchestration.md +43 -18
- package/dist/host/agents/platforms/windows.md +3 -3
- package/dist/host/agents/skills/orkestrel-align/SKILL.md +10 -13
- package/dist/host/agents/skills/orkestrel-align/references/fleet.md +4 -10
- package/dist/host/agents/skills/orkestrel-align/references/integration.md +1 -1
- package/dist/host/agents/skills/orkestrel-build/SKILL.md +16 -48
- package/dist/host/agents/skills/orkestrel-build/agents/openai.yaml +1 -1
- package/dist/host/agents/skills/orkestrel-debrief/SKILL.md +19 -16
- package/dist/host/agents/skills/orkestrel-debrief/agents/openai.yaml +1 -1
- package/dist/host/agents/skills/orkestrel-debrief/references/field-testing.md +15 -28
- package/dist/host/agents/skills/orkestrel-debrief/references/instruction-audit.md +5 -6
- package/dist/host/agents/skills/orkestrel-debrief/references/retention.md +4 -6
- package/dist/host/agents/skills/orkestrel-dispatch/SKILL.md +31 -20
- package/dist/host/agents/skills/orkestrel-dispatch/references/launch.md +3 -4
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/bench.ts +366 -75
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/brief.ts +126 -16
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/chain.ts +323 -0
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/cite.ts +220 -31
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/launch.ts +176 -33
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/login.ts +294 -59
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/patch.ts +401 -0
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/result.ts +199 -34
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/sweep.ts +330 -69
- package/dist/host/agents/skills/orkestrel-falsify/SKILL.md +10 -8
- package/dist/host/agents/skills/orkestrel-falsify/references/brief.md +1 -5
- package/dist/host/agents/skills/orkestrel-falsify/references/reconcile.md +1 -1
- package/dist/host/agents/skills/orkestrel-falsify/scripts/mutate.ts +701 -0
- package/dist/host/agents/skills/orkestrel-harden/SKILL.md +16 -12
- package/dist/host/agents/skills/orkestrel-harden/references/bindings.md +23 -81
- package/dist/host/agents/skills/orkestrel-harden/references/centralization.md +4 -5
- package/dist/host/agents/skills/orkestrel-harden/references/contract.md +2 -0
- package/dist/host/agents/skills/orkestrel-harden/references/document.md +33 -0
- package/dist/host/agents/skills/orkestrel-harden/references/equivalence.md +6 -3
- package/dist/host/agents/skills/orkestrel-harden/references/hardening.md +8 -4
- package/dist/host/agents/skills/orkestrel-harden/references/research.md +2 -0
- package/dist/host/agents/skills/orkestrel-harden/scripts/discovery.ts +1272 -279
- package/dist/host/agents/skills/orkestrel-harden/scripts/measure.ts +425 -0
- package/dist/host/agents/skills/orkestrel-harden/scripts/titles.ts +603 -98
- package/dist/host/agents/skills/orkestrel-journey/SKILL.md +75 -98
- package/dist/host/agents/skills/orkestrel-journey/references/captures.md +54 -17
- package/dist/host/agents/skills/orkestrel-journey/references/decide.md +1 -20
- package/dist/host/agents/skills/orkestrel-journey/references/layer.md +21 -31
- package/dist/host/agents/skills/orkestrel-journey/references/styles.md +1 -1
- package/dist/host/agents/skills/orkestrel-parallel/SKILL.md +28 -24
- package/dist/host/agents/skills/orkestrel-parallel/references/claude.md +15 -13
- package/dist/host/agents/skills/orkestrel-parallel/references/codex.md +1 -0
- package/dist/host/agents/skills/orkestrel-parallel/references/cursor.md +13 -13
- package/dist/host/agents/skills/orkestrel-parallel/scripts/cases.ts +389 -0
- package/dist/host/agents/skills/orkestrel-polish/SKILL.md +16 -20
- package/dist/host/agents/skills/orkestrel-polish/references/capture-harness.md +16 -26
- package/dist/host/agents/skills/orkestrel-publish/SKILL.md +12 -26
- package/dist/host/agents/skills/orkestrel-publish/agents/openai.yaml +2 -2
- package/dist/host/agents/skills/orkestrel-publish/references/adopt.md +23 -0
- package/dist/host/agents/skills/orkestrel-publish/references/release.md +4 -4
- package/dist/host/agents/skills/orkestrel-publish/references/wave.md +10 -9
- package/dist/host/agents/skills/orkestrel-publish/references/window.md +20 -25
- package/dist/host/agents/skills/orkestrel-publish/scripts/compare.ts +348 -59
- package/dist/host/agents/skills/orkestrel-publish/scripts/pins.ts +202 -22
- package/dist/host/agents/skills/orkestrel-publish/scripts/swap.ts +1061 -191
- package/dist/host/agents/skills/orkestrel-publish/scripts/wave.ts +812 -140
- package/dist/host/agents/skills/orkestrel-publish/scripts/window.ts +280 -38
- package/dist/host/agents/skills/orkestrel-scout/SKILL.md +3 -3
- package/dist/host/agents/skills/orkestrel-scout/scripts/map.ts +428 -70
- package/dist/host/agents/templates/brief.md +1 -1
- package/dist/host/agents/transports/claude-cli.md +3 -3
- package/dist/host/agents/transports/codex.md +2 -7
- package/dist/host/agents/transports/cursor.md +2 -3
- package/dist/host/claude/AGENTS.md +2 -2
- package/dist/host/claude/agents/analyst.md +3 -3
- package/dist/host/claude/agents/astra.md +3 -3
- package/dist/host/claude/agents/builder.md +1 -1
- package/dist/host/claude/agents/checker.md +1 -1
- package/dist/host/claude/agents/distiller.md +1 -1
- package/dist/host/claude/agents/grok.md +1 -1
- package/dist/host/claude/agents/opus.md +2 -2
- package/dist/host/claude/agents/orkestrel.md +57 -90
- package/dist/host/claude/agents/researcher.md +1 -1
- package/dist/host/claude/agents/reviewer.md +4 -0
- package/dist/host/claude/agents/scout.md +1 -1
- package/dist/host/claude/agents/verifier.md +1 -1
- package/dist/host/claude/rules/application.md +14 -21
- package/dist/host/claude/rules/architecture.md +34 -44
- package/dist/host/claude/rules/browser.md +3 -4
- package/dist/host/claude/rules/campaign.md +2 -2
- package/dist/host/claude/rules/documentation.md +25 -12
- package/dist/host/claude/rules/names.md +9 -4
- package/dist/host/claude/rules/patterns.md +6 -7
- package/dist/host/claude/rules/portability.md +1 -1
- package/dist/host/claude/rules/quality.md +4 -5
- package/dist/host/claude/rules/styles.md +2 -2
- package/dist/host/claude/rules/tests.md +57 -114
- package/dist/host/claude/rules/typescript.md +7 -9
- package/dist/host/claude/rules/workspace.md +21 -19
- package/dist/host/claude/rules/writing.md +2 -5
- package/dist/host/claude/skills/orkestrel-dispatch/SKILL.md +1 -1
- package/dist/host/claude/skills/orkestrel-harden/SKILL.md +1 -1
- package/dist/host/claude/skills/orkestrel-publish/SKILL.md +1 -1
- package/dist/host/claude/skills/orkestrel-scout/SKILL.md +1 -1
- package/dist/host/codex/agents/analyst.toml +9 -3
- package/dist/host/codex/agents/astra.toml +3 -4
- package/dist/host/codex/agents/builder.toml +1 -1
- package/dist/host/codex/agents/opus.toml +3 -4
- package/dist/host/codex/agents/orkestrel.toml +1 -1
- package/dist/host/codex/agents/researcher.toml +2 -2
- package/dist/host/codex/agents/verifier.toml +1 -1
- package/dist/host/configs/helpers.ts +21 -1
- package/dist/host/configs/policy.ts +299 -15
- package/dist/host/dotfiles/oxlintrc.json +137 -3
- package/dist/host/guides/agent.md +111 -134
- package/dist/host/guides/capture.md +801 -0
- package/dist/host/guides/probe.md +27 -8
- package/dist/host/guides/scaffold.md +96 -18
- package/dist/host/guides/study.md +534 -440
- package/dist/host/guides/test.md +111 -44
- package/dist/host/manifest.json +160 -240
- package/dist/host/tests/config.test.ts +971 -187
- package/dist/host/tests/policy.test.ts +127 -1
- package/dist/host/tests/setupPolicy.ts +893 -33
- package/dist/src/core/index.cjs +214 -86
- package/dist/src/core/index.cjs.map +1 -1
- package/dist/src/core/index.d.cts +122 -38
- package/dist/src/core/index.d.ts +122 -38
- package/dist/src/core/index.js +209 -85
- package/dist/src/core/index.js.map +1 -1
- package/dist/src/server/index.cjs +20 -27
- package/dist/src/server/index.cjs.map +1 -1
- package/dist/src/server/index.js +21 -28
- package/dist/src/server/index.js.map +1 -1
- package/package.json +11 -11
- package/dist/agents/skills/orkestrel-dispatch/scripts/helpers.js +0 -283
- package/dist/host/agents/skills/orkestrel-dispatch/references/bench.md +0 -25
- package/dist/host/agents/skills/orkestrel-dispatch/scripts/helpers.ts +0 -333
- package/dist/host/guides/supervisor.md +0 -4890
|
@@ -2,13 +2,7 @@
|
|
|
2
2
|
// P2: Required items are checked strictly; extra items are ignored before their shape is read.
|
|
3
3
|
|
|
4
4
|
import type { InlineConfig, UserConfig } from 'vite'
|
|
5
|
-
import type {
|
|
6
|
-
PolicyComment,
|
|
7
|
-
PolicyContext,
|
|
8
|
-
PolicyDiagnostic,
|
|
9
|
-
PolicyExpression,
|
|
10
|
-
PolicySourceCode,
|
|
11
|
-
} from '../configs/policy.js'
|
|
5
|
+
import type { PolicyComment, PolicyDiagnostic, PolicyExpression } from '../configs/policy.js'
|
|
12
6
|
import type { PolicyProject } from './setupPolicy.js'
|
|
13
7
|
import { spawnSync } from 'node:child_process'
|
|
14
8
|
import {
|
|
@@ -42,16 +36,21 @@ import policyPlugin, {
|
|
|
42
36
|
FUNCTION_RULE,
|
|
43
37
|
FUNCTION_SOURCE_FILES,
|
|
44
38
|
HIDDEN_RULE,
|
|
39
|
+
LET_RULE,
|
|
45
40
|
MOCKING_RULE,
|
|
41
|
+
MUTABLE_RULE,
|
|
46
42
|
NESTED_RULE,
|
|
47
43
|
PARSER_RULE,
|
|
48
44
|
PLUGIN_RULE,
|
|
49
45
|
POLICY_BANNED_TERMS,
|
|
50
46
|
POLICY_ENDING_GLOBS,
|
|
51
47
|
POLICY_JUDGED_TERMS,
|
|
48
|
+
POLICY_NESTED_GLOBS,
|
|
52
49
|
POLICY_PLACEMENT_GLOBS,
|
|
50
|
+
POLICY_SCRIPT_GLOBS,
|
|
53
51
|
POLICY_VOICE_STOPWORDS,
|
|
54
52
|
PRIVACY_RULE,
|
|
53
|
+
SCRIPT_RULE,
|
|
55
54
|
TERM_RULE,
|
|
56
55
|
TYPE_RULE,
|
|
57
56
|
VOICE_RULE,
|
|
@@ -85,16 +84,25 @@ import {
|
|
|
85
84
|
SHEET_POLICY_ORDER_PATTERN,
|
|
86
85
|
readImportDiagnostics,
|
|
87
86
|
readNativeDiagnostics,
|
|
87
|
+
createPolicyComment,
|
|
88
|
+
createPolicyContext,
|
|
89
|
+
createPolicyDeclarator,
|
|
90
|
+
createPolicyExport,
|
|
88
91
|
createPolicyLazyCases,
|
|
92
|
+
createPolicyRefused,
|
|
89
93
|
createPolicyScratch,
|
|
94
|
+
createPolicySource,
|
|
90
95
|
createPolicySparse,
|
|
91
96
|
inspectPolicyConfiguration,
|
|
92
97
|
inspectPolicyWiring,
|
|
93
98
|
normalizePolicyFilename,
|
|
94
99
|
POLICY_EXTRACTOR,
|
|
100
|
+
POLICY_IMPORT_CASES,
|
|
95
101
|
POLICY_LAZY_SHAPES,
|
|
102
|
+
POLICY_NAME_CASES,
|
|
96
103
|
POLICY_PUBLISHES,
|
|
97
104
|
POLICY_ROOT,
|
|
105
|
+
POLICY_WIRING_SETTINGS,
|
|
98
106
|
POLICY_VUE_COMPOSABLE_SOURCE,
|
|
99
107
|
POLICY_VUE_RELATIVE_SOURCES,
|
|
100
108
|
POLICY_VUE_TARGET_CASES,
|
|
@@ -244,7 +252,7 @@ describe('selected faces', () => {
|
|
|
244
252
|
const sheets = collectSheets(POLICY_ROOT)
|
|
245
253
|
const frameworks = collectFrameworks(POLICY_ROOT)
|
|
246
254
|
const wrappers = collectFaceWrappers(POLICY_ROOT)
|
|
247
|
-
const
|
|
255
|
+
const configurations: Array<{
|
|
248
256
|
readonly axis: string
|
|
249
257
|
readonly face: string
|
|
250
258
|
readonly wrapper: string
|
|
@@ -279,10 +287,10 @@ describe('selected faces', () => {
|
|
|
279
287
|
expect(
|
|
280
288
|
Object.getOwnPropertyDescriptor(tsconfig.compilerOptions.paths, `@${axis}/${face}`)?.value,
|
|
281
289
|
).toEqual([`./${axis}/${face}/index.ts`])
|
|
282
|
-
|
|
290
|
+
configurations.push({ axis, face, wrapper, project })
|
|
283
291
|
}
|
|
284
|
-
for (const { face, wrapper, project } of
|
|
285
|
-
sheets.includes(
|
|
292
|
+
for (const { face, wrapper, project } of configurations.filter((faceConfiguration) =>
|
|
293
|
+
sheets.includes(faceConfiguration.face),
|
|
286
294
|
)) {
|
|
287
295
|
expect(readFileSync(resolve(POLICY_ROOT, wrapper), 'utf8')).toContain('sheetProject(')
|
|
288
296
|
expect(inspectSheetConfiguration(project)).toEqual([])
|
|
@@ -296,16 +304,18 @@ describe('selected faces', () => {
|
|
|
296
304
|
)
|
|
297
305
|
expect(readConfigScript(scripts, 'build:src')).toContain(`npm run build:src:${face}`)
|
|
298
306
|
}
|
|
299
|
-
for (const { project } of
|
|
300
|
-
frameworks.includes(`${
|
|
307
|
+
for (const { project } of configurations.filter((faceConfiguration) =>
|
|
308
|
+
frameworks.includes(`${faceConfiguration.axis}/${faceConfiguration.face}`),
|
|
301
309
|
)) {
|
|
302
310
|
expect(project.optimizeDeps?.include).toContain('vue')
|
|
303
311
|
expect(project.test?.setupFiles).toEqual(
|
|
304
312
|
expect.arrayContaining(['./tests/setup.ts', './tests/setupBrowser.ts']),
|
|
305
313
|
)
|
|
306
314
|
}
|
|
307
|
-
for (const { wrapper } of
|
|
308
|
-
(
|
|
315
|
+
for (const { wrapper } of configurations.filter(
|
|
316
|
+
(faceConfiguration) =>
|
|
317
|
+
faceConfiguration.axis === 'src' &&
|
|
318
|
+
frameworks.includes(`${faceConfiguration.axis}/${faceConfiguration.face}`),
|
|
309
319
|
)) {
|
|
310
320
|
const text = readFileSync(resolve(POLICY_ROOT, wrapper), 'utf8')
|
|
311
321
|
expect(text).toContain('rewriteCoreSpecifier')
|
|
@@ -804,7 +814,7 @@ describe('root configuration', () => {
|
|
|
804
814
|
[
|
|
805
815
|
'probe',
|
|
806
816
|
{
|
|
807
|
-
benchmark: ['tmp/probes/**/*.
|
|
817
|
+
benchmark: ['tmp/probes/**/*.bench.ts', 'tests/**/*.bench.ts'],
|
|
808
818
|
include: 'tmp/probes/**/*.test.ts',
|
|
809
819
|
parallel: false,
|
|
810
820
|
pool: 'threads',
|
|
@@ -994,8 +1004,8 @@ describe('root configuration', () => {
|
|
|
994
1004
|
sentinel: true,
|
|
995
1005
|
}
|
|
996
1006
|
const test = { name: { label: 'control' }, include: ['tests/control.test.ts'] }
|
|
997
|
-
const
|
|
998
|
-
const
|
|
1007
|
+
const ignoreInput = Object.defineProperty(() => ({ test }), 'name', { value: 'ignoring' })
|
|
1008
|
+
const spreadInput = Object.defineProperty((record: object) => ({ ...record, test }), 'name', {
|
|
999
1009
|
value: 'spreading',
|
|
1000
1010
|
})
|
|
1001
1011
|
const wrappers = projects.filter((entry) => typeof entry === 'string')
|
|
@@ -1027,8 +1037,8 @@ describe('root configuration', () => {
|
|
|
1027
1037
|
).toThrow('expected')
|
|
1028
1038
|
}
|
|
1029
1039
|
const factories = projects.filter((entry) => typeof entry !== 'string')
|
|
1030
|
-
const entries: readonly unknown[] = [...factories,
|
|
1031
|
-
const
|
|
1040
|
+
const entries: readonly unknown[] = [...factories, ignoreInput, spreadInput, { test }]
|
|
1041
|
+
const outcomes = entries.map((entry) => {
|
|
1032
1042
|
if (typeof entry !== 'function') {
|
|
1033
1043
|
return { name: undefined, callable: false, mode: undefined, leaked: [] }
|
|
1034
1044
|
}
|
|
@@ -1046,13 +1056,13 @@ describe('root configuration', () => {
|
|
|
1046
1056
|
}
|
|
1047
1057
|
})
|
|
1048
1058
|
const forwarded = { callable: true, mode: 'sentinel-mode', leaked: [] }
|
|
1049
|
-
expect(
|
|
1059
|
+
expect(outcomes.slice(0, factories.length)).toStrictEqual(
|
|
1050
1060
|
factories.map((entry) => ({
|
|
1051
1061
|
name: typeof entry === 'function' ? entry.name : undefined,
|
|
1052
1062
|
...forwarded,
|
|
1053
1063
|
})),
|
|
1054
1064
|
)
|
|
1055
|
-
expect(
|
|
1065
|
+
expect(outcomes.slice(factories.length)).toStrictEqual([
|
|
1056
1066
|
{ name: 'ignoring', callable: true, mode: undefined, leaked: [] },
|
|
1057
1067
|
{
|
|
1058
1068
|
name: 'spreading',
|
|
@@ -1062,8 +1072,8 @@ describe('root configuration', () => {
|
|
|
1062
1072
|
},
|
|
1063
1073
|
{ name: undefined, callable: false, mode: undefined, leaked: [] },
|
|
1064
1074
|
])
|
|
1065
|
-
for (const
|
|
1066
|
-
expect({ ...
|
|
1075
|
+
for (const outcome of outcomes.slice(factories.length)) {
|
|
1076
|
+
expect({ ...outcome, name: undefined }).not.toStrictEqual({ name: undefined, ...forwarded })
|
|
1067
1077
|
}
|
|
1068
1078
|
|
|
1069
1079
|
// A record that carries no string `mode` is refused rather than forwarded as a project
|
|
@@ -1331,7 +1341,7 @@ describe('root configuration', () => {
|
|
|
1331
1341
|
"process.stdout.write('lazy\\n')\nexport function lazy(): string {\n\treturn String(process.argv.length)\n}\n",
|
|
1332
1342
|
)
|
|
1333
1343
|
const rosters: Array<readonly string[]> = []
|
|
1334
|
-
for (const
|
|
1344
|
+
for (const chunked of [false, true]) {
|
|
1335
1345
|
const loaded = await loadConfigFromFile(
|
|
1336
1346
|
{ command: 'build', mode: 'production', isSsrBuild: false, isPreview: false },
|
|
1337
1347
|
wrapper,
|
|
@@ -1358,7 +1368,7 @@ describe('root configuration', () => {
|
|
|
1358
1368
|
formats: ['cjs'],
|
|
1359
1369
|
fileName: () => 'main.cjs',
|
|
1360
1370
|
},
|
|
1361
|
-
rolldownOptions:
|
|
1371
|
+
rolldownOptions: chunked
|
|
1362
1372
|
? { ...rolldown, output: { codeSplitting: true } }
|
|
1363
1373
|
: { ...rolldown },
|
|
1364
1374
|
},
|
|
@@ -1647,6 +1657,49 @@ describe('root configuration', () => {
|
|
|
1647
1657
|
expect(inspectPolicyConfiguration(controlled)).toEqual([
|
|
1648
1658
|
'overrides must not configure policy/no-mocking',
|
|
1649
1659
|
])
|
|
1660
|
+
// The skill scripts sit under .agents, so an ignore pattern that reaches the folder defeats the
|
|
1661
|
+
// script rules as an ignore of a source root defeats the placement rules.
|
|
1662
|
+
const ignored = structuredClone(parsed)
|
|
1663
|
+
Object.defineProperty(ignored, 'ignorePatterns', {
|
|
1664
|
+
value: ['.orkestrel/', '.agents/skills/'],
|
|
1665
|
+
enumerable: true,
|
|
1666
|
+
configurable: true,
|
|
1667
|
+
writable: true,
|
|
1668
|
+
})
|
|
1669
|
+
expect(inspectPolicyConfiguration(ignored)).toEqual([
|
|
1670
|
+
'ignorePatterns must not reach .agents/skills/',
|
|
1671
|
+
])
|
|
1672
|
+
// An entry without an inner slash matches at any depth, so a glob or a directory name the
|
|
1673
|
+
// overrides route through silences guarded files while its first segment names no root.
|
|
1674
|
+
for (const pattern of ['*.ts', 'scripts', 'scripts/', 'policy.ts']) {
|
|
1675
|
+
expect(
|
|
1676
|
+
inspectPolicyConfiguration({ ...readConfigRecord(parsed), ignorePatterns: [pattern] }),
|
|
1677
|
+
).toEqual([`ignorePatterns must not reach ${pattern}`])
|
|
1678
|
+
}
|
|
1679
|
+
for (const patterns of [['.orkestrel/'], ['.orkestrel/', '!src/keep.ts'], ['./scripts']]) {
|
|
1680
|
+
expect(
|
|
1681
|
+
inspectPolicyConfiguration({ ...readConfigRecord(parsed), ignorePatterns: patterns }),
|
|
1682
|
+
).toEqual([])
|
|
1683
|
+
}
|
|
1684
|
+
})
|
|
1685
|
+
|
|
1686
|
+
it('refuses a wiring rule whose options weaken it beneath an error severity', () => {
|
|
1687
|
+
const parsed: unknown = JSON.parse(readFileSync(resolve(POLICY_ROOT, '.oxlintrc.json'), 'utf8'))
|
|
1688
|
+
const rules = readConfigRecord(readConfigRecord(parsed).rules)
|
|
1689
|
+
for (const [rule, setting] of [
|
|
1690
|
+
['typescript/explicit-member-accessibility', ['error', { accessibility: 'off' }]],
|
|
1691
|
+
['typescript/parameter-properties', ['error', { allow: ['private readonly'] }]],
|
|
1692
|
+
['no-var', ['error', { ignore: true }]],
|
|
1693
|
+
] as const) {
|
|
1694
|
+
expect(
|
|
1695
|
+
inspectPolicyConfiguration({
|
|
1696
|
+
...readConfigRecord(parsed),
|
|
1697
|
+
rules: { ...rules, [rule]: setting },
|
|
1698
|
+
}),
|
|
1699
|
+
).toEqual([
|
|
1700
|
+
`${rule} must have the top-level setting ${JSON.stringify(POLICY_WIRING_SETTINGS[rule])}`,
|
|
1701
|
+
])
|
|
1702
|
+
}
|
|
1650
1703
|
})
|
|
1651
1704
|
|
|
1652
1705
|
it('omits the audit-confirmed dead policy type exports', () => {
|
|
@@ -1761,6 +1814,80 @@ describe('policy plugin', () => {
|
|
|
1761
1814
|
],
|
|
1762
1815
|
})
|
|
1763
1816
|
|
|
1817
|
+
tester.run('no-let', LET_RULE, {
|
|
1818
|
+
valid: [
|
|
1819
|
+
{ name: 'leaves an unwritten let to prefer-const', code: 'let value = 1\nread(value)' },
|
|
1820
|
+
{
|
|
1821
|
+
name: 'leaves an unwritten for-of head to prefer-const',
|
|
1822
|
+
code: 'for (let entry of entries) read(entry)',
|
|
1823
|
+
},
|
|
1824
|
+
{
|
|
1825
|
+
name: 'leaves an unwritten for-in head to prefer-const',
|
|
1826
|
+
code: 'for (let key in record) read(key)',
|
|
1827
|
+
},
|
|
1828
|
+
{ name: 'accepts const', code: 'const value = 1\nread(value)' },
|
|
1829
|
+
{ name: 'leaves var to no-var', code: 'var value\nvalue = 1' },
|
|
1830
|
+
{ name: 'accepts an ambient let', code: 'declare let value: number' },
|
|
1831
|
+
{
|
|
1832
|
+
name: 'accepts a let in a global augmentation',
|
|
1833
|
+
code: 'declare global { let ambient: number }',
|
|
1834
|
+
},
|
|
1835
|
+
{
|
|
1836
|
+
name: 'accepts an exported let in a declared module',
|
|
1837
|
+
code: "declare module 'm' { export let ambient: string }",
|
|
1838
|
+
},
|
|
1839
|
+
{
|
|
1840
|
+
name: 'accepts a let in a declared namespace',
|
|
1841
|
+
code: 'declare namespace Ambient { let value: number }',
|
|
1842
|
+
},
|
|
1843
|
+
{
|
|
1844
|
+
name: 'accepts a let in a namespace nested in a declared module',
|
|
1845
|
+
code: "declare module 'm' { namespace Inner { let value: number } }",
|
|
1846
|
+
},
|
|
1847
|
+
],
|
|
1848
|
+
invalid: [
|
|
1849
|
+
...[
|
|
1850
|
+
{ form: 'assignment', code: 'let count = 0\ncount = 1' },
|
|
1851
|
+
{ form: 'compound assignment', code: 'let count = 0\ncount += 1' },
|
|
1852
|
+
{ form: 'logical assignment', code: 'let count: number | undefined = 0\ncount ??= 1' },
|
|
1853
|
+
{ form: 'update', code: 'let count = 0\ncount++' },
|
|
1854
|
+
{ form: 'counter loop', code: 'for (let index = 0; index < 3; index++) read(index)' },
|
|
1855
|
+
{ form: 'array destructuring target', code: 'let [first] = [1]\n;[first] = [2]' },
|
|
1856
|
+
{ form: 'object destructuring target', code: 'let { a } = record\n;({ a } = other)' },
|
|
1857
|
+
{
|
|
1858
|
+
form: 'for-of head written in the body',
|
|
1859
|
+
code: 'for (let entry of entries) { entry = 1 }',
|
|
1860
|
+
},
|
|
1861
|
+
{ form: 'closure write', code: 'let ready = false\nlisten(() => { ready = true })' },
|
|
1862
|
+
{ form: 'later declarator', code: 'let a = 1, b = 2\nb = 3\nread(a, b)' },
|
|
1863
|
+
{ form: 'declaration without a value', code: 'let value\nread(value)' },
|
|
1864
|
+
{ form: 'value assigned later', code: 'let value: number\nvalue = 1\nread(value)' },
|
|
1865
|
+
].map(({ form, code }) => ({
|
|
1866
|
+
name: `rejects a let by ${form} [membership: let that prefer-const leaves]`,
|
|
1867
|
+
code,
|
|
1868
|
+
errors: [{ messageId: 'let' }],
|
|
1869
|
+
})),
|
|
1870
|
+
...[
|
|
1871
|
+
{
|
|
1872
|
+
form: 'a bare let beside a global augmentation',
|
|
1873
|
+
code: 'declare global { let ambient: number }\nlet value\nread(value)',
|
|
1874
|
+
},
|
|
1875
|
+
{
|
|
1876
|
+
form: 'a reassigned let beside a declared module',
|
|
1877
|
+
code: "declare module 'm' { export let ambient: string }\nlet count = 0\ncount++",
|
|
1878
|
+
},
|
|
1879
|
+
{
|
|
1880
|
+
form: 'a bare let in a namespace that is not declared',
|
|
1881
|
+
code: 'namespace Local { let value: number }',
|
|
1882
|
+
},
|
|
1883
|
+
].map(({ form, code }) => ({
|
|
1884
|
+
name: `rejects ${form} [membership: let outside an ambient block]`,
|
|
1885
|
+
code,
|
|
1886
|
+
errors: [{ messageId: 'let' }],
|
|
1887
|
+
})),
|
|
1888
|
+
],
|
|
1889
|
+
})
|
|
1890
|
+
|
|
1764
1891
|
tester.run('no-nested-functions', NESTED_RULE, {
|
|
1765
1892
|
valid: [
|
|
1766
1893
|
{
|
|
@@ -2520,6 +2647,253 @@ createSomething({
|
|
|
2520
2647
|
],
|
|
2521
2648
|
})
|
|
2522
2649
|
|
|
2650
|
+
const SCRIPT_GUARD = [
|
|
2651
|
+
'if (process.argv[1] !== undefined && realpathSync(process.argv[1]) === import.meta.filename) {',
|
|
2652
|
+
'\tprocess.exitCode = main()',
|
|
2653
|
+
'}',
|
|
2654
|
+
].join('\n')
|
|
2655
|
+
|
|
2656
|
+
tester.run('no-malformed-script', SCRIPT_RULE, {
|
|
2657
|
+
valid: [
|
|
2658
|
+
{
|
|
2659
|
+
name: 'accepts imports, documented exports, and the one guarded entry',
|
|
2660
|
+
filename: '.agents/skills/sample/scripts/tool.ts',
|
|
2661
|
+
code: [
|
|
2662
|
+
"import { realpathSync } from 'node:fs'",
|
|
2663
|
+
'',
|
|
2664
|
+
'/** Reads the one value. */',
|
|
2665
|
+
'export function read(): number {',
|
|
2666
|
+
'\treturn 1',
|
|
2667
|
+
'}',
|
|
2668
|
+
'',
|
|
2669
|
+
'/** Runs the script. */',
|
|
2670
|
+
'export function main(): number {',
|
|
2671
|
+
'\treturn read()',
|
|
2672
|
+
'}',
|
|
2673
|
+
'',
|
|
2674
|
+
SCRIPT_GUARD,
|
|
2675
|
+
].join('\n'),
|
|
2676
|
+
},
|
|
2677
|
+
{
|
|
2678
|
+
name: 'accepts a documented exported type, interface, and constant',
|
|
2679
|
+
filename: '.agents/skills/sample/scripts/tool.ts',
|
|
2680
|
+
code: [
|
|
2681
|
+
'/** Names the one value. */',
|
|
2682
|
+
'export type Value = number',
|
|
2683
|
+
'',
|
|
2684
|
+
'/** Describes the one record. */',
|
|
2685
|
+
'export interface Entry {',
|
|
2686
|
+
'\treadonly value: Value',
|
|
2687
|
+
'}',
|
|
2688
|
+
'',
|
|
2689
|
+
'/** Holds the one constant. */',
|
|
2690
|
+
'export const VALUE = 1',
|
|
2691
|
+
].join('\n'),
|
|
2692
|
+
},
|
|
2693
|
+
{
|
|
2694
|
+
name: 'accepts a guard the formatter wrapped over lines',
|
|
2695
|
+
filename: '.agents/skills/sample/scripts/tool.ts',
|
|
2696
|
+
code: [
|
|
2697
|
+
'if (',
|
|
2698
|
+
'\tprocess.argv[1] !== undefined &&',
|
|
2699
|
+
'\trealpathSync(process.argv[1]) === import.meta.filename',
|
|
2700
|
+
') {',
|
|
2701
|
+
'\tmain()',
|
|
2702
|
+
'}',
|
|
2703
|
+
].join('\n'),
|
|
2704
|
+
},
|
|
2705
|
+
{
|
|
2706
|
+
name: 'accepts a doc block separated from its export by a blank line',
|
|
2707
|
+
filename: '.agents/skills/sample/scripts/tool.ts',
|
|
2708
|
+
code: ['/** Holds the one constant. */', '', 'export const VALUE = 1'].join('\n'),
|
|
2709
|
+
},
|
|
2710
|
+
{
|
|
2711
|
+
name: 'accepts a module outside the skill scripts',
|
|
2712
|
+
filename: 'src/worker/helpers.ts',
|
|
2713
|
+
code: 'function hidden(): void {}\nhidden()',
|
|
2714
|
+
},
|
|
2715
|
+
{
|
|
2716
|
+
name: 'accepts a file beside the skill scripts',
|
|
2717
|
+
filename: '.agents/skills/sample/references/tool.ts',
|
|
2718
|
+
code: 'function hidden(): void {}\nhidden()',
|
|
2719
|
+
},
|
|
2720
|
+
],
|
|
2721
|
+
invalid: [
|
|
2722
|
+
{
|
|
2723
|
+
name: 'rejects an unexported function [membership: top-level declarations of a skill script]',
|
|
2724
|
+
filename: '.agents/skills/sample/scripts/tool.ts',
|
|
2725
|
+
code: 'function read(): number { return 1 }',
|
|
2726
|
+
errors: [{ messageId: 'hidden' }],
|
|
2727
|
+
},
|
|
2728
|
+
{
|
|
2729
|
+
name: 'rejects an unexported constant [membership: top-level declarations of a skill script]',
|
|
2730
|
+
filename: '.agents/skills/sample/scripts/tool.ts',
|
|
2731
|
+
code: 'const VALUE = 1',
|
|
2732
|
+
errors: [{ messageId: 'hidden' }],
|
|
2733
|
+
},
|
|
2734
|
+
{
|
|
2735
|
+
name: 'rejects an unexported interface even with a doc block [membership: top-level declarations of a skill script]',
|
|
2736
|
+
filename: '.agents/skills/sample/scripts/tool.ts',
|
|
2737
|
+
code: [
|
|
2738
|
+
'/** Describes the one record. */',
|
|
2739
|
+
'interface Value { readonly value: number }',
|
|
2740
|
+
].join('\n'),
|
|
2741
|
+
errors: [{ messageId: 'hidden' }],
|
|
2742
|
+
},
|
|
2743
|
+
{
|
|
2744
|
+
name: 'rejects an export with no comment above it [membership: exported declarations of a skill script]',
|
|
2745
|
+
filename: '.agents/skills/sample/scripts/tool.ts',
|
|
2746
|
+
code: 'export function read(): number { return 1 }',
|
|
2747
|
+
errors: [{ messageId: 'doc' }],
|
|
2748
|
+
},
|
|
2749
|
+
{
|
|
2750
|
+
name: 'rejects an export under a line comment [membership: exported declarations of a skill script]',
|
|
2751
|
+
filename: '.agents/skills/sample/scripts/tool.ts',
|
|
2752
|
+
code: ['// Reads the one value.', 'export function read(): number { return 1 }'].join('\n'),
|
|
2753
|
+
errors: [{ messageId: 'doc' }],
|
|
2754
|
+
},
|
|
2755
|
+
{
|
|
2756
|
+
name: 'rejects an export under a plain block comment [membership: exported declarations of a skill script]',
|
|
2757
|
+
filename: '.agents/skills/sample/scripts/tool.ts',
|
|
2758
|
+
code: ['/* Reads the one value. */', 'export const VALUE = 1'].join('\n'),
|
|
2759
|
+
errors: [{ messageId: 'doc' }],
|
|
2760
|
+
},
|
|
2761
|
+
{
|
|
2762
|
+
name: 'rejects a doc block separated from its export by code [membership: exported declarations of a skill script]',
|
|
2763
|
+
filename: '.agents/skills/sample/scripts/tool.ts',
|
|
2764
|
+
code: ['/** Holds the one constant. */', 'const FIRST = 1', 'export const SECOND = 2'].join(
|
|
2765
|
+
'\n',
|
|
2766
|
+
),
|
|
2767
|
+
errors: [{ messageId: 'hidden' }, { messageId: 'doc' }],
|
|
2768
|
+
},
|
|
2769
|
+
{
|
|
2770
|
+
name: 'rejects a bare call [membership: top-level statements of a skill script]',
|
|
2771
|
+
filename: '.agents/skills/sample/scripts/tool.ts',
|
|
2772
|
+
code: 'main()',
|
|
2773
|
+
errors: [{ messageId: 'entry' }],
|
|
2774
|
+
},
|
|
2775
|
+
{
|
|
2776
|
+
name: 'rejects an entry under another condition [membership: top-level statements of a skill script]',
|
|
2777
|
+
filename: '.agents/skills/sample/scripts/tool.ts',
|
|
2778
|
+
code: ['if (process.argv[1] !== undefined) {', '\tmain()', '}'].join('\n'),
|
|
2779
|
+
errors: [{ messageId: 'entry' }],
|
|
2780
|
+
},
|
|
2781
|
+
{
|
|
2782
|
+
name: 'rejects the guard when it carries an else branch [membership: top-level statements of a skill script]',
|
|
2783
|
+
filename: '.agents/skills/sample/scripts/tool.ts',
|
|
2784
|
+
code: [SCRIPT_GUARD.replace(/\}$/u, '} else {'), '\tmain()', '}'].join('\n'),
|
|
2785
|
+
errors: [{ messageId: 'entry' }],
|
|
2786
|
+
},
|
|
2787
|
+
{
|
|
2788
|
+
name: 'rejects a second guard [membership: top-level statements of a skill script]',
|
|
2789
|
+
filename: '.agents/skills/sample/scripts/tool.ts',
|
|
2790
|
+
code: [SCRIPT_GUARD, SCRIPT_GUARD].join('\n'),
|
|
2791
|
+
errors: [{ messageId: 'entry' }],
|
|
2792
|
+
},
|
|
2793
|
+
{
|
|
2794
|
+
name: 'rejects an export list [membership: top-level statements of a skill script]',
|
|
2795
|
+
filename: '.agents/skills/sample/scripts/tool.ts',
|
|
2796
|
+
code: ['/** Holds the one constant. */', 'const VALUE = 1', 'export { VALUE }'].join('\n'),
|
|
2797
|
+
errors: [{ messageId: 'hidden' }, { messageId: 'entry' }],
|
|
2798
|
+
},
|
|
2799
|
+
{
|
|
2800
|
+
name: 'rejects a default export [membership: top-level statements of a skill script]',
|
|
2801
|
+
filename: '.agents/skills/sample/scripts/tool.ts',
|
|
2802
|
+
code: 'export default {}',
|
|
2803
|
+
errors: [{ messageId: 'entry' }],
|
|
2804
|
+
},
|
|
2805
|
+
],
|
|
2806
|
+
})
|
|
2807
|
+
|
|
2808
|
+
tester.run('no-mutable-property', MUTABLE_RULE, {
|
|
2809
|
+
valid: [
|
|
2810
|
+
{
|
|
2811
|
+
name: 'accepts a readonly interface property',
|
|
2812
|
+
filename: 'src/core/types.ts',
|
|
2813
|
+
code: 'export interface Value { readonly id: string }',
|
|
2814
|
+
},
|
|
2815
|
+
{
|
|
2816
|
+
name: 'accepts a readonly property of a type alias object',
|
|
2817
|
+
filename: 'src/core/types.ts',
|
|
2818
|
+
code: 'export type Value = { readonly id: string; readonly name?: string }',
|
|
2819
|
+
},
|
|
2820
|
+
{
|
|
2821
|
+
name: 'accepts a method signature',
|
|
2822
|
+
filename: 'src/core/types.ts',
|
|
2823
|
+
code: 'export interface Value { read(): string }',
|
|
2824
|
+
},
|
|
2825
|
+
{
|
|
2826
|
+
name: 'accepts an inline parameter type',
|
|
2827
|
+
filename: 'src/core/helpers.ts',
|
|
2828
|
+
code: 'export function read(options: { id: string }): string { return options.id }',
|
|
2829
|
+
},
|
|
2830
|
+
{
|
|
2831
|
+
name: 'accepts an inline parameter type of an interface method',
|
|
2832
|
+
filename: 'src/core/types.ts',
|
|
2833
|
+
code: 'export interface Value { read(options: { id: string }): string }',
|
|
2834
|
+
},
|
|
2835
|
+
{
|
|
2836
|
+
name: 'accepts an inline parameter type of a function typed property',
|
|
2837
|
+
filename: 'src/core/types.ts',
|
|
2838
|
+
code: 'export interface Value { readonly read: (options: { id: string }) => string }',
|
|
2839
|
+
},
|
|
2840
|
+
{
|
|
2841
|
+
name: 'accepts an object type annotating a binding',
|
|
2842
|
+
filename: 'src/core/constants.ts',
|
|
2843
|
+
code: "export const VALUE: { id: string } = { id: 'a' }",
|
|
2844
|
+
},
|
|
2845
|
+
{
|
|
2846
|
+
name: 'accepts a mutable property of an ambient declaration file',
|
|
2847
|
+
filename: 'src/core/types.d.ts',
|
|
2848
|
+
code: 'export interface Value { id: string }',
|
|
2849
|
+
},
|
|
2850
|
+
],
|
|
2851
|
+
invalid: [
|
|
2852
|
+
{
|
|
2853
|
+
name: 'rejects a mutable interface property [membership: property signatures of an interface]',
|
|
2854
|
+
filename: 'src/core/types.ts',
|
|
2855
|
+
code: 'export interface Value { id: string }',
|
|
2856
|
+
errors: [{ messageId: 'mutable' }],
|
|
2857
|
+
},
|
|
2858
|
+
{
|
|
2859
|
+
name: 'rejects a mutable optional interface property [membership: property signatures of an interface]',
|
|
2860
|
+
filename: 'src/core/types.ts',
|
|
2861
|
+
code: 'export interface Value { readonly id: string; name?: string }',
|
|
2862
|
+
errors: [{ messageId: 'mutable' }],
|
|
2863
|
+
},
|
|
2864
|
+
{
|
|
2865
|
+
name: 'rejects a mutable property of a type alias object [membership: property signatures of a type alias object type]',
|
|
2866
|
+
filename: 'src/core/types.ts',
|
|
2867
|
+
code: 'export type Value = { id: string }',
|
|
2868
|
+
errors: [{ messageId: 'mutable' }],
|
|
2869
|
+
},
|
|
2870
|
+
{
|
|
2871
|
+
name: 'rejects a mutable property of a union member [membership: property signatures of a type alias object type]',
|
|
2872
|
+
filename: 'src/core/types.ts',
|
|
2873
|
+
code: 'export type Value = { readonly id: string } | { name: string }',
|
|
2874
|
+
errors: [{ messageId: 'mutable' }],
|
|
2875
|
+
},
|
|
2876
|
+
{
|
|
2877
|
+
name: 'rejects a mutable property of a nested object type [membership: property signatures of a type alias object type]',
|
|
2878
|
+
filename: 'src/core/types.ts',
|
|
2879
|
+
code: 'export interface Value { readonly inner: { id: string } }',
|
|
2880
|
+
errors: [{ messageId: 'mutable' }],
|
|
2881
|
+
},
|
|
2882
|
+
{
|
|
2883
|
+
name: 'rejects a mutable property of a generic argument [membership: property signatures of a type alias object type]',
|
|
2884
|
+
filename: 'src/core/types.ts',
|
|
2885
|
+
code: 'export type Value = ReadonlyArray<{ id: string }>',
|
|
2886
|
+
errors: [{ messageId: 'mutable' }],
|
|
2887
|
+
},
|
|
2888
|
+
{
|
|
2889
|
+
name: 'rejects a mutable function typed property [membership: property signatures of an interface]',
|
|
2890
|
+
filename: 'src/core/types.ts',
|
|
2891
|
+
code: 'export interface Value { read: () => string }',
|
|
2892
|
+
errors: [{ messageId: 'mutable' }],
|
|
2893
|
+
},
|
|
2894
|
+
],
|
|
2895
|
+
})
|
|
2896
|
+
|
|
2523
2897
|
tester.run('no-malformed-summary', VOICE_RULE, {
|
|
2524
2898
|
valid: [
|
|
2525
2899
|
{
|
|
@@ -2887,7 +3261,7 @@ createSomething({
|
|
|
2887
3261
|
range: [47, 63],
|
|
2888
3262
|
declaration: { type: 'FunctionDeclaration', range: [0, 0], id: name },
|
|
2889
3263
|
}
|
|
2890
|
-
const source
|
|
3264
|
+
const source = createPolicySource('')
|
|
2891
3265
|
|
|
2892
3266
|
it('refuses a hole in the declarators of a statement as the declarator it reads as', () => {
|
|
2893
3267
|
const declarator: PolicyExpression = {
|
|
@@ -2935,14 +3309,7 @@ createSomething({
|
|
|
2935
3309
|
|
|
2936
3310
|
it('refuses a hole in the body of a function-domain module as the statement it reads as', () => {
|
|
2937
3311
|
const reports: PolicyDiagnostic[] = []
|
|
2938
|
-
const context
|
|
2939
|
-
filename: '/ws/src/server/execution/run.ts',
|
|
2940
|
-
cwd: '/ws',
|
|
2941
|
-
sourceCode: source,
|
|
2942
|
-
report: (diagnostic) => {
|
|
2943
|
-
reports.push(diagnostic)
|
|
2944
|
-
},
|
|
2945
|
-
}
|
|
3312
|
+
const context = createPolicyContext('/ws/src/server/execution/run.ts', '/ws', reports)
|
|
2946
3313
|
const dense: PolicyExpression = { type: 'Program', range: [0, 0], body: [exported] }
|
|
2947
3314
|
const sparse: PolicyExpression = {
|
|
2948
3315
|
type: 'Program',
|
|
@@ -3008,12 +3375,12 @@ createSomething({
|
|
|
3008
3375
|
{ esmodule: false },
|
|
3009
3376
|
])
|
|
3010
3377
|
if (!Array.isArray(configured.overrides)) throw new Error('Missing lint overrides')
|
|
3011
|
-
const
|
|
3378
|
+
const overrides = configured.overrides
|
|
3012
3379
|
.map(readConfigRecord)
|
|
3013
3380
|
.filter(
|
|
3014
3381
|
(override) => readConfigRecord(override.rules)['import/no-dynamic-require'] !== undefined,
|
|
3015
3382
|
)
|
|
3016
|
-
expect(
|
|
3383
|
+
expect(overrides).toEqual([])
|
|
3017
3384
|
})
|
|
3018
3385
|
|
|
3019
3386
|
it('permits a computed import() and refuses a computed require at every module path', () => {
|
|
@@ -3484,12 +3851,13 @@ createSomething({
|
|
|
3484
3851
|
if (!Array.isArray(rule)) return []
|
|
3485
3852
|
const patterns = readConfigRecord(rule[1]).patterns
|
|
3486
3853
|
if (!Array.isArray(patterns)) throw new Error('Missing restriction patterns')
|
|
3487
|
-
|
|
3854
|
+
const fromPatterns = patterns.map((patternValue: unknown, patternIndex: number) => {
|
|
3488
3855
|
const pattern = readConfigRecord(patternValue)
|
|
3489
3856
|
const message = pattern.message
|
|
3490
3857
|
if (typeof message !== 'string') throw new Error('Missing pattern message')
|
|
3491
3858
|
const alias = /@src\/(core|browser|server) alias/u.exec(message)?.[1]
|
|
3492
|
-
const
|
|
3859
|
+
const fence = POLICY_IMPORT_CASES.find(({ marker }) => message.includes(marker))
|
|
3860
|
+
const chained: ReadonlyArray<readonly [string, boolean]> =
|
|
3493
3861
|
alias !== undefined
|
|
3494
3862
|
? [
|
|
3495
3863
|
[`../${alias}/index.js`, true],
|
|
@@ -3504,73 +3872,90 @@ createSomething({
|
|
|
3504
3872
|
[`../${alias}x/index.js`, false],
|
|
3505
3873
|
[`../${alias}.ext/index.js`, false],
|
|
3506
3874
|
]
|
|
3507
|
-
: message.includes('
|
|
3875
|
+
: message.includes('self-contained')
|
|
3508
3876
|
? [
|
|
3509
|
-
['
|
|
3510
|
-
['
|
|
3877
|
+
['./helpers.ts', true],
|
|
3878
|
+
['../../orkestrel-dispatch/scripts/helpers.ts', true],
|
|
3879
|
+
['@orkestrel/test', true],
|
|
3880
|
+
['fs', true],
|
|
3881
|
+
['', true],
|
|
3882
|
+
['n', true],
|
|
3883
|
+
['no', true],
|
|
3884
|
+
['nod', true],
|
|
3885
|
+
['node', true],
|
|
3886
|
+
['nodes:fs', true],
|
|
3511
3887
|
['node:fs', false],
|
|
3512
|
-
['
|
|
3513
|
-
['no:entry', true],
|
|
3514
|
-
['nod:entry', true],
|
|
3515
|
-
['nodea:entry', true],
|
|
3516
|
-
['1bad:entry', false],
|
|
3517
|
-
['./data:entry', false],
|
|
3888
|
+
['node:child_process', false],
|
|
3518
3889
|
]
|
|
3519
|
-
: message.includes('
|
|
3890
|
+
: message.includes('URL schemes')
|
|
3520
3891
|
? [
|
|
3521
|
-
['
|
|
3522
|
-
['
|
|
3523
|
-
['
|
|
3524
|
-
['
|
|
3525
|
-
['
|
|
3526
|
-
['
|
|
3527
|
-
['
|
|
3892
|
+
['https://host/x', true],
|
|
3893
|
+
['data:text/plain,x', true],
|
|
3894
|
+
['node:fs', false],
|
|
3895
|
+
['n:entry', true],
|
|
3896
|
+
['no:entry', true],
|
|
3897
|
+
['nod:entry', true],
|
|
3898
|
+
['nodea:entry', true],
|
|
3899
|
+
['1bad:entry', false],
|
|
3900
|
+
['./data:entry', false],
|
|
3528
3901
|
]
|
|
3529
|
-
: message.includes('
|
|
3902
|
+
: message.includes('noncanonical dot')
|
|
3530
3903
|
? [
|
|
3531
|
-
['
|
|
3532
|
-
|
|
3533
|
-
['
|
|
3534
|
-
['
|
|
3535
|
-
['
|
|
3536
|
-
['
|
|
3537
|
-
['
|
|
3538
|
-
['@src/corex', true],
|
|
3539
|
-
['@src/', true],
|
|
3540
|
-
// The app browser query is admitted there; src retains its alias refusal.
|
|
3541
|
-
['../browser?x', false],
|
|
3542
|
-
['../print.ext/index.js', false],
|
|
3543
|
-
...(Array.isArray(block.files) &&
|
|
3544
|
-
block.files.some(
|
|
3545
|
-
(file: unknown) =>
|
|
3546
|
-
typeof file === 'string' && file.startsWith('app/vue/'),
|
|
3547
|
-
)
|
|
3548
|
-
? [['@app/print', true] as const]
|
|
3549
|
-
: []),
|
|
3550
|
-
]
|
|
3551
|
-
: [
|
|
3552
|
-
[
|
|
3553
|
-
message.includes('absolute paths')
|
|
3554
|
-
? '/machine/module.js'
|
|
3555
|
-
: message.includes('normalized traversal')
|
|
3556
|
-
? './../core/index.js'
|
|
3557
|
-
: message.includes('forward slashes')
|
|
3558
|
-
? '..\\core\\index.js'
|
|
3559
|
-
: message.includes('compiler API')
|
|
3560
|
-
? 'typescript'
|
|
3561
|
-
: message.includes('private app')
|
|
3562
|
-
? '@app/core'
|
|
3563
|
-
: message.includes('stylesheets')
|
|
3564
|
-
? './index.css'
|
|
3565
|
-
: message.includes('host-independent') ||
|
|
3566
|
-
message.includes('Node or server')
|
|
3567
|
-
? 'node:fs'
|
|
3568
|
-
: message.includes('Vue extension')
|
|
3569
|
-
? '@src/vue'
|
|
3570
|
-
: '@src/browser',
|
|
3571
|
-
true,
|
|
3572
|
-
],
|
|
3904
|
+
['../core/../server/index.js', true],
|
|
3905
|
+
['../core/./index.js', true],
|
|
3906
|
+
['../core/index.js', false],
|
|
3907
|
+
['./index.js', false],
|
|
3908
|
+
['../../core/index.js', false],
|
|
3909
|
+
['.../../index.js', true],
|
|
3910
|
+
['.name/../index.js', true],
|
|
3573
3911
|
]
|
|
3912
|
+
: message.includes('sibling sheet')
|
|
3913
|
+
? [
|
|
3914
|
+
['@src/print', true],
|
|
3915
|
+
// Relative refusals now belong to the canonical filename-based matrix.
|
|
3916
|
+
['@src/core', false],
|
|
3917
|
+
['@src/browser', false],
|
|
3918
|
+
['@src/vue', false],
|
|
3919
|
+
['@app/core', false],
|
|
3920
|
+
['@app/browser', false],
|
|
3921
|
+
['@src/corex', true],
|
|
3922
|
+
['@src/', true],
|
|
3923
|
+
// The app browser query is admitted there; src retains its alias refusal.
|
|
3924
|
+
['../browser?x', false],
|
|
3925
|
+
['../print.ext/index.js', false],
|
|
3926
|
+
...(Array.isArray(block.files) &&
|
|
3927
|
+
block.files.some(
|
|
3928
|
+
(file: unknown) =>
|
|
3929
|
+
typeof file === 'string' && file.startsWith('app/vue/'),
|
|
3930
|
+
)
|
|
3931
|
+
? [['@app/print', true] as const]
|
|
3932
|
+
: []),
|
|
3933
|
+
]
|
|
3934
|
+
: [
|
|
3935
|
+
[
|
|
3936
|
+
message.includes('absolute paths')
|
|
3937
|
+
? '/machine/module.js'
|
|
3938
|
+
: message.includes('normalized traversal')
|
|
3939
|
+
? './../core/index.js'
|
|
3940
|
+
: message.includes('forward slashes')
|
|
3941
|
+
? '..\\core\\index.js'
|
|
3942
|
+
: message.includes('compiler API')
|
|
3943
|
+
? 'typescript'
|
|
3944
|
+
: message.includes('private app')
|
|
3945
|
+
? '@app/core'
|
|
3946
|
+
: message.includes('stylesheets')
|
|
3947
|
+
? './index.css'
|
|
3948
|
+
: message.includes('host-independent') ||
|
|
3949
|
+
message.includes('Node or server')
|
|
3950
|
+
? 'node:fs'
|
|
3951
|
+
: message.includes('Vue extension')
|
|
3952
|
+
? '@src/vue'
|
|
3953
|
+
: '@src/browser',
|
|
3954
|
+
true,
|
|
3955
|
+
],
|
|
3956
|
+
]
|
|
3957
|
+
// A setup-module fence lists its own refused and admitted specifiers in the setup module.
|
|
3958
|
+
const listed = fence?.cases ?? chained
|
|
3574
3959
|
const face =
|
|
3575
3960
|
message.includes('another styles face') && typeof pattern.regex === 'string'
|
|
3576
3961
|
? /@src\/(bootstrap|tailwindcss|styles)\(/u.exec(pattern.regex)?.[1]
|
|
@@ -3591,24 +3976,31 @@ createSomething({
|
|
|
3591
3976
|
[`@src/${face}x`, false],
|
|
3592
3977
|
[`../${face}.ext/index.js`, false],
|
|
3593
3978
|
]
|
|
3979
|
+
// A per-file glob such as `.agents/skills/*/scripts/*.ts` or `tests/setup.ts` names a
|
|
3980
|
+
// directory once its file part is dropped and each wildcard segment takes a sample name.
|
|
3594
3981
|
const owner =
|
|
3595
3982
|
Array.isArray(block.files) && typeof block.files[0] === 'string'
|
|
3596
|
-
? block.files[0]
|
|
3983
|
+
? block.files[0]
|
|
3984
|
+
.split('/**')[0]
|
|
3985
|
+
?.replace(/\/[^/]*\.[a-z]+$/u, '')
|
|
3986
|
+
.replaceAll('*', 'sample')
|
|
3597
3987
|
: undefined
|
|
3598
3988
|
if (owner === undefined) throw new Error('Missing pattern owner')
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
|
|
3606
|
-
|
|
3607
|
-
|
|
3608
|
-
|
|
3609
|
-
|
|
3610
|
-
|
|
3611
|
-
|
|
3989
|
+
// Every relative specifier is refused in a self-contained script and in the policy
|
|
3990
|
+
// plugin, which imports nothing, so their admitted controls are the rows above.
|
|
3991
|
+
const controls =
|
|
3992
|
+
message.includes('self-contained') || fence?.controlled === false
|
|
3993
|
+
? []
|
|
3994
|
+
: [['./index.js', false] as const]
|
|
3995
|
+
const cases = [...sources, ...controls].map(([source, refusal], index) => {
|
|
3996
|
+
const path = `${owner}/pattern${blockIndex}_${patternIndex}_${index}.ts`
|
|
3997
|
+
mkdirSync(dirname(resolve(scratch, path)), { recursive: true })
|
|
3998
|
+
writeFileSync(
|
|
3999
|
+
resolve(scratch, path),
|
|
4000
|
+
'import * as boundary from ' + JSON.stringify(source) + '\nvoid boundary\ndebugger\n',
|
|
4001
|
+
)
|
|
4002
|
+
return { path, refusal }
|
|
4003
|
+
})
|
|
3612
4004
|
return {
|
|
3613
4005
|
cases,
|
|
3614
4006
|
override: {
|
|
@@ -3617,7 +4009,60 @@ createSomething({
|
|
|
3617
4009
|
},
|
|
3618
4010
|
}
|
|
3619
4011
|
})
|
|
4012
|
+
// A path entry restricts named imports of one module, so each case imports one clause.
|
|
4013
|
+
const entries = readConfigRecord(rule[1]).paths
|
|
4014
|
+
const fromPaths = (Array.isArray(entries) ? entries : []).map(
|
|
4015
|
+
(entryValue: unknown, entryIndex: number) => {
|
|
4016
|
+
const entry = readConfigRecord(entryValue)
|
|
4017
|
+
const message = entry.message
|
|
4018
|
+
if (typeof message !== 'string' || typeof entry.name !== 'string') {
|
|
4019
|
+
throw new Error('Missing path entry message or module')
|
|
4020
|
+
}
|
|
4021
|
+
const named = POLICY_NAME_CASES.find(({ marker }) => message.includes(marker))
|
|
4022
|
+
if (named === undefined) throw new Error('Missing named import cases for a path entry')
|
|
4023
|
+
const owner =
|
|
4024
|
+
Array.isArray(block.files) && typeof block.files[0] === 'string'
|
|
4025
|
+
? block.files[0].replace(/\/[^/]*\.[a-z]+$/u, '').replaceAll('*', 'sample')
|
|
4026
|
+
: undefined
|
|
4027
|
+
if (owner === undefined) throw new Error('Missing path owner')
|
|
4028
|
+
const cases = [
|
|
4029
|
+
...named.refused.map((clause): [string, boolean] => [clause, true]),
|
|
4030
|
+
...named.admitted.map((clause): [string, boolean] => [clause, false]),
|
|
4031
|
+
].map(([clause, refusal], index) => {
|
|
4032
|
+
const path = `${owner}/path${blockIndex}_${entryIndex}_${index}.ts`
|
|
4033
|
+
mkdirSync(dirname(resolve(scratch, path)), { recursive: true })
|
|
4034
|
+
writeFileSync(
|
|
4035
|
+
resolve(scratch, path),
|
|
4036
|
+
`import { ${clause} } from ${JSON.stringify(entry.name)}\ndebugger\n`,
|
|
4037
|
+
)
|
|
4038
|
+
return { path, refusal }
|
|
4039
|
+
})
|
|
4040
|
+
return {
|
|
4041
|
+
cases,
|
|
4042
|
+
override: {
|
|
4043
|
+
files: cases.map(({ path }) => path),
|
|
4044
|
+
rules: { 'no-restricted-imports': ['error', { paths: [entry] }] },
|
|
4045
|
+
},
|
|
4046
|
+
}
|
|
4047
|
+
},
|
|
4048
|
+
)
|
|
4049
|
+
return [...fromPatterns, ...fromPaths]
|
|
4050
|
+
})
|
|
4051
|
+
// Each fence keeps a pattern or path entry in the configuration, so deleting one fails here
|
|
4052
|
+
// rather than leaving the loop above with nothing to read.
|
|
4053
|
+
const messages = configured.overrides.flatMap((value: unknown) => {
|
|
4054
|
+
const rule = readConfigRecord(readConfigRecord(value).rules)['no-restricted-imports']
|
|
4055
|
+
if (!Array.isArray(rule)) return []
|
|
4056
|
+
const options = readConfigRecord(rule[1])
|
|
4057
|
+
return [options.patterns, options.paths]
|
|
4058
|
+
.flatMap((list: unknown) => (Array.isArray(list) ? list : []))
|
|
4059
|
+
.map((entry: unknown) => String(readConfigRecord(entry).message))
|
|
3620
4060
|
})
|
|
4061
|
+
expect(
|
|
4062
|
+
[...POLICY_IMPORT_CASES, ...POLICY_NAME_CASES]
|
|
4063
|
+
.map(({ marker }) => marker)
|
|
4064
|
+
.filter((marker) => !messages.some((message) => message.includes(marker))),
|
|
4065
|
+
).toEqual([])
|
|
3621
4066
|
const overrides = built.map(({ override }) => override)
|
|
3622
4067
|
const expected = new Map(
|
|
3623
4068
|
built.flatMap(({ cases }) =>
|
|
@@ -3629,12 +4074,12 @@ createSomething({
|
|
|
3629
4074
|
JSON.stringify({ rules: { 'no-debugger': 'error' }, overrides }),
|
|
3630
4075
|
)
|
|
3631
4076
|
const codes = readImportDiagnostics(scratch, [...expected.keys()])
|
|
3632
|
-
const
|
|
4077
|
+
const verdicts = [...expected].map(([path, refusal]) => ({
|
|
3633
4078
|
path,
|
|
3634
4079
|
expected: refusal,
|
|
3635
4080
|
actual: codes.has('eslint(no-restricted-imports) ' + path),
|
|
3636
4081
|
}))
|
|
3637
|
-
expect(
|
|
4082
|
+
expect(verdicts.filter((verdict) => verdict.expected !== verdict.actual)).toEqual([])
|
|
3638
4083
|
writeFileSync(
|
|
3639
4084
|
resolve(scratch, '.oxlintrc.json'),
|
|
3640
4085
|
JSON.stringify({ rules: { 'no-debugger': 'error', 'no-restricted-imports': 'off' } }),
|
|
@@ -3846,14 +4291,14 @@ createSomething({
|
|
|
3846
4291
|
const [enabled, disabled] = reports
|
|
3847
4292
|
if (enabled === undefined || disabled === undefined)
|
|
3848
4293
|
throw new Error('Missing boundary readings')
|
|
3849
|
-
const
|
|
4294
|
+
const verdicts = [...expected].map(([path, refusal]) => ({
|
|
3850
4295
|
path,
|
|
3851
4296
|
expected: refusal,
|
|
3852
4297
|
actual:
|
|
3853
4298
|
enabled.has('eslint(no-restricted-imports) ' + path) ||
|
|
3854
4299
|
enabled.has('policy(no-cross-vue-face) ' + path),
|
|
3855
4300
|
}))
|
|
3856
|
-
expect(
|
|
4301
|
+
expect(verdicts.filter((verdict) => verdict.actual !== verdict.expected)).toEqual([])
|
|
3857
4302
|
const control = [...expected].map(([path, refusal]) => ({
|
|
3858
4303
|
path,
|
|
3859
4304
|
expected: refusal,
|
|
@@ -3862,7 +4307,7 @@ createSomething({
|
|
|
3862
4307
|
disabled.has('policy(no-cross-vue-face) ' + path),
|
|
3863
4308
|
}))
|
|
3864
4309
|
expect(() =>
|
|
3865
|
-
expect(control.filter((
|
|
4310
|
+
expect(control.filter((verdict) => verdict.actual !== verdict.expected)).toEqual([]),
|
|
3866
4311
|
).toThrow('deeply equal')
|
|
3867
4312
|
expect(control).toContainEqual({ path: 'src/vue/refused3.ts', expected: true, actual: false })
|
|
3868
4313
|
expect(control).toContainEqual({ path: 'src/vue/refused8.ts', expected: true, actual: false })
|
|
@@ -3881,13 +4326,52 @@ createSomething({
|
|
|
3881
4326
|
expect(declared).toContain('no-misplaced-function')
|
|
3882
4327
|
expect(declared).toContain('no-host-line-endings')
|
|
3883
4328
|
expect(declared).toContain('no-cross-vue-face')
|
|
4329
|
+
expect(declared).toContain('no-malformed-script')
|
|
4330
|
+
expect(declared).toContain('no-mutable-property')
|
|
3884
4331
|
expect(
|
|
3885
4332
|
inspectPolicyWiring(
|
|
3886
4333
|
parsed,
|
|
3887
4334
|
declared.map((name) => `policy/${name}`),
|
|
3888
|
-
[POLICY_PLACEMENT_GLOBS, POLICY_ENDING_GLOBS],
|
|
4335
|
+
[POLICY_PLACEMENT_GLOBS, POLICY_ENDING_GLOBS, POLICY_SCRIPT_GLOBS, POLICY_NESTED_GLOBS],
|
|
3889
4336
|
),
|
|
3890
4337
|
).toEqual([])
|
|
4338
|
+
// The controls: a configuration with the skill-script override or the nested-function
|
|
4339
|
+
// override removed reports the population it no longer declares.
|
|
4340
|
+
const overrides: unknown = Object.getOwnPropertyDescriptor(parsed, 'overrides')?.value
|
|
4341
|
+
if (!Array.isArray(overrides)) throw new Error('The Oxlint configuration has no overrides')
|
|
4342
|
+
for (const population of [POLICY_SCRIPT_GLOBS, POLICY_NESTED_GLOBS]) {
|
|
4343
|
+
const trimmed = {
|
|
4344
|
+
...readConfigRecord(parsed),
|
|
4345
|
+
overrides: overrides.filter((override: unknown) => {
|
|
4346
|
+
const files = readConfigRecord(override).files
|
|
4347
|
+
return !Array.isArray(files) || files.join(' ') !== population.join(' ')
|
|
4348
|
+
}),
|
|
4349
|
+
}
|
|
4350
|
+
expect(
|
|
4351
|
+
inspectPolicyWiring(
|
|
4352
|
+
trimmed,
|
|
4353
|
+
[],
|
|
4354
|
+
[POLICY_PLACEMENT_GLOBS, POLICY_ENDING_GLOBS, POLICY_SCRIPT_GLOBS, POLICY_NESTED_GLOBS],
|
|
4355
|
+
),
|
|
4356
|
+
).toEqual([`no override files list declares the population exactly: ${population.join(' ')}`])
|
|
4357
|
+
}
|
|
4358
|
+
// The control: the script override keeps its exact population while the rule it enables is
|
|
4359
|
+
// set to off, which names the rule as enabled by no record.
|
|
4360
|
+
const disabled = {
|
|
4361
|
+
...readConfigRecord(parsed),
|
|
4362
|
+
overrides: overrides.map((override: unknown) => {
|
|
4363
|
+
const record = readConfigRecord(override)
|
|
4364
|
+
return Object.hasOwn(readConfigRecord(record.rules ?? {}), 'policy/no-malformed-script')
|
|
4365
|
+
? {
|
|
4366
|
+
...record,
|
|
4367
|
+
rules: { ...readConfigRecord(record.rules), 'policy/no-malformed-script': 'off' },
|
|
4368
|
+
}
|
|
4369
|
+
: record
|
|
4370
|
+
}),
|
|
4371
|
+
}
|
|
4372
|
+
expect(
|
|
4373
|
+
inspectPolicyWiring(disabled, ['policy/no-malformed-script'], [POLICY_SCRIPT_GLOBS]),
|
|
4374
|
+
).toEqual(['policy/no-malformed-script is enabled by no top-level or override rules record'])
|
|
3891
4375
|
})
|
|
3892
4376
|
|
|
3893
4377
|
it('loads every configured policy rule through the real binary', () => {
|
|
@@ -3916,6 +4400,15 @@ createSomething({
|
|
|
3916
4400
|
"export const STATUS = 'ready'",
|
|
3917
4401
|
"export const lines = text.trim().split('\\n')",
|
|
3918
4402
|
'export const ending = os.EOL + EOL',
|
|
4403
|
+
'let counter = 0',
|
|
4404
|
+
'counter++',
|
|
4405
|
+
'let unchanged = 1',
|
|
4406
|
+
'var legacy = 1',
|
|
4407
|
+
'function shiftValue(value: number) { value += 1; return value }',
|
|
4408
|
+
'try { void counter } catch (error) { error = undefined; void error }',
|
|
4409
|
+
'void unchanged',
|
|
4410
|
+
'void legacy',
|
|
4411
|
+
'void shiftValue',
|
|
3919
4412
|
'void PrivateMember',
|
|
3920
4413
|
'void ParameterMember',
|
|
3921
4414
|
'void PublicMember',
|
|
@@ -3933,6 +4426,180 @@ createSomething({
|
|
|
3933
4426
|
scratch.write('src/violations/composables.ts', "export const READY = 'yes'\n")
|
|
3934
4427
|
scratch.write('app/browser/composables/useTheme.ts', 'export function useMode(): void {}\n')
|
|
3935
4428
|
scratch.write('app/vue/helpers.ts', "export * from '../print/index.js'\ndebugger\n")
|
|
4429
|
+
scratch.write(
|
|
4430
|
+
'.agents/skills/sample/scripts/shared.ts',
|
|
4431
|
+
["import { readOption } from './helpers.ts'", 'void readOption'].join('\n'),
|
|
4432
|
+
)
|
|
4433
|
+
// The fence reads a dynamic specifier as it reads a static one, and the empty specifier opens
|
|
4434
|
+
// with no `node:` as every other refused specifier does.
|
|
4435
|
+
scratch.write(
|
|
4436
|
+
'.agents/skills/sample/scripts/dynamic.ts',
|
|
4437
|
+
[
|
|
4438
|
+
'/** Loads a sibling. */',
|
|
4439
|
+
"export function load(): Promise<unknown> { return import('./other.ts') }",
|
|
4440
|
+
].join('\n'),
|
|
4441
|
+
)
|
|
4442
|
+
scratch.write(
|
|
4443
|
+
'.agents/skills/sample/scripts/empty.ts',
|
|
4444
|
+
[
|
|
4445
|
+
'/** Loads nothing. */',
|
|
4446
|
+
"export function load(): Promise<unknown> { return import('') }",
|
|
4447
|
+
].join('\n'),
|
|
4448
|
+
)
|
|
4449
|
+
scratch.write(
|
|
4450
|
+
'.agents/skills/sample/scripts/alone.ts',
|
|
4451
|
+
["import { readFileSync } from 'node:fs'", 'void readFileSync'].join('\n'),
|
|
4452
|
+
)
|
|
4453
|
+
// A script that hides a declaration, exports one with no doc block, and runs at import draws
|
|
4454
|
+
// every message; the conforming script beside it draws none, and its `debugger` statement
|
|
4455
|
+
// sits inside a function, outside the top-level statements the script rule reads, so the
|
|
4456
|
+
// root `no-debugger` rule proves the file entered the run.
|
|
4457
|
+
scratch.write(
|
|
4458
|
+
'.agents/skills/sample/scripts/malformed.ts',
|
|
4459
|
+
[
|
|
4460
|
+
'function hidden(): void {}',
|
|
4461
|
+
'export function bare(): void {}',
|
|
4462
|
+
'hidden()',
|
|
4463
|
+
'bare()',
|
|
4464
|
+
].join('\n'),
|
|
4465
|
+
)
|
|
4466
|
+
scratch.write(
|
|
4467
|
+
'.agents/skills/sample/scripts/conforming.ts',
|
|
4468
|
+
[
|
|
4469
|
+
"import { realpathSync } from 'node:fs'",
|
|
4470
|
+
'',
|
|
4471
|
+
'/** Reads the one value. */',
|
|
4472
|
+
'export function read(): number {',
|
|
4473
|
+
'\tdebugger',
|
|
4474
|
+
'\treturn 1',
|
|
4475
|
+
'}',
|
|
4476
|
+
'',
|
|
4477
|
+
'/** Runs the script. */',
|
|
4478
|
+
'export function main(): number {',
|
|
4479
|
+
'\treturn read()',
|
|
4480
|
+
'}',
|
|
4481
|
+
'',
|
|
4482
|
+
'if (process.argv[1] !== undefined && realpathSync(process.argv[1]) === import.meta.filename) {',
|
|
4483
|
+
'\tprocess.exitCode = main()',
|
|
4484
|
+
'}',
|
|
4485
|
+
].join('\n'),
|
|
4486
|
+
)
|
|
4487
|
+
// Every form of write to a parameter or a catch binding draws the built-in rule, one file per
|
|
4488
|
+
// form so that a form the binary misses names itself.
|
|
4489
|
+
const reassignments = [
|
|
4490
|
+
{
|
|
4491
|
+
file: 'param-update',
|
|
4492
|
+
code: 'no-param-reassign',
|
|
4493
|
+
source: 'export function f(a: number) { a++; return a }',
|
|
4494
|
+
},
|
|
4495
|
+
{
|
|
4496
|
+
file: 'param-array-target',
|
|
4497
|
+
code: 'no-param-reassign',
|
|
4498
|
+
source: 'export function f(b: number[]) { ;[b] = [[1]]; return b }',
|
|
4499
|
+
},
|
|
4500
|
+
{
|
|
4501
|
+
file: 'param-object-target',
|
|
4502
|
+
code: 'no-param-reassign',
|
|
4503
|
+
source: 'export function f(c: object) { ;({ c } = { c: {} }); return c }',
|
|
4504
|
+
},
|
|
4505
|
+
{
|
|
4506
|
+
file: 'param-for-in-head',
|
|
4507
|
+
code: 'no-param-reassign',
|
|
4508
|
+
source: 'export function f(e: string, c: object) { for (e in c) void e; return e }',
|
|
4509
|
+
},
|
|
4510
|
+
{
|
|
4511
|
+
file: 'param-for-of-head',
|
|
4512
|
+
code: 'no-param-reassign',
|
|
4513
|
+
source: "export function f(e: string) { for (e of ['x']) void e; return e }",
|
|
4514
|
+
},
|
|
4515
|
+
{
|
|
4516
|
+
file: 'catch-update',
|
|
4517
|
+
code: 'no-ex-assign',
|
|
4518
|
+
source: 'try { void 0 } catch (err) { err++ }',
|
|
4519
|
+
},
|
|
4520
|
+
{
|
|
4521
|
+
file: 'catch-destructured',
|
|
4522
|
+
code: 'no-ex-assign',
|
|
4523
|
+
source: "try { void 0 } catch ({ message }) { message = 'x' }",
|
|
4524
|
+
},
|
|
4525
|
+
{
|
|
4526
|
+
file: 'catch-for-of-head',
|
|
4527
|
+
code: 'no-ex-assign',
|
|
4528
|
+
source: 'try { void 0 } catch (err) { for (err of [1]) void err }',
|
|
4529
|
+
},
|
|
4530
|
+
].map(({ file, code, source }) => ({
|
|
4531
|
+
code: `eslint(${code})`,
|
|
4532
|
+
filename: `src/violations/reassign/${file}.ts`,
|
|
4533
|
+
source,
|
|
4534
|
+
}))
|
|
4535
|
+
for (const { filename, source } of reassignments) scratch.write(filename, source)
|
|
4536
|
+
// An ambient block holds a `let` with no value and no `declare` flag of its own, and the
|
|
4537
|
+
// `debugger` statement proves the file entered the run the absence assertion reads.
|
|
4538
|
+
scratch.write(
|
|
4539
|
+
'src/violations/ambient.ts',
|
|
4540
|
+
[
|
|
4541
|
+
'declare global { let ambientGlobal: number }',
|
|
4542
|
+
"declare module 'ambient-module' { export let ambientExport: string }",
|
|
4543
|
+
'debugger',
|
|
4544
|
+
'export {}',
|
|
4545
|
+
].join('\n'),
|
|
4546
|
+
)
|
|
4547
|
+
scratch.write(
|
|
4548
|
+
'src/violations/types.ts',
|
|
4549
|
+
[
|
|
4550
|
+
'/** Describes a mutable record. */',
|
|
4551
|
+
'export interface MutableRecord { id: string }',
|
|
4552
|
+
'/** Describes a frozen record. */',
|
|
4553
|
+
'export interface FrozenRecord { readonly id: string }',
|
|
4554
|
+
'/** Aliases an object type. */',
|
|
4555
|
+
'export type MutableAlias = { value: number }',
|
|
4556
|
+
'/** Reads one field through an inline parameter type. */',
|
|
4557
|
+
'export type Reader = (options: { id: string }) => string',
|
|
4558
|
+
].join('\n'),
|
|
4559
|
+
)
|
|
4560
|
+
// The setup-module fences replace the generic tests restriction for the files they name,
|
|
4561
|
+
// and the proofs beside them keep the generic one. The `debugger` statement proves each file
|
|
4562
|
+
// entered the run.
|
|
4563
|
+
scratch.write(
|
|
4564
|
+
'tests/setup.ts',
|
|
4565
|
+
["import { readFileSync } from 'node:fs'", "import { ref } from 'vue'", 'debugger'].join(
|
|
4566
|
+
'\n',
|
|
4567
|
+
),
|
|
4568
|
+
)
|
|
4569
|
+
scratch.write(
|
|
4570
|
+
'tests/setupBrowser.ts',
|
|
4571
|
+
[
|
|
4572
|
+
"import { describe, it } from 'vitest'",
|
|
4573
|
+
"import { readFileSync } from 'node:fs'",
|
|
4574
|
+
'debugger',
|
|
4575
|
+
].join('\n'),
|
|
4576
|
+
)
|
|
4577
|
+
scratch.write(
|
|
4578
|
+
'tests/setupServer.ts',
|
|
4579
|
+
["import { expect } from 'vitest'", 'debugger'].join('\n'),
|
|
4580
|
+
)
|
|
4581
|
+
scratch.write(
|
|
4582
|
+
'tests/setupPolicy.ts',
|
|
4583
|
+
["import { readFileSync } from 'node:fs'", "import { vi } from 'vitest'", 'debugger'].join(
|
|
4584
|
+
'\n',
|
|
4585
|
+
),
|
|
4586
|
+
)
|
|
4587
|
+
scratch.write(
|
|
4588
|
+
'tests/setupStyles.ts',
|
|
4589
|
+
["import { TestRunner } from 'vitest'", 'debugger'].join('\n'),
|
|
4590
|
+
)
|
|
4591
|
+
scratch.write(
|
|
4592
|
+
'tests/setup.test.ts',
|
|
4593
|
+
["import { describe, expect, it } from 'vitest'", 'debugger'].join('\n'),
|
|
4594
|
+
)
|
|
4595
|
+
scratch.write(
|
|
4596
|
+
'tests/titles.test.ts',
|
|
4597
|
+
[
|
|
4598
|
+
"import { expect, it } from 'vitest'",
|
|
4599
|
+
"it('reads one value', () => expect(1).toBe(1))",
|
|
4600
|
+
"it('reads one value', () => expect(2).toBe(2))",
|
|
4601
|
+
].join('\n'),
|
|
4602
|
+
)
|
|
3936
4603
|
// The line-ending population reaches src, app, and configs alone, so this module
|
|
3937
4604
|
// outside them carries the same defect and must draw no diagnostic. The `debugger`
|
|
3938
4605
|
// statement is the arrival control the root `no-debugger` rule reports, which proves
|
|
@@ -3985,7 +4652,18 @@ createSomething({
|
|
|
3985
4652
|
const config = resolve(scratch.path, '.oxlintrc.json')
|
|
3986
4653
|
const violations = spawnSync(
|
|
3987
4654
|
process.execPath,
|
|
3988
|
-
[
|
|
4655
|
+
[
|
|
4656
|
+
binary,
|
|
4657
|
+
'--config',
|
|
4658
|
+
config,
|
|
4659
|
+
'--format',
|
|
4660
|
+
'json',
|
|
4661
|
+
'src/violations',
|
|
4662
|
+
'app',
|
|
4663
|
+
'scripts',
|
|
4664
|
+
'tests',
|
|
4665
|
+
'.agents',
|
|
4666
|
+
],
|
|
3989
4667
|
{ cwd: scratch.path, encoding: 'utf8', timeout: 15_000 },
|
|
3990
4668
|
)
|
|
3991
4669
|
const clean = spawnSync(
|
|
@@ -4023,7 +4701,33 @@ createSomething({
|
|
|
4023
4701
|
for (const { code, filename } of [
|
|
4024
4702
|
{ code: 'policy(no-mocking)', filename: 'src/violations/fixture.ts' },
|
|
4025
4703
|
{ code: 'policy(no-keyword-privacy)', filename: 'src/violations/fixture.ts' },
|
|
4704
|
+
{ code: 'policy(no-let)', filename: 'src/violations/fixture.ts' },
|
|
4705
|
+
{ code: 'eslint(prefer-const)', filename: 'src/violations/fixture.ts' },
|
|
4706
|
+
{ code: 'eslint(no-var)', filename: 'src/violations/fixture.ts' },
|
|
4707
|
+
{ code: 'eslint(no-param-reassign)', filename: 'src/violations/fixture.ts' },
|
|
4708
|
+
{ code: 'eslint(no-ex-assign)', filename: 'src/violations/fixture.ts' },
|
|
4709
|
+
{ code: 'vitest(no-identical-title)', filename: 'tests/titles.test.ts' },
|
|
4710
|
+
{
|
|
4711
|
+
code: 'eslint(no-restricted-imports)',
|
|
4712
|
+
filename: '.agents/skills/sample/scripts/shared.ts',
|
|
4713
|
+
},
|
|
4714
|
+
{
|
|
4715
|
+
code: 'eslint(no-restricted-imports)',
|
|
4716
|
+
filename: '.agents/skills/sample/scripts/dynamic.ts',
|
|
4717
|
+
},
|
|
4718
|
+
{
|
|
4719
|
+
code: 'eslint(no-restricted-imports)',
|
|
4720
|
+
filename: '.agents/skills/sample/scripts/empty.ts',
|
|
4721
|
+
},
|
|
4026
4722
|
{ code: 'policy(no-nested-functions)', filename: 'src/violations/fixture.ts' },
|
|
4723
|
+
{
|
|
4724
|
+
code: 'policy(no-malformed-script)',
|
|
4725
|
+
filename: '.agents/skills/sample/scripts/malformed.ts',
|
|
4726
|
+
},
|
|
4727
|
+
{ code: 'policy(no-mutable-property)', filename: 'src/violations/types.ts' },
|
|
4728
|
+
{ code: 'eslint(no-restricted-imports)', filename: 'tests/setup.ts' },
|
|
4729
|
+
{ code: 'eslint(no-restricted-imports)', filename: 'tests/setupBrowser.ts' },
|
|
4730
|
+
{ code: 'eslint(no-restricted-imports)', filename: 'tests/setupServer.ts' },
|
|
4027
4731
|
{ code: 'policy(no-misplaced-type)', filename: 'src/violations/fixture.ts' },
|
|
4028
4732
|
{ code: 'policy(no-misplaced-data)', filename: 'src/violations/fixture.ts' },
|
|
4029
4733
|
{ code: 'policy(no-misplaced-function)', filename: 'src/violations/fixture.ts' },
|
|
@@ -4053,12 +4757,55 @@ createSomething({
|
|
|
4053
4757
|
`${code} ${normalizePolicyFilename(comparisonRoot, filename)}`,
|
|
4054
4758
|
)
|
|
4055
4759
|
}
|
|
4760
|
+
for (const { code, filename } of reassignments) {
|
|
4761
|
+
expect(violationCodes).toContain(
|
|
4762
|
+
`${code} ${normalizePolicyFilename(comparisonRoot, filename)}`,
|
|
4763
|
+
)
|
|
4764
|
+
}
|
|
4765
|
+
expect(violationCodes).toContain(
|
|
4766
|
+
`eslint(no-debugger) ${normalizePolicyFilename(comparisonRoot, 'src/violations/ambient.ts')}`,
|
|
4767
|
+
)
|
|
4768
|
+
expect(violationCodes).not.toContain(
|
|
4769
|
+
`policy(no-let) ${normalizePolicyFilename(comparisonRoot, 'src/violations/ambient.ts')}`,
|
|
4770
|
+
)
|
|
4056
4771
|
expect(violationCodes).toContain(
|
|
4057
4772
|
`eslint(no-debugger) ${normalizePolicyFilename(comparisonRoot, 'scripts/read.ts')}`,
|
|
4058
4773
|
)
|
|
4059
4774
|
expect(violationCodes).not.toContain(
|
|
4060
4775
|
`policy(no-host-line-endings) ${normalizePolicyFilename(comparisonRoot, 'scripts/read.ts')}`,
|
|
4061
4776
|
)
|
|
4777
|
+
expect(violationCodes).not.toContain(
|
|
4778
|
+
`eslint(no-restricted-imports) ${normalizePolicyFilename(comparisonRoot, '.agents/skills/sample/scripts/alone.ts')}`,
|
|
4779
|
+
)
|
|
4780
|
+
// The conforming script arrives, through the `debugger` statement inside its function, and
|
|
4781
|
+
// draws no script diagnostic; the inline parameter type beside the two mutable properties
|
|
4782
|
+
// draws none either, so the types file draws exactly two.
|
|
4783
|
+
expect(violationCodes).toContain(
|
|
4784
|
+
`eslint(no-debugger) ${normalizePolicyFilename(comparisonRoot, '.agents/skills/sample/scripts/conforming.ts')}`,
|
|
4785
|
+
)
|
|
4786
|
+
expect(violationCodes).not.toContain(
|
|
4787
|
+
`policy(no-malformed-script) ${normalizePolicyFilename(comparisonRoot, '.agents/skills/sample/scripts/conforming.ts')}`,
|
|
4788
|
+
)
|
|
4789
|
+
expect(
|
|
4790
|
+
violationCodes.filter(
|
|
4791
|
+
(code) =>
|
|
4792
|
+
code ===
|
|
4793
|
+
`policy(no-mutable-property) ${normalizePolicyFilename(comparisonRoot, 'src/violations/types.ts')}`,
|
|
4794
|
+
),
|
|
4795
|
+
).toHaveLength(2)
|
|
4796
|
+
// The fences replace only the restriction of the files they name: a setup module that
|
|
4797
|
+
// imports a Node module or a vitest helper the law admits, a browser-enabled helper module
|
|
4798
|
+
// the law admits, and a setup proof that registers tests all draw the root arrival
|
|
4799
|
+
// diagnostic and no import restriction.
|
|
4800
|
+
for (const filename of [
|
|
4801
|
+
'tests/setupPolicy.ts',
|
|
4802
|
+
'tests/setupStyles.ts',
|
|
4803
|
+
'tests/setup.test.ts',
|
|
4804
|
+
]) {
|
|
4805
|
+
const normalized = normalizePolicyFilename(comparisonRoot, filename)
|
|
4806
|
+
expect(violationCodes).toContain(`eslint(no-debugger) ${normalized}`)
|
|
4807
|
+
expect(violationCodes).not.toContain(`eslint(no-restricted-imports) ${normalized}`)
|
|
4808
|
+
}
|
|
4062
4809
|
expect(clean.status).toBe(0)
|
|
4063
4810
|
expect(cleanCodes).toHaveLength(0)
|
|
4064
4811
|
} finally {
|
|
@@ -4228,6 +4975,7 @@ describe('configuration helpers', () => {
|
|
|
4228
4975
|
'readBoundedFile',
|
|
4229
4976
|
'readHandleBytes',
|
|
4230
4977
|
'readCompilerOutput',
|
|
4978
|
+
'resolveCompiler',
|
|
4231
4979
|
'rewriteCoreSpecifier',
|
|
4232
4980
|
'sourceFallback',
|
|
4233
4981
|
'workspacePath',
|
|
@@ -4335,7 +5083,7 @@ describe('configuration helpers', () => {
|
|
|
4335
5083
|
const scratch = createPolicyScratch({ prefix: 'orkestrel-config-handle-' })
|
|
4336
5084
|
try {
|
|
4337
5085
|
// Each case is a file's text, the offset already read into the buffer, and the largest total allowed.
|
|
4338
|
-
const
|
|
5086
|
+
const results = [
|
|
4339
5087
|
{ text: 'abc', offset: 0, limit: 4 },
|
|
4340
5088
|
{ text: 'abcd', offset: 0, limit: 4 },
|
|
4341
5089
|
{ text: 'abcde', offset: 0, limit: 4 },
|
|
@@ -4355,8 +5103,8 @@ describe('configuration helpers', () => {
|
|
|
4355
5103
|
})
|
|
4356
5104
|
// A file at or under the limit answers its length; the shifted read adds the offset to the
|
|
4357
5105
|
// length and fills the buffer past it; a file over the limit answers nothing.
|
|
4358
|
-
expect(
|
|
4359
|
-
expect(
|
|
5106
|
+
expect(results.map(({ count }) => count)).toStrictEqual([3, 4, undefined, 0, 5, undefined])
|
|
5107
|
+
expect(results.map(({ filled }) => filled)).toStrictEqual(['abc', 'abcd', '', '', 'abc', ''])
|
|
4360
5108
|
} finally {
|
|
4361
5109
|
scratch.destroy()
|
|
4362
5110
|
}
|
|
@@ -4409,18 +5157,18 @@ describe('configuration helpers', () => {
|
|
|
4409
5157
|
logLevel: 'silent',
|
|
4410
5158
|
build: { outDir: 'out', lib: { entry, formats: ['es'] } },
|
|
4411
5159
|
}
|
|
4412
|
-
const
|
|
4413
|
-
|
|
4414
|
-
expect(
|
|
4415
|
-
expect(
|
|
4416
|
-
expect(
|
|
4417
|
-
expect(
|
|
5160
|
+
const buildState = new configHelpers.BuildState()
|
|
5161
|
+
buildState.resolve(await resolveConfig(inline, 'build'))
|
|
5162
|
+
expect(buildState.building).toBe(true)
|
|
5163
|
+
expect(buildState.output).toBe(resolve(scratch.path, 'out'))
|
|
5164
|
+
expect(buildState.root).toBe(realpathSync.native(scratch.path))
|
|
5165
|
+
expect(buildState.source).toBe(entry)
|
|
4418
5166
|
|
|
4419
5167
|
// The control: a serve command is not a build, and a config with no library entry has no source.
|
|
4420
|
-
const
|
|
4421
|
-
|
|
4422
|
-
expect(
|
|
4423
|
-
expect(
|
|
5168
|
+
const serveState = new configHelpers.BuildState()
|
|
5169
|
+
serveState.resolve(await resolveConfig({ ...inline, build: { outDir: 'out' } }, 'serve'))
|
|
5170
|
+
expect(serveState.building).toBe(false)
|
|
5171
|
+
expect(serveState.source).toBe('')
|
|
4424
5172
|
} finally {
|
|
4425
5173
|
scratch.destroy()
|
|
4426
5174
|
}
|
|
@@ -4615,6 +5363,65 @@ describe('configuration helpers', () => {
|
|
|
4615
5363
|
}
|
|
4616
5364
|
})
|
|
4617
5365
|
|
|
5366
|
+
it('resolves the installed compiler through bin.tsc when private package paths are unexported', () => {
|
|
5367
|
+
const compiler = configHelpers.resolveCompiler(import.meta.url)
|
|
5368
|
+
expect(configHelpers.readCompilerOutput(compiler, ['--version'])).toMatch(/^Version \d+\./u)
|
|
5369
|
+
const scratch = createPolicyScratch({ prefix: 'orkestrel-compiler-exports-' })
|
|
5370
|
+
try {
|
|
5371
|
+
const root = join(scratch.path, 'node_modules/typescript')
|
|
5372
|
+
scratch.write('package.json', '{"type":"module"}')
|
|
5373
|
+
scratch.write(
|
|
5374
|
+
'node_modules/typescript/package.json',
|
|
5375
|
+
JSON.stringify({
|
|
5376
|
+
name: 'typescript',
|
|
5377
|
+
exports: { './package.json': './package.json' },
|
|
5378
|
+
bin: { tsc: relative(root, compiler) },
|
|
5379
|
+
}),
|
|
5380
|
+
)
|
|
5381
|
+
const path = join(scratch.path, 'package.json')
|
|
5382
|
+
expect(() => createRequire(path).resolve('typescript/bin/tsc')).toThrow(
|
|
5383
|
+
'not defined by "exports"',
|
|
5384
|
+
)
|
|
5385
|
+
expect(configHelpers.resolveCompiler(path)).toBe(compiler)
|
|
5386
|
+
expect(
|
|
5387
|
+
configHelpers.readCompilerOutput(configHelpers.resolveCompiler(path), ['--version']),
|
|
5388
|
+
).toMatch(/^Version \d+\./u)
|
|
5389
|
+
scratch.write('consumer.ts', 'const value: string = 1\nconsole.log(value)\n')
|
|
5390
|
+
const refused = spawnSync(
|
|
5391
|
+
process.execPath,
|
|
5392
|
+
[compiler, '--noEmit', '--pretty', 'false', '--skipLibCheck', 'consumer.ts'],
|
|
5393
|
+
{ cwd: scratch.path, encoding: 'utf8', windowsHide: true },
|
|
5394
|
+
)
|
|
5395
|
+
expect(refused.status).toBeGreaterThan(0)
|
|
5396
|
+
expect(refused.stdout).toContain('error TS2322')
|
|
5397
|
+
scratch.write('consumer.ts', "const value: string = 'accepted'\nconsole.log(value)\n")
|
|
5398
|
+
const accepted = spawnSync(
|
|
5399
|
+
process.execPath,
|
|
5400
|
+
[compiler, '--noEmit', '--pretty', 'false', '--skipLibCheck', 'consumer.ts'],
|
|
5401
|
+
{ cwd: scratch.path, encoding: 'utf8', windowsHide: true },
|
|
5402
|
+
)
|
|
5403
|
+
expect(accepted.status, `${accepted.stdout}${accepted.stderr}`).toBe(0)
|
|
5404
|
+
} finally {
|
|
5405
|
+
scratch.destroy()
|
|
5406
|
+
}
|
|
5407
|
+
})
|
|
5408
|
+
|
|
5409
|
+
it.each([{}, { bin: null }, { bin: { tsc: 7 } }])(
|
|
5410
|
+
'refuses a compiler manifest without a bin.tsc command: %s',
|
|
5411
|
+
(declared) => {
|
|
5412
|
+
const scratch = createPolicyScratch({ prefix: 'orkestrel-compiler-manifest-' })
|
|
5413
|
+
try {
|
|
5414
|
+
scratch.write('package.json', '{"type":"module"}')
|
|
5415
|
+
scratch.write('node_modules/typescript/package.json', JSON.stringify(declared))
|
|
5416
|
+
expect(() => configHelpers.resolveCompiler(join(scratch.path, 'package.json'))).toThrow(
|
|
5417
|
+
'The TypeScript package declares no bin.tsc command',
|
|
5418
|
+
)
|
|
5419
|
+
} finally {
|
|
5420
|
+
scratch.destroy()
|
|
5421
|
+
}
|
|
5422
|
+
},
|
|
5423
|
+
)
|
|
5424
|
+
|
|
4618
5425
|
// The scope reading applies only where the workspace publishes source from `src`, which is what
|
|
4619
5426
|
// `publishes` reads, and the name ends at that mechanism. An absent face project is the defect
|
|
4620
5427
|
// this case exists to report rather than a second reason to excuse it, so a workspace holding
|
|
@@ -4626,7 +5433,7 @@ describe('configuration helpers', () => {
|
|
|
4626
5433
|
it.skipIf(!POLICY_PUBLISHES)(
|
|
4627
5434
|
'reads the compiler scope a declaration roll-up requires [inapplicable where src holds no recognized environment directory]',
|
|
4628
5435
|
() => {
|
|
4629
|
-
const compiler =
|
|
5436
|
+
const compiler = configHelpers.resolveCompiler(import.meta.url)
|
|
4630
5437
|
// The order mirrors ENVIRONMENTS in src/core/constants.ts; a server-only workspace vendors
|
|
4631
5438
|
// no core project, so this walks to the first face the workspace actually carries.
|
|
4632
5439
|
const faces = ['core', 'browser', 'server']
|
|
@@ -4686,7 +5493,7 @@ describe('configuration helpers', () => {
|
|
|
4686
5493
|
// reading a face project must not excuse it from the helpers behind the roll-up, which it
|
|
4687
5494
|
// vendors whether or not it publishes.
|
|
4688
5495
|
it('reads the refusals, guards, overrides, and rewrites a declaration roll-up requires from every workspace', () => {
|
|
4689
|
-
const compiler =
|
|
5496
|
+
const compiler = configHelpers.resolveCompiler(import.meta.url)
|
|
4690
5497
|
// Any project path resolves these, because each reading is refused by the text before the
|
|
4691
5498
|
// path is read. The root project is the one file every workspace carries.
|
|
4692
5499
|
const project = resolve(POLICY_ROOT, 'tsconfig.json')
|
|
@@ -4850,8 +5657,15 @@ describe('configuration helpers', () => {
|
|
|
4850
5657
|
const rewritten = join(workspace, 'rewritten')
|
|
4851
5658
|
const kept = join(workspace, 'kept')
|
|
4852
5659
|
const builds = [
|
|
4853
|
-
{
|
|
4854
|
-
|
|
5660
|
+
{
|
|
5661
|
+
output: rewritten,
|
|
5662
|
+
plugin: configHelpers.declarationRollup({
|
|
5663
|
+
project,
|
|
5664
|
+
rewrite: (text) =>
|
|
5665
|
+
configHelpers.rewriteBrowserSpecifier(configHelpers.rewriteCoreSpecifier(text)),
|
|
5666
|
+
}),
|
|
5667
|
+
},
|
|
5668
|
+
{ output: kept, plugin: configHelpers.declarationRollup({ project }) },
|
|
4855
5669
|
]
|
|
4856
5670
|
for (const face of builds) {
|
|
4857
5671
|
await build({
|
|
@@ -4859,19 +5673,7 @@ describe('configuration helpers', () => {
|
|
|
4859
5673
|
configFile: false,
|
|
4860
5674
|
logLevel: 'silent',
|
|
4861
5675
|
publicDir: false,
|
|
4862
|
-
plugins: [
|
|
4863
|
-
configHelpers.declarationRollup(
|
|
4864
|
-
face.rewrite
|
|
4865
|
-
? {
|
|
4866
|
-
project,
|
|
4867
|
-
rewrite: (text) =>
|
|
4868
|
-
configHelpers.rewriteBrowserSpecifier(
|
|
4869
|
-
configHelpers.rewriteCoreSpecifier(text),
|
|
4870
|
-
),
|
|
4871
|
-
}
|
|
4872
|
-
: { project },
|
|
4873
|
-
),
|
|
4874
|
-
],
|
|
5676
|
+
plugins: [face.plugin],
|
|
4875
5677
|
build: {
|
|
4876
5678
|
write: true,
|
|
4877
5679
|
outDir: face.output,
|
|
@@ -4886,12 +5688,12 @@ describe('configuration helpers', () => {
|
|
|
4886
5688
|
}
|
|
4887
5689
|
|
|
4888
5690
|
const idle = join(workspace, 'idle')
|
|
4889
|
-
const
|
|
5691
|
+
const plugins = configHelpers.declarationRollup({
|
|
4890
5692
|
project,
|
|
4891
5693
|
rewrite: configHelpers.rewriteCoreSpecifier,
|
|
4892
5694
|
})
|
|
4893
|
-
const configure =
|
|
4894
|
-
const close =
|
|
5695
|
+
const configure = plugins.configResolved
|
|
5696
|
+
const close = plugins.closeBundle
|
|
4895
5697
|
if (typeof configure !== 'function' || typeof close !== 'function') {
|
|
4896
5698
|
throw new Error('The declaration roll-up exposes no build hooks')
|
|
4897
5699
|
}
|
|
@@ -4938,28 +5740,7 @@ describe('configuration helpers', () => {
|
|
|
4938
5740
|
// list reads every entry first. Each case runs a policy reader over a recorded list and pins the
|
|
4939
5741
|
// outcome and the entries it read, as the loop the reader replaced gave them.
|
|
4940
5742
|
describe('policy readers consume a caller-supplied list one entry at a time', () => {
|
|
4941
|
-
const
|
|
4942
|
-
const declarator = (): PolicyExpression => ({
|
|
4943
|
-
type: 'VariableDeclarator',
|
|
4944
|
-
range: [0, 0],
|
|
4945
|
-
id: name,
|
|
4946
|
-
init: { type: 'ArrowFunctionExpression', range: [0, 0] },
|
|
4947
|
-
})
|
|
4948
|
-
const exported = (): PolicyExpression => ({
|
|
4949
|
-
type: 'ExportNamedDeclaration',
|
|
4950
|
-
range: [47, 63],
|
|
4951
|
-
declaration: { type: 'FunctionDeclaration', range: [0, 0], id: name },
|
|
4952
|
-
})
|
|
4953
|
-
const comment = (): PolicyComment => ({ type: 'Block', value: '* One.', range: [0, 11] })
|
|
4954
|
-
// A revoked proxy refuses every read, which fails the reader once it holds the entry.
|
|
4955
|
-
const refused =
|
|
4956
|
-
<T extends object>(entry: () => T) =>
|
|
4957
|
-
(): T => {
|
|
4958
|
-
const { proxy, revoke } = Proxy.revocable(entry(), {})
|
|
4959
|
-
revoke()
|
|
4960
|
-
return proxy
|
|
4961
|
-
}
|
|
4962
|
-
const throwing = [
|
|
5743
|
+
const failureCases = [
|
|
4963
5744
|
[POLICY_LAZY_SHAPES.hole, 'throw TypeError', []],
|
|
4964
5745
|
[POLICY_LAZY_SHAPES.holeFailure, 'throw TypeError', []],
|
|
4965
5746
|
[POLICY_LAZY_SHAPES.failureFirst, 'throw RangeError', [0]],
|
|
@@ -4975,11 +5756,11 @@ describe('policy readers consume a caller-supplied list one entry at a time', ()
|
|
|
4975
5756
|
statementToPolicyBindings({ type: 'VariableDeclaration', range: [0, 0], declarations }).map(
|
|
4976
5757
|
(binding) => binding.name,
|
|
4977
5758
|
),
|
|
4978
|
-
{ valid:
|
|
5759
|
+
{ valid: createPolicyDeclarator, refusal: createPolicyRefused(createPolicyDeclarator) },
|
|
4979
5760
|
[
|
|
4980
5761
|
[POLICY_LAZY_SHAPES.empty, 'return []', []],
|
|
4981
5762
|
[POLICY_LAZY_SHAPES.dense, 'return ["run"]', [0]],
|
|
4982
|
-
...
|
|
5763
|
+
...failureCases,
|
|
4983
5764
|
],
|
|
4984
5765
|
),
|
|
4985
5766
|
...createPolicyLazyCases(
|
|
@@ -4990,7 +5771,7 @@ describe('policy readers consume a caller-supplied list one entry at a time', ()
|
|
|
4990
5771
|
{
|
|
4991
5772
|
filename: '/ws/src/server/execution/run.ts',
|
|
4992
5773
|
cwd: '/ws',
|
|
4993
|
-
sourceCode: { text: '', getAllComments: () => [] },
|
|
5774
|
+
sourceCode: { text: '', getAllComments: () => [], getDeclaredVariables: () => [] },
|
|
4994
5775
|
report: (diagnostic) => {
|
|
4995
5776
|
reports.push(diagnostic.messageId)
|
|
4996
5777
|
},
|
|
@@ -4999,11 +5780,11 @@ describe('policy readers consume a caller-supplied list one entry at a time', ()
|
|
|
4999
5780
|
)
|
|
5000
5781
|
return reports
|
|
5001
5782
|
},
|
|
5002
|
-
{ valid:
|
|
5783
|
+
{ valid: createPolicyExport, refusal: createPolicyRefused(createPolicyExport) },
|
|
5003
5784
|
[
|
|
5004
5785
|
[POLICY_LAZY_SHAPES.empty, 'return ["module"]', []],
|
|
5005
5786
|
[POLICY_LAZY_SHAPES.dense, 'return []', [0]],
|
|
5006
|
-
...
|
|
5787
|
+
...failureCases,
|
|
5007
5788
|
],
|
|
5008
5789
|
),
|
|
5009
5790
|
...createPolicyLazyCases(
|
|
@@ -5011,27 +5792,30 @@ describe('policy readers consume a caller-supplied list one entry at a time', ()
|
|
|
5011
5792
|
(body: PolicyExpression[]) =>
|
|
5012
5793
|
programToPolicyDocs(
|
|
5013
5794
|
{ type: 'Program', range: [0, 0], body },
|
|
5014
|
-
{
|
|
5795
|
+
{
|
|
5796
|
+
text: '/** One. */\nexport function run() {}',
|
|
5797
|
+
getAllComments: () => [createPolicyComment()],
|
|
5798
|
+
},
|
|
5015
5799
|
).map((doc) => doc.name),
|
|
5016
|
-
{ valid:
|
|
5800
|
+
{ valid: createPolicyExport, refusal: createPolicyRefused(createPolicyExport) },
|
|
5017
5801
|
[
|
|
5018
5802
|
[POLICY_LAZY_SHAPES.empty, 'return []', []],
|
|
5019
5803
|
[POLICY_LAZY_SHAPES.dense, 'return []', [0]],
|
|
5020
|
-
...
|
|
5804
|
+
...failureCases,
|
|
5021
5805
|
],
|
|
5022
5806
|
),
|
|
5023
5807
|
...createPolicyLazyCases(
|
|
5024
5808
|
'programToPolicyDocs comments',
|
|
5025
5809
|
(comments: PolicyComment[]) =>
|
|
5026
5810
|
programToPolicyDocs(
|
|
5027
|
-
{ type: 'Program', range: [0, 0], body: [
|
|
5811
|
+
{ type: 'Program', range: [0, 0], body: [createPolicyExport()] },
|
|
5028
5812
|
{ text: '/** One. */\n', getAllComments: () => comments },
|
|
5029
5813
|
).map((doc) => doc.name),
|
|
5030
|
-
{ valid:
|
|
5814
|
+
{ valid: createPolicyComment, refusal: createPolicyRefused(createPolicyComment) },
|
|
5031
5815
|
[
|
|
5032
5816
|
[POLICY_LAZY_SHAPES.empty, 'return []', []],
|
|
5033
5817
|
[POLICY_LAZY_SHAPES.dense, 'return ["run"]', [0]],
|
|
5034
|
-
...
|
|
5818
|
+
...failureCases,
|
|
5035
5819
|
],
|
|
5036
5820
|
),
|
|
5037
5821
|
...createPolicyLazyCases(
|
|
@@ -5041,7 +5825,7 @@ describe('policy readers consume a caller-supplied list one entry at a time', ()
|
|
|
5041
5825
|
{ type: 'Program', range: [0, 0], body: [{ type: 'ImportDeclaration', range: [0, 0] }] },
|
|
5042
5826
|
{ text: 'import x', getAllComments: () => comments },
|
|
5043
5827
|
).map((doc) => doc.name),
|
|
5044
|
-
{ valid:
|
|
5828
|
+
{ valid: createPolicyComment, refusal: createPolicyRefused(createPolicyComment) },
|
|
5045
5829
|
[
|
|
5046
5830
|
[POLICY_LAZY_SHAPES.empty, 'return []', []],
|
|
5047
5831
|
[POLICY_LAZY_SHAPES.dense, 'return []', []],
|