@polymorphism-tech/morph-spec 4.8.19 → 4.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (214) hide show
  1. package/CLAUDE.md +21 -0
  2. package/README.md +2 -2
  3. package/bin/morph-spec.js +44 -55
  4. package/bin/task-manager.js +133 -20
  5. package/bin/validate.js +67 -33
  6. package/claude-plugin.json +1 -1
  7. package/docs/CHEATSHEET.md +201 -203
  8. package/docs/QUICKSTART.md +2 -2
  9. package/framework/CLAUDE.md +99 -77
  10. package/framework/agents.json +734 -182
  11. package/framework/commands/commit.md +166 -0
  12. package/framework/commands/morph-apply.md +13 -2
  13. package/framework/commands/morph-archive.md +8 -2
  14. package/framework/commands/morph-infra.md +6 -0
  15. package/framework/commands/morph-preflight.md +6 -0
  16. package/framework/commands/morph-proposal.md +56 -7
  17. package/framework/commands/morph-status.md +6 -0
  18. package/framework/commands/morph-troubleshoot.md +6 -0
  19. package/framework/hooks/claude-code/notification/approval-reminder.js +3 -2
  20. package/framework/hooks/claude-code/post-tool-use/context-refresh.js +1 -1
  21. package/framework/hooks/claude-code/post-tool-use/dispatch.js +155 -32
  22. package/framework/hooks/claude-code/post-tool-use/skill-reminder.js +78 -0
  23. package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +8 -17
  24. package/framework/hooks/claude-code/pre-compact/save-morph-context.js +16 -3
  25. package/framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js +4 -3
  26. package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +4 -3
  27. package/framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js +60 -0
  28. package/framework/hooks/claude-code/session-start/inject-morph-context.js +124 -2
  29. package/framework/hooks/claude-code/session-start/post-compact-restore.js +41 -0
  30. package/framework/hooks/claude-code/statusline.py +76 -30
  31. package/framework/hooks/claude-code/stop/validate-completion.js +2 -15
  32. package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +23 -5
  33. package/framework/hooks/claude-code/user-prompt/set-terminal-title.js +14 -6
  34. package/framework/hooks/shared/activity-logger.js +0 -24
  35. package/framework/hooks/shared/compact-restore.js +100 -0
  36. package/framework/hooks/shared/dispatch-helpers.js +116 -0
  37. package/framework/hooks/shared/phase-utils.js +12 -5
  38. package/framework/hooks/shared/skill-reminder-helpers.js +79 -0
  39. package/framework/hooks/shared/stale-task-reset.js +57 -0
  40. package/framework/hooks/shared/state-reader.js +29 -5
  41. package/framework/hooks/shared/worktree-helpers.js +53 -0
  42. package/framework/phases.json +69 -14
  43. package/framework/rules/morph-workflow.md +88 -86
  44. package/framework/skills/level-0-meta/mcp-registry.json +86 -51
  45. package/framework/skills/level-0-meta/{brainstorming → morph-brainstorming}/SKILL.md +14 -17
  46. package/framework/skills/level-0-meta/morph-checklist/SKILL.md +2 -2
  47. package/framework/skills/level-0-meta/{code-review → morph-code-review}/SKILL.md +2 -2
  48. package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/SKILL.md +163 -163
  49. package/framework/skills/level-0-meta/{frontend-review → morph-frontend-review}/SKILL.md +9 -9
  50. package/framework/skills/level-0-meta/morph-init/SKILL.md +77 -12
  51. package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/SKILL.md +62 -15
  52. package/framework/skills/level-0-meta/morph-replicate/SKILL.md +5 -5
  53. package/framework/skills/level-0-meta/morph-replicate/references/blazor-html-mapping.md +1 -1
  54. package/framework/skills/level-0-meta/{simulation-checklist → morph-simulation-checklist}/SKILL.md +1 -1
  55. package/framework/skills/level-0-meta/{terminal-title → morph-terminal-title}/SKILL.md +2 -2
  56. package/framework/skills/level-0-meta/{tool-usage-guide → morph-tool-usage-guide}/SKILL.md +3 -4
  57. package/framework/skills/level-0-meta/{tool-usage-guide → morph-tool-usage-guide}/references/tools-per-phase.md +7 -7
  58. package/framework/skills/level-0-meta/{verification-before-completion → morph-verification-before-completion}/SKILL.md +2 -2
  59. package/framework/skills/level-0-meta/{verification-before-completion → morph-verification-before-completion}/scripts/check-phase-outputs.mjs +2 -2
  60. package/framework/skills/level-1-workflows/morph-phase-clarify/SKILL.md +238 -0
  61. package/framework/skills/level-1-workflows/{phase-codebase-analysis → morph-phase-codebase-analysis}/SKILL.md +3 -3
  62. package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +507 -0
  63. package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/SKILL.md +168 -27
  64. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/code-quality-reviewer-prompt.md +50 -0
  65. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/implementer-prompt.md +45 -0
  66. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/spec-reviewer-prompt.md +47 -0
  67. package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +254 -0
  68. package/framework/skills/level-1-workflows/{phase-setup → morph-phase-setup}/SKILL.md +50 -3
  69. package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/SKILL.md +48 -11
  70. package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/scripts/validate-tasks.mjs +3 -3
  71. package/framework/skills/level-1-workflows/{phase-uiux → morph-phase-uiux}/SKILL.md +46 -11
  72. package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +97 -0
  73. package/framework/standards/STANDARDS.json +640 -88
  74. package/framework/standards/infrastructure/vercel/vercel-database.md +106 -0
  75. package/framework/standards/integration/mcp/mcp-tools.md +25 -7
  76. package/framework/templates/REGISTRY.json +1825 -1909
  77. package/framework/templates/context/CONTEXT-FEATURE.md +276 -276
  78. package/framework/templates/docs/onboarding.md +3 -7
  79. package/package.json +2 -7
  80. package/src/commands/agents/dispatch-agents.js +104 -6
  81. package/src/commands/mcp/mcp-setup.js +39 -2
  82. package/src/commands/phase/phase-reset.js +74 -0
  83. package/src/commands/project/doctor.js +34 -51
  84. package/src/commands/project/init.js +1 -1
  85. package/src/commands/project/status.js +2 -2
  86. package/src/commands/project/update.js +381 -365
  87. package/src/commands/project/worktree.js +154 -0
  88. package/src/commands/scope/escalate.js +215 -0
  89. package/src/commands/state/advance-phase.js +132 -68
  90. package/src/commands/state/approve.js +2 -2
  91. package/src/commands/state/index.js +7 -8
  92. package/src/commands/state/phase-runner.js +1 -1
  93. package/src/commands/state/state.js +61 -6
  94. package/src/commands/task/expand.js +100 -0
  95. package/src/commands/tasks/task.js +78 -99
  96. package/src/commands/templates/template-render.js +93 -173
  97. package/src/commands/trust/trust.js +26 -21
  98. package/src/core/paths/output-schema.js +19 -3
  99. package/src/core/state/phase-state-machine.js +7 -4
  100. package/src/core/state/state-manager.js +32 -57
  101. package/src/core/workflows/workflow-detector.js +9 -87
  102. package/src/lib/detectors/claude-config-detector.js +93 -347
  103. package/src/lib/detectors/design-system-detector.js +189 -189
  104. package/src/lib/detectors/index.js +155 -57
  105. package/src/lib/generators/context-generator.js +2 -2
  106. package/src/lib/installers/mcp-installer.js +37 -5
  107. package/src/lib/phase-chain/phase-validator.js +336 -0
  108. package/src/lib/scope/impact-analyzer.js +106 -0
  109. package/src/lib/stack/stack-profile.js +88 -0
  110. package/src/lib/tasks/task-classifier.js +16 -0
  111. package/src/lib/tasks/task-parser.js +1 -1
  112. package/src/lib/tasks/test-runner.js +77 -0
  113. package/src/lib/trust/trust-manager.js +32 -144
  114. package/src/lib/validators/shared/emit-validator-dispatch.js +64 -0
  115. package/src/lib/validators/spec-validator.js +58 -4
  116. package/src/lib/validators/validation-runner.js +23 -11
  117. package/src/scripts/setup-infra.js +255 -224
  118. package/src/utils/agents-installer.js +34 -14
  119. package/src/utils/banner.js +1 -1
  120. package/src/utils/claude-settings-manager.js +1 -1
  121. package/src/utils/file-copier.js +1 -1
  122. package/src/utils/hooks-installer.js +272 -8
  123. package/framework/hooks/dev/check-sync-health.js +0 -117
  124. package/framework/hooks/dev/guard-version-numbers.js +0 -57
  125. package/framework/hooks/dev/sync-standards-registry.js +0 -60
  126. package/framework/hooks/dev/sync-template-registry.js +0 -60
  127. package/framework/hooks/dev/validate-skill-format.js +0 -70
  128. package/framework/hooks/dev/validate-standard-format.js +0 -73
  129. package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +0 -190
  130. package/framework/skills/level-1-workflows/phase-design/SKILL.md +0 -366
  131. package/framework/templates/meta-prompts/hops/hop-retry.md +0 -78
  132. package/framework/templates/meta-prompts/hops/hop-validation.md +0 -97
  133. package/framework/templates/meta-prompts/hops/hop-wrapper.md +0 -36
  134. package/framework/workflows/configs/design-impl.json +0 -49
  135. package/framework/workflows/configs/express.json +0 -45
  136. package/framework/workflows/configs/fast-track.json +0 -42
  137. package/framework/workflows/configs/full-morph.json +0 -79
  138. package/framework/workflows/configs/fusion.json +0 -39
  139. package/framework/workflows/configs/long-running.json +0 -33
  140. package/framework/workflows/configs/spec-only.json +0 -43
  141. package/framework/workflows/configs/ui-refresh.json +0 -49
  142. package/framework/workflows/configs/zero-touch.json +0 -82
  143. package/src/commands/project/index.js +0 -8
  144. package/src/commands/project/monitor.js +0 -295
  145. package/src/commands/project/tutorial.js +0 -115
  146. package/src/commands/state/validate-phase.js +0 -238
  147. package/src/commands/templates/generate-contracts.js +0 -445
  148. package/src/core/index.js +0 -10
  149. package/src/core/orchestrator.js +0 -171
  150. package/src/core/registry/command-registry.js +0 -28
  151. package/src/core/registry/index.js +0 -8
  152. package/src/core/registry/validator-registry.js +0 -204
  153. package/src/core/state/index.js +0 -8
  154. package/src/core/templates/index.js +0 -9
  155. package/src/core/templates/template-data-sources.js +0 -325
  156. package/src/core/templates/template-validator.js +0 -296
  157. package/src/core/workflows/index.js +0 -7
  158. package/src/generator/config-generator.js +0 -206
  159. package/src/generator/templates/config.json.template +0 -40
  160. package/src/generator/templates/project.md.template +0 -67
  161. package/src/lib/agents/micro-agent-factory.js +0 -161
  162. package/src/lib/analysis/complexity-analyzer.js +0 -441
  163. package/src/lib/analysis/index.js +0 -7
  164. package/src/lib/analytics/analytics-engine.js +0 -345
  165. package/src/lib/checkpoints/checkpoint-hooks.js +0 -298
  166. package/src/lib/checkpoints/index.js +0 -7
  167. package/src/lib/context/context-bundler.js +0 -241
  168. package/src/lib/context/context-optimizer.js +0 -212
  169. package/src/lib/context/context-tracker.js +0 -273
  170. package/src/lib/context/core-four-tracker.js +0 -201
  171. package/src/lib/context/mcp-optimizer.js +0 -200
  172. package/src/lib/detectors/config-detector.js +0 -223
  173. package/src/lib/detectors/standards-generator.js +0 -335
  174. package/src/lib/detectors/structure-detector.js +0 -275
  175. package/src/lib/execution/fusion-executor.js +0 -304
  176. package/src/lib/execution/parallel-executor.js +0 -270
  177. package/src/lib/hooks/stop-hook-executor.js +0 -286
  178. package/src/lib/hops/hop-composer.js +0 -221
  179. package/src/lib/monitor/agent-resolver.js +0 -144
  180. package/src/lib/monitor/renderer.js +0 -230
  181. package/src/lib/orchestration/index.js +0 -7
  182. package/src/lib/orchestration/team-orchestrator.js +0 -404
  183. package/src/lib/phase-chain/eligibility-checker.js +0 -243
  184. package/src/lib/threads/thread-coordinator.js +0 -238
  185. package/src/lib/threads/thread-manager.js +0 -317
  186. package/src/lib/tracking/artifact-trail.js +0 -202
  187. package/src/sanitizer/context-sanitizer.js +0 -221
  188. package/src/sanitizer/patterns.js +0 -163
  189. package/src/scanner/project-scanner.js +0 -242
  190. package/src/ui/diff-display.js +0 -91
  191. package/src/ui/interactive-wizard.js +0 -96
  192. package/src/ui/user-review.js +0 -211
  193. package/src/ui/wizard-questions.js +0 -188
  194. package/src/utils/color-utils.js +0 -70
  195. package/src/utils/process-handler.js +0 -97
  196. package/src/writer/file-writer.js +0 -86
  197. /package/framework/skills/level-0-meta/{brainstorming → morph-brainstorming}/references/proposal-example.md +0 -0
  198. /package/framework/skills/level-0-meta/{code-review → morph-code-review}/references/review-example.md +0 -0
  199. /package/framework/skills/level-0-meta/{code-review → morph-code-review}/references/review-guidelines.md +0 -0
  200. /package/framework/skills/level-0-meta/{code-review → morph-code-review}/scripts/scan-csharp.mjs +0 -0
  201. /package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/references/review-example-nextjs.md +0 -0
  202. /package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/scripts/scan-nextjs.mjs +0 -0
  203. /package/framework/skills/level-0-meta/{frontend-review → morph-frontend-review}/scripts/scan-accessibility.mjs +0 -0
  204. /package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/scripts/detect-dev-server.mjs +0 -0
  205. /package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/scripts/detect-stack.mjs +0 -0
  206. /package/framework/skills/level-0-meta/{terminal-title → morph-terminal-title}/scripts/set_title.sh +0 -0
  207. /package/framework/skills/level-1-workflows/{phase-clarify → morph-phase-clarify}/references/clarifications-example.md +0 -0
  208. /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/architecture-analysis-guide.md +0 -0
  209. /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/spec-authoring-guide.md +0 -0
  210. /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/spec-example.md +0 -0
  211. /package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/references/recap-example.md +0 -0
  212. /package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/references/vsa-implementation-guide.md +0 -0
  213. /package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/references/task-planning-patterns.md +0 -0
  214. /package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/references/tasks-example.md +0 -0
