@wrongstack/core 0.283.1 → 0.284.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/dist/{compactor-BxddyCFu.d.ts → compactor-DQLL4HTo.d.ts} +1 -1
  2. package/dist/{config-DkCbA3Bw.d.ts → config-MRqn1V-u.d.ts} +130 -22
  3. package/dist/coordination/index.d.ts +43 -20
  4. package/dist/coordination/index.js +1744 -912
  5. package/dist/coordination/index.js.map +1 -1
  6. package/dist/{default-config-Buho8yge.d.ts → default-config-B79_gJYv.d.ts} +7 -0
  7. package/dist/defaults/index.d.ts +25 -29
  8. package/dist/defaults/index.js +2546 -7396
  9. package/dist/defaults/index.js.map +1 -1
  10. package/dist/{director-state-B_GPa78Q.d.ts → director-state-CMS_bMs4.d.ts} +2 -0
  11. package/dist/{events-KmxSmvho.d.ts → events-Bgnh43r9.d.ts} +27 -10
  12. package/dist/execution/index.d.ts +15 -16
  13. package/dist/execution/index.js +534 -140
  14. package/dist/execution/index.js.map +1 -1
  15. package/dist/execution/prompt-enhancer.d.ts +1 -1
  16. package/dist/extension/index.d.ts +6 -6
  17. package/dist/{global-mailbox-rWVt8YlO.d.ts → global-mailbox-sZwyI6CS.d.ts} +13 -2
  18. package/dist/{goal-store-8owBXJT2.d.ts → goal-store-Datpp-ar.d.ts} +1 -1
  19. package/dist/hq/index.d.ts +6 -6
  20. package/dist/hq/index.js +58 -7
  21. package/dist/hq/index.js.map +1 -1
  22. package/dist/{index-BhCteHAF.d.ts → index--Gm41aBJ.d.ts} +26 -11
  23. package/dist/{index-BEFWNZYs.d.ts → index-CbyuOE5y.d.ts} +5 -1
  24. package/dist/index.d.ts +50 -1015
  25. package/dist/index.js +21028 -27775
  26. package/dist/index.js.map +1 -1
  27. package/dist/infrastructure/index.d.ts +9 -7
  28. package/dist/infrastructure/index.js +7 -2
  29. package/dist/infrastructure/index.js.map +1 -1
  30. package/dist/kernel/index.d.ts +11 -11
  31. package/dist/kernel/index.js.map +1 -1
  32. package/dist/{mcp-servers-BUx9A_AQ.d.ts → mcp-servers-DEV4s6Ks.d.ts} +3 -3
  33. package/dist/models/index.d.ts +4 -4
  34. package/dist/models/index.js +18 -3
  35. package/dist/models/index.js.map +1 -1
  36. package/dist/{agent-subagent-runner-BsuWhB28.d.ts → multi-agent-coordinator-4RCRZKjN.d.ts} +417 -9
  37. package/dist/{null-fleet-bus-D4e9R7Qc.d.ts → null-fleet-bus-CFoNVQEK.d.ts} +53 -12
  38. package/dist/observability/index.d.ts +3 -3
  39. package/dist/{parallel-eternal-engine-iY2uxocj.d.ts → parallel-eternal-engine-HUrtePLd.d.ts} +4 -5
  40. package/dist/{permission-B6YAtNFz.d.ts → permission-ByDKXEcG.d.ts} +1 -1
  41. package/dist/{pipeline-ixSFe5jg.d.ts → pipeline-DGDyIohT.d.ts} +28 -7
  42. package/dist/{provider-model-resolve-DIxKL_T-.d.ts → provider-model-resolve-o2hHLs79.d.ts} +3 -3
  43. package/dist/{provider-runner-oQhDaZmH.d.ts → provider-runner-ChL-kVg6.d.ts} +3 -3
  44. package/dist/{retry-policy-V67lBOJz.d.ts → retry-policy-CQ7KwXJa.d.ts} +2 -2
  45. package/dist/{secret-vault-D7jeyXUQ.d.ts → secret-vault-DxohkUBc.d.ts} +3 -2
  46. package/dist/security/index.d.ts +4 -4
  47. package/dist/security/index.js +232 -2
  48. package/dist/security/index.js.map +1 -1
  49. package/dist/{selector-Bx2HgUkU.d.ts → selector-D8O6B_Rm.d.ts} +1 -1
  50. package/dist/{session-event-bridge-Bkp7ORfc.d.ts → session-event-bridge-D_z_mBwk.d.ts} +3 -3
  51. package/dist/{session-reader-B-Fi9L6I.d.ts → session-reader-7Esxrkpw.d.ts} +1 -1
  52. package/dist/storage/index.d.ts +77 -39
  53. package/dist/storage/index.js +1223 -288
  54. package/dist/storage/index.js.map +1 -1
  55. package/dist/tasking/index.d.ts +124 -0
  56. package/dist/tasking/index.js +492 -0
  57. package/dist/tasking/index.js.map +1 -0
  58. package/dist/{todos-checkpoint-BwCrj4Cb.d.ts → todos-checkpoint-FY7PjmMx.d.ts} +13 -4
  59. package/dist/{tool-LVSLiE1s.d.ts → tool-BNlnIJvo.d.ts} +216 -17
  60. package/dist/{tool-executor-kElSEgO0.d.ts → tool-executor-CckSCM5E.d.ts} +12 -15
  61. package/dist/tools/index.d.ts +2 -2
  62. package/dist/tools/index.js.map +1 -1
  63. package/dist/types/index.d.ts +94 -19
  64. package/dist/types/index.js +76 -12
  65. package/dist/types/index.js.map +1 -1
  66. package/dist/utils/index.d.ts +44 -10
  67. package/dist/utils/index.js +106 -17
  68. package/dist/utils/index.js.map +1 -1
  69. package/dist/{worktree-manager-BjyAW30D.d.ts → worktree-manager-DzwxKE35.d.ts} +1 -1
  70. package/instructions/coordination/subagent-baseline.md +5 -0
  71. package/instructions/llm/autonomy-brain.md +3 -2
  72. package/instructions/system.md +4 -0
  73. package/package.json +10 -3
  74. package/dist/multi-agent-coordinator-CrjeTB9i.d.ts +0 -357
  75. package/dist/sdd/index.d.ts +0 -1993
  76. package/dist/sdd/index.js +0 -9500
  77. package/dist/sdd/index.js.map +0 -1
  78. package/dist/spec-TBi3Jr6T.d.ts +0 -78
