agentic-workflow-manager 6.5.2 → 8.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/scripts/sensor-support-matrix.js +103 -0
- package/dist/scripts/support-matrix.js +40 -17
- package/dist/src/commands/init.js +1 -3
- package/dist/src/commands/ledger/index.js +6 -0
- package/dist/src/commands/preflight/checks.js +4 -4
- package/dist/src/commands/preflight/index.js +2 -2
- package/dist/src/commands/registry/add.js +2 -2
- package/dist/src/commands/registry/index.js +5 -0
- package/dist/src/commands/registry/status.js +7 -0
- package/dist/src/commands/sensors/compatibility/contract.js +382 -0
- package/dist/src/commands/sensors/compatibility/discovery.js +207 -0
- package/dist/src/commands/sensors/compatibility/live.js +92 -0
- package/dist/src/commands/sensors/compatibility/manifest.js +207 -0
- package/dist/src/commands/sensors/compatibility/materialize.js +134 -0
- package/dist/src/commands/sensors/compatibility/pack-source.js +78 -0
- package/dist/src/commands/sensors/compatibility/probe.js +47 -0
- package/dist/src/commands/sensors/compatibility/resolve.js +99 -0
- package/dist/src/commands/sensors/compatibility/types.js +2 -0
- package/dist/src/commands/sensors/coverage/contract.js +0 -50
- package/dist/src/commands/sensors/coverage/empirical.js +131 -0
- package/dist/src/commands/sensors/coverage/evaluate.js +85 -7
- package/dist/src/commands/sensors/coverage/evidence.js +5 -1
- package/dist/src/commands/sensors/coverage/index.js +44 -7
- package/dist/src/commands/sensors/coverage/render.js +214 -14
- package/dist/src/commands/sensors/coverage/resolve.js +24 -38
- package/dist/src/commands/sensors/exec.js +244 -9
- package/dist/src/commands/sensors/index.js +18 -6
- package/dist/src/commands/sensors/init.js +95 -6
- package/dist/src/commands/sensors/run.js +71 -0
- package/dist/src/commands/sensors/status.js +48 -4
- package/dist/src/commands/sync.js +7 -0
- package/dist/src/core/bundles.js +38 -3
- package/dist/src/core/init/steps.js +7 -4
- package/dist/src/core/ledger/scan.js +228 -0
- package/dist/src/core/ledger/store.js +89 -25
- package/dist/src/core/ledger/types.js +54 -0
- package/dist/src/core/paths.js +10 -9
- package/dist/src/core/registries.js +134 -14
- package/dist/src/index.js +24 -1
- package/dist/tests/commands/ledger/index.test.js +11 -0
- package/dist/tests/commands/preflight/preflight.test.js +64 -64
- package/dist/tests/commands/registry/add.test.js +69 -2
- package/dist/tests/commands/registry/status.test.js +14 -0
- package/dist/tests/commands/sensors/compatibility/contract.test.js +193 -0
- package/dist/tests/commands/sensors/compatibility/discovery.test.js +119 -0
- package/dist/tests/commands/sensors/compatibility/live.test.js +124 -0
- package/dist/tests/commands/sensors/compatibility/manifest.test.js +77 -0
- package/dist/tests/commands/sensors/compatibility/materialize.test.js +72 -0
- package/dist/tests/commands/sensors/compatibility/pack-source.test.js +55 -0
- package/dist/tests/commands/sensors/compatibility/probe.test.js +37 -0
- package/dist/tests/commands/sensors/compatibility/python-venv-fixture.js +29 -0
- package/dist/tests/commands/sensors/compatibility/resolve.test.js +83 -0
- package/dist/tests/commands/sensors/coverage/contract.test.js +8 -22
- package/dist/tests/commands/sensors/coverage/empirical.test.js +111 -0
- package/dist/tests/commands/sensors/coverage/evaluate.test.js +29 -0
- package/dist/tests/commands/sensors/coverage/evidence.test.js +9 -0
- package/dist/tests/commands/sensors/coverage/index.test.js +40 -13
- package/dist/tests/commands/sensors/coverage/render.test.js +187 -12
- package/dist/tests/commands/sensors/coverage/resolve.test.js +25 -4
- package/dist/tests/commands/sensors/exec-windows.test.js +140 -2
- package/dist/tests/commands/sensors/exec.test.js +115 -5
- package/dist/tests/commands/sensors/index.test.js +8 -0
- package/dist/tests/commands/sensors/init-pack-unavailable.test.js +12 -12
- package/dist/tests/commands/sensors/init.test.js +204 -48
- package/dist/tests/commands/sensors/run-inconclusive.test.js +9 -2
- package/dist/tests/commands/sensors/run-is-read-only.test.js +0 -0
- package/dist/tests/commands/sensors/run.test.js +66 -3
- package/dist/tests/commands/sensors/status-windows.test.js +7 -7
- package/dist/tests/commands/sensors/status.test.js +74 -23
- package/dist/tests/commands/update.test.js +1 -1
- package/dist/tests/core/bundles.test.js +37 -0
- package/dist/tests/core/discovery.test.js +4 -0
- package/dist/tests/core/init/orchestrator.test.js +5 -1
- package/dist/tests/core/init/steps.test.js +20 -4
- package/dist/tests/core/ledger/scan.test.js +217 -0
- package/dist/tests/core/ledger/store.test.js +108 -0
- package/dist/tests/core/profile-pins.test.js +2 -1
- package/dist/tests/core/project-skill-links.test.js +22 -0
- package/dist/tests/core/registries-capability.test.js +10 -0
- package/dist/tests/core/registries-sync.test.js +16 -0
- package/dist/tests/core/registries.test.js +129 -1
- package/dist/tests/core/registry-manifest.test.js +7 -0
- package/dist/tests/core/same-existing-path.test.js +35 -0
- package/dist/tests/core/sync-gates.test.js +2 -1
- package/dist/tests/integration/copilot-init-isolated.test.js +18 -0
- package/dist/tests/integration/sensor-compatibility.e2e.test.js +141 -0
- package/dist/tests/integration/sensor-coverage.e2e.test.js +35 -6
- package/dist/tests/integration/sensor-structured-run.e2e.test.js +127 -0
- package/dist/tests/structural/active-documentation.test.js +128 -0
- package/dist/tests/structural/async-entrypoint.test.js +15 -0
- package/dist/tests/structural/jest-environment-is-isolated.test.js +44 -0
- package/dist/tests/structural/r3-cli-major-version.test.js +37 -0
- package/dist/tests/structural/sensor-documentation-contract.test.js +77 -0
- package/dist/tests/structural/support-matrix-is-current.test.js +69 -2
- package/package.json +8 -2
|
@@ -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,37 @@
|
|
|
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
|
+
it('keeps Windows Semgrep validation on the discovered .venv executable', async () => {
|
|
28
|
+
await (0, probe_1.runCompatibilityProbe)({ kind: 'semgrep-validate' }, {
|
|
29
|
+
cwd: process.cwd(), toolExecutable: 'semgrep', toolResolution: 'python-environment', pythonEnvironmentRoot: '.venv',
|
|
30
|
+
}, fakeExecutor);
|
|
31
|
+
expect(fakeExecutor).toHaveBeenCalledWith({ executable: 'semgrep', resolution: 'python-environment', pythonEnvironmentRoot: '.venv', args: ['--validate'] }, expect.objectContaining({ cwd: process.cwd() }));
|
|
32
|
+
});
|
|
33
|
+
it.each(['mypy', 'ruff', 'pytest'])('probes a %s variant through the contained Python environment', async (toolExecutable) => {
|
|
34
|
+
await (0, probe_1.runCompatibilityProbe)({ kind: 'version' }, { ...evidence, toolExecutable, toolResolution: 'python-environment' }, fakeExecutor);
|
|
35
|
+
expect(fakeExecutor).toHaveBeenCalledWith(expect.objectContaining({ executable: toolExecutable, resolution: 'python-environment', args: ['--version'] }), expect.any(Object));
|
|
36
|
+
});
|
|
37
|
+
});
|
|
@@ -0,0 +1,29 @@
|
|
|
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
|
+
exports.createSemgrepVenvFixture = createSemgrepVenvFixture;
|
|
7
|
+
const fs_1 = __importDefault(require("fs"));
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
/** Creates contained Semgrep metadata and the host-appropriate virtualenv executable. */
|
|
10
|
+
function createSemgrepVenvFixture(root, targetPlatform = process.platform, environmentRoot = '.venv', options = {}) {
|
|
11
|
+
if (typeof root !== 'string' || root.trim() === '')
|
|
12
|
+
throw new Error('fixture root must be a non-empty path');
|
|
13
|
+
const windows = targetPlatform === 'win32';
|
|
14
|
+
const sitePackages = windows
|
|
15
|
+
? [environmentRoot, 'Lib', 'site-packages']
|
|
16
|
+
: [environmentRoot, 'lib', 'python3.12', 'site-packages'];
|
|
17
|
+
const metadata = path_1.default.join(root, ...sitePackages, 'semgrep-1.91.0.dist-info');
|
|
18
|
+
fs_1.default.mkdirSync(metadata, { recursive: true });
|
|
19
|
+
fs_1.default.writeFileSync(path_1.default.join(root, environmentRoot, 'pyvenv.cfg'), 'version = 3.12.4\n');
|
|
20
|
+
fs_1.default.writeFileSync(path_1.default.join(metadata, 'METADATA'), 'Name: semgrep\nVersion: 1.91.0\n');
|
|
21
|
+
if (options.executable === false)
|
|
22
|
+
return;
|
|
23
|
+
const executable = path_1.default.join(root, environmentRoot, windows ? 'Scripts' : 'bin', windows ? 'semgrep.exe' : 'semgrep');
|
|
24
|
+
fs_1.default.mkdirSync(path_1.default.dirname(executable), { recursive: true });
|
|
25
|
+
if (windows)
|
|
26
|
+
fs_1.default.copyFileSync(process.execPath, executable);
|
|
27
|
+
else
|
|
28
|
+
fs_1.default.writeFileSync(executable, '#!/bin/sh\necho local-semgrep\n', { mode: 0o755 });
|
|
29
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
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
|
+
it('keeps an automatically selected generic pack not-applicable despite language markers', () => {
|
|
16
|
+
const resolved = (0, resolve_1.resolveSensorCompatibility)({ applicability: { kind: 'explicit-or-supported-language' }, variants: [variant('semgrep')] }, evidence({ applicable: undefined, paths: ['pyproject.toml'], toolVersion: '1.0.0', runtimeVersion: '3.12.0', probe: { status: 'matched' } }), { pack: 'generic', sensor: 'security' });
|
|
17
|
+
expect(resolved).toMatchObject({ state: 'not-applicable', reason: 'applicability-not-met' });
|
|
18
|
+
});
|
|
19
|
+
it('treats the persisted explicit generic pack selection as positive capability in project resolution', () => {
|
|
20
|
+
const resolved = (0, resolve_1.resolveProjectCompatibility)({ schemaVersion: 2, name: 'generic', sensors: { security: { applicability: { kind: 'explicit-or-supported-language' }, variants: [variant('semgrep')] } } }, evidence({ applicable: undefined, paths: [], packSelection: 'explicit' }));
|
|
21
|
+
expect(resolved.sensors.security.state).not.toBe('not-applicable');
|
|
22
|
+
});
|
|
23
|
+
test.each([
|
|
24
|
+
['certified', evidence(), 'eslint-main'],
|
|
25
|
+
['compatible-unverified', evidence({ toolVersion: '11.0.0' }), 'eslint-main'],
|
|
26
|
+
['incompatible', evidence({ toolVersion: '8.0.0' }), null],
|
|
27
|
+
['missing-tool', evidence({ toolVersion: null }), null],
|
|
28
|
+
['unverifiable', evidence({ probe: { status: 'unverifiable' } }), 'eslint-main'],
|
|
29
|
+
['not-applicable', evidence({ applicable: false, paths: [] }), null],
|
|
30
|
+
])('resolves %s without conflating states', (state, discovered, variantId) => {
|
|
31
|
+
expect((0, resolve_1.resolveSensorCompatibility)(sensor, discovered, context)).toMatchObject({ state, variantId });
|
|
32
|
+
});
|
|
33
|
+
it('preserves legacy behavior without executing a probe', () => {
|
|
34
|
+
expect((0, resolve_1.resolveSensorCompatibility)({ defaultCmd: 'eslint .' }, evidence(), context)).toEqual((0, manifest_1.legacyCompatibility)('legacy pack without schemaVersion'));
|
|
35
|
+
});
|
|
36
|
+
it('fails an equal-precedence match with its pack, sensor, IDs, and ranges', () => {
|
|
37
|
+
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);
|
|
38
|
+
});
|
|
39
|
+
it('requires a real pack and sensor identity for direct resolution', () => {
|
|
40
|
+
expect(() => (0, resolve_1.resolveSensorCompatibility)(sensor, evidence(), undefined)).toThrow(/pack and sensor identity are required/i);
|
|
41
|
+
});
|
|
42
|
+
it('reports lockfile conflicts as unverifiable', () => {
|
|
43
|
+
expect((0, resolve_1.resolveSensorCompatibility)(sensor, evidence({ packageManagerConflict: true }), context)).toMatchObject({ state: 'unverifiable', reason: 'package-manager-conflict' });
|
|
44
|
+
});
|
|
45
|
+
it('selects an operational variant when another tool variant is absent locally', () => {
|
|
46
|
+
const variants = [
|
|
47
|
+
{ ...variant('eslint'), requirements: { ...variant('eslint').requirements, tool: 'eslint' } },
|
|
48
|
+
{ ...variant('biome'), requirements: { ...variant('biome').requirements, tool: 'biome' } },
|
|
49
|
+
];
|
|
50
|
+
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);
|
|
51
|
+
expect(resolved).toMatchObject({ state: 'certified', variantId: 'eslint', toolVersion: '10.4.1', runtimeVersion: '22.0.0' });
|
|
52
|
+
});
|
|
53
|
+
it('does not reuse scalar evidence for a missing key in a version map', () => {
|
|
54
|
+
const biome = { ...variant('biome'), requirements: { ...variant('biome').requirements, tool: 'biome', runtime: 'bun' } };
|
|
55
|
+
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))
|
|
56
|
+
.toMatchObject({ state: 'missing-tool' });
|
|
57
|
+
});
|
|
58
|
+
it('carries bounded, sanitized evidence without raw probe output', () => {
|
|
59
|
+
const resolved = (0, resolve_1.resolveSensorCompatibility)(sensor, evidence({ paths: ['package.json', '../SECRET_VALUE'], probe: { status: 'matched', output: 'SECRET_VALUE' } }), context);
|
|
60
|
+
expect(resolved.evidence).toEqual(expect.arrayContaining([{ kind: 'project-path', status: 'present', path: 'package.json' }, { kind: 'probe', status: 'matched' }]));
|
|
61
|
+
expect(JSON.stringify(resolved)).not.toContain('SECRET_VALUE');
|
|
62
|
+
});
|
|
63
|
+
test.each(['linux', 'darwin', 'win32'])('resolves the six states under controlled %s evidence', (os) => {
|
|
64
|
+
const states = [
|
|
65
|
+
(0, resolve_1.resolveSensorCompatibility)(sensor, evidence({ os }), context),
|
|
66
|
+
(0, resolve_1.resolveSensorCompatibility)(sensor, evidence({ os, toolVersion: '11.0.0' }), context),
|
|
67
|
+
(0, resolve_1.resolveSensorCompatibility)(sensor, evidence({ os, toolVersion: '8.0.0' }), context),
|
|
68
|
+
(0, resolve_1.resolveSensorCompatibility)(sensor, evidence({ os, toolVersion: null }), context),
|
|
69
|
+
(0, resolve_1.resolveSensorCompatibility)(sensor, evidence({ os, probe: { status: 'unverifiable' } }), context),
|
|
70
|
+
(0, resolve_1.resolveSensorCompatibility)(sensor, evidence({ os, applicable: false, paths: [] }), context),
|
|
71
|
+
].map(result => result.state);
|
|
72
|
+
expect(states).toEqual(['certified', 'compatible-unverified', 'incompatible', 'missing-tool', 'unverifiable', 'not-applicable']);
|
|
73
|
+
});
|
|
74
|
+
test.each(['linux', 'darwin', 'win32'])('keeps applicability and deterministic precedence above version resolution on %s', (os) => {
|
|
75
|
+
const broad = variant('broad', 10, '>=9 <12');
|
|
76
|
+
const narrow = variant('narrow', 10, '>=10 <11');
|
|
77
|
+
const precedenceSensor = { applicability: { allFiles: ['package.json'] }, variants: [broad, narrow] };
|
|
78
|
+
expect((0, resolve_1.resolveSensorCompatibility)(precedenceSensor, evidence({ os, packageManagerConflict: true, applicable: false, paths: [] }), context))
|
|
79
|
+
.toMatchObject({ state: 'not-applicable', reason: 'applicability-not-met' });
|
|
80
|
+
expect((0, resolve_1.resolveSensorCompatibility)(precedenceSensor, evidence({ os }), context)).toMatchObject({ variantId: 'narrow' });
|
|
81
|
+
expect(() => (0, resolve_1.resolveSensorCompatibility)({ ...precedenceSensor, variants: [variant('a'), variant('b')] }, evidence({ os }), context)).toThrow(/ambiguous/i);
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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.
|
|
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'],
|
|
@@ -1,20 +1,45 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const coverage_1 = require("../../../../src/commands/sensors/coverage");
|
|
4
|
-
test('not configured is explicit, actionable and exit-0 data', () => {
|
|
5
|
-
expect((0, coverage_1.runCoverage)('/fixture', { resolve: () => ({ kind: 'not_configured' })
|
|
6
|
-
|
|
4
|
+
test('not configured is explicit, actionable and exit-0 data', async () => {
|
|
5
|
+
await expect((0, coverage_1.runCoverage)('/fixture', { resolve: () => ({ kind: 'not_configured' }), scan: () => ({
|
|
6
|
+
entries: [], sources: { activeFiles: 0, archivedFiles: 0, validEntries: 0, validFindings: 0, skippedFindings: 0, skippedByReason: {} }, omittedEvidenceRefs: 0,
|
|
7
|
+
}) })).resolves.toEqual({
|
|
8
|
+
schemaVersion: 2, pack: null, registry: null, overall: 'inconclusive',
|
|
7
9
|
static: { status: 'inconclusive', reason: 'not_configured', classes: [] },
|
|
10
|
+
empirical: expect.objectContaining({ status: 'no-evidence' }),
|
|
8
11
|
});
|
|
9
12
|
});
|
|
10
|
-
test('
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
13
|
+
test('unavailable static contracts make empirical findings coverage-unverifiable', async () => {
|
|
14
|
+
const scan = {
|
|
15
|
+
entries: [{ entry: { ts: 'x', branch: 'main', phase: 'qa', source_skill: 'test', polarity: 'finding',
|
|
16
|
+
class: 'structural', signature: 'private', severity: 'minor', desc: 'secret', defectClass: 'lint-errors' }, source: '.awm/ledger/main.jsonl:1', evidenceRef: '.awm/ledger/main.jsonl:1' }],
|
|
17
|
+
sources: { activeFiles: 1, archivedFiles: 0, validEntries: 1, validFindings: 1, skippedFindings: 0, skippedByReason: {} }, omittedEvidenceRefs: 0,
|
|
18
|
+
};
|
|
19
|
+
const notConfigured = await (0, coverage_1.runCoverage)('/fixture', { resolve: () => ({ kind: 'not_configured' }), scan: () => scan });
|
|
20
|
+
expect(notConfigured.empirical?.classes[0].outcome).toBe('coverage-unverifiable');
|
|
21
|
+
const manifest = { kind: 'legacy', pack: { pack: 'legacy', sensors: {}, compatibility: {
|
|
22
|
+
state: 'compatible-unverified', reason: 'legacy', variantId: null, toolVersion: null, runtimeVersion: null, certifiedRange: null, evidence: [],
|
|
23
|
+
} } };
|
|
24
|
+
const noReference = await (0, coverage_1.runCoverage)('/fixture', { resolve: () => ({ kind: 'no_reference', projectRoot: '/fixture', pack: 'legacy', registry: 'baseline', manifest }), scan: () => scan });
|
|
25
|
+
expect(noReference.empirical?.classes[0].outcome).toBe('coverage-unverifiable');
|
|
15
26
|
});
|
|
16
|
-
test('
|
|
17
|
-
const manifest = {
|
|
27
|
+
test('old pack is no_reference and preserves pack and registry', async () => {
|
|
28
|
+
const manifest = { kind: 'legacy', pack: { pack: 'legacy', sensors: {}, compatibility: {
|
|
29
|
+
state: 'compatible-unverified', reason: 'legacy', variantId: null, toolVersion: null, runtimeVersion: null, certifiedRange: null, evidence: [],
|
|
30
|
+
} } };
|
|
31
|
+
await expect((0, coverage_1.runCoverage)('/fixture', { resolve: () => ({ kind: 'no_reference', projectRoot: '/fixture', pack: 'legacy', registry: 'baseline', manifest }), scan: () => ({
|
|
32
|
+
entries: [{ entry: { ts: 'x', branch: 'main', phase: 'qa', source_skill: 'test', polarity: 'finding', class: 'structural', signature: 'private', severity: 'minor', desc: 'secret', defectClass: 'lint-errors' }, source: '.awm/ledger/main.jsonl:1', evidenceRef: '.awm/ledger/main.jsonl:1' }],
|
|
33
|
+
sources: { activeFiles: 1, archivedFiles: 0, validEntries: 1, validFindings: 1, skippedFindings: 0, skippedByReason: {} }, omittedEvidenceRefs: 0,
|
|
34
|
+
}) }))
|
|
35
|
+
.resolves.toEqual({ schemaVersion: 2, pack: 'legacy', registry: 'baseline', overall: 'inconclusive',
|
|
36
|
+
static: { status: 'inconclusive', reason: 'no_reference', classes: [] },
|
|
37
|
+
empirical: expect.objectContaining({ status: 'evidence', classes: [expect.objectContaining({ outcome: 'coverage-unverifiable' })] }) });
|
|
38
|
+
});
|
|
39
|
+
test('ready input observes every declared detector and evaluates once', async () => {
|
|
40
|
+
const manifest = { kind: 'legacy', pack: { pack: 'js-ts', sensors: { lint: { cmd: 'eslint .' }, format: { cmd: 'prettier --check .' } }, compatibility: {
|
|
41
|
+
state: 'compatible-unverified', reason: 'legacy', variantId: null, toolVersion: null, runtimeVersion: null, certifiedRange: null, evidence: [],
|
|
42
|
+
} } };
|
|
18
43
|
const contract = { schemaVersion: 1, classes: {
|
|
19
44
|
formatting: { description: 'Formatting', detectors: [{ sensor: 'format' }], remedy: { summary: 'Add format', command: 'npm i -D prettier' } },
|
|
20
45
|
linting: { description: 'Linting', detectors: [{ sensor: 'lint' }], remedy: { summary: 'Add lint', command: 'npm i -D eslint' } },
|
|
@@ -22,9 +47,11 @@ test('ready input observes every declared detector and evaluates once', () => {
|
|
|
22
47
|
const observe = jest.fn((_root, classId, detectorIndex, detector) => ({
|
|
23
48
|
classId, detectorIndex, sensor: detector.sensor, status: 'covered', evidence: [],
|
|
24
49
|
}));
|
|
25
|
-
const out = (0, coverage_1.runCoverage)('/fixture', { resolve: () => ({ kind: 'ready', projectRoot: '/fixture', pack: 'js-ts', registry: 'baseline', manifest, contract }), observe
|
|
50
|
+
const out = await (0, coverage_1.runCoverage)('/fixture', { resolve: () => ({ kind: 'ready', projectRoot: '/fixture', pack: 'js-ts', registry: 'baseline', manifest, contract }), observe, scan: () => ({
|
|
51
|
+
entries: [], sources: { activeFiles: 0, archivedFiles: 0, validEntries: 0, validFindings: 0, skippedFindings: 0, skippedByReason: {} }, omittedEvidenceRefs: 0,
|
|
52
|
+
}) });
|
|
26
53
|
expect(observe.mock.calls.map((call) => [call[1], call[2]])).toEqual([['formatting', 0], ['linting', 0]]);
|
|
27
54
|
expect(out.static.classes.map((item) => item.id)).toEqual(['formatting', 'linting']);
|
|
28
|
-
expect(out.overall).toBe('
|
|
29
|
-
expect(out).
|
|
55
|
+
expect(out.overall).toBe('inconclusive');
|
|
56
|
+
expect(out.empirical).toMatchObject({ status: 'no-evidence' });
|
|
30
57
|
});
|