@polymorphism-tech/morph-spec 4.22.0 → 5.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/CLAUDE.md +57 -71
  2. package/README.md +2 -2
  3. package/bin/morph-spec.js +224 -491
  4. package/claude-plugin.json +1 -1
  5. package/docs/CHEATSHEET.md +1 -1
  6. package/docs/QUICKSTART.md +1 -1
  7. package/framework/CLAUDE.md +55 -38
  8. package/framework/MORPH.md +244 -0
  9. package/framework/agents.json +127 -1198
  10. package/framework/commands/morph-apply.md +56 -240
  11. package/framework/commands/morph-archive.md +48 -85
  12. package/framework/commands/morph-preflight.md +7 -7
  13. package/framework/commands/morph-proposal.md +35 -191
  14. package/framework/commands/morph-status.md +19 -50
  15. package/framework/commands/morph-troubleshoot.md +2 -2
  16. package/framework/evals/rubrics/README.md +44 -0
  17. package/framework/evals/rubrics/architecture.md +46 -0
  18. package/framework/evals/rubrics/code-quality.md +50 -0
  19. package/framework/evals/rubrics/contracts.md +47 -0
  20. package/framework/evals/rubrics/test-coverage.md +50 -0
  21. package/framework/evals/scenarios/dotnet-crud.yaml +42 -0
  22. package/framework/evals/scenarios/nextjs-auth.yaml +38 -0
  23. package/framework/evals/suites/standard.yaml +5 -0
  24. package/framework/evals/trace-schema.md +67 -0
  25. package/framework/hooks/claude-code/core/README.md +3 -0
  26. package/framework/hooks/claude-code/core/denylist.json +14 -0
  27. package/framework/hooks/claude-code/{pre-tool-use → core}/enforce-phase-writes.js +5 -5
  28. package/framework/hooks/claude-code/core/post-edit-typecheck.js +69 -0
  29. package/framework/hooks/claude-code/core/pre-bash-denylist.js +29 -0
  30. package/framework/hooks/claude-code/{pre-tool-use → core}/protect-spec-files.js +4 -6
  31. package/framework/hooks/claude-code/{stop → core}/validate-completion.js +13 -82
  32. package/framework/hooks/shared/phase-utils.js +20 -31
  33. package/framework/hooks/shared/state-reader.js +4 -5
  34. package/framework/{skills/level-0-meta/mcp-registry.json → mcp/registry.json} +254 -243
  35. package/framework/phases.json +41 -115
  36. package/framework/rubrics/eval-rubric.json +66 -0
  37. package/framework/skills/README.md +39 -64
  38. package/framework/skills/core/README.md +3 -0
  39. package/framework/skills/core/morph-brainstorming/SKILL.md +102 -0
  40. package/framework/skills/core/morph-eval/SKILL.md +57 -0
  41. package/framework/skills/core/morph-implement/SKILL.md +83 -0
  42. package/framework/skills/core/morph-plan/SKILL.md +64 -0
  43. package/framework/skills/core/morph-review/SKILL.md +62 -0
  44. package/framework/skills/core/morph-stack-scan/SKILL.md +109 -0
  45. package/framework/skills/core/morph-standards/SKILL.md +49 -0
  46. package/framework/templates/CLAUDE.md.core.guide.md +29 -0
  47. package/framework/templates/CLAUDE.md.core.template +22 -0
  48. package/framework/templates/REGISTRY.json +4 -4
  49. package/framework/workflows/configs/fast-track.json +5 -10
  50. package/framework/workflows/configs/full.json +4 -8
  51. package/framework/workflows/configs/quick.json +32 -0
  52. package/framework/workflows/configs/standard.json +57 -64
  53. package/package.json +7 -6
  54. package/src/commands/mcp/mcp-setup.js +58 -31
  55. package/src/commands/project/doctor.js +69 -70
  56. package/src/commands/project/init.js +26 -5
  57. package/src/commands/project/status.js +18 -19
  58. package/src/commands/state/approve.js +1 -1
  59. package/src/commands/validation/validate-feature.js +18 -112
  60. package/src/core/paths/output-schema.js +133 -152
  61. package/src/core/state/phase-state-machine.js +10 -24
  62. package/src/core/state/state-manager.js +17 -17
  63. package/src/core/workflows/workflow-detector.js +214 -31
  64. package/src/core/workflows/workflow-router.js +204 -0
  65. package/src/dashboard/app/pages/AgentMap.jsx +32 -33
  66. package/src/dashboard/app/pages/LiveState.jsx +1 -1
  67. package/src/dashboard/app/pages/PhasePipeline.jsx +2 -4
  68. package/src/dashboard/app/pages/SkillsLibrary.jsx +2 -3
  69. package/src/dashboard/app/pages/Workflows.jsx +2 -2
  70. package/src/dashboard/scanner/agents-scanner.js +30 -35
  71. package/src/lib/agents/metrics-aggregator.js +1 -1
  72. package/src/lib/agents/output-collector.js +7 -9
  73. package/src/lib/context/context-builder.js +289 -0
  74. package/src/lib/detectors/claude-config-detector.js +19 -3
  75. package/src/lib/generators/context-generator.js +20 -42
  76. package/src/lib/generators/index.js +1 -0
  77. package/src/lib/generators/mandate-generator.js +512 -0
  78. package/src/lib/generators/metadata-extractor.js +1 -1
  79. package/src/lib/generators/recap-generator.js +3 -4
  80. package/src/lib/installers/mcp-installer.js +300 -92
  81. package/src/lib/phase-chain/phase-validator.js +36 -40
  82. package/src/lib/stack/stack-profile.js +74 -91
  83. package/src/lib/tasks/task-parser.js +1 -1
  84. package/src/lib/tasks/wave-resolver.js +353 -0
  85. package/src/lib/validators/checkpoint-persistence.js +2 -2
  86. package/src/lib/validators/content/cross-phase-validator.js +281 -0
  87. package/src/lib/validators/content/index.js +1 -0
  88. package/src/lib/validators/shared/emit-validator-dispatch.js +6 -56
  89. package/src/scripts/global-install.js +2 -27
  90. package/src/utils/agents-installer.js +224 -447
  91. package/src/utils/claude-settings-manager.js +57 -15
  92. package/src/utils/file-copier.js +1 -0
  93. package/src/utils/hooks-installer.js +15 -117
  94. package/src/utils/skills-installer.js +12 -18
  95. package/bin/detect-agents.js +0 -226
  96. package/bin/task-manager.js +0 -1036
  97. package/framework/agents/README.md +0 -13
  98. package/framework/agents/ai-agents/ai-system-architect.md +0 -194
  99. package/framework/agents/ai-agents/vector-search-expert.md +0 -247
  100. package/framework/agents/architecture/architecture-expert.md +0 -98
  101. package/framework/agents/architecture/morph-spec-architect.md +0 -66
  102. package/framework/agents/architecture/packages-validator.md +0 -89
  103. package/framework/agents/architecture/security-expert.md +0 -79
  104. package/framework/agents/architecture/seo-growth-hacker.md +0 -168
  105. package/framework/agents/architecture/standards-architect.md +0 -153
  106. package/framework/agents/backend/dotnet-senior.md +0 -163
  107. package/framework/agents/backend/ef-modeler.md +0 -119
  108. package/framework/agents/backend/hangfire-orchestrator.md +0 -133
  109. package/framework/agents/backend/ms-agent-expert.md +0 -55
  110. package/framework/agents/backend/neon-expert.md +0 -379
  111. package/framework/agents/frontend/css-specialist.md +0 -89
  112. package/framework/agents/frontend/design-system-validator.md +0 -100
  113. package/framework/agents/frontend/gsap-animator.md +0 -131
  114. package/framework/agents/frontend/nextjs-component-validator.md +0 -97
  115. package/framework/agents/frontend/nextjs-expert.md +0 -201
  116. package/framework/agents/frontend/ui-designer.md +0 -259
  117. package/framework/agents/infrastructure/infra-architect.md +0 -215
  118. package/framework/agents/integrations/asaas-financial.md +0 -138
  119. package/framework/agents/integrations/neon-auth.md +0 -151
  120. package/framework/agents/integrations/resend-email.md +0 -127
  121. package/framework/hooks/claude-code/post-tool-use/agent-complete.js +0 -122
  122. package/framework/hooks/claude-code/post-tool-use/dispatch.js +0 -363
  123. package/framework/hooks/claude-code/post-tool-use/handle-tool-failure.js +0 -78
  124. package/framework/hooks/claude-code/post-tool-use/validator-feedback.js +0 -194
  125. package/framework/hooks/claude-code/pre-compact/save-morph-context.js +0 -199
  126. package/framework/hooks/claude-code/pre-tool-use/task-tracking-guard.js +0 -55
  127. package/framework/hooks/claude-code/session-start/inject-morph-context.js +0 -323
  128. package/framework/hooks/claude-code/session-start/post-compact-restore.js +0 -41
  129. package/framework/hooks/claude-code/session-start/resume-context.js +0 -77
  130. package/framework/hooks/claude-code/teammate-idle/teammate-idle.js +0 -90
  131. package/framework/hooks/claude-code/user-prompt/enrich-prompt.js +0 -114
  132. package/framework/hooks/shared/compact-restore.js +0 -123
  133. package/framework/hooks/shared/dispatch-helpers.js +0 -147
  134. package/framework/hooks/shared/skill-reminder-helpers.js +0 -79
  135. package/framework/hooks/shared/stale-task-reset.js +0 -57
  136. package/framework/hooks/shared/worktree-helpers.js +0 -75
  137. package/framework/skills/level-0-meta/README.md +0 -7
  138. package/framework/skills/level-0-meta/morph-agent-teams/SKILL.md +0 -472
  139. package/framework/skills/level-0-meta/morph-brainstorming/SKILL.md +0 -174
  140. package/framework/skills/level-0-meta/morph-brainstorming/references/proposal-example.md +0 -138
  141. package/framework/skills/level-0-meta/morph-checklist/SKILL.md +0 -145
  142. package/framework/skills/level-0-meta/morph-code-review/SKILL.md +0 -250
  143. package/framework/skills/level-0-meta/morph-code-review/references/review-example.md +0 -164
  144. package/framework/skills/level-0-meta/morph-code-review/references/review-guidelines.md +0 -100
  145. package/framework/skills/level-0-meta/morph-code-review/scripts/scan-csharp.mjs +0 -151
  146. package/framework/skills/level-0-meta/morph-code-review-nextjs/SKILL.md +0 -165
  147. package/framework/skills/level-0-meta/morph-code-review-nextjs/references/review-example-nextjs.md +0 -254
  148. package/framework/skills/level-0-meta/morph-code-review-nextjs/scripts/scan-nextjs.mjs +0 -189
  149. package/framework/skills/level-0-meta/morph-frontend-review/SKILL.md +0 -344
  150. package/framework/skills/level-0-meta/morph-frontend-review/scripts/scan-accessibility.mjs +0 -376
  151. package/framework/skills/level-0-meta/morph-gemini-image/SKILL.md +0 -290
  152. package/framework/skills/level-0-meta/morph-init/SKILL.md +0 -537
  153. package/framework/skills/level-0-meta/morph-parallel-dispatch/SKILL.md +0 -225
  154. package/framework/skills/level-0-meta/morph-post-implementation/SKILL.md +0 -401
  155. package/framework/skills/level-0-meta/morph-post-implementation/scripts/detect-dev-server.mjs +0 -153
  156. package/framework/skills/level-0-meta/morph-post-implementation/scripts/detect-stack.mjs +0 -234
  157. package/framework/skills/level-0-meta/morph-simulation-checklist/SKILL.md +0 -106
  158. package/framework/skills/level-0-meta/morph-tool-usage-guide/SKILL.md +0 -243
  159. package/framework/skills/level-0-meta/morph-tool-usage-guide/references/tools-per-phase.md +0 -169
  160. package/framework/skills/level-0-meta/morph-verification-before-completion/SKILL.md +0 -148
  161. package/framework/skills/level-0-meta/morph-verification-before-completion/scripts/check-phase-outputs.mjs +0 -110
  162. package/framework/skills/level-1-workflows/README.md +0 -7
  163. package/framework/skills/level-1-workflows/morph-phase-codebase-analysis/SKILL.md +0 -269
  164. package/framework/skills/level-1-workflows/morph-phase-design/SKILL.md +0 -453
  165. package/framework/skills/level-1-workflows/morph-phase-design/references/architecture-analysis-guide.md +0 -44
  166. package/framework/skills/level-1-workflows/morph-phase-design/references/spec-authoring-guide.md +0 -55
  167. package/framework/skills/level-1-workflows/morph-phase-design/references/spec-example.md +0 -253
  168. package/framework/skills/level-1-workflows/morph-phase-implement/.morph/logs/activity.json +0 -38
  169. package/framework/skills/level-1-workflows/morph-phase-implement/SKILL.md +0 -634
  170. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/code-quality-reviewer-prompt.md +0 -50
  171. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/implementer-prompt.md +0 -54
  172. package/framework/skills/level-1-workflows/morph-phase-implement/prompts/spec-reviewer-prompt.md +0 -47
  173. package/framework/skills/level-1-workflows/morph-phase-implement/references/recap-example.md +0 -132
  174. package/framework/skills/level-1-workflows/morph-phase-implement/references/vsa-implementation-guide.md +0 -92
  175. package/framework/skills/level-1-workflows/morph-phase-plan/SKILL.md +0 -257
  176. package/framework/skills/level-1-workflows/morph-phase-tasks/.morph/logs/activity.json +0 -14
  177. package/framework/skills/level-1-workflows/morph-phase-tasks/SKILL.md +0 -291
  178. package/framework/skills/level-1-workflows/morph-phase-tasks/references/task-planning-patterns.md +0 -143
  179. package/framework/skills/level-1-workflows/morph-phase-tasks/references/tasks-example.md +0 -404
  180. package/framework/skills/level-1-workflows/morph-phase-tasks/scripts/validate-tasks.mjs +0 -112
  181. package/framework/skills/level-1-workflows/morph-phase-uiux/SKILL.md +0 -995
  182. package/framework/skills/level-1-workflows/morph-scope-escalation/SKILL.md +0 -147
  183. package/framework/squad-templates/backend-only.json +0 -47
  184. package/framework/squad-templates/frontend-only.json +0 -56
  185. package/framework/squad-templates/full-stack.json +0 -74
  186. package/src/commands/agents/agent-metrics.js +0 -174
  187. package/src/commands/agents/audit-dispatch.js +0 -82
  188. package/src/commands/agents/detect-agents.js +0 -275
  189. package/src/commands/agents/dispatch-agents.js +0 -461
  190. package/src/commands/agents/index.js +0 -13
  191. package/src/commands/agents/manage-agents.js +0 -352
  192. package/src/commands/agents/project-metrics.js +0 -74
  193. package/src/commands/phase/phase-reset.js +0 -74
  194. package/src/commands/scope/escalate.js +0 -217
  195. package/src/commands/state/advance-phase.js +0 -600
  196. package/src/commands/state/phase-runner.js +0 -215
  197. package/src/commands/state/state.js +0 -444
  198. package/src/commands/task/expand.js +0 -100
  199. package/src/commands/tasks/index.js +0 -4
  200. package/src/commands/tasks/task.js +0 -105
  201. package/src/commands/templates/index.js +0 -5
  202. package/src/commands/templates/template-render.js +0 -93
  203. package/src/commands/trust/trust.js +0 -118
  204. package/src/lib/agents/agents-manager.js +0 -349
  205. package/src/lib/build/build-runner.js +0 -99
  206. package/src/lib/checkpoints/checkpoint-hooks.js +0 -111
  207. package/src/lib/contracts/sprint-contract-generator.js +0 -137
  208. package/src/lib/scope/impact-analyzer.js +0 -106
  209. package/src/lib/trust/trust-manager.js +0 -157
  210. package/src/utils/tier-helpers.js +0 -24
  211. /package/framework/hooks/claude-code/{post-tool-use → core}/loop-detect.js +0 -0
