@wrongstack/sdd 1.1.4 → 1.1.5

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/dist/index.js CHANGED
@@ -1040,6 +1040,10 @@ var TaskGraphStore = class {
1040
1040
  saveGraph(graph) {
1041
1041
  return this.save(graph);
1042
1042
  }
1043
+ /** Wait until every queued graph/index mutation has settled. */
1044
+ flush() {
1045
+ return this.writeChain;
1046
+ }
1043
1047
  loadGraph(id) {
1044
1048
  return this.load(id);
1045
1049
  }
@@ -5155,39 +5159,39 @@ function renderTaskGraph(graph, opts) {
5155
5159
  const progress = computeTaskProgress2(graph);
5156
5160
  lines.push(renderProgress(progress));
5157
5161
  lines.push("");
5158
- const childrenMap = /* @__PURE__ */ new Map();
5162
+ const blockersMap = /* @__PURE__ */ new Map();
5159
5163
  const dependentsMap = /* @__PURE__ */ new Map();
5160
5164
  for (const edge of graph.edges) {
5161
5165
  if (edge.type === "depends_on") {
5162
- const deps = childrenMap.get(edge.from) ?? [];
5163
- deps.push(edge.to);
5164
- childrenMap.set(edge.from, deps);
5165
- if (graph.nodes.has(edge.from)) {
5166
- const dependents = dependentsMap.get(edge.to) ?? [];
5167
- dependents.push(edge.from);
5168
- dependentsMap.set(edge.to, dependents);
5166
+ const blockers = blockersMap.get(edge.to) ?? [];
5167
+ blockers.push(edge.from);
5168
+ blockersMap.set(edge.to, blockers);
5169
+ if (graph.nodes.has(edge.to)) {
5170
+ const dependents = dependentsMap.get(edge.from) ?? [];
5171
+ dependents.push(edge.to);
5172
+ dependentsMap.set(edge.from, dependents);
5169
5173
  }
5170
5174
  }
5171
5175
  }
5172
5176
  const rendered = /* @__PURE__ */ new Set();
5173
5177
  const rootNodes = graph.rootNodes.filter((id) => graph.nodes.has(id));
5174
5178
  const startNodes = rootNodes.length > 0 ? rootNodes : Array.from(graph.nodes.keys()).filter((id) => {
5175
- const deps = childrenMap.get(id);
5176
- return !deps || deps.length === 0;
5179
+ const blockers = blockersMap.get(id);
5180
+ return !blockers || blockers.length === 0;
5177
5181
  });
5178
5182
  for (const rootId of startNodes) {
5179
- renderNode(graph, rootId, lines, rendered, childrenMap, dependentsMap, compact, "");
5183
+ renderNode(graph, rootId, lines, rendered, blockersMap, dependentsMap, compact, "");
5180
5184
  }
5181
5185
  for (const [id] of graph.nodes) {
5182
5186
  if (!rendered.has(id)) {
5183
- renderNode(graph, id, lines, rendered, childrenMap, dependentsMap, compact, "");
5187
+ renderNode(graph, id, lines, rendered, blockersMap, dependentsMap, compact, "");
5184
5188
  }
5185
5189
  }
5186
5190
  lines.push("");
5187
5191
  lines.push("Legend: \u25CF done \u25D0 in-progress \u25CB pending \u2297 blocked \u2717 failed \u25D2 review");
5188
5192
  return lines.join("\n");
5189
5193
  }
5190
- function renderNode(graph, nodeId, lines, rendered, childrenMap, dependentsMap, compact, prefix) {
5194
+ function renderNode(graph, nodeId, lines, rendered, blockersMap, dependentsMap, compact, prefix) {
5191
5195
  if (rendered.has(nodeId)) return;
5192
5196
  rendered.add(nodeId);
5193
5197
  const node = expectDefined5(graph.nodes.get(nodeId));
@@ -5195,7 +5199,7 @@ function renderNode(graph, nodeId, lines, rendered, childrenMap, dependentsMap,
5195
5199
  const prioIcon = PRIORITY_ICON[node.priority];
5196
5200
  const typeIcon = TYPE_ICON[node.type];
5197
5201
  const title = compact ? truncate(node.title, 40) : node.title;
5198
- const blockedBy = childrenMap.get(nodeId) ?? [];
5202
+ const blockedBy = blockersMap.get(nodeId) ?? [];
5199
5203
  const depsStr = blockedBy.length > 0 ? ` \u2190 [${blockedBy.map((d) => graph.nodes.get(d)?.title?.slice(0, 12) ?? "?").join(", ")}]` : "";
5200
5204
  lines.push(`${prefix}${icon} ${typeIcon} ${prioIcon} ${title}${depsStr}`);
5201
5205
  if (!compact && node.description) {
@@ -5206,7 +5210,7 @@ function renderNode(graph, nodeId, lines, rendered, childrenMap, dependentsMap,
5206
5210
  }
5207
5211
  const dependents = dependentsMap.get(nodeId) ?? [];
5208
5212
  for (const depId of dependents) {
5209
- renderNode(graph, depId, lines, rendered, childrenMap, dependentsMap, compact, prefix + " ");
5213
+ renderNode(graph, depId, lines, rendered, blockersMap, dependentsMap, compact, prefix + " ");
5210
5214
  }
5211
5215
  }
5212
5216
  function renderProgress(progress) {
@@ -5279,19 +5283,18 @@ function renderSpecAnalysis(spec, analysis) {
5279
5283
  }
5280
5284
 
5281
5285
  // src/critical-path.ts
5282
- import { topologicalSort } from "@wrongstack/core/tasking";
5283
5286
  import { expectDefined as expectDefined6 } from "@wrongstack/core/utils";
5284
5287
  function analyzeCriticalPath(graph) {
5285
5288
  const nodes = Array.from(graph.nodes.values());
5286
- const topoOrder = topologicalSort(graph);
5289
+ const topoOrder = dependencyFirstOrder(graph);
5287
5290
  const blockedByMap = /* @__PURE__ */ new Map();
5288
5291
  const blocksMap = /* @__PURE__ */ new Map();
5289
5292
  for (const edge of graph.edges) {
5290
5293
  if (edge.type === "depends_on") {
5291
- if (!blockedByMap.has(edge.from)) blockedByMap.set(edge.from, /* @__PURE__ */ new Set());
5292
- blockedByMap.get(edge.from)?.add(edge.to);
5293
- if (!blocksMap.has(edge.to)) blocksMap.set(edge.to, /* @__PURE__ */ new Set());
5294
- blocksMap.get(edge.to)?.add(edge.from);
5294
+ if (!blockedByMap.has(edge.to)) blockedByMap.set(edge.to, /* @__PURE__ */ new Set());
5295
+ blockedByMap.get(edge.to)?.add(edge.from);
5296
+ if (!blocksMap.has(edge.from)) blocksMap.set(edge.from, /* @__PURE__ */ new Set());
5297
+ blocksMap.get(edge.from)?.add(edge.to);
5295
5298
  }
5296
5299
  }
5297
5300
  const readyTasks = [];
@@ -5336,7 +5339,7 @@ function analyzeCriticalPath(graph) {
5336
5339
  }
5337
5340
  }
5338
5341
  bottlenecks.sort((a, b) => b.severity - a.severity);
5339
- const criticalPath = computeCriticalPath(graph, topoOrder, blockedByMap);
5342
+ const criticalPath = computeCriticalPath(graph, blocksMap);
5340
5343
  const totalHours = criticalPath.reduce((sum, id) => {
5341
5344
  return sum + taskEstimateHours(graph, id, 0);
5342
5345
  }, 0);
@@ -5355,6 +5358,35 @@ function analyzeCriticalPath(graph) {
5355
5358
  blockedTasks
5356
5359
  };
5357
5360
  }
5361
+ function dependencyFirstOrder(graph) {
5362
+ const ids = Array.from(graph.nodes.keys());
5363
+ const indegree = new Map(ids.map((id) => [id, 0]));
5364
+ const outgoing = /* @__PURE__ */ new Map();
5365
+ for (const edge of graph.edges) {
5366
+ if (edge.type !== "depends_on") continue;
5367
+ if (!graph.nodes.has(edge.from) || !graph.nodes.has(edge.to)) continue;
5368
+ indegree.set(edge.to, indegree.get(edge.to) + 1);
5369
+ const dependents = outgoing.get(edge.from);
5370
+ if (dependents) dependents.push(edge.to);
5371
+ else outgoing.set(edge.from, [edge.to]);
5372
+ }
5373
+ const ready = ids.filter((id) => indegree.get(id) === 0);
5374
+ const order = [];
5375
+ for (let cursor = 0; cursor < ready.length; cursor++) {
5376
+ const id = ready[cursor];
5377
+ order.push(id);
5378
+ for (const dependent of outgoing.get(id) ?? []) {
5379
+ const next = indegree.get(dependent) - 1;
5380
+ indegree.set(dependent, next);
5381
+ if (next === 0) ready.push(dependent);
5382
+ }
5383
+ }
5384
+ const ordered = new Set(order);
5385
+ for (const id of ids) {
5386
+ if (!ordered.has(id)) order.push(id);
5387
+ }
5388
+ return order;
5389
+ }
5358
5390
  function getTransitiveBlocked(_graph, taskId, blocksMap) {
5359
5391
  const visited = /* @__PURE__ */ new Set();
5360
5392
  const queue = [taskId];
@@ -5371,7 +5403,7 @@ function getTransitiveBlocked(_graph, taskId, blocksMap) {
5371
5403
  }
5372
5404
  return visited;
5373
5405
  }
5374
- function computeCriticalPath(graph, _topoOrder, blockedByMap) {
5406
+ function computeCriticalPath(graph, blocksMap) {
5375
5407
  const allIds = Array.from(graph.nodes.keys());
5376
5408
  if (allIds.length === 0) return [];
5377
5409
  const dist = /* @__PURE__ */ new Map();
@@ -5380,13 +5412,6 @@ function computeCriticalPath(graph, _topoOrder, blockedByMap) {
5380
5412
  dist.set(id, taskEstimateHours(graph, id, 1));
5381
5413
  prev.set(id, null);
5382
5414
  }
5383
- const blocksMap = /* @__PURE__ */ new Map();
5384
- for (const [taskId, blockers] of blockedByMap) {
5385
- for (const blockerId of blockers) {
5386
- if (!blocksMap.has(blockerId)) blocksMap.set(blockerId, /* @__PURE__ */ new Set());
5387
- blocksMap.get(blockerId)?.add(taskId);
5388
- }
5389
- }
5390
5415
  const n = allIds.length;
5391
5416
  for (let i = 0; i < n - 1; i++) {
5392
5417
  let changed = false;
@@ -5662,14 +5687,6 @@ var SpecVersioning = class {
5662
5687
  };
5663
5688
 
5664
5689
  // src/auto-executor.ts
5665
- function asDependentFirstGraph(graph) {
5666
- return {
5667
- ...graph,
5668
- edges: graph.edges.map(
5669
- (edge) => edge.type === "depends_on" ? { ...edge, from: edge.to, to: edge.from } : edge
5670
- )
5671
- };
5672
- }
5673
5690
  var AutoExecutor = class {
5674
5691
  opts;
5675
5692
  stopped = false;
@@ -5686,7 +5703,7 @@ var AutoExecutor = class {
5686
5703
  this.stopped = false;
5687
5704
  this.retryMap.clear();
5688
5705
  const startTime = Date.now();
5689
- const critical = analyzeCriticalPath(asDependentFirstGraph(graph));
5706
+ const critical = analyzeCriticalPath(graph);
5690
5707
  let completed = 0;
5691
5708
  let failed = 0;
5692
5709
  const skipped = 0;
@@ -29,6 +29,8 @@ export declare class TaskGraphStore implements TaskStore {
29
29
  delete(id: string): Promise<boolean>;
30
30
  exists(id: string): Promise<boolean>;
31
31
  saveGraph(graph: TaskGraph): Promise<void>;
32
+ /** Wait until every queued graph/index mutation has settled. */
33
+ flush(): Promise<void>;
32
34
  loadGraph(id: string): Promise<TaskGraph | null>;
33
35
  listGraphs(): Promise<Array<{
34
36
  id: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrongstack/sdd",
3
- "version": "1.1.4",
3
+ "version": "1.1.5",
4
4
  "license": "MIT",
5
5
  "description": "WrongStack Spec-Driven Development engine — standalone package extracted from @wrongstack/core. Task graph generation, tracking, execution, lifecycle management, and AI-driven spec building for SDD workflows.",
6
6
  "repository": {