agentic-workflow-manager 6.5.2 → 8.1.0
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/scripts/sensor-support-matrix.js +103 -0
- package/dist/scripts/support-matrix.js +40 -17
- package/dist/src/commands/init.js +1 -3
- package/dist/src/commands/ledger/index.js +6 -0
- package/dist/src/commands/preflight/checks.js +4 -4
- package/dist/src/commands/preflight/index.js +2 -2
- package/dist/src/commands/registry/add.js +2 -2
- package/dist/src/commands/registry/index.js +5 -0
- package/dist/src/commands/registry/status.js +7 -0
- package/dist/src/commands/sensors/compatibility/contract.js +382 -0
- package/dist/src/commands/sensors/compatibility/discovery.js +207 -0
- package/dist/src/commands/sensors/compatibility/live.js +92 -0
- package/dist/src/commands/sensors/compatibility/manifest.js +207 -0
- package/dist/src/commands/sensors/compatibility/materialize.js +134 -0
- package/dist/src/commands/sensors/compatibility/pack-source.js +78 -0
- package/dist/src/commands/sensors/compatibility/probe.js +47 -0
- package/dist/src/commands/sensors/compatibility/resolve.js +99 -0
- package/dist/src/commands/sensors/compatibility/types.js +2 -0
- package/dist/src/commands/sensors/coverage/contract.js +0 -50
- package/dist/src/commands/sensors/coverage/empirical.js +131 -0
- package/dist/src/commands/sensors/coverage/evaluate.js +85 -7
- package/dist/src/commands/sensors/coverage/evidence.js +5 -1
- package/dist/src/commands/sensors/coverage/index.js +44 -7
- package/dist/src/commands/sensors/coverage/render.js +214 -14
- package/dist/src/commands/sensors/coverage/resolve.js +24 -38
- package/dist/src/commands/sensors/exec.js +244 -9
- package/dist/src/commands/sensors/index.js +18 -6
- package/dist/src/commands/sensors/init.js +95 -6
- package/dist/src/commands/sensors/run.js +71 -0
- package/dist/src/commands/sensors/status.js +48 -4
- package/dist/src/commands/sync.js +7 -0
- package/dist/src/core/bundles.js +38 -3
- package/dist/src/core/init/steps.js +7 -4
- package/dist/src/core/ledger/scan.js +228 -0
- package/dist/src/core/ledger/store.js +89 -25
- package/dist/src/core/ledger/types.js +54 -0
- package/dist/src/core/paths.js +10 -9
- package/dist/src/core/registries.js +134 -14
- package/dist/src/index.js +24 -1
- package/dist/tests/commands/ledger/index.test.js +11 -0
- package/dist/tests/commands/preflight/preflight.test.js +64 -64
- package/dist/tests/commands/registry/add.test.js +69 -2
- package/dist/tests/commands/registry/status.test.js +14 -0
- package/dist/tests/commands/sensors/compatibility/contract.test.js +193 -0
- package/dist/tests/commands/sensors/compatibility/discovery.test.js +119 -0
- package/dist/tests/commands/sensors/compatibility/live.test.js +124 -0
- package/dist/tests/commands/sensors/compatibility/manifest.test.js +77 -0
- package/dist/tests/commands/sensors/compatibility/materialize.test.js +72 -0
- package/dist/tests/commands/sensors/compatibility/pack-source.test.js +55 -0
- package/dist/tests/commands/sensors/compatibility/probe.test.js +37 -0
- package/dist/tests/commands/sensors/compatibility/python-venv-fixture.js +29 -0
- package/dist/tests/commands/sensors/compatibility/resolve.test.js +83 -0
- package/dist/tests/commands/sensors/coverage/contract.test.js +8 -22
- package/dist/tests/commands/sensors/coverage/empirical.test.js +111 -0
- package/dist/tests/commands/sensors/coverage/evaluate.test.js +29 -0
- package/dist/tests/commands/sensors/coverage/evidence.test.js +9 -0
- package/dist/tests/commands/sensors/coverage/index.test.js +40 -13
- package/dist/tests/commands/sensors/coverage/render.test.js +187 -12
- package/dist/tests/commands/sensors/coverage/resolve.test.js +25 -4
- package/dist/tests/commands/sensors/exec-windows.test.js +140 -2
- package/dist/tests/commands/sensors/exec.test.js +115 -5
- package/dist/tests/commands/sensors/index.test.js +8 -0
- package/dist/tests/commands/sensors/init-pack-unavailable.test.js +12 -12
- package/dist/tests/commands/sensors/init.test.js +204 -48
- package/dist/tests/commands/sensors/run-inconclusive.test.js +9 -2
- package/dist/tests/commands/sensors/run-is-read-only.test.js +0 -0
- package/dist/tests/commands/sensors/run.test.js +66 -3
- package/dist/tests/commands/sensors/status-windows.test.js +7 -7
- package/dist/tests/commands/sensors/status.test.js +74 -23
- package/dist/tests/commands/update.test.js +1 -1
- package/dist/tests/core/bundles.test.js +37 -0
- package/dist/tests/core/discovery.test.js +4 -0
- package/dist/tests/core/init/orchestrator.test.js +5 -1
- package/dist/tests/core/init/steps.test.js +20 -4
- package/dist/tests/core/ledger/scan.test.js +217 -0
- package/dist/tests/core/ledger/store.test.js +108 -0
- package/dist/tests/core/profile-pins.test.js +2 -1
- package/dist/tests/core/project-skill-links.test.js +22 -0
- package/dist/tests/core/registries-capability.test.js +10 -0
- package/dist/tests/core/registries-sync.test.js +16 -0
- package/dist/tests/core/registries.test.js +129 -1
- package/dist/tests/core/registry-manifest.test.js +7 -0
- package/dist/tests/core/same-existing-path.test.js +35 -0
- package/dist/tests/core/sync-gates.test.js +2 -1
- package/dist/tests/integration/copilot-init-isolated.test.js +18 -0
- package/dist/tests/integration/sensor-compatibility.e2e.test.js +141 -0
- package/dist/tests/integration/sensor-coverage.e2e.test.js +35 -6
- package/dist/tests/integration/sensor-structured-run.e2e.test.js +127 -0
- package/dist/tests/structural/active-documentation.test.js +128 -0
- package/dist/tests/structural/async-entrypoint.test.js +15 -0
- package/dist/tests/structural/jest-environment-is-isolated.test.js +44 -0
- package/dist/tests/structural/r3-cli-major-version.test.js +37 -0
- package/dist/tests/structural/sensor-documentation-contract.test.js +77 -0
- package/dist/tests/structural/support-matrix-is-current.test.js +69 -2
- package/package.json +8 -2
|
@@ -52,6 +52,14 @@ describe('sensors coverage Commander wiring', () => {
|
|
|
52
52
|
expect(render_1.renderCoverageHuman).toHaveBeenCalledWith(report);
|
|
53
53
|
expect(stdoutWrite).toHaveBeenCalledWith('human\n');
|
|
54
54
|
});
|
|
55
|
+
it.each(['0', '-1', '1.5', '2x', 'Infinity', 'NaN', '9007199254740992'])('Commander rejects unsafe --min %s before coverage I/O (R5.5)', async (value) => {
|
|
56
|
+
await expect(programWithSensors().parseAsync(['node', 'awm', 'sensors', 'coverage', '--min', value])).rejects.toThrow('--min must be a positive safe integer');
|
|
57
|
+
expect(coverage_1.runCoverage).not.toHaveBeenCalled();
|
|
58
|
+
});
|
|
59
|
+
it('passes --min as a positive integer to coverage', async () => {
|
|
60
|
+
await programWithSensors().parseAsync(['node', 'awm', 'sensors', 'coverage', '--min', '3']);
|
|
61
|
+
expect(coverage_1.runCoverage).toHaveBeenCalledWith(process.cwd(), {}, { min: 3 });
|
|
62
|
+
});
|
|
55
63
|
it('emits JSON for --json and does not exit for gaps or inconclusive (R2.9)', async () => {
|
|
56
64
|
for (const overall of ['gaps', 'inconclusive']) {
|
|
57
65
|
coverage_1.runCoverage.mockReturnValue({ ...report, overall, static: { ...report.static, status: overall } });
|
|
@@ -47,49 +47,49 @@ describe('awm sensors init — detected pack missing from the registry', () => {
|
|
|
47
47
|
const manifestOf = (dir) => JSON.parse(fs_1.default.readFileSync(path_1.default.join(dir, '.awm', 'sensors.json'), 'utf-8'));
|
|
48
48
|
afterAll(() => { for (const d of made)
|
|
49
49
|
fs_1.default.rmSync(d, { recursive: true, force: true }); });
|
|
50
|
-
it('names the missing pack instead of writing an empty manifest silently', () => {
|
|
50
|
+
it('names the missing pack instead of writing an empty manifest silently', async () => {
|
|
51
51
|
const cwd = project();
|
|
52
|
-
const result = (0, init_1.initSensors)({ cwd, registryRoot: registry({ 'js-ts': {}, generic: GENERIC }) });
|
|
52
|
+
const result = await (0, init_1.initSensors)({ cwd, registryRoot: registry({ 'js-ts': {}, generic: GENERIC }) });
|
|
53
53
|
expect(result.unavailablePack).toBe('python');
|
|
54
54
|
expect(result.detection.pack).toBe('python'); // what the tree actually is — unchanged
|
|
55
55
|
});
|
|
56
|
-
it('falls back to a pack the registry does have, so the gate still measures something', () => {
|
|
56
|
+
it('falls back to a pack the registry does have, so the gate still measures something', async () => {
|
|
57
57
|
const cwd = project();
|
|
58
|
-
const result = (0, init_1.initSensors)({ cwd, registryRoot: registry({ 'js-ts': {}, generic: GENERIC }) });
|
|
58
|
+
const result = await (0, init_1.initSensors)({ cwd, registryRoot: registry({ 'js-ts': {}, generic: GENERIC }) });
|
|
59
59
|
expect(result.manifest.pack).toBe('generic');
|
|
60
60
|
expect(Object.keys(result.manifest.sensors)).toEqual(['security']);
|
|
61
61
|
expect(manifestOf(cwd).pack).toBe('generic');
|
|
62
62
|
});
|
|
63
|
-
it('stays on the detected pack when the registry has no fallback either', () => {
|
|
63
|
+
it('stays on the detected pack when the registry has no fallback either', async () => {
|
|
64
64
|
// Nothing better exists. The manifest is honestly empty and still says so —
|
|
65
65
|
// preflight's `manifest` check fails on `total === 0` with a registry remedy.
|
|
66
66
|
const cwd = project();
|
|
67
|
-
const result = (0, init_1.initSensors)({ cwd, registryRoot: registry({ 'js-ts': {} }) });
|
|
67
|
+
const result = await (0, init_1.initSensors)({ cwd, registryRoot: registry({ 'js-ts': {} }) });
|
|
68
68
|
expect(result.unavailablePack).toBe('python');
|
|
69
69
|
expect(result.manifest.pack).toBe('python');
|
|
70
70
|
expect(result.manifest.sensors).toEqual({});
|
|
71
71
|
});
|
|
72
|
-
it('is quiet when the registry does have the detected pack', () => {
|
|
72
|
+
it('is quiet when the registry does have the detected pack', async () => {
|
|
73
73
|
const cwd = project();
|
|
74
|
-
const result = (0, init_1.initSensors)({ cwd, registryRoot: registry({ python: PYTHON, generic: GENERIC }) });
|
|
74
|
+
const result = await (0, init_1.initSensors)({ cwd, registryRoot: registry({ python: PYTHON, generic: GENERIC }) });
|
|
75
75
|
expect(result.unavailablePack).toBeUndefined();
|
|
76
76
|
expect(result.manifest.pack).toBe('python');
|
|
77
77
|
expect(Object.keys(result.manifest.sensors)).toEqual(['typecheck']);
|
|
78
78
|
});
|
|
79
|
-
it('does not fall back when there is no registry to check against', () => {
|
|
79
|
+
it('does not fall back when there is no registry to check against', async () => {
|
|
80
80
|
// No registryRoot means nothing to validate against, the same tolerance
|
|
81
81
|
// `--pack` already has — not evidence that the pack is missing.
|
|
82
82
|
const cwd = project();
|
|
83
|
-
const result = (0, init_1.initSensors)({ cwd });
|
|
83
|
+
const result = await (0, init_1.initSensors)({ cwd });
|
|
84
84
|
expect(result.unavailablePack).toBeUndefined();
|
|
85
85
|
expect(result.manifest.pack).toBe('python');
|
|
86
86
|
});
|
|
87
|
-
it('keeps sensors the user already had when falling back', () => {
|
|
87
|
+
it('keeps sensors the user already had when falling back', async () => {
|
|
88
88
|
// The fallback must not be a way to lose hand-written configuration.
|
|
89
89
|
const cwd = project();
|
|
90
90
|
fs_1.default.mkdirSync(path_1.default.join(cwd, '.awm'), { recursive: true });
|
|
91
91
|
fs_1.default.writeFileSync(path_1.default.join(cwd, '.awm', 'sensors.json'), JSON.stringify({ pack: 'python', sensors: { test: { cmd: 'pytest -q', fast: false } } }));
|
|
92
|
-
const result = (0, init_1.initSensors)({ cwd, registryRoot: registry({ generic: GENERIC }) });
|
|
92
|
+
const result = await (0, init_1.initSensors)({ cwd, registryRoot: registry({ generic: GENERIC }) });
|
|
93
93
|
expect(result.manifest.sensors.test?.cmd).toBe('pytest -q');
|
|
94
94
|
expect(Object.keys(result.manifest.sensors).sort()).toEqual(['security', 'test']);
|
|
95
95
|
});
|
|
@@ -7,6 +7,7 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
7
7
|
const path_1 = __importDefault(require("path"));
|
|
8
8
|
const os_1 = __importDefault(require("os"));
|
|
9
9
|
const init_1 = require("../../../src/commands/sensors/init");
|
|
10
|
+
const status_1 = require("../../../src/commands/sensors/status");
|
|
10
11
|
// Build a throwaway registry with a js-ts pack.json (the single source of truth
|
|
11
12
|
// init now reads from). `defaultCmd` uses the {{SOURCE_DIRS}} placeholder for depcheck.
|
|
12
13
|
function makeRegistry() {
|
|
@@ -40,69 +41,105 @@ function makePythonRegistry() {
|
|
|
40
41
|
}));
|
|
41
42
|
return registryRoot;
|
|
42
43
|
}
|
|
44
|
+
function makeV2Registry() {
|
|
45
|
+
const registryRoot = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-reg-v2-'));
|
|
46
|
+
const packDir = path_1.default.join(registryRoot, 'sensor-packs', 'js-ts');
|
|
47
|
+
fs_1.default.mkdirSync(packDir, { recursive: true });
|
|
48
|
+
fs_1.default.writeFileSync(path_1.default.join(packDir, 'eslint.config.awm.mjs'), 'export default []\n');
|
|
49
|
+
fs_1.default.writeFileSync(path_1.default.join(packDir, 'tsconfig.awm.json'), '{ "compilerOptions": { "strict": true } }\n');
|
|
50
|
+
fs_1.default.writeFileSync(path_1.default.join(packDir, 'pack.json'), JSON.stringify({
|
|
51
|
+
schemaVersion: 2, name: 'js-ts', description: 'fixture', detects: ['package.json'],
|
|
52
|
+
coverage: { schemaVersion: 1, classes: { lint: { description: 'lint', detectors: [{ sensor: 'lint' }], remedy: { summary: 'fix lint', command: 'awm sensors init --pack js-ts' } } } },
|
|
53
|
+
hardening: { 'typescript-strict': { assets: ['tsconfig.awm.json'] } },
|
|
54
|
+
sensors: { lint: { applicability: { allFiles: ['package.json'] }, variants: [{
|
|
55
|
+
id: 'eslint-10', priority: 10, certifiedRange: '>=10.0.0 <11.0.0',
|
|
56
|
+
requirements: { tool: 'eslint', toolRange: '>=10.0.0 <11.0.0', runtime: 'node', runtimeRange: '>=0.0.0' },
|
|
57
|
+
assets: ['eslint.config.awm.mjs'], formatter: 'eslint-llm', probe: { kind: 'config-present' },
|
|
58
|
+
command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] },
|
|
59
|
+
}] } },
|
|
60
|
+
}));
|
|
61
|
+
return registryRoot;
|
|
62
|
+
}
|
|
63
|
+
function makeGenericV2Registry() {
|
|
64
|
+
const registryRoot = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-reg-generic-v2-'));
|
|
65
|
+
const packDir = path_1.default.join(registryRoot, 'sensor-packs', 'generic');
|
|
66
|
+
fs_1.default.mkdirSync(packDir, { recursive: true });
|
|
67
|
+
fs_1.default.writeFileSync(path_1.default.join(packDir, 'generic.config'), 'fixture\n');
|
|
68
|
+
fs_1.default.writeFileSync(path_1.default.join(packDir, 'pack.json'), JSON.stringify({
|
|
69
|
+
schemaVersion: 2, name: 'generic', description: 'fixture', detects: ['README.md'],
|
|
70
|
+
coverage: { schemaVersion: 1, classes: { security: { description: 'security', detectors: [{ sensor: 'security' }], remedy: { summary: 'run security', command: 'awm sensors init --pack generic' } } } },
|
|
71
|
+
sensors: { security: { applicability: { kind: 'explicit-or-supported-language' }, variants: [{
|
|
72
|
+
id: 'eslint-10', priority: 10, certifiedRange: '>=10.0.0 <11.0.0',
|
|
73
|
+
requirements: { tool: 'eslint', toolRange: '>=10.0.0 <11.0.0', runtime: 'node', runtimeRange: '>=0.0.0', configFiles: ['generic.config'] },
|
|
74
|
+
assets: ['generic.config'], formatter: 'eslint-llm', probe: { kind: 'config-present' },
|
|
75
|
+
command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] },
|
|
76
|
+
}] } },
|
|
77
|
+
}));
|
|
78
|
+
return registryRoot;
|
|
79
|
+
}
|
|
43
80
|
describe('detectStack', () => {
|
|
44
81
|
let tmpDir;
|
|
45
82
|
beforeEach(() => { tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-init-')); });
|
|
46
83
|
afterEach(() => { fs_1.default.rmSync(tmpDir, { recursive: true }); });
|
|
47
|
-
it('detects js-ts when package.json exists', () => {
|
|
84
|
+
it('detects js-ts when package.json exists', async () => {
|
|
48
85
|
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), '{}');
|
|
49
86
|
expect((0, init_1.detectStack)(tmpDir).pack).toBe('js-ts');
|
|
50
87
|
});
|
|
51
|
-
it('detects python when pyproject.toml exists', () => {
|
|
88
|
+
it('detects python when pyproject.toml exists', async () => {
|
|
52
89
|
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'pyproject.toml'), '');
|
|
53
90
|
expect((0, init_1.detectStack)(tmpDir).pack).toBe('python');
|
|
54
91
|
});
|
|
55
|
-
it('falls back to generic when no indicators found', () => {
|
|
92
|
+
it('falls back to generic when no indicators found', async () => {
|
|
56
93
|
expect((0, init_1.detectStack)(tmpDir).pack).toBe('generic');
|
|
57
94
|
});
|
|
58
|
-
it('detects shell from a root-level *.sh file when no js-ts/python marker exists', () => {
|
|
95
|
+
it('detects shell from a root-level *.sh file when no js-ts/python marker exists', async () => {
|
|
59
96
|
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'deploy.sh'), '#!/bin/sh\n');
|
|
60
97
|
const result = (0, init_1.detectStack)(tmpDir);
|
|
61
98
|
expect(result.pack).toBe('shell');
|
|
62
99
|
expect(result.indicators).toEqual(['deploy.sh']);
|
|
63
100
|
});
|
|
64
|
-
it('detects shell from a scripts/*.sh file when root has nothing', () => {
|
|
101
|
+
it('detects shell from a scripts/*.sh file when root has nothing', async () => {
|
|
65
102
|
fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'scripts'));
|
|
66
103
|
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'scripts', 'build.sh'), '#!/bin/sh\n');
|
|
67
104
|
const result = (0, init_1.detectStack)(tmpDir);
|
|
68
105
|
expect(result.pack).toBe('shell');
|
|
69
106
|
expect(result.indicators).toEqual([path_1.default.join('scripts', 'build.sh')]);
|
|
70
107
|
});
|
|
71
|
-
it('js-ts wins over shell when both package.json and a root .sh file exist', () => {
|
|
108
|
+
it('js-ts wins over shell when both package.json and a root .sh file exist', async () => {
|
|
72
109
|
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), '{}');
|
|
73
110
|
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'deploy.sh'), '#!/bin/sh\n');
|
|
74
111
|
expect((0, init_1.detectStack)(tmpDir).pack).toBe('js-ts');
|
|
75
112
|
});
|
|
76
|
-
it('python wins over shell when both a python marker and a root .sh file exist', () => {
|
|
113
|
+
it('python wins over shell when both a python marker and a root .sh file exist', async () => {
|
|
77
114
|
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'pyproject.toml'), '');
|
|
78
115
|
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'deploy.sh'), '#!/bin/sh\n');
|
|
79
116
|
expect((0, init_1.detectStack)(tmpDir).pack).toBe('python');
|
|
80
117
|
});
|
|
81
|
-
it('falls through to generic when scripts/ has only non-.sh files (glob must not over-match)', () => {
|
|
118
|
+
it('falls through to generic when scripts/ has only non-.sh files (glob must not over-match)', async () => {
|
|
82
119
|
fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'scripts'));
|
|
83
120
|
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'scripts', 'notes.txt'), 'not shell');
|
|
84
121
|
expect((0, init_1.detectStack)(tmpDir).pack).toBe('generic');
|
|
85
122
|
});
|
|
86
|
-
it('detects python from requirements.txt alone', () => {
|
|
123
|
+
it('detects python from requirements.txt alone', async () => {
|
|
87
124
|
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'requirements.txt'), '');
|
|
88
125
|
expect((0, init_1.detectStack)(tmpDir).pack).toBe('python');
|
|
89
126
|
});
|
|
90
|
-
it('detects python from Pipfile alone', () => {
|
|
127
|
+
it('detects python from Pipfile alone', async () => {
|
|
91
128
|
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'Pipfile'), '');
|
|
92
129
|
expect((0, init_1.detectStack)(tmpDir).pack).toBe('python');
|
|
93
130
|
});
|
|
94
|
-
it('python (via Pipfile) wins over shell when both a Pipfile and a root .sh file exist', () => {
|
|
131
|
+
it('python (via Pipfile) wins over shell when both a Pipfile and a root .sh file exist', async () => {
|
|
95
132
|
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'Pipfile'), '');
|
|
96
133
|
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'deploy.sh'), '#!/bin/sh\n');
|
|
97
134
|
expect((0, init_1.detectStack)(tmpDir).pack).toBe('python');
|
|
98
135
|
});
|
|
99
|
-
it('does not report a directory named "*.sh" as a shell indicator', () => {
|
|
136
|
+
it('does not report a directory named "*.sh" as a shell indicator', async () => {
|
|
100
137
|
// Directory literally named `something.sh` (not a file) — findShellIndicators'
|
|
101
138
|
// `entry.isFile()` guard must exclude it. Nothing else present → generic.
|
|
102
139
|
fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'something.sh'));
|
|
103
140
|
expect((0, init_1.detectStack)(tmpDir).pack).toBe('generic');
|
|
104
141
|
});
|
|
105
|
-
it('ignores a directory named "*.sh" but still finds a real .sh file alongside it', () => {
|
|
142
|
+
it('ignores a directory named "*.sh" but still finds a real .sh file alongside it', async () => {
|
|
106
143
|
fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'notreal.sh'));
|
|
107
144
|
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'deploy.sh'), '#!/bin/sh\n');
|
|
108
145
|
const result = (0, init_1.detectStack)(tmpDir);
|
|
@@ -114,16 +151,16 @@ describe('detectSourceDirs', () => {
|
|
|
114
151
|
let tmpDir;
|
|
115
152
|
beforeEach(() => { tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-src-')); });
|
|
116
153
|
afterEach(() => { fs_1.default.rmSync(tmpDir, { recursive: true }); });
|
|
117
|
-
it('returns the App-Router-style dirs that exist', () => {
|
|
154
|
+
it('returns the App-Router-style dirs that exist', async () => {
|
|
118
155
|
for (const d of ['app', 'lib', 'components'])
|
|
119
156
|
fs_1.default.mkdirSync(path_1.default.join(tmpDir, d));
|
|
120
157
|
expect((0, init_1.detectSourceDirs)(tmpDir)).toEqual(['app', 'lib', 'components']);
|
|
121
158
|
});
|
|
122
|
-
it('returns src when it exists', () => {
|
|
159
|
+
it('returns src when it exists', async () => {
|
|
123
160
|
fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'src'));
|
|
124
161
|
expect((0, init_1.detectSourceDirs)(tmpDir)).toEqual(['src']);
|
|
125
162
|
});
|
|
126
|
-
it('falls back to ["src"] when no known source dir exists', () => {
|
|
163
|
+
it('falls back to ["src"] when no known source dir exists', async () => {
|
|
127
164
|
expect((0, init_1.detectSourceDirs)(tmpDir)).toEqual(['src']);
|
|
128
165
|
});
|
|
129
166
|
});
|
|
@@ -138,26 +175,26 @@ describe('buildManifest', () => {
|
|
|
138
175
|
fs_1.default.rmSync(registryRoot, { recursive: true });
|
|
139
176
|
fs_1.default.rmSync(cwd, { recursive: true });
|
|
140
177
|
});
|
|
141
|
-
it('builds manifest from the pack.json single source', () => {
|
|
178
|
+
it('builds manifest from the pack.json single source', async () => {
|
|
142
179
|
const m = (0, init_1.buildManifest)('js-ts', undefined, registryRoot, cwd);
|
|
143
180
|
expect(m.pack).toBe('js-ts');
|
|
144
181
|
expect(m.sensors.typecheck.cmd).toBe('npx tsc --noEmit');
|
|
145
182
|
expect(m.sensors.lint.cmd).toContain('--config eslint.config.awm.mjs');
|
|
146
183
|
});
|
|
147
|
-
it('substitutes {{SOURCE_DIRS}} in depcheck with the detected dirs', () => {
|
|
184
|
+
it('substitutes {{SOURCE_DIRS}} in depcheck with the detected dirs', async () => {
|
|
148
185
|
for (const d of ['app', 'lib'])
|
|
149
186
|
fs_1.default.mkdirSync(path_1.default.join(cwd, d));
|
|
150
187
|
const m = (0, init_1.buildManifest)('js-ts', undefined, registryRoot, cwd);
|
|
151
188
|
expect(m.sensors.depcheck.cmd).toBe('npx depcruise --config .dep-cruiser.awm.js app lib');
|
|
152
189
|
expect(m.sensors.depcheck.cmd).not.toContain('{{SOURCE_DIRS}}');
|
|
153
190
|
});
|
|
154
|
-
it('merges conservatively — existing sensor commands are preserved', () => {
|
|
191
|
+
it('merges conservatively — existing sensor commands are preserved', async () => {
|
|
155
192
|
const existing = { pack: 'js-ts', sensors: { typecheck: { cmd: 'custom-tsc', fast: true } } };
|
|
156
193
|
const m = (0, init_1.buildManifest)('js-ts', existing, registryRoot, cwd);
|
|
157
194
|
expect(m.sensors.typecheck.cmd).toBe('custom-tsc');
|
|
158
195
|
expect(m.sensors.lint).toBeDefined();
|
|
159
196
|
});
|
|
160
|
-
it('carries the formatter field through from pack.json into the built manifest', () => {
|
|
197
|
+
it('carries the formatter field through from pack.json into the built manifest', async () => {
|
|
161
198
|
// readPackDefaults must copy `formatter` the same way it already copies
|
|
162
199
|
// `changedCmd`/`changedExtensions` — this is what lets run.ts's getFormatter
|
|
163
200
|
// dispatch by real tool (ruff/mypy/shellcheck) instead of guessing from the
|
|
@@ -167,14 +204,14 @@ describe('buildManifest', () => {
|
|
|
167
204
|
expect(m.sensors.typecheck.formatter).toBe('tsc');
|
|
168
205
|
expect(m.sensors.lint.formatter).toBe('eslint-llm');
|
|
169
206
|
});
|
|
170
|
-
it('returns an empty sensors object when the pack has no pack.json in the registry', () => {
|
|
207
|
+
it('returns an empty sensors object when the pack has no pack.json in the registry', async () => {
|
|
171
208
|
// No FALLBACK_DEFAULTS anymore: `python` has no pack dir in this fixture
|
|
172
209
|
// registry (only js-ts does — see makeRegistry) → the honest floor is `{}`,
|
|
173
210
|
// never CLI-hardcoded commands that can drift from what the registry ships.
|
|
174
211
|
const m = (0, init_1.buildManifest)('python', undefined, registryRoot, cwd);
|
|
175
212
|
expect(m.sensors).toEqual({});
|
|
176
213
|
});
|
|
177
|
-
it('per-field merge: an existing sensor missing a newer pack field still inherits it', () => {
|
|
214
|
+
it('per-field merge: an existing sensor missing a newer pack field still inherits it', async () => {
|
|
178
215
|
// Regression for Finding 1: a manifest written by the old FALLBACK_DEFAULTS-era
|
|
179
216
|
// CLI has `typecheck: { cmd: 'mypy .', fast: true }` — no `formatter`, because
|
|
180
217
|
// that field didn't exist yet. A naive `{ ...defaults, ...existingSensors }`
|
|
@@ -209,37 +246,163 @@ describe('initSensors', () => {
|
|
|
209
246
|
fs_1.default.rmSync(tmpDir, { recursive: true });
|
|
210
247
|
fs_1.default.rmSync(registryRoot, { recursive: true });
|
|
211
248
|
});
|
|
212
|
-
it('creates .awm/sensors.json for js-ts project', () => {
|
|
249
|
+
it('creates .awm/sensors.json for js-ts project', async () => {
|
|
213
250
|
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), '{}');
|
|
214
|
-
const result = (0, init_1.initSensors)({ cwd: tmpDir, registryRoot });
|
|
251
|
+
const result = await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot });
|
|
215
252
|
expect(fs_1.default.existsSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'))).toBe(true);
|
|
216
253
|
expect(result.detection.pack).toBe('js-ts');
|
|
217
254
|
expect(result.manifest.sensors.typecheck).toBeDefined();
|
|
218
255
|
});
|
|
219
|
-
it('is idempotent — existing sensor commands survive re-init', () => {
|
|
256
|
+
it('is idempotent — existing sensor commands survive re-init', async () => {
|
|
220
257
|
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), '{}');
|
|
221
258
|
fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.awm'), { recursive: true });
|
|
222
259
|
const existing = { pack: 'js-ts', sensors: { typecheck: { cmd: 'my-tsc', fast: true } } };
|
|
223
260
|
fs_1.default.writeFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), JSON.stringify(existing));
|
|
224
|
-
(0, init_1.initSensors)({ cwd: tmpDir, registryRoot });
|
|
261
|
+
await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot });
|
|
225
262
|
const written = JSON.parse(fs_1.default.readFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), 'utf-8'));
|
|
226
263
|
expect(written.sensors.typecheck.cmd).toBe('my-tsc');
|
|
227
264
|
expect(written.sensors.lint).toBeDefined(); // new sensor added
|
|
228
265
|
});
|
|
229
|
-
it('copies pack config files into the repo by default (configure on)', () => {
|
|
266
|
+
it('copies pack config files into the repo by default (configure on)', async () => {
|
|
230
267
|
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), '{}');
|
|
231
268
|
fs_1.default.writeFileSync(path_1.default.join(registryRoot, 'sensor-packs', 'js-ts', 'tsconfig.awm.json'), '{}');
|
|
232
|
-
const result = (0, init_1.initSensors)({ cwd: tmpDir, registryRoot }); // no explicit configure → default true
|
|
269
|
+
const result = await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot }); // no explicit configure → default true
|
|
233
270
|
expect(result.configured).toContain('tsconfig.awm.json');
|
|
234
271
|
expect(fs_1.default.existsSync(path_1.default.join(tmpDir, 'tsconfig.awm.json'))).toBe(true);
|
|
235
272
|
});
|
|
236
|
-
it('does NOT copy config files when configure is false', () => {
|
|
273
|
+
it('does NOT copy config files when configure is false', async () => {
|
|
237
274
|
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), '{}');
|
|
238
275
|
fs_1.default.writeFileSync(path_1.default.join(registryRoot, 'sensor-packs', 'js-ts', 'tsconfig.awm.json'), '{}');
|
|
239
|
-
const result = (0, init_1.initSensors)({ cwd: tmpDir, registryRoot, configure: false });
|
|
276
|
+
const result = await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot, configure: false });
|
|
240
277
|
expect(result.configured).toEqual([]);
|
|
241
278
|
expect(fs_1.default.existsSync(path_1.default.join(tmpDir, 'tsconfig.awm.json'))).toBe(false);
|
|
242
279
|
});
|
|
280
|
+
it('awaits v2 compatibility probes and persists their materialized evidence', async () => {
|
|
281
|
+
const v2Registry = makeV2Registry();
|
|
282
|
+
try {
|
|
283
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), JSON.stringify({ devDependencies: { eslint: '^10.0.0' } }));
|
|
284
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'node_modules', 'eslint'), { recursive: true });
|
|
285
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'node_modules', 'eslint', 'package.json'), JSON.stringify({ version: '10.0.0' }));
|
|
286
|
+
const result = await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot: v2Registry });
|
|
287
|
+
const written = JSON.parse(fs_1.default.readFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), 'utf8'));
|
|
288
|
+
expect(result.manifest).toMatchObject({ schemaVersion: 2, sensors: { lint: { variantId: 'eslint-10' } } });
|
|
289
|
+
// `package.json` is a declared detection file, so config-present matches.
|
|
290
|
+
// Without init awaiting the probe the resolver stays `unverifiable`; this
|
|
291
|
+
// exact assertion therefore distinguishes the async probe integration.
|
|
292
|
+
expect(written.sensors.lint.initializedCompatibility).toMatchObject({ variantId: 'eslint-10', state: 'certified', reason: 'range-and-probe' });
|
|
293
|
+
expect(written.registryRoot).toBe(v2Registry);
|
|
294
|
+
expect(fs_1.default.existsSync(path_1.default.join(tmpDir, 'eslint.config.awm.mjs'))).toBe(true);
|
|
295
|
+
}
|
|
296
|
+
finally {
|
|
297
|
+
fs_1.default.rmSync(v2Registry, { recursive: true, force: true });
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
it('persists an explicit generic selection for live revalidation without promoting automatic generic fallback', async () => {
|
|
301
|
+
const v2Registry = makeGenericV2Registry();
|
|
302
|
+
try {
|
|
303
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'node_modules', 'eslint'), { recursive: true });
|
|
304
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'node_modules', 'eslint', 'package.json'), JSON.stringify({ version: '10.0.0' }));
|
|
305
|
+
const automatic = await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot: v2Registry, configure: false });
|
|
306
|
+
expect(automatic.manifest).toMatchObject({ schemaVersion: 2, pack: 'generic', sensors: {} });
|
|
307
|
+
expect(automatic.manifest.packSelection).toBeUndefined();
|
|
308
|
+
const explicit = await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot: v2Registry, pack: 'generic' });
|
|
309
|
+
const written = JSON.parse(fs_1.default.readFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), 'utf8'));
|
|
310
|
+
expect(explicit.manifest).toMatchObject({ schemaVersion: 2, pack: 'generic', packSelection: 'explicit', sensors: { security: { variantId: 'eslint-10' } } });
|
|
311
|
+
expect(written.packSelection).toBe('explicit');
|
|
312
|
+
await expect((0, status_1.computeSensorStatus)(tmpDir)).resolves.toMatchObject({ overall: 'HEALTHY', checks: { security: { ok: true } } });
|
|
313
|
+
}
|
|
314
|
+
finally {
|
|
315
|
+
fs_1.default.rmSync(v2Registry, { recursive: true, force: true });
|
|
316
|
+
}
|
|
317
|
+
});
|
|
318
|
+
it('never materializes an opt-in hardening asset during ordinary v2 init', async () => {
|
|
319
|
+
const v2Registry = makeV2Registry();
|
|
320
|
+
try {
|
|
321
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), JSON.stringify({ devDependencies: { eslint: '^10.0.0' } }));
|
|
322
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'node_modules', 'eslint'), { recursive: true });
|
|
323
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'node_modules', 'eslint', 'package.json'), JSON.stringify({ version: '10.0.0' }));
|
|
324
|
+
await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot: v2Registry });
|
|
325
|
+
expect(fs_1.default.existsSync(path_1.default.join(tmpDir, 'eslint.config.awm.mjs'))).toBe(true);
|
|
326
|
+
expect(fs_1.default.existsSync(path_1.default.join(tmpDir, 'tsconfig.awm.json'))).toBe(false);
|
|
327
|
+
}
|
|
328
|
+
finally {
|
|
329
|
+
fs_1.default.rmSync(v2Registry, { recursive: true, force: true });
|
|
330
|
+
}
|
|
331
|
+
});
|
|
332
|
+
it('initializes a selected v2 variant with an explicitly empty assets list', async () => {
|
|
333
|
+
const v2Registry = makeV2Registry();
|
|
334
|
+
try {
|
|
335
|
+
const packPath = path_1.default.join(v2Registry, 'sensor-packs', 'js-ts', 'pack.json');
|
|
336
|
+
const pack = JSON.parse(fs_1.default.readFileSync(packPath, 'utf8'));
|
|
337
|
+
pack.sensors.lint.variants[0].assets = [];
|
|
338
|
+
fs_1.default.writeFileSync(packPath, JSON.stringify(pack));
|
|
339
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), JSON.stringify({ devDependencies: { eslint: '^10.0.0' } }));
|
|
340
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'node_modules', 'eslint'), { recursive: true });
|
|
341
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'node_modules', 'eslint', 'package.json'), JSON.stringify({ version: '10.0.0' }));
|
|
342
|
+
await expect((0, init_1.initSensors)({ cwd: tmpDir, registryRoot: v2Registry })).resolves.toMatchObject({
|
|
343
|
+
manifest: { schemaVersion: 2, sensors: { lint: { assets: [] } } },
|
|
344
|
+
configured: [],
|
|
345
|
+
});
|
|
346
|
+
expect(fs_1.default.existsSync(path_1.default.join(tmpDir, 'eslint.config.awm.mjs'))).toBe(false);
|
|
347
|
+
}
|
|
348
|
+
finally {
|
|
349
|
+
fs_1.default.rmSync(v2Registry, { recursive: true, force: true });
|
|
350
|
+
}
|
|
351
|
+
});
|
|
352
|
+
it('preserves enabled and fast choices when a valid v2 manifest is re-initialized', async () => {
|
|
353
|
+
const v2Registry = makeV2Registry();
|
|
354
|
+
try {
|
|
355
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), JSON.stringify({ devDependencies: { eslint: '^10.0.0' } }));
|
|
356
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'node_modules', 'eslint'), { recursive: true });
|
|
357
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'node_modules', 'eslint', 'package.json'), JSON.stringify({ version: '10.0.0' }));
|
|
358
|
+
await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot: v2Registry });
|
|
359
|
+
const manifestPath = path_1.default.join(tmpDir, '.awm', 'sensors.json');
|
|
360
|
+
const selected = JSON.parse(fs_1.default.readFileSync(manifestPath, 'utf8'));
|
|
361
|
+
selected.sensors.lint.enabled = false;
|
|
362
|
+
selected.sensors.lint.fast = true;
|
|
363
|
+
fs_1.default.writeFileSync(manifestPath, JSON.stringify(selected));
|
|
364
|
+
await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot: v2Registry });
|
|
365
|
+
const written = JSON.parse(fs_1.default.readFileSync(manifestPath, 'utf8'));
|
|
366
|
+
expect(written.sensors.lint).toMatchObject({ enabled: false, fast: true, variantId: 'eslint-10' });
|
|
367
|
+
}
|
|
368
|
+
finally {
|
|
369
|
+
fs_1.default.rmSync(v2Registry, { recursive: true, force: true });
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
it('rejects a symlinked v2 pack source instead of reading it through init', async () => {
|
|
373
|
+
const v2Registry = makeV2Registry();
|
|
374
|
+
try {
|
|
375
|
+
const packPath = path_1.default.join(v2Registry, 'sensor-packs', 'js-ts', 'pack.json');
|
|
376
|
+
const outside = path_1.default.join(v2Registry, 'outside-pack.json');
|
|
377
|
+
fs_1.default.renameSync(packPath, outside);
|
|
378
|
+
fs_1.default.symlinkSync(outside, packPath);
|
|
379
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), '{}');
|
|
380
|
+
await expect((0, init_1.initSensors)({ cwd: tmpDir, registryRoot: v2Registry })).rejects.toThrow(/symbolic|regular|contain/i);
|
|
381
|
+
expect(fs_1.default.existsSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'))).toBe(false);
|
|
382
|
+
}
|
|
383
|
+
finally {
|
|
384
|
+
fs_1.default.rmSync(v2Registry, { recursive: true, force: true });
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
it('migrates legacy overrides explicitly without silently certifying or re-enabling them', async () => {
|
|
388
|
+
const v2Registry = makeV2Registry();
|
|
389
|
+
try {
|
|
390
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), JSON.stringify({ devDependencies: { eslint: '^10.0.0' } }));
|
|
391
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'node_modules', 'eslint'), { recursive: true });
|
|
392
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'node_modules', 'eslint', 'package.json'), JSON.stringify({ version: '10.0.0' }));
|
|
393
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.awm'));
|
|
394
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), JSON.stringify({
|
|
395
|
+
pack: 'js-ts', sensors: { lint: { cmd: 'my-company-eslint .', enabled: false, fast: true } },
|
|
396
|
+
}));
|
|
397
|
+
await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot: v2Registry });
|
|
398
|
+
const written = JSON.parse(fs_1.default.readFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), 'utf8'));
|
|
399
|
+
expect(written.sensors.lint).toMatchObject({ enabled: false, fast: true });
|
|
400
|
+
expect(written.sensors.lint.initializedCompatibility).toMatchObject({ state: 'compatible-unverified', reason: expect.stringContaining('legacy custom command') });
|
|
401
|
+
}
|
|
402
|
+
finally {
|
|
403
|
+
fs_1.default.rmSync(v2Registry, { recursive: true, force: true });
|
|
404
|
+
}
|
|
405
|
+
});
|
|
243
406
|
});
|
|
244
407
|
describe('initSensors — --pack override', () => {
|
|
245
408
|
let tmpDir;
|
|
@@ -252,38 +415,31 @@ describe('initSensors — --pack override', () => {
|
|
|
252
415
|
fs_1.default.rmSync(tmpDir, { recursive: true });
|
|
253
416
|
fs_1.default.rmSync(registryRoot, { recursive: true });
|
|
254
417
|
});
|
|
255
|
-
it('skips detection and uses the override pack when it exists in the registry', () => {
|
|
418
|
+
it('skips detection and uses the override pack when it exists in the registry', async () => {
|
|
256
419
|
// No package.json/pyproject.toml here — if detection ran, this would be 'generic'.
|
|
257
|
-
const result = (0, init_1.initSensors)({ pack: 'js-ts', registryRoot, cwd: tmpDir });
|
|
420
|
+
const result = await (0, init_1.initSensors)({ pack: 'js-ts', registryRoot, cwd: tmpDir });
|
|
258
421
|
expect(result.detection.pack).toBe('js-ts');
|
|
259
422
|
// Indicators must reflect an override, not file-based detection.
|
|
260
423
|
expect(result.detection.indicators).not.toEqual(['package.json']);
|
|
261
424
|
expect(result.detection.indicators.join(' ')).toMatch(/pack override/i);
|
|
262
425
|
});
|
|
263
|
-
it('throws listing available packs when the override pack is not in the registry', () => {
|
|
264
|
-
expect((
|
|
265
|
-
|
|
266
|
-
(0, init_1.initSensors)({ pack: 'bogus', registryRoot, cwd: tmpDir });
|
|
267
|
-
throw new Error('expected initSensors to throw');
|
|
268
|
-
}
|
|
269
|
-
catch (e) {
|
|
270
|
-
expect(e.message).toContain('bogus');
|
|
271
|
-
expect(e.message).toContain('js-ts');
|
|
272
|
-
}
|
|
426
|
+
it('throws listing available packs when the override pack is not in the registry', async () => {
|
|
427
|
+
await expect((0, init_1.initSensors)({ pack: 'bogus', registryRoot, cwd: tmpDir })).rejects.toThrow(/js-ts/);
|
|
428
|
+
await expect((0, init_1.initSensors)({ pack: 'bogus', registryRoot, cwd: tmpDir })).rejects.toThrow(/bogus/);
|
|
273
429
|
});
|
|
274
|
-
it('does not throw when no registryRoot is given — nothing to validate against', () => {
|
|
275
|
-
expect((
|
|
276
|
-
const result = (0, init_1.initSensors)({ pack: 'anything', cwd: tmpDir });
|
|
430
|
+
it('does not throw when no registryRoot is given — nothing to validate against', async () => {
|
|
431
|
+
await expect((0, init_1.initSensors)({ pack: 'anything', cwd: tmpDir })).resolves.toBeDefined();
|
|
432
|
+
const result = await (0, init_1.initSensors)({ pack: 'anything', cwd: tmpDir });
|
|
277
433
|
expect(result.detection.pack).toBe('anything');
|
|
278
434
|
});
|
|
279
|
-
it('throws a distinct message when the registry root has no sensor-packs directory at all', () => {
|
|
435
|
+
it('throws a distinct message when the registry root has no sensor-packs directory at all', async () => {
|
|
280
436
|
// Different failure shape from "pack not in the list": the registry root
|
|
281
437
|
// itself is missing sensor-packs/, so there's no list to show — must say
|
|
282
438
|
// so plainly instead of reporting an empty `available: `.
|
|
283
439
|
const emptyRegistryRoot = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-empty-reg-'));
|
|
284
440
|
try {
|
|
285
|
-
expect((
|
|
286
|
-
.toThrow(/no sensor-packs directory/);
|
|
441
|
+
await expect((0, init_1.initSensors)({ pack: 'js-ts', registryRoot: emptyRegistryRoot, cwd: tmpDir }))
|
|
442
|
+
.rejects.toThrow(/no sensor-packs directory/);
|
|
287
443
|
}
|
|
288
444
|
finally {
|
|
289
445
|
fs_1.default.rmSync(emptyRegistryRoot, { recursive: true });
|
|
@@ -157,7 +157,7 @@ describe('runSensors — inconclusive: a sensor that could not certify is never
|
|
|
157
157
|
expect(out.sensors.find((s) => s.name === 'security').status).toBe('inconclusive');
|
|
158
158
|
expect(out.overall).toBe('not_certified');
|
|
159
159
|
});
|
|
160
|
-
it('does not
|
|
160
|
+
it('does not let healthy legacy commands certify a run when another sensor is disabled', async () => {
|
|
161
161
|
fs_1.default.writeFileSync(path_1.default.join(root, '.awm', 'sensors.json'), JSON.stringify({
|
|
162
162
|
pack: 'js-ts',
|
|
163
163
|
sensors: {
|
|
@@ -168,7 +168,14 @@ describe('runSensors — inconclusive: a sensor that could not certify is never
|
|
|
168
168
|
mockRunCommand.mockResolvedValueOnce(ok());
|
|
169
169
|
const { runSensors } = load();
|
|
170
170
|
const out = await runSensors({ cwd: root });
|
|
171
|
-
|
|
171
|
+
// `enabled: false` is informational; the non-certification comes solely
|
|
172
|
+
// from this pre-R3 (schema-less) manifest. Legacy commands remain
|
|
173
|
+
// operational, but their shell-backed, unversioned contract cannot issue
|
|
174
|
+
// a certified `pass` verdict (R3 design R1.4 / R7.2).
|
|
175
|
+
expect(out.sensors.find((s) => s.name === 'typecheck').status).toBe('pass');
|
|
176
|
+
expect(out.sensors.find((s) => s.name === 'mutation').status).toBe('skipped');
|
|
177
|
+
expect(out.sensors.find((s) => s.name === 'mutation').skipReason).toBe('disabled');
|
|
178
|
+
expect(out.overall).toBe('not_certified');
|
|
172
179
|
});
|
|
173
180
|
it('still refuses to certify a tree whose sensors are all disabled', async () => {
|
|
174
181
|
fs_1.default.writeFileSync(path_1.default.join(root, '.awm', 'sensors.json'), JSON.stringify({
|
|
Binary file
|