agentic-workflow-manager 9.16.1 → 9.18.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 (54) hide show
  1. package/dist/src/commands/env/index.js +192 -0
  2. package/dist/src/commands/preflight/checks.js +63 -13
  3. package/dist/src/commands/registry/doctor.js +428 -0
  4. package/dist/src/commands/registry/index.js +52 -0
  5. package/dist/src/commands/registry/scaffold.js +140 -0
  6. package/dist/src/commands/sensors/bootstrap.js +2 -1
  7. package/dist/src/commands/sensors/compatibility/contract.js +30 -26
  8. package/dist/src/commands/sensors/compatibility/discovery.js +21 -0
  9. package/dist/src/commands/sensors/compatibility/manifest.js +169 -17
  10. package/dist/src/commands/sensors/compatibility/materialize.js +30 -5
  11. package/dist/src/commands/sensors/compatibility/source.js +3 -2
  12. package/dist/src/commands/sensors/coverage/render.js +8 -2
  13. package/dist/src/commands/sensors/coverage/resolve.js +7 -0
  14. package/dist/src/commands/sensors/init.js +15 -2
  15. package/dist/src/commands/sensors/migrate.js +13 -10
  16. package/dist/src/commands/sensors/prepare.js +53 -6
  17. package/dist/src/commands/sensors/project.js +60 -6
  18. package/dist/src/commands/sensors/result.js +18 -3
  19. package/dist/src/commands/sensors/run.js +124 -16
  20. package/dist/src/commands/sensors/status.js +163 -18
  21. package/dist/src/commands/watch/index.js +3 -0
  22. package/dist/src/core/environments/resolve.js +122 -0
  23. package/dist/src/core/environments/store.js +323 -0
  24. package/dist/src/core/facts/catalog.js +138 -0
  25. package/dist/src/core/facts/needs.js +57 -0
  26. package/dist/src/core/facts/types.js +21 -0
  27. package/dist/src/core/init/steps.js +5 -1
  28. package/dist/src/index.js +2 -0
  29. package/dist/tests/commands/env/index.test.js +261 -0
  30. package/dist/tests/commands/preflight/preflight.test.js +121 -0
  31. package/dist/tests/commands/registry/doctor.test.js +283 -0
  32. package/dist/tests/commands/registry/scaffold.test.js +186 -0
  33. package/dist/tests/commands/sensors/bootstrap.test.js +10 -0
  34. package/dist/tests/commands/sensors/compatibility/manifest.test.js +204 -0
  35. package/dist/tests/commands/sensors/init.test.js +107 -0
  36. package/dist/tests/commands/sensors/migrate.test.js +4 -3
  37. package/dist/tests/commands/sensors/partial-pack-bootstrap.test.js +1 -1
  38. package/dist/tests/commands/sensors/prepare.test.js +62 -0
  39. package/dist/tests/commands/sensors/result.test.js +66 -0
  40. package/dist/tests/commands/sensors/run.test.js +331 -0
  41. package/dist/tests/commands/sensors/status.test.js +204 -0
  42. package/dist/tests/commands/watch/reopened-196.test.js +9 -2
  43. package/dist/tests/core/environments/resolve.test.js +347 -0
  44. package/dist/tests/core/environments/store.test.js +248 -0
  45. package/dist/tests/core/facts/catalog.test.js +179 -0
  46. package/dist/tests/core/facts/needs.test.js +88 -0
  47. package/dist/tests/structural/native-release-artifacts.test.js +4 -2
  48. package/dist/tests/structural/operator-environments-docs-contract.test.js +70 -0
  49. package/dist/tests/structural/project-declared-sensors-docs-contract.test.js +104 -0
  50. package/package.json +1 -1
  51. package/prebuilds/darwin-arm64/secure_fs.node +0 -0
  52. package/prebuilds/darwin-x64/secure_fs.node +0 -0
  53. package/prebuilds/win32-arm64/secure_fs.node +0 -0
  54. package/prebuilds/win32-x64/secure_fs.node +0 -0
