agentic-workflow-manager 9.4.1 → 9.6.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 (57) hide show
  1. package/dist/src/commands/preflight/checks.js +58 -27
  2. package/dist/src/commands/sensors/bootstrap.js +171 -0
  3. package/dist/src/commands/sensors/compatibility/contract.js +18 -15
  4. package/dist/src/commands/sensors/compatibility/manifest.js +75 -2
  5. package/dist/src/commands/sensors/compatibility/materialize.js +256 -54
  6. package/dist/src/commands/sensors/compatibility/pack-source.js +61 -17
  7. package/dist/src/commands/sensors/compatibility/safe-file.js +286 -0
  8. package/dist/src/commands/sensors/compatibility/source.js +75 -0
  9. package/dist/src/commands/sensors/coverage/index.js +2 -2
  10. package/dist/src/commands/sensors/coverage/resolve.js +35 -29
  11. package/dist/src/commands/sensors/detection.js +31 -0
  12. package/dist/src/commands/sensors/exec.js +116 -11
  13. package/dist/src/commands/sensors/index.js +51 -10
  14. package/dist/src/commands/sensors/init.js +17 -168
  15. package/dist/src/commands/sensors/migrate.js +209 -0
  16. package/dist/src/commands/sensors/project.js +107 -0
  17. package/dist/src/commands/sensors/run.js +66 -47
  18. package/dist/src/commands/sensors/status.js +97 -56
  19. package/dist/src/core/init/steps.js +24 -11
  20. package/dist/src/core/secure-fs/native-bridge.js +209 -0
  21. package/dist/tests/commands/preflight/preflight.test.js +31 -0
  22. package/dist/tests/commands/sensors/bootstrap-observational.test.js +24 -0
  23. package/dist/tests/commands/sensors/bootstrap.test.js +175 -0
  24. package/dist/tests/commands/sensors/compatibility/contract.test.js +38 -0
  25. package/dist/tests/commands/sensors/compatibility/manifest.test.js +90 -0
  26. package/dist/tests/commands/sensors/compatibility/materialize.test.js +410 -0
  27. package/dist/tests/commands/sensors/compatibility/pack-source.test.js +249 -0
  28. package/dist/tests/commands/sensors/compatibility/safe-file.test.js +52 -0
  29. package/dist/tests/commands/sensors/compatibility/source.test.js +121 -0
  30. package/dist/tests/commands/sensors/coverage/index.test.js +20 -0
  31. package/dist/tests/commands/sensors/coverage/resolve.test.js +36 -34
  32. package/dist/tests/commands/sensors/exec.test.js +86 -0
  33. package/dist/tests/commands/sensors/index.test.js +54 -0
  34. package/dist/tests/commands/sensors/init-pack-unavailable.test.js +21 -84
  35. package/dist/tests/commands/sensors/init.test.js +56 -490
  36. package/dist/tests/commands/sensors/migrate.test.js +513 -0
  37. package/dist/tests/commands/sensors/project.test.js +102 -0
  38. package/dist/tests/commands/sensors/run.test.js +107 -4
  39. package/dist/tests/commands/sensors/status.test.js +106 -2
  40. package/dist/tests/core/init/orchestrator.test.js +3 -1
  41. package/dist/tests/core/secure-fs/native-bridge.test.js +733 -0
  42. package/dist/tests/integration/preflight-json-pipe.e2e.test.js +5 -0
  43. package/dist/tests/integration/sensor-bootstrap.e2e.test.js +165 -0
  44. package/dist/tests/integration/sensor-compatibility.e2e.test.js +14 -23
  45. package/dist/tests/integration/sensor-coverage.e2e.test.js +15 -24
  46. package/dist/tests/integration/sensor-structured-run.e2e.test.js +3 -1
  47. package/dist/tests/structural/native-release-artifacts.test.js +22 -0
  48. package/dist/tests/structural/release-flags-are-reachable-from-ci.test.js +10 -0
  49. package/dist/tests/structural/release-registry-provenance.test.js +4 -0
  50. package/dist/tests/structural/sensor-portability-contract.test.js +44 -0
  51. package/package.json +6 -2
  52. package/prebuilds/darwin-arm64/secure_fs.node +0 -0
  53. package/prebuilds/darwin-x64/secure_fs.node +0 -0
  54. package/prebuilds/linux-arm64/secure_fs.node +0 -0
  55. package/prebuilds/linux-x64/secure_fs.node +0 -0
  56. package/prebuilds/win32-arm64/secure_fs.node +0 -0
  57. package/prebuilds/win32-x64/secure_fs.node +0 -0
