@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,199 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * PreCompact Hook: Inject MORPH Context into Compact Summary
5
- *
6
- * Event: PreCompact
7
- *
8
- * Injects a rich morph-spec state snapshot as additionalContext so that
9
- * Claude's compacted summary includes feature state, task progress, outputs,
10
- * and next steps. Ensures the session resumes with full morph awareness
11
- * without having to re-read all the spec files.
12
- *
13
- * Also saves snapshot to .morph/memory/ for SessionStart to reference.
14
- *
15
- * Fail-open: exits 0 on any error.
16
- */
17
-
18
- import { writeFileSync, mkdirSync, existsSync, readFileSync } from 'fs';
19
- import { join } from 'path';
20
- import {
21
- stateExists, loadState, getActiveFeature,
22
- derivePhaseForFeature, getPendingGates, getOutputs,
23
- } from '../../shared/state-reader.js';
24
- import { injectContext, pass } from '../../shared/hook-response.js';
25
- import { logHookActivity } from '../../shared/activity-logger.js';
26
- import { buildRichContext, DECISIONS_MAX_CHARS, MAX_PENDING_TASKS } from '../../shared/compact-restore.js';
27
-
28
- const PHASE_POSITIONS = {
29
- proposal: 1,
30
- uiux: 2, design: 2,
31
- plan: 3,
32
- tasks: 4,
33
- implement: 5, sync: 5,
34
- };
35
- const PIPELINE_TOTAL = 5;
36
-
37
- const NEXT_STEP_HINT = {
38
- proposal: (n) => `Continue spec pipeline: /morph-proposal ${n}`,
39
- design: (n) => `Approve design gate: morph-spec approve ${n} design`,
40
- uiux: (n) => `Approve UI gate: morph-spec approve ${n} uiux`,
41
- tasks: (n) => `Approve tasks gate: morph-spec approve ${n} tasks`,
42
- implement: (n) => `Continue implementation: morph-spec task start ${n} <next-task-id>`,
43
- };
44
-
45
- try {
46
- if (!stateExists()) pass();
47
-
48
- const state = loadState();
49
- if (!state) pass();
50
-
51
- const active = getActiveFeature();
52
- const cwd = process.cwd();
53
-
54
- // ── Disk snapshot (for SessionStart restore) ──────────────────────────────
55
- const snapshot = {
56
- timestamp: new Date().toISOString(),
57
- event: 'pre-compact',
58
- activeFeature: active ? active.name : null,
59
- features: {},
60
- };
61
-
62
- for (const [name, feature] of Object.entries(state.features || {})) {
63
- snapshot.features[name] = {
64
- phase: feature.phase || derivePhaseForFeature(name),
65
- status: feature.status,
66
- workflow: feature.workflow,
67
- tasks: feature.tasks,
68
- approvalGates: Object.fromEntries(
69
- Object.entries(feature.approvalGates || {}).map(([gate, data]) => [gate, data.approved])
70
- ),
71
- };
72
- }
73
-
74
- // ── Enrich snapshot with richContext (decisions + taskList) ───────────────
75
- if (active) {
76
- try {
77
- const { name: activeName, feature: activeFeature } = active;
78
- const activePhase = derivePhaseForFeature(activeName);
79
- let decisionsContent = '';
80
- const decisionsPath = join(cwd, `.morph/features/${activeName}/2-design/decisions.md`);
81
- if (existsSync(decisionsPath)) {
82
- try { decisionsContent = readFileSync(decisionsPath, 'utf-8'); } catch { /* ignore */ }
83
- }
84
- let specContent = '';
85
- const specPath2 = join(cwd, `.morph/features/${activeName}/2-design/spec.md`);
86
- if (existsSync(specPath2)) {
87
- try { specContent = readFileSync(specPath2, 'utf-8'); } catch { /* ignore */ }
88
- }
89
- snapshot.richContext = buildRichContext(activeFeature, activePhase, decisionsContent, specContent);
90
- } catch { /* fail-open: richContext is optional */ }
91
- }
92
- // ─────────────────────────────────────────────────────────────────────────
93
-
94
- const memoryDir = join(cwd, '.morph', 'memory');
95
- if (!existsSync(memoryDir)) mkdirSync(memoryDir, { recursive: true });
96
- const ts = new Date().toISOString().replace(/[:.]/g, '-');
97
- writeFileSync(join(memoryDir, `pre-compact-${ts}.json`), JSON.stringify(snapshot, null, 2), 'utf-8');
98
-
99
- // ── Nothing more to inject if no active feature ───────────────────────────
100
- if (!active) pass();
101
-
102
- const { name, feature } = active;
103
- const phase = derivePhaseForFeature(name);
104
- const pos = PHASE_POSITIONS[phase];
105
- const phaseLabel = pos ? `${phase} (${pos}/${PIPELINE_TOTAL})` : phase;
106
-
107
- const lines = [
108
- '=== MORPH-SPEC COMPACT CONTEXT ===',
109
- `Preserve this section verbatim in the compact summary.`,
110
- '',
111
- `Feature : ${name}`,
112
- `Phase : ${phaseLabel}`,
113
- `Status : ${feature.status} | Workflow: ${feature.workflow || 'auto'}`,
114
- ];
115
-
116
- // Task progress
117
- const tasks = feature.tasks || {};
118
- const done = tasks.completed || 0;
119
- const total = tasks.total || 0;
120
- if (total > 0) {
121
- const pct = Math.round(done / total * 100);
122
- lines.push(`Tasks : ${done}/${total} completed (${pct}%)`);
123
- }
124
-
125
- // In-progress + next pending tasks from taskList
126
- const taskList = Array.isArray(feature.taskList) ? feature.taskList : [];
127
- const inProg = taskList.filter(t => t.status === 'in_progress');
128
- const pending = taskList.filter(t => t.status === 'pending').slice(0, MAX_PENDING_TASKS);
129
-
130
- if (inProg.length > 0) {
131
- lines.push('');
132
- lines.push('In progress:');
133
- for (const t of inProg) lines.push(` [${t.id}] ${t.title}`);
134
- }
135
- if (pending.length > 0) {
136
- lines.push('');
137
- lines.push('Next pending tasks:');
138
- for (const t of pending) lines.push(` [${t.id}] ${t.title}`);
139
- if (taskList.filter(t => t.status === 'pending').length > MAX_PENDING_TASKS) {
140
- lines.push(` ... (see tasks.md for full list)`);
141
- }
142
- }
143
-
144
- // Created outputs
145
- const outputs = getOutputs(name) || {};
146
- const created = Object.entries(outputs).filter(([, o]) => o.created);
147
- const missing = Object.entries(outputs).filter(([, o]) => !o.created);
148
- if (created.length > 0) {
149
- lines.push('');
150
- lines.push('Created outputs:');
151
- for (const [type, o] of created) lines.push(` ✓ ${type}: ${o.path}`);
152
- }
153
- if (missing.length > 0) {
154
- lines.push('Pending outputs:');
155
- for (const [type, o] of missing) lines.push(` ✗ ${type}: ${o.path}`);
156
- }
157
-
158
- // Active agents
159
- const agents = feature.activeAgents || [];
160
- if (agents.length > 0) {
161
- lines.push('');
162
- lines.push(`Active agents: ${agents.join(', ')}`);
163
- }
164
-
165
- // Pending approval gates
166
- const pendingGates = getPendingGates(name);
167
- if (pendingGates.length > 0) {
168
- lines.push(`Pending approvals: ${pendingGates.join(', ')}`);
169
- }
170
-
171
- // Key decisions (truncated to stay within context budget)
172
- const decisionsPath = join(cwd, `.morph/features/${name}/2-design/decisions.md`);
173
- if (existsSync(decisionsPath)) {
174
- try {
175
- const raw = readFileSync(decisionsPath, 'utf-8');
176
- const content = raw.length > DECISIONS_MAX_CHARS
177
- ? raw.slice(0, DECISIONS_MAX_CHARS) + '\n[... truncated — full file at decisions.md]'
178
- : raw;
179
- lines.push('');
180
- lines.push('Key decisions (decisions.md):');
181
- lines.push(content);
182
- } catch { /* non-blocking */ }
183
- }
184
-
185
- // Next step
186
- const hintFn = NEXT_STEP_HINT[phase];
187
- if (hintFn) {
188
- lines.push('');
189
- lines.push(`Next step: ${hintFn(name)}`);
190
- }
191
-
192
- lines.push('');
193
- lines.push('=== END MORPH CONTEXT ===');
194
-
195
- logHookActivity('save-morph-context', 'PreCompact', 'context_saved');
196
- injectContext(lines.join('\n'));
197
- } catch {
198
- process.exit(0);
199
- }
@@ -1,55 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * PreToolUse Hook: Advisory Task Tracking Guard
5
- *
6
- * Event: PreToolUse | Matcher: Write|Edit
7
- *
8
- * During implement phase, injects a warning if no task is currently in_progress.
9
- * Advisory only (type: approve with context) — never blocks.
10
- *
11
- * Fail-open: exits 0 on any error.
12
- */
13
-
14
- import { readStdin } from '../../shared/stdin-reader.js';
15
- import { stateExists, loadState, getActiveFeature, derivePhaseForFeature } from '../../shared/state-reader.js';
16
- import { approve, pass } from '../../shared/hook-response.js';
17
- import { getFilePath } from '../../shared/payload-utils.js';
18
-
19
- try {
20
- if (!stateExists()) pass();
21
-
22
- const payload = await readStdin();
23
- if (!payload) pass();
24
-
25
- const filePath = getFilePath(payload);
26
- if (!filePath) pass();
27
-
28
- // Skip files inside .morph/ — internal framework writes don't need task tracking
29
- if (filePath.includes('.morph/') || filePath.includes('.morph\\')) pass();
30
-
31
- // Only check during implement phase
32
- const active = getActiveFeature();
33
- if (!active) pass();
34
-
35
- const { name, feature } = active;
36
- const phase = feature.phase || derivePhaseForFeature(name);
37
- if (phase !== 'implement') pass();
38
-
39
- // Check if any task is in_progress (read directly from taskList)
40
- if (feature.taskList?.some(t => t.status === 'in_progress')) pass();
41
- // Fallback: legacy counter check
42
- if (feature.tasks?.inProgress > 0) pass();
43
-
44
- // No task in_progress — inject advisory warning
45
- approve(
46
- `[morph-spec] WARNING: No active task tracking detected during implement phase.\n` +
47
- `Before writing code, start a task:\n` +
48
- ` morph-spec task start ${name} <id> # mark it in progress\n` +
49
- `Task tracking ensures validators run, checkpoints fire, and progress is recorded.\n` +
50
- `If a task is more complex than expected, use: morph-spec task expand ${name} <id> --into "T00Xa: Sub" "T00Xb: Sub"`
51
- );
52
- } catch {
53
- // Fail-open
54
- process.exit(0);
55
- }
@@ -1,323 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * SessionStart Hook: Inject MORPH-SPEC Context
5
- *
6
- * Event: SessionStart | Matcher: startup|resume|compact
7
- *
8
- * Reads state.json and injects a summary as additionalContext so Claude
9
- * knows the current morph-spec state at session start.
10
- *
11
- * Fail-open: exits 0 on any error.
12
- */
13
-
14
- import { loadState, getActiveFeature, getPendingGates, getMissingOutputs, derivePhaseForFeature, getTaskStats } from '../../shared/state-reader.js';
15
- import { loadSessionSnapshot, formatPreviousSession } from '../../shared/session-snapshot.js';
16
- import { stateExists } from '../../shared/state-reader.js';
17
- import { injectContext, pass } from '../../shared/hook-response.js';
18
- import { resetActivity, logHookActivity, archiveSession, logEvent } from '../../shared/activity-logger.js';
19
- import { readFileSync, existsSync, writeFileSync, renameSync, unlinkSync, statSync } from 'fs';
20
- import { join } from 'path';
21
- import { resetStaleTasks } from '../../shared/stale-task-reset.js';
22
- import { execSync as _execSync } from 'child_process';
23
- import { parseWorktreeResult, buildWorktreeContextLine, buildWorktreeResumeContext } from '../../shared/worktree-helpers.js';
24
-
25
- const DEFAULT_SPEC_MAX_CHARS = 6000;
26
-
27
- function getSpecMaxChars() {
28
- try {
29
- const configPath = join(process.cwd(), '.morph/config/config.json');
30
- if (!existsSync(configPath)) return DEFAULT_SPEC_MAX_CHARS;
31
- const cfg = JSON.parse(readFileSync(configPath, 'utf-8'));
32
- return cfg?.hooks?.specInjectionLimit ?? DEFAULT_SPEC_MAX_CHARS;
33
- } catch {
34
- return DEFAULT_SPEC_MAX_CHARS;
35
- }
36
- }
37
-
38
- function getProjectConfig() {
39
- try {
40
- const configPath = join(process.cwd(), '.morph/config/config.json');
41
- if (!existsSync(configPath)) return null;
42
- return JSON.parse(readFileSync(configPath, 'utf-8'));
43
- } catch {
44
- return null;
45
- }
46
- }
47
-
48
- const SPEC_MAX_CHARS = getSpecMaxChars();
49
-
50
- /**
51
- * Atomic state save for hook context.
52
- * Same approach as state-manager.js saveState — write to tmp, then rename.
53
- * Fail-open: any error is silently ignored.
54
- * @param {Object} state - Full state object
55
- * @param {string} cwd - Project root path
56
- */
57
- function saveStateSync(state, cwd) {
58
- const statePath = join(cwd, '.morph/state.json');
59
- const tmpPath = `${statePath}.tmp.hook.${process.pid}`;
60
- try {
61
- state.metadata = state.metadata || {};
62
- state.metadata.lastUpdated = new Date().toISOString();
63
- writeFileSync(tmpPath, JSON.stringify(state, null, 2), 'utf8');
64
- renameSync(tmpPath, statePath);
65
- } catch {
66
- try { unlinkSync(tmpPath); } catch { /* ignore cleanup errors */ }
67
- }
68
- }
69
-
70
- try {
71
- if (!stateExists()) pass();
72
-
73
- const state = loadState();
74
- if (!state?.features || Object.keys(state.features).length === 0) pass();
75
-
76
- const active = getActiveFeature();
77
-
78
- // Archive previous session log before resetting
79
- const activeFeatureName = active?.name || '';
80
- const activePhase = active ? derivePhaseForFeature(active.name) : '';
81
- archiveSession();
82
-
83
- // Reset activity log for new session
84
- resetActivity(new Date().toISOString(), activeFeatureName, activePhase);
85
- logEvent('session_start', { feature: activeFeatureName, phase: activePhase });
86
- logHookActivity('inject-morph-context', 'SessionStart', 'ok');
87
-
88
- // ── Stale task cleanup ──────────────────────────────────────────────────────
89
- // Reset any in_progress tasks older than 1 hour — they are orphans from an
90
- // interrupted session and are no longer being actively implemented.
91
- const staleResetLog = resetStaleTasks(state);
92
- if (staleResetLog.length > 0) {
93
- saveStateSync(state, process.cwd());
94
- }
95
- // ────────────────────────────────────────────────────────────────────────────
96
-
97
- const lines = ['MORPH-SPEC Status:'];
98
-
99
- // ── Context freshness check ─────────────────────────────────────────────
100
- // If .morph/context/README.md is missing or older than 24h, suggest refresh.
101
- try {
102
- const contextReadmePath = join(process.cwd(), '.morph/context/README.md');
103
- let contextStale = false;
104
- if (!existsSync(contextReadmePath)) {
105
- contextStale = true;
106
- } else {
107
- const stat = statSync(contextReadmePath);
108
- const ageMs = Date.now() - stat.mtimeMs;
109
- const TWENTY_FOUR_HOURS = 24 * 60 * 60 * 1000;
110
- contextStale = ageMs > TWENTY_FOUR_HOURS;
111
- }
112
- if (contextStale) {
113
- lines.push('');
114
- lines.push('⚠ Architecture context is stale or missing. Run: morph-spec context refresh');
115
- }
116
- } catch {
117
- // Fail-open — context freshness check must never block the session
118
- }
119
- // ────────────────────────────────────────────────────────────────────────
120
-
121
- if (staleResetLog.length > 0) {
122
- lines.push(`⚠️ ${staleResetLog.length} task(s) auto-reset in_progress → pending (orphaned from previous session): ${staleResetLog.join(', ')}`);
123
- }
124
-
125
- if (active) {
126
- const { name, feature } = active;
127
- const phase = derivePhaseForFeature(name);
128
- lines.push(`- Active feature: ${name} (phase: ${phase}, workflow: ${feature.workflow || 'auto'})`);
129
- lines.push(`- Status: ${feature.status}`);
130
-
131
- // Task progress (read directly from taskList)
132
- const stats = getTaskStats(feature);
133
- if (stats.total > 0) {
134
- lines.push(`- Tasks: ${stats.completed}/${stats.total} completed`);
135
- }
136
-
137
- // Pending approvals
138
- const pending = getPendingGates(name);
139
- if (pending.length > 0) {
140
- lines.push(`- Pending approvals: ${pending.join(', ')}`);
141
- }
142
-
143
- // Next required output
144
- const missing = getMissingOutputs(name);
145
- if (missing.length > 0) {
146
- const next = missing[0];
147
- lines.push(`- Next required output: ${next.type} → ${next.path}`);
148
- }
149
-
150
- // Checkpoints
151
- if (feature.checkpoints?.length > 0) {
152
- const lastCp = feature.checkpoints[feature.checkpoints.length - 1];
153
- lines.push(`- Last checkpoint: #${lastCp.checkpointNum} (${lastCp.passed ? 'passed' : 'failed'})`);
154
- }
155
-
156
- // Trust level visibility — warn when low trust may block approval gates
157
- const APPROVAL_GATE_PHASES = new Set(['proposal', 'design', 'tasks']);
158
- if (APPROVAL_GATE_PHASES.has(phase)) {
159
- const config = getProjectConfig();
160
- const trustLevel = config?.trust?.level || config?.trustLevel || 'low';
161
- const workflow = feature.workflow || config?.workflow;
162
- if (trustLevel !== 'high' && trustLevel !== 'maximum') {
163
- lines.push('');
164
- lines.push(`⚠ Trust level: ${trustLevel} — approval gates require manual approval`);
165
- lines.push(` To auto-approve: morph-spec trust set ${name} high`);
166
- }
167
- if (!workflow && config?.workflowDetection?.confidence === 0) {
168
- lines.push('');
169
- lines.push('ℹ Workflow not detected (confidence: 0). Set manually in .morph/config/config.json → workflow');
170
- }
171
- }
172
-
173
- // Active feature spec (truncated for context budget)
174
- const specPath = join(process.cwd(), `.morph/features/${name}/2-design/spec.md`);
175
- if (existsSync(specPath)) {
176
- try {
177
- const specContent = readFileSync(specPath, 'utf-8');
178
- const truncated = specContent.length > SPEC_MAX_CHARS
179
- ? specContent.slice(0, SPEC_MAX_CHARS) + `\n\n[... spec truncated — full file at .morph/features/${name}/2-design/spec.md]`
180
- : specContent;
181
- lines.push('');
182
- lines.push(`--- Active feature spec (${name}/2-design/spec.md) ---`);
183
- lines.push(truncated);
184
- lines.push('--- End spec ---');
185
- } catch {
186
- // Non-blocking: skip spec injection on read error
187
- }
188
- }
189
-
190
- // ── Worktree context injection ─────────────────────────────────────────────
191
- // For the active feature, detect existing worktree from state metadata and
192
- // inject MANDATORY instructions to work inside it.
193
- // The worktree is created by `morph-proposal` Step 0 or `morph-apply` Step 0.
194
- // This hook only injects the context — it does NOT create worktrees.
195
- // Fail-open: any error is silently ignored.
196
- try {
197
- const worktreeMeta = feature.worktree;
198
- if (worktreeMeta?.path) {
199
- const contextLine = buildWorktreeResumeContext(worktreeMeta, name);
200
- if (contextLine) {
201
- lines.push('');
202
- lines.push(contextLine);
203
- }
204
- }
205
- } catch {
206
- // Fail-open — worktree context must never block the session
207
- }
208
- // ────────────────────────────────────────────────────────────────────────────
209
-
210
- // ── Previous session context ──────────────────────────────────────────────
211
- // Inject summary from previous session for "getting up to speed"
212
- try {
213
- const snapshot = loadSessionSnapshot(process.cwd());
214
- const previousSessionText = formatPreviousSession(snapshot);
215
- if (previousSessionText) {
216
- lines.push('');
217
- lines.push(previousSessionText);
218
- }
219
- } catch {
220
- // Fail-open — previous session context must never block
221
- }
222
- // ────────────────────────────────────────────────────────────────────────
223
- } else {
224
- // Show summary of all features
225
- const featureNames = Object.keys(state.features);
226
- lines.push(`- Features: ${featureNames.length} (${featureNames.join(', ')})`);
227
-
228
- for (const [fname, feat] of Object.entries(state.features)) {
229
- const fPhase = derivePhaseForFeature(fname);
230
- lines.push(` - ${fname}: phase=${fPhase}, status=${feat.status}`);
231
- }
232
- }
233
-
234
- // ── MCP status injection ────────────────────────────────────────────────────
235
- // Cross-reference configured MCPs with phase recommendations from mcp-registry.json.
236
- // Fail-open: all inside try/catch, no crash on missing files.
237
- try {
238
- const currentPhase = active ? derivePhaseForFeature(active.name) : '';
239
- if (currentPhase) {
240
- // Read configured MCPs from settings
241
- const configuredMcps = new Set();
242
- const settingsFiles = [
243
- join(process.cwd(), '.claude', 'settings.local.json'),
244
- join(process.cwd(), '.claude', 'settings.json'),
245
- ];
246
- for (const sf of settingsFiles) {
247
- if (existsSync(sf)) {
248
- try {
249
- const s = JSON.parse(readFileSync(sf, 'utf8'));
250
- for (const name of Object.keys(s.mcpServers || {})) {
251
- configuredMcps.add(name.toLowerCase());
252
- }
253
- } catch { /* ignore */ }
254
- }
255
- }
256
-
257
- // Read recommended MCPs from phases.json or mcp-registry.json
258
- let recommended = [];
259
- const registryPaths = [
260
- join(process.cwd(), 'framework', 'skills', 'level-0-meta', 'mcp-registry.json'),
261
- join(process.cwd(), '.morph', 'framework', 'skills', 'level-0-meta', 'mcp-registry.json'),
262
- ];
263
- for (const rp of registryPaths) {
264
- if (existsSync(rp)) {
265
- try {
266
- const registry = JSON.parse(readFileSync(rp, 'utf8'));
267
- recommended = registry.phaseMatrix?.[currentPhase] || [];
268
- break;
269
- } catch { /* ignore */ }
270
- }
271
- }
272
-
273
- if (recommended.length > 0) {
274
- const mcpStatus = recommended.map(name => {
275
- const isConfigured = configuredMcps.has(name.toLowerCase());
276
- return `${name} ${isConfigured ? '✓' : '✗ (recommended)'}`;
277
- });
278
- lines.push('');
279
- lines.push(`MCPs: ${mcpStatus.join(', ')}`);
280
- }
281
- }
282
- } catch {
283
- // Fail-open — MCP status must never block the session
284
- }
285
- // ────────────────────────────────────────────────────────────────────────────
286
-
287
- // ── CLI availability detection ─────────────────────────────────────────────
288
- // Check which development CLIs are available for the current phase.
289
- // Uses execSync with short timeout — fail-open on any error.
290
- try {
291
- const currentPhaseForCli = active ? derivePhaseForFeature(active.name) : '';
292
- if (currentPhaseForCli) {
293
- const cliChecks = [
294
- { name: 'docker', cmd: 'docker --version', phases: ['design', 'implement'] },
295
- { name: 'neonctl', cmd: 'neonctl --version', phases: ['design', 'implement'] },
296
- { name: 'psql', cmd: 'psql --version', phases: ['design', 'implement'] },
297
- { name: 'az', cmd: 'az version --output none', phases: ['implement'] },
298
- { name: 'gh', cmd: 'gh --version', phases: ['design', 'tasks', 'implement'] },
299
- ];
300
-
301
- const relevant = cliChecks.filter(c => c.phases.includes(currentPhaseForCli));
302
- if (relevant.length > 0) {
303
- const cliStatus = relevant.map(c => {
304
- try {
305
- _execSync(c.cmd, { stdio: 'pipe', timeout: 5000 });
306
- return `${c.name} ✓`;
307
- } catch {
308
- return `${c.name} ✗`;
309
- }
310
- });
311
- lines.push(`CLIs: ${cliStatus.join(', ')}`);
312
- }
313
- }
314
- } catch {
315
- // Fail-open — CLI detection must never block the session
316
- }
317
- // ────────────────────────────────────────────────────────────────────────────
318
-
319
- injectContext(lines.join('\n'));
320
- } catch {
321
- // Fail-open
322
- process.exit(0);
323
- }
@@ -1,41 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- /**
4
- * SessionStart Hook: Post-Compact Context Restore
5
- *
6
- * Event: SessionStart | Matcher: compact
7
- *
8
- * Fires ONLY when a session starts after context compaction.
9
- * Reads the latest pre-compact memory file and injects the richContext
10
- * block (decisions.md snippet + task list) so Claude resumes with full
11
- * morph awareness even if the compact summary was truncated.
12
- *
13
- * Fail-open: exits 0 on any error.
14
- */
15
-
16
- import { existsSync, readdirSync, readFileSync } from 'fs';
17
- import { join } from 'path';
18
- import { injectContext, pass } from '../../shared/hook-response.js';
19
- import { stateExists } from '../../shared/state-reader.js';
20
- import { buildRestoreBlock, findLatestMemoryFile } from '../../shared/compact-restore.js';
21
-
22
- try {
23
- if (!stateExists()) pass();
24
-
25
- const memoryDir = join(process.cwd(), '.morph', 'memory');
26
- if (!existsSync(memoryDir)) pass();
27
-
28
- const files = readdirSync(memoryDir);
29
- const latest = findLatestMemoryFile(files);
30
- if (!latest) pass();
31
-
32
- const raw = readFileSync(join(memoryDir, latest), 'utf-8');
33
- const snapshot = JSON.parse(raw);
34
-
35
- const block = buildRestoreBlock(snapshot);
36
- if (!block) pass();
37
-
38
- injectContext(block);
39
- } catch {
40
- process.exit(0);
41
- }