@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 +91 -54
- package/dist/sdd-parallel-run.d.ts +1 -19
- package/dist/sdd-task-recovery.d.ts +20 -0
- package/package.json +5 -5
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
|
-
|
|
3368
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
31
|
-
"@wrongstack/kanban": "1.0.
|
|
32
|
-
"@wrongstack/primitives": "1.0.
|
|
33
|
-
"@wrongstack/requirement-intake": "1.0.
|
|
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",
|