@polymorphism-tech/morph-spec 4.9.0 → 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 (124) 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 +99 -98
  8. package/framework/agents.json +37 -7
  9. package/framework/commands/commit.md +166 -0
  10. package/framework/commands/morph-apply.md +13 -2
  11. package/framework/commands/morph-archive.md +8 -2
  12. package/framework/commands/morph-infra.md +6 -0
  13. package/framework/commands/morph-preflight.md +6 -0
  14. package/framework/commands/morph-proposal.md +56 -7
  15. package/framework/commands/morph-status.md +6 -0
  16. package/framework/commands/morph-troubleshoot.md +6 -0
  17. package/framework/hooks/claude-code/notification/approval-reminder.js +3 -2
  18. package/framework/hooks/claude-code/post-tool-use/dispatch.js +154 -31
  19. package/framework/hooks/claude-code/post-tool-use/skill-reminder.js +7 -84
  20. package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +8 -17
  21. package/framework/hooks/claude-code/pre-compact/save-morph-context.js +16 -3
  22. package/framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js +4 -3
  23. package/framework/hooks/claude-code/pre-tool-use/protect-spec-files.js +3 -2
  24. package/framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js +60 -0
  25. package/framework/hooks/claude-code/session-start/inject-morph-context.js +55 -2
  26. package/framework/hooks/claude-code/session-start/post-compact-restore.js +41 -0
  27. package/framework/hooks/claude-code/stop/validate-completion.js +2 -15
  28. package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +23 -5
  29. package/framework/hooks/shared/compact-restore.js +100 -0
  30. package/framework/hooks/shared/dispatch-helpers.js +116 -0
  31. package/framework/hooks/shared/phase-utils.js +9 -5
  32. package/framework/hooks/shared/state-reader.js +27 -3
  33. package/framework/phases.json +30 -7
  34. package/framework/rules/morph-workflow.md +88 -86
  35. package/framework/skills/level-0-meta/mcp-registry.json +86 -51
  36. package/framework/skills/level-0-meta/{brainstorming → morph-brainstorming}/SKILL.md +13 -16
  37. package/framework/skills/level-0-meta/{code-review → morph-code-review}/SKILL.md +1 -1
  38. package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/SKILL.md +2 -2
  39. package/framework/skills/level-0-meta/{frontend-review → morph-frontend-review}/SKILL.md +5 -5
  40. package/framework/skills/level-0-meta/morph-init/SKILL.md +72 -7
  41. package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/SKILL.md +9 -9
  42. package/framework/skills/level-0-meta/morph-replicate/SKILL.md +1 -1
  43. package/framework/skills/level-0-meta/{terminal-title → morph-terminal-title}/SKILL.md +1 -1
  44. package/framework/skills/level-0-meta/{tool-usage-guide → morph-tool-usage-guide}/SKILL.md +2 -3
  45. package/framework/skills/level-0-meta/{tool-usage-guide → morph-tool-usage-guide}/references/tools-per-phase.md +1 -2
  46. package/framework/skills/level-0-meta/{verification-before-completion → morph-verification-before-completion}/SKILL.md +1 -1
  47. package/framework/skills/level-0-meta/{verification-before-completion → morph-verification-before-completion}/scripts/check-phase-outputs.mjs +2 -2
  48. package/framework/skills/level-1-workflows/morph-phase-clarify/SKILL.md +238 -0
  49. package/framework/skills/level-1-workflows/{phase-codebase-analysis → morph-phase-codebase-analysis}/SKILL.md +251 -251
  50. package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +507 -0
  51. package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/SKILL.md +590 -491
  52. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/code-quality-reviewer-prompt.md +50 -0
  53. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/implementer-prompt.md +45 -0
  54. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/spec-reviewer-prompt.md +47 -0
  55. package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +254 -0
  56. package/framework/skills/level-1-workflows/{phase-setup → morph-phase-setup}/SKILL.md +237 -194
  57. package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/SKILL.md +307 -270
  58. package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/scripts/validate-tasks.mjs +3 -3
  59. package/framework/skills/level-1-workflows/{phase-uiux → morph-phase-uiux}/SKILL.md +320 -285
  60. package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +97 -0
  61. package/framework/standards/integration/mcp/mcp-tools.md +25 -7
  62. package/framework/templates/docs/onboarding.md +2 -2
  63. package/package.json +1 -2
  64. package/src/commands/agents/dispatch-agents.js +50 -3
  65. package/src/commands/mcp/mcp-setup.js +39 -2
  66. package/src/commands/phase/phase-reset.js +74 -0
  67. package/src/commands/project/doctor.js +19 -5
  68. package/src/commands/scope/escalate.js +215 -0
  69. package/src/commands/state/advance-phase.js +27 -53
  70. package/src/commands/state/state.js +1 -1
  71. package/src/commands/task/expand.js +100 -0
  72. package/src/core/paths/output-schema.js +4 -3
  73. package/src/core/state/phase-state-machine.js +7 -4
  74. package/src/core/state/state-manager.js +4 -3
  75. package/src/lib/detectors/claude-config-detector.js +93 -347
  76. package/src/lib/detectors/design-system-detector.js +189 -189
  77. package/src/lib/detectors/index.js +155 -57
  78. package/src/lib/generators/context-generator.js +2 -2
  79. package/src/lib/installers/mcp-installer.js +37 -5
  80. package/src/lib/phase-chain/phase-validator.js +22 -16
  81. package/src/lib/scope/impact-analyzer.js +106 -0
  82. package/src/lib/tasks/task-parser.js +1 -1
  83. package/src/lib/validators/shared/emit-validator-dispatch.js +64 -0
  84. package/src/scripts/setup-infra.js +15 -0
  85. package/src/utils/agents-installer.js +32 -12
  86. package/src/utils/file-copier.js +0 -1
  87. package/src/utils/hooks-installer.js +15 -1
  88. package/framework/skills/level-1-workflows/phase-clarify/SKILL.md +0 -216
  89. package/framework/skills/level-1-workflows/phase-design/SKILL.md +0 -383
  90. package/src/commands/project/index.js +0 -8
  91. package/src/core/index.js +0 -10
  92. package/src/core/state/index.js +0 -8
  93. package/src/core/templates/index.js +0 -9
  94. package/src/core/templates/template-data-sources.js +0 -325
  95. package/src/core/workflows/index.js +0 -7
  96. package/src/lib/detectors/config-detector.js +0 -223
  97. package/src/lib/detectors/standards-generator.js +0 -335
  98. package/src/lib/detectors/structure-detector.js +0 -275
  99. package/src/lib/monitor/agent-resolver.js +0 -144
  100. package/src/lib/monitor/renderer.js +0 -230
  101. package/src/lib/orchestration/index.js +0 -7
  102. package/src/lib/orchestration/team-orchestrator.js +0 -404
  103. package/src/sanitizer/context-sanitizer.js +0 -221
  104. package/src/sanitizer/patterns.js +0 -163
  105. package/src/writer/file-writer.js +0 -86
  106. /package/framework/skills/level-0-meta/{brainstorming → morph-brainstorming}/references/proposal-example.md +0 -0
  107. /package/framework/skills/level-0-meta/{code-review → morph-code-review}/references/review-example.md +0 -0
  108. /package/framework/skills/level-0-meta/{code-review → morph-code-review}/references/review-guidelines.md +0 -0
  109. /package/framework/skills/level-0-meta/{code-review → morph-code-review}/scripts/scan-csharp.mjs +0 -0
  110. /package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/references/review-example-nextjs.md +0 -0
  111. /package/framework/skills/level-0-meta/{code-review-nextjs → morph-code-review-nextjs}/scripts/scan-nextjs.mjs +0 -0
  112. /package/framework/skills/level-0-meta/{frontend-review → morph-frontend-review}/scripts/scan-accessibility.mjs +0 -0
  113. /package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/scripts/detect-dev-server.mjs +0 -0
  114. /package/framework/skills/level-0-meta/{post-implementation → morph-post-implementation}/scripts/detect-stack.mjs +0 -0
  115. /package/framework/skills/level-0-meta/{simulation-checklist → morph-simulation-checklist}/SKILL.md +0 -0
  116. /package/framework/skills/level-0-meta/{terminal-title → morph-terminal-title}/scripts/set_title.sh +0 -0
  117. /package/framework/skills/level-1-workflows/{phase-clarify → morph-phase-clarify}/references/clarifications-example.md +0 -0
  118. /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/architecture-analysis-guide.md +0 -0
  119. /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/spec-authoring-guide.md +0 -0
  120. /package/framework/skills/level-1-workflows/{phase-design → morph-phase-design}/references/spec-example.md +0 -0
  121. /package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/references/recap-example.md +0 -0
  122. /package/framework/skills/level-1-workflows/{phase-implement → morph-phase-implement}/references/vsa-implementation-guide.md +0 -0
  123. /package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/references/task-planning-patterns.md +0 -0
  124. /package/framework/skills/level-1-workflows/{phase-tasks → morph-phase-tasks}/references/tasks-example.md +0 -0
