@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.
Files changed (36) hide show
  1. package/README.md +6 -6
  2. package/bin/morph-spec.js +3 -3
  3. package/claude-plugin.json +1 -1
  4. package/docs/CHEATSHEET.md +5 -5
  5. package/docs/QUICKSTART.md +5 -5
  6. package/framework/CLAUDE.md +4 -4
  7. package/framework/agents/infrastructure/azure-deploy-specialist.md +1 -1
  8. package/framework/agents/quality/code-analyzer.md +1 -1
  9. package/framework/agents.json +4 -0
  10. package/framework/commands/morph-apply.md +4 -4
  11. package/framework/commands/morph-infra.md +10 -10
  12. package/framework/commands/morph-preflight.md +3 -3
  13. package/framework/commands/morph-proposal.md +2 -2
  14. package/framework/commands/morph-status.md +3 -3
  15. package/framework/hooks/claude-code/pre-compact/save-morph-context.js +1 -1
  16. package/framework/rules/morph-workflow.md +3 -3
  17. package/framework/skills/README.md +1 -1
  18. package/framework/skills/level-0-meta/morph-init/SKILL.md +2 -2
  19. package/framework/skills/level-1-workflows/morph-phase-clarify/SKILL.md +2 -2
  20. package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +1 -1
  21. package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +2 -2
  22. package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +3 -3
  23. package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +2 -2
  24. package/framework/skills/level-1-workflows/morph-phase-setup/SKILL.md +22 -2
  25. package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +3 -3
  26. package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +2 -2
  27. package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +2 -1
  28. package/framework/standards/ai-agents/team-orchestration.md +1 -1
  29. package/package.json +1 -1
  30. package/src/commands/project/doctor.js +27 -4
  31. package/src/commands/project/init.js +33 -3
  32. package/src/commands/state/advance-phase.js +7 -7
  33. package/src/lib/detectors/index.js +86 -38
  34. package/src/lib/stack-filter.js +11 -4
  35. package/src/scripts/setup-infra.js +2 -2
  36. 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.2
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-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 |
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.2 by [Polymorphism Tech](https://polymorphism.tech)*
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-init skill.')
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-init skill.')
86
+ .description('Install a Claude Code plugin (superpowers, context7). Called by /morph:init skill.')
87
87
  .action(installPluginCommand);
88
88
 
89
89
  program
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "morph-spec",
3
- "version": "4.10.2",
3
+ "version": "4.10.4",
4
4
  "displayName": "MORPH-SPEC Framework",
5
5
  "description": "Spec-driven development with 38 agents and 8-phase workflow for .NET/Blazor/Next.js/Azure",
6
6
  "publisher": "polymorphism-tech",
@@ -39,10 +39,10 @@ Phases in brackets are optional.
39
39
 
40
40
  | Command | Description |
41
41
  |---------|-------------|
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) |
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.2 by Polymorphism Tech*
201
+ *morph-spec v4.10.4 by Polymorphism Tech*
@@ -45,7 +45,7 @@ morph-spec doctor
45
45
  Open your project in Claude Code and run:
46
46
 
47
47
  ```
48
- /morph-proposal my-feature
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-apply my-feature to implement
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-apply my-feature
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-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.
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.2 by Polymorphism Tech*
206
+ *morph-spec v4.10.4 by Polymorphism Tech*
@@ -23,10 +23,10 @@
23
23
 
24
24
  | Command | Purpose |
25
25
  |---------|---------|
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 |
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-infra** - Rodar comando de setup de infra primeiro
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-apply` done) | Yes | Full feature code |
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 |
@@ -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-apply — Feature Implementation
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-apply {feature-name}
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-proposal $ARGUMENTS to complete planning phases.
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-preflight` before deploying to validate packages, migrations, Docker, and secrets."
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-infra - Infrastructure Management
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-infra [action] [options]
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-infra init`:
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-infra validate`:
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-infra plan [env]`:
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-infra deploy [env]`:
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-infra destroy [env]`:
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-infra init
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-infra validate to verify.
185
+ Done! Run /morph:infra validate to verify.
186
186
 
187
- User: /morph-infra plan dev
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-preflight azure
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-apply {feature} to complete implementation before deploying.`
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-preflight azure
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-proposal. This is the primary entry point for all MORPH-SPEC feature work.
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-apply {feature-name} to start implementation.
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-proposal {feature}` - Create a new feature
87
- - `/morph-apply {feature}` - Implement a feature
88
- - `/morph-archive {feature}` - Archive a completed feature
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-proposal ${n}`,
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-proposal {feature}` | Full spec pipeline (phases 1-4, pauses for approval) |
41
- | `/morph-apply {feature}` | Implement feature (phase 5) |
42
- | `/morph-status` | Feature dashboard |
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-proposal` and `/morph-apply`. Not user-invocable.
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-proposal`.
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-proposal [feature-name]
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.2"
7
+ cliVersion: "4.10.4"
8
8
  ---
