@polymorphism-tech/morph-spec 4.22.0 → 5.0.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.
- package/CLAUDE.md +57 -71
- package/README.md +2 -2
- package/bin/morph-spec.js +224 -491
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +55 -38
- package/framework/MORPH.md +244 -0
- package/framework/agents.json +127 -1198
- package/framework/commands/morph-apply.md +56 -240
- package/framework/commands/morph-archive.md +48 -85
- package/framework/commands/morph-preflight.md +7 -7
- package/framework/commands/morph-proposal.md +35 -191
- package/framework/commands/morph-status.md +19 -50
- package/framework/commands/morph-troubleshoot.md +2 -2
- package/framework/evals/rubrics/README.md +44 -0
- package/framework/evals/rubrics/architecture.md +46 -0
- package/framework/evals/rubrics/code-quality.md +50 -0
- package/framework/evals/rubrics/contracts.md +47 -0
- package/framework/evals/rubrics/test-coverage.md +50 -0
- package/framework/evals/scenarios/dotnet-crud.yaml +42 -0
- package/framework/evals/scenarios/nextjs-auth.yaml +38 -0
- package/framework/evals/suites/standard.yaml +5 -0
- package/framework/evals/trace-schema.md +67 -0
- package/framework/hooks/claude-code/core/README.md +3 -0
- package/framework/hooks/claude-code/core/denylist.json +14 -0
- package/framework/hooks/claude-code/{pre-tool-use → core}/enforce-phase-writes.js +5 -5
- package/framework/hooks/claude-code/core/post-edit-typecheck.js +69 -0
- package/framework/hooks/claude-code/core/pre-bash-denylist.js +29 -0
- package/framework/hooks/claude-code/{pre-tool-use → core}/protect-spec-files.js +4 -6
- package/framework/hooks/claude-code/{stop → core}/validate-completion.js +13 -82
- package/framework/hooks/shared/phase-utils.js +20 -31
- package/framework/hooks/shared/state-reader.js +4 -5
- package/framework/{skills/level-0-meta/mcp-registry.json → mcp/registry.json} +254 -243
- package/framework/phases.json +41 -115
- package/framework/rubrics/eval-rubric.json +66 -0
- package/framework/skills/README.md +39 -64
- package/framework/skills/core/README.md +3 -0
- package/framework/skills/core/morph-brainstorming/SKILL.md +102 -0
- package/framework/skills/core/morph-eval/SKILL.md +57 -0
- package/framework/skills/core/morph-implement/SKILL.md +83 -0
- package/framework/skills/core/morph-plan/SKILL.md +64 -0
- package/framework/skills/core/morph-review/SKILL.md +62 -0
- package/framework/skills/core/morph-stack-scan/SKILL.md +109 -0
- package/framework/skills/core/morph-standards/SKILL.md +49 -0
- package/framework/templates/CLAUDE.md.core.guide.md +29 -0
- package/framework/templates/CLAUDE.md.core.template +22 -0
- package/framework/templates/REGISTRY.json +4 -4
- package/framework/workflows/configs/fast-track.json +5 -10
- package/framework/workflows/configs/full.json +4 -8
- package/framework/workflows/configs/quick.json +32 -0
- package/framework/workflows/configs/standard.json +57 -64
- package/package.json +7 -6
- package/src/commands/mcp/mcp-setup.js +58 -31
- package/src/commands/project/doctor.js +69 -70
- package/src/commands/project/init.js +26 -5
- package/src/commands/project/status.js +18 -19
- package/src/commands/state/approve.js +1 -1
- package/src/commands/validation/validate-feature.js +18 -112
- package/src/core/paths/output-schema.js +133 -152
- package/src/core/state/phase-state-machine.js +10 -24
- package/src/core/state/state-manager.js +17 -17
- package/src/core/workflows/workflow-detector.js +214 -31
- package/src/core/workflows/workflow-router.js +204 -0
- package/src/dashboard/app/pages/AgentMap.jsx +32 -33
- package/src/dashboard/app/pages/LiveState.jsx +1 -1
- package/src/dashboard/app/pages/PhasePipeline.jsx +2 -4
- package/src/dashboard/app/pages/SkillsLibrary.jsx +2 -3
- package/src/dashboard/app/pages/Workflows.jsx +2 -2
- package/src/dashboard/scanner/agents-scanner.js +30 -35
- package/src/lib/agents/metrics-aggregator.js +1 -1
- package/src/lib/agents/output-collector.js +7 -9
- package/src/lib/context/context-builder.js +289 -0
- package/src/lib/detectors/claude-config-detector.js +19 -3
- package/src/lib/generators/context-generator.js +20 -42
- package/src/lib/generators/index.js +1 -0
- package/src/lib/generators/mandate-generator.js +512 -0
- package/src/lib/generators/metadata-extractor.js +1 -1
- package/src/lib/generators/recap-generator.js +3 -4
- package/src/lib/installers/mcp-installer.js +300 -92
- package/src/lib/phase-chain/phase-validator.js +36 -40
- package/src/lib/stack/stack-profile.js +74 -91
- package/src/lib/tasks/task-parser.js +1 -1
- package/src/lib/tasks/wave-resolver.js +353 -0
- package/src/lib/validators/checkpoint-persistence.js +2 -2
- package/src/lib/validators/content/cross-phase-validator.js +281 -0
- package/src/lib/validators/content/index.js +1 -0
- package/src/lib/validators/shared/emit-validator-dispatch.js +6 -56
- package/src/scripts/global-install.js +2 -27
- package/src/utils/agents-installer.js +224 -447
- package/src/utils/claude-settings-manager.js +57 -15
- package/src/utils/file-copier.js +1 -0
- package/src/utils/hooks-installer.js +15 -117
- package/src/utils/skills-installer.js +12 -18
- package/bin/detect-agents.js +0 -226
- package/bin/task-manager.js +0 -1036
- package/framework/agents/README.md +0 -13
- package/framework/agents/ai-agents/ai-system-architect.md +0 -194
- package/framework/agents/ai-agents/vector-search-expert.md +0 -247
- package/framework/agents/architecture/architecture-expert.md +0 -98
- package/framework/agents/architecture/morph-spec-architect.md +0 -66
- package/framework/agents/architecture/packages-validator.md +0 -89
- package/framework/agents/architecture/security-expert.md +0 -79
- package/framework/agents/architecture/seo-growth-hacker.md +0 -168
- package/framework/agents/architecture/standards-architect.md +0 -153
- package/framework/agents/backend/dotnet-senior.md +0 -163
- package/framework/agents/backend/ef-modeler.md +0 -119
- package/framework/agents/backend/hangfire-orchestrator.md +0 -133
- package/framework/agents/backend/ms-agent-expert.md +0 -55
- package/framework/agents/backend/neon-expert.md +0 -379
- package/framework/agents/frontend/css-specialist.md +0 -89
- package/framework/agents/frontend/design-system-validator.md +0 -100
- package/framework/agents/frontend/gsap-animator.md +0 -131
- package/framework/agents/frontend/nextjs-component-validator.md +0 -97
- package/framework/agents/frontend/nextjs-expert.md +0 -201
- package/framework/agents/frontend/ui-designer.md +0 -259
- package/framework/agents/infrastructure/infra-architect.md +0 -215
- package/framework/agents/integrations/asaas-financial.md +0 -138
- package/framework/agents/integrations/neon-auth.md +0 -151
- package/framework/agents/integrations/resend-email.md +0 -127
- package/framework/hooks/claude-code/post-tool-use/agent-complete.js +0 -122
- package/framework/hooks/claude-code/post-tool-use/dispatch.js +0 -363
- package/framework/hooks/claude-code/post-tool-use/handle-tool-failure.js +0 -78
- package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +0 -194
- package/framework/hooks/claude-code/pre-compact/save-morph-context.js +0 -199
- package/framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js +0 -55
- package/framework/hooks/claude-code/session-start/inject-morph-context.js +0 -323
- package/framework/hooks/claude-code/session-start/post-compact-restore.js +0 -41
- package/framework/hooks/claude-code/session-start/resume-context.js +0 -77
- package/framework/hooks/claude-code/teammate-idle/teammate-idle.js +0 -90
- package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +0 -114
- package/framework/hooks/shared/compact-restore.js +0 -123
- package/framework/hooks/shared/dispatch-helpers.js +0 -147
- package/framework/hooks/shared/skill-reminder-helpers.js +0 -79
- package/framework/hooks/shared/stale-task-reset.js +0 -57
- package/framework/hooks/shared/worktree-helpers.js +0 -75
- package/framework/skills/level-0-meta/README.md +0 -7
- package/framework/skills/level-0-meta/morph-agent-teams/SKILL.md +0 -472
- package/framework/skills/level-0-meta/morph-brainstorming/SKILL.md +0 -174
- package/framework/skills/level-0-meta/morph-brainstorming/references/proposal-example.md +0 -138
- package/framework/skills/level-0-meta/morph-checklist/SKILL.md +0 -145
- package/framework/skills/level-0-meta/morph-code-review/SKILL.md +0 -250
- package/framework/skills/level-0-meta/morph-code-review/references/review-example.md +0 -164
- package/framework/skills/level-0-meta/morph-code-review/references/review-guidelines.md +0 -100
- package/framework/skills/level-0-meta/morph-code-review/scripts/scan-csharp.mjs +0 -151
- package/framework/skills/level-0-meta/morph-code-review-nextjs/SKILL.md +0 -165
- package/framework/skills/level-0-meta/morph-code-review-nextjs/references/review-example-nextjs.md +0 -254
- package/framework/skills/level-0-meta/morph-code-review-nextjs/scripts/scan-nextjs.mjs +0 -189
- package/framework/skills/level-0-meta/morph-frontend-review/SKILL.md +0 -344
- package/framework/skills/level-0-meta/morph-frontend-review/scripts/scan-accessibility.mjs +0 -376
- package/framework/skills/level-0-meta/morph-gemini-image/SKILL.md +0 -290
- package/framework/skills/level-0-meta/morph-init/SKILL.md +0 -537
- package/framework/skills/level-0-meta/morph-parallel-dispatch/SKILL.md +0 -225
- package/framework/skills/level-0-meta/morph-post-implementation/SKILL.md +0 -401
- package/framework/skills/level-0-meta/morph-post-implementation/scripts/detect-dev-server.mjs +0 -153
- package/framework/skills/level-0-meta/morph-post-implementation/scripts/detect-stack.mjs +0 -234
- package/framework/skills/level-0-meta/morph-simulation-checklist/SKILL.md +0 -106
- package/framework/skills/level-0-meta/morph-tool-usage-guide/SKILL.md +0 -243
- package/framework/skills/level-0-meta/morph-tool-usage-guide/references/tools-per-phase.md +0 -169
- package/framework/skills/level-0-meta/morph-verification-before-completion/SKILL.md +0 -148
- package/framework/skills/level-0-meta/morph-verification-before-completion/scripts/check-phase-outputs.mjs +0 -110
- package/framework/skills/level-1-workflows/README.md +0 -7
- package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +0 -269
- package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +0 -453
- package/framework/skills/level-1-workflows/morph-phase-design/references/architecture-analysis-guide.md +0 -44
- package/framework/skills/level-1-workflows/morph-phase-design/references/spec-authoring-guide.md +0 -55
- package/framework/skills/level-1-workflows/morph-phase-design/references/spec-example.md +0 -253
- package/framework/skills/level-1-workflows/morph-phase-implement/.morph/logs/activity.json +0 -38
- package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +0 -634
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/code-quality-reviewer-prompt.md +0 -50
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/implementer-prompt.md +0 -54
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/spec-reviewer-prompt.md +0 -47
- package/framework/skills/level-1-workflows/morph-phase-implement/references/recap-example.md +0 -132
- package/framework/skills/level-1-workflows/morph-phase-implement/references/vsa-implementation-guide.md +0 -92
- package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +0 -257
- package/framework/skills/level-1-workflows/morph-phase-tasks/.morph/logs/activity.json +0 -14
- package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +0 -291
- package/framework/skills/level-1-workflows/morph-phase-tasks/references/task-planning-patterns.md +0 -143
- package/framework/skills/level-1-workflows/morph-phase-tasks/references/tasks-example.md +0 -404
- package/framework/skills/level-1-workflows/morph-phase-tasks/scripts/validate-tasks.mjs +0 -112
- package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +0 -995
- package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +0 -147
- package/framework/squad-templates/backend-only.json +0 -47
- package/framework/squad-templates/frontend-only.json +0 -56
- package/framework/squad-templates/full-stack.json +0 -74
- package/src/commands/agents/agent-metrics.js +0 -174
- package/src/commands/agents/audit-dispatch.js +0 -82
- package/src/commands/agents/detect-agents.js +0 -275
- package/src/commands/agents/dispatch-agents.js +0 -461
- package/src/commands/agents/index.js +0 -13
- package/src/commands/agents/manage-agents.js +0 -352
- package/src/commands/agents/project-metrics.js +0 -74
- package/src/commands/phase/phase-reset.js +0 -74
- package/src/commands/scope/escalate.js +0 -217
- package/src/commands/state/advance-phase.js +0 -600
- package/src/commands/state/phase-runner.js +0 -215
- package/src/commands/state/state.js +0 -444
- package/src/commands/task/expand.js +0 -100
- package/src/commands/tasks/index.js +0 -4
- package/src/commands/tasks/task.js +0 -105
- package/src/commands/templates/index.js +0 -5
- package/src/commands/templates/template-render.js +0 -93
- package/src/commands/trust/trust.js +0 -118
- package/src/lib/agents/agents-manager.js +0 -349
- package/src/lib/build/build-runner.js +0 -99
- package/src/lib/checkpoints/checkpoint-hooks.js +0 -111
- package/src/lib/contracts/sprint-contract-generator.js +0 -137
- package/src/lib/scope/impact-analyzer.js +0 -106
- package/src/lib/trust/trust-manager.js +0 -157
- package/src/utils/tier-helpers.js +0 -24
- /package/framework/hooks/claude-code/{post-tool-use → core}/loop-detect.js +0 -0
|
@@ -1,447 +1,224 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Agents Installer
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* @param {string}
|
|
21
|
-
* @param {
|
|
22
|
-
* @param {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
const
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
const eligible = agents.filter(a => {
|
|
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
|
-
for (const
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
const
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
*
|
|
226
|
-
* @param {string|{id: string, scope?: string, priority?: string}} standard
|
|
227
|
-
* @returns {string} e.g., "- core-coding (all, required)" or "- core-coding"
|
|
228
|
-
*/
|
|
229
|
-
export function formatStandardRef(standard) {
|
|
230
|
-
if (typeof standard === 'string') return `- ${standard}`;
|
|
231
|
-
const id = standard.id || String(standard);
|
|
232
|
-
const parts = [standard.scope, standard.priority].filter(Boolean);
|
|
233
|
-
return parts.length > 0 ? `- ${id} (${parts.join(', ')})` : `- ${id}`;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
function buildBody(agent) {
|
|
237
|
-
const lines = [];
|
|
238
|
-
if (agent.teammate?.spawn_prompt) {
|
|
239
|
-
lines.push(agent.teammate.spawn_prompt);
|
|
240
|
-
} else {
|
|
241
|
-
lines.push(`You are the ${agent.title} agent in the MORPH-SPEC framework.`);
|
|
242
|
-
if (agent.domains?.length) {
|
|
243
|
-
lines.push(`\n## Domains\n${agent.domains.map(d => `- ${d}`).join('\n')}`);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// Task routing — tells the agent which task categories it handles
|
|
248
|
-
const categories = GROUP_TO_CATEGORIES[agent.group];
|
|
249
|
-
if (categories) {
|
|
250
|
-
lines.push(`\n## Task Routing\nHandle tasks categorized as: ${categories}`);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// Key standards — @ references for upfront awareness
|
|
254
|
-
if (agent.standards?.length) {
|
|
255
|
-
lines.push(`\n## Key Standards\n${agent.standards.map(formatStandardRef).join('\n')}`);
|
|
256
|
-
}
|
|
257
|
-
return lines.join('\n');
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/**
|
|
261
|
-
* Parses YAML frontmatter from a skill markdown file.
|
|
262
|
-
* Handles both inline values and YAML block scalars (>).
|
|
263
|
-
*
|
|
264
|
-
* @param {string} rawContent - Raw file content
|
|
265
|
-
* @returns {{ name: string|null, description: string|null, allowedTools: string, color: string|null, effort: string|null, permissionMode: string|null, background: boolean, stacks: string[], body: string }}
|
|
266
|
-
*/
|
|
267
|
-
export function parseSkillFile(rawContent) {
|
|
268
|
-
// Normalize CRLF to LF for consistent parsing on Windows
|
|
269
|
-
const content = rawContent.replace(/\r\n/g, '\n');
|
|
270
|
-
const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
271
|
-
if (!match) return { name: null, description: null, allowedTools: 'Read, Write, Edit, Bash, Glob, Grep', color: null, effort: null, permissionMode: null, background: false, body: rawContent };
|
|
272
|
-
|
|
273
|
-
const fm = match[1];
|
|
274
|
-
const body = match[2];
|
|
275
|
-
|
|
276
|
-
const name = (fm.match(/^name:\s*(.+)$/m) ?? [])[1]?.trim() ?? null;
|
|
277
|
-
const allowedTools = (fm.match(/^allowed-tools:\s*(.+)$/m) ?? [])[1]?.trim() ?? 'Read, Write, Edit, Bash, Glob, Grep';
|
|
278
|
-
const color = (fm.match(/^color:\s*(.+)$/m) ?? [])[1]?.trim() ?? null;
|
|
279
|
-
const effort = (fm.match(/^effort:\s*(.+)$/m) ?? [])[1]?.trim() ?? null;
|
|
280
|
-
const permissionMode = (fm.match(/^permissionMode:\s*(.+)$/m) ?? [])[1]?.trim() ?? null;
|
|
281
|
-
const background = (fm.match(/^background:\s*(.+)$/m) ?? [])[1]?.trim() === 'true';
|
|
282
|
-
|
|
283
|
-
let description = null;
|
|
284
|
-
const blockMatch = fm.match(/^description:\s*>\s*\n((?:[ \t]+.*\n?)*)/m);
|
|
285
|
-
if (blockMatch) {
|
|
286
|
-
description = blockMatch[1].trim().replace(/\s+/g, ' ');
|
|
287
|
-
} else {
|
|
288
|
-
const inlineMatch = fm.match(/^description:\s*(.+)$/m);
|
|
289
|
-
if (inlineMatch) description = inlineMatch[1].trim();
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
const stacks = parseStacks(rawContent);
|
|
293
|
-
return { name, description, allowedTools, color, effort, permissionMode, background, stacks, body };
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
/**
|
|
297
|
-
* Recursively collects all .md skill files from a directory, excluding README.md.
|
|
298
|
-
*
|
|
299
|
-
* @param {string} dir - Directory to walk
|
|
300
|
-
* @returns {string[]} Absolute paths to skill .md files
|
|
301
|
-
*/
|
|
302
|
-
export function collectSkillFiles(dir) {
|
|
303
|
-
const results = [];
|
|
304
|
-
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
305
|
-
const fullPath = join(dir, entry.name);
|
|
306
|
-
if (entry.isDirectory()) {
|
|
307
|
-
results.push(...collectSkillFiles(fullPath));
|
|
308
|
-
} else if (entry.name.endsWith('.md') && entry.name !== 'README.md') {
|
|
309
|
-
results.push(fullPath);
|
|
310
|
-
}
|
|
311
|
-
}
|
|
312
|
-
return results;
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
/**
|
|
316
|
-
* Installs domain agent files as native Claude Code subagents
|
|
317
|
-
* in .claude/agents/ with memory:project, model:sonnet, maxTurns:20.
|
|
318
|
-
* Supports optional CC-native fields (color, effort, permissionMode, background)
|
|
319
|
-
* declared in each domain file's YAML frontmatter.
|
|
320
|
-
*
|
|
321
|
-
* @param {string} projectDir - Target project directory
|
|
322
|
-
* @param {string} frameworkDir - Path to morph-spec framework/ directory
|
|
323
|
-
*/
|
|
324
|
-
export async function installDomainAgents(projectDir, frameworkDir = 'framework', { projectTags = [] } = {}) {
|
|
325
|
-
const domainsDir = join(frameworkDir, 'agents');
|
|
326
|
-
if (!existsSync(domainsDir)) return { specialists: 0 };
|
|
327
|
-
|
|
328
|
-
const targetDir = join(projectDir, '.claude', 'agents');
|
|
329
|
-
mkdirSync(targetDir, { recursive: true });
|
|
330
|
-
|
|
331
|
-
let specialists = 0;
|
|
332
|
-
const installed = new Set();
|
|
333
|
-
for (const filePath of collectSkillFiles(domainsDir)) {
|
|
334
|
-
const raw = readFileSync(filePath, 'utf-8');
|
|
335
|
-
const { name, description, allowedTools, color, effort, permissionMode, background, stacks, body } = parseSkillFile(raw);
|
|
336
|
-
if (!name) continue;
|
|
337
|
-
if (!shouldInstall(stacks, projectTags)) continue;
|
|
338
|
-
|
|
339
|
-
const desc = description ?? `MORPH-SPEC domain agent: ${name}`;
|
|
340
|
-
const lines = [
|
|
341
|
-
`name: ${name}`,
|
|
342
|
-
`description: ${desc}`,
|
|
343
|
-
`model: sonnet`,
|
|
344
|
-
`tools: ${allowedTools}`,
|
|
345
|
-
`maxTurns: 20`,
|
|
346
|
-
];
|
|
347
|
-
if (color) lines.push(`color: ${color}`);
|
|
348
|
-
if (effort) lines.push(`effort: ${effort}`);
|
|
349
|
-
if (permissionMode && permissionMode !== 'default') lines.push(`permissionMode: ${permissionMode}`);
|
|
350
|
-
if (background) lines.push(`background: true`);
|
|
351
|
-
lines.push(`memory: project`, '');
|
|
352
|
-
const frontmatter = lines.join('\n');
|
|
353
|
-
|
|
354
|
-
// If agents.json already generated morph-{name}.md, append domain content there
|
|
355
|
-
const agentsJsonFile = `morph-${name}.md`;
|
|
356
|
-
const agentsJsonPath = join(targetDir, agentsJsonFile);
|
|
357
|
-
if (existsSync(agentsJsonPath)) {
|
|
358
|
-
appendFileSync(agentsJsonPath, `\n\n---\n\n## Domain Knowledge\n\n${body.trimStart()}`, 'utf-8');
|
|
359
|
-
specialists++;
|
|
360
|
-
} else {
|
|
361
|
-
const filename = `morph-domain-${name}.md`;
|
|
362
|
-
const targetPath = join(targetDir, filename);
|
|
363
|
-
writeFileSync(targetPath, `---\n${frontmatter}---\n\n${body.trimStart()}`, 'utf-8');
|
|
364
|
-
installed.add(filename);
|
|
365
|
-
specialists++;
|
|
366
|
-
}
|
|
367
|
-
}
|
|
368
|
-
|
|
369
|
-
// Cleanup orphan morph-domain-* files
|
|
370
|
-
for (const file of readdirSync(targetDir)) {
|
|
371
|
-
if (file.startsWith('morph-domain-') && !installed.has(file)) {
|
|
372
|
-
unlinkSync(join(targetDir, file));
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
return { specialists };
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
/**
|
|
380
|
-
* Check which agents SHOULD be installed but are NOT.
|
|
381
|
-
* Returns a list of missing agent names with their source (agents.json or domain).
|
|
382
|
-
*
|
|
383
|
-
* @param {string} projectDir - Target project directory
|
|
384
|
-
* @param {string} frameworkDir - Path to morph-spec framework/ directory
|
|
385
|
-
* @param {string[]} projectTags - Stack + integration tags
|
|
386
|
-
* @returns {{ missing: Array<{ name: string, stacks: string[], source: string }>, installed: number, expected: number }}
|
|
387
|
-
*/
|
|
388
|
-
export function checkMissingAgents(projectDir, frameworkDir, projectTags = []) {
|
|
389
|
-
const targetDir = join(projectDir, '.claude', 'agents');
|
|
390
|
-
const installedFiles = new Set();
|
|
391
|
-
if (existsSync(targetDir)) {
|
|
392
|
-
for (const f of readdirSync(targetDir)) installedFiles.add(f);
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
const missing = [];
|
|
396
|
-
let expected = 0;
|
|
397
|
-
|
|
398
|
-
// Check tier 1/2/4 from agents.json
|
|
399
|
-
const agentsJsonPath = join(frameworkDir, 'agents.json');
|
|
400
|
-
if (existsSync(agentsJsonPath)) {
|
|
401
|
-
const agentsJson = JSON.parse(readFileSync(agentsJsonPath, 'utf-8'));
|
|
402
|
-
const agentsRaw = agentsJson.agents ?? agentsJson;
|
|
403
|
-
const agents = Array.isArray(agentsRaw)
|
|
404
|
-
? agentsRaw
|
|
405
|
-
: Object.entries(agentsRaw)
|
|
406
|
-
.filter(([key]) => !key.startsWith('_comment'))
|
|
407
|
-
.map(([id, data]) => ({ id, ...data }));
|
|
408
|
-
|
|
409
|
-
for (const a of agents) {
|
|
410
|
-
if (a.tier < 1 || a.tier > 4) continue;
|
|
411
|
-
if (a.tier >= 3 && !a.teammate?.spawn_prompt) continue;
|
|
412
|
-
const stacks = a.stacks ?? [];
|
|
413
|
-
if (!shouldInstall(stacks, projectTags)) continue;
|
|
414
|
-
|
|
415
|
-
expected++;
|
|
416
|
-
const slug = a.id ?? a.name?.toLowerCase().replace(/\s+/g, '-');
|
|
417
|
-
const prefix = a.tier === 4 ? 'morph-validator-' : 'morph-';
|
|
418
|
-
const filename = `${prefix}${slug}.md`;
|
|
419
|
-
if (!installedFiles.has(filename)) {
|
|
420
|
-
missing.push({ name: a.title ?? slug, stacks, source: `tier-${a.tier}` });
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
// Check domain agents (skip if merged into agents.json entry)
|
|
426
|
-
const domainsDir = join(frameworkDir, 'agents');
|
|
427
|
-
if (existsSync(domainsDir)) {
|
|
428
|
-
for (const filePath of collectSkillFiles(domainsDir)) {
|
|
429
|
-
const raw = readFileSync(filePath, 'utf-8');
|
|
430
|
-
const { name, stacks } = parseSkillFile(raw);
|
|
431
|
-
if (!name) continue;
|
|
432
|
-
if (!shouldInstall(stacks, projectTags)) continue;
|
|
433
|
-
|
|
434
|
-
// If agents.json has morph-{name}.md, domain content is merged there
|
|
435
|
-
const agentsJsonFile = `morph-${name}.md`;
|
|
436
|
-
if (installedFiles.has(agentsJsonFile)) continue;
|
|
437
|
-
|
|
438
|
-
expected++;
|
|
439
|
-
const filename = `morph-domain-${name}.md`;
|
|
440
|
-
if (!installedFiles.has(filename)) {
|
|
441
|
-
missing.push({ name, stacks, source: 'domain' });
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
return { missing, installed: installedFiles.size, expected };
|
|
447
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Agents Installer (v5)
|
|
3
|
+
*
|
|
4
|
+
* Installs morph-spec domain personas as native Claude Code subagents
|
|
5
|
+
* in .claude/agents/morph-{id}.md.
|
|
6
|
+
*
|
|
7
|
+
* In v5 the harness has 7 domain personas (no tiers) — each persona is a
|
|
8
|
+
* subagent the Specialist (Claude in implementer role) can dispatch when
|
|
9
|
+
* working in that domain. The 3 roles (Orchestrator/Specialist/Evaluator)
|
|
10
|
+
* are assumed by Claude itself per MORPH.md, not as separate agents.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, unlinkSync } from 'node:fs';
|
|
14
|
+
import { join } from 'path';
|
|
15
|
+
import { shouldInstall } from '../lib/stack-filter.js';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Install morph-spec domain personas as native Claude Code subagents.
|
|
19
|
+
*
|
|
20
|
+
* @param {string} projectDir - Target project directory
|
|
21
|
+
* @param {string} frameworkDir - Path to morph-spec framework/ directory
|
|
22
|
+
* @param {object} options
|
|
23
|
+
* @param {string[]} [options.projectTags=[]] - Stack tags from config (e.g. ['dotnet', 'nextjs'])
|
|
24
|
+
* @returns {Promise<{ installed: number }>}
|
|
25
|
+
*/
|
|
26
|
+
export async function installAgents(projectDir, frameworkDir = 'framework', options = {}) {
|
|
27
|
+
const { projectTags = [] } = options;
|
|
28
|
+
|
|
29
|
+
const agentsJsonPath = join(frameworkDir, 'agents.json');
|
|
30
|
+
const agentsJson = JSON.parse(readFileSync(agentsJsonPath, 'utf-8'));
|
|
31
|
+
|
|
32
|
+
const agentsRaw = agentsJson.agents ?? {};
|
|
33
|
+
const agents = Object.entries(agentsRaw)
|
|
34
|
+
.filter(([key]) => !key.startsWith('_comment'))
|
|
35
|
+
.map(([id, data]) => ({ id, ...data }));
|
|
36
|
+
|
|
37
|
+
const targetDir = join(projectDir, '.claude', 'agents');
|
|
38
|
+
mkdirSync(targetDir, { recursive: true });
|
|
39
|
+
|
|
40
|
+
// Filter by stack
|
|
41
|
+
const eligible = agents.filter(a => {
|
|
42
|
+
const stack = a.stack ?? ['*'];
|
|
43
|
+
return shouldInstall(stack, projectTags);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// Build set of all possible filenames for orphan cleanup
|
|
47
|
+
const allPossible = new Set(agents.map(a => `morph-${a.id}.md`));
|
|
48
|
+
|
|
49
|
+
let installed = 0;
|
|
50
|
+
const installedFilenames = new Set();
|
|
51
|
+
|
|
52
|
+
for (const agent of eligible) {
|
|
53
|
+
const filename = `morph-${agent.id}.md`;
|
|
54
|
+
const targetPath = join(targetDir, filename);
|
|
55
|
+
|
|
56
|
+
const description = buildDescription(agent);
|
|
57
|
+
const body = buildBody(agent);
|
|
58
|
+
const frontmatter = buildFrontmatter(agent, description);
|
|
59
|
+
|
|
60
|
+
writeFileSync(targetPath, `---\n${frontmatter}---\n\n${body}\n`, 'utf-8');
|
|
61
|
+
installedFilenames.add(filename);
|
|
62
|
+
installed++;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Cleanup orphan morph-* files filtered out by stack
|
|
66
|
+
for (const file of readdirSync(targetDir)) {
|
|
67
|
+
if (allPossible.has(file) && !installedFilenames.has(file)) {
|
|
68
|
+
unlinkSync(join(targetDir, file));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return { installed };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function buildFrontmatter(agent, description) {
|
|
76
|
+
const lines = [
|
|
77
|
+
`name: ${agent.id}`,
|
|
78
|
+
`description: ${description}`,
|
|
79
|
+
`model: sonnet`,
|
|
80
|
+
`tools: Read, Write, Edit, Bash, Glob, Grep`,
|
|
81
|
+
`maxTurns: 20`,
|
|
82
|
+
];
|
|
83
|
+
if (agent.color) lines.push(`color: ${agent.color}`);
|
|
84
|
+
lines.push(`memory: project`, '');
|
|
85
|
+
return lines.join('\n');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function buildDescription(agent) {
|
|
89
|
+
const role = agent.title ?? agent.id;
|
|
90
|
+
const domains = (agent.domains ?? []).join(', ');
|
|
91
|
+
const desc = domains ? `${role} — domains: ${domains}` : role;
|
|
92
|
+
return desc.length > 200 ? desc.slice(0, 197) + '...' : desc;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function buildBody(agent) {
|
|
96
|
+
const lines = [
|
|
97
|
+
`# ${agent.title ?? agent.id}`,
|
|
98
|
+
'',
|
|
99
|
+
agent.persona ?? `You are the ${agent.title} persona for morph-spec.`,
|
|
100
|
+
'',
|
|
101
|
+
];
|
|
102
|
+
|
|
103
|
+
if (agent.domains?.length) {
|
|
104
|
+
lines.push('## Domains', ...agent.domains.map(d => `- ${d}`), '');
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (agent.standards?.length) {
|
|
108
|
+
lines.push('## Standards to consult on demand');
|
|
109
|
+
for (const s of agent.standards) lines.push(`- ${s}`);
|
|
110
|
+
lines.push('');
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
lines.push(
|
|
114
|
+
'## How you operate',
|
|
115
|
+
'',
|
|
116
|
+
'You are a domain persona dispatched per task. Read the spec/mandate/tasks/standards passed to you, do the work, and return a concise summary plus an auto-score 0-10. Refer to MORPH.md section 6 for the dispatch contract.',
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
return lines.join('\n');
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Compatibility shim for older callers that expected installDomainAgents.
|
|
124
|
+
* In v5 there is no separate "domain" install step — installAgents handles
|
|
125
|
+
* everything from agents.json.
|
|
126
|
+
*/
|
|
127
|
+
export async function installDomainAgents(projectDir, frameworkDir, options) {
|
|
128
|
+
// No-op: domain content is now defined inline in agents.json (persona field).
|
|
129
|
+
return { specialists: 0 };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Check which agents SHOULD be installed but are NOT.
|
|
134
|
+
*
|
|
135
|
+
* @param {string} projectDir
|
|
136
|
+
* @param {string} frameworkDir
|
|
137
|
+
* @param {string[]} projectTags
|
|
138
|
+
* @returns {{ missing: Array<{ name: string, stacks: string[] }>, installed: number, expected: number }}
|
|
139
|
+
*/
|
|
140
|
+
export function checkMissingAgents(projectDir, frameworkDir, projectTags = []) {
|
|
141
|
+
const targetDir = join(projectDir, '.claude', 'agents');
|
|
142
|
+
const installedFiles = new Set();
|
|
143
|
+
if (existsSync(targetDir)) {
|
|
144
|
+
for (const f of readdirSync(targetDir)) installedFiles.add(f);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const agentsJsonPath = join(frameworkDir, 'agents.json');
|
|
148
|
+
if (!existsSync(agentsJsonPath)) {
|
|
149
|
+
return { missing: [], installed: installedFiles.size, expected: 0 };
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const agentsJson = JSON.parse(readFileSync(agentsJsonPath, 'utf-8'));
|
|
153
|
+
const agentsRaw = agentsJson.agents ?? {};
|
|
154
|
+
const agents = Object.entries(agentsRaw)
|
|
155
|
+
.filter(([key]) => !key.startsWith('_comment'))
|
|
156
|
+
.map(([id, data]) => ({ id, ...data }));
|
|
157
|
+
|
|
158
|
+
const missing = [];
|
|
159
|
+
let expected = 0;
|
|
160
|
+
|
|
161
|
+
for (const a of agents) {
|
|
162
|
+
const stack = a.stack ?? ['*'];
|
|
163
|
+
if (!shouldInstall(stack, projectTags)) continue;
|
|
164
|
+
expected++;
|
|
165
|
+
const filename = `morph-${a.id}.md`;
|
|
166
|
+
if (!installedFiles.has(filename)) {
|
|
167
|
+
missing.push({ name: a.title ?? a.id, stacks: stack });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
return { missing, installed: installedFiles.size, expected };
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Compatibility export — kept so existing tests/CLI code that imports
|
|
176
|
+
* formatStandardRef or parseSkillFile / collectSkillFiles still work,
|
|
177
|
+
* but they are no-ops (will be removed in v5.1).
|
|
178
|
+
*/
|
|
179
|
+
export function formatStandardRef(standard) {
|
|
180
|
+
if (typeof standard === 'string') return `- ${standard}`;
|
|
181
|
+
const id = standard.id || String(standard);
|
|
182
|
+
const parts = [standard.scope, standard.priority].filter(Boolean);
|
|
183
|
+
return parts.length > 0 ? `- ${id} (${parts.join(', ')})` : `- ${id}`;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function parseSkillFile(rawContent) {
|
|
187
|
+
const content = rawContent.replace(/\r\n/g, '\n');
|
|
188
|
+
const match = content.match(/^---\n([\s\S]*?)\n---\n?([\s\S]*)$/);
|
|
189
|
+
if (!match) {
|
|
190
|
+
return {
|
|
191
|
+
name: null, description: null, allowedTools: 'Read, Write, Edit, Bash, Glob, Grep',
|
|
192
|
+
color: null, effort: null, permissionMode: null, background: false, stacks: [],
|
|
193
|
+
body: rawContent,
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
const fm = match[1];
|
|
197
|
+
const body = match[2];
|
|
198
|
+
const name = (fm.match(/^name:\s*(.+)$/m) ?? [])[1]?.trim() ?? null;
|
|
199
|
+
const allowedTools = (fm.match(/^allowed-tools:\s*(.+)$/m) ?? [])[1]?.trim() ?? 'Read, Write, Edit, Bash, Glob, Grep';
|
|
200
|
+
const color = (fm.match(/^color:\s*(.+)$/m) ?? [])[1]?.trim() ?? null;
|
|
201
|
+
let description = null;
|
|
202
|
+
const blockMatch = fm.match(/^description:\s*>\s*\n((?:[ \t]+.*\n?)*)/m);
|
|
203
|
+
if (blockMatch) {
|
|
204
|
+
description = blockMatch[1].trim().replace(/\s+/g, ' ');
|
|
205
|
+
} else {
|
|
206
|
+
const inlineMatch = fm.match(/^description:\s*(.+)$/m);
|
|
207
|
+
if (inlineMatch) description = inlineMatch[1].trim();
|
|
208
|
+
}
|
|
209
|
+
return { name, description, allowedTools, color, effort: null, permissionMode: null, background: false, stacks: [], body };
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export function collectSkillFiles(dir) {
|
|
213
|
+
const results = [];
|
|
214
|
+
if (!existsSync(dir)) return results;
|
|
215
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
216
|
+
const fullPath = join(dir, entry.name);
|
|
217
|
+
if (entry.isDirectory()) {
|
|
218
|
+
results.push(...collectSkillFiles(fullPath));
|
|
219
|
+
} else if (entry.name.endsWith('.md') && entry.name !== 'README.md') {
|
|
220
|
+
results.push(fullPath);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return results;
|
|
224
|
+
}
|