@@ -12,7 +12,7 @@ import { execSync } from 'child_process';
12
12
  import { installMcpServers } from '../../utils/claude-settings-manager.js';
13
13
 
14
14
  /**
15
- * Load the enhanced MCP registry (v2.0.0)
15
+ * Load the enhanced MCP registry (v3.0.0)
16
16
  * @returns {Object} Registry with install blocks
17
17
  */
18
18
  export function loadMcpRegistry() {
@@ -96,14 +96,26 @@ export function checkPrerequisites(mcpEntry) {
96
96
  return results;
97
97
  }
98
98
 
99
+ /**
100
+ * Check if an MCP config uses remote HTTP transport (no local process).
101
+ * @param {Object} config - MCP server config
102
+ * @returns {boolean}
103
+ */
104
+ export function isRemoteMcp(config) {
105
+ return config.type === 'http' || config.url != null;
106
+ }
107
+
99
108
  /**
100
109
  * Patch MCP config for the current platform.
101
110
  * On Windows, npx must be invoked via `cmd /c` to avoid ENOENT errors.
102
111
  * Registry already stores cmd/c format; this handles legacy configs that still use npx directly.
103
- * @param {Object} config - MCP server config { command, args, env? }
112
+ * Remote HTTP MCPs are returned as-is (no local process to patch).
113
+ * @param {Object} config - MCP server config { command, args, env? } or { type: "http", url }
104
114
  * @returns {Object} Patched config (new object, original unmodified)
105
115
  */
106
116
  export function patchConfigForPlatform(config) {
117
+ // Remote MCPs need no platform patching
118
+ if (isRemoteMcp(config)) return config;
107
119
  if (config.command !== 'npx') return config;
108
120
  return {
109
121
  ...config,
@@ -123,9 +135,12 @@ export async function installAutoMcps(targetPath, mcpsToInstall) {
123
135
 
124
136
  for (const [name, entry] of Object.entries(mcpsToInstall)) {
125
137
  let config = { ...entry.install.config };
126
- // Only include env if it has actual values
127
- if (config.env && Object.values(config.env).every(v => !v)) {
128
- delete config.env;
138
+ // Remote MCPs don't have env to strip
139
+ if (!isRemoteMcp(config)) {
140
+ // Only include env if it has actual values
141
+ if (config.env && Object.values(config.env).every(v => !v)) {
142
+ delete config.env;
143
+ }
129
144
  }
130
145
  servers[name] = patchConfigForPlatform(config);
131
146
  }
@@ -156,6 +171,23 @@ export async function installMcpWithCredentials(targetPath, name, mcpEntry, cred
156
171
  export function generateSetupInstructions(name, mcpEntry) {
157
172
  const config = mcpEntry.install.config;
158
173
  const credentials = mcpEntry.install.credentials || [];
174
+ const setupGuide = mcpEntry.install.setupGuide;
175
+
176
+ // Remote HTTP MCPs — show claude mcp add command instead of config snippet
177
+ if (isRemoteMcp(config)) {
178
+ const configSnippet = JSON.stringify({ [name]: config }, null, 2);
179
+ const credentialUrls = credentials.map(c => ({
180
+ name: c.name,
181
+ envVar: c.envVar,
182
+ helpUrl: c.helpUrl
183
+ }));
184
+ return {
185
+ configSnippet,
186
+ credentialUrls,
187
+ cliCommand: setupGuide || `claude mcp add --transport http ${name} ${config.url}`,
188
+ isRemote: true
189
+ };
190
+ }
159
191
 
160
192
  // Build the config snippet with placeholder values
161
193
  const snippetConfig = { ...config };
@@ -10,7 +10,6 @@
10
10
  */
11
11
 
12
12
  import fs from 'fs';
13
- import { existsSync } from 'fs';
14
13
  import path, { join } from 'path';
15
14
  import { loadState, derivePhase, getFeature } from '../../core/state/state-manager.js';
16
15
  import { getWorkflowConfig } from '../../core/workflows/workflow-detector.js';
@@ -53,22 +52,28 @@ export const PHASES = {
53
52
  requiredOutputs: ['proposal.md', 'spec.md'],
54
53
  description: 'Clarify ambiguities and edge cases'
55
54
  },
56
- 'tasks': {
55
+ 'plan': {
57
56
  order: 5,
58
- name: 'FASE 4: TASKS',
57
+ name: 'FASE 4: PLAN',
59
58
  requiredOutputs: ['proposal.md', 'spec.md'],
59
+ description: 'Planning phase before task breakdown'
60
+ },
61
+ 'tasks': {
62
+ order: 6,
63
+ name: 'FASE 5: TASKS',
64
+ requiredOutputs: ['proposal.md', 'spec.md', 'plan.md'],
60
65
  description: 'Break down into executable tasks'
61
66
  },
62
67
  'implement': {
63
- order: 6,
64
- name: 'FASE 5: IMPLEMENT',
65
- requiredOutputs: ['proposal.md', 'spec.md'],
68
+ order: 7,
69
+ name: 'FASE 6: IMPLEMENT',
70
+ requiredOutputs: ['proposal.md', 'spec.md', 'tasks.md'],
66
71
  description: 'Execute tasks and implement code'
67
72
  },
68
73
  'sync': {
69
- order: 7,
70
- name: 'FASE 6: SYNC',
71
- requiredOutputs: ['proposal.md', 'spec.md', 'decisions.md'],
74
+ order: 8,
75
+ name: 'FASE 7: SYNC',
76
+ requiredOutputs: ['proposal.md', 'spec.md', 'decisions.md', 'recap.md'],
72
77
  description: 'Sync decisions to project standards',
73
78
  optional: true
74
79
  }
@@ -86,8 +91,9 @@ const OUTPUT_PHASE_MAP = {
86
91
  'ui-mockups.md': '2-ui',
87
92
  'ui-components.md': '2-ui',
88
93
  'ui-flows.md': '2-ui',
89
- 'tasks.md': '3-tasks',
90
- 'recap.md': '4-implement'
94
+ 'plan.md': '3-plan',
95
+ 'tasks.md': '4-tasks',
96
+ 'recap.md': '5-implement'
91
97
  };
92
98
 
93
99
  function checkOutput(featurePath, outputFile) {
@@ -133,12 +139,12 @@ export function validatePhase(featureName, targetPhase) {
133
139
 
134
140
  // Special validation: implement phase requires tasks.md to have actual tasks
135
141
  if (targetPhase === 'implement') {
136
- const tasksFilePath = path.join(featurePath, '3-tasks', 'tasks.md');
142
+ const tasksFilePath = path.join(featurePath, '4-tasks', 'tasks.md');
137
143
  if (fs.existsSync(tasksFilePath)) {
138
144
  const content = fs.readFileSync(tasksFilePath, 'utf-8');
139
145
  const taskCount = (content.match(/^###\s+T\d+/gm) || []).length;
140
146
  if (taskCount === 0) {
141
- missingOutputs.push(`tasks.md exists but has no task entries — add ### T001 Task title entries`);
147
+ missingOutputs.push(`tasks.md exists but has no task entries — add ### T001: Task title entries`);
142
148
  }
143
149
  }
144
150
  }
@@ -172,7 +178,7 @@ export function validatePhase(featureName, targetPhase) {
172
178
  // Phase eligibility
173
179
  // ─────────────────────────────────────────────────────────────────────────────
174
180
 
175
- const PHASE_ORDER = ['proposal', 'setup', 'uiux', 'design', 'clarify', 'tasks', 'implement', 'sync'];
181
+ const PHASE_ORDER = ['proposal', 'setup', 'uiux', 'design', 'clarify', 'plan', 'tasks', 'implement', 'sync'];
176
182
 
177
183
  function getNextPhase(currentPhase) {
178
184
  const idx = PHASE_ORDER.indexOf(currentPhase);
@@ -196,7 +202,7 @@ const OUTPUT_PATH_MAP = {
196
202
  'proposal': '0-proposal/proposal.md',
197
203
  'spec': '1-design/spec.md',
198
204
  'contracts': '1-design/contracts.cs',
199
- 'tasks': '3-tasks/tasks.md',
205
+ 'tasks': '4-tasks/tasks.md',
200
206
  'schemaAnalysis': '1-design/schema-analysis.md',
201
207
  };
202
208
 
@@ -209,7 +215,7 @@ function getMissingRequiredOutputs(featureName, phase, projectPath) {
209
215
 
210
216
  for (const output of phaseDef.requiredOutputs) {
211
217
  const relPath = OUTPUT_PATH_MAP[output];
212
- if (relPath && !existsSync(join(featureBase, relPath))) {
218
+ if (relPath && !fs.existsSync(join(featureBase, relPath))) {
213
219
  missing.push(output);
214
220
  }
215
221
  }
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Impact Analyzer for Scope Escalation
3
+ *
4
+ * Analyzes tasks.md + spec.md to determine the impact of a discovered
5
+ * complexity and recommend an escalation action.
6
+ *
7
+ * Pure functions — no I/O, no state mutations.
8
+ */
9
+
10
+ /**
11
+ * Classify impact based on affected task count and spec status.
12
+ *
13
+ * @param {{ affectedTasks: string[], specNeedsUpdate: boolean }} input
14
+ * @returns {{ impact: 'low'|'medium'|'high', recommendation: string, targetPhase?: string }}
15
+ */
16
+ export function classifyImpact({ affectedTasks, specNeedsUpdate }) {
17
+ if (specNeedsUpdate) {
18
+ return { impact: 'high', recommendation: 'regress-design', targetPhase: 'design' };
19
+ }
20
+ if (affectedTasks.length <= 3) {
21
+ return { impact: 'low', recommendation: 'expand' };
22
+ }
23
+ return { impact: 'medium', recommendation: 'regress-tasks', targetPhase: 'tasks' };
24
+ }
25
+
26
+ /**
27
+ * Parse task IDs from tasks.md content.
28
+ * @param {string} tasksMd
29
+ * @returns {string[]} Ordered task IDs
30
+ */
31
+ function parseTaskIds(tasksMd) {
32
+ const ids = [];
33
+ const re = /^###\s+(T\d+)\s*[—–:\-]/gm;
34
+ let match;
35
+ while ((match = re.exec(tasksMd)) !== null) {
36
+ ids.push(match[1]);
37
+ }
38
+ return ids;
39
+ }
40
+
41
+ /**
42
+ * Find consecutive task IDs starting from a trigger task.
43
+ * Tasks are "consecutive" if their numeric IDs have no gaps.
44
+ *
45
+ * @param {string[]} allIds - All task IDs in order
46
+ * @param {string} triggerId - The trigger task ID
47
+ * @returns {string[]} Consecutive group including trigger
48
+ */
49
+ function findConsecutiveGroup(allIds, triggerId) {
50
+ const triggerIdx = allIds.indexOf(triggerId);
51
+ if (triggerIdx === -1) return [triggerId];
52
+
53
+ const numOf = id => parseInt(id.replace(/\D/g, ''), 10);
54
+
55
+ const group = [triggerId];
56
+
57
+ // Look forward
58
+ for (let i = triggerIdx + 1; i < allIds.length; i++) {
59
+ const prevNum = numOf(allIds[i - 1]);
60
+ const currNum = numOf(allIds[i]);
61
+ if (currNum - prevNum <= 1) {
62
+ group.push(allIds[i]);
63
+ } else {
64
+ break;
65
+ }
66
+ }
67
+
68
+ // Look backward
69
+ for (let i = triggerIdx - 1; i >= 0; i--) {
70
+ const nextNum = numOf(allIds[i + 1]);
71
+ const currNum = numOf(allIds[i]);
72
+ if (nextNum - currNum <= 1) {
73
+ group.unshift(allIds[i]);
74
+ } else {
75
+ break;
76
+ }
77
+ }
78
+
79
+ return group;
80
+ }
81
+
82
+ /**
83
+ * Analyze the impact of a scope escalation.
84
+ *
85
+ * @param {{ triggerTaskId: string, tasksMd: string, specMd: string, reason: string }} input
86
+ * @returns {{ triggerTask: string, affectedTasks: string[], impact: string, recommendation: string, targetPhase?: string, reason: string }}
87
+ */
88
+ export function analyzeImpact({ triggerTaskId, tasksMd, specMd, reason }) {
89
+ const allIds = parseTaskIds(tasksMd);
90
+ const affectedTasks = findConsecutiveGroup(allIds, triggerTaskId);
91
+
92
+ // Heuristic: detect phrases that indicate the spec itself is wrong, not just casual mentions.
93
+ // Requires phrases like "spec is wrong", "architecture needs", "contract mismatch", etc.
94
+ // Avoids false positives on casual uses like "the design of the component" or "schema file".
95
+ const specPhrases = /\b(spec\s+(is|was|needs|incorrect|wrong|outdated|assumed|missed)|architecture\s+(needs|is wrong|incorrect|mismatch)|contract\s+(mismatch|wrong|incorrect|needs|outdated)|schema\s+(needs|incorrect|wrong|mismatch|change))\b/i;
96
+ const specNeedsUpdate = specPhrases.test(reason);
97
+
98
+ const classification = classifyImpact({ affectedTasks, specNeedsUpdate });
99
+
100
+ return {
101
+ triggerTask: triggerTaskId,
102
+ affectedTasks,
103
+ ...classification,
104
+ reason
105
+ };
106
+ }
@@ -23,7 +23,7 @@ import { join } from 'path';
23
23
  * @returns {Promise<Array<{id, title, status, dependencies, files, checkpoint}>>}
24
24
  */
25
25
  export async function parseTasksMd(featureName, cwd = process.cwd()) {
26
- const tasksPath = join(cwd, `.morph/features/${featureName}/3-tasks/tasks.md`);
26
+ const tasksPath = join(cwd, `.morph/features/${featureName}/4-tasks/tasks.md`);
27
27
  let content = '';
28
28
  try {
29
29
  content = await readFile(tasksPath, 'utf-8');
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Shared utility: emit a VALIDATION DISPATCH block to stdout.
3
+ *
4
+ * Both `advance-phase.js` and `task-manager.js` call this so that the
5
+ * PostToolUse dispatch hook can parse the JSON via strategy 1 (from
6
+ * tool_result) without falling back to a subprocess.
7
+ *
8
+ * @module emit-validator-dispatch
9
+ */
10
+
11
+ import chalk from 'chalk';
12
+ import { readFileSync } from 'fs';
13
+ import { join, dirname } from 'path';
14
+ import { fileURLToPath } from 'url';
15
+
16
+ const __dirname = dirname(fileURLToPath(import.meta.url));
17
+
18
+ /**
19
+ * Emit a VALIDATION DISPATCH block to stdout for the PostToolUse hook.
20
+ *
21
+ * Non-blocking — fails silently on any error.
22
+ *
23
+ * @param {string} featureName - Feature name
24
+ * @param {string} phase - Current phase
25
+ * @param {string} cwd - Project root
26
+ */
27
+ export async function emitValidatorDispatch(featureName, phase, cwd) {
28
+ try {
29
+ const { buildDispatchConfig } = await import('../../../commands/agents/dispatch-agents.js');
30
+ const config = await buildDispatchConfig(cwd, featureName, phase, { mode: 'validate' });
31
+ const validators = config.agents?.filter(a => a.tier === 4);
32
+ if (!validators || validators.length === 0) return;
33
+
34
+ const agentsPath = join(__dirname, '..', '..', '..', '..', 'framework', 'agents.json');
35
+ const agentsData = JSON.parse(readFileSync(agentsPath, 'utf8'));
36
+ const allAgents = agentsData.agents || {};
37
+
38
+ const validatorEntries = validators.map(v => {
39
+ const agentData = allAgents[v.id];
40
+ return {
41
+ id: v.id,
42
+ title: v.title,
43
+ severity: agentData?.hook_behavior?.severity || 'error',
44
+ blocksOnFail: agentData?.hook_behavior?.blocks_on_fail ?? true,
45
+ checks: agentData?.hook_behavior?.validates || [],
46
+ taskPrompt: v.taskPrompt,
47
+ };
48
+ });
49
+
50
+ const dispatch = {
51
+ validationRequired: true,
52
+ phase,
53
+ feature: featureName,
54
+ validators: validatorEntries,
55
+ instruction: 'Dispatch these validators as READ-ONLY subagents before continuing. Each must output { "passed": boolean, "issues": [] }. Blocking validators (blocksOnFail: true) must pass before marking phase complete.',
56
+ };
57
+
58
+ console.log(chalk.cyan('\n--- VALIDATION DISPATCH ---'));
59
+ console.log(JSON.stringify(dispatch, null, 2));
60
+ console.log(chalk.cyan('--- END VALIDATION DISPATCH ---\n'));
61
+ } catch {
62
+ // Non-blocking — fail silently
63
+ }
64
+ }
@@ -228,6 +228,21 @@ export async function setupInfra(targetPath, { _exec = execSync } = {}) {
228
228
  log('Step 18: Updating .gitignore...');
229
229
  await updateGitignore(targetPath);
230
230
 
231
+ // --- 19. Initialize state.json (only if not exists) ---
232
+ log('Step 19: Initializing state.json...');
233
+ const statePath = join(morphPath, 'state.json');
234
+ if (!(await pathExists(statePath))) {
235
+ const dirName = targetPath.split(/[\/\\]/).pop();
236
+ const now = new Date().toISOString();
237
+ await writeJson(statePath, {
238
+ version: '5.0.0',
239
+ project: { name: dirName, type: 'unknown', createdAt: now, updatedAt: now },
240
+ features: {},
241
+ threads: {},
242
+ metadata: { totalFeatures: 0, completedFeatures: 0, totalTimeSpent: 0, lastUpdated: now }
243
+ });
244
+ }
245
+
231
246
  log('setup-infra: complete.');
232
247
 
233
248
  return {
@@ -39,7 +39,9 @@ export async function installAgents(projectDir, frameworkDir = 'framework', opti
39
39
 
40
40
  const DOTNET_STACKS = ['dotnet', 'blazor', 'dotnet-api', 'fullstack'];
41
41
  const eligible = agents.filter(a => {
42
- if (a.tier !== 1 && a.tier !== 2) return false;
42
+ if (a.tier !== 1 && a.tier !== 2 && a.tier !== 4) return false;
43
+ // Tier 4 validators must have a teammate with spawn_prompt
44
+ if (a.tier === 4 && !a.teammate?.spawn_prompt) return false;
43
45
  // Skip dotnet-senior for non-.NET projects
44
46
  if (a.id === 'dotnet-senior' && projectStack && !DOTNET_STACKS.includes(projectStack)) {
45
47
  return false;
@@ -49,9 +51,11 @@ export async function installAgents(projectDir, frameworkDir = 'framework', opti
49
51
 
50
52
  let tier1 = 0;
51
53
  let tier2 = 0;
54
+ let tier4 = 0;
52
55
  for (const agent of eligible) {
53
56
  const slug = agent.id ?? agent.name?.toLowerCase().replace(/\s+/g, '-');
54
- const filename = `morph-${slug}.md`;
57
+ const prefix = agent.tier === 4 ? 'morph-validator-' : 'morph-';
58
+ const filename = `${prefix}${slug}.md`;
55
59
  const targetPath = join(targetDir, filename);
56
60
 
57
61
  const description = buildDescription(agent);
@@ -63,8 +67,9 @@ export async function installAgents(projectDir, frameworkDir = 'framework', opti
63
67
 
64
68
  if (agent.tier === 1) tier1++;
65
69
  else if (agent.tier === 2) tier2++;
70
+ else if (agent.tier === 4) tier4++;
66
71
  }
67
- return { tier1, tier2 };
72
+ return { tier1, tier2, tier4 };
68
73
  }
69
74
 
70
75
  /**
@@ -88,6 +93,13 @@ const TIER_DEFAULTS = {
88
93
  skills: ['morph:checklist'],
89
94
  memory: 'local',
90
95
  },
96
+ 4: {
97
+ model: 'haiku',
98
+ tools: 'Read, Glob, Grep',
99
+ maxTurns: 10,
100
+ skills: [],
101
+ memory: 'project',
102
+ },
91
103
  };
92
104
 
93
105
  /**
@@ -101,19 +113,27 @@ const TIER_DEFAULTS = {
101
113
  function buildFrontmatter(agent, description) {
102
114
  const defaults = TIER_DEFAULTS[agent.tier] ?? TIER_DEFAULTS[2];
103
115
  const name = agent.title ?? agent.name;
104
- const skillsList = defaults.skills.map(s => ` - ${s}`).join('\n');
105
116
 
106
- return [
117
+ // Allow per-agent tool overrides (e.g., morph-spec-validator needs Bash for npm test)
118
+ const tools = agent.teammate?.tools ?? defaults.tools;
119
+ // Allow per-agent maxTurns override
120
+ const maxTurns = agent.teammate?.maxTurns ?? defaults.maxTurns;
121
+
122
+ const lines = [
107
123
  `name: ${name}`,
108
124
  `description: ${description}`,
109
125
  `model: ${defaults.model}`,
110
- `tools: ${defaults.tools}`,
111
- `maxTurns: ${defaults.maxTurns}`,
112
- `skills:`,
113
- skillsList,
114
- `memory: ${defaults.memory}`,
115
- '',
116
- ].join('\n');
126
+ `tools: ${tools}`,
127
+ `maxTurns: ${maxTurns}`,
128
+ ];
129
+
130
+ if (defaults.skills.length > 0) {
131
+ const skillsList = defaults.skills.map(s => ` - ${s}`).join('\n');
132
+ lines.push(`skills:`, skillsList);
133
+ }
134
+
135
+ lines.push(`memory: ${defaults.memory}`, '');
136
+ return lines.join('\n');
117
137
  }
118
138
 
119
139
  function buildDescription(agent) {
@@ -144,7 +144,6 @@ export async function updateGitignore(projectPath) {
144
144
  '.claude/agents/',
145
145
  '.claude/CLAUDE.md',
146
146
  '.claude/settings.local.json',
147
- '.mcp.json',
148
147
  '.worktrees/',
149
148
  ''
150
149
  ];
@@ -15,7 +15,7 @@ import { homedir } from 'os';
15
15
  import { execSync } from 'child_process';
16
16
 
17
17
  /** Current hooks schema version — bump when hook definitions change */
18
- const HOOKS_VERSION = '2.10.0';
18
+ const HOOKS_VERSION = '2.12.0';
19
19
 
20
20
  /** Marker for old dispatch.js (v1) */
21
21
  const OLD_DISPATCH_COMMAND = 'node framework/hooks/agent-teams/dispatch.js';
@@ -63,6 +63,16 @@ const MORPH_HOOKS = [
63
63
  }]
64
64
  },
65
65
 
66
+ // === SessionStart: compact only — post-compact context restore ===
67
+ {
68
+ event: 'SessionStart',
69
+ matcher: 'compact',
70
+ hooks: [{
71
+ type: 'command',
72
+ command: 'node framework/hooks/claude-code/session-start/post-compact-restore.js'
73
+ }]
74
+ },
75
+
66
76
  // === UserPromptSubmit ===
67
77
  {
68
78
  event: 'UserPromptSubmit',
@@ -93,6 +103,10 @@ const MORPH_HOOKS = [
93
103
  {
94
104
  type: 'command',
95
105
  command: 'node framework/hooks/claude-code/pre-tool-use/enforce-phase-writes.js'
106
+ },
107
+ {
108
+ type: 'command',
109
+ command: 'node framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js'
96
110
  }
97
111
  ]
98
112
  },