agentic-workflow-manager 2.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 (145) hide show
  1. package/README.md +62 -0
  2. package/dist/src/commands/doctor.js +77 -0
  3. package/dist/src/commands/hooks/index.js +118 -0
  4. package/dist/src/commands/hooks/install.js +113 -0
  5. package/dist/src/commands/hooks/resync.js +50 -0
  6. package/dist/src/commands/hooks/status.js +74 -0
  7. package/dist/src/commands/hooks/uninstall.js +59 -0
  8. package/dist/src/commands/init.js +181 -0
  9. package/dist/src/commands/ledger/index.js +73 -0
  10. package/dist/src/commands/pin.js +75 -0
  11. package/dist/src/commands/registry/add.js +62 -0
  12. package/dist/src/commands/registry/index.js +166 -0
  13. package/dist/src/commands/registry/install-bundles.js +36 -0
  14. package/dist/src/commands/registry/remove.js +18 -0
  15. package/dist/src/commands/registry/status.js +31 -0
  16. package/dist/src/commands/sensors/baseline.js +93 -0
  17. package/dist/src/commands/sensors/formatters/eslint.js +29 -0
  18. package/dist/src/commands/sensors/formatters/generic.js +8 -0
  19. package/dist/src/commands/sensors/formatters/semgrep.js +18 -0
  20. package/dist/src/commands/sensors/formatters/test.js +12 -0
  21. package/dist/src/commands/sensors/formatters/tsc.js +23 -0
  22. package/dist/src/commands/sensors/index.js +94 -0
  23. package/dist/src/commands/sensors/init.js +112 -0
  24. package/dist/src/commands/sensors/install.js +78 -0
  25. package/dist/src/commands/sensors/run.js +217 -0
  26. package/dist/src/commands/sensors/status.js +85 -0
  27. package/dist/src/commands/sensors/types.js +2 -0
  28. package/dist/src/core/bundle-install.js +116 -0
  29. package/dist/src/core/bundles.js +122 -0
  30. package/dist/src/core/cli-version.js +30 -0
  31. package/dist/src/core/context/materializer.js +30 -0
  32. package/dist/src/core/context/orchestrator.js +75 -0
  33. package/dist/src/core/context/project-constitution-inject.js +47 -0
  34. package/dist/src/core/context/provider.js +29 -0
  35. package/dist/src/core/context/regenerate.js +61 -0
  36. package/dist/src/core/context/strategies/config-instructions.js +73 -0
  37. package/dist/src/core/context/strategies/hook-merge.js +23 -0
  38. package/dist/src/core/context/strategies/strategy.js +2 -0
  39. package/dist/src/core/context/types.js +2 -0
  40. package/dist/src/core/diagnostics/checks.js +141 -0
  41. package/dist/src/core/diagnostics/context.js +181 -0
  42. package/dist/src/core/diagnostics/types.js +2 -0
  43. package/dist/src/core/discovery.js +135 -0
  44. package/dist/src/core/executor.js +41 -0
  45. package/dist/src/core/init/detector.js +67 -0
  46. package/dist/src/core/init/orchestrator.js +54 -0
  47. package/dist/src/core/init/steps.js +279 -0
  48. package/dist/src/core/init/types.js +2 -0
  49. package/dist/src/core/ledger/store.js +73 -0
  50. package/dist/src/core/ledger/types.js +2 -0
  51. package/dist/src/core/miro.js +314 -0
  52. package/dist/src/core/profile-pins.js +36 -0
  53. package/dist/src/core/profile.js +146 -0
  54. package/dist/src/core/registries.js +205 -0
  55. package/dist/src/core/registry.js +28 -0
  56. package/dist/src/core/skill-integrity.js +97 -0
  57. package/dist/src/core/story-map-parser.js +83 -0
  58. package/dist/src/core/update-check-worker.js +10 -0
  59. package/dist/src/core/update-check.js +106 -0
  60. package/dist/src/core/versioning.js +112 -0
  61. package/dist/src/index.js +689 -0
  62. package/dist/src/providers/index.js +63 -0
  63. package/dist/src/utils/config.js +35 -0
  64. package/dist/src/utils/grouping.js +51 -0
  65. package/dist/src/utils/registry-view.js +154 -0
  66. package/dist/tests/commands/doctor.test.js +98 -0
  67. package/dist/tests/commands/hooks/install.test.js +149 -0
  68. package/dist/tests/commands/hooks/resync.test.js +135 -0
  69. package/dist/tests/commands/hooks/router.test.js +26 -0
  70. package/dist/tests/commands/hooks/status.test.js +88 -0
  71. package/dist/tests/commands/hooks/uninstall.test.js +104 -0
  72. package/dist/tests/commands/init.test.js +87 -0
  73. package/dist/tests/commands/ledger/index.test.js +64 -0
  74. package/dist/tests/commands/pin.test.js +72 -0
  75. package/dist/tests/commands/registry/add.test.js +223 -0
  76. package/dist/tests/commands/registry/install-bundles.test.js +87 -0
  77. package/dist/tests/commands/registry/remove.test.js +49 -0
  78. package/dist/tests/commands/registry/status.test.js +43 -0
  79. package/dist/tests/commands/sensors/baseline.test.js +106 -0
  80. package/dist/tests/commands/sensors/formatters/eslint.test.js +32 -0
  81. package/dist/tests/commands/sensors/formatters/semgrep.test.js +38 -0
  82. package/dist/tests/commands/sensors/formatters/tsc.test.js +25 -0
  83. package/dist/tests/commands/sensors/index.test.js +18 -0
  84. package/dist/tests/commands/sensors/init.test.js +137 -0
  85. package/dist/tests/commands/sensors/install.test.js +60 -0
  86. package/dist/tests/commands/sensors/router.test.js +23 -0
  87. package/dist/tests/commands/sensors/run.test.js +353 -0
  88. package/dist/tests/commands/sensors/status.test.js +98 -0
  89. package/dist/tests/core/base-remote.test.js +70 -0
  90. package/dist/tests/core/bundle-install.test.js +198 -0
  91. package/dist/tests/core/bundles-multiroot.test.js +68 -0
  92. package/dist/tests/core/bundles-overrides.test.js +49 -0
  93. package/dist/tests/core/bundles.test.js +96 -0
  94. package/dist/tests/core/cli-version.test.js +17 -0
  95. package/dist/tests/core/context/materializer.test.js +46 -0
  96. package/dist/tests/core/context/orchestrator.test.js +127 -0
  97. package/dist/tests/core/context/project-constitution-inject.test.js +82 -0
  98. package/dist/tests/core/context/provider.test.js +45 -0
  99. package/dist/tests/core/context/regenerate.test.js +106 -0
  100. package/dist/tests/core/context/strategies/config-instructions.test.js +88 -0
  101. package/dist/tests/core/context/strategies/hook-merge.test.js +71 -0
  102. package/dist/tests/core/context/types.test.js +15 -0
  103. package/dist/tests/core/diagnostics/checks.test.js +208 -0
  104. package/dist/tests/core/diagnostics/context.test.js +170 -0
  105. package/dist/tests/core/discovery-multiroot.test.js +63 -0
  106. package/dist/tests/core/discovery-overrides.test.js +105 -0
  107. package/dist/tests/core/discovery.test.js +113 -0
  108. package/dist/tests/core/executor.test.js +44 -0
  109. package/dist/tests/core/init/detector.test.js +56 -0
  110. package/dist/tests/core/init/orchestrator.test.js +122 -0
  111. package/dist/tests/core/init/steps.test.js +363 -0
  112. package/dist/tests/core/ledger/gitignore.test.js +13 -0
  113. package/dist/tests/core/ledger/store.test.js +122 -0
  114. package/dist/tests/core/miro-layout.test.js +150 -0
  115. package/dist/tests/core/profile-pins.test.js +131 -0
  116. package/dist/tests/core/profile-registries.test.js +59 -0
  117. package/dist/tests/core/profile.test.js +110 -0
  118. package/dist/tests/core/registries-capability.test.js +52 -0
  119. package/dist/tests/core/registries-seed.test.js +66 -0
  120. package/dist/tests/core/registries-sync.test.js +205 -0
  121. package/dist/tests/core/registries.test.js +91 -0
  122. package/dist/tests/core/registry-manifest.test.js +95 -0
  123. package/dist/tests/core/registry-versioned-sync.test.js +113 -0
  124. package/dist/tests/core/registry.test.js +51 -0
  125. package/dist/tests/core/skill-integrity.test.js +126 -0
  126. package/dist/tests/core/story-map-parser.test.js +136 -0
  127. package/dist/tests/core/sync-gates.test.js +97 -0
  128. package/dist/tests/core/update-check.test.js +106 -0
  129. package/dist/tests/core/versioning.test.js +169 -0
  130. package/dist/tests/integration/pack-e2e.test.js +50 -0
  131. package/dist/tests/providers/hooks-config.test.js +45 -0
  132. package/dist/tests/providers/index.test.js +58 -0
  133. package/dist/tests/providers/injection-config.test.js +25 -0
  134. package/dist/tests/registry/b3-ledger-wiring.test.js +74 -0
  135. package/dist/tests/registry/catalog-consistency.test.js +47 -0
  136. package/dist/tests/registry/design-skills.test.js +146 -0
  137. package/dist/tests/registry/prose-agnostic.test.js +18 -0
  138. package/dist/tests/registry/sensor-packs.test.js +45 -0
  139. package/dist/tests/registry/skill-versions.test.js +26 -0
  140. package/dist/tests/registry/using-awm.test.js +39 -0
  141. package/dist/tests/utils/config.test.js +31 -0
  142. package/dist/tests/utils/grouping.test.js +43 -0
  143. package/dist/tests/utils/registry-view-overrides.test.js +41 -0
  144. package/dist/tests/utils/registry-view.test.js +156 -0
  145. package/package.json +49 -0
