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.
Files changed (93) hide show
  1. package/dist/scripts/sensor-support-matrix.js +103 -0
  2. package/dist/scripts/support-matrix.js +40 -17
  3. package/dist/src/commands/init.js +1 -3
  4. package/dist/src/commands/ledger/index.js +6 -0
  5. package/dist/src/commands/preflight/checks.js +4 -4
  6. package/dist/src/commands/preflight/index.js +2 -2
  7. package/dist/src/commands/registry/add.js +2 -2
  8. package/dist/src/commands/registry/index.js +5 -0
  9. package/dist/src/commands/registry/status.js +7 -0
  10. package/dist/src/commands/sensors/compatibility/contract.js +261 -0
  11. package/dist/src/commands/sensors/compatibility/discovery.js +207 -0
  12. package/dist/src/commands/sensors/compatibility/live.js +59 -0
  13. package/dist/src/commands/sensors/compatibility/manifest.js +197 -0
  14. package/dist/src/commands/sensors/compatibility/materialize.js +126 -0
  15. package/dist/src/commands/sensors/compatibility/pack-source.js +78 -0
  16. package/dist/src/commands/sensors/compatibility/probe.js +47 -0
  17. package/dist/src/commands/sensors/compatibility/resolve.js +96 -0
  18. package/dist/src/commands/sensors/compatibility/types.js +2 -0
  19. package/dist/src/commands/sensors/coverage/contract.js +0 -50
  20. package/dist/src/commands/sensors/coverage/empirical.js +131 -0
  21. package/dist/src/commands/sensors/coverage/evaluate.js +85 -7
  22. package/dist/src/commands/sensors/coverage/evidence.js +5 -1
  23. package/dist/src/commands/sensors/coverage/index.js +44 -7
  24. package/dist/src/commands/sensors/coverage/render.js +214 -14
  25. package/dist/src/commands/sensors/coverage/resolve.js +24 -38
  26. package/dist/src/commands/sensors/exec.js +150 -9
  27. package/dist/src/commands/sensors/index.js +18 -6
  28. package/dist/src/commands/sensors/init.js +92 -6
  29. package/dist/src/commands/sensors/run.js +71 -0
  30. package/dist/src/commands/sensors/status.js +48 -4
  31. package/dist/src/commands/sync.js +7 -0
  32. package/dist/src/core/bundles.js +38 -3
  33. package/dist/src/core/init/steps.js +7 -4
  34. package/dist/src/core/ledger/scan.js +228 -0
  35. package/dist/src/core/ledger/store.js +89 -25
  36. package/dist/src/core/ledger/types.js +54 -0
  37. package/dist/src/core/paths.js +10 -9
  38. package/dist/src/core/registries.js +134 -14
  39. package/dist/src/index.js +24 -1
  40. package/dist/tests/commands/ledger/index.test.js +11 -0
  41. package/dist/tests/commands/preflight/preflight.test.js +64 -64
  42. package/dist/tests/commands/registry/add.test.js +69 -2
  43. package/dist/tests/commands/registry/status.test.js +14 -0
  44. package/dist/tests/commands/sensors/compatibility/contract.test.js +98 -0
  45. package/dist/tests/commands/sensors/compatibility/discovery.test.js +119 -0
  46. package/dist/tests/commands/sensors/compatibility/manifest.test.js +68 -0
  47. package/dist/tests/commands/sensors/compatibility/materialize.test.js +53 -0
  48. package/dist/tests/commands/sensors/compatibility/pack-source.test.js +55 -0
  49. package/dist/tests/commands/sensors/compatibility/probe.test.js +27 -0
  50. package/dist/tests/commands/sensors/compatibility/resolve.test.js +75 -0
  51. package/dist/tests/commands/sensors/coverage/contract.test.js +8 -22
  52. package/dist/tests/commands/sensors/coverage/empirical.test.js +111 -0
  53. package/dist/tests/commands/sensors/coverage/evaluate.test.js +29 -0
  54. package/dist/tests/commands/sensors/coverage/evidence.test.js +9 -0
  55. package/dist/tests/commands/sensors/coverage/index.test.js +40 -13
  56. package/dist/tests/commands/sensors/coverage/render.test.js +187 -12
  57. package/dist/tests/commands/sensors/coverage/resolve.test.js +25 -4
  58. package/dist/tests/commands/sensors/exec-windows.test.js +92 -2
  59. package/dist/tests/commands/sensors/exec.test.js +67 -5
  60. package/dist/tests/commands/sensors/index.test.js +8 -0
  61. package/dist/tests/commands/sensors/init-pack-unavailable.test.js +12 -12
  62. package/dist/tests/commands/sensors/init.test.js +132 -48
  63. package/dist/tests/commands/sensors/run-inconclusive.test.js +9 -2
  64. package/dist/tests/commands/sensors/run-is-read-only.test.js +0 -0
  65. package/dist/tests/commands/sensors/run.test.js +66 -3
  66. package/dist/tests/commands/sensors/status-windows.test.js +7 -7
  67. package/dist/tests/commands/sensors/status.test.js +74 -23
  68. package/dist/tests/commands/update.test.js +1 -1
  69. package/dist/tests/core/bundles.test.js +37 -0
  70. package/dist/tests/core/discovery.test.js +4 -0
  71. package/dist/tests/core/init/orchestrator.test.js +5 -1
  72. package/dist/tests/core/init/steps.test.js +20 -4
  73. package/dist/tests/core/ledger/scan.test.js +217 -0
  74. package/dist/tests/core/ledger/store.test.js +108 -0
  75. package/dist/tests/core/profile-pins.test.js +2 -1
  76. package/dist/tests/core/project-skill-links.test.js +22 -0
  77. package/dist/tests/core/registries-capability.test.js +10 -0
  78. package/dist/tests/core/registries-sync.test.js +16 -0
  79. package/dist/tests/core/registries.test.js +129 -1
  80. package/dist/tests/core/registry-manifest.test.js +7 -0
  81. package/dist/tests/core/same-existing-path.test.js +35 -0
  82. package/dist/tests/core/sync-gates.test.js +2 -1
  83. package/dist/tests/integration/copilot-init-isolated.test.js +18 -0
  84. package/dist/tests/integration/sensor-compatibility.e2e.test.js +141 -0
  85. package/dist/tests/integration/sensor-coverage.e2e.test.js +35 -6
  86. package/dist/tests/integration/sensor-structured-run.e2e.test.js +127 -0
  87. package/dist/tests/structural/active-documentation.test.js +128 -0
  88. package/dist/tests/structural/async-entrypoint.test.js +15 -0
  89. package/dist/tests/structural/jest-environment-is-isolated.test.js +44 -0
  90. package/dist/tests/structural/r3-cli-major-version.test.js +37 -0
  91. package/dist/tests/structural/sensor-documentation-contract.test.js +77 -0
  92. package/dist/tests/structural/support-matrix-is-current.test.js +69 -2
  93. 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