@@ -7,6 +7,7 @@ const fs_1 = __importDefault(require("fs"));
7
7
  const os_1 = __importDefault(require("os"));
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const run_1 = require("../../../src/commands/sensors/run");
10
+ const status_1 = require("../../../src/commands/sensors/status");
10
11
  const verdict_1 = require("../../../src/commands/sensors/verdict");
11
12
  function mkTmp() {
12
13
  return fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-sensors-'));
@@ -14,10 +15,18 @@ function mkTmp() {
14
15
  // Define stable mock before jest.mock hoisting
15
16
  const mockRunCommand = jest.fn();
16
17
  const mockRunStructuredCommand = jest.fn();
18
+ const mockResolveSensorProject = jest.fn((startCwd) => {
19
+ const actual = jest.requireActual('../../../src/commands/sensors/project');
20
+ return actual.resolveSensorProject(startCwd);
21
+ });
17
22
  jest.mock('../../../src/commands/sensors/exec', () => ({
18
23
  runCommand: (...args) => mockRunCommand(...args),
19
24
  runStructuredCommand: (...args) => mockRunStructuredCommand(...args),
20
25
  }));
26
+ jest.mock('../../../src/commands/sensors/project', () => {
27
+ const actual = jest.requireActual('../../../src/commands/sensors/project');
28
+ return { ...actual, resolveSensorProject: (startCwd) => mockResolveSensorProject(startCwd) };
29
+ });
21
30
  const { ok, exited, timedOut } = require('./exec-fixtures');
22
31
  const MANIFEST = {
23
32
  pack: 'js-ts',
@@ -39,6 +48,7 @@ describe('runSensors', () => {
39
48
  fs_1.default.writeFileSync(path.join(tmpDir, '.awm', 'sensors.json'), JSON.stringify(MANIFEST));
40
49
  mockRunCommand.mockReset();
41
50
  mockRunStructuredCommand.mockReset();
51
+ mockResolveSensorProject.mockClear();
42
52
  });
43
53
  afterEach(() => { fs_1.default.rmSync(tmpDir, { recursive: true }); });
44
54
  const load = () => require('../../../src/commands/sensors/run');
@@ -282,6 +292,22 @@ describe('runSensors v2 lifecycle contract', () => {
282
292
  expect(result).toEqual(expect.objectContaining({ overall: 'pass' }));
283
293
  expect(mockRunStructuredCommand).toHaveBeenCalledWith(expect.objectContaining({ executable: 'live-eslint' }), expect.any(Object));
284
294
  });
295
+ it('rebinds a missing v2 registryRoot through the unique configured source without mutation', async () => {
296
+ const manifestPath = path_1.default.join(project, '.awm', 'sensors.json');
297
+ const manifest = JSON.parse(fs_1.default.readFileSync(manifestPath, 'utf8'));
298
+ delete manifest.registryRoot;
299
+ fs_1.default.writeFileSync(manifestPath, JSON.stringify(manifest));
300
+ const before = fs_1.default.readFileSync(manifestPath, 'utf8');
301
+ const inventoryPath = path_1.default.join(home, 'registries.json');
302
+ const inventoryBefore = fs_1.default.readFileSync(inventoryPath, 'utf8');
303
+ const { runSensors } = require('../../../src/commands/sensors/run');
304
+ const result = await runSensors({ cwd: project, fast: true });
305
+ expect(result).toMatchObject({ overall: 'pass', mode: 'project-sensors', reason: 'legacy-rebound' });
306
+ expect(result.source).toMatchObject({ kind: 'legacy-rebound', registry: 'baseline' });
307
+ expect(mockRunStructuredCommand).toHaveBeenCalledWith(expect.objectContaining({ executable: 'live-eslint' }), expect.any(Object));
308
+ expect(fs_1.default.readFileSync(manifestPath, 'utf8')).toBe(before);
309
+ expect(fs_1.default.readFileSync(inventoryPath, 'utf8')).toBe(inventoryBefore);
310
+ });
285
311
  it('scopes applicability detection and execution to packageRoot for a monorepo manifest', async () => {
286
312
  // Move the fixture project into a subdirectory ("cli") and point the
287
313
  // manifest at it via packageRoot — mirrors a monorepo where the
@@ -388,15 +414,92 @@ describe('runSensors — not_certified + auto-discovery', () => {
388
414
  expect(out.overall).toBe('not_certified');
389
415
  expect(out.sensors).toEqual([]);
390
416
  });
391
- it('discovers .awm/sensors.json in a parent directory (walk-up)', async () => {
417
+ it('uses the same stable schema-unsupported reason as status', async () => {
418
+ tmpDir = mkTmp();
419
+ fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.git'));
420
+ fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.awm'));
421
+ fs_1.default.writeFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), JSON.stringify({ schemaVersion: 99 }));
422
+ const status = await (0, status_1.computeSensorStatus)(tmpDir);
423
+ const run = await (0, run_1.runSensors)({ cwd: tmpDir });
424
+ expect(status).toMatchObject({ mode: 'invalid', reason: 'schema-unsupported' });
425
+ expect(run).toMatchObject({ mode: 'invalid', reason: 'schema-unsupported' });
426
+ });
427
+ it('resolves the same legacy project and execution cwd from root and nested CWD', async () => {
392
428
  tmpDir = mkTmp();
429
+ fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.git'));
393
430
  fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.awm'));
394
431
  fs_1.default.writeFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), JSON.stringify({ pack: 'test', sensors: { noop: { cmd: 'echo ok', fast: true } } }));
395
432
  const nested = path_1.default.join(tmpDir, 'a', 'b');
396
433
  fs_1.default.mkdirSync(nested, { recursive: true });
397
- const out = await (0, run_1.runSensors)({ cwd: nested });
398
- expect(out.overall).toBe('not_certified');
399
- expect(out.sensors.length).toBe(1);
434
+ const rootRun = await (0, run_1.runSensors)({ cwd: tmpDir });
435
+ const nestedRun = await (0, run_1.runSensors)({ cwd: nested });
436
+ expect(nestedRun).toEqual(rootRun);
437
+ expect(rootRun.overall).toBe('not_certified');
438
+ expect(rootRun.sensors).toHaveLength(1);
439
+ expect(mockResolveSensorProject).toHaveBeenCalledWith(tmpDir);
440
+ expect(mockResolveSensorProject).toHaveBeenCalledWith(nested);
441
+ expect(mockRunCommand).toHaveBeenNthCalledWith(1, 'echo ok', expect.objectContaining({ cwd: tmpDir }));
442
+ expect(mockRunCommand).toHaveBeenNthCalledWith(2, 'echo ok', expect.objectContaining({ cwd: tmpDir }));
443
+ });
444
+ it.each([
445
+ ['native-gate', 'Release-blocking CI is authoritative.'],
446
+ ['opt-out', 'This repository intentionally has no local gate.'],
447
+ ])('does not dispatch v3 %s declarations', async (mode, declarationReason) => {
448
+ tmpDir = mkTmp();
449
+ fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.git'));
450
+ fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.awm'), { recursive: true });
451
+ fs_1.default.writeFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), JSON.stringify({ schemaVersion: 3, mode, reason: declarationReason }));
452
+ const out = await (0, run_1.runSensors)({ cwd: tmpDir, all: true });
453
+ expect(out).toMatchObject({
454
+ overall: 'not_certified', mode, reason: `${mode}-declared`,
455
+ declarationReason,
456
+ projectRoot: tmpDir, manifestPath: path_1.default.join(tmpDir, '.awm', 'sensors.json'),
457
+ });
458
+ expect(out.sensors).toEqual([]);
459
+ expect(mockRunCommand).not.toHaveBeenCalled();
460
+ expect(mockRunStructuredCommand).not.toHaveBeenCalled();
461
+ });
462
+ it('reports bounded candidates and a remedy when v2 source resolution is ambiguous', async () => {
463
+ const previousHome = process.env.AWM_HOME;
464
+ const home = mkTmp();
465
+ try {
466
+ process.env.AWM_HOME = home;
467
+ const pack = {
468
+ schemaVersion: 2, name: 'js-ts', description: 'fixture', detects: ['package.json'],
469
+ coverage: { schemaVersion: 1, classes: { lint: { description: 'lint', detectors: [{ sensor: 'lint' }], remedy: { summary: 'fix', command: 'awm sensors init' } } } },
470
+ sensors: { lint: { applicability: { allFiles: ['package.json'] }, variants: [{
471
+ id: 'eslint-10', priority: 1, certifiedRange: '>=10 <11',
472
+ requirements: { tool: 'eslint', toolRange: '>=10 <11', runtime: 'node', runtimeRange: '>=0' },
473
+ assets: [], formatter: 'generic', probe: { kind: 'package-script-present' },
474
+ command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] },
475
+ }] } },
476
+ };
477
+ const registries = ['alpha', 'beta'];
478
+ fs_1.default.writeFileSync(path_1.default.join(home, 'registries.json'), JSON.stringify(registries.map(name => ({ name, remote: `https://example.test/${name}.git` }))));
479
+ for (const name of registries) {
480
+ const packDir = path_1.default.join(home, 'registries', name, 'sensor-packs', 'js-ts');
481
+ fs_1.default.mkdirSync(packDir, { recursive: true });
482
+ fs_1.default.writeFileSync(path_1.default.join(packDir, 'pack.json'), JSON.stringify(pack));
483
+ }
484
+ tmpDir = mkTmp();
485
+ fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.git'));
486
+ fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.awm'), { recursive: true });
487
+ fs_1.default.writeFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), JSON.stringify({ schemaVersion: 2, pack: 'js-ts', sensors: {} }));
488
+ const out = await (0, run_1.runSensors)({ cwd: tmpDir });
489
+ expect(out).toMatchObject({
490
+ overall: 'not_certified', mode: 'source-ambiguous', reason: 'multiple-compatible-registries',
491
+ remedy: 'configure-one-logical-registry',
492
+ source: { kind: 'source-ambiguous', candidates: ['alpha', 'beta'] },
493
+ });
494
+ expect(out.source.candidates).toHaveLength(2);
495
+ }
496
+ finally {
497
+ if (previousHome === undefined)
498
+ delete process.env.AWM_HOME;
499
+ else
500
+ process.env.AWM_HOME = previousHome;
501
+ fs_1.default.rmSync(home, { recursive: true, force: true });
502
+ }
400
503
  });
