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.
- package/dist/scripts/sensor-support-matrix.js +103 -0
- package/dist/scripts/support-matrix.js +40 -17
- package/dist/src/commands/init.js +1 -3
- package/dist/src/commands/ledger/index.js +6 -0
- package/dist/src/commands/preflight/checks.js +4 -4
- package/dist/src/commands/preflight/index.js +2 -2
- package/dist/src/commands/registry/add.js +2 -2
- package/dist/src/commands/registry/index.js +5 -0
- package/dist/src/commands/registry/status.js +7 -0
- package/dist/src/commands/sensors/compatibility/contract.js +261 -0
- package/dist/src/commands/sensors/compatibility/discovery.js +207 -0
- package/dist/src/commands/sensors/compatibility/live.js +59 -0
- package/dist/src/commands/sensors/compatibility/manifest.js +197 -0
- package/dist/src/commands/sensors/compatibility/materialize.js +126 -0
- package/dist/src/commands/sensors/compatibility/pack-source.js +78 -0
- package/dist/src/commands/sensors/compatibility/probe.js +47 -0
- package/dist/src/commands/sensors/compatibility/resolve.js +96 -0
- package/dist/src/commands/sensors/compatibility/types.js +2 -0
- package/dist/src/commands/sensors/coverage/contract.js +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 +150 -9
- package/dist/src/commands/sensors/index.js +18 -6
- package/dist/src/commands/sensors/init.js +92 -6
- package/dist/src/commands/sensors/run.js +71 -0
- package/dist/src/commands/sensors/status.js +48 -4
- package/dist/src/commands/sync.js +7 -0
- package/dist/src/core/bundles.js +38 -3
- package/dist/src/core/init/steps.js +7 -4
- package/dist/src/core/ledger/scan.js +228 -0
- package/dist/src/core/ledger/store.js +89 -25
- package/dist/src/core/ledger/types.js +54 -0
- package/dist/src/core/paths.js +10 -9
- package/dist/src/core/registries.js +134 -14
- package/dist/src/index.js +24 -1
- package/dist/tests/commands/ledger/index.test.js +11 -0
- package/dist/tests/commands/preflight/preflight.test.js +64 -64
- package/dist/tests/commands/registry/add.test.js +69 -2
- package/dist/tests/commands/registry/status.test.js +14 -0
- package/dist/tests/commands/sensors/compatibility/contract.test.js +98 -0
- package/dist/tests/commands/sensors/compatibility/discovery.test.js +119 -0
- package/dist/tests/commands/sensors/compatibility/manifest.test.js +68 -0
- package/dist/tests/commands/sensors/compatibility/materialize.test.js +53 -0
- package/dist/tests/commands/sensors/compatibility/pack-source.test.js +55 -0
- package/dist/tests/commands/sensors/compatibility/probe.test.js +27 -0
- package/dist/tests/commands/sensors/compatibility/resolve.test.js +75 -0
- package/dist/tests/commands/sensors/coverage/contract.test.js +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 +92 -2
- package/dist/tests/commands/sensors/exec.test.js +67 -5
- package/dist/tests/commands/sensors/index.test.js +8 -0
- package/dist/tests/commands/sensors/init-pack-unavailable.test.js +12 -12
- package/dist/tests/commands/sensors/init.test.js +132 -48
- package/dist/tests/commands/sensors/run-inconclusive.test.js +9 -2
- package/dist/tests/commands/sensors/run-is-read-only.test.js +0 -0
- package/dist/tests/commands/sensors/run.test.js +66 -3
- package/dist/tests/commands/sensors/status-windows.test.js +7 -7
- package/dist/tests/commands/sensors/status.test.js +74 -23
- package/dist/tests/commands/update.test.js +1 -1
- package/dist/tests/core/bundles.test.js +37 -0
- package/dist/tests/core/discovery.test.js +4 -0
- package/dist/tests/core/init/orchestrator.test.js +5 -1
- package/dist/tests/core/init/steps.test.js +20 -4
- package/dist/tests/core/ledger/scan.test.js +217 -0
- package/dist/tests/core/ledger/store.test.js +108 -0
- package/dist/tests/core/profile-pins.test.js +2 -1
- package/dist/tests/core/project-skill-links.test.js +22 -0
- package/dist/tests/core/registries-capability.test.js +10 -0
- package/dist/tests/core/registries-sync.test.js +16 -0
- package/dist/tests/core/registries.test.js +129 -1
- package/dist/tests/core/registry-manifest.test.js +7 -0
- package/dist/tests/core/same-existing-path.test.js +35 -0
- package/dist/tests/core/sync-gates.test.js +2 -1
- package/dist/tests/integration/copilot-init-isolated.test.js +18 -0
- package/dist/tests/integration/sensor-compatibility.e2e.test.js +141 -0
- package/dist/tests/integration/sensor-coverage.e2e.test.js +35 -6
- package/dist/tests/integration/sensor-structured-run.e2e.test.js +127 -0
- package/dist/tests/structural/active-documentation.test.js +128 -0
- package/dist/tests/structural/async-entrypoint.test.js +15 -0
- package/dist/tests/structural/jest-environment-is-isolated.test.js +44 -0
- package/dist/tests/structural/r3-cli-major-version.test.js +37 -0
- package/dist/tests/structural/sensor-documentation-contract.test.js +77 -0
- package/dist/tests/structural/support-matrix-is-current.test.js +69 -2
- package/package.json +8 -2
|
@@ -0,0 +1,103 @@
|
|
|
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.R3_PREPUBLICATION_FIXTURE_PURPOSE = exports.R3_PREPUBLICATION_FIXTURE_RELATIVE_PATH = exports.SENSOR_DOC_PATH = exports.SENSOR_PACKS = exports.SENSOR_END_MARKER = exports.SENSOR_BEGIN_MARKER = void 0;
|
|
7
|
+
exports.parseRegistrySensorPacks = parseRegistrySensorPacks;
|
|
8
|
+
exports.renderSensorSupportMatrix = renderSensorSupportMatrix;
|
|
9
|
+
exports.spliceSensorSupportMatrix = spliceSensorSupportMatrix;
|
|
10
|
+
exports.registryRootFromArgs = registryRootFromArgs;
|
|
11
|
+
/** Generates the sensor-pack evidence block in docs/support-matrix.md.
|
|
12
|
+
*
|
|
13
|
+
* The registry owns pack definitions. This renderer is intentionally a consumer of
|
|
14
|
+
* those manifests instead of a second hand-written list of tools or versions.
|
|
15
|
+
*/
|
|
16
|
+
const fs_1 = __importDefault(require("fs"));
|
|
17
|
+
const path_1 = __importDefault(require("path"));
|
|
18
|
+
const contract_1 = require("../src/commands/sensors/compatibility/contract");
|
|
19
|
+
exports.SENSOR_BEGIN_MARKER = '<!-- BEGIN GENERATED: sensor-pack-support -->';
|
|
20
|
+
exports.SENSOR_END_MARKER = '<!-- END GENERATED: sensor-pack-support -->';
|
|
21
|
+
exports.SENSOR_PACKS = ['generic', 'js-ts', 'python', 'shell'];
|
|
22
|
+
exports.SENSOR_DOC_PATH = path_1.default.resolve(__dirname, '..', '..', 'docs', 'support-matrix.md');
|
|
23
|
+
exports.R3_PREPUBLICATION_FIXTURE_RELATIVE_PATH = 'tests/fixtures/sensor-support-matrix/registry';
|
|
24
|
+
/**
|
|
25
|
+
* The CLI R3 contract is implemented before the baseline registry publishes its
|
|
26
|
+
* v2 manifests. The generated matrix intentionally renders this pinned fixture,
|
|
27
|
+
* never an invented description of an unpublished registry release.
|
|
28
|
+
*/
|
|
29
|
+
exports.R3_PREPUBLICATION_FIXTURE_PURPOSE = 'R3 pre-publication contract fixture';
|
|
30
|
+
function registryPath(value) {
|
|
31
|
+
if (typeof value !== 'string' || value.trim().length === 0 || value.includes('\0')) {
|
|
32
|
+
throw new Error('sensor support matrix requires a non-empty --registry-root');
|
|
33
|
+
}
|
|
34
|
+
return path_1.default.resolve(value);
|
|
35
|
+
}
|
|
36
|
+
function packPath(registryRoot, name) {
|
|
37
|
+
if (!exports.SENSOR_PACKS.includes(name))
|
|
38
|
+
throw new Error(`unsupported first-party pack: ${name}`);
|
|
39
|
+
return path_1.default.join(registryRoot, 'sensor-packs', name, 'pack.json');
|
|
40
|
+
}
|
|
41
|
+
function parseRegistrySensorPacks(registryRoot) {
|
|
42
|
+
const root = registryPath(registryRoot);
|
|
43
|
+
return exports.SENSOR_PACKS.map((name) => {
|
|
44
|
+
const file = packPath(root, name);
|
|
45
|
+
const stat = fs_1.default.lstatSync(file);
|
|
46
|
+
if (!stat.isFile() || stat.isSymbolicLink())
|
|
47
|
+
throw new Error(`sensor pack must be a regular file: ${file}`);
|
|
48
|
+
let source;
|
|
49
|
+
try {
|
|
50
|
+
source = JSON.parse(fs_1.default.readFileSync(file, 'utf8'));
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
throw new Error(`cannot parse sensor pack ${name}: ${error instanceof Error ? error.message : String(error)}`);
|
|
54
|
+
}
|
|
55
|
+
return { name, parsed: (0, contract_1.parseSensorPack)(source, file) };
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function variants(pack) {
|
|
59
|
+
return Object.entries(pack.sensors).flatMap(([sensor, definition]) => definition.variants.map((variant) => `\`${sensor}/${variant.id}\`: ${variant.requirements.tool} ${variant.requirements.toolRange}; ${variant.requirements.runtime} ${variant.requirements.runtimeRange}; certified ${variant.certifiedRange}`)).join('<br>');
|
|
60
|
+
}
|
|
61
|
+
/** Pure renderer used by freshness tests and the documentation command. */
|
|
62
|
+
function renderSensorSupportMatrix(registryRoot) {
|
|
63
|
+
const lines = [
|
|
64
|
+
`### Sensor-pack compatibility contract (${exports.R3_PREPUBLICATION_FIXTURE_PURPOSE})`,
|
|
65
|
+
'',
|
|
66
|
+
'| Pack | Contract | Version-aware variants and certified ranges | Evidence status |',
|
|
67
|
+
'|---|---|---|---|',
|
|
68
|
+
];
|
|
69
|
+
for (const { name, parsed } of parseRegistrySensorPacks(registryRoot)) {
|
|
70
|
+
if (parsed.kind === 'legacy') {
|
|
71
|
+
lines.push(`| \`${name}\` | legacy pack | No v2 variants declared | compatible-unverified — migrate the pack before claiming version certification |`);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
lines.push(`| \`${name}\` | pack schema v2 | ${variants(parsed.pack)} | Fixture-declared ranges only; real-tool and OS certification awaits published registry release evidence |`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
lines.push('');
|
|
78
|
+
lines.push('> Generated from the pinned R3 pre-publication contract fixture, not the published `awm-baseline-registry` manifests. **Do not edit by hand** — `npm run docs:matrix` regenerates this block. T13 verifies the actual registry tag and release evidence.');
|
|
79
|
+
return lines.join('\n');
|
|
80
|
+
}
|
|
81
|
+
function spliceSensorSupportMatrix(markdown, generated) {
|
|
82
|
+
const begin = markdown.indexOf(exports.SENSOR_BEGIN_MARKER);
|
|
83
|
+
const end = markdown.indexOf(exports.SENSOR_END_MARKER);
|
|
84
|
+
if (begin === -1 || end === -1 || end < begin)
|
|
85
|
+
throw new Error(`support-matrix.md lacks ${exports.SENSOR_BEGIN_MARKER} / ${exports.SENSOR_END_MARKER}`);
|
|
86
|
+
const eol = markdown.includes('\r\n') ? '\r\n' : '\n';
|
|
87
|
+
const block = generated.split('\n').join(eol);
|
|
88
|
+
return markdown.slice(0, begin + exports.SENSOR_BEGIN_MARKER.length) + eol + eol + block + eol + eol + markdown.slice(end);
|
|
89
|
+
}
|
|
90
|
+
function registryRootFromArgs(argv) {
|
|
91
|
+
const index = argv.indexOf('--registry-root');
|
|
92
|
+
if (index === -1 || argv[index + 1] === undefined || argv[index + 1].startsWith('--') || index !== argv.lastIndexOf('--registry-root')) {
|
|
93
|
+
throw new Error('sensor support matrix requires exactly one --registry-root <path>');
|
|
94
|
+
}
|
|
95
|
+
return registryPath(argv[index + 1]);
|
|
96
|
+
}
|
|
97
|
+
/* istanbul ignore next: command shell is covered through exported functions. */
|
|
98
|
+
if (require.main === module) {
|
|
99
|
+
const root = registryRootFromArgs(process.argv.slice(2));
|
|
100
|
+
const current = fs_1.default.readFileSync(exports.SENSOR_DOC_PATH, 'utf8');
|
|
101
|
+
fs_1.default.writeFileSync(exports.SENSOR_DOC_PATH, spliceSensorSupportMatrix(current, renderSensorSupportMatrix(root)), 'utf8');
|
|
102
|
+
process.stdout.write('support-matrix.md sensor-pack evidence regenerated from registry manifests\n');
|
|
103
|
+
}
|
|
@@ -47,6 +47,29 @@ function cell(value, absent, home) {
|
|
|
47
47
|
return absent;
|
|
48
48
|
return `\`${homeRelative(value, home)}\``;
|
|
49
49
|
}
|
|
50
|
+
/** The matrix documents portable defaults, not one operator's provider override.
|
|
51
|
+
* `providers()` deliberately honors settings such as CODEX_HOME at runtime; turning
|
|
52
|
+
* that runtime path into public documentation would make the checked-in matrix vary
|
|
53
|
+
* by the machine used to regenerate it. Rebase only the provider-owned prefix onto
|
|
54
|
+
* its declared default while preserving every suffix derived by the provider table. */
|
|
55
|
+
function defaultProviderPath(value, c, home) {
|
|
56
|
+
if (value === null)
|
|
57
|
+
return null;
|
|
58
|
+
if (c.configHome.envVar === null)
|
|
59
|
+
return value;
|
|
60
|
+
// `path.join` normalizes a provider override while configHome.resolved keeps
|
|
61
|
+
// the literal environment value. Compare their canonical filesystem forms:
|
|
62
|
+
// otherwise a trailing slash or `..` segment makes the prefix check fail and
|
|
63
|
+
// leaks the operator-specific path into this public, committed document.
|
|
64
|
+
const relative = path_1.default.relative(path_1.default.resolve(c.configHome.resolved), path_1.default.resolve(value));
|
|
65
|
+
if (relative === '' || (!relative.startsWith(`..${path_1.default.sep}`) && relative !== '..' && !path_1.default.isAbsolute(relative))) {
|
|
66
|
+
return path_1.default.join(home, c.configHome.dir, relative);
|
|
67
|
+
}
|
|
68
|
+
return value;
|
|
69
|
+
}
|
|
70
|
+
function providerCell(value, absent, c, home) {
|
|
71
|
+
return cell(defaultProviderPath(value, c, home), absent, home);
|
|
72
|
+
}
|
|
50
73
|
/** El tier declarado por la forma de la config — la misma derivacion que `providerTier`
|
|
51
74
|
* en `core/diagnostics/provider-checks.ts`, no una segunda opinion. */
|
|
52
75
|
function tier(c) {
|
|
@@ -61,14 +84,14 @@ function tier(c) {
|
|
|
61
84
|
function injectionCell(c, home) {
|
|
62
85
|
const inj = c.injection;
|
|
63
86
|
if (!inj)
|
|
64
|
-
return '— (
|
|
87
|
+
return '— (none)';
|
|
65
88
|
if (inj.type === 'cc-settings-merge')
|
|
66
89
|
return 'hook `SessionStart`';
|
|
67
90
|
if (inj.type === 'config-instructions')
|
|
68
|
-
return
|
|
91
|
+
return `${providerCell(inj.configPath, '**unsupported**', c, home)} → \`${inj.field}\` field`;
|
|
69
92
|
return inj.globalPath === null
|
|
70
|
-
?
|
|
71
|
-
: `\`${inj.localFile}\` +
|
|
93
|
+
? `project \`${inj.localFile}\` (no global equivalent)`
|
|
94
|
+
: `\`${inj.localFile}\` + ${providerCell(inj.globalPath, '**unsupported**', c, home)}`;
|
|
72
95
|
}
|
|
73
96
|
function renderProviderTables() {
|
|
74
97
|
// `homeDir()`, la MISMA funcion de la que `providers()` deriva sus rutas. Tomarlo
|
|
@@ -79,33 +102,33 @@ function renderProviderTables() {
|
|
|
79
102
|
const p = (0, providers_1.providers)();
|
|
80
103
|
const row = (a) => p[a];
|
|
81
104
|
const lines = [];
|
|
82
|
-
lines.push('###
|
|
105
|
+
lines.push('### Where each artifact is installed');
|
|
83
106
|
lines.push('');
|
|
84
|
-
lines.push('|
|
|
107
|
+
lines.push('| Agent | Tier | Skills (global) | Skills (project) | Renderer |');
|
|
85
108
|
lines.push('|---|---|---|---|---|');
|
|
86
109
|
for (const a of providers_1.AGENT_TARGETS) {
|
|
87
110
|
const c = row(a);
|
|
88
|
-
lines.push(`| \`${a}\` | ${tier(c)} | ${
|
|
111
|
+
lines.push(`| \`${a}\` | ${tier(c)} | ${providerCell(c.skill.global, '**unsupported**', c, home)} | \`${c.skill.local}\` | \`${c.skill.renderer}\` |`);
|
|
89
112
|
}
|
|
90
113
|
lines.push('');
|
|
91
|
-
lines.push('###
|
|
114
|
+
lines.push('### Agent profiles, workflows, hooks, and context');
|
|
92
115
|
lines.push('');
|
|
93
|
-
lines.push('|
|
|
116
|
+
lines.push('| Agent | Agent profiles | Workflows | Hooks | Context delivery | Minimum version |');
|
|
94
117
|
lines.push('|---|---|---|---|---|---|');
|
|
95
118
|
for (const a of providers_1.AGENT_TARGETS) {
|
|
96
119
|
const c = row(a);
|
|
97
120
|
const agentCell = c.agent === null
|
|
98
|
-
? '— (
|
|
99
|
-
: `${
|
|
121
|
+
? '— (not applicable)'
|
|
122
|
+
: `${providerCell(c.agent.global, '**unsupported**', c, home)} · \`${c.agent.renderer}\``;
|
|
100
123
|
const wfCell = c.workflow === null
|
|
101
|
-
? '— (
|
|
102
|
-
:
|
|
103
|
-
lines.push(`| \`${a}\` | ${agentCell} | ${wfCell} | ${c.hooks ? `\`${c.hooks.type}\`` : '— (
|
|
104
|
-
`| ${injectionCell(c, home)} | ${c.minimumVersion ?? '— (
|
|
124
|
+
? '— (not applicable)'
|
|
125
|
+
: providerCell(c.workflow.global, '**unsupported**', c, home);
|
|
126
|
+
lines.push(`| \`${a}\` | ${agentCell} | ${wfCell} | ${c.hooks ? `\`${c.hooks.type}\`` : '— (none)'} ` +
|
|
127
|
+
`| ${injectionCell(c, home)} | ${c.minimumVersion ?? '— (no gate)'} |`);
|
|
105
128
|
}
|
|
106
129
|
lines.push('');
|
|
107
|
-
lines.push('>
|
|
108
|
-
lines.push('> `tests/structural/support-matrix-is-current.test.ts`
|
|
130
|
+
lines.push('> Generated from `cli/src/providers/index.ts`. **Do not edit by hand** — `npm run docs:matrix` regenerates it and');
|
|
131
|
+
lines.push('> `tests/structural/support-matrix-is-current.test.ts` fails when the document and code diverge.');
|
|
109
132
|
return lines.join('\n');
|
|
110
133
|
}
|
|
111
134
|
/** Reemplaza el bloque entre marcadores. Falla ruidosamente si faltan: un doc sin
|
|
@@ -40,8 +40,6 @@ exports.renderInitOutcome = renderInitOutcome;
|
|
|
40
40
|
exports.runInit = runInit;
|
|
41
41
|
exports.makeConfirmExtensions = makeConfirmExtensions;
|
|
42
42
|
exports.registerInitCommand = registerInitCommand;
|
|
43
|
-
// src/commands/init.ts
|
|
44
|
-
const fs_1 = __importDefault(require("fs"));
|
|
45
43
|
const picocolors_1 = __importDefault(require("picocolors"));
|
|
46
44
|
const doctor_1 = require("./doctor");
|
|
47
45
|
const context_1 = require("../core/diagnostics/context");
|
|
@@ -202,7 +200,7 @@ async function runInit(opts = {}) {
|
|
|
202
200
|
try {
|
|
203
201
|
(0, config_1.savePreferences)(nextPreferences);
|
|
204
202
|
(0, registries_1.seedBaselineRegistry)();
|
|
205
|
-
if ((0, registries_1.
|
|
203
|
+
if ((0, registries_1.registriesNeedSync)()) {
|
|
206
204
|
// `syncRegistries()` reports per-registry failures as RESULTS,
|
|
207
205
|
// never as throws (core/registries.ts) — swallowing them here
|
|
208
206
|
// let an unavailable registry degrade silently into some later
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.registerLedgerCommand = registerLedgerCommand;
|
|
4
4
|
const store_1 = require("../../core/ledger/store");
|
|
5
|
+
const types_1 = require("../../core/ledger/types");
|
|
5
6
|
function archiveLabel() {
|
|
6
7
|
return new Date().toISOString().replace(/[-:]/g, '').replace(/\.\d+Z$/, '');
|
|
7
8
|
}
|
|
@@ -18,8 +19,12 @@ function registerLedgerCommand(program) {
|
|
|
18
19
|
.option('--ref <ref>', 'file:line or PR/commit reference')
|
|
19
20
|
.option('--phase <phase>', 'lifecycle phase', 'unknown')
|
|
20
21
|
.option('--source-skill <skill>', 'emitting skill', 'unknown')
|
|
22
|
+
.option('--defect-class <id>', 'reusable lowercase kebab-case defect class')
|
|
21
23
|
.option('--branch <branch>', 'override branch (default: git current branch)')
|
|
22
24
|
.action((opts) => {
|
|
25
|
+
if (opts.defectClass !== undefined && !types_1.DEFECT_CLASS.test(opts.defectClass)) {
|
|
26
|
+
throw new Error('--defect-class requires a lowercase kebab-case value');
|
|
27
|
+
}
|
|
23
28
|
const cwd = process.cwd();
|
|
24
29
|
const branch = opts.branch ?? (0, store_1.detectBranch)(cwd);
|
|
25
30
|
const entry = {
|
|
@@ -33,6 +38,7 @@ function registerLedgerCommand(program) {
|
|
|
33
38
|
severity: opts.severity,
|
|
34
39
|
desc: opts.desc,
|
|
35
40
|
ref: opts.ref,
|
|
41
|
+
defectClass: opts.defectClass,
|
|
36
42
|
};
|
|
37
43
|
(0, store_1.addEntry)(cwd, entry);
|
|
38
44
|
});
|
|
@@ -97,8 +97,8 @@ function checkManifest(cwd, manifest) {
|
|
|
97
97
|
};
|
|
98
98
|
}
|
|
99
99
|
/** Every enabled sensor's command must resolve. This is the check nothing was calling. */
|
|
100
|
-
function checkTools(cwd) {
|
|
101
|
-
const status = (0, status_1.computeSensorStatus)(cwd);
|
|
100
|
+
async function checkTools(cwd) {
|
|
101
|
+
const status = await (0, status_1.computeSensorStatus)(cwd);
|
|
102
102
|
if (status.overall === 'NOT_CONFIGURED') {
|
|
103
103
|
return { id: 'tools', ok: false, detail: 'no manifest to check', remedy: 'run `awm sensors init`' };
|
|
104
104
|
}
|
|
@@ -276,7 +276,7 @@ function checkHost(cwd) {
|
|
|
276
276
|
// Bitbucket, Azure DevOps, an internal git server, etc. — don't overclaim support.
|
|
277
277
|
return { id: 'host', ok: true, detail: 'git host not recognized (github/gitlab) — PR/MR automation not applicable' };
|
|
278
278
|
}
|
|
279
|
-
function preflight(cwd = process.cwd()) {
|
|
279
|
+
async function preflight(cwd = process.cwd()) {
|
|
280
280
|
const manifest = readManifest(cwd);
|
|
281
281
|
const manifestExists = fs_1.default.existsSync(path_1.default.join(cwd, MANIFEST));
|
|
282
282
|
const checks = [
|
|
@@ -285,7 +285,7 @@ function preflight(cwd = process.cwd()) {
|
|
|
285
285
|
// Skipped when there is no manifest: reporting "tools broken" (or nudging toward
|
|
286
286
|
// a baseline that has nothing to snapshot) on a repo that was never set up
|
|
287
287
|
// buries the one thing the operator needs to read.
|
|
288
|
-
...(manifestExists ? [checkTools(cwd), checkPack(cwd, manifest), checkSensorsBaseline(cwd, manifest)] : []),
|
|
288
|
+
...(manifestExists ? [await checkTools(cwd), checkPack(cwd, manifest), checkSensorsBaseline(cwd, manifest)] : []),
|
|
289
289
|
// Runs unconditionally — orthogonal to sensor configuration entirely, this is
|
|
290
290
|
// about PR/MR tooling, not sensors.
|
|
291
291
|
checkHost(cwd),
|
|
@@ -45,8 +45,8 @@ function registerPreflightCommand(program) {
|
|
|
45
45
|
.description('verify the project harness can actually gate before development starts')
|
|
46
46
|
.option('--json', 'emit the report as JSON')
|
|
47
47
|
.option('--cwd <path>', 'project directory to check (default: current)')
|
|
48
|
-
.action((opts) => {
|
|
49
|
-
const report = (0, checks_1.preflight)(opts.cwd ?? process.cwd());
|
|
48
|
+
.action(async (opts) => {
|
|
49
|
+
const report = await (0, checks_1.preflight)(opts.cwd ?? process.cwd());
|
|
50
50
|
process.stdout.write(opts.json ? JSON.stringify(report, null, 2) + '\n' : formatReport(report));
|
|
51
51
|
const code = exitCodeFor(report);
|
|
52
52
|
if (code !== 0)
|
|
@@ -27,7 +27,7 @@ function deriveRegistryName(remote) {
|
|
|
27
27
|
}
|
|
28
28
|
async function addRegistry(remote, nameOverride) {
|
|
29
29
|
const name = nameOverride ?? deriveRegistryName(remote);
|
|
30
|
-
if (!name || name === '.' || /[/\\]/.test(name)) {
|
|
30
|
+
if (!name || name === '.' || name.includes('..') || /[/\\]/.test(name)) {
|
|
31
31
|
return { ok: false, error: `Invalid registry name "${name}" — use --name <simple-dir-name>` };
|
|
32
32
|
}
|
|
33
33
|
const existing = (0, registries_1.readRegistriesConfig)();
|
|
@@ -51,7 +51,7 @@ async function addRegistry(remote, nameOverride) {
|
|
|
51
51
|
return {
|
|
52
52
|
ok: false,
|
|
53
53
|
name,
|
|
54
|
-
error: `Invalid registry layout: expected at least one of ${registries_1.
|
|
54
|
+
error: `Invalid registry layout: expected at least one of ${registries_1.REGISTRY_DIR_NAMES.map((d) => `${d}/`).join(', ')} at the repo root of ${remote}`,
|
|
55
55
|
};
|
|
56
56
|
}
|
|
57
57
|
// Collision check against already-known content — BEFORE writing config.
|
|
@@ -105,6 +105,7 @@ function registerRegistryCommand(program) {
|
|
|
105
105
|
console.log(picocolors_1.default.dim('No additional registries. Add one with `awm registry add <git-url>`.'));
|
|
106
106
|
return;
|
|
107
107
|
}
|
|
108
|
+
const safeRoots = new Set((0, registries_1.contentRoots)());
|
|
108
109
|
const earlier = [];
|
|
109
110
|
for (const r of regs) {
|
|
110
111
|
if (!fs_1.default.existsSync(r.contentRoot)) {
|
|
@@ -114,6 +115,10 @@ function registerRegistryCommand(program) {
|
|
|
114
115
|
earlier.push(r.contentRoot);
|
|
115
116
|
continue;
|
|
116
117
|
}
|
|
118
|
+
if (!(0, status_1.canDiscoverRegistryContent)(r.contentRoot, safeRoots)) {
|
|
119
|
+
console.log(`${picocolors_1.default.cyan(r.name)} ${r.remote} ${picocolors_1.default.yellow("unsafe layout — remove the registry and add it again")}`);
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
117
122
|
try {
|
|
118
123
|
const counts = [
|
|
119
124
|
`${(0, discovery_1.discoverSkills)([r.contentRoot]).length} skills`,
|
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.canDiscoverRegistryContent = canDiscoverRegistryContent;
|
|
3
4
|
exports.overrideStatus = overrideStatus;
|
|
4
5
|
// Estado de los overrides declarados por un registry: activo (tapa un artifact
|
|
5
6
|
// de un root anterior) o sin efecto (huérfano — el nombre ya no existe upstream).
|
|
6
7
|
const registries_1 = require("../../core/registries");
|
|
7
8
|
const discovery_1 = require("../../core/discovery");
|
|
8
9
|
const bundles_1 = require("../../core/bundles");
|
|
10
|
+
/** Registry discovery is permitted only for a root that survived the core
|
|
11
|
+
* safety scan. Kept as a small pure guard so the command wiring has a
|
|
12
|
+
* behavioral regression test instead of relying on source-text fragments. */
|
|
13
|
+
function canDiscoverRegistryContent(contentRoot, safeRoots) {
|
|
14
|
+
return safeRoots.has(contentRoot);
|
|
15
|
+
}
|
|
9
16
|
function artifactNamesInRoot(root) {
|
|
10
17
|
const names = new Set();
|
|
11
18
|
for (const s of (0, discovery_1.discoverSkills)([root]))
|
|
@@ -0,0 +1,261 @@
|
|
|
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.parseStructuredCommand = parseStructuredCommand;
|
|
7
|
+
exports.assertNoEqualPriorityOverlap = assertNoEqualPriorityOverlap;
|
|
8
|
+
exports.parseSensorPack = parseSensorPack;
|
|
9
|
+
const semver_1 = __importDefault(require("semver"));
|
|
10
|
+
const contract_1 = require("../coverage/contract");
|
|
11
|
+
const PACK_SCHEMA_VERSION = 2;
|
|
12
|
+
const SHELL_EXECUTABLES = new Set(['sh', 'bash', 'cmd', 'powershell']);
|
|
13
|
+
const ALLOWED_PROBES = new Set([
|
|
14
|
+
'version',
|
|
15
|
+
'eslint-print-config',
|
|
16
|
+
'typescript-show-config',
|
|
17
|
+
'semgrep-validate',
|
|
18
|
+
'package-script-present',
|
|
19
|
+
'config-present',
|
|
20
|
+
]);
|
|
21
|
+
function isRecord(value) {
|
|
22
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
23
|
+
}
|
|
24
|
+
function sourceSuffix(source) {
|
|
25
|
+
return typeof source === 'string' && source.length > 0 && !/[\0\r\n]/.test(source) ? ` in ${source}` : ' in <unknown source>';
|
|
26
|
+
}
|
|
27
|
+
function invalid(source, message) {
|
|
28
|
+
throw new Error(`Invalid sensor pack${sourceSuffix(source)}: ${message}`);
|
|
29
|
+
}
|
|
30
|
+
function record(value, source, location) {
|
|
31
|
+
if (!isRecord(value))
|
|
32
|
+
invalid(source, `${location} must be an object`);
|
|
33
|
+
return value;
|
|
34
|
+
}
|
|
35
|
+
function fields(value, allowed, source, location) {
|
|
36
|
+
for (const key of Object.keys(value)) {
|
|
37
|
+
if (!allowed.includes(key))
|
|
38
|
+
invalid(source, `${location} has unknown field "${key}"`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function text(value, source, location) {
|
|
42
|
+
if (typeof value !== 'string' || value.trim().length === 0 || /[\0\r\n]/.test(value)) {
|
|
43
|
+
invalid(source, `${location} must be a nonempty single-line string without NUL`);
|
|
44
|
+
}
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
function id(value, source, location) {
|
|
48
|
+
const parsed = text(value, source, location);
|
|
49
|
+
if (!/^[a-z][a-z0-9-]*$/.test(parsed))
|
|
50
|
+
invalid(source, `${location} must be a stable lowercase id`);
|
|
51
|
+
return parsed;
|
|
52
|
+
}
|
|
53
|
+
function asset(value, source, location) {
|
|
54
|
+
const parsed = text(value, source, location);
|
|
55
|
+
if (parsed.startsWith('/') || /^[A-Za-z]:[\\/]/.test(parsed) || parsed.startsWith('\\\\') || parsed.includes('\\') || parsed.split('/').some(part => part === '' || part === '.' || part === '..')) {
|
|
56
|
+
invalid(source, `${location} must be a contained relative asset path`);
|
|
57
|
+
}
|
|
58
|
+
return parsed;
|
|
59
|
+
}
|
|
60
|
+
function stringArray(value, source, location) {
|
|
61
|
+
if (!Array.isArray(value) || value.length === 0)
|
|
62
|
+
invalid(source, `${location} must be a nonempty array`);
|
|
63
|
+
return value.map((item, index) => text(item, source, `${location}[${index}]`));
|
|
64
|
+
}
|
|
65
|
+
function parseStructuredCommand(input, source) {
|
|
66
|
+
const value = record(input, source, 'command');
|
|
67
|
+
fields(value, ['executable', 'resolution', 'args', 'fileInput'], source, 'command');
|
|
68
|
+
const executable = text(value.executable, source, 'command.executable');
|
|
69
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(executable) || SHELL_EXECUTABLES.has(executable.toLowerCase().replace(/\.exe$/, ''))) {
|
|
70
|
+
invalid(source, 'command.executable must not be a shell or path');
|
|
71
|
+
}
|
|
72
|
+
if (value.resolution !== 'node-modules-bin' && value.resolution !== 'python-environment' && value.resolution !== 'path') {
|
|
73
|
+
invalid(source, 'command.resolution must be a supported resolution');
|
|
74
|
+
}
|
|
75
|
+
const args = stringArray(value.args, source, 'command.args');
|
|
76
|
+
for (const [index, arg] of args.entries()) {
|
|
77
|
+
if (arg.includes('{files}') && arg !== '{files}')
|
|
78
|
+
invalid(source, `command.args[${index}] must not embed {files}`);
|
|
79
|
+
}
|
|
80
|
+
const command = { executable, resolution: value.resolution, args };
|
|
81
|
+
if ('fileInput' in value) {
|
|
82
|
+
const fileInput = record(value.fileInput, source, 'command.fileInput');
|
|
83
|
+
fields(fileInput, ['placeholder', 'extensions'], source, 'command.fileInput');
|
|
84
|
+
if (fileInput.placeholder !== '{files}')
|
|
85
|
+
invalid(source, 'command.fileInput.placeholder must be {files}');
|
|
86
|
+
const extensions = stringArray(fileInput.extensions, source, 'command.fileInput.extensions');
|
|
87
|
+
for (const [index, extension] of extensions.entries()) {
|
|
88
|
+
if (!/^\.[A-Za-z0-9]+$/.test(extension))
|
|
89
|
+
invalid(source, `command.fileInput.extensions[${index}] must be an extension`);
|
|
90
|
+
}
|
|
91
|
+
if (args.filter(arg => arg === '{files}').length !== 1)
|
|
92
|
+
invalid(source, 'command.fileInput requires exactly one {files} argument');
|
|
93
|
+
command.fileInput = { placeholder: '{files}', extensions };
|
|
94
|
+
}
|
|
95
|
+
else if (args.includes('{files}')) {
|
|
96
|
+
invalid(source, 'command {files} argument requires fileInput');
|
|
97
|
+
}
|
|
98
|
+
return command;
|
|
99
|
+
}
|
|
100
|
+
function parseVariant(input, source, location) {
|
|
101
|
+
const value = record(input, source, location);
|
|
102
|
+
fields(value, ['id', 'priority', 'requirements', 'certifiedRange', 'command', 'assets', 'formatter', 'probe'], source, location);
|
|
103
|
+
const certifiedRange = text(value.certifiedRange, source, `${location}.certifiedRange`);
|
|
104
|
+
if (semver_1.default.validRange(certifiedRange) === null)
|
|
105
|
+
invalid(source, `${location}.certifiedRange must be a valid semver range`);
|
|
106
|
+
if (typeof value.priority !== 'number' || !Number.isSafeInteger(value.priority))
|
|
107
|
+
invalid(source, `${location}.priority must be a safe integer`);
|
|
108
|
+
const requirements = record(value.requirements, source, `${location}.requirements`);
|
|
109
|
+
fields(requirements, ['tool', 'toolRange', 'runtime', 'runtimeRange', 'configFiles'], source, `${location}.requirements`);
|
|
110
|
+
const toolRange = text(requirements.toolRange, source, `${location}.requirements.toolRange`);
|
|
111
|
+
const runtimeRange = text(requirements.runtimeRange, source, `${location}.requirements.runtimeRange`);
|
|
112
|
+
if (semver_1.default.validRange(toolRange) === null || semver_1.default.validRange(runtimeRange) === null)
|
|
113
|
+
invalid(source, `${location}.requirements ranges must be valid semver ranges`);
|
|
114
|
+
const probe = record(value.probe, source, `${location}.probe`);
|
|
115
|
+
fields(probe, ['kind'], source, `${location}.probe`);
|
|
116
|
+
if (typeof probe.kind !== 'string' || !ALLOWED_PROBES.has(probe.kind))
|
|
117
|
+
invalid(source, `${location}.probe.kind must be an allowed probe`);
|
|
118
|
+
return {
|
|
119
|
+
id: id(value.id, source, `${location}.id`),
|
|
120
|
+
priority: value.priority,
|
|
121
|
+
certifiedRange,
|
|
122
|
+
requirements: { tool: text(requirements.tool, source, `${location}.requirements.tool`), toolRange, runtime: text(requirements.runtime, source, `${location}.requirements.runtime`), runtimeRange, ...('configFiles' in requirements ? { configFiles: stringArray(requirements.configFiles, source, `${location}.requirements.configFiles`).map((file, index) => asset(file, source, `${location}.requirements.configFiles[${index}]`)) } : {}) },
|
|
123
|
+
assets: stringArray(value.assets, source, `${location}.assets`).map((entry, index) => asset(entry, source, `${location}.assets[${index}]`)),
|
|
124
|
+
formatter: text(value.formatter, source, `${location}.formatter`),
|
|
125
|
+
probe: { kind: probe.kind },
|
|
126
|
+
command: parseStructuredCommand(value.command, source),
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
function assertNoEqualPriorityOverlap(variants) {
|
|
130
|
+
if (!Array.isArray(variants) || variants.length === 0)
|
|
131
|
+
throw new Error('variants must be a nonempty array');
|
|
132
|
+
const parsed = variants.map((variant, index) => parseVariant(variant, 'public overlap validator', `variants[${index}]`));
|
|
133
|
+
for (let left = 0; left < parsed.length; left++) {
|
|
134
|
+
const first = parsed[left];
|
|
135
|
+
for (let right = left + 1; right < variants.length; right++) {
|
|
136
|
+
const second = parsed[right];
|
|
137
|
+
const toolRangesIntersect = semver_1.default.intersects(first.requirements.toolRange, second.requirements.toolRange);
|
|
138
|
+
const runtimeRangesIntersect = semver_1.default.intersects(first.requirements.runtimeRange, second.requirements.runtimeRange);
|
|
139
|
+
if (first.priority === second.priority && toolRangesIntersect && runtimeRangesIntersect) {
|
|
140
|
+
throw new Error(`variants "${first.id}" and "${second.id}" overlap at priority ${first.priority}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
function parseSensor(input, source, location, variantIds) {
|
|
146
|
+
const value = record(input, source, location);
|
|
147
|
+
fields(value, ['applicability', 'variants', 'fast'], source, location);
|
|
148
|
+
if (!Array.isArray(value.variants) || value.variants.length === 0)
|
|
149
|
+
invalid(source, `${location}.variants must be a nonempty array`);
|
|
150
|
+
const variants = value.variants.map((variant, index) => parseVariant(variant, source, `${location}.variants[${index}]`));
|
|
151
|
+
for (const variant of variants) {
|
|
152
|
+
if (variantIds.has(variant.id))
|
|
153
|
+
invalid(source, `variant id "${variant.id}" must be unique`);
|
|
154
|
+
variantIds.add(variant.id);
|
|
155
|
+
}
|
|
156
|
+
try {
|
|
157
|
+
assertNoEqualPriorityOverlap(variants);
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
invalid(source, `${location}.variants ${error instanceof Error ? error.message : 'overlap validation failed'}`);
|
|
161
|
+
}
|
|
162
|
+
const applicabilityInput = record(value.applicability, source, `${location}.applicability`);
|
|
163
|
+
fields(applicabilityInput, ['allFiles', 'anyFiles', 'kind'], source, `${location}.applicability`);
|
|
164
|
+
const applicability = {};
|
|
165
|
+
if ('allFiles' in applicabilityInput)
|
|
166
|
+
applicability.allFiles = stringArray(applicabilityInput.allFiles, source, `${location}.applicability.allFiles`).map((file, index) => asset(file, source, `${location}.applicability.allFiles[${index}]`));
|
|
167
|
+
if ('anyFiles' in applicabilityInput)
|
|
168
|
+
applicability.anyFiles = stringArray(applicabilityInput.anyFiles, source, `${location}.applicability.anyFiles`).map((file, index) => asset(file, source, `${location}.applicability.anyFiles[${index}]`));
|
|
169
|
+
if ('kind' in applicabilityInput)
|
|
170
|
+
applicability.kind = text(applicabilityInput.kind, source, `${location}.applicability.kind`);
|
|
171
|
+
if (Object.keys(applicability).length === 0)
|
|
172
|
+
invalid(source, `${location}.applicability must declare a condition`);
|
|
173
|
+
if ('fast' in value && typeof value.fast !== 'boolean')
|
|
174
|
+
invalid(source, `${location}.fast must be a boolean`);
|
|
175
|
+
return { applicability, variants, ...('fast' in value ? { fast: value.fast } : {}) };
|
|
176
|
+
}
|
|
177
|
+
function legacyCompatibility() {
|
|
178
|
+
return {
|
|
179
|
+
state: 'compatible-unverified',
|
|
180
|
+
reason: 'legacy pack without schemaVersion',
|
|
181
|
+
variantId: null,
|
|
182
|
+
toolVersion: null,
|
|
183
|
+
runtimeVersion: null,
|
|
184
|
+
certifiedRange: null,
|
|
185
|
+
evidence: [],
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function parseLegacyPack(value, source) {
|
|
189
|
+
fields(value, ['name', 'description', 'detects', 'sensors', 'coverage'], source, 'root');
|
|
190
|
+
const name = id(value.name, source, 'name');
|
|
191
|
+
const sensorsInput = record(value.sensors, source, 'sensors');
|
|
192
|
+
const sensors = {};
|
|
193
|
+
for (const sensorName of Object.keys(sensorsInput)) {
|
|
194
|
+
const sensor = record(sensorsInput[sensorName], source, `sensors.${sensorName}`);
|
|
195
|
+
fields(sensor, ['defaultCmd', 'fast', 'enabled', 'changedCmd', 'changedExtensions', 'formatter', 'configFile', 'configFileFallback'], source, `sensors.${sensorName}`);
|
|
196
|
+
const parsed = {};
|
|
197
|
+
if ('defaultCmd' in sensor)
|
|
198
|
+
parsed.defaultCmd = text(sensor.defaultCmd, source, `sensors.${sensorName}.defaultCmd`);
|
|
199
|
+
if ('fast' in sensor) {
|
|
200
|
+
if (typeof sensor.fast !== 'boolean')
|
|
201
|
+
invalid(source, `sensors.${sensorName}.fast must be a boolean`);
|
|
202
|
+
parsed.fast = sensor.fast;
|
|
203
|
+
}
|
|
204
|
+
if ('enabled' in sensor) {
|
|
205
|
+
if (typeof sensor.enabled !== 'boolean')
|
|
206
|
+
invalid(source, `sensors.${sensorName}.enabled must be a boolean`);
|
|
207
|
+
parsed.enabled = sensor.enabled;
|
|
208
|
+
}
|
|
209
|
+
if ('changedCmd' in sensor)
|
|
210
|
+
parsed.changedCmd = text(sensor.changedCmd, source, `sensors.${sensorName}.changedCmd`);
|
|
211
|
+
if ('changedExtensions' in sensor)
|
|
212
|
+
parsed.changedExtensions = stringArray(sensor.changedExtensions, source, `sensors.${sensorName}.changedExtensions`);
|
|
213
|
+
if ('formatter' in sensor)
|
|
214
|
+
parsed.formatter = text(sensor.formatter, source, `sensors.${sensorName}.formatter`);
|
|
215
|
+
if ('configFile' in sensor)
|
|
216
|
+
parsed.configFile = asset(sensor.configFile, source, `sensors.${sensorName}.configFile`);
|
|
217
|
+
if ('configFileFallback' in sensor)
|
|
218
|
+
parsed.configFileFallback = asset(sensor.configFileFallback, source, `sensors.${sensorName}.configFileFallback`);
|
|
219
|
+
sensors[id(sensorName, source, 'sensor id')] = parsed;
|
|
220
|
+
}
|
|
221
|
+
const legacy = { name, sensors, compatibility: legacyCompatibility() };
|
|
222
|
+
if ('description' in value)
|
|
223
|
+
legacy.description = text(value.description, source, 'description');
|
|
224
|
+
if ('detects' in value) {
|
|
225
|
+
if (!Array.isArray(value.detects))
|
|
226
|
+
invalid(source, 'detects must be an array');
|
|
227
|
+
legacy.detects = value.detects.map((detect, index) => text(detect, source, `detects[${index}]`));
|
|
228
|
+
}
|
|
229
|
+
if ('coverage' in value)
|
|
230
|
+
legacy.coverage = (0, contract_1.parseCoverageContract)(value.coverage, source);
|
|
231
|
+
return legacy;
|
|
232
|
+
}
|
|
233
|
+
function parseSensorPack(input, source) {
|
|
234
|
+
const value = record(input, source, 'root');
|
|
235
|
+
if (!('schemaVersion' in value))
|
|
236
|
+
return { kind: 'legacy', pack: parseLegacyPack(value, source) };
|
|
237
|
+
fields(value, ['schemaVersion', 'name', 'description', 'detects', 'sensors', 'coverage'], source, 'root');
|
|
238
|
+
if (value.schemaVersion !== PACK_SCHEMA_VERSION)
|
|
239
|
+
invalid(source, `unsupported pack schemaVersion ${String(value.schemaVersion)}; supported: legacy, 2; upgrade or migrate the pack`);
|
|
240
|
+
const sensorsInput = record(value.sensors, source, 'sensors');
|
|
241
|
+
const sensorNames = Object.keys(sensorsInput);
|
|
242
|
+
if (sensorNames.length === 0)
|
|
243
|
+
invalid(source, 'sensors must be nonempty');
|
|
244
|
+
const sensors = {};
|
|
245
|
+
const variantIds = new Set();
|
|
246
|
+
for (const name of sensorNames)
|
|
247
|
+
sensors[id(name, source, 'sensor id')] = parseSensor(sensorsInput[name], source, `sensors.${name}`, variantIds);
|
|
248
|
+
let coverage;
|
|
249
|
+
try {
|
|
250
|
+
coverage = (0, contract_1.parseCoverageContract)(value.coverage, source);
|
|
251
|
+
}
|
|
252
|
+
catch (error) {
|
|
253
|
+
invalid(source, `coverage.${error instanceof Error ? error.message.replace(/^.*?: /, '') : 'is invalid'}`);
|
|
254
|
+
}
|
|
255
|
+
return { kind: 'v2', pack: {
|
|
256
|
+
schemaVersion: PACK_SCHEMA_VERSION,
|
|
257
|
+
name: id(value.name, source, 'name'), description: text(value.description, source, 'description'), detects: stringArray(value.detects, source, 'detects'),
|
|
258
|
+
sensors,
|
|
259
|
+
coverage,
|
|
260
|
+
} };
|
|
261
|
+
}
|