@wrongstack/sdd 1.0.24 → 1.0.26

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
@@ -2822,6 +2822,68 @@ import {
2822
2822
  writeKanbanWorkflowState
2823
2823
  } from "@wrongstack/kanban";
2824
2824
 
2825
+ // src/sdd-task-recovery.ts
2826
+ function computeDeadlockChains(host) {
2827
+ const tracker = host.opts.tracker;
2828
+ const chains = [];
2829
+ for (const node of tracker.getAllNodes()) {
2830
+ if (node.status === "completed" || node.status === "failed") continue;
2831
+ const blockedBy = tracker.getBlockers(node.id).filter((id) => tracker.getNode(id)?.status !== "completed");
2832
+ if (blockedBy.length > 0) chains.push({ blocked: node.id, blockedBy });
2833
+ }
2834
+ return chains;
2835
+ }
2836
+ function recoverFailedBlockers(host) {
2837
+ const tracker = host.opts.tracker;
2838
+ let recovered = false;
2839
+ for (const node of tracker.getAllNodes({ status: ["failed"] })) {
2840
+ const blocksIncomplete = tracker.getDependents(node.id).some((d) => {
2841
+ const s = tracker.getNode(d)?.status;
2842
+ return s !== "completed" && s !== "failed";
2843
+ });
2844
+ if (blocksIncomplete) {
2845
+ host.retryMap.delete(node.id);
2846
+ host.persistRetries(node.id, 0);
2847
+ tracker.updateNodeStatus(node.id, "pending", "deadlock recovery");
2848
+ recovered = true;
2849
+ }
2850
+ }
2851
+ return recovered;
2852
+ }
2853
+ function requeueFailedTasks(host, reason = "retry failed sweep") {
2854
+ const tracker = host.opts.tracker;
2855
+ let n = 0;
2856
+ for (const node of tracker.getAllNodes({ status: ["failed"] })) {
2857
+ if (host.cancelledTasks.has(node.id) || node.metadata?.cancelled) continue;
2858
+ host.retryMap.delete(node.id);
2859
+ host.persistRetries(node.id, 0);
2860
+ tracker.updateNodeStatus(node.id, "pending", reason);
2861
+ host.emit("sdd.task.retrying", {
2862
+ runId: host.runId,
2863
+ taskId: node.id,
2864
+ attempt: 0,
2865
+ maxRetries: host.maxRetries
2866
+ });
2867
+ n++;
2868
+ }
2869
+ return n;
2870
+ }
2871
+ function restoreRetryMap(host) {
2872
+ host.retryMap.clear();
2873
+ for (const node of host.opts.tracker.getAllNodes()) {
2874
+ const r = node.metadata?.retries;
2875
+ if (typeof r === "number" && r > 0) host.retryMap.set(node.id, r);
2876
+ }
2877
+ }
2878
+ function resetOrphans(tracker) {
2879
+ let n = 0;
2880
+ for (const node of tracker.getAllNodes({ status: ["in_progress"] })) {
2881
+ tracker.updateNodeStatus(node.id, "pending", "resume: orphaned in_progress");
2882
+ n++;
2883
+ }
2884
+ return n;
2885
+ }
2886
+
2825
2887
  // src/sdd-parallel-run.ts
2826
2888
  import { randomUUID as randomUUID3 } from "node:crypto";
