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,135 @@
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('resyncInstalledHooks', () => {
10
+ let tmpHome;
11
+ let tmpRegistry;
12
+ let originalHome;
13
+ let originalAwmHome;
14
+ function writeRegistry(sessionStartContent) {
15
+ const regHooks = path_1.default.join(tmpRegistry, 'hooks');
16
+ const regSkill = path_1.default.join(tmpRegistry, 'skills/using-awm');
17
+ fs_1.default.mkdirSync(regHooks, { recursive: true });
18
+ fs_1.default.mkdirSync(regSkill, { recursive: true });
19
+ fs_1.default.writeFileSync(path_1.default.join(regHooks, 'session-start'), sessionStartContent, { mode: 0o755 });
20
+ fs_1.default.writeFileSync(path_1.default.join(regHooks, 'run-hook.cmd'), '#!/usr/bin/env bash\nexec bash "$1"', { mode: 0o755 });
21
+ fs_1.default.writeFileSync(path_1.default.join(regSkill, 'SKILL.md'), '---\nname: using-awm\n---\nMUST invoke skills.');
22
+ }
23
+ function writeSettingsWithAwmEntry(scriptsDir) {
24
+ const settingsPath = path_1.default.join(tmpHome, '.claude/settings.json');
25
+ fs_1.default.mkdirSync(path_1.default.dirname(settingsPath), { recursive: true });
26
+ fs_1.default.writeFileSync(settingsPath, JSON.stringify({
27
+ hooks: {
28
+ SessionStart: [{
29
+ matcher: 'startup|clear|compact',
30
+ hooks: [{ type: 'command', command: `${path_1.default.join(scriptsDir, 'run-hook.cmd')} session-start`, async: false }]
31
+ }]
32
+ }
33
+ }, null, 2));
34
+ }
35
+ beforeEach(() => {
36
+ tmpHome = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-resync-'));
37
+ tmpRegistry = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-resync-registry-'));
38
+ originalHome = process.env.HOME;
39
+ originalAwmHome = process.env.AWM_HOME;
40
+ process.env.HOME = tmpHome;
41
+ process.env.AWM_HOME = path_1.default.join(tmpHome, '.awm');
42
+ jest.resetModules();
43
+ });
44
+ afterEach(() => {
45
+ fs_1.default.rmSync(tmpHome, { recursive: true, force: true });
46
+ fs_1.default.rmSync(tmpRegistry, { recursive: true, force: true });
47
+ if (originalHome === undefined)
48
+ delete process.env.HOME;
49
+ else
50
+ process.env.HOME = originalHome;
51
+ if (originalAwmHome === undefined)
52
+ delete process.env.AWM_HOME;
53
+ else
54
+ process.env.AWM_HOME = originalAwmHome;
55
+ });
56
+ it('refreshes stale COPIED hook scripts when the settings entry is present', () => {
57
+ writeRegistry('#!/usr/bin/env bash\necho "NEW VERSION"');
58
+ const scriptsDir = path_1.default.join(tmpHome, '.awm/hooks');
59
+ fs_1.default.mkdirSync(scriptsDir, { recursive: true });
60
+ fs_1.default.writeFileSync(path_1.default.join(scriptsDir, 'session-start'), '#!/usr/bin/env bash\necho "OLD VERSION"', { mode: 0o755 });
61
+ fs_1.default.writeFileSync(path_1.default.join(scriptsDir, 'run-hook.cmd'), '#!/usr/bin/env bash\nexec bash "$1"', { mode: 0o755 });
62
+ writeSettingsWithAwmEntry(scriptsDir);
63
+ const { resyncInstalledHooks } = require('../../../src/commands/hooks/resync');
64
+ const results = resyncInstalledHooks(tmpRegistry);
65
+ expect(results).toEqual([{ agent: 'claude-code', action: 'resynced' }]);
66
+ const synced = fs_1.default.readFileSync(path_1.default.join(scriptsDir, 'session-start'), 'utf-8');
67
+ expect(synced).toContain('NEW VERSION');
68
+ expect(fs_1.default.lstatSync(path_1.default.join(scriptsDir, 'session-start')).isSymbolicLink()).toBe(false);
69
+ expect(() => fs_1.default.accessSync(path_1.default.join(scriptsDir, 'session-start'), fs_1.default.constants.X_OK)).not.toThrow();
70
+ expect(fs_1.default.lstatSync(path_1.default.join(scriptsDir, 'using-awm.md')).isSymbolicLink()).toBe(true);
71
+ });
72
+ it('does NOT touch anything when the hook was never installed (no settings entry)', () => {
73
+ writeRegistry('#!/usr/bin/env bash\necho "NEW VERSION"');
74
+ const { resyncInstalledHooks } = require('../../../src/commands/hooks/resync');
75
+ const results = resyncInstalledHooks(tmpRegistry);
76
+ expect(results).toEqual([{ agent: 'claude-code', action: 'not-installed' }]);
77
+ expect(fs_1.default.existsSync(path_1.default.join(tmpHome, '.awm/hooks/session-start'))).toBe(false);
78
+ });
79
+ it('preserves symlink install method', () => {
80
+ writeRegistry('#!/usr/bin/env bash\necho "V2"');
81
+ const scriptsDir = path_1.default.join(tmpHome, '.awm/hooks');
82
+ fs_1.default.mkdirSync(scriptsDir, { recursive: true });
83
+ fs_1.default.symlinkSync(path_1.default.join(tmpRegistry, 'hooks/session-start'), path_1.default.join(scriptsDir, 'session-start'));
84
+ fs_1.default.symlinkSync(path_1.default.join(tmpRegistry, 'hooks/run-hook.cmd'), path_1.default.join(scriptsDir, 'run-hook.cmd'));
85
+ writeSettingsWithAwmEntry(scriptsDir);
86
+ const { resyncInstalledHooks } = require('../../../src/commands/hooks/resync');
87
+ const results = resyncInstalledHooks(tmpRegistry);
88
+ expect(results).toEqual([{ agent: 'claude-code', action: 'resynced' }]);
89
+ expect(fs_1.default.lstatSync(path_1.default.join(scriptsDir, 'session-start')).isSymbolicLink()).toBe(true);
90
+ expect(fs_1.default.lstatSync(path_1.default.join(scriptsDir, 'using-awm.md')).isSymbolicLink()).toBe(true);
91
+ });
92
+ it('skips with registry-missing when the registry has no hooks dir', () => {
93
+ const scriptsDir = path_1.default.join(tmpHome, '.awm/hooks');
94
+ fs_1.default.mkdirSync(scriptsDir, { recursive: true });
95
+ fs_1.default.writeFileSync(path_1.default.join(scriptsDir, 'session-start'), '#!/usr/bin/env bash\necho "OLD"', { mode: 0o755 });
96
+ writeSettingsWithAwmEntry(scriptsDir);
97
+ const { resyncInstalledHooks } = require('../../../src/commands/hooks/resync');
98
+ const results = resyncInstalledHooks(tmpRegistry);
99
+ expect(results).toEqual([{ agent: 'claude-code', action: 'registry-missing' }]);
100
+ expect(fs_1.default.readFileSync(path_1.default.join(scriptsDir, 'session-start'), 'utf-8')).toContain('OLD');
101
+ });
102
+ it('re-creates session-start as copy when scriptsDir exists but session-start is missing', () => {
103
+ writeRegistry('#!/usr/bin/env bash\necho "FRESH"');
104
+ const scriptsDir = path_1.default.join(tmpHome, '.awm/hooks');
105
+ fs_1.default.mkdirSync(scriptsDir, { recursive: true });
106
+ // session-start intentionally absent — only run-hook.cmd exists
107
+ fs_1.default.writeFileSync(path_1.default.join(scriptsDir, 'run-hook.cmd'), '#!/usr/bin/env bash\nexec bash "$1"', { mode: 0o755 });
108
+ writeSettingsWithAwmEntry(scriptsDir);
109
+ const { resyncInstalledHooks } = require('../../../src/commands/hooks/resync');
110
+ const results = resyncInstalledHooks(tmpRegistry);
111
+ expect(results).toEqual([{ agent: 'claude-code', action: 'resynced' }]);
112
+ expect(fs_1.default.existsSync(path_1.default.join(scriptsDir, 'session-start'))).toBe(true);
113
+ // detectInstallMethod fell back to copy since lstatSync threw
114
+ expect(fs_1.default.lstatSync(path_1.default.join(scriptsDir, 'session-start')).isSymbolicLink()).toBe(false);
115
+ });
116
+ it('returns registry-missing when run-hook.cmd is absent from registry', () => {
117
+ // Partial registry: only session-start present, run-hook.cmd absent
118
+ const regHooks = path_1.default.join(tmpRegistry, 'hooks');
119
+ const regSkill = path_1.default.join(tmpRegistry, 'skills/using-awm');
120
+ fs_1.default.mkdirSync(regHooks, { recursive: true });
121
+ fs_1.default.mkdirSync(regSkill, { recursive: true });
122
+ fs_1.default.writeFileSync(path_1.default.join(regHooks, 'session-start'), '#!/usr/bin/env bash\necho "V2"', { mode: 0o755 });
123
+ // run-hook.cmd intentionally NOT written
124
+ fs_1.default.writeFileSync(path_1.default.join(regSkill, 'SKILL.md'), '---\nname: using-awm\n---');
125
+ const scriptsDir = path_1.default.join(tmpHome, '.awm/hooks');
126
+ fs_1.default.mkdirSync(scriptsDir, { recursive: true });
127
+ fs_1.default.writeFileSync(path_1.default.join(scriptsDir, 'session-start'), '#!/usr/bin/env bash\necho "OLD"', { mode: 0o755 });
128
+ writeSettingsWithAwmEntry(scriptsDir);
129
+ const { resyncInstalledHooks } = require('../../../src/commands/hooks/resync');
130
+ const results = resyncInstalledHooks(tmpRegistry);
131
+ expect(results).toEqual([{ agent: 'claude-code', action: 'registry-missing' }]);
132
+ // old script left intact — never leave user without hook
133
+ expect(fs_1.default.readFileSync(path_1.default.join(scriptsDir, 'session-start'), 'utf-8')).toContain('OLD');
134
+ });
135
+ });
@@ -0,0 +1,26 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const commander_1 = require("commander");
4
+ // @clack/prompts ships as ESM; mock it so Jest (CommonJS mode) can load the router
5
+ jest.mock('@clack/prompts', () => ({
6
+ confirm: jest.fn(),
7
+ isCancel: jest.fn(),
8
+ }));
9
+ // Mock heavy dependencies that are not needed to verify command registration
10
+ jest.mock('../../../src/utils/config', () => ({
11
+ getPreferences: jest.fn(() => ({ installMethod: 'symlink', defaultAgent: 'claude-code', defaultScope: 'global' })),
12
+ }));
13
+ jest.mock('../../../src/commands/hooks/install', () => ({ installHook: jest.fn() }));
14
+ jest.mock('../../../src/commands/hooks/uninstall', () => ({ uninstallHook: jest.fn() }));
15
+ jest.mock('../../../src/commands/hooks/status', () => ({ computeHookStatus: jest.fn() }));
16
+ describe('hooks command router', () => {
17
+ it('registers install, uninstall, and status subcommands', () => {
18
+ const program = new commander_1.Command();
19
+ const { registerHooksCommand } = require('../../../src/commands/hooks');
20
+ registerHooksCommand(program);
21
+ const hooks = program.commands.find((c) => c.name() === 'hooks');
22
+ expect(hooks).toBeDefined();
23
+ const subNames = hooks.commands.map((c) => c.name());
24
+ expect(subNames).toEqual(expect.arrayContaining(['install', 'uninstall', 'status']));
25
+ });
26
+ });
@@ -0,0 +1,88 @@
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('computeHookStatus', () => {
10
+ let tmpHome;
11
+ let originalHome;
12
+ let originalAwmHome;
13
+ beforeEach(() => {
14
+ tmpHome = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-status-'));
15
+ originalHome = process.env.HOME;
16
+ originalAwmHome = process.env.AWM_HOME;
17
+ process.env.HOME = tmpHome;
18
+ process.env.AWM_HOME = path_1.default.join(tmpHome, '.awm');
19
+ jest.resetModules();
20
+ });
21
+ afterEach(() => {
22
+ fs_1.default.rmSync(tmpHome, { recursive: true, force: true });
23
+ if (originalHome === undefined)
24
+ delete process.env.HOME;
25
+ else
26
+ process.env.HOME = originalHome;
27
+ if (originalAwmHome === undefined)
28
+ delete process.env.AWM_HOME;
29
+ else
30
+ process.env.AWM_HOME = originalAwmHome;
31
+ });
32
+ function setupInstalledHook(scriptContent = '#!/usr/bin/env bash\necho "{}"') {
33
+ const hooksDir = path_1.default.join(tmpHome, '.awm/hooks');
34
+ fs_1.default.mkdirSync(hooksDir, { recursive: true });
35
+ fs_1.default.writeFileSync(path_1.default.join(hooksDir, 'session-start'), scriptContent, { mode: 0o755 });
36
+ fs_1.default.writeFileSync(path_1.default.join(hooksDir, 'run-hook.cmd'), '#!/usr/bin/env bash\nexec bash "$1"', { mode: 0o755 });
37
+ fs_1.default.writeFileSync(path_1.default.join(hooksDir, 'using-awm.md'), '# using-awm\nMUST invoke skills.\n');
38
+ const claudeDir = path_1.default.join(tmpHome, '.claude');
39
+ fs_1.default.mkdirSync(claudeDir, { recursive: true });
40
+ fs_1.default.writeFileSync(path_1.default.join(claudeDir, 'settings.json'), JSON.stringify({
41
+ hooks: {
42
+ SessionStart: [{
43
+ matcher: 'startup|clear|compact',
44
+ hooks: [{ type: 'command', command: `${hooksDir}/run-hook.cmd session-start`, async: false }]
45
+ }]
46
+ }
47
+ }, null, 2));
48
+ }
49
+ it('reports HEALTHY when everything is in place', () => {
50
+ setupInstalledHook();
51
+ const { computeHookStatus } = require('../../../src/commands/hooks/status');
52
+ const result = computeHookStatus('claude-code');
53
+ expect(result.overall).toBe('HEALTHY');
54
+ expect(result.checks.bootstrapSkill.ok).toBe(true);
55
+ expect(result.checks.sessionStartScript.ok).toBe(true);
56
+ expect(result.checks.runHookWrapper.ok).toBe(true);
57
+ expect(result.checks.settingsEntry.ok).toBe(true);
58
+ });
59
+ it('reports DEGRADED when bootstrap skill is missing', () => {
60
+ setupInstalledHook();
61
+ fs_1.default.unlinkSync(path_1.default.join(tmpHome, '.awm/hooks/using-awm.md'));
62
+ const { computeHookStatus } = require('../../../src/commands/hooks/status');
63
+ const result = computeHookStatus('claude-code');
64
+ expect(result.overall).toBe('DEGRADED');
65
+ expect(result.checks.bootstrapSkill.ok).toBe(false);
66
+ });
67
+ it('reports NOT_INSTALLED when settings.json has no AWM entry', () => {
68
+ setupInstalledHook();
69
+ const claudeDir = path_1.default.join(tmpHome, '.claude');
70
+ fs_1.default.writeFileSync(path_1.default.join(claudeDir, 'settings.json'), JSON.stringify({}, null, 2));
71
+ const { computeHookStatus } = require('../../../src/commands/hooks/status');
72
+ const result = computeHookStatus('claude-code');
73
+ expect(result.overall).toBe('NOT_INSTALLED');
74
+ expect(result.checks.settingsEntry.ok).toBe(false);
75
+ });
76
+ it('reports DEGRADED when script is missing executable bit', () => {
77
+ setupInstalledHook();
78
+ fs_1.default.chmodSync(path_1.default.join(tmpHome, '.awm/hooks/session-start'), 0o644);
79
+ const { computeHookStatus } = require('../../../src/commands/hooks/status');
80
+ const result = computeHookStatus('claude-code');
81
+ expect(result.overall).toBe('DEGRADED');
82
+ expect(result.checks.sessionStartScript.ok).toBe(false);
83
+ });
84
+ it('throws when agent target has no hooks config', () => {
85
+ const { computeHookStatus } = require('../../../src/commands/hooks/status');
86
+ expect(() => computeHookStatus('antigravity')).toThrow(/hooks not supported/i);
87
+ });
88
+ });
@@ -0,0 +1,104 @@
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('uninstallHook', () => {
10
+ let tmpHome;
11
+ let originalHome;
12
+ let originalAwmHome;
13
+ beforeEach(() => {
14
+ tmpHome = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-uninstall-'));
15
+ originalHome = process.env.HOME;
16
+ originalAwmHome = process.env.AWM_HOME;
17
+ process.env.HOME = tmpHome;
18
+ process.env.AWM_HOME = path_1.default.join(tmpHome, '.awm');
19
+ jest.resetModules();
20
+ });
21
+ afterEach(() => {
22
+ fs_1.default.rmSync(tmpHome, { recursive: true, force: true });
23
+ if (originalHome === undefined)
24
+ delete process.env.HOME;
25
+ else
26
+ process.env.HOME = originalHome;
27
+ if (originalAwmHome === undefined)
28
+ delete process.env.AWM_HOME;
29
+ else
30
+ process.env.AWM_HOME = originalAwmHome;
31
+ });
32
+ function writeSettings(content) {
33
+ const claudeDir = path_1.default.join(tmpHome, '.claude');
34
+ fs_1.default.mkdirSync(claudeDir, { recursive: true });
35
+ fs_1.default.writeFileSync(path_1.default.join(claudeDir, 'settings.json'), JSON.stringify(content, null, 2));
36
+ }
37
+ function readSettings() {
38
+ return JSON.parse(fs_1.default.readFileSync(path_1.default.join(tmpHome, '.claude/settings.json'), 'utf-8'));
39
+ }
40
+ it('removes only the AWM entry, preserves other SessionStart entries', () => {
41
+ const scriptsDir = path_1.default.join(tmpHome, '.awm/hooks');
42
+ writeSettings({
43
+ theme: 'dark',
44
+ hooks: {
45
+ SessionStart: [
46
+ { matcher: 'startup', hooks: [{ type: 'command', command: '/other/plugin' }] },
47
+ { matcher: 'startup|clear|compact', hooks: [{ type: 'command', command: `${scriptsDir}/run-hook.cmd session-start`, async: false }] }
48
+ ]
49
+ }
50
+ });
51
+ const { uninstallHook } = require('../../../src/commands/hooks/uninstall');
52
+ const result = uninstallHook({ agent: 'claude-code' });
53
+ expect(result.status).toBe('uninstalled');
54
+ const settings = readSettings();
55
+ expect(settings.theme).toBe('dark');
56
+ expect(settings.hooks.SessionStart).toHaveLength(1);
57
+ expect(settings.hooks.SessionStart[0].hooks[0].command).toBe('/other/plugin');
58
+ });
59
+ it('removes the SessionStart key entirely if AWM was the only entry', () => {
60
+ const scriptsDir = path_1.default.join(tmpHome, '.awm/hooks');
61
+ writeSettings({
62
+ hooks: {
63
+ SessionStart: [
64
+ { matcher: 'startup|clear|compact', hooks: [{ type: 'command', command: `${scriptsDir}/run-hook.cmd session-start`, async: false }] }
65
+ ]
66
+ }
67
+ });
68
+ const { uninstallHook } = require('../../../src/commands/hooks/uninstall');
69
+ uninstallHook({ agent: 'claude-code' });
70
+ const settings = readSettings();
71
+ expect(settings.hooks?.SessionStart).toBeUndefined();
72
+ });
73
+ it('is a no-op when no AWM entry exists', () => {
74
+ writeSettings({
75
+ hooks: {
76
+ SessionStart: [{ matcher: 'startup', hooks: [{ type: 'command', command: '/other/plugin' }] }]
77
+ }
78
+ });
79
+ const { uninstallHook } = require('../../../src/commands/hooks/uninstall');
80
+ const result = uninstallHook({ agent: 'claude-code' });
81
+ expect(result.status).toBe('not-installed');
82
+ const settings = readSettings();
83
+ expect(settings.hooks.SessionStart).toHaveLength(1);
84
+ });
85
+ it('is a no-op when settings.json does not exist', () => {
86
+ const { uninstallHook } = require('../../../src/commands/hooks/uninstall');
87
+ const result = uninstallHook({ agent: 'claude-code' });
88
+ expect(result.status).toBe('not-installed');
89
+ });
90
+ it('creates a backup before modifying', () => {
91
+ const scriptsDir = path_1.default.join(tmpHome, '.awm/hooks');
92
+ writeSettings({
93
+ hooks: {
94
+ SessionStart: [
95
+ { matcher: 'startup|clear|compact', hooks: [{ type: 'command', command: `${scriptsDir}/run-hook.cmd session-start`, async: false }] }
96
+ ]
97
+ }
98
+ });
99
+ const { uninstallHook } = require('../../../src/commands/hooks/uninstall');
100
+ const result = uninstallHook({ agent: 'claude-code' });
101
+ expect(result.backupPath).not.toBeNull();
102
+ expect(fs_1.default.existsSync(result.backupPath)).toBe(true);
103
+ });
104
+ });
@@ -0,0 +1,87 @@
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 init_1 = require("../../src/commands/init");
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const os_1 = __importDefault(require("os"));
9
+ const path_1 = __importDefault(require("path"));
10
+ describe('makeConfirmExtensions (#1 empty-multiselect guard)', () => {
11
+ it('returns [] for empty proposed without invoking clack (non-yes path)', async () => {
12
+ const fn = (0, init_1.makeConfirmExtensions)(false);
13
+ await expect(fn([], [])).resolves.toEqual([]);
14
+ });
15
+ it('auto-confirms all proposed in --yes mode', async () => {
16
+ const fn = (0, init_1.makeConfirmExtensions)(true);
17
+ await expect(fn(['frontend'], ['package.json: next'])).resolves.toEqual(['frontend']);
18
+ });
19
+ });
20
+ function report(over = {}) {
21
+ return {
22
+ results: [{ id: 'machine.cli', level: 'machine', label: 'CLI v1.0.0', status: 'ok', remedy: { kind: 'none' } }],
23
+ overall: 'degraded', hasProject: false, ...over,
24
+ };
25
+ }
26
+ describe('renderInitOutcome', () => {
27
+ it('renders before, actions and after blocks, reusing the doctor dashboard', () => {
28
+ const outcome = {
29
+ steps: [
30
+ { id: 'machine.cache', level: 'machine', action: 'applied', detail: 'cache clonado' },
31
+ { id: 'project.constitution', level: 'project', action: 'pending', detail: 'skill: project-constitution' },
32
+ ],
33
+ applied: 1, pending: 1, failed: 0,
34
+ before: report(), after: report({ overall: 'degraded' }),
35
+ };
36
+ const out = (0, init_1.renderInitOutcome)(outcome);
37
+ expect(out).toContain('AWM · init');
38
+ expect(out).toContain('Estado inicial');
39
+ expect(out).toContain('Acciones');
40
+ expect(out).toContain('machine.cache');
41
+ expect(out).toContain('skill: project-constitution');
42
+ expect(out).toContain('Estado final');
43
+ expect(out).toContain('AWM · estado del harness'); // viene de renderReport
44
+ expect(out).toContain('1 pasos requieren un agente');
45
+ });
46
+ });
47
+ describe('runInit', () => {
48
+ let tmpHome;
49
+ let originalHome;
50
+ let originalAwmHome;
51
+ let writeSpy;
52
+ beforeEach(() => {
53
+ tmpHome = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-init-run-'));
54
+ originalHome = process.env.HOME;
55
+ originalAwmHome = process.env.AWM_HOME;
56
+ process.env.HOME = tmpHome;
57
+ process.env.AWM_HOME = path_1.default.join(tmpHome, '.awm');
58
+ jest.resetModules();
59
+ writeSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => true);
60
+ });
61
+ afterEach(() => {
62
+ writeSpy.mockRestore();
63
+ fs_1.default.rmSync(tmpHome, { recursive: true, force: true });
64
+ if (originalHome === undefined)
65
+ delete process.env.HOME;
66
+ else
67
+ process.env.HOME = originalHome;
68
+ if (originalAwmHome === undefined)
69
+ delete process.env.AWM_HOME;
70
+ else
71
+ process.env.AWM_HOME = originalAwmHome;
72
+ });
73
+ it('returns exit 1 on a bare HOME and never prompts with --yes (cache stubbed)', async () => {
74
+ const { runInit } = require('../../src/commands/init');
75
+ const code = await runInit({ cwd: tmpHome, yes: true, actions: { syncCache: async () => { } } });
76
+ expect(code).toBe(1); // cache/hook/devCore siguen ausentes (syncCache no-op) → degradado
77
+ });
78
+ it('--json emits a parseable InitOutcome', async () => {
79
+ const { runInit } = require('../../src/commands/init');
80
+ const code = await runInit({ cwd: tmpHome, yes: true, json: true, actions: { syncCache: async () => { } } });
81
+ const written = writeSpy.mock.calls.map((c) => c[0]).join('');
82
+ const parsed = JSON.parse(written);
83
+ expect(Array.isArray(parsed.steps)).toBe(true);
84
+ expect(parsed.after.overall).toBe('degraded');
85
+ expect(code).toBe(1);
86
+ });
87
+ });
@@ -0,0 +1,64 @@
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 commander_1 = require("commander");
10
+ const ledger_1 = require("../../../src/commands/ledger");
11
+ const store_1 = require("../../../src/core/ledger/store");
12
+ function mkTmp() {
13
+ return fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-ledger-cli-'));
14
+ }
15
+ function run(argv, cwd) {
16
+ const prog = new commander_1.Command();
17
+ prog.exitOverride();
18
+ (0, ledger_1.registerLedgerCommand)(prog);
19
+ const spy = jest.spyOn(process.stdout, 'write').mockImplementation(() => true);
20
+ const cwdSpy = jest.spyOn(process, 'cwd').mockReturnValue(cwd);
21
+ try {
22
+ prog.parse(['node', 'awm', 'ledger', ...argv]);
23
+ }
24
+ finally {
25
+ cwdSpy.mockRestore();
26
+ }
27
+ const out = spy.mock.calls.map(c => String(c[0])).join('');
28
+ spy.mockRestore();
29
+ return out;
30
+ }
31
+ describe('awm ledger CLI', () => {
32
+ let cwd;
33
+ beforeEach(() => { cwd = mkTmp(); });
34
+ afterEach(() => { fs_1.default.rmSync(cwd, { recursive: true, force: true }); });
35
+ test('add writes an entry to the current branch ledger', () => {
36
+ run(['add', '--branch', 'feat-x', '--polarity', 'finding', '--class', 'logica',
37
+ '--signature', 'sig-1', '--severity', 'blocker', '--desc', 'boom', '--ref', 'a.ts:1'], cwd);
38
+ const entries = (0, store_1.listEntries)(cwd, 'feat-x');
39
+ expect(entries).toHaveLength(1);
40
+ expect(entries[0]).toMatchObject({ signature: 'sig-1', polarity: 'finding', class: 'logica' });
41
+ expect(entries[0].ts).toMatch(/\d{4}-\d{2}-\d{2}T/);
42
+ });
43
+ test('list emits the branch entries as JSON', () => {
44
+ run(['add', '--branch', 'feat-x', '--polarity', 'win', '--class', 'proceso',
45
+ '--signature', 'good', '--severity', 'info', '--desc', 'nice'], cwd);
46
+ const out = run(['list', '--branch', 'feat-x'], cwd);
47
+ expect(JSON.parse(out)).toHaveLength(1);
48
+ expect(JSON.parse(out)[0].polarity).toBe('win');
49
+ });
50
+ test('recurring reports clusters at or above --min', () => {
51
+ for (const _ of [0, 1]) {
52
+ run(['add', '--branch', 'feat-x', '--polarity', 'finding', '--class', 'logica',
53
+ '--signature', 'dup', '--severity', 'minor', '--desc', 'x'], cwd);
54
+ }
55
+ const out = run(['recurring', '--branch', 'feat-x', '--min', '2'], cwd);
56
+ expect(JSON.parse(out)).toMatchObject([{ signature: 'dup', count: 2 }]);
57
+ });
58
+ test('archive removes the active ledger', () => {
59
+ run(['add', '--branch', 'feat-x', '--polarity', 'finding', '--class', 'logica',
60
+ '--signature', 's', '--severity', 'minor', '--desc', 'x'], cwd);
61
+ run(['archive', '--branch', 'feat-x'], cwd);
62
+ expect((0, store_1.listEntries)(cwd, 'feat-x')).toEqual([]);
63
+ });
64
+ });
@@ -0,0 +1,72 @@
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
+ // cli/tests/commands/pin.test.ts
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ const os_1 = __importDefault(require("os"));
10
+ describe('pin/unpin (editores de preferences)', () => {
11
+ let tmpHome;
12
+ let originalHome;
13
+ let originalAwmHome;
14
+ beforeEach(() => {
15
+ tmpHome = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-pin-home-'));
16
+ originalHome = process.env.HOME;
17
+ originalAwmHome = process.env.AWM_HOME;
18
+ process.env.HOME = tmpHome;
19
+ process.env.AWM_HOME = path_1.default.join(tmpHome, '.awm');
20
+ jest.resetModules();
21
+ });
22
+ afterEach(() => {
23
+ fs_1.default.rmSync(tmpHome, { recursive: true, force: true });
24
+ if (originalHome === undefined)
25
+ delete process.env.HOME;
26
+ else
27
+ process.env.HOME = originalHome;
28
+ if (originalAwmHome === undefined)
29
+ delete process.env.AWM_HOME;
30
+ else
31
+ process.env.AWM_HOME = originalAwmHome;
32
+ });
33
+ const readPrefs = () => JSON.parse(fs_1.default.readFileSync(path_1.default.join(tmpHome, '.awm/preferences.json'), 'utf-8'));
34
+ it('setPin escribe pins.base normalizado (acepta prefijo v)', () => {
35
+ const { setPin } = require('../../src/commands/pin');
36
+ setPin('base', 'v1.2.0');
37
+ expect(readPrefs().pins).toEqual({ base: '1.2.0' });
38
+ });
39
+ it('setPin acepta un registry adicional configurado', () => {
40
+ const { writeRegistriesConfig } = require('../../src/core/registries');
41
+ writeRegistriesConfig([{ name: 'equipo', remote: '/tmp/x' }]);
42
+ const { setPin } = require('../../src/commands/pin');
43
+ setPin('equipo', '0.3.0');
44
+ expect(readPrefs().pins).toEqual({ equipo: '0.3.0' });
45
+ });
46
+ it('setPin rechaza un registry desconocido listando los válidos', () => {
47
+ const { setPin } = require('../../src/commands/pin');
48
+ expect(() => setPin('nope', '1.0.0')).toThrow(/nope.*base/s);
49
+ });
50
+ it('setPin rechaza versión malformada', () => {
51
+ const { setPin } = require('../../src/commands/pin');
52
+ expect(() => setPin('base', '1.2')).toThrow(/X\.Y\.Z/);
53
+ expect(() => setPin('base', 'latest')).toThrow(/X\.Y\.Z/);
54
+ });
55
+ it('removePin borra la entrada y reporta si existía', () => {
56
+ const { setPin, removePin } = require('../../src/commands/pin');
57
+ setPin('base', '1.2.0');
58
+ expect(removePin('base')).toBe(true);
59
+ expect(readPrefs().pins).toEqual({});
60
+ expect(removePin('base')).toBe(false);
61
+ });
62
+ it('setPin preserva las demás preferencias y pins existentes', () => {
63
+ const { setPin } = require('../../src/commands/pin');
64
+ setPin('base', '1.0.0');
65
+ const { writeRegistriesConfig } = require('../../src/core/registries');
66
+ writeRegistriesConfig([{ name: 'equipo', remote: '/tmp/x' }]);
67
+ setPin('equipo', '2.0.0');
68
+ const prefs = readPrefs();
69
+ expect(prefs.pins).toEqual({ base: '1.0.0', equipo: '2.0.0' });
70
+ expect(prefs.defaultAgent).toBeDefined();
71
+ });
72
+ });