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,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 '— (ninguna)';
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 `\`${homeRelative(inj.configPath, home)}\`campo \`${inj.field}\``;
91
+ return `${providerCell(inj.configPath, '**unsupported**', c, home)} → \`${inj.field}\` field`;
69
92
  return inj.globalPath === null
70
- ? `\`${inj.localFile}\` del proyecto (sin equivalente global)`
71
- : `\`${inj.localFile}\` + \`${homeRelative(inj.globalPath, home)}\``;
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('### Dónde aterriza cada artefacto');
105
+ lines.push('### Where each artifact is installed');
83
106
  lines.push('');
84
- lines.push('| Agente | Tier | Skills (global) | Skills (proyecto) | Formato |');
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)} | ${cell(c.skill.global, '**no soportado**', home)} | \`${c.skill.local}\` | \`${c.skill.renderer}\` |`);
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('### Perfiles de agente, workflows, hooks y contexto');
114
+ lines.push('### Agent profiles, workflows, hooks, and context');
92
115
  lines.push('');
93
- lines.push('| Agente | Perfiles de agente | Workflows | Hooks | Entrega de contexto | Versión mínima |');
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
- ? '— (no aplica)'
99
- : `${cell(c.agent.global, '**no soportado**', home)} · \`${c.agent.renderer}\``;
121
+ ? '— (not applicable)'
122
+ : `${providerCell(c.agent.global, '**unsupported**', c, home)} · \`${c.agent.renderer}\``;
100
123
  const wfCell = c.workflow === null
101
- ? '— (no aplica)'
102
- : cell(c.workflow.global, '**no soportado**', home);
103
- lines.push(`| \`${a}\` | ${agentCell} | ${wfCell} | ${c.hooks ? `\`${c.hooks.type}\`` : '— (no tiene)'} ` +
104
- `| ${injectionCell(c, home)} | ${c.minimumVersion ?? '— (sin gate)'} |`);
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('> Generado desde `cli/src/providers/index.ts`. **No editar a mano** — `npm run docs:matrix` lo regenera y');
108
- lines.push('> `tests/structural/support-matrix-is-current.test.ts` falla si el documento y el código se separan.');
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.listRegistries)().some((r) => !fs_1.default.existsSync(r.contentRoot))) {
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.CONTENT_DIR_NAMES.map((d) => `${d}/`).join(', ')} at the repo root of ${remote}`,
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
+ }