@polymorphism-tech/morph-spec 4.10.5 → 4.10.7

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 (54) hide show
  1. package/CLAUDE.md +3 -0
  2. package/README.md +3 -3
  3. package/bin/morph-spec.js +90 -1
  4. package/bin/task-manager.js +59 -6
  5. package/claude-plugin.json +1 -1
  6. package/docs/CHEATSHEET.md +1 -1
  7. package/docs/QUICKSTART.md +1 -1
  8. package/framework/agents/backend/hangfire-orchestrator.md +1 -1
  9. package/framework/agents/backend/ms-agent-expert.md +2 -1
  10. package/framework/agents/integrations/asaas-financial.md +1 -1
  11. package/framework/agents/integrations/clerk-auth.md +1 -2
  12. package/framework/agents/integrations/hangfire-integration.md +1 -1
  13. package/framework/agents/integrations/resend-email.md +1 -1
  14. package/framework/agents.json +345 -708
  15. package/framework/hooks/claude-code/post-tool-use/dispatch.js +10 -5
  16. package/framework/skills/README.md +0 -1
  17. package/framework/skills/level-1-workflows/morph-phase-clarify/SKILL.md +1 -1
  18. package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +1 -1
  19. package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +1 -1
  20. package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +42 -13
  21. package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +1 -1
  22. package/framework/skills/level-1-workflows/morph-phase-setup/SKILL.md +16 -12
  23. package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +1 -1
  24. package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +1 -1
  25. package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +1 -1
  26. package/package.json +1 -1
  27. package/src/commands/agents/agent-metrics.js +174 -0
  28. package/src/commands/agents/audit-dispatch.js +82 -0
  29. package/src/commands/agents/detect-agents.js +275 -0
  30. package/src/commands/agents/dispatch-agents.js +41 -135
  31. package/src/commands/agents/index.js +9 -0
  32. package/src/commands/agents/manage-agents.js +352 -0
  33. package/src/commands/project/doctor.js +61 -2
  34. package/src/commands/project/init.js +10 -2
  35. package/src/commands/project/update.js +12 -25
  36. package/src/commands/state/advance-phase.js +11 -3
  37. package/src/core/state/state-manager.js +81 -0
  38. package/src/lib/agents/agents-manager.js +349 -0
  39. package/src/lib/agents/metrics-calculator.js +125 -0
  40. package/src/lib/agents/output-collector.js +75 -0
  41. package/src/lib/generators/context-generator.js +8 -7
  42. package/src/lib/tasks/task-parser.js +94 -3
  43. package/src/lib/validators/checkpoint-persistence.js +39 -0
  44. package/src/lib/validators/content/content-validator.js +9 -1
  45. package/src/lib/validators/spec-validator.js +2 -2
  46. package/src/lib/validators/validation-runner.js +20 -0
  47. package/src/scripts/setup-infra.js +1 -17
  48. package/src/utils/agent-id-mapper.js +11 -0
  49. package/src/utils/agents-installer.js +40 -8
  50. package/src/utils/claude-settings-manager.js +1 -1
  51. package/src/utils/hooks-installer.js +0 -245
  52. package/src/utils/tier-helpers.js +24 -0
  53. package/framework/agents/architecture/po-pm-advisor.md +0 -203
  54. package/framework/hooks/claude-code/user-prompt/set-terminal-title.js +0 -68
@@ -69,11 +69,16 @@ async function dispatch(command, payload) {
69
69
  // Non-blocking
70
70
  }
71
71
 
