@polymorphism-tech/morph-spec 7.9.3 → 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 (213) 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/STANDARDS.json +7 -2
  58. package/framework/standards/ai-agents/agent-session.md +160 -160
  59. package/framework/standards/ai-agents/batching.md +193 -193
  60. package/framework/standards/ai-agents/context-providers.md +188 -188
  61. package/framework/standards/ai-agents/mcp-server.md +250 -250
  62. package/framework/standards/ai-agents/mcp-tools.md +197 -197
  63. package/framework/standards/ai-agents/middleware-patterns.md +207 -207
  64. package/framework/standards/ai-agents/modalities-image-gen.md +173 -173
  65. package/framework/standards/ai-agents/modalities-speech-to-text.md +171 -171
  66. package/framework/standards/ai-agents/modalities-text-to-speech.md +181 -181
  67. package/framework/standards/ai-agents/modalities-vision.md +201 -201
  68. package/framework/standards/ai-agents/multi-agent-patterns.md +155 -155
  69. package/framework/standards/ai-agents/production.md +275 -275
  70. package/framework/standards/ai-agents/prompts-in-db.md +214 -214
  71. package/framework/standards/ai-agents/providers/model-registry.md +158 -158
  72. package/framework/standards/ai-agents/service-tier-flex.md +135 -135
  73. package/framework/standards/ai-agents/structured-output.md +249 -249
  74. package/framework/standards/ai-agents/sweet-spot.md +109 -109
  75. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +45 -3
  76. package/framework/templates/.idea/morph-templates.xml +92 -0
  77. package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
  78. package/framework/templates/IDE-SNIPPETS.md +262 -262
  79. package/framework/templates/REGISTRY.json +0 -62
  80. package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
  81. package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -39
  82. package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -68
  83. package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -117
  84. package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +95 -95
  85. package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -61
  86. package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -107
  87. package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -58
  88. package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +281 -281
  89. package/framework/templates/code/dotnet/database/migration.cs +83 -83
  90. package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -21
  91. package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
  92. package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
  93. package/framework/templates/code/dotnet/feature/validator.cs.hbs +15 -15
  94. package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +58 -58
  95. package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
  96. package/framework/templates/code/typescript/contracts.ts +167 -167
  97. package/framework/templates/docs/clarifications.md +253 -253
  98. package/framework/templates/docs/schema-analysis.md +119 -119
  99. package/framework/templates/docs/ui-components.md +52 -52
  100. package/framework/templates/docs/ui-design-system.md +76 -76
  101. package/framework/templates/docs/ui-flows.md +167 -167
  102. package/framework/templates/docs/ui-mockups.md +98 -98
  103. package/framework/templates/feature/decisions.md +122 -122
  104. package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +43 -43
  105. package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
  106. package/framework/templates/frontend/nextjs/env.mjs.hbs +32 -32
  107. package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
  108. package/framework/templates/frontend/nextjs/page.tsx.hbs +22 -22
  109. package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
  110. package/framework/templates/frontend/nextjs/use-feature.ts.hbs +54 -54
  111. package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
  112. package/framework/templates/infrastructure/docker/docker-compose.template.yml +50 -50
  113. package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
  114. package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
  115. package/framework/templates/infrastructure/github/README.md +56 -56
  116. package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
  117. package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
  118. package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
  119. package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
  120. package/framework/templates/integrations/asaas-client.cs +387 -387
  121. package/framework/templates/integrations/asaas-webhook.cs +351 -351
  122. package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
  123. package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
  124. package/framework/templates/meta-prompts/validators/pre-commit-validator.md +95 -95
  125. package/framework/templates/project/validate.js +120 -120
  126. package/framework/templates/state.template.json +17 -17
  127. package/framework/templates/ui/design-system.css +226 -226
  128. package/package.json +1 -1
  129. package/src/commands/dashboard/dashboard.js +53 -53
  130. package/src/commands/index.js +13 -13
  131. package/src/commands/mcp/mcp-setup.js +519 -519
  132. package/src/commands/project/checkpoint.js +209 -209
  133. package/src/commands/project/doctor.js +4 -2
  134. package/src/commands/project/init.js +46 -2
  135. package/src/commands/project/update.js +5 -3
  136. package/src/commands/project/worktree.js +1005 -46
  137. package/src/commands/state/advance.js +17 -2
  138. package/src/commands/state/approve.js +18 -1
  139. package/src/commands/state/create.js +158 -14
  140. package/src/commands/state/retype.js +126 -0
  141. package/src/commands/state/score.js +4 -0
  142. package/src/commands/tasks/dag.js +32 -0
  143. package/src/commands/validation/index.js +1 -0
  144. package/src/commands/validation/validate-feature.js +33 -5
  145. package/src/commands/validation/verify.js +190 -0
  146. package/src/core/state/phase-state-machine.js +197 -197
  147. package/src/core/state/state-manager.js +35 -10
  148. package/src/core/templates/template-renderer.js +477 -477
  149. package/src/core/work-types.js +79 -0
  150. package/src/dashboard/app/components/DetailPanel.jsx +11 -11
  151. package/src/dashboard/app/components/FlowGraph.jsx +17 -17
  152. package/src/dashboard/app/components/SearchFilter.jsx +22 -22
  153. package/src/dashboard/app/hooks/useDetailPanel.js +15 -15
  154. package/src/dashboard/app/main.jsx +13 -13
  155. package/src/dashboard/app/pages/AgentMap.jsx +370 -370
  156. package/src/dashboard/app/pages/HookInspector.jsx +418 -418
  157. package/src/dashboard/app/pages/LiveState.jsx +11 -1
  158. package/src/dashboard/app/pages/PhasePipeline.jsx +408 -408
  159. package/src/dashboard/app/pages/RulesMap.jsx +273 -273
  160. package/src/dashboard/app/pages/SkillsLibrary.jsx +366 -366
  161. package/src/dashboard/app/pages/Standards.jsx +332 -332
  162. package/src/dashboard/app/styles/index.css +47 -47
  163. package/src/dashboard/index.html +12 -12
  164. package/src/dashboard/package.json +24 -24
  165. package/src/dashboard/scanner/hooks-scanner.js +41 -41
  166. package/src/dashboard/scanner/phases-scanner.js +15 -15
  167. package/src/dashboard/scanner/rubrics-scanner.js +12 -12
  168. package/src/dashboard/scanner/rules-scanner.js +37 -37
  169. package/src/dashboard/scanner/skills-scanner.js +43 -43
  170. package/src/dashboard/scanner/squads-scanner.js +13 -13
  171. package/src/dashboard/scanner/standards-scanner.js +26 -26
  172. package/src/dashboard/scanner/state-scanner.js +42 -0
  173. package/src/dashboard/vite-plugin-morph-data.js +17 -17
  174. package/src/dashboard/vite.config.js +24 -24
  175. package/src/lib/agents/metrics-aggregator.js +56 -56
  176. package/src/lib/agents/metrics-calculator.js +125 -125
  177. package/src/lib/agents/output-collector.js +73 -73
  178. package/src/lib/detectors/claude-config-detector.js +152 -152
  179. package/src/lib/detectors/design-system-detector.js +231 -231
  180. package/src/lib/detectors/index.js +217 -217
  181. package/src/lib/generators/architecture-scanner.js +639 -639
  182. package/src/lib/generators/design-system-generator.js +508 -508
  183. package/src/lib/generators/index.js +13 -13
  184. package/src/lib/generators/settings-generator.js +209 -209
  185. package/src/lib/hooks/hook-executor.js +167 -167
  186. package/src/lib/installers/mcp-installer.js +719 -719
  187. package/src/lib/installers/memory-installer.js +1 -1
  188. package/src/lib/risk-detector.js +16 -0
  189. package/src/lib/tasks/dispatch-planner.js +122 -4
  190. package/src/lib/tasks/mermaid-renderer.js +132 -0
  191. package/src/lib/tasks/task-classifier.js +30 -30
  192. package/src/lib/tasks/task-parser.js +209 -209
  193. package/src/lib/tasks/test-runner.js +27 -6
  194. package/src/lib/validators/architecture/architecture-validator.js +259 -259
  195. package/src/lib/validators/checkpoint-persistence.js +39 -39
  196. package/src/lib/validators/contracts/contract-compliance-validator.js +274 -274
  197. package/src/lib/validators/css/css-validator.js +340 -340
  198. package/src/lib/validators/packages/package-validator.js +347 -347
  199. package/src/lib/validators/shared/emit-validator-dispatch.js +14 -14
  200. package/src/lib/validators/shared/issue-counter.js +14 -14
  201. package/src/lib/validators/validation-runner.js +349 -349
  202. package/src/lib/validators/verify-runner.js +322 -0
  203. package/src/lib/work-type-detector.js +143 -0
  204. package/src/scripts/setup-infra.js +32 -5
  205. package/src/utils/agent-id-mapper.js +11 -11
  206. package/src/utils/agents-installer.js +10 -6
  207. package/src/utils/claude-settings-manager.js +300 -300
  208. package/src/utils/file-copier.js +43 -0
  209. package/src/utils/hooks-installer.js +41 -11
  210. package/src/utils/skills-installer.js +174 -174
  211. package/framework/templates/meta-prompts/parallel-workers/parallel-coordinator.md +0 -113
  212. package/framework/templates/meta-prompts/parallel-workers/parallel-worker.md +0 -80
  213. 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
- }