agentic-workflow-manager 6.5.2 → 8.0.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 +261 -0
- package/dist/src/commands/sensors/compatibility/discovery.js +207 -0
- package/dist/src/commands/sensors/compatibility/live.js +59 -0
- package/dist/src/commands/sensors/compatibility/manifest.js +197 -0
- package/dist/src/commands/sensors/compatibility/materialize.js +126 -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 +96 -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 +150 -9
- package/dist/src/commands/sensors/index.js +18 -6
- package/dist/src/commands/sensors/init.js +92 -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 +98 -0
- package/dist/tests/commands/sensors/compatibility/discovery.test.js +119 -0
- package/dist/tests/commands/sensors/compatibility/manifest.test.js +68 -0
- package/dist/tests/commands/sensors/compatibility/materialize.test.js +53 -0
- package/dist/tests/commands/sensors/compatibility/pack-source.test.js +55 -0
- package/dist/tests/commands/sensors/compatibility/probe.test.js +27 -0
- package/dist/tests/commands/sensors/compatibility/resolve.test.js +75 -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 +92 -2
- package/dist/tests/commands/sensors/exec.test.js +67 -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 +132 -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
|
@@ -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
|
});
|
|
@@ -40,69 +40,86 @@ function makePythonRegistry() {
|
|
|
40
40
|
}));
|
|
41
41
|
return registryRoot;
|
|
42
42
|
}
|
|
43
|
+
function makeV2Registry() {
|
|
44
|
+
const registryRoot = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-reg-v2-'));
|
|
45
|
+
const packDir = path_1.default.join(registryRoot, 'sensor-packs', 'js-ts');
|
|
46
|
+
fs_1.default.mkdirSync(packDir, { recursive: true });
|
|
47
|
+
fs_1.default.writeFileSync(path_1.default.join(packDir, 'eslint.config.awm.mjs'), 'export default []\n');
|
|
48
|
+
fs_1.default.writeFileSync(path_1.default.join(packDir, 'pack.json'), JSON.stringify({
|
|
49
|
+
schemaVersion: 2, name: 'js-ts', description: 'fixture', detects: ['package.json'],
|
|
50
|
+
coverage: { schemaVersion: 1, classes: { lint: { description: 'lint', detectors: [{ sensor: 'lint' }], remedy: { summary: 'fix lint', command: 'awm sensors init --pack js-ts' } } } },
|
|
51
|
+
sensors: { lint: { applicability: { allFiles: ['package.json'] }, variants: [{
|
|
52
|
+
id: 'eslint-10', priority: 10, certifiedRange: '>=10.0.0 <11.0.0',
|
|
53
|
+
requirements: { tool: 'eslint', toolRange: '>=10.0.0 <11.0.0', runtime: 'node', runtimeRange: '>=0.0.0' },
|
|
54
|
+
assets: ['eslint.config.awm.mjs'], formatter: 'eslint-llm', probe: { kind: 'config-present' },
|
|
55
|
+
command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] },
|
|
56
|
+
}] } },
|
|
57
|
+
}));
|
|
58
|
+
return registryRoot;
|
|
59
|
+
}
|
|
43
60
|
describe('detectStack', () => {
|
|
44
61
|
let tmpDir;
|
|
45
62
|
beforeEach(() => { tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-init-')); });
|
|
46
63
|
afterEach(() => { fs_1.default.rmSync(tmpDir, { recursive: true }); });
|
|
47
|
-
it('detects js-ts when package.json exists', () => {
|
|
64
|
+
it('detects js-ts when package.json exists', async () => {
|
|
48
65
|
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), '{}');
|
|
49
66
|
expect((0, init_1.detectStack)(tmpDir).pack).toBe('js-ts');
|
|
50
67
|
});
|
|
51
|
-
it('detects python when pyproject.toml exists', () => {
|
|
68
|
+
it('detects python when pyproject.toml exists', async () => {
|
|
52
69
|
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'pyproject.toml'), '');
|
|
53
70
|
expect((0, init_1.detectStack)(tmpDir).pack).toBe('python');
|
|
54
71
|
});
|
|
55
|
-
it('falls back to generic when no indicators found', () => {
|
|
72
|
+
it('falls back to generic when no indicators found', async () => {
|
|
56
73
|
expect((0, init_1.detectStack)(tmpDir).pack).toBe('generic');
|
|
57
74
|
});
|
|
58
|
-
it('detects shell from a root-level *.sh file when no js-ts/python marker exists', () => {
|
|
75
|
+
it('detects shell from a root-level *.sh file when no js-ts/python marker exists', async () => {
|
|
59
76
|
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'deploy.sh'), '#!/bin/sh\n');
|
|
60
77
|
const result = (0, init_1.detectStack)(tmpDir);
|
|
61
78
|
expect(result.pack).toBe('shell');
|
|
62
79
|
expect(result.indicators).toEqual(['deploy.sh']);
|
|
63
80
|
});
|
|
64
|
-
it('detects shell from a scripts/*.sh file when root has nothing', () => {
|
|
81
|
+
it('detects shell from a scripts/*.sh file when root has nothing', async () => {
|
|
65
82
|
fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'scripts'));
|
|
66
83
|
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'scripts', 'build.sh'), '#!/bin/sh\n');
|
|
67
84
|
const result = (0, init_1.detectStack)(tmpDir);
|
|
68
85
|
expect(result.pack).toBe('shell');
|
|
69
86
|
expect(result.indicators).toEqual([path_1.default.join('scripts', 'build.sh')]);
|
|
70
87
|
});
|
|
71
|
-
it('js-ts wins over shell when both package.json and a root .sh file exist', () => {
|
|
88
|
+
it('js-ts wins over shell when both package.json and a root .sh file exist', async () => {
|
|
72
89
|
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), '{}');
|
|
73
90
|
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'deploy.sh'), '#!/bin/sh\n');
|
|
74
91
|
expect((0, init_1.detectStack)(tmpDir).pack).toBe('js-ts');
|
|
75
92
|
});
|
|
76
|
-
it('python wins over shell when both a python marker and a root .sh file exist', () => {
|
|
93
|
+
it('python wins over shell when both a python marker and a root .sh file exist', async () => {
|
|
77
94
|
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'pyproject.toml'), '');
|
|
78
95
|
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'deploy.sh'), '#!/bin/sh\n');
|
|
79
96
|
expect((0, init_1.detectStack)(tmpDir).pack).toBe('python');
|
|
80
97
|
});
|
|
81
|
-
it('falls through to generic when scripts/ has only non-.sh files (glob must not over-match)', () => {
|
|
98
|
+
it('falls through to generic when scripts/ has only non-.sh files (glob must not over-match)', async () => {
|
|
82
99
|
fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'scripts'));
|
|
83
100
|
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'scripts', 'notes.txt'), 'not shell');
|
|
84
101
|
expect((0, init_1.detectStack)(tmpDir).pack).toBe('generic');
|
|
85
102
|
});
|
|
86
|
-
it('detects python from requirements.txt alone', () => {
|
|
103
|
+
it('detects python from requirements.txt alone', async () => {
|
|
87
104
|
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'requirements.txt'), '');
|
|
88
105
|
expect((0, init_1.detectStack)(tmpDir).pack).toBe('python');
|
|
89
106
|
});
|
|
90
|
-
it('detects python from Pipfile alone', () => {
|
|
107
|
+
it('detects python from Pipfile alone', async () => {
|
|
91
108
|
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'Pipfile'), '');
|
|
92
109
|
expect((0, init_1.detectStack)(tmpDir).pack).toBe('python');
|
|
93
110
|
});
|
|
94
|
-
it('python (via Pipfile) wins over shell when both a Pipfile and a root .sh file exist', () => {
|
|
111
|
+
it('python (via Pipfile) wins over shell when both a Pipfile and a root .sh file exist', async () => {
|
|
95
112
|
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'Pipfile'), '');
|
|
96
113
|
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'deploy.sh'), '#!/bin/sh\n');
|
|
97
114
|
expect((0, init_1.detectStack)(tmpDir).pack).toBe('python');
|
|
98
115
|
});
|
|
99
|
-
it('does not report a directory named "*.sh" as a shell indicator', () => {
|
|
116
|
+
it('does not report a directory named "*.sh" as a shell indicator', async () => {
|
|
100
117
|
// Directory literally named `something.sh` (not a file) — findShellIndicators'
|
|
101
118
|
// `entry.isFile()` guard must exclude it. Nothing else present → generic.
|
|
102
119
|
fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'something.sh'));
|
|
103
120
|
expect((0, init_1.detectStack)(tmpDir).pack).toBe('generic');
|
|
104
121
|
});
|
|
105
|
-
it('ignores a directory named "*.sh" but still finds a real .sh file alongside it', () => {
|
|
122
|
+
it('ignores a directory named "*.sh" but still finds a real .sh file alongside it', async () => {
|
|
106
123
|
fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'notreal.sh'));
|
|
107
124
|
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'deploy.sh'), '#!/bin/sh\n');
|
|
108
125
|
const result = (0, init_1.detectStack)(tmpDir);
|
|
@@ -114,16 +131,16 @@ describe('detectSourceDirs', () => {
|
|
|
114
131
|
let tmpDir;
|
|
115
132
|
beforeEach(() => { tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-src-')); });
|
|
116
133
|
afterEach(() => { fs_1.default.rmSync(tmpDir, { recursive: true }); });
|
|
117
|
-
it('returns the App-Router-style dirs that exist', () => {
|
|
134
|
+
it('returns the App-Router-style dirs that exist', async () => {
|
|
118
135
|
for (const d of ['app', 'lib', 'components'])
|
|
119
136
|
fs_1.default.mkdirSync(path_1.default.join(tmpDir, d));
|
|
120
137
|
expect((0, init_1.detectSourceDirs)(tmpDir)).toEqual(['app', 'lib', 'components']);
|
|
121
138
|
});
|
|
122
|
-
it('returns src when it exists', () => {
|
|
139
|
+
it('returns src when it exists', async () => {
|
|
123
140
|
fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'src'));
|
|
124
141
|
expect((0, init_1.detectSourceDirs)(tmpDir)).toEqual(['src']);
|
|
125
142
|
});
|
|
126
|
-
it('falls back to ["src"] when no known source dir exists', () => {
|
|
143
|
+
it('falls back to ["src"] when no known source dir exists', async () => {
|
|
127
144
|
expect((0, init_1.detectSourceDirs)(tmpDir)).toEqual(['src']);
|
|
128
145
|
});
|
|
129
146
|
});
|
|
@@ -138,26 +155,26 @@ describe('buildManifest', () => {
|
|
|
138
155
|
fs_1.default.rmSync(registryRoot, { recursive: true });
|
|
139
156
|
fs_1.default.rmSync(cwd, { recursive: true });
|
|
140
157
|
});
|
|
141
|
-
it('builds manifest from the pack.json single source', () => {
|
|
158
|
+
it('builds manifest from the pack.json single source', async () => {
|
|
142
159
|
const m = (0, init_1.buildManifest)('js-ts', undefined, registryRoot, cwd);
|
|
143
160
|
expect(m.pack).toBe('js-ts');
|
|
144
161
|
expect(m.sensors.typecheck.cmd).toBe('npx tsc --noEmit');
|
|
145
162
|
expect(m.sensors.lint.cmd).toContain('--config eslint.config.awm.mjs');
|
|
146
163
|
});
|
|
147
|
-
it('substitutes {{SOURCE_DIRS}} in depcheck with the detected dirs', () => {
|
|
164
|
+
it('substitutes {{SOURCE_DIRS}} in depcheck with the detected dirs', async () => {
|
|
148
165
|
for (const d of ['app', 'lib'])
|
|
149
166
|
fs_1.default.mkdirSync(path_1.default.join(cwd, d));
|
|
150
167
|
const m = (0, init_1.buildManifest)('js-ts', undefined, registryRoot, cwd);
|
|
151
168
|
expect(m.sensors.depcheck.cmd).toBe('npx depcruise --config .dep-cruiser.awm.js app lib');
|
|
152
169
|
expect(m.sensors.depcheck.cmd).not.toContain('{{SOURCE_DIRS}}');
|
|
153
170
|
});
|
|
154
|
-
it('merges conservatively — existing sensor commands are preserved', () => {
|
|
171
|
+
it('merges conservatively — existing sensor commands are preserved', async () => {
|
|
155
172
|
const existing = { pack: 'js-ts', sensors: { typecheck: { cmd: 'custom-tsc', fast: true } } };
|
|
156
173
|
const m = (0, init_1.buildManifest)('js-ts', existing, registryRoot, cwd);
|
|
157
174
|
expect(m.sensors.typecheck.cmd).toBe('custom-tsc');
|
|
158
175
|
expect(m.sensors.lint).toBeDefined();
|
|
159
176
|
});
|
|
160
|
-
it('carries the formatter field through from pack.json into the built manifest', () => {
|
|
177
|
+
it('carries the formatter field through from pack.json into the built manifest', async () => {
|
|
161
178
|
// readPackDefaults must copy `formatter` the same way it already copies
|
|
162
179
|
// `changedCmd`/`changedExtensions` — this is what lets run.ts's getFormatter
|
|
163
180
|
// dispatch by real tool (ruff/mypy/shellcheck) instead of guessing from the
|
|
@@ -167,14 +184,14 @@ describe('buildManifest', () => {
|
|
|
167
184
|
expect(m.sensors.typecheck.formatter).toBe('tsc');
|
|
168
185
|
expect(m.sensors.lint.formatter).toBe('eslint-llm');
|
|
169
186
|
});
|
|
170
|
-
it('returns an empty sensors object when the pack has no pack.json in the registry', () => {
|
|
187
|
+
it('returns an empty sensors object when the pack has no pack.json in the registry', async () => {
|
|
171
188
|
// No FALLBACK_DEFAULTS anymore: `python` has no pack dir in this fixture
|
|
172
189
|
// registry (only js-ts does — see makeRegistry) → the honest floor is `{}`,
|
|
173
190
|
// never CLI-hardcoded commands that can drift from what the registry ships.
|
|
174
191
|
const m = (0, init_1.buildManifest)('python', undefined, registryRoot, cwd);
|
|
175
192
|
expect(m.sensors).toEqual({});
|
|
176
193
|
});
|
|
177
|
-
it('per-field merge: an existing sensor missing a newer pack field still inherits it', () => {
|
|
194
|
+
it('per-field merge: an existing sensor missing a newer pack field still inherits it', async () => {
|
|
178
195
|
// Regression for Finding 1: a manifest written by the old FALLBACK_DEFAULTS-era
|
|
179
196
|
// CLI has `typecheck: { cmd: 'mypy .', fast: true }` — no `formatter`, because
|
|
180
197
|
// that field didn't exist yet. A naive `{ ...defaults, ...existingSensors }`
|
|
@@ -209,37 +226,111 @@ describe('initSensors', () => {
|
|
|
209
226
|
fs_1.default.rmSync(tmpDir, { recursive: true });
|
|
210
227
|
fs_1.default.rmSync(registryRoot, { recursive: true });
|
|
211
228
|
});
|
|
212
|
-
it('creates .awm/sensors.json for js-ts project', () => {
|
|
229
|
+
it('creates .awm/sensors.json for js-ts project', async () => {
|
|
213
230
|
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), '{}');
|
|
214
|
-
const result = (0, init_1.initSensors)({ cwd: tmpDir, registryRoot });
|
|
231
|
+
const result = await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot });
|
|
215
232
|
expect(fs_1.default.existsSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'))).toBe(true);
|
|
216
233
|
expect(result.detection.pack).toBe('js-ts');
|
|
217
234
|
expect(result.manifest.sensors.typecheck).toBeDefined();
|
|
218
235
|
});
|
|
219
|
-
it('is idempotent — existing sensor commands survive re-init', () => {
|
|
236
|
+
it('is idempotent — existing sensor commands survive re-init', async () => {
|
|
220
237
|
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), '{}');
|
|
221
238
|
fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.awm'), { recursive: true });
|
|
222
239
|
const existing = { pack: 'js-ts', sensors: { typecheck: { cmd: 'my-tsc', fast: true } } };
|
|
223
240
|
fs_1.default.writeFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), JSON.stringify(existing));
|
|
224
|
-
(0, init_1.initSensors)({ cwd: tmpDir, registryRoot });
|
|
241
|
+
await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot });
|
|
225
242
|
const written = JSON.parse(fs_1.default.readFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), 'utf-8'));
|
|
226
243
|
expect(written.sensors.typecheck.cmd).toBe('my-tsc');
|
|
227
244
|
expect(written.sensors.lint).toBeDefined(); // new sensor added
|
|
228
245
|
});
|
|
229
|
-
it('copies pack config files into the repo by default (configure on)', () => {
|
|
246
|
+
it('copies pack config files into the repo by default (configure on)', async () => {
|
|
230
247
|
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), '{}');
|
|
231
248
|
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
|
|
249
|
+
const result = await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot }); // no explicit configure → default true
|
|
233
250
|
expect(result.configured).toContain('tsconfig.awm.json');
|
|
234
251
|
expect(fs_1.default.existsSync(path_1.default.join(tmpDir, 'tsconfig.awm.json'))).toBe(true);
|
|
235
252
|
});
|
|
236
|
-
it('does NOT copy config files when configure is false', () => {
|
|
253
|
+
it('does NOT copy config files when configure is false', async () => {
|
|
237
254
|
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), '{}');
|
|
238
255
|
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 });
|
|
256
|
+
const result = await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot, configure: false });
|
|
240
257
|
expect(result.configured).toEqual([]);
|
|
241
258
|
expect(fs_1.default.existsSync(path_1.default.join(tmpDir, 'tsconfig.awm.json'))).toBe(false);
|
|
242
259
|
});
|
|
260
|
+
it('awaits v2 compatibility probes and persists their materialized evidence', async () => {
|
|
261
|
+
const v2Registry = makeV2Registry();
|
|
262
|
+
try {
|
|
263
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), JSON.stringify({ devDependencies: { eslint: '^10.0.0' } }));
|
|
264
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'node_modules', 'eslint'), { recursive: true });
|
|
265
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'node_modules', 'eslint', 'package.json'), JSON.stringify({ version: '10.0.0' }));
|
|
266
|
+
const result = await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot: v2Registry });
|
|
267
|
+
const written = JSON.parse(fs_1.default.readFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), 'utf8'));
|
|
268
|
+
expect(result.manifest).toMatchObject({ schemaVersion: 2, sensors: { lint: { variantId: 'eslint-10' } } });
|
|
269
|
+
// `package.json` is a declared detection file, so config-present matches.
|
|
270
|
+
// Without init awaiting the probe the resolver stays `unverifiable`; this
|
|
271
|
+
// exact assertion therefore distinguishes the async probe integration.
|
|
272
|
+
expect(written.sensors.lint.initializedCompatibility).toMatchObject({ variantId: 'eslint-10', state: 'certified', reason: 'range-and-probe' });
|
|
273
|
+
expect(written.registryRoot).toBe(v2Registry);
|
|
274
|
+
expect(fs_1.default.existsSync(path_1.default.join(tmpDir, 'eslint.config.awm.mjs'))).toBe(true);
|
|
275
|
+
}
|
|
276
|
+
finally {
|
|
277
|
+
fs_1.default.rmSync(v2Registry, { recursive: true, force: true });
|
|
278
|
+
}
|
|
279
|
+
});
|
|
280
|
+
it('preserves enabled and fast choices when a valid v2 manifest is re-initialized', 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
|
+
await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot: v2Registry });
|
|
287
|
+
const manifestPath = path_1.default.join(tmpDir, '.awm', 'sensors.json');
|
|
288
|
+
const selected = JSON.parse(fs_1.default.readFileSync(manifestPath, 'utf8'));
|
|
289
|
+
selected.sensors.lint.enabled = false;
|
|
290
|
+
selected.sensors.lint.fast = true;
|
|
291
|
+
fs_1.default.writeFileSync(manifestPath, JSON.stringify(selected));
|
|
292
|
+
await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot: v2Registry });
|
|
293
|
+
const written = JSON.parse(fs_1.default.readFileSync(manifestPath, 'utf8'));
|
|
294
|
+
expect(written.sensors.lint).toMatchObject({ enabled: false, fast: true, variantId: 'eslint-10' });
|
|
295
|
+
}
|
|
296
|
+
finally {
|
|
297
|
+
fs_1.default.rmSync(v2Registry, { recursive: true, force: true });
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
it('rejects a symlinked v2 pack source instead of reading it through init', async () => {
|
|
301
|
+
const v2Registry = makeV2Registry();
|
|
302
|
+
try {
|
|
303
|
+
const packPath = path_1.default.join(v2Registry, 'sensor-packs', 'js-ts', 'pack.json');
|
|
304
|
+
const outside = path_1.default.join(v2Registry, 'outside-pack.json');
|
|
305
|
+
fs_1.default.renameSync(packPath, outside);
|
|
306
|
+
fs_1.default.symlinkSync(outside, packPath);
|
|
307
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), '{}');
|
|
308
|
+
await expect((0, init_1.initSensors)({ cwd: tmpDir, registryRoot: v2Registry })).rejects.toThrow(/symbolic|regular|contain/i);
|
|
309
|
+
expect(fs_1.default.existsSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'))).toBe(false);
|
|
310
|
+
}
|
|
311
|
+
finally {
|
|
312
|
+
fs_1.default.rmSync(v2Registry, { recursive: true, force: true });
|
|
313
|
+
}
|
|
314
|
+
});
|
|
315
|
+
it('migrates legacy overrides explicitly without silently certifying or re-enabling them', async () => {
|
|
316
|
+
const v2Registry = makeV2Registry();
|
|
317
|
+
try {
|
|
318
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), JSON.stringify({ devDependencies: { eslint: '^10.0.0' } }));
|
|
319
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'node_modules', 'eslint'), { recursive: true });
|
|
320
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'node_modules', 'eslint', 'package.json'), JSON.stringify({ version: '10.0.0' }));
|
|
321
|
+
fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.awm'));
|
|
322
|
+
fs_1.default.writeFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), JSON.stringify({
|
|
323
|
+
pack: 'js-ts', sensors: { lint: { cmd: 'my-company-eslint .', enabled: false, fast: true } },
|
|
324
|
+
}));
|
|
325
|
+
await (0, init_1.initSensors)({ cwd: tmpDir, registryRoot: v2Registry });
|
|
326
|
+
const written = JSON.parse(fs_1.default.readFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), 'utf8'));
|
|
327
|
+
expect(written.sensors.lint).toMatchObject({ enabled: false, fast: true });
|
|
328
|
+
expect(written.sensors.lint.initializedCompatibility).toMatchObject({ state: 'compatible-unverified', reason: expect.stringContaining('legacy custom command') });
|
|
329
|
+
}
|
|
330
|
+
finally {
|
|
331
|
+
fs_1.default.rmSync(v2Registry, { recursive: true, force: true });
|
|
332
|
+
}
|
|
333
|
+
});
|
|
243
334
|
});
|
|
244
335
|
describe('initSensors — --pack override', () => {
|
|
245
336
|
let tmpDir;
|
|
@@ -252,38 +343,31 @@ describe('initSensors — --pack override', () => {
|
|
|
252
343
|
fs_1.default.rmSync(tmpDir, { recursive: true });
|
|
253
344
|
fs_1.default.rmSync(registryRoot, { recursive: true });
|
|
254
345
|
});
|
|
255
|
-
it('skips detection and uses the override pack when it exists in the registry', () => {
|
|
346
|
+
it('skips detection and uses the override pack when it exists in the registry', async () => {
|
|
256
347
|
// 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 });
|
|
348
|
+
const result = await (0, init_1.initSensors)({ pack: 'js-ts', registryRoot, cwd: tmpDir });
|
|
258
349
|
expect(result.detection.pack).toBe('js-ts');
|
|
259
350
|
// Indicators must reflect an override, not file-based detection.
|
|
260
351
|
expect(result.detection.indicators).not.toEqual(['package.json']);
|
|
261
352
|
expect(result.detection.indicators.join(' ')).toMatch(/pack override/i);
|
|
262
353
|
});
|
|
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
|
-
}
|
|
354
|
+
it('throws listing available packs when the override pack is not in the registry', async () => {
|
|
355
|
+
await expect((0, init_1.initSensors)({ pack: 'bogus', registryRoot, cwd: tmpDir })).rejects.toThrow(/js-ts/);
|
|
356
|
+
await expect((0, init_1.initSensors)({ pack: 'bogus', registryRoot, cwd: tmpDir })).rejects.toThrow(/bogus/);
|
|
273
357
|
});
|
|
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 });
|
|
358
|
+
it('does not throw when no registryRoot is given — nothing to validate against', async () => {
|
|
359
|
+
await expect((0, init_1.initSensors)({ pack: 'anything', cwd: tmpDir })).resolves.toBeDefined();
|
|
360
|
+
const result = await (0, init_1.initSensors)({ pack: 'anything', cwd: tmpDir });
|
|
277
361
|
expect(result.detection.pack).toBe('anything');
|
|
278
362
|
});
|
|
279
|
-
it('throws a distinct message when the registry root has no sensor-packs directory at all', () => {
|
|
363
|
+
it('throws a distinct message when the registry root has no sensor-packs directory at all', async () => {
|
|
280
364
|
// Different failure shape from "pack not in the list": the registry root
|
|
281
365
|
// itself is missing sensor-packs/, so there's no list to show — must say
|
|
282
366
|
// so plainly instead of reporting an empty `available: `.
|
|
283
367
|
const emptyRegistryRoot = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-empty-reg-'));
|
|
284
368
|
try {
|
|
285
|
-
expect((
|
|
286
|
-
.toThrow(/no sensor-packs directory/);
|
|
369
|
+
await expect((0, init_1.initSensors)({ pack: 'js-ts', registryRoot: emptyRegistryRoot, cwd: tmpDir }))
|
|
370
|
+
.rejects.toThrow(/no sensor-packs directory/);
|
|
287
371
|
}
|
|
288
372
|
finally {
|
|
289
373
|
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
|
|
@@ -12,8 +12,10 @@ function mkTmp() {
|
|
|
12
12
|
}
|
|
13
13
|
// Define stable mock before jest.mock hoisting
|
|
14
14
|
const mockRunCommand = jest.fn();
|
|
15
|
+
const mockRunStructuredCommand = jest.fn();
|
|
15
16
|
jest.mock('../../../src/commands/sensors/exec', () => ({
|
|
16
17
|
runCommand: (...args) => mockRunCommand(...args),
|
|
18
|
+
runStructuredCommand: (...args) => mockRunStructuredCommand(...args),
|
|
17
19
|
}));
|
|
18
20
|
const { ok, exited, timedOut } = require('./exec-fixtures');
|
|
19
21
|
const MANIFEST = {
|
|
@@ -35,6 +37,7 @@ describe('runSensors', () => {
|
|
|
35
37
|
fs_1.default.mkdirSync(path.join(tmpDir, '.awm'), { recursive: true });
|
|
36
38
|
fs_1.default.writeFileSync(path.join(tmpDir, '.awm', 'sensors.json'), JSON.stringify(MANIFEST));
|
|
37
39
|
mockRunCommand.mockReset();
|
|
40
|
+
mockRunStructuredCommand.mockReset();
|
|
38
41
|
});
|
|
39
42
|
afterEach(() => { fs_1.default.rmSync(tmpDir, { recursive: true }); });
|
|
40
43
|
const load = () => require('../../../src/commands/sensors/run');
|
|
@@ -56,7 +59,7 @@ describe('runSensors', () => {
|
|
|
56
59
|
const result = await runSensors({ fast: true, cwd: tmpDir });
|
|
57
60
|
expect(mockRunCommand).toHaveBeenCalledTimes(2); // typecheck + lint (security disabled, mutation disabled)
|
|
58
61
|
expect(result.sensors.some((s) => s.name === 'security')).toBe(false);
|
|
59
|
-
expect(result.overall).toBe('
|
|
62
|
+
expect(result.overall).toBe('not_certified');
|
|
60
63
|
});
|
|
61
64
|
it('returns fail when a fast sensor has errors', async () => {
|
|
62
65
|
mockRunCommand
|
|
@@ -175,7 +178,7 @@ describe('runSensors', () => {
|
|
|
175
178
|
const tc = second.sensors.find((s) => s.name === 'typecheck');
|
|
176
179
|
expect(tc.status).toBe('pass');
|
|
177
180
|
expect(tc.baselineCount).toBe(1);
|
|
178
|
-
expect(second.overall).toBe('
|
|
181
|
+
expect(second.overall).toBe('not_certified');
|
|
179
182
|
});
|
|
180
183
|
it('baseline lets NEW findings through (still fails)', async () => {
|
|
181
184
|
const { runSensors } = load();
|
|
@@ -200,6 +203,66 @@ describe('runSensors', () => {
|
|
|
200
203
|
expect(result.overall).toBe('fail');
|
|
201
204
|
});
|
|
202
205
|
});
|
|
206
|
+
describe('runSensors v2 lifecycle contract', () => {
|
|
207
|
+
let project;
|
|
208
|
+
let home;
|
|
209
|
+
const version = '10.0.0';
|
|
210
|
+
beforeEach(() => {
|
|
211
|
+
jest.resetModules();
|
|
212
|
+
project = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-v2-run-project-'));
|
|
213
|
+
home = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-v2-run-home-'));
|
|
214
|
+
process.env.AWM_HOME = home;
|
|
215
|
+
const registry = path_1.default.join(home, 'registries', 'baseline', 'sensor-packs', 'js-ts');
|
|
216
|
+
fs_1.default.mkdirSync(registry, { recursive: true });
|
|
217
|
+
fs_1.default.writeFileSync(path_1.default.join(registry, 'eslint.config.awm.mjs'), 'export default []');
|
|
218
|
+
fs_1.default.writeFileSync(path_1.default.join(home, 'registries.json'), JSON.stringify([{ name: 'baseline', remote: 'https://example.test/baseline.git' }]));
|
|
219
|
+
fs_1.default.writeFileSync(path_1.default.join(project, 'package.json'), JSON.stringify({ scripts: { lint: 'eslint .' }, devDependencies: { eslint: '^10.0.0' } }));
|
|
220
|
+
fs_1.default.mkdirSync(path_1.default.join(project, 'node_modules', 'eslint'), { recursive: true });
|
|
221
|
+
fs_1.default.writeFileSync(path_1.default.join(project, 'node_modules', 'eslint', 'package.json'), JSON.stringify({ version }));
|
|
222
|
+
fs_1.default.writeFileSync(path_1.default.join(registry, 'pack.json'), JSON.stringify({
|
|
223
|
+
schemaVersion: 2, name: 'js-ts', description: 'fixture', detects: ['package.json'],
|
|
224
|
+
coverage: { schemaVersion: 1, classes: { lint: { description: 'lint', detectors: [{ sensor: 'lint' }], remedy: { summary: 'fix', command: 'awm sensors init' } } } },
|
|
225
|
+
sensors: { lint: { fast: true, applicability: { allFiles: ['package.json'] }, variants: [{
|
|
226
|
+
id: 'eslint-10', priority: 1, certifiedRange: '>=10 <11',
|
|
227
|
+
requirements: { tool: 'eslint', toolRange: '>=10 <11', runtime: 'node', runtimeRange: '>=0' },
|
|
228
|
+
assets: ['eslint.config.awm.mjs'], formatter: 'generic', probe: { kind: 'package-script-present' },
|
|
229
|
+
command: { executable: 'live-eslint', resolution: 'node-modules-bin', args: ['.'] },
|
|
230
|
+
}] } },
|
|
231
|
+
}));
|
|
232
|
+
fs_1.default.mkdirSync(path_1.default.join(project, '.awm'));
|
|
233
|
+
fs_1.default.writeFileSync(path_1.default.join(project, '.awm', 'sensors.json'), JSON.stringify({
|
|
234
|
+
schemaVersion: 2, pack: 'js-ts', registryRoot: path_1.default.join(home, 'registries', 'baseline'), sensors: { lint: {
|
|
235
|
+
enabled: true, fast: true, variantId: 'eslint-10',
|
|
236
|
+
command: { executable: 'stale-eslint', resolution: 'node-modules-bin', args: ['.'] }, assets: ['eslint.config.awm.mjs'],
|
|
237
|
+
initializedCompatibility: { state: 'certified', reason: 'range-and-probe', variantId: 'eslint-10', toolVersion: version, runtimeVersion: process.versions.node, certifiedRange: '>=10 <11', evidence: [] },
|
|
238
|
+
} },
|
|
239
|
+
}));
|
|
240
|
+
mockRunStructuredCommand.mockReset();
|
|
241
|
+
mockRunStructuredCommand.mockResolvedValue(ok());
|
|
242
|
+
});
|
|
243
|
+
afterEach(() => {
|
|
244
|
+
delete process.env.AWM_HOME;
|
|
245
|
+
fs_1.default.rmSync(project, { recursive: true, force: true });
|
|
246
|
+
fs_1.default.rmSync(home, { recursive: true, force: true });
|
|
247
|
+
});
|
|
248
|
+
it('runs the re-resolved command for an unchanged variant id, never the stale manifest command', async () => {
|
|
249
|
+
const { resolveLiveCompatibility } = require('../../../src/commands/sensors/compatibility/live');
|
|
250
|
+
await expect(resolveLiveCompatibility(project, 'js-ts', path_1.default.join(home, 'registries', 'baseline'))).resolves.toBeDefined();
|
|
251
|
+
const { runSensors } = require('../../../src/commands/sensors/run');
|
|
252
|
+
const result = await runSensors({ cwd: project, fast: true });
|
|
253
|
+
expect(result).toEqual(expect.objectContaining({ overall: 'pass' }));
|
|
254
|
+
expect(mockRunStructuredCommand).toHaveBeenCalledWith(expect.objectContaining({ executable: 'live-eslint' }), expect.any(Object));
|
|
255
|
+
});
|
|
256
|
+
it('honors disabled v2 sensors before dispatching them', async () => {
|
|
257
|
+
const manifest = JSON.parse(fs_1.default.readFileSync(path_1.default.join(project, '.awm', 'sensors.json'), 'utf8'));
|
|
258
|
+
manifest.sensors.lint.enabled = false;
|
|
259
|
+
fs_1.default.writeFileSync(path_1.default.join(project, '.awm', 'sensors.json'), JSON.stringify(manifest));
|
|
260
|
+
const { runSensors } = require('../../../src/commands/sensors/run');
|
|
261
|
+
const result = await runSensors({ cwd: project, fast: true });
|
|
262
|
+
expect(result.sensors).toEqual([expect.objectContaining({ status: 'skipped', skipReason: 'disabled' })]);
|
|
263
|
+
expect(mockRunStructuredCommand).not.toHaveBeenCalled();
|
|
264
|
+
});
|
|
265
|
+
});
|
|
203
266
|
describe('runSensors — missing tool is a fail, not a skip', () => {
|
|
204
267
|
let root;
|
|
205
268
|
afterEach(() => {
|
|
@@ -276,7 +339,7 @@ describe('runSensors — not_certified + auto-discovery', () => {
|
|
|
276
339
|
const nested = path_1.default.join(tmpDir, 'a', 'b');
|
|
277
340
|
fs_1.default.mkdirSync(nested, { recursive: true });
|
|
278
341
|
const out = await (0, run_1.runSensors)({ cwd: nested });
|
|
279
|
-
expect(out.overall).toBe('
|
|
342
|
+
expect(out.overall).toBe('not_certified');
|
|
280
343
|
expect(out.sensors.length).toBe(1);
|
|
281
344
|
});
|
|
282
345
|
});
|
|
@@ -48,25 +48,25 @@ describe('computeSensorStatus — Windows PATH resolution', () => {
|
|
|
48
48
|
sensors: { security: { cmd: 'semgrep --json .', fast: false } }
|
|
49
49
|
}));
|
|
50
50
|
}
|
|
51
|
-
it('resuelve un binario instalado en win32 via PATHEXT (incluido un shim .cmd)', () => {
|
|
51
|
+
it('resuelve un binario instalado en win32 via PATHEXT (incluido un shim .cmd)', async () => {
|
|
52
52
|
writeManifest();
|
|
53
53
|
// En win32 el usuario escribe `semgrep` y en disco existe `semgrep.cmd`.
|
|
54
54
|
fs_1.default.writeFileSync(path_1.default.join(pathDir, 'semgrep.cmd'), '@echo off\r\n');
|
|
55
|
-
const result = (0, status_1.computeSensorStatus)(tmpDir);
|
|
56
|
-
expect(result.overall).toBe('
|
|
55
|
+
const result = await (0, status_1.computeSensorStatus)(tmpDir);
|
|
56
|
+
expect(result.overall).toBe('DEGRADED');
|
|
57
57
|
expect(result.checks.security.ok).toBe(true);
|
|
58
58
|
});
|
|
59
|
-
it('reporta ok:false en win32 cuando el binario no esta en PATH', () => {
|
|
59
|
+
it('reporta ok:false en win32 cuando el binario no esta en PATH', async () => {
|
|
60
60
|
writeManifest();
|
|
61
61
|
// PATH aislado y vacio.
|
|
62
|
-
const result = (0, status_1.computeSensorStatus)(tmpDir);
|
|
62
|
+
const result = await (0, status_1.computeSensorStatus)(tmpDir);
|
|
63
63
|
expect(result.overall).toBe('DEGRADED');
|
|
64
64
|
expect(result.checks.security.ok).toBe(false);
|
|
65
65
|
});
|
|
66
|
-
it('en win32 no exige bit de ejecucion — un .exe sin permisos POSIX igual resuelve', () => {
|
|
66
|
+
it('en win32 no exige bit de ejecucion — un .exe sin permisos POSIX igual resuelve', async () => {
|
|
67
67
|
writeManifest();
|
|
68
68
|
fs_1.default.writeFileSync(path_1.default.join(pathDir, 'semgrep.exe'), '');
|
|
69
69
|
fs_1.default.chmodSync(path_1.default.join(pathDir, 'semgrep.exe'), 0o644);
|
|
70
|
-
expect((0, status_1.computeSensorStatus)(tmpDir).checks.security.ok).toBe(true);
|
|
70
|
+
expect((await (0, status_1.computeSensorStatus)(tmpDir)).checks.security.ok).toBe(true);
|
|
71
71
|
});
|
|
72
72
|
});
|