@wrongstack/sdd 1.0.26 → 1.0.28
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/conflict-resolver.d.ts +7 -2
- package/dist/index.js +78 -42
- package/dist/sdd-supervisor.d.ts +2 -0
- package/package.json +5 -5
|
@@ -7,10 +7,15 @@ interface ConflictFileIO {
|
|
|
7
7
|
/**
|
|
8
8
|
* Resolve every standard git conflict hunk in `text` by keeping `side`. Handles
|
|
9
9
|
* both 2-way (`<<<<<<< / ======= / >>>>>>>`) and diff3 (`||||||| base`) markers.
|
|
10
|
-
* Returns the rewritten text (markers removed).
|
|
10
|
+
* Returns the rewritten text (markers removed). A hunk that never closes is not
|
|
11
|
+
* guessed at: the input comes back unchanged, so the marker check refuses it.
|
|
11
12
|
*/
|
|
12
13
|
export declare function resolveConflictText(text: string, side: ConflictSide): string;
|
|
13
|
-
/**
|
|
14
|
+
/**
|
|
15
|
+
* True when `text` still contains a git conflict marker line. A bare `=======`
|
|
16
|
+
* is not counted: without a `<<<<<<<` it cannot be told apart from a setext
|
|
17
|
+
* heading underline (core's WorktreeManager check draws the same line).
|
|
18
|
+
*/
|
|
14
19
|
export declare function hasConflictMarkers(text: string): boolean;
|
|
15
20
|
/**
|
|
16
21
|
* Build a `conflictResolver` that keeps `side` of every hunk in each conflicted
|
package/dist/index.js
CHANGED
|
@@ -2049,10 +2049,10 @@ var AISpecBuilder = class {
|
|
|
2049
2049
|
return;
|
|
2050
2050
|
}
|
|
2051
2051
|
const fsp7 = await import("node:fs/promises");
|
|
2052
|
-
const
|
|
2052
|
+
const path7 = await import("node:path");
|
|
2053
2053
|
const { atomicWrite: atomicWrite4 } = await import("@wrongstack/core/utils");
|
|
2054
2054
|
const sessionPath = expectDefined2(this.sessionPath);
|
|
2055
|
-
await fsp7.mkdir(
|
|
2055
|
+
await fsp7.mkdir(path7.dirname(sessionPath), { recursive: true });
|
|
2056
2056
|
await atomicWrite4(sessionPath, JSON.stringify(snapshot, null, 2));
|
|
2057
2057
|
} catch (error) {
|
|
2058
2058
|
console.warn(
|
|
@@ -5083,15 +5083,15 @@ function computeCriticalPath(graph, _topoOrder, blockedByMap) {
|
|
|
5083
5083
|
maxId = id;
|
|
5084
5084
|
}
|
|
5085
5085
|
}
|
|
5086
|
-
const
|
|
5086
|
+
const path7 = [];
|
|
5087
5087
|
let current = maxId;
|
|
5088
5088
|
const visited = /* @__PURE__ */ new Set();
|
|
5089
5089
|
while (current && !visited.has(current)) {
|
|
5090
5090
|
visited.add(current);
|
|
5091
|
-
|
|
5091
|
+
path7.unshift(current);
|
|
5092
5092
|
current = prev.get(current) ?? null;
|
|
5093
5093
|
}
|
|
5094
|
-
return
|
|
5094
|
+
return path7;
|
|
5095
5095
|
}
|
|
5096
5096
|
function computeParallelGroups(graph, blockedByMap) {
|
|
5097
5097
|
const groups = [];
|
|
@@ -5312,6 +5312,14 @@ var SpecVersioning = class {
|
|
|
5312
5312
|
};
|
|
5313
5313
|
|
|
5314
5314
|
// src/auto-executor.ts
|
|
5315
|
+
function asDependentFirstGraph(graph) {
|
|
5316
|
+
return {
|
|
5317
|
+
...graph,
|
|
5318
|
+
edges: graph.edges.map(
|
|
5319
|
+
(edge) => edge.type === "depends_on" ? { ...edge, from: edge.to, to: edge.from } : edge
|
|
5320
|
+
)
|
|
5321
|
+
};
|
|
5322
|
+
}
|
|
5315
5323
|
var AutoExecutor = class {
|
|
5316
5324
|
opts;
|
|
5317
5325
|
stopped = false;
|
|
@@ -5328,7 +5336,7 @@ var AutoExecutor = class {
|
|
|
5328
5336
|
this.stopped = false;
|
|
5329
5337
|
this.retryMap.clear();
|
|
5330
5338
|
const startTime = Date.now();
|
|
5331
|
-
const critical = analyzeCriticalPath(graph);
|
|
5339
|
+
const critical = analyzeCriticalPath(asDependentFirstGraph(graph));
|
|
5332
5340
|
let completed = 0;
|
|
5333
5341
|
let failed = 0;
|
|
5334
5342
|
const skipped = 0;
|
|
@@ -5501,6 +5509,7 @@ var SddSupervisor = class {
|
|
|
5501
5509
|
const canSplit = Boolean(this.opts.generateSubtasks);
|
|
5502
5510
|
const decision = await this.opts.brain.decide({
|
|
5503
5511
|
id: `sdd-supervisor-${task.id}-${attempts}`,
|
|
5512
|
+
sessionId: this.opts.sessionId,
|
|
5504
5513
|
source: "system",
|
|
5505
5514
|
question: `SDD task "${task.title}" exhausted its retries. How should the run proceed?`,
|
|
5506
5515
|
context: `Error: ${error}
|
|
@@ -5539,6 +5548,28 @@ Supervisor rescues already used: ${attempts}`,
|
|
|
5539
5548
|
|
|
5540
5549
|
// src/verify-task.ts
|
|
5541
5550
|
import { spawn } from "node:child_process";
|
|
5551
|
+
import { accessSync } from "node:fs";
|
|
5552
|
+
import * as path6 from "node:path";
|
|
5553
|
+
import { buildWin32CmdShimInvocation } from "@wrongstack/core/utils";
|
|
5554
|
+
function win32BatchTarget(executable) {
|
|
5555
|
+
if (/\.(?:cmd|bat)$/i.test(executable)) return executable;
|
|
5556
|
+
if (/[\\/]/.test(executable) || path6.extname(executable)) return void 0;
|
|
5557
|
+
const exts = (process.env["PATHEXT"] ?? ".COM;.EXE;.BAT;.CMD").toLowerCase().split(";");
|
|
5558
|
+
for (const dir of (process.env["PATH"] ?? "").split(path6.delimiter)) {
|
|
5559
|
+
if (!dir) continue;
|
|
5560
|
+
for (const ext of exts) {
|
|
5561
|
+
if (!ext) continue;
|
|
5562
|
+
const full = path6.join(dir, `${executable}${ext}`);
|
|
5563
|
+
try {
|
|
5564
|
+
accessSync(full);
|
|
5565
|
+
} catch {
|
|
5566
|
+
continue;
|
|
5567
|
+
}
|
|
5568
|
+
return ext === ".cmd" || ext === ".bat" ? full : void 0;
|
|
5569
|
+
}
|
|
5570
|
+
}
|
|
5571
|
+
return void 0;
|
|
5572
|
+
}
|
|
5542
5573
|
function tokenizeCommand(command) {
|
|
5543
5574
|
const trimmed = command.trim();
|
|
5544
5575
|
if (!trimmed) return void 0;
|
|
@@ -5675,12 +5706,22 @@ function makeCommandVerifier(options = {}) {
|
|
|
5675
5706
|
return { ok: false, reason: `verification command is malformed: ${rawCommand}` };
|
|
5676
5707
|
}
|
|
5677
5708
|
const [executable, ...args] = argv;
|
|
5709
|
+
const batch = process.platform === "win32" ? win32BatchTarget(executable) : void 0;
|
|
5710
|
+
let shim;
|
|
5711
|
+
if (batch) {
|
|
5712
|
+
try {
|
|
5713
|
+
shim = buildWin32CmdShimInvocation(batch, args);
|
|
5714
|
+
} catch (err) {
|
|
5715
|
+
return { ok: false, reason: `verification command refused: ${String(err)}` };
|
|
5716
|
+
}
|
|
5717
|
+
}
|
|
5678
5718
|
return await new Promise((resolve3) => {
|
|
5679
|
-
const child = spawn(executable, args, {
|
|
5719
|
+
const child = spawn(shim?.command ?? executable, shim?.args ?? args, {
|
|
5680
5720
|
cwd: info.cwd,
|
|
5681
5721
|
shell: false,
|
|
5682
5722
|
windowsHide: true,
|
|
5683
|
-
stdio: "ignore"
|
|
5723
|
+
stdio: "ignore",
|
|
5724
|
+
...shim ? { windowsVerbatimArguments: shim.windowsVerbatimArguments } : {}
|
|
5684
5725
|
});
|
|
5685
5726
|
let timedOut = false;
|
|
5686
5727
|
const timer = setTimeout(() => {
|
|
@@ -5856,56 +5897,54 @@ async function decomposeNonAtomicTasks(opts) {
|
|
|
5856
5897
|
|
|
5857
5898
|
// src/conflict-resolver.ts
|
|
5858
5899
|
import { readFile as readFile6, writeFile } from "node:fs/promises";
|
|
5859
|
-
import { isAbsolute as isAbsolute3, join as
|
|
5900
|
+
import { isAbsolute as isAbsolute3, join as join7 } from "node:path";
|
|
5860
5901
|
import { readBundledInstructionText as readBundledInstructionText2, renderInstructionTemplate as renderInstructionTemplate2 } from "@wrongstack/core/utils";
|
|
5861
5902
|
var defaultFileIO = {
|
|
5862
|
-
read: (
|
|
5863
|
-
write: async (
|
|
5864
|
-
await writeFile(
|
|
5903
|
+
read: (path7) => readFile6(path7, "utf8"),
|
|
5904
|
+
write: async (path7, content) => {
|
|
5905
|
+
await writeFile(path7, content, "utf8");
|
|
5865
5906
|
}
|
|
5866
5907
|
};
|
|
5867
|
-
var
|
|
5868
|
-
var
|
|
5869
|
-
var
|
|
5870
|
-
|
|
5908
|
+
var START_LINE = /^(<{7,})(?: |\r?$)/;
|
|
5909
|
+
var LABELLED_MARKER_LINE = /^(?:<{7,}|\|{7,}|>{7,})(?: |\r?$)/;
|
|
5910
|
+
var ANY_MARKER_LINE = /^(?:<{7,}|\|{7,}|>{7,})(?: |\r?$)|^={7,}\r?$/;
|
|
5911
|
+
function isMarker(line, char, size) {
|
|
5912
|
+
for (let i = 0; i < size; i++) if (line[i] !== char) return false;
|
|
5913
|
+
const rest = line.slice(size).replace(/\r$/, "");
|
|
5914
|
+
return char === "=" ? rest === "" : rest === "" || rest.startsWith(" ");
|
|
5915
|
+
}
|
|
5871
5916
|
function resolveConflictText(text, side) {
|
|
5872
5917
|
const out = [];
|
|
5873
5918
|
let state = "normal";
|
|
5919
|
+
let size = 0;
|
|
5874
5920
|
for (const line of text.split("\n")) {
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
}
|
|
5884
|
-
if (state !== "normal" && marker === SEP) {
|
|
5885
|
-
state = "theirs";
|
|
5886
|
-
continue;
|
|
5887
|
-
}
|
|
5888
|
-
if (state !== "normal" && marker === END) {
|
|
5889
|
-
state = "normal";
|
|
5921
|
+
if (state === "normal") {
|
|
5922
|
+
const start = START_LINE.exec(line);
|
|
5923
|
+
if (start) {
|
|
5924
|
+
size = start[1].length;
|
|
5925
|
+
state = "ours";
|
|
5926
|
+
} else {
|
|
5927
|
+
out.push(line);
|
|
5928
|
+
}
|
|
5890
5929
|
continue;
|
|
5891
5930
|
}
|
|
5892
|
-
if (state
|
|
5931
|
+
if (isMarker(line, "|", size)) state = "base";
|
|
5932
|
+
else if (isMarker(line, "=", size)) state = "theirs";
|
|
5933
|
+
else if (isMarker(line, ">", size)) state = "normal";
|
|
5893
5934
|
else if (state === "ours" && side === "base") out.push(line);
|
|
5894
5935
|
else if (state === "theirs" && side === "incoming") out.push(line);
|
|
5895
5936
|
}
|
|
5937
|
+
if (state !== "normal") return text;
|
|
5896
5938
|
return out.join("\n");
|
|
5897
5939
|
}
|
|
5898
5940
|
function hasConflictMarkers(text) {
|
|
5899
|
-
return text.split("\n").some((l) =>
|
|
5900
|
-
const m = l.slice(0, 7);
|
|
5901
|
-
return m === START || m === SEP || m === END || m === BASE;
|
|
5902
|
-
});
|
|
5941
|
+
return text.split("\n").some((l) => LABELLED_MARKER_LINE.test(l));
|
|
5903
5942
|
}
|
|
5904
5943
|
function makePreferSideConflictResolver(side, io = defaultFileIO) {
|
|
5905
5944
|
return async function conflictResolver(info) {
|
|
5906
5945
|
if (info.conflictFiles.length === 0) return false;
|
|
5907
5946
|
for (const rel of info.conflictFiles) {
|
|
5908
|
-
const abs = isAbsolute3(rel) ? rel :
|
|
5947
|
+
const abs = isAbsolute3(rel) ? rel : join7(info.cwd, rel);
|
|
5909
5948
|
let content;
|
|
5910
5949
|
try {
|
|
5911
5950
|
content = await io.read(abs);
|
|
@@ -5928,10 +5967,7 @@ function unfence(text) {
|
|
|
5928
5967
|
return m?.[1] !== void 0 ? m[1] : text.trim();
|
|
5929
5968
|
}
|
|
5930
5969
|
function nonMarkerLineCount(text) {
|
|
5931
|
-
return text.split("\n").filter((l) =>
|
|
5932
|
-
const m = l.slice(0, 7);
|
|
5933
|
-
return m !== START && m !== SEP && m !== END && m !== BASE;
|
|
5934
|
-
}).length;
|
|
5970
|
+
return text.split("\n").filter((l) => !ANY_MARKER_LINE.test(l)).length;
|
|
5935
5971
|
}
|
|
5936
5972
|
function makeLlmConflictResolver(opts) {
|
|
5937
5973
|
const minFraction = opts.minRetainedFraction ?? 0.5;
|
|
@@ -5939,7 +5975,7 @@ function makeLlmConflictResolver(opts) {
|
|
|
5939
5975
|
return async function conflictResolver(info) {
|
|
5940
5976
|
if (info.conflictFiles.length === 0) return false;
|
|
5941
5977
|
for (const rel of info.conflictFiles) {
|
|
5942
|
-
const abs = isAbsolute3(rel) ? rel :
|
|
5978
|
+
const abs = isAbsolute3(rel) ? rel : join7(info.cwd, rel);
|
|
5943
5979
|
let content;
|
|
5944
5980
|
try {
|
|
5945
5981
|
content = await io.read(abs);
|
package/dist/sdd-supervisor.d.ts
CHANGED
|
@@ -4,6 +4,8 @@ import type { SddSubtaskSpec, SddSupervisorVerdict } from './sdd-parallel-run.js
|
|
|
4
4
|
export interface SddSupervisorOptions {
|
|
5
5
|
/** Decision authority (policy/LLM/human). Reuse the session's TOKENS.BrainArbiter. */
|
|
6
6
|
brain: BrainArbiter;
|
|
7
|
+
/** Session that started this run, retained when another tab becomes foreground. */
|
|
8
|
+
sessionId?: string | undefined;
|
|
7
9
|
/**
|
|
8
10
|
* Models to rotate through on a `reassign` verdict (e.g. the run's fallback
|
|
9
11
|
* chain). Omit to drop the reassign option entirely.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wrongstack/sdd",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.28",
|
|
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.28",
|
|
31
|
+
"@wrongstack/kanban": "1.0.28",
|
|
32
|
+
"@wrongstack/primitives": "1.0.28",
|
|
33
|
+
"@wrongstack/requirement-intake": "1.0.28"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"@types/node": "^26.5.1",
|