@polymorphism-tech/morph-spec 4.9.0 → 4.10.1

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 (164) hide show
  1. package/README.md +2 -2
  2. package/bin/morph-spec.js +30 -0
  3. package/bin/task-manager.js +34 -22
  4. package/claude-plugin.json +1 -1
  5. package/docs/CHEATSHEET.md +1 -1
  6. package/docs/QUICKSTART.md +1 -1
  7. package/framework/CLAUDE.md +35 -98
  8. package/framework/agents/backend/api-designer.md +3 -0
  9. package/framework/agents/backend/dotnet-senior.md +3 -0
  10. package/framework/agents/backend/ef-modeler.md +2 -0
  11. package/framework/agents/backend/hangfire-orchestrator.md +2 -0
  12. package/framework/agents/backend/ms-agent-expert.md +2 -0
  13. package/framework/agents/frontend/blazor-builder.md +2 -0
  14. package/framework/agents/frontend/nextjs-expert.md +2 -0
  15. package/framework/agents/infrastructure/azure-architect.md +2 -0
  16. package/framework/agents/infrastructure/azure-deploy-specialist.md +2 -0
  17. package/framework/agents/infrastructure/bicep-architect.md +2 -0
  18. package/framework/agents/infrastructure/container-specialist.md +2 -0
  19. package/framework/agents/infrastructure/devops-engineer.md +3 -0
  20. package/framework/agents/infrastructure/infra-architect.md +3 -0
  21. package/framework/agents/integrations/asaas-financial.md +2 -0
  22. package/framework/agents/integrations/azure-identity.md +2 -0
  23. package/framework/agents/integrations/clerk-auth.md +3 -0
  24. package/framework/agents/integrations/hangfire-integration.md +2 -0
  25. package/framework/agents/integrations/resend-email.md +2 -0
  26. package/framework/agents.json +37 -7
  27. package/framework/commands/commit.md +166 -0
  28. package/framework/commands/morph-apply.md +156 -155
  29. package/framework/commands/morph-archive.md +33 -27
  30. package/framework/commands/morph-infra.md +83 -77
  31. package/framework/commands/morph-preflight.md +97 -55
  32. package/framework/commands/morph-proposal.md +131 -58
  33. package/framework/commands/morph-status.md +36 -30
  34. package/framework/commands/morph-troubleshoot.md +68 -59
  35. package/framework/hooks/claude-code/notification/approval-reminder.js +3 -2
  36. package/framework/hooks/claude-code/post-tool-use/dispatch.js +154 -31
  37. package/framework/hooks/claude-code/post-tool-use/skill-reminder.js +7 -84
  38. package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +8 -17
  39. package/framework/hooks/claude-code/pre-compact/save-morph-context.js +16 -3
  40. package/framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js +4 -3
  41. package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +3 -2
  42. package/framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js +60 -0
  43. package/framework/hooks/claude-code/session-start/inject-morph-context.js +55 -2
  44. package/framework/hooks/claude-code/session-start/post-compact-restore.js +41 -0
  45. package/framework/hooks/claude-code/stop/validate-completion.js +2 -15
  46. package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +23 -5
  47. package/framework/hooks/shared/compact-restore.js +100 -0
  48. package/framework/hooks/shared/dispatch-helpers.js +116 -0
  49. package/framework/hooks/shared/phase-utils.js +9 -5
  50. package/framework/hooks/shared/state-reader.js +27 -3
  51. package/framework/phases.json +30 -7
  52. package/framework/rules/csharp-standards.md +3 -0
  53. package/framework/rules/frontend-standards.md +2 -0
  54. package/framework/rules/infrastructure-standards.md +3 -0
  55. package/framework/rules/morph-workflow.md +143 -86
  56. package/framework/rules/nextjs-standards.md +2 -0
  57. package/framework/rules/testing-standards.md +3 -0
  58. package/framework/skills/level-0-meta/mcp-registry.json +86 -51
  59. package/framework/skills/level-0-meta/morph-brainstorming/SKILL.md +139 -0
  60. package/framework/skills/level-0-meta/morph-checklist/SKILL.md +42 -19
  61. package/framework/skills/level-0-meta/{code-review → morph-code-review}/SKILL.md +8 -5
  62. package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/SKILL.md +8 -6
  63. package/framework/skills/level-0-meta/morph-frontend-review/SKILL.md +362 -0
  64. package/framework/skills/level-0-meta/morph-init/SKILL.md +114 -20
  65. package/framework/skills/level-0-meta/morph-post-implementation/SKILL.md +362 -0
  66. package/framework/skills/level-0-meta/morph-replicate/SKILL.md +95 -87
  67. package/framework/skills/level-0-meta/{simulation-checklist → morph-simulation-checklist}/SKILL.md +24 -0
  68. package/framework/skills/level-0-meta/{tool-usage-guide → morph-tool-usage-guide}/SKILL.md +43 -43
  69. package/framework/skills/level-0-meta/{tool-usage-guide → morph-tool-usage-guide}/references/tools-per-phase.md +1 -2
  70. package/framework/skills/level-0-meta/{verification-before-completion → morph-verification-before-completion}/SKILL.md +23 -12
  71. package/framework/skills/level-0-meta/{verification-before-completion → morph-verification-before-completion}/scripts/check-phase-outputs.mjs +2 -2
  72. package/framework/skills/level-1-workflows/morph-phase-clarify/SKILL.md +247 -0
  73. package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +270 -0
  74. package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +499 -0
  75. package/framework/skills/level-1-workflows/morph-phase-implement/.morph/logs/activity.json +38 -0
  76. package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +472 -0
  77. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/code-quality-reviewer-prompt.md +50 -0
  78. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/implementer-prompt.md +45 -0
  79. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/spec-reviewer-prompt.md +47 -0
  80. package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +246 -0
  81. package/framework/skills/level-1-workflows/morph-phase-setup/SKILL.md +238 -0
  82. package/framework/skills/level-1-workflows/morph-phase-tasks/.morph/logs/activity.json +14 -0
  83. package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +312 -0
  84. package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/scripts/validate-tasks.mjs +3 -3
  85. package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +324 -0
  86. package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +146 -0
  87. package/framework/standards/integration/mcp/mcp-tools.md +25 -7
  88. package/framework/templates/docs/onboarding.md +2 -2
  89. package/package.json +3 -4
  90. package/src/commands/agents/dispatch-agents.js +50 -3
  91. package/src/commands/mcp/mcp-setup.js +39 -2
  92. package/src/commands/phase/phase-reset.js +74 -0
  93. package/src/commands/project/doctor.js +26 -7
  94. package/src/commands/project/update.js +4 -4
  95. package/src/commands/scope/escalate.js +215 -0
  96. package/src/commands/state/advance-phase.js +27 -53
  97. package/src/commands/state/state.js +1 -1
  98. package/src/commands/task/expand.js +100 -0
  99. package/src/core/paths/output-schema.js +4 -3
  100. package/src/core/state/phase-state-machine.js +7 -4
  101. package/src/core/state/state-manager.js +4 -3
  102. package/src/lib/detectors/claude-config-detector.js +93 -347
  103. package/src/lib/detectors/design-system-detector.js +189 -189
  104. package/src/lib/detectors/index.js +155 -57
  105. package/src/lib/generators/context-generator.js +2 -2
  106. package/src/lib/installers/mcp-installer.js +37 -5
  107. package/src/lib/phase-chain/phase-validator.js +22 -16
  108. package/src/lib/scope/impact-analyzer.js +106 -0
  109. package/src/lib/stack-filter.js +58 -0
  110. package/src/lib/tasks/task-parser.js +1 -1
  111. package/src/lib/validators/shared/emit-validator-dispatch.js +64 -0
  112. package/src/scripts/setup-infra.js +68 -18
  113. package/src/utils/agents-installer.js +51 -17
  114. package/src/utils/claude-md-injector.js +90 -0
  115. package/src/utils/file-copier.js +0 -1
  116. package/src/utils/hooks-installer.js +16 -5
  117. package/src/utils/skills-installer.js +67 -7
  118. package/CLAUDE.md +0 -98
  119. package/framework/memory/patterns-learned.md +0 -766
  120. package/framework/skills/level-0-meta/brainstorming/SKILL.md +0 -137
  121. package/framework/skills/level-0-meta/frontend-review/SKILL.md +0 -359
  122. package/framework/skills/level-0-meta/post-implementation/SKILL.md +0 -362
  123. package/framework/skills/level-0-meta/terminal-title/SKILL.md +0 -61
  124. package/framework/skills/level-0-meta/terminal-title/scripts/set_title.sh +0 -65
  125. package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +0 -216
  126. package/framework/skills/level-1-workflows/phase-codebase-analysis/SKILL.md +0 -252
  127. package/framework/skills/level-1-workflows/phase-design/SKILL.md +0 -383
  128. package/framework/skills/level-1-workflows/phase-implement/SKILL.md +0 -492
  129. package/framework/skills/level-1-workflows/phase-setup/SKILL.md +0 -195
  130. package/framework/skills/level-1-workflows/phase-tasks/SKILL.md +0 -271
  131. package/framework/skills/level-1-workflows/phase-uiux/SKILL.md +0 -286
  132. package/src/commands/project/index.js +0 -8
  133. package/src/core/index.js +0 -10
  134. package/src/core/state/index.js +0 -8
  135. package/src/core/templates/index.js +0 -9
  136. package/src/core/templates/template-data-sources.js +0 -325
  137. package/src/core/workflows/index.js +0 -7
  138. package/src/lib/detectors/config-detector.js +0 -223
  139. package/src/lib/detectors/standards-generator.js +0 -335
  140. package/src/lib/detectors/structure-detector.js +0 -275
  141. package/src/lib/monitor/agent-resolver.js +0 -144
  142. package/src/lib/monitor/renderer.js +0 -230
  143. package/src/lib/orchestration/index.js +0 -7
  144. package/src/lib/orchestration/team-orchestrator.js +0 -404
  145. package/src/sanitizer/context-sanitizer.js +0 -221
  146. package/src/sanitizer/patterns.js +0 -163
  147. package/src/writer/file-writer.js +0 -86
  148. /package/framework/skills/level-0-meta/{brainstorming → morph-brainstorming}/references/proposal-example.md +0 -0
  149. /package/framework/skills/level-0-meta/{code-review → morph-code-review}/references/review-example.md +0 -0
  150. /package/framework/skills/level-0-meta/{code-review → morph-code-review}/references/review-guidelines.md +0 -0
  151. /package/framework/skills/level-0-meta/{code-review → morph-code-review}/scripts/scan-csharp.mjs +0 -0
  152. /package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/references/review-example-nextjs.md +0 -0
  153. /package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/scripts/scan-nextjs.mjs +0 -0
  154. /package/framework/skills/level-0-meta/{frontend-review → morph-frontend-review}/scripts/scan-accessibility.mjs +0 -0
  155. /package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/scripts/detect-dev-server.mjs +0 -0
  156. /package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/scripts/detect-stack.mjs +0 -0
  157. /package/framework/skills/level-1-workflows/{phase-clarify → morph-phase-clarify}/references/clarifications-example.md +0 -0
  158. /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/architecture-analysis-guide.md +0 -0
  159. /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/spec-authoring-guide.md +0 -0
  160. /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/spec-example.md +0 -0
  161. /package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/references/recap-example.md +0 -0
  162. /package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/references/vsa-implementation-guide.md +0 -0
  163. /package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/references/task-planning-patterns.md +0 -0
  164. /package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/references/tasks-example.md +0 -0
