@polymorphism-tech/morph-spec 4.22.0 → 5.0.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 (211) hide show
  1. package/CLAUDE.md +57 -71
  2. package/README.md +2 -2
  3. package/bin/morph-spec.js +224 -491
  4. package/claude-plugin.json +1 -1
  5. package/docs/CHEATSHEET.md +1 -1
  6. package/docs/QUICKSTART.md +1 -1
  7. package/framework/CLAUDE.md +55 -38
  8. package/framework/MORPH.md +244 -0
  9. package/framework/agents.json +127 -1198
  10. package/framework/commands/morph-apply.md +56 -240
  11. package/framework/commands/morph-archive.md +48 -85
  12. package/framework/commands/morph-preflight.md +7 -7
  13. package/framework/commands/morph-proposal.md +35 -191
  14. package/framework/commands/morph-status.md +19 -50
  15. package/framework/commands/morph-troubleshoot.md +2 -2
  16. package/framework/evals/rubrics/README.md +44 -0
  17. package/framework/evals/rubrics/architecture.md +46 -0
  18. package/framework/evals/rubrics/code-quality.md +50 -0
  19. package/framework/evals/rubrics/contracts.md +47 -0
  20. package/framework/evals/rubrics/test-coverage.md +50 -0
  21. package/framework/evals/scenarios/dotnet-crud.yaml +42 -0
  22. package/framework/evals/scenarios/nextjs-auth.yaml +38 -0
  23. package/framework/evals/suites/standard.yaml +5 -0
  24. package/framework/evals/trace-schema.md +67 -0
  25. package/framework/hooks/claude-code/core/README.md +3 -0
  26. package/framework/hooks/claude-code/core/denylist.json +14 -0
  27. package/framework/hooks/claude-code/{pre-tool-use → core}/enforce-phase-writes.js +5 -5
  28. package/framework/hooks/claude-code/core/post-edit-typecheck.js +69 -0
  29. package/framework/hooks/claude-code/core/pre-bash-denylist.js +29 -0
  30. package/framework/hooks/claude-code/{pre-tool-use → core}/protect-spec-files.js +4 -6
  31. package/framework/hooks/claude-code/{stop → core}/validate-completion.js +13 -82
  32. package/framework/hooks/shared/phase-utils.js +20 -31
  33. package/framework/hooks/shared/state-reader.js +4 -5
  34. package/framework/{skills/level-0-meta/mcp-registry.json → mcp/registry.json} +254 -243
  35. package/framework/phases.json +41 -115
  36. package/framework/rubrics/eval-rubric.json +66 -0
  37. package/framework/skills/README.md +39 -64
  38. package/framework/skills/core/README.md +3 -0
  39. package/framework/skills/core/morph-brainstorming/SKILL.md +102 -0
  40. package/framework/skills/core/morph-eval/SKILL.md +57 -0
  41. package/framework/skills/core/morph-implement/SKILL.md +83 -0
  42. package/framework/skills/core/morph-plan/SKILL.md +64 -0
  43. package/framework/skills/core/morph-review/SKILL.md +62 -0
  44. package/framework/skills/core/morph-stack-scan/SKILL.md +109 -0
  45. package/framework/skills/core/morph-standards/SKILL.md +49 -0
  46. package/framework/templates/CLAUDE.md.core.guide.md +29 -0
  47. package/framework/templates/CLAUDE.md.core.template +22 -0
  48. package/framework/templates/REGISTRY.json +4 -4
  49. package/framework/workflows/configs/fast-track.json +5 -10
  50. package/framework/workflows/configs/full.json +4 -8
  51. package/framework/workflows/configs/quick.json +32 -0
  52. package/framework/workflows/configs/standard.json +57 -64
  53. package/package.json +7 -6
  54. package/src/commands/mcp/mcp-setup.js +58 -31
  55. package/src/commands/project/doctor.js +69 -70
  56. package/src/commands/project/init.js +26 -5
  57. package/src/commands/project/status.js +18 -19
  58. package/src/commands/state/approve.js +1 -1
  59. package/src/commands/validation/validate-feature.js +18 -112
  60. package/src/core/paths/output-schema.js +133 -152
  61. package/src/core/state/phase-state-machine.js +10 -24
  62. package/src/core/state/state-manager.js +17 -17
  63. package/src/core/workflows/workflow-detector.js +214 -31
  64. package/src/core/workflows/workflow-router.js +204 -0
  65. package/src/dashboard/app/pages/AgentMap.jsx +32 -33
  66. package/src/dashboard/app/pages/LiveState.jsx +1 -1
  67. package/src/dashboard/app/pages/PhasePipeline.jsx +2 -4
  68. package/src/dashboard/app/pages/SkillsLibrary.jsx +2 -3
  69. package/src/dashboard/app/pages/Workflows.jsx +2 -2
  70. package/src/dashboard/scanner/agents-scanner.js +30 -35
  71. package/src/lib/agents/metrics-aggregator.js +1 -1
  72. package/src/lib/agents/output-collector.js +7 -9
  73. package/src/lib/context/context-builder.js +289 -0
  74. package/src/lib/detectors/claude-config-detector.js +19 -3
  75. package/src/lib/generators/context-generator.js +20 -42
  76. package/src/lib/generators/index.js +1 -0
  77. package/src/lib/generators/mandate-generator.js +512 -0
  78. package/src/lib/generators/metadata-extractor.js +1 -1
  79. package/src/lib/generators/recap-generator.js +3 -4
  80. package/src/lib/installers/mcp-installer.js +300 -92
  81. package/src/lib/phase-chain/phase-validator.js +36 -40
  82. package/src/lib/stack/stack-profile.js +74 -91
  83. package/src/lib/tasks/task-parser.js +1 -1
  84. package/src/lib/tasks/wave-resolver.js +353 -0
  85. package/src/lib/validators/checkpoint-persistence.js +2 -2
  86. package/src/lib/validators/content/cross-phase-validator.js +281 -0
  87. package/src/lib/validators/content/index.js +1 -0
  88. package/src/lib/validators/shared/emit-validator-dispatch.js +6 -56
  89. package/src/scripts/global-install.js +2 -27
  90. package/src/utils/agents-installer.js +224 -447
  91. package/src/utils/claude-settings-manager.js +57 -15
  92. package/src/utils/file-copier.js +1 -0
  93. package/src/utils/hooks-installer.js +15 -117
  94. package/src/utils/skills-installer.js +12 -18
  95. package/bin/detect-agents.js +0 -226
  96. package/bin/task-manager.js +0 -1036
  97. package/framework/agents/README.md +0 -13
  98. package/framework/agents/ai-agents/ai-system-architect.md +0 -194
  99. package/framework/agents/ai-agents/vector-search-expert.md +0 -247
  100. package/framework/agents/architecture/architecture-expert.md +0 -98
  101. package/framework/agents/architecture/morph-spec-architect.md +0 -66
  102. package/framework/agents/architecture/packages-validator.md +0 -89
  103. package/framework/agents/architecture/security-expert.md +0 -79
  104. package/framework/agents/architecture/seo-growth-hacker.md +0 -168
  105. package/framework/agents/architecture/standards-architect.md +0 -153
  106. package/framework/agents/backend/dotnet-senior.md +0 -163
  107. package/framework/agents/backend/ef-modeler.md +0 -119
  108. package/framework/agents/backend/hangfire-orchestrator.md +0 -133
  109. package/framework/agents/backend/ms-agent-expert.md +0 -55
  110. package/framework/agents/backend/neon-expert.md +0 -379
  111. package/framework/agents/frontend/css-specialist.md +0 -89
  112. package/framework/agents/frontend/design-system-validator.md +0 -100
  113. package/framework/agents/frontend/gsap-animator.md +0 -131
  114. package/framework/agents/frontend/nextjs-component-validator.md +0 -97
  115. package/framework/agents/frontend/nextjs-expert.md +0 -201
  116. package/framework/agents/frontend/ui-designer.md +0 -259
  117. package/framework/agents/infrastructure/infra-architect.md +0 -215
  118. package/framework/agents/integrations/asaas-financial.md +0 -138
  119. package/framework/agents/integrations/neon-auth.md +0 -151
  120. package/framework/agents/integrations/resend-email.md +0 -127
  121. package/framework/hooks/claude-code/post-tool-use/agent-complete.js +0 -122
  122. package/framework/hooks/claude-code/post-tool-use/dispatch.js +0 -363
  123. package/framework/hooks/claude-code/post-tool-use/handle-tool-failure.js +0 -78
  124. package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +0 -194
  125. package/framework/hooks/claude-code/pre-compact/save-morph-context.js +0 -199
  126. package/framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js +0 -55
  127. package/framework/hooks/claude-code/session-start/inject-morph-context.js +0 -323
  128. package/framework/hooks/claude-code/session-start/post-compact-restore.js +0 -41
  129. package/framework/hooks/claude-code/session-start/resume-context.js +0 -77
  130. package/framework/hooks/claude-code/teammate-idle/teammate-idle.js +0 -90
  131. package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +0 -114
  132. package/framework/hooks/shared/compact-restore.js +0 -123
  133. package/framework/hooks/shared/dispatch-helpers.js +0 -147
  134. package/framework/hooks/shared/skill-reminder-helpers.js +0 -79
  135. package/framework/hooks/shared/stale-task-reset.js +0 -57
  136. package/framework/hooks/shared/worktree-helpers.js +0 -75
  137. package/framework/skills/level-0-meta/README.md +0 -7
  138. package/framework/skills/level-0-meta/morph-agent-teams/SKILL.md +0 -472
  139. package/framework/skills/level-0-meta/morph-brainstorming/SKILL.md +0 -174
  140. package/framework/skills/level-0-meta/morph-brainstorming/references/proposal-example.md +0 -138
  141. package/framework/skills/level-0-meta/morph-checklist/SKILL.md +0 -145
  142. package/framework/skills/level-0-meta/morph-code-review/SKILL.md +0 -250
  143. package/framework/skills/level-0-meta/morph-code-review/references/review-example.md +0 -164
  144. package/framework/skills/level-0-meta/morph-code-review/references/review-guidelines.md +0 -100
  145. package/framework/skills/level-0-meta/morph-code-review/scripts/scan-csharp.mjs +0 -151
  146. package/framework/skills/level-0-meta/morph-code-review-nextjs/SKILL.md +0 -165
  147. package/framework/skills/level-0-meta/morph-code-review-nextjs/references/review-example-nextjs.md +0 -254
  148. package/framework/skills/level-0-meta/morph-code-review-nextjs/scripts/scan-nextjs.mjs +0 -189
  149. package/framework/skills/level-0-meta/morph-frontend-review/SKILL.md +0 -344
  150. package/framework/skills/level-0-meta/morph-frontend-review/scripts/scan-accessibility.mjs +0 -376
  151. package/framework/skills/level-0-meta/morph-gemini-image/SKILL.md +0 -290
  152. package/framework/skills/level-0-meta/morph-init/SKILL.md +0 -537
  153. package/framework/skills/level-0-meta/morph-parallel-dispatch/SKILL.md +0 -225
  154. package/framework/skills/level-0-meta/morph-post-implementation/SKILL.md +0 -401
  155. package/framework/skills/level-0-meta/morph-post-implementation/scripts/detect-dev-server.mjs +0 -153
  156. package/framework/skills/level-0-meta/morph-post-implementation/scripts/detect-stack.mjs +0 -234
  157. package/framework/skills/level-0-meta/morph-simulation-checklist/SKILL.md +0 -106
  158. package/framework/skills/level-0-meta/morph-tool-usage-guide/SKILL.md +0 -243
  159. package/framework/skills/level-0-meta/morph-tool-usage-guide/references/tools-per-phase.md +0 -169
  160. package/framework/skills/level-0-meta/morph-verification-before-completion/SKILL.md +0 -148
  161. package/framework/skills/level-0-meta/morph-verification-before-completion/scripts/check-phase-outputs.mjs +0 -110
  162. package/framework/skills/level-1-workflows/README.md +0 -7
  163. package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +0 -269
  164. package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +0 -453
  165. package/framework/skills/level-1-workflows/morph-phase-design/references/architecture-analysis-guide.md +0 -44
  166. package/framework/skills/level-1-workflows/morph-phase-design/references/spec-authoring-guide.md +0 -55
  167. package/framework/skills/level-1-workflows/morph-phase-design/references/spec-example.md +0 -253
  168. package/framework/skills/level-1-workflows/morph-phase-implement/.morph/logs/activity.json +0 -38
  169. package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +0 -634
  170. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/code-quality-reviewer-prompt.md +0 -50
  171. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/implementer-prompt.md +0 -54
  172. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/spec-reviewer-prompt.md +0 -47
  173. package/framework/skills/level-1-workflows/morph-phase-implement/references/recap-example.md +0 -132
  174. package/framework/skills/level-1-workflows/morph-phase-implement/references/vsa-implementation-guide.md +0 -92
  175. package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +0 -257
  176. package/framework/skills/level-1-workflows/morph-phase-tasks/.morph/logs/activity.json +0 -14
  177. package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +0 -291
  178. package/framework/skills/level-1-workflows/morph-phase-tasks/references/task-planning-patterns.md +0 -143
  179. package/framework/skills/level-1-workflows/morph-phase-tasks/references/tasks-example.md +0 -404
  180. package/framework/skills/level-1-workflows/morph-phase-tasks/scripts/validate-tasks.mjs +0 -112
  181. package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +0 -995
  182. package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +0 -147
  183. package/framework/squad-templates/backend-only.json +0 -47
  184. package/framework/squad-templates/frontend-only.json +0 -56
  185. package/framework/squad-templates/full-stack.json +0 -74
  186. package/src/commands/agents/agent-metrics.js +0 -174
  187. package/src/commands/agents/audit-dispatch.js +0 -82
  188. package/src/commands/agents/detect-agents.js +0 -275
  189. package/src/commands/agents/dispatch-agents.js +0 -461
  190. package/src/commands/agents/index.js +0 -13
  191. package/src/commands/agents/manage-agents.js +0 -352
  192. package/src/commands/agents/project-metrics.js +0 -74
  193. package/src/commands/phase/phase-reset.js +0 -74
  194. package/src/commands/scope/escalate.js +0 -217
  195. package/src/commands/state/advance-phase.js +0 -600
  196. package/src/commands/state/phase-runner.js +0 -215
  197. package/src/commands/state/state.js +0 -444
  198. package/src/commands/task/expand.js +0 -100
  199. package/src/commands/tasks/index.js +0 -4
  200. package/src/commands/tasks/task.js +0 -105
  201. package/src/commands/templates/index.js +0 -5
  202. package/src/commands/templates/template-render.js +0 -93
  203. package/src/commands/trust/trust.js +0 -118
  204. package/src/lib/agents/agents-manager.js +0 -349
  205. package/src/lib/build/build-runner.js +0 -99
  206. package/src/lib/checkpoints/checkpoint-hooks.js +0 -111
  207. package/src/lib/contracts/sprint-contract-generator.js +0 -137
  208. package/src/lib/scope/impact-analyzer.js +0 -106
  209. package/src/lib/trust/trust-manager.js +0 -157
  210. package/src/utils/tier-helpers.js +0 -24
  211. /package/framework/hooks/claude-code/{post-tool-use → core}/loop-detect.js +0 -0