2827
2889
  import {
@@ -3363,10 +3425,10 @@ var SddParallelRun = class {
3363
3425
  /** Type-safe emit on the optional EventBus (no-op when unwired). */
3364
3426
  emit(event, payload) {
3365
3427
  const sessionId = this.currentSessionId();
3366
- this.events?.emit(
3367
- event,
3368
- { ...payload, sessionId }
3369
- );
3428
+ this.events?.emit(event, {
3429
+ ...payload,
3430
+ sessionId
3431
+ });
3370
3432
  }
3371
3433
  currentSessionId() {
3372
3434
  const value = typeof this.sessionIdSource === "function" ? this.sessionIdSource() : this.sessionIdSource;
@@ -3692,32 +3754,11 @@ var SddParallelRun = class {
3692
3754
  * included since they're the usual deadlock cause once retries are exhausted.
3693
3755
  */
3694
3756
  computeDeadlockChains() {
3695
- const tracker = this.opts.tracker;
3696
- const chains = [];
3697
- for (const node of tracker.getAllNodes()) {
3698
- if (node.status === "completed" || node.status === "failed") continue;
3699
- const blockedBy = tracker.getBlockers(node.id).filter((id) => tracker.getNode(id)?.status !== "completed");
3700
- if (blockedBy.length > 0) chains.push({ blocked: node.id, blockedBy });
3701
- }
3702
- return chains;
3757
+ return computeDeadlockChains(this.sddTaskRecoveryHost());
3703
3758
  }
3704
3759
  /** Requeue failed tasks that block an incomplete dependent. Returns true if any. */
3705
3760
  recoverFailedBlockers() {
3706
- const tracker = this.opts.tracker;
3707
- let recovered = false;
3708
- for (const node of tracker.getAllNodes({ status: ["failed"] })) {
3709
- const blocksIncomplete = tracker.getDependents(node.id).some((d) => {
3710
- const s = tracker.getNode(d)?.status;
3711
- return s !== "completed" && s !== "failed";
3712
- });
3713
- if (blocksIncomplete) {
3714
- this.retryMap.delete(node.id);
3715
- this.persistRetries(node.id, 0);
3716
- tracker.updateNodeStatus(node.id, "pending", "deadlock recovery");
3717
- recovered = true;
3718
- }
3719
- }
3720
- return recovered;
3761
+ return recoverFailedBlockers(this.sddTaskRecoveryHost());
3721
3762
  }
3722
3763
  /**
3723
3764
  * Requeue every terminal-failed task that the user did NOT cancel, giving each
@@ -3725,22 +3766,7 @@ var SddParallelRun = class {
3725
3766
  * the manual "retry all failed" control. Returns the number requeued.
3726
3767
  */
3727
3768
  requeueFailedTasks(reason = "retry failed sweep") {
3728
- const tracker = this.opts.tracker;
3729
- let n = 0;
3730
- for (const node of tracker.getAllNodes({ status: ["failed"] })) {
3731
- if (this.cancelledTasks.has(node.id) || node.metadata?.cancelled) continue;
3732
- this.retryMap.delete(node.id);
3733
- this.persistRetries(node.id, 0);
3734
- tracker.updateNodeStatus(node.id, "pending", reason);
3735
- this.emit("sdd.task.retrying", {
3736
- runId: this.runId,
3737
- taskId: node.id,
3738
- attempt: 0,
3739
- maxRetries: this.maxRetries
3740
- });
3741
- n++;
3742
- }
3743
- return n;
3769
+ return requeueFailedTasks(this.sddTaskRecoveryHost(), reason);
3744
3770
  }
3745
3771
  /**
3746
3772
  * Manually requeue all failed tasks to `pending` (board "Retry all failed").
@@ -3759,11 +3785,7 @@ var SddParallelRun = class {
3759
3785
  }
3760
3786
  /** Restore per-task retry counts persisted in node metadata (resume support). */
3761
3787
  restoreRetryMap() {
3762
- this.retryMap.clear();
3763
- for (const node of this.opts.tracker.getAllNodes()) {
3764
- const r = node.metadata?.retries;
3765
- if (typeof r === "number" && r > 0) this.retryMap.set(node.id, r);
3766
- }
3788
+ restoreRetryMap(this.sddTaskRecoveryHost());
3767
3789
  }
3768
3790
  /**
3769
3791
  * Reset orphaned `in_progress` tasks (no agent runs them after a crash) back
@@ -3771,12 +3793,7 @@ var SddParallelRun = class {
3771
3793
  * from a reloaded graph. Static so callers don't need a run instance.
3772
3794
  */
3773
3795
  static resetOrphans(tracker) {
3774
- let n = 0;
3775
- for (const node of tracker.getAllNodes({ status: ["in_progress"] })) {
3776
- tracker.updateNodeStatus(node.id, "pending", "resume: orphaned in_progress");
3777
- n++;
3778
- }
3779
- return n;
3796
+ return resetOrphans(tracker);
3780
3797
  }
3781
3798
  /** Clean teardown after a stop: reset interrupted tasks + release worktrees. */
3782
3799
  async teardown() {
@@ -4027,6 +4044,24 @@ var SddParallelRun = class {
4027
4044
  deadlocked: isDeadlocked
4028
4045
  };
4029
4046
  }
4047
+ sddTaskRecoveryHost() {
4048
+ const self = this;
4049
+ return {
4050
+ get opts() {
4051
+ return self.opts;
4052
+ },
4053
+ get retryMap() {
4054
+ return self.retryMap;
4055
+ },
4056
+ persistRetries: (...args) => this.persistRetries(...args),
4057
+ get cancelledTasks() {
4058
+ return self.cancelledTasks;
4059
+ },
4060
+ emit: (...args) => this.emit(...args),
4061
+ runId: this.runId,
4062
+ maxRetries: this.maxRetries
4063
+ };
4064
+ }
4030
4065
  };
4031
4066
 
4032
4067
  // src/start-sdd-run.ts
@@ -5325,6 +5360,8 @@ var AutoExecutor = class {
5325
5360
  this.opts.onTaskComplete?.(task, execResult);
5326
5361
  } else if (execResult.retry) {
5327
5362
  retried++;
5363
+ this.opts.tracker.updateNodeStatus(task.id, "failed", execResult.error);
5364
+ failed++;
5328
5365
  } else {
5329
5366
  this.opts.tracker.updateNodeStatus(task.id, "failed", execResult.error);
5330
5367
  failed++;
@@ -1,22 +1,3 @@
1
- /**
2
- * SddParallelRun
3
- *
4
- * Drives a TaskGraph through ParallelEternalEngine's infrastructure
5
- * (DefaultMultiAgentCoordinator + AgentSubagentRunner) but powered by
6
- * SddTaskDecomposer — producing dependency-aware waves instead of
7
- * goal-driven iterations.
8
- *
9
- * One-shot: completes when all tasks are done OR a deadlock is detected.
10
- * Does NOT loop — each run() call is a discrete execution.
11
- *
12
- * Usage:
13
- * ```
14
- * const run = new SddParallelRun({ tracker, graph, agent, projectRoot });
15
- * await run.run({ onWave });
16
- * // or with progress callback:
17
- * await run.run({ onProgress: (p) => console.log(renderProgress(p)) });
18
- * ```
19
- */
20
1
  import type { TaskTracker } from '@wrongstack/core/tasking';
21
2
  import type { TaskNode } from '@wrongstack/core/types';
22
3
  import type { RunResult, SddParallelRunOptions, SddSubtaskSpec, TaskOutcome, WaveResult } from './sdd-parallel-run-types.js';
@@ -270,5 +251,6 @@ export declare class SddParallelRun {
270
251
  /** Persist a task's retry count into node metadata (survives crash → resume). */
271
252
  private persistRetries;
272
253
  private buildProgress;
254
+ private sddTaskRecoveryHost;
273
255
  }
274
256
  //# sourceMappingURL=sdd-parallel-run.d.ts.map
@@ -0,0 +1,20 @@
1
+ import type { TaskTracker } from '@wrongstack/core/tasking';
2
+ import type { SddParallelRunOptions } from './sdd-parallel-run-types.js';
3
+ export interface SddTaskRecoveryHost {
4
+ opts: SddParallelRunOptions;
5
+ retryMap: Map<string, number>;
6
+ persistRetries: (taskId: string, retries: number) => void;
7
+ cancelledTasks: Set<string>;
8
+ emit: <K extends keyof import('@wrongstack/core/kernel').EventMap>(event: K, payload: import('@wrongstack/core/kernel').EventMap[K]) => void;
9
+ runId: string;
10
+ maxRetries: number;
11
+ }
12
+ export declare function computeDeadlockChains(host: SddTaskRecoveryHost): Array<{
13
+ blocked: string;
14
+ blockedBy: string[];
15
+ }>;
16
+ export declare function recoverFailedBlockers(host: SddTaskRecoveryHost): boolean;
17
+ export declare function requeueFailedTasks(host: SddTaskRecoveryHost, reason?: string): number;
18
+ export declare function restoreRetryMap(host: SddTaskRecoveryHost): void;
19
+ export declare function resetOrphans(tracker: TaskTracker): number;
20
+ //# sourceMappingURL=sdd-task-recovery.d.ts.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wrongstack/sdd",
3
- "version": "1.0.24",
3
+ "version": "1.0.26",
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": {
@@ -27,10 +27,10 @@
27
27
  "!dist/**/*.map"
28
28
  ],
29
29
  "dependencies": {
30
- "@wrongstack/core": "1.0.24",
31
- "@wrongstack/kanban": "1.0.24",
32
- "@wrongstack/primitives": "1.0.24",
33
- "@wrongstack/requirement-intake": "1.0.24"
30
+ "@wrongstack/core": "1.0.26",
31
+ "@wrongstack/kanban": "1.0.26",
32
+ "@wrongstack/primitives": "1.0.26",
33
+ "@wrongstack/requirement-intake": "1.0.26"
34
34
  },
35
35
  "devDependencies": {
36
36
  "@types/node": "^26.5.1",