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,63 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.PROVIDERS = void 0;
7
+ exports.getTargetPath = getTargetPath;
8
+ exports.getHookConfig = getHookConfig;
9
+ exports.getInjection = getInjection;
10
+ // src/providers/index.ts
11
+ const os_1 = __importDefault(require("os"));
12
+ const path_1 = __importDefault(require("path"));
13
+ const homedir = process.env.HOME || os_1.default.homedir();
14
+ const awmHome = process.env.AWM_HOME || path_1.default.join(homedir, '.awm');
15
+ exports.PROVIDERS = {
16
+ antigravity: {
17
+ label: 'Antigravity',
18
+ skill: { global: path_1.default.join(homedir, '.gemini/antigravity/skills'), local: '.agent/skills' },
19
+ workflow: { global: path_1.default.join(homedir, '.gemini/antigravity/global_workflows'), local: '.agent/workflows' },
20
+ agent: null
21
+ },
22
+ opencode: {
23
+ label: 'OpenCode',
24
+ skill: { global: path_1.default.join(homedir, '.agents/skills'), local: '.agents/skills' },
25
+ workflow: null,
26
+ agent: { global: path_1.default.join(homedir, '.config/opencode/agents'), local: '.agents/profiles' },
27
+ injection: {
28
+ type: 'config-instructions',
29
+ configPath: path_1.default.join(homedir, '.config/opencode/opencode.json'),
30
+ field: 'instructions',
31
+ },
32
+ },
33
+ 'claude-code': {
34
+ label: 'Claude Code',
35
+ skill: { global: path_1.default.join(homedir, '.claude/skills'), local: '.claude/skills' },
36
+ workflow: null,
37
+ agent: { global: path_1.default.join(homedir, '.claude/agents'), local: '.claude/agents' },
38
+ hooks: {
39
+ type: 'cc-settings-merge',
40
+ settingsPath: path_1.default.join(homedir, '.claude/settings.json'),
41
+ scriptsDir: path_1.default.join(awmHome, 'hooks'),
42
+ matcher: 'startup|clear|compact',
43
+ eventName: 'SessionStart'
44
+ },
45
+ injection: { type: 'cc-settings-merge' }
46
+ }
47
+ };
48
+ function getTargetPath(type, agent, scope) {
49
+ const provider = exports.PROVIDERS[agent];
50
+ if (!provider)
51
+ throw new Error(`Unknown agent target: ${agent}`);
52
+ const config = provider[type];
53
+ if (!config)
54
+ throw new Error(`${type}s are not supported by ${provider.label}.`);
55
+ return config[scope];
56
+ }
57
+ function getHookConfig(agent) {
58
+ const provider = exports.PROVIDERS[agent];
59
+ return provider?.hooks;
60
+ }
61
+ function getInjection(agent) {
62
+ return exports.PROVIDERS[agent]?.injection;
63
+ }
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.getPreferences = getPreferences;
7
+ exports.savePreferences = savePreferences;
8
+ // src/utils/config.ts
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const path_1 = __importDefault(require("path"));
11
+ const os_1 = __importDefault(require("os"));
12
+ const DEFAULT_PREFS = {
13
+ defaultAgent: 'antigravity',
14
+ installMethod: 'symlink',
15
+ defaultScope: 'local'
16
+ };
17
+ function prefsDir() {
18
+ return process.env.AWM_HOME || path_1.default.join(process.env.HOME || os_1.default.homedir(), '.awm');
19
+ }
20
+ function getPreferences() {
21
+ const file = path_1.default.join(prefsDir(), 'preferences.json');
22
+ if (!fs_1.default.existsSync(file)) {
23
+ savePreferences(DEFAULT_PREFS);
24
+ return DEFAULT_PREFS;
25
+ }
26
+ const raw = fs_1.default.readFileSync(file, 'utf-8');
27
+ return JSON.parse(raw);
28
+ }
29
+ function savePreferences(prefs) {
30
+ const dir = prefsDir();
31
+ if (!fs_1.default.existsSync(dir)) {
32
+ fs_1.default.mkdirSync(dir, { recursive: true });
33
+ }
34
+ fs_1.default.writeFileSync(path_1.default.join(dir, 'preferences.json'), JSON.stringify(prefs, null, 2), 'utf-8');
35
+ }
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.buildGroupedOptions = buildGroupedOptions;
4
+ function buildGroupedOptions(artifacts, bundles, formatLabel) {
5
+ const grouped = new Map();
6
+ const standalone = new Map();
7
+ for (const a of artifacts) {
8
+ let foundParent = false;
9
+ const baseName = (a.type === 'workflow' || a.type === 'agent') ? a.name.replace(/\.md$/, '') : a.name;
10
+ for (const p of bundles) {
11
+ const skillNames = p.skills.map((s) => s.name);
12
+ if ((a.type === 'skill' && skillNames.includes(baseName)) ||
13
+ (a.type === 'workflow' && p.workflows.includes(baseName)) ||
14
+ (a.type === 'agent' && p.agents.includes(baseName))) {
15
+ if (!grouped.has(p.name))
16
+ grouped.set(p.name, new Map());
17
+ const procGroup = grouped.get(p.name);
18
+ if (!procGroup.has(baseName))
19
+ procGroup.set(baseName, []);
20
+ procGroup.get(baseName).push(a);
21
+ foundParent = true;
22
+ }
23
+ }
24
+ if (!foundParent) {
25
+ if (!standalone.has(baseName))
26
+ standalone.set(baseName, []);
27
+ standalone.get(baseName).push(a);
28
+ }
29
+ }
30
+ const options = [];
31
+ for (const [procName, baseNameMap] of grouped.entries()) {
32
+ const proc = bundles.find(p => p.name === procName);
33
+ const children = Array.from(baseNameMap.entries()).map(([baseName, arr]) => ({ baseName, artifacts: arr }));
34
+ options.push({
35
+ value: { _group: true, processName: procName, children },
36
+ label: `📦 ${procName}`,
37
+ hint: `${proc.description} — ${children.length} artifacts`
38
+ });
39
+ children.forEach((c, idx) => {
40
+ const prefix = idx === children.length - 1 ? ' └─ ' : ' ├─ ';
41
+ options.push({ value: { _child: true, combined: c }, label: `${prefix}${formatLabel(c)}` });
42
+ });
43
+ }
44
+ if (standalone.size > 0) {
45
+ Array.from(standalone.entries()).forEach(([baseName, arr]) => {
46
+ const c = { baseName, artifacts: arr };
47
+ options.push({ value: { _child: true, combined: c }, label: `🔹 ${formatLabel(c)}` });
48
+ });
49
+ }
50
+ return options;
51
+ }
@@ -0,0 +1,154 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ALL_SENTINEL = exports.STANDALONE_NAME = void 0;
7
+ exports.buildPackageView = buildPackageView;
8
+ exports.artifactCountLabel = artifactCountLabel;
9
+ exports.packageSummaryLines = packageSummaryLines;
10
+ exports.packageDetailLines = packageDetailLines;
11
+ exports.findPackage = findPackage;
12
+ exports.artifactValue = artifactValue;
13
+ exports.buildLevel1Options = buildLevel1Options;
14
+ exports.buildLevel2Options = buildLevel2Options;
15
+ exports.resolveLevel2Selection = resolveLevel2Selection;
16
+ const picocolors_1 = __importDefault(require("picocolors"));
17
+ const registries_1 = require("../core/registries");
18
+ exports.STANDALONE_NAME = 'standalone';
19
+ function makePackage(name, description, isStandalone, visibility, artifacts) {
20
+ return {
21
+ name,
22
+ description,
23
+ isStandalone,
24
+ visibility,
25
+ artifacts,
26
+ counts: {
27
+ skills: artifacts.filter((a) => a.type === 'skill').length,
28
+ workflows: artifacts.filter((a) => a.type === 'workflow').length,
29
+ agents: artifacts.filter((a) => a.type === 'agent').length,
30
+ },
31
+ };
32
+ }
33
+ function buildPackageView(skills, workflows, agents, bundles) {
34
+ const all = [
35
+ ...skills.map((s) => ({ name: s.name, type: 'skill', sourcePath: s.path, installName: s.name, description: s.description ?? '', overrode: s.overrode })),
36
+ ...workflows.map((w) => ({ name: w.name, type: 'workflow', sourcePath: w.path, installName: `${w.name}.md`, description: w.description ?? '', overrode: w.overrode })),
37
+ ...agents.map((a) => ({ name: a.name, type: 'agent', sourcePath: a.path, installName: `${a.name}.md`, description: a.description ?? '', overrode: a.overrode })),
38
+ ];
39
+ const packages = [];
40
+ const claimed = new Set();
41
+ for (const b of bundles) {
42
+ const skillNames = b.skills.map((s) => s.name);
43
+ const arts = all.filter((a) => (a.type === 'skill' && skillNames.includes(a.name)) ||
44
+ (a.type === 'workflow' && b.workflows.includes(a.name)) ||
45
+ (a.type === 'agent' && b.agents.includes(a.name)));
46
+ if (arts.length === 0)
47
+ continue;
48
+ arts.forEach((a) => claimed.add(a));
49
+ packages.push(makePackage(b.name, b.description, false, b.visibility, arts));
50
+ }
51
+ const orphans = all.filter((a) => !claimed.has(a));
52
+ if (orphans.length > 0) {
53
+ packages.push(makePackage(exports.STANDALONE_NAME, 'Artifacts not part of any package', true, 'public', orphans));
54
+ }
55
+ return packages;
56
+ }
57
+ const TYPE_ICON = { skill: '', workflow: '⚡ ', agent: '🤖 ' };
58
+ function plural(n, noun) {
59
+ return `${n} ${noun}${n === 1 ? '' : 's'}`;
60
+ }
61
+ function artifactCountLabel(counts) {
62
+ const parts = [];
63
+ if (counts.skills > 0)
64
+ parts.push(plural(counts.skills, 'skill'));
65
+ if (counts.workflows > 0)
66
+ parts.push(plural(counts.workflows, 'workflow'));
67
+ if (counts.agents > 0)
68
+ parts.push(plural(counts.agents, 'agent'));
69
+ if (parts.length === 0)
70
+ return '0 artifacts';
71
+ return parts.join(' · ');
72
+ }
73
+ function packageIcon(pkg) {
74
+ return pkg.isStandalone ? '🔹' : '📦';
75
+ }
76
+ function packageSummaryLines(packages) {
77
+ const uniqueSkillNames = new Set(packages.flatMap((p) => p.artifacts.filter((a) => a.type === 'skill').map((a) => a.name)));
78
+ const totalSkills = uniqueSkillNames.size;
79
+ const lines = [`AWM Registry — ${plural(packages.length, 'package')}, ${plural(totalSkills, 'skill')}`, ''];
80
+ const nameWidth = Math.max(0, ...packages.map((p) => p.name.length));
81
+ const countLabels = packages.map((p) => (p.isStandalone ? plural(p.artifacts.length, 'artifact') : artifactCountLabel(p.counts)));
82
+ const countWidth = Math.max(0, ...countLabels.map((c) => c.length));
83
+ packages.forEach((p, i) => {
84
+ const name = p.name.padEnd(nameWidth);
85
+ const count = countLabels[i].padEnd(countWidth);
86
+ const desc = p.isStandalone ? '' : p.description;
87
+ lines.push(`${packageIcon(p)} ${name} ${count} ${desc}`.trimEnd());
88
+ });
89
+ return lines;
90
+ }
91
+ function packageDetailLines(pkg) {
92
+ const lines = [];
93
+ const header = pkg.isStandalone
94
+ ? `${packageIcon(pkg)} ${pkg.name} — ${plural(pkg.artifacts.length, 'artifact')}`
95
+ : `${packageIcon(pkg)} ${pkg.name} — ${pkg.description} [${artifactCountLabel(pkg.counts)}]`;
96
+ lines.push(header);
97
+ pkg.artifacts.forEach((a) => {
98
+ const mark = a.overrode
99
+ ? picocolors_1.default.yellow(` ← ${(0, registries_1.registryNameForPath)(a.sourcePath) ?? 'unknown'} (override)`)
100
+ : '';
101
+ lines.push(` ${TYPE_ICON[a.type]}${a.name}${mark}`);
102
+ if (a.description)
103
+ lines.push(` ${a.description}`);
104
+ });
105
+ return lines;
106
+ }
107
+ function findPackage(packages, query) {
108
+ const q = query.toLowerCase();
109
+ const exact = packages.find((p) => p.name.toLowerCase() === q);
110
+ if (exact)
111
+ return { match: exact };
112
+ const contains = packages.find((p) => p.name.toLowerCase().includes(q) || q.includes(p.name.toLowerCase()));
113
+ if (contains)
114
+ return { suggestion: contains.name };
115
+ let best;
116
+ let bestPrefix = -1;
117
+ for (const p of packages) {
118
+ const name = p.name.toLowerCase();
119
+ let i = 0;
120
+ while (i < name.length && i < q.length && name[i] === q[i])
121
+ i++;
122
+ if (i > bestPrefix) {
123
+ bestPrefix = i;
124
+ best = p;
125
+ }
126
+ }
127
+ return { suggestion: best?.name };
128
+ }
129
+ exports.ALL_SENTINEL = '__ALL__';
130
+ function artifactValue(a) {
131
+ return `${a.type}:${a.name}`;
132
+ }
133
+ function buildLevel1Options(packages) {
134
+ return packages.map((p) => {
135
+ const count = p.isStandalone ? plural(p.artifacts.length, 'artifact') : artifactCountLabel(p.counts);
136
+ const desc = p.isStandalone ? '' : ` · ${p.description}`;
137
+ return { value: p.name, label: `${packageIcon(p)} ${p.name} ${picocolors_1.default.dim(`${count}${desc}`)}` };
138
+ });
139
+ }
140
+ function buildLevel2Options(pkg) {
141
+ const installAll = { value: exports.ALL_SENTINEL, label: `✨ Install entire package (${pkg.artifacts.length})` };
142
+ const rest = pkg.artifacts.map((a) => {
143
+ const title = `${TYPE_ICON[a.type]}${a.name}`;
144
+ const label = a.description ? `${title}\n ${picocolors_1.default.dim(a.description)}` : title;
145
+ return { value: artifactValue(a), label };
146
+ });
147
+ return [installAll, ...rest];
148
+ }
149
+ function resolveLevel2Selection(pkg, selectedValues) {
150
+ if (selectedValues.includes(exports.ALL_SENTINEL))
151
+ return [...pkg.artifacts];
152
+ const wanted = new Set(selectedValues);
153
+ return pkg.artifacts.filter((a) => wanted.has(artifactValue(a)));
154
+ }
@@ -0,0 +1,98 @@
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 doctor_1 = require("../../src/commands/doctor");
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const os_1 = __importDefault(require("os"));
9
+ const path_1 = __importDefault(require("path"));
10
+ function report(partial = {}) {
11
+ return {
12
+ results: [
13
+ { id: 'machine.cli', level: 'machine', label: 'CLI v1.0.0', status: 'ok', remedy: { kind: 'none' } },
14
+ { id: 'machine.hook', level: 'machine', label: 'hook SessionStart', status: 'missing',
15
+ remedy: { kind: 'command', value: 'awm init' } },
16
+ ],
17
+ overall: 'degraded',
18
+ hasProject: false,
19
+ ...partial,
20
+ };
21
+ }
22
+ describe('renderReport', () => {
23
+ it('renders the machine block with glyphs and remedies', () => {
24
+ const out = (0, doctor_1.renderReport)(report());
25
+ expect(out).toContain('AWM · estado del harness');
26
+ expect(out).toContain('Máquina (global)');
27
+ expect(out).toContain('✔ CLI v1.0.0');
28
+ expect(out).toContain('✖ hook SessionStart');
29
+ expect(out).toContain('→ awm init');
30
+ expect(out).toContain('estado: degradado · 1 acciones sugeridas');
31
+ });
32
+ it('omits the project block and shows a hint when hasProject is false', () => {
33
+ const out = (0, doctor_1.renderReport)(report());
34
+ expect(out).toContain('(sin proyecto en el cwd)');
35
+ expect(out).not.toContain('Proyecto:');
36
+ });
37
+ it('renders the detail field in parentheses when present', () => {
38
+ const out = (0, doctor_1.renderReport)(report({
39
+ results: [
40
+ { id: 'machine.cli', level: 'machine', label: 'CLI v1.2.0', status: 'warn',
41
+ detail: 'cache desactualizado', remedy: { kind: 'command', value: 'awm update' } },
42
+ ],
43
+ }));
44
+ expect(out).toContain('CLI v1.2.0');
45
+ expect(out).toContain('(cache desactualizado)');
46
+ expect(out).toContain('→ awm update');
47
+ });
48
+ it('renders the project block titled with projectName', () => {
49
+ const out = (0, doctor_1.renderReport)(report({
50
+ hasProject: true,
51
+ projectName: 'belanz',
52
+ results: [
53
+ { id: 'project.constitution', level: 'project', label: 'CONSTITUTION.md ausente',
54
+ status: 'missing', remedy: { kind: 'skill', value: 'project-constitution' } },
55
+ ],
56
+ }));
57
+ expect(out).toContain('Proyecto: belanz');
58
+ expect(out).toContain('→ skill: project-constitution');
59
+ });
60
+ });
61
+ describe('runDoctor', () => {
62
+ let tmpHome;
63
+ let originalHome;
64
+ let originalAwmHome;
65
+ let writeSpy;
66
+ beforeEach(() => {
67
+ tmpHome = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-doctor-run-'));
68
+ originalHome = process.env.HOME;
69
+ originalAwmHome = process.env.AWM_HOME;
70
+ process.env.HOME = tmpHome;
71
+ process.env.AWM_HOME = path_1.default.join(tmpHome, '.awm');
72
+ writeSpy = jest.spyOn(process.stdout, 'write').mockImplementation(() => true);
73
+ });
74
+ afterEach(() => {
75
+ writeSpy.mockRestore();
76
+ fs_1.default.rmSync(tmpHome, { recursive: true, force: true });
77
+ if (originalHome === undefined)
78
+ delete process.env.HOME;
79
+ else
80
+ process.env.HOME = originalHome;
81
+ if (originalAwmHome === undefined)
82
+ delete process.env.AWM_HOME;
83
+ else
84
+ process.env.AWM_HOME = originalAwmHome;
85
+ });
86
+ it('returns exit code 1 when the harness is degraded (bare HOME, no project)', () => {
87
+ const code = (0, doctor_1.runDoctor)({ cwd: tmpHome });
88
+ expect(code).toBe(1);
89
+ });
90
+ it('--json emits a parseable CheckReport and keeps the same exit code', () => {
91
+ const code = (0, doctor_1.runDoctor)({ cwd: tmpHome, json: true });
92
+ const written = writeSpy.mock.calls.map((c) => c[0]).join('');
93
+ const parsed = JSON.parse(written);
94
+ expect(parsed.overall).toBe('degraded');
95
+ expect(Array.isArray(parsed.results)).toBe(true);
96
+ expect(code).toBe(1);
97
+ });
98
+ });
@@ -0,0 +1,149 @@
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('installHook (happy path + merge)', () => {
10
+ let tmpHome;
11
+ let tmpRegistry;
12
+ let originalHome;
13
+ let originalAwmHome;
14
+ beforeEach(() => {
15
+ tmpHome = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-install-'));
16
+ tmpRegistry = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'awm-registry-'));
17
+ // Mock registry layout (content root — no registry/ prefix)
18
+ const regHooks = path_1.default.join(tmpRegistry, 'hooks');
19
+ const regSkill = path_1.default.join(tmpRegistry, 'skills/using-awm');
20
+ fs_1.default.mkdirSync(regHooks, { recursive: true });
21
+ fs_1.default.mkdirSync(regSkill, { recursive: true });
22
+ fs_1.default.writeFileSync(path_1.default.join(regHooks, 'session-start'), '#!/usr/bin/env bash\necho "{}"', { mode: 0o755 });
23
+ fs_1.default.writeFileSync(path_1.default.join(regHooks, 'run-hook.cmd'), '#!/usr/bin/env bash\nexec bash "$1"', { mode: 0o755 });
24
+ fs_1.default.writeFileSync(path_1.default.join(regSkill, 'SKILL.md'), '---\nname: using-awm\n---\nMUST invoke skills.');
25
+ originalHome = process.env.HOME;
26
+ originalAwmHome = process.env.AWM_HOME;
27
+ process.env.HOME = tmpHome;
28
+ process.env.AWM_HOME = path_1.default.join(tmpHome, '.awm');
29
+ jest.resetModules();
30
+ });
31
+ afterEach(() => {
32
+ fs_1.default.rmSync(tmpHome, { recursive: true, force: true });
33
+ fs_1.default.rmSync(tmpRegistry, { recursive: true, force: true });
34
+ if (originalHome === undefined)
35
+ delete process.env.HOME;
36
+ else
37
+ process.env.HOME = originalHome;
38
+ if (originalAwmHome === undefined)
39
+ delete process.env.AWM_HOME;
40
+ else
41
+ process.env.AWM_HOME = originalAwmHome;
42
+ });
43
+ it('installs on a clean system, creating settings.json with the AWM entry', () => {
44
+ const { installHook } = require('../../../src/commands/hooks/install');
45
+ const result = installHook({
46
+ agent: 'claude-code',
47
+ registryRoot: tmpRegistry,
48
+ installMethod: 'symlink'
49
+ });
50
+ expect(result.status).toBe('installed');
51
+ const scriptsDir = path_1.default.join(tmpHome, '.awm/hooks');
52
+ expect(fs_1.default.existsSync(path_1.default.join(scriptsDir, 'session-start'))).toBe(true);
53
+ expect(fs_1.default.existsSync(path_1.default.join(scriptsDir, 'run-hook.cmd'))).toBe(true);
54
+ expect(fs_1.default.lstatSync(path_1.default.join(scriptsDir, 'using-awm.md')).isSymbolicLink()).toBe(true);
55
+ const settings = JSON.parse(fs_1.default.readFileSync(path_1.default.join(tmpHome, '.claude/settings.json'), 'utf-8'));
56
+ expect(settings.hooks.SessionStart).toHaveLength(1);
57
+ expect(settings.hooks.SessionStart[0].matcher).toBe('startup|clear|compact');
58
+ expect(settings.hooks.SessionStart[0].hooks[0].command).toContain('run-hook.cmd');
59
+ expect(result.backupPath).toBeNull();
60
+ });
61
+ it('merges with pre-existing SessionStart entry from another plugin', () => {
62
+ const claudeDir = path_1.default.join(tmpHome, '.claude');
63
+ fs_1.default.mkdirSync(claudeDir, { recursive: true });
64
+ const preExisting = {
65
+ theme: 'dark',
66
+ hooks: {
67
+ SessionStart: [{
68
+ matcher: 'startup',
69
+ hooks: [{ type: 'command', command: '/some/other/plugin/hook' }]
70
+ }]
71
+ }
72
+ };
73
+ fs_1.default.writeFileSync(path_1.default.join(claudeDir, 'settings.json'), JSON.stringify(preExisting, null, 2));
74
+ const { installHook } = require('../../../src/commands/hooks/install');
75
+ const result = installHook({
76
+ agent: 'claude-code',
77
+ registryRoot: tmpRegistry,
78
+ installMethod: 'symlink'
79
+ });
80
+ expect(result.status).toBe('installed');
81
+ expect(result.backupPath).not.toBeNull();
82
+ expect(fs_1.default.existsSync(result.backupPath)).toBe(true);
83
+ const settings = JSON.parse(fs_1.default.readFileSync(path_1.default.join(claudeDir, 'settings.json'), 'utf-8'));
84
+ expect(settings.theme).toBe('dark');
85
+ expect(settings.hooks.SessionStart).toHaveLength(2);
86
+ expect(settings.hooks.SessionStart[0].hooks[0].command).toBe('/some/other/plugin/hook');
87
+ expect(settings.hooks.SessionStart[1].hooks[0].command).toContain('run-hook.cmd');
88
+ });
89
+ it('is idempotent — second install does not duplicate', () => {
90
+ const { installHook } = require('../../../src/commands/hooks/install');
91
+ installHook({ agent: 'claude-code', registryRoot: tmpRegistry, installMethod: 'symlink' });
92
+ const result2 = installHook({ agent: 'claude-code', registryRoot: tmpRegistry, installMethod: 'symlink' });
93
+ expect(result2.status).toBe('already-up-to-date');
94
+ const settings = JSON.parse(fs_1.default.readFileSync(path_1.default.join(tmpHome, '.claude/settings.json'), 'utf-8'));
95
+ expect(settings.hooks.SessionStart).toHaveLength(1);
96
+ });
97
+ it('replaces a stale AWM entry when paths change', () => {
98
+ const claudeDir = path_1.default.join(tmpHome, '.claude');
99
+ fs_1.default.mkdirSync(claudeDir, { recursive: true });
100
+ const scriptsDir = path_1.default.join(tmpHome, '.awm/hooks');
101
+ fs_1.default.writeFileSync(path_1.default.join(claudeDir, 'settings.json'), JSON.stringify({
102
+ hooks: {
103
+ SessionStart: [{
104
+ matcher: 'startup|clear|compact',
105
+ hooks: [{ type: 'command', command: `${scriptsDir}/old-script session-start`, async: true }]
106
+ }]
107
+ }
108
+ }, null, 2));
109
+ const { installHook } = require('../../../src/commands/hooks/install');
110
+ const result = installHook({ agent: 'claude-code', registryRoot: tmpRegistry, installMethod: 'symlink' });
111
+ expect(result.status).toBe('installed');
112
+ const settings = JSON.parse(fs_1.default.readFileSync(path_1.default.join(claudeDir, 'settings.json'), 'utf-8'));
113
+ expect(settings.hooks.SessionStart).toHaveLength(1);
114
+ expect(settings.hooks.SessionStart[0].hooks[0].command).toContain('run-hook.cmd');
115
+ expect(settings.hooks.SessionStart[0].hooks[0].async).toBe(false);
116
+ });
117
+ it('aborts and backs up when settings.json is invalid JSON', () => {
118
+ const claudeDir = path_1.default.join(tmpHome, '.claude');
119
+ fs_1.default.mkdirSync(claudeDir, { recursive: true });
120
+ fs_1.default.writeFileSync(path_1.default.join(claudeDir, 'settings.json'), '{ this is not json');
121
+ const { installHook } = require('../../../src/commands/hooks/install');
122
+ expect(() => installHook({ agent: 'claude-code', registryRoot: tmpRegistry, installMethod: 'symlink' }))
123
+ .toThrow(/not valid JSON/);
124
+ // Original file untouched
125
+ expect(fs_1.default.readFileSync(path_1.default.join(claudeDir, 'settings.json'), 'utf-8')).toBe('{ this is not json');
126
+ // Backup created
127
+ const backups = fs_1.default.readdirSync(path_1.default.join(tmpHome, '.awm/backups'));
128
+ expect(backups.length).toBeGreaterThan(0);
129
+ });
130
+ it('fails fast when registry is missing', () => {
131
+ fs_1.default.rmSync(path_1.default.join(tmpRegistry, 'hooks'), { recursive: true });
132
+ const { installHook } = require('../../../src/commands/hooks/install');
133
+ expect(() => installHook({ agent: 'claude-code', registryRoot: tmpRegistry, installMethod: 'symlink' }))
134
+ .toThrow(/registry not found/);
135
+ // Did not create settings.json
136
+ expect(fs_1.default.existsSync(path_1.default.join(tmpHome, '.claude/settings.json'))).toBe(false);
137
+ });
138
+ it('symlinks using-awm.md even when installMethod is copy (UX choice)', () => {
139
+ const { installHook } = require('../../../src/commands/hooks/install');
140
+ installHook({ agent: 'claude-code', registryRoot: tmpRegistry, installMethod: 'copy' });
141
+ const skillPath = path_1.default.join(tmpHome, '.awm/hooks/using-awm.md');
142
+ expect(fs_1.default.lstatSync(skillPath).isSymbolicLink()).toBe(true);
143
+ });
144
+ it('throws for unsupported agent target', () => {
145
+ const { installHook } = require('../../../src/commands/hooks/install');
146
+ expect(() => installHook({ agent: 'antigravity', registryRoot: tmpRegistry, installMethod: 'symlink' }))
147
+ .toThrow(/not supported/);
148
+ });
149
+ });