wildcrane-runner 0.6.17 → 0.6.19

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.14 — built by runner/scripts/build.mjs. Do not edit.
2
+ // ai4kanban 0.11.17 — 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,7 +1167,7 @@ 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 path87 = __require("node:path");
1170
+ var path88 = __require("node:path");
1171
1171
  var fs87 = __require("node:fs");
1172
1172
  var process2 = __require("node:process");
1173
1173
  var { Argument: Argument2, humanReadableArgName } = require_argument();
@@ -2167,9 +2167,9 @@ 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 = path87.resolve(baseDir, baseName);
2170
+ const localBin = path88.resolve(baseDir, baseName);
2171
2171
  if (fs87.existsSync(localBin)) return localBin;
2172
- if (sourceExt.includes(path87.extname(baseName))) return void 0;
2172
+ if (sourceExt.includes(path88.extname(baseName))) return void 0;
2173
2173
  const foundExt = sourceExt.find(
2174
2174
  (ext) => fs87.existsSync(`${localBin}${ext}`)
2175
2175
  );
@@ -2187,17 +2187,17 @@ Expecting one of '${allowedValues.join("', '")}'`);
2187
2187
  } catch {
2188
2188
  resolvedScriptPath = this._scriptPath;
2189
2189
  }
2190
- executableDir = path87.resolve(
2191
- path87.dirname(resolvedScriptPath),
2190
+ executableDir = path88.resolve(
2191
+ path88.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 = path87.basename(
2198
+ const legacyName = path88.basename(
2199
2199
  this._scriptPath,
2200
- path87.extname(this._scriptPath)
2200
+ path88.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(path87.extname(executableFile));
2211
+ launchWithNode = sourceExt.includes(path88.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 = path87.basename(filename, path87.extname(filename));
3058
+ this._name = path88.basename(filename, path88.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(path88) {
3073
- if (path88 === void 0) return this._executableDir;
3074
- this._executableDir = path88;
3072
+ executableDir(path89) {
3073
+ if (path89 === void 0) return this._executableDir;
3074
+ this._executableDir = path89;
3075
3075
  return this;
3076
3076
  }
3077
3077
  /**
@@ -3487,11 +3487,11 @@ function projectStateDir(board2, root = stateRootOf(board2)) {
3487
3487
  function ensureProjectState(board2, root = stateRootOf(board2)) {
3488
3488
  const dir = projectStateDir(board2, root);
3489
3489
  if (!fs.existsSync(dir)) {
3490
- const legacy = legacyProjectStateDir(board2);
3490
+ const legacy2 = legacyProjectStateDir(board2);
3491
3491
  fs.mkdirSync(path2.dirname(dir), { recursive: true });
3492
3492
  const temporary = fs.mkdtempSync(`${dir}.import-`);
3493
3493
  try {
3494
- if (fs.existsSync(legacy)) fs.cpSync(legacy, temporary, {
3494
+ if (fs.existsSync(legacy2)) fs.cpSync(legacy2, temporary, {
3495
3495
  recursive: true,
3496
3496
  filter: (source) => ![".lock", ".answering"].some((suffix) => path2.basename(source).endsWith(suffix))
3497
3497
  });
@@ -3842,6 +3842,9 @@ async function refresh() {
3842
3842
  }
3843
3843
  const fresh = sessionFrom(await response.json(), now);
3844
3844
  if (!fresh) return { ok: false, reason: "expired" };
3845
+ const current = readSession();
3846
+ if (!current || current.subject !== now.subject) return { ok: false, reason: "expired" };
3847
+ if (current.refreshToken !== now.refreshToken) return { ok: true, token: current.accessToken, session: current };
3845
3848
  writeSession(fresh);
3846
3849
  return { ok: true, token: fresh.accessToken, session: fresh };
3847
3850
  } finally {
@@ -4515,13 +4518,13 @@ function parse(file, lenient = false) {
4515
4518
  if (i >= lines.length) return null;
4516
4519
  const required = lenient ? ["source_id"] : FIELDS;
4517
4520
  if (required.some((field2) => !held4[field2])) return null;
4518
- const legacy = held4.source_type ? "" : held4.source || held4.platform || "";
4519
- const missed = legacy && !matchSourceType(legacy) ? metaPair("source", legacy) : null;
4521
+ const legacy2 = held4.source_type ? "" : held4.source || held4.platform || "";
4522
+ const missed = legacy2 && !matchSourceType(legacy2) ? metaPair("source", legacy2) : null;
4520
4523
  return {
4521
4524
  sourceId: held4.source_id,
4522
4525
  title: held4.title ?? "",
4523
4526
  summary: lines.slice(i + 1).join("\n").replace(/^\n+/, "").replace(/\s+$/, ""),
4524
- sourceType: held4.source_type ? readSourceType(held4.source_type) : matchSourceType(legacy),
4527
+ sourceType: held4.source_type ? readSourceType(held4.source_type) : matchSourceType(legacy2),
4525
4528
  meta: missed ? [missed, ...written] : written,
4526
4529
  url: held4.url || "",
4527
4530
  ...held4.agent ? { agent: held4.agent } : {},
@@ -5379,8 +5382,8 @@ function renamedPlan(rel2) {
5379
5382
  return found2.sort((a, b) => b.at - a.at)[0].rel;
5380
5383
  }
5381
5384
  function planFromText(text5) {
5382
- const legacy = `${legacyProjectStateDir(KANBAN)}/`;
5383
- if (text5.startsWith(legacy)) text5 = text5.slice(legacy.length);
5385
+ const legacy2 = `${legacyProjectStateDir(KANBAN)}/`;
5386
+ if (text5.startsWith(legacy2)) text5 = text5.slice(legacy2.length);
5384
5387
  const local = `${path15.dirname(PLANS)}/`;
5385
5388
  if (text5.startsWith(local)) text5 = text5.slice(local.length);
5386
5389
  if (text5.startsWith(`${KANBAN}/`)) text5 = text5.slice(KANBAN.length + 1);
@@ -5514,7 +5517,7 @@ var init_package = __esm({
5514
5517
  "../cli/package.json"() {
5515
5518
  package_default = {
5516
5519
  name: "wildcrane",
5517
- version: "0.11.14",
5520
+ version: "0.11.17",
5518
5521
  akbContract: 5,
5519
5522
  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.",
5520
5523
  keywords: [
@@ -6115,7 +6118,7 @@ var init_file_memo = __esm({
6115
6118
  var AGENT_default;
6116
6119
  var init_AGENT = __esm({
6117
6120
  "../cli/src/agents/builder/AGENT.md"() {
6118
- AGENT_default = "---\nname: builder\ndescription: \u5B9E\u73B0\u5E76\u843D\u5730\u5DF2\u6279\u51C6\u7684\u8F6F\u4EF6\u53D8\u66F4\u3002\n---\n\n- **\u5DF2\u6279\u51C6\u7684\u754C\u9762\u9884\u89C8**\uFF1A\u5B9E\u73B0\u5361\u7247\u9884\u89C8\u4E2D\u7684\u6BCF\u4E2A\u5C4F\u5E55\uFF0C\u5728\u684C\u9762\u548C\u624B\u673A\u5BBD\u5EA6\u4E0B\u9010\u4E00\u5BF9\u7167\u5E76\u4FEE\u6B63\u5DEE\u5F02\u3002\n- **\u5DF2\u6279\u51C6\u7684 API \u9884\u89C8**\uFF1A\u6309\u5361\u7247\u9884\u89C8\u5B9E\u73B0\u6BCF\u4E2A\u63A5\u53E3\uFF0C\u5E76\u636E\u6B64\u7F16\u5199\u6587\u6863\u3002\n- **\u5DF2\u6279\u51C6\u7684\u6570\u636E\u5E93\u9884\u89C8**\uFF1A\u7F16\u5199\u8FC1\u79FB\uFF0C\u4F7F\u6700\u7EC8\u7ED3\u6784\u4E0E\u5361\u7247\u6279\u51C6\u7684\u9884\u89C8\u4E00\u81F4\u3002\n- **\u5B8C\u6210\u8FB9\u754C**\uFF1A\u9664\u975E\u5361\u7247\u7684\u76EE\u6807\u672C\u8EAB\u5C31\u662F\u90E8\u7F72\u6216\u53D1\u5E03\uFF0C\u5426\u5219\u53D8\u66F4\u843D\u5730\u4E14\u68C0\u67E5\u901A\u8FC7\u5373\u5B8C\u6210\uFF1B\u4E0D\u8981\u4E3A\u5B8C\u6210\u4EFB\u52A1\u800C\u63A8\u9001\u3001\u90E8\u7F72\u6216\u53D1\u5E03\u3002\n- **\u9700\u8981\u4E0A\u7EBF\u7684\u4E8B\u9879**\uFF1A\u628A\u5FC5\u987B\u4E0A\u7EBF\u624D\u80FD\u8FDB\u884C\u7684\u9A8C\u8BC1\u6216\u6B65\u9AA4\u4EA4\u7ED9\u7528\u6237\uFF0C\u53EA\u5728\u6700\u7EC8\u56DE\u590D\u4E2D\u8BF4\u660E\uFF0C\u4E0D\u521B\u5EFA\u8DDF\u8FDB\u4EFB\u52A1\uFF0C\u4E5F\u4E0D\u63D0\u95EE\u3002\n- **\u68C0\u67E5**\uFF1A\u6B64\u540E\u6CA1\u6709\u4EBA\u518D\u5BA1\u67E5\u5B9E\u73B0\uFF1B\u8FD0\u884C\u53D7\u5F71\u54CD\u8DEF\u5F84\u7684\u9488\u5BF9\u6027\u68C0\u67E5\uFF0C\u4EE5\u53CA\u4ED3\u5E93\u8981\u6C42\u7684\u5168\u90E8\u68C0\u67E5\u3002\n";
6121
+ AGENT_default = "---\nname: builder\ndescription: Implement and land approved software changes.\n---\n\n- **Autonomy**: Decide technical details within the approved scope and existing permissions. Diagnose build problems, fix them, verify the result, and continue; do not repeat the same failed attempt instead of investigating. Ask the user only for decisions or blockers only they can resolve.\n- **Approved UI previews**: Implement every screen in the approved previews, compare desktop and mobile layouts, and correct differences.\n- **Approved API previews**: Implement every approved endpoint and document it.\n- **Approved database previews**: Write migrations that match the approved structure.\n- **Completion**: Finish when changes have landed and checks pass, unless deployment or publication is the task's goal. Do not push, deploy, or publish merely to finish a task.\n- **Production steps**: Hand the user any steps or validation that require production access. Mention them only in the final response; create no follow-up tasks and ask no questions.\n- **Checks**: Nobody reviews the implementation after you. Run targeted checks for the affected paths and all checks the repository requires for the changes.\n";
6119
6122
  }
6120
6123
  });
6121
6124
 
@@ -7512,13 +7515,15 @@ var init_identity = __esm({
7512
7515
  });
7513
7516
 
7514
7517
  // ../cli/src/lib/agent/harnesses/types.ts
7518
+ import { createHash as createHash2 } from "node:crypto";
7515
7519
  import fs22 from "node:fs";
7516
7520
  import path24 from "node:path";
7517
7521
  function namesFlag(argv, flags) {
7518
7522
  return argv.some((tok) => flags.some((flag) => tok === flag || tok.startsWith(`${flag}=`)));
7519
7523
  }
7520
7524
  function machineFile(name, text5) {
7521
- const file = path24.join(machineHome(), "prompts", name);
7525
+ const suffix = createHash2("sha256").update(text5).digest("hex").slice(0, 16);
7526
+ const file = path24.join(machineHome(), "prompts", `${path24.parse(name).name}-${suffix}${path24.extname(name)}`);
7522
7527
  try {
7523
7528
  if (fs22.readFileSync(file, "utf8") === text5) return file;
7524
7529
  } catch {
@@ -8353,10 +8358,10 @@ function findRollout(threadId) {
8353
8358
  }
8354
8359
  for (const name of names4) {
8355
8360
  if (!name.startsWith("rollout-") || !name.endsWith(tail2)) continue;
8356
- const path87 = join(dir, name);
8361
+ const path88 = join(dir, name);
8357
8362
  try {
8358
- const at = statSync(path87).mtimeMs;
8359
- if (!newest2 || at > newest2.at) newest2 = { path: path87, at };
8363
+ const at = statSync(path88).mtimeMs;
8364
+ if (!newest2 || at > newest2.at) newest2 = { path: path88, at };
8360
8365
  } catch {
8361
8366
  }
8362
8367
  }
@@ -8364,11 +8369,11 @@ function findRollout(threadId) {
8364
8369
  return newest2?.path;
8365
8370
  }
8366
8371
  function readCodexRunFacts(threadId) {
8367
- const path87 = findRollout(threadId);
8368
- if (!path87) return void 0;
8372
+ const path88 = findRollout(threadId);
8373
+ if (!path88) return void 0;
8369
8374
  let raw;
8370
8375
  try {
8371
- raw = readFileSync(path87, "utf8");
8376
+ raw = readFileSync(path88, "utf8");
8372
8377
  } catch {
8373
8378
  return void 0;
8374
8379
  }
@@ -8496,14 +8501,19 @@ function renderCompleted(item) {
8496
8501
  const changes = Array.isArray(item.changes) ? item.changes : [];
8497
8502
  return changes.map((raw) => {
8498
8503
  const change2 = obj(raw);
8499
- const path87 = str2(change2.path);
8500
- return path87 ? `\u23FA ${str2(change2.kind) || "change"}(${path87})
8504
+ const path88 = str2(change2.path);
8505
+ return path88 ? `\u23FA ${str2(change2.kind) || "change"}(${path88})
8501
8506
  ` : "";
8502
8507
  }).join("");
8503
8508
  }
8504
8509
  case "error":
8505
8510
  return `[error] ${str2(item.message)}
8506
8511
  `;
8512
+ case "mcp_tool_call": {
8513
+ const error = str2(obj(item.error).message);
8514
+ return error ? `[MCP ${str2(item.server)}.${str2(item.tool)}] ${error}
8515
+ ` : "";
8516
+ }
8507
8517
  default:
8508
8518
  return "";
8509
8519
  }
@@ -8617,16 +8627,13 @@ function createCodexStreamRenderer(carried = false) {
8617
8627
  return renderCompleted(item);
8618
8628
  }
8619
8629
  case "turn.completed":
8630
+ failure = void 0;
8620
8631
  total = codexUsage(ev.usage) ?? total;
8621
8632
  return "";
8622
- // The two events that say the RUN did not get there — the turn giving up, and the
8623
- // stream itself failing. Kept as the run's failure (#525), because `codex exec`
8624
- // reports its reason on this stream and nowhere else, and a retry has to read it.
8625
- // An item's own `error` is not one of them: a tool that failed is the agent's
8626
- // problem to work around, and the turn around it may still finish.
8633
+ // 最终失败保留;连接提示只在没有后续成功完成时才作为失败。
8627
8634
  case "turn.failed": {
8628
- const said3 = str2(obj(ev.error).message);
8629
- failure = said3 || failure;
8635
+ const said3 = str2(obj(ev.error).message) || failure || "Codex \u6267\u884C\u5931\u8D25";
8636
+ failure = said3;
8630
8637
  events.error(said3);
8631
8638
  return `[error] ${said3}
8632
8639
  `;
@@ -8634,8 +8641,9 @@ function createCodexStreamRenderer(carried = false) {
8634
8641
  case "error": {
8635
8642
  const said3 = str2(ev.message);
8636
8643
  failure = said3 || failure;
8637
- events.error(said3);
8638
- return `[error] ${said3}
8644
+ events.log(`[Codex] ${said3}
8645
+ `);
8646
+ return `[Codex] ${said3}
8639
8647
  `;
8640
8648
  }
8641
8649
  default:
@@ -8756,10 +8764,10 @@ function readKimiRunFacts(sessionId) {
8756
8764
  const total = { input: 0, cacheCreation: 0, cacheRead: 0, output: 0 };
8757
8765
  let model;
8758
8766
  let holding = 0;
8759
- for (const { path: path87, main } of wireFiles(entry.sessionDir)) {
8767
+ for (const { path: path88, main } of wireFiles(entry.sessionDir)) {
8760
8768
  let raw;
8761
8769
  try {
8762
- raw = readFileSync2(path87, "utf8");
8770
+ raw = readFileSync2(path88, "utf8");
8763
8771
  } catch {
8764
8772
  continue;
8765
8773
  }
@@ -9040,8 +9048,8 @@ var init_tail = __esm({
9040
9048
  import fs24 from "node:fs";
9041
9049
  function whereHint(update2, title) {
9042
9050
  const locations = Array.isArray(update2.locations) ? update2.locations : [];
9043
- const path87 = str2(obj(locations[0]).path);
9044
- return path87 && !title.includes(path87) ? hint(path87) : "";
9051
+ const path88 = str2(obj(locations[0]).path);
9052
+ return path88 && !title.includes(path88) ? hint(path88) : "";
9045
9053
  }
9046
9054
  function saidBy(update2) {
9047
9055
  const content = Array.isArray(update2.content) ? update2.content : [];
@@ -9747,9 +9755,12 @@ var init_claude_code = __esm({
9747
9755
  forkArgs(argv, resumeId, sessionId) {
9748
9756
  return [...claudeStreamArgs(argv), "--resume", resumeId, "--fork-session", "--session-id", sessionId];
9749
9757
  },
9750
- // Set again on every turn, a resumed one too: the system prompt is not part of the session.
9758
+ // 默认快照会忽略续跑传入的新规则;仅显式关闭快照的会话支持更新。
9751
9759
  systemPrompt(text5, argv) {
9752
- return namesFlag(argv, ["--append-system-prompt", "--append-system-prompt-file"]) ? {} : { args: ["--append-system-prompt", text5] };
9760
+ if (namesFlag(argv, ["--append-system-prompt", "--append-system-prompt-file"])) return {};
9761
+ const snapshot = argv.flatMap((arg, i) => arg === "--system-prompt-snapshot" ? [argv[i + 1]] : arg.startsWith("--system-prompt-snapshot=") ? [arg.slice("--system-prompt-snapshot=".length)] : []).at(-1);
9762
+ if (namesFlag(argv, ["--resume", "-r", "--continue", "-c"]) && snapshot !== "off") return {};
9763
+ return { args: ["--append-system-prompt", text5] };
9753
9764
  },
9754
9765
  // What Claude Code takes: who pays for the run, where that run goes, a model id, and how
9755
9766
  // hard that model thinks. The provider comes first because it decides the rest — which
@@ -9978,6 +9989,46 @@ var init_claude_code = __esm({
9978
9989
  }
9979
9990
  });
9980
9991
 
9992
+ // ../cli/src/lib/agent/proxy.ts
9993
+ import { execFileSync } from "node:child_process";
9994
+ function codexProxyEnv(env2 = process.env, platform = process.platform, read4 = () => execFileSync("/usr/sbin/scutil", ["--proxy"], { encoding: "utf8", timeout: 1e3, stdio: ["ignore", "pipe", "ignore"] })) {
9995
+ const out = { ...env2 };
9996
+ if (platform !== "darwin" || PROXY_KEYS.some((key) => env2[key] !== void 0)) return out;
9997
+ let raw;
9998
+ try {
9999
+ raw = read4();
10000
+ } catch {
10001
+ return out;
10002
+ }
10003
+ const value = (key) => new RegExp(`^\\s*${key}\\s*:\\s*(.*?)\\s*$`, "m").exec(raw)?.[1];
10004
+ for (const [name, key, scheme] of [["HTTP", "HTTP_PROXY", "http"], ["HTTPS", "HTTPS_PROXY", "http"], ["SOCKS", "ALL_PROXY", "socks5"]]) {
10005
+ if (value(`${name}Enable`) !== "1") continue;
10006
+ const host2 = value(`${name}Proxy`);
10007
+ const port = Number(value(`${name}Port`));
10008
+ if (!host2 || /[\s/@?#]/.test(host2) || !Number.isInteger(port) || port < 1 || port > 65535) continue;
10009
+ try {
10010
+ out[key] = new URL(`${scheme}://${host2.includes(":") && !host2.startsWith("[") ? `[${host2}]` : host2}:${port}`).href.replace(/\/$/, "");
10011
+ } catch {
10012
+ }
10013
+ }
10014
+ if (PROXY_KEYS.some((key) => out[key]) && env2.NO_PROXY === void 0 && env2.no_proxy === void 0) {
10015
+ const exceptions = /ExceptionsList\s*:\s*<array>\s*\{([^}]+)\}/.exec(raw)?.[1];
10016
+ const bypass = exceptions?.split("\n").flatMap((line) => {
10017
+ const host2 = /^\s*\d+\s*:\s*(\S+)\s*$/.exec(line)?.[1];
10018
+ return host2 ? [host2.startsWith("*.") ? host2.slice(1) : host2] : [];
10019
+ });
10020
+ if (bypass?.length) out.NO_PROXY = bypass.join(",");
10021
+ }
10022
+ return out;
10023
+ }
10024
+ var PROXY_KEYS;
10025
+ var init_proxy = __esm({
10026
+ "../cli/src/lib/agent/proxy.ts"() {
10027
+ "use strict";
10028
+ PROXY_KEYS = ["HTTPS_PROXY", "https_proxy", "HTTP_PROXY", "http_proxy", "ALL_PROXY", "all_proxy"];
10029
+ }
10030
+ });
10031
+
9981
10032
  // ../cli/src/lib/agent/worktree.ts
9982
10033
  import { spawnSync as spawnSync3 } from "node:child_process";
9983
10034
  import crypto3 from "node:crypto";
