@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.
- package/CLAUDE.md +2 -2
- package/README.md +31 -2
- package/bin/morph-spec.js +50 -2
- package/bin/validate.js +379 -379
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +38 -1
- package/docs/QUICKSTART.md +60 -1
- package/framework/CLAUDE.md +13 -2
- package/framework/MORPH.md +38 -2
- package/framework/agents.json +22 -3
- package/framework/commands/commit.md +166 -166
- package/framework/commands/morph-hotfix.md +39 -0
- package/framework/commands/morph-infra.md +215 -215
- package/framework/commands/morph-preflight.md +205 -205
- package/framework/commands/morph-troubleshoot.md +124 -124
- package/framework/hooks/README.md +25 -10
- package/framework/hooks/claude-code/core/gate-guard.js +4 -2
- package/framework/hooks/claude-code/core/loop-detect.js +2 -1
- package/framework/hooks/claude-code/core/post-edit-typecheck.js +14 -5
- package/framework/hooks/claude-code/core/tasks-json-guard.js +4 -2
- package/framework/hooks/claude-code/core/validate-completion.js +1 -1
- package/framework/hooks/claude-code/core/worktree-context.js +160 -0
- package/framework/hooks/claude-code/statusline.py +679 -679
- package/framework/hooks/claude-code/statusline.sh +11 -11
- package/framework/hooks/git/commit-msg/conventional-commits.sh +33 -33
- package/framework/hooks/git/pre-commit/agents.sh +25 -25
- package/framework/hooks/git/pre-commit/orchestrator.sh +64 -64
- package/framework/hooks/git/pre-commit/specs.sh +50 -50
- package/framework/hooks/git/pre-push/run-tests.sh +44 -44
- package/framework/hooks/shared/activity-logger.js +160 -160
- package/framework/hooks/shared/build-check.js +2 -2
- package/framework/hooks/shared/hook-response.js +17 -5
- package/framework/hooks/shared/payload-utils.js +42 -0
- package/framework/hooks/shared/state-reader.js +22 -2
- package/framework/hooks/shared/stdin-reader.js +26 -26
- package/framework/rubrics/backend.json +41 -41
- package/framework/rules/ai-agents-standards.md +45 -45
- package/framework/rules/dotnet-templates.md +86 -86
- package/framework/rules/frontend-standards.md +13 -13
- package/framework/rules/infrastructure-standards.md +27 -27
- package/framework/rules/neon-standards.md +25 -25
- package/framework/rules/nextjs-standards.md +20 -20
- package/framework/rules/nextjs-templates.md +40 -40
- package/framework/schemas/feature.schema.json +95 -1
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +39 -8
- package/framework/skills/core/morph-brainstorming/SKILL.md +9 -1
- package/framework/skills/core/morph-eval/SKILL.md +6 -1
- package/framework/skills/core/morph-hotfix/SKILL.md +136 -0
- package/framework/skills/core/morph-implement/SKILL.md +8 -4
- package/framework/skills/core/morph-plan/SKILL.md +5 -1
- package/framework/skills/core/morph-proposal/SKILL.md +24 -1
- package/framework/skills/core/morph-review/SKILL.md +3 -2
- package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/standards/STANDARDS.json +7 -2
- package/framework/standards/ai-agents/agent-session.md +160 -160
- package/framework/standards/ai-agents/batching.md +193 -193
- package/framework/standards/ai-agents/context-providers.md +188 -188
- package/framework/standards/ai-agents/mcp-server.md +250 -250
- package/framework/standards/ai-agents/mcp-tools.md +197 -197
- package/framework/standards/ai-agents/middleware-patterns.md +207 -207
- package/framework/standards/ai-agents/modalities-image-gen.md +173 -173
- package/framework/standards/ai-agents/modalities-speech-to-text.md +171 -171
- package/framework/standards/ai-agents/modalities-text-to-speech.md +181 -181
- package/framework/standards/ai-agents/modalities-vision.md +201 -201
- package/framework/standards/ai-agents/multi-agent-patterns.md +155 -155
- package/framework/standards/ai-agents/production.md +275 -275
- package/framework/standards/ai-agents/prompts-in-db.md +214 -214
- package/framework/standards/ai-agents/providers/model-registry.md +158 -158
- package/framework/standards/ai-agents/service-tier-flex.md +135 -135
- package/framework/standards/ai-agents/structured-output.md +249 -249
- package/framework/standards/ai-agents/sweet-spot.md +109 -109
- package/framework/standards/backend/integrations/neon-auth/neon-auth.md +45 -3
- package/framework/templates/.idea/morph-templates.xml +92 -0
- package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
- package/framework/templates/IDE-SNIPPETS.md +262 -262
- package/framework/templates/REGISTRY.json +0 -62
- package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
- package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -39
- package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -68
- package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -117
- package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +95 -95
- package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -61
- package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -107
- package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -58
- package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +281 -281
- package/framework/templates/code/dotnet/database/migration.cs +83 -83
- package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -21
- package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
- package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
- package/framework/templates/code/dotnet/feature/validator.cs.hbs +15 -15
- package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +58 -58
- package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
- package/framework/templates/code/typescript/contracts.ts +167 -167
- package/framework/templates/docs/clarifications.md +253 -253
- package/framework/templates/docs/schema-analysis.md +119 -119
- package/framework/templates/docs/ui-components.md +52 -52
- package/framework/templates/docs/ui-design-system.md +76 -76
- package/framework/templates/docs/ui-flows.md +167 -167
- package/framework/templates/docs/ui-mockups.md +98 -98
- package/framework/templates/feature/decisions.md +122 -122
- package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +43 -43
- package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
- package/framework/templates/frontend/nextjs/env.mjs.hbs +32 -32
- package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
- package/framework/templates/frontend/nextjs/page.tsx.hbs +22 -22
- package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
- package/framework/templates/frontend/nextjs/use-feature.ts.hbs +54 -54
- package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
- package/framework/templates/infrastructure/docker/docker-compose.template.yml +50 -50
- package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
- package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
- package/framework/templates/infrastructure/github/README.md +56 -56
- package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
- package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
- package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
- package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
- package/framework/templates/integrations/asaas-client.cs +387 -387
- package/framework/templates/integrations/asaas-webhook.cs +351 -351
- package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
- package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
- package/framework/templates/meta-prompts/validators/pre-commit-validator.md +95 -95
- package/framework/templates/project/validate.js +120 -120
- package/framework/templates/state.template.json +17 -17
- package/framework/templates/ui/design-system.css +226 -226
- package/package.json +1 -1
- package/src/commands/dashboard/dashboard.js +53 -53
- package/src/commands/index.js +13 -13
- package/src/commands/mcp/mcp-setup.js +519 -519
- package/src/commands/project/checkpoint.js +209 -209
- package/src/commands/project/doctor.js +4 -2
- package/src/commands/project/init.js +46 -2
- package/src/commands/project/update.js +5 -3
- package/src/commands/project/worktree.js +1005 -46
- package/src/commands/state/advance.js +17 -2
- package/src/commands/state/approve.js +18 -1
- package/src/commands/state/create.js +158 -14
- package/src/commands/state/retype.js +126 -0
- package/src/commands/state/score.js +4 -0
- package/src/commands/tasks/dag.js +32 -0
- package/src/commands/validation/index.js +1 -0
- package/src/commands/validation/validate-feature.js +33 -5
- package/src/commands/validation/verify.js +190 -0
- package/src/core/state/phase-state-machine.js +197 -197
- package/src/core/state/state-manager.js +35 -10
- package/src/core/templates/template-renderer.js +477 -477
- package/src/core/work-types.js +79 -0
- package/src/dashboard/app/components/DetailPanel.jsx +11 -11
- package/src/dashboard/app/components/FlowGraph.jsx +17 -17
- package/src/dashboard/app/components/SearchFilter.jsx +22 -22
- package/src/dashboard/app/hooks/useDetailPanel.js +15 -15
- package/src/dashboard/app/main.jsx +13 -13
- package/src/dashboard/app/pages/AgentMap.jsx +370 -370
- package/src/dashboard/app/pages/HookInspector.jsx +418 -418
- package/src/dashboard/app/pages/LiveState.jsx +11 -1
- package/src/dashboard/app/pages/PhasePipeline.jsx +408 -408
- package/src/dashboard/app/pages/RulesMap.jsx +273 -273
- package/src/dashboard/app/pages/SkillsLibrary.jsx +366 -366
- package/src/dashboard/app/pages/Standards.jsx +332 -332
- package/src/dashboard/app/styles/index.css +47 -47
- package/src/dashboard/index.html +12 -12
- package/src/dashboard/package.json +24 -24
- package/src/dashboard/scanner/hooks-scanner.js +41 -41
- package/src/dashboard/scanner/phases-scanner.js +15 -15
- package/src/dashboard/scanner/rubrics-scanner.js +12 -12
- package/src/dashboard/scanner/rules-scanner.js +37 -37
- package/src/dashboard/scanner/skills-scanner.js +43 -43
- package/src/dashboard/scanner/squads-scanner.js +13 -13
- package/src/dashboard/scanner/standards-scanner.js +26 -26
- package/src/dashboard/scanner/state-scanner.js +42 -0
- package/src/dashboard/vite-plugin-morph-data.js +17 -17
- package/src/dashboard/vite.config.js +24 -24
- package/src/lib/agents/metrics-aggregator.js +56 -56
- package/src/lib/agents/metrics-calculator.js +125 -125
- package/src/lib/agents/output-collector.js +73 -73
- package/src/lib/detectors/claude-config-detector.js +152 -152
- package/src/lib/detectors/design-system-detector.js +231 -231
- package/src/lib/detectors/index.js +217 -217
- package/src/lib/generators/architecture-scanner.js +639 -639
- package/src/lib/generators/design-system-generator.js +508 -508
- package/src/lib/generators/index.js +13 -13
- package/src/lib/generators/settings-generator.js +209 -209
- package/src/lib/hooks/hook-executor.js +167 -167
- package/src/lib/installers/mcp-installer.js +719 -719
- package/src/lib/installers/memory-installer.js +1 -1
- package/src/lib/risk-detector.js +16 -0
- package/src/lib/tasks/dispatch-planner.js +122 -4
- package/src/lib/tasks/mermaid-renderer.js +132 -0
- package/src/lib/tasks/task-classifier.js +30 -30
- package/src/lib/tasks/task-parser.js +209 -209
- package/src/lib/tasks/test-runner.js +27 -6
- package/src/lib/validators/architecture/architecture-validator.js +259 -259
- package/src/lib/validators/checkpoint-persistence.js +39 -39
- package/src/lib/validators/contracts/contract-compliance-validator.js +274 -274
- package/src/lib/validators/css/css-validator.js +340 -340
- package/src/lib/validators/packages/package-validator.js +347 -347
- package/src/lib/validators/shared/emit-validator-dispatch.js +14 -14
- package/src/lib/validators/shared/issue-counter.js +14 -14
- package/src/lib/validators/validation-runner.js +349 -349
- package/src/lib/validators/verify-runner.js +322 -0
- package/src/lib/work-type-detector.js +143 -0
- package/src/scripts/setup-infra.js +32 -5
- package/src/utils/agent-id-mapper.js +11 -11
- package/src/utils/agents-installer.js +10 -6
- package/src/utils/claude-settings-manager.js +300 -300
- package/src/utils/file-copier.js +43 -0
- package/src/utils/hooks-installer.js +41 -11
- package/src/utils/skills-installer.js +174 -174
- package/framework/templates/meta-prompts/parallel-workers/parallel-coordinator.md +0 -113
- package/framework/templates/meta-prompts/parallel-workers/parallel-worker.md +0 -80
- 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
|
-
}
|