@polymorphism-tech/morph-spec 4.9.0 → 4.10.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 (164) hide show
  1. package/README.md +2 -2
  2. package/bin/morph-spec.js +30 -0
  3. package/bin/task-manager.js +34 -22
  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 +35 -98
  8. package/framework/agents/backend/api-designer.md +3 -0
  9. package/framework/agents/backend/dotnet-senior.md +3 -0
  10. package/framework/agents/backend/ef-modeler.md +2 -0
  11. package/framework/agents/backend/hangfire-orchestrator.md +2 -0
  12. package/framework/agents/backend/ms-agent-expert.md +2 -0
  13. package/framework/agents/frontend/blazor-builder.md +2 -0
  14. package/framework/agents/frontend/nextjs-expert.md +2 -0
  15. package/framework/agents/infrastructure/azure-architect.md +2 -0
  16. package/framework/agents/infrastructure/azure-deploy-specialist.md +2 -0
  17. package/framework/agents/infrastructure/bicep-architect.md +2 -0
  18. package/framework/agents/infrastructure/container-specialist.md +2 -0
  19. package/framework/agents/infrastructure/devops-engineer.md +3 -0
  20. package/framework/agents/infrastructure/infra-architect.md +3 -0
  21. package/framework/agents/integrations/asaas-financial.md +2 -0
  22. package/framework/agents/integrations/azure-identity.md +2 -0
  23. package/framework/agents/integrations/clerk-auth.md +3 -0
  24. package/framework/agents/integrations/hangfire-integration.md +2 -0
  25. package/framework/agents/integrations/resend-email.md +2 -0
  26. package/framework/agents.json +37 -7
  27. package/framework/commands/commit.md +166 -0
  28. package/framework/commands/morph-apply.md +156 -155
  29. package/framework/commands/morph-archive.md +33 -27
  30. package/framework/commands/morph-infra.md +83 -77
  31. package/framework/commands/morph-preflight.md +97 -55
  32. package/framework/commands/morph-proposal.md +131 -58
  33. package/framework/commands/morph-status.md +36 -30
  34. package/framework/commands/morph-troubleshoot.md +68 -59
  35. package/framework/hooks/claude-code/notification/approval-reminder.js +3 -2
  36. package/framework/hooks/claude-code/post-tool-use/dispatch.js +154 -31
  37. package/framework/hooks/claude-code/post-tool-use/skill-reminder.js +7 -84
  38. package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +8 -17
  39. package/framework/hooks/claude-code/pre-compact/save-morph-context.js +16 -3
  40. package/framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js +4 -3
  41. package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +3 -2
  42. package/framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js +60 -0
  43. package/framework/hooks/claude-code/session-start/inject-morph-context.js +55 -2
  44. package/framework/hooks/claude-code/session-start/post-compact-restore.js +41 -0
  45. package/framework/hooks/claude-code/stop/validate-completion.js +2 -15
  46. package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +23 -5
  47. package/framework/hooks/shared/compact-restore.js +100 -0
  48. package/framework/hooks/shared/dispatch-helpers.js +116 -0
  49. package/framework/hooks/shared/phase-utils.js +9 -5
  50. package/framework/hooks/shared/state-reader.js +27 -3
  51. package/framework/phases.json +30 -7
  52. package/framework/rules/csharp-standards.md +3 -0
  53. package/framework/rules/frontend-standards.md +2 -0
  54. package/framework/rules/infrastructure-standards.md +3 -0
  55. package/framework/rules/morph-workflow.md +143 -86
  56. package/framework/rules/nextjs-standards.md +2 -0
  57. package/framework/rules/testing-standards.md +3 -0
  58. package/framework/skills/level-0-meta/mcp-registry.json +86 -51
  59. package/framework/skills/level-0-meta/morph-brainstorming/SKILL.md +139 -0
  60. package/framework/skills/level-0-meta/morph-checklist/SKILL.md +42 -19
  61. package/framework/skills/level-0-meta/{code-review → morph-code-review}/SKILL.md +8 -5
  62. package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/SKILL.md +8 -6
  63. package/framework/skills/level-0-meta/morph-frontend-review/SKILL.md +362 -0
  64. package/framework/skills/level-0-meta/morph-init/SKILL.md +114 -20
  65. package/framework/skills/level-0-meta/morph-post-implementation/SKILL.md +362 -0
  66. package/framework/skills/level-0-meta/morph-replicate/SKILL.md +95 -87
  67. package/framework/skills/level-0-meta/{simulation-checklist → morph-simulation-checklist}/SKILL.md +24 -0
  68. package/framework/skills/level-0-meta/{tool-usage-guide → morph-tool-usage-guide}/SKILL.md +43 -43
  69. package/framework/skills/level-0-meta/{tool-usage-guide → morph-tool-usage-guide}/references/tools-per-phase.md +1 -2
  70. package/framework/skills/level-0-meta/{verification-before-completion → morph-verification-before-completion}/SKILL.md +23 -12
  71. package/framework/skills/level-0-meta/{verification-before-completion → morph-verification-before-completion}/scripts/check-phase-outputs.mjs +2 -2
  72. package/framework/skills/level-1-workflows/morph-phase-clarify/SKILL.md +247 -0
  73. package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +270 -0
  74. package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +499 -0
  75. package/framework/skills/level-1-workflows/morph-phase-implement/.morph/logs/activity.json +38 -0
  76. package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +472 -0
  77. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/code-quality-reviewer-prompt.md +50 -0
  78. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/implementer-prompt.md +45 -0
  79. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/spec-reviewer-prompt.md +47 -0
  80. package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +246 -0
  81. package/framework/skills/level-1-workflows/morph-phase-setup/SKILL.md +238 -0
  82. package/framework/skills/level-1-workflows/morph-phase-tasks/.morph/logs/activity.json +14 -0
  83. package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +312 -0
  84. package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/scripts/validate-tasks.mjs +3 -3
  85. package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +324 -0
  86. package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +146 -0
  87. package/framework/standards/integration/mcp/mcp-tools.md +25 -7
  88. package/framework/templates/docs/onboarding.md +2 -2
  89. package/package.json +3 -4
  90. package/src/commands/agents/dispatch-agents.js +50 -3
  91. package/src/commands/mcp/mcp-setup.js +39 -2
  92. package/src/commands/phase/phase-reset.js +74 -0
  93. package/src/commands/project/doctor.js +26 -7
  94. package/src/commands/project/update.js +4 -4
  95. package/src/commands/scope/escalate.js +215 -0
  96. package/src/commands/state/advance-phase.js +27 -53
  97. package/src/commands/state/state.js +1 -1
  98. package/src/commands/task/expand.js +100 -0
  99. package/src/core/paths/output-schema.js +4 -3
  100. package/src/core/state/phase-state-machine.js +7 -4
  101. package/src/core/state/state-manager.js +4 -3
  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 +22 -16
  108. package/src/lib/scope/impact-analyzer.js +106 -0
  109. package/src/lib/stack-filter.js +58 -0
  110. package/src/lib/tasks/task-parser.js +1 -1
  111. package/src/lib/validators/shared/emit-validator-dispatch.js +64 -0
  112. package/src/scripts/setup-infra.js +68 -18
  113. package/src/utils/agents-installer.js +51 -17
  114. package/src/utils/claude-md-injector.js +90 -0
  115. package/src/utils/file-copier.js +0 -1
  116. package/src/utils/hooks-installer.js +16 -5
  117. package/src/utils/skills-installer.js +67 -7
  118. package/CLAUDE.md +0 -98
  119. package/framework/memory/patterns-learned.md +0 -766
  120. package/framework/skills/level-0-meta/brainstorming/SKILL.md +0 -137
  121. package/framework/skills/level-0-meta/frontend-review/SKILL.md +0 -359
  122. package/framework/skills/level-0-meta/post-implementation/SKILL.md +0 -362
  123. package/framework/skills/level-0-meta/terminal-title/SKILL.md +0 -61
  124. package/framework/skills/level-0-meta/terminal-title/scripts/set_title.sh +0 -65
  125. package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +0 -216
  126. package/framework/skills/level-1-workflows/phase-codebase-analysis/SKILL.md +0 -252
  127. package/framework/skills/level-1-workflows/phase-design/SKILL.md +0 -383
  128. package/framework/skills/level-1-workflows/phase-implement/SKILL.md +0 -492
  129. package/framework/skills/level-1-workflows/phase-setup/SKILL.md +0 -195
  130. package/framework/skills/level-1-workflows/phase-tasks/SKILL.md +0 -271
  131. package/framework/skills/level-1-workflows/phase-uiux/SKILL.md +0 -286
  132. package/src/commands/project/index.js +0 -8
  133. package/src/core/index.js +0 -10
  134. package/src/core/state/index.js +0 -8
  135. package/src/core/templates/index.js +0 -9
  136. package/src/core/templates/template-data-sources.js +0 -325
  137. package/src/core/workflows/index.js +0 -7
  138. package/src/lib/detectors/config-detector.js +0 -223
  139. package/src/lib/detectors/standards-generator.js +0 -335
  140. package/src/lib/detectors/structure-detector.js +0 -275
  141. package/src/lib/monitor/agent-resolver.js +0 -144
  142. package/src/lib/monitor/renderer.js +0 -230
  143. package/src/lib/orchestration/index.js +0 -7
  144. package/src/lib/orchestration/team-orchestrator.js +0 -404
  145. package/src/sanitizer/context-sanitizer.js +0 -221
  146. package/src/sanitizer/patterns.js +0 -163
  147. package/src/writer/file-writer.js +0 -86
  148. /package/framework/skills/level-0-meta/{brainstorming → morph-brainstorming}/references/proposal-example.md +0 -0
  149. /package/framework/skills/level-0-meta/{code-review → morph-code-review}/references/review-example.md +0 -0
  150. /package/framework/skills/level-0-meta/{code-review → morph-code-review}/references/review-guidelines.md +0 -0
  151. /package/framework/skills/level-0-meta/{code-review → morph-code-review}/scripts/scan-csharp.mjs +0 -0
  152. /package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/references/review-example-nextjs.md +0 -0
  153. /package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/scripts/scan-nextjs.mjs +0 -0
  154. /package/framework/skills/level-0-meta/{frontend-review → morph-frontend-review}/scripts/scan-accessibility.mjs +0 -0
  155. /package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/scripts/detect-dev-server.mjs +0 -0
  156. /package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/scripts/detect-stack.mjs +0 -0
  157. /package/framework/skills/level-1-workflows/{phase-clarify → morph-phase-clarify}/references/clarifications-example.md +0 -0
  158. /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/architecture-analysis-guide.md +0 -0
  159. /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/spec-authoring-guide.md +0 -0
  160. /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/spec-example.md +0 -0
  161. /package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/references/recap-example.md +0 -0
  162. /package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/references/vsa-implementation-guide.md +0 -0
  163. /package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/references/task-planning-patterns.md +0 -0
  164. /package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/references/tasks-example.md +0 -0
