@polymorphism-tech/morph-spec 7.9.4 → 8.0.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 (211) hide show
  1. package/CLAUDE.md +2 -2
  2. package/README.md +31 -2
  3. package/bin/morph-spec.js +50 -2
  4. package/bin/validate.js +379 -379
  5. package/claude-plugin.json +1 -1
  6. package/docs/CHEATSHEET.md +38 -1
  7. package/docs/QUICKSTART.md +60 -1
  8. package/framework/CLAUDE.md +13 -2
  9. package/framework/MORPH.md +38 -2
  10. package/framework/agents.json +22 -3
  11. package/framework/commands/commit.md +166 -166
  12. package/framework/commands/morph-hotfix.md +39 -0
  13. package/framework/commands/morph-infra.md +215 -215
  14. package/framework/commands/morph-preflight.md +205 -205
  15. package/framework/commands/morph-troubleshoot.md +124 -124
  16. package/framework/hooks/README.md +25 -10
  17. package/framework/hooks/claude-code/core/gate-guard.js +4 -2
  18. package/framework/hooks/claude-code/core/loop-detect.js +2 -1
  19. package/framework/hooks/claude-code/core/post-edit-typecheck.js +14 -5
  20. package/framework/hooks/claude-code/core/tasks-json-guard.js +4 -2
  21. package/framework/hooks/claude-code/core/validate-completion.js +1 -1
  22. package/framework/hooks/claude-code/core/worktree-context.js +160 -0
  23. package/framework/hooks/claude-code/statusline.py +679 -679
  24. package/framework/hooks/claude-code/statusline.sh +11 -11
  25. package/framework/hooks/git/commit-msg/conventional-commits.sh +33 -33
  26. package/framework/hooks/git/pre-commit/agents.sh +25 -25
  27. package/framework/hooks/git/pre-commit/orchestrator.sh +64 -64
  28. package/framework/hooks/git/pre-commit/specs.sh +50 -50
  29. package/framework/hooks/git/pre-push/run-tests.sh +44 -44
  30. package/framework/hooks/shared/activity-logger.js +160 -160
  31. package/framework/hooks/shared/build-check.js +2 -2
  32. package/framework/hooks/shared/hook-response.js +17 -5
  33. package/framework/hooks/shared/payload-utils.js +42 -0
  34. package/framework/hooks/shared/state-reader.js +22 -2
  35. package/framework/hooks/shared/stdin-reader.js +26 -26
  36. package/framework/rubrics/backend.json +41 -41
  37. package/framework/rules/ai-agents-standards.md +45 -45
  38. package/framework/rules/dotnet-templates.md +86 -86
  39. package/framework/rules/frontend-standards.md +13 -13
  40. package/framework/rules/infrastructure-standards.md +27 -27
  41. package/framework/rules/neon-standards.md +25 -25
  42. package/framework/rules/nextjs-standards.md +20 -20
  43. package/framework/rules/nextjs-templates.md +40 -40
  44. package/framework/schemas/feature.schema.json +95 -1
  45. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  46. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  47. package/framework/skills/core/morph-apply/SKILL.md +39 -8
  48. package/framework/skills/core/morph-brainstorming/SKILL.md +9 -1
  49. package/framework/skills/core/morph-eval/SKILL.md +6 -1
  50. package/framework/skills/core/morph-hotfix/SKILL.md +136 -0
  51. package/framework/skills/core/morph-implement/SKILL.md +8 -4
  52. package/framework/skills/core/morph-plan/SKILL.md +5 -1
  53. package/framework/skills/core/morph-proposal/SKILL.md +24 -1
  54. package/framework/skills/core/morph-review/SKILL.md +3 -2
  55. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  56. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  57. package/framework/standards/ai-agents/agent-session.md +160 -160
  58. package/framework/standards/ai-agents/batching.md +193 -193
  59. package/framework/standards/ai-agents/context-providers.md +188 -188
  60. package/framework/standards/ai-agents/mcp-server.md +250 -250
  61. package/framework/standards/ai-agents/mcp-tools.md +197 -197
  62. package/framework/standards/ai-agents/middleware-patterns.md +207 -207
  63. package/framework/standards/ai-agents/modalities-image-gen.md +173 -173
  64. package/framework/standards/ai-agents/modalities-speech-to-text.md +171 -171
  65. package/framework/standards/ai-agents/modalities-text-to-speech.md +181 -181
  66. package/framework/standards/ai-agents/modalities-vision.md +201 -201
  67. package/framework/standards/ai-agents/multi-agent-patterns.md +155 -155
  68. package/framework/standards/ai-agents/production.md +275 -275
  69. package/framework/standards/ai-agents/prompts-in-db.md +214 -214
  70. package/framework/standards/ai-agents/providers/model-registry.md +158 -158
  71. package/framework/standards/ai-agents/service-tier-flex.md +135 -135
  72. package/framework/standards/ai-agents/structured-output.md +249 -249
  73. package/framework/standards/ai-agents/sweet-spot.md +109 -109
  74. package/framework/templates/.idea/morph-templates.xml +92 -0
  75. package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
  76. package/framework/templates/IDE-SNIPPETS.md +262 -262
  77. package/framework/templates/REGISTRY.json +0 -62
  78. package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
  79. package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -39
  80. package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -68
  81. package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -117
  82. package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +95 -95
  83. package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -61
  84. package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -107
  85. package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -58
  86. package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +281 -281
  87. package/framework/templates/code/dotnet/database/migration.cs +83 -83
  88. package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -21
  89. package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
  90. package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
  91. package/framework/templates/code/dotnet/feature/validator.cs.hbs +15 -15
  92. package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +58 -58
  93. package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
  94. package/framework/templates/code/typescript/contracts.ts +167 -167
  95. package/framework/templates/docs/clarifications.md +253 -253
  96. package/framework/templates/docs/schema-analysis.md +119 -119
  97. package/framework/templates/docs/ui-components.md +52 -52
  98. package/framework/templates/docs/ui-design-system.md +76 -76
  99. package/framework/templates/docs/ui-flows.md +167 -167
  100. package/framework/templates/docs/ui-mockups.md +98 -98
  101. package/framework/templates/feature/decisions.md +122 -122
  102. package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +43 -43
  103. package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
  104. package/framework/templates/frontend/nextjs/env.mjs.hbs +32 -32
  105. package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
  106. package/framework/templates/frontend/nextjs/page.tsx.hbs +22 -22
  107. package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
  108. package/framework/templates/frontend/nextjs/use-feature.ts.hbs +54 -54
  109. package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
  110. package/framework/templates/infrastructure/docker/docker-compose.template.yml +50 -50
  111. package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
  112. package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
  113. package/framework/templates/infrastructure/github/README.md +56 -56
  114. package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
  115. package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
  116. package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
  117. package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
  118. package/framework/templates/integrations/asaas-client.cs +387 -387
  119. package/framework/templates/integrations/asaas-webhook.cs +351 -351
  120. package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
  121. package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
  122. package/framework/templates/meta-prompts/validators/pre-commit-validator.md +95 -95
  123. package/framework/templates/project/validate.js +120 -120
  124. package/framework/templates/state.template.json +17 -17
  125. package/framework/templates/ui/design-system.css +226 -226
  126. package/package.json +1 -1
  127. package/src/commands/dashboard/dashboard.js +53 -53
  128. package/src/commands/index.js +13 -13
  129. package/src/commands/mcp/mcp-setup.js +519 -519
  130. package/src/commands/project/checkpoint.js +209 -209
  131. package/src/commands/project/doctor.js +4 -2
  132. package/src/commands/project/init.js +46 -2
  133. package/src/commands/project/update.js +5 -3
  134. package/src/commands/project/worktree.js +1005 -46
  135. package/src/commands/state/advance.js +17 -2
  136. package/src/commands/state/approve.js +18 -1
  137. package/src/commands/state/create.js +158 -14
  138. package/src/commands/state/retype.js +126 -0
  139. package/src/commands/state/score.js +4 -0
  140. package/src/commands/tasks/dag.js +32 -0
  141. package/src/commands/validation/index.js +1 -0
  142. package/src/commands/validation/validate-feature.js +33 -5
  143. package/src/commands/validation/verify.js +190 -0
  144. package/src/core/state/phase-state-machine.js +197 -197
  145. package/src/core/state/state-manager.js +35 -10
  146. package/src/core/templates/template-renderer.js +477 -477
  147. package/src/core/work-types.js +79 -0
  148. package/src/dashboard/app/components/DetailPanel.jsx +11 -11
  149. package/src/dashboard/app/components/FlowGraph.jsx +17 -17
  150. package/src/dashboard/app/components/SearchFilter.jsx +22 -22
  151. package/src/dashboard/app/hooks/useDetailPanel.js +15 -15
  152. package/src/dashboard/app/main.jsx +13 -13
  153. package/src/dashboard/app/pages/AgentMap.jsx +370 -370
  154. package/src/dashboard/app/pages/HookInspector.jsx +418 -418
  155. package/src/dashboard/app/pages/LiveState.jsx +11 -1
  156. package/src/dashboard/app/pages/PhasePipeline.jsx +408 -408
  157. package/src/dashboard/app/pages/RulesMap.jsx +273 -273
  158. package/src/dashboard/app/pages/SkillsLibrary.jsx +366 -366
  159. package/src/dashboard/app/pages/Standards.jsx +332 -332
  160. package/src/dashboard/app/styles/index.css +47 -47
  161. package/src/dashboard/index.html +12 -12
  162. package/src/dashboard/package.json +24 -24
  163. package/src/dashboard/scanner/hooks-scanner.js +41 -41
  164. package/src/dashboard/scanner/phases-scanner.js +15 -15
  165. package/src/dashboard/scanner/rubrics-scanner.js +12 -12
  166. package/src/dashboard/scanner/rules-scanner.js +37 -37
  167. package/src/dashboard/scanner/skills-scanner.js +43 -43
  168. package/src/dashboard/scanner/squads-scanner.js +13 -13
  169. package/src/dashboard/scanner/standards-scanner.js +26 -26
  170. package/src/dashboard/scanner/state-scanner.js +42 -0
  171. package/src/dashboard/vite-plugin-morph-data.js +17 -17
  172. package/src/dashboard/vite.config.js +24 -24
  173. package/src/lib/agents/metrics-aggregator.js +56 -56
  174. package/src/lib/agents/metrics-calculator.js +125 -125
  175. package/src/lib/agents/output-collector.js +73 -73
  176. package/src/lib/detectors/claude-config-detector.js +152 -152
  177. package/src/lib/detectors/design-system-detector.js +231 -231
  178. package/src/lib/detectors/index.js +217 -217
  179. package/src/lib/generators/architecture-scanner.js +639 -639
  180. package/src/lib/generators/design-system-generator.js +508 -508
  181. package/src/lib/generators/index.js +13 -13
  182. package/src/lib/generators/settings-generator.js +209 -209
  183. package/src/lib/hooks/hook-executor.js +167 -167
  184. package/src/lib/installers/mcp-installer.js +719 -719
  185. package/src/lib/installers/memory-installer.js +1 -1
  186. package/src/lib/risk-detector.js +16 -0
  187. package/src/lib/tasks/dispatch-planner.js +122 -4
  188. package/src/lib/tasks/mermaid-renderer.js +132 -0
  189. package/src/lib/tasks/task-classifier.js +30 -30
  190. package/src/lib/tasks/task-parser.js +209 -209
  191. package/src/lib/tasks/test-runner.js +27 -6
  192. package/src/lib/validators/architecture/architecture-validator.js +259 -259
  193. package/src/lib/validators/checkpoint-persistence.js +39 -39
  194. package/src/lib/validators/contracts/contract-compliance-validator.js +274 -274
  195. package/src/lib/validators/css/css-validator.js +340 -340
  196. package/src/lib/validators/packages/package-validator.js +347 -347
  197. package/src/lib/validators/shared/emit-validator-dispatch.js +14 -14
  198. package/src/lib/validators/shared/issue-counter.js +14 -14
  199. package/src/lib/validators/validation-runner.js +349 -349
  200. package/src/lib/validators/verify-runner.js +322 -0
  201. package/src/lib/work-type-detector.js +143 -0
  202. package/src/scripts/setup-infra.js +32 -5
  203. package/src/utils/agent-id-mapper.js +11 -11
  204. package/src/utils/agents-installer.js +10 -6
  205. package/src/utils/claude-settings-manager.js +300 -300
  206. package/src/utils/file-copier.js +43 -0
  207. package/src/utils/hooks-installer.js +41 -11
  208. package/src/utils/skills-installer.js +174 -174
  209. package/framework/templates/meta-prompts/parallel-workers/parallel-coordinator.md +0 -113
  210. package/framework/templates/meta-prompts/parallel-workers/parallel-worker.md +0 -80
  211. package/src/lib/tasks/wave-resolver.js +0 -353
