@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.
@@ -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
- /** True when `text` still contains a git conflict marker line. */
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 path6 = await import("node:path");
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(path6.dirname(sessionPath), { recursive: true });
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 path6 = [];
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
- path6.unshift(current);
5091
+ path7.unshift(current);
5092
5092
  current = prev.get(current) ?? null;
5093
5093
  }
5094
- return path6;
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 join6 } from "node:path";
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: (path6) => readFile6(path6, "utf8"),
5863
- write: async (path6, content) => {
5864
- await writeFile(path6, content, "utf8");
5903
+ read: (path7) => readFile6(path7, "utf8"),
5904
+ write: async (path7, content) => {
5905
+ await writeFile(path7, content, "utf8");
5865
5906
  }
5866
5907
  };
5867
- var START = "<<<<<<<";
5868
- var BASE = "|||||||";
5869
- var SEP = "=======";
5870
- var END = ">>>>>>>";
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
- const marker = line.slice(0, 7);
5876
- if (state === "normal" && marker === START) {
5877
- state = "ours";
5878
- continue;
5879
- }
5880
- if (state !== "normal" && marker === BASE) {
5881
- state = "base";
5882
- continue;
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 === "normal") out.push(line);
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 : join6(info.cwd, 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 : join6(info.cwd, 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);
@@ -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.26",
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.26",
31
- "@wrongstack/kanban": "1.0.26",
32
- "@wrongstack/primitives": "1.0.26",
33
- "@wrongstack/requirement-intake": "1.0.26"
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",