@polymorphism-tech/morph-spec 4.10.6 → 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 (47) hide show
  1. package/CLAUDE.md +3 -0
  2. package/README.md +2 -2
  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.json +345 -708
  9. package/framework/hooks/claude-code/post-tool-use/dispatch.js +10 -5
  10. package/framework/skills/README.md +0 -1
  11. package/framework/skills/level-1-workflows/morph-phase-clarify/SKILL.md +1 -1
  12. package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +1 -1
  13. package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +1 -1
  14. package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +42 -13
  15. package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +1 -1
  16. package/framework/skills/level-1-workflows/morph-phase-setup/SKILL.md +14 -10
  17. package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +1 -1
  18. package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +1 -1
  19. package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +1 -1
  20. package/package.json +1 -1
  21. package/src/commands/agents/agent-metrics.js +174 -0
  22. package/src/commands/agents/audit-dispatch.js +82 -0
  23. package/src/commands/agents/detect-agents.js +275 -0
  24. package/src/commands/agents/dispatch-agents.js +41 -135
  25. package/src/commands/agents/index.js +9 -0
  26. package/src/commands/agents/manage-agents.js +352 -0
  27. package/src/commands/project/doctor.js +59 -0
  28. package/src/commands/project/update.js +1 -17
  29. package/src/commands/state/advance-phase.js +11 -3
  30. package/src/core/state/state-manager.js +81 -0
  31. package/src/lib/agents/agents-manager.js +349 -0
  32. package/src/lib/agents/metrics-calculator.js +125 -0
  33. package/src/lib/agents/output-collector.js +75 -0
  34. package/src/lib/generators/context-generator.js +8 -7
  35. package/src/lib/tasks/task-parser.js +94 -3
  36. package/src/lib/validators/checkpoint-persistence.js +39 -0
  37. package/src/lib/validators/content/content-validator.js +9 -1
  38. package/src/lib/validators/spec-validator.js +2 -2
  39. package/src/lib/validators/validation-runner.js +20 -0
  40. package/src/scripts/setup-infra.js +1 -17
  41. package/src/utils/agent-id-mapper.js +11 -0
  42. package/src/utils/agents-installer.js +40 -8
  43. package/src/utils/claude-settings-manager.js +1 -1
  44. package/src/utils/hooks-installer.js +0 -245
  45. package/src/utils/tier-helpers.js +24 -0
  46. package/framework/agents/architecture/po-pm-advisor.md +0 -203
  47. package/framework/hooks/claude-code/user-prompt/set-terminal-title.js +0 -68
package/CLAUDE.md ADDED
@@ -0,0 +1,3 @@
1
+ # Project
2
+
3
+ @.claude/CLAUDE.md
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
6
+ **Version:** 4.10.7
7
7
  **Requires:** Node.js 18+, Claude Code
8
8
 
9
9
  ---