@@ -10430,6 +10481,7 @@ var init_codex = __esm({
10430
10481
  init_paths();
10431
10482
  init_wire();
10432
10483
  init_locate();
10484
+ init_proxy();
10433
10485
  init_models();
10434
10486
  init_worktree();
10435
10487
  init_transient();
@@ -10464,10 +10516,15 @@ var init_codex = __esm({
10464
10516
  forkArgs(argv, resumeId, _sessionId, cwd) {
10465
10517
  return [...codexExtraArgs(argv, cwd), "fork", resumeId];
10466
10518
  },
10467
- // Codex's developer message. `-c` is global, so it may sit before `exec`. It stands in for a
10468
- // `developer_instructions` in the user's config.toml, which is then left out of board runs.
10519
+ // 自设系统指令保持原样;无法追加时由调用方明确提示。
10469
10520
  systemPrompt(text5, argv) {
10470
- return namesFlag(argv, ["developer_instructions"]) ? {} : { args: ["-c", `developer_instructions=${JSON.stringify(text5)}`] };
10521
+ if (namesFlag(argv, ["developer_instructions", "model_instructions_file"])) return {};
10522
+ try {
10523
+ const config = fs26.readFileSync(path29.join(process.env.CODEX_HOME || home2(".codex"), "config.toml"), "utf8");
10524
+ if (/^\s*(?:developer_instructions|model_instructions_file)\s*=/m.test(config)) return {};
10525
+ } catch {
10526
+ }
10527
+ return { args: ["-c", `developer_instructions=${JSON.stringify(text5)}`] };
10471
10528
  },
10472
10529
  // What Codex takes, in the order Claude Code's dialog draws the same four: who pays for
10473
10530
  // the run and where it goes, that endpoint's address, the key, a model, and how hard that
@@ -10595,7 +10652,7 @@ var init_codex = __esm({
10595
10652
  // No `providerEnv` either: the pick already drops and sets OPENAI_API_KEY, and there is
10596
10653
  // no second variable to drop. Codex reads no base URL from the environment — an exported
10597
10654
  // OPENAI_BASE_URL moves nothing — and where a run goes is settled on its command line.
10598
- env: () => ({ ...process.env }),
10655
+ env: () => codexProxyEnv(),
10599
10656
  // A completed turn carries its token counts and nothing else. The model comes from the
10600
10657
  // session's rollout on disk instead, and the price is worked out from the two
10601
10658
  // (agent/wire/codex-stream.ts). A model whose rates the board doesn't know shows no price.
@@ -10806,9 +10863,9 @@ Verify your work by running the code or tests. Keep answers brief and factual.`;
10806
10863
  // dsh-acp's persona IS its system prompt, so the ask is added to the one in effect: the
10807
10864
  // user's own `DSH_SYSTEM_PROMPT`, else the bridge's default. A `--persona` on the command
10808
10865
  // line outranks both, and is left alone.
10809
- systemPrompt(text5, argv) {
10866
+ systemPrompt(text5, argv, env2) {
10810
10867
  if (namesFlag(argv, ["--persona"])) return {};
10811
- return { env: { DSH_SYSTEM_PROMPT: `${process.env.DSH_SYSTEM_PROMPT || DSH_PERSONA}
10868
+ return { env: { DSH_SYSTEM_PROMPT: `${env2.DSH_SYSTEM_PROMPT || DSH_PERSONA}
10812
10869
 
10813
10870
  ${text5}` } };
10814
10871
  },
@@ -11226,12 +11283,12 @@ var init_opencode = __esm({
11226
11283
  env: () => ({ ...process.env }),
11227
11284
  // `instructions` files join the system prompt, and OpenCode adds this config's list to the
11228
11285
  // user's own rather than replacing it.
11229
- systemPrompt(text5) {
11286
+ systemPrompt(text5, _argv, env2) {
11230
11287
  const file = machineFile("opencode-language.md", `${text5}
11231
11288
  `);
11232
11289
  let own = {};
11233
11290
  try {
11234
- own = obj2(JSON.parse(process.env.OPENCODE_CONFIG_CONTENT ?? "{}"));
11291
+ own = obj2(JSON.parse(env2.OPENCODE_CONFIG_CONTENT ?? "{}"));
11235
11292
  } catch {
11236
11293
  }
11237
11294
  const instructions = [...Array.isArray(own.instructions) ? own.instructions : [], file];
@@ -11484,26 +11541,36 @@ var init_capabilities = __esm({
11484
11541
  });
11485
11542
 
11486
11543
  // ../cli/src/lib/agent/language.ts
11487
- function languageNote() {
11488
- return process.env[LANGUAGE_LINE_ENV] ?? LINES[readLanguage()];
11544
+ function runLanguage() {
11545
+ if (process.env[LANGUAGE_ENV] !== void 0) return languageCode(process.env[LANGUAGE_ENV]);
11546
+ if (process.env[LANGUAGE_LINE_ENV] !== void 0) return legacyLanguage(process.env[LANGUAGE_LINE_ENV]);
11547
+ return readLanguage();
11489
11548
  }
11490
- var LANGUAGE_LINE_ENV, LINES, SYSTEM, asksForTranslation, translating, systemLanguage, inLanguage;
11549
+ var LANGUAGE_ENV, LANGUAGE_LINE_ENV, SYSTEM_LANGUAGE_ZH, SYSTEM_LANGUAGE_EN, systemLanguages, isSystemLanguage, legacy, languageCode, legacyLanguage, translating, systemLanguage;
11491
11550
  var init_language = __esm({
11492
11551
  "../cli/src/lib/agent/language.ts"() {
11493
11552
  "use strict";
11494
11553
  init_settings();
11554
+ LANGUAGE_ENV = "AI4KANBAN_LANGUAGE";
11495
11555
  LANGUAGE_LINE_ENV = "AI4KANBAN_LANGUAGE_LINE";
11496
- LINES = {
11497
- en: "",
11498
- zh: "\u8BF7\u7528\u4E2D\u6587\u56DE\u590D\u548C\u5199\u4F5C\u3002"
11499
- };
11500
- SYSTEM = {
11501
- [LINES.zh]: "\u65E0\u8BBA\u5BF9\u8BDD\u91CC\u7684\u5361\u7247\u3001\u4EE3\u7801\u548C\u8D34\u8FDB\u6765\u7684\u5185\u5BB9\u662F\u4EC0\u4E48\u8BED\u8A00\uFF0C\u56DE\u590D\u548C\u5199\u4E0B\u7684\u5185\u5BB9\u90FD\u7528\u4E2D\u6587\u3002\u4EE3\u7801\u3001\u547D\u4EE4\u3001\u6587\u4EF6\u540D\u548C\u5B57\u6BB5\u540D\u4FDD\u6301\u539F\u6587\u3002"
11502
- };
11503
- asksForTranslation = (line) => line !== "" && !/\bEnglish\b/.test(line);
11504
- translating = () => asksForTranslation(languageNote());
11505
- systemLanguage = (line) => asksForTranslation(line) ? SYSTEM[line] ?? line : "";
11506
- inLanguage = (prompt) => [prompt, languageNote()].filter(Boolean).join("\n\n");
11556
+ SYSTEM_LANGUAGE_ZH = "\u7528\u4E2D\u6587\u56DE\u590D\u3002\u6210\u679C\u8BED\u8A00\u4F9D\u6B21\u9075\u5FAA\u7528\u6237\u660E\u786E\u8981\u6C42\u3001\u6210\u679C\u4E13\u7528\u6307\u4EE4\u3001\u65E2\u6709\u6210\u679C\u6216\u9879\u76EE\u60EF\u4F8B\uFF0C\u6700\u540E\u91C7\u7528\u672C\u6B21\u8BF7\u6C42\u7684\u8BED\u8A00\u3002\u4EE3\u7801\u3001\u547D\u4EE4\u3001\u6587\u4EF6\u540D\u548C\u5B57\u6BB5\u540D\u4FDD\u6301\u539F\u6587\u3002";
11557
+ SYSTEM_LANGUAGE_EN = "Reply in English. Artifact language follows explicit user requests, artifact instructions, existing or project conventions, then the current request's language. Keep code, commands, filenames, and field names unchanged.";
11558
+ systemLanguages = /* @__PURE__ */ new Set([
11559
+ SYSTEM_LANGUAGE_ZH,
11560
+ SYSTEM_LANGUAGE_EN,
11561
+ "\u65E0\u8BBA\u5BF9\u8BDD\u91CC\u7684\u5361\u7247\u3001\u4EE3\u7801\u548C\u8D34\u8FDB\u6765\u7684\u5185\u5BB9\u662F\u4EC0\u4E48\u8BED\u8A00\uFF0C\u56DE\u590D\u548C\u5199\u4E0B\u7684\u5185\u5BB9\u90FD\u7528\u4E2D\u6587\u3002\u4EE3\u7801\u3001\u547D\u4EE4\u3001\u6587\u4EF6\u540D\u548C\u5B57\u6BB5\u540D\u4FDD\u6301\u539F\u6587\u3002",
11562
+ "\u5BF9\u8BDD\u548C\u89E3\u91CA\u4F7F\u7528\u4E2D\u6587\uFF1B\u4EA7\u7269\u4F7F\u7528\u7528\u6237\u660E\u786E\u6307\u5B9A\u7684\u8BED\u8A00\uFF0C\u5426\u5219\u4F7F\u7528\u6240\u5C5E agent \u660E\u786E\u6307\u5B9A\u7684\u8BED\u8A00\uFF0C\u5747\u672A\u6307\u5B9A\u65F6\u4F7F\u7528\u4E2D\u6587\u3002\u4EE3\u7801\u3001\u547D\u4EE4\u3001\u6587\u4EF6\u540D\u548C\u5B57\u6BB5\u540D\u4FDD\u6301\u539F\u6587\u3002",
11563
+ "Use English for conversation and explanations. For artifacts, use the language explicitly specified by the user, otherwise the responsible agent, otherwise English. Keep code, commands, filenames, and field names unchanged."
11564
+ ]);
11565
+ isSystemLanguage = (text5) => systemLanguages.has(text5);
11566
+ legacy = /* @__PURE__ */ new Map([
11567
+ ["\u8BF7\u7528\u4E2D\u6587\u56DE\u590D\u548C\u5199\u4F5C\u3002", "zh"],
11568
+ ["Reply and write in English.", "en"]
11569
+ ]);
11570
+ languageCode = (value) => value === "zh" || value === "en" ? value : null;
11571
+ legacyLanguage = (line) => typeof line === "string" ? legacy.get(line) ?? null : null;
11572
+ translating = () => runLanguage() === "zh";
11573
+ systemLanguage = (language) => language === "zh" ? SYSTEM_LANGUAGE_ZH : language === "en" ? SYSTEM_LANGUAGE_EN : "";
11507
11574
  }
11508
11575
  });
11509
11576
 
@@ -11537,6 +11604,7 @@ var init_providers = __esm({
11537
11604
  });
11538
11605
 
11539
11606
  // ../cli/src/lib/agent/run-settings.ts
11607
+ import path31 from "node:path";
11540
11608
  function isFilled(harness, { values, secretsSet }) {
11541
11609
  return (key) => {
11542
11610
  const setting = harness.settings.find((s) => s.key === key);
@@ -11606,15 +11674,39 @@ function effectiveValues(harness, held4) {
11606
11674
  }
11607
11675
  return out;
11608
11676
  }
11609
- function withSystemLanguage(harness, argv, env2, line) {
11610
- const text5 = systemLanguage(line);
11611
- const added = text5 && argv.length ? harness.systemPrompt?.(text5, argv) : void 0;
11612
- if (!added) return { argv, env: env2 };
11613
- return { argv: [argv[0], ...added.args ?? [], ...argv.slice(1)], env: { ...env2, ...added.env } };
11677
+ function languageEnv(given) {
11678
+ const env2 = { ...given };
11679
+ if (env2.DSH_SYSTEM_PROMPT) {
11680
+ const parts = env2.DSH_SYSTEM_PROMPT.split("\n\n");
11681
+ while (isSystemLanguage(parts.at(-1) ?? "")) parts.pop();
11682
+ env2.DSH_SYSTEM_PROMPT = parts.join("\n\n");
11683
+ }
11684
+ try {
11685
+ const config = JSON.parse(env2.OPENCODE_CONFIG_CONTENT ?? "{}");
11686
+ if (Array.isArray(config.instructions)) {
11687
+ config.instructions = config.instructions.filter((file) => typeof file !== "string" || path31.dirname(file) !== path31.join(machineHome(), "prompts") || !/^opencode-language(?:-[a-f0-9]{16})?\.md$/.test(path31.basename(file)));
11688
+ env2.OPENCODE_CONFIG_CONTENT = JSON.stringify(config);
11689
+ }
11690
+ } catch {
11691
+ }
11692
+ return env2;
11693
+ }
11694
+ function withSystemLanguage(harness, argv, env2, language) {
11695
+ env2 = languageEnv(env2);
11696
+ const text5 = systemLanguage(language);
11697
+ if (!text5) return { argv, env: env2 };
11698
+ const added = argv.length ? harness.systemPrompt?.(text5, argv, env2) : void 0;
11699
+ if (!added?.args?.length && !Object.keys(added?.env ?? {}).length) {
11700
+ const note = language === "zh" ? "\u8BED\u8A00\u89C4\u5219\u672A\u80FD\u66F4\u65B0\uFF1A\u8FDE\u63A5\u5668\u3001\u5DF2\u6709\u4F1A\u8BDD\u6216\u81EA\u8BBE\u7CFB\u7EDF\u63D0\u793A\u8BCD\u4E0D\u652F\u6301\u6CE8\u5165\u3002\u8BF7\u81EA\u884C\u53E6\u5F00\u652F\u6301\u7684\u4F1A\u8BDD\u4EE5\u91C7\u7528\u65B0\u89C4\u5219\u3002" : "Language rules could not be updated: the connector, existing session, or custom system prompt prevents injection. Open a supported new session to apply them.";
11701
+ return { argv, env: env2, note };
11702
+ }
11703
+ const at = /^node(?:\.exe)?$/.test(path31.basename(argv[0])) && argv[1] && !argv[1].startsWith("-") ? 2 : 1;
11704
+ return { argv: [...argv.slice(0, at), ...added?.args ?? [], ...argv.slice(at)], env: { ...env2, ...added?.env } };
11614
11705
  }
11615
11706
  var init_run_settings = __esm({
11616
11707
  "../cli/src/lib/agent/run-settings.ts"() {
11617
11708
  "use strict";
11709
+ init_home();
11618
11710
  init_argv();
11619
11711
  init_env();
11620
11712
  init_language();
@@ -11625,10 +11717,10 @@ var init_run_settings = __esm({
11625
11717
 
11626
11718
  // ../cli/src/lib/agent/log.ts
11627
11719
  import fs28 from "node:fs";
11628
- import path31 from "node:path";
11720
+ import path32 from "node:path";
11629
11721
  function pruneLogs() {
11630
11722
  try {
11631
- const runs = fs28.readdirSync(SESSIONS_DIR).filter((f) => f.endsWith(".log") && !f.endsWith(".watch.log")).map((f) => ({ id: f.slice(0, -".log".length), t: fs28.statSync(path31.join(SESSIONS_DIR, f)).mtimeMs })).sort((a, b) => b.t - a.t);
11723
+ const runs = fs28.readdirSync(SESSIONS_DIR).filter((f) => f.endsWith(".log") && !f.endsWith(".watch.log")).map((f) => ({ id: f.slice(0, -".log".length), t: fs28.statSync(path32.join(SESSIONS_DIR, f)).mtimeMs })).sort((a, b) => b.t - a.t);
11632
11724
  const kept2 = new Set(runs.slice(0, KEEP_LOGS).map((r) => r.id));
11633
11725
  const drop = new Set(runs.slice(KEEP_LOGS).map((r) => r.id));
11634
11726
  for (const f of fs28.readdirSync(SESSIONS_DIR)) {
@@ -11637,7 +11729,7 @@ function pruneLogs() {
11637
11729
  const stray = f.endsWith(".images") && !kept2.has(id) && !drop.has(id) && stale2(f);
11638
11730
  if (!stray && !drop.has(id)) continue;
11639
11731
  try {
11640
- fs28.rmSync(path31.join(SESSIONS_DIR, f), { recursive: true, force: true });
11732
+ fs28.rmSync(path32.join(SESSIONS_DIR, f), { recursive: true, force: true });
11641
11733
  } catch {
11642
11734
  }
11643
11735
  }
@@ -11646,7 +11738,7 @@ function pruneLogs() {
11646
11738
  }
11647
11739
  function stale2(name) {
11648
11740
  try {
11649
- return Date.now() - fs28.statSync(path31.join(SESSIONS_DIR, name)).mtimeMs > STRAY_MS;
11741
+ return Date.now() - fs28.statSync(path32.join(SESSIONS_DIR, name)).mtimeMs > STRAY_MS;
11650
11742
  } catch {
11651
11743
  return false;
11652
11744
  }
@@ -11783,9 +11875,9 @@ ${DURATION_MARKER} ${Math.max(0, ms)}
11783
11875
 
11784
11876
  // ../cli/src/lib/agent/store.ts
11785
11877
  import fs29 from "node:fs";
11786
- import path32 from "node:path";
11878
+ import path33 from "node:path";
11787
11879
  function logPathIn(held4, sessionId) {
11788
- return typeof held4 === "string" && held4.startsWith(SESSIONS_DIR + path32.sep) ? held4 : logPathOf(sessionId);
11880
+ return typeof held4 === "string" && held4.startsWith(SESSIONS_DIR + path33.sep) ? held4 : logPathOf(sessionId);
11789
11881
  }
11790
11882
  function parseStore() {
11791
11883
  let data;
@@ -12155,7 +12247,7 @@ function writeStore(store) {
12155
12247
  marks: store.marks,
12156
12248
  ...store.refined ? { refined: store.refined } : {}
12157
12249
  };
12158
- fs29.mkdirSync(path32.dirname(SESSIONS), { recursive: true });
12250
+ fs29.mkdirSync(path33.dirname(SESSIONS), { recursive: true });
12159
12251
  const tmp = `${SESSIONS}.tmp`;
12160
12252
  fs29.writeFileSync(tmp, JSON.stringify(kept2, null, 2) + "\n");
12161
12253
  fs29.renameSync(tmp, SESSIONS);
@@ -12221,8 +12313,8 @@ var init_store = __esm({
12221
12313
  init_log();
12222
12314
  KEEP_RUNS = 100;
12223
12315
  KEEP_DELIVERIES = 30;
12224
- logPathOf = (sessionId) => path32.join(SESSIONS_DIR, `${sessionId}.log`);
12225
- eventsPathOf = (sessionId) => path32.join(SESSIONS_DIR, `${sessionId}.events.jsonl`);
12316
+ logPathOf = (sessionId) => path33.join(SESSIONS_DIR, `${sessionId}.log`);
12317
+ eventsPathOf = (sessionId) => path33.join(SESSIONS_DIR, `${sessionId}.events.jsonl`);
12226
12318
  WAS_CLARIFY = /* @__PURE__ */ new Set(["auto-refine", "refine", "raise-questions"]);
12227
12319
  RETIRED_ACTIONS = /* @__PURE__ */ new Set([
12228
12320
  "channel",
@@ -12442,7 +12534,7 @@ var init_spec_agent_names = __esm({
12442
12534
  const canonical = canonicalSpecAgent(name);
12443
12535
  return [
12444
12536
  canonical,
12445
- ...Object.entries(LEGACY_SPEC_AGENT_NAMES).filter(([, current]) => current === canonical).map(([legacy]) => legacy)
12537
+ ...Object.entries(LEGACY_SPEC_AGENT_NAMES).filter(([, current]) => current === canonical).map(([legacy2]) => legacy2)
12446
12538
  ];
12447
12539
  };
12448
12540
  }
@@ -12470,7 +12562,7 @@ var init_types3 = __esm({
12470
12562
 
12471
12563
  // ../cli/src/lib/agent/settings.ts
12472
12564
  import fs30 from "node:fs";
12473
- import path33 from "node:path";
12565
+ import path34 from "node:path";
12474
12566
  function configBlock(value) {
12475
12567
  if (!value || typeof value !== "object" || Array.isArray(value)) return {};
12476
12568
  return value;
@@ -12495,7 +12587,7 @@ function adoptProjectNames(cfg) {
12495
12587
  function adoptLegacyConfig() {
12496
12588
  if (!LEGACY_UI_CONFIG || !fs30.existsSync(LEGACY_UI_CONFIG)) return;
12497
12589
  if (!fs30.existsSync(UI_CONFIG)) {
12498
- fs30.mkdirSync(path33.dirname(UI_CONFIG), { recursive: true });
12590
+ fs30.mkdirSync(path34.dirname(UI_CONFIG), { recursive: true });
12499
12591
  fs30.copyFileSync(LEGACY_UI_CONFIG, UI_CONFIG);
12500
12592
  }
12501
12593
  fs30.rmSync(LEGACY_UI_CONFIG, { force: true });
@@ -12588,7 +12680,7 @@ function writeConfig(change2) {
12588
12680
  }
12589
12681
  change2(cfg);
12590
12682
  try {
12591
- fs30.mkdirSync(path33.dirname(UI_CONFIG), { recursive: true });
12683
+ fs30.mkdirSync(path34.dirname(UI_CONFIG), { recursive: true });
12592
12684
  fs30.writeFileSync(UI_CONFIG, JSON.stringify(cfg, null, 2) + "\n");
12593
12685
  forgetFileMemos();
12594
12686
  return { ok: true };
@@ -12630,7 +12722,7 @@ function readEnvFile() {
12630
12722
  function ensureIgnored() {
12631
12723
  try {
12632
12724
  if (!fs30.existsSync(KANBAN_GITIGNORE)) {
12633
- fs30.mkdirSync(path33.dirname(KANBAN_GITIGNORE), { recursive: true });
12725
+ fs30.mkdirSync(path34.dirname(KANBAN_GITIGNORE), { recursive: true });
12634
12726
  fs30.writeFileSync(KANBAN_GITIGNORE, IGNORE_BLOCK);
12635
12727
  return { ok: true };
12636
12728
  }
@@ -13215,12 +13307,12 @@ function openPlan(plan) {
13215
13307
  const { harness } = resolved2;
13216
13308
  const start = startableBinary(plan.argv[0] ?? "", harness, pinnedBy(resolved2.runtime.settings, harness));
13217
13309
  const started = [start.binary, ...plan.argv.slice(1)];
13218
- const { argv, env: planned } = withSystemLanguage(harness, started, runEnv2(resolved2, plan.cwd ?? REPO_ROOT), languageNote());
13310
+ const { argv, env: planned, note } = withSystemLanguage(harness, started, runEnv2(resolved2, plan.cwd ?? REPO_ROOT), runLanguage());
13219
13311
  const env2 = copyEnv(start.binary, planned);
13220
13312
  return {
13221
13313
  ...plan,
13222
13314
  argv,
13223
- ...start.note ? { startNote: start.note } : {},
13315
+ ...[start.note, note].some(Boolean) ? { startNote: [start.note, note].filter(Boolean).join("\n") } : {},
13224
13316
  env: env2,
13225
13317
  // The folder and the binary: what a renderer needs to go looking for what the stream
13226
13318
  // left out (agent/harnesses/types.ts). argv's first word is the command's own binary,
@@ -13442,7 +13534,7 @@ var init_slug = __esm({
13442
13534
 
13443
13535
  // ../cli/src/lib/validate.ts
13444
13536
  import fs32 from "node:fs";
13445
- import path34 from "node:path";
13537
+ import path35 from "node:path";
13446
13538
  function moduleNames() {
13447
13539
  if (!fs32.existsSync(MODULES_MD)) return null;
13448
13540
  return moduleNamesFrom(fs32.readFileSync(MODULES_MD, "utf8"));
@@ -13479,7 +13571,7 @@ function dependencyCycle(id, dep) {
13479
13571
  seen.add(at);
13480
13572
  const found2 = locate(at);
13481
13573
  if (!found2) return null;
13482
- const file = found2.kind === "group" ? path34.join(found2.target, "root.md") : found2.target;
13574
+ const file = found2.kind === "group" ? path35.join(found2.target, "root.md") : found2.target;
13483
13575
  const nexts = parseFrontmatter(fs32.readFileSync(file, "utf8")).meta?.blocked_by ?? [];
13484
13576
  if (found2.kind === "group") nexts.push(...groupMembers(found2.target));
13485
13577
  for (const next of nexts) {
@@ -13492,7 +13584,7 @@ function dependencyCycle(id, dep) {
13492
13584
  return rest && [id, ...rest];
13493
13585
  }
13494
13586
  function groupMembers(dir) {
13495
- return walkMd(dir).filter((f) => f !== path34.join(dir, "root.md")).map((f) => idPrefix(path34.basename(path34.basename(f) === "root.md" ? path34.dirname(f) : f))).filter((n) => n !== null);
13587
+ return walkMd(dir).filter((f) => f !== path35.join(dir, "root.md")).map((f) => idPrefix(path35.basename(path35.basename(f) === "root.md" ? path35.dirname(f) : f))).filter((n) => n !== null);
13496
13588
  }
13497
13589
  var LEVELS;
13498
13590
  var init_validate = __esm({
@@ -13529,7 +13621,7 @@ var init_bundled2 = __esm({
13529
13621
 
13530
13622
  // ../cli/src/lib/memory.ts
13531
13623
  import fs33 from "node:fs";
13532
- import path35 from "node:path";
13624
+ import path36 from "node:path";
13533
13625
  function scaffoldProjectMemory() {
13534
13626
  const board2 = scaffoldMemoryDir(MEMORY, setOf(BOARD_MEMORY_FILES));
13535
13627
  const planner = scaffoldMemoryDir(agentMemoryDir(PLANNER), setOf(PLANNER_MEMORY_FILES));
@@ -13540,7 +13632,7 @@ function scaffoldMemoryDir(dir, set) {
13540
13632
  fs33.mkdirSync(dir, { recursive: true });
13541
13633
  const made = [];
13542
13634
  for (const [name, body] of Object.entries(set)) {
13543
- const file = path35.join(dir, name);
13635
+ const file = path36.join(dir, name);
13544
13636
  if (!fs33.existsSync(file)) {
13545
13637
  fs33.writeFileSync(file, body);
13546
13638
  made.push(name);
@@ -13600,16 +13692,16 @@ ${carried}`].filter(Boolean).join("\n\n")}
13600
13692
  }
13601
13693
  }
13602
13694
  function adoptOneFileMemory(agent) {
13603
- const legacy = legacyAgentMemoryFile(agent);
13695
+ const legacy2 = legacyAgentMemoryFile(agent);
13604
13696
  try {
13605
- if (!fs33.existsSync(legacy)) return;
13606
- const carried = stripHeading(fs33.readFileSync(legacy, "utf8"));
13697
+ if (!fs33.existsSync(legacy2)) return;
13698
+ const carried = stripHeading(fs33.readFileSync(legacy2, "utf8"));
13607
13699
  const into = agentMemoryFile(agent, "redesign.md");
13608
13700
  const kept2 = fs33.existsSync(into) ? stripHeading(fs33.readFileSync(into, "utf8")) : "";
13609
13701
  writeLegacyAgentMemory(agent, "redesign.md", [kept2, carried].filter(Boolean).join("\n\n"));
13610
- fs33.rmSync(legacy, { force: true });
13702
+ fs33.rmSync(legacy2, { force: true });
13611
13703
  } catch {
13612
- warn2(`couldn't move ${legacy} into ${agentMemoryDir(agent)}/ \u2014 reading what is there`);
13704
+ warn2(`couldn't move ${legacy2} into ${agentMemoryDir(agent)}/ \u2014 reading what is there`);
13613
13705
  }
13614
13706
  }
13615
13707
  function plannerModules() {
@@ -13631,7 +13723,7 @@ function memoryTarget(name, module = "") {
13631
13723
  }
13632
13724
  function scaffoldFile(file, name) {
13633
13725
  if (fs33.existsSync(file) || !STARTERS[name]) return;
13634
- fs33.mkdirSync(path35.dirname(file), { recursive: true });
13726
+ fs33.mkdirSync(path36.dirname(file), { recursive: true });
13635
13727
  fs33.writeFileSync(file, STARTERS[name]);
13636
13728
  }
13637
13729
  function readTopics(file) {
@@ -13651,7 +13743,7 @@ function readTopics(file) {
13651
13743
  }
13652
13744
  function mergeInto(file, block) {
13653
13745
  const before = fs33.existsSync(file) ? fs33.readFileSync(file, "utf8").replace(/\s+$/, "") : "";
13654
- fs33.mkdirSync(path35.dirname(file), { recursive: true });
13746
+ fs33.mkdirSync(path36.dirname(file), { recursive: true });
13655
13747
  fs33.writeFileSync(file, `${before ? `${before}
13656
13748
 
13657
13749
  ` : ""}${block}
@@ -13661,7 +13753,7 @@ function liftMemoryFile(from, into, topic) {
13661
13753
  if (!fs33.existsSync(from)) return false;
13662
13754
  const body = entriesOf2(fs33.readFileSync(from, "utf8"));
13663
13755
  if (body && !topic && !fs33.existsSync(into)) {
13664
- fs33.mkdirSync(path35.dirname(into), { recursive: true });
13756
+ fs33.mkdirSync(path36.dirname(into), { recursive: true });
13665
13757
  fs33.renameSync(from, into);
13666
13758
  return true;
13667
13759
  }
@@ -13717,8 +13809,8 @@ function legacyModuleDirs() {
13717
13809
  }
13718
13810
  }
13719
13811
  function renameProductFile() {
13720
- const from = path35.join(MEMORY, "product.md");
13721
- const to = path35.join(MEMORY, "project.md");
13812
+ const from = path36.join(MEMORY, "product.md");
13813
+ const to = path36.join(MEMORY, "project.md");
13722
13814
  if (!fs33.existsSync(from)) return null;
13723
13815
  try {
13724
13816
  const was = fs33.readFileSync(from, "utf8").trim();
@@ -13735,22 +13827,22 @@ function renameProductFile() {
13735
13827
  }
13736
13828
  function migrateMemory() {
13737
13829
  renameProductFile();
13738
- const roots = PLANNER_MEMORY_FILES.filter((name) => fs33.existsSync(path35.join(MEMORY, name)));
13830
+ const roots = PLANNER_MEMORY_FILES.filter((name) => fs33.existsSync(path36.join(MEMORY, name)));
13739
13831
  const modules = legacyModuleDirs();
13740
13832
  const moved = [];
13741
13833
  try {
13742
13834
  if (roots.length || modules.length) {
13743
13835
  scaffoldProjectMemory();
13744
13836
  for (const name of PLANNER_MEMORY_FILES) {
13745
- const from = path35.join(MEMORY, name);
13837
+ const from = path36.join(MEMORY, name);
13746
13838
  if (liftMemoryFile(from, agentMemoryFile(PLANNER, name), "")) moved.push(rel(from));
13747
13839
  }
13748
13840
  for (const module of modules) {
13749
- const dir = path35.join(MEMORY, module);
13841
+ const dir = path36.join(MEMORY, module);
13750
13842
  for (const name of [...PLANNER_MEMORY_FILES, "readme.md"]) {
13751
- const from = path35.join(dir, name);
13843
+ const from = path36.join(dir, name);
13752
13844
  const readme = name === "readme.md";
13753
- const into = readme ? path35.join(MEMORY, name) : isModule(module) ? moduleMemoryFile(module, name) : agentMemoryFile(PLANNER, name);
13845
+ const into = readme ? path36.join(MEMORY, name) : isModule(module) ? moduleMemoryFile(module, name) : agentMemoryFile(PLANNER, name);
13754
13846
  if (liftMemoryFile(from, into, readme || !isModule(module) ? module : "")) moved.push(rel(from));
13755
13847
  }
13756
13848
  dropIfEmpty(dir);
@@ -13767,14 +13859,14 @@ function mergeDismissed() {
13767
13859
  const root = agentMemoryDir(PLANNER);
13768
13860
  let dirs;
13769
13861
  try {
13770
- dirs = [root, ...fs33.readdirSync(root, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => path35.join(root, e.name))];
13862
+ dirs = [root, ...fs33.readdirSync(root, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => path36.join(root, e.name))];
13771
13863
  } catch {
13772
13864
  return [];
13773
13865
  }
13774
13866
  return dirs.flatMap((dir) => {
13775
- const from = path35.join(dir, "dismissed.md");
13867
+ const from = path36.join(dir, "dismissed.md");
13776
13868
  if (!fs33.existsSync(from)) return [];
13777
- const into = path35.join(dir, "rejected.md");
13869
+ const into = path36.join(dir, "rejected.md");
13778
13870
  scaffoldFile(into, "rejected.md");
13779
13871
  return liftMemoryFile(from, into, "") ? [rel(from)] : [];
13780
13872
  });
@@ -13789,7 +13881,7 @@ function goalWords(text5) {
13789
13881
  return text5.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, "").split(/\r?\n\s*\r?\n/).filter((p) => !SEEDED_GOAL_PARAGRAPHS.has(p.trim().replace(/\s+/g, " "))).join("\n\n").trim();
13790
13882
  }
13791
13883
  function retireGoal() {
13792
- const goal = path35.join(MEMORY, "goal.md");
13884
+ const goal = path36.join(MEMORY, "goal.md");
13793
13885
  if (!fs33.existsSync(goal)) return null;
13794
13886
  const words = goalWords(fs33.readFileSync(goal, "utf8"));
13795
13887
  if (words) {
@@ -13816,7 +13908,7 @@ var init_memory = __esm({
13816
13908
  init_spec_agent_names();
13817
13909
  init_validate();
13818
13910
  init_bundled2();
13819
- isGlobalMemory = (name) => name in BUNDLED_MEMORY_FILES || fs33.existsSync(path35.join(MEMORY, name, "MEMORY.md"));
13911
+ isGlobalMemory = (name) => name in BUNDLED_MEMORY_FILES || fs33.existsSync(path36.join(MEMORY, name, "MEMORY.md"));
13820
13912
  PLANNER = "planner";
13821
13913
  PLANNER_MEMORY_FILES = ["decisions.md", "rejected.md", "redesign.md"];
13822
13914
  BOARD_MEMORY_FILES = ["readme.md", "project.md"];
@@ -13855,8 +13947,8 @@ edits here do not last.
13855
13947
  setOf = (names4) => Object.fromEntries(names4.map((name) => [name, STARTERS[name]]));
13856
13948
  RESERVED_MEMORY_DIR = "agents";
13857
13949
  KEPT_MEMORY_FOLDERS = { "hyperframes-editor": "video-assets" };
13858
- agentMemoryDir = (agent) => path35.join(AGENT_MEMORY, KEPT_MEMORY_FOLDERS[agent] ?? agent);
13859
- agentMemoryFile = (agent, name) => path35.join(agentMemoryDir(agent), name);
13950
+ agentMemoryDir = (agent) => path36.join(AGENT_MEMORY, KEPT_MEMORY_FOLDERS[agent] ?? agent);
13951
+ agentMemoryFile = (agent, name) => path36.join(agentMemoryDir(agent), name);
13860
13952
  writtenMemoryNames = (agent) => {
13861
13953
  try {
13862
13954
  return fs33.readdirSync(agentMemoryDir(agent), { withFileTypes: true }).filter((e) => e.isFile() && e.name.endsWith(".md")).map((e) => e.name).sort();
@@ -13866,7 +13958,7 @@ edits here do not last.
13866
13958
  };
13867
13959
  memoryNamesOf = (agent) => agent === PLANNER ? PLANNER_MEMORY_FILES : writtenMemoryNames(agent);
13868
13960
  agentMemoryFiles = (agent) => memoryNamesOf(agent).map((name) => agentMemoryFile(agent, name));
13869
- legacyAgentMemoryFile = (agent) => path35.join(AGENT_MEMORY, `${agent}.md`);
13961
+ legacyAgentMemoryFile = (agent) => path36.join(AGENT_MEMORY, `${agent}.md`);
13870
13962
  HEADINGS = {
13871
13963
  "redesign.md": (agent) => `# What \`${agent}\` was corrected on`,
13872
13964
  "decisions.md": (agent) => `# What the user chose for \`${agent}\``
@@ -13881,7 +13973,7 @@ edits here do not last.
13881
13973
  moveMemory = (from, into) => {
13882
13974
  try {
13883
13975
  if (!fs33.existsSync(from) || fs33.existsSync(into)) return;
13884
- fs33.mkdirSync(path35.dirname(into), { recursive: true });
13976
+ fs33.mkdirSync(path36.dirname(into), { recursive: true });
13885
13977
  fs33.renameSync(from, into);
13886
13978
  } catch {
13887
13979
  warn2(`couldn't move ${from} to ${into} \u2014 reading what is there`);
@@ -13889,12 +13981,12 @@ edits here do not last.
13889
13981
  };
13890
13982
  RESERVED_MODULE_NAMES = PLANNER_MEMORY_FILES.map((name) => name.replace(/\.md$/, ""));
13891
13983
  isModule = (name) => !!name && !RESERVED_MODULE_NAMES.includes(name);
13892
- moduleMemoryFile = (module, name) => path35.join(agentMemoryDir(PLANNER), module, name);
13984
+ moduleMemoryFile = (module, name) => path36.join(agentMemoryDir(PLANNER), module, name);
13893
13985
  plannerCopies = (name) => [
13894
13986
  agentMemoryFile(PLANNER, name),
13895
13987
  ...plannerModules().map((m) => moduleMemoryFile(m, name)).filter((file) => fs33.existsSync(file))
13896
13988
  ];
13897
- memoryFile = (name, module = "") => BOARD_MEMORY_FILES.includes(name) ? path35.join(MEMORY, name) : isModule(module) ? moduleMemoryFile(module, name) : agentMemoryFile(PLANNER, name);
13989
+ memoryFile = (name, module = "") => BOARD_MEMORY_FILES.includes(name) ? path36.join(MEMORY, name) : isModule(module) ? moduleMemoryFile(module, name) : agentMemoryFile(PLANNER, name);
13898
13990
  ENTRY_RE = /^\s*([-*]|#{2,6})\s/;
13899
13991
  entriesOf2 = (text5) => {
13900
13992
  const lines = text5.replace(/^---\n[\s\S]*?\n---\n/, "").split("\n");
@@ -14057,7 +14149,7 @@ var init_WORKFLOW7 = __esm({
14057
14149
 
14058
14150
  // ../cli/src/lib/agent/workflow-file.ts
14059
14151
  import fs34 from "node:fs";
14060
- import path36 from "node:path";
14152
+ import path37 from "node:path";
14061
14153
  function agentLines(agents, indent2) {
14062
14154
  return agents.flatMap(
14063
14155
  (h) => !h.extra && !h.off ? [`${indent2}- ${scalar(h.agent)}`] : [
@@ -14116,12 +14208,12 @@ function boardWorkflowDefs() {
14116
14208
  }
14117
14209
  function writeWorkflowDef(def) {
14118
14210
  const file = fileOf(def.id);
14119
- fs34.mkdirSync(path36.dirname(file), { recursive: true });
14211
+ fs34.mkdirSync(path37.dirname(file), { recursive: true });
14120
14212
  fs34.writeFileSync(file, workflowFileText(def));
14121
14213
  forgetFileMemos();
14122
14214
  }
14123
14215
  function removeWorkflowDef(id) {
14124
- fs34.rmSync(path36.join(WORKFLOWS, id), { recursive: true, force: true });
14216
+ fs34.rmSync(path37.join(WORKFLOWS, id), { recursive: true, force: true });
14125
14217
  forgetFileMemos();
14126
14218
  }
14127
14219
  var BUNDLED, WORKFLOW_FILE, scalar, plain, flowList, BUILTIN_DEFS, fileOf;
@@ -14158,13 +14250,13 @@ var init_workflow_file = __esm({
14158
14250
  if ("problem" in read4) throw new Error(read4.problem);
14159
14251
  return read4.def;
14160
14252
  });
14161
- fileOf = (id) => path36.join(WORKFLOWS, id, WORKFLOW_FILE);
14253
+ fileOf = (id) => path37.join(WORKFLOWS, id, WORKFLOW_FILE);
14162
14254
  }
14163
14255
  });
14164
14256
 
14165
14257
  // ../cli/src/lib/agent/workflows.ts
14166
14258
  import fs35 from "node:fs";
14167
- import path37 from "node:path";
14259
+ import path38 from "node:path";
14168
14260
  function workflowPurpose(flow) {
14169
14261
  if (flow.builtIn) return builtinDescription(flow.id) || flow.name;
14170
14262
  return boardWorkflowDefs().defs.find((d) => d.id === flow.id)?.description || flow.name;
@@ -14579,7 +14671,7 @@ function cardMeta(id) {
14579
14671
  try {
14580
14672
  const found2 = locate(id) ?? locateArchived(id);
14581
14673
  if (!found2) return null;
14582
- const file = found2.kind === "group" ? path37.join(found2.target, "root.md") : found2.target;
14674
+ const file = found2.kind === "group" ? path38.join(found2.target, "root.md") : found2.target;
14583
14675
  return parseFrontmatter(fs35.readFileSync(file, "utf8")).meta;
14584
14676
  } catch {
14585
14677
  return null;
@@ -14678,7 +14770,7 @@ var init_workflows = __esm({
14678
14770
  previewAgents = (flow) => previewMembers(flow).filter((h) => !h.off);
14679
14771
  landAgents = (flow) => flow.land.filter((h) => !h.off);
14680
14772
  archivesOnApproval = (flow) => !!flow && !landAgents(flow).length;
14681
- workflowRuleFile = (id) => path37.join(RULES, "workflows", `${id}.md`);
14773
+ workflowRuleFile = (id) => path38.join(RULES, "workflows", `${id}.md`);
14682
14774
  save2 = (change2) => writeConfig((cfg) => {
14683
14775
  const block = configBlock(cfg.workflows);
14684
14776
  change2(block);
@@ -14709,7 +14801,7 @@ var init_workflows = __esm({
14709
14801
  });
14710
14802
 
14711
14803
  // ../cli/src/lib/agents/index.ts
14712
- import path38 from "node:path";
14804
+ import path39 from "node:path";
14713
14805
  function agentLines2(agent, language = readLanguage()) {
14714
14806
  const said3 = agent.i18n[language];
14715
14807
  return {
@@ -14756,7 +14848,7 @@ function agentFilesBlock(agent) {
14756
14848
  "Read one only when the work calls for it:",
14757
14849
  "",
14758
14850
  ...agent.files.map(
14759
- (f) => dir ? `- \`${rel(path38.join(dir, f))}\`` : `- \`${f}\` \u2014 \`${rawMove(`agent-file ${agent.name} ${f}`)}\``
14851
+ (f) => dir ? `- \`${rel(path39.join(dir, f))}\`` : `- \`${f}\` \u2014 \`${rawMove(`agent-file ${agent.name} ${f}`)}\``
14760
14852
  )
14761
14853
  ].join("\n");
14762
14854
  }
@@ -14985,7 +15077,7 @@ var init_stages = __esm({
14985
15077
  });
14986
15078
 
14987
15079
  // ../cli/src/lib/agent/roles.ts
14988
- import path39 from "node:path";
15080
+ import path40 from "node:path";
14989
15081
  function landRole(name) {
14990
15082
  const agent = specAgentCatalog().agents.find((a) => a.name === name);
14991
15083
  return agent ? { name, gloss: agent.description, memory: [] } : void 0;
@@ -15128,8 +15220,8 @@ var init_roles = __esm({
15128
15220
  hiddenRole = (name) => roleNamed(name)?.hidden === true;
15129
15221
  stageContractProblems = () => contractProblems([...agentNames(), ...roles().filter((role) => role.hidden).map((role) => role.name)]);
15130
15222
  memoryOf = (role) => role.memory.flatMap((file) => {
15131
- const at = path39.join(KANBAN, file);
15132
- return path39.dirname(at) === agentMemoryDir(PLANNER) ? plannerCopies(path39.basename(at)) : [at];
15223
+ const at = path40.join(KANBAN, file);
15224
+ return path40.dirname(at) === agentMemoryDir(PLANNER) ? plannerCopies(path40.basename(at)) : [at];
15133
15225
  }).map(rel);
15134
15226
  ownMemoryOf = (name, memory) => memoryNamesOf(name).filter((file) => memory.includes(rel(agentMemoryFile(name, file))));
15135
15227
  agentNames = () => agentRoster().map((entry) => entry.name);
@@ -15138,7 +15230,7 @@ var init_roles = __esm({
15138
15230
 
15139
15231
  // ../cli/src/lib/agents/roster.ts
15140
15232
  import fs36 from "node:fs";
15141
- import path40 from "node:path";
15233
+ import path41 from "node:path";
15142
15234
  function customAgentsOn() {
15143
15235
  const running2 = /* @__PURE__ */ new Set();
15144
15236
  for (const flow of workflows()) {
@@ -15151,26 +15243,26 @@ function copyAgent(from, taken = /* @__PURE__ */ new Set()) {
15151
15243
  const catalog = specAgentCatalog().agents;
15152
15244
  const agent = catalog.find((a) => a.name === from);
15153
15245
  if (!agent) return { ok: false, error: `"${from}" is not an agent on this board.` };
15154
- const free = (name2) => !taken.has(name2) && !ROLE_NAMES.includes(name2) && !catalog.some((a) => a.name === name2) && ![AGENTS, LEGACY_AGENTS].some((root) => fs36.existsSync(path40.join(root, name2)));
15246
+ const free = (name2) => !taken.has(name2) && !ROLE_NAMES.includes(name2) && !catalog.some((a) => a.name === name2) && ![AGENTS, LEGACY_AGENTS].some((root) => fs36.existsSync(path41.join(root, name2)));
15155
15247
  const base = from.replace(/-\d+$/, "");
15156
15248
  let name = "";
15157
15249
  for (let n = 2; !name; n++) if (free(`${base}-${n}`)) name = `${base}-${n}`;
15158
15250
  taken.add(name);
15159
- const dir = path40.join(AGENTS, name);
15251
+ const dir = path41.join(AGENTS, name);
15160
15252
  try {
15161
15253
  if (agent.dir) fs36.cpSync(agent.dir, dir, { recursive: true });
15162
15254
  else {
15163
15255
  for (const [key, text5] of Object.entries(BUNDLED_AGENT_FILES)) {
15164
15256
  if (!key.startsWith(`${from}/`)) continue;
15165
- const file2 = path40.join(dir, key.slice(from.length + 1));
15166
- fs36.mkdirSync(path40.dirname(file2), { recursive: true });
15257
+ const file2 = path41.join(dir, key.slice(from.length + 1));
15258
+ fs36.mkdirSync(path41.dirname(file2), { recursive: true });
15167
15259
  fs36.writeFileSync(file2, text5);
15168
15260
  }
15169
15261
  }
15170
- const file = path40.join(dir, agent.dir ? path40.basename(agent.from) : AGENT_FILE2);
15262
+ const file = path41.join(dir, agent.dir ? path41.basename(agent.from) : AGENT_FILE2);
15171
15263
  fs36.writeFileSync(file, fs36.readFileSync(file, "utf8").replace(/^name:.*$/m, `name: ${name}`));
15172
- const rule = path40.join(RULES, `${from}.md`);
15173
- if (fs36.existsSync(rule)) fs36.copyFileSync(rule, path40.join(RULES, `${name}.md`));
15264
+ const rule = path41.join(RULES, `${from}.md`);
15265
+ if (fs36.existsSync(rule)) fs36.copyFileSync(rule, path41.join(RULES, `${name}.md`));
15174
15266
  if (fs36.existsSync(agentMemoryDir(from))) fs36.cpSync(agentMemoryDir(from), agentMemoryDir(name), { recursive: true });
15175
15267
  } catch (err) {
15176
15268
  fs36.rmSync(dir, { recursive: true, force: true });
@@ -15189,7 +15281,7 @@ function copyAgent(from, taken = /* @__PURE__ */ new Set()) {
15189
15281
  function renameCopiedAfter(copied) {
15190
15282
  const rename = (list) => list.replace(/[\w-]+/g, (name) => copied.get(name) ?? name);
15191
15283
  for (const copy2 of copied.values()) {
15192
- const file = [AGENT_FILE2, "SKILL.md"].map((f) => path40.join(AGENTS, copy2, f)).find((f) => fs36.existsSync(f));
15284
+ const file = [AGENT_FILE2, "SKILL.md"].map((f) => path41.join(AGENTS, copy2, f)).find((f) => fs36.existsSync(f));
15193
15285
  if (!file) continue;
15194
15286
  const text5 = fs36.readFileSync(file, "utf8");
15195
15287
  const next = text5.replace(/^(\s+after:\s*)\[(.*)\]\s*$/m, (_, key, list) => `${key}[${rename(list)}]`).replace(/^(\s+after:\s*\n)((?:\s+-\s+.*\n)+)/m, (_, key, items) => key + rename(items));
@@ -15205,7 +15297,7 @@ function deleteAgent(name) {
15205
15297
  }
15206
15298
  const removed = [];
15207
15299
  try {
15208
- for (const file of [agent.dir, path40.join(RULES, `${name}.md`), agentMemoryDir(name), legacyAgentMemoryFile(name)]) {
15300
+ for (const file of [agent.dir, path41.join(RULES, `${name}.md`), agentMemoryDir(name), legacyAgentMemoryFile(name)]) {
15209
15301
  if (!fs36.existsSync(file)) continue;
15210
15302
  fs36.rmSync(file, { recursive: true, force: true });
15211
15303
  removed.push(rel(file));
@@ -15305,12 +15397,12 @@ var init_candidate = __esm({
15305
15397
  // ../cli/src/lib/agent/outputs.ts
15306
15398
  import fs37 from "node:fs";
15307
15399
  import os5 from "node:os";
15308
- import path41 from "node:path";
15400
+ import path42 from "node:path";
15309
15401
  function cardBody(cardId2) {
15310
15402
  const found2 = locate(cardId2) ?? locateArchived(cardId2);
15311
15403
  if (!found2) return "";
15312
15404
  try {
15313
- return fs37.readFileSync(found2.kind === "group" ? path41.join(found2.target, "root.md") : found2.target, "utf8");
15405
+ return fs37.readFileSync(found2.kind === "group" ? path42.join(found2.target, "root.md") : found2.target, "utf8");
15314
15406
  } catch {
15315
15407
  return "";
15316
15408
  }
@@ -15366,17 +15458,17 @@ var init_outputs = __esm({
15366
15458
  plannedTodos = (cardId2) => todos(cardId2).map((t) => t.text);
15367
15459
  looksLikePath = (token3) => !/\s/.test(token3) && !token3.includes("://") && !token3.startsWith("-") && token3.includes("/");
15368
15460
  resolveOutput = (file) => {
15369
- if (file.startsWith("~/")) return path41.join(os5.homedir(), file.slice(2));
15370
- if (path41.isAbsolute(file)) return file;
15371
- if (file.startsWith(".assets/")) return path41.join(ASSETS, file.slice(".assets/".length));
15372
- return path41.join(REPO_ROOT, file);
15461
+ if (file.startsWith("~/")) return path42.join(os5.homedir(), file.slice(2));
15462
+ if (path42.isAbsolute(file)) return file;
15463
+ if (file.startsWith(".assets/")) return path42.join(ASSETS, file.slice(".assets/".length));
15464
+ return path42.join(REPO_ROOT, file);
15373
15465
  };
15374
15466
  }
15375
15467
  });
15376
15468
 
15377
15469
  // ../cli/src/lib/agent/commit-mode.ts
15378
15470
  import fs38 from "node:fs";
15379
- import path42 from "node:path";
15471
+ import path43 from "node:path";
15380
15472
  function recordedIds() {
15381
15473
  try {
15382
15474
  return new Set(
@@ -15497,7 +15589,7 @@ function snapshotReviewed(delivery) {
15497
15589
  const diff = candidateDiff(candidateOf(delivery));
15498
15590
  if (diff) {
15499
15591
  try {
15500
- fs38.mkdirSync(path42.dirname(reviewedDiffPath(delivery.deliveryId)), { recursive: true });
15592
+ fs38.mkdirSync(path43.dirname(reviewedDiffPath(delivery.deliveryId)), { recursive: true });
15501
15593
  fs38.writeFileSync(reviewedDiffPath(delivery.deliveryId), diff);
15502
15594
  where = rel(reviewedDiffPath(delivery.deliveryId));
15503
15595
  } catch {
@@ -15537,7 +15629,7 @@ var init_commit_mode = __esm({
15537
15629
  });
15538
15630
  MAX_SUBJECT = 72;
15539
15631
  deliveryName = (delivery) => delivery.cardId === null ? `delivery ${delivery.deliveryId}` : `#${delivery.cardId}`;
15540
- reviewedDiffPath = (deliveryId) => path42.join(AKB_DIR, "reviewed", `${deliveryId}.diff`);
15632
+ reviewedDiffPath = (deliveryId) => path43.join(AKB_DIR, "reviewed", `${deliveryId}.diff`);
15541
15633
  }
15542
15634
  });
15543
15635
 
@@ -15572,7 +15664,7 @@ import { spawnSync as spawnSync4 } from "node:child_process";
15572
15664
  import { randomUUID as randomUUID5 } from "node:crypto";
15573
15665
  import fs39 from "node:fs";
15574
15666
  import os6 from "node:os";
15575
- import path43 from "node:path";
15667
+ import path44 from "node:path";
15576
15668
  function markedInProc(entry, strict = false) {
15577
15669
  const pids = [];
15578
15670
  for (const name of fs39.readdirSync("/proc")) {
@@ -15655,7 +15747,7 @@ function stopWatchers(sessions, onlyPid, stillOwns = () => true, startedBefore)
15655
15747
  const match = /^"?[^\n]*?\bnode(?:\.exe)?"?\s+(?:"[^\n"]+\.mjs"|\S+\.mjs)\s+__watch\s+(\S+)(?:\s|$)/.exec(proc.command);
15656
15748
  const args2 = `${proc.command} `;
15657
15749
  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);
15750
+ const board2 = names4("--board", KANBAN) || KANBAN === path44.join(REPO_ROOT, "docs", "kanban") && names4("--dir", REPO_ROOT);
15659
15751
  if (!match || known2 && !known2.has(match[1]) || !board2 || !stillOwns()) continue;
15660
15752
  if (WINDOWS3) {
15661
15753
  if (killTreeOnWindows(proc.pid)) stopped2.push(proc.pid);
@@ -16002,7 +16094,7 @@ var init_runner = __esm({
16002
16094
 
16003
16095
  // ../cli/src/lib/agent/rules.ts
16004
16096
  import fs40 from "node:fs";
16005
- import path44 from "node:path";
16097
+ import path45 from "node:path";
16006
16098
  function ruleFile(agent) {
16007
16099
  if (!agent || hiddenRole(agent)) return "";
16008
16100
  try {
@@ -16133,7 +16225,7 @@ var init_rules2 = __esm({
16133
16225
  init_runner();
16134
16226
  init_workflows();
16135
16227
  init_types3();
16136
- rulePath = (agent) => path44.join(RULES, `${agent}.md`);
16228
+ rulePath = (agent) => path45.join(RULES, `${agent}.md`);
16137
16229
  notOnTheRoster = (name) => `"${name}" is not an agent on this board. It has: ${agentNames().join(", ")}.`;
16138
16230
  flowForRequest = (req) => req.refineRound !== void 0 && REFINE_ACTIONS.has(req.action) ? flowByCommand("refine") : flowByAction(req.action);
16139
16231
  ruleOwnerSays = (owner) => `\`${owner.name}\` agent carries one rule of its own${owner.role ? ", on every flow it runs" : ""}`;
@@ -16219,7 +16311,7 @@ var init_source = __esm({
16219
16311
 
16220
16312
  // ../cli/src/lib/card-sources.ts
16221
16313
  import fs41 from "node:fs";
16222
- import path45 from "node:path";
16314
+ import path46 from "node:path";
16223
16315
  function planById(id) {
16224
16316
  const found2 = readPlan(`plans/${id}-.md`);
16225
16317
  return found2?.text.trim() ? found2 : null;
@@ -16229,7 +16321,7 @@ function cardLink(id) {
16229
16321
  const found2 = open3 ?? locateArchived(id);
16230
16322
  if (!found2) return null;
16231
16323
  try {
16232
- const file = found2.kind === "group" ? path45.join(found2.target, "root.md") : found2.target;
16324
+ const file = found2.kind === "group" ? path46.join(found2.target, "root.md") : found2.target;
16233
16325
  const title = parseFrontmatter(fs41.readFileSync(file, "utf8")).meta?.title ?? "";
16234
16326
  return { kind: "card", ref: String(id), title, ...open3 ? {} : { archived: true } };
16235
16327
  } catch {
@@ -16255,7 +16347,7 @@ function link(ref, items) {
16255
16347
  if (ref.kind === "plan") return planLink(ref.id);
16256
16348
  if (ref.kind === "card") return cardLink(ref.id);
16257
16349
  if (ref.kind === "url") return urlLink(ref.url);
16258
- const item = items().find((signal2) => path45.basename(signal2.relPath) === ref.file);
16350
+ const item = items().find((signal2) => path46.basename(signal2.relPath) === ref.file);
16259
16351
  return item ? itemLink(item) : null;
16260
16352
  }
16261
16353
  function cardSources(meta, body) {
@@ -16297,11 +16389,11 @@ var init_card_sources = __esm({
16297
16389
 
16298
16390
  // ../cli/src/lib/view/read.ts
16299
16391
  import fs42 from "node:fs";
16300
- import path46 from "node:path";
16392
+ import path47 from "node:path";
16301
16393
  function readCard(file, relFromTodo) {
16302
- const id = idPrefix(path46.basename(relFromTodo));
16394
+ const id = idPrefix(path47.basename(relFromTodo));
16303
16395
  if (id === null) {
16304
- const parts = relFromTodo.split(path46.sep);
16396
+ const parts = relFromTodo.split(path47.sep);
16305
16397
  const folderId = idPrefix(parts[parts.length - 2] || "");
16306
16398
  if (folderId === null) return null;
16307
16399
  return buildCard(folderId, file, relFromTodo);
@@ -16312,7 +16404,7 @@ function buildCard(id, file, relFromTodo) {
16312
16404
  const text5 = fs42.readFileSync(file, "utf8");
16313
16405
  const { meta, body } = parseFrontmatter(text5);
16314
16406
  if (!meta) return null;
16315
- const relPath = relFromTodo.split(path46.sep).join("/");
16407
+ const relPath = relFromTodo.split(path47.sep).join("/");
16316
16408
  const flow = workflowFor(meta.workflow);
16317
16409
  const plan = !!meta.workflow && archivesOnApproval(flow);
16318
16410
  const stages = cardStages(meta, flow).length;
@@ -16342,10 +16434,10 @@ function buildCard(id, file, relFromTodo) {
16342
16434
  };
16343
16435
  }
16344
16436
  function readGroup2(folderName) {
16345
- const groupDir = path46.join(TODO, folderName);
16346
- const rootFile = path46.join(groupDir, "root.md");
16437
+ const groupDir = path47.join(TODO, folderName);
16438
+ const rootFile = path47.join(groupDir, "root.md");
16347
16439
  if (!fs42.existsSync(rootFile)) return null;
16348
- const root = readCard(rootFile, path46.join(folderName, "root.md"));
16440
+ const root = readCard(rootFile, path47.join(folderName, "root.md"));
16349
16441
  if (!root) return null;
16350
16442
  root.isGroup = true;
16351
16443
  const { total, resolved: resolved2 } = subtaskLines(root.body);
@@ -16353,11 +16445,11 @@ function readGroup2(folderName) {
16353
16445
  const subCards = [];
16354
16446
  const recurse = (dir, relDir) => {
16355
16447
  for (const entry of fs42.readdirSync(dir, { withFileTypes: true })) {
16356
- const childRel = path46.join(relDir, entry.name);
16448
+ const childRel = path47.join(relDir, entry.name);
16357
16449
  if (entry.isDirectory()) {
16358
- recurse(path46.join(dir, entry.name), childRel);
16450
+ recurse(path47.join(dir, entry.name), childRel);
16359
16451
  } else if (entry.name.endsWith(".md") && entry.name !== "README.md" && entry.name !== "root.md") {
16360
- const c = readCard(path46.join(dir, entry.name), childRel);
16452
+ const c = readCard(path47.join(dir, entry.name), childRel);
16361
16453
  if (c) {
16362
16454
  c.parent = { id: root.id, title: root.title };
16363
16455
  subCards.push(c);
@@ -16378,12 +16470,12 @@ function readGroup2(folderName) {
16378
16470
  return { root, subCards };
16379
16471
  }
16380
16472
  function standaloneCards(dirRel) {
16381
- const dir = dirRel ? path46.join(TODO, dirRel) : TODO;
16473
+ const dir = dirRel ? path47.join(TODO, dirRel) : TODO;
16382
16474
  if (!fs42.existsSync(dir)) return [];
16383
16475
  const cards = [];
16384
16476
  for (const entry of fs42.readdirSync(dir, { withFileTypes: true })) {
16385
16477
  if (entry.isFile() && entry.name.endsWith(".md") && entry.name !== "README.md") {
16386
- const c = readCard(path46.join(dir, entry.name), path46.join(dirRel, entry.name));
16478
+ const c = readCard(path47.join(dir, entry.name), path47.join(dirRel, entry.name));
16387
16479
  if (c) cards.push(c);
16388
16480
  }
16389
16481
  }
@@ -16427,7 +16519,7 @@ function findCard(id) {
16427
16519
  }
16428
16520
  function attachSources(card) {
16429
16521
  try {
16430
- const { meta, body } = parseFrontmatter(fs42.readFileSync(path46.join(TODO, card.relPath), "utf8"));
16522
+ const { meta, body } = parseFrontmatter(fs42.readFileSync(path47.join(TODO, card.relPath), "utf8"));
16431
16523
  const sources = meta ? cardSources(meta, body) : [];
16432
16524
  if (sources.length) card.sources = sources;
16433
16525
  } catch {
@@ -16616,7 +16708,7 @@ var init_review = __esm({
16616
16708
 
16617
16709
  // ../cli/src/lib/agent/deliveries.ts
16618
16710
  import fs43 from "node:fs";
16619
- import path47 from "node:path";
16711
+ import path48 from "node:path";
16620
16712
  function writeAudit(delivery, runs) {
16621
16713
  const known2 = new Map(readAudit(delivery.deliveryId)?.sessions.map((s) => [s.sessionId, s]) ?? []);
16622
16714
  const sessions = delivery.sessions.flatMap((id) => {
@@ -16690,7 +16782,7 @@ function allAudits() {
16690
16782
  const out = [];
16691
16783
  for (const name of names4) {
16692
16784
  if (!name.endsWith(".json")) continue;
16693
- const audit = wholeAudit(path47.join(DELIVERIES, name));
16785
+ const audit = wholeAudit(path48.join(DELIVERIES, name));
16694
16786
  if (audit) out.push(audit);
16695
16787
  }
16696
16788
  return out;
@@ -16801,11 +16893,11 @@ function resumeRefusal(delivery, store) {
16801
16893
  );
16802
16894
  }
16803
16895
  if (!worktreeExists(delivery.worktree)) {
16804
- const path87 = delivery.worktree ?? "";
16896
+ const path88 = delivery.worktree ?? "";
16805
16897
  return refusal(
16806
16898
  "deliveryWorktreeGone",
16807
- `delivery ${id}'s worktree ${path87} is gone, so there is nothing to carry on. ${rebuild}`,
16808
- { id, task, path: path87, command: discard }
16899
+ `delivery ${id}'s worktree ${path88} is gone, so there is nothing to carry on. ${rebuild}`,
16900
+ { id, task, path: path88, command: discard }
16809
16901
  );
16810
16902
  }
16811
16903
  if (!branchExists(delivery.branch)) {
@@ -16948,7 +17040,7 @@ function carryOnFrom(deliveryId) {
16948
17040
  function approvedRequirements(cardId2) {
16949
17041
  const found2 = locate(cardId2);
16950
17042
  if (!found2) return "";
16951
- const file = found2.kind === "group" ? path47.join(found2.target, "root.md") : found2.target;
17043
+ const file = found2.kind === "group" ? path48.join(found2.target, "root.md") : found2.target;
16952
17044
  let text5;
16953
17045
  try {
16954
17046
  text5 = fs43.readFileSync(file, "utf8");
@@ -16981,7 +17073,7 @@ function cardMeta2(cardId2) {
16981
17073
  try {
16982
17074
  const found2 = locate(cardId2);
16983
17075
  if (!found2) return null;
16984
- const file = found2.kind === "group" ? path47.join(found2.target, "root.md") : found2.target;
17076
+ const file = found2.kind === "group" ? path48.join(found2.target, "root.md") : found2.target;
16985
17077
  return parseFrontmatter(fs43.readFileSync(file, "utf8")).meta;
16986
17078
  } catch {
16987
17079
  return null;
@@ -17241,7 +17333,7 @@ var init_deliveries = __esm({
17241
17333
  init_store();
17242
17334
  init_types3();
17243
17335
  init_worktree();
17244
- auditPath = (deliveryId) => path47.join(DELIVERIES, `${deliveryId}.json`);
17336
+ auditPath = (deliveryId) => path48.join(DELIVERIES, `${deliveryId}.json`);
17245
17337
  activeAudits = () => allAudits().filter((a) => a.status === "active");
17246
17338
  auditRow = (audit) => readDeliveryRow({ ...audit, sessions: audit.sessions.map((s) => s.sessionId) });
17247
17339
  lastSessionRunning = (audit) => audit.sessions[audit.sessions.length - 1]?.status === "running";
@@ -17271,9 +17363,9 @@ var init_deliveries = __esm({
17271
17363
 
17272
17364
  // ../cli/src/lib/agent/landing-lock.ts
17273
17365
  import fs44 from "node:fs";
17274
- import path48 from "node:path";
17366
+ import path49 from "node:path";
17275
17367
  function lockLanding() {
17276
- const dir = path48.join(SESSIONS_DIR, ".landing.lock");
17368
+ const dir = path49.join(SESSIONS_DIR, ".landing.lock");
17277
17369
  try {
17278
17370
  const entries = fs44.readdirSync(dir);
17279
17371
  const token3 = entries.length === 1 ? entries[0] : "";
@@ -17291,7 +17383,7 @@ function lockLanding() {
17291
17383
  }
17292
17384
  function renewLandingLock() {
17293
17385
  const now = /* @__PURE__ */ new Date();
17294
- fs44.utimesSync(path48.join(SESSIONS_DIR, ".landing.lock"), now, now);
17386
+ fs44.utimesSync(path49.join(SESSIONS_DIR, ".landing.lock"), now, now);
17295
17387
  }
17296
17388
  var landingLimits;
17297
17389
  var init_landing_lock = __esm({
@@ -17369,7 +17461,7 @@ var init_retry = __esm({
17369
17461
 
17370
17462
  // ../cli/src/lib/agent/landing.ts
17371
17463
  import fs45 from "node:fs";
17372
- import path49 from "node:path";
17464
+ import path50 from "node:path";
17373
17465
  function waitingOut(delivery, now = Date.now()) {
17374
17466
  const landing = delivery.landing;
17375
17467
  if (!landing) return false;
@@ -17927,7 +18019,7 @@ var init_landing = __esm({
17927
18019
  try {
17928
18020
  return fs45.realpathSync(dir);
17929
18021
  } catch {
17930
- return path49.resolve(dir);
18022
+ return path50.resolve(dir);
17931
18023
  }
17932
18024
  };
17933
18025
  return real2(a) === real2(b);
@@ -17937,7 +18029,7 @@ var init_landing = __esm({
17937
18029
 
17938
18030
  // ../cli/src/lib/agent/pictures.ts
17939
18031
  import fs46 from "node:fs";
17940
- import path50 from "node:path";
18032
+ import path51 from "node:path";
17941
18033
  function claimRunPictures(box2, sessionId, names4 = []) {
17942
18034
  if (!box2 || !freeBox(box2) || names4.length === 0) return [];
17943
18035
  const from = pictureBox(box2);
@@ -17948,7 +18040,7 @@ function claimRunPictures(box2, sessionId, names4 = []) {
17948
18040
  } catch {
17949
18041
  return [];
17950
18042
  }
17951
- return names4.filter(pictureName).map((name) => path50.join(to, name)).filter((file) => fs46.existsSync(file));
18043
+ return names4.filter(pictureName).map((name) => path51.join(to, name)).filter((file) => fs46.existsSync(file));
17952
18044
  }
17953
18045
  function returnRunPictures(sessionId, box2) {
17954
18046
  if (!box2 || !boxId(box2)) return;
@@ -17964,7 +18056,7 @@ function moveRunPictures(from, to, files2 = []) {
17964
18056
  } catch {
17965
18057
  return files2;
17966
18058
  }
17967
- return files2.map((file) => path50.join(pictureBox(to), path50.basename(file)));
18059
+ return files2.map((file) => path51.join(pictureBox(to), path51.basename(file)));
17968
18060
  }
17969
18061
  var pictureName, boxId, freeBox, pictureBox;
17970
18062
  var init_pictures = __esm({
@@ -17973,14 +18065,14 @@ var init_pictures = __esm({
17973
18065
  init_paths();
17974
18066
  pictureName = (name) => /^[0-9a-f-]{36}\.[a-z0-9]{2,5}$/.test(name);
17975
18067
  boxId = (box2) => /^[0-9a-f-]{36}$/.test(box2);
17976
- freeBox = (box2) => boxId(box2) && !fs46.existsSync(path50.join(SESSIONS_DIR, `${box2}.log`));
17977
- pictureBox = (box2) => path50.join(SESSIONS_DIR, `${box2}.images`);
18068
+ freeBox = (box2) => boxId(box2) && !fs46.existsSync(path51.join(SESSIONS_DIR, `${box2}.log`));
18069
+ pictureBox = (box2) => path51.join(SESSIONS_DIR, `${box2}.images`);
17978
18070
  }
17979
18071
  });
17980
18072
 
17981
18073
  // ../cli/src/lib/memories.ts
17982
18074
  import fs47 from "node:fs";
17983
- import path51 from "node:path";
18075
+ import path52 from "node:path";
17984
18076
  function readMemory(text5, builtIn, folder) {
17985
18077
  const { meta, body } = splitFrontmatter(text5);
17986
18078
  if (meta === null) return null;
@@ -18000,7 +18092,7 @@ function readMemory(text5, builtIn, folder) {
18000
18092
  rules: body.trim(),
18001
18093
  text: text5,
18002
18094
  builtIn,
18003
- dir: path51.join(MEMORY, folder)
18095
+ dir: path52.join(MEMORY, folder)
18004
18096
  };
18005
18097
  }
18006
18098
  function globalMemories() {
@@ -18016,7 +18108,7 @@ function globalMemories() {
18016
18108
  }
18017
18109
  for (const name of folders2) {
18018
18110
  if (name in BUNDLED_MEMORY_FILES || name === RESERVED_MEMORY_DIR) continue;
18019
- const file = path51.join(MEMORY, name, GLOBAL_MEMORY_FILE);
18111
+ const file = path52.join(MEMORY, name, GLOBAL_MEMORY_FILE);
18020
18112
  if (!fs47.existsSync(file)) continue;
18021
18113
  const memory = readMemory(fs47.readFileSync(file, "utf8"), false, name);
18022
18114
  if (memory) out.push(memory);
@@ -18075,10 +18167,10 @@ var init_facts = __esm({
18075
18167
 
18076
18168
  // ../cli/src/lib/view/archive.ts
18077
18169
  import fs48 from "node:fs";
18078
- import path52 from "node:path";
18170
+ import path53 from "node:path";
18079
18171
  function archivedId(file) {
18080
- const own = idPrefix(path52.basename(file));
18081
- return own !== null ? own : idPrefix(path52.basename(path52.dirname(file)));
18172
+ const own = idPrefix(path53.basename(file));
18173
+ return own !== null ? own : idPrefix(path53.basename(path53.dirname(file)));
18082
18174
  }
18083
18175
  function readFile(file) {
18084
18176
  const id = archivedId(file);
@@ -18091,14 +18183,14 @@ function readFile(file) {
18091
18183
  title: meta.title,
18092
18184
  archived: meta.archived,
18093
18185
  rejected: meta.rejected,
18094
- relPath: rel(file).split(path52.sep).join("/")
18186
+ relPath: rel(file).split(path53.sep).join("/")
18095
18187
  },
18096
18188
  body: body.replace(/^\n+/, "").replace(/\s+$/, ""),
18097
18189
  meta
18098
18190
  };
18099
18191
  }
18100
18192
  function readArchive() {
18101
- const relPath = rel(ARCHIVE).split(path52.sep).join("/");
18193
+ const relPath = rel(ARCHIVE).split(path53.sep).join("/");
18102
18194
  if (!fs48.existsSync(ARCHIVE)) return { relPath, cards: [] };
18103
18195
  const cards = walkMd(ARCHIVE).map(readFile).filter((found2) => found2 !== null).map((found2) => found2.row);
18104
18196
  cards.sort((a, b) => b.id - a.id);
@@ -18114,7 +18206,7 @@ var init_archive = __esm({
18114
18206
  });
18115
18207
 
18116
18208
  // ../cli/src/lib/agent/prompts.ts
18117
- import path53 from "node:path";
18209
+ import path54 from "node:path";
18118
18210
  function resumePrompt(deliveryId, cardId2, action) {
18119
18211
  if (!deliveryId) return RESUME_PROMPT;
18120
18212
  if (action === "conflict") {
@@ -18158,7 +18250,7 @@ function buildAsk(rawReq, notes = []) {
18158
18250
  }
18159
18251
  function cardFileOf(id) {
18160
18252
  const found2 = id === void 0 ? null : locate(id);
18161
- return found2 ? found2.kind === "group" ? path53.join(found2.target, "root.md") : found2.target : "";
18253
+ return found2 ? found2.kind === "group" ? path54.join(found2.target, "root.md") : found2.target : "";
18162
18254
  }
18163
18255
  function realPaths(text5, req) {
18164
18256
  if (req.action !== "scheduled" && req.action !== "sub") return text5;
@@ -18251,7 +18343,7 @@ function frozenRules(req) {
18251
18343
  function archivedCardFile(id) {
18252
18344
  const found2 = locateArchived(id);
18253
18345
  if (!found2) return `${rel(ARCHIVE)}/`;
18254
- return rel(found2.kind === "group" ? path53.join(found2.target, "root.md") : found2.target);
18346
+ return rel(found2.kind === "group" ? path54.join(found2.target, "root.md") : found2.target);
18255
18347
  }
18256
18348
  function shippedLines(cardId2) {
18257
18349
  const delivery = endedDelivery(`${cardId2}`);
@@ -18422,7 +18514,7 @@ function actionPrompt(req, command, notes) {
18422
18514
  case "spec": {
18423
18515
  const agent = findSpecAgent(req.specAgent ?? "");
18424
18516
  const found2 = req.id === void 0 ? null : locate(req.id);
18425
- const cardFile3 = found2 ? rel(found2.kind === "group" ? path53.join(found2.target, "root.md") : found2.target) : `task #${req.id}`;
18517
+ const cardFile3 = found2 ? rel(found2.kind === "group" ? path54.join(found2.target, "root.md") : found2.target) : `task #${req.id}`;
18426
18518
  const own = agent ? specAgentInstructions(agent) : null;
18427
18519
  if (own) notes.push(...own.notes);
18428
18520
  const memory = agent ? agentMemoryBlock(agent) : "";
@@ -18609,7 +18701,7 @@ var init_prompts = __esm({
18609
18701
  // ../cli/src/lib/agent/sessions.ts
18610
18702
  import { randomUUID as randomUUID6 } from "node:crypto";
18611
18703
  import fs49 from "node:fs";
18612
- import path54 from "node:path";
18704
+ import path55 from "node:path";
18613
18705
  function reap(runs, reaped = [], restore = []) {
18614
18706
  let changed = false;
18615
18707
  const now = Date.now();
@@ -18780,7 +18872,7 @@ function leftBoardOnLanding(id) {
18780
18872
  function archivedRejected(id) {
18781
18873
  const filed = locateArchived(id);
18782
18874
  if (!filed) return null;
18783
- const file = filed.kind === "group" ? path54.join(filed.target, "root.md") : filed.target;
18875
+ const file = filed.kind === "group" ? path55.join(filed.target, "root.md") : filed.target;
18784
18876
  try {
18785
18877
  return !!parseFrontmatter(fs49.readFileSync(file, "utf8")).meta?.rejected;
18786
18878
  } catch {
@@ -19039,7 +19131,7 @@ function openRun(req, prompt, notes = [], sessionId = randomUUID6(), said3) {
19039
19131
  return { run: record, spec };
19040
19132
  }
19041
19133
  function planningSession(cardId2, cwd = REPO_ROOT) {
19042
- const here = (dir) => !dir || path54.resolve(dir) === cwd;
19134
+ const here = (dir) => !dir || path55.resolve(dir) === cwd;
19043
19135
  const ended = readRuns().filter((r) => r.status === "done").sort((a, b) => b.startedAt - a.startedAt);
19044
19136
  const of = (run3, fork) => {
19045
19137
  if (!here(run3.cwd)) return { none: "elsewhere" };
@@ -19108,7 +19200,9 @@ function requestOf(record) {
19108
19200
  pictures: record.pictures
19109
19201
  };
19110
19202
  }
19111
- async function openResume(id, requireSession = false) {
19203
+ async function openResume(id, options) {
19204
+ const build = typeof options === "object" ? options : void 0;
19205
+ const requireSession = options === true || !!build;
19112
19206
  const restore = [];
19113
19207
  const runs = withRuns((all2) => {
19114
19208
  reap(all2, [], restore);
@@ -19122,14 +19216,19 @@ async function openResume(id, requireSession = false) {
19122
19216
  if (prev.action === "sub") {
19123
19217
  return refusal("subRunResume", "a sub-run cannot be continued on its own \u2014 the run that started it starts it again");
19124
19218
  }
19125
- if (!canPickUp(prev)) return refusal("runNotResumable", "only a failed, interrupted or stopped run can be continued");
19219
+ const delivery = build ? findDelivery(build.deliveryId) : void 0;
19220
+ if (build && (prev.action !== "implement" || prev.deliveryId !== build.deliveryId || delivery?.status !== "active" || delivery.landing)) {
19221
+ return refusal("deliveryTakenOver", "\u539F\u6784\u5EFA\u4E0D\u5C5E\u4E8E\u5F53\u524D\u5F85\u7EED\u8DD1\u7684\u4EA4\u4ED8\uFF0C\u4E0D\u4F1A\u542F\u52A8\u65B0\u4F1A\u8BDD\u3002", { id: build.deliveryId });
19222
+ }
19223
+ const completedBuild = prev.status === "done" && prev.action === "implement" && delivery?.status === "active" && !delivery.landing && prev.deliveryId === delivery.deliveryId;
19224
+ if (!canPickUp(prev) && !completedBuild) return refusal("runNotResumable", "only a failed, interrupted or stopped run can be continued");
19126
19225
  if (prev.action === "create" && !prev.createdCardIds?.length) {
19127
19226
  const refused = await preflightCreation(prev);
19128
19227
  if (refused) return refused;
19129
19228
  }
19130
19229
  const retrying = lacksSession(prev);
19131
- if (requireSession && !resumeSessionId(prev)) {
19132
- return refusal("runNoSession", "the original conversation cannot be resumed; no new conversation was started");
19230
+ if (requireSession && (!resumeSessionId(prev) || !resumesUnder(prev.harness))) {
19231
+ return refusal("runNoSession", "\u539F\u6784\u5EFA\u6CA1\u6709\u53EF\u6062\u590D\u7684\u4F1A\u8BDD\u6807\u8BC6\uFF0C\u6216\u539F Agent \u4E0D\u652F\u6301\u7EED\u8DD1\uFF1B\u672C\u6B21\u672A\u542F\u52A8\u65B0\u4F1A\u8BDD\u3002");
19133
19232
  }
19134
19233
  const ask2 = retrying && prev.status !== "stopped" ? retryAsk(prev) : void 0;
19135
19234
  if (retrying && !ask2) return refusal("runNoSession", "that run never reported a session id to continue by");
@@ -19163,7 +19262,13 @@ async function openResume(id, requireSession = false) {
19163
19262
  const agent = prev.harness || "the agent that started it";
19164
19263
  return refusal("runForeign", `this version can't continue a conversation ${agent} opened`, { agent });
19165
19264
  }
19166
- return resumeHeld(prev, plan, sessionId, resuming, fresh);
19265
+ const opened = await resumeHeld(prev, plan, sessionId, resuming, fresh);
19266
+ if (!("error" in opened) && build) {
19267
+ opened.spec.notes = [...opened.spec.notes ?? [], "\u7EE7\u7EED\u539F\u6784\u5EFA\u4F1A\u8BDD\uFF1B\u82E5\u539F\u4F1A\u8BDD\u4E0D\u53EF\u7528\uFF0C\u672C\u6B21\u4E0D\u4F1A\u65B0\u5EFA\u4F1A\u8BDD\u3002"];
19268
+ if (build.notes?.trim()) opened.spec.prompt = [opened.spec.prompt || resumePrompt(prev.deliveryId, prev.cardId, prev.action), build.notes.trim()].join("\n\n");
19269
+ writeSpec(opened.spec);
19270
+ }
19271
+ return opened;
19167
19272
  }
19168
19273
  async function planRetry2(prev, ask2, sessionId, cwd) {
19169
19274
  const runtime = prev.runtime && runtimeById(prev.runtime) ? prev.runtime : void 0;
@@ -19531,7 +19636,7 @@ function dropSpec(sessionId) {
19531
19636
  }
19532
19637
  async function acquireIndexLock(giveUp) {
19533
19638
  const until = Date.now() + INDEX_WAIT_MS;
19534
- const holder = path54.join(INDEX_LOCK2, "holder");
19639
+ const holder = path55.join(INDEX_LOCK2, "holder");
19535
19640
  for (; ; ) {
19536
19641
  if (giveUp()) return null;
19537
19642
  try {
@@ -19541,7 +19646,7 @@ async function acquireIndexLock(giveUp) {
19541
19646
  } catch (err) {
19542
19647
  const code = err.code;
19543
19648
  if (code === "ENOENT") {
19544
- fs49.mkdirSync(path54.dirname(INDEX_LOCK2), { recursive: true });
19649
+ fs49.mkdirSync(path55.dirname(INDEX_LOCK2), { recursive: true });
19545
19650
  continue;
19546
19651
  }
19547
19652
  if (code !== "EEXIST") throw err;
@@ -19648,8 +19753,8 @@ var init_sessions = __esm({
19648
19753
  conflict: "implementing",
19649
19754
  hook: "implementing"
19650
19755
  };
19651
- specPathOf = (sessionId) => path54.join(SESSIONS_DIR, `${sessionId}.plan.json`);
19652
- asksPathOf = (sessionId) => path54.join(SESSIONS_DIR, `${sessionId}.asks.json`);
19756
+ specPathOf = (sessionId) => path55.join(SESSIONS_DIR, `${sessionId}.plan.json`);
19757
+ asksPathOf = (sessionId) => path55.join(SESSIONS_DIR, `${sessionId}.asks.json`);
19653
19758
  carriesSession = (r) => !!(r.resumedFrom || r.continues && !r.continues.fork);
19654
19759
  canPickUp = (r) => (r.status === "error" || r.status === "interrupted" || r.status === "stopped") && !(r.action === "create" && readRuns().some((next) => next.resumedFrom === r.sessionId));
19655
19760
  neverStarted = (r) => r.unspawned === true || !!r.errorWhy?.some((why) => why.kind === "notInstalled");
@@ -19784,7 +19889,7 @@ var init_questions = __esm({
19784
19889
 
19785
19890
  // ../cli/src/lib/agent/workflow-cards.ts
19786
19891
  import fs50 from "node:fs";
19787
- import path55 from "node:path";
19892
+ import path56 from "node:path";
19788
19893
  function cardsOnWorkflow(id) {
19789
19894
  const wanted = id || DEFAULT_WORKFLOW;
19790
19895
  const ids = [];
@@ -19795,8 +19900,8 @@ function cardsOnWorkflow(id) {
19795
19900
  return [];
19796
19901
  }
19797
19902
  for (const file of files2) {
19798
- const base = path55.basename(file);
19799
- const card = base === "root.md" ? idPrefix(path55.basename(path55.dirname(file))) : idPrefix(base);
19903
+ const base = path56.basename(file);
19904
+ const card = base === "root.md" ? idPrefix(path56.basename(path56.dirname(file))) : idPrefix(base);
19800
19905
  if (card === null) continue;
19801
19906
  let meta;
19802
19907
  try {
@@ -19859,7 +19964,7 @@ var init_workflow_cards = __esm({
19859
19964
 
19860
19965
  // ../cli/src/lib/readme.ts
19861
19966
  import fs51 from "node:fs";
19862
- import path56 from "node:path";
19967
+ import path57 from "node:path";
19863
19968
  function stripReadmeRefs(target) {
19864
19969
  if (!fs51.existsSync(README)) return [];
19865
19970
  const needle = target.kind === "group" ? `](${target.rel}/` : `](${target.rel})`;
@@ -19890,7 +19995,7 @@ function stripReadmeRefs(target) {
19890
19995
  }
19891
19996
  function addReadmeRef(id, title, relPath) {
19892
19997
  if (!fs51.existsSync(README)) return false;
19893
- const link2 = relPath.split(path56.sep).join("/");
19998
+ const link2 = relPath.split(path57.sep).join("/");
19894
19999
  const bullet = `- [#${id} ${title}](${link2})`;
19895
20000
  const heading = `## ${TASKS_HEADING}`;
19896
20001
  let lines = fs51.readFileSync(README, "utf8").split("\n");
@@ -19917,8 +20022,8 @@ function addReadmeRef(id, title, relPath) {
19917
20022
  }
19918
20023
  function repointReadmeLink(id, oldRel, newRel, title) {
19919
20024
  if (!fs51.existsSync(README)) return false;
19920
- const oldLink = oldRel.split(path56.sep).join("/");
19921
- const newLink = newRel.split(path56.sep).join("/");
20025
+ const oldLink = oldRel.split(path57.sep).join("/");
20026
+ const newLink = newRel.split(path57.sep).join("/");
19922
20027
  const linkRe = new RegExp(`\\[#${id}\\b[^\\]]*\\]\\(${escapeRegex(oldLink)}\\)`);
19923
20028
  const lines = fs51.readFileSync(README, "utf8").split("\n");
19924
20029
  let changed = false;
@@ -19948,13 +20053,13 @@ var init_readme = __esm({
19948
20053
 
19949
20054
  // ../cli/src/lib/reconcile.ts
19950
20055
  import fs52 from "node:fs";
19951
- import path57 from "node:path";
20056
+ import path58 from "node:path";
19952
20057
  function liveIds() {
19953
20058
  const ids = /* @__PURE__ */ new Set();
19954
20059
  for (const file of walkMd(TODO)) {
19955
- const base = path57.basename(file);
20060
+ const base = path58.basename(file);
19956
20061
  if (base === "README.md") continue;
19957
- const id = base === "root.md" ? idPrefix(path57.basename(path57.dirname(file))) : idPrefix(base);
20062
+ const id = base === "root.md" ? idPrefix(path58.basename(path58.dirname(file))) : idPrefix(base);
19958
20063
  if (id != null) ids.add(id);
19959
20064
  }
19960
20065
  return ids;
@@ -19962,16 +20067,16 @@ function liveIds() {
19962
20067
  function indexableCards() {
19963
20068
  const out = [];
19964
20069
  for (const dir of walkDirs(TODO)) {
19965
- const id = idPrefix(path57.basename(dir));
19966
- if (id != null && fs52.existsSync(path57.join(dir, "root.md"))) {
19967
- out.push({ id, rel: path57.join(path57.relative(TODO, dir), "root.md").split(path57.sep).join("/") });
20070
+ const id = idPrefix(path58.basename(dir));
20071
+ if (id != null && fs52.existsSync(path58.join(dir, "root.md"))) {
20072
+ out.push({ id, rel: path58.join(path58.relative(TODO, dir), "root.md").split(path58.sep).join("/") });
19968
20073
  }
19969
20074
  }
19970
20075
  for (const file of walkMd(TODO)) {
19971
- const base = path57.basename(file);
20076
+ const base = path58.basename(file);
19972
20077
  if (base === "README.md" || base === "root.md") continue;
19973
- const relTODO = path57.relative(TODO, file);
19974
- const segs = relTODO.split(path57.sep);
20078
+ const relTODO = path58.relative(TODO, file);
20079
+ const segs = relTODO.split(path58.sep);
19975
20080
  if (segs.length !== 1) continue;
19976
20081
  const id = idPrefix(base);
19977
20082
  if (id != null) out.push({ id, rel: segs.join("/") });
@@ -19989,13 +20094,13 @@ function readmeComplaints(fix) {
19989
20094
  const id = Number(m[1]);
19990
20095
  const linkPath = m[2];
19991
20096
  indexed.add(id);
19992
- if (fs52.existsSync(path57.join(TODO, linkPath))) continue;
20097
+ if (fs52.existsSync(path58.join(TODO, linkPath))) continue;
19993
20098
  const found2 = locate(id);
19994
20099
  if (!found2) {
19995
20100
  said3.push(`README links #${id} \u2192 ${linkPath}, but no card with that id exists (removed by hand?). Drop the entry or restore the file.`);
19996
20101
  continue;
19997
20102
  }
19998
- const want = (found2.kind === "group" ? path57.join(found2.rel, "root.md") : found2.rel).split(path57.sep).join("/");
20103
+ const want = (found2.kind === "group" ? path58.join(found2.rel, "root.md") : found2.rel).split(path58.sep).join("/");
19999
20104
  if (want === linkPath) continue;
20000
20105
  if (!fix) {
20001
20106
  said3.push(`README links #${id} \u2192 ${linkPath}, which isn't there any more; the card is at ${want}.`);
@@ -20017,9 +20122,9 @@ function crossRefComplaints() {
20017
20122
  const said3 = [];
20018
20123
  const live = liveIds();
20019
20124
  for (const file of walkMd(TODO)) {
20020
- const base = path57.basename(file);
20125
+ const base = path58.basename(file);
20021
20126
  if (base === "README.md") continue;
20022
- const ownerId = base === "root.md" ? idPrefix(path57.basename(path57.dirname(file))) : idPrefix(base);
20127
+ const ownerId = base === "root.md" ? idPrefix(path58.basename(path58.dirname(file))) : idPrefix(base);
20023
20128
  if (ownerId == null) continue;
20024
20129
  const { meta } = parseFrontmatter(fs52.readFileSync(file, "utf8"));
20025
20130
  if (!meta) continue;
@@ -20059,7 +20164,7 @@ var init_reconcile = __esm({
20059
20164
 
20060
20165
  // ../cli/src/commands/card.ts
20061
20166
  import fs53 from "node:fs";
20062
- import path58 from "node:path";
20167
+ import path59 from "node:path";
20063
20168
  function defaultBody() {
20064
20169
  return [
20065
20170
  "<\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>",
@@ -20113,13 +20218,13 @@ function validateCreate(opts) {
20113
20218
  }
20114
20219
  function cmdCreate(opts) {
20115
20220
  const { title, priority, blocked_by, related, modules, triage, workflow, source, questions, written } = validateCreate(opts);
20116
- if (opts.id === void 0) die("a card's number comes from Cloud: create it inside a task's job, or pass that task's --id", { kind: "needs-cloud" });
20221
+ if (opts.id === void 0) die("\u8BF7\u901A\u8FC7 `akb raw create` \u521B\u5EFA\u5361\u7247\uFF0C\u7531 Cloud \u5206\u914D\u7F16\u53F7", { kind: "needs-cloud" });
20117
20222
  const start = opts.id;
20118
20223
  const slug2 = slugify(opts.slug !== void 0 ? opts.slug : title);
20119
20224
  const fileRel = `${start}-${slug2}.md`;
20120
- const file = path58.join(TODO, fileRel);
20225
+ const file = path59.join(TODO, fileRel);
20121
20226
  const held4 = locate(start) ?? locateArchived(start);
20122
- if (held4 && path58.basename(held4.target) === `${start}-${slug2}.md`) {
20227
+ if (held4 && path59.basename(held4.target) === `${start}-${slug2}.md`) {
20123
20228
  say(start);
20124
20229
  say(` ${rel(held4.target)} was already written`);
20125
20230
  return { id: start, ids: [start], title, file: rel(held4.target), indexed: false };
@@ -20168,7 +20273,7 @@ function cmdUpdate(id, flags) {
20168
20273
  if (creating) die(creating, { kind: "card-being-created", id });
20169
20274
  const found2 = locate(id);
20170
20275
  if (!found2) die(`no task with id ${id} under ${rel(TODO)}`, { kind: "card-not-found", id });
20171
- const file = found2.kind === "group" ? path58.join(found2.target, "root.md") : found2.target;
20276
+ const file = found2.kind === "group" ? path59.join(found2.target, "root.md") : found2.target;
20172
20277
  const { meta, body } = parseFrontmatter(fs53.readFileSync(file, "utf8"));
20173
20278
  if (!meta) die(`${rel(file)} has no frontmatter \u2014 run \`migrate\` first`);
20174
20279
  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");
@@ -20249,26 +20354,26 @@ function cmdUpdate(id, flags) {
20249
20354
  meta.status = "todo";
20250
20355
  changes.push("status\u2192todo (open questions)");
20251
20356
  }
20252
- const curRel = path58.relative(TODO, file);
20357
+ const curRel = path59.relative(TODO, file);
20253
20358
  const isSubtask = found2.kind === "file" && enclosingGroupRoot(file) !== null;
20254
- let base = path58.basename(file);
20359
+ let base = path59.basename(file);
20255
20360
  if (flags.slug !== void 0) {
20256
20361
  if (found2.kind === "group") die("renaming a group root by script is not supported");
20257
20362
  base = `${id}-${slugify(flags.slug)}.md`;
20258
20363
  }
20259
- const destRel = path58.join(path58.dirname(curRel), base);
20260
- const dest = path58.join(TODO, destRel);
20364
+ const destRel = path59.join(path59.dirname(curRel), base);
20365
+ const dest = path59.join(TODO, destRel);
20261
20366
  const moving = dest !== file;
20262
20367
  if (moving && fs53.existsSync(dest)) die(`${rel(dest)} already exists`);
20263
20368
  fs53.writeFileSync(file, serializeFrontmatter(meta) + "\n" + body);
20264
20369
  if (moving) fs53.renameSync(file, dest);
20265
20370
  if (isSubtask) {
20266
20371
  if (moving || changes.includes("title")) repointReadmeLink(id, curRel, destRel, meta.title);
20267
- if (moving) changes.push(`renamed \u2192 ${destRel.split(path58.sep).join("/")}`);
20372
+ if (moving) changes.push(`renamed \u2192 ${destRel.split(path59.sep).join("/")}`);
20268
20373
  } else if (moving) {
20269
20374
  stripReadmeRefs({ kind: "file", rel: curRel });
20270
20375
  addReadmeRef(id, meta.title, destRel);
20271
- changes.push(`renamed \u2192 ${destRel.split(path58.sep).join("/")}`);
20376
+ changes.push(`renamed \u2192 ${destRel.split(path59.sep).join("/")}`);
20272
20377
  } else if (changes.includes("title")) {
20273
20378
  stripReadmeRefs({ kind: "file", rel: curRel });
20274
20379
  addReadmeRef(id, meta.title, curRel);
@@ -20280,7 +20385,7 @@ function cmdUpdateQuestions(id, input) {
20280
20385
  const ops = readQuestionOps(input.ops ?? []);
20281
20386
  const found2 = locate(id);
20282
20387
  if (!found2) die(`no task with id ${id} under ${rel(TODO)}`, { kind: "card-not-found", id });
20283
- const file = found2.kind === "group" ? path58.join(found2.target, "root.md") : found2.target;
20388
+ const file = found2.kind === "group" ? path59.join(found2.target, "root.md") : found2.target;
20284
20389
  const { meta, body } = parseFrontmatter(fs53.readFileSync(file, "utf8"));
20285
20390
  if (!meta) die(`${rel(file)} has no frontmatter \u2014 run \`migrate\` first`);
20286
20391
  if (insideRun() && ops.some((op) => op.kind === "skip" || op.kind === "unskip")) {
@@ -20429,9 +20534,9 @@ var init_config_template = __esm({
20429
20534
 
20430
20535
  // ../cli/src/lib/agent/local.ts
20431
20536
  import fs54 from "node:fs";
20432
- import path59 from "node:path";
20537
+ import path60 from "node:path";
20433
20538
  function takeLocalModels(board2) {
20434
- const file = path59.join(board2, ".local.json");
20539
+ const file = path60.join(board2, ".local.json");
20435
20540
  let held4;
20436
20541
  try {
20437
20542
  held4 = configBlock(JSON.parse(fs54.readFileSync(file, "utf8")));
@@ -20486,7 +20591,7 @@ var init_check2 = __esm({
20486
20591
  });
20487
20592
 
20488
20593
  // ../cli/src/lib/signals/identity.ts
20489
- import { createHash as createHash2 } from "node:crypto";
20594
+ import { createHash as createHash3 } from "node:crypto";
20490
20595
  function host(url) {
20491
20596
  try {
20492
20597
  return new URL(url).hostname.replace(/^www\./, "");
@@ -20508,13 +20613,13 @@ var init_identity2 = __esm({
20508
20613
  "../cli/src/lib/signals/identity.ts"() {
20509
20614
  "use strict";
20510
20615
  DERIVED = "derived-";
20511
- derivedSourceId = (seed) => `${DERIVED}${createHash2("sha256").update(seed).digest("hex").slice(0, 16)}`;
20616
+ derivedSourceId = (seed) => `${DERIVED}${createHash3("sha256").update(seed).digest("hex").slice(0, 16)}`;
20512
20617
  }
20513
20618
  });
20514
20619
 
20515
20620
  // ../cli/src/lib/signals/migrate.ts
20516
20621
  import fs55 from "node:fs";
20517
- import path60 from "node:path";
20622
+ import path61 from "node:path";
20518
20623
  function readHandled() {
20519
20624
  let text5;
20520
20625
  try {
@@ -20536,7 +20641,7 @@ function readHandled() {
20536
20641
  }
20537
20642
  function lift(from, into) {
20538
20643
  fs55.mkdirSync(into, { recursive: true });
20539
- fs55.renameSync(from, path60.join(into, freeName(into, path60.basename(from))));
20644
+ fs55.renameSync(from, path61.join(into, freeName(into, path61.basename(from))));
20540
20645
  }
20541
20646
  function repoint(dir, from, into) {
20542
20647
  let entries;
@@ -20547,7 +20652,7 @@ function repoint(dir, from, into) {
20547
20652
  }
20548
20653
  for (const entry of entries) {
20549
20654
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
20550
- const file = path60.join(dir, entry.name);
20655
+ const file = path61.join(dir, entry.name);
20551
20656
  const text5 = fs55.readFileSync(file, "utf8");
20552
20657
  if (text5.includes(from)) fs55.writeFileSync(file, text5.split(from).join(into));
20553
20658
  }
@@ -20560,17 +20665,17 @@ function dropIfEmpty2(dir) {
20560
20665
  }
20561
20666
  function liftInbox() {
20562
20667
  if (!fs55.existsSync(SIGNALS_OLD_INBOX)) return;
20563
- const oldFiles = path60.join(SIGNALS_OLD_INBOX, "files");
20668
+ const oldFiles = path61.join(SIGNALS_OLD_INBOX, "files");
20564
20669
  if (fs55.existsSync(oldFiles)) {
20565
20670
  for (const entry of fs55.readdirSync(oldFiles, { withFileTypes: true })) {
20566
- if (entry.isFile()) lift(path60.join(oldFiles, entry.name), SIGNALS_FILES);
20671
+ if (entry.isFile()) lift(path61.join(oldFiles, entry.name), SIGNALS_FILES);
20567
20672
  }
20568
20673
  dropIfEmpty2(oldFiles);
20569
20674
  }
20570
20675
  const held4 = new Set(readFolder(TRIAGE).map((signal2) => signal2.sourceId));
20571
20676
  for (const entry of fs55.readdirSync(SIGNALS_OLD_INBOX, { withFileTypes: true })) {
20572
20677
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
20573
- const from = path60.join(SIGNALS_OLD_INBOX, entry.name);
20678
+ const from = path61.join(SIGNALS_OLD_INBOX, entry.name);
20574
20679
  const item = parse(from);
20575
20680
  if (item && held4.has(item.sourceId)) {
20576
20681
  fs55.rmSync(from, { force: true });
@@ -20579,7 +20684,7 @@ function liftInbox() {
20579
20684
  if (item) held4.add(item.sourceId);
20580
20685
  lift(from, TRIAGE);
20581
20686
  }
20582
- const wasAt = asWritten(path60.join(SIGNALS_OLD_INBOX, "files"));
20687
+ const wasAt = asWritten(path61.join(SIGNALS_OLD_INBOX, "files"));
20583
20688
  const nowAt = asWritten(SIGNALS_FILES);
20584
20689
  for (const dir of [TRIAGE, SIGNALS_ARCHIVED, SIGNALS_DISMISSED]) repoint(dir, wasAt, nowAt);
20585
20690
  dropIfEmpty2(SIGNALS_OLD_INBOX);
@@ -20605,7 +20710,7 @@ function liftHandled() {
20605
20710
  "---",
20606
20711
  ""
20607
20712
  ];
20608
- fs55.writeFileSync(path60.join(SIGNALS_DISMISSED, name), `${front.join("\n")}
20713
+ fs55.writeFileSync(path61.join(SIGNALS_DISMISSED, name), `${front.join("\n")}
20609
20714
  `);
20610
20715
  }
20611
20716
  fs55.rmSync(SIGNALS_OLD_HANDLED, { force: true });
@@ -20626,14 +20731,14 @@ var init_migrate = __esm({
20626
20731
  init_yaml();
20627
20732
  init_inbox();
20628
20733
  HANDLED_LINE = /^-\s+(.+?)\s+—\s+(.+)$/;
20629
- asWritten = (dir) => `${rel(dir).split(path60.sep).join("/")}/`;
20734
+ asWritten = (dir) => `${rel(dir).split(path61.sep).join("/")}/`;
20630
20735
  }
20631
20736
  });
20632
20737
 
20633
20738
  // ../cli/src/lib/signals/add.ts
20634
20739
  import fs56 from "node:fs";
20635
- import path61 from "node:path";
20636
- import { createHash as createHash3 } from "node:crypto";
20740
+ import path62 from "node:path";
20741
+ import { createHash as createHash4 } from "node:crypto";
20637
20742
  function linkTitle(url) {
20638
20743
  let at;
20639
20744
  try {
@@ -20645,7 +20750,7 @@ function linkTitle(url) {
20645
20750
  return oneLine(where) || url.slice(0, TITLE_MAX);
20646
20751
  }
20647
20752
  function asText(file) {
20648
- const ext = path61.extname(file.name).toLowerCase();
20753
+ const ext = path62.extname(file.name).toLowerCase();
20649
20754
  if (!file.type.startsWith("text/") && file.type !== "application/json" && !TEXT_EXTENSIONS.has(ext)) return null;
20650
20755
  try {
20651
20756
  const text5 = new TextDecoder("utf-8", { fatal: true }).decode(file.data);
@@ -20655,10 +20760,10 @@ function asText(file) {
20655
20760
  }
20656
20761
  }
20657
20762
  function fileName2(name, data) {
20658
- const base = path61.basename(name);
20659
- const ext = path61.extname(base).toLowerCase().slice(0, 12);
20660
- const word = path61.basename(base, path61.extname(base)).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40) || "file";
20661
- const hash2 = createHash3("sha256").update(data).digest("hex").slice(0, 8);
20763
+ const base = path62.basename(name);
20764
+ const ext = path62.extname(base).toLowerCase().slice(0, 12);
20765
+ const word = path62.basename(base, path62.extname(base)).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40) || "file";
20766
+ const hash2 = createHash4("sha256").update(data).digest("hex").slice(0, 8);
20662
20767
  return `${word}-${hash2}${ext}`;
20663
20768
  }
20664
20769
  function addToInbox(drop) {
@@ -20666,9 +20771,9 @@ function addToInbox(drop) {
20666
20771
  const typed = (drop.text ?? "").trim();
20667
20772
  const file = drop.file;
20668
20773
  if (!typed && !file) return { ok: false, error: "nothing to add \u2014 paste a link or some text, or drop a file." };
20669
- if (file && file.data.length === 0) return { ok: false, error: `${path61.basename(file.name)} is empty.` };
20774
+ if (file && file.data.length === 0) return { ok: false, error: `${path62.basename(file.name)} is empty.` };
20670
20775
  if (file && file.data.length > MAX_BYTES) {
20671
- return { ok: false, error: `${path61.basename(file.name)} is over ${MAX_BYTES / 1024 / 1024} MB \u2014 too big for the board.` };
20776
+ return { ok: false, error: `${path62.basename(file.name)} is over ${MAX_BYTES / 1024 / 1024} MB \u2014 too big for the board.` };
20672
20777
  }
20673
20778
  const read4 = describe(typed, file);
20674
20779
  const keep2 = read4.keep;
@@ -20705,7 +20810,7 @@ function describe(typed, file) {
20705
20810
  return only ? [only] : [];
20706
20811
  };
20707
20812
  if (file) {
20708
- const name = path61.basename(file.name);
20813
+ const name = path62.basename(file.name);
20709
20814
  const text5 = asText(file);
20710
20815
  const note = typed ? `
20711
20816
 
@@ -20717,7 +20822,7 @@ ${typed}` : "";
20717
20822
  const at = keepAt(file);
20718
20823
  return {
20719
20824
  incoming: {
20720
- title: path61.basename(name, path61.extname(name)) || name,
20825
+ title: path62.basename(name, path62.extname(name)) || name,
20721
20826
  summary: `${name} \u2014 ${boardRel2(at)}${note}`,
20722
20827
  ...named2
20723
20828
  },
@@ -20755,19 +20860,19 @@ var init_add = __esm({
20755
20860
  MAX_BYTES = 25 * 1024 * 1024;
20756
20861
  TITLE_MAX = 120;
20757
20862
  TEXT_EXTENSIONS = /* @__PURE__ */ new Set([".md", ".markdown", ".txt", ".text", ".csv", ".json", ".yml", ".yaml", ".log"]);
20758
- boardRel2 = (file) => rel(file).split(path61.sep).join("/");
20863
+ boardRel2 = (file) => rel(file).split(path62.sep).join("/");
20759
20864
  oneLine = (text5) => {
20760
20865
  const first = text5.split("\n").find((line) => line.trim()) ?? "";
20761
20866
  const clean = first.replace(/^#+\s*/, "").trim();
20762
20867
  return clean.length > TITLE_MAX ? `${clean.slice(0, TITLE_MAX - 1).trimEnd()}\u2026` : clean;
20763
20868
  };
20764
- keepAt = (file) => path61.join(SIGNALS_FILES, fileName2(file.name, file.data));
20869
+ keepAt = (file) => path62.join(SIGNALS_FILES, fileName2(file.name, file.data));
20765
20870
  }
20766
20871
  });
20767
20872
 
20768
20873
  // ../cli/src/lib/recurring.ts
20769
20874
  import fs57 from "node:fs";
20770
- import path62 from "node:path";
20875
+ import path63 from "node:path";
20771
20876
  function migratePruneMemoryCard() {
20772
20877
  const dir = recurringDir();
20773
20878
  let names4;
@@ -20778,7 +20883,7 @@ function migratePruneMemoryCard() {
20778
20883
  }
20779
20884
  const name = names4.find((entry) => entry.endsWith(`-${PRUNE_SLUG}.md`));
20780
20885
  if (!name) return null;
20781
- const file = path62.join(dir, name);
20886
+ const file = path63.join(dir, name);
20782
20887
  try {
20783
20888
  const { meta } = parseFrontmatter(fs57.readFileSync(file, "utf8"));
20784
20889
  const cadence = meta?.cadence;
@@ -20795,11 +20900,11 @@ function legacyCards() {
20795
20900
  for (const entry of fs57.readdirSync(dir, { withFileTypes: true })) {
20796
20901
  const id = idPrefix(entry.name);
20797
20902
  if (id === null) continue;
20798
- const full = path62.join(dir, entry.name);
20903
+ const full = path63.join(dir, entry.name);
20799
20904
  const slug2 = entry.name.replace(/\.md$/, "").replace(/^\d+-?/, "");
20800
20905
  if (entry.isFile() && entry.name.endsWith(".md")) cards.push({ id, slug: slug2, file: full });
20801
- else if (entry.isDirectory() && fs57.existsSync(path62.join(full, "root.md"))) {
20802
- cards.push({ id, slug: slug2, file: path62.join(full, "root.md"), folder: full });
20906
+ else if (entry.isDirectory() && fs57.existsSync(path63.join(full, "root.md"))) {
20907
+ cards.push({ id, slug: slug2, file: path63.join(full, "root.md"), folder: full });
20803
20908
  }
20804
20909
  }
20805
20910
  return cards.sort((a, b) => a.id - b.id);
@@ -20808,8 +20913,8 @@ function mergeInto2(from, to, skip = []) {
20808
20913
  fs57.mkdirSync(to, { recursive: true });
20809
20914
  for (const entry of fs57.readdirSync(from, { withFileTypes: true })) {
20810
20915
  if (skip.includes(entry.name)) continue;
20811
- const source = path62.join(from, entry.name);
20812
- const target = path62.join(to, entry.name);
20916
+ const source = path63.join(from, entry.name);
20917
+ const target = path63.join(to, entry.name);
20813
20918
  if (!fs57.existsSync(target)) fs57.renameSync(source, target);
20814
20919
  else if (entry.isDirectory() && fs57.statSync(target).isDirectory()) mergeInto2(source, target);
20815
20920
  }
@@ -20819,9 +20924,9 @@ function moveSideFolders() {
20819
20924
  const dir = recurringDir();
20820
20925
  for (const entry of fs57.readdirSync(dir, { withFileTypes: true })) {
20821
20926
  if (!entry.isDirectory() || idPrefix(entry.name) !== null) continue;
20822
- const from = path62.join(dir, entry.name);
20927
+ const from = path63.join(dir, entry.name);
20823
20928
  if (isEmpty(from)) fs57.rmSync(from, { recursive: true, force: true });
20824
- else mergeInto2(from, path62.join(AGENTS, entry.name));
20929
+ else mergeInto2(from, path63.join(AGENTS, entry.name));
20825
20930
  }
20826
20931
  }
20827
20932
  function sections(body) {
@@ -20871,7 +20976,7 @@ function agentName(card, text5) {
20871
20976
  const known2 = /* @__PURE__ */ new Set([...specAgentCatalog().agents.map((a) => a.name), ...ROLE_NAMES]);
20872
20977
  for (const name of [slug2, `${slug2}-${card.id}`]) {
20873
20978
  if (!AGENT_NAME.test(name)) continue;
20874
- const file = path62.join(AGENTS, name, AGENT_FILE3);
20979
+ const file = path63.join(AGENTS, name, AGENT_FILE3);
20875
20980
  const written = fs57.existsSync(file) ? fs57.readFileSync(file, "utf8") : null;
20876
20981
  if (written === text5(name)) return name;
20877
20982
  if (written === null && !known2.has(name)) return name;
@@ -20889,7 +20994,7 @@ ${q.options.map((o) => `- ${o}`).join("\n")}` : "";
20889
20994
  function removeCard(card) {
20890
20995
  dropCrossRefs(card.id);
20891
20996
  const target = card.folder ?? card.file;
20892
- stripReadmeRefs({ kind: card.folder ? "group" : "file", rel: path62.relative(TODO, target).split(path62.sep).join("/") });
20997
+ stripReadmeRefs({ kind: card.folder ? "group" : "file", rel: path63.relative(TODO, target).split(path63.sep).join("/") });
20893
20998
  fs57.rmSync(target, { recursive: true, force: true });
20894
20999
  }
20895
21000
  function migrateCard(card) {
@@ -20903,8 +21008,8 @@ function migrateCard(card) {
20903
21008
  if (!flow) throw new Error("this board has no workflow to put it in");
20904
21009
  const text5 = (name2) => agentText(name2, meta.title || card.slug, body);
20905
21010
  const name = agentName(card, text5);
20906
- const dir = path62.join(AGENTS, name);
20907
- const file = path62.join(dir, AGENT_FILE3);
21011
+ const dir = path63.join(AGENTS, name);
21012
+ const file = path63.join(dir, AGENT_FILE3);
20908
21013
  const madeDir = !fs57.existsSync(dir);
20909
21014
  const wrote = !fs57.existsSync(file);
20910
21015
  try {
@@ -20971,7 +21076,7 @@ var init_recurring = __esm({
20971
21076
  PRUNE_SLUG = "prune-the-memory";
20972
21077
  FETCH_SLUG = "fetch-triage-items";
20973
21078
  AGENT_FILE3 = "AGENT.md";
20974
- recurringDir = () => path62.join(TODO, LEGACY_RECURRING);
21079
+ recurringDir = () => path63.join(TODO, LEGACY_RECURRING);
20975
21080
  isEmpty = (dir) => fs57.readdirSync(dir).every((name) => name === ".DS_Store");
20976
21081
  SECTION = /^##\s+(.+?)\s*$/;
20977
21082
  }
@@ -21176,7 +21281,7 @@ var init_scheduled = __esm({
21176
21281
 
21177
21282
  // ../cli/src/lib/card-age.ts
21178
21283
  import fs59 from "node:fs";
21179
- import path63 from "node:path";
21284
+ import path64 from "node:path";
21180
21285
  function staleAfterIn(config) {
21181
21286
  const days = Number(LINE.exec(config)?.[1]);
21182
21287
  return Number.isInteger(days) && days > 0 ? days : STALE_AFTER_DEFAULT;
@@ -21186,14 +21291,14 @@ function staleAfter() {
21186
21291
  return staleAfterIn(fs59.readFileSync(CONFIG, "utf8"));
21187
21292
  }
21188
21293
  function todoPathspec(top) {
21189
- const from = path63.relative(real(top), real(TODO));
21190
- if (!from || from.startsWith("..") || path63.isAbsolute(from)) return null;
21191
- return from.split(path63.sep).join("/");
21294
+ const from = path64.relative(real(top), real(TODO));
21295
+ if (!from || from.startsWith("..") || path64.isAbsolute(from)) return null;
21296
+ return from.split(path64.sep).join("/");
21192
21297
  }
21193
21298
  function cardFile2(spec, printed) {
21194
21299
  const p = unquotePath(printed);
21195
21300
  if (!p.startsWith(`${spec}/`)) return null;
21196
- return path63.join(TODO, ...p.slice(spec.length + 1).split("/"));
21301
+ return path64.join(TODO, ...p.slice(spec.length + 1).split("/"));
21197
21302
  }
21198
21303
  function dirtyCards(top, spec) {
21199
21304
  const out = git(["status", "--porcelain", "--untracked-files=all", "--", spec], top);
@@ -21242,7 +21347,7 @@ var init_card_age = __esm({
21242
21347
  try {
21243
21348
  return fs59.realpathSync(p);
21244
21349
  } catch {
21245
- return path63.resolve(p);
21350
+ return path64.resolve(p);
21246
21351
  }
21247
21352
  };
21248
21353
  unquotePath = (line) => {
@@ -21278,7 +21383,7 @@ var init_card_holds = __esm({
21278
21383
 
21279
21384
  // ../cli/src/commands/list.ts
21280
21385
  import fs60 from "node:fs";
21281
- import path64 from "node:path";
21386
+ import path65 from "node:path";
21282
21387
  function summaryLine(body) {
21283
21388
  const lines = body.split("\n");
21284
21389
  let i = 0;
@@ -21294,12 +21399,12 @@ function summaryLine(body) {
21294
21399
  function openRows() {
21295
21400
  const rows = [];
21296
21401
  for (const file of walkMd(TODO)) {
21297
- const base = path64.basename(file);
21402
+ const base = path65.basename(file);
21298
21403
  if (base === "README.md") continue;
21299
21404
  const isRoot = base === "root.md";
21300
- const id = isRoot ? idPrefix(path64.basename(path64.dirname(file))) : idPrefix(base);
21405
+ const id = isRoot ? idPrefix(path65.basename(path65.dirname(file))) : idPrefix(base);
21301
21406
  if (id == null) continue;
21302
- if (isLegacyRecurring(path64.relative(TODO, file))) continue;
21407
+ if (isLegacyRecurring(path65.relative(TODO, file))) continue;
21303
21408
  const { meta, body } = parseFrontmatter(fs60.readFileSync(file, "utf8"));
21304
21409
  rows.push({
21305
21410
  id,
@@ -21344,12 +21449,12 @@ function archivedFiles() {
21344
21449
  const files2 = /* @__PURE__ */ new Map();
21345
21450
  if (!fs60.existsSync(ARCHIVE)) return files2;
21346
21451
  for (const file of walkMd(ARCHIVE)) {
21347
- const id = idPrefix(path64.basename(file));
21452
+ const id = idPrefix(path65.basename(file));
21348
21453
  if (id !== null) files2.set(id, file);
21349
21454
  }
21350
21455
  for (const dir of walkDirs(ARCHIVE)) {
21351
- const id = idPrefix(path64.basename(dir));
21352
- const root = path64.join(dir, "root.md");
21456
+ const id = idPrefix(path65.basename(dir));
21457
+ const root = path65.join(dir, "root.md");
21353
21458
  if (id !== null && fs60.existsSync(root)) files2.set(id, root);
21354
21459
  }
21355
21460
  return files2;
@@ -21462,13 +21567,13 @@ var init_list = __esm({
21462
21567
 
21463
21568
  // ../cli/src/commands/misc.ts
21464
21569
  import fs61 from "node:fs";
21465
- import path65 from "node:path";
21570
+ import path66 from "node:path";
21466
21571
  function extractOldMeta(text5, file) {
21467
21572
  const grab = (re) => {
21468
21573
  const m = text5.match(re);
21469
21574
  return m ? m[1].trim() : null;
21470
21575
  };
21471
- const title = grab(/^#\s+(.+)$/m) || slugify(path65.basename(file, ".md").replace(/^\d+-/, "")).replace(/-/g, " ");
21576
+ const title = grab(/^#\s+(.+)$/m) || slugify(path66.basename(file, ".md").replace(/^\d+-/, "")).replace(/-/g, " ");
21472
21577
  const norm = (raw) => {
21473
21578
  const v = (raw || "").toLowerCase();
21474
21579
  return LEVELS.includes(v) ? v : "med";
@@ -21495,7 +21600,7 @@ function stripOldHeader(text5) {
21495
21600
  }
21496
21601
  function cmdMigrate(opts) {
21497
21602
  const dry = opts.dryRun === true;
21498
- const files2 = walkMd(TODO).filter((f) => path65.basename(f) !== "README.md");
21603
+ const files2 = walkMd(TODO).filter((f) => path66.basename(f) !== "README.md");
21499
21604
  let changed = 0;
21500
21605
  let skipped = 0;
21501
21606
  for (const file of files2) {
@@ -21546,11 +21651,11 @@ var init_group_close = __esm({
21546
21651
 
21547
21652
  // ../cli/src/commands/remove.ts
21548
21653
  import fs63 from "node:fs";
21549
- import path66 from "node:path";
21654
+ import path67 from "node:path";
21550
21655
  function leavingIds(id, found2) {
21551
21656
  const ids = /* @__PURE__ */ new Set([id]);
21552
21657
  if (found2.kind === "group") {
21553
- const names4 = [...walkMd(found2.target), ...walkDirs(found2.target)].map((f) => path66.basename(f));
21658
+ const names4 = [...walkMd(found2.target), ...walkDirs(found2.target)].map((f) => path67.basename(f));
21554
21659
  for (const name of names4) {
21555
21660
  const n = idPrefix(name);
21556
21661
  if (n !== null) ids.add(n);
@@ -21564,7 +21669,7 @@ function findMentions(ids) {
21564
21669
  for (const dir of [TODO, MEMORY]) {
21565
21670
  if (!fs63.existsSync(dir)) continue;
21566
21671
  for (const file of walkMd(dir)) {
21567
- if (path66.basename(file) === "README.md") continue;
21672
+ if (path67.basename(file) === "README.md") continue;
21568
21673
  const lines = fs63.readFileSync(file, "utf8").split("\n");
21569
21674
  const fmEnd = frontmatterEnd(lines);
21570
21675
  lines.forEach((line, i) => {
@@ -21581,12 +21686,12 @@ function findMentions(ids) {
21581
21686
  return hits;
21582
21687
  }
21583
21688
  function leavingCards(id, found2) {
21584
- const own = found2.kind === "group" ? path66.join(found2.target, "root.md") : found2.target;
21689
+ const own = found2.kind === "group" ? path67.join(found2.target, "root.md") : found2.target;
21585
21690
  const cards = [{ id, file: own }];
21586
21691
  if (found2.kind !== "group") return cards;
21587
21692
  for (const file of walkMd(found2.target)) {
21588
21693
  if (file === own) continue;
21589
- const n = idPrefix(path66.basename(file));
21694
+ const n = idPrefix(path67.basename(file));
21590
21695
  if (n !== null) cards.push({ id: n, file });
21591
21696
  }
21592
21697
  return cards;
@@ -21614,7 +21719,7 @@ function unfinishedDelivery(id) {
21614
21719
  }[gap];
21615
21720
  return `#${id} is not finished: ${what}.`;
21616
21721
  }
21617
- const missing = planDeliverables(card.body).filter((src) => src.startsWith(".assets/")).find((src) => !fs63.existsSync(path66.join(ASSETS, src.slice(".assets/".length))));
21722
+ const missing = planDeliverables(card.body).filter((src) => src.startsWith(".assets/")).find((src) => !fs63.existsSync(path67.join(ASSETS, src.slice(".assets/".length))));
21618
21723
  return missing ? `#${id} is not finished: ${missing} is not on this machine.` : null;
21619
21724
  }
21620
21725
  function agentDecisions(text5) {
@@ -21641,7 +21746,7 @@ function removeCard2(id, metric, options) {
21641
21746
  const unfinished = metric === "completed" ? unfinishedDelivery(id) : null;
21642
21747
  if (unfinished) die(unfinished, { kind: "plan-delivery-unfinished" });
21643
21748
  const dest = options.cleanupDiscarded && locateArchived(id) ? null : archiveDest(found2);
21644
- const cardFile3 = found2.kind === "group" ? path66.join(found2.target, "root.md") : found2.target;
21749
+ const cardFile3 = found2.kind === "group" ? path67.join(found2.target, "root.md") : found2.target;
21645
21750
  const cardText = fs63.existsSync(cardFile3) ? fs63.readFileSync(cardFile3, "utf8") : "";
21646
21751
  const { meta: cardMeta3 } = parseFrontmatter(cardText);
21647
21752
  const alsoRemoved = found2.kind === "group" ? walkMd(found2.target).filter((f) => f !== cardFile3).map((f) => rel(f)).sort() : [];
@@ -21725,7 +21830,7 @@ function removeCard2(id, metric, options) {
21725
21830
  };
21726
21831
  }
21727
21832
  function closeGroup(rootFile) {
21728
- const rootId = idPrefix(path66.basename(path66.dirname(rootFile)));
21833
+ const rootId = idPrefix(path67.basename(path67.dirname(rootFile)));
21729
21834
  if (rootId === null) return null;
21730
21835
  if (runnerProject(KANBAN)) return null;
21731
21836
  if (!groupCloses(rootFile)) return null;
@@ -21789,7 +21894,7 @@ function cleanupDiscardedCards(sessionId) {
21789
21894
  if (card.pending) throw new Error(`Discarding #${card.id} did not finish. Retry the discard.`);
21790
21895
  while (locate(card.id)) cmdRemove(card.id, "rejected", { discard: true, cleanupDiscarded: true });
21791
21896
  dropCrossRefs(card.id);
21792
- stripReadmeRefs({ kind: "file", rel: path66.relative(TODO, path66.resolve(REPO_ROOT, card.path)).split(path66.sep).join("/") });
21897
+ stripReadmeRefs({ kind: "file", rel: path67.relative(TODO, path67.resolve(REPO_ROOT, card.path)).split(path67.sep).join("/") });
21793
21898
  }
21794
21899
  return discarded.map((c) => c.id);
21795
21900
  });
@@ -21876,7 +21981,7 @@ var init_memory_file = __esm({
21876
21981
 
21877
21982
  // ../cli/src/commands/rule.ts
21878
21983
  import fs64 from "node:fs";
21879
- import path67 from "node:path";
21984
+ import path68 from "node:path";
21880
21985
  function cmdRule(askedName, flags) {
21881
21986
  const agent = askedName.trim();
21882
21987
  if (!agentRoster().some((a) => a.name === agent)) {
@@ -21885,7 +21990,7 @@ function cmdRule(askedName, flags) {
21885
21990
  const rule = readRuleText(flags.file, flags.text);
21886
21991
  const res = setAgentRule(agent, rule);
21887
21992
  if (!res.ok) die(res.error);
21888
- const file = rel(path67.join(RULES, `${agent}.md`));
21993
+ const file = rel(path68.join(RULES, `${agent}.md`));
21889
21994
  say(rule ? `wrote the \`${agent}\` rule (${file})` : `cleared the \`${agent}\` rule`);
21890
21995
  return { agent, file, cleared: !rule };
21891
21996
  }
@@ -22021,13 +22126,13 @@ var init_storyboard = __esm({
22021
22126
 
22022
22127
  // ../cli/src/lib/spec-contract.ts
22023
22128
  import fs65 from "node:fs";
22024
- import path68 from "node:path";
22129
+ import path69 from "node:path";
22025
22130
  function snapshotSpecs() {
22026
22131
  const out = /* @__PURE__ */ new Map();
22027
22132
  if (!fs65.existsSync(TODO)) return out;
22028
22133
  for (const file of walkMd(TODO)) {
22029
- if (isLegacyRecurring(path68.relative(TODO, file))) continue;
22030
- const id = idPrefix(path68.basename(file) === "root.md" ? path68.basename(path68.dirname(file)) : path68.basename(file));
22134
+ if (isLegacyRecurring(path69.relative(TODO, file))) continue;
22135
+ const id = idPrefix(path69.basename(file) === "root.md" ? path69.basename(path69.dirname(file)) : path69.basename(file));
22031
22136
  if (id !== null) out.set(file, { id, text: fs65.readFileSync(file, "utf8") });
22032
22137
  }
22033
22138
  return out;
@@ -22035,7 +22140,7 @@ function snapshotSpecs() {
22035
22140
  function enclosingGroups(file) {
22036
22141
  const ids = [];
22037
22142
  for (let root = enclosingGroupRoot(file); root; root = enclosingGroupRoot(root)) {
22038
- const id = idPrefix(path68.basename(path68.dirname(root)));
22143
+ const id = idPrefix(path69.basename(path69.dirname(root)));
22039
22144
  if (id !== null) ids.push(id);
22040
22145
  }
22041
22146
  return ids;
@@ -22205,8 +22310,8 @@ function validateSpec(file, text5, id) {
22205
22310
  function inside(dir, name) {
22206
22311
  try {
22207
22312
  const root = fs65.realpathSync(dir);
22208
- const real2 = fs65.realpathSync(path68.join(dir, name));
22209
- return real2.startsWith(root + path68.sep) && fs65.statSync(real2).isFile() ? real2 : null;
22313
+ const real2 = fs65.realpathSync(path69.join(dir, name));
22314
+ return real2.startsWith(root + path69.sep) && fs65.statSync(real2).isFile() ? real2 : null;
22210
22315
  } catch {
22211
22316
  return null;
22212
22317
  }
@@ -22224,7 +22329,7 @@ function variantsProblem(raw, src, id) {
22224
22329
  if (files2.length < 2 || !files2.every((f) => typeof f === "string" && f)) return `variants needs at least two labels, each naming a file. ${shape}`;
22225
22330
  if (!files2.includes(src)) return `src="${src}" is not one of the variants. Set src to the version shown first.`;
22226
22331
  if (id === void 0) return null;
22227
- const dir = path68.join(ASSETS, String(id));
22332
+ const dir = path69.join(ASSETS, String(id));
22228
22333
  for (const f of files2) {
22229
22334
  const parts = f.split("/");
22230
22335
  if (parts.length !== 3 || parts[0] !== ".assets" || parts[1] !== String(id) || !inside(dir, parts[2])) {
@@ -22234,7 +22339,7 @@ function variantsProblem(raw, src, id) {
22234
22339
  return null;
22235
22340
  }
22236
22341
  function compareProblem(attrs, id) {
22237
- const dir = path68.join(ASSETS, String(id));
22342
+ const dir = path69.join(ASSETS, String(id));
22238
22343
  const src = (message) => ({ rule: "compare-src", message });
22239
22344
  const shots = (message) => ({ rule: "compare-shots", message });
22240
22345
  for (const side of ["a", "b"]) {
@@ -22274,7 +22379,7 @@ function compareProblem(attrs, id) {
22274
22379
  }
22275
22380
  function validateStoryboards(file, id, text5) {
22276
22381
  const errors = [];
22277
- const dir = path68.join(ASSETS, String(id));
22382
+ const dir = path69.join(ASSETS, String(id));
22278
22383
  for (const marker of storyboardMarkers(text5)) {
22279
22384
  const named2 = assetName(marker.src, id, ["json"]);
22280
22385
  const add = (message) => errors.push({ file: rel(file), line: marker.line, rule: "storyboard-src", message });
@@ -22424,7 +22529,7 @@ var init_mockups = __esm({
22424
22529
 
22425
22530
  // ../cli/src/commands/spec-write.ts
22426
22531
  import fs66 from "node:fs";
22427
- import path69 from "node:path";
22532
+ import path70 from "node:path";
22428
22533
  function cmdSpecWrite(id, askedName, flags) {
22429
22534
  const agent = findSpecAgent(askedName);
22430
22535
  if (!agent) die(notAnAgent(askedName), { kind: "no-such-spec-agent", specAgent: askedName });
@@ -22432,7 +22537,7 @@ function cmdSpecWrite(id, askedName, flags) {
22432
22537
  const section = readSection(flags.file, flags.text);
22433
22538
  const found2 = locate(id);
22434
22539
  if (!found2) die(`no task with id ${id} under ${rel(TODO)}`, { kind: "card-not-found", id });
22435
- const file = found2.kind === "group" ? path69.join(found2.target, "root.md") : found2.target;
22540
+ const file = found2.kind === "group" ? path70.join(found2.target, "root.md") : found2.target;
22436
22541
  const { meta, body } = parseFrontmatter(fs66.readFileSync(file, "utf8"));
22437
22542
  if (!meta) die(`${rel(file)} has no frontmatter \u2014 run \`migrate\` first`);
22438
22543
  const { body: next, replaced } = splice(body, name, section);
@@ -23023,7 +23128,7 @@ __export(board_exports, {
23023
23128
  });
23024
23129
  import crypto4 from "node:crypto";
23025
23130
  import fs68 from "node:fs";
23026
- import path70 from "node:path";
23131
+ import path71 from "node:path";
23027
23132
  async function dispatch(move2, cmd, args, opts, cli) {
23028
23133
  const ctx = ctxOf(cmd, cli.program);
23029
23134
  const data = await runAction(ctx, {}, async () => {
@@ -23092,7 +23197,7 @@ async function cloudCard(task, opts) {
23092
23197
  const file = creationReceipt(`${checked.value}:${requestId}`, slot);
23093
23198
  const journal = readCreation(file, said3);
23094
23199
  if (!journal.made) {
23095
- const key = `akb-${path70.basename(file, ".json").slice(0, 40)}`;
23200
+ const key = `akb-${path71.basename(file, ".json").slice(0, 40)}`;
23096
23201
  const made = await createProjectTask(checked.value, journal.said, key);
23097
23202
  if (!made.ok) throw new BoardError(creationError(`Cloud did not make the task: ${made.error}`, triage), { kind: "refused" });
23098
23203
  journal.made = made.value;
@@ -23238,15 +23343,15 @@ var init_board2 = __esm({
23238
23343
 
23239
23344
  // ../cli/src/lib/board-cli.ts
23240
23345
  import fs69 from "node:fs";
23241
- import path71 from "node:path";
23346
+ import path72 from "node:path";
23242
23347
  function useBoard(found2, dirNamed) {
23243
- if (!found2.named && found2.board === path71.join(found2.root, "docs", "kanban")) setBoardRoot(found2.root, dirNamed);
23348
+ if (!found2.named && found2.board === path72.join(found2.root, "docs", "kanban")) setBoardRoot(found2.root, dirNamed);
23244
23349
  else setBoardDir(found2.board, found2.root);
23245
23350
  useProjectState();
23246
23351
  }
23247
23352
  function whole(found2, move2) {
23248
23353
  if (move2 === MAKES_A_BOARD) return found2;
23249
- if (!fs69.existsSync(path71.join(found2.board, "todo"))) {
23354
+ if (!fs69.existsSync(path72.join(found2.board, "todo"))) {
23250
23355
  die(`the board in ${found2.board} has no todo/ \u2014 run \`init\` to add what is missing.`, {
23251
23356
  kind: "board-incomplete",
23252
23357
  dir: found2.root
@@ -23255,13 +23360,13 @@ function whole(found2, move2) {
23255
23360
  return found2;
23256
23361
  }
23257
23362
  function findBoardUpward(from, move2) {
23258
- let dir = path71.resolve(from);
23363
+ let dir = path72.resolve(from);
23259
23364
  for (; ; ) {
23260
- if (hasBoard(dir)) return { root: dir, board: path71.join(dir, "docs", "kanban") };
23365
+ if (hasBoard(dir)) return { root: dir, board: path72.join(dir, "docs", "kanban") };
23261
23366
  if (isBoardDir(dir)) return { root: projectRootOf(dir), board: dir };
23262
- const kept2 = path71.join(dir, ".akb", "kanban");
23263
- if (move2 !== MAKES_A_BOARD && fs69.existsSync(path71.join(kept2, "todo"))) return { root: dir, board: kept2 };
23264
- const up = path71.dirname(dir);
23367
+ const kept2 = path72.join(dir, ".akb", "kanban");
23368
+ if (move2 !== MAKES_A_BOARD && fs69.existsSync(path72.join(kept2, "todo"))) return { root: dir, board: kept2 };
23369
+ const up = path72.dirname(dir);
23265
23370
  if (up === dir) return null;
23266
23371
  dir = up;
23267
23372
  }
@@ -23269,7 +23374,7 @@ function findBoardUpward(from, move2) {
23269
23374
  function resolveBoard(move2, { board: board2, dir, cwd, installHint }) {
23270
23375
  const named2 = board2 ?? boardFromEnv();
23271
23376
  if (named2) {
23272
- const folder = path71.resolve(cwd, named2);
23377
+ const folder = path72.resolve(cwd, named2);
23273
23378
  if (!fs69.existsSync(folder) && move2 !== MAKES_A_BOARD) {
23274
23379
  die(`no such folder: ${folder}`, { kind: "no-such-folder", dir: folder });
23275
23380
  }
@@ -23282,24 +23387,24 @@ function resolveBoard(move2, { board: board2, dir, cwd, installHint }) {
23282
23387
  return { root: projectRootOf(folder), board: folder, named: true };
23283
23388
  }
23284
23389
  if (dir !== null) {
23285
- const root = path71.resolve(dir);
23390
+ const root = path72.resolve(dir);
23286
23391
  if (!fs69.existsSync(root)) die(`no such folder: ${root}`, { kind: "no-such-folder", dir: root });
23287
- const kept2 = path71.join(root, ".akb", "kanban");
23288
- if (!hasBoard(root) && move2 !== MAKES_A_BOARD && fs69.existsSync(path71.join(kept2, "todo"))) return whole({ root, board: kept2 }, move2);
23392
+ const kept2 = path72.join(root, ".akb", "kanban");
23393
+ if (!hasBoard(root) && move2 !== MAKES_A_BOARD && fs69.existsSync(path72.join(kept2, "todo"))) return whole({ root, board: kept2 }, move2);
23289
23394
  if (!hasBoard(root) && move2 !== MAKES_A_BOARD) {
23290
23395
  die(`no board in ${root} \u2014 it has no docs/kanban/. ${installHint}\u3002`, {
23291
23396
  kind: "no-board",
23292
23397
  dir: root
23293
23398
  });
23294
23399
  }
23295
- return whole({ root, board: path71.join(root, "docs", "kanban") }, move2);
23400
+ return whole({ root, board: path72.join(root, "docs", "kanban") }, move2);
23296
23401
  }
23297
23402
  const found2 = findBoardUpward(cwd, move2);
23298
23403
  if (found2) return whole(found2, move2);
23299
- if (move2 === MAKES_A_BOARD) return { root: path71.resolve(cwd), board: path71.join(path71.resolve(cwd), "docs", "kanban") };
23404
+ if (move2 === MAKES_A_BOARD) return { root: path72.resolve(cwd), board: path72.join(path72.resolve(cwd), "docs", "kanban") };
23300
23405
  die(
23301
- `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`,
23302
- { kind: "no-board", dir: path71.resolve(cwd) }
23406
+ `no board here. Looked in ${path72.resolve(cwd)} and every folder above it for docs/kanban/. ${installHint}\uFF0C\u6216\u7528 --board \u6307\u5B9A\u5DF2\u6709\u770B\u677F\u3002`,
23407
+ { kind: "no-board", dir: path72.resolve(cwd) }
23303
23408
  );
23304
23409
  }
23305
23410
  function editDistance(a, b) {
@@ -23381,8 +23486,8 @@ var init_board_cli = __esm({
23381
23486
  init_io();
23382
23487
  init_pointer();
23383
23488
  MAKES_A_BOARD = "init";
23384
- hasBoard = (dir) => fs69.existsSync(path71.join(dir, "docs", "kanban"));
23385
- isBoardDir = (dir) => fs69.existsSync(path71.join(dir, "todo")) && fs69.existsSync(path71.join(dir, "config.md"));
23489
+ hasBoard = (dir) => fs69.existsSync(path72.join(dir, "docs", "kanban"));
23490
+ isBoardDir = (dir) => fs69.existsSync(path72.join(dir, "todo")) && fs69.existsSync(path72.join(dir, "config.md"));
23386
23491
  boardFromEnv = () => {
23387
23492
  const named2 = process.env.AI4KANBAN_BOARD?.trim();
23388
23493
  return named2 ? named2 : null;
@@ -23418,7 +23523,7 @@ var init_board3 = __esm({
23418
23523
  var card_chat_default;
23419
23524
  var init_card_chat = __esm({
23420
23525
  "../cli/src/guide/card-chat.md"() {
23421
- card_chat_default = "# Answer a card's conversation\n\n- **Plain words**: answer directly in the user's language, using everyday words.\n- **Use judgment**: use context to decide when to answer, revise the card or record an answer, and handle all intents in the message without reconfirming clear decisions.\n- **Apply changes**: follow `akb card revise <id> --print \"<the change>\"` for revisions and `akb card resolve <id> --print` for answers to open questions, then validate the card; reuse flows already in context.\n- **Stay on this card**: revise this card's requirements or outcome; suggest a new task for independent work, and change no other card.\n- **Build from review**: edit drafts and previews here, never project code; when asked to build, direct the user to approve on the review page or answer remaining open questions first.\n- **Questions**: follow `akb guide update-questions` for decisions that need the user.\n- **Report results**: briefly state actual changes or failures, identifying recorded answers; pure answers need no change report.\n- **Write no memory**: neither this conversation nor any flow it starts may write memory, regardless of that flow's own instructions.\n- **Hide machinery**: name actions by their UI labels and keep commands and internal instructions out of replies.\n";
23526
+ card_chat_default = "# Answer a card's conversation\n\n- **Plain words**: answer directly using everyday words.\n- **Use judgment**: use context to decide when to answer, revise the card or record an answer, and handle all intents in the message without reconfirming clear decisions.\n- **Apply changes**: follow `akb card revise <id> --print \"<the change>\"` for revisions and `akb card resolve <id> --print` for answers to open questions, then validate the card; reuse flows already in context.\n- **Stay on this card**: revise this card's requirements or outcome; suggest a new task for independent work, and change no other card.\n- **Build from review**: edit drafts and previews here, never project code; when asked to build, direct the user to approve on the review page or answer remaining open questions first.\n- **Questions**: follow `akb guide update-questions` for decisions that need the user.\n- **Report results**: briefly state actual changes or failures, identifying recorded answers; pure answers need no change report.\n- **Write no memory**: neither this conversation nor any flow it starts may write memory, regardless of that flow's own instructions.\n- **Hide machinery**: name actions by their UI labels and keep commands and internal instructions out of replies.\n";
23422
23527
  }
23423
23528
  });
23424
23529
 
@@ -23442,7 +23547,7 @@ var init_describe_project = __esm({
23442
23547
  var discuss_goal_default;
23443
23548
  var init_discuss_goal = __esm({
23444
23549
  "../cli/src/guide/discuss-goal.md"() {
23445
- discuss_goal_default = "# \u76EE\u6807\u8BA8\u8BBA\n\n\u50CF\u6CA1\u6709\u989D\u5916\u4E0A\u4E0B\u6587\u65F6\u4E00\u6837\u4E0E\u7528\u6237\u6C9F\u901A\uFF1B\u5B9E\u9645\u6784\u5EFA\u524D\u7684\u4EFB\u4F55\u5DE5\u4F5C\u90FD\u53EF\u4EE5\u505A\u3002\n\n- **\u6784\u5EFA\u8FB9\u754C**\uFF1A\u9700\u8981\u5B9E\u9645\u6784\u5EFA\u65F6\uFF0C\u7528 `akb raw goal` \u4FDD\u5B58\u6216\u66F4\u65B0\u76EE\u6807\uFF08\u53C2\u89C1 `akb raw goal --help`\uFF09\uFF0C\u7136\u540E\u505C\u6B62\uFF1B\u65E2\u6709\u6784\u5EFA\u6D41\u7A0B\u6CBF\u5F53\u524D\u4F1A\u8BDD\u7EE7\u7EED\uFF0C\u5E76\u9075\u5FAA\u9879\u76EE\u65E2\u6709\u6784\u5EFA\u8981\u6C42\u3002\n- **\u7F3A\u5931\u4FE1\u606F**\uFF1A\u6682\u65F6\u65E0\u6CD5\u5F62\u6210\u76EE\u6807\u65F6\uFF0C\u53EF\u4EE5\u5411\u7528\u6237\u8BE2\u95EE\u7F3A\u5931\u4FE1\u606F\u3002\n\n## Goals\n\n- **\u76EE\u6807\u683C\u5F0F**\uFF1A\u6BCF\u4E2A\u76EE\u6807\u5305\u542B\u6807\u9898\u3001\u7528\u7528\u6237\u8BED\u8A00\u5199\u7684\u4E0D\u8D85\u8FC7 150 \u8BCD\u7684\u7ED3\u679C\uFF0C\u4EE5\u53CA\u5217\u51FA\u53EF\u9A8C\u8BC1\u9A8C\u6536\u9879\u7684 `## Done when`\uFF0C\u4E0D\u542B\u6280\u672F\u8BBE\u8BA1\u3002\n- **\u72EC\u7ACB\u4EA4\u4ED8**\uFF1A\u4E0D\u540C\u4EA4\u4ED8\u7269\u5206\u522B\u4FDD\u5B58\u4E3A\u76EE\u6807\uFF0C\u7528 `akb raw goal drop --id <goal>` \u5220\u9664\u88AB\u66FF\u6362\u7684\u76EE\u6807\u3002\n- **\u81EA\u884C\u5224\u65AD**\uFF1A\u81EA\u884C\u51B3\u5B9A\u80FD\u591F\u5224\u65AD\u7684\u7ED3\u679C\u95EE\u9898\uFF0C\u5E76\u8BF4\u660E\u6240\u4F5C\u51B3\u5B9A\uFF1B\u53EA\u95EE\u7528\u6237\u624D\u80FD\u56DE\u7B54\u4E14\u4F1A\u6539\u53D8\u7ED3\u679C\u7684\u95EE\u9898\uFF0C\u540C\u65F6\u7ED9\u51FA\u63A8\u8350\u9009\u9879\u3002\n- **\u76F4\u767D\u8868\u8FBE**\uFF1A\u7528\u7528\u6237\u7684\u8BED\u8A00\u548C\u65E5\u5E38\u7528\u8BED\u76F4\u63A5\u56DE\u7B54\u3002\n- **\u9690\u85CF\u5185\u90E8\u64CD\u4F5C**\uFF1A\u4E0D\u63D0\u53CA\u8BA1\u5212\u3001\u6587\u4EF6\u3001\u8DEF\u5F84\u3001\u547D\u4EE4\u6216\u4F7F\u7528\u7684\u5DE5\u5177\u4E0E\u8FDE\u63A5\u5668\uFF1B\u4EE5\u6309\u94AE\u540D\u79F0\u6307\u4EE3\u64CD\u4F5C\u3002\n";
23550
+ discuss_goal_default = "# Discuss a goal\n\nTalk with the user as you would without extra context; any work before actual building is allowed.\n\n- **Build boundary**: when actual building is needed, save or update the goal with `akb raw goal` (see `akb raw goal --help`), then stop; an existing build continues in its current session under the project's build requirements.\n- **Missing information**: ask the user for information needed to form a goal.\n\n## Goals\n\n- **Goal format**: each goal has a title, an outcome of at most 150 words, and a `## Done when` section listing verifiable acceptance criteria, without technical design.\n- **Independent deliverables**: save separate deliverables as separate goals; remove a replaced goal with `akb raw goal drop --id <goal>`.\n- **Use judgment**: settle outcome decisions you can and explain them; ask only for decisions the user must make that change the result, with a recommendation.\n- **Plain words**: answer directly using everyday words.\n- **Hide machinery**: omit plans, files, paths, commands, tools, and connectors from replies; refer to actions by their button labels.\n";
23446
23551
  }
23447
23552
  });
23448
23553
 
@@ -23650,7 +23755,7 @@ var init_guide = __esm({
23650
23755
 
23651
23756
  // ../cli/src/lib/agent/approve.ts
23652
23757
  import fs75 from "node:fs";
23653
- import path77 from "node:path";
23758
+ import path78 from "node:path";
23654
23759
  function approvalRequest(req) {
23655
23760
  const id = req.id;
23656
23761
  const card = id === void 0 ? null : findCard(id);
@@ -23668,7 +23773,7 @@ function advancePhase(id) {
23668
23773
  const card = findCard(id);
23669
23774
  const found2 = locate(id);
23670
23775
  if (!card || !found2 || !awaitingApproval(card)) return { error: `#${id} is no longer waiting on approval.` };
23671
- const file = found2.kind === "group" ? path77.join(found2.target, "root.md") : found2.target;
23776
+ const file = found2.kind === "group" ? path78.join(found2.target, "root.md") : found2.target;
23672
23777
  const { meta, body } = parseFrontmatter(fs75.readFileSync(file, "utf8"));
23673
23778
  if (!meta) return { error: `#${id} has no frontmatter` };
23674
23779
  meta.phase = card.stage + 1;
@@ -23699,6 +23804,12 @@ async function startRun(req) {
23699
23804
  const cardId2 = Number.isInteger(req.id) ? req.id : null;
23700
23805
  const short2 = workflowRefusal(req) ?? await scheduledRefusal(req);
23701
23806
  if (short2) return short2;
23807
+ const delivery = req.action === "implement" ? deliveryFor(req) : void 0;
23808
+ if (delivery?.landing) return refusal("deliveryHeld", "\u6784\u5EFA\u5DF2\u5B8C\u6210\uFF0C\u7B49\u5F85\u4EA4\u4ED8\u843D\u5730\uFF0C\u4E0D\u4F1A\u91CD\u65B0\u6784\u5EFA\u3002");
23809
+ if (delivery && req.session !== "new") {
23810
+ const previous = readRuns().filter((r) => r.deliveryId === delivery.deliveryId && r.action === "implement").reverse().sort((a, b) => b.startedAt - a.startedAt)[0];
23811
+ if (previous) return startResume(previous.sessionId, { deliveryId: delivery.deliveryId, notes: req.notes });
23812
+ }
23702
23813
  const moved = req.action === "approve" ? advancePhase(cardId2) : null;
23703
23814
  if (moved) return moved;
23704
23815
  return open2(req, sessionId);
@@ -23771,8 +23882,8 @@ function open2(req, sessionId) {
23771
23882
  markSpawned(run2.sessionId, pid);
23772
23883
  return { run: run2, spawned: pid !== void 0 };
23773
23884
  }
23774
- async function startResume(id, requireSession = false) {
23775
- const opened = await openResume(id, requireSession);
23885
+ async function startResume(id, options) {
23886
+ const opened = await openResume(id, options);
23776
23887
  if ("error" in opened) return opened;
23777
23888
  const pid = spawnWatcher(opened.run.sessionId);
23778
23889
  markSpawned(opened.run.sessionId, pid);
@@ -23787,6 +23898,7 @@ var init_start = __esm({
23787
23898
  init_launch();
23788
23899
  init_pictures();
23789
23900
  init_deliveries();
23901
+ init_store();
23790
23902
  init_prompts();
23791
23903
  init_workflows();
23792
23904
  init_sessions();
@@ -23798,10 +23910,10 @@ var init_start = __esm({
23798
23910
 
23799
23911
  // ../cli/src/lib/agent/task-chats.ts
23800
23912
  import fs76 from "node:fs";
23801
- import path78 from "node:path";
23913
+ import path79 from "node:path";
23802
23914
  function taskChatFile(task, home3) {
23803
23915
  if (!TASK.test(task)) throw new Error(`${task} is not a task id.`);
23804
- return path78.join(taskChatsDir(home3), `${task}.jsonl`);
23916
+ return path79.join(taskChatsDir(home3), `${task}.jsonl`);
23805
23917
  }
23806
23918
  function readTaskChat(task, home3) {
23807
23919
  let text5;
@@ -23827,13 +23939,13 @@ var init_task_chats = __esm({
23827
23939
  "use strict";
23828
23940
  init_home();
23829
23941
  TASK = /^[\w-]{1,100}$/;
23830
- taskChatsDir = (home3 = machineHome()) => path78.join(home3, "chats");
23942
+ taskChatsDir = (home3 = machineHome()) => path79.join(home3, "chats");
23831
23943
  }
23832
23944
  });
23833
23945
 
23834
23946
  // ../cli/src/lib/agent/memory-review.ts
23835
23947
  import fs77 from "node:fs";
23836
- import path79 from "node:path";
23948
+ import path80 from "node:path";
23837
23949
  function taskChatsToReview(tasks) {
23838
23950
  const out = [];
23839
23951
  for (const ref of [...new Set(tasks)]) {
@@ -23855,7 +23967,7 @@ function readArchived2(id) {
23855
23967
  const found2 = Number.isInteger(id) && id > 0 ? locateArchived(id) : null;
23856
23968
  if (!found2) return null;
23857
23969
  try {
23858
- const file = found2.kind === "group" ? path79.join(found2.target, "root.md") : found2.target;
23970
+ const file = found2.kind === "group" ? path80.join(found2.target, "root.md") : found2.target;
23859
23971
  const { meta, body } = parseFrontmatter(fs77.readFileSync(file, "utf8"));
23860
23972
  if (!meta) return null;
23861
23973
  return { id, title: meta.title, modules: meta.modules ?? [], rejected: meta.rejected, body };
@@ -23926,9 +24038,9 @@ var init_memory_review = __esm({
23926
24038
 
23927
24039
  // ../cli/src/lib/agent/dismissal-review.ts
23928
24040
  import fs78 from "node:fs";
23929
- import path80 from "node:path";
24041
+ import path81 from "node:path";
23930
24042
  function rejectionsToReview() {
23931
- const dir = path80.join(ARCHIVE, REJECTIONS_TO_REVIEW);
24043
+ const dir = path81.join(ARCHIVE, REJECTIONS_TO_REVIEW);
23932
24044
  if (!fs78.existsSync(dir)) return [];
23933
24045
  const found2 = [];
23934
24046
  for (const file of walkMd(dir)) {
@@ -23991,7 +24103,7 @@ var init_dismissal_review = __esm({
23991
24103
 
23992
24104
  // ../cli/src/lib/agent/flow.ts
23993
24105
  import fs79 from "node:fs";
23994
- import path81 from "node:path";
24106
+ import path82 from "node:path";
23995
24107
  function readCard2(id, home3 = "board") {
23996
24108
  const found2 = home3 === "archive" ? locateArchived(id) : locate(id);
23997
24109
  if (!found2) {
@@ -24000,7 +24112,7 @@ function readCard2(id, home3 = "board") {
24000
24112
  { kind: "card-not-found", id }
24001
24113
  );
24002
24114
  }
24003
- const file = found2.kind === "group" ? path81.join(found2.target, "root.md") : found2.target;
24115
+ const file = found2.kind === "group" ? path82.join(found2.target, "root.md") : found2.target;
24004
24116
  let text5;
24005
24117
  try {
24006
24118
  text5 = fs79.readFileSync(file, "utf8");
@@ -24127,7 +24239,7 @@ function workspaceField(delivery) {
24127
24239
  if (!delivery?.worktree) return [];
24128
24240
  return field("workspace", [
24129
24241
  `write code in ${delivery.worktree} \u2014 this delivery's own worktree, on branch ${delivery.branch}.`,
24130
- 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.`,
24242
+ path82.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.`,
24131
24243
  `every board command names the project's own copy: \`${boardCommandFor()} <command>\`.`,
24132
24244
  `${rel(REPO_ROOT)} is the project \u2014 the card, the memory files and the docs are changed there, not here.`
24133
24245
  ]);
@@ -24499,11 +24611,6 @@ function printFlow(rawReq, program2 = "akb") {
24499
24611
  say("");
24500
24612
  say(rule);
24501
24613
  }
24502
- const language = languageNote();
24503
- if (language) {
24504
- say("");
24505
- say(language);
24506
- }
24507
24614
  return {
24508
24615
  mode: "print",
24509
24616
  action: req.action,
@@ -24591,7 +24698,7 @@ var init_flow = __esm({
24591
24698
  });
24592
24699
 
24593
24700
  // ../cli/src/lib/agent/refine.ts
24594
- import { createHash as createHash5 } from "node:crypto";
24701
+ import { createHash as createHash6 } from "node:crypto";
24595
24702
  function markBoard() {
24596
24703
  try {
24597
24704
  return new Map(allCards().map((card) => [card.id, wroteOf(card)]));
@@ -24728,7 +24835,7 @@ var init_refine2 = __esm({
24728
24835
  };
24729
24836
  MARKER2 = /^<!--\s*agent\s*-->$/;
24730
24837
  asMoved = (body) => body.split("\n").map((line) => line.trimEnd()).filter((line) => line.trim() && !MARKER2.test(line.trim())).sort();
24731
- wroteOf = (card) => createHash5("sha1").update(
24838
+ wroteOf = (card) => createHash6("sha1").update(
24732
24839
  JSON.stringify([
24733
24840
  card.status,
24734
24841
  card.relPath,
@@ -24932,7 +25039,7 @@ async function watchRun(sessionId, resume = startResume) {
24932
25039
  if (active2.startNote) log.write(`[board] ${active2.startNote}
24933
25040
  `);
24934
25041
  const basePrompt = record.formatRepair ? contractRepairPrompt(requestOf(record), record.formatRepair.errors) : record.resumedFrom ? spec.prompt || resumePrompt(record.deliveryId, record.cardId, record.action) : spec.prompt;
24935
- const prompt = inLanguage([basePrompt, discardedCardsPrompt(record.discardedCards)].filter(Boolean).join("\n\n"));
25042
+ const prompt = [basePrompt, discardedCardsPrompt(record.discardedCards)].filter(Boolean).join("\n\n");
24936
25043
  const before = markBoard();
24937
25044
  const sources = snapshotSpecs();
24938
25045
  const wasBroken = new Set(boardComplaints());
@@ -24944,8 +25051,8 @@ async function watchRun(sessionId, resume = startResume) {
24944
25051
  const silenceMs = silenceFor * 6e4;
24945
25052
  void refreshCatalog();
24946
25053
  const client = active2.client;
24947
- const restartBase = !client || record.formatRepair ? void 0 : record.resumedFrom ? restartPrompt(requestOf(record), record.deliveryId) : record.continues && !record.continues.fork ? spec.prompt : void 0;
24948
- const restart = restartBase ? inLanguage([restartBase, discardedCardsPrompt(record.discardedCards)].filter(Boolean).join("\n\n")) : void 0;
25054
+ const restartBase = !client || record.formatRepair || record.action === "implement" && record.resumedFrom ? void 0 : record.resumedFrom ? restartPrompt(requestOf(record), record.deliveryId) : record.continues && !record.continues.fork ? spec.prompt : void 0;
25055
+ const restart = restartBase ? [restartBase, discardedCardsPrompt(record.discardedCards)].filter(Boolean).join("\n\n") : void 0;
24949
25056
  const stdinPrompt = client ? void 0 : active2.stdinPrompt;
24950
25057
  const stdio = [client || stdinPrompt ? "pipe" : "ignore", "pipe", "pipe"];
24951
25058
  let child;
@@ -25480,7 +25587,6 @@ var init_watch = __esm({
25480
25587
  init_creation();
25481
25588
  init_wire();
25482
25589
  init_prompts();
25483
- init_language();
25484
25590
  init_resolve();
25485
25591
  init_runner();
25486
25592
  init_retry();
@@ -25990,7 +26096,7 @@ async function testAgent(args) {
25990
26096
 
25991
26097
  // ../cli/src/commands/cloud.ts
25992
26098
  import fs71 from "node:fs";
25993
- import path73 from "node:path";
26099
+ import path74 from "node:path";
25994
26100
 
25995
26101
  // ../cli/src/lib/cloud/avatar.ts
25996
26102
  init_session();
@@ -26039,10 +26145,10 @@ init_home();
26039
26145
  init_config();
26040
26146
  init_session();
26041
26147
  import fs70 from "node:fs";
26042
- import path72 from "node:path";
26148
+ import path73 from "node:path";
26043
26149
  var FRESH_MS = 10 * 6e4;
26044
26150
  var GRACE_MS = 7 * 24 * 60 * 6e4;
26045
- var heldFile = () => path72.join(machineHome(), "pro.json");
26151
+ var heldFile = () => path73.join(machineHome(), "pro.json");
26046
26152
  function forgetPro() {
26047
26153
  fs70.rmSync(heldFile(), { force: true });
26048
26154
  }
@@ -26221,7 +26327,7 @@ async function cloudImage(opts, program2) {
26221
26327
  const prompt = opts.prompt.trim();
26222
26328
  if (!prompt) usage("Missing the prompt.", line);
26223
26329
  const references = opts.refs.map((file) => {
26224
- const type = REF_TYPES[path73.extname(file).toLowerCase()];
26330
+ const type = REF_TYPES[path74.extname(file).toLowerCase()];
26225
26331
  if (!type) usage(`--ref ${file}: use a PNG, JPEG or WebP file.`, line);
26226
26332
  try {
26227
26333
  return `data:${type};base64,${fs71.readFileSync(file).toString("base64")}`;
@@ -26330,7 +26436,7 @@ async function generate(g) {
26330
26436
  refuse(g.failed, g.fallback);
26331
26437
  }
26332
26438
  const out = g.outFor?.(res, g.out) ?? g.out;
26333
- fs71.mkdirSync(path73.dirname(path73.resolve(out)), { recursive: true });
26439
+ fs71.mkdirSync(path74.dirname(path74.resolve(out)), { recursive: true });
26334
26440
  fs71.writeFileSync(out, Buffer.from(await res.arrayBuffer()));
26335
26441
  say(`${out} (${g.label(res)})`);
26336
26442
  return { out };
@@ -26339,22 +26445,22 @@ async function generate(g) {
26339
26445
  // ../cli/src/commands/cloud-import.ts
26340
26446
  import crypto5 from "node:crypto";
26341
26447
  import fs73 from "node:fs";
26342
- import path75 from "node:path";
26448
+ import path76 from "node:path";
26343
26449
 
26344
26450
  // ../cloud/src/board-files.ts
26345
26451
  var ROOTS = ["todo/", ".archive/", "memory/", "workflows/", "triage/", "assets/", "rules/"];
26346
26452
  var SETTINGS = ["modules.md", "config.md"];
26347
- var isSettingsPath = (path87) => SETTINGS.includes(path87) || /^rules\/(?:workflows\/)?[^/]+\.md$/.test(path87);
26453
+ var isSettingsPath = (path88) => SETTINGS.includes(path88) || /^rules\/(?:workflows\/)?[^/]+\.md$/.test(path88);
26348
26454
  var BOARD_FILE_LIMIT = 256 * 1024;
26349
- function isBoardPath(path87) {
26350
- if (!path87 || path87.length > 500 || path87.startsWith("/") || path87.includes("\\") || path87.includes("\0")) return false;
26351
- if (path87.split("/").some((part) => !part || part === "." || part === "..")) return false;
26352
- if (path87 === "todo/README.md" || path87.split("/").includes(".sync")) return false;
26353
- if (path87.startsWith("workflows/")) return true;
26354
- if (SETTINGS.includes(path87)) return true;
26355
- if (path87.startsWith("rules/")) return isSettingsPath(path87);
26356
- if (path87.startsWith("assets/")) return path87.endsWith(".json");
26357
- return ROOTS.some((root) => path87.startsWith(root)) && path87.endsWith(".md");
26455
+ function isBoardPath(path88) {
26456
+ if (!path88 || path88.length > 500 || path88.startsWith("/") || path88.includes("\\") || path88.includes("\0")) return false;
26457
+ if (path88.split("/").some((part) => !part || part === "." || part === "..")) return false;
26458
+ if (path88 === "todo/README.md" || path88.split("/").includes(".sync")) return false;
26459
+ if (path88.startsWith("workflows/")) return true;
26460
+ if (SETTINGS.includes(path88)) return true;
26461
+ if (path88.startsWith("rules/")) return isSettingsPath(path88);
26462
+ if (path88.startsWith("assets/")) return path88.endsWith(".json");
26463
+ return ROOTS.some((root) => path88.startsWith(root)) && path88.endsWith(".md");
26358
26464
  }
26359
26465
  var workflowFolder = (id) => id.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
26360
26466
 
@@ -26459,7 +26565,7 @@ init_bundled();
26459
26565
  init_bundled();
26460
26566
  init_parse();
26461
26567
  import fs72 from "node:fs";
26462
- import path74 from "node:path";
26568
+ import path75 from "node:path";
26463
26569
  function sharedMemory(board2) {
26464
26570
  const declared = /* @__PURE__ */ new Map();
26465
26571
  const take = (folder, text5, builtIn) => {
@@ -26471,7 +26577,7 @@ function sharedMemory(board2) {
26471
26577
  const at = /^([^/]+)\/AGENT\.md$/.exec(key);
26472
26578
  if (at) take(at[1], text5, true);
26473
26579
  }
26474
- const root = path74.join(board2, "agents");
26580
+ const root = path75.join(board2, "agents");
26475
26581
  let folders2 = [];
26476
26582
  try {
26477
26583
  folders2 = fs72.readdirSync(root, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name);
@@ -26480,7 +26586,7 @@ function sharedMemory(board2) {
26480
26586
  for (const folder of folders2.sort()) {
26481
26587
  let text5 = null;
26482
26588
  try {
26483
- text5 = fs72.readFileSync(path74.join(root, folder, "AGENT.md"), "utf8");
26589
+ text5 = fs72.readFileSync(path75.join(root, folder, "AGENT.md"), "utf8");
26484
26590
  } catch {
26485
26591
  }
26486
26592
  take(folder, text5, false);
@@ -26576,17 +26682,17 @@ function files(dir) {
26576
26682
  const out = [];
26577
26683
  for (const entry of fs73.readdirSync(dir, { withFileTypes: true })) {
26578
26684
  if (entry.name.startsWith(".")) continue;
26579
- const full = path75.join(dir, entry.name);
26685
+ const full = path76.join(dir, entry.name);
26580
26686
  if (entry.isDirectory()) out.push(...files(full));
26581
26687
  else if (entry.isFile()) out.push(full);
26582
26688
  }
26583
26689
  return out;
26584
26690
  }
26585
- var posix2 = (p) => p.split(path75.sep).join("/");
26691
+ var posix2 = (p) => p.split(path76.sep).join("/");
26586
26692
  function openCards() {
26587
26693
  const out = [];
26588
26694
  for (const file of walkMd(TODO)) {
26589
- const rel2 = posix2(path75.relative(TODO, file));
26695
+ const rel2 = posix2(path76.relative(TODO, file));
26590
26696
  if (rel2 === "README.md") continue;
26591
26697
  const parts = rel2.split("/");
26592
26698
  const text5 = read(file);
@@ -26620,14 +26726,14 @@ function bodyProblems(text5) {
26620
26726
  return out;
26621
26727
  }
26622
26728
  function packWorkflow(flow) {
26623
- const own = path75.join(WORKFLOWS, flow.id);
26729
+ const own = path76.join(WORKFLOWS, flow.id);
26624
26730
  const out = {};
26625
- for (const file of files(own)) out[posix2(path75.relative(own, file))] = read(file);
26731
+ for (const file of files(own)) out[posix2(path76.relative(own, file))] = read(file);
26626
26732
  const named2 = [...flow.previews, ...flow.land, ...flow.scheduled].map((h) => h.agent).filter((a) => !LAND_ROLES.includes(a));
26627
26733
  for (const agent of new Set(named2)) {
26628
- const dir = path75.join(AGENTS, agent);
26629
- if (fs73.existsSync(path75.join(dir, "AGENT.md"))) {
26630
- for (const file of files(dir)) out[`agents/${agent}/${posix2(path75.relative(dir, file))}`] = read(file);
26734
+ const dir = path76.join(AGENTS, agent);
26735
+ if (fs73.existsSync(path76.join(dir, "AGENT.md"))) {
26736
+ for (const file of files(dir)) out[`agents/${agent}/${posix2(path76.relative(dir, file))}`] = read(file);
26631
26737
  continue;
26632
26738
  }
26633
26739
  for (const [key, text5] of Object.entries(BUNDLED_AGENT_FILES)) {
@@ -26679,8 +26785,8 @@ function draftsOf(text5) {
26679
26785
  const at = /^\.(assets|mockups)\/(.+)$/.exec(name);
26680
26786
  if (!at) continue;
26681
26787
  const base = at[1] === "assets" ? ASSETS : MOCKUPS;
26682
- const file = path75.resolve(base, at[2]);
26683
- if (file.startsWith(base + path75.sep) && fs73.statSync(file, { throwIfNoEntry: false })?.isFile()) out.set(name, file);
26788
+ const file = path76.resolve(base, at[2]);
26789
+ if (file.startsWith(base + path76.sep) && fs73.statSync(file, { throwIfNoEntry: false })?.isFile()) out.set(name, file);
26684
26790
  }
26685
26791
  return [...out].map(([name, file]) => ({ name, file }));
26686
26792
  }
@@ -26714,7 +26820,7 @@ async function uploadDrafts(task, drafts) {
26714
26820
  const assets = {};
26715
26821
  for (const { name, file } of drafts) {
26716
26822
  if (!card.body.includes(name)) continue;
26717
- const ext = path75.extname(file).toLowerCase();
26823
+ const ext = path76.extname(file).toLowerCase();
26718
26824
  const type = TYPES[ext] ?? "application/octet-stream";
26719
26825
  const data = fs73.readFileSync(file);
26720
26826
  const artifact = await api("POST", `/v1/tasks/${task}/artifacts`, { type: kindOf(ext), name: name.slice(0, 200) });
@@ -26762,7 +26868,7 @@ async function run(asked, dryRun) {
26762
26868
  const board2 = crypto5.createHash("sha256").update(fs73.realpathSync(KANBAN)).digest("hex").slice(0, 16);
26763
26869
  const cards = openCards();
26764
26870
  const problems = [];
26765
- const where = (file) => posix2(path75.relative(KANBAN, file));
26871
+ const where = (file) => posix2(path76.relative(KANBAN, file));
26766
26872
  const own = workflows().filter((w) => !w.builtIn);
26767
26873
  const packed = /* @__PURE__ */ new Map();
26768
26874
  for (const flow of own) {
@@ -26906,7 +27012,7 @@ function leftLocal(own) {
26906
27012
  const triage = count4(TRIAGE);
26907
27013
  if (triage) out.push(`triage/ (${triage})`);
26908
27014
  if (fs73.existsSync(DELIVERIES)) out.push("deliveries/");
26909
- if (fs73.existsSync(path75.join(KANBAN, "metrics.csv"))) out.push("metrics.csv");
27015
+ if (fs73.existsSync(path76.join(KANBAN, "metrics.csv"))) out.push("metrics.csv");
26910
27016
  const named2 = new Set(own.flatMap((w) => [...w.previews, ...w.land, ...w.scheduled].map((h) => h.agent)));
26911
27017
  if (fs73.existsSync(AGENTS)) {
26912
27018
  for (const e of fs73.readdirSync(AGENTS, { withFileTypes: true })) {
@@ -26926,9 +27032,9 @@ init_config();
26926
27032
  init_session();
26927
27033
  init_io();
26928
27034
  init_paths();
26929
- import { createHash as createHash4 } from "node:crypto";
27035
+ import { createHash as createHash5 } from "node:crypto";
26930
27036
  import fs74 from "node:fs";
26931
- import path76 from "node:path";
27037
+ import path77 from "node:path";
26932
27038
  var SLUG = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
26933
27039
  var CAPTURED = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:\d{2})$/;
26934
27040
  var MAX_BYTES3 = 50 * 1024 * 1024;
@@ -27027,8 +27133,8 @@ This template has no preview.`);
27027
27133
  const written = [];
27028
27134
  for (const p of previews) {
27029
27135
  const bytes3 = Buffer.from(await (await ask(`/${at}/previews/${route(p.path)}`, {}, WITHOUT)).arrayBuffer());
27030
- const file = path76.basename(p.path);
27031
- const out = path76.join(opts.to, file.startsWith(`${name}-`) ? file : `${name}-${file}`);
27136
+ const file = path77.basename(p.path);
27137
+ const out = path77.join(opts.to, file.startsWith(`${name}-`) ? file : `${name}-${file}`);
27032
27138
  fs74.mkdirSync(opts.to, { recursive: true });
27033
27139
  fs74.writeFileSync(out, bytes3);
27034
27140
  written.push(out);
@@ -27043,15 +27149,15 @@ async function templateGet(name, opts, program2) {
27043
27149
  const { template } = await asJson(await ask(`/${at}`, {}, WITHOUT));
27044
27150
  const fetched = [];
27045
27151
  for (const f of template.files) {
27046
- const out = path76.resolve(to, f.path);
27047
- if (!out.startsWith(path76.resolve(to) + path76.sep)) refuse2(T.corrupt, WITHOUT);
27152
+ const out = path77.resolve(to, f.path);
27153
+ if (!out.startsWith(path77.resolve(to) + path77.sep)) refuse2(T.corrupt, WITHOUT);
27048
27154
  const bytes3 = Buffer.from(await (await ask(`/${at}/files/${route(f.path)}`, {}, WITHOUT)).arrayBuffer());
27049
- if (bytes3.byteLength !== f.size || createHash4("sha256").update(bytes3).digest("hex") !== f.sha256) {
27155
+ if (bytes3.byteLength !== f.size || createHash5("sha256").update(bytes3).digest("hex") !== f.sha256) {
27050
27156
  refuse2(T.corrupt, WITHOUT);
27051
27157
  }
27052
27158
  fetched.push({ out, bytes: bytes3 });
27053
27159
  }
27054
- const taken = fetched.filter((f) => fs74.existsSync(f.out)).map((f) => path76.relative(to, f.out));
27160
+ const taken = fetched.filter((f) => fs74.existsSync(f.out)).map((f) => path77.relative(to, f.out));
27055
27161
  if (taken.length > 0) {
27056
27162
  refuse2({
27057
27163
  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`,
@@ -27059,7 +27165,7 @@ async function templateGet(name, opts, program2) {
27059
27165
  });
27060
27166
  }
27061
27167
  for (const f of fetched) {
27062
- fs74.mkdirSync(path76.dirname(f.out), { recursive: true });
27168
+ fs74.mkdirSync(path77.dirname(f.out), { recursive: true });
27063
27169
  fs74.writeFileSync(f.out, f.bytes);
27064
27170
  }
27065
27171
  say(`\u5DF2\u53D6\u56DE ${name}\uFF1A${fetched.length} \u4E2A\u6587\u4EF6\uFF0C\u4F4D\u4E8E ${to}
@@ -27078,7 +27184,7 @@ async function templateSource(name, opts) {
27078
27184
  say(`evidence: ${e.path}`);
27079
27185
  continue;
27080
27186
  }
27081
- const out = path76.join(opts.to, `${name}-${path76.basename(e.path)}`);
27187
+ const out = path77.join(opts.to, `${name}-${path77.basename(e.path)}`);
27082
27188
  fs74.mkdirSync(opts.to, { recursive: true });
27083
27189
  fs74.writeFileSync(out, Buffer.from(await (await ask(`/${at}/evidence/${route(e.path)}`)).arrayBuffer()));
27084
27190
  written.push(out);
@@ -27087,16 +27193,16 @@ async function templateSource(name, opts) {
27087
27193
  return { name, source, evidence: written.length > 0 ? written : evidence.map((e) => e.path) };
27088
27194
  }
27089
27195
  async function templatePublish(dir, opts) {
27090
- const root = path76.resolve(dir);
27091
- const manifest = path76.join(root, "template.md");
27196
+ const root = path77.resolve(dir);
27197
+ const manifest = path77.join(root, "template.md");
27092
27198
  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.");
27093
27199
  const meta = readTemplateMeta(fs74.readFileSync(manifest, "utf8"));
27094
27200
  const files2 = walk2(root);
27095
- const total = files2.reduce((sum, f) => sum + fs74.statSync(path76.join(root, f)).size, 0);
27201
+ const total = files2.reduce((sum, f) => sum + fs74.statSync(path77.join(root, f)).size, 0);
27096
27202
  if (total > MAX_BYTES3) invalid("\u6A21\u677F\u6587\u4EF6\u5408\u8BA1\u8D85\u8FC7 50 MB\u3002", "the template\u2019s files exceed 50 MB.");
27097
27203
  for (const extra of [...opts.preview, ...opts.evidence]) {
27098
27204
  if (!fs74.existsSync(extra)) invalid(`\u627E\u4E0D\u5230\u6587\u4EF6 ${extra}\u3002`, `${extra} does not exist.`);
27099
- if (path76.resolve(extra).startsWith(root + path76.sep)) {
27205
+ if (path77.resolve(extra).startsWith(root + path77.sep)) {
27100
27206
  invalid(
27101
27207
  `${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`,
27102
27208
  `${extra} is inside the template folder; keep previews and evidence outside it.`
@@ -27108,10 +27214,10 @@ async function templatePublish(dir, opts) {
27108
27214
  }
27109
27215
  const form = new FormData();
27110
27216
  form.set("meta", JSON.stringify({ ...meta, replace: !!opts.replace }));
27111
- const add = (kind, name, file) => form.append(`${kind}:${name}`, new Blob([fs74.readFileSync(file)]), path76.basename(name));
27112
- for (const f of files2) add("files", f, path76.join(root, f));
27113
- for (const f of opts.preview) add("previews", path76.basename(f), f);
27114
- for (const f of opts.evidence) add("evidence", path76.basename(f), f);
27217
+ const add = (kind, name, file) => form.append(`${kind}:${name}`, new Blob([fs74.readFileSync(file)]), path77.basename(name));
27218
+ for (const f of files2) add("files", f, path77.join(root, f));
27219
+ for (const f of opts.preview) add("previews", path77.basename(f), f);
27220
+ for (const f of opts.evidence) add("evidence", path77.basename(f), f);
27115
27221
  const done2 = await asJson(await ask("", { method: "POST", body: form }));
27116
27222
  say(
27117
27223
  done2.replaced ? `\u5DF2\u66FF\u6362 ${done2.name}\uFF1A${files2.length} \u4E2A\u6587\u4EF6\u3002
@@ -27127,7 +27233,7 @@ Deleted ${name} from the catalog.`);
27127
27233
  return { name, deleted: true };
27128
27234
  }
27129
27235
  async function templateShowcase(name, dir) {
27130
- const file = path76.join(dir, "showcase.json");
27236
+ const file = path77.join(dir, "showcase.json");
27131
27237
  if (!fs74.existsSync(file)) invalid(`${dir} \u4E2D\u7F3A\u5C11 showcase.json\u3002`, `showcase.json is missing from ${dir}.`);
27132
27238
  let meta;
27133
27239
  try {
@@ -27143,7 +27249,7 @@ async function templateShowcase(name, dir) {
27143
27249
  const form = new FormData();
27144
27250
  form.set("meta", JSON.stringify(meta));
27145
27251
  for (const p of new Set(named2)) {
27146
- const at = path76.join(dir, p);
27252
+ const at = path77.join(dir, p);
27147
27253
  if (!fs74.existsSync(at)) invalid(`\u627E\u4E0D\u5230\u6587\u4EF6 ${at}\u3002`, `${at} does not exist.`);
27148
27254
  form.append(`media:${p}`, new Blob([fs74.readFileSync(at)]), p);
27149
27255
  }
@@ -27154,7 +27260,7 @@ Updated ${name}'s showcase: ${done2.files} files.`);
27154
27260
  }
27155
27261
  function walk2(root, sub = "") {
27156
27262
  const out = [];
27157
- for (const entry of fs74.readdirSync(path76.join(root, sub), { withFileTypes: true })) {
27263
+ for (const entry of fs74.readdirSync(path77.join(root, sub), { withFileTypes: true })) {
27158
27264
  if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
27159
27265
  const rel2 = sub ? `${sub}/${entry.name}` : entry.name;
27160
27266
  if (entry.isDirectory()) out.push(...walk2(root, rel2));
@@ -27439,7 +27545,7 @@ init_opencode();
27439
27545
  init_locate();
27440
27546
  init_store();
27441
27547
  import { spawnSync as spawnSync5 } from "node:child_process";
27442
- import path82 from "node:path";
27548
+ import path83 from "node:path";
27443
27549
  var AGENTS2 = {
27444
27550
  claude: "claude-code",
27445
27551
  codex: "codex",
@@ -27454,10 +27560,10 @@ var AGENTS2 = {
27454
27560
  var RUNTIMES = /* @__PURE__ */ new Set(["node", "bun", "deno"]);
27455
27561
  function agentOf2(args) {
27456
27562
  const words = args.trim().split(/\s+/);
27457
- const first = path82.basename(words[0] ?? "");
27563
+ const first = path83.basename(words[0] ?? "");
27458
27564
  const script = RUNTIMES.has(first) ? words[1] ?? "" : words[0] ?? "";
27459
27565
  if (script.includes("/@anthropic-ai/claude-code/")) return "claude-code";
27460
- return AGENTS2[path82.basename(script).replace(/\.(m?js|cjs|exe)$/, "")];
27566
+ return AGENTS2[path83.basename(script).replace(/\.(m?js|cjs|exe)$/, "")];
27461
27567
  }
27462
27568
  function processChain() {
27463
27569
  if (process.platform === "win32") return [];
@@ -27522,7 +27628,7 @@ init_read();
27522
27628
  init_rules();
27523
27629
  init_board();
27524
27630
  import fs81 from "node:fs";
27525
- import path83 from "node:path";
27631
+ import path84 from "node:path";
27526
27632
  var FOLLOW_MS = 400;
27527
27633
  async function cmdStartRun(action, args, opts, program2 = "akb") {
27528
27634
  const { req, follow: follow2, print } = readRequest(action, args, opts);
@@ -27568,6 +27674,14 @@ async function cmdStartRun(action, args, opts, program2 = "akb") {
27568
27674
  }
27569
27675
  sayBeforeStart(req, program2);
27570
27676
  sayIfHeld(req, program2);
27677
+ const landing = runnable.action === "implement" && runnable.id !== void 0 ? activeDelivery(runnable.id) : void 0;
27678
+ if (landing?.landing) {
27679
+ const previous = readRuns().filter((r) => r.deliveryId === landing.deliveryId).reverse().sort((a, b) => b.startedAt - a.startedAt)[0];
27680
+ if (!previous) die("\u6784\u5EFA\u5DF2\u5B8C\u6210\uFF0C\u7B49\u5F85\u4EA4\u4ED8\u843D\u5730\uFF1B\u6CA1\u6709\u53EF\u8DDF\u968F\u7684\u8FD0\u884C\u8BB0\u5F55\u3002", { kind: "run-refused", action });
27681
+ say(`\u4EA4\u4ED8 ${landing.deliveryId} \u5DF2\u5B8C\u6210\u6784\u5EFA\uFF0C\u7EE7\u7EED\u843D\u5730\u3002`);
27682
+ if (follow2) return followStarted(previous.sessionId, program2, opts.events === true);
27683
+ return { sessionId: previous.sessionId, action, cardId: previous.cardId };
27684
+ }
27571
27685
  const started = action === "refine" ? await startRefinement(req) : await startRun(runnable);
27572
27686
  if ("error" in started) die(started.error, { kind: "run-refused", action });
27573
27687
  const { run: run2, spawned } = started;
@@ -27814,7 +27928,7 @@ async function cmdSubStart(words, opts) {
27814
27928
  let prompt = words.join(" ");
27815
27929
  if (opts.file) {
27816
27930
  try {
27817
- prompt = fs81.readFileSync(path83.resolve(opts.file), "utf8");
27931
+ prompt = fs81.readFileSync(path84.resolve(opts.file), "utf8");
27818
27932
  } catch {
27819
27933
  die(`--file: cannot read ${opts.file}`, { kind: "run-refused" });
27820
27934
  }
@@ -28116,11 +28230,11 @@ init_yaml();
28116
28230
  init_settings2();
28117
28231
  init_paths();
28118
28232
  import fs82 from "node:fs";
28119
- import path84 from "node:path";
28233
+ import path85 from "node:path";
28120
28234
  var TOKEN_KEY = "TRIAGE_ENDPOINT_TOKEN";
28121
28235
  var ENDPOINT_SETTING = "Triage endpoint";
28122
28236
  var LINE2 = /^- \*\*Triage endpoint\*\*\s*[—-]\s*(.+)$/m;
28123
- var boardRel3 = (file) => rel(file).split(path84.sep).join("/");
28237
+ var boardRel3 = (file) => rel(file).split(path85.sep).join("/");
28124
28238
  function signalEndpoint() {
28125
28239
  let text5;
28126
28240
  try {
@@ -28865,7 +28979,7 @@ function declareAgent(program2, cli) {
28865
28979
 
28866
28980
  // ../cli/src/lib/cli/setup.ts
28867
28981
  import fs85 from "node:fs";
28868
- import path86 from "node:path";
28982
+ import path87 from "node:path";
28869
28983
  import { fileURLToPath as fileURLToPath2 } from "node:url";
28870
28984
 
28871
28985
  // ../cli/src/commands/update.ts
@@ -28880,7 +28994,7 @@ init_memory();
28880
28994
  init_yaml2();
28881
28995
  init_version();
28882
28996
  import fs84 from "node:fs";
28883
- import path85 from "node:path";
28997
+ import path86 from "node:path";
28884
28998
  import { spawnSync as spawnSync6 } from "node:child_process";
28885
28999
  var NAME = "wildcrane";
28886
29000
  var REPO = "https://github.com/ai4kanban/ai4kanban";
@@ -28930,9 +29044,9 @@ async function cmdUpdate2(ctx) {
28930
29044
  return { updated: ctx.dir, from, to: SKILL_VERSION };
28931
29045
  }
28932
29046
  async function repairBoard(root, report3, named2) {
28933
- const legacy = path85.join(root, "docs", "kanban");
28934
- const kept2 = path85.join(root, ".akb", "kanban");
28935
- const board2 = named2 ?? (fs84.existsSync(legacy) ? legacy : kept2);
29047
+ const legacy2 = path86.join(root, "docs", "kanban");
29048
+ const kept2 = path86.join(root, ".akb", "kanban");
29049
+ const board2 = named2 ?? (fs84.existsSync(legacy2) ? legacy2 : kept2);
28936
29050
  if (!fs84.existsSync(board2)) {
28937
29051
  report3.notes.push("\u672A\u627E\u5230\u770B\u677F\uFF0C\u8BF7\u901A\u8FC7\u63D2\u4EF6\u8FDE\u63A5\u4E91\u7AEF\u9879\u76EE\u3002");
28938
29052
  return false;
@@ -28964,14 +29078,14 @@ function moveRuntimes(root, board2, report3) {
28964
29078
  }
28965
29079
  }
28966
29080
  function moveLegacyMemory(board2, report3) {
28967
- const memory = path85.join(board2, "memory");
29081
+ const memory = path86.join(board2, "memory");
28968
29082
  const moved = [];
28969
29083
  for (const name of MEMORY_FILES) {
28970
- const from = path85.join(board2, name);
29084
+ const from = path86.join(board2, name);
28971
29085
  const st = statOf(from);
28972
29086
  if (!st || !st.isFile()) continue;
28973
29087
  fs84.mkdirSync(memory, { recursive: true });
28974
- const to = path85.join(memory, name);
29088
+ const to = path86.join(memory, name);
28975
29089
  const there = read3(to);
28976
29090
  if (there === null) {
28977
29091
  fs84.renameSync(from, to);
@@ -28986,19 +29100,19 @@ function moveLegacyMemory(board2, report3) {
28986
29100
  if (moved.length) report3.did.push(`moved the memory set into docs/kanban/memory/: ${moved.join(", ")}`);
28987
29101
  }
28988
29102
  function dropRecordFile(board2) {
28989
- const file = path85.join(board2, "record.csv");
29103
+ const file = path86.join(board2, "record.csv");
28990
29104
  if (!fs84.existsSync(file)) return null;
28991
29105
  fs84.rmSync(file);
28992
29106
  return "removed docs/kanban/record.csv (the planning scores it fed are gone)";
28993
29107
  }
28994
29108
  function dropModuleGoals(board2, report3) {
28995
- const memory = path85.join(board2, "memory");
29109
+ const memory = path86.join(board2, "memory");
28996
29110
  if (!fs84.existsSync(memory)) return;
28997
- const root = read3(path85.join(memory, "goal.md"));
29111
+ const root = read3(path86.join(memory, "goal.md"));
28998
29112
  let dropped = 0;
28999
29113
  for (const entry of fs84.readdirSync(memory, { withFileTypes: true })) {
29000
29114
  if (!entry.isDirectory()) continue;
29001
- const file = path85.join(memory, entry.name, "goal.md");
29115
+ const file = path86.join(memory, entry.name, "goal.md");
29002
29116
  const text5 = read3(file);
29003
29117
  if (text5 === null) continue;
29004
29118
  if (text5 === root || text5.includes(UNFILLED) || !goalWords(text5)) {
@@ -29024,10 +29138,10 @@ var PLAN_LEADS = {
29024
29138
  var GONE_LEADS = ["software-planner", "planner"];
29025
29139
  function planLeads(board2) {
29026
29140
  const leads = { ...PLAN_LEADS };
29027
- const root = path85.join(board2, "workflows");
29141
+ const root = path86.join(board2, "workflows");
29028
29142
  for (const entry of fs84.existsSync(root) ? fs84.readdirSync(root, { withFileTypes: true }) : []) {
29029
29143
  if (!entry.isDirectory()) continue;
29030
- const meta = splitFrontmatter(read3(path85.join(root, entry.name, "WORKFLOW.md")) ?? "").meta;
29144
+ const meta = splitFrontmatter(read3(path86.join(root, entry.name, "WORKFLOW.md")) ?? "").meta;
29031
29145
  const stages = meta === null ? void 0 : parseYamlBlock(meta).stages;
29032
29146
  const plan = stages && typeof stages === "object" && !Array.isArray(stages) ? stages.plan : void 0;
29033
29147
  const lead = plan && typeof plan === "object" && !Array.isArray(plan) && typeof plan.lead === "string" ? plan.lead.trim() : "";
@@ -29036,30 +29150,30 @@ function planLeads(board2) {
29036
29150
  return leads;
29037
29151
  }
29038
29152
  function movePlanRules(board2, report3) {
29039
- const rules = path85.join(board2, "rules");
29153
+ const rules = path86.join(board2, "rules");
29040
29154
  const moved = [];
29041
29155
  for (const [id, leads] of Object.entries(planLeads(board2))) {
29042
29156
  for (const lead of leads) {
29043
- const from = path85.join(rules, `${lead}.md`);
29157
+ const from = path86.join(rules, `${lead}.md`);
29044
29158
  const rule = read3(from)?.trim();
29045
29159
  if (!rule) continue;
29046
- const to = path85.join(rules, "workflows", `${id}.md`);
29160
+ const to = path86.join(rules, "workflows", `${id}.md`);
29047
29161
  const there = read3(to)?.trim() ?? "";
29048
29162
  if (!there.includes(rule)) {
29049
- fs84.mkdirSync(path85.dirname(to), { recursive: true });
29163
+ fs84.mkdirSync(path86.dirname(to), { recursive: true });
29050
29164
  fs84.writeFileSync(to, `${[there, rule].filter(Boolean).join("\n\n")}
29051
29165
  `);
29052
29166
  moved.push(`${lead} \u2192 ${id}`);
29053
29167
  }
29054
29168
  }
29055
29169
  }
29056
- for (const lead of GONE_LEADS) fs84.rmSync(path85.join(rules, `${lead}.md`), { force: true });
29170
+ for (const lead of GONE_LEADS) fs84.rmSync(path86.join(rules, `${lead}.md`), { force: true });
29057
29171
  if (moved.length) report3.did.push(`made each plan agent's rule its workflow's card rule (docs/kanban/rules/workflows/): ${moved.join(", ")}`);
29058
29172
  }
29059
29173
  function noteOldWorkflows(board2, report3) {
29060
29174
  const old = (dir, file, keys) => {
29061
- const root = path85.join(board2, dir);
29062
- 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}`);
29175
+ const root = path86.join(board2, dir);
29176
+ return (fs84.existsSync(root) ? fs84.readdirSync(root, { withFileTypes: true }) : []).filter((e) => e.isDirectory() && keys.test(splitFrontmatter(read3(path86.join(root, e.name, file)) ?? "").meta ?? "")).map((e) => `docs/kanban/${dir}/${e.name}/${file}`);
29063
29177
  };
29064
29178
  const files2 = [...old("workflows", "WORKFLOW.md", /^(stages|delivers):/m), ...old("agents", "AGENT.md", /^\s+(hook|lead):/m)];
29065
29179
  if (files2.length) {
@@ -29069,7 +29183,7 @@ function noteOldWorkflows(board2, report3) {
29069
29183
  }
29070
29184
  }
29071
29185
  function checkConfig(board2, report3) {
29072
- const current = read3(path85.join(board2, "config.md"));
29186
+ const current = read3(path86.join(board2, "config.md"));
29073
29187
  if (!current) return;
29074
29188
  const added = missingConfigKeys(current);
29075
29189
  if (added.length) {
@@ -29079,7 +29193,7 @@ function checkConfig(board2, report3) {
29079
29193
  }
29080
29194
  }
29081
29195
  function checkModules(board2, report3) {
29082
- const map2 = read3(path85.join(board2, "modules.md"));
29196
+ const map2 = read3(path86.join(board2, "modules.md"));
29083
29197
  if (map2 && map2.includes(UNFILLED)) {
29084
29198
  report3.notes.push(
29085
29199
  "docs/kanban/modules.md is still blank \u2014 write it from the repo (`akb guide module-map`)"
@@ -29133,14 +29247,14 @@ function declareSetup(program2, cli) {
29133
29247
  program2.version(SKILL_VERSION, "-v, --version", "print this version").addHelpText("beforeAll", spelled(cli.program));
29134
29248
  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");
29135
29249
  const where = (cmd) => {
29136
- const dir = path86.resolve(String(cmd.optsWithGlobals().dir ?? cli.cwd));
29250
+ const dir = path87.resolve(String(cmd.optsWithGlobals().dir ?? cli.cwd));
29137
29251
  if (!fs85.existsSync(dir)) throw new BoardError(`no such folder: ${dir}`, { kind: "no-such-folder", dir });
29138
29252
  return dir;
29139
29253
  };
29140
29254
  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() {
29141
29255
  const dir = where(this);
29142
29256
  const named2 = this.optsWithGlobals().board ?? process.env.AI4KANBAN_BOARD?.trim();
29143
- const board2 = named2 ? path86.resolve(cli.cwd, named2) : null;
29257
+ const board2 = named2 ? path87.resolve(cli.cwd, named2) : null;
29144
29258
  await runAction(ctxOf(this, cli.program), {}, () => cmdUpdate2({ dir, program: cli.program, board: board2 }));
29145
29259
  });
29146
29260
  program2.command("__rules", { hidden: true }).action(function() {