@polymorphism-tech/morph-spec 4.10.2 → 4.10.4
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 +6 -6
- package/bin/morph-spec.js +3 -3
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +5 -5
- package/docs/QUICKSTART.md +5 -5
- package/framework/CLAUDE.md +4 -4
- package/framework/agents/infrastructure/azure-deploy-specialist.md +1 -1
- package/framework/agents/quality/code-analyzer.md +1 -1
- package/framework/agents.json +4 -0
- package/framework/commands/morph-apply.md +4 -4
- package/framework/commands/morph-infra.md +10 -10
- package/framework/commands/morph-preflight.md +3 -3
- package/framework/commands/morph-proposal.md +2 -2
- package/framework/commands/morph-status.md +3 -3
- package/framework/hooks/claude-code/pre-compact/save-morph-context.js +1 -1
- package/framework/rules/morph-workflow.md +3 -3
- package/framework/skills/README.md +1 -1
- package/framework/skills/level-0-meta/morph-init/SKILL.md +2 -2
- package/framework/skills/level-1-workflows/morph-phase-clarify/SKILL.md +2 -2
- package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +1 -1
- package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +2 -2
- package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +3 -3
- package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +2 -2
- package/framework/skills/level-1-workflows/morph-phase-setup/SKILL.md +22 -2
- package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +3 -3
- package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +2 -2
- package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +2 -1
- package/framework/standards/ai-agents/team-orchestration.md +1 -1
- package/package.json +1 -1
- package/src/commands/project/doctor.js +27 -4
- package/src/commands/project/init.js +33 -3
- package/src/commands/state/advance-phase.js +7 -7
- package/src/lib/detectors/index.js +86 -38
- package/src/lib/stack-filter.js +11 -4
- package/src/scripts/setup-infra.js +2 -2
- package/src/utils/agents-installer.js +90 -6
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
> Spec-driven development framework for multi-stack projects. Turns feature requests into implementation-ready code through structured, AI-orchestrated phases.
|
|
4
4
|
|
|
5
5
|
**Package:** `@polymorphism-tech/morph-spec`
|
|
6
|
-
**Version:** 4.10.
|
|
6
|
+
**Version:** 4.10.4
|
|
7
7
|
**Requires:** Node.js 18+, Claude Code
|
|
8
8
|
|
|
9
9
|
---
|
|
@@ -125,10 +125,10 @@ These commands are available inside Claude Code after init.
|
|
|
125
125
|
|
|
126
126
|
| Command | Description |
|
|
127
127
|
| --------------------------- | --------------------------------------------------------- |
|
|
128
|
-
| `/morph
|
|
129
|
-
| `/morph
|
|
130
|
-
| `/morph
|
|
131
|
-
| `/morph
|
|
128
|
+
| `/morph:proposal {feature}` | Full spec pipeline (phases 0-4, pauses at approval gates) |
|
|
129
|
+
| `/morph:apply {feature}` | Implement approved feature (phase 5) |
|
|
130
|
+
| `/morph:status` | Feature status dashboard |
|
|
131
|
+
| `/morph:preflight` | Pre-implementation validation |
|
|
132
132
|
|
|
133
133
|
|
|
134
134
|
---
|
|
@@ -376,4 +376,4 @@ Code generated by morph-spec (contracts, templates, implementation output) belon
|
|
|
376
376
|
|
|
377
377
|
---
|
|
378
378
|
|
|
379
|
-
*morph-spec v4.10.
|
|
379
|
+
*morph-spec v4.10.4 by [Polymorphism Tech](https://polymorphism.tech)*
|
package/bin/morph-spec.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
1
|
+
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { program } from 'commander';
|
|
4
4
|
import chalk from 'chalk';
|
|
@@ -77,13 +77,13 @@ program
|
|
|
77
77
|
|
|
78
78
|
program
|
|
79
79
|
.command('setup-infra')
|
|
80
|
-
.description('Install MORPH-SPEC infrastructure (headless, no prompts). Called by /morph
|
|
80
|
+
.description('Install MORPH-SPEC infrastructure (headless, no prompts). Called by /morph:init skill.')
|
|
81
81
|
.option('-p, --path <path>', 'Target path (default: current directory)')
|
|
82
82
|
.action(setupInfraCommand);
|
|
83
83
|
|
|
84
84
|
program
|
|
85
85
|
.command('install-plugin <name>')
|
|
86
|
-
.description('Install a Claude Code plugin (superpowers, context7). Called by /morph
|
|
86
|
+
.description('Install a Claude Code plugin (superpowers, context7). Called by /morph:init skill.')
|
|
87
87
|
.action(installPluginCommand);
|
|
88
88
|
|
|
89
89
|
program
|
package/claude-plugin.json
CHANGED
package/docs/CHEATSHEET.md
CHANGED
|
@@ -39,10 +39,10 @@ Phases in brackets are optional.
|
|
|
39
39
|
|
|
40
40
|
| Command | Description |
|
|
41
41
|
|---------|-------------|
|
|
42
|
-
| `/morph
|
|
43
|
-
| `/morph
|
|
44
|
-
| `/morph
|
|
45
|
-
| `/morph
|
|
42
|
+
| `/morph:proposal {feature}` | Full spec pipeline — phases 0–4 with approval gates |
|
|
43
|
+
| `/morph:apply {feature}` | Implement approved feature — phase 5 |
|
|
44
|
+
| `/morph:status` | Project-wide feature status dashboard |
|
|
45
|
+
| `/morph:preflight` | Pre-implementation validation (7 checks) |
|
|
46
46
|
|
|
47
47
|
---
|
|
48
48
|
|
|
@@ -198,4 +198,4 @@ These files are never edited directly. Use CLI commands or `morph-spec update` i
|
|
|
198
198
|
|
|
199
199
|
---
|
|
200
200
|
|
|
201
|
-
*morph-spec v4.10.
|
|
201
|
+
*morph-spec v4.10.4 by Polymorphism Tech*
|
package/docs/QUICKSTART.md
CHANGED
|
@@ -45,7 +45,7 @@ morph-spec doctor
|
|
|
45
45
|
Open your project in Claude Code and run:
|
|
46
46
|
|
|
47
47
|
```
|
|
48
|
-
/morph
|
|
48
|
+
/morph:proposal my-feature
|
|
49
49
|
```
|
|
50
50
|
|
|
51
51
|
Replace `my-feature` with a short kebab-case name for your feature (e.g., `user-notifications`, `payment-flow`).
|
|
@@ -90,7 +90,7 @@ Claude breaks the spec into atomic tasks (`T001`, `T002`, …) saved to `3-tasks
|
|
|
90
90
|
At this point, planning is complete:
|
|
91
91
|
|
|
92
92
|
```
|
|
93
|
-
✅ Planning complete — run /morph
|
|
93
|
+
✅ Planning complete — run /morph:apply my-feature to implement
|
|
94
94
|
```
|
|
95
95
|
|
|
96
96
|
---
|
|
@@ -98,7 +98,7 @@ At this point, planning is complete:
|
|
|
98
98
|
## Implement
|
|
99
99
|
|
|
100
100
|
```
|
|
101
|
-
/morph
|
|
101
|
+
/morph:apply my-feature
|
|
102
102
|
```
|
|
103
103
|
|
|
104
104
|
Claude reads `spec.md`, `contracts.cs`, and `tasks.md`, then implements task by task:
|
|
@@ -184,7 +184,7 @@ morph-spec doctor
|
|
|
184
184
|
|
|
185
185
|
## Tips
|
|
186
186
|
|
|
187
|
-
**Use `/morph
|
|
187
|
+
**Use `/morph:proposal` even for small features.** The proposal phase catches scope creep, identifies the right agents, and generates contracts early — before implementation makes it expensive to change direction.
|
|
188
188
|
|
|
189
189
|
**Approve explicitly at each gate.** The design gate exists to prevent implementing the wrong thing. If something in `spec.md` or `contracts.cs` looks off, say so before approving.
|
|
190
190
|
|
|
@@ -203,4 +203,4 @@ morph-spec doctor
|
|
|
203
203
|
|
|
204
204
|
---
|
|
205
205
|
|
|
206
|
-
*morph-spec v4.10.
|
|
206
|
+
*morph-spec v4.10.4 by Polymorphism Tech*
|
package/framework/CLAUDE.md
CHANGED
|
@@ -23,10 +23,10 @@
|
|
|
23
23
|
|
|
24
24
|
| Command | Purpose |
|
|
25
25
|
|---------|---------|
|
|
26
|
-
| `/morph
|
|
27
|
-
| `/morph
|
|
28
|
-
| `/morph
|
|
29
|
-
| `/morph
|
|
26
|
+
| `/morph:proposal {feature}` | Full spec pipeline (pauses for approval) |
|
|
27
|
+
| `/morph:apply {feature}` | Implement feature |
|
|
28
|
+
| `/morph:status` | Status dashboard |
|
|
29
|
+
| `/morph:preflight` | Pre-deploy validation |
|
|
30
30
|
|
|
31
31
|
## Agents
|
|
32
32
|
|
|
@@ -256,7 +256,7 @@ O projeto nao possui arquivos de infraestrutura em `infra/`.
|
|
|
256
256
|
|
|
257
257
|
Opcoes:
|
|
258
258
|
1. **Criar agora** - Gerar templates baseado no projeto detectado
|
|
259
|
-
2. **Usar /morph
|
|
259
|
+
2. **Usar /morph:infra** - Rodar comando de setup de infra primeiro
|
|
260
260
|
3. **Cancelar** - Abortar deploy
|
|
261
261
|
|
|
262
262
|
Qual opcao voce prefere?
|
|
@@ -18,7 +18,7 @@ Deep code analysis specialist. Automates architecture review, clean code checks,
|
|
|
18
18
|
|
|
19
19
|
| Trigger | Automatic? | Scope |
|
|
20
20
|
|---------|-----------|-------|
|
|
21
|
-
| FASE 5 completion (`/morph
|
|
21
|
+
| FASE 5 completion (`/morph:apply` done) | Yes | Full feature code |
|
|
22
22
|
| Checkpoint (every 3 tasks) | Yes | Code since last checkpoint |
|
|
23
23
|
| Manual: `analyze`, `review code`, `code quality` | No | Specified scope |
|
|
24
24
|
| Pre-merge review | Recommended | All changed files |
|
package/framework/agents.json
CHANGED
|
@@ -178,6 +178,7 @@
|
|
|
178
178
|
"tier": 2,
|
|
179
179
|
"role": "domain-leader",
|
|
180
180
|
"title": "Backend Squad Leader",
|
|
181
|
+
"stacks": ["dotnet", "blazor"],
|
|
181
182
|
"domains": [
|
|
182
183
|
"backend"
|
|
183
184
|
],
|
|
@@ -249,6 +250,7 @@
|
|
|
249
250
|
"tier": 2,
|
|
250
251
|
"role": "domain-leader",
|
|
251
252
|
"title": "Infrastructure Squad Leader",
|
|
253
|
+
"stacks": ["azure", "docker"],
|
|
252
254
|
"domains": [
|
|
253
255
|
"infrastructure"
|
|
254
256
|
],
|
|
@@ -2322,6 +2324,7 @@
|
|
|
2322
2324
|
"tier": 4,
|
|
2323
2325
|
"role": "validator",
|
|
2324
2326
|
"title": "Blazor Concurrency Validator",
|
|
2327
|
+
"stacks": ["blazor"],
|
|
2325
2328
|
"domains": [
|
|
2326
2329
|
"blazor-concurrency"
|
|
2327
2330
|
],
|
|
@@ -2428,6 +2431,7 @@
|
|
|
2428
2431
|
},
|
|
2429
2432
|
"nextjs-component-validator": {
|
|
2430
2433
|
"tier": 4,
|
|
2434
|
+
"stacks": ["nextjs"],
|
|
2431
2435
|
"title": "Next.js Component Validator",
|
|
2432
2436
|
"description": "Tier-4 validator for Next.js Server/Client component patterns",
|
|
2433
2437
|
"keywords": [
|
|
@@ -4,14 +4,14 @@ argument-hint: <feature-name>
|
|
|
4
4
|
allowed-tools: Read, Write, Edit, Bash, Glob, Grep, Agent, AskUserQuestion
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
# /morph
|
|
7
|
+
# /morph:apply — Feature Implementation
|
|
8
8
|
|
|
9
9
|
Implement the feature by executing its task breakdown, tracking progress through the morph-spec state machine, and producing a recap at the end.
|
|
10
10
|
|
|
11
11
|
## Usage
|
|
12
12
|
|
|
13
13
|
```
|
|
14
|
-
/morph
|
|
14
|
+
/morph:apply {feature-name}
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
## Recommended Tools
|
|
@@ -46,7 +46,7 @@ npx morph-spec validate-feature $ARGUMENTS
|
|
|
46
46
|
|
|
47
47
|
```
|
|
48
48
|
Feature is not ready for implementation.
|
|
49
|
-
Run /morph
|
|
49
|
+
Run /morph:proposal $ARGUMENTS to complete planning phases.
|
|
50
50
|
(Resumes automatically from the current phase.)
|
|
51
51
|
```
|
|
52
52
|
|
|
@@ -192,7 +192,7 @@ After implementation, present a summary to the user:
|
|
|
192
192
|
3. Validation results
|
|
193
193
|
4. Any deviations from spec
|
|
194
194
|
|
|
195
|
-
For Azure projects, remind the user: "Run `/morph
|
|
195
|
+
For Azure projects, remind the user: "Run `/morph:preflight` before deploying to validate packages, migrations, Docker, and secrets."
|
|
196
196
|
|
|
197
197
|
---
|
|
198
198
|
|
|
@@ -4,14 +4,14 @@ argument-hint: [up|down|plan|status]
|
|
|
4
4
|
allowed-tools: Read, Write, Edit, Bash, Glob, AskUserQuestion
|
|
5
5
|
---
|
|
6
6
|
|
|
7
|
-
# /morph
|
|
7
|
+
# /morph:infra - Infrastructure Management
|
|
8
8
|
|
|
9
9
|
Manage Azure infrastructure using Bicep templates.
|
|
10
10
|
|
|
11
11
|
## Usage
|
|
12
12
|
|
|
13
13
|
```
|
|
14
|
-
/morph
|
|
14
|
+
/morph:infra [action] [options]
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
### Actions
|
|
@@ -30,7 +30,7 @@ Manage Azure infrastructure using Bicep templates.
|
|
|
30
30
|
|
|
31
31
|
### 1. INIT - Initialize IaC
|
|
32
32
|
|
|
33
|
-
When the user requests `/morph
|
|
33
|
+
When the user requests `/morph:infra init`:
|
|
34
34
|
|
|
35
35
|
1. Create the `infra/` structure in the project:
|
|
36
36
|
```
|
|
@@ -60,7 +60,7 @@ When the user requests `/morph-infra init`:
|
|
|
60
60
|
|
|
61
61
|
### 2. VALIDATE - Validate Templates
|
|
62
62
|
|
|
63
|
-
When the user requests `/morph
|
|
63
|
+
When the user requests `/morph:infra validate`:
|
|
64
64
|
|
|
65
65
|
1. Run Bicep validation:
|
|
66
66
|
```bash
|
|
@@ -75,7 +75,7 @@ When the user requests `/morph-infra validate`:
|
|
|
75
75
|
|
|
76
76
|
### 3. PLAN - Preview Changes
|
|
77
77
|
|
|
78
|
-
When the user requests `/morph
|
|
78
|
+
When the user requests `/morph:infra plan [env]`:
|
|
79
79
|
|
|
80
80
|
1. Check if Azure CLI is authenticated:
|
|
81
81
|
```bash
|
|
@@ -102,7 +102,7 @@ When the user requests `/morph-infra plan [env]`:
|
|
|
102
102
|
|
|
103
103
|
### 4. DEPLOY - Execute Deploy
|
|
104
104
|
|
|
105
|
-
When the user requests `/morph
|
|
105
|
+
When the user requests `/morph:infra deploy [env]`:
|
|
106
106
|
|
|
107
107
|
1. Verify plan was approved
|
|
108
108
|
|
|
@@ -130,7 +130,7 @@ When the user requests `/morph-infra deploy [env]`:
|
|
|
130
130
|
|
|
131
131
|
### 5. DESTROY - Remove Resources
|
|
132
132
|
|
|
133
|
-
When the user requests `/morph
|
|
133
|
+
When the user requests `/morph:infra destroy [env]`:
|
|
134
134
|
|
|
135
135
|
1. **WARN** that this action is irreversible
|
|
136
136
|
|
|
@@ -177,14 +177,14 @@ Before any deploy, check with Cost Guardian:
|
|
|
177
177
|
## Usage Example
|
|
178
178
|
|
|
179
179
|
```
|
|
180
|
-
User: /morph
|
|
180
|
+
User: /morph:infra init
|
|
181
181
|
Claude: Creating IaC structure...
|
|
182
182
|
- infra/main.bicep
|
|
183
183
|
- infra/parameters.dev.json
|
|
184
184
|
- infra/modules/*.bicep
|
|
185
|
-
Done! Run /morph
|
|
185
|
+
Done! Run /morph:infra validate to verify.
|
|
186
186
|
|
|
187
|
-
User: /morph
|
|
187
|
+
User: /morph:infra plan dev
|
|
188
188
|
Claude: Analyzing changes for dev environment...
|
|
189
189
|
|
|
190
190
|
CREATE:
|
|
@@ -11,7 +11,7 @@ Validates project readiness before deploying to Azure.
|
|
|
11
11
|
## Usage
|
|
12
12
|
|
|
13
13
|
```
|
|
14
|
-
/morph
|
|
14
|
+
/morph:preflight azure
|
|
15
15
|
```
|
|
16
16
|
|
|
17
17
|
## Purpose
|
|
@@ -39,7 +39,7 @@ npx morph-spec validate-feature {feature}
|
|
|
39
39
|
| All tasks complete | `state get` → tasks array | 0 tasks in `pending` or `in_progress` |
|
|
40
40
|
| Recap generated | Read `5-implement/recap.md` | File exists and is non-empty |
|
|
41
41
|
|
|
42
|
-
**If any MORPH check fails:** Show which check failed and recommend: `Run /morph
|
|
42
|
+
**If any MORPH check fails:** Show which check failed and recommend: `Run /morph:apply {feature} to complete implementation before deploying.`
|
|
43
43
|
|
|
44
44
|
---
|
|
45
45
|
|
|
@@ -222,7 +222,7 @@ Recommended fixes:
|
|
|
222
222
|
### Before creating PR for production
|
|
223
223
|
|
|
224
224
|
```bash
|
|
225
|
-
/morph
|
|
225
|
+
/morph:preflight azure
|
|
226
226
|
```
|
|
227
227
|
|
|
228
228
|
### In CI/CD pipeline
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: MORPH Spec Pipeline — creates or resumes feature planning through phases 0-6 with mandatory approval pauses. Use whenever the user wants to plan, spec, or design a new feature, or when they say /morph
|
|
2
|
+
description: MORPH Spec Pipeline — creates or resumes feature planning through phases 0-6 with mandatory approval pauses. Use whenever the user wants to plan, spec, or design a new feature, or when they say /morph:proposal. This is the primary entry point for all MORPH-SPEC feature work.
|
|
3
3
|
argument-hint: <feature-name>
|
|
4
4
|
allowed-tools: Read, Write, Edit, Bash, Glob, Grep, AskUserQuestion
|
|
5
5
|
---
|
|
@@ -187,7 +187,7 @@ npx morph-spec phase advance {feature-name}
|
|
|
187
187
|
After task approval:
|
|
188
188
|
|
|
189
189
|
```
|
|
190
|
-
Planning complete! Run /morph
|
|
190
|
+
Planning complete! Run /morph:apply {feature-name} to start implementation.
|
|
191
191
|
```
|
|
192
192
|
|
|
193
193
|
---
|
|
@@ -83,9 +83,9 @@ The `npx morph-spec state list` command automatically generates a formatted dash
|
|
|
83
83
|
## Available Actions
|
|
84
84
|
|
|
85
85
|
Suggest next actions:
|
|
86
|
-
- `/morph
|
|
87
|
-
- `/morph
|
|
88
|
-
- `/morph
|
|
86
|
+
- `/morph:proposal {feature}` - Create a new feature
|
|
87
|
+
- `/morph:apply {feature}` - Implement a feature
|
|
88
|
+
- `/morph:archive {feature}` - Archive a completed feature
|
|
89
89
|
|
|
90
90
|
---
|
|
91
91
|
|
|
@@ -35,7 +35,7 @@ const PHASE_POSITIONS = {
|
|
|
35
35
|
const PIPELINE_TOTAL = 5;
|
|
36
36
|
|
|
37
37
|
const NEXT_STEP_HINT = {
|
|
38
|
-
proposal: (n) => `Continue spec pipeline: /morph
|
|
38
|
+
proposal: (n) => `Continue spec pipeline: /morph:proposal ${n}`,
|
|
39
39
|
design: (n) => `Approve design gate: morph-spec approve ${n} design`,
|
|
40
40
|
uiux: (n) => `Approve UI gate: morph-spec approve ${n} uiux`,
|
|
41
41
|
tasks: (n) => `Approve tasks gate: morph-spec approve ${n} tasks`,
|
|
@@ -37,9 +37,9 @@ Use `morph-spec status {feature}` to see current phase and pending approval gate
|
|
|
37
37
|
|
|
38
38
|
| Command | Purpose |
|
|
39
39
|
|---------|---------|
|
|
40
|
-
| `/morph
|
|
41
|
-
| `/morph
|
|
42
|
-
| `/morph
|
|
40
|
+
| `/morph:proposal {feature}` | Full spec pipeline (phases 1-4, pauses for approval) |
|
|
41
|
+
| `/morph:apply {feature}` | Implement feature (phase 5) |
|
|
42
|
+
| `/morph:status` | Feature dashboard |
|
|
43
43
|
|
|
44
44
|
---
|
|
45
45
|
|
|
@@ -32,7 +32,7 @@ Each skill is a directory: `{name}/SKILL.md` + optional `scripts/`, `references/
|
|
|
32
32
|
|
|
33
33
|
## level-1-workflows — Phase Workflow Skills
|
|
34
34
|
|
|
35
|
-
Invoked internally by `/morph
|
|
35
|
+
Invoked internally by `/morph:proposal` and `/morph:apply`. Not user-invocable.
|
|
36
36
|
|
|
37
37
|
| Skill | Phase | Description |
|
|
38
38
|
|-------|-------|-------------|
|
|
@@ -218,7 +218,7 @@ And inform the user:
|
|
|
218
218
|
→ morph-spec will use vsa-architect + contracts-vsa.cs for features
|
|
219
219
|
```
|
|
220
220
|
|
|
221
|
-
> This key is what activates `vsa-architect` instead of `domain-architect` during `/morph
|
|
221
|
+
> This key is what activates `vsa-architect` instead of `domain-architect` during `/morph:proposal`.
|
|
222
222
|
|
|
223
223
|
**If the project doesn't exist yet (new VSA project from scratch)**, also inform:
|
|
224
224
|
|
|
@@ -377,5 +377,5 @@ Before printing the summary, check `~/.claude/settings.local.json` for `env.CLAU
|
|
|
377
377
|
[If any plugin was newly installed:]
|
|
378
378
|
○ Restart Claude Code to activate newly installed plugins.
|
|
379
379
|
|
|
380
|
-
Next: /morph
|
|
380
|
+
Next: /morph:proposal [feature-name]
|
|
381
381
|
```
|
|
@@ -4,12 +4,12 @@ description: MORPH-SPEC Phase 3 (Clarify). Iterative clarification loop driven b
|
|
|
4
4
|
argument-hint: "[feature-name]"
|
|
5
5
|
user-invocable: false
|
|
6
6
|
allowed-tools: Read, Write, Edit, Bash, Glob, Grep, AskUserQuestion
|
|
7
|
-
cliVersion: "4.10.
|
|
7
|
+
cliVersion: "4.10.4"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# MORPH Clarify — Phase 3
|
|
11
11
|
|
|
12
|
-
> INTERNAL: Workflow skill used by /morph
|
|
12
|
+
> INTERNAL: Workflow skill used by /morph:proposal during automated phase orchestration. Not a user command.
|
|
13
13
|
|
|
14
14
|
Eliminate spec ambiguities through an iterative question loop until the spec has sufficient quality to generate tasks.
|
|
15
15
|
|
|
@@ -3,7 +3,7 @@ name: morph:phase-codebase-analysis
|
|
|
3
3
|
description: MORPH-SPEC Design sub-phase that analyzes existing codebase and database schema, producing schema-analysis.md with real column names, types, relationships, and field mismatches. Use at the start of Design phase before generating contracts.cs to prevent incorrect field names or types.
|
|
4
4
|
user-invocable: false
|
|
5
5
|
allowed-tools: Read, Write, Edit, Bash, Glob, Grep, AskUserQuestion
|
|
6
|
-
cliVersion: "4.10.
|
|
6
|
+
cliVersion: "4.10.4"
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# MORPH Codebase Analysis - Design Sub-phase
|
|
@@ -4,12 +4,12 @@ description: MORPH-SPEC Phase 2 (Design). Schema-first interactive design: reads
|
|
|
4
4
|
argument-hint: "[feature-name]"
|
|
5
5
|
user-invocable: false
|
|
6
6
|
allowed-tools: Read, Write, Edit, Bash, Glob, Grep, AskUserQuestion, Agent
|
|
7
|
-
cliVersion: "4.10.
|
|
7
|
+
cliVersion: "4.10.4"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# MORPH Design — Phase 2
|
|
11
11
|
|
|
12
|
-
> INTERNAL: Workflow skill used by /morph
|
|
12
|
+
> INTERNAL: Workflow skill used by /morph:proposal during automated phase orchestration. Not a user command.
|
|
13
13
|
|
|
14
14
|
Schema-first interactive design: read the real schema, validate with user, generate blueprint, produce artifacts through a quality loop.
|
|
15
15
|
|
|
@@ -6,13 +6,13 @@ disable-model-invocation: true
|
|
|
6
6
|
context: fork
|
|
7
7
|
agent: general-purpose
|
|
8
8
|
user-invocable: false
|
|
9
|
-
cliVersion: "4.10.
|
|
9
|
+
cliVersion: "4.10.4"
|
|
10
10
|
allowed-tools: Read, Write, Edit, Bash, Glob, Grep, AskUserQuestion, Agent
|
|
11
11
|
---
|
|
12
12
|
|
|
13
13
|
# MORPH Implement — Phase 5
|
|
14
14
|
|
|
15
|
-
> INTERNAL: Workflow skill used by /morph
|
|
15
|
+
> INTERNAL: Workflow skill used by /morph:apply during automated phase orchestration. Not a user command.
|
|
16
16
|
|
|
17
17
|
Implement the tasks defined in Phase 4, with checkpoints every 3 tasks and a final recap.
|
|
18
18
|
|
|
@@ -98,7 +98,7 @@ Before starting tasks, verify that MCPs needed for the implement phase are worki
|
|
|
98
98
|
Use `AskUserQuestion` with header `"{MCP}"` and options:
|
|
99
99
|
- **Continue without {MCP}** — Show `fallback` from registry. Implementation will use the fallback automatically.
|
|
100
100
|
- **Reconfigure credentials** — Collect credentials and update `.claude/settings.local.json`. Re-test after reconfiguring.
|
|
101
|
-
- **Stop implementation** — Abort. User must fix the MCP and re-run `/morph
|
|
101
|
+
- **Stop implementation** — Abort. User must fix the MCP and re-run `/morph:apply`.
|
|
102
102
|
|
|
103
103
|
> **Design:** This check is fail-open — consistent with morph-spec philosophy. The user gets explicit choice instead of tasks failing silently.
|
|
104
104
|
|
|
@@ -5,12 +5,12 @@ argument-hint: "[feature-name]"
|
|
|
5
5
|
disable-model-invocation: true
|
|
6
6
|
user-invocable: false
|
|
7
7
|
allowed-tools: Read, Write, Edit, Bash, Glob, Grep, AskUserQuestion
|
|
8
|
-
cliVersion: "4.10.
|
|
8
|
+
cliVersion: "4.10.4"
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
# MORPH Plan — Phase 4
|
|
12
12
|
|
|
13
|
-
> INTERNAL: Workflow skill used by /morph
|
|
13
|
+
> INTERNAL: Workflow skill used by /morph:proposal during automated phase orchestration. Not a user command.
|
|
14
14
|
|
|
15
15
|
Generate a detailed implementation plan with exact paths, TDD code, and execution strategy.
|
|
16
16
|
|
|
@@ -4,12 +4,12 @@ description: MORPH-SPEC Phase 1 (Setup). Reads project context, detects tech sta
|
|
|
4
4
|
argument-hint: "[feature-name]"
|
|
5
5
|
user-invocable: false
|
|
6
6
|
allowed-tools: Read, Write, Edit, Bash, Glob, Grep, AskUserQuestion
|
|
7
|
-
cliVersion: "4.10.
|
|
7
|
+
cliVersion: "4.10.4"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# MORPH Setup — Phase 1
|
|
11
11
|
|
|
12
|
-
> INTERNAL: Workflow skill used by /morph
|
|
12
|
+
> INTERNAL: Workflow skill used by /morph:proposal during automated phase orchestration. Not a user command.
|
|
13
13
|
|
|
14
14
|
Initialize the context and prepare the environment for an approved feature.
|
|
15
15
|
|
|
@@ -151,6 +151,26 @@ MCP Readiness:
|
|
|
151
151
|
|
|
152
152
|
---
|
|
153
153
|
|
|
154
|
+
### Step 2.9: Check for Missing Agents
|
|
155
|
+
|
|
156
|
+
Run a quick check to verify all expected agents are installed for this project's stack:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
npx morph-spec doctor 2>&1 | grep -A 1 "agents completeness"
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
If the output shows **missing agents**, inform the user:
|
|
163
|
+
|
|
164
|
+
> ⚠ Some agents for your stack are not installed. Run `npx morph-spec init --force` to install them.
|
|
165
|
+
|
|
166
|
+
Use `AskUserQuestion` with header `"Agents"` and options:
|
|
167
|
+
- **Install now** — run `npx morph-spec init --force` to reinstall with current stack tags
|
|
168
|
+
- **Continue without** — proceed with available agents (may lack specialized support)
|
|
169
|
+
|
|
170
|
+
If "Install now" is selected, run the command and continue.
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
154
174
|
### Step 3: Confirm Stack
|
|
155
175
|
|
|
156
176
|
Based on the proposal and context, confirm:
|
|
@@ -5,12 +5,12 @@ argument-hint: "[feature-name]"
|
|
|
5
5
|
disable-model-invocation: true
|
|
6
6
|
user-invocable: false
|
|
7
7
|
allowed-tools: Read, Write, Edit, Bash, Glob, Grep, AskUserQuestion
|
|
8
|
-
cliVersion: "4.10.
|
|
8
|
+
cliVersion: "4.10.4"
|
|
9
9
|
---
|
|
10
10
|
|
|
11
11
|
# MORPH Tasks — Phase 4
|
|
12
12
|
|
|
13
|
-
> INTERNAL: Workflow skill used by /morph
|
|
13
|
+
> INTERNAL: Workflow skill used by /morph:proposal during automated phase orchestration. Not a user command.
|
|
14
14
|
|
|
15
15
|
Break the approved specification into executable tasks, define execution order, and establish checkpoints.
|
|
16
16
|
|
|
@@ -309,4 +309,4 @@ Use `AskUserQuestion` to capture explicit approval before advancing. This gate p
|
|
|
309
309
|
|
|
310
310
|
---
|
|
311
311
|
|
|
312
|
-
After approval: "Planning complete! Run `/morph
|
|
312
|
+
After approval: "Planning complete! Run `/morph:apply $ARGUMENTS` to start implementation."
|
|
@@ -4,12 +4,12 @@ description: MORPH-SPEC Phase 1.5 (UI/UX). Creates design-system.md, mockups.md,
|
|
|
4
4
|
argument-hint: "[feature-name]"
|
|
5
5
|
user-invocable: false
|
|
6
6
|
allowed-tools: Read, Write, Edit, Bash, Glob, Grep, AskUserQuestion
|
|
7
|
-
cliVersion: "4.10.
|
|
7
|
+
cliVersion: "4.10.4"
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# MORPH UI/UX Design - Phase 1.5
|
|
11
11
|
|
|
12
|
-
> INTERNAL: Workflow skill used by /morph
|
|
12
|
+
> INTERNAL: Workflow skill used by /morph:proposal during automated phase orchestration. Not a user command.
|
|
13
13
|
|
|
14
14
|
Conditional phase for features with front-end. Collects UI/UX requirements, generates wireframes, component specs, and user flows.
|
|
15
15
|
|
|
@@ -4,6 +4,7 @@ description: Guided workflow for mid-implementation scope escalation — analyze
|
|
|
4
4
|
user-invocable: true
|
|
5
5
|
argument-hint: "[feature-name]"
|
|
6
6
|
allowed-tools: Read, Write, Edit, Bash, Glob, Grep, AskUserQuestion
|
|
7
|
+
cliVersion: "4.10.4"
|
|
7
8
|
---
|
|
8
9
|
|
|
9
10
|
# Scope Escalation Workflow
|
|
@@ -132,7 +133,7 @@ npx morph-spec scope escalate <feature> --task <id> --reason "<reason>" --target
|
|
|
132
133
|
|
|
133
134
|
After escalation:
|
|
134
135
|
|
|
135
|
-
- **If regressed to tasks:** "Re-analyze the affected tasks. Rewrite tasks.md with accurate scope. Run `/morph
|
|
136
|
+
- **If regressed to tasks:** "Re-analyze the affected tasks. Rewrite tasks.md with accurate scope. Run `/morph:proposal <feature>` to continue from the tasks phase."
|
|
136
137
|
- **If regressed to design:** "Re-analyze the spec. Update spec.md with the discovered architecture. Then regenerate tasks."
|
|
137
138
|
- **If expanded:** "Continue implementing. Use `morph-spec task start <feature> <sub-task-id>` to start the first sub-task."
|
|
138
139
|
|
|
@@ -406,7 +406,7 @@ morph-spec state set {feature} workflow full-morph
|
|
|
406
406
|
|
|
407
407
|
```bash
|
|
408
408
|
# FASES 0-4: Single Claude session
|
|
409
|
-
/morph
|
|
409
|
+
/morph:proposal scheduled-reports
|
|
410
410
|
# ... proposal approved, design approved, tasks generated ...
|
|
411
411
|
|
|
412
412
|
# FASE 5: Create agent team
|
package/package.json
CHANGED
|
@@ -12,6 +12,8 @@ import {
|
|
|
12
12
|
getInstalledCLIVersion
|
|
13
13
|
} from '../../utils/version-checker.js';
|
|
14
14
|
import { resetMorphSettings } from '../../utils/claude-settings-manager.js';
|
|
15
|
+
import { checkMissingAgents } from '../../utils/agents-installer.js';
|
|
16
|
+
import { getProjectTags } from '../../lib/stack-filter.js';
|
|
15
17
|
|
|
16
18
|
const isWindows = platform() === 'win32';
|
|
17
19
|
/**
|
|
@@ -687,11 +689,32 @@ export async function doctorCommand(options = {}) {
|
|
|
687
689
|
}
|
|
688
690
|
|
|
689
691
|
// Check .claude/agents/
|
|
690
|
-
const
|
|
691
|
-
if (await pathExists(
|
|
692
|
-
const agentFiles = await fs.readdir(
|
|
692
|
+
const agentsPathClaude = join(claudePath, 'agents');
|
|
693
|
+
if (await pathExists(agentsPathClaude)) {
|
|
694
|
+
const agentFiles = await fs.readdir(agentsPathClaude);
|
|
693
695
|
const morphAgents = agentFiles.filter(f => f.startsWith('morph-') && f.endsWith('.md'));
|
|
694
696
|
checks.push({ name: '.claude/agents/', status: 'ok', msg: `${morphAgents.length} agents` });
|
|
697
|
+
|
|
698
|
+
// Check for missing agents based on stack tags
|
|
699
|
+
try {
|
|
700
|
+
const config = await readJson(configPath);
|
|
701
|
+
const projectTags = getProjectTags(config);
|
|
702
|
+
if (projectTags.length > 0) {
|
|
703
|
+
const frameworkDir = join(import.meta.dirname, '..', '..', '..', 'framework');
|
|
704
|
+
const { missing } = checkMissingAgents(targetPath, frameworkDir, projectTags);
|
|
705
|
+
if (missing.length > 0) {
|
|
706
|
+
const names = missing.map(m => m.name).join(', ');
|
|
707
|
+
checks.push({
|
|
708
|
+
name: 'agents completeness',
|
|
709
|
+
status: 'warn',
|
|
710
|
+
msg: `${missing.length} agent(s) missing for [${projectTags.join(', ')}]: ${names} — run morph-spec init --force`
|
|
711
|
+
});
|
|
712
|
+
hasWarnings = true;
|
|
713
|
+
} else {
|
|
714
|
+
checks.push({ name: 'agents completeness', status: 'ok', msg: `all agents for [${projectTags.join(', ')}]` });
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
} catch { /* config not readable — skip completeness check */ }
|
|
695
718
|
} else {
|
|
696
719
|
checks.push({ name: '.claude/agents/', status: 'warn', msg: 'missing — run morph-spec update' });
|
|
697
720
|
hasWarnings = true;
|
|
@@ -757,7 +780,7 @@ export async function doctorCommand(options = {}) {
|
|
|
757
780
|
} catch { /* non-fatal */ }
|
|
758
781
|
|
|
759
782
|
// ── Claude Code Plugins ──────────────────────────────────────────────────
|
|
760
|
-
console.log(chalk.cyan('\n Claude Code Plugins (required for /morph
|
|
783
|
+
console.log(chalk.cyan('\n Claude Code Plugins (required for /morph:init)'));
|
|
761
784
|
const pluginsFile = join(homedir(), '.claude', 'plugins', 'installed_plugins.json');
|
|
762
785
|
let pluginData = { plugins: {} };
|
|
763
786
|
try {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { join } from 'path';
|
|
1
|
+
import { join, dirname } from 'path';
|
|
2
|
+
import { fileURLToPath } from 'url';
|
|
2
3
|
import { homedir } from 'os';
|
|
3
4
|
import fs from 'fs-extra';
|
|
4
5
|
import ora from 'ora';
|
|
@@ -22,7 +23,10 @@ import {
|
|
|
22
23
|
generateSetupInstructions,
|
|
23
24
|
formatMcpStatusTable
|
|
24
25
|
} from '../../lib/installers/mcp-installer.js';
|
|
25
|
-
import { setupInfra } from '../../scripts/setup-infra.js';
|
|
26
|
+
import { setupInfra, installRulesForStack } from '../../scripts/setup-infra.js';
|
|
27
|
+
import { installSkills } from '../../utils/skills-installer.js';
|
|
28
|
+
import { installAgents, installDomainAgents } from '../../utils/agents-installer.js';
|
|
29
|
+
import { getProjectTags } from '../../lib/stack-filter.js';
|
|
26
30
|
|
|
27
31
|
export async function initCommand(options) {
|
|
28
32
|
const targetPath = options.path || process.cwd();
|
|
@@ -147,6 +151,9 @@ export async function initCommand(options) {
|
|
|
147
151
|
if (structure?.uiLibrary) {
|
|
148
152
|
config.project.uiLibrary = structure.uiLibrary;
|
|
149
153
|
}
|
|
154
|
+
if (structure?.integrations?.length > 0) {
|
|
155
|
+
config.project.integrations = structure.integrations;
|
|
156
|
+
}
|
|
150
157
|
await writeJson(configPath, config);
|
|
151
158
|
|
|
152
159
|
spinner.stop();
|
|
@@ -155,6 +162,7 @@ export async function initCommand(options) {
|
|
|
155
162
|
logger.info(`Stack detected: ${structure.stack}`);
|
|
156
163
|
if (structure?.architecture) logger.dim(`Architecture: ${structure.architecture}`);
|
|
157
164
|
if (structure?.uiLibrary) logger.dim(`UI Library: ${structure.uiLibrary}`);
|
|
165
|
+
if (structure?.integrations?.length > 0) logger.dim(`Integrations: ${structure.integrations.join(', ')}`);
|
|
158
166
|
|
|
159
167
|
const { standardsChoice } = await inquirer.prompt([{
|
|
160
168
|
type: 'list',
|
|
@@ -175,6 +183,28 @@ export async function initCommand(options) {
|
|
|
175
183
|
logger.dim('Review and edit .morph/context/standards.md as needed.');
|
|
176
184
|
}
|
|
177
185
|
|
|
186
|
+
// Re-install stack-filtered assets now that we know the stack
|
|
187
|
+
const stackTags = getProjectTags(config);
|
|
188
|
+
if (stackTags.length > 0) {
|
|
189
|
+
spinner.start('Re-installing stack-filtered assets...');
|
|
190
|
+
const frameworkDir = join(targetPath, '.morph', 'framework');
|
|
191
|
+
const fwSrcDir = join(dirname(fileURLToPath(import.meta.url)), '..', '..', '..', 'framework');
|
|
192
|
+
|
|
193
|
+
// Rules
|
|
194
|
+
const rulesSrc = join(fwSrcDir, 'rules');
|
|
195
|
+
const rulesDest = join(targetPath, '.claude', 'rules');
|
|
196
|
+
installRulesForStack(rulesSrc, rulesDest, stackTags);
|
|
197
|
+
|
|
198
|
+
// Skills
|
|
199
|
+
await installSkills(targetPath, { projectTags: stackTags });
|
|
200
|
+
|
|
201
|
+
// Agents (tier 1/2/4 from agents.json + domain agents)
|
|
202
|
+
await installAgents(targetPath, fwSrcDir, { projectTags: stackTags });
|
|
203
|
+
await installDomainAgents(targetPath, fwSrcDir, { projectTags: stackTags });
|
|
204
|
+
|
|
205
|
+
spinner.succeed(`Stack-filtered assets reinstalled for [${stackTags.join(', ')}]`);
|
|
206
|
+
}
|
|
207
|
+
|
|
178
208
|
spinner.start('Continuing...');
|
|
179
209
|
}
|
|
180
210
|
} catch (error) {
|
|
@@ -385,7 +415,7 @@ export async function initCommand(options) {
|
|
|
385
415
|
// Suggest tutorial for first-time users
|
|
386
416
|
if (integrationsCreated) {
|
|
387
417
|
logger.blank();
|
|
388
|
-
logger.info('First time using morph-spec? Run `/morph
|
|
418
|
+
logger.info('First time using morph-spec? Run `/morph:proposal <feature>` to start a new feature.');
|
|
389
419
|
}
|
|
390
420
|
|
|
391
421
|
logger.blank();
|
|
@@ -468,38 +468,38 @@ export async function advancePhaseCommand(feature, options = {}) {
|
|
|
468
468
|
function getPhaseGuidance(phase, feature) {
|
|
469
469
|
const guides = {
|
|
470
470
|
'setup': [
|
|
471
|
-
`Resume spec pipeline: /morph
|
|
471
|
+
`Resume spec pipeline: /morph:proposal ${feature}`,
|
|
472
472
|
'Auto-continues from setup phase'
|
|
473
473
|
],
|
|
474
474
|
'uiux': [
|
|
475
|
-
`Resume spec pipeline: /morph
|
|
475
|
+
`Resume spec pipeline: /morph:proposal ${feature}`,
|
|
476
476
|
'Provide layout references and preferences',
|
|
477
477
|
'Review wireframes before proceeding'
|
|
478
478
|
],
|
|
479
479
|
'design': (() => {
|
|
480
480
|
const { isDotnet } = getStackProfile();
|
|
481
481
|
return [
|
|
482
|
-
`Resume spec pipeline: /morph
|
|
482
|
+
`Resume spec pipeline: /morph:proposal ${feature}`,
|
|
483
483
|
'Review DECISION POINTS carefully',
|
|
484
484
|
isDotnet ? 'Approve spec.md and contracts.cs' : 'Approve spec.md and contracts.ts (TypeScript + Zod schemas)'
|
|
485
485
|
];
|
|
486
486
|
})(),
|
|
487
487
|
'clarify': [
|
|
488
|
-
`Resume spec pipeline: /morph
|
|
488
|
+
`Resume spec pipeline: /morph:proposal ${feature}`,
|
|
489
489
|
'Resolve edge cases and unknowns'
|
|
490
490
|
],
|
|
491
491
|
'plan': [
|
|
492
|
-
`Resume spec pipeline: /morph
|
|
492
|
+
`Resume spec pipeline: /morph:proposal ${feature}`,
|
|
493
493
|
'Review implementation plan for completeness',
|
|
494
494
|
'Approve plan before generating task breakdown'
|
|
495
495
|
],
|
|
496
496
|
'tasks': [
|
|
497
|
-
`Resume spec pipeline: /morph
|
|
497
|
+
`Resume spec pipeline: /morph:proposal ${feature}`,
|
|
498
498
|
'Review task order and dependencies',
|
|
499
499
|
'Approve task list before implementing'
|
|
500
500
|
],
|
|
501
501
|
'implement': [
|
|
502
|
-
`Start implementing: /morph
|
|
502
|
+
`Start implementing: /morph:apply ${feature}`,
|
|
503
503
|
'Complete tasks one by one with: morph-spec task done',
|
|
504
504
|
'Validators run automatically on task completion'
|
|
505
505
|
],
|
|
@@ -29,21 +29,28 @@ export function detectProject(targetPath, opts = {}) {
|
|
|
29
29
|
|
|
30
30
|
try {
|
|
31
31
|
// ── Package.json analysis ──────────────────────────────────────────────
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
32
|
+
// Collect deps from root and common monorepo subfolders
|
|
33
|
+
const pkgLocations = ['.', 'src/frontend', 'frontend', 'apps/web', 'packages/app'];
|
|
34
|
+
const allDeps = {};
|
|
35
|
+
let rootPkg = null;
|
|
36
|
+
let scripts = [];
|
|
37
|
+
for (const loc of pkgLocations) {
|
|
38
|
+
const pkgPath = join(targetPath, loc, 'package.json');
|
|
39
|
+
if (!existsSync(pkgPath)) continue;
|
|
35
40
|
try {
|
|
36
|
-
pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
41
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf8'));
|
|
42
|
+
if (loc === '.') rootPkg = pkg;
|
|
43
|
+
Object.assign(allDeps, pkg.dependencies, pkg.devDependencies);
|
|
44
|
+
scripts.push(...Object.keys(pkg.scripts || {}));
|
|
45
|
+
// Track frontend path for config
|
|
46
|
+
if (loc !== '.' && (pkg.dependencies?.next || pkg.dependencies?.react || pkg.dependencies?.vue)) {
|
|
47
|
+
structure.frontendPath = loc;
|
|
48
|
+
}
|
|
37
49
|
} catch { /* invalid JSON */ }
|
|
38
50
|
}
|
|
51
|
+
const depNames = Object.keys(allDeps);
|
|
39
52
|
|
|
40
|
-
if (
|
|
41
|
-
const allDeps = {
|
|
42
|
-
...pkg.dependencies,
|
|
43
|
-
...pkg.devDependencies,
|
|
44
|
-
};
|
|
45
|
-
const depNames = Object.keys(allDeps);
|
|
46
|
-
const scripts = Object.keys(pkg.scripts || {});
|
|
53
|
+
if (depNames.length > 0) {
|
|
47
54
|
|
|
48
55
|
// Stack detection from deps
|
|
49
56
|
if (depNames.includes('next')) {
|
|
@@ -54,12 +61,12 @@ export function detectProject(targetPath, opts = {}) {
|
|
|
54
61
|
structure.stack = 'vue';
|
|
55
62
|
} else if (depNames.includes('express') || depNames.includes('fastify') || depNames.includes('hono')) {
|
|
56
63
|
structure.stack = 'nodejs';
|
|
57
|
-
} else if (
|
|
64
|
+
} else if (rootPkg?.name) {
|
|
58
65
|
structure.stack = 'nodejs';
|
|
59
66
|
}
|
|
60
67
|
|
|
61
68
|
// UI library detection
|
|
62
|
-
if (existsSync(join(targetPath, 'components.json')) || depNames.includes('@shadcn/ui')) {
|
|
69
|
+
if (existsSync(join(targetPath, 'components.json')) || existsSync(join(targetPath, structure.frontendPath || '.', 'components.json')) || depNames.includes('@shadcn/ui')) {
|
|
63
70
|
structure.uiLibrary = 'shadcn';
|
|
64
71
|
} else if (depNames.some(d => d.includes('mudblazor'))) {
|
|
65
72
|
structure.uiLibrary = 'mudblazor';
|
|
@@ -79,36 +86,57 @@ export function detectProject(targetPath, opts = {}) {
|
|
|
79
86
|
}
|
|
80
87
|
|
|
81
88
|
// Vercel detection
|
|
82
|
-
if (depNames.includes('vercel') || scripts.includes('vercel')
|
|
83
|
-
scripts.some(s => (pkg.scripts[s] || '').includes('vercel'))) {
|
|
89
|
+
if (depNames.includes('vercel') || scripts.includes('vercel')) {
|
|
84
90
|
structure.hasVercel = true;
|
|
85
91
|
}
|
|
86
92
|
}
|
|
87
93
|
|
|
88
94
|
// ── .csproj detection ──────────────────────────────────────────────────
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
95
|
+
let hasDotnet = false;
|
|
96
|
+
let hasBlazer = false;
|
|
97
|
+
const csprojLocations = ['.', 'src', 'src/Api', 'src/Web', 'src/backend'];
|
|
98
|
+
|
|
99
|
+
function checkCsproj(filePath) {
|
|
100
|
+
hasDotnet = true;
|
|
101
|
+
try {
|
|
102
|
+
const content = readFileSync(filePath, 'utf8');
|
|
103
|
+
if (content.includes('Microsoft.AspNetCore.Components') || content.includes('Blazor')) {
|
|
104
|
+
hasBlazer = true;
|
|
105
|
+
}
|
|
106
|
+
if (content.includes('Hangfire')) {
|
|
107
|
+
if (!structure.integrations.includes('hangfire')) structure.integrations.push('hangfire');
|
|
108
|
+
}
|
|
109
|
+
} catch { /* ignore */ }
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
for (const loc of csprojLocations) {
|
|
113
|
+
try {
|
|
114
|
+
const dir = join(targetPath, loc);
|
|
115
|
+
if (!existsSync(dir)) continue;
|
|
116
|
+
const entries = readdirSyncSafe(dir);
|
|
117
|
+
// Direct .csproj files
|
|
118
|
+
for (const e of entries.filter(f => f.endsWith('.csproj'))) {
|
|
119
|
+
checkCsproj(join(dir, e));
|
|
120
|
+
}
|
|
121
|
+
// One level deeper (e.g., src/backend/MyProject/*.csproj)
|
|
122
|
+
for (const sub of entries) {
|
|
123
|
+
const subDir = join(dir, sub);
|
|
124
|
+
for (const e of readdirSyncSafe(subDir).filter(f => f.endsWith('.csproj'))) {
|
|
125
|
+
checkCsproj(join(subDir, e));
|
|
110
126
|
}
|
|
111
|
-
}
|
|
127
|
+
}
|
|
128
|
+
} catch { /* ignore */ }
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Determine stack: monorepo (nextjs+dotnet), blazor, dotnet, or keep frontend stack
|
|
132
|
+
if (hasDotnet) {
|
|
133
|
+
if (structure.stack === 'nextjs' || structure.stack === 'react' || structure.stack === 'vue') {
|
|
134
|
+
// Monorepo: frontend + .NET backend
|
|
135
|
+
structure.stack = `${structure.stack}+dotnet`;
|
|
136
|
+
} else if (hasBlazer) {
|
|
137
|
+
structure.stack = 'blazor';
|
|
138
|
+
} else if (structure.stack === 'unknown') {
|
|
139
|
+
structure.stack = 'dotnet';
|
|
112
140
|
}
|
|
113
141
|
}
|
|
114
142
|
|
|
@@ -137,7 +165,27 @@ export function detectProject(targetPath, opts = {}) {
|
|
|
137
165
|
if (existsSync(join(targetPath, 'docker-compose.yml')) ||
|
|
138
166
|
existsSync(join(targetPath, 'docker-compose.yaml')) ||
|
|
139
167
|
existsSync(join(targetPath, 'Dockerfile'))) {
|
|
140
|
-
structure.integrations.push('docker');
|
|
168
|
+
if (!structure.integrations.includes('docker')) structure.integrations.push('docker');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ── Azure detection ────────────────────────────────────────────────────
|
|
172
|
+
const azureSignals = [
|
|
173
|
+
'.azure',
|
|
174
|
+
'azure-pipelines.yml',
|
|
175
|
+
'infra/main.bicep',
|
|
176
|
+
'infra',
|
|
177
|
+
];
|
|
178
|
+
const hasAzure = azureSignals.some(signal => {
|
|
179
|
+
const p = join(targetPath, signal);
|
|
180
|
+
if (!existsSync(p)) return false;
|
|
181
|
+
if (signal === 'infra') {
|
|
182
|
+
// Check for .bicep files inside infra/
|
|
183
|
+
return readdirSyncSafe(p).some(f => f.endsWith('.bicep'));
|
|
184
|
+
}
|
|
185
|
+
return true;
|
|
186
|
+
});
|
|
187
|
+
if (hasAzure && !structure.integrations.includes('azure')) {
|
|
188
|
+
structure.integrations.push('azure');
|
|
141
189
|
}
|
|
142
190
|
} catch {
|
|
143
191
|
// Fail-safe — return defaults
|
package/src/lib/stack-filter.js
CHANGED
|
@@ -37,13 +37,20 @@ export function parseStacks(content) {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
|
-
* Extract project
|
|
41
|
-
*
|
|
40
|
+
* Extract project tags from config object.
|
|
41
|
+
* Combines stack tags (from `project.stack` split by '-' or '+')
|
|
42
|
+
* with integration tags (from `project.integrations` array).
|
|
43
|
+
* Deduplicates the result.
|
|
42
44
|
*/
|
|
43
45
|
export function getProjectTags(config) {
|
|
44
46
|
const stack = config?.project?.stack;
|
|
45
|
-
|
|
46
|
-
|
|
47
|
+
const stackTags = (stack && typeof stack === 'string')
|
|
48
|
+
? stack.split(/[-+]/).filter(Boolean)
|
|
49
|
+
: [];
|
|
50
|
+
const integrations = Array.isArray(config?.project?.integrations)
|
|
51
|
+
? config.project.integrations
|
|
52
|
+
: [];
|
|
53
|
+
return [...new Set([...stackTags, ...integrations])];
|
|
47
54
|
}
|
|
48
55
|
|
|
49
56
|
/**
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Headless infrastructure setup for MORPH-SPEC.
|
|
5
5
|
* Extracted from init.js (steps 1-10) so it can be called by the
|
|
6
|
-
* /morph
|
|
6
|
+
* /morph:init Claude Code skill via `morph-spec setup-infra` without
|
|
7
7
|
* interactive prompts or stack detection.
|
|
8
8
|
*
|
|
9
9
|
* Usage:
|
|
@@ -212,7 +212,7 @@ export async function setupInfra(targetPath, { _exec = execSync } = {}) {
|
|
|
212
212
|
|
|
213
213
|
// --- 12. installAgents ---
|
|
214
214
|
log('Step 12: Installing agents...');
|
|
215
|
-
const agentCounts = await installAgents(targetPath, FRAMEWORK_DIR, {
|
|
215
|
+
const agentCounts = await installAgents(targetPath, FRAMEWORK_DIR, { projectTags });
|
|
216
216
|
|
|
217
217
|
// --- 13. installDomainAgents (filtered by stack) ---
|
|
218
218
|
log('Step 13: Installing domain agents...');
|
|
@@ -22,7 +22,7 @@ import { parseStacks, shouldInstall } from '../lib/stack-filter.js';
|
|
|
22
22
|
* @param {string|null} options.projectStack - Detected project stack (e.g. 'nextjs', 'blazor')
|
|
23
23
|
*/
|
|
24
24
|
export async function installAgents(projectDir, frameworkDir = 'framework', options = {}) {
|
|
25
|
-
const {
|
|
25
|
+
const { projectTags = [] } = options;
|
|
26
26
|
|
|
27
27
|
const agentsJsonPath = join(frameworkDir, 'agents.json');
|
|
28
28
|
const agentsJson = JSON.parse(readFileSync(agentsJsonPath, 'utf-8'));
|
|
@@ -38,21 +38,30 @@ export async function installAgents(projectDir, frameworkDir = 'framework', opti
|
|
|
38
38
|
const targetDir = join(projectDir, '.claude', 'agents');
|
|
39
39
|
mkdirSync(targetDir, { recursive: true });
|
|
40
40
|
|
|
41
|
-
const DOTNET_STACKS = ['dotnet', 'blazor', 'dotnet-api', 'fullstack'];
|
|
42
41
|
const eligible = agents.filter(a => {
|
|
43
42
|
if (a.tier !== 1 && a.tier !== 2 && a.tier !== 4) return false;
|
|
44
43
|
// Tier 4 validators must have a teammate with spawn_prompt
|
|
45
44
|
if (a.tier === 4 && !a.teammate?.spawn_prompt) return false;
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
}
|
|
45
|
+
// Filter by stacks if the agent declares them
|
|
46
|
+
const stacks = a.stacks ?? [];
|
|
47
|
+
if (!shouldInstall(stacks, projectTags)) return false;
|
|
50
48
|
return true;
|
|
51
49
|
});
|
|
52
50
|
|
|
53
51
|
let tier1 = 0;
|
|
54
52
|
let tier2 = 0;
|
|
55
53
|
let tier4 = 0;
|
|
54
|
+
const installed = new Set();
|
|
55
|
+
|
|
56
|
+
// Build the full set of possible filenames for orphan cleanup
|
|
57
|
+
const allPossible = new Set();
|
|
58
|
+
for (const a of agents) {
|
|
59
|
+
if (a.tier !== 1 && a.tier !== 2 && a.tier !== 4) continue;
|
|
60
|
+
const slug = a.id ?? a.name?.toLowerCase().replace(/\s+/g, '-');
|
|
61
|
+
const prefix = a.tier === 4 ? 'morph-validator-' : 'morph-';
|
|
62
|
+
allPossible.add(`${prefix}${slug}.md`);
|
|
63
|
+
}
|
|
64
|
+
|
|
56
65
|
for (const agent of eligible) {
|
|
57
66
|
const slug = agent.id ?? agent.name?.toLowerCase().replace(/\s+/g, '-');
|
|
58
67
|
const prefix = agent.tier === 4 ? 'morph-validator-' : 'morph-';
|
|
@@ -65,11 +74,20 @@ export async function installAgents(projectDir, frameworkDir = 'framework', opti
|
|
|
65
74
|
const frontmatter = buildFrontmatter(agent, description);
|
|
66
75
|
const content = `---\n${frontmatter}---\n\n${body}\n`;
|
|
67
76
|
writeFileSync(targetPath, content, 'utf-8');
|
|
77
|
+
installed.add(filename);
|
|
68
78
|
|
|
69
79
|
if (agent.tier === 1) tier1++;
|
|
70
80
|
else if (agent.tier === 2) tier2++;
|
|
71
81
|
else if (agent.tier === 4) tier4++;
|
|
72
82
|
}
|
|
83
|
+
|
|
84
|
+
// Cleanup orphan morph-* / morph-validator-* files that were filtered out
|
|
85
|
+
for (const file of readdirSync(targetDir)) {
|
|
86
|
+
if (allPossible.has(file) && !installed.has(file)) {
|
|
87
|
+
unlinkSync(join(targetDir, file));
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
73
91
|
return { tier1, tier2, tier4 };
|
|
74
92
|
}
|
|
75
93
|
|
|
@@ -259,3 +277,69 @@ export async function installDomainAgents(projectDir, frameworkDir = 'framework'
|
|
|
259
277
|
|
|
260
278
|
return { specialists };
|
|
261
279
|
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Check which agents SHOULD be installed but are NOT.
|
|
283
|
+
* Returns a list of missing agent names with their source (agents.json or domain).
|
|
284
|
+
*
|
|
285
|
+
* @param {string} projectDir - Target project directory
|
|
286
|
+
* @param {string} frameworkDir - Path to morph-spec framework/ directory
|
|
287
|
+
* @param {string[]} projectTags - Stack + integration tags
|
|
288
|
+
* @returns {{ missing: Array<{ name: string, stacks: string[], source: string }>, installed: number, expected: number }}
|
|
289
|
+
*/
|
|
290
|
+
export function checkMissingAgents(projectDir, frameworkDir, projectTags = []) {
|
|
291
|
+
const targetDir = join(projectDir, '.claude', 'agents');
|
|
292
|
+
const installedFiles = new Set();
|
|
293
|
+
if (existsSync(targetDir)) {
|
|
294
|
+
for (const f of readdirSync(targetDir)) installedFiles.add(f);
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const missing = [];
|
|
298
|
+
let expected = 0;
|
|
299
|
+
|
|
300
|
+
// Check tier 1/2/4 from agents.json
|
|
301
|
+
const agentsJsonPath = join(frameworkDir, 'agents.json');
|
|
302
|
+
if (existsSync(agentsJsonPath)) {
|
|
303
|
+
const agentsJson = JSON.parse(readFileSync(agentsJsonPath, 'utf-8'));
|
|
304
|
+
const agentsRaw = agentsJson.agents ?? agentsJson;
|
|
305
|
+
const agents = Array.isArray(agentsRaw)
|
|
306
|
+
? agentsRaw
|
|
307
|
+
: Object.entries(agentsRaw)
|
|
308
|
+
.filter(([key]) => !key.startsWith('_comment'))
|
|
309
|
+
.map(([id, data]) => ({ id, ...data }));
|
|
310
|
+
|
|
311
|
+
for (const a of agents) {
|
|
312
|
+
if (a.tier !== 1 && a.tier !== 2 && a.tier !== 4) continue;
|
|
313
|
+
if (a.tier === 4 && !a.teammate?.spawn_prompt) continue;
|
|
314
|
+
const stacks = a.stacks ?? [];
|
|
315
|
+
if (!shouldInstall(stacks, projectTags)) continue;
|
|
316
|
+
|
|
317
|
+
expected++;
|
|
318
|
+
const slug = a.id ?? a.name?.toLowerCase().replace(/\s+/g, '-');
|
|
319
|
+
const prefix = a.tier === 4 ? 'morph-validator-' : 'morph-';
|
|
320
|
+
const filename = `${prefix}${slug}.md`;
|
|
321
|
+
if (!installedFiles.has(filename)) {
|
|
322
|
+
missing.push({ name: a.title ?? slug, stacks, source: `tier-${a.tier}` });
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Check domain agents
|
|
328
|
+
const domainsDir = join(frameworkDir, 'agents');
|
|
329
|
+
if (existsSync(domainsDir)) {
|
|
330
|
+
for (const filePath of collectSkillFiles(domainsDir)) {
|
|
331
|
+
const raw = readFileSync(filePath, 'utf-8');
|
|
332
|
+
const { name, stacks } = parseSkillFile(raw);
|
|
333
|
+
if (!name) continue;
|
|
334
|
+
if (!shouldInstall(stacks, projectTags)) continue;
|
|
335
|
+
|
|
336
|
+
expected++;
|
|
337
|
+
const filename = `morph-domain-${name}.md`;
|
|
338
|
+
if (!installedFiles.has(filename)) {
|
|
339
|
+
missing.push({ name, stacks, source: 'domain' });
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return { missing, installed: installedFiles.size, expected };
|
|
345
|
+
}
|