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,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runCompatibilityProbe = runCompatibilityProbe;
4
+ const exec_1 = require("../exec");
5
+ const KINDS = new Set(['version', 'eslint-print-config', 'typescript-show-config', 'semgrep-validate', 'package-script-present', 'config-present']);
6
+ function commandFor(kind, evidence) {
7
+ const executable = evidence.toolExecutable ?? (kind.startsWith('typescript') ? 'tsc' : kind.startsWith('eslint') ? 'eslint' : kind.startsWith('semgrep') ? 'semgrep' : 'node');
8
+ // A tool version certified from local package metadata must be probed through
9
+ // the same project installation. Only the Node runtime is intentionally PATH
10
+ // resolved: it is runtime evidence, not a package-local tool assertion.
11
+ const resolution = executable === 'node'
12
+ ? 'path'
13
+ : kind === 'semgrep-validate'
14
+ ? 'python-environment'
15
+ : 'node-modules-bin';
16
+ if (kind === 'package-script-present')
17
+ return null;
18
+ if (kind === 'config-present')
19
+ return null;
20
+ if (kind === 'version')
21
+ return { executable, resolution, args: ['--version'] };
22
+ if (kind === 'eslint-print-config')
23
+ return { executable, resolution, args: ['--print-config', evidence.configFiles?.[0] ?? 'package.json'] };
24
+ if (kind === 'typescript-show-config')
25
+ return { executable, resolution, args: ['--showConfig'] };
26
+ return { executable, resolution, args: ['--validate'] };
27
+ }
28
+ /** Executes only the closed probe enum. Raw output is intentionally discarded. */
29
+ async function runCompatibilityProbe(probe, evidence, executor = exec_1.runStructuredCommand) {
30
+ if (!probe || typeof probe !== 'object' || !KINDS.has(probe.kind))
31
+ throw new Error('compatibility probe kind must be allowed');
32
+ if (!evidence || typeof evidence.cwd !== 'string' || evidence.cwd.trim() === '')
33
+ throw new Error('probe evidence requires cwd');
34
+ if (probe.kind === 'package-script-present')
35
+ return { status: (evidence.scripts?.length ?? 0) > 0 ? 'matched' : 'not-matched', reason: 'package-script' };
36
+ if (probe.kind === 'config-present')
37
+ return { status: (evidence.configFiles?.length ?? 0) > 0 ? 'matched' : 'not-matched', reason: 'config-file' };
38
+ try {
39
+ const result = await executor(commandFor(probe.kind, evidence), { cwd: evidence.cwd, timeout: 5_000, maxBuffer: 64 * 1024 });
40
+ if (result.timedOut || result.overflowed || result.spawnError)
41
+ return { status: 'unverifiable', reason: result.timedOut ? 'probe-timeout' : result.overflowed ? 'probe-output-limit' : 'probe-spawn-error' };
42
+ return result.code === 0 ? { status: 'matched', reason: 'probe-exit-zero' } : { status: 'not-matched', reason: 'probe-exit-nonzero' };
43
+ }
44
+ catch {
45
+ return { status: 'unverifiable', reason: 'probe-exception' };
46
+ }
47
+ }
@@ -0,0 +1,96 @@
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.resolveSensorCompatibility = resolveSensorCompatibility;
7
+ exports.resolveProjectCompatibility = resolveProjectCompatibility;
8
+ const semver_1 = __importDefault(require("semver"));
9
+ const manifest_1 = require("./manifest");
10
+ function result(state, reason, variant, evidence) {
11
+ const toolVersion = (variant ? toolFor(variant, evidence) : evidence.toolVersion) ?? null;
12
+ const runtimeVersion = (variant ? runtimeFor(variant, evidence) : evidence.runtimeVersion) ?? null;
13
+ const refs = [];
14
+ for (const candidate of (evidence.paths ?? []).slice(0, 32)) {
15
+ if (typeof candidate === 'string' && candidate.length <= 240 && !candidate.startsWith('/') && !candidate.includes('\\') && !candidate.split('/').some(part => part === '' || part === '.' || part === '..'))
16
+ refs.push({ kind: 'project-path', status: 'present', path: candidate });
17
+ }
18
+ if (evidence.packageManagerConflict)
19
+ refs.push({ kind: 'package-manager', status: 'conflict' });
20
+ if (variant && validVersion(toolVersion))
21
+ refs.push({ kind: 'tool-version', status: toolVersion });
22
+ if (variant && validVersion(runtimeVersion))
23
+ refs.push({ kind: 'runtime-version', status: runtimeVersion });
24
+ if (evidence.probe?.status === 'matched' || evidence.probe?.status === 'not-matched' || evidence.probe?.status === 'unverifiable')
25
+ refs.push({ kind: 'probe', status: evidence.probe.status });
26
+ return { state, reason, variantId: variant?.id ?? null, toolVersion, runtimeVersion, certifiedRange: variant?.certifiedRange ?? null, evidence: refs.slice(0, 32) };
27
+ }
28
+ function applies(sensor, evidence) {
29
+ if (evidence.applicable === false)
30
+ return false;
31
+ const paths = new Set(evidence.paths ?? []);
32
+ const rule = sensor.applicability;
33
+ return (!rule.allFiles || rule.allFiles.every(file => paths.has(file))) && (!rule.anyFiles || rule.anyFiles.some(file => paths.has(file)));
34
+ }
35
+ function specificity(variant) {
36
+ return variant.requirements.toolRange.length + variant.requirements.runtimeRange.length + (variant.requirements.configFiles?.length ?? 0);
37
+ }
38
+ function validVersion(value) { return typeof value === 'string' && semver_1.default.valid(value) !== null; }
39
+ function toolFor(variant, evidence) {
40
+ return evidence.toolVersions === undefined ? evidence.toolVersion : evidence.toolVersions[variant.requirements.tool];
41
+ }
42
+ function runtimeFor(variant, evidence) {
43
+ return evidence.runtimeVersions === undefined ? evidence.runtimeVersion : evidence.runtimeVersions[variant.requirements.runtime];
44
+ }
45
+ /** Pure precedence resolver. It consumes discovered evidence and neither probes nor executes commands. */
46
+ function resolveSensorCompatibility(sensor, evidence, context) {
47
+ if (!sensor || typeof sensor !== 'object' || !evidence || typeof evidence !== 'object')
48
+ throw new Error('sensor and discovered evidence are required');
49
+ if (!context || typeof context.pack !== 'string' || !context.pack.trim() || typeof context.sensor !== 'string' || !context.sensor.trim())
50
+ throw new Error('pack and sensor identity are required for compatibility resolution');
51
+ if (!('variants' in sensor))
52
+ return (0, manifest_1.legacyCompatibility)('legacy pack without schemaVersion');
53
+ const v2 = sensor;
54
+ if (!Array.isArray(v2.variants) || !v2.applicability)
55
+ throw new Error('v2 sensor must declare variants and applicability');
56
+ if (!applies(v2, evidence))
57
+ return result('not-applicable', 'applicability-not-met', null, evidence);
58
+ if (evidence.packageManagerConflict)
59
+ return result('unverifiable', 'package-manager-conflict', null, evidence);
60
+ const availableTools = v2.variants.map(variant => toolFor(variant, evidence));
61
+ if (availableTools.every(version => version === null || version === undefined))
62
+ return result('missing-tool', 'tool-not-found', null, evidence);
63
+ const operational = v2.variants.filter(variant => validVersion(toolFor(variant, evidence)) && validVersion(runtimeFor(variant, evidence)));
64
+ if (operational.length === 0)
65
+ return result('unverifiable', 'invalid-or-missing-version-evidence', null, evidence);
66
+ const matches = operational.filter(variant => semver_1.default.satisfies(toolFor(variant, evidence), variant.requirements.toolRange) && semver_1.default.satisfies(runtimeFor(variant, evidence), variant.requirements.runtimeRange));
67
+ if (matches.length === 0)
68
+ return result('incompatible', 'no-operational-variant', null, evidence);
69
+ matches.sort((a, b) => b.priority - a.priority || specificity(b) - specificity(a) || a.id.localeCompare(b.id));
70
+ const best = matches[0];
71
+ const tied = matches.filter(variant => variant.priority === best.priority && specificity(variant) === specificity(best));
72
+ if (tied.length !== 1) {
73
+ const matching = tied.sort((a, b) => a.id.localeCompare(b.id)).map(variant => `${variant.id} (tool ${variant.requirements.toolRange}, runtime ${variant.requirements.runtimeRange})`).join(', ');
74
+ throw new Error(`ambiguous sensor variants in pack "${context.pack}" sensor "${context.sensor}": ${matching}`);
75
+ }
76
+ if (evidence.probe?.status !== 'matched')
77
+ return result('unverifiable', evidence.probe?.status === 'not-matched' ? 'probe-not-matched' : 'probe-inconclusive', best, evidence);
78
+ return semver_1.default.satisfies(toolFor(best, evidence), best.certifiedRange)
79
+ ? result('certified', 'range-and-probe', best, evidence)
80
+ : result('compatible-unverified', 'operational-range-and-probe', best, evidence);
81
+ }
82
+ /** Adapt bounded local project discovery into one source-aware resolution per sensor. */
83
+ function resolveProjectCompatibility(pack, evidence) {
84
+ if (!pack || typeof pack !== 'object' || typeof pack.name !== 'string')
85
+ throw new Error('pack must be a parsed sensor pack');
86
+ const sensors = {};
87
+ if (!('schemaVersion' in pack)) {
88
+ for (const name of Object.keys(pack.sensors))
89
+ sensors[name] = (0, manifest_1.legacyCompatibility)('legacy pack without schemaVersion');
90
+ return { sensors };
91
+ }
92
+ for (const [name, sensor] of Object.entries(pack.sensors)) {
93
+ sensors[name] = resolveSensorCompatibility(sensor, evidence, { pack: pack.name, sensor: name });
94
+ }
95
+ return { sensors };
96
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -2,7 +2,6 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.MAX_COVERAGE_FILE_BYTES = exports.COVERAGE_SCHEMA_VERSION = void 0;
4
4
  exports.parseCoverageContract = parseCoverageContract;
5
- exports.parseCoverageManifest = parseCoverageManifest;
6
5
  exports.COVERAGE_SCHEMA_VERSION = 1;
7
6
  exports.MAX_COVERAGE_FILE_BYTES = 1024 * 1024;
8
7
  const COVERAGE_EVIDENCE_PATHS = new Set([
@@ -123,52 +122,3 @@ function parseCoverageContract(input, source) {
123
122
  }
124
123
  return { schemaVersion: exports.COVERAGE_SCHEMA_VERSION, classes };
125
124
  }
126
- function manifestString(value, source, location) {
127
- return nonEmptyString(value, source, location);
128
- }
129
- function parseManifestSensor(input, source, location) {
130
- const value = record(input, source, location);
131
- fields(value, ['cmd', 'fast', 'enabled', 'timeout', 'changedCmd', 'changedExtensions', 'formatter'], source, location);
132
- const sensor = {};
133
- if ('cmd' in value)
134
- sensor.cmd = manifestString(value.cmd, source, `${location}.cmd`);
135
- if ('fast' in value) {
136
- if (typeof value.fast !== 'boolean')
137
- invalid(source, `${location}.fast must be a boolean`);
138
- sensor.fast = value.fast;
139
- }
140
- if ('enabled' in value) {
141
- if (typeof value.enabled !== 'boolean')
142
- invalid(source, `${location}.enabled must be a boolean`);
143
- sensor.enabled = value.enabled;
144
- }
145
- if ('timeout' in value) {
146
- if (typeof value.timeout !== 'number' || !Number.isSafeInteger(value.timeout) || value.timeout <= 0)
147
- invalid(source, `${location}.timeout must be a positive safe integer`);
148
- sensor.timeout = value.timeout;
149
- }
150
- if ('changedCmd' in value)
151
- sensor.changedCmd = manifestString(value.changedCmd, source, `${location}.changedCmd`);
152
- if ('changedExtensions' in value)
153
- sensor.changedExtensions = stringArray(value.changedExtensions, source, `${location}.changedExtensions`, true);
154
- if ('formatter' in value)
155
- sensor.formatter = manifestString(value.formatter, source, `${location}.formatter`);
156
- return sensor;
157
- }
158
- function parseCoverageManifest(input, source) {
159
- const value = record(input, source, 'manifest root');
160
- fields(value, ['pack', 'sensors', 'concurrency'], source, 'manifest root');
161
- const pack = safeName(value.pack, source, 'manifest.pack');
162
- const sensorsInput = record(value.sensors, source, 'manifest.sensors');
163
- const sensors = {};
164
- for (const name of Object.keys(sensorsInput)) {
165
- sensors[safeName(name, source, 'manifest sensor name')] = parseManifestSensor(sensorsInput[name], source, `manifest.sensors.${name}`);
166
- }
167
- const manifest = { pack, sensors };
168
- if ('concurrency' in value) {
169
- if (typeof value.concurrency !== 'number' || !Number.isSafeInteger(value.concurrency) || value.concurrency <= 0)
170
- invalid(source, 'manifest.concurrency must be a positive safe integer');
171
- manifest.concurrency = value.concurrency;
172
- }
173
- return manifest;
174
- }
@@ -0,0 +1,131 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.outcomeFor = outcomeFor;
4
+ exports.evaluateEmpiricalCoverage = evaluateEmpiricalCoverage;
5
+ const cluster_1 = require("../../../core/ledger/cluster");
6
+ const ANSI = /\x1B\[[0-?]*[ -/]*[@-~]/g;
7
+ const OSC = /\x1B\][\s\S]*?(?:\x07|\x1B\\)/g;
8
+ const CONTROLS = /[\u0000-\u001F\u007F-\u009F]/g;
9
+ const severityRank = { blocker: 4, important: 3, minor: 2, info: 1 };
10
+ const relativeLine = /^(?!\/)(?!.*(?:^|\/)\.\.?\/)[A-Za-z0-9._@+~=-]+(?:\/[A-Za-z0-9._@+~=-]+)*:[1-9][0-9]*$/;
11
+ const prRef = /^PR #[1-9][0-9]*$/;
12
+ const hashRef = /^[a-f0-9]{7,64}$/i;
13
+ function safeRef(value) {
14
+ if (typeof value !== 'string')
15
+ return null;
16
+ const cleaned = value.replace(OSC, '').replace(ANSI, '').replace(CONTROLS, '').trim().slice(0, 256);
17
+ return relativeLine.test(cleaned) || prRef.test(cleaned) || hashRef.test(cleaned) ? cleaned : null;
18
+ }
19
+ function refsFor(entries, scanned) {
20
+ const refs = new Set();
21
+ let omitted = 0;
22
+ for (const entry of entries) {
23
+ const scannedEntry = scanned.get(entry);
24
+ if (!scannedEntry || scannedEntry.evidenceRef === null)
25
+ continue;
26
+ const ref = safeRef(scannedEntry.evidenceRef);
27
+ if (ref === null)
28
+ omitted += 1;
29
+ else
30
+ refs.add(ref);
31
+ }
32
+ return { refs: [...refs].sort((a, b) => a.localeCompare(b)), omitted };
33
+ }
34
+ function signaturesFor(signatures) {
35
+ const safe = new Set();
36
+ let omitted = 0;
37
+ for (const signature of signatures) {
38
+ const publicSignature = safeRef(signature);
39
+ if (publicSignature === null)
40
+ omitted += 1;
41
+ else
42
+ safe.add(publicSignature);
43
+ }
44
+ return { signatures: [...safe].sort((a, b) => a.localeCompare(b)), omitted };
45
+ }
46
+ function maxSeverity(entries) {
47
+ return entries.reduce((best, entry) => severityRank[entry.severity] > severityRank[best] ? entry.severity : best, 'info');
48
+ }
49
+ function outcomeFor(staticState, hasEvidence, staticAvailability = 'available') {
50
+ if (typeof hasEvidence !== 'boolean')
51
+ throw new Error('outcomeFor: hasEvidence must be boolean');
52
+ if (staticAvailability !== 'available' && staticAvailability !== 'unavailable')
53
+ throw new Error('outcomeFor: invalid static availability');
54
+ if (staticAvailability === 'unavailable')
55
+ return 'coverage-unverifiable';
56
+ if (!hasEvidence || staticState === 'unverifiable' || staticState === 'compatible-unverified')
57
+ return 'coverage-unverifiable';
58
+ if (staticState === undefined)
59
+ return 'unmapped-class';
60
+ if (staticState === 'covered')
61
+ return 'covered-by-sensor';
62
+ if (staticState === 'not-applicable')
63
+ return 'applicability-contradiction';
64
+ if (staticState === 'missing' || staticState === 'incompatible' || staticState === 'missing-tool')
65
+ return 'gap';
66
+ throw new Error('outcomeFor: invalid static state');
67
+ }
68
+ /**
69
+ * Converts bounded ledger findings into a public aggregate. Defect classes are
70
+ * the sole grouping boundary: no description or signature is used to classify
71
+ * an entry, and those private fields never cross this module's output.
72
+ */
73
+ function evaluateEmpiricalCoverage(scan, staticStates, min, staticAvailability = 'available') {
74
+ if (!scan || typeof scan !== 'object' || !Array.isArray(scan.entries) || !scan.sources || typeof scan.omittedEvidenceRefs !== 'number') {
75
+ throw new Error('evaluateEmpiricalCoverage: invalid ledger scan');
76
+ }
77
+ if (!Number.isSafeInteger(min) || min < 1)
78
+ throw new Error('evaluateEmpiricalCoverage: min must be a positive safe integer');
79
+ if (staticAvailability !== 'available' && staticAvailability !== 'unavailable')
80
+ throw new Error('evaluateEmpiricalCoverage: invalid static availability');
81
+ const scanned = new Map(scan.entries.map(item => [item.entry, item]));
82
+ const typed = new Map();
83
+ const unclassified = [];
84
+ for (const item of scan.entries) {
85
+ if (item.entry.polarity !== 'finding')
86
+ continue;
87
+ if (item.entry.defectClass === undefined)
88
+ unclassified.push(item.entry);
89
+ else
90
+ typed.set(item.entry.defectClass, [...(typed.get(item.entry.defectClass) ?? []), item.entry]);
91
+ }
92
+ const classes = [];
93
+ for (const [defectClass, entries] of [...typed.entries()].sort(([a], [b]) => a.localeCompare(b))) {
94
+ const evidence = refsFor(entries, scanned);
95
+ const clusters = (0, cluster_1.clusterEntries)(entries, 1).map((cluster) => {
96
+ const signatures = signaturesFor(cluster.signatures);
97
+ const clusterEvidence = refsFor(cluster.entries, scanned);
98
+ return {
99
+ occurrences: cluster.count,
100
+ recurrent: cluster.count >= min,
101
+ severity: maxSeverity(cluster.entries),
102
+ kind: cluster.kind,
103
+ signatures: signatures.signatures,
104
+ omittedSignatures: signatures.omitted,
105
+ evidenceRefs: clusterEvidence.refs,
106
+ omittedEvidenceRefs: clusterEvidence.omitted,
107
+ };
108
+ });
109
+ classes.push({ defectClass, occurrences: entries.length, recurrent: clusters.some((cluster) => cluster.recurrent),
110
+ severity: maxSeverity(entries), outcome: outcomeFor(staticStates[defectClass], entries.length > 0, staticAvailability),
111
+ evidenceRefs: evidence.refs, omittedEvidenceRefs: evidence.omitted, clusters });
112
+ }
113
+ // Stable public order: recurrence emphasis, count, then class ID. The
114
+ // cluster helper already supplies its own deterministic inner ordering.
115
+ classes.sort((a, b) => Number(b.recurrent) - Number(a.recurrent) || b.occurrences - a.occurrences
116
+ || a.defectClass.localeCompare(b.defectClass));
117
+ const unclassifiedEvidence = refsFor(unclassified, scanned);
118
+ const retained = classes.reduce((total, item) => total + item.occurrences, 0) + unclassified.length;
119
+ const omittedEvidenceRefs = scan.omittedEvidenceRefs
120
+ + classes.reduce((total, item) => total + item.omittedEvidenceRefs, 0)
121
+ + unclassifiedEvidence.omitted;
122
+ const skipped = scan.sources.skippedFindings > 0 || omittedEvidenceRefs > 0 || unclassified.length > 0;
123
+ return {
124
+ recurrenceThreshold: min,
125
+ status: retained === 0 ? (skipped ? 'inconclusive' : 'no-evidence') : (skipped ? 'partial' : 'evidence'),
126
+ classes,
127
+ unclassified: { occurrences: unclassified.length, evidenceRefs: unclassifiedEvidence.refs, omittedEvidenceRefs: unclassifiedEvidence.omitted },
128
+ sources: { ...scan.sources, skippedByReason: { ...scan.sources.skippedByReason } },
129
+ omittedEvidenceRefs,
130
+ };
131
+ }
@@ -1,9 +1,15 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.toClassStatus = toClassStatus;
4
+ exports.reduceClassStatus = reduceClassStatus;
5
+ exports.crossEmpiricalOutcome = crossEmpiricalOutcome;
3
6
  exports.evaluateCoverage = evaluateCoverage;
4
7
  const detectorStatuses = ['covered', 'missing', 'disabled', 'ineffective', 'unverifiable'];
5
8
  const commandStatuses = ['matched', 'custom', 'missing'];
6
9
  const fileStatuses = ['matched', 'missing', 'unverifiable'];
10
+ const compatibilityStates = [
11
+ 'certified', 'compatible-unverified', 'incompatible', 'missing-tool', 'unverifiable', 'not-applicable',
12
+ ];
7
13
  function keyFor(classId, detectorIndex) {
8
14
  return `${classId}:${detectorIndex}`;
9
15
  }
@@ -31,6 +37,20 @@ function validateEvidence(evidence) {
31
37
  return false;
32
38
  });
33
39
  }