72
- // Inject validator dispatch for task completion.
73
- // Skip when --skip-validation: no validators needed, avoids subprocess fallback.
74
- if (!skipValidation) {
75
- const validatorText = await buildValidatorDispatchContext(featureName, payload);
76
- if (validatorText) {
72
+ // Always inject validator dispatch — deferred if --skip-validation
73
+ const validatorText = await buildValidatorDispatchContext(featureName, payload);
74
+ if (validatorText) {
75
+ if (skipValidation) {
76
+ contextParts.push(
77
+ '⏩ DEFERRED TO CHECKPOINT — validators skipped on this task but MUST run at next checkpoint:\n\n' +
78
+ validatorText
79
+ );
80
+ dispatchResult = 'task_done_deferred_validators';
81
+ } else {
77
82
  contextParts.push(validatorText);
78
83
  dispatchResult = 'task_done_with_validators';
79
84
  }
@@ -17,7 +17,6 @@ Each skill is a directory: `{name}/SKILL.md` + optional `scripts/`, `references/
17
17
  | `verification-before-completion` | Gate check before marking any work done |
18
18
  | `code-review` | .NET/C# review checklist (naming, arch, async, DI, DTOs) |
19
19
  | `morph-replicate` | Convert HTML prototypes to Blazor components |
20
- | `terminal-title` | Manually override the terminal window title for the current task |
21
20
 
22
21
  ### level-0-meta extras
23
22
 
@@ -4,7 +4,7 @@ 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.5"
7
+ cliVersion: "4.10.7"
8
8
  ---
9
9
 
10
10
  # MORPH Clarify — Phase 3
@@ -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.5"
6
+ cliVersion: "4.10.7"
7
7
  ---
8
8
 
9
9
  # MORPH Codebase Analysis - Design Sub-phase
@@ -4,7 +4,7 @@ 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.5"
7
+ cliVersion: "4.10.7"
8
8
  ---
9
9
 
10
10
  # MORPH Design — Phase 2
@@ -5,7 +5,7 @@ argument-hint: "[feature-name]"
5
5
  context: fork
6
6
  agent: general-purpose
7
7
  user-invocable: false
8
- cliVersion: "4.10.5"
8
+ cliVersion: "4.10.7"
9
9
  allowed-tools: Read, Write, Edit, Bash, Glob, Grep, AskUserQuestion, Agent
10
10
  ---
11
11
 
@@ -112,7 +112,7 @@ Use `AskUserQuestion` with header `"{MCP}"` and options:
112
112
  | Action | Tool | Alternative |
113
113
  |--------|------|-------------|
114
114
  | Check execution mode (single/subagents/agent-teams) | **Bash** `npx morph-spec dispatch-agents $ARGUMENTS implement` | Task groups + subagent prompts |
115
- | **Dispatch implementers by domain** (subagents) | **Agent** `subagent_type=<taskGroups[group].agentId>` + `prompt=taskPrompt` from dispatch config | Backend + frontend + infra in parallel |
115
+ | **Dispatch implementers by domain** (subagents) | **Agent** `subagent_type=<taskGroups[group].subagentType>` + `prompt=taskPrompt` from dispatch config | Backend + frontend + infra in parallel |
116
116
  | **Create Agent Team** (critical multi-domain features) | Natural language — see Step 0.5b | — |
117
117
  | Read task details | **Read** tasks.md, spec.md, contracts.cs/.ts | — |
118
118
  | Create new files | **Write** source files | — |
@@ -216,21 +216,44 @@ The strategy was defined and approved in the Plan phase. Use it directly:
216
216
  | `"subagents"` | → Dispatch Task subagents in parallel (below) |
217
217
  | `"agent-teams"` | → Create Agent Team with plan approval (Step 0.5b) |
218
218
 
219
- If `plan.md` doesn't exist or the field is missing, run as fallback:
219
+ If `plan.md` doesn't exist or the field is missing, auto-detect via dispatch-agents:
220
220
 
221
221
  ```bash
222
222
  npx morph-spec dispatch-agents $ARGUMENTS implement
223
223
  ```
224
224
 
225
+ Check the `mode` field in the JSON output to determine the execution strategy.
226
+
225
227
  ---
226
228
 
227
229
  #### Subagents Mode (if strategy is `"subagents"`):
228
230
 
229
- 1. Read `taskGroups` from dispatch config (e.g., `backend`, `frontend`, `infra`)
230
- 2. For each group with >= 3 tasks and no cross-group dependencies, use **Agent tool** with `subagent_type=<agentId>` and `taskPrompt` from dispatch config:
231
+ **Step 0.5a: Get dispatch config:**
232
+
233
+ ```bash
234
+ npx morph-spec dispatch-agents $ARGUMENTS implement
235
+ ```
236
+
237
+ Parse the JSON output. Extract `taskGroups` and each group's `subagentType`.
238
+
239
+ **Step 0.5b: Verify agent files exist:**
240
+
241
+ For each group, verify `.claude/agents/{subagentType}.md` exists:
231
242
 
232
- > **Mapping:** `taskGroups[group].agentId` = `subagent_type` in Agent tool.
233
- > Example: `agentId: "dotnet-senior"` → `Agent(subagent_type=dotnet-senior, prompt=taskPrompt)`.
243
+ ```bash
244
+ ls .claude/agents/{subagentType}.md
245
+ ```
246
+
247
+ If missing → **STOP** with: `"Agent file .claude/agents/{subagentType}.md not found. Run: npx morph-spec setup-infra"`. Do NOT fall back to general-purpose agents for missing files.
248
+
249
+ **Step 0.5c: Dispatch subagents:**
250
+
251
+ 1. For each group with >= 3 tasks and no cross-group dependencies, use **Agent tool** with `subagent_type` from dispatch config:
252
+
253
+ > **Mapping:** `taskGroups[group].subagentType` = `subagent_type` in Agent tool.
254
+ > Example: `subagentType: "morph-dotnet-senior"` → `Agent(subagent_type="morph-dotnet-senior", prompt=taskPrompt)`.
255
+ >
256
+ > **MANDATORY:** Use the exact `subagentType` value. Do NOT use `general-purpose` agents when a `subagentType` is available.
234
257
 
235
258
  Each subagent receives a prompt with:
236
259
  - Full spec.md and contracts.cs/.ts content
@@ -239,8 +262,10 @@ Each subagent receives a prompt with:
239
262
  - Domain isolation: modify ONLY files in their domain
240
263
  - **MANDATORY verification before every `task done`** (see Step 2, item 6b)
241
264
 
242
- 3. Dispatch subagents for groups in **parallel** (when no cross-group dependencies)
243
- 4. After all complete, validate integration: `npx morph-spec validate-feature $ARGUMENTS --phase implement`
265
+ 2. Dispatch subagents for groups in **parallel** (when no cross-group dependencies)
266
+ 3. After all complete, validate integration: `npx morph-spec validate-feature $ARGUMENTS --phase implement`
267
+
268
+ **Fallback:** If `dispatch-agents` fails (non-zero exit or invalid JSON), log a warning and fall back to sequential single-agent mode (Step 2).
244
269
 
245
270
  **If strategy is `"single"`** → continue to sequential task loop below.
246
271
 
@@ -410,22 +435,26 @@ Implementers are dispatched via `dispatch-agents`:
410
435
  npx morph-spec dispatch-agents $ARGUMENTS implement
411
436
  ```
412
437
 
413
- Use the `agentId` from each group as `subagent_type` in the Agent tool:
438
+ Use the `subagentType` from each group in the Agent tool:
414
439
  ```
415
- Agent(subagent_type=<agentId>, prompt=<filled prompts/implementer-prompt.md>)
440
+ Agent(subagent_type=<subagentType>, prompt=<filled prompts/implementer-prompt.md>)
416
441
  ```
417
442
 
443
+ > The `subagentType` maps agent IDs to `.claude/agents/` filenames:
444
+ > - Tier 1-3: `morph-{id}` (e.g., `morph-dotnet-senior`)
445
+ > - Tier 4: `morph-validator-{id}` (e.g., `morph-validator-architecture-expert`)
446
+
418
447
  ### Review (Two-Stage)
419
448
 
420
449
  **Stage 1 — Spec Compliance (mandatory):**
421
450
  ```
422
- Agent(subagent_type=<tier-4 validator>, prompt=<filled prompts/spec-reviewer-prompt.md>)
451
+ Agent(subagent_type=<validator subagentType>, prompt=<filled prompts/spec-reviewer-prompt.md>)
423
452
  ```
424
453
  Use validators from `dispatch-agents --mode validate`. Spec compliance MUST pass before proceeding.
425
454
 
426
455
  **Stage 2 — Code Quality (mandatory):**
427
456
  ```
428
- Agent(subagent_type=standards-architect, prompt=<filled prompts/code-quality-reviewer-prompt.md>)
457
+ Agent(subagent_type=morph-standards-architect, prompt=<filled prompts/code-quality-reviewer-prompt.md>)
429
458
  ```
430
459
  `critical` issues block. `important` issues must be fixed. `suggestion` is optional.
431
460
 
@@ -4,7 +4,7 @@ description: MORPH-SPEC Phase 4 (Plan). Generates a detailed implementation plan
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.5"
7
+ cliVersion: "4.10.7"
8
8
  ---
9
9
 
10
10
  # MORPH Plan — Phase 4
@@ -4,7 +4,7 @@ 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.5"
7
+ cliVersion: "4.10.7"
8
8
  ---
9
9
 
10
10
  # MORPH Setup — Phase 1
@@ -27,7 +27,7 @@ Initialize the context and prepare the environment for an approved feature.
27
27
  | Action | Tool | Alternative |
28
28
  |--------|------|-------------|
29
29
  | Verify state | **Bash** `npx morph-spec state get` | — |
30
- | Detect agents + standards | **Read** `.morph/framework/agents.json` → match keywords → **Bash** `npx morph-spec state add-agent` | — |
30
+ | Detect agents + standards | **Bash** `npx morph-spec detect-agents $ARGUMENTS --json` → parse → **Bash** `npx morph-spec state add-agent` | **Read** agents.json manually |
31
31
  | Read project context | **Read** `.morph/context/README.md` | — |
32
32
  | Read config | **Read** `.morph/config/config.json` | — |
33
33
  | Detect VSA architecture | **Read** `.morph/config/config.json` → check `config.architecture.style` | — |
@@ -38,8 +38,8 @@ Initialize the context and prepare the environment for an approved feature.
38
38
  **MCPs for this phase:** GitHub (optional — repo metadata).
39
39
 
40
40
  **Anti-patterns:**
41
- - Do not call a `detect-agents` CLI command (it doesn't exist — read `.morph/framework/agents.json` directly)
42
- - Do not use a Task agent for inline keyword matching (fast enough to do directly)
41
+ - Do not read agents.json manually for keyword matching — use `npx morph-spec detect-agents` CLI
42
+ - Do not use a Task agent for agent detection (CLI is faster and consistent)
43
43
  - Do not use WebSearch for local project info (use Read/Glob)
44
44
 
45
45
  ---
@@ -87,18 +87,22 @@ Proceed with normal keyword detection for all other agents. The architect was al
87
87
 
88
88
  #### Step 2.1: Keyword Detection
89
89
 
90
- Read agents.json and match keywords (except `domain-architect` and `vsa-architect` which were handled in Step 2.0):
90
+ Use the `detect-agents` CLI to match agent keywords against the proposal (excludes architects already handled in Step 2.0):
91
91
 
92
- 1. Read `.morph/framework/agents.json` (or `framework/agents.json` in the framework project itself)
93
- 2. Extract the feature title and description from `0-proposal/proposal.md`
94
- 3. For each agent in the JSON (except architects), check if any keyword from the `keywords[]` field appears in the proposal text
95
- 4. Include all agents with `always_active: true` automatically
96
- 5. Add matched agents to state:
92
+ ```bash
93
+ npx morph-spec detect-agents $ARGUMENTS --exclude domain-architect,vsa-architect --json
94
+ ```
95
+
96
+ This reads the proposal from `.morph/features/$ARGUMENTS/0-proposal/proposal.md` and agents.json, returning JSON with `detected[]` and `skipped[]` arrays. Agents with `active_phases: ['*']` are always included.
97
+
98
+ Parse the JSON output and add each detected agent to state:
97
99
 
98
100
  ```bash
99
101
  npx morph-spec state add-agent $ARGUMENTS {agent-id}
100
102
  ```
101
103
 
104
+ Run `add-agent` once per detected agent ID from the `detected[]` array.
105
+
102
106
  **To load standards for detected agents:**
103
107
  - For each active agent, read the standards referenced by the `standards[]` field in `agents.json`
104
108
  - Standard files are located at `.morph/framework/standards/{path}`
@@ -161,10 +165,10 @@ npx morph-spec doctor 2>&1 | grep -A 1 "agents completeness"
161
165
 
162
166
  If the output shows **missing agents**, inform the user:
163
167
 
164
- > ⚠ Some agents for your stack are not installed. Run `npx morph-spec init --force` to install them.
168
+ > ⚠ Some agents for your stack are not installed. Run `npx morph-spec setup-infra` to install them.
165
169
 
166
170
  Use `AskUserQuestion` with header `"Agents"` and options:
167
- - **Install now** — run `npx morph-spec init --force` to reinstall with current stack tags
171
+ - **Install now** — run `npx morph-spec setup-infra` to reinstall with current stack tags
168
172
  - **Continue without** — proceed with available agents (may lack specialized support)
169
173
 
170
174
  If "Install now" is selected, run the command and continue.
@@ -4,7 +4,7 @@ description: MORPH-SPEC Phase 4 (Tasks). Breaks approved spec into bottom-up ord
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.5"
7
+ cliVersion: "4.10.7"
8
8
  ---
9
9
 
10
10
  # MORPH Tasks — Phase 4
@@ -4,7 +4,7 @@ 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.5"
7
+ cliVersion: "4.10.7"
8
8
  ---
9
9
 
10
10
  # MORPH UI/UX Design - Phase 1.5
@@ -4,7 +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.5"
7
+ cliVersion: "4.10.7"
8
8
  ---
9
9
 
10
10
  # Scope Escalation Workflow
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polymorphism-tech/morph-spec",
3
- "version": "4.10.5",
3
+ "version": "4.10.7",
4
4
  "description": "MORPH-SPEC: AI-First development framework with validation pipeline and multi-stack support",
5
5
  "keywords": [
6
6
  "claude-code",
@@ -0,0 +1,174 @@
1
+ /**
2
+ * agent-metrics — Display agent performance metrics for a feature
3
+ *
4
+ * Reads dispatchLog from state.json, computes per-agent metrics
5
+ * (dispatch count, task coverage, completion rate, avg duration),
6
+ * and renders a table or JSON output.
7
+ *
8
+ * Usage:
9
+ * morph-spec agent-metrics <feature>
10
+ * morph-spec agent-metrics <feature> --json
11
+ * morph-spec agent-metrics --all
12
+ */
13
+
14
+ import { loadState, stateExists } from '../../core/state/state-manager.js';
15
+ import { calculateMetrics, calculateAggregatedMetrics, formatDuration } from '../../lib/agents/metrics-calculator.js';
16
+ import chalk from 'chalk';
17
+
18
+ /**
19
+ * CLI command: morph-spec agent-metrics [feature]
20
+ *
21
+ * @param {string} [featureName] - Feature name (required unless --all)
22
+ * @param {Object} [options] - CLI options
23
+ * @param {boolean} [options.json] - Output as JSON instead of table
24
+ * @param {boolean} [options.all] - Aggregate metrics across all features
25
+ */
26
+ export async function agentMetricsCommand(featureName, options = {}) {
27
+ if (!options.all && !featureName) {
28
+ console.error(chalk.red('Usage: morph-spec agent-metrics <feature> [--json] [--all]'));
29
+ process.exit(1);
30
+ }
31
+
32
+ if (!stateExists()) {
33
+ console.error(chalk.red('No state.json found.'));
34
+ process.exit(1);
35
+ }
36
+
37
+ const state = loadState();
38
+
39
+ if (options.all) {
40
+ return renderAggregated(state.features || {}, options);
41
+ }
42
+
43
+ const feature = state.features?.[featureName];
44
+ if (!feature) {
45
+ console.error(chalk.red(`Feature '${featureName}' not found.`));
46
+ process.exit(1);
47
+ }
48
+
49
+ const log = feature.dispatchLog || [];
50
+ if (log.length === 0) {
51
+ console.log(chalk.gray('\n No dispatch events recorded.\n'));
52
+ return;
53
+ }
54
+
55
+ const result = calculateMetrics(log);
56
+ result.feature = featureName;
57
+
58
+ if (options.json) {
59
+ console.log(JSON.stringify(result, null, 2));
60
+ return;
61
+ }
62
+
63
+ renderTable(featureName, result);
64
+ }
65
+
66
+ /**
67
+ * Render per-feature metrics table.
68
+ */
69
+ function renderTable(featureName, result) {
70
+ const { agents, summary } = result;
71
+
72
+ console.log(chalk.bold(`\n Agent Performance \u2014 ${featureName}`));
73
+ console.log(' ' + '\u2500'.repeat(70));
74
+
75
+ // Header
76
+ console.log(
77
+ ' ' +
78
+ pad('Agent', 28) +
79
+ pad('Dispatches', 12) +
80
+ pad('Tasks', 8) +
81
+ pad('Completed', 12) +
82
+ pad('Avg Duration', 14)
83
+ );
84
+ console.log(' ' + '\u2500'.repeat(70));
85
+
86
+ for (const agent of agents) {
87
+ const tasks = agent.tasksAssigned > 0 ? String(agent.tasksAssigned) : '\u2014';
88
+ const completed = agent.tasksAssigned > 0
89
+ ? `${agent.tasksCompleted}/${agent.tasksAssigned}`
90
+ : '\u2014';
91
+ const duration = formatDuration(agent.avgDurationMs);
92
+
93
+ console.log(
94
+ ' ' +
95
+ pad(agent.id, 28) +
96
+ pad(String(agent.dispatches), 12) +
97
+ pad(tasks, 8) +
98
+ pad(completed, 12) +
99
+ pad(duration, 14)
100
+ );
101
+ }
102
+
103
+ console.log();
104
+ const rate = summary.totalTasks > 0
105
+ ? `${Math.round(summary.completionRate * 100)}%`
106
+ : '0%';
107
+ console.log(` Summary: ${summary.completedTasks} tasks completed of ${summary.totalTasks} (${rate}), avg ${formatDuration(summary.avgTaskDurationMs)}/task`);
108
+ console.log(` Dispatches: ${summary.totalDispatches} total`);
109
+ console.log();
110
+ }
111
+
112
+ /**
113
+ * Render aggregated metrics across all features.
114
+ */
115
+ function renderAggregated(features, options) {
116
+ const featureNames = Object.keys(features);
117
+ if (featureNames.length === 0) {
118
+ console.log(chalk.gray('\n No features found.\n'));
119
+ return;
120
+ }
121
+
122
+ const result = calculateAggregatedMetrics(features);
123
+
124
+ if (options.json) {
125
+ console.log(JSON.stringify(result, null, 2));
126
+ return;
127
+ }
128
+
129
+ console.log(chalk.bold(`\n Agent Performance \u2014 All Features (${result.featureCount})`));
130
+ console.log(' ' + '\u2500'.repeat(70));
131
+
132
+ console.log(
133
+ ' ' +
134
+ pad('Agent', 28) +
135
+ pad('Dispatches', 12) +
136
+ pad('Tasks', 8) +
137
+ pad('Completed', 12) +
138
+ pad('Avg Duration', 14)
139
+ );
140
+ console.log(' ' + '\u2500'.repeat(70));
141
+
142
+ for (const agent of result.agents) {
143
+ const tasks = agent.tasksAssigned > 0 ? String(agent.tasksAssigned) : '\u2014';
144
+ const completed = agent.tasksAssigned > 0
145
+ ? `${agent.tasksCompleted}/${agent.tasksAssigned}`
146
+ : '\u2014';
147
+ const duration = formatDuration(agent.avgDurationMs);
148
+
149
+ console.log(
150
+ ' ' +
151
+ pad(agent.id, 28) +
152
+ pad(String(agent.dispatches), 12) +
153
+ pad(tasks, 8) +
154
+ pad(completed, 12) +
155
+ pad(duration, 14)
156
+ );
157
+ }
158
+
159
+ console.log();
160
+ const { summary } = result;
161
+ const rate = summary.totalTasks > 0
162
+ ? `${Math.round(summary.completionRate * 100)}%`
163
+ : '0%';
164
+ console.log(` Summary: ${summary.completedTasks} tasks completed of ${summary.totalTasks} (${rate}), avg ${formatDuration(summary.avgTaskDurationMs)}/task`);
165
+ console.log(` Dispatches: ${summary.totalDispatches} total across ${result.featureCount} features`);
166
+ console.log();
167
+ }
168
+
169
+ /**
170
+ * Right-pad a string to a given width.
171
+ */
172
+ function pad(str, width) {
173
+ return str.length >= width ? str : str + ' '.repeat(width - str.length);
174
+ }
@@ -0,0 +1,82 @@
1
+ /**
2
+ * audit-dispatch — Display dispatch audit log for a feature
3
+ *
4
+ * Reads dispatchLog from state.json and presents a summary:
5
+ * - Total dispatches
6
+ * - Per-dispatch: timestamp, phase, mode, agents with subagentTypes
7
+ * - Agent coverage: which agents handled which tasks
8
+ *
9
+ * Usage:
10
+ * morph-spec audit-dispatch <feature>
11
+ * morph-spec audit-dispatch <feature> --json
12
+ */
13
+
14
+ import { loadState, stateExists } from '../../core/state/state-manager.js';
15
+ import chalk from 'chalk';
16
+
17
+ /**
18
+ * CLI command: morph-spec audit-dispatch <feature>
19
+ *
20
+ * @param {string} featureName - Feature name
21
+ * @param {Object} [options] - CLI options
22
+ * @param {boolean} [options.json] - Output as JSON instead of table
23
+ */
24
+ export async function auditDispatchCommand(featureName, options = {}) {
25
+ if (!featureName) {
26
+ console.error(chalk.red('Usage: morph-spec audit-dispatch <feature>'));
27
+ process.exit(1);
28
+ }
29
+
30
+ if (!stateExists()) {
31
+ console.error(chalk.red('No state.json found.'));
32
+ process.exit(1);
33
+ }
34
+
35
+ const state = loadState();
36
+ const feature = state.features?.[featureName];
37
+ if (!feature) {
38
+ console.error(chalk.red(`Feature '${featureName}' not found.`));
39
+ process.exit(1);
40
+ }
41
+
42
+ const log = feature.dispatchLog || [];
43
+
44
+ if (options.json) {
45
+ console.log(JSON.stringify(log, null, 2));
46
+ return;
47
+ }
48
+
49
+ console.log(chalk.bold(`\n Dispatch Audit — ${featureName}`));
50
+ console.log(' ' + '─'.repeat(50));
51
+ console.log(` Total dispatches: ${log.length}`);
52
+
53
+ if (log.length === 0) {
54
+ console.log(chalk.gray('\n No dispatch events recorded.\n'));
55
+ return;
56
+ }
57
+
58
+ for (let i = 0; i < log.length; i++) {
59
+ const entry = log[i];
60
+ console.log(`\n #${i + 1}: ${entry.timestamp} — ${entry.phase} (${entry.mode})`);
61
+ for (const agent of entry.agents || []) {
62
+ const taskCount = agent.tasks?.length || 0;
63
+ console.log(` ${agent.id} → ${agent.subagentType} (${agent.group}) — ${taskCount} tasks`);
64
+ }
65
+ }
66
+
67
+ // Agent coverage summary
68
+ const coverage = {};
69
+ for (const entry of log) {
70
+ for (const agent of entry.agents || []) {
71
+ if (!coverage[agent.id]) coverage[agent.id] = { tasks: 0, dispatches: 0 };
72
+ coverage[agent.id].tasks += agent.tasks?.length || 0;
73
+ coverage[agent.id].dispatches += 1;
74
+ }
75
+ }
76
+
77
+ console.log(chalk.bold('\n Agent Coverage:'));
78
+ for (const [id, data] of Object.entries(coverage)) {
79
+ console.log(` ${id}: ${data.tasks} tasks across ${data.dispatches} dispatches`);
80
+ }
81
+ console.log();
82
+ }