@polymorphism-tech/morph-spec 4.2.0 → 4.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/CLAUDE.md +108 -946
  2. package/bin/morph-spec.js +284 -9
  3. package/bin/task-manager.cjs +102 -14
  4. package/bin/validate.js +4 -4
  5. package/docs/{v3.0 → next-generation}/AGENTS.md +1 -1
  6. package/docs/next-generation/CONTEXT-OPTIMIZATION.md +267 -0
  7. package/docs/next-generation/EXECUTION-FLOW.md +274 -0
  8. package/docs/next-generation/META-PROMPTS.md +235 -0
  9. package/docs/next-generation/MIGRATION-GUIDE.md +253 -0
  10. package/docs/next-generation/THREAD-MANAGEMENT.md +240 -0
  11. package/package.json +5 -5
  12. package/src/commands/agents/agents-fuse.js +97 -0
  13. package/src/commands/agents/micro-agent.js +112 -0
  14. package/src/commands/agents/spawn-team.js +69 -4
  15. package/src/commands/agents/squad-template.js +146 -0
  16. package/src/commands/analytics/analytics.js +176 -0
  17. package/src/commands/context/context-prime.js +63 -0
  18. package/src/commands/context/core-four.js +54 -0
  19. package/src/commands/mcp/mcp.js +102 -0
  20. package/src/commands/project/detect-agents.js +32 -2
  21. package/src/commands/project/detect.js +11 -1
  22. package/src/commands/project/doctor.js +573 -356
  23. package/src/commands/project/init.js +9 -2
  24. package/src/commands/project/update.js +13 -3
  25. package/src/commands/state/advance-phase.js +448 -416
  26. package/src/commands/state/state.js +14 -12
  27. package/src/commands/tasks/task.js +1 -1
  28. package/src/commands/templates/template-render.js +80 -1
  29. package/src/commands/threads/thread-template.js +103 -0
  30. package/src/commands/threads/threads.js +261 -0
  31. package/src/commands/trust/trust.js +205 -0
  32. package/src/{orchestrator.js → core/orchestrator.js} +8 -8
  33. package/src/core/state/state-manager.js +37 -17
  34. package/src/core/workflows/workflow-detector.js +114 -3
  35. package/src/lib/agents/micro-agent-factory.js +161 -0
  36. package/src/lib/analytics/analytics-engine.js +345 -0
  37. package/src/lib/checkpoints/checkpoint-hooks.js +298 -258
  38. package/src/lib/context/context-bundler.js +240 -0
  39. package/src/lib/context/context-optimizer.js +212 -0
  40. package/src/lib/context/context-tracker.js +273 -0
  41. package/src/lib/context/core-four-tracker.js +201 -0
  42. package/src/lib/context/mcp-optimizer.js +200 -0
  43. package/src/lib/detectors/index.js +1 -1
  44. package/src/lib/detectors/standards-generator.js +77 -17
  45. package/src/lib/detectors/structure-detector.js +67 -39
  46. package/src/lib/execution/fusion-executor.js +304 -0
  47. package/src/lib/execution/parallel-executor.js +270 -0
  48. package/src/lib/generators/context-generator.js +3 -3
  49. package/src/lib/generators/recap-generator.js +32 -12
  50. package/src/lib/hooks/hook-executor.js +169 -0
  51. package/src/lib/hooks/stop-hook-executor.js +286 -0
  52. package/src/lib/hops/hop-composer.js +221 -0
  53. package/src/lib/threads/thread-coordinator.js +238 -0
  54. package/src/lib/threads/thread-manager.js +317 -0
  55. package/src/lib/tracking/artifact-trail.js +202 -0
  56. package/src/lib/trust/trust-manager.js +269 -0
  57. package/src/lib/validators/design-system/design-system-validator.js +2 -2
  58. package/src/lib/validators/validation-runner.js +14 -30
  59. package/src/utils/hooks-installer.js +69 -0
  60. package/stacks/blazor-azure/.morph/config/agents.json +72 -3
  61. package/stacks/nextjs-supabase/.morph/config/agents.json +3 -3
  62. package/docs/llm-interaction-config.md +0 -735
  63. package/docs/v3.0/EXECUTION-FLOW.md +0 -1304
  64. package/src/commands/utils/migrate-state.js +0 -158
  65. package/src/commands/utils/upgrade.js +0 -346
  66. package/src/lib/validators/architecture-validator.js +0 -60
  67. package/src/lib/validators/content-validator.js +0 -164
  68. package/src/lib/validators/package-validator.js +0 -61
  69. package/src/lib/validators/ui-contrast-validator.js +0 -44
  70. package/stacks/blazor-azure/.claude/commands/morph-apply.md +0 -221
  71. package/stacks/blazor-azure/.claude/commands/morph-archive.md +0 -79
  72. package/stacks/blazor-azure/.claude/commands/morph-deploy.md +0 -529
  73. package/stacks/blazor-azure/.claude/commands/morph-infra.md +0 -209
  74. package/stacks/blazor-azure/.claude/commands/morph-preflight.md +0 -227
  75. package/stacks/blazor-azure/.claude/commands/morph-proposal.md +0 -122
  76. package/stacks/blazor-azure/.claude/commands/morph-status.md +0 -86
  77. package/stacks/blazor-azure/.claude/commands/morph-troubleshoot.md +0 -122
  78. package/stacks/blazor-azure/.claude/skills/level-0-meta/README.md +0 -7
  79. package/stacks/blazor-azure/.claude/skills/level-0-meta/code-review.md +0 -226
  80. package/stacks/blazor-azure/.claude/skills/level-0-meta/morph-checklist.md +0 -117
  81. package/stacks/blazor-azure/.claude/skills/level-0-meta/simulation-checklist.md +0 -77
  82. package/stacks/blazor-azure/.claude/skills/level-1-workflows/README.md +0 -7
  83. package/stacks/blazor-azure/.claude/skills/level-1-workflows/morph-replicate.md +0 -213
  84. package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-clarify.md +0 -131
  85. package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-design.md +0 -213
  86. package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-setup.md +0 -106
  87. package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-tasks.md +0 -164
  88. package/stacks/blazor-azure/.claude/skills/level-1-workflows/phase-uiux.md +0 -169
  89. package/stacks/blazor-azure/.claude/skills/level-2-domains/README.md +0 -14
  90. package/stacks/blazor-azure/.claude/skills/level-2-domains/ai-agents/ai-system-architect.md +0 -192
  91. package/stacks/blazor-azure/.claude/skills/level-2-domains/architecture/po-pm-advisor.md +0 -197
  92. package/stacks/blazor-azure/.claude/skills/level-2-domains/architecture/prompt-engineer.md +0 -189
  93. package/stacks/blazor-azure/.claude/skills/level-2-domains/architecture/seo-growth-hacker.md +0 -320
  94. package/stacks/blazor-azure/.claude/skills/level-2-domains/architecture/standards-architect.md +0 -156
  95. package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/api-designer.md +0 -59
  96. package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/dotnet-senior.md +0 -77
  97. package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/ef-modeler.md +0 -58
  98. package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/hangfire-orchestrator.md +0 -126
  99. package/stacks/blazor-azure/.claude/skills/level-2-domains/backend/ms-agent-expert.md +0 -45
  100. package/stacks/blazor-azure/.claude/skills/level-2-domains/frontend/blazor-builder.md +0 -210
  101. package/stacks/blazor-azure/.claude/skills/level-2-domains/frontend/nextjs-expert.md +0 -154
  102. package/stacks/blazor-azure/.claude/skills/level-2-domains/frontend/ui-ux-designer.md +0 -191
  103. package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/azure-architect.md +0 -142
  104. package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/azure-deploy-specialist.md +0 -699
  105. package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/bicep-architect.md +0 -126
  106. package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/container-specialist.md +0 -131
  107. package/stacks/blazor-azure/.claude/skills/level-2-domains/infrastructure/devops-engineer.md +0 -119
  108. package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/asaas-financial.md +0 -130
  109. package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/azure-identity.md +0 -142
  110. package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/clerk-auth.md +0 -108
  111. package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/hangfire-orchestrator.md +0 -64
  112. package/stacks/blazor-azure/.claude/skills/level-2-domains/integrations/resend-email.md +0 -119
  113. package/stacks/blazor-azure/.claude/skills/level-2-domains/quality/code-analyzer.md +0 -235
  114. package/stacks/blazor-azure/.claude/skills/level-2-domains/quality/testing-specialist.md +0 -126
  115. package/stacks/blazor-azure/.claude/skills/level-3-technologies/README.md +0 -7
  116. package/stacks/blazor-azure/.claude/skills/level-4-patterns/README.md +0 -7
  117. package/stacks/blazor-azure/.morph/archive/.gitkeep +0 -25
  118. package/stacks/blazor-azure/.morph/features/.gitkeep +0 -25
  119. package/stacks/blazor-azure/.morph/schemas/agent.schema.json +0 -296
  120. package/stacks/blazor-azure/.morph/schemas/tasks.schema.json +0 -220
  121. package/stacks/blazor-azure/.morph/specs/.gitkeep +0 -20
  122. package/stacks/blazor-azure/.morph/test-infra/example.bicep +0 -59
  123. package/stacks/nextjs-supabase/.claude/commands/morph-apply.md +0 -221
  124. package/stacks/nextjs-supabase/.claude/commands/morph-archive.md +0 -79
  125. package/stacks/nextjs-supabase/.claude/commands/morph-deploy.md +0 -529
  126. package/stacks/nextjs-supabase/.claude/commands/morph-infra.md +0 -209
  127. package/stacks/nextjs-supabase/.claude/commands/morph-preflight.md +0 -227
  128. package/stacks/nextjs-supabase/.claude/commands/morph-proposal.md +0 -122
  129. package/stacks/nextjs-supabase/.claude/commands/morph-status.md +0 -86
  130. package/stacks/nextjs-supabase/.claude/commands/morph-troubleshoot.md +0 -122
  131. package/stacks/nextjs-supabase/.claude/settings.local.json +0 -6
  132. package/stacks/nextjs-supabase/.claude/skills/level-2-domains/backend/dotnet-supabase.md +0 -244
  133. package/stacks/nextjs-supabase/.claude/skills/level-2-domains/frontend/nextjs-supabase.md +0 -335
  134. package/stacks/nextjs-supabase/.claude/skills/level-2-domains/infrastructure/easypanel-deployer.md +0 -189
  135. package/stacks/nextjs-supabase/.claude/skills/level-2-domains/integrations/supabase-expert.md +0 -50
  136. /package/docs/{v3.0 → next-generation}/ANALYSIS.md +0 -0
  137. /package/docs/{v3.0 → next-generation}/ARCHITECTURE.md +0 -0
  138. /package/docs/{v3.0 → next-generation}/FEATURES.md +0 -0
  139. /package/docs/{v3.0 → next-generation}/README.md +0 -0
  140. /package/docs/{v3.0 → next-generation}/ROADMAP.md +0 -0
