@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.
- package/CLAUDE.md +21 -0
- package/README.md +2 -2
- package/bin/morph-spec.js +44 -55
- package/bin/task-manager.js +133 -20
- package/bin/validate.js +67 -33
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +201 -203
- package/docs/QUICKSTART.md +2 -2
- package/framework/CLAUDE.md +99 -77
- package/framework/agents.json +734 -182
- package/framework/commands/commit.md +166 -0
- package/framework/commands/morph-apply.md +13 -2
- package/framework/commands/morph-archive.md +8 -2
- package/framework/commands/morph-infra.md +6 -0
- package/framework/commands/morph-preflight.md +6 -0
- package/framework/commands/morph-proposal.md +56 -7
- package/framework/commands/morph-status.md +6 -0
- package/framework/commands/morph-troubleshoot.md +6 -0
- package/framework/hooks/claude-code/notification/approval-reminder.js +3 -2
- package/framework/hooks/claude-code/post-tool-use/context-refresh.js +1 -1
- package/framework/hooks/claude-code/post-tool-use/dispatch.js +155 -32
- package/framework/hooks/claude-code/post-tool-use/skill-reminder.js +78 -0
- package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +8 -17
- package/framework/hooks/claude-code/pre-compact/save-morph-context.js +16 -3
- package/framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js +4 -3
- package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +4 -3
- package/framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js +60 -0
- package/framework/hooks/claude-code/session-start/inject-morph-context.js +124 -2
- package/framework/hooks/claude-code/session-start/post-compact-restore.js +41 -0
- package/framework/hooks/claude-code/statusline.py +76 -30
- package/framework/hooks/claude-code/stop/validate-completion.js +2 -15
- package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +23 -5
- package/framework/hooks/claude-code/user-prompt/set-terminal-title.js +14 -6
- package/framework/hooks/shared/activity-logger.js +0 -24
- package/framework/hooks/shared/compact-restore.js +100 -0
- package/framework/hooks/shared/dispatch-helpers.js +116 -0
- package/framework/hooks/shared/phase-utils.js +12 -5
- package/framework/hooks/shared/skill-reminder-helpers.js +79 -0
- package/framework/hooks/shared/stale-task-reset.js +57 -0
- package/framework/hooks/shared/state-reader.js +29 -5
- package/framework/hooks/shared/worktree-helpers.js +53 -0
- package/framework/phases.json +69 -14
- package/framework/rules/morph-workflow.md +88 -86
- package/framework/skills/level-0-meta/mcp-registry.json +86 -51
- package/framework/skills/level-0-meta/{brainstorming → morph-brainstorming}/SKILL.md +14 -17
- package/framework/skills/level-0-meta/morph-checklist/SKILL.md +2 -2
- package/framework/skills/level-0-meta/{code-review → morph-code-review}/SKILL.md +2 -2
- package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/SKILL.md +163 -163
- package/framework/skills/level-0-meta/{frontend-review → morph-frontend-review}/SKILL.md +9 -9
- package/framework/skills/level-0-meta/morph-init/SKILL.md +77 -12
- package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/SKILL.md +62 -15
- package/framework/skills/level-0-meta/morph-replicate/SKILL.md +5 -5
- package/framework/skills/level-0-meta/morph-replicate/references/blazor-html-mapping.md +1 -1
- package/framework/skills/level-0-meta/{simulation-checklist → morph-simulation-checklist}/SKILL.md +1 -1
- package/framework/skills/level-0-meta/{terminal-title → morph-terminal-title}/SKILL.md +2 -2
- package/framework/skills/level-0-meta/{tool-usage-guide → morph-tool-usage-guide}/SKILL.md +3 -4
- package/framework/skills/level-0-meta/{tool-usage-guide → morph-tool-usage-guide}/references/tools-per-phase.md +7 -7
- package/framework/skills/level-0-meta/{verification-before-completion → morph-verification-before-completion}/SKILL.md +2 -2
- package/framework/skills/level-0-meta/{verification-before-completion → morph-verification-before-completion}/scripts/check-phase-outputs.mjs +2 -2
- package/framework/skills/level-1-workflows/morph-phase-clarify/SKILL.md +238 -0
- package/framework/skills/level-1-workflows/{phase-codebase-analysis → morph-phase-codebase-analysis}/SKILL.md +3 -3
- package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +507 -0
- package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/SKILL.md +168 -27
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/code-quality-reviewer-prompt.md +50 -0
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/implementer-prompt.md +45 -0
- package/framework/skills/level-1-workflows/morph-phase-implement/prompts/spec-reviewer-prompt.md +47 -0
- package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +254 -0
- package/framework/skills/level-1-workflows/{phase-setup → morph-phase-setup}/SKILL.md +50 -3
- package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/SKILL.md +48 -11
- package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/scripts/validate-tasks.mjs +3 -3
- package/framework/skills/level-1-workflows/{phase-uiux → morph-phase-uiux}/SKILL.md +46 -11
- package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +97 -0
- package/framework/standards/STANDARDS.json +640 -88
- package/framework/standards/infrastructure/vercel/vercel-database.md +106 -0
- package/framework/standards/integration/mcp/mcp-tools.md +25 -7
- package/framework/templates/REGISTRY.json +1825 -1909
- package/framework/templates/context/CONTEXT-FEATURE.md +276 -276
- package/framework/templates/docs/onboarding.md +3 -7
- package/package.json +2 -7
- package/src/commands/agents/dispatch-agents.js +104 -6
- package/src/commands/mcp/mcp-setup.js +39 -2
- package/src/commands/phase/phase-reset.js +74 -0
- package/src/commands/project/doctor.js +34 -51
- package/src/commands/project/init.js +1 -1
- package/src/commands/project/status.js +2 -2
- package/src/commands/project/update.js +381 -365
- package/src/commands/project/worktree.js +154 -0
- package/src/commands/scope/escalate.js +215 -0
- package/src/commands/state/advance-phase.js +132 -68
- package/src/commands/state/approve.js +2 -2
- package/src/commands/state/index.js +7 -8
- package/src/commands/state/phase-runner.js +1 -1
- package/src/commands/state/state.js +61 -6
- package/src/commands/task/expand.js +100 -0
- package/src/commands/tasks/task.js +78 -99
- package/src/commands/templates/template-render.js +93 -173
- package/src/commands/trust/trust.js +26 -21
- package/src/core/paths/output-schema.js +19 -3
- package/src/core/state/phase-state-machine.js +7 -4
- package/src/core/state/state-manager.js +32 -57
- package/src/core/workflows/workflow-detector.js +9 -87
- package/src/lib/detectors/claude-config-detector.js +93 -347
- package/src/lib/detectors/design-system-detector.js +189 -189
- package/src/lib/detectors/index.js +155 -57
- package/src/lib/generators/context-generator.js +2 -2
- package/src/lib/installers/mcp-installer.js +37 -5
- package/src/lib/phase-chain/phase-validator.js +336 -0
- package/src/lib/scope/impact-analyzer.js +106 -0
- package/src/lib/stack/stack-profile.js +88 -0
- package/src/lib/tasks/task-classifier.js +16 -0
- package/src/lib/tasks/task-parser.js +1 -1
- package/src/lib/tasks/test-runner.js +77 -0
- package/src/lib/trust/trust-manager.js +32 -144
- package/src/lib/validators/shared/emit-validator-dispatch.js +64 -0
- package/src/lib/validators/spec-validator.js +58 -4
- package/src/lib/validators/validation-runner.js +23 -11
- package/src/scripts/setup-infra.js +255 -224
- package/src/utils/agents-installer.js +34 -14
- package/src/utils/banner.js +1 -1
- package/src/utils/claude-settings-manager.js +1 -1
- package/src/utils/file-copier.js +1 -1
- package/src/utils/hooks-installer.js +272 -8
- package/framework/hooks/dev/check-sync-health.js +0 -117
- package/framework/hooks/dev/guard-version-numbers.js +0 -57
- package/framework/hooks/dev/sync-standards-registry.js +0 -60
- package/framework/hooks/dev/sync-template-registry.js +0 -60
- package/framework/hooks/dev/validate-skill-format.js +0 -70
- package/framework/hooks/dev/validate-standard-format.js +0 -73
- package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +0 -190
- package/framework/skills/level-1-workflows/phase-design/SKILL.md +0 -366
- package/framework/templates/meta-prompts/hops/hop-retry.md +0 -78
- package/framework/templates/meta-prompts/hops/hop-validation.md +0 -97
- package/framework/templates/meta-prompts/hops/hop-wrapper.md +0 -36
- package/framework/workflows/configs/design-impl.json +0 -49
- package/framework/workflows/configs/express.json +0 -45
- package/framework/workflows/configs/fast-track.json +0 -42
- package/framework/workflows/configs/full-morph.json +0 -79
- package/framework/workflows/configs/fusion.json +0 -39
- package/framework/workflows/configs/long-running.json +0 -33
- package/framework/workflows/configs/spec-only.json +0 -43
- package/framework/workflows/configs/ui-refresh.json +0 -49
- package/framework/workflows/configs/zero-touch.json +0 -82
- package/src/commands/project/index.js +0 -8
- package/src/commands/project/monitor.js +0 -295
- package/src/commands/project/tutorial.js +0 -115
- package/src/commands/state/validate-phase.js +0 -238
- package/src/commands/templates/generate-contracts.js +0 -445
- package/src/core/index.js +0 -10
- package/src/core/orchestrator.js +0 -171
- package/src/core/registry/command-registry.js +0 -28
- package/src/core/registry/index.js +0 -8
- package/src/core/registry/validator-registry.js +0 -204
- package/src/core/state/index.js +0 -8
- package/src/core/templates/index.js +0 -9
- package/src/core/templates/template-data-sources.js +0 -325
- package/src/core/templates/template-validator.js +0 -296
- package/src/core/workflows/index.js +0 -7
- package/src/generator/config-generator.js +0 -206
- package/src/generator/templates/config.json.template +0 -40
- package/src/generator/templates/project.md.template +0 -67
- package/src/lib/agents/micro-agent-factory.js +0 -161
- package/src/lib/analysis/complexity-analyzer.js +0 -441
- package/src/lib/analysis/index.js +0 -7
- package/src/lib/analytics/analytics-engine.js +0 -345
- package/src/lib/checkpoints/checkpoint-hooks.js +0 -298
- package/src/lib/checkpoints/index.js +0 -7
- package/src/lib/context/context-bundler.js +0 -241
- package/src/lib/context/context-optimizer.js +0 -212
- package/src/lib/context/context-tracker.js +0 -273
- package/src/lib/context/core-four-tracker.js +0 -201
- package/src/lib/context/mcp-optimizer.js +0 -200
- package/src/lib/detectors/config-detector.js +0 -223
- package/src/lib/detectors/standards-generator.js +0 -335
- package/src/lib/detectors/structure-detector.js +0 -275
- package/src/lib/execution/fusion-executor.js +0 -304
- package/src/lib/execution/parallel-executor.js +0 -270
- package/src/lib/hooks/stop-hook-executor.js +0 -286
- package/src/lib/hops/hop-composer.js +0 -221
- package/src/lib/monitor/agent-resolver.js +0 -144
- package/src/lib/monitor/renderer.js +0 -230
- package/src/lib/orchestration/index.js +0 -7
- package/src/lib/orchestration/team-orchestrator.js +0 -404
- package/src/lib/phase-chain/eligibility-checker.js +0 -243
- package/src/lib/threads/thread-coordinator.js +0 -238
- package/src/lib/threads/thread-manager.js +0 -317
- package/src/lib/tracking/artifact-trail.js +0 -202
- package/src/sanitizer/context-sanitizer.js +0 -221
- package/src/sanitizer/patterns.js +0 -163
- package/src/scanner/project-scanner.js +0 -242
- package/src/ui/diff-display.js +0 -91
- package/src/ui/interactive-wizard.js +0 -96
- package/src/ui/user-review.js +0 -211
- package/src/ui/wizard-questions.js +0 -188
- package/src/utils/color-utils.js +0 -70
- package/src/utils/process-handler.js +0 -97
- package/src/writer/file-writer.js +0 -86
- /package/framework/skills/level-0-meta/{brainstorming → morph-brainstorming}/references/proposal-example.md +0 -0
- /package/framework/skills/level-0-meta/{code-review → morph-code-review}/references/review-example.md +0 -0
- /package/framework/skills/level-0-meta/{code-review → morph-code-review}/references/review-guidelines.md +0 -0
- /package/framework/skills/level-0-meta/{code-review → morph-code-review}/scripts/scan-csharp.mjs +0 -0
- /package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/references/review-example-nextjs.md +0 -0
- /package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/scripts/scan-nextjs.mjs +0 -0
- /package/framework/skills/level-0-meta/{frontend-review → morph-frontend-review}/scripts/scan-accessibility.mjs +0 -0
- /package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/scripts/detect-dev-server.mjs +0 -0
- /package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/scripts/detect-stack.mjs +0 -0
- /package/framework/skills/level-0-meta/{terminal-title → morph-terminal-title}/scripts/set_title.sh +0 -0
- /package/framework/skills/level-1-workflows/{phase-clarify → morph-phase-clarify}/references/clarifications-example.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/architecture-analysis-guide.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/spec-authoring-guide.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/spec-example.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/references/recap-example.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/references/vsa-implementation-guide.md +0 -0
- /package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/references/task-planning-patterns.md +0 -0
- /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
|
-
}
|