agentic-workflow-manager 6.5.1 → 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 +13 -53
- 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 +22 -26
- 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
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const fs_1 = __importDefault(require("fs"));
|
|
7
|
+
const os_1 = __importDefault(require("os"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const discovery_1 = require("../../../../src/commands/sensors/compatibility/discovery");
|
|
10
|
+
describe('discoverProjectEvidence', () => {
|
|
11
|
+
it('returns only local, relative project evidence and detects conflicting lockfiles', () => {
|
|
12
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-discovery-'));
|
|
13
|
+
try {
|
|
14
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'package.json'), JSON.stringify({ scripts: { lint: 'eslint .' }, devDependencies: { eslint: '10.0.0' } }));
|
|
15
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'package-lock.json'), '{}');
|
|
16
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'pnpm-lock.yaml'), 'lockfileVersion: 9');
|
|
17
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'eslint.config.js'), 'export default []');
|
|
18
|
+
fs_1.default.mkdirSync(path_1.default.join(root, 'node_modules', 'eslint'), { recursive: true });
|
|
19
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'node_modules', 'eslint', 'package.json'), JSON.stringify({ name: 'eslint', version: '10.4.1' }));
|
|
20
|
+
const evidence = (0, discovery_1.discoverProjectEvidence)(root, { schemaVersion: 2, name: 'js-ts', detects: ['package.json'], sensors: { lint: { applicability: { allFiles: ['package.json'] }, variants: [{ requirements: { tool: 'eslint', configFiles: [] } }] } } }, { platform: () => 'darwin' });
|
|
21
|
+
expect(evidence.packageManagerConflict).toBe(true);
|
|
22
|
+
expect(evidence.os).toBe('darwin');
|
|
23
|
+
expect(evidence.declaredToolRanges.eslint).toBe('10.0.0');
|
|
24
|
+
expect(evidence.toolVersions.eslint).toBe('10.4.1');
|
|
25
|
+
expect(evidence.scripts).toContain('lint');
|
|
26
|
+
expect(evidence.configFiles).toContain('eslint.config.js');
|
|
27
|
+
expect(evidence.paths.every((item) => !path_1.default.isAbsolute(item) && !item.includes('..'))).toBe(true);
|
|
28
|
+
}
|
|
29
|
+
finally {
|
|
30
|
+
fs_1.default.rmSync(root, { recursive: true, force: true });
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
it('includes pack and applicability markers when deciding whether a sensor applies', () => {
|
|
34
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-discovery-markers-'));
|
|
35
|
+
try {
|
|
36
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'pack-marker'), 'present');
|
|
37
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'sensor-marker'), 'present');
|
|
38
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'one-of-these'), 'present');
|
|
39
|
+
const evidence = (0, discovery_1.discoverProjectEvidence)(root, {
|
|
40
|
+
schemaVersion: 2,
|
|
41
|
+
name: 'custom',
|
|
42
|
+
detects: ['pack-marker'],
|
|
43
|
+
sensors: {
|
|
44
|
+
lint: {
|
|
45
|
+
applicability: { allFiles: ['sensor-marker'], anyFiles: ['one-of-these', 'missing-marker'] },
|
|
46
|
+
variants: [{ requirements: { tool: 'eslint', configFiles: [] } }],
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
});
|
|
50
|
+
expect(evidence.paths).toEqual(expect.arrayContaining(['pack-marker', 'sensor-marker', 'one-of-these']));
|
|
51
|
+
}
|
|
52
|
+
finally {
|
|
53
|
+
fs_1.default.rmSync(root, { recursive: true, force: true });
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
it('does not report package.json as evidence when the project has none', () => {
|
|
57
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-discovery-no-package-'));
|
|
58
|
+
try {
|
|
59
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'pyproject.toml'), '[project]\nname = "sample"');
|
|
60
|
+
const evidence = (0, discovery_1.discoverProjectEvidence)(root, {
|
|
61
|
+
schemaVersion: 2,
|
|
62
|
+
name: 'python',
|
|
63
|
+
detects: ['pyproject.toml'],
|
|
64
|
+
sensors: {
|
|
65
|
+
lint: { applicability: { allFiles: ['package.json'] }, variants: [{ requirements: { tool: 'ruff', configFiles: [] } }] },
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
expect(evidence.paths).not.toContain('package.json');
|
|
69
|
+
}
|
|
70
|
+
finally {
|
|
71
|
+
fs_1.default.rmSync(root, { recursive: true, force: true });
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
test.each([
|
|
75
|
+
['linux', ['.venv', 'lib', 'python3.12', 'site-packages']],
|
|
76
|
+
['win32', ['.venv', 'Lib', 'site-packages']],
|
|
77
|
+
])('discovers exact local Python runtime and tool metadata on %s', (targetPlatform, sitePackages) => {
|
|
78
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-python-discovery-'));
|
|
79
|
+
try {
|
|
80
|
+
fs_1.default.mkdirSync(path_1.default.join(root, ...sitePackages), { recursive: true });
|
|
81
|
+
fs_1.default.writeFileSync(path_1.default.join(root, '.venv', 'pyvenv.cfg'), 'version = 3.12.4\n');
|
|
82
|
+
for (const [tool, version] of Object.entries({ mypy: '1.11.2', ruff: '0.6.9', pytest: '8.3.3', semgrep: '1.91.0' })) {
|
|
83
|
+
const info = path_1.default.join(root, ...sitePackages, `${tool}-${version}.dist-info`);
|
|
84
|
+
fs_1.default.mkdirSync(info);
|
|
85
|
+
fs_1.default.writeFileSync(path_1.default.join(info, 'METADATA'), `Metadata-Version: 2.3\nName: ${tool}\nVersion: ${version}\n`);
|
|
86
|
+
}
|
|
87
|
+
const pack = { schemaVersion: 2, name: 'python', detects: ['pyproject.toml'], sensors: {
|
|
88
|
+
quality: { applicability: { allFiles: ['pyproject.toml'] }, variants: Object.keys({ mypy: 0, ruff: 0, pytest: 0, semgrep: 0 }).map(tool => ({ requirements: { tool, runtime: 'python', configFiles: [] } })) },
|
|
89
|
+
} };
|
|
90
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'pyproject.toml'), '[project]\nname = "sample"');
|
|
91
|
+
const evidence = (0, discovery_1.discoverProjectEvidence)(root, pack, { platform: () => targetPlatform });
|
|
92
|
+
expect(evidence.runtimeVersions.python).toBe('3.12.4');
|
|
93
|
+
expect(evidence.toolVersions).toMatchObject({ mypy: '1.11.2', ruff: '0.6.9', pytest: '8.3.3', semgrep: '1.91.0' });
|
|
94
|
+
}
|
|
95
|
+
finally {
|
|
96
|
+
fs_1.default.rmSync(root, { recursive: true, force: true });
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
it('does not trust missing or escaping Python environment metadata', () => {
|
|
100
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-python-discovery-untrusted-'));
|
|
101
|
+
const outside = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-python-discovery-outside-'));
|
|
102
|
+
try {
|
|
103
|
+
fs_1.default.mkdirSync(path_1.default.join(root, '.venv', 'lib', 'python3.12', 'site-packages'), { recursive: true });
|
|
104
|
+
fs_1.default.writeFileSync(path_1.default.join(root, '.venv', 'pyvenv.cfg'), 'version = not-a-version\n');
|
|
105
|
+
const escaped = path_1.default.join(root, '.venv', 'lib', 'python3.12', 'site-packages', 'ruff-0.6.9.dist-info');
|
|
106
|
+
fs_1.default.mkdirSync(path_1.default.join(outside, 'ruff-0.6.9.dist-info'));
|
|
107
|
+
fs_1.default.writeFileSync(path_1.default.join(outside, 'ruff-0.6.9.dist-info', 'METADATA'), 'Name: ruff\nVersion: 0.6.9\n');
|
|
108
|
+
fs_1.default.symlinkSync(path_1.default.join(outside, 'ruff-0.6.9.dist-info'), escaped, 'dir');
|
|
109
|
+
const pack = { schemaVersion: 2, name: 'python', detects: [], sensors: { lint: { applicability: {}, variants: [{ requirements: { tool: 'ruff', runtime: 'python', configFiles: [] } }] } } };
|
|
110
|
+
const evidence = (0, discovery_1.discoverProjectEvidence)(root, pack, { platform: () => 'linux' });
|
|
111
|
+
expect(evidence.runtimeVersions.python).toBeNull();
|
|
112
|
+
expect(evidence.toolVersions.ruff).toBeNull();
|
|
113
|
+
}
|
|
114
|
+
finally {
|
|
115
|
+
fs_1.default.rmSync(root, { recursive: true, force: true });
|
|
116
|
+
fs_1.default.rmSync(outside, { recursive: true, force: true });
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const manifest_1 = require("../../../../src/commands/sensors/compatibility/manifest");
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
describe('sensor manifest contract', () => {
|
|
10
|
+
it('keeps compatibility contracts on an acyclic import boundary', () => {
|
|
11
|
+
const source = (relative) => fs_1.default.readFileSync(path_1.default.join(__dirname, '../../../../src/commands/sensors', relative), 'utf8');
|
|
12
|
+
expect(source('types.ts')).not.toContain('./compatibility/');
|
|
13
|
+
expect(source('compatibility/types.ts')).not.toContain('../coverage/');
|
|
14
|
+
expect(source('coverage/contract.ts')).not.toContain('../compatibility/manifest');
|
|
15
|
+
});
|
|
16
|
+
it('normalizes a legacy string command with compatible-unverified evidence', () => {
|
|
17
|
+
expect((0, manifest_1.parseSensorManifest)({ pack: 'js-ts', sensors: { lint: 'npm run lint' } }, 'sensors.json')).toMatchObject({ kind: 'legacy', pack: {
|
|
18
|
+
pack: 'js-ts',
|
|
19
|
+
sensors: { lint: { cmd: 'npm run lint' } },
|
|
20
|
+
compatibility: (0, manifest_1.legacyCompatibility)(),
|
|
21
|
+
} });
|
|
22
|
+
});
|
|
23
|
+
it('accepts a v2 selected variant and structured command', () => {
|
|
24
|
+
const manifest = {
|
|
25
|
+
schemaVersion: 2,
|
|
26
|
+
pack: 'js-ts',
|
|
27
|
+
sensors: {
|
|
28
|
+
lint: {
|
|
29
|
+
enabled: true, variantId: 'eslint-9',
|
|
30
|
+
command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.', '--format', 'json'] },
|
|
31
|
+
initializedCompatibility: { state: 'certified', reason: 'range-and-probe', variantId: 'eslint-9', toolVersion: '9.0.0', runtimeVersion: '24.0.0', certifiedRange: '>=9 <10', evidence: [] },
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
expect((0, manifest_1.parseSensorManifest)(manifest, 'sensors.json')).toMatchObject({ kind: 'v2', pack: manifest });
|
|
36
|
+
expect(JSON.parse((0, manifest_1.serializeManifestV2)(manifest))).toEqual(manifest);
|
|
37
|
+
});
|
|
38
|
+
it('accepts optional contained assets and rejects traversal', () => {
|
|
39
|
+
const sensor = { enabled: true, variantId: 'eslint-9', assets: ['eslint.config.awm.mjs'], command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, initializedCompatibility: { state: 'certified', reason: 'ok', variantId: 'eslint-9', toolVersion: '9.0.0', runtimeVersion: '24.0.0', certifiedRange: '>=9 <10', evidence: [] } };
|
|
40
|
+
expect((0, manifest_1.parseSensorManifest)({ schemaVersion: 2, pack: 'js-ts', sensors: { lint: sensor } }, 'sensors.json')).toMatchObject({ kind: 'v2' });
|
|
41
|
+
expect(() => (0, manifest_1.parseSensorManifest)({ schemaVersion: 2, pack: 'js-ts', sensors: { lint: { ...sensor, assets: ['C:/secret'] } } }, 'sensors.json')).toThrow('asset');
|
|
42
|
+
});
|
|
43
|
+
it('rejects mismatched compatibility variant and hostile source labels', () => {
|
|
44
|
+
const sensor = { enabled: true, variantId: 'eslint-9', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, initializedCompatibility: { state: 'certified', reason: 'ok', variantId: 'other', toolVersion: '9.0.0', runtimeVersion: '24.0.0', certifiedRange: '>=9 <10', evidence: [] } };
|
|
45
|
+
expect(() => (0, manifest_1.parseSensorManifest)({ schemaVersion: 2, pack: 'js-ts', sensors: { lint: sensor } }, { path: 'bad' })).toThrow('<unknown source>');
|
|
46
|
+
expect(() => (0, manifest_1.parseSensorManifest)({ schemaVersion: 2, pack: 'js-ts', sensors: { lint: sensor } }, 'source\nleak')).toThrow('<unknown source>');
|
|
47
|
+
});
|
|
48
|
+
it('rejects invalid semver evidence independently', () => {
|
|
49
|
+
const sensor = { enabled: true, variantId: 'eslint-9', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, initializedCompatibility: { state: 'certified', reason: 'ok', variantId: 'eslint-9', toolVersion: 'bad', runtimeVersion: '24.0.0', certifiedRange: '>=9 <10', evidence: [] } };
|
|
50
|
+
expect(() => (0, manifest_1.parseSensorManifest)({ schemaVersion: 2, pack: 'js-ts', sensors: { lint: sensor } }, 'source')).toThrow('toolVersion');
|
|
51
|
+
});
|
|
52
|
+
it('rejects certified evidence without a selected variant', () => {
|
|
53
|
+
const sensor = { enabled: true, variantId: 'eslint-9', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, initializedCompatibility: { state: 'certified', reason: 'ok', variantId: null, toolVersion: '9.0.0', runtimeVersion: '24.0.0', certifiedRange: '>=9 <10', evidence: [] } };
|
|
54
|
+
expect(() => (0, manifest_1.parseSensorManifest)({ schemaVersion: 2, pack: 'js-ts', sensors: { lint: sensor } }, 'source')).toThrow('variantId');
|
|
55
|
+
});
|
|
56
|
+
it('rejects a certified tool version outside its certified range', () => {
|
|
57
|
+
const sensor = { enabled: true, variantId: 'eslint-9', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, initializedCompatibility: { state: 'certified', reason: 'ok', variantId: 'eslint-9', toolVersion: '10.0.0', runtimeVersion: '24.0.0', certifiedRange: '>=9 <10', evidence: [] } };
|
|
58
|
+
expect(() => (0, manifest_1.parseSensorManifest)({ schemaVersion: 2, pack: 'js-ts', sensors: { lint: sensor } }, 'source')).toThrow('certifiedRange');
|
|
59
|
+
});
|
|
60
|
+
test.each([
|
|
61
|
+
[null, 'object'],
|
|
62
|
+
[{}, 'pack'],
|
|
63
|
+
[{ schemaVersion: 3, pack: 'js-ts', sensors: {} }, 'schemaVersion'],
|
|
64
|
+
[{ schemaVersion: 2, pack: 'js-ts', sensors: { lint: { variantId: 'eslint-9', command: { executable: 'eslint', resolution: 'path', args: [] } } } }, 'enabled'],
|
|
65
|
+
])('rejects malformed manifest %j', (input, message) => {
|
|
66
|
+
expect(() => (0, manifest_1.parseSensorManifest)(input, 'sensors.json')).toThrow(message);
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const fs_1 = __importDefault(require("fs"));
|
|
7
|
+
const os_1 = __importDefault(require("os"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const materialize_1 = require("../../../../src/commands/sensors/compatibility/materialize");
|
|
10
|
+
const evidence = { state: 'certified', reason: 'range-and-probe', variantId: 'eslint-10', toolVersion: '10.0.0', runtimeVersion: '22.0.0', certifiedRange: '>=10 <11', evidence: [] };
|
|
11
|
+
describe('materializeResolvedSensors', () => {
|
|
12
|
+
let projectRoot;
|
|
13
|
+
let packRoot;
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
projectRoot = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-materialize-project-'));
|
|
16
|
+
packRoot = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-materialize-pack-'));
|
|
17
|
+
fs_1.default.writeFileSync(path_1.default.join(packRoot, 'eslint.config.awm.mjs'), 'export default [];');
|
|
18
|
+
fs_1.default.writeFileSync(path_1.default.join(packRoot, 'tsconfig.awm.json'), '{}');
|
|
19
|
+
});
|
|
20
|
+
afterEach(() => {
|
|
21
|
+
fs_1.default.rmSync(projectRoot, { recursive: true, force: true });
|
|
22
|
+
fs_1.default.rmSync(packRoot, { recursive: true, force: true });
|
|
23
|
+
});
|
|
24
|
+
it('copies only selected assets and writes a v2 manifest atomically', () => {
|
|
25
|
+
const result = (0, materialize_1.materializeResolvedSensors)({ projectRoot, packRoot, pack: 'js-ts', sensors: {
|
|
26
|
+
lint: { enabled: true, variantId: 'eslint-10', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, assets: ['eslint.config.awm.mjs'], initializedCompatibility: evidence },
|
|
27
|
+
} });
|
|
28
|
+
expect(result.configured).toEqual(['eslint.config.awm.mjs']);
|
|
29
|
+
expect(fs_1.default.existsSync(path_1.default.join(projectRoot, 'tsconfig.awm.json'))).toBe(false);
|
|
30
|
+
expect(JSON.parse(fs_1.default.readFileSync(path_1.default.join(projectRoot, '.awm', 'sensors.json'), 'utf8'))).toMatchObject({ schemaVersion: 2, sensors: { lint: { variantId: 'eslint-10' } } });
|
|
31
|
+
expect(fs_1.default.readdirSync(path_1.default.join(projectRoot, '.awm'))).not.toContain('sensors.json.tmp');
|
|
32
|
+
});
|
|
33
|
+
it('preserves a destination that already exists', () => {
|
|
34
|
+
fs_1.default.writeFileSync(path_1.default.join(projectRoot, 'eslint.config.awm.mjs'), 'owner content');
|
|
35
|
+
const result = (0, materialize_1.materializeResolvedSensors)({ projectRoot, packRoot, pack: 'js-ts', sensors: {
|
|
36
|
+
lint: { enabled: true, variantId: 'eslint-10', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, assets: ['eslint.config.awm.mjs'], initializedCompatibility: evidence },
|
|
37
|
+
} });
|
|
38
|
+
expect(result.preserved).toEqual(['eslint.config.awm.mjs']);
|
|
39
|
+
expect(fs_1.default.readFileSync(path_1.default.join(projectRoot, 'eslint.config.awm.mjs'), 'utf8')).toBe('owner content');
|
|
40
|
+
});
|
|
41
|
+
it('reports previous AWM assets as orphaned and never deletes them', () => {
|
|
42
|
+
fs_1.default.mkdirSync(path_1.default.join(projectRoot, '.awm'));
|
|
43
|
+
fs_1.default.writeFileSync(path_1.default.join(projectRoot, '.awm', 'sensors.json'), JSON.stringify({ schemaVersion: 2, pack: 'js-ts', sensors: {
|
|
44
|
+
lint: { enabled: true, variantId: 'old', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, assets: ['eslint.config.awm.cjs'], initializedCompatibility: { ...evidence, variantId: 'old' } },
|
|
45
|
+
} }));
|
|
46
|
+
fs_1.default.writeFileSync(path_1.default.join(projectRoot, 'eslint.config.awm.cjs'), 'old');
|
|
47
|
+
const result = (0, materialize_1.materializeResolvedSensors)({ projectRoot, packRoot, pack: 'js-ts', sensors: {
|
|
48
|
+
lint: { enabled: true, variantId: 'eslint-10', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] }, assets: ['eslint.config.awm.mjs'], initializedCompatibility: evidence },
|
|
49
|
+
} });
|
|
50
|
+
expect(result.orphaned).toEqual(['eslint.config.awm.cjs']);
|
|
51
|
+
expect(fs_1.default.existsSync(path_1.default.join(projectRoot, 'eslint.config.awm.cjs'))).toBe(true);
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const fs_1 = __importDefault(require("fs"));
|
|
7
|
+
const os_1 = __importDefault(require("os"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const pack_source_1 = require("../../../../src/commands/sensors/compatibility/pack-source");
|
|
10
|
+
describe('resolvePackSource', () => {
|
|
11
|
+
it('uses the first registry containing the exact contained pack file', () => {
|
|
12
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-pack-source-'));
|
|
13
|
+
try {
|
|
14
|
+
const first = path_1.default.join(root, 'first');
|
|
15
|
+
const second = path_1.default.join(root, 'second');
|
|
16
|
+
for (const registry of [first, second])
|
|
17
|
+
fs_1.default.mkdirSync(path_1.default.join(registry, 'sensor-packs', 'js-ts'), { recursive: true });
|
|
18
|
+
fs_1.default.writeFileSync(path_1.default.join(first, 'sensor-packs', 'js-ts', 'pack.json'), '{"from":"first"}');
|
|
19
|
+
fs_1.default.writeFileSync(path_1.default.join(second, 'sensor-packs', 'js-ts', 'pack.json'), '{"from":"second"}');
|
|
20
|
+
expect((0, pack_source_1.resolvePackSource)('js-ts', { registries: [{ name: 'first', remote: '', contentRoot: first }, { name: 'second', remote: '', contentRoot: second }] }).content).toContain('first');
|
|
21
|
+
}
|
|
22
|
+
finally {
|
|
23
|
+
fs_1.default.rmSync(root, { recursive: true, force: true });
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
it('rejects symlink, non-regular, and escaping pack sources', () => {
|
|
27
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-pack-source-bad-'));
|
|
28
|
+
try {
|
|
29
|
+
const packs = path_1.default.join(root, 'sensor-packs');
|
|
30
|
+
fs_1.default.mkdirSync(packs, { recursive: true });
|
|
31
|
+
fs_1.default.symlinkSync(path_1.default.join(root, 'outside.json'), path_1.default.join(packs, 'js-ts'));
|
|
32
|
+
fs_1.default.writeFileSync(path_1.default.join(root, 'outside.json'), '{}');
|
|
33
|
+
expect(() => (0, pack_source_1.resolvePackSource)('js-ts', { registries: [{ name: 'bad', remote: '', contentRoot: root }] })).toThrow(/symbolic|contain/i);
|
|
34
|
+
expect(() => (0, pack_source_1.resolvePackSource)('../escape', { registries: [] })).toThrow(/pack/i);
|
|
35
|
+
}
|
|
36
|
+
finally {
|
|
37
|
+
fs_1.default.rmSync(root, { recursive: true, force: true });
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
it('rejects a symlinked pack directory even when its target remains inside the registry', () => {
|
|
41
|
+
const root = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-pack-source-internal-link-'));
|
|
42
|
+
try {
|
|
43
|
+
const packRoot = path_1.default.join(root, 'sensor-packs');
|
|
44
|
+
const target = path_1.default.join(packRoot, 'js-ts-source');
|
|
45
|
+
fs_1.default.mkdirSync(target, { recursive: true });
|
|
46
|
+
fs_1.default.writeFileSync(path_1.default.join(target, 'pack.json'), '{}');
|
|
47
|
+
fs_1.default.symlinkSync(target, path_1.default.join(packRoot, 'js-ts'), 'dir');
|
|
48
|
+
expect(() => (0, pack_source_1.resolvePackSource)('js-ts', { registries: [{ name: 'linked', remote: '', contentRoot: root }] }))
|
|
49
|
+
.toThrow(/symbolic|symlink/i);
|
|
50
|
+
}
|
|
51
|
+
finally {
|
|
52
|
+
fs_1.default.rmSync(root, { recursive: true, force: true });
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const probe_1 = require("../../../../src/commands/sensors/compatibility/probe");
|
|
4
|
+
const fakeExecutor = jest.fn(async () => ({ code: 0, signal: null, timedOut: false, overflowed: false, stdout: 'SECRET_VALUE\nmatched', stderr: '' }));
|
|
5
|
+
const evidence = { cwd: process.cwd(), configFiles: ['eslint.config.js'], scripts: ['lint'] };
|
|
6
|
+
describe('runCompatibilityProbe', () => {
|
|
7
|
+
beforeEach(() => fakeExecutor.mockClear());
|
|
8
|
+
test.each(['eslint-print-config', 'typescript-show-config', 'semgrep-validate', 'version', 'package-script-present', 'config-present'])('runs the closed probe %s with sanitized output', async (kind) => {
|
|
9
|
+
const result = await (0, probe_1.runCompatibilityProbe)({ kind }, evidence, fakeExecutor);
|
|
10
|
+
expect(['matched', 'unverifiable']).toContain(result.status);
|
|
11
|
+
expect(JSON.stringify(result)).not.toContain('SECRET_VALUE');
|
|
12
|
+
});
|
|
13
|
+
it('never treats a timeout or overflow as a match', async () => {
|
|
14
|
+
fakeExecutor.mockResolvedValueOnce({ code: null, signal: 'SIGKILL', timedOut: true, overflowed: false, stdout: '', stderr: '' });
|
|
15
|
+
await expect((0, probe_1.runCompatibilityProbe)({ kind: 'version' }, evidence, fakeExecutor)).resolves.toMatchObject({ status: 'unverifiable' });
|
|
16
|
+
fakeExecutor.mockResolvedValueOnce({ code: 0, signal: null, timedOut: false, overflowed: true, stdout: 'ok', stderr: '' });
|
|
17
|
+
await expect((0, probe_1.runCompatibilityProbe)({ kind: 'version' }, evidence, fakeExecutor)).resolves.toMatchObject({ status: 'unverifiable' });
|
|
18
|
+
});
|
|
19
|
+
it('binds tool probes to the project node_modules executable instead of PATH', async () => {
|
|
20
|
+
await (0, probe_1.runCompatibilityProbe)({ kind: 'eslint-print-config' }, evidence, fakeExecutor);
|
|
21
|
+
expect(fakeExecutor).toHaveBeenCalledWith(expect.objectContaining({ executable: 'eslint', resolution: 'node-modules-bin' }), expect.any(Object));
|
|
22
|
+
});
|
|
23
|
+
it('binds Semgrep validation to the contained Python environment', async () => {
|
|
24
|
+
await (0, probe_1.runCompatibilityProbe)({ kind: 'semgrep-validate' }, evidence, fakeExecutor);
|
|
25
|
+
expect(fakeExecutor).toHaveBeenCalledWith(expect.objectContaining({ executable: 'semgrep', resolution: 'python-environment' }), expect.any(Object));
|
|
26
|
+
});
|
|
27
|
+
});
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const resolve_1 = require("../../../../src/commands/sensors/compatibility/resolve");
|
|
4
|
+
const manifest_1 = require("../../../../src/commands/sensors/compatibility/manifest");
|
|
5
|
+
const variant = (id, priority = 10, toolRange = '>=9 <12', certifiedRange = '>=10 <11') => ({
|
|
6
|
+
id, priority, certifiedRange,
|
|
7
|
+
requirements: { tool: 'eslint', toolRange, runtime: 'node', runtimeRange: '>=20' },
|
|
8
|
+
assets: [], formatter: 'eslint', probe: { kind: 'version' },
|
|
9
|
+
command: { executable: 'eslint', resolution: 'path', args: ['--version'] },
|
|
10
|
+
});
|
|
11
|
+
const sensor = { applicability: { allFiles: ['package.json'] }, variants: [variant('eslint-main')] };
|
|
12
|
+
const evidence = (input = {}) => ({ paths: ['package.json'], applicable: true, packageManagerConflict: false, toolVersion: '10.4.1', runtimeVersion: '22.0.0', probe: { status: 'matched' }, ...input });
|
|
13
|
+
const context = { pack: 'js-ts', sensor: 'lint' };
|
|
14
|
+
describe('resolveSensorCompatibility', () => {
|
|
15
|
+
test.each([
|
|
16
|
+
['certified', evidence(), 'eslint-main'],
|
|
17
|
+
['compatible-unverified', evidence({ toolVersion: '11.0.0' }), 'eslint-main'],
|
|
18
|
+
['incompatible', evidence({ toolVersion: '8.0.0' }), null],
|
|
19
|
+
['missing-tool', evidence({ toolVersion: null }), null],
|
|
20
|
+
['unverifiable', evidence({ probe: { status: 'unverifiable' } }), 'eslint-main'],
|
|
21
|
+
['not-applicable', evidence({ applicable: false, paths: [] }), null],
|
|
22
|
+
])('resolves %s without conflating states', (state, discovered, variantId) => {
|
|
23
|
+
expect((0, resolve_1.resolveSensorCompatibility)(sensor, discovered, context)).toMatchObject({ state, variantId });
|
|
24
|
+
});
|
|
25
|
+
it('preserves legacy behavior without executing a probe', () => {
|
|
26
|
+
expect((0, resolve_1.resolveSensorCompatibility)({ defaultCmd: 'eslint .' }, evidence(), context)).toEqual((0, manifest_1.legacyCompatibility)('legacy pack without schemaVersion'));
|
|
27
|
+
});
|
|
28
|
+
it('fails an equal-precedence match with its pack, sensor, IDs, and ranges', () => {
|
|
29
|
+
expect(() => (0, resolve_1.resolveProjectCompatibility)({ schemaVersion: 2, name: 'js-ts', sensors: { lint: { ...sensor, variants: [variant('one'), variant('two')] } } }, evidence())).toThrow(/js-ts.*lint.*one.*>=9 <12.*two.*>=9 <12/i);
|
|
30
|
+
});
|
|
31
|
+
it('requires a real pack and sensor identity for direct resolution', () => {
|
|
32
|
+
expect(() => (0, resolve_1.resolveSensorCompatibility)(sensor, evidence(), undefined)).toThrow(/pack and sensor identity are required/i);
|
|
33
|
+
});
|
|
34
|
+
it('reports lockfile conflicts as unverifiable', () => {
|
|
35
|
+
expect((0, resolve_1.resolveSensorCompatibility)(sensor, evidence({ packageManagerConflict: true }), context)).toMatchObject({ state: 'unverifiable', reason: 'package-manager-conflict' });
|
|
36
|
+
});
|
|
37
|
+
it('selects an operational variant when another tool variant is absent locally', () => {
|
|
38
|
+
const variants = [
|
|
39
|
+
{ ...variant('eslint'), requirements: { ...variant('eslint').requirements, tool: 'eslint' } },
|
|
40
|
+
{ ...variant('biome'), requirements: { ...variant('biome').requirements, tool: 'biome' } },
|
|
41
|
+
];
|
|
42
|
+
const resolved = (0, resolve_1.resolveSensorCompatibility)({ applicability: { allFiles: ['package.json'] }, variants }, evidence({ toolVersions: { eslint: '10.4.1', biome: null }, runtimeVersions: { node: '22.0.0' }, toolVersion: '8.0.0', runtimeVersion: '8.0.0' }), context);
|
|
43
|
+
expect(resolved).toMatchObject({ state: 'certified', variantId: 'eslint', toolVersion: '10.4.1', runtimeVersion: '22.0.0' });
|
|
44
|
+
});
|
|
45
|
+
it('does not reuse scalar evidence for a missing key in a version map', () => {
|
|
46
|
+
const biome = { ...variant('biome'), requirements: { ...variant('biome').requirements, tool: 'biome', runtime: 'bun' } };
|
|
47
|
+
expect((0, resolve_1.resolveSensorCompatibility)({ applicability: { allFiles: ['package.json'] }, variants: [biome] }, evidence({ toolVersions: { biome: null }, runtimeVersions: { bun: null }, toolVersion: '10.4.1', runtimeVersion: '22.0.0' }), context))
|
|
48
|
+
.toMatchObject({ state: 'missing-tool' });
|
|
49
|
+
});
|
|
50
|
+
it('carries bounded, sanitized evidence without raw probe output', () => {
|
|
51
|
+
const resolved = (0, resolve_1.resolveSensorCompatibility)(sensor, evidence({ paths: ['package.json', '../SECRET_VALUE'], probe: { status: 'matched', output: 'SECRET_VALUE' } }), context);
|
|
52
|
+
expect(resolved.evidence).toEqual(expect.arrayContaining([{ kind: 'project-path', status: 'present', path: 'package.json' }, { kind: 'probe', status: 'matched' }]));
|
|
53
|
+
expect(JSON.stringify(resolved)).not.toContain('SECRET_VALUE');
|
|
54
|
+
});
|
|
55
|
+
test.each(['linux', 'darwin', 'win32'])('resolves the six states under controlled %s evidence', (os) => {
|
|
56
|
+
const states = [
|
|
57
|
+
(0, resolve_1.resolveSensorCompatibility)(sensor, evidence({ os }), context),
|
|
58
|
+
(0, resolve_1.resolveSensorCompatibility)(sensor, evidence({ os, toolVersion: '11.0.0' }), context),
|
|
59
|
+
(0, resolve_1.resolveSensorCompatibility)(sensor, evidence({ os, toolVersion: '8.0.0' }), context),
|
|
60
|
+
(0, resolve_1.resolveSensorCompatibility)(sensor, evidence({ os, toolVersion: null }), context),
|
|
61
|
+
(0, resolve_1.resolveSensorCompatibility)(sensor, evidence({ os, probe: { status: 'unverifiable' } }), context),
|
|
62
|
+
(0, resolve_1.resolveSensorCompatibility)(sensor, evidence({ os, applicable: false, paths: [] }), context),
|
|
63
|
+
].map(result => result.state);
|
|
64
|
+
expect(states).toEqual(['certified', 'compatible-unverified', 'incompatible', 'missing-tool', 'unverifiable', 'not-applicable']);
|
|
65
|
+
});
|
|
66
|
+
test.each(['linux', 'darwin', 'win32'])('keeps applicability and deterministic precedence above version resolution on %s', (os) => {
|
|
67
|
+
const broad = variant('broad', 10, '>=9 <12');
|
|
68
|
+
const narrow = variant('narrow', 10, '>=10 <11');
|
|
69
|
+
const precedenceSensor = { applicability: { allFiles: ['package.json'] }, variants: [broad, narrow] };
|
|
70
|
+
expect((0, resolve_1.resolveSensorCompatibility)(precedenceSensor, evidence({ os, packageManagerConflict: true, applicable: false, paths: [] }), context))
|
|
71
|
+
.toMatchObject({ state: 'not-applicable', reason: 'applicability-not-met' });
|
|
72
|
+
expect((0, resolve_1.resolveSensorCompatibility)(precedenceSensor, evidence({ os }), context)).toMatchObject({ variantId: 'narrow' });
|
|
73
|
+
expect(() => (0, resolve_1.resolveSensorCompatibility)({ ...precedenceSensor, variants: [variant('a'), variant('b')] }, evidence({ os }), context)).toThrow(/ambiguous/i);
|
|
74
|
+
});
|
|
75
|
+
});
|
|
@@ -12,7 +12,7 @@ describe('coverage contract v1', () => {
|
|
|
12
12
|
sensor: 'contract-test',
|
|
13
13
|
evidence: {
|
|
14
14
|
commandIncludes: ['coverage'],
|
|
15
|
-
files: [{ path: '
|
|
15
|
+
files: [{ path: 'eslint.config.awm.mjs', containsAll: ['parseCoverageContract'] }],
|
|
16
16
|
},
|
|
17
17
|
}],
|
|
18
18
|
remedy: { summary: 'Add a parser.', command: 'npm test -- contract.test.ts' },
|
|
@@ -32,7 +32,7 @@ describe('coverage contract v1', () => {
|
|
|
32
32
|
])('rejects malformed contract %j', (input, message) => {
|
|
33
33
|
expect(() => (0, contract_1.parseCoverageContract)(input, 'coverage.json')).toThrow(message);
|
|
34
34
|
});
|
|
35
|
-
test.each(['', '.', '..', 'a..b', '../secret', 'a/../../secret', '/etc/passwd', 'C:\\secret', 'a\\..\\secret', ' report.txt', 'report!.txt', 'ñ.txt', '.env', '.gitignore'])('rejects hostile evidence path %p', (path) => {
|
|
35
|
+
test.each(['', '.', '..', 'a..b', '../secret', 'a/../../secret', '/etc/passwd', 'C:\\secret', 'a\\..\\secret', ' report.txt', 'report!.txt', 'ñ.txt', '.env', '.gitignore', 'secrets.txt', 'id_rsa'])('rejects hostile evidence path %p', (path) => {
|
|
36
36
|
const input = {
|
|
37
37
|
schemaVersion: 1,
|
|
38
38
|
classes: {
|
|
@@ -45,7 +45,17 @@ describe('coverage contract v1', () => {
|
|
|
45
45
|
};
|
|
46
46
|
expect(() => (0, contract_1.parseCoverageContract)(input, 'coverage.json')).toThrow('path');
|
|
47
47
|
});
|
|
48
|
-
test.each([
|
|
48
|
+
test.each([
|
|
49
|
+
'eslint.config.awm.mjs',
|
|
50
|
+
'eslint.config.js',
|
|
51
|
+
'eslint.config.mjs',
|
|
52
|
+
'eslint.config.cjs',
|
|
53
|
+
'eslint.config.ts',
|
|
54
|
+
'eslint.config.mts',
|
|
55
|
+
'eslint.config.cts',
|
|
56
|
+
'.semgrep.awm.yml',
|
|
57
|
+
'.dep-cruiser.awm.js',
|
|
58
|
+
])('accepts contractual evidence path %p', (path) => {
|
|
49
59
|
const input = {
|
|
50
60
|
schemaVersion: 1,
|
|
51
61
|
classes: {
|
|
@@ -64,7 +74,7 @@ describe('coverage contract v1', () => {
|
|
|
64
74
|
classes: {
|
|
65
75
|
valid: {
|
|
66
76
|
description: 'x',
|
|
67
|
-
detectors: [{ sensor: 'test', evidence: { files: [{ path: '
|
|
77
|
+
detectors: [{ sensor: 'test', evidence: { files: [{ path: 'eslint.config.awm.mjs', containsAll: [' \n'] }] } }],
|
|
68
78
|
remedy: { summary: 'x', command: 'x' },
|
|
69
79
|
},
|
|
70
80
|
},
|
|
@@ -84,31 +94,17 @@ describe('coverage contract v1', () => {
|
|
|
84
94
|
};
|
|
85
95
|
expect(() => (0, contract_1.parseCoverageContract)(input, 'coverage.json')).toThrow('unknown field');
|
|
86
96
|
});
|
|
87
|
-
|
|
88
|
-
describe('coverage manifest boundary', () => {
|
|
89
|
-
it('accepts all legacy sensor fields', () => {
|
|
97
|
+
it('rejects a detector sensor name that is not a safe component', () => {
|
|
90
98
|
const input = {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
99
|
+
schemaVersion: 1,
|
|
100
|
+
classes: {
|
|
101
|
+
valid: {
|
|
102
|
+
description: 'x',
|
|
103
|
+
detectors: [{ sensor: '../escape' }],
|
|
104
|
+
remedy: { summary: 'x', command: 'x' },
|
|
96
105
|
},
|
|
97
106
|
},
|
|
98
107
|
};
|
|
99
|
-
expect((0, contract_1.
|
|
100
|
-
});
|
|
101
|
-
test.each([
|
|
102
|
-
[null, 'object'],
|
|
103
|
-
[{}, 'pack'],
|
|
104
|
-
[{ pack: '', sensors: {} }, 'pack'],
|
|
105
|
-
[{ pack: ' js-ts', sensors: {} }, 'pack'],
|
|
106
|
-
[{ pack: 'js ts', sensors: {} }, 'pack'],
|
|
107
|
-
[{ pack: 'js@ts', sensors: {} }, 'pack'],
|
|
108
|
-
[{ pack: 'js-ts', sensors: null }, 'sensors'],
|
|
109
|
-
[{ pack: 'js-ts', sensors: { lint: { cmd: 3 } } }, 'cmd'],
|
|
110
|
-
[{ pack: 'js-ts', sensors: { 'lint!': {} } }, 'sensor name'],
|
|
111
|
-
])('rejects malformed manifest %j', (input, message) => {
|
|
112
|
-
expect(() => (0, contract_1.parseCoverageManifest)(input, 'sensors.json')).toThrow(message);
|
|
108
|
+
expect(() => (0, contract_1.parseCoverageContract)(input, 'coverage.json')).toThrow('sensor');
|
|
113
109
|
});
|
|
114
110
|
});
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const empirical_1 = require("../../../../src/commands/sensors/coverage/empirical");
|
|
4
|
+
const finding = (defectClass, signature, ref, severity = 'minor') => ({
|
|
5
|
+
entry: { ts: '2026-08-14', branch: 'main', phase: 'qa', source_skill: 'test', polarity: 'finding',
|
|
6
|
+
class: 'structural', signature, severity, desc: 'private ledger description', ...(defectClass ? { defectClass } : {}) },
|
|
7
|
+
source: ref,
|
|
8
|
+
evidenceRef: ref,
|
|
9
|
+
});
|
|
10
|
+
const scanOf = (entries) => ({
|
|
11
|
+
entries,
|
|
12
|
+
sources: { activeFiles: 1, archivedFiles: 0, validEntries: entries.length, validFindings: entries.length, skippedFindings: 0, skippedByReason: {} },
|
|
13
|
+
omittedEvidenceRefs: 0,
|
|
14
|
+
});
|
|
15
|
+
test('distinguishes evidence, partial, no-evidence and inconclusive ledger states (R5.11)', () => {
|
|
16
|
+
const valid = scanOf([finding('lint-errors', 'valid', 'src/a.ts:1')]);
|
|
17
|
+
expect((0, empirical_1.evaluateEmpiricalCoverage)(valid, { 'lint-errors': 'covered' }, 2).status).toBe('evidence');
|
|
18
|
+
expect((0, empirical_1.evaluateEmpiricalCoverage)({ ...valid, sources: { ...valid.sources, skippedFindings: 1, skippedByReason: { 'invalid-json': 1 } } }, { 'lint-errors': 'covered' }, 2).status).toBe('partial');
|
|
19
|
+
expect((0, empirical_1.evaluateEmpiricalCoverage)(scanOf([]), {}, 2).status).toBe('no-evidence');
|
|
20
|
+
expect((0, empirical_1.evaluateEmpiricalCoverage)({ ...scanOf([]), sources: { activeFiles: 1, archivedFiles: 0, validEntries: 0, validFindings: 0, skippedFindings: 1, skippedByReason: { 'invalid-json': 1 } } }, {}, 2).status).toBe('inconclusive');
|
|
21
|
+
});
|
|
22
|
+
test('clusters only inside defectClass and keeps singles below min (R5.4, R5.6)', () => {
|
|
23
|
+
const report = (0, empirical_1.evaluateEmpiricalCoverage)(scanOf([
|
|
24
|
+
finding('lint-errors', 'same-signature', 'a.ts:1'),
|
|
25
|
+
finding('static-type-errors', 'same-signature', 'b.ts:1'),
|
|
26
|
+
]), { 'lint-errors': 'covered', 'static-type-errors': 'covered' }, 2);
|
|
27
|
+
expect(report.classes).toHaveLength(2);
|
|
28
|
+
expect(report.classes.every((item) => item.occurrences === 1 && item.recurrent === false)).toBe(true);
|
|
29
|
+
});
|
|
30
|
+
test('preserves cluster kind and only sorted safe signatures in public empirical clusters (R5.5)', () => {
|
|
31
|
+
const report = (0, empirical_1.evaluateEmpiricalCoverage)(scanOf([
|
|
32
|
+
finding('lint-errors', 'src/z.ts:9', 'src/z.ts:9'),
|
|
33
|
+
finding('lint-errors', 'PR #12', 'src/z.ts:10'),
|
|
34
|
+
finding('lint-errors', 'secret-token=do-not-expose', 'src/z.ts:11'),
|
|
35
|
+
]), { 'lint-errors': 'covered' }, 1);
|
|
36
|
+
expect(report.classes).toHaveLength(1);
|
|
37
|
+
expect(report.classes[0].clusters).toEqual([expect.objectContaining({
|
|
38
|
+
kind: 'convergent',
|
|
39
|
+
signatures: ['PR #12', 'src/z.ts:9'],
|
|
40
|
+
omittedSignatures: 1,
|
|
41
|
+
})]);
|
|
42
|
+
expect(JSON.stringify(report)).not.toContain('secret-token=do-not-expose');
|
|
43
|
+
});
|
|
44
|
+
test.each([
|
|
45
|
+
['covered', 'covered-by-sensor'], ['missing', 'gap'], ['incompatible', 'gap'],
|
|
46
|
+
['missing-tool', 'gap'], ['unverifiable', 'coverage-unverifiable'],
|
|
47
|
+
['compatible-unverified', 'coverage-unverifiable'], ['not-applicable', 'applicability-contradiction'],
|
|
48
|
+
])('crosses %s to %s (R5.9)', (staticState, outcome) => {
|
|
49
|
+
expect((0, empirical_1.outcomeFor)(staticState, true)).toBe(outcome);
|
|
50
|
+
});
|
|
51
|
+
test('maps a valid defect class absent from the static contract to unmapped-class (R5.12)', () => {
|
|
52
|
+
expect((0, empirical_1.outcomeFor)(undefined, true)).toBe('unmapped-class');
|
|
53
|
+
expect((0, empirical_1.evaluateEmpiricalCoverage)(scanOf([finding('legacy-lint', 'legacy', 'src/a.ts:1')]), {}, 2).classes[0].outcome)
|
|
54
|
+
.toBe('unmapped-class');
|
|
55
|
+
});
|
|
56
|
+
test('treats a class as coverage-unverifiable when the static catalog is unavailable', () => {
|
|
57
|
+
const scan = scanOf([finding('legacy-lint', 'legacy', 'src/a.ts:1')]);
|
|
58
|
+
expect((0, empirical_1.evaluateEmpiricalCoverage)(scan, {}, 2, 'unavailable').classes[0].outcome)
|
|
59
|
+
.toBe('coverage-unverifiable');
|
|
60
|
+
});
|
|
61
|
+
test('changes recurrence emphasis and stable ordering when --min changes (R5.13)', () => {
|
|
62
|
+
const entries = scanOf([
|
|
63
|
+
{ ...finding('lint-errors', 'solo-orange', 'src/a.ts:1'), entry: { ...finding('lint-errors', 'solo-orange', 'src/a.ts:1').entry, desc: 'orchard fruit gamma' } },
|
|
64
|
+
{ ...finding('lint-errors', 'repeat-protocol', 'src/b.ts:1'), entry: { ...finding('lint-errors', 'repeat-protocol', 'src/b.ts:1').entry, desc: 'network handshake delta' } },
|
|
65
|
+
{ ...finding('lint-errors', 'repeat-protocol', 'src/b.ts:2'), entry: { ...finding('lint-errors', 'repeat-protocol', 'src/b.ts:2').entry, desc: 'network handshake delta' } },
|
|
66
|
+
]);
|
|
67
|
+
const minTwo = (0, empirical_1.evaluateEmpiricalCoverage)(entries, { 'lint-errors': 'covered' }, 2);
|
|
68
|
+
const minThree = (0, empirical_1.evaluateEmpiricalCoverage)(entries, { 'lint-errors': 'covered' }, 3);
|
|
69
|
+
expect(minTwo.recurrenceThreshold).toBe(2);
|
|
70
|
+
expect(minTwo.classes.map((item) => [item.occurrences, item.recurrent])).toEqual([[3, true]]);
|
|
71
|
+
expect(minTwo.classes[0].clusters).toHaveLength(2);
|
|
72
|
+
expect(minThree.recurrenceThreshold).toBe(3);
|
|
73
|
+
expect(minThree.classes.map((item) => [item.occurrences, item.recurrent])).toEqual([[3, false]]);
|
|
74
|
+
expect(JSON.stringify(minTwo)).not.toEqual(JSON.stringify(minThree));
|
|
75
|
+
});
|
|
76
|
+
test('does not infer a missing class from text and never emits description or signature (R5.3, R5.10)', () => {
|
|
77
|
+
const report = (0, empirical_1.evaluateEmpiricalCoverage)(scanOf([finding(undefined, 'secret-signature', 'src/a.ts:2')]), { 'lint-errors': 'covered' }, 2);
|
|
78
|
+
expect(report.unclassified.occurrences).toBe(1);
|
|
79
|
+
expect(report.classes).toEqual([]);
|
|
80
|
+
expect(JSON.stringify(report)).not.toContain('private ledger description');
|
|
81
|
+
expect(JSON.stringify(report)).not.toContain('secret-signature');
|
|
82
|
+
});
|
|
83
|
+
test('sanitizes, deduplicates and bounds allowed evidence refs (R5.7, R5.8)', () => {
|
|
84
|
+
const report = (0, empirical_1.evaluateEmpiricalCoverage)(scanOf([
|
|
85
|
+
finding('lint-errors', 'a', '\u001b]8;;https://evil\u0007src/z.ts:8'),
|
|
86
|
+
finding('lint-errors', 'b', 'PR #12', 'blocker'),
|
|
87
|
+
finding('lint-errors', 'c', 'PR #12'),
|
|
88
|
+
finding('lint-errors', 'd', 'not an allowed ref'),
|
|
89
|
+
]), { 'lint-errors': 'missing' }, 1);
|
|
90
|
+
expect(report.classes[0]).toMatchObject({ outcome: 'gap', severity: 'blocker', evidenceRefs: ['PR #12', 'src/z.ts:8'], omittedEvidenceRefs: 1 });
|
|
91
|
+
expect(JSON.stringify(report)).not.toMatch(/[\u0000-\u001f\u007f-\u009f]/);
|
|
92
|
+
});
|
|
93
|
+
test('includes invalid and scanner-omitted evidence refs in the partial public status', () => {
|
|
94
|
+
const scan = {
|
|
95
|
+
...scanOf([finding('lint-errors', 'invalid-ref', 'not an allowed ref')]),
|
|
96
|
+
sources: {
|
|
97
|
+
activeFiles: 1, archivedFiles: 0, validEntries: 1, validFindings: 1,
|
|
98
|
+
skippedFindings: 3, skippedByReason: { 'evidence-ref-limit': 3 },
|
|
99
|
+
},
|
|
100
|
+
omittedEvidenceRefs: 3,
|
|
101
|
+
};
|
|
102
|
+
const report = (0, empirical_1.evaluateEmpiricalCoverage)(scan, { 'lint-errors': 'covered' }, 2);
|
|
103
|
+
expect(report).toMatchObject({ status: 'partial', omittedEvidenceRefs: 4 });
|
|
104
|
+
expect(report.classes[0]).toMatchObject({ omittedEvidenceRefs: 1, evidenceRefs: [] });
|
|
105
|
+
});
|
|
106
|
+
test('does not double-count scanner-truncated evidence refs', () => {
|
|
107
|
+
const scan = { ...scanOf([finding('lint-errors', 'a', 'src/a.ts:1')]),
|
|
108
|
+
entries: [{ ...finding('lint-errors', 'a', 'src/a.ts:1'), evidenceRef: null }], omittedEvidenceRefs: 1 };
|
|
109
|
+
const report = (0, empirical_1.evaluateEmpiricalCoverage)(scan, { 'lint-errors': 'covered' }, 2);
|
|
110
|
+
expect(report.omittedEvidenceRefs).toBe(1);
|
|
111
|
+
});
|