@@ -1,390 +1,136 @@
1
1
  /**
2
- * Claude Config Detector
2
+ * @fileoverview Claude Config Detector
3
3
  *
4
- * Detects the user's Claude Code environment: plugins, MCP servers,
5
- * custom skills, commands, hooks, and superpowers installation.
4
+ * Scans .claude/ settings and plugins for MCP servers, hooks, skills, and commands.
5
+ * Used by mcp-setup and session-start hooks.
6
+ *
7
+ * @module detectors/claude-config-detector
6
8
  */
7
9
 
10
+ import { existsSync, readFileSync, readdirSync } from 'fs';
8
11
  import { join } from 'path';
9
- import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
10
12
  import { homedir } from 'os';
11
13
 
12
14
  /**
13
- * Detect full Claude Code configuration for a project
14
- * @param {string} targetPath - Project root directory
15
- * @param {Object} options - Options
16
- * @param {string} options.globalDir - Override global .claude dir (for testing)
17
- * @returns {Promise<Object>} Detected configuration
15
+ * Detect Claude Code configuration for a project.
16
+ *
17
+ * @param {string} targetPath - Root path of the project
18
+ * @returns {{ plugins: string[], mcpServers: Array<{name: string, config: Object}>, superpowers: boolean, customSkills: string[], customCommands: string[], hooks: string[] }}
18
19
  */