@@ -1,353 +0,0 @@
1
- /**
2
- * @fileoverview Wave Resolver — Dependency-aware task grouping into execution waves.
3
- *
4
- * Pure algorithm module (no I/O, no state access). Takes parsed task lists and
5
- * returns wave assignments. Tasks within a wave have no interdependencies and
6
- * don't touch the same files, so they can safely run in parallel.
7
- *
8
- * @module wave-resolver
9
- */
10
-
11
- /**
12
- * Validate that all dependency references point to existing task IDs.
13
- *
14
- * @param {Array<{id: string, dependencies: string[]}>} taskList
15
- * @returns {{ valid: boolean, errors: string[] }}
16
- */
17
- export function validateDependencies(taskList) {
18
- const ids = new Set(taskList.map(t => t.id));
19
- const errors = [];
20
-
21
- for (const task of taskList) {
22
- for (const dep of task.dependencies || []) {
23
- if (dep === task.id) {
24
- errors.push(`${task.id} depends on itself`);
25
- } else if (!ids.has(dep)) {
26
- errors.push(`${task.id} depends on ${dep} which does not exist`);
27
- }
28
- }
29
- }
30
-
31
- return { valid: errors.length === 0, errors };
32
- }
33
-
34
- /**
35
- * Detect circular dependencies using Kahn's algorithm.
36
- * Returns the cycle path if found, or null if acyclic.
37
- *
38
- * @param {Array<{id: string, dependencies: string[]}>} taskList
39
- * @returns {{ hasCycle: boolean, cycle: string[]|null }}
40
- */
41
- export function detectCircularDeps(taskList) {
42
- const ids = new Set(taskList.map(t => t.id));
43
- const inDegree = new Map();
44
- const adjacency = new Map(); // dependency → dependents
45
-
46
- for (const task of taskList) {
47
- inDegree.set(task.id, 0);
48
- if (!adjacency.has(task.id)) adjacency.set(task.id, []);
49
- }
50
-
51
- for (const task of taskList) {
52
- for (const dep of task.dependencies || []) {
53
- if (!ids.has(dep)) continue; // skip invalid refs (caught by validateDependencies)
54
- inDegree.set(task.id, (inDegree.get(task.id) || 0) + 1);
55
- if (!adjacency.has(dep)) adjacency.set(dep, []);
56
- adjacency.get(dep).push(task.id);
57
- }
58
- }
59
-
60
- // BFS: process zero-in-degree nodes
61
- const queue = [];
62
- for (const [id, deg] of inDegree) {
63
- if (deg === 0) queue.push(id);
64
- }
65
-
66
- const processed = new Set();
67
- while (queue.length > 0) {
68
- const node = queue.shift();
69
- processed.add(node);
70
- for (const dependent of adjacency.get(node) || []) {
71
- inDegree.set(dependent, inDegree.get(dependent) - 1);
72
- if (inDegree.get(dependent) === 0) queue.push(dependent);
73
- }
74
- }
75
-
76
- if (processed.size === taskList.length) {
77
- return { hasCycle: false, cycle: null };
78
- }
79
-
80
- // Extract cycle from unprocessed nodes via DFS
81
- const unprocessed = taskList.filter(t => !processed.has(t.id));
82
- const depMap = new Map(taskList.map(t => [t.id, t.dependencies || []]));
83
- const visiting = new Set();
84
- const path = [];
85
-
86
- function dfs(nodeId) {
87
- if (visiting.has(nodeId)) {
88
- const cycleStart = path.indexOf(nodeId);
89
- return path.slice(cycleStart).concat(nodeId);
90
- }
91
- if (processed.has(nodeId)) return null;
92
-
93
- visiting.add(nodeId);
94
- path.push(nodeId);
95
-
96
- for (const dep of depMap.get(nodeId) || []) {
97
- if (!ids.has(dep)) continue;
98
- const cycle = dfs(dep);
99
- if (cycle) return cycle;
100
- }
101
-
102
- visiting.delete(nodeId);
103
- path.pop();
104
- return null;
105
- }
106
-
107
- for (const task of unprocessed) {
108
- const cycle = dfs(task.id);
109
- if (cycle) return { hasCycle: true, cycle };
110
- }
111
-
112
- // Fallback: cycle exists but couldn't extract path
113
- return { hasCycle: true, cycle: unprocessed.map(t => t.id) };
114
- }
115
-
116
- /**
117
- * Assign a wave level to each task via topological sort.
118
- * Tasks with no dependencies go to wave 0. Tasks whose dependencies
119
- * are all in wave N go to wave N+1.
120
- *
121
- * @param {Array<{id: string, dependencies: string[], status?: string}>} taskList
122
- * @param {{ includeCompleted?: boolean }} options
123
- * @returns {Array<{ waveIndex: number, tasks: string[] }>}
124
- */
125
- export function resolveWaves(taskList, options = {}) {
126
- // Filter tasks
127
- const activeTasks = options.includeCompleted
128
- ? taskList
129
- : taskList.filter(t => t.status !== 'completed' && t.status !== 'expanded');
130
-
131
- if (activeTasks.length === 0) return [];
132
-
133
- const ids = new Set(activeTasks.map(t => t.id));
134
- const completedIds = new Set(
135
- taskList.filter(t => t.status === 'completed' || t.status === 'expanded').map(t => t.id)
136
- );
137
-
138
- // Assign levels via BFS
139
- const level = new Map();
140
- const inDegree = new Map();
141
- const adjacency = new Map();
142
-
143
- for (const task of activeTasks) {
144
- inDegree.set(task.id, 0);
145
- adjacency.set(task.id, []);
146
- level.set(task.id, 0);
147
- }
148
-
149
- for (const task of activeTasks) {
150
- for (const dep of task.dependencies || []) {
151
- if (!ids.has(dep)) continue; // dep is completed or doesn't exist → satisfied
152
- inDegree.set(task.id, inDegree.get(task.id) + 1);
153
- adjacency.get(dep).push(task.id);
154
- }
155
- }
156
-
157
- // BFS with level tracking
158
- const queue = [];
159
- for (const [id, deg] of inDegree) {
160
- if (deg === 0) {
161
- queue.push(id);
162
- level.set(id, 0);
163
- }
164
- }
165
-
166
- while (queue.length > 0) {
167
- const node = queue.shift();
168
- const nodeLevel = level.get(node);
169
-
170
- for (const dependent of adjacency.get(node)) {
171
- const newDeg = inDegree.get(dependent) - 1;
172
- inDegree.set(dependent, newDeg);
173
-
174
- // Level = max of all dependency levels + 1
175
- const candidateLevel = nodeLevel + 1;
176
- if (candidateLevel > level.get(dependent)) {
177
- level.set(dependent, candidateLevel);
178
- }
179
-
180
- if (newDeg === 0) queue.push(dependent);
181
- }
182
- }
183
-
184
- // Group by level
185
- const waveMap = new Map();
186
- for (const [id, lvl] of level) {
187
- if (!waveMap.has(lvl)) waveMap.set(lvl, []);
188
- waveMap.get(lvl).push(id);
189
- }
190
-
191
- // Sort waves and task IDs within waves
192
- const waves = [...waveMap.entries()]
193
- .sort((a, b) => a[0] - b[0])
194
- .map(([idx, tasks]) => ({
195
- waveIndex: idx,
196
- tasks: tasks.sort()
197
- }));
198
-
199
- return waves;
200
- }
201
-
202
- /**
203
- * Parse file paths from a task's deliverables/steps section.
204
- * Looks for backtick-wrapped paths in deliverable lines.
205
- *
206
- * @param {string} taskBlock - The markdown block for a single task
207
- * @returns {string[]} Array of file paths (deduplicated, normalized to forward slashes)
208
- */
209
- export function parseTaskFiles(taskBlock) {
210
- const files = new Set();
211
-
212
- // Match: - [ ] Create/Update/Modify/Delete: `path/to/file`
213
- const deliverableRe = /- \[[ x]\]\s*(?:Create|Update|Modify|Delete|Add|Edit|Remove):\s*`([^`]+)`/gi;
214
- let match;
215
- while ((match = deliverableRe.exec(taskBlock)) !== null) {
216
- files.add(match[1].replace(/\\/g, '/'));
217
- }
218
-
219
- // Also match: **Files:** `path1`, `path2`
220
- const filesFieldRe = /\*\*Files?:\*\*\s*(.+)/i;
221
- const filesMatch = filesFieldRe.exec(taskBlock);
222
- if (filesMatch) {
223
- const backtickRe = /`([^`]+)`/g;
224
- let fMatch;
225
- while ((fMatch = backtickRe.exec(filesMatch[1])) !== null) {
226
- files.add(fMatch[1].replace(/\\/g, '/'));
227
- }
228
- }
229
-
230
- return [...files];
231
- }
232
-
233
- /**
234
- * Detect file conflicts within each wave and redistribute conflicting
235
- * tasks to later waves.
236
- *
237
- * @param {Array<{ waveIndex: number, tasks: string[] }>} waves
238
- * @param {Map<string, string[]>} taskFileMap - taskId → file paths
239
- * @returns {Array<{ waveIndex: number, tasks: string[] }>}
240
- */
241
- export function resolveFileConflicts(waves, taskFileMap) {
242
- if (!taskFileMap || taskFileMap.size === 0) return waves;
243
-
244
- // Deep copy to avoid mutation
245
- let result = waves.map(w => ({ waveIndex: w.waveIndex, tasks: [...w.tasks] }));
246
- const maxIterations = waves.reduce((sum, w) => sum + w.tasks.length, 0) + 1;
247
-
248
- for (let iter = 0; iter < maxIterations; iter++) {
249
- let changed = false;
250
-
251
- for (let wi = 0; wi < result.length; wi++) {
252
- const wave = result[wi];
253
- const fileOwner = new Map(); // file → first taskId
254
- const toDefer = [];
255
-
256
- for (const taskId of wave.tasks) {
257
- const files = taskFileMap.get(taskId) || [];
258
- let hasConflict = false;
259
-
260
- for (const file of files) {
261
- if (fileOwner.has(file)) {
262
- hasConflict = true;
263
- break;
264
- }
265
- }
266
-
267
- if (hasConflict) {
268
- toDefer.push(taskId);
269
- } else {
270
- for (const file of files) {
271
- fileOwner.set(file, taskId);
272
- }
273
- }
274
- }
275
-
276
- if (toDefer.length > 0) {
277
- changed = true;
278
- wave.tasks = wave.tasks.filter(t => !toDefer.includes(t));
279
-
280
- // Push to next wave or create one
281
- const nextWi = wi + 1;
282
- if (nextWi < result.length) {
283
- result[nextWi].tasks.push(...toDefer);
284
- result[nextWi].tasks.sort();
285
- } else {
286
- result.push({ waveIndex: result.length, tasks: toDefer.sort() });
287
- }
288
- }
289
- }
290
-
291
- if (!changed) break;
292
- }
293
-
294
- // Remove empty waves and re-index
295
- result = result.filter(w => w.tasks.length > 0);
296
- result.forEach((w, i) => { w.waveIndex = i; });
297
-
298
- return result;
299
- }
300
-
301
- /**
302
- * Full resolution pipeline: validate → detect cycles → resolve waves → resolve conflicts.
303
- *
304
- * @param {Array<{id: string, title?: string, status?: string, dependencies: string[]}>} taskList
305
- * @param {Map<string, string[]>} taskFileMap - Optional file map for conflict detection
306
- * @returns {{ waves: Array<{ waveIndex: number, tasks: string[] }>, errors: string[], warnings: string[] }}
307
- */
308
- export function resolveWavePlan(taskList, taskFileMap = new Map()) {
309
- const errors = [];
310
- const warnings = [];
311
-
312
- if (!taskList || taskList.length === 0) {
313
- return { waves: [], errors: [], warnings: ['No tasks to resolve'] };
314
- }
315
-
316
- // Step 1: Validate dependencies
317
- const validation = validateDependencies(taskList);
318
- if (!validation.valid) {
319
- errors.push(...validation.errors);
320
- return { waves: [], errors, warnings };
321
- }
322
-
323
- // Step 2: Detect circular dependencies
324
- const cycleCheck = detectCircularDeps(taskList);
325
- if (cycleCheck.hasCycle) {
326
- errors.push(`Circular dependency detected: ${cycleCheck.cycle.join(' → ')}`);
327
- return { waves: [], errors, warnings };
328
- }
329
-
330
- // Step 3: Resolve waves from dependencies
331
- let waves = resolveWaves(taskList);
332
-
333
- // Step 4: Resolve file conflicts
334
- if (taskFileMap.size > 0) {
335
- const before = waves.map(w => w.tasks.length);
336
- waves = resolveFileConflicts(waves, taskFileMap);
337
- const after = waves.map(w => w.tasks.length);
338
-
339
- // Report redistributions
340
- if (JSON.stringify(before) !== JSON.stringify(after)) {
341
- warnings.push('Some tasks were moved to later waves due to file conflicts');
342
- }
343
- }
344
-
345
- // Report skipped tasks
346
- const activeTasks = taskList.filter(t => t.status !== 'completed' && t.status !== 'expanded');
347
- const skipped = taskList.length - activeTasks.length;
348
- if (skipped > 0) {
349
- warnings.push(`${skipped} completed/expanded task(s) excluded from wave plan`);
350
- }
351
-
352
- return { waves, errors, warnings };
353
- }