40
+ function validateCompatibility(value) {
41
+ if (!isRecord(value) || !compatibilityStates.includes(value.state)
42
+ || !isNonEmptyString(value.reason)
43
+ || !(value.variantId === null || isNonEmptyString(value.variantId))
44
+ || !(value.toolVersion === null || isNonEmptyString(value.toolVersion))
45
+ || !(value.runtimeVersion === null || isNonEmptyString(value.runtimeVersion))
46
+ || !(value.certifiedRange === null || isNonEmptyString(value.certifiedRange))
47
+ || !Array.isArray(value.evidence))
48
+ return false;
49
+ return value.evidence.every((item) => isRecord(item)
50
+ && isNonEmptyString(item.kind) && isNonEmptyString(item.status)
51
+ && (!('path' in item) || isNonEmptyString(item.path))
52
+ && Object.keys(item).every((key) => key === 'kind' || key === 'status' || key === 'path'));
53
+ }
34
54
  function validateContract(contract) {
35
55
  if (!isRecord(contract) || contract.schemaVersion !== 1 || !isRecord(contract.classes) || Object.keys(contract.classes).length === 0) {
36
56
  throw new Error('evaluateCoverage: invalid coverage contract');
@@ -52,11 +72,64 @@ function validateContract(contract) {
52
72
  function validateObservation(item) {
53
73
  return isRecord(item) && isNonEmptyString(item.classId) && typeof item.detectorIndex === 'number' && Number.isSafeInteger(item.detectorIndex)
54
74
  && item.detectorIndex >= 0 && isNonEmptyString(item.sensor) && typeof item.status === 'string'
55
- && detectorStatuses.includes(item.status) && validateEvidence(item.evidence);
75
+ && detectorStatuses.includes(item.status) && validateEvidence(item.evidence)
76
+ && (!('compatibility' in item) || validateCompatibility(item.compatibility));
56
77
  }
57
78
  function copyEvidence(evidence) {
58
79
  return evidence.map((item) => ({ ...item }));
59
80
  }
81
+ function copyCompatibility(value) {
82
+ return value === undefined ? undefined : {
83
+ ...value,
84
+ evidence: value.evidence.map((item) => ({ ...item })),
85
+ };
86
+ }
87
+ /** Map a detector's live compatibility plus its R2 structural evidence to a
88
+ * deterministic class alternative. A missing compatibility field is the R2
89
+ * compatibility path retained for direct callers while legacy/v2 coverage
90
+ * collection always supplies an explicit live resolution. */
91
+ function toClassStatus(detector) {
92
+ if (!detector || typeof detector !== 'object' || !detectorStatuses.includes(detector.status)
93
+ || !validateEvidence(detector.evidence))
94
+ throw new Error('toClassStatus: malformed detector observation');
95
+ const state = detector.compatibility?.state;
96
+ if (state === 'not-applicable')
97
+ return 'not-applicable';
98
+ if (state === 'compatible-unverified' || state === 'unverifiable')
99
+ return 'unverifiable';
100
+ if (state === 'incompatible' || state === 'missing-tool')
101
+ return 'missing';
102
+ if (detector.status === 'unverifiable')
103
+ return 'unverifiable';
104
+ if (detector.status === 'covered')
105
+ return 'covered';
106
+ return 'missing';
107
+ }
108
+ const rank = {
109
+ covered: 4,
110
+ unverifiable: 3,
111
+ missing: 2,
112
+ 'not-applicable': 1,
113
+ };
114
+ function reduceClassStatus(detectors) {
115
+ if (!Array.isArray(detectors) || detectors.length === 0)
116
+ throw new Error('coverage class requires detectors');
117
+ const statuses = detectors.map(toClassStatus);
118
+ return statuses.reduce((best, current) => rank[current] > rank[best] ? current : best);
119
+ }
120
+ /** Shared static/empirical vocabulary. The empirical analyzer supplies the
121
+ * occurrence boolean in T6; keeping the contradiction decision here prevents
122
+ * a second interpretation of applicability in that later layer. */
123
+ function crossEmpiricalOutcome(status, hasEvidence) {
124
+ if (typeof hasEvidence !== 'boolean' || !['covered', 'missing', 'unverifiable', 'not-applicable'].includes(status)) {
125
+ throw new Error('crossEmpiricalOutcome: invalid static status or evidence flag');
126
+ }
127
+ if (status === 'not-applicable' && hasEvidence)
128
+ return 'applicability-contradiction';
129
+ if (status === 'covered')
130
+ return 'covered-by-sensor';
131
+ return status === 'missing' ? 'gap' : 'coverage-unverifiable';
132
+ }
60
133
  function evaluateCoverage(contract, observations) {
61
134
  validateContract(contract);
62
135
  if (!Array.isArray(observations))
@@ -80,11 +153,14 @@ function evaluateCoverage(contract, observations) {
80
153
  if (found.sensor !== detector.sensor) {
81
154
  throw new Error(`evaluateCoverage: observation for '${identity}' must use sensor '${detector.sensor}'`);
82
155
  }
83
- return { sensor: found.sensor, status: found.status, evidence: copyEvidence(found.evidence) };
156
+ return {
157
+ sensor: found.sensor,
158
+ status: found.status,
159
+ evidence: copyEvidence(found.evidence),
160
+ ...(found.compatibility === undefined ? {} : { compatibility: copyCompatibility(found.compatibility) }),
161
+ };
84
162
  });
85
- const status = detectors.some((item) => item.status === 'covered') ? 'covered'
86
- : detectors.some((item) => item.status === 'unverifiable') ? 'unverifiable'
87
- : 'missing';
163
+ const status = reduceClassStatus(detectors);
88
164
  return {
89
165
  id,
90
166
  description: expected.description,
@@ -100,8 +176,10 @@ function evaluateCoverage(contract, observations) {
100
176
  throw new Error(`evaluateCoverage: unexpected observation for '${identity}' (${observation.sensor})`);
101
177
  }
102
178
  }
103
- const overall = classes.some((item) => item.status === 'missing') ? 'gaps'
179
+ const applicable = classes.filter((item) => item.status !== 'not-applicable');
180
+ const overall = applicable.some((item) => item.status === 'missing') ? 'gaps'
104
181
  : classes.some((item) => item.status === 'unverifiable') ? 'inconclusive'
105
- : 'covered';
182
+ : applicable.some((item) => item.status === 'covered') ? 'covered'
183
+ : 'inconclusive';
106
184
  return { overall, classes };
107
185
  }
@@ -98,7 +98,11 @@ function observeDetector(root, classId, detectorIndex, detector, sensor, io = re
98
98
  if (sensor.enabled === false)
99
99
  return { ...base, status: 'disabled', evidence: [] };
100
100
  const requiredCommand = detector.evidence?.commandIncludes ?? [];
101
- const command = sensor.cmd;
101
+ // v2 commands are compared only in-memory to the catalog's structural
102
+ // fragments. Neither the command nor its args cross the coverage boundary.
103
+ const command = 'command' in sensor
104
+ ? [sensor.command.executable, ...sensor.command.args].join(' ')
105
+ : sensor.cmd;
102
106
  if (requiredCommand.length > 0 && typeof command !== 'string') {
103
107
  return { ...base, status: 'unverifiable', evidence: [{ kind: 'command', status: 'missing' }] };
104
108
  }
@@ -1,31 +1,68 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.runCoverage = runCoverage;
4
7
  const evidence_1 = require("./evidence");
5
8
  const evaluate_1 = require("./evaluate");
6
9
  const resolve_1 = require("./resolve");
7
- const defaults = { resolve: resolve_1.resolveCoverageInputs, observe: evidence_1.observeDetector };
10
+ const live_1 = require("../compatibility/live");
11
+ const manifest_1 = require("../compatibility/manifest");
12
+ const scan_1 = require("../../../core/ledger/scan");
13
+ const empirical_1 = require("./empirical");
14
+ const path_1 = __importDefault(require("path"));
15
+ const defaults = { resolve: resolve_1.resolveCoverageInputs, observe: evidence_1.observeDetector, resolveLive: live_1.resolveLiveCompatibility, scan: scan_1.scanProjectLedgers };
16
+ function normalizedMin(value) {
17
+ if (value === undefined)
18
+ return 2;
19
+ if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 1)
20
+ throw new Error('runCoverage: min must be a positive safe integer');
21
+ return value;
22
+ }
23
+ function empirical(deps, projectRoot, states, min, staticAvailability = 'available') {
24
+ return (0, empirical_1.evaluateEmpiricalCoverage)(deps.scan(projectRoot), states, min, staticAvailability);
25
+ }
8
26
  /** Build static coverage data only: no command execution and no writes. */
9
- function runCoverage(cwd, dependencies = {}) {
27
+ async function runCoverage(cwd, dependencies = {}, options = {}) {
10
28
  if (typeof cwd !== 'string' || cwd.trim().length === 0)
11
29
  throw new Error('runCoverage: cwd must be a non-empty string');
30
+ const min = normalizedMin(options.min);
12
31
  const deps = { ...defaults, ...dependencies };
13
- const input = deps.resolve(cwd);
32
+ const input = await deps.resolve(cwd);
14
33
  if (input.kind === 'not_configured') {
15
- return { schemaVersion: 1, pack: null, registry: null, overall: 'inconclusive', static: { status: 'inconclusive', reason: 'not_configured', classes: [] } };
34
+ return { schemaVersion: 2, pack: null, registry: null, overall: 'inconclusive', static: { status: 'inconclusive', reason: 'not_configured', classes: [] },
35
+ empirical: empirical(deps, path_1.default.resolve(cwd), {}, min, 'unavailable') };
16
36
  }
17
37
  if (input.kind === 'no_reference') {
18
- return { schemaVersion: 1, pack: input.pack, registry: input.registry, overall: 'inconclusive', static: { status: 'inconclusive', reason: 'no_reference', classes: [] } };
38
+ return { schemaVersion: 2, pack: input.pack, registry: input.registry, overall: 'inconclusive', static: { status: 'inconclusive', reason: 'no_reference', classes: [] },
39
+ empirical: empirical(deps, input.projectRoot, {}, min, 'unavailable') };
40
+ }
41
+ let live;
42
+ if (input.manifest.kind === 'v2') {
43
+ live = (await deps.resolveLive(input.projectRoot, input.pack, input.manifest.pack.registryRoot)).sensors;
44
+ }
45
+ else {
46
+ live = Object.fromEntries(Object.keys(input.manifest.pack.sensors).map((name) => [name, (0, manifest_1.legacyCompatibility)('legacy manifest without schemaVersion')]));
19
47
  }
20
48
  const observations = [];
21
49
  for (const [classId, coverageClass] of Object.entries(input.contract.classes)) {
22
50
  coverageClass.detectors.forEach((detector, detectorIndex) => {
23
- observations.push(deps.observe(input.projectRoot, classId, detectorIndex, detector, input.manifest.sensors[detector.sensor]));
51
+ const observed = deps.observe(input.projectRoot, classId, detectorIndex, detector, input.manifest.pack.sensors[detector.sensor]);
52
+ observations.push({
53
+ ...observed,
54
+ compatibility: live[detector.sensor] ?? {
55
+ state: 'unverifiable', reason: 'sensor-not-resolved', variantId: null,
56
+ toolVersion: null, runtimeVersion: null, certifiedRange: null, evidence: [],
57
+ },
58
+ });
24
59
  });
25
60
  }
26
61
  const evaluated = (0, evaluate_1.evaluateCoverage)(input.contract, observations);
62
+ const states = Object.fromEntries(evaluated.classes.map((entry) => [entry.id, entry.status]));
27
63
  return {
28
- schemaVersion: 1, pack: input.pack, registry: input.registry, overall: evaluated.overall,
64
+ schemaVersion: 2, pack: input.pack, registry: input.registry, overall: evaluated.overall,
29
65
  static: { status: evaluated.overall, reason: null, classes: evaluated.classes },
66
+ empirical: empirical(deps, input.projectRoot, states, min),
30
67
  };
31
68
  }