19
- export async function detectClaudeConfig(targetPath, options = {}) {
20
- const home = homedir();
21
- const globalClaudeDir = options.globalDir || join(home, '.claude');
22
- const projectClaudeDir = join(targetPath, '.claude');
23
-
20
+ export function detectClaudeConfig(targetPath) {
24
21
  const result = {
25
22
  plugins: [],
26
23
  mcpServers: [],
24
+ superpowers: false,
27
25
  customSkills: [],
28
26
  customCommands: [],
29
27
  hooks: [],
30
- superpowers: { installed: false, version: null, skills: [] }
31
28
  };
32
29
 
33
- // 1. Detect enabled plugins from global settings
34
- result.plugins = detectPlugins(globalClaudeDir);
35
-
36
- // 2. Detect MCP servers from global + project settings
37
- result.mcpServers = detectMcpServers(globalClaudeDir, projectClaudeDir);
38
-
39
- // 3. Detect custom skills
40
- result.customSkills = detectCustomSkills(globalClaudeDir, projectClaudeDir);
41
-
42
- // 4. Detect custom commands
43
- result.customCommands = detectCustomCommands(globalClaudeDir, projectClaudeDir);
44
-
45
- // 5. Detect hooks
46
- result.hooks = detectHooks(globalClaudeDir, projectClaudeDir);
47
-
48
- // 6. Detect superpowers plugin
49
- result.superpowers = detectSuperpowers(result.plugins, result.customSkills);
50
-
51
- return result;
52
- }
53
-
54
- /**
55
- * Detect enabled plugins from settings.json
56
- */
57
- function detectPlugins(globalClaudeDir) {
58
- const plugins = [];
59
-
60
- // Read global settings for enabledPlugins
61
- const settingsPath = join(globalClaudeDir, 'settings.json');
62
- const settings = readJsonSafe(settingsPath);
63
- const rawPlugins = settings?.enabledPlugins;
64
- const enabledPlugins = Array.isArray(rawPlugins) ? rawPlugins : [];
65
-
66
- // Read installed plugins for version info
67
- const installedPath = join(globalClaudeDir, 'plugins', 'installed_plugins.json');
68
- const installed = readJsonSafe(installedPath) || {};
69
-
70
- for (const pluginId of enabledPlugins) {
71
- const pluginInfo = installed[pluginId] || {};
72
- plugins.push({
73
- name: extractPluginName(pluginId),
74
- id: pluginId,
75
- version: pluginInfo.version || null,
76
- enabled: true
77
- });
78
- }
79
-
80
- return plugins;
81
- }
82
-
83
- /**
84
- * Detect MCP servers from global and project settings
85
- */
86
- function detectMcpServers(globalClaudeDir, projectClaudeDir) {
87
- const servers = [];
88
-
89
- // Global MCP servers from settings.json
90
- const globalSettings = readJsonSafe(join(globalClaudeDir, 'settings.json'));
91
- if (globalSettings?.mcpServers) {
92
- for (const [name, config] of Object.entries(globalSettings.mcpServers)) {
93
- servers.push({ name, source: 'global', config });
94
- }
95
- }
96
-
97
- // Global MCP servers from settings.local.json
98
- const globalLocal = readJsonSafe(join(globalClaudeDir, 'settings.local.json'));
99
- if (globalLocal?.mcpServers) {
100
- for (const [name, config] of Object.entries(globalLocal.mcpServers)) {
101
- if (!servers.find(s => s.name === name)) {
102
- servers.push({ name, source: 'global', config });
103
- }
104
- }
105
- }
106
-
107
- // Project MCP servers from .claude/settings.local.json
108
- const projectSettings = readJsonSafe(join(projectClaudeDir, 'settings.local.json'));
109
- if (projectSettings?.mcpServers) {
110
- for (const [name, config] of Object.entries(projectSettings.mcpServers)) {
111
- const existing = servers.find(s => s.name === name);
112
- if (existing) {
113
- existing.source = 'project'; // project overrides global
114
- } else {
115
- servers.push({ name, source: 'project', config });
30
+ try {
31
+ // ── MCP Servers from settings ────────────────────────────────────────────
32
+ const settingsPaths = [
33
+ join(targetPath, '.claude', 'settings.json'),
34
+ join(targetPath, '.claude', 'settings.local.json'),
35
+ ];
36
+
37
+ for (const sp of settingsPaths) {
38
+ if (existsSync(sp)) {
39
+ try {
40
+ const settings = JSON.parse(readFileSync(sp, 'utf8'));
41
+ const mcps = settings.mcpServers || {};
42
+ for (const [name, config] of Object.entries(mcps)) {
43
+ // Avoid duplicates — later file wins
44
+ const idx = result.mcpServers.findIndex(m => m.name === name);
45
+ if (idx >= 0) {
46
+ result.mcpServers[idx] = { name, config };
47
+ } else {
48
+ result.mcpServers.push({ name, config });
49
+ }
50
+ }
51
+
52
+ // Collect hooks
53
+ if (settings.hooks) {
54
+ for (const [event, eventHooks] of Object.entries(settings.hooks)) {
55
+ for (const group of (Array.isArray(eventHooks) ? eventHooks : [])) {
56
+ for (const h of (group.hooks || [])) {
57
+ result.hooks.push(`${event}:${h.type || 'unknown'}`);
58
+ }
59
+ }
60
+ }
61
+ }
62
+ } catch { /* invalid JSON */ }
116
63
  }
117
64
  }
118
- }
119
65
 
120
- return servers;
121
- }
122
-
123
- /**
124
- * Detect custom skills from .claude/skills/ directories
125
- */
126
- function detectCustomSkills(globalClaudeDir, projectClaudeDir) {
127
- const skills = [];
128
-
129
- // Global skills
130
- const globalSkills = join(globalClaudeDir, 'skills');
131
- if (existsSync(globalSkills)) {
132
- for (const file of walkMdFiles(globalSkills)) {
133
- skills.push({
134
- name: extractSkillName(file),
135
- path: file,
136
- source: 'global'
137
- });
66
+ // ── Plugins from global config ──────────────────────────────────────────
67
+ const pluginsPath = join(homedir(), '.claude', 'plugins', 'installed_plugins.json');
68
+ if (existsSync(pluginsPath)) {
69
+ try {
70
+ const pluginsData = JSON.parse(readFileSync(pluginsPath, 'utf8'));
71
+ const plugins = pluginsData.plugins || {};
72
+ result.plugins = Object.keys(plugins);
73
+ result.superpowers = 'superpowers@claude-plugins-official' in plugins;
74
+ } catch { /* invalid JSON */ }
138
75
  }
139
- }
140
76
 
141
- // Project skills (may include morph-spec skills)
142
- const projectSkills = join(projectClaudeDir, 'skills');
143
- if (existsSync(projectSkills)) {
144
- for (const file of walkMdFiles(projectSkills)) {
145
- const name = extractSkillName(file);
146
- // Skip morph-spec framework skills (they're managed by init)
147
- if (file.includes('level-0-meta') || file.includes('level-1-workflows')) {
148
- continue;
149
- }
150
- skills.push({
151
- name,
152
- path: file,
153
- source: 'project'
154
- });
77
+ // ── Custom skills ───────────────────────────────────────────────────────
78
+ const skillsPath = join(targetPath, '.claude', 'skills');
79
+ if (existsSync(skillsPath)) {
80
+ try {
81
+ result.customSkills = readdirSync(skillsPath).filter(f => !f.startsWith('.'));
82
+ } catch { /* ignore */ }
155
83
  }
156
- }
157
84
 
158
- return skills;
159
- }
160
-
161
- /**
162
- * Detect custom commands from .claude/commands/ directories
163
- */
164
- function detectCustomCommands(globalClaudeDir, projectClaudeDir) {
165
- const commands = [];
166
-
167
- // Global commands
168
- const globalCommands = join(globalClaudeDir, 'commands');
169
- if (existsSync(globalCommands)) {
170
- for (const file of walkMdFiles(globalCommands)) {
171
- commands.push({
172
- name: extractCommandName(file),
173
- path: file,
174
- source: 'global'
175
- });
176
- }
177
- }
178
-
179
- // Project commands (excluding morph-spec managed commands)
180
- const projectCommands = join(projectClaudeDir, 'commands');
181
- if (existsSync(projectCommands)) {
182
- for (const file of walkMdFiles(projectCommands)) {
183
- const name = extractCommandName(file);
184
- // Skip morph-spec commands
185
- if (name.startsWith('morph-')) continue;
186
- commands.push({
187
- name,
188
- path: file,
189
- source: 'project'
190
- });
191
- }
192
- }
193
-
194
- return commands;
195
- }
196
-
197
- /**
198
- * Detect hooks from settings files
199
- */
200
- function detectHooks(globalClaudeDir, projectClaudeDir) {
201
- const hooks = [];
202
-
203
- // Global hooks
204
- const globalSettings = readJsonSafe(join(globalClaudeDir, 'settings.json'));
205
- if (globalSettings?.hooks) {
206
- for (const [trigger, entries] of Object.entries(globalSettings.hooks)) {
207
- hooks.push({ trigger, source: 'global', entries });
85
+ // ── Custom commands ─────────────────────────────────────────────────────
86
+ const commandsPath = join(targetPath, '.claude', 'commands');
87
+ if (existsSync(commandsPath)) {
88
+ try {
89
+ result.customCommands = readdirSync(commandsPath).filter(f => f.endsWith('.md'));
90
+ } catch { /* ignore */ }
208
91
  }
209
- }
210
-
211
- // Project hooks
212
- const projectSettings = readJsonSafe(join(projectClaudeDir, 'settings.local.json'));
213
- if (projectSettings?.hooks) {
214
- for (const [trigger, entries] of Object.entries(projectSettings.hooks)) {
215
- hooks.push({ trigger, source: 'project', entries });
216
- }
217
- }
218
-
219
- return hooks;
220
- }
221
-
222
- /**
223
- * Detect superpowers plugin installation
224
- */
225
- function detectSuperpowers(plugins, customSkills) {
226
- const result = { installed: false, version: null, skills: [] };
227
-
228
- // Check if superpowers is in enabled plugins
229
- const superPlugin = plugins.find(p =>
230
- p.name === 'superpowers' || p.id?.includes('superpowers')
231
- );
232
-
233
- if (superPlugin) {
234
- result.installed = true;
235
- result.version = superPlugin.version;
236
- }
237
-
238
- // Collect superpowers skills from detected skills
239
- const superpowerSkills = customSkills.filter(s =>
240
- s.path?.includes('superpowers') || s.name?.startsWith('superpowers:')
241
- );
242
-
243
- if (superpowerSkills.length > 0) {
244
- result.installed = true;
245
- result.skills = superpowerSkills.map(s => s.name);
92
+ } catch {
93
+ // Fail-safe
246
94
  }
247
95
 
248
96
  return result;
249
97
  }
250
98
 
251
- // ============================================================================
252
- // MCP Phase Mapping
253
- // ============================================================================
254
-
255
99
  /**
256
- * Map detected MCPs to morph-spec phases where they're useful
257
- * @param {Array} mcpServers - Detected MCP servers
258
- * @returns {Object} Map of phase relevant MCPs
100
+ * Cross-reference configured MCPs with the MCP registry's phase matrix.
101
+ *
102
+ * @param {Array<{name: string}>} mcpServers - Detected MCP server list
103
+ * @param {Object} phaseMatrix - From mcp-registry.json → phaseMatrix
104
+ * @param {string} currentPhase - Current morph-spec phase
105
+ * @returns {{ available: string[], recommended: string[], missing: string[] }}
259
106
  */
260
- export function mapMcpsToPhases(mcpServers) {
261
- const phaseMap = {
262
- proposal: [],
263
- setup: [],
264
- uiux: [],
265
- design: [],
266
- clarify: [],
267
- tasks: [],
268
- implement: []
269
- };
270
-
271
- const mcpPhaseConfig = {
272
- context7: ['proposal', 'design', 'tasks', 'implement'],
273
- supabase: ['design', 'implement'],
274
- playwright: ['uiux', 'implement'],
275
- github: ['setup', 'tasks', 'implement'],
276
- figma: ['uiux'],
277
- docker: ['implement'],
278
- azure: ['implement']
107
+ export function mapMcpsToPhases(mcpServers, phaseMatrix, currentPhase) {
108
+ const configuredNames = new Set(mcpServers.map(m => m.name.toLowerCase()));
109
+ const recommended = (phaseMatrix[currentPhase] || []);
110
+
111
+ return {
112
+ available: recommended.filter(name => configuredNames.has(name)),
113
+ recommended,
114
+ missing: recommended.filter(name => !configuredNames.has(name)),
279
115
  };
280
-
281
- for (const server of mcpServers) {
282
- const name = server.name.toLowerCase();
283
- for (const [mcpKey, phases] of Object.entries(mcpPhaseConfig)) {
284
- if (name.includes(mcpKey)) {
285
- for (const phase of phases) {
286
- phaseMap[phase].push({
287
- name: server.name,
288
- source: server.source
289
- });
290
- }
291
- }
292
- }
293
- }
294
-
295
- return phaseMap;
296
116
  }
297
117
 
298
118
  /**
299
- * Format detected config as a summary table string
300
- * @param {Object} config - Detected Claude config
301
- * @returns {string} Formatted summary
119
+ * Format a human-readable one-liner summary of detected config.
120
+ *
121
+ * @param {{ plugins: string[], mcpServers: Array<{name: string}>, superpowers: boolean }} config
122
+ * @returns {string}
302
123
  */
303
124
  export function formatConfigSummary(config) {
304
- const lines = [];
305
-
306
- if (config.plugins.length > 0) {
307
- lines.push('Plugins:');
308
- for (const p of config.plugins) {
309
- const ver = p.version ? ` v${p.version}` : '';
310
- lines.push(` - ${p.name}${ver}`);
311
- }
312
- }
313
-
125
+ const parts = [];
314
126
  if (config.mcpServers.length > 0) {
315
- lines.push('MCP Servers:');
316
- for (const s of config.mcpServers) {
317
- lines.push(` - ${s.name} (${s.source})`);
318
- }
319
- }
320
-
321
- if (config.superpowers.installed) {
322
- lines.push(`Superpowers: installed (${config.superpowers.skills.length} skills)`);
323
- }
324
-
325
- if (config.customSkills.length > 0) {
326
- lines.push(`Custom Skills: ${config.customSkills.length}`);
327
- }
328
-
329
- if (config.customCommands.length > 0) {
330
- lines.push(`Custom Commands: ${config.customCommands.length}`);
331
- }
332
-
333
- return lines.join('\n');
334
- }
335
-
336
- // ============================================================================
337
- // Helpers
338
- // ============================================================================
339
-
340
- function readJsonSafe(filePath) {
341
- try {
342
- if (!existsSync(filePath)) return null;
343
- return JSON.parse(readFileSync(filePath, 'utf-8'));
344
- } catch {
345
- return null;
127
+ parts.push(`MCPs: ${config.mcpServers.map(m => m.name).join(', ')}`);
346
128
  }
347
- }
348
-
349
- function extractPluginName(pluginId) {
350
- // Plugin IDs can be URLs or package names
351
- // e.g., "https://github.com/user/superpowers" → "superpowers"
352
- // e.g., "@user/superpowers" → "superpowers"
353
- const parts = pluginId.split('/');
354
- return parts[parts.length - 1].replace(/\.git$/, '');
355
- }
356
-
357
- function extractSkillName(filePath) {
358
- // Extract skill name from file path
359
- // e.g., ".claude/skills/superpowers/brainstorming.md" → "superpowers:brainstorming"
360
- const parts = filePath.replace(/\\/g, '/').split('/');
361
- const mdFile = parts[parts.length - 1].replace('.md', '');
362
- const parentDir = parts.length >= 2 ? parts[parts.length - 2] : null;
363
-
364
- if (parentDir && parentDir !== 'skills') {
365
- return `${parentDir}:${mdFile}`;
129
+ if (config.plugins.length > 0) {
130
+ parts.push(`Plugins: ${config.plugins.length}`);
366
131
  }
367
- return mdFile;
368
- }
369
-
370
- function extractCommandName(filePath) {
371
- return filePath.replace(/\\/g, '/').split('/').pop().replace('.md', '');
372
- }
373
-
374
- function walkMdFiles(dir) {
375
- const results = [];
376
- try {
377
- const entries = readdirSync(dir, { withFileTypes: true });
378
- for (const entry of entries) {
379
- const fullPath = join(dir, entry.name);
380
- if (entry.isDirectory()) {
381
- results.push(...walkMdFiles(fullPath));
382
- } else if (entry.isFile() && entry.name.endsWith('.md')) {
383
- results.push(fullPath);
384
- }
385
- }
386
- } catch {
387
- // Permission errors or broken symlinks
132
+ if (config.superpowers) {
133
+ parts.push('superpowers: yes');
388
134
  }
389
- return results;
135
+ return parts.join(' | ') || 'No Claude config detected';
390
136
  }