@wrongstack/sdd 0.295.1 → 0.296.3
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/graph-split.d.ts.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +444 -202
- package/dist/index.js.map +4 -4
- package/dist/project-context.d.ts +6 -0
- package/dist/project-context.d.ts.map +1 -0
- package/dist/sdd-interview-driver.d.ts +15 -0
- package/dist/sdd-interview-driver.d.ts.map +1 -1
- package/dist/sdd-lifecycle.d.ts.map +1 -1
- package/dist/sdd-parallel-run-types.d.ts +200 -0
- package/dist/sdd-parallel-run-types.d.ts.map +1 -0
- package/dist/sdd-parallel-run.d.ts +22 -198
- package/dist/sdd-parallel-run.d.ts.map +1 -1
- package/dist/sdd-task-execution.d.ts +28 -0
- package/dist/sdd-task-execution.d.ts.map +1 -0
- package/dist/spec-builder.d.ts +33 -2
- package/dist/spec-builder.d.ts.map +1 -1
- package/dist/verify-task.d.ts +21 -4
- package/dist/verify-task.d.ts.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -1836,20 +1836,21 @@ var AISpecBuilder = class {
|
|
|
1836
1836
|
async saveSession() {
|
|
1837
1837
|
if (!this.sessionPath) return;
|
|
1838
1838
|
try {
|
|
1839
|
-
const
|
|
1840
|
-
const
|
|
1839
|
+
const fsp6 = await import("node:fs/promises");
|
|
1840
|
+
const path6 = await import("node:path");
|
|
1841
1841
|
const { atomicWrite: atomicWrite4 } = await import("@wrongstack/core/utils");
|
|
1842
|
-
await
|
|
1842
|
+
await fsp6.mkdir(path6.dirname(this.sessionPath), { recursive: true });
|
|
1843
1843
|
await atomicWrite4(this.sessionPath, JSON.stringify(this.session, null, 2));
|
|
1844
|
-
} catch {
|
|
1844
|
+
} catch (error) {
|
|
1845
|
+
console.error("[sdd] Failed to persist session", error);
|
|
1845
1846
|
}
|
|
1846
1847
|
}
|
|
1847
1848
|
/** Load session state from disk. Returns true if a session was loaded. */
|
|
1848
1849
|
async loadSession() {
|
|
1849
1850
|
if (!this.sessionPath) return false;
|
|
1850
1851
|
try {
|
|
1851
|
-
const
|
|
1852
|
-
const raw = await
|
|
1852
|
+
const fsp6 = await import("node:fs/promises");
|
|
1853
|
+
const raw = await fsp6.readFile(this.sessionPath, "utf8");
|
|
1853
1854
|
const loaded = JSON.parse(raw);
|
|
1854
1855
|
if (loaded?.id && loaded?.phase && loaded?.title) {
|
|
1855
1856
|
this.session = loaded;
|
|
@@ -1863,8 +1864,8 @@ var AISpecBuilder = class {
|
|
|
1863
1864
|
async deleteSession() {
|
|
1864
1865
|
if (!this.sessionPath) return;
|
|
1865
1866
|
try {
|
|
1866
|
-
const
|
|
1867
|
-
await
|
|
1867
|
+
const fsp6 = await import("node:fs/promises");
|
|
1868
|
+
await fsp6.unlink(this.sessionPath);
|
|
1868
1869
|
} catch {
|
|
1869
1870
|
}
|
|
1870
1871
|
}
|
|
@@ -2003,11 +2004,15 @@ var AISpecBuilder = class {
|
|
|
2003
2004
|
this.autoSave();
|
|
2004
2005
|
}
|
|
2005
2006
|
/**
|
|
2006
|
-
* Set the task graph ID for this session.
|
|
2007
|
+
* Set the task graph ID for this session. Awaits the save so a caller that
|
|
2008
|
+
* immediately follows with `await saveSession()` cannot end up with the
|
|
2009
|
+
* awaited write committing first and the fire-and-forget rename reverting
|
|
2010
|
+
* the persisted `taskGraphId` to its pre-set value. Same race window that
|
|
2011
|
+
* broke `setLastAgentText`/`setLastRunId` on the resume test.
|
|
2007
2012
|
*/
|
|
2008
|
-
setTaskGraphId(graphId) {
|
|
2013
|
+
async setTaskGraphId(graphId) {
|
|
2009
2014
|
this.session.taskGraphId = graphId;
|
|
2010
|
-
this.
|
|
2015
|
+
await this.saveSession();
|
|
2011
2016
|
}
|
|
2012
2017
|
/**
|
|
2013
2018
|
* Get the task graph ID for this session.
|
|
@@ -2015,6 +2020,50 @@ var AISpecBuilder = class {
|
|
|
2015
2020
|
getTaskGraphId() {
|
|
2016
2021
|
return this.session.taskGraphId;
|
|
2017
2022
|
}
|
|
2023
|
+
/**
|
|
2024
|
+
* Persist the last agent utterance so resume can rehydrate the UI + Q/A
|
|
2025
|
+
* pairing. Awaits the save so the next mutation in the call chain (e.g.
|
|
2026
|
+
* `setLastRunId`) cannot fire a concurrent save that overwrites this one
|
|
2027
|
+
* with a stale snapshot — the fire-and-forget `autoSave()` pattern leaves
|
|
2028
|
+
* a race window where an earlier queued save may commit its rename after
|
|
2029
|
+
* a later one, silently reverting the persisted state.
|
|
2030
|
+
*/
|
|
2031
|
+
async setLastAgentText(text) {
|
|
2032
|
+
this.session.lastAgentText = text;
|
|
2033
|
+
this.session.updatedAt = Date.now();
|
|
2034
|
+
await this.saveSession();
|
|
2035
|
+
}
|
|
2036
|
+
getLastAgentText() {
|
|
2037
|
+
return this.session.lastAgentText;
|
|
2038
|
+
}
|
|
2039
|
+
/** Record a run kicked off from this interview (board deep-link after restart). See {@link setLastAgentText} for the awaited-save rationale. */
|
|
2040
|
+
async setLastRunId(runId) {
|
|
2041
|
+
this.session.lastRunId = runId;
|
|
2042
|
+
this.session.updatedAt = Date.now();
|
|
2043
|
+
await this.saveSession();
|
|
2044
|
+
}
|
|
2045
|
+
getLastRunId() {
|
|
2046
|
+
return this.session.lastRunId;
|
|
2047
|
+
}
|
|
2048
|
+
/**
|
|
2049
|
+
* Hard-reset in-memory session fields while keeping the same session id /
|
|
2050
|
+
* store binding. Used when the operator abandons a resumed interview and
|
|
2051
|
+
* starts a brand-new goal (the next save overwrites the session file).
|
|
2052
|
+
*/
|
|
2053
|
+
resetForNewInterview() {
|
|
2054
|
+
this.session.phase = "questioning";
|
|
2055
|
+
this.session.title = "";
|
|
2056
|
+
this.session.userIntent = "";
|
|
2057
|
+
this.session.answers = [];
|
|
2058
|
+
this.session.questionCount = 0;
|
|
2059
|
+
this.session.spec = void 0;
|
|
2060
|
+
this.session.implementation = void 0;
|
|
2061
|
+
this.session.taskGraphId = void 0;
|
|
2062
|
+
this.session.lastAgentText = void 0;
|
|
2063
|
+
this.session.lastRunId = void 0;
|
|
2064
|
+
this.session.approved = false;
|
|
2065
|
+
this.session.updatedAt = Date.now();
|
|
2066
|
+
}
|
|
2018
2067
|
// ── Spec Persistence ──────────────────────────────────────────────────────
|
|
2019
2068
|
/**
|
|
2020
2069
|
* Save the current spec to the store.
|
|
@@ -2181,6 +2230,8 @@ var SddInterviewDriver = class {
|
|
|
2181
2230
|
maxQuestions;
|
|
2182
2231
|
tracker = null;
|
|
2183
2232
|
graph = null;
|
|
2233
|
+
/** Set when {@link loadExisting} successfully rehydrated a session from disk. */
|
|
2234
|
+
resumedFromDisk = false;
|
|
2184
2235
|
constructor(opts) {
|
|
2185
2236
|
this.o = opts;
|
|
2186
2237
|
this.minQuestions = opts.minQuestions ?? 2;
|
|
@@ -2195,9 +2246,11 @@ var SddInterviewDriver = class {
|
|
|
2195
2246
|
}
|
|
2196
2247
|
/** Begin a fresh interview. Returns the first AI prompt (a question kickoff). */
|
|
2197
2248
|
start(title, intent) {
|
|
2249
|
+
this.builder.resetForNewInterview();
|
|
2198
2250
|
this.builder.startSession(title, intent);
|
|
2199
2251
|
this.tracker = null;
|
|
2200
2252
|
this.graph = null;
|
|
2253
|
+
this.resumedFromDisk = false;
|
|
2201
2254
|
return this.builder.getAIPrompt();
|
|
2202
2255
|
}
|
|
2203
2256
|
/**
|
|
@@ -2217,8 +2270,32 @@ var SddInterviewDriver = class {
|
|
|
2217
2270
|
this.tracker = tracker;
|
|
2218
2271
|
}
|
|
2219
2272
|
}
|
|
2273
|
+
this.resumedFromDisk = true;
|
|
2220
2274
|
return true;
|
|
2221
2275
|
}
|
|
2276
|
+
/** Drop the on-disk session (if any) and clear in-memory interview state. */
|
|
2277
|
+
async discard() {
|
|
2278
|
+
await this.builder.deleteSession();
|
|
2279
|
+
this.builder.resetForNewInterview();
|
|
2280
|
+
this.tracker = null;
|
|
2281
|
+
this.graph = null;
|
|
2282
|
+
this.resumedFromDisk = false;
|
|
2283
|
+
}
|
|
2284
|
+
setLastAgentText(text) {
|
|
2285
|
+
return this.builder.setLastAgentText(text);
|
|
2286
|
+
}
|
|
2287
|
+
getLastAgentText() {
|
|
2288
|
+
return this.builder.getLastAgentText();
|
|
2289
|
+
}
|
|
2290
|
+
setLastRunId(runId) {
|
|
2291
|
+
return this.builder.setLastRunId(runId);
|
|
2292
|
+
}
|
|
2293
|
+
getLastRunId() {
|
|
2294
|
+
return this.builder.getLastRunId();
|
|
2295
|
+
}
|
|
2296
|
+
wasResumed() {
|
|
2297
|
+
return this.resumedFromDisk;
|
|
2298
|
+
}
|
|
2222
2299
|
phase() {
|
|
2223
2300
|
return this.builder.getPhase();
|
|
2224
2301
|
}
|
|
@@ -2303,7 +2380,7 @@ var SddInterviewDriver = class {
|
|
|
2303
2380
|
this.tracker = tracker;
|
|
2304
2381
|
this.graph = graph;
|
|
2305
2382
|
await this.persistGraph(graph);
|
|
2306
|
-
this.builder.setTaskGraphId(graph.id);
|
|
2383
|
+
await this.builder.setTaskGraphId(graph.id);
|
|
2307
2384
|
await this.builder.saveSession();
|
|
2308
2385
|
return graph;
|
|
2309
2386
|
}
|
|
@@ -2319,6 +2396,9 @@ var SddInterviewDriver = class {
|
|
|
2319
2396
|
minQuestions: this.minQuestions,
|
|
2320
2397
|
maxQuestions: this.maxQuestions,
|
|
2321
2398
|
answers: s.answers.map((a) => ({ question: a.question, answer: a.answer })),
|
|
2399
|
+
lastAgentText: s.lastAgentText,
|
|
2400
|
+
lastRunId: s.lastRunId,
|
|
2401
|
+
resumed: this.resumedFromDisk || void 0,
|
|
2322
2402
|
spec: spec ? {
|
|
2323
2403
|
id: spec.id,
|
|
2324
2404
|
title: spec.title,
|
|
@@ -2413,7 +2493,7 @@ var SddInterviewDriver = class {
|
|
|
2413
2493
|
}
|
|
2414
2494
|
}
|
|
2415
2495
|
await this.persistGraph(graph);
|
|
2416
|
-
this.builder.setTaskGraphId(graph.id);
|
|
2496
|
+
await this.builder.setTaskGraphId(graph.id);
|
|
2417
2497
|
await this.builder.saveSession();
|
|
2418
2498
|
return graph.id;
|
|
2419
2499
|
}
|
|
@@ -2443,15 +2523,12 @@ function isExplanatoryText(text) {
|
|
|
2443
2523
|
import { TOKENS } from "@wrongstack/core/kernel";
|
|
2444
2524
|
|
|
2445
2525
|
// src/sdd-parallel-run.ts
|
|
2446
|
-
import {
|
|
2447
|
-
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
2526
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
2448
2527
|
import {
|
|
2449
|
-
assignNickname,
|
|
2450
2528
|
DefaultMultiAgentCoordinator,
|
|
2451
2529
|
makeAgentSubagentRunner,
|
|
2452
2530
|
withDisabledToolFiltering
|
|
2453
2531
|
} from "@wrongstack/core/coordination";
|
|
2454
|
-
import { ERROR_CODES as ERROR_CODES3, SddError as SddError3 } from "@wrongstack/core/types";
|
|
2455
2532
|
|
|
2456
2533
|
// src/graph-split.ts
|
|
2457
2534
|
function splitGraphNode(tracker, taskId, subtasks, options = {}) {
|
|
@@ -2463,8 +2540,7 @@ function splitGraphNode(tracker, taskId, subtasks, options = {}) {
|
|
|
2463
2540
|
const dependents = tracker.getDependents(taskId);
|
|
2464
2541
|
const leafIds = subtasks.map((s) => {
|
|
2465
2542
|
const criterion = s.successCriterion?.trim();
|
|
2466
|
-
const
|
|
2467
|
-
const description = criterion && !verificationCommand ? `${s.description}
|
|
2543
|
+
const description = criterion ? `${s.description}
|
|
2468
2544
|
|
|
2469
2545
|
**Acceptance Criteria:**
|
|
2470
2546
|
- ${criterion}` : s.description;
|
|
@@ -2474,8 +2550,7 @@ function splitGraphNode(tracker, taskId, subtasks, options = {}) {
|
|
|
2474
2550
|
type: s.type ?? node.type,
|
|
2475
2551
|
priority: s.priority ?? node.priority,
|
|
2476
2552
|
status: "pending",
|
|
2477
|
-
parentId: taskId
|
|
2478
|
-
...verificationCommand ? { metadata: { verificationCommand } } : {}
|
|
2553
|
+
parentId: taskId
|
|
2479
2554
|
}).id;
|
|
2480
2555
|
});
|
|
2481
2556
|
for (const leaf of leafIds) {
|
|
@@ -2486,6 +2561,180 @@ function splitGraphNode(tracker, taskId, subtasks, options = {}) {
|
|
|
2486
2561
|
return leafIds;
|
|
2487
2562
|
}
|
|
2488
2563
|
|
|
2564
|
+
// src/sdd-task-execution.ts
|
|
2565
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
2566
|
+
import { expectDefined as expectDefined3 } from "@wrongstack/core/utils";
|
|
2567
|
+
import { assignNickname } from "@wrongstack/core/coordination";
|
|
2568
|
+
import { ERROR_CODES as ERROR_CODES3, SddError as SddError3 } from "@wrongstack/core/types";
|
|
2569
|
+
async function executeSddTask(params) {
|
|
2570
|
+
const { task, opts } = params;
|
|
2571
|
+
const taskId = task.id;
|
|
2572
|
+
let agentName = task.assignee;
|
|
2573
|
+
if (!agentName) {
|
|
2574
|
+
const nick = assignNickname("executor", params.usedNicknames);
|
|
2575
|
+
params.usedNicknames.add(nick.key);
|
|
2576
|
+
agentName = nick.display.replace(/\s*\([^)]*\)\s*$/, "");
|
|
2577
|
+
opts.tracker.updateNode(taskId, { assignee: agentName });
|
|
2578
|
+
}
|
|
2579
|
+
opts.tracker.updateNodeStatus(taskId, "in_progress");
|
|
2580
|
+
await params.allocateWorktrees([task]);
|
|
2581
|
+
if (!params.coordinator)
|
|
2582
|
+
throw new SddError3({
|
|
2583
|
+
message: "SDD parallel runner requires a coordinator",
|
|
2584
|
+
code: ERROR_CODES3.SDD_INVALID_STATE
|
|
2585
|
+
});
|
|
2586
|
+
const coordinator = params.coordinator;
|
|
2587
|
+
const subagentId = params.nextSubagentId();
|
|
2588
|
+
const correlationId = randomUUID2();
|
|
2589
|
+
const meta = task.metadata ?? {};
|
|
2590
|
+
const model = (typeof meta.model === "string" ? meta.model : void 0) ?? opts.defaultModel;
|
|
2591
|
+
const provider = (typeof meta.provider === "string" ? meta.provider : void 0) ?? opts.defaultProvider;
|
|
2592
|
+
const fallbackModels = Array.isArray(meta.fallbackModels) ? meta.fallbackModels : opts.fallbackModels;
|
|
2593
|
+
const spawnResult = await coordinator.spawn({
|
|
2594
|
+
id: subagentId,
|
|
2595
|
+
name: agentName,
|
|
2596
|
+
role: "executor",
|
|
2597
|
+
idleTimeoutMs: params.idleTimeoutMs,
|
|
2598
|
+
...params.timeoutMs ? { timeoutMs: params.timeoutMs } : {},
|
|
2599
|
+
cwd: params.taskCwds.get(taskId),
|
|
2600
|
+
disabledTools: ["delegate"],
|
|
2601
|
+
...model ? { model } : {},
|
|
2602
|
+
...provider ? { provider } : {},
|
|
2603
|
+
...fallbackModels?.length ? { fallbackModels } : {}
|
|
2604
|
+
});
|
|
2605
|
+
if (!spawnResult.subagentId) {
|
|
2606
|
+
throw new SddError3({
|
|
2607
|
+
message: "One or more subagent spawns failed",
|
|
2608
|
+
code: ERROR_CODES3.SDD_INVALID_STATE
|
|
2609
|
+
});
|
|
2610
|
+
}
|
|
2611
|
+
params.taskSubagents.set(taskId, subagentId);
|
|
2612
|
+
params.emit("sdd.task.started", {
|
|
2613
|
+
runId: params.runId,
|
|
2614
|
+
taskId,
|
|
2615
|
+
subagentId,
|
|
2616
|
+
agentName,
|
|
2617
|
+
worktreeBranch: params.taskBranches.get(taskId)
|
|
2618
|
+
});
|
|
2619
|
+
await coordinator.assign({
|
|
2620
|
+
id: correlationId,
|
|
2621
|
+
description: buildTaskDirective(opts.graph.title, task),
|
|
2622
|
+
subagentId,
|
|
2623
|
+
...params.timeoutMs ? { timeoutMs: params.timeoutMs } : {},
|
|
2624
|
+
context: {
|
|
2625
|
+
telemetryTaskId: taskId,
|
|
2626
|
+
telemetryRunId: params.runId,
|
|
2627
|
+
telemetryBoardId: opts.graph.id
|
|
2628
|
+
}
|
|
2629
|
+
});
|
|
2630
|
+
let result;
|
|
2631
|
+
try {
|
|
2632
|
+
const got = await coordinator.awaitTasks([correlationId]);
|
|
2633
|
+
result = expectDefined3(got[0]);
|
|
2634
|
+
} catch (err) {
|
|
2635
|
+
result = {
|
|
2636
|
+
subagentId,
|
|
2637
|
+
taskId: correlationId,
|
|
2638
|
+
status: "failed",
|
|
2639
|
+
error: { kind: "unknown", message: String(err), retryable: false },
|
|
2640
|
+
iterations: 0,
|
|
2641
|
+
toolCalls: 0,
|
|
2642
|
+
durationMs: 0
|
|
2643
|
+
};
|
|
2644
|
+
}
|
|
2645
|
+
params.taskSubagents.delete(taskId);
|
|
2646
|
+
if (params.cancelledTasks.has(taskId)) {
|
|
2647
|
+
await params.resolveWorktrees([task]);
|
|
2648
|
+
return { taskId, success: false, result };
|
|
2649
|
+
}
|
|
2650
|
+
const verificationFailReason = await verifyTaskResult(params, result);
|
|
2651
|
+
let success = false;
|
|
2652
|
+
if (result.status === "success" && !verificationFailReason) {
|
|
2653
|
+
const merged = await params.integrateWorktree(task, result);
|
|
2654
|
+
if (merged.ok) {
|
|
2655
|
+
success = true;
|
|
2656
|
+
opts.tracker.updateNodeStatus(taskId, "completed");
|
|
2657
|
+
params.emit("sdd.task.completed", {
|
|
2658
|
+
runId: params.runId,
|
|
2659
|
+
taskId,
|
|
2660
|
+
subagentId,
|
|
2661
|
+
durationMs: result.durationMs
|
|
2662
|
+
});
|
|
2663
|
+
} else if (merged.reason) {
|
|
2664
|
+
params.emit("sdd.task.verification_failed", {
|
|
2665
|
+
runId: params.runId,
|
|
2666
|
+
taskId,
|
|
2667
|
+
reason: merged.reason
|
|
2668
|
+
});
|
|
2669
|
+
await params.applyTaskFailure(taskId, subagentId, merged.reason);
|
|
2670
|
+
} else {
|
|
2671
|
+
const conflictFiles = merged.conflictFiles ?? [];
|
|
2672
|
+
params.emit("sdd.task.conflict", { runId: params.runId, taskId, conflictFiles });
|
|
2673
|
+
const reason = `merge conflict${conflictFiles.length ? `: ${conflictFiles.join(", ")}` : ""}`;
|
|
2674
|
+
await params.applyTaskFailure(taskId, subagentId, reason);
|
|
2675
|
+
}
|
|
2676
|
+
} else {
|
|
2677
|
+
const errMsg = verificationFailReason ?? (result.error?.kind ? `${result.error.kind}: ${result.error.message}` : result.error?.message ?? "unknown error");
|
|
2678
|
+
await params.applyTaskFailure(taskId, subagentId, errMsg);
|
|
2679
|
+
await params.resolveWorktrees([task]);
|
|
2680
|
+
}
|
|
2681
|
+
return { taskId, success, result };
|
|
2682
|
+
}
|
|
2683
|
+
function buildTaskDirective(graphTitle, task) {
|
|
2684
|
+
const directivePreamble = [
|
|
2685
|
+
"\u2550\u2550\u2550 SDD PARALLEL EXECUTION \u2550\u2550\u2550",
|
|
2686
|
+
"",
|
|
2687
|
+
`Graph: ${graphTitle}`,
|
|
2688
|
+
"",
|
|
2689
|
+
"\u2500\u2500 EXECUTION PROTOCOL \u2500\u2500",
|
|
2690
|
+
"\u2022 Execute the assigned SDD task end-to-end using multiple tool calls.",
|
|
2691
|
+
"\u2022 Mark the task [done] in the tracker when complete.",
|
|
2692
|
+
"\u2022 Do not ask before routine in-project tool use; if a permission gate appears, wait for that flow.",
|
|
2693
|
+
"\u2022 Keep output concise \u2014 summarize changes, do not transcribe files."
|
|
2694
|
+
].join("\n");
|
|
2695
|
+
return [
|
|
2696
|
+
directivePreamble,
|
|
2697
|
+
"",
|
|
2698
|
+
`\u2500\u2500 TASK \u2500\u2500`,
|
|
2699
|
+
`[${task.priority.toUpperCase()}] ${task.title}`,
|
|
2700
|
+
"",
|
|
2701
|
+
task.description
|
|
2702
|
+
].join("\n");
|
|
2703
|
+
}
|
|
2704
|
+
async function verifyTaskResult(params, result) {
|
|
2705
|
+
const { task, opts, taskCwds } = params;
|
|
2706
|
+
if (result.status !== "success" || !opts.verifyTask) return void 0;
|
|
2707
|
+
const taskId = task.id;
|
|
2708
|
+
const cwd = taskCwds.get(taskId) ?? opts.projectRoot;
|
|
2709
|
+
let verificationFailReason;
|
|
2710
|
+
try {
|
|
2711
|
+
const verdict = await opts.verifyTask({ task, result, cwd });
|
|
2712
|
+
if (!verdict.ok) {
|
|
2713
|
+
verificationFailReason = `verification failed: ${verdict.reason ?? "acceptance criteria not met"}`;
|
|
2714
|
+
}
|
|
2715
|
+
} catch (err) {
|
|
2716
|
+
verificationFailReason = `verification error: ${String(err)}`;
|
|
2717
|
+
}
|
|
2718
|
+
const hadVerifiable = typeof task.metadata?.["verificationCommand"] === "string" || task.description.includes("**Acceptance Criteria:**");
|
|
2719
|
+
if (verificationFailReason) {
|
|
2720
|
+
opts.tracker.patchMetadata(taskId, {
|
|
2721
|
+
verificationState: "failed",
|
|
2722
|
+
verificationDetail: verificationFailReason
|
|
2723
|
+
});
|
|
2724
|
+
params.emit("sdd.task.verification_failed", {
|
|
2725
|
+
runId: params.runId,
|
|
2726
|
+
taskId,
|
|
2727
|
+
reason: verificationFailReason
|
|
2728
|
+
});
|
|
2729
|
+
} else if (hadVerifiable) {
|
|
2730
|
+
opts.tracker.patchMetadata(taskId, {
|
|
2731
|
+
verificationState: "passed",
|
|
2732
|
+
verificationDetail: void 0
|
|
2733
|
+
});
|
|
2734
|
+
}
|
|
2735
|
+
return verificationFailReason;
|
|
2736
|
+
}
|
|
2737
|
+
|
|
2489
2738
|
// src/sdd-task-decomposer.ts
|
|
2490
2739
|
var SddTaskDecomposer = class {
|
|
2491
2740
|
constructor(tracker, _graph, opts = {}) {
|
|
@@ -2602,7 +2851,7 @@ var SddParallelRun = class {
|
|
|
2602
2851
|
this.maxRetries = Math.max(0, opts.maxRetries ?? 3);
|
|
2603
2852
|
this.maxSupervisorEscalations = Math.max(0, opts.maxSupervisorEscalations ?? 2);
|
|
2604
2853
|
this.maxFailedSweeps = Math.max(0, opts.maxFailedRetrySweeps ?? 2);
|
|
2605
|
-
this.runId = opts.runId ?? `sdd-${
|
|
2854
|
+
this.runId = opts.runId ?? `sdd-${randomUUID3().slice(0, 8)}`;
|
|
2606
2855
|
this.events = opts.events;
|
|
2607
2856
|
this.sessionIdSource = opts.sessionId;
|
|
2608
2857
|
this.maxTotalWaves = opts.maxTotalWaves ?? opts.graph.nodes.size * (this.maxRetries + 2) + 10;
|
|
@@ -3082,7 +3331,7 @@ var SddParallelRun = class {
|
|
|
3082
3331
|
// -------------------------------------------------------------------
|
|
3083
3332
|
buildCoordinator() {
|
|
3084
3333
|
const config = {
|
|
3085
|
-
coordinatorId: `sdd-parallel-${
|
|
3334
|
+
coordinatorId: `sdd-parallel-${randomUUID3().slice(0, 8)}`,
|
|
3086
3335
|
maxConcurrent: this.slots,
|
|
3087
3336
|
doneCondition: { type: "all_tasks_done" },
|
|
3088
3337
|
// Default budget guard for every spawned worker: idle reaper (resets on
|
|
@@ -3139,170 +3388,30 @@ var SddParallelRun = class {
|
|
|
3139
3388
|
* missing coordinator or failed spawn so callers can enforce all-or-nothing.
|
|
3140
3389
|
*/
|
|
3141
3390
|
async executeOne(task) {
|
|
3142
|
-
const
|
|
3143
|
-
|
|
3144
|
-
|
|
3145
|
-
|
|
3146
|
-
this.usedNicknames
|
|
3147
|
-
agentName = nick.display.replace(/\s*\([^)]*\)\s*$/, "");
|
|
3148
|
-
this.opts.tracker.updateNode(taskId, { assignee: agentName });
|
|
3149
|
-
}
|
|
3150
|
-
this.opts.tracker.updateNodeStatus(taskId, "in_progress");
|
|
3151
|
-
await this.allocateWorktrees([task]);
|
|
3152
|
-
if (!this.coordinator)
|
|
3153
|
-
throw new SddError3({
|
|
3154
|
-
message: "SDD parallel runner requires a coordinator",
|
|
3155
|
-
code: ERROR_CODES3.SDD_INVALID_STATE
|
|
3156
|
-
});
|
|
3157
|
-
const coordinator = this.coordinator;
|
|
3158
|
-
const subagentId = `sdd-d${this.dispatchSeq++}`;
|
|
3159
|
-
const correlationId = randomUUID2();
|
|
3160
|
-
const meta = task.metadata ?? {};
|
|
3161
|
-
const model = (typeof meta.model === "string" ? meta.model : void 0) ?? this.opts.defaultModel;
|
|
3162
|
-
const provider = (typeof meta.provider === "string" ? meta.provider : void 0) ?? this.opts.defaultProvider;
|
|
3163
|
-
const fallbackModels = Array.isArray(meta.fallbackModels) ? meta.fallbackModels : this.opts.fallbackModels;
|
|
3164
|
-
const spawnResult = await coordinator.spawn({
|
|
3165
|
-
id: subagentId,
|
|
3166
|
-
name: agentName,
|
|
3167
|
-
role: "executor",
|
|
3168
|
-
// Idle reaper is always on; the hard wall-clock cap only when opted in.
|
|
3391
|
+
const outcome = await executeSddTask({
|
|
3392
|
+
task,
|
|
3393
|
+
opts: this.opts,
|
|
3394
|
+
coordinator: this.coordinator,
|
|
3395
|
+
usedNicknames: this.usedNicknames,
|
|
3169
3396
|
idleTimeoutMs: this.idleTimeoutMs,
|
|
3170
|
-
|
|
3171
|
-
cwd: this.taskCwds.get(taskId),
|
|
3172
|
-
disabledTools: ["delegate"],
|
|
3173
|
-
...model ? { model } : {},
|
|
3174
|
-
...provider ? { provider } : {},
|
|
3175
|
-
...fallbackModels?.length ? { fallbackModels } : {}
|
|
3176
|
-
});
|
|
3177
|
-
if (!spawnResult.subagentId) {
|
|
3178
|
-
throw new SddError3({
|
|
3179
|
-
message: "One or more subagent spawns failed",
|
|
3180
|
-
code: ERROR_CODES3.SDD_INVALID_STATE
|
|
3181
|
-
});
|
|
3182
|
-
}
|
|
3183
|
-
this.taskSubagents.set(taskId, subagentId);
|
|
3184
|
-
this.emit("sdd.task.started", {
|
|
3397
|
+
timeoutMs: this.timeoutMs,
|
|
3185
3398
|
runId: this.runId,
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3399
|
+
nextSubagentId: () => `sdd-d${this.dispatchSeq++}`,
|
|
3400
|
+
emit: (event, payload) => this.emit(event, payload),
|
|
3401
|
+
taskCwds: this.taskCwds,
|
|
3402
|
+
taskBranches: this.taskBranches,
|
|
3403
|
+
taskSubagents: this.taskSubagents,
|
|
3404
|
+
cancelledTasks: this.cancelledTasks,
|
|
3405
|
+
allocateWorktrees: (tasks) => this.allocateWorktrees(tasks),
|
|
3406
|
+
resolveWorktrees: (tasks) => this.resolveWorktrees(tasks),
|
|
3407
|
+
integrateWorktree: (taskNode, result) => this.integrateWorktree(taskNode, result),
|
|
3408
|
+
applyTaskFailure: (taskId, subagentId, errMsg) => this.applyTaskFailure(taskId, subagentId, errMsg)
|
|
3190
3409
|
});
|
|
3191
|
-
|
|
3192
|
-
|
|
3193
|
-
|
|
3194
|
-
`Graph: ${this.opts.graph.title}`,
|
|
3195
|
-
"",
|
|
3196
|
-
"\u2500\u2500 EXECUTION PROTOCOL \u2500\u2500",
|
|
3197
|
-
"\u2022 Execute the assigned SDD task end-to-end using multiple tool calls.",
|
|
3198
|
-
"\u2022 Mark the task [done] in the tracker when complete.",
|
|
3199
|
-
"\u2022 Do not ask before routine in-project tool use; if a permission gate appears, wait for that flow.",
|
|
3200
|
-
"\u2022 Keep output concise \u2014 summarize changes, do not transcribe files."
|
|
3201
|
-
].join("\n");
|
|
3202
|
-
await coordinator.assign({
|
|
3203
|
-
id: correlationId,
|
|
3204
|
-
description: [
|
|
3205
|
-
directivePreamble,
|
|
3206
|
-
"",
|
|
3207
|
-
`\u2500\u2500 TASK \u2500\u2500`,
|
|
3208
|
-
`[${task.priority.toUpperCase()}] ${task.title}`,
|
|
3209
|
-
"",
|
|
3210
|
-
task.description
|
|
3211
|
-
].join("\n"),
|
|
3212
|
-
subagentId,
|
|
3213
|
-
...this.timeoutMs ? { timeoutMs: this.timeoutMs } : {},
|
|
3214
|
-
context: {
|
|
3215
|
-
telemetryTaskId: taskId,
|
|
3216
|
-
telemetryRunId: this.runId,
|
|
3217
|
-
telemetryBoardId: this.opts.graph.id
|
|
3218
|
-
}
|
|
3219
|
-
});
|
|
3220
|
-
let result;
|
|
3221
|
-
try {
|
|
3222
|
-
const got = await coordinator.awaitTasks([correlationId]);
|
|
3223
|
-
result = expectDefined3(got[0]);
|
|
3224
|
-
} catch (err) {
|
|
3225
|
-
result = {
|
|
3226
|
-
subagentId,
|
|
3227
|
-
taskId: correlationId,
|
|
3228
|
-
status: "failed",
|
|
3229
|
-
error: { kind: "unknown", message: String(err), retryable: false },
|
|
3230
|
-
iterations: 0,
|
|
3231
|
-
toolCalls: 0,
|
|
3232
|
-
durationMs: 0
|
|
3233
|
-
};
|
|
3234
|
-
}
|
|
3235
|
-
this.taskSubagents.delete(taskId);
|
|
3236
|
-
if (this.cancelledTasks.has(taskId)) {
|
|
3237
|
-
await this.resolveWorktrees([task]);
|
|
3238
|
-
return { taskId, success: false, result };
|
|
3239
|
-
}
|
|
3240
|
-
let verificationFailReason;
|
|
3241
|
-
if (result.status === "success" && this.opts.verifyTask) {
|
|
3242
|
-
const cwd = this.taskCwds.get(taskId) ?? this.opts.projectRoot;
|
|
3243
|
-
try {
|
|
3244
|
-
const verdict = await this.opts.verifyTask({ task, result, cwd });
|
|
3245
|
-
if (!verdict.ok) {
|
|
3246
|
-
verificationFailReason = `verification failed: ${verdict.reason ?? "acceptance criteria not met"}`;
|
|
3247
|
-
}
|
|
3248
|
-
} catch (err) {
|
|
3249
|
-
verificationFailReason = `verification error: ${String(err)}`;
|
|
3250
|
-
}
|
|
3251
|
-
const hadVerifiable = typeof task.metadata?.["verificationCommand"] === "string" || task.description.includes("**Acceptance Criteria:**");
|
|
3252
|
-
if (verificationFailReason) {
|
|
3253
|
-
this.opts.tracker.patchMetadata(taskId, {
|
|
3254
|
-
verificationState: "failed",
|
|
3255
|
-
verificationDetail: verificationFailReason
|
|
3256
|
-
});
|
|
3257
|
-
this.emit("sdd.task.verification_failed", {
|
|
3258
|
-
runId: this.runId,
|
|
3259
|
-
taskId,
|
|
3260
|
-
reason: verificationFailReason
|
|
3261
|
-
});
|
|
3262
|
-
} else if (hadVerifiable) {
|
|
3263
|
-
this.opts.tracker.patchMetadata(taskId, {
|
|
3264
|
-
verificationState: "passed",
|
|
3265
|
-
verificationDetail: void 0
|
|
3266
|
-
});
|
|
3267
|
-
}
|
|
3268
|
-
}
|
|
3269
|
-
let success = false;
|
|
3270
|
-
if (result.status === "success" && !verificationFailReason) {
|
|
3271
|
-
const merged = await this.integrateWorktree(task, result);
|
|
3272
|
-
if (merged.ok) {
|
|
3273
|
-
success = true;
|
|
3274
|
-
this.opts.tracker.updateNodeStatus(taskId, "completed");
|
|
3275
|
-
this.retryMap.delete(taskId);
|
|
3276
|
-
this.persistRetries(taskId, 0);
|
|
3277
|
-
this.emit("sdd.task.completed", {
|
|
3278
|
-
runId: this.runId,
|
|
3279
|
-
taskId,
|
|
3280
|
-
subagentId,
|
|
3281
|
-
durationMs: result.durationMs
|
|
3282
|
-
});
|
|
3283
|
-
} else if (merged.reason) {
|
|
3284
|
-
this.emit("sdd.task.verification_failed", {
|
|
3285
|
-
runId: this.runId,
|
|
3286
|
-
taskId,
|
|
3287
|
-
reason: merged.reason
|
|
3288
|
-
});
|
|
3289
|
-
await this.applyTaskFailure(taskId, subagentId, merged.reason);
|
|
3290
|
-
} else {
|
|
3291
|
-
const conflictFiles = merged.conflictFiles ?? [];
|
|
3292
|
-
this.emit("sdd.task.conflict", {
|
|
3293
|
-
runId: this.runId,
|
|
3294
|
-
taskId,
|
|
3295
|
-
conflictFiles
|
|
3296
|
-
});
|
|
3297
|
-
const reason = `merge conflict${conflictFiles.length ? `: ${conflictFiles.join(", ")}` : ""}`;
|
|
3298
|
-
await this.applyTaskFailure(taskId, subagentId, reason);
|
|
3299
|
-
}
|
|
3300
|
-
} else {
|
|
3301
|
-
const errMsg = verificationFailReason ?? (result.error?.kind ? `${result.error.kind}: ${result.error.message}` : result.error?.message ?? "unknown error");
|
|
3302
|
-
await this.applyTaskFailure(taskId, subagentId, errMsg);
|
|
3303
|
-
await this.resolveWorktrees([task]);
|
|
3410
|
+
if (outcome.success) {
|
|
3411
|
+
this.retryMap.delete(task.id);
|
|
3412
|
+
this.persistRetries(task.id, 0);
|
|
3304
3413
|
}
|
|
3305
|
-
return
|
|
3414
|
+
return outcome;
|
|
3306
3415
|
}
|
|
3307
3416
|
/**
|
|
3308
3417
|
* Apply a task failure: retry (→ pending, bump retry count) while attempts
|
|
@@ -3647,8 +3756,10 @@ function startSddRun(opts) {
|
|
|
3647
3756
|
|
|
3648
3757
|
// src/sdd-lifecycle.ts
|
|
3649
3758
|
import * as fsp4 from "node:fs/promises";
|
|
3759
|
+
import * as path4 from "node:path";
|
|
3650
3760
|
import { toErrorMessage as toErrorMessage2 } from "@wrongstack/core/utils";
|
|
3651
3761
|
import { WorktreeManager } from "@wrongstack/core/worktree";
|
|
3762
|
+
import { listBoards, removeBoard } from "@wrongstack/kanban";
|
|
3652
3763
|
async function cleanupSddWorktrees(projectRoot) {
|
|
3653
3764
|
const wt = new WorktreeManager({ projectRoot });
|
|
3654
3765
|
return wt.cleanupAllManaged();
|
|
@@ -3734,9 +3845,22 @@ async function destroySddProject(opts) {
|
|
|
3734
3845
|
}
|
|
3735
3846
|
};
|
|
3736
3847
|
await rmFile(opts.paths.projectSddSession, "session");
|
|
3848
|
+
await rmFile(
|
|
3849
|
+
path4.join(path4.dirname(opts.paths.projectSddSession), "sdd-wizard-session.json"),
|
|
3850
|
+
"wizard-session"
|
|
3851
|
+
);
|
|
3737
3852
|
await rmDir(opts.paths.projectSpecs, "specs");
|
|
3738
3853
|
await rmDir(opts.paths.projectTaskGraphs, "task-graphs");
|
|
3739
3854
|
await rmDir(opts.paths.projectSddBoards, "boards");
|
|
3855
|
+
try {
|
|
3856
|
+
const mirrors = (await listBoards(opts.projectRoot)).filter((b) => b.tags?.includes("sdd"));
|
|
3857
|
+
let mirrorsRemoved = 0;
|
|
3858
|
+
for (const b of mirrors) {
|
|
3859
|
+
if (await removeBoard(opts.projectRoot, b.id)) mirrorsRemoved++;
|
|
3860
|
+
}
|
|
3861
|
+
if (mirrorsRemoved > 0) deleted.push(`kanban-mirrors(${mirrorsRemoved})`);
|
|
3862
|
+
} catch {
|
|
3863
|
+
}
|
|
3740
3864
|
return { worktreesRemoved: removed, deleted, reverted, revertOk, revertReason };
|
|
3741
3865
|
}
|
|
3742
3866
|
async function applySddLifecycle(op, opts) {
|
|
@@ -3774,6 +3898,56 @@ async function applySddLifecycle(op, opts) {
|
|
|
3774
3898
|
}
|
|
3775
3899
|
}
|
|
3776
3900
|
|
|
3901
|
+
// src/project-context.ts
|
|
3902
|
+
import * as fsp5 from "node:fs/promises";
|
|
3903
|
+
import * as path5 from "node:path";
|
|
3904
|
+
async function gatherProjectContext(projectRoot) {
|
|
3905
|
+
const parts = [];
|
|
3906
|
+
const root = projectRoot.trim() || process.cwd();
|
|
3907
|
+
try {
|
|
3908
|
+
const pkgPath = path5.join(root, "package.json");
|
|
3909
|
+
const pkgRaw = await fsp5.readFile(pkgPath, "utf8");
|
|
3910
|
+
const pkg = JSON.parse(pkgRaw);
|
|
3911
|
+
parts.push(`Project: ${String(pkg.name ?? "unknown")}`);
|
|
3912
|
+
parts.push(`Description: ${String(pkg.description ?? "none")}`);
|
|
3913
|
+
if (pkg.dependencies && typeof pkg.dependencies === "object") {
|
|
3914
|
+
const deps = Object.keys(pkg.dependencies);
|
|
3915
|
+
parts.push(`Dependencies: ${deps.slice(0, 20).join(", ")}${deps.length > 20 ? "..." : ""}`);
|
|
3916
|
+
}
|
|
3917
|
+
if (pkg.devDependencies && typeof pkg.devDependencies === "object") {
|
|
3918
|
+
const devDeps = Object.keys(pkg.devDependencies);
|
|
3919
|
+
parts.push(
|
|
3920
|
+
`Dev Dependencies: ${devDeps.slice(0, 15).join(", ")}${devDeps.length > 15 ? "..." : ""}`
|
|
3921
|
+
);
|
|
3922
|
+
}
|
|
3923
|
+
} catch {
|
|
3924
|
+
}
|
|
3925
|
+
try {
|
|
3926
|
+
await fsp5.access(path5.join(root, "tsconfig.json"));
|
|
3927
|
+
parts.push("Language: TypeScript");
|
|
3928
|
+
} catch {
|
|
3929
|
+
}
|
|
3930
|
+
try {
|
|
3931
|
+
const srcDir = path5.join(root, "src");
|
|
3932
|
+
const entries = await fsp5.readdir(srcDir, { withFileTypes: true });
|
|
3933
|
+
const dirs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
3934
|
+
if (dirs.length > 0) parts.push(`Source structure: src/${dirs.join(", src/")}`);
|
|
3935
|
+
} catch {
|
|
3936
|
+
}
|
|
3937
|
+
try {
|
|
3938
|
+
const packagesDir = path5.join(root, "packages");
|
|
3939
|
+
const entries = await fsp5.readdir(packagesDir, { withFileTypes: true });
|
|
3940
|
+
const pkgs = entries.filter((e) => e.isDirectory()).map((e) => e.name);
|
|
3941
|
+
if (pkgs.length > 0) {
|
|
3942
|
+
parts.push(
|
|
3943
|
+
`Packages: ${pkgs.slice(0, 25).join(", ")}${pkgs.length > 25 ? "..." : ""}`
|
|
3944
|
+
);
|
|
3945
|
+
}
|
|
3946
|
+
} catch {
|
|
3947
|
+
}
|
|
3948
|
+
return parts.join("\n");
|
|
3949
|
+
}
|
|
3950
|
+
|
|
3777
3951
|
// src/spec-templates.ts
|
|
3778
3952
|
var SPEC_TEMPLATES = [
|
|
3779
3953
|
{
|
|
@@ -4175,15 +4349,15 @@ function computeCriticalPath(graph, _topoOrder, blockedByMap) {
|
|
|
4175
4349
|
maxId = id;
|
|
4176
4350
|
}
|
|
4177
4351
|
}
|
|
4178
|
-
const
|
|
4352
|
+
const path6 = [];
|
|
4179
4353
|
let current = maxId;
|
|
4180
4354
|
const visited = /* @__PURE__ */ new Set();
|
|
4181
4355
|
while (current && !visited.has(current)) {
|
|
4182
4356
|
visited.add(current);
|
|
4183
|
-
|
|
4357
|
+
path6.unshift(current);
|
|
4184
4358
|
current = prev.get(current) ?? null;
|
|
4185
4359
|
}
|
|
4186
|
-
return
|
|
4360
|
+
return path6;
|
|
4187
4361
|
}
|
|
4188
4362
|
function computeParallelGroups(graph, blockedByMap) {
|
|
4189
4363
|
const groups = [];
|
|
@@ -4604,8 +4778,70 @@ Supervisor rescues already used: ${attempts}`,
|
|
|
4604
4778
|
|
|
4605
4779
|
// src/verify-task.ts
|
|
4606
4780
|
import { spawn } from "node:child_process";
|
|
4607
|
-
function
|
|
4608
|
-
|
|
4781
|
+
function tokenizeCommand(command) {
|
|
4782
|
+
const trimmed = command.trim();
|
|
4783
|
+
if (!trimmed) return void 0;
|
|
4784
|
+
const argv = [];
|
|
4785
|
+
let current = "";
|
|
4786
|
+
let inSingle = false;
|
|
4787
|
+
let inDouble = false;
|
|
4788
|
+
let hasToken = false;
|
|
4789
|
+
for (let i = 0; i < trimmed.length; i++) {
|
|
4790
|
+
const ch = trimmed[i];
|
|
4791
|
+
if (inSingle) {
|
|
4792
|
+
if (ch === "'") {
|
|
4793
|
+
inSingle = false;
|
|
4794
|
+
} else {
|
|
4795
|
+
current += ch;
|
|
4796
|
+
}
|
|
4797
|
+
continue;
|
|
4798
|
+
}
|
|
4799
|
+
if (inDouble) {
|
|
4800
|
+
if (ch === '"') {
|
|
4801
|
+
inDouble = false;
|
|
4802
|
+
} else if (ch === "\\" && i + 1 < trimmed.length) {
|
|
4803
|
+
const next = trimmed[i + 1];
|
|
4804
|
+
if (next === '"' || next === "\\" || next === "$" || next === "`") {
|
|
4805
|
+
current += next;
|
|
4806
|
+
i++;
|
|
4807
|
+
} else {
|
|
4808
|
+
current += ch;
|
|
4809
|
+
}
|
|
4810
|
+
} else {
|
|
4811
|
+
current += ch;
|
|
4812
|
+
}
|
|
4813
|
+
continue;
|
|
4814
|
+
}
|
|
4815
|
+
if (ch === "'") {
|
|
4816
|
+
inSingle = true;
|
|
4817
|
+
hasToken = true;
|
|
4818
|
+
continue;
|
|
4819
|
+
}
|
|
4820
|
+
if (ch === '"') {
|
|
4821
|
+
inDouble = true;
|
|
4822
|
+
hasToken = true;
|
|
4823
|
+
continue;
|
|
4824
|
+
}
|
|
4825
|
+
if (ch === "\\" && i + 1 < trimmed.length) {
|
|
4826
|
+
current += trimmed[i + 1];
|
|
4827
|
+
i++;
|
|
4828
|
+
hasToken = true;
|
|
4829
|
+
continue;
|
|
4830
|
+
}
|
|
4831
|
+
if (ch === " " || ch === " ") {
|
|
4832
|
+
if (hasToken) {
|
|
4833
|
+
argv.push(current);
|
|
4834
|
+
current = "";
|
|
4835
|
+
hasToken = false;
|
|
4836
|
+
}
|
|
4837
|
+
continue;
|
|
4838
|
+
}
|
|
4839
|
+
current += ch;
|
|
4840
|
+
hasToken = true;
|
|
4841
|
+
}
|
|
4842
|
+
if (inSingle || inDouble) return void 0;
|
|
4843
|
+
if (hasToken) argv.push(current);
|
|
4844
|
+
return argv.length > 0 ? argv : void 0;
|
|
4609
4845
|
}
|
|
4610
4846
|
function makeCompositeVerifier(parts) {
|
|
4611
4847
|
return async function verifyTask(info) {
|
|
@@ -4656,11 +4892,15 @@ function makeCommandVerifier(options = {}) {
|
|
|
4656
4892
|
const metadataKey = options.metadataKey ?? "verificationCommand";
|
|
4657
4893
|
const timeoutMs = options.timeoutMs ?? 18e4;
|
|
4658
4894
|
return async function verifyTask(info) {
|
|
4659
|
-
const
|
|
4660
|
-
if (typeof
|
|
4895
|
+
const rawCommand = info.task.metadata?.[metadataKey];
|
|
4896
|
+
if (typeof rawCommand !== "string" || !rawCommand.trim()) return { ok: true };
|
|
4897
|
+
const argv = tokenizeCommand(rawCommand);
|
|
4898
|
+
if (!argv || argv.length === 0) {
|
|
4899
|
+
return { ok: false, reason: `verification command is malformed: ${rawCommand}` };
|
|
4900
|
+
}
|
|
4901
|
+
const [executable, ...args] = argv;
|
|
4661
4902
|
return await new Promise((resolve) => {
|
|
4662
|
-
const
|
|
4663
|
-
const child = spawn(shell, [...shellArgs, cmd], {
|
|
4903
|
+
const child = spawn(executable, args, {
|
|
4664
4904
|
cwd: info.cwd,
|
|
4665
4905
|
shell: false,
|
|
4666
4906
|
windowsHide: true,
|
|
@@ -4670,13 +4910,13 @@ function makeCommandVerifier(options = {}) {
|
|
|
4670
4910
|
const timer = setTimeout(() => {
|
|
4671
4911
|
timedOut = true;
|
|
4672
4912
|
child.kill();
|
|
4673
|
-
resolve({ ok: false, reason: `verification timed out: ${
|
|
4913
|
+
resolve({ ok: false, reason: `verification timed out: ${rawCommand}` });
|
|
4674
4914
|
}, timeoutMs);
|
|
4675
4915
|
child.on("exit", (code) => {
|
|
4676
4916
|
clearTimeout(timer);
|
|
4677
4917
|
if (timedOut) return;
|
|
4678
4918
|
resolve(
|
|
4679
|
-
code === 0 ? { ok: true } : { ok: false, reason: `verification failed (exit ${code}): ${
|
|
4919
|
+
code === 0 ? { ok: true } : { ok: false, reason: `verification failed (exit ${code}): ${rawCommand}` }
|
|
4680
4920
|
);
|
|
4681
4921
|
});
|
|
4682
4922
|
child.on("error", (err) => {
|
|
@@ -4841,13 +5081,13 @@ async function decomposeNonAtomicTasks(opts) {
|
|
|
4841
5081
|
}
|
|
4842
5082
|
|
|
4843
5083
|
// src/conflict-resolver.ts
|
|
4844
|
-
import { readFile as
|
|
4845
|
-
import { isAbsolute, join as
|
|
5084
|
+
import { readFile as readFile5, writeFile } from "node:fs/promises";
|
|
5085
|
+
import { isAbsolute, join as join6 } from "node:path";
|
|
4846
5086
|
import { readBundledInstructionText as readBundledInstructionText2, renderInstructionTemplate as renderInstructionTemplate2 } from "@wrongstack/core/utils";
|
|
4847
5087
|
var defaultFileIO = {
|
|
4848
|
-
read: (
|
|
4849
|
-
write: async (
|
|
4850
|
-
await writeFile(
|
|
5088
|
+
read: (path6) => readFile5(path6, "utf8"),
|
|
5089
|
+
write: async (path6, content) => {
|
|
5090
|
+
await writeFile(path6, content, "utf8");
|
|
4851
5091
|
}
|
|
4852
5092
|
};
|
|
4853
5093
|
var START = "<<<<<<<";
|
|
@@ -4891,7 +5131,7 @@ function makePreferSideConflictResolver(side, io = defaultFileIO) {
|
|
|
4891
5131
|
return async function conflictResolver(info) {
|
|
4892
5132
|
if (info.conflictFiles.length === 0) return false;
|
|
4893
5133
|
for (const rel of info.conflictFiles) {
|
|
4894
|
-
const abs = isAbsolute(rel) ? rel :
|
|
5134
|
+
const abs = isAbsolute(rel) ? rel : join6(info.cwd, rel);
|
|
4895
5135
|
let content;
|
|
4896
5136
|
try {
|
|
4897
5137
|
content = await io.read(abs);
|
|
@@ -4925,7 +5165,7 @@ function makeLlmConflictResolver(opts) {
|
|
|
4925
5165
|
return async function conflictResolver(info) {
|
|
4926
5166
|
if (info.conflictFiles.length === 0) return false;
|
|
4927
5167
|
for (const rel of info.conflictFiles) {
|
|
4928
|
-
const abs = isAbsolute(rel) ? rel :
|
|
5168
|
+
const abs = isAbsolute(rel) ? rel : join6(info.cwd, rel);
|
|
4929
5169
|
let content;
|
|
4930
5170
|
try {
|
|
4931
5171
|
content = await io.read(abs);
|
|
@@ -4993,6 +5233,7 @@ export {
|
|
|
4993
5233
|
decomposeNonAtomicTasks,
|
|
4994
5234
|
destroySddProject,
|
|
4995
5235
|
extractVerificationCommand,
|
|
5236
|
+
gatherProjectContext,
|
|
4996
5237
|
getTemplate,
|
|
4997
5238
|
hasConflictMarkers,
|
|
4998
5239
|
isExplanatoryText,
|
|
@@ -5013,6 +5254,7 @@ export {
|
|
|
5013
5254
|
shortIdMap,
|
|
5014
5255
|
splitGraphNode,
|
|
5015
5256
|
startSddRun,
|
|
5016
|
-
templateToMarkdown
|
|
5257
|
+
templateToMarkdown,
|
|
5258
|
+
tokenizeCommand
|
|
5017
5259
|
};
|
|
5018
5260
|
//# sourceMappingURL=index.js.map
|