@@ -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.6 by [Polymorphism Tech](https://polymorphism.tech)*
379
+ *morph-spec v4.10.7 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';
@@ -26,6 +26,16 @@ import { phaseResetCommand } from '../src/commands/phase/phase-reset.js';
26
26
 
27
27
  // Agent commands
28
28
  import { dispatchAgentsCommand } from '../src/commands/agents/dispatch-agents.js';
29
+ import { auditDispatchCommand } from '../src/commands/agents/audit-dispatch.js';
30
+ import { detectAgentsCommand } from '../src/commands/agents/detect-agents.js';
31
+ import { agentMetricsCommand } from '../src/commands/agents/agent-metrics.js';
32
+ import {
33
+ agentListCommand,
34
+ agentShowCommand,
35
+ agentAddCommand,
36
+ agentEditCommand,
37
+ agentRemoveCommand,
38
+ } from '../src/commands/agents/manage-agents.js';
29
39
 
30
40
  // Task commands
31
41
  import { taskDoneCommand, taskStartCommand, taskNextCommand } from '../src/commands/tasks/task.js';
@@ -300,9 +310,33 @@ program
300
310
  .description('Build dispatch config for parallel agent orchestration (design | tasks | implement)')
301
311
  .option('--table', 'Human-readable table output instead of JSON')
302
312
  .option('--mode <mode>', 'Dispatch mode: validate (includes Tier-4 validators as read-only)')
313
+ .option('--preflight', 'Exit with code 1 if any agent files are missing')
303
314
  .option('-v, --verbose', 'Show stack trace on error')
304
315
  .action((feature, phase, options) => dispatchAgentsCommand(feature, phase, options));
305
316
 
317
+ program
318
+ .command('audit-dispatch <feature>')
319
+ .description('Display dispatch audit log for a feature')
320
+ .option('--json', 'Output as JSON instead of table')
321
+ .action((feature, options) => auditDispatchCommand(feature, options));
322
+
323
+ program
324
+ .command('agent-metrics [feature]')
325
+ .description('Display agent performance metrics for a feature')
326
+ .option('--json', 'Output as JSON instead of table')
327
+ .option('--all', 'Aggregate metrics across all features')
328
+ .action((feature, options) => agentMetricsCommand(feature, options));
329
+
330
+ program
331
+ .command('detect-agents <feature>')
332
+ .description('Detect relevant agents for a feature based on proposal keywords')
333
+ .option('--proposal <path>', 'Path to proposal file (default: .morph/features/{feature}/0-proposal/proposal.md)')
334
+ .option('--json', 'Output as JSON (default)')
335
+ .option('--table', 'Human-readable table output')
336
+ .option('--exclude <ids>', 'Comma-separated agent IDs to exclude')
337
+ .option('--refresh', 'Refresh detection using all phase outputs (additive, mutates state)')
338
+ .action((feature, options) => detectAgentsCommand(feature, options));
339
+
306
340
  // Scope escalation commands
307
341
  const scopeCommand = program
308
342
  .command('scope')
@@ -317,4 +351,59 @@ scopeCommand
317
351
  .option('--dry-run', 'Show recommendation without executing')
318
352
  .action((feature, options) => scopeEscalateCommand(feature, options));
319
353
 
354
+ // Agent management commands (CRUD)
355
+ const agentCommand = program
356
+ .command('agent')
357
+ .description('Manage agents in agents.json (list, show, add, edit, remove)');
358
+
359
+ agentCommand
360
+ .command('list')
361
+ .description('List all agents with optional filters')
362
+ .option('--group <name>', 'Filter by group')
363
+ .option('--tier <n>', 'Filter by tier (1-4)')
364
+ .option('--json', 'Output as JSON')
365
+ .action((options) => agentListCommand(options));
366
+
367
+ agentCommand
368
+ .command('show <id>')
369
+ .description('Show detailed info for an agent')
370
+ .option('--json', 'Output as JSON')
371
+ .option('--full', 'Show full spawn_prompt (no truncation)')
372
+ .action((id, options) => agentShowCommand(id, options));
373
+
374
+ agentCommand
375
+ .command('add <id>')
376
+ .description('Add a new agent via interactive wizard')
377
+ .option('--non-interactive', 'Use flags instead of prompts')
378
+ .option('--tier <n>', 'Tier (1-4)')
379
+ .option('--title <title>', 'Agent title')
380
+ .option('--group <group>', 'Agent group')
381
+ .option('--active-phases <phases>', 'Comma-separated phases or *')
382
+ .option('--reports-to <id>', 'Parent agent ID or "user"')
383
+ .option('--domains <domains>', 'Comma-separated domains')
384
+ .option('--keywords <keywords>', 'Comma-separated keywords')
385
+ .option('--spawn-prompt <prompt>', 'Teammate spawn prompt')
386
+ .option('--role <role>', 'Teammate role')
387
+ .option('--icon <emoji>', 'Teammate icon')
388
+ .action((id, options) => agentAddCommand(id, options));
389
+
390
+ agentCommand
391
+ .command('edit <id>')
392
+ .description('Edit agent fields via flags')
393
+ .option('--tier <n>', 'New tier')
394
+ .option('--title <title>', 'New title')
395
+ .option('--group <group>', 'New group')
396
+ .option('--active-phases <phases>', 'New phases (comma-sep)')
397
+ .option('--reports-to <id>', 'New reports_to')
398
+ .option('--keywords <keywords>', 'New keywords (comma-sep)')
399
+ .option('--domains <domains>', 'New domains (comma-sep)')
400
+ .option('--spawn-prompt <prompt>', 'New spawn prompt')
401
+ .action((id, options) => agentEditCommand(id, options));
402
+
403
+ agentCommand
404
+ .command('remove <id>')
405
+ .description('Remove an agent from agents.json')
406
+ .option('--force', 'Skip confirmation prompt')
407
+ .action((id, options) => agentRemoveCommand(id, options));
408
+
320
409
  program.parse();
@@ -13,7 +13,7 @@ import { access } from 'fs/promises';
13
13
  import { execSync } from 'child_process';
14
14
  import chalk from 'chalk';
15
15
 
16
- import { loadState, saveState } from '../src/core/state/state-manager.js';
16
+ import { loadState, saveState, resolveAgentForTask, updateDispatchAgentTiming } from '../src/core/state/state-manager.js';
17
17
  import { parseTasksMd, ensureTaskList, syncCounters } from '../src/lib/tasks/task-parser.js';
18
18
  import { isTestTask } from '../src/lib/tasks/task-classifier.js';
19
19
  import { runTestSuite } from '../src/lib/tasks/test-runner.js';
@@ -124,6 +124,7 @@ class TaskManager {
124
124
  // Persist initial taskList so counters are visible in `state get` even if
125
125
  // validation fails later and the completion save never runs.
126
126
  syncCounters(feature);
127
+ feature.updatedAt = new Date().toISOString();
127
128
  saveState(state);
128
129
 
129
130
  const results = [];
@@ -160,7 +161,7 @@ class TaskManager {
160
161
 
161
162
  // Run validation BEFORE marking tasks as complete
162
163
  if (tasksToComplete.length > 0 && !options.skipValidation) {
163
- const validationResult = await this.runValidation(featureName);
164
+ const validationResult = await this.runValidation(featureName, { mode: 'light' });
164
165
  if (options.dryRun) {
165
166
  console.log(chalk.cyan('\n ℹ️ Dry-run — tasks NOT marked as complete'));
166
167
  return [];
@@ -240,10 +241,14 @@ class TaskManager {
240
241
  }
241
242
 
242
243
  // Mark tasks as completed (only after validation passes)
244
+ // Mark tasks as completed with agent correlation
243
245
  for (const task of tasksToComplete) {
244
246
  task.status = 'completed';
245
247
  task.completedAt = new Date().toISOString();
246
- task.completedBy = 'claude';
248
+
249
+ // Resolve agent from dispatch (falls back to 'claude')
250
+ const agentId = resolveAgentForTask(featureName, task.id);
251
+ task.completedBy = agentId || 'claude';
247
252
 
248
253
  results.push(task);
249
254
 
@@ -255,6 +260,25 @@ class TaskManager {
255
260
  }
256
261
  }
257
262
 
263
+ // Update dispatch agent timing for completed tasks
264
+ const agentCompletions = new Map();
265
+ for (const task of results) {
266
+ const aid = task.completedBy;
267
+ if (aid && aid !== 'claude') {
268
+ if (!agentCompletions.has(aid)) agentCompletions.set(aid, []);
269
+ agentCompletions.get(aid).push(task.id);
270
+ }
271
+ }
272
+ for (const [aid, taskIds] of agentCompletions) {
273
+ const lastDispatch = feature.dispatchLog?.[feature.dispatchLog.length - 1];
274
+ const dispatchAgent = lastDispatch?.agents?.find(a => a.id === aid);
275
+ const existing = dispatchAgent?.tasksCompleted || [];
276
+ updateDispatchAgentTiming(featureName, aid, {
277
+ completedAt: new Date().toISOString(),
278
+ tasksCompleted: [...new Set([...existing, ...taskIds])]
279
+ });
280
+ }
281
+
258
282
  // Sync v3 counters then compute progress
259
283
  syncCounters(feature);
260
284
  feature.progress = this.calculateProgress(taskList);
@@ -273,6 +297,7 @@ class TaskManager {
273
297
  }
274
298
 
275
299
  // Persist state (atomic write via state-manager)
300
+ feature.updatedAt = new Date().toISOString();
276
301
  saveState(state);
277
302
 
278
303
  // Run TaskCompleted agent-teams hook for each completed task (non-blocking)
@@ -335,12 +360,12 @@ class TaskManager {
335
360
  * @param {string} featureName - Feature name
336
361
  * @returns {{ passed: boolean, validators: Object, passRate: number }}
337
362
  */
338
- async runValidation(featureName) {
363
+ async runValidation(featureName, opts = {}) {
339
364
  try {
340
365
  console.log(chalk.cyan('\n🔍 Running code validation...'));
341
366
 
342
367
  const { runValidation, formatValidationResults } = await import('../src/lib/validators/validation-runner.js');
343
- const result = await runValidation('.', featureName, { verbose: true });
368
+ const result = await runValidation('.', featureName, { verbose: true, ...opts });
344
369
 
345
370
  formatValidationResults(result);
346
371
 
@@ -498,10 +523,24 @@ class TaskManager {
498
523
  console.log(chalk.yellow('⚠️ Checkpoint hooks not available, using legacy validation'));
499
524
  try {
500
525
  const { runValidation } = await import('../src/lib/validators/validation-runner.js');
501
- const result = await runValidation('.', featureName, { verbose: false });
526
+ const result = await runValidation('.', featureName, { verbose: false, mode: 'full' });
502
527
  validationNote = result.passed
503
528
  ? ' | Validation: PASSED'
504
529
  : ` | Validation: ${result.errors} errors, ${result.warnings} warnings`;
530
+
531
+ // Persist checkpoint validation results to disk
532
+ try {
533
+ const { persistCheckpointValidation } = await import('../src/lib/validators/checkpoint-persistence.js');
534
+ persistCheckpointValidation(process.cwd(), featureName, {
535
+ checkpointNum,
536
+ timestamp: new Date().toISOString(),
537
+ passed: result.passed,
538
+ validators: result.results || {},
539
+ summary: result.summary || '',
540
+ });
541
+ } catch {
542
+ // Non-blocking
543
+ }
505
544
  } catch {
506
545
  // No validation available
507
546
  }
@@ -683,7 +722,21 @@ class TaskManager {
683
722
 
684
723
  task.status = 'in_progress';
685
724
  task.startedAt = new Date().toISOString();
725
+
726
+ // Agent correlation: resolve which agent is assigned this task from the latest dispatch
727
+ const agentId = resolveAgentForTask(featureName, taskId);
728
+ if (agentId) {
729
+ task.assignedAgent = agentId;
730
+ // Record agent startedAt only for the first task (don't overwrite)
731
+ const lastDispatch = feature.dispatchLog?.[feature.dispatchLog.length - 1];
732
+ const dispatchAgent = lastDispatch?.agents?.find(a => a.id === agentId);
733
+ if (dispatchAgent && !dispatchAgent.startedAt) {
734
+ updateDispatchAgentTiming(featureName, agentId, { startedAt: task.startedAt });
735
+ }
736
+ }
737
+
686
738
  syncCounters(feature);
739
+ feature.updatedAt = new Date().toISOString();
687
740
 
688
741
  saveState(state);
689
742
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "morph-spec",
3
- "version": "4.10.6",
3
+ "version": "4.10.7",
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",
@@ -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.6 by Polymorphism Tech*
201
+ *morph-spec v4.10.7 by Polymorphism Tech*
@@ -203,4 +203,4 @@ morph-spec doctor
203
203
 
204
204
  ---
205
205
 
206
- *morph-spec v4.10.6 by Polymorphism Tech*
206
+ *morph-spec v4.10.7 by Polymorphism Tech*