@@ -0,0 +1,124 @@
1
+ import { d as TaskGraph, e as TaskNode, k as TaskFilter, l as TaskSort, c as TaskProgress } from '../task-graph-CH3acNQ7.js';
2
+
3
+ interface TaskStore {
4
+ saveGraph(graph: TaskGraph): Promise<void>;
5
+ loadGraph(id: string): Promise<TaskGraph | null>;
6
+ listGraphs(): Promise<{
7
+ id: string;
8
+ title: string;
9
+ updatedAt: number;
10
+ }[]>;
11
+ deleteGraph(id: string): Promise<void>;
12
+ }
13
+ interface TaskTrackerOptions {
14
+ store: TaskStore;
15
+ /**
16
+ * Called when an in-the-background persistence (`saveGraph`) rejects.
17
+ * The synchronous TaskTracker methods (addNode/addEdge/updateNodeStatus)
18
+ * fire-and-forget their writes; without this, a failing store silently
19
+ * loses graph mutations. Defaults to a console.warn.
20
+ */
21
+ onPersistError?: (((err: unknown) => void)) | undefined;
22
+ }
23
+ interface TaskTransition {
24
+ from: TaskNode['status'];
25
+ to: TaskNode['status'];
26
+ timestamp: number;
27
+ reason?: string | undefined;
28
+ }
29
+ /** A change notification emitted to `TaskTracker.subscribe` listeners. */
30
+ interface TaskTrackerChange {
31
+ type: 'node_added' | 'node_updated' | 'status_changed' | 'node_removed';
32
+ nodeId: string;
33
+ /** For `node_removed` this is the node as it was just before deletion. */
34
+ node: TaskNode;
35
+ transition?: TaskTransition | undefined;
36
+ }
37
+ type TaskTrackerListener = (change: TaskTrackerChange) => void;
38
+ declare class TaskTracker {
39
+ private readonly opts;
40
+ private graph;
41
+ private transitions;
42
+ private listeners;
43
+ constructor(opts: TaskTrackerOptions);
44
+ /**
45
+ * Subscribe to live task mutations (add / update / status change). Returns an
46
+ * unsubscribe fn. This is the hook the board projector uses to stream a live
47
+ * snapshot — the tracker was previously fire-and-forget with no observability.
48
+ */
49
+ subscribe(listener: TaskTrackerListener): () => void;
50
+ private notifyChange;
51
+ /**
52
+ * Attach an existing graph (used by PhaseOrchestrator to associate a tracker
53
+ * with a phase's pre-built task graph without re-creating it).
54
+ */
55
+ setGraph(graph: TaskGraph): void;
56
+ createGraph(specId: string, title: string): Promise<TaskGraph>;
57
+ loadGraph(id: string): Promise<TaskGraph | null>;
58
+ addNode(node: Omit<TaskNode, 'id' | 'createdAt' | 'updatedAt'>): TaskNode;
59
+ addEdge(from: string, to: string, type?: TaskGraph['edges'][0]['type']): void;
60
+ /**
61
+ * Declare that `taskId` depends on `depId` (a `depends_on` edge `depId → taskId`),
62
+ * guarding against self-loops, duplicates, missing nodes, and cycles. Returns
63
+ * true if the dependency now holds (added or already present), false if it was
64
+ * rejected (would create a cycle / unknown node). This is the safe entry point
65
+ * for wiring agent-declared `dependsOn` references into the graph.
66
+ */
67
+ addDependency(depId: string, taskId: string): boolean;
68
+ /** True when `taskId` transitively depends on `targetId` (follows depends_on blockers). */
69
+ private dependsOnTransitively;
70
+ /**
71
+ * Merge `patch` into a node's `metadata` (used for per-task model/provider/
72
+ * fallback assignment and the cancel marker). Persists + notifies as a node
73
+ * update. No-op if the node is missing.
74
+ */
75
+ patchMetadata(id: string, patch: Record<string, unknown>): void;
76
+ /**
77
+ * Remove a node and every edge touching it. Intended for deleting a task that
78
+ * has not started yet — callers must gate on status (do not remove a running
79
+ * task). Dependents simply lose this blocker (re-evaluated by `canStart`).
80
+ * Returns true if a node was removed.
81
+ */
82
+ removeNode(id: string): boolean;
83
+ updateNodeStatus(id: string, status: TaskNode['status'], reason?: string): void;
84
+ updateNode(id: string, patch: Partial<Pick<TaskNode, 'title' | 'description' | 'priority' | 'estimateHours' | 'tags' | 'assignee'>>): void;
85
+ getNode(id: string): TaskNode | undefined;
86
+ getAllNodes(filter?: TaskFilter, sort?: TaskSort): TaskNode[];
87
+ getChildren(parentId: string): TaskNode[];
88
+ getDependents(taskId: string): string[];
89
+ getBlockers(taskId: string): string[];
90
+ canStart(taskId: string): boolean;
91
+ getProgress(): TaskProgress;
92
+ getTransitions(_taskId?: string): TaskTransition[];
93
+ private unblockDependents;
94
+ private checkAndBlockIfNeeded;
95
+ /**
96
+ * Fire-and-forget persistence with attached error handler.
97
+ * Synchronous mutators (addNode/addEdge/updateNodeStatus) use this to
98
+ * avoid forcing an async cascade through every caller; if the store
99
+ * is missing or throwing, the error is surfaced via onPersistError.
100
+ */
101
+ private persist;
102
+ }
103
+
104
+ /**
105
+ * In-memory task-graph store used by AutoPhase and tests.
106
+ *
107
+ * The store clones the graph container on read/write so callers cannot replace
108
+ * its Maps/arrays behind the store's back. Task nodes themselves intentionally
109
+ * remain shared: TaskTracker owns node mutation and persists after each change.
110
+ */
111
+ declare class DefaultTaskStore implements TaskStore {
112
+ private readonly graphs;
113
+ saveGraph(graph: TaskGraph): Promise<void>;
114
+ loadGraph(id: string): Promise<TaskGraph | null>;
115
+ listGraphs(): Promise<Array<{
116
+ id: string;
117
+ title: string;
118
+ updatedAt: number;
119
+ }>>;
120
+ deleteGraph(id: string): Promise<void>;
121
+ private cloneGraph;
122
+ }
123
+
124
+ export { DefaultTaskStore, type TaskStore, TaskTracker, type TaskTrackerChange, type TaskTrackerListener, type TaskTrackerOptions, type TaskTransition };
@@ -0,0 +1,492 @@
1
+ // src/tasking/task-store.ts
2
+ var DefaultTaskStore = class {
3
+ graphs = /* @__PURE__ */ new Map();
4
+ async saveGraph(graph) {
5
+ this.graphs.set(graph.id, this.cloneGraph(graph));
6
+ }
7
+ async loadGraph(id) {
8
+ const graph = this.graphs.get(id);
9
+ return graph ? this.cloneGraph(graph) : null;
10
+ }
11
+ async listGraphs() {
12
+ return Array.from(this.graphs.values()).map((graph) => ({
13
+ id: graph.id,
14
+ title: graph.title,
15
+ updatedAt: graph.updatedAt
16
+ }));
17
+ }
18
+ async deleteGraph(id) {
19
+ this.graphs.delete(id);
20
+ }
21
+ cloneGraph(graph) {
22
+ return {
23
+ ...graph,
24
+ nodes: new Map(graph.nodes),
25
+ edges: [...graph.edges],
26
+ rootNodes: [...graph.rootNodes]
27
+ };
28
+ }
29
+ };
30
+
31
+ // src/types/task-graph.ts
32
+ function computeTaskProgress(graph) {
33
+ let completed = 0;
34
+ let pending = 0;
35
+ let inProgress = 0;
36
+ let blocked = 0;
37
+ let failed = 0;
38
+ let review = 0;
39
+ let estimatedHours = 0;
40
+ let actualHours = 0;
41
+ for (const n of graph.nodes.values()) {
42
+ switch (n.status) {
43
+ case "completed":
44
+ completed++;
45
+ break;
46
+ case "pending":
47
+ pending++;
48
+ break;
49
+ case "in_progress":
50
+ inProgress++;
51
+ break;
52
+ case "blocked":
53
+ blocked++;
54
+ break;
55
+ case "failed":
56
+ failed++;
57
+ break;
58
+ case "review":
59
+ review++;
60
+ break;
61
+ }
62
+ estimatedHours += n.estimateHours ?? 0;
63
+ actualHours += n.actualHours ?? 0;
64
+ }
65
+ const total = graph.nodes.size;
66
+ return {
67
+ total,
68
+ pending,
69
+ inProgress,
70
+ blocked,
71
+ failed,
72
+ review,
73
+ completed,
74
+ percentComplete: total > 0 ? Math.round(completed / total * 100) : 0,
75
+ estimatedHours,
76
+ actualHours
77
+ };
78
+ }
79
+
80
+ // src/utils/error.ts
81
+ function toErrorMessage(err) {
82
+ return err instanceof Error ? err.message : String(err);
83
+ }
84
+
85
+ // src/types/errors.ts
86
+ var ERROR_CODES = {
87
+ SDD_PARSE_FAILED: "SDD_PARSE_FAILED",
88
+ SDD_INVALID_STATE: "SDD_INVALID_STATE",
89
+ SDD_NOT_READY: "SDD_NOT_READY"};
90
+ var WrongStackError = class extends Error {
91
+ code;
92
+ subsystem;
93
+ severity;
94
+ recoverable;
95
+ context;
96
+ constructor(opts) {
97
+ super(opts.message, { cause: opts.cause });
98
+ this.name = "WrongStackError";
99
+ this.code = opts.code;
100
+ this.subsystem = opts.subsystem;
101
+ this.severity = opts.severity ?? "error";
102
+ this.recoverable = opts.recoverable ?? false;
103
+ this.context = opts.context;
104
+ }
105
+ /**
106
+ * Render a one-line user-facing description.
107
+ * Subclasses should override for domain-specific formatting.
108
+ */
109
+ describe() {
110
+ const ctx = this.context ? ` ${formatContext(this.context)}` : "";
111
+ return `${this.code}: ${this.message}${ctx}`;
112
+ }
113
+ };
114
+ function formatContext(ctx) {
115
+ const parts = Object.entries(ctx).filter(([, v]) => v !== void 0).slice(0, 3).map(([k, v]) => `${k}=${String(v)}`);
116
+ return parts.length > 0 ? `[${parts.join(" ")}]` : "";
117
+ }
118
+ var SddError = class extends WrongStackError {
119
+ constructor(opts) {
120
+ super({
121
+ message: opts.message,
122
+ code: opts.code,
123
+ subsystem: "sdd",
124
+ severity: opts.code === ERROR_CODES.SDD_PARSE_FAILED ? "warning" : "error",
125
+ recoverable: opts.code === ERROR_CODES.SDD_NOT_READY,
126
+ context: opts.context,
127
+ cause: opts.cause
128
+ });
129
+ this.name = "SddError";
130
+ }
131
+ };
132
+
133
+ // src/tasking/task-tracker.ts
134
+ var TaskTracker = class {
135
+ constructor(opts) {
136
+ this.opts = opts;
137
+ }
138
+ opts;
139
+ graph = null;
140
+ transitions = [];
141
+ listeners = [];
142
+ /**
143
+ * Subscribe to live task mutations (add / update / status change). Returns an
144
+ * unsubscribe fn. This is the hook the board projector uses to stream a live
145
+ * snapshot — the tracker was previously fire-and-forget with no observability.
146
+ */
147
+ subscribe(listener) {
148
+ this.listeners.push(listener);
149
+ return () => {
150
+ const i = this.listeners.indexOf(listener);
151
+ if (i >= 0) this.listeners.splice(i, 1);
152
+ };
153
+ }
154
+ notifyChange(change) {
155
+ for (const l of this.listeners) {
156
+ try {
157
+ l(change);
158
+ } catch {
159
+ }
160
+ }
161
+ }
162
+ /**
163
+ * Attach an existing graph (used by PhaseOrchestrator to associate a tracker
164
+ * with a phase's pre-built task graph without re-creating it).
165
+ */
166
+ setGraph(graph) {
167
+ this.graph = graph;
168
+ }
169
+ async createGraph(specId, title) {
170
+ this.graph = {
171
+ id: crypto.randomUUID(),
172
+ specId,
173
+ title,
174
+ nodes: /* @__PURE__ */ new Map(),
175
+ edges: [],
176
+ rootNodes: [],
177
+ createdAt: Date.now(),
178
+ updatedAt: Date.now()
179
+ };
180
+ await this.opts.store.saveGraph(this.graph);
181
+ return this.graph;
182
+ }
183
+ async loadGraph(id) {
184
+ this.graph = await this.opts.store.loadGraph(id);
185
+ return this.graph;
186
+ }
187
+ addNode(node) {
188
+ if (!this.graph) throw new SddError({
189
+ message: "No graph loaded",
190
+ code: ERROR_CODES.SDD_INVALID_STATE
191
+ });
192
+ const now = Date.now();
193
+ const newNode = {
194
+ ...node,
195
+ id: crypto.randomUUID(),
196
+ status: node.status ?? "pending",
197
+ createdAt: now,
198
+ updatedAt: now
199
+ };
200
+ this.graph.nodes.set(newNode.id, newNode);
201
+ if (!node.parentId) {
202
+ this.graph.rootNodes.push(newNode.id);
203
+ }
204
+ this.graph.updatedAt = now;
205
+ this.persist();
206
+ this.notifyChange({ type: "node_added", nodeId: newNode.id, node: newNode });
207
+ return newNode;
208
+ }
209
+ addEdge(from, to, type = "depends_on") {
210
+ if (!this.graph) throw new SddError({
211
+ message: "No graph loaded",
212
+ code: ERROR_CODES.SDD_INVALID_STATE
213
+ });
214
+ this.graph.edges.push({
215
+ id: crypto.randomUUID(),
216
+ from,
217
+ to,
218
+ type
219
+ });
220
+ this.graph.updatedAt = Date.now();
221
+ this.persist();
222
+ }
223
+ /**
224
+ * Declare that `taskId` depends on `depId` (a `depends_on` edge `depId → taskId`),
225
+ * guarding against self-loops, duplicates, missing nodes, and cycles. Returns
226
+ * true if the dependency now holds (added or already present), false if it was
227
+ * rejected (would create a cycle / unknown node). This is the safe entry point
228
+ * for wiring agent-declared `dependsOn` references into the graph.
229
+ */
230
+ addDependency(depId, taskId) {
231
+ if (!this.graph) return false;
232
+ if (depId === taskId) return false;
233
+ if (!this.graph.nodes.has(depId) || !this.graph.nodes.has(taskId)) return false;
234
+ if (this.getBlockers(taskId).includes(depId)) return true;
235
+ if (this.dependsOnTransitively(depId, taskId, /* @__PURE__ */ new Set())) return false;
236
+ this.addEdge(depId, taskId, "depends_on");
237
+ return true;
238
+ }
239
+ /** True when `taskId` transitively depends on `targetId` (follows depends_on blockers). */
240
+ dependsOnTransitively(taskId, targetId, seen) {
241
+ if (taskId === targetId) return true;
242
+ if (seen.has(taskId)) return false;
243
+ seen.add(taskId);
244
+ for (const blocker of this.getBlockers(taskId)) {
245
+ if (this.dependsOnTransitively(blocker, targetId, seen)) return true;
246
+ }
247
+ return false;
248
+ }
249
+ /**
250
+ * Merge `patch` into a node's `metadata` (used for per-task model/provider/
251
+ * fallback assignment and the cancel marker). Persists + notifies as a node
252
+ * update. No-op if the node is missing.
253
+ */
254
+ patchMetadata(id, patch) {
255
+ if (!this.graph) return;
256
+ const node = this.graph.nodes.get(id);
257
+ if (!node) return;
258
+ node.metadata = { ...node.metadata, ...patch };
259
+ node.updatedAt = Date.now();
260
+ this.graph.updatedAt = node.updatedAt;
261
+ this.persist();
262
+ this.notifyChange({ type: "node_updated", nodeId: id, node });
263
+ }
264
+ /**
265
+ * Remove a node and every edge touching it. Intended for deleting a task that
266
+ * has not started yet — callers must gate on status (do not remove a running
267
+ * task). Dependents simply lose this blocker (re-evaluated by `canStart`).
268
+ * Returns true if a node was removed.
269
+ */
270
+ removeNode(id) {
271
+ if (!this.graph) return false;
272
+ const node = this.graph.nodes.get(id);
273
+ if (!node) return false;
274
+ this.graph.nodes.delete(id);
275
+ this.graph.edges = this.graph.edges.filter((e) => e.from !== id && e.to !== id);
276
+ this.graph.rootNodes = this.graph.rootNodes.filter((r) => r !== id);
277
+ for (const n of this.graph.nodes.values()) {
278
+ if (n.children?.includes(id)) n.children = n.children.filter((c) => c !== id);
279
+ }
280
+ this.graph.updatedAt = Date.now();
281
+ this.persist();
282
+ this.notifyChange({ type: "node_removed", nodeId: id, node });
283
+ return true;
284
+ }
285
+ updateNodeStatus(id, status, reason) {
286
+ if (!this.graph) throw new SddError({
287
+ message: "No graph loaded",
288
+ code: ERROR_CODES.SDD_INVALID_STATE
289
+ });
290
+ const node = this.graph.nodes.get(id);
291
+ if (!node) throw new SddError({
292
+ message: `Node ${id} not found`,
293
+ code: ERROR_CODES.SDD_NOT_READY,
294
+ context: { nodeId: id }
295
+ });
296
+ const from = node.status;
297
+ const now = Date.now();
298
+ node.status = status;
299
+ node.updatedAt = now;
300
+ if (status === "completed") {
301
+ node.completedAt = now;
302
+ node.startedAt = node.startedAt ?? now;
303
+ }
304
+ if (status === "in_progress") {
305
+ node.startedAt = now;
306
+ }
307
+ this.transitions.push({ from, to: status, timestamp: now, reason });
308
+ if (status === "completed") {
309
+ this.unblockDependents(id);
310
+ }
311
+ if (status === "in_progress") {
312
+ this.checkAndBlockIfNeeded(id);
313
+ }
314
+ this.graph.updatedAt = now;
315
+ this.persist();
316
+ this.notifyChange({
317
+ type: "status_changed",
318
+ nodeId: id,
319
+ node,
320
+ transition: { from, to: status, timestamp: now, reason }
321
+ });
322
+ }
323
+ updateNode(id, patch) {
324
+ if (!this.graph) throw new SddError({
325
+ message: "No graph loaded",
326
+ code: ERROR_CODES.SDD_INVALID_STATE
327
+ });
328
+ const node = this.graph.nodes.get(id);
329
+ if (!node) throw new SddError({
330
+ message: `Node ${id} not found`,
331
+ code: ERROR_CODES.SDD_NOT_READY,
332
+ context: { nodeId: id }
333
+ });
334
+ if (patch.title !== void 0) node.title = patch.title;
335
+ if (patch.description !== void 0) node.description = patch.description;
336
+ if (patch.priority !== void 0) node.priority = patch.priority;
337
+ if (patch.estimateHours !== void 0) node.estimateHours = patch.estimateHours;
338
+ if (patch.tags !== void 0) node.tags = patch.tags;
339
+ if (patch.assignee !== void 0) node.assignee = patch.assignee;
340
+ node.updatedAt = Date.now();
341
+ this.graph.updatedAt = node.updatedAt;
342
+ this.persist();
343
+ this.notifyChange({ type: "node_updated", nodeId: id, node });
344
+ }
345
+ getNode(id) {
346
+ return this.graph?.nodes.get(id);
347
+ }
348
+ getAllNodes(filter, sort) {
349
+ if (!this.graph) return [];
350
+ let nodes = Array.from(this.graph.nodes.values());
351
+ if (filter) {
352
+ nodes = nodes.filter((n) => {
353
+ if (filter.status?.length && !filter.status.includes(n.status)) return false;
354
+ if (filter.priority?.length && !filter.priority.includes(n.priority)) return false;
355
+ if (filter.type?.length && !filter.type.includes(n.type)) return false;
356
+ if (filter.assignee?.length && n.assignee && !filter.assignee.includes(n.assignee))
357
+ return false;
358
+ if (filter.tags?.length && n.tags && !n.tags.some((t) => filter.tags?.includes(t)))
359
+ return false;
360
+ if (filter.specRequirementId && n.specRequirementId !== filter.specRequirementId)
361
+ return false;
362
+ return true;
363
+ });
364
+ }
365
+ if (sort) {
366
+ nodes.sort((a, b) => {
367
+ const cmp = compareByField(a, b, sort.field);
368
+ return sort.direction === "asc" ? cmp : -cmp;
369
+ });
370
+ }
371
+ return nodes;
372
+ }
373
+ getChildren(parentId) {
374
+ if (!this.graph) return [];
375
+ return Array.from(this.graph.nodes.values()).filter((n) => n.parentId === parentId);
376
+ }
377
+ getDependents(taskId) {
378
+ if (!this.graph) return [];
379
+ return this.graph.edges.filter((e) => e.from === taskId && e.type === "depends_on").map((e) => e.to);
380
+ }
381
+ getBlockers(taskId) {
382
+ if (!this.graph) return [];
383
+ return this.graph.edges.filter((e) => e.to === taskId && e.type === "depends_on").map((e) => e.from);
384
+ }
385
+ canStart(taskId) {
386
+ const blockers = this.getBlockers(taskId);
387
+ return blockers.every((id) => {
388
+ const node = this.graph?.nodes.get(id);
389
+ return node?.status === "completed" || node?.status === "failed";
390
+ });
391
+ }
392
+ getProgress() {
393
+ if (!this.graph) {
394
+ return {
395
+ total: 0,
396
+ pending: 0,
397
+ inProgress: 0,
398
+ blocked: 0,
399
+ failed: 0,
400
+ review: 0,
401
+ completed: 0,
402
+ percentComplete: 0,
403
+ estimatedHours: 0,
404
+ actualHours: 0
405
+ };
406
+ }
407
+ return computeTaskProgress(this.graph);
408
+ }
409
+ getTransitions(_taskId) {
410
+ return [...this.transitions];
411
+ }
412
+ unblockDependents(completedId) {
413
+ if (!this.graph) return;
414
+ const dependents = this.getDependents(completedId);
415
+ for (const depId of dependents) {
416
+ const dep = this.graph.nodes.get(depId);
417
+ if (dep?.status === "blocked") {
418
+ const remainingBlockers = this.getBlockers(depId);
419
+ const allUnblocked = remainingBlockers.every((id) => {
420
+ const blocker = this.graph?.nodes.get(id);
421
+ return blocker?.status === "completed" || blocker?.status === "failed";
422
+ });
423
+ if (allUnblocked) {
424
+ dep.status = "pending";
425
+ dep.updatedAt = Date.now();
426
+ }
427
+ }
428
+ }
429
+ }
430
+ checkAndBlockIfNeeded(taskId) {
431
+ if (!this.graph) return;
432
+ const blockers = this.getBlockers(taskId);
433
+ const someBlocked = blockers.some((id) => {
434
+ const blocker = this.graph?.nodes.get(id);
435
+ return blocker?.status !== "completed" && blocker?.status !== "failed";
436
+ });
437
+ if (someBlocked) {
438
+ const node = this.graph.nodes.get(taskId);
439
+ if (node) {
440
+ node.status = "blocked";
441
+ node.updatedAt = Date.now();
442
+ }
443
+ }
444
+ }
445
+ /**
446
+ * Fire-and-forget persistence with attached error handler.
447
+ * Synchronous mutators (addNode/addEdge/updateNodeStatus) use this to
448
+ * avoid forcing an async cascade through every caller; if the store
449
+ * is missing or throwing, the error is surfaced via onPersistError.
450
+ */
451
+ persist() {
452
+ if (!this.graph) return;
453
+ this.opts.store.saveGraph(this.graph).catch((err) => {
454
+ this.opts.onPersistError ? this.opts.onPersistError(err) : console.warn(JSON.stringify({
455
+ level: "warn",
456
+ event: "task_tracker.save_graph_failed",
457
+ message: toErrorMessage(err),
458
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
459
+ }));
460
+ });
461
+ }
462
+ };
463
+ var PRIORITY_RANK = {
464
+ critical: 0,
465
+ high: 1,
466
+ medium: 2,
467
+ low: 3
468
+ };
469
+ var STATUS_RANK = {
470
+ in_progress: 0,
471
+ pending: 1,
472
+ review: 2,
473
+ blocked: 3,
474
+ failed: 4,
475
+ completed: 5
476
+ };
477
+ function compareByField(a, b, field) {
478
+ switch (field) {
479
+ case "priority":
480
+ return PRIORITY_RANK[a.priority] - PRIORITY_RANK[b.priority];
481
+ case "status":
482
+ return STATUS_RANK[a.status] - STATUS_RANK[b.status];
483
+ case "createdAt":
484
+ return a.createdAt - b.createdAt;
485
+ case "updatedAt":
486
+ return a.updatedAt - b.updatedAt;
487
+ }
488
+ }
489
+
490
+ export { DefaultTaskStore, TaskTracker };
491
+ //# sourceMappingURL=index.js.map
492
+ //# sourceMappingURL=index.js.map