@@ -35,13 +35,9 @@ const OUTPUT_SECTIONS = {
35
35
  'Performance', 'Edge Cases', 'Clarifications'
36
36
  ]
37
37
  },
38
- 'contracts.cs': {
39
- required: ['namespace', 'record', 'interface'],
40
- isCode: true
41
- },
42
- 'schema-analysis.md': {
43
- required: ['Tables Analyzed', 'Columns', 'Relationships'],
44
- optional: ['CRITICAL FINDINGS', 'Recommendations', 'Indexes']
38
+ 'mandate.md': {
39
+ required: ['Stack', 'Standards', 'Done'],
40
+ optional: ['Patterns proibidos', 'Riscos']
45
41
  },
46
42
  'decisions.md': {
47
43
  required: ['ADR', 'Status', 'Context', 'Decision'],
@@ -51,29 +47,32 @@ const OUTPUT_SECTIONS = {
51
47
  required: ['T001', 'category', 'dependencies'],
52
48
  optional: ['CHECKPOINT', 'estimatedMinutes']
53
49
  },
50
+ 'review-report.md': {
51
+ required: ['Scores', 'Recommendation'],
52
+ optional: ['Blockers', 'Improvements']
53
+ },
54
54
  };
55
55
 
56
56
  /**
57
- * Phase-to-output mapping
57
+ * Phase-to-output mapping (v5 NLH)
58
58
  */
59
59
  const PHASE_OUTPUTS = {
60
60
  proposal: ['proposal.md'],
61
61
  uiux: ['design-system.md', 'mockups.md', 'components.md', 'flows.md'],
62
- design: ['spec.md', 'contracts.cs', 'schema-analysis.md', 'decisions.md'],
63
- tasks: ['tasks.md'],
64
- implement: ['spec.md', 'contracts.cs', 'tasks.md']
62
+ plan: ['spec.md', 'mandate.md', 'tasks.md'],
63
+ implement: ['recap.md'],
64
+ review: ['review-report.md'],
65
65
  };
66
66
 
67
67
  /**
68
- * Phase-to-folder mapping (filesystem convention)
68
+ * Phase-to-folder mapping (v5 filesystem convention)
69
69
  */
70
70
  const PHASE_FOLDERS = {
71
71
  proposal: '0-proposal',
72
72
  uiux: '1-ui',
73
- design: '2-design',
74
- plan: '3-plan',
75
- tasks: '4-tasks',
76
- implement: '5-implement',
73
+ plan: '2-plan',
74
+ implement: '3-implement',
75
+ review: '4-review',
77
76
  };
78
77
 
79
78
  /**
@@ -105,13 +104,8 @@ function validateOutput(featurePath, outputFile) {
105
104
  return result;
106
105
  }
107
106
 
108
- if (sections.isCode) {
109
- // C# code validation
110
- validateCSharpContent(content, result);
111
- } else {
112
- // Markdown section validation
113
- validateMarkdownSections(content, sections, result);
114
- }
107
+ // v5: only markdown outputs — contracts moved into spec.md
108
+ validateMarkdownSections(content, sections, result);
115
109
 
116
110
  // Size warnings
117
111
  if (result.lineCount < 10) {
@@ -143,94 +137,6 @@ function validateMarkdownSections(content, sections, result) {
143
137
  }
144
138
  }
145
139
 
146
- /**
147
- * Validate C# contracts content
148
- */
149
- function validateCSharpContent(content, result) {
150
- // Check for namespace
151
- if (/namespace\s+\S+/.test(content)) {
152
- result.sections.found.push('namespace');
153
- } else {
154
- result.sections.missing.push('namespace');
155
- result.errors.push('Missing namespace declaration');
156
- }
157
-
158
- // Check for records (DTOs)
159
- const records = content.match(/public\s+record\s+\w+/g) || [];
160
- if (records.length > 0) {
161
- result.sections.found.push(`${records.length} record(s)`);
162
- } else {
163
- result.warnings.push('No record types found — DTOs expected');
164
- }
165
-
166
- // Check for interfaces
167
- const interfaces = content.match(/public\s+interface\s+I\w+/g) || [];
168
- if (interfaces.length > 0) {
169
- result.sections.found.push(`${interfaces.length} interface(s)`);
170
- } else {
171
- result.warnings.push('No interfaces found — service contracts expected');
172
- }
173
-
174
- // Check for CancellationToken in async methods
175
- const asyncMethods = content.match(/Task<.*>\s+\w+\(/g) || [];
176
- const methodsWithCt = content.match(/CancellationToken\s+ct/g) || [];
177
- if (asyncMethods.length > 0 && methodsWithCt.length < asyncMethods.length) {
178
- result.warnings.push(
179
- `${asyncMethods.length - methodsWithCt.length} async method(s) may be missing CancellationToken`
180
- );
181
- }
182
-
183
- // Check for schema-analysis reference
184
- if (content.includes('schema-analysis')) {
185
- result.sections.found.push('schema reference');
186
- } else {
187
- result.warnings.push('No reference to schema-analysis.md — fields may not match database');
188
- }
189
- }
190
-
191
- /**
192
- * Cross-validate contracts against schema-analysis
193
- */
194
- function crossValidateSchema(featurePath) {
195
- const schemaPath = path.join(featurePath, 'schema-analysis.md');
196
- const contractsPath = path.join(featurePath, 'contracts.cs');
197
- const warnings = [];
198
-
199
- if (!fs.existsSync(schemaPath) || !fs.existsSync(contractsPath)) {
200
- return warnings;
201
- }
202
-
203
- const schema = fs.readFileSync(schemaPath, 'utf8');
204
- const contracts = fs.readFileSync(contractsPath, 'utf8');
205
-
206
- // Extract field names from schema (look for column names in tables)
207
- const schemaFields = new Set();
208
- const columnRegex = /\|\s*(\w+)\s*\|/g;
209
- let match;
210
- while ((match = columnRegex.exec(schema)) !== null) {
211
- const field = match[1].toLowerCase();
212
- if (!['column', 'name', 'type', 'nullable', 'default', 'notes', 'table', '---'].includes(field)) {
213
- schemaFields.add(field);
214
- }
215
- }
216
-
217
- // Extract field names from contracts
218
- const contractFields = new Set();
219
- const fieldRegex = /(?:string|int|Guid|DateTime|bool|decimal|JsonObject)\???\s+(\w+)/g;
220
- while ((match = fieldRegex.exec(contracts)) !== null) {
221
- contractFields.add(match[1].toLowerCase());
222
- }
223
-
224
- // Check for contract fields not in schema
225
- for (const field of contractFields) {
226
- if (schemaFields.size > 0 && !schemaFields.has(field) && field !== 'ct') {
227
- warnings.push(`Contract field "${field}" not found in schema-analysis.md — verify correctness`);
228
- }
229
- }
230
-
231
- return warnings;
232
- }
233
-
234
140
  /**
235
141
  * Main validate command
236
142
  */
@@ -282,7 +188,7 @@ export async function validateFeatureCommand(featureName, options = {}) {
282
188
  const results = phase
283
189
  ? outputFiles.map(file => validateOutput(searchPath, file))
284
190
  : outputFiles.map(({ file, dir }) => validateOutput(dir, file));
285
- const schemaWarnings = crossValidateSchema(featurePath);
191
+ const schemaWarnings = []; // v5: cross-validation removed (contracts in spec.md)
286
192
 
287
193
  // Aggregate results
288
194
  const totalErrors = results.reduce((sum, r) => sum + r.errors.length, 0);
@@ -1,152 +1,133 @@
1
- /**
2
- * Output Schema — Single Source of Truth
3
- *
4
- * ALL output type definitions live here. Edit ONLY this file to add, rename,
5
- * or relocate output types. Derived constants and generated files (phase-utils.js)
6
- * are automatically updated by: node scripts/generate-refs.js
7
- */
8
-
9
- import { join } from 'path';
10
-
11
- // ============================================================================
12
- // Schema Definition
13
- // ============================================================================
14
-
15
- /**
16
- * Complete schema for all 12 output types produced by morph-spec phases.
17
- *
18
- * Fields:
19
- * filename - The file's basename (e.g. 'spec.md')
20
- * phaseDir - Subdirectory within .morph/features/{feature}/ (e.g. '2-design')
21
- * phase - The morph-spec phase that produces this output
22
- * protected - Whether the file requires an approval gate before editing
23
- * approvalGate - (optional) Which gate protects this file
24
- */
25
- export const OUTPUT_SCHEMA = {
26
- proposal: { filename: 'proposal.md', phaseDir: '0-proposal', phase: 'proposal', protected: false },
27
- schemaAnalysis:{ filename: 'schema-analysis.md', phaseDir: '2-design', phase: 'design', protected: true, approvalGate: 'design' },
28
- spec: { filename: 'spec.md', phaseDir: '2-design', phase: 'design', protected: true, approvalGate: 'design' },
29
- contracts: { filename: 'contracts.cs', phaseDir: '2-design', phase: 'design', protected: true, approvalGate: 'design' },
30
- contractsTs: { filename: 'contracts.ts', phaseDir: '2-design', phase: 'design', protected: true, approvalGate: 'design' },
31
- contractsVsa: { filename: 'contracts-vsa.cs', phaseDir: '2-design', phase: 'design', protected: true, approvalGate: 'design' },
32
- plan: { filename: 'plan.md', phaseDir: '3-plan', phase: 'plan', protected: true, approvalGate: 'plan' },
33
- tasks: { filename: 'tasks.md', phaseDir: '4-tasks', phase: 'tasks', protected: true, approvalGate: 'tasks' },
34
- uiDesignSystem:{ filename: 'design-system.md', phaseDir: '1-ui', phase: 'uiux', protected: true, approvalGate: 'uiux' },
35
- uiMockups: { filename: 'mockups.md', phaseDir: '1-ui', phase: 'uiux', protected: true, approvalGate: 'uiux' },
36
- uiComponents: { filename: 'components.md', phaseDir: '1-ui', phase: 'uiux', protected: true, approvalGate: 'uiux' },
37
- uiFlows: { filename: 'flows.md', phaseDir: '1-ui', phase: 'uiux', protected: true, approvalGate: 'uiux' },
38
- stitchHtml: { filename: 'stitch-screen-map.json', phaseDir: '1-ui', phase: 'uiux', protected: true, approvalGate: 'uiux' },
39
- decisions: { filename: 'decisions.md', phaseDir: '2-design', phase: 'design', protected: false },
40
- recap: { filename: 'recap.md', phaseDir: '5-implement', phase: 'implement', protected: false },
41
- };
42
-
43
- // ============================================================================
44
- // Builder Functions (Windows-safe via path.join)
45
- // ============================================================================
46
-
47
- /**
48
- * Get the morph-relative path for a specific output type of a feature.
49
- * Returns forward-slash paths suitable for state.json storage.
50
- *
51
- * @param {string} featureName - The feature name
52
- * @param {string} outputType - camelCase output type key from OUTPUT_SCHEMA
53
- * @returns {string} e.g. ".morph/features/my-feature/2-design/spec.md"
54
- */
55
- export function getOutputPath(featureName, outputType) {
56
- const entry = OUTPUT_SCHEMA[outputType];
57
- if (!entry) {
58
- throw new Error(`Unknown output type: "${outputType}". Valid types: ${Object.keys(OUTPUT_SCHEMA).join(', ')}`);
59
- }
60
- // Use path.join for OS-safe construction, then normalise to forward slashes
61
- return join('.morph', 'features', featureName, entry.phaseDir, entry.filename)
62
- .replace(/\\/g, '/');
63
- }
64
-
65
- /**
66
- * Get the absolute filesystem path for a specific output type.
67
- *
68
- * @param {string} projectRoot - Absolute project root (e.g. process.cwd())
69
- * @param {string} featureName - The feature name
70
- * @param {string} outputType - camelCase output type key from OUTPUT_SCHEMA
71
- * @returns {string} Absolute path using OS path separator
72
- */
73
- export function getAbsoluteOutputPath(projectRoot, featureName, outputType) {
74
- const entry = OUTPUT_SCHEMA[outputType];
75
- if (!entry) {
76
- throw new Error(`Unknown output type: "${outputType}". Valid types: ${Object.keys(OUTPUT_SCHEMA).join(', ')}`);
77
- }
78
- return join(projectRoot, '.morph', 'features', featureName, entry.phaseDir, entry.filename);
79
- }
80
-
81
- /**
82
- * Get the feature base directory (morph-relative, forward slashes).
83
- *
84
- * @param {string} featureName
85
- * @returns {string} e.g. ".morph/features/my-feature"
86
- */
87
- export function getFeatureDir(featureName) {
88
- return join('.morph', 'features', featureName).replace(/\\/g, '/');
89
- }
90
-
91
- /**
92
- * Get all output paths for a feature, ready for use in state.json `outputs` block.
93
- * Returns an object shaped like the state.json outputs for a single feature.
94
- *
95
- * @param {string} featureName
96
- * @returns {Object} e.g. { proposal: { created: false, path: '...' }, ... }
97
- */
98
- export function getAllOutputPaths(featureName) {
99
- const outputs = {};
100
- for (const [key] of Object.entries(OUTPUT_SCHEMA)) {
101
- outputs[key] = {
102
- created: false,
103
- path: getOutputPath(featureName, key),
104
- };
105
- }
106
- return outputs;
107
- }
108
-
109
- // ============================================================================
110
- // Derived Constants (for direct import without calling functions)
111
- // ============================================================================
112
-
113
- /**
114
- * Map filename → output type (camelCase)
115
- * e.g. { 'spec.md': 'spec', 'contracts.cs': 'contracts', ... }
116
- */
117
- export const FILENAME_TO_OUTPUT = Object.fromEntries(
118
- Object.entries(OUTPUT_SCHEMA).map(([key, entry]) => [entry.filename, key])
119
- );
120
-
121
- /**
122
- * Map output type (camelCase) → phase that produces it
123
- * e.g. { spec: 'design', tasks: 'tasks', ... }
124
- */
125
- export const OUTPUT_PHASE_MAP = Object.fromEntries(
126
- Object.entries(OUTPUT_SCHEMA).map(([key, entry]) => [key, entry.phase])
127
- );
128
-
129
- /**
130
- * Protected spec files and the approval gate that locks them.
131
- * Only includes files where protected === true.
132
- * e.g. { 'spec.md': 'design', 'tasks.md': 'tasks', ... }
133
- */
134
- export const PROTECTED_SPEC_FILES = Object.fromEntries(
135
- Object.entries(OUTPUT_SCHEMA)
136
- .filter(([, entry]) => entry.protected && entry.approvalGate)
137
- .map(([, entry]) => [entry.filename, entry.approvalGate])
138
- );
139
-
140
- // ============================================================================
141
- // Stack-aware Helpers
142
- // ============================================================================
143
-
144
- /**
145
- * Resolve the correct contracts output type key for a given stack.
146
- * @param {string|null} stack
147
- * @returns {'contracts'|'contractsTs'}
148
- */
149
- export function resolveContractsOutputType(stack) {
150
- const NEXTJS_STACKS = new Set(['nextjs', 'next', 'next.js']);
151
- return NEXTJS_STACKS.has(stack?.toLowerCase?.()) ? 'contractsTs' : 'contracts';
152
- }
1
+ /**
2
+ * Output Schema — Single Source of Truth (v5)
3
+ *
4
+ * ALL output type definitions live here. Edit ONLY this file to add, rename,
5
+ * or relocate output types.
6
+ */
7
+
8
+ import { join } from 'path';
9
+
10
+ // ============================================================================
11
+ // Schema Definition
12
+ // ============================================================================
13
+
14
+ /**
15
+ * Schema for output types produced by morph-spec NLH phases.
16
+ *
17
+ * Fields:
18
+ * filename - The file's basename (e.g. 'spec.md')
19
+ * phaseDir - Subdirectory within .morph/features/{feature}/
20
+ * phase - The morph-spec phase that produces this output
21
+ * protected - Whether the file requires an approval gate before editing
22
+ * approvalGate - (optional) Which gate protects this file
23
+ */
24
+ export const OUTPUT_SCHEMA = {
25
+ // 0-proposal/
26
+ proposal: { filename: 'proposal.md', phaseDir: '0-proposal', phase: 'proposal', protected: false },
27
+ stackScan: { filename: 'stack-scan.md', phaseDir: '0-proposal', phase: 'proposal', protected: false },
28
+ migrationPlan: { filename: 'migration-plan.md', phaseDir: '0-proposal', phase: 'proposal', protected: false },
29
+
30
+ // 1-ui/ (optional)
31
+ uiDesignSystem: { filename: 'design-system.md', phaseDir: '1-ui', phase: 'uiux', protected: true, approvalGate: 'uiux' },
32
+ uiMockups: { filename: 'mockups.md', phaseDir: '1-ui', phase: 'uiux', protected: true, approvalGate: 'uiux' },
33
+ uiComponents: { filename: 'components.md', phaseDir: '1-ui', phase: 'uiux', protected: true, approvalGate: 'uiux' },
34
+ uiFlows: { filename: 'flows.md', phaseDir: '1-ui', phase: 'uiux', protected: true, approvalGate: 'uiux' },
35
+
36
+ // 2-plan/
37
+ spec: { filename: 'spec.md', phaseDir: '2-plan', phase: 'plan', protected: true, approvalGate: 'plan' },
38
+ mandate: { filename: 'mandate.md', phaseDir: '2-plan', phase: 'plan', protected: true, approvalGate: 'plan' },
39
+ tasks: { filename: 'tasks.md', phaseDir: '2-plan', phase: 'plan', protected: true, approvalGate: 'plan' },
40
+ decisions: { filename: 'decisions.md', phaseDir: '2-plan', phase: 'plan', protected: false },
41
+
42
+ // 3-implement/
43
+ recap: { filename: 'recap.md', phaseDir: '3-implement', phase: 'implement', protected: false },
44
+
45
+ // 4-review/
46
+ reviewReport: { filename: 'review-report.md', phaseDir: '4-review', phase: 'review', protected: false },
47
+ };
48
+
49
+ // ============================================================================
50
+ // Builder Functions (Windows-safe via path.join)
51
+ // ============================================================================
52
+
53
+ /**
54
+ * Get the morph-relative path for a specific output type of a feature.
55
+ *
56
+ * @param {string} featureName
57
+ * @param {string} outputType - camelCase output type key from OUTPUT_SCHEMA
58
+ * @returns {string} e.g. ".morph/features/my-feature/2-plan/spec.md"
59
+ */
60
+ export function getOutputPath(featureName, outputType) {
61
+ const entry = OUTPUT_SCHEMA[outputType];
62
+ if (!entry) {
63
+ throw new Error(`Unknown output type: "${outputType}". Valid types: ${Object.keys(OUTPUT_SCHEMA).join(', ')}`);
64
+ }
65
+ return join('.morph', 'features', featureName, entry.phaseDir, entry.filename)
66
+ .replace(/\\/g, '/');
67
+ }
68
+
69
+ /**
70
+ * Get the absolute filesystem path for a specific output type.
71
+ */
72
+ export function getAbsoluteOutputPath(projectRoot, featureName, outputType) {
73
+ const entry = OUTPUT_SCHEMA[outputType];
74
+ if (!entry) {
75
+ throw new Error(`Unknown output type: "${outputType}". Valid types: ${Object.keys(OUTPUT_SCHEMA).join(', ')}`);
76
+ }
77
+ return join(projectRoot, '.morph', 'features', featureName, entry.phaseDir, entry.filename);
78
+ }
79
+
80
+ /**
81
+ * Get the feature base directory (morph-relative, forward slashes).
82
+ */
83
+ export function getFeatureDir(featureName) {
84
+ return join('.morph', 'features', featureName).replace(/\\/g, '/');
85
+ }
86
+
87
+ /**
88
+ * Get all output paths for a feature, ready for state.json `outputs` block.
89
+ */
90
+ export function getAllOutputPaths(featureName) {
91
+ const outputs = {};
92
+ for (const [key] of Object.entries(OUTPUT_SCHEMA)) {
93
+ outputs[key] = {
94
+ created: false,
95
+ path: getOutputPath(featureName, key),
96
+ };
97
+ }
98
+ return outputs;
99
+ }
100
+
101
+ // ============================================================================
102
+ // Derived Constants
103
+ // ============================================================================
104
+
105
+ export const FILENAME_TO_OUTPUT = Object.fromEntries(
106
+ Object.entries(OUTPUT_SCHEMA).map(([key, entry]) => [entry.filename, key])
107
+ );
108
+
109
+ export const OUTPUT_PHASE_MAP = Object.fromEntries(
110
+ Object.entries(OUTPUT_SCHEMA).map(([key, entry]) => [key, entry.phase])
111
+ );
112
+
113
+ /**
114
+ * Protected spec files and the approval gate that locks them.
115
+ */
116
+ export const PROTECTED_SPEC_FILES = Object.fromEntries(
117
+ Object.entries(OUTPUT_SCHEMA)
118
+ .filter(([, entry]) => entry.protected && entry.approvalGate)
119
+ .map(([, entry]) => [entry.filename, entry.approvalGate])
120
+ );
121
+
122
+ // ============================================================================
123
+ // Backwards-compat shim (will be removed in v5.1)
124
+ // ============================================================================
125
+
126
+ /**
127
+ * v4 had separate contracts.cs / contracts.ts / contracts-vsa.cs output types.
128
+ * In v5 contracts live inside spec.md (or in source files for VSA). This
129
+ * function keeps callers happy until they're updated.
130
+ */
131
+ export function resolveContractsOutputType() {
132
+ return 'spec';
133
+ }
@@ -10,13 +10,11 @@
10
10
  * Each phase can only transition to specific next phases
11
11
  */
12
12
  const VALID_TRANSITIONS = {
13
- 'proposal': ['uiux', 'design'], // Can skip UI/UX if no frontend
14
- 'uiux': ['design'],
15
- 'design': ['plan'],
16
- 'plan': ['tasks'],
17
- 'tasks': ['implement'],
18
- 'implement': ['sync', 'archived'], // Can skip sync
19
- 'sync': ['archived']
13
+ 'proposal': ['uiux', 'plan'],
14
+ 'uiux': ['plan'],
15
+ 'plan': ['implement'],
16
+ 'implement': ['review'],
17
+ 'review': []
20
18
  };
21
19
 
22
20
  /**
@@ -25,18 +23,15 @@ const VALID_TRANSITIONS = {
25
23
  const PHASE_NAMES = {
26
24
  'proposal': 'Proposal (Phase 0)',
27
25
  'uiux': 'UI/UX Design (Phase 1)',
28
- 'design': 'Design (Phase 2)',
29
- 'plan': 'Plan (Phase 3)',
30
- 'tasks': 'Tasks (Phase 4)',
31
- 'implement': 'Implement (Phase 5)',
32
- 'sync': 'Sync (Phase 6)',
33
- 'archived': 'Archived'
26
+ 'plan': 'Plan (Phase 2)',
27
+ 'implement': 'Implement (Phase 3)',
28
+ 'review': 'Quality Review (Phase 4)'
34
29
  };
35
30
 
36
31
  /**
37
32
  * Optional phases that can be skipped
38
33
  */
39
- const OPTIONAL_PHASES = ['uiux', 'sync'];
34
+ const OPTIONAL_PHASES = ['uiux'];
40
35
 
41
36
  /**
42
37
  * Validate if a phase transition is allowed
@@ -116,16 +111,7 @@ export function validateTransition(fromPhase, toPhase) {
116
111
  * @returns {string[]} Ordered list of phases
117
112
  */
118
113
  export function getPhaseSequence() {
119
- return [
120
- 'proposal',
121
- 'uiux', // optional
122
- 'design',
123
- 'plan',
124
- 'tasks',
125
- 'implement',
126
- 'sync', // optional
127
- 'archived'
128
- ];
114
+ return ['proposal', 'uiux', 'plan', 'implement', 'review'];
129
115
  }
130
116
 
131
117
  /**
@@ -27,14 +27,13 @@ export function getStatePath() {
27
27
  * Returns the phase corresponding to the highest-numbered folder present.
28
28
  *
29
29
  * @param {string} featurePath - Absolute path to .morph/features/{feature}/
30
- * @returns {string} Phase name: 'implement' | 'tasks' | 'plan' | 'uiux' | 'design' | 'proposal'
30
+ * @returns {string} Phase name: 'review' | 'implement' | 'plan' | 'uiux' | 'proposal'
31
31
  */
32
32
  export function derivePhase(featurePath) {
33
33
  const phaseMap = [
34
- ['5-implement', 'implement'],
35
- ['4-tasks', 'tasks'],
36
- ['3-plan', 'plan'],
37
- ['2-design', 'design'],
34
+ ['4-review', 'review'],
35
+ ['3-implement', 'implement'],
36
+ ['2-plan', 'plan'],
38
37
  ['1-ui', 'uiux'],
39
38
  ['0-proposal', 'proposal'],
40
39
  ];
@@ -79,7 +78,7 @@ export function loadState(throwOnError = true) {
79
78
 
80
79
  if (!existsSync(statePath)) {
81
80
  if (throwOnError) {
82
- throw new Error(`State file not found: ${statePath}\nRun 'morph-spec state init' first.`);
81
+ throw new Error(`State file not found: ${statePath}\nRun 'morph-spec init' to initialize MORPH in this project.`);
83
82
  }
84
83
  return null;
85
84
  }
@@ -297,6 +296,7 @@ async function ensureFeature(featureName, options = {}) {
297
296
  workflowDetection.matchedKeywords = detection.matchedKeywords;
298
297
  workflowDetection.estimatedComplexity = detection.estimatedComplexity;
299
298
  workflowDetection.reasoning = detection.reasoning;
299
+ workflowDetection.warnings = detection.warnings || [];
300
300
  } catch (err) {
301
301
  // If detection fails, fall back to auto
302
302
  console.warn('Workflow detection failed:', err.message);
@@ -313,8 +313,8 @@ async function ensureFeature(featureName, options = {}) {
313
313
  approvalGates: {
314
314
  proposal: { approved: false, timestamp: null, approvedBy: null },
315
315
  uiux: { approved: false, timestamp: null, approvedBy: null },
316
- design: { approved: false, timestamp: null, approvedBy: null },
317
- tasks: { approved: false, timestamp: null, approvedBy: null }
316
+ plan: { approved: false, timestamp: null, approvedBy: null },
317
+ review: { approved: false, timestamp: null, approvedBy: null }
318
318
  },
319
319
  tasks: {
320
320
  total: 0,
@@ -491,7 +491,7 @@ export function logDispatch(featureName, entry) {
491
491
  /**
492
492
  * Normalize output type from kebab-case to camelCase (BUG-002 fix - Enhanced)
493
493
  * Supports both kebab-case and camelCase input formats for all output types
494
- * @param {string} type - Output type (e.g., 'ui-design-system', 'schema-analysis', or 'uiDesignSystem')
494
+ * @param {string} type - Output type (e.g., 'ui-design-system', 'stack-scan', or 'uiDesignSystem')
495
495
  * @returns {string} Normalized type in camelCase
496
496
  */
497
497
  function normalizeOutputType(type) {
@@ -502,7 +502,7 @@ function normalizeOutputType(type) {
502
502
 
503
503
  // Convert kebab-case to camelCase automatically
504
504
  // Example: 'ui-design-system' → 'uiDesignSystem'
505
- // Example: 'schema-analysis' → 'schemaAnalysis'
505
+ // Example: 'stack-scan' → 'stackScan'
506
506
  const camelCase = type.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase());
507
507
 
508
508
  return camelCase;
@@ -582,7 +582,7 @@ function findClosestMatch(inputType, validTypes) {
582
582
  /**
583
583
  * Mark output as created
584
584
  * @param {string} featureName - Feature name
585
- * @param {string} outputType - Output type (proposal, spec, contracts, etc.)
585
+ * @param {string} outputType - Output type (proposal, spec, mandate, etc.)
586
586
  */
587
587
  export async function markOutput(featureName, outputType) {
588
588
  await ensureFeature(featureName);
@@ -618,7 +618,7 @@ export async function markOutput(featureName, outputType) {
618
618
 
619
619
  errorMsg += `\n\nValid types:\n`;
620
620
  errorMsg += ` ${validCamelCaseTypes.join(', ')}\n`;
621
- errorMsg += `\nNote: UI types also accept kebab-case (e.g., 'ui-design-system', 'schema-analysis')`;
621
+ errorMsg += `\nNote: kebab-case is also accepted (e.g., 'ui-design-system', 'stack-scan')`;
622
622
 
623
623
  throw new Error(errorMsg);
624
624
  }
@@ -769,7 +769,7 @@ export function getSummary() {
769
769
  /**
770
770
  * Set approval gate status
771
771
  * @param {string} featureName - Feature name
772
- * @param {string} gate - Gate name (proposal, uiux, design, tasks)
772
+ * @param {string} gate - Gate name (proposal, uiux, plan, review)
773
773
  * @param {boolean} approved - Approval status
774
774
  * @param {Object} metadata - Additional metadata (approvedBy, reason, etc.)
775
775
  */
@@ -781,8 +781,8 @@ export async function setApprovalGate(featureName, gate, approved, metadata = {}
781
781
  feature.approvalGates = {
782
782
  proposal: { approved: false, timestamp: null, approvedBy: null },
783
783
  uiux: { approved: false, timestamp: null, approvedBy: null },
784
- design: { approved: false, timestamp: null, approvedBy: null },
785
- tasks: { approved: false, timestamp: null, approvedBy: null }
784
+ plan: { approved: false, timestamp: null, approvedBy: null },
785
+ review: { approved: false, timestamp: null, approvedBy: null }
786
786
  };
787
787
  }
788
788
 
@@ -831,8 +831,8 @@ export function isPendingApproval(featureName) {
831
831
 
832
832
  // Check if current phase has an approval gate
833
833
  const phaseGateMap = {
834
- 'design': 'design',
835
- 'tasks': 'tasks',
834
+ 'plan': 'plan',
835
+ 'review': 'review',
836
836
  'uiux': 'uiux'
837
837
  };
838
838