@polymorphism-tech/morph-spec 4.8.19 → 4.10.0

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 (214) hide show
  1. package/CLAUDE.md +21 -0
  2. package/README.md +2 -2
  3. package/bin/morph-spec.js +44 -55
  4. package/bin/task-manager.js +133 -20
  5. package/bin/validate.js +67 -33
  6. package/claude-plugin.json +1 -1
  7. package/docs/CHEATSHEET.md +201 -203
  8. package/docs/QUICKSTART.md +2 -2
  9. package/framework/CLAUDE.md +99 -77
  10. package/framework/agents.json +734 -182
  11. package/framework/commands/commit.md +166 -0
  12. package/framework/commands/morph-apply.md +13 -2
  13. package/framework/commands/morph-archive.md +8 -2
  14. package/framework/commands/morph-infra.md +6 -0
  15. package/framework/commands/morph-preflight.md +6 -0
  16. package/framework/commands/morph-proposal.md +56 -7
  17. package/framework/commands/morph-status.md +6 -0
  18. package/framework/commands/morph-troubleshoot.md +6 -0
  19. package/framework/hooks/claude-code/notification/approval-reminder.js +3 -2
  20. package/framework/hooks/claude-code/post-tool-use/context-refresh.js +1 -1
  21. package/framework/hooks/claude-code/post-tool-use/dispatch.js +155 -32
  22. package/framework/hooks/claude-code/post-tool-use/skill-reminder.js +78 -0
  23. package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +8 -17
  24. package/framework/hooks/claude-code/pre-compact/save-morph-context.js +16 -3
  25. package/framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js +4 -3
  26. package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +4 -3
  27. package/framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js +60 -0
  28. package/framework/hooks/claude-code/session-start/inject-morph-context.js +124 -2
  29. package/framework/hooks/claude-code/session-start/post-compact-restore.js +41 -0
  30. package/framework/hooks/claude-code/statusline.py +76 -30
  31. package/framework/hooks/claude-code/stop/validate-completion.js +2 -15
  32. package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +23 -5
  33. package/framework/hooks/claude-code/user-prompt/set-terminal-title.js +14 -6
  34. package/framework/hooks/shared/activity-logger.js +0 -24
  35. package/framework/hooks/shared/compact-restore.js +100 -0
  36. package/framework/hooks/shared/dispatch-helpers.js +116 -0
  37. package/framework/hooks/shared/phase-utils.js +12 -5
  38. package/framework/hooks/shared/skill-reminder-helpers.js +79 -0
  39. package/framework/hooks/shared/stale-task-reset.js +57 -0
  40. package/framework/hooks/shared/state-reader.js +29 -5
  41. package/framework/hooks/shared/worktree-helpers.js +53 -0
  42. package/framework/phases.json +69 -14
  43. package/framework/rules/morph-workflow.md +88 -86
  44. package/framework/skills/level-0-meta/mcp-registry.json +86 -51
  45. package/framework/skills/level-0-meta/{brainstorming → morph-brainstorming}/SKILL.md +14 -17
  46. package/framework/skills/level-0-meta/morph-checklist/SKILL.md +2 -2
  47. package/framework/skills/level-0-meta/{code-review → morph-code-review}/SKILL.md +2 -2
  48. package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/SKILL.md +163 -163
  49. package/framework/skills/level-0-meta/{frontend-review → morph-frontend-review}/SKILL.md +9 -9
  50. package/framework/skills/level-0-meta/morph-init/SKILL.md +77 -12
  51. package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/SKILL.md +62 -15
  52. package/framework/skills/level-0-meta/morph-replicate/SKILL.md +5 -5
  53. package/framework/skills/level-0-meta/morph-replicate/references/blazor-html-mapping.md +1 -1
  54. package/framework/skills/level-0-meta/{simulation-checklist → morph-simulation-checklist}/SKILL.md +1 -1
  55. package/framework/skills/level-0-meta/{terminal-title → morph-terminal-title}/SKILL.md +2 -2
  56. package/framework/skills/level-0-meta/{tool-usage-guide → morph-tool-usage-guide}/SKILL.md +3 -4
  57. package/framework/skills/level-0-meta/{tool-usage-guide → morph-tool-usage-guide}/references/tools-per-phase.md +7 -7
  58. package/framework/skills/level-0-meta/{verification-before-completion → morph-verification-before-completion}/SKILL.md +2 -2
  59. package/framework/skills/level-0-meta/{verification-before-completion → morph-verification-before-completion}/scripts/check-phase-outputs.mjs +2 -2
  60. package/framework/skills/level-1-workflows/morph-phase-clarify/SKILL.md +238 -0
  61. package/framework/skills/level-1-workflows/{phase-codebase-analysis → morph-phase-codebase-analysis}/SKILL.md +3 -3
  62. package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +507 -0
  63. package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/SKILL.md +168 -27
  64. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/code-quality-reviewer-prompt.md +50 -0
  65. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/implementer-prompt.md +45 -0
  66. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/spec-reviewer-prompt.md +47 -0
  67. package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +254 -0
  68. package/framework/skills/level-1-workflows/{phase-setup → morph-phase-setup}/SKILL.md +50 -3
  69. package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/SKILL.md +48 -11
  70. package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/scripts/validate-tasks.mjs +3 -3
  71. package/framework/skills/level-1-workflows/{phase-uiux → morph-phase-uiux}/SKILL.md +46 -11
  72. package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +97 -0
  73. package/framework/standards/STANDARDS.json +640 -88
  74. package/framework/standards/infrastructure/vercel/vercel-database.md +106 -0
  75. package/framework/standards/integration/mcp/mcp-tools.md +25 -7
  76. package/framework/templates/REGISTRY.json +1825 -1909
  77. package/framework/templates/context/CONTEXT-FEATURE.md +276 -276
  78. package/framework/templates/docs/onboarding.md +3 -7
  79. package/package.json +2 -7
  80. package/src/commands/agents/dispatch-agents.js +104 -6
  81. package/src/commands/mcp/mcp-setup.js +39 -2
  82. package/src/commands/phase/phase-reset.js +74 -0
  83. package/src/commands/project/doctor.js +34 -51
  84. package/src/commands/project/init.js +1 -1
  85. package/src/commands/project/status.js +2 -2
  86. package/src/commands/project/update.js +381 -365
  87. package/src/commands/project/worktree.js +154 -0
  88. package/src/commands/scope/escalate.js +215 -0
  89. package/src/commands/state/advance-phase.js +132 -68
  90. package/src/commands/state/approve.js +2 -2
  91. package/src/commands/state/index.js +7 -8
  92. package/src/commands/state/phase-runner.js +1 -1
  93. package/src/commands/state/state.js +61 -6
  94. package/src/commands/task/expand.js +100 -0
  95. package/src/commands/tasks/task.js +78 -99
  96. package/src/commands/templates/template-render.js +93 -173
  97. package/src/commands/trust/trust.js +26 -21
  98. package/src/core/paths/output-schema.js +19 -3
  99. package/src/core/state/phase-state-machine.js +7 -4
  100. package/src/core/state/state-manager.js +32 -57
  101. package/src/core/workflows/workflow-detector.js +9 -87
  102. package/src/lib/detectors/claude-config-detector.js +93 -347
  103. package/src/lib/detectors/design-system-detector.js +189 -189
  104. package/src/lib/detectors/index.js +155 -57
  105. package/src/lib/generators/context-generator.js +2 -2
  106. package/src/lib/installers/mcp-installer.js +37 -5
  107. package/src/lib/phase-chain/phase-validator.js +336 -0
  108. package/src/lib/scope/impact-analyzer.js +106 -0
  109. package/src/lib/stack/stack-profile.js +88 -0
  110. package/src/lib/tasks/task-classifier.js +16 -0
  111. package/src/lib/tasks/task-parser.js +1 -1
  112. package/src/lib/tasks/test-runner.js +77 -0
  113. package/src/lib/trust/trust-manager.js +32 -144
  114. package/src/lib/validators/shared/emit-validator-dispatch.js +64 -0
  115. package/src/lib/validators/spec-validator.js +58 -4
  116. package/src/lib/validators/validation-runner.js +23 -11
  117. package/src/scripts/setup-infra.js +255 -224
  118. package/src/utils/agents-installer.js +34 -14
  119. package/src/utils/banner.js +1 -1
  120. package/src/utils/claude-settings-manager.js +1 -1
  121. package/src/utils/file-copier.js +1 -1
  122. package/src/utils/hooks-installer.js +272 -8
  123. package/framework/hooks/dev/check-sync-health.js +0 -117
  124. package/framework/hooks/dev/guard-version-numbers.js +0 -57
  125. package/framework/hooks/dev/sync-standards-registry.js +0 -60
  126. package/framework/hooks/dev/sync-template-registry.js +0 -60
  127. package/framework/hooks/dev/validate-skill-format.js +0 -70
  128. package/framework/hooks/dev/validate-standard-format.js +0 -73
  129. package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +0 -190
  130. package/framework/skills/level-1-workflows/phase-design/SKILL.md +0 -366
  131. package/framework/templates/meta-prompts/hops/hop-retry.md +0 -78
  132. package/framework/templates/meta-prompts/hops/hop-validation.md +0 -97
  133. package/framework/templates/meta-prompts/hops/hop-wrapper.md +0 -36
  134. package/framework/workflows/configs/design-impl.json +0 -49
  135. package/framework/workflows/configs/express.json +0 -45
  136. package/framework/workflows/configs/fast-track.json +0 -42
  137. package/framework/workflows/configs/full-morph.json +0 -79
  138. package/framework/workflows/configs/fusion.json +0 -39
  139. package/framework/workflows/configs/long-running.json +0 -33
  140. package/framework/workflows/configs/spec-only.json +0 -43
  141. package/framework/workflows/configs/ui-refresh.json +0 -49
  142. package/framework/workflows/configs/zero-touch.json +0 -82
  143. package/src/commands/project/index.js +0 -8
  144. package/src/commands/project/monitor.js +0 -295
  145. package/src/commands/project/tutorial.js +0 -115
  146. package/src/commands/state/validate-phase.js +0 -238
  147. package/src/commands/templates/generate-contracts.js +0 -445
  148. package/src/core/index.js +0 -10
  149. package/src/core/orchestrator.js +0 -171
  150. package/src/core/registry/command-registry.js +0 -28
  151. package/src/core/registry/index.js +0 -8
  152. package/src/core/registry/validator-registry.js +0 -204
  153. package/src/core/state/index.js +0 -8
  154. package/src/core/templates/index.js +0 -9
  155. package/src/core/templates/template-data-sources.js +0 -325
  156. package/src/core/templates/template-validator.js +0 -296
  157. package/src/core/workflows/index.js +0 -7
  158. package/src/generator/config-generator.js +0 -206
  159. package/src/generator/templates/config.json.template +0 -40
  160. package/src/generator/templates/project.md.template +0 -67
  161. package/src/lib/agents/micro-agent-factory.js +0 -161
  162. package/src/lib/analysis/complexity-analyzer.js +0 -441
  163. package/src/lib/analysis/index.js +0 -7
  164. package/src/lib/analytics/analytics-engine.js +0 -345
  165. package/src/lib/checkpoints/checkpoint-hooks.js +0 -298
  166. package/src/lib/checkpoints/index.js +0 -7
  167. package/src/lib/context/context-bundler.js +0 -241
  168. package/src/lib/context/context-optimizer.js +0 -212
  169. package/src/lib/context/context-tracker.js +0 -273
  170. package/src/lib/context/core-four-tracker.js +0 -201
  171. package/src/lib/context/mcp-optimizer.js +0 -200
  172. package/src/lib/detectors/config-detector.js +0 -223
  173. package/src/lib/detectors/standards-generator.js +0 -335
  174. package/src/lib/detectors/structure-detector.js +0 -275
  175. package/src/lib/execution/fusion-executor.js +0 -304
  176. package/src/lib/execution/parallel-executor.js +0 -270
  177. package/src/lib/hooks/stop-hook-executor.js +0 -286
  178. package/src/lib/hops/hop-composer.js +0 -221
  179. package/src/lib/monitor/agent-resolver.js +0 -144
  180. package/src/lib/monitor/renderer.js +0 -230
  181. package/src/lib/orchestration/index.js +0 -7
  182. package/src/lib/orchestration/team-orchestrator.js +0 -404
  183. package/src/lib/phase-chain/eligibility-checker.js +0 -243
  184. package/src/lib/threads/thread-coordinator.js +0 -238
  185. package/src/lib/threads/thread-manager.js +0 -317
  186. package/src/lib/tracking/artifact-trail.js +0 -202
  187. package/src/sanitizer/context-sanitizer.js +0 -221
  188. package/src/sanitizer/patterns.js +0 -163
  189. package/src/scanner/project-scanner.js +0 -242
  190. package/src/ui/diff-display.js +0 -91
  191. package/src/ui/interactive-wizard.js +0 -96
  192. package/src/ui/user-review.js +0 -211
  193. package/src/ui/wizard-questions.js +0 -188
  194. package/src/utils/color-utils.js +0 -70
  195. package/src/utils/process-handler.js +0 -97
  196. package/src/writer/file-writer.js +0 -86
  197. /package/framework/skills/level-0-meta/{brainstorming → morph-brainstorming}/references/proposal-example.md +0 -0
  198. /package/framework/skills/level-0-meta/{code-review → morph-code-review}/references/review-example.md +0 -0
  199. /package/framework/skills/level-0-meta/{code-review → morph-code-review}/references/review-guidelines.md +0 -0
  200. /package/framework/skills/level-0-meta/{code-review → morph-code-review}/scripts/scan-csharp.mjs +0 -0
  201. /package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/references/review-example-nextjs.md +0 -0
  202. /package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/scripts/scan-nextjs.mjs +0 -0
  203. /package/framework/skills/level-0-meta/{frontend-review → morph-frontend-review}/scripts/scan-accessibility.mjs +0 -0
  204. /package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/scripts/detect-dev-server.mjs +0 -0
  205. /package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/scripts/detect-stack.mjs +0 -0
  206. /package/framework/skills/level-0-meta/{terminal-title → morph-terminal-title}/scripts/set_title.sh +0 -0
  207. /package/framework/skills/level-1-workflows/{phase-clarify → morph-phase-clarify}/references/clarifications-example.md +0 -0
  208. /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/architecture-analysis-guide.md +0 -0
  209. /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/spec-authoring-guide.md +0 -0
  210. /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/spec-example.md +0 -0
  211. /package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/references/recap-example.md +0 -0
  212. /package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/references/vsa-implementation-guide.md +0 -0
  213. /package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/references/task-planning-patterns.md +0 -0
  214. /package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/references/tasks-example.md +0 -0
