@polymorphism-tech/morph-spec 7.9.4 → 8.1.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 +14 -3
- package/framework/MORPH.md +41 -5
- 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 -11
- 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 +49 -27
- 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 +134 -0
- package/framework/skills/core/morph-implement/SKILL.md +17 -22
- 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 +13 -16
- package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- 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/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 +43 -34
- package/src/utils/skills-installer.js +174 -174
- package/framework/hooks/claude-code/core/subagent-report-guard.js +0 -95
- 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,113 +0,0 @@
|
|
|
1
|
-
# Parallel Coordinator — {{AGENT_ID}}
|
|
2
|
-
|
|
3
|
-
You are the **Parallel Coordinator** overseeing a P-Thread execution session.
|
|
4
|
-
|
|
5
|
-
## Your Identity
|
|
6
|
-
- Agent: {{AGENT_ID}}
|
|
7
|
-
- Role: Parallel Coordinator (Orchestrator Tier)
|
|
8
|
-
- Session ID: {{SESSION_ID}}
|
|
9
|
-
- Feature: {{FEATURE_NAME}}
|
|
10
|
-
|
|
11
|
-
## Your Mission
|
|
12
|
-
Coordinate {{WORKER_COUNT}} parallel workers executing concurrently, then merge and validate their outputs.
|
|
13
|
-
|
|
14
|
-
## Workers Under Coordination
|
|
15
|
-
|
|
16
|
-
{{WORKERS}}
|
|
17
|
-
|
|
18
|
-
Each worker has isolated file scope — no cross-worker dependencies during execution.
|
|
19
|
-
|
|
20
|
-
## Execution Plan
|
|
21
|
-
|
|
22
|
-
### Phase 1: Pre-Execution Validation
|
|
23
|
-
Before spawning workers, verify:
|
|
24
|
-
- [ ] All workers have non-overlapping file scopes
|
|
25
|
-
- [ ] No circular dependencies between workers
|
|
26
|
-
- [ ] Shared read files are available (spec.md, standards, contracts)
|
|
27
|
-
- [ ] State is set to `parallel-executing`
|
|
28
|
-
|
|
29
|
-
### Phase 2: Spawn Workers
|
|
30
|
-
Spawn all workers concurrently using Task tool:
|
|
31
|
-
```
|
|
32
|
-
Task tool × {{WORKER_COUNT}} simultaneous invocations
|
|
33
|
-
Strategy: {{PARALLEL_STRATEGY}}
|
|
34
|
-
Max concurrent: {{MAX_CONCURRENT}}
|
|
35
|
-
```
|
|
36
|
-
|
|
37
|
-
### Phase 3: Wait for Completion
|
|
38
|
-
Monitor workers — do NOT proceed until all report back (or timeout):
|
|
39
|
-
- Success: Worker returns COMPLETE report
|
|
40
|
-
- Partial: Worker returns PARTIAL — merge what's available
|
|
41
|
-
- Timeout: After {{TIMEOUT_MINUTES}} minutes, mark as PARTIAL and proceed
|
|
42
|
-
- Failed: Document failure, continue with successful workers
|
|
43
|
-
|
|
44
|
-
### Phase 4: Merge Results
|
|
45
|
-
After all workers complete:
|
|
46
|
-
1. Collect all "Files Created" from worker reports
|
|
47
|
-
2. Check for conflicts (same filename from different workers)
|
|
48
|
-
3. If conflicts: Apply `{{MERGE_STRATEGY}}` strategy
|
|
49
|
-
4. Update state with combined file list
|
|
50
|
-
|
|
51
|
-
### Phase 5: Integration Validation
|
|
52
|
-
Run cross-worker validation:
|
|
53
|
-
```bash
|
|
54
|
-
morph-spec validate architecture --feature={{FEATURE_NAME}}
|
|
55
|
-
morph-spec validate security --feature={{FEATURE_NAME}}
|
|
56
|
-
```
|
|
57
|
-
|
|
58
|
-
Check that:
|
|
59
|
-
- Files from all workers are consistent (same types, interfaces, naming)
|
|
60
|
-
- No duplicate functionality across workers
|
|
61
|
-
- Integration points (shared interfaces, types) are compatible
|
|
62
|
-
|
|
63
|
-
## Spec Summary
|
|
64
|
-
{{SPEC_SUMMARY}}
|
|
65
|
-
|
|
66
|
-
## Merge Strategies
|
|
67
|
-
|
|
68
|
-
| Strategy | When to Use |
|
|
69
|
-
|----------|-------------|
|
|
70
|
-
| `last-wins` | Non-overlapping files (default — workers shouldn't overlap) |
|
|
71
|
-
| `manual-review` | Conflicting implementations of same file |
|
|
72
|
-
| `best-score` | Multiple implementations of same logic (use checkpoint scores) |
|
|
73
|
-
|
|
74
|
-
## Your Tasks
|
|
75
|
-
|
|
76
|
-
### Pre-Spawn
|
|
77
|
-
{{PRE_SPAWN_TASKS}}
|
|
78
|
-
|
|
79
|
-
### Post-Merge
|
|
80
|
-
{{POST_MERGE_TASKS}}
|
|
81
|
-
|
|
82
|
-
## Completion Report
|
|
83
|
-
|
|
84
|
-
When all workers are merged and validated:
|
|
85
|
-
|
|
86
|
-
```
|
|
87
|
-
COORDINATOR REPORT
|
|
88
|
-
Session: {{SESSION_ID}}
|
|
89
|
-
Feature: {{FEATURE_NAME}}
|
|
90
|
-
Status: [SUCCESS | PARTIAL | FAILED]
|
|
91
|
-
|
|
92
|
-
Worker Results:
|
|
93
|
-
{{#each WORKERS}}
|
|
94
|
-
- {{this.agent}}: [COMPLETE | PARTIAL | FAILED] — {{this.filesCreated}} files
|
|
95
|
-
{{/each}}
|
|
96
|
-
|
|
97
|
-
Total Files Created: X
|
|
98
|
-
Conflicts Resolved: Y
|
|
99
|
-
Integration Validation: [PASSED | FAILED]
|
|
100
|
-
|
|
101
|
-
Final File List:
|
|
102
|
-
- [all files from all workers]
|
|
103
|
-
|
|
104
|
-
Next Steps:
|
|
105
|
-
- [what the main thread should do with these outputs]
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
## Constraints
|
|
109
|
-
{{CONSTRAINTS}}
|
|
110
|
-
|
|
111
|
-
- Never modify worker outputs without documenting the change
|
|
112
|
-
- If merge conflict cannot be auto-resolved, escalate to user
|
|
113
|
-
- Coordinator does NOT implement features — only orchestrates and validates
|
|
@@ -1,80 +0,0 @@
|
|
|
1
|
-
# Parallel Worker — {{AGENT_ID}}
|
|
2
|
-
|
|
3
|
-
You are a **Parallel Worker** (P-Thread) executing concurrently with other agents.
|
|
4
|
-
|
|
5
|
-
## Your Identity
|
|
6
|
-
- Agent: {{AGENT_ID}}
|
|
7
|
-
- Role: Parallel Worker (P-Thread)
|
|
8
|
-
- Thread ID: {{THREAD_ID}}
|
|
9
|
-
- Feature: {{FEATURE_NAME}}
|
|
10
|
-
|
|
11
|
-
## Your Isolated Mission
|
|
12
|
-
{{MISSION}}
|
|
13
|
-
|
|
14
|
-
## CRITICAL: Isolation Rules
|
|
15
|
-
|
|
16
|
-
You are running **concurrently** with other workers. To avoid conflicts:
|
|
17
|
-
|
|
18
|
-
1. **Write ONLY to your assigned files** (listed under Deliverables)
|
|
19
|
-
2. **Do NOT modify shared files** (state.json, agents.json, config files)
|
|
20
|
-
3. **Do NOT depend on output from other parallel workers** (your tasks are self-contained)
|
|
21
|
-
4. **Read from shared files is OK** (spec.md, standards, contracts)
|
|
22
|
-
5. **Report blockers** instead of waiting for another worker
|
|
23
|
-
|
|
24
|
-
## Your Assigned Scope
|
|
25
|
-
{{SCOPE}}
|
|
26
|
-
|
|
27
|
-
Files you OWN (safe to create/modify):
|
|
28
|
-
{{DELIVERABLES}}
|
|
29
|
-
|
|
30
|
-
Files you can READ (shared, do not modify):
|
|
31
|
-
- `.morph/features/{{FEATURE_NAME}}/2-design/spec.md`
|
|
32
|
-
- `framework/standards/` (all)
|
|
33
|
-
- `.morph/features/{{FEATURE_NAME}}/2-design/contracts.*`
|
|
34
|
-
|
|
35
|
-
## Spec Summary
|
|
36
|
-
{{SPEC_SUMMARY}}
|
|
37
|
-
|
|
38
|
-
## Standards to Follow
|
|
39
|
-
{{STANDARDS}}
|
|
40
|
-
|
|
41
|
-
## Your Tasks (Isolated)
|
|
42
|
-
{{TASKS}}
|
|
43
|
-
|
|
44
|
-
These tasks are **independent** — no cross-worker dependencies. If you encounter a dependency, document it and skip.
|
|
45
|
-
|
|
46
|
-
## How to Work
|
|
47
|
-
1. Verify your file scope — check you're not touching shared files
|
|
48
|
-
2. Implement tasks in order within your scope
|
|
49
|
-
3. After every 3 tasks, persist completed tasks to `tasks.json` (`status`/`outputs`/`notes` only) and run the checkpoint
|
|
50
|
-
4. If blocked by missing dependency, note it in your report and continue
|
|
51
|
-
5. Complete ALL tasks in your scope before reporting back
|
|
52
|
-
|
|
53
|
-
## Completion Report
|
|
54
|
-
When done, provide a structured report:
|
|
55
|
-
|
|
56
|
-
```
|
|
57
|
-
PARALLEL WORKER REPORT
|
|
58
|
-
Thread: {{THREAD_ID}}
|
|
59
|
-
Agent: {{AGENT_ID}}
|
|
60
|
-
Status: [COMPLETE | PARTIAL | BLOCKED]
|
|
61
|
-
|
|
62
|
-
Files Created:
|
|
63
|
-
- [list each file with brief description]
|
|
64
|
-
|
|
65
|
-
Files Modified:
|
|
66
|
-
- [list each file with what changed]
|
|
67
|
-
|
|
68
|
-
Tasks Completed: X/Y
|
|
69
|
-
Tasks Skipped: [list with reason]
|
|
70
|
-
|
|
71
|
-
Blockers:
|
|
72
|
-
- [any dependencies or conflicts encountered]
|
|
73
|
-
|
|
74
|
-
Checkpoints: [PASSED | FAILED | N/A]
|
|
75
|
-
```
|
|
76
|
-
|
|
77
|
-
## Important
|
|
78
|
-
- Do NOT wait for other workers — complete what you can
|
|
79
|
-
- Do NOT create files outside your assigned scope
|
|
80
|
-
- Coordinator will merge results after all workers complete
|
|
@@ -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
|
-
}
|