@polymorphism-tech/morph-spec 4.9.0 → 4.10.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.
- package/README.md +2 -2
- package/bin/morph-spec.js +30 -0
- package/bin/task-manager.js +34 -22
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +99 -98
- package/framework/agents.json +37 -7
- package/framework/commands/commit.md +166 -0
- package/framework/commands/morph-apply.md +13 -2
- package/framework/commands/morph-archive.md +8 -2
- package/framework/commands/morph-infra.md +6 -0
- package/framework/commands/morph-preflight.md +6 -0
- package/framework/commands/morph-proposal.md +56 -7
- package/framework/commands/morph-status.md +6 -0
- package/framework/commands/morph-troubleshoot.md +6 -0
- package/framework/hooks/claude-code/notification/approval-reminder.js +3 -2
- package/framework/hooks/claude-code/post-tool-use/dispatch.js +154 -31
- package/framework/hooks/claude-code/post-tool-use/skill-reminder.js +7 -84
- package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +8 -17
- package/framework/hooks/claude-code/pre-compact/save-morph-context.js +16 -3
- package/framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js +4 -3
- package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +3 -2
- package/framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js +60 -0
- package/framework/hooks/claude-code/session-start/inject-morph-context.js +55 -2
- package/framework/hooks/claude-code/session-start/post-compact-restore.js +41 -0
- package/framework/hooks/claude-code/stop/validate-completion.js +2 -15
- package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +23 -5
- package/framework/hooks/shared/compact-restore.js +100 -0
- package/framework/hooks/shared/dispatch-helpers.js +116 -0
- package/framework/hooks/shared/phase-utils.js +9 -5
- package/framework/hooks/shared/state-reader.js +27 -3
- package/framework/phases.json +30 -7
- package/framework/rules/morph-workflow.md +88 -86
- package/framework/skills/level-0-meta/mcp-registry.json +86 -51
- package/framework/skills/level-0-meta/{brainstorming → morph-brainstorming}/SKILL.md +13 -16
- package/framework/skills/level-0-meta/{code-review → morph-code-review}/SKILL.md +1 -1
- package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/SKILL.md +2 -2
- package/framework/skills/level-0-meta/{frontend-review → morph-frontend-review}/SKILL.md +5 -5
- package/framework/skills/level-0-meta/morph-init/SKILL.md +72 -7
- package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/SKILL.md +9 -9
- package/framework/skills/level-0-meta/morph-replicate/SKILL.md +1 -1
- package/framework/skills/level-0-meta/{terminal-title → morph-terminal-title}/SKILL.md +1 -1
- package/framework/skills/level-0-meta/{tool-usage-guide → morph-tool-usage-guide}/SKILL.md +2 -3
- package/framework/skills/level-0-meta/{tool-usage-guide → morph-tool-usage-guide}/references/tools-per-phase.md +1 -2
- package/framework/skills/level-0-meta/{verification-before-completion → morph-verification-before-completion}/SKILL.md +1 -1
- package/framework/skills/level-0-meta/{verification-before-completion → morph-verification-before-completion}/scripts/check-phase-outputs.mjs +2 -2
- package/framework/skills/level-1-workflows/morph-phase-clarify/SKILL.md +238 -0
- package/framework/skills/level-1-workflows/{phase-codebase-analysis → morph-phase-codebase-analysis}/SKILL.md +251 -251
- package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +507 -0
- package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/SKILL.md +590 -491
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/code-quality-reviewer-prompt.md +50 -0
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/implementer-prompt.md +45 -0
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/spec-reviewer-prompt.md +47 -0
- package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +254 -0
- package/framework/skills/level-1-workflows/{phase-setup → morph-phase-setup}/SKILL.md +237 -194
- package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/SKILL.md +307 -270
- package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/scripts/validate-tasks.mjs +3 -3
- package/framework/skills/level-1-workflows/{phase-uiux → morph-phase-uiux}/SKILL.md +320 -285
- package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +97 -0
- package/framework/standards/integration/mcp/mcp-tools.md +25 -7
- package/framework/templates/docs/onboarding.md +2 -2
- package/package.json +1 -2
- package/src/commands/agents/dispatch-agents.js +50 -3
- package/src/commands/mcp/mcp-setup.js +39 -2
- package/src/commands/phase/phase-reset.js +74 -0
- package/src/commands/project/doctor.js +19 -5
- package/src/commands/scope/escalate.js +215 -0
- package/src/commands/state/advance-phase.js +27 -53
- package/src/commands/state/state.js +1 -1
- package/src/commands/task/expand.js +100 -0
- package/src/core/paths/output-schema.js +4 -3
- package/src/core/state/phase-state-machine.js +7 -4
- package/src/core/state/state-manager.js +4 -3
- package/src/lib/detectors/claude-config-detector.js +93 -347
- package/src/lib/detectors/design-system-detector.js +189 -189
- package/src/lib/detectors/index.js +155 -57
- package/src/lib/generators/context-generator.js +2 -2
- package/src/lib/installers/mcp-installer.js +37 -5
- package/src/lib/phase-chain/phase-validator.js +22 -16
- package/src/lib/scope/impact-analyzer.js +106 -0
- package/src/lib/tasks/task-parser.js +1 -1
- package/src/lib/validators/shared/emit-validator-dispatch.js +64 -0
- package/src/scripts/setup-infra.js +15 -0
- package/src/utils/agents-installer.js +32 -12
- package/src/utils/file-copier.js +0 -1
- package/src/utils/hooks-installer.js +15 -1
- package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +0 -216
- package/framework/skills/level-1-workflows/phase-design/SKILL.md +0 -383
- package/src/commands/project/index.js +0 -8
- package/src/core/index.js +0 -10
- package/src/core/state/index.js +0 -8
- package/src/core/templates/index.js +0 -9
- package/src/core/templates/template-data-sources.js +0 -325
- package/src/core/workflows/index.js +0 -7
- package/src/lib/detectors/config-detector.js +0 -223
- package/src/lib/detectors/standards-generator.js +0 -335
- package/src/lib/detectors/structure-detector.js +0 -275
- package/src/lib/monitor/agent-resolver.js +0 -144
- package/src/lib/monitor/renderer.js +0 -230
- package/src/lib/orchestration/index.js +0 -7
- package/src/lib/orchestration/team-orchestrator.js +0 -404
- package/src/sanitizer/context-sanitizer.js +0 -221
- package/src/sanitizer/patterns.js +0 -163
- package/src/writer/file-writer.js +0 -86
- /package/framework/skills/level-0-meta/{brainstorming → morph-brainstorming}/references/proposal-example.md +0 -0
- /package/framework/skills/level-0-meta/{code-review → morph-code-review}/references/review-example.md +0 -0
- /package/framework/skills/level-0-meta/{code-review → morph-code-review}/references/review-guidelines.md +0 -0
- /package/framework/skills/level-0-meta/{code-review → morph-code-review}/scripts/scan-csharp.mjs +0 -0
- /package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/references/review-example-nextjs.md +0 -0
- /package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/scripts/scan-nextjs.mjs +0 -0
- /package/framework/skills/level-0-meta/{frontend-review → morph-frontend-review}/scripts/scan-accessibility.mjs +0 -0
- /package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/scripts/detect-dev-server.mjs +0 -0
- /package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/scripts/detect-stack.mjs +0 -0
- /package/framework/skills/level-0-meta/{simulation-checklist → morph-simulation-checklist}/SKILL.md +0 -0
- /package/framework/skills/level-0-meta/{terminal-title → morph-terminal-title}/scripts/set_title.sh +0 -0
- /package/framework/skills/level-1-workflows/{phase-clarify → morph-phase-clarify}/references/clarifications-example.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/architecture-analysis-guide.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/spec-authoring-guide.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/spec-example.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/references/recap-example.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/references/vsa-implementation-guide.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/references/task-planning-patterns.md +0 -0
- /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
|
-
*
|
|
5
|
-
*
|
|
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
|
|
14
|
-
*
|
|
15
|
-
* @param {
|
|
16
|
-
* @
|
|
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
|
|
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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
-
*
|
|
257
|
-
*
|
|
258
|
-
* @
|
|
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
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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
|
|
300
|
-
*
|
|
301
|
-
* @
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
135
|
+
return parts.join(' | ') || 'No Claude config detected';
|
|
390
136
|
}
|