wildcrane-runner 0.6.13 → 0.6.14

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.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // ai4kanban 0.11.10 — built by runner/scripts/build.mjs. Do not edit.
2
+ // ai4kanban 0.11.11 — built by runner/scripts/build.mjs. Do not edit.
3
3
  import { createRequire as __ai4kanbanRequire } from 'node:module'
4
4
  const require = __ai4kanbanRequire(import.meta.url)
5
5
  var __create = Object.create;
@@ -1167,8 +1167,8 @@ var require_command = __commonJS({
1167
1167
  "../cli/node_modules/commander/lib/command.js"(exports) {
1168
1168
  var EventEmitter = __require("node:events").EventEmitter;
1169
1169
  var childProcess = __require("node:child_process");
1170
- var path85 = __require("node:path");
1171
- var fs86 = __require("node:fs");
1170
+ var path87 = __require("node:path");
1171
+ var fs87 = __require("node:fs");
1172
1172
  var process2 = __require("node:process");
1173
1173
  var { Argument: Argument2, humanReadableArgName } = require_argument();
1174
1174
  var { CommanderError: CommanderError2 } = require_error();
@@ -2149,7 +2149,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2149
2149
  * @param {string} subcommandName
2150
2150
  */
2151
2151
  _checkForMissingExecutable(executableFile, executableDir, subcommandName) {
2152
- if (fs86.existsSync(executableFile)) return;
2152
+ if (fs87.existsSync(executableFile)) return;
2153
2153
  const executableDirMessage = executableDir ? `searched for local subcommand relative to directory '${executableDir}'` : "no directory for search for local subcommand, use .executableDir() to supply a custom directory";
2154
2154
  const executableMissing = `'${executableFile}' does not exist
2155
2155
  - if '${subcommandName}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
@@ -2167,11 +2167,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
2167
2167
  let launchWithNode = false;
2168
2168
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
2169
2169
  function findFile(baseDir, baseName) {
2170
- const localBin = path85.resolve(baseDir, baseName);
2171
- if (fs86.existsSync(localBin)) return localBin;
2172
- if (sourceExt.includes(path85.extname(baseName))) return void 0;
2170
+ const localBin = path87.resolve(baseDir, baseName);
2171
+ if (fs87.existsSync(localBin)) return localBin;
2172
+ if (sourceExt.includes(path87.extname(baseName))) return void 0;
2173
2173
  const foundExt = sourceExt.find(
2174
- (ext) => fs86.existsSync(`${localBin}${ext}`)
2174
+ (ext) => fs87.existsSync(`${localBin}${ext}`)
2175
2175
  );
2176
2176
  if (foundExt) return `${localBin}${foundExt}`;
2177
2177
  return void 0;
@@ -2183,21 +2183,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
2183
2183
  if (this._scriptPath) {
2184
2184
  let resolvedScriptPath;
2185
2185
  try {
2186
- resolvedScriptPath = fs86.realpathSync(this._scriptPath);
2186
+ resolvedScriptPath = fs87.realpathSync(this._scriptPath);
2187
2187
  } catch {
2188
2188
  resolvedScriptPath = this._scriptPath;
2189
2189
  }
2190
- executableDir = path85.resolve(
2191
- path85.dirname(resolvedScriptPath),
2190
+ executableDir = path87.resolve(
2191
+ path87.dirname(resolvedScriptPath),
2192
2192
  executableDir
2193
2193
  );
2194
2194
  }
2195
2195
  if (executableDir) {
2196
2196
  let localFile = findFile(executableDir, executableFile);
2197
2197
  if (!localFile && !subcommand._executableFile && this._scriptPath) {
2198
- const legacyName = path85.basename(
2198
+ const legacyName = path87.basename(
2199
2199
  this._scriptPath,
2200
- path85.extname(this._scriptPath)
2200
+ path87.extname(this._scriptPath)
2201
2201
  );
2202
2202
  if (legacyName !== this._name) {
2203
2203
  localFile = findFile(
@@ -2208,7 +2208,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2208
2208
  }
2209
2209
  executableFile = localFile || executableFile;
2210
2210
  }
2211
- launchWithNode = sourceExt.includes(path85.extname(executableFile));
2211
+ launchWithNode = sourceExt.includes(path87.extname(executableFile));
2212
2212
  let proc;
2213
2213
  if (process2.platform !== "win32") {
2214
2214
  if (launchWithNode) {
@@ -3055,7 +3055,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
3055
3055
  * @return {Command}
3056
3056
  */
3057
3057
  nameFromFilename(filename) {
3058
- this._name = path85.basename(filename, path85.extname(filename));
3058
+ this._name = path87.basename(filename, path87.extname(filename));
3059
3059
  return this;
3060
3060
  }
3061
3061
  /**
@@ -3069,9 +3069,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
3069
3069
  * @param {string} [path]
3070
3070
  * @return {(string|null|Command)}
3071
3071
  */
3072
- executableDir(path86) {
3073
- if (path86 === void 0) return this._executableDir;
3074
- this._executableDir = path86;
3072
+ executableDir(path88) {
3073
+ if (path88 === void 0) return this._executableDir;
3074
+ this._executableDir = path88;
3075
3075
  return this;
3076
3076
  }
3077
3077
  /**
@@ -5477,7 +5477,7 @@ var init_package = __esm({
5477
5477
  "../cli/package.json"() {
5478
5478
  package_default = {
5479
5479
  name: "wildcrane",
5480
- version: "0.11.10",
5480
+ version: "0.11.11",
5481
5481
  akbContract: 5,
5482
5482
  description: "WildCrane's `akb` command \u2014 scaffold the Markdown board under docs/kanban/, put an agent on a card, and carry the flows the agent works by.",
5483
5483
  keywords: [
@@ -8316,10 +8316,10 @@ function findRollout(threadId) {
8316
8316
  }
8317
8317
  for (const name of names4) {
8318
8318
  if (!name.startsWith("rollout-") || !name.endsWith(tail2)) continue;
8319
- const path85 = join(dir, name);
8319
+ const path87 = join(dir, name);
8320
8320
  try {
8321
- const at = statSync(path85).mtimeMs;
8322
- if (!newest2 || at > newest2.at) newest2 = { path: path85, at };
8321
+ const at = statSync(path87).mtimeMs;
8322
+ if (!newest2 || at > newest2.at) newest2 = { path: path87, at };
8323
8323
  } catch {
8324
8324
  }
8325
8325
  }
@@ -8327,11 +8327,11 @@ function findRollout(threadId) {
8327
8327
  return newest2?.path;
8328
8328
  }
8329
8329
  function readCodexRunFacts(threadId) {
8330
- const path85 = findRollout(threadId);
8331
- if (!path85) return void 0;
8330
+ const path87 = findRollout(threadId);
8331
+ if (!path87) return void 0;
8332
8332
  let raw;
8333
8333
  try {
8334
- raw = readFileSync(path85, "utf8");
8334
+ raw = readFileSync(path87, "utf8");
8335
8335
  } catch {
8336
8336
  return void 0;
8337
8337
  }
@@ -8459,8 +8459,8 @@ function renderCompleted(item) {
8459
8459
  const changes = Array.isArray(item.changes) ? item.changes : [];
8460
8460
  return changes.map((raw) => {
8461
8461
  const change2 = obj(raw);
8462
- const path85 = str2(change2.path);
8463
- return path85 ? `\u23FA ${str2(change2.kind) || "change"}(${path85})
8462
+ const path87 = str2(change2.path);
8463
+ return path87 ? `\u23FA ${str2(change2.kind) || "change"}(${path87})
8464
8464
  ` : "";
8465
8465
  }).join("");
8466
8466
  }
@@ -8719,10 +8719,10 @@ function readKimiRunFacts(sessionId) {
8719
8719
  const total = { input: 0, cacheCreation: 0, cacheRead: 0, output: 0 };
8720
8720
  let model;
8721
8721
  let holding = 0;
8722
- for (const { path: path85, main } of wireFiles(entry.sessionDir)) {
8722
+ for (const { path: path87, main } of wireFiles(entry.sessionDir)) {
8723
8723
  let raw;
8724
8724
  try {
8725
- raw = readFileSync2(path85, "utf8");
8725
+ raw = readFileSync2(path87, "utf8");
8726
8726
  } catch {
8727
8727
  continue;
8728
8728
  }
@@ -9003,8 +9003,8 @@ var init_tail = __esm({
9003
9003
  import fs24 from "node:fs";
9004
9004
  function whereHint(update2, title) {
9005
9005
  const locations = Array.isArray(update2.locations) ? update2.locations : [];
9006
- const path85 = str2(obj(locations[0]).path);
9007
- return path85 && !title.includes(path85) ? hint(path85) : "";
9006
+ const path87 = str2(obj(locations[0]).path);
9007
+ return path87 && !title.includes(path87) ? hint(path87) : "";
9008
9008
  }
9009
9009
  function saidBy(update2) {
9010
9010
  const content = Array.isArray(update2.content) ? update2.content : [];
@@ -9947,27 +9947,40 @@ import crypto3 from "node:crypto";
9947
9947
  import fs25 from "node:fs";
9948
9948
  import os4 from "node:os";
9949
9949
  import path28 from "node:path";
9950
+ function spawnGit(args, options) {
9951
+ const out = spawnSync3("git", args, {
9952
+ ...options,
9953
+ encoding: "utf8",
9954
+ stdio: ["ignore", "pipe", "pipe"],
9955
+ timeout: gitLimits.timeoutMs,
9956
+ killSignal: "SIGKILL"
9957
+ });
9958
+ if (out.error?.code === "ETIMEDOUT") throw new GitTimeoutError(`Git ${args[0]} timed out after ${gitLimits.timeoutMs / 1e3}s; the command was killed.`);
9959
+ return out;
9960
+ }
9950
9961
  function git(args, cwd = REPO_ROOT) {
9951
9962
  try {
9952
- const out = spawnSync3("git", args, { cwd, encoding: "utf8", maxBuffer: MAX_OUTPUT, windowsHide: true });
9963
+ const out = spawnGit(args, { cwd, encoding: "utf8", maxBuffer: MAX_OUTPUT, windowsHide: true });
9953
9964
  if (out.error || out.status !== 0) return null;
9954
9965
  return out.stdout;
9955
- } catch {
9966
+ } catch (error) {
9967
+ if (error instanceof GitTimeoutError) throw error;
9956
9968
  return null;
9957
9969
  }
9958
9970
  }
9959
9971
  function gitDiff(args, cwd = REPO_ROOT) {
9960
9972
  try {
9961
- const out = spawnSync3("git", args, { cwd, encoding: "utf8", maxBuffer: MAX_OUTPUT, windowsHide: true });
9973
+ const out = spawnGit(args, { cwd, encoding: "utf8", maxBuffer: MAX_OUTPUT, windowsHide: true });
9962
9974
  if (out.error || out.status !== 0 && out.status !== 1) return null;
9963
9975
  return out.stdout;
9964
- } catch {
9976
+ } catch (error) {
9977
+ if (error instanceof GitTimeoutError) throw error;
9965
9978
  return null;
9966
9979
  }
9967
9980
  }
9968
9981
  function tryGit(args, cwd = REPO_ROOT, env2) {
9969
9982
  try {
9970
- const out = spawnSync3("git", args, {
9983
+ const out = spawnGit(args, {
9971
9984
  cwd,
9972
9985
  encoding: "utf8",
9973
9986
  maxBuffer: MAX_OUTPUT,
@@ -9978,6 +9991,7 @@ function tryGit(args, cwd = REPO_ROOT, env2) {
9978
9991
  if (out.status !== 0) return { ok: false, why: (out.stderr || out.stdout || "").trim() };
9979
9992
  return { ok: true };
9980
9993
  } catch (e) {
9994
+ if (e instanceof GitTimeoutError) throw e;
9981
9995
  return { ok: false, why: e instanceof Error ? e.message : String(e) };
9982
9996
  }
9983
9997
  }
@@ -9986,7 +10000,7 @@ function boardManagedPaths() {
9986
10000
  }
9987
10001
  function outsideBoardForAdd(cwd = REPO_ROOT) {
9988
10002
  const kept2 = boardManagedPaths().filter(
9989
- (p) => spawnSync3("git", ["check-ignore", "-q", p], { cwd, windowsHide: true }).status !== 0
10003
+ (p) => spawnGit(["check-ignore", "-q", p], { cwd, windowsHide: true }).status !== 0
9990
10004
  );
9991
10005
  return ["--", ".", ...kept2.map((p) => `:(exclude)${p}`)];
9992
10006
  }
@@ -10106,10 +10120,10 @@ function treeMark(base, cwd = REPO_ROOT) {
10106
10120
  const index = path28.join(ensureAkbDir(), `index-${process.pid}-${Date.now()}`);
10107
10121
  try {
10108
10122
  const env2 = { ...process.env, GIT_INDEX_FILE: index };
10109
- const run2 = (args) => spawnSync3("git", args, { cwd, encoding: "utf8", maxBuffer: MAX_OUTPUT, windowsHide: true, env: env2 }).status === 0;
10123
+ const run2 = (args) => spawnGit(args, { cwd, encoding: "utf8", maxBuffer: MAX_OUTPUT, windowsHide: true, env: env2 }).status === 0;
10110
10124
  if (!run2(["read-tree", base])) return null;
10111
10125
  if (!run2(["add", "-A", ...outsideBoardForAdd(cwd)])) return null;
10112
- const tree = spawnSync3("git", ["write-tree"], {
10126
+ const tree = spawnGit(["write-tree"], {
10113
10127
  cwd,
10114
10128
  encoding: "utf8",
10115
10129
  maxBuffer: MAX_OUTPUT,
@@ -10117,7 +10131,8 @@ function treeMark(base, cwd = REPO_ROOT) {
10117
10131
  env: env2
10118
10132
  });
10119
10133
  return tree.status === 0 ? tree.stdout.trim() || null : null;
10120
- } catch {
10134
+ } catch (error) {
10135
+ if (error instanceof GitTimeoutError) throw error;
10121
10136
  return null;
10122
10137
  } finally {
10123
10138
  try {
@@ -10128,23 +10143,26 @@ function treeMark(base, cwd = REPO_ROOT) {
10128
10143
  }
10129
10144
  function reverseApplies(patch2, ref, cwd = REPO_ROOT) {
10130
10145
  if (!patch2.trim()) return false;
10131
- const index = path28.join(os4.tmpdir(), `akb-apply-${process.pid}-${Date.now()}.index`);
10146
+ const folder = fs25.mkdtempSync(path28.join(os4.tmpdir(), `akb-apply-${process.pid}-`));
10147
+ const index = path28.join(folder, "index");
10148
+ const patchFile = path28.join(folder, "patch");
10132
10149
  const env2 = { GIT_INDEX_FILE: index };
10133
10150
  try {
10151
+ fs25.writeFileSync(patchFile, patch2);
10134
10152
  if (!tryGit(["read-tree", ref], cwd, env2).ok) return false;
10135
- const out = spawnSync3("git", ["apply", "--cached", "--reverse", "--check", "-"], {
10153
+ const out = spawnGit(["apply", "--cached", "--reverse", "--check", patchFile], {
10136
10154
  cwd,
10137
- input: patch2,
10138
10155
  encoding: "utf8",
10139
10156
  maxBuffer: MAX_OUTPUT,
10140
10157
  windowsHide: true,
10141
10158
  env: { ...process.env, ...env2 }
10142
10159
  });
10143
10160
  return !out.error && out.status === 0;
10144
- } catch {
10161
+ } catch (error) {
10162
+ if (error instanceof GitTimeoutError) throw error;
10145
10163
  return false;
10146
10164
  } finally {
10147
- fs25.rmSync(index, { force: true });
10165
+ fs25.rmSync(folder, { recursive: true, force: true });
10148
10166
  }
10149
10167
  }
10150
10168
  function squashOnto(dir, base, message) {
@@ -10226,12 +10244,15 @@ function moveBranchRef(branch, commit, expect) {
10226
10244
  if (branchTip(branch) !== expect) return { moved: true };
10227
10245
  return { ok: false, error: `couldn't move ${branch} to ${commit.slice(0, 12)}: ${done2.why ?? "git refused"}` };
10228
10246
  }
10229
- var MAX_OUTPUT, scripted, posix, outsideBoard, deliveryBranch, deliveryWorktree, worktreeDir, inGitRepo, headCommit, worktreeExists, branchExists, pruneWorktreeMetadata, branchTip, isAncestor, changedPaths, branchPatch, lastCommitTouching, continueRebase, abortRebase, conflictedPaths, headingKind;
10247
+ var MAX_OUTPUT, gitLimits, GitTimeoutError, scripted, posix, outsideBoard, deliveryBranch, deliveryWorktree, worktreeDir, inGitRepo, headCommit, worktreeExists, branchExists, pruneWorktreeMetadata, branchTip, isAncestor, changedPaths, branchPatch, lastCommitTouching, continueRebase, abortRebase, conflictedPaths, headingKind;
10230
10248
  var init_worktree = __esm({
10231
10249
  "../cli/src/lib/agent/worktree.ts"() {
10232
10250
  "use strict";
10233
10251
  init_paths();
10234
10252
  MAX_OUTPUT = 64 * 1024 * 1024;
10253
+ gitLimits = { timeoutMs: 3e4 };
10254
+ GitTimeoutError = class extends Error {
10255
+ };
10235
10256
  scripted = (args, cwd) => tryGit(["-c", "core.hooksPath=/dev/null", ...args], cwd, { GIT_EDITOR: "true", GIT_SEQUENCE_EDITOR: "true" });
10236
10257
  posix = (p) => p.split(path28.sep).join("/");
10237
10258
  outsideBoard = () => [
@@ -10248,7 +10269,7 @@ var init_worktree = __esm({
10248
10269
  branchExists = (branch) => !!branch && git(["rev-parse", "--verify", "--quiet", `refs/heads/${branch}`]) !== null;
10249
10270
  pruneWorktreeMetadata = () => void git(["worktree", "prune"]);
10250
10271
  branchTip = (branch) => git(["rev-parse", "--verify", "--quiet", `refs/heads/${branch}`])?.trim() || null;
10251
- isAncestor = (older, newer, cwd = REPO_ROOT) => spawnSync3("git", ["merge-base", "--is-ancestor", older, newer], { cwd, windowsHide: true }).status === 0;
10272
+ isAncestor = (older, newer, cwd = REPO_ROOT) => spawnGit(["merge-base", "--is-ancestor", older, newer], { cwd, windowsHide: true }).status === 0;
10252
10273
  changedPaths = (base, branch, cwd = REPO_ROOT) => {
10253
10274
  const out = git(["diff", "--name-only", base, branch, ...outsideBoard()], cwd);
10254
10275
  return out === null ? null : out.split("\n").filter(Boolean);
@@ -15546,6 +15567,252 @@ var init_complete = __esm({
15546
15567
  }
15547
15568
  });
15548
15569
 
15570
+ // ../cli/src/lib/agent/stop.ts
15571
+ import { spawnSync as spawnSync4 } from "node:child_process";
15572
+ import { randomUUID as randomUUID5 } from "node:crypto";
15573
+ import fs39 from "node:fs";
15574
+ import os6 from "node:os";
15575
+ import path43 from "node:path";
15576
+ function markedInProc(entry, strict = false) {
15577
+ const pids = [];
15578
+ for (const name of fs39.readdirSync("/proc")) {
15579
+ if (!/^\d+$/.test(name)) continue;
15580
+ try {
15581
+ if (fs39.readFileSync(`/proc/${name}/environ`, "utf8").split("\0").includes(entry)) pids.push(Number(name));
15582
+ } catch (error) {
15583
+ if (strict && !["ENOENT", "ESRCH"].includes(error.code ?? "")) {
15584
+ const stat = fs39.statSync(`/proc/${name}`, { throwIfNoEntry: false });
15585
+ if (stat?.uid === process.getuid?.()) throw error;
15586
+ }
15587
+ }
15588
+ }
15589
+ return pids;
15590
+ }
15591
+ function markedInPs(entry, roots) {
15592
+ const ps = spawnSync4("ps", ["-A", "-E", "-ww", "-o", "pid=,ppid=,command="], {
15593
+ encoding: "utf8",
15594
+ maxBuffer: 64 * 1024 * 1024,
15595
+ timeout: 5e3
15596
+ });
15597
+ if (ps.status !== 0 || !ps.stdout) throw new Error("\u65E0\u6CD5\u67E5\u8BE2\u8FDB\u7A0B\u6811\u3002");
15598
+ const children = /* @__PURE__ */ new Map();
15599
+ const listed2 = /* @__PURE__ */ new Set();
15600
+ const found2 = /* @__PURE__ */ new Set();
15601
+ for (const line of ps.stdout.split("\n")) {
15602
+ const m = /^\s*(\d+)\s+(\d+)\s(.*)$/.exec(line);
15603
+ if (!m) continue;
15604
+ const pid = Number(m[1]);
15605
+ const ppid = Number(m[2]);
15606
+ listed2.add(pid);
15607
+ children.set(ppid, [...children.get(ppid) ?? [], pid]);
15608
+ if (`${m[3]} `.includes(` ${entry} `)) found2.add(pid);
15609
+ }
15610
+ const queue = [...found2, ...roots.filter((pid) => listed2.has(pid))];
15611
+ for (const pid of queue) {
15612
+ found2.add(pid);
15613
+ for (const child of children.get(pid) ?? []) if (!found2.has(child)) queue.push(child);
15614
+ }
15615
+ return [...found2];
15616
+ }
15617
+ function marked(mark, roots = []) {
15618
+ if (WINDOWS3 || !mark.value) return [];
15619
+ const entry = `${mark.name}=${mark.value}`;
15620
+ try {
15621
+ const pids = process.platform === "linux" ? markedInProc(entry) : markedInPs(entry, roots);
15622
+ return pids.filter((pid) => pid !== process.pid);
15623
+ } catch {
15624
+ return [];
15625
+ }
15626
+ }
15627
+ function signal(pids, sig) {
15628
+ for (const pid of pids) {
15629
+ try {
15630
+ process.kill(pid, sig);
15631
+ } catch {
15632
+ }
15633
+ }
15634
+ }
15635
+ function stopWatchers(sessions, onlyPid, stillOwns = () => true, startedBefore) {
15636
+ if (sessions ? !sessions.length : onlyPid === void 0) return [];
15637
+ const command = WINDOWS3 ? "powershell.exe" : "ps";
15638
+ const args = WINDOWS3 ? ["-NoProfile", "-NonInteractive", "-Command", "Get-CimInstance Win32_Process | Select-Object ProcessId,CommandLine,@{Name='StartedAt';Expression={([DateTimeOffset]$_.CreationDate).ToUnixTimeMilliseconds()}} | ConvertTo-Json -Compress"] : ["-A", "-ww", "-o", "pid=,lstart=,command="];
15639
+ const out = spawnSync4(command, args, { encoding: "utf8", timeout: 5e3, maxBuffer: 16 * 1024 * 1024, windowsHide: true, env: { ...process.env, LC_ALL: "C" } });
15640
+ if (out.status !== 0) return [];
15641
+ let procs;
15642
+ try {
15643
+ procs = WINDOWS3 ? [].concat(JSON.parse(out.stdout)).map((p) => ({ pid: p.ProcessId, command: p.CommandLine ?? "", startedAt: p.StartedAt })) : out.stdout.split("\n").flatMap((line) => {
15644
+ const m = /^\s*(\d+)\s+(\S+\s+\S+\s+\d+\s+\d+:\d+:\d+\s+\d{4})\s+(.*)$/.exec(line);
15645
+ return m ? [{ pid: Number(m[1]), command: m[3], startedAt: Date.parse(m[2]) }] : [];
15646
+ });
15647
+ } catch {
15648
+ return [];
15649
+ }
15650
+ const known2 = sessions && new Set(sessions);
15651
+ const stopped2 = [];
15652
+ for (const proc of procs) {
15653
+ if (proc.pid === process.pid || onlyPid !== void 0 && proc.pid !== onlyPid) continue;
15654
+ if (startedBefore !== void 0 && (!Number.isFinite(proc.startedAt) || proc.startedAt > startedBefore)) continue;
15655
+ const match = /^"?[^\n]*?\bnode(?:\.exe)?"?\s+(?:"[^\n"]+\.mjs"|\S+\.mjs)\s+__watch\s+(\S+)(?:\s|$)/.exec(proc.command);
15656
+ const args2 = `${proc.command} `;
15657
+ const names4 = (flag, value) => args2.includes(` ${flag} ${value} `) || args2.includes(` ${flag} "${value}" `);
15658
+ const board2 = names4("--board", KANBAN) || KANBAN === path43.join(REPO_ROOT, "docs", "kanban") && names4("--dir", REPO_ROOT);
15659
+ if (!match || known2 && !known2.has(match[1]) || !board2 || !stillOwns()) continue;
15660
+ if (WINDOWS3) {
15661
+ if (killTreeOnWindows(proc.pid)) stopped2.push(proc.pid);
15662
+ } else {
15663
+ try {
15664
+ process.kill(-proc.pid, "SIGKILL");
15665
+ stopped2.push(proc.pid);
15666
+ } catch {
15667
+ }
15668
+ }
15669
+ }
15670
+ return stopped2;
15671
+ }
15672
+ function startedUnder(list, root, since) {
15673
+ const self = list.procs.find((p) => p.pid === root);
15674
+ const from = since ?? self?.startedAt;
15675
+ if (from === void 0 || list.bootedAt > from) return [];
15676
+ if (self && self.startedAt > from) return [];
15677
+ const found2 = self ? [root] : [];
15678
+ const seen = /* @__PURE__ */ new Set([root]);
15679
+ const queue = [{ pid: root, startedAt: from }];
15680
+ for (const parent of queue) {
15681
+ for (const p of list.procs) {
15682
+ if (p.ppid !== parent.pid || seen.has(p.pid) || p.startedAt < parent.startedAt) continue;
15683
+ seen.add(p.pid);
15684
+ found2.push(p.pid);
15685
+ queue.push(p);
15686
+ }
15687
+ }
15688
+ return found2.reverse();
15689
+ }
15690
+ function readProcList(out) {
15691
+ const procs = [];
15692
+ let bootedAt;
15693
+ for (const line of out.split(/\r?\n/)) {
15694
+ const boot = /^boot (\d+)$/.exec(line.trim());
15695
+ if (boot) bootedAt = Number(boot[1]);
15696
+ const m = /^(\d+) (\d+) (\d+)$/.exec(line.trim());
15697
+ if (m) procs.push({ pid: Number(m[1]), ppid: Number(m[2]), startedAt: Number(m[3]) });
15698
+ }
15699
+ return bootedAt !== void 0 && procs.length ? { procs, bootedAt } : void 0;
15700
+ }
15701
+ function procsOnWindows() {
15702
+ const ps = spawnSync4("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", LIST_PROCS], {
15703
+ encoding: "utf8",
15704
+ maxBuffer: 64 * 1024 * 1024,
15705
+ timeout: 5e3,
15706
+ windowsHide: true
15707
+ });
15708
+ return ps.status === 0 && ps.stdout ? readProcList(ps.stdout) : void 0;
15709
+ }
15710
+ function killUnderOnWindows(pid, since) {
15711
+ if (!WINDOWS3 || !pid) return;
15712
+ const list = procsOnWindows();
15713
+ if (!list) return;
15714
+ const pids = startedUnder(list, pid, since).filter((p) => p !== process.pid);
15715
+ if (!pids.length) return;
15716
+ spawnSync4("taskkill", ["/F", ...pids.flatMap((p) => ["/PID", String(p)])], { stdio: "ignore", windowsHide: true, timeout: 1e4 });
15717
+ }
15718
+ function killMarked(mark, agent) {
15719
+ if (WINDOWS3) {
15720
+ if (agent) killUnderOnWindows(agent.pid, agent.since);
15721
+ return;
15722
+ }
15723
+ signal(marked(mark), "SIGKILL");
15724
+ }
15725
+ function killTreeOnWindows(pid) {
15726
+ if (!WINDOWS3 || !pid) return false;
15727
+ const out = spawnSync4("taskkill", ["/PID", String(pid), "/T", "/F"], { stdio: "ignore", windowsHide: true, timeout: 1e4 });
15728
+ return out.status === 0;
15729
+ }
15730
+ function later(ms, fn) {
15731
+ const t = setTimeout(fn, ms);
15732
+ if (typeof t.unref === "function") t.unref();
15733
+ }
15734
+ function endAgent(child, mark, graceMs) {
15735
+ const term = (sig) => {
15736
+ try {
15737
+ child.kill(sig);
15738
+ } catch {
15739
+ }
15740
+ };
15741
+ if (killTreeOnWindows(child.pid)) return;
15742
+ killUnderOnWindows(child.pid);
15743
+ const seen = new Set(marked(mark, child.pid ? [child.pid] : []));
15744
+ term("SIGTERM");
15745
+ signal([...seen].filter((pid) => pid !== child.pid), "SIGTERM");
15746
+ const round = (left) => {
15747
+ const pids = marked(mark, [...seen]);
15748
+ signal(pids, "SIGKILL");
15749
+ for (const pid of pids) seen.add(pid);
15750
+ if (pids.length && left > 1) later(KILL_ROUND_MS, () => round(left - 1));
15751
+ };
15752
+ later(graceMs, () => {
15753
+ term("SIGKILL");
15754
+ round(KILL_ROUNDS);
15755
+ });
15756
+ }
15757
+ function killRunning() {
15758
+ for (const [child, mark] of running) {
15759
+ if (killTreeOnWindows(child.pid)) continue;
15760
+ killUnderOnWindows(child.pid);
15761
+ signal(marked(mark, child.pid ? [child.pid] : []), "SIGKILL");
15762
+ try {
15763
+ child.kill("SIGKILL");
15764
+ } catch {
15765
+ }
15766
+ }
15767
+ running.clear();
15768
+ }
15769
+ function unlisten() {
15770
+ for (const [sig, fn] of listening) process.removeListener(sig, fn);
15771
+ listening.clear();
15772
+ }
15773
+ function onQuit(sig) {
15774
+ killRunning();
15775
+ unlisten();
15776
+ if (process.listenerCount(sig) === 0) process.exit(128 + os6.constants.signals[sig]);
15777
+ }
15778
+ function trackAgent(child, mark) {
15779
+ if (!child.pid) return;
15780
+ running.set(child, mark);
15781
+ child.once("close", () => {
15782
+ running.delete(child);
15783
+ if (!running.size) unlisten();
15784
+ });
15785
+ for (const sig of QUIT_SIGNALS) {
15786
+ if (listening.has(sig) || sig === "SIGINT" && interruptIsTheCallers) continue;
15787
+ const fn = () => onQuit(sig);
15788
+ listening.set(sig, fn);
15789
+ process.on(sig, fn);
15790
+ }
15791
+ }
15792
+ var runMark, stopMark, markEnv, WINDOWS3, KILL_ROUND_MS, KILL_ROUNDS, LIST_PROCS, running, QUIT_SIGNALS, listening, interruptIsTheCallers;
15793
+ var init_stop = __esm({
15794
+ "../cli/src/lib/agent/stop.ts"() {
15795
+ "use strict";
15796
+ init_env();
15797
+ init_paths();
15798
+ runMark = (sessionId) => ({ name: RUN_ENV, value: sessionId });
15799
+ stopMark = () => ({ name: STOP_ENV, value: randomUUID5() });
15800
+ markEnv = (env2, mark) => ({ ...env2, [mark.name]: mark.value });
15801
+ WINDOWS3 = process.platform === "win32";
15802
+ KILL_ROUND_MS = 300;
15803
+ KILL_ROUNDS = 3;
15804
+ LIST_PROCS = [
15805
+ "$ErrorActionPreference = 'Stop'",
15806
+ "Get-CimInstance Win32_Process | ForEach-Object { if ($_.CreationDate) { '{0} {1} {2}' -f $_.ProcessId, $_.ParentProcessId, ([DateTimeOffset]$_.CreationDate).ToUnixTimeMilliseconds() } }",
15807
+ "'boot {0}' -f ([DateTimeOffset](Get-CimInstance Win32_OperatingSystem).LastBootUpTime).ToUnixTimeMilliseconds()"
15808
+ ].join("; ");
15809
+ running = /* @__PURE__ */ new Map();
15810
+ QUIT_SIGNALS = ["SIGTERM", "SIGINT", "SIGHUP"];
15811
+ listening = /* @__PURE__ */ new Map();
15812
+ interruptIsTheCallers = false;
15813
+ }
15814
+ });
15815
+
15549
15816
  // ../cli/src/lib/agent/pause.ts
15550
15817
  function deliveryState(delivery, questions) {
15551
15818
  const landing = delivery.landing;
@@ -15734,12 +16001,12 @@ var init_runner = __esm({
15734
16001
  });
15735
16002
 
15736
16003
  // ../cli/src/lib/agent/rules.ts
15737
- import fs39 from "node:fs";
15738
- import path43 from "node:path";
16004
+ import fs40 from "node:fs";
16005
+ import path44 from "node:path";
15739
16006
  function ruleFile(agent) {
15740
16007
  if (!agent || hiddenRole(agent)) return "";
15741
16008
  try {
15742
- return fs39.readFileSync(rulePath(agent), "utf8").trim();
16009
+ return fs40.readFileSync(rulePath(agent), "utf8").trim();
15743
16010
  } catch {
15744
16011
  return "";
15745
16012
  }
@@ -15754,11 +16021,11 @@ function setAgentRule(agent, text5) {
15754
16021
  const rule = String(text5 ?? "").trim();
15755
16022
  try {
15756
16023
  if (!rule) {
15757
- fs39.rmSync(rulePath(agent), { force: true });
16024
+ fs40.rmSync(rulePath(agent), { force: true });
15758
16025
  return { ok: true };
15759
16026
  }
15760
- fs39.mkdirSync(RULES, { recursive: true });
15761
- fs39.writeFileSync(rulePath(agent), `${rule}
16027
+ fs40.mkdirSync(RULES, { recursive: true });
16028
+ fs40.writeFileSync(rulePath(agent), `${rule}
15762
16029
  `);
15763
16030
  return { ok: true };
15764
16031
  } catch (err) {
@@ -15806,7 +16073,7 @@ function migrateFlowRules() {
15806
16073
  adoptRenamedRules();
15807
16074
  let here;
15808
16075
  try {
15809
- here = new Set(fs39.readdirSync(RULES));
16076
+ here = new Set(fs40.readdirSync(RULES));
15810
16077
  } catch {
15811
16078
  return [];
15812
16079
  }
@@ -15817,9 +16084,9 @@ function migrateFlowRules() {
15817
16084
  if (!from.length) continue;
15818
16085
  const parts = [ruleFile(owner.name), ...from.map(ruleFile)].filter(Boolean);
15819
16086
  try {
15820
- if (parts.length) fs39.writeFileSync(rulePath(owner.name), `${parts.join("\n\n")}
16087
+ if (parts.length) fs40.writeFileSync(rulePath(owner.name), `${parts.join("\n\n")}
15821
16088
  `);
15822
- for (const flow of from) fs39.rmSync(rulePath(flow), { force: true });
16089
+ for (const flow of from) fs40.rmSync(rulePath(flow), { force: true });
15823
16090
  } catch (err) {
15824
16091
  notes.push(`could not move the ${from.join(", ")} rule(s) onto \`${owner.name}\`: ${String(err)}`);
15825
16092
  continue;
@@ -15833,7 +16100,7 @@ function migrateFlowRules() {
15833
16100
  function adoptRenamedRules() {
15834
16101
  let here;
15835
16102
  try {
15836
- here = fs39.readdirSync(RULES);
16103
+ here = fs40.readdirSync(RULES);
15837
16104
  } catch {
15838
16105
  return;
15839
16106
  }
@@ -15843,13 +16110,13 @@ function adoptRenamedRules() {
15843
16110
  const now = RENAMED_ROLES[was] ?? canonicalSpecAgent(was);
15844
16111
  if (now === was) continue;
15845
16112
  try {
15846
- if (!fs39.existsSync(rulePath(now))) fs39.renameSync(rulePath(was), rulePath(now));
16113
+ if (!fs40.existsSync(rulePath(now))) fs40.renameSync(rulePath(was), rulePath(now));
15847
16114
  else if (FOLDED_SPEC_AGENTS.has(was)) {
15848
16115
  const kept2 = ruleFile(now);
15849
16116
  const carried = ruleFile(was);
15850
- if (carried && !kept2.includes(carried)) fs39.writeFileSync(rulePath(now), `${[kept2, carried].filter(Boolean).join("\n\n")}
16117
+ if (carried && !kept2.includes(carried)) fs40.writeFileSync(rulePath(now), `${[kept2, carried].filter(Boolean).join("\n\n")}
15851
16118
  `);
15852
- fs39.rmSync(rulePath(was), { force: true });
16119
+ fs40.rmSync(rulePath(was), { force: true });
15853
16120
  }
15854
16121
  } catch {
15855
16122
  }
@@ -15866,7 +16133,7 @@ var init_rules2 = __esm({
15866
16133
  init_runner();
15867
16134
  init_workflows();
15868
16135
  init_types3();
15869
- rulePath = (agent) => path43.join(RULES, `${agent}.md`);
16136
+ rulePath = (agent) => path44.join(RULES, `${agent}.md`);
15870
16137
  notOnTheRoster = (name) => `"${name}" is not an agent on this board. It has: ${agentNames().join(", ")}.`;
15871
16138
  flowForRequest = (req) => req.refineRound !== void 0 && REFINE_ACTIONS.has(req.action) ? flowByCommand("refine") : flowByAction(req.action);
15872
16139
  ruleOwnerSays = (owner) => `\`${owner.name}\` agent carries one rule of its own${owner.role ? ", on every flow it runs" : ""}`;
@@ -15951,8 +16218,8 @@ var init_source = __esm({
15951
16218
  });
15952
16219
 
15953
16220
  // ../cli/src/lib/card-sources.ts
15954
- import fs40 from "node:fs";
15955
- import path44 from "node:path";
16221
+ import fs41 from "node:fs";
16222
+ import path45 from "node:path";
15956
16223
  function planById(id) {
15957
16224
  const found2 = readPlan(`plans/${id}-.md`);
15958
16225
  return found2?.text.trim() ? found2 : null;
@@ -15962,8 +16229,8 @@ function cardLink(id) {
15962
16229
  const found2 = open3 ?? locateArchived(id);
15963
16230
  if (!found2) return null;
15964
16231
  try {
15965
- const file = found2.kind === "group" ? path44.join(found2.target, "root.md") : found2.target;
15966
- const title = parseFrontmatter(fs40.readFileSync(file, "utf8")).meta?.title ?? "";
16232
+ const file = found2.kind === "group" ? path45.join(found2.target, "root.md") : found2.target;
16233
+ const title = parseFrontmatter(fs41.readFileSync(file, "utf8")).meta?.title ?? "";
15967
16234
  return { kind: "card", ref: String(id), title, ...open3 ? {} : { archived: true } };
15968
16235
  } catch {
15969
16236
  return null;
@@ -15988,7 +16255,7 @@ function link(ref, items) {
15988
16255
  if (ref.kind === "plan") return planLink(ref.id);
15989
16256
  if (ref.kind === "card") return cardLink(ref.id);
15990
16257
  if (ref.kind === "url") return urlLink(ref.url);
15991
- const item = items().find((signal2) => path44.basename(signal2.relPath) === ref.file);
16258
+ const item = items().find((signal2) => path45.basename(signal2.relPath) === ref.file);
15992
16259
  return item ? itemLink(item) : null;
15993
16260
  }
15994
16261
  function cardSources(meta, body) {
@@ -16029,12 +16296,12 @@ var init_card_sources = __esm({
16029
16296
  });
16030
16297
 
16031
16298
  // ../cli/src/lib/view/read.ts
16032
- import fs41 from "node:fs";
16033
- import path45 from "node:path";
16299
+ import fs42 from "node:fs";
16300
+ import path46 from "node:path";
16034
16301
  function readCard(file, relFromTodo) {
16035
- const id = idPrefix(path45.basename(relFromTodo));
16302
+ const id = idPrefix(path46.basename(relFromTodo));
16036
16303
  if (id === null) {
16037
- const parts = relFromTodo.split(path45.sep);
16304
+ const parts = relFromTodo.split(path46.sep);
16038
16305
  const folderId = idPrefix(parts[parts.length - 2] || "");
16039
16306
  if (folderId === null) return null;
16040
16307
  return buildCard(folderId, file, relFromTodo);
@@ -16042,10 +16309,10 @@ function readCard(file, relFromTodo) {
16042
16309
  return buildCard(id, file, relFromTodo);
16043
16310
  }
16044
16311
  function buildCard(id, file, relFromTodo) {
16045
- const text5 = fs41.readFileSync(file, "utf8");
16312
+ const text5 = fs42.readFileSync(file, "utf8");
16046
16313
  const { meta, body } = parseFrontmatter(text5);
16047
16314
  if (!meta) return null;
16048
- const relPath = relFromTodo.split(path45.sep).join("/");
16315
+ const relPath = relFromTodo.split(path46.sep).join("/");
16049
16316
  const flow = workflowFor(meta.workflow);
16050
16317
  const plan = !!meta.workflow && archivesOnApproval(flow);
16051
16318
  const stages = cardStages(meta, flow).length;
@@ -16054,7 +16321,7 @@ function buildCard(id, file, relFromTodo) {
16054
16321
  // Derived from the file exactly as it is on disk (lib/board/revision.ts), so a card
16055
16322
  // read here and a card read by the contract agree on what version this is.
16056
16323
  revision: revisionOf(text5),
16057
- updatedAt: fs41.statSync(file).mtime.toISOString(),
16324
+ updatedAt: fs42.statSync(file).mtime.toISOString(),
16058
16325
  relPath,
16059
16326
  title: meta.title,
16060
16327
  priority: meta.priority,
@@ -16075,22 +16342,22 @@ function buildCard(id, file, relFromTodo) {
16075
16342
  };
16076
16343
  }
16077
16344
  function readGroup2(folderName) {
16078
- const groupDir = path45.join(TODO, folderName);
16079
- const rootFile = path45.join(groupDir, "root.md");
16080
- if (!fs41.existsSync(rootFile)) return null;
16081
- const root = readCard(rootFile, path45.join(folderName, "root.md"));
16345
+ const groupDir = path46.join(TODO, folderName);
16346
+ const rootFile = path46.join(groupDir, "root.md");
16347
+ if (!fs42.existsSync(rootFile)) return null;
16348
+ const root = readCard(rootFile, path46.join(folderName, "root.md"));
16082
16349
  if (!root) return null;
16083
16350
  root.isGroup = true;
16084
16351
  const { total, resolved: resolved2 } = subtaskLines(root.body);
16085
16352
  root.subtaskLines = { total, resolved: resolved2 };
16086
16353
  const subCards = [];
16087
16354
  const recurse = (dir, relDir) => {
16088
- for (const entry of fs41.readdirSync(dir, { withFileTypes: true })) {
16089
- const childRel = path45.join(relDir, entry.name);
16355
+ for (const entry of fs42.readdirSync(dir, { withFileTypes: true })) {
16356
+ const childRel = path46.join(relDir, entry.name);
16090
16357
  if (entry.isDirectory()) {
16091
- recurse(path45.join(dir, entry.name), childRel);
16358
+ recurse(path46.join(dir, entry.name), childRel);
16092
16359
  } else if (entry.name.endsWith(".md") && entry.name !== "README.md" && entry.name !== "root.md") {
16093
- const c = readCard(path45.join(dir, entry.name), childRel);
16360
+ const c = readCard(path46.join(dir, entry.name), childRel);
16094
16361
  if (c) {
16095
16362
  c.parent = { id: root.id, title: root.title };
16096
16363
  subCards.push(c);
@@ -16111,12 +16378,12 @@ function readGroup2(folderName) {
16111
16378
  return { root, subCards };
16112
16379
  }
16113
16380
  function standaloneCards(dirRel) {
16114
- const dir = dirRel ? path45.join(TODO, dirRel) : TODO;
16115
- if (!fs41.existsSync(dir)) return [];
16381
+ const dir = dirRel ? path46.join(TODO, dirRel) : TODO;
16382
+ if (!fs42.existsSync(dir)) return [];
16116
16383
  const cards = [];
16117
- for (const entry of fs41.readdirSync(dir, { withFileTypes: true })) {
16384
+ for (const entry of fs42.readdirSync(dir, { withFileTypes: true })) {
16118
16385
  if (entry.isFile() && entry.name.endsWith(".md") && entry.name !== "README.md") {
16119
- const c = readCard(path45.join(dir, entry.name), path45.join(dirRel, entry.name));
16386
+ const c = readCard(path46.join(dir, entry.name), path46.join(dirRel, entry.name));
16120
16387
  if (c) cards.push(c);
16121
16388
  }
16122
16389
  }
@@ -16128,7 +16395,7 @@ function collectCards() {
16128
16395
  const top = standaloneCards("");
16129
16396
  board2.push(...top);
16130
16397
  every.push(...top);
16131
- for (const entry of fs41.readdirSync(TODO, { withFileTypes: true })) {
16398
+ for (const entry of fs42.readdirSync(TODO, { withFileTypes: true })) {
16132
16399
  if (!entry.isDirectory()) continue;
16133
16400
  if (isGroupFolder(entry.name)) {
16134
16401
  const g = readGroup2(entry.name);
@@ -16160,7 +16427,7 @@ function findCard(id) {
16160
16427
  }
16161
16428
  function attachSources(card) {
16162
16429
  try {
16163
- const { meta, body } = parseFrontmatter(fs41.readFileSync(path45.join(TODO, card.relPath), "utf8"));
16430
+ const { meta, body } = parseFrontmatter(fs42.readFileSync(path46.join(TODO, card.relPath), "utf8"));
16164
16431
  const sources = meta ? cardSources(meta, body) : [];
16165
16432
  if (sources.length) card.sources = sources;
16166
16433
  } catch {
@@ -16348,8 +16615,8 @@ var init_review = __esm({
16348
16615
  });
16349
16616
 
16350
16617
  // ../cli/src/lib/agent/deliveries.ts
16351
- import fs42 from "node:fs";
16352
- import path46 from "node:path";
16618
+ import fs43 from "node:fs";
16619
+ import path47 from "node:path";
16353
16620
  function writeAudit(delivery, runs) {
16354
16621
  const known2 = new Map(readAudit(delivery.deliveryId)?.sessions.map((s) => [s.sessionId, s]) ?? []);
16355
16622
  const sessions = delivery.sessions.flatMap((id) => {
@@ -16377,10 +16644,10 @@ function writeAudit(delivery, runs) {
16377
16644
  }
16378
16645
  function writeAuditFile(record) {
16379
16646
  try {
16380
- fs42.mkdirSync(DELIVERIES, { recursive: true });
16647
+ fs43.mkdirSync(DELIVERIES, { recursive: true });
16381
16648
  const tmp = `${auditPath(record.deliveryId)}.tmp`;
16382
- fs42.writeFileSync(tmp, JSON.stringify(record, null, 2) + "\n");
16383
- fs42.renameSync(tmp, auditPath(record.deliveryId));
16649
+ fs43.writeFileSync(tmp, JSON.stringify(record, null, 2) + "\n");
16650
+ fs43.renameSync(tmp, auditPath(record.deliveryId));
16384
16651
  return true;
16385
16652
  } catch {
16386
16653
  return false;
@@ -16388,7 +16655,7 @@ function writeAuditFile(record) {
16388
16655
  }
16389
16656
  function readAudit(deliveryId) {
16390
16657
  try {
16391
- const data = JSON.parse(fs42.readFileSync(auditPath(deliveryId), "utf8"));
16658
+ const data = JSON.parse(fs43.readFileSync(auditPath(deliveryId), "utf8"));
16392
16659
  return { sessions: Array.isArray(data?.sessions) ? data.sessions : [] };
16393
16660
  } catch {
16394
16661
  return null;
@@ -16403,7 +16670,7 @@ function syncAudit(deliveryId, just) {
16403
16670
  }
16404
16671
  function wholeAudit(file) {
16405
16672
  try {
16406
- const data = JSON.parse(fs42.readFileSync(file, "utf8"));
16673
+ const data = JSON.parse(fs43.readFileSync(file, "utf8"));
16407
16674
  return typeof data?.deliveryId === "string" && Array.isArray(data.sessions) ? data : null;
16408
16675
  } catch {
16409
16676
  return null;
@@ -16416,14 +16683,14 @@ function activeAudit(file) {
16416
16683
  function allAudits() {
16417
16684
  let names4;
16418
16685
  try {
16419
- names4 = fs42.readdirSync(DELIVERIES);
16686
+ names4 = fs43.readdirSync(DELIVERIES);
16420
16687
  } catch {
16421
16688
  return [];
16422
16689
  }
16423
16690
  const out = [];
16424
16691
  for (const name of names4) {
16425
16692
  if (!name.endsWith(".json")) continue;
16426
- const audit = wholeAudit(path46.join(DELIVERIES, name));
16693
+ const audit = wholeAudit(path47.join(DELIVERIES, name));
16427
16694
  if (audit) out.push(audit);
16428
16695
  }
16429
16696
  return out;
@@ -16534,11 +16801,11 @@ function resumeRefusal(delivery, store) {
16534
16801
  );
16535
16802
  }
16536
16803
  if (!worktreeExists(delivery.worktree)) {
16537
- const path85 = delivery.worktree ?? "";
16804
+ const path87 = delivery.worktree ?? "";
16538
16805
  return refusal(
16539
16806
  "deliveryWorktreeGone",
16540
- `delivery ${id}'s worktree ${path85} is gone, so there is nothing to carry on. ${rebuild}`,
16541
- { id, task, path: path85, command: discard }
16807
+ `delivery ${id}'s worktree ${path87} is gone, so there is nothing to carry on. ${rebuild}`,
16808
+ { id, task, path: path87, command: discard }
16542
16809
  );
16543
16810
  }
16544
16811
  if (!branchExists(delivery.branch)) {
@@ -16591,11 +16858,12 @@ function tidyCheckout(deliveryId) {
16591
16858
  const store = readStore();
16592
16859
  const row = store.deliveries.find((d) => d.deliveryId === deliveryId) ?? auditOnly(deliveryId);
16593
16860
  if (!row || row.status === "active") return NOTHING;
16594
- if (!row.worktree && !row.branch) return NOTHING;
16595
- if (isResumable(row)) return { removed: false, kept: true };
16596
16861
  if (store.runs.some((r) => r.deliveryId === deliveryId && runIsLive(r))) {
16597
16862
  return { removed: false, kept: false, error: `a run of delivery ${deliveryId} is still going`, reason: "deliveryRunGoing", args: { id: deliveryId } };
16598
16863
  }
16864
+ if (row.status !== "finished" || Date.now() - (row.endedAt ?? 0) > 3e4) stopWatchers(row.sessions);
16865
+ if (!row.worktree && !row.branch) return NOTHING;
16866
+ if (isResumable(row)) return { removed: false, kept: true };
16599
16867
  const removed = removeWorktree(row.worktree, row.branch, true);
16600
16868
  if (!removed.ok) return { removed: false, kept: false, error: removed.error };
16601
16869
  forgetCheckout(deliveryId);
@@ -16618,7 +16886,10 @@ function forgetCheckout(deliveryId) {
16618
16886
  function sweepCheckouts() {
16619
16887
  const seen = /* @__PURE__ */ new Set();
16620
16888
  const complaints = [];
16621
- const rows = [...readStore().deliveries, ...allAudits().flatMap((a) => auditRow(a) ?? [])];
16889
+ const store = readStore();
16890
+ const rows = [...store.deliveries, ...allAudits().flatMap((a) => auditRow(a) ?? [])];
16891
+ const ended = rows.filter((row) => row.status !== "active" && (row.status !== "finished" || Date.now() - (row.endedAt ?? 0) > 3e4) && !store.runs.some((r) => r.deliveryId === row.deliveryId && runIsLive(r)));
16892
+ stopWatchers([...new Set(ended.flatMap((row) => row.sessions))]);
16622
16893
  for (const row of rows) {
16623
16894
  if (seen.has(row.deliveryId) || row.status === "active" || !row.worktree) continue;
16624
16895
  seen.add(row.deliveryId);
@@ -16671,10 +16942,10 @@ function carryOnFrom(deliveryId) {
16671
16942
  function approvedRequirements(cardId2) {
16672
16943
  const found2 = locate(cardId2);
16673
16944
  if (!found2) return "";
16674
- const file = found2.kind === "group" ? path46.join(found2.target, "root.md") : found2.target;
16945
+ const file = found2.kind === "group" ? path47.join(found2.target, "root.md") : found2.target;
16675
16946
  let text5;
16676
16947
  try {
16677
- text5 = fs42.readFileSync(file, "utf8");
16948
+ text5 = fs43.readFileSync(file, "utf8");
16678
16949
  } catch {
16679
16950
  return "";
16680
16951
  }
@@ -16704,8 +16975,8 @@ function cardMeta2(cardId2) {
16704
16975
  try {
16705
16976
  const found2 = locate(cardId2);
16706
16977
  if (!found2) return null;
16707
- const file = found2.kind === "group" ? path46.join(found2.target, "root.md") : found2.target;
16708
- return parseFrontmatter(fs42.readFileSync(file, "utf8")).meta;
16978
+ const file = found2.kind === "group" ? path47.join(found2.target, "root.md") : found2.target;
16979
+ return parseFrontmatter(fs43.readFileSync(file, "utf8")).meta;
16709
16980
  } catch {
16710
16981
  return null;
16711
16982
  }
@@ -16954,6 +17225,7 @@ var init_deliveries = __esm({
16954
17225
  init_complete();
16955
17226
  init_outputs();
16956
17227
  init_env();
17228
+ init_stop();
16957
17229
  init_flows();
16958
17230
  init_pause();
16959
17231
  init_rules2();
@@ -16963,7 +17235,7 @@ var init_deliveries = __esm({
16963
17235
  init_store();
16964
17236
  init_types3();
16965
17237
  init_worktree();
16966
- auditPath = (deliveryId) => path46.join(DELIVERIES, `${deliveryId}.json`);
17238
+ auditPath = (deliveryId) => path47.join(DELIVERIES, `${deliveryId}.json`);
16967
17239
  activeAudits = () => allAudits().filter((a) => a.status === "active");
16968
17240
  auditRow = (audit) => readDeliveryRow({ ...audit, sessions: audit.sessions.map((s) => s.sessionId) });
16969
17241
  lastSessionRunning = (audit) => audit.sessions[audit.sessions.length - 1]?.status === "running";
@@ -16991,9 +17263,44 @@ var init_deliveries = __esm({
16991
17263
  }
16992
17264
  });
16993
17265
 
16994
- // ../cli/src/lib/agent/answers.ts
16995
- function answerOutcome(delivery) {
16996
- return pendingAnswers(delivery).some((a) => a.outcome === "changed") ? "changed" : "unchanged";
17266
+ // ../cli/src/lib/agent/landing-lock.ts
17267
+ import fs44 from "node:fs";
17268
+ import path48 from "node:path";
17269
+ function lockLanding() {
17270
+ const dir = path48.join(SESSIONS_DIR, ".landing.lock");
17271
+ try {
17272
+ const entries = fs44.readdirSync(dir);
17273
+ const token3 = entries.length === 1 ? entries[0] : "";
17274
+ const match = /^(\d+)-[a-f0-9-]{36}$/.exec(token3);
17275
+ const stat = fs44.statSync(dir);
17276
+ if (match && Date.now() - stat.mtimeMs > landingLimits.staleMs) {
17277
+ stopWatchers(void 0, Number(match[1]), () => {
17278
+ const now = fs44.readdirSync(dir);
17279
+ return now.length === 1 && now[0] === token3 && Date.now() - fs44.statSync(dir).mtimeMs > landingLimits.staleMs;
17280
+ }, stat.birthtimeMs || stat.mtimeMs);
17281
+ }
17282
+ } catch {
17283
+ }
17284
+ return tryLock(dir);
17285
+ }
17286
+ function renewLandingLock() {
17287
+ const now = /* @__PURE__ */ new Date();
17288
+ fs44.utimesSync(path48.join(SESSIONS_DIR, ".landing.lock"), now, now);
17289
+ }
17290
+ var landingLimits;
17291
+ var init_landing_lock = __esm({
17292
+ "../cli/src/lib/agent/landing-lock.ts"() {
17293
+ "use strict";
17294
+ init_lock();
17295
+ init_paths();
17296
+ init_stop();
17297
+ landingLimits = { staleMs: 5 * 6e4 };
17298
+ }
17299
+ });
17300
+
17301
+ // ../cli/src/lib/agent/answers.ts
17302
+ function answerOutcome(delivery) {
17303
+ return pendingAnswers(delivery).some((a) => a.outcome === "changed") ? "changed" : "unchanged";
16997
17304
  }
16998
17305
  function takeUnchanged(delivery) {
16999
17306
  if (!pendingAnswers(delivery).some((a) => a.outcome === "unchanged")) return;
@@ -17055,8 +17362,8 @@ var init_retry = __esm({
17055
17362
  });
17056
17363
 
17057
17364
  // ../cli/src/lib/agent/landing.ts
17058
- import fs43 from "node:fs";
17059
- import path47 from "node:path";
17365
+ import fs45 from "node:fs";
17366
+ import path49 from "node:path";
17060
17367
  function waitingOut(delivery, now = Date.now()) {
17061
17368
  const landing = delivery.landing;
17062
17369
  if (!landing) return false;
@@ -17192,8 +17499,9 @@ function noteQueue(held4) {
17192
17499
  }
17193
17500
  async function advanceLanding() {
17194
17501
  let release;
17502
+ let current;
17195
17503
  try {
17196
- release = tryLock(path47.join(SESSIONS_DIR, ".landing.lock"));
17504
+ release = lockLanding();
17197
17505
  if (!release) return null;
17198
17506
  const owed = await owedRestart();
17199
17507
  if (owed) return owed;
@@ -17208,11 +17516,19 @@ async function advanceLanding() {
17208
17516
  return null;
17209
17517
  }
17210
17518
  tried.add(picked.deliveryId);
17519
+ current = picked;
17520
+ renewLandingLock();
17211
17521
  const step = await landStep(picked);
17212
17522
  if (step.start) return step.start;
17213
17523
  if (!step.done) return null;
17214
17524
  }
17215
- } catch {
17525
+ } catch (error) {
17526
+ if (current && error instanceof GitTimeoutError) {
17527
+ giveUpSlot(current, error.message);
17528
+ patchLanding(current.deliveryId, (landing) => {
17529
+ landing.retryAt = Date.now() + 3e4;
17530
+ });
17531
+ }
17216
17532
  return null;
17217
17533
  } finally {
17218
17534
  release?.();
@@ -17529,7 +17845,7 @@ async function settleAlreadyLanded(delivery) {
17529
17845
  return true;
17530
17846
  }
17531
17847
  function repairLanding() {
17532
- const release = tryLock(path47.join(SESSIONS_DIR, ".landing.lock"));
17848
+ const release = lockLanding();
17533
17849
  if (!release) return [];
17534
17850
  try {
17535
17851
  return repairIdleLanding();
@@ -17562,7 +17878,7 @@ var init_landing = __esm({
17562
17878
  init_execution();
17563
17879
  init_board();
17564
17880
  init_io();
17565
- init_lock();
17881
+ init_landing_lock();
17566
17882
  init_paths();
17567
17883
  init_answers();
17568
17884
  init_commit_mode();
@@ -17603,9 +17919,9 @@ var init_landing = __esm({
17603
17919
  sameDir = (a, b) => {
17604
17920
  const real2 = (dir) => {
17605
17921
  try {
17606
- return fs43.realpathSync(dir);
17922
+ return fs45.realpathSync(dir);
17607
17923
  } catch {
17608
- return path47.resolve(dir);
17924
+ return path49.resolve(dir);
17609
17925
  }
17610
17926
  };
17611
17927
  return real2(a) === real2(b);
@@ -17614,35 +17930,35 @@ var init_landing = __esm({
17614
17930
  });
17615
17931
 
17616
17932
  // ../cli/src/lib/agent/pictures.ts
17617
- import fs44 from "node:fs";
17618
- import path48 from "node:path";
17933
+ import fs46 from "node:fs";
17934
+ import path50 from "node:path";
17619
17935
  function claimRunPictures(box2, sessionId, names4 = []) {
17620
17936
  if (!box2 || !freeBox(box2) || names4.length === 0) return [];
17621
17937
  const from = pictureBox(box2);
17622
17938
  const to = pictureBox(sessionId);
17623
17939
  try {
17624
- fs44.mkdirSync(SESSIONS_DIR, { recursive: true });
17625
- fs44.renameSync(from, to);
17940
+ fs46.mkdirSync(SESSIONS_DIR, { recursive: true });
17941
+ fs46.renameSync(from, to);
17626
17942
  } catch {
17627
17943
  return [];
17628
17944
  }
17629
- return names4.filter(pictureName).map((name) => path48.join(to, name)).filter((file) => fs44.existsSync(file));
17945
+ return names4.filter(pictureName).map((name) => path50.join(to, name)).filter((file) => fs46.existsSync(file));
17630
17946
  }
17631
17947
  function returnRunPictures(sessionId, box2) {
17632
17948
  if (!box2 || !boxId(box2)) return;
17633
17949
  try {
17634
- fs44.renameSync(pictureBox(sessionId), pictureBox(box2));
17950
+ fs46.renameSync(pictureBox(sessionId), pictureBox(box2));
17635
17951
  } catch {
17636
17952
  }
17637
17953
  }
17638
17954
  function moveRunPictures(from, to, files2 = []) {
17639
17955
  if (files2.length === 0) return [];
17640
17956
  try {
17641
- fs44.renameSync(pictureBox(from), pictureBox(to));
17957
+ fs46.renameSync(pictureBox(from), pictureBox(to));
17642
17958
  } catch {
17643
17959
  return files2;
17644
17960
  }
17645
- return files2.map((file) => path48.join(pictureBox(to), path48.basename(file)));
17961
+ return files2.map((file) => path50.join(pictureBox(to), path50.basename(file)));
17646
17962
  }
17647
17963
  var pictureName, boxId, freeBox, pictureBox;
17648
17964
  var init_pictures = __esm({
@@ -17651,14 +17967,14 @@ var init_pictures = __esm({
17651
17967
  init_paths();
17652
17968
  pictureName = (name) => /^[0-9a-f-]{36}\.[a-z0-9]{2,5}$/.test(name);
17653
17969
  boxId = (box2) => /^[0-9a-f-]{36}$/.test(box2);
17654
- freeBox = (box2) => boxId(box2) && !fs44.existsSync(path48.join(SESSIONS_DIR, `${box2}.log`));
17655
- pictureBox = (box2) => path48.join(SESSIONS_DIR, `${box2}.images`);
17970
+ freeBox = (box2) => boxId(box2) && !fs46.existsSync(path50.join(SESSIONS_DIR, `${box2}.log`));
17971
+ pictureBox = (box2) => path50.join(SESSIONS_DIR, `${box2}.images`);
17656
17972
  }
17657
17973
  });
17658
17974
 
17659
17975
  // ../cli/src/lib/memories.ts
17660
- import fs45 from "node:fs";
17661
- import path49 from "node:path";
17976
+ import fs47 from "node:fs";
17977
+ import path51 from "node:path";
17662
17978
  function readMemory(text5, builtIn, folder) {
17663
17979
  const { meta, body } = splitFrontmatter(text5);
17664
17980
  if (meta === null) return null;
@@ -17678,7 +17994,7 @@ function readMemory(text5, builtIn, folder) {
17678
17994
  rules: body.trim(),
17679
17995
  text: text5,
17680
17996
  builtIn,
17681
- dir: path49.join(MEMORY, folder)
17997
+ dir: path51.join(MEMORY, folder)
17682
17998
  };
17683
17999
  }
17684
18000
  function globalMemories() {
@@ -17689,14 +18005,14 @@ function globalMemories() {
17689
18005
  }
17690
18006
  let folders2 = [];
17691
18007
  try {
17692
- folders2 = fs45.readdirSync(MEMORY, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).sort();
18008
+ folders2 = fs47.readdirSync(MEMORY, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).sort();
17693
18009
  } catch {
17694
18010
  }
17695
18011
  for (const name of folders2) {
17696
18012
  if (name in BUNDLED_MEMORY_FILES || name === RESERVED_MEMORY_DIR) continue;
17697
- const file = path49.join(MEMORY, name, GLOBAL_MEMORY_FILE);
17698
- if (!fs45.existsSync(file)) continue;
17699
- const memory = readMemory(fs45.readFileSync(file, "utf8"), false, name);
18013
+ const file = path51.join(MEMORY, name, GLOBAL_MEMORY_FILE);
18014
+ if (!fs47.existsSync(file)) continue;
18015
+ const memory = readMemory(fs47.readFileSync(file, "utf8"), false, name);
17700
18016
  if (memory) out.push(memory);
17701
18017
  }
17702
18018
  return out;
@@ -17752,16 +18068,16 @@ var init_facts = __esm({
17752
18068
  });
17753
18069
 
17754
18070
  // ../cli/src/lib/view/archive.ts
17755
- import fs46 from "node:fs";
17756
- import path50 from "node:path";
18071
+ import fs48 from "node:fs";
18072
+ import path52 from "node:path";
17757
18073
  function archivedId(file) {
17758
- const own = idPrefix(path50.basename(file));
17759
- return own !== null ? own : idPrefix(path50.basename(path50.dirname(file)));
18074
+ const own = idPrefix(path52.basename(file));
18075
+ return own !== null ? own : idPrefix(path52.basename(path52.dirname(file)));
17760
18076
  }
17761
18077
  function readFile(file) {
17762
18078
  const id = archivedId(file);
17763
18079
  if (id === null) return null;
17764
- const { meta, body } = parseFrontmatter(fs46.readFileSync(file, "utf8"));
18080
+ const { meta, body } = parseFrontmatter(fs48.readFileSync(file, "utf8"));
17765
18081
  if (!meta) return null;
17766
18082
  return {
17767
18083
  row: {
@@ -17769,15 +18085,15 @@ function readFile(file) {
17769
18085
  title: meta.title,
17770
18086
  archived: meta.archived,
17771
18087
  rejected: meta.rejected,
17772
- relPath: rel(file).split(path50.sep).join("/")
18088
+ relPath: rel(file).split(path52.sep).join("/")
17773
18089
  },
17774
18090
  body: body.replace(/^\n+/, "").replace(/\s+$/, ""),
17775
18091
  meta
17776
18092
  };
17777
18093
  }
17778
18094
  function readArchive() {
17779
- const relPath = rel(ARCHIVE).split(path50.sep).join("/");
17780
- if (!fs46.existsSync(ARCHIVE)) return { relPath, cards: [] };
18095
+ const relPath = rel(ARCHIVE).split(path52.sep).join("/");
18096
+ if (!fs48.existsSync(ARCHIVE)) return { relPath, cards: [] };
17781
18097
  const cards = walkMd(ARCHIVE).map(readFile).filter((found2) => found2 !== null).map((found2) => found2.row);
17782
18098
  cards.sort((a, b) => b.id - a.id);
17783
18099
  return { relPath, cards };
@@ -17792,7 +18108,7 @@ var init_archive = __esm({
17792
18108
  });
17793
18109
 
17794
18110
  // ../cli/src/lib/agent/prompts.ts
17795
- import path51 from "node:path";
18111
+ import path53 from "node:path";
17796
18112
  function resumePrompt(deliveryId, cardId2, action) {
17797
18113
  if (!deliveryId) return RESUME_PROMPT;
17798
18114
  if (action === "conflict") {
@@ -17836,7 +18152,7 @@ function buildAsk(rawReq, notes = []) {
17836
18152
  }
17837
18153
  function cardFileOf(id) {
17838
18154
  const found2 = id === void 0 ? null : locate(id);
17839
- return found2 ? found2.kind === "group" ? path51.join(found2.target, "root.md") : found2.target : "";
18155
+ return found2 ? found2.kind === "group" ? path53.join(found2.target, "root.md") : found2.target : "";
17840
18156
  }
17841
18157
  function realPaths(text5, req) {
17842
18158
  if (req.action !== "scheduled" && req.action !== "sub") return text5;
@@ -17929,7 +18245,7 @@ function frozenRules(req) {
17929
18245
  function archivedCardFile(id) {
17930
18246
  const found2 = locateArchived(id);
17931
18247
  if (!found2) return `${rel(ARCHIVE)}/`;
17932
- return rel(found2.kind === "group" ? path51.join(found2.target, "root.md") : found2.target);
18248
+ return rel(found2.kind === "group" ? path53.join(found2.target, "root.md") : found2.target);
17933
18249
  }
17934
18250
  function shippedLines(cardId2) {
17935
18251
  const delivery = endedDelivery(`${cardId2}`);
@@ -18100,7 +18416,7 @@ function actionPrompt(req, command, notes) {
18100
18416
  case "spec": {
18101
18417
  const agent = findSpecAgent(req.specAgent ?? "");
18102
18418
  const found2 = req.id === void 0 ? null : locate(req.id);
18103
- const cardFile3 = found2 ? rel(found2.kind === "group" ? path51.join(found2.target, "root.md") : found2.target) : `task #${req.id}`;
18419
+ const cardFile3 = found2 ? rel(found2.kind === "group" ? path53.join(found2.target, "root.md") : found2.target) : `task #${req.id}`;
18104
18420
  const own = agent ? specAgentInstructions(agent) : null;
18105
18421
  if (own) notes.push(...own.notes);
18106
18422
  const memory = agent ? agentMemoryBlock(agent) : "";
@@ -18211,290 +18527,83 @@ ${shared}` : ""
18211
18527
  return [
18212
18528
  `${kb}. Task ${req.id} ${named2} has been approved: write the previews that were waiting on that Approve from the card as it stands.`,
18213
18529
  req.notes ? `Extra notes: ${req.notes}` : "",
18214
- `Don't ask me questions with human-in-the-loop. Leave any questions as open questions.`
18215
- ].filter(Boolean).join(" ");
18216
- case "resolve":
18217
- return [
18218
- `${kb}. Apply my answers to the open questions on task ${req.id} ${named2} following \`akb guide resolve\`.`,
18219
- carriedNote(req),
18220
- answeredNote(req.id, command),
18221
- req.notes ? `Extra notes: ${req.notes}` : ""
18222
- ].filter(Boolean).join(" ");
18223
- }
18224
- }
18225
- function discardedCardsPrompt(cards = []) {
18226
- if (!cards.length) return "";
18227
- return "The user discarded the cards listed below. Their absence is intentional. Do not restore or recreate them, including under new IDs or filenames. Continue the remaining work from the current board, preserving the other cards.\n\n" + cards.map((c) => `- #${c.id}: ${c.path}`).join("\n");
18228
- }
18229
- var RESUME_PROMPT, DELIVERY_RESUME, DELIVERY_RESUME_CARD, RESTARTABLE, RESTART_LEAD, restartable, NO_IMPLEMENT, BOARD_FOLDER, CARD_FILE, PROJECT_FOLDER, PLANNING_FOR, SPEC_SELECTOR_FOR, reflectedCards;
18230
- var init_prompts = __esm({
18231
- "../cli/src/lib/agent/prompts.ts"() {
18232
- "use strict";
18233
- init_cards();
18234
- init_memory();
18235
- init_memories();
18236
- init_paths();
18237
- init_agents();
18238
- init_command();
18239
- init_deliveries();
18240
- init_env();
18241
- init_facts();
18242
- init_outputs();
18243
- init_archive();
18244
- init_flows();
18245
- init_resolve();
18246
- init_runner();
18247
- init_workflows();
18248
- init_catalog2();
18249
- init_roles();
18250
- init_rules2();
18251
- init_settings2();
18252
- init_types3();
18253
- RESUME_PROMPT = [
18254
- `Continue. The previous run ended before it finished the task.`,
18255
- `Pick up where you left off and carry it through.`
18256
- ].join(" ");
18257
- DELIVERY_RESUME = [
18258
- `Continue delivery %s. The previous run ended before the delivery finished.`,
18259
- `Re-enter the flow from the top and check each step's precondition before you do it \u2014 work that is already done is done, so do not repeat it.`
18260
- ].join(" ");
18261
- DELIVERY_RESUME_CARD = `Build the card as the delivery holds it, not as the file reads now: \`%c\` prints the approved copy.`;
18262
- RESTARTABLE = /* @__PURE__ */ new Set([
18263
- "implement",
18264
- "clarify",
18265
- "resolve",
18266
- "approve",
18267
- "archive",
18268
- "spec",
18269
- "conflict",
18270
- "scheduled"
18271
- ]);
18272
- RESTART_LEAD = [
18273
- `The session you were continuing is gone, and this one holds nothing of it.`,
18274
- `Do the task below from the top, checking each step's precondition before you do it \u2014 work an earlier run already finished is done, so don't repeat it.`
18275
- ].join(" ");
18276
- restartable = (run2) => (run2.cardId !== null || !!run2.deliveryId) && RESTARTABLE.has(run2.action);
18277
- NO_IMPLEMENT = `(Unless the request explicitly asks for implementation, don't implement it; revising the card's drafts is not implementing.)`;
18278
- BOARD_FOLDER = "<board folder>";
18279
- CARD_FILE = "<card file>";
18280
- PROJECT_FOLDER = "<project folder>";
18281
- PLANNING_FOR = /* @__PURE__ */ new Set(["create", "clarify", "resolve", "approve", "edit"]);
18282
- SPEC_SELECTOR_FOR = /* @__PURE__ */ new Set(["clarify", "resolve", "approve", "edit"]);
18283
- reflectedCards = (req) => req.cards ?? (req.id !== void 0 ? [req.id] : []);
18284
- }
18285
- });
18286
-
18287
- // ../cli/src/lib/agent/stop.ts
18288
- import { spawnSync as spawnSync4 } from "node:child_process";
18289
- import { randomUUID as randomUUID5 } from "node:crypto";
18290
- import fs47 from "node:fs";
18291
- import os6 from "node:os";
18292
- function markedInProc(entry, strict = false) {
18293
- const pids = [];
18294
- for (const name of fs47.readdirSync("/proc")) {
18295
- if (!/^\d+$/.test(name)) continue;
18296
- try {
18297
- if (fs47.readFileSync(`/proc/${name}/environ`, "utf8").split("\0").includes(entry)) pids.push(Number(name));
18298
- } catch (error) {
18299
- if (strict && !["ENOENT", "ESRCH"].includes(error.code ?? "")) {
18300
- const stat = fs47.statSync(`/proc/${name}`, { throwIfNoEntry: false });
18301
- if (stat?.uid === process.getuid?.()) throw error;
18302
- }
18303
- }
18304
- }
18305
- return pids;
18306
- }
18307
- function markedInPs(entry, roots) {
18308
- const ps = spawnSync4("ps", ["-A", "-E", "-ww", "-o", "pid=,ppid=,command="], {
18309
- encoding: "utf8",
18310
- maxBuffer: 64 * 1024 * 1024,
18311
- timeout: 5e3
18312
- });
18313
- if (ps.status !== 0 || !ps.stdout) throw new Error("\u65E0\u6CD5\u67E5\u8BE2\u8FDB\u7A0B\u6811\u3002");
18314
- const children = /* @__PURE__ */ new Map();
18315
- const listed2 = /* @__PURE__ */ new Set();
18316
- const found2 = /* @__PURE__ */ new Set();
18317
- for (const line of ps.stdout.split("\n")) {
18318
- const m = /^\s*(\d+)\s+(\d+)\s(.*)$/.exec(line);
18319
- if (!m) continue;
18320
- const pid = Number(m[1]);
18321
- const ppid = Number(m[2]);
18322
- listed2.add(pid);
18323
- children.set(ppid, [...children.get(ppid) ?? [], pid]);
18324
- if (`${m[3]} `.includes(` ${entry} `)) found2.add(pid);
18325
- }
18326
- const queue = [...found2, ...roots.filter((pid) => listed2.has(pid))];
18327
- for (const pid of queue) {
18328
- found2.add(pid);
18329
- for (const child of children.get(pid) ?? []) if (!found2.has(child)) queue.push(child);
18330
- }
18331
- return [...found2];
18332
- }
18333
- function marked(mark, roots = []) {
18334
- if (WINDOWS3 || !mark.value) return [];
18335
- const entry = `${mark.name}=${mark.value}`;
18336
- try {
18337
- const pids = process.platform === "linux" ? markedInProc(entry) : markedInPs(entry, roots);
18338
- return pids.filter((pid) => pid !== process.pid);
18339
- } catch {
18340
- return [];
18341
- }
18342
- }
18343
- function signal(pids, sig) {
18344
- for (const pid of pids) {
18345
- try {
18346
- process.kill(pid, sig);
18347
- } catch {
18348
- }
18349
- }
18350
- }
18351
- function startedUnder(list, root, since) {
18352
- const self = list.procs.find((p) => p.pid === root);
18353
- const from = since ?? self?.startedAt;
18354
- if (from === void 0 || list.bootedAt > from) return [];
18355
- if (self && self.startedAt > from) return [];
18356
- const found2 = self ? [root] : [];
18357
- const seen = /* @__PURE__ */ new Set([root]);
18358
- const queue = [{ pid: root, startedAt: from }];
18359
- for (const parent of queue) {
18360
- for (const p of list.procs) {
18361
- if (p.ppid !== parent.pid || seen.has(p.pid) || p.startedAt < parent.startedAt) continue;
18362
- seen.add(p.pid);
18363
- found2.push(p.pid);
18364
- queue.push(p);
18365
- }
18366
- }
18367
- return found2.reverse();
18368
- }
18369
- function readProcList(out) {
18370
- const procs = [];
18371
- let bootedAt;
18372
- for (const line of out.split(/\r?\n/)) {
18373
- const boot = /^boot (\d+)$/.exec(line.trim());
18374
- if (boot) bootedAt = Number(boot[1]);
18375
- const m = /^(\d+) (\d+) (\d+)$/.exec(line.trim());
18376
- if (m) procs.push({ pid: Number(m[1]), ppid: Number(m[2]), startedAt: Number(m[3]) });
18377
- }
18378
- return bootedAt !== void 0 && procs.length ? { procs, bootedAt } : void 0;
18379
- }
18380
- function procsOnWindows() {
18381
- const ps = spawnSync4("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", LIST_PROCS], {
18382
- encoding: "utf8",
18383
- maxBuffer: 64 * 1024 * 1024,
18384
- timeout: 5e3,
18385
- windowsHide: true
18386
- });
18387
- return ps.status === 0 && ps.stdout ? readProcList(ps.stdout) : void 0;
18388
- }
18389
- function killUnderOnWindows(pid, since) {
18390
- if (!WINDOWS3 || !pid) return;
18391
- const list = procsOnWindows();
18392
- if (!list) return;
18393
- const pids = startedUnder(list, pid, since).filter((p) => p !== process.pid);
18394
- if (!pids.length) return;
18395
- spawnSync4("taskkill", ["/F", ...pids.flatMap((p) => ["/PID", String(p)])], { stdio: "ignore", windowsHide: true, timeout: 1e4 });
18396
- }
18397
- function killMarked(mark, agent) {
18398
- if (WINDOWS3) {
18399
- if (agent) killUnderOnWindows(agent.pid, agent.since);
18400
- return;
18401
- }
18402
- signal(marked(mark), "SIGKILL");
18403
- }
18404
- function killTreeOnWindows(pid) {
18405
- if (!WINDOWS3 || !pid) return false;
18406
- const out = spawnSync4("taskkill", ["/PID", String(pid), "/T", "/F"], { stdio: "ignore", windowsHide: true, timeout: 1e4 });
18407
- return out.status === 0;
18408
- }
18409
- function later(ms, fn) {
18410
- const t = setTimeout(fn, ms);
18411
- if (typeof t.unref === "function") t.unref();
18412
- }
18413
- function endAgent(child, mark, graceMs) {
18414
- const term = (sig) => {
18415
- try {
18416
- child.kill(sig);
18417
- } catch {
18418
- }
18419
- };
18420
- if (killTreeOnWindows(child.pid)) return;
18421
- killUnderOnWindows(child.pid);
18422
- const seen = new Set(marked(mark, child.pid ? [child.pid] : []));
18423
- term("SIGTERM");
18424
- signal([...seen].filter((pid) => pid !== child.pid), "SIGTERM");
18425
- const round = (left) => {
18426
- const pids = marked(mark, [...seen]);
18427
- signal(pids, "SIGKILL");
18428
- for (const pid of pids) seen.add(pid);
18429
- if (pids.length && left > 1) later(KILL_ROUND_MS, () => round(left - 1));
18430
- };
18431
- later(graceMs, () => {
18432
- term("SIGKILL");
18433
- round(KILL_ROUNDS);
18434
- });
18435
- }
18436
- function killRunning() {
18437
- for (const [child, mark] of running) {
18438
- if (killTreeOnWindows(child.pid)) continue;
18439
- killUnderOnWindows(child.pid);
18440
- signal(marked(mark, child.pid ? [child.pid] : []), "SIGKILL");
18441
- try {
18442
- child.kill("SIGKILL");
18443
- } catch {
18444
- }
18530
+ `Don't ask me questions with human-in-the-loop. Leave any questions as open questions.`
18531
+ ].filter(Boolean).join(" ");
18532
+ case "resolve":
18533
+ return [
18534
+ `${kb}. Apply my answers to the open questions on task ${req.id} ${named2} following \`akb guide resolve\`.`,
18535
+ carriedNote(req),
18536
+ answeredNote(req.id, command),
18537
+ req.notes ? `Extra notes: ${req.notes}` : ""
18538
+ ].filter(Boolean).join(" ");
18445
18539
  }
18446
- running.clear();
18447
- }
18448
- function unlisten() {
18449
- for (const [sig, fn] of listening) process.removeListener(sig, fn);
18450
- listening.clear();
18451
- }
18452
- function onQuit(sig) {
18453
- killRunning();
18454
- unlisten();
18455
- if (process.listenerCount(sig) === 0) process.exit(128 + os6.constants.signals[sig]);
18456
18540
  }
18457
- function trackAgent(child, mark) {
18458
- if (!child.pid) return;
18459
- running.set(child, mark);
18460
- child.once("close", () => {
18461
- running.delete(child);
18462
- if (!running.size) unlisten();
18463
- });
18464
- for (const sig of QUIT_SIGNALS) {
18465
- if (listening.has(sig) || sig === "SIGINT" && interruptIsTheCallers) continue;
18466
- const fn = () => onQuit(sig);
18467
- listening.set(sig, fn);
18468
- process.on(sig, fn);
18469
- }
18541
+ function discardedCardsPrompt(cards = []) {
18542
+ if (!cards.length) return "";
18543
+ return "The user discarded the cards listed below. Their absence is intentional. Do not restore or recreate them, including under new IDs or filenames. Continue the remaining work from the current board, preserving the other cards.\n\n" + cards.map((c) => `- #${c.id}: ${c.path}`).join("\n");
18470
18544
  }
18471
- var runMark, stopMark, markEnv, WINDOWS3, KILL_ROUND_MS, KILL_ROUNDS, LIST_PROCS, running, QUIT_SIGNALS, listening, interruptIsTheCallers;
18472
- var init_stop = __esm({
18473
- "../cli/src/lib/agent/stop.ts"() {
18545
+ var RESUME_PROMPT, DELIVERY_RESUME, DELIVERY_RESUME_CARD, RESTARTABLE, RESTART_LEAD, restartable, NO_IMPLEMENT, BOARD_FOLDER, CARD_FILE, PROJECT_FOLDER, PLANNING_FOR, SPEC_SELECTOR_FOR, reflectedCards;
18546
+ var init_prompts = __esm({
18547
+ "../cli/src/lib/agent/prompts.ts"() {
18474
18548
  "use strict";
18549
+ init_cards();
18550
+ init_memory();
18551
+ init_memories();
18552
+ init_paths();
18553
+ init_agents();
18554
+ init_command();
18555
+ init_deliveries();
18475
18556
  init_env();
18476
- runMark = (sessionId) => ({ name: RUN_ENV, value: sessionId });
18477
- stopMark = () => ({ name: STOP_ENV, value: randomUUID5() });
18478
- markEnv = (env2, mark) => ({ ...env2, [mark.name]: mark.value });
18479
- WINDOWS3 = process.platform === "win32";
18480
- KILL_ROUND_MS = 300;
18481
- KILL_ROUNDS = 3;
18482
- LIST_PROCS = [
18483
- "$ErrorActionPreference = 'Stop'",
18484
- "Get-CimInstance Win32_Process | ForEach-Object { if ($_.CreationDate) { '{0} {1} {2}' -f $_.ProcessId, $_.ParentProcessId, ([DateTimeOffset]$_.CreationDate).ToUnixTimeMilliseconds() } }",
18485
- "'boot {0}' -f ([DateTimeOffset](Get-CimInstance Win32_OperatingSystem).LastBootUpTime).ToUnixTimeMilliseconds()"
18486
- ].join("; ");
18487
- running = /* @__PURE__ */ new Map();
18488
- QUIT_SIGNALS = ["SIGTERM", "SIGINT", "SIGHUP"];
18489
- listening = /* @__PURE__ */ new Map();
18490
- interruptIsTheCallers = false;
18557
+ init_facts();
18558
+ init_outputs();
18559
+ init_archive();
18560
+ init_flows();
18561
+ init_resolve();
18562
+ init_runner();
18563
+ init_workflows();
18564
+ init_catalog2();
18565
+ init_roles();
18566
+ init_rules2();
18567
+ init_settings2();
18568
+ init_types3();
18569
+ RESUME_PROMPT = [
18570
+ `Continue. The previous run ended before it finished the task.`,
18571
+ `Pick up where you left off and carry it through.`
18572
+ ].join(" ");
18573
+ DELIVERY_RESUME = [
18574
+ `Continue delivery %s. The previous run ended before the delivery finished.`,
18575
+ `Re-enter the flow from the top and check each step's precondition before you do it \u2014 work that is already done is done, so do not repeat it.`
18576
+ ].join(" ");
18577
+ DELIVERY_RESUME_CARD = `Build the card as the delivery holds it, not as the file reads now: \`%c\` prints the approved copy.`;
18578
+ RESTARTABLE = /* @__PURE__ */ new Set([
18579
+ "implement",
18580
+ "clarify",
18581
+ "resolve",
18582
+ "approve",
18583
+ "archive",
18584
+ "spec",
18585
+ "conflict",
18586
+ "scheduled"
18587
+ ]);
18588
+ RESTART_LEAD = [
18589
+ `The session you were continuing is gone, and this one holds nothing of it.`,
18590
+ `Do the task below from the top, checking each step's precondition before you do it \u2014 work an earlier run already finished is done, so don't repeat it.`
18591
+ ].join(" ");
18592
+ restartable = (run2) => (run2.cardId !== null || !!run2.deliveryId) && RESTARTABLE.has(run2.action);
18593
+ NO_IMPLEMENT = `(Unless the request explicitly asks for implementation, don't implement it; revising the card's drafts is not implementing.)`;
18594
+ BOARD_FOLDER = "<board folder>";
18595
+ CARD_FILE = "<card file>";
18596
+ PROJECT_FOLDER = "<project folder>";
18597
+ PLANNING_FOR = /* @__PURE__ */ new Set(["create", "clarify", "resolve", "approve", "edit"]);
18598
+ SPEC_SELECTOR_FOR = /* @__PURE__ */ new Set(["clarify", "resolve", "approve", "edit"]);
18599
+ reflectedCards = (req) => req.cards ?? (req.id !== void 0 ? [req.id] : []);
18491
18600
  }
18492
18601
  });
18493
18602
 
18494
18603
  // ../cli/src/lib/agent/sessions.ts
18495
18604
  import { randomUUID as randomUUID6 } from "node:crypto";
18496
- import fs48 from "node:fs";
18497
- import path52 from "node:path";
18605
+ import fs49 from "node:fs";
18606
+ import path54 from "node:path";
18498
18607
  function reap(runs, reaped = [], restore = []) {
18499
18608
  let changed = false;
18500
18609
  const now = Date.now();
@@ -18514,7 +18623,7 @@ function reap(runs, reaped = [], restore = []) {
18514
18623
  }
18515
18624
  function stampDuration(run2, endedAt) {
18516
18625
  try {
18517
- fs48.appendFileSync(run2.logPath, durationLine(endedAt - run2.startedAt));
18626
+ fs49.appendFileSync(run2.logPath, durationLine(endedAt - run2.startedAt));
18518
18627
  } catch {
18519
18628
  }
18520
18629
  }
@@ -18528,7 +18637,7 @@ function cardNow(cardId2) {
18528
18637
  try {
18529
18638
  const file = cardFile(cardId2);
18530
18639
  if (!file) return null;
18531
- const { meta } = parseFrontmatter(fs48.readFileSync(file, "utf8"));
18640
+ const { meta } = parseFrontmatter(fs49.readFileSync(file, "utf8"));
18532
18641
  if (!meta) return null;
18533
18642
  return { status: meta.status || "todo", questions: openOf(meta.questions).length, title: meta.title };
18534
18643
  } catch {
@@ -18665,9 +18774,9 @@ function leftBoardOnLanding(id) {
18665
18774
  function archivedRejected(id) {
18666
18775
  const filed = locateArchived(id);
18667
18776
  if (!filed) return null;
18668
- const file = filed.kind === "group" ? path52.join(filed.target, "root.md") : filed.target;
18777
+ const file = filed.kind === "group" ? path54.join(filed.target, "root.md") : filed.target;
18669
18778
  try {
18670
- return !!parseFrontmatter(fs48.readFileSync(file, "utf8")).meta?.rejected;
18779
+ return !!parseFrontmatter(fs49.readFileSync(file, "utf8")).meta?.rejected;
18671
18780
  } catch {
18672
18781
  return false;
18673
18782
  }
@@ -18924,7 +19033,7 @@ function openRun(req, prompt, notes = [], sessionId = randomUUID6(), said3) {
18924
19033
  return { run: record, spec };
18925
19034
  }
18926
19035
  function planningSession(cardId2, cwd = REPO_ROOT) {
18927
- const here = (dir) => !dir || path52.resolve(dir) === cwd;
19036
+ const here = (dir) => !dir || path54.resolve(dir) === cwd;
18928
19037
  const ended = readRuns().filter((r) => r.status === "done").sort((a, b) => b.startedAt - a.startedAt);
18929
19038
  const of = (run3, fork) => {
18930
19039
  if (!here(run3.cwd)) return { none: "elsewhere" };
@@ -19146,8 +19255,8 @@ async function resumeHeld(prev, plan, sessionId, resuming, fresh) {
19146
19255
  clearAsks(prev.sessionId);
19147
19256
  try {
19148
19257
  if (prev.action !== "create") {
19149
- fs48.unlinkSync(prev.logPath);
19150
- fs48.rmSync(eventsPathOf(prev.sessionId), { force: true });
19258
+ fs49.unlinkSync(prev.logPath);
19259
+ fs49.rmSync(eventsPathOf(prev.sessionId), { force: true });
19151
19260
  }
19152
19261
  } catch {
19153
19262
  }
@@ -19171,8 +19280,8 @@ function forgetRun(sessionId) {
19171
19280
  if (!gone) return;
19172
19281
  clearAsks(sessionId);
19173
19282
  try {
19174
- fs48.unlinkSync(gone.logPath);
19175
- fs48.rmSync(eventsPathOf(gone.sessionId), { force: true });
19283
+ fs49.unlinkSync(gone.logPath);
19284
+ fs49.rmSync(eventsPathOf(gone.sessionId), { force: true });
19176
19285
  } catch {
19177
19286
  }
19178
19287
  }
@@ -19201,14 +19310,14 @@ function noteSpecRefusals(sessionId, refused) {
19201
19310
  }
19202
19311
  function clearAsks(sessionId) {
19203
19312
  try {
19204
- fs48.unlinkSync(asksPathOf(sessionId));
19313
+ fs49.unlinkSync(asksPathOf(sessionId));
19205
19314
  } catch {
19206
19315
  }
19207
19316
  }
19208
19317
  function readAsks(sessionId) {
19209
19318
  let data;
19210
19319
  try {
19211
- data = JSON.parse(fs48.readFileSync(asksPathOf(sessionId), "utf8"));
19320
+ data = JSON.parse(fs49.readFileSync(asksPathOf(sessionId), "utf8"));
19212
19321
  } catch {
19213
19322
  return { asks: [], refines: [], refused: [] };
19214
19323
  }
@@ -19235,10 +19344,10 @@ function readAsks(sessionId) {
19235
19344
  return { asks: asks.filter((a) => !discarded.has(a.cardId)), refines: refines.filter((a) => !discarded.has(a.cardId)), refused };
19236
19345
  }
19237
19346
  function writeAsks(sessionId, file) {
19238
- fs48.mkdirSync(SESSIONS_DIR, { recursive: true });
19347
+ fs49.mkdirSync(SESSIONS_DIR, { recursive: true });
19239
19348
  const tmp = `${asksPathOf(sessionId)}.tmp`;
19240
- fs48.writeFileSync(tmp, JSON.stringify(file, null, 2) + "\n");
19241
- fs48.renameSync(tmp, asksPathOf(sessionId));
19349
+ fs49.writeFileSync(tmp, JSON.stringify(file, null, 2) + "\n");
19350
+ fs49.renameSync(tmp, asksPathOf(sessionId));
19242
19351
  }
19243
19352
  function markSpawned(sessionId, pid) {
19244
19353
  patch(sessionId, (r) => {
@@ -19326,7 +19435,10 @@ async function cancelDelivery(id) {
19326
19435
  if (!id.trim()) return { ok: false, ...refusal("deliveryUnnamed", "name the delivery to cancel", { action: "cancel" }) };
19327
19436
  const delivery = namedDelivery(id);
19328
19437
  if (!delivery) return { ok: false, ...unknownDelivery(id) };
19329
- if (delivery.status !== "active") return { ok: true, deliveryId: delivery.deliveryId };
19438
+ if (delivery.status !== "active") {
19439
+ tidyCheckout(delivery.deliveryId);
19440
+ return { ok: true, deliveryId: delivery.deliveryId };
19441
+ }
19330
19442
  endDelivery(delivery.deliveryId, "cancelled");
19331
19443
  const live = readRuns().find((r) => r.status === "running" && r.deliveryId === delivery.deliveryId && r.action !== "sub");
19332
19444
  if (live) await stopRun(live.sessionId);
@@ -19392,12 +19504,12 @@ async function discardDelivery(id) {
19392
19504
  return { ok: true, deliveryId: delivery.deliveryId };
19393
19505
  }
19394
19506
  function writeSpec(spec) {
19395
- fs48.mkdirSync(SESSIONS_DIR, { recursive: true });
19396
- fs48.writeFileSync(specPathOf(spec.sessionId), JSON.stringify(spec, null, 2) + "\n");
19507
+ fs49.mkdirSync(SESSIONS_DIR, { recursive: true });
19508
+ fs49.writeFileSync(specPathOf(spec.sessionId), JSON.stringify(spec, null, 2) + "\n");
19397
19509
  }
19398
19510
  function readSpec(sessionId) {
19399
19511
  try {
19400
- const spec = JSON.parse(fs48.readFileSync(specPathOf(sessionId), "utf8"));
19512
+ const spec = JSON.parse(fs49.readFileSync(specPathOf(sessionId), "utf8"));
19401
19513
  return spec && Array.isArray(spec.plan?.argv) ? spec : null;
19402
19514
  } catch {
19403
19515
  return null;
@@ -19405,28 +19517,28 @@ function readSpec(sessionId) {
19405
19517
  }
19406
19518
  function dropSpec(sessionId) {
19407
19519
  try {
19408
- fs48.unlinkSync(specPathOf(sessionId));
19520
+ fs49.unlinkSync(specPathOf(sessionId));
19409
19521
  } catch {
19410
19522
  }
19411
19523
  }
19412
19524
  async function acquireIndexLock(giveUp) {
19413
19525
  const until = Date.now() + INDEX_WAIT_MS;
19414
- const holder = path52.join(INDEX_LOCK2, "holder");
19526
+ const holder = path54.join(INDEX_LOCK2, "holder");
19415
19527
  for (; ; ) {
19416
19528
  if (giveUp()) return null;
19417
19529
  try {
19418
- fs48.mkdirSync(INDEX_LOCK2, { recursive: false });
19419
- fs48.writeFileSync(holder, String(process.pid));
19420
- return () => fs48.rmSync(INDEX_LOCK2, { recursive: true, force: true });
19530
+ fs49.mkdirSync(INDEX_LOCK2, { recursive: false });
19531
+ fs49.writeFileSync(holder, String(process.pid));
19532
+ return () => fs49.rmSync(INDEX_LOCK2, { recursive: true, force: true });
19421
19533
  } catch (err) {
19422
19534
  const code = err.code;
19423
19535
  if (code === "ENOENT") {
19424
- fs48.mkdirSync(path52.dirname(INDEX_LOCK2), { recursive: true });
19536
+ fs49.mkdirSync(path54.dirname(INDEX_LOCK2), { recursive: true });
19425
19537
  continue;
19426
19538
  }
19427
19539
  if (code !== "EEXIST") throw err;
19428
19540
  if (!pidAlive(Number(readHolder(holder)))) {
19429
- fs48.rmSync(INDEX_LOCK2, { recursive: true, force: true });
19541
+ fs49.rmSync(INDEX_LOCK2, { recursive: true, force: true });
19430
19542
  continue;
19431
19543
  }
19432
19544
  if (Date.now() > until) return null;
@@ -19436,7 +19548,7 @@ async function acquireIndexLock(giveUp) {
19436
19548
  }
19437
19549
  function readHolder(file) {
19438
19550
  try {
19439
- return fs48.readFileSync(file, "utf8").trim();
19551
+ return fs49.readFileSync(file, "utf8").trim();
19440
19552
  } catch {
19441
19553
  return String(process.pid);
19442
19554
  }
@@ -19527,8 +19639,8 @@ var init_sessions = __esm({
19527
19639
  conflict: "implementing",
19528
19640
  hook: "implementing"
19529
19641
  };
19530
- specPathOf = (sessionId) => path52.join(SESSIONS_DIR, `${sessionId}.plan.json`);
19531
- asksPathOf = (sessionId) => path52.join(SESSIONS_DIR, `${sessionId}.asks.json`);
19642
+ specPathOf = (sessionId) => path54.join(SESSIONS_DIR, `${sessionId}.plan.json`);
19643
+ asksPathOf = (sessionId) => path54.join(SESSIONS_DIR, `${sessionId}.asks.json`);
19532
19644
  carriesSession = (r) => !!(r.resumedFrom || r.continues && !r.continues.fork);
19533
19645
  canPickUp = (r) => (r.status === "error" || r.status === "interrupted" || r.status === "stopped") && !(r.action === "create" && readRuns().some((next) => next.resumedFrom === r.sessionId));
19534
19646
  neverStarted = (r) => r.unspawned === true || !!r.errorWhy?.some((why) => why.kind === "notInstalled");
@@ -19662,8 +19774,8 @@ var init_questions = __esm({
19662
19774
  });
19663
19775
 
19664
19776
  // ../cli/src/lib/agent/workflow-cards.ts
19665
- import fs49 from "node:fs";
19666
- import path53 from "node:path";
19777
+ import fs50 from "node:fs";
19778
+ import path55 from "node:path";
19667
19779
  function cardsOnWorkflow(id) {
19668
19780
  const wanted = id || DEFAULT_WORKFLOW;
19669
19781
  const ids = [];
@@ -19674,12 +19786,12 @@ function cardsOnWorkflow(id) {
19674
19786
  return [];
19675
19787
  }
19676
19788
  for (const file of files2) {
19677
- const base = path53.basename(file);
19678
- const card = base === "root.md" ? idPrefix(path53.basename(path53.dirname(file))) : idPrefix(base);
19789
+ const base = path55.basename(file);
19790
+ const card = base === "root.md" ? idPrefix(path55.basename(path55.dirname(file))) : idPrefix(base);
19679
19791
  if (card === null) continue;
19680
19792
  let meta;
19681
19793
  try {
19682
- meta = parseFrontmatter(fs49.readFileSync(file, "utf8")).meta;
19794
+ meta = parseFrontmatter(fs50.readFileSync(file, "utf8")).meta;
19683
19795
  } catch {
19684
19796
  continue;
19685
19797
  }
@@ -19737,12 +19849,12 @@ var init_workflow_cards = __esm({
19737
19849
  });
19738
19850
 
19739
19851
  // ../cli/src/lib/readme.ts
19740
- import fs50 from "node:fs";
19741
- import path54 from "node:path";
19852
+ import fs51 from "node:fs";
19853
+ import path56 from "node:path";
19742
19854
  function stripReadmeRefs(target) {
19743
- if (!fs50.existsSync(README)) return [];
19855
+ if (!fs51.existsSync(README)) return [];
19744
19856
  const needle = target.kind === "group" ? `](${target.rel}/` : `](${target.rel})`;
19745
- const lines = fs50.readFileSync(README, "utf8").split("\n");
19857
+ const lines = fs51.readFileSync(README, "utf8").split("\n");
19746
19858
  const out = [];
19747
19859
  const removed = [];
19748
19860
  let i = 0;
@@ -19764,20 +19876,20 @@ function stripReadmeRefs(target) {
19764
19876
  out.push(line);
19765
19877
  i++;
19766
19878
  }
19767
- if (removed.length) fs50.writeFileSync(README, out.join("\n"));
19879
+ if (removed.length) fs51.writeFileSync(README, out.join("\n"));
19768
19880
  return removed;
19769
19881
  }
19770
19882
  function addReadmeRef(id, title, relPath) {
19771
- if (!fs50.existsSync(README)) return false;
19772
- const link2 = relPath.split(path54.sep).join("/");
19883
+ if (!fs51.existsSync(README)) return false;
19884
+ const link2 = relPath.split(path56.sep).join("/");
19773
19885
  const bullet = `- [#${id} ${title}](${link2})`;
19774
19886
  const heading = `## ${TASKS_HEADING}`;
19775
- let lines = fs50.readFileSync(README, "utf8").split("\n");
19887
+ let lines = fs51.readFileSync(README, "utf8").split("\n");
19776
19888
  const hi = lines.findIndex((l) => l.trim().toLowerCase() === heading.toLowerCase());
19777
19889
  if (hi === -1) {
19778
19890
  while (lines.length && lines[lines.length - 1].trim() === "") lines.pop();
19779
19891
  lines.push("", heading, "", bullet);
19780
- fs50.writeFileSync(README, lines.join("\n") + "\n");
19892
+ fs51.writeFileSync(README, lines.join("\n") + "\n");
19781
19893
  return true;
19782
19894
  }
19783
19895
  let end2 = hi + 1;
@@ -19791,15 +19903,15 @@ function addReadmeRef(id, title, relPath) {
19791
19903
  const at = lastBullet !== -1 ? lastBullet + 1 : lines[hi + 1] === "" ? hi + 2 : hi + 1;
19792
19904
  lines.splice(at, 0, bullet);
19793
19905
  }
19794
- fs50.writeFileSync(README, lines.join("\n"));
19906
+ fs51.writeFileSync(README, lines.join("\n"));
19795
19907
  return true;
19796
19908
  }
19797
19909
  function repointReadmeLink(id, oldRel, newRel, title) {
19798
- if (!fs50.existsSync(README)) return false;
19799
- const oldLink = oldRel.split(path54.sep).join("/");
19800
- const newLink = newRel.split(path54.sep).join("/");
19910
+ if (!fs51.existsSync(README)) return false;
19911
+ const oldLink = oldRel.split(path56.sep).join("/");
19912
+ const newLink = newRel.split(path56.sep).join("/");
19801
19913
  const linkRe = new RegExp(`\\[#${id}\\b[^\\]]*\\]\\(${escapeRegex(oldLink)}\\)`);
19802
- const lines = fs50.readFileSync(README, "utf8").split("\n");
19914
+ const lines = fs51.readFileSync(README, "utf8").split("\n");
19803
19915
  let changed = false;
19804
19916
  for (let i = 0; i < lines.length; i++) {
19805
19917
  if (!lines[i].includes(`](${oldLink})`)) continue;
@@ -19809,7 +19921,7 @@ function repointReadmeLink(id, oldRel, newRel, title) {
19809
19921
  changed = true;
19810
19922
  }
19811
19923
  }
19812
- if (changed) fs50.writeFileSync(README, lines.join("\n"));
19924
+ if (changed) fs51.writeFileSync(README, lines.join("\n"));
19813
19925
  return changed;
19814
19926
  }
19815
19927
  var isTableRow, isBulletStart, bulletIndent, TASKS_HEADING, escapeRegex;
@@ -19826,14 +19938,14 @@ var init_readme = __esm({
19826
19938
  });
19827
19939
 
19828
19940
  // ../cli/src/lib/reconcile.ts
19829
- import fs51 from "node:fs";
19830
- import path55 from "node:path";
19941
+ import fs52 from "node:fs";
19942
+ import path57 from "node:path";
19831
19943
  function liveIds() {
19832
19944
  const ids = /* @__PURE__ */ new Set();
19833
19945
  for (const file of walkMd(TODO)) {
19834
- const base = path55.basename(file);
19946
+ const base = path57.basename(file);
19835
19947
  if (base === "README.md") continue;
19836
- const id = base === "root.md" ? idPrefix(path55.basename(path55.dirname(file))) : idPrefix(base);
19948
+ const id = base === "root.md" ? idPrefix(path57.basename(path57.dirname(file))) : idPrefix(base);
19837
19949
  if (id != null) ids.add(id);
19838
19950
  }
19839
19951
  return ids;
@@ -19841,16 +19953,16 @@ function liveIds() {
19841
19953
  function indexableCards() {
19842
19954
  const out = [];
19843
19955
  for (const dir of walkDirs(TODO)) {
19844
- const id = idPrefix(path55.basename(dir));
19845
- if (id != null && fs51.existsSync(path55.join(dir, "root.md"))) {
19846
- out.push({ id, rel: path55.join(path55.relative(TODO, dir), "root.md").split(path55.sep).join("/") });
19956
+ const id = idPrefix(path57.basename(dir));
19957
+ if (id != null && fs52.existsSync(path57.join(dir, "root.md"))) {
19958
+ out.push({ id, rel: path57.join(path57.relative(TODO, dir), "root.md").split(path57.sep).join("/") });
19847
19959
  }
19848
19960
  }
19849
19961
  for (const file of walkMd(TODO)) {
19850
- const base = path55.basename(file);
19962
+ const base = path57.basename(file);
19851
19963
  if (base === "README.md" || base === "root.md") continue;
19852
- const relTODO = path55.relative(TODO, file);
19853
- const segs = relTODO.split(path55.sep);
19964
+ const relTODO = path57.relative(TODO, file);
19965
+ const segs = relTODO.split(path57.sep);
19854
19966
  if (segs.length !== 1) continue;
19855
19967
  const id = idPrefix(base);
19856
19968
  if (id != null) out.push({ id, rel: segs.join("/") });
@@ -19859,7 +19971,7 @@ function indexableCards() {
19859
19971
  }
19860
19972
  function readmeComplaints(fix) {
19861
19973
  const said3 = [];
19862
- const lines = fs51.readFileSync(README, "utf8").split("\n");
19974
+ const lines = fs52.readFileSync(README, "utf8").split("\n");
19863
19975
  const indexed = /* @__PURE__ */ new Set();
19864
19976
  let fixed = 0;
19865
19977
  for (let i = 0; i < lines.length; i++) {
@@ -19868,13 +19980,13 @@ function readmeComplaints(fix) {
19868
19980
  const id = Number(m[1]);
19869
19981
  const linkPath = m[2];
19870
19982
  indexed.add(id);
19871
- if (fs51.existsSync(path55.join(TODO, linkPath))) continue;
19983
+ if (fs52.existsSync(path57.join(TODO, linkPath))) continue;
19872
19984
  const found2 = locate(id);
19873
19985
  if (!found2) {
19874
19986
  said3.push(`README links #${id} \u2192 ${linkPath}, but no card with that id exists (removed by hand?). Drop the entry or restore the file.`);
19875
19987
  continue;
19876
19988
  }
19877
- const want = (found2.kind === "group" ? path55.join(found2.rel, "root.md") : found2.rel).split(path55.sep).join("/");
19989
+ const want = (found2.kind === "group" ? path57.join(found2.rel, "root.md") : found2.rel).split(path57.sep).join("/");
19878
19990
  if (want === linkPath) continue;
19879
19991
  if (!fix) {
19880
19992
  said3.push(`README links #${id} \u2192 ${linkPath}, which isn't there any more; the card is at ${want}.`);
@@ -19884,7 +19996,7 @@ function readmeComplaints(fix) {
19884
19996
  said3.push(`README link #${id} pointed at missing ${linkPath} \u2192 repointed to ${want}.`);
19885
19997
  fixed++;
19886
19998
  }
19887
- if (fixed) fs51.writeFileSync(README, lines.join("\n"));
19999
+ if (fixed) fs52.writeFileSync(README, lines.join("\n"));
19888
20000
  for (const c of indexableCards()) {
19889
20001
  if (!indexed.has(c.id)) {
19890
20002
  said3.push(`card ${c.rel} (#${c.id}) is not in the README index. Add it under "## ${TASKS_HEADING}".`);
@@ -19896,11 +20008,11 @@ function crossRefComplaints() {
19896
20008
  const said3 = [];
19897
20009
  const live = liveIds();
19898
20010
  for (const file of walkMd(TODO)) {
19899
- const base = path55.basename(file);
20011
+ const base = path57.basename(file);
19900
20012
  if (base === "README.md") continue;
19901
- const ownerId = base === "root.md" ? idPrefix(path55.basename(path55.dirname(file))) : idPrefix(base);
20013
+ const ownerId = base === "root.md" ? idPrefix(path57.basename(path57.dirname(file))) : idPrefix(base);
19902
20014
  if (ownerId == null) continue;
19903
- const { meta } = parseFrontmatter(fs51.readFileSync(file, "utf8"));
20015
+ const { meta } = parseFrontmatter(fs52.readFileSync(file, "utf8"));
19904
20016
  if (!meta) continue;
19905
20017
  for (const field2 of ["blocked_by", "related"]) {
19906
20018
  for (const ref of meta[field2] || []) {
@@ -19913,11 +20025,11 @@ function crossRefComplaints() {
19913
20025
  return said3;
19914
20026
  }
19915
20027
  function reconcileBoard() {
19916
- if (!fs51.existsSync(README)) return;
20028
+ if (!fs52.existsSync(README)) return;
19917
20029
  for (const line of [...readmeComplaints(true), ...crossRefComplaints()]) warn2(line);
19918
20030
  }
19919
20031
  function boardComplaints() {
19920
- if (!fs51.existsSync(README)) return [];
20032
+ if (!fs52.existsSync(README)) return [];
19921
20033
  try {
19922
20034
  return [...readmeComplaints(false), ...crossRefComplaints()];
19923
20035
  } catch {
@@ -19937,8 +20049,8 @@ var init_reconcile = __esm({
19937
20049
  });
19938
20050
 
19939
20051
  // ../cli/src/commands/card.ts
19940
- import fs52 from "node:fs";
19941
- import path56 from "node:path";
20052
+ import fs53 from "node:fs";
20053
+ import path58 from "node:path";
19942
20054
  function defaultBody() {
19943
20055
  return [
19944
20056
  "<\u7528\u4E00\u5C0F\u6BB5\u8BDD\u5148\u8BB2\u53D1\u751F\u4E86\u4EC0\u4E48\u3001\u7528\u6237\u9047\u5230\u4EC0\u4E48\uFF0C\u518D\u8BB2\u8FD9\u5F20\u5361\u8981\u8FBE\u5230\u7684\u7ED3\u679C\u3002>",
@@ -19964,7 +20076,7 @@ function bodyFromFile(opts) {
19964
20076
  if (opts.body === false) die("pass --body-file or --no-body, not both");
19965
20077
  let text5;
19966
20078
  try {
19967
- text5 = fs52.readFileSync(opts.bodyFile, "utf8");
20079
+ text5 = fs53.readFileSync(opts.bodyFile, "utf8");
19968
20080
  } catch {
19969
20081
  die(`can't read ${opts.bodyFile} \u2014 write the body to a file, then pass its path`, { kind: "needs-input" });
19970
20082
  }
@@ -19996,20 +20108,20 @@ function cmdCreate(opts) {
19996
20108
  const start = opts.id;
19997
20109
  const slug2 = slugify(opts.slug !== void 0 ? opts.slug : title);
19998
20110
  const fileRel = `${start}-${slug2}.md`;
19999
- const file = path56.join(TODO, fileRel);
20111
+ const file = path58.join(TODO, fileRel);
20000
20112
  const held4 = locate(start) ?? locateArchived(start);
20001
- if (held4 && path56.basename(held4.target) === `${start}-${slug2}.md`) {
20113
+ if (held4 && path58.basename(held4.target) === `${start}-${slug2}.md`) {
20002
20114
  say(start);
20003
20115
  say(` ${rel(held4.target)} was already written`);
20004
20116
  return { id: start, ids: [start], title, file: rel(held4.target), indexed: false };
20005
20117
  }
20006
20118
  if (held4) die(`#${start} is already a card here (${rel(held4.target)})`, { kind: "id-taken", id: start });
20007
- if (fs52.existsSync(file)) die(`${rel(file)} already exists \u2014 pick a different --slug`);
20119
+ if (fs53.existsSync(file)) die(`${rel(file)} already exists \u2014 pick a different --slug`);
20008
20120
  countBoardEvent(triage ? "cards_created_proposed" : "cards_created_asked");
20009
20121
  const meta = { title, priority, status: "todo", blocked_by, related, modules, workflow, triage, source, questions };
20010
20122
  const scaffolded = !written && opts.body !== false;
20011
20123
  const body = written ?? (scaffolded ? defaultBody() : "");
20012
- fs52.writeFileSync(file, serializeFrontmatter(meta) + "\n\n" + body);
20124
+ fs53.writeFileSync(file, serializeFrontmatter(meta) + "\n\n" + body);
20013
20125
  const indexed = addReadmeRef(start, title, fileRel);
20014
20126
  say(start);
20015
20127
  say(
@@ -20047,8 +20159,8 @@ function cmdUpdate(id, flags) {
20047
20159
  if (creating) die(creating, { kind: "card-being-created", id });
20048
20160
  const found2 = locate(id);
20049
20161
  if (!found2) die(`no task with id ${id} under ${rel(TODO)}`, { kind: "card-not-found", id });
20050
- const file = found2.kind === "group" ? path56.join(found2.target, "root.md") : found2.target;
20051
- const { meta, body } = parseFrontmatter(fs52.readFileSync(file, "utf8"));
20162
+ const file = found2.kind === "group" ? path58.join(found2.target, "root.md") : found2.target;
20163
+ const { meta, body } = parseFrontmatter(fs53.readFileSync(file, "utf8"));
20052
20164
  if (!meta) die(`${rel(file)} has no frontmatter \u2014 run \`migrate\` first`);
20053
20165
  if (flags.blockedBy !== void 0 && flags.addBlockedBy !== void 0) die("--blocked-by replaces the list and --add-blocked-by appends to it \u2014 pass one");
20054
20166
  if (flags.related !== void 0 && flags.addRelated !== void 0) die("--related replaces the list and --add-related appends to it \u2014 pass one");
@@ -20128,26 +20240,26 @@ function cmdUpdate(id, flags) {
20128
20240
  meta.status = "todo";
20129
20241
  changes.push("status\u2192todo (open questions)");
20130
20242
  }
20131
- const curRel = path56.relative(TODO, file);
20243
+ const curRel = path58.relative(TODO, file);
20132
20244
  const isSubtask = found2.kind === "file" && enclosingGroupRoot(file) !== null;
20133
- let base = path56.basename(file);
20245
+ let base = path58.basename(file);
20134
20246
  if (flags.slug !== void 0) {
20135
20247
  if (found2.kind === "group") die("renaming a group root by script is not supported");
20136
20248
  base = `${id}-${slugify(flags.slug)}.md`;
20137
20249
  }
20138
- const destRel = path56.join(path56.dirname(curRel), base);
20139
- const dest = path56.join(TODO, destRel);
20250
+ const destRel = path58.join(path58.dirname(curRel), base);
20251
+ const dest = path58.join(TODO, destRel);
20140
20252
  const moving = dest !== file;
20141
- if (moving && fs52.existsSync(dest)) die(`${rel(dest)} already exists`);
20142
- fs52.writeFileSync(file, serializeFrontmatter(meta) + "\n" + body);
20143
- if (moving) fs52.renameSync(file, dest);
20253
+ if (moving && fs53.existsSync(dest)) die(`${rel(dest)} already exists`);
20254
+ fs53.writeFileSync(file, serializeFrontmatter(meta) + "\n" + body);
20255
+ if (moving) fs53.renameSync(file, dest);
20144
20256
  if (isSubtask) {
20145
20257
  if (moving || changes.includes("title")) repointReadmeLink(id, curRel, destRel, meta.title);
20146
- if (moving) changes.push(`renamed \u2192 ${destRel.split(path56.sep).join("/")}`);
20258
+ if (moving) changes.push(`renamed \u2192 ${destRel.split(path58.sep).join("/")}`);
20147
20259
  } else if (moving) {
20148
20260
  stripReadmeRefs({ kind: "file", rel: curRel });
20149
20261
  addReadmeRef(id, meta.title, destRel);
20150
- changes.push(`renamed \u2192 ${destRel.split(path56.sep).join("/")}`);
20262
+ changes.push(`renamed \u2192 ${destRel.split(path58.sep).join("/")}`);
20151
20263
  } else if (changes.includes("title")) {
20152
20264
  stripReadmeRefs({ kind: "file", rel: curRel });
20153
20265
  addReadmeRef(id, meta.title, curRel);
@@ -20159,8 +20271,8 @@ function cmdUpdateQuestions(id, input) {
20159
20271
  const ops = readQuestionOps(input.ops ?? []);
20160
20272
  const found2 = locate(id);
20161
20273
  if (!found2) die(`no task with id ${id} under ${rel(TODO)}`, { kind: "card-not-found", id });
20162
- const file = found2.kind === "group" ? path56.join(found2.target, "root.md") : found2.target;
20163
- const { meta, body } = parseFrontmatter(fs52.readFileSync(file, "utf8"));
20274
+ const file = found2.kind === "group" ? path58.join(found2.target, "root.md") : found2.target;
20275
+ const { meta, body } = parseFrontmatter(fs53.readFileSync(file, "utf8"));
20164
20276
  if (!meta) die(`${rel(file)} has no frontmatter \u2014 run \`migrate\` first`);
20165
20277
  if (insideRun() && ops.some((op) => op.kind === "skip" || op.kind === "unskip")) {
20166
20278
  die("only the user skips or reopens a question \u2014 a run leaves questions for them to answer");
@@ -20233,7 +20345,7 @@ function cmdUpdateQuestions(id, input) {
20233
20345
  meta.status = "todo";
20234
20346
  changes.push("status\u2192todo (open questions)");
20235
20347
  }
20236
- fs52.writeFileSync(file, serializeFrontmatter(meta) + "\n" + body);
20348
+ fs53.writeFileSync(file, serializeFrontmatter(meta) + "\n" + body);
20237
20349
  for (const kind of closed2) countBoardEvent(kind);
20238
20350
  settleDelivery2(id, skipped, unskipped);
20239
20351
  say(
@@ -20307,13 +20419,13 @@ var init_config_template = __esm({
20307
20419
  });
20308
20420
 
20309
20421
  // ../cli/src/lib/agent/local.ts
20310
- import fs53 from "node:fs";
20311
- import path57 from "node:path";
20422
+ import fs54 from "node:fs";
20423
+ import path59 from "node:path";
20312
20424
  function takeLocalModels(board2) {
20313
- const file = path57.join(board2, ".local.json");
20425
+ const file = path59.join(board2, ".local.json");
20314
20426
  let held4;
20315
20427
  try {
20316
- held4 = configBlock(JSON.parse(fs53.readFileSync(file, "utf8")));
20428
+ held4 = configBlock(JSON.parse(fs54.readFileSync(file, "utf8")));
20317
20429
  } catch {
20318
20430
  return {};
20319
20431
  }
@@ -20331,7 +20443,7 @@ function takeLocalModels(board2) {
20331
20443
  }
20332
20444
  if (!Object.keys(agents).length) return out;
20333
20445
  delete held4.agents;
20334
- fs53.writeFileSync(file, JSON.stringify(held4, null, 2) + "\n");
20446
+ fs54.writeFileSync(file, JSON.stringify(held4, null, 2) + "\n");
20335
20447
  return out;
20336
20448
  }
20337
20449
  var LOCAL_IGNORE_LINE;
@@ -20392,12 +20504,12 @@ var init_identity2 = __esm({
20392
20504
  });
20393
20505
 
20394
20506
  // ../cli/src/lib/signals/migrate.ts
20395
- import fs54 from "node:fs";
20396
- import path58 from "node:path";
20507
+ import fs55 from "node:fs";
20508
+ import path60 from "node:path";
20397
20509
  function readHandled() {
20398
20510
  let text5;
20399
20511
  try {
20400
- text5 = fs54.readFileSync(SIGNALS_OLD_HANDLED, "utf8");
20512
+ text5 = fs55.readFileSync(SIGNALS_OLD_HANDLED, "utf8");
20401
20513
  } catch {
20402
20514
  return [];
20403
20515
  }
@@ -20414,57 +20526,57 @@ function readHandled() {
20414
20526
  return held4;
20415
20527
  }
20416
20528
  function lift(from, into) {
20417
- fs54.mkdirSync(into, { recursive: true });
20418
- fs54.renameSync(from, path58.join(into, freeName(into, path58.basename(from))));
20529
+ fs55.mkdirSync(into, { recursive: true });
20530
+ fs55.renameSync(from, path60.join(into, freeName(into, path60.basename(from))));
20419
20531
  }
20420
20532
  function repoint(dir, from, into) {
20421
20533
  let entries;
20422
20534
  try {
20423
- entries = fs54.readdirSync(dir, { withFileTypes: true });
20535
+ entries = fs55.readdirSync(dir, { withFileTypes: true });
20424
20536
  } catch {
20425
20537
  return;
20426
20538
  }
20427
20539
  for (const entry of entries) {
20428
20540
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
20429
- const file = path58.join(dir, entry.name);
20430
- const text5 = fs54.readFileSync(file, "utf8");
20431
- if (text5.includes(from)) fs54.writeFileSync(file, text5.split(from).join(into));
20541
+ const file = path60.join(dir, entry.name);
20542
+ const text5 = fs55.readFileSync(file, "utf8");
20543
+ if (text5.includes(from)) fs55.writeFileSync(file, text5.split(from).join(into));
20432
20544
  }
20433
20545
  }
20434
20546
  function dropIfEmpty2(dir) {
20435
20547
  try {
20436
- fs54.rmdirSync(dir);
20548
+ fs55.rmdirSync(dir);
20437
20549
  } catch {
20438
20550
  }
20439
20551
  }
20440
20552
  function liftInbox() {
20441
- if (!fs54.existsSync(SIGNALS_OLD_INBOX)) return;
20442
- const oldFiles = path58.join(SIGNALS_OLD_INBOX, "files");
20443
- if (fs54.existsSync(oldFiles)) {
20444
- for (const entry of fs54.readdirSync(oldFiles, { withFileTypes: true })) {
20445
- if (entry.isFile()) lift(path58.join(oldFiles, entry.name), SIGNALS_FILES);
20553
+ if (!fs55.existsSync(SIGNALS_OLD_INBOX)) return;
20554
+ const oldFiles = path60.join(SIGNALS_OLD_INBOX, "files");
20555
+ if (fs55.existsSync(oldFiles)) {
20556
+ for (const entry of fs55.readdirSync(oldFiles, { withFileTypes: true })) {
20557
+ if (entry.isFile()) lift(path60.join(oldFiles, entry.name), SIGNALS_FILES);
20446
20558
  }
20447
20559
  dropIfEmpty2(oldFiles);
20448
20560
  }
20449
20561
  const held4 = new Set(readFolder(TRIAGE).map((signal2) => signal2.sourceId));
20450
- for (const entry of fs54.readdirSync(SIGNALS_OLD_INBOX, { withFileTypes: true })) {
20562
+ for (const entry of fs55.readdirSync(SIGNALS_OLD_INBOX, { withFileTypes: true })) {
20451
20563
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
20452
- const from = path58.join(SIGNALS_OLD_INBOX, entry.name);
20564
+ const from = path60.join(SIGNALS_OLD_INBOX, entry.name);
20453
20565
  const item = parse(from);
20454
20566
  if (item && held4.has(item.sourceId)) {
20455
- fs54.rmSync(from, { force: true });
20567
+ fs55.rmSync(from, { force: true });
20456
20568
  continue;
20457
20569
  }
20458
20570
  if (item) held4.add(item.sourceId);
20459
20571
  lift(from, TRIAGE);
20460
20572
  }
20461
- const wasAt = asWritten(path58.join(SIGNALS_OLD_INBOX, "files"));
20573
+ const wasAt = asWritten(path60.join(SIGNALS_OLD_INBOX, "files"));
20462
20574
  const nowAt = asWritten(SIGNALS_FILES);
20463
20575
  for (const dir of [TRIAGE, SIGNALS_ARCHIVED, SIGNALS_DISMISSED]) repoint(dir, wasAt, nowAt);
20464
20576
  dropIfEmpty2(SIGNALS_OLD_INBOX);
20465
20577
  }
20466
20578
  function liftHandled() {
20467
- if (!fs54.existsSync(SIGNALS_OLD_HANDLED)) return;
20579
+ if (!fs55.existsSync(SIGNALS_OLD_HANDLED)) return;
20468
20580
  const lines = readHandled();
20469
20581
  const held4 = /* @__PURE__ */ new Set([
20470
20582
  ...readFolder(TRIAGE).map((signal2) => signal2.sourceId),
@@ -20474,7 +20586,7 @@ function liftHandled() {
20474
20586
  for (const { sourceId, when } of lines) {
20475
20587
  if (held4.has(sourceId)) continue;
20476
20588
  held4.add(sourceId);
20477
- fs54.mkdirSync(SIGNALS_DISMISSED, { recursive: true });
20589
+ fs55.mkdirSync(SIGNALS_DISMISSED, { recursive: true });
20478
20590
  const name = freeName(SIGNALS_DISMISSED, fileName("", sourceId));
20479
20591
  const front = [
20480
20592
  "---",
@@ -20484,13 +20596,13 @@ function liftHandled() {
20484
20596
  "---",
20485
20597
  ""
20486
20598
  ];
20487
- fs54.writeFileSync(path58.join(SIGNALS_DISMISSED, name), `${front.join("\n")}
20599
+ fs55.writeFileSync(path60.join(SIGNALS_DISMISSED, name), `${front.join("\n")}
20488
20600
  `);
20489
20601
  }
20490
- fs54.rmSync(SIGNALS_OLD_HANDLED, { force: true });
20602
+ fs55.rmSync(SIGNALS_OLD_HANDLED, { force: true });
20491
20603
  }
20492
20604
  function migrateTriage() {
20493
- if (!fs54.existsSync(SIGNALS_OLD_INBOX) && !fs54.existsSync(SIGNALS_OLD_HANDLED)) return;
20605
+ if (!fs55.existsSync(SIGNALS_OLD_INBOX) && !fs55.existsSync(SIGNALS_OLD_HANDLED)) return;
20494
20606
  try {
20495
20607
  liftInbox();
20496
20608
  liftHandled();
@@ -20505,13 +20617,13 @@ var init_migrate = __esm({
20505
20617
  init_yaml();
20506
20618
  init_inbox();
20507
20619
  HANDLED_LINE = /^-\s+(.+?)\s+—\s+(.+)$/;
20508
- asWritten = (dir) => `${rel(dir).split(path58.sep).join("/")}/`;
20620
+ asWritten = (dir) => `${rel(dir).split(path60.sep).join("/")}/`;
20509
20621
  }
20510
20622
  });
20511
20623
 
20512
20624
  // ../cli/src/lib/signals/add.ts
20513
- import fs55 from "node:fs";
20514
- import path59 from "node:path";
20625
+ import fs56 from "node:fs";
20626
+ import path61 from "node:path";
20515
20627
  import { createHash as createHash3 } from "node:crypto";
20516
20628
  function linkTitle(url) {
20517
20629
  let at;
@@ -20524,7 +20636,7 @@ function linkTitle(url) {
20524
20636
  return oneLine(where) || url.slice(0, TITLE_MAX);
20525
20637
  }
20526
20638
  function asText(file) {
20527
- const ext = path59.extname(file.name).toLowerCase();
20639
+ const ext = path61.extname(file.name).toLowerCase();
20528
20640
  if (!file.type.startsWith("text/") && file.type !== "application/json" && !TEXT_EXTENSIONS.has(ext)) return null;
20529
20641
  try {
20530
20642
  const text5 = new TextDecoder("utf-8", { fatal: true }).decode(file.data);
@@ -20534,9 +20646,9 @@ function asText(file) {
20534
20646
  }
20535
20647
  }
20536
20648
  function fileName2(name, data) {
20537
- const base = path59.basename(name);
20538
- const ext = path59.extname(base).toLowerCase().slice(0, 12);
20539
- const word = path59.basename(base, path59.extname(base)).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40) || "file";
20649
+ const base = path61.basename(name);
20650
+ const ext = path61.extname(base).toLowerCase().slice(0, 12);
20651
+ const word = path61.basename(base, path61.extname(base)).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40) || "file";
20540
20652
  const hash2 = createHash3("sha256").update(data).digest("hex").slice(0, 8);
20541
20653
  return `${word}-${hash2}${ext}`;
20542
20654
  }
@@ -20545,9 +20657,9 @@ function addToInbox(drop) {
20545
20657
  const typed = (drop.text ?? "").trim();
20546
20658
  const file = drop.file;
20547
20659
  if (!typed && !file) return { ok: false, error: "nothing to add \u2014 paste a link or some text, or drop a file." };
20548
- if (file && file.data.length === 0) return { ok: false, error: `${path59.basename(file.name)} is empty.` };
20660
+ if (file && file.data.length === 0) return { ok: false, error: `${path61.basename(file.name)} is empty.` };
20549
20661
  if (file && file.data.length > MAX_BYTES) {
20550
- return { ok: false, error: `${path59.basename(file.name)} is over ${MAX_BYTES / 1024 / 1024} MB \u2014 too big for the board.` };
20662
+ return { ok: false, error: `${path61.basename(file.name)} is over ${MAX_BYTES / 1024 / 1024} MB \u2014 too big for the board.` };
20551
20663
  }
20552
20664
  const read4 = describe(typed, file);
20553
20665
  const keep2 = read4.keep;
@@ -20569,8 +20681,8 @@ ${incoming.summary}`);
20569
20681
  if (seen.status === "pending" || seen.status === "archived") return { ok: false, error: `${sayHit(seen)}.` };
20570
20682
  try {
20571
20683
  if (keep2) {
20572
- fs55.mkdirSync(SIGNALS_FILES, { recursive: true });
20573
- fs55.writeFileSync(keep2.at, keep2.data);
20684
+ fs56.mkdirSync(SIGNALS_FILES, { recursive: true });
20685
+ fs56.writeFileSync(keep2.at, keep2.data);
20574
20686
  }
20575
20687
  return { ok: true, signal: writeSignal({ ...incoming, sourceId }, formatStamp(/* @__PURE__ */ new Date()), drop.slug) };
20576
20688
  } catch (e) {
@@ -20584,7 +20696,7 @@ function describe(typed, file) {
20584
20696
  return only ? [only] : [];
20585
20697
  };
20586
20698
  if (file) {
20587
- const name = path59.basename(file.name);
20699
+ const name = path61.basename(file.name);
20588
20700
  const text5 = asText(file);
20589
20701
  const note = typed ? `
20590
20702
 
@@ -20596,7 +20708,7 @@ ${typed}` : "";
20596
20708
  const at = keepAt(file);
20597
20709
  return {
20598
20710
  incoming: {
20599
- title: path59.basename(name, path59.extname(name)) || name,
20711
+ title: path61.basename(name, path61.extname(name)) || name,
20600
20712
  summary: `${name} \u2014 ${boardRel2(at)}${note}`,
20601
20713
  ...named2
20602
20714
  },
@@ -20634,73 +20746,73 @@ var init_add = __esm({
20634
20746
  MAX_BYTES = 25 * 1024 * 1024;
20635
20747
  TITLE_MAX = 120;
20636
20748
  TEXT_EXTENSIONS = /* @__PURE__ */ new Set([".md", ".markdown", ".txt", ".text", ".csv", ".json", ".yml", ".yaml", ".log"]);
20637
- boardRel2 = (file) => rel(file).split(path59.sep).join("/");
20749
+ boardRel2 = (file) => rel(file).split(path61.sep).join("/");
20638
20750
  oneLine = (text5) => {
20639
20751
  const first = text5.split("\n").find((line) => line.trim()) ?? "";
20640
20752
  const clean = first.replace(/^#+\s*/, "").trim();
20641
20753
  return clean.length > TITLE_MAX ? `${clean.slice(0, TITLE_MAX - 1).trimEnd()}\u2026` : clean;
20642
20754
  };
20643
- keepAt = (file) => path59.join(SIGNALS_FILES, fileName2(file.name, file.data));
20755
+ keepAt = (file) => path61.join(SIGNALS_FILES, fileName2(file.name, file.data));
20644
20756
  }
20645
20757
  });
20646
20758
 
20647
20759
  // ../cli/src/lib/recurring.ts
20648
- import fs56 from "node:fs";
20649
- import path60 from "node:path";
20760
+ import fs57 from "node:fs";
20761
+ import path62 from "node:path";
20650
20762
  function migratePruneMemoryCard() {
20651
20763
  const dir = recurringDir();
20652
20764
  let names4;
20653
20765
  try {
20654
- names4 = fs56.readdirSync(dir);
20766
+ names4 = fs57.readdirSync(dir);
20655
20767
  } catch {
20656
20768
  return null;
20657
20769
  }
20658
20770
  const name = names4.find((entry) => entry.endsWith(`-${PRUNE_SLUG}.md`));
20659
20771
  if (!name) return null;
20660
- const file = path60.join(dir, name);
20772
+ const file = path62.join(dir, name);
20661
20773
  try {
20662
- const { meta } = parseFrontmatter(fs56.readFileSync(file, "utf8"));
20774
+ const { meta } = parseFrontmatter(fs57.readFileSync(file, "utf8"));
20663
20775
  const cadence = meta?.cadence;
20664
20776
  if (typeof cadence === "string") adoptMemoryPruneCadence(cadence);
20665
20777
  } catch {
20666
20778
  }
20667
- fs56.rmSync(file, { force: true });
20779
+ fs57.rmSync(file, { force: true });
20668
20780
  return rel(file);
20669
20781
  }
20670
20782
  function legacyCards() {
20671
20783
  const dir = recurringDir();
20672
- if (!fs56.existsSync(dir)) return [];
20784
+ if (!fs57.existsSync(dir)) return [];
20673
20785
  const cards = [];
20674
- for (const entry of fs56.readdirSync(dir, { withFileTypes: true })) {
20786
+ for (const entry of fs57.readdirSync(dir, { withFileTypes: true })) {
20675
20787
  const id = idPrefix(entry.name);
20676
20788
  if (id === null) continue;
20677
- const full = path60.join(dir, entry.name);
20789
+ const full = path62.join(dir, entry.name);
20678
20790
  const slug2 = entry.name.replace(/\.md$/, "").replace(/^\d+-?/, "");
20679
20791
  if (entry.isFile() && entry.name.endsWith(".md")) cards.push({ id, slug: slug2, file: full });
20680
- else if (entry.isDirectory() && fs56.existsSync(path60.join(full, "root.md"))) {
20681
- cards.push({ id, slug: slug2, file: path60.join(full, "root.md"), folder: full });
20792
+ else if (entry.isDirectory() && fs57.existsSync(path62.join(full, "root.md"))) {
20793
+ cards.push({ id, slug: slug2, file: path62.join(full, "root.md"), folder: full });
20682
20794
  }
20683
20795
  }
20684
20796
  return cards.sort((a, b) => a.id - b.id);
20685
20797
  }
20686
20798
  function mergeInto2(from, to, skip = []) {
20687
- fs56.mkdirSync(to, { recursive: true });
20688
- for (const entry of fs56.readdirSync(from, { withFileTypes: true })) {
20799
+ fs57.mkdirSync(to, { recursive: true });
20800
+ for (const entry of fs57.readdirSync(from, { withFileTypes: true })) {
20689
20801
  if (skip.includes(entry.name)) continue;
20690
- const source = path60.join(from, entry.name);
20691
- const target = path60.join(to, entry.name);
20692
- if (!fs56.existsSync(target)) fs56.renameSync(source, target);
20693
- else if (entry.isDirectory() && fs56.statSync(target).isDirectory()) mergeInto2(source, target);
20802
+ const source = path62.join(from, entry.name);
20803
+ const target = path62.join(to, entry.name);
20804
+ if (!fs57.existsSync(target)) fs57.renameSync(source, target);
20805
+ else if (entry.isDirectory() && fs57.statSync(target).isDirectory()) mergeInto2(source, target);
20694
20806
  }
20695
- if (isEmpty(from)) fs56.rmSync(from, { recursive: true, force: true });
20807
+ if (isEmpty(from)) fs57.rmSync(from, { recursive: true, force: true });
20696
20808
  }
20697
20809
  function moveSideFolders() {
20698
20810
  const dir = recurringDir();
20699
- for (const entry of fs56.readdirSync(dir, { withFileTypes: true })) {
20811
+ for (const entry of fs57.readdirSync(dir, { withFileTypes: true })) {
20700
20812
  if (!entry.isDirectory() || idPrefix(entry.name) !== null) continue;
20701
- const from = path60.join(dir, entry.name);
20702
- if (isEmpty(from)) fs56.rmSync(from, { recursive: true, force: true });
20703
- else mergeInto2(from, path60.join(AGENTS, entry.name));
20813
+ const from = path62.join(dir, entry.name);
20814
+ if (isEmpty(from)) fs57.rmSync(from, { recursive: true, force: true });
20815
+ else mergeInto2(from, path62.join(AGENTS, entry.name));
20704
20816
  }
20705
20817
  }
20706
20818
  function sections(body) {
@@ -20750,8 +20862,8 @@ function agentName(card, text5) {
20750
20862
  const known2 = /* @__PURE__ */ new Set([...specAgentCatalog().agents.map((a) => a.name), ...ROLE_NAMES]);
20751
20863
  for (const name of [slug2, `${slug2}-${card.id}`]) {
20752
20864
  if (!AGENT_NAME.test(name)) continue;
20753
- const file = path60.join(AGENTS, name, AGENT_FILE3);
20754
- const written = fs56.existsSync(file) ? fs56.readFileSync(file, "utf8") : null;
20865
+ const file = path62.join(AGENTS, name, AGENT_FILE3);
20866
+ const written = fs57.existsSync(file) ? fs57.readFileSync(file, "utf8") : null;
20755
20867
  if (written === text5(name)) return name;
20756
20868
  if (written === null && !known2.has(name)) return name;
20757
20869
  }
@@ -20768,11 +20880,11 @@ ${q.options.map((o) => `- ${o}`).join("\n")}` : "";
20768
20880
  function removeCard(card) {
20769
20881
  dropCrossRefs(card.id);
20770
20882
  const target = card.folder ?? card.file;
20771
- stripReadmeRefs({ kind: card.folder ? "group" : "file", rel: path60.relative(TODO, target).split(path60.sep).join("/") });
20772
- fs56.rmSync(target, { recursive: true, force: true });
20883
+ stripReadmeRefs({ kind: card.folder ? "group" : "file", rel: path62.relative(TODO, target).split(path62.sep).join("/") });
20884
+ fs57.rmSync(target, { recursive: true, force: true });
20773
20885
  }
20774
20886
  function migrateCard(card) {
20775
- const { meta, body } = parseFrontmatter(fs56.readFileSync(card.file, "utf8"));
20887
+ const { meta, body } = parseFrontmatter(fs57.readFileSync(card.file, "utf8"));
20776
20888
  if (!meta) throw new Error("its frontmatter cannot be read");
20777
20889
  const raw = meta;
20778
20890
  const said3 = (key) => typeof raw[key] === "string" ? raw[key].trim() : "";
@@ -20782,19 +20894,19 @@ function migrateCard(card) {
20782
20894
  if (!flow) throw new Error("this board has no workflow to put it in");
20783
20895
  const text5 = (name2) => agentText(name2, meta.title || card.slug, body);
20784
20896
  const name = agentName(card, text5);
20785
- const dir = path60.join(AGENTS, name);
20786
- const file = path60.join(dir, AGENT_FILE3);
20787
- const madeDir = !fs56.existsSync(dir);
20788
- const wrote = !fs56.existsSync(file);
20897
+ const dir = path62.join(AGENTS, name);
20898
+ const file = path62.join(dir, AGENT_FILE3);
20899
+ const madeDir = !fs57.existsSync(dir);
20900
+ const wrote = !fs57.existsSync(file);
20789
20901
  try {
20790
- fs56.mkdirSync(dir, { recursive: true });
20791
- if (wrote) fs56.writeFileSync(file, text5(name));
20902
+ fs57.mkdirSync(dir, { recursive: true });
20903
+ if (wrote) fs57.writeFileSync(file, text5(name));
20792
20904
  const adopted = adoptWorkflowScheduled(flow.id, name, { cadence, lastRun });
20793
20905
  if (!adopted.ok) throw new Error(adopted.error);
20794
20906
  questionsToTriage(meta, name);
20795
20907
  } catch (e) {
20796
- if (madeDir) fs56.rmSync(dir, { recursive: true, force: true });
20797
- else if (wrote) fs56.rmSync(file, { force: true });
20908
+ if (madeDir) fs57.rmSync(dir, { recursive: true, force: true });
20909
+ else if (wrote) fs57.rmSync(file, { force: true });
20798
20910
  throw e;
20799
20911
  }
20800
20912
  if (card.folder) mergeInto2(card.folder, dir, ["root.md"]);
@@ -20804,7 +20916,7 @@ function migrateCard(card) {
20804
20916
  function migrateRecurringCards() {
20805
20917
  const done2 = { agents: [], removed: [], failed: [] };
20806
20918
  const dir = recurringDir();
20807
- if (!fs56.existsSync(dir)) return done2;
20919
+ if (!fs57.existsSync(dir)) return done2;
20808
20920
  migratePruneMemoryCard();
20809
20921
  moveSideFolders();
20810
20922
  for (const card of legacyCards()) {
@@ -20817,7 +20929,7 @@ function migrateRecurringCards() {
20817
20929
  done2.failed.push({ card: rel(card.file), why: e instanceof Error ? e.message : String(e) });
20818
20930
  }
20819
20931
  }
20820
- if (isEmpty(dir)) fs56.rmSync(dir, { recursive: true, force: true });
20932
+ if (isEmpty(dir)) fs57.rmSync(dir, { recursive: true, force: true });
20821
20933
  return done2;
20822
20934
  }
20823
20935
  function recurringMigrationLines(done2) {
@@ -20850,14 +20962,14 @@ var init_recurring = __esm({
20850
20962
  PRUNE_SLUG = "prune-the-memory";
20851
20963
  FETCH_SLUG = "fetch-triage-items";
20852
20964
  AGENT_FILE3 = "AGENT.md";
20853
- recurringDir = () => path60.join(TODO, LEGACY_RECURRING);
20854
- isEmpty = (dir) => fs56.readdirSync(dir).every((name) => name === ".DS_Store");
20965
+ recurringDir = () => path62.join(TODO, LEGACY_RECURRING);
20966
+ isEmpty = (dir) => fs57.readdirSync(dir).every((name) => name === ".DS_Store");
20855
20967
  SECTION = /^##\s+(.+?)\s*$/;
20856
20968
  }
20857
20969
  });
20858
20970
 
20859
20971
  // ../cli/src/commands/init.ts
20860
- import fs57 from "node:fs";
20972
+ import fs58 from "node:fs";
20861
20973
  function boardReadme() {
20862
20974
  return `# Board
20863
20975
 
@@ -20870,20 +20982,20 @@ _(none)_
20870
20982
  `;
20871
20983
  }
20872
20984
  function writeConfigIfMissing() {
20873
- if (fs57.existsSync(CONFIG)) return false;
20874
- fs57.mkdirSync(KANBAN, { recursive: true });
20875
- fs57.writeFileSync(CONFIG, CONFIG_TEMPLATE);
20985
+ if (fs58.existsSync(CONFIG)) return false;
20986
+ fs58.mkdirSync(KANBAN, { recursive: true });
20987
+ fs58.writeFileSync(CONFIG, CONFIG_TEMPLATE);
20876
20988
  return true;
20877
20989
  }
20878
20990
  function writeModulesIfMissing() {
20879
- if (fs57.existsSync(MODULES_MD)) return false;
20880
- fs57.mkdirSync(KANBAN, { recursive: true });
20881
- fs57.writeFileSync(MODULES_MD, MODULES_TEMPLATE);
20991
+ if (fs58.existsSync(MODULES_MD)) return false;
20992
+ fs58.mkdirSync(KANBAN, { recursive: true });
20993
+ fs58.writeFileSync(MODULES_MD, MODULES_TEMPLATE);
20882
20994
  return true;
20883
20995
  }
20884
20996
  function writeGitignoreIfMissing() {
20885
- fs57.mkdirSync(KANBAN, { recursive: true });
20886
- const text5 = fs57.existsSync(KANBAN_GITIGNORE) ? fs57.readFileSync(KANBAN_GITIGNORE, "utf8") : "";
20997
+ fs58.mkdirSync(KANBAN, { recursive: true });
20998
+ const text5 = fs58.existsSync(KANBAN_GITIGNORE) ? fs58.readFileSync(KANBAN_GITIGNORE, "utf8") : "";
20887
20999
  const has = (rule) => text5.split("\n").some((line) => line.trim() === rule.line);
20888
21000
  const missing = IGNORE_RULES.filter((rule) => !has(rule));
20889
21001
  if (!missing.length) return false;
@@ -20891,37 +21003,37 @@ function writeGitignoreIfMissing() {
20891
21003
  ${rule.line}
20892
21004
  `).join("");
20893
21005
  const separator = !text5 || text5.endsWith("\n") ? "" : "\n";
20894
- fs57.writeFileSync(KANBAN_GITIGNORE, `${text5}${separator}${block}`);
21006
+ fs58.writeFileSync(KANBAN_GITIGNORE, `${text5}${separator}${block}`);
20895
21007
  return true;
20896
21008
  }
20897
21009
  function writeSkeletonIfMissing() {
20898
21010
  const added = [];
20899
- if (!fs57.existsSync(TODO)) {
20900
- fs57.mkdirSync(TODO, { recursive: true });
21011
+ if (!fs58.existsSync(TODO)) {
21012
+ fs58.mkdirSync(TODO, { recursive: true });
20901
21013
  added.push(`${rel(TODO)}/`);
20902
21014
  }
20903
- if (!fs57.existsSync(README)) {
20904
- fs57.writeFileSync(README, boardReadme());
21015
+ if (!fs58.existsSync(README)) {
21016
+ fs58.writeFileSync(README, boardReadme());
20905
21017
  added.push(rel(README));
20906
21018
  }
20907
21019
  return added;
20908
21020
  }
20909
21021
  function retireReleases() {
20910
21022
  const done2 = [];
20911
- if (fs57.existsSync(RELEASES)) {
20912
- fs57.rmSync(RELEASES);
21023
+ if (fs58.existsSync(RELEASES)) {
21024
+ fs58.rmSync(RELEASES);
20913
21025
  done2.push(`deleted ${rel(RELEASES)}`);
20914
21026
  }
20915
21027
  let cards = 0;
20916
21028
  for (const dir of [TODO, ARCHIVE]) {
20917
- if (!fs57.existsSync(dir)) continue;
21029
+ if (!fs58.existsSync(dir)) continue;
20918
21030
  for (const file of walkMd(dir)) {
20919
- const text5 = fs57.readFileSync(file, "utf8");
21031
+ const text5 = fs58.readFileSync(file, "utf8");
20920
21032
  const end2 = text5.startsWith("---\n") ? text5.indexOf("\n---", 3) : -1;
20921
21033
  if (end2 < 0) continue;
20922
21034
  const head = text5.slice(0, end2).replace(/\nrelease:[^\n]*/, "");
20923
21035
  if (head.length === end2) continue;
20924
- fs57.writeFileSync(file, head + text5.slice(end2));
21036
+ fs58.writeFileSync(file, head + text5.slice(end2));
20925
21037
  cards++;
20926
21038
  }
20927
21039
  }
@@ -20929,7 +21041,7 @@ function retireReleases() {
20929
21041
  return done2.join(", ");
20930
21042
  }
20931
21043
  function cmdInit() {
20932
- if (fs57.existsSync(KANBAN)) {
21044
+ if (fs58.existsSync(KANBAN)) {
20933
21045
  const added = [
20934
21046
  ...writeSkeletonIfMissing(),
20935
21047
  writeConfigIfMissing() && rel(CONFIG),
@@ -20964,8 +21076,8 @@ function cmdInit() {
20964
21076
  }
20965
21077
  return { board: rel(KANBAN), created: false, added, memory_paths: scaffolded.map((s) => rel(s.dir)) };
20966
21078
  }
20967
- fs57.mkdirSync(TODO, { recursive: true });
20968
- fs57.writeFileSync(README, boardReadme());
21079
+ fs58.mkdirSync(TODO, { recursive: true });
21080
+ fs58.writeFileSync(README, boardReadme());
20969
21081
  writeConfigIfMissing();
20970
21082
  scaffoldProjectMemory();
20971
21083
  writeModulesIfMissing();
@@ -21054,25 +21166,25 @@ var init_scheduled = __esm({
21054
21166
  });
21055
21167
 
21056
21168
  // ../cli/src/lib/card-age.ts
21057
- import fs58 from "node:fs";
21058
- import path61 from "node:path";
21169
+ import fs59 from "node:fs";
21170
+ import path63 from "node:path";
21059
21171
  function staleAfterIn(config) {
21060
21172
  const days = Number(LINE.exec(config)?.[1]);
21061
21173
  return Number.isInteger(days) && days > 0 ? days : STALE_AFTER_DEFAULT;
21062
21174
  }
21063
21175
  function staleAfter() {
21064
- if (!fs58.existsSync(CONFIG)) return STALE_AFTER_DEFAULT;
21065
- return staleAfterIn(fs58.readFileSync(CONFIG, "utf8"));
21176
+ if (!fs59.existsSync(CONFIG)) return STALE_AFTER_DEFAULT;
21177
+ return staleAfterIn(fs59.readFileSync(CONFIG, "utf8"));
21066
21178
  }
21067
21179
  function todoPathspec(top) {
21068
- const from = path61.relative(real(top), real(TODO));
21069
- if (!from || from.startsWith("..") || path61.isAbsolute(from)) return null;
21070
- return from.split(path61.sep).join("/");
21180
+ const from = path63.relative(real(top), real(TODO));
21181
+ if (!from || from.startsWith("..") || path63.isAbsolute(from)) return null;
21182
+ return from.split(path63.sep).join("/");
21071
21183
  }
21072
21184
  function cardFile2(spec, printed) {
21073
21185
  const p = unquotePath(printed);
21074
21186
  if (!p.startsWith(`${spec}/`)) return null;
21075
- return path61.join(TODO, ...p.slice(spec.length + 1).split("/"));
21187
+ return path63.join(TODO, ...p.slice(spec.length + 1).split("/"));
21076
21188
  }
21077
21189
  function dirtyCards(top, spec) {
21078
21190
  const out = git(["status", "--porcelain", "--untracked-files=all", "--", spec], top);
@@ -21119,9 +21231,9 @@ var init_card_age = __esm({
21119
21231
  gitTop = () => git(["rev-parse", "--show-toplevel"])?.trim() || null;
21120
21232
  real = (p) => {
21121
21233
  try {
21122
- return fs58.realpathSync(p);
21234
+ return fs59.realpathSync(p);
21123
21235
  } catch {
21124
- return path61.resolve(p);
21236
+ return path63.resolve(p);
21125
21237
  }
21126
21238
  };
21127
21239
  unquotePath = (line) => {
@@ -21156,8 +21268,8 @@ var init_card_holds = __esm({
21156
21268
  });
21157
21269
 
21158
21270
  // ../cli/src/commands/list.ts
21159
- import fs59 from "node:fs";
21160
- import path62 from "node:path";
21271
+ import fs60 from "node:fs";
21272
+ import path64 from "node:path";
21161
21273
  function summaryLine(body) {
21162
21274
  const lines = body.split("\n");
21163
21275
  let i = 0;
@@ -21173,13 +21285,13 @@ function summaryLine(body) {
21173
21285
  function openRows() {
21174
21286
  const rows = [];
21175
21287
  for (const file of walkMd(TODO)) {
21176
- const base = path62.basename(file);
21288
+ const base = path64.basename(file);
21177
21289
  if (base === "README.md") continue;
21178
21290
  const isRoot = base === "root.md";
21179
- const id = isRoot ? idPrefix(path62.basename(path62.dirname(file))) : idPrefix(base);
21291
+ const id = isRoot ? idPrefix(path64.basename(path64.dirname(file))) : idPrefix(base);
21180
21292
  if (id == null) continue;
21181
- if (isLegacyRecurring(path62.relative(TODO, file))) continue;
21182
- const { meta, body } = parseFrontmatter(fs59.readFileSync(file, "utf8"));
21293
+ if (isLegacyRecurring(path64.relative(TODO, file))) continue;
21294
+ const { meta, body } = parseFrontmatter(fs60.readFileSync(file, "utf8"));
21183
21295
  rows.push({
21184
21296
  id,
21185
21297
  file,
@@ -21221,15 +21333,15 @@ function cmdStale(rows, all2, scope, mod) {
21221
21333
  }
21222
21334
  function archivedFiles() {
21223
21335
  const files2 = /* @__PURE__ */ new Map();
21224
- if (!fs59.existsSync(ARCHIVE)) return files2;
21336
+ if (!fs60.existsSync(ARCHIVE)) return files2;
21225
21337
  for (const file of walkMd(ARCHIVE)) {
21226
- const id = idPrefix(path62.basename(file));
21338
+ const id = idPrefix(path64.basename(file));
21227
21339
  if (id !== null) files2.set(id, file);
21228
21340
  }
21229
21341
  for (const dir of walkDirs(ARCHIVE)) {
21230
- const id = idPrefix(path62.basename(dir));
21231
- const root = path62.join(dir, "root.md");
21232
- if (id !== null && fs59.existsSync(root)) files2.set(id, root);
21342
+ const id = idPrefix(path64.basename(dir));
21343
+ const root = path64.join(dir, "root.md");
21344
+ if (id !== null && fs60.existsSync(root)) files2.set(id, root);
21233
21345
  }
21234
21346
  return files2;
21235
21347
  }
@@ -21340,14 +21452,14 @@ var init_list = __esm({
21340
21452
  });
21341
21453
 
21342
21454
  // ../cli/src/commands/misc.ts
21343
- import fs60 from "node:fs";
21344
- import path63 from "node:path";
21455
+ import fs61 from "node:fs";
21456
+ import path65 from "node:path";
21345
21457
  function extractOldMeta(text5, file) {
21346
21458
  const grab = (re) => {
21347
21459
  const m = text5.match(re);
21348
21460
  return m ? m[1].trim() : null;
21349
21461
  };
21350
- const title = grab(/^#\s+(.+)$/m) || slugify(path63.basename(file, ".md").replace(/^\d+-/, "")).replace(/-/g, " ");
21462
+ const title = grab(/^#\s+(.+)$/m) || slugify(path65.basename(file, ".md").replace(/^\d+-/, "")).replace(/-/g, " ");
21351
21463
  const norm = (raw) => {
21352
21464
  const v = (raw || "").toLowerCase();
21353
21465
  return LEVELS.includes(v) ? v : "med";
@@ -21374,11 +21486,11 @@ function stripOldHeader(text5) {
21374
21486
  }
21375
21487
  function cmdMigrate(opts) {
21376
21488
  const dry = opts.dryRun === true;
21377
- const files2 = walkMd(TODO).filter((f) => path63.basename(f) !== "README.md");
21489
+ const files2 = walkMd(TODO).filter((f) => path65.basename(f) !== "README.md");
21378
21490
  let changed = 0;
21379
21491
  let skipped = 0;
21380
21492
  for (const file of files2) {
21381
- const text5 = fs60.readFileSync(file, "utf8");
21493
+ const text5 = fs61.readFileSync(file, "utf8");
21382
21494
  if (text5.trimStart().startsWith("---")) {
21383
21495
  skipped++;
21384
21496
  continue;
@@ -21387,7 +21499,7 @@ function cmdMigrate(opts) {
21387
21499
  const out = serializeFrontmatter(meta) + "\n\n" + stripOldHeader(text5).replace(/^\n+/, "");
21388
21500
  if (dry) say(`would migrate ${rel(file)} (priority=${meta.priority})`);
21389
21501
  else {
21390
- fs60.writeFileSync(file, out.endsWith("\n") ? out : out + "\n");
21502
+ fs61.writeFileSync(file, out.endsWith("\n") ? out : out + "\n");
21391
21503
  say(`migrated ${rel(file)}`);
21392
21504
  }
21393
21505
  changed++;
@@ -21408,10 +21520,10 @@ var init_misc = __esm({
21408
21520
  });
21409
21521
 
21410
21522
  // ../cli/src/lib/group-close.ts
21411
- import fs61 from "node:fs";
21523
+ import fs62 from "node:fs";
21412
21524
  function groupCloses(rootFile) {
21413
21525
  try {
21414
- return groupCloseOf(fs61.readFileSync(rootFile, "utf8"));
21526
+ return groupCloseOf(fs62.readFileSync(rootFile, "utf8"));
21415
21527
  } catch {
21416
21528
  return false;
21417
21529
  }
@@ -21424,12 +21536,12 @@ var init_group_close = __esm({
21424
21536
  });
21425
21537
 
21426
21538
  // ../cli/src/commands/remove.ts
21427
- import fs62 from "node:fs";
21428
- import path64 from "node:path";
21539
+ import fs63 from "node:fs";
21540
+ import path66 from "node:path";
21429
21541
  function leavingIds(id, found2) {
21430
21542
  const ids = /* @__PURE__ */ new Set([id]);
21431
21543
  if (found2.kind === "group") {
21432
- const names4 = [...walkMd(found2.target), ...walkDirs(found2.target)].map((f) => path64.basename(f));
21544
+ const names4 = [...walkMd(found2.target), ...walkDirs(found2.target)].map((f) => path66.basename(f));
21433
21545
  for (const name of names4) {
21434
21546
  const n = idPrefix(name);
21435
21547
  if (n !== null) ids.add(n);
@@ -21441,10 +21553,10 @@ function findMentions(ids) {
21441
21553
  const hits = [];
21442
21554
  const re = new RegExp(ids.map((id) => `#${id}(?!\\d)`).join("|"));
21443
21555
  for (const dir of [TODO, MEMORY]) {
21444
- if (!fs62.existsSync(dir)) continue;
21556
+ if (!fs63.existsSync(dir)) continue;
21445
21557
  for (const file of walkMd(dir)) {
21446
- if (path64.basename(file) === "README.md") continue;
21447
- const lines = fs62.readFileSync(file, "utf8").split("\n");
21558
+ if (path66.basename(file) === "README.md") continue;
21559
+ const lines = fs63.readFileSync(file, "utf8").split("\n");
21448
21560
  const fmEnd = frontmatterEnd(lines);
21449
21561
  lines.forEach((line, i) => {
21450
21562
  if (!re.test(line)) return;
@@ -21460,12 +21572,12 @@ function findMentions(ids) {
21460
21572
  return hits;
21461
21573
  }
21462
21574
  function leavingCards(id, found2) {
21463
- const own = found2.kind === "group" ? path64.join(found2.target, "root.md") : found2.target;
21575
+ const own = found2.kind === "group" ? path66.join(found2.target, "root.md") : found2.target;
21464
21576
  const cards = [{ id, file: own }];
21465
21577
  if (found2.kind !== "group") return cards;
21466
21578
  for (const file of walkMd(found2.target)) {
21467
21579
  if (file === own) continue;
21468
- const n = idPrefix(path64.basename(file));
21580
+ const n = idPrefix(path66.basename(file));
21469
21581
  if (n !== null) cards.push({ id: n, file });
21470
21582
  }
21471
21583
  return cards;
@@ -21474,10 +21586,10 @@ function stampLeaving(cards, rejection) {
21474
21586
  const day = formatDay();
21475
21587
  const stamp4 = rejection && !rejection.discard ? { reason: rejection.reason, at: (/* @__PURE__ */ new Date()).toISOString(), by: insideRun() ? "agent" : "user" } : rejection && { reason: rejection.reason };
21476
21588
  for (const card of cards) {
21477
- if (!fs62.existsSync(card.file)) continue;
21478
- const text5 = fs62.readFileSync(card.file, "utf8");
21589
+ if (!fs63.existsSync(card.file)) continue;
21590
+ const text5 = fs63.readFileSync(card.file, "utf8");
21479
21591
  const stamped = stampLeavingIn(text5, day, stamp4);
21480
- if (stamped !== text5) fs62.writeFileSync(card.file, stamped);
21592
+ if (stamped !== text5) fs63.writeFileSync(card.file, stamped);
21481
21593
  }
21482
21594
  }
21483
21595
  function unfinishedDelivery(id) {
@@ -21493,7 +21605,7 @@ function unfinishedDelivery(id) {
21493
21605
  }[gap];
21494
21606
  return `#${id} is not finished: ${what}.`;
21495
21607
  }
21496
- const missing = planDeliverables(card.body).filter((src) => src.startsWith(".assets/")).find((src) => !fs62.existsSync(path64.join(ASSETS, src.slice(".assets/".length))));
21608
+ const missing = planDeliverables(card.body).filter((src) => src.startsWith(".assets/")).find((src) => !fs63.existsSync(path66.join(ASSETS, src.slice(".assets/".length))));
21497
21609
  return missing ? `#${id} is not finished: ${missing} is not on this machine.` : null;
21498
21610
  }
21499
21611
  function agentDecisions(text5) {
@@ -21520,8 +21632,8 @@ function removeCard2(id, metric, options) {
21520
21632
  const unfinished = metric === "completed" ? unfinishedDelivery(id) : null;
21521
21633
  if (unfinished) die(unfinished, { kind: "plan-delivery-unfinished" });
21522
21634
  const dest = options.cleanupDiscarded && locateArchived(id) ? null : archiveDest(found2);
21523
- const cardFile3 = found2.kind === "group" ? path64.join(found2.target, "root.md") : found2.target;
21524
- const cardText = fs62.existsSync(cardFile3) ? fs62.readFileSync(cardFile3, "utf8") : "";
21635
+ const cardFile3 = found2.kind === "group" ? path66.join(found2.target, "root.md") : found2.target;
21636
+ const cardText = fs63.existsSync(cardFile3) ? fs63.readFileSync(cardFile3, "utf8") : "";
21525
21637
  const { meta: cardMeta3 } = parseFrontmatter(cardText);
21526
21638
  const alsoRemoved = found2.kind === "group" ? walkMd(found2.target).filter((f) => f !== cardFile3).map((f) => rel(f)).sort() : [];
21527
21639
  const leftIds = leavingIds(id, found2);
@@ -21552,10 +21664,10 @@ function removeCard2(id, metric, options) {
21552
21664
  const groupRoot = found2.kind === "file" && !options.closing ? enclosingGroupRoot(found2.target) : null;
21553
21665
  if (dest) {
21554
21666
  stampLeaving(leaving, metric === "rejected" ? { reason: options.reason?.trim() ?? "", discard: options.discard === true } : null);
21555
- fs62.mkdirSync(ARCHIVE, { recursive: true });
21556
- fs62.renameSync(found2.target, dest);
21667
+ fs63.mkdirSync(ARCHIVE, { recursive: true });
21668
+ fs63.renameSync(found2.target, dest);
21557
21669
  } else {
21558
- fs62.rmSync(found2.target, { recursive: true, force: true });
21670
+ fs63.rmSync(found2.target, { recursive: true, force: true });
21559
21671
  }
21560
21672
  const removedRefs = stripReadmeRefs(found2);
21561
21673
  let marked2 = null;
@@ -21604,14 +21716,14 @@ function removeCard2(id, metric, options) {
21604
21716
  };
21605
21717
  }
21606
21718
  function closeGroup(rootFile) {
21607
- const rootId = idPrefix(path64.basename(path64.dirname(rootFile)));
21719
+ const rootId = idPrefix(path66.basename(path66.dirname(rootFile)));
21608
21720
  if (rootId === null) return null;
21609
21721
  if (runnerProject(KANBAN)) return null;
21610
21722
  if (!groupCloses(rootFile)) return null;
21611
21723
  say(`
21612
21724
  every subtask line on #${rootId} is resolved \u2014 closing the group:`);
21613
21725
  try {
21614
- const { meta, body } = parseFrontmatter(fs62.readFileSync(rootFile, "utf8"));
21726
+ const { meta, body } = parseFrontmatter(fs63.readFileSync(rootFile, "utf8"));
21615
21727
  const res = cmdRemove(rootId, "completed", { closing: true });
21616
21728
  return {
21617
21729
  id: rootId,
@@ -21668,7 +21780,7 @@ function cleanupDiscardedCards(sessionId) {
21668
21780
  if (card.pending) throw new Error(`Discarding #${card.id} did not finish. Retry the discard.`);
21669
21781
  while (locate(card.id)) cmdRemove(card.id, "rejected", { discard: true, cleanupDiscarded: true });
21670
21782
  dropCrossRefs(card.id);
21671
- stripReadmeRefs({ kind: "file", rel: path64.relative(TODO, path64.resolve(REPO_ROOT, card.path)).split(path64.sep).join("/") });
21783
+ stripReadmeRefs({ kind: "file", rel: path66.relative(TODO, path66.resolve(REPO_ROOT, card.path)).split(path66.sep).join("/") });
21672
21784
  }
21673
21785
  return discarded.map((c) => c.id);
21674
21786
  });
@@ -21754,8 +21866,8 @@ var init_memory_file = __esm({
21754
21866
  });
21755
21867
 
21756
21868
  // ../cli/src/commands/rule.ts
21757
- import fs63 from "node:fs";
21758
- import path65 from "node:path";
21869
+ import fs64 from "node:fs";
21870
+ import path67 from "node:path";
21759
21871
  function cmdRule(askedName, flags) {
21760
21872
  const agent = askedName.trim();
21761
21873
  if (!agentRoster().some((a) => a.name === agent)) {
@@ -21764,7 +21876,7 @@ function cmdRule(askedName, flags) {
21764
21876
  const rule = readRuleText(flags.file, flags.text);
21765
21877
  const res = setAgentRule(agent, rule);
21766
21878
  if (!res.ok) die(res.error);
21767
- const file = rel(path65.join(RULES, `${agent}.md`));
21879
+ const file = rel(path67.join(RULES, `${agent}.md`));
21768
21880
  say(rule ? `wrote the \`${agent}\` rule (${file})` : `cleared the \`${agent}\` rule`);
21769
21881
  return { agent, file, cleared: !rule };
21770
21882
  }
@@ -21772,7 +21884,7 @@ function readRuleText(file, text5) {
21772
21884
  if (file !== void 0 && text5 !== void 0) die("pass --file or --text, not both");
21773
21885
  if (file !== void 0) {
21774
21886
  try {
21775
- return fs63.readFileSync(file, "utf8").trim();
21887
+ return fs64.readFileSync(file, "utf8").trim();
21776
21888
  } catch {
21777
21889
  die(`can't read ${file} \u2014 write the rule to a file, then pass its path`);
21778
21890
  }
@@ -21899,22 +22011,22 @@ var init_storyboard = __esm({
21899
22011
  });
21900
22012
 
21901
22013
  // ../cli/src/lib/spec-contract.ts
21902
- import fs64 from "node:fs";
21903
- import path66 from "node:path";
22014
+ import fs65 from "node:fs";
22015
+ import path68 from "node:path";
21904
22016
  function snapshotSpecs() {
21905
22017
  const out = /* @__PURE__ */ new Map();
21906
- if (!fs64.existsSync(TODO)) return out;
22018
+ if (!fs65.existsSync(TODO)) return out;
21907
22019
  for (const file of walkMd(TODO)) {
21908
- if (isLegacyRecurring(path66.relative(TODO, file))) continue;
21909
- const id = idPrefix(path66.basename(file) === "root.md" ? path66.basename(path66.dirname(file)) : path66.basename(file));
21910
- if (id !== null) out.set(file, { id, text: fs64.readFileSync(file, "utf8") });
22020
+ if (isLegacyRecurring(path68.relative(TODO, file))) continue;
22021
+ const id = idPrefix(path68.basename(file) === "root.md" ? path68.basename(path68.dirname(file)) : path68.basename(file));
22022
+ if (id !== null) out.set(file, { id, text: fs65.readFileSync(file, "utf8") });
21911
22023
  }
21912
22024
  return out;
21913
22025
  }
21914
22026
  function enclosingGroups(file) {
21915
22027
  const ids = [];
21916
22028
  for (let root = enclosingGroupRoot(file); root; root = enclosingGroupRoot(root)) {
21917
- const id = idPrefix(path66.basename(path66.dirname(root)));
22029
+ const id = idPrefix(path68.basename(path68.dirname(root)));
21918
22030
  if (id !== null) ids.push(id);
21919
22031
  }
21920
22032
  return ids;
@@ -22083,9 +22195,9 @@ function validateSpec(file, text5, id) {
22083
22195
  }
22084
22196
  function inside(dir, name) {
22085
22197
  try {
22086
- const root = fs64.realpathSync(dir);
22087
- const real2 = fs64.realpathSync(path66.join(dir, name));
22088
- return real2.startsWith(root + path66.sep) && fs64.statSync(real2).isFile() ? real2 : null;
22198
+ const root = fs65.realpathSync(dir);
22199
+ const real2 = fs65.realpathSync(path68.join(dir, name));
22200
+ return real2.startsWith(root + path68.sep) && fs65.statSync(real2).isFile() ? real2 : null;
22089
22201
  } catch {
22090
22202
  return null;
22091
22203
  }
@@ -22103,7 +22215,7 @@ function variantsProblem(raw, src, id) {
22103
22215
  if (files2.length < 2 || !files2.every((f) => typeof f === "string" && f)) return `variants needs at least two labels, each naming a file. ${shape}`;
22104
22216
  if (!files2.includes(src)) return `src="${src}" is not one of the variants. Set src to the version shown first.`;
22105
22217
  if (id === void 0) return null;
22106
- const dir = path66.join(ASSETS, String(id));
22218
+ const dir = path68.join(ASSETS, String(id));
22107
22219
  for (const f of files2) {
22108
22220
  const parts = f.split("/");
22109
22221
  if (parts.length !== 3 || parts[0] !== ".assets" || parts[1] !== String(id) || !inside(dir, parts[2])) {
@@ -22113,7 +22225,7 @@ function variantsProblem(raw, src, id) {
22113
22225
  return null;
22114
22226
  }
22115
22227
  function compareProblem(attrs, id) {
22116
- const dir = path66.join(ASSETS, String(id));
22228
+ const dir = path68.join(ASSETS, String(id));
22117
22229
  const src = (message) => ({ rule: "compare-src", message });
22118
22230
  const shots = (message) => ({ rule: "compare-shots", message });
22119
22231
  for (const side of ["a", "b"]) {
@@ -22133,7 +22245,7 @@ function compareProblem(attrs, id) {
22133
22245
  const file = inside(dir, named2.name);
22134
22246
  if (!file) return { message: `Compare shots ${at} names no file in this card's asset folder. Write the JSON there or fix the path.` };
22135
22247
  try {
22136
- return { data: JSON.parse(fs64.readFileSync(file, "utf8")) };
22248
+ return { data: JSON.parse(fs65.readFileSync(file, "utf8")) };
22137
22249
  } catch {
22138
22250
  return { message: `${at} is not valid JSON.` };
22139
22251
  }
@@ -22153,7 +22265,7 @@ function compareProblem(attrs, id) {
22153
22265
  }
22154
22266
  function validateStoryboards(file, id, text5) {
22155
22267
  const errors = [];
22156
- const dir = path66.join(ASSETS, String(id));
22268
+ const dir = path68.join(ASSETS, String(id));
22157
22269
  for (const marker of storyboardMarkers(text5)) {
22158
22270
  const named2 = assetName(marker.src, id, ["json"]);
22159
22271
  const add = (message) => errors.push({ file: rel(file), line: marker.line, rule: "storyboard-src", message });
@@ -22302,8 +22414,8 @@ var init_mockups = __esm({
22302
22414
  });
22303
22415
 
22304
22416
  // ../cli/src/commands/spec-write.ts
22305
- import fs65 from "node:fs";
22306
- import path67 from "node:path";
22417
+ import fs66 from "node:fs";
22418
+ import path69 from "node:path";
22307
22419
  function cmdSpecWrite(id, askedName, flags) {
22308
22420
  const agent = findSpecAgent(askedName);
22309
22421
  if (!agent) die(notAnAgent(askedName), { kind: "no-such-spec-agent", specAgent: askedName });
@@ -22311,11 +22423,11 @@ function cmdSpecWrite(id, askedName, flags) {
22311
22423
  const section = readSection(flags.file, flags.text);
22312
22424
  const found2 = locate(id);
22313
22425
  if (!found2) die(`no task with id ${id} under ${rel(TODO)}`, { kind: "card-not-found", id });
22314
- const file = found2.kind === "group" ? path67.join(found2.target, "root.md") : found2.target;
22315
- const { meta, body } = parseFrontmatter(fs65.readFileSync(file, "utf8"));
22426
+ const file = found2.kind === "group" ? path69.join(found2.target, "root.md") : found2.target;
22427
+ const { meta, body } = parseFrontmatter(fs66.readFileSync(file, "utf8"));
22316
22428
  if (!meta) die(`${rel(file)} has no frontmatter \u2014 run \`migrate\` first`);
22317
22429
  const { body: next, replaced } = splice(body, name, section);
22318
- fs65.writeFileSync(file, serializeFrontmatter(meta) + "\n" + next);
22430
+ fs66.writeFileSync(file, serializeFrontmatter(meta) + "\n" + next);
22319
22431
  say(`${replaced ? "rewrote" : "wrote"} the \`${name}\` section on #${id} (${rel(file)})`);
22320
22432
  return { id, specAgent: name, replaced, file: rel(file) };
22321
22433
  }
@@ -22324,7 +22436,7 @@ function readSection(file, text5) {
22324
22436
  let raw;
22325
22437
  if (file !== void 0) {
22326
22438
  try {
22327
- raw = fs65.readFileSync(file, "utf8");
22439
+ raw = fs66.readFileSync(file, "utf8");
22328
22440
  } catch {
22329
22441
  die(`can't read ${file} \u2014 write your section to a file, then pass its path`);
22330
22442
  }
@@ -22790,11 +22902,11 @@ var init_shared = __esm({
22790
22902
  });
22791
22903
 
22792
22904
  // ../cli/src/commands/goal.ts
22793
- import fs66 from "node:fs";
22905
+ import fs67 from "node:fs";
22794
22906
  function load() {
22795
22907
  const file = goalsFile();
22796
22908
  if (!file) die("Goals exist only inside a WildCrane goal discussion.");
22797
- const held4 = JSON.parse(fs66.readFileSync(file, "utf8"));
22909
+ const held4 = JSON.parse(fs67.readFileSync(file, "utf8"));
22798
22910
  if (held4.goals !== void 0 && held4.ideas !== void 0 && JSON.stringify(held4.goals) !== JSON.stringify(held4.ideas)) die("goals and ideas do not match.");
22799
22911
  const goals = held4.goals ?? held4.ideas;
22800
22912
  if (!Array.isArray(goals)) die("The goals array is missing or invalid.");
@@ -22819,7 +22931,7 @@ function splitBody2(text5) {
22819
22931
  }
22820
22932
  function readBody(bodyFile) {
22821
22933
  if (!bodyFile) die("--body-file is required; write the goal in a writable temporary file first.");
22822
- const text5 = fs66.readFileSync(bodyFile, "utf8");
22934
+ const text5 = fs67.readFileSync(bodyFile, "utf8");
22823
22935
  if (!text5.trim()) die("The goal must not be empty.");
22824
22936
  return splitBody2(text5);
22825
22937
  }
@@ -22831,7 +22943,7 @@ function named(held4, id) {
22831
22943
  function cmdGoal(args, opts) {
22832
22944
  const sub = args[0] ?? "";
22833
22945
  const { file, held: held4 } = load();
22834
- const write2 = () => fs66.writeFileSync(file, `${JSON.stringify({ workflows: held4.workflows, ideas: held4.goals }, null, 2)}
22946
+ const write2 = () => fs67.writeFileSync(file, `${JSON.stringify({ workflows: held4.workflows, ideas: held4.goals }, null, 2)}
22835
22947
  `);
22836
22948
  if (sub === "new") {
22837
22949
  const title = opts.title?.trim();
@@ -22901,8 +23013,8 @@ __export(board_exports, {
22901
23013
  buildBoardProgram: () => buildBoardProgram
22902
23014
  });
22903
23015
  import crypto4 from "node:crypto";
22904
- import fs67 from "node:fs";
22905
- import path68 from "node:path";
23016
+ import fs68 from "node:fs";
23017
+ import path70 from "node:path";
22906
23018
  async function dispatch(move2, cmd, args, opts, cli) {
22907
23019
  const ctx = ctxOf(cmd, cli.program);
22908
23020
  const data = await runAction(ctx, {}, async () => {
@@ -22941,7 +23053,7 @@ async function cloudCard(task, opts) {
22941
23053
  let request;
22942
23054
  if (typeof opts.bodyFile === "string") {
22943
23055
  try {
22944
- request = fs67.readFileSync(opts.bodyFile, "utf8");
23056
+ request = fs68.readFileSync(opts.bodyFile, "utf8");
22945
23057
  } catch {
22946
23058
  }
22947
23059
  }
@@ -22971,7 +23083,7 @@ async function cloudCard(task, opts) {
22971
23083
  const file = creationReceipt(`${checked.value}:${requestId}`, slot);
22972
23084
  const journal = readCreation(file, said3);
22973
23085
  if (!journal.made) {
22974
- const key = `akb-${path68.basename(file, ".json").slice(0, 40)}`;
23086
+ const key = `akb-${path70.basename(file, ".json").slice(0, 40)}`;
22975
23087
  const made = await createProjectTask(checked.value, journal.said, key);
22976
23088
  if (!made.ok) throw new BoardError(creationError(`Cloud did not make the task: ${made.error}`, triage), { kind: "refused" });
22977
23089
  journal.made = made.value;
@@ -23116,16 +23228,16 @@ var init_board2 = __esm({
23116
23228
  });
23117
23229
 
23118
23230
  // ../cli/src/lib/board-cli.ts
23119
- import fs68 from "node:fs";
23120
- import path69 from "node:path";
23231
+ import fs69 from "node:fs";
23232
+ import path71 from "node:path";
23121
23233
  function useBoard(found2, dirNamed) {
23122
- if (!found2.named && found2.board === path69.join(found2.root, "docs", "kanban")) setBoardRoot(found2.root, dirNamed);
23234
+ if (!found2.named && found2.board === path71.join(found2.root, "docs", "kanban")) setBoardRoot(found2.root, dirNamed);
23123
23235
  else setBoardDir(found2.board, found2.root);
23124
23236
  useProjectState();
23125
23237
  }
23126
23238
  function whole(found2, move2) {
23127
23239
  if (move2 === MAKES_A_BOARD) return found2;
23128
- if (!fs68.existsSync(path69.join(found2.board, "todo"))) {
23240
+ if (!fs69.existsSync(path71.join(found2.board, "todo"))) {
23129
23241
  die(`the board in ${found2.board} has no todo/ \u2014 run \`init\` to add what is missing.`, {
23130
23242
  kind: "board-incomplete",
23131
23243
  dir: found2.root
@@ -23134,13 +23246,13 @@ function whole(found2, move2) {
23134
23246
  return found2;
23135
23247
  }
23136
23248
  function findBoardUpward(from, move2) {
23137
- let dir = path69.resolve(from);
23249
+ let dir = path71.resolve(from);
23138
23250
  for (; ; ) {
23139
- if (hasBoard(dir)) return { root: dir, board: path69.join(dir, "docs", "kanban") };
23251
+ if (hasBoard(dir)) return { root: dir, board: path71.join(dir, "docs", "kanban") };
23140
23252
  if (isBoardDir(dir)) return { root: projectRootOf(dir), board: dir };
23141
- const kept2 = path69.join(dir, ".akb", "kanban");
23142
- if (move2 !== MAKES_A_BOARD && fs68.existsSync(path69.join(kept2, "todo"))) return { root: dir, board: kept2 };
23143
- const up = path69.dirname(dir);
23253
+ const kept2 = path71.join(dir, ".akb", "kanban");
23254
+ if (move2 !== MAKES_A_BOARD && fs69.existsSync(path71.join(kept2, "todo"))) return { root: dir, board: kept2 };
23255
+ const up = path71.dirname(dir);
23144
23256
  if (up === dir) return null;
23145
23257
  dir = up;
23146
23258
  }
@@ -23148,11 +23260,11 @@ function findBoardUpward(from, move2) {
23148
23260
  function resolveBoard(move2, { board: board2, dir, cwd, installHint }) {
23149
23261
  const named2 = board2 ?? boardFromEnv();
23150
23262
  if (named2) {
23151
- const folder = path69.resolve(cwd, named2);
23152
- if (!fs68.existsSync(folder) && move2 !== MAKES_A_BOARD) {
23263
+ const folder = path71.resolve(cwd, named2);
23264
+ if (!fs69.existsSync(folder) && move2 !== MAKES_A_BOARD) {
23153
23265
  die(`no such folder: ${folder}`, { kind: "no-such-folder", dir: folder });
23154
23266
  }
23155
- if (fs68.existsSync(folder) && !isBoardDir(folder) && move2 !== MAKES_A_BOARD) {
23267
+ if (fs69.existsSync(folder) && !isBoardDir(folder) && move2 !== MAKES_A_BOARD) {
23156
23268
  die(`${folder} is not a board \u2014 it has no todo/ and config.md. ${installHint}\u3002`, {
23157
23269
  kind: "no-board",
23158
23270
  dir: folder
@@ -23161,24 +23273,24 @@ function resolveBoard(move2, { board: board2, dir, cwd, installHint }) {
23161
23273
  return { root: projectRootOf(folder), board: folder, named: true };
23162
23274
  }
23163
23275
  if (dir !== null) {
23164
- const root = path69.resolve(dir);
23165
- if (!fs68.existsSync(root)) die(`no such folder: ${root}`, { kind: "no-such-folder", dir: root });
23166
- const kept2 = path69.join(root, ".akb", "kanban");
23167
- if (!hasBoard(root) && move2 !== MAKES_A_BOARD && fs68.existsSync(path69.join(kept2, "todo"))) return whole({ root, board: kept2 }, move2);
23276
+ const root = path71.resolve(dir);
23277
+ if (!fs69.existsSync(root)) die(`no such folder: ${root}`, { kind: "no-such-folder", dir: root });
23278
+ const kept2 = path71.join(root, ".akb", "kanban");
23279
+ if (!hasBoard(root) && move2 !== MAKES_A_BOARD && fs69.existsSync(path71.join(kept2, "todo"))) return whole({ root, board: kept2 }, move2);
23168
23280
  if (!hasBoard(root) && move2 !== MAKES_A_BOARD) {
23169
23281
  die(`no board in ${root} \u2014 it has no docs/kanban/. ${installHint}\u3002`, {
23170
23282
  kind: "no-board",
23171
23283
  dir: root
23172
23284
  });
23173
23285
  }
23174
- return whole({ root, board: path69.join(root, "docs", "kanban") }, move2);
23286
+ return whole({ root, board: path71.join(root, "docs", "kanban") }, move2);
23175
23287
  }
23176
23288
  const found2 = findBoardUpward(cwd, move2);
23177
23289
  if (found2) return whole(found2, move2);
23178
- if (move2 === MAKES_A_BOARD) return { root: path69.resolve(cwd), board: path69.join(path69.resolve(cwd), "docs", "kanban") };
23290
+ if (move2 === MAKES_A_BOARD) return { root: path71.resolve(cwd), board: path71.join(path71.resolve(cwd), "docs", "kanban") };
23179
23291
  die(
23180
- `no board here. Looked in ${path69.resolve(cwd)} and every folder above it for docs/kanban/. ${installHint}\uFF0C\u6216\u7528 --board \u6307\u5B9A\u5DF2\u6709\u770B\u677F\u3002`,
23181
- { kind: "no-board", dir: path69.resolve(cwd) }
23292
+ `no board here. Looked in ${path71.resolve(cwd)} and every folder above it for docs/kanban/. ${installHint}\uFF0C\u6216\u7528 --board \u6307\u5B9A\u5DF2\u6709\u770B\u677F\u3002`,
23293
+ { kind: "no-board", dir: path71.resolve(cwd) }
23182
23294
  );
23183
23295
  }
23184
23296
  function editDistance(a, b) {
@@ -23260,8 +23372,8 @@ var init_board_cli = __esm({
23260
23372
  init_io();
23261
23373
  init_pointer();
23262
23374
  MAKES_A_BOARD = "init";
23263
- hasBoard = (dir) => fs68.existsSync(path69.join(dir, "docs", "kanban"));
23264
- isBoardDir = (dir) => fs68.existsSync(path69.join(dir, "todo")) && fs68.existsSync(path69.join(dir, "config.md"));
23375
+ hasBoard = (dir) => fs69.existsSync(path71.join(dir, "docs", "kanban"));
23376
+ isBoardDir = (dir) => fs69.existsSync(path71.join(dir, "todo")) && fs69.existsSync(path71.join(dir, "config.md"));
23265
23377
  boardFromEnv = () => {
23266
23378
  const named2 = process.env.AI4KANBAN_BOARD?.trim();
23267
23379
  return named2 ? named2 : null;
@@ -23528,8 +23640,8 @@ var init_guide = __esm({
23528
23640
  });
23529
23641
 
23530
23642
  // ../cli/src/lib/agent/approve.ts
23531
- import fs74 from "node:fs";
23532
- import path75 from "node:path";
23643
+ import fs75 from "node:fs";
23644
+ import path77 from "node:path";
23533
23645
  function approvalRequest(req) {
23534
23646
  const id = req.id;
23535
23647
  const card = id === void 0 ? null : findCard(id);
@@ -23547,12 +23659,12 @@ function advancePhase(id) {
23547
23659
  const card = findCard(id);
23548
23660
  const found2 = locate(id);
23549
23661
  if (!card || !found2 || !awaitingApproval(card)) return { error: `#${id} is no longer waiting on approval.` };
23550
- const file = found2.kind === "group" ? path75.join(found2.target, "root.md") : found2.target;
23551
- const { meta, body } = parseFrontmatter(fs74.readFileSync(file, "utf8"));
23662
+ const file = found2.kind === "group" ? path77.join(found2.target, "root.md") : found2.target;
23663
+ const { meta, body } = parseFrontmatter(fs75.readFileSync(file, "utf8"));
23552
23664
  if (!meta) return { error: `#${id} has no frontmatter` };
23553
23665
  meta.phase = card.stage + 1;
23554
23666
  meta.status = "todo";
23555
- fs74.writeFileSync(file, serializeFrontmatter(meta) + "\n" + body);
23667
+ fs75.writeFileSync(file, serializeFrontmatter(meta) + "\n" + body);
23556
23668
  return null;
23557
23669
  }
23558
23670
  var init_approve = __esm({
@@ -23676,16 +23788,16 @@ var init_start = __esm({
23676
23788
  });
23677
23789
 
23678
23790
  // ../cli/src/lib/agent/task-chats.ts
23679
- import fs75 from "node:fs";
23680
- import path76 from "node:path";
23791
+ import fs76 from "node:fs";
23792
+ import path78 from "node:path";
23681
23793
  function taskChatFile(task, home3) {
23682
23794
  if (!TASK.test(task)) throw new Error(`${task} is not a task id.`);
23683
- return path76.join(taskChatsDir(home3), `${task}.jsonl`);
23795
+ return path78.join(taskChatsDir(home3), `${task}.jsonl`);
23684
23796
  }
23685
23797
  function readTaskChat(task, home3) {
23686
23798
  let text5;
23687
23799
  try {
23688
- text5 = fs75.readFileSync(taskChatFile(task, home3), "utf8");
23800
+ text5 = fs76.readFileSync(taskChatFile(task, home3), "utf8");
23689
23801
  } catch {
23690
23802
  return [];
23691
23803
  }
@@ -23706,13 +23818,13 @@ var init_task_chats = __esm({
23706
23818
  "use strict";
23707
23819
  init_home();
23708
23820
  TASK = /^[\w-]{1,100}$/;
23709
- taskChatsDir = (home3 = machineHome()) => path76.join(home3, "chats");
23821
+ taskChatsDir = (home3 = machineHome()) => path78.join(home3, "chats");
23710
23822
  }
23711
23823
  });
23712
23824
 
23713
23825
  // ../cli/src/lib/agent/memory-review.ts
23714
- import fs76 from "node:fs";
23715
- import path77 from "node:path";
23826
+ import fs77 from "node:fs";
23827
+ import path79 from "node:path";
23716
23828
  function taskChatsToReview(tasks) {
23717
23829
  const out = [];
23718
23830
  for (const ref of [...new Set(tasks)]) {
@@ -23734,8 +23846,8 @@ function readArchived2(id) {
23734
23846
  const found2 = Number.isInteger(id) && id > 0 ? locateArchived(id) : null;
23735
23847
  if (!found2) return null;
23736
23848
  try {
23737
- const file = found2.kind === "group" ? path77.join(found2.target, "root.md") : found2.target;
23738
- const { meta, body } = parseFrontmatter(fs76.readFileSync(file, "utf8"));
23849
+ const file = found2.kind === "group" ? path79.join(found2.target, "root.md") : found2.target;
23850
+ const { meta, body } = parseFrontmatter(fs77.readFileSync(file, "utf8"));
23739
23851
  if (!meta) return null;
23740
23852
  return { id, title: meta.title, modules: meta.modules ?? [], rejected: meta.rejected, body };
23741
23853
  } catch {
@@ -23804,17 +23916,17 @@ var init_memory_review = __esm({
23804
23916
  });
23805
23917
 
23806
23918
  // ../cli/src/lib/agent/dismissal-review.ts
23807
- import fs77 from "node:fs";
23808
- import path78 from "node:path";
23919
+ import fs78 from "node:fs";
23920
+ import path80 from "node:path";
23809
23921
  function rejectionsToReview() {
23810
- const dir = path78.join(ARCHIVE, REJECTIONS_TO_REVIEW);
23811
- if (!fs77.existsSync(dir)) return [];
23922
+ const dir = path80.join(ARCHIVE, REJECTIONS_TO_REVIEW);
23923
+ if (!fs78.existsSync(dir)) return [];
23812
23924
  const found2 = [];
23813
23925
  for (const file of walkMd(dir)) {
23814
23926
  const id = archivedId(file);
23815
23927
  if (id === null) continue;
23816
23928
  try {
23817
- const { meta } = parseFrontmatter(fs77.readFileSync(file, "utf8"));
23929
+ const { meta } = parseFrontmatter(fs78.readFileSync(file, "utf8"));
23818
23930
  if (!meta?.rejected || meta.rejected_by !== "user" || !meta.rejected_reason.trim()) continue;
23819
23931
  found2.push({ id, title: meta.title, file: rel(file), reason: meta.rejected_reason.trim(), rejectedAt: meta.rejected_at });
23820
23932
  } catch {
@@ -23844,7 +23956,7 @@ function citedSources(text5) {
23844
23956
  function withdrawnSources() {
23845
23957
  const cited = [...new Set(plannerCopies("rejected.md").flatMap((file) => {
23846
23958
  try {
23847
- return citedSources(fs77.readFileSync(file, "utf8"));
23959
+ return citedSources(fs78.readFileSync(file, "utf8"));
23848
23960
  } catch {
23849
23961
  return [];
23850
23962
  }
@@ -23869,8 +23981,8 @@ var init_dismissal_review = __esm({
23869
23981
  });
23870
23982
 
23871
23983
  // ../cli/src/lib/agent/flow.ts
23872
- import fs78 from "node:fs";
23873
- import path79 from "node:path";
23984
+ import fs79 from "node:fs";
23985
+ import path81 from "node:path";
23874
23986
  function readCard2(id, home3 = "board") {
23875
23987
  const found2 = home3 === "archive" ? locateArchived(id) : locate(id);
23876
23988
  if (!found2) {
@@ -23879,10 +23991,10 @@ function readCard2(id, home3 = "board") {
23879
23991
  { kind: "card-not-found", id }
23880
23992
  );
23881
23993
  }
23882
- const file = found2.kind === "group" ? path79.join(found2.target, "root.md") : found2.target;
23994
+ const file = found2.kind === "group" ? path81.join(found2.target, "root.md") : found2.target;
23883
23995
  let text5;
23884
23996
  try {
23885
- text5 = fs78.readFileSync(file, "utf8");
23997
+ text5 = fs79.readFileSync(file, "utf8");
23886
23998
  } catch {
23887
23999
  die(`#${id} is on the board but ${rel(file)} can't be read.`, { kind: "card-unreadable", id });
23888
24000
  }
@@ -24006,7 +24118,7 @@ function workspaceField(delivery) {
24006
24118
  if (!delivery?.worktree) return [];
24007
24119
  return field("workspace", [
24008
24120
  `write code in ${delivery.worktree} \u2014 this delivery's own worktree, on branch ${delivery.branch}.`,
24009
- path79.resolve(process.cwd()).startsWith(worktreeDir(delivery.worktree)) ? `it is your working folder already; the board's own files are NOT in it and never go on that branch.` : `cd into it first \u2014 you are in the project checkout, and code written here is not part of this build. The board's own files are NOT in it and never go on that branch.`,
24121
+ path81.resolve(process.cwd()).startsWith(worktreeDir(delivery.worktree)) ? `it is your working folder already; the board's own files are NOT in it and never go on that branch.` : `cd into it first \u2014 you are in the project checkout, and code written here is not part of this build. The board's own files are NOT in it and never go on that branch.`,
24010
24122
  `every board command names the project's own copy: \`${boardCommandFor()} <command>\`.`,
24011
24123
  `${rel(REPO_ROOT)} is the project \u2014 the card, the memory files and the docs are changed there, not here.`
24012
24124
  ]);
@@ -24750,7 +24862,7 @@ __export(watch_exports, {
24750
24862
  watchRun: () => watchRun
24751
24863
  });
24752
24864
  import { spawn as spawn4 } from "node:child_process";
24753
- import fs79 from "node:fs";
24865
+ import fs80 from "node:fs";
24754
24866
  async function watchRun(sessionId, resume = startResume) {
24755
24867
  const spec = readSpec(sessionId);
24756
24868
  const run2 = peekRun(sessionId);
@@ -24786,11 +24898,11 @@ async function watchRun(sessionId, resume = startResume) {
24786
24898
  const record = claimed ?? run2;
24787
24899
  const sub = record.action === "sub";
24788
24900
  if (claim) await setCardStatus(claim.cardId, claim.status);
24789
- fs79.mkdirSync(SESSIONS_DIR, { recursive: true });
24790
- const logFile = fs79.createWriteStream(record.logPath, { flags: "a" });
24901
+ fs80.mkdirSync(SESSIONS_DIR, { recursive: true });
24902
+ const logFile = fs80.createWriteStream(record.logPath, { flags: "a" });
24791
24903
  logFile.on("error", () => {
24792
24904
  });
24793
- const eventsFile = fs79.createWriteStream(eventsPathOf(sessionId), { flags: "a" });
24905
+ const eventsFile = fs80.createWriteStream(eventsPathOf(sessionId), { flags: "a" });
24794
24906
  eventsFile.on("error", () => {
24795
24907
  });
24796
24908
  const event2 = (e) => eventsFile.write(`${JSON.stringify(e)}
@@ -25379,7 +25491,7 @@ var init_watch = __esm({
25379
25491
  });
25380
25492
 
25381
25493
  // ../cli/src/kanban.ts
25382
- import fs85 from "node:fs";
25494
+ import fs86 from "node:fs";
25383
25495
  import { fileURLToPath as fileURLToPath3 } from "node:url";
25384
25496
 
25385
25497
  // ../cli/src/lib/cli/agent.ts
@@ -25868,8 +25980,8 @@ async function testAgent(args) {
25868
25980
  }
25869
25981
 
25870
25982
  // ../cli/src/commands/cloud.ts
25871
- import fs70 from "node:fs";
25872
- import path71 from "node:path";
25983
+ import fs71 from "node:fs";
25984
+ import path73 from "node:path";
25873
25985
 
25874
25986
  // ../cli/src/lib/cloud/avatar.ts
25875
25987
  init_session();
@@ -25917,13 +26029,13 @@ init_config();
25917
26029
  init_home();
25918
26030
  init_config();
25919
26031
  init_session();
25920
- import fs69 from "node:fs";
25921
- import path70 from "node:path";
26032
+ import fs70 from "node:fs";
26033
+ import path72 from "node:path";
25922
26034
  var FRESH_MS = 10 * 6e4;
25923
26035
  var GRACE_MS = 7 * 24 * 60 * 6e4;
25924
- var heldFile = () => path70.join(machineHome(), "pro.json");
26036
+ var heldFile = () => path72.join(machineHome(), "pro.json");
25925
26037
  function forgetPro() {
25926
- fs69.rmSync(heldFile(), { force: true });
26038
+ fs70.rmSync(heldFile(), { force: true });
25927
26039
  }
25928
26040
 
25929
26041
  // ../cli/src/lib/cloud/account.ts
@@ -26100,10 +26212,10 @@ async function cloudImage(opts, program2) {
26100
26212
  const prompt = opts.prompt.trim();
26101
26213
  if (!prompt) usage("Missing the prompt.", line);
26102
26214
  const references = opts.refs.map((file) => {
26103
- const type = REF_TYPES[path71.extname(file).toLowerCase()];
26215
+ const type = REF_TYPES[path73.extname(file).toLowerCase()];
26104
26216
  if (!type) usage(`--ref ${file}: use a PNG, JPEG or WebP file.`, line);
26105
26217
  try {
26106
- return `data:${type};base64,${fs70.readFileSync(file).toString("base64")}`;
26218
+ return `data:${type};base64,${fs71.readFileSync(file).toString("base64")}`;
26107
26219
  } catch (e) {
26108
26220
  usage(`--ref ${file}: ${e instanceof Error ? e.message : String(e)}`, line);
26109
26221
  }
@@ -26209,31 +26321,31 @@ async function generate(g) {
26209
26321
  refuse(g.failed, g.fallback);
26210
26322
  }
26211
26323
  const out = g.outFor?.(res, g.out) ?? g.out;
26212
- fs70.mkdirSync(path71.dirname(path71.resolve(out)), { recursive: true });
26213
- fs70.writeFileSync(out, Buffer.from(await res.arrayBuffer()));
26324
+ fs71.mkdirSync(path73.dirname(path73.resolve(out)), { recursive: true });
26325
+ fs71.writeFileSync(out, Buffer.from(await res.arrayBuffer()));
26214
26326
  say(`${out} (${g.label(res)})`);
26215
26327
  return { out };
26216
26328
  }
26217
26329
 
26218
26330
  // ../cli/src/commands/cloud-import.ts
26219
26331
  import crypto5 from "node:crypto";
26220
- import fs72 from "node:fs";
26221
- import path73 from "node:path";
26332
+ import fs73 from "node:fs";
26333
+ import path75 from "node:path";
26222
26334
 
26223
26335
  // ../cloud/src/board-files.ts
26224
26336
  var ROOTS = ["todo/", ".archive/", "memory/", "workflows/", "triage/", "assets/", "rules/"];
26225
26337
  var SETTINGS = ["modules.md", "config.md"];
26226
- var isSettingsPath = (path85) => SETTINGS.includes(path85) || /^rules\/(?:workflows\/)?[^/]+\.md$/.test(path85);
26338
+ var isSettingsPath = (path87) => SETTINGS.includes(path87) || /^rules\/(?:workflows\/)?[^/]+\.md$/.test(path87);
26227
26339
  var BOARD_FILE_LIMIT = 256 * 1024;
26228
- function isBoardPath(path85) {
26229
- if (!path85 || path85.length > 500 || path85.startsWith("/") || path85.includes("\\") || path85.includes("\0")) return false;
26230
- if (path85.split("/").some((part) => !part || part === "." || part === "..")) return false;
26231
- if (path85 === "todo/README.md" || path85.split("/").includes(".sync")) return false;
26232
- if (path85.startsWith("workflows/")) return true;
26233
- if (SETTINGS.includes(path85)) return true;
26234
- if (path85.startsWith("rules/")) return isSettingsPath(path85);
26235
- if (path85.startsWith("assets/")) return path85.endsWith(".json");
26236
- return ROOTS.some((root) => path85.startsWith(root)) && path85.endsWith(".md");
26340
+ function isBoardPath(path87) {
26341
+ if (!path87 || path87.length > 500 || path87.startsWith("/") || path87.includes("\\") || path87.includes("\0")) return false;
26342
+ if (path87.split("/").some((part) => !part || part === "." || part === "..")) return false;
26343
+ if (path87 === "todo/README.md" || path87.split("/").includes(".sync")) return false;
26344
+ if (path87.startsWith("workflows/")) return true;
26345
+ if (SETTINGS.includes(path87)) return true;
26346
+ if (path87.startsWith("rules/")) return isSettingsPath(path87);
26347
+ if (path87.startsWith("assets/")) return path87.endsWith(".json");
26348
+ return ROOTS.some((root) => path87.startsWith(root)) && path87.endsWith(".md");
26237
26349
  }
26238
26350
  var workflowFolder = (id) => id.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
26239
26351
 
@@ -26337,8 +26449,8 @@ init_bundled();
26337
26449
  // ../cli/src/lib/agents/shared-memory.ts
26338
26450
  init_bundled();
26339
26451
  init_parse();
26340
- import fs71 from "node:fs";
26341
- import path72 from "node:path";
26452
+ import fs72 from "node:fs";
26453
+ import path74 from "node:path";
26342
26454
  function sharedMemory(board2) {
26343
26455
  const declared = /* @__PURE__ */ new Map();
26344
26456
  const take = (folder, text5, builtIn) => {
@@ -26350,16 +26462,16 @@ function sharedMemory(board2) {
26350
26462
  const at = /^([^/]+)\/AGENT\.md$/.exec(key);
26351
26463
  if (at) take(at[1], text5, true);
26352
26464
  }
26353
- const root = path72.join(board2, "agents");
26465
+ const root = path74.join(board2, "agents");
26354
26466
  let folders2 = [];
26355
26467
  try {
26356
- folders2 = fs71.readdirSync(root, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name);
26468
+ folders2 = fs72.readdirSync(root, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name);
26357
26469
  } catch {
26358
26470
  }
26359
26471
  for (const folder of folders2.sort()) {
26360
26472
  let text5 = null;
26361
26473
  try {
26362
- text5 = fs71.readFileSync(path72.join(root, folder, "AGENT.md"), "utf8");
26474
+ text5 = fs72.readFileSync(path74.join(root, folder, "AGENT.md"), "utf8");
26363
26475
  } catch {
26364
26476
  }
26365
26477
  take(folder, text5, false);
@@ -26448,24 +26560,24 @@ async function findProject(asked) {
26448
26560
  }
26449
26561
  throw new Refused(`No project of yours has the id or prefix "${asked}".`, "not_found");
26450
26562
  }
26451
- var read = (file) => fs72.readFileSync(file, "utf8");
26563
+ var read = (file) => fs73.readFileSync(file, "utf8");
26452
26564
  var bytes2 = (text5) => Buffer.byteLength(text5, "utf8");
26453
26565
  function files(dir) {
26454
- if (!fs72.existsSync(dir)) return [];
26566
+ if (!fs73.existsSync(dir)) return [];
26455
26567
  const out = [];
26456
- for (const entry of fs72.readdirSync(dir, { withFileTypes: true })) {
26568
+ for (const entry of fs73.readdirSync(dir, { withFileTypes: true })) {
26457
26569
  if (entry.name.startsWith(".")) continue;
26458
- const full = path73.join(dir, entry.name);
26570
+ const full = path75.join(dir, entry.name);
26459
26571
  if (entry.isDirectory()) out.push(...files(full));
26460
26572
  else if (entry.isFile()) out.push(full);
26461
26573
  }
26462
26574
  return out;
26463
26575
  }
26464
- var posix2 = (p) => p.split(path73.sep).join("/");
26576
+ var posix2 = (p) => p.split(path75.sep).join("/");
26465
26577
  function openCards() {
26466
26578
  const out = [];
26467
26579
  for (const file of walkMd(TODO)) {
26468
- const rel2 = posix2(path73.relative(TODO, file));
26580
+ const rel2 = posix2(path75.relative(TODO, file));
26469
26581
  if (rel2 === "README.md") continue;
26470
26582
  const parts = rel2.split("/");
26471
26583
  const text5 = read(file);
@@ -26499,14 +26611,14 @@ function bodyProblems(text5) {
26499
26611
  return out;
26500
26612
  }
26501
26613
  function packWorkflow(flow) {
26502
- const own = path73.join(WORKFLOWS, flow.id);
26614
+ const own = path75.join(WORKFLOWS, flow.id);
26503
26615
  const out = {};
26504
- for (const file of files(own)) out[posix2(path73.relative(own, file))] = read(file);
26616
+ for (const file of files(own)) out[posix2(path75.relative(own, file))] = read(file);
26505
26617
  const named2 = [...flow.previews, ...flow.land, ...flow.scheduled].map((h) => h.agent).filter((a) => !LAND_ROLES.includes(a));
26506
26618
  for (const agent of new Set(named2)) {
26507
- const dir = path73.join(AGENTS, agent);
26508
- if (fs72.existsSync(path73.join(dir, "AGENT.md"))) {
26509
- for (const file of files(dir)) out[`agents/${agent}/${posix2(path73.relative(dir, file))}`] = read(file);
26619
+ const dir = path75.join(AGENTS, agent);
26620
+ if (fs73.existsSync(path75.join(dir, "AGENT.md"))) {
26621
+ for (const file of files(dir)) out[`agents/${agent}/${posix2(path75.relative(dir, file))}`] = read(file);
26510
26622
  continue;
26511
26623
  }
26512
26624
  for (const [key, text5] of Object.entries(BUNDLED_AGENT_FILES)) {
@@ -26558,8 +26670,8 @@ function draftsOf(text5) {
26558
26670
  const at = /^\.(assets|mockups)\/(.+)$/.exec(name);
26559
26671
  if (!at) continue;
26560
26672
  const base = at[1] === "assets" ? ASSETS : MOCKUPS;
26561
- const file = path73.resolve(base, at[2]);
26562
- if (file.startsWith(base + path73.sep) && fs72.statSync(file, { throwIfNoEntry: false })?.isFile()) out.set(name, file);
26673
+ const file = path75.resolve(base, at[2]);
26674
+ if (file.startsWith(base + path75.sep) && fs73.statSync(file, { throwIfNoEntry: false })?.isFile()) out.set(name, file);
26563
26675
  }
26564
26676
  return [...out].map(([name, file]) => ({ name, file }));
26565
26677
  }
@@ -26593,9 +26705,9 @@ async function uploadDrafts(task, drafts) {
26593
26705
  const assets = {};
26594
26706
  for (const { name, file } of drafts) {
26595
26707
  if (!card.body.includes(name)) continue;
26596
- const ext = path73.extname(file).toLowerCase();
26708
+ const ext = path75.extname(file).toLowerCase();
26597
26709
  const type = TYPES[ext] ?? "application/octet-stream";
26598
- const data = fs72.readFileSync(file);
26710
+ const data = fs73.readFileSync(file);
26599
26711
  const artifact = await api("POST", `/v1/tasks/${task}/artifacts`, { type: kindOf(ext), name: name.slice(0, 200) });
26600
26712
  const upload = await api("POST", `/v1/artifacts/${artifact.id}/uploads`, { content_type: type, size: Math.max(data.length, 1) });
26601
26713
  await api("PUT", `/v1/uploads/${upload.id}`, void 0, { bytes: data, type });
@@ -26638,10 +26750,10 @@ usage: ${program2} cloud import --project <id or prefix> [--dry-run]`, { kind: "
26638
26750
  async function run(asked, dryRun) {
26639
26751
  const project = await findProject(asked);
26640
26752
  const cloudFlows = await all(`/v1/workflows?workspace=${encodeURIComponent(project.workspace)}`);
26641
- const board2 = crypto5.createHash("sha256").update(fs72.realpathSync(KANBAN)).digest("hex").slice(0, 16);
26753
+ const board2 = crypto5.createHash("sha256").update(fs73.realpathSync(KANBAN)).digest("hex").slice(0, 16);
26642
26754
  const cards = openCards();
26643
26755
  const problems = [];
26644
- const where = (file) => posix2(path73.relative(KANBAN, file));
26756
+ const where = (file) => posix2(path75.relative(KANBAN, file));
26645
26757
  const own = workflows().filter((w) => !w.builtIn);
26646
26758
  const packed = /* @__PURE__ */ new Map();
26647
26759
  for (const flow of own) {
@@ -26668,7 +26780,7 @@ async function run(asked, dryRun) {
26668
26780
  const root = `projects/${project.id}/`;
26669
26781
  const boardFiles = [];
26670
26782
  for (const file of [...files(MEMORY), ...files(RULES), CONFIG, MODULES_MD]) {
26671
- if (!fs72.existsSync(file)) continue;
26783
+ if (!fs73.existsSync(file)) continue;
26672
26784
  const rel2 = where(file);
26673
26785
  if (!isBoardPath(rel2)) continue;
26674
26786
  const content = read(file);
@@ -26779,16 +26891,16 @@ async function run(asked, dryRun) {
26779
26891
  }
26780
26892
  function leftLocal(own) {
26781
26893
  const out = [];
26782
- const count4 = (dir) => fs72.existsSync(dir) ? walkMd(dir).length : 0;
26894
+ const count4 = (dir) => fs73.existsSync(dir) ? walkMd(dir).length : 0;
26783
26895
  const archived = count4(ARCHIVE);
26784
26896
  if (archived) out.push(`.archive/ (${archived})`);
26785
26897
  const triage = count4(TRIAGE);
26786
26898
  if (triage) out.push(`triage/ (${triage})`);
26787
- if (fs72.existsSync(DELIVERIES)) out.push("deliveries/");
26788
- if (fs72.existsSync(path73.join(KANBAN, "metrics.csv"))) out.push("metrics.csv");
26899
+ if (fs73.existsSync(DELIVERIES)) out.push("deliveries/");
26900
+ if (fs73.existsSync(path75.join(KANBAN, "metrics.csv"))) out.push("metrics.csv");
26789
26901
  const named2 = new Set(own.flatMap((w) => [...w.previews, ...w.land, ...w.scheduled].map((h) => h.agent)));
26790
- if (fs72.existsSync(AGENTS)) {
26791
- for (const e of fs72.readdirSync(AGENTS, { withFileTypes: true })) {
26902
+ if (fs73.existsSync(AGENTS)) {
26903
+ for (const e of fs73.readdirSync(AGENTS, { withFileTypes: true })) {
26792
26904
  if (e.isDirectory() && !e.name.startsWith(".") && !named2.has(e.name)) out.push(`agents/${e.name}/`);
26793
26905
  }
26794
26906
  }
@@ -26806,8 +26918,8 @@ init_session();
26806
26918
  init_io();
26807
26919
  init_paths();
26808
26920
  import { createHash as createHash4 } from "node:crypto";
26809
- import fs73 from "node:fs";
26810
- import path74 from "node:path";
26921
+ import fs74 from "node:fs";
26922
+ import path76 from "node:path";
26811
26923
  var SLUG = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
26812
26924
  var CAPTURED = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:\d{2})$/;
26813
26925
  var MAX_BYTES3 = 50 * 1024 * 1024;
@@ -26906,10 +27018,10 @@ This template has no preview.`);
26906
27018
  const written = [];
26907
27019
  for (const p of previews) {
26908
27020
  const bytes3 = Buffer.from(await (await ask(`/${at}/previews/${route(p.path)}`, {}, WITHOUT)).arrayBuffer());
26909
- const file = path74.basename(p.path);
26910
- const out = path74.join(opts.to, file.startsWith(`${name}-`) ? file : `${name}-${file}`);
26911
- fs73.mkdirSync(opts.to, { recursive: true });
26912
- fs73.writeFileSync(out, bytes3);
27021
+ const file = path76.basename(p.path);
27022
+ const out = path76.join(opts.to, file.startsWith(`${name}-`) ? file : `${name}-${file}`);
27023
+ fs74.mkdirSync(opts.to, { recursive: true });
27024
+ fs74.writeFileSync(out, bytes3);
26913
27025
  written.push(out);
26914
27026
  say(out);
26915
27027
  }
@@ -26922,15 +27034,15 @@ async function templateGet(name, opts, program2) {
26922
27034
  const { template } = await asJson(await ask(`/${at}`, {}, WITHOUT));
26923
27035
  const fetched = [];
26924
27036
  for (const f of template.files) {
26925
- const out = path74.resolve(to, f.path);
26926
- if (!out.startsWith(path74.resolve(to) + path74.sep)) refuse2(T.corrupt, WITHOUT);
27037
+ const out = path76.resolve(to, f.path);
27038
+ if (!out.startsWith(path76.resolve(to) + path76.sep)) refuse2(T.corrupt, WITHOUT);
26927
27039
  const bytes3 = Buffer.from(await (await ask(`/${at}/files/${route(f.path)}`, {}, WITHOUT)).arrayBuffer());
26928
27040
  if (bytes3.byteLength !== f.size || createHash4("sha256").update(bytes3).digest("hex") !== f.sha256) {
26929
27041
  refuse2(T.corrupt, WITHOUT);
26930
27042
  }
26931
27043
  fetched.push({ out, bytes: bytes3 });
26932
27044
  }
26933
- const taken = fetched.filter((f) => fs73.existsSync(f.out)).map((f) => path74.relative(to, f.out));
27045
+ const taken = fetched.filter((f) => fs74.existsSync(f.out)).map((f) => path76.relative(to, f.out));
26934
27046
  if (taken.length > 0) {
26935
27047
  refuse2({
26936
27048
  zh: `\u76EE\u6807\u6587\u4EF6\u5939\u5DF2\u6709\u540C\u540D\u6587\u4EF6\uFF1A${taken.join("\u3001")}\u3002\u8BF7\u6362\u4E00\u4E2A\u6587\u4EF6\u5939\uFF0C\u6216\u5148\u79FB\u8D70\u8FD9\u4E9B\u6587\u4EF6\u3002`,
@@ -26938,8 +27050,8 @@ async function templateGet(name, opts, program2) {
26938
27050
  });
26939
27051
  }
26940
27052
  for (const f of fetched) {
26941
- fs73.mkdirSync(path74.dirname(f.out), { recursive: true });
26942
- fs73.writeFileSync(f.out, f.bytes);
27053
+ fs74.mkdirSync(path76.dirname(f.out), { recursive: true });
27054
+ fs74.writeFileSync(f.out, f.bytes);
26943
27055
  }
26944
27056
  say(`\u5DF2\u53D6\u56DE ${name}\uFF1A${fetched.length} \u4E2A\u6587\u4EF6\uFF0C\u4F4D\u4E8E ${to}
26945
27057
  Fetched ${name}: ${fetched.length} files into ${to}`);
@@ -26957,25 +27069,25 @@ async function templateSource(name, opts) {
26957
27069
  say(`evidence: ${e.path}`);
26958
27070
  continue;
26959
27071
  }
26960
- const out = path74.join(opts.to, `${name}-${path74.basename(e.path)}`);
26961
- fs73.mkdirSync(opts.to, { recursive: true });
26962
- fs73.writeFileSync(out, Buffer.from(await (await ask(`/${at}/evidence/${route(e.path)}`)).arrayBuffer()));
27072
+ const out = path76.join(opts.to, `${name}-${path76.basename(e.path)}`);
27073
+ fs74.mkdirSync(opts.to, { recursive: true });
27074
+ fs74.writeFileSync(out, Buffer.from(await (await ask(`/${at}/evidence/${route(e.path)}`)).arrayBuffer()));
26963
27075
  written.push(out);
26964
27076
  say(out);
26965
27077
  }
26966
27078
  return { name, source, evidence: written.length > 0 ? written : evidence.map((e) => e.path) };
26967
27079
  }
26968
27080
  async function templatePublish(dir, opts) {
26969
- const root = path74.resolve(dir);
26970
- const manifest = path74.join(root, "template.md");
26971
- if (!fs73.existsSync(manifest)) invalid("\u6587\u4EF6\u5939\u9876\u5C42\u7F3A\u5C11 template.md\u3002", "template.md is missing from the folder\u2019s top level.");
26972
- const meta = readTemplateMeta(fs73.readFileSync(manifest, "utf8"));
27081
+ const root = path76.resolve(dir);
27082
+ const manifest = path76.join(root, "template.md");
27083
+ if (!fs74.existsSync(manifest)) invalid("\u6587\u4EF6\u5939\u9876\u5C42\u7F3A\u5C11 template.md\u3002", "template.md is missing from the folder\u2019s top level.");
27084
+ const meta = readTemplateMeta(fs74.readFileSync(manifest, "utf8"));
26973
27085
  const files2 = walk2(root);
26974
- const total = files2.reduce((sum, f) => sum + fs73.statSync(path74.join(root, f)).size, 0);
27086
+ const total = files2.reduce((sum, f) => sum + fs74.statSync(path76.join(root, f)).size, 0);
26975
27087
  if (total > MAX_BYTES3) invalid("\u6A21\u677F\u6587\u4EF6\u5408\u8BA1\u8D85\u8FC7 50 MB\u3002", "the template\u2019s files exceed 50 MB.");
26976
27088
  for (const extra of [...opts.preview, ...opts.evidence]) {
26977
- if (!fs73.existsSync(extra)) invalid(`\u627E\u4E0D\u5230\u6587\u4EF6 ${extra}\u3002`, `${extra} does not exist.`);
26978
- if (path74.resolve(extra).startsWith(root + path74.sep)) {
27089
+ if (!fs74.existsSync(extra)) invalid(`\u627E\u4E0D\u5230\u6587\u4EF6 ${extra}\u3002`, `${extra} does not exist.`);
27090
+ if (path76.resolve(extra).startsWith(root + path76.sep)) {
26979
27091
  invalid(
26980
27092
  `${extra} \u5728\u6A21\u677F\u6587\u4EF6\u5939\u5185\uFF1B\u9884\u89C8\u56FE\u4E0E\u6765\u6E90\u51ED\u8BC1\u9700\u653E\u5728\u6A21\u677F\u6587\u4EF6\u5939\u4E4B\u5916\u3002`,
26981
27093
  `${extra} is inside the template folder; keep previews and evidence outside it.`
@@ -26987,10 +27099,10 @@ async function templatePublish(dir, opts) {
26987
27099
  }
26988
27100
  const form = new FormData();
26989
27101
  form.set("meta", JSON.stringify({ ...meta, replace: !!opts.replace }));
26990
- const add = (kind, name, file) => form.append(`${kind}:${name}`, new Blob([fs73.readFileSync(file)]), path74.basename(name));
26991
- for (const f of files2) add("files", f, path74.join(root, f));
26992
- for (const f of opts.preview) add("previews", path74.basename(f), f);
26993
- for (const f of opts.evidence) add("evidence", path74.basename(f), f);
27102
+ const add = (kind, name, file) => form.append(`${kind}:${name}`, new Blob([fs74.readFileSync(file)]), path76.basename(name));
27103
+ for (const f of files2) add("files", f, path76.join(root, f));
27104
+ for (const f of opts.preview) add("previews", path76.basename(f), f);
27105
+ for (const f of opts.evidence) add("evidence", path76.basename(f), f);
26994
27106
  const done2 = await asJson(await ask("", { method: "POST", body: form }));
26995
27107
  say(
26996
27108
  done2.replaced ? `\u5DF2\u66FF\u6362 ${done2.name}\uFF1A${files2.length} \u4E2A\u6587\u4EF6\u3002
@@ -27006,11 +27118,11 @@ Deleted ${name} from the catalog.`);
27006
27118
  return { name, deleted: true };
27007
27119
  }
27008
27120
  async function templateShowcase(name, dir) {
27009
- const file = path74.join(dir, "showcase.json");
27010
- if (!fs73.existsSync(file)) invalid(`${dir} \u4E2D\u7F3A\u5C11 showcase.json\u3002`, `showcase.json is missing from ${dir}.`);
27121
+ const file = path76.join(dir, "showcase.json");
27122
+ if (!fs74.existsSync(file)) invalid(`${dir} \u4E2D\u7F3A\u5C11 showcase.json\u3002`, `showcase.json is missing from ${dir}.`);
27011
27123
  let meta;
27012
27124
  try {
27013
- meta = JSON.parse(fs73.readFileSync(file, "utf8"));
27125
+ meta = JSON.parse(fs74.readFileSync(file, "utf8"));
27014
27126
  } catch {
27015
27127
  return invalid("showcase.json \u4E0D\u662F\u6709\u6548\u7684 JSON\u3002", "showcase.json is not valid JSON.");
27016
27128
  }
@@ -27022,9 +27134,9 @@ async function templateShowcase(name, dir) {
27022
27134
  const form = new FormData();
27023
27135
  form.set("meta", JSON.stringify(meta));
27024
27136
  for (const p of new Set(named2)) {
27025
- const at = path74.join(dir, p);
27026
- if (!fs73.existsSync(at)) invalid(`\u627E\u4E0D\u5230\u6587\u4EF6 ${at}\u3002`, `${at} does not exist.`);
27027
- form.append(`media:${p}`, new Blob([fs73.readFileSync(at)]), p);
27137
+ const at = path76.join(dir, p);
27138
+ if (!fs74.existsSync(at)) invalid(`\u627E\u4E0D\u5230\u6587\u4EF6 ${at}\u3002`, `${at} does not exist.`);
27139
+ form.append(`media:${p}`, new Blob([fs74.readFileSync(at)]), p);
27028
27140
  }
27029
27141
  const done2 = await asJson(await ask(`/${encodeURIComponent(name)}/showcase`, { method: "PUT", body: form }));
27030
27142
  say(`\u5DF2\u66F4\u65B0 ${name} \u7684\u5C55\u793A\u8D44\u6599\uFF1A${done2.files} \u4E2A\u6587\u4EF6\u3002
@@ -27033,7 +27145,7 @@ Updated ${name}'s showcase: ${done2.files} files.`);
27033
27145
  }
27034
27146
  function walk2(root, sub = "") {
27035
27147
  const out = [];
27036
- for (const entry of fs73.readdirSync(path74.join(root, sub), { withFileTypes: true })) {
27148
+ for (const entry of fs74.readdirSync(path76.join(root, sub), { withFileTypes: true })) {
27037
27149
  if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
27038
27150
  const rel2 = sub ? `${sub}/${entry.name}` : entry.name;
27039
27151
  if (entry.isDirectory()) out.push(...walk2(root, rel2));
@@ -27318,7 +27430,7 @@ init_opencode();
27318
27430
  init_locate();
27319
27431
  init_store();
27320
27432
  import { spawnSync as spawnSync5 } from "node:child_process";
27321
- import path80 from "node:path";
27433
+ import path82 from "node:path";
27322
27434
  var AGENTS2 = {
27323
27435
  claude: "claude-code",
27324
27436
  codex: "codex",
@@ -27333,10 +27445,10 @@ var AGENTS2 = {
27333
27445
  var RUNTIMES = /* @__PURE__ */ new Set(["node", "bun", "deno"]);
27334
27446
  function agentOf2(args) {
27335
27447
  const words = args.trim().split(/\s+/);
27336
- const first = path80.basename(words[0] ?? "");
27448
+ const first = path82.basename(words[0] ?? "");
27337
27449
  const script = RUNTIMES.has(first) ? words[1] ?? "" : words[0] ?? "";
27338
27450
  if (script.includes("/@anthropic-ai/claude-code/")) return "claude-code";
27339
- return AGENTS2[path80.basename(script).replace(/\.(m?js|cjs|exe)$/, "")];
27451
+ return AGENTS2[path82.basename(script).replace(/\.(m?js|cjs|exe)$/, "")];
27340
27452
  }
27341
27453
  function processChain() {
27342
27454
  if (process.platform === "win32") return [];
@@ -27400,8 +27512,8 @@ init_tasks();
27400
27512
  init_read();
27401
27513
  init_rules();
27402
27514
  init_board();
27403
- import fs80 from "node:fs";
27404
- import path81 from "node:path";
27515
+ import fs81 from "node:fs";
27516
+ import path83 from "node:path";
27405
27517
  var FOLLOW_MS = 400;
27406
27518
  async function cmdStartRun(action, args, opts, program2 = "akb") {
27407
27519
  const { req, follow: follow2, print } = readRequest(action, args, opts);
@@ -27691,7 +27803,7 @@ async function cmdSubStart(words, opts) {
27691
27803
  let prompt = words.join(" ");
27692
27804
  if (opts.file) {
27693
27805
  try {
27694
- prompt = fs80.readFileSync(path81.resolve(opts.file), "utf8");
27806
+ prompt = fs81.readFileSync(path83.resolve(opts.file), "utf8");
27695
27807
  } catch {
27696
27808
  die(`--file: cannot read ${opts.file}`, { kind: "run-refused" });
27697
27809
  }
@@ -27982,7 +28094,7 @@ async function cmdWatch(sessionId) {
27982
28094
  // ../cli/src/commands/triage.ts
27983
28095
  init_scheduled();
27984
28096
  init_workflows();
27985
- import fs82 from "node:fs";
28097
+ import fs83 from "node:fs";
27986
28098
 
27987
28099
  // ../cli/src/lib/signals/index.ts
27988
28100
  init_cards();
@@ -27992,16 +28104,16 @@ init_yaml();
27992
28104
  // ../cli/src/lib/signals/config.ts
27993
28105
  init_settings2();
27994
28106
  init_paths();
27995
- import fs81 from "node:fs";
27996
- import path82 from "node:path";
28107
+ import fs82 from "node:fs";
28108
+ import path84 from "node:path";
27997
28109
  var TOKEN_KEY = "TRIAGE_ENDPOINT_TOKEN";
27998
28110
  var ENDPOINT_SETTING = "Triage endpoint";
27999
28111
  var LINE2 = /^- \*\*Triage endpoint\*\*\s*[—-]\s*(.+)$/m;
28000
- var boardRel3 = (file) => rel(file).split(path82.sep).join("/");
28112
+ var boardRel3 = (file) => rel(file).split(path84.sep).join("/");
28001
28113
  function signalEndpoint() {
28002
28114
  let text5;
28003
28115
  try {
28004
- text5 = fs81.readFileSync(CONFIG, "utf8");
28116
+ text5 = fs82.readFileSync(CONFIG, "utf8");
28005
28117
  } catch {
28006
28118
  return "";
28007
28119
  }
@@ -28177,7 +28289,7 @@ async function cmdTriageAdd(opts) {
28177
28289
  let body = opts.text;
28178
28290
  if (opts.file !== void 0) {
28179
28291
  try {
28180
- body = fs82.readFileSync(opts.file, "utf8");
28292
+ body = fs83.readFileSync(opts.file, "utf8");
28181
28293
  } catch {
28182
28294
  die(`can't read ${opts.file} \u2014 write the item to a file, then pass its path`, { kind: "needs-input" });
28183
28295
  }
@@ -28741,8 +28853,8 @@ function declareAgent(program2, cli) {
28741
28853
  }
28742
28854
 
28743
28855
  // ../cli/src/lib/cli/setup.ts
28744
- import fs84 from "node:fs";
28745
- import path84 from "node:path";
28856
+ import fs85 from "node:fs";
28857
+ import path86 from "node:path";
28746
28858
  import { fileURLToPath as fileURLToPath2 } from "node:url";
28747
28859
 
28748
28860
  // ../cli/src/commands/update.ts
@@ -28756,8 +28868,8 @@ init_paths();
28756
28868
  init_memory();
28757
28869
  init_yaml2();
28758
28870
  init_version();
28759
- import fs83 from "node:fs";
28760
- import path83 from "node:path";
28871
+ import fs84 from "node:fs";
28872
+ import path85 from "node:path";
28761
28873
  import { spawnSync as spawnSync6 } from "node:child_process";
28762
28874
  var NAME = "wildcrane";
28763
28875
  var REPO = "https://github.com/ai4kanban/ai4kanban";
@@ -28779,10 +28891,10 @@ var Report = class {
28779
28891
  for (const line of this.notes) say(` ! ${line}`);
28780
28892
  }
28781
28893
  };
28782
- var read3 = (file) => fs83.existsSync(file) ? fs83.readFileSync(file, "utf8") : null;
28894
+ var read3 = (file) => fs84.existsSync(file) ? fs84.readFileSync(file, "utf8") : null;
28783
28895
  function statOf(p) {
28784
28896
  try {
28785
- return fs83.lstatSync(p);
28897
+ return fs84.lstatSync(p);
28786
28898
  } catch {
28787
28899
  return null;
28788
28900
  }
@@ -28807,10 +28919,10 @@ async function cmdUpdate2(ctx) {
28807
28919
  return { updated: ctx.dir, from, to: SKILL_VERSION };
28808
28920
  }
28809
28921
  async function repairBoard(root, report3, named2) {
28810
- const legacy = path83.join(root, "docs", "kanban");
28811
- const kept2 = path83.join(root, ".akb", "kanban");
28812
- const board2 = named2 ?? (fs83.existsSync(legacy) ? legacy : kept2);
28813
- if (!fs83.existsSync(board2)) {
28922
+ const legacy = path85.join(root, "docs", "kanban");
28923
+ const kept2 = path85.join(root, ".akb", "kanban");
28924
+ const board2 = named2 ?? (fs84.existsSync(legacy) ? legacy : kept2);
28925
+ if (!fs84.existsSync(board2)) {
28814
28926
  report3.notes.push("\u672A\u627E\u5230\u770B\u677F\uFF0C\u8BF7\u901A\u8FC7\u63D2\u4EF6\u8FDE\u63A5\u4E91\u7AEF\u9879\u76EE\u3002");
28815
28927
  return false;
28816
28928
  }
@@ -28841,20 +28953,20 @@ function moveRuntimes(root, board2, report3) {
28841
28953
  }
28842
28954
  }
28843
28955
  function moveLegacyMemory(board2, report3) {
28844
- const memory = path83.join(board2, "memory");
28956
+ const memory = path85.join(board2, "memory");
28845
28957
  const moved = [];
28846
28958
  for (const name of MEMORY_FILES) {
28847
- const from = path83.join(board2, name);
28959
+ const from = path85.join(board2, name);
28848
28960
  const st = statOf(from);
28849
28961
  if (!st || !st.isFile()) continue;
28850
- fs83.mkdirSync(memory, { recursive: true });
28851
- const to = path83.join(memory, name);
28962
+ fs84.mkdirSync(memory, { recursive: true });
28963
+ const to = path85.join(memory, name);
28852
28964
  const there = read3(to);
28853
28965
  if (there === null) {
28854
- fs83.renameSync(from, to);
28966
+ fs84.renameSync(from, to);
28855
28967
  moved.push(name);
28856
28968
  } else if (there === read3(from)) {
28857
- fs83.rmSync(from);
28969
+ fs84.rmSync(from);
28858
28970
  moved.push(name);
28859
28971
  } else {
28860
28972
  report3.notes.push(`both docs/kanban/${name} and docs/kanban/memory/${name} exist and differ \u2014 merge them by hand`);
@@ -28863,23 +28975,23 @@ function moveLegacyMemory(board2, report3) {
28863
28975
  if (moved.length) report3.did.push(`moved the memory set into docs/kanban/memory/: ${moved.join(", ")}`);
28864
28976
  }
28865
28977
  function dropRecordFile(board2) {
28866
- const file = path83.join(board2, "record.csv");
28867
- if (!fs83.existsSync(file)) return null;
28868
- fs83.rmSync(file);
28978
+ const file = path85.join(board2, "record.csv");
28979
+ if (!fs84.existsSync(file)) return null;
28980
+ fs84.rmSync(file);
28869
28981
  return "removed docs/kanban/record.csv (the planning scores it fed are gone)";
28870
28982
  }
28871
28983
  function dropModuleGoals(board2, report3) {
28872
- const memory = path83.join(board2, "memory");
28873
- if (!fs83.existsSync(memory)) return;
28874
- const root = read3(path83.join(memory, "goal.md"));
28984
+ const memory = path85.join(board2, "memory");
28985
+ if (!fs84.existsSync(memory)) return;
28986
+ const root = read3(path85.join(memory, "goal.md"));
28875
28987
  let dropped = 0;
28876
- for (const entry of fs83.readdirSync(memory, { withFileTypes: true })) {
28988
+ for (const entry of fs84.readdirSync(memory, { withFileTypes: true })) {
28877
28989
  if (!entry.isDirectory()) continue;
28878
- const file = path83.join(memory, entry.name, "goal.md");
28990
+ const file = path85.join(memory, entry.name, "goal.md");
28879
28991
  const text5 = read3(file);
28880
28992
  if (text5 === null) continue;
28881
28993
  if (text5 === root || text5.includes(UNFILLED) || !goalWords(text5)) {
28882
- fs83.rmSync(file);
28994
+ fs84.rmSync(file);
28883
28995
  dropped++;
28884
28996
  } else {
28885
28997
  report3.notes.push(
@@ -28901,10 +29013,10 @@ var PLAN_LEADS = {
28901
29013
  var GONE_LEADS = ["software-planner", "planner"];
28902
29014
  function planLeads(board2) {
28903
29015
  const leads = { ...PLAN_LEADS };
28904
- const root = path83.join(board2, "workflows");
28905
- for (const entry of fs83.existsSync(root) ? fs83.readdirSync(root, { withFileTypes: true }) : []) {
29016
+ const root = path85.join(board2, "workflows");
29017
+ for (const entry of fs84.existsSync(root) ? fs84.readdirSync(root, { withFileTypes: true }) : []) {
28906
29018
  if (!entry.isDirectory()) continue;
28907
- const meta = splitFrontmatter(read3(path83.join(root, entry.name, "WORKFLOW.md")) ?? "").meta;
29019
+ const meta = splitFrontmatter(read3(path85.join(root, entry.name, "WORKFLOW.md")) ?? "").meta;
28908
29020
  const stages = meta === null ? void 0 : parseYamlBlock(meta).stages;
28909
29021
  const plan = stages && typeof stages === "object" && !Array.isArray(stages) ? stages.plan : void 0;
28910
29022
  const lead = plan && typeof plan === "object" && !Array.isArray(plan) && typeof plan.lead === "string" ? plan.lead.trim() : "";
@@ -28913,30 +29025,30 @@ function planLeads(board2) {
28913
29025
  return leads;
28914
29026
  }
28915
29027
  function movePlanRules(board2, report3) {
28916
- const rules = path83.join(board2, "rules");
29028
+ const rules = path85.join(board2, "rules");
28917
29029
  const moved = [];
28918
29030
  for (const [id, leads] of Object.entries(planLeads(board2))) {
28919
29031
  for (const lead of leads) {
28920
- const from = path83.join(rules, `${lead}.md`);
29032
+ const from = path85.join(rules, `${lead}.md`);
28921
29033
  const rule = read3(from)?.trim();
28922
29034
  if (!rule) continue;
28923
- const to = path83.join(rules, "workflows", `${id}.md`);
29035
+ const to = path85.join(rules, "workflows", `${id}.md`);
28924
29036
  const there = read3(to)?.trim() ?? "";
28925
29037
  if (!there.includes(rule)) {
28926
- fs83.mkdirSync(path83.dirname(to), { recursive: true });
28927
- fs83.writeFileSync(to, `${[there, rule].filter(Boolean).join("\n\n")}
29038
+ fs84.mkdirSync(path85.dirname(to), { recursive: true });
29039
+ fs84.writeFileSync(to, `${[there, rule].filter(Boolean).join("\n\n")}
28928
29040
  `);
28929
29041
  moved.push(`${lead} \u2192 ${id}`);
28930
29042
  }
28931
29043
  }
28932
29044
  }
28933
- for (const lead of GONE_LEADS) fs83.rmSync(path83.join(rules, `${lead}.md`), { force: true });
29045
+ for (const lead of GONE_LEADS) fs84.rmSync(path85.join(rules, `${lead}.md`), { force: true });
28934
29046
  if (moved.length) report3.did.push(`made each plan agent's rule its workflow's card rule (docs/kanban/rules/workflows/): ${moved.join(", ")}`);
28935
29047
  }
28936
29048
  function noteOldWorkflows(board2, report3) {
28937
29049
  const old = (dir, file, keys) => {
28938
- const root = path83.join(board2, dir);
28939
- return (fs83.existsSync(root) ? fs83.readdirSync(root, { withFileTypes: true }) : []).filter((e) => e.isDirectory() && keys.test(splitFrontmatter(read3(path83.join(root, e.name, file)) ?? "").meta ?? "")).map((e) => `docs/kanban/${dir}/${e.name}/${file}`);
29050
+ const root = path85.join(board2, dir);
29051
+ return (fs84.existsSync(root) ? fs84.readdirSync(root, { withFileTypes: true }) : []).filter((e) => e.isDirectory() && keys.test(splitFrontmatter(read3(path85.join(root, e.name, file)) ?? "").meta ?? "")).map((e) => `docs/kanban/${dir}/${e.name}/${file}`);
28940
29052
  };
28941
29053
  const files2 = [...old("workflows", "WORKFLOW.md", /^(stages|delivers):/m), ...old("agents", "AGENT.md", /^\s+(hook|lead):/m)];
28942
29054
  if (files2.length) {
@@ -28946,7 +29058,7 @@ function noteOldWorkflows(board2, report3) {
28946
29058
  }
28947
29059
  }
28948
29060
  function checkConfig(board2, report3) {
28949
- const current = read3(path83.join(board2, "config.md"));
29061
+ const current = read3(path85.join(board2, "config.md"));
28950
29062
  if (!current) return;
28951
29063
  const added = missingConfigKeys(current);
28952
29064
  if (added.length) {
@@ -28956,7 +29068,7 @@ function checkConfig(board2, report3) {
28956
29068
  }
28957
29069
  }
28958
29070
  function checkModules(board2, report3) {
28959
- const map2 = read3(path83.join(board2, "modules.md"));
29071
+ const map2 = read3(path85.join(board2, "modules.md"));
28960
29072
  if (map2 && map2.includes(UNFILLED)) {
28961
29073
  report3.notes.push(
28962
29074
  "docs/kanban/modules.md is still blank \u2014 write it from the repo (`akb guide module-map`)"
@@ -29010,14 +29122,14 @@ function declareSetup(program2, cli) {
29010
29122
  program2.version(SKILL_VERSION, "-v, --version", "print this version").addHelpText("beforeAll", spelled(cli.program));
29011
29123
  const dirOption = (cmd) => cmd.option("--dir <path>", "the project to work on (default: the current folder)").option("--json", "answer as one JSON object instead of prose");
29012
29124
  const where = (cmd) => {
29013
- const dir = path84.resolve(String(cmd.optsWithGlobals().dir ?? cli.cwd));
29014
- if (!fs84.existsSync(dir)) throw new BoardError(`no such folder: ${dir}`, { kind: "no-such-folder", dir });
29125
+ const dir = path86.resolve(String(cmd.optsWithGlobals().dir ?? cli.cwd));
29126
+ if (!fs85.existsSync(dir)) throw new BoardError(`no such folder: ${dir}`, { kind: "no-such-folder", dir });
29015
29127
  return dir;
29016
29128
  };
29017
29129
  dirOption(program2.command("update")).summary("\u4FEE\u590D\u5DF2\u6709\u770B\u677F\u5E76\u68C0\u67E5 CLI \u65B0\u7248\u672C").description("\u4FEE\u590D\u5DF2\u6709\u770B\u677F\u5E76\u63D0\u793A CLI \u5347\u7EA7\u547D\u4EE4\u3002\u6CA1\u6709\u770B\u677F\u65F6\uFF0C\u8BF7\u901A\u8FC7\u63D2\u4EF6\u8FDE\u63A5\u4E91\u7AEF\u9879\u76EE\u3002").action(async function() {
29018
29130
  const dir = where(this);
29019
29131
  const named2 = this.optsWithGlobals().board ?? process.env.AI4KANBAN_BOARD?.trim();
29020
- const board2 = named2 ? path84.resolve(cli.cwd, named2) : null;
29132
+ const board2 = named2 ? path86.resolve(cli.cwd, named2) : null;
29021
29133
  await runAction(ctxOf(this, cli.program), {}, () => cmdUpdate2({ dir, program: cli.program, board: board2 }));
29022
29134
  });
29023
29135
  program2.command("__rules", { hidden: true }).action(function() {
@@ -29061,7 +29173,7 @@ function invokedDirectly() {
29061
29173
  const entry = process.argv[1];
29062
29174
  if (!entry) return false;
29063
29175
  try {
29064
- return fs85.realpathSync(entry) === fs85.realpathSync(SELF2);
29176
+ return fs86.realpathSync(entry) === fs86.realpathSync(SELF2);
29065
29177
  } catch {
29066
29178
  return false;
29067
29179
  }