@@ -0,0 +1,137 @@
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
+ const fs_1 = __importDefault(require("fs"));
7
+ const path_1 = __importDefault(require("path"));
8
+ const os_1 = __importDefault(require("os"));
9
+ const init_1 = require("../../../src/commands/sensors/init");
10
+ // Build a throwaway registry with a js-ts pack.json (the single source of truth
11
+ // init now reads from). `defaultCmd` uses the {{SOURCE_DIRS}} placeholder for depcheck.
12
+ function makeRegistry() {
13
+ const registryRoot = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-reg-'));
14
+ const packDir = path_1.default.join(registryRoot, 'sensor-packs', 'js-ts');
15
+ fs_1.default.mkdirSync(packDir, { recursive: true });
16
+ fs_1.default.writeFileSync(path_1.default.join(packDir, 'pack.json'), JSON.stringify({
17
+ name: 'js-ts',
18
+ sensors: {
19
+ typecheck: { fast: true, defaultCmd: 'npx tsc --noEmit' },
20
+ lint: { fast: true, defaultCmd: 'npx eslint . --config eslint.config.awm.mjs --cache --format json' },
21
+ depcheck: { fast: false, defaultCmd: 'npx depcruise --config .dep-cruiser.awm.js {{SOURCE_DIRS}}' },
22
+ mutation: { fast: false, enabled: false, defaultCmd: 'npx stryker run' },
23
+ },
24
+ }));
25
+ return registryRoot;
26
+ }
27
+ describe('detectStack', () => {
28
+ let tmpDir;
29
+ beforeEach(() => { tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-init-')); });
30
+ afterEach(() => { fs_1.default.rmSync(tmpDir, { recursive: true }); });
31
+ it('detects js-ts when package.json exists', () => {
32
+ fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), '{}');
33
+ expect((0, init_1.detectStack)(tmpDir).pack).toBe('js-ts');
34
+ });
35
+ it('detects python when pyproject.toml exists', () => {
36
+ fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'pyproject.toml'), '');
37
+ expect((0, init_1.detectStack)(tmpDir).pack).toBe('python');
38
+ });
39
+ it('falls back to generic when no indicators found', () => {
40
+ expect((0, init_1.detectStack)(tmpDir).pack).toBe('generic');
41
+ });
42
+ });
43
+ describe('detectSourceDirs', () => {
44
+ let tmpDir;
45
+ beforeEach(() => { tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-src-')); });
46
+ afterEach(() => { fs_1.default.rmSync(tmpDir, { recursive: true }); });
47
+ it('returns the App-Router-style dirs that exist', () => {
48
+ for (const d of ['app', 'lib', 'components'])
49
+ fs_1.default.mkdirSync(path_1.default.join(tmpDir, d));
50
+ expect((0, init_1.detectSourceDirs)(tmpDir)).toEqual(['app', 'lib', 'components']);
51
+ });
52
+ it('returns src when it exists', () => {
53
+ fs_1.default.mkdirSync(path_1.default.join(tmpDir, 'src'));
54
+ expect((0, init_1.detectSourceDirs)(tmpDir)).toEqual(['src']);
55
+ });
56
+ it('falls back to ["src"] when no known source dir exists', () => {
57
+ expect((0, init_1.detectSourceDirs)(tmpDir)).toEqual(['src']);
58
+ });
59
+ });
60
+ describe('buildManifest', () => {
61
+ let registryRoot;
62
+ let cwd;
63
+ beforeEach(() => {
64
+ registryRoot = makeRegistry();
65
+ cwd = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-cwd-'));
66
+ });
67
+ afterEach(() => {
68
+ fs_1.default.rmSync(registryRoot, { recursive: true });
69
+ fs_1.default.rmSync(cwd, { recursive: true });
70
+ });
71
+ it('builds manifest from the pack.json single source', () => {
72
+ const m = (0, init_1.buildManifest)('js-ts', undefined, registryRoot, cwd);
73
+ expect(m.pack).toBe('js-ts');
74
+ expect(m.sensors.typecheck.cmd).toBe('npx tsc --noEmit');
75
+ expect(m.sensors.lint.cmd).toContain('--config eslint.config.awm.mjs');
76
+ });
77
+ it('substitutes {{SOURCE_DIRS}} in depcheck with the detected dirs', () => {
78
+ for (const d of ['app', 'lib'])
79
+ fs_1.default.mkdirSync(path_1.default.join(cwd, d));
80
+ const m = (0, init_1.buildManifest)('js-ts', undefined, registryRoot, cwd);
81
+ expect(m.sensors.depcheck.cmd).toBe('npx depcruise --config .dep-cruiser.awm.js app lib');
82
+ expect(m.sensors.depcheck.cmd).not.toContain('{{SOURCE_DIRS}}');
83
+ });
84
+ it('merges conservatively — existing sensor commands are preserved', () => {
85
+ const existing = { pack: 'js-ts', sensors: { typecheck: { cmd: 'custom-tsc', fast: true } } };
86
+ const m = (0, init_1.buildManifest)('js-ts', existing, registryRoot, cwd);
87
+ expect(m.sensors.typecheck.cmd).toBe('custom-tsc');
88
+ expect(m.sensors.lint).toBeDefined();
89
+ });
90
+ it('uses the python fallback when the pack has no pack.json', () => {
91
+ const m = (0, init_1.buildManifest)('python', undefined, registryRoot, cwd);
92
+ expect(m.sensors.typecheck.cmd).toBe('mypy .');
93
+ });
94
+ });
95
+ describe('initSensors', () => {
96
+ let tmpDir;
97
+ let registryRoot;
98
+ beforeEach(() => {
99
+ tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-init-'));
100
+ registryRoot = makeRegistry();
101
+ });
102
+ afterEach(() => {
103
+ fs_1.default.rmSync(tmpDir, { recursive: true });
104
+ fs_1.default.rmSync(registryRoot, { recursive: true });
105
+ });
106
+ it('creates .awm/sensors.json for js-ts project', () => {
107
+ fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), '{}');
108
+ const result = (0, init_1.initSensors)({ cwd: tmpDir, registryRoot });
109
+ expect(fs_1.default.existsSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'))).toBe(true);
110
+ expect(result.detection.pack).toBe('js-ts');
111
+ expect(result.manifest.sensors.typecheck).toBeDefined();
112
+ });
113
+ it('is idempotent — existing sensor commands survive re-init', () => {
114
+ fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), '{}');
115
+ fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.awm'), { recursive: true });
116
+ const existing = { pack: 'js-ts', sensors: { typecheck: { cmd: 'my-tsc', fast: true } } };
117
+ fs_1.default.writeFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), JSON.stringify(existing));
118
+ (0, init_1.initSensors)({ cwd: tmpDir, registryRoot });
119
+ const written = JSON.parse(fs_1.default.readFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), 'utf-8'));
120
+ expect(written.sensors.typecheck.cmd).toBe('my-tsc');
121
+ expect(written.sensors.lint).toBeDefined(); // new sensor added
122
+ });
123
+ it('copies pack config files into the repo by default (configure on)', () => {
124
+ fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), '{}');
125
+ fs_1.default.writeFileSync(path_1.default.join(registryRoot, 'sensor-packs', 'js-ts', 'tsconfig.awm.json'), '{}');
126
+ const result = (0, init_1.initSensors)({ cwd: tmpDir, registryRoot }); // no explicit configure → default true
127
+ expect(result.configured).toContain('tsconfig.awm.json');
128
+ expect(fs_1.default.existsSync(path_1.default.join(tmpDir, 'tsconfig.awm.json'))).toBe(true);
129
+ });
130
+ it('does NOT copy config files when configure is false', () => {
131
+ fs_1.default.writeFileSync(path_1.default.join(tmpDir, 'package.json'), '{}');
132
+ fs_1.default.writeFileSync(path_1.default.join(registryRoot, 'sensor-packs', 'js-ts', 'tsconfig.awm.json'), '{}');
133
+ const result = (0, init_1.initSensors)({ cwd: tmpDir, registryRoot, configure: false });
134
+ expect(result.configured).toEqual([]);
135
+ expect(fs_1.default.existsSync(path_1.default.join(tmpDir, 'tsconfig.awm.json'))).toBe(false);
136
+ });
137
+ });
@@ -0,0 +1,60 @@
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
+ const fs_1 = __importDefault(require("fs"));
7
+ const path_1 = __importDefault(require("path"));
8
+ const os_1 = __importDefault(require("os"));
9
+ describe('sensor hook install/uninstall', () => {
10
+ let tmpDir;
11
+ let settingsPath;
12
+ beforeEach(() => {
13
+ tmpDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-install-'));
14
+ settingsPath = path_1.default.join(tmpDir, 'settings.json');
15
+ jest.resetModules();
16
+ });
17
+ afterEach(() => { fs_1.default.rmSync(tmpDir, { recursive: true }); });
18
+ const load = () => require('../../../src/commands/sensors/install');
19
+ it('installs PostToolUse hook into fresh settings.json', () => {
20
+ const { installSensorHook } = load();
21
+ const result = installSensorHook(settingsPath);
22
+ expect(result.status).toBe('installed');
23
+ const settings = JSON.parse(fs_1.default.readFileSync(settingsPath, 'utf-8'));
24
+ expect(settings.hooks.PostToolUse).toHaveLength(1);
25
+ expect(settings.hooks.PostToolUse[0].matcher).toBe('Write|Edit|MultiEdit');
26
+ expect(settings.hooks.PostToolUse[0].hooks[0].command).toBe('awm sensors run --fast');
27
+ });
28
+ it('merges with existing hooks — SessionStart entries preserved', () => {
29
+ const existing = { hooks: { SessionStart: [{ matcher: 'startup', hooks: [] }] } };
30
+ fs_1.default.writeFileSync(settingsPath, JSON.stringify(existing));
31
+ const { installSensorHook } = load();
32
+ installSensorHook(settingsPath);
33
+ const settings = JSON.parse(fs_1.default.readFileSync(settingsPath, 'utf-8'));
34
+ expect(settings.hooks.SessionStart).toHaveLength(1);
35
+ expect(settings.hooks.PostToolUse).toHaveLength(1);
36
+ });
37
+ it('is idempotent — second install returns already-installed', () => {
38
+ const { installSensorHook } = load();
39
+ installSensorHook(settingsPath);
40
+ const result2 = installSensorHook(settingsPath);
41
+ expect(result2.status).toBe('already-installed');
42
+ const settings = JSON.parse(fs_1.default.readFileSync(settingsPath, 'utf-8'));
43
+ expect(settings.hooks.PostToolUse).toHaveLength(1);
44
+ });
45
+ it('creates a backup before modifying settings.json', () => {
46
+ fs_1.default.writeFileSync(settingsPath, JSON.stringify({ hooks: {} }));
47
+ const { installSensorHook } = load();
48
+ const result = installSensorHook(settingsPath);
49
+ expect(result.backupPath).toBeDefined();
50
+ expect(fs_1.default.existsSync(result.backupPath)).toBe(true);
51
+ });
52
+ it('uninstall removes only the AWM sensor PostToolUse entry', () => {
53
+ const { installSensorHook, uninstallSensorHook } = load();
54
+ installSensorHook(settingsPath);
55
+ const result = uninstallSensorHook(settingsPath);
56
+ expect(result.status).toBe('removed');
57
+ const settings = JSON.parse(fs_1.default.readFileSync(settingsPath, 'utf-8'));
58
+ expect(settings.hooks?.PostToolUse ?? []).toHaveLength(0);
59
+ });
60
+ });
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ jest.mock('@clack/prompts', () => ({ log: { success: jest.fn(), info: jest.fn() } }));
4
+ jest.mock('picocolors', () => ({ green: (s) => s, yellow: (s) => s, red: (s) => s }));
5
+ jest.mock('../../../src/commands/sensors/run', () => ({ runSensors: jest.fn().mockReturnValue({ sensors: [], overall: 'pass' }) }));
6
+ jest.mock('../../../src/commands/sensors/init', () => ({ initSensors: jest.fn().mockReturnValue({ detection: { pack: 'js-ts', indicators: [] }, manifest: { sensors: {} }, configured: [] }) }));
7
+ jest.mock('../../../src/commands/sensors/status', () => ({ computeSensorStatus: jest.fn().mockReturnValue({ overall: 'HEALTHY', pack: 'js-ts', checks: {} }) }));
8
+ jest.mock('../../../src/commands/sensors/install', () => ({ installSensorHook: jest.fn().mockReturnValue({ status: 'installed' }) }));
9
+ const commander_1 = require("commander");
10
+ const index_1 = require("../../../src/commands/sensors/index");
11
+ describe('registerSensorsCommand', () => {
12
+ it('registers sensors command with 4 subcommands', () => {
13
+ const program = new commander_1.Command();
14
+ (0, index_1.registerSensorsCommand)(program);
15
+ const cmd = program.commands.find(c => c.name() === 'sensors');
16
+ expect(cmd).toBeDefined();
17
+ const subNames = cmd.commands.map(c => c.name());
18
+ expect(subNames).toContain('run');
19
+ expect(subNames).toContain('init');
20
+ expect(subNames).toContain('status');
21
+ expect(subNames).toContain('install');
22
+ });
23
+ });
@@ -0,0 +1,353 @@
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
+ const fs_1 = __importDefault(require("fs"));
7
+ const os_1 = __importDefault(require("os"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const run_1 = require("../../../src/commands/sensors/run");
10
+ function mkTmp() {
11
+ return fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-sensors-'));
12
+ }
13
+ // Define stable mock before jest.mock hoisting
14
+ const mockExecSyncFn = jest.fn();
15
+ jest.mock('child_process', () => ({
16
+ execSync: (...args) => mockExecSyncFn(...args),
17
+ }));
18
+ const MANIFEST = {
19
+ pack: 'js-ts',
20
+ sensors: {
21
+ typecheck: { cmd: 'npx tsc --noEmit', fast: true },
22
+ lint: { cmd: 'npx eslint . --format json', fast: true },
23
+ security: { cmd: 'semgrep .', fast: false, enabled: false },
24
+ mutation: { enabled: false }
25
+ }
26
+ };
27
+ describe('runSensors', () => {
28
+ let tmpDir;
29
+ const path = require('path');
30
+ const os = require('os');
31
+ beforeEach(() => {
32
+ jest.resetModules();
33
+ tmpDir = fs_1.default.mkdtempSync(path.join(os.tmpdir(), 'awm-run-test-'));
34
+ fs_1.default.mkdirSync(path.join(tmpDir, '.awm'), { recursive: true });
35
+ fs_1.default.writeFileSync(path.join(tmpDir, '.awm', 'sensors.json'), JSON.stringify(MANIFEST));
36
+ mockExecSyncFn.mockReset();
37
+ });
38
+ afterEach(() => { fs_1.default.rmSync(tmpDir, { recursive: true }); });
39
+ const load = () => require('../../../src/commands/sensors/run');
40
+ it('returns not_certified output when manifest does not exist', () => {
41
+ const emptyDir = fs_1.default.mkdtempSync(path.join(os.tmpdir(), 'awm-empty-'));
42
+ try {
43
+ const { runSensors } = load();
44
+ const result = runSensors({ fast: true, cwd: emptyDir });
45
+ expect(result.overall).toBe('not_certified');
46
+ expect(result.sensors).toHaveLength(0);
47
+ }
48
+ finally {
49
+ fs_1.default.rmSync(emptyDir, { recursive: true });
50
+ }
51
+ });
52
+ it('runs only fast sensors with --fast flag', () => {
53
+ mockExecSyncFn.mockReturnValue('');
54
+ const { runSensors } = load();
55
+ const result = runSensors({ fast: true, cwd: tmpDir });
56
+ expect(mockExecSyncFn).toHaveBeenCalledTimes(2); // typecheck + lint (security disabled, mutation disabled)
57
+ expect(result.sensors.some((s) => s.name === 'security')).toBe(false);
58
+ expect(result.overall).toBe('pass');
59
+ });
60
+ it('returns fail when a fast sensor has errors', () => {
61
+ mockExecSyncFn
62
+ .mockImplementationOnce(() => { throw Object.assign(new Error(), { stdout: "src/a.ts(1,1): error TS0001: Bad type.", stderr: '', status: 1 }); })
63
+ .mockReturnValueOnce('');
64
+ const { runSensors } = load();
65
+ const result = runSensors({ fast: true, cwd: tmpDir });
66
+ expect(result.overall).toBe('fail');
67
+ const tc = result.sensors.find((s) => s.name === 'typecheck');
68
+ expect(tc.status).toBe('fail');
69
+ expect(tc.errors[0].message).toMatch('SENSOR[typecheck]');
70
+ });
71
+ it('marks sensor as skipped on timeout', () => {
72
+ mockExecSyncFn.mockImplementationOnce(() => { throw Object.assign(new Error('killed'), { code: 'ETIMEDOUT' }); });
73
+ mockExecSyncFn.mockReturnValueOnce('');
74
+ const { runSensors } = load();
75
+ const result = runSensors({ fast: true, cwd: tmpDir });
76
+ const tc = result.sensors.find((s) => s.name === 'typecheck');
77
+ expect(tc.status).toBe('skipped');
78
+ expect(tc.skipReason).toMatch('timeout');
79
+ });
80
+ it('skips disabled sensors', () => {
81
+ mockExecSyncFn.mockReturnValue('');
82
+ const { runSensors } = load();
83
+ const result = runSensors({ all: true, cwd: tmpDir });
84
+ const sec = result.sensors.find((s) => s.name === 'security');
85
+ expect(sec.status).toBe('skipped');
86
+ expect(sec.skipReason).toBe('disabled');
87
+ });
88
+ const tcError = () => { throw Object.assign(new Error(), { stdout: 'src/a.ts(1,1): error TS0001: Bad type.', stderr: '', status: 1 }); };
89
+ it('baseline suppresses accepted findings — sensor passes on no NEW findings', () => {
90
+ const { runSensors } = load();
91
+ const { buildBaseline, writeBaseline } = require('../../../src/commands/sensors/baseline');
92
+ // Run 1 (no baseline): typecheck reports a TS error → fail.
93
+ mockExecSyncFn.mockImplementationOnce(tcError).mockReturnValueOnce('');
94
+ const first = runSensors({ fast: true, cwd: tmpDir });
95
+ expect(first.overall).toBe('fail');
96
+ // Accept the current findings as baseline.
97
+ writeBaseline(tmpDir, buildBaseline(first.sensors.map((s) => ({ name: s.name, errors: s.errors }))));
98
+ // Run 2 (same finding): baseline-suppressed → pass.
99
+ mockExecSyncFn.mockImplementationOnce(tcError).mockReturnValueOnce('');
100
+ const second = runSensors({ fast: true, cwd: tmpDir });
101
+ const tc = second.sensors.find((s) => s.name === 'typecheck');
102
+ expect(tc.status).toBe('pass');
103
+ expect(tc.baselineCount).toBe(1);
104
+ expect(second.overall).toBe('pass');
105
+ });
106
+ it('baseline lets NEW findings through (still fails)', () => {
107
+ const { runSensors } = load();
108
+ const { writeBaseline } = require('../../../src/commands/sensors/baseline');
109
+ writeBaseline(tmpDir, { typecheck: ['some-unrelated-fingerprint'] });
110
+ mockExecSyncFn.mockImplementationOnce(tcError).mockReturnValueOnce('');
111
+ const result = runSensors({ fast: true, cwd: tmpDir });
112
+ const tc = result.sensors.find((s) => s.name === 'typecheck');
113
+ expect(tc.status).toBe('fail');
114
+ expect(result.overall).toBe('fail');
115
+ });
116
+ it('--ignore-baseline reports all findings even when a baseline exists', () => {
117
+ const { runSensors } = load();
118
+ const { buildBaseline, writeBaseline } = require('../../../src/commands/sensors/baseline');
119
+ // First capture + accept the finding.
120
+ mockExecSyncFn.mockImplementationOnce(tcError).mockReturnValueOnce('');
121
+ const first = runSensors({ fast: true, cwd: tmpDir });
122
+ writeBaseline(tmpDir, buildBaseline(first.sensors.map((s) => ({ name: s.name, errors: s.errors }))));
123
+ // With ignoreBaseline, the accepted finding still counts → fail.
124
+ mockExecSyncFn.mockImplementationOnce(tcError).mockReturnValueOnce('');
125
+ const result = runSensors({ fast: true, cwd: tmpDir, ignoreBaseline: true });
126
+ expect(result.overall).toBe('fail');
127
+ });
128
+ });
129
+ describe('runSensors — missing tool is a fail, not a skip', () => {
130
+ let root;
131
+ afterEach(() => {
132
+ if (root)
133
+ fs_1.default.rmSync(root, { recursive: true, force: true });
134
+ });
135
+ beforeEach(() => {
136
+ mockExecSyncFn.mockReset();
137
+ // Simulate shell exit 127 "command not found" — matches what Node's execSync
138
+ // captures in err.stderr when the binary does not exist on PATH.
139
+ mockExecSyncFn.mockImplementation(() => {
140
+ throw Object.assign(new Error('Command failed: awm-nonexistent-binary-xyz --check'), {
141
+ stdout: '',
142
+ stderr: '/bin/sh: awm-nonexistent-binary-xyz: command not found\n',
143
+ status: 127,
144
+ });
145
+ });
146
+ });
147
+ it('marks a sensor whose binary is missing as fail', () => {
148
+ root = mkTmp();
149
+ fs_1.default.mkdirSync(path_1.default.join(root, '.awm'));
150
+ fs_1.default.writeFileSync(path_1.default.join(root, '.awm', 'sensors.json'), JSON.stringify({
151
+ pack: 'test',
152
+ sensors: { ghost: { cmd: 'awm-nonexistent-binary-xyz --check', fast: true } },
153
+ }));
154
+ const out = (0, run_1.runSensors)({ cwd: root });
155
+ const ghost = out.sensors.find((s) => s.name === 'ghost');
156
+ expect(ghost?.status).toBe('fail');
157
+ expect(out.overall).toBe('fail');
158
+ });
159
+ });
160
+ describe('runSensors — not_certified + auto-discovery', () => {
161
+ let tmpDir;
162
+ beforeEach(() => {
163
+ mockExecSyncFn.mockReset();
164
+ mockExecSyncFn.mockReturnValue('');
165
+ });
166
+ afterEach(() => {
167
+ if (tmpDir) {
168
+ fs_1.default.rmSync(tmpDir, { recursive: true, force: true });
169
+ tmpDir = undefined;
170
+ }
171
+ });
172
+ it('returns not_certified when no manifest exists anywhere up the tree', () => {
173
+ tmpDir = mkTmp();
174
+ const out = (0, run_1.runSensors)({ cwd: tmpDir });
175
+ expect(out.overall).toBe('not_certified');
176
+ expect(out.sensors).toEqual([]);
177
+ });
178
+ it('discovers .awm/sensors.json in a parent directory (walk-up)', () => {
179
+ tmpDir = mkTmp();
180
+ fs_1.default.mkdirSync(path_1.default.join(tmpDir, '.awm'));
181
+ fs_1.default.writeFileSync(path_1.default.join(tmpDir, '.awm', 'sensors.json'), JSON.stringify({ pack: 'test', sensors: { noop: { cmd: 'echo ok', fast: true } } }));
182
+ const nested = path_1.default.join(tmpDir, 'a', 'b');
183
+ fs_1.default.mkdirSync(nested, { recursive: true });
184
+ const out = (0, run_1.runSensors)({ cwd: nested });
185
+ expect(out.overall).toBe('pass');
186
+ expect(out.sensors.length).toBe(1);
187
+ });
188
+ });
189
+ describe('reconcilePack', () => {
190
+ // Build a minimal fake registry in a tmpdir (registry/ no longer lives in this repo —
191
+ // content lives in awm-baseline-registry / awm-documentation-registry).
192
+ let FAKE_REGISTRY;
193
+ beforeAll(() => {
194
+ FAKE_REGISTRY = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-fake-reg-'));
195
+ const jstsPackDir = path_1.default.join(FAKE_REGISTRY, 'sensor-packs', 'js-ts');
196
+ const genericPackDir = path_1.default.join(FAKE_REGISTRY, 'sensor-packs', 'generic');
197
+ fs_1.default.mkdirSync(jstsPackDir, { recursive: true });
198
+ fs_1.default.mkdirSync(genericPackDir, { recursive: true });
199
+ // Minimal pack.json matching what the real js-ts pack ships
200
+ fs_1.default.writeFileSync(path_1.default.join(jstsPackDir, 'pack.json'), JSON.stringify({
201
+ name: 'js-ts',
202
+ description: 'JavaScript/TypeScript sensor pack (test fixture)',
203
+ sensors: {
204
+ typecheck: { defaultCmd: 'npx tsc --noEmit', fast: true },
205
+ lint: { defaultCmd: 'npx eslint . --format json', fast: true },
206
+ security: { defaultCmd: 'semgrep --config .semgrep.awm.yml --json .', fast: false },
207
+ mutation: { enabled: false },
208
+ },
209
+ }));
210
+ fs_1.default.writeFileSync(path_1.default.join(genericPackDir, 'pack.json'), JSON.stringify({
211
+ name: 'generic',
212
+ description: 'Generic sensor pack (test fixture)',
213
+ sensors: {
214
+ security: { defaultCmd: 'semgrep .', fast: false },
215
+ },
216
+ }));
217
+ });
218
+ afterAll(() => { fs_1.default.rmSync(FAKE_REGISTRY, { recursive: true, force: true }); });
219
+ function tmpProject(pack, withPackageJson) {
220
+ const dir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-reconcile-'));
221
+ fs_1.default.mkdirSync(path_1.default.join(dir, '.awm'), { recursive: true });
222
+ fs_1.default.writeFileSync(path_1.default.join(dir, '.awm', 'sensors.json'), JSON.stringify({ pack, sensors: pack === 'generic'
223
+ ? { security: { cmd: 'semgrep .', fast: false } }
224
+ : {} }));
225
+ if (withPackageJson)
226
+ fs_1.default.writeFileSync(path_1.default.join(dir, 'package.json'), '{}');
227
+ return dir;
228
+ }
229
+ it('upgrades generic→js-ts when package.json is present', () => {
230
+ const { reconcilePack } = require('../../../src/commands/sensors/run');
231
+ const dir = tmpProject('generic', true);
232
+ try {
233
+ const manifest = JSON.parse(fs_1.default.readFileSync(path_1.default.join(dir, '.awm', 'sensors.json'), 'utf-8'));
234
+ const res = reconcilePack(dir, manifest, FAKE_REGISTRY);
235
+ expect(res.manifest.pack).toBe('js-ts');
236
+ expect(res.upgradedFrom).toBe('generic');
237
+ expect(Object.keys(res.manifest.sensors)).toContain('typecheck');
238
+ // custom sensor cmd preserved through merge
239
+ expect(res.manifest.sensors.security?.cmd).toBe('semgrep .');
240
+ // persisted to disk
241
+ const onDisk = JSON.parse(fs_1.default.readFileSync(path_1.default.join(dir, '.awm', 'sensors.json'), 'utf-8'));
242
+ expect(onDisk.pack).toBe('js-ts');
243
+ }
244
+ finally {
245
+ fs_1.default.rmSync(dir, { recursive: true });
246
+ }
247
+ });
248
+ it('is a no-op when pack is already real (idempotent)', () => {
249
+ const { reconcilePack } = require('../../../src/commands/sensors/run');
250
+ const dir = tmpProject('js-ts', true);
251
+ try {
252
+ const manifest = JSON.parse(fs_1.default.readFileSync(path_1.default.join(dir, '.awm', 'sensors.json'), 'utf-8'));
253
+ const res = reconcilePack(dir, manifest, FAKE_REGISTRY);
254
+ expect(res.manifest.pack).toBe('js-ts');
255
+ expect(res.upgradedFrom).toBeUndefined();
256
+ }
257
+ finally {
258
+ fs_1.default.rmSync(dir, { recursive: true });
259
+ }
260
+ });
261
+ it('does not upgrade a truly generic project (no indicators)', () => {
262
+ const { reconcilePack } = require('../../../src/commands/sensors/run');
263
+ const dir = tmpProject('generic', false);
264
+ try {
265
+ const manifest = JSON.parse(fs_1.default.readFileSync(path_1.default.join(dir, '.awm', 'sensors.json'), 'utf-8'));
266
+ const res = reconcilePack(dir, manifest, FAKE_REGISTRY);
267
+ expect(res.manifest.pack).toBe('generic');
268
+ expect(res.upgradedFrom).toBeUndefined();
269
+ }
270
+ finally {
271
+ fs_1.default.rmSync(dir, { recursive: true });
272
+ }
273
+ });
274
+ });
275
+ describe('runSensors — test sensor (exit-code)', () => {
276
+ let tmpDir;
277
+ const path = require('path');
278
+ const os = require('os');
279
+ beforeEach(() => {
280
+ jest.resetModules();
281
+ tmpDir = fs_1.default.mkdtempSync(path.join(os.tmpdir(), 'awm-run-test-'));
282
+ fs_1.default.mkdirSync(path.join(tmpDir, '.awm'), { recursive: true });
283
+ mockExecSyncFn.mockReset();
284
+ });
285
+ afterEach(() => { fs_1.default.rmSync(tmpDir, { recursive: true }); });
286
+ const load = () => require('../../../src/commands/sensors/run');
287
+ it('test sensor: passing run (exit 0 with output) is pass, not fail', () => {
288
+ fs_1.default.writeFileSync(path.join(tmpDir, '.awm', 'sensors.json'), JSON.stringify({ pack: 'js-ts', sensors: { test: { cmd: 'npm test', fast: false } } }));
289
+ mockExecSyncFn.mockReturnValue('Tests: 6 passed, 6 total\n'); // runner prints on success
290
+ const { runSensors } = load();
291
+ const result = runSensors({ all: true, cwd: tmpDir });
292
+ const test = result.sensors.find((s) => s.name === 'test');
293
+ expect(test.status).toBe('pass');
294
+ });
295
+ it('test sensor: failing run (non-zero exit) is fail, not skipped', () => {
296
+ fs_1.default.writeFileSync(path.join(tmpDir, '.awm', 'sensors.json'), JSON.stringify({ pack: 'js-ts', sensors: { test: { cmd: 'npm test', fast: false } } }));
297
+ mockExecSyncFn.mockImplementation(() => {
298
+ const err = new Error('jest failed');
299
+ err.status = 1;
300
+ err.stdout = 'Tests: 1 failed, 5 passed\n';
301
+ err.stderr = '';
302
+ throw err;
303
+ });
304
+ const { runSensors } = load();
305
+ const result = runSensors({ all: true, cwd: tmpDir });
306
+ const test = result.sensors.find((s) => s.name === 'test');
307
+ expect(test.status).toBe('fail');
308
+ expect(result.overall).toBe('fail');
309
+ });
310
+ it('test sensor: missing npm test script exits non-zero → fail, not skipped', () => {
311
+ fs_1.default.writeFileSync(path.join(tmpDir, '.awm', 'sensors.json'), JSON.stringify({ pack: 'js-ts', sensors: { test: { cmd: 'npm test', fast: false } } }));
312
+ mockExecSyncFn.mockImplementation(() => {
313
+ const err = new Error('npm test: missing script');
314
+ err.status = 1;
315
+ err.stdout = 'npm error Missing script: test\n';
316
+ err.stderr = '';
317
+ throw err;
318
+ });
319
+ const { runSensors } = load();
320
+ const result = runSensors({ all: true, cwd: tmpDir });
321
+ const test = result.sensors.find((s) => s.name === 'test');
322
+ expect(test.status).toBe('fail');
323
+ expect(result.overall).toBe('fail');
324
+ });
325
+ });
326
+ describe('runSensors — honest floor (not_certified over real stack)', () => {
327
+ const path = require('path');
328
+ const os = require('os');
329
+ beforeEach(() => {
330
+ mockExecSyncFn.mockReset();
331
+ mockExecSyncFn.mockReturnValue('');
332
+ });
333
+ it('returns not_certified (not skipped) for a generic manifest over a real stack', () => {
334
+ const dir = fs_1.default.mkdtempSync(path.join(os.tmpdir(), 'awm-floor-'));
335
+ fs_1.default.mkdirSync(path.join(dir, '.awm'), { recursive: true });
336
+ fs_1.default.writeFileSync(path.join(dir, '.awm', 'sensors.json'), JSON.stringify({ pack: 'generic', sensors: { security: { cmd: 'semgrep .', fast: false } } }));
337
+ fs_1.default.writeFileSync(path.join(dir, 'package.json'), '{}');
338
+ const prevHome = process.env.AWM_HOME;
339
+ const fakeHome = fs_1.default.mkdtempSync(path.join(os.tmpdir(), 'awm-nohome-'));
340
+ process.env.AWM_HOME = fakeHome; // no registry cache → no upgrade
341
+ try {
342
+ jest.resetModules();
343
+ const { runSensors } = require('../../../src/commands/sensors/run');
344
+ const result = runSensors({ fast: true, cwd: dir }); // --fast filters the fast:false security sensor → empty
345
+ expect(result.overall).toBe('not_certified');
346
+ }
347
+ finally {
348
+ process.env.AWM_HOME = prevHome;
349
+ fs_1.default.rmSync(dir, { recursive: true });
350
+ fs_1.default.rmSync(fakeHome, { recursive: true, force: true });
351
+ }
352
+ });
353
+ });