wildcrane-runner 0.6.0 → 0.6.2

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.
@@ -29,9 +29,9 @@ var __commonJS = (cb, mod) => function __require2() {
29
29
  throw mod = 0, e;
30
30
  }
31
31
  };
32
- var __export = (target, all) => {
33
- for (var name in all)
34
- __defProp(target, name, { get: all[name], enumerable: true });
32
+ var __export = (target, all2) => {
33
+ for (var name in all2)
34
+ __defProp(target, name, { get: all2[name], enumerable: true });
35
35
  };
36
36
  var __copyProps = (to, from, except, desc) => {
37
37
  if (from && typeof from === "object" || typeof from === "function") {
@@ -50,9 +50,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
50
50
  mod
51
51
  ));
52
52
 
53
- // ../../../../../cli/node_modules/commander/lib/error.js
53
+ // ../../../../Users/wutao/git/ai4kanban/cli/node_modules/commander/lib/error.js
54
54
  var require_error = __commonJS({
55
- "../../../../../cli/node_modules/commander/lib/error.js"(exports) {
55
+ "../../../../Users/wutao/git/ai4kanban/cli/node_modules/commander/lib/error.js"(exports) {
56
56
  var CommanderError2 = class extends Error {
57
57
  /**
58
58
  * Constructs the CommanderError class
@@ -85,9 +85,9 @@ var require_error = __commonJS({
85
85
  }
86
86
  });
87
87
 
88
- // ../../../../../cli/node_modules/commander/lib/argument.js
88
+ // ../../../../Users/wutao/git/ai4kanban/cli/node_modules/commander/lib/argument.js
89
89
  var require_argument = __commonJS({
90
- "../../../../../cli/node_modules/commander/lib/argument.js"(exports) {
90
+ "../../../../Users/wutao/git/ai4kanban/cli/node_modules/commander/lib/argument.js"(exports) {
91
91
  var { InvalidArgumentError: InvalidArgumentError2 } = require_error();
92
92
  var Argument2 = class {
93
93
  /**
@@ -212,9 +212,9 @@ var require_argument = __commonJS({
212
212
  }
213
213
  });
214
214
 
215
- // ../../../../../cli/node_modules/commander/lib/help.js
215
+ // ../../../../Users/wutao/git/ai4kanban/cli/node_modules/commander/lib/help.js
216
216
  var require_help = __commonJS({
217
- "../../../../../cli/node_modules/commander/lib/help.js"(exports) {
217
+ "../../../../Users/wutao/git/ai4kanban/cli/node_modules/commander/lib/help.js"(exports) {
218
218
  var { humanReadableArgName } = require_argument();
219
219
  var Help2 = class {
220
220
  constructor() {
@@ -781,9 +781,9 @@ ${itemIndentStr}`);
781
781
  }
782
782
  });
783
783
 
784
- // ../../../../../cli/node_modules/commander/lib/option.js
784
+ // ../../../../Users/wutao/git/ai4kanban/cli/node_modules/commander/lib/option.js
785
785
  var require_option = __commonJS({
786
- "../../../../../cli/node_modules/commander/lib/option.js"(exports) {
786
+ "../../../../Users/wutao/git/ai4kanban/cli/node_modules/commander/lib/option.js"(exports) {
787
787
  var { InvalidArgumentError: InvalidArgumentError2 } = require_error();
788
788
  var Option2 = class {
789
789
  /**
@@ -1082,9 +1082,9 @@ var require_option = __commonJS({
1082
1082
  }
1083
1083
  });
1084
1084
 
1085
- // ../../../../../cli/node_modules/commander/lib/suggestSimilar.js
1085
+ // ../../../../Users/wutao/git/ai4kanban/cli/node_modules/commander/lib/suggestSimilar.js
1086
1086
  var require_suggestSimilar = __commonJS({
1087
- "../../../../../cli/node_modules/commander/lib/suggestSimilar.js"(exports) {
1087
+ "../../../../Users/wutao/git/ai4kanban/cli/node_modules/commander/lib/suggestSimilar.js"(exports) {
1088
1088
  var maxDistance = 3;
1089
1089
  function editDistance2(a, b) {
1090
1090
  if (Math.abs(a.length - b.length) > maxDistance)
@@ -1162,13 +1162,13 @@ var require_suggestSimilar = __commonJS({
1162
1162
  }
1163
1163
  });
1164
1164
 
1165
- // ../../../../../cli/node_modules/commander/lib/command.js
1165
+ // ../../../../Users/wutao/git/ai4kanban/cli/node_modules/commander/lib/command.js
1166
1166
  var require_command = __commonJS({
1167
- "../../../../../cli/node_modules/commander/lib/command.js"(exports) {
1167
+ "../../../../Users/wutao/git/ai4kanban/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 path80 = __require("node:path");
1171
- var fs86 = __require("node:fs");
1170
+ var path83 = __require("node:path");
1171
+ var fs88 = __require("node:fs");
1172
1172
  var process2 = __require("node:process");
1173
1173
  var { Argument: Argument2, humanReadableArgName } = require_argument();
1174
1174
  var { CommanderError: CommanderError2 } = require_error();
@@ -2149,7 +2149,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2149
2149
  * @param {string} subcommandName
2150
2150
  */
2151
2151
  _checkForMissingExecutable(executableFile, executableDir, subcommandName) {
2152
- if (fs86.existsSync(executableFile)) return;
2152
+ if (fs88.existsSync(executableFile)) return;
2153
2153
  const executableDirMessage = executableDir ? `searched for local subcommand relative to directory '${executableDir}'` : "no directory for search for local subcommand, use .executableDir() to supply a custom directory";
2154
2154
  const executableMissing = `'${executableFile}' does not exist
2155
2155
  - if '${subcommandName}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
@@ -2167,11 +2167,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
2167
2167
  let launchWithNode = false;
2168
2168
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
2169
2169
  function findFile(baseDir, baseName) {
2170
- const localBin = path80.resolve(baseDir, baseName);
2171
- if (fs86.existsSync(localBin)) return localBin;
2172
- if (sourceExt.includes(path80.extname(baseName))) return void 0;
2170
+ const localBin = path83.resolve(baseDir, baseName);
2171
+ if (fs88.existsSync(localBin)) return localBin;
2172
+ if (sourceExt.includes(path83.extname(baseName))) return void 0;
2173
2173
  const foundExt = sourceExt.find(
2174
- (ext) => fs86.existsSync(`${localBin}${ext}`)
2174
+ (ext) => fs88.existsSync(`${localBin}${ext}`)
2175
2175
  );
2176
2176
  if (foundExt) return `${localBin}${foundExt}`;
2177
2177
  return void 0;
@@ -2183,21 +2183,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
2183
2183
  if (this._scriptPath) {
2184
2184
  let resolvedScriptPath;
2185
2185
  try {
2186
- resolvedScriptPath = fs86.realpathSync(this._scriptPath);
2186
+ resolvedScriptPath = fs88.realpathSync(this._scriptPath);
2187
2187
  } catch {
2188
2188
  resolvedScriptPath = this._scriptPath;
2189
2189
  }
2190
- executableDir = path80.resolve(
2191
- path80.dirname(resolvedScriptPath),
2190
+ executableDir = path83.resolve(
2191
+ path83.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 = path80.basename(
2198
+ const legacyName = path83.basename(
2199
2199
  this._scriptPath,
2200
- path80.extname(this._scriptPath)
2200
+ path83.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(path80.extname(executableFile));
2211
+ launchWithNode = sourceExt.includes(path83.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 = path80.basename(filename, path80.extname(filename));
3058
+ this._name = path83.basename(filename, path83.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(path81) {
3073
- if (path81 === void 0) return this._executableDir;
3074
- this._executableDir = path81;
3072
+ executableDir(path84) {
3073
+ if (path84 === void 0) return this._executableDir;
3074
+ this._executableDir = path84;
3075
3075
  return this;
3076
3076
  }
3077
3077
  /**
@@ -3321,9 +3321,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
3321
3321
  }
3322
3322
  });
3323
3323
 
3324
- // ../../../../../cli/node_modules/commander/index.js
3324
+ // ../../../../Users/wutao/git/ai4kanban/cli/node_modules/commander/index.js
3325
3325
  var require_commander = __commonJS({
3326
- "../../../../../cli/node_modules/commander/index.js"(exports) {
3326
+ "../../../../Users/wutao/git/ai4kanban/cli/node_modules/commander/index.js"(exports) {
3327
3327
  var { Argument: Argument2 } = require_argument();
3328
3328
  var { Command: Command2 } = require_command();
3329
3329
  var { CommanderError: CommanderError2, InvalidArgumentError: InvalidArgumentError2 } = require_error();
@@ -3343,10 +3343,10 @@ var require_commander = __commonJS({
3343
3343
  }
3344
3344
  });
3345
3345
 
3346
- // ../../../../../cli/node_modules/commander/esm.mjs
3346
+ // ../../../../Users/wutao/git/ai4kanban/cli/node_modules/commander/esm.mjs
3347
3347
  var import_index, program, createCommand, createArgument, createOption, CommanderError, InvalidArgumentError, InvalidOptionArgumentError, Command, Argument, Option, Help;
3348
3348
  var init_esm = __esm({
3349
- "../../../../../cli/node_modules/commander/esm.mjs"() {
3349
+ "../../../../Users/wutao/git/ai4kanban/cli/node_modules/commander/esm.mjs"() {
3350
3350
  import_index = __toESM(require_commander(), 1);
3351
3351
  ({
3352
3352
  program,
@@ -3637,11 +3637,11 @@ function boardPath() {
3637
3637
  return rel(KANBAN).split(path3.sep).join("/");
3638
3638
  }
3639
3639
  function boardText(text5) {
3640
- const posix2 = (p) => rel(p).split(path3.sep).join("/");
3640
+ const posix3 = (p) => rel(p).split(path3.sep).join("/");
3641
3641
  const swaps = [
3642
- [ASSETS_PATH_IN_TEXT, posix2(ASSETS)],
3643
- [MOCKUPS_PATH_IN_TEXT, posix2(MOCKUPS)],
3644
- [BOARD_STATE_IN_TEXT, posix2(BOARD_STATE)],
3642
+ [ASSETS_PATH_IN_TEXT, posix3(ASSETS)],
3643
+ [MOCKUPS_PATH_IN_TEXT, posix3(MOCKUPS)],
3644
+ [BOARD_STATE_IN_TEXT, posix3(BOARD_STATE)],
3645
3645
  [BOARD_PATH_IN_TEXT, boardPath()]
3646
3646
  ];
3647
3647
  const swap = (part, i) => i === swaps.length ? part : part.split(swaps[i][0]).map((p) => swap(p, i + 1)).join(swaps[i][1]);
@@ -3850,8 +3850,8 @@ var init_question_block = __esm({
3850
3850
  // ../cli/src/lib/view/rules.ts
3851
3851
  function creationRefusal(id, creation, what, discard = false) {
3852
3852
  if (!creation || creation.state === "unfinished" && discard) return null;
3853
- const run = creation.runId.slice(0, 8);
3854
- return creation.state === "creating" ? `#${id} is still being created by run ${run} \u2014 \`${what}\` is refused until that run finishes.` : `#${id} was never finished being created: run ${run} stopped short. \`${what}\` is refused until that run is picked back up from the board and finishes. Use reject --discard to discard it instead.`;
3853
+ const run2 = creation.runId.slice(0, 8);
3854
+ return creation.state === "creating" ? `#${id} is still being created by run ${run2} \u2014 \`${what}\` is refused until that run finishes.` : `#${id} was never finished being created: run ${run2} stopped short. \`${what}\` is refused until that run is picked back up from the board and finishes. Use reject --discard to discard it instead.`;
3855
3855
  }
3856
3856
  function hasOptions(q) {
3857
3857
  return Array.isArray(q.options) && q.options.length > 0;
@@ -4481,10 +4481,10 @@ function claimBreak(dir) {
4481
4481
  return false;
4482
4482
  }
4483
4483
  }
4484
- function removeClaim(dir, token2) {
4485
- if (token2) {
4484
+ function removeClaim(dir, token3) {
4485
+ if (token3) {
4486
4486
  try {
4487
- fs7.unlinkSync(path8.join(dir, token2));
4487
+ fs7.unlinkSync(path8.join(dir, token3));
4488
4488
  } catch (err) {
4489
4489
  if (err.code !== "ENOENT") throw err;
4490
4490
  }
@@ -4501,15 +4501,15 @@ function removeClaim(dir, token2) {
4501
4501
  }
4502
4502
  function tryLock(dir) {
4503
4503
  fs7.mkdirSync(path8.dirname(dir), { recursive: true });
4504
- const token2 = `${process.pid}-${randomUUID()}`;
4504
+ const token3 = `${process.pid}-${randomUUID()}`;
4505
4505
  const candidate = fs7.mkdtempSync(`${dir}.`);
4506
4506
  try {
4507
- fs7.writeFileSync(path8.join(candidate, token2), "");
4507
+ fs7.writeFileSync(path8.join(candidate, token3), "");
4508
4508
  for (; ; ) {
4509
4509
  try {
4510
4510
  fs7.renameSync(candidate, dir);
4511
4511
  return () => {
4512
- removeClaim(dir, token2);
4512
+ removeClaim(dir, token3);
4513
4513
  };
4514
4514
  } catch (err) {
4515
4515
  const code = err.code;
@@ -5133,16 +5133,12 @@ var init_catalog = __esm({
5133
5133
  function isLanguage(value) {
5134
5134
  return typeof value === "string" && LANGUAGES.includes(value);
5135
5135
  }
5136
- var LANGUAGES, DEFAULT_LANGUAGE, LANGUAGE_NAMES;
5136
+ var LANGUAGES, DEFAULT_LANGUAGE;
5137
5137
  var init_types = __esm({
5138
5138
  "../cli/src/lib/machine/types.ts"() {
5139
5139
  "use strict";
5140
5140
  LANGUAGES = ["en", "zh"];
5141
5141
  DEFAULT_LANGUAGE = "en";
5142
- LANGUAGE_NAMES = {
5143
- en: "English",
5144
- zh: "\u4E2D\u6587"
5145
- };
5146
5142
  }
5147
5143
  });
5148
5144
 
@@ -5483,13 +5479,13 @@ async function attempt(now) {
5483
5479
  }
5484
5480
  function fill(queued, install) {
5485
5481
  const events = [];
5486
- let bytes = Buffer.byteLength(JSON.stringify({ v: VERSION, install, events: [] }));
5482
+ let bytes3 = Buffer.byteLength(JSON.stringify({ v: VERSION, install, events: [] }));
5487
5483
  for (const event2 of queued) {
5488
5484
  if (events.length >= LIMITS.batchEvents) break;
5489
5485
  const piece = Buffer.byteLength(JSON.stringify(event2)) + (events.length ? 1 : 0);
5490
- if (bytes + piece > LIMITS.batchBytes) break;
5486
+ if (bytes3 + piece > LIMITS.batchBytes) break;
5491
5487
  events.push(event2);
5492
- bytes += piece;
5488
+ bytes3 += piece;
5493
5489
  }
5494
5490
  return { events, body: JSON.stringify({ v: VERSION, install, events }) };
5495
5491
  }
@@ -5585,14 +5581,14 @@ function currentTurn() {
5585
5581
  function forgetFileMemos() {
5586
5582
  turn++;
5587
5583
  }
5588
- function memoByFiles(paths, read5) {
5584
+ function memoByFiles(paths, read6) {
5589
5585
  let last = null;
5590
5586
  return () => {
5591
5587
  const now = currentTurn();
5592
5588
  if (last?.turn !== now) {
5593
5589
  const seen = stamp2(paths());
5594
5590
  if (last?.stamp === seen) last.turn = now;
5595
- else last = { stamp: seen, turn: now, value: read5() };
5591
+ else last = { stamp: seen, turn: now, value: read6() };
5596
5592
  }
5597
5593
  return copy(last.value);
5598
5594
  };
@@ -5611,7 +5607,7 @@ var init_file_memo = __esm({
5611
5607
  var AGENT_default;
5612
5608
  var init_AGENT = __esm({
5613
5609
  "../cli/src/agents/api-designer/AGENT.md"() {
5614
- AGENT_default = "---\nname: api-designer\ndescription: Writes the caller's documentation for the APIs a card changes.\nakb:\n when: A card adds or changes a surface a user or third party calls directly, such as HTTP routes, WebSocket messages, MCP tools, or CLI commands and flags. Not a protocol between the project's own components.\n i18n:\n zh:\n title: \u63A5\u53E3\u8BBE\u8BA1\u5E08\n description: \u4E3A\u5361\u7247\u6539\u52A8\u7684\u63A5\u53E3\u5199\u51FA\u8C03\u7528\u65B9\u6587\u6863\u3002\n when: \u5361\u7247\u65B0\u589E\u6216\u4FEE\u6539\u7528\u6237\u6216\u7B2C\u4E09\u65B9\u76F4\u63A5\u8C03\u7528\u7684\u63A5\u53E3\u65F6\uFF0C\u5982 HTTP \u8DEF\u7531\u3001WebSocket \u6D88\u606F\u3001MCP \u5DE5\u5177\u3001CLI \u547D\u4EE4\u548C\u53C2\u6570\u3002\u4E0D\u542B\u9879\u76EE\u81EA\u8EAB\u7EC4\u4EF6\u4E4B\u95F4\u7684\u534F\u8BAE\u3002\n---\n\nWrite each surface this card changes as the documentation entry that will be committed, in\nthe project's existing docs style: inputs, outputs, errors and one example call. Use a\n`diff` when an entry already exists, and a `mermaid` `sequenceDiagram` for a message\nexchange. One design, no alternatives, nothing else in the section.\n\n- **Glanceable**: a junior engineer understands each entry in one read. Plain names, short\n lines, no field or error the card does not change.\n";
5610
+ AGENT_default = "---\nname: api-designer\ndescription: Writes the caller's documentation for the APIs a card changes.\nakb:\n when: A card adds or changes a surface a user or third party calls directly, such as HTTP routes, WebSocket messages, MCP tools, or CLI commands and flags. Not a protocol between the project's own components.\n i18n:\n en:\n title: API docs\n zh:\n title: \u63A5\u53E3\u6587\u6863\n description: \u4E3A\u5361\u7247\u6539\u52A8\u7684\u63A5\u53E3\u5199\u51FA\u8C03\u7528\u65B9\u6587\u6863\u3002\n when: \u5361\u7247\u65B0\u589E\u6216\u4FEE\u6539\u7528\u6237\u6216\u7B2C\u4E09\u65B9\u76F4\u63A5\u8C03\u7528\u7684\u63A5\u53E3\u65F6\uFF0C\u5982 HTTP \u8DEF\u7531\u3001WebSocket \u6D88\u606F\u3001MCP \u5DE5\u5177\u3001CLI \u547D\u4EE4\u548C\u53C2\u6570\u3002\u4E0D\u542B\u9879\u76EE\u81EA\u8EAB\u7EC4\u4EF6\u4E4B\u95F4\u7684\u534F\u8BAE\u3002\n---\n\nWrite each surface this card changes as the documentation entry that will be committed, in\nthe project's existing docs style: inputs, outputs, errors and one example call. Use a\n`diff` when an entry already exists, and a `mermaid` `sequenceDiagram` for a message\nexchange. One design, no alternatives, nothing else in the section.\n\n- **Glanceable**: a junior engineer understands each entry in one read. Plain names, short\n lines, no field or error the card does not change.\n";
5615
5611
  }
5616
5612
  });
5617
5613
 
@@ -5619,7 +5615,7 @@ var init_AGENT = __esm({
5619
5615
  var AGENT_default2;
5620
5616
  var init_AGENT2 = __esm({
5621
5617
  "../cli/src/agents/blog-illustrator/AGENT.md"() {
5622
- AGENT_default2 = "---\nname: blog-illustrator\ndescription: Makes a blog post's cover and in-article images, each with alt text.\nakb:\n after: [blog-planner]\n memoryFiles:\n workspace: [style.md]\n i18n:\n en:\n title: Blog illustrator\n zh:\n title: \u535A\u5BA2\u914D\u56FE\n description: \u6309\u786E\u8BA4\u7684\u5927\u7EB2\u4E3A\u535A\u5BA2\u6587\u7AE0\u65B0\u505A\u5C01\u9762\u548C\u6B63\u6587\u914D\u56FE\uFF0C\u5E76\u5199\u597D\u66FF\u4EE3\u6587\u5B57\u3002\n---\n\nMake the cover and every image the approved outline in ``## By `blog-planner` agent`` names.\nMake every image new for this post; never search for or reuse existing images. On an\nexisting card, redo only the images an outline change affects.\n\n## Methods\n\nChoose one per image:\n\n- **Screenshot**: capture the real product in the state the section describes.\n- **Drawn**: lay out a diagram or chart in HTML/CSS or SVG and render it to PNG.\n- **Generated**: `akb cloud image --aspect <ratio> --out <file> \"<prompt>\"`, which spends the\n user's Pro credits; never for a product interface.\n- **Fallback**: when a method cannot deliver, use another and say so in your section.\n\n## Rules\n\n- **Alt text**: one sentence per image saying what it shows; draw it on each in-article image as its caption.\n- **Cover**: the aspect ratio the blog's existing covers use, 16:9 when there are none;\n legible at thumbnail size.\n- **Consistent**: one post's images share palette, fonts and treatment.\n- **Review**: view each image at full size and at the width the article shows it; fix\n defects and recheck.\n\n## Paths\n\n- **Assets**: `<board-state>/assets/<card id>/` \u2014 `cover.png` and `fig-<nn>.png`, each with\n the source that renders it beside it.\n- **Hand over**: in your section, one line per image \u2014 file, the outline section it belongs\n to, and its alt text.\n\n## Memory\n\nKeep two files in your memory folder, one line each, following \"What earns a note\" in\n`akb guide board`:\n\n- **`style.md`**: diagram treatment and distilled user corrections that hold for any blog.\n- **`brand.md`**: this product's palette and fonts.\n";
5618
+ AGENT_default2 = "---\nname: blog-illustrator\ndescription: Makes a blog post's cover and in-article images, each with alt text.\nakb:\n after: [blog-planner]\n memoryFiles:\n workspace: [style.md]\n i18n:\n en:\n title: Images\n zh:\n title: \u914D\u56FE\n description: \u6309\u786E\u8BA4\u7684\u5927\u7EB2\u4E3A\u535A\u5BA2\u6587\u7AE0\u65B0\u505A\u5C01\u9762\u548C\u6B63\u6587\u914D\u56FE\uFF0C\u5E76\u5199\u597D\u66FF\u4EE3\u6587\u5B57\u3002\n---\n\nMake the cover and every image the approved outline in ``## By `blog-planner` agent`` names.\nMake every image new for this post; never search for or reuse existing images. On an\nexisting card, redo only the images an outline change affects.\n\n## Methods\n\nChoose one per image:\n\n- **Screenshot**: capture the real product in the state the section describes.\n- **Drawn**: lay out a diagram or chart in HTML/CSS or SVG and render it to PNG.\n- **Generated**: `akb cloud image --aspect <ratio> --out <file> \"<prompt>\"`, which spends the\n user's Pro credits; never for a product interface.\n- **Fallback**: when a method cannot deliver, use another and say so in your section.\n\n## Rules\n\n- **Alt text**: one sentence per image saying what it shows; draw it on each in-article image as its caption.\n- **Cover**: the aspect ratio the blog's existing covers use, 16:9 when there are none;\n legible at thumbnail size.\n- **Consistent**: one post's images share palette, fonts and treatment.\n- **Review**: view each image at full size and at the width the article shows it; fix\n defects and recheck.\n\n## Paths\n\n- **Assets**: `<board-state>/assets/<card id>/` \u2014 `cover.png` and `fig-<nn>.png`, each with\n the source that renders it beside it.\n- **Hand over**: in your section, one line per image \u2014 file, the outline section it belongs\n to, and its alt text.\n\n## Memory\n\nKeep two files in your memory folder, one line each, following \"What earns a note\" in\n`akb guide board`:\n\n- **`style.md`**: diagram treatment and distilled user corrections that hold for any blog.\n- **`brand.md`**: this product's palette and fonts.\n";
5623
5619
  }
5624
5620
  });
5625
5621
 
@@ -5627,7 +5623,7 @@ var init_AGENT2 = __esm({
5627
5623
  var AGENT_default3;
5628
5624
  var init_AGENT3 = __esm({
5629
5625
  "../cli/src/agents/blog-planner/AGENT.md"() {
5630
- AGENT_default3 = "---\nname: blog-planner\ndescription: Writes a blog post's brief and outline, the plan its article is written from.\nakb:\n memoryFiles:\n workspace: [general.md]\n i18n:\n en:\n title: Blog planner\n zh:\n title: \u535A\u5BA2\u7B56\u5212\n description: \u5199\u535A\u5BA2\u6587\u7AE0\u7684\u7B80\u62A5\u548C\u5927\u7EB2\uFF0C\u6B63\u6587\u6309\u5B83\u6765\u5199\u3002\n---\n\nYou write a blog post's brief and outline. Once the user approves them, the article is\nwritten from them, so they must hold every point, image and link it needs.\n\n## Deciding\n\n- **Evidence first**: settle what the card, the project, the existing blog and your memory answer.\n- **Default with a reason**: propose a justified default for every content choice.\n- **Ask little**: a `[user]` question only for what the user owns \u2014 the audience, the angle,\n the keyword to win, or a fact only they know.\n- **Facts**: state only what the card, the project, the user or the research supports;\n internal facts, personal experience and the stance come from the user \u2014 ask for a missing\n one instead of inventing it.\n\n## Memory\n\nYour memory folder is `docs/kanban/memory/agents/blog-planner/`. Read it before writing; the\ncurrent card wins.\n\n- **General**: `general.md` holds writing preferences shared by every destination.\n- **Product**: `product.md` holds how posts use this product as an example.\n- **Destinations**: `destination.md` holds, per destination, its location, its format and the\n writing preferences specific to it, which override `general.md`.\n- **Keywords**: `keywords.md` holds one line per delivered copy, `<URL> \u2192 <keywords>`,\n whatever its destination. When you read it, drop lines whose URL no longer resolves.\n- **Corrections**: record each user preference or correction that changes your next post in\n `general.md`, in `destination.md` when it applies to one destination only, or in\n `product.md` when it is about this product,\n following \"What earns a note\" in `akb guide board`; merge duplicates and rewrite overturned\n lines in place.\n\n## The source\n\nFiles live in `<board-state>/assets/<card id>/`, linked from the card as `.assets/<card id>/...`.\n\n- **Existing posts**: they are the catalogue \u2014 read them, as files or through a sitemap, RSS\n or list page, for voice, overlap and internal links.\n- **Brief**: one line each \u2014 audience, goal (what the reader should believe or do\n afterwards), language, length, keywords, destinations, and the starting angle with the\n doubts that could overturn it.\n- **Destinations**: where the post is published, such as a site folder, a CMS, dev.to or\n Medium; there may be several. Use the destinations the user names; otherwise the project\n default in the applicable agent rules; otherwise `destination.md`; otherwise infer them\n from the card and earlier posts. Ask only when all of these leave them open.\n- **Keywords**: zero or more, the first is the one to win. Ask which one the user wants to\n win; when they give none, leave it empty. No keyword research or rank tracking.\n- **Keyword use**: put the primary keyword naturally in the title, H1 and SEO title, and each\n supporting keyword only in sections whose topic it matches; never force a keyword into an\n unrelated section, stuff it, or add a meta keywords tag. Flag a keyword the topic cannot\n cover. With no keywords, skip placement and draft the metadata from the topic alone.\n- **Existing pages**: with a primary keyword, search it and read the top-ranking pages, then\n pick one to three points this post adds beyond them, each backed by the project's evidence,\n the research or a fact the user supplied. Keep this reading out of the card.\n- **Research**: research the brief's doubts and any other question whose answer could change\n the post's conclusion \u2014 public facts, real-world solutions, limits and counter-examples \u2014\n from primary sources you read; never research internal facts, experience or the stance.\n- **Research file**: `research.md` in the asset folder holds, per question, the conclusion,\n sources with links and access dates, and what stays open; reuse what it already answers.\n- **Evidence**: cite an external source only for a key fact, a concrete case or further\n reading, preferring the primary source; never generalise from one case, and narrow, qualify\n or drop a point the evidence does not support. Not every finding belongs in the article.\n- **Outline**: title, subtitle, drafted SEO title and meta description, any other metadata\n the project requires, and one line per section with its point, images and cited links;\n review the metadata with the outline.\n- **Links**: internal links go to existing posts or pages. Check that every link resolves.\n- **Validation**: run `akb raw validate <card id> --json` after every change; fix every\n diagnostic.\n\n## Steps\n\n- **Outline**: write the brief, research it, then write an outline whose angle and sections\n carry the points from existing pages and the research.\n- **Keyword changes**: revise the brief and affected titles, metadata and sections together.\n";
5626
+ AGENT_default3 = "---\nname: blog-planner\ndescription: Writes a blog post's brief and outline, the plan its article is written from.\nakb:\n memoryFiles:\n workspace: [general.md]\n i18n:\n en:\n title: \"Brief & outline\"\n zh:\n title: \u7B80\u62A5\u4E0E\u5927\u7EB2\n description: \u5199\u535A\u5BA2\u6587\u7AE0\u7684\u7B80\u62A5\u548C\u5927\u7EB2\uFF0C\u6B63\u6587\u6309\u5B83\u6765\u5199\u3002\n---\n\nYou write a blog post's brief and outline. Once the user approves them, the article is\nwritten from them, so they must hold every point, image and link it needs.\n\n## Deciding\n\n- **Evidence first**: settle what the card, the project, the existing blog and your memory answer.\n- **Default with a reason**: propose a justified default for every content choice.\n- **Ask little**: a `[user]` question only for what the user owns \u2014 the audience, the angle,\n the keyword to win, or a fact only they know.\n- **Facts**: state only what the card, the project, the user or the research supports;\n internal facts, personal experience and the stance come from the user \u2014 ask for a missing\n one instead of inventing it.\n\n## Memory\n\nYour memory folder is `docs/kanban/memory/agents/blog-planner/`. Read it before writing; the\ncurrent card wins.\n\n- **General**: `general.md` holds writing preferences shared by every destination.\n- **Product**: `product.md` holds how posts use this product as an example.\n- **Destinations**: `destination.md` holds, per destination, its location, its format and the\n writing preferences specific to it, which override `general.md`.\n- **Keywords**: `keywords.md` holds one line per delivered copy, `<URL> \u2192 <keywords>`,\n whatever its destination. When you read it, drop lines whose URL no longer resolves.\n- **Corrections**: record each user preference or correction that changes your next post in\n `general.md`, in `destination.md` when it applies to one destination only, or in\n `product.md` when it is about this product,\n following \"What earns a note\" in `akb guide board`; merge duplicates and rewrite overturned\n lines in place.\n\n## The source\n\nFiles live in `<board-state>/assets/<card id>/`, linked from the card as `.assets/<card id>/...`.\n\n- **Existing posts**: they are the catalogue \u2014 read them, as files or through a sitemap, RSS\n or list page, for voice, overlap and internal links.\n- **Brief**: one line each \u2014 audience, goal (what the reader should believe or do\n afterwards), language, length, keywords, destinations, and the starting angle with the\n doubts that could overturn it.\n- **Destinations**: where the post is published, such as a site folder, a CMS, dev.to or\n Medium; there may be several. Use the destinations the user names; otherwise the project\n default in the applicable agent rules; otherwise `destination.md`; otherwise infer them\n from the card and earlier posts. Ask only when all of these leave them open.\n- **Keywords**: zero or more, the first is the one to win. Ask which one the user wants to\n win; when they give none, leave it empty. No keyword research or rank tracking.\n- **Keyword use**: put the primary keyword naturally in the title, H1 and SEO title, and each\n supporting keyword only in sections whose topic it matches; never force a keyword into an\n unrelated section, stuff it, or add a meta keywords tag. Flag a keyword the topic cannot\n cover. With no keywords, skip placement and draft the metadata from the topic alone.\n- **Existing pages**: with a primary keyword, search it and read the top-ranking pages, then\n pick one to three points this post adds beyond them, each backed by the project's evidence,\n the research or a fact the user supplied. Keep this reading out of the card.\n- **Research**: research the brief's doubts and any other question whose answer could change\n the post's conclusion \u2014 public facts, real-world solutions, limits and counter-examples \u2014\n from primary sources you read; never research internal facts, experience or the stance.\n- **Research file**: `research.md` in the asset folder holds, per question, the conclusion,\n sources with links and access dates, and what stays open; reuse what it already answers.\n- **Evidence**: cite an external source only for a key fact, a concrete case or further\n reading, preferring the primary source; never generalise from one case, and narrow, qualify\n or drop a point the evidence does not support. Not every finding belongs in the article.\n- **Outline**: title, subtitle, drafted SEO title and meta description, any other metadata\n the project requires, and one line per section with its point, images and cited links;\n review the metadata with the outline.\n- **Links**: internal links go to existing posts or pages. Check that every link resolves.\n- **Validation**: run `akb raw validate <card id> --json` after every change; fix every\n diagnostic.\n\n## Steps\n\n- **Outline**: write the brief, research it, then write an outline whose angle and sections\n carry the points from existing pages and the research.\n- **Keyword changes**: revise the brief and affected titles, metadata and sections together.\n";
5631
5627
  }
5632
5628
  });
5633
5629
 
@@ -5635,7 +5631,7 @@ var init_AGENT3 = __esm({
5635
5631
  var AGENT_default4;
5636
5632
  var init_AGENT4 = __esm({
5637
5633
  "../cli/src/agents/blog-writer/AGENT.md"() {
5638
- AGENT_default4 = "---\nname: blog-writer\ndescription: Writes a blog post's article from its approved outline and delivers one copy per destination.\nakb:\n after: [blog-planner, blog-illustrator]\n i18n:\n en:\n title: Blog writer\n zh:\n title: \u535A\u5BA2\u5199\u4F5C\n description: \u6309\u786E\u8BA4\u7684\u5927\u7EB2\u5199\u51FA\u5E26\u914D\u56FE\u3001\u5185\u94FE\u548C\u5916\u94FE\u7684\u5B8C\u6574\u6587\u7AE0\uFF0C\u5E76\u4E3A\u6BCF\u4E2A\u53D1\u5E03\u53BB\u5904\u4EA4\u4ED8\u4E00\u4EFD\u3002\n---\n\nWrite the article the approved outline in ``## By `blog-planner` agent`` describes, with the\nimages in ``## By `blog-illustrator` agent``. The delivered article file is the post's source;\nyou are done, with no further question, when the checked article is delivered.\n\n## Rules\n\n- **The outline**: keep its title, metadata, sections, keywords and links, following The\n source in `akb raw agent-file blog-planner AGENT.md`.\n- **Facts**: state only what the outline, `research.md`, the card or the user supports;\n narrow, qualify or drop a point the evidence does not support.\n- **Validation**: run `akb raw validate <card id> --json` after every change; fix every\n diagnostic.\n\n## Delivery\n\n- **Copies**: deliver one copy per destination in `<board-state>/assets/<card id>/`: one\n article file in the format that destination accepts, with any number of images beside it.\n- **Card**: at the top of your section, put one\n `<Asset src=\".assets/<card id>/<article file>\" label=\"<destination>\" />` per copy. Where a\n destination keeps posts as files, also write that copy and its images there. Put each\n copy's keywords where its format holds them. Append a ticked todo listing every delivered\n path in backticks.\n- **Separate copies**: never write a copy into another destination.\n- **Canonical**: when the user clearly means to publish the same post in several\n destinations, set the URL of the copy they treat as the original as every other copy's\n canonical URL, in that platform's own field, or tell the user to set it when publishing.\n- **Changes**: keep every delivered copy matching the article.\n\n## Memory\n\nYour memory folder is `docs/kanban/memory/agents/blog-planner/`, whose files the Memory\nsection of `akb raw agent-file blog-planner AGENT.md` describes. Read it before writing; the\ncurrent card wins.\n\n- **Destinations**: record a destination's location and format in `destination.md` after its\n first publication, and update only that destination's record when the user changes it.\n- **Keywords**: add one line per delivered copy to `keywords.md`, `<URL> \u2192 <keywords>`.\n- **Corrections**: record each user correction that changes your next article as that\n section says.\n";
5634
+ AGENT_default4 = "---\nname: blog-writer\ndescription: Writes a blog post's article from its approved outline and delivers one copy per destination.\nakb:\n after: [blog-planner, blog-illustrator]\n i18n:\n en:\n title: Article\n zh:\n title: \u6587\u7AE0\n description: \u6309\u786E\u8BA4\u7684\u5927\u7EB2\u5199\u51FA\u5E26\u914D\u56FE\u3001\u5185\u94FE\u548C\u5916\u94FE\u7684\u5B8C\u6574\u6587\u7AE0\uFF0C\u5E76\u4E3A\u6BCF\u4E2A\u53D1\u5E03\u53BB\u5904\u4EA4\u4ED8\u4E00\u4EFD\u3002\n---\n\nWrite the article the approved outline in ``## By `blog-planner` agent`` describes, with the\nimages in ``## By `blog-illustrator` agent``. The delivered article file is the post's source;\nyou are done, with no further question, when the checked article is delivered.\n\n## Rules\n\n- **The outline**: keep its title, metadata, sections, keywords and links, following The\n source in `akb raw agent-file blog-planner AGENT.md`.\n- **Facts**: state only what the outline, `research.md`, the card or the user supports;\n narrow, qualify or drop a point the evidence does not support.\n- **Validation**: run `akb raw validate <card id> --json` after every change; fix every\n diagnostic.\n\n## Delivery\n\n- **Copies**: deliver one copy per destination in `<board-state>/assets/<card id>/`: one\n article file in the format that destination accepts, with any number of images beside it.\n- **Card**: at the top of your section, put one\n `<Asset src=\".assets/<card id>/<article file>\" label=\"<destination>\" />` per copy. Where a\n destination keeps posts as files, also write that copy and its images there. Put each\n copy's keywords where its format holds them. Append a ticked todo listing every delivered\n path in backticks.\n- **Separate copies**: never write a copy into another destination.\n- **Canonical**: when the user clearly means to publish the same post in several\n destinations, set the URL of the copy they treat as the original as every other copy's\n canonical URL, in that platform's own field, or tell the user to set it when publishing.\n- **Changes**: keep every delivered copy matching the article.\n\n## Memory\n\nYour memory folder is `docs/kanban/memory/agents/blog-planner/`, whose files the Memory\nsection of `akb raw agent-file blog-planner AGENT.md` describes. Read it before writing; the\ncurrent card wins.\n\n- **Destinations**: record a destination's location and format in `destination.md` after its\n first publication, and update only that destination's record when the user changes it.\n- **Keywords**: add one line per delivered copy to `keywords.md`, `<URL> \u2192 <keywords>`.\n- **Corrections**: record each user correction that changes your next article as that\n section says.\n";
5639
5635
  }
5640
5636
  });
5641
5637
 
@@ -5643,7 +5639,7 @@ var init_AGENT4 = __esm({
5643
5639
  var AGENT_default5;
5644
5640
  var init_AGENT5 = __esm({
5645
5641
  "../cli/src/agents/carousel-planner/AGENT.md"() {
5646
- AGENT_default5 = "---\nname: carousel-planner\ndescription: Writes a social post's brief and copy and, for a carousel, a page outline on a saved format.\nakb:\n memoryFiles:\n workspace: [general.md, format-*.md]\n i18n:\n en:\n title: Social post planner\n zh:\n title: \u793E\u5A92\u5E16\u7B56\u5212\n description: \u5199\u793E\u5A92\u5E16\u7684\u7B80\u62A5\u548C\u6587\u6848\uFF0C\u56FE\u6587\u8F6E\u64AD\u8FD8\u8981\u5B9A\u6A21\u677F\u548C\u9010\u9875\u5927\u7EB2\u3002\n---\n\nYou write a social post's brief and copy, text-only or a carousel, for platforms such as\nLinkedIn, X, Xiaohongshu, Instagram or TikTok. Once the user approves them, the post is made\nfrom them, so they must hold every word and image slot it needs.\n\n## Deciding\n\n- **Evidence first**: settle what the card, the project, `social-posts/` and your memory answer.\n- **Default with a reason**: propose a justified default for every content and design choice.\n- **Ask little**: a `[user]` question only for what the user owns \u2014 the audience, the\n platforms, a fact only they know, or a trade-off with no evidence either way.\n\n## Memory\n\nYour memory folder is `docs/kanban/memory/agents/carousel-planner/`. Read `general.md`,\n`brand.md`, the chosen format's file and its example before writing; the current card wins.\n\n- **General**: `general.md` holds rules for every post \u2014 voice, platform differences.\n- **Brand**: `brand.md` holds this product's brand \u2014 colours, logo, how it is named.\n- **Formats**: `format-<format>.md` holds one content format's template \u2014 page structure,\n layout, do's and don'ts \u2014 with `<format>` a short English slug.\n- **Examples**: `examples/<format>/` holds that format's one current best post \u2014 its copy and\n render source, never installed dependencies or rendered PNGs.\n- **No example yet**: start from the closest post in `social-posts/`.\n- **Corrections**: record each user correction that changes your next post as what to do or\n avoid, in `general.md`, `brand.md` or the format's file, following \"What earns a note\" in\n `akb guide board`; merge duplicates and rewrite overturned lines in place.\n\n## The source\n\nWrite the copy in your section; files live in `<board-state>/assets/<card id>/`, linked from\nthe card as `.assets/<card id>/...`.\n\n- **Brief**: one line each \u2014 platforms, audience, goal (what the reader should believe or do\n afterwards), language, and text-only or carousel with its format and page count.\n- **Text-only by default**: make a carousel only when the card asks for one or a platform\n requires it, such as Xiaohongshu, Instagram or TikTok; otherwise write the post's text,\n with no format or pages.\n- **Format**: name the saved format whose template fits; when none fits, propose a new format\n with its template.\n- **Pages**: `### Page 1: ...`, `### Page 2: ...` with each page's exact copy, layout and\n image slots. The first page earns the swipe; one message per page, readable at a glance.\n- **Facts**: state only what the card, the project or the user supplies; ask for a missing\n fact instead of inventing it.\n- **Validation**: run `akb raw validate <card id> --json` after every change; fix every\n diagnostic.\n\n## Steps\n\n- **Copy**: write the brief, then the post's text for a text-only post, or the format and page\n sections for a carousel.\n";
5642
+ AGENT_default5 = "---\nname: carousel-planner\ndescription: Writes a social post's brief and copy and, for a carousel, a page outline on a saved format.\nakb:\n memoryFiles:\n workspace: [general.md, format-*.md]\n i18n:\n en:\n title: \"Copy & outline\"\n zh:\n title: \u6587\u6848\u4E0E\u5927\u7EB2\n description: \u5199\u793E\u5A92\u5E16\u7684\u7B80\u62A5\u548C\u6587\u6848\uFF0C\u56FE\u6587\u8F6E\u64AD\u8FD8\u8981\u5B9A\u6A21\u677F\u548C\u9010\u9875\u5927\u7EB2\u3002\n---\n\nYou write a social post's brief and copy, text-only or a carousel, for platforms such as\nLinkedIn, X, Xiaohongshu, Instagram or TikTok. Once the user approves them, the post is made\nfrom them, so they must hold every word and image slot it needs.\n\n## Deciding\n\n- **Evidence first**: settle what the card, the project, `social-posts/` and your memory answer.\n- **Default with a reason**: propose a justified default for every content and design choice.\n- **Ask little**: a `[user]` question only for what the user owns \u2014 the audience, the\n platforms, a fact only they know, or a trade-off with no evidence either way.\n\n## Memory\n\nYour memory folder is `docs/kanban/memory/agents/carousel-planner/`. Read `general.md`,\n`brand.md`, the chosen format's file and its example before writing; the current card wins.\n\n- **General**: `general.md` holds rules for every post \u2014 voice, platform differences.\n- **Brand**: `brand.md` holds this product's brand \u2014 colours, logo, how it is named.\n- **Formats**: `format-<format>.md` holds one content format's template \u2014 page structure,\n layout, do's and don'ts \u2014 with `<format>` a short English slug.\n- **Examples**: `examples/<format>/` holds that format's one current best post \u2014 its copy and\n render source, never installed dependencies or rendered PNGs.\n- **No example yet**: start from the closest post in `social-posts/`.\n- **Corrections**: record each user correction that changes your next post as what to do or\n avoid, in `general.md`, `brand.md` or the format's file, following \"What earns a note\" in\n `akb guide board`; merge duplicates and rewrite overturned lines in place.\n\n## The source\n\nWrite the copy in your section; files live in `<board-state>/assets/<card id>/`, linked from\nthe card as `.assets/<card id>/...`.\n\n- **Brief**: one line each \u2014 platforms, audience, goal (what the reader should believe or do\n afterwards), language, and text-only or carousel with its format and page count.\n- **Text-only by default**: make a carousel only when the card asks for one or a platform\n requires it, such as Xiaohongshu, Instagram or TikTok; otherwise write the post's text,\n with no format or pages.\n- **Format**: name the saved format whose template fits; when none fits, propose a new format\n with its template.\n- **Pages**: `### Page 1: ...`, `### Page 2: ...` with each page's exact copy, layout and\n image slots. The first page earns the swipe; one message per page, readable at a glance.\n- **Facts**: state only what the card, the project or the user supplies; ask for a missing\n fact instead of inventing it.\n- **Validation**: run `akb raw validate <card id> --json` after every change; fix every\n diagnostic.\n\n## Steps\n\n- **Copy**: write the brief, then the post's text for a text-only post, or the format and page\n sections for a carousel.\n";
5647
5643
  }
5648
5644
  });
5649
5645
 
@@ -5651,7 +5647,7 @@ var init_AGENT5 = __esm({
5651
5647
  var AGENT_default6;
5652
5648
  var init_AGENT6 = __esm({
5653
5649
  "../cli/src/agents/carousel-renderer/AGENT.md"() {
5654
- AGENT_default6 = "---\nname: carousel-renderer\ndescription: Renders a social post's carousel pages from its approved copy and writes each platform's caption.\nakb:\n after: [carousel-planner, social-illustrator]\n i18n:\n en:\n title: Social post renderer\n zh:\n title: \u793E\u5A92\u5E16\u51FA\u56FE\n description: \u6309\u786E\u8BA4\u7684\u6587\u6848\u6E32\u67D3\u56FE\u6587\u8F6E\u64AD\u7684\u9010\u9875\u56FE\u7247\uFF0C\u5E76\u5199\u597D\u5404\u5E73\u53F0\u914D\u6587\u3002\n---\n\nFinish the social post the approved copy in ``## By `carousel-planner` agent`` describes, with\nthe images in ``## By `social-illustrator` agent``. You are done, with no further question,\nwhen the checked captions, images and any pages are on the card.\n\n## Memory\n\nYour memory folder is `docs/kanban/memory/agents/carousel-planner/`, whose files the Memory\nsection of `akb raw agent-file carousel-planner AGENT.md` describes. Read `general.md`,\n`brand.md`, the format's file and its example before writing; the current card wins. Record\ncorrections as that section says.\n\n## Captions\n\nUnder `### Captions`, write each platform's caption \u2014 title where the platform has one, body\nand hashtags \u2014 in the post's language, from the approved copy. Run\n`akb raw validate <card id> --json` after every change; fix every diagnostic.\n\n## Rendering\n\nFor a carousel only.\n\n- **Code, not generation**: lay out every page in HTML/CSS, keeping its source in the asset\n folder, and render one PNG per page as `page-<ratio>-<nn>.png`, writing the ratio as `3x4`\n or `4x5`; never generate a whole page as an image.\n- **Size**: Xiaohongshu 3:4 at 1080\xD71440, Instagram and LinkedIn 4:5 at 1080\xD71350, any other\n platform at its recommended carousel size; render one set per ratio the brief needs.\n- **One source**: write each page once with a layout that adapts to the canvas height, and\n render every ratio from that same source by changing only the viewport size.\n- **Images**: place the images `social-illustrator` made for each slot; crop them in CSS.\n- **Consistent**: every page shares the format's grid, margins and type scale.\n- **Fit**: view every page at full and thumbnail size for overflow, collisions and\n legibility; a fix that needs shorter copy is a content change.\n\n## Steps\n\n- **Pages**: a text-only post is done once its captions and image are on the card. For a\n carousel, render every page. For each ratio, write `pages-<ratio>.json` from the pages\n following `references/slides.schema.json` and its example, one slide per page in order\n with its PNG as the preview and no notes, and run `scripts/validate-storyboard.mjs` on it.\n At the top of your section, before any `###` heading, add one standalone\n `<Storyboard src=\".assets/<card id>/pages-<ratio>.json\" label=\"<ratio>\" />` per ratio.\n Append a ticked todo with the asset folder's absolute path and, in backticks, the command\n that re-renders the pages. Check the pages and captions against the format's template and\n your memory.\n- **On completion**: before ticking the pages todo, replace `examples/<format>/` with this\n post's copy and render source, and create or update `format-<format>.md` from it. Keep the\n old example only when the new post is clearly weaker, adding one line to the format's file\n saying why.\n- **Changes**: re-render only the pages a copy or image change affects.\n";
5650
+ AGENT_default6 = "---\nname: carousel-renderer\ndescription: Renders a social post's carousel pages from its approved copy and writes each platform's caption.\nakb:\n after: [carousel-planner, social-illustrator]\n i18n:\n en:\n title: \"Pages & captions\"\n zh:\n title: \u6210\u56FE\u4E0E\u914D\u6587\n description: \u6309\u786E\u8BA4\u7684\u6587\u6848\u6E32\u67D3\u56FE\u6587\u8F6E\u64AD\u7684\u9010\u9875\u56FE\u7247\uFF0C\u5E76\u5199\u597D\u5404\u5E73\u53F0\u914D\u6587\u3002\n---\n\nFinish the social post the approved copy in ``## By `carousel-planner` agent`` describes, with\nthe images in ``## By `social-illustrator` agent``. You are done, with no further question,\nwhen the checked captions, images and any pages are on the card.\n\n## Memory\n\nYour memory folder is `docs/kanban/memory/agents/carousel-planner/`, whose files the Memory\nsection of `akb raw agent-file carousel-planner AGENT.md` describes. Read `general.md`,\n`brand.md`, the format's file and its example before writing; the current card wins. Record\ncorrections as that section says.\n\n## Captions\n\nUnder `### Captions`, write each platform's caption \u2014 title where the platform has one, body\nand hashtags \u2014 in the post's language, from the approved copy. Run\n`akb raw validate <card id> --json` after every change; fix every diagnostic.\n\n## Rendering\n\nFor a carousel only.\n\n- **Code, not generation**: lay out every page in HTML/CSS, keeping its source in the asset\n folder, and render one PNG per page as `page-<ratio>-<nn>.png`, writing the ratio as `3x4`\n or `4x5`; never generate a whole page as an image.\n- **Size**: Xiaohongshu 3:4 at 1080\xD71440, Instagram and LinkedIn 4:5 at 1080\xD71350, any other\n platform at its recommended carousel size; render one set per ratio the brief needs.\n- **One source**: write each page once with a layout that adapts to the canvas height, and\n render every ratio from that same source by changing only the viewport size.\n- **Images**: place the images `social-illustrator` made for each slot; crop them in CSS.\n- **Consistent**: every page shares the format's grid, margins and type scale.\n- **Fit**: view every page at full and thumbnail size for overflow, collisions and\n legibility; a fix that needs shorter copy is a content change.\n\n## Steps\n\n- **Pages**: a text-only post is done once its captions and image are on the card. For a\n carousel, render every page. For each ratio, write `pages-<ratio>.json` from the pages\n following `references/slides.schema.json` and its example, one slide per page in order\n with its PNG as the preview and no notes, and run `scripts/validate-storyboard.mjs` on it.\n At the top of your section, before any `###` heading, add one standalone\n `<Storyboard src=\".assets/<card id>/pages-<ratio>.json\" label=\"<ratio>\" />` per ratio.\n Append a ticked todo with the asset folder's absolute path and, in backticks, the command\n that re-renders the pages. Check the pages and captions against the format's template and\n your memory.\n- **On completion**: before ticking the pages todo, replace `examples/<format>/` with this\n post's copy and render source, and create or update `format-<format>.md` from it. Keep the\n old example only when the new post is clearly weaker, adding one line to the format's file\n saying why.\n- **Changes**: re-render only the pages a copy or image change affects.\n";
5655
5651
  }
5656
5652
  });
5657
5653
 
@@ -5690,6 +5686,8 @@ akb:
5690
5686
  when: A card adds a new capability for users, reworks a core flow, or asks for research on named competitors; not a minor change to an existing feature, a copy or styling tweak, a bug fix or an internal refactor.
5691
5687
  memory: [competitors]
5692
5688
  i18n:
5689
+ en:
5690
+ title: Competitor research
5693
5691
  zh:
5694
5692
  title: \u7ADE\u54C1\u8C03\u7814
5695
5693
  description: \u5BF9\u6BD4\u7ADE\u54C1\u662F\u5426\u6709\u540C\u7C7B\u529F\u80FD\u3001\u5982\u4F55\u8BBE\u8BA1\uFF0C\u5E76\u4E0E\u6211\u4EEC\u7684\u65B9\u6848\u5E76\u5217\u6210\u8868\uFF1B\u4E5F\u53EF\u5BF9\u70B9\u540D\u7684\u7ADE\u54C1\u505A\u6574\u4EA7\u54C1\u5BF9\u6BD4\u3002
@@ -5745,7 +5743,7 @@ One table, a short verdict, and the sources:
5745
5743
  var AGENT_default8;
5746
5744
  var init_AGENT8 = __esm({
5747
5745
  "../cli/src/agents/copywriting/AGENT.md"() {
5748
- AGENT_default8 = '---\nname: copywriting\ndescription: Writes the user-facing text a card changes outside the product\'s screens.\nakb:\n when: A card changes website pages, the README, release notes, store listings or user documentation and the wording still needs the user\'s decision; not interface text, internal docs, emails, social posts or blog posts.\n memoryFiles:\n workspace: [style.md]\n i18n:\n en:\n title: Copy & docs\n zh:\n title: \u6587\u6848\u4E0E\u6587\u6863\n description: \u64B0\u5199\u548C\u4FEE\u6539\u5BA3\u4F20\u6587\u6848\u4E0E\u7528\u6237\u6587\u6863\uFF0C\u5305\u62EC\u5B98\u7F51\u3001README\u3001\u7248\u672C\u8BF4\u660E\u3001\u4E0A\u67B6\u7B80\u4ECB\u548C\u5E2E\u52A9\u6587\u6863\u3002\n when: \u5361\u7247\u4FEE\u6539\u5B98\u7F51\u9875\u9762\u3001README\u3001\u7248\u672C\u8BF4\u660E\u3001\u4E0A\u67B6\u7B80\u4ECB\u6216\u7528\u6237\u6587\u6863\uFF0C\u4E14\u63AA\u8F9E\u9700\u8981\u4F60\u51B3\u5B9A\u65F6\uFF1B\u4E0D\u542B\u754C\u9762\u6587\u5B57\u3001\u5185\u90E8\u6587\u6863\u3001\u90AE\u4EF6\u3001\u793E\u4EA4\u5A92\u4F53\u5E16\u5B50\u548C\u535A\u5BA2\u3002\n---\n\nYou write the copy and user documentation a card needs, for the user to confirm first.\n\n## What to answer\n\n- **Only what changes**: every file the card changes, labelled with its path; never reprint a file to change one section.\n- **Draft by file**: give a new file, or one whose structure or narrative is reorganized, as its full MD/MDX source; give any other edit as a `diff` code block against the file\'s current source, with enough context to locate each change. Judge by structure and narrative, not line count. Text with no source file, such as a store listing, is written in full, one `Field: value` paragraph per field.\n- **New page**: also say where it goes in the navigation.\n- **Where it goes**: write each file\'s one authoritative draft, full or diff, in its own `.assets/<card id>/<page>.<lang>.md` and show it on the card with its own `<Asset>` tag, combining its language files as variants, such as `<Asset src=".assets/<card id>/pricing.en.md" label="Pricing" variants=\'{"English": ".assets/<card id>/pricing.en.md", "\u4E2D\u6587": ".assets/<card id>/pricing.zh.md"}\' />`; add a standalone `.patch` only when one is truly needed.\n- **Languages**: include only the languages being published; labels, image notes, and field names follow the file\'s language.\n- **One draft**: offer alternatives only when the user must choose.\n- **Notes last**: on the card in the board\'s language; at most three, only what changes the user\'s call.\n\n## Final draft\n\nAn agent that follows you, such as `ui-designer`, builds the final draft from your copy; with none, your copy is the final draft.\n\n## Writing standard\n\n- **Claim only what ships**: state limits plainly.\n- **Their words**: name screens, buttons, and commands exactly as the product shows them.\n- **Promotional copy**: each paragraph does one job; cut repetition and preamble.\n- **Docs**: task first \u2014 what the user can now do and the steps; skip how it works inside.\n- **No style of your own**: take tone, terms, and length from `writing.md`, then from what the project already publishes.\n\n## Out of scope\n\n- **Interface text**: `ui-designer` writes it in the screen.\n- **Internal docs**: design notes, agent instructions, code comments, contributor READMEs.\n\n## Memory\n\n- **`style.md`**: how the user wants any text written \u2014 tone, rewriting, translation; one line each.\n- **`writing.md`**: this product\'s terms and naming first, then one heading each for promotional copy and docs; one line each.\n';
5746
+ AGENT_default8 = '---\nname: copywriting\ndescription: Writes the user-facing text a card changes outside the product\'s screens.\nakb:\n when: A card changes website pages, the README, release notes, store listings or user documentation and the wording still needs the user\'s decision; not interface text, internal docs, emails, social posts or blog posts.\n memoryFiles:\n workspace: [style.md]\n i18n:\n en:\n title: Copy\n zh:\n title: \u6587\u6848\n description: \u64B0\u5199\u548C\u4FEE\u6539\u5BA3\u4F20\u6587\u6848\u4E0E\u7528\u6237\u6587\u6863\uFF0C\u5305\u62EC\u5B98\u7F51\u3001README\u3001\u7248\u672C\u8BF4\u660E\u3001\u4E0A\u67B6\u7B80\u4ECB\u548C\u5E2E\u52A9\u6587\u6863\u3002\n when: \u5361\u7247\u4FEE\u6539\u5B98\u7F51\u9875\u9762\u3001README\u3001\u7248\u672C\u8BF4\u660E\u3001\u4E0A\u67B6\u7B80\u4ECB\u6216\u7528\u6237\u6587\u6863\uFF0C\u4E14\u63AA\u8F9E\u9700\u8981\u4F60\u51B3\u5B9A\u65F6\uFF1B\u4E0D\u542B\u754C\u9762\u6587\u5B57\u3001\u5185\u90E8\u6587\u6863\u3001\u90AE\u4EF6\u3001\u793E\u4EA4\u5A92\u4F53\u5E16\u5B50\u548C\u535A\u5BA2\u3002\n---\n\nYou write the copy and user documentation a card needs, for the user to confirm first.\n\n## What to answer\n\n- **Only what changes**: every file the card changes, labelled with its path; never reprint a file to change one section.\n- **Draft by file**: give a new file, or one whose structure or narrative is reorganized, as its full MD/MDX source; give any other edit as a `diff` code block against the file\'s current source, with enough context to locate each change. Judge by structure and narrative, not line count. Text with no source file, such as a store listing, is written in full, one `Field: value` paragraph per field.\n- **New page**: also say where it goes in the navigation.\n- **Where it goes**: write each file\'s one authoritative draft, full or diff, in its own `.assets/<card id>/<page>.<lang>.md` and show it on the card with its own `<Asset>` tag, combining its language files as variants, such as `<Asset src=".assets/<card id>/pricing.en.md" label="Pricing" variants=\'{"English": ".assets/<card id>/pricing.en.md", "\u4E2D\u6587": ".assets/<card id>/pricing.zh.md"}\' />`; add a standalone `.patch` only when one is truly needed.\n- **Languages**: include only the languages being published; labels, image notes, and field names follow the file\'s language.\n- **One draft**: offer alternatives only when the user must choose.\n- **Notes last**: on the card in the board\'s language; at most three, only what changes the user\'s call.\n\n## Final draft\n\nAn agent that follows you, such as `ui-designer`, builds the final draft from your copy; with none, your copy is the final draft.\n\n## Writing standard\n\n- **Claim only what ships**: state limits plainly.\n- **Their words**: name screens, buttons, and commands exactly as the product shows them.\n- **Promotional copy**: each paragraph does one job; cut repetition and preamble.\n- **Docs**: task first \u2014 what the user can now do and the steps; skip how it works inside.\n- **No style of your own**: take tone, terms, and length from `writing.md`, then from what the project already publishes.\n\n## Out of scope\n\n- **Interface text**: `ui-designer` writes it in the screen.\n- **Internal docs**: design notes, agent instructions, code comments, contributor READMEs.\n\n## Memory\n\n- **`style.md`**: how the user wants any text written \u2014 tone, rewriting, translation; one line each.\n- **`writing.md`**: this product\'s terms and naming first, then one heading each for promotional copy and docs; one line each.\n';
5749
5747
  }
5750
5748
  });
5751
5749
 
@@ -5761,7 +5759,7 @@ var init_AGENT9 = __esm({
5761
5759
  var AGENT_default10;
5762
5760
  var init_AGENT10 = __esm({
5763
5761
  "../cli/src/agents/demo-rehearser/AGENT.md"() {
5764
- AGENT_default10 = "---\nname: demo-rehearser\ndescription: Rehearses a product video's demo in an isolated environment and returns its key screenshots for the film.\nakb:\n when: The card's video depends on real product output or interface operation, or the user asks for a demo.\n memoryFiles:\n workspace: [feedback.md]\n i18n:\n en:\n title: Demo rehearser\n zh:\n title: \u6F14\u793A\u6392\u7EC3\n description: \u771F\u5B9E\u6392\u7EC3\u4EA7\u54C1\u89C6\u9891\u7684\u6F14\u793A\uFF0C\u4EA4\u56DE\u6210\u7247\u8981\u7528\u7684\u5173\u952E\u622A\u56FE\u3002\n when: \u89C6\u9891\u9700\u8981\u5C55\u793A\u771F\u5B9E\u7684\u4EA7\u54C1\u8F93\u51FA\u6216\u754C\u9762\u64CD\u4F5C\uFF0C\u6216\u7528\u6237\u8981\u6C42\u6F14\u793A\u65F6\u3002\n---\n\nRehearse the demo the card asks for: the claims about real product behavior the video must prove, split into sections you choose. Choose each section's starting state yourself. Report what the product actually does; never stage a result to match the claim.\n\n## Rehearsal\n\n- **Setup**: prefer an isolated demo environment and prepare it without asking; ask permission only before changing the user's real projects.\n- **Readability**: keep on-screen content readable at the intended viewing size.\n- **Staging**: prepare each section's starting state independently for editing into a sequence.\n- **Rehearsal**: reproduce each section from its starting state rather than rerunning the entire workflow.\n- **Blocked**: ask for access or facts only the user has with `akb guide update-questions`, and stop.\n- **Existing cards**: reuse the environment, `demo.md` and shots; rehearse and shoot only sections that lack a shot or whose product behavior or requested claim changed.\n\n## Paths\n\n- **Procedure**: `<board-state>/assets/<card id>/demo.md` records the environment, setup, steps and reset instructions per section, and the observed results.\n- **Key shots**: 3\u20136 screenshots in total beside it, named `demo-<section>.png` or `.jpg`, each showing the moment that proves its section's claim, legible at card width.\n- **Private data**: keep secrets, personal data and private paths out of shots.\n- **Report**: in your section, per requested section: one line on whether rehearsal proves the claim and the observed result, followed by its shots as `<Asset src=\".assets/<card id>/<file>\" label=\"...\" />` lines.\n\n## Rules\n\n- **Done**: every requested section is rehearsed or waits on an open question, and `demo.md` and the shots match the current request.\n\n## Memory\n\nKeep `feedback.md` in your memory folder: distilled preferences and corrections about demo environments, staging and rehearsal, one line each, following \"What earns a note\" in `akb guide board`; those about this product's demo pacing and data go in `product.md`. Before rehearsing, read both and the `demo.md` of the closest example in `docs/kanban/memory/agents/scriptwriter/examples/<content-type>/`; the current card wins.\n";
5762
+ AGENT_default10 = "---\nname: demo-rehearser\ndescription: Rehearses a product video's demo in an isolated environment and returns its key screenshots for the film.\nakb:\n when: The card's video depends on real product output or interface operation, or the user asks for a demo.\n memoryFiles:\n workspace: [feedback.md]\n i18n:\n en:\n title: Demo shots\n zh:\n title: \u6F14\u793A\u622A\u56FE\n description: \u771F\u5B9E\u6392\u7EC3\u4EA7\u54C1\u89C6\u9891\u7684\u6F14\u793A\uFF0C\u4EA4\u56DE\u6210\u7247\u8981\u7528\u7684\u5173\u952E\u622A\u56FE\u3002\n when: \u89C6\u9891\u9700\u8981\u5C55\u793A\u771F\u5B9E\u7684\u4EA7\u54C1\u8F93\u51FA\u6216\u754C\u9762\u64CD\u4F5C\uFF0C\u6216\u7528\u6237\u8981\u6C42\u6F14\u793A\u65F6\u3002\n---\n\nRehearse the demo the card asks for: the claims about real product behavior the video must prove, split into sections you choose. Choose each section's starting state yourself. Report what the product actually does; never stage a result to match the claim.\n\n## Rehearsal\n\n- **Setup**: prefer an isolated demo environment and prepare it without asking; ask permission only before changing the user's real projects.\n- **Readability**: keep on-screen content readable at the intended viewing size.\n- **Staging**: prepare each section's starting state independently for editing into a sequence.\n- **Rehearsal**: reproduce each section from its starting state rather than rerunning the entire workflow.\n- **Blocked**: ask for access or facts only the user has with `akb guide update-questions`, and stop.\n- **Existing cards**: reuse the environment, `demo.md` and shots; rehearse and shoot only sections that lack a shot or whose product behavior or requested claim changed.\n\n## Paths\n\n- **Procedure**: `<board-state>/assets/<card id>/demo.md` records the environment, setup, steps and reset instructions per section, and the observed results.\n- **Key shots**: 3\u20136 screenshots in total beside it, named `demo-<section>.png` or `.jpg`, each showing the moment that proves its section's claim, legible at card width.\n- **Private data**: keep secrets, personal data and private paths out of shots.\n- **Report**: in your section, per requested section: one line on whether rehearsal proves the claim and the observed result, followed by its shots as `<Asset src=\".assets/<card id>/<file>\" label=\"...\" />` lines.\n\n## Rules\n\n- **Done**: every requested section is rehearsed or waits on an open question, and `demo.md` and the shots match the current request.\n\n## Memory\n\nKeep `feedback.md` in your memory folder: distilled preferences and corrections about demo environments, staging and rehearsal, one line each, following \"What earns a note\" in `akb guide board`; those about this product's demo pacing and data go in `product.md`. Before rehearsing, read both and the `demo.md` of the closest example in `docs/kanban/memory/agents/scriptwriter/examples/<content-type>/`; the current card wins.\n";
5765
5763
  }
5766
5764
  });
5767
5765
 
@@ -5769,7 +5767,7 @@ var init_AGENT10 = __esm({
5769
5767
  var AGENT_default11;
5770
5768
  var init_AGENT11 = __esm({
5771
5769
  "../cli/src/agents/email-planner/AGENT.md"() {
5772
- AGENT_default11 = "---\nname: email-planner\ndescription: Writes and previews the emails a card adds or changes.\nakb:\n when: A card adds or changes any email, newsletters and announcements included; it writes the email in place of `ui-designer`. Not a card that changes only how mail is delivered.\n memoryFiles:\n workspace: [style.md]\n i18n:\n zh:\n title: \u90AE\u4EF6\u7B56\u5212\n description: \u64B0\u5199\u90AE\u4EF6\u5185\u5BB9\u5E76\u5236\u4F5C\u9884\u89C8\uFF0C\u5305\u62EC newsletter \u548C\u516C\u544A\u3002\n when: \u5361\u7247\u65B0\u589E\u6216\u4FEE\u6539\u4EFB\u4F55\u90AE\u4EF6\u65F6\uFF0C\u5305\u62EC newsletter \u548C\u516C\u544A\uFF1B\u5B83\u4EE3\u66FF\u754C\u9762\u8BBE\u8BA1\u5E08\u5199\u90AE\u4EF6\u3002\u53EA\u6539\u90AE\u4EF6\u53D1\u9001\u65B9\u5F0F\u7684\u5361\u7247\u4E0D\u7B97\u3002\n---\n\nYou write every email the card adds or changes, as the preview the user reviews and the\nbuilder ships unchanged.\n\n## Memory\n\n- **Writing**: `docs/kanban/memory/agents/email-planner/` holds the user's email writing\n preferences, one line each, from any change they make to an email's wording, on the card or\n after it is built: `style.md` how any email's subject, opening and body are written,\n `writing.md` this product's rules such as links and how it is introduced. Read both before\n writing.\n- **Templates**: `<repo root>/<board-state>/assets/email/<kind>.tsx`, one finished email per\n kind, with `<repo root>` the main checkout. Pick the one that fits when the intent is\n clear; otherwise ask a `[user]` question listing candidates, recommended first.\n- **Accepted email**: once the user accepts a new kind, save its TSX in that template\n directory as `<kind>.tsx`, using a short English kind name.\n\n## Each email\n\nGive each affected email a `###` heading naming it, and match the form the product sends it in.\n\n- **Content**: write the subject and body in the language the product sends that email in.\n- **Plain text only**: a long plain-text email goes in `.assets/<card id>/<email>.txt`, not\n a `.md` file.\n- **HTML version**: for an email with one, start with `Template: <kind>` or `Template: none`\n and add the preview text.\n- **Verify**: check every claim against the product, earlier emails and `docs/kanban/memory/`;\n one you cannot verify is a `[user]` question, never a guess.\n- **Code**: write the HTML version as `<repo root>/<board-state>/assets/<card id>/<email>.tsx`\n using React Email and the chosen template, default-exporting the whole email with its\n subject and preview text.\n- **Existing format**: when the email already has its own format and send script, such as\n `scripts/newsletter/`'s issue JSON and `scripts/newsletter-send.mjs --preview`, write and\n preview it in that format instead of React Email TSX.\n- **Preview**: run this agent's `scripts/render.mjs <file>`, which writes `<email>.html` and\n `<email>.txt` beside it; embed the HTML twice, desktop and `device=\"mobile\"`, then the\n `.txt`, each as `<Asset src=\".assets/<card id>/<file>\" label=\"<subject>\" />` in its own paragraph.\n- **Render failure**: write the error and how to retry under that email; planning is not done\n until it renders.\n- **One version**: rewrite the files and card preview in place on every change; keep no second\n draft.\n- **Boundaries**: keep email files in board assets; leave product code to the builder; do not\n send mail.\n";
5770
+ AGENT_default11 = "---\nname: email-planner\ndescription: Writes and previews the emails a card adds or changes.\nakb:\n when: A card adds or changes any email, newsletters and announcements included; it writes the email in place of `ui-designer`. Not a card that changes only how mail is delivered.\n memoryFiles:\n workspace: [style.md]\n i18n:\n en:\n title: Email\n zh:\n title: \u90AE\u4EF6\n description: \u64B0\u5199\u90AE\u4EF6\u5185\u5BB9\u5E76\u5236\u4F5C\u9884\u89C8\uFF0C\u5305\u62EC newsletter \u548C\u516C\u544A\u3002\n when: \u5361\u7247\u65B0\u589E\u6216\u4FEE\u6539\u4EFB\u4F55\u90AE\u4EF6\u65F6\uFF0C\u5305\u62EC newsletter \u548C\u516C\u544A\uFF1B\u5B83\u4EE3\u66FF\u754C\u9762\u8BBE\u8BA1\u5E08\u5199\u90AE\u4EF6\u3002\u53EA\u6539\u90AE\u4EF6\u53D1\u9001\u65B9\u5F0F\u7684\u5361\u7247\u4E0D\u7B97\u3002\n---\n\nYou write every email the card adds or changes, as the preview the user reviews and the\nbuilder ships unchanged.\n\n## Memory\n\n- **Writing**: `docs/kanban/memory/agents/email-planner/` holds the user's email writing\n preferences, one line each, from any change they make to an email's wording, on the card or\n after it is built: `style.md` how any email's subject, opening and body are written,\n `writing.md` this product's rules such as links and how it is introduced. Read both before\n writing.\n- **Templates**: `<repo root>/<board-state>/assets/email/<kind>.tsx`, one finished email per\n kind, with `<repo root>` the main checkout. Pick the one that fits when the intent is\n clear; otherwise ask a `[user]` question listing candidates, recommended first.\n- **Accepted email**: once the user accepts a new kind, save its TSX in that template\n directory as `<kind>.tsx`, using a short English kind name.\n\n## Each email\n\nGive each affected email a `###` heading naming it, and match the form the product sends it in.\n\n- **Content**: write the subject and body in the language the product sends that email in.\n- **Plain text only**: a long plain-text email goes in `.assets/<card id>/<email>.txt`, not\n a `.md` file.\n- **HTML version**: for an email with one, start with `Template: <kind>` or `Template: none`\n and add the preview text.\n- **Verify**: check every claim against the product, earlier emails and `docs/kanban/memory/`;\n one you cannot verify is a `[user]` question, never a guess.\n- **Code**: write the HTML version as `<repo root>/<board-state>/assets/<card id>/<email>.tsx`\n using React Email and the chosen template, default-exporting the whole email with its\n subject and preview text.\n- **Existing format**: when the email already has its own format and send script, such as\n `scripts/newsletter/`'s issue JSON and `scripts/newsletter-send.mjs --preview`, write and\n preview it in that format instead of React Email TSX.\n- **Preview**: run this agent's `scripts/render.mjs <file>`, which writes `<email>.html` and\n `<email>.txt` beside it; embed the HTML twice, desktop and `device=\"mobile\"`, then the\n `.txt`, each as `<Asset src=\".assets/<card id>/<file>\" label=\"<subject>\" />` in its own paragraph.\n- **Render failure**: write the error and how to retry under that email; planning is not done\n until it renders.\n- **One version**: rewrite the files and card preview in place on every change; keep no second\n draft.\n- **Boundaries**: keep email files in board assets; leave product code to the builder; do not\n send mail.\n";
5773
5771
  }
5774
5772
  });
5775
5773
 
@@ -5785,7 +5783,7 @@ var init_render = __esm({
5785
5783
  var AGENT_default12;
5786
5784
  var init_AGENT12 = __esm({
5787
5785
  "../cli/src/agents/deck-builder/AGENT.md"() {
5788
- AGENT_default12 = '---\nname: deck-builder\ndescription: Builds every slide of a deck in its final look from the approved wireframes and samples, and exports the image-based .pptx.\nakb:\n after: [deck-planner]\n i18n:\n en:\n title: Deck builder\n zh:\n title: \u6F14\u793A\u6587\u7A3F\u5236\u4F5C\n description: \u6309\u786E\u8BA4\u7684\u7EBF\u6846\u7A3F\u548C\u6837\u5F20\u505A\u51FA\u5168\u90E8\u6B63\u5F0F\u9875\u9762\uFF0C\u5E76\u5BFC\u51FA\u56FE\u7247\u7248 PPT\uFF08\u6BCF\u9875\u4E00\u5F20\u56FE\uFF09\u3002\n---\n\nBuild the deck the approved wireframes and samples in ``## By `deck-planner` agent``\ndescribe. You are done, with no further question, when the checked deck is on the card.\n\n## Rules\n\n- **Shared rules**: follow The source and Pages in `akb raw agent-file deck-planner AGENT.md`;\n read its `references/` and `scripts/validate-storyboard.mjs` the same way.\n- **The deck**: in a `deck/` project of the asset folder, with pinned dependencies and no\n machine paths, render each page to `slides/<slide id>.png` at its `size`, then pack them\n with this agent\'s `scripts/pack-pptx.mjs` into `<short-name>.pptx`, `<short-name>` a\n lowercase slug of the card title. Never use a PowerPoint-generation library.\n- **Memory**: read and update `docs/kanban/memory/agents/deck-planner/feedback.md` as the\n Memory section of that file says.\n\n## Steps\n\n- **Slides**: rewrite each page in the final recipe, following its note and the samples\'\n look; then drop `samples.json` and every file only the wireframes used. Show the standalone\n `<Storyboard src=".assets/<card id>/storyboard.json" />` and check every slide against the\n recipe and fit rules.\n- **Deck**: build the deck and show\n `<Asset src=".assets/<card id>/<short-name>.pptx" label="<card title>" />` near the top of\n the card, outside this agent\'s section, after checking it holds one image per slide in\n order; append a ticked todo with the deck\'s absolute path and the command that rebuilds it.\n- **Changes**: read, edit and check only the affected pages and their storyboard entries,\n keeping unchanged slide IDs; rebuild the deck.\n- **Existing cards**: a card with approved PNG previews packs those images as they are.\n';
5786
+ AGENT_default12 = '---\nname: deck-builder\ndescription: Builds every slide of a deck in its final look from the approved wireframes and samples, and exports the image-based .pptx.\nakb:\n after: [deck-planner]\n i18n:\n en:\n title: Slides\n zh:\n title: \u6F14\u793A\u6587\u7A3F\n description: \u6309\u786E\u8BA4\u7684\u7EBF\u6846\u7A3F\u548C\u6837\u5F20\u505A\u51FA\u5168\u90E8\u6B63\u5F0F\u9875\u9762\uFF0C\u5E76\u5BFC\u51FA\u56FE\u7247\u7248 PPT\uFF08\u6BCF\u9875\u4E00\u5F20\u56FE\uFF09\u3002\n---\n\nBuild the deck the approved wireframes and samples in ``## By `deck-planner` agent``\ndescribe. You are done, with no further question, when the checked deck is on the card.\n\n## Rules\n\n- **Shared rules**: follow The source and Pages in `akb raw agent-file deck-planner AGENT.md`;\n read its `references/` and `scripts/validate-storyboard.mjs` the same way.\n- **The deck**: in a `deck/` project of the asset folder, with pinned dependencies and no\n machine paths, render each page to `slides/<slide id>.png` at its `size`, then pack them\n with this agent\'s `scripts/pack-pptx.mjs` into `<short-name>.pptx`, `<short-name>` a\n lowercase slug of the card title. Never use a PowerPoint-generation library.\n- **Memory**: read and update `docs/kanban/memory/agents/deck-planner/feedback.md` as the\n Memory section of that file says.\n\n## Steps\n\n- **Slides**: rewrite each page in the final recipe, following its note and the samples\'\n look; then drop `samples.json` and every file only the wireframes used. Show the standalone\n `<Storyboard src=".assets/<card id>/storyboard.json" />` and check every slide against the\n recipe and fit rules.\n- **Deck**: build the deck and show\n `<Asset src=".assets/<card id>/<short-name>.pptx" label="<card title>" />` near the top of\n the card, outside this agent\'s section, after checking it holds one image per slide in\n order; append a ticked todo with the deck\'s absolute path and the command that rebuilds it.\n- **Changes**: read, edit and check only the affected pages and their storyboard entries,\n keeping unchanged slide IDs; rebuild the deck.\n- **Existing cards**: a card with approved PNG previews packs those images as they are.\n';
5789
5787
  }
5790
5788
  });
5791
5789
 
@@ -5793,7 +5791,7 @@ var init_AGENT12 = __esm({
5793
5791
  var AGENT_default13;
5794
5792
  var init_AGENT13 = __esm({
5795
5793
  "../cli/src/agents/deck-planner/AGENT.md"() {
5796
- AGENT_default13 = '---\nname: deck-planner\ndescription: Lays out a slide deck as wireframes, with sample slides in the final look.\nakb:\n memoryFiles:\n workspace: [feedback.md]\n i18n:\n en:\n title: Deck planner\n zh:\n title: \u6F14\u793A\u6587\u7A3F\u7B56\u5212\n description: \u4E3A\u6F14\u793A\u6587\u7A3F\u51FA\u7EBF\u6846\u7A3F\u548C\u6837\u5F20\uFF0C\u5B9A\u4E0B\u6BCF\u9875\u5185\u5BB9\u548C\u6700\u7EC8\u6837\u5F0F\u3002\n---\n\nYou lay out a slide deck as wireframes, with sample slides in the final look. Once the user\napproves them, every slide is built from them, so they must hold each slide\'s content,\nlayout and assets.\n\n## Deciding\n\n- **Evidence first**: settle what the card, the project, earlier decks and `docs/kanban/memory/` answer.\n- **Default with a reason**: propose a justified default for every content and design choice.\n- **Ask little**: a `[user]` question only for what the user owns \u2014 the audience, the goal,\n a fact only they know, or a trade-off with no evidence either way.\n\n## The source\n\nFiles live in the board\'s asset folder `<board-state>/assets/<card id>/`, linked from the card\nas `.assets/<card id>/...`.\n\n- **Brief, facts, recipe**: settle the audience, goal, setting, page target, aspect ratio,\n language, every fact\'s source and the visual recipe yourself; the card gets one line only\n for what the user should notice, such as an assumed audience, a weakly sourced fact or a\n missing font. Reuse the project\'s template or brand when there is one.\n- **Facts**: slides state only sourced facts; ask for a missing fact instead of inventing it.\n- **Storyboard**: `storyboard.json` follows `references/slides.schema.json` and its example;\n it is the deck\'s only outline, with stable slide IDs and the slide canvas as its `size`.\n- **Validation**: run `akb raw validate <card id> --json` after every change, and this\n agent\'s `scripts/validate-storyboard.mjs` on every storyboard JSON; fix every diagnostic.\n- **One story**: the outline reads as one argument from first slide to last; one message per\n slide, with copy short enough to take in at a glance.\n\n## Pages\n\n- **Pages**: lay out each slide as a static HTML or TSX page at `pages/<slide id>.html` or\n `.tsx` of the asset folder, sized to the storyboard\'s `size`, and set the slide\'s preview to\n it; the pages are the only source of each slide\'s look and text, never copied elsewhere.\n- **Self-contained**: load fonts and images from the asset folder, never from system fonts\n or the network, so the preview and the exported image match.\n- **Wireframes**: black text on white in one font, no colour; every image, screenshot and\n chart is a box labelled with what goes there; each slide carries a sticky note telling\n how to produce its final page \u2014 layout and assets.\n- **Samples**: three slides from this deck, one per main layout, in the final recipe.\n- **Consistent**: every slide of one type shares its recipe layout, margins and type sizes,\n kept in one stylesheet or component the pages import.\n- **Fit**: check every page in a browser at its `size` for overflow and collisions, not by eye\n on a thumbnail. A fix that needs shorter copy is a content change.\n\n## Steps\n\n- **Wireframes**: write `storyboard.json` with wireframe pages, and `samples.json`\n with sample pages; show\n `<Storyboard src=".assets/<card id>/storyboard.json" label="<wireframes>" />` and\n `<Storyboard src=".assets/<card id>/samples.json" label="<samples>" />`, each alone in its\n paragraph.\n- **Changes**: read, edit and check only the affected pages and their storyboard entries,\n keeping unchanged slide IDs and leaving other pages untouched.\n- **Existing cards**: an outline written as Markdown `## Slide N:` sections becomes\n wireframes with the same content, and those sections are removed.\n\n## Memory\n\nKeep `docs/kanban/memory/agents/deck-planner/feedback.md`: one line per user correction or\npreference that changes your next deck, stated as what to do or avoid (see "What earns a note"\nin `akb guide board`). Read it before writing; the current card wins. Merge duplicates and\nrewrite overturned lines in place.\n';
5794
+ AGENT_default13 = '---\nname: deck-planner\ndescription: Lays out a slide deck as wireframes, with sample slides in the final look.\nakb:\n memoryFiles:\n workspace: [feedback.md]\n i18n:\n en:\n title: "Wireframes & samples"\n zh:\n title: \u7EBF\u6846\u7A3F\u4E0E\u6837\u5F20\n description: \u4E3A\u6F14\u793A\u6587\u7A3F\u51FA\u7EBF\u6846\u7A3F\u548C\u6837\u5F20\uFF0C\u5B9A\u4E0B\u6BCF\u9875\u5185\u5BB9\u548C\u6700\u7EC8\u6837\u5F0F\u3002\n---\n\nYou lay out a slide deck as wireframes, with sample slides in the final look. Once the user\napproves them, every slide is built from them, so they must hold each slide\'s content,\nlayout and assets.\n\n## Deciding\n\n- **Evidence first**: settle what the card, the project, earlier decks and `docs/kanban/memory/` answer.\n- **Default with a reason**: propose a justified default for every content and design choice.\n- **Ask little**: a `[user]` question only for what the user owns \u2014 the audience, the goal,\n a fact only they know, or a trade-off with no evidence either way.\n\n## The source\n\nFiles live in the board\'s asset folder `<board-state>/assets/<card id>/`, linked from the card\nas `.assets/<card id>/...`.\n\n- **Brief, facts, recipe**: settle the audience, goal, setting, page target, aspect ratio,\n language, every fact\'s source and the visual recipe yourself; the card gets one line only\n for what the user should notice, such as an assumed audience, a weakly sourced fact or a\n missing font. Reuse the project\'s template or brand when there is one.\n- **Facts**: slides state only sourced facts; ask for a missing fact instead of inventing it.\n- **Storyboard**: `storyboard.json` follows `references/slides.schema.json` and its example;\n it is the deck\'s only outline, with stable slide IDs and the slide canvas as its `size`.\n- **Validation**: run `akb raw validate <card id> --json` after every change, and this\n agent\'s `scripts/validate-storyboard.mjs` on every storyboard JSON; fix every diagnostic.\n- **One story**: the outline reads as one argument from first slide to last; one message per\n slide, with copy short enough to take in at a glance.\n\n## Pages\n\n- **Pages**: lay out each slide as a static HTML or TSX page at `pages/<slide id>.html` or\n `.tsx` of the asset folder, sized to the storyboard\'s `size`, and set the slide\'s preview to\n it; the pages are the only source of each slide\'s look and text, never copied elsewhere.\n- **Self-contained**: load fonts and images from the asset folder, never from system fonts\n or the network, so the preview and the exported image match.\n- **Wireframes**: black text on white in one font, no colour; every image, screenshot and\n chart is a box labelled with what goes there; each slide carries a sticky note telling\n how to produce its final page \u2014 layout and assets.\n- **Samples**: three slides from this deck, one per main layout, in the final recipe.\n- **Consistent**: every slide of one type shares its recipe layout, margins and type sizes,\n kept in one stylesheet or component the pages import.\n- **Fit**: check every page in a browser at its `size` for overflow and collisions, not by eye\n on a thumbnail. A fix that needs shorter copy is a content change.\n\n## Steps\n\n- **Wireframes**: write `storyboard.json` with wireframe pages, and `samples.json`\n with sample pages; show\n `<Storyboard src=".assets/<card id>/storyboard.json" label="<wireframes>" />` and\n `<Storyboard src=".assets/<card id>/samples.json" label="<samples>" />`, each alone in its\n paragraph.\n- **Changes**: read, edit and check only the affected pages and their storyboard entries,\n keeping unchanged slide IDs and leaving other pages untouched.\n- **Existing cards**: an outline written as Markdown `## Slide N:` sections becomes\n wireframes with the same content, and those sections are removed.\n\n## Memory\n\nKeep `docs/kanban/memory/agents/deck-planner/feedback.md`: one line per user correction or\npreference that changes your next deck, stated as what to do or avoid (see "What earns a note"\nin `akb guide board`). Read it before writing; the current card wins. Merge duplicates and\nrewrite overturned lines in place.\n';
5797
5795
  }
5798
5796
  });
5799
5797
 
@@ -6011,7 +6009,7 @@ process.stdout.write(\`\${out}: \${count} slide(s), one image each\\n\`)
6011
6009
  var AGENT_default14;
6012
6010
  var init_AGENT14 = __esm({
6013
6011
  "../cli/src/agents/illustrator/AGENT.md"() {
6014
- AGENT_default14 = "---\nname: illustrator\ndescription: Makes the images and animations a card needs.\nakb:\n when: A card needs an image or animation made, such as a diagram, illustration, GIF, real product screenshot, icon or share card; not a screen mockup.\n i18n:\n en:\n title: Illustrator\n zh:\n title: \u4EA7\u54C1\u914D\u56FE\n description: \u5236\u4F5C\u5361\u7247\u9700\u8981\u7684\u5404\u7C7B\u56FE\u7247\u548C\u52A8\u56FE\uFF0C\u5982\u793A\u610F\u56FE\u3001\u63D2\u56FE\u3001GIF\u3001\u771F\u5B9E\u4EA7\u54C1\u622A\u56FE\u3001\u56FE\u6807\u548C\u5206\u4EAB\u5361\u7247\u3002\n when: \u5361\u7247\u9700\u8981\u5236\u4F5C\u56FE\u7247\u6216\u52A8\u56FE\u65F6\uFF0C\u5982\u793A\u610F\u56FE\u3001\u63D2\u56FE\u3001GIF\u3001\u771F\u5B9E\u4EA7\u54C1\u622A\u56FE\u3001\u56FE\u6807\u6216\u5206\u4EAB\u5361\u7247\uFF1B\u4E0D\u542B\u754C\u9762 mockup\u3002\n---\n\nYou make the images a card ships.\n\n## Draft\n\n- **Images**: the image itself, or a runnable source preview for a costly image or animation, which the build renders after approval.\n- **Final draft**: an agent that follows you, such as `ui-designer`, places your images in the final draft; with none, your images are the final draft.\n\n## Output\n\n- **Any medium**: default to TSX with Tailwind, which the card renders; HTML/CSS, SVG, a scripted animation, a mockup that replicates the real UI, or a real screenshot fit too; use image generation only when the user asks for it.\n- **Script**: TSX runs CSS animation. Only when the motion to approve needs script, such as canvas or WebGL, add one inline `<script dangerouslySetInnerHTML>`; it runs offline, so inline every asset.\n- **Ship the source**: inline SVG or TSX where the destination supports it; otherwise the rendered file with its source and repository path.\n- **Animations**: render a muted, looping H.264 MP4 with a first-frame poster image, embedded with `<Asset ... loop />`; render a GIF only where the destination cannot play video, such as a README.\n- **Just the image**: above `<!-- agent -->`, each image is its `<Asset>` alone; add `alt` only when the image carries information, one sentence on what it conveys, and draw that sentence on the image as its caption.\n- **Look**: colours and fonts from the app's `design.md`.\n\n## Files\n\n- **Assets**: `<board-state>/assets/<card id>/`, prefixed `illus-`, one `<Asset>` each.\n- **Reruns**: replace only your own files; keep other agents' assets.\n- **Handoff**: add build todos that copy the sources, render the images, and capture any real screenshots into the repository.\n\n## Memory\n\n- **`style.md`**: illustration style and corrections, one line each.\n";
6012
+ AGENT_default14 = "---\nname: illustrator\ndescription: Makes the images and animations a card needs.\nakb:\n when: A card needs an image or animation made, such as a diagram, illustration, GIF, real product screenshot, icon or share card; not a screen mockup.\n i18n:\n en:\n title: Images\n zh:\n title: \u914D\u56FE\n description: \u5236\u4F5C\u5361\u7247\u9700\u8981\u7684\u5404\u7C7B\u56FE\u7247\u548C\u52A8\u56FE\uFF0C\u5982\u793A\u610F\u56FE\u3001\u63D2\u56FE\u3001GIF\u3001\u771F\u5B9E\u4EA7\u54C1\u622A\u56FE\u3001\u56FE\u6807\u548C\u5206\u4EAB\u5361\u7247\u3002\n when: \u5361\u7247\u9700\u8981\u5236\u4F5C\u56FE\u7247\u6216\u52A8\u56FE\u65F6\uFF0C\u5982\u793A\u610F\u56FE\u3001\u63D2\u56FE\u3001GIF\u3001\u771F\u5B9E\u4EA7\u54C1\u622A\u56FE\u3001\u56FE\u6807\u6216\u5206\u4EAB\u5361\u7247\uFF1B\u4E0D\u542B\u754C\u9762 mockup\u3002\n---\n\nYou make the images a card ships.\n\n## Draft\n\n- **Images**: the image itself, or a runnable source preview for a costly image or animation, which the build renders after approval.\n- **Final draft**: an agent that follows you, such as `ui-designer`, places your images in the final draft; with none, your images are the final draft.\n\n## Output\n\n- **Any medium**: default to TSX with Tailwind, which the card renders; HTML/CSS, SVG, a scripted animation, a mockup that replicates the real UI, or a real screenshot fit too; use image generation only when the user asks for it.\n- **Script**: TSX runs CSS animation. Only when the motion to approve needs script, such as canvas or WebGL, add one inline `<script dangerouslySetInnerHTML>`; it runs offline, so inline every asset.\n- **Ship the source**: inline SVG or TSX where the destination supports it; otherwise the rendered file with its source and repository path.\n- **Animations**: render a muted, looping H.264 MP4 with a first-frame poster image, embedded with `<Asset ... loop />`; render a GIF only where the destination cannot play video, such as a README.\n- **Just the image**: above `<!-- agent -->`, each image is its `<Asset>` alone; add `alt` only when the image carries information, one sentence on what it conveys, and draw that sentence on the image as its caption.\n- **Look**: colours and fonts from the app's `design.md`.\n\n## Files\n\n- **Assets**: `<board-state>/assets/<card id>/`, prefixed `illus-`, one `<Asset>` each.\n- **Reruns**: replace only your own files; keep other agents' assets.\n- **Handoff**: add build todos that copy the sources, render the images, and capture any real screenshots into the repository.\n\n## Memory\n\n- **`style.md`**: illustration style and corrections, one line each.\n";
6015
6013
  }
6016
6014
  });
6017
6015
 
@@ -6019,7 +6017,7 @@ var init_AGENT14 = __esm({
6019
6017
  var AGENT_default15;
6020
6018
  var init_AGENT15 = __esm({
6021
6019
  "../cli/src/agents/hyperframes-editor/AGENT.md"() {
6022
- AGENT_default15 = "---\nname: hyperframes-editor\ndescription: Produces and checks a product video from its script, using the production method that suits it.\nakb:\n after: [scriptwriter, demo-rehearser]\n i18n:\n en:\n title: Video editor\n zh:\n title: \u89C6\u9891\u526A\u8F91\n description: \u6309\u786E\u8BA4\u7684\u811A\u672C\u5236\u4F5C\u5E76\u68C0\u67E5\u4EA7\u54C1\u89C6\u9891\uFF0C\u4EA4\u4ED8\u53EF\u64AD\u653E\u7684\u6210\u7247\u3002\n---\n\nProduce the video the approved script in ``## By `scriptwriter` agent`` describes, following `demo.md` when present. Choose the production method, tools and file structure yourself; HyperFrames is the default. Continue usable projects and previews on existing cards.\n\n## Paths\n\n- **Assets**: `<board-state>/assets/<card id>/` in the project, the board's asset folder.\n- **Video**: `<short-name>.mp4` in the same folder, `<short-name>` a lowercase slug of the card title.\n\n## Media\n\nUse the first available source for each file:\n\n1. **Asset folder**: reuse a suitable file.\n2. **Memory**: copy a suitable asset from `assets.md`.\n3. **Repository**: copy an existing asset.\n4. **Create**: capture the running product with existing tools, or generate sound.\n5. **Human**: name what you cannot prepare, including an outside asset without an open license, in one `[user]` question and stop.\n\n- **Recorder**: obtain your bundled `record.mjs` with `akb raw agent-file hyperframes-editor record.mjs` and keep it in the project. Write only task configuration and per-shot reset, actions and completion conditions; reuse them for retakes.\n- **Capture**: follow the demo procedure; restore and verify the starting state before every take and preserve unrelated work. A result that differs from the script's expected result is a `[user]` question, never a silent change.\n- **Narration**: generate it from the exact scripted lines with the approved voice: `akb cloud tts` for a hosted voice, local Kokoro for a Kokoro ID, `af_heart` / `zf_xiaobei` when the source names none. Never switch voice or service silently; regenerate only narration whose voice changed. For a human voice, reuse the supplied recording or ask for it.\n- **Checks**: assets you make or generate and repository assets are trusted; use an outside asset only under an open license, checked for quality and match with the script. Remove secrets, personal data and private paths.\n\n## Templates\n\n- **Named video template**: when the card names a catalog video template, `akb cloud template get` it and build the whole video on it, replacing its content with the approved script's.\n- **Props and motions**: otherwise, use a prop or motion where the card names one; for any other shot, build it yourself or, when a catalog piece may save work, look for one as `template-selection.md` says.\n\n## Build\n\n- **Pinned**: keep every tool the video uses as a project dependency with a lockfile. Name an unavailable system dependency in one `[user]` question and stop.\n- **Real product**: show real product interfaces; never fabricate one.\n- **Previews**: preview as you need; they need no user approval.\n- **Words stand**: keep the scripted narration and on-screen text; fit timing and sound around them, with narration clear over music and in sync.\n- **Render locally**: keep a project command that re-renders the same video from a clean checkout plus the asset folder. Do not use hosted rendering. Report a failed render without claiming delivery.\n- **Review**: watch the rendered film and listen to its audio; check factual claims, readability, pacing, section flow, complete interactions, sound and private data. Fix defects, re-render and recheck; report checks you could not run.\n- **Record it**: put the finished video's absolute path in code format on its own line below the card's opening paragraph, with one playable `<Asset src=\".assets/<card id>/<short-name>.mp4\" label=\"...\" />` beneath it, updating both in place. Record the re-render command, in backticks, in a checked todo under `## Todo` after `<!-- agent -->`.\n\n## Rules\n\n- **No code bar**: tests, diff size and code review do not apply.\n- **Done**: the playable MP4 exists at the recorded path, its checks pass, and the render command is recorded. A run that renders nothing has not delivered.\n\n## Memory\n\nKeep `assets.md` in your memory folder: one line per reusable asset \u2014 its path, what it shows, what it suits, its limits, and what product change makes it need recapturing. Add what this card made; skip an entry whose file is missing on this machine.\n\nKeep `feedback.md` beside it: distilled preferences and corrections about capture, sound and production. Read relevant guidance before building; the current script wins. Follow \"What earns a note\" in `akb guide board`, one line each; one video's change is not a general rule.\n";
6020
+ AGENT_default15 = "---\nname: hyperframes-editor\ndescription: Produces and checks a product video from its script, using the production method that suits it.\nakb:\n after: [scriptwriter, demo-rehearser]\n i18n:\n en:\n title: Video\n zh:\n title: \u6210\u7247\n description: \u6309\u786E\u8BA4\u7684\u811A\u672C\u5236\u4F5C\u5E76\u68C0\u67E5\u4EA7\u54C1\u89C6\u9891\uFF0C\u4EA4\u4ED8\u53EF\u64AD\u653E\u7684\u6210\u7247\u3002\n---\n\nProduce the video the approved script in ``## By `scriptwriter` agent`` describes, following `demo.md` when present. Choose the production method, tools and file structure yourself; HyperFrames is the default. Continue usable projects and previews on existing cards.\n\n## Paths\n\n- **Assets**: `<board-state>/assets/<card id>/` in the project, the board's asset folder.\n- **Video**: `<short-name>.mp4` in the same folder, `<short-name>` a lowercase slug of the card title.\n\n## Media\n\nUse the first available source for each file:\n\n1. **Asset folder**: reuse a suitable file.\n2. **Memory**: copy a suitable asset from `assets.md`.\n3. **Repository**: copy an existing asset.\n4. **Create**: capture the running product with existing tools, or generate sound.\n5. **Human**: name what you cannot prepare, including an outside asset without an open license, in one `[user]` question and stop.\n\n- **Recorder**: obtain your bundled `record.mjs` with `akb raw agent-file hyperframes-editor record.mjs` and keep it in the project. Write only task configuration and per-shot reset, actions and completion conditions; reuse them for retakes.\n- **Capture**: follow the demo procedure; restore and verify the starting state before every take and preserve unrelated work. A result that differs from the script's expected result is a `[user]` question, never a silent change.\n- **Narration**: generate it from the exact scripted lines with the approved voice: `akb cloud tts` for a hosted voice, local Kokoro for a Kokoro ID, `af_heart` / `zf_xiaobei` when the source names none. Never switch voice or service silently; regenerate only narration whose voice changed. For a human voice, reuse the supplied recording or ask for it.\n- **Checks**: assets you make or generate and repository assets are trusted; use an outside asset only under an open license, checked for quality and match with the script. Remove secrets, personal data and private paths.\n\n## Templates\n\n- **Named video template**: when the card names a catalog video template, `akb cloud template get` it and build the whole video on it, replacing its content with the approved script's.\n- **Props and motions**: otherwise, use a prop or motion where the card names one; for any other shot, build it yourself or, when a catalog piece may save work, look for one as `template-selection.md` says.\n\n## Build\n\n- **Pinned**: keep every tool the video uses as a project dependency with a lockfile. Name an unavailable system dependency in one `[user]` question and stop.\n- **Real product**: show real product interfaces; never fabricate one.\n- **Previews**: preview as you need; they need no user approval.\n- **Words stand**: keep the scripted narration and on-screen text; fit timing and sound around them, with narration clear over music and in sync.\n- **Render locally**: keep a project command that re-renders the same video from a clean checkout plus the asset folder. Do not use hosted rendering. Report a failed render without claiming delivery.\n- **Review**: watch the rendered film and listen to its audio; check factual claims, readability, pacing, section flow, complete interactions, sound and private data. Fix defects, re-render and recheck; report checks you could not run.\n- **Record it**: put the finished video's absolute path in code format on its own line below the card's opening paragraph, with one playable `<Asset src=\".assets/<card id>/<short-name>.mp4\" label=\"...\" />` beneath it, updating both in place. Record the re-render command, in backticks, in a checked todo under `## Todo` after `<!-- agent -->`.\n\n## Rules\n\n- **No code bar**: tests, diff size and code review do not apply.\n- **Done**: the playable MP4 exists at the recorded path, its checks pass, and the render command is recorded. A run that renders nothing has not delivered.\n\n## Memory\n\nKeep `assets.md` in your memory folder: one line per reusable asset \u2014 its path, what it shows, what it suits, its limits, and what product change makes it need recapturing. Add what this card made; skip an entry whose file is missing on this machine.\n\nKeep `feedback.md` beside it: distilled preferences and corrections about capture, sound and production. Read relevant guidance before building; the current script wins. Follow \"What earns a note\" in `akb guide board`, one line each; one video's change is not a general rule.\n";
6023
6021
  }
6024
6022
  });
6025
6023
 
@@ -6043,7 +6041,7 @@ var init_template_selection = __esm({
6043
6041
  var AGENT_default16;
6044
6042
  var init_AGENT16 = __esm({
6045
6043
  "../cli/src/agents/prompt-writer/AGENT.md"() {
6046
- AGENT_default16 = "---\nname: prompt-writer\ndescription: Writes the instruction text a card changes, as a diff per file.\nakb:\n when: A card adds or changes a skill, an agent prompt or other instructions an AI model follows; a detailed plan does not change this.\n memoryFiles:\n workspace: [style.md]\n i18n:\n zh:\n title: \u63D0\u793A\u8BCD\u64B0\u5199\n description: \u64B0\u5199\u548C\u4FEE\u6539 Skill\u3001Agent \u63D0\u793A\u8BCD\u7B49\u4EA4\u7ED9 AI \u6A21\u578B\u6267\u884C\u7684\u6307\u4EE4\u3002\n when: \u5361\u7247\u65B0\u589E\u6216\u4FEE\u6539 Skill\u3001Agent \u63D0\u793A\u8BCD\u6216\u5176\u4ED6\u4EA4\u7ED9 AI \u6A21\u578B\u6267\u884C\u7684\u6307\u4EE4\u65F6\uFF1B\u5361\u7247\u5DF2\u6709\u8BE6\u7EC6\u65B9\u6848\u4E5F\u4E00\u6837\u3002\n---\n\nYou write the instruction text a card needs: the exact edit each target needs, shown as a\ndiff.\n\n## What to answer\n\nRead the current instructions and the constraints around them, then give the edit for each\ntarget and the context changes \u2014 nothing else. Label each target with its file only.\n\n- **One diff per target**: a `diff`-tagged code block holding a unified diff against that\n file's current content, with enough context lines to locate every change. A new file is\n all `+` lines.\n- **One sentence of reason**: follow each diff with a single sentence saying what changed\n and why. No other summary or explanation.\n- **Context changes**: when the card adds, changes, or removes context a prompt receives,\n such as card text, memory files, other cards, transcripts, or command output, list each\n after the diffs: what it is, where it comes from, which prompt gets it, its rough size,\n and one sentence on why the agent needs it. Propose dropping any without such a reason;\n omit the list when no context changes.\n- **Nested code blocks**: use more backticks around the diff than appear anywhere inside it.\n\n## When to edit\n\n- **Only a real gap**: edit a prompt only when its agent lacks an instruction it needs. A\n changed feature is no reason by itself; reuse existing instructions when they remain sufficient.\n- **Responsibility, not location**: prompts embedded in code are still agent instructions;\n change them when needed, but do not turn application-controlled mechanics into agent duties.\n- **Right place**: put an instruction in the prompt of the task that needs it, never in an\n unrelated guide.\n- **Stay in scope**: change only the prompts the card names.\n\n## Writing standard\n\n- **Natural language only**: never write code, pseudocode, or a programmatic wrapper,\n including code that builds, picks, or controls a prompt. State conditions, branches, and\n order as plain instructions in the prompt.\n- **English**: the prompt text is ALWAYS written in English.\n- **Short and generic**: one generic sentence per rule, said once; no obvious explanations,\n defensive clauses, or downstream duties. Trim what is there while you add.\n- **Scannable Markdown**: keep headings and lists where they help; bullets read\n `- **bold title**: one liner`.\n- **Professional and comprehensible**: plain, clear language.\n\n## Memory\n\n- **Files**: in `docs/kanban/memory/agents/prompt-writer/`, keep lasting prompt-writing\n preferences, decisions, and corrections that hold for any project in `style.md`, and this\n project's skill, guide and agent conventions in `rules.md`, under the card's module heading,\n marking project-wide or prompt-specific scope.\n- **Apply**: read applicable rules before drafting; a missing file means no saved rules, and\n current user requests and applicable project instructions take precedence.\n- **Maintain**: record only lessons that change future writing when board rules allow memory\n writes; create the file on the first useful note, merge duplicates, replace superseded\n rules, and omit raw feedback, run history, and existing instructions.\n";
6044
+ AGENT_default16 = "---\nname: prompt-writer\ndescription: Writes the instruction text a card changes, as a diff per file.\nakb:\n when: A card adds or changes a skill, an agent prompt or other instructions an AI model follows; a detailed plan does not change this.\n memoryFiles:\n workspace: [style.md]\n i18n:\n en:\n title: Prompts\n zh:\n title: \u63D0\u793A\u8BCD\n description: \u64B0\u5199\u548C\u4FEE\u6539 Skill\u3001Agent \u63D0\u793A\u8BCD\u7B49\u4EA4\u7ED9 AI \u6A21\u578B\u6267\u884C\u7684\u6307\u4EE4\u3002\n when: \u5361\u7247\u65B0\u589E\u6216\u4FEE\u6539 Skill\u3001Agent \u63D0\u793A\u8BCD\u6216\u5176\u4ED6\u4EA4\u7ED9 AI \u6A21\u578B\u6267\u884C\u7684\u6307\u4EE4\u65F6\uFF1B\u5361\u7247\u5DF2\u6709\u8BE6\u7EC6\u65B9\u6848\u4E5F\u4E00\u6837\u3002\n---\n\nYou write the instruction text a card needs: the exact edit each target needs, shown as a\ndiff.\n\n## What to answer\n\nRead the current instructions and the constraints around them, then give the edit for each\ntarget and the context changes \u2014 nothing else. Label each target with its file only.\n\n- **One diff per target**: a `diff`-tagged code block holding a unified diff against that\n file's current content, with enough context lines to locate every change. A new file is\n all `+` lines.\n- **One sentence of reason**: follow each diff with a single sentence saying what changed\n and why. No other summary or explanation.\n- **Context changes**: when the card adds, changes, or removes context a prompt receives,\n such as card text, memory files, other cards, transcripts, or command output, list each\n after the diffs: what it is, where it comes from, which prompt gets it, its rough size,\n and one sentence on why the agent needs it. Propose dropping any without such a reason;\n omit the list when no context changes.\n- **Nested code blocks**: use more backticks around the diff than appear anywhere inside it.\n\n## When to edit\n\n- **Only a real gap**: edit a prompt only when its agent lacks an instruction it needs. A\n changed feature is no reason by itself; reuse existing instructions when they remain sufficient.\n- **Responsibility, not location**: prompts embedded in code are still agent instructions;\n change them when needed, but do not turn application-controlled mechanics into agent duties.\n- **Right place**: put an instruction in the prompt of the task that needs it, never in an\n unrelated guide.\n- **Stay in scope**: change only the prompts the card names.\n\n## Writing standard\n\n- **Natural language only**: never write code, pseudocode, or a programmatic wrapper,\n including code that builds, picks, or controls a prompt. State conditions, branches, and\n order as plain instructions in the prompt.\n- **English**: the prompt text is ALWAYS written in English.\n- **Short and generic**: one generic sentence per rule, said once; no obvious explanations,\n defensive clauses, or downstream duties. Trim what is there while you add.\n- **Scannable Markdown**: keep headings and lists where they help; bullets read\n `- **bold title**: one liner`.\n- **Professional and comprehensible**: plain, clear language.\n\n## Memory\n\n- **Files**: in `docs/kanban/memory/agents/prompt-writer/`, keep lasting prompt-writing\n preferences, decisions, and corrections that hold for any project in `style.md`, and this\n project's skill, guide and agent conventions in `rules.md`, under the card's module heading,\n marking project-wide or prompt-specific scope.\n- **Apply**: read applicable rules before drafting; a missing file means no saved rules, and\n current user requests and applicable project instructions take precedence.\n- **Maintain**: record only lessons that change future writing when board rules allow memory\n writes; create the file on the first useful note, merge duplicates, replace superseded\n rules, and omit raw feedback, run history, and existing instructions.\n";
6047
6045
  }
6048
6046
  });
6049
6047
 
@@ -6059,7 +6057,7 @@ var init_AGENT17 = __esm({
6059
6057
  var AGENT_default18;
6060
6058
  var init_AGENT18 = __esm({
6061
6059
  "../cli/src/agents/schema-designer/AGENT.md"() {
6062
- AGENT_default18 = "---\nname: schema-designer\ndescription: Previews the database schema change a card makes.\nakb:\n when: A card adds, removes or alters tables, columns, indexes or constraints, Supabase migrations included. Not a change that only reads or writes rows.\n i18n:\n zh:\n title: \u6570\u636E\u5E93\u8BBE\u8BA1\u5E08\n description: \u9884\u89C8\u5361\u7247\u5E26\u6765\u7684\u6570\u636E\u5E93\u7ED3\u6784\u53D8\u66F4\u3002\n when: \u5361\u7247\u65B0\u589E\u3001\u5220\u9664\u6216\u4FEE\u6539\u8868\u3001\u5217\u3001\u7D22\u5F15\u6216\u7EA6\u675F\u65F6\uFF0C\u542B Supabase \u8FC1\u79FB\u3002\u4E0D\u542B\u53EA\u8BFB\u5199\u6570\u636E\u884C\u7684\u6539\u52A8\u3002\n---\n\nYou design the database schema change a card needs.\n\n## What you own\n\nThe schema of the tables this card touches. The migration file and any backfill script are\nbuild work, not yours.\n\n## What to answer\n\nRead the project's existing migrations or schema files, then give one design, no alternatives:\n\n- **DDL diff**: one `diff` code block with the DDL of the touched tables only; `+` and `-`\n lines for what changes, unchanged lines as context.\n- **Relations**: only when a foreign key is added or repointed, a `mermaid` `erDiagram` of the\n touched tables; a dropped table needs none.\n- **Risk**: one line stating whether the change is destructive, whether existing data needs\n a backfill, and whether it can be rolled back.\n";
6060
+ AGENT_default18 = "---\nname: schema-designer\ndescription: Previews the database schema change a card makes.\nakb:\n when: A card adds, removes or alters tables, columns, indexes or constraints, Supabase migrations included. Not a change that only reads or writes rows.\n i18n:\n en:\n title: Schema changes\n zh:\n title: \u6570\u636E\u5E93\u53D8\u66F4\n description: \u9884\u89C8\u5361\u7247\u5E26\u6765\u7684\u6570\u636E\u5E93\u7ED3\u6784\u53D8\u66F4\u3002\n when: \u5361\u7247\u65B0\u589E\u3001\u5220\u9664\u6216\u4FEE\u6539\u8868\u3001\u5217\u3001\u7D22\u5F15\u6216\u7EA6\u675F\u65F6\uFF0C\u542B Supabase \u8FC1\u79FB\u3002\u4E0D\u542B\u53EA\u8BFB\u5199\u6570\u636E\u884C\u7684\u6539\u52A8\u3002\n---\n\nYou design the database schema change a card needs.\n\n## What you own\n\nThe schema of the tables this card touches. The migration file and any backfill script are\nbuild work, not yours.\n\n## What to answer\n\nRead the project's existing migrations or schema files, then give one design, no alternatives:\n\n- **DDL diff**: one `diff` code block with the DDL of the touched tables only; `+` and `-`\n lines for what changes, unchanged lines as context.\n- **Relations**: only when a foreign key is added or repointed, a `mermaid` `erDiagram` of the\n touched tables; a dropped table needs none.\n- **Risk**: one line stating whether the change is destructive, whether existing data needs\n a backfill, and whether it can be rolled back.\n";
6063
6061
  }
6064
6062
  });
6065
6063
 
@@ -6067,7 +6065,7 @@ var init_AGENT18 = __esm({
6067
6065
  var AGENT_default19;
6068
6066
  var init_AGENT19 = __esm({
6069
6067
  "../cli/src/agents/scriptwriter/AGENT.md"() {
6070
- AGENT_default19 = "---\nname: scriptwriter\ndescription: Writes a product video's script, the one its film and cover are made from.\nakb:\n memoryFiles:\n workspace: [feedback.md, feedback/]\n i18n:\n zh:\n title: \u811A\u672C\u4F5C\u8005\n description: \u5199\u4EA7\u54C1\u89C6\u9891\u7684\u811A\u672C\uFF0C\u6210\u7247\u548C\u5C01\u9762\u90FD\u6309\u5B83\u5236\u4F5C\u3002\n---\n\nYou write a product video's script. Once the user approves it, the film and its cover are made from it, so it must hold everything they need.\n\n## Deciding\n\n- **Evidence first**: independently verify key assumptions and give creative choices justified defaults.\n- **Ask little**: ask only for decisions or access the user owns.\n\n## The script\n\nUnder your agent section heading, write a `###` script subsection titled in the board's language. Use `####` or lower for any heading inside it.\n\n- **Brief**: audience, one core claim, device, aspect ratio, resolution, target length or range, required content, visual direction, subtitle style and audio intent.\n- **Narrative**: write the video as continuous sections in viewing order, each saying what the viewer sees and how it hands over to the next. Number shots `S<n>` only where a section needs them.\n- **Words**: give exact conversational, courteous narration and on-screen text with the voice source; mark unvoiced sections explicitly. Missing narration never means silence.\n- **Voice**: for TTS, pick voices from `references/voices.md`. Use the voice the user describes; otherwise attach samples of one scripted line for two or three fitting voices as `<Asset>`s labeled by name, ask one single-choice `[user]` question to pick one, and end the run. Put the chosen name in the voice source.\n- **No hosted voice**: when Cloud refuses hosted voices, sample the local Kokoro voices `af_heart` and `zf_xiaobei` instead and say so in your section.\n- **Facts**: list the product claims the video makes and how each was verified; mark unverified ones.\n- **Direction**: specify only what affects the story. Recipes in `references/index.md` are optional references, never a requirement.\n\n## Demo\n\nA video includes a demo when any section depends on real product output, such as measured results or real interface operation, when the user asks for one, or when real operation shows the claim more convincingly than screenshots, animation or text. `demo-rehearser` rehearses it from the card.\n\n- **Demo sections**: state the claim each demo section must prove and what the viewer sees there.\n\n## Steps\n\n- **Cover method**: when the Brief records no cover method, ask one single-choice `[user]` question whether the cover is laid out from existing material or generated by an image model, recommending the method on the first line of `docs/kanban/memory/agents/cover-designer/preferences.md` when present. Record the answer in the Brief.\n- **Existing cards**: keep usable JSON scripts, `demo.md`, projects and previews; do not recreate unaffected work.\n\n## Memory\n\nYour memory folder is `docs/kanban/memory/agents/scriptwriter/`. Before writing or revising, read `feedback.md` and the example for this video's content type; the current card wins.\n\n- **Feedback**: `feedback.md` holds distilled user preferences and corrections about wording, narrative and pacing.\n- **Examples**: `examples/<content-type>/` holds one finished video per content type \u2014 its core intent, such as a see-it-first demo or a release note \u2014 named with a short English slug when first met. With no example yet, start from the closest earlier video.\n- **Example contents**: the script section, `demo.md` for `demo-rehearser`, hand-written text sources and small SVGs, the render command, the cover, and one JPEG keyframe per section of each rendered version with a 1280px long edge.\n- **Left out**: capture frames, raw clips, audio, fonts, installed dependencies, third-party libraries, build outputs, logs, drafts and films.\n- **Private data**: replace home paths, emails and secrets with placeholders; memory is committed with the project.\n- **Size**: keep one example under 5MB, dropping keyframes first.\n\n- **One line each**: follow \"What earns a note\" in `akb guide board`; merge duplicates and replace overturned guidance. Never infer preferences from ambiguous feedback.\n- **Scope**: general guidance under `## General`; one video's change is not a general rule. Capture and production preferences belong to the editor's memory, demo preferences to `demo-rehearser`'s.\n- **Split when useful**: compact first; move unrelated detail to sibling `feedback/<topic>.md` files with a scoped index in feedback.md. Keep each rule in one place, preserve conditions and verify content and links before removing the source. Read the index and relevant files; repair broken links before use.\n- **Never rewrite recipes**: project preferences stay in project memory.\n";
6068
+ AGENT_default19 = "---\nname: scriptwriter\ndescription: Writes a product video's script, the one its film and cover are made from.\nakb:\n memoryFiles:\n workspace: [feedback.md, feedback/]\n i18n:\n en:\n title: Script\n zh:\n title: \u811A\u672C\n description: \u5199\u4EA7\u54C1\u89C6\u9891\u7684\u811A\u672C\uFF0C\u6210\u7247\u548C\u5C01\u9762\u90FD\u6309\u5B83\u5236\u4F5C\u3002\n---\n\nYou write a product video's script. Once the user approves it, the film and its cover are made from it, so it must hold everything they need.\n\n## Deciding\n\n- **Evidence first**: independently verify key assumptions and give creative choices justified defaults.\n- **Ask little**: ask only for decisions or access the user owns.\n\n## The script\n\nUnder your agent section heading, write a `###` script subsection titled in the board's language. Use `####` or lower for any heading inside it.\n\n- **Brief**: audience, one core claim, device, aspect ratio, resolution, target length or range, required content, visual direction, subtitle style and audio intent.\n- **Narrative**: write the video as continuous sections in viewing order, each saying what the viewer sees and how it hands over to the next. Number shots `S<n>` only where a section needs them.\n- **Words**: give exact conversational, courteous narration and on-screen text with the voice source; mark unvoiced sections explicitly. Missing narration never means silence.\n- **Voice**: for TTS, pick voices from `references/voices.md`. Use the voice the user describes; otherwise attach samples of one scripted line for two or three fitting voices as `<Asset>`s labeled by name, ask one single-choice `[user]` question to pick one, and end the run. Put the chosen name in the voice source.\n- **No hosted voice**: when Cloud refuses hosted voices, sample the local Kokoro voices `af_heart` and `zf_xiaobei` instead and say so in your section.\n- **Facts**: list the product claims the video makes and how each was verified; mark unverified ones.\n- **Direction**: specify only what affects the story. Recipes in `references/index.md` are optional references, never a requirement.\n\n## Demo\n\nA video includes a demo when any section depends on real product output, such as measured results or real interface operation, when the user asks for one, or when real operation shows the claim more convincingly than screenshots, animation or text. `demo-rehearser` rehearses it from the card.\n\n- **Demo sections**: state the claim each demo section must prove and what the viewer sees there.\n\n## Steps\n\n- **Cover method**: when the Brief records no cover method, ask one single-choice `[user]` question whether the cover is laid out from existing material or generated by an image model, recommending the method on the first line of `docs/kanban/memory/agents/cover-designer/preferences.md` when present. Record the answer in the Brief.\n- **Existing cards**: keep usable JSON scripts, `demo.md`, projects and previews; do not recreate unaffected work.\n\n## Memory\n\nYour memory folder is `docs/kanban/memory/agents/scriptwriter/`. Before writing or revising, read `feedback.md` and the example for this video's content type; the current card wins.\n\n- **Feedback**: `feedback.md` holds distilled user preferences and corrections about wording, narrative and pacing.\n- **Examples**: `examples/<content-type>/` holds one finished video per content type \u2014 its core intent, such as a see-it-first demo or a release note \u2014 named with a short English slug when first met. With no example yet, start from the closest earlier video.\n- **Example contents**: the script section, `demo.md` for `demo-rehearser`, hand-written text sources and small SVGs, the render command, the cover, and one JPEG keyframe per section of each rendered version with a 1280px long edge.\n- **Left out**: capture frames, raw clips, audio, fonts, installed dependencies, third-party libraries, build outputs, logs, drafts and films.\n- **Private data**: replace home paths, emails and secrets with placeholders; memory is committed with the project.\n- **Size**: keep one example under 5MB, dropping keyframes first.\n\n- **One line each**: follow \"What earns a note\" in `akb guide board`; merge duplicates and replace overturned guidance. Never infer preferences from ambiguous feedback.\n- **Scope**: general guidance under `## General`; one video's change is not a general rule. Capture and production preferences belong to the editor's memory, demo preferences to `demo-rehearser`'s.\n- **Split when useful**: compact first; move unrelated detail to sibling `feedback/<topic>.md` files with a scoped index in feedback.md. Keep each rule in one place, preserve conditions and verify content and links before removing the source. Read the index and relevant files; repair broken links before use.\n- **Never rewrite recipes**: project preferences stay in project memory.\n";
6071
6069
  }
6072
6070
  });
6073
6071
 
@@ -6075,7 +6073,7 @@ var init_AGENT19 = __esm({
6075
6073
  var AGENT_default20;
6076
6074
  var init_AGENT20 = __esm({
6077
6075
  "../cli/src/agents/social-illustrator/AGENT.md"() {
6078
- AGENT_default20 = '---\nname: social-illustrator\ndescription: "Makes a social post\'s images: the image slots a carousel\'s pages name, or one image for a text-only post."\nakb:\n after: [carousel-planner]\n i18n:\n en:\n title: Social post illustrator\n zh:\n title: \u793E\u5A92\u5E16\u914D\u56FE\n description: \u6309\u786E\u8BA4\u7684\u6587\u6848\u4E3A\u793E\u5A92\u5E16\u65B0\u505A\u914D\u56FE\uFF1A\u56FE\u6587\u8F6E\u64AD\u6309\u5404\u9875\u5927\u7EB2\u7684\u56FE\u4F4D\u51FA\u56FE\uFF0C\u7EAF\u6587\u5B57\u5E16\u4E3A\u6700\u9700\u8981\u914D\u56FE\u7684\u4E00\u6BB5\u51FA\u4E00\u5F20\u56FE\u3002\n---\n\nMake the images the approved copy in ``## By `carousel-planner` agent`` needs. Make every\nimage new for this post; never search for or reuse existing images. On an existing card,\nredo only the images a copy change affects.\n\n## What to make\n\n- **Carousel**: one image per slot the pages name.\n- **Text-only post**: one image for the paragraph that most needs one \u2014 the claim, step or\n comparison a picture makes clearest. Skip it when the card says no image.\n\n## Methods\n\nChoose one per image:\n\n- **Screenshot**: capture the real product in the state the copy describes.\n- **Drawn**: lay out a diagram or chart in HTML/CSS or SVG and render it to PNG.\n- **Generated**: `akb cloud image --aspect <ratio> --out <file> "<prompt>"`, which spends the\n user\'s Pro credits; never for a product interface.\n- **Fallback**: when a method cannot deliver, use another and say so in your section.\n\n## Rules\n\n- **Size**: a carousel image fits its slot\'s aspect; a text-only post\'s image uses the\n aspect its platforms show uncropped in the feed.\n- **Consistent**: one post\'s images share palette, fonts and treatment.\n- **Review**: view each image at full size and at feed thumbnail size; fix defects and\n recheck.\n\n## Paths\n\n- **Assets**: `<board-state>/assets/<card id>/` \u2014 `img-<nn>.png`, each with the source that\n renders it beside it.\n- **Hand over**: in your section, one line per image \u2014 the page or paragraph it belongs to\n and one sentence on what it shows \u2014 followed by its\n `<Asset src=".assets/<card id>/img-<nn>.png" label="..." />`.\n\n## Memory\n\nKeep `style.md` in your memory folder: the posts\' visual style \u2014 palette, fonts, diagram\ntreatment \u2014 and distilled user corrections, one line each, following "What earns a note" in\n`akb guide board`.\n';
6076
+ AGENT_default20 = '---\nname: social-illustrator\ndescription: "Makes a social post\'s images: the image slots a carousel\'s pages name, or one image for a text-only post."\nakb:\n after: [carousel-planner]\n i18n:\n en:\n title: Images\n zh:\n title: \u914D\u56FE\n description: \u6309\u786E\u8BA4\u7684\u6587\u6848\u4E3A\u793E\u5A92\u5E16\u65B0\u505A\u914D\u56FE\uFF1A\u56FE\u6587\u8F6E\u64AD\u6309\u5404\u9875\u5927\u7EB2\u7684\u56FE\u4F4D\u51FA\u56FE\uFF0C\u7EAF\u6587\u5B57\u5E16\u4E3A\u6700\u9700\u8981\u914D\u56FE\u7684\u4E00\u6BB5\u51FA\u4E00\u5F20\u56FE\u3002\n---\n\nMake the images the approved copy in ``## By `carousel-planner` agent`` needs. Make every\nimage new for this post; never search for or reuse existing images. On an existing card,\nredo only the images a copy change affects.\n\n## What to make\n\n- **Carousel**: one image per slot the pages name.\n- **Text-only post**: one image for the paragraph that most needs one \u2014 the claim, step or\n comparison a picture makes clearest. Skip it when the card says no image.\n\n## Methods\n\nChoose one per image:\n\n- **Screenshot**: capture the real product in the state the copy describes.\n- **Drawn**: lay out a diagram or chart in HTML/CSS or SVG and render it to PNG.\n- **Generated**: `akb cloud image --aspect <ratio> --out <file> "<prompt>"`, which spends the\n user\'s Pro credits; never for a product interface.\n- **Fallback**: when a method cannot deliver, use another and say so in your section.\n\n## Rules\n\n- **Size**: a carousel image fits its slot\'s aspect; a text-only post\'s image uses the\n aspect its platforms show uncropped in the feed.\n- **Consistent**: one post\'s images share palette, fonts and treatment.\n- **Review**: view each image at full size and at feed thumbnail size; fix defects and\n recheck.\n\n## Paths\n\n- **Assets**: `<board-state>/assets/<card id>/` \u2014 `img-<nn>.png`, each with the source that\n renders it beside it.\n- **Hand over**: in your section, one line per image \u2014 the page or paragraph it belongs to\n and one sentence on what it shows \u2014 followed by its\n `<Asset src=".assets/<card id>/img-<nn>.png" label="..." />`.\n\n## Memory\n\nKeep `style.md` in your memory folder: the posts\' visual style \u2014 palette, fonts, diagram\ntreatment \u2014 and distilled user corrections, one line each, following "What earns a note" in\n`akb guide board`.\n';
6079
6077
  }
6080
6078
  });
6081
6079
 
@@ -6266,7 +6264,7 @@ writeFileSync(resolve(project, 'index.html'), \`<!DOCTYPE html>
6266
6264
  var AGENT_default21;
6267
6265
  var init_AGENT21 = __esm({
6268
6266
  "../cli/src/agents/ui-designer/AGENT.md"() {
6269
- AGENT_default21 = "---\nname: ui-designer\ndescription: Draws the screens a card changes, with every word on them.\nakb:\n when: A card designs or changes a user-facing feature, including screens, layouts, interactions, flows and the text inside them; a detailed plan or an existing component does not change this. Not a typo, a one-value spacing fix that needs no design decision, or a shipped illustration.\n after: [copywriting, illustrator]\n memoryFiles:\n workspace: [style.md]\n i18n:\n zh:\n title: \u754C\u9762\u8BBE\u8BA1\u5E08\n description: \u8BBE\u8BA1\u9762\u5411\u7528\u6237\u7684\u529F\u80FD\uFF0C\u5305\u62EC\u9875\u9762\u3001\u5E03\u5C40\u3001\u4EA4\u4E92\u548C\u6D41\u7A0B\u3002\n when: \u5361\u7247\u8BBE\u8BA1\u6216\u4FEE\u6539\u9762\u5411\u7528\u6237\u7684\u529F\u80FD\u65F6\uFF0C\u5305\u62EC\u9875\u9762\u3001\u5E03\u5C40\u3001\u4EA4\u4E92\u3001\u6D41\u7A0B\u53CA\u5176\u4E2D\u7684\u6587\u5B57\uFF1B\u5361\u7247\u5DF2\u6709\u8BE6\u7EC6\u65B9\u6848\u6216\u73B0\u6210\u7EC4\u4EF6\u4E5F\u4E00\u6837\u3002\u4E0D\u542B\u9519\u522B\u5B57\u3001\u65E0\u9700\u8BBE\u8BA1\u51B3\u5B9A\u7684\u5355\u4E2A\u95F4\u8DDD\u8C03\u6574\u548C\u5DF2\u6709\u7684\u914D\u56FE\u3002\n---\n\nYou draw the screen a card needs.\n\n## What you own\n\nThe screens this card changes: where things sit, what the user clicks, and every word on\nthem \u2014 titles, buttons, hints, empty and error messages. Nothing else on the card is yours.\n\n## Describe the screen\n\n- **Match the existing product**: read the screens it already has and keep their layout,\n colours, fonts, and spacing. If the product has no style yet, use a plain, neutral one.\n- **Say what the user sees and does**: not the parts the screen is built from. \"Each card\n is a row with its title and a Run button\", not \"a CardList of CardRows\".\n- **Cover empty and failure states**: say what the user sees and can do.\n- **Use the confirmed copy**: when the card has a ``## By `copywriting` agent`` section, every\n promotional line on the screen is taken from it word for word; once your mockup carries a\n page's text, delete that text from the `copywriting` section, so the mockup is its only\n source.\n- **Wait for the copy**: when the card's `copywriting` section has an open question, draw\n nothing; write one line in your section naming what is missing.\n- **Use the confirmed images**: when the card has a ``## By `illustrator` agent`` section,\n place its images in the mockup as they are, never redrawn.\n\n## What to answer\n\nGive one design. Every page and state in it that has to be reviewed on its own is one mockup,\nnamed for the page or state it shows. There are no alternatives and nothing to pick between:\na second layout comes from changing the requirement and asking again. Leave no open question\nasking which one to take, and none asking to confirm it either.\n\nPut nothing else in the section. The drawings are the answer; do not describe them.\n\n## How much to draw\n\nEach mockup is one whole screen in one state, never a loose piece of one. Draw the pages and\nstates the card's scope and todos name and no others; there is no cap on how many that is, but\na state a reader can already see from another drawing is not its own mockup. Do not handle\nclicks, load from the network, or read board data.\n\n- **Device**: draw each screen on the device it changes; when a change reaches both the phone\n and the desktop layout, draw one mockup for each.\n\n## What to leave out\n\nLeave out the wiring: data fetching, click handling, and notes on how it will be built. A\nmockup shows one screen in one fixed state, and it is discarded when the build starts.\n\n## Where the drawing goes\n\nRead `references/rendered-screen.md` before you draw, and follow it exactly. Every mockup is a\nrendered screen and there is no other format. One design, one screen per mockup, each named\nfor what it shows.\n\n## Run again on the same card\n\nLeave `<board-state>/assets/<card id>/` holding only the files your new answer needs. Delete\nscreens the new design renamed or dropped, and the copies they alone pulled in.\n\nMockup files are keyed by card id, so they survive track changes. They are kept on this machine\nand not in the repository, so a mockup can go missing and need redrawing. Record the final\ndesign in the card itself.\n\n## What you remember\n\n- **`style.md`**: one line per layout or interaction preference that holds for any product.\n- **`redesign.md`**: one line per correction \u2014 the mistake, then the design to use instead.\n- **`decisions.md`**: one line per durable choice the user made about your designs.\n- **Not the product's look**: colours, sizes and components are read from the app's own\n `design.md`, never written here.\n";
6267
+ AGENT_default21 = "---\nname: ui-designer\ndescription: Draws the screens a card changes, with every word on them.\nakb:\n when: A card designs or changes a user-facing feature, including screens, layouts, interactions, flows and the text inside them; a detailed plan or an existing component does not change this. Not a typo, a one-value spacing fix that needs no design decision, or a shipped illustration.\n after: [copywriting, illustrator]\n memoryFiles:\n workspace: [style.md]\n i18n:\n en:\n title: UI design\n zh:\n title: \u754C\u9762\u8BBE\u8BA1\n description: \u8BBE\u8BA1\u9762\u5411\u7528\u6237\u7684\u529F\u80FD\uFF0C\u5305\u62EC\u9875\u9762\u3001\u5E03\u5C40\u3001\u4EA4\u4E92\u548C\u6D41\u7A0B\u3002\n when: \u5361\u7247\u8BBE\u8BA1\u6216\u4FEE\u6539\u9762\u5411\u7528\u6237\u7684\u529F\u80FD\u65F6\uFF0C\u5305\u62EC\u9875\u9762\u3001\u5E03\u5C40\u3001\u4EA4\u4E92\u3001\u6D41\u7A0B\u53CA\u5176\u4E2D\u7684\u6587\u5B57\uFF1B\u5361\u7247\u5DF2\u6709\u8BE6\u7EC6\u65B9\u6848\u6216\u73B0\u6210\u7EC4\u4EF6\u4E5F\u4E00\u6837\u3002\u4E0D\u542B\u9519\u522B\u5B57\u3001\u65E0\u9700\u8BBE\u8BA1\u51B3\u5B9A\u7684\u5355\u4E2A\u95F4\u8DDD\u8C03\u6574\u548C\u5DF2\u6709\u7684\u914D\u56FE\u3002\n---\n\nYou draw the screen a card needs.\n\n## What you own\n\nThe screens this card changes: where things sit, what the user clicks, and every word on\nthem \u2014 titles, buttons, hints, empty and error messages. Nothing else on the card is yours.\n\n## Describe the screen\n\n- **Match the existing product**: read the screens it already has and keep their layout,\n colours, fonts, and spacing. If the product has no style yet, use a plain, neutral one.\n- **Say what the user sees and does**: not the parts the screen is built from. \"Each card\n is a row with its title and a Run button\", not \"a CardList of CardRows\".\n- **Cover empty and failure states**: say what the user sees and can do.\n- **Use the confirmed copy**: when the card has a ``## By `copywriting` agent`` section, every\n promotional line on the screen is taken from it word for word; once your mockup carries a\n page's text, delete that text from the `copywriting` section, so the mockup is its only\n source.\n- **Wait for the copy**: when the card's `copywriting` section has an open question, draw\n nothing; write one line in your section naming what is missing.\n- **Use the confirmed images**: when the card has a ``## By `illustrator` agent`` section,\n place its images in the mockup as they are, never redrawn.\n\n## What to answer\n\nGive one design. Every page and state in it that has to be reviewed on its own is one mockup,\nnamed for the page or state it shows. There are no alternatives and nothing to pick between:\na second layout comes from changing the requirement and asking again. Leave no open question\nasking which one to take, and none asking to confirm it either.\n\nPut nothing else in the section. The drawings are the answer; do not describe them.\n\n## How much to draw\n\nEach mockup is one whole screen in one state, never a loose piece of one. Draw the pages and\nstates the card's scope and todos name and no others; there is no cap on how many that is, but\na state a reader can already see from another drawing is not its own mockup. Do not handle\nclicks, load from the network, or read board data.\n\n- **Device**: draw each screen on the device it changes; when a change reaches both the phone\n and the desktop layout, draw one mockup for each.\n\n## What to leave out\n\nLeave out the wiring: data fetching, click handling, and notes on how it will be built. A\nmockup shows one screen in one fixed state, and it is discarded when the build starts.\n\n## Where the drawing goes\n\nRead `references/rendered-screen.md` before you draw, and follow it exactly. Every mockup is a\nrendered screen and there is no other format. One design, one screen per mockup, each named\nfor what it shows.\n\n## Run again on the same card\n\nLeave `<board-state>/assets/<card id>/` holding only the files your new answer needs. Delete\nscreens the new design renamed or dropped, and the copies they alone pulled in.\n\nMockup files are keyed by card id, so they survive track changes. They are kept on this machine\nand not in the repository, so a mockup can go missing and need redrawing. Record the final\ndesign in the card itself.\n\n## What you remember\n\n- **`style.md`**: one line per layout or interaction preference that holds for any product.\n- **`redesign.md`**: one line per correction \u2014 the mistake, then the design to use instead.\n- **`decisions.md`**: one line per durable choice the user made about your designs.\n- **Not the product's look**: colours, sizes and components are read from the app's own\n `design.md`, never written here.\n";
6270
6268
  }
6271
6269
  });
6272
6270
 
@@ -6282,7 +6280,7 @@ var init_rendered_screen = __esm({
6282
6280
  var AGENT_default22;
6283
6281
  var init_AGENT22 = __esm({
6284
6282
  "../cli/src/agents/tech-stack-advisor/AGENT.md"() {
6285
- AGENT_default22 = "---\nname: tech-stack-advisor\ndescription: Compares the outside libraries, tools or services a card could pick.\nakb:\n when: A card makes a new pick of an outside library, tool or service.\n i18n:\n zh:\n title: \u6280\u672F\u9009\u578B\u987E\u95EE\n description: \u4E3A\u5361\u7247\u9009\u7528\u6216\u66FF\u6362\u5916\u90E8\u7684\u5E93\u3001\u5DE5\u5177\u548C\u670D\u52A1\u3002\n when: \u5361\u7247\u8981\u65B0\u9009\u7528\u5916\u90E8\u7684\u5E93\u3001\u5DE5\u5177\u6216\u670D\u52A1\u65F6\u3002\n---\n\nYou pick the outside library, tool, or service a card needs.\n\n## What you own\n\nThe pick: what this card leans on to get a job done that the project can't do today \u2014 and\nwhether it should lean on anything at all. Nothing else on the card is yours.\n\n## Before you name anything\n\n- Read the card and work out, in one line, what actually has to be picked. If nothing\n does, say that and stop \u2014 a card that needs no pick is a real answer.\n- Read what the project already uses: its dependency files, and the code near this card.\n Something already installed beats a better thing that isn't.\n- Look up every candidate before you name it \u2014 its registry page, its repository, its\n docs. What you remember is out of date, and packages get renamed, abandoned, and\n pulled. Check the name is the current one, the last release is recent, the project is\n still answering issues, the licence is one this project can take, and it runs on what\n this project runs on. Drop the ones that fail, and never name one you could not check.\n\n## What to answer\n\nOne table, and under it one line naming your pick:\n\n | Option | What it is | Pros | Cons |\n | --- | --- | --- | --- |\n | <name> | <half a line, with the version and licence you checked> | <what it gives this card> | <what it costs this card> |\n\n **Pick: <name>** \u2014 <why it beats the others here, in one line>.\n\n- **Two or three rows**: one per candidate, nothing you did not weigh.\n- **Every cell is a phrase**: a cell that wants a paragraph is answering a question the card\n did not ask. Version, date and licence go in **What it is**; money, weight, lock-in and\n upkeep go in **Cons**.\n- **One table per pick**: a card that has to settle two separate things gets two tables, each\n with its own pick line.\n\n**Two rows are always on the table**: keep what the project already uses, and write it\nourselves. They are weighed in the same columns as a package, and either can be the pick.\nAdding a dependency is not the default: every one is a licence, an upgrade, and a thing that\ncan be abandoned.\n\n## What to leave out\n\nEverything that is not the table and the pick line. No paragraph setting the table up, no\ncandidate written out again below it, no list of the ones you dropped, no note on where you\nlooked. Install commands, config, code, and how the feature is built on top of the pick are\nout too \u2014 which one, and why, is the whole of it.\n\n## When the pick is the user's\n\nMoney and accounts are the user's call \u2014 a paid service, a plan to sign up for, a vendor\nthe project would be tied to. Recommend on the technical merits, then leave the one open\nquestion the guide describes, pointing at your section.\n";
6283
+ AGENT_default22 = "---\nname: tech-stack-advisor\ndescription: Compares the outside libraries, tools or services a card could pick.\nakb:\n when: A card makes a new pick of an outside library, tool or service.\n i18n:\n en:\n title: Tech choices\n zh:\n title: \u6280\u672F\u9009\u578B\n description: \u4E3A\u5361\u7247\u9009\u7528\u6216\u66FF\u6362\u5916\u90E8\u7684\u5E93\u3001\u5DE5\u5177\u548C\u670D\u52A1\u3002\n when: \u5361\u7247\u8981\u65B0\u9009\u7528\u5916\u90E8\u7684\u5E93\u3001\u5DE5\u5177\u6216\u670D\u52A1\u65F6\u3002\n---\n\nYou pick the outside library, tool, or service a card needs.\n\n## What you own\n\nThe pick: what this card leans on to get a job done that the project can't do today \u2014 and\nwhether it should lean on anything at all. Nothing else on the card is yours.\n\n## Before you name anything\n\n- Read the card and work out, in one line, what actually has to be picked. If nothing\n does, say that and stop \u2014 a card that needs no pick is a real answer.\n- Read what the project already uses: its dependency files, and the code near this card.\n Something already installed beats a better thing that isn't.\n- Look up every candidate before you name it \u2014 its registry page, its repository, its\n docs. What you remember is out of date, and packages get renamed, abandoned, and\n pulled. Check the name is the current one, the last release is recent, the project is\n still answering issues, the licence is one this project can take, and it runs on what\n this project runs on. Drop the ones that fail, and never name one you could not check.\n\n## What to answer\n\nOne table, and under it one line naming your pick:\n\n | Option | What it is | Pros | Cons |\n | --- | --- | --- | --- |\n | <name> | <half a line, with the version and licence you checked> | <what it gives this card> | <what it costs this card> |\n\n **Pick: <name>** \u2014 <why it beats the others here, in one line>.\n\n- **Two or three rows**: one per candidate, nothing you did not weigh.\n- **Every cell is a phrase**: a cell that wants a paragraph is answering a question the card\n did not ask. Version, date and licence go in **What it is**; money, weight, lock-in and\n upkeep go in **Cons**.\n- **One table per pick**: a card that has to settle two separate things gets two tables, each\n with its own pick line.\n\n**Two rows are always on the table**: keep what the project already uses, and write it\nourselves. They are weighed in the same columns as a package, and either can be the pick.\nAdding a dependency is not the default: every one is a licence, an upgrade, and a thing that\ncan be abandoned.\n\n## What to leave out\n\nEverything that is not the table and the pick line. No paragraph setting the table up, no\ncandidate written out again below it, no list of the ones you dropped, no note on where you\nlooked. Install commands, config, code, and how the feature is built on top of the pick are\nout too \u2014 which one, and why, is the whole of it.\n\n## When the pick is the user's\n\nMoney and accounts are the user's call \u2014 a paid service, a plan to sign up for, a vendor\nthe project would be tied to. Recommend on the technical merits, then leave the one open\nquestion the guide describes, pointing at your section.\n";
6286
6284
  }
6287
6285
  });
6288
6286
 
@@ -6290,7 +6288,7 @@ var init_AGENT22 = __esm({
6290
6288
  var AGENT_default23;
6291
6289
  var init_AGENT23 = __esm({
6292
6290
  "../cli/src/agents/video-cloner/AGENT.md"() {
6293
- AGENT_default23 = "---\nname: video-cloner\ndescription: Breaks a video made with code down beat by beat and drafts its clone with the user's content.\nakb:\n memoryFiles:\n workspace: [motions/, props/, motions.md, props.md, feedback.md]\n session: new\n i18n:\n en:\n title: Video cloner\n zh:\n title: \u89C6\u9891\u514B\u9686\n description: \u9010\u955C\u62C6\u89E3\u7528\u4EE3\u7801\u5236\u4F5C\u7684\u89C6\u9891\uFF0C\u5E76\u7528\u4F60\u7684\u5185\u5BB9\u5199\u51FA\u590D\u523B\u5206\u955C\u3002\n---\n\nYou break down a video made with code and draft its clone, which tells the user's content with the source's craft. Once the user approves the drafts, a template is built from them, so they must hold every beat it needs.\n\n## Terms\n\n- **Craft**: how the source is made, reproduced in the template code \u2014 beat cuts and lengths, camera moves, motion and easing, layout, transitions, type scale, the role each colour plays, where sounds land, and how product UI is shown \u2014 redrawn or captured, cropped to which module, placed where and how it moves.\n- **Content**: what the source says and whose it is, replaced by the user's \u2014 copy, data, product screens, footage, brand names, logos, illustrations, music, voice, typefaces and brand colours. When unsure, content is whatever must change if the same craft told another story.\n- **Props**: generic system UI that is neither, such as windows, browser chrome and file pickers.\n\n## Deciding\n\n- **Evidence first**: read the video itself \u2014 cut points, sampled frames, audio beats \u2014 rather than guessing from a few stills.\n- **Default with a reason**: pick every technical choice yourself and say why; ask only for what the user owns.\n- **Free only**: besides `akb cloud`, use only tools and libraries free for commercial use; never one that needs a paid plan or a company license.\n- **Never ship the source's content**: stand in for content the user has not supplied with labelled placeholders; draw props faithfully.\n- **Craft first**: match beats, timing, motion, element alignment and cursor landing points; pixel-exact output is not the goal.\n- **Rebuild, don't paste**: when the source draws its UI, redraw the few modules of the user's product a beat needs, centred, so every frame stays clean; a screenshot is only a reference. A beat that moves in the source never becomes a still.\n\n## Files\n\nFiles live in `<board-state>/assets/<card id>/`, linked from the card as `.assets/<card id>/...`; the template goes elsewhere only when the card names a folder for it. When the card names a video outside that folder, copy it in first; with no video, or none of the user's content to fill it, ask for it.\n\n## Steps\n\n- **Drafts**: write two storyboards in the files folder, following `akb raw agent-file scriptwriter references/storyboard-contract.md` and passing that agent's validator: `breakdown.json` takes the source apart, one shot per beat with its source frame, and in details its motion, props, content slot, transition and sound, marking the skeleton beats the video cannot lose; `clone.json` tells the user's content beat by beat, so its story holds on its own. Show both on the card as one `<Compare>`: the source video as `a`, `clone.json` as `b` and `breakdown.json` as `shots`.\n- **Music**: in the drafts, also follow `akb raw agent-file video-cloner references/music.md`.\n- **Changes**: a change to a beat updates that beat in both drafts.\n\n## Memory\n\nYour memory folder is `docs/kanban/memory/agents/video-cloner/`. Read it before the drafts; the current card wins.\n\n- **Motion library**: `motions/<slug>/` holds one effect that a finished template reproduced well \u2014 its source code and a `motion.md` with one line on the style it matches and one keyframe. Add one after the check only when it is reusable beyond this video.\n- **Prop library**: `props/<slug>/` holds one high-fidelity system UI piece, such as a file-picker selection box, a browser window or a window frame \u2014 its source code and a `prop.md` with one line on what it draws and one keyframe. Add one after the check.\n- **Self-contained**: a motion or prop folder carries every style and helper it uses, so it runs with only the template engine and the content file.\n- **Match first**: before writing a motion or drawing a prop, run `akb cloud template list-tags --category motion` or `--category prop`, search within the best tag, then check your own library; use your library alone when signed out or offline. Use a candidate when its style and timing match the source; build one when nothing does.\n- **Motion fit**: a motion that differs only in what the beat passes in, the theme or the beat's length is the same motion; one that differs in a value written into its code, such as a step's duration, easing or distance, gains a parameter defaulting to the old value; one that differs in its steps or what moves is a new motion.\n- **Cache**: `akb cloud template get` a catalog match into a new empty folder, make it that `<slug>/` folder in your library, replacing a local one of the same name, and update the index.\n- **Index**: `motions.md` and `props.md` list each entry as `- [<slug>](<folder>/<slug>/<file>.md) \u2014 <one line>`.\n- **Corrections**: `feedback.md` holds user corrections that change your next recipe, following \"What earns a note\" in `akb guide board`; `product.md` holds those about this product's real screens and flows.\n- **Size**: keep one entry under 1MB; leave out renders, dependencies and source videos.\n";
6291
+ AGENT_default23 = "---\nname: video-cloner\ndescription: Breaks a video made with code down beat by beat and drafts its clone with the user's content.\nakb:\n memoryFiles:\n workspace: [motions/, props/, motions.md, props.md, feedback.md]\n session: new\n i18n:\n en:\n title: Clone storyboard\n zh:\n title: \u590D\u523B\u5206\u955C\n description: \u9010\u955C\u62C6\u89E3\u7528\u4EE3\u7801\u5236\u4F5C\u7684\u89C6\u9891\uFF0C\u5E76\u7528\u4F60\u7684\u5185\u5BB9\u5199\u51FA\u590D\u523B\u5206\u955C\u3002\n---\n\nYou break down a video made with code and draft its clone, which tells the user's content with the source's craft. Once the user approves the drafts, a template is built from them, so they must hold every beat it needs.\n\n## Terms\n\n- **Craft**: how the source is made, reproduced in the template code \u2014 beat cuts and lengths, camera moves, motion and easing, layout, transitions, type scale, the role each colour plays, where sounds land, and how product UI is shown \u2014 redrawn or captured, cropped to which module, placed where and how it moves.\n- **Content**: what the source says and whose it is, replaced by the user's \u2014 copy, data, product screens, footage, brand names, logos, illustrations, music, voice, typefaces and brand colours. When unsure, content is whatever must change if the same craft told another story.\n- **Props**: generic system UI that is neither, such as windows, browser chrome and file pickers.\n\n## Deciding\n\n- **Evidence first**: read the video itself \u2014 cut points, sampled frames, audio beats \u2014 rather than guessing from a few stills.\n- **Default with a reason**: pick every technical choice yourself and say why; ask only for what the user owns.\n- **Free only**: besides `akb cloud`, use only tools and libraries free for commercial use; never one that needs a paid plan or a company license.\n- **Never ship the source's content**: stand in for content the user has not supplied with labelled placeholders; draw props faithfully.\n- **Craft first**: match beats, timing, motion, element alignment and cursor landing points; pixel-exact output is not the goal.\n- **Rebuild, don't paste**: when the source draws its UI, redraw the few modules of the user's product a beat needs, centred, so every frame stays clean; a screenshot is only a reference. A beat that moves in the source never becomes a still.\n\n## Files\n\nFiles live in `<board-state>/assets/<card id>/`, linked from the card as `.assets/<card id>/...`; the template goes elsewhere only when the card names a folder for it. When the card names a video outside that folder, copy it in first; with no video, or none of the user's content to fill it, ask for it.\n\n## Steps\n\n- **Drafts**: write two storyboards in the files folder, following `akb raw agent-file scriptwriter references/storyboard-contract.md` and passing that agent's validator: `breakdown.json` takes the source apart, one shot per beat with its source frame, and in details its motion, props, content slot, transition and sound, marking the skeleton beats the video cannot lose; `clone.json` tells the user's content beat by beat, so its story holds on its own. Show both on the card as one `<Compare>`: the source video as `a`, `clone.json` as `b` and `breakdown.json` as `shots`.\n- **Music**: in the drafts, also follow `akb raw agent-file video-cloner references/music.md`.\n- **Changes**: a change to a beat updates that beat in both drafts.\n\n## Memory\n\nYour memory folder is `docs/kanban/memory/agents/video-cloner/`. Read it before the drafts; the current card wins.\n\n- **Motion library**: `motions/<slug>/` holds one effect that a finished template reproduced well \u2014 its source code and a `motion.md` with one line on the style it matches and one keyframe. Add one after the check only when it is reusable beyond this video.\n- **Prop library**: `props/<slug>/` holds one high-fidelity system UI piece, such as a file-picker selection box, a browser window or a window frame \u2014 its source code and a `prop.md` with one line on what it draws and one keyframe. Add one after the check.\n- **Self-contained**: a motion or prop folder carries every style and helper it uses, so it runs with only the template engine and the content file.\n- **Match first**: before writing a motion or drawing a prop, run `akb cloud template list-tags --category motion` or `--category prop`, search within the best tag, then check your own library; use your library alone when signed out or offline. Use a candidate when its style and timing match the source; build one when nothing does.\n- **Motion fit**: a motion that differs only in what the beat passes in, the theme or the beat's length is the same motion; one that differs in a value written into its code, such as a step's duration, easing or distance, gains a parameter defaulting to the old value; one that differs in its steps or what moves is a new motion.\n- **Cache**: `akb cloud template get` a catalog match into a new empty folder, make it that `<slug>/` folder in your library, replacing a local one of the same name, and update the index.\n- **Index**: `motions.md` and `props.md` list each entry as `- [<slug>](<folder>/<slug>/<file>.md) \u2014 <one line>`.\n- **Corrections**: `feedback.md` holds user corrections that change your next recipe, following \"What earns a note\" in `akb guide board`; `product.md` holds those about this product's real screens and flows.\n- **Size**: keep one entry under 1MB; leave out renders, dependencies and source videos.\n";
6294
6292
  }
6295
6293
  });
6296
6294
 
@@ -6306,7 +6304,7 @@ var init_music = __esm({
6306
6304
  var AGENT_default24;
6307
6305
  var init_AGENT24 = __esm({
6308
6306
  "../cli/src/agents/video-template-builder/AGENT.md"() {
6309
- AGENT_default24 = "---\nname: video-template-builder\ndescription: Builds a video clone's reusable beat-table template from its approved drafts, renders it once and checks it against the source beat by beat.\nakb:\n after: [video-cloner]\n session: new\n i18n:\n en:\n title: Video template builder\n zh:\n title: \u89C6\u9891\u6A21\u677F\u5236\u4F5C\n description: \u6309\u786E\u8BA4\u7684\u590D\u523B\u5206\u955C\u505A\u6210\u4FDD\u7559\u62CD\u6CD5\u3001\u53EF\u6362\u5185\u5BB9\u7684 beat \u8868\u6A21\u677F\uFF0C\u6E32\u67D3\u4E00\u652F\u5E76\u4E0E\u539F\u7247\u9010\u955C\u5BF9\u6BD4\u3002\n---\n\nBuild the template the approved drafts in ``## By `video-cloner` agent`` describe: it reproduces the source's craft and renders the user's content in place of the source's. You are done, with no further question, when the clone's comparison with the source and the render command are recorded on the card.\n\nFollow Terms, Deciding, Files and Memory in `akb raw agent-file video-cloner AGENT.md`; its memory folder is yours.\n\n## Steps\n\n- **Recipe**: write the format (size, frame rate, length, fonts, colours) to `recipe.md` in the files folder; it is your working file, never shown on the card.\n- **Template**: build a project that renders `clone.json` beat by beat, picking for each motion an animation library that fits its style; one project may combine several. The beat table and all content live in one file, holding the user's content from the start, so a beat can be removed or reordered and the content swapped without code changes; the code holds only craft and props. Its `template.md` states that the content in that file is to be replaced with the user's own and that the template reproduces only the source's craft.\n- **Cached clips**: render each beat to its own cached clip and splice the clips into the full video; a change re-renders only the clips it touches.\n- **Check**: render it once and compare it with the source beat by beat side by side on rhythm, motion and layout, and check every on-screen step makes sense to a viewer. A beat that misses the source's structure means the template is wrong: fix it and render again, never hand over the breakdown instead.\n- **Music**: in the template and check, also follow `akb raw agent-file video-cloner references/music.md`.\n- **Card**: under your section heading, show only the clone against the source as a `<Compare>` with `breakdown.json` as `shots`, and each difference left in one line, naming its beat id; append a ticked todo with the template folder's absolute path and, in backticks, its render command.\n- **Changes**: a change to a beat updates only that beat in the template.\n";
6307
+ AGENT_default24 = "---\nname: video-template-builder\ndescription: Builds a video clone's reusable beat-table template from its approved drafts, renders it once and checks it against the source beat by beat.\nakb:\n after: [video-cloner]\n session: new\n i18n:\n en:\n title: Video template\n zh:\n title: \u89C6\u9891\u6A21\u677F\n description: \u6309\u786E\u8BA4\u7684\u590D\u523B\u5206\u955C\u505A\u6210\u4FDD\u7559\u62CD\u6CD5\u3001\u53EF\u6362\u5185\u5BB9\u7684 beat \u8868\u6A21\u677F\uFF0C\u6E32\u67D3\u4E00\u652F\u5E76\u4E0E\u539F\u7247\u9010\u955C\u5BF9\u6BD4\u3002\n---\n\nBuild the template the approved drafts in ``## By `video-cloner` agent`` describe: it reproduces the source's craft and renders the user's content in place of the source's. You are done, with no further question, when the clone's comparison with the source and the render command are recorded on the card.\n\nFollow Terms, Deciding, Files and Memory in `akb raw agent-file video-cloner AGENT.md`; its memory folder is yours.\n\n## Steps\n\n- **Recipe**: write the format (size, frame rate, length, fonts, colours) to `recipe.md` in the files folder; it is your working file, never shown on the card.\n- **Template**: build a project that renders `clone.json` beat by beat, picking for each motion an animation library that fits its style; one project may combine several. The beat table and all content live in one file, holding the user's content from the start, so a beat can be removed or reordered and the content swapped without code changes; the code holds only craft and props. Its `template.md` states that the content in that file is to be replaced with the user's own and that the template reproduces only the source's craft.\n- **Cached clips**: render each beat to its own cached clip and splice the clips into the full video; a change re-renders only the clips it touches.\n- **Check**: render it once and compare it with the source beat by beat side by side on rhythm, motion and layout, and check every on-screen step makes sense to a viewer. A beat that misses the source's structure means the template is wrong: fix it and render again, never hand over the breakdown instead.\n- **Music**: in the template and check, also follow `akb raw agent-file video-cloner references/music.md`.\n- **Card**: under your section heading, show only the clone against the source as a `<Compare>` with `breakdown.json` as `shots`, and each difference left in one line, naming its beat id; append a ticked todo with the template folder's absolute path and, in backticks, its render command.\n- **Changes**: a change to a beat updates only that beat in the template.\n";
6310
6308
  }
6311
6309
  });
6312
6310
 
@@ -6314,7 +6312,7 @@ var init_AGENT24 = __esm({
6314
6312
  var AGENT_default25;
6315
6313
  var init_AGENT25 = __esm({
6316
6314
  "../cli/src/agents/workflow-writer/AGENT.md"() {
6317
- AGENT_default25 = "---\nname: workflow-writer\ndescription: Writes a new workflow, or changes one of the user's own, as a `WORKFLOW.md` and the agents it names.\nakb:\n i18n:\n en:\n title: Workflow writer\n zh:\n title: \u642D\u5EFA\u5DE5\u4F5C\u6D41\n description: \u6309\u4F60\u7684\u63CF\u8FF0\u5199\u51FA\u4E00\u4E2A\u5DE5\u4F5C\u6D41\u53CA\u5176 agent\uFF0C\u6216\u4FEE\u6539\u4F60\u5DF2\u6709\u7684\u5DE5\u4F5C\u6D41\u3002\n---\n\nYou write a workflow from the user's request: a kind of work the user runs again and again, where each task shows a person previews to approve before the work goes on.\n\n## Read first\n\n- **The project**: when the board belongs to a project folder, read its README and agent instruction files (such as `AGENTS.md` or `CLAUDE.md`) for what it makes, and its existing outputs of this kind for their format and destination.\n- **This board**: run `akb workflow list` and read `docs/kanban/agents/`, so a new workflow does not repeat one the board already has.\n\n## What to write\n\n- **The workflow**: when the card names a workflow folder, the workflow is that folder: keep its id and change it in place. Otherwise create it with `akb workflow new`. Name it with `akb workflow rename`, and list its agents in its `WORKFLOW.md`: `previews` the agents whose drafts a person approves, `land` the agents that run unattended in order after the last approval, and `scheduled`. Its body is how a card of this kind is written.\n- **Its agents**: write each agent it names as `akb guide write-agent` says.\n- **Approvals**: default to previews a person approves once. Give an agent `after` only when its preview is too costly to make before another is approved, such as a video before its script; it then waits for that approval.\n- **Previews**: give each part a person must see its own agent, such as the screens of a UI change or the script of a video. Show only what the person decides on, and keep previews from overlapping: a UI preview already shows its text, so add no separate copy preview.\n- **Land**: add agents to `land` only for work that needs no review, such as building and merging an approved change; leave it empty when the last approved preview is the deliverable.\n- **Memory**: for each agent, decide whether a user's correction should change its next output, such as a writer's tone or a designer's look; if so, declare its memory as `akb guide write-agent` says, otherwise give it none.\n- **Changing a workflow**: edit its `WORKFLOW.md` and its agents under `docs/kanban/agents/` in place, and keep what the request does not ask to change.\n\n## Check it\n\nRun `akb spec` and `akb workflow list`, and fix every problem reported for this workflow or its agents before finishing.\n\n## Card\n\nUnder your section heading, say in a few lines, for a reader who has never seen a workflow file, what the workflow is for, what each approval shows, what runs after the last one, and what each agent remembers; then embed each file you wrote as an `<Asset>`.\n";
6315
+ AGENT_default25 = "---\nname: workflow-writer\ndescription: Writes a new workflow, or changes one of the user's own, as a `WORKFLOW.md` and the agents it names.\nakb:\n i18n:\n en:\n title: Workflow\n zh:\n title: \u5DE5\u4F5C\u6D41\n description: \u6309\u4F60\u7684\u63CF\u8FF0\u5199\u51FA\u4E00\u4E2A\u5DE5\u4F5C\u6D41\u53CA\u5176 agent\uFF0C\u6216\u4FEE\u6539\u4F60\u5DF2\u6709\u7684\u5DE5\u4F5C\u6D41\u3002\n---\n\nYou write a workflow from the user's request: a kind of work the user runs again and again, where each task shows a person previews to approve before the work goes on.\n\n## Read first\n\n- **The project**: when the board belongs to a project folder, read its README and agent instruction files (such as `AGENTS.md` or `CLAUDE.md`) for what it makes, and its existing outputs of this kind for their format and destination.\n- **This board**: run `akb workflow list` and read `docs/kanban/agents/`, so a new workflow does not repeat one the board already has.\n\n## What to write\n\n- **The workflow**: when the card names a workflow folder, the workflow is that folder: keep its id and change it in place. Otherwise create it with `akb workflow new`. Name it with `akb workflow rename`, and list its agents in its `WORKFLOW.md`: `previews` the agents whose drafts a person approves, `land` the agents that run unattended in order after the last approval, and `scheduled`. Its body is how a card of this kind is written.\n- **Its agents**: write each agent it names as `akb guide write-agent` says.\n- **Approvals**: default to previews a person approves once. Give an agent `after` only when its preview is too costly to make before another is approved, such as a video before its script; it then waits for that approval.\n- **Previews**: give each part a person must see its own agent, such as the screens of a UI change or the script of a video. Show only what the person decides on, and keep previews from overlapping: a UI preview already shows its text, so add no separate copy preview.\n- **Land**: add agents to `land` only for work that needs no review, such as building and merging an approved change; leave it empty when the last approved preview is the deliverable.\n- **Memory**: for each agent, decide whether a user's correction should change its next output, such as a writer's tone or a designer's look; if so, declare its memory as `akb guide write-agent` says, otherwise give it none.\n- **Changing a workflow**: edit its `WORKFLOW.md` and its agents under `docs/kanban/agents/` in place, and keep what the request does not ask to change.\n\n## Check it\n\nRun `akb spec` and `akb workflow list`, and fix every problem reported for this workflow or its agents before finishing.\n\n## Card\n\nUnder your section heading, say in a few lines, for a reader who has never seen a workflow file, what the workflow is for, what each approval shows, what runs after the last one, and what each agent remembers; then embed each file you wrote as an `<Asset>`.\n";
6318
6316
  }
6319
6317
  });
6320
6318
 
@@ -6653,16 +6651,16 @@ function readCatalog() {
6653
6651
  const agents = [];
6654
6652
  const problems = [];
6655
6653
  const refused = [];
6656
- const take = (read5, folder) => {
6657
- if ("problem" in read5) {
6658
- if (!read5.missing) problems.push(read5.problem);
6659
- if (!read5.file) return;
6660
- const { file, name: name2, old } = read5;
6661
- const cause = read5.missing ? "noFile" : old ? "oldKeys" : "file";
6654
+ const take = (read6, folder) => {
6655
+ if ("problem" in read6) {
6656
+ if (!read6.missing) problems.push(read6.problem);
6657
+ if (!read6.file) return;
6658
+ const { file, name: name2, old } = read6;
6659
+ const cause = read6.missing ? "noFile" : old ? "oldKeys" : "file";
6662
6660
  refused.push({ folder, file, cause, ...name2 ? { name: name2 } : {}, ...old ? { keys: old.keys } : {} });
6663
6661
  return;
6664
6662
  }
6665
- const { name, from, builtIn } = read5.agent;
6663
+ const { name, from, builtIn } = read6.agent;
6666
6664
  const refuse3 = (cause, problem) => {
6667
6665
  problems.push(problem);
6668
6666
  if (!builtIn) refused.push({ folder, name, file: from, cause });
@@ -6686,14 +6684,14 @@ function readCatalog() {
6686
6684
  `${from}: its folder is \`${folder}\` but it calls itself \`${name}\`. An agent is asked for by its folder name \u2014 make the two match.`
6687
6685
  );
6688
6686
  }
6689
- agents.push(read5.agent);
6687
+ agents.push(read6.agent);
6690
6688
  };
6691
6689
  for (const folder of bundledFolders()) take(readBundled(folder), folder);
6692
6690
  for (const folder of projectFolders(AGENTS)) {
6693
- const read5 = readProject(AGENTS, folder);
6694
- take(read5, folder);
6695
- if ("agent" in read5 && read5.agent.from.endsWith(LEGACY_AGENT_FILE)) {
6696
- problems.push(`${read5.agent.from}: rename it to ${AGENT_FILE}`);
6691
+ const read6 = readProject(AGENTS, folder);
6692
+ take(read6, folder);
6693
+ if ("agent" in read6 && read6.agent.from.endsWith(LEGACY_AGENT_FILE)) {
6694
+ problems.push(`${read6.agent.from}: rename it to ${AGENT_FILE}`);
6697
6695
  }
6698
6696
  }
6699
6697
  for (const folder of projectFolders(LEGACY_AGENTS)) {
@@ -6795,10 +6793,10 @@ function readProject(root, folder) {
6795
6793
  const missing = rel(path17.join(dir, AGENT_FILE));
6796
6794
  if (!found2) return { problem: `${missing} is missing`, file: missing, missing: true };
6797
6795
  const from = rel(path17.join(dir, found2.name));
6798
- const read5 = parseSpecAgent(found2.text, from, file, false, () => ownFiles(walkFolder(dir, "")));
6799
- if ("problem" in read5) return { ...read5, file: from };
6800
- if ("agent" in read5) Object.assign(read5.agent, { dir, text: found2.text });
6801
- return read5;
6796
+ const read6 = parseSpecAgent(found2.text, from, file, false, () => ownFiles(walkFolder(dir, "")));
6797
+ if ("problem" in read6) return { ...read6, file: from };
6798
+ if ("agent" in read6) Object.assign(read6.agent, { dir, text: found2.text });
6799
+ return read6;
6802
6800
  }
6803
6801
  var specAgentCatalog, bundledFolders, AGENT_FILE, LEGACY_AGENT_FILE, AGENT_FILES, ownFiles;
6804
6802
  var init_catalog2 = __esm({
@@ -7602,10 +7600,10 @@ function findRollout(threadId) {
7602
7600
  }
7603
7601
  for (const name of names5) {
7604
7602
  if (!name.startsWith("rollout-") || !name.endsWith(tail2)) continue;
7605
- const path80 = join(dir, name);
7603
+ const path83 = join(dir, name);
7606
7604
  try {
7607
- const at = statSync(path80).mtimeMs;
7608
- if (!newest || at > newest.at) newest = { path: path80, at };
7605
+ const at = statSync(path83).mtimeMs;
7606
+ if (!newest || at > newest.at) newest = { path: path83, at };
7609
7607
  } catch {
7610
7608
  }
7611
7609
  }
@@ -7613,11 +7611,11 @@ function findRollout(threadId) {
7613
7611
  return newest?.path;
7614
7612
  }
7615
7613
  function readCodexRunFacts(threadId) {
7616
- const path80 = findRollout(threadId);
7617
- if (!path80) return void 0;
7614
+ const path83 = findRollout(threadId);
7615
+ if (!path83) return void 0;
7618
7616
  let raw;
7619
7617
  try {
7620
- raw = readFileSync(path80, "utf8");
7618
+ raw = readFileSync(path83, "utf8");
7621
7619
  } catch {
7622
7620
  return void 0;
7623
7621
  }
@@ -7745,8 +7743,8 @@ function renderCompleted(item) {
7745
7743
  const changes = Array.isArray(item.changes) ? item.changes : [];
7746
7744
  return changes.map((raw) => {
7747
7745
  const change2 = obj(raw);
7748
- const path80 = str2(change2.path);
7749
- return path80 ? `\u23FA ${str2(change2.kind) || "change"}(${path80})
7746
+ const path83 = str2(change2.path);
7747
+ return path83 ? `\u23FA ${str2(change2.kind) || "change"}(${path83})
7750
7748
  ` : "";
7751
7749
  }).join("");
7752
7750
  }
@@ -8005,10 +8003,10 @@ function readKimiRunFacts(sessionId) {
8005
8003
  const total = { input: 0, cacheCreation: 0, cacheRead: 0, output: 0 };
8006
8004
  let model;
8007
8005
  let holding = 0;
8008
- for (const { path: path80, main } of wireFiles(entry.sessionDir)) {
8006
+ for (const { path: path83, main } of wireFiles(entry.sessionDir)) {
8009
8007
  let raw;
8010
8008
  try {
8011
- raw = readFileSync2(path80, "utf8");
8009
+ raw = readFileSync2(path83, "utf8");
8012
8010
  } catch {
8013
8011
  continue;
8014
8012
  }
@@ -8289,8 +8287,8 @@ var init_tail = __esm({
8289
8287
  import fs20 from "node:fs";
8290
8288
  function whereHint(update2, title) {
8291
8289
  const locations = Array.isArray(update2.locations) ? update2.locations : [];
8292
- const path80 = str2(obj(locations[0]).path);
8293
- return path80 && !title.includes(path80) ? hint(path80) : "";
8290
+ const path83 = str2(obj(locations[0]).path);
8291
+ return path83 && !title.includes(path83) ? hint(path83) : "";
8294
8292
  }
8295
8293
  function saidBy(update2) {
8296
8294
  const content = Array.isArray(update2.content) ? update2.content : [];
@@ -8761,8 +8759,8 @@ async function tokensOf(rpc, sessionId) {
8761
8759
  }
8762
8760
  async function contextOf(rpc, sessionId) {
8763
8761
  try {
8764
- const read5 = await rpc.call("session/read", { sessionId, messageLimit: 1 });
8765
- const state = obj(read5.projection);
8762
+ const read6 = await rpc.call("session/read", { sessionId, messageLimit: 1 });
8763
+ const state = obj(read6.projection);
8766
8764
  const used = num(state.contextUsed);
8767
8765
  const limit = num(state.contextWindow);
8768
8766
  return used > 0 ? { used, limit: limit > 0 ? limit : void 0 } : void 0;
@@ -9259,9 +9257,9 @@ function outsideBoardForAdd(cwd = REPO_ROOT) {
9259
9257
  );
9260
9258
  return ["--", ".", ...kept.map((p) => `:(exclude)${p}`)];
9261
9259
  }
9262
- function boardPathsAmong(files) {
9260
+ function boardPathsAmong(files2) {
9263
9261
  const owned = boardManagedPaths();
9264
- return files.filter((file) => {
9262
+ return files2.filter((file) => {
9265
9263
  const p = posix(file);
9266
9264
  return owned.some((dir) => p === dir || p.startsWith(`${dir}/`));
9267
9265
  });
@@ -9375,9 +9373,9 @@ function treeMark(base, cwd = REPO_ROOT) {
9375
9373
  const index = path22.join(ensureAkbDir(), `index-${process.pid}-${Date.now()}`);
9376
9374
  try {
9377
9375
  const env = { ...process.env, GIT_INDEX_FILE: index };
9378
- const run = (args) => spawnSync2("git", args, { cwd, encoding: "utf8", maxBuffer: MAX_OUTPUT, windowsHide: true, env }).status === 0;
9379
- if (!run(["read-tree", base])) return null;
9380
- if (!run(["add", "-A", ...outsideBoardForAdd(cwd)])) return null;
9376
+ const run2 = (args) => spawnSync2("git", args, { cwd, encoding: "utf8", maxBuffer: MAX_OUTPUT, windowsHide: true, env }).status === 0;
9377
+ if (!run2(["read-tree", base])) return null;
9378
+ if (!run2(["add", "-A", ...outsideBoardForAdd(cwd)])) return null;
9381
9379
  const tree = spawnSync2("git", ["write-tree"], {
9382
9380
  cwd,
9383
9381
  encoding: "utf8",
@@ -9467,7 +9465,7 @@ function ignoredInTheWay(commit, cwd) {
9467
9465
  }
9468
9466
  function blockedBy(why) {
9469
9467
  let kind;
9470
- const files = [];
9468
+ const files2 = [];
9471
9469
  for (const line2 of why.split("\n")) {
9472
9470
  if (!kind) {
9473
9471
  kind = headingKind(line2);
@@ -9475,9 +9473,9 @@ function blockedBy(why) {
9475
9473
  }
9476
9474
  if (!/^\s/.test(line2)) break;
9477
9475
  const file = line2.trim();
9478
- if (file) files.push(file);
9476
+ if (file) files2.push(file);
9479
9477
  }
9480
- return kind && files.length ? { kind, files } : void 0;
9478
+ return kind && files2.length ? { kind, files: files2 } : void 0;
9481
9479
  }
9482
9480
  function branchWorktree(branch) {
9483
9481
  const out = git(["worktree", "list", "--porcelain"]);
@@ -9523,7 +9521,7 @@ var init_worktree = __esm({
9523
9521
  return out === null ? null : out.split("\n").filter(Boolean);
9524
9522
  };
9525
9523
  branchPatch = (base, branch, cwd = REPO_ROOT) => gitDiff(["diff", "--binary", base, branch, ...outsideBoard()], cwd);
9526
- lastCommitTouching = (ref, files, cwd = REPO_ROOT) => git(["log", "-1", "--format=%H", ref, "--", ...files], cwd)?.trim() || null;
9524
+ lastCommitTouching = (ref, files2, cwd = REPO_ROOT) => git(["log", "-1", "--format=%H", ref, "--", ...files2], cwd)?.trim() || null;
9527
9525
  continueRebase = (dir) => scripted(["rebase", "--continue"], dir);
9528
9526
  abortRebase = (dir) => void scripted(["rebase", "--abort"], dir);
9529
9527
  conflictedPaths = (dir) => (git(["diff", "--name-only", "--diff-filter=U"], dir) ?? "").split("\n").filter(Boolean);
@@ -9591,8 +9589,8 @@ function modified(file) {
9591
9589
  function byPath(a, b) {
9592
9590
  return a < b ? -1 : a > b ? 1 : 0;
9593
9591
  }
9594
- function byNewest(files) {
9595
- return files.map((file) => ({ file, at: modified(file) })).sort((a, b) => b.at - a.at || byPath(a.file, b.file)).map((one) => one.file);
9592
+ function byNewest(files2) {
9593
+ return files2.map((file) => ({ file, at: modified(file) })).sort((a, b) => b.at - a.at || byPath(a.file, b.file)).map((one) => one.file);
9596
9594
  }
9597
9595
  function versionOf(name) {
9598
9596
  const found2 = /(\d+(?:\.\d+)+)/.exec(name);
@@ -11058,17 +11056,17 @@ function noteRefineTried(store, cardId2) {
11058
11056
  }
11059
11057
  function cardsBeingCreated(runs = readRuns()) {
11060
11058
  const newest = /* @__PURE__ */ new Map();
11061
- for (const run of runs) {
11062
- for (const id of run.createdCardIds ?? []) {
11063
- if (run.cardId === id) continue;
11059
+ for (const run2 of runs) {
11060
+ for (const id of run2.createdCardIds ?? []) {
11061
+ if (run2.cardId === id) continue;
11064
11062
  const best = newest.get(id);
11065
- if (!best || run.startedAt >= best.startedAt) newest.set(id, run);
11063
+ if (!best || run2.startedAt >= best.startedAt) newest.set(id, run2);
11066
11064
  }
11067
11065
  }
11068
11066
  const out = /* @__PURE__ */ new Map();
11069
- for (const [id, run] of newest) {
11070
- if (runIsLive(run)) out.set(id, { state: "creating", runId: run.sessionId });
11071
- else if (run.status !== "done") out.set(id, { state: "unfinished", runId: run.sessionId });
11067
+ for (const [id, run2] of newest) {
11068
+ if (runIsLive(run2)) out.set(id, { state: "creating", runId: run2.sessionId });
11069
+ else if (run2.status !== "done") out.set(id, { state: "unfinished", runId: run2.sessionId });
11072
11070
  }
11073
11071
  return out;
11074
11072
  }
@@ -11156,8 +11154,8 @@ function readTouched(raw) {
11156
11154
  }
11157
11155
  function readPictures(raw) {
11158
11156
  if (!Array.isArray(raw)) return void 0;
11159
- const files = raw.filter((p) => typeof p === "string" && p.length > 0);
11160
- return files.length ? files : void 0;
11157
+ const files2 = raw.filter((p) => typeof p === "string" && p.length > 0);
11158
+ return files2.length ? files2 : void 0;
11161
11159
  }
11162
11160
  function readBlocker(raw) {
11163
11161
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) return void 0;
@@ -11214,18 +11212,18 @@ function readLandingWait(raw) {
11214
11212
  if (!raw || typeof raw !== "object") return void 0;
11215
11213
  const box2 = raw;
11216
11214
  if (box2.kind !== "overwrite" && box2.kind !== "untracked") return void 0;
11217
- const files = Array.isArray(box2.files) ? box2.files.filter((f) => typeof f === "string") : [];
11218
- return files.length ? { kind: box2.kind, files } : void 0;
11215
+ const files2 = Array.isArray(box2.files) ? box2.files.filter((f) => typeof f === "string") : [];
11216
+ return files2.length ? { kind: box2.kind, files: files2 } : void 0;
11219
11217
  }
11220
11218
  function readLandingReason(raw) {
11221
11219
  if (!raw || typeof raw !== "object") return void 0;
11222
11220
  const box2 = raw;
11223
11221
  if (!LANDING_REASONS.has(box2.kind)) return void 0;
11224
- const files = Array.isArray(box2.files) ? box2.files.filter((f) => typeof f === "string") : [];
11222
+ const files2 = Array.isArray(box2.files) ? box2.files.filter((f) => typeof f === "string") : [];
11225
11223
  return {
11226
11224
  kind: box2.kind,
11227
11225
  ...text2(box2.behind) ? { behind: text2(box2.behind) } : {},
11228
- ...files.length ? { files } : {}
11226
+ ...files2.length ? { files: files2 } : {}
11229
11227
  };
11230
11228
  }
11231
11229
  function readAnswers(raw) {
@@ -11249,7 +11247,7 @@ function readReview(raw) {
11249
11247
  if (!raw || typeof raw !== "object") return void 0;
11250
11248
  const box2 = raw;
11251
11249
  const rounds = Array.isArray(box2.rounds) ? box2.rounds : [];
11252
- const stop = box2.stopped;
11250
+ const stop2 = box2.stopped;
11253
11251
  return {
11254
11252
  rounds: rounds.flatMap(
11255
11253
  (r) => r && typeof r.sessionId === "string" && isVerdict(r.verdict) ? [
@@ -11263,11 +11261,11 @@ function readReview(raw) {
11263
11261
  }
11264
11262
  ] : []
11265
11263
  ),
11266
- stopped: stop && typeof stop === "object" && typeof stop.why === "string" ? {
11267
- reason: asStopReason(stop.reason),
11268
- why: stop.why,
11269
- at: typeof stop.at === "number" ? stop.at : 0,
11270
- ...Array.isArray(stop.paths) ? { paths: stop.paths.filter((p) => typeof p === "string") } : {}
11264
+ stopped: stop2 && typeof stop2 === "object" && typeof stop2.why === "string" ? {
11265
+ reason: asStopReason(stop2.reason),
11266
+ why: stop2.why,
11267
+ at: typeof stop2.at === "number" ? stop2.at : 0,
11268
+ ...Array.isArray(stop2.paths) ? { paths: stop2.paths.filter((p) => typeof p === "string") } : {}
11271
11269
  } : void 0
11272
11270
  };
11273
11271
  }
@@ -11330,9 +11328,9 @@ function recordCreatedCards(sessionId, ids) {
11330
11328
  const valid = ids.filter((id) => Number.isInteger(id) && id > 0);
11331
11329
  if (!valid.length) return false;
11332
11330
  return withRuns((runs) => {
11333
- const run = runs.find((entry) => entry.sessionId === sessionId && entry.status === "running");
11334
- if (!run) return false;
11335
- run.createdCardIds = [.../* @__PURE__ */ new Set([...run.createdCardIds ?? [], ...valid])];
11331
+ const run2 = runs.find((entry) => entry.sessionId === sessionId && entry.status === "running");
11332
+ if (!run2) return false;
11333
+ run2.createdCardIds = [.../* @__PURE__ */ new Set([...run2.createdCardIds ?? [], ...valid])];
11336
11334
  return true;
11337
11335
  });
11338
11336
  }
@@ -11382,7 +11380,7 @@ var init_store = __esm({
11382
11380
  stringRecord = (raw) => raw && typeof raw === "object" && !Array.isArray(raw) && Object.values(raw).every((v) => typeof v === "string") ? raw : void 0;
11383
11381
  readRuns = () => readStore().runs;
11384
11382
  PENDING_MS = 3e4;
11385
- runIsLive = (run) => run.status === "running" && (run.pid ? pidAlive(run.pid) : Date.now() - run.startedAt < PENDING_MS);
11383
+ runIsLive = (run2) => run2.status === "running" && (run2.pid ? pidAlive(run2.pid) : Date.now() - run2.startedAt < PENDING_MS);
11386
11384
  creationOf = (runs, cardId2) => cardsBeingCreated(runs).get(cardId2);
11387
11385
  cardCreation = (cardId2, strict = false) => {
11388
11386
  const runs = readRuns();
@@ -11553,24 +11551,24 @@ var init_flows = __esm({
11553
11551
  });
11554
11552
 
11555
11553
  // ../cli/src/lib/agent/language.ts
11556
- function translating(language = readLanguage()) {
11557
- return language !== DEFAULT_LANGUAGE;
11554
+ function languageNote() {
11555
+ return process.env[LANGUAGE_LINE_ENV] ?? LINES[readLanguage()];
11558
11556
  }
11559
- function languageNote(language = readLanguage()) {
11560
- if (!translating(language)) return "";
11561
- const name = LANGUAGE_NAMES[language];
11562
- return [
11563
- `Write this board's prose in ${name}: card titles and bodies, open questions and their options, memory notes, and what you say back to me.`,
11564
- `Keep English whatever the language: frontmatter keys and their fixed values, \`##\` and \`###\` section headings, the \`<!-- agent -->\` boundary, todo checkboxes, the \`[user]\` tag, track and module names, and card filenames. Never rename or translate a section title \u2014 the board matches those by literal English text, so a changed one is a card it can no longer read, and a title that is not English needs \`akb raw create --slug\` a short English slug.`,
11565
- `Rewriting a card or a memory file that already exists keeps the language that file is already in; only what you write for me to read personally \u2014 an open question and its options \u2014 follows ${name} on every card, and so does the first note in a memory file still holding nothing but its seeded header.`,
11566
- `This governs the board's prose alone: code, comments, commit messages and the repository's own documents follow the repository, not me. \`akb guide board\` carries the rule in full.`
11567
- ].join(" ");
11557
+ function translating() {
11558
+ const line2 = languageNote();
11559
+ return line2 !== "" && !/\bEnglish\b/.test(line2);
11568
11560
  }
11561
+ var LANGUAGE_LINE_ENV, LINES, inLanguage;
11569
11562
  var init_language = __esm({
11570
11563
  "../cli/src/lib/agent/language.ts"() {
11571
11564
  "use strict";
11572
11565
  init_settings();
11573
- init_types();
11566
+ LANGUAGE_LINE_ENV = "AI4KANBAN_LANGUAGE_LINE";
11567
+ LINES = {
11568
+ en: "",
11569
+ zh: "\u8BF7\u7528\u4E2D\u6587\u56DE\u590D\u548C\u5199\u4F5C\u3002"
11570
+ };
11571
+ inLanguage = (prompt) => [prompt, languageNote()].filter(Boolean).join("\n\n");
11574
11572
  }
11575
11573
  });
11576
11574
 
@@ -12497,11 +12495,11 @@ function modelsRun() {
12497
12495
  const ran = /* @__PURE__ */ new Map();
12498
12496
  const { runs } = readStore();
12499
12497
  for (let i = runs.length - 1; i >= 0; i--) {
12500
- const run = runs[i];
12501
- if (!run.harness || !run.model) continue;
12502
- const seen = ran.get(run.harness) ?? [];
12503
- if (!seen.includes(run.model)) seen.push(run.model);
12504
- ran.set(run.harness, seen);
12498
+ const run2 = runs[i];
12499
+ if (!run2.harness || !run2.model) continue;
12500
+ const seen = ran.get(run2.harness) ?? [];
12501
+ if (!seen.includes(run2.model)) seen.push(run2.model);
12502
+ ran.set(run2.harness, seen);
12505
12503
  }
12506
12504
  return ran;
12507
12505
  }
@@ -13346,9 +13344,9 @@ function boardWorkflowDefs() {
13346
13344
  problems.push(`${from} is missing`);
13347
13345
  continue;
13348
13346
  }
13349
- const read5 = parseWorkflowFile(id, source, from);
13350
- if ("problem" in read5) problems.push(read5.problem);
13351
- else defs.push(read5.def);
13347
+ const read6 = parseWorkflowFile(id, source, from);
13348
+ if ("problem" in read6) problems.push(read6.problem);
13349
+ else defs.push(read6.def);
13352
13350
  }
13353
13351
  return { defs, problems };
13354
13352
  }
@@ -13392,9 +13390,9 @@ var init_workflow_file = __esm({
13392
13390
  plain = (agents) => agents.every((h) => !h.extra && !h.off);
13393
13391
  flowList = (agents) => `[${agents.map((h) => scalar(h.agent)).join(", ")}]`;
13394
13392
  BUILTIN_DEFS = BUNDLED.map(([id, source]) => {
13395
- const read5 = parseWorkflowFile(id, source, `the built-in \`${id}\` workflow`);
13396
- if ("problem" in read5) throw new Error(read5.problem);
13397
- return read5.def;
13393
+ const read6 = parseWorkflowFile(id, source, `the built-in \`${id}\` workflow`);
13394
+ if ("problem" in read6) throw new Error(read6.problem);
13395
+ return read6.def;
13398
13396
  });
13399
13397
  fileOf = (id) => path30.join(WORKFLOWS, id, WORKFLOW_FILE);
13400
13398
  }
@@ -13465,8 +13463,8 @@ function foldOldStages(cfg) {
13465
13463
  if (!savedOldStages(cfg)) return false;
13466
13464
  const { ok } = writeConfig((raw) => {
13467
13465
  const block = configBlock(raw.workflows);
13468
- const all = configBlock(block.stages);
13469
- for (const [id, flow] of Object.entries(all)) {
13466
+ const all2 = configBlock(block.stages);
13467
+ for (const [id, flow] of Object.entries(all2)) {
13470
13468
  const mine = { ...configBlock(flow) };
13471
13469
  const plan = readRows(mine.plan);
13472
13470
  const lead = configBlock(mine.execute).lead;
@@ -13476,10 +13474,10 @@ function foldOldStages(cfg) {
13476
13474
  if (typeof lead === "string" && lead.trim() && isBuiltinWorkflow(id) && !BUILTINS.find((w) => w.id === id).land.length) {
13477
13475
  mine[LAND] = { helpers: [{ agent: canonicalSpecAgent(lead), extra: "" }] };
13478
13476
  }
13479
- if (Object.keys(mine).length) all[id] = mine;
13480
- else delete all[id];
13477
+ if (Object.keys(mine).length) all2[id] = mine;
13478
+ else delete all2[id];
13481
13479
  }
13482
- if (Object.keys(all).length) block.stages = all;
13480
+ if (Object.keys(all2).length) block.stages = all2;
13483
13481
  else delete block.stages;
13484
13482
  for (const key of ["added", "agentsOwned", "shipped", "qaScheduled"]) delete block[key];
13485
13483
  if (Object.keys(block).length) raw.workflows = block;
@@ -13753,17 +13751,17 @@ function forgetWorkflowAgent(agent) {
13753
13751
  }
13754
13752
  }
13755
13753
  return save2((block) => {
13756
- const all = configBlock(block.stages);
13757
- for (const [id, flow] of Object.entries(all)) {
13754
+ const all2 = configBlock(block.stages);
13755
+ for (const [id, flow] of Object.entries(all2)) {
13758
13756
  const mine = configBlock(flow);
13759
13757
  for (const [key, value] of Object.entries(mine)) {
13760
13758
  const one = configBlock(value);
13761
13759
  if (Array.isArray(one.helpers)) one.helpers = one.helpers.filter((h) => configBlock(h).agent !== agent);
13762
13760
  mine[key] = one;
13763
13761
  }
13764
- all[id] = mine;
13762
+ all2[id] = mine;
13765
13763
  }
13766
- if (Object.keys(all).length) block.stages = all;
13764
+ if (Object.keys(all2).length) block.stages = all2;
13767
13765
  });
13768
13766
  }
13769
13767
  function scheduledMembers(flow) {
@@ -14042,11 +14040,11 @@ function agentFilesBlock(agent) {
14042
14040
  }
14043
14041
  function agentMemoryBlock(agent) {
14044
14042
  const dir = rel(agentMemoryDir(agent.name));
14045
- const files = readAgentMemory(agent.name);
14046
- if (!files.length) return `Your memory folder is \`${dir}/\`. Nothing is in it yet.`;
14043
+ const files2 = readAgentMemory(agent.name);
14044
+ if (!files2.length) return `Your memory folder is \`${dir}/\`. Nothing is in it yet.`;
14047
14045
  return [
14048
14046
  `Your memory, from \`${dir}/\` \u2014 what you kept on this board in earlier runs. Follow it here:`,
14049
- ...files.map((file) => `### \`${file.name}\`
14047
+ ...files2.map((file) => `### \`${file.name}\`
14050
14048
 
14051
14049
  ${file.text || "_(empty)_"}`)
14052
14050
  ].join("\n\n");
@@ -14145,8 +14143,8 @@ var init_agents = __esm({
14145
14143
  return RETIRED_SETTINGS.filter((r) => r.agent === agent.name && r.key in entry.extra).map((r) => r.note);
14146
14144
  };
14147
14145
  agentsOf = (names5) => {
14148
- const all = specAgents();
14149
- return names5.map((name) => all.find((a) => a.name === name)).filter((a) => !!a);
14146
+ const all2 = specAgents();
14147
+ return names5.map((name) => all2.find((a) => a.name === name)).filter((a) => !!a);
14150
14148
  };
14151
14149
  specAgentAssigned = (name, workflow) => {
14152
14150
  const flow = workflowFor(workflow);
@@ -14558,12 +14556,12 @@ function candidateFileStats(candidate) {
14558
14556
  const change2 = added === "-" || removed === "-" ? "binary" : `+${added} -${removed}`;
14559
14557
  return `${file} (${change2}${fresh ? ", new" : ""})`;
14560
14558
  };
14561
- const files = tracked.split("\n").filter(Boolean).map((line2) => format(line2));
14559
+ const files2 = tracked.split("\n").filter(Boolean).map((line2) => format(line2));
14562
14560
  for (const file of untrackedFiles(candidate)) {
14563
14561
  const stat = gitDiff(["diff", "--no-index", "--numstat", "--", "/dev/null", file], candidate.cwd)?.trim();
14564
- files.push(stat ? format(stat, true) : `${file} (new)`);
14562
+ files2.push(stat ? format(stat, true) : `${file} (new)`);
14565
14563
  }
14566
- return files;
14564
+ return files2;
14567
14565
  }
14568
14566
  function candidateStat(candidate) {
14569
14567
  if (!candidate.base) return null;
@@ -14644,7 +14642,7 @@ var init_outputs = __esm({
14644
14642
  return m ? [{ done: m[1] !== " ", text: m[2].trim() }] : [];
14645
14643
  });
14646
14644
  plannedTodos = (cardId2) => todos(cardId2).map((t) => t.text);
14647
- looksLikePath = (token2) => !/\s/.test(token2) && !token2.includes("://") && !token2.startsWith("-") && token2.includes("/");
14645
+ looksLikePath = (token3) => !/\s/.test(token3) && !token3.includes("://") && !token3.startsWith("-") && token3.includes("/");
14648
14646
  resolveOutput = (file) => {
14649
14647
  if (file.startsWith("~/")) return path35.join(os4.homedir(), file.slice(2));
14650
14648
  if (path35.isAbsolute(file)) return file;
@@ -14681,11 +14679,11 @@ function noWorktreeWhy() {
14681
14679
  if (!currentBranch()) return "this checkout is on a detached HEAD, so a build has nowhere to land";
14682
14680
  return void 0;
14683
14681
  }
14684
- function prepareDelivery(cardId2, wants, files = false) {
14682
+ function prepareDelivery(cardId2, wants, files2 = false) {
14685
14683
  const deliveryId = newDeliveryId();
14686
14684
  const gated = cardId2 !== null;
14687
14685
  const base = inGitRepo() ? headCommit() : null;
14688
- if (files) return { start: { deliveryId, commitMode: "files", base: base ?? void 0 } };
14686
+ if (files2) return { start: { deliveryId, commitMode: "files", base: base ?? void 0 } };
14689
14687
  if (gated && cardWorkflow(cardId2)?.needsArtifact) {
14690
14688
  return {
14691
14689
  start: {
@@ -14805,15 +14803,15 @@ var init_commit_mode = __esm({
14805
14803
  ID_LENGTH2 = 8;
14806
14804
  MAX_NAMED = 5;
14807
14805
  deliveryCwd = (delivery) => delivery.worktree ? worktreeDir(delivery.worktree) : REPO_ROOT;
14808
- dirtyRefusal = (files) => ({
14806
+ dirtyRefusal = (files2) => ({
14809
14807
  error: [
14810
14808
  "Commit or stash your changes before starting.",
14811
14809
  "",
14812
- ...files.slice(0, MAX_NAMED),
14813
- ...files.length > MAX_NAMED ? [`and ${files.length - MAX_NAMED} more`] : []
14810
+ ...files2.slice(0, MAX_NAMED),
14811
+ ...files2.length > MAX_NAMED ? [`and ${files2.length - MAX_NAMED} more`] : []
14814
14812
  ].join("\n"),
14815
14813
  reason: "dirty",
14816
- paths: files.slice(0, MAX_NAMED)
14814
+ paths: files2.slice(0, MAX_NAMED)
14817
14815
  });
14818
14816
  MAX_SUBJECT = 72;
14819
14817
  deliveryName = (delivery) => delivery.cardId === null ? `delivery ${delivery.deliveryId}` : `#${delivery.cardId}`;
@@ -14899,19 +14897,19 @@ function deliveryState(delivery, questions) {
14899
14897
  };
14900
14898
  }
14901
14899
  if (landing?.conflictFiles?.length || landing?.fitAt) {
14902
- const files = landing.conflictFiles ?? [];
14900
+ const files2 = landing.conflictFiles ?? [];
14903
14901
  const attempt2 = (landing.conflictFails ?? 0) + 1;
14904
14902
  const branch2 = `\`${delivery.targetBranch ?? "the target branch"}\``;
14905
- const where2 = files.length ? `${some(files)} against ${branch2}` : `the combined result with ${branch2}`;
14903
+ const where2 = files2.length ? `${some(files2)} against ${branch2}` : `the combined result with ${branch2}`;
14906
14904
  if (landing.status === "waiting" && landing.conflictAt) {
14907
14905
  const left = Math.max(0, Math.round((landing.conflictAt - Date.now()) / 1e3));
14908
14906
  return {
14909
14907
  stage: "retry",
14910
14908
  label: "Waiting to retry",
14911
- line: `Attempt ${attempt2 - 1} left ${where2} ${files.length ? "conflicted" : "unchecked"}. It gave the landing slot up and opens attempt ${attempt2} ${left ? `in ${left}s` : "now"} \u2014 another delivery can land while it waits.`,
14909
+ line: `Attempt ${attempt2 - 1} left ${where2} ${files2.length ? "conflicted" : "unchecked"}. It gave the landing slot up and opens attempt ${attempt2} ${left ? `in ${left}s` : "now"} \u2014 another delivery can land while it waits.`,
14912
14910
  paused: false,
14913
14911
  kind: "conflict-wait",
14914
- files,
14912
+ files: files2,
14915
14913
  branch: delivery.targetBranch,
14916
14914
  attempt: attempt2,
14917
14915
  retryIn: left
@@ -14920,11 +14918,11 @@ function deliveryState(delivery, questions) {
14920
14918
  if (landing.status === "landing") {
14921
14919
  return {
14922
14920
  stage: "conflict",
14923
- label: files.length ? "Resolving a conflict" : "Checking the combined result",
14924
- line: `Attempt ${attempt2}: ${files.length ? "resolving" : "checking"} ${where2}. It lands by itself once ${files.length ? "the conflict is out" : "that is done"} \u2014 nothing is asked of you.`,
14921
+ label: files2.length ? "Resolving a conflict" : "Checking the combined result",
14922
+ line: `Attempt ${attempt2}: ${files2.length ? "resolving" : "checking"} ${where2}. It lands by itself once ${files2.length ? "the conflict is out" : "that is done"} \u2014 nothing is asked of you.`,
14925
14923
  paused: false,
14926
14924
  kind: "conflict",
14927
- files,
14925
+ files: files2,
14928
14926
  branch: delivery.targetBranch,
14929
14927
  attempt: attempt2
14930
14928
  };
@@ -14965,6 +14963,16 @@ function deliveryState(delivery, questions) {
14965
14963
  ...reason ? { kind: reason.kind, files: reason.files, branch: delivery.targetBranch, worktree: delivery.worktree } : { kind: "refused", raw: line2 }
14966
14964
  };
14967
14965
  }
14966
+ if (landing?.status === "landing" || landing?.status === "waiting") {
14967
+ return {
14968
+ stage: "landing",
14969
+ label: "Landing",
14970
+ line: `Landing on \`${delivery.targetBranch ?? "the target branch"}\` \u2014 nothing is asked of you.`,
14971
+ paused: false,
14972
+ kind: "landing",
14973
+ branch: delivery.targetBranch
14974
+ };
14975
+ }
14968
14976
  const branch = delivery.commitMode === "auto" ? delivery.targetBranch : void 0;
14969
14977
  const where = branch ? `, to land on \`${branch}\`` : "";
14970
14978
  return {
@@ -14986,7 +14994,7 @@ var init_pause = __esm({
14986
14994
  HELD_ON_QUESTIONS = "held on an open question";
14987
14995
  IN_LINE = "in line behind";
14988
14996
  count2 = (n) => `${n} open question${n === 1 ? "" : "s"}`;
14989
- some = (files) => files.length === 1 ? `\`${files[0]}\`` : `${files.length} files`;
14997
+ some = (files2) => files2.length === 1 ? `\`${files2[0]}\`` : `${files2.length} files`;
14990
14998
  upper = (text5) => text5 ? text5[0].toUpperCase() + text5.slice(1) : text5;
14991
14999
  end = (text5) => /[.!?)]$/.test(text5) ? text5 : `${text5}.`;
14992
15000
  isHold = (why) => why.startsWith(HELD_ON_QUESTIONS);
@@ -15863,7 +15871,7 @@ var init_stage_end = __esm({
15863
15871
  const flow = flowByAction(action);
15864
15872
  return flow ? stageOfFlow(flow.command) : void 0;
15865
15873
  };
15866
- askedFor = (cardId2, agent) => readStore().runs.some((run) => run.action === "spec" && run.cardId === cardId2 && run.specAgent === agent);
15874
+ askedFor = (cardId2, agent) => readStore().runs.some((run2) => run2.action === "spec" && run2.cardId === cardId2 && run2.specAgent === agent);
15867
15875
  }
15868
15876
  });
15869
15877
 
@@ -15872,10 +15880,10 @@ function reviewOf(delivery) {
15872
15880
  if (!delivery.review) delivery.review = { rounds: [] };
15873
15881
  return delivery.review;
15874
15882
  }
15875
- function nextAfterSession(delivery, run) {
15883
+ function nextAfterSession(delivery, run2) {
15876
15884
  if (delivery.status !== "active") return HOLD;
15877
- if (run.action === "scheduled") return run.status === "done" ? { finish: true } : HOLD;
15878
- if (run.action !== "implement" || run.status !== "done") return HOLD;
15885
+ if (run2.action === "scheduled") return run2.status === "done" ? { finish: true } : HOLD;
15886
+ if (run2.action !== "implement" || run2.status !== "done") return HOLD;
15879
15887
  return stageShortfall("build", delivery) ?? { finish: true };
15880
15888
  }
15881
15889
  function stageShortfall(stage, delivery) {
@@ -15913,23 +15921,23 @@ import path41 from "node:path";
15913
15921
  function writeAudit(delivery, runs) {
15914
15922
  const known = new Map(readAudit(delivery.deliveryId)?.sessions.map((s) => [s.sessionId, s]) ?? []);
15915
15923
  const sessions = delivery.sessions.flatMap((id) => {
15916
- const run = runs.find((r) => r.sessionId === id);
15917
- if (!run) {
15924
+ const run2 = runs.find((r) => r.sessionId === id);
15925
+ if (!run2) {
15918
15926
  const before = known.get(id);
15919
15927
  return before ? [before] : [];
15920
15928
  }
15921
15929
  return [
15922
15930
  {
15923
- sessionId: run.sessionId,
15924
- action: run.action,
15925
- status: run.status,
15926
- startedAt: run.startedAt,
15927
- endedAt: run.endedAt,
15928
- harness: run.harness || void 0,
15929
- model: run.model,
15930
- costUsd: run.costUsd,
15931
- resumedFrom: run.resumedFrom,
15932
- log: rel(run.logPath)
15931
+ sessionId: run2.sessionId,
15932
+ action: run2.action,
15933
+ status: run2.status,
15934
+ startedAt: run2.startedAt,
15935
+ endedAt: run2.endedAt,
15936
+ harness: run2.harness || void 0,
15937
+ model: run2.model,
15938
+ costUsd: run2.costUsd,
15939
+ resumedFrom: run2.resumedFrom,
15940
+ log: rel(run2.logPath)
15933
15941
  }
15934
15942
  ];
15935
15943
  });
@@ -16007,7 +16015,7 @@ function settleOrphanedDeliveries() {
16007
16015
  const recovered = [];
16008
16016
  withStore((store) => {
16009
16017
  const known = new Set(store.deliveries.map((d) => d.deliveryId));
16010
- for (const run of store.runs) if (run.deliveryId) known.add(run.deliveryId);
16018
+ for (const run2 of store.runs) if (run2.deliveryId) known.add(run2.deliveryId);
16011
16019
  for (const audit of audits) {
16012
16020
  if (known.has(audit.deliveryId) || !worktreeExists(audit.worktree)) continue;
16013
16021
  if (lastSessionRunning(audit)) {
@@ -16056,13 +16064,13 @@ function endedDelivery(id) {
16056
16064
  const row = auditRow(audit);
16057
16065
  if (row) rows.set(row.deliveryId, row);
16058
16066
  }
16059
- const all = [...rows.values()].sort((a, b) => a.startedAt - b.startedAt);
16067
+ const all2 = [...rows.values()].sort((a, b) => a.startedAt - b.startedAt);
16060
16068
  if (/^#?\d+$/.test(key)) {
16061
16069
  const cardId2 = Number(key.replace("#", ""));
16062
- const mine = all.filter((d) => d.cardId === cardId2);
16070
+ const mine = all2.filter((d) => d.cardId === cardId2);
16063
16071
  return mine.filter((d) => d.status !== "active").pop() ?? mine.pop();
16064
16072
  }
16065
- return all.find((d) => d.deliveryId === key) ?? all.find((d) => d.deliveryId.startsWith(key));
16073
+ return all2.find((d) => d.deliveryId === key) ?? all2.find((d) => d.deliveryId.startsWith(key));
16066
16074
  }
16067
16075
  function resumeRefusal(delivery, store) {
16068
16076
  const id = delivery.deliveryId;
@@ -16094,11 +16102,11 @@ function resumeRefusal(delivery, store) {
16094
16102
  );
16095
16103
  }
16096
16104
  if (!worktreeExists(delivery.worktree)) {
16097
- const path80 = delivery.worktree ?? "";
16105
+ const path83 = delivery.worktree ?? "";
16098
16106
  return refusal(
16099
16107
  "deliveryWorktreeGone",
16100
- `delivery ${id}'s worktree ${path80} is gone, so there is nothing to carry on. ${rebuild}`,
16101
- { id, task, path: path80, command: discard }
16108
+ `delivery ${id}'s worktree ${path83} is gone, so there is nothing to carry on. ${rebuild}`,
16109
+ { id, task, path: path83, command: discard }
16102
16110
  );
16103
16111
  }
16104
16112
  if (!branchExists(delivery.branch)) {
@@ -16275,8 +16283,8 @@ function activeDelivery(cardId2) {
16275
16283
  function findDelivery(id) {
16276
16284
  const key = id.trim();
16277
16285
  if (!key) return void 0;
16278
- const all = readStore().deliveries;
16279
- return all.find((d) => d.deliveryId === key) ?? all.find((d) => d.deliveryId.startsWith(key));
16286
+ const all2 = readStore().deliveries;
16287
+ return all2.find((d) => d.deliveryId === key) ?? all2.find((d) => d.deliveryId.startsWith(key));
16280
16288
  }
16281
16289
  function namedDelivery(id) {
16282
16290
  const key = id.trim();
@@ -16292,8 +16300,8 @@ function deliveryFor(req) {
16292
16300
  }
16293
16301
  return req.id === void 0 ? void 0 : activeDelivery(req.id);
16294
16302
  }
16295
- function joinDelivery(store, run, title, step, start, direct) {
16296
- const cardId2 = run.cardId;
16303
+ function joinDelivery(store, run2, title, step, start, direct) {
16304
+ const cardId2 = run2.cardId;
16297
16305
  let delivery = cardId2 === null ? void 0 : activeIn(store, cardId2);
16298
16306
  if (!delivery) {
16299
16307
  delivery = {
@@ -16301,7 +16309,7 @@ function joinDelivery(store, run, title, step, start, direct) {
16301
16309
  cardId: cardId2,
16302
16310
  title,
16303
16311
  status: "active",
16304
- startedAt: run.startedAt,
16312
+ startedAt: run2.startedAt,
16305
16313
  sessions: [],
16306
16314
  // The one read of the card this delivery will ever make for its requirements — or,
16307
16315
  // with no card, the sentence or the plan's words it was handed, which can never move
@@ -16346,21 +16354,21 @@ function joinDelivery(store, run, title, step, start, direct) {
16346
16354
  } else if (step === "implement" && delivery.review?.stopped) {
16347
16355
  delivery.review.stopped = void 0;
16348
16356
  }
16349
- delivery.sessions.push(run.sessionId);
16350
- delivery.steps.push({ step, at: run.startedAt });
16351
- joinFlow(run, delivery);
16357
+ delivery.sessions.push(run2.sessionId);
16358
+ delivery.steps.push({ step, at: run2.startedAt });
16359
+ joinFlow(run2, delivery);
16352
16360
  writeAudit(delivery, store.runs);
16353
16361
  return delivery;
16354
16362
  }
16355
16363
  function adoptDirectCard(sessionId, cardId2) {
16356
16364
  return withStore((store) => {
16357
- const run = store.runs.find((r) => r.sessionId === sessionId && r.status === "running");
16358
- if (!run || run.action !== "implement" || run.cardId !== null) return false;
16359
- const delivery = run.deliveryId ? store.deliveries.find((d) => d.deliveryId === run.deliveryId && d.status === "active") : void 0;
16360
- if (run.deliveryId && (!delivery || delivery.cardId !== null)) return false;
16365
+ const run2 = store.runs.find((r) => r.sessionId === sessionId && r.status === "running");
16366
+ if (!run2 || run2.action !== "implement" || run2.cardId !== null) return false;
16367
+ const delivery = run2.deliveryId ? store.deliveries.find((d) => d.deliveryId === run2.deliveryId && d.status === "active") : void 0;
16368
+ if (run2.deliveryId && (!delivery || delivery.cardId !== null)) return false;
16361
16369
  const idle = "ready";
16362
- run.cardId = cardId2;
16363
- run.priorStatus = idle;
16370
+ run2.cardId = cardId2;
16371
+ run2.priorStatus = idle;
16364
16372
  if (!delivery) return true;
16365
16373
  delivery.cardId = cardId2;
16366
16374
  delivery.priorStatus = idle;
@@ -16368,21 +16376,21 @@ function adoptDirectCard(sessionId, cardId2) {
16368
16376
  return true;
16369
16377
  });
16370
16378
  }
16371
- function joinActive(store, run, step, deliveryId) {
16372
- const delivery = deliveryId ? store.deliveries.find((d) => d.status === "active" && d.deliveryId === deliveryId) : run.cardId === null ? void 0 : activeIn(store, run.cardId);
16379
+ function joinActive(store, run2, step, deliveryId) {
16380
+ const delivery = deliveryId ? store.deliveries.find((d) => d.status === "active" && d.deliveryId === deliveryId) : run2.cardId === null ? void 0 : activeIn(store, run2.cardId);
16373
16381
  if (!delivery) return void 0;
16374
- delivery.sessions.push(run.sessionId);
16375
- delivery.steps.push({ step, at: run.startedAt });
16382
+ delivery.sessions.push(run2.sessionId);
16383
+ delivery.steps.push({ step, at: run2.startedAt });
16376
16384
  if (delivery.review?.stopped) delivery.review.stopped = void 0;
16377
16385
  const now = Date.now();
16378
16386
  for (const answer2 of delivery.answers ?? []) if (!answer2.actedAt) answer2.actedAt = now;
16379
- joinFlow(run, delivery);
16387
+ joinFlow(run2, delivery);
16380
16388
  writeAudit(delivery, store.runs);
16381
16389
  return delivery;
16382
16390
  }
16383
- function joinFlow(run, delivery) {
16384
- run.deliveryId = delivery.deliveryId;
16385
- run.flowId = delivery.deliveryId;
16391
+ function joinFlow(run2, delivery) {
16392
+ run2.deliveryId = delivery.deliveryId;
16393
+ run2.flowId = delivery.deliveryId;
16386
16394
  }
16387
16395
  function endDelivery(deliveryId, status) {
16388
16396
  const ended = withStore((store) => {
@@ -16398,18 +16406,18 @@ function endDelivery(deliveryId, status) {
16398
16406
  }
16399
16407
  return ended;
16400
16408
  }
16401
- async function settleDelivery(run) {
16402
- if (!run.deliveryId) return;
16403
- const before = readStore().deliveries.find((d) => d.deliveryId === run.deliveryId);
16409
+ async function settleDelivery(run2) {
16410
+ if (!run2.deliveryId) return;
16411
+ const before = readStore().deliveries.find((d) => d.deliveryId === run2.deliveryId);
16404
16412
  if (!before) return;
16405
- const built = run.status === "done" && run.action === "implement";
16406
- const passed = run.status === "done" && run.action === "scheduled";
16413
+ const built = run2.status === "done" && run2.action === "implement";
16414
+ const passed = run2.status === "done" && run2.action === "scheduled";
16407
16415
  const commit = (built || passed) && before.status === "active" ? commitDeliveryWork(before) : { ok: true };
16408
16416
  const uncommitted = commit.ok ? void 0 : commit.why;
16409
16417
  const reviewed = !uncommitted && before.commitMode === "manual" && before.cardId !== null && (built || passed) ? snapshotReviewed(before) : void 0;
16410
- const files = (built || passed) && before.commitMode === "files" && !before.scheduled ? filesStop(before) : void 0;
16418
+ const files2 = (built || passed) && before.commitMode === "files" && !before.scheduled ? filesStop(before) : void 0;
16411
16419
  const settled = withStore((store) => {
16412
- const delivery = store.deliveries.find((d) => d.deliveryId === run.deliveryId);
16420
+ const delivery = store.deliveries.find((d) => d.deliveryId === run2.deliveryId);
16413
16421
  if (!delivery) return null;
16414
16422
  if (uncommitted && delivery.status === "active") {
16415
16423
  const review = reviewOf(delivery);
@@ -16417,7 +16425,7 @@ async function settleDelivery(run) {
16417
16425
  releaseLanding(delivery);
16418
16426
  return null;
16419
16427
  }
16420
- const next = nextAfterSession(delivery, run);
16428
+ const next = nextAfterSession(delivery, run2);
16421
16429
  if ("hold" in next) return null;
16422
16430
  if ("finish" in next) {
16423
16431
  if (reviewed) {
@@ -16426,12 +16434,12 @@ async function settleDelivery(run) {
16426
16434
  }
16427
16435
  if (delivery.commitMode === "manual" && !delivery.base) return { end: "finished", complete: delivery.cardId !== null };
16428
16436
  if (delivery.commitMode === "files") {
16429
- if (!files) return { end: "finished", complete: delivery.cardId !== null };
16430
- reviewOf(delivery).stopped = { reason: files.reason, why: files.why, at: Date.now(), paths: files.paths };
16437
+ if (!files2) return { end: "finished", complete: delivery.cardId !== null };
16438
+ reviewOf(delivery).stopped = { reason: files2.reason, why: files2.why, at: Date.now(), paths: files2.paths };
16431
16439
  return null;
16432
16440
  }
16433
16441
  if (wantsLanding(delivery)) {
16434
- queueLanding(delivery, run);
16442
+ queueLanding(delivery, run2);
16435
16443
  return null;
16436
16444
  }
16437
16445
  return { end: "finished" };
@@ -16441,15 +16449,15 @@ async function settleDelivery(run) {
16441
16449
  return null;
16442
16450
  });
16443
16451
  if (settled && "end" in settled) {
16444
- endDelivery(run.deliveryId, settled.end);
16445
- if (settled.complete) await completeCard(before.cardId, run.deliveryId);
16452
+ endDelivery(run2.deliveryId, settled.end);
16453
+ if (settled.complete) await completeCard(before.cardId, run2.deliveryId);
16446
16454
  }
16447
- syncAudit(run.deliveryId, run);
16455
+ syncAudit(run2.deliveryId, run2);
16448
16456
  }
16449
16457
  function deliveryAcceptsAnswers(cardId2) {
16450
16458
  return !!deliveryStateOf(cardId2)?.paused;
16451
16459
  }
16452
- function queueLanding(delivery, run) {
16460
+ function queueLanding(delivery, run2) {
16453
16461
  const landing = delivery.landing ?? { status: "waiting", attempts: 0, at: Date.now() };
16454
16462
  delivery.landing = {
16455
16463
  ...landing,
@@ -16458,7 +16466,7 @@ function queueLanding(delivery, run) {
16458
16466
  reason: void 0,
16459
16467
  checks: [
16460
16468
  ...landing.checks ?? [],
16461
- { name: `${run.action} ${run.sessionId.slice(0, 8)}`, ok: true, at: Date.now() }
16469
+ { name: `${run2.action} ${run2.sessionId.slice(0, 8)}`, ok: true, at: Date.now() }
16462
16470
  ],
16463
16471
  at: Date.now()
16464
16472
  };
@@ -16917,20 +16925,20 @@ function waitForTarget(delivery) {
16917
16925
  });
16918
16926
  return { done: true };
16919
16927
  }
16920
- function startConflict(delivery, target, files) {
16928
+ function startConflict(delivery, target, files2) {
16921
16929
  patchLanding(delivery.deliveryId, (landing) => {
16922
16930
  landing.onto = target;
16923
- landing.conflictFiles = files;
16924
- landing.why = `resolving a conflict with ${delivery.targetBranch} in ${names2(files)}`;
16931
+ landing.conflictFiles = files2;
16932
+ landing.why = `resolving a conflict with ${delivery.targetBranch} in ${names2(files2)}`;
16925
16933
  landing.reason = void 0;
16926
16934
  });
16927
16935
  return { start: conflictRun(delivery) };
16928
16936
  }
16929
16937
  function reopenConflict(delivery, dir) {
16930
- const files = conflictedPaths(dir);
16938
+ const files2 = conflictedPaths(dir);
16931
16939
  patchLanding(delivery.deliveryId, (landing) => {
16932
16940
  landing.conflictAt = void 0;
16933
- if (files.length) landing.conflictFiles = files;
16941
+ if (files2.length) landing.conflictFiles = files2;
16934
16942
  if (landing.fitAt) landing.fitAt = Date.now();
16935
16943
  });
16936
16944
  return { start: conflictRun(delivery) };
@@ -16939,7 +16947,8 @@ async function finishConflict(delivery, dir) {
16939
16947
  const left = conflictedPaths(dir);
16940
16948
  const done2 = left.length ? { ok: false, why: `${names2(left)} ${are(left.length)} still conflicted` } : continueRebase(dir);
16941
16949
  if (done2.ok && !rebaseInProgress(dir)) {
16942
- return await afterRebase(delivery, delivery.landing?.onto ?? delivery.base, "conflict");
16950
+ const after2 = await afterRebase(delivery, delivery.landing?.onto ?? delivery.base, "conflict");
16951
+ return after2.done ? after2 : await landNow(delivery);
16943
16952
  }
16944
16953
  return failAttempt(
16945
16954
  delivery,
@@ -16962,7 +16971,12 @@ async function finishFit(delivery, dir) {
16962
16971
  landing.conflictFails = void 0;
16963
16972
  landing.why = void 0;
16964
16973
  });
16965
- return {};
16974
+ return await landNow(delivery);
16975
+ }
16976
+ async function landNow(delivery) {
16977
+ const live = readStore().deliveries.find((d) => d.deliveryId === delivery.deliveryId);
16978
+ if (!live || live.status !== "active") return { done: true };
16979
+ return await landStep(live);
16966
16980
  }
16967
16981
  function failAttempt(delivery, why, left = []) {
16968
16982
  const fails = (delivery.landing?.conflictFails ?? 0) + 1;
@@ -17034,9 +17048,9 @@ async function move(delivery, tip, target) {
17034
17048
  return { done: true };
17035
17049
  }
17036
17050
  function landWait(wait, where) {
17037
- const files = wait.files;
17051
+ const files2 = wait.files;
17038
17052
  const at = sameDir(where, REPO_ROOT) ? "your checkout" : `\`${rel(where)}\``;
17039
- return wait.kind === "untracked" ? `${some2(files)} in ${at} ${are(files.length)} not in git, and landing would write over ${them(files.length)} \u2014 move or delete ${them(files.length)}` : `your changes to ${some2(files)} in ${at} would be overwritten by landing \u2014 commit or stash ${them(files.length)}`;
17053
+ return wait.kind === "untracked" ? `${some2(files2)} in ${at} ${are(files2.length)} not in git, and landing would write over ${them(files2.length)} \u2014 move or delete ${them(files2.length)}` : `your changes to ${some2(files2)} in ${at} would be overwritten by landing \u2014 commit or stash ${them(files2.length)}`;
17040
17054
  }
17041
17055
  async function finish(delivery, landed) {
17042
17056
  patchLanding(delivery.deliveryId, (landing) => {
@@ -17059,11 +17073,11 @@ function alreadyOnTarget(delivery) {
17059
17073
  const onto = branchTip(delivery.targetBranch);
17060
17074
  if (!onto) return null;
17061
17075
  const dir = worktreeDir(delivery.worktree);
17062
- const files = changedPaths(delivery.base, delivery.branch, dir);
17063
- if (!files?.length) return null;
17076
+ const files2 = changedPaths(delivery.base, delivery.branch, dir);
17077
+ if (!files2?.length) return null;
17064
17078
  const patch2 = branchPatch(delivery.base, delivery.branch, dir);
17065
17079
  if (!patch2 || !reverseApplies(patch2, onto, dir)) return null;
17066
- return { commit: lastCommitTouching(onto, files, dir) ?? void 0, onto };
17080
+ return { commit: lastCommitTouching(onto, files2, dir) ?? void 0, onto };
17067
17081
  }
17068
17082
  async function settleAlreadyLanded(delivery) {
17069
17083
  const found2 = alreadyOnTarget(delivery);
@@ -17120,8 +17134,8 @@ var init_landing = __esm({
17120
17134
  init_store();
17121
17135
  init_worktree();
17122
17136
  MAX_NAMED2 = 5;
17123
- names2 = (files) => `${files.slice(0, MAX_NAMED2).join(", ")}${files.length > MAX_NAMED2 ? `, and ${files.length - MAX_NAMED2} more` : ""}`;
17124
- some2 = (files) => files.length === 1 ? `\`${files[0]}\`` : `${files.length} files`;
17137
+ names2 = (files2) => `${files2.slice(0, MAX_NAMED2).join(", ")}${files2.length > MAX_NAMED2 ? `, and ${files2.length - MAX_NAMED2} more` : ""}`;
17138
+ some2 = (files2) => files2.length === 1 ? `\`${files2[0]}\`` : `${files2.length} files`;
17125
17139
  are = (n) => n === 1 ? "is" : "are";
17126
17140
  them = (n) => n === 1 ? "it" : "them";
17127
17141
  sayWhy = (deliveryId, why, wait, reason) => patchLanding(deliveryId, (landing) => {
@@ -17181,14 +17195,14 @@ function returnRunPictures(sessionId, box2) {
17181
17195
  } catch {
17182
17196
  }
17183
17197
  }
17184
- function moveRunPictures(from, to, files = []) {
17185
- if (files.length === 0) return [];
17198
+ function moveRunPictures(from, to, files2 = []) {
17199
+ if (files2.length === 0) return [];
17186
17200
  try {
17187
17201
  fs42.renameSync(pictureBox(from), pictureBox(to));
17188
17202
  } catch {
17189
- return files;
17203
+ return files2;
17190
17204
  }
17191
- return files.map((file) => path43.join(pictureBox(to), path43.basename(file)));
17205
+ return files2.map((file) => path43.join(pictureBox(to), path43.basename(file)));
17192
17206
  }
17193
17207
  var pictureName, boxId, freeBox, pictureBox;
17194
17208
  var init_pictures = __esm({
@@ -17248,8 +17262,8 @@ function globalMemories() {
17248
17262
  return out;
17249
17263
  }
17250
17264
  function globalMemoryBlock(agent) {
17251
- const all = globalMemories();
17252
- const kept = agent.memory.map((name) => all.find((m) => m.name === name)).filter((m) => !!m);
17265
+ const all2 = globalMemories();
17266
+ const kept = agent.memory.map((name) => all2.find((m) => m.name === name)).filter((m) => !!m);
17253
17267
  if (!kept.length) return "";
17254
17268
  return [
17255
17269
  "Global memories you share with other agents \u2014 open one only when the work needs it, and",
@@ -17455,7 +17469,7 @@ var init_write_agent = __esm({
17455
17469
  var writing_default;
17456
17470
  var init_writing = __esm({
17457
17471
  "../cli/src/guide/writing.md"() {
17458
- writing_default = '# Write a card\n\nA card has a **human half** for review and a folded **agent half** for implementation.\nEvery flow writes a card in this order:\n\n```text\n<one short paragraph: the observable result and the current behavior or constraint it changes.>\n\n## Worth noting <- answered material decisions; keep empty when none\n- **<question the decision settles>**: <answer>\n\n## Worth noting after implementation\n- **<question the decision settles>**: <answer>\n <- answered material decisions building turned up; written\n by the build, omit when empty. Never approved delivery scope\n\n## By `<name>` agent <- every preview agent\'s section\n\n<!-- agent --> <- boundary\n\n## Today\n## Scope <- requirements, not rationale\n## Todo <- one build step per checkbox\n## Decided by the agent\n- **<question the decision settles>**: <answer>\n### Overruled by the user <- always last\n```\n\n## `Worth noting`\n\n- **Classify first**: follow `akb guide update-questions`; this section contains only answers\n classified as `Worth noting`.\n- **Keep it glanceable**: one entry is one reviewer decision, written as one sentence that\n states the chosen behavior and its main cost. Merge entries governed by one policy. One\n to three independent entries is normal.\n- **Cut the story**: omit chronology, evidence trails, exhaustive consequences, and the\n rejected option unless it is necessary to understand the main cost.\n\n## `Worth noting after implementation`\n\n- **Classify first**: follow `akb guide update-questions`; this section contains only answers\n classified as `Worth noting after implementation`.\n- **Keep it non-blocking**: this section never changes or blocks a delivery already in\n flight.\n- **Use the same compact format**: one reviewer decision and its main cost in one sentence.\n- **Exclude work logs**: never put diagnostics, run history, check results, suggestions,\n or unresolved questions here.\n- **Never approve a deviation here**: work that contradicts the approved requirements must\n be fixed or sent to the user by the build.\n\n## Agent sections\n\n- **Owner\'s rules first**: before rewriting, removing, or overruling content a preview agent\n owns, read that agent\'s rules with `akb raw agent-file <agent> AGENT.md`, and the\n references they point to for the affected part with the same command and their path. When\n they cannot be read, surface the gap before changing that content.\n\n## `Today`\n\n- **Omit by default**: write it, heading included, only when the build depends on findings\n the code does not make obvious; a small change to a few simple files needs none.\n\n## `Todo`\n\n- **Build steps only**: finish planning on the card; never defer it to a todo.\n- **Preserve completed work**: never edit, delete, or untick a checked todo; append a todo to\n reverse it.\n\n## `Decided by the agent`\n\n- **Only what changes the build**: include answered decisions the builder would otherwise\n get wrong and neither `Worth noting` section holds; omit a finding that needs no action.\n- **Preserve superseded calls**: move agent or review decisions the user reverses to\n `Overruled by the user`; delete one the card leaves the builder no room to repeat.\n\n## General writing requirements\n\n- **Keep section titles fixed**: do not rename or translate an existing `##` or `###`\n section title, and leave empty scaffold sections in place.\n- **Edit only the body**: use board commands for every frontmatter field (`akb guide board`).\n- **Name the source in frontmatter**: pass each plan path, card `#id`, or URL the card came\n from as `--source` to `akb raw create`, or `--add-source` to `akb raw update`; never write\n a `## Source` section.\n- **Write for a fresh reader**: use professional, comprehensible language with no assumed\n conversation context.\n- **Keep human half self-contained**: nothing above `<!-- agent -->` may rely on the folded\n agent half.\n- **Keep bullets atomic**: use `- **<short title>**: <one clear sentence>` for one rule per\n bullet.\n- **Specify behavior**: omit planning notes and unnecessary coding details.\n- **Supported embeds**: only valid `<Asset>`, `<Compare>` and `<Storyboard>` markers alone in a\n paragraph embed content; other HTML or JSX is not allowed. Inline or fenced examples remain text.\n- **Compare**: to show two images or two videos against each other, such as a source and its\n clone, write `<Compare a="<src>" b="<src>" label="..." a-label="..." b-label="..." />`;\n never render a side-by-side file for it. For videos, add `shots="<src>"`: a JSON list of\n `{ "id": "S1", "start": 0, "end": 2.25, "name": "..." }` covering video A in order, or a\n storyboard of A. B may be a storyboard instead of a video; it then shows its shot with the\n id A is on, beside that shot of a storyboard `shots`.\n- **Asset label and alt**: `label` is a tag of a few words; an image\'s caption goes in its\n optional `alt="..."`, its shipped alt text, which cannot contain `<`, `>` or its own quote mark.\n- **Asset variants**: versions of the same content, such as languages or devices, are one tag\n with `variants=\'{"<label>": "<src>", ...}\'`, `src` being the one shown first; alternatives the\n user picks between stay separate tags.\n- **Say each fact once**: a section holds only what the card does not already say; leave it\n empty rather than restate the opening paragraph or another section. Cut only the repeated\n part of a line: a name, value or case stated nowhere else stays.\n\n- **Validate after edits**: run `akb raw validate <id>` for every card you wrote. Fix each\n reported file and line before finishing; validation does not rewrite the card for you.\n';
17472
+ writing_default = '# Write a card\n\nA card has a **human half** for review and a folded **agent half** for implementation.\nEvery flow writes a card in this order:\n\n```text\n<one short paragraph: the observable result and the current behavior or constraint it changes.>\n\n## Worth noting <- answered material decisions; keep empty when none\n- **<question the decision settles>**: <answer>\n\n## Worth noting after implementation\n- **<question the decision settles>**: <answer>\n <- answered material decisions building turned up; written\n by the build, omit when empty. Never approved delivery scope\n\n## By `<name>` agent <- every preview agent\'s section\n\n<!-- agent --> <- boundary\n\n## Today\n## Scope <- requirements, not rationale\n## Todo <- one build step per checkbox\n## Decided by the agent\n- **<question the decision settles>**: <answer>\n### Overruled by the user <- always last\n```\n\n## `Worth noting`\n\n- **Classify first**: follow `akb guide update-questions`; this section contains only answers\n classified as `Worth noting`.\n- **Keep it glanceable**: one entry is one reviewer decision, written as one sentence that\n states the chosen behavior and its main cost. Merge entries governed by one policy. One\n to three independent entries is normal.\n- **Cut the story**: omit chronology, evidence trails, exhaustive consequences, and the\n rejected option unless it is necessary to understand the main cost.\n\n## `Worth noting after implementation`\n\n- **Classify first**: follow `akb guide update-questions`; this section contains only answers\n classified as `Worth noting after implementation`.\n- **Keep it non-blocking**: this section never changes or blocks a delivery already in\n flight.\n- **Use the same compact format**: one reviewer decision and its main cost in one sentence.\n- **Exclude work logs**: never put diagnostics, run history, check results, suggestions,\n or unresolved questions here.\n- **Never approve a deviation here**: work that contradicts the approved requirements must\n be fixed or sent to the user by the build.\n\n## Agent sections\n\n- **Owner\'s rules first**: before rewriting, removing, or overruling content a preview agent\n owns, read that agent\'s rules with `akb raw agent-file <agent> AGENT.md`, and the\n references they point to for the affected part with the same command and their path. When\n they cannot be read, surface the gap before changing that content.\n\n## `Today`\n\n- **Omit by default**: write it, heading included, only when the build depends on findings\n the code does not make obvious; a small change to a few simple files needs none.\n\n## `Todo`\n\n- **Build steps only**: finish planning on the card; never defer it to a todo.\n- **Preserve completed work**: never edit, delete, or untick a checked todo; append a todo to\n reverse it.\n\n## `Decided by the agent`\n\n- **Only what changes the build**: include answered decisions the builder would otherwise\n get wrong and neither `Worth noting` section holds; omit a finding that needs no action.\n- **Preserve superseded calls**: move agent or review decisions the user reverses to\n `Overruled by the user`; delete one the card leaves the builder no room to repeat.\n\n## General writing requirements\n\n- **Keep section titles fixed**: write every `##` and `###` section title in English, never\n rename or translate an existing one, and leave empty scaffold sections in place.\n- **Edit only the body**: use board commands for every frontmatter field (`akb guide board`).\n- **Name the source in frontmatter**: pass each plan path, card `#id`, or URL the card came\n from as `--source` to `akb raw create`, or `--add-source` to `akb raw update`; never write\n a `## Source` section.\n- **Write for a fresh reader**: use professional, comprehensible language with no assumed\n conversation context.\n- **Keep human half self-contained**: nothing above `<!-- agent -->` may rely on the folded\n agent half.\n- **Keep bullets atomic**: use `- **<short title>**: <one clear sentence>` for one rule per\n bullet.\n- **Specify behavior**: omit planning notes and unnecessary coding details.\n- **Supported embeds**: only valid `<Asset>`, `<Compare>` and `<Storyboard>` markers alone in a\n paragraph embed content; other HTML or JSX is not allowed. Inline or fenced examples remain text.\n- **Compare**: to show two images or two videos against each other, such as a source and its\n clone, write `<Compare a="<src>" b="<src>" label="..." a-label="..." b-label="..." />`;\n never render a side-by-side file for it. For videos, add `shots="<src>"`: a JSON list of\n `{ "id": "S1", "start": 0, "end": 2.25, "name": "..." }` covering video A in order, or a\n storyboard of A. B may be a storyboard instead of a video; it then shows its shot with the\n id A is on, beside that shot of a storyboard `shots`.\n- **Asset label and alt**: `label` is a tag of a few words; an image\'s caption goes in its\n optional `alt="..."`, its shipped alt text, which cannot contain `<`, `>` or its own quote mark.\n- **Asset variants**: versions of the same content, such as languages or devices, are one tag\n with `variants=\'{"<label>": "<src>", ...}\'`, `src` being the one shown first; alternatives the\n user picks between stay separate tags.\n- **Say each fact once**: a section holds only what the card does not already say; leave it\n empty rather than restate the opening paragraph or another section. Cut only the repeated\n part of a line: a name, value or case stated nowhere else stays.\n\n- **Validate after edits**: run `akb raw validate <id>` for every card you wrote. Fix each\n reported file and line before finishing; validation does not rewrite the card for you.\n';
17459
17473
  }
17460
17474
  });
17461
17475
 
@@ -17633,7 +17647,6 @@ function contractRepairPrompt(req, errors) {
17633
17647
  "The task ended with invalid card formatting. Fix only the errors below in the listed files. Preserve planned behavior and completed work; do not repeat the task or request other agents.",
17634
17648
  errors,
17635
17649
  `Run \`${command} raw validate <id>\` for every affected card and fix every error before finishing.`,
17636
- languageNote(),
17637
17650
  ruleBlock(req, frozenRules(req))
17638
17651
  ].filter(Boolean).join("\n\n"));
17639
17652
  }
@@ -17647,7 +17660,7 @@ function buildAsk(rawReq, notes = []) {
17647
17660
  const req = withWorkflow(rawReq);
17648
17661
  const command = DELIVERY_FLOWS.has(req.action) ? boardCommandFor(req.id) : boardCommand();
17649
17662
  const ask2 = [actionPrompt(req, command, notes), pictureNote(req), commandNote(command)].filter(Boolean).join(" ");
17650
- return realPaths(boardText([ask2, languageNote(), roster(req)].filter(Boolean).join("\n\n")), req);
17663
+ return realPaths(boardText([ask2, roster(req)].filter(Boolean).join("\n\n")), req);
17651
17664
  }
17652
17665
  function cardFileOf(id) {
17653
17666
  const found2 = id === void 0 ? null : locate(id);
@@ -17733,10 +17746,10 @@ function settingsBlock(req) {
17733
17746
  return role ? roleSettingsBlock(role) : "";
17734
17747
  }
17735
17748
  function pictureNote(req) {
17736
- const files = req.pictures ?? [];
17737
- if (!files.length || agentImages(agentForRun(req), req.runtime)?.as !== "message") return "";
17738
- const one = files.length === 1;
17739
- return `${one ? "A picture came" : `${files.length} pictures came`} with this. Read ${one ? "it" : "them"} first: ${files.join(", ")}.`;
17749
+ const files2 = req.pictures ?? [];
17750
+ if (!files2.length || agentImages(agentForRun(req), req.runtime)?.as !== "message") return "";
17751
+ const one = files2.length === 1;
17752
+ return `${one ? "A picture came" : `${files2.length} pictures came`} with this. Read ${one ? "it" : "them"} first: ${files2.join(", ")}.`;
17740
17753
  }
17741
17754
  function frozenRules(req) {
17742
17755
  return deliveryFor(req)?.rules;
@@ -17751,8 +17764,8 @@ function shippedLines(cardId2) {
17751
17764
  const commit = delivery?.landing?.commit;
17752
17765
  if (commit) return [`\`git show ${commit.slice(0, 12)}\` \u2014 the change that landed`];
17753
17766
  if (delivery?.commitMode === "files") {
17754
- const files = recordedOutputs(delivery);
17755
- if (files.length) return ["the files it delivered:", ...files.map((file) => ` ${file}`)];
17767
+ const files2 = recordedOutputs(delivery);
17768
+ if (files2.length) return ["the files it delivered:", ...files2.map((file) => ` ${file}`)];
17756
17769
  }
17757
17770
  return [`nothing on record \u2014 \`git log --grep "(#${cardId2})"\`, or check the card against the project as it stands`];
17758
17771
  }
@@ -18077,7 +18090,6 @@ var init_prompts = __esm({
18077
18090
  init_outputs();
18078
18091
  init_archive();
18079
18092
  init_flows();
18080
- init_language();
18081
18093
  init_resolve();
18082
18094
  init_runner();
18083
18095
  init_workflows();
@@ -18109,7 +18121,7 @@ var init_prompts = __esm({
18109
18121
  `The session you were continuing is gone, and this one holds nothing of it.`,
18110
18122
  `Do the task below from the top, checking each step's precondition before you do it \u2014 work an earlier run already finished is done, so don't repeat it.`
18111
18123
  ].join(" ");
18112
- restartable = (run) => (run.cardId !== null || !!run.deliveryId) && RESTARTABLE.has(run.action);
18124
+ restartable = (run2) => (run2.cardId !== null || !!run2.deliveryId) && RESTARTABLE.has(run2.action);
18113
18125
  NO_IMPLEMENT = `(Unless the request explicitly asks for implementation, don't implement it; revising the card's drafts is not implementing.)`;
18114
18126
  BOARD_FOLDER = "<board folder>";
18115
18127
  CARD_FILE = "<card file>";
@@ -18367,9 +18379,9 @@ function reap(runs, reaped = [], restore = []) {
18367
18379
  }
18368
18380
  return changed;
18369
18381
  }
18370
- function stampDuration(run, endedAt) {
18382
+ function stampDuration(run2, endedAt) {
18371
18383
  try {
18372
- fs46.appendFileSync(run.logPath, durationLine(endedAt - run.startedAt));
18384
+ fs46.appendFileSync(run2.logPath, durationLine(endedAt - run2.startedAt));
18373
18385
  } catch {
18374
18386
  }
18375
18387
  }
@@ -18390,19 +18402,19 @@ function cardNow(cardId2) {
18390
18402
  return null;
18391
18403
  }
18392
18404
  }
18393
- function claimCard(run) {
18394
- const wanted = run.cardId !== null ? RUN_STATUS[run.action] : void 0;
18395
- if (run.cardId === null || !wanted) return void 0;
18396
- const delivery = run.deliveryId ? activeDelivery(run.cardId) : void 0;
18397
- run.priorStatus = (delivery && delivery.deliveryId === run.deliveryId ? delivery.priorStatus : void 0) ?? cardNow(run.cardId)?.status ?? "todo";
18398
- return { cardId: run.cardId, status: wanted };
18405
+ function claimCard(run2) {
18406
+ const wanted = run2.cardId !== null ? RUN_STATUS[run2.action] : void 0;
18407
+ if (run2.cardId === null || !wanted) return void 0;
18408
+ const delivery = run2.deliveryId ? activeDelivery(run2.cardId) : void 0;
18409
+ run2.priorStatus = (delivery && delivery.deliveryId === run2.deliveryId ? delivery.priorStatus : void 0) ?? cardNow(run2.cardId)?.status ?? "todo";
18410
+ return { cardId: run2.cardId, status: wanted };
18399
18411
  }
18400
- async function restoreCardStatus(run) {
18401
- if (run.cardId === null || !RUN_STATUS[run.action]) return;
18402
- if (run.deliveryId && activeDelivery(run.cardId)?.deliveryId === run.deliveryId) return;
18403
- const card = cardNow(run.cardId);
18412
+ async function restoreCardStatus(run2) {
18413
+ if (run2.cardId === null || !RUN_STATUS[run2.action]) return;
18414
+ if (run2.deliveryId && activeDelivery(run2.cardId)?.deliveryId === run2.deliveryId) return;
18415
+ const card = cardNow(run2.cardId);
18404
18416
  if (!card) return;
18405
- await setCardStatus(run.cardId, card.questions > 0 ? "todo" : run.priorStatus ?? "todo");
18417
+ await setCardStatus(run2.cardId, card.questions > 0 ? "todo" : run2.priorStatus ?? "todo");
18406
18418
  }
18407
18419
  async function releaseCard(delivery) {
18408
18420
  if (delivery.cardId === null) return;
@@ -18410,46 +18422,46 @@ async function releaseCard(delivery) {
18410
18422
  if (card?.status !== "implementing") return;
18411
18423
  await setCardStatus(delivery.cardId, card.questions > 0 ? "todo" : delivery.priorStatus ?? "ready");
18412
18424
  }
18413
- function recordPrune(run) {
18414
- if (run.action !== "prune-memory" || run.status !== "done") return;
18425
+ function recordPrune(run2) {
18426
+ if (run2.action !== "prune-memory" || run2.status !== "done") return;
18415
18427
  try {
18416
18428
  stampMemoryPrune();
18417
18429
  } catch {
18418
18430
  }
18419
18431
  }
18420
- function recordMemoryReview(run) {
18421
- if (run.action !== "review-memory" || run.status !== "done") return;
18432
+ function recordMemoryReview(run2) {
18433
+ if (run2.action !== "review-memory" || run2.status !== "done") return;
18422
18434
  try {
18423
- stampMemoryReview(new Date(run.startedAt));
18435
+ stampMemoryReview(new Date(run2.startedAt));
18424
18436
  } catch {
18425
18437
  }
18426
18438
  }
18427
- function recordReflection(run) {
18428
- if (run.action !== "reflect" || run.status !== "done" || !run.cards?.length) return;
18439
+ function recordReflection(run2) {
18440
+ if (run2.action !== "reflect" || run2.status !== "done" || !run2.cards?.length) return;
18429
18441
  try {
18430
- dropReflected(run.cards);
18442
+ dropReflected(run2.cards);
18431
18443
  } catch {
18432
18444
  }
18433
18445
  }
18434
- function recordDismissalReview(run) {
18435
- if (run.action !== "review-dismissals" || run.status !== "done") return;
18446
+ function recordDismissalReview(run2) {
18447
+ if (run2.action !== "review-dismissals" || run2.status !== "done") return;
18436
18448
  try {
18437
- stampDismissalReview(new Date(run.startedAt));
18449
+ stampDismissalReview(new Date(run2.startedAt));
18438
18450
  } catch {
18439
18451
  }
18440
18452
  }
18441
- function recordScheduledRun(run) {
18442
- if (run.action !== "scheduled" || run.status !== "done" || !run.workflow || !run.specAgent) return;
18453
+ function recordScheduledRun(run2) {
18454
+ if (run2.action !== "scheduled" || run2.status !== "done" || !run2.workflow || !run2.specAgent) return;
18443
18455
  try {
18444
- const began = (run.deliveryId ? findDelivery(run.deliveryId)?.startedAt : void 0) ?? run.startedAt;
18445
- stampScheduledRun(run.workflow, run.specAgent, new Date(began));
18456
+ const began = (run2.deliveryId ? findDelivery(run2.deliveryId)?.startedAt : void 0) ?? run2.startedAt;
18457
+ stampScheduledRun(run2.workflow, run2.specAgent, new Date(began));
18446
18458
  } catch {
18447
18459
  }
18448
18460
  }
18449
- function recordProjectDescription(run) {
18450
- if (run.action !== "describe-project" || run.status !== "done") return;
18461
+ function recordProjectDescription(run2) {
18462
+ if (run2.action !== "describe-project" || run2.status !== "done") return;
18451
18463
  try {
18452
- stampProjectDescription(new Date(run.startedAt));
18464
+ stampProjectDescription(new Date(run2.startedAt));
18453
18465
  } catch {
18454
18466
  }
18455
18467
  }
@@ -18457,8 +18469,8 @@ function resumeIdOf(r) {
18457
18469
  if (adoptsSessionId(r.harness) && !carriesSession(r)) return r.sessionId;
18458
18470
  return r.resumeId;
18459
18471
  }
18460
- function resumeSessionId(run) {
18461
- return resumesUnder(run.harness) && !neverStarted(run) ? resumeIdOf(run) : void 0;
18472
+ function resumeSessionId(run2) {
18473
+ return resumesUnder(run2.harness) && !neverStarted(run2) ? resumeIdOf(run2) : void 0;
18462
18474
  }
18463
18475
  function endedDeliveryRefusal(r, refusals) {
18464
18476
  if (!r.deliveryId || !canPickUp(r)) return void 0;
@@ -18532,7 +18544,7 @@ function leftBoardOnLanding(id) {
18532
18544
  }
18533
18545
  function goneCards(runs) {
18534
18546
  const asking = /* @__PURE__ */ new Set();
18535
- for (const run of runs) if (run.cardId !== null && canPickUp(run)) asking.add(run.cardId);
18547
+ for (const run2 of runs) if (run2.cardId !== null && canPickUp(run2)) asking.add(run2.cardId);
18536
18548
  if (asking.size === 0) return asking;
18537
18549
  const onBoard3 = boardCardIds();
18538
18550
  const gone = /* @__PURE__ */ new Set();
@@ -18542,12 +18554,12 @@ function goneCards(runs) {
18542
18554
  async function listRuns() {
18543
18555
  const reaped = [];
18544
18556
  const restore = [];
18545
- const runs = withRuns((all) => {
18546
- reap(all, reaped, restore);
18547
- return all.map((r) => ({ ...r }));
18557
+ const runs = withRuns((all2) => {
18558
+ reap(all2, reaped, restore);
18559
+ return all2.map((r) => ({ ...r }));
18548
18560
  });
18549
- for (const run of restore) await restoreCardStatus(run);
18550
- for (const run of reaped) await settleDelivery(run);
18561
+ for (const run2 of restore) await restoreCardStatus(run2);
18562
+ for (const run2 of reaped) await settleDelivery(run2);
18551
18563
  for (const sub of runs) {
18552
18564
  if (sub.action !== "sub" || sub.status !== "running" || !sub.parentId) continue;
18553
18565
  if (!runs.some((r) => r.sessionId === sub.parentId && r.status === "running")) await stopRun(sub.sessionId);
@@ -18584,17 +18596,17 @@ function findRun(runs, id) {
18584
18596
  if (hits.length === 1) return hits[0];
18585
18597
  return hits.length > 1 ? null : void 0;
18586
18598
  }
18587
- async function getRun(id, bytes) {
18599
+ async function getRun(id, bytes3) {
18588
18600
  const restore = [];
18589
- const runs = withRuns((all) => {
18590
- reap(all, [], restore);
18591
- return all.map((r) => ({ ...r }));
18601
+ const runs = withRuns((all2) => {
18602
+ reap(all2, [], restore);
18603
+ return all2.map((r) => ({ ...r }));
18592
18604
  });
18593
- for (const run of restore) await restoreCardStatus(run);
18605
+ for (const run2 of restore) await restoreCardStatus(run2);
18594
18606
  const found2 = findRun(runs, id);
18595
18607
  if (!found2) return null;
18596
18608
  const view = toView(found2, goneCards([found2]));
18597
- const raw = readLogTail(found2.logPath, bytes) ?? "";
18609
+ const raw = readLogTail(found2.logPath, bytes3) ?? "";
18598
18610
  const { tail: tail2, result, durationMs, costUsd, model, usage: usage3 } = splitLog(raw);
18599
18611
  view.tail = tail2;
18600
18612
  if (result && found2.status !== "running" && !view.result) view.result = result;
@@ -18689,8 +18701,8 @@ function openRun(req, prompt, notes = [], sessionId = randomUUID6(), said3) {
18689
18701
  const cardless = !!direct;
18690
18702
  let start;
18691
18703
  if (pass || req.action === "implement" && (cardless || cardId2 !== null && !activeDelivery(cardId2))) {
18692
- const files = direct?.scheduled ? !!workflowById(direct.scheduled.workflow)?.needsArtifact : false;
18693
- const prepared = prepareDelivery(cardId2, req.commitMode, files);
18704
+ const files2 = direct?.scheduled ? !!workflowById(direct.scheduled.workflow)?.needsArtifact : false;
18705
+ const prepared = prepareDelivery(cardId2, req.commitMode, files2);
18694
18706
  if ("error" in prepared) return prepared;
18695
18707
  start = prepared.start;
18696
18708
  }
@@ -18783,15 +18795,15 @@ function openRun(req, prompt, notes = [], sessionId = randomUUID6(), said3) {
18783
18795
  function planningSession(cardId2, cwd = REPO_ROOT) {
18784
18796
  const here = (dir) => !dir || path48.resolve(dir) === cwd;
18785
18797
  const ended = readRuns().filter((r) => r.status === "done").sort((a, b) => b.startedAt - a.startedAt);
18786
- const of = (run2, fork) => {
18787
- if (!here(run2.cwd)) return { none: "elsewhere" };
18788
- const resumeId = resumeSessionId(run2);
18789
- return resumeId ? { session: { harness: run2.harness, resumeId }, fork } : { none: "unrecorded" };
18798
+ const of = (run3, fork) => {
18799
+ if (!here(run3.cwd)) return { none: "elsewhere" };
18800
+ const resumeId = resumeSessionId(run3);
18801
+ return resumeId ? { session: { harness: run3.harness, resumeId }, fork } : { none: "unrecorded" };
18790
18802
  };
18791
18803
  const planned = ended.find((r) => r.cardId === cardId2 && PLANNING.has(r.action));
18792
18804
  if (planned) return of(planned, false);
18793
- const run = ended.find((r) => (r.action === "create" || r.action === "setup") && r.createdCardIds?.includes(cardId2));
18794
- const created = run ? of(run, true) : void 0;
18805
+ const run2 = ended.find((r) => (r.action === "create" || r.action === "setup") && r.createdCardIds?.includes(cardId2));
18806
+ const created = run2 ? of(run2, true) : void 0;
18795
18807
  return created ?? { none: "unrecorded" };
18796
18808
  }
18797
18809
  function carriedSession(req) {
@@ -18822,8 +18834,8 @@ function carriedSession(req) {
18822
18834
  };
18823
18835
  }
18824
18836
  function planningRun(sessionId) {
18825
- const run = peekRun(sessionId);
18826
- return run?.status === "running" && PLANNING.has(run.action) ? run : void 0;
18837
+ const run2 = peekRun(sessionId);
18838
+ return run2?.status === "running" && PLANNING.has(run2.action) ? run2 : void 0;
18827
18839
  }
18828
18840
  function requestOf(record) {
18829
18841
  const id = record.cardId ?? void 0;
@@ -18849,11 +18861,11 @@ function requestOf(record) {
18849
18861
  }
18850
18862
  async function openResume(id) {
18851
18863
  const restore = [];
18852
- const runs = withRuns((all) => {
18853
- reap(all, [], restore);
18854
- return all.map((r) => ({ ...r }));
18864
+ const runs = withRuns((all2) => {
18865
+ reap(all2, [], restore);
18866
+ return all2.map((r) => ({ ...r }));
18855
18867
  });
18856
- for (const run of restore) await restoreCardStatus(run);
18868
+ for (const run2 of restore) await restoreCardStatus(run2);
18857
18869
  const prev = findRun(runs, id);
18858
18870
  if (prev === null) return ambiguousRun(id);
18859
18871
  if (!prev) return unknownRun(id);
@@ -18952,8 +18964,8 @@ async function resumeHeld(prev, plan, sessionId, resuming, fresh) {
18952
18964
  flowId: prev.flowId
18953
18965
  };
18954
18966
  const out = withCreationLock(() => withStore((store) => {
18955
- const all = store.runs;
18956
- const latest = all.find((r) => r.sessionId === prev.sessionId);
18967
+ const all2 = store.runs;
18968
+ const latest = all2.find((r) => r.sessionId === prev.sessionId);
18957
18969
  if (!latest) return refusal("runContinued", "that run has already been continued");
18958
18970
  if (latest.discardedCards?.some((c) => c.pending || locate(c.id))) {
18959
18971
  return refusal("discardUnfinished", "A card discard did not finish. Retry discarding it before resuming creation.");
@@ -18968,11 +18980,11 @@ async function resumeHeld(prev, plan, sessionId, resuming, fresh) {
18968
18980
  repair.errors = repair.errors.split("\n").filter((line2) => !record.discardedCards.some((c) => line2.includes(c.path) || new RegExp(`Task #${c.id}(?![0-9])`).test(line2))).join("\n");
18969
18981
  if (!repair.cardIds.length) record.formatRepair = void 0;
18970
18982
  }
18971
- const heldCreation = all.find((r) => runIsLive(r) && (r.cardId !== null && (latest.createdCardIds ?? []).includes(r.cardId) || (r.createdCardIds ?? []).some((id) => (latest.createdCardIds ?? []).includes(id))));
18983
+ const heldCreation = all2.find((r) => runIsLive(r) && (r.cardId !== null && (latest.createdCardIds ?? []).includes(r.cardId) || (r.createdCardIds ?? []).some((id) => (latest.createdCardIds ?? []).includes(id))));
18972
18984
  if (heldCreation) return refusal("creationHeld", "A card from this creation is already being worked on. Wait for that run to finish.");
18973
- const locked = lockedBy(all, prev.action, prev.cardId, prev.discard);
18985
+ const locked = lockedBy(all2, prev.action, prev.cardId, prev.discard);
18974
18986
  if (locked) return locked;
18975
- all.push(record);
18987
+ all2.push(record);
18976
18988
  const delivery = store.deliveries.find((d) => d.deliveryId === prev.deliveryId && d.status === "active");
18977
18989
  if (delivery) {
18978
18990
  delivery.sessions.push(record.sessionId);
@@ -18980,8 +18992,8 @@ async function resumeHeld(prev, plan, sessionId, resuming, fresh) {
18980
18992
  record.deliveryId = delivery.deliveryId;
18981
18993
  record.flowId = delivery.deliveryId;
18982
18994
  }
18983
- const at = all.findIndex((r) => r.sessionId === prev.sessionId);
18984
- if (at >= 0) all.splice(at, 1);
18995
+ const at = all2.findIndex((r) => r.sessionId === prev.sessionId);
18996
+ if (at >= 0) all2.splice(at, 1);
18985
18997
  return { run: record };
18986
18998
  }));
18987
18999
  if ("error" in out) return out;
@@ -19020,8 +19032,8 @@ function forgetRun(sessionId) {
19020
19032
  }
19021
19033
  }
19022
19034
  function askForSpec(sessionId, ask2) {
19023
- const run = peekRun(sessionId);
19024
- if (!run) return "no-run";
19035
+ const run2 = peekRun(sessionId);
19036
+ if (!run2) return "no-run";
19025
19037
  const file = readAsks(sessionId);
19026
19038
  if (file.asks.some((a) => a.specAgent === ask2.specAgent && a.cardId === ask2.cardId)) return "already";
19027
19039
  file.asks.push(ask2);
@@ -19090,9 +19102,9 @@ function markSpawned(sessionId, pid) {
19090
19102
  }
19091
19103
  function patch(sessionId, change2) {
19092
19104
  return withRuns((runs) => {
19093
- const run = runs.find((r) => r.sessionId === sessionId);
19094
- if (run) change2(run);
19095
- return run ? { ...run } : void 0;
19105
+ const run2 = runs.find((r) => r.sessionId === sessionId);
19106
+ if (run2) change2(run2);
19107
+ return run2 ? { ...run2 } : void 0;
19096
19108
  });
19097
19109
  }
19098
19110
  function peekRun(sessionId) {
@@ -19100,19 +19112,19 @@ function peekRun(sessionId) {
19100
19112
  }
19101
19113
  async function closeRun(sessionId, res) {
19102
19114
  const closed = withRuns((runs) => {
19103
- const run = runs.find((r) => r.sessionId === sessionId);
19104
- if (!run || run.status !== "running") return void 0;
19105
- run.status = res.status;
19106
- if (res.ok !== void 0) run.ok = res.ok;
19107
- run.code = res.code ?? null;
19108
- if (res.error) run.error = res.error;
19109
- if (res.note) run.note = res.note;
19110
- if (res.errorWhy?.length) run.errorWhy = res.errorWhy;
19111
- if (res.noteWhy?.length) run.noteWhy = res.noteWhy;
19112
- if (res.tickedNothing) run.tickedNothing = true;
19113
- run.endedAt = res.endedAt ?? Date.now();
19114
- run.pid = void 0;
19115
- return { ...run };
19115
+ const run2 = runs.find((r) => r.sessionId === sessionId);
19116
+ if (!run2 || run2.status !== "running") return void 0;
19117
+ run2.status = res.status;
19118
+ if (res.ok !== void 0) run2.ok = res.ok;
19119
+ run2.code = res.code ?? null;
19120
+ if (res.error) run2.error = res.error;
19121
+ if (res.note) run2.note = res.note;
19122
+ if (res.errorWhy?.length) run2.errorWhy = res.errorWhy;
19123
+ if (res.noteWhy?.length) run2.noteWhy = res.noteWhy;
19124
+ if (res.tickedNothing) run2.tickedNothing = true;
19125
+ run2.endedAt = res.endedAt ?? Date.now();
19126
+ run2.pid = void 0;
19127
+ return { ...run2 };
19116
19128
  });
19117
19129
  if (!closed) return;
19118
19130
  if (closed.status === "done" || closed.status === "error") {
@@ -19138,14 +19150,14 @@ async function stopRun(id) {
19138
19150
  const restore = [];
19139
19151
  const out = withRuns((runs) => {
19140
19152
  reap(runs, [], restore);
19141
- const run = findRun(runs, id);
19142
- if (run === null) return { ok: false, ...ambiguousRun(id) };
19143
- if (!run) return { ok: false, ...unknownRun(id) };
19144
- if (run.status !== "running") return { ok: true, sessionId: run.sessionId };
19145
- run.stopping = true;
19146
- return { ok: true, sessionId: run.sessionId, pid: run.pid, live: true };
19153
+ const run2 = findRun(runs, id);
19154
+ if (run2 === null) return { ok: false, ...ambiguousRun(id) };
19155
+ if (!run2) return { ok: false, ...unknownRun(id) };
19156
+ if (run2.status !== "running") return { ok: true, sessionId: run2.sessionId };
19157
+ run2.stopping = true;
19158
+ return { ok: true, sessionId: run2.sessionId, pid: run2.pid, live: true };
19147
19159
  });
19148
- for (const run of restore) await restoreCardStatus(run);
19160
+ for (const run2 of restore) await restoreCardStatus(run2);
19149
19161
  const live = out;
19150
19162
  if (!live.ok || !live.live) {
19151
19163
  return { ok: live.ok, sessionId: live.sessionId, error: live.error, reason: live.reason, args: live.args };
@@ -19179,13 +19191,13 @@ async function cancelDelivery(id) {
19179
19191
  }
19180
19192
  async function stopSubRuns(parentId) {
19181
19193
  const live = liveSubRuns(parentId);
19182
- for (const run of live) await stopRun(run.sessionId);
19183
- await Promise.all(live.map((run) => runIsDown(run.sessionId)));
19194
+ for (const run2 of live) await stopRun(run2.sessionId);
19195
+ await Promise.all(live.map((run2) => runIsDown(run2.sessionId)));
19184
19196
  }
19185
19197
  async function runIsDown(sessionId) {
19186
19198
  for (const until = Date.now() + STOP_WAIT_MS; Date.now() < until; ) {
19187
- const run = readRuns().find((r) => r.sessionId === sessionId);
19188
- if (!run || !runIsLive(run)) return;
19199
+ const run2 = readRuns().find((r) => r.sessionId === sessionId);
19200
+ if (!run2 || !runIsLive(run2)) return;
19189
19201
  await sleep2(STOP_POLL_MS);
19190
19202
  }
19191
19203
  }
@@ -19529,13 +19541,13 @@ import path49 from "node:path";
19529
19541
  function cardsOnWorkflow(id) {
19530
19542
  const wanted = id || DEFAULT_WORKFLOW;
19531
19543
  const ids = [];
19532
- let files;
19544
+ let files2;
19533
19545
  try {
19534
- files = walkMd(TODO);
19546
+ files2 = walkMd(TODO);
19535
19547
  } catch {
19536
19548
  return [];
19537
19549
  }
19538
- for (const file of files) {
19550
+ for (const file of files2) {
19539
19551
  const base = path49.basename(file);
19540
19552
  const card = base === "root.md" ? idPrefix(path49.basename(path49.dirname(file))) : idPrefix(base);
19541
19553
  if (card === null) continue;
@@ -20116,8 +20128,8 @@ function settleDelivery2(id, skipped, unskipped) {
20116
20128
  function specRunAgent() {
20117
20129
  const id = insideRun();
20118
20130
  if (!id) return void 0;
20119
- const run = readStore().runs.find((r) => r.sessionId === id);
20120
- return run?.action === "spec" ? run.specAgent : void 0;
20131
+ const run2 = readStore().runs.find((r) => r.sessionId === id);
20132
+ return run2?.action === "spec" ? run2.specAgent : void 0;
20121
20133
  }
20122
20134
  function cmdTag(id, positions, tagRaw) {
20123
20135
  const ns = positions.split(",").map((s) => s.trim()).filter((s) => s.length > 0).map(Number);
@@ -20442,16 +20454,16 @@ function addToInbox(drop) {
20442
20454
  if (file && file.data.length > MAX_BYTES) {
20443
20455
  return { ok: false, error: `${path55.basename(file.name)} is over ${MAX_BYTES / 1024 / 1024} MB \u2014 too big for the board.` };
20444
20456
  }
20445
- const read5 = describe(typed, file);
20446
- const keep = read5.keep;
20457
+ const read6 = describe(typed, file);
20458
+ const keep = read6.keep;
20447
20459
  const said3 = drop.source?.trim() ?? "";
20448
20460
  const hit = said3 ? matchSourceType(said3) : "";
20449
20461
  const kept = said3 && !hit ? metaPair("source", said3) : null;
20450
20462
  const incoming = {
20451
- ...read5.incoming,
20463
+ ...read6.incoming,
20452
20464
  ...drop.title?.trim() ? { title: oneLine(drop.title) } : {},
20453
20465
  ...hit ? { sourceType: hit } : {},
20454
- ...kept ? { meta: [kept, ...read5.incoming.meta] } : {},
20466
+ ...kept ? { meta: [kept, ...read6.incoming.meta] } : {},
20455
20467
  ...drop.agent ? { agent: drop.agent } : {},
20456
20468
  ...drop.workflow ? { workflow: drop.workflow } : {}
20457
20469
  };
@@ -21080,21 +21092,21 @@ function scheduledNext(workflow, one) {
21080
21092
  }
21081
21093
  function insideScheduledPass() {
21082
21094
  const sessionId = insideRun();
21083
- const run = sessionId ? readStore().runs.find((r) => r.sessionId === sessionId) : void 0;
21084
- return run?.action === "scheduled" && run.workflow && run.specAgent ? { workflow: run.workflow, agent: run.specAgent } : null;
21095
+ const run2 = sessionId ? readStore().runs.find((r) => r.sessionId === sessionId) : void 0;
21096
+ return run2?.action === "scheduled" && run2.workflow && run2.specAgent ? { workflow: run2.workflow, agent: run2.specAgent } : null;
21085
21097
  }
21086
21098
  function runAgent() {
21087
21099
  const sessionId = insideRun();
21088
21100
  const runs = sessionId ? readStore().runs : [];
21089
- const run = runs.find((r) => r.sessionId === sessionId);
21090
- if (!run) return null;
21091
- if (run.action !== "sub") return agentOf(run);
21092
- const parent = runs.find((r) => r.sessionId === run.parentId);
21093
- return parent ? agentOf(parent) : run.specAgent ?? null;
21101
+ const run2 = runs.find((r) => r.sessionId === sessionId);
21102
+ if (!run2) return null;
21103
+ if (run2.action !== "sub") return agentOf(run2);
21104
+ const parent = runs.find((r) => r.sessionId === run2.parentId);
21105
+ return parent ? agentOf(parent) : run2.specAgent ?? null;
21094
21106
  }
21095
- function agentOf(run) {
21096
- if (run.action === "scheduled") return run.specAgent ?? null;
21097
- return flowNodes().find((n) => n.flow === run.action)?.agent ?? null;
21107
+ function agentOf(run2) {
21108
+ if (run2.action === "scheduled") return run2.specAgent ?? null;
21109
+ return flowNodes().find((n) => n.flow === run2.action)?.agent ?? null;
21098
21110
  }
21099
21111
  var scheduledRequest, isPass, openPasses, stalePasses;
21100
21112
  var init_scheduled = __esm({
@@ -21112,7 +21124,7 @@ var init_scheduled = __esm({
21112
21124
  workflow: pass.workflow,
21113
21125
  specAgent: pass.agent
21114
21126
  });
21115
- isPass = (run, pass) => run.action === "scheduled" && run.workflow === pass.workflow && run.specAgent === pass.agent;
21127
+ isPass = (run2, pass) => run2.action === "scheduled" && run2.workflow === pass.workflow && run2.specAgent === pass.agent;
21116
21128
  openPasses = (deliveries, pass) => deliveries.filter(
21117
21129
  (d) => d.status === "active" && d.scheduled?.workflow === pass.workflow && d.scheduled.agent === pass.agent
21118
21130
  );
@@ -21263,14 +21275,14 @@ function openRows() {
21263
21275
  }
21264
21276
  return rows.sort((a, b) => a.id - b.id);
21265
21277
  }
21266
- function cmdStale(rows, all, scope, mod) {
21278
+ function cmdStale(rows, all2, scope, mod) {
21267
21279
  const days = staleAfter();
21268
21280
  const ages = cardAges();
21269
21281
  if (ages === null) {
21270
21282
  say(`not a git repository \u2014 a card's age is the date git last saw its file, so there is nothing to list.`);
21271
21283
  return { cards: [], module: mod, staleAfter: days };
21272
21284
  }
21273
- const open3 = new Set(all.map((r) => r.id));
21285
+ const open3 = new Set(all2.map((r) => r.id));
21274
21286
  const stale3 = rows.filter(canGoStale).map((r) => ({ row: r, age: ages.get(r.file) })).filter((r) => r.age !== void 0 && r.age.days >= days).sort((a, b) => b.age.days - a.age.days || a.row.id - b.row.id);
21275
21287
  const cards = stale3.map(({ row, age }) => ({ ...row, file: rel(row.file), ...age }));
21276
21288
  if (!stale3.length) {
@@ -21288,18 +21300,18 @@ function cmdStale(rows, all, scope, mod) {
21288
21300
  return { cards, module: mod, staleAfter: days };
21289
21301
  }
21290
21302
  function archivedFiles() {
21291
- const files = /* @__PURE__ */ new Map();
21292
- if (!fs59.existsSync(ARCHIVE)) return files;
21303
+ const files2 = /* @__PURE__ */ new Map();
21304
+ if (!fs59.existsSync(ARCHIVE)) return files2;
21293
21305
  for (const file of walkMd(ARCHIVE)) {
21294
21306
  const id = idPrefix(path58.basename(file));
21295
- if (id !== null) files.set(id, file);
21307
+ if (id !== null) files2.set(id, file);
21296
21308
  }
21297
21309
  for (const dir of walkDirs(ARCHIVE)) {
21298
21310
  const id = idPrefix(path58.basename(dir));
21299
21311
  const root = path58.join(dir, "root.md");
21300
- if (id !== null && fs59.existsSync(root)) files.set(id, root);
21312
+ if (id !== null && fs59.existsSync(root)) files2.set(id, root);
21301
21313
  }
21302
- return files;
21314
+ return files2;
21303
21315
  }
21304
21316
  function cmdArchived(opts) {
21305
21317
  let since = null;
@@ -21319,14 +21331,14 @@ function cmdArchived(opts) {
21319
21331
  }
21320
21332
  const workflow = opts.workflow === void 0 ? null : knownWorkflow(opts.workflow);
21321
21333
  const open3 = boardCardIds();
21322
- const files = archivedFiles();
21334
+ const files2 = archivedFiles();
21323
21335
  const cards = landedDeliveries({ since: since?.getTime(), workflow: workflow ?? void 0 }).filter((d) => !open3.has(d.cardId)).map((d) => ({
21324
21336
  id: d.cardId,
21325
21337
  title: d.title,
21326
21338
  workflow: d.workflow,
21327
21339
  landedAt: formatStamp(new Date(d.at)),
21328
21340
  commit: d.commit,
21329
- file: files.has(d.cardId) ? rel(files.get(d.cardId)) : ""
21341
+ file: files2.has(d.cardId) ? rel(files2.get(d.cardId)) : ""
21330
21342
  }));
21331
21343
  const result = { cards, since: since ? formatStamp(since) : null, workflow };
21332
21344
  const scope = [workflow ? `on workflow \`${workflow}\`` : "", since ? `since ${formatStamp(since)}` : ""].filter(Boolean).join(" ");
@@ -21350,8 +21362,8 @@ function cmdList(opts) {
21350
21362
  }
21351
21363
  if (opts.since !== void 0) die("--since only applies to --archived.");
21352
21364
  if (opts.workflow !== void 0) die("--workflow only applies to --archived.");
21353
- const all = openRows();
21354
- let rows = all;
21365
+ const all2 = openRows();
21366
+ let rows = all2;
21355
21367
  let scope = "on the board";
21356
21368
  const mod = opts.module;
21357
21369
  if (mod !== void 0) {
@@ -21363,7 +21375,7 @@ function cmdList(opts) {
21363
21375
  rows = rows.filter((r) => r.modules.includes(mod));
21364
21376
  scope = `tagged \`${mod}\``;
21365
21377
  }
21366
- if (opts.stale) return cmdStale(rows, all, scope, mod === void 0 ? null : mod);
21378
+ if (opts.stale) return cmdStale(rows, all2, scope, mod === void 0 ? null : mod);
21367
21379
  const cards = rows.map((r) => ({ ...r, file: rel(r.file) }));
21368
21380
  if (!rows.length) {
21369
21381
  say(`no open cards ${scope}.`);
@@ -21443,10 +21455,10 @@ function stripOldHeader(text5) {
21443
21455
  }
21444
21456
  function cmdMigrate(opts) {
21445
21457
  const dry = opts.dryRun === true;
21446
- const files = walkMd(TODO).filter((f) => path59.basename(f) !== "README.md");
21458
+ const files2 = walkMd(TODO).filter((f) => path59.basename(f) !== "README.md");
21447
21459
  let changed = 0;
21448
21460
  let skipped = 0;
21449
- for (const file of files) {
21461
+ for (const file of files2) {
21450
21462
  const text5 = fs60.readFileSync(file, "utf8");
21451
21463
  if (text5.trimStart().startsWith("---")) {
21452
21464
  skipped++;
@@ -21646,16 +21658,16 @@ function removeCard2(id, metric, options) {
21646
21658
  }
21647
21659
  }
21648
21660
  withStore((store) => {
21649
- for (const run of store.runs) {
21650
- if (!leaving.some((c) => run.createdCardIds?.includes(c.id))) continue;
21651
- const marks = new Map((run.discardedCards ?? []).map((c) => [c.id, c]));
21661
+ for (const run2 of store.runs) {
21662
+ if (!leaving.some((c) => run2.createdCardIds?.includes(c.id))) continue;
21663
+ const marks = new Map((run2.discardedCards ?? []).map((c) => [c.id, c]));
21652
21664
  for (const c of leaving) marks.set(c.id, { id: c.id, path: rel(c.file), pending: true });
21653
- run.discardedCards = [...marks.values()];
21665
+ run2.discardedCards = [...marks.values()];
21654
21666
  }
21655
21667
  });
21656
21668
  }
21657
21669
  if (options.discard) withStore((store) => {
21658
- for (const run of store.runs) for (const card of run.discardedCards ?? []) {
21670
+ for (const run2 of store.runs) for (const card of run2.discardedCards ?? []) {
21659
21671
  if (leftIds.includes(card.id)) delete card.pending;
21660
21672
  }
21661
21673
  });
@@ -21856,10 +21868,10 @@ var init_agent_file = __esm({
21856
21868
  // ../cli/src/commands/memory-file.ts
21857
21869
  function cmdMemoryFile(askedName) {
21858
21870
  const name = askedName.trim();
21859
- const all = globalMemories();
21860
- const memory = all.find((m) => m.name === name);
21871
+ const all2 = globalMemories();
21872
+ const memory = all2.find((m) => m.name === name);
21861
21873
  if (!memory) {
21862
- const has = all.length ? `It has: ${all.map((m) => m.name).join(", ")}.` : "It has none.";
21874
+ const has = all2.length ? `It has: ${all2.map((m) => m.name).join(", ")}.` : "It has none.";
21863
21875
  die(`"${name}" is not a global memory on this board. ${has}`, { kind: "no-such-memory", memory: name });
21864
21876
  }
21865
21877
  say(memory.text);
@@ -22295,12 +22307,12 @@ function variantsProblem(raw, src, id) {
22295
22307
  return `variants is not valid JSON. ${shape}`;
22296
22308
  }
22297
22309
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return `variants is not a JSON object. ${shape}`;
22298
- const files = Object.values(parsed);
22299
- if (files.length < 2 || !files.every((f) => typeof f === "string" && f)) return `variants needs at least two labels, each naming a file. ${shape}`;
22300
- if (!files.includes(src)) return `src="${src}" is not one of the variants. Set src to the version shown first.`;
22310
+ const files2 = Object.values(parsed);
22311
+ if (files2.length < 2 || !files2.every((f) => typeof f === "string" && f)) return `variants needs at least two labels, each naming a file. ${shape}`;
22312
+ if (!files2.includes(src)) return `src="${src}" is not one of the variants. Set src to the version shown first.`;
22301
22313
  if (id === void 0) return null;
22302
22314
  const dir = path62.join(ASSETS, String(id));
22303
- for (const f of files) {
22315
+ for (const f of files2) {
22304
22316
  const parts = f.split("/");
22305
22317
  if (parts.length !== 3 || parts[0] !== ".assets" || parts[1] !== String(id) || !inside(dir, parts[2])) {
22306
22318
  return `Variant ${f} names no file in this card's asset folder .assets/${id}/. Write the file there or fix the path.`;
@@ -22323,7 +22335,7 @@ function compareProblem(attrs, id) {
22323
22335
  if (!script && kind !== compareKind(attrs.b)) return src("Compare a and b must both be images or both be videos.");
22324
22336
  if (attrs.shots === void 0) return script ? shots('shots is required when b is a storyboard. Add shots="..." naming a storyboard or shots list of video a.') : null;
22325
22337
  if (kind !== "video") return shots("shots is for videos only. Remove it from this Compare tag.");
22326
- const read5 = (at) => {
22338
+ const read6 = (at) => {
22327
22339
  const named2 = assetName(at, id, ["json"]);
22328
22340
  if (!("name" in named2)) return { message: `Compare shots is ${named2.actual}; expected ${named2.expected}.` };
22329
22341
  const file = inside(dir, named2.name);
@@ -22334,12 +22346,12 @@ function compareProblem(attrs, id) {
22334
22346
  return { message: `${at} is not valid JSON.` };
22335
22347
  }
22336
22348
  };
22337
- const list = read5(attrs.shots);
22349
+ const list = read6(attrs.shots);
22338
22350
  if ("message" in list) return shots(list.message);
22339
22351
  const why = Array.isArray(list.data) ? shotsProblem(list.data) : storyboardProblem(list.data);
22340
22352
  if (why) return shots(`${attrs.shots}: ${why}`);
22341
22353
  if (!script) return null;
22342
- const b = read5(attrs.b);
22354
+ const b = read6(attrs.b);
22343
22355
  if ("message" in b) return src(b.message);
22344
22356
  const bad = storyboardProblem(b.data);
22345
22357
  if (bad) return src(`${attrs.b}: ${bad}`);
@@ -22586,11 +22598,11 @@ function newOpId() {
22586
22598
  opCounter += 1;
22587
22599
  return `${process.pid.toString(36)}-${Date.now().toString(36)}-${opCounter.toString(36)}`;
22588
22600
  }
22589
- async function leaseAnd(provider, target, run) {
22601
+ async function leaseAnd(provider, target, run2) {
22590
22602
  const got = await provider.lease(target);
22591
22603
  if (!got.ok) return { ok: false, kind: "refused", error: got.error };
22592
22604
  try {
22593
- return await run(envelope(got.lease.revision, got.lease.id));
22605
+ return await run2(envelope(got.lease.revision, got.lease.id));
22594
22606
  } finally {
22595
22607
  await provider.releaseLease(got.lease.id);
22596
22608
  }
@@ -22732,14 +22744,14 @@ function localBoard() {
22732
22744
  },
22733
22745
  // ---- the named `akb raw` moves ----------------------------------------
22734
22746
  async runMove(move2, input, env) {
22735
- const run = MOVES[move2];
22736
- if (!run) return Promise.resolve(opRefused(new Error(`unknown command "${move2}".`)));
22747
+ const run2 = MOVES[move2];
22748
+ if (!run2) return Promise.resolve(opRefused(new Error(`unknown command "${move2}".`)));
22737
22749
  const target = moveTarget(move2, input.args);
22738
22750
  try {
22739
22751
  return await withBoardLock(() => {
22740
22752
  const no = checkWrite(target, env, revisionAt(target));
22741
22753
  if (no) return no;
22742
- const data = run(input);
22754
+ const data = run2(input);
22743
22755
  if (data instanceof Promise) return data.then((data2) => opOk(revisionAt(target), { data: data2 }));
22744
22756
  return opOk(revisionAt(target), { data: data || {} });
22745
22757
  });
@@ -22748,10 +22760,10 @@ function localBoard() {
22748
22760
  }
22749
22761
  },
22750
22762
  readMove(move2, input) {
22751
- const run = MOVES[move2];
22752
- if (!run) return Promise.reject(new Error(`unknown command "${move2}".`));
22763
+ const run2 = MOVES[move2];
22764
+ if (!run2) return Promise.reject(new Error(`unknown command "${move2}".`));
22753
22765
  try {
22754
- return Promise.resolve(run(input) || {});
22766
+ return Promise.resolve(run2(input) || {});
22755
22767
  } catch (e) {
22756
22768
  return Promise.reject(e);
22757
22769
  }
@@ -22813,8 +22825,8 @@ function board() {
22813
22825
  if (!active) active = localBoard();
22814
22826
  return active;
22815
22827
  }
22816
- function withLease(target, run) {
22817
- return leaseAnd(board(), target, run);
22828
+ function withLease(target, run2) {
22829
+ return leaseAnd(board(), target, run2);
22818
22830
  }
22819
22831
  var active, setCardStatusOn, archiveCard, finishSetupStep, cancelBoardDelivery, resumeBoardDelivery, discardBoardDelivery;
22820
22832
  var init_board2 = __esm({
@@ -23093,18 +23105,18 @@ async function refresh() {
23093
23105
  }
23094
23106
  }
23095
23107
  function sessionFrom(body, previous) {
23096
- const token2 = body;
23097
- const subject2 = token2.user?.id ?? previous?.subject;
23098
- if (!token2.access_token || !token2.refresh_token || !subject2) return null;
23099
- const expiresAt = token2.expires_at ? token2.expires_at * 1e3 : Date.now() + (token2.expires_in ?? 3600) * 1e3;
23108
+ const token3 = body;
23109
+ const subject2 = token3.user?.id ?? previous?.subject;
23110
+ if (!token3.access_token || !token3.refresh_token || !subject2) return null;
23111
+ const expiresAt = token3.expires_at ? token3.expires_at * 1e3 : Date.now() + (token3.expires_in ?? 3600) * 1e3;
23100
23112
  return {
23101
23113
  version: 1,
23102
23114
  supabaseUrl: cloudEndpoints().supabaseUrl,
23103
- accessToken: token2.access_token,
23104
- refreshToken: token2.refresh_token,
23115
+ accessToken: token3.access_token,
23116
+ refreshToken: token3.refresh_token,
23105
23117
  expiresAt,
23106
23118
  subject: subject2,
23107
- email: token2.user?.email ?? previous?.email,
23119
+ email: token3.user?.email ?? previous?.email,
23108
23120
  handle: previous?.handle,
23109
23121
  name: previous?.name,
23110
23122
  avatarUrl: previous?.avatarUrl,
@@ -23193,16 +23205,16 @@ function cloudWorkflow(name) {
23193
23205
  }
23194
23206
  async function call(method, route2, body, key) {
23195
23207
  if (!cloudConfigured()) return { ok: false, error: NOT_CONFIGURED, code: "bad_request" };
23196
- const token2 = await accessToken();
23197
- if (!token2.ok) {
23198
- return token2.reason === "unreachable" ? { ok: false, error: `Cloud could not be reached: ${token2.error}` } : { ok: false, error: "This machine is not signed in to Cloud.", code: "unauthenticated" };
23208
+ const token3 = await accessToken();
23209
+ if (!token3.ok) {
23210
+ return token3.reason === "unreachable" ? { ok: false, error: `Cloud could not be reached: ${token3.error}` } : { ok: false, error: "This machine is not signed in to Cloud.", code: "unauthenticated" };
23199
23211
  }
23200
23212
  let response;
23201
23213
  try {
23202
23214
  response = await fetch(`${cloudEndpoints().api}${route2}`, {
23203
23215
  method,
23204
23216
  headers: {
23205
- authorization: `Bearer ${token2.token}`,
23217
+ authorization: `Bearer ${token3.token}`,
23206
23218
  ...body ? { "content-type": "application/json" } : {},
23207
23219
  ...key ? { "idempotency-key": key } : {}
23208
23220
  },
@@ -23228,7 +23240,8 @@ async function createTask(from, said3) {
23228
23240
  source: "api",
23229
23241
  source_context: { task: from },
23230
23242
  ...said3.run ? { run: true } : {},
23231
- ...said3.metadata && Object.keys(said3.metadata).length ? { metadata: said3.metadata } : {}
23243
+ ...said3.metadata && Object.keys(said3.metadata).length ? { metadata: said3.metadata } : {},
23244
+ ...said3.triage ? { triage: `tri_${source.value.project.replace(/^prj_/, "")}_${said3.triage}` } : {}
23232
23245
  };
23233
23246
  const key = `akb-${crypto2.createHash("sha256").update(`${from}
23234
23247
  ${said3.slug?.trim() || said3.title}`).digest("hex").slice(0, 40)}`;
@@ -23358,6 +23371,7 @@ __export(board_exports, {
23358
23371
  buildBoardProgram: () => buildBoardProgram
23359
23372
  });
23360
23373
  import fs68 from "node:fs";
23374
+ import path65 from "node:path";
23361
23375
  async function dispatch(move2, cmd, args, opts, cli) {
23362
23376
  const ctx = ctxOf(cmd, cli.program);
23363
23377
  const data = await runAction(ctx, {}, async () => {
@@ -23391,24 +23405,30 @@ async function cloudCard(task, opts) {
23391
23405
  } catch {
23392
23406
  }
23393
23407
  }
23394
- const run = opts.schedule !== void 0;
23408
+ const run2 = opts.schedule !== void 0;
23395
23409
  const made = await createTask(task, {
23396
23410
  title: String(opts.title ?? "").trim(),
23397
23411
  request,
23398
23412
  workflow: typeof opts.workflow === "string" && opts.workflow.trim() ? opts.workflow.trim() : void 0,
23399
- run,
23413
+ run: run2,
23400
23414
  slug: typeof opts.slug === "string" ? opts.slug : void 0,
23401
- metadata: run ? cardFields(opts) : void 0
23415
+ metadata: run2 ? cardFields(opts) : void 0,
23416
+ triage: typeof opts.triage === "string" ? triageFile(opts.triage) : void 0
23402
23417
  });
23403
23418
  if (!made.ok) throw new BoardError(`Cloud did not make the task: ${made.error}`, { kind: "refused" });
23404
23419
  const { task: created, number, from } = made.value;
23405
- if (run) {
23420
+ if (run2) {
23406
23421
  say(number);
23407
23422
  say(` made ${created.key} in Cloud${made.value.replayed ? " (already made)" : ""} \u2014 it is planned there, from ${from.key}`);
23408
23423
  return { done: { board: KANBAN, id: number, ids: [], key: created.key, task: created.id, replayed: made.value.replayed } };
23409
23424
  }
23410
23425
  return { opts: { ...opts, id: number, fromTask: from.key, schedule: void 0 } };
23411
23426
  }
23427
+ function triageFile(sourceId) {
23428
+ const found2 = [...readInbox(), ...readAllDismissed(), ...readArchived()].find((s) => s.sourceId === sourceId.trim());
23429
+ if (!found2) throw new BoardError(`nothing in triage is ${sourceId}`, { kind: "refused" });
23430
+ return path65.basename(found2.relPath, ".md");
23431
+ }
23412
23432
  function cardFields(opts) {
23413
23433
  const list = (v) => Array.isArray(v) && v.length ? v.map((x) => String(x).replace(/^#/, "")) : void 0;
23414
23434
  return Object.fromEntries(Object.entries({
@@ -23568,6 +23588,7 @@ var init_board3 = __esm({
23568
23588
  init_rules();
23569
23589
  init_shared();
23570
23590
  init_esm();
23591
+ init_inbox();
23571
23592
  init_tasks();
23572
23593
  init_idea();
23573
23594
  CREATE_SCHEDULES = SCHEDULED_ACTIONS.filter((a) => a !== "revise");
@@ -23577,15 +23598,15 @@ var init_board3 = __esm({
23577
23598
 
23578
23599
  // ../cli/src/lib/board-cli.ts
23579
23600
  import fs69 from "node:fs";
23580
- import path65 from "node:path";
23601
+ import path66 from "node:path";
23581
23602
  function useBoard(found2, dirNamed) {
23582
- if (!found2.named && found2.board === path65.join(found2.root, "docs", "kanban")) setBoardRoot(found2.root, dirNamed);
23603
+ if (!found2.named && found2.board === path66.join(found2.root, "docs", "kanban")) setBoardRoot(found2.root, dirNamed);
23583
23604
  else setBoardDir(found2.board, found2.root);
23584
23605
  useProjectState();
23585
23606
  }
23586
23607
  function whole(found2, move2) {
23587
23608
  if (move2 === MAKES_A_BOARD) return found2;
23588
- if (!fs69.existsSync(path65.join(found2.board, "todo"))) {
23609
+ if (!fs69.existsSync(path66.join(found2.board, "todo"))) {
23589
23610
  die(`the board in ${found2.board} has no todo/ \u2014 run \`init\` to add what is missing.`, {
23590
23611
  kind: "board-incomplete",
23591
23612
  dir: found2.root
@@ -23594,11 +23615,11 @@ function whole(found2, move2) {
23594
23615
  return found2;
23595
23616
  }
23596
23617
  function findBoardUpward(from) {
23597
- let dir = path65.resolve(from);
23618
+ let dir = path66.resolve(from);
23598
23619
  for (; ; ) {
23599
- if (hasBoard(dir)) return { root: dir, board: path65.join(dir, "docs", "kanban") };
23620
+ if (hasBoard(dir)) return { root: dir, board: path66.join(dir, "docs", "kanban") };
23600
23621
  if (isBoardDir(dir)) return { root: projectRootOf(dir), board: dir };
23601
- const up = path65.dirname(dir);
23622
+ const up = path66.dirname(dir);
23602
23623
  if (up === dir) return null;
23603
23624
  dir = up;
23604
23625
  }
@@ -23606,7 +23627,7 @@ function findBoardUpward(from) {
23606
23627
  function resolveBoard(move2, { board: board2, dir, cwd, installHint }) {
23607
23628
  const named2 = board2 ?? boardFromEnv();
23608
23629
  if (named2) {
23609
- const folder = path65.resolve(cwd, named2);
23630
+ const folder = path66.resolve(cwd, named2);
23610
23631
  if (!fs69.existsSync(folder) && move2 !== MAKES_A_BOARD) {
23611
23632
  die(`no such folder: ${folder}`, { kind: "no-such-folder", dir: folder });
23612
23633
  }
@@ -23619,7 +23640,7 @@ function resolveBoard(move2, { board: board2, dir, cwd, installHint }) {
23619
23640
  return { root: projectRootOf(folder), board: folder, named: true };
23620
23641
  }
23621
23642
  if (dir !== null) {
23622
- const root = path65.resolve(dir);
23643
+ const root = path66.resolve(dir);
23623
23644
  if (!fs69.existsSync(root)) die(`no such folder: ${root}`, { kind: "no-such-folder", dir: root });
23624
23645
  if (!hasBoard(root) && move2 !== MAKES_A_BOARD) {
23625
23646
  die(`no board in ${root} \u2014 it has no docs/kanban/. Run ${installHint} there to make one.`, {
@@ -23627,14 +23648,14 @@ function resolveBoard(move2, { board: board2, dir, cwd, installHint }) {
23627
23648
  dir: root
23628
23649
  });
23629
23650
  }
23630
- return whole({ root, board: path65.join(root, "docs", "kanban") }, move2);
23651
+ return whole({ root, board: path66.join(root, "docs", "kanban") }, move2);
23631
23652
  }
23632
23653
  const found2 = findBoardUpward(cwd);
23633
23654
  if (found2) return whole(found2, move2);
23634
- if (move2 === MAKES_A_BOARD) return { root: path65.resolve(cwd), board: path65.join(path65.resolve(cwd), "docs", "kanban") };
23655
+ if (move2 === MAKES_A_BOARD) return { root: path66.resolve(cwd), board: path66.join(path66.resolve(cwd), "docs", "kanban") };
23635
23656
  die(
23636
- `no board here. Looked in ${path65.resolve(cwd)} and every folder above it for docs/kanban/. Run ${installHint} to make one, or name the board with --board.`,
23637
- { kind: "no-board", dir: path65.resolve(cwd) }
23657
+ `no board here. Looked in ${path66.resolve(cwd)} and every folder above it for docs/kanban/. Run ${installHint} to make one, or name the board with --board.`,
23658
+ { kind: "no-board", dir: path66.resolve(cwd) }
23638
23659
  );
23639
23660
  }
23640
23661
  function editDistance(a, b) {
@@ -23716,8 +23737,8 @@ var init_board_cli = __esm({
23716
23737
  init_io();
23717
23738
  init_pointer();
23718
23739
  MAKES_A_BOARD = "init";
23719
- hasBoard = (dir) => fs69.existsSync(path65.join(dir, "docs", "kanban"));
23720
- isBoardDir = (dir) => fs69.existsSync(path65.join(dir, "todo")) && fs69.existsSync(path65.join(dir, "config.md"));
23740
+ hasBoard = (dir) => fs69.existsSync(path66.join(dir, "docs", "kanban"));
23741
+ isBoardDir = (dir) => fs69.existsSync(path66.join(dir, "todo")) && fs69.existsSync(path66.join(dir, "config.md"));
23721
23742
  boardFromEnv = () => {
23722
23743
  const named2 = process.env.AI4KANBAN_BOARD?.trim();
23723
23744
  return named2 ? named2 : null;
@@ -23726,8 +23747,8 @@ var init_board_cli = __esm({
23726
23747
  });
23727
23748
 
23728
23749
  // ../cli/src/lib/agent/approve.ts
23729
- import fs73 from "node:fs";
23730
- import path69 from "node:path";
23750
+ import fs75 from "node:fs";
23751
+ import path72 from "node:path";
23731
23752
  function approvalRequest(req) {
23732
23753
  const id = req.id;
23733
23754
  const card = id === void 0 ? null : findCard(id);
@@ -23745,12 +23766,12 @@ function advancePhase(id) {
23745
23766
  const card = findCard(id);
23746
23767
  const found2 = locate(id);
23747
23768
  if (!card || !found2 || !awaitingApproval(card)) return { error: `#${id} is no longer waiting on approval.` };
23748
- const file = found2.kind === "group" ? path69.join(found2.target, "root.md") : found2.target;
23749
- const { meta, body } = parseFrontmatter(fs73.readFileSync(file, "utf8"));
23769
+ const file = found2.kind === "group" ? path72.join(found2.target, "root.md") : found2.target;
23770
+ const { meta, body } = parseFrontmatter(fs75.readFileSync(file, "utf8"));
23750
23771
  if (!meta) return { error: `#${id} has no frontmatter` };
23751
23772
  meta.phase = card.stage + 1;
23752
23773
  meta.status = "todo";
23753
- fs73.writeFileSync(file, serializeFrontmatter(meta) + "\n" + body);
23774
+ fs75.writeFileSync(file, serializeFrontmatter(meta) + "\n" + body);
23754
23775
  return null;
23755
23776
  }
23756
23777
  var init_approve = __esm({
@@ -23840,10 +23861,10 @@ function open2(req, sessionId) {
23840
23861
  returnRunPictures(sessionId, req.box);
23841
23862
  return opened;
23842
23863
  }
23843
- const { run } = opened;
23844
- const pid = spawnWatcher(run.sessionId);
23845
- markSpawned(run.sessionId, pid);
23846
- return { run, spawned: pid !== void 0 };
23864
+ const { run: run2 } = opened;
23865
+ const pid = spawnWatcher(run2.sessionId);
23866
+ markSpawned(run2.sessionId, pid);
23867
+ return { run: run2, spawned: pid !== void 0 };
23847
23868
  }
23848
23869
  async function startResume(id) {
23849
23870
  const opened = await openResume(id);
@@ -23870,16 +23891,16 @@ var init_start = __esm({
23870
23891
  });
23871
23892
 
23872
23893
  // ../cli/src/lib/agent/task-chats.ts
23873
- import fs74 from "node:fs";
23874
- import path70 from "node:path";
23894
+ import fs76 from "node:fs";
23895
+ import path73 from "node:path";
23875
23896
  function taskChatFile(task, home3) {
23876
23897
  if (!TASK.test(task)) throw new Error(`${task} is not a task id.`);
23877
- return path70.join(taskChatsDir(home3), `${task}.jsonl`);
23898
+ return path73.join(taskChatsDir(home3), `${task}.jsonl`);
23878
23899
  }
23879
23900
  function readTaskChat(task, home3) {
23880
23901
  let text5;
23881
23902
  try {
23882
- text5 = fs74.readFileSync(taskChatFile(task, home3), "utf8");
23903
+ text5 = fs76.readFileSync(taskChatFile(task, home3), "utf8");
23883
23904
  } catch {
23884
23905
  return [];
23885
23906
  }
@@ -23900,20 +23921,20 @@ var init_task_chats = __esm({
23900
23921
  "use strict";
23901
23922
  init_home();
23902
23923
  TASK = /^[\w-]{1,100}$/;
23903
- taskChatsDir = (home3 = machineHome()) => path70.join(home3, "chats");
23924
+ taskChatsDir = (home3 = machineHome()) => path73.join(home3, "chats");
23904
23925
  }
23905
23926
  });
23906
23927
 
23907
23928
  // ../cli/src/lib/agent/memory-review.ts
23908
- import fs75 from "node:fs";
23909
- import path71 from "node:path";
23929
+ import fs77 from "node:fs";
23930
+ import path74 from "node:path";
23910
23931
  function taskChatsToReview(tasks) {
23911
- const cards = fs75.existsSync(ARCHIVE) ? walkMd(ARCHIVE) : [];
23932
+ const cards = fs77.existsSync(ARCHIVE) ? walkMd(ARCHIVE) : [];
23912
23933
  const out = [];
23913
23934
  for (const task of [...new Set(tasks)]) {
23914
23935
  const slug2 = task.toLowerCase().replace(/_/g, "-");
23915
- const file = cards.find((f) => /^\d+-/.test(path71.basename(f)) && path71.basename(f).endsWith(`-${slug2}.md`));
23916
- const id = file ? idPrefix(path71.basename(file)) : null;
23936
+ const file = cards.find((f) => /^\d+-/.test(path74.basename(f)) && path74.basename(f).endsWith(`-${slug2}.md`));
23937
+ const id = file ? idPrefix(path74.basename(file)) : null;
23917
23938
  const card = file && id !== null ? readArchived2(id, file) : null;
23918
23939
  let lines = [];
23919
23940
  try {
@@ -23929,7 +23950,7 @@ function taskChatsToReview(tasks) {
23929
23950
  }
23930
23951
  function readArchived2(id, file) {
23931
23952
  try {
23932
- const { meta, body } = parseFrontmatter(fs75.readFileSync(file, "utf8"));
23953
+ const { meta, body } = parseFrontmatter(fs77.readFileSync(file, "utf8"));
23933
23954
  if (!meta) return null;
23934
23955
  return { id, title: meta.title, modules: meta.modules ?? [], rejected: meta.rejected, body };
23935
23956
  } catch {
@@ -23975,8 +23996,8 @@ var init_memory_review = __esm({
23975
23996
  });
23976
23997
 
23977
23998
  // ../cli/src/lib/agent/flow.ts
23978
- import fs76 from "node:fs";
23979
- import path72 from "node:path";
23999
+ import fs78 from "node:fs";
24000
+ import path75 from "node:path";
23980
24001
  function readCard2(id, home3 = "board") {
23981
24002
  const found2 = home3 === "archive" ? locateArchived(id) : locate(id);
23982
24003
  if (!found2) {
@@ -23985,10 +24006,10 @@ function readCard2(id, home3 = "board") {
23985
24006
  { kind: "card-not-found", id }
23986
24007
  );
23987
24008
  }
23988
- const file = found2.kind === "group" ? path72.join(found2.target, "root.md") : found2.target;
24009
+ const file = found2.kind === "group" ? path75.join(found2.target, "root.md") : found2.target;
23989
24010
  let text5;
23990
24011
  try {
23991
- text5 = fs76.readFileSync(file, "utf8");
24012
+ text5 = fs78.readFileSync(file, "utf8");
23992
24013
  } catch {
23993
24014
  die(`#${id} is on the board but ${rel(file)} can't be read.`, { kind: "card-unreadable", id });
23994
24015
  }
@@ -24046,7 +24067,7 @@ function memoryLines(modules, name) {
24046
24067
  }
24047
24068
  function configText() {
24048
24069
  try {
24049
- return fs76.readFileSync(CONFIG, "utf8").trim() || null;
24070
+ return fs78.readFileSync(CONFIG, "utf8").trim() || null;
24050
24071
  } catch {
24051
24072
  return null;
24052
24073
  }
@@ -24092,10 +24113,10 @@ function candidateField(delivery) {
24092
24113
  }
24093
24114
  const candidate = candidateOf(delivery);
24094
24115
  const stat = candidateStat(candidate);
24095
- const files = candidateFileStats(candidate);
24116
+ const files2 = candidateFileStats(candidate);
24096
24117
  const patch2 = candidatePatch(candidate);
24097
24118
  const command = delivery.worktree && delivery.branch ? `git diff ${delivery.base.slice(0, 12)} ${delivery.branch}` : `git diff ${delivery.base.slice(0, 12)}`;
24098
- const shown2 = files?.length ? ["changed files:", ...files.map((file) => ` ${file}`)] : ["no file changed"];
24119
+ const shown2 = files2?.length ? ["changed files:", ...files2.map((file) => ` ${file}`)] : ["no file changed"];
24099
24120
  let diff = [];
24100
24121
  if (patch2 === null) diff = ["", "the diff could not be read"];
24101
24122
  else if (patch2.length > MAX_DIFF) {
@@ -24119,7 +24140,7 @@ function workspaceField(delivery) {
24119
24140
  if (!delivery?.worktree) return [];
24120
24141
  return field("workspace", [
24121
24142
  `write code in ${delivery.worktree} \u2014 this delivery's own worktree, on branch ${delivery.branch}.`,
24122
- path72.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.`,
24143
+ path75.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.`,
24123
24144
  `every board command names the project's own copy: \`${boardCommandFor()} <command>\`.`,
24124
24145
  `${rel(REPO_ROOT)} is the project \u2014 the card, the memory files and the docs are changed there, not here.`
24125
24146
  ]);
@@ -24127,7 +24148,7 @@ function workspaceField(delivery) {
24127
24148
  function conflictField(delivery) {
24128
24149
  if (!delivery?.worktree) return field("conflict", "no delivery with a worktree is landing right now");
24129
24150
  const dir = worktreeDir(delivery.worktree);
24130
- const files = conflictedPaths(dir);
24151
+ const files2 = conflictedPaths(dir);
24131
24152
  const target = delivery.targetBranch ?? "the target branch";
24132
24153
  const since = delivery.landing?.originalBase ?? delivery.base;
24133
24154
  const onto = delivery.landing?.onto ?? delivery.targetBranch;
@@ -24138,7 +24159,7 @@ function conflictField(delivery) {
24138
24159
  return field("conflict", [
24139
24160
  ...delivery.landing?.why ? [`landing: ${delivery.landing.why}`] : [],
24140
24161
  `worktree: ${delivery.worktree}`,
24141
- ...files.length ? [`conflicted files (${files.length}):`, ...listed(files)] : ["conflicted files: none"],
24162
+ ...files2.length ? [`conflicted files (${files2.length}):`, ...listed(files2)] : ["conflicted files: none"],
24142
24163
  ...since && onto ? [
24143
24164
  `original base: ${short2(since)} \u2014 \`git diff ${short2(since)} ${short2(onto)}\` is what ${target} brought in since this delivery started`,
24144
24165
  `${target} changed since then (${theirs.length}):`,
@@ -24500,7 +24521,7 @@ function printFlow(rawReq, program2 = "akb") {
24500
24521
  say("");
24501
24522
  say(`there is no \`akb\` on this machine \u2014 every \`akb\` below, in the flows too, is \`${program2}\` here.`);
24502
24523
  }
24503
- if (req.action !== "setup" && fs76.existsSync(SETUP_CHECKLIST)) {
24524
+ if (req.action !== "setup" && fs78.existsSync(SETUP_CHECKLIST)) {
24504
24525
  say("");
24505
24526
  say(`this board is not set up yet \u2014 ${rel(SETUP_CHECKLIST)} is still there.`);
24506
24527
  say(`finish it first: ${setupInstruction()}`);
@@ -24540,6 +24561,11 @@ function printFlow(rawReq, program2 = "akb") {
24540
24561
  say("");
24541
24562
  say(rule);
24542
24563
  }
24564
+ const language = languageNote();
24565
+ if (language) {
24566
+ say("");
24567
+ say(language);
24568
+ }
24543
24569
  return {
24544
24570
  mode: "print",
24545
24571
  action: req.action,
@@ -24589,9 +24615,9 @@ var init_flow = __esm({
24589
24615
  EDITABLE_FIELDS = "--title|--priority|--roi|--modules|--blocked-by|--related";
24590
24616
  bodyScaffoldClose = (lead = "fill the existing") => [`${lead} body scaffold; do not rename or translate its section titles, and leave empty scaffold sections in place`];
24591
24617
  MAX_LISTED = 50;
24592
- listed = (files) => [
24593
- ...files.slice(0, MAX_LISTED).map((f) => ` ${f}`),
24594
- ...files.length > MAX_LISTED ? [` \u2026and ${files.length - MAX_LISTED} more`] : []
24618
+ listed = (files2) => [
24619
+ ...files2.slice(0, MAX_LISTED).map((f) => ` ${f}`),
24620
+ ...files2.length > MAX_LISTED ? [` \u2026and ${files2.length - MAX_LISTED} more`] : []
24595
24621
  ];
24596
24622
  answeredClose = (delivery, self) => `${self} delivery answered ${delivery.deliveryId} --changed|--unchanged "<why>" \u2014 before you drop the questions; dropping the last one with nothing said carries the build on as --unchanged`;
24597
24623
  stageClose = (meta) => {
@@ -24715,30 +24741,30 @@ function stalledLine(cardId2, next) {
24715
24741
  if (!card || card.schedule || refinementStep(card) === "done") return null;
24716
24742
  return next === "incomplete" ? `QA left untagged questions on #${card.id}, so it did not finish. #${card.id} is still at todo and nothing else will pick it up \u2014 refine it again, or mark it ready yourself.` : `#${card.id} is still in To do after planning, and the board won't plan it again on its own \u2014 refine it again, or mark it ready yourself.`;
24717
24743
  }
24718
- function planStageEnd(run) {
24719
- if (run.cardId === null || stageOfAction(run.action) !== "plan") return null;
24720
- const card = currentCard(run.cardId);
24744
+ function planStageEnd(run2) {
24745
+ if (run2.cardId === null || stageOfAction(run2.action) !== "plan") return null;
24746
+ const card = currentCard(run2.cardId);
24721
24747
  if (!card || card.openBlockers.length > 0 || openOf(card.questions).length > 0 || card.schedule) return null;
24722
24748
  if (refinementStep(card) !== "done") return null;
24723
24749
  return endOfStage(stageContract("plan"), card);
24724
24750
  }
24725
- function refinementRunsAfter(run, changed, before, waitingForSpec = false, refused = []) {
24726
- for (const id of /* @__PURE__ */ new Set([...changed, ...run.cardId === null ? [] : [run.cardId]])) {
24751
+ function refinementRunsAfter(run2, changed, before, waitingForSpec = false, refused = []) {
24752
+ for (const id of /* @__PURE__ */ new Set([...changed, ...run2.cardId === null ? [] : [run2.cardId]])) {
24727
24753
  scheduleRefineOnBlock(id);
24728
24754
  }
24729
- const next = waitingForSpec || run.cardId === null || !["clarify", "resolve", "edit", "approve"].includes(run.action) ? null : refinementAfter(run.action, run.cardId, run.flowId);
24730
- const starts2 = refinesAfter(run.action, changed, before).filter(
24731
- (req) => req.id !== run.cardId || run.refineRound === void 0 && run.action !== "spec"
24755
+ const next = waitingForSpec || run2.cardId === null || !["clarify", "resolve", "edit", "approve"].includes(run2.action) ? null : refinementAfter(run2.action, run2.cardId, run2.flowId);
24756
+ const starts2 = refinesAfter(run2.action, changed, before).filter(
24757
+ (req) => req.id !== run2.cardId || run2.refineRound === void 0 && run2.action !== "spec"
24732
24758
  );
24733
24759
  const refusedNote = waitingForSpec || !refused.length ? "" : `Spec agents not started: ${refused.map((r) => r.error).join(" ")}`;
24734
24760
  const plain2 = typeof next === "object" && next ? next : null;
24735
24761
  const carryOn = plain2 && refusedNote ? { ...plain2, notes: [plain2.notes, refusedNote].filter(Boolean).join("\n\n") } : plain2;
24736
- const end2 = waitingForSpec || carryOn || starts2.length ? null : planStageEnd(run);
24762
+ const end2 = waitingForSpec || carryOn || starts2.length ? null : planStageEnd(run2);
24737
24763
  const short2 = end2 && "missing" in end2 ? end2 : null;
24738
- const stalled = (short2 ? shortLine(short2, run.cardId) : void 0) ?? (waitingForSpec ? void 0 : (run.refineRound !== void 0 || next === "incomplete") && stalledLine(run.cardId, next) || void 0);
24764
+ const stalled = (short2 ? shortLine(short2, run2.cardId) : void 0) ?? (waitingForSpec ? void 0 : (run2.refineRound !== void 0 || next === "incomplete") && stalledLine(run2.cardId, next) || void 0);
24739
24765
  const stalledWhy = [
24740
24766
  ...refusedNote ? [{ kind: "specRefused", refusals: refused }] : [],
24741
- ...!stalled ? [] : short2 ? [{ kind: "stageShort", args: { stage: short2.stage, card: String(run.cardId), agents: short2.missing.join(", ") } }] : next === "incomplete" ? [{ kind: "qaUnfinished", args: { card: String(run.cardId) } }] : [{ kind: "refineStalled", args: { card: String(run.cardId) } }]
24767
+ ...!stalled ? [] : short2 ? [{ kind: "stageShort", args: { stage: short2.stage, card: String(run2.cardId), agents: short2.missing.join(", ") } }] : next === "incomplete" ? [{ kind: "qaUnfinished", args: { card: String(run2.cardId) } }] : [{ kind: "refineStalled", args: { card: String(run2.cardId) } }]
24742
24768
  ];
24743
24769
  return {
24744
24770
  runs: [...starts2, ...carryOn ? [carryOn] : [], ...end2 && "ask" in end2 ? [end2.ask] : []],
@@ -24921,21 +24947,21 @@ __export(watch_exports, {
24921
24947
  watchRun: () => watchRun
24922
24948
  });
24923
24949
  import { spawn as spawn3 } from "node:child_process";
24924
- import fs77 from "node:fs";
24950
+ import fs79 from "node:fs";
24925
24951
  async function watchRun(sessionId, resume = startResume) {
24926
24952
  const spec = readSpec(sessionId);
24927
- const run = peekRun(sessionId);
24928
- if (!run || !spec) {
24929
- if (run?.status === "running") await closeRun(sessionId, { status: "interrupted", code: null });
24953
+ const run2 = peekRun(sessionId);
24954
+ if (!run2 || !spec) {
24955
+ if (run2?.status === "running") await closeRun(sessionId, { status: "interrupted", code: null });
24930
24956
  return 1;
24931
24957
  }
24932
- if (run.stopping) {
24958
+ if (run2.stopping) {
24933
24959
  await closeRun(sessionId, { status: "stopped", code: null });
24934
24960
  return 1;
24935
24961
  }
24936
24962
  const stopping = () => peekRun(sessionId)?.status !== "running";
24937
24963
  let releaseIndex = null;
24938
- if (needsIndexLock(run.action)) {
24964
+ if (needsIndexLock(run2.action)) {
24939
24965
  releaseIndex = await acquireIndexLock(stopping);
24940
24966
  if (!releaseIndex) {
24941
24967
  if (!stopping()) await closeRun(sessionId, { status: "interrupted", code: null });
@@ -24954,14 +24980,14 @@ async function watchRun(sessionId, resume = startResume) {
24954
24980
  const claimed = patch(sessionId, (r) => {
24955
24981
  claim = claimCard(r);
24956
24982
  });
24957
- const record = claimed ?? run;
24983
+ const record = claimed ?? run2;
24958
24984
  const sub = record.action === "sub";
24959
24985
  if (claim) await setCardStatus(claim.cardId, claim.status);
24960
- fs77.mkdirSync(SESSIONS_DIR, { recursive: true });
24961
- const logFile = fs77.createWriteStream(record.logPath, { flags: "a" });
24986
+ fs79.mkdirSync(SESSIONS_DIR, { recursive: true });
24987
+ const logFile = fs79.createWriteStream(record.logPath, { flags: "a" });
24962
24988
  logFile.on("error", () => {
24963
24989
  });
24964
- const eventsFile = fs77.createWriteStream(eventsPathOf(sessionId), { flags: "a" });
24990
+ const eventsFile = fs79.createWriteStream(eventsPathOf(sessionId), { flags: "a" });
24965
24991
  eventsFile.on("error", () => {
24966
24992
  });
24967
24993
  const event2 = (e) => eventsFile.write(`${JSON.stringify(e)}
@@ -24982,7 +25008,7 @@ async function watchRun(sessionId, resume = startResume) {
24982
25008
  if (active2.startNote) log.write(`[board] ${active2.startNote}
24983
25009
  `);
24984
25010
  const basePrompt = record.formatRepair ? contractRepairPrompt(requestOf(record), record.formatRepair.errors) : record.resumedFrom ? spec.prompt || resumePrompt(record.deliveryId, record.cardId, record.action) : spec.prompt;
24985
- const prompt = [basePrompt, discardedCardsPrompt(record.discardedCards)].filter(Boolean).join("\n\n");
25011
+ const prompt = inLanguage([basePrompt, discardedCardsPrompt(record.discardedCards)].filter(Boolean).join("\n\n"));
24986
25012
  const before = markBoard();
24987
25013
  const sources = snapshotSpecs();
24988
25014
  const wasBroken = new Set(boardComplaints());
@@ -24995,7 +25021,7 @@ async function watchRun(sessionId, resume = startResume) {
24995
25021
  void refreshCatalog();
24996
25022
  const client = active2.client;
24997
25023
  const restartBase = !client || record.formatRepair ? void 0 : record.resumedFrom ? restartPrompt(requestOf(record), record.deliveryId) : record.continues && !record.continues.fork ? spec.prompt : void 0;
24998
- const restart = restartBase ? [restartBase, discardedCardsPrompt(record.discardedCards)].filter(Boolean).join("\n\n") : void 0;
25024
+ const restart = restartBase ? inLanguage([restartBase, discardedCardsPrompt(record.discardedCards)].filter(Boolean).join("\n\n")) : void 0;
24999
25025
  const stdinPrompt = client ? void 0 : active2.stdinPrompt;
25000
25026
  const stdio = [client || stdinPrompt ? "pipe" : "ignore", "pipe", "pipe"];
25001
25027
  let child;
@@ -25444,11 +25470,11 @@ function tickedNoSetupStep(record) {
25444
25470
  const now = tickedSetupSteps();
25445
25471
  return now !== void 0 && now <= record.setupTicked;
25446
25472
  }
25447
- function settleBoard(run, changed, before) {
25473
+ function settleBoard(run2, changed, before) {
25448
25474
  try {
25449
- const helpers = specRunsAfter(readSpecAsks(run.sessionId));
25450
- const waitingForSpec = helpers.some((req) => req.id === run.cardId);
25451
- return refinementRunsAfter(run, changed, before, waitingForSpec, readSpecRefusals(run.sessionId));
25475
+ const helpers = specRunsAfter(readSpecAsks(run2.sessionId));
25476
+ const waitingForSpec = helpers.some((req) => req.id === run2.cardId);
25477
+ return refinementRunsAfter(run2, changed, before, waitingForSpec, readSpecRefusals(run2.sessionId));
25452
25478
  } catch {
25453
25479
  return null;
25454
25480
  }
@@ -25530,6 +25556,7 @@ var init_watch = __esm({
25530
25556
  init_log();
25531
25557
  init_wire();
25532
25558
  init_prompts();
25559
+ init_language();
25533
25560
  init_resolve();
25534
25561
  init_runner();
25535
25562
  init_retry();
@@ -25549,7 +25576,7 @@ var init_watch = __esm({
25549
25576
  });
25550
25577
 
25551
25578
  // ../cli/src/kanban.ts
25552
- import fs85 from "node:fs";
25579
+ import fs87 from "node:fs";
25553
25580
  import { fileURLToPath as fileURLToPath3 } from "node:url";
25554
25581
 
25555
25582
  // ../cli/src/lib/cli/agent.ts
@@ -25646,11 +25673,11 @@ ${final}` : lines;
25646
25673
  }
25647
25674
  function testConnection(pin) {
25648
25675
  const ask2 = !pin ? {} : runtimeById(pin) ? { pin } : { pin, harness: pin };
25649
- const run = openPlan(planRun(randomUUID7(), REPO_ROOT, void 0, ask2));
25676
+ const run2 = openPlan(planRun(randomUUID7(), REPO_ROOT, void 0, ask2));
25650
25677
  const startedAt = Date.now();
25651
- const [cmd, ...args] = run.argv;
25652
- const client = run.client;
25653
- const stdinPrompt = client ? void 0 : run.stdinPrompt;
25678
+ const [cmd, ...args] = run2.argv;
25679
+ const client = run2.client;
25680
+ const stdinPrompt = client ? void 0 : run2.stdinPrompt;
25654
25681
  const stdio = [client || stdinPrompt ? "pipe" : "ignore", "pipe", "pipe"];
25655
25682
  return new Promise((resolve2) => {
25656
25683
  let events = "";
@@ -25662,16 +25689,16 @@ function testConnection(pin) {
25662
25689
  if (settled) return;
25663
25690
  settled = true;
25664
25691
  stopTimer();
25665
- const output = run.startNote && res.output ? `${run.startNote}
25692
+ const output = run2.startNote && res.output ? `${run2.startNote}
25666
25693
  ${res.output}` : res.output;
25667
- resolve2({ ...res, output, harness: run.harness, ms: Date.now() - startedAt });
25694
+ resolve2({ ...res, output, harness: run2.harness, ms: Date.now() - startedAt });
25668
25695
  };
25669
25696
  const spawnFailed = (e) => {
25670
25697
  if (e?.code === "ENOENT") {
25671
- done2({ ok: false, missing: cmd, install: run.install });
25698
+ done2({ ok: false, missing: cmd, install: run2.install });
25672
25699
  return;
25673
25700
  }
25674
- done2({ ok: false, output: said2(events, run.renderer?.result(), `${stderr}
25701
+ done2({ ok: false, output: said2(events, run2.renderer?.result(), `${stderr}
25675
25702
  ${String(e)}`) });
25676
25703
  };
25677
25704
  const mark = stopMark();
@@ -25683,7 +25710,7 @@ ${String(e)}`) });
25683
25710
  // folder — an agent started there is outside the repo, and codex refuses to run at
25684
25711
  // all ("Not inside a trusted directory").
25685
25712
  cwd: REPO_ROOT,
25686
- env: markEnv(run.env, mark),
25713
+ env: markEnv(run2.env, mark),
25687
25714
  shell: false,
25688
25715
  // Same as a run: a piped stdin makes `claude -p` wait and then warn, which would
25689
25716
  // land in the output as if it were the failure — and it is a pipe, and stays open,
@@ -25705,18 +25732,18 @@ ${String(e)}`) });
25705
25732
  done2({
25706
25733
  ok: false,
25707
25734
  timedOut: true,
25708
- output: said2(events + (run.renderer?.flush() ?? ""), run.renderer?.result(), stderr + errs.flush())
25735
+ output: said2(events + (run2.renderer?.flush() ?? ""), run2.renderer?.result(), stderr + errs.flush())
25709
25736
  });
25710
25737
  }, TIMEOUT_MS);
25711
25738
  if (typeof timer.unref === "function") timer.unref();
25712
25739
  stopTimer = () => clearTimeout(timer);
25713
- const renderer = run.renderer;
25740
+ const renderer = run2.renderer;
25714
25741
  if (renderer) {
25715
25742
  child.stdout.on("data", (d) => {
25716
25743
  events += renderer.push(d.toString());
25717
25744
  });
25718
25745
  }
25719
- const errs = createStderrFilter(run.quietStderr);
25746
+ const errs = createStderrFilter(run2.quietStderr);
25720
25747
  child.stderr.on("data", (d) => {
25721
25748
  stderr += errs.push(d.toString());
25722
25749
  });
@@ -26053,7 +26080,7 @@ async function testAgent(args) {
26053
26080
 
26054
26081
  // ../cli/src/commands/cloud.ts
26055
26082
  import fs71 from "node:fs";
26056
- import path67 from "node:path";
26083
+ import path68 from "node:path";
26057
26084
 
26058
26085
  // ../cli/src/lib/cloud/avatar.ts
26059
26086
  init_session();
@@ -26077,9 +26104,9 @@ async function download(url) {
26077
26104
  if (!response.ok) return null;
26078
26105
  const type = response.headers.get("content-type")?.split(";")[0]?.trim() ?? "";
26079
26106
  if (!type.startsWith("image/")) return null;
26080
- const bytes = Buffer.from(await response.arrayBuffer());
26081
- if (bytes.byteLength === 0 || bytes.byteLength > MAX_BYTES2) return null;
26082
- return `data:${type};base64,${bytes.toString("base64")}`;
26107
+ const bytes3 = Buffer.from(await response.arrayBuffer());
26108
+ if (bytes3.byteLength === 0 || bytes3.byteLength > MAX_BYTES2) return null;
26109
+ return `data:${type};base64,${bytes3.toString("base64")}`;
26083
26110
  } catch {
26084
26111
  return null;
26085
26112
  }
@@ -26102,10 +26129,10 @@ init_home();
26102
26129
  init_config2();
26103
26130
  init_session();
26104
26131
  import fs70 from "node:fs";
26105
- import path66 from "node:path";
26132
+ import path67 from "node:path";
26106
26133
  var FRESH_MS = 10 * 6e4;
26107
26134
  var GRACE_MS = 7 * 24 * 60 * 6e4;
26108
- var heldFile = () => path66.join(machineHome(), "pro.json");
26135
+ var heldFile = () => path67.join(machineHome(), "pro.json");
26109
26136
  function forgetPro() {
26110
26137
  fs70.rmSync(heldFile(), { force: true });
26111
26138
  }
@@ -26126,18 +26153,18 @@ async function readCloudAccount() {
26126
26153
  configured: cloudConfigured()
26127
26154
  };
26128
26155
  if (!blank.configured) return { ...blank, message: NOT_CONFIGURED };
26129
- const token2 = await accessToken();
26130
- if (!token2.ok) {
26131
- if (token2.reason === "signed-out") return blank;
26132
- if (token2.reason === "unreachable") {
26133
- return { ...blank, ...held3(), state: "signed-in", error: token2.error };
26156
+ const token3 = await accessToken();
26157
+ if (!token3.ok) {
26158
+ if (token3.reason === "signed-out") return blank;
26159
+ if (token3.reason === "unreachable") {
26160
+ return { ...blank, ...held3(), state: "signed-in", error: token3.error };
26134
26161
  }
26135
26162
  return { ...blank, ...held3(), state: "expired" };
26136
26163
  }
26137
26164
  let response;
26138
26165
  try {
26139
26166
  response = await fetch(`${cloudEndpoints().api}/v1/session`, {
26140
- headers: { authorization: `Bearer ${token2.token}` }
26167
+ headers: { authorization: `Bearer ${token3.token}` }
26141
26168
  });
26142
26169
  } catch (e) {
26143
26170
  return {
@@ -26280,7 +26307,7 @@ async function cloudImage(opts, program2) {
26280
26307
  const prompt = opts.prompt.trim();
26281
26308
  if (!prompt) usage("Missing the prompt.", line2);
26282
26309
  const references = opts.refs.map((file) => {
26283
- const type = REF_TYPES[path67.extname(file).toLowerCase()];
26310
+ const type = REF_TYPES[path68.extname(file).toLowerCase()];
26284
26311
  if (!type) usage(`--ref ${file}: use a PNG, JPEG or WebP file.`, line2);
26285
26312
  try {
26286
26313
  return `data:${type};base64,${fs71.readFileSync(file).toString("base64")}`;
@@ -26361,13 +26388,13 @@ function refuse(m, fallback) {
26361
26388
  die([m.zh, m.en, fallback.zh, fallback.en].join("\n"), { kind: "cloud-refused", bare: true });
26362
26389
  }
26363
26390
  async function generate(g) {
26364
- const token2 = await accessToken();
26365
- if (!token2.ok) refuse(SAID[token2.reason], g.fallback);
26391
+ const token3 = await accessToken();
26392
+ if (!token3.ok) refuse(SAID[token3.reason], g.fallback);
26366
26393
  let res;
26367
26394
  try {
26368
26395
  res = await fetch(`${cloudEndpoints().api}${g.endpoint}`, {
26369
26396
  method: "POST",
26370
- headers: { authorization: `Bearer ${token2.token}`, "content-type": "application/json" },
26397
+ headers: { authorization: `Bearer ${token3.token}`, "content-type": "application/json" },
26371
26398
  body: JSON.stringify(g.body)
26372
26399
  });
26373
26400
  } catch {
@@ -26383,20 +26410,602 @@ async function generate(g) {
26383
26410
  if (res.status === 400) usage(error?.message ?? "The request was refused.", g.usage);
26384
26411
  refuse(g.failed, g.fallback);
26385
26412
  }
26386
- fs71.mkdirSync(path67.dirname(path67.resolve(g.out)), { recursive: true });
26413
+ fs71.mkdirSync(path68.dirname(path68.resolve(g.out)), { recursive: true });
26387
26414
  fs71.writeFileSync(g.out, Buffer.from(await res.arrayBuffer()));
26388
26415
  say(`${g.out} (${g.label(res)})`);
26389
26416
  return { out: g.out };
26390
26417
  }
26391
26418
 
26419
+ // ../cli/src/commands/cloud-import.ts
26420
+ import crypto3 from "node:crypto";
26421
+ import fs73 from "node:fs";
26422
+ import path70 from "node:path";
26423
+
26424
+ // ../cloud/src/board-files.ts
26425
+ var ROOTS = ["todo/", ".archive/", "memory/", "workflows/", "triage/", "assets/", "rules/"];
26426
+ var SETTINGS = ["modules.md", "config.md"];
26427
+ var isSettingsPath = (path83) => SETTINGS.includes(path83) || /^rules\/(?:workflows\/)?[^/]+\.md$/.test(path83);
26428
+ var BOARD_FILE_LIMIT = 256 * 1024;
26429
+ function isBoardPath(path83) {
26430
+ if (!path83 || path83.length > 500 || path83.startsWith("/") || path83.includes("\\") || path83.includes("\0")) return false;
26431
+ if (path83.split("/").some((part) => !part || part === "." || part === "..")) return false;
26432
+ if (path83 === "todo/README.md" || path83.split("/").includes(".sync")) return false;
26433
+ if (path83.startsWith("workflows/")) return true;
26434
+ if (SETTINGS.includes(path83)) return true;
26435
+ if (path83.startsWith("rules/")) return isSettingsPath(path83);
26436
+ if (path83.startsWith("assets/")) return path83.endsWith(".json");
26437
+ return ROOTS.some((root) => path83.startsWith(root)) && path83.endsWith(".md");
26438
+ }
26439
+ var workflowFolder = (id) => id.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
26440
+
26441
+ // ../cloud/src/v1/workflow-files.ts
26442
+ init_workflow_def();
26443
+ init_parse();
26444
+ var WORKFLOW_FILE2 = "WORKFLOW.md";
26445
+ var MCP_FILE = "mcp.json";
26446
+ var KB = 1024;
26447
+ var FILE_LIMITS = { file: 256 * KB, total: 2 * KB * KB, count: 500, path: 512 };
26448
+ var LAND_ROLES2 = ["builder"];
26449
+ var SEGMENT2 = /^[\w.-]+$/;
26450
+ var bytes = (text5) => new TextEncoder().encode(text5).length;
26451
+ function folderPath(file, top) {
26452
+ const parts = file.split("/");
26453
+ if (parts.length < 3 || parts[0] !== top || !AGENT_NAME.test(parts[1])) return null;
26454
+ if (parts.slice(2).some((p) => !SEGMENT2.test(p) || p === "." || p === "..")) return null;
26455
+ return [parts[1], parts.slice(2).join("/")];
26456
+ }
26457
+ function mcpProblem(text5) {
26458
+ let read6;
26459
+ try {
26460
+ read6 = JSON.parse(text5);
26461
+ } catch {
26462
+ return "is not JSON";
26463
+ }
26464
+ const servers = read6?.mcpServers;
26465
+ if (!servers || typeof servers !== "object" || Array.isArray(servers)) return "must hold `mcpServers`, an object of name \u2192 server";
26466
+ for (const [name, server] of Object.entries(servers)) {
26467
+ const { command, args, ...rest } = server ?? {};
26468
+ if (!AGENT_NAME.test(name)) return `names a server \`${name}\`; use lowercase letters, digits and dashes`;
26469
+ if (typeof command !== "string" || !command) return `server \`${name}\` needs a \`command\``;
26470
+ if (args !== void 0 && !(Array.isArray(args) && args.every((a) => typeof a === "string"))) return `server \`${name}\`: \`args\` must be a list of strings`;
26471
+ if (Object.keys(rest).length) return `server \`${name}\` takes only \`command\` and \`args\``;
26472
+ }
26473
+ return null;
26474
+ }
26475
+ function checkWorkflowFiles(value, official = false) {
26476
+ const fail = (file, reason, tooLarge = false) => ({ ok: false, file, reason, ...tooLarge ? { tooLarge } : {} });
26477
+ if (!value || typeof value !== "object" || Array.isArray(value)) return fail("files", "must be an object of path \u2192 text");
26478
+ const entries = Object.entries(value);
26479
+ if (entries.length > FILE_LIMITS.count) return fail("files", `holds at most ${FILE_LIMITS.count} files`, true);
26480
+ let total = 0;
26481
+ const folders2 = /* @__PURE__ */ new Map();
26482
+ const skills = /* @__PURE__ */ new Set();
26483
+ for (const [file, text5] of entries) {
26484
+ if (typeof text5 !== "string" || text5.includes("\0")) return fail(file, "is not a text file");
26485
+ if (file.length > FILE_LIMITS.path) return fail(file, `the path is longer than ${FILE_LIMITS.path} characters`);
26486
+ const size = bytes(text5);
26487
+ if (size > FILE_LIMITS.file) return fail(file, `is larger than ${FILE_LIMITS.file / KB} KB`, true);
26488
+ total += size;
26489
+ if (total > FILE_LIMITS.total) return fail("files", `are larger than ${FILE_LIMITS.total / KB / KB} MB together`, true);
26490
+ if (file === WORKFLOW_FILE2) continue;
26491
+ if (official && file === MCP_FILE) {
26492
+ const problem = mcpProblem(text5);
26493
+ if (problem) return fail(file, problem);
26494
+ continue;
26495
+ }
26496
+ if (official && file.startsWith("skills/")) {
26497
+ if (!folderPath(file, "skills")) return fail(file, "is not inside skills/<name>/");
26498
+ skills.add(file.split("/")[1]);
26499
+ continue;
26500
+ }
26501
+ const at = folderPath(file, "agents");
26502
+ if (!at) return fail(file, `is neither ${WORKFLOW_FILE2} nor inside agents/<name>/`);
26503
+ if (!folders2.has(at[0])) folders2.set(at[0], /* @__PURE__ */ new Map());
26504
+ folders2.get(at[0]).set(at[1], text5);
26505
+ }
26506
+ for (const name of skills) {
26507
+ if (value[`skills/${name}/SKILL.md`] === void 0) return fail(`skills/${name}/SKILL.md`, "is missing");
26508
+ }
26509
+ const source = value[WORKFLOW_FILE2];
26510
+ if (source === void 0) return fail(WORKFLOW_FILE2, "is missing");
26511
+ const read6 = parseWorkflowFile("workflow", source, WORKFLOW_FILE2);
26512
+ if ("problem" in read6) return fail(WORKFLOW_FILE2, read6.problem.replace(`${WORKFLOW_FILE2}: `, ""));
26513
+ const def = read6.def;
26514
+ const agents = /* @__PURE__ */ new Map();
26515
+ for (const [name, own] of folders2) {
26516
+ const from = `agents/${name}/AGENT.md`;
26517
+ const text5 = own.get("AGENT.md");
26518
+ if (text5 === void 0) return fail(from, "is missing");
26519
+ const parsed = parseSpecAgent(text5, from, (p) => own.get(p) ?? null, false, () => [...own.keys()].filter((p) => p !== "AGENT.md"));
26520
+ if ("problem" in parsed) return fail(from, parsed.problem.replace(`${from}: `, ""));
26521
+ if (parsed.agent.name !== name) return fail(from, `calls itself \`${parsed.agent.name}\`; it must match its folder`);
26522
+ agents.set(name, parsed.agent);
26523
+ }
26524
+ const named2 = [
26525
+ ...def.previews.map((h) => [h.agent, "it previews with"]),
26526
+ ...def.land.filter((h) => !LAND_ROLES2.includes(h.agent)).map((h) => [h.agent, "it lands with"]),
26527
+ ...def.scheduled.map((s) => [s.agent, "it schedules"])
26528
+ ];
26529
+ for (const [name, why] of named2) {
26530
+ if (!agents.has(name)) return fail(WORKFLOW_FILE2, `${why} \`${name}\`, which has no agents/${name}/AGENT.md`);
26531
+ }
26532
+ return { ok: true, def, files: value };
26533
+ }
26534
+
26535
+ // ../cli/src/commands/cloud-import.ts
26536
+ init_bundled();
26537
+
26538
+ // ../cli/src/lib/agents/shared-memory.ts
26539
+ init_bundled();
26540
+ init_parse();
26541
+ import fs72 from "node:fs";
26542
+ import path69 from "node:path";
26543
+ function sharedMemory(board2) {
26544
+ const declared = /* @__PURE__ */ new Map();
26545
+ const take = (folder, text5, builtIn) => {
26546
+ if (text5 === null || declared.has(folder)) return;
26547
+ const read6 = parseSpecAgent(text5, folder, () => null, builtIn);
26548
+ if ("agent" in read6 && read6.agent.name === folder) declared.set(folder, read6.agent.memoryFiles.workspace);
26549
+ };
26550
+ for (const [key, text5] of Object.entries(BUNDLED_AGENT_FILES)) {
26551
+ const at = /^([^/]+)\/AGENT\.md$/.exec(key);
26552
+ if (at) take(at[1], text5, true);
26553
+ }
26554
+ const root = path69.join(board2, "agents");
26555
+ let folders2 = [];
26556
+ try {
26557
+ folders2 = fs72.readdirSync(root, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name);
26558
+ } catch {
26559
+ }
26560
+ for (const folder of folders2.sort()) {
26561
+ let text5 = null;
26562
+ try {
26563
+ text5 = fs72.readFileSync(path69.join(root, folder, "AGENT.md"), "utf8");
26564
+ } catch {
26565
+ }
26566
+ take(folder, text5, false);
26567
+ }
26568
+ const matches = [...declared].flatMap(([agent, paths]) => paths.map((p) => matcher(`agents/${agent}/${p}`)));
26569
+ return (name) => matches.some((m) => m(name));
26570
+ }
26571
+ function matcher(entry) {
26572
+ if (entry.endsWith("/")) return (name) => name.startsWith(entry);
26573
+ const re = new RegExp(`^${entry.split("*").map((part) => part.replace(/[.+?^${}()|[\]\\]/g, "\\$&")).join("[^/]*")}$`);
26574
+ return (name) => re.test(name);
26575
+ }
26576
+
26577
+ // ../cli/src/commands/cloud-import.ts
26578
+ init_workflows();
26579
+ init_cards();
26580
+ init_config2();
26581
+ init_tasks();
26582
+ init_session();
26583
+ init_frontmatter();
26584
+ init_io();
26585
+ init_paths();
26586
+ init_questions();
26587
+ var BATCH = 50;
26588
+ var TITLE_LIMIT = 200;
26589
+ var APP_URL = "https://app.wildcrane.ai";
26590
+ var BOUNDARY = "<!-- agent -->";
26591
+ var Refused = class extends Error {
26592
+ constructor(message, code) {
26593
+ super(message);
26594
+ this.code = code;
26595
+ }
26596
+ code;
26597
+ };
26598
+ var token2 = "";
26599
+ async function api(method, route2, body, raw) {
26600
+ let res;
26601
+ try {
26602
+ res = await fetch(`${cloudEndpoints().api}${route2}`, {
26603
+ method,
26604
+ headers: {
26605
+ authorization: `Bearer ${token2}`,
26606
+ ...raw ? { "content-type": raw.type } : body !== void 0 ? { "content-type": "application/json" } : {}
26607
+ },
26608
+ ...raw ? { body: raw.bytes } : body !== void 0 ? { body: JSON.stringify(body) } : {},
26609
+ signal: AbortSignal.timeout(12e4)
26610
+ });
26611
+ } catch (e) {
26612
+ throw new Refused(`Cloud could not be reached: ${e instanceof Error ? e.message : String(e)}`);
26613
+ }
26614
+ const parsed = await res.json().catch(() => ({}));
26615
+ if (!res.ok) throw new Refused(parsed.error?.message ?? `Cloud answered ${res.status}.`, parsed.error?.code);
26616
+ return parsed;
26617
+ }
26618
+ async function all(route2) {
26619
+ const out = [];
26620
+ let after2 = null;
26621
+ do {
26622
+ const sep = route2.includes("?") ? "&" : "?";
26623
+ const page = await api("GET", `${route2}${sep}limit=100${after2 ? `&after=${encodeURIComponent(after2)}` : ""}`);
26624
+ out.push(...page.data);
26625
+ after2 = page.next;
26626
+ } while (after2);
26627
+ return out;
26628
+ }
26629
+ async function findProject(asked) {
26630
+ if (/^prj_/.test(asked)) return api("GET", `/v1/projects/${encodeURIComponent(asked)}`);
26631
+ const workspaces = await all("/v1/workspaces");
26632
+ for (const ws of workspaces) {
26633
+ const found2 = (await all(`/v1/projects?workspace=${encodeURIComponent(ws.id)}`)).find((p) => p.prefix.toLowerCase() === asked.toLowerCase());
26634
+ if (found2) return found2;
26635
+ }
26636
+ throw new Refused(`No project of yours has the id or prefix "${asked}".`, "not_found");
26637
+ }
26638
+ var read = (file) => fs73.readFileSync(file, "utf8");
26639
+ var bytes2 = (text5) => Buffer.byteLength(text5, "utf8");
26640
+ function files(dir) {
26641
+ if (!fs73.existsSync(dir)) return [];
26642
+ const out = [];
26643
+ for (const entry of fs73.readdirSync(dir, { withFileTypes: true })) {
26644
+ if (entry.name.startsWith(".")) continue;
26645
+ const full = path70.join(dir, entry.name);
26646
+ if (entry.isDirectory()) out.push(...files(full));
26647
+ else if (entry.isFile()) out.push(full);
26648
+ }
26649
+ return out;
26650
+ }
26651
+ var posix2 = (p) => p.split(path70.sep).join("/");
26652
+ function openCards() {
26653
+ const out = [];
26654
+ for (const file of walkMd(TODO)) {
26655
+ const rel2 = posix2(path70.relative(TODO, file));
26656
+ if (rel2 === "README.md") continue;
26657
+ const parts = rel2.split("/");
26658
+ const text5 = read(file);
26659
+ const meta = parseFrontmatter(text5).meta;
26660
+ if (!meta) continue;
26661
+ const name = parts[parts.length - 1];
26662
+ const root = name === "root.md" && parts.length === 2;
26663
+ const id = root ? idPrefix(parts[0]) : idPrefix(name);
26664
+ if (id === null) continue;
26665
+ const group = !root && parts.length === 2 ? idPrefix(parts[0]) : null;
26666
+ const slug2 = (root ? parts[0] : name.replace(/\.md$/, "")).replace(/^\d+-?/, "");
26667
+ out.push({ id, rel: rel2, text: text5, meta, group, root, slug: slug2 });
26668
+ }
26669
+ return out.sort((a, b) => a.id - b.id);
26670
+ }
26671
+ function bodyProblems(text5) {
26672
+ const out = [];
26673
+ if (bytes2(text5) > BOARD_FILE_LIMIT) out.push({ zh: "\u8D85\u8FC7 256 KB", en: "over 256 KB" });
26674
+ let fence = null;
26675
+ let boundaries = 0;
26676
+ for (const line2 of text5.split("\n")) {
26677
+ const mark = /^ {0,3}(`{3,}|~{3,})(.*)$/.exec(line2);
26678
+ const run2 = mark?.[1] ?? "";
26679
+ if (fence) {
26680
+ if (run2[0] === fence.char && run2.length >= fence.size && !mark?.[2]?.trim()) fence = null;
26681
+ } else if (run2) fence = { char: run2[0], size: run2.length };
26682
+ else if (line2.trim() === BOUNDARY) boundaries++;
26683
+ }
26684
+ if (fence) out.push({ zh: "\u4EE3\u7801\u5757\u672A\u95ED\u5408", en: "a code fence is never closed" });
26685
+ if (boundaries > 1) out.push({ zh: `\`${BOUNDARY}\` \u51FA\u73B0\u591A\u4E8E\u4E00\u6B21`, en: `holds \`${BOUNDARY}\` more than once` });
26686
+ return out;
26687
+ }
26688
+ function packWorkflow(flow) {
26689
+ const own = path70.join(WORKFLOWS, flow.id);
26690
+ const out = {};
26691
+ for (const file of files(own)) out[posix2(path70.relative(own, file))] = read(file);
26692
+ const named2 = [...flow.previews, ...flow.land, ...flow.scheduled].map((h) => h.agent).filter((a) => !LAND_ROLES.includes(a));
26693
+ for (const agent of new Set(named2)) {
26694
+ const dir = path70.join(AGENTS, agent);
26695
+ if (fs73.existsSync(path70.join(dir, "AGENT.md"))) {
26696
+ for (const file of files(dir)) out[`agents/${agent}/${posix2(path70.relative(dir, file))}`] = read(file);
26697
+ continue;
26698
+ }
26699
+ for (const [key, text5] of Object.entries(BUNDLED_AGENT_FILES)) {
26700
+ if (key.startsWith(`${agent}/`)) out[`agents/${key}`] = text5;
26701
+ }
26702
+ }
26703
+ return out;
26704
+ }
26705
+ var builtinId = (id) => `wf_builtin_${id.replace(/-/g, "_")}`;
26706
+ function rewriteRefs(text5, numbers) {
26707
+ const swap = (s) => s.replace(/(^|[^\w#&/])#(\d+)\b/g, (_, lead, n) => {
26708
+ const to = numbers.get(Number(n));
26709
+ return to === void 0 ? `${lead}old#${n}` : `${lead}#${to}`;
26710
+ });
26711
+ let fence = null;
26712
+ return text5.split("\n").map((line2) => {
26713
+ const mark = /^ {0,3}(`{3,}|~{3,})(.*)$/.exec(line2);
26714
+ const run2 = mark?.[1] ?? "";
26715
+ if (fence) {
26716
+ if (run2[0] === fence.char && run2.length >= fence.size && !mark?.[2]?.trim()) fence = null;
26717
+ return line2;
26718
+ }
26719
+ if (run2) {
26720
+ fence = { char: run2[0], size: run2.length };
26721
+ return line2;
26722
+ }
26723
+ return line2.split(/(`+[^`]*`+)/).map((part, i) => i % 2 ? part : swap(part)).join("");
26724
+ }).join("\n");
26725
+ }
26726
+ function cardForCloud(text5, numbers, workflow) {
26727
+ const end2 = /^---\r?\n[\s\S]*?\r?\n---(?:\r?\n|$)/.exec(text5)?.[0].length ?? 0;
26728
+ const front = end2 ? text5.slice(0, end2) : "";
26729
+ const lines = rewriteRefs(front, numbers).split("\n").map((line2) => {
26730
+ const list = /^(blocked_by|related):\s*\[(.*)\]\s*$/.exec(line2);
26731
+ if (list) {
26732
+ const kept = list[2].split(",").map((s) => numbers.get(Number(s.trim()))).filter((n) => n !== void 0);
26733
+ return `${list[1]}: [${kept.join(", ")}]`;
26734
+ }
26735
+ if (/^status:\s*implementing\s*$/.test(line2)) return "status: ready";
26736
+ if (workflow && /^workflow:/.test(line2)) return `workflow: ${workflow}`;
26737
+ return line2;
26738
+ });
26739
+ return lines.join("\n") + rewriteRefs(text5.slice(end2), numbers);
26740
+ }
26741
+ function reviewOf2(meta) {
26742
+ const questions = meta.questions ?? [];
26743
+ let open3 = false;
26744
+ for (const [i, q] of questions.entries()) {
26745
+ const { tag, text: text5 } = parseQuestion(q.text);
26746
+ if (tag !== "user" || !openOf([q]).length) continue;
26747
+ open3 = true;
26748
+ if (q.options?.length) return { question: text5, options: q.options.slice(0, 8), position: i + 1 };
26749
+ }
26750
+ return open3 || meta.status === "ready" || meta.status === "implementing" ? true : null;
26751
+ }
26752
+ function draftsOf(text5) {
26753
+ const out = /* @__PURE__ */ new Map();
26754
+ for (const m of text5.matchAll(/\]\(\s*<?([^)\s>]+)>?(?:\s+"[^"]*")?\s*\)|\bsrc=["']([^"']+)["']/g)) {
26755
+ const name = m[1] ?? m[2];
26756
+ const at = /^\.(assets|mockups)\/(.+)$/.exec(name);
26757
+ if (!at) continue;
26758
+ const base = at[1] === "assets" ? ASSETS : MOCKUPS;
26759
+ const file = path70.resolve(base, at[2]);
26760
+ if (file.startsWith(base + path70.sep) && fs73.statSync(file, { throwIfNoEntry: false })?.isFile()) out.set(name, file);
26761
+ }
26762
+ return [...out].map(([name, file]) => ({ name, file }));
26763
+ }
26764
+ var TYPES = {
26765
+ ".png": "image/png",
26766
+ ".jpg": "image/jpeg",
26767
+ ".jpeg": "image/jpeg",
26768
+ ".gif": "image/gif",
26769
+ ".webp": "image/webp",
26770
+ ".svg": "image/svg+xml",
26771
+ ".mp4": "video/mp4",
26772
+ ".webm": "video/webm",
26773
+ ".mp3": "audio/mpeg",
26774
+ ".pdf": "application/pdf",
26775
+ ".html": "text/html",
26776
+ ".md": "text/markdown",
26777
+ ".mov": "video/quicktime",
26778
+ ".wav": "audio/wav",
26779
+ ".json": "application/json"
26780
+ };
26781
+ function kindOf(ext) {
26782
+ if (ext === ".html") return "html";
26783
+ if (ext === ".md" || ext === ".mdx") return "markdown";
26784
+ const type = TYPES[ext] ?? "";
26785
+ if (type.startsWith("image/")) return "image";
26786
+ return type.startsWith("video/") || type.startsWith("audio/") ? "video" : "asset";
26787
+ }
26788
+ async function uploadDrafts(task, drafts) {
26789
+ const card = await api("GET", `/v1/tasks/${task}/card`);
26790
+ if (Object.keys(card.assets ?? {}).length) return 0;
26791
+ const assets = {};
26792
+ for (const { name, file } of drafts) {
26793
+ if (!card.body.includes(name)) continue;
26794
+ const ext = path70.extname(file).toLowerCase();
26795
+ const type = TYPES[ext] ?? "application/octet-stream";
26796
+ const data = fs73.readFileSync(file);
26797
+ const artifact = await api("POST", `/v1/tasks/${task}/artifacts`, { type: kindOf(ext), name: name.slice(0, 200) });
26798
+ const upload = await api("POST", `/v1/artifacts/${artifact.id}/uploads`, { content_type: type, size: Math.max(data.length, 1) });
26799
+ await api("PUT", `/v1/uploads/${upload.id}`, void 0, { bytes: data, type });
26800
+ const version = await api("POST", `/v1/artifacts/${artifact.id}/versions`, { upload: upload.id });
26801
+ assets[name] = { artifact: artifact.id, version: version.version };
26802
+ }
26803
+ if (!Object.keys(assets).length) return 0;
26804
+ await api("PUT", `/v1/tasks/${task}/card`, { body: card.body, base_version: card.version, assets });
26805
+ return Object.keys(assets).length;
26806
+ }
26807
+ function stop(lines) {
26808
+ die(lines.join("\n"), { kind: "cloud-refused", bare: true });
26809
+ }
26810
+ async function cloudImport(opts, program2) {
26811
+ if (!opts.project) {
26812
+ die(`Missing --project.
26813
+ usage: ${program2} cloud import --project <id or prefix> [--dry-run]`, { kind: "bad-args", bare: true, exitCode: 2 });
26814
+ }
26815
+ const held4 = await accessToken();
26816
+ if (!held4.ok) {
26817
+ stop(held4.reason === "unreachable" ? ["\u65E0\u6CD5\u8FDE\u63A5 WildCrane Cloud\u3002\u8BF7\u68C0\u67E5\u7F51\u7EDC\u540E\u91CD\u8BD5\u3002", "Could not reach WildCrane Cloud. Check your connection and try again."] : [
26818
+ "\u672A\u767B\u5F55 WildCrane Cloud\u3002\u8BF7\u5728 WildCrane \u5E94\u7528\u7684\u201C\u914D\u7F6E \u2192 Cloud\u201D\u4E2D\u767B\u5F55\u540E\u91CD\u8BD5\u3002",
26819
+ "Not signed in to WildCrane Cloud. Sign in from Configuration \u2192 Cloud in the WildCrane app, then try again."
26820
+ ]);
26821
+ }
26822
+ token2 = held4.token;
26823
+ try {
26824
+ return await run(opts.project, !!opts.dryRun);
26825
+ } catch (e) {
26826
+ if (!(e instanceof Refused)) throw e;
26827
+ stop([
26828
+ "\u5BFC\u5165\u5DF2\u505C\u6B62\uFF0CCloud \u62D2\u7EDD\u4E86\u8BF7\u6C42\uFF1A",
26829
+ "The import stopped; Cloud refused it:",
26830
+ ` ${e.message}`,
26831
+ "\u4FEE\u6B63\u540E\u91CD\u65B0\u8FD0\u884C\uFF0C\u5DF2\u5BFC\u5165\u7684\u5185\u5BB9\u4E0D\u4F1A\u91CD\u590D\u3002",
26832
+ "Fix it and run again; what was imported is not imported twice."
26833
+ ]);
26834
+ }
26835
+ }
26836
+ async function run(asked, dryRun) {
26837
+ const project = await findProject(asked);
26838
+ const cloudFlows = await all(`/v1/workflows?workspace=${encodeURIComponent(project.workspace)}`);
26839
+ const board2 = crypto3.createHash("sha256").update(fs73.realpathSync(KANBAN)).digest("hex").slice(0, 16);
26840
+ const cards = openCards();
26841
+ const problems = [];
26842
+ const where = (file) => posix2(path70.relative(KANBAN, file));
26843
+ const own = workflows().filter((w) => !w.builtIn);
26844
+ const packed = /* @__PURE__ */ new Map();
26845
+ for (const flow of own) {
26846
+ const files2 = packWorkflow(flow);
26847
+ const checked = checkWorkflowFiles(files2);
26848
+ if (!checked.ok) problems.push({ file: `workflows/${flow.id}`, reason: { zh: `${checked.file} ${checked.reason}`, en: `${checked.file} ${checked.reason}` } });
26849
+ else packed.set(flow.id, files2);
26850
+ }
26851
+ for (const card of cards) {
26852
+ const file = `todo/${card.rel}`;
26853
+ for (const reason of bodyProblems(card.text)) problems.push({ file, reason });
26854
+ if (!card.meta.title.trim()) problems.push({ file, reason: { zh: "\u6CA1\u6709\u6807\u9898", en: "has no title" } });
26855
+ if (bytes2(card.meta.title.trim()) > TITLE_LIMIT) problems.push({ file, reason: { zh: `\u6807\u9898\u8D85\u8FC7 ${TITLE_LIMIT} \u5B57\u8282`, en: `the title is over ${TITLE_LIMIT} bytes` } });
26856
+ const wf = card.meta.workflow || "coding";
26857
+ if (isBuiltinWorkflow(wf)) {
26858
+ if (!cloudFlows.some((w) => w.id === builtinId(wf))) {
26859
+ problems.push({ file, reason: { zh: `workflow \`${wf}\` \u672A\u5728\u8BE5 workspace \u5B89\u88C5\uFF0C\u8BF7\u5148\u5728 Cloud \u7684 Workflows \u9875\u5B89\u88C5`, en: `workflow \`${wf}\` is not installed in this workspace; install it from Workflows in Cloud first` } });
26860
+ }
26861
+ } else if (!workflowById(wf)) {
26862
+ problems.push({ file, reason: { zh: `\u672A\u77E5\u7684 workflow \`${wf}\``, en: `unknown workflow \`${wf}\`` } });
26863
+ }
26864
+ }
26865
+ const shared = sharedMemory(KANBAN);
26866
+ const root = `projects/${project.id}/`;
26867
+ const boardFiles = [];
26868
+ for (const file of [...files(MEMORY), ...files(RULES), CONFIG, MODULES_MD]) {
26869
+ if (!fs73.existsSync(file)) continue;
26870
+ const rel2 = where(file);
26871
+ if (!isBoardPath(rel2)) continue;
26872
+ const content = read(file);
26873
+ if (bytes2(content) > BOARD_FILE_LIMIT) {
26874
+ problems.push({ file: rel2, reason: { zh: "\u8D85\u8FC7 256 KB", en: "over 256 KB" } });
26875
+ continue;
26876
+ }
26877
+ const isShared = rel2.startsWith("memory/") && shared(rel2.slice("memory/".length));
26878
+ boardFiles.push({ local: rel2, path: isShared ? rel2 : root + rel2, content });
26879
+ }
26880
+ const listed2 = await all(
26881
+ `/v1/tasks?project=${project.id}&workspace=${encodeURIComponent(project.workspace)}`
26882
+ );
26883
+ if (listed2.some((t) => t.source_context?.import?.board !== board2)) {
26884
+ problems.push({ file: `${project.name} (${project.prefix})`, reason: { zh: "\u8BE5\u9879\u76EE\u5DF2\u6709\u81EA\u5DF1\u7684\u4EFB\u52A1\uFF0C\u8BF7\u5BFC\u5165\u4E00\u4E2A\u7A7A\u9879\u76EE", en: "this project already has tasks of its own; import into an empty project" } });
26885
+ }
26886
+ const left = leftLocal(own);
26887
+ if (problems.length) {
26888
+ stop([
26889
+ "\u5BFC\u5165\u524D\u68C0\u67E5\u53D1\u73B0\u95EE\u9898\uFF0C\u4EC0\u4E48\u90FD\u6CA1\u6709\u5199\u5165\uFF1A",
26890
+ "The check before importing found problems; nothing was written:",
26891
+ ...problems.map((p) => ` ${p.file}: ${p.reason.zh} / ${p.reason.en}`)
26892
+ ]);
26893
+ }
26894
+ const reviews = cards.filter((c) => reviewOf2(c.meta));
26895
+ if (dryRun) {
26896
+ const wouldShare = boardFiles.filter((f) => !f.path.startsWith(root)).length;
26897
+ say(`\u9884\u68C0\u901A\u8FC7 \xB7 Check passed \u2014 ${project.name} (${project.prefix})`);
26898
+ say(` \u5361\u7247 Cards: ${cards.length}\uFF08\u5F85\u5BA1 on review: ${reviews.length}\uFF09`);
26899
+ say(` \u81EA\u5EFA workflow Own workflows: ${own.length}`);
26900
+ say(` \u9879\u76EE\u6587\u4EF6 Project files: ${boardFiles.length - wouldShare} \xB7 workspace \u8BB0\u5FC6 Workspace memory: ${wouldShare}`);
26901
+ cards.forEach((c, i) => say(` #${c.id} \u2192 ${project.prefix}-${i + 1} ${c.meta.title}${reviewOf2(c.meta) ? " [\u5F85\u5BA1 review]" : ""}`));
26902
+ sayLeft(left);
26903
+ return { dryRun: true, cards: cards.length, reviews: reviews.length, workflows: own.length, files: boardFiles.length, left };
26904
+ }
26905
+ const cloudOf = /* @__PURE__ */ new Map();
26906
+ for (const flow of own) {
26907
+ const found2 = cloudFlows.find((w) => !w.builtin && w.name === flow.name);
26908
+ const id = found2?.id ?? (await api("POST", "/v1/workflows", { files: packed.get(flow.id), workspace: project.workspace })).id;
26909
+ cloudOf.set(flow.id, id);
26910
+ }
26911
+ const { next } = await api("POST", `/v1/projects/${project.id}/import`, { board: board2, cards: [] });
26912
+ const numbers = /* @__PURE__ */ new Map();
26913
+ for (const t of listed2) {
26914
+ const at = t.source_context?.import;
26915
+ if (at?.board === board2 && typeof at.number === "number") numbers.set(at.number, numberOf(t.key));
26916
+ }
26917
+ let n = next;
26918
+ for (const c of cards) if (!numbers.has(c.id)) numbers.set(c.id, n++);
26919
+ const ruleFile2 = (rel2) => {
26920
+ const at = /^rules\/workflows\/([^/]+)\.md$/.exec(rel2);
26921
+ const id = at && cloudOf.get(at[1]);
26922
+ return id ? `rules/workflows/${workflowFolder(id)}.md` : rel2;
26923
+ };
26924
+ const sent = boardFiles.map((f) => ({ path: f.path.startsWith(root) ? root + ruleFile2(f.local) : f.path, content: f.content }));
26925
+ const pathOf = (c) => {
26926
+ const at = numbers.get(c.id);
26927
+ if (c.root) return `todo/${at}-${c.slug || "group"}/root.md`;
26928
+ const group = c.group === null ? null : cards.find((x) => x.root && x.id === c.group);
26929
+ const name = `${at}-${c.slug || "task"}.md`;
26930
+ return group ? `todo/${numbers.get(group.id)}-${group.slug || "group"}/${name}` : `todo/${name}`;
26931
+ };
26932
+ const made = [];
26933
+ const kept = [];
26934
+ for (let i = 0; i < Math.max(cards.length, 1); i += BATCH) {
26935
+ const batch = cards.slice(i, i + BATCH).map((c) => {
26936
+ const wf = c.meta.workflow || "coding";
26937
+ const cloud = cloudOf.get(wf);
26938
+ const review = reviewOf2(c.meta);
26939
+ return {
26940
+ ref: c.id,
26941
+ number: numbers.get(c.id),
26942
+ title: c.meta.title.trim(),
26943
+ workflow: cloud ?? builtinId(wf),
26944
+ path: pathOf(c),
26945
+ body: cardForCloud(c.text, numbers, cloud ? workflowFolder(cloud) : null),
26946
+ ...review ? { review } : {}
26947
+ };
26948
+ });
26949
+ const done2 = await api(
26950
+ "POST",
26951
+ `/v1/projects/${project.id}/import`,
26952
+ { board: board2, cards: batch, ...i === 0 ? { files: sent } : {} }
26953
+ );
26954
+ made.push(...done2.cards);
26955
+ kept.push(...done2.kept);
26956
+ }
26957
+ let drafts = 0;
26958
+ for (const c of reviews) {
26959
+ const task = made.find((m) => m.ref === c.id)?.task;
26960
+ const named2 = draftsOf(c.text);
26961
+ if (task && named2.length) drafts += await uploadDrafts(task, named2);
26962
+ }
26963
+ const memory = sent.filter((f) => /(^|\/)memory\//.test(f.path)).length - kept.length;
26964
+ say(`\u5DF2\u5BFC\u5165\u5230 ${project.name} \xB7 Imported into ${project.name} (${project.prefix})`);
26965
+ say(` \u5361\u7247 Cards: ${made.length}\uFF08\u5F85\u5BA1 on review: ${reviews.length}\uFF09`);
26966
+ say(` \u81EA\u5EFA workflow Own workflows: ${cloudOf.size}`);
26967
+ say(` \u8BB0\u5FC6 Memory files: ${memory}${drafts ? ` \xB7 \u8349\u7A3F Drafts: ${drafts}` : ""}`);
26968
+ if (kept.length) {
26969
+ say(" \u8FD9\u4E9B workspace \u8BB0\u5FC6 Cloud \u5DF2\u6709\uFF0C\u4FDD\u7559\u4E86 Cloud \u7684\u7248\u672C \xB7 Kept Cloud\u2019s copy of these workspace memory files:");
26970
+ for (const k of kept) say(` ${k}`);
26971
+ }
26972
+ say(` ${APP_URL}/projects/${project.id}`);
26973
+ sayLeft(left);
26974
+ say("\u672C\u5730\u770B\u677F\u5DF2\u4E0D\u518D\u751F\u6548\uFF0C\u6838\u5BF9\u65E0\u8BEF\u540E\u8BF7\u81EA\u884C\u5220\u9664 `docs/kanban/`\u3002");
26975
+ say("This local board is no longer used. Once you have checked Cloud, delete `docs/kanban/` yourself.");
26976
+ return { project: project.id, cards: made, workflows: [...cloudOf.values()], memory, drafts, kept, left };
26977
+ }
26978
+ function leftLocal(own) {
26979
+ const out = [];
26980
+ const count4 = (dir) => fs73.existsSync(dir) ? walkMd(dir).length : 0;
26981
+ const archived = count4(ARCHIVE);
26982
+ if (archived) out.push(`.archive/ (${archived})`);
26983
+ const triage = count4(TRIAGE);
26984
+ if (triage) out.push(`triage/ (${triage})`);
26985
+ if (fs73.existsSync(DELIVERIES)) out.push("deliveries/");
26986
+ if (fs73.existsSync(path70.join(KANBAN, "metrics.csv"))) out.push("metrics.csv");
26987
+ const named2 = new Set(own.flatMap((w) => [...w.previews, ...w.land, ...w.scheduled].map((h) => h.agent)));
26988
+ if (fs73.existsSync(AGENTS)) {
26989
+ for (const e of fs73.readdirSync(AGENTS, { withFileTypes: true })) {
26990
+ if (e.isDirectory() && !e.name.startsWith(".") && !named2.has(e.name)) out.push(`agents/${e.name}/`);
26991
+ }
26992
+ }
26993
+ return out;
26994
+ }
26995
+ function sayLeft(left) {
26996
+ if (!left.length) return;
26997
+ say(" \u7559\u5728\u672C\u5730\u3001\u672A\u5BFC\u5165 \xB7 Left on this computer, not imported:");
26998
+ for (const l of left) say(` ${l}`);
26999
+ }
27000
+
26392
27001
  // ../cli/src/commands/cloud-template.ts
26393
27002
  init_config2();
26394
27003
  init_session();
26395
27004
  init_io();
26396
27005
  init_paths();
26397
27006
  import { createHash as createHash4 } from "node:crypto";
26398
- import fs72 from "node:fs";
26399
- import path68 from "node:path";
27007
+ import fs74 from "node:fs";
27008
+ import path71 from "node:path";
26400
27009
  var SLUG = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
26401
27010
  var CAPTURED = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:\d{2})$/;
26402
27011
  var MAX_BYTES3 = 50 * 1024 * 1024;
@@ -26429,13 +27038,13 @@ function invalid(zh, en) {
26429
27038
  refuse2({ zh: `\u6A21\u677F\u672A\u901A\u8FC7\u6821\u9A8C\uFF1A${zh}`, en: `The template is not valid: ${en}` });
26430
27039
  }
26431
27040
  async function ask(route2, init = {}, fallback) {
26432
- const token2 = await accessToken();
26433
- if (!token2.ok) refuse2(SAID[token2.reason], fallback);
27041
+ const token3 = await accessToken();
27042
+ if (!token3.ok) refuse2(SAID[token3.reason], fallback);
26434
27043
  let res;
26435
27044
  try {
26436
27045
  res = await fetch(`${cloudEndpoints().api}/v1/templates${route2}`, {
26437
27046
  ...init,
26438
- headers: { authorization: `Bearer ${token2.token}` }
27047
+ headers: { authorization: `Bearer ${token3.token}` }
26439
27048
  });
26440
27049
  } catch {
26441
27050
  refuse2(SAID.unreachable, fallback);
@@ -26494,11 +27103,11 @@ async function templatePreview(name, opts, program2) {
26494
27103
  This template has no preview.`);
26495
27104
  const written = [];
26496
27105
  for (const p of previews) {
26497
- const bytes = Buffer.from(await (await ask(`/${at}/previews/${route(p.path)}`, {}, WITHOUT)).arrayBuffer());
26498
- const file = path68.basename(p.path);
26499
- const out = path68.join(opts.to, file.startsWith(`${name}-`) ? file : `${name}-${file}`);
26500
- fs72.mkdirSync(opts.to, { recursive: true });
26501
- fs72.writeFileSync(out, bytes);
27106
+ const bytes3 = Buffer.from(await (await ask(`/${at}/previews/${route(p.path)}`, {}, WITHOUT)).arrayBuffer());
27107
+ const file = path71.basename(p.path);
27108
+ const out = path71.join(opts.to, file.startsWith(`${name}-`) ? file : `${name}-${file}`);
27109
+ fs74.mkdirSync(opts.to, { recursive: true });
27110
+ fs74.writeFileSync(out, bytes3);
26502
27111
  written.push(out);
26503
27112
  say(out);
26504
27113
  }
@@ -26511,15 +27120,15 @@ async function templateGet(name, opts, program2) {
26511
27120
  const { template } = await asJson(await ask(`/${at}`, {}, WITHOUT));
26512
27121
  const fetched = [];
26513
27122
  for (const f of template.files) {
26514
- const out = path68.resolve(to, f.path);
26515
- if (!out.startsWith(path68.resolve(to) + path68.sep)) refuse2(T.corrupt, WITHOUT);
26516
- const bytes = Buffer.from(await (await ask(`/${at}/files/${route(f.path)}`, {}, WITHOUT)).arrayBuffer());
26517
- if (bytes.byteLength !== f.size || createHash4("sha256").update(bytes).digest("hex") !== f.sha256) {
27123
+ const out = path71.resolve(to, f.path);
27124
+ if (!out.startsWith(path71.resolve(to) + path71.sep)) refuse2(T.corrupt, WITHOUT);
27125
+ const bytes3 = Buffer.from(await (await ask(`/${at}/files/${route(f.path)}`, {}, WITHOUT)).arrayBuffer());
27126
+ if (bytes3.byteLength !== f.size || createHash4("sha256").update(bytes3).digest("hex") !== f.sha256) {
26518
27127
  refuse2(T.corrupt, WITHOUT);
26519
27128
  }
26520
- fetched.push({ out, bytes });
27129
+ fetched.push({ out, bytes: bytes3 });
26521
27130
  }
26522
- const taken = fetched.filter((f) => fs72.existsSync(f.out)).map((f) => path68.relative(to, f.out));
27131
+ const taken = fetched.filter((f) => fs74.existsSync(f.out)).map((f) => path71.relative(to, f.out));
26523
27132
  if (taken.length > 0) {
26524
27133
  refuse2({
26525
27134
  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`,
@@ -26527,8 +27136,8 @@ async function templateGet(name, opts, program2) {
26527
27136
  });
26528
27137
  }
26529
27138
  for (const f of fetched) {
26530
- fs72.mkdirSync(path68.dirname(f.out), { recursive: true });
26531
- fs72.writeFileSync(f.out, f.bytes);
27139
+ fs74.mkdirSync(path71.dirname(f.out), { recursive: true });
27140
+ fs74.writeFileSync(f.out, f.bytes);
26532
27141
  }
26533
27142
  say(`\u5DF2\u53D6\u56DE ${name}\uFF1A${fetched.length} \u4E2A\u6587\u4EF6\uFF0C\u4F4D\u4E8E ${to}
26534
27143
  Fetched ${name}: ${fetched.length} files into ${to}`);
@@ -26546,25 +27155,25 @@ async function templateSource(name, opts) {
26546
27155
  say(`evidence: ${e.path}`);
26547
27156
  continue;
26548
27157
  }
26549
- const out = path68.join(opts.to, `${name}-${path68.basename(e.path)}`);
26550
- fs72.mkdirSync(opts.to, { recursive: true });
26551
- fs72.writeFileSync(out, Buffer.from(await (await ask(`/${at}/evidence/${route(e.path)}`)).arrayBuffer()));
27158
+ const out = path71.join(opts.to, `${name}-${path71.basename(e.path)}`);
27159
+ fs74.mkdirSync(opts.to, { recursive: true });
27160
+ fs74.writeFileSync(out, Buffer.from(await (await ask(`/${at}/evidence/${route(e.path)}`)).arrayBuffer()));
26552
27161
  written.push(out);
26553
27162
  say(out);
26554
27163
  }
26555
27164
  return { name, source, evidence: written.length > 0 ? written : evidence.map((e) => e.path) };
26556
27165
  }
26557
27166
  async function templatePublish(dir, opts) {
26558
- const root = path68.resolve(dir);
26559
- const manifest = path68.join(root, "template.md");
26560
- if (!fs72.existsSync(manifest)) invalid("\u6587\u4EF6\u5939\u9876\u5C42\u7F3A\u5C11 template.md\u3002", "template.md is missing from the folder\u2019s top level.");
26561
- const meta = readTemplateMeta(fs72.readFileSync(manifest, "utf8"));
26562
- const files = walk2(root);
26563
- const total = files.reduce((sum, f) => sum + fs72.statSync(path68.join(root, f)).size, 0);
27167
+ const root = path71.resolve(dir);
27168
+ const manifest = path71.join(root, "template.md");
27169
+ 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.");
27170
+ const meta = readTemplateMeta(fs74.readFileSync(manifest, "utf8"));
27171
+ const files2 = walk2(root);
27172
+ const total = files2.reduce((sum, f) => sum + fs74.statSync(path71.join(root, f)).size, 0);
26564
27173
  if (total > MAX_BYTES3) invalid("\u6A21\u677F\u6587\u4EF6\u5408\u8BA1\u8D85\u8FC7 50 MB\u3002", "the template\u2019s files exceed 50 MB.");
26565
27174
  for (const extra of [...opts.preview, ...opts.evidence]) {
26566
- if (!fs72.existsSync(extra)) invalid(`\u627E\u4E0D\u5230\u6587\u4EF6 ${extra}\u3002`, `${extra} does not exist.`);
26567
- if (path68.resolve(extra).startsWith(root + path68.sep)) {
27175
+ if (!fs74.existsSync(extra)) invalid(`\u627E\u4E0D\u5230\u6587\u4EF6 ${extra}\u3002`, `${extra} does not exist.`);
27176
+ if (path71.resolve(extra).startsWith(root + path71.sep)) {
26568
27177
  invalid(
26569
27178
  `${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`,
26570
27179
  `${extra} is inside the template folder; keep previews and evidence outside it.`
@@ -26576,17 +27185,17 @@ async function templatePublish(dir, opts) {
26576
27185
  }
26577
27186
  const form = new FormData();
26578
27187
  form.set("meta", JSON.stringify({ ...meta, replace: !!opts.replace }));
26579
- const add = (kind, name, file) => form.append(`${kind}:${name}`, new Blob([fs72.readFileSync(file)]), path68.basename(name));
26580
- for (const f of files) add("files", f, path68.join(root, f));
26581
- for (const f of opts.preview) add("previews", path68.basename(f), f);
26582
- for (const f of opts.evidence) add("evidence", path68.basename(f), f);
27188
+ const add = (kind, name, file) => form.append(`${kind}:${name}`, new Blob([fs74.readFileSync(file)]), path71.basename(name));
27189
+ for (const f of files2) add("files", f, path71.join(root, f));
27190
+ for (const f of opts.preview) add("previews", path71.basename(f), f);
27191
+ for (const f of opts.evidence) add("evidence", path71.basename(f), f);
26583
27192
  const done2 = await asJson(await ask("", { method: "POST", body: form }));
26584
27193
  say(
26585
- done2.replaced ? `\u5DF2\u66FF\u6362 ${done2.name}\uFF1A${files.length} \u4E2A\u6587\u4EF6\u3002
26586
- Replaced ${done2.name}: ${files.length} files.` : `\u5DF2\u53D1\u5E03 ${done2.name}\uFF1A${files.length} \u4E2A\u6587\u4EF6\u3002
26587
- Published ${done2.name}: ${files.length} files.`
27194
+ done2.replaced ? `\u5DF2\u66FF\u6362 ${done2.name}\uFF1A${files2.length} \u4E2A\u6587\u4EF6\u3002
27195
+ Replaced ${done2.name}: ${files2.length} files.` : `\u5DF2\u53D1\u5E03 ${done2.name}\uFF1A${files2.length} \u4E2A\u6587\u4EF6\u3002
27196
+ Published ${done2.name}: ${files2.length} files.`
26588
27197
  );
26589
- return { name: done2.name, replaced: done2.replaced, files };
27198
+ return { name: done2.name, replaced: done2.replaced, files: files2 };
26590
27199
  }
26591
27200
  async function templateDelete(name) {
26592
27201
  await ask(`/${encodeURIComponent(name)}`, { method: "DELETE" });
@@ -26595,11 +27204,11 @@ Deleted ${name} from the catalog.`);
26595
27204
  return { name, deleted: true };
26596
27205
  }
26597
27206
  async function templateShowcase(name, dir) {
26598
- const file = path68.join(dir, "showcase.json");
26599
- if (!fs72.existsSync(file)) invalid(`${dir} \u4E2D\u7F3A\u5C11 showcase.json\u3002`, `showcase.json is missing from ${dir}.`);
27207
+ const file = path71.join(dir, "showcase.json");
27208
+ if (!fs74.existsSync(file)) invalid(`${dir} \u4E2D\u7F3A\u5C11 showcase.json\u3002`, `showcase.json is missing from ${dir}.`);
26600
27209
  let meta;
26601
27210
  try {
26602
- meta = JSON.parse(fs72.readFileSync(file, "utf8"));
27211
+ meta = JSON.parse(fs74.readFileSync(file, "utf8"));
26603
27212
  } catch {
26604
27213
  return invalid("showcase.json \u4E0D\u662F\u6709\u6548\u7684 JSON\u3002", "showcase.json is not valid JSON.");
26605
27214
  }
@@ -26611,9 +27220,9 @@ async function templateShowcase(name, dir) {
26611
27220
  const form = new FormData();
26612
27221
  form.set("meta", JSON.stringify(meta));
26613
27222
  for (const p of new Set(named2)) {
26614
- const at = path68.join(dir, p);
26615
- if (!fs72.existsSync(at)) invalid(`\u627E\u4E0D\u5230\u6587\u4EF6 ${at}\u3002`, `${at} does not exist.`);
26616
- form.append(`media:${p}`, new Blob([fs72.readFileSync(at)]), p);
27223
+ const at = path71.join(dir, p);
27224
+ if (!fs74.existsSync(at)) invalid(`\u627E\u4E0D\u5230\u6587\u4EF6 ${at}\u3002`, `${at} does not exist.`);
27225
+ form.append(`media:${p}`, new Blob([fs74.readFileSync(at)]), p);
26617
27226
  }
26618
27227
  const done2 = await asJson(await ask(`/${encodeURIComponent(name)}/showcase`, { method: "PUT", body: form }));
26619
27228
  say(`\u5DF2\u66F4\u65B0 ${name} \u7684\u5C55\u793A\u8D44\u6599\uFF1A${done2.files} \u4E2A\u6587\u4EF6\u3002
@@ -26622,7 +27231,7 @@ Updated ${name}'s showcase: ${done2.files} files.`);
26622
27231
  }
26623
27232
  function walk2(root, sub = "") {
26624
27233
  const out = [];
26625
- for (const entry of fs72.readdirSync(path68.join(root, sub), { withFileTypes: true })) {
27234
+ for (const entry of fs74.readdirSync(path71.join(root, sub), { withFileTypes: true })) {
26626
27235
  if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
26627
27236
  const rel2 = sub ? `${sub}/${entry.name}` : entry.name;
26628
27237
  if (entry.isDirectory()) out.push(...walk2(root, rel2));
@@ -26956,7 +27565,7 @@ init_sessions();
26956
27565
  init_env();
26957
27566
  init_store();
26958
27567
  import { spawnSync as spawnSync5 } from "node:child_process";
26959
- import path73 from "node:path";
27568
+ import path76 from "node:path";
26960
27569
  var AGENTS2 = {
26961
27570
  claude: "claude-code",
26962
27571
  codex: "codex",
@@ -26971,10 +27580,10 @@ var AGENTS2 = {
26971
27580
  var RUNTIMES = /* @__PURE__ */ new Set(["node", "bun", "deno"]);
26972
27581
  function agentOf2(args) {
26973
27582
  const words = args.trim().split(/\s+/);
26974
- const first = path73.basename(words[0] ?? "");
27583
+ const first = path76.basename(words[0] ?? "");
26975
27584
  const script = RUNTIMES.has(first) ? words[1] ?? "" : words[0] ?? "";
26976
27585
  if (script.includes("/@anthropic-ai/claude-code/")) return "claude-code";
26977
- return AGENTS2[path73.basename(script).replace(/\.(m?js|cjs|exe)$/, "")];
27586
+ return AGENTS2[path76.basename(script).replace(/\.(m?js|cjs|exe)$/, "")];
26978
27587
  }
26979
27588
  function processChain() {
26980
27589
  if (process.platform === "win32") return [];
@@ -27018,8 +27627,8 @@ function terminalSession(chain, env, opencode, cwd = process.cwd()) {
27018
27627
  return resumeId ? { harness, resumeId, cwd } : void 0;
27019
27628
  }
27020
27629
  function currentSession() {
27021
- const run = insideRun();
27022
- if (run) return readStore().runs.find((r) => r.sessionId === run)?.origin;
27630
+ const run2 = insideRun();
27631
+ if (run2) return readStore().runs.find((r) => r.sessionId === run2)?.origin;
27023
27632
  return terminalSession(probes.chain(), process.env, probes.opencode);
27024
27633
  }
27025
27634
 
@@ -27035,11 +27644,11 @@ init_tasks();
27035
27644
  init_read();
27036
27645
  init_rules();
27037
27646
  init_board2();
27038
- import fs78 from "node:fs";
27039
- import path74 from "node:path";
27647
+ import fs80 from "node:fs";
27648
+ import path77 from "node:path";
27040
27649
  var FOLLOW_MS = 400;
27041
27650
  async function cmdStartRun(action, args, opts, program2 = "akb") {
27042
- const { req, follow, print } = readRequest(action, args, opts);
27651
+ const { req, follow: follow2, print } = readRequest(action, args, opts);
27043
27652
  const task = action === "create" && req.fromCard !== void 0 ? workspaceTask() : null;
27044
27653
  if (task) return await createInCloud(task, req.description, req.workflow);
27045
27654
  if (Number.isInteger(req.id)) {
@@ -27076,13 +27685,13 @@ async function cmdStartRun(action, args, opts, program2 = "akb") {
27076
27685
  sayIfHeld(req, program2);
27077
27686
  const started = action === "refine" ? await startRefinement(req) : await startRun(runnable);
27078
27687
  if ("error" in started) die(started.error, { kind: "run-refused", action });
27079
- const { run, spawned } = started;
27080
- if (!spawned) die(`couldn't start a process for run ${run.sessionId}`, { kind: "spawn-failed" });
27081
- say(`${action} \u2014 run ${run.sessionId}${run.deliveryId ? ` in delivery ${run.deliveryId}` : ""}`);
27082
- say(` follow it: ${program2} run log ${short(run.sessionId)} --follow${BOARD_FLAG}`);
27083
- say(` stop it: ${program2} run stop ${short(run.sessionId)}${BOARD_FLAG}`);
27084
- if (follow) return await followStarted(run.sessionId, program2, opts.events === true);
27085
- return { sessionId: run.sessionId, action, cardId: run.cardId };
27688
+ const { run: run2, spawned } = started;
27689
+ if (!spawned) die(`couldn't start a process for run ${run2.sessionId}`, { kind: "spawn-failed" });
27690
+ say(`${action} \u2014 run ${run2.sessionId}${run2.deliveryId ? ` in delivery ${run2.deliveryId}` : ""}`);
27691
+ say(` follow it: ${program2} run log ${short(run2.sessionId)} --follow${BOARD_FLAG}`);
27692
+ say(` stop it: ${program2} run stop ${short(run2.sessionId)}${BOARD_FLAG}`);
27693
+ if (follow2) return await followStarted(run2.sessionId, program2, opts.events === true);
27694
+ return { sessionId: run2.sessionId, action, cardId: run2.cardId };
27086
27695
  }
27087
27696
  async function cmdRunScheduled(workflow, agent, opts, program2 = "akb") {
27088
27697
  const id = knownWorkflow(workflow);
@@ -27092,12 +27701,12 @@ async function cmdRunScheduled(workflow, agent, opts, program2 = "akb") {
27092
27701
  if (insideRun() || opts.print) return printFlow(req, program2);
27093
27702
  const started = await startRun(req);
27094
27703
  if ("error" in started) die(started.error, { kind: "run-refused", action: "scheduled" });
27095
- const { run, spawned } = started;
27096
- if (!spawned) die(`couldn't start a process for run ${run.sessionId}`, { kind: "spawn-failed" });
27097
- say(`${one.agent} \u2014 run ${run.sessionId}${run.deliveryId ? ` in delivery ${run.deliveryId}` : ""}`);
27098
- say(` follow it: ${program2} run log ${short(run.sessionId)} --follow${BOARD_FLAG}`);
27099
- if (opts.follow) return await followStarted(run.sessionId, program2, opts.events === true);
27100
- return { sessionId: run.sessionId, action: "scheduled" };
27704
+ const { run: run2, spawned } = started;
27705
+ if (!spawned) die(`couldn't start a process for run ${run2.sessionId}`, { kind: "spawn-failed" });
27706
+ say(`${one.agent} \u2014 run ${run2.sessionId}${run2.deliveryId ? ` in delivery ${run2.deliveryId}` : ""}`);
27707
+ say(` follow it: ${program2} run log ${short(run2.sessionId)} --follow${BOARD_FLAG}`);
27708
+ if (opts.follow) return await followStarted(run2.sessionId, program2, opts.events === true);
27709
+ return { sessionId: run2.sessionId, action: "scheduled" };
27101
27710
  }
27102
27711
  async function createInCloud(task, description, workflow) {
27103
27712
  const title = description.split("\n")[0].trim().slice(0, 120);
@@ -27163,7 +27772,7 @@ function sayBeforeStart(req, program2) {
27163
27772
  }
27164
27773
  }
27165
27774
  function readRequest(action, args, opts) {
27166
- const follow = opts.follow === true;
27775
+ const follow2 = opts.follow === true;
27167
27776
  const print = opts.print === true;
27168
27777
  const words = (at) => {
27169
27778
  const value = args[at];
@@ -27178,28 +27787,28 @@ function readRequest(action, args, opts) {
27178
27787
  }
27179
27788
  return {
27180
27789
  req: { action, description: words(0), workflow, runtime: opts.runtime, origin: currentSession(), fromCard },
27181
- follow,
27790
+ follow: follow2,
27182
27791
  print
27183
27792
  };
27184
27793
  }
27185
- if (action === "setup") return { req: { action }, follow, print };
27794
+ if (action === "setup") return { req: { action }, follow: follow2, print };
27186
27795
  if (action === "review-memory") {
27187
27796
  if (!opts.task?.length) die("name the Cloud tasks whose chats to review: --task <id...>", { kind: "run-refused", action });
27188
- return { req: { action, tasks: opts.task }, follow, print };
27797
+ return { req: { action, tasks: opts.task }, follow: follow2, print };
27189
27798
  }
27190
- if (action === "review-dismissals") return { req: { action, ...opts.all ? { allReasons: true } : {} }, follow, print };
27799
+ if (action === "review-dismissals") return { req: { action, ...opts.all ? { allReasons: true } : {} }, follow: follow2, print };
27191
27800
  if (action === "reflect") {
27192
27801
  const cards = (opts.cards ?? []).map(Number);
27193
27802
  if (!cards.length || !cards.every((n) => Number.isInteger(n) && n > 0)) {
27194
27803
  die("name the archived cards to reflect on: --cards <id...>", { kind: "run-refused", action });
27195
27804
  }
27196
- return { req: { action, cards }, follow, print };
27805
+ return { req: { action, cards }, follow: follow2, print };
27197
27806
  }
27198
27807
  if (action === "conflict") {
27199
27808
  const delivery = args[0];
27200
27809
  return {
27201
27810
  req: { action, id: delivery.cardId ?? void 0, deliveryId: delivery.deliveryId, title: delivery.title },
27202
- follow,
27811
+ follow: follow2,
27203
27812
  print
27204
27813
  };
27205
27814
  }
@@ -27210,17 +27819,17 @@ function readRequest(action, args, opts) {
27210
27819
  if (opts.session || opts.transcript) {
27211
27820
  req.carry = { resumeId: opts.session, fork: opts.session ? opts.fork === true : void 0, transcript: opts.transcript };
27212
27821
  }
27213
- return { req, follow, print };
27822
+ return { req, follow: follow2, print };
27214
27823
  }
27215
27824
  async function cmdResume(id, opts) {
27216
27825
  const opened = await startResume(id ?? "last");
27217
27826
  if ("error" in opened) die(opened.error, { kind: "run-refused" });
27218
- const { run } = opened;
27219
- if (!opened.spawned) die(`couldn't start a process for run ${run.sessionId}`, { kind: "spawn-failed" });
27220
- const within = run.deliveryId ? ` in delivery ${run.deliveryId}` : "";
27221
- say(run.resumedFrom ? `continuing ${short(run.resumedFrom)} \u2014 run ${run.sessionId}${within}` : `trying again \u2014 run ${run.sessionId}${within}`);
27222
- if (opts.follow === true) return await followStarted(run.sessionId, "akb");
27223
- return { sessionId: run.sessionId, resumedFrom: run.resumedFrom };
27827
+ const { run: run2 } = opened;
27828
+ if (!opened.spawned) die(`couldn't start a process for run ${run2.sessionId}`, { kind: "spawn-failed" });
27829
+ const within = run2.deliveryId ? ` in delivery ${run2.deliveryId}` : "";
27830
+ say(run2.resumedFrom ? `continuing ${short(run2.resumedFrom)} \u2014 run ${run2.sessionId}${within}` : `trying again \u2014 run ${run2.sessionId}${within}`);
27831
+ if (opts.follow === true) return await followStarted(run2.sessionId, "akb");
27832
+ return { sessionId: run2.sessionId, resumedFrom: run2.resumedFrom };
27224
27833
  }
27225
27834
  async function cmdCancel(named2) {
27226
27835
  const res = await cancelBoardDelivery(named2);
@@ -27310,7 +27919,7 @@ async function cmdSubStart(words, opts) {
27310
27919
  let prompt = words.join(" ");
27311
27920
  if (opts.file) {
27312
27921
  try {
27313
- prompt = fs78.readFileSync(path74.resolve(opts.file), "utf8");
27922
+ prompt = fs80.readFileSync(path77.resolve(opts.file), "utf8");
27314
27923
  } catch {
27315
27924
  die(`--file: cannot read ${opts.file}`, { kind: "run-refused" });
27316
27925
  }
@@ -27412,15 +28021,16 @@ async function followRun(sessionId, already = "", program2 = "akb", events = fal
27412
28021
  let current = first;
27413
28022
  let last = first;
27414
28023
  let printed = already.length;
28024
+ const idle = { mark: "", since: Date.now() };
27415
28025
  for (; ; ) {
27416
28026
  last = await followOne(current, printed, events);
27417
28027
  seen.add(current.sessionId);
27418
28028
  printed = 0;
27419
28029
  const deliveryId2 = last.deliveryId ?? first.deliveryId;
27420
- const next = nextInFlow(first, current.sessionId, seen) ?? (deliveryId2 ? await moveDelivery(deliveryId2) : void 0);
28030
+ const next = nextInFlow(first, current.sessionId, seen) ?? (deliveryId2 ? await moveDelivery(deliveryId2, idle) : void 0);
27421
28031
  if (next === void 0) break;
27422
28032
  if (next === null) {
27423
- await pause(LANDING_POLL_MS);
28033
+ await pause(follow.pollMs);
27424
28034
  continue;
27425
28035
  }
27426
28036
  current = next;
@@ -27440,7 +28050,7 @@ async function followRun(sessionId, already = "", program2 = "akb", events = fal
27440
28050
  const deliveryId = last.deliveryId ?? first.deliveryId;
27441
28051
  const delivery = deliveryId ? deliveryOutcome(deliveryId) : void 0;
27442
28052
  if (delivery) say(`delivery ${delivery.id}: ${delivery.line}`);
27443
- const failed = last.status !== "done" ? `run ${short(last.sessionId)} ended ${last.status}${last.error ? `: ${last.error}` : ""}` : delivery && delivery.status !== "finished" ? `delivery ${delivery.id} did not land: ${delivery.line}` : void 0;
28053
+ const failed = last.status !== "done" ? `run ${short(last.sessionId)} ended ${last.status}${last.error ? `: ${last.error}` : ""}` : delivery && delivery.status !== "finished" ? `delivery ${delivery.id} did not land${idle.timedOut ? ` \u2014 nothing moved it for ${ago(follow.idleMs)}` : ""}: ${delivery.line}` : void 0;
27444
28054
  const held4 = peekRun(last.sessionId);
27445
28055
  const agentSession = held4 ? resumeSessionId(held4) : void 0;
27446
28056
  return {
@@ -27458,7 +28068,7 @@ async function followStarted(sessionId, program2, events = false) {
27458
28068
  if (typeof res.failed === "string") die(res.failed, { kind: "run-failed", sessionId, ...res });
27459
28069
  return { sessionId, ...res };
27460
28070
  }
27461
- var LANDING_POLL_MS = 2e3;
28071
+ var follow = { pollMs: 2e3, idleMs: 10 * 6e4 };
27462
28072
  var pause = (ms) => new Promise((done2) => setTimeout(done2, ms));
27463
28073
  async function followOne(view, already, events) {
27464
28074
  let seen = already;
@@ -27499,11 +28109,20 @@ function nextInFlow(first, after2, seen) {
27499
28109
  const next = runs.filter((r) => !seen.has(r.sessionId) && r.action !== "sub" && r.startedAt >= first.startedAt).filter((r) => r.resumedFrom === after2 || !!first.flowId && r.flowId === first.flowId).sort((a, b) => a.startedAt - b.startedAt)[0];
27500
28110
  return next ? { ...next } : void 0;
27501
28111
  }
27502
- async function moveDelivery(deliveryId) {
28112
+ async function moveDelivery(deliveryId, idle) {
27503
28113
  const delivery = findDelivery(deliveryId);
27504
28114
  if (!delivery || delivery.status !== "active") return void 0;
27505
28115
  const state = deliveryState(delivery, delivery.cardId === null ? 0 : openQuestions(delivery.cardId));
27506
28116
  if (state.paused || state.stage === "working") return void 0;
28117
+ const mark = JSON.stringify(delivery);
28118
+ const busy = !!state.retryIn || readRuns().some((r) => !!r.deliveryId && r.status === "running");
28119
+ if (busy || mark !== idle.mark) {
28120
+ idle.mark = mark;
28121
+ idle.since = Date.now();
28122
+ } else if (Date.now() - idle.since >= follow.idleMs) {
28123
+ idle.timedOut = true;
28124
+ return void 0;
28125
+ }
27507
28126
  const ask2 = await advanceLanding();
27508
28127
  if (!ask2) return null;
27509
28128
  const started = await startRun(ask2);
@@ -27576,7 +28195,7 @@ async function cmdWatch(sessionId) {
27576
28195
  // ../cli/src/commands/triage.ts
27577
28196
  init_scheduled();
27578
28197
  init_workflows();
27579
- import fs80 from "node:fs";
28198
+ import fs82 from "node:fs";
27580
28199
 
27581
28200
  // ../cli/src/lib/signals/index.ts
27582
28201
  init_cards();
@@ -27586,16 +28205,16 @@ init_yaml();
27586
28205
  // ../cli/src/lib/signals/config.ts
27587
28206
  init_settings2();
27588
28207
  init_paths();
27589
- import fs79 from "node:fs";
27590
- import path75 from "node:path";
28208
+ import fs81 from "node:fs";
28209
+ import path78 from "node:path";
27591
28210
  var TOKEN_KEY = "TRIAGE_ENDPOINT_TOKEN";
27592
28211
  var ENDPOINT_SETTING = "Triage endpoint";
27593
28212
  var LINE2 = /^- \*\*Triage endpoint\*\*\s*[—-]\s*(.+)$/m;
27594
- var boardRel3 = (file) => rel(file).split(path75.sep).join("/");
28213
+ var boardRel3 = (file) => rel(file).split(path78.sep).join("/");
27595
28214
  function signalEndpoint() {
27596
28215
  let text5;
27597
28216
  try {
27598
- text5 = fs79.readFileSync(CONFIG, "utf8");
28217
+ text5 = fs81.readFileSync(CONFIG, "utf8");
27599
28218
  } catch {
27600
28219
  return "";
27601
28220
  }
@@ -27639,7 +28258,7 @@ function readMeta(sent) {
27639
28258
  }
27640
28259
  return pairs;
27641
28260
  }
27642
- function read(raw) {
28261
+ function read2(raw) {
27643
28262
  const missing = REQUIRED.filter((field2) => !text4(raw[field2]));
27644
28263
  if (missing.length > 0) return { ok: false, why: `missing ${missing.join(", ")}` };
27645
28264
  const sent = text4(raw.collected_at);
@@ -27715,7 +28334,7 @@ async function fetchSignals() {
27715
28334
  report3.failed.push({ which: `item ${at + 1}`, why: "not an object" });
27716
28335
  return;
27717
28336
  }
27718
- const found2 = read(raw);
28337
+ const found2 = read2(raw);
27719
28338
  if (!found2.ok) {
27720
28339
  report3.failed.push({ which: text4(raw.source_id) || `item ${at + 1}`, why: found2.why });
27721
28340
  return;
@@ -27771,7 +28390,7 @@ async function cmdTriageAdd(opts) {
27771
28390
  let body = opts.text;
27772
28391
  if (opts.file !== void 0) {
27773
28392
  try {
27774
- body = fs80.readFileSync(opts.file, "utf8");
28393
+ body = fs82.readFileSync(opts.file, "utf8");
27775
28394
  } catch {
27776
28395
  die(`can't read ${opts.file} \u2014 write the item to a file, then pass its path`, { kind: "needs-input" });
27777
28396
  }
@@ -27895,16 +28514,16 @@ async function cmdSpec(opts, program2 = "akb") {
27895
28514
  }
27896
28515
  const started = await startRun(req);
27897
28516
  if ("error" in started) die(started.error, { kind: "run-refused", action: "spec" });
27898
- const { run, spawned } = started;
27899
- if (!spawned) die(`couldn't start a process to run ${run.sessionId}`, { kind: "spawn-failed" });
28517
+ const { run: run2, spawned } = started;
28518
+ if (!spawned) die(`couldn't start a process to run ${run2.sessionId}`, { kind: "spawn-failed" });
27900
28519
  if (ownRuntime || ownSession) {
27901
28520
  say(`${name} runs in ${ownRuntime ? "its own runtime" : "a session of its own"}, so it started as a separate run \u2014 don't write its section yourself.`);
27902
28521
  }
27903
- say(`spec ${name} #${id} \u2014 run ${run.sessionId}`);
27904
- say(` follow it: ${program2} run log ${short(run.sessionId)} --follow${BOARD_FLAG}`);
27905
- say(` stop it: ${program2} run stop ${short(run.sessionId)}${BOARD_FLAG}`);
27906
- if (opts.follow === true) return await followStarted(run.sessionId, program2);
27907
- return { sessionId: run.sessionId, action: "spec", specAgent: name, cardId: id };
28522
+ say(`spec ${name} #${id} \u2014 run ${run2.sessionId}`);
28523
+ say(` follow it: ${program2} run log ${short(run2.sessionId)} --follow${BOARD_FLAG}`);
28524
+ say(` stop it: ${program2} run stop ${short(run2.sessionId)}${BOARD_FLAG}`);
28525
+ if (opts.follow === true) return await followStarted(run2.sessionId, program2);
28526
+ return { sessionId: run2.sessionId, action: "spec", specAgent: name, cardId: id };
27908
28527
  }
27909
28528
  function noteOf(words, flag) {
27910
28529
  const typed = words.join(" ").trim();
@@ -28055,7 +28674,13 @@ function declareRuns(program2, cli) {
28055
28674
  withShared(cloud.command("sign-out")).summary("forget it: this machine stops reaching Cloud, and nothing on any board changes").action(async function() {
28056
28675
  await boardless(this, cli, (p) => cmdCloud(["sign-out"], p));
28057
28676
  });
28058
- withShared(cloud.command("image")).argument("[prompt...]", "what to draw").summary("generate an image through Cloud, spending Pro credits").option("--aspect <ratio>", "the aspect ratio, e.g. 16:9").option("--ref <file>", "a PNG, JPEG or WebP to draw from; repeat for more", (v, all) => [...all, v], []).option("--out <file>", "where to write the image").action(async function(prompt) {
28677
+ withShared(cloud.command("import")).summary("import this board into an empty Cloud project").description(
28678
+ "Imports the open cards, memory, rules and own workflows of this board into an empty Cloud project, numbered from 1 in the order of their ids. Checks everything first and writes nothing on a problem; changes nothing on this computer. Run again to finish an import that stopped."
28679
+ ).option("--project <id-or-prefix>", "the Cloud project, by id or by its prefix").option("--dry-run", "only check, and list what would be imported and what stays here").action(async function() {
28680
+ const o = this.opts();
28681
+ await onBoard2(this, cli, (p) => cloudImport({ project: o.project, dryRun: o.dryRun }, p));
28682
+ });
28683
+ withShared(cloud.command("image")).argument("[prompt...]", "what to draw").summary("generate an image through Cloud, spending Pro credits").option("--aspect <ratio>", "the aspect ratio, e.g. 16:9").option("--ref <file>", "a PNG, JPEG or WebP to draw from; repeat for more", (v, all2) => [...all2, v], []).option("--out <file>", "where to write the image").action(async function(prompt) {
28059
28684
  const o = this.opts();
28060
28685
  await boardless(
28061
28686
  this,
@@ -28094,7 +28719,7 @@ function declareRuns(program2, cli) {
28094
28719
  withShared(template.command("source")).argument("<name>", "the template").summary("where a template came from, and the evidence captured at the time").option("--to <dir>", "download the evidence into this folder").action(async function(name) {
28095
28720
  await boardless(this, cli, () => templateSource(name, this.opts()));
28096
28721
  });
28097
- withShared(template.command("publish")).argument("<dir>", "the template's folder, holding template.md").summary("publish a template (maintainers only)").option("--preview <file>", "a catalog preview image, kept outside the folder; repeat for more", (v, all) => [...all, v], []).option("--evidence <file>", "a capture of the reference, kept outside the folder; repeat for more", (v, all) => [...all, v], []).option("--replace", "replace the template already published under this name").action(async function(dir) {
28722
+ withShared(template.command("publish")).argument("<dir>", "the template's folder, holding template.md").summary("publish a template (maintainers only)").option("--preview <file>", "a catalog preview image, kept outside the folder; repeat for more", (v, all2) => [...all2, v], []).option("--evidence <file>", "a capture of the reference, kept outside the folder; repeat for more", (v, all2) => [...all2, v], []).option("--replace", "replace the template already published under this name").action(async function(dir) {
28098
28723
  const o = this.opts();
28099
28724
  await boardless(this, cli, () => templatePublish(dir, o));
28100
28725
  });
@@ -28177,8 +28802,8 @@ function declareRuns(program2, cli) {
28177
28802
  }
28178
28803
  var RUN_ID = "a run's id, any prefix of one that names only one run, or `last`. Left out, it means the newest run";
28179
28804
  var DELIVERY = "the delivery, or the card it is on";
28180
- function declareRunning(run, cli) {
28181
- const verb = (name) => withShared(run.command(name));
28805
+ function declareRunning(run2, cli) {
28806
+ const verb = (name) => withShared(run2.command(name));
28182
28807
  verb("list").summary("what is running, and what ran lately").option("-c, --card <id>", "only the runs on that card", cardId).option("-a, --all", "every run this board has kept, not just the last ten").action(async function() {
28183
28808
  await onBoard2(this, cli, (p) => cmdRuns(this.opts(), p));
28184
28809
  });
@@ -28334,8 +28959,8 @@ function declareAgent(program2, cli) {
28334
28959
  }
28335
28960
 
28336
28961
  // ../cli/src/lib/cli/setup.ts
28337
- import fs84 from "node:fs";
28338
- import path79 from "node:path";
28962
+ import fs86 from "node:fs";
28963
+ import path82 from "node:path";
28339
28964
 
28340
28965
  // ../cli/src/commands/install.ts
28341
28966
  init_local();
@@ -28347,14 +28972,14 @@ init_io();
28347
28972
  init_paths();
28348
28973
  init_memory();
28349
28974
  init_yaml2();
28350
- import fs83 from "node:fs";
28351
- import path78 from "node:path";
28975
+ import fs85 from "node:fs";
28976
+ import path81 from "node:path";
28352
28977
  import { spawnSync as spawnSync8 } from "node:child_process";
28353
28978
 
28354
28979
  // ../cli/src/lib/skill/install.ts
28355
28980
  import { spawnSync as spawnSync7 } from "node:child_process";
28356
- import fs82 from "node:fs";
28357
- import path77 from "node:path";
28981
+ import fs84 from "node:fs";
28982
+ import path80 from "node:path";
28358
28983
  import { fileURLToPath as fileURLToPath2 } from "node:url";
28359
28984
 
28360
28985
  // ../skill/SKILL.md
@@ -28369,8 +28994,8 @@ init_version();
28369
28994
  init_version();
28370
28995
  init_project();
28371
28996
  import { spawnSync as spawnSync6 } from "node:child_process";
28372
- import fs81 from "node:fs";
28373
- import path76 from "node:path";
28997
+ import fs83 from "node:fs";
28998
+ import path79 from "node:path";
28374
28999
  var MARKER3 = "# ai4kanban commit guard";
28375
29000
  var MARKER_LINE = new RegExp(`^${MARKER3}\\b`, "m");
28376
29001
  var CHECK = [
@@ -28402,9 +29027,9 @@ function hookScript(sessions) {
28402
29027
  ""
28403
29028
  ].join("\n");
28404
29029
  }
28405
- var read2 = (file) => {
29030
+ var read3 = (file) => {
28406
29031
  try {
28407
- return fs81.readFileSync(file, "utf8");
29032
+ return fs83.readFileSync(file, "utf8");
28408
29033
  } catch {
28409
29034
  return null;
28410
29035
  }
@@ -28416,7 +29041,7 @@ function hooksDir(root) {
28416
29041
  windowsHide: true
28417
29042
  });
28418
29043
  if (out.error || out.status !== 0 || !out.stdout.trim()) return null;
28419
- return path76.resolve(root, out.stdout.trim());
29044
+ return path79.resolve(root, out.stdout.trim());
28420
29045
  }
28421
29046
  function hasOwnHooksPath(root) {
28422
29047
  const out = spawnSync6("git", ["config", "--get", "core.hooksPath"], {
@@ -28426,18 +29051,18 @@ function hasOwnHooksPath(root) {
28426
29051
  });
28427
29052
  return !out.error && out.status === 0 && !!out.stdout.trim();
28428
29053
  }
28429
- var sessionsPath = (root) => path76.join(projectStateDir(path76.join(root, "docs", "kanban"), root), SESSIONS_FILE);
29054
+ var sessionsPath = (root) => path79.join(projectStateDir(path79.join(root, "docs", "kanban"), root), SESSIONS_FILE);
28430
29055
  function readCommitHook(root) {
28431
29056
  const dir = hooksDir(root);
28432
29057
  if (!dir) return { state: "no-git" };
28433
- const where = shown(root, path76.join(dir, "pre-commit"));
29058
+ const where = shown(root, path79.join(dir, "pre-commit"));
28434
29059
  const line2 = guardLine(sessionsPath(root));
28435
29060
  if (hasOwnHooksPath(root)) return { state: "hooks-path", path: where, line: line2 };
28436
- const text5 = read2(path76.join(dir, "pre-commit"));
29061
+ const text5 = read3(path79.join(dir, "pre-commit"));
28437
29062
  if (text5 === null) return { state: "absent", path: where };
28438
29063
  return MARKER_LINE.test(text5) ? { state: "ours", path: where } : { state: "foreign", path: where, line: line2 };
28439
29064
  }
28440
- var shown = (root, p) => (path76.relative(root, p) || p).split(path76.sep).join("/");
29065
+ var shown = (root, p) => (path79.relative(root, p) || p).split(path79.sep).join("/");
28441
29066
  function installCommitHook(root) {
28442
29067
  const hook = readCommitHook(root);
28443
29068
  if (hook.state === "no-git") return {};
@@ -28448,11 +29073,11 @@ function installCommitHook(root) {
28448
29073
  ${hook.line}`
28449
29074
  };
28450
29075
  }
28451
- const file = path76.join(root, hook.path);
29076
+ const file = path79.join(root, hook.path);
28452
29077
  try {
28453
- fs81.mkdirSync(path76.dirname(file), { recursive: true });
28454
- fs81.writeFileSync(file, hookScript(sessionsPath(root)), { mode: 493 });
28455
- fs81.chmodSync(file, 493);
29078
+ fs83.mkdirSync(path79.dirname(file), { recursive: true });
29079
+ fs83.writeFileSync(file, hookScript(sessionsPath(root)), { mode: 493 });
29080
+ fs83.chmodSync(file, 493);
28456
29081
  } catch (e) {
28457
29082
  return { note: `${hook.path} could not be written \u2014 ${e instanceof Error ? e.message : String(e)}` };
28458
29083
  }
@@ -28468,9 +29093,9 @@ function sayCommitHook(state) {
28468
29093
 
28469
29094
  // ../cli/src/lib/skill/install.ts
28470
29095
  var TARGETS = [
28471
- { rel: path77.join(".claude", "skills", "kanban"), agent: "Claude Code" },
29096
+ { rel: path80.join(".claude", "skills", "kanban"), agent: "Claude Code" },
28472
29097
  {
28473
- rel: path77.join(".agents", "skills", "kanban"),
29098
+ rel: path80.join(".agents", "skills", "kanban"),
28474
29099
  agent: "Codex, Cursor, OpenCode, Kimi Code, DeepSeek Harness, ZCode, Grok Build"
28475
29100
  }
28476
29101
  ];
@@ -28482,25 +29107,25 @@ var SELF2 = fileURLToPath2(import.meta.url);
28482
29107
  function rulesPath() {
28483
29108
  return SELF2;
28484
29109
  }
28485
- function read3(file) {
29110
+ function read4(file) {
28486
29111
  try {
28487
- return fs82.readFileSync(file, "utf8");
29112
+ return fs84.readFileSync(file, "utf8");
28488
29113
  } catch {
28489
29114
  return null;
28490
29115
  }
28491
29116
  }
28492
29117
  function statOf(p) {
28493
29118
  try {
28494
- return fs82.lstatSync(p);
29119
+ return fs84.lstatSync(p);
28495
29120
  } catch {
28496
29121
  return null;
28497
29122
  }
28498
29123
  }
28499
29124
  function versionIn(dir) {
28500
- const note = read3(path77.join(dir, "SKILL.md"));
29125
+ const note = read4(path80.join(dir, "SKILL.md"));
28501
29126
  const stampedNote = note?.match(NOTE_STAMP);
28502
29127
  if (stampedNote) return stampedNote[1];
28503
- const src = read3(path77.join(dir, "kanban.mjs"));
29128
+ const src = read4(path80.join(dir, "kanban.mjs"));
28504
29129
  if (!src) return null;
28505
29130
  const stamped = src.slice(0, 400).match(BUILT_STAMP);
28506
29131
  if (stamped) return stamped[1];
@@ -28524,7 +29149,7 @@ function folderState(dir) {
28524
29149
  const st = statOf(dir);
28525
29150
  if (!st) return { state: "absent", version: null };
28526
29151
  if (st.isSymbolicLink()) return { state: "linked", version: versionIn(dir) };
28527
- if (!fs82.existsSync(path77.join(dir, "SKILL.md"))) return { state: "absent", version: null };
29152
+ if (!fs84.existsSync(path80.join(dir, "SKILL.md"))) return { state: "absent", version: null };
28528
29153
  const version = versionIn(dir);
28529
29154
  if (!version) return { state: "unknown", version: null };
28530
29155
  return { state: isOlder(version, SKILL_VERSION) ? "stale" : "current", version };
@@ -28532,9 +29157,9 @@ function folderState(dir) {
28532
29157
  function readSkillState(root) {
28533
29158
  const dir = rootOr(root);
28534
29159
  const folders2 = TARGETS.map((target) => ({
28535
- path: target.rel.split(path77.sep).join("/"),
29160
+ path: target.rel.split(path80.sep).join("/"),
28536
29161
  agent: target.agent,
28537
- ...folderState(path77.join(dir, target.rel))
29162
+ ...folderState(path80.join(dir, target.rel))
28538
29163
  }));
28539
29164
  return {
28540
29165
  version: SKILL_VERSION,
@@ -28571,10 +29196,10 @@ function commandParagraph(command) {
28571
29196
  ].join("\n");
28572
29197
  }
28573
29198
  function writeFolder(dir, command) {
28574
- fs82.rmSync(dir, { recursive: true, force: true });
28575
- fs82.mkdirSync(dir, { recursive: true });
29199
+ fs84.rmSync(dir, { recursive: true, force: true });
29200
+ fs84.mkdirSync(dir, { recursive: true });
28576
29201
  const filled = SKILL_default.replace(COMMAND_BLOCK, commandParagraph(command));
28577
- fs82.writeFileSync(path77.join(dir, "SKILL.md"), `${filled.trimEnd()}
29202
+ fs84.writeFileSync(path80.join(dir, "SKILL.md"), `${filled.trimEnd()}
28578
29203
 
28579
29204
  <!-- ai4kanban ${SKILL_VERSION} -->
28580
29205
  `);
@@ -28590,8 +29215,8 @@ function installSkill(root, only, invoked) {
28590
29215
  const wrote = [];
28591
29216
  const skipped = [];
28592
29217
  for (const target of TARGETS) {
28593
- const dest = path77.join(dir, target.rel);
28594
- const shown2 = target.rel.split(path77.sep).join("/");
29218
+ const dest = path80.join(dir, target.rel);
29219
+ const shown2 = target.rel.split(path80.sep).join("/");
28595
29220
  const { state: state2 } = folderState(dest);
28596
29221
  if (state2 === "linked") {
28597
29222
  skipped.push({ path: shown2, agent: target.agent, why: "a symlink into a source checkout \u2014 left untouched" });
@@ -28661,10 +29286,10 @@ var Report = class {
28661
29286
  for (const line2 of this.notes) say(` ! ${line2}`);
28662
29287
  }
28663
29288
  };
28664
- var read4 = (file) => fs83.existsSync(file) ? fs83.readFileSync(file, "utf8") : null;
29289
+ var read5 = (file) => fs85.existsSync(file) ? fs85.readFileSync(file, "utf8") : null;
28665
29290
  function statOf2(p) {
28666
29291
  try {
28667
- return fs83.lstatSync(p);
29292
+ return fs85.lstatSync(p);
28668
29293
  } catch {
28669
29294
  return null;
28670
29295
  }
@@ -28675,10 +29300,10 @@ async function boardMove(root, args, board2) {
28675
29300
  if (code !== 0) throw new BoardError(`\`board ${args.join(" ")}\` failed \u2014 nothing else was changed`, { kind: "board-move-failed" });
28676
29301
  }
28677
29302
  function boardAbove(root) {
28678
- let at = path78.dirname(path78.resolve(root));
29303
+ let at = path81.dirname(path81.resolve(root));
28679
29304
  for (; ; ) {
28680
- if (fs83.existsSync(path78.join(at, "docs", "kanban"))) return at;
28681
- const up = path78.dirname(at);
29305
+ if (fs85.existsSync(path81.join(at, "docs", "kanban"))) return at;
29306
+ const up = path81.dirname(at);
28682
29307
  if (up === at) return null;
28683
29308
  at = up;
28684
29309
  }
@@ -28686,10 +29311,10 @@ function boardAbove(root) {
28686
29311
  async function cmdInstall(ctx) {
28687
29312
  const report3 = new Report();
28688
29313
  const board2 = ctx.board ?? null;
28689
- const where = board2 ? path78.relative(ctx.dir, board2) || board2 : "docs/kanban";
29314
+ const where = board2 ? path81.relative(ctx.dir, board2) || board2 : "docs/kanban";
28690
29315
  say(`wildcrane ${SKILL_VERSION} \u2014 installing into ${ctx.dir}`);
28691
29316
  say("");
28692
- const above = board2 || fs83.existsSync(path78.join(ctx.dir, "docs", "kanban")) ? null : boardAbove(ctx.dir);
29317
+ const above = board2 || fs85.existsSync(path81.join(ctx.dir, "docs", "kanban")) ? null : boardAbove(ctx.dir);
28693
29318
  if (above) {
28694
29319
  report3.notes.push(`there is already a board at ${above} \u2014 this makes a second one, and commands run here will find this one`);
28695
29320
  }
@@ -28711,7 +29336,7 @@ async function cmdInstall(ctx) {
28711
29336
  say("board UI (Configuration \u2192 Agent setup), or here:");
28712
29337
  say("");
28713
29338
  say(` ${ctx.program} skill`);
28714
- return { installed: ctx.dir, board: board2 ?? path78.join(ctx.dir, "docs", "kanban") };
29339
+ return { installed: ctx.dir, board: board2 ?? path81.join(ctx.dir, "docs", "kanban") };
28715
29340
  }
28716
29341
  async function cmdSkill(ctx, mode) {
28717
29342
  const report3 = new Report();
@@ -28756,7 +29381,7 @@ function placeSkill(ctx, mode, report3) {
28756
29381
  const before = readSkillState(ctx.dir).folders;
28757
29382
  for (const folder of before) {
28758
29383
  if (folder.state !== "absent" && folder.state !== "linked") {
28759
- rescueSkillConfig(ctx.dir, path78.join(ctx.dir, folder.path), report3);
29384
+ rescueSkillConfig(ctx.dir, path81.join(ctx.dir, folder.path), report3);
28760
29385
  }
28761
29386
  }
28762
29387
  const result = installSkill(ctx.dir, mode === "update" ? "present" : void 0, ctx.program);
@@ -28779,20 +29404,20 @@ function placeSkill(ctx, mode, report3) {
28779
29404
  return { result, from: before.map((f) => f.version).find(Boolean) || null };
28780
29405
  }
28781
29406
  function rescueSkillConfig(root, skillDir, report3) {
28782
- const old = read4(path78.join(skillDir, "config.md"));
29407
+ const old = read5(path81.join(skillDir, "config.md"));
28783
29408
  if (!old || old.includes("{{")) return;
28784
- const boardConfig = path78.join(root, "docs", "kanban", "config.md");
28785
- const current = read4(boardConfig);
29409
+ const boardConfig = path81.join(root, "docs", "kanban", "config.md");
29410
+ const current = read5(boardConfig);
28786
29411
  if (current === null) {
28787
- fs83.mkdirSync(path78.dirname(boardConfig), { recursive: true });
28788
- fs83.writeFileSync(boardConfig, old);
29412
+ fs85.mkdirSync(path81.dirname(boardConfig), { recursive: true });
29413
+ fs85.writeFileSync(boardConfig, old);
28789
29414
  report3.did.push("moved your filled-in config.md out of the skill folder to docs/kanban/config.md");
28790
29415
  return;
28791
29416
  }
28792
29417
  if (current === old) return;
28793
- const aside = path78.join(root, "docs", "kanban", "config.from-skill.md");
28794
- if (fs83.existsSync(aside)) return;
28795
- fs83.writeFileSync(aside, old);
29418
+ const aside = path81.join(root, "docs", "kanban", "config.from-skill.md");
29419
+ if (fs85.existsSync(aside)) return;
29420
+ fs85.writeFileSync(aside, old);
28796
29421
  report3.notes.push(
28797
29422
  "the old skill folder held a different filled-in config.md \u2014 saved as docs/kanban/config.from-skill.md; fold anything worth keeping into docs/kanban/config.md and delete it"
28798
29423
  );
@@ -28846,8 +29471,8 @@ async function cmdUpdate2(ctx) {
28846
29471
  return { updated: ctx.dir, from, to: SKILL_VERSION };
28847
29472
  }
28848
29473
  async function repairBoard(root, report3) {
28849
- const board2 = path78.join(root, "docs", "kanban");
28850
- if (!fs83.existsSync(board2)) {
29474
+ const board2 = path81.join(root, "docs", "kanban");
29475
+ if (!fs85.existsSync(board2)) {
28851
29476
  report3.notes.push("no docs/kanban/ here \u2014 run `akb install` to scaffold the board");
28852
29477
  return;
28853
29478
  }
@@ -28877,20 +29502,20 @@ function moveRuntimes(root, board2, report3) {
28877
29502
  }
28878
29503
  }
28879
29504
  function moveLegacyMemory(board2, report3) {
28880
- const memory = path78.join(board2, "memory");
29505
+ const memory = path81.join(board2, "memory");
28881
29506
  const moved = [];
28882
29507
  for (const name of MEMORY_FILES) {
28883
- const from = path78.join(board2, name);
29508
+ const from = path81.join(board2, name);
28884
29509
  const st = statOf2(from);
28885
29510
  if (!st || !st.isFile()) continue;
28886
- fs83.mkdirSync(memory, { recursive: true });
28887
- const to = path78.join(memory, name);
28888
- const there = read4(to);
29511
+ fs85.mkdirSync(memory, { recursive: true });
29512
+ const to = path81.join(memory, name);
29513
+ const there = read5(to);
28889
29514
  if (there === null) {
28890
- fs83.renameSync(from, to);
29515
+ fs85.renameSync(from, to);
28891
29516
  moved.push(name);
28892
- } else if (there === read4(from)) {
28893
- fs83.rmSync(from);
29517
+ } else if (there === read5(from)) {
29518
+ fs85.rmSync(from);
28894
29519
  moved.push(name);
28895
29520
  } else {
28896
29521
  report3.notes.push(`both docs/kanban/${name} and docs/kanban/memory/${name} exist and differ \u2014 merge them by hand`);
@@ -28899,23 +29524,23 @@ function moveLegacyMemory(board2, report3) {
28899
29524
  if (moved.length) report3.did.push(`moved the memory set into docs/kanban/memory/: ${moved.join(", ")}`);
28900
29525
  }
28901
29526
  function dropRecordFile(board2) {
28902
- const file = path78.join(board2, "record.csv");
28903
- if (!fs83.existsSync(file)) return null;
28904
- fs83.rmSync(file);
29527
+ const file = path81.join(board2, "record.csv");
29528
+ if (!fs85.existsSync(file)) return null;
29529
+ fs85.rmSync(file);
28905
29530
  return "removed docs/kanban/record.csv (the planning scores it fed are gone)";
28906
29531
  }
28907
29532
  function dropModuleGoals(board2, report3) {
28908
- const memory = path78.join(board2, "memory");
28909
- if (!fs83.existsSync(memory)) return;
28910
- const root = read4(path78.join(memory, "goal.md"));
29533
+ const memory = path81.join(board2, "memory");
29534
+ if (!fs85.existsSync(memory)) return;
29535
+ const root = read5(path81.join(memory, "goal.md"));
28911
29536
  let dropped = 0;
28912
- for (const entry of fs83.readdirSync(memory, { withFileTypes: true })) {
29537
+ for (const entry of fs85.readdirSync(memory, { withFileTypes: true })) {
28913
29538
  if (!entry.isDirectory()) continue;
28914
- const file = path78.join(memory, entry.name, "goal.md");
28915
- const text5 = read4(file);
29539
+ const file = path81.join(memory, entry.name, "goal.md");
29540
+ const text5 = read5(file);
28916
29541
  if (text5 === null) continue;
28917
29542
  if (text5 === root || text5.includes(UNFILLED) || !goalWords(text5)) {
28918
- fs83.rmSync(file);
29543
+ fs85.rmSync(file);
28919
29544
  dropped++;
28920
29545
  } else {
28921
29546
  report3.notes.push(
@@ -28937,10 +29562,10 @@ var PLAN_LEADS = {
28937
29562
  var GONE_LEADS = ["software-planner", "planner"];
28938
29563
  function planLeads(board2) {
28939
29564
  const leads = { ...PLAN_LEADS };
28940
- const root = path78.join(board2, "workflows");
28941
- for (const entry of fs83.existsSync(root) ? fs83.readdirSync(root, { withFileTypes: true }) : []) {
29565
+ const root = path81.join(board2, "workflows");
29566
+ for (const entry of fs85.existsSync(root) ? fs85.readdirSync(root, { withFileTypes: true }) : []) {
28942
29567
  if (!entry.isDirectory()) continue;
28943
- const meta = splitFrontmatter(read4(path78.join(root, entry.name, "WORKFLOW.md")) ?? "").meta;
29568
+ const meta = splitFrontmatter(read5(path81.join(root, entry.name, "WORKFLOW.md")) ?? "").meta;
28944
29569
  const stages = meta === null ? void 0 : parseYamlBlock(meta).stages;
28945
29570
  const plan = stages && typeof stages === "object" && !Array.isArray(stages) ? stages.plan : void 0;
28946
29571
  const lead = plan && typeof plan === "object" && !Array.isArray(plan) && typeof plan.lead === "string" ? plan.lead.trim() : "";
@@ -28949,40 +29574,40 @@ function planLeads(board2) {
28949
29574
  return leads;
28950
29575
  }
28951
29576
  function movePlanRules(board2, report3) {
28952
- const rules = path78.join(board2, "rules");
29577
+ const rules = path81.join(board2, "rules");
28953
29578
  const moved = [];
28954
29579
  for (const [id, leads] of Object.entries(planLeads(board2))) {
28955
29580
  for (const lead of leads) {
28956
- const from = path78.join(rules, `${lead}.md`);
28957
- const rule = read4(from)?.trim();
29581
+ const from = path81.join(rules, `${lead}.md`);
29582
+ const rule = read5(from)?.trim();
28958
29583
  if (!rule) continue;
28959
- const to = path78.join(rules, "workflows", `${id}.md`);
28960
- const there = read4(to)?.trim() ?? "";
29584
+ const to = path81.join(rules, "workflows", `${id}.md`);
29585
+ const there = read5(to)?.trim() ?? "";
28961
29586
  if (!there.includes(rule)) {
28962
- fs83.mkdirSync(path78.dirname(to), { recursive: true });
28963
- fs83.writeFileSync(to, `${[there, rule].filter(Boolean).join("\n\n")}
29587
+ fs85.mkdirSync(path81.dirname(to), { recursive: true });
29588
+ fs85.writeFileSync(to, `${[there, rule].filter(Boolean).join("\n\n")}
28964
29589
  `);
28965
29590
  moved.push(`${lead} \u2192 ${id}`);
28966
29591
  }
28967
29592
  }
28968
29593
  }
28969
- for (const lead of GONE_LEADS) fs83.rmSync(path78.join(rules, `${lead}.md`), { force: true });
29594
+ for (const lead of GONE_LEADS) fs85.rmSync(path81.join(rules, `${lead}.md`), { force: true });
28970
29595
  if (moved.length) report3.did.push(`made each plan agent's rule its workflow's card rule (docs/kanban/rules/workflows/): ${moved.join(", ")}`);
28971
29596
  }
28972
29597
  function noteOldWorkflows(board2, report3) {
28973
29598
  const old = (dir, file, keys) => {
28974
- const root = path78.join(board2, dir);
28975
- return (fs83.existsSync(root) ? fs83.readdirSync(root, { withFileTypes: true }) : []).filter((e) => e.isDirectory() && keys.test(splitFrontmatter(read4(path78.join(root, e.name, file)) ?? "").meta ?? "")).map((e) => `docs/kanban/${dir}/${e.name}/${file}`);
29599
+ const root = path81.join(board2, dir);
29600
+ return (fs85.existsSync(root) ? fs85.readdirSync(root, { withFileTypes: true }) : []).filter((e) => e.isDirectory() && keys.test(splitFrontmatter(read5(path81.join(root, e.name, file)) ?? "").meta ?? "")).map((e) => `docs/kanban/${dir}/${e.name}/${file}`);
28976
29601
  };
28977
- const files = [...old("workflows", "WORKFLOW.md", /^(stages|delivers):/m), ...old("agents", "AGENT.md", /^\s+(hook|lead):/m)];
28978
- if (files.length) {
29602
+ const files2 = [...old("workflows", "WORKFLOW.md", /^(stages|delivers):/m), ...old("agents", "AGENT.md", /^\s+(hook|lead):/m)];
29603
+ if (files2.length) {
28979
29604
  report3.notes.push(
28980
- `written in the old form, so not read until rewritten \u2014 list a workflow's agents under \`previews\`, \`land\` and \`scheduled\`, and drop \`hook\`/\`lead\` from each agent (or ask the Workflow builder): ${files.join(", ")}`
29605
+ `written in the old form, so not read until rewritten \u2014 list a workflow's agents under \`previews\`, \`land\` and \`scheduled\`, and drop \`hook\`/\`lead\` from each agent (or ask the Workflow builder): ${files2.join(", ")}`
28981
29606
  );
28982
29607
  }
28983
29608
  }
28984
29609
  function checkConfig(board2, report3) {
28985
- const current = read4(path78.join(board2, "config.md"));
29610
+ const current = read5(path81.join(board2, "config.md"));
28986
29611
  if (!current) return;
28987
29612
  const added = missingConfigKeys(current);
28988
29613
  if (added.length) {
@@ -28992,7 +29617,7 @@ function checkConfig(board2, report3) {
28992
29617
  }
28993
29618
  }
28994
29619
  function checkModules(board2, report3) {
28995
- const map2 = read4(path78.join(board2, "modules.md"));
29620
+ const map2 = read5(path81.join(board2, "modules.md"));
28996
29621
  if (map2 && map2.includes(UNFILLED)) {
28997
29622
  report3.notes.push(
28998
29623
  "docs/kanban/modules.md is still blank \u2014 write it from the repo (`akb guide module-map`)"
@@ -29046,13 +29671,13 @@ function declareSetup(program2, cli) {
29046
29671
  program2.version(SKILL_VERSION, "-v, --version", "print this version").addHelpText("beforeAll", spelled(cli.program));
29047
29672
  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");
29048
29673
  const where = (cmd) => {
29049
- const dir = path79.resolve(String(cmd.optsWithGlobals().dir ?? cli.cwd));
29050
- if (!fs84.existsSync(dir)) throw new BoardError(`no such folder: ${dir}`, { kind: "no-such-folder", dir });
29674
+ const dir = path82.resolve(String(cmd.optsWithGlobals().dir ?? cli.cwd));
29675
+ if (!fs86.existsSync(dir)) throw new BoardError(`no such folder: ${dir}`, { kind: "no-such-folder", dir });
29051
29676
  return dir;
29052
29677
  };
29053
29678
  const boardOf = (cmd) => {
29054
29679
  const named2 = cmd.optsWithGlobals().board;
29055
- return named2 ? path79.resolve(cli.cwd, named2) : null;
29680
+ return named2 ? path82.resolve(cli.cwd, named2) : null;
29056
29681
  };
29057
29682
  dirOption(program2.command("install")).option("--board <dir>", "put the board here instead of docs/kanban/ (relative to this folder)").summary("scaffold docs/kanban/ \u2014 the board, and nothing else").description(
29058
29683
  "Installing writes the board and nothing outside docs/kanban/. Driving that board from a coding agent is a later extra \u2014 `akb skill install`, or the button in the board UI under Configuration \u2192 Agent setup. Safe to run twice."
@@ -29125,7 +29750,7 @@ function invokedDirectly() {
29125
29750
  const entry = process.argv[1];
29126
29751
  if (!entry) return false;
29127
29752
  try {
29128
- return fs85.realpathSync(entry) === fs85.realpathSync(SELF3);
29753
+ return fs87.realpathSync(entry) === fs87.realpathSync(SELF3);
29129
29754
  } catch {
29130
29755
  return false;
29131
29756
  }