401
504
  });
402
505
  describe('detectPackDrift', () => {
@@ -46,6 +46,26 @@ describe('computeSensorStatus', () => {
46
46
  expect(result.overall).toBe('NOT_CONFIGURED');
47
47
  expect(result.pack).toBeNull();
48
48
  });
49
+ it('resolves the Git-bounded project manifest from a nested CWD', async () => {
50
+ fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.git'));
51
+ fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.awm'), { recursive: true });
52
+ fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'node_modules', '.bin'), { recursive: true });
53
+ fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'node_modules', '.bin', 'tsc'), '');
54
+ fs_1.default.writeFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), JSON.stringify({
55
+ pack: 'js-ts', sensors: { typecheck: { cmd: 'npx tsc --noEmit', fast: true } },
56
+ }));
57
+ const nestedCwd = path_1.default.join(tmpDir, 'packages', 'app');
58
+ fs_1.default.mkdirSync(nestedCwd, { recursive: true });
59
+ await expect((0, status_1.computeSensorStatus)(nestedCwd)).resolves.toMatchObject({
60
+ overall: 'READY',
61
+ pack: 'js-ts',
62
+ mode: 'project-sensors',
63
+ reason: 'legacy-v1',
64
+ projectRoot: tmpDir,
65
+ manifestPath: path_1.default.join(tmpDir, '.awm', 'sensors.json'),
66
+ checks: { typecheck: { ok: true } },
67
+ });
68
+ });
49
69
  it.each([
50
70
  ['valid manifest', 'READY', () => {
51
71
  installLocalBin('eslint');
@@ -218,17 +238,24 @@ describe('computeSensorStatus', () => {
218
238
  fs_1.default.writeFileSync(path_1.default.join(packageDir, 'node_modules', '.bin', 'eslint'), '');
219
239
  fs_1.default.writeFileSync(path_1.default.join(packageDir, 'eslint.config.awm.mjs'), 'export default []');
220
240
  fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.awm'), { recursive: true });
221
- fs_1.default.writeFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), JSON.stringify({
222
- schemaVersion: 2, pack: 'js-ts', packageRoot: 'cli', registryRoot: registry,
241
+ const manifestPath = path_1.default.join(tmpDir, '.awm', 'sensors.json');
242
+ fs_1.default.writeFileSync(manifestPath, JSON.stringify({
243
+ schemaVersion: 2, pack: 'js-ts', packageRoot: 'cli',
223
244
  sensors: { lint: {
224
245
  enabled: true, variantId: 'eslint-10', command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.', '--config', 'eslint.config.awm.mjs'] },
225
246
  assets: ['eslint.config.awm.mjs'],
226
247
  initializedCompatibility: { state: 'certified', reason: 'range-and-probe', variantId: 'eslint-10', toolVersion: '10.4.1', runtimeVersion: process.versions.node, certifiedRange: '>=10.0.0 <11.0.0', evidence: [] },
227
248
  } },
228
249
  }));
250
+ const manifestBefore = fs_1.default.readFileSync(manifestPath);
251
+ const inventoryPath = path_1.default.join(home, 'registries.json');
252
+ const inventoryBefore = fs_1.default.readFileSync(inventoryPath);
229
253
  const result = await (0, status_1.computeSensorStatus)(tmpDir);
230
254
  expect(result.checks.lint).toMatchObject({ ok: true });
231
255
  expect(result.overall).toBe('READY');
256
+ expect(result.source).toMatchObject({ kind: 'legacy-rebound', registry: 'baseline' });
257
+ expect(fs_1.default.readFileSync(manifestPath)).toEqual(manifestBefore);
258
+ expect(fs_1.default.readFileSync(inventoryPath)).toEqual(inventoryBefore);
232
259
  expect(runCommand).not.toHaveBeenCalled();
233
260
  expect(runStructuredCommand).not.toHaveBeenCalled();
234
261
  }
@@ -304,4 +331,81 @@ describe('computeSensorStatus', () => {
304
331
  fs_1.default.rmSync(home, { recursive: true, force: true });
305
332
  }
306
333
  });
334
+ it.each([
335
+ ['native-gate', 'Release-blocking CI is authoritative.', 'native-gate-declared'],
336
+ ['opt-out', 'This repository intentionally has no local gate.', 'opt-out-declared'],
337
+ ])('reports v3 %s without dispatching sensors', async (mode, declarationReason, diagnosticReason) => {
338
+ fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.git'));
339
+ fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.awm'), { recursive: true });
340
+ fs_1.default.writeFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), JSON.stringify({ schemaVersion: 3, mode, reason: declarationReason }));
341
+ await expect((0, status_1.computeSensorStatus)(tmpDir)).resolves.toMatchObject({
342
+ overall: 'NOT_CONFIGURED', mode, reason: diagnosticReason,
343
+ declarationReason,
344
+ projectRoot: tmpDir, manifestPath: path_1.default.join(tmpDir, '.awm', 'sensors.json'),
345
+ });
346
+ expect(runCommand).not.toHaveBeenCalled();
347
+ expect(runStructuredCommand).not.toHaveBeenCalled();
348
+ });
349
+ it('reports a v2 source-unavailable state instead of accepting a missing pack', async () => {
350
+ const previousHome = process.env.AWM_HOME;
351
+ const home = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-status-missing-source-home-'));
352
+ try {
353
+ process.env.AWM_HOME = home;
354
+ fs_1.default.writeFileSync(path_1.default.join(home, 'registries.json'), JSON.stringify([]));
355
+ fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.awm'), { recursive: true });
356
+ fs_1.default.writeFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), JSON.stringify({
357
+ schemaVersion: 2, pack: 'js-ts', registryRoot: path_1.default.join(home, 'gone'), sensors: {},
358
+ }));
359
+ await expect((0, status_1.computeSensorStatus)(tmpDir)).resolves.toMatchObject({
360
+ overall: 'DEGRADED', pack: 'js-ts', mode: 'source-unavailable',
361
+ reason: 'no-compatible-registry', remedy: 'install-registry-or-run-awm-update',
362
+ projectRoot: tmpDir, manifestPath: path_1.default.join(tmpDir, '.awm', 'sensors.json'),
363
+ });
364
+ }
365
+ finally {
366
+ if (previousHome === undefined)
367
+ delete process.env.AWM_HOME;
368
+ else
369
+ process.env.AWM_HOME = previousHome;
370
+ fs_1.default.rmSync(home, { recursive: true, force: true });
371
+ }
372
+ });
373
+ it('reports bounded candidates and a remedy when v2 source resolution is ambiguous', async () => {
374
+ const previousHome = process.env.AWM_HOME;
375
+ const home = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-status-ambiguous-home-'));
376
+ try {
377
+ process.env.AWM_HOME = home;
378
+ const pack = {
379
+ schemaVersion: 2, name: 'js-ts', description: 'fixture', detects: ['package.json'],
380
+ coverage: { schemaVersion: 1, classes: { lint: { description: 'lint', detectors: [{ sensor: 'lint' }], remedy: { summary: 'fix', command: 'awm sensors init' } } } },
381
+ sensors: { lint: { applicability: { allFiles: ['package.json'] }, variants: [{
382
+ id: 'eslint-10', priority: 1, certifiedRange: '>=10 <11',
383
+ requirements: { tool: 'eslint', toolRange: '>=10 <11', runtime: 'node', runtimeRange: '>=0' },
384
+ assets: [], formatter: 'generic', probe: { kind: 'package-script-present' },
385
+ command: { executable: 'eslint', resolution: 'node-modules-bin', args: ['.'] },
386
+ }] } },
387
+ };
388
+ const registries = ['alpha', 'beta'];
389
+ fs_1.default.writeFileSync(path_1.default.join(home, 'registries.json'), JSON.stringify(registries.map(name => ({ name, remote: `https://example.test/${name}.git` }))));
390
+ for (const name of registries) {
391
+ const packDir = path_1.default.join(home, 'registries', name, 'sensor-packs', 'js-ts');
392
+ fs_1.default.mkdirSync(packDir, { recursive: true });
393
+ fs_1.default.writeFileSync(path_1.default.join(packDir, 'pack.json'), JSON.stringify(pack));
394
+ }
395
+ fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.git'));
396
+ fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.awm'), { recursive: true });
397
+ fs_1.default.writeFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), JSON.stringify({ schemaVersion: 2, pack: 'js-ts', sensors: {} }));
398
+ await expect((0, status_1.computeSensorStatus)(tmpDir)).resolves.toMatchObject({
399
+ overall: 'DEGRADED', mode: 'source-ambiguous', reason: 'multiple-compatible-registries',
400
+ remedy: 'configure-one-logical-registry', candidates: ['alpha', 'beta'],
401
+ });
402
+ }
403
+ finally {
404
+ if (previousHome === undefined)
405
+ delete process.env.AWM_HOME;
406
+ else
407
+ process.env.AWM_HOME = previousHome;
408
+ fs_1.default.rmSync(home, { recursive: true, force: true });
409
+ }
410
+ });
307
411
  });
@@ -135,7 +135,9 @@ describe('runInitSteps — orchestrator', () => {
135
135
  const deps = buildDeps(root);
136
136
  const { runInitSteps } = require('../../../src/core/init/orchestrator');
137
137
  const out = await runInitSteps(deps);
138
- expect(out.steps.some((s) => s.id === 'project.sensors' && s.action === 'applied')).toBe(true);
138
+ // The sensor step is always represented; whether this fixture's pack is
139
+ // portable is orthogonal to activation/constitution orchestration.
140
+ expect(out.steps.some((s) => s.id === 'project.sensors')).toBe(true);
139
141
  expect(out.steps.find((s) => s.id === 'project.constitution').action).toBe('pending');
140
142
  expect(out.steps.find((s) => s.id === 'project.context').action).toBe('pending');
141
143
  });