+ });
@@ -94,31 +94,17 @@ describe('coverage contract v1', () => {
94
94
  };
95
95
  expect(() => (0, contract_1.parseCoverageContract)(input, 'coverage.json')).toThrow('unknown field');
96
96
  });
97
- });
98
- describe('coverage manifest boundary', () => {
99
- it('accepts all legacy sensor fields', () => {
97
+ it('rejects a detector sensor name that is not a safe component', () => {
100
98
  const input = {
101
- pack: 'js-ts',
102
- concurrency: 2,
103
- sensors: {
104
- lint: {
105
- cmd: 'npm run lint', fast: true, enabled: true, timeout: 120, changedCmd: 'npm run lint -- {files}', changedExtensions: ['.ts'], formatter: 'eslint-llm',
99
+ schemaVersion: 1,
100
+ classes: {
101
+ valid: {
102
+ description: 'x',
103
+ detectors: [{ sensor: '../escape' }],
104
+ remedy: { summary: 'x', command: 'x' },
106
105
  },
107
106
  },
108
107
  };
109
- expect((0, contract_1.parseCoverageManifest)(input, 'sensors.json')).toEqual(input);
110
- });
111
- test.each([
112
- [null, 'object'],
113
- [{}, 'pack'],
114
- [{ pack: '', sensors: {} }, 'pack'],
115
- [{ pack: ' js-ts', sensors: {} }, 'pack'],
116
- [{ pack: 'js ts', sensors: {} }, 'pack'],
117
- [{ pack: 'js@ts', sensors: {} }, 'pack'],
118
- [{ pack: 'js-ts', sensors: null }, 'sensors'],
119
- [{ pack: 'js-ts', sensors: { lint: { cmd: 3 } } }, 'cmd'],
120
- [{ pack: 'js-ts', sensors: { 'lint!': {} } }, 'sensor name'],
121
- ])('rejects malformed manifest %j', (input, message) => {
122
- expect(() => (0, contract_1.parseCoverageManifest)(input, 'sensors.json')).toThrow(message);
108
+ expect(() => (0, contract_1.parseCoverageContract)(input, 'coverage.json')).toThrow('sensor');
123
109
  });
124
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
+ });
@@ -18,6 +18,35 @@ const contract = {
18
18
  };
19
19
  const observation = (classId, detectorIndex, sensor, status) => ({ classId, detectorIndex, sensor, status, evidence: [] });
20
20
  describe('coverage evaluation', () => {
21
+ test('empirical evidence contradicts an inapplicable static class', () => {
22
+ expect((0, evaluate_1.crossEmpiricalOutcome)('not-applicable', true)).toBe('applicability-contradiction');
23
+ });
24
+ test.each([
25
+ [['certified', 'incompatible'], 'covered'],
26
+ [['compatible-unverified', 'missing-tool'], 'unverifiable'],
27
+ [['unverifiable', 'not-applicable'], 'unverifiable'],
28
+ [['incompatible', 'not-applicable'], 'missing'],
29
+ [['not-applicable', 'not-applicable'], 'not-applicable'],
30
+ ])('reduces compatibility states %p to %s', (states, expected) => {
31
+ const compatibility = (state) => ({
32
+ state,
33
+ reason: 'fixture',
34
+ variantId: null,
35
+ toolVersion: null,
36
+ runtimeVersion: null,
37
+ certifiedRange: null,
38
+ evidence: [],
39
+ });
40
+ const observations = states.map((state, detectorIndex) => ({
41
+ ...observation('alpha', detectorIndex, detectorIndex === 0 ? 'one' : 'two', 'covered'),
42
+ compatibility: compatibility(state),
43
+ }));
44
+ const result = (0, evaluate_1.evaluateCoverage)(contract, [...observations, {
45
+ ...observation('zeta', 0, 'three', 'covered'),
46
+ compatibility: compatibility('certified'),
47
+ }]);
48
+ expect(result.classes.find((item) => item.id === 'alpha')?.status).toBe(expected);
49
+ });
21
50
  test.each([
22
51
  [[observation('alpha', 0, 'one', 'covered'), observation('alpha', 1, 'two', 'missing')], 'covered'],
23
52
  [[observation('alpha', 0, 'one', 'missing'), observation('alpha', 1, 'two', 'disabled')], 'missing'],
@@ -54,6 +54,15 @@ test('active matching sensor with all AND evidence is covered (R2.2)', () => {
54
54
  ],
55
55
  });
56
56
  });
57
+ test('structured v2 commands supply structural coverage evidence without exposing argv', () => {
58
+ fs_1.default.writeFileSync(path_1.default.join(root, 'eslint.config.js'), "rules: { 'no-unreachable': 'error' }");
59
+ const observed = (0, evidence_1.observeDetector)(root, 'style', 0, detector, {
60
+ enabled: true,
61
+ command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.', '--config', 'eslint.config.js'] },
62
+ });
63
+ expect(observed.status).toBe(regularFileStatus('covered'));
64
+ expect(JSON.stringify(observed)).not.toContain('--config');
65
+ });
57
66
  test.each([
58
67
  [undefined, 'missing'],
59
68
  [{ cmd: 'npx eslint .', enabled: false }, 'disabled'],