feat(mcpd): EpicTask resource — roadmap nodes, edges, and the roadmap view
Roadmap nodes plus the edges that chain them into the dependency graph the design doc calls the spider web. Edge direction is "from depends on to". The database cannot enforce graph integrity here — production provisions with `prisma db push`, so a trigger or CHECK would never exist there, and the schema test added earlier documents that boundary explicitly. So the service rejects the three things that would otherwise corrupt the roadmap: - self-edges, - edges crossing Epics, since a roadmap is one Epic's graph, - cycles, which make the graph unschedulable and hang any topological walk. Cycle detection is a BFS over the prerequisite direction: adding from→to closes a loop exactly when `to` already depends transitively on `from`. The error names the offending path, not just "cycle", so a failed apply says which nodes to look at. The traversal carries a seen-set that also guarantees termination if the stored graph ALREADY contains a cycle — from a hand-edited database or an earlier bug — rather than spinning forever. There is a test for that case. Adding an existing edge is a no-op rather than an error, so applying a manifest twice is safe. GET /api/v1/epics/:id/roadmap returns nodes with per-node completeness, both edge directions, and `ready`: the nodes whose prerequisites are all satisfied, which is the question anyone actually asks of a roadmap. A cancelled prerequisite counts as satisfied — it is not coming. Completeness reuses EpicService.completeness verbatim so a node and its Epic can never disagree about what "met" means. Tests: 19 service tests — the full cycle matrix (self, 2-cycle, long transitive, diamond-is-fine, pre-existing-cycle termination), roadmap edge directions, readiness including the cancelled-prerequisite case, and CRUD guards. Local: build clean, workspace 2366 passed (+19), db 128 passed (same 4 pre-existing Secret failures), lint unchanged at 869. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01N4wNHWf7xSwnZCWpJcyv9p
This commit is contained in:
@@ -2727,9 +2727,9 @@
|
||||
"117",
|
||||
"123"
|
||||
],
|
||||
"status": "pending",
|
||||
"status": "done",
|
||||
"subtasks": [],
|
||||
"updatedAt": "2026-08-08T08:55:58.627Z"
|
||||
"updatedAt": "2026-08-08T10:10:39.751Z"
|
||||
},
|
||||
{
|
||||
"id": "128",
|
||||
@@ -2741,9 +2741,9 @@
|
||||
"dependencies": [
|
||||
"127"
|
||||
],
|
||||
"status": "pending",
|
||||
"status": "done",
|
||||
"subtasks": [],
|
||||
"updatedAt": "2026-08-08T08:55:58.627Z"
|
||||
"updatedAt": "2026-08-08T10:10:39.761Z"
|
||||
},
|
||||
{
|
||||
"id": "129",
|
||||
@@ -3251,9 +3251,9 @@
|
||||
],
|
||||
"metadata": {
|
||||
"version": "1.0.0",
|
||||
"lastModified": "2026-08-08T10:05:28.178Z",
|
||||
"lastModified": "2026-08-08T10:10:39.761Z",
|
||||
"taskCount": 162,
|
||||
"completedCount": 111,
|
||||
"completedCount": 113,
|
||||
"tags": [
|
||||
"master"
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user