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
@@ -14,6 +14,7 @@ const resolve_1 = require("./compatibility/resolve");
14
14
  const project_1 = require("./project");
15
15
  const source_1 = require("./compatibility/source");
16
16
  const registries_1 = require("../../core/registries");
17
+ const manifest_1 = require("./compatibility/manifest");
17
18
  /** First non-flag token after `npx` — the tool the command actually runs. */
18
19
  function npxTool(parts) {
19
20
  for (let i = 1; i < parts.length; i++) {
@@ -197,6 +198,90 @@ function invalidStatus(project, pack, reason = 'manifest-malformed', remedy = 'r
197
198
  ...projectMetadata(project, 'invalid', reason, { remedy }),
198
199
  };
199
200
  }
201
+ function hasSelectedPack(manifest) {
202
+ return typeof manifest.pack === 'string' && manifest.pack.length > 0;
203
+ }
204
+ function projectSensorCheck(sensor, cwd) {
205
+ if (sensor.enabled === false) {
206
+ return { ok: true, detail: 'disabled (project-declared)', certification: 'project-declared' };
207
+ }
208
+ const check = checkStructuredCommand(sensor.command, cwd, sensor.assets);
209
+ return {
210
+ ok: check.ok,
211
+ detail: check.ok ? `project-declared: ${check.detail}` : `${check.detail} (project-declared)`,
212
+ certification: 'project-declared',
213
+ };
214
+ }
215
+ function packBoundOnly(manifest) {
216
+ const sensors = Object.fromEntries(Object.entries(manifest.sensors).filter(([, sensor]) => !(0, manifest_1.isProjectDeclaredSensor)(sensor)));
217
+ const pack = 'pack' in manifest && typeof manifest.pack === 'string' ? manifest.pack : '';
218
+ return { schemaVersion: 2, pack, sensors };
219
+ }
220
+ /** Live pack sensor ids, or null when the pack cannot be resolved (fail closed). */
221
+ function livePackSensorIds(source) {
222
+ try {
223
+ const parsed = (0, contract_1.parseSensorPack)(JSON.parse(source.content), source.path);
224
+ if (parsed.kind !== 'v2')
225
+ return null;
226
+ return new Set(Object.keys(parsed.pack.sensors));
227
+ }
228
+ catch {
229
+ return null;
230
+ }
231
+ }
232
+ /**
233
+ * RF-1.7 status: never label a colliding project id as project-declared READY.
234
+ * When pack is selected and live is unavailable, fail closed rather than claiming
235
+ * the project override is safe.
236
+ */
237
+ function projectDeclaredOrCollisionCheck(name, sensor, packageRoot, packSelected, liveIds) {
238
+ if (packSelected && (liveIds === null || liveIds.has(name))) {
239
+ return {
240
+ ok: false,
241
+ detail: liveIds === null
242
+ ? `project-sensor-name-collision: ${name} (live pack unavailable)`
243
+ : `project-sensor-name-collision: ${name}; pack sensor kept`,
244
+ };
245
+ }
246
+ return projectSensorCheck(sensor, packageRoot);
247
+ }
248
+ /** Soft-isolate valid siblings into checks while authority remains invalid (RF soft-parse). */
249
+ function softIsolatedChecks(project) {
250
+ const recovered = (0, project_1.softRecoverInvalidProject)(project);
251
+ if (!recovered)
252
+ return {};
253
+ const checks = {};
254
+ const v3Project = recovered.manifest.kind === 'v3' && recovered.manifest.pack.mode === 'project-sensors'
255
+ ? recovered.manifest.pack
256
+ : null;
257
+ const sensors = v3Project?.sensors ?? {};
258
+ // RF-1.7: same collision path as configured status — live pack probe when pack is selected.
259
+ const packSelected = v3Project !== null && hasSelectedPack(v3Project);
260
+ let liveIds = null;
261
+ if (packSelected) {
262
+ try {
263
+ const resolution = (0, source_1.resolveSensorSource)(recovered.manifest, { registries: (0, registries_1.listRegistries)() });
264
+ liveIds = 'source' in resolution ? livePackSensorIds(resolution.source) : null;
265
+ }
266
+ catch {
267
+ liveIds = null;
268
+ }
269
+ }
270
+ for (const [name, sensor] of Object.entries(sensors)) {
271
+ if ((0, manifest_1.isProjectDeclaredSensor)(sensor)) {
272
+ checks[name] = projectDeclaredOrCollisionCheck(name, sensor, recovered.packageRoot, packSelected, liveIds);
273
+ continue;
274
+ }
275
+ const packSensor = sensor;
276
+ checks[name] = packSensor.enabled === false
277
+ ? { ok: true, detail: 'disabled' }
278
+ : checkStructuredCommand(packSensor.command, recovered.packageRoot, packSensor.assets);
279
+ }
280
+ for (const entry of recovered.invalidEntries) {
281
+ checks[entry.name] = { ok: false, detail: entry.reason };
282
+ }
283
+ return checks;
284
+ }
200
285
  async function computeSensorStatus(cwd = process.cwd()) {
201
286
  let project;
202
287
  try {
@@ -211,15 +296,40 @@ async function computeSensorStatus(cwd = process.cwd()) {
211
296
  }
212
297
  if (project.state === 'invalid') {
213
298
  let pack = null;
299
+ let reason = 'manifest-malformed';
300
+ let remedy = 'repair-sensor-manifest';
214
301
  try {
215
- const raw = JSON.parse(fs_1.default.readFileSync(project.manifestPath, 'utf-8'));
216
- if (raw && typeof raw === 'object' && !Array.isArray(raw) && !('schemaVersion' in raw) && typeof raw.pack === 'string') {
217
- pack = raw.pack;
302
+ const text = fs_1.default.readFileSync(project.manifestPath, 'utf-8');
303
+ try {
304
+ const raw = JSON.parse(text);
305
+ if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
306
+ const record = raw;
307
+ if (!('schemaVersion' in record) && typeof record.pack === 'string')
308
+ pack = record.pack;
309
+ else if (typeof record.pack === 'string')
310
+ pack = record.pack;
311
+ }
312
+ // JSON.parse succeeded — this is schema invalidity, not "not valid JSON" (RF-4.1).
313
+ if (project.reason.includes('unsupported manifest schemaVersion')) {
314
+ reason = 'schema-unsupported';
315
+ }
316
+ else {
317
+ reason = `schema-invalid: ${project.reason}`;
318
+ // Project-entry schema failures must not lead with `awm sensors init` (RF-3.3).
319
+ remedy = 'repair-the-invalid-sensor-entry-in-sensors-json';
320
+ }
321
+ }
322
+ catch {
323
+ reason = 'manifest-malformed';
218
324
  }
219
325
  }
220
- catch { /* malformed JSON is not configured */ }
221
- const reason = project.reason.includes('unsupported manifest schemaVersion') ? 'schema-unsupported' : 'manifest-malformed';
222
- return invalidStatus(project, pack, reason);
326
+ catch { /* unreadable manifest stays malformed */ }
327
+ // Soft-isolate valid siblings into checks (same WithIssues path as run) while
328
+ // keeping mode=invalid so preflight RF-4.1 still sees schema-invalid authority.
329
+ return {
330
+ ...invalidStatus(project, pack, reason, remedy),
331
+ checks: softIsolatedChecks(project),
332
+ };
223
333
  }
224
334
  const packageRoot = project.packageRoot;
225
335
  const parsed = project.manifest;
@@ -229,38 +339,73 @@ async function computeSensorStatus(cwd = process.cwd()) {
229
339
  ...projectMetadata(project, parsed.pack.mode, `${parsed.pack.mode}-declared`, { declarationReason: parsed.pack.reason }),
230
340
  };
231
341
  }
342
+ if (parsed.kind === 'v3' && parsed.pack.mode === 'project-sensors' && !hasSelectedPack(parsed.pack)) {
343
+ // Only-project: no pack source to resolve. Report each project sensor with
344
+ // project-declared provenance rather than an empty Pack:none DEGRADED (RF-2.8).
345
+ const checks = {};
346
+ for (const [name, sensor] of Object.entries(parsed.pack.sensors)) {
347
+ if ((0, manifest_1.isProjectDeclaredSensor)(sensor)) {
348
+ checks[name] = projectSensorCheck(sensor, packageRoot);
349
+ }
350
+ else {
351
+ checks[name] = { ok: false, detail: 'pack-bound sensor requires a nonempty pack' };
352
+ }
353
+ }
354
+ return {
355
+ overall: Object.keys(checks).length > 0 && Object.values(checks).every(check => check.ok) ? 'READY' : 'DEGRADED',
356
+ pack: null,
357
+ checks,
358
+ ...projectMetadata(project, 'project-sensors', 'project-declared-only'),
359
+ };
360
+ }
232
361
  if (parsed.kind === 'v2' || parsed.kind === 'v3') {
233
362
  const parsedPack = parsed.pack;
363
+ const packId = typeof parsedPack.pack === 'string' ? parsedPack.pack : null;
364
+ const packSelected = typeof parsedPack.pack === 'string' && parsedPack.pack.length > 0;
234
365
  let resolution;
235
366
  try {
236
367
  resolution = (0, source_1.resolveSensorSource)(parsed, { registries: (0, registries_1.listRegistries)() });
237
368
  }
238
369
  catch {
239
- return invalidStatus(project, parsedPack.pack, 'sensor-source-invalid', 'repair-or-run-awm-update');
370
+ return invalidStatus(project, packId, 'sensor-source-invalid', 'repair-or-run-awm-update');
240
371
  }
241
372
  if (!('source' in resolution))
242
- return sourceFailure(project, parsedPack.pack, resolution);
373
+ return sourceFailure(project, packId ?? 'unknown', resolution);
243
374
  const sourceMeta = resolvedSourceMetadata(resolution);
244
- const manifest = parsedPack;
375
+ const liveIds = livePackSensorIds(resolution.source);
376
+ const packManifest = packBoundOnly(parsedPack);
245
377
  const checks = {};
246
378
  let compatibility;
247
379
  try {
248
- compatibility = resolveStaticV2Compatibility(packageRoot, manifest, resolution.source);
380
+ compatibility = resolveStaticV2Compatibility(packageRoot, packManifest, resolution.source);
249
381
  }
250
382
  catch (error) {
251
383
  const detail = error instanceof Error ? error.message : 'live compatibility unavailable';
252
- for (const [name, sensor] of Object.entries(manifest.sensors))
253
- checks[name] = sensor.enabled === false ? { ok: true, detail: 'disabled' } : { ok: false, detail };
254
- return { overall: 'DEGRADED', pack: manifest.pack, checks, ...projectMetadata(project, 'project-sensors', sourceMeta.reason, { ...sourceMeta }) };
384
+ for (const [name, sensor] of Object.entries(parsedPack.sensors)) {
385
+ if ((0, manifest_1.isProjectDeclaredSensor)(sensor)) {
386
+ checks[name] = projectDeclaredOrCollisionCheck(name, sensor, packageRoot, packSelected, liveIds);
387
+ }
388
+ else {
389
+ checks[name] = sensor.enabled === false
390
+ ? { ok: true, detail: 'disabled' }
391
+ : { ok: false, detail };
392
+ }
393
+ }
394
+ return { overall: 'DEGRADED', pack: packId, checks, ...projectMetadata(project, 'project-sensors', sourceMeta.reason, { ...sourceMeta }) };
255
395
  }
256
- for (const [name, sensor] of Object.entries(manifest.sensors)) {
257
- checks[name] = sensor.enabled === false ? { ok: true, detail: 'disabled' }
258
- : staticCompatibilityCheck(sensor, compatibility[name]) ?? checkStructuredCommand(sensor.command, packageRoot, sensor.assets);
396
+ for (const [name, sensor] of Object.entries(parsedPack.sensors)) {
397
+ if ((0, manifest_1.isProjectDeclaredSensor)(sensor)) {
398
+ checks[name] = projectDeclaredOrCollisionCheck(name, sensor, packageRoot, packSelected, liveIds);
399
+ continue;
400
+ }
401
+ const packSensor = sensor;
402
+ checks[name] = packSensor.enabled === false ? { ok: true, detail: 'disabled' }
403
+ : staticCompatibilityCheck(packSensor, compatibility[name]) ?? checkStructuredCommand(packSensor.command, packageRoot, packSensor.assets);
259
404
  }
260
- const uninitialized = uninitializedSensors(manifest, compatibility);
405
+ const uninitialized = uninitializedSensors(packManifest, compatibility);
261
406
  return {
262
407
  overall: Object.keys(checks).length > 0 && Object.values(checks).every(check => check.ok) ? 'READY' : 'DEGRADED',
263
- pack: manifest.pack, checks,
408
+ pack: packId, checks,
264
409
  ...(uninitialized ? { uninitialized } : {}),
265
410
  ...projectMetadata(project, 'project-sensors', sourceMeta.reason, { ...sourceMeta }),
266
411
  };
@@ -160,6 +160,9 @@ function registerWatchCommand(program) {
160
160
  }
161
161
  else {
162
162
  process.stdout.write(`awm watch: ${outcome === 'stopped' ? 'detenido' : 'track congelado'}; ciclo ${final.cycleState ?? final.state} — sin certificacion de cierre\n`);
163
+ // exitCode only — never process.exit here: in-process tests call parseAsync
164
+ // inside Jest; a hard exit kills the worker (CI exit 130 on every OS).
165
+ // exitCode=130 is intentional Darwin CI flake harden (reopened-196), not operator-environments scope.
163
166
  if (outcome === 'stopped')
164
167
  process.exitCode = 130;
165
168
  else if (outcome === 'complete' || final.state !== 'present')
@@ -0,0 +1,122 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CONTEXT_INJECT_MAX_CHARS = void 0;
4
+ exports.statusFor = statusFor;
5
+ exports.buildInjection = buildInjection;
6
+ /** Fixed budget for `inject: context` fresh values (RF-3.2). */
7
+ exports.CONTEXT_INJECT_MAX_CHARS = 2048;
8
+ function assertValidNow(now) {
9
+ if (!(now instanceof Date) || !Number.isFinite(now.getTime())) {
10
+ throw new Error('now must be a Date with a finite getTime()');
11
+ }
12
+ }
13
+ function effectiveStatus(definitionDurability, stored, now) {
14
+ if (!stored)
15
+ return 'missing';
16
+ if (stored.status === 'missing')
17
+ return 'missing';
18
+ if (stored.status === 'stale')
19
+ return 'stale';
20
+ // Durable never time-stales (RF-2.3).
21
+ if (definitionDurability === 'durable') {
22
+ return stored.status === 'fresh' ? 'fresh' : stored.status;
23
+ }
24
+ // Volatile: fail closed — missing/invalid expiresAt or now >= expiresAt → stale (RF-2.4).
25
+ if (typeof stored.expiresAt !== 'string' || stored.expiresAt.trim() === '') {
26
+ return 'stale';
27
+ }
28
+ const expiresMs = Date.parse(stored.expiresAt);
29
+ if (!Number.isFinite(expiresMs) || now.getTime() >= expiresMs) {
30
+ return 'stale';
31
+ }
32
+ return stored.status === 'fresh' ? 'fresh' : stored.status;
33
+ }
34
+ /**
35
+ * Per-catalog-id status against the store. Missing → `missing` with no default (RF-2.5).
36
+ */
37
+ function statusFor(catalog, store, now = new Date()) {
38
+ if (!catalog || typeof catalog !== 'object' || !catalog.facts) {
39
+ throw new Error('statusFor: catalog with facts is required');
40
+ }
41
+ if (!store || typeof store !== 'object') {
42
+ throw new Error('statusFor: store must be an object');
43
+ }
44
+ assertValidNow(now);
45
+ const out = {};
46
+ for (const [id, def] of Object.entries(catalog.facts)) {
47
+ out[id] = effectiveStatus(def.durability, store[id], now);
48
+ }
49
+ return out;
50
+ }
51
+ /**
52
+ * Build injection payload. Default / explicit `reference` exposes path + ids only (RF-3.1).
53
+ * `context` includes fresh `inject: context` values under budget; overflow degrades (RF-3.2).
54
+ */
55
+ function buildInjection(opts) {
56
+ if (!opts || typeof opts !== 'object') {
57
+ throw new Error('buildInjection: options object is required');
58
+ }
59
+ if (!opts.catalog?.facts) {
60
+ throw new Error('buildInjection: catalog with facts is required');
61
+ }
62
+ if (typeof opts.storePath !== 'string' || opts.storePath.trim() === '') {
63
+ throw new Error('buildInjection: storePath must be a non-empty string');
64
+ }
65
+ if (!opts.store || typeof opts.store !== 'object') {
66
+ throw new Error('buildInjection: store must be an object');
67
+ }
68
+ const rawInject = opts.inject ?? 'reference';
69
+ // Unknown/typo must not fall through into context (fail-open). Prefer reference (RNF-2).
70
+ const inject = rawInject === 'reference' || rawInject === 'context' ? rawInject : 'reference';
71
+ const maxChars = opts.maxChars ?? exports.CONTEXT_INJECT_MAX_CHARS;
72
+ if (typeof maxChars !== 'number' || !Number.isFinite(maxChars) || maxChars < 0) {
73
+ throw new Error('buildInjection: maxChars must be a non-negative finite number');
74
+ }
75
+ const now = opts.now ?? new Date();
76
+ assertValidNow(now);
77
+ if (opts.factIds !== undefined) {
78
+ if (!Array.isArray(opts.factIds) || opts.factIds.some((id) => typeof id !== 'string' || id === '')) {
79
+ throw new Error('buildInjection: factIds must be an array of non-empty strings');
80
+ }
81
+ }
82
+ const factIds = opts.factIds ?? Object.keys(opts.catalog.facts);
83
+ const statuses = statusFor(opts.catalog, opts.store, now);
84
+ const referenceResult = () => ({
85
+ mode: 'reference',
86
+ storePath: opts.storePath,
87
+ factIds: [...factIds],
88
+ });
89
+ if (inject !== 'context') {
90
+ return referenceResult();
91
+ }
92
+ // Context mode: only fresh values whose catalog inject is `context` (RF-3.2).
93
+ const contextValues = {};
94
+ let totalChars = 0;
95
+ for (const id of factIds) {
96
+ const def = opts.catalog.facts[id];
97
+ if (!def || def.inject !== 'context')
98
+ continue;
99
+ if (statuses[id] !== 'fresh')
100
+ continue;
101
+ const stored = opts.store[id];
102
+ if (!stored || typeof stored.value !== 'string')
103
+ continue;
104
+ totalChars += stored.value.length;
105
+ contextValues[id] = stored.value;
106
+ }
107
+ if (Object.keys(contextValues).length === 0) {
108
+ return referenceResult();
109
+ }
110
+ if (totalChars > maxChars) {
111
+ return {
112
+ ...referenceResult(),
113
+ warning: `Context inject budget exceeded (${totalChars} > ${maxChars} chars); degraded to reference`,
114
+ };
115
+ }
116
+ return {
117
+ mode: 'context',
118
+ storePath: opts.storePath,
119
+ factIds: [...factIds],
120
+ values: contextValues,
121
+ };
122
+ }