@@ -0,0 +1,192 @@
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.mergeFactDefinitions = mergeFactDefinitions;
7
+ exports.registerEnvCommand = registerEnvCommand;
8
+ const fs_1 = __importDefault(require("fs"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const readline_1 = __importDefault(require("readline"));
11
+ const catalog_1 = require("../../core/facts/catalog");
12
+ const registries_1 = require("../../core/registries");
13
+ const store_1 = require("../../core/environments/store");
14
+ const resolve_1 = require("../../core/environments/resolve");
15
+ /**
16
+ * Merge facts catalogs from installed registry roots.
17
+ * Later roots may refine a fact, but must not weaken durability: volatile wins
18
+ * over durable so an expired secret cannot be revived by a durable override.
19
+ */
20
+ function mergeFactDefinitions(into, from) {
21
+ for (const [id, next] of Object.entries(from)) {
22
+ const prev = Object.hasOwn(into, id) ? into[id] : undefined;
23
+ if (!prev) {
24
+ into[id] = next;
25
+ continue;
26
+ }
27
+ if (prev.durability === 'volatile' && next.durability === 'durable') {
28
+ const kept = { ...next, durability: 'volatile' };
29
+ if (prev.ttl !== undefined)
30
+ kept.ttl = prev.ttl;
31
+ into[id] = kept;
32
+ continue;
33
+ }
34
+ into[id] = next;
35
+ }
36
+ }
37
+ function loadCatalogFromInstalledRoots() {
38
+ const merged = { schemaVersion: 1, facts: {} };
39
+ for (const root of (0, registries_1.contentRoots)()) {
40
+ const factsPath = path_1.default.join(root, 'facts.json');
41
+ if (!fs_1.default.existsSync(factsPath))
42
+ continue;
43
+ const catalog = (0, catalog_1.parseFactsCatalog)(factsPath);
44
+ mergeFactDefinitions(merged.facts, catalog.facts);
45
+ }
46
+ return merged;
47
+ }
48
+ function defaultConsultedRoots() {
49
+ return (0, registries_1.contentRoots)();
50
+ }
51
+ function requireValue(value, flagHint) {
52
+ if (value === undefined) {
53
+ throw new store_1.EnvironmentStoreError(`${flagHint} requires --value <text> in non-interactive mode (prompt / editable store / --value only; RF-3.3)`);
54
+ }
55
+ return value;
56
+ }
57
+ async function defaultPromptValue(question) {
58
+ const rl = readline_1.default.createInterface({ input: process.stdin, output: process.stdout });
59
+ try {
60
+ return await new Promise((resolve) => {
61
+ rl.question(question, (answer) => resolve(answer));
62
+ });
63
+ }
64
+ finally {
65
+ rl.close();
66
+ }
67
+ }
68
+ async function resolveValue(optsValue, flagHint, factId, deps) {
69
+ if (optsValue !== undefined)
70
+ return optsValue;
71
+ if (!deps.stdinIsTTY()) {
72
+ return requireValue(undefined, flagHint);
73
+ }
74
+ return Promise.resolve(deps.promptValue(`Value for ${factId}: `));
75
+ }
76
+ function setFact(factId, rawValue, deps) {
77
+ const catalog = deps.loadCatalog();
78
+ const name = (0, store_1.getActiveEnvironmentName)();
79
+ (0, store_1.writeFact)(name, factId, {
80
+ value: rawValue,
81
+ status: 'fresh',
82
+ updatedAt: new Date().toISOString(),
83
+ }, {
84
+ catalog,
85
+ consultedRoots: deps.consultedRoots(),
86
+ });
87
+ }
88
+ function registerEnvCommand(program, deps = {}) {
89
+ const loadCatalog = deps.loadCatalog ?? loadCatalogFromInstalledRoots;
90
+ const consultedRoots = deps.consultedRoots ?? defaultConsultedRoots;
91
+ const now = deps.now ?? (() => new Date());
92
+ const stdinIsTTY = deps.stdinIsTTY ?? (() => Boolean(process.stdin.isTTY));
93
+ const promptValue = deps.promptValue ?? defaultPromptValue;
94
+ const env = program.command('env').description('manage machine-local operator environments (facts store)');
95
+ env.command('status')
96
+ .description('show active environment and fact statuses vs catalog')
97
+ .action(() => {
98
+ const name = (0, store_1.getActiveEnvironmentName)();
99
+ const catalog = loadCatalog();
100
+ const store = (0, store_1.readValues)(name);
101
+ const statuses = (0, resolve_1.statusFor)(catalog, store, now());
102
+ const lines = [
103
+ `Active environment: ${name}`,
104
+ `Store: ${(0, store_1.valuesPath)(name)}`,
105
+ '',
106
+ 'Facts:',
107
+ ];
108
+ const ids = Object.keys(catalog.facts).sort();
109
+ if (ids.length === 0) {
110
+ lines.push(' (no facts in consulted catalogs)');
111
+ }
112
+ else {
113
+ for (const id of ids) {
114
+ lines.push(` ${id}: ${statuses[id]}`);
115
+ }
116
+ }
117
+ process.stdout.write(lines.join('\n') + '\n');
118
+ });
119
+ env.command('set <factId>')
120
+ .description('set a fact value in the active environment (--value for non-interactive)')
121
+ .option('--value <text>', 'fact value (required non-interactive; never runs registry commands)')
122
+ .action(async (factId, opts) => {
123
+ const value = await resolveValue(opts.value, 'awm env set', factId, {
124
+ stdinIsTTY,
125
+ promptValue,
126
+ });
127
+ setFact(factId, value, { loadCatalog, consultedRoots });
128
+ process.stdout.write(`Set ${factId} in environment ${(0, store_1.getActiveEnvironmentName)()}\n`);
129
+ });
130
+ env.command('edit')
131
+ .description('print the absolute path of the active environment values.json')
132
+ .action(() => {
133
+ const name = (0, store_1.getActiveEnvironmentName)();
134
+ const file = (0, store_1.valuesPath)(name);
135
+ process.stdout.write(`${file}\n`);
136
+ });
137
+ env.command('refresh [factId]')
138
+ .description('re-set a fact value (prompt / --value only; no registry commands)')
139
+ .option('--value <text>', 'new value (required non-interactive)')
140
+ .option('--all', 'refresh every fact present in the store (interactive prompts, or pass factId)')
141
+ .action(async (factId, opts) => {
142
+ if (opts.all && !factId) {
143
+ if (!stdinIsTTY()) {
144
+ throw new store_1.EnvironmentStoreError('awm env refresh --all requires a TTY (or pass <factId> with --value in non-interactive mode)');
145
+ }
146
+ const name = (0, store_1.getActiveEnvironmentName)();
147
+ const ids = Object.keys((0, store_1.readValues)(name));
148
+ if (ids.length === 0) {
149
+ throw new store_1.EnvironmentStoreError('awm env refresh --all: active store has no facts');
150
+ }
151
+ for (const id of ids) {
152
+ const value = await Promise.resolve(promptValue(`Value for ${id}: `));
153
+ setFact(id, value, { loadCatalog, consultedRoots });
154
+ process.stdout.write(`Refreshed ${id} in environment ${(0, store_1.getActiveEnvironmentName)()}\n`);
155
+ }
156
+ return;
157
+ }
158
+ if (!factId) {
159
+ throw new store_1.EnvironmentStoreError('awm env refresh requires <factId> (or interactive --all)');
160
+ }
161
+ const value = await resolveValue(opts.value, 'awm env refresh', factId, {
162
+ stdinIsTTY,
163
+ promptValue,
164
+ });
165
+ setFact(factId, value, { loadCatalog, consultedRoots });
166
+ process.stdout.write(`Refreshed ${factId} in environment ${(0, store_1.getActiveEnvironmentName)()}\n`);
167
+ });
168
+ env.command('use <name>')
169
+ .description('switch the active environment (one active per machine)')
170
+ .action((name) => {
171
+ (0, store_1.setActiveEnvironmentName)(name);
172
+ process.stdout.write(`Active environment: ${name}\n`);
173
+ });
174
+ env.command('inject')
175
+ .description('print JSON injection payload for the active environment (default mode: reference)')
176
+ .option('--mode <mode>', 'injection mode: reference|context', 'reference')
177
+ .action((opts) => {
178
+ const rawMode = opts.mode ?? 'reference';
179
+ const mode = rawMode === 'reference' || rawMode === 'context' ? rawMode : 'reference';
180
+ const name = (0, store_1.getActiveEnvironmentName)();
181
+ const catalog = loadCatalog();
182
+ const store = (0, store_1.readValues)(name);
183
+ const result = (0, resolve_1.buildInjection)({
184
+ catalog,
185
+ store,
186
+ storePath: (0, store_1.valuesPath)(name),
187
+ inject: mode,
188
+ now: now(),
189
+ });
190
+ process.stdout.write(JSON.stringify(result, null, 2) + '\n');
191
+ });
192
+ }
@@ -100,13 +100,18 @@ function checkContextKernel(cwd) {
100
100
  remedy: inspection.remedy,
101
101
  };
102
102
  }
103
+ function isDisabledCheckDetail(detail) {
104
+ return detail === 'disabled' || detail.startsWith('disabled ');
105
+ }
103
106
  function manifestView(status) {
104
- if (!status.pack)
107
+ // Only-project READY has pack null but non-empty checks — still a valid view (RF-2.8).
108
+ // Do not invent a fake pack id; keep pack null and surface sensors from status.checks.
109
+ if (status.mode !== 'project-sensors' && status.mode !== 'legacy-v1')
105
110
  return null;
106
111
  const sensors = Object.fromEntries(Object.entries(status.checks).map(([name, check]) => [name, {
107
- enabled: check.detail !== 'disabled',
112
+ enabled: !isDisabledCheckDetail(check.detail),
108
113
  }]));
109
- return { pack: status.pack, sensors };
114
+ return { pack: status.pack ?? null, sensors };
110
115
  }
111
116
  /**
112
117
  * Total sensor entries and how many are enabled (`enabled !== false`, so a missing
@@ -139,12 +144,32 @@ function checkManifest(status) {
139
144
  };
140
145
  }
141
146
  if (status.mode === 'invalid') {
147
+ if (status.reason === 'schema-unsupported') {
148
+ return {
149
+ id: 'manifest',
150
+ ok: false,
151
+ detail: '.awm/sensors.json uses an unsupported schema',
152
+ remedy: 'fix or regenerate it with `awm sensors init`',
153
+ };
154
+ }
155
+ // JSON.parse succeeded but schema/entry validation failed (RF-4.1/4.2).
156
+ // Never claim "not valid JSON"; name the sensor/field from status.reason.
157
+ if (typeof status.reason === 'string' && (status.reason.startsWith('schema-invalid') || status.reason.includes('Invalid sensor manifest'))) {
158
+ const detail = status.reason.startsWith('schema-invalid:')
159
+ ? status.reason.slice('schema-invalid:'.length).trim()
160
+ : status.reason;
161
+ // RF-3.3: project schema invalidity must not lead with `awm sensors init`.
162
+ return {
163
+ id: 'manifest',
164
+ ok: false,
165
+ detail,
166
+ remedy: 'repair the invalid sensor entry in .awm/sensors.json',
167
+ };
168
+ }
142
169
  return {
143
170
  id: 'manifest',
144
171
  ok: false,
145
- detail: status.reason === 'schema-unsupported'
146
- ? '.awm/sensors.json uses an unsupported schema'
147
- : '.awm/sensors.json is not valid JSON',
172
+ detail: '.awm/sensors.json is not valid JSON',
148
173
  remedy: 'fix or regenerate it with `awm sensors init`',
149
174
  };
150
175
  }
@@ -165,6 +190,14 @@ function checkManifest(status) {
165
190
  // for the detected stack, so `awm sensors init` built an honest, empty manifest
166
191
  // rather than inventing defaults. That must not read as "all sensors disabled".
167
192
  if (total === 0) {
193
+ if (manifest.pack === null) {
194
+ return {
195
+ id: 'manifest',
196
+ ok: false,
197
+ detail: 'only-project manifest has no sensors',
198
+ remedy: 'add source:"project" sensor entries to .awm/sensors.json, or declare a pack',
199
+ };
200
+ }
168
201
  return {
169
202
  id: 'manifest',
170
203
  ok: false,
@@ -179,12 +212,14 @@ function checkManifest(status) {
179
212
  // gap is one missing tool could then configure sensors and still never pass.
180
213
  const uninitialized = Object.keys(status.uninitialized ?? {});
181
214
  const gap = uninitialized.length === 0 ? '' : `; ${uninitialized.length} applicable sensor(s) not initialized: ${uninitialized.join(', ')}`;
182
- return {
183
- id: 'manifest',
184
- ok: true,
185
- detail: (enabled === 0 ? `pack ${manifest.pack}, all ${total} sensors disabled (deliberate opt-out)`
186
- : `pack ${manifest.pack}, ${enabled}/${total} sensors enabled`) + gap,
187
- };
215
+ const summary = manifest.pack === null
216
+ ? (enabled === 0
217
+ ? `only-project, all ${total} sensors disabled (deliberate opt-out)`
218
+ : `only-project, ${enabled}/${total} project-declared sensors enabled`)
219
+ : (enabled === 0
220
+ ? `pack ${manifest.pack}, all ${total} sensors disabled (deliberate opt-out)`
221
+ : `pack ${manifest.pack}, ${enabled}/${total} sensors enabled`);
222
+ return { id: 'manifest', ok: true, detail: summary + gap };
188
223
  }
189
224
  /** Every enabled sensor's command must resolve. This is the check nothing was calling. */
190
225
  async function checkTools(cwd, status) {
@@ -198,11 +233,14 @@ async function checkTools(cwd, status) {
198
233
  // already guards against; this defends the invariant independently rather than
199
234
  // relying solely on `checkManifest`'s gate to catch this exact manifest shape.
200
235
  if (Object.keys(status.checks).length === 0) {
236
+ const packId = typeof status.pack === 'string' && status.pack.length > 0 ? status.pack : null;
201
237
  return {
202
238
  id: 'tools',
203
239
  ok: false,
204
240
  detail: 'no sensors configured to check (0 sensor entries in the manifest)',
205
- remedy: `registry has no pack for '${status.pack}': run \`awm update\` or add a registry that has it`,
241
+ remedy: packId === null
242
+ ? 'add source:"project" sensor entries to .awm/sensors.json, or declare a pack'
243
+ : `registry has no pack for '${packId}': run \`awm update\` or add a registry that has it`,
206
244
  };
207
245
  }
208
246
  const broken = Object.entries(status.checks).filter(([, c]) => !c.ok);
@@ -227,6 +265,9 @@ function checkPack(cwd, status) {
227
265
  const manifest = manifestView(status);
228
266
  if (!manifest)
229
267
  return { id: 'pack', ok: true, detail: 'skipped (no manifest)' };
268
+ if (manifest.pack === null) {
269
+ return { id: 'pack', ok: true, detail: 'no pack (project-declared sensors only)' };
270
+ }
230
271
  const detection = (0, detection_1.detectStack)(cwd);
231
272
  if (manifest.pack === 'generic' && detection.pack !== 'generic') {
232
273
  return {
@@ -327,6 +368,15 @@ async function checkSensorExecution(cwd) {
327
368
  try {
328
369
  const output = await (0, run_1.runSensors)({ cwd, all: true });
329
370
  if (output.overall === 'pass') {
371
+ const invalid = output.invalidEntries ?? [];
372
+ if (invalid.length > 0) {
373
+ return {
374
+ id: 'sensors-execution',
375
+ ok: false,
376
+ detail: `sensors passed but ${invalid.length} invalidEntries remain: ${invalid.map(entry => `${entry.name} (${entry.reason})`).join('; ')}`,
377
+ remedy: 'repair the invalid sensor entries in .awm/sensors.json, then rerun `awm preflight --verify-sensors`',
378
+ };
379
+ }
330
380
  return { id: 'sensors-execution', ok: true, detail: 'all selected sensors completed with pass' };
331
381
  }
332
382
  return {