@polymorphism-tech/morph-spec 4.21.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 (213) 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 -1183
  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 -28
  33. package/framework/hooks/shared/state-reader.js +4 -5
  34. package/framework/{skills/level-0-meta/mcp-registry.json → mcp/registry.json} +254 -210
  35. package/framework/phases.json +40 -114
  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/standards/STANDARDS.json +32 -0
  47. package/framework/standards/frontend/design-system/ai-image-generation.md +244 -0
  48. package/framework/standards/integration/mcp/mcp-tools.md +48 -0
  49. package/framework/templates/CLAUDE.md.core.guide.md +29 -0
  50. package/framework/templates/CLAUDE.md.core.template +22 -0
  51. package/framework/templates/REGISTRY.json +4 -4
  52. package/framework/workflows/configs/fast-track.json +5 -10
  53. package/framework/workflows/configs/full.json +4 -8
  54. package/framework/workflows/configs/quick.json +32 -0
  55. package/framework/workflows/configs/standard.json +57 -64
  56. package/package.json +7 -6
  57. package/src/commands/mcp/mcp-setup.js +58 -31
  58. package/src/commands/project/doctor.js +69 -70
  59. package/src/commands/project/init.js +26 -5
  60. package/src/commands/project/status.js +18 -19
  61. package/src/commands/state/approve.js +1 -1
  62. package/src/commands/validation/validate-feature.js +18 -112
  63. package/src/core/paths/output-schema.js +133 -151
  64. package/src/core/state/phase-state-machine.js +10 -24
  65. package/src/core/state/state-manager.js +17 -17
  66. package/src/core/workflows/workflow-detector.js +214 -31
  67. package/src/core/workflows/workflow-router.js +204 -0
  68. package/src/dashboard/app/pages/AgentMap.jsx +32 -33
  69. package/src/dashboard/app/pages/LiveState.jsx +1 -1
  70. package/src/dashboard/app/pages/PhasePipeline.jsx +2 -4
  71. package/src/dashboard/app/pages/SkillsLibrary.jsx +2 -3
  72. package/src/dashboard/app/pages/Workflows.jsx +2 -2
  73. package/src/dashboard/scanner/agents-scanner.js +30 -35
  74. package/src/lib/agents/metrics-aggregator.js +1 -1
  75. package/src/lib/agents/output-collector.js +7 -8
  76. package/src/lib/context/context-builder.js +289 -0
  77. package/src/lib/detectors/claude-config-detector.js +19 -3
  78. package/src/lib/generators/context-generator.js +20 -42
  79. package/src/lib/generators/index.js +1 -0
  80. package/src/lib/generators/mandate-generator.js +512 -0
  81. package/src/lib/generators/metadata-extractor.js +1 -1
  82. package/src/lib/generators/recap-generator.js +3 -4
  83. package/src/lib/installers/mcp-installer.js +300 -92
  84. package/src/lib/phase-chain/phase-validator.js +36 -40
  85. package/src/lib/stack/stack-profile.js +74 -91
  86. package/src/lib/tasks/task-parser.js +1 -1
  87. package/src/lib/tasks/wave-resolver.js +353 -0
  88. package/src/lib/validators/checkpoint-persistence.js +2 -2
  89. package/src/lib/validators/content/cross-phase-validator.js +281 -0
  90. package/src/lib/validators/content/index.js +1 -0
  91. package/src/lib/validators/shared/emit-validator-dispatch.js +6 -56
  92. package/src/scripts/global-install.js +2 -27
  93. package/src/utils/agents-installer.js +224 -447
  94. package/src/utils/claude-settings-manager.js +57 -15
  95. package/src/utils/file-copier.js +1 -0
  96. package/src/utils/hooks-installer.js +15 -117
  97. package/src/utils/skills-installer.js +12 -18
  98. package/bin/detect-agents.js +0 -226
  99. package/bin/task-manager.js +0 -1036
  100. package/framework/agents/README.md +0 -13
  101. package/framework/agents/ai-agents/ai-system-architect.md +0 -194
  102. package/framework/agents/ai-agents/vector-search-expert.md +0 -247
  103. package/framework/agents/architecture/architecture-expert.md +0 -98
  104. package/framework/agents/architecture/morph-spec-architect.md +0 -66
  105. package/framework/agents/architecture/packages-validator.md +0 -89
  106. package/framework/agents/architecture/security-expert.md +0 -79
  107. package/framework/agents/architecture/seo-growth-hacker.md +0 -168
  108. package/framework/agents/architecture/standards-architect.md +0 -153
  109. package/framework/agents/backend/dotnet-senior.md +0 -163
  110. package/framework/agents/backend/ef-modeler.md +0 -119
  111. package/framework/agents/backend/hangfire-orchestrator.md +0 -133
  112. package/framework/agents/backend/ms-agent-expert.md +0 -55
  113. package/framework/agents/backend/neon-expert.md +0 -379
  114. package/framework/agents/frontend/css-specialist.md +0 -89
  115. package/framework/agents/frontend/design-system-validator.md +0 -100
  116. package/framework/agents/frontend/gsap-animator.md +0 -131
  117. package/framework/agents/frontend/nextjs-component-validator.md +0 -97
  118. package/framework/agents/frontend/nextjs-expert.md +0 -201
  119. package/framework/agents/frontend/ui-designer.md +0 -259
  120. package/framework/agents/infrastructure/infra-architect.md +0 -215
  121. package/framework/agents/integrations/asaas-financial.md +0 -138
  122. package/framework/agents/integrations/neon-auth.md +0 -151
  123. package/framework/agents/integrations/resend-email.md +0 -127
  124. package/framework/hooks/claude-code/post-tool-use/agent-complete.js +0 -122
  125. package/framework/hooks/claude-code/post-tool-use/dispatch.js +0 -363
  126. package/framework/hooks/claude-code/post-tool-use/handle-tool-failure.js +0 -78
  127. package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +0 -194
  128. package/framework/hooks/claude-code/pre-compact/save-morph-context.js +0 -199
  129. package/framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js +0 -55
  130. package/framework/hooks/claude-code/session-start/inject-morph-context.js +0 -323
  131. package/framework/hooks/claude-code/session-start/post-compact-restore.js +0 -41
  132. package/framework/hooks/claude-code/session-start/resume-context.js +0 -77
  133. package/framework/hooks/claude-code/teammate-idle/teammate-idle.js +0 -90
  134. package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +0 -114
  135. package/framework/hooks/shared/compact-restore.js +0 -123
  136. package/framework/hooks/shared/dispatch-helpers.js +0 -147
  137. package/framework/hooks/shared/skill-reminder-helpers.js +0 -79
  138. package/framework/hooks/shared/stale-task-reset.js +0 -57
  139. package/framework/hooks/shared/worktree-helpers.js +0 -75
  140. package/framework/skills/level-0-meta/README.md +0 -7
  141. package/framework/skills/level-0-meta/morph-agent-teams/SKILL.md +0 -472
  142. package/framework/skills/level-0-meta/morph-brainstorming/SKILL.md +0 -174
  143. package/framework/skills/level-0-meta/morph-brainstorming/references/proposal-example.md +0 -138
  144. package/framework/skills/level-0-meta/morph-checklist/SKILL.md +0 -145
  145. package/framework/skills/level-0-meta/morph-code-review/SKILL.md +0 -250
  146. package/framework/skills/level-0-meta/morph-code-review/references/review-example.md +0 -164
  147. package/framework/skills/level-0-meta/morph-code-review/references/review-guidelines.md +0 -100
  148. package/framework/skills/level-0-meta/morph-code-review/scripts/scan-csharp.mjs +0 -151
  149. package/framework/skills/level-0-meta/morph-code-review-nextjs/SKILL.md +0 -165
  150. package/framework/skills/level-0-meta/morph-code-review-nextjs/references/review-example-nextjs.md +0 -254
  151. package/framework/skills/level-0-meta/morph-code-review-nextjs/scripts/scan-nextjs.mjs +0 -189
  152. package/framework/skills/level-0-meta/morph-frontend-review/SKILL.md +0 -344
  153. package/framework/skills/level-0-meta/morph-frontend-review/scripts/scan-accessibility.mjs +0 -376
  154. package/framework/skills/level-0-meta/morph-init/SKILL.md +0 -537
  155. package/framework/skills/level-0-meta/morph-parallel-dispatch/SKILL.md +0 -225
  156. package/framework/skills/level-0-meta/morph-post-implementation/SKILL.md +0 -382
  157. package/framework/skills/level-0-meta/morph-post-implementation/scripts/detect-dev-server.mjs +0 -153
  158. package/framework/skills/level-0-meta/morph-post-implementation/scripts/detect-stack.mjs +0 -234
  159. package/framework/skills/level-0-meta/morph-simulation-checklist/SKILL.md +0 -106
  160. package/framework/skills/level-0-meta/morph-tool-usage-guide/SKILL.md +0 -243
  161. package/framework/skills/level-0-meta/morph-tool-usage-guide/references/tools-per-phase.md +0 -169
  162. package/framework/skills/level-0-meta/morph-verification-before-completion/SKILL.md +0 -148
  163. package/framework/skills/level-0-meta/morph-verification-before-completion/scripts/check-phase-outputs.mjs +0 -110
  164. package/framework/skills/level-1-workflows/README.md +0 -7
  165. package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +0 -269
  166. package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +0 -453
  167. package/framework/skills/level-1-workflows/morph-phase-design/references/architecture-analysis-guide.md +0 -44
  168. package/framework/skills/level-1-workflows/morph-phase-design/references/spec-authoring-guide.md +0 -55
  169. package/framework/skills/level-1-workflows/morph-phase-design/references/spec-example.md +0 -253
  170. package/framework/skills/level-1-workflows/morph-phase-implement/.morph/logs/activity.json +0 -38
  171. package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +0 -493
  172. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/code-quality-reviewer-prompt.md +0 -50
  173. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/implementer-prompt.md +0 -46
  174. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/spec-reviewer-prompt.md +0 -47
  175. package/framework/skills/level-1-workflows/morph-phase-implement/references/recap-example.md +0 -132
  176. package/framework/skills/level-1-workflows/morph-phase-implement/references/vsa-implementation-guide.md +0 -92
  177. package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +0 -257
  178. package/framework/skills/level-1-workflows/morph-phase-tasks/.morph/logs/activity.json +0 -14
  179. package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +0 -291
  180. package/framework/skills/level-1-workflows/morph-phase-tasks/references/task-planning-patterns.md +0 -143
  181. package/framework/skills/level-1-workflows/morph-phase-tasks/references/tasks-example.md +0 -404
  182. package/framework/skills/level-1-workflows/morph-phase-tasks/scripts/validate-tasks.mjs +0 -112
  183. package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +0 -788
  184. package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +0 -147
  185. package/framework/squad-templates/backend-only.json +0 -47
  186. package/framework/squad-templates/frontend-only.json +0 -56
  187. package/framework/squad-templates/full-stack.json +0 -74
  188. package/src/commands/agents/agent-metrics.js +0 -174
  189. package/src/commands/agents/audit-dispatch.js +0 -82
  190. package/src/commands/agents/detect-agents.js +0 -275
  191. package/src/commands/agents/dispatch-agents.js +0 -461
  192. package/src/commands/agents/index.js +0 -13
  193. package/src/commands/agents/manage-agents.js +0 -352
  194. package/src/commands/agents/project-metrics.js +0 -74
  195. package/src/commands/phase/phase-reset.js +0 -74
  196. package/src/commands/scope/escalate.js +0 -217
  197. package/src/commands/state/advance-phase.js +0 -600
  198. package/src/commands/state/phase-runner.js +0 -215
  199. package/src/commands/state/state.js +0 -444
  200. package/src/commands/task/expand.js +0 -100
  201. package/src/commands/tasks/index.js +0 -4
  202. package/src/commands/tasks/task.js +0 -105
  203. package/src/commands/templates/index.js +0 -5
  204. package/src/commands/templates/template-render.js +0 -93
  205. package/src/commands/trust/trust.js +0 -118
  206. package/src/lib/agents/agents-manager.js +0 -349
  207. package/src/lib/build/build-runner.js +0 -99
  208. package/src/lib/checkpoints/checkpoint-hooks.js +0 -111
  209. package/src/lib/contracts/sprint-contract-generator.js +0 -137
  210. package/src/lib/scope/impact-analyzer.js +0 -106
  211. package/src/lib/trust/trust-manager.js +0 -157
  212. package/src/utils/tier-helpers.js +0 -24
  213. /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
- }