@@ -0,0 +1,116 @@
1
+ /**
2
+ * Pure helper functions for the dispatch PostToolUse hook.
3
+ *
4
+ * Extracted for testability — no I/O, no process.exit, no stdin/stdout.
5
+ */
6
+
7
+ /**
8
+ * Extract VALIDATION DISPATCH JSON from tool output string.
9
+ * Strips ANSI escape codes and parses the JSON block between delimiters.
10
+ *
11
+ * @param {string} toolOutput - Raw tool output (may contain ANSI codes)
12
+ * @returns {object|null} - Parsed dispatch object or null
13
+ */
14
+ export function extractValidationDispatch(toolOutput) {
15
+ if (!toolOutput || typeof toolOutput !== 'string') return null;
16
+
17
+ // Strip ANSI escape codes (chalk colors)
18
+ const clean = toolOutput.replace(/\x1B\[[0-9;]*m/g, '');
19
+
20
+ const match = clean.match(/--- VALIDATION DISPATCH ---\s*\n([\s\S]*?)\n\s*--- END VALIDATION DISPATCH ---/);
21
+ if (!match) return null;
22
+
23
+ try {
24
+ const dispatch = JSON.parse(match[1]);
25
+ if (!dispatch?.validators?.length) return null;
26
+ return dispatch;
27
+ } catch {
28
+ return null;
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Format a dispatch object as a mandatory instruction string for Claude.
34
+ *
35
+ * @param {object} dispatch - Dispatch object with validators array
36
+ * @returns {string} - Formatted injection text
37
+ */
38
+ export function formatValidatorInstruction(dispatch) {
39
+ if (!dispatch?.validators?.length) return '';
40
+
41
+ const lines = [
42
+ '🔍 MANDATORY VALIDATOR DISPATCH',
43
+ '════════════════════════════════════════',
44
+ 'You MUST dispatch each validator below as a READ-ONLY subagent using the Agent tool.',
45
+ 'Do NOT skip this step. Do NOT proceed until all blocking validators pass.',
46
+ '',
47
+ ];
48
+
49
+ for (const v of dispatch.validators) {
50
+ const blocks = v.blocksOnFail !== false;
51
+ const sev = v.severity || 'error';
52
+ lines.push(`### ${v.title} (${v.id})`);
53
+ lines.push(`Severity: ${sev} | Blocks: ${blocks}`);
54
+ if (v.checks?.length) lines.push(`Checks: ${v.checks.join(', ')}`);
55
+ lines.push('');
56
+ if (v.taskPrompt) {
57
+ lines.push('Subagent prompt:');
58
+ lines.push(v.taskPrompt);
59
+ }
60
+ lines.push('---');
61
+ }
62
+
63
+ lines.push('');
64
+ lines.push('DISPATCH INSTRUCTIONS:');
65
+ lines.push('1. Use the Agent tool for each validator. Set subagent_type to the validator name shown above.');
66
+ lines.push('2. Use model="haiku" for efficiency. Each validator is read-only.');
67
+ lines.push('3. Each must return { "passed": boolean, "issues": [] }');
68
+ lines.push('4. If any blocking validator (Blocks: true) fails, fix issues before proceeding.');
69
+
70
+ return lines.join('\n');
71
+ }
72
+
73
+ /**
74
+ * Parse `morph-spec task done <feature> <taskId>` from a bash command string.
75
+ * Returns { featureName, taskId } or null if the command doesn't match.
76
+ *
77
+ * @param {string} command
78
+ * @returns {{ featureName: string, taskId: string } | null}
79
+ */
80
+ export function parseTaskDoneCommand(command) {
81
+ if (!command || typeof command !== 'string') return null;
82
+ const match = command.match(/morph-spec\s+task\s+done\s+(\S+)\s+(\S+)/);
83
+ if (!match) return null;
84
+ return { featureName: match[1], taskId: match[2] };
85
+ }
86
+
87
+ /**
88
+ * Parse `morph-spec phase advance <feature>` from a bash command string.
89
+ * Returns { featureName } or null if the command doesn't match.
90
+ *
91
+ * @param {string} command
92
+ * @returns {{ featureName: string } | null}
93
+ */
94
+ export function parsePhaseAdvanceCommand(command) {
95
+ if (!command || typeof command !== 'string') return null;
96
+ const match = command.match(/morph-spec\s+phase\s+advance\s+(\S+)/);
97
+ if (!match) return null;
98
+ return { featureName: match[1] };
99
+ }
100
+
101
+ /**
102
+ * Extract the tool result string from a PostToolUse hook payload.
103
+ * Handles multiple possible formats.
104
+ *
105
+ * @param {object} payload - Hook payload
106
+ * @returns {string} - Tool output string (empty string if not found)
107
+ */
108
+ export function getToolOutput(payload) {
109
+ if (!payload) return '';
110
+ const result = payload.tool_result;
111
+ if (typeof result === 'string') return result;
112
+ if (result && typeof result === 'object') {
113
+ return result.stdout || result.content || '';
114
+ }
115
+ return '';
116
+ }
@@ -8,7 +8,7 @@
8
8
  */
9
9
 
10
10
  /** Phase order */
11
- export const PHASE_ORDER = ["proposal","setup","uiux","design","clarify","tasks","implement","sync"];
11
+ export const PHASE_ORDER = ["proposal","setup","uiux","design","clarify","plan","tasks","implement","sync"];
12
12
 
13
13
  /** Map phase → allowed output subdirectory */
14
14
  export const PHASE_DIRS = {
@@ -16,10 +16,11 @@ export const PHASE_DIRS = {
16
16
  setup: '0-proposal',
17
17
  uiux: '2-ui',
18
18
  design: '1-design',
19
- clarify: '2-clarify',
20
- tasks: '3-tasks',
21
- implement: '4-implement',
22
- sync: '4-implement',
19
+ clarify: '1-design',
20
+ plan: '3-plan',
21
+ tasks: '4-tasks',
22
+ implement: '5-implement',
23
+ sync: '5-implement',
23
24
  };
24
25
 
25
26
  /** Map output type (camelCase) → phase that produces it */
@@ -29,7 +30,9 @@ export const OUTPUT_PHASE_MAP = {
29
30
  spec: 'design',
30
31
  clarifications: 'clarify',
31
32
  contracts: 'design',
33
+ contractsTs: 'design',
32
34
  contractsVsa: 'design',
35
+ plan: 'plan',
33
36
  tasks: 'tasks',
34
37
  uiDesignSystem: 'uiux',
35
38
  uiMockups: 'uiux',
@@ -46,7 +49,9 @@ export const FILENAME_TO_OUTPUT = {
46
49
  'spec.md': 'spec',
47
50
  'clarifications.md': 'clarifications',
48
51
  'contracts.cs': 'contracts',
52
+ 'contracts.ts': 'contractsTs',
49
53
  'contracts-vsa.cs': 'contractsVsa',
54
+ 'plan.md': 'plan',
50
55
  'tasks.md': 'tasks',
51
56
  'design-system.md': 'uiDesignSystem',
52
57
  'mockups.md': 'uiMockups',
@@ -61,7 +66,9 @@ export const PROTECTED_SPEC_FILES = {
61
66
  'schema-analysis.md': 'design',
62
67
  'spec.md': 'design',
63
68
  'contracts.cs': 'design',
69
+ 'contracts.ts': 'design',
64
70
  'contracts-vsa.cs': 'design',
71
+ 'plan.md': 'plan',
65
72
  'tasks.md': 'tasks',
66
73
  'design-system.md': 'uiux',
67
74
  'mockups.md': 'uiux',
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Pure helper functions for the skill-reminder PostToolUse hook.
3
+ * Extracted to shared/ so they can be unit-tested without running the hook entry point.
4
+ */
5
+
6
+ /**
7
+ * Parse `morph-spec task start <feature> <taskId>` from a bash command string.
8
+ * Returns { featureName, taskId } or null if the command doesn't match.
9
+ *
10
+ * @param {string} command
11
+ * @returns {{ featureName: string, taskId: string } | null}
12
+ */
13
+ export function parseTaskStartCommand(command) {
14
+ if (!command || typeof command !== 'string') return null;
15
+ const match = command.match(/morph-spec\s+task\s+start\s+(\S+)\s+(\S+)/);
16
+ if (!match) return null;
17
+ return { featureName: match[1], taskId: match[2] };
18
+ }
19
+
20
+ /**
21
+ * Build the mandatory skill reminder message for a task start event.
22
+ * Returns null if no relevant skills exist for this phase at task-start time.
23
+ *
24
+ * Relevant triggers:
25
+ * - beforeEachTask → invoke NOW before writing any code
26
+ * - beforeTaskDone → invoke BEFORE marking task done
27
+ * - onBugOrUnexpected → invoke IF a bug is encountered
28
+ *
29
+ * @param {string} featureName
30
+ * @param {string} taskId
31
+ * @param {string} phase
32
+ * @param {Object|null} phasesData - Parsed phases.json content
33
+ * @returns {string|null}
34
+ */
35
+ export function buildSkillReminderMessage(featureName, taskId, phase, phasesData) {
36
+ try {
37
+ const allSkills = phasesData?.phases?.[phase]?.requiredSkills;
38
+ if (!allSkills || allSkills.length === 0) return null;
39
+
40
+ const nowSkills = allSkills.filter(s => s.trigger === 'beforeEachTask');
41
+ const laterSkills = allSkills.filter(s => s.trigger === 'beforeTaskDone');
42
+ const bugSkills = allSkills.filter(s => s.trigger === 'onBugOrUnexpected');
43
+
44
+ if (nowSkills.length === 0 && laterSkills.length === 0) return null;
45
+
46
+ const lines = [];
47
+ lines.push(`⚠️ MORPH-SPEC SKILL REMINDER — Task ${taskId} started (phase: ${phase})`);
48
+ lines.push('');
49
+
50
+ if (nowSkills.length > 0) {
51
+ lines.push('INVOKE NOW before writing any code:');
52
+ for (const s of nowSkills) {
53
+ lines.push(` → Skill(${s.skill})`);
54
+ }
55
+ lines.push('');
56
+ }
57
+
58
+ if (laterSkills.length > 0) {
59
+ lines.push('INVOKE BEFORE marking task done:');
60
+ for (const s of laterSkills) {
61
+ lines.push(` → Skill(${s.skill})`);
62
+ }
63
+ lines.push('');
64
+ }
65
+
66
+ if (bugSkills.length > 0) {
67
+ lines.push('IF you encounter a bug or unexpected behavior:');
68
+ for (const s of bugSkills) {
69
+ lines.push(` → Skill(${s.skill})`);
70
+ }
71
+ lines.push('');
72
+ }
73
+
74
+ lines.push('These are MANDATORY. Use the Skill() tool. Do NOT skip.');
75
+ return lines.join('\n');
76
+ } catch {
77
+ return null;
78
+ }
79
+ }
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Stale Task Reset — shared hook utility
3
+ *
4
+ * Detects tasks stuck in `in_progress` for longer than STALE_MS and resets
5
+ * them to `pending`. Works for both v2 (tasks is array) and v3 (taskList)
6
+ * state formats. Pure function: mutates state in-place, returns reset log.
7
+ *
8
+ * Stale threshold: 1 hour (hardcoded — not user-configurable)
9
+ */
10
+
11
+ export const STALE_MS = 60 * 60 * 1000; // 1 hour
12
+
13
+ /**
14
+ * Determine if a task is stale (in_progress for longer than STALE_MS).
15
+ * @param {Object} task
16
+ * @param {number} [now] - timestamp for testability (defaults to Date.now())
17
+ * @returns {boolean}
18
+ */
19
+ export function isStaleTask(task, now = Date.now()) {
20
+ if (task.status !== 'in_progress') return false;
21
+ if (!task.startedAt) return true; // missing timestamp → treat as stale
22
+ return now - new Date(task.startedAt).getTime() > STALE_MS;
23
+ }
24
+
25
+ /**
26
+ * Reset all stale in_progress tasks across all features in state.
27
+ * Mutates state in-place. Returns log of reset task identifiers.
28
+ *
29
+ * @param {Object} state - Full state object (state.features)
30
+ * @param {number} [now] - Timestamp for testability
31
+ * @returns {string[]} resetLog — entries like "feature-name/T008"
32
+ */
33
+ export function resetStaleTasks(state, now = Date.now()) {
34
+ const resetLog = [];
35
+
36
+ for (const [featureName, feature] of Object.entries(state.features || {})) {
37
+ // Resolve task list for both v2 (array) and v3 (taskList) formats
38
+ const isV2 = Array.isArray(feature.tasks);
39
+ const list = isV2 ? feature.tasks : (feature.taskList || []);
40
+
41
+ for (const task of list) {
42
+ if (isStaleTask(task, now)) {
43
+ task.status = 'pending';
44
+ delete task.startedAt;
45
+ resetLog.push(`${featureName}/${task.id ?? '(unknown)'}`);
46
+ }
47
+ }
48
+
49
+ // Re-sync counters (v3 only — v2 tasks IS the list, no separate counters)
50
+ if (!isV2 && feature.tasks) {
51
+ feature.tasks.inProgress = list.filter(t => t.status === 'in_progress').length;
52
+ feature.tasks.pending = list.filter(t => t.status === 'pending').length;
53
+ }
54
+ }
55
+
56
+ return resetLog;
57
+ }
@@ -62,6 +62,30 @@ export function getActiveFeature(projectPath) {
62
62
  return active;
63
63
  }
64
64
 
65
+ /**
66
+ * Get the most recently updated feature (any status).
67
+ * Useful as a fallback when getActiveFeature returns null (all features done).
68
+ * @param {string} [projectPath]
69
+ * @returns {{ name: string, feature: Object }|null}
70
+ */
71
+ export function getMostRecentFeature(projectPath) {
72
+ const state = loadState(projectPath);
73
+ if (!state?.features) return null;
74
+
75
+ let result = null;
76
+ let latestUpdate = '';
77
+
78
+ for (const [name, feature] of Object.entries(state.features)) {
79
+ const updated = feature.updatedAt || feature.createdAt || '';
80
+ if (updated >= latestUpdate) {
81
+ latestUpdate = updated;
82
+ result = { name, feature };
83
+ }
84
+ }
85
+
86
+ return result;
87
+ }
88
+
65
89
  /**
66
90
  * Get a specific feature by name.
67
91
  * @param {string} featureName
@@ -74,7 +98,7 @@ export function getFeature(featureName, projectPath) {
74
98
  }
75
99
 
76
100
  /**
77
- * Derive phase from filesystem (v5.0.0+: phase deleted from state).
101
+ * Derive phase from filesystem (phase deleted from state).
78
102
  * Checks for phase folders in descending order, returns highest present.
79
103
  * @param {string} featureName
80
104
  * @param {string} [projectPath]
@@ -83,9 +107,9 @@ export function getFeature(featureName, projectPath) {
83
107
  export function derivePhaseForFeature(featureName, projectPath) {
84
108
  const basePath = join(projectPath || process.cwd(), '.morph/features', featureName);
85
109
  const phaseMap = [
86
- ['4-implement', 'implement'],
87
- ['3-tasks', 'tasks'],
88
- ['2-clarify', 'clarify'],
110
+ ['5-implement', 'implement'],
111
+ ['4-tasks', 'tasks'],
112
+ ['3-plan', 'plan'],
89
113
  ['2-ui', 'uiux'],
90
114
  ['1-design', 'design'],
91
115
  ['0-proposal', 'proposal'],
@@ -98,7 +122,7 @@ export function derivePhaseForFeature(featureName, projectPath) {
98
122
 
99
123
  /**
100
124
  * Get the current phase of a feature.
101
- * Falls back to filesystem derivation when phase is not in state (v5.0.0+).
125
+ * Falls back to filesystem derivation when phase is not in state.
102
126
  * @param {string} featureName
103
127
  * @param {string} [projectPath]
104
128
  * @returns {string|null}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Shared helpers for worktree-related hook logic.
3
+ * Pure functions — no side effects, easy to test.
4
+ */
5
+
6
+ /**
7
+ * Parse JSON output from `morph-spec worktree setup`.
8
+ * Finds the last line starting with '{' to skip chalk-colored output.
9
+ * @param {string} stdout - Raw stdout from the command
10
+ * @returns {Object|null}
11
+ */
12
+ export function parseWorktreeResult(stdout) {
13
+ if (!stdout || typeof stdout !== 'string') return null;
14
+ try {
15
+ const trimmed = stdout.trim();
16
+ if (!trimmed) return null;
17
+ const lines = trimmed.split('\n');
18
+ for (let i = lines.length - 1; i >= 0; i--) {
19
+ const line = lines[i].trim();
20
+ if (line.startsWith('{')) {
21
+ return JSON.parse(line);
22
+ }
23
+ }
24
+ return null;
25
+ } catch {
26
+ return null;
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Build context line(s) to inject based on worktree setup result.
32
+ * @param {Object|null} result - Parsed result from parseWorktreeResult
33
+ * @returns {string} Context lines to inject, or '' to skip injection
34
+ */
35
+ export function buildWorktreeContextLine(result) {
36
+ if (!result) return '';
37
+ if (result.error) return '';
38
+
39
+ if (result.created) {
40
+ return `🪴 Worktree created: Working in .worktrees/${result.feature || ''} on branch ${result.branch}`;
41
+ }
42
+
43
+ if (result.alreadyExists) {
44
+ return [
45
+ `⚠️ Existing worktree found for feature '${result.feature}': ${result.path} (branch: ${result.branch})`,
46
+ ` Use AskUserQuestion to ask: "resume existing worktree or start fresh (--fresh)?"`,
47
+ ` • Resume: continue in existing worktree (no change)`,
48
+ ` • Fresh: run \`morph-spec worktree setup ${result.feature} --fresh\``
49
+ ].join('\n');
50
+ }
51
+
52
+ return '';
53
+ }
@@ -17,7 +17,10 @@
17
17
  "pausePoints": [
18
18
  { "id": "proposal", "label": "Approve Proposal" }
19
19
  ],
20
- "agentTiers": [1]
20
+ "agentTiers": [1],
21
+ "requiredSkills": [
22
+ { "trigger": "beforePhaseStart", "skill": "morph:brainstorming" }
23
+ ]
21
24
  },
22
25
  "setup": {
23
26
  "id": "setup",
@@ -33,7 +36,8 @@
33
36
  "optionalOutputs": [],
34
37
  "recommendedMCPs": ["github"],
35
38
  "pausePoints": [],
36
- "agentTiers": [1, 2]
39
+ "agentTiers": [1, 2],
40
+ "requiredSkills": []
37
41
  },
38
42
  "uiux": {
39
43
  "id": "uiux",
@@ -52,7 +56,12 @@
52
56
  "pausePoints": [
53
57
  { "id": "uiux", "label": "Approve UI/UX Design" }
54
58
  ],
55
- "agentTiers": [1, 2, 3]
59
+ "agentTiers": [1, 2, 3],
60
+ "requiredSkills": [
61
+ { "trigger": "beforeArchitecturalDecision", "skill": "morph:brainstorming" },
62
+ { "trigger": "onHTMLPrototypeAvailable", "skill": "morph:replicate" },
63
+ { "trigger": "beforePhaseComplete", "skill": "morph:frontend-review" }
64
+ ]
56
65
  },
57
66
  "design": {
58
67
  "id": "design",
@@ -71,7 +80,14 @@
71
80
  "pausePoints": [
72
81
  { "id": "design", "label": "Approve Design Spec" }
73
82
  ],
74
- "agentTiers": [1, 2, 3, 4]
83
+ "agentTiers": [1, 2, 3, 4],
84
+ "requiredSkills": [
85
+ { "trigger": "beforePhaseStart", "skill": "morph:phase-design" },
86
+ { "trigger": "beforePhaseStart", "skill": "morph:phase-codebase-analysis" },
87
+ { "trigger": "beforeArchitecturalDecision", "skill": "morph:brainstorming" },
88
+ { "trigger": "onExternalServiceIntegration", "skill": "morph:simulation-checklist" },
89
+ { "trigger": "beforePhaseComplete", "skill": "morph:verification-before-completion" }
90
+ ]
75
91
  },
76
92
  "clarify": {
77
93
  "id": "clarify",
@@ -87,12 +103,38 @@
87
103
  "optionalOutputs": [],
88
104
  "recommendedMCPs": ["context7", "github"],
89
105
  "pausePoints": [],
90
- "agentTiers": [1, 2]
106
+ "agentTiers": [1, 2],
107
+ "requiredSkills": [
108
+ { "trigger": "beforePhaseStart", "skill": "morph:phase-clarify" },
109
+ { "trigger": "beforePhaseComplete", "skill": "morph:verification-before-completion" }
110
+ ]
111
+ },
112
+ "plan": {
113
+ "id": "plan",
114
+ "index": 5,
115
+ "folder": "3-plan",
116
+ "displayName": "Implementation Plan",
117
+ "goals": [
118
+ "Create detailed implementation plan with exact file paths and code",
119
+ "Define bite-sized TDD tasks with test-first approach",
120
+ "Establish execution strategy based on project analysis"
121
+ ],
122
+ "requiredOutputs": ["plan"],
123
+ "optionalOutputs": [],
124
+ "recommendedMCPs": ["context7", "github"],
125
+ "pausePoints": [
126
+ { "id": "plan", "label": "Approve Implementation Plan" }
127
+ ],
128
+ "agentTiers": [1, 2],
129
+ "requiredSkills": [
130
+ { "trigger": "beforePhaseStart", "skill": "morph:phase-plan" },
131
+ { "trigger": "beforePhaseComplete", "skill": "morph:verification-before-completion" }
132
+ ]
91
133
  },
92
134
  "tasks": {
93
135
  "id": "tasks",
94
- "index": 4,
95
- "folder": "3-tasks",
136
+ "index": 6,
137
+ "folder": "4-tasks",
96
138
  "displayName": "Task Breakdown",
97
139
  "goals": [
98
140
  "Break approved spec into numbered, atomic tasks",
@@ -105,12 +147,16 @@
105
147
  "pausePoints": [
106
148
  { "id": "tasks", "label": "Approve Task List" }
107
149
  ],
108
- "agentTiers": [1, 2, 3]
150
+ "agentTiers": [1, 2, 3],
151
+ "requiredSkills": [
152
+ { "trigger": "onParallelTasksIdentified", "skill": "superpowers:dispatching-parallel-agents" },
153
+ { "trigger": "beforePhaseComplete", "skill": "morph:verification-before-completion" }
154
+ ]
109
155
  },
110
156
  "implement": {
111
157
  "id": "implement",
112
- "index": 5,
113
- "folder": "4-implement",
158
+ "index": 7,
159
+ "folder": "5-implement",
114
160
  "displayName": "Implementation",
115
161
  "goals": [
116
162
  "Execute approved tasks in order",
@@ -120,13 +166,21 @@
120
166
  ],
121
167
  "requiredOutputs": ["recap"],
122
168
  "optionalOutputs": [],
123
- "recommendedMCPs": ["supabase", "context7", "playwright", "github", "docker", "azure"],
169
+ "recommendedMCPs": ["supabase", "context7", "playwright", "github", "vercel", "docker", "azure"],
124
170
  "pausePoints": [],
125
- "agentTiers": [1, 2, 3, 4]
171
+ "agentTiers": [1, 2, 3, 4],
172
+ "requiredSkills": [
173
+ { "trigger": "beforeEachTask", "skill": "superpowers:test-driven-development" },
174
+ { "trigger": "beforeTaskDone", "skill": "morph:verification-before-completion" },
175
+ { "trigger": "onBugOrUnexpected", "skill": "superpowers:systematic-debugging" },
176
+ { "trigger": "afterAllTasks", "skill": "morph:post-implementation" },
177
+ { "trigger": "afterAllTasks", "skill": "superpowers:requesting-code-review" },
178
+ { "trigger": "beforePR", "skill": "morph:checklist" }
179
+ ]
126
180
  },
127
181
  "sync": {
128
182
  "id": "sync",
129
- "index": 6,
183
+ "index": 8,
130
184
  "folder": null,
131
185
  "displayName": "Sync Standards",
132
186
  "optional": true,
@@ -139,7 +193,8 @@
139
193
  "optionalOutputs": [],
140
194
  "recommendedMCPs": [],
141
195
  "pausePoints": [],
142
- "agentTiers": [1]
196
+ "agentTiers": [1],
197
+ "requiredSkills": []
143
198
  }
144
199
  }
145
200
  }