@@ -181,13 +181,13 @@ async function checkpointCommand(featureName, note, options) {
181
181
  try {
182
182
  const spinner = ora('Creating checkpoint...').start();
183
183
 
184
- const checkpoint = StateManager.addCheckpoint(featureName, note);
184
+ const checkpoint = await StateManager.addCheckpoint(featureName, note);
185
185
 
186
186
  spinner.succeed(`Checkpoint registered for ${chalk.cyan(featureName)}`);
187
187
  logger.blank();
188
- logger.dim(` ${checkpoint.note}`);
188
+ logger.dim(` ${checkpoint.summary?.note || note}`);
189
189
  logger.dim(` Phase: ${checkpoint.phase}`);
190
- logger.dim(` Tasks completed: ${checkpoint.completedTasks}`);
190
+ logger.dim(` Tasks completed: ${checkpoint.summary?.completedTasks ?? 0}`);
191
191
  logger.blank();
192
192
 
193
193
  } catch (error) {
@@ -208,8 +208,10 @@ async function listCommand(options) {
208
208
  const features = StateManager.listFeatures();
209
209
 
210
210
  // Project info
211
- logger.info(`Project: ${chalk.cyan(summary.project.name)}`);
212
- logger.info(`Type: ${chalk.cyan(summary.project.type)}`);
211
+ const projectName = summary.project?.name || '(unnamed)';
212
+ const projectType = summary.project?.type || '(unknown)';
213
+ logger.info(`Project: ${chalk.cyan(projectName)}`);
214
+ logger.info(`Type: ${chalk.cyan(projectType)}`);
213
215
  logger.blank();
214
216
 
215
217
  if (features.length === 0) {
@@ -232,21 +234,21 @@ async function listCommand(options) {
232
234
  archived: '📦'
233
235
  }[feature.status] || '❓';
234
236
 
235
- const progress = feature.tasks.total > 0
236
- ? `${feature.tasks.completed}/${feature.tasks.total}`
237
+ const tasks = feature.tasks || {};
238
+ const progress = (tasks.total || 0) > 0
239
+ ? `${tasks.completed || 0}/${tasks.total}`
237
240
  : '0/0';
238
241
 
239
242
  logger.info(`${statusEmoji} ${chalk.bold(name)}`);
240
- logger.dim(` Phase: ${feature.phase.padEnd(16)} │ Tasks: ${progress}`);
241
- logger.dim(` Agents: ${feature.activeAgents.slice(0, 5).join(', ') || 'None'}`);
243
+ logger.dim(` Phase: ${(feature.phase || 'unknown').padEnd(16)} │ Tasks: ${progress}`);
244
+ logger.dim(` Agents: ${(feature.activeAgents || []).slice(0, 5).join(', ') || 'None'}`);
242
245
  logger.blank();
243
246
  });
244
247
 
245
248
  // Summary
246
249
  logger.header('Summary:');
247
- logger.info(`Total Features: ${chalk.cyan(summary.metadata.totalFeatures)}`);
248
- logger.info(`Completed: ${chalk.cyan(summary.metadata.completedFeatures)}`);
249
- logger.info(`Estimated Cost: ${chalk.cyan(`$${summary.metadata.totalCostEstimated.toFixed(2)}/month`)}`);
250
+ logger.info(`Total Features: ${chalk.cyan(summary.metadata?.totalFeatures ?? features.length)}`);
251
+ logger.info(`Completed: ${chalk.cyan(summary.metadata?.completedFeatures ?? 0)}`);
250
252
  logger.blank();
251
253
 
252
254
  } catch (error) {
@@ -9,7 +9,7 @@ import { dirname, join } from 'path';
9
9
  import chalk from 'chalk';
10
10
 
11
11
  const __dirname = dirname(fileURLToPath(import.meta.url));
12
- const taskManagerPath = join(__dirname, '..', '..', 'bin', 'task-manager.cjs');
12
+ const taskManagerPath = join(__dirname, '..', '..', '..', 'bin', 'task-manager.cjs');
13
13
 
14
14
  /**
15
15
  * Execute task-manager.js with given arguments
@@ -3,11 +3,90 @@
3
3
  */
4
4
 
5
5
  import { writeFileSync, mkdirSync } from 'fs';
6
- import { dirname } from 'path';
6
+ import { dirname, join } from 'path';
7
7
  import chalk from 'chalk';
8
8
  import { logger } from '../../utils/logger.js';
9
9
  import { getTemplateById, resolveTemplatePathById } from '../../core/templates/template-registry.js';
10
10
  import { renderTemplate } from '../../core/templates/template-renderer.js';
11
+ import { renderHOP, listAvailableHOPs } from '../../lib/hops/hop-composer.js';
12
+
13
+ /**
14
+ * Render HOP (Higher-Order Prompt) template command
15
+ */
16
+ export async function hopRenderCommand(hopId, outputPath, variablesJson, options) {
17
+ logger.header(`Rendering HOP: ${hopId}`);
18
+ logger.blank();
19
+
20
+ try {
21
+ // Parse variables
22
+ let variables = {};
23
+ try {
24
+ variables = JSON.parse(variablesJson || '{}');
25
+ } catch (error) {
26
+ logger.error('Invalid JSON in variables argument');
27
+ logger.dim(` ${error.message}`);
28
+ process.exit(1);
29
+ }
30
+
31
+ // Add defaults
32
+ variables.DATE = variables.DATE || new Date().toISOString().split('T')[0];
33
+ variables.YEAR = variables.YEAR || new Date().getFullYear().toString();
34
+ variables.TIMESTAMP = variables.TIMESTAMP || new Date().toISOString();
35
+
36
+ // Render via hop-composer
37
+ const frameworkRoot = join(process.cwd(), 'framework');
38
+ const rendered = renderHOP(hopId, variables, { frameworkRoot });
39
+
40
+ if (options?.dryRun || !outputPath) {
41
+ logger.info(chalk.cyan('📋 HOP Preview:'));
42
+ logger.blank();
43
+ console.log(rendered);
44
+ logger.blank();
45
+ if (outputPath) logger.dim(`Would be written to: ${outputPath}`);
46
+ } else {
47
+ mkdirSync(dirname(outputPath), { recursive: true });
48
+ writeFileSync(outputPath, rendered, 'utf-8');
49
+ logger.success(`✅ HOP rendered: ${chalk.cyan(outputPath)}`);
50
+ }
51
+ } catch (error) {
52
+ logger.error(`Failed to render HOP: ${error.message}`);
53
+ process.exit(1);
54
+ }
55
+ }
56
+
57
+ /**
58
+ * List available HOPs
59
+ */
60
+ export async function hopListCommand(options) {
61
+ logger.header('Available HOPs (Higher-Order Prompts)');
62
+ logger.blank();
63
+
64
+ const frameworkRoot = join(process.cwd(), 'framework');
65
+ const hops = listAvailableHOPs(frameworkRoot);
66
+
67
+ if (hops.length === 0) {
68
+ logger.warn('No HOPs found in framework/templates/meta-prompts/');
69
+ return;
70
+ }
71
+
72
+ const byType = {};
73
+ for (const hop of hops) {
74
+ const type = hop.type || 'other';
75
+ if (!byType[type]) byType[type] = [];
76
+ byType[type].push(hop);
77
+ }
78
+
79
+ for (const [type, list] of Object.entries(byType)) {
80
+ console.log(chalk.cyan(`\n ${type.toUpperCase()}`));
81
+ for (const hop of list) {
82
+ console.log(` ${chalk.white(hop.id.padEnd(30))} ${chalk.gray(hop.description || hop.path)}`);
83
+ }
84
+ }
85
+
86
+ logger.blank();
87
+ logger.dim(`Total: ${hops.length} HOPs`);
88
+ logger.dim(`Render: morph-spec hop render <hop-id> <output-file> '<variables-json>'`);
89
+ }
11
90
 
12
91
  /**
13
92
  * Render template command
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Thread Template CLI command — Thread type management
3
+ *
4
+ * Usage:
5
+ * morph-spec thread-template list
6
+ * morph-spec thread-template use <type> --feature=<feature>
7
+ */
8
+
9
+ import chalk from 'chalk';
10
+ import { THREAD_TYPES } from '../../lib/threads/thread-manager.js';
11
+
12
+ const THREAD_TYPE_INFO = {
13
+ [THREAD_TYPES.BASE]: {
14
+ name: 'B-Thread (Base)',
15
+ description: 'Standard single-agent thread. Sequential execution, all approval gates required.',
16
+ useCase: 'Default for simple and medium features',
17
+ phases: 'All standard phases',
18
+ approvalGates: 'Manual (all)',
19
+ maxConcurrent: 1
20
+ },
21
+ [THREAD_TYPES.PARALLEL]: {
22
+ name: 'P-Thread (Parallel)',
23
+ description: 'Multiple agents run simultaneously on different feature domains.',
24
+ useCase: 'Full-stack features with independent backend/frontend/infra work',
25
+ phases: 'All phases (parallel IMPLEMENT)',
26
+ approvalGates: 'Manual (all)',
27
+ maxConcurrent: 5
28
+ },
29
+ [THREAD_TYPES.FUSION]: {
30
+ name: 'F-Thread (Fusion)',
31
+ description: 'N agents independently generate solutions; best-of-N result is selected.',
32
+ useCase: 'High-uncertainty decisions, critical architecture choices, prototype evaluation',
33
+ phases: 'DESIGN → parallel IMPLEMENT → AGGREGATE → SYNC',
34
+ approvalGates: 'Manual select (or auto best-of-n)',
35
+ maxConcurrent: 3
36
+ },
37
+ [THREAD_TYPES.LONG_RUNNING]: {
38
+ name: 'L-Thread (Long-Running)',
39
+ description: 'Autonomous agent with stop hooks every 30 minutes for validation.',
40
+ useCase: 'Large scope features (>10 files), autonomous implementation without interruption',
41
+ phases: 'IMPLEMENT (continuous) with periodic validation',
42
+ approvalGates: 'Auto (stop hooks validate)',
43
+ maxConcurrent: 1
44
+ },
45
+ [THREAD_TYPES.ZERO_TOUCH]: {
46
+ name: 'Z-Thread (Zero-Touch)',
47
+ description: 'Fully autonomous execution. Auto-approves all gates, auto-commits on success.',
48
+ useCase: 'Maximum-trust features with >95% checkpoint pass rate',
49
+ phases: 'All phases (auto-approved)',
50
+ approvalGates: 'Auto (trust-based)',
51
+ maxConcurrent: 1
52
+ }
53
+ };
54
+
55
+ export async function threadTemplateListCommand(options) {
56
+ console.log(chalk.cyan('\n Thread Types\n'));
57
+ console.log(' ' + '─'.repeat(70));
58
+
59
+ for (const [type, info] of Object.entries(THREAD_TYPE_INFO)) {
60
+ console.log(`\n ${chalk.bold(info.name)}`);
61
+ console.log(` ${info.description}`);
62
+ console.log(` ${chalk.gray('Use case:')} ${info.useCase}`);
63
+ console.log(` ${chalk.gray('Phases:')} ${info.phases}`);
64
+ console.log(` ${chalk.gray('Gates:')} ${info.approvalGates}`);
65
+ console.log(` ${chalk.gray('Max concurrent:')} ${info.maxConcurrent}`);
66
+ }
67
+
68
+ console.log('\n ' + '─'.repeat(70));
69
+ console.log(chalk.gray('\n Set thread type: morph-spec thread-template use <type> --feature=<feature>\n'));
70
+ }
71
+
72
+ export async function threadTemplateUseCommand(type, options) {
73
+ if (!options.feature) {
74
+ console.error(chalk.red('--feature is required'));
75
+ process.exit(1);
76
+ }
77
+
78
+ if (!THREAD_TYPE_INFO[type]) {
79
+ console.error(chalk.red(`Unknown thread type: ${type}`));
80
+ console.error(chalk.gray(`Valid types: ${Object.keys(THREAD_TYPE_INFO).join(', ')}`));
81
+ process.exit(1);
82
+ }
83
+
84
+ const info = THREAD_TYPE_INFO[type];
85
+
86
+ // Special checks
87
+ if (type === THREAD_TYPES.ZERO_TOUCH) {
88
+ console.log(chalk.yellow('\n ⚠ Z-Thread requires maximum trust level (>95% checkpoint pass rate)'));
89
+ console.log(chalk.gray(' Check trust: morph-spec trust status\n'));
90
+ }
91
+
92
+ // Update feature state with thread type
93
+ try {
94
+ const { updateFeature } = await import('../../core/state/state-manager.js');
95
+ await updateFeature(options.feature, 'threadType', type);
96
+ console.log(chalk.green(`\n ✓ Thread type set: ${type} for feature: ${options.feature}`));
97
+ console.log(` ${info.name}: ${info.description}`);
98
+ console.log('');
99
+ } catch (err) {
100
+ console.error(chalk.red(` Error updating state: ${err.message}`));
101
+ process.exit(1);
102
+ }
103
+ }
@@ -0,0 +1,261 @@
1
+ /**
2
+ * Threads CLI command — Thread lifecycle management
3
+ *
4
+ * Usage:
5
+ * morph-spec threads list [--feature=<f>] [--status=<s>]
6
+ * morph-spec threads status <id>
7
+ * morph-spec threads kill <id>
8
+ * morph-spec threads analytics [--feature=<f>]
9
+ * morph-spec threads wait-all <feature>
10
+ * morph-spec threads wait-any <feature>
11
+ */
12
+
13
+ import chalk from 'chalk';
14
+ import {
15
+ listThreads, getThread, killThread, getThreadAnalytics,
16
+ THREAD_STATUS, THREAD_TYPES
17
+ } from '../../lib/threads/thread-manager.js';
18
+ import { generateAsciiChart, generateTimelineChart } from '../../lib/analytics/analytics-engine.js';
19
+
20
+ const STATUS_COLORS = {
21
+ [THREAD_STATUS.PENDING]: chalk.yellow,
22
+ [THREAD_STATUS.RUNNING]: chalk.cyan,
23
+ [THREAD_STATUS.COMPLETED]: chalk.green,
24
+ [THREAD_STATUS.FAILED]: chalk.red,
25
+ [THREAD_STATUS.KILLED]: chalk.gray
26
+ };
27
+
28
+ const TYPE_ICONS = {
29
+ [THREAD_TYPES.BASE]: '◆',
30
+ [THREAD_TYPES.PARALLEL]: '⫸',
31
+ [THREAD_TYPES.FUSION]: '⊕',
32
+ [THREAD_TYPES.LONG_RUNNING]: '∞',
33
+ [THREAD_TYPES.ZERO_TOUCH]: '⚡'
34
+ };
35
+
36
+ function formatDuration(seconds) {
37
+ if (!seconds) return '—';
38
+ if (seconds < 60) return `${seconds}s`;
39
+ if (seconds < 3600) return `${Math.floor(seconds / 60)}m ${seconds % 60}s`;
40
+ return `${Math.floor(seconds / 3600)}h ${Math.floor((seconds % 3600) / 60)}m`;
41
+ }
42
+
43
+ /**
44
+ * List threads
45
+ */
46
+ export async function threadsListCommand(options) {
47
+ try {
48
+ const threads = listThreads({
49
+ feature: options.feature,
50
+ status: options.status,
51
+ type: options.type
52
+ });
53
+
54
+ if (threads.length === 0) {
55
+ console.log(chalk.gray('No threads found.'));
56
+ return;
57
+ }
58
+
59
+ console.log(chalk.cyan(`\n Threads (${threads.length})\n`));
60
+ console.log(
61
+ ' ' + 'ID'.padEnd(10) + ' ' +
62
+ 'Type'.padEnd(14) + ' ' +
63
+ 'Status'.padEnd(12) + ' ' +
64
+ 'Feature'.padEnd(20) + ' ' +
65
+ 'Agent'.padEnd(20) + ' ' +
66
+ 'Duration'
67
+ );
68
+ console.log(' ' + '─'.repeat(90));
69
+
70
+ for (const t of threads) {
71
+ const icon = TYPE_ICONS[t.type] || '◆';
72
+ const colorFn = STATUS_COLORS[t.status] || chalk.white;
73
+ const shortId = t.id.substring(0, 8);
74
+
75
+ console.log(
76
+ ' ' +
77
+ chalk.gray(shortId) + ' ' +
78
+ `${icon} ${t.type}`.padEnd(14) + ' ' +
79
+ colorFn(t.status.padEnd(12)) + ' ' +
80
+ (t.feature || '—').padEnd(20) + ' ' +
81
+ (t.agent || '—').padEnd(20) + ' ' +
82
+ formatDuration(t.duration)
83
+ );
84
+ }
85
+ console.log('');
86
+ } catch (err) {
87
+ console.error(chalk.red(`Error: ${err.message}`));
88
+ process.exit(1);
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Show thread status detail
94
+ */
95
+ export async function threadsStatusCommand(id) {
96
+ try {
97
+ const thread = getThread(id);
98
+ if (!thread) {
99
+ console.error(chalk.red(`Thread not found: ${id}`));
100
+ process.exit(1);
101
+ }
102
+
103
+ const colorFn = STATUS_COLORS[thread.status] || chalk.white;
104
+ const icon = TYPE_ICONS[thread.type] || '◆';
105
+
106
+ console.log(chalk.cyan('\n Thread Detail\n'));
107
+ console.log(` ID: ${chalk.gray(thread.id)}`);
108
+ console.log(` Type: ${icon} ${thread.type}`);
109
+ console.log(` Status: ${colorFn(thread.status)}`);
110
+ console.log(` Feature: ${thread.feature || '—'}`);
111
+ console.log(` Agent: ${thread.agent || '—'}`);
112
+ console.log(` Mission: ${thread.mission || '—'}`);
113
+ console.log(` Created: ${thread.createdAt}`);
114
+ console.log(` Started: ${thread.startedAt || '—'}`);
115
+ console.log(` Ended: ${thread.completedAt || '—'}`);
116
+ console.log(` Duration: ${formatDuration(thread.duration)}`);
117
+
118
+ if (thread.metrics) {
119
+ console.log('\n Metrics:');
120
+ console.log(` Tool calls: ${thread.metrics.toolCalls || 0}`);
121
+ console.log(` Tokens used: ${(thread.metrics.tokensUsed || 0).toLocaleString()}`);
122
+ console.log(` Checkpoints OK: ${thread.metrics.checkpointsPassed || 0}`);
123
+ console.log(` Checkpoints KO: ${thread.metrics.checkpointsFailed || 0}`);
124
+ }
125
+
126
+ if (thread.events?.length > 0) {
127
+ console.log(`\n Recent events (${thread.events.length}):`);
128
+ thread.events.slice(-5).forEach(e => {
129
+ console.log(` ${chalk.gray(e.timestamp)} ${e.type}`);
130
+ });
131
+ }
132
+
133
+ if (thread.failureReason) {
134
+ console.log(`\n ${chalk.red('Failure:')} ${thread.failureReason}`);
135
+ }
136
+ console.log('');
137
+ } catch (err) {
138
+ console.error(chalk.red(`Error: ${err.message}`));
139
+ process.exit(1);
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Kill a thread
145
+ */
146
+ export async function threadsKillCommand(id) {
147
+ try {
148
+ const thread = killThread(id);
149
+ console.log(chalk.yellow(`✓ Thread ${id.substring(0, 8)} killed.`));
150
+ console.log(` Status: ${thread.status} | Duration: ${formatDuration(thread.duration)}`);
151
+ } catch (err) {
152
+ console.error(chalk.red(`Error: ${err.message}`));
153
+ process.exit(1);
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Show threads analytics dashboard
159
+ */
160
+ export async function threadsAnalyticsCommand(options) {
161
+ try {
162
+ const feature = options.feature;
163
+ if (!feature) {
164
+ console.error(chalk.red('--feature is required for analytics'));
165
+ process.exit(1);
166
+ }
167
+
168
+ const analytics = getThreadAnalytics(feature);
169
+
170
+ console.log(chalk.cyan(`\n Thread Analytics — ${feature}\n`));
171
+ console.log(` Total threads: ${analytics.total}`);
172
+ console.log(` By status:`);
173
+ for (const [status, count] of Object.entries(analytics.byStatus)) {
174
+ const colorFn = STATUS_COLORS[status] || chalk.white;
175
+ console.log(` ${colorFn(status.padEnd(12))} ${count}`);
176
+ }
177
+
178
+ console.log('\n By type:');
179
+ for (const [type, count] of Object.entries(analytics.byType)) {
180
+ const icon = TYPE_ICONS[type] || '◆';
181
+ console.log(` ${icon} ${type.padEnd(14)} ${count}`);
182
+ }
183
+
184
+ console.log(`\n Avg duration: ${formatDuration(analytics.avgDuration)}`);
185
+ console.log(` Total tool calls: ${analytics.totalToolCalls}`);
186
+ console.log(` Checkpoint pass rate: ${Math.round(analytics.checkpointPassRate * 100)}%`);
187
+
188
+ if (analytics.total > 0) {
189
+ const statusChart = generateAsciiChart(analytics.byStatus, { title: 'Threads by Status' });
190
+ console.log('\n' + statusChart);
191
+ }
192
+ console.log('');
193
+ } catch (err) {
194
+ console.error(chalk.red(`Error: ${err.message}`));
195
+ process.exit(1);
196
+ }
197
+ }
198
+
199
+ /**
200
+ * Wait for all threads in a feature to complete
201
+ */
202
+ export async function threadsWaitAllCommand(feature, options) {
203
+ const timeoutMs = (options.timeout || 60) * 60 * 1000;
204
+ const pollInterval = 5000;
205
+ const start = Date.now();
206
+
207
+ console.log(chalk.cyan(`\n Waiting for all threads in: ${feature}\n`));
208
+
209
+ while (Date.now() - start < timeoutMs) {
210
+ const threads = listThreads({ feature });
211
+ const active = threads.filter(t =>
212
+ t.status === THREAD_STATUS.PENDING || t.status === THREAD_STATUS.RUNNING
213
+ );
214
+
215
+ if (active.length === 0) {
216
+ const failed = threads.filter(t => t.status === THREAD_STATUS.FAILED);
217
+ if (failed.length > 0) {
218
+ console.log(chalk.yellow(` ⚠ ${failed.length} thread(s) failed`));
219
+ process.exit(1);
220
+ }
221
+ console.log(chalk.green(` ✓ All ${threads.length} threads completed`));
222
+ return;
223
+ }
224
+
225
+ process.stdout.write(`\r Still running: ${active.length} thread(s)...`);
226
+ await new Promise(r => setTimeout(r, pollInterval));
227
+ }
228
+
229
+ console.error(chalk.red('\n Timeout waiting for threads'));
230
+ process.exit(1);
231
+ }
232
+
233
+ /**
234
+ * Wait for any thread to complete
235
+ */
236
+ export async function threadsWaitAnyCommand(feature, options) {
237
+ const timeoutMs = (options.timeout || 60) * 60 * 1000;
238
+ const pollInterval = 3000;
239
+ const start = Date.now();
240
+
241
+ console.log(chalk.cyan(`\n Waiting for first thread to complete in: ${feature}\n`));
242
+
243
+ while (Date.now() - start < timeoutMs) {
244
+ const threads = listThreads({ feature });
245
+ const done = threads.find(t =>
246
+ t.status === THREAD_STATUS.COMPLETED || t.status === THREAD_STATUS.FAILED
247
+ );
248
+
249
+ if (done) {
250
+ const colorFn = STATUS_COLORS[done.status] || chalk.white;
251
+ console.log(` ${colorFn('✓')} Thread ${done.id.substring(0, 8)} ${done.status}`);
252
+ if (done.agent) console.log(` Agent: ${done.agent}`);
253
+ return;
254
+ }
255
+
256
+ await new Promise(r => setTimeout(r, pollInterval));
257
+ }
258
+
259
+ console.error(chalk.red('\n Timeout waiting for thread'));
260
+ process.exit(1);
261
+ }