@@ -1,363 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * PostToolUse Hook: Dispatch + Validator Injection
5
- *
6
- * Event: PostToolUse | Matcher: Bash
7
- *
8
- * Handles:
9
- * - morph-spec task done: auto-checkpoint every 3 tasks + validator dispatch injection
10
- * - morph-spec phase advance: phase chain evaluation + validator dispatch injection
11
- * - morph-spec approve: pass-through
12
- *
13
- * Validator dispatch: extracts VALIDATION DISPATCH JSON from tool output
14
- * and injects it as additionalContext so Claude reliably dispatches validators
15
- * as subagents via the Agent tool.
16
- *
17
- * Fail-open: exits 0 on any error.
18
- */
19
-
20
- import { execSync } from 'child_process';
21
- import { readFileSync, existsSync } from 'fs';
22
- import { resolve, join, dirname } from 'path';
23
- import { fileURLToPath } from 'url';
24
-
25
- const __dirname = dirname(fileURLToPath(import.meta.url));
26
- import { readStdin } from '../../shared/stdin-reader.js';
27
- import { stateExists, getFeature, getFeaturePhase } from '../../shared/state-reader.js';
28
- import { pass, injectContext } from '../../shared/hook-response.js';
29
- import { logHookActivity } from '../../shared/activity-logger.js';
30
- import { extractValidationDispatch, formatValidatorInstruction, getToolOutput, parseTaskDoneCommand, parsePhaseAdvanceCommand, parseDispatchAgentsCommand } from '../../shared/dispatch-helpers.js';
31
-
32
- try {
33
- if (!stateExists()) pass();
34
-
35
- const payload = await readStdin();
36
- if (!payload) pass();
37
-
38
- const command = payload?.tool_input?.command || '';
39
- if (!command) pass();
40
-
41
- await dispatch(command, payload);
42
- } catch {
43
- // Fail-open
44
- process.exit(0);
45
- }
46
-
47
- async function dispatch(command, payload) {
48
- // morph-spec task done <feature> <taskId> [--skip-validation]
49
- const taskDoneParsed = parseTaskDoneCommand(command);
50
- if (taskDoneParsed) {
51
- const { featureName } = taskDoneParsed;
52
- const skipValidation = command.includes('--skip-validation');
53
-
54
- const contextParts = [];
55
-
56
- // Check if checkpoint should run (every 3 tasks)
57
- let dispatchResult = 'task_done';
58
- try {
59
- const feature = getFeature(featureName);
60
- if (feature?.tasks) {
61
- const completed = (feature.tasks.completed || 0) + 1;
62
- if (completed > 0 && completed % 3 === 0) {
63
- const checkpointNum = Math.floor(completed / 3);
64
- run(`morph-spec checkpoint-save ${featureName} --note "Auto-checkpoint #${checkpointNum} at task ${completed}"`);
65
- dispatchResult = 'checkpoint_saved';
66
- }
67
- }
68
- } catch {
69
- // Non-blocking
70
- }
71
-
72
- // Evaluator dispatch: if sprint contract exists, instruct Claude to dispatch evaluator
73
- try {
74
- const taskId = taskDoneParsed.taskId;
75
- if (taskId && !command.includes('--skip-eval')) {
76
- const contractPath = join(process.cwd(), '.morph', 'features', featureName, '5-implement', 'contracts', `${taskId}-contract.yaml`);
77
- if (existsSync(contractPath)) {
78
- const contractContent = readFileSync(contractPath, 'utf-8');
79
- let changedFiles = '';
80
- try {
81
- changedFiles = execSync('git diff --name-only HEAD~1 2>/dev/null | head -10', {
82
- cwd: process.cwd(), encoding: 'utf-8', timeout: 5000, stdio: ['pipe', 'pipe', 'pipe']
83
- }).trim();
84
- } catch { /* no git */ }
85
-
86
- // Include score context from latest validation if available
87
- let scoreContext = '';
88
- try {
89
- const evalFeature = getFeature(featureName);
90
- const taskScores = evalFeature?.validationHistory?.[taskId]?.scores;
91
- if (taskScores) {
92
- scoreContext = `\n## Previous Validation Score: ${taskScores.score}/10\n` +
93
- Object.entries(taskScores.breakdown || {})
94
- .map(([cat, d]) => ` ${cat}: ${Math.round(d.score * 10) / 10}/10 (weight ${Math.round(d.weight * 100)}%)`)
95
- .join('\n') + '\n\nFocus your evaluation on categories with the lowest scores.\n';
96
- }
97
- } catch { /* non-blocking */ }
98
-
99
- contextParts.push(
100
- `🔍 EVALUATOR DISPATCH\n` +
101
- `════════════════════════════════════════\n` +
102
- `Sprint contract exists for ${taskId}. Dispatch an evaluator sub-agent:\n\n` +
103
- `Use the Agent tool with:\n` +
104
- ` subagent_type: "general-purpose"\n` +
105
- ` model: "sonnet"\n` +
106
- ` prompt: (see below)\n\n` +
107
- `--- EVALUATOR PROMPT ---\n` +
108
- `You are an ADVERSARIAL evaluator. Your default assumption is that defects exist.\n` +
109
- `Do NOT confirm correctness — actively search for problems.\n\n` +
110
- `## Contract\n${contractContent}\n\n` +
111
- `## Changed Files\n${changedFiles || '(unable to determine)'}\n` +
112
- `${scoreContext}\n` +
113
- `## Instructions\n` +
114
- `1. Read each changed file\n` +
115
- `2. Run the verification commands from the contract\n` +
116
- `3. Evaluate each definitionOfDone item: PASS or FAIL with evidence\n` +
117
- `4. For each category, score 0-10 and explain deficiencies found\n` +
118
- `5. A clean report is suspicious — dig deeper before declaring PASS\n` +
119
- `6. Return a structured result with overall PASS/FAIL and score breakdown\n` +
120
- `--- END EVALUATOR PROMPT ---`
121
- );
122
- }
123
- }
124
- } catch {
125
- // Non-blocking — evaluator is optional
126
- }
127
-
128
- // Always inject validator dispatch — deferred if --skip-validation
129
- const validatorText = await buildValidatorDispatchContext(featureName, payload);
130
- if (validatorText) {
131
- if (skipValidation) {
132
- // Include budget info when available
133
- let budgetInfo = '';
134
- try {
135
- const feature = getFeature(featureName);
136
- if (feature) {
137
- const total = feature.tasks?.total || 0;
138
- const budget = total === 0 ? 1 : Math.max(1, Math.min(5, Math.ceil(total * 0.2)));
139
- const used = (feature.skipValidationLog || []).length;
140
- budgetInfo = ` (budget: ${used}/${budget})`;
141
- }
142
- } catch {
143
- // Non-blocking
144
- }
145
- contextParts.push(
146
- `⏩ DEFERRED TO CHECKPOINT — validators skipped on this task${budgetInfo} but MUST run at next checkpoint:\n\n` +
147
- validatorText
148
- );
149
- dispatchResult = 'task_done_deferred_validators';
150
- } else {
151
- contextParts.push(validatorText);
152
- dispatchResult = 'task_done_with_validators';
153
- }
154
- }
155
-
156
- logHookActivity('phase-dispatch', 'PostToolUse', dispatchResult);
157
-
158
- if (contextParts.length > 0) {
159
- injectContext(contextParts.join('\n\n'));
160
- }
161
- pass();
162
- }
163
-
164
- // morph-spec phase advance <feature>
165
- const phaseAdvanceParsed = parsePhaseAdvanceCommand(command);
166
- if (phaseAdvanceParsed) {
167
- const { featureName } = phaseAdvanceParsed;
168
-
169
- const contextParts = [];
170
-
171
- // Phase chain evaluation
172
- const chainText = buildPhaseChainContext(featureName);
173
- if (chainText) contextParts.push(chainText);
174
-
175
- // Validator dispatch injection
176
- const validatorText = await buildValidatorDispatchContext(featureName, payload);
177
- if (validatorText) contextParts.push(validatorText);
178
-
179
- // Mandatory dispatch instruction when advancing to implement phase
180
- try {
181
- const advancedPhase = getFeaturePhase(featureName);
182
- if (advancedPhase === 'implement') {
183
- contextParts.push(
184
- '🔧 MANDATORY AGENT DISPATCH\n' +
185
- '════════════════════════════════════════\n' +
186
- `Run this command BEFORE starting any task:\n\n` +
187
- ` morph-spec dispatch-agents ${featureName} implement\n\n` +
188
- `Task start will be BLOCKED until dispatch is recorded.\n` +
189
- `Use the dispatch output to determine execution mode (single/subagents/agent-teams).`
190
- );
191
- }
192
- } catch {
193
- // Non-blocking
194
- }
195
-
196
- logHookActivity('phase-dispatch', 'PostToolUse',
197
- validatorText ? 'phase_advance_with_validators' : 'phase_chain');
198
-
199
- if (contextParts.length > 0) {
200
- injectContext(contextParts.join('\n\n'));
201
- }
202
- pass();
203
- }
204
-
205
- // morph-spec dispatch-agents <feature> <phase>
206
- const dispatchParsed = parseDispatchAgentsCommand(command);
207
- if (dispatchParsed) {
208
- const { featureName: dispFeature, phase: dispPhase } = dispatchParsed;
209
- if (dispPhase === 'implement') {
210
- injectContext(
211
- `✓ Agent dispatch recorded for '${dispFeature}' (implement phase).\n` +
212
- `You may now start tasks with: morph-spec task start ${dispFeature} <taskId>\n` +
213
- `Use the dispatch config above to assign tasks to the correct typed agents.`
214
- );
215
- }
216
- logHookActivity('phase-dispatch', 'PostToolUse', 'dispatch_agents_confirmed');
217
- pass();
218
- }
219
-
220
- // morph-spec approve <feature> <gate>
221
- const approveMatch = command.match(/morph-spec\s+approve\s+(\S+)\s+(\S+)/);
222
- if (approveMatch) {
223
- // Approval handled by CLI — just pass through
224
- pass();
225
- }
226
-
227
- // No match — exit cleanly
228
- pass();
229
- }
230
-
231
- /**
232
- * Build phase chain context text (returns null if not applicable).
233
- * Replaces the old evaluatePhaseChain that called injectContext directly.
234
- *
235
- * @param {string} featureName
236
- * @returns {string|null}
237
- */
238
- function buildPhaseChainContext(featureName) {
239
- try {
240
- const feature = getFeature(featureName);
241
- if (!feature) return null;
242
-
243
- const workflowId = feature.workflow;
244
- if (!workflowId || workflowId === 'auto') return null;
245
-
246
- const configPath = resolve(
247
- process.cwd(),
248
- `.morph/framework/workflows/configs/${workflowId}.json`
249
- );
250
- if (!existsSync(configPath)) return null;
251
-
252
- let workflowConfig;
253
- try {
254
- workflowConfig = JSON.parse(readFileSync(configPath, 'utf8'));
255
- } catch {
256
- return null;
257
- }
258
-
259
- if (!workflowConfig?.phaseChain?.enabled) return null;
260
-
261
- return (
262
- `Phase advance complete. Continue the phase chain:\n` +
263
- ` morph-spec phase run ${featureName}\n\n` +
264
- `The phase runner will check eligibility and auto-advance if all gates pass. ` +
265
- `It will pause automatically on blocked tasks, low pass rate, or trust gates.`
266
- );
267
- } catch {
268
- return null;
269
- }
270
- }
271
-
272
- /**
273
- * Extract validator dispatch from tool output and build injection context.
274
- *
275
- * Strategy:
276
- * 1. Try to parse VALIDATION DISPATCH JSON from the tool_result (stdout of phase advance)
277
- * 2. If not found, fallback to running dispatch-agents CLI in validate mode
278
- *
279
- * @param {string} featureName
280
- * @param {object} payload - PostToolUse hook payload
281
- * @returns {string|null} - Formatted injection text or null
282
- */
283
- async function buildValidatorDispatchContext(featureName, payload) {
284
- try {
285
- // Strategy 1: Extract from tool output
286
- const toolOutput = getToolOutput(payload);
287
- const dispatch = extractValidationDispatch(toolOutput);
288
- if (dispatch) return formatValidatorInstruction(dispatch);
289
-
290
- // Strategy 2: Direct import of buildDispatchConfig (no subprocess)
291
- return await buildDispatchDirect(featureName);
292
- } catch {
293
- return null;
294
- }
295
- }
296
-
297
- /**
298
- * Fallback: build validator dispatch config via direct import (no subprocess).
299
- *
300
- * Attempts to import buildDispatchConfig from the installed package or local
301
- * framework. Falls back to null on any error (fail-open).
302
- *
303
- * @param {string} featureName
304
- * @returns {Promise<string|null>}
305
- */
306
- async function buildDispatchDirect(featureName) {
307
- try {
308
- const phase = getFeaturePhase(featureName);
309
- if (!phase) return null;
310
-
311
- // Try local project path first, then package path
312
- let buildDispatchConfig;
313
- const localPath = resolve(process.cwd(), 'src/commands/agents/dispatch-agents.js');
314
- const pkgPath = resolve(process.cwd(), 'node_modules/@polymorphism-tech/morph-spec/src/commands/agents/dispatch-agents.js');
315
-
316
- if (existsSync(localPath)) {
317
- ({ buildDispatchConfig } = await import(`file://${localPath.replace(/\\/g, '/')}`));
318
- } else if (existsSync(pkgPath)) {
319
- ({ buildDispatchConfig } = await import(`file://${pkgPath.replace(/\\/g, '/')}`));
320
- } else {
321
- return null;
322
- }
323
-
324
- const config = await buildDispatchConfig(process.cwd(), featureName, phase, { mode: 'validate' });
325
- const validators = config.agents?.filter(a => a.tier === 4);
326
- if (!validators || validators.length === 0) return null;
327
-
328
- const agentsJsonPath = resolve(process.cwd(), 'framework/agents.json');
329
- const pkgAgentsPath = resolve(process.cwd(), 'node_modules/@polymorphism-tech/morph-spec/framework/agents.json');
330
- const agentsPath = existsSync(agentsJsonPath) ? agentsJsonPath : pkgAgentsPath;
331
-
332
- let allAgents = {};
333
- if (existsSync(agentsPath)) {
334
- allAgents = JSON.parse(readFileSync(agentsPath, 'utf8')).agents || {};
335
- }
336
-
337
- const dispatch = {
338
- validators: validators.map(v => {
339
- const agentData = allAgents[v.id];
340
- return {
341
- id: v.id,
342
- title: v.title,
343
- taskPrompt: v.taskPrompt,
344
- severity: agentData?.hook_behavior?.severity || 'error',
345
- blocksOnFail: agentData?.hook_behavior?.blocks_on_fail ?? true,
346
- checks: agentData?.hook_behavior?.validates || [],
347
- };
348
- }),
349
- };
350
-
351
- return formatValidatorInstruction(dispatch);
352
- } catch {
353
- return null;
354
- }
355
- }
356
-
357
- function run(cmd) {
358
- try {
359
- execSync(cmd, { stdio: 'pipe', cwd: process.cwd(), timeout: 30000 });
360
- } catch {
361
- // Non-blocking
362
- }
363
- }
@@ -1,78 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * PostToolUseFailure Hook: Log Tool Failures + Rollback Awareness
5
- *
6
- * Event: PostToolUseFailure
7
- *
8
- * Runs when any tool fails. Appends a structured JSON entry to
9
- * .morph/logs/tool-failures.log for debugging and pattern analysis.
10
- *
11
- * If a critical failure occurs and there are unstaged changes,
12
- * suggests git stash as a recovery option.
13
- *
14
- * Fail-open: exits 0 on any error.
15
- */
16
-
17
- import { appendFileSync, mkdirSync } from 'fs';
18
- import { join } from 'path';
19
- import { execSync } from 'child_process';
20
- import { readStdin } from '../../shared/stdin-reader.js';
21
- import { stateExists } from '../../shared/state-reader.js';
22
- import { injectContext } from '../../shared/hook-response.js';
23
- import { logHookActivity, logEvent } from '../../shared/activity-logger.js';
24
-
25
- const CRITICAL_ERROR_PATTERNS = ['ENOENT', 'EPERM', 'EACCES', 'segfault', 'out of memory', 'ENOMEM'];
26
-
27
- try {
28
- // Only log in morph-spec projects
29
- if (!stateExists()) {
30
- process.exit(0);
31
- }
32
-
33
- const input = await readStdin();
34
-
35
- const logDir = join(process.cwd(), '.morph', 'logs');
36
- mkdirSync(logDir, { recursive: true });
37
-
38
- const entry = {
39
- timestamp: new Date().toISOString(),
40
- tool: input?.tool_name ?? 'unknown',
41
- error: input?.error ?? null,
42
- input: input?.tool_input ?? null,
43
- };
44
-
45
- appendFileSync(join(logDir, 'tool-failures.log'), JSON.stringify(entry) + '\n');
46
- logHookActivity('handle-tool-failure', 'PostToolUseFailure', `logged(${entry.tool})`);
47
-
48
- // Rollback awareness: check for unstaged changes after critical failures
49
- const errorStr = JSON.stringify(input?.error || '');
50
- const isCritical = CRITICAL_ERROR_PATTERNS.some(p => errorStr.includes(p));
51
-
52
- logEvent('tool_failure', { tool: entry.tool, critical: isCritical });
53
-
54
- if (isCritical) {
55
- try {
56
- const gitStatus = execSync('git status --porcelain', {
57
- cwd: process.cwd(),
58
- encoding: 'utf-8',
59
- timeout: 5000,
60
- stdio: ['pipe', 'pipe', 'pipe']
61
- }).trim();
62
-
63
- if (gitStatus) {
64
- injectContext(
65
- `⚠️ [morph-spec] Critical tool failure (${entry.tool}). ` +
66
- `Unstaged changes detected:\n${gitStatus.split('\n').slice(0, 5).join('\n')}\n\n` +
67
- `Consider: git stash to save current state before retrying.`
68
- );
69
- }
70
- } catch {
71
- // git not available or not a git repo — ignore
72
- }
73
- }
74
- } catch {
75
- // Fail-open: logging is non-critical
76
- }
77
-
78
- process.exit(0);
@@ -1,194 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * PostToolUse Hook: Validator Feedback
5
- *
6
- * Event: PostToolUse | Matcher: Bash
7
- *
8
- * Fires after `morph-spec task done` completes.
9
- * Reads validationHistory for the completed task and injects
10
- * remediation context when validation failed.
11
- *
12
- * This closes the Builder-Validator loop:
13
- * task done → runValidation → validationHistory → hook injects context
14
- * → LLM sees remediation prompt before next step → auto-fix attempt
15
- *
16
- * Fail-open: exits 0 on any error.
17
- */
18
-
19
- import { readStdin } from '../../shared/stdin-reader.js';
20
- import { stateExists, loadState } from '../../shared/state-reader.js';
21
- import { injectContext, pass } from '../../shared/hook-response.js';
22
- import { logHookActivity } from '../../shared/activity-logger.js';
23
- import { parseTaskDoneCommand } from '../../shared/dispatch-helpers.js';
24
-
25
- // Standard IDs referenced in remediation messages
26
- const STANDARD_REFS = {
27
- 'di': 'dependency injection best practices',
28
- 'async': 'async/await and CancellationToken patterns',
29
- 'security': 'security best practices',
30
- 'packages': 'NuGet package compatibility',
31
- 'design-system': 'frontend/design-system/naming.md#css-class-conventions',
32
- };
33
-
34
- try {
35
- if (!stateExists()) pass();
36
-
37
- const payload = await readStdin();
38
- if (!payload) pass();
39
-
40
- const command = payload?.tool_input?.command || '';
41
- if (!command) pass();
42
-
43
- const parsed = parseTaskDoneCommand(command);
44
- if (!parsed) pass();
45
-
46
- const { featureName, taskId } = parsed;
47
-
48
- // Load state and find validationHistory for this task
49
- const state = loadState();
50
- if (!state) pass();
51
-
52
- const feature = state?.features?.[featureName];
53
- if (!feature) pass();
54
-
55
- const taskHistory = feature.validationHistory?.[taskId];
56
- if (!taskHistory) pass();
57
-
58
- // Only act on failed validations (passed = no feedback needed)
59
- if (taskHistory.status === 'passed') {
60
- logHookActivity('validator-feedback', 'PostToolUse', 'passed');
61
- pass();
62
- }
63
-
64
- // Build remediation context based on validation issues
65
- logHookActivity('validator-feedback', 'PostToolUse', `remediation_injected(attempt_${taskHistory.attempt || 1})`);
66
- buildRemediationContext(featureName, taskId, taskHistory);
67
- } catch {
68
- // Fail-open
69
- process.exit(0);
70
- }
71
-
72
- /**
73
- * Build and inject remediation context for a failed validation.
74
- *
75
- * @param {string} featureName
76
- * @param {string} taskId
77
- * @param {Object} taskHistory - validationHistory[taskId]
78
- */
79
- function buildRemediationContext(featureName, taskId, taskHistory) {
80
- const attempt = taskHistory.attempt || 1;
81
- const validators = taskHistory.validators || {};
82
- const scores = taskHistory.scores || null;
83
-
84
- // Collect all issues across all validators
85
- const allIssues = [];
86
- for (const [validatorName, result] of Object.entries(validators)) {
87
- if (result.passed) continue;
88
- const issues = result.issues || [];
89
- for (const issue of issues) {
90
- allIssues.push({ validator: validatorName, ...issue });
91
- }
92
- }
93
-
94
- if (allIssues.length === 0 && taskHistory.status !== 'blocked') return;
95
-
96
- const lines = [];
97
-
98
- if (taskHistory.status === 'blocked') {
99
- // Max attempts reached OR score-based escalation
100
- const scoreInfo = scores ? ` (score: ${scores.score}/10)` : '';
101
- lines.push(`⛔ ESCALATION REQUIRED — Task ${taskId} in feature '${featureName}'${scoreInfo}`);
102
- if (scores?.action === 'escalate') {
103
- lines.push(` Score below retry threshold — immediate escalation.`);
104
- } else {
105
- lines.push(` Validation failed ${attempt} times (max attempts reached).`);
106
- }
107
- lines.push(` Human review required before proceeding.`);
108
- if (scores) {
109
- lines.push(``);
110
- lines.push(` Score breakdown:`);
111
- appendScoreBreakdown(lines, scores);
112
- }
113
- lines.push(` Last failures:`);
114
- for (const issue of allIssues.slice(0, 5)) {
115
- lines.push(` • [${issue.validator}] ${issue.message} ${issue.file ? `(${issue.file}${issue.line ? ':' + issue.line : ''})` : ''}`);
116
- }
117
- } else {
118
- // Active remediation — attempt N of 3
119
- const scoreInfo = scores ? ` | score: ${scores.score}/10` : '';
120
- lines.push(`🔄 REMEDIATION REQUIRED — Task ${taskId} (attempt ${attempt}/3${scoreInfo})`);
121
- lines.push(` Validation failed. Fix the following issues and re-run:`);
122
- lines.push(` morph-spec task done ${featureName} ${taskId}`);
123
- lines.push(``);
124
-
125
- // Score-based prioritized feedback (when available)
126
- if (scores?.breakdown) {
127
- lines.push(` Priority fixes (by impact):`);
128
- const sorted = Object.entries(scores.breakdown)
129
- .filter(([, cat]) => (cat.issues?.length || 0) > 0)
130
- .sort((a, b) => ((10 - b[1].score) * b[1].weight) - ((10 - a[1].score) * a[1].weight));
131
-
132
- for (const [category, cat] of sorted) {
133
- const topIssue = cat.issues[0];
134
- const location = topIssue?.file ? ` (${topIssue.file}${topIssue.line ? ':' + topIssue.line : ''})` : '';
135
- const msg = topIssue?.message || 'Issues detected';
136
- lines.push(` ${Math.round(cat.score * 10) / 10}/10 [${category}] ${msg}${location}`);
137
- if (cat.issues.length > 1) {
138
- lines.push(` ... and ${cat.issues.length - 1} more issue(s)`);
139
- }
140
- }
141
- lines.push(``);
142
- appendScoreBreakdown(lines, scores);
143
- } else {
144
- // Fallback: original issue-by-issue format
145
- for (const issue of allIssues) {
146
- const refKey = getRefKey(issue.rule || issue.validator);
147
- const ref = refKey ? ` — Ref: ${STANDARD_REFS[refKey] || refKey}` : '';
148
- const location = issue.file
149
- ? ` (${issue.file}${issue.line ? ':' + issue.line : ''})`
150
- : '';
151
- lines.push(` • [${issue.validator}] ${issue.message}${location}${ref}`);
152
- }
153
- }
154
-
155
- if (attempt >= 2) {
156
- lines.push(``);
157
- lines.push(` ⚠️ If this fails again, it will be escalated (attempt ${attempt + 1} = final).`);
158
- }
159
- }
160
-
161
- injectContext(lines.join('\n'));
162
- }
163
-
164
- /**
165
- * Append score breakdown lines to output.
166
- * @param {string[]} lines - Output array
167
- * @param {Object} scores - { score, action, breakdown }
168
- */
169
- function appendScoreBreakdown(lines, scores) {
170
- if (!scores?.breakdown) return;
171
- const approveThreshold = scores.approveThreshold ?? 8.0;
172
- for (const [category, data] of Object.entries(scores.breakdown)) {
173
- if (category === '_unmapped') continue; // skip internal unmapped category
174
- const pct = Math.round(data.weight * 100);
175
- const indicator = data.score >= approveThreshold ? '\u2713' : data.score >= 6 ? '\u2193' : '\u2193\u2193';
176
- lines.push(` ${category}: ${Math.round(data.score * 10) / 10}/10 (weight ${pct}%) ${indicator}`);
177
- }
178
- }
179
-
180
- /**
181
- * Map a rule/validator name to a STANDARD_REFS key.
182
- * @param {string} ruleOrValidator
183
- * @returns {string|null}
184
- */
185
- function getRefKey(ruleOrValidator) {
186
- if (!ruleOrValidator) return null;
187
- const lower = ruleOrValidator.toLowerCase();
188
- if (lower.includes('di') || lower.includes('dependency') || lower.includes('injection')) return 'di';
189
- if (lower.includes('async') || lower.includes('await') || lower.includes('cancellation')) return 'async';
190
- if (lower.includes('security') || lower.includes('sql') || lower.includes('xss')) return 'security';
191
- if (lower.includes('package') || lower.includes('nuget')) return 'packages';
192
- if (lower.includes('design') || lower.includes('css') || lower.includes('naming')) return 'design-system';
193
- return null;
194
- }