agentic-workflow-manager 6.4.2 → 6.5.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.
@@ -0,0 +1,156 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.MAX_COVERAGE_FILE_BYTES = exports.COVERAGE_SCHEMA_VERSION = void 0;
4
+ exports.parseCoverageContract = parseCoverageContract;
5
+ exports.parseCoverageManifest = parseCoverageManifest;
6
+ exports.COVERAGE_SCHEMA_VERSION = 1;
7
+ exports.MAX_COVERAGE_FILE_BYTES = 1024 * 1024;
8
+ function isRecord(value) {
9
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
10
+ }
11
+ function sourceSuffix(source) {
12
+ return typeof source === 'string' && source.length > 0 ? ` in ${source}` : '';
13
+ }
14
+ function invalid(source, message) {
15
+ throw new Error(`Invalid coverage contract${sourceSuffix(source)}: ${message}`);
16
+ }
17
+ function record(value, source, location) {
18
+ if (!isRecord(value))
19
+ invalid(source, `${location} must be an object`);
20
+ return value;
21
+ }
22
+ function fields(value, allowed, source, location) {
23
+ for (const key of Object.keys(value)) {
24
+ if (!allowed.includes(key))
25
+ invalid(source, `${location} has unknown field "${key}"`);
26
+ }
27
+ }
28
+ function nonEmptyString(value, source, location) {
29
+ if (typeof value !== 'string' || value.trim().length === 0)
30
+ invalid(source, `${location} must be a nonempty string`);
31
+ return value;
32
+ }
33
+ function safeName(value, source, location) {
34
+ const name = nonEmptyString(value, source, location);
35
+ if (name === '.' || name === '..' || name.includes('..') || /[/\\]/.test(name) || !/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(name)) {
36
+ invalid(source, `${location} must be a safe filename component`);
37
+ }
38
+ return name;
39
+ }
40
+ function stringArray(value, source, location, allowEmpty) {
41
+ if (!Array.isArray(value) || (!allowEmpty && value.length === 0)) {
42
+ invalid(source, `${location} must be ${allowEmpty ? 'an array' : 'a nonempty array'}`);
43
+ }
44
+ return value.map((item, index) => nonEmptyString(item, source, `${location}[${index}]`));
45
+ }
46
+ function parseFileRequirement(input, source, location) {
47
+ const value = record(input, source, location);
48
+ fields(value, ['path', 'containsAll'], source, location);
49
+ const path = safeName(value.path, source, `${location}.path`);
50
+ const containsAll = stringArray(value.containsAll, source, `${location}.containsAll`, true);
51
+ return { path, containsAll };
52
+ }
53
+ function parseEvidence(input, source, location) {
54
+ const value = record(input, source, location);
55
+ fields(value, ['commandIncludes', 'files'], source, location);
56
+ const evidence = {};
57
+ if ('commandIncludes' in value)
58
+ evidence.commandIncludes = stringArray(value.commandIncludes, source, `${location}.commandIncludes`, false);
59
+ if ('files' in value) {
60
+ if (!Array.isArray(value.files) || value.files.length === 0)
61
+ invalid(source, `${location}.files must be a nonempty array`);
62
+ evidence.files = value.files.map((file, index) => parseFileRequirement(file, source, `${location}.files[${index}]`));
63
+ }
64
+ return evidence;
65
+ }
66
+ function parseDetector(input, source, location) {
67
+ const value = record(input, source, location);
68
+ fields(value, ['sensor', 'evidence'], source, location);
69
+ const detector = { sensor: safeName(value.sensor, source, `${location}.sensor`) };
70
+ if ('evidence' in value)
71
+ detector.evidence = parseEvidence(value.evidence, source, `${location}.evidence`);
72
+ return detector;
73
+ }
74
+ function parseClass(input, source, location) {
75
+ const value = record(input, source, location);
76
+ fields(value, ['description', 'detectors', 'remedy'], source, location);
77
+ const description = nonEmptyString(value.description, source, `${location}.description`);
78
+ if (!Array.isArray(value.detectors) || value.detectors.length === 0)
79
+ invalid(source, `${location}.detectors must be a nonempty array`);
80
+ const remedyInput = record(value.remedy, source, `${location}.remedy`);
81
+ fields(remedyInput, ['summary', 'command'], source, `${location}.remedy`);
82
+ return {
83
+ description,
84
+ detectors: value.detectors.map((detector, index) => parseDetector(detector, source, `${location}.detectors[${index}]`)),
85
+ remedy: {
86
+ summary: nonEmptyString(remedyInput.summary, source, `${location}.remedy.summary`),
87
+ command: nonEmptyString(remedyInput.command, source, `${location}.remedy.command`),
88
+ },
89
+ };
90
+ }
91
+ function parseCoverageContract(input, source) {
92
+ const value = record(input, source, 'root');
93
+ fields(value, ['schemaVersion', 'classes'], source, 'root');
94
+ if (value.schemaVersion !== exports.COVERAGE_SCHEMA_VERSION)
95
+ invalid(source, `schemaVersion must be ${exports.COVERAGE_SCHEMA_VERSION}`);
96
+ const classesInput = record(value.classes, source, 'classes');
97
+ const names = Object.keys(classesInput);
98
+ if (names.length === 0)
99
+ invalid(source, 'classes must be nonempty');
100
+ const classes = {};
101
+ for (const name of names) {
102
+ if (!/^[a-z][a-z0-9-]*$/.test(name))
103
+ invalid(source, `class "${name}" must use [a-z][a-z0-9-]*`);
104
+ classes[name] = parseClass(classesInput[name], source, `classes.${name}`);
105
+ }
106
+ return { schemaVersion: exports.COVERAGE_SCHEMA_VERSION, classes };
107
+ }
108
+ function manifestString(value, source, location) {
109
+ return nonEmptyString(value, source, location);
110
+ }
111
+ function parseManifestSensor(input, source, location) {
112
+ const value = record(input, source, location);
113
+ fields(value, ['cmd', 'fast', 'enabled', 'timeout', 'changedCmd', 'changedExtensions', 'formatter'], source, location);
114
+ const sensor = {};
115
+ if ('cmd' in value)
116
+ sensor.cmd = manifestString(value.cmd, source, `${location}.cmd`);
117
+ if ('fast' in value) {
118
+ if (typeof value.fast !== 'boolean')
119
+ invalid(source, `${location}.fast must be a boolean`);
120
+ sensor.fast = value.fast;
121
+ }
122
+ if ('enabled' in value) {
123
+ if (typeof value.enabled !== 'boolean')
124
+ invalid(source, `${location}.enabled must be a boolean`);
125
+ sensor.enabled = value.enabled;
126
+ }
127
+ if ('timeout' in value) {
128
+ if (typeof value.timeout !== 'number' || !Number.isSafeInteger(value.timeout) || value.timeout <= 0)
129
+ invalid(source, `${location}.timeout must be a positive safe integer`);
130
+ sensor.timeout = value.timeout;
131
+ }
132
+ if ('changedCmd' in value)
133
+ sensor.changedCmd = manifestString(value.changedCmd, source, `${location}.changedCmd`);
134
+ if ('changedExtensions' in value)
135
+ sensor.changedExtensions = stringArray(value.changedExtensions, source, `${location}.changedExtensions`, true);
136
+ if ('formatter' in value)
137
+ sensor.formatter = manifestString(value.formatter, source, `${location}.formatter`);
138
+ return sensor;
139
+ }
140
+ function parseCoverageManifest(input, source) {
141
+ const value = record(input, source, 'manifest root');
142
+ fields(value, ['pack', 'sensors', 'concurrency'], source, 'manifest root');
143
+ const pack = safeName(value.pack, source, 'manifest.pack');
144
+ const sensorsInput = record(value.sensors, source, 'manifest.sensors');
145
+ const sensors = {};
146
+ for (const name of Object.keys(sensorsInput)) {
147
+ sensors[safeName(name, source, 'manifest sensor name')] = parseManifestSensor(sensorsInput[name], source, `manifest.sensors.${name}`);
148
+ }
149
+ const manifest = { pack, sensors };
150
+ if ('concurrency' in value) {
151
+ if (typeof value.concurrency !== 'number' || !Number.isSafeInteger(value.concurrency) || value.concurrency <= 0)
152
+ invalid(source, 'manifest.concurrency must be a positive safe integer');
153
+ manifest.concurrency = value.concurrency;
154
+ }
155
+ return manifest;
156
+ }
@@ -0,0 +1,107 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.evaluateCoverage = evaluateCoverage;
4
+ const detectorStatuses = ['covered', 'missing', 'disabled', 'ineffective', 'unverifiable'];
5
+ const commandStatuses = ['matched', 'custom', 'missing'];
6
+ const fileStatuses = ['matched', 'missing', 'unverifiable'];
7
+ function keyFor(classId, detectorIndex) {
8
+ return `${classId}:${detectorIndex}`;
9
+ }
10
+ function isRecord(value) {
11
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
12
+ }
13
+ function isNonEmptyString(value) {
14
+ return typeof value === 'string' && value.length > 0;
15
+ }
16
+ function validateEvidence(evidence) {
17
+ if (!Array.isArray(evidence))
18
+ return false;
19
+ return evidence.every((item) => {
20
+ if (!isRecord(item) || typeof item.kind !== 'string' || typeof item.status !== 'string')
21
+ return false;
22
+ if (item.kind === 'command')
23
+ return commandStatuses.includes(item.status) && Object.keys(item).every((key) => key === 'kind' || key === 'status');
24
+ if (item.kind === 'file')
25
+ return fileStatuses.includes(item.status) && isNonEmptyString(item.path)
26
+ && Object.keys(item).every((key) => key === 'kind' || key === 'path' || key === 'status');
27
+ if (item.kind === 'marker')
28
+ return fileStatuses.includes(item.status) && isNonEmptyString(item.path)
29
+ && typeof item.ordinal === 'number' && Number.isSafeInteger(item.ordinal) && item.ordinal > 0
30
+ && Object.keys(item).every((key) => key === 'kind' || key === 'path' || key === 'ordinal' || key === 'status');
31
+ return false;
32
+ });
33
+ }
34
+ function validateContract(contract) {
35
+ if (!isRecord(contract) || contract.schemaVersion !== 1 || !isRecord(contract.classes) || Object.keys(contract.classes).length === 0) {
36
+ throw new Error('evaluateCoverage: invalid coverage contract');
37
+ }
38
+ for (const [classId, coverageClass] of Object.entries(contract.classes)) {
39
+ if (!isNonEmptyString(classId) || !isRecord(coverageClass) || !isNonEmptyString(coverageClass.description)
40
+ || !Array.isArray(coverageClass.detectors) || coverageClass.detectors.length === 0
41
+ || !isRecord(coverageClass.remedy) || !isNonEmptyString(coverageClass.remedy.summary)
42
+ || !isNonEmptyString(coverageClass.remedy.command)) {
43
+ throw new Error(`evaluateCoverage: invalid coverage contract class '${classId}'`);
44
+ }
45
+ for (const detector of coverageClass.detectors) {
46
+ if (!isRecord(detector) || !isNonEmptyString(detector.sensor)) {
47
+ throw new Error(`evaluateCoverage: invalid coverage contract detector for '${classId}'`);
48
+ }
49
+ }
50
+ }
51
+ }
52
+ function validateObservation(item) {
53
+ return isRecord(item) && isNonEmptyString(item.classId) && typeof item.detectorIndex === 'number' && Number.isSafeInteger(item.detectorIndex)
54
+ && item.detectorIndex >= 0 && isNonEmptyString(item.sensor) && typeof item.status === 'string'
55
+ && detectorStatuses.includes(item.status) && validateEvidence(item.evidence);
56
+ }
57
+ function copyEvidence(evidence) {
58
+ return evidence.map((item) => ({ ...item }));
59
+ }
60
+ function evaluateCoverage(contract, observations) {
61
+ validateContract(contract);
62
+ if (!Array.isArray(observations))
63
+ throw new Error('evaluateCoverage: observations must be an array');
64
+ const indexed = new Map();
65
+ for (const item of observations) {
66
+ if (!validateObservation(item))
67
+ throw new Error('evaluateCoverage: malformed observation');
68
+ const key = keyFor(item.classId, item.detectorIndex);
69
+ if (indexed.has(key))
70
+ throw new Error(`evaluateCoverage: duplicate observation for '${key}' (${item.sensor})`);
71
+ indexed.set(key, item);
72
+ }
73
+ const classes = Object.keys(contract.classes).sort().map((id) => {
74
+ const expected = contract.classes[id];
75
+ const detectors = expected.detectors.map((detector, detectorIndex) => {
76
+ const identity = keyFor(id, detectorIndex);
77
+ const found = indexed.get(identity);
78
+ if (!found)
79
+ throw new Error(`evaluateCoverage: missing observation for '${identity}' (${detector.sensor})`);
80
+ if (found.sensor !== detector.sensor) {
81
+ throw new Error(`evaluateCoverage: observation for '${identity}' must use sensor '${detector.sensor}'`);
82
+ }
83
+ return { sensor: found.sensor, status: found.status, evidence: copyEvidence(found.evidence) };
84
+ });
85
+ const status = detectors.some((item) => item.status === 'covered') ? 'covered'
86
+ : detectors.some((item) => item.status === 'unverifiable') ? 'unverifiable'
87
+ : 'missing';
88
+ return {
89
+ id,
90
+ description: expected.description,
91
+ status,
92
+ detectors,
93
+ remedy: { ...expected.remedy },
94
+ };
95
+ });
96
+ if (indexed.size !== classes.reduce((count, coverageClass) => count + coverageClass.detectors.length, 0)) {
97
+ for (const [identity, observation] of indexed) {
98
+ const expected = contract.classes[observation.classId]?.detectors[observation.detectorIndex];
99
+ if (!expected)
100
+ throw new Error(`evaluateCoverage: unexpected observation for '${identity}' (${observation.sensor})`);
101
+ }
102
+ }
103
+ const overall = classes.some((item) => item.status === 'missing') ? 'gaps'
104
+ : classes.some((item) => item.status === 'unverifiable') ? 'inconclusive'
105
+ : 'covered';
106
+ return { overall, classes };
107
+ }
@@ -0,0 +1,118 @@
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.observeDetector = observeDetector;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const contract_1 = require("./contract");
10
+ const realIo = {
11
+ lstatSync: fs_1.default.lstatSync,
12
+ openSync: fs_1.default.openSync,
13
+ fstatSync: fs_1.default.fstatSync,
14
+ readSync: fs_1.default.readSync,
15
+ closeSync: fs_1.default.closeSync,
16
+ };
17
+ function resolvedEvidencePath(root, relative) {
18
+ const rootPath = path_1.default.resolve(root);
19
+ const absolute = path_1.default.resolve(rootPath, relative);
20
+ const fromRoot = path_1.default.relative(rootPath, absolute);
21
+ if (fromRoot === '..' || fromRoot.startsWith(`..${path_1.default.sep}`) || path_1.default.isAbsolute(fromRoot)) {
22
+ throw new Error(`evidence path escaped project root: ${relative}`);
23
+ }
24
+ return absolute;
25
+ }
26
+ function inspectFile(root, relative, markers, io) {
27
+ const absolute = resolvedEvidencePath(root, relative);
28
+ let stat;
29
+ try {
30
+ stat = io.lstatSync(absolute);
31
+ }
32
+ catch (error) {
33
+ if (error.code === 'ENOENT') {
34
+ return { status: 'missing', evidence: [{ kind: 'file', path: relative, status: 'missing' }] };
35
+ }
36
+ return { status: 'unverifiable', evidence: [{ kind: 'file', path: relative, status: 'unverifiable' }] };
37
+ }
38
+ if (stat.isSymbolicLink() || !stat.isFile() || stat.size > contract_1.MAX_COVERAGE_FILE_BYTES) {
39
+ return { status: 'unverifiable', evidence: [{ kind: 'file', path: relative, status: 'unverifiable' }] };
40
+ }
41
+ const noFollow = fs_1.default.constants.O_NOFOLLOW;
42
+ if (typeof noFollow !== 'number') {
43
+ return { status: 'unverifiable', evidence: [{ kind: 'file', path: relative, status: 'unverifiable' }] };
44
+ }
45
+ let fd;
46
+ let content;
47
+ try {
48
+ fd = (io.openSync ?? realIo.openSync)(absolute, fs_1.default.constants.O_RDONLY | noFollow);
49
+ const opened = (io.fstatSync ?? realIo.fstatSync)(fd);
50
+ if (!opened.isFile() || opened.size > contract_1.MAX_COVERAGE_FILE_BYTES) {
51
+ return { status: 'unverifiable', evidence: [{ kind: 'file', path: relative, status: 'unverifiable' }] };
52
+ }
53
+ const buffer = Buffer.allocUnsafe(contract_1.MAX_COVERAGE_FILE_BYTES + 1);
54
+ const bytesRead = (io.readSync ?? realIo.readSync)(fd, buffer, 0, buffer.length, null);
55
+ if (!Number.isSafeInteger(bytesRead) || bytesRead < 0 || bytesRead > contract_1.MAX_COVERAGE_FILE_BYTES) {
56
+ return { status: 'unverifiable', evidence: [{ kind: 'file', path: relative, status: 'unverifiable' }] };
57
+ }
58
+ content = buffer.subarray(0, bytesRead).toString('utf8');
59
+ }
60
+ catch {
61
+ return { status: 'unverifiable', evidence: [{ kind: 'file', path: relative, status: 'unverifiable' }] };
62
+ }
63
+ finally {
64
+ if (fd !== undefined) {
65
+ try {
66
+ (io.closeSync ?? realIo.closeSync)(fd);
67
+ }
68
+ catch {
69
+ // best-effort: a close failure does not make already-read evidence unsafe.
70
+ }
71
+ }
72
+ }
73
+ const evidence = [{ kind: 'file', path: relative, status: 'matched' }];
74
+ for (const [index, marker] of markers.entries()) {
75
+ evidence.push({
76
+ kind: 'marker',
77
+ path: relative,
78
+ ordinal: index + 1,
79
+ status: content.includes(marker) ? 'matched' : 'missing',
80
+ });
81
+ }
82
+ return {
83
+ status: evidence.some((item) => item.kind === 'marker' && item.status === 'missing') ? 'missing' : 'matched',
84
+ evidence,
85
+ };
86
+ }
87
+ function observeDetector(root, classId, detectorIndex, detector, sensor, io = realIo) {
88
+ if (typeof root !== 'string' || root.trim().length === 0)
89
+ throw new Error('observeDetector: root must be a non-empty string');
90
+ if (typeof classId !== 'string' || classId.trim().length === 0)
91
+ throw new Error('observeDetector: classId must be a non-empty string');
92
+ if (typeof detectorIndex !== 'number' || !Number.isSafeInteger(detectorIndex) || detectorIndex < 0) {
93
+ throw new Error('observeDetector: detectorIndex must be a non-negative integer');
94
+ }
95
+ const base = { classId, detectorIndex, sensor: detector.sensor };
96
+ if (!sensor)
97
+ return { ...base, status: 'missing', evidence: [] };
98
+ if (sensor.enabled === false)
99
+ return { ...base, status: 'disabled', evidence: [] };
100
+ const requiredCommand = detector.evidence?.commandIncludes ?? [];
101
+ const command = sensor.cmd;
102
+ if (requiredCommand.length > 0 && typeof command !== 'string') {
103
+ return { ...base, status: 'unverifiable', evidence: [{ kind: 'command', status: 'missing' }] };
104
+ }
105
+ if (requiredCommand.some((fragment) => !command.includes(fragment))) {
106
+ return { ...base, status: 'unverifiable', evidence: [{ kind: 'command', status: 'custom' }] };
107
+ }
108
+ const evidence = requiredCommand.length > 0 ? [{ kind: 'command', status: 'matched' }] : [];
109
+ let ineffective = false;
110
+ let unverifiable = false;
111
+ for (const file of detector.evidence?.files ?? []) {
112
+ const result = inspectFile(root, file.path, file.containsAll, io);
113
+ evidence.push(...result.evidence);
114
+ ineffective ||= result.status === 'missing';
115
+ unverifiable ||= result.status === 'unverifiable';
116
+ }
117
+ return { ...base, status: unverifiable ? 'unverifiable' : ineffective ? 'ineffective' : 'covered', evidence };
118
+ }
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.runCoverage = runCoverage;
4
+ const evidence_1 = require("./evidence");
5
+ const evaluate_1 = require("./evaluate");
6
+ const resolve_1 = require("./resolve");
7
+ const defaults = { resolve: resolve_1.resolveCoverageInputs, observe: evidence_1.observeDetector };
8
+ /** Build static coverage data only: no command execution and no writes. */
9
+ function runCoverage(cwd, dependencies = {}) {
10
+ if (typeof cwd !== 'string' || cwd.trim().length === 0)
11
+ throw new Error('runCoverage: cwd must be a non-empty string');
12
+ const deps = { ...defaults, ...dependencies };
13
+ const input = deps.resolve(cwd);
14
+ if (input.kind === 'not_configured') {
15
+ return { schemaVersion: 1, pack: null, registry: null, overall: 'inconclusive', static: { status: 'inconclusive', reason: 'not_configured', classes: [] } };
16
+ }
17
+ if (input.kind === 'no_reference') {
18
+ return { schemaVersion: 1, pack: input.pack, registry: input.registry, overall: 'inconclusive', static: { status: 'inconclusive', reason: 'no_reference', classes: [] } };
19
+ }
20
+ const observations = [];
21
+ for (const [classId, coverageClass] of Object.entries(input.contract.classes)) {
22
+ coverageClass.detectors.forEach((detector, detectorIndex) => {
23
+ observations.push(deps.observe(input.projectRoot, classId, detectorIndex, detector, input.manifest.sensors[detector.sensor]));
24
+ });
25
+ }
26
+ const evaluated = (0, evaluate_1.evaluateCoverage)(input.contract, observations);
27
+ return {
28
+ schemaVersion: 1, pack: input.pack, registry: input.registry, overall: evaluated.overall,
29
+ static: { status: evaluated.overall, reason: null, classes: evaluated.classes },
30
+ };
31
+ }
@@ -0,0 +1,152 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderCoverageJson = renderCoverageJson;
4
+ exports.renderCoverageHuman = renderCoverageHuman;
5
+ const ANSI = /\x1B\[[0-?]*[ -/]*[@-~]/g;
6
+ const OSC = /\x1B\][\s\S]*?(?:\x07|\x1B\\)/g;
7
+ const CONTROLS = /[\u0000-\u001F\u007F-\u009F]/g;
8
+ const OVERALL = ['covered', 'gaps', 'inconclusive'];
9
+ const CLASS_STATUS = ['covered', 'missing', 'unverifiable'];
10
+ const DETECTOR_STATUS = ['covered', 'missing', 'disabled', 'ineffective', 'unverifiable'];
11
+ const REASON = ['not_configured', 'no_reference'];
12
+ const COMMAND_EVIDENCE_STATUS = ['matched', 'custom', 'missing'];
13
+ const FILE_EVIDENCE_STATUS = ['matched', 'missing', 'unverifiable'];
14
+ function isRecord(value) {
15
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
16
+ }
17
+ function isOneOf(value, options) {
18
+ return typeof value === 'string' && options.includes(value);
19
+ }
20
+ function hasExactFields(value, fields) {
21
+ const keys = Object.keys(value);
22
+ return keys.length === fields.length && keys.every((key) => fields.includes(key));
23
+ }
24
+ function isNonBlankString(value) {
25
+ return typeof value === 'string' && value.trim().length > 0;
26
+ }
27
+ function invalidReport(renderer) {
28
+ throw new Error(`${renderer}: invalid report`);
29
+ }
30
+ function assertEvidence(evidence, renderer) {
31
+ if (!Array.isArray(evidence))
32
+ invalidReport(renderer);
33
+ for (const item of evidence) {
34
+ if (!isRecord(item))
35
+ invalidReport(renderer);
36
+ if (item.kind === 'command') {
37
+ if (!hasExactFields(item, ['kind', 'status']) || !isOneOf(item.status, COMMAND_EVIDENCE_STATUS))
38
+ invalidReport(renderer);
39
+ }
40
+ else if (item.kind === 'file') {
41
+ if (!hasExactFields(item, ['kind', 'path', 'status']) || !isNonBlankString(item.path)
42
+ || !isOneOf(item.status, FILE_EVIDENCE_STATUS))
43
+ invalidReport(renderer);
44
+ }
45
+ else if (item.kind === 'marker') {
46
+ if (!hasExactFields(item, ['kind', 'path', 'ordinal', 'status']) || !isNonBlankString(item.path)
47
+ || typeof item.ordinal !== 'number' || !Number.isSafeInteger(item.ordinal) || item.ordinal <= 0
48
+ || !isOneOf(item.status, FILE_EVIDENCE_STATUS))
49
+ invalidReport(renderer);
50
+ }
51
+ else {
52
+ invalidReport(renderer);
53
+ }
54
+ }
55
+ }
56
+ function assertCoverageEnvelope(report, renderer) {
57
+ if (!isRecord(report) || !hasExactFields(report, 'empirical' in report
58
+ ? ['schemaVersion', 'pack', 'registry', 'overall', 'static', 'empirical']
59
+ : ['schemaVersion', 'pack', 'registry', 'overall', 'static']) || report.schemaVersion !== 1
60
+ || !(report.pack === null || isNonBlankString(report.pack))
61
+ || !(report.registry === null || isNonBlankString(report.registry))
62
+ || !isOneOf(report.overall, OVERALL) || !isRecord(report.static)) {
63
+ invalidReport(renderer);
64
+ }
65
+ const staticReport = report.static;
66
+ if (!hasExactFields(staticReport, ['status', 'reason', 'classes']) || staticReport.status !== report.overall
67
+ || !isOneOf(staticReport.status, OVERALL)
68
+ || !(staticReport.reason === null || isOneOf(staticReport.reason, REASON))
69
+ || !Array.isArray(staticReport.classes)) {
70
+ invalidReport(renderer);
71
+ }
72
+ if (staticReport.reason === 'not_configured') {
73
+ if (report.overall !== 'inconclusive' || report.pack !== null || report.registry !== null || staticReport.classes.length !== 0) {
74
+ invalidReport(renderer);
75
+ }
76
+ return;
77
+ }
78
+ if (staticReport.reason === 'no_reference') {
79
+ if (report.overall !== 'inconclusive' || !isNonBlankString(report.pack) || !isNonBlankString(report.registry)
80
+ || staticReport.classes.length !== 0) {
81
+ invalidReport(renderer);
82
+ }
83
+ return;
84
+ }
85
+ if (!isNonBlankString(report.pack) || !isNonBlankString(report.registry) || staticReport.classes.length === 0) {
86
+ invalidReport(renderer);
87
+ }
88
+ let previousId;
89
+ let hasMissingClass = false;
90
+ let hasUnverifiableClass = false;
91
+ for (const coverageClass of staticReport.classes) {
92
+ if (!isRecord(coverageClass) || !hasExactFields(coverageClass, ['id', 'description', 'status', 'detectors', 'remedy'])
93
+ || !isNonBlankString(coverageClass.id) || !isNonBlankString(coverageClass.description)
94
+ || !isOneOf(coverageClass.status, CLASS_STATUS) || !Array.isArray(coverageClass.detectors) || coverageClass.detectors.length === 0
95
+ || !isRecord(coverageClass.remedy)) {
96
+ invalidReport(renderer);
97
+ }
98
+ if (previousId !== undefined && previousId >= coverageClass.id)
99
+ invalidReport(renderer);
100
+ previousId = coverageClass.id;
101
+ if (!hasExactFields(coverageClass.remedy, ['summary', 'command']))
102
+ invalidReport(renderer);
103
+ if (!isNonBlankString(coverageClass.remedy.summary) || !isNonBlankString(coverageClass.remedy.command))
104
+ invalidReport(renderer);
105
+ let hasCoveredDetector = false;
106
+ let hasUnverifiableDetector = false;
107
+ for (const detector of coverageClass.detectors) {
108
+ if (!isRecord(detector) || !hasExactFields(detector, ['sensor', 'status', 'evidence'])
109
+ || !isNonBlankString(detector.sensor) || !isOneOf(detector.status, DETECTOR_STATUS)) {
110
+ invalidReport(renderer);
111
+ }
112
+ assertEvidence(detector.evidence, renderer);
113
+ hasCoveredDetector ||= detector.status === 'covered';
114
+ hasUnverifiableDetector ||= detector.status === 'unverifiable';
115
+ }
116
+ const expectedClassStatus = hasCoveredDetector ? 'covered' : hasUnverifiableDetector ? 'unverifiable' : 'missing';
117
+ if (coverageClass.status !== expectedClassStatus)
118
+ invalidReport(renderer);
119
+ hasMissingClass ||= expectedClassStatus === 'missing';
120
+ hasUnverifiableClass ||= expectedClassStatus === 'unverifiable';
121
+ }
122
+ const expectedOverall = hasMissingClass ? 'gaps' : hasUnverifiableClass ? 'inconclusive' : 'covered';
123
+ if (report.overall !== expectedOverall)
124
+ invalidReport(renderer);
125
+ }
126
+ function safeHumanText(value) {
127
+ return value.replace(OSC, '').replace(ANSI, '').replace(CONTROLS, ' ');
128
+ }
129
+ function renderCoverageJson(report) {
130
+ assertCoverageEnvelope(report, 'renderCoverageJson');
131
+ return `${JSON.stringify(report, null, 2)}\n`;
132
+ }
133
+ function renderCoverageHuman(report) {
134
+ assertCoverageEnvelope(report, 'renderCoverageHuman');
135
+ if (report.static.reason === 'not_configured') {
136
+ return ['Sensor coverage', 'Overall: inconclusive', 'Reason: sensors are not configured', 'Run: awm sensors init', ''].join('\n');
137
+ }
138
+ if (report.static.reason === 'no_reference') {
139
+ return ['Sensor coverage', `Pack: ${safeHumanText(report.pack ?? 'unknown')}`, `Registry: ${safeHumanText(report.registry ?? 'unknown')}`,
140
+ 'Overall: inconclusive', `No coverage reference for pack '${safeHumanText(report.pack ?? 'unknown')}'`, ''].join('\n');
141
+ }
142
+ const lines = ['Sensor coverage', `Pack: ${safeHumanText(report.pack ?? 'unknown')}`, `Registry: ${safeHumanText(report.registry ?? 'unknown')}`,
143
+ `Overall: ${report.overall}`, ''];
144
+ for (const item of report.static.classes.filter((entry) => entry.status !== 'covered')) {
145
+ lines.push(`${item.status} ${safeHumanText(item.id)} — ${safeHumanText(item.description)}`);
146
+ item.detectors.forEach((detector) => lines.push(` detector: ${safeHumanText(detector.sensor)} (${detector.status})`));
147
+ lines.push(` remedy: ${safeHumanText(item.remedy.summary)}`, ` command: ${safeHumanText(item.remedy.command)}`);
148
+ }
149
+ const count = (status) => report.static.classes.filter((item) => item.status === status).length;
150
+ lines.push('', `Summary: ${count('covered')} covered, ${count('missing')} missing, ${count('unverifiable')} unverifiable`, '');
151
+ return lines.join('\n');
152
+ }