@@ -1,270 +0,0 @@
1
- /**
2
- * Parallel Executor — P-Thread concurrent agent spawning
3
- *
4
- * Manages concurrent thread execution for parallel feature development.
5
- * Configurable max-concurrent (1-5, default 3).
6
- *
7
- * Thread types supported:
8
- * P-Thread — Parallel execution (multiple agents simultaneously)
9
- * Managed via thread-manager.js + analytics-engine.js
10
- */
11
-
12
- import { createThread, startThread, completeThread, failThread, listThreads, THREAD_TYPES, THREAD_STATUS } from '../threads/thread-manager.js';
13
- import { recordEvent } from '../analytics/analytics-engine.js';
14
-
15
- const DEFAULT_MAX_CONCURRENT = 3;
16
- const MAX_CONCURRENT_LIMIT = 5;
17
-
18
- // ============================================================================
19
- // Parallel Execution
20
- // ============================================================================
21
-
22
- /**
23
- * Spawn N threads in parallel for the same feature
24
- * @param {Object} opts
25
- * @param {string} opts.feature - Feature name
26
- * @param {Array} opts.threadConfigs - Array of { agent, mission, meta } configs
27
- * @param {number} [opts.maxConcurrent=3] - Maximum concurrent threads
28
- * @returns {Promise<Array>} Array of created thread objects
29
- */
30
- export async function spawnParallel({ feature, threadConfigs, maxConcurrent = DEFAULT_MAX_CONCURRENT }) {
31
- const limit = Math.min(maxConcurrent, MAX_CONCURRENT_LIMIT);
32
-
33
- if (!threadConfigs || threadConfigs.length === 0) {
34
- throw new Error('threadConfigs is required and must be non-empty');
35
- }
36
-
37
- const threads = [];
38
- const batches = chunkArray(threadConfigs, limit);
39
-
40
- recordEvent({
41
- type: 'parallel_spawn_started',
42
- feature,
43
- data: {
44
- totalThreads: threadConfigs.length,
45
- maxConcurrent: limit,
46
- batches: batches.length
47
- }
48
- });
49
-
50
- for (const batch of batches) {
51
- const batchThreads = batch.map(config =>
52
- createThread({
53
- feature,
54
- type: THREAD_TYPES.PARALLEL,
55
- agent: config.agent,
56
- mission: config.mission,
57
- meta: { ...config.meta, maxConcurrent: limit }
58
- })
59
- );
60
-
61
- threads.push(...batchThreads);
62
-
63
- // Mark all batch threads as running simultaneously
64
- for (const t of batchThreads) {
65
- startThread(t.id);
66
- }
67
-
68
- recordEvent({
69
- type: 'parallel_batch_started',
70
- feature,
71
- data: {
72
- batchSize: batch.length,
73
- threadIds: batchThreads.map(t => t.id),
74
- concurrency: batch.length
75
- }
76
- });
77
- }
78
-
79
- return threads;
80
- }
81
-
82
- /**
83
- * Coordinate parallel thread lifecycle — wait for all to complete
84
- * @param {string} feature - Feature name
85
- * @param {string[]} threadIds - Thread IDs to track
86
- * @param {Object} [opts]
87
- * @param {number} [opts.pollIntervalMs=5000] - Poll interval
88
- * @param {number} [opts.timeoutMs=3600000] - Max wait (1 hour default)
89
- * @returns {Promise<Object>} { completed, failed, duration }
90
- */
91
- export async function coordinateParallel(feature, threadIds, {
92
- pollIntervalMs = 5000,
93
- timeoutMs = 3600000
94
- } = {}) {
95
- const start = Date.now();
96
- const completed = [];
97
- const failed = [];
98
- const remaining = new Set(threadIds);
99
-
100
- while (remaining.size > 0 && Date.now() - start < timeoutMs) {
101
- for (const id of [...remaining]) {
102
- const threads = listThreads({ feature });
103
- const thread = threads.find(t => t.id === id);
104
-
105
- if (!thread) {
106
- remaining.delete(id);
107
- continue;
108
- }
109
-
110
- if (thread.status === THREAD_STATUS.COMPLETED) {
111
- completed.push(id);
112
- remaining.delete(id);
113
- } else if (thread.status === THREAD_STATUS.FAILED || thread.status === THREAD_STATUS.KILLED) {
114
- failed.push(id);
115
- remaining.delete(id);
116
- }
117
- }
118
-
119
- if (remaining.size > 0) {
120
- await sleep(pollIntervalMs);
121
- }
122
- }
123
-
124
- const duration = Math.round((Date.now() - start) / 1000);
125
-
126
- if (remaining.size > 0) {
127
- recordEvent({
128
- type: 'parallel_timeout',
129
- feature,
130
- data: { timedOut: [...remaining], duration }
131
- });
132
- }
133
-
134
- recordEvent({
135
- type: 'parallel_coordination_complete',
136
- feature,
137
- data: { completed: completed.length, failed: failed.length, duration }
138
- });
139
-
140
- return { completed, failed, timedOut: [...remaining], duration };
141
- }
142
-
143
- /**
144
- * Wait until all specified threads complete
145
- * @param {string} feature - Feature name
146
- * @param {string[]} threadIds - Thread IDs to wait for
147
- * @param {number} [timeoutMs=3600000] - Max wait time
148
- * @returns {Promise<Object>} Coordination result
149
- */
150
- export async function waitAll(feature, threadIds, timeoutMs = 3600000) {
151
- return coordinateParallel(feature, threadIds, { timeoutMs });
152
- }
153
-
154
- /**
155
- * Wait until any thread completes, then return it
156
- * @param {string} feature - Feature name
157
- * @param {string[]} threadIds - Thread IDs to watch
158
- * @param {number} [timeoutMs=3600000] - Max wait time
159
- * @returns {Promise<Object|null>} First completed/failed thread or null on timeout
160
- */
161
- export async function waitAny(feature, threadIds, timeoutMs = 3600000) {
162
- const start = Date.now();
163
- const idSet = new Set(threadIds);
164
-
165
- while (Date.now() - start < timeoutMs) {
166
- const threads = listThreads({ feature });
167
- for (const thread of threads) {
168
- if (idSet.has(thread.id)) {
169
- if (thread.status === THREAD_STATUS.COMPLETED || thread.status === THREAD_STATUS.FAILED) {
170
- return thread;
171
- }
172
- }
173
- }
174
- await sleep(3000);
175
- }
176
-
177
- return null;
178
- }
179
-
180
- // ============================================================================
181
- // Efficiency Metrics
182
- // ============================================================================
183
-
184
- /**
185
- * Plan parallel execution batches without spawning threads
186
- * @param {Object} opts
187
- * @param {Array} opts.threadConfigs - Array of { agent, mission } configs
188
- * @param {number} [opts.maxConcurrent=3] - Max concurrent threads
189
- * @returns {Object} { batches, totalThreads, maxConcurrent, batchCount }
190
- */
191
- export function planParallelBatches({ threadConfigs, maxConcurrent = DEFAULT_MAX_CONCURRENT }) {
192
- const limit = Math.min(maxConcurrent, MAX_CONCURRENT_LIMIT);
193
- const batches = chunkArray(threadConfigs, limit);
194
- return {
195
- batches,
196
- totalThreads: threadConfigs.length,
197
- maxConcurrent: limit,
198
- batchCount: batches.length
199
- };
200
- }
201
-
202
- /**
203
- * Calculate parallel execution efficiency
204
- * @param {string|Object} featureOrOpts - Feature name, or { totalTasks, waves, maxConcurrent }
205
- * @returns {Object} Efficiency metrics (efficiency is 0-1 when given opts, 0-100 for real threads)
206
- */
207
- export function getParallelEfficiency(featureOrOpts) {
208
- // Accept stats object for testing/preview
209
- if (featureOrOpts && typeof featureOrOpts === 'object' && 'totalTasks' in featureOrOpts) {
210
- const { totalTasks, waves, maxConcurrent = DEFAULT_MAX_CONCURRENT } = featureOrOpts;
211
- const serialSteps = Math.ceil(totalTasks / Math.max(maxConcurrent, 1));
212
- const efficiency = waves > 0 ? Math.min(1, serialSteps / waves) : 0;
213
- return { totalTasks, waves, maxConcurrent, efficiency: Math.round(efficiency * 100) / 100 };
214
- }
215
-
216
- const feature = featureOrOpts;
217
- const threads = listThreads({ feature, type: THREAD_TYPES.PARALLEL });
218
-
219
- if (threads.length === 0) {
220
- return {
221
- feature,
222
- parallelThreads: 0,
223
- avgParallel: 0,
224
- maxParallel: 0,
225
- efficiency: 0
226
- };
227
- }
228
-
229
- // Group by time windows to estimate concurrent execution
230
- const completed = threads.filter(t => t.completedAt && t.startedAt);
231
- let maxParallel = 0;
232
-
233
- if (completed.length > 1) {
234
- // Find the time when most threads were running simultaneously
235
- for (const t1 of completed) {
236
- const concurrent = completed.filter(t2 =>
237
- t2.startedAt <= t1.completedAt && t2.completedAt >= t1.startedAt
238
- ).length;
239
- if (concurrent > maxParallel) maxParallel = concurrent;
240
- }
241
- } else {
242
- maxParallel = threads.length;
243
- }
244
-
245
- const avgParallel = threads.length / Math.max(1, Math.ceil(threads.length / maxParallel));
246
-
247
- return {
248
- feature,
249
- parallelThreads: threads.length,
250
- avgParallel: Math.round(avgParallel * 10) / 10,
251
- maxParallel,
252
- efficiency: Math.round(avgParallel / Math.max(maxParallel, 1) * 100)
253
- };
254
- }
255
-
256
- // ============================================================================
257
- // Helpers
258
- // ============================================================================
259
-
260
- function chunkArray(arr, size) {
261
- const chunks = [];
262
- for (let i = 0; i < arr.length; i += size) {
263
- chunks.push(arr.slice(i, i + size));
264
- }
265
- return chunks;
266
- }
267
-
268
- function sleep(ms) {
269
- return new Promise(resolve => setTimeout(resolve, ms));
270
- }
@@ -1,286 +0,0 @@
1
- /**
2
- * Stop Hook Executor — L-Thread validation loops
3
- *
4
- * Manages stop hooks for Long-Running threads.
5
- * Executes hook scripts, captures output, provides structured feedback.
6
- *
7
- * On failure: writes feedback to .morph/stop-hook-feedback/{threadId}.json
8
- * Agent reads feedback before retrying.
9
- *
10
- * Max retries: 5. Interval: configurable (default: 30 min).
11
- */
12
-
13
- import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
14
- import { join } from 'path';
15
- import { execSync } from 'child_process';
16
- import { recordEvent } from '../analytics/analytics-engine.js';
17
-
18
- const FEEDBACK_DIR = join(process.cwd(), '.morph/stop-hook-feedback');
19
- const MAX_RETRIES = 5;
20
- const DEFAULT_INTERVAL_MINUTES = 30;
21
-
22
- // Hook registry — hooks registered for each thread
23
- const hookRegistry = new Map(); // threadId → [{ hookPath, config }]
24
-
25
- // ============================================================================
26
- // Hook Registration
27
- // ============================================================================
28
-
29
- /**
30
- * Register a stop hook for a thread
31
- * @param {string} threadId - Thread ID
32
- * @param {string} hookPath - Path to hook script
33
- * @param {Object} [config]
34
- * @param {string[]} [config.triggers] - When to run: ['interval', 'on-stop', 'on-error']
35
- * @param {number} [config.intervalMinutes] - Interval for periodic execution
36
- * @param {boolean} [config.blocking] - Whether failure blocks thread progress
37
- */
38
- export function registerStopHook(threadId, hookPath, config = {}) {
39
- if (!hookRegistry.has(threadId)) {
40
- hookRegistry.set(threadId, []);
41
- }
42
-
43
- hookRegistry.get(threadId).push({
44
- hookPath,
45
- config: {
46
- triggers: config.triggers || ['on-stop'],
47
- intervalMinutes: config.intervalMinutes || DEFAULT_INTERVAL_MINUTES,
48
- blocking: config.blocking !== false,
49
- ...config
50
- }
51
- });
52
- }
53
-
54
- /**
55
- * Get registered hooks for a thread
56
- * @param {string} threadId
57
- * @returns {Array}
58
- */
59
- export function getThreadHooks(threadId) {
60
- return hookRegistry.get(threadId) || [];
61
- }
62
-
63
- // ============================================================================
64
- // Hook Execution
65
- // ============================================================================
66
-
67
- /**
68
- * Execute all stop hooks for a thread
69
- * @param {string} threadId - Thread ID
70
- * @param {string} hookType - Trigger type ('interval' | 'on-stop' | 'on-error')
71
- * @param {Object} [context] - Additional context passed to hooks
72
- * @returns {Promise<Object>} { passed, feedback, results }
73
- */
74
- export async function executeStopHooks(threadId, hookType = 'on-stop', context = {}) {
75
- const hooks = hookRegistry.get(threadId) || loadDefaultHooks(threadId);
76
- const results = [];
77
- let allPassed = true;
78
-
79
- for (const hook of hooks) {
80
- if (!hook.config.triggers.includes(hookType) && !hook.config.triggers.includes('all')) {
81
- continue;
82
- }
83
-
84
- const result = await executeHook(hook.hookPath, { threadId, hookType, ...context });
85
- results.push(result);
86
-
87
- if (!result.passed && hook.config.blocking) {
88
- allPassed = false;
89
- }
90
- }
91
-
92
- const passed = allPassed;
93
- const feedback = buildFeedback(results, passed);
94
-
95
- if (!passed) {
96
- saveFeedback(threadId, feedback);
97
- }
98
-
99
- recordEvent({
100
- type: passed ? 'stop_hook_passed' : 'stop_hook_failed',
101
- data: { threadId, hookType, resultsCount: results.length, passed }
102
- });
103
-
104
- return { passed, feedback, results };
105
- }
106
-
107
- /**
108
- * Execute a single hook script
109
- * @param {string} hookPath - Path to hook script
110
- * @param {Object} context - Context passed as environment variables
111
- * @returns {Promise<Object>} { hookPath, passed, output, error }
112
- */
113
- async function executeHook(hookPath, context) {
114
- const fullPath = existsSync(hookPath) ? hookPath : join(process.cwd(), hookPath);
115
-
116
- if (!existsSync(fullPath)) {
117
- return {
118
- hookPath,
119
- passed: true, // Missing hooks don't block
120
- output: null,
121
- error: `Hook not found: ${hookPath} (skipped)`,
122
- skipped: true
123
- };
124
- }
125
-
126
- try {
127
- const env = {
128
- ...process.env,
129
- MORPH_THREAD_ID: context.threadId || '',
130
- MORPH_HOOK_TYPE: context.hookType || '',
131
- MORPH_FEATURE: context.feature || ''
132
- };
133
-
134
- const output = execSync(`node "${fullPath}"`, {
135
- encoding: 'utf8',
136
- stdio: 'pipe',
137
- timeout: 120000, // 2 minute timeout per hook
138
- env
139
- });
140
-
141
- // Parse JSON output from hook
142
- let parsed;
143
- try {
144
- parsed = JSON.parse(output);
145
- } catch {
146
- // Non-JSON output means pass (for legacy hooks)
147
- parsed = { passed: true, output };
148
- }
149
-
150
- return {
151
- hookPath,
152
- passed: parsed.passed !== false,
153
- output: parsed,
154
- error: parsed.error || null
155
- };
156
- } catch (err) {
157
- return {
158
- hookPath,
159
- passed: false,
160
- output: null,
161
- error: err.message || 'Hook execution failed'
162
- };
163
- }
164
- }
165
-
166
- // ============================================================================
167
- // Feedback Management
168
- // ============================================================================
169
-
170
- /**
171
- * Build structured feedback from hook results
172
- * @param {Array} results - Hook execution results
173
- * @param {boolean} passed - Overall pass/fail
174
- * @returns {Object} Structured feedback
175
- */
176
- function buildFeedback(results, passed) {
177
- const failures = results.filter(r => !r.passed && !r.skipped);
178
- const issues = failures.flatMap(r => {
179
- const output = r.output;
180
- if (output?.issues) return output.issues;
181
- if (output?.errors) return output.errors;
182
- if (r.error) return [{ message: r.error, severity: 'error' }];
183
- return [];
184
- });
185
-
186
- return {
187
- passed,
188
- timestamp: new Date().toISOString(),
189
- hooksRun: results.length,
190
- hooksFailed: failures.length,
191
- issues,
192
- suggestions: failures.flatMap(r => r.output?.suggestions || []),
193
- nextAction: passed
194
- ? 'continue'
195
- : failures.length > 0 ? 'fix-and-retry' : 'investigate'
196
- };
197
- }
198
-
199
- /**
200
- * Save feedback to file for agent to read
201
- * @param {string} threadId - Thread ID
202
- * @param {Object} feedback - Feedback object
203
- * @returns {string} Path to feedback file
204
- */
205
- export function saveFeedback(threadId, feedback) {
206
- if (!existsSync(FEEDBACK_DIR)) {
207
- mkdirSync(FEEDBACK_DIR, { recursive: true });
208
- }
209
-
210
- const filePath = join(FEEDBACK_DIR, `${threadId}.json`);
211
- writeFileSync(filePath, JSON.stringify(feedback, null, 2), 'utf8');
212
- return filePath;
213
- }
214
-
215
- /**
216
- * Load feedback for a thread (agent reads this after failure)
217
- * @param {string} threadId - Thread ID
218
- * @returns {Object|null} Feedback object or null
219
- */
220
- export function loadFeedback(threadId) {
221
- const filePath = join(FEEDBACK_DIR, `${threadId}.json`);
222
- if (!existsSync(filePath)) return null;
223
- try {
224
- return JSON.parse(readFileSync(filePath, 'utf8'));
225
- } catch {
226
- return null;
227
- }
228
- }
229
-
230
- /**
231
- * Track retry count for a thread
232
- * @param {string} threadId
233
- * @returns {number} Current retry count
234
- */
235
- export function getRetryCount(threadId) {
236
- const feedback = loadFeedback(threadId);
237
- return feedback?.retryCount || 0;
238
- }
239
-
240
- /**
241
- * Increment retry count
242
- * @param {string} threadId
243
- * @returns {number} New retry count
244
- */
245
- export function incrementRetryCount(threadId) {
246
- const feedback = loadFeedback(threadId) || {};
247
- feedback.retryCount = (feedback.retryCount || 0) + 1;
248
- saveFeedback(threadId, feedback);
249
- return feedback.retryCount;
250
- }
251
-
252
- /**
253
- * Check if max retries exceeded
254
- * @param {string} threadId
255
- * @param {number} [maxRetries=5]
256
- * @returns {boolean}
257
- */
258
- export function isMaxRetriesExceeded(threadId, maxRetries = MAX_RETRIES) {
259
- return getRetryCount(threadId) >= maxRetries;
260
- }
261
-
262
- // ============================================================================
263
- // Helpers
264
- // ============================================================================
265
-
266
- function loadDefaultHooks(threadId) {
267
- // Load hooks from llm-interaction.json if no hooks registered
268
- const configPath = join(process.cwd(), '.morph/config/llm-interaction.json');
269
- if (!existsSync(configPath)) return [];
270
-
271
- try {
272
- const config = JSON.parse(readFileSync(configPath, 'utf8'));
273
- const hookNames = config.stopHooks?.hooks || [];
274
-
275
- return hookNames.map(name => ({
276
- hookPath: `framework/hooks/agent-stop/${name}.js`,
277
- config: {
278
- triggers: ['on-stop', 'interval'],
279
- intervalMinutes: config.stopHooks?.interval || DEFAULT_INTERVAL_MINUTES,
280
- blocking: true
281
- }
282
- }));
283
- } catch {
284
- return [];
285
- }
286
- }