@@ -0,0 +1,41 @@
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
+ }
@@ -21,7 +21,7 @@
21
21
  import { readFileSync, writeFileSync, existsSync } from 'fs';
22
22
  import { join } from 'path';
23
23
  import {
24
- stateExists, loadState, getActiveFeature, getMissingOutputs, derivePhaseForFeature,
24
+ stateExists, getActiveFeature, getMostRecentFeature, getMissingOutputs, derivePhaseForFeature,
25
25
  } from '../../shared/state-reader.js';
26
26
  import { injectContext, pass } from '../../shared/hook-response.js';
27
27
  import { logHookActivity } from '../../shared/activity-logger.js';
@@ -40,20 +40,7 @@ try {
40
40
 
41
41
  // getActiveFeature() only returns in_progress/draft features.
42
42
  // Fall back to the most recently updated feature when all features are 'done'.
43
- let active = getActiveFeature();
44
- if (!active) {
45
- const state = loadState();
46
- if (state?.features) {
47
- let latestUpdate = '';
48
- for (const [name, feature] of Object.entries(state.features)) {
49
- const updated = feature.updatedAt || feature.createdAt || '';
50
- if (updated >= latestUpdate) {
51
- latestUpdate = updated;
52
- active = { name, feature };
53
- }
54
- }
55
- }
56
- }
43
+ let active = getActiveFeature() || getMostRecentFeature();
57
44
  if (!active) pass();
58
45
 
59
46
  const { name, feature } = active;
@@ -15,7 +15,7 @@
15
15
  */
16
16
 
17
17
  import { readStdin } from '../../shared/stdin-reader.js';
18
- import { stateExists, loadState, getActiveFeature, getFeature, getPendingGates } from '../../shared/state-reader.js';
18
+ import { stateExists, loadState, getActiveFeature, getFeature, getPendingGates, derivePhaseForFeature } from '../../shared/state-reader.js';
19
19
  import { injectContext, pass } from '../../shared/hook-response.js';
20
20
  import { logHookActivity } from '../../shared/activity-logger.js';
21
21
 
@@ -37,7 +37,8 @@ try {
37
37
  // Check if a feature name is mentioned
38
38
  for (const [featureName, feature] of Object.entries(state.features)) {
39
39
  if (promptLower.includes(featureName.toLowerCase())) {
40
- context.push(`[morph-spec] Feature '${featureName}': phase=${feature.phase}, status=${feature.status}`);
40
+ const featurePhase = feature.phase || derivePhaseForFeature(featureName);
41
+ context.push(`[morph-spec] Feature '${featureName}': phase=${featurePhase}, status=${feature.status}`);
41
42
  if (feature.tasks?.total > 0) {
42
43
  context.push(` Tasks: ${feature.tasks.completed || 0}/${feature.tasks.total} completed`);
43
44
  }
@@ -50,16 +51,33 @@ try {
50
51
  if (active) {
51
52
  const { name, feature } = active;
52
53
 
54
+ const activePhase = feature.phase || derivePhaseForFeature(name);
53
55
  const codeKeywords = ['implement', 'code', 'start coding', 'write the code', 'build it', 'let\'s build'];
54
56
  const wantsToCode = codeKeywords.some(kw => promptLower.includes(kw));
55
57
 
56
- if (wantsToCode && feature.phase !== 'implement' && feature.phase !== 'sync') {
58
+ if (wantsToCode && activePhase !== 'implement' && activePhase !== 'sync') {
57
59
  context.push(
58
- `[morph-spec] WARNING: Feature '${name}' is in '${feature.phase}' phase, not 'implement'.` +
60
+ `[morph-spec] WARNING: Feature '${name}' is in '${activePhase}' phase, not 'implement'.` +
59
61
  ` Complete the current phase first or advance: morph-spec phase advance ${name}`
60
62
  );
61
63
  }
62
64
 
65
+ // Coding intent during implement phase but no task in_progress
66
+ if (wantsToCode && activePhase === 'implement') {
67
+ let hasActiveTask = feature.tasks?.inProgress > 0;
68
+ if (!hasActiveTask && feature.taskList) {
69
+ hasActiveTask = feature.taskList.some(t => t.status === 'in_progress');
70
+ }
71
+ if (!hasActiveTask) {
72
+ context.push(
73
+ `[morph-spec] REMINDER: No task is currently in_progress for '${name}'.` +
74
+ ` Start a task before coding:\n` +
75
+ ` npx morph-spec task next ${name} # see what's next\n` +
76
+ ` npx morph-spec task start ${name} <id> # mark it in progress`
77
+ );
78
+ }
79
+ }
80
+
63
81
  // Check for approval intent
64
82
  const approvalKeywords = ['approve', 'approved', 'looks good', 'lgtm', 'ship it'];
65
83
  const wantsToApprove = approvalKeywords.some(kw => promptLower.includes(kw));
@@ -75,7 +93,7 @@ try {
75
93
 
76
94
  // Check for "next task" intent
77
95
  if (promptLower.includes('next task') || promptLower.includes('what\'s next')) {
78
- if (feature.phase === 'implement') {
96
+ if (activePhase === 'implement') {
79
97
  context.push(
80
98
  `[morph-spec] Use: morph-spec task next ${name}`
81
99
  );
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Shared helpers for post-compact context restoration.
3
+ *
4
+ * Used by:
5
+ * - pre-compact hook: buildRichContext() to enrich memory file
6
+ * - post-compact-restore hook: buildRestoreBlock() + findLatestMemoryFile() to inject context
7
+ *
8
+ * Pure functions — no I/O.
9
+ */
10
+
11
+ export const DECISIONS_MAX_CHARS = 1500;
12
+ export const MAX_PENDING_TASKS = 8;
13
+
14
+ /**
15
+ * Build the richContext object to embed in pre-compact memory file.
16
+ * @param {Object} feature - Feature state object (tasks, taskList)
17
+ * @param {string} phase - Current derived phase string
18
+ * @param {string} decisionsContent - Raw text of decisions.md (may be empty string)
19
+ * @returns {Object} richContext
20
+ */
21
+ export function buildRichContext(feature, phase, decisionsContent) {
22
+ const taskList = Array.isArray(feature.taskList) ? feature.taskList : [];
23
+
24
+ const inProgress = taskList
25
+ .filter(t => t.status === 'in_progress')
26
+ .map(t => t.id);
27
+
28
+ const nextPending = taskList
29
+ .filter(t => t.status === 'pending')
30
+ .slice(0, MAX_PENDING_TASKS)
31
+ .map(t => ({ id: t.id, title: t.title }));
32
+
33
+ const decisionsSnippet = decisionsContent.length > DECISIONS_MAX_CHARS
34
+ ? decisionsContent.slice(0, DECISIONS_MAX_CHARS)
35
+ : decisionsContent;
36
+
37
+ return {
38
+ phase,
39
+ tasks: feature.tasks || {},
40
+ decisionsSnippet,
41
+ inProgress,
42
+ nextPending,
43
+ };
44
+ }
45
+
46
+ /**
47
+ * Build the context block string to inject in session-start after compact.
48
+ * @param {Object} memorySnapshot - Parsed pre-compact-*.json file contents
49
+ * @returns {string|null} Block string, or null if no richContext present
50
+ */
51
+ export function buildRestoreBlock(memorySnapshot) {
52
+ const rc = memorySnapshot?.richContext;
53
+ if (!rc) return null;
54
+
55
+ const { timestamp, activeFeature } = memorySnapshot;
56
+ const { phase, tasks, decisionsSnippet, inProgress, nextPending } = rc;
57
+
58
+ const done = tasks?.completed ?? '?';
59
+ const total = tasks?.total ?? '?';
60
+
61
+ const lines = [
62
+ `\uD83D\uDD04 POST-COMPACT RESTORE \u2014 context from pre-compact snapshot (${timestamp})`,
63
+ `Active feature: ${activeFeature} | Phase: ${phase} | Tasks: ${done}/${total}`,
64
+ ];
65
+
66
+ if (decisionsSnippet) {
67
+ lines.push('');
68
+ lines.push('Key decisions (at time of compact):');
69
+ lines.push(decisionsSnippet);
70
+ }
71
+
72
+ if (inProgress.length > 0) {
73
+ lines.push('');
74
+ lines.push(`In progress at compact time: [${inProgress.join(', ')}]`);
75
+ }
76
+
77
+ if (nextPending.length > 0) {
78
+ lines.push('');
79
+ lines.push('Next pending at compact time:');
80
+ for (const t of nextPending) {
81
+ lines.push(` [${t.id}] ${t.title}`);
82
+ }
83
+ }
84
+
85
+ return lines.join('\n');
86
+ }
87
+
88
+ /**
89
+ * Find the most recent pre-compact memory file from a list of filenames.
90
+ * Files named pre-compact-{ISO-timestamp-sanitized}.json — sort descending picks latest.
91
+ * @param {string[]} filenames - Array of filenames in the memory directory
92
+ * @returns {string|null} Latest filename, or null if none found
93
+ */
94
+ export function findLatestMemoryFile(filenames) {
95
+ const matches = filenames
96
+ .filter(f => f.startsWith('pre-compact-') && f.endsWith('.json'))
97
+ .sort()
98
+ .reverse();
99
+ return matches.length > 0 ? matches[0] : null;
100
+ }
@@ -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 */
@@ -31,6 +32,7 @@ export const OUTPUT_PHASE_MAP = {
31
32
  contracts: 'design',
32
33
  contractsTs: 'design',
33
34
  contractsVsa: 'design',
35
+ plan: 'plan',
34
36
  tasks: 'tasks',
35
37
  uiDesignSystem: 'uiux',
36
38
  uiMockups: 'uiux',
@@ -49,6 +51,7 @@ export const FILENAME_TO_OUTPUT = {
49
51
  'contracts.cs': 'contracts',
50
52
  'contracts.ts': 'contractsTs',
51
53
  'contracts-vsa.cs': 'contractsVsa',
54
+ 'plan.md': 'plan',
52
55
  'tasks.md': 'tasks',
53
56
  'design-system.md': 'uiDesignSystem',
54
57
  'mockups.md': 'uiMockups',
@@ -65,6 +68,7 @@ export const PROTECTED_SPEC_FILES = {
65
68
  'contracts.cs': 'design',
66
69
  'contracts.ts': 'design',
67
70
  'contracts-vsa.cs': 'design',
71
+ 'plan.md': 'plan',
68
72
  'tasks.md': 'tasks',
69
73
  'design-system.md': 'uiux',
70
74
  'mockups.md': 'uiux',
@@ -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
@@ -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'],
@@ -82,6 +82,7 @@
82
82
  ],
83
83
  "agentTiers": [1, 2, 3, 4],
84
84
  "requiredSkills": [
85
+ { "trigger": "beforePhaseStart", "skill": "morph:phase-design" },
85
86
  { "trigger": "beforePhaseStart", "skill": "morph:phase-codebase-analysis" },
86
87
  { "trigger": "beforeArchitecturalDecision", "skill": "morph:brainstorming" },
87
88
  { "trigger": "onExternalServiceIntegration", "skill": "morph:simulation-checklist" },
@@ -104,13 +105,36 @@
104
105
  "pausePoints": [],
105
106
  "agentTiers": [1, 2],
106
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" },
107
131
  { "trigger": "beforePhaseComplete", "skill": "morph:verification-before-completion" }
108
132
  ]
109
133
  },
110
134
  "tasks": {
111
135
  "id": "tasks",
112
- "index": 4,
113
- "folder": "3-tasks",
136
+ "index": 6,
137
+ "folder": "4-tasks",
114
138
  "displayName": "Task Breakdown",
115
139
  "goals": [
116
140
  "Break approved spec into numbered, atomic tasks",
@@ -125,15 +149,14 @@
125
149
  ],
126
150
  "agentTiers": [1, 2, 3],
127
151
  "requiredSkills": [
128
- { "trigger": "beforePhaseStart", "skill": "superpowers:writing-plans" },
129
152
  { "trigger": "onParallelTasksIdentified", "skill": "superpowers:dispatching-parallel-agents" },
130
153
  { "trigger": "beforePhaseComplete", "skill": "morph:verification-before-completion" }
131
154
  ]
132
155
  },
133
156
  "implement": {
134
157
  "id": "implement",
135
- "index": 5,
136
- "folder": "4-implement",
158
+ "index": 7,
159
+ "folder": "5-implement",
137
160
  "displayName": "Implementation",
138
161
  "goals": [
139
162
  "Execute approved tasks in order",
@@ -143,7 +166,7 @@
143
166
  ],
144
167
  "requiredOutputs": ["recap"],
145
168
  "optionalOutputs": [],
146
- "recommendedMCPs": ["supabase", "context7", "playwright", "github", "docker", "azure"],
169
+ "recommendedMCPs": ["supabase", "context7", "playwright", "github", "vercel", "docker", "azure"],
147
170
  "pausePoints": [],
148
171
  "agentTiers": [1, 2, 3, 4],
149
172
  "requiredSkills": [
@@ -157,7 +180,7 @@
157
180
  },
158
181
  "sync": {
159
182
  "id": "sync",
160
- "index": 6,
183
+ "index": 8,
161
184
  "folder": null,
162
185
  "displayName": "Sync Standards",
163
186
  "optional": true,
@@ -2,6 +2,9 @@
2
2
  paths:
3
3
  - "**/*.cs"
4
4
  - "**/*.csproj"
5
+ stacks:
6
+ - dotnet
7
+ - blazor
5
8
  ---
6
9
 
7
10
  # C# and .NET Standards
@@ -3,6 +3,8 @@ paths:
3
3
  - "**/*.razor"
4
4
  - "**/*.css"
5
5
  - "**/*.scss"
6
+ stacks:
7
+ - blazor
6
8
  ---
7
9
 
8
10
  # Frontend Standards
@@ -5,6 +5,9 @@ paths:
5
5
  - "**/docker-compose*.yml"
6
6
  - "**/pipelines/**"
7
7
  - "**/.github/workflows/**"
8
+ stacks:
9
+ - azure
10
+ - docker
8
11
  ---
9
12
 
10
13
  # Infrastructure Standards