octie-cli 1.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/README.md +523 -0
- package/dist/cli/commands/approve.d.ts +27 -0
- package/dist/cli/commands/approve.d.ts.map +1 -0
- package/dist/cli/commands/approve.js +119 -0
- package/dist/cli/commands/approve.js.map +1 -0
- package/dist/cli/commands/batch.d.ts +15 -0
- package/dist/cli/commands/batch.d.ts.map +1 -0
- package/dist/cli/commands/batch.js +521 -0
- package/dist/cli/commands/batch.js.map +1 -0
- package/dist/cli/commands/create.d.ts +9 -0
- package/dist/cli/commands/create.d.ts.map +1 -0
- package/dist/cli/commands/create.js +321 -0
- package/dist/cli/commands/create.js.map +1 -0
- package/dist/cli/commands/delete.d.ts +9 -0
- package/dist/cli/commands/delete.d.ts.map +1 -0
- package/dist/cli/commands/delete.js +143 -0
- package/dist/cli/commands/delete.js.map +1 -0
- package/dist/cli/commands/export.d.ts +9 -0
- package/dist/cli/commands/export.d.ts.map +1 -0
- package/dist/cli/commands/export.js +66 -0
- package/dist/cli/commands/export.js.map +1 -0
- package/dist/cli/commands/find.d.ts +16 -0
- package/dist/cli/commands/find.d.ts.map +1 -0
- package/dist/cli/commands/find.js +252 -0
- package/dist/cli/commands/find.js.map +1 -0
- package/dist/cli/commands/get.d.ts +9 -0
- package/dist/cli/commands/get.d.ts.map +1 -0
- package/dist/cli/commands/get.js +74 -0
- package/dist/cli/commands/get.js.map +1 -0
- package/dist/cli/commands/graph.d.ts +9 -0
- package/dist/cli/commands/graph.d.ts.map +1 -0
- package/dist/cli/commands/graph.js +200 -0
- package/dist/cli/commands/graph.js.map +1 -0
- package/dist/cli/commands/import.d.ts +9 -0
- package/dist/cli/commands/import.d.ts.map +1 -0
- package/dist/cli/commands/import.js +807 -0
- package/dist/cli/commands/import.js.map +1 -0
- package/dist/cli/commands/init.d.ts +9 -0
- package/dist/cli/commands/init.d.ts.map +1 -0
- package/dist/cli/commands/init.js +57 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/list.d.ts +9 -0
- package/dist/cli/commands/list.d.ts.map +1 -0
- package/dist/cli/commands/list.js +175 -0
- package/dist/cli/commands/list.js.map +1 -0
- package/dist/cli/commands/merge.d.ts +9 -0
- package/dist/cli/commands/merge.d.ts.map +1 -0
- package/dist/cli/commands/merge.js +113 -0
- package/dist/cli/commands/merge.js.map +1 -0
- package/dist/cli/commands/serve.d.ts +9 -0
- package/dist/cli/commands/serve.d.ts.map +1 -0
- package/dist/cli/commands/serve.js +94 -0
- package/dist/cli/commands/serve.js.map +1 -0
- package/dist/cli/commands/update.d.ts +9 -0
- package/dist/cli/commands/update.d.ts.map +1 -0
- package/dist/cli/commands/update.js +423 -0
- package/dist/cli/commands/update.js.map +1 -0
- package/dist/cli/commands/wire.d.ts +15 -0
- package/dist/cli/commands/wire.d.ts.map +1 -0
- package/dist/cli/commands/wire.js +164 -0
- package/dist/cli/commands/wire.js.map +1 -0
- package/dist/cli/index.d.ts +7 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +100 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/output/json.d.ts +16 -0
- package/dist/cli/output/json.d.ts.map +1 -0
- package/dist/cli/output/json.js +29 -0
- package/dist/cli/output/json.js.map +1 -0
- package/dist/cli/output/markdown.d.ts +15 -0
- package/dist/cli/output/markdown.d.ts.map +1 -0
- package/dist/cli/output/markdown.js +206 -0
- package/dist/cli/output/markdown.js.map +1 -0
- package/dist/cli/output/table.d.ts +23 -0
- package/dist/cli/output/table.d.ts.map +1 -0
- package/dist/cli/output/table.js +150 -0
- package/dist/cli/output/table.js.map +1 -0
- package/dist/cli/utils/helpers.d.ts +126 -0
- package/dist/cli/utils/helpers.d.ts.map +1 -0
- package/dist/cli/utils/helpers.js +325 -0
- package/dist/cli/utils/helpers.js.map +1 -0
- package/dist/core/graph/algorithms.d.ts +11 -0
- package/dist/core/graph/algorithms.d.ts.map +1 -0
- package/dist/core/graph/algorithms.js +14 -0
- package/dist/core/graph/algorithms.js.map +1 -0
- package/dist/core/graph/cycle.d.ts +155 -0
- package/dist/core/graph/cycle.d.ts.map +1 -0
- package/dist/core/graph/cycle.js +297 -0
- package/dist/core/graph/cycle.js.map +1 -0
- package/dist/core/graph/index.d.ts +223 -0
- package/dist/core/graph/index.d.ts.map +1 -0
- package/dist/core/graph/index.js +475 -0
- package/dist/core/graph/index.js.map +1 -0
- package/dist/core/graph/operations.d.ts +240 -0
- package/dist/core/graph/operations.d.ts.map +1 -0
- package/dist/core/graph/operations.js +503 -0
- package/dist/core/graph/operations.js.map +1 -0
- package/dist/core/graph/sort.d.ts +76 -0
- package/dist/core/graph/sort.d.ts.map +1 -0
- package/dist/core/graph/sort.js +254 -0
- package/dist/core/graph/sort.js.map +1 -0
- package/dist/core/graph/traversal.d.ts +122 -0
- package/dist/core/graph/traversal.d.ts.map +1 -0
- package/dist/core/graph/traversal.js +336 -0
- package/dist/core/graph/traversal.js.map +1 -0
- package/dist/core/models/task-node.d.ts +328 -0
- package/dist/core/models/task-node.d.ts.map +1 -0
- package/dist/core/models/task-node.js +1090 -0
- package/dist/core/models/task-node.js.map +1 -0
- package/dist/core/registry/index.d.ts +102 -0
- package/dist/core/registry/index.d.ts.map +1 -0
- package/dist/core/registry/index.js +249 -0
- package/dist/core/registry/index.js.map +1 -0
- package/dist/core/registry/root-guard.d.ts +19 -0
- package/dist/core/registry/root-guard.d.ts.map +1 -0
- package/dist/core/registry/root-guard.js +28 -0
- package/dist/core/registry/root-guard.js.map +1 -0
- package/dist/core/storage/atomic-write.d.ts +181 -0
- package/dist/core/storage/atomic-write.d.ts.map +1 -0
- package/dist/core/storage/atomic-write.js +379 -0
- package/dist/core/storage/atomic-write.js.map +1 -0
- package/dist/core/storage/file-store.d.ts +148 -0
- package/dist/core/storage/file-store.d.ts.map +1 -0
- package/dist/core/storage/file-store.js +423 -0
- package/dist/core/storage/file-store.js.map +1 -0
- package/dist/core/storage/indexer.d.ts +138 -0
- package/dist/core/storage/indexer.d.ts.map +1 -0
- package/dist/core/storage/indexer.js +350 -0
- package/dist/core/storage/indexer.js.map +1 -0
- package/dist/core/utils/status-helpers.d.ts +59 -0
- package/dist/core/utils/status-helpers.d.ts.map +1 -0
- package/dist/core/utils/status-helpers.js +149 -0
- package/dist/core/utils/status-helpers.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/types/index.d.ts +504 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +182 -0
- package/dist/types/index.js.map +1 -0
- package/dist/web/routes/graph.d.ts +17 -0
- package/dist/web/routes/graph.d.ts.map +1 -0
- package/dist/web/routes/graph.js +277 -0
- package/dist/web/routes/graph.js.map +1 -0
- package/dist/web/routes/projects.d.ts +14 -0
- package/dist/web/routes/projects.d.ts.map +1 -0
- package/dist/web/routes/projects.js +102 -0
- package/dist/web/routes/projects.js.map +1 -0
- package/dist/web/routes/tasks.d.ts +17 -0
- package/dist/web/routes/tasks.d.ts.map +1 -0
- package/dist/web/routes/tasks.js +538 -0
- package/dist/web/routes/tasks.js.map +1 -0
- package/dist/web/server.d.ts +121 -0
- package/dist/web/server.d.ts.map +1 -0
- package/dist/web/server.js +389 -0
- package/dist/web/server.js.map +1 -0
- package/dist/web-ui/assets/index-BB0qvF1y.css +1 -0
- package/dist/web-ui/assets/index-Vmm72oKY.js +34 -0
- package/dist/web-ui/index.html +14 -0
- package/dist/web-ui/vite.svg +1 -0
- package/package.json +94 -0
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graph manipulation operations
|
|
3
|
+
*
|
|
4
|
+
* Implements complex graph operations for task management:
|
|
5
|
+
* - Cut nodes from graph while reconnecting edges
|
|
6
|
+
* - Insert nodes between existing nodes
|
|
7
|
+
* - Move subtrees to new parents
|
|
8
|
+
* - Merge tasks together
|
|
9
|
+
*
|
|
10
|
+
* @module core/graph/operations
|
|
11
|
+
*/
|
|
12
|
+
import { TaskNode } from '../models/task-node.js';
|
|
13
|
+
import { TaskNotFoundError, ValidationError } from '../../types/index.js';
|
|
14
|
+
/**
|
|
15
|
+
* Cut a node from the graph, reconnecting its incoming edges to its outgoing edges
|
|
16
|
+
*
|
|
17
|
+
* Before: A -> B -> C
|
|
18
|
+
* After: A -> C (B removed)
|
|
19
|
+
*
|
|
20
|
+
* Algorithm:
|
|
21
|
+
* 1. Get all incoming edges to the node
|
|
22
|
+
* 2. Get all outgoing edges from the node
|
|
23
|
+
* 3. For each incoming source, connect it to all outgoing targets
|
|
24
|
+
* 4. Update target blockers to reference new sources instead of deleted node
|
|
25
|
+
* 5. Remove the node from the graph
|
|
26
|
+
*
|
|
27
|
+
* Time Complexity: O(k * m) where k = incoming edges, m = outgoing edges
|
|
28
|
+
*
|
|
29
|
+
* @param graph - Task graph store
|
|
30
|
+
* @param nodeId - Task ID to cut
|
|
31
|
+
* @throws {TaskNotFoundError} If task not found
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* // Graph: A -> B -> C
|
|
36
|
+
* cutNode(graph, 'B');
|
|
37
|
+
* // Result: A -> C (B removed, A now points directly to C)
|
|
38
|
+
* // Also: C.blockers updated from [B] to [A]
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
export function cutNode(graph, nodeId) {
|
|
42
|
+
if (!graph.hasNode(nodeId)) {
|
|
43
|
+
throw new TaskNotFoundError(nodeId);
|
|
44
|
+
}
|
|
45
|
+
// Get all incoming and outgoing edges before removing the node
|
|
46
|
+
const incomingSources = graph.getIncomingEdges(nodeId);
|
|
47
|
+
const outgoingTargets = graph.getOutgoingEdges(nodeId);
|
|
48
|
+
// Reconnect: for each incoming source, connect to all outgoing targets
|
|
49
|
+
for (const sourceId of incomingSources) {
|
|
50
|
+
for (const targetId of outgoingTargets) {
|
|
51
|
+
// Only add edge if it doesn't already exist
|
|
52
|
+
if (!graph.hasEdge(sourceId, targetId)) {
|
|
53
|
+
graph.addEdge(sourceId, targetId);
|
|
54
|
+
// Update target's blockers array to include the new source
|
|
55
|
+
const targetNode = graph.getNode(targetId);
|
|
56
|
+
if (targetNode && !targetNode.blockers.includes(sourceId)) {
|
|
57
|
+
targetNode.addBlocker(sourceId);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
// Remove deleted nodeId from all targets' blockers arrays
|
|
63
|
+
for (const targetId of outgoingTargets) {
|
|
64
|
+
const targetNode = graph.getNode(targetId);
|
|
65
|
+
if (targetNode && targetNode.blockers.includes(nodeId)) {
|
|
66
|
+
targetNode.removeBlocker(nodeId);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// Remove the node (this also removes all its edges)
|
|
70
|
+
graph.removeNode(nodeId);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Insert a node between two existing nodes
|
|
74
|
+
*
|
|
75
|
+
* Before: A -> C
|
|
76
|
+
* After: A -> B -> C
|
|
77
|
+
*
|
|
78
|
+
* Algorithm:
|
|
79
|
+
* 1. Verify both nodes exist
|
|
80
|
+
* 2. Verify edge exists from afterId to beforeId
|
|
81
|
+
* 3. Remove the existing edge
|
|
82
|
+
* 4. Add the new node to the graph
|
|
83
|
+
* 5. Create edges: afterId -> newNodeId -> beforeId
|
|
84
|
+
*
|
|
85
|
+
* Time Complexity: O(1) for edge operations
|
|
86
|
+
*
|
|
87
|
+
* @param graph - Task graph store
|
|
88
|
+
* @param newNode - Task node to insert
|
|
89
|
+
* @param afterId - Source node ID (edge comes from here)
|
|
90
|
+
* @param beforeId - Target node ID (edge goes to here)
|
|
91
|
+
* @throws {TaskNotFoundError} If afterId or beforeId not found
|
|
92
|
+
* @throws {ValidationError} If edge doesn't exist or would create duplicate
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```ts
|
|
96
|
+
* // Graph: A -> C
|
|
97
|
+
* const newNode = createTaskNode({ title: 'B', ... });
|
|
98
|
+
* insertNodeBetween(graph, newNode, 'A', 'C');
|
|
99
|
+
* // Result: A -> B -> C
|
|
100
|
+
* ```
|
|
101
|
+
*/
|
|
102
|
+
export function insertNodeBetween(graph, newNode, afterId, beforeId) {
|
|
103
|
+
// Verify source and target nodes exist
|
|
104
|
+
if (!graph.hasNode(afterId)) {
|
|
105
|
+
throw new TaskNotFoundError(afterId);
|
|
106
|
+
}
|
|
107
|
+
if (!graph.hasNode(beforeId)) {
|
|
108
|
+
throw new TaskNotFoundError(beforeId);
|
|
109
|
+
}
|
|
110
|
+
// Verify edge exists
|
|
111
|
+
if (!graph.hasEdge(afterId, beforeId)) {
|
|
112
|
+
throw new ValidationError(`Edge from '${afterId}' to '${beforeId}' does not exist`, 'edges');
|
|
113
|
+
}
|
|
114
|
+
// Add the new node to the graph first
|
|
115
|
+
graph.addNode(newNode);
|
|
116
|
+
// Remove the existing edge
|
|
117
|
+
graph.removeEdge(afterId, beforeId);
|
|
118
|
+
// Add new edges: afterId -> newNodeId -> beforeId
|
|
119
|
+
graph.addEdge(afterId, newNode.id);
|
|
120
|
+
graph.addEdge(newNode.id, beforeId);
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Move a subtree to a new parent
|
|
124
|
+
*
|
|
125
|
+
* Moves a task (and all its descendants) to be under a new parent.
|
|
126
|
+
* This is useful for reorganizing task hierarchies.
|
|
127
|
+
*
|
|
128
|
+
* Before:
|
|
129
|
+
* A -> X
|
|
130
|
+
* B -> Y
|
|
131
|
+
* After moveSubtree(graph, 'Y', 'A'):
|
|
132
|
+
* A -> X -> Y
|
|
133
|
+
* B
|
|
134
|
+
*
|
|
135
|
+
* Algorithm:
|
|
136
|
+
* 1. Get all current parents of the subtree root
|
|
137
|
+
* 2. Remove edges from all current parents
|
|
138
|
+
* 3. Add edge from new parent to subtree root
|
|
139
|
+
*
|
|
140
|
+
* Time Complexity: O(k) where k = number of current parents
|
|
141
|
+
*
|
|
142
|
+
* @param graph - Task graph store
|
|
143
|
+
* @param subtreeRootId - Root of the subtree to move
|
|
144
|
+
* @param newParentId - New parent task ID
|
|
145
|
+
* @throws {TaskNotFoundError} If subtreeRootId or newParentId not found
|
|
146
|
+
* @throws {ValidationError} If edge already exists or would create self-loop
|
|
147
|
+
*
|
|
148
|
+
* @example
|
|
149
|
+
* ```ts
|
|
150
|
+
* // Graph: A -> X, B -> Y
|
|
151
|
+
* moveSubtree(graph, 'Y', 'X');
|
|
152
|
+
* // Result: A -> X -> Y, B
|
|
153
|
+
* ```
|
|
154
|
+
*/
|
|
155
|
+
export function moveSubtree(graph, subtreeRootId, newParentId) {
|
|
156
|
+
// Verify both nodes exist
|
|
157
|
+
if (!graph.hasNode(subtreeRootId)) {
|
|
158
|
+
throw new TaskNotFoundError(subtreeRootId);
|
|
159
|
+
}
|
|
160
|
+
if (!graph.hasNode(newParentId)) {
|
|
161
|
+
throw new TaskNotFoundError(newParentId);
|
|
162
|
+
}
|
|
163
|
+
// Prevent self-loops
|
|
164
|
+
if (subtreeRootId === newParentId) {
|
|
165
|
+
throw new ValidationError('Cannot move subtree to be its own parent', 'subtreeRootId');
|
|
166
|
+
}
|
|
167
|
+
// Check if edge already exists
|
|
168
|
+
if (graph.hasEdge(newParentId, subtreeRootId)) {
|
|
169
|
+
throw new ValidationError(`Edge from '${newParentId}' to '${subtreeRootId}' already exists`, 'edges');
|
|
170
|
+
}
|
|
171
|
+
// Get all current parents (nodes that point to subtreeRootId)
|
|
172
|
+
const currentParents = graph.getIncomingEdges(subtreeRootId);
|
|
173
|
+
// Remove from all current parents
|
|
174
|
+
for (const parentId of currentParents) {
|
|
175
|
+
graph.removeEdge(parentId, subtreeRootId);
|
|
176
|
+
}
|
|
177
|
+
// Add to new parent
|
|
178
|
+
graph.addEdge(newParentId, subtreeRootId);
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Merge two tasks into one
|
|
182
|
+
*
|
|
183
|
+
* Combines the source task into the target task by:
|
|
184
|
+
* 1. Merging all properties (description, success_criteria, deliverables, etc.)
|
|
185
|
+
* 2. Reconnecting all edges from source to point to target
|
|
186
|
+
* 3. Removing the source task
|
|
187
|
+
*
|
|
188
|
+
* Before:
|
|
189
|
+
* A -> source -> C
|
|
190
|
+
* B -> target -> D
|
|
191
|
+
* After mergeTasks(graph, 'source', 'target'):
|
|
192
|
+
* A -> target -> C, D
|
|
193
|
+
* B -> target -> C, D
|
|
194
|
+
* (source is removed)
|
|
195
|
+
*
|
|
196
|
+
* Algorithm:
|
|
197
|
+
* 1. Get source and target tasks
|
|
198
|
+
* 2. Merge target properties with source properties
|
|
199
|
+
* 3. Get all incoming edges to source (except target)
|
|
200
|
+
* 4. Get all outgoing edges from source (except target)
|
|
201
|
+
* 5. Reconnect edges: incoming -> target, target -> outgoing
|
|
202
|
+
* 6. Remove source node
|
|
203
|
+
* 7. Return merge result
|
|
204
|
+
*
|
|
205
|
+
* Time Complexity: O(k + m) where k = incoming edges, m = outgoing edges
|
|
206
|
+
*
|
|
207
|
+
* @param graph - Task graph store
|
|
208
|
+
* @param sourceId - Source task ID (will be removed)
|
|
209
|
+
* @param targetId - Target task ID (will be kept and merged into)
|
|
210
|
+
* @returns Merge result with merged task and affected task IDs
|
|
211
|
+
* @throws {TaskNotFoundError} If source or target not found
|
|
212
|
+
* @throws {ValidationError} If source and target are the same
|
|
213
|
+
*
|
|
214
|
+
* @example
|
|
215
|
+
* ```ts
|
|
216
|
+
* const result = mergeTasks(graph, 'task-001', 'task-002');
|
|
217
|
+
* console.log('Merged task:', result.task.id);
|
|
218
|
+
* console.log('Removed tasks:', result.removedTasks);
|
|
219
|
+
* ```
|
|
220
|
+
*/
|
|
221
|
+
export function mergeTasks(graph, sourceId, targetId) {
|
|
222
|
+
// Verify both tasks exist
|
|
223
|
+
const source = graph.getNode(sourceId);
|
|
224
|
+
const target = graph.getNode(targetId);
|
|
225
|
+
if (!source) {
|
|
226
|
+
throw new TaskNotFoundError(sourceId);
|
|
227
|
+
}
|
|
228
|
+
if (!target) {
|
|
229
|
+
throw new TaskNotFoundError(targetId);
|
|
230
|
+
}
|
|
231
|
+
// Cannot merge a task with itself
|
|
232
|
+
if (sourceId === targetId) {
|
|
233
|
+
throw new ValidationError('Cannot merge a task with itself', 'sourceId');
|
|
234
|
+
}
|
|
235
|
+
// Merge properties into target task
|
|
236
|
+
// Merge descriptions with separator
|
|
237
|
+
const newDescription = target.description
|
|
238
|
+
? `${target.description}\n\n--- Merged from "${source.title}" ---\n${source.description}`
|
|
239
|
+
: source.description;
|
|
240
|
+
target.setDescription(newDescription);
|
|
241
|
+
// Merge success criteria (avoiding duplicates by ID)
|
|
242
|
+
for (const sc of source.success_criteria) {
|
|
243
|
+
if (!target.success_criteria.some(tsc => tsc.id === sc.id)) {
|
|
244
|
+
target.addSuccessCriterion(sc);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
// Merge deliverables (avoiding duplicates by ID)
|
|
248
|
+
for (const d of source.deliverables) {
|
|
249
|
+
if (!target.deliverables.some(td => td.id === d.id)) {
|
|
250
|
+
target.addDeliverable(d);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
// Merge related files (avoiding duplicates)
|
|
254
|
+
for (const file of source.related_files) {
|
|
255
|
+
target.addRelatedFile(file);
|
|
256
|
+
}
|
|
257
|
+
// Merge notes with separator
|
|
258
|
+
const newNotes = target.notes
|
|
259
|
+
? `${target.notes}\n\nMerged notes from "${source.title}":\n${source.notes}`
|
|
260
|
+
: source.notes;
|
|
261
|
+
target.appendNotes(newNotes);
|
|
262
|
+
// Merge C7 verifications
|
|
263
|
+
for (const cv of source.c7_verified) {
|
|
264
|
+
target.addC7Verification(cv);
|
|
265
|
+
}
|
|
266
|
+
// Remove source from target's blockers (source is being merged/removed)
|
|
267
|
+
if (target.blockers.includes(sourceId)) {
|
|
268
|
+
target.removeBlocker(sourceId);
|
|
269
|
+
}
|
|
270
|
+
// Merge blockers (avoiding duplicates and self-references)
|
|
271
|
+
for (const blockerId of source.blockers) {
|
|
272
|
+
if (blockerId !== targetId && !target.blockers.includes(blockerId)) {
|
|
273
|
+
target.addBlocker(blockerId);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
// Merge dependencies explanation text (combine both)
|
|
277
|
+
if (source.dependencies) {
|
|
278
|
+
if (target.dependencies) {
|
|
279
|
+
target.setDependencies(`${target.dependencies}\n${source.dependencies}`);
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
target.setDependencies(source.dependencies);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
// Track which tasks were affected by reconnection
|
|
286
|
+
const reconnectSources = [];
|
|
287
|
+
const reconnectTargets = [];
|
|
288
|
+
// Get source's incoming edges (tasks pointing to source)
|
|
289
|
+
const sourceIncoming = graph.getIncomingEdges(sourceId);
|
|
290
|
+
// Get source's outgoing edges (tasks source points to)
|
|
291
|
+
const sourceOutgoing = graph.getOutgoingEdges(sourceId);
|
|
292
|
+
// Reconnect incoming edges (except target and self-loops)
|
|
293
|
+
for (const fromId of sourceIncoming) {
|
|
294
|
+
if (fromId !== targetId && !graph.hasEdge(fromId, targetId)) {
|
|
295
|
+
graph.addEdge(fromId, targetId);
|
|
296
|
+
reconnectSources.push(fromId);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
// Reconnect outgoing edges (except target and self-loops)
|
|
300
|
+
for (const toId of sourceOutgoing) {
|
|
301
|
+
if (toId !== targetId && !graph.hasEdge(targetId, toId)) {
|
|
302
|
+
graph.addEdge(targetId, toId);
|
|
303
|
+
reconnectTargets.push(toId);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
// Update the target task in the graph with merged data
|
|
307
|
+
graph.updateNode(target);
|
|
308
|
+
// Remove the source task
|
|
309
|
+
graph.removeNode(sourceId);
|
|
310
|
+
// Collect all affected task IDs
|
|
311
|
+
const allAffected = new Set([
|
|
312
|
+
...reconnectSources,
|
|
313
|
+
...reconnectTargets,
|
|
314
|
+
targetId
|
|
315
|
+
]);
|
|
316
|
+
return {
|
|
317
|
+
task: target,
|
|
318
|
+
removedTasks: [sourceId],
|
|
319
|
+
updatedTasks: Array.from(allAffected)
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Get all descendants of a node (transitive closure via outgoing edges)
|
|
324
|
+
*
|
|
325
|
+
* Returns all tasks reachable from the given node.
|
|
326
|
+
*
|
|
327
|
+
* @param graph - Task graph store
|
|
328
|
+
* @param nodeId - Starting task ID
|
|
329
|
+
* @returns Set of descendant task IDs (including the starting node)
|
|
330
|
+
* @throws {TaskNotFoundError} If node not found
|
|
331
|
+
*
|
|
332
|
+
* @example
|
|
333
|
+
* ```ts
|
|
334
|
+
* // Graph: A -> B -> C, A -> D
|
|
335
|
+
* const descendants = getDescendants(graph, 'A');
|
|
336
|
+
* // Returns: ['A', 'B', 'C', 'D']
|
|
337
|
+
* ```
|
|
338
|
+
*/
|
|
339
|
+
export function getDescendants(graph, nodeId) {
|
|
340
|
+
if (!graph.hasNode(nodeId)) {
|
|
341
|
+
throw new TaskNotFoundError(nodeId);
|
|
342
|
+
}
|
|
343
|
+
const descendants = new Set();
|
|
344
|
+
const stack = [nodeId];
|
|
345
|
+
while (stack.length > 0) {
|
|
346
|
+
const current = stack.pop();
|
|
347
|
+
if (descendants.has(current)) {
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
descendants.add(current);
|
|
351
|
+
// Add all children to the stack
|
|
352
|
+
const children = graph.getOutgoingEdges(current);
|
|
353
|
+
for (const child of children) {
|
|
354
|
+
if (!descendants.has(child)) {
|
|
355
|
+
stack.push(child);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
return descendants;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Get all ancestors of a node (transitive closure via incoming edges)
|
|
363
|
+
*
|
|
364
|
+
* Returns all tasks that can reach the given node.
|
|
365
|
+
*
|
|
366
|
+
* @param graph - Task graph store
|
|
367
|
+
* @param nodeId - Starting task ID
|
|
368
|
+
* @returns Set of ancestor task IDs (including the starting node)
|
|
369
|
+
* @throws {TaskNotFoundError} If node not found
|
|
370
|
+
*
|
|
371
|
+
* @example
|
|
372
|
+
* ```ts
|
|
373
|
+
* // Graph: A -> B -> C
|
|
374
|
+
* const ancestors = getAncestors(graph, 'C');
|
|
375
|
+
* // Returns: ['C', 'B', 'A']
|
|
376
|
+
* ```
|
|
377
|
+
*/
|
|
378
|
+
export function getAncestors(graph, nodeId) {
|
|
379
|
+
if (!graph.hasNode(nodeId)) {
|
|
380
|
+
throw new TaskNotFoundError(nodeId);
|
|
381
|
+
}
|
|
382
|
+
const ancestors = new Set();
|
|
383
|
+
const stack = [nodeId];
|
|
384
|
+
while (stack.length > 0) {
|
|
385
|
+
const current = stack.pop();
|
|
386
|
+
if (ancestors.has(current)) {
|
|
387
|
+
continue;
|
|
388
|
+
}
|
|
389
|
+
ancestors.add(current);
|
|
390
|
+
// Add all parents to the stack
|
|
391
|
+
const parents = graph.getIncomingEdges(current);
|
|
392
|
+
for (const parent of parents) {
|
|
393
|
+
if (!ancestors.has(parent)) {
|
|
394
|
+
stack.push(parent);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
return ancestors;
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* Validate that moving a subtree won't create a cycle
|
|
402
|
+
*
|
|
403
|
+
* Checks if adding an edge from newParentId to subtreeRootId would create a cycle.
|
|
404
|
+
* This is important for moveSubtree operations.
|
|
405
|
+
*
|
|
406
|
+
* @param graph - Task graph store
|
|
407
|
+
* @param subtreeRootId - Root of the subtree to move
|
|
408
|
+
* @param newParentId - Potential new parent task ID
|
|
409
|
+
* @returns true if move is safe (won't create cycle), false otherwise
|
|
410
|
+
* @throws {TaskNotFoundError} If either task not found
|
|
411
|
+
*
|
|
412
|
+
* @example
|
|
413
|
+
* ```ts
|
|
414
|
+
* // Graph: A -> B -> C
|
|
415
|
+
* if (isValidSubtreeMove(graph, 'C', 'A')) {
|
|
416
|
+
* // This would create a cycle (A -> B -> C -> A)
|
|
417
|
+
* // Don't allow the move
|
|
418
|
+
* }
|
|
419
|
+
* ```
|
|
420
|
+
*/
|
|
421
|
+
export function isValidSubtreeMove(graph, subtreeRootId, newParentId) {
|
|
422
|
+
if (!graph.hasNode(subtreeRootId)) {
|
|
423
|
+
throw new TaskNotFoundError(subtreeRootId);
|
|
424
|
+
}
|
|
425
|
+
if (!graph.hasNode(newParentId)) {
|
|
426
|
+
throw new TaskNotFoundError(newParentId);
|
|
427
|
+
}
|
|
428
|
+
// Check if newParentId is in the descendants of subtreeRootId
|
|
429
|
+
// If so, adding edge subtreeRootId -> newParentId would create a cycle
|
|
430
|
+
const descendants = getDescendants(graph, subtreeRootId);
|
|
431
|
+
return !descendants.has(newParentId);
|
|
432
|
+
}
|
|
433
|
+
/**
|
|
434
|
+
* Cascade delete a node and all its dependent tasks
|
|
435
|
+
*
|
|
436
|
+
* Deletes the specified node and all tasks that depend on it (directly or transitively).
|
|
437
|
+
* Tasks are deleted in reverse order (leaves first) to maintain graph integrity.
|
|
438
|
+
*
|
|
439
|
+
* Before:
|
|
440
|
+
* A -> B -> C -> D
|
|
441
|
+
* After cascadeDelete(graph, 'B'):
|
|
442
|
+
* A (B, C, D removed)
|
|
443
|
+
*
|
|
444
|
+
* Algorithm:
|
|
445
|
+
* 1. Get all descendants of the node (tasks that depend on it transitively)
|
|
446
|
+
* 2. Iteratively find and delete leaf nodes (no outgoing edges) first
|
|
447
|
+
* 3. Continue until all descendants are deleted
|
|
448
|
+
*
|
|
449
|
+
* Time Complexity: O(k * m) where k = descendants, m = average edges
|
|
450
|
+
*
|
|
451
|
+
* @param graph - Task graph store
|
|
452
|
+
* @param nodeId - Task ID to cascade delete
|
|
453
|
+
* @returns Array of deleted task IDs (in order of deletion)
|
|
454
|
+
* @throws {TaskNotFoundError} If node not found
|
|
455
|
+
*
|
|
456
|
+
* @example
|
|
457
|
+
* ```ts
|
|
458
|
+
* // Graph: A -> B -> C -> D
|
|
459
|
+
* const deleted = cascadeDelete(graph, 'B');
|
|
460
|
+
* // Returns: ['D', 'C', 'B'] (deleted in this order)
|
|
461
|
+
* // Result: Only A remains
|
|
462
|
+
* ```
|
|
463
|
+
*/
|
|
464
|
+
export function cascadeDelete(graph, nodeId) {
|
|
465
|
+
if (!graph.hasNode(nodeId)) {
|
|
466
|
+
throw new TaskNotFoundError(nodeId);
|
|
467
|
+
}
|
|
468
|
+
const deletedIds = [];
|
|
469
|
+
// Get all tasks that depend on this node (directly or transitively)
|
|
470
|
+
const descendants = getDescendants(graph, nodeId);
|
|
471
|
+
// Remove the original node from descendants set for processing
|
|
472
|
+
// We'll delete it last (or as part of the iterative process)
|
|
473
|
+
const toDelete = new Set(descendants);
|
|
474
|
+
// Iteratively delete leaf nodes (nodes with no outgoing edges in remaining set)
|
|
475
|
+
while (toDelete.size > 0) {
|
|
476
|
+
let deletedThisRound = false;
|
|
477
|
+
for (const id of toDelete) {
|
|
478
|
+
// Check if this node is a leaf (no outgoing edges to other nodes in toDelete)
|
|
479
|
+
const outgoing = graph.getOutgoingEdges(id);
|
|
480
|
+
const hasDependentsInSet = outgoing.some(depId => toDelete.has(depId));
|
|
481
|
+
if (!hasDependentsInSet) {
|
|
482
|
+
// This is a leaf in our deletion set, safe to delete
|
|
483
|
+
graph.removeNode(id);
|
|
484
|
+
deletedIds.push(id);
|
|
485
|
+
toDelete.delete(id);
|
|
486
|
+
deletedThisRound = true;
|
|
487
|
+
break; // Restart loop after each deletion
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
// Safety check to prevent infinite loop
|
|
491
|
+
if (!deletedThisRound && toDelete.size > 0) {
|
|
492
|
+
// This shouldn't happen in a valid DAG, but handle gracefully
|
|
493
|
+
// Force delete remaining nodes
|
|
494
|
+
for (const id of toDelete) {
|
|
495
|
+
graph.removeNode(id);
|
|
496
|
+
deletedIds.push(id);
|
|
497
|
+
}
|
|
498
|
+
break;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
return deletedIds;
|
|
502
|
+
}
|
|
503
|
+
//# sourceMappingURL=operations.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"operations.js","sourceRoot":"","sources":["../../../src/core/graph/operations.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Graph topological sort algorithms
|
|
3
|
+
*
|
|
4
|
+
* Implements Kahn's algorithm for topological sorting with cycle detection.
|
|
5
|
+
* Time complexity: O(V + E) where V = vertices, E = edges
|
|
6
|
+
*
|
|
7
|
+
* @module core/graph/sort
|
|
8
|
+
*/
|
|
9
|
+
import type { TaskGraphStore } from './index.js';
|
|
10
|
+
import type { TopologicalSortResult } from '../../types/index.js';
|
|
11
|
+
/**
|
|
12
|
+
* Clear the topological sort cache
|
|
13
|
+
* Call this after modifying the graph structure
|
|
14
|
+
*/
|
|
15
|
+
export declare function clearSortCache(): void;
|
|
16
|
+
/**
|
|
17
|
+
* Perform topological sort using Kahn's algorithm
|
|
18
|
+
*
|
|
19
|
+
* Algorithm steps:
|
|
20
|
+
* 1. Calculate in-degree for all nodes
|
|
21
|
+
* 2. Initialize queue with nodes having zero in-degree
|
|
22
|
+
* 3. Process queue: remove node, add to result, reduce neighbors' in-degree
|
|
23
|
+
* 4. Add any neighbors with zero in-degree to queue
|
|
24
|
+
* 5. Detect cycle if result doesn't contain all nodes
|
|
25
|
+
*
|
|
26
|
+
* Time Complexity: O(V + E)
|
|
27
|
+
* Space Complexity: O(V)
|
|
28
|
+
*
|
|
29
|
+
* @param graph - Task graph store
|
|
30
|
+
* @param useCache - Whether to use memoization cache (default: true)
|
|
31
|
+
* @returns Topological sort result with sorted order and cycle detection
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```ts
|
|
35
|
+
* const result = topologicalSort(graph);
|
|
36
|
+
* if (result.hasCycle) {
|
|
37
|
+
* console.error('Cycle detected:', result.cycleNodes);
|
|
38
|
+
* } else {
|
|
39
|
+
* console.log('Execution order:', result.sorted);
|
|
40
|
+
* }
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
export declare function topologicalSort(graph: TaskGraphStore, useCache?: boolean): TopologicalSortResult;
|
|
44
|
+
/**
|
|
45
|
+
* Find the critical path (longest path) in the DAG
|
|
46
|
+
* Uses topological sort and dynamic programming
|
|
47
|
+
*
|
|
48
|
+
* Time Complexity: O(V + E)
|
|
49
|
+
*
|
|
50
|
+
* @param graph - Task graph store
|
|
51
|
+
* @param taskDuration - Duration for each task (default: 1)
|
|
52
|
+
* @returns Object with path array and total duration
|
|
53
|
+
* @throws {CircularDependencyError} If graph contains cycles
|
|
54
|
+
*/
|
|
55
|
+
export declare function findCriticalPath(graph: TaskGraphStore, taskDuration?: number | Map<string, number>): {
|
|
56
|
+
path: string[];
|
|
57
|
+
duration: number;
|
|
58
|
+
};
|
|
59
|
+
/**
|
|
60
|
+
* Validate that a graph is a valid DAG
|
|
61
|
+
* Convenience wrapper around topologicalSort
|
|
62
|
+
*
|
|
63
|
+
* @param graph - Task graph store
|
|
64
|
+
* @returns true if graph is a valid DAG, false otherwise
|
|
65
|
+
*/
|
|
66
|
+
export declare function isValidDAG(graph: TaskGraphStore): boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Get task execution levels (parallelizable stages)
|
|
69
|
+
* Tasks at the same level have no dependencies between them
|
|
70
|
+
*
|
|
71
|
+
* @param graph - Task graph store
|
|
72
|
+
* @returns Array of task ID arrays (each level is a parallelizable stage)
|
|
73
|
+
* @throws {CircularDependencyError} If graph contains cycles
|
|
74
|
+
*/
|
|
75
|
+
export declare function getExecutionLevels(graph: TaskGraphStore): string[][];
|
|
76
|
+
//# sourceMappingURL=sort.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sort.d.ts","sourceRoot":"","sources":["../../../src/core/graph/sort.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAmBlE;;;GAGG;AACH,wBAAgB,cAAc,IAAI,IAAI,CAErC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,cAAc,EAAE,QAAQ,UAAO,GAAG,qBAAqB,CAmB7F;AAiED;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE,cAAc,EACrB,YAAY,GAAE,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAK,GAC7C;IAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAiEtC;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,cAAc,GAAG,OAAO,CAGzD;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,cAAc,GAAG,MAAM,EAAE,EAAE,CA8CpE"}
|