9
9
 
10
10
  # MORPH Clarify — Phase 3
11
11
 
12
- > INTERNAL: Workflow skill used by /morph-proposal during automated phase orchestration. Not a user command.
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.2"
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.2"
7
+ cliVersion: "4.10.4"
8
8
  ---
9
9
 
10
10
  # MORPH Design — Phase 2
11
11
 
12
- > INTERNAL: Workflow skill used by /morph-proposal during automated phase orchestration. Not a user command.
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.2"
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-apply during automated phase orchestration. Not a user command.
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-apply`.
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.2"
8
+ cliVersion: "4.10.4"
9
9
  ---
10
10
 
11
11
  # MORPH Plan — Phase 4
12
12
 
13
- > INTERNAL: Workflow skill used by /morph-proposal during automated phase orchestration. Not a user command.
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.2"
7
+ cliVersion: "4.10.4"
8
8
  ---
9
9
 
10
10
  # MORPH Setup — Phase 1
11
11
 
12
- > INTERNAL: Workflow skill used by /morph-proposal during automated phase orchestration. Not a user command.
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.2"
8
+ cliVersion: "4.10.4"
9
9
  ---
10
10
 
11
11
  # MORPH Tasks — Phase 4
12
12
 
13
- > INTERNAL: Workflow skill used by /morph-proposal during automated phase orchestration. Not a user command.
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-apply $ARGUMENTS` to start implementation."
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.2"
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-proposal during automated phase orchestration. Not a user command.
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-proposal <feature>` to continue from the tasks phase."
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-proposal scheduled-reports
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polymorphism-tech/morph-spec",
3
- "version": "4.10.2",
3
+ "version": "4.10.4",
4
4
  "description": "MORPH-SPEC: AI-First development framework with validation pipeline and multi-stack support",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -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 agentsPath = join(claudePath, 'agents');
691
- if (await pathExists(agentsPath)) {
692
- const agentFiles = await fs.readdir(agentsPath);
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-init)'));
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-proposal <feature>` to start a new feature.');
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-proposal ${feature}`,
471
+ `Resume spec pipeline: /morph:proposal ${feature}`,
472
472
  'Auto-continues from setup phase'
473
473
  ],
474
474
  'uiux': [
475
- `Resume spec pipeline: /morph-proposal ${feature}`,
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-proposal ${feature}`,
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-proposal ${feature}`,
488
+ `Resume spec pipeline: /morph:proposal ${feature}`,
489
489
  'Resolve edge cases and unknowns'
490
490
  ],
491
491
  'plan': [
492
- `Resume spec pipeline: /morph-proposal ${feature}`,
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-proposal ${feature}`,
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-apply ${feature}`,
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
- const pkgPath = join(targetPath, 'package.json');
33
- let pkg = null;
34
- if (existsSync(pkgPath)) {
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 (pkg) {
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 (pkg.name) {
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
- if (structure.stack === 'unknown') {
90
- // Check for .csproj in common locations
91
- const csprojLocations = ['.', 'src', 'src/Api', 'src/Web'];
92
- for (const loc of csprojLocations) {
93
- try {
94
- const dir = join(targetPath, loc);
95
- if (existsSync(dir)) {
96
- const entries = readdirSyncSafe(dir);
97
- if (entries.some(e => e.endsWith('.csproj'))) {
98
- structure.stack = 'dotnet';
99
- // Check for Blazor
100
- for (const entry of entries.filter(e => e.endsWith('.csproj'))) {
101
- try {
102
- const content = readFileSync(join(dir, entry), 'utf8');
103
- if (content.includes('Microsoft.AspNetCore.Components') || content.includes('Blazor')) {
104
- structure.stack = 'blazor';
105
- }
106
- } catch { /* ignore */ }
107
- }
108
- break;
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
- } catch { /* ignore */ }
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
@@ -37,13 +37,20 @@ export function parseStacks(content) {
37
37
  }
38
38
 
39
39
  /**
40
- * Extract project stack tags from config object.
41
- * Splits `project.stack` by '-' or '+' to produce tag array.
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
- if (!stack || typeof stack !== 'string') return [];
46
- return stack.split(/[-+]/).filter(Boolean);
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-init Claude Code skill via `morph-spec setup-infra` without
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, { projectStack: null });
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 { projectStack = null } = options;
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
- // Skip dotnet-senior for non-.NET projects
47
- if (a.id === 'dotnet-senior' && projectStack && !DOTNET_STACKS.includes(projectStack)) {
48
- return false;
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
+ }