wildcrane 0.11.3 → 0.11.4

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.
Files changed (2) hide show
  1. package/dist/kanban.mjs +371 -616
  2. package/package.json +2 -2
package/dist/kanban.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // ai4kanban 0.11.3 — built from the CLI's TypeScript sources by cli/scripts/build.mjs.
2
+ // ai4kanban 0.11.4 — built from the CLI's TypeScript sources by cli/scripts/build.mjs.
3
3
  // Do not edit: every change belongs in cli/src/, and a build overwrites this file.
4
4
  import { createRequire as __ai4kanbanRequire } from 'node:module'
5
5
  const require = __ai4kanbanRequire(import.meta.url)
@@ -1168,8 +1168,8 @@ var require_command = __commonJS({
1168
1168
  "node_modules/commander/lib/command.js"(exports) {
1169
1169
  var EventEmitter = __require("node:events").EventEmitter;
1170
1170
  var childProcess = __require("node:child_process");
1171
- var path85 = __require("node:path");
1172
- var fs88 = __require("node:fs");
1171
+ var path86 = __require("node:path");
1172
+ var fs87 = __require("node:fs");
1173
1173
  var process2 = __require("node:process");
1174
1174
  var { Argument: Argument2, humanReadableArgName } = require_argument();
1175
1175
  var { CommanderError: CommanderError2 } = require_error();
@@ -2150,7 +2150,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2150
2150
  * @param {string} subcommandName
2151
2151
  */
2152
2152
  _checkForMissingExecutable(executableFile, executableDir, subcommandName) {
2153
- if (fs88.existsSync(executableFile)) return;
2153
+ if (fs87.existsSync(executableFile)) return;
2154
2154
  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";
2155
2155
  const executableMissing = `'${executableFile}' does not exist
2156
2156
  - if '${subcommandName}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
@@ -2168,11 +2168,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
2168
2168
  let launchWithNode = false;
2169
2169
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
2170
2170
  function findFile(baseDir, baseName) {
2171
- const localBin = path85.resolve(baseDir, baseName);
2172
- if (fs88.existsSync(localBin)) return localBin;
2173
- if (sourceExt.includes(path85.extname(baseName))) return void 0;
2171
+ const localBin = path86.resolve(baseDir, baseName);
2172
+ if (fs87.existsSync(localBin)) return localBin;
2173
+ if (sourceExt.includes(path86.extname(baseName))) return void 0;
2174
2174
  const foundExt = sourceExt.find(
2175
- (ext) => fs88.existsSync(`${localBin}${ext}`)
2175
+ (ext) => fs87.existsSync(`${localBin}${ext}`)
2176
2176
  );
2177
2177
  if (foundExt) return `${localBin}${foundExt}`;
2178
2178
  return void 0;
@@ -2184,21 +2184,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
2184
2184
  if (this._scriptPath) {
2185
2185
  let resolvedScriptPath;
2186
2186
  try {
2187
- resolvedScriptPath = fs88.realpathSync(this._scriptPath);
2187
+ resolvedScriptPath = fs87.realpathSync(this._scriptPath);
2188
2188
  } catch {
2189
2189
  resolvedScriptPath = this._scriptPath;
2190
2190
  }
2191
- executableDir = path85.resolve(
2192
- path85.dirname(resolvedScriptPath),
2191
+ executableDir = path86.resolve(
2192
+ path86.dirname(resolvedScriptPath),
2193
2193
  executableDir
2194
2194
  );
2195
2195
  }
2196
2196
  if (executableDir) {
2197
2197
  let localFile = findFile(executableDir, executableFile);
2198
2198
  if (!localFile && !subcommand._executableFile && this._scriptPath) {
2199
- const legacyName = path85.basename(
2199
+ const legacyName = path86.basename(
2200
2200
  this._scriptPath,
2201
- path85.extname(this._scriptPath)
2201
+ path86.extname(this._scriptPath)
2202
2202
  );
2203
2203
  if (legacyName !== this._name) {
2204
2204
  localFile = findFile(
@@ -2209,7 +2209,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2209
2209
  }
2210
2210
  executableFile = localFile || executableFile;
2211
2211
  }
2212
- launchWithNode = sourceExt.includes(path85.extname(executableFile));
2212
+ launchWithNode = sourceExt.includes(path86.extname(executableFile));
2213
2213
  let proc;
2214
2214
  if (process2.platform !== "win32") {
2215
2215
  if (launchWithNode) {
@@ -3056,7 +3056,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
3056
3056
  * @return {Command}
3057
3057
  */
3058
3058
  nameFromFilename(filename) {
3059
- this._name = path85.basename(filename, path85.extname(filename));
3059
+ this._name = path86.basename(filename, path86.extname(filename));
3060
3060
  return this;
3061
3061
  }
3062
3062
  /**
@@ -3070,9 +3070,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
3070
3070
  * @param {string} [path]
3071
3071
  * @return {(string|null|Command)}
3072
3072
  */
3073
- executableDir(path86) {
3074
- if (path86 === void 0) return this._executableDir;
3075
- this._executableDir = path86;
3073
+ executableDir(path87) {
3074
+ if (path87 === void 0) return this._executableDir;
3075
+ this._executableDir = path87;
3076
3076
  return this;
3077
3077
  }
3078
3078
  /**
@@ -3651,7 +3651,7 @@ function useProjectState() {
3651
3651
  ensureAkbDir();
3652
3652
  return ensureProjectState(KANBAN, REPO_ROOT);
3653
3653
  }
3654
- var REPO_ROOT, KANBAN, TODO, ARCHIVE, ARCHIVE_MD, README, MODULES_MD, CONFIG, KANBAN_GITIGNORE, RELEASES, RELEASE_SUMMARIES, AGENTS, WORKFLOWS, LEGACY_AGENTS, ASSETS, BOARD_STATE, MOCKUPS, MEMORY, AGENT_MEMORY, PROJECT_MD, LOCK, UI_CONFIG, LEGACY_UI_CONFIG, LOCAL_CONFIG, ENV_FILE, SESSIONS, SESSIONS_DIR, SESSIONS_LOCK2, INDEX_LOCK2, DELIVERIES, PLANS, PLANS_ARCHIVE, RULES, TRIAGE, SIGNALS_ARCHIVED, SIGNALS_DISMISSED, SIGNALS_FILES, SIGNALS_OLD_INBOX, SIGNALS_OLD_HANDLED, AKB_DIR, ROOT_GITIGNORE, AKB_IGNORE_LINE, BOARD_FLAG, rel, BOARD_PATH_IN_TEXT, ASSETS_PATH_IN_TEXT, MOCKUPS_PATH_IN_TEXT, BOARD_STATE_IN_TEXT;
3654
+ var REPO_ROOT, KANBAN, TODO, ARCHIVE, REJECTIONS_TO_REVIEW, ARCHIVE_MD, README, MODULES_MD, CONFIG, KANBAN_GITIGNORE, RELEASES, RELEASE_SUMMARIES, AGENTS, WORKFLOWS, LEGACY_AGENTS, ASSETS, BOARD_STATE, MOCKUPS, MEMORY, AGENT_MEMORY, PROJECT_MD, LOCK, UI_CONFIG, LEGACY_UI_CONFIG, LOCAL_CONFIG, ENV_FILE, SESSIONS, SESSIONS_DIR, SESSIONS_LOCK2, INDEX_LOCK2, DELIVERIES, PLANS, PLANS_ARCHIVE, RULES, TRIAGE, SIGNALS_ARCHIVED, SIGNALS_DISMISSED, SIGNALS_FILES, SIGNALS_OLD_INBOX, SIGNALS_OLD_HANDLED, AKB_DIR, ROOT_GITIGNORE, AKB_IGNORE_LINE, BOARD_FLAG, rel, BOARD_PATH_IN_TEXT, ASSETS_PATH_IN_TEXT, MOCKUPS_PATH_IN_TEXT, BOARD_STATE_IN_TEXT;
3655
3655
  var init_paths = __esm({
3656
3656
  "src/lib/paths.ts"() {
3657
3657
  "use strict";
@@ -3661,6 +3661,7 @@ var init_paths = __esm({
3661
3661
  KANBAN = "";
3662
3662
  TODO = "";
3663
3663
  ARCHIVE = "";
3664
+ REJECTIONS_TO_REVIEW = ".sync";
3664
3665
  ARCHIVE_MD = "";
3665
3666
  README = "";
3666
3667
  MODULES_MD = "";
@@ -4019,11 +4020,12 @@ function insideRun() {
4019
4020
  function runEnv(env2, value) {
4020
4021
  return { ...env2, [RUN_ENV]: value };
4021
4022
  }
4022
- var RUN_ENV, STOP_ENV, SESSION_VARS;
4023
+ var RUN_ENV, SCHEDULED_AGENTS_ENV, STOP_ENV, SESSION_VARS;
4023
4024
  var init_env = __esm({
4024
4025
  "src/lib/agent/env.ts"() {
4025
4026
  "use strict";
4026
4027
  RUN_ENV = "KANBAN_RUN";
4028
+ SCHEDULED_AGENTS_ENV = "AI4KANBAN_SCHEDULED_AGENTS";
4027
4029
  STOP_ENV = "KANBAN_STOP_ID";
4028
4030
  SESSION_VARS = {
4029
4031
  "claude-code": "CLAUDE_CODE_SESSION_ID",
@@ -4975,8 +4977,8 @@ var init_package = __esm({
4975
4977
  "package.json"() {
4976
4978
  package_default = {
4977
4979
  name: "wildcrane",
4978
- version: "0.11.3",
4979
- akbContract: 1,
4980
+ version: "0.11.4",
4981
+ akbContract: 2,
4980
4982
  description: "WildCrane's `akb` command \u2014 scaffold the Markdown board under docs/kanban/, put an agent on a card, and carry the flows the agent works by.",
4981
4983
  keywords: [
4982
4984
  "kanban",
@@ -7868,10 +7870,10 @@ function findRollout(threadId) {
7868
7870
  }
7869
7871
  for (const name of names4) {
7870
7872
  if (!name.startsWith("rollout-") || !name.endsWith(tail2)) continue;
7871
- const path85 = join(dir, name);
7873
+ const path86 = join(dir, name);
7872
7874
  try {
7873
- const at = statSync(path85).mtimeMs;
7874
- if (!newest2 || at > newest2.at) newest2 = { path: path85, at };
7875
+ const at = statSync(path86).mtimeMs;
7876
+ if (!newest2 || at > newest2.at) newest2 = { path: path86, at };
7875
7877
  } catch {
7876
7878
  }
7877
7879
  }
@@ -7879,11 +7881,11 @@ function findRollout(threadId) {
7879
7881
  return newest2?.path;
7880
7882
  }
7881
7883
  function readCodexRunFacts(threadId) {
7882
- const path85 = findRollout(threadId);
7883
- if (!path85) return void 0;
7884
+ const path86 = findRollout(threadId);
7885
+ if (!path86) return void 0;
7884
7886
  let raw;
7885
7887
  try {
7886
- raw = readFileSync(path85, "utf8");
7888
+ raw = readFileSync(path86, "utf8");
7887
7889
  } catch {
7888
7890
  return void 0;
7889
7891
  }
@@ -8011,8 +8013,8 @@ function renderCompleted(item) {
8011
8013
  const changes = Array.isArray(item.changes) ? item.changes : [];
8012
8014
  return changes.map((raw) => {
8013
8015
  const change2 = obj(raw);
8014
- const path85 = str2(change2.path);
8015
- return path85 ? `\u23FA ${str2(change2.kind) || "change"}(${path85})
8016
+ const path86 = str2(change2.path);
8017
+ return path86 ? `\u23FA ${str2(change2.kind) || "change"}(${path86})
8016
8018
  ` : "";
8017
8019
  }).join("");
8018
8020
  }
@@ -8271,10 +8273,10 @@ function readKimiRunFacts(sessionId) {
8271
8273
  const total = { input: 0, cacheCreation: 0, cacheRead: 0, output: 0 };
8272
8274
  let model;
8273
8275
  let holding = 0;
8274
- for (const { path: path85, main } of wireFiles(entry.sessionDir)) {
8276
+ for (const { path: path86, main } of wireFiles(entry.sessionDir)) {
8275
8277
  let raw;
8276
8278
  try {
8277
- raw = readFileSync2(path85, "utf8");
8279
+ raw = readFileSync2(path86, "utf8");
8278
8280
  } catch {
8279
8281
  continue;
8280
8282
  }
@@ -8555,8 +8557,8 @@ var init_tail = __esm({
8555
8557
  import fs22 from "node:fs";
8556
8558
  function whereHint(update2, title) {
8557
8559
  const locations = Array.isArray(update2.locations) ? update2.locations : [];
8558
- const path85 = str2(obj(locations[0]).path);
8559
- return path85 && !title.includes(path85) ? hint(path85) : "";
8560
+ const path86 = str2(obj(locations[0]).path);
8561
+ return path86 && !title.includes(path86) ? hint(path86) : "";
8560
8562
  }
8561
8563
  function saidBy(update2) {
8562
8564
  const content = Array.isArray(update2.content) ? update2.content : [];
@@ -11782,7 +11784,7 @@ var init_flows = __esm({
11782
11784
  argument: "<id> [note...]",
11783
11785
  argumentNote: NOTE,
11784
11786
  gloss: "build the card",
11785
- options: [RUNTIME_OPTION]
11787
+ options: [RUNTIME_OPTION, ...CARRY_OPTIONS]
11786
11788
  },
11787
11789
  {
11788
11790
  command: "conflict",
@@ -11861,9 +11863,8 @@ var init_flows = __esm({
11861
11863
  action: "review-dismissals",
11862
11864
  argument: "",
11863
11865
  gloss: "learn what the user does not want from their rejection and dismissal reasons",
11864
- options: [{ flags: "--all", description: "every reason on the board, not only those since the last review \u2014 a Runner's board" }],
11865
11866
  more: [
11866
- "Reads the reasons written since the last review that passed, and withdraws what rested on an item since restored."
11867
+ "Reads the reasons Cloud has not had learned, and withdraws what rested on an item since restored."
11867
11868
  ]
11868
11869
  },
11869
11870
  // The project writer's one flow (#1268). Typed bare: it acts on the project.
@@ -11958,21 +11959,10 @@ function parseStamp(raw) {
11958
11959
  const d = new Date(Number(m[1]), Number(m[2]) - 1, Number(m[3]), Number(m[4]), Number(m[5]), 0, 0);
11959
11960
  return Number.isNaN(d.getTime()) ? null : d;
11960
11961
  }
11961
- function nextDue(lastRun, cadence) {
11962
- const c = typeof cadence === "string" ? parseCadence(cadence) : cadence;
11963
- if (!c) return null;
11964
- const last = parseStamp(lastRun);
11965
- if (!last) return /* @__PURE__ */ new Date(0);
11966
- if (c.unit === "m") return new Date(last.getTime() + c.n * 6e4);
11967
- if (c.unit === "h") return new Date(last.getTime() + c.n * 36e5);
11968
- const [hour, minute] = c.at ? c.at.split(":").map(Number) : [last.getHours(), last.getMinutes()];
11969
- return new Date(last.getFullYear(), last.getMonth(), last.getDate() + c.n, hour, minute, 0, 0);
11970
- }
11971
- var CADENCE_FORMS, AUTO_CADENCE, isAuto, CADENCE_RE, pad;
11962
+ var AUTO_CADENCE, isAuto, CADENCE_RE, pad;
11972
11963
  var init_cadence = __esm({
11973
11964
  "src/lib/cadence.ts"() {
11974
11965
  "use strict";
11975
- CADENCE_FORMS = "<N>m (minutes), <N>h (hours), <N>d (days), or <N>d at HH:MM (whole days, at that time of day) \u2014 e.g. 30m, 6h, 1d at 09:30";
11976
11966
  AUTO_CADENCE = "auto";
11977
11967
  isAuto = (cadence) => cadence.trim().toLowerCase() === AUTO_CADENCE;
11978
11968
  CADENCE_RE = /^(\d+)\s*([mhd])(?:\s+at\s+(\d{1,2}):(\d{2}))?$/i;
@@ -11981,7 +11971,7 @@ var init_cadence = __esm({
11981
11971
  });
11982
11972
 
11983
11973
  // src/lib/agent/types.ts
11984
- var SPECIALIST_ACTIONS, CARD_FREE_ACTIONS, holdsCard, REFINE_ACTIONS, RETIRED, isRetired, refusal, RETIRED_WORKFLOWS, workflowDeleted, SCHEDULED_CADENCE;
11974
+ var SPECIALIST_ACTIONS, CARD_FREE_ACTIONS, holdsCard, REFINE_ACTIONS, RETIRED, isRetired, refusal, RETIRED_WORKFLOWS, workflowDeleted;
11985
11975
  var init_types3 = __esm({
11986
11976
  "src/lib/agent/types.ts"() {
11987
11977
  "use strict";
@@ -11997,7 +11987,6 @@ var init_types3 = __esm({
11997
11987
  refusal = (reason, error, args) => args ? { error, reason, args } : { error, reason };
11998
11988
  RETIRED_WORKFLOWS = ["content"];
11999
11989
  workflowDeleted = (id, known2) => !!id && !known2.includes(id) && !RETIRED_WORKFLOWS.includes(id);
12000
- SCHEDULED_CADENCE = "1d";
12001
11990
  }
12002
11991
  });
12003
11992
 
@@ -12218,16 +12207,6 @@ function setSecret(name, value) {
12218
12207
  return { ok: false, ...refusal("fileWrite", `couldn't write ${ENV_FILE}: ${why}`, { path: ENV_FILE, details: why }) };
12219
12208
  }
12220
12209
  }
12221
- function readSchedule(key) {
12222
- const block = configBlock(safeConfig()[key]);
12223
- const saved = stringIn(block, "cadence");
12224
- return {
12225
- enabled: block.off !== true,
12226
- cadence: saved && parseCadence(saved) ? saved : AUTO_CADENCE,
12227
- lastRun: stringIn(block, "lastRun"),
12228
- ...stringIn(block, "since") ? { since: stringIn(block, "since") } : {}
12229
- };
12230
- }
12231
12210
  function stampSchedule(key, when) {
12232
12211
  return writeConfig((cfg) => {
12233
12212
  cfg[key] = { ...configBlock(cfg[key]), lastRun: formatStamp(when) };
@@ -12260,7 +12239,7 @@ function dropReflected(ids) {
12260
12239
  cfg.reflectQueue = queueIn(cfg).filter((id) => !ids.includes(id));
12261
12240
  });
12262
12241
  }
12263
- var SILENCE_MINUTES, BACKGROUND_WAIT, BOARD_SPEC_KEYS, IGNORE_BLOCK, stringIn, stampMemoryPrune, queueIn, reflectQueue, dismissalReview, stampDismissalReview, stampProjectDescription;
12242
+ var SILENCE_MINUTES, BACKGROUND_WAIT, BOARD_SPEC_KEYS, IGNORE_BLOCK, stringIn, stampMemoryPrune, queueIn, reflectQueue, stampProjectDescription;
12264
12243
  var init_settings2 = __esm({
12265
12244
  "src/lib/agent/settings.ts"() {
12266
12245
  "use strict";
@@ -12277,8 +12256,6 @@ var init_settings2 = __esm({
12277
12256
  stampMemoryPrune = (when = /* @__PURE__ */ new Date()) => void stampSchedule("memoryPrune", when);
12278
12257
  queueIn = (cfg) => Array.isArray(cfg.reflectQueue) ? cfg.reflectQueue.filter((id) => Number.isInteger(id) && id > 0) : [];
12279
12258
  reflectQueue = () => queueIn(safeConfig());
12280
- dismissalReview = () => readSchedule("dismissalReview");
12281
- stampDismissalReview = (when) => void stampSchedule("dismissalReview", when);
12282
12259
  stampProjectDescription = (when) => void stampSchedule("projectDescription", when);
12283
12260
  }
12284
12261
  });
@@ -13353,7 +13330,7 @@ ${rest}`);
13353
13330
  fs31.rmSync(goal, { force: true });
13354
13331
  return words ? `moved the goal to the top of ${rel(agentMemoryFile(PLANNER, "decisions.md"))}` : "removed the empty goal";
13355
13332
  }
13356
- var isGlobalMemory, PLANNER, PLANNER_MEMORY_FILES, PROPOSER, PROPOSER_MISSED, proposerMissedFile, BOARD_MEMORY_FILES, STARTERS, onlyStarter, setOf, RESERVED_MEMORY_DIR, KEPT_MEMORY_FOLDERS, agentMemoryDir, agentMemoryFile, writtenMemoryNames, memoryNamesOf, agentMemoryFiles, legacyAgentMemoryFile, HEADINGS, HEADING_RE, stripHeading, FOLDED_UNDER, moveMemory, RESERVED_MODULE_NAMES, isModule, moduleMemoryFile, plannerCopies, memoryFile, ENTRY_RE, entriesOf2, demote, promote, PRODUCT_STARTER, SEEDED_GOAL_PARAGRAPHS;
13333
+ var isGlobalMemory, PLANNER, PLANNER_MEMORY_FILES, BOARD_MEMORY_FILES, STARTERS, onlyStarter, setOf, RESERVED_MEMORY_DIR, KEPT_MEMORY_FOLDERS, agentMemoryDir, agentMemoryFile, writtenMemoryNames, memoryNamesOf, agentMemoryFiles, legacyAgentMemoryFile, HEADINGS, HEADING_RE, stripHeading, FOLDED_UNDER, moveMemory, RESERVED_MODULE_NAMES, isModule, moduleMemoryFile, plannerCopies, memoryFile, ENTRY_RE, entriesOf2, demote, promote, PRODUCT_STARTER, SEEDED_GOAL_PARAGRAPHS;
13357
13334
  var init_memory = __esm({
13358
13335
  "src/lib/memory.ts"() {
13359
13336
  "use strict";
@@ -13364,9 +13341,6 @@ var init_memory = __esm({
13364
13341
  isGlobalMemory = (name) => name in BUNDLED_MEMORY_FILES || fs31.existsSync(path32.join(MEMORY, name, "MEMORY.md"));
13365
13342
  PLANNER = "planner";
13366
13343
  PLANNER_MEMORY_FILES = ["decisions.md", "rejected.md", "redesign.md"];
13367
- PROPOSER = "proposer";
13368
- PROPOSER_MISSED = "missed.md";
13369
- proposerMissedFile = () => rel(agentMemoryFile(PROPOSER, PROPOSER_MISSED));
13370
13344
  BOARD_MEMORY_FILES = ["readme.md", "project.md"];
13371
13345
  STARTERS = {
13372
13346
  "readme.md": `# Shipped
@@ -14023,7 +13997,7 @@ function switchWorkflowAgent(id, agent, on) {
14023
13997
  if (!listedHere(flow, wanted)) {
14024
13998
  if (!onBoard().has(wanted)) return { ok: false, ...refusal("agentNotFound", `this board has no \`${wanted}\` agent`, { agent: wanted }) };
14025
13999
  if (agentWorkflow(wanted) === id && flow.scheduled.some((h) => h.agent === wanted)) {
14026
- return { ok: false, ...refusal("agentNotListed", `\`${wanted}\` is a scheduled agent of "${flow.name}" \u2014 switch it with \`akb workflow schedule\``, { agent: wanted, name: flow.name, workflow: flow.id }) };
14000
+ return { ok: false, ...refusal("agentNotListed", `\`${wanted}\` is a scheduled agent of "${flow.name}" \u2014 switch it on the project's Agents page`, { agent: wanted, name: flow.name, workflow: flow.id }) };
14027
14001
  }
14028
14002
  const refused = elsewhere(wanted, id);
14029
14003
  if (refused) return refused;
@@ -14080,6 +14054,11 @@ function scheduledMembers(flow) {
14080
14054
  const known2 = onBoard();
14081
14055
  return flow.scheduled.filter((h) => known2.has(h.agent));
14082
14056
  }
14057
+ function listedScheduled(flow) {
14058
+ const def = (flow.builtIn ? BUILTINS : boardWorkflowDefs().defs).find((d) => d.id === flow.id);
14059
+ const known2 = onBoard();
14060
+ return (def?.scheduled ?? []).map((s) => s.agent).filter((agent) => known2.has(agent));
14061
+ }
14083
14062
  function setScheduled(id, change2, claim = "") {
14084
14063
  const flow = workflowById(id);
14085
14064
  if (!flow) return notFound(id);
@@ -14092,48 +14071,6 @@ function setScheduled(id, change2, claim = "") {
14092
14071
  block.stages = stages;
14093
14072
  });
14094
14073
  }
14095
- function switchWorkflowScheduled(id, agent, on, now = /* @__PURE__ */ new Date()) {
14096
- const wanted = canonicalSpecAgent(agent.trim());
14097
- return setScheduled(id, (rows) => {
14098
- let one = agentWorkflow(wanted) === id ? rows.find((h) => h.agent === wanted) : void 0;
14099
- if (!one) {
14100
- if (!onBoard().has(wanted)) return { ok: false, ...refusal("agentNotFound", `this board has no \`${wanted}\` agent`, { agent: wanted }) };
14101
- const flow = workflowById(id);
14102
- if (listedHere(flow, wanted)) return notScheduled(wanted, flow);
14103
- const refused = elsewhere(wanted, id);
14104
- if (refused) return refused;
14105
- one = { agent: wanted, extra: "", off: true, cadence: AUTO_CADENCE, lastRun: "" };
14106
- rows.push(one);
14107
- }
14108
- if (!on) one.off = true;
14109
- else if (one.off) {
14110
- delete one.off;
14111
- one.since = formatStamp(now);
14112
- }
14113
- });
14114
- }
14115
- function setWorkflowScheduledCadence(id, agent, cadence) {
14116
- const wanted = canonicalSpecAgent(agent.trim());
14117
- const said3 = cadence.trim();
14118
- const next = !said3 || isAuto(said3) ? AUTO_CADENCE : said3;
14119
- if (!isAuto(next) && !parseCadence(next)) {
14120
- const formats = `${AUTO_CADENCE}, or ${CADENCE_FORMS}`;
14121
- return { ok: false, ...refusal("cadence", `"${next}" isn't a cadence \u2014 use ${formats}`, { cadence: next, formats }) };
14122
- }
14123
- return setScheduled(id, (rows) => {
14124
- const one = rows.find((h) => h.agent === wanted);
14125
- if (!one) return notScheduled(wanted, workflowById(id));
14126
- one.cadence = next;
14127
- });
14128
- }
14129
- function setWorkflowScheduledExtra(id, agent, extra) {
14130
- const wanted = canonicalSpecAgent(agent.trim());
14131
- return setScheduled(id, (rows) => {
14132
- const one = rows.find((h) => h.agent === wanted);
14133
- if (!one) return notScheduled(wanted, workflowById(id));
14134
- one.extra = extra;
14135
- });
14136
- }
14137
14074
  function stampScheduledRun(id, agent, startedAt) {
14138
14075
  return setScheduled(
14139
14076
  id,
@@ -14182,7 +14119,7 @@ function frozenWorkflow(id) {
14182
14119
  land: landAgents(flow).map(row)
14183
14120
  };
14184
14121
  }
14185
- var LAND_ROLES, DEFAULT_WORKFLOW, BUILTINS, BUILTIN_WORKFLOW_IDS, isBuiltinWorkflow, builtinDescription, workflowsBlock, storedStages, PREVIEWS, LAND, SCHEDULE, toRow, scheduledRow, OLD_STAGE_KEYS, savedOldStages, workflowFileProblems, allListed, resolved, agentAfter, agentKeyProblems, agentWorkflow, workflows, workflowById, onBoard, previewMembers, previewAgents, landAgents, archivesOnApproval, workflowRuleFile, save2, ownDef, notFound, trimmedName, numbered, freeId2, nameTaken, listedHere, scheduledAgent, scheduledClock, notScheduled, cardWorkflowId, cardWorkflow, cardStages, cardStage;
14122
+ var LAND_ROLES, DEFAULT_WORKFLOW, BUILTINS, BUILTIN_WORKFLOW_IDS, isBuiltinWorkflow, builtinDescription, workflowsBlock, storedStages, PREVIEWS, LAND, SCHEDULE, toRow, scheduledRow, OLD_STAGE_KEYS, savedOldStages, workflowFileProblems, allListed, resolved, agentAfter, agentKeyProblems, agentWorkflow, workflows, workflowById, onBoard, previewMembers, previewAgents, landAgents, archivesOnApproval, workflowRuleFile, save2, ownDef, notFound, trimmedName, numbered, freeId2, nameTaken, listedHere, scheduledAgent, cardWorkflowId, cardWorkflow, cardStages, cardStage;
14186
14123
  var init_workflows = __esm({
14187
14124
  "src/lib/agent/workflows.ts"() {
14188
14125
  "use strict";
@@ -14286,11 +14223,6 @@ var init_workflows = __esm({
14286
14223
  const flow = workflowById(id);
14287
14224
  return flow ? scheduledMembers(flow).find((h) => h.agent === canonicalSpecAgent(agent.trim())) : void 0;
14288
14225
  };
14289
- scheduledClock = (one) => [one.lastRun, one.since ?? ""].filter((stamp4) => parseStamp(stamp4)).sort().pop() ?? "";
14290
- notScheduled = (agent, flow) => ({
14291
- ok: false,
14292
- ...refusal("agentNotScheduled", `\`${agent}\` is not a scheduled agent of "${flow.name}"`, { agent, name: flow.name, workflow: flow.id })
14293
- });
14294
14226
  cardWorkflowId = (id) => cardMeta(id)?.workflow ?? "";
14295
14227
  cardWorkflow = (id) => workflowFor(typeof id === "number" ? cardWorkflowId(id) : "");
14296
14228
  cardStages = (meta, flow) => previewStages(flow, meta?.previews ?? []);
@@ -14615,7 +14547,7 @@ function agentRoster() {
14615
14547
  ...specialists.filter((a) => !a.builtIn)
14616
14548
  ];
14617
14549
  }
14618
- var PLANNER_DECISIONS, PLANNER_REJECTED, PLANNER_REDESIGN, DISCUSSION_HELPER, MEMORY_PRUNER, MEMORY_REVIEWER, DISMISSAL_REVIEWER, PROJECT_WRITER, SMALL_FIX, PROPOSER2, PLANNER_ROLE, BOARD_ROLES, DISCUSSION_ROLE, ROLE_NAMES, PLANNING_ROLE, roles, roleForFlow, roleNamed, hiddenRole, stageContractProblems, memoryOf, ownMemoryOf, agentNames;
14550
+ var PLANNER_DECISIONS, PLANNER_REJECTED, PLANNER_REDESIGN, DISCUSSION_HELPER, MEMORY_PRUNER, MEMORY_REVIEWER, DISMISSAL_REVIEWER, PROJECT_WRITER, SMALL_FIX, PROPOSER, PLANNER_ROLE, BOARD_ROLES, DISCUSSION_ROLE, ROLE_NAMES, PLANNING_ROLE, roles, roleForFlow, roleNamed, hiddenRole, stageContractProblems, memoryOf, ownMemoryOf, agentNames;
14619
14551
  var init_roles = __esm({
14620
14552
  "src/lib/agent/roles.ts"() {
14621
14553
  "use strict";
@@ -14654,10 +14586,10 @@ var init_roles = __esm({
14654
14586
  memory: ["memory/project.md"]
14655
14587
  };
14656
14588
  SMALL_FIX = "A small fix is a small change whose workflow needs none of its previews.";
14657
- PROPOSER2 = {
14589
+ PROPOSER = {
14658
14590
  name: "proposer",
14659
14591
  gloss: "proposes the work a finished card leaves behind",
14660
- memory: [`memory/agents/proposer/${PROPOSER_MISSED}`],
14592
+ memory: [],
14661
14593
  settings: [
14662
14594
  {
14663
14595
  key: "small-fixes",
@@ -14704,7 +14636,7 @@ var init_roles = __esm({
14704
14636
  MEMORY_REVIEWER,
14705
14637
  DISMISSAL_REVIEWER,
14706
14638
  PROJECT_WRITER,
14707
- PROPOSER2
14639
+ PROPOSER
14708
14640
  ];
14709
14641
  DISCUSSION_ROLE = DISCUSSION_HELPER.name;
14710
14642
  ROLE_NAMES = BOARD_ROLES.map((r) => r.name);
@@ -15598,11 +15530,6 @@ function parse(file, lenient = false) {
15598
15530
  cardId: /^\d+$/.test(held4.card_id ?? "") ? Number(held4.card_id) : null,
15599
15531
  archivedAt: held4.archived_at || "",
15600
15532
  contentKept: held4.content_kept !== "false",
15601
- verdict: VERDICTS.includes(held4.verdict) ? held4.verdict : "",
15602
- verdictReason: REASONS.includes(held4.verdict_reason) ? held4.verdict_reason : "",
15603
- verdictCard: /^\d+$/.test(held4.verdict_card ?? "") ? Number(held4.verdict_card) : null,
15604
- dropConfidence: confidenceOf(held4.drop_confidence),
15605
- doConfidence: confidenceOf(held4.do_confidence),
15606
15533
  relPath: boardRel(file)
15607
15534
  };
15608
15535
  }
@@ -15638,11 +15565,6 @@ function writeSignal(incoming, importedAt, slug2 = "") {
15638
15565
  cardId: null,
15639
15566
  archivedAt: "",
15640
15567
  contentKept: true,
15641
- verdict: "",
15642
- verdictReason: "",
15643
- verdictCard: null,
15644
- dropConfidence: null,
15645
- doConfidence: null,
15646
15568
  relPath: ""
15647
15569
  };
15648
15570
  const file = path41.join(TRIAGE, freeName2(TRIAGE, fileName(signal2.title, slug2)));
@@ -15727,7 +15649,7 @@ function restoreInboxItems(sourceIds) {
15727
15649
  function restoreInboxItem(sourceId) {
15728
15650
  return restoreInboxItems([sourceId])[0];
15729
15651
  }
15730
- var boardRel, VERDICTS, REASONS, FIELDS, META_LINE, confidenceOf, slugOf, fileName, readArchived, readAllDismissed, bySource;
15652
+ var boardRel, FIELDS, META_LINE, slugOf, fileName, readArchived, readAllDismissed, bySource;
15731
15653
  var init_inbox = __esm({
15732
15654
  "src/lib/signals/inbox.ts"() {
15733
15655
  "use strict";
@@ -15736,11 +15658,8 @@ var init_inbox = __esm({
15736
15658
  init_paths();
15737
15659
  init_yaml();
15738
15660
  boardRel = (file) => rel(file).split(path41.sep).join("/");
15739
- VERDICTS = ["plan", "plan-without-refine", "skip", "human-review"];
15740
- REASONS = ["supported", "rejected", "duplicate", "low-value", "needs-user", "unsure", "small", "plan", "no-workflow"];
15741
15661
  FIELDS = ["source_id", "title", "collected_at", "imported_at"];
15742
15662
  META_LINE = /^\s+("(?:[^"\\]|\\.)*"|[^:]+):\s*(.*)$/;
15743
- confidenceOf = (said3) => /^(0(\.\d+)?|1(\.0+)?)$/.test(said3 ?? "") ? Number(said3) : null;
15744
15663
  slugOf = (said3) => said3.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 56).replace(/-+$/, "");
15745
15664
  fileName = (title, slug2 = "") => `${slugOf(slug2) || slugOf(title) || "item"}.md`;
15746
15665
  readArchived = () => readFolder(SIGNALS_ARCHIVED);
@@ -16405,11 +16324,11 @@ function resumeRefusal(delivery, store) {
16405
16324
  );
16406
16325
  }
16407
16326
  if (!worktreeExists(delivery.worktree)) {
16408
- const path85 = delivery.worktree ?? "";
16327
+ const path86 = delivery.worktree ?? "";
16409
16328
  return refusal(
16410
16329
  "deliveryWorktreeGone",
16411
- `delivery ${id}'s worktree ${path85} is gone, so there is nothing to carry on. ${rebuild}`,
16412
- { id, task, path: path85, command: discard }
16330
+ `delivery ${id}'s worktree ${path86} is gone, so there is nothing to carry on. ${rebuild}`,
16331
+ { id, task, path: path86, command: discard }
16413
16332
  );
16414
16333
  }
16415
16334
  if (!branchExists(delivery.branch)) {
@@ -17813,7 +17732,8 @@ function reflectBlocks(ids) {
17813
17732
  ]);
17814
17733
  }
17815
17734
  function scheduledBlock() {
17816
- const on = new Set(workflows().flatMap((flow) => scheduledMembers(flow).filter((h) => !h.off).map((h) => h.agent)));
17735
+ const given = process.env[SCHEDULED_AGENTS_ENV];
17736
+ const on = new Set(given === void 0 ? workflows().flatMap((flow) => listedScheduled(flow)) : given.split(","));
17817
17737
  const lines = specAgentCatalog().agents.filter((a) => on.has(a.name)).map((a) => `${a.name} \u2014 ${a.description}`);
17818
17738
  return lines.length ? ["<scheduled-agents>", ...lines, "</scheduled-agents>"] : [];
17819
17739
  }
@@ -17841,10 +17761,10 @@ function answeredNote(cardId2, command) {
17841
17761
  }
17842
17762
  function carriedNote(req) {
17843
17763
  if (req.session !== "resume" && req.session !== "fork") {
17844
- return req.carry?.transcript ? `This card came out of a discussion. Read it for what the card leaves out: ${req.carry.transcript}` : "";
17764
+ return req.carry?.transcript ? `This card came out of a discussion. The card is the final word: the discussion only fills in the background, and what it dropped stays dropped. Read it for what the card leaves out: ${req.carry.transcript}` : "";
17845
17765
  }
17846
- const held4 = req.session === "resume" ? "This session planned this card before." : "This session is a copy of the one this card was created in.";
17847
- return `${held4} The card as it reads now outranks what you remember of it: read it again before you act.`;
17766
+ const held4 = req.session === "resume" ? "This session planned this card before." : req.carry?.resumeId ? "This session is a copy of the discussion this card came from, and that discussion's rules ended with it: follow this ask." : "This session is a copy of the one this card was created in.";
17767
+ return `${held4} The card as it reads now outranks what you remember of it, and what was dropped along the way stays dropped: read it again before you act.`;
17848
17768
  }
17849
17769
  function actionPrompt(req, command, notes) {
17850
17770
  const kb = skillCall(agentForRun(req), req.runtime);
@@ -17867,6 +17787,7 @@ function actionPrompt(req, command, notes) {
17867
17787
  }
17868
17788
  return [
17869
17789
  `${kb}. Implement task ${req.id} ${named2} following \`akb guide implement\`.`,
17790
+ carriedNote(req),
17870
17791
  req.notes ? `Extra notes: ${req.notes}` : ""
17871
17792
  ].filter(Boolean).join(" ");
17872
17793
  case "archive":
@@ -17934,10 +17855,7 @@ function actionPrompt(req, command, notes) {
17934
17855
  // The dismissal review (#929). The flow lists the dismissals and restored ids; the guide
17935
17856
  // holds every rule.
17936
17857
  case "review-dismissals":
17937
- return [
17938
- `${kb}. Learn what the user does not want from their rejections and dismissals, following \`akb guide review-rejections\`.`,
17939
- req.allReasons ? `The reasons are the ones \`${command} review-dismissals --all --print\` prints.` : ""
17940
- ].filter(Boolean).join(" ");
17858
+ return `${kb}. Learn what the user does not want from their rejections and dismissals, following \`akb guide review-rejections\`.`;
17941
17859
  case "describe-project":
17942
17860
  return `${kb}. Describe this project following \`akb guide describe-project\`.`;
17943
17861
  // Reflecting on the cards the board has just completed (#534, #1467). They are off the
@@ -17952,7 +17870,7 @@ function actionPrompt(req, command, notes) {
17952
17870
  `${kb}. These tasks have just been completed: ${titles.join(", ")}. Propose the work that should follow them, following \`akb guide reflect\`.`,
17953
17871
  `They have left the board \u2014 read each in its own <card> block below, with the discussion it came from when the block lists one.`,
17954
17872
  `Skip anything already on the board, already in the inbox, or turned down before.`,
17955
- `Write each survivor with \`${command} triage add\`, and revise an open card whose plan what shipped made untrue with \`${command} card revise\`: that is all you may do unless your settings below say otherwise \u2014 you edit and archive no card yourself, and finding nothing worth proposing is a complete result.`,
17873
+ `Write each survivor with \`${command} triage add\`, and revise an open card whose plan what shipped made untrue with \`${command} card revise\`: that is all you may do${settingsBlock(req) ? " unless your settings below say otherwise" : ""} \u2014 you edit and archive no card yourself, and finding nothing worth proposing is a complete result.`,
17956
17874
  `Don't ask me questions with human-in-the-loop.`
17957
17875
  ].join(" "),
17958
17876
  ...reflectBlocks(ids),
@@ -18100,6 +18018,7 @@ var init_prompts = __esm({
18100
18018
  init_agents();
18101
18019
  init_command();
18102
18020
  init_deliveries();
18021
+ init_env();
18103
18022
  init_facts();
18104
18023
  init_outputs();
18105
18024
  init_archive();
@@ -18457,13 +18376,6 @@ function recordReflection(run2) {
18457
18376
  } catch {
18458
18377
  }
18459
18378
  }
18460
- function recordDismissalReview(run2) {
18461
- if (run2.action !== "review-dismissals" || run2.status !== "done") return;
18462
- try {
18463
- stampDismissalReview(new Date(run2.startedAt));
18464
- } catch {
18465
- }
18466
- }
18467
18379
  function recordScheduledRun(run2) {
18468
18380
  if (run2.action !== "scheduled" || run2.status !== "done" || !run2.workflow || !run2.specAgent) return;
18469
18381
  try {
@@ -18823,7 +18735,8 @@ function planningSession(cardId2, cwd = REPO_ROOT) {
18823
18735
  return created ?? { none: "unrecorded" };
18824
18736
  }
18825
18737
  function carriedSession(req) {
18826
- if (!Number.isInteger(req.id) || !PLANNING.has(req.action)) return void 0;
18738
+ const carries = PLANNING.has(req.action) || req.action === "implement" && !!req.carry?.resumeId;
18739
+ if (!Number.isInteger(req.id) || !carries) return void 0;
18827
18740
  const fresh = (why) => ({ note: `new session \u2014 ${why}` });
18828
18741
  if (req.session === "new") return fresh("the earlier one is gone, so this step was started again");
18829
18742
  const agent = agentForRun(req);
@@ -19154,7 +19067,6 @@ async function closeRun(sessionId, res) {
19154
19067
  recordPrune(closed2);
19155
19068
  recordMemoryReview(closed2);
19156
19069
  recordReflection(closed2);
19157
- recordDismissalReview(closed2);
19158
19070
  recordProjectDescription(closed2);
19159
19071
  recordScheduledRun(closed2);
19160
19072
  dropSpec(sessionId);
@@ -19806,7 +19718,7 @@ import fs51 from "node:fs";
19806
19718
  import path55 from "node:path";
19807
19719
  function defaultBody() {
19808
19720
  return [
19809
- "<one short paragraph: the observable result and the current behavior or constraint it changes.>",
19721
+ "<\u7528\u4E00\u5C0F\u6BB5\u8BDD\u5148\u8BB2\u53D1\u751F\u4E86\u4EC0\u4E48\u3001\u7528\u6237\u9047\u5230\u4EC0\u4E48\uFF0C\u518D\u8BB2\u8FD9\u5F20\u5361\u8981\u8FBE\u5230\u7684\u7ED3\u679C\u3002>",
19810
19722
  "",
19811
19723
  "## Worth noting",
19812
19724
  "",
@@ -20864,145 +20776,6 @@ _(not filled in yet \u2014 build the map from the repo before tagging a card.)_
20864
20776
  }
20865
20777
  });
20866
20778
 
20867
- // src/lib/agent/dismissal-review.ts
20868
- import fs57 from "node:fs";
20869
- function rejectionsToReview(since) {
20870
- if (!fs57.existsSync(ARCHIVE)) return [];
20871
- const found2 = [];
20872
- for (const file of walkMd(ARCHIVE)) {
20873
- const id = archivedId(file);
20874
- if (id === null) continue;
20875
- try {
20876
- const { meta } = parseFrontmatter(fs57.readFileSync(file, "utf8"));
20877
- if (!meta?.rejected || meta.rejected_by !== "user" || !meta.rejected_reason.trim()) continue;
20878
- if ((parseStamp(meta.rejected_at)?.getTime() ?? 0) < since) continue;
20879
- found2.push({ id, title: meta.title, file: rel(file), reason: meta.rejected_reason.trim(), rejectedAt: meta.rejected_at });
20880
- } catch {
20881
- continue;
20882
- }
20883
- }
20884
- return found2.sort((a, b) => a.rejectedAt.localeCompare(b.rejectedAt) || a.id - b.id);
20885
- }
20886
- function dismissalsToReview(since) {
20887
- return readAllDismissed().filter((s) => s.dismissedBy === "user" && s.dismissedReason.trim()).filter((s) => (parseStamp(s.dismissedAt)?.getTime() ?? 0) >= since).sort((a, b) => a.dismissedAt.localeCompare(b.dismissedAt)).map((s) => ({
20888
- sourceId: s.sourceId,
20889
- title: s.title,
20890
- file: s.relPath,
20891
- reason: s.dismissedReason.trim(),
20892
- dismissedAt: s.dismissedAt
20893
- }));
20894
- }
20895
- function citedSources(text5) {
20896
- const ids = /* @__PURE__ */ new Set();
20897
- for (const line of text5.split("\n")) {
20898
- if (!/^\s*[-*]\s/.test(line)) continue;
20899
- const m = line.match(SOURCES);
20900
- for (const id of m?.[1].split(",") ?? []) if (id.trim()) ids.add(id.trim());
20901
- }
20902
- return [...ids];
20903
- }
20904
- function withdrawnSources() {
20905
- const cited = [...new Set(plannerCopies("rejected.md").flatMap((file) => {
20906
- try {
20907
- return citedSources(fs57.readFileSync(file, "utf8"));
20908
- } catch {
20909
- return [];
20910
- }
20911
- }))];
20912
- if (cited.length === 0) return [];
20913
- const dismissed = new Set(readAllDismissed().map((s) => s.sourceId));
20914
- const restored = new Set([...readInbox(), ...readArchived()].map((s) => s.sourceId));
20915
- return cited.filter((id) => restored.has(id) && !dismissed.has(id));
20916
- }
20917
- function rejectionWorkWaiting(since) {
20918
- return rejectionsToReview(since).length > 0 || dismissalsToReview(since).length > 0 || withdrawnSources().length > 0;
20919
- }
20920
- var SOURCES;
20921
- var init_dismissal_review = __esm({
20922
- "src/lib/agent/dismissal-review.ts"() {
20923
- "use strict";
20924
- init_cadence();
20925
- init_cards();
20926
- init_frontmatter();
20927
- init_memory();
20928
- init_paths();
20929
- init_inbox();
20930
- init_archive();
20931
- SOURCES = /\(([^()]+)\)\s*$/;
20932
- }
20933
- });
20934
-
20935
- // src/lib/agent/due.ts
20936
- import fs58 from "node:fs";
20937
- function scheduleDue(ask2, now = Date.now()) {
20938
- const gap = !isAuto(ask2.cadence) ? ask2.cadence : autoSchedule(ask2.reads, ask2.fallback).every;
20939
- const from = Math.max(ask2.from, ...ask2.attempts.map((a) => a.startedAt));
20940
- let next = ask2.goesOn ? /* @__PURE__ */ new Date(0) : from ? nextDue(formatStamp(new Date(from)), gap) ?? /* @__PURE__ */ new Date(0) : /* @__PURE__ */ new Date(0);
20941
- const failed = failures(ask2.attempts);
20942
- if (!ask2.goesOn && from && failed > 1) {
20943
- const base = next.getTime() - from;
20944
- next = new Date(from + Math.min(base * 2 ** (failed - 1), Math.max(base, DAY)));
20945
- }
20946
- const newest2 = ask2.attempts.reduce((a, r) => a && a.startedAt >= r.startedAt ? a : r, null);
20947
- const retrying = isAuto(ask2.cadence) && newest2?.status === "error";
20948
- const wait = next.getTime() > now ? retrying ? "retrying" : "tooSoon" : !ask2.newWork() ? "nothingNew" : ask2.building?.() ? "building" : null;
20949
- const reason = !wait || wait === "tooSoon" ? void 0 : wait === "nothingNew" ? ask2.reads : wait;
20950
- return { next, wait, ...reason ? { reason } : {} };
20951
- }
20952
- function readsNew(reads, since) {
20953
- switch (reads) {
20954
- case "archived-cards":
20955
- return archivedSince(since);
20956
- case "chats":
20957
- return false;
20958
- case "dismissals":
20959
- return rejectionWorkWaiting(since);
20960
- }
20961
- }
20962
- function archivedSince(since) {
20963
- if (!fs58.existsSync(ARCHIVE)) return false;
20964
- const day = formatDay(new Date(since));
20965
- for (const file of walkMd(ARCHIVE)) {
20966
- try {
20967
- if (fs58.statSync(file).mtimeMs <= since) continue;
20968
- const { meta } = parseFrontmatter(fs58.readFileSync(file, "utf8"));
20969
- if (meta && !meta.rejected && (meta.archived ?? "") >= day) return true;
20970
- } catch {
20971
- continue;
20972
- }
20973
- }
20974
- return false;
20975
- }
20976
- function building(store) {
20977
- const builds = new Set(store.deliveries.filter((d) => d.status === "active" && !d.scheduled).map((d) => d.deliveryId));
20978
- if (store.deliveries.some((d) => builds.has(d.deliveryId) && d.landing)) return true;
20979
- return store.runs.some((r) => r.status === "running" && r.deliveryId !== void 0 && builds.has(r.deliveryId));
20980
- }
20981
- var AUTO_GAP, DAY, autoSchedule, failures, stampMs;
20982
- var init_due = __esm({
20983
- "src/lib/agent/due.ts"() {
20984
- "use strict";
20985
- init_cadence();
20986
- init_cards();
20987
- init_frontmatter();
20988
- init_paths();
20989
- init_dismissal_review();
20990
- AUTO_GAP = "6h";
20991
- DAY = 864e5;
20992
- autoSchedule = (reads, fallback) => reads ? { every: AUTO_GAP, reads } : { every: fallback };
20993
- failures = (attempts) => {
20994
- let n = 0;
20995
- for (const a of [...attempts].sort((x, y) => y.startedAt - x.startedAt)) {
20996
- if (a.status === "running") continue;
20997
- if (a.status === "done") break;
20998
- n++;
20999
- }
21000
- return n;
21001
- };
21002
- stampMs = (stamp4) => parseStamp(stamp4)?.getTime() ?? 0;
21003
- }
21004
- });
21005
-
21006
20779
  // src/lib/agent/scheduled.ts
21007
20780
  function scheduledBusy(pass, store = readStore()) {
21008
20781
  const args = { agent: pass.agent, workflow: pass.workflow };
@@ -21014,25 +20787,6 @@ function scheduledBusy(pass, store = readStore()) {
21014
20787
  }
21015
20788
  return null;
21016
20789
  }
21017
- function scheduledWait(pass, one, store = readStore(), now = /* @__PURE__ */ new Date()) {
21018
- const reads = specAgentCatalog().agents.find((a) => a.name === one.agent)?.reads;
21019
- const lastRun = stampMs(one.lastRun);
21020
- return scheduleDue(
21021
- {
21022
- cadence: one.cadence,
21023
- fallback: SCHEDULED_CADENCE,
21024
- reads,
21025
- from: stampMs(scheduledClock(one)),
21026
- attempts: store.runs.filter((r) => isPass(r, pass)),
21027
- newWork: () => !reads || readsNew(reads, lastRun),
21028
- building: () => building(store)
21029
- },
21030
- now.getTime()
21031
- );
21032
- }
21033
- function scheduledNext(workflow, one) {
21034
- return one.off || !scheduledClock(one) ? null : scheduledWait({ workflow, agent: one.agent }, one);
21035
- }
21036
20790
  function insideScheduledPass() {
21037
20791
  const sessionId = insideRun();
21038
20792
  const run2 = sessionId ? readStore().runs.find((r) => r.sessionId === sessionId) : void 0;
@@ -21055,13 +20809,10 @@ var scheduledRequest, isPass, openPasses, stalePasses;
21055
20809
  var init_scheduled = __esm({
21056
20810
  "src/lib/agent/scheduled.ts"() {
21057
20811
  "use strict";
21058
- init_catalog2();
21059
- init_due();
21060
20812
  init_env();
21061
20813
  init_stages();
21062
20814
  init_store();
21063
20815
  init_types3();
21064
- init_workflows();
21065
20816
  scheduledRequest = (pass) => ({
21066
20817
  action: "scheduled",
21067
20818
  workflow: pass.workflow,
@@ -21076,15 +20827,15 @@ var init_scheduled = __esm({
21076
20827
  });
21077
20828
 
21078
20829
  // src/lib/card-age.ts
21079
- import fs59 from "node:fs";
20830
+ import fs57 from "node:fs";
21080
20831
  import path60 from "node:path";
21081
20832
  function staleAfterIn(config) {
21082
20833
  const days = Number(LINE.exec(config)?.[1]);
21083
20834
  return Number.isInteger(days) && days > 0 ? days : STALE_AFTER_DEFAULT;
21084
20835
  }
21085
20836
  function staleAfter() {
21086
- if (!fs59.existsSync(CONFIG)) return STALE_AFTER_DEFAULT;
21087
- return staleAfterIn(fs59.readFileSync(CONFIG, "utf8"));
20837
+ if (!fs57.existsSync(CONFIG)) return STALE_AFTER_DEFAULT;
20838
+ return staleAfterIn(fs57.readFileSync(CONFIG, "utf8"));
21088
20839
  }
21089
20840
  function todoPathspec(top) {
21090
20841
  const from = path60.relative(real(top), real(TODO));
@@ -21126,11 +20877,11 @@ function cardAges(now = Date.now()) {
21126
20877
  if (!file || ages.has(file) || dirty.has(file)) continue;
21127
20878
  const at = Date.parse(when);
21128
20879
  if (Number.isNaN(at)) continue;
21129
- ages.set(file, { lastTouched: when.slice(0, 10), days: Math.max(0, Math.floor((now - at) / DAY2)) });
20880
+ ages.set(file, { lastTouched: when.slice(0, 10), days: Math.max(0, Math.floor((now - at) / DAY)) });
21130
20881
  }
21131
20882
  return ages;
21132
20883
  }
21133
- var STALE_AFTER_DEFAULT, LINE, gitTop, real, unquotePath, DAY2;
20884
+ var STALE_AFTER_DEFAULT, LINE, gitTop, real, unquotePath, DAY;
21134
20885
  var init_card_age = __esm({
21135
20886
  "src/lib/card-age.ts"() {
21136
20887
  "use strict";
@@ -21141,7 +20892,7 @@ var init_card_age = __esm({
21141
20892
  gitTop = () => git(["rev-parse", "--show-toplevel"])?.trim() || null;
21142
20893
  real = (p) => {
21143
20894
  try {
21144
- return fs59.realpathSync(p);
20895
+ return fs57.realpathSync(p);
21145
20896
  } catch {
21146
20897
  return path60.resolve(p);
21147
20898
  }
@@ -21154,7 +20905,7 @@ var init_card_age = __esm({
21154
20905
  return line.slice(1, -1);
21155
20906
  }
21156
20907
  };
21157
- DAY2 = 24 * 60 * 60 * 1e3;
20908
+ DAY = 24 * 60 * 60 * 1e3;
21158
20909
  }
21159
20910
  });
21160
20911
 
@@ -21178,7 +20929,7 @@ var init_card_holds = __esm({
21178
20929
  });
21179
20930
 
21180
20931
  // src/commands/list.ts
21181
- import fs60 from "node:fs";
20932
+ import fs58 from "node:fs";
21182
20933
  import path61 from "node:path";
21183
20934
  function summaryLine(body) {
21184
20935
  const lines = body.split("\n");
@@ -21201,7 +20952,7 @@ function openRows() {
21201
20952
  const id = isRoot ? idPrefix(path61.basename(path61.dirname(file))) : idPrefix(base);
21202
20953
  if (id == null) continue;
21203
20954
  if (isLegacyRecurring(path61.relative(TODO, file))) continue;
21204
- const { meta, body } = parseFrontmatter(fs60.readFileSync(file, "utf8"));
20955
+ const { meta, body } = parseFrontmatter(fs58.readFileSync(file, "utf8"));
21205
20956
  rows.push({
21206
20957
  id,
21207
20958
  file,
@@ -21243,7 +20994,7 @@ function cmdStale(rows, all2, scope, mod) {
21243
20994
  }
21244
20995
  function archivedFiles() {
21245
20996
  const files2 = /* @__PURE__ */ new Map();
21246
- if (!fs60.existsSync(ARCHIVE)) return files2;
20997
+ if (!fs58.existsSync(ARCHIVE)) return files2;
21247
20998
  for (const file of walkMd(ARCHIVE)) {
21248
20999
  const id = idPrefix(path61.basename(file));
21249
21000
  if (id !== null) files2.set(id, file);
@@ -21251,7 +21002,7 @@ function archivedFiles() {
21251
21002
  for (const dir of walkDirs(ARCHIVE)) {
21252
21003
  const id = idPrefix(path61.basename(dir));
21253
21004
  const root = path61.join(dir, "root.md");
21254
- if (id !== null && fs60.existsSync(root)) files2.set(id, root);
21005
+ if (id !== null && fs58.existsSync(root)) files2.set(id, root);
21255
21006
  }
21256
21007
  return files2;
21257
21008
  }
@@ -21362,7 +21113,7 @@ var init_list = __esm({
21362
21113
  });
21363
21114
 
21364
21115
  // src/commands/misc.ts
21365
- import fs61 from "node:fs";
21116
+ import fs59 from "node:fs";
21366
21117
  import path62 from "node:path";
21367
21118
  function extractOldMeta(text5, file) {
21368
21119
  const grab = (re) => {
@@ -21400,7 +21151,7 @@ function cmdMigrate(opts) {
21400
21151
  let changed = 0;
21401
21152
  let skipped = 0;
21402
21153
  for (const file of files2) {
21403
- const text5 = fs61.readFileSync(file, "utf8");
21154
+ const text5 = fs59.readFileSync(file, "utf8");
21404
21155
  if (text5.trimStart().startsWith("---")) {
21405
21156
  skipped++;
21406
21157
  continue;
@@ -21409,7 +21160,7 @@ function cmdMigrate(opts) {
21409
21160
  const out = serializeFrontmatter(meta) + "\n\n" + stripOldHeader(text5).replace(/^\n+/, "");
21410
21161
  if (dry) say(`would migrate ${rel(file)} (priority=${meta.priority})`);
21411
21162
  else {
21412
- fs61.writeFileSync(file, out.endsWith("\n") ? out : out + "\n");
21163
+ fs59.writeFileSync(file, out.endsWith("\n") ? out : out + "\n");
21413
21164
  say(`migrated ${rel(file)}`);
21414
21165
  }
21415
21166
  changed++;
@@ -21430,10 +21181,10 @@ var init_misc = __esm({
21430
21181
  });
21431
21182
 
21432
21183
  // src/lib/group-close.ts
21433
- import fs62 from "node:fs";
21184
+ import fs60 from "node:fs";
21434
21185
  function groupCloses(rootFile) {
21435
21186
  try {
21436
- return groupCloseOf(fs62.readFileSync(rootFile, "utf8"));
21187
+ return groupCloseOf(fs60.readFileSync(rootFile, "utf8"));
21437
21188
  } catch {
21438
21189
  return false;
21439
21190
  }
@@ -21446,7 +21197,7 @@ var init_group_close = __esm({
21446
21197
  });
21447
21198
 
21448
21199
  // src/commands/remove.ts
21449
- import fs63 from "node:fs";
21200
+ import fs61 from "node:fs";
21450
21201
  import path63 from "node:path";
21451
21202
  function leavingIds(id, found2) {
21452
21203
  const ids = /* @__PURE__ */ new Set([id]);
@@ -21463,10 +21214,10 @@ function findMentions(ids) {
21463
21214
  const hits = [];
21464
21215
  const re = new RegExp(ids.map((id) => `#${id}(?!\\d)`).join("|"));
21465
21216
  for (const dir of [TODO, MEMORY]) {
21466
- if (!fs63.existsSync(dir)) continue;
21217
+ if (!fs61.existsSync(dir)) continue;
21467
21218
  for (const file of walkMd(dir)) {
21468
21219
  if (path63.basename(file) === "README.md") continue;
21469
- const lines = fs63.readFileSync(file, "utf8").split("\n");
21220
+ const lines = fs61.readFileSync(file, "utf8").split("\n");
21470
21221
  const fmEnd = frontmatterEnd(lines);
21471
21222
  lines.forEach((line, i) => {
21472
21223
  if (!re.test(line)) return;
@@ -21496,10 +21247,10 @@ function stampLeaving(cards, rejection) {
21496
21247
  const day = formatDay();
21497
21248
  const stamp4 = rejection && !rejection.discard ? { reason: rejection.reason, at: (/* @__PURE__ */ new Date()).toISOString(), by: insideRun() ? "agent" : "user" } : rejection && { reason: rejection.reason };
21498
21249
  for (const card of cards) {
21499
- if (!fs63.existsSync(card.file)) continue;
21500
- const text5 = fs63.readFileSync(card.file, "utf8");
21250
+ if (!fs61.existsSync(card.file)) continue;
21251
+ const text5 = fs61.readFileSync(card.file, "utf8");
21501
21252
  const stamped = stampLeavingIn(text5, day, stamp4);
21502
- if (stamped !== text5) fs63.writeFileSync(card.file, stamped);
21253
+ if (stamped !== text5) fs61.writeFileSync(card.file, stamped);
21503
21254
  }
21504
21255
  }
21505
21256
  function unfinishedDelivery(id) {
@@ -21515,7 +21266,7 @@ function unfinishedDelivery(id) {
21515
21266
  }[gap];
21516
21267
  return `#${id} is not finished: ${what}.`;
21517
21268
  }
21518
- const missing = planDeliverables(card.body).filter((src) => src.startsWith(".assets/")).find((src) => !fs63.existsSync(path63.join(ASSETS, src.slice(".assets/".length))));
21269
+ const missing = planDeliverables(card.body).filter((src) => src.startsWith(".assets/")).find((src) => !fs61.existsSync(path63.join(ASSETS, src.slice(".assets/".length))));
21519
21270
  return missing ? `#${id} is not finished: ${missing} is not on this machine.` : null;
21520
21271
  }
21521
21272
  function agentDecisions(text5) {
@@ -21543,7 +21294,7 @@ function removeCard2(id, metric, options) {
21543
21294
  if (unfinished) die(unfinished, { kind: "plan-delivery-unfinished" });
21544
21295
  const dest = options.cleanupDiscarded && locateArchived(id) ? null : archiveDest(found2);
21545
21296
  const cardFile3 = found2.kind === "group" ? path63.join(found2.target, "root.md") : found2.target;
21546
- const cardText = fs63.existsSync(cardFile3) ? fs63.readFileSync(cardFile3, "utf8") : "";
21297
+ const cardText = fs61.existsSync(cardFile3) ? fs61.readFileSync(cardFile3, "utf8") : "";
21547
21298
  const { meta: cardMeta3 } = parseFrontmatter(cardText);
21548
21299
  const alsoRemoved = found2.kind === "group" ? walkMd(found2.target).filter((f) => f !== cardFile3).map((f) => rel(f)).sort() : [];
21549
21300
  const leftIds = leavingIds(id, found2);
@@ -21574,10 +21325,10 @@ function removeCard2(id, metric, options) {
21574
21325
  const groupRoot = found2.kind === "file" && !options.closing ? enclosingGroupRoot(found2.target) : null;
21575
21326
  if (dest) {
21576
21327
  stampLeaving(leaving, metric === "rejected" ? { reason: options.reason?.trim() ?? "", discard: options.discard === true } : null);
21577
- fs63.mkdirSync(ARCHIVE, { recursive: true });
21578
- fs63.renameSync(found2.target, dest);
21328
+ fs61.mkdirSync(ARCHIVE, { recursive: true });
21329
+ fs61.renameSync(found2.target, dest);
21579
21330
  } else {
21580
- fs63.rmSync(found2.target, { recursive: true, force: true });
21331
+ fs61.rmSync(found2.target, { recursive: true, force: true });
21581
21332
  }
21582
21333
  const removedRefs = stripReadmeRefs(found2);
21583
21334
  let marked2 = null;
@@ -21628,11 +21379,12 @@ function removeCard2(id, metric, options) {
21628
21379
  function closeGroup(rootFile) {
21629
21380
  const rootId = idPrefix(path63.basename(path63.dirname(rootFile)));
21630
21381
  if (rootId === null) return null;
21382
+ if (runnerProject(KANBAN)) return null;
21631
21383
  if (!groupCloses(rootFile)) return null;
21632
21384
  say(`
21633
21385
  every subtask line on #${rootId} is resolved \u2014 closing the group:`);
21634
21386
  try {
21635
- const { meta, body } = parseFrontmatter(fs63.readFileSync(rootFile, "utf8"));
21387
+ const { meta, body } = parseFrontmatter(fs61.readFileSync(rootFile, "utf8"));
21636
21388
  const res = cmdRemove(rootId, "completed", { closing: true });
21637
21389
  return {
21638
21390
  id: rootId,
@@ -21699,6 +21451,7 @@ var init_remove = __esm({
21699
21451
  "use strict";
21700
21452
  init_deliveries();
21701
21453
  init_store();
21454
+ init_tasks();
21702
21455
  init_env();
21703
21456
  init_creation_lock();
21704
21457
  init_lock();
@@ -21774,7 +21527,7 @@ var init_memory_file = __esm({
21774
21527
  });
21775
21528
 
21776
21529
  // src/commands/rule.ts
21777
- import fs64 from "node:fs";
21530
+ import fs62 from "node:fs";
21778
21531
  import path64 from "node:path";
21779
21532
  function cmdRule(askedName, flags) {
21780
21533
  const agent = askedName.trim();
@@ -21792,7 +21545,7 @@ function readRuleText(file, text5) {
21792
21545
  if (file !== void 0 && text5 !== void 0) die("pass --file or --text, not both");
21793
21546
  if (file !== void 0) {
21794
21547
  try {
21795
- return fs64.readFileSync(file, "utf8").trim();
21548
+ return fs62.readFileSync(file, "utf8").trim();
21796
21549
  } catch {
21797
21550
  die(`can't read ${file} \u2014 write the rule to a file, then pass its path`);
21798
21551
  }
@@ -21919,15 +21672,15 @@ var init_storyboard = __esm({
21919
21672
  });
21920
21673
 
21921
21674
  // src/lib/spec-contract.ts
21922
- import fs65 from "node:fs";
21675
+ import fs63 from "node:fs";
21923
21676
  import path65 from "node:path";
21924
21677
  function snapshotSpecs() {
21925
21678
  const out = /* @__PURE__ */ new Map();
21926
- if (!fs65.existsSync(TODO)) return out;
21679
+ if (!fs63.existsSync(TODO)) return out;
21927
21680
  for (const file of walkMd(TODO)) {
21928
21681
  if (isLegacyRecurring(path65.relative(TODO, file))) continue;
21929
21682
  const id = idPrefix(path65.basename(file) === "root.md" ? path65.basename(path65.dirname(file)) : path65.basename(file));
21930
- if (id !== null) out.set(file, { id, text: fs65.readFileSync(file, "utf8") });
21683
+ if (id !== null) out.set(file, { id, text: fs63.readFileSync(file, "utf8") });
21931
21684
  }
21932
21685
  return out;
21933
21686
  }
@@ -22103,9 +21856,9 @@ function validateSpec(file, text5, id) {
22103
21856
  }
22104
21857
  function inside(dir, name) {
22105
21858
  try {
22106
- const root = fs65.realpathSync(dir);
22107
- const real2 = fs65.realpathSync(path65.join(dir, name));
22108
- return real2.startsWith(root + path65.sep) && fs65.statSync(real2).isFile() ? real2 : null;
21859
+ const root = fs63.realpathSync(dir);
21860
+ const real2 = fs63.realpathSync(path65.join(dir, name));
21861
+ return real2.startsWith(root + path65.sep) && fs63.statSync(real2).isFile() ? real2 : null;
22109
21862
  } catch {
22110
21863
  return null;
22111
21864
  }
@@ -22153,7 +21906,7 @@ function compareProblem(attrs, id) {
22153
21906
  const file = inside(dir, named2.name);
22154
21907
  if (!file) return { message: `Compare shots ${at} names no file in this card's asset folder. Write the JSON there or fix the path.` };
22155
21908
  try {
22156
- return { data: JSON.parse(fs65.readFileSync(file, "utf8")) };
21909
+ return { data: JSON.parse(fs63.readFileSync(file, "utf8")) };
22157
21910
  } catch {
22158
21911
  return { message: `${at} is not valid JSON.` };
22159
21912
  }
@@ -22322,7 +22075,7 @@ var init_mockups = __esm({
22322
22075
  });
22323
22076
 
22324
22077
  // src/commands/spec-write.ts
22325
- import fs66 from "node:fs";
22078
+ import fs64 from "node:fs";
22326
22079
  import path66 from "node:path";
22327
22080
  function cmdSpecWrite(id, askedName, flags) {
22328
22081
  const agent = findSpecAgent(askedName);
@@ -22332,10 +22085,10 @@ function cmdSpecWrite(id, askedName, flags) {
22332
22085
  const found2 = locate(id);
22333
22086
  if (!found2) die(`no task with id ${id} under ${rel(TODO)}`, { kind: "card-not-found", id });
22334
22087
  const file = found2.kind === "group" ? path66.join(found2.target, "root.md") : found2.target;
22335
- const { meta, body } = parseFrontmatter(fs66.readFileSync(file, "utf8"));
22088
+ const { meta, body } = parseFrontmatter(fs64.readFileSync(file, "utf8"));
22336
22089
  if (!meta) die(`${rel(file)} has no frontmatter \u2014 run \`migrate\` first`);
22337
22090
  const { body: next, replaced } = splice(body, name, section);
22338
- fs66.writeFileSync(file, serializeFrontmatter(meta) + "\n" + next);
22091
+ fs64.writeFileSync(file, serializeFrontmatter(meta) + "\n" + next);
22339
22092
  say(`${replaced ? "rewrote" : "wrote"} the \`${name}\` section on #${id} (${rel(file)})`);
22340
22093
  return { id, specAgent: name, replaced, file: rel(file) };
22341
22094
  }
@@ -22344,7 +22097,7 @@ function readSection(file, text5) {
22344
22097
  let raw;
22345
22098
  if (file !== void 0) {
22346
22099
  try {
22347
- raw = fs66.readFileSync(file, "utf8");
22100
+ raw = fs64.readFileSync(file, "utf8");
22348
22101
  } catch {
22349
22102
  die(`can't read ${file} \u2014 write your section to a file, then pass its path`);
22350
22103
  }
@@ -22810,11 +22563,11 @@ var init_shared = __esm({
22810
22563
  });
22811
22564
 
22812
22565
  // src/commands/idea.ts
22813
- import fs67 from "node:fs";
22566
+ import fs65 from "node:fs";
22814
22567
  function load() {
22815
22568
  const file = ideasFile();
22816
22569
  if (!file) die("Ideas exist only inside a WildCrane idea conversation.");
22817
- const held4 = JSON.parse(fs67.readFileSync(file, "utf8"));
22570
+ const held4 = JSON.parse(fs65.readFileSync(file, "utf8"));
22818
22571
  return { file, held: { workflows: held4.workflows ?? [], ideas: held4.ideas ?? [] } };
22819
22572
  }
22820
22573
  function workflowOf(held4, asked) {
@@ -22836,7 +22589,7 @@ function splitBody2(text5) {
22836
22589
  }
22837
22590
  function readBody(bodyFile) {
22838
22591
  if (!bodyFile) die("--body-file is required; write the idea in a writable temporary file first.");
22839
- const text5 = fs67.readFileSync(bodyFile, "utf8");
22592
+ const text5 = fs65.readFileSync(bodyFile, "utf8");
22840
22593
  if (!text5.trim()) die("The idea must not be empty.");
22841
22594
  return splitBody2(text5);
22842
22595
  }
@@ -22848,7 +22601,7 @@ function named(held4, id) {
22848
22601
  function cmdIdea(args, opts) {
22849
22602
  const sub = args[0] ?? "";
22850
22603
  const { file, held: held4 } = load();
22851
- const write2 = () => fs67.writeFileSync(file, `${JSON.stringify(held4, null, 2)}
22604
+ const write2 = () => fs65.writeFileSync(file, `${JSON.stringify(held4, null, 2)}
22852
22605
  `);
22853
22606
  if (sub === "new") {
22854
22607
  const title = opts.title?.trim();
@@ -22917,7 +22670,7 @@ var board_exports = {};
22917
22670
  __export(board_exports, {
22918
22671
  buildBoardProgram: () => buildBoardProgram
22919
22672
  });
22920
- import fs68 from "node:fs";
22673
+ import fs66 from "node:fs";
22921
22674
  import path67 from "node:path";
22922
22675
  async function dispatch(move2, cmd, args, opts, cli) {
22923
22676
  const ctx = ctxOf(cmd, cli.program);
@@ -22944,7 +22697,7 @@ async function cloudCard(task, opts) {
22944
22697
  let request;
22945
22698
  if (typeof opts.bodyFile === "string") {
22946
22699
  try {
22947
- request = fs68.readFileSync(opts.bodyFile, "utf8");
22700
+ request = fs66.readFileSync(opts.bodyFile, "utf8");
22948
22701
  } catch {
22949
22702
  }
22950
22703
  }
@@ -23098,7 +22851,7 @@ var init_board2 = __esm({
23098
22851
  });
23099
22852
 
23100
22853
  // src/lib/board-cli.ts
23101
- import fs69 from "node:fs";
22854
+ import fs67 from "node:fs";
23102
22855
  import path68 from "node:path";
23103
22856
  function useBoard(found2, dirNamed) {
23104
22857
  if (!found2.named && found2.board === path68.join(found2.root, "docs", "kanban")) setBoardRoot(found2.root, dirNamed);
@@ -23107,7 +22860,7 @@ function useBoard(found2, dirNamed) {
23107
22860
  }
23108
22861
  function whole(found2, move2) {
23109
22862
  if (move2 === MAKES_A_BOARD) return found2;
23110
- if (!fs69.existsSync(path68.join(found2.board, "todo"))) {
22863
+ if (!fs67.existsSync(path68.join(found2.board, "todo"))) {
23111
22864
  die(`the board in ${found2.board} has no todo/ \u2014 run \`init\` to add what is missing.`, {
23112
22865
  kind: "board-incomplete",
23113
22866
  dir: found2.root
@@ -23131,10 +22884,10 @@ function resolveBoard(move2, { board: board2, dir, cwd, installHint }) {
23131
22884
  const named2 = board2 ?? boardFromEnv();
23132
22885
  if (named2) {
23133
22886
  const folder = path68.resolve(cwd, named2);
23134
- if (!fs69.existsSync(folder) && move2 !== MAKES_A_BOARD) {
22887
+ if (!fs67.existsSync(folder) && move2 !== MAKES_A_BOARD) {
23135
22888
  die(`no such folder: ${folder}`, { kind: "no-such-folder", dir: folder });
23136
22889
  }
23137
- if (fs69.existsSync(folder) && !isBoardDir(folder) && move2 !== MAKES_A_BOARD) {
22890
+ if (fs67.existsSync(folder) && !isBoardDir(folder) && move2 !== MAKES_A_BOARD) {
23138
22891
  die(`${folder} is not a board \u2014 it has no todo/ and config.md. Run ${installHint} --board ${named2} to make one.`, {
23139
22892
  kind: "no-board",
23140
22893
  dir: folder
@@ -23144,7 +22897,7 @@ function resolveBoard(move2, { board: board2, dir, cwd, installHint }) {
23144
22897
  }
23145
22898
  if (dir !== null) {
23146
22899
  const root = path68.resolve(dir);
23147
- if (!fs69.existsSync(root)) die(`no such folder: ${root}`, { kind: "no-such-folder", dir: root });
22900
+ if (!fs67.existsSync(root)) die(`no such folder: ${root}`, { kind: "no-such-folder", dir: root });
23148
22901
  if (!hasBoard(root) && move2 !== MAKES_A_BOARD) {
23149
22902
  die(`no board in ${root} \u2014 it has no docs/kanban/. Run ${installHint} there to make one.`, {
23150
22903
  kind: "no-board",
@@ -23241,8 +22994,8 @@ var init_board_cli = __esm({
23241
22994
  init_pointer();
23242
22995
  init_tasks();
23243
22996
  MAKES_A_BOARD = "init";
23244
- hasBoard = (dir) => fs69.existsSync(path68.join(dir, "docs", "kanban"));
23245
- isBoardDir = (dir) => fs69.existsSync(path68.join(dir, "todo")) && fs69.existsSync(path68.join(dir, "config.md"));
22997
+ hasBoard = (dir) => fs67.existsSync(path68.join(dir, "docs", "kanban"));
22998
+ isBoardDir = (dir) => fs67.existsSync(path68.join(dir, "todo")) && fs67.existsSync(path68.join(dir, "config.md"));
23246
22999
  boardFromEnv = () => {
23247
23000
  const named2 = process.env.AI4KANBAN_BOARD?.trim();
23248
23001
  return named2 ? named2 : null;
@@ -23270,7 +23023,7 @@ var init_archive2 = __esm({
23270
23023
  var board_default;
23271
23024
  var init_board3 = __esm({
23272
23025
  "src/guide/board.md"() {
23273
- board_default = "# How this board works\n\nThese rules apply to every card workflow. Read them once; each workflow's guide covers\nthe remaining details.\n\n## Layout\n\n```\ndocs/kanban/\n\u251C\u2500\u2500 todo/ open tasks\n\u2502 \u251C\u2500\u2500 README.md the index \u2014 read it first\n\u2502 \u2514\u2500\u2500 <id>-<slug>.md\n\u2502 one card per file; a group keeps its cards in one folder\n\u251C\u2500\u2500 memory/ all memory \u2014 see \"Who owns a memory file\"\n\u2502 \u251C\u2500\u2500 readme.md what shipped \u2014 the board's own record\n\u2502 \u251C\u2500\u2500 project.md what the project is today \u2014 written only by `akb describe-project`\n\u2502 \u251C\u2500\u2500 <name>/ a global memory: `MEMORY.md` (its rules, unless built in), `README.md`\n\u2502 \u2502 (its index) and its entries\n\u2502 \u2514\u2500\u2500 agents/ one folder per agent that keeps memory, named after it \u2014\n\u2502 `planner/` holds `decisions.md`, `rejected.md`, `redesign.md`\n\u251C\u2500\u2500 rules/ `<agent>.md` \u2014 the user's rule for one agent, appended to every run it\n\u2502 does; written from the board UI or `akb raw rule`\n\u251C\u2500\u2500 triage/ items waiting to be sorted, one file each \u2014 not cards. Only\n\u2502 \u2502 Cloud's sort or Refine idea on the web's Triage page turns one\n\u2502 \u2502 into a card; `akb triage check <source-id>` finds an existing one\n\u2502 \u251C\u2500\u2500 archived/ items a card was made of\n\u2502 \u251C\u2500\u2500 dismissed/ items ignored, with the reason \u2014 `akb triage restore` puts one back\n\u2502 \u2514\u2500\u2500 files/ dropped-in files\n\u2514\u2500\u2500 modules.md one line per module \u2014 `akb guide module-map` writes it\n```\n\n## Task ID\n\nEvery task's id is the number at the front of its filename (`04-plan-cap-enforcement.md` \u2192\nid 4): its Cloud task's number (`WC-4`). Only Cloud assigns one; `akb raw create` takes it.\n\n## Never hand-write a card's frontmatter\n\n`akb raw create`, `update`, and `update-questions` manage all of a card's frontmatter,\nincluding its `previews`. Edit only the card's **body** by hand. `akb raw help`\nlists all operations; `akb raw help <move>` explains one operation.\n\n## Inside a board run\n\nA board run carries `KANBAN_RUN` in its environment. Never unset or bypass it (for example\n`env -u KANBAN_RUN`): when a command only prints a flow, follow the printed flow.\n\n## Group task\n\nA group lives in `todo/<id>-<slug>/`: `root.md` tracks the whole job and each subtask is its\nown card beside it; `akb guide add-task` says how to build one. The root's `## Todo` lists the\nsubtasks, one line each ending in `#<subid>`: archiving a subtask ticks its line, rejecting\none strikes it through, and resolving the last line archives the root in the same run, with\nany open question or unticked todo of its own. A root listing no subtasks is only closed by\nhand.\n\n## Who owns a memory file\n\n**A memory file belongs to whoever reads and writes it.**\n\n- **`memory/readme.md`**: the board's record of shipped user-facing work.\n- **`memory/project.md`**: what the project is today, from its users' side, and the\n direction every judgement serves. Only `akb describe-project` writes it.\n- **`memory/agents/planner/`**: `decisions.md` holds user-facing answers that guide future\n planning and `redesign.md` design mistakes to avoid, both written by the memory review (`akb\n guide review-memory`); `rejected.md` holds what the user turned down and why, written only\n by the rejection and dismissal review (`akb guide review-rejections`). Other flows only read\n them, `akb prune-memory` aside.\n- **`memory/agents/proposer/missed.md`**: kinds of follow-up the proposer failed to propose \u2014\n written only by the memory review (`akb guide review-memory`), read by every reflection.\n- **`memory/<name>/`**: a global memory, kept by every agent listing it under `akb.memory`\n by the rules each run is given. Only the user changes its `MEMORY.md`, from the board UI.\n- **`memory/agents/<agent>/`**: an agent keeps only the files and split paths its own AGENT.md\n declares. An agent whose instructions name none keeps none.\n\nFile a planning note about one module in that module's folder,\n`memory/agents/planner/<module>/`, under the same file name, creating it if missing. A note\nspanning modules, or from a card with none, stays in `memory/agents/planner/`.\n\n### What earns a note\n\nOnly what changes a future planning choice. Writing nothing is a complete outcome.\n\n- **Honor an opt-out**: told not to record, write no memory and finish the requested action.\n- **A conversation writes none**: a card chat, a discussion, a feedback conversation, and\n any flow started in it write no memory and say nothing about it.\n- **Require lasting value**: a durable preference, constraint, decision, or lesson.\n- **Skip housekeeping**: duplicates, routine status changes, and facts recorded elsewhere.\n A rejected duplicate is not a rejected feature.\n- **Merge, don't repeat**: rewrite an equivalent entry in place.\n\n`readme.md` is not planning memory.\n";
23026
+ board_default = "# How this board works\n\nThese rules apply to every card workflow. Read them once; each workflow's guide covers\nthe remaining details.\n\n## Layout\n\n```\ndocs/kanban/\n\u251C\u2500\u2500 todo/ open tasks\n\u2502 \u251C\u2500\u2500 README.md the index \u2014 read it first\n\u2502 \u2514\u2500\u2500 <id>-<slug>.md\n\u2502 one card per file; a group keeps its cards in one folder\n\u251C\u2500\u2500 memory/ all memory \u2014 see \"Who owns a memory file\"\n\u2502 \u251C\u2500\u2500 readme.md what shipped \u2014 the board's own record\n\u2502 \u251C\u2500\u2500 project.md what the project is today \u2014 written only by `akb describe-project`\n\u2502 \u251C\u2500\u2500 <name>/ a global memory: `MEMORY.md` (its rules, unless built in), `README.md`\n\u2502 \u2502 (its index) and its entries\n\u2502 \u2514\u2500\u2500 agents/ one folder per agent that keeps memory, named after it \u2014\n\u2502 `planner/` holds `decisions.md`, `rejected.md`, `redesign.md`\n\u251C\u2500\u2500 rules/ `<agent>.md` \u2014 the user's rule for one agent, appended to every run it\n\u2502 does; written from the board UI or `akb raw rule`\n\u251C\u2500\u2500 triage/ items waiting to be sorted, one file each \u2014 not cards. Only\n\u2502 \u2502 Cloud's sort or Refine idea on the web's Triage page turns one\n\u2502 \u2502 into a card; `akb triage check <source-id>` finds an existing one\n\u2502 \u251C\u2500\u2500 archived/ items a card was made of\n\u2502 \u251C\u2500\u2500 dismissed/ items ignored, with the reason \u2014 `akb triage restore` puts one back\n\u2502 \u2514\u2500\u2500 files/ dropped-in files\n\u2514\u2500\u2500 modules.md one line per module \u2014 `akb guide module-map` writes it\n```\n\n## Task ID\n\nEvery task's id is the number at the front of its filename (`04-plan-cap-enforcement.md` \u2192\nid 4): its Cloud task's number (`WC-4`). Only Cloud assigns one; `akb raw create` takes it.\n\n## Never hand-write a card's frontmatter\n\n`akb raw create`, `update`, and `update-questions` manage all of a card's frontmatter,\nincluding its `previews`. Edit only the card's **body** by hand. `akb raw help`\nlists all operations; `akb raw help <move>` explains one operation.\n\n## Inside a board run\n\nA board run carries `KANBAN_RUN` in its environment. Never unset or bypass it (for example\n`env -u KANBAN_RUN`): when a command only prints a flow, follow the printed flow.\n\n## Group task\n\nA group lives in `todo/<id>-<slug>/`: `root.md` tracks the whole job and each subtask is its\nown card beside it; `akb guide add-task` says how to build one. The root's `## Todo` lists the\nsubtasks, one line each ending in `#<subid>`: archiving a subtask ticks its line, rejecting\none strikes it through, and resolving the last line archives the root in the same run, with\nany open question or unticked todo of its own. A board a Runner keeps leaves that close to\nCloud, which waits for the root to be queued with no step on it. A root listing no subtasks is\nonly closed by hand.\n\n## Who owns a memory file\n\n**A memory file belongs to whoever reads and writes it.**\n\n- **`memory/readme.md`**: the board's record of shipped user-facing work.\n- **`memory/project.md`**: what the project is today, from its users' side, and the\n direction every judgement serves. Only `akb describe-project` writes it.\n- **`memory/agents/planner/`**: `decisions.md` holds user-facing answers that guide future\n planning and `redesign.md` design mistakes to avoid, both written by the memory review (`akb\n guide review-memory`); `rejected.md` holds what the user turned down and why, written only\n by the rejection and dismissal review (`akb guide review-rejections`). Other flows only read\n them, `akb prune-memory` aside.\n- **`memory/<name>/`**: a global memory, kept by every agent listing it under `akb.memory`\n by the rules each run is given. Only the user changes its `MEMORY.md`, from the board UI.\n- **`memory/agents/<agent>/`**: an agent keeps only the files and split paths its own AGENT.md\n declares. An agent whose instructions name none keeps none.\n\nFile a planning note about one module in that module's folder,\n`memory/agents/planner/<module>/`, under the same file name, creating it if missing. A note\nspanning modules, or from a card with none, stays in `memory/agents/planner/`.\n\n### What earns a note\n\nOnly what changes a future planning choice. Writing nothing is a complete outcome.\n\n- **Honor an opt-out**: told not to record, write no memory and finish the requested action.\n- **A conversation writes none**: a card chat, a discussion, a feedback conversation, and\n any flow started in it write no memory and say nothing about it.\n- **Require lasting value**: a durable preference, constraint, decision, or lesson.\n- **Skip housekeeping**: duplicates, routine status changes, and facts recorded elsewhere.\n A rejected duplicate is not a rejected feature.\n- **Merge, don't repeat**: rewrite an equivalent entry in place.\n\n`readme.md` is not planning memory.\n";
23274
23027
  }
23275
23028
  });
23276
23029
 
@@ -23302,7 +23055,7 @@ var init_describe_project = __esm({
23302
23055
  var discuss_idea_default;
23303
23056
  var init_discuss_idea = __esm({
23304
23057
  "src/guide/discuss-idea.md"() {
23305
- discuss_idea_default = "# Help decide what is worth building\n\nYou are this workspace's **Idea helper**. Help the user decide what to build and shape it into\nideas they start as tasks. Read the current project's code and board when it helps, but never\nchange a file. Each turn takes one of three branches.\n\n## Draft ideas (default)\n\nWork worth doing gets an idea: a title, the outcome it should reach, what counts as done, and\nthe workflow that will do it. The user turns an idea into a task with its **Start** button.\n\n- **Draft at once**: a clear ask gets a complete idea on the first turn, with\n `akb raw idea new --title \"<title>\" --body-file <draft>`; update it on later turns with\n `akb raw idea save --id <idea> --body-file <draft>`. Report it only after the command confirms.\n- **Idea shape**: in the user's language, the outcome in at most 150 words, then a\n `## Done when` list of checks a reviewer can verify. No technical design.\n- **Pick the workflow**: add `--workflow <id>` naming the workflow in `akb workflow list` that\n does the work, judged by the work and not the topic, and `coding` when none fits; use the\n one the user names or picks. Call it by its listed name in the user's language.\n- **One idea per deliverable**: give separate deliverables separate ideas, at most 10, and drop\n a replaced one with `akb raw idea drop --id <idea>`.\n- **Settle what you can**: decide the outcome questions you can judge and say which calls you\n made; ask only what the user alone can answer and would change the outcome, with the option\n you recommend.\n- **Finish the turn**: save the ideas and stop; never end by asking them to confirm or press\n **Start**.\n\n## Start on a go\n\nWhen the user tells you to go ahead, start the ideas they mean with\n`akb raw idea start --id <idea>`, drafting them first when needed, and say which tasks started.\n\n## Just answer\n\nA question, an idea not yet clear enough to draft, or a doubt about whether it is worth doing\ngets an answer, with no idea.\n\n- **Answer first**: say whether it is worth doing and why; disagreeing, advising a pause, or\n asking for evidence is a complete answer. Go as deep as the uncertainty, and read a reliable\n source before stating an external fact.\n- **Board progress**: answer from the project's board as it stands now.\n\n## Always\n\n- **Hide machinery**: the user sees only ideas and tasks; never mention plans, files, paths,\n akb, commands, or the tools and connectors you run with, and refer to actions by their\n button labels.\n";
23058
+ discuss_idea_default = "# Help decide what is worth building\n\nYou are this workspace's **Idea helper**. Help the user decide what to build and shape it into\nideas they start as tasks. Read the current project's code and board when it helps, but never\nchange a file. Each turn takes one of three branches.\n\n## Draft ideas (default)\n\nWork worth doing gets an idea.\n\n- **Draft at once**: a clear ask gets a complete idea on the first turn, with\n `akb raw idea new --title \"<title>\" --body-file <draft>`; update it on later turns with\n `akb raw idea save --id <idea> --body-file <draft>`. Report it only after the command confirms.\n- **Idea shape**: in the user's language, the outcome in at most 150 words, then a\n `## Done when` list of checks a reviewer can verify. No technical design.\n- **Pick the workflow**: add `--workflow <id>` naming the workflow in `akb workflow list` that\n does the work, judged by the work and not the topic, and `coding` when none fits; use the\n one the user names or picks. Call it by its listed name in the user's language.\n- **One idea per deliverable**: give separate deliverables separate ideas, and drop a replaced\n one with `akb raw idea drop --id <idea>`.\n- **Settle what you can**: decide the outcome questions you can judge and say which calls you\n made; ask only what the user alone can answer and would change the outcome, with the option\n you recommend.\n- **Finish the turn**: save the ideas and stop; never end by asking them to confirm or press\n **Start**.\n\n## Start on a go\n\nWhen the user tells you to go ahead, start the ideas they mean with\n`akb raw idea start --id <idea>`, drafting them first when needed, and say which tasks started.\n\n## Just answer\n\nA question, an idea not yet clear enough to draft, or a doubt about whether it is worth doing\ngets an answer, with no idea.\n\n- **Answer first**: say whether it is worth doing and why; disagreeing, advising a pause, or\n asking for evidence is a complete answer. Read a reliable source before stating an external\n fact.\n\n## Always\n\n- **Hide machinery**: never mention plans, files, paths, commands, or the tools and connectors\n you run with, and refer to actions by their button labels.\n";
23306
23059
  }
23307
23060
  });
23308
23061
 
@@ -23318,7 +23071,7 @@ var init_evaluate_task = __esm({
23318
23071
  var follow_up_default;
23319
23072
  var init_follow_up = __esm({
23320
23073
  "src/guide/follow-up.md"() {
23321
- follow_up_default = '# Handle follow-up work\n\nUse when planning or implementation discovers additional work.\n\n- **Place it**: fix what the current card requires here; never defer a required fix to make\n the card pass. Queue independent follow-ups in triage without asking permission or blocking\n the original, and create no card for them.\n- **Another card\'s change is not a follow-up**: when it only changes an open card, run\n `akb card revise <id> "<the change and why>"` instead.\n- **Queue it**: `akb triage add --title "<one line>" --slug <short-english-slug> --source "#<id>" --workflow <workflow> --text "<what and why, readable without this session>"`,\n then resume the original workflow. Skip one the command reports as already there.\n- **Workflow**: pass the `workflow:` of the card you are working on; when it has none, pass\n the board\'s default workflow shown by `akb workflow`.\n';
23074
+ follow_up_default = '# Handle follow-up work\n\nUse when planning or implementation discovers additional work.\n\n- **Current task**: finish required work on this card; queue independent follow-ups in triage, then resume the original task.\n- **Existing card**: when only another open card needs changing, run `akb card revise <id> "<the change and why>"`.\n- **Queue it**: use the command below; skip an item the command reports as already there.\n- **Workflow**: use the source card\'s `workflow:`, or the board default shown by `akb workflow` when it has none.\n\n```text\nakb triage add --title "<one line>" --slug <short-english-slug> --source "#<id>" --workflow <workflow> --text "<body>"\n```\n\n## Write the item\n\n- **Reader**: someone who has read neither the original card nor the code can decide in ten seconds whether the work is worth doing.\n- **Order**: first explain what happened and what the user runs into, then say what the work would do.\n- **Plain words**: keep file, function and field names, card ids, agent names and evidence trails out of the title and body.\n';
23322
23075
  }
23323
23076
  });
23324
23077
 
@@ -23350,7 +23103,7 @@ var init_prune_memory = __esm({
23350
23103
  var review_memory_default;
23351
23104
  var init_review_memory = __esm({
23352
23105
  "src/guide/review-memory.md"() {
23353
- review_memory_default = "# Review what the conversations settled\n\nRead each conversation given to you right through, then write what it settled into memory.\n\nChats and planning flows write no memory themselves. One turn cannot see where an exchange\nis going, so a \"what if\" thrown out and taken back three turns later used to land as a\ndecision. You read the whole exchange, so you can tell a decision from a passing thought \u2014\nand that judgement is the whole job.\n\n- **Read what you are given**: each card comes in its own `<conversation>` block: its summary,\n its `Worth noting` and `Overruled by the user` lines, then its chat and the comments the user\n left reviewing it, when it had any, the user's words as written and the agent's replies,\n oldest first. The card is archived, so it is complete and reviewed this once; open no card or\n transcript file.\n- **Keep them apart**: judge each block by itself; what one conversation settled never\n becomes a note for another's card or agents.\n- **Hold to the same bar**: \"What earns a note\" in `akb guide board` governs what you write,\n exactly as it governs a flow that writes one. A conversation that settled nothing earns no\n note, and most do not \u2014 writing nothing is a complete result.\n- **Only what the user settled**: a durable preference, constraint, decision or correction\n they stated. Your own proposal is not their decision, and neither is a question, a \"what\n if\", or a direction the conversation then moved off.\n- **Take the last word**: where they said one thing and then another, the later one stands.\n Where a note already in the file says what they have since overturned, rewrite that note in\n place \u2014 never leave both and never add a second.\n- **Honour \"don't record this\"**: told in the conversation not to record something, write no\n note for it. If one is already there from an earlier review, delete it now.\n- **Where it goes**: a durable choice goes in the planner's `decisions.md` as one\n `**<key>**: <call>` line under the topic that fits, without its rationale; a design lesson\n goes in its `redesign.md` under the relevant topic as\n `- \u274C **<mistake>** \u2192 \u2705 <what the design should be instead, one line>.`, written as guidance\n for future tasks, not a history of the fix. Both go in the folder of the module each\n conversation is listed with (`akb guide board`). Each conversation also lists the agents\n whose sections its card holds and the memory files each keeps: where what was settled lands\n on one agent's own work, it goes in that agent's memory instead. Read that agent's AGENT.md\n (`akb raw agent-file <agent> AGENT.md`) only then, and keep its memory the way it says.\n Distill settled feedback into reusable preferences (dos) and corrections (don'ts), retaining\n their scope; never copy raw reviews. An agent whose instructions name no memory gets no new file.\n- **What the card settled**: the summary says what the card was about and earns no note. A\n `Worth noting` line is a decision the user approved, by answering it or letting it stand, so\n it can be a choice but never a correction, and it earns a planner note only where the user's\n own words in the conversation back it; a line under\n `Overruled by the user` is a call the user reversed \u2014 a correction for the agent whose\n section it came from, or for the planner when none did.\n- **A follow-up the proposer missed**: when the user points out work a finished card left\n behind that no proposal named, add the kind of miss, not the case, to\n `docs/kanban/memory/agents/proposer/missed.md` as `- **<kind>**: <what to check> (#<card>)`,\n creating the file if missing.\n- **Change nothing else**: no card, no open question, no code. The memory\n files are the whole of what this run writes.\n- **Say what you wrote**: finish by reporting each conversation you read, and for each the\n notes you added, rewrote, deleted, or that you wrote none.\n";
23106
+ review_memory_default = "# Review what the conversations settled\n\nRead each conversation given to you right through, then write what it settled into memory.\n\nChats and planning flows write no memory themselves. One turn cannot see where an exchange\nis going, so a \"what if\" thrown out and taken back three turns later used to land as a\ndecision. You read the whole exchange, so you can tell a decision from a passing thought \u2014\nand that judgement is the whole job.\n\n- **Read what you are given**: each card comes in its own `<conversation>` block: its summary,\n its `Worth noting` and `Overruled by the user` lines, then its chat and the comments the user\n left reviewing it, when it had any, the user's words as written and the agent's replies,\n oldest first. The card is archived, so it is complete and reviewed this once; open no card or\n transcript file.\n- **Keep them apart**: judge each block by itself; what one conversation settled never\n becomes a note for another's card or agents.\n- **Hold to the same bar**: \"What earns a note\" in `akb guide board` governs what you write,\n exactly as it governs a flow that writes one. A conversation that settled nothing earns no\n note, and most do not \u2014 writing nothing is a complete result.\n- **Only what the user settled**: a durable preference, constraint, decision or correction\n they stated. Your own proposal is not their decision, and neither is a question, a \"what\n if\", or a direction the conversation then moved off.\n- **Take the last word**: where they said one thing and then another, the later one stands.\n Where a note already in the file says what they have since overturned, rewrite that note in\n place \u2014 never leave both and never add a second.\n- **Honour \"don't record this\"**: told in the conversation not to record something, write no\n note for it. If one is already there from an earlier review, delete it now.\n- **Where it goes**: a durable choice goes in the planner's `decisions.md` as one\n `**<key>**: <call>` line under the topic that fits, without its rationale; a design lesson\n goes in its `redesign.md` under the relevant topic as\n `- \u274C **<mistake>** \u2192 \u2705 <what the design should be instead, one line>.`, written as guidance\n for future tasks, not a history of the fix. Both go in the folder of the module each\n conversation is listed with (`akb guide board`). Each conversation also lists the agents\n whose sections its card holds and the memory files each keeps: where what was settled lands\n on one agent's own work, it goes in that agent's memory instead. Read that agent's AGENT.md\n (`akb raw agent-file <agent> AGENT.md`) only then, and keep its memory the way it says.\n Distill settled feedback into reusable preferences (dos) and corrections (don'ts), retaining\n their scope; never copy raw reviews. An agent whose instructions name no memory gets no new file.\n- **What the card settled**: the summary says what the card was about and earns no note. A\n `Worth noting` line is a decision the user approved, by answering it or letting it stand, so\n it can be a choice but never a correction, and it earns a planner note only where the user's\n own words in the conversation back it; a line under\n `Overruled by the user` is a call the user reversed, so it goes in `redesign.md`: that of\n the agent whose section it came from, or the planner's when none did.\n- **Change nothing else**: no card, no open question, no code. The memory\n files are the whole of what this run writes.\n- **Say what you wrote**: finish by reporting each conversation you read, and for each the\n notes you added, rewrote, deleted, or that you wrote none.\n";
23354
23107
  }
23355
23108
  });
23356
23109
 
@@ -23374,7 +23127,7 @@ var init_refine = __esm({
23374
23127
  var reflect_default;
23375
23128
  var init_reflect = __esm({
23376
23129
  "src/guide/reflect.md"() {
23377
- reflect_default = "# Reflect on completed cards\n\nSome cards have just been finished. Propose the work that should follow them, one inbox item\neach. Proposing nothing is a normal result. Do not read archived cards you were not given,\nand do not edit or archive any card yourself; create one only when your settings say so.\n\n## Inputs\n\nEach card comes in its own `<card>` block; read each in full and take nothing from one block\nfor another:\n\n- **The card**: what was planned.\n\nWhat helps check it \u2014 read only as far as it sharpens a candidate:\n\n- **What shipped**: what each block lists under `shipped`.\n- **Past misses**: `docs/kanban/memory/agents/proposer/missed.md`, when it exists, lists kinds\n of follow-up missed before; check every card for each one.\n\n## Candidates\n\nEach is work on the project itself that traces to a line in the card or in what shipped:\nwhat it left out, revealed, relied on without anyone providing it, or made worth doing now.\nVerify a gap in the project; never take the card's word.\n\nJudge for yourself what is worth the user's time to read, exploratory ideas included; say so\nin its text when one is a small chore.\n\nWhen what shipped makes an open card's plan untrue, propose no item for it; run\n`akb card revise <id> \"<the change and why>\"` instead.\n\n## Filter\n\nDrop a candidate that is already on the board (`akb raw list`) or in the inbox\n(`akb triage check <source-id>`), is work an agent in the `<scheduled-agents>` block does on its\nown runs, is turned down by `docs/kanban/memory/agents/planner/rejected.md` or\n`docs/kanban/memory/agents/planner/*/rejected.md`, or is a guess nothing in the card or the\nproject backs. When several cards lead to the same work, propose it once, from the card it\ntraces to most directly.\n\n## Write\n\nWrite each item's body to a temporary Markdown file: a short `####` heading per part \u2014 what,\nto do, why, and any caveat \u2014 then the archived card's path `docs/kanban/.archive/<file>`.\n\n```text\nakb triage add --title \"<one line>\" --slug <short-english-slug> --source \"#<id>\" --workflow <workflow> --file <body.md>\n```\n\n`--source` names the card the item traces to, and `--workflow` is that card's `workflow:`, or\nthe board's default workflow shown by `akb workflow` when it has none. Report what you\nproposed and what you skipped, with a reason for each skip.\n";
23130
+ reflect_default = '# Reflect on completed cards\n\n## Read\n\nRead each given `<card>` in full and check its own `shipped` content; keep cards separate.\nFind needs the work left behind or revealed, and verify them in the project. Proposing nothing is fine.\n\n## Filter\n\n- **Duplicates**: check `akb raw list` and `akb triage check <source-id>`; propose shared work only once.\n- **Exclude**: skip unsupported guesses, work covered by `<scheduled-agents>`, work rejected in `docs/kanban/memory/agents/planner/rejected.md` or its module folders, and calls the user reversed, in the `redesign.md` beside each of those files or under a card\'s `Overruled by the user`.\n- **Existing cards**: when shipped work invalidates an open card\'s plan, run `akb card revise <id> "<the change and why>"` instead of proposing an item.\n\n## Write\n\n- **Reader**: someone who has read neither the original card nor the code can decide in ten seconds whether the work is worth doing.\n- **Order**: first explain what happened and what the user runs into, then say what the work would do.\n- **Plain words**: keep file, function and field names, card ids, agent names and evidence trails out of the title and body.\n\nWrite the body to a temporary Markdown file and end with the source card\'s archive path.\nWrite one triage item per proposal. Exploratory ideas are welcome; identify small chores as such.\nUse the source card\'s `workflow:`, or the board default shown by `akb workflow` when it has none.\n\n```text\nakb triage add --title "<one line>" --slug <short-english-slug> --source "#<id>" --workflow <workflow> --file <body.md>\n```\n\nBriefly report what you proposed and what you skipped, with reasons.\n';
23378
23131
  }
23379
23132
  });
23380
23133
 
@@ -23430,7 +23183,7 @@ var init_write_agent = __esm({
23430
23183
  var writing_default;
23431
23184
  var init_writing = __esm({
23432
23185
  "src/guide/writing.md"() {
23433
- 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 <- what whoever builds it must not miss; 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- **What goes here**: what whoever builds the card must not miss \u2014 the user\'s answers first,\n then a decision that passes the material-decision test in `akb guide update-questions`; a lasting\n answer that fails it goes below the human half.\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- **What goes here**: the same, for what the build surfaced and settled.\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- **File name**: when the card\'s file name does not say in English what the card is, rename\n it with `akb raw update <id> --slug <short-english-slug>`; leave a group\'s root as it is.\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';
23186
+ 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<\u7528\u4E00\u5C0F\u6BB5\u8BDD\u5148\u8BB2\u53D1\u751F\u4E86\u4EC0\u4E48\u3001\u7528\u6237\u9047\u5230\u4EC0\u4E48\uFF0C\u518D\u8BB2\u8FD9\u5F20\u5361\u8981\u8FBE\u5230\u7684\u7ED3\u679C\u3002>\n\n## Worth noting <- what whoever builds it must not miss; 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- **What goes here**: what whoever builds the card must not miss \u2014 the user\'s answers first,\n then a decision that passes the material-decision test in `akb guide update-questions`; a lasting\n answer that fails it goes below the human half.\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- **What goes here**: the same, for what the build surfaced and settled.\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- **File name**: when the card\'s file name does not say in English what the card is, rename\n it with `akb raw update <id> --slug <short-english-slug>`; leave a group\'s root as it is.\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';
23434
23187
  }
23435
23188
  });
23436
23189
 
@@ -23518,7 +23271,7 @@ var init_guide = __esm({
23518
23271
  });
23519
23272
 
23520
23273
  // src/lib/agent/approve.ts
23521
- import fs75 from "node:fs";
23274
+ import fs73 from "node:fs";
23522
23275
  import path74 from "node:path";
23523
23276
  function approvalRequest(req) {
23524
23277
  const id = req.id;
@@ -23538,11 +23291,11 @@ function advancePhase(id) {
23538
23291
  const found2 = locate(id);
23539
23292
  if (!card || !found2 || !awaitingApproval(card)) return { error: `#${id} is no longer waiting on approval.` };
23540
23293
  const file = found2.kind === "group" ? path74.join(found2.target, "root.md") : found2.target;
23541
- const { meta, body } = parseFrontmatter(fs75.readFileSync(file, "utf8"));
23294
+ const { meta, body } = parseFrontmatter(fs73.readFileSync(file, "utf8"));
23542
23295
  if (!meta) return { error: `#${id} has no frontmatter` };
23543
23296
  meta.phase = card.stage + 1;
23544
23297
  meta.status = "todo";
23545
- fs75.writeFileSync(file, serializeFrontmatter(meta) + "\n" + body);
23298
+ fs73.writeFileSync(file, serializeFrontmatter(meta) + "\n" + body);
23546
23299
  return null;
23547
23300
  }
23548
23301
  var init_approve = __esm({
@@ -23662,7 +23415,7 @@ var init_start = __esm({
23662
23415
  });
23663
23416
 
23664
23417
  // src/lib/agent/task-chats.ts
23665
- import fs76 from "node:fs";
23418
+ import fs74 from "node:fs";
23666
23419
  import path75 from "node:path";
23667
23420
  function taskChatFile(task, home3) {
23668
23421
  if (!TASK.test(task)) throw new Error(`${task} is not a task id.`);
@@ -23671,7 +23424,7 @@ function taskChatFile(task, home3) {
23671
23424
  function readTaskChat(task, home3) {
23672
23425
  let text5;
23673
23426
  try {
23674
- text5 = fs76.readFileSync(taskChatFile(task, home3), "utf8");
23427
+ text5 = fs74.readFileSync(taskChatFile(task, home3), "utf8");
23675
23428
  } catch {
23676
23429
  return [];
23677
23430
  }
@@ -23697,7 +23450,7 @@ var init_task_chats = __esm({
23697
23450
  });
23698
23451
 
23699
23452
  // src/lib/agent/memory-review.ts
23700
- import fs77 from "node:fs";
23453
+ import fs75 from "node:fs";
23701
23454
  import path76 from "node:path";
23702
23455
  function taskChatsToReview(tasks) {
23703
23456
  const out = [];
@@ -23712,7 +23465,7 @@ function taskChatsToReview(tasks) {
23712
23465
  }
23713
23466
  const messages = lines.map((l) => ({ role: l.role === "user" ? "you" : "agent", text: l.text }));
23714
23467
  const review = handed(task, messages, card);
23715
- if (review.settled || review.transcript) out.push(review);
23468
+ if (review.settled) out.push(review);
23716
23469
  }
23717
23470
  return out;
23718
23471
  }
@@ -23721,7 +23474,7 @@ function readArchived2(id) {
23721
23474
  if (!found2) return null;
23722
23475
  try {
23723
23476
  const file = found2.kind === "group" ? path76.join(found2.target, "root.md") : found2.target;
23724
- const { meta, body } = parseFrontmatter(fs77.readFileSync(file, "utf8"));
23477
+ const { meta, body } = parseFrontmatter(fs75.readFileSync(file, "utf8"));
23725
23478
  if (!meta) return null;
23726
23479
  return { id, title: meta.title, modules: meta.modules ?? [], rejected: meta.rejected, body };
23727
23480
  } catch {
@@ -23789,9 +23542,74 @@ var init_memory_review = __esm({
23789
23542
  }
23790
23543
  });
23791
23544
 
23792
- // src/lib/agent/flow.ts
23793
- import fs78 from "node:fs";
23545
+ // src/lib/agent/dismissal-review.ts
23546
+ import fs76 from "node:fs";
23794
23547
  import path77 from "node:path";
23548
+ function rejectionsToReview() {
23549
+ const dir = path77.join(ARCHIVE, REJECTIONS_TO_REVIEW);
23550
+ if (!fs76.existsSync(dir)) return [];
23551
+ const found2 = [];
23552
+ for (const file of walkMd(dir)) {
23553
+ const id = archivedId(file);
23554
+ if (id === null) continue;
23555
+ try {
23556
+ const { meta } = parseFrontmatter(fs76.readFileSync(file, "utf8"));
23557
+ if (!meta?.rejected || meta.rejected_by !== "user" || !meta.rejected_reason.trim()) continue;
23558
+ found2.push({ id, title: meta.title, file: rel(file), reason: meta.rejected_reason.trim(), rejectedAt: meta.rejected_at });
23559
+ } catch {
23560
+ continue;
23561
+ }
23562
+ }
23563
+ return found2.sort((a, b) => a.rejectedAt.localeCompare(b.rejectedAt) || a.id - b.id);
23564
+ }
23565
+ function dismissalsToReview() {
23566
+ return readAllDismissed().filter((s) => s.dismissedBy === "user" && s.dismissedReason.trim()).sort((a, b) => a.dismissedAt.localeCompare(b.dismissedAt)).map((s) => ({
23567
+ sourceId: s.sourceId,
23568
+ title: s.title,
23569
+ file: s.relPath,
23570
+ reason: s.dismissedReason.trim(),
23571
+ dismissedAt: s.dismissedAt
23572
+ }));
23573
+ }
23574
+ function citedSources(text5) {
23575
+ const ids = /* @__PURE__ */ new Set();
23576
+ for (const line of text5.split("\n")) {
23577
+ if (!/^\s*[-*]\s/.test(line)) continue;
23578
+ const m = line.match(SOURCES);
23579
+ for (const id of m?.[1].split(",") ?? []) if (id.trim()) ids.add(id.trim());
23580
+ }
23581
+ return [...ids];
23582
+ }
23583
+ function withdrawnSources() {
23584
+ const cited = [...new Set(plannerCopies("rejected.md").flatMap((file) => {
23585
+ try {
23586
+ return citedSources(fs76.readFileSync(file, "utf8"));
23587
+ } catch {
23588
+ return [];
23589
+ }
23590
+ }))];
23591
+ if (cited.length === 0) return [];
23592
+ const dismissed = new Set(readAllDismissed().map((s) => s.sourceId));
23593
+ const restored = new Set([...readInbox(), ...readArchived()].map((s) => s.sourceId));
23594
+ return cited.filter((id) => restored.has(id) && !dismissed.has(id));
23595
+ }
23596
+ var SOURCES;
23597
+ var init_dismissal_review = __esm({
23598
+ "src/lib/agent/dismissal-review.ts"() {
23599
+ "use strict";
23600
+ init_cards();
23601
+ init_frontmatter();
23602
+ init_memory();
23603
+ init_paths();
23604
+ init_inbox();
23605
+ init_archive();
23606
+ SOURCES = /\(([^()]+)\)\s*$/;
23607
+ }
23608
+ });
23609
+
23610
+ // src/lib/agent/flow.ts
23611
+ import fs77 from "node:fs";
23612
+ import path78 from "node:path";
23795
23613
  function readCard2(id, home3 = "board") {
23796
23614
  const found2 = home3 === "archive" ? locateArchived(id) : locate(id);
23797
23615
  if (!found2) {
@@ -23800,10 +23618,10 @@ function readCard2(id, home3 = "board") {
23800
23618
  { kind: "card-not-found", id }
23801
23619
  );
23802
23620
  }
23803
- const file = found2.kind === "group" ? path77.join(found2.target, "root.md") : found2.target;
23621
+ const file = found2.kind === "group" ? path78.join(found2.target, "root.md") : found2.target;
23804
23622
  let text5;
23805
23623
  try {
23806
- text5 = fs78.readFileSync(file, "utf8");
23624
+ text5 = fs77.readFileSync(file, "utf8");
23807
23625
  } catch {
23808
23626
  die(`#${id} is on the board but ${rel(file)} can't be read.`, { kind: "card-unreadable", id });
23809
23627
  }
@@ -23927,7 +23745,7 @@ function workspaceField(delivery) {
23927
23745
  if (!delivery?.worktree) return [];
23928
23746
  return field("workspace", [
23929
23747
  `write code in ${delivery.worktree} \u2014 this delivery's own worktree, on branch ${delivery.branch}.`,
23930
- path77.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.`,
23748
+ path78.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.`,
23931
23749
  `every board command names the project's own copy: \`${boardCommandFor()} <command>\`.`,
23932
23750
  `${rel(REPO_ROOT)} is the project \u2014 the card, the memory files and the docs are changed there, not here.`
23933
23751
  ]);
@@ -24108,12 +23926,12 @@ function buildFlow(req, program2) {
24108
23926
  }
24109
23927
  case "resolve": {
24110
23928
  facts.push(...questionsField(card.meta), ...stageField(card.meta));
24111
- const building2 = activeDelivery(req.id);
24112
- if (building2) {
24113
- facts.push(...answeringField(building2, self));
23929
+ const building = activeDelivery(req.id);
23930
+ if (building) {
23931
+ facts.push(...answeringField(building, self));
24114
23932
  facts.push(...notesField(card));
24115
23933
  }
24116
- if (building2) close.push(answeredClose(building2, self));
23934
+ if (building) close.push(answeredClose(building, self));
24117
23935
  close.push(...stageClose(card.meta));
24118
23936
  break;
24119
23937
  }
@@ -24190,7 +24008,6 @@ function buildFlow(req, program2) {
24190
24008
  facts.push(
24191
24009
  ...field("memory", [
24192
24010
  `${rel(agentMemoryDir(PLANNER))}/<module>/ \u2014 decisions.md, rejected.md, redesign.md: where a planning note goes, the planner's own files for one spanning modules`,
24193
- `${proposerMissedFile()} \u2014 a follow-up the user says the proposer missed`,
24194
24011
  `${rel(AGENT_MEMORY)}/<agent>/ \u2014 an agent's own files, which its AGENT.md names`
24195
24012
  ])
24196
24013
  );
@@ -24205,12 +24022,9 @@ function buildFlow(req, program2) {
24205
24022
  }
24206
24023
  // The rejection review (#929, #1497): the lists are the job, decided before any run starts.
24207
24024
  case "review-dismissals": {
24208
- const lastRun = req.allReasons ? void 0 : dismissalReview().lastRun;
24209
- const since = parseStamp(lastRun)?.getTime() ?? 0;
24210
- const rejections = rejectionsToReview(since);
24211
- const dismissals = dismissalsToReview(since);
24025
+ const rejections = rejectionsToReview();
24026
+ const dismissals = dismissalsToReview();
24212
24027
  const withdrawn = withdrawnSources();
24213
- facts.push(...field("window", lastRun ? `since the last review that passed, ${lastRun}` : req.allReasons ? "every reason on the board" : "every reason \u2014 none has been reviewed yet"));
24214
24028
  facts.push(
24215
24029
  ...field(
24216
24030
  "rejections",
@@ -24328,7 +24142,6 @@ var init_flow = __esm({
24328
24142
  init_frontmatter();
24329
24143
  init_io();
24330
24144
  init_guide();
24331
- init_cadence();
24332
24145
  init_memory();
24333
24146
  init_paths();
24334
24147
  init_start();
@@ -24339,7 +24152,6 @@ var init_flow = __esm({
24339
24152
  init_deliveries();
24340
24153
  init_facts();
24341
24154
  init_memory_review();
24342
- init_settings2();
24343
24155
  init_dismissal_review();
24344
24156
  init_language();
24345
24157
  init_prompts();
@@ -24677,7 +24489,7 @@ __export(watch_exports, {
24677
24489
  watchRun: () => watchRun
24678
24490
  });
24679
24491
  import { spawn as spawn4 } from "node:child_process";
24680
- import fs79 from "node:fs";
24492
+ import fs78 from "node:fs";
24681
24493
  async function watchRun(sessionId, resume = startResume) {
24682
24494
  const spec = readSpec(sessionId);
24683
24495
  const run2 = peekRun(sessionId);
@@ -24713,11 +24525,11 @@ async function watchRun(sessionId, resume = startResume) {
24713
24525
  const record = claimed ?? run2;
24714
24526
  const sub = record.action === "sub";
24715
24527
  if (claim) await setCardStatus(claim.cardId, claim.status);
24716
- fs79.mkdirSync(SESSIONS_DIR, { recursive: true });
24717
- const logFile = fs79.createWriteStream(record.logPath, { flags: "a" });
24528
+ fs78.mkdirSync(SESSIONS_DIR, { recursive: true });
24529
+ const logFile = fs78.createWriteStream(record.logPath, { flags: "a" });
24718
24530
  logFile.on("error", () => {
24719
24531
  });
24720
- const eventsFile = fs79.createWriteStream(eventsPathOf(sessionId), { flags: "a" });
24532
+ const eventsFile = fs78.createWriteStream(eventsPathOf(sessionId), { flags: "a" });
24721
24533
  eventsFile.on("error", () => {
24722
24534
  });
24723
24535
  const event2 = (e) => eventsFile.write(`${JSON.stringify(e)}
@@ -24926,7 +24738,7 @@ async function watchRun(sessionId, resume = startResume) {
24926
24738
  }
24927
24739
  const retrying = !!again && !asked;
24928
24740
  const spent = spoken ? spoken.usage : renderer?.usage?.();
24929
- const lost = !!spec.again && !held4.deliveryId && !ours && !retrying && !spawnError && code !== 0 && lastUsed === 0 && !(spent && spent.input + spent.output + spent.cacheRead + spent.cacheCreation > 0);
24741
+ const lost = !!spec.again && (!held4.deliveryId || held4.action === "implement") && !ours && !retrying && !spawnError && code !== 0 && lastUsed === 0 && !(spent && spent.input + spent.output + spent.cacheRead + spent.cacheCreation > 0);
24930
24742
  let status = asked ? "stopped" : silent || unfinished ? "error" : code === 0 ? "done" : "error";
24931
24743
  const reported = spoken ? spoken.costUsd : renderer?.costUsd?.();
24932
24744
  const totals = reportsSessionTotal(record.harness);
@@ -25298,7 +25110,7 @@ var init_watch = __esm({
25298
25110
  });
25299
25111
 
25300
25112
  // src/kanban.ts
25301
- import fs87 from "node:fs";
25113
+ import fs86 from "node:fs";
25302
25114
  import { fileURLToPath as fileURLToPath3 } from "node:url";
25303
25115
 
25304
25116
  // src/lib/cli/agent.ts
@@ -25787,7 +25599,7 @@ async function testAgent(args) {
25787
25599
  }
25788
25600
 
25789
25601
  // src/commands/cloud.ts
25790
- import fs71 from "node:fs";
25602
+ import fs69 from "node:fs";
25791
25603
  import path70 from "node:path";
25792
25604
 
25793
25605
  // src/lib/cloud/avatar.ts
@@ -25836,13 +25648,13 @@ init_config();
25836
25648
  init_home();
25837
25649
  init_config();
25838
25650
  init_session();
25839
- import fs70 from "node:fs";
25651
+ import fs68 from "node:fs";
25840
25652
  import path69 from "node:path";
25841
25653
  var FRESH_MS = 10 * 6e4;
25842
25654
  var GRACE_MS = 7 * 24 * 60 * 6e4;
25843
25655
  var heldFile = () => path69.join(machineHome(), "pro.json");
25844
25656
  function forgetPro() {
25845
- fs70.rmSync(heldFile(), { force: true });
25657
+ fs68.rmSync(heldFile(), { force: true });
25846
25658
  }
25847
25659
 
25848
25660
  // src/lib/cloud/account.ts
@@ -26022,7 +25834,7 @@ async function cloudImage(opts, program2) {
26022
25834
  const type = REF_TYPES[path70.extname(file).toLowerCase()];
26023
25835
  if (!type) usage(`--ref ${file}: use a PNG, JPEG or WebP file.`, line);
26024
25836
  try {
26025
- return `data:${type};base64,${fs71.readFileSync(file).toString("base64")}`;
25837
+ return `data:${type};base64,${fs69.readFileSync(file).toString("base64")}`;
26026
25838
  } catch (e) {
26027
25839
  usage(`--ref ${file}: ${e instanceof Error ? e.message : String(e)}`, line);
26028
25840
  }
@@ -26128,31 +25940,31 @@ async function generate(g) {
26128
25940
  refuse(g.failed, g.fallback);
26129
25941
  }
26130
25942
  const out = g.outFor?.(res, g.out) ?? g.out;
26131
- fs71.mkdirSync(path70.dirname(path70.resolve(out)), { recursive: true });
26132
- fs71.writeFileSync(out, Buffer.from(await res.arrayBuffer()));
25943
+ fs69.mkdirSync(path70.dirname(path70.resolve(out)), { recursive: true });
25944
+ fs69.writeFileSync(out, Buffer.from(await res.arrayBuffer()));
26133
25945
  say(`${out} (${g.label(res)})`);
26134
25946
  return { out };
26135
25947
  }
26136
25948
 
26137
25949
  // src/commands/cloud-import.ts
26138
25950
  import crypto3 from "node:crypto";
26139
- import fs73 from "node:fs";
25951
+ import fs71 from "node:fs";
26140
25952
  import path72 from "node:path";
26141
25953
 
26142
25954
  // ../cloud/src/board-files.ts
26143
25955
  var ROOTS = ["todo/", ".archive/", "memory/", "workflows/", "triage/", "assets/", "rules/"];
26144
25956
  var SETTINGS = ["modules.md", "config.md"];
26145
- var isSettingsPath = (path85) => SETTINGS.includes(path85) || /^rules\/(?:workflows\/)?[^/]+\.md$/.test(path85);
25957
+ var isSettingsPath = (path86) => SETTINGS.includes(path86) || /^rules\/(?:workflows\/)?[^/]+\.md$/.test(path86);
26146
25958
  var BOARD_FILE_LIMIT = 256 * 1024;
26147
- function isBoardPath(path85) {
26148
- if (!path85 || path85.length > 500 || path85.startsWith("/") || path85.includes("\\") || path85.includes("\0")) return false;
26149
- if (path85.split("/").some((part) => !part || part === "." || part === "..")) return false;
26150
- if (path85 === "todo/README.md" || path85.split("/").includes(".sync")) return false;
26151
- if (path85.startsWith("workflows/")) return true;
26152
- if (SETTINGS.includes(path85)) return true;
26153
- if (path85.startsWith("rules/")) return isSettingsPath(path85);
26154
- if (path85.startsWith("assets/")) return path85.endsWith(".json");
26155
- return ROOTS.some((root) => path85.startsWith(root)) && path85.endsWith(".md");
25959
+ function isBoardPath(path86) {
25960
+ if (!path86 || path86.length > 500 || path86.startsWith("/") || path86.includes("\\") || path86.includes("\0")) return false;
25961
+ if (path86.split("/").some((part) => !part || part === "." || part === "..")) return false;
25962
+ if (path86 === "todo/README.md" || path86.split("/").includes(".sync")) return false;
25963
+ if (path86.startsWith("workflows/")) return true;
25964
+ if (SETTINGS.includes(path86)) return true;
25965
+ if (path86.startsWith("rules/")) return isSettingsPath(path86);
25966
+ if (path86.startsWith("assets/")) return path86.endsWith(".json");
25967
+ return ROOTS.some((root) => path86.startsWith(root)) && path86.endsWith(".md");
26156
25968
  }
26157
25969
  var workflowFolder = (id) => id.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
26158
25970
 
@@ -26256,7 +26068,7 @@ init_bundled();
26256
26068
  // src/lib/agents/shared-memory.ts
26257
26069
  init_bundled();
26258
26070
  init_parse();
26259
- import fs72 from "node:fs";
26071
+ import fs70 from "node:fs";
26260
26072
  import path71 from "node:path";
26261
26073
  function sharedMemory(board2) {
26262
26074
  const declared = /* @__PURE__ */ new Map();
@@ -26272,13 +26084,13 @@ function sharedMemory(board2) {
26272
26084
  const root = path71.join(board2, "agents");
26273
26085
  let folders2 = [];
26274
26086
  try {
26275
- folders2 = fs72.readdirSync(root, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name);
26087
+ folders2 = fs70.readdirSync(root, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name);
26276
26088
  } catch {
26277
26089
  }
26278
26090
  for (const folder of folders2.sort()) {
26279
26091
  let text5 = null;
26280
26092
  try {
26281
- text5 = fs72.readFileSync(path71.join(root, folder, "AGENT.md"), "utf8");
26093
+ text5 = fs70.readFileSync(path71.join(root, folder, "AGENT.md"), "utf8");
26282
26094
  } catch {
26283
26095
  }
26284
26096
  take(folder, text5, false);
@@ -26367,12 +26179,12 @@ async function findProject(asked) {
26367
26179
  }
26368
26180
  throw new Refused(`No project of yours has the id or prefix "${asked}".`, "not_found");
26369
26181
  }
26370
- var read = (file) => fs73.readFileSync(file, "utf8");
26182
+ var read = (file) => fs71.readFileSync(file, "utf8");
26371
26183
  var bytes2 = (text5) => Buffer.byteLength(text5, "utf8");
26372
26184
  function files(dir) {
26373
- if (!fs73.existsSync(dir)) return [];
26185
+ if (!fs71.existsSync(dir)) return [];
26374
26186
  const out = [];
26375
- for (const entry of fs73.readdirSync(dir, { withFileTypes: true })) {
26187
+ for (const entry of fs71.readdirSync(dir, { withFileTypes: true })) {
26376
26188
  if (entry.name.startsWith(".")) continue;
26377
26189
  const full = path72.join(dir, entry.name);
26378
26190
  if (entry.isDirectory()) out.push(...files(full));
@@ -26424,7 +26236,7 @@ function packWorkflow(flow) {
26424
26236
  const named2 = [...flow.previews, ...flow.land, ...flow.scheduled].map((h) => h.agent).filter((a) => !LAND_ROLES.includes(a));
26425
26237
  for (const agent of new Set(named2)) {
26426
26238
  const dir = path72.join(AGENTS, agent);
26427
- if (fs73.existsSync(path72.join(dir, "AGENT.md"))) {
26239
+ if (fs71.existsSync(path72.join(dir, "AGENT.md"))) {
26428
26240
  for (const file of files(dir)) out[`agents/${agent}/${posix2(path72.relative(dir, file))}`] = read(file);
26429
26241
  continue;
26430
26242
  }
@@ -26478,7 +26290,7 @@ function draftsOf(text5) {
26478
26290
  if (!at) continue;
26479
26291
  const base = at[1] === "assets" ? ASSETS : MOCKUPS;
26480
26292
  const file = path72.resolve(base, at[2]);
26481
- if (file.startsWith(base + path72.sep) && fs73.statSync(file, { throwIfNoEntry: false })?.isFile()) out.set(name, file);
26293
+ if (file.startsWith(base + path72.sep) && fs71.statSync(file, { throwIfNoEntry: false })?.isFile()) out.set(name, file);
26482
26294
  }
26483
26295
  return [...out].map(([name, file]) => ({ name, file }));
26484
26296
  }
@@ -26514,7 +26326,7 @@ async function uploadDrafts(task, drafts) {
26514
26326
  if (!card.body.includes(name)) continue;
26515
26327
  const ext = path72.extname(file).toLowerCase();
26516
26328
  const type = TYPES[ext] ?? "application/octet-stream";
26517
- const data = fs73.readFileSync(file);
26329
+ const data = fs71.readFileSync(file);
26518
26330
  const artifact = await api("POST", `/v1/tasks/${task}/artifacts`, { type: kindOf(ext), name: name.slice(0, 200) });
26519
26331
  const upload = await api("POST", `/v1/artifacts/${artifact.id}/uploads`, { content_type: type, size: Math.max(data.length, 1) });
26520
26332
  await api("PUT", `/v1/uploads/${upload.id}`, void 0, { bytes: data, type });
@@ -26557,7 +26369,7 @@ usage: ${program2} cloud import --project <id or prefix> [--dry-run]`, { kind: "
26557
26369
  async function run(asked, dryRun) {
26558
26370
  const project = await findProject(asked);
26559
26371
  const cloudFlows = await all(`/v1/workflows?workspace=${encodeURIComponent(project.workspace)}`);
26560
- const board2 = crypto3.createHash("sha256").update(fs73.realpathSync(KANBAN)).digest("hex").slice(0, 16);
26372
+ const board2 = crypto3.createHash("sha256").update(fs71.realpathSync(KANBAN)).digest("hex").slice(0, 16);
26561
26373
  const cards = openCards();
26562
26374
  const problems = [];
26563
26375
  const where = (file) => posix2(path72.relative(KANBAN, file));
@@ -26587,7 +26399,7 @@ async function run(asked, dryRun) {
26587
26399
  const root = `projects/${project.id}/`;
26588
26400
  const boardFiles = [];
26589
26401
  for (const file of [...files(MEMORY), ...files(RULES), CONFIG, MODULES_MD]) {
26590
- if (!fs73.existsSync(file)) continue;
26402
+ if (!fs71.existsSync(file)) continue;
26591
26403
  const rel2 = where(file);
26592
26404
  if (!isBoardPath(rel2)) continue;
26593
26405
  const content = read(file);
@@ -26698,16 +26510,16 @@ async function run(asked, dryRun) {
26698
26510
  }
26699
26511
  function leftLocal(own) {
26700
26512
  const out = [];
26701
- const count4 = (dir) => fs73.existsSync(dir) ? walkMd(dir).length : 0;
26513
+ const count4 = (dir) => fs71.existsSync(dir) ? walkMd(dir).length : 0;
26702
26514
  const archived = count4(ARCHIVE);
26703
26515
  if (archived) out.push(`.archive/ (${archived})`);
26704
26516
  const triage = count4(TRIAGE);
26705
26517
  if (triage) out.push(`triage/ (${triage})`);
26706
- if (fs73.existsSync(DELIVERIES)) out.push("deliveries/");
26707
- if (fs73.existsSync(path72.join(KANBAN, "metrics.csv"))) out.push("metrics.csv");
26518
+ if (fs71.existsSync(DELIVERIES)) out.push("deliveries/");
26519
+ if (fs71.existsSync(path72.join(KANBAN, "metrics.csv"))) out.push("metrics.csv");
26708
26520
  const named2 = new Set(own.flatMap((w) => [...w.previews, ...w.land, ...w.scheduled].map((h) => h.agent)));
26709
- if (fs73.existsSync(AGENTS)) {
26710
- for (const e of fs73.readdirSync(AGENTS, { withFileTypes: true })) {
26521
+ if (fs71.existsSync(AGENTS)) {
26522
+ for (const e of fs71.readdirSync(AGENTS, { withFileTypes: true })) {
26711
26523
  if (e.isDirectory() && !e.name.startsWith(".") && !named2.has(e.name)) out.push(`agents/${e.name}/`);
26712
26524
  }
26713
26525
  }
@@ -26725,7 +26537,7 @@ init_session();
26725
26537
  init_io();
26726
26538
  init_paths();
26727
26539
  import { createHash as createHash4 } from "node:crypto";
26728
- import fs74 from "node:fs";
26540
+ import fs72 from "node:fs";
26729
26541
  import path73 from "node:path";
26730
26542
  var SLUG = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
26731
26543
  var CAPTURED = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:\d{2})$/;
@@ -26827,8 +26639,8 @@ This template has no preview.`);
26827
26639
  const bytes3 = Buffer.from(await (await ask(`/${at}/previews/${route(p.path)}`, {}, WITHOUT)).arrayBuffer());
26828
26640
  const file = path73.basename(p.path);
26829
26641
  const out = path73.join(opts.to, file.startsWith(`${name}-`) ? file : `${name}-${file}`);
26830
- fs74.mkdirSync(opts.to, { recursive: true });
26831
- fs74.writeFileSync(out, bytes3);
26642
+ fs72.mkdirSync(opts.to, { recursive: true });
26643
+ fs72.writeFileSync(out, bytes3);
26832
26644
  written.push(out);
26833
26645
  say(out);
26834
26646
  }
@@ -26849,7 +26661,7 @@ async function templateGet(name, opts, program2) {
26849
26661
  }
26850
26662
  fetched.push({ out, bytes: bytes3 });
26851
26663
  }
26852
- const taken = fetched.filter((f) => fs74.existsSync(f.out)).map((f) => path73.relative(to, f.out));
26664
+ const taken = fetched.filter((f) => fs72.existsSync(f.out)).map((f) => path73.relative(to, f.out));
26853
26665
  if (taken.length > 0) {
26854
26666
  refuse2({
26855
26667
  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`,
@@ -26857,8 +26669,8 @@ async function templateGet(name, opts, program2) {
26857
26669
  });
26858
26670
  }
26859
26671
  for (const f of fetched) {
26860
- fs74.mkdirSync(path73.dirname(f.out), { recursive: true });
26861
- fs74.writeFileSync(f.out, f.bytes);
26672
+ fs72.mkdirSync(path73.dirname(f.out), { recursive: true });
26673
+ fs72.writeFileSync(f.out, f.bytes);
26862
26674
  }
26863
26675
  say(`\u5DF2\u53D6\u56DE ${name}\uFF1A${fetched.length} \u4E2A\u6587\u4EF6\uFF0C\u4F4D\u4E8E ${to}
26864
26676
  Fetched ${name}: ${fetched.length} files into ${to}`);
@@ -26877,8 +26689,8 @@ async function templateSource(name, opts) {
26877
26689
  continue;
26878
26690
  }
26879
26691
  const out = path73.join(opts.to, `${name}-${path73.basename(e.path)}`);
26880
- fs74.mkdirSync(opts.to, { recursive: true });
26881
- fs74.writeFileSync(out, Buffer.from(await (await ask(`/${at}/evidence/${route(e.path)}`)).arrayBuffer()));
26692
+ fs72.mkdirSync(opts.to, { recursive: true });
26693
+ fs72.writeFileSync(out, Buffer.from(await (await ask(`/${at}/evidence/${route(e.path)}`)).arrayBuffer()));
26882
26694
  written.push(out);
26883
26695
  say(out);
26884
26696
  }
@@ -26887,13 +26699,13 @@ async function templateSource(name, opts) {
26887
26699
  async function templatePublish(dir, opts) {
26888
26700
  const root = path73.resolve(dir);
26889
26701
  const manifest = path73.join(root, "template.md");
26890
- 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.");
26891
- const meta = readTemplateMeta(fs74.readFileSync(manifest, "utf8"));
26702
+ 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.");
26703
+ const meta = readTemplateMeta(fs72.readFileSync(manifest, "utf8"));
26892
26704
  const files2 = walk2(root);
26893
- const total = files2.reduce((sum, f) => sum + fs74.statSync(path73.join(root, f)).size, 0);
26705
+ const total = files2.reduce((sum, f) => sum + fs72.statSync(path73.join(root, f)).size, 0);
26894
26706
  if (total > MAX_BYTES3) invalid("\u6A21\u677F\u6587\u4EF6\u5408\u8BA1\u8D85\u8FC7 50 MB\u3002", "the template\u2019s files exceed 50 MB.");
26895
26707
  for (const extra of [...opts.preview, ...opts.evidence]) {
26896
- if (!fs74.existsSync(extra)) invalid(`\u627E\u4E0D\u5230\u6587\u4EF6 ${extra}\u3002`, `${extra} does not exist.`);
26708
+ if (!fs72.existsSync(extra)) invalid(`\u627E\u4E0D\u5230\u6587\u4EF6 ${extra}\u3002`, `${extra} does not exist.`);
26897
26709
  if (path73.resolve(extra).startsWith(root + path73.sep)) {
26898
26710
  invalid(
26899
26711
  `${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`,
@@ -26906,7 +26718,7 @@ async function templatePublish(dir, opts) {
26906
26718
  }
26907
26719
  const form = new FormData();
26908
26720
  form.set("meta", JSON.stringify({ ...meta, replace: !!opts.replace }));
26909
- const add = (kind, name, file) => form.append(`${kind}:${name}`, new Blob([fs74.readFileSync(file)]), path73.basename(name));
26721
+ const add = (kind, name, file) => form.append(`${kind}:${name}`, new Blob([fs72.readFileSync(file)]), path73.basename(name));
26910
26722
  for (const f of files2) add("files", f, path73.join(root, f));
26911
26723
  for (const f of opts.preview) add("previews", path73.basename(f), f);
26912
26724
  for (const f of opts.evidence) add("evidence", path73.basename(f), f);
@@ -26926,10 +26738,10 @@ Deleted ${name} from the catalog.`);
26926
26738
  }
26927
26739
  async function templateShowcase(name, dir) {
26928
26740
  const file = path73.join(dir, "showcase.json");
26929
- if (!fs74.existsSync(file)) invalid(`${dir} \u4E2D\u7F3A\u5C11 showcase.json\u3002`, `showcase.json is missing from ${dir}.`);
26741
+ if (!fs72.existsSync(file)) invalid(`${dir} \u4E2D\u7F3A\u5C11 showcase.json\u3002`, `showcase.json is missing from ${dir}.`);
26930
26742
  let meta;
26931
26743
  try {
26932
- meta = JSON.parse(fs74.readFileSync(file, "utf8"));
26744
+ meta = JSON.parse(fs72.readFileSync(file, "utf8"));
26933
26745
  } catch {
26934
26746
  return invalid("showcase.json \u4E0D\u662F\u6709\u6548\u7684 JSON\u3002", "showcase.json is not valid JSON.");
26935
26747
  }
@@ -26942,8 +26754,8 @@ async function templateShowcase(name, dir) {
26942
26754
  form.set("meta", JSON.stringify(meta));
26943
26755
  for (const p of new Set(named2)) {
26944
26756
  const at = path73.join(dir, p);
26945
- if (!fs74.existsSync(at)) invalid(`\u627E\u4E0D\u5230\u6587\u4EF6 ${at}\u3002`, `${at} does not exist.`);
26946
- form.append(`media:${p}`, new Blob([fs74.readFileSync(at)]), p);
26757
+ if (!fs72.existsSync(at)) invalid(`\u627E\u4E0D\u5230\u6587\u4EF6 ${at}\u3002`, `${at} does not exist.`);
26758
+ form.append(`media:${p}`, new Blob([fs72.readFileSync(at)]), p);
26947
26759
  }
26948
26760
  const done2 = await asJson(await ask(`/${encodeURIComponent(name)}/showcase`, { method: "PUT", body: form }));
26949
26761
  say(`\u5DF2\u66F4\u65B0 ${name} \u7684\u5C55\u793A\u8D44\u6599\uFF1A${done2.files} \u4E2A\u6587\u4EF6\u3002
@@ -26952,7 +26764,7 @@ Updated ${name}'s showcase: ${done2.files} files.`);
26952
26764
  }
26953
26765
  function walk2(root, sub = "") {
26954
26766
  const out = [];
26955
- for (const entry of fs74.readdirSync(path73.join(root, sub), { withFileTypes: true })) {
26767
+ for (const entry of fs72.readdirSync(path73.join(root, sub), { withFileTypes: true })) {
26956
26768
  if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
26957
26769
  const rel2 = sub ? `${sub}/${entry.name}` : entry.name;
26958
26770
  if (entry.isDirectory()) out.push(...walk2(root, rel2));
@@ -27093,12 +26905,6 @@ init_io();
27093
26905
  init_paths();
27094
26906
  init_workflows();
27095
26907
  init_workflow_cards();
27096
- init_cadence();
27097
- init_scheduled();
27098
- init_start();
27099
- var cadenceLine = (one) => one.off ? "off" : isAuto(one.cadence) ? "auto" : `every ${one.cadence}`;
27100
- var WAITS = { tooSoon: "", retrying: "retrying after a failed run", nothingNew: "nothing new yet", building: "waits for the build to finish" };
27101
- var nextLine = (due) => !due ? "" : ` \xB7 next after ${formatStamp(due.next)}${due.wait && WAITS[due.wait] ? ` (${WAITS[due.wait]})` : ""}`;
27102
26908
  var found = (id) => {
27103
26909
  const flow = workflowById(id);
27104
26910
  if (!flow) {
@@ -27122,11 +26928,7 @@ function cmdWorkflowList() {
27122
26928
  if (description !== flow.name) say(` ${description}`);
27123
26929
  say(` previews ${previewNames(previewMembers(flow)) || "(none)"}`);
27124
26930
  say(` land ${names3(flow.land) || "(none) \u2014 the last approval archives the card"}`);
27125
- for (const one of scheduledMembers(flow)) {
27126
- const due = scheduledNext(flow.id, one);
27127
- const ran = one.lastRun ? `last run ${one.lastRun}` : "never run";
27128
- say(` scheduled ${one.agent} ${cadenceLine(one)} \xB7 ${ran}${nextLine(due)}`);
27129
- }
26931
+ for (const agent of listedScheduled(flow)) say(` scheduled ${agent}`);
27130
26932
  for (const problem of workflowProblems(flow.id)) say(` ! ${problem}`);
27131
26933
  }
27132
26934
  const fileProblems = workflowFileProblems();
@@ -27135,7 +26937,7 @@ function cmdWorkflowList() {
27135
26937
  workflows: rows.map((flow) => ({
27136
26938
  ...flow,
27137
26939
  previews: previewMembers(flow).map((h) => ({ ...h, after: agentAfter(h.agent) })),
27138
- scheduled: scheduledMembers(flow),
26940
+ scheduled: listedScheduled(flow),
27139
26941
  description: workflowPurpose(flow),
27140
26942
  problems: workflowProblems(flow.id)
27141
26943
  })),
@@ -27209,48 +27011,6 @@ function cmdWorkflowAgent(id, flags) {
27209
27011
  say(`${flow.name}: ${changes.join(", ")}`);
27210
27012
  return { id: flow.id, changes };
27211
27013
  }
27212
- async function cmdWorkflowSchedule(id, flags) {
27213
- const flow = found(id);
27214
- const changes = [];
27215
- if (flags.on !== void 0) {
27216
- done(switchWorkflowScheduled(flow.id, flags.on, true));
27217
- changes.push(`${flags.on.trim()} on`);
27218
- }
27219
- if (flags.off !== void 0) {
27220
- done(switchWorkflowScheduled(flow.id, flags.off, false));
27221
- changes.push(`${flags.off.trim()} off`);
27222
- }
27223
- const who = (flags.on ?? flags.off ?? flags.run ?? "").trim();
27224
- if (flags.cadence !== void 0) {
27225
- if (!who) die("--cadence says how often one agent runs, so name it: --on <agent> --cadence 1d");
27226
- done(setWorkflowScheduledCadence(flow.id, who, flags.cadence));
27227
- changes.push(`${who}: every ${flags.cadence.trim()}`);
27228
- }
27229
- if (flags.extra !== void 0) {
27230
- if (!who) die('--extra says what one agent is asked for here, so name it: --on <agent> --extra "\u2026"');
27231
- done(setWorkflowScheduledExtra(flow.id, who, flags.extra));
27232
- changes.push(`${who}: extra requirements`);
27233
- }
27234
- if (flags.run !== void 0) {
27235
- const started = await startRun(scheduledRequest({ workflow: flow.id, agent: flags.run.trim() }));
27236
- if ("error" in started) die(started.error);
27237
- say(`${flow.name}: started ${flags.run.trim()} \u2014 run ${started.run.sessionId.slice(0, 8)}`);
27238
- return { id: flow.id, changes, sessionId: started.run.sessionId };
27239
- }
27240
- if (!changes.length) {
27241
- const rows = scheduledMembers(workflowById(flow.id) ?? flow);
27242
- say(`${flow.name} \xB7 scheduled`);
27243
- if (!rows.length) say(" (none) \u2014 an agent no workflow lists can be switched on here");
27244
- for (const one of rows) {
27245
- const due = scheduledNext(flow.id, one);
27246
- const ran = one.lastRun ? `last run ${one.lastRun}` : "never run";
27247
- say(` ${one.agent} ${cadenceLine(one)} \xB7 ${ran}${nextLine(due)}`);
27248
- }
27249
- return { id: flow.id, scheduled: rows };
27250
- }
27251
- say(`${flow.name} \xB7 scheduled: ${changes.join(", ")}`);
27252
- return { id: flow.id, changes };
27253
- }
27254
27014
 
27255
27015
  // src/commands/run.ts
27256
27016
  init_answers();
@@ -27289,7 +27049,7 @@ init_opencode();
27289
27049
  init_locate();
27290
27050
  init_store();
27291
27051
  import { spawnSync as spawnSync5 } from "node:child_process";
27292
- import path78 from "node:path";
27052
+ import path79 from "node:path";
27293
27053
  var AGENTS2 = {
27294
27054
  claude: "claude-code",
27295
27055
  codex: "codex",
@@ -27304,10 +27064,10 @@ var AGENTS2 = {
27304
27064
  var RUNTIMES = /* @__PURE__ */ new Set(["node", "bun", "deno"]);
27305
27065
  function agentOf2(args) {
27306
27066
  const words = args.trim().split(/\s+/);
27307
- const first = path78.basename(words[0] ?? "");
27067
+ const first = path79.basename(words[0] ?? "");
27308
27068
  const script = RUNTIMES.has(first) ? words[1] ?? "" : words[0] ?? "";
27309
27069
  if (script.includes("/@anthropic-ai/claude-code/")) return "claude-code";
27310
- return AGENTS2[path78.basename(script).replace(/\.(m?js|cjs|exe)$/, "")];
27070
+ return AGENTS2[path79.basename(script).replace(/\.(m?js|cjs|exe)$/, "")];
27311
27071
  }
27312
27072
  function processChain() {
27313
27073
  if (process.platform === "win32") return [];
@@ -27370,8 +27130,8 @@ init_tasks();
27370
27130
  init_read();
27371
27131
  init_rules();
27372
27132
  init_board();
27373
- import fs80 from "node:fs";
27374
- import path79 from "node:path";
27133
+ import fs79 from "node:fs";
27134
+ import path80 from "node:path";
27375
27135
  var FOLLOW_MS = 400;
27376
27136
  async function cmdStartRun(action, args, opts, program2 = "akb") {
27377
27137
  const { req, follow: follow2, print } = readRequest(action, args, opts);
@@ -27533,7 +27293,7 @@ function readRequest(action, args, opts) {
27533
27293
  if (!opts.task?.length) die("name the archived Cloud tasks to review: --task <task:card...>", { kind: "run-refused", action });
27534
27294
  return { req: { action, tasks: opts.task }, follow: follow2, print };
27535
27295
  }
27536
- if (action === "review-dismissals") return { req: { action, ...opts.all ? { allReasons: true } : {} }, follow: follow2, print };
27296
+ if (action === "review-dismissals") return { req: { action }, follow: follow2, print };
27537
27297
  if (action === "reflect") {
27538
27298
  const cards = (opts.cards ?? []).map(Number);
27539
27299
  if (!cards.length || !cards.every((n) => Number.isInteger(n) && n > 0)) {
@@ -27656,7 +27416,7 @@ async function cmdSubStart(words, opts) {
27656
27416
  let prompt = words.join(" ");
27657
27417
  if (opts.file) {
27658
27418
  try {
27659
- prompt = fs80.readFileSync(path79.resolve(opts.file), "utf8");
27419
+ prompt = fs79.readFileSync(path80.resolve(opts.file), "utf8");
27660
27420
  } catch {
27661
27421
  die(`--file: cannot read ${opts.file}`, { kind: "run-refused" });
27662
27422
  }
@@ -27938,7 +27698,7 @@ async function cmdWatch(sessionId) {
27938
27698
  // src/commands/triage.ts
27939
27699
  init_scheduled();
27940
27700
  init_workflows();
27941
- import fs82 from "node:fs";
27701
+ import fs81 from "node:fs";
27942
27702
 
27943
27703
  // src/lib/signals/index.ts
27944
27704
  init_cards();
@@ -27948,16 +27708,16 @@ init_yaml();
27948
27708
  // src/lib/signals/config.ts
27949
27709
  init_settings2();
27950
27710
  init_paths();
27951
- import fs81 from "node:fs";
27952
- import path80 from "node:path";
27711
+ import fs80 from "node:fs";
27712
+ import path81 from "node:path";
27953
27713
  var TOKEN_KEY = "TRIAGE_ENDPOINT_TOKEN";
27954
27714
  var ENDPOINT_SETTING = "Triage endpoint";
27955
27715
  var LINE2 = /^- \*\*Triage endpoint\*\*\s*[—-]\s*(.+)$/m;
27956
- var boardRel3 = (file) => rel(file).split(path80.sep).join("/");
27716
+ var boardRel3 = (file) => rel(file).split(path81.sep).join("/");
27957
27717
  function signalEndpoint() {
27958
27718
  let text5;
27959
27719
  try {
27960
- text5 = fs81.readFileSync(CONFIG, "utf8");
27720
+ text5 = fs80.readFileSync(CONFIG, "utf8");
27961
27721
  } catch {
27962
27722
  return "";
27963
27723
  }
@@ -28133,7 +27893,7 @@ async function cmdTriageAdd(opts) {
28133
27893
  let body = opts.text;
28134
27894
  if (opts.file !== void 0) {
28135
27895
  try {
28136
- body = fs82.readFileSync(opts.file, "utf8");
27896
+ body = fs81.readFileSync(opts.file, "utf8");
28137
27897
  } catch {
28138
27898
  die(`can't read ${opts.file} \u2014 write the item to a file, then pass its path`, { kind: "needs-input" });
28139
27899
  }
@@ -28486,7 +28246,8 @@ function declareRuns(program2, cli) {
28486
28246
  }
28487
28247
  const workflow = withShared(program2.command("workflow")).summary("the workflows this board runs, and the agents each one lists").description(
28488
28248
  "Every card runs through one workflow: previews approved by you, each waiting on the approvals its `after` names, then the agents that land the work, after which the card is archived; with none, the last approval archives it. A card chooses its previews when first planned. The built-ins can have their agents switched, and can be copied but not renamed or deleted. A card names its workflow in its own frontmatter (`akb raw create --workflow`), and a delivery freezes the one it started with."
28489
- ).action(async function() {
28249
+ ).allowExcessArguments().allowUnknownOption().action(async function() {
28250
+ if (this.args.length) this.error(`unknown command '${this.args[0]}'`);
28490
28251
  await workflowList(this, cli);
28491
28252
  });
28492
28253
  const flowWord = (name) => withShared(workflow.command(name));
@@ -28518,12 +28279,6 @@ function declareRuns(program2, cli) {
28518
28279
  const flags = this.opts();
28519
28280
  await onBoard2(this, cli, () => cmdWorkflowAgent(id, flags));
28520
28281
  });
28521
- flowWord("schedule").argument("<id>", "the workflow, from `workflow list`").option("--on <agent>", "switch one of this workflow's scheduled agents on").option("--off <agent>", "switch one off; it stays in the workflow").option("--cadence <cadence>", "how often the --on agent runs, e.g. 6h, 1d, 1d at 09:30, or auto (the default)").option("--extra <text>", "what the --on or --off agent is asked for here, on top of its own instructions").option("--run <agent>", "start one run of it now, due or not").summary("switch a workflow's scheduled agents, set how often each runs, or run one now").description(
28522
- "With no change asked for, it lists them with their cadence and last run. A scheduled agent is listed under its workflow's `scheduled` and runs by itself on no card \u2014 in `auto`, when what its `akb.reads` names has something new; what it changes is committed and landed like a build."
28523
- ).action(async function(id) {
28524
- const flags = this.opts();
28525
- await onBoard2(this, cli, () => cmdWorkflowSchedule(id, flags));
28526
- });
28527
28282
  withShared(program2.command("guide")).argument("[topic]", "one flow in full; left off, every flow is listed one line each").summary("the flows the board works by, shipped with this command").description(
28528
28283
  "A printed flow already carries the flows its action is done by, so this is for the rest: how the board works at all, the module map, updating, the local UI. `guide board` is the card format, the layout and the memory set."
28529
28284
  ).action(async function(...vals) {
@@ -28702,8 +28457,8 @@ function declareAgent(program2, cli) {
28702
28457
  }
28703
28458
 
28704
28459
  // src/lib/cli/setup.ts
28705
- import fs86 from "node:fs";
28706
- import path84 from "node:path";
28460
+ import fs85 from "node:fs";
28461
+ import path85 from "node:path";
28707
28462
 
28708
28463
  // src/commands/install.ts
28709
28464
  init_local();
@@ -28715,14 +28470,14 @@ init_io();
28715
28470
  init_paths();
28716
28471
  init_memory();
28717
28472
  init_yaml2();
28718
- import fs85 from "node:fs";
28719
- import path83 from "node:path";
28473
+ import fs84 from "node:fs";
28474
+ import path84 from "node:path";
28720
28475
  import { spawnSync as spawnSync8 } from "node:child_process";
28721
28476
 
28722
28477
  // src/lib/skill/install.ts
28723
28478
  import { spawnSync as spawnSync7 } from "node:child_process";
28724
- import fs84 from "node:fs";
28725
- import path82 from "node:path";
28479
+ import fs83 from "node:fs";
28480
+ import path83 from "node:path";
28726
28481
  import { fileURLToPath as fileURLToPath2 } from "node:url";
28727
28482
 
28728
28483
  // ../skill/SKILL.md
@@ -28736,8 +28491,8 @@ init_version();
28736
28491
  init_version();
28737
28492
  init_project();
28738
28493
  import { spawnSync as spawnSync6 } from "node:child_process";
28739
- import fs83 from "node:fs";
28740
- import path81 from "node:path";
28494
+ import fs82 from "node:fs";
28495
+ import path82 from "node:path";
28741
28496
  var MARKER3 = "# ai4kanban commit guard";
28742
28497
  var MARKER_LINE = new RegExp(`^${MARKER3}\\b`, "m");
28743
28498
  var CHECK = [
@@ -28771,7 +28526,7 @@ function hookScript(sessions) {
28771
28526
  }
28772
28527
  var read3 = (file) => {
28773
28528
  try {
28774
- return fs83.readFileSync(file, "utf8");
28529
+ return fs82.readFileSync(file, "utf8");
28775
28530
  } catch {
28776
28531
  return null;
28777
28532
  }
@@ -28783,7 +28538,7 @@ function hooksDir(root) {
28783
28538
  windowsHide: true
28784
28539
  });
28785
28540
  if (out.error || out.status !== 0 || !out.stdout.trim()) return null;
28786
- return path81.resolve(root, out.stdout.trim());
28541
+ return path82.resolve(root, out.stdout.trim());
28787
28542
  }
28788
28543
  function hasOwnHooksPath(root) {
28789
28544
  const out = spawnSync6("git", ["config", "--get", "core.hooksPath"], {
@@ -28793,18 +28548,18 @@ function hasOwnHooksPath(root) {
28793
28548
  });
28794
28549
  return !out.error && out.status === 0 && !!out.stdout.trim();
28795
28550
  }
28796
- var sessionsPath = (root) => path81.join(projectStateDir(path81.join(root, "docs", "kanban"), root), SESSIONS_FILE);
28551
+ var sessionsPath = (root) => path82.join(projectStateDir(path82.join(root, "docs", "kanban"), root), SESSIONS_FILE);
28797
28552
  function readCommitHook(root) {
28798
28553
  const dir = hooksDir(root);
28799
28554
  if (!dir) return { state: "no-git" };
28800
- const where = shown(root, path81.join(dir, "pre-commit"));
28555
+ const where = shown(root, path82.join(dir, "pre-commit"));
28801
28556
  const line = guardLine(sessionsPath(root));
28802
28557
  if (hasOwnHooksPath(root)) return { state: "hooks-path", path: where, line };
28803
- const text5 = read3(path81.join(dir, "pre-commit"));
28558
+ const text5 = read3(path82.join(dir, "pre-commit"));
28804
28559
  if (text5 === null) return { state: "absent", path: where };
28805
28560
  return MARKER_LINE.test(text5) ? { state: "ours", path: where } : { state: "foreign", path: where, line };
28806
28561
  }
28807
- var shown = (root, p) => (path81.relative(root, p) || p).split(path81.sep).join("/");
28562
+ var shown = (root, p) => (path82.relative(root, p) || p).split(path82.sep).join("/");
28808
28563
  function installCommitHook(root) {
28809
28564
  const hook = readCommitHook(root);
28810
28565
  if (hook.state === "no-git") return {};
@@ -28815,11 +28570,11 @@ function installCommitHook(root) {
28815
28570
  ${hook.line}`
28816
28571
  };
28817
28572
  }
28818
- const file = path81.join(root, hook.path);
28573
+ const file = path82.join(root, hook.path);
28819
28574
  try {
28820
- fs83.mkdirSync(path81.dirname(file), { recursive: true });
28821
- fs83.writeFileSync(file, hookScript(sessionsPath(root)), { mode: 493 });
28822
- fs83.chmodSync(file, 493);
28575
+ fs82.mkdirSync(path82.dirname(file), { recursive: true });
28576
+ fs82.writeFileSync(file, hookScript(sessionsPath(root)), { mode: 493 });
28577
+ fs82.chmodSync(file, 493);
28823
28578
  } catch (e) {
28824
28579
  return { note: `${hook.path} could not be written \u2014 ${e instanceof Error ? e.message : String(e)}` };
28825
28580
  }
@@ -28835,9 +28590,9 @@ function sayCommitHook(state) {
28835
28590
 
28836
28591
  // src/lib/skill/install.ts
28837
28592
  var TARGETS = [
28838
- { rel: path82.join(".claude", "skills", "kanban"), agent: "Claude Code" },
28593
+ { rel: path83.join(".claude", "skills", "kanban"), agent: "Claude Code" },
28839
28594
  {
28840
- rel: path82.join(".agents", "skills", "kanban"),
28595
+ rel: path83.join(".agents", "skills", "kanban"),
28841
28596
  agent: "Codex, Cursor, OpenCode, Kimi Code, DeepSeek Harness, ZCode, Grok Build"
28842
28597
  }
28843
28598
  ];
@@ -28851,23 +28606,23 @@ function rulesPath() {
28851
28606
  }
28852
28607
  function read4(file) {
28853
28608
  try {
28854
- return fs84.readFileSync(file, "utf8");
28609
+ return fs83.readFileSync(file, "utf8");
28855
28610
  } catch {
28856
28611
  return null;
28857
28612
  }
28858
28613
  }
28859
28614
  function statOf(p) {
28860
28615
  try {
28861
- return fs84.lstatSync(p);
28616
+ return fs83.lstatSync(p);
28862
28617
  } catch {
28863
28618
  return null;
28864
28619
  }
28865
28620
  }
28866
28621
  function versionIn2(dir) {
28867
- const note = read4(path82.join(dir, "SKILL.md"));
28622
+ const note = read4(path83.join(dir, "SKILL.md"));
28868
28623
  const stampedNote = note?.match(NOTE_STAMP);
28869
28624
  if (stampedNote) return stampedNote[1];
28870
- const src = read4(path82.join(dir, "kanban.mjs"));
28625
+ const src = read4(path83.join(dir, "kanban.mjs"));
28871
28626
  if (!src) return null;
28872
28627
  const stamped = src.slice(0, 400).match(BUILT_STAMP);
28873
28628
  if (stamped) return stamped[1];
@@ -28891,7 +28646,7 @@ function folderState(dir) {
28891
28646
  const st = statOf(dir);
28892
28647
  if (!st) return { state: "absent", version: null };
28893
28648
  if (st.isSymbolicLink()) return { state: "linked", version: versionIn2(dir) };
28894
- if (!fs84.existsSync(path82.join(dir, "SKILL.md"))) return { state: "absent", version: null };
28649
+ if (!fs83.existsSync(path83.join(dir, "SKILL.md"))) return { state: "absent", version: null };
28895
28650
  const version = versionIn2(dir);
28896
28651
  if (!version) return { state: "unknown", version: null };
28897
28652
  return { state: isOlder(version, SKILL_VERSION) ? "stale" : "current", version };
@@ -28899,9 +28654,9 @@ function folderState(dir) {
28899
28654
  function readSkillState(root) {
28900
28655
  const dir = rootOr(root);
28901
28656
  const folders2 = TARGETS.map((target) => ({
28902
- path: target.rel.split(path82.sep).join("/"),
28657
+ path: target.rel.split(path83.sep).join("/"),
28903
28658
  agent: target.agent,
28904
- ...folderState(path82.join(dir, target.rel))
28659
+ ...folderState(path83.join(dir, target.rel))
28905
28660
  }));
28906
28661
  return {
28907
28662
  version: SKILL_VERSION,
@@ -28911,9 +28666,9 @@ function readSkillState(root) {
28911
28666
  };
28912
28667
  }
28913
28668
  function writeFolder(dir) {
28914
- fs84.rmSync(dir, { recursive: true, force: true });
28915
- fs84.mkdirSync(dir, { recursive: true });
28916
- fs84.writeFileSync(path82.join(dir, "SKILL.md"), `${SKILL_default.trimEnd()}
28669
+ fs83.rmSync(dir, { recursive: true, force: true });
28670
+ fs83.mkdirSync(dir, { recursive: true });
28671
+ fs83.writeFileSync(path83.join(dir, "SKILL.md"), `${SKILL_default.trimEnd()}
28917
28672
 
28918
28673
  <!-- ai4kanban ${SKILL_VERSION} -->
28919
28674
  `);
@@ -28928,8 +28683,8 @@ function installSkill(root, only) {
28928
28683
  const wrote = [];
28929
28684
  const skipped = [];
28930
28685
  for (const target of TARGETS) {
28931
- const dest = path82.join(dir, target.rel);
28932
- const shown2 = target.rel.split(path82.sep).join("/");
28686
+ const dest = path83.join(dir, target.rel);
28687
+ const shown2 = target.rel.split(path83.sep).join("/");
28933
28688
  const { state: state2 } = folderState(dest);
28934
28689
  if (state2 === "linked") {
28935
28690
  skipped.push({ path: shown2, agent: target.agent, why: "a symlink into a source checkout \u2014 left untouched" });
@@ -28998,10 +28753,10 @@ var Report = class {
28998
28753
  for (const line of this.notes) say(` ! ${line}`);
28999
28754
  }
29000
28755
  };
29001
- var read5 = (file) => fs85.existsSync(file) ? fs85.readFileSync(file, "utf8") : null;
28756
+ var read5 = (file) => fs84.existsSync(file) ? fs84.readFileSync(file, "utf8") : null;
29002
28757
  function statOf2(p) {
29003
28758
  try {
29004
- return fs85.lstatSync(p);
28759
+ return fs84.lstatSync(p);
29005
28760
  } catch {
29006
28761
  return null;
29007
28762
  }
@@ -29012,10 +28767,10 @@ async function boardMove(root, args, board2) {
29012
28767
  if (code !== 0) throw new BoardError(`\`board ${args.join(" ")}\` failed \u2014 nothing else was changed`, { kind: "board-move-failed" });
29013
28768
  }
29014
28769
  function boardAbove(root) {
29015
- let at = path83.dirname(path83.resolve(root));
28770
+ let at = path84.dirname(path84.resolve(root));
29016
28771
  for (; ; ) {
29017
- if (fs85.existsSync(path83.join(at, "docs", "kanban"))) return at;
29018
- const up = path83.dirname(at);
28772
+ if (fs84.existsSync(path84.join(at, "docs", "kanban"))) return at;
28773
+ const up = path84.dirname(at);
29019
28774
  if (up === at) return null;
29020
28775
  at = up;
29021
28776
  }
@@ -29023,10 +28778,10 @@ function boardAbove(root) {
29023
28778
  async function cmdInstall(ctx) {
29024
28779
  const report3 = new Report();
29025
28780
  const board2 = ctx.board ?? null;
29026
- const where = board2 ? path83.relative(ctx.dir, board2) || board2 : "docs/kanban";
28781
+ const where = board2 ? path84.relative(ctx.dir, board2) || board2 : "docs/kanban";
29027
28782
  say(`wildcrane ${SKILL_VERSION} \u2014 installing into ${ctx.dir}`);
29028
28783
  say("");
29029
- const above = board2 || fs85.existsSync(path83.join(ctx.dir, "docs", "kanban")) ? null : boardAbove(ctx.dir);
28784
+ const above = board2 || fs84.existsSync(path84.join(ctx.dir, "docs", "kanban")) ? null : boardAbove(ctx.dir);
29030
28785
  if (above) {
29031
28786
  report3.notes.push(`there is already a board at ${above} \u2014 this makes a second one, and commands run here will find this one`);
29032
28787
  }
@@ -29048,7 +28803,7 @@ async function cmdInstall(ctx) {
29048
28803
  say("board UI (Configuration \u2192 Agent setup), or here:");
29049
28804
  say("");
29050
28805
  say(` ${ctx.program} skill`);
29051
- return { installed: ctx.dir, board: board2 ?? path83.join(ctx.dir, "docs", "kanban") };
28806
+ return { installed: ctx.dir, board: board2 ?? path84.join(ctx.dir, "docs", "kanban") };
29052
28807
  }
29053
28808
  async function cmdSkill(ctx, mode) {
29054
28809
  const report3 = new Report();
@@ -29089,7 +28844,7 @@ function placeSkill(ctx, mode, report3) {
29089
28844
  const before = readSkillState(ctx.dir).folders;
29090
28845
  for (const folder of before) {
29091
28846
  if (folder.state !== "absent" && folder.state !== "linked") {
29092
- rescueSkillConfig(ctx.dir, path83.join(ctx.dir, folder.path), report3);
28847
+ rescueSkillConfig(ctx.dir, path84.join(ctx.dir, folder.path), report3);
29093
28848
  }
29094
28849
  }
29095
28850
  const result = installSkill(ctx.dir, mode === "update" ? "present" : void 0);
@@ -29112,20 +28867,20 @@ function placeSkill(ctx, mode, report3) {
29112
28867
  return { result, from: before.map((f) => f.version).find(Boolean) || null };
29113
28868
  }
29114
28869
  function rescueSkillConfig(root, skillDir, report3) {
29115
- const old = read5(path83.join(skillDir, "config.md"));
28870
+ const old = read5(path84.join(skillDir, "config.md"));
29116
28871
  if (!old || old.includes("{{")) return;
29117
- const boardConfig = path83.join(root, "docs", "kanban", "config.md");
28872
+ const boardConfig = path84.join(root, "docs", "kanban", "config.md");
29118
28873
  const current = read5(boardConfig);
29119
28874
  if (current === null) {
29120
- fs85.mkdirSync(path83.dirname(boardConfig), { recursive: true });
29121
- fs85.writeFileSync(boardConfig, old);
28875
+ fs84.mkdirSync(path84.dirname(boardConfig), { recursive: true });
28876
+ fs84.writeFileSync(boardConfig, old);
29122
28877
  report3.did.push("moved your filled-in config.md out of the skill folder to docs/kanban/config.md");
29123
28878
  return;
29124
28879
  }
29125
28880
  if (current === old) return;
29126
- const aside = path83.join(root, "docs", "kanban", "config.from-skill.md");
29127
- if (fs85.existsSync(aside)) return;
29128
- fs85.writeFileSync(aside, old);
28881
+ const aside = path84.join(root, "docs", "kanban", "config.from-skill.md");
28882
+ if (fs84.existsSync(aside)) return;
28883
+ fs84.writeFileSync(aside, old);
29129
28884
  report3.notes.push(
29130
28885
  "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"
29131
28886
  );
@@ -29178,8 +28933,8 @@ async function cmdUpdate2(ctx) {
29178
28933
  return { updated: ctx.dir, from, to: SKILL_VERSION };
29179
28934
  }
29180
28935
  async function repairBoard(root, report3) {
29181
- const board2 = path83.join(root, "docs", "kanban");
29182
- if (!fs85.existsSync(board2)) {
28936
+ const board2 = path84.join(root, "docs", "kanban");
28937
+ if (!fs84.existsSync(board2)) {
29183
28938
  report3.notes.push("no docs/kanban/ here \u2014 run `akb install` to scaffold the board");
29184
28939
  return;
29185
28940
  }
@@ -29209,20 +28964,20 @@ function moveRuntimes(root, board2, report3) {
29209
28964
  }
29210
28965
  }
29211
28966
  function moveLegacyMemory(board2, report3) {
29212
- const memory = path83.join(board2, "memory");
28967
+ const memory = path84.join(board2, "memory");
29213
28968
  const moved = [];
29214
28969
  for (const name of MEMORY_FILES) {
29215
- const from = path83.join(board2, name);
28970
+ const from = path84.join(board2, name);
29216
28971
  const st = statOf2(from);
29217
28972
  if (!st || !st.isFile()) continue;
29218
- fs85.mkdirSync(memory, { recursive: true });
29219
- const to = path83.join(memory, name);
28973
+ fs84.mkdirSync(memory, { recursive: true });
28974
+ const to = path84.join(memory, name);
29220
28975
  const there = read5(to);
29221
28976
  if (there === null) {
29222
- fs85.renameSync(from, to);
28977
+ fs84.renameSync(from, to);
29223
28978
  moved.push(name);
29224
28979
  } else if (there === read5(from)) {
29225
- fs85.rmSync(from);
28980
+ fs84.rmSync(from);
29226
28981
  moved.push(name);
29227
28982
  } else {
29228
28983
  report3.notes.push(`both docs/kanban/${name} and docs/kanban/memory/${name} exist and differ \u2014 merge them by hand`);
@@ -29231,23 +28986,23 @@ function moveLegacyMemory(board2, report3) {
29231
28986
  if (moved.length) report3.did.push(`moved the memory set into docs/kanban/memory/: ${moved.join(", ")}`);
29232
28987
  }
29233
28988
  function dropRecordFile(board2) {
29234
- const file = path83.join(board2, "record.csv");
29235
- if (!fs85.existsSync(file)) return null;
29236
- fs85.rmSync(file);
28989
+ const file = path84.join(board2, "record.csv");
28990
+ if (!fs84.existsSync(file)) return null;
28991
+ fs84.rmSync(file);
29237
28992
  return "removed docs/kanban/record.csv (the planning scores it fed are gone)";
29238
28993
  }
29239
28994
  function dropModuleGoals(board2, report3) {
29240
- const memory = path83.join(board2, "memory");
29241
- if (!fs85.existsSync(memory)) return;
29242
- const root = read5(path83.join(memory, "goal.md"));
28995
+ const memory = path84.join(board2, "memory");
28996
+ if (!fs84.existsSync(memory)) return;
28997
+ const root = read5(path84.join(memory, "goal.md"));
29243
28998
  let dropped = 0;
29244
- for (const entry of fs85.readdirSync(memory, { withFileTypes: true })) {
28999
+ for (const entry of fs84.readdirSync(memory, { withFileTypes: true })) {
29245
29000
  if (!entry.isDirectory()) continue;
29246
- const file = path83.join(memory, entry.name, "goal.md");
29001
+ const file = path84.join(memory, entry.name, "goal.md");
29247
29002
  const text5 = read5(file);
29248
29003
  if (text5 === null) continue;
29249
29004
  if (text5 === root || text5.includes(UNFILLED) || !goalWords(text5)) {
29250
- fs85.rmSync(file);
29005
+ fs84.rmSync(file);
29251
29006
  dropped++;
29252
29007
  } else {
29253
29008
  report3.notes.push(
@@ -29269,10 +29024,10 @@ var PLAN_LEADS = {
29269
29024
  var GONE_LEADS = ["software-planner", "planner"];
29270
29025
  function planLeads(board2) {
29271
29026
  const leads = { ...PLAN_LEADS };
29272
- const root = path83.join(board2, "workflows");
29273
- for (const entry of fs85.existsSync(root) ? fs85.readdirSync(root, { withFileTypes: true }) : []) {
29027
+ const root = path84.join(board2, "workflows");
29028
+ for (const entry of fs84.existsSync(root) ? fs84.readdirSync(root, { withFileTypes: true }) : []) {
29274
29029
  if (!entry.isDirectory()) continue;
29275
- const meta = splitFrontmatter(read5(path83.join(root, entry.name, "WORKFLOW.md")) ?? "").meta;
29030
+ const meta = splitFrontmatter(read5(path84.join(root, entry.name, "WORKFLOW.md")) ?? "").meta;
29276
29031
  const stages = meta === null ? void 0 : parseYamlBlock(meta).stages;
29277
29032
  const plan = stages && typeof stages === "object" && !Array.isArray(stages) ? stages.plan : void 0;
29278
29033
  const lead = plan && typeof plan === "object" && !Array.isArray(plan) && typeof plan.lead === "string" ? plan.lead.trim() : "";
@@ -29281,30 +29036,30 @@ function planLeads(board2) {
29281
29036
  return leads;
29282
29037
  }
29283
29038
  function movePlanRules(board2, report3) {
29284
- const rules = path83.join(board2, "rules");
29039
+ const rules = path84.join(board2, "rules");
29285
29040
  const moved = [];
29286
29041
  for (const [id, leads] of Object.entries(planLeads(board2))) {
29287
29042
  for (const lead of leads) {
29288
- const from = path83.join(rules, `${lead}.md`);
29043
+ const from = path84.join(rules, `${lead}.md`);
29289
29044
  const rule = read5(from)?.trim();
29290
29045
  if (!rule) continue;
29291
- const to = path83.join(rules, "workflows", `${id}.md`);
29046
+ const to = path84.join(rules, "workflows", `${id}.md`);
29292
29047
  const there = read5(to)?.trim() ?? "";
29293
29048
  if (!there.includes(rule)) {
29294
- fs85.mkdirSync(path83.dirname(to), { recursive: true });
29295
- fs85.writeFileSync(to, `${[there, rule].filter(Boolean).join("\n\n")}
29049
+ fs84.mkdirSync(path84.dirname(to), { recursive: true });
29050
+ fs84.writeFileSync(to, `${[there, rule].filter(Boolean).join("\n\n")}
29296
29051
  `);
29297
29052
  moved.push(`${lead} \u2192 ${id}`);
29298
29053
  }
29299
29054
  }
29300
29055
  }
29301
- for (const lead of GONE_LEADS) fs85.rmSync(path83.join(rules, `${lead}.md`), { force: true });
29056
+ for (const lead of GONE_LEADS) fs84.rmSync(path84.join(rules, `${lead}.md`), { force: true });
29302
29057
  if (moved.length) report3.did.push(`made each plan agent's rule its workflow's card rule (docs/kanban/rules/workflows/): ${moved.join(", ")}`);
29303
29058
  }
29304
29059
  function noteOldWorkflows(board2, report3) {
29305
29060
  const old = (dir, file, keys) => {
29306
- const root = path83.join(board2, dir);
29307
- return (fs85.existsSync(root) ? fs85.readdirSync(root, { withFileTypes: true }) : []).filter((e) => e.isDirectory() && keys.test(splitFrontmatter(read5(path83.join(root, e.name, file)) ?? "").meta ?? "")).map((e) => `docs/kanban/${dir}/${e.name}/${file}`);
29061
+ const root = path84.join(board2, dir);
29062
+ return (fs84.existsSync(root) ? fs84.readdirSync(root, { withFileTypes: true }) : []).filter((e) => e.isDirectory() && keys.test(splitFrontmatter(read5(path84.join(root, e.name, file)) ?? "").meta ?? "")).map((e) => `docs/kanban/${dir}/${e.name}/${file}`);
29308
29063
  };
29309
29064
  const files2 = [...old("workflows", "WORKFLOW.md", /^(stages|delivers):/m), ...old("agents", "AGENT.md", /^\s+(hook|lead):/m)];
29310
29065
  if (files2.length) {
@@ -29314,7 +29069,7 @@ function noteOldWorkflows(board2, report3) {
29314
29069
  }
29315
29070
  }
29316
29071
  function checkConfig(board2, report3) {
29317
- const current = read5(path83.join(board2, "config.md"));
29072
+ const current = read5(path84.join(board2, "config.md"));
29318
29073
  if (!current) return;
29319
29074
  const added = missingConfigKeys(current);
29320
29075
  if (added.length) {
@@ -29324,7 +29079,7 @@ function checkConfig(board2, report3) {
29324
29079
  }
29325
29080
  }
29326
29081
  function checkModules(board2, report3) {
29327
- const map2 = read5(path83.join(board2, "modules.md"));
29082
+ const map2 = read5(path84.join(board2, "modules.md"));
29328
29083
  if (map2 && map2.includes(UNFILLED)) {
29329
29084
  report3.notes.push(
29330
29085
  "docs/kanban/modules.md is still blank \u2014 write it from the repo (`akb guide module-map`)"
@@ -29378,13 +29133,13 @@ function declareSetup(program2, cli) {
29378
29133
  program2.version(SKILL_VERSION, "-v, --version", "print this version").addHelpText("beforeAll", spelled(cli.program));
29379
29134
  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");
29380
29135
  const where = (cmd) => {
29381
- const dir = path84.resolve(String(cmd.optsWithGlobals().dir ?? cli.cwd));
29382
- if (!fs86.existsSync(dir)) throw new BoardError(`no such folder: ${dir}`, { kind: "no-such-folder", dir });
29136
+ const dir = path85.resolve(String(cmd.optsWithGlobals().dir ?? cli.cwd));
29137
+ if (!fs85.existsSync(dir)) throw new BoardError(`no such folder: ${dir}`, { kind: "no-such-folder", dir });
29383
29138
  return dir;
29384
29139
  };
29385
29140
  const boardOf = (cmd) => {
29386
29141
  const named2 = cmd.optsWithGlobals().board;
29387
- return named2 ? path84.resolve(cli.cwd, named2) : null;
29142
+ return named2 ? path85.resolve(cli.cwd, named2) : null;
29388
29143
  };
29389
29144
  dirOption(program2.command("install")).option("--board <dir>", "put the board here instead of docs/kanban/ (relative to this folder)").summary("create the board in this project, and nothing else").description(
29390
29145
  "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."
@@ -29457,7 +29212,7 @@ function invokedDirectly() {
29457
29212
  const entry = process.argv[1];
29458
29213
  if (!entry) return false;
29459
29214
  try {
29460
- return fs87.realpathSync(entry) === fs87.realpathSync(SELF3);
29215
+ return fs86.realpathSync(entry) === fs86.realpathSync(SELF3);
29461
29216
  } catch {
29462
29217
  return false;
29463
29218
  }