wildcrane-runner 0.6.18 → 0.6.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // ai4kanban 0.11.16 — built by runner/scripts/build.mjs. Do not edit.
2
+ // ai4kanban 0.11.17 — built by runner/scripts/build.mjs. Do not edit.
3
3
  import { createRequire as __ai4kanbanRequire } from 'node:module'
4
4
  const require = __ai4kanbanRequire(import.meta.url)
5
5
  var __create = Object.create;
@@ -1167,8 +1167,8 @@ var require_command = __commonJS({
1167
1167
  "../cli/node_modules/commander/lib/command.js"(exports) {
1168
1168
  var EventEmitter = __require("node:events").EventEmitter;
1169
1169
  var childProcess = __require("node:child_process");
1170
- var path86 = __require("node:path");
1171
- var fs86 = __require("node:fs");
1170
+ var path88 = __require("node:path");
1171
+ var fs87 = __require("node:fs");
1172
1172
  var process2 = __require("node:process");
1173
1173
  var { Argument: Argument2, humanReadableArgName } = require_argument();
1174
1174
  var { CommanderError: CommanderError2 } = require_error();
@@ -2149,7 +2149,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2149
2149
  * @param {string} subcommandName
2150
2150
  */
2151
2151
  _checkForMissingExecutable(executableFile, executableDir, subcommandName) {
2152
- if (fs86.existsSync(executableFile)) return;
2152
+ if (fs87.existsSync(executableFile)) return;
2153
2153
  const executableDirMessage = executableDir ? `searched for local subcommand relative to directory '${executableDir}'` : "no directory for search for local subcommand, use .executableDir() to supply a custom directory";
2154
2154
  const executableMissing = `'${executableFile}' does not exist
2155
2155
  - if '${subcommandName}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
@@ -2167,11 +2167,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
2167
2167
  let launchWithNode = false;
2168
2168
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
2169
2169
  function findFile(baseDir, baseName) {
2170
- const localBin = path86.resolve(baseDir, baseName);
2171
- if (fs86.existsSync(localBin)) return localBin;
2172
- if (sourceExt.includes(path86.extname(baseName))) return void 0;
2170
+ const localBin = path88.resolve(baseDir, baseName);
2171
+ if (fs87.existsSync(localBin)) return localBin;
2172
+ if (sourceExt.includes(path88.extname(baseName))) return void 0;
2173
2173
  const foundExt = sourceExt.find(
2174
- (ext) => fs86.existsSync(`${localBin}${ext}`)
2174
+ (ext) => fs87.existsSync(`${localBin}${ext}`)
2175
2175
  );
2176
2176
  if (foundExt) return `${localBin}${foundExt}`;
2177
2177
  return void 0;
@@ -2183,21 +2183,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
2183
2183
  if (this._scriptPath) {
2184
2184
  let resolvedScriptPath;
2185
2185
  try {
2186
- resolvedScriptPath = fs86.realpathSync(this._scriptPath);
2186
+ resolvedScriptPath = fs87.realpathSync(this._scriptPath);
2187
2187
  } catch {
2188
2188
  resolvedScriptPath = this._scriptPath;
2189
2189
  }
2190
- executableDir = path86.resolve(
2191
- path86.dirname(resolvedScriptPath),
2190
+ executableDir = path88.resolve(
2191
+ path88.dirname(resolvedScriptPath),
2192
2192
  executableDir
2193
2193
  );
2194
2194
  }
2195
2195
  if (executableDir) {
2196
2196
  let localFile = findFile(executableDir, executableFile);
2197
2197
  if (!localFile && !subcommand._executableFile && this._scriptPath) {
2198
- const legacyName = path86.basename(
2198
+ const legacyName = path88.basename(
2199
2199
  this._scriptPath,
2200
- path86.extname(this._scriptPath)
2200
+ path88.extname(this._scriptPath)
2201
2201
  );
2202
2202
  if (legacyName !== this._name) {
2203
2203
  localFile = findFile(
@@ -2208,7 +2208,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2208
2208
  }
2209
2209
  executableFile = localFile || executableFile;
2210
2210
  }
2211
- launchWithNode = sourceExt.includes(path86.extname(executableFile));
2211
+ launchWithNode = sourceExt.includes(path88.extname(executableFile));
2212
2212
  let proc;
2213
2213
  if (process2.platform !== "win32") {
2214
2214
  if (launchWithNode) {
@@ -3055,7 +3055,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
3055
3055
  * @return {Command}
3056
3056
  */
3057
3057
  nameFromFilename(filename) {
3058
- this._name = path86.basename(filename, path86.extname(filename));
3058
+ this._name = path88.basename(filename, path88.extname(filename));
3059
3059
  return this;
3060
3060
  }
3061
3061
  /**
@@ -3069,9 +3069,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
3069
3069
  * @param {string} [path]
3070
3070
  * @return {(string|null|Command)}
3071
3071
  */
3072
- executableDir(path87) {
3073
- if (path87 === void 0) return this._executableDir;
3074
- this._executableDir = path87;
3072
+ executableDir(path89) {
3073
+ if (path89 === void 0) return this._executableDir;
3074
+ this._executableDir = path89;
3075
3075
  return this;
3076
3076
  }
3077
3077
  /**
@@ -3754,192 +3754,6 @@ var init_env = __esm({
3754
3754
  }
3755
3755
  });
3756
3756
 
3757
- // ../cli/src/lib/lock.ts
3758
- import { randomUUID } from "node:crypto";
3759
- import fs4 from "node:fs";
3760
- import path5 from "node:path";
3761
- function sleep(ms) {
3762
- Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
3763
- }
3764
- function ageOf(dir) {
3765
- try {
3766
- return Date.now() - fs4.statSync(dir).mtimeMs;
3767
- } catch {
3768
- return 0;
3769
- }
3770
- }
3771
- function pidAlive(pid) {
3772
- if (!pid) return false;
3773
- try {
3774
- process.kill(pid, 0);
3775
- return true;
3776
- } catch (e) {
3777
- return e.code === "EPERM";
3778
- }
3779
- }
3780
- function ownerOf(dir) {
3781
- try {
3782
- const pid = Number(fs4.readFileSync(path5.join(dir, OWNER), "utf8").trim());
3783
- return Number.isInteger(pid) && pid > 0 ? pid : void 0;
3784
- } catch {
3785
- return void 0;
3786
- }
3787
- }
3788
- function claimBreak(dir) {
3789
- try {
3790
- fs4.mkdirSync(path5.join(dir, BREAKING));
3791
- return true;
3792
- } catch {
3793
- return false;
3794
- }
3795
- }
3796
- function removeClaim(dir, token3) {
3797
- if (token3) {
3798
- try {
3799
- fs4.unlinkSync(path5.join(dir, token3));
3800
- } catch (err) {
3801
- if (err.code !== "ENOENT") throw err;
3802
- }
3803
- }
3804
- try {
3805
- fs4.rmdirSync(dir);
3806
- return true;
3807
- } catch (err) {
3808
- const code = err.code;
3809
- if (code === "ENOENT") return true;
3810
- if (code === "ENOTEMPTY" || code === "EEXIST") return false;
3811
- throw err;
3812
- }
3813
- }
3814
- function tryLock(dir) {
3815
- fs4.mkdirSync(path5.dirname(dir), { recursive: true });
3816
- const token3 = `${process.pid}-${randomUUID()}`;
3817
- const candidate = fs4.mkdtempSync(`${dir}.`);
3818
- try {
3819
- fs4.writeFileSync(path5.join(candidate, token3), "");
3820
- for (; ; ) {
3821
- try {
3822
- fs4.renameSync(candidate, dir);
3823
- return () => {
3824
- removeClaim(dir, token3);
3825
- };
3826
- } catch (err) {
3827
- const code = err.code;
3828
- if (code !== "ENOTEMPTY" && code !== "EEXIST") throw err;
3829
- }
3830
- let entries;
3831
- try {
3832
- entries = fs4.readdirSync(dir);
3833
- } catch (err) {
3834
- if (err.code === "ENOENT") continue;
3835
- throw err;
3836
- }
3837
- if (entries.length) {
3838
- if (entries.length !== 1 || !/^\d+-[a-f0-9-]{36}$/.test(entries[0])) return void 0;
3839
- const owner = Number(entries[0].split("-")[0]);
3840
- if (pidAlive(owner)) return void 0;
3841
- }
3842
- if (!removeClaim(dir, entries[0])) return void 0;
3843
- }
3844
- } finally {
3845
- fs4.rmSync(candidate, { recursive: true, force: true });
3846
- }
3847
- }
3848
- function withLock(dir, what, fn) {
3849
- const until = Date.now() + WAIT_MS;
3850
- let held4;
3851
- for (; ; ) {
3852
- try {
3853
- fs4.mkdirSync(dir, { recursive: false });
3854
- try {
3855
- fs4.writeFileSync(path5.join(dir, OWNER), `${process.pid}
3856
- `);
3857
- } catch {
3858
- }
3859
- break;
3860
- } catch (err) {
3861
- const code = err.code;
3862
- if (code === "ENOENT") {
3863
- fs4.mkdirSync(path5.dirname(dir), { recursive: true });
3864
- continue;
3865
- }
3866
- if (code !== "EEXIST") throw err;
3867
- if (ageOf(dir) > STALE_MS) {
3868
- fs4.rmSync(dir, { recursive: true, force: true });
3869
- continue;
3870
- }
3871
- held4 = ownerOf(dir);
3872
- if (held4 !== void 0 && !pidAlive(held4) && claimBreak(dir)) {
3873
- fs4.rmSync(dir, { recursive: true, force: true });
3874
- continue;
3875
- }
3876
- if (Date.now() > until) {
3877
- die(
3878
- `another command is ${what}${held4 ? ` (process ${held4})` : ""} and did not finish in ${WAIT_MS / 1e3}s. If nothing else is running, remove ${rel(dir)}/ and try again.`,
3879
- "board-busy"
3880
- );
3881
- }
3882
- sleep(RETRY_MS);
3883
- }
3884
- }
3885
- try {
3886
- return fn();
3887
- } finally {
3888
- const owner = ownerOf(dir);
3889
- if (owner === void 0 || owner === process.pid) fs4.rmSync(dir, { recursive: true, force: true });
3890
- }
3891
- }
3892
- function withBoardLock(fn) {
3893
- if (!fs4.existsSync(KANBAN)) return fn();
3894
- if (depth > 0) return fn();
3895
- try {
3896
- ensureAkbDir();
3897
- } catch {
3898
- }
3899
- depth++;
3900
- try {
3901
- return withLock(LOCK, "writing this board", fn);
3902
- } finally {
3903
- depth--;
3904
- }
3905
- }
3906
- var WAIT_MS, STALE_MS, RETRY_MS, OWNER, BREAKING, depth;
3907
- var init_lock = __esm({
3908
- "../cli/src/lib/lock.ts"() {
3909
- "use strict";
3910
- init_paths();
3911
- WAIT_MS = 1e4;
3912
- STALE_MS = 6e4;
3913
- RETRY_MS = 25;
3914
- OWNER = "owner";
3915
- BREAKING = "breaking";
3916
- depth = 0;
3917
- }
3918
- });
3919
-
3920
- // ../cli/src/lib/agent/creation-lock.ts
3921
- import path6 from "node:path";
3922
- function withCreationLock(fn) {
3923
- if (depth2) return fn();
3924
- return withLock(path6.join(SESSIONS_DIR, "creation.lock"), "changing card creation", () => {
3925
- depth2++;
3926
- try {
3927
- return fn();
3928
- } finally {
3929
- depth2--;
3930
- }
3931
- });
3932
- }
3933
- var depth2;
3934
- var init_creation_lock = __esm({
3935
- "../cli/src/lib/agent/creation-lock.ts"() {
3936
- "use strict";
3937
- init_lock();
3938
- init_paths();
3939
- depth2 = 0;
3940
- }
3941
- });
3942
-
3943
3757
  // ../cli/src/lib/cloud/config.ts
3944
3758
  function cloudEndpoints() {
3945
3759
  return {
@@ -3965,12 +3779,12 @@ var init_config = __esm({
3965
3779
  });
3966
3780
 
3967
3781
  // ../cli/src/lib/cloud/session.ts
3968
- import fs5 from "node:fs";
3969
- import path7 from "node:path";
3782
+ import fs4 from "node:fs";
3783
+ import path5 from "node:path";
3970
3784
  function readSession() {
3971
3785
  let raw;
3972
3786
  try {
3973
- raw = fs5.readFileSync(sessionFile(), "utf8");
3787
+ raw = fs4.readFileSync(sessionFile(), "utf8");
3974
3788
  } catch {
3975
3789
  return null;
3976
3790
  }
@@ -3984,15 +3798,15 @@ function readSession() {
3984
3798
  }
3985
3799
  }
3986
3800
  function writeSession(session) {
3987
- fs5.mkdirSync(machineHome(), { recursive: true, mode: 448 });
3801
+ fs4.mkdirSync(machineHome(), { recursive: true, mode: 448 });
3988
3802
  const tmp = `${sessionFile()}.${process.pid}.tmp`;
3989
- fs5.writeFileSync(tmp, `${JSON.stringify(session, null, 2)}
3803
+ fs4.writeFileSync(tmp, `${JSON.stringify(session, null, 2)}
3990
3804
  `, { mode: 384 });
3991
- fs5.renameSync(tmp, sessionFile());
3805
+ fs4.renameSync(tmp, sessionFile());
3992
3806
  }
3993
3807
  function clearSession() {
3994
3808
  try {
3995
- fs5.rmSync(sessionFile());
3809
+ fs4.rmSync(sessionFile());
3996
3810
  } catch {
3997
3811
  }
3998
3812
  }
@@ -4060,42 +3874,42 @@ async function takeLock() {
4060
3874
  const until = Date.now() + LOCK_WAIT_MS;
4061
3875
  for (; ; ) {
4062
3876
  try {
4063
- fs5.mkdirSync(machineHome(), { recursive: true, mode: 448 });
4064
- fs5.mkdirSync(lockDir());
3877
+ fs4.mkdirSync(machineHome(), { recursive: true, mode: 448 });
3878
+ fs4.mkdirSync(lockDir());
4065
3879
  try {
4066
- fs5.writeFileSync(lockOwner(), `${process.pid}
3880
+ fs4.writeFileSync(lockOwner(), `${process.pid}
4067
3881
  `);
4068
3882
  } catch {
4069
3883
  }
4070
3884
  return () => {
4071
3885
  try {
4072
- fs5.rmSync(lockDir(), { recursive: true, force: true });
3886
+ fs4.rmSync(lockDir(), { recursive: true, force: true });
4073
3887
  } catch {
4074
3888
  }
4075
3889
  };
4076
3890
  } catch (err) {
4077
3891
  if (err.code !== "EEXIST") throw err;
4078
3892
  if (stale()) {
4079
- fs5.rmSync(lockDir(), { recursive: true, force: true });
3893
+ fs4.rmSync(lockDir(), { recursive: true, force: true });
4080
3894
  continue;
4081
3895
  }
4082
3896
  if (Date.now() > until) return () => {
4083
3897
  };
4084
- await sleep2(LOCK_RETRY_MS);
3898
+ await sleep(LOCK_RETRY_MS);
4085
3899
  }
4086
3900
  }
4087
3901
  }
4088
3902
  function stale() {
4089
3903
  let held4;
4090
3904
  try {
4091
- const pid = Number(fs5.readFileSync(lockOwner(), "utf8").trim());
3905
+ const pid = Number(fs4.readFileSync(lockOwner(), "utf8").trim());
4092
3906
  held4 = Number.isInteger(pid) && pid > 0 ? pid : void 0;
4093
3907
  } catch {
4094
3908
  held4 = void 0;
4095
3909
  }
4096
3910
  if (held4 !== void 0 && !alive(held4)) return true;
4097
3911
  try {
4098
- return Date.now() - fs5.statSync(lockDir()).mtimeMs > LOCK_STALE_MS;
3912
+ return Date.now() - fs4.statSync(lockDir()).mtimeMs > LOCK_STALE_MS;
4099
3913
  } catch {
4100
3914
  return false;
4101
3915
  }
@@ -4108,7 +3922,7 @@ function alive(pid) {
4108
3922
  return e.code === "EPERM";
4109
3923
  }
4110
3924
  }
4111
- var REFRESH_MARGIN_MS, LOCK_STALE_MS, LOCK_WAIT_MS, LOCK_RETRY_MS, sessionFile, lockDir, lockOwner, expiringSoon, sleep2;
3925
+ var REFRESH_MARGIN_MS, LOCK_STALE_MS, LOCK_WAIT_MS, LOCK_RETRY_MS, sessionFile, lockDir, lockOwner, expiringSoon, sleep;
4112
3926
  var init_session = __esm({
4113
3927
  "../cli/src/lib/cloud/session.ts"() {
4114
3928
  "use strict";
@@ -4118,11 +3932,234 @@ var init_session = __esm({
4118
3932
  LOCK_STALE_MS = 3e4;
4119
3933
  LOCK_WAIT_MS = 2e4;
4120
3934
  LOCK_RETRY_MS = 50;
4121
- sessionFile = () => path7.join(machineHome(), "session.json");
4122
- lockDir = () => path7.join(machineHome(), "session.lock");
4123
- lockOwner = () => path7.join(lockDir(), "owner");
3935
+ sessionFile = () => path5.join(machineHome(), "session.json");
3936
+ lockDir = () => path5.join(machineHome(), "session.lock");
3937
+ lockOwner = () => path5.join(lockDir(), "owner");
4124
3938
  expiringSoon = (session) => session.expiresAt - REFRESH_MARGIN_MS <= Date.now();
4125
- sleep2 = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
3939
+ sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
3940
+ }
3941
+ });
3942
+
3943
+ // ../cli/src/lib/cloud/execution.ts
3944
+ function executionOf(value = process.env.AI4KANBAN_EXECUTION) {
3945
+ try {
3946
+ const held4 = typeof value === "string" ? JSON.parse(value) : value;
3947
+ if (held4 && UUID.test(held4.job) && UUID.test(held4.machine)) return { job: held4.job, machine: held4.machine };
3948
+ } catch {
3949
+ }
3950
+ return void 0;
3951
+ }
3952
+ async function executionHeld(execution) {
3953
+ if (!execution) return true;
3954
+ try {
3955
+ const auth = await accessToken();
3956
+ if (!auth.ok) return false;
3957
+ const response = await fetch(`${cloudEndpoints().api}/v1/runner/jobs/${execution.job}/renew`, {
3958
+ method: "POST",
3959
+ headers: { authorization: `Bearer ${auth.token}`, "content-type": "application/json" },
3960
+ body: JSON.stringify({ machineId: execution.machine }),
3961
+ signal: AbortSignal.timeout(2e4)
3962
+ });
3963
+ if (!response.ok) return false;
3964
+ const held4 = await response.json();
3965
+ return held4.renewed === true && held4.stop !== true;
3966
+ } catch {
3967
+ return false;
3968
+ }
3969
+ }
3970
+ var UUID;
3971
+ var init_execution = __esm({
3972
+ "../cli/src/lib/cloud/execution.ts"() {
3973
+ "use strict";
3974
+ init_config();
3975
+ init_session();
3976
+ UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
3977
+ }
3978
+ });
3979
+
3980
+ // ../cli/src/lib/lock.ts
3981
+ import { randomUUID } from "node:crypto";
3982
+ import fs5 from "node:fs";
3983
+ import path6 from "node:path";
3984
+ function sleep2(ms) {
3985
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
3986
+ }
3987
+ function ageOf(dir) {
3988
+ try {
3989
+ return Date.now() - fs5.statSync(dir).mtimeMs;
3990
+ } catch {
3991
+ return 0;
3992
+ }
3993
+ }
3994
+ function pidAlive(pid) {
3995
+ if (!pid) return false;
3996
+ try {
3997
+ process.kill(pid, 0);
3998
+ return true;
3999
+ } catch (e) {
4000
+ return e.code === "EPERM";
4001
+ }
4002
+ }
4003
+ function ownerOf(dir) {
4004
+ try {
4005
+ const pid = Number(fs5.readFileSync(path6.join(dir, OWNER), "utf8").trim());
4006
+ return Number.isInteger(pid) && pid > 0 ? pid : void 0;
4007
+ } catch {
4008
+ return void 0;
4009
+ }
4010
+ }
4011
+ function claimBreak(dir) {
4012
+ try {
4013
+ fs5.mkdirSync(path6.join(dir, BREAKING));
4014
+ return true;
4015
+ } catch {
4016
+ return false;
4017
+ }
4018
+ }
4019
+ function removeClaim(dir, token3) {
4020
+ if (token3) {
4021
+ try {
4022
+ fs5.unlinkSync(path6.join(dir, token3));
4023
+ } catch (err) {
4024
+ if (err.code !== "ENOENT") throw err;
4025
+ }
4026
+ }
4027
+ try {
4028
+ fs5.rmdirSync(dir);
4029
+ return true;
4030
+ } catch (err) {
4031
+ const code = err.code;
4032
+ if (code === "ENOENT") return true;
4033
+ if (code === "ENOTEMPTY" || code === "EEXIST") return false;
4034
+ throw err;
4035
+ }
4036
+ }
4037
+ function tryLock(dir) {
4038
+ fs5.mkdirSync(path6.dirname(dir), { recursive: true });
4039
+ const token3 = `${process.pid}-${randomUUID()}`;
4040
+ const candidate = fs5.mkdtempSync(`${dir}.`);
4041
+ try {
4042
+ fs5.writeFileSync(path6.join(candidate, token3), "");
4043
+ for (; ; ) {
4044
+ try {
4045
+ fs5.renameSync(candidate, dir);
4046
+ return () => {
4047
+ removeClaim(dir, token3);
4048
+ };
4049
+ } catch (err) {
4050
+ const code = err.code;
4051
+ if (code !== "ENOTEMPTY" && code !== "EEXIST") throw err;
4052
+ }
4053
+ let entries;
4054
+ try {
4055
+ entries = fs5.readdirSync(dir);
4056
+ } catch (err) {
4057
+ if (err.code === "ENOENT") continue;
4058
+ throw err;
4059
+ }
4060
+ if (entries.length) {
4061
+ if (entries.length !== 1 || !/^\d+-[a-f0-9-]{36}$/.test(entries[0])) return void 0;
4062
+ const owner = Number(entries[0].split("-")[0]);
4063
+ if (pidAlive(owner)) return void 0;
4064
+ }
4065
+ if (!removeClaim(dir, entries[0])) return void 0;
4066
+ }
4067
+ } finally {
4068
+ fs5.rmSync(candidate, { recursive: true, force: true });
4069
+ }
4070
+ }
4071
+ function withLock(dir, what, fn) {
4072
+ const until = Date.now() + WAIT_MS;
4073
+ let held4;
4074
+ for (; ; ) {
4075
+ try {
4076
+ fs5.mkdirSync(dir, { recursive: false });
4077
+ try {
4078
+ fs5.writeFileSync(path6.join(dir, OWNER), `${process.pid}
4079
+ `);
4080
+ } catch {
4081
+ }
4082
+ break;
4083
+ } catch (err) {
4084
+ const code = err.code;
4085
+ if (code === "ENOENT") {
4086
+ fs5.mkdirSync(path6.dirname(dir), { recursive: true });
4087
+ continue;
4088
+ }
4089
+ if (code !== "EEXIST") throw err;
4090
+ if (ageOf(dir) > STALE_MS) {
4091
+ fs5.rmSync(dir, { recursive: true, force: true });
4092
+ continue;
4093
+ }
4094
+ held4 = ownerOf(dir);
4095
+ if (held4 !== void 0 && !pidAlive(held4) && claimBreak(dir)) {
4096
+ fs5.rmSync(dir, { recursive: true, force: true });
4097
+ continue;
4098
+ }
4099
+ if (Date.now() > until) {
4100
+ die(
4101
+ `another command is ${what}${held4 ? ` (process ${held4})` : ""} and did not finish in ${WAIT_MS / 1e3}s. If nothing else is running, remove ${rel(dir)}/ and try again.`,
4102
+ "board-busy"
4103
+ );
4104
+ }
4105
+ sleep2(RETRY_MS);
4106
+ }
4107
+ }
4108
+ try {
4109
+ return fn();
4110
+ } finally {
4111
+ const owner = ownerOf(dir);
4112
+ if (owner === void 0 || owner === process.pid) fs5.rmSync(dir, { recursive: true, force: true });
4113
+ }
4114
+ }
4115
+ function withBoardLock(fn) {
4116
+ if (!fs5.existsSync(KANBAN)) return fn();
4117
+ if (depth > 0) return fn();
4118
+ try {
4119
+ ensureAkbDir();
4120
+ } catch {
4121
+ }
4122
+ depth++;
4123
+ try {
4124
+ return withLock(LOCK, "writing this board", fn);
4125
+ } finally {
4126
+ depth--;
4127
+ }
4128
+ }
4129
+ var WAIT_MS, STALE_MS, RETRY_MS, OWNER, BREAKING, depth;
4130
+ var init_lock = __esm({
4131
+ "../cli/src/lib/lock.ts"() {
4132
+ "use strict";
4133
+ init_paths();
4134
+ WAIT_MS = 1e4;
4135
+ STALE_MS = 6e4;
4136
+ RETRY_MS = 25;
4137
+ OWNER = "owner";
4138
+ BREAKING = "breaking";
4139
+ depth = 0;
4140
+ }
4141
+ });
4142
+
4143
+ // ../cli/src/lib/agent/creation-lock.ts
4144
+ import path7 from "node:path";
4145
+ function withCreationLock(fn) {
4146
+ if (depth2) return fn();
4147
+ return withLock(path7.join(SESSIONS_DIR, "creation.lock"), "changing card creation", () => {
4148
+ depth2++;
4149
+ try {
4150
+ return fn();
4151
+ } finally {
4152
+ depth2--;
4153
+ }
4154
+ });
4155
+ }
4156
+ var depth2;
4157
+ var init_creation_lock = __esm({
4158
+ "../cli/src/lib/agent/creation-lock.ts"() {
4159
+ "use strict";
4160
+ init_lock();
4161
+ init_paths();
4162
+ depth2 = 0;
4126
4163
  }
4127
4164
  });
4128
4165
 
@@ -5480,7 +5517,7 @@ var init_package = __esm({
5480
5517
  "../cli/package.json"() {
5481
5518
  package_default = {
5482
5519
  name: "wildcrane",
5483
- version: "0.11.16",
5520
+ version: "0.11.17",
5484
5521
  akbContract: 5,
5485
5522
  description: "WildCrane's `akb` command \u2014 scaffold the Markdown board under docs/kanban/, put an agent on a card, and carry the flows the agent works by.",
5486
5523
  keywords: [
@@ -8321,10 +8358,10 @@ function findRollout(threadId) {
8321
8358
  }
8322
8359
  for (const name of names4) {
8323
8360
  if (!name.startsWith("rollout-") || !name.endsWith(tail2)) continue;
8324
- const path86 = join(dir, name);
8361
+ const path88 = join(dir, name);
8325
8362
  try {
8326
- const at = statSync(path86).mtimeMs;
8327
- if (!newest2 || at > newest2.at) newest2 = { path: path86, at };
8363
+ const at = statSync(path88).mtimeMs;
8364
+ if (!newest2 || at > newest2.at) newest2 = { path: path88, at };
8328
8365
  } catch {
8329
8366
  }
8330
8367
  }
@@ -8332,11 +8369,11 @@ function findRollout(threadId) {
8332
8369
  return newest2?.path;
8333
8370
  }
8334
8371
  function readCodexRunFacts(threadId) {
8335
- const path86 = findRollout(threadId);
8336
- if (!path86) return void 0;
8372
+ const path88 = findRollout(threadId);
8373
+ if (!path88) return void 0;
8337
8374
  let raw;
8338
8375
  try {
8339
- raw = readFileSync(path86, "utf8");
8376
+ raw = readFileSync(path88, "utf8");
8340
8377
  } catch {
8341
8378
  return void 0;
8342
8379
  }
@@ -8464,8 +8501,8 @@ function renderCompleted(item) {
8464
8501
  const changes = Array.isArray(item.changes) ? item.changes : [];
8465
8502
  return changes.map((raw) => {
8466
8503
  const change2 = obj(raw);
8467
- const path86 = str2(change2.path);
8468
- return path86 ? `\u23FA ${str2(change2.kind) || "change"}(${path86})
8504
+ const path88 = str2(change2.path);
8505
+ return path88 ? `\u23FA ${str2(change2.kind) || "change"}(${path88})
8469
8506
  ` : "";
8470
8507
  }).join("");
8471
8508
  }
@@ -8727,10 +8764,10 @@ function readKimiRunFacts(sessionId) {
8727
8764
  const total = { input: 0, cacheCreation: 0, cacheRead: 0, output: 0 };
8728
8765
  let model;
8729
8766
  let holding = 0;
8730
- for (const { path: path86, main } of wireFiles(entry.sessionDir)) {
8767
+ for (const { path: path88, main } of wireFiles(entry.sessionDir)) {
8731
8768
  let raw;
8732
8769
  try {
8733
- raw = readFileSync2(path86, "utf8");
8770
+ raw = readFileSync2(path88, "utf8");
8734
8771
  } catch {
8735
8772
  continue;
8736
8773
  }
@@ -9011,8 +9048,8 @@ var init_tail = __esm({
9011
9048
  import fs24 from "node:fs";
9012
9049
  function whereHint(update2, title) {
9013
9050
  const locations = Array.isArray(update2.locations) ? update2.locations : [];
9014
- const path86 = str2(obj(locations[0]).path);
9015
- return path86 && !title.includes(path86) ? hint(path86) : "";
9051
+ const path88 = str2(obj(locations[0]).path);
9052
+ return path88 && !title.includes(path88) ? hint(path88) : "";
9016
9053
  }
9017
9054
  function saidBy(update2) {
9018
9055
  const content = Array.isArray(update2.content) ? update2.content : [];
@@ -9998,27 +10035,40 @@ import crypto3 from "node:crypto";
9998
10035
  import fs25 from "node:fs";
9999
10036
  import os4 from "node:os";
10000
10037
  import path28 from "node:path";
10038
+ function spawnGit(args, options) {
10039
+ const out = spawnSync3("git", args, {
10040
+ ...options,
10041
+ encoding: "utf8",
10042
+ stdio: ["ignore", "pipe", "pipe"],
10043
+ timeout: gitLimits.timeoutMs,
10044
+ killSignal: "SIGKILL"
10045
+ });
10046
+ if (out.error?.code === "ETIMEDOUT") throw new GitTimeoutError(`Git ${args[0]} timed out after ${gitLimits.timeoutMs / 1e3}s; the command was killed.`);
10047
+ return out;
10048
+ }
10001
10049
  function git(args, cwd = REPO_ROOT) {
10002
10050
  try {
10003
- const out = spawnSync3("git", args, { cwd, encoding: "utf8", maxBuffer: MAX_OUTPUT, windowsHide: true });
10051
+ const out = spawnGit(args, { cwd, encoding: "utf8", maxBuffer: MAX_OUTPUT, windowsHide: true });
10004
10052
  if (out.error || out.status !== 0) return null;
10005
10053
  return out.stdout;
10006
- } catch {
10054
+ } catch (error) {
10055
+ if (error instanceof GitTimeoutError) throw error;
10007
10056
  return null;
10008
10057
  }
10009
10058
  }
10010
10059
  function gitDiff(args, cwd = REPO_ROOT) {
10011
10060
  try {
10012
- const out = spawnSync3("git", args, { cwd, encoding: "utf8", maxBuffer: MAX_OUTPUT, windowsHide: true });
10061
+ const out = spawnGit(args, { cwd, encoding: "utf8", maxBuffer: MAX_OUTPUT, windowsHide: true });
10013
10062
  if (out.error || out.status !== 0 && out.status !== 1) return null;
10014
10063
  return out.stdout;
10015
- } catch {
10064
+ } catch (error) {
10065
+ if (error instanceof GitTimeoutError) throw error;
10016
10066
  return null;
10017
10067
  }
10018
10068
  }
10019
10069
  function tryGit(args, cwd = REPO_ROOT, env2) {
10020
10070
  try {
10021
- const out = spawnSync3("git", args, {
10071
+ const out = spawnGit(args, {
10022
10072
  cwd,
10023
10073
  encoding: "utf8",
10024
10074
  maxBuffer: MAX_OUTPUT,
@@ -10029,6 +10079,7 @@ function tryGit(args, cwd = REPO_ROOT, env2) {
10029
10079
  if (out.status !== 0) return { ok: false, why: (out.stderr || out.stdout || "").trim() };
10030
10080
  return { ok: true };
10031
10081
  } catch (e) {
10082
+ if (e instanceof GitTimeoutError) throw e;
10032
10083
  return { ok: false, why: e instanceof Error ? e.message : String(e) };
10033
10084
  }
10034
10085
  }
@@ -10037,7 +10088,7 @@ function boardManagedPaths() {
10037
10088
  }
10038
10089
  function outsideBoardForAdd(cwd = REPO_ROOT) {
10039
10090
  const kept2 = boardManagedPaths().filter(
10040
- (p) => spawnSync3("git", ["check-ignore", "-q", p], { cwd, windowsHide: true }).status !== 0
10091
+ (p) => spawnGit(["check-ignore", "-q", p], { cwd, windowsHide: true }).status !== 0
10041
10092
  );
10042
10093
  return ["--", ".", ...kept2.map((p) => `:(exclude)${p}`)];
10043
10094
  }
@@ -10157,10 +10208,10 @@ function treeMark(base, cwd = REPO_ROOT) {
10157
10208
  const index = path28.join(ensureAkbDir(), `index-${process.pid}-${Date.now()}`);
10158
10209
  try {
10159
10210
  const env2 = { ...process.env, GIT_INDEX_FILE: index };
10160
- const run2 = (args) => spawnSync3("git", args, { cwd, encoding: "utf8", maxBuffer: MAX_OUTPUT, windowsHide: true, env: env2 }).status === 0;
10211
+ const run2 = (args) => spawnGit(args, { cwd, encoding: "utf8", maxBuffer: MAX_OUTPUT, windowsHide: true, env: env2 }).status === 0;
10161
10212
  if (!run2(["read-tree", base])) return null;
10162
10213
  if (!run2(["add", "-A", ...outsideBoardForAdd(cwd)])) return null;
10163
- const tree = spawnSync3("git", ["write-tree"], {
10214
+ const tree = spawnGit(["write-tree"], {
10164
10215
  cwd,
10165
10216
  encoding: "utf8",
10166
10217
  maxBuffer: MAX_OUTPUT,
@@ -10168,7 +10219,8 @@ function treeMark(base, cwd = REPO_ROOT) {
10168
10219
  env: env2
10169
10220
  });
10170
10221
  return tree.status === 0 ? tree.stdout.trim() || null : null;
10171
- } catch {
10222
+ } catch (error) {
10223
+ if (error instanceof GitTimeoutError) throw error;
10172
10224
  return null;
10173
10225
  } finally {
10174
10226
  try {
@@ -10179,23 +10231,26 @@ function treeMark(base, cwd = REPO_ROOT) {
10179
10231
  }
10180
10232
  function reverseApplies(patch2, ref, cwd = REPO_ROOT) {
10181
10233
  if (!patch2.trim()) return false;
10182
- const index = path28.join(os4.tmpdir(), `akb-apply-${process.pid}-${Date.now()}.index`);
10234
+ const folder = fs25.mkdtempSync(path28.join(os4.tmpdir(), `akb-apply-${process.pid}-`));
10235
+ const index = path28.join(folder, "index");
10236
+ const patchFile = path28.join(folder, "patch");
10183
10237
  const env2 = { GIT_INDEX_FILE: index };
10184
10238
  try {
10239
+ fs25.writeFileSync(patchFile, patch2);
10185
10240
  if (!tryGit(["read-tree", ref], cwd, env2).ok) return false;
10186
- const out = spawnSync3("git", ["apply", "--cached", "--reverse", "--check", "-"], {
10241
+ const out = spawnGit(["apply", "--cached", "--reverse", "--check", patchFile], {
10187
10242
  cwd,
10188
- input: patch2,
10189
10243
  encoding: "utf8",
10190
10244
  maxBuffer: MAX_OUTPUT,
10191
10245
  windowsHide: true,
10192
10246
  env: { ...process.env, ...env2 }
10193
10247
  });
10194
10248
  return !out.error && out.status === 0;
10195
- } catch {
10249
+ } catch (error) {
10250
+ if (error instanceof GitTimeoutError) throw error;
10196
10251
  return false;
10197
10252
  } finally {
10198
- fs25.rmSync(index, { force: true });
10253
+ fs25.rmSync(folder, { recursive: true, force: true });
10199
10254
  }
10200
10255
  }
10201
10256
  function squashOnto(dir, base, message) {
@@ -10277,12 +10332,15 @@ function moveBranchRef(branch, commit, expect) {
10277
10332
  if (branchTip(branch) !== expect) return { moved: true };
10278
10333
  return { ok: false, error: `couldn't move ${branch} to ${commit.slice(0, 12)}: ${done2.why ?? "git refused"}` };
10279
10334
  }
10280
- var MAX_OUTPUT, scripted, posix, outsideBoard, deliveryBranch, deliveryWorktree, worktreeDir, inGitRepo, headCommit, worktreeExists, branchExists, pruneWorktreeMetadata, branchTip, isAncestor, changedPaths, branchPatch, lastCommitTouching, continueRebase, abortRebase, conflictedPaths, headingKind;
10335
+ var MAX_OUTPUT, gitLimits, GitTimeoutError, scripted, posix, outsideBoard, deliveryBranch, deliveryWorktree, worktreeDir, inGitRepo, headCommit, worktreeExists, branchExists, pruneWorktreeMetadata, branchTip, isAncestor, changedPaths, branchPatch, lastCommitTouching, continueRebase, abortRebase, conflictedPaths, headingKind;
10281
10336
  var init_worktree = __esm({
10282
10337
  "../cli/src/lib/agent/worktree.ts"() {
10283
10338
  "use strict";
10284
10339
  init_paths();
10285
10340
  MAX_OUTPUT = 64 * 1024 * 1024;
10341
+ gitLimits = { timeoutMs: 3e4 };
10342
+ GitTimeoutError = class extends Error {
10343
+ };
10286
10344
  scripted = (args, cwd) => tryGit(["-c", "core.hooksPath=/dev/null", ...args], cwd, { GIT_EDITOR: "true", GIT_SEQUENCE_EDITOR: "true" });
10287
10345
  posix = (p) => p.split(path28.sep).join("/");
10288
10346
  outsideBoard = () => [
@@ -10299,7 +10357,7 @@ var init_worktree = __esm({
10299
10357
  branchExists = (branch) => !!branch && git(["rev-parse", "--verify", "--quiet", `refs/heads/${branch}`]) !== null;
10300
10358
  pruneWorktreeMetadata = () => void git(["worktree", "prune"]);
10301
10359
  branchTip = (branch) => git(["rev-parse", "--verify", "--quiet", `refs/heads/${branch}`])?.trim() || null;
10302
- isAncestor = (older, newer, cwd = REPO_ROOT) => spawnSync3("git", ["merge-base", "--is-ancestor", older, newer], { cwd, windowsHide: true }).status === 0;
10360
+ isAncestor = (older, newer, cwd = REPO_ROOT) => spawnGit(["merge-base", "--is-ancestor", older, newer], { cwd, windowsHide: true }).status === 0;
10303
10361
  changedPaths = (base, branch, cwd = REPO_ROOT) => {
10304
10362
  const out = git(["diff", "--name-only", base, branch, ...outsideBoard()], cwd);
10305
10363
  return out === null ? null : out.split("\n").filter(Boolean);
@@ -11647,50 +11705,13 @@ function withSystemLanguage(harness, argv, env2, language) {
11647
11705
  }
11648
11706
  var init_run_settings = __esm({
11649
11707
  "../cli/src/lib/agent/run-settings.ts"() {
11650
- "use strict";
11651
- init_home();
11652
- init_argv();
11653
- init_env();
11654
- init_language();
11655
- init_types2();
11656
- init_providers();
11657
- }
11658
- });
11659
-
11660
- // ../cli/src/lib/cloud/execution.ts
11661
- function executionOf(value = process.env.AI4KANBAN_EXECUTION) {
11662
- try {
11663
- const held4 = typeof value === "string" ? JSON.parse(value) : value;
11664
- if (held4 && UUID.test(held4.job) && UUID.test(held4.machine)) return { job: held4.job, machine: held4.machine };
11665
- } catch {
11666
- }
11667
- return void 0;
11668
- }
11669
- async function executionHeld(execution) {
11670
- if (!execution) return true;
11671
- try {
11672
- const auth = await accessToken();
11673
- if (!auth.ok) return false;
11674
- const response = await fetch(`${cloudEndpoints().api}/v1/runner/jobs/${execution.job}/renew`, {
11675
- method: "POST",
11676
- headers: { authorization: `Bearer ${auth.token}`, "content-type": "application/json" },
11677
- body: JSON.stringify({ machineId: execution.machine }),
11678
- signal: AbortSignal.timeout(2e4)
11679
- });
11680
- if (!response.ok) return false;
11681
- const held4 = await response.json();
11682
- return held4.renewed === true && held4.stop !== true;
11683
- } catch {
11684
- return false;
11685
- }
11686
- }
11687
- var UUID;
11688
- var init_execution = __esm({
11689
- "../cli/src/lib/cloud/execution.ts"() {
11690
- "use strict";
11691
- init_config();
11692
- init_session();
11693
- UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
11708
+ "use strict";
11709
+ init_home();
11710
+ init_argv();
11711
+ init_env();
11712
+ init_language();
11713
+ init_types2();
11714
+ init_providers();
11694
11715
  }
11695
11716
  });
11696
11717
 
@@ -15638,6 +15659,252 @@ var init_complete = __esm({
15638
15659
  }
15639
15660
  });
15640
15661
 
15662
+ // ../cli/src/lib/agent/stop.ts
15663
+ import { spawnSync as spawnSync4 } from "node:child_process";
15664
+ import { randomUUID as randomUUID5 } from "node:crypto";
15665
+ import fs39 from "node:fs";
15666
+ import os6 from "node:os";
15667
+ import path44 from "node:path";
15668
+ function markedInProc(entry, strict = false) {
15669
+ const pids = [];
15670
+ for (const name of fs39.readdirSync("/proc")) {
15671
+ if (!/^\d+$/.test(name)) continue;
15672
+ try {
15673
+ if (fs39.readFileSync(`/proc/${name}/environ`, "utf8").split("\0").includes(entry)) pids.push(Number(name));
15674
+ } catch (error) {
15675
+ if (strict && !["ENOENT", "ESRCH"].includes(error.code ?? "")) {
15676
+ const stat = fs39.statSync(`/proc/${name}`, { throwIfNoEntry: false });
15677
+ if (stat?.uid === process.getuid?.()) throw error;
15678
+ }
15679
+ }
15680
+ }
15681
+ return pids;
15682
+ }
15683
+ function markedInPs(entry, roots) {
15684
+ const ps = spawnSync4("ps", ["-A", "-E", "-ww", "-o", "pid=,ppid=,command="], {
15685
+ encoding: "utf8",
15686
+ maxBuffer: 64 * 1024 * 1024,
15687
+ timeout: 5e3
15688
+ });
15689
+ if (ps.status !== 0 || !ps.stdout) throw new Error("\u65E0\u6CD5\u67E5\u8BE2\u8FDB\u7A0B\u6811\u3002");
15690
+ const children = /* @__PURE__ */ new Map();
15691
+ const listed2 = /* @__PURE__ */ new Set();
15692
+ const found2 = /* @__PURE__ */ new Set();
15693
+ for (const line of ps.stdout.split("\n")) {
15694
+ const m = /^\s*(\d+)\s+(\d+)\s(.*)$/.exec(line);
15695
+ if (!m) continue;
15696
+ const pid = Number(m[1]);
15697
+ const ppid = Number(m[2]);
15698
+ listed2.add(pid);
15699
+ children.set(ppid, [...children.get(ppid) ?? [], pid]);
15700
+ if (`${m[3]} `.includes(` ${entry} `)) found2.add(pid);
15701
+ }
15702
+ const queue = [...found2, ...roots.filter((pid) => listed2.has(pid))];
15703
+ for (const pid of queue) {
15704
+ found2.add(pid);
15705
+ for (const child of children.get(pid) ?? []) if (!found2.has(child)) queue.push(child);
15706
+ }
15707
+ return [...found2];
15708
+ }
15709
+ function marked(mark, roots = []) {
15710
+ if (WINDOWS3 || !mark.value) return [];
15711
+ const entry = `${mark.name}=${mark.value}`;
15712
+ try {
15713
+ const pids = process.platform === "linux" ? markedInProc(entry) : markedInPs(entry, roots);
15714
+ return pids.filter((pid) => pid !== process.pid);
15715
+ } catch {
15716
+ return [];
15717
+ }
15718
+ }
15719
+ function signal(pids, sig) {
15720
+ for (const pid of pids) {
15721
+ try {
15722
+ process.kill(pid, sig);
15723
+ } catch {
15724
+ }
15725
+ }
15726
+ }
15727
+ function stopWatchers(sessions, onlyPid, stillOwns = () => true, startedBefore) {
15728
+ if (sessions ? !sessions.length : onlyPid === void 0) return [];
15729
+ const command = WINDOWS3 ? "powershell.exe" : "ps";
15730
+ const args = WINDOWS3 ? ["-NoProfile", "-NonInteractive", "-Command", "Get-CimInstance Win32_Process | Select-Object ProcessId,CommandLine,@{Name='StartedAt';Expression={([DateTimeOffset]$_.CreationDate).ToUnixTimeMilliseconds()}} | ConvertTo-Json -Compress"] : ["-A", "-ww", "-o", "pid=,lstart=,command="];
15731
+ const out = spawnSync4(command, args, { encoding: "utf8", timeout: 5e3, maxBuffer: 16 * 1024 * 1024, windowsHide: true, env: { ...process.env, LC_ALL: "C" } });
15732
+ if (out.status !== 0) return [];
15733
+ let procs;
15734
+ try {
15735
+ procs = WINDOWS3 ? [].concat(JSON.parse(out.stdout)).map((p) => ({ pid: p.ProcessId, command: p.CommandLine ?? "", startedAt: p.StartedAt })) : out.stdout.split("\n").flatMap((line) => {
15736
+ const m = /^\s*(\d+)\s+(\S+\s+\S+\s+\d+\s+\d+:\d+:\d+\s+\d{4})\s+(.*)$/.exec(line);
15737
+ return m ? [{ pid: Number(m[1]), command: m[3], startedAt: Date.parse(m[2]) }] : [];
15738
+ });
15739
+ } catch {
15740
+ return [];
15741
+ }
15742
+ const known2 = sessions && new Set(sessions);
15743
+ const stopped2 = [];
15744
+ for (const proc of procs) {
15745
+ if (proc.pid === process.pid || onlyPid !== void 0 && proc.pid !== onlyPid) continue;
15746
+ if (startedBefore !== void 0 && (!Number.isFinite(proc.startedAt) || proc.startedAt > startedBefore)) continue;
15747
+ const match = /^"?[^\n]*?\bnode(?:\.exe)?"?\s+(?:"[^\n"]+\.mjs"|\S+\.mjs)\s+__watch\s+(\S+)(?:\s|$)/.exec(proc.command);
15748
+ const args2 = `${proc.command} `;
15749
+ const names4 = (flag, value) => args2.includes(` ${flag} ${value} `) || args2.includes(` ${flag} "${value}" `);
15750
+ const board2 = names4("--board", KANBAN) || KANBAN === path44.join(REPO_ROOT, "docs", "kanban") && names4("--dir", REPO_ROOT);
15751
+ if (!match || known2 && !known2.has(match[1]) || !board2 || !stillOwns()) continue;
15752
+ if (WINDOWS3) {
15753
+ if (killTreeOnWindows(proc.pid)) stopped2.push(proc.pid);
15754
+ } else {
15755
+ try {
15756
+ process.kill(-proc.pid, "SIGKILL");
15757
+ stopped2.push(proc.pid);
15758
+ } catch {
15759
+ }
15760
+ }
15761
+ }
15762
+ return stopped2;
15763
+ }
15764
+ function startedUnder(list, root, since) {
15765
+ const self = list.procs.find((p) => p.pid === root);
15766
+ const from = since ?? self?.startedAt;
15767
+ if (from === void 0 || list.bootedAt > from) return [];
15768
+ if (self && self.startedAt > from) return [];
15769
+ const found2 = self ? [root] : [];
15770
+ const seen = /* @__PURE__ */ new Set([root]);
15771
+ const queue = [{ pid: root, startedAt: from }];
15772
+ for (const parent of queue) {
15773
+ for (const p of list.procs) {
15774
+ if (p.ppid !== parent.pid || seen.has(p.pid) || p.startedAt < parent.startedAt) continue;
15775
+ seen.add(p.pid);
15776
+ found2.push(p.pid);
15777
+ queue.push(p);
15778
+ }
15779
+ }
15780
+ return found2.reverse();
15781
+ }
15782
+ function readProcList(out) {
15783
+ const procs = [];
15784
+ let bootedAt;
15785
+ for (const line of out.split(/\r?\n/)) {
15786
+ const boot = /^boot (\d+)$/.exec(line.trim());
15787
+ if (boot) bootedAt = Number(boot[1]);
15788
+ const m = /^(\d+) (\d+) (\d+)$/.exec(line.trim());
15789
+ if (m) procs.push({ pid: Number(m[1]), ppid: Number(m[2]), startedAt: Number(m[3]) });
15790
+ }
15791
+ return bootedAt !== void 0 && procs.length ? { procs, bootedAt } : void 0;
15792
+ }
15793
+ function procsOnWindows() {
15794
+ const ps = spawnSync4("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", LIST_PROCS], {
15795
+ encoding: "utf8",
15796
+ maxBuffer: 64 * 1024 * 1024,
15797
+ timeout: 5e3,
15798
+ windowsHide: true
15799
+ });
15800
+ return ps.status === 0 && ps.stdout ? readProcList(ps.stdout) : void 0;
15801
+ }
15802
+ function killUnderOnWindows(pid, since) {
15803
+ if (!WINDOWS3 || !pid) return;
15804
+ const list = procsOnWindows();
15805
+ if (!list) return;
15806
+ const pids = startedUnder(list, pid, since).filter((p) => p !== process.pid);
15807
+ if (!pids.length) return;
15808
+ spawnSync4("taskkill", ["/F", ...pids.flatMap((p) => ["/PID", String(p)])], { stdio: "ignore", windowsHide: true, timeout: 1e4 });
15809
+ }
15810
+ function killMarked(mark, agent) {
15811
+ if (WINDOWS3) {
15812
+ if (agent) killUnderOnWindows(agent.pid, agent.since);
15813
+ return;
15814
+ }
15815
+ signal(marked(mark), "SIGKILL");
15816
+ }
15817
+ function killTreeOnWindows(pid) {
15818
+ if (!WINDOWS3 || !pid) return false;
15819
+ const out = spawnSync4("taskkill", ["/PID", String(pid), "/T", "/F"], { stdio: "ignore", windowsHide: true, timeout: 1e4 });
15820
+ return out.status === 0;
15821
+ }
15822
+ function later(ms, fn) {
15823
+ const t = setTimeout(fn, ms);
15824
+ if (typeof t.unref === "function") t.unref();
15825
+ }
15826
+ function endAgent(child, mark, graceMs) {
15827
+ const term = (sig) => {
15828
+ try {
15829
+ child.kill(sig);
15830
+ } catch {
15831
+ }
15832
+ };
15833
+ if (killTreeOnWindows(child.pid)) return;
15834
+ killUnderOnWindows(child.pid);
15835
+ const seen = new Set(marked(mark, child.pid ? [child.pid] : []));
15836
+ term("SIGTERM");
15837
+ signal([...seen].filter((pid) => pid !== child.pid), "SIGTERM");
15838
+ const round = (left) => {
15839
+ const pids = marked(mark, [...seen]);
15840
+ signal(pids, "SIGKILL");
15841
+ for (const pid of pids) seen.add(pid);
15842
+ if (pids.length && left > 1) later(KILL_ROUND_MS, () => round(left - 1));
15843
+ };
15844
+ later(graceMs, () => {
15845
+ term("SIGKILL");
15846
+ round(KILL_ROUNDS);
15847
+ });
15848
+ }
15849
+ function killRunning() {
15850
+ for (const [child, mark] of running) {
15851
+ if (killTreeOnWindows(child.pid)) continue;
15852
+ killUnderOnWindows(child.pid);
15853
+ signal(marked(mark, child.pid ? [child.pid] : []), "SIGKILL");
15854
+ try {
15855
+ child.kill("SIGKILL");
15856
+ } catch {
15857
+ }
15858
+ }
15859
+ running.clear();
15860
+ }
15861
+ function unlisten() {
15862
+ for (const [sig, fn] of listening) process.removeListener(sig, fn);
15863
+ listening.clear();
15864
+ }
15865
+ function onQuit(sig) {
15866
+ killRunning();
15867
+ unlisten();
15868
+ if (process.listenerCount(sig) === 0) process.exit(128 + os6.constants.signals[sig]);
15869
+ }
15870
+ function trackAgent(child, mark) {
15871
+ if (!child.pid) return;
15872
+ running.set(child, mark);
15873
+ child.once("close", () => {
15874
+ running.delete(child);
15875
+ if (!running.size) unlisten();
15876
+ });
15877
+ for (const sig of QUIT_SIGNALS) {
15878
+ if (listening.has(sig) || sig === "SIGINT" && interruptIsTheCallers) continue;
15879
+ const fn = () => onQuit(sig);
15880
+ listening.set(sig, fn);
15881
+ process.on(sig, fn);
15882
+ }
15883
+ }
15884
+ var runMark, stopMark, markEnv, WINDOWS3, KILL_ROUND_MS, KILL_ROUNDS, LIST_PROCS, running, QUIT_SIGNALS, listening, interruptIsTheCallers;
15885
+ var init_stop = __esm({
15886
+ "../cli/src/lib/agent/stop.ts"() {
15887
+ "use strict";
15888
+ init_env();
15889
+ init_paths();
15890
+ runMark = (sessionId) => ({ name: RUN_ENV, value: sessionId });
15891
+ stopMark = () => ({ name: STOP_ENV, value: randomUUID5() });
15892
+ markEnv = (env2, mark) => ({ ...env2, [mark.name]: mark.value });
15893
+ WINDOWS3 = process.platform === "win32";
15894
+ KILL_ROUND_MS = 300;
15895
+ KILL_ROUNDS = 3;
15896
+ LIST_PROCS = [
15897
+ "$ErrorActionPreference = 'Stop'",
15898
+ "Get-CimInstance Win32_Process | ForEach-Object { if ($_.CreationDate) { '{0} {1} {2}' -f $_.ProcessId, $_.ParentProcessId, ([DateTimeOffset]$_.CreationDate).ToUnixTimeMilliseconds() } }",
15899
+ "'boot {0}' -f ([DateTimeOffset](Get-CimInstance Win32_OperatingSystem).LastBootUpTime).ToUnixTimeMilliseconds()"
15900
+ ].join("; ");
15901
+ running = /* @__PURE__ */ new Map();
15902
+ QUIT_SIGNALS = ["SIGTERM", "SIGINT", "SIGHUP"];
15903
+ listening = /* @__PURE__ */ new Map();
15904
+ interruptIsTheCallers = false;
15905
+ }
15906
+ });
15907
+
15641
15908
  // ../cli/src/lib/agent/pause.ts
15642
15909
  function deliveryState(delivery, questions) {
15643
15910
  const landing = delivery.landing;
@@ -15826,12 +16093,12 @@ var init_runner = __esm({
15826
16093
  });
15827
16094
 
15828
16095
  // ../cli/src/lib/agent/rules.ts
15829
- import fs39 from "node:fs";
15830
- import path44 from "node:path";
16096
+ import fs40 from "node:fs";
16097
+ import path45 from "node:path";
15831
16098
  function ruleFile(agent) {
15832
16099
  if (!agent || hiddenRole(agent)) return "";
15833
16100
  try {
15834
- return fs39.readFileSync(rulePath(agent), "utf8").trim();
16101
+ return fs40.readFileSync(rulePath(agent), "utf8").trim();
15835
16102
  } catch {
15836
16103
  return "";
15837
16104
  }
@@ -15846,11 +16113,11 @@ function setAgentRule(agent, text5) {
15846
16113
  const rule = String(text5 ?? "").trim();
15847
16114
  try {
15848
16115
  if (!rule) {
15849
- fs39.rmSync(rulePath(agent), { force: true });
16116
+ fs40.rmSync(rulePath(agent), { force: true });
15850
16117
  return { ok: true };
15851
16118
  }
15852
- fs39.mkdirSync(RULES, { recursive: true });
15853
- fs39.writeFileSync(rulePath(agent), `${rule}
16119
+ fs40.mkdirSync(RULES, { recursive: true });
16120
+ fs40.writeFileSync(rulePath(agent), `${rule}
15854
16121
  `);
15855
16122
  return { ok: true };
15856
16123
  } catch (err) {
@@ -15898,7 +16165,7 @@ function migrateFlowRules() {
15898
16165
  adoptRenamedRules();
15899
16166
  let here;
15900
16167
  try {
15901
- here = new Set(fs39.readdirSync(RULES));
16168
+ here = new Set(fs40.readdirSync(RULES));
15902
16169
  } catch {
15903
16170
  return [];
15904
16171
  }
@@ -15909,9 +16176,9 @@ function migrateFlowRules() {
15909
16176
  if (!from.length) continue;
15910
16177
  const parts = [ruleFile(owner.name), ...from.map(ruleFile)].filter(Boolean);
15911
16178
  try {
15912
- if (parts.length) fs39.writeFileSync(rulePath(owner.name), `${parts.join("\n\n")}
16179
+ if (parts.length) fs40.writeFileSync(rulePath(owner.name), `${parts.join("\n\n")}
15913
16180
  `);
15914
- for (const flow of from) fs39.rmSync(rulePath(flow), { force: true });
16181
+ for (const flow of from) fs40.rmSync(rulePath(flow), { force: true });
15915
16182
  } catch (err) {
15916
16183
  notes.push(`could not move the ${from.join(", ")} rule(s) onto \`${owner.name}\`: ${String(err)}`);
15917
16184
  continue;
@@ -15925,7 +16192,7 @@ function migrateFlowRules() {
15925
16192
  function adoptRenamedRules() {
15926
16193
  let here;
15927
16194
  try {
15928
- here = fs39.readdirSync(RULES);
16195
+ here = fs40.readdirSync(RULES);
15929
16196
  } catch {
15930
16197
  return;
15931
16198
  }
@@ -15935,13 +16202,13 @@ function adoptRenamedRules() {
15935
16202
  const now = RENAMED_ROLES[was] ?? canonicalSpecAgent(was);
15936
16203
  if (now === was) continue;
15937
16204
  try {
15938
- if (!fs39.existsSync(rulePath(now))) fs39.renameSync(rulePath(was), rulePath(now));
16205
+ if (!fs40.existsSync(rulePath(now))) fs40.renameSync(rulePath(was), rulePath(now));
15939
16206
  else if (FOLDED_SPEC_AGENTS.has(was)) {
15940
16207
  const kept2 = ruleFile(now);
15941
16208
  const carried = ruleFile(was);
15942
- if (carried && !kept2.includes(carried)) fs39.writeFileSync(rulePath(now), `${[kept2, carried].filter(Boolean).join("\n\n")}
16209
+ if (carried && !kept2.includes(carried)) fs40.writeFileSync(rulePath(now), `${[kept2, carried].filter(Boolean).join("\n\n")}
15943
16210
  `);
15944
- fs39.rmSync(rulePath(was), { force: true });
16211
+ fs40.rmSync(rulePath(was), { force: true });
15945
16212
  }
15946
16213
  } catch {
15947
16214
  }
@@ -15958,7 +16225,7 @@ var init_rules2 = __esm({
15958
16225
  init_runner();
15959
16226
  init_workflows();
15960
16227
  init_types3();
15961
- rulePath = (agent) => path44.join(RULES, `${agent}.md`);
16228
+ rulePath = (agent) => path45.join(RULES, `${agent}.md`);
15962
16229
  notOnTheRoster = (name) => `"${name}" is not an agent on this board. It has: ${agentNames().join(", ")}.`;
15963
16230
  flowForRequest = (req) => req.refineRound !== void 0 && REFINE_ACTIONS.has(req.action) ? flowByCommand("refine") : flowByAction(req.action);
15964
16231
  ruleOwnerSays = (owner) => `\`${owner.name}\` agent carries one rule of its own${owner.role ? ", on every flow it runs" : ""}`;
@@ -16043,8 +16310,8 @@ var init_source = __esm({
16043
16310
  });
16044
16311
 
16045
16312
  // ../cli/src/lib/card-sources.ts
16046
- import fs40 from "node:fs";
16047
- import path45 from "node:path";
16313
+ import fs41 from "node:fs";
16314
+ import path46 from "node:path";
16048
16315
  function planById(id) {
16049
16316
  const found2 = readPlan(`plans/${id}-.md`);
16050
16317
  return found2?.text.trim() ? found2 : null;
@@ -16054,8 +16321,8 @@ function cardLink(id) {
16054
16321
  const found2 = open3 ?? locateArchived(id);
16055
16322
  if (!found2) return null;
16056
16323
  try {
16057
- const file = found2.kind === "group" ? path45.join(found2.target, "root.md") : found2.target;
16058
- const title = parseFrontmatter(fs40.readFileSync(file, "utf8")).meta?.title ?? "";
16324
+ const file = found2.kind === "group" ? path46.join(found2.target, "root.md") : found2.target;
16325
+ const title = parseFrontmatter(fs41.readFileSync(file, "utf8")).meta?.title ?? "";
16059
16326
  return { kind: "card", ref: String(id), title, ...open3 ? {} : { archived: true } };
16060
16327
  } catch {
16061
16328
  return null;
@@ -16080,7 +16347,7 @@ function link(ref, items) {
16080
16347
  if (ref.kind === "plan") return planLink(ref.id);
16081
16348
  if (ref.kind === "card") return cardLink(ref.id);
16082
16349
  if (ref.kind === "url") return urlLink(ref.url);
16083
- const item = items().find((signal2) => path45.basename(signal2.relPath) === ref.file);
16350
+ const item = items().find((signal2) => path46.basename(signal2.relPath) === ref.file);
16084
16351
  return item ? itemLink(item) : null;
16085
16352
  }
16086
16353
  function cardSources(meta, body) {
@@ -16121,12 +16388,12 @@ var init_card_sources = __esm({
16121
16388
  });
16122
16389
 
16123
16390
  // ../cli/src/lib/view/read.ts
16124
- import fs41 from "node:fs";
16125
- import path46 from "node:path";
16391
+ import fs42 from "node:fs";
16392
+ import path47 from "node:path";
16126
16393
  function readCard(file, relFromTodo) {
16127
- const id = idPrefix(path46.basename(relFromTodo));
16394
+ const id = idPrefix(path47.basename(relFromTodo));
16128
16395
  if (id === null) {
16129
- const parts = relFromTodo.split(path46.sep);
16396
+ const parts = relFromTodo.split(path47.sep);
16130
16397
  const folderId = idPrefix(parts[parts.length - 2] || "");
16131
16398
  if (folderId === null) return null;
16132
16399
  return buildCard(folderId, file, relFromTodo);
@@ -16134,10 +16401,10 @@ function readCard(file, relFromTodo) {
16134
16401
  return buildCard(id, file, relFromTodo);
16135
16402
  }
16136
16403
  function buildCard(id, file, relFromTodo) {
16137
- const text5 = fs41.readFileSync(file, "utf8");
16404
+ const text5 = fs42.readFileSync(file, "utf8");
16138
16405
  const { meta, body } = parseFrontmatter(text5);
16139
16406
  if (!meta) return null;
16140
- const relPath = relFromTodo.split(path46.sep).join("/");
16407
+ const relPath = relFromTodo.split(path47.sep).join("/");
16141
16408
  const flow = workflowFor(meta.workflow);
16142
16409
  const plan = !!meta.workflow && archivesOnApproval(flow);
16143
16410
  const stages = cardStages(meta, flow).length;
@@ -16146,7 +16413,7 @@ function buildCard(id, file, relFromTodo) {
16146
16413
  // Derived from the file exactly as it is on disk (lib/board/revision.ts), so a card
16147
16414
  // read here and a card read by the contract agree on what version this is.
16148
16415
  revision: revisionOf(text5),
16149
- updatedAt: fs41.statSync(file).mtime.toISOString(),
16416
+ updatedAt: fs42.statSync(file).mtime.toISOString(),
16150
16417
  relPath,
16151
16418
  title: meta.title,
16152
16419
  priority: meta.priority,
@@ -16167,22 +16434,22 @@ function buildCard(id, file, relFromTodo) {
16167
16434
  };
16168
16435
  }
16169
16436
  function readGroup2(folderName) {
16170
- const groupDir = path46.join(TODO, folderName);
16171
- const rootFile = path46.join(groupDir, "root.md");
16172
- if (!fs41.existsSync(rootFile)) return null;
16173
- const root = readCard(rootFile, path46.join(folderName, "root.md"));
16437
+ const groupDir = path47.join(TODO, folderName);
16438
+ const rootFile = path47.join(groupDir, "root.md");
16439
+ if (!fs42.existsSync(rootFile)) return null;
16440
+ const root = readCard(rootFile, path47.join(folderName, "root.md"));
16174
16441
  if (!root) return null;
16175
16442
  root.isGroup = true;
16176
16443
  const { total, resolved: resolved2 } = subtaskLines(root.body);
16177
16444
  root.subtaskLines = { total, resolved: resolved2 };
16178
16445
  const subCards = [];
16179
16446
  const recurse = (dir, relDir) => {
16180
- for (const entry of fs41.readdirSync(dir, { withFileTypes: true })) {
16181
- const childRel = path46.join(relDir, entry.name);
16447
+ for (const entry of fs42.readdirSync(dir, { withFileTypes: true })) {
16448
+ const childRel = path47.join(relDir, entry.name);
16182
16449
  if (entry.isDirectory()) {
16183
- recurse(path46.join(dir, entry.name), childRel);
16450
+ recurse(path47.join(dir, entry.name), childRel);
16184
16451
  } else if (entry.name.endsWith(".md") && entry.name !== "README.md" && entry.name !== "root.md") {
16185
- const c = readCard(path46.join(dir, entry.name), childRel);
16452
+ const c = readCard(path47.join(dir, entry.name), childRel);
16186
16453
  if (c) {
16187
16454
  c.parent = { id: root.id, title: root.title };
16188
16455
  subCards.push(c);
@@ -16203,12 +16470,12 @@ function readGroup2(folderName) {
16203
16470
  return { root, subCards };
16204
16471
  }
16205
16472
  function standaloneCards(dirRel) {
16206
- const dir = dirRel ? path46.join(TODO, dirRel) : TODO;
16207
- if (!fs41.existsSync(dir)) return [];
16473
+ const dir = dirRel ? path47.join(TODO, dirRel) : TODO;
16474
+ if (!fs42.existsSync(dir)) return [];
16208
16475
  const cards = [];
16209
- for (const entry of fs41.readdirSync(dir, { withFileTypes: true })) {
16476
+ for (const entry of fs42.readdirSync(dir, { withFileTypes: true })) {
16210
16477
  if (entry.isFile() && entry.name.endsWith(".md") && entry.name !== "README.md") {
16211
- const c = readCard(path46.join(dir, entry.name), path46.join(dirRel, entry.name));
16478
+ const c = readCard(path47.join(dir, entry.name), path47.join(dirRel, entry.name));
16212
16479
  if (c) cards.push(c);
16213
16480
  }
16214
16481
  }
@@ -16220,7 +16487,7 @@ function collectCards() {
16220
16487
  const top = standaloneCards("");
16221
16488
  board2.push(...top);
16222
16489
  every.push(...top);
16223
- for (const entry of fs41.readdirSync(TODO, { withFileTypes: true })) {
16490
+ for (const entry of fs42.readdirSync(TODO, { withFileTypes: true })) {
16224
16491
  if (!entry.isDirectory()) continue;
16225
16492
  if (isGroupFolder(entry.name)) {
16226
16493
  const g = readGroup2(entry.name);
@@ -16252,7 +16519,7 @@ function findCard(id) {
16252
16519
  }
16253
16520
  function attachSources(card) {
16254
16521
  try {
16255
- const { meta, body } = parseFrontmatter(fs41.readFileSync(path46.join(TODO, card.relPath), "utf8"));
16522
+ const { meta, body } = parseFrontmatter(fs42.readFileSync(path47.join(TODO, card.relPath), "utf8"));
16256
16523
  const sources = meta ? cardSources(meta, body) : [];
16257
16524
  if (sources.length) card.sources = sources;
16258
16525
  } catch {
@@ -16440,8 +16707,8 @@ var init_review = __esm({
16440
16707
  });
16441
16708
 
16442
16709
  // ../cli/src/lib/agent/deliveries.ts
16443
- import fs42 from "node:fs";
16444
- import path47 from "node:path";
16710
+ import fs43 from "node:fs";
16711
+ import path48 from "node:path";
16445
16712
  function writeAudit(delivery, runs) {
16446
16713
  const known2 = new Map(readAudit(delivery.deliveryId)?.sessions.map((s) => [s.sessionId, s]) ?? []);
16447
16714
  const sessions = delivery.sessions.flatMap((id) => {
@@ -16469,10 +16736,10 @@ function writeAudit(delivery, runs) {
16469
16736
  }
16470
16737
  function writeAuditFile(record) {
16471
16738
  try {
16472
- fs42.mkdirSync(DELIVERIES, { recursive: true });
16739
+ fs43.mkdirSync(DELIVERIES, { recursive: true });
16473
16740
  const tmp = `${auditPath(record.deliveryId)}.tmp`;
16474
- fs42.writeFileSync(tmp, JSON.stringify(record, null, 2) + "\n");
16475
- fs42.renameSync(tmp, auditPath(record.deliveryId));
16741
+ fs43.writeFileSync(tmp, JSON.stringify(record, null, 2) + "\n");
16742
+ fs43.renameSync(tmp, auditPath(record.deliveryId));
16476
16743
  return true;
16477
16744
  } catch {
16478
16745
  return false;
@@ -16480,7 +16747,7 @@ function writeAuditFile(record) {
16480
16747
  }
16481
16748
  function readAudit(deliveryId) {
16482
16749
  try {
16483
- const data = JSON.parse(fs42.readFileSync(auditPath(deliveryId), "utf8"));
16750
+ const data = JSON.parse(fs43.readFileSync(auditPath(deliveryId), "utf8"));
16484
16751
  return { sessions: Array.isArray(data?.sessions) ? data.sessions : [] };
16485
16752
  } catch {
16486
16753
  return null;
@@ -16495,7 +16762,7 @@ function syncAudit(deliveryId, just) {
16495
16762
  }
16496
16763
  function wholeAudit(file) {
16497
16764
  try {
16498
- const data = JSON.parse(fs42.readFileSync(file, "utf8"));
16765
+ const data = JSON.parse(fs43.readFileSync(file, "utf8"));
16499
16766
  return typeof data?.deliveryId === "string" && Array.isArray(data.sessions) ? data : null;
16500
16767
  } catch {
16501
16768
  return null;
@@ -16508,14 +16775,14 @@ function activeAudit(file) {
16508
16775
  function allAudits() {
16509
16776
  let names4;
16510
16777
  try {
16511
- names4 = fs42.readdirSync(DELIVERIES);
16778
+ names4 = fs43.readdirSync(DELIVERIES);
16512
16779
  } catch {
16513
16780
  return [];
16514
16781
  }
16515
16782
  const out = [];
16516
16783
  for (const name of names4) {
16517
16784
  if (!name.endsWith(".json")) continue;
16518
- const audit = wholeAudit(path47.join(DELIVERIES, name));
16785
+ const audit = wholeAudit(path48.join(DELIVERIES, name));
16519
16786
  if (audit) out.push(audit);
16520
16787
  }
16521
16788
  return out;
@@ -16626,11 +16893,11 @@ function resumeRefusal(delivery, store) {
16626
16893
  );
16627
16894
  }
16628
16895
  if (!worktreeExists(delivery.worktree)) {
16629
- const path86 = delivery.worktree ?? "";
16896
+ const path88 = delivery.worktree ?? "";
16630
16897
  return refusal(
16631
16898
  "deliveryWorktreeGone",
16632
- `delivery ${id}'s worktree ${path86} is gone, so there is nothing to carry on. ${rebuild}`,
16633
- { id, task, path: path86, command: discard }
16899
+ `delivery ${id}'s worktree ${path88} is gone, so there is nothing to carry on. ${rebuild}`,
16900
+ { id, task, path: path88, command: discard }
16634
16901
  );
16635
16902
  }
16636
16903
  if (!branchExists(delivery.branch)) {
@@ -16683,11 +16950,12 @@ function tidyCheckout(deliveryId) {
16683
16950
  const store = readStore();
16684
16951
  const row = store.deliveries.find((d) => d.deliveryId === deliveryId) ?? auditOnly(deliveryId);
16685
16952
  if (!row || row.status === "active") return NOTHING;
16686
- if (!row.worktree && !row.branch) return NOTHING;
16687
- if (isResumable(row)) return { removed: false, kept: true };
16688
16953
  if (store.runs.some((r) => r.deliveryId === deliveryId && runIsLive(r))) {
16689
16954
  return { removed: false, kept: false, error: `a run of delivery ${deliveryId} is still going`, reason: "deliveryRunGoing", args: { id: deliveryId } };
16690
16955
  }
16956
+ if (row.status !== "finished" || Date.now() - (row.endedAt ?? 0) > 3e4) stopWatchers(row.sessions);
16957
+ if (!row.worktree && !row.branch) return NOTHING;
16958
+ if (isResumable(row)) return { removed: false, kept: true };
16691
16959
  const removed = removeWorktree(row.worktree, row.branch, true);
16692
16960
  if (!removed.ok) return { removed: false, kept: false, error: removed.error };
16693
16961
  forgetCheckout(deliveryId);
@@ -16710,7 +16978,10 @@ function forgetCheckout(deliveryId) {
16710
16978
  function sweepCheckouts() {
16711
16979
  const seen = /* @__PURE__ */ new Set();
16712
16980
  const complaints = [];
16713
- const rows = [...readStore().deliveries, ...allAudits().flatMap((a) => auditRow(a) ?? [])];
16981
+ const store = readStore();
16982
+ const rows = [...store.deliveries, ...allAudits().flatMap((a) => auditRow(a) ?? [])];
16983
+ const ended = rows.filter((row) => row.status !== "active" && (row.status !== "finished" || Date.now() - (row.endedAt ?? 0) > 3e4) && !store.runs.some((r) => r.deliveryId === row.deliveryId && runIsLive(r)));
16984
+ stopWatchers([...new Set(ended.flatMap((row) => row.sessions))]);
16714
16985
  for (const row of rows) {
16715
16986
  if (seen.has(row.deliveryId) || row.status === "active" || !row.worktree) continue;
16716
16987
  seen.add(row.deliveryId);
@@ -16720,21 +16991,27 @@ function sweepCheckouts() {
16720
16991
  dropEmptyWorktreeFolders();
16721
16992
  return complaints;
16722
16993
  }
16723
- function resumeRecord(id) {
16724
- const found2 = endedDelivery(id);
16994
+ function resumeRecord(id, execution) {
16995
+ const continueActive = !!execution;
16996
+ const found2 = (continueActive ? namedDelivery(id) : void 0) ?? endedDelivery(id);
16725
16997
  if (!found2) return { ok: false, ...unknownDelivery(id) };
16726
16998
  const refused = resumeRefusal(found2);
16727
- if (refused) return { ok: false, ...refused };
16999
+ if (refused && !(continueActive && found2.status === "active")) return { ok: false, ...refused };
16728
17000
  const out = withStore((store) => {
16729
17001
  const audit = wholeAudit(auditPath(found2.deliveryId));
16730
- const row = (audit && auditRow(audit)) ?? store.deliveries.find((d) => d.deliveryId === found2.deliveryId);
17002
+ const live = store.deliveries.find((d) => d.deliveryId === found2.deliveryId);
17003
+ const row = continueActive && live?.status === "active" ? live : (audit && auditRow(audit)) ?? live;
16731
17004
  if (!row) return { ok: false, ...unknownDelivery(id) };
16732
17005
  const again = resumeRefusal(row, store);
16733
- if (again) return { ok: false, ...again };
17006
+ if (continueActive && row.status === "active") {
17007
+ if (store.runs.some((r) => r.deliveryId === row.deliveryId && runIsLive(r))) {
17008
+ return { ok: false, ...refusal("deliveryRunGoing", "the delivery still has a running agent; stop it before continuing") };
17009
+ }
17010
+ } else if (again) return { ok: false, ...again };
16734
17011
  const at = Date.now();
16735
17012
  const resumed = {
16736
17013
  ...row,
16737
- cloudExecution: executionOf() ?? row.cloudExecution,
17014
+ cloudExecution: execution ?? executionOf() ?? row.cloudExecution,
16738
17015
  status: "active",
16739
17016
  endedAt: void 0,
16740
17017
  steps: [...row.steps, { step: "resume", at }],
@@ -16763,10 +17040,10 @@ function carryOnFrom(deliveryId) {
16763
17040
  function approvedRequirements(cardId2) {
16764
17041
  const found2 = locate(cardId2);
16765
17042
  if (!found2) return "";
16766
- const file = found2.kind === "group" ? path47.join(found2.target, "root.md") : found2.target;
17043
+ const file = found2.kind === "group" ? path48.join(found2.target, "root.md") : found2.target;
16767
17044
  let text5;
16768
17045
  try {
16769
- text5 = fs42.readFileSync(file, "utf8");
17046
+ text5 = fs43.readFileSync(file, "utf8");
16770
17047
  } catch {
16771
17048
  return "";
16772
17049
  }
@@ -16796,8 +17073,8 @@ function cardMeta2(cardId2) {
16796
17073
  try {
16797
17074
  const found2 = locate(cardId2);
16798
17075
  if (!found2) return null;
16799
- const file = found2.kind === "group" ? path47.join(found2.target, "root.md") : found2.target;
16800
- return parseFrontmatter(fs42.readFileSync(file, "utf8")).meta;
17076
+ const file = found2.kind === "group" ? path48.join(found2.target, "root.md") : found2.target;
17077
+ return parseFrontmatter(fs43.readFileSync(file, "utf8")).meta;
16801
17078
  } catch {
16802
17079
  return null;
16803
17080
  }
@@ -17046,6 +17323,7 @@ var init_deliveries = __esm({
17046
17323
  init_complete();
17047
17324
  init_outputs();
17048
17325
  init_env();
17326
+ init_stop();
17049
17327
  init_flows();
17050
17328
  init_pause();
17051
17329
  init_rules2();
@@ -17055,7 +17333,7 @@ var init_deliveries = __esm({
17055
17333
  init_store();
17056
17334
  init_types3();
17057
17335
  init_worktree();
17058
- auditPath = (deliveryId) => path47.join(DELIVERIES, `${deliveryId}.json`);
17336
+ auditPath = (deliveryId) => path48.join(DELIVERIES, `${deliveryId}.json`);
17059
17337
  activeAudits = () => allAudits().filter((a) => a.status === "active");
17060
17338
  auditRow = (audit) => readDeliveryRow({ ...audit, sessions: audit.sessions.map((s) => s.sessionId) });
17061
17339
  lastSessionRunning = (audit) => audit.sessions[audit.sessions.length - 1]?.status === "running";
@@ -17083,6 +17361,41 @@ var init_deliveries = __esm({
17083
17361
  }
17084
17362
  });
17085
17363
 
17364
+ // ../cli/src/lib/agent/landing-lock.ts
17365
+ import fs44 from "node:fs";
17366
+ import path49 from "node:path";
17367
+ function lockLanding() {
17368
+ const dir = path49.join(SESSIONS_DIR, ".landing.lock");
17369
+ try {
17370
+ const entries = fs44.readdirSync(dir);
17371
+ const token3 = entries.length === 1 ? entries[0] : "";
17372
+ const match = /^(\d+)-[a-f0-9-]{36}$/.exec(token3);
17373
+ const stat = fs44.statSync(dir);
17374
+ if (match && Date.now() - stat.mtimeMs > landingLimits.staleMs) {
17375
+ stopWatchers(void 0, Number(match[1]), () => {
17376
+ const now = fs44.readdirSync(dir);
17377
+ return now.length === 1 && now[0] === token3 && Date.now() - fs44.statSync(dir).mtimeMs > landingLimits.staleMs;
17378
+ }, stat.birthtimeMs || stat.mtimeMs);
17379
+ }
17380
+ } catch {
17381
+ }
17382
+ return tryLock(dir);
17383
+ }
17384
+ function renewLandingLock() {
17385
+ const now = /* @__PURE__ */ new Date();
17386
+ fs44.utimesSync(path49.join(SESSIONS_DIR, ".landing.lock"), now, now);
17387
+ }
17388
+ var landingLimits;
17389
+ var init_landing_lock = __esm({
17390
+ "../cli/src/lib/agent/landing-lock.ts"() {
17391
+ "use strict";
17392
+ init_lock();
17393
+ init_paths();
17394
+ init_stop();
17395
+ landingLimits = { staleMs: 5 * 6e4 };
17396
+ }
17397
+ });
17398
+
17086
17399
  // ../cli/src/lib/agent/answers.ts
17087
17400
  function answerOutcome(delivery) {
17088
17401
  return pendingAnswers(delivery).some((a) => a.outcome === "changed") ? "changed" : "unchanged";
@@ -17147,8 +17460,8 @@ var init_retry = __esm({
17147
17460
  });
17148
17461
 
17149
17462
  // ../cli/src/lib/agent/landing.ts
17150
- import fs43 from "node:fs";
17151
- import path48 from "node:path";
17463
+ import fs45 from "node:fs";
17464
+ import path50 from "node:path";
17152
17465
  function waitingOut(delivery, now = Date.now()) {
17153
17466
  const landing = delivery.landing;
17154
17467
  if (!landing) return false;
@@ -17284,8 +17597,9 @@ function noteQueue(held4) {
17284
17597
  }
17285
17598
  async function advanceLanding() {
17286
17599
  let release;
17600
+ let current;
17287
17601
  try {
17288
- release = tryLock(path48.join(SESSIONS_DIR, ".landing.lock"));
17602
+ release = lockLanding();
17289
17603
  if (!release) return null;
17290
17604
  const owed = await owedRestart();
17291
17605
  if (owed) return owed;
@@ -17300,11 +17614,19 @@ async function advanceLanding() {
17300
17614
  return null;
17301
17615
  }
17302
17616
  tried.add(picked.deliveryId);
17617
+ current = picked;
17618
+ renewLandingLock();
17303
17619
  const step = await landStep(picked);
17304
17620
  if (step.start) return step.start;
17305
17621
  if (!step.done) return null;
17306
17622
  }
17307
- } catch {
17623
+ } catch (error) {
17624
+ if (current && error instanceof GitTimeoutError) {
17625
+ giveUpSlot(current, error.message);
17626
+ patchLanding(current.deliveryId, (landing) => {
17627
+ landing.retryAt = Date.now() + 3e4;
17628
+ });
17629
+ }
17308
17630
  return null;
17309
17631
  } finally {
17310
17632
  release?.();
@@ -17621,7 +17943,7 @@ async function settleAlreadyLanded(delivery) {
17621
17943
  return true;
17622
17944
  }
17623
17945
  function repairLanding() {
17624
- const release = tryLock(path48.join(SESSIONS_DIR, ".landing.lock"));
17946
+ const release = lockLanding();
17625
17947
  if (!release) return [];
17626
17948
  try {
17627
17949
  return repairIdleLanding();
@@ -17654,7 +17976,7 @@ var init_landing = __esm({
17654
17976
  init_execution();
17655
17977
  init_board();
17656
17978
  init_io();
17657
- init_lock();
17979
+ init_landing_lock();
17658
17980
  init_paths();
17659
17981
  init_answers();
17660
17982
  init_commit_mode();
@@ -17695,9 +18017,9 @@ var init_landing = __esm({
17695
18017
  sameDir = (a, b) => {
17696
18018
  const real2 = (dir) => {
17697
18019
  try {
17698
- return fs43.realpathSync(dir);
18020
+ return fs45.realpathSync(dir);
17699
18021
  } catch {
17700
- return path48.resolve(dir);
18022
+ return path50.resolve(dir);
17701
18023
  }
17702
18024
  };
17703
18025
  return real2(a) === real2(b);
@@ -17706,35 +18028,35 @@ var init_landing = __esm({
17706
18028
  });
17707
18029
 
17708
18030
  // ../cli/src/lib/agent/pictures.ts
17709
- import fs44 from "node:fs";
17710
- import path49 from "node:path";
18031
+ import fs46 from "node:fs";
18032
+ import path51 from "node:path";
17711
18033
  function claimRunPictures(box2, sessionId, names4 = []) {
17712
18034
  if (!box2 || !freeBox(box2) || names4.length === 0) return [];
17713
18035
  const from = pictureBox(box2);
17714
18036
  const to = pictureBox(sessionId);
17715
18037
  try {
17716
- fs44.mkdirSync(SESSIONS_DIR, { recursive: true });
17717
- fs44.renameSync(from, to);
18038
+ fs46.mkdirSync(SESSIONS_DIR, { recursive: true });
18039
+ fs46.renameSync(from, to);
17718
18040
  } catch {
17719
18041
  return [];
17720
18042
  }
17721
- return names4.filter(pictureName).map((name) => path49.join(to, name)).filter((file) => fs44.existsSync(file));
18043
+ return names4.filter(pictureName).map((name) => path51.join(to, name)).filter((file) => fs46.existsSync(file));
17722
18044
  }
17723
18045
  function returnRunPictures(sessionId, box2) {
17724
18046
  if (!box2 || !boxId(box2)) return;
17725
18047
  try {
17726
- fs44.renameSync(pictureBox(sessionId), pictureBox(box2));
18048
+ fs46.renameSync(pictureBox(sessionId), pictureBox(box2));
17727
18049
  } catch {
17728
18050
  }
17729
18051
  }
17730
18052
  function moveRunPictures(from, to, files2 = []) {
17731
18053
  if (files2.length === 0) return [];
17732
18054
  try {
17733
- fs44.renameSync(pictureBox(from), pictureBox(to));
18055
+ fs46.renameSync(pictureBox(from), pictureBox(to));
17734
18056
  } catch {
17735
18057
  return files2;
17736
18058
  }
17737
- return files2.map((file) => path49.join(pictureBox(to), path49.basename(file)));
18059
+ return files2.map((file) => path51.join(pictureBox(to), path51.basename(file)));
17738
18060
  }
17739
18061
  var pictureName, boxId, freeBox, pictureBox;
17740
18062
  var init_pictures = __esm({
@@ -17743,14 +18065,14 @@ var init_pictures = __esm({
17743
18065
  init_paths();
17744
18066
  pictureName = (name) => /^[0-9a-f-]{36}\.[a-z0-9]{2,5}$/.test(name);
17745
18067
  boxId = (box2) => /^[0-9a-f-]{36}$/.test(box2);
17746
- freeBox = (box2) => boxId(box2) && !fs44.existsSync(path49.join(SESSIONS_DIR, `${box2}.log`));
17747
- pictureBox = (box2) => path49.join(SESSIONS_DIR, `${box2}.images`);
18068
+ freeBox = (box2) => boxId(box2) && !fs46.existsSync(path51.join(SESSIONS_DIR, `${box2}.log`));
18069
+ pictureBox = (box2) => path51.join(SESSIONS_DIR, `${box2}.images`);
17748
18070
  }
17749
18071
  });
17750
18072
 
17751
18073
  // ../cli/src/lib/memories.ts
17752
- import fs45 from "node:fs";
17753
- import path50 from "node:path";
18074
+ import fs47 from "node:fs";
18075
+ import path52 from "node:path";
17754
18076
  function readMemory(text5, builtIn, folder) {
17755
18077
  const { meta, body } = splitFrontmatter(text5);
17756
18078
  if (meta === null) return null;
@@ -17770,7 +18092,7 @@ function readMemory(text5, builtIn, folder) {
17770
18092
  rules: body.trim(),
17771
18093
  text: text5,
17772
18094
  builtIn,
17773
- dir: path50.join(MEMORY, folder)
18095
+ dir: path52.join(MEMORY, folder)
17774
18096
  };
17775
18097
  }
17776
18098
  function globalMemories() {
@@ -17781,14 +18103,14 @@ function globalMemories() {
17781
18103
  }
17782
18104
  let folders2 = [];
17783
18105
  try {
17784
- folders2 = fs45.readdirSync(MEMORY, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).sort();
18106
+ folders2 = fs47.readdirSync(MEMORY, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).sort();
17785
18107
  } catch {
17786
18108
  }
17787
18109
  for (const name of folders2) {
17788
18110
  if (name in BUNDLED_MEMORY_FILES || name === RESERVED_MEMORY_DIR) continue;
17789
- const file = path50.join(MEMORY, name, GLOBAL_MEMORY_FILE);
17790
- if (!fs45.existsSync(file)) continue;
17791
- const memory = readMemory(fs45.readFileSync(file, "utf8"), false, name);
18111
+ const file = path52.join(MEMORY, name, GLOBAL_MEMORY_FILE);
18112
+ if (!fs47.existsSync(file)) continue;
18113
+ const memory = readMemory(fs47.readFileSync(file, "utf8"), false, name);
17792
18114
  if (memory) out.push(memory);
17793
18115
  }
17794
18116
  return out;
@@ -17844,16 +18166,16 @@ var init_facts = __esm({
17844
18166
  });
17845
18167
 
17846
18168
  // ../cli/src/lib/view/archive.ts
17847
- import fs46 from "node:fs";
17848
- import path51 from "node:path";
18169
+ import fs48 from "node:fs";
18170
+ import path53 from "node:path";
17849
18171
  function archivedId(file) {
17850
- const own = idPrefix(path51.basename(file));
17851
- return own !== null ? own : idPrefix(path51.basename(path51.dirname(file)));
18172
+ const own = idPrefix(path53.basename(file));
18173
+ return own !== null ? own : idPrefix(path53.basename(path53.dirname(file)));
17852
18174
  }
17853
18175
  function readFile(file) {
17854
18176
  const id = archivedId(file);
17855
18177
  if (id === null) return null;
17856
- const { meta, body } = parseFrontmatter(fs46.readFileSync(file, "utf8"));
18178
+ const { meta, body } = parseFrontmatter(fs48.readFileSync(file, "utf8"));
17857
18179
  if (!meta) return null;
17858
18180
  return {
17859
18181
  row: {
@@ -17861,15 +18183,15 @@ function readFile(file) {
17861
18183
  title: meta.title,
17862
18184
  archived: meta.archived,
17863
18185
  rejected: meta.rejected,
17864
- relPath: rel(file).split(path51.sep).join("/")
18186
+ relPath: rel(file).split(path53.sep).join("/")
17865
18187
  },
17866
18188
  body: body.replace(/^\n+/, "").replace(/\s+$/, ""),
17867
18189
  meta
17868
18190
  };
17869
18191
  }
17870
18192
  function readArchive() {
17871
- const relPath = rel(ARCHIVE).split(path51.sep).join("/");
17872
- if (!fs46.existsSync(ARCHIVE)) return { relPath, cards: [] };
18193
+ const relPath = rel(ARCHIVE).split(path53.sep).join("/");
18194
+ if (!fs48.existsSync(ARCHIVE)) return { relPath, cards: [] };
17873
18195
  const cards = walkMd(ARCHIVE).map(readFile).filter((found2) => found2 !== null).map((found2) => found2.row);
17874
18196
  cards.sort((a, b) => b.id - a.id);
17875
18197
  return { relPath, cards };
@@ -17884,7 +18206,7 @@ var init_archive = __esm({
17884
18206
  });
17885
18207
 
17886
18208
  // ../cli/src/lib/agent/prompts.ts
17887
- import path52 from "node:path";
18209
+ import path54 from "node:path";
17888
18210
  function resumePrompt(deliveryId, cardId2, action) {
17889
18211
  if (!deliveryId) return RESUME_PROMPT;
17890
18212
  if (action === "conflict") {
@@ -17928,7 +18250,7 @@ function buildAsk(rawReq, notes = []) {
17928
18250
  }
17929
18251
  function cardFileOf(id) {
17930
18252
  const found2 = id === void 0 ? null : locate(id);
17931
- return found2 ? found2.kind === "group" ? path52.join(found2.target, "root.md") : found2.target : "";
18253
+ return found2 ? found2.kind === "group" ? path54.join(found2.target, "root.md") : found2.target : "";
17932
18254
  }
17933
18255
  function realPaths(text5, req) {
17934
18256
  if (req.action !== "scheduled" && req.action !== "sub") return text5;
@@ -18021,7 +18343,7 @@ function frozenRules(req) {
18021
18343
  function archivedCardFile(id) {
18022
18344
  const found2 = locateArchived(id);
18023
18345
  if (!found2) return `${rel(ARCHIVE)}/`;
18024
- return rel(found2.kind === "group" ? path52.join(found2.target, "root.md") : found2.target);
18346
+ return rel(found2.kind === "group" ? path54.join(found2.target, "root.md") : found2.target);
18025
18347
  }
18026
18348
  function shippedLines(cardId2) {
18027
18349
  const delivery = endedDelivery(`${cardId2}`);
@@ -18192,7 +18514,7 @@ function actionPrompt(req, command, notes) {
18192
18514
  case "spec": {
18193
18515
  const agent = findSpecAgent(req.specAgent ?? "");
18194
18516
  const found2 = req.id === void 0 ? null : locate(req.id);
18195
- const cardFile3 = found2 ? rel(found2.kind === "group" ? path52.join(found2.target, "root.md") : found2.target) : `task #${req.id}`;
18517
+ const cardFile3 = found2 ? rel(found2.kind === "group" ? path54.join(found2.target, "root.md") : found2.target) : `task #${req.id}`;
18196
18518
  const own = agent ? specAgentInstructions(agent) : null;
18197
18519
  if (own) notes.push(...own.notes);
18198
18520
  const memory = agent ? agentMemoryBlock(agent) : "";
@@ -18305,288 +18627,81 @@ ${shared}` : ""
18305
18627
  req.notes ? `Extra notes: ${req.notes}` : "",
18306
18628
  `Don't ask me questions with human-in-the-loop. Leave any questions as open questions.`
18307
18629
  ].filter(Boolean).join(" ");
18308
- case "resolve":
18309
- return [
18310
- `${kb}. Apply my answers to the open questions on task ${req.id} ${named2} following \`akb guide resolve\`.`,
18311
- carriedNote(req),
18312
- answeredNote(req.id, command),
18313
- req.notes ? `Extra notes: ${req.notes}` : ""
18314
- ].filter(Boolean).join(" ");
18315
- }
18316
- }
18317
- function discardedCardsPrompt(cards = []) {
18318
- if (!cards.length) return "";
18319
- return "The user discarded the cards listed below. Their absence is intentional. Do not restore or recreate them, including under new IDs or filenames. Continue the remaining work from the current board, preserving the other cards.\n\n" + cards.map((c) => `- #${c.id}: ${c.path}`).join("\n");
18320
- }
18321
- var RESUME_PROMPT, DELIVERY_RESUME, DELIVERY_RESUME_CARD, RESTARTABLE, RESTART_LEAD, restartable, NO_IMPLEMENT, BOARD_FOLDER, CARD_FILE, PROJECT_FOLDER, PLANNING_FOR, SPEC_SELECTOR_FOR, reflectedCards;
18322
- var init_prompts = __esm({
18323
- "../cli/src/lib/agent/prompts.ts"() {
18324
- "use strict";
18325
- init_cards();
18326
- init_memory();
18327
- init_memories();
18328
- init_paths();
18329
- init_agents();
18330
- init_command();
18331
- init_deliveries();
18332
- init_env();
18333
- init_facts();
18334
- init_outputs();
18335
- init_archive();
18336
- init_flows();
18337
- init_resolve();
18338
- init_runner();
18339
- init_workflows();
18340
- init_catalog2();
18341
- init_roles();
18342
- init_rules2();
18343
- init_settings2();
18344
- init_types3();
18345
- RESUME_PROMPT = [
18346
- `Continue. The previous run ended before it finished the task.`,
18347
- `Pick up where you left off and carry it through.`
18348
- ].join(" ");
18349
- DELIVERY_RESUME = [
18350
- `Continue delivery %s. The previous run ended before the delivery finished.`,
18351
- `Re-enter the flow from the top and check each step's precondition before you do it \u2014 work that is already done is done, so do not repeat it.`
18352
- ].join(" ");
18353
- DELIVERY_RESUME_CARD = `Build the card as the delivery holds it, not as the file reads now: \`%c\` prints the approved copy.`;
18354
- RESTARTABLE = /* @__PURE__ */ new Set([
18355
- "implement",
18356
- "clarify",
18357
- "resolve",
18358
- "approve",
18359
- "archive",
18360
- "spec",
18361
- "conflict",
18362
- "scheduled"
18363
- ]);
18364
- RESTART_LEAD = [
18365
- `The session you were continuing is gone, and this one holds nothing of it.`,
18366
- `Do the task below from the top, checking each step's precondition before you do it \u2014 work an earlier run already finished is done, so don't repeat it.`
18367
- ].join(" ");
18368
- restartable = (run2) => (run2.cardId !== null || !!run2.deliveryId) && RESTARTABLE.has(run2.action);
18369
- NO_IMPLEMENT = `(Unless the request explicitly asks for implementation, don't implement it; revising the card's drafts is not implementing.)`;
18370
- BOARD_FOLDER = "<board folder>";
18371
- CARD_FILE = "<card file>";
18372
- PROJECT_FOLDER = "<project folder>";
18373
- PLANNING_FOR = /* @__PURE__ */ new Set(["create", "clarify", "resolve", "approve", "edit"]);
18374
- SPEC_SELECTOR_FOR = /* @__PURE__ */ new Set(["clarify", "resolve", "approve", "edit"]);
18375
- reflectedCards = (req) => req.cards ?? (req.id !== void 0 ? [req.id] : []);
18376
- }
18377
- });
18378
-
18379
- // ../cli/src/lib/agent/stop.ts
18380
- import { spawnSync as spawnSync4 } from "node:child_process";
18381
- import { randomUUID as randomUUID5 } from "node:crypto";
18382
- import fs47 from "node:fs";
18383
- import os6 from "node:os";
18384
- function markedInProc(entry, strict = false) {
18385
- const pids = [];
18386
- for (const name of fs47.readdirSync("/proc")) {
18387
- if (!/^\d+$/.test(name)) continue;
18388
- try {
18389
- if (fs47.readFileSync(`/proc/${name}/environ`, "utf8").split("\0").includes(entry)) pids.push(Number(name));
18390
- } catch (error) {
18391
- if (strict && !["ENOENT", "ESRCH"].includes(error.code ?? "")) {
18392
- const stat = fs47.statSync(`/proc/${name}`, { throwIfNoEntry: false });
18393
- if (stat?.uid === process.getuid?.()) throw error;
18394
- }
18395
- }
18396
- }
18397
- return pids;
18398
- }
18399
- function markedInPs(entry, roots) {
18400
- const ps = spawnSync4("ps", ["-A", "-E", "-ww", "-o", "pid=,ppid=,command="], {
18401
- encoding: "utf8",
18402
- maxBuffer: 64 * 1024 * 1024,
18403
- timeout: 5e3
18404
- });
18405
- if (ps.status !== 0 || !ps.stdout) throw new Error("\u65E0\u6CD5\u67E5\u8BE2\u8FDB\u7A0B\u6811\u3002");
18406
- const children = /* @__PURE__ */ new Map();
18407
- const listed2 = /* @__PURE__ */ new Set();
18408
- const found2 = /* @__PURE__ */ new Set();
18409
- for (const line of ps.stdout.split("\n")) {
18410
- const m = /^\s*(\d+)\s+(\d+)\s(.*)$/.exec(line);
18411
- if (!m) continue;
18412
- const pid = Number(m[1]);
18413
- const ppid = Number(m[2]);
18414
- listed2.add(pid);
18415
- children.set(ppid, [...children.get(ppid) ?? [], pid]);
18416
- if (`${m[3]} `.includes(` ${entry} `)) found2.add(pid);
18417
- }
18418
- const queue = [...found2, ...roots.filter((pid) => listed2.has(pid))];
18419
- for (const pid of queue) {
18420
- found2.add(pid);
18421
- for (const child of children.get(pid) ?? []) if (!found2.has(child)) queue.push(child);
18422
- }
18423
- return [...found2];
18424
- }
18425
- function marked(mark, roots = []) {
18426
- if (WINDOWS3 || !mark.value) return [];
18427
- const entry = `${mark.name}=${mark.value}`;
18428
- try {
18429
- const pids = process.platform === "linux" ? markedInProc(entry) : markedInPs(entry, roots);
18430
- return pids.filter((pid) => pid !== process.pid);
18431
- } catch {
18432
- return [];
18433
- }
18434
- }
18435
- function signal(pids, sig) {
18436
- for (const pid of pids) {
18437
- try {
18438
- process.kill(pid, sig);
18439
- } catch {
18440
- }
18441
- }
18442
- }
18443
- function startedUnder(list, root, since) {
18444
- const self = list.procs.find((p) => p.pid === root);
18445
- const from = since ?? self?.startedAt;
18446
- if (from === void 0 || list.bootedAt > from) return [];
18447
- if (self && self.startedAt > from) return [];
18448
- const found2 = self ? [root] : [];
18449
- const seen = /* @__PURE__ */ new Set([root]);
18450
- const queue = [{ pid: root, startedAt: from }];
18451
- for (const parent of queue) {
18452
- for (const p of list.procs) {
18453
- if (p.ppid !== parent.pid || seen.has(p.pid) || p.startedAt < parent.startedAt) continue;
18454
- seen.add(p.pid);
18455
- found2.push(p.pid);
18456
- queue.push(p);
18457
- }
18458
- }
18459
- return found2.reverse();
18460
- }
18461
- function readProcList(out) {
18462
- const procs = [];
18463
- let bootedAt;
18464
- for (const line of out.split(/\r?\n/)) {
18465
- const boot = /^boot (\d+)$/.exec(line.trim());
18466
- if (boot) bootedAt = Number(boot[1]);
18467
- const m = /^(\d+) (\d+) (\d+)$/.exec(line.trim());
18468
- if (m) procs.push({ pid: Number(m[1]), ppid: Number(m[2]), startedAt: Number(m[3]) });
18469
- }
18470
- return bootedAt !== void 0 && procs.length ? { procs, bootedAt } : void 0;
18471
- }
18472
- function procsOnWindows() {
18473
- const ps = spawnSync4("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", LIST_PROCS], {
18474
- encoding: "utf8",
18475
- maxBuffer: 64 * 1024 * 1024,
18476
- timeout: 5e3,
18477
- windowsHide: true
18478
- });
18479
- return ps.status === 0 && ps.stdout ? readProcList(ps.stdout) : void 0;
18480
- }
18481
- function killUnderOnWindows(pid, since) {
18482
- if (!WINDOWS3 || !pid) return;
18483
- const list = procsOnWindows();
18484
- if (!list) return;
18485
- const pids = startedUnder(list, pid, since).filter((p) => p !== process.pid);
18486
- if (!pids.length) return;
18487
- spawnSync4("taskkill", ["/F", ...pids.flatMap((p) => ["/PID", String(p)])], { stdio: "ignore", windowsHide: true, timeout: 1e4 });
18488
- }
18489
- function killMarked(mark, agent) {
18490
- if (WINDOWS3) {
18491
- if (agent) killUnderOnWindows(agent.pid, agent.since);
18492
- return;
18493
- }
18494
- signal(marked(mark), "SIGKILL");
18495
- }
18496
- function killTreeOnWindows(pid) {
18497
- if (!WINDOWS3 || !pid) return false;
18498
- const out = spawnSync4("taskkill", ["/PID", String(pid), "/T", "/F"], { stdio: "ignore", windowsHide: true, timeout: 1e4 });
18499
- return out.status === 0;
18500
- }
18501
- function later(ms, fn) {
18502
- const t = setTimeout(fn, ms);
18503
- if (typeof t.unref === "function") t.unref();
18504
- }
18505
- function endAgent(child, mark, graceMs) {
18506
- const term = (sig) => {
18507
- try {
18508
- child.kill(sig);
18509
- } catch {
18510
- }
18511
- };
18512
- if (killTreeOnWindows(child.pid)) return;
18513
- killUnderOnWindows(child.pid);
18514
- const seen = new Set(marked(mark, child.pid ? [child.pid] : []));
18515
- term("SIGTERM");
18516
- signal([...seen].filter((pid) => pid !== child.pid), "SIGTERM");
18517
- const round = (left) => {
18518
- const pids = marked(mark, [...seen]);
18519
- signal(pids, "SIGKILL");
18520
- for (const pid of pids) seen.add(pid);
18521
- if (pids.length && left > 1) later(KILL_ROUND_MS, () => round(left - 1));
18522
- };
18523
- later(graceMs, () => {
18524
- term("SIGKILL");
18525
- round(KILL_ROUNDS);
18526
- });
18527
- }
18528
- function killRunning() {
18529
- for (const [child, mark] of running) {
18530
- if (killTreeOnWindows(child.pid)) continue;
18531
- killUnderOnWindows(child.pid);
18532
- signal(marked(mark, child.pid ? [child.pid] : []), "SIGKILL");
18533
- try {
18534
- child.kill("SIGKILL");
18535
- } catch {
18536
- }
18630
+ case "resolve":
18631
+ return [
18632
+ `${kb}. Apply my answers to the open questions on task ${req.id} ${named2} following \`akb guide resolve\`.`,
18633
+ carriedNote(req),
18634
+ answeredNote(req.id, command),
18635
+ req.notes ? `Extra notes: ${req.notes}` : ""
18636
+ ].filter(Boolean).join(" ");
18537
18637
  }
18538
- running.clear();
18539
- }
18540
- function unlisten() {
18541
- for (const [sig, fn] of listening) process.removeListener(sig, fn);
18542
- listening.clear();
18543
- }
18544
- function onQuit(sig) {
18545
- killRunning();
18546
- unlisten();
18547
- if (process.listenerCount(sig) === 0) process.exit(128 + os6.constants.signals[sig]);
18548
18638
  }
18549
- function trackAgent(child, mark) {
18550
- if (!child.pid) return;
18551
- running.set(child, mark);
18552
- child.once("close", () => {
18553
- running.delete(child);
18554
- if (!running.size) unlisten();
18555
- });
18556
- for (const sig of QUIT_SIGNALS) {
18557
- if (listening.has(sig) || sig === "SIGINT" && interruptIsTheCallers) continue;
18558
- const fn = () => onQuit(sig);
18559
- listening.set(sig, fn);
18560
- process.on(sig, fn);
18561
- }
18639
+ function discardedCardsPrompt(cards = []) {
18640
+ if (!cards.length) return "";
18641
+ return "The user discarded the cards listed below. Their absence is intentional. Do not restore or recreate them, including under new IDs or filenames. Continue the remaining work from the current board, preserving the other cards.\n\n" + cards.map((c) => `- #${c.id}: ${c.path}`).join("\n");
18562
18642
  }
18563
- var runMark, stopMark, markEnv, WINDOWS3, KILL_ROUND_MS, KILL_ROUNDS, LIST_PROCS, running, QUIT_SIGNALS, listening, interruptIsTheCallers;
18564
- var init_stop = __esm({
18565
- "../cli/src/lib/agent/stop.ts"() {
18643
+ var RESUME_PROMPT, DELIVERY_RESUME, DELIVERY_RESUME_CARD, RESTARTABLE, RESTART_LEAD, restartable, NO_IMPLEMENT, BOARD_FOLDER, CARD_FILE, PROJECT_FOLDER, PLANNING_FOR, SPEC_SELECTOR_FOR, reflectedCards;
18644
+ var init_prompts = __esm({
18645
+ "../cli/src/lib/agent/prompts.ts"() {
18566
18646
  "use strict";
18647
+ init_cards();
18648
+ init_memory();
18649
+ init_memories();
18650
+ init_paths();
18651
+ init_agents();
18652
+ init_command();
18653
+ init_deliveries();
18567
18654
  init_env();
18568
- runMark = (sessionId) => ({ name: RUN_ENV, value: sessionId });
18569
- stopMark = () => ({ name: STOP_ENV, value: randomUUID5() });
18570
- markEnv = (env2, mark) => ({ ...env2, [mark.name]: mark.value });
18571
- WINDOWS3 = process.platform === "win32";
18572
- KILL_ROUND_MS = 300;
18573
- KILL_ROUNDS = 3;
18574
- LIST_PROCS = [
18575
- "$ErrorActionPreference = 'Stop'",
18576
- "Get-CimInstance Win32_Process | ForEach-Object { if ($_.CreationDate) { '{0} {1} {2}' -f $_.ProcessId, $_.ParentProcessId, ([DateTimeOffset]$_.CreationDate).ToUnixTimeMilliseconds() } }",
18577
- "'boot {0}' -f ([DateTimeOffset](Get-CimInstance Win32_OperatingSystem).LastBootUpTime).ToUnixTimeMilliseconds()"
18578
- ].join("; ");
18579
- running = /* @__PURE__ */ new Map();
18580
- QUIT_SIGNALS = ["SIGTERM", "SIGINT", "SIGHUP"];
18581
- listening = /* @__PURE__ */ new Map();
18582
- interruptIsTheCallers = false;
18655
+ init_facts();
18656
+ init_outputs();
18657
+ init_archive();
18658
+ init_flows();
18659
+ init_resolve();
18660
+ init_runner();
18661
+ init_workflows();
18662
+ init_catalog2();
18663
+ init_roles();
18664
+ init_rules2();
18665
+ init_settings2();
18666
+ init_types3();
18667
+ RESUME_PROMPT = [
18668
+ `Continue. The previous run ended before it finished the task.`,
18669
+ `Pick up where you left off and carry it through.`
18670
+ ].join(" ");
18671
+ DELIVERY_RESUME = [
18672
+ `Continue delivery %s. The previous run ended before the delivery finished.`,
18673
+ `Re-enter the flow from the top and check each step's precondition before you do it \u2014 work that is already done is done, so do not repeat it.`
18674
+ ].join(" ");
18675
+ DELIVERY_RESUME_CARD = `Build the card as the delivery holds it, not as the file reads now: \`%c\` prints the approved copy.`;
18676
+ RESTARTABLE = /* @__PURE__ */ new Set([
18677
+ "implement",
18678
+ "clarify",
18679
+ "resolve",
18680
+ "approve",
18681
+ "archive",
18682
+ "spec",
18683
+ "conflict",
18684
+ "scheduled"
18685
+ ]);
18686
+ RESTART_LEAD = [
18687
+ `The session you were continuing is gone, and this one holds nothing of it.`,
18688
+ `Do the task below from the top, checking each step's precondition before you do it \u2014 work an earlier run already finished is done, so don't repeat it.`
18689
+ ].join(" ");
18690
+ restartable = (run2) => (run2.cardId !== null || !!run2.deliveryId) && RESTARTABLE.has(run2.action);
18691
+ NO_IMPLEMENT = `(Unless the request explicitly asks for implementation, don't implement it; revising the card's drafts is not implementing.)`;
18692
+ BOARD_FOLDER = "<board folder>";
18693
+ CARD_FILE = "<card file>";
18694
+ PROJECT_FOLDER = "<project folder>";
18695
+ PLANNING_FOR = /* @__PURE__ */ new Set(["create", "clarify", "resolve", "approve", "edit"]);
18696
+ SPEC_SELECTOR_FOR = /* @__PURE__ */ new Set(["clarify", "resolve", "approve", "edit"]);
18697
+ reflectedCards = (req) => req.cards ?? (req.id !== void 0 ? [req.id] : []);
18583
18698
  }
18584
18699
  });
18585
18700
 
18586
18701
  // ../cli/src/lib/agent/sessions.ts
18587
18702
  import { randomUUID as randomUUID6 } from "node:crypto";
18588
- import fs48 from "node:fs";
18589
- import path53 from "node:path";
18703
+ import fs49 from "node:fs";
18704
+ import path55 from "node:path";
18590
18705
  function reap(runs, reaped = [], restore = []) {
18591
18706
  let changed = false;
18592
18707
  const now = Date.now();
@@ -18606,7 +18721,7 @@ function reap(runs, reaped = [], restore = []) {
18606
18721
  }
18607
18722
  function stampDuration(run2, endedAt) {
18608
18723
  try {
18609
- fs48.appendFileSync(run2.logPath, durationLine(endedAt - run2.startedAt));
18724
+ fs49.appendFileSync(run2.logPath, durationLine(endedAt - run2.startedAt));
18610
18725
  } catch {
18611
18726
  }
18612
18727
  }
@@ -18620,7 +18735,7 @@ function cardNow(cardId2) {
18620
18735
  try {
18621
18736
  const file = cardFile(cardId2);
18622
18737
  if (!file) return null;
18623
- const { meta } = parseFrontmatter(fs48.readFileSync(file, "utf8"));
18738
+ const { meta } = parseFrontmatter(fs49.readFileSync(file, "utf8"));
18624
18739
  if (!meta) return null;
18625
18740
  return { status: meta.status || "todo", questions: openOf(meta.questions).length, title: meta.title };
18626
18741
  } catch {
@@ -18757,9 +18872,9 @@ function leftBoardOnLanding(id) {
18757
18872
  function archivedRejected(id) {
18758
18873
  const filed = locateArchived(id);
18759
18874
  if (!filed) return null;
18760
- const file = filed.kind === "group" ? path53.join(filed.target, "root.md") : filed.target;
18875
+ const file = filed.kind === "group" ? path55.join(filed.target, "root.md") : filed.target;
18761
18876
  try {
18762
- return !!parseFrontmatter(fs48.readFileSync(file, "utf8")).meta?.rejected;
18877
+ return !!parseFrontmatter(fs49.readFileSync(file, "utf8")).meta?.rejected;
18763
18878
  } catch {
18764
18879
  return false;
18765
18880
  }
@@ -19016,7 +19131,7 @@ function openRun(req, prompt, notes = [], sessionId = randomUUID6(), said3) {
19016
19131
  return { run: record, spec };
19017
19132
  }
19018
19133
  function planningSession(cardId2, cwd = REPO_ROOT) {
19019
- const here = (dir) => !dir || path53.resolve(dir) === cwd;
19134
+ const here = (dir) => !dir || path55.resolve(dir) === cwd;
19020
19135
  const ended = readRuns().filter((r) => r.status === "done").sort((a, b) => b.startedAt - a.startedAt);
19021
19136
  const of = (run3, fork) => {
19022
19137
  if (!here(run3.cwd)) return { none: "elsewhere" };
@@ -19085,7 +19200,9 @@ function requestOf(record) {
19085
19200
  pictures: record.pictures
19086
19201
  };
19087
19202
  }
19088
- async function openResume(id, build) {
19203
+ async function openResume(id, options) {
19204
+ const build = typeof options === "object" ? options : void 0;
19205
+ const requireSession = options === true || !!build;
19089
19206
  const restore = [];
19090
19207
  const runs = withRuns((all2) => {
19091
19208
  reap(all2, [], restore);
@@ -19110,7 +19227,7 @@ async function openResume(id, build) {
19110
19227
  if (refused) return refused;
19111
19228
  }
19112
19229
  const retrying = lacksSession(prev);
19113
- if (build && (retrying || !resumesUnder(prev.harness))) {
19230
+ if (requireSession && (!resumeSessionId(prev) || !resumesUnder(prev.harness))) {
19114
19231
  return refusal("runNoSession", "\u539F\u6784\u5EFA\u6CA1\u6709\u53EF\u6062\u590D\u7684\u4F1A\u8BDD\u6807\u8BC6\uFF0C\u6216\u539F Agent \u4E0D\u652F\u6301\u7EED\u8DD1\uFF1B\u672C\u6B21\u672A\u542F\u52A8\u65B0\u4F1A\u8BDD\u3002");
19115
19232
  }
19116
19233
  const ask2 = retrying && prev.status !== "stopped" ? retryAsk(prev) : void 0;
@@ -19249,8 +19366,8 @@ async function resumeHeld(prev, plan, sessionId, resuming, fresh) {
19249
19366
  clearAsks(prev.sessionId);
19250
19367
  try {
19251
19368
  if (prev.action !== "create") {
19252
- fs48.unlinkSync(prev.logPath);
19253
- fs48.rmSync(eventsPathOf(prev.sessionId), { force: true });
19369
+ fs49.unlinkSync(prev.logPath);
19370
+ fs49.rmSync(eventsPathOf(prev.sessionId), { force: true });
19254
19371
  }
19255
19372
  } catch {
19256
19373
  }
@@ -19274,8 +19391,8 @@ function forgetRun(sessionId) {
19274
19391
  if (!gone) return;
19275
19392
  clearAsks(sessionId);
19276
19393
  try {
19277
- fs48.unlinkSync(gone.logPath);
19278
- fs48.rmSync(eventsPathOf(gone.sessionId), { force: true });
19394
+ fs49.unlinkSync(gone.logPath);
19395
+ fs49.rmSync(eventsPathOf(gone.sessionId), { force: true });
19279
19396
  } catch {
19280
19397
  }
19281
19398
  }
@@ -19304,14 +19421,14 @@ function noteSpecRefusals(sessionId, refused) {
19304
19421
  }
19305
19422
  function clearAsks(sessionId) {
19306
19423
  try {
19307
- fs48.unlinkSync(asksPathOf(sessionId));
19424
+ fs49.unlinkSync(asksPathOf(sessionId));
19308
19425
  } catch {
19309
19426
  }
19310
19427
  }
19311
19428
  function readAsks(sessionId) {
19312
19429
  let data;
19313
19430
  try {
19314
- data = JSON.parse(fs48.readFileSync(asksPathOf(sessionId), "utf8"));
19431
+ data = JSON.parse(fs49.readFileSync(asksPathOf(sessionId), "utf8"));
19315
19432
  } catch {
19316
19433
  return { asks: [], refines: [], refused: [] };
19317
19434
  }
@@ -19338,10 +19455,10 @@ function readAsks(sessionId) {
19338
19455
  return { asks: asks.filter((a) => !discarded.has(a.cardId)), refines: refines.filter((a) => !discarded.has(a.cardId)), refused };
19339
19456
  }
19340
19457
  function writeAsks(sessionId, file) {
19341
- fs48.mkdirSync(SESSIONS_DIR, { recursive: true });
19458
+ fs49.mkdirSync(SESSIONS_DIR, { recursive: true });
19342
19459
  const tmp = `${asksPathOf(sessionId)}.tmp`;
19343
- fs48.writeFileSync(tmp, JSON.stringify(file, null, 2) + "\n");
19344
- fs48.renameSync(tmp, asksPathOf(sessionId));
19460
+ fs49.writeFileSync(tmp, JSON.stringify(file, null, 2) + "\n");
19461
+ fs49.renameSync(tmp, asksPathOf(sessionId));
19345
19462
  }
19346
19463
  function markSpawned(sessionId, pid) {
19347
19464
  patch(sessionId, (r) => {
@@ -19429,7 +19546,10 @@ async function cancelDelivery(id) {
19429
19546
  if (!id.trim()) return { ok: false, ...refusal("deliveryUnnamed", "name the delivery to cancel", { action: "cancel" }) };
19430
19547
  const delivery = namedDelivery(id);
19431
19548
  if (!delivery) return { ok: false, ...unknownDelivery(id) };
19432
- if (delivery.status !== "active") return { ok: true, deliveryId: delivery.deliveryId };
19549
+ if (delivery.status !== "active") {
19550
+ tidyCheckout(delivery.deliveryId);
19551
+ return { ok: true, deliveryId: delivery.deliveryId };
19552
+ }
19433
19553
  endDelivery(delivery.deliveryId, "cancelled");
19434
19554
  const live = readRuns().find((r) => r.status === "running" && r.deliveryId === delivery.deliveryId && r.action !== "sub");
19435
19555
  if (live) await stopRun(live.sessionId);
@@ -19454,7 +19574,9 @@ async function runIsDown(sessionId) {
19454
19574
  }
19455
19575
  async function resumeDelivery(id) {
19456
19576
  if (!id.trim()) return { ok: false, ...refusal("deliveryUnnamed", "name the delivery to carry on", { action: "resume" }) };
19457
- const put = resumeRecord(id);
19577
+ const execution = executionOf();
19578
+ if (execution && !await executionHeld(execution)) return { ok: false, error: "\u65E0\u6CD5\u786E\u8BA4\u672C\u6B21\u7EED\u8DD1\u7684\u6267\u884C\u6743\uFF0C\u4EA4\u4ED8\u4FDD\u6301\u539F\u72B6\u3002" };
19579
+ const put = resumeRecord(id, execution);
19458
19580
  if (!put.ok) return put;
19459
19581
  const delivery = put.delivery;
19460
19582
  if (await settleAlreadyLanded(delivery)) {
@@ -19495,12 +19617,12 @@ async function discardDelivery(id) {
19495
19617
  return { ok: true, deliveryId: delivery.deliveryId };
19496
19618
  }
19497
19619
  function writeSpec(spec) {
19498
- fs48.mkdirSync(SESSIONS_DIR, { recursive: true });
19499
- fs48.writeFileSync(specPathOf(spec.sessionId), JSON.stringify(spec, null, 2) + "\n");
19620
+ fs49.mkdirSync(SESSIONS_DIR, { recursive: true });
19621
+ fs49.writeFileSync(specPathOf(spec.sessionId), JSON.stringify(spec, null, 2) + "\n");
19500
19622
  }
19501
19623
  function readSpec(sessionId) {
19502
19624
  try {
19503
- const spec = JSON.parse(fs48.readFileSync(specPathOf(sessionId), "utf8"));
19625
+ const spec = JSON.parse(fs49.readFileSync(specPathOf(sessionId), "utf8"));
19504
19626
  return spec && Array.isArray(spec.plan?.argv) ? spec : null;
19505
19627
  } catch {
19506
19628
  return null;
@@ -19508,28 +19630,28 @@ function readSpec(sessionId) {
19508
19630
  }
19509
19631
  function dropSpec(sessionId) {
19510
19632
  try {
19511
- fs48.unlinkSync(specPathOf(sessionId));
19633
+ fs49.unlinkSync(specPathOf(sessionId));
19512
19634
  } catch {
19513
19635
  }
19514
19636
  }
19515
19637
  async function acquireIndexLock(giveUp) {
19516
19638
  const until = Date.now() + INDEX_WAIT_MS;
19517
- const holder = path53.join(INDEX_LOCK2, "holder");
19639
+ const holder = path55.join(INDEX_LOCK2, "holder");
19518
19640
  for (; ; ) {
19519
19641
  if (giveUp()) return null;
19520
19642
  try {
19521
- fs48.mkdirSync(INDEX_LOCK2, { recursive: false });
19522
- fs48.writeFileSync(holder, String(process.pid));
19523
- return () => fs48.rmSync(INDEX_LOCK2, { recursive: true, force: true });
19643
+ fs49.mkdirSync(INDEX_LOCK2, { recursive: false });
19644
+ fs49.writeFileSync(holder, String(process.pid));
19645
+ return () => fs49.rmSync(INDEX_LOCK2, { recursive: true, force: true });
19524
19646
  } catch (err) {
19525
19647
  const code = err.code;
19526
19648
  if (code === "ENOENT") {
19527
- fs48.mkdirSync(path53.dirname(INDEX_LOCK2), { recursive: true });
19649
+ fs49.mkdirSync(path55.dirname(INDEX_LOCK2), { recursive: true });
19528
19650
  continue;
19529
19651
  }
19530
19652
  if (code !== "EEXIST") throw err;
19531
19653
  if (!pidAlive(Number(readHolder(holder)))) {
19532
- fs48.rmSync(INDEX_LOCK2, { recursive: true, force: true });
19654
+ fs49.rmSync(INDEX_LOCK2, { recursive: true, force: true });
19533
19655
  continue;
19534
19656
  }
19535
19657
  if (Date.now() > until) return null;
@@ -19539,7 +19661,7 @@ async function acquireIndexLock(giveUp) {
19539
19661
  }
19540
19662
  function readHolder(file) {
19541
19663
  try {
19542
- return fs48.readFileSync(file, "utf8").trim();
19664
+ return fs49.readFileSync(file, "utf8").trim();
19543
19665
  } catch {
19544
19666
  return String(process.pid);
19545
19667
  }
@@ -19548,6 +19670,7 @@ var INDEX_ACTIONS, SINGLETON_ACTIONS, VERB, SINGLETON_BUSY, RUN_STATUS, specPath
19548
19670
  var init_sessions = __esm({
19549
19671
  "../cli/src/lib/agent/sessions.ts"() {
19550
19672
  "use strict";
19673
+ init_execution();
19551
19674
  init_creation();
19552
19675
  init_cards();
19553
19676
  init_frontmatter();
@@ -19630,8 +19753,8 @@ var init_sessions = __esm({
19630
19753
  conflict: "implementing",
19631
19754
  hook: "implementing"
19632
19755
  };
19633
- specPathOf = (sessionId) => path53.join(SESSIONS_DIR, `${sessionId}.plan.json`);
19634
- asksPathOf = (sessionId) => path53.join(SESSIONS_DIR, `${sessionId}.asks.json`);
19756
+ specPathOf = (sessionId) => path55.join(SESSIONS_DIR, `${sessionId}.plan.json`);
19757
+ asksPathOf = (sessionId) => path55.join(SESSIONS_DIR, `${sessionId}.asks.json`);
19635
19758
  carriesSession = (r) => !!(r.resumedFrom || r.continues && !r.continues.fork);
19636
19759
  canPickUp = (r) => (r.status === "error" || r.status === "interrupted" || r.status === "stopped") && !(r.action === "create" && readRuns().some((next) => next.resumedFrom === r.sessionId));
19637
19760
  neverStarted = (r) => r.unspawned === true || !!r.errorWhy?.some((why) => why.kind === "notInstalled");
@@ -19765,8 +19888,8 @@ var init_questions = __esm({
19765
19888
  });
19766
19889
 
19767
19890
  // ../cli/src/lib/agent/workflow-cards.ts
19768
- import fs49 from "node:fs";
19769
- import path54 from "node:path";
19891
+ import fs50 from "node:fs";
19892
+ import path56 from "node:path";
19770
19893
  function cardsOnWorkflow(id) {
19771
19894
  const wanted = id || DEFAULT_WORKFLOW;
19772
19895
  const ids = [];
@@ -19777,12 +19900,12 @@ function cardsOnWorkflow(id) {
19777
19900
  return [];
19778
19901
  }
19779
19902
  for (const file of files2) {
19780
- const base = path54.basename(file);
19781
- const card = base === "root.md" ? idPrefix(path54.basename(path54.dirname(file))) : idPrefix(base);
19903
+ const base = path56.basename(file);
19904
+ const card = base === "root.md" ? idPrefix(path56.basename(path56.dirname(file))) : idPrefix(base);
19782
19905
  if (card === null) continue;
19783
19906
  let meta;
19784
19907
  try {
19785
- meta = parseFrontmatter(fs49.readFileSync(file, "utf8")).meta;
19908
+ meta = parseFrontmatter(fs50.readFileSync(file, "utf8")).meta;
19786
19909
  } catch {
19787
19910
  continue;
19788
19911
  }
@@ -19840,12 +19963,12 @@ var init_workflow_cards = __esm({
19840
19963
  });
19841
19964
 
19842
19965
  // ../cli/src/lib/readme.ts
19843
- import fs50 from "node:fs";
19844
- import path55 from "node:path";
19966
+ import fs51 from "node:fs";
19967
+ import path57 from "node:path";
19845
19968
  function stripReadmeRefs(target) {
19846
- if (!fs50.existsSync(README)) return [];
19969
+ if (!fs51.existsSync(README)) return [];
19847
19970
  const needle = target.kind === "group" ? `](${target.rel}/` : `](${target.rel})`;
19848
- const lines = fs50.readFileSync(README, "utf8").split("\n");
19971
+ const lines = fs51.readFileSync(README, "utf8").split("\n");
19849
19972
  const out = [];
19850
19973
  const removed = [];
19851
19974
  let i = 0;
@@ -19867,20 +19990,20 @@ function stripReadmeRefs(target) {
19867
19990
  out.push(line);
19868
19991
  i++;
19869
19992
  }
19870
- if (removed.length) fs50.writeFileSync(README, out.join("\n"));
19993
+ if (removed.length) fs51.writeFileSync(README, out.join("\n"));
19871
19994
  return removed;
19872
19995
  }
19873
19996
  function addReadmeRef(id, title, relPath) {
19874
- if (!fs50.existsSync(README)) return false;
19875
- const link2 = relPath.split(path55.sep).join("/");
19997
+ if (!fs51.existsSync(README)) return false;
19998
+ const link2 = relPath.split(path57.sep).join("/");
19876
19999
  const bullet = `- [#${id} ${title}](${link2})`;
19877
20000
  const heading = `## ${TASKS_HEADING}`;
19878
- let lines = fs50.readFileSync(README, "utf8").split("\n");
20001
+ let lines = fs51.readFileSync(README, "utf8").split("\n");
19879
20002
  const hi = lines.findIndex((l) => l.trim().toLowerCase() === heading.toLowerCase());
19880
20003
  if (hi === -1) {
19881
20004
  while (lines.length && lines[lines.length - 1].trim() === "") lines.pop();
19882
20005
  lines.push("", heading, "", bullet);
19883
- fs50.writeFileSync(README, lines.join("\n") + "\n");
20006
+ fs51.writeFileSync(README, lines.join("\n") + "\n");
19884
20007
  return true;
19885
20008
  }
19886
20009
  let end2 = hi + 1;
@@ -19894,15 +20017,15 @@ function addReadmeRef(id, title, relPath) {
19894
20017
  const at = lastBullet !== -1 ? lastBullet + 1 : lines[hi + 1] === "" ? hi + 2 : hi + 1;
19895
20018
  lines.splice(at, 0, bullet);
19896
20019
  }
19897
- fs50.writeFileSync(README, lines.join("\n"));
20020
+ fs51.writeFileSync(README, lines.join("\n"));
19898
20021
  return true;
19899
20022
  }
19900
20023
  function repointReadmeLink(id, oldRel, newRel, title) {
19901
- if (!fs50.existsSync(README)) return false;
19902
- const oldLink = oldRel.split(path55.sep).join("/");
19903
- const newLink = newRel.split(path55.sep).join("/");
20024
+ if (!fs51.existsSync(README)) return false;
20025
+ const oldLink = oldRel.split(path57.sep).join("/");
20026
+ const newLink = newRel.split(path57.sep).join("/");
19904
20027
  const linkRe = new RegExp(`\\[#${id}\\b[^\\]]*\\]\\(${escapeRegex(oldLink)}\\)`);
19905
- const lines = fs50.readFileSync(README, "utf8").split("\n");
20028
+ const lines = fs51.readFileSync(README, "utf8").split("\n");
19906
20029
  let changed = false;
19907
20030
  for (let i = 0; i < lines.length; i++) {
19908
20031
  if (!lines[i].includes(`](${oldLink})`)) continue;
@@ -19912,7 +20035,7 @@ function repointReadmeLink(id, oldRel, newRel, title) {
19912
20035
  changed = true;
19913
20036
  }
19914
20037
  }
19915
- if (changed) fs50.writeFileSync(README, lines.join("\n"));
20038
+ if (changed) fs51.writeFileSync(README, lines.join("\n"));
19916
20039
  return changed;
19917
20040
  }
19918
20041
  var isTableRow, isBulletStart, bulletIndent, TASKS_HEADING, escapeRegex;
@@ -19929,14 +20052,14 @@ var init_readme = __esm({
19929
20052
  });
19930
20053
 
19931
20054
  // ../cli/src/lib/reconcile.ts
19932
- import fs51 from "node:fs";
19933
- import path56 from "node:path";
20055
+ import fs52 from "node:fs";
20056
+ import path58 from "node:path";
19934
20057
  function liveIds() {
19935
20058
  const ids = /* @__PURE__ */ new Set();
19936
20059
  for (const file of walkMd(TODO)) {
19937
- const base = path56.basename(file);
20060
+ const base = path58.basename(file);
19938
20061
  if (base === "README.md") continue;
19939
- const id = base === "root.md" ? idPrefix(path56.basename(path56.dirname(file))) : idPrefix(base);
20062
+ const id = base === "root.md" ? idPrefix(path58.basename(path58.dirname(file))) : idPrefix(base);
19940
20063
  if (id != null) ids.add(id);
19941
20064
  }
19942
20065
  return ids;
@@ -19944,16 +20067,16 @@ function liveIds() {
19944
20067
  function indexableCards() {
19945
20068
  const out = [];
19946
20069
  for (const dir of walkDirs(TODO)) {
19947
- const id = idPrefix(path56.basename(dir));
19948
- if (id != null && fs51.existsSync(path56.join(dir, "root.md"))) {
19949
- out.push({ id, rel: path56.join(path56.relative(TODO, dir), "root.md").split(path56.sep).join("/") });
20070
+ const id = idPrefix(path58.basename(dir));
20071
+ if (id != null && fs52.existsSync(path58.join(dir, "root.md"))) {
20072
+ out.push({ id, rel: path58.join(path58.relative(TODO, dir), "root.md").split(path58.sep).join("/") });
19950
20073
  }
19951
20074
  }
19952
20075
  for (const file of walkMd(TODO)) {
19953
- const base = path56.basename(file);
20076
+ const base = path58.basename(file);
19954
20077
  if (base === "README.md" || base === "root.md") continue;
19955
- const relTODO = path56.relative(TODO, file);
19956
- const segs = relTODO.split(path56.sep);
20078
+ const relTODO = path58.relative(TODO, file);
20079
+ const segs = relTODO.split(path58.sep);
19957
20080
  if (segs.length !== 1) continue;
19958
20081
  const id = idPrefix(base);
19959
20082
  if (id != null) out.push({ id, rel: segs.join("/") });
@@ -19962,7 +20085,7 @@ function indexableCards() {
19962
20085
  }
19963
20086
  function readmeComplaints(fix) {
19964
20087
  const said3 = [];
19965
- const lines = fs51.readFileSync(README, "utf8").split("\n");
20088
+ const lines = fs52.readFileSync(README, "utf8").split("\n");
19966
20089
  const indexed = /* @__PURE__ */ new Set();
19967
20090
  let fixed = 0;
19968
20091
  for (let i = 0; i < lines.length; i++) {
@@ -19971,13 +20094,13 @@ function readmeComplaints(fix) {
19971
20094
  const id = Number(m[1]);
19972
20095
  const linkPath = m[2];
19973
20096
  indexed.add(id);
19974
- if (fs51.existsSync(path56.join(TODO, linkPath))) continue;
20097
+ if (fs52.existsSync(path58.join(TODO, linkPath))) continue;
19975
20098
  const found2 = locate(id);
19976
20099
  if (!found2) {
19977
20100
  said3.push(`README links #${id} \u2192 ${linkPath}, but no card with that id exists (removed by hand?). Drop the entry or restore the file.`);
19978
20101
  continue;
19979
20102
  }
19980
- const want = (found2.kind === "group" ? path56.join(found2.rel, "root.md") : found2.rel).split(path56.sep).join("/");
20103
+ const want = (found2.kind === "group" ? path58.join(found2.rel, "root.md") : found2.rel).split(path58.sep).join("/");
19981
20104
  if (want === linkPath) continue;
19982
20105
  if (!fix) {
19983
20106
  said3.push(`README links #${id} \u2192 ${linkPath}, which isn't there any more; the card is at ${want}.`);
@@ -19987,7 +20110,7 @@ function readmeComplaints(fix) {
19987
20110
  said3.push(`README link #${id} pointed at missing ${linkPath} \u2192 repointed to ${want}.`);
19988
20111
  fixed++;
19989
20112
  }
19990
- if (fixed) fs51.writeFileSync(README, lines.join("\n"));
20113
+ if (fixed) fs52.writeFileSync(README, lines.join("\n"));
19991
20114
  for (const c of indexableCards()) {
19992
20115
  if (!indexed.has(c.id)) {
19993
20116
  said3.push(`card ${c.rel} (#${c.id}) is not in the README index. Add it under "## ${TASKS_HEADING}".`);
@@ -19999,11 +20122,11 @@ function crossRefComplaints() {
19999
20122
  const said3 = [];
20000
20123
  const live = liveIds();
20001
20124
  for (const file of walkMd(TODO)) {
20002
- const base = path56.basename(file);
20125
+ const base = path58.basename(file);
20003
20126
  if (base === "README.md") continue;
20004
- const ownerId = base === "root.md" ? idPrefix(path56.basename(path56.dirname(file))) : idPrefix(base);
20127
+ const ownerId = base === "root.md" ? idPrefix(path58.basename(path58.dirname(file))) : idPrefix(base);
20005
20128
  if (ownerId == null) continue;
20006
- const { meta } = parseFrontmatter(fs51.readFileSync(file, "utf8"));
20129
+ const { meta } = parseFrontmatter(fs52.readFileSync(file, "utf8"));
20007
20130
  if (!meta) continue;
20008
20131
  for (const field2 of ["blocked_by", "related"]) {
20009
20132
  for (const ref of meta[field2] || []) {
@@ -20016,11 +20139,11 @@ function crossRefComplaints() {
20016
20139
  return said3;
20017
20140
  }
20018
20141
  function reconcileBoard() {
20019
- if (!fs51.existsSync(README)) return;
20142
+ if (!fs52.existsSync(README)) return;
20020
20143
  for (const line of [...readmeComplaints(true), ...crossRefComplaints()]) warn2(line);
20021
20144
  }
20022
20145
  function boardComplaints() {
20023
- if (!fs51.existsSync(README)) return [];
20146
+ if (!fs52.existsSync(README)) return [];
20024
20147
  try {
20025
20148
  return [...readmeComplaints(false), ...crossRefComplaints()];
20026
20149
  } catch {
@@ -20040,8 +20163,8 @@ var init_reconcile = __esm({
20040
20163
  });
20041
20164
 
20042
20165
  // ../cli/src/commands/card.ts
20043
- import fs52 from "node:fs";
20044
- import path57 from "node:path";
20166
+ import fs53 from "node:fs";
20167
+ import path59 from "node:path";
20045
20168
  function defaultBody() {
20046
20169
  return [
20047
20170
  "<\u7528\u4E00\u5C0F\u6BB5\u8BDD\u5148\u8BB2\u53D1\u751F\u4E86\u4EC0\u4E48\u3001\u7528\u6237\u9047\u5230\u4EC0\u4E48\uFF0C\u518D\u8BB2\u8FD9\u5F20\u5361\u8981\u8FBE\u5230\u7684\u7ED3\u679C\u3002>",
@@ -20067,7 +20190,7 @@ function bodyFromFile(opts) {
20067
20190
  if (opts.body === false) die("pass --body-file or --no-body, not both");
20068
20191
  let text5;
20069
20192
  try {
20070
- text5 = fs52.readFileSync(opts.bodyFile, "utf8");
20193
+ text5 = fs53.readFileSync(opts.bodyFile, "utf8");
20071
20194
  } catch {
20072
20195
  die(`can't read ${opts.bodyFile} \u2014 write the body to a file, then pass its path`, { kind: "needs-input" });
20073
20196
  }
@@ -20099,20 +20222,20 @@ function cmdCreate(opts) {
20099
20222
  const start = opts.id;
20100
20223
  const slug2 = slugify(opts.slug !== void 0 ? opts.slug : title);
20101
20224
  const fileRel = `${start}-${slug2}.md`;
20102
- const file = path57.join(TODO, fileRel);
20225
+ const file = path59.join(TODO, fileRel);
20103
20226
  const held4 = locate(start) ?? locateArchived(start);
20104
- if (held4 && path57.basename(held4.target) === `${start}-${slug2}.md`) {
20227
+ if (held4 && path59.basename(held4.target) === `${start}-${slug2}.md`) {
20105
20228
  say(start);
20106
20229
  say(` ${rel(held4.target)} was already written`);
20107
20230
  return { id: start, ids: [start], title, file: rel(held4.target), indexed: false };
20108
20231
  }
20109
20232
  if (held4) die(`#${start} is already a card here (${rel(held4.target)})`, { kind: "id-taken", id: start });
20110
- if (fs52.existsSync(file)) die(`${rel(file)} already exists \u2014 pick a different --slug`);
20233
+ if (fs53.existsSync(file)) die(`${rel(file)} already exists \u2014 pick a different --slug`);
20111
20234
  countBoardEvent(triage ? "cards_created_proposed" : "cards_created_asked");
20112
20235
  const meta = { title, priority, status: "todo", blocked_by, related, modules, workflow, triage, source, questions };
20113
20236
  const scaffolded = !written && opts.body !== false;
20114
20237
  const body = written ?? (scaffolded ? defaultBody() : "");
20115
- fs52.writeFileSync(file, serializeFrontmatter(meta) + "\n\n" + body);
20238
+ fs53.writeFileSync(file, serializeFrontmatter(meta) + "\n\n" + body);
20116
20239
  const indexed = addReadmeRef(start, title, fileRel);
20117
20240
  say(start);
20118
20241
  say(
@@ -20150,8 +20273,8 @@ function cmdUpdate(id, flags) {
20150
20273
  if (creating) die(creating, { kind: "card-being-created", id });
20151
20274
  const found2 = locate(id);
20152
20275
  if (!found2) die(`no task with id ${id} under ${rel(TODO)}`, { kind: "card-not-found", id });
20153
- const file = found2.kind === "group" ? path57.join(found2.target, "root.md") : found2.target;
20154
- const { meta, body } = parseFrontmatter(fs52.readFileSync(file, "utf8"));
20276
+ const file = found2.kind === "group" ? path59.join(found2.target, "root.md") : found2.target;
20277
+ const { meta, body } = parseFrontmatter(fs53.readFileSync(file, "utf8"));
20155
20278
  if (!meta) die(`${rel(file)} has no frontmatter \u2014 run \`migrate\` first`);
20156
20279
  if (flags.blockedBy !== void 0 && flags.addBlockedBy !== void 0) die("--blocked-by replaces the list and --add-blocked-by appends to it \u2014 pass one");
20157
20280
  if (flags.related !== void 0 && flags.addRelated !== void 0) die("--related replaces the list and --add-related appends to it \u2014 pass one");
@@ -20231,26 +20354,26 @@ function cmdUpdate(id, flags) {
20231
20354
  meta.status = "todo";
20232
20355
  changes.push("status\u2192todo (open questions)");
20233
20356
  }
20234
- const curRel = path57.relative(TODO, file);
20357
+ const curRel = path59.relative(TODO, file);
20235
20358
  const isSubtask = found2.kind === "file" && enclosingGroupRoot(file) !== null;
20236
- let base = path57.basename(file);
20359
+ let base = path59.basename(file);
20237
20360
  if (flags.slug !== void 0) {
20238
20361
  if (found2.kind === "group") die("renaming a group root by script is not supported");
20239
20362
  base = `${id}-${slugify(flags.slug)}.md`;
20240
20363
  }
20241
- const destRel = path57.join(path57.dirname(curRel), base);
20242
- const dest = path57.join(TODO, destRel);
20364
+ const destRel = path59.join(path59.dirname(curRel), base);
20365
+ const dest = path59.join(TODO, destRel);
20243
20366
  const moving = dest !== file;
20244
- if (moving && fs52.existsSync(dest)) die(`${rel(dest)} already exists`);
20245
- fs52.writeFileSync(file, serializeFrontmatter(meta) + "\n" + body);
20246
- if (moving) fs52.renameSync(file, dest);
20367
+ if (moving && fs53.existsSync(dest)) die(`${rel(dest)} already exists`);
20368
+ fs53.writeFileSync(file, serializeFrontmatter(meta) + "\n" + body);
20369
+ if (moving) fs53.renameSync(file, dest);
20247
20370
  if (isSubtask) {
20248
20371
  if (moving || changes.includes("title")) repointReadmeLink(id, curRel, destRel, meta.title);
20249
- if (moving) changes.push(`renamed \u2192 ${destRel.split(path57.sep).join("/")}`);
20372
+ if (moving) changes.push(`renamed \u2192 ${destRel.split(path59.sep).join("/")}`);
20250
20373
  } else if (moving) {
20251
20374
  stripReadmeRefs({ kind: "file", rel: curRel });
20252
20375
  addReadmeRef(id, meta.title, destRel);
20253
- changes.push(`renamed \u2192 ${destRel.split(path57.sep).join("/")}`);
20376
+ changes.push(`renamed \u2192 ${destRel.split(path59.sep).join("/")}`);
20254
20377
  } else if (changes.includes("title")) {
20255
20378
  stripReadmeRefs({ kind: "file", rel: curRel });
20256
20379
  addReadmeRef(id, meta.title, curRel);
@@ -20262,8 +20385,8 @@ function cmdUpdateQuestions(id, input) {
20262
20385
  const ops = readQuestionOps(input.ops ?? []);
20263
20386
  const found2 = locate(id);
20264
20387
  if (!found2) die(`no task with id ${id} under ${rel(TODO)}`, { kind: "card-not-found", id });
20265
- const file = found2.kind === "group" ? path57.join(found2.target, "root.md") : found2.target;
20266
- const { meta, body } = parseFrontmatter(fs52.readFileSync(file, "utf8"));
20388
+ const file = found2.kind === "group" ? path59.join(found2.target, "root.md") : found2.target;
20389
+ const { meta, body } = parseFrontmatter(fs53.readFileSync(file, "utf8"));
20267
20390
  if (!meta) die(`${rel(file)} has no frontmatter \u2014 run \`migrate\` first`);
20268
20391
  if (insideRun() && ops.some((op) => op.kind === "skip" || op.kind === "unskip")) {
20269
20392
  die("only the user skips or reopens a question \u2014 a run leaves questions for them to answer");
@@ -20336,7 +20459,7 @@ function cmdUpdateQuestions(id, input) {
20336
20459
  meta.status = "todo";
20337
20460
  changes.push("status\u2192todo (open questions)");
20338
20461
  }
20339
- fs52.writeFileSync(file, serializeFrontmatter(meta) + "\n" + body);
20462
+ fs53.writeFileSync(file, serializeFrontmatter(meta) + "\n" + body);
20340
20463
  for (const kind of closed2) countBoardEvent(kind);
20341
20464
  settleDelivery2(id, skipped, unskipped);
20342
20465
  say(
@@ -20410,13 +20533,13 @@ var init_config_template = __esm({
20410
20533
  });
20411
20534
 
20412
20535
  // ../cli/src/lib/agent/local.ts
20413
- import fs53 from "node:fs";
20414
- import path58 from "node:path";
20536
+ import fs54 from "node:fs";
20537
+ import path60 from "node:path";
20415
20538
  function takeLocalModels(board2) {
20416
- const file = path58.join(board2, ".local.json");
20539
+ const file = path60.join(board2, ".local.json");
20417
20540
  let held4;
20418
20541
  try {
20419
- held4 = configBlock(JSON.parse(fs53.readFileSync(file, "utf8")));
20542
+ held4 = configBlock(JSON.parse(fs54.readFileSync(file, "utf8")));
20420
20543
  } catch {
20421
20544
  return {};
20422
20545
  }
@@ -20434,7 +20557,7 @@ function takeLocalModels(board2) {
20434
20557
  }
20435
20558
  if (!Object.keys(agents).length) return out;
20436
20559
  delete held4.agents;
20437
- fs53.writeFileSync(file, JSON.stringify(held4, null, 2) + "\n");
20560
+ fs54.writeFileSync(file, JSON.stringify(held4, null, 2) + "\n");
20438
20561
  return out;
20439
20562
  }
20440
20563
  var LOCAL_IGNORE_LINE;
@@ -20495,12 +20618,12 @@ var init_identity2 = __esm({
20495
20618
  });
20496
20619
 
20497
20620
  // ../cli/src/lib/signals/migrate.ts
20498
- import fs54 from "node:fs";
20499
- import path59 from "node:path";
20621
+ import fs55 from "node:fs";
20622
+ import path61 from "node:path";
20500
20623
  function readHandled() {
20501
20624
  let text5;
20502
20625
  try {
20503
- text5 = fs54.readFileSync(SIGNALS_OLD_HANDLED, "utf8");
20626
+ text5 = fs55.readFileSync(SIGNALS_OLD_HANDLED, "utf8");
20504
20627
  } catch {
20505
20628
  return [];
20506
20629
  }
@@ -20517,57 +20640,57 @@ function readHandled() {
20517
20640
  return held4;
20518
20641
  }
20519
20642
  function lift(from, into) {
20520
- fs54.mkdirSync(into, { recursive: true });
20521
- fs54.renameSync(from, path59.join(into, freeName(into, path59.basename(from))));
20643
+ fs55.mkdirSync(into, { recursive: true });
20644
+ fs55.renameSync(from, path61.join(into, freeName(into, path61.basename(from))));
20522
20645
  }
20523
20646
  function repoint(dir, from, into) {
20524
20647
  let entries;
20525
20648
  try {
20526
- entries = fs54.readdirSync(dir, { withFileTypes: true });
20649
+ entries = fs55.readdirSync(dir, { withFileTypes: true });
20527
20650
  } catch {
20528
20651
  return;
20529
20652
  }
20530
20653
  for (const entry of entries) {
20531
20654
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
20532
- const file = path59.join(dir, entry.name);
20533
- const text5 = fs54.readFileSync(file, "utf8");
20534
- if (text5.includes(from)) fs54.writeFileSync(file, text5.split(from).join(into));
20655
+ const file = path61.join(dir, entry.name);
20656
+ const text5 = fs55.readFileSync(file, "utf8");
20657
+ if (text5.includes(from)) fs55.writeFileSync(file, text5.split(from).join(into));
20535
20658
  }
20536
20659
  }
20537
20660
  function dropIfEmpty2(dir) {
20538
20661
  try {
20539
- fs54.rmdirSync(dir);
20662
+ fs55.rmdirSync(dir);
20540
20663
  } catch {
20541
20664
  }
20542
20665
  }
20543
20666
  function liftInbox() {
20544
- if (!fs54.existsSync(SIGNALS_OLD_INBOX)) return;
20545
- const oldFiles = path59.join(SIGNALS_OLD_INBOX, "files");
20546
- if (fs54.existsSync(oldFiles)) {
20547
- for (const entry of fs54.readdirSync(oldFiles, { withFileTypes: true })) {
20548
- if (entry.isFile()) lift(path59.join(oldFiles, entry.name), SIGNALS_FILES);
20667
+ if (!fs55.existsSync(SIGNALS_OLD_INBOX)) return;
20668
+ const oldFiles = path61.join(SIGNALS_OLD_INBOX, "files");
20669
+ if (fs55.existsSync(oldFiles)) {
20670
+ for (const entry of fs55.readdirSync(oldFiles, { withFileTypes: true })) {
20671
+ if (entry.isFile()) lift(path61.join(oldFiles, entry.name), SIGNALS_FILES);
20549
20672
  }
20550
20673
  dropIfEmpty2(oldFiles);
20551
20674
  }
20552
20675
  const held4 = new Set(readFolder(TRIAGE).map((signal2) => signal2.sourceId));
20553
- for (const entry of fs54.readdirSync(SIGNALS_OLD_INBOX, { withFileTypes: true })) {
20676
+ for (const entry of fs55.readdirSync(SIGNALS_OLD_INBOX, { withFileTypes: true })) {
20554
20677
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
20555
- const from = path59.join(SIGNALS_OLD_INBOX, entry.name);
20678
+ const from = path61.join(SIGNALS_OLD_INBOX, entry.name);
20556
20679
  const item = parse(from);
20557
20680
  if (item && held4.has(item.sourceId)) {
20558
- fs54.rmSync(from, { force: true });
20681
+ fs55.rmSync(from, { force: true });
20559
20682
  continue;
20560
20683
  }
20561
20684
  if (item) held4.add(item.sourceId);
20562
20685
  lift(from, TRIAGE);
20563
20686
  }
20564
- const wasAt = asWritten(path59.join(SIGNALS_OLD_INBOX, "files"));
20687
+ const wasAt = asWritten(path61.join(SIGNALS_OLD_INBOX, "files"));
20565
20688
  const nowAt = asWritten(SIGNALS_FILES);
20566
20689
  for (const dir of [TRIAGE, SIGNALS_ARCHIVED, SIGNALS_DISMISSED]) repoint(dir, wasAt, nowAt);
20567
20690
  dropIfEmpty2(SIGNALS_OLD_INBOX);
20568
20691
  }
20569
20692
  function liftHandled() {
20570
- if (!fs54.existsSync(SIGNALS_OLD_HANDLED)) return;
20693
+ if (!fs55.existsSync(SIGNALS_OLD_HANDLED)) return;
20571
20694
  const lines = readHandled();
20572
20695
  const held4 = /* @__PURE__ */ new Set([
20573
20696
  ...readFolder(TRIAGE).map((signal2) => signal2.sourceId),
@@ -20577,7 +20700,7 @@ function liftHandled() {
20577
20700
  for (const { sourceId, when } of lines) {
20578
20701
  if (held4.has(sourceId)) continue;
20579
20702
  held4.add(sourceId);
20580
- fs54.mkdirSync(SIGNALS_DISMISSED, { recursive: true });
20703
+ fs55.mkdirSync(SIGNALS_DISMISSED, { recursive: true });
20581
20704
  const name = freeName(SIGNALS_DISMISSED, fileName("", sourceId));
20582
20705
  const front = [
20583
20706
  "---",
@@ -20587,13 +20710,13 @@ function liftHandled() {
20587
20710
  "---",
20588
20711
  ""
20589
20712
  ];
20590
- fs54.writeFileSync(path59.join(SIGNALS_DISMISSED, name), `${front.join("\n")}
20713
+ fs55.writeFileSync(path61.join(SIGNALS_DISMISSED, name), `${front.join("\n")}
20591
20714
  `);
20592
20715
  }
20593
- fs54.rmSync(SIGNALS_OLD_HANDLED, { force: true });
20716
+ fs55.rmSync(SIGNALS_OLD_HANDLED, { force: true });
20594
20717
  }
20595
20718
  function migrateTriage() {
20596
- if (!fs54.existsSync(SIGNALS_OLD_INBOX) && !fs54.existsSync(SIGNALS_OLD_HANDLED)) return;
20719
+ if (!fs55.existsSync(SIGNALS_OLD_INBOX) && !fs55.existsSync(SIGNALS_OLD_HANDLED)) return;
20597
20720
  try {
20598
20721
  liftInbox();
20599
20722
  liftHandled();
@@ -20608,13 +20731,13 @@ var init_migrate = __esm({
20608
20731
  init_yaml();
20609
20732
  init_inbox();
20610
20733
  HANDLED_LINE = /^-\s+(.+?)\s+—\s+(.+)$/;
20611
- asWritten = (dir) => `${rel(dir).split(path59.sep).join("/")}/`;
20734
+ asWritten = (dir) => `${rel(dir).split(path61.sep).join("/")}/`;
20612
20735
  }
20613
20736
  });
20614
20737
 
20615
20738
  // ../cli/src/lib/signals/add.ts
20616
- import fs55 from "node:fs";
20617
- import path60 from "node:path";
20739
+ import fs56 from "node:fs";
20740
+ import path62 from "node:path";
20618
20741
  import { createHash as createHash4 } from "node:crypto";
20619
20742
  function linkTitle(url) {
20620
20743
  let at;
@@ -20627,7 +20750,7 @@ function linkTitle(url) {
20627
20750
  return oneLine(where) || url.slice(0, TITLE_MAX);
20628
20751
  }
20629
20752
  function asText(file) {
20630
- const ext = path60.extname(file.name).toLowerCase();
20753
+ const ext = path62.extname(file.name).toLowerCase();
20631
20754
  if (!file.type.startsWith("text/") && file.type !== "application/json" && !TEXT_EXTENSIONS.has(ext)) return null;
20632
20755
  try {
20633
20756
  const text5 = new TextDecoder("utf-8", { fatal: true }).decode(file.data);
@@ -20637,9 +20760,9 @@ function asText(file) {
20637
20760
  }
20638
20761
  }
20639
20762
  function fileName2(name, data) {
20640
- const base = path60.basename(name);
20641
- const ext = path60.extname(base).toLowerCase().slice(0, 12);
20642
- const word = path60.basename(base, path60.extname(base)).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40) || "file";
20763
+ const base = path62.basename(name);
20764
+ const ext = path62.extname(base).toLowerCase().slice(0, 12);
20765
+ const word = path62.basename(base, path62.extname(base)).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40) || "file";
20643
20766
  const hash2 = createHash4("sha256").update(data).digest("hex").slice(0, 8);
20644
20767
  return `${word}-${hash2}${ext}`;
20645
20768
  }
@@ -20648,9 +20771,9 @@ function addToInbox(drop) {
20648
20771
  const typed = (drop.text ?? "").trim();
20649
20772
  const file = drop.file;
20650
20773
  if (!typed && !file) return { ok: false, error: "nothing to add \u2014 paste a link or some text, or drop a file." };
20651
- if (file && file.data.length === 0) return { ok: false, error: `${path60.basename(file.name)} is empty.` };
20774
+ if (file && file.data.length === 0) return { ok: false, error: `${path62.basename(file.name)} is empty.` };
20652
20775
  if (file && file.data.length > MAX_BYTES) {
20653
- return { ok: false, error: `${path60.basename(file.name)} is over ${MAX_BYTES / 1024 / 1024} MB \u2014 too big for the board.` };
20776
+ return { ok: false, error: `${path62.basename(file.name)} is over ${MAX_BYTES / 1024 / 1024} MB \u2014 too big for the board.` };
20654
20777
  }
20655
20778
  const read4 = describe(typed, file);
20656
20779
  const keep2 = read4.keep;
@@ -20672,8 +20795,8 @@ ${incoming.summary}`);
20672
20795
  if (seen.status === "pending" || seen.status === "archived") return { ok: false, error: `${sayHit(seen)}.` };
20673
20796
  try {
20674
20797
  if (keep2) {
20675
- fs55.mkdirSync(SIGNALS_FILES, { recursive: true });
20676
- fs55.writeFileSync(keep2.at, keep2.data);
20798
+ fs56.mkdirSync(SIGNALS_FILES, { recursive: true });
20799
+ fs56.writeFileSync(keep2.at, keep2.data);
20677
20800
  }
20678
20801
  return { ok: true, signal: writeSignal({ ...incoming, sourceId }, formatStamp(/* @__PURE__ */ new Date()), drop.slug) };
20679
20802
  } catch (e) {
@@ -20687,7 +20810,7 @@ function describe(typed, file) {
20687
20810
  return only ? [only] : [];
20688
20811
  };
20689
20812
  if (file) {
20690
- const name = path60.basename(file.name);
20813
+ const name = path62.basename(file.name);
20691
20814
  const text5 = asText(file);
20692
20815
  const note = typed ? `
20693
20816
 
@@ -20699,7 +20822,7 @@ ${typed}` : "";
20699
20822
  const at = keepAt(file);
20700
20823
  return {
20701
20824
  incoming: {
20702
- title: path60.basename(name, path60.extname(name)) || name,
20825
+ title: path62.basename(name, path62.extname(name)) || name,
20703
20826
  summary: `${name} \u2014 ${boardRel2(at)}${note}`,
20704
20827
  ...named2
20705
20828
  },
@@ -20737,73 +20860,73 @@ var init_add = __esm({
20737
20860
  MAX_BYTES = 25 * 1024 * 1024;
20738
20861
  TITLE_MAX = 120;
20739
20862
  TEXT_EXTENSIONS = /* @__PURE__ */ new Set([".md", ".markdown", ".txt", ".text", ".csv", ".json", ".yml", ".yaml", ".log"]);
20740
- boardRel2 = (file) => rel(file).split(path60.sep).join("/");
20863
+ boardRel2 = (file) => rel(file).split(path62.sep).join("/");
20741
20864
  oneLine = (text5) => {
20742
20865
  const first = text5.split("\n").find((line) => line.trim()) ?? "";
20743
20866
  const clean = first.replace(/^#+\s*/, "").trim();
20744
20867
  return clean.length > TITLE_MAX ? `${clean.slice(0, TITLE_MAX - 1).trimEnd()}\u2026` : clean;
20745
20868
  };
20746
- keepAt = (file) => path60.join(SIGNALS_FILES, fileName2(file.name, file.data));
20869
+ keepAt = (file) => path62.join(SIGNALS_FILES, fileName2(file.name, file.data));
20747
20870
  }
20748
20871
  });
20749
20872
 
20750
20873
  // ../cli/src/lib/recurring.ts
20751
- import fs56 from "node:fs";
20752
- import path61 from "node:path";
20874
+ import fs57 from "node:fs";
20875
+ import path63 from "node:path";
20753
20876
  function migratePruneMemoryCard() {
20754
20877
  const dir = recurringDir();
20755
20878
  let names4;
20756
20879
  try {
20757
- names4 = fs56.readdirSync(dir);
20880
+ names4 = fs57.readdirSync(dir);
20758
20881
  } catch {
20759
20882
  return null;
20760
20883
  }
20761
20884
  const name = names4.find((entry) => entry.endsWith(`-${PRUNE_SLUG}.md`));
20762
20885
  if (!name) return null;
20763
- const file = path61.join(dir, name);
20886
+ const file = path63.join(dir, name);
20764
20887
  try {
20765
- const { meta } = parseFrontmatter(fs56.readFileSync(file, "utf8"));
20888
+ const { meta } = parseFrontmatter(fs57.readFileSync(file, "utf8"));
20766
20889
  const cadence = meta?.cadence;
20767
20890
  if (typeof cadence === "string") adoptMemoryPruneCadence(cadence);
20768
20891
  } catch {
20769
20892
  }
20770
- fs56.rmSync(file, { force: true });
20893
+ fs57.rmSync(file, { force: true });
20771
20894
  return rel(file);
20772
20895
  }
20773
20896
  function legacyCards() {
20774
20897
  const dir = recurringDir();
20775
- if (!fs56.existsSync(dir)) return [];
20898
+ if (!fs57.existsSync(dir)) return [];
20776
20899
  const cards = [];
20777
- for (const entry of fs56.readdirSync(dir, { withFileTypes: true })) {
20900
+ for (const entry of fs57.readdirSync(dir, { withFileTypes: true })) {
20778
20901
  const id = idPrefix(entry.name);
20779
20902
  if (id === null) continue;
20780
- const full = path61.join(dir, entry.name);
20903
+ const full = path63.join(dir, entry.name);
20781
20904
  const slug2 = entry.name.replace(/\.md$/, "").replace(/^\d+-?/, "");
20782
20905
  if (entry.isFile() && entry.name.endsWith(".md")) cards.push({ id, slug: slug2, file: full });
20783
- else if (entry.isDirectory() && fs56.existsSync(path61.join(full, "root.md"))) {
20784
- cards.push({ id, slug: slug2, file: path61.join(full, "root.md"), folder: full });
20906
+ else if (entry.isDirectory() && fs57.existsSync(path63.join(full, "root.md"))) {
20907
+ cards.push({ id, slug: slug2, file: path63.join(full, "root.md"), folder: full });
20785
20908
  }
20786
20909
  }
20787
20910
  return cards.sort((a, b) => a.id - b.id);
20788
20911
  }
20789
20912
  function mergeInto2(from, to, skip = []) {
20790
- fs56.mkdirSync(to, { recursive: true });
20791
- for (const entry of fs56.readdirSync(from, { withFileTypes: true })) {
20913
+ fs57.mkdirSync(to, { recursive: true });
20914
+ for (const entry of fs57.readdirSync(from, { withFileTypes: true })) {
20792
20915
  if (skip.includes(entry.name)) continue;
20793
- const source = path61.join(from, entry.name);
20794
- const target = path61.join(to, entry.name);
20795
- if (!fs56.existsSync(target)) fs56.renameSync(source, target);
20796
- else if (entry.isDirectory() && fs56.statSync(target).isDirectory()) mergeInto2(source, target);
20916
+ const source = path63.join(from, entry.name);
20917
+ const target = path63.join(to, entry.name);
20918
+ if (!fs57.existsSync(target)) fs57.renameSync(source, target);
20919
+ else if (entry.isDirectory() && fs57.statSync(target).isDirectory()) mergeInto2(source, target);
20797
20920
  }
20798
- if (isEmpty(from)) fs56.rmSync(from, { recursive: true, force: true });
20921
+ if (isEmpty(from)) fs57.rmSync(from, { recursive: true, force: true });
20799
20922
  }
20800
20923
  function moveSideFolders() {
20801
20924
  const dir = recurringDir();
20802
- for (const entry of fs56.readdirSync(dir, { withFileTypes: true })) {
20925
+ for (const entry of fs57.readdirSync(dir, { withFileTypes: true })) {
20803
20926
  if (!entry.isDirectory() || idPrefix(entry.name) !== null) continue;
20804
- const from = path61.join(dir, entry.name);
20805
- if (isEmpty(from)) fs56.rmSync(from, { recursive: true, force: true });
20806
- else mergeInto2(from, path61.join(AGENTS, entry.name));
20927
+ const from = path63.join(dir, entry.name);
20928
+ if (isEmpty(from)) fs57.rmSync(from, { recursive: true, force: true });
20929
+ else mergeInto2(from, path63.join(AGENTS, entry.name));
20807
20930
  }
20808
20931
  }
20809
20932
  function sections(body) {
@@ -20853,8 +20976,8 @@ function agentName(card, text5) {
20853
20976
  const known2 = /* @__PURE__ */ new Set([...specAgentCatalog().agents.map((a) => a.name), ...ROLE_NAMES]);
20854
20977
  for (const name of [slug2, `${slug2}-${card.id}`]) {
20855
20978
  if (!AGENT_NAME.test(name)) continue;
20856
- const file = path61.join(AGENTS, name, AGENT_FILE3);
20857
- const written = fs56.existsSync(file) ? fs56.readFileSync(file, "utf8") : null;
20979
+ const file = path63.join(AGENTS, name, AGENT_FILE3);
20980
+ const written = fs57.existsSync(file) ? fs57.readFileSync(file, "utf8") : null;
20858
20981
  if (written === text5(name)) return name;
20859
20982
  if (written === null && !known2.has(name)) return name;
20860
20983
  }
@@ -20871,11 +20994,11 @@ ${q.options.map((o) => `- ${o}`).join("\n")}` : "";
20871
20994
  function removeCard(card) {
20872
20995
  dropCrossRefs(card.id);
20873
20996
  const target = card.folder ?? card.file;
20874
- stripReadmeRefs({ kind: card.folder ? "group" : "file", rel: path61.relative(TODO, target).split(path61.sep).join("/") });
20875
- fs56.rmSync(target, { recursive: true, force: true });
20997
+ stripReadmeRefs({ kind: card.folder ? "group" : "file", rel: path63.relative(TODO, target).split(path63.sep).join("/") });
20998
+ fs57.rmSync(target, { recursive: true, force: true });
20876
20999
  }
20877
21000
  function migrateCard(card) {
20878
- const { meta, body } = parseFrontmatter(fs56.readFileSync(card.file, "utf8"));
21001
+ const { meta, body } = parseFrontmatter(fs57.readFileSync(card.file, "utf8"));
20879
21002
  if (!meta) throw new Error("its frontmatter cannot be read");
20880
21003
  const raw = meta;
20881
21004
  const said3 = (key) => typeof raw[key] === "string" ? raw[key].trim() : "";
@@ -20885,19 +21008,19 @@ function migrateCard(card) {
20885
21008
  if (!flow) throw new Error("this board has no workflow to put it in");
20886
21009
  const text5 = (name2) => agentText(name2, meta.title || card.slug, body);
20887
21010
  const name = agentName(card, text5);
20888
- const dir = path61.join(AGENTS, name);
20889
- const file = path61.join(dir, AGENT_FILE3);
20890
- const madeDir = !fs56.existsSync(dir);
20891
- const wrote = !fs56.existsSync(file);
21011
+ const dir = path63.join(AGENTS, name);
21012
+ const file = path63.join(dir, AGENT_FILE3);
21013
+ const madeDir = !fs57.existsSync(dir);
21014
+ const wrote = !fs57.existsSync(file);
20892
21015
  try {
20893
- fs56.mkdirSync(dir, { recursive: true });
20894
- if (wrote) fs56.writeFileSync(file, text5(name));
21016
+ fs57.mkdirSync(dir, { recursive: true });
21017
+ if (wrote) fs57.writeFileSync(file, text5(name));
20895
21018
  const adopted = adoptWorkflowScheduled(flow.id, name, { cadence, lastRun });
20896
21019
  if (!adopted.ok) throw new Error(adopted.error);
20897
21020
  questionsToTriage(meta, name);
20898
21021
  } catch (e) {
20899
- if (madeDir) fs56.rmSync(dir, { recursive: true, force: true });
20900
- else if (wrote) fs56.rmSync(file, { force: true });
21022
+ if (madeDir) fs57.rmSync(dir, { recursive: true, force: true });
21023
+ else if (wrote) fs57.rmSync(file, { force: true });
20901
21024
  throw e;
20902
21025
  }
20903
21026
  if (card.folder) mergeInto2(card.folder, dir, ["root.md"]);
@@ -20907,7 +21030,7 @@ function migrateCard(card) {
20907
21030
  function migrateRecurringCards() {
20908
21031
  const done2 = { agents: [], removed: [], failed: [] };
20909
21032
  const dir = recurringDir();
20910
- if (!fs56.existsSync(dir)) return done2;
21033
+ if (!fs57.existsSync(dir)) return done2;
20911
21034
  migratePruneMemoryCard();
20912
21035
  moveSideFolders();
20913
21036
  for (const card of legacyCards()) {
@@ -20920,7 +21043,7 @@ function migrateRecurringCards() {
20920
21043
  done2.failed.push({ card: rel(card.file), why: e instanceof Error ? e.message : String(e) });
20921
21044
  }
20922
21045
  }
20923
- if (isEmpty(dir)) fs56.rmSync(dir, { recursive: true, force: true });
21046
+ if (isEmpty(dir)) fs57.rmSync(dir, { recursive: true, force: true });
20924
21047
  return done2;
20925
21048
  }
20926
21049
  function recurringMigrationLines(done2) {
@@ -20953,14 +21076,14 @@ var init_recurring = __esm({
20953
21076
  PRUNE_SLUG = "prune-the-memory";
20954
21077
  FETCH_SLUG = "fetch-triage-items";
20955
21078
  AGENT_FILE3 = "AGENT.md";
20956
- recurringDir = () => path61.join(TODO, LEGACY_RECURRING);
20957
- isEmpty = (dir) => fs56.readdirSync(dir).every((name) => name === ".DS_Store");
21079
+ recurringDir = () => path63.join(TODO, LEGACY_RECURRING);
21080
+ isEmpty = (dir) => fs57.readdirSync(dir).every((name) => name === ".DS_Store");
20958
21081
  SECTION = /^##\s+(.+?)\s*$/;
20959
21082
  }
20960
21083
  });
20961
21084
 
20962
21085
  // ../cli/src/commands/init.ts
20963
- import fs57 from "node:fs";
21086
+ import fs58 from "node:fs";
20964
21087
  function boardReadme() {
20965
21088
  return `# Board
20966
21089
 
@@ -20973,20 +21096,20 @@ _(none)_
20973
21096
  `;
20974
21097
  }
20975
21098
  function writeConfigIfMissing() {
20976
- if (fs57.existsSync(CONFIG)) return false;
20977
- fs57.mkdirSync(KANBAN, { recursive: true });
20978
- fs57.writeFileSync(CONFIG, CONFIG_TEMPLATE);
21099
+ if (fs58.existsSync(CONFIG)) return false;
21100
+ fs58.mkdirSync(KANBAN, { recursive: true });
21101
+ fs58.writeFileSync(CONFIG, CONFIG_TEMPLATE);
20979
21102
  return true;
20980
21103
  }
20981
21104
  function writeModulesIfMissing() {
20982
- if (fs57.existsSync(MODULES_MD)) return false;
20983
- fs57.mkdirSync(KANBAN, { recursive: true });
20984
- fs57.writeFileSync(MODULES_MD, MODULES_TEMPLATE);
21105
+ if (fs58.existsSync(MODULES_MD)) return false;
21106
+ fs58.mkdirSync(KANBAN, { recursive: true });
21107
+ fs58.writeFileSync(MODULES_MD, MODULES_TEMPLATE);
20985
21108
  return true;
20986
21109
  }
20987
21110
  function writeGitignoreIfMissing() {
20988
- fs57.mkdirSync(KANBAN, { recursive: true });
20989
- const text5 = fs57.existsSync(KANBAN_GITIGNORE) ? fs57.readFileSync(KANBAN_GITIGNORE, "utf8") : "";
21111
+ fs58.mkdirSync(KANBAN, { recursive: true });
21112
+ const text5 = fs58.existsSync(KANBAN_GITIGNORE) ? fs58.readFileSync(KANBAN_GITIGNORE, "utf8") : "";
20990
21113
  const has = (rule) => text5.split("\n").some((line) => line.trim() === rule.line);
20991
21114
  const missing = IGNORE_RULES.filter((rule) => !has(rule));
20992
21115
  if (!missing.length) return false;
@@ -20994,37 +21117,37 @@ function writeGitignoreIfMissing() {
20994
21117
  ${rule.line}
20995
21118
  `).join("");
20996
21119
  const separator = !text5 || text5.endsWith("\n") ? "" : "\n";
20997
- fs57.writeFileSync(KANBAN_GITIGNORE, `${text5}${separator}${block}`);
21120
+ fs58.writeFileSync(KANBAN_GITIGNORE, `${text5}${separator}${block}`);
20998
21121
  return true;
20999
21122
  }
21000
21123
  function writeSkeletonIfMissing() {
21001
21124
  const added = [];
21002
- if (!fs57.existsSync(TODO)) {
21003
- fs57.mkdirSync(TODO, { recursive: true });
21125
+ if (!fs58.existsSync(TODO)) {
21126
+ fs58.mkdirSync(TODO, { recursive: true });
21004
21127
  added.push(`${rel(TODO)}/`);
21005
21128
  }
21006
- if (!fs57.existsSync(README)) {
21007
- fs57.writeFileSync(README, boardReadme());
21129
+ if (!fs58.existsSync(README)) {
21130
+ fs58.writeFileSync(README, boardReadme());
21008
21131
  added.push(rel(README));
21009
21132
  }
21010
21133
  return added;
21011
21134
  }
21012
21135
  function retireReleases() {
21013
21136
  const done2 = [];
21014
- if (fs57.existsSync(RELEASES)) {
21015
- fs57.rmSync(RELEASES);
21137
+ if (fs58.existsSync(RELEASES)) {
21138
+ fs58.rmSync(RELEASES);
21016
21139
  done2.push(`deleted ${rel(RELEASES)}`);
21017
21140
  }
21018
21141
  let cards = 0;
21019
21142
  for (const dir of [TODO, ARCHIVE]) {
21020
- if (!fs57.existsSync(dir)) continue;
21143
+ if (!fs58.existsSync(dir)) continue;
21021
21144
  for (const file of walkMd(dir)) {
21022
- const text5 = fs57.readFileSync(file, "utf8");
21145
+ const text5 = fs58.readFileSync(file, "utf8");
21023
21146
  const end2 = text5.startsWith("---\n") ? text5.indexOf("\n---", 3) : -1;
21024
21147
  if (end2 < 0) continue;
21025
21148
  const head = text5.slice(0, end2).replace(/\nrelease:[^\n]*/, "");
21026
21149
  if (head.length === end2) continue;
21027
- fs57.writeFileSync(file, head + text5.slice(end2));
21150
+ fs58.writeFileSync(file, head + text5.slice(end2));
21028
21151
  cards++;
21029
21152
  }
21030
21153
  }
@@ -21032,7 +21155,7 @@ function retireReleases() {
21032
21155
  return done2.join(", ");
21033
21156
  }
21034
21157
  function cmdInit() {
21035
- if (fs57.existsSync(KANBAN)) {
21158
+ if (fs58.existsSync(KANBAN)) {
21036
21159
  const added = [
21037
21160
  ...writeSkeletonIfMissing(),
21038
21161
  writeConfigIfMissing() && rel(CONFIG),
@@ -21067,8 +21190,8 @@ function cmdInit() {
21067
21190
  }
21068
21191
  return { board: rel(KANBAN), created: false, added, memory_paths: scaffolded.map((s) => rel(s.dir)) };
21069
21192
  }
21070
- fs57.mkdirSync(TODO, { recursive: true });
21071
- fs57.writeFileSync(README, boardReadme());
21193
+ fs58.mkdirSync(TODO, { recursive: true });
21194
+ fs58.writeFileSync(README, boardReadme());
21072
21195
  writeConfigIfMissing();
21073
21196
  scaffoldProjectMemory();
21074
21197
  writeModulesIfMissing();
@@ -21157,25 +21280,25 @@ var init_scheduled = __esm({
21157
21280
  });
21158
21281
 
21159
21282
  // ../cli/src/lib/card-age.ts
21160
- import fs58 from "node:fs";
21161
- import path62 from "node:path";
21283
+ import fs59 from "node:fs";
21284
+ import path64 from "node:path";
21162
21285
  function staleAfterIn(config) {
21163
21286
  const days = Number(LINE.exec(config)?.[1]);
21164
21287
  return Number.isInteger(days) && days > 0 ? days : STALE_AFTER_DEFAULT;
21165
21288
  }
21166
21289
  function staleAfter() {
21167
- if (!fs58.existsSync(CONFIG)) return STALE_AFTER_DEFAULT;
21168
- return staleAfterIn(fs58.readFileSync(CONFIG, "utf8"));
21290
+ if (!fs59.existsSync(CONFIG)) return STALE_AFTER_DEFAULT;
21291
+ return staleAfterIn(fs59.readFileSync(CONFIG, "utf8"));
21169
21292
  }
21170
21293
  function todoPathspec(top) {
21171
- const from = path62.relative(real(top), real(TODO));
21172
- if (!from || from.startsWith("..") || path62.isAbsolute(from)) return null;
21173
- return from.split(path62.sep).join("/");
21294
+ const from = path64.relative(real(top), real(TODO));
21295
+ if (!from || from.startsWith("..") || path64.isAbsolute(from)) return null;
21296
+ return from.split(path64.sep).join("/");
21174
21297
  }
21175
21298
  function cardFile2(spec, printed) {
21176
21299
  const p = unquotePath(printed);
21177
21300
  if (!p.startsWith(`${spec}/`)) return null;
21178
- return path62.join(TODO, ...p.slice(spec.length + 1).split("/"));
21301
+ return path64.join(TODO, ...p.slice(spec.length + 1).split("/"));
21179
21302
  }
21180
21303
  function dirtyCards(top, spec) {
21181
21304
  const out = git(["status", "--porcelain", "--untracked-files=all", "--", spec], top);
@@ -21222,9 +21345,9 @@ var init_card_age = __esm({
21222
21345
  gitTop = () => git(["rev-parse", "--show-toplevel"])?.trim() || null;
21223
21346
  real = (p) => {
21224
21347
  try {
21225
- return fs58.realpathSync(p);
21348
+ return fs59.realpathSync(p);
21226
21349
  } catch {
21227
- return path62.resolve(p);
21350
+ return path64.resolve(p);
21228
21351
  }
21229
21352
  };
21230
21353
  unquotePath = (line) => {
@@ -21259,8 +21382,8 @@ var init_card_holds = __esm({
21259
21382
  });
21260
21383
 
21261
21384
  // ../cli/src/commands/list.ts
21262
- import fs59 from "node:fs";
21263
- import path63 from "node:path";
21385
+ import fs60 from "node:fs";
21386
+ import path65 from "node:path";
21264
21387
  function summaryLine(body) {
21265
21388
  const lines = body.split("\n");
21266
21389
  let i = 0;
@@ -21276,13 +21399,13 @@ function summaryLine(body) {
21276
21399
  function openRows() {
21277
21400
  const rows = [];
21278
21401
  for (const file of walkMd(TODO)) {
21279
- const base = path63.basename(file);
21402
+ const base = path65.basename(file);
21280
21403
  if (base === "README.md") continue;
21281
21404
  const isRoot = base === "root.md";
21282
- const id = isRoot ? idPrefix(path63.basename(path63.dirname(file))) : idPrefix(base);
21405
+ const id = isRoot ? idPrefix(path65.basename(path65.dirname(file))) : idPrefix(base);
21283
21406
  if (id == null) continue;
21284
- if (isLegacyRecurring(path63.relative(TODO, file))) continue;
21285
- const { meta, body } = parseFrontmatter(fs59.readFileSync(file, "utf8"));
21407
+ if (isLegacyRecurring(path65.relative(TODO, file))) continue;
21408
+ const { meta, body } = parseFrontmatter(fs60.readFileSync(file, "utf8"));
21286
21409
  rows.push({
21287
21410
  id,
21288
21411
  file,
@@ -21324,15 +21447,15 @@ function cmdStale(rows, all2, scope, mod) {
21324
21447
  }
21325
21448
  function archivedFiles() {
21326
21449
  const files2 = /* @__PURE__ */ new Map();
21327
- if (!fs59.existsSync(ARCHIVE)) return files2;
21450
+ if (!fs60.existsSync(ARCHIVE)) return files2;
21328
21451
  for (const file of walkMd(ARCHIVE)) {
21329
- const id = idPrefix(path63.basename(file));
21452
+ const id = idPrefix(path65.basename(file));
21330
21453
  if (id !== null) files2.set(id, file);
21331
21454
  }
21332
21455
  for (const dir of walkDirs(ARCHIVE)) {
21333
- const id = idPrefix(path63.basename(dir));
21334
- const root = path63.join(dir, "root.md");
21335
- if (id !== null && fs59.existsSync(root)) files2.set(id, root);
21456
+ const id = idPrefix(path65.basename(dir));
21457
+ const root = path65.join(dir, "root.md");
21458
+ if (id !== null && fs60.existsSync(root)) files2.set(id, root);
21336
21459
  }
21337
21460
  return files2;
21338
21461
  }
@@ -21443,14 +21566,14 @@ var init_list = __esm({
21443
21566
  });
21444
21567
 
21445
21568
  // ../cli/src/commands/misc.ts
21446
- import fs60 from "node:fs";
21447
- import path64 from "node:path";
21569
+ import fs61 from "node:fs";
21570
+ import path66 from "node:path";
21448
21571
  function extractOldMeta(text5, file) {
21449
21572
  const grab = (re) => {
21450
21573
  const m = text5.match(re);
21451
21574
  return m ? m[1].trim() : null;
21452
21575
  };
21453
- const title = grab(/^#\s+(.+)$/m) || slugify(path64.basename(file, ".md").replace(/^\d+-/, "")).replace(/-/g, " ");
21576
+ const title = grab(/^#\s+(.+)$/m) || slugify(path66.basename(file, ".md").replace(/^\d+-/, "")).replace(/-/g, " ");
21454
21577
  const norm = (raw) => {
21455
21578
  const v = (raw || "").toLowerCase();
21456
21579
  return LEVELS.includes(v) ? v : "med";
@@ -21477,11 +21600,11 @@ function stripOldHeader(text5) {
21477
21600
  }
21478
21601
  function cmdMigrate(opts) {
21479
21602
  const dry = opts.dryRun === true;
21480
- const files2 = walkMd(TODO).filter((f) => path64.basename(f) !== "README.md");
21603
+ const files2 = walkMd(TODO).filter((f) => path66.basename(f) !== "README.md");
21481
21604
  let changed = 0;
21482
21605
  let skipped = 0;
21483
21606
  for (const file of files2) {
21484
- const text5 = fs60.readFileSync(file, "utf8");
21607
+ const text5 = fs61.readFileSync(file, "utf8");
21485
21608
  if (text5.trimStart().startsWith("---")) {
21486
21609
  skipped++;
21487
21610
  continue;
@@ -21490,7 +21613,7 @@ function cmdMigrate(opts) {
21490
21613
  const out = serializeFrontmatter(meta) + "\n\n" + stripOldHeader(text5).replace(/^\n+/, "");
21491
21614
  if (dry) say(`would migrate ${rel(file)} (priority=${meta.priority})`);
21492
21615
  else {
21493
- fs60.writeFileSync(file, out.endsWith("\n") ? out : out + "\n");
21616
+ fs61.writeFileSync(file, out.endsWith("\n") ? out : out + "\n");
21494
21617
  say(`migrated ${rel(file)}`);
21495
21618
  }
21496
21619
  changed++;
@@ -21511,10 +21634,10 @@ var init_misc = __esm({
21511
21634
  });
21512
21635
 
21513
21636
  // ../cli/src/lib/group-close.ts
21514
- import fs61 from "node:fs";
21637
+ import fs62 from "node:fs";
21515
21638
  function groupCloses(rootFile) {
21516
21639
  try {
21517
- return groupCloseOf(fs61.readFileSync(rootFile, "utf8"));
21640
+ return groupCloseOf(fs62.readFileSync(rootFile, "utf8"));
21518
21641
  } catch {
21519
21642
  return false;
21520
21643
  }
@@ -21527,12 +21650,12 @@ var init_group_close = __esm({
21527
21650
  });
21528
21651
 
21529
21652
  // ../cli/src/commands/remove.ts
21530
- import fs62 from "node:fs";
21531
- import path65 from "node:path";
21653
+ import fs63 from "node:fs";
21654
+ import path67 from "node:path";
21532
21655
  function leavingIds(id, found2) {
21533
21656
  const ids = /* @__PURE__ */ new Set([id]);
21534
21657
  if (found2.kind === "group") {
21535
- const names4 = [...walkMd(found2.target), ...walkDirs(found2.target)].map((f) => path65.basename(f));
21658
+ const names4 = [...walkMd(found2.target), ...walkDirs(found2.target)].map((f) => path67.basename(f));
21536
21659
  for (const name of names4) {
21537
21660
  const n = idPrefix(name);
21538
21661
  if (n !== null) ids.add(n);
@@ -21544,10 +21667,10 @@ function findMentions(ids) {
21544
21667
  const hits = [];
21545
21668
  const re = new RegExp(ids.map((id) => `#${id}(?!\\d)`).join("|"));
21546
21669
  for (const dir of [TODO, MEMORY]) {
21547
- if (!fs62.existsSync(dir)) continue;
21670
+ if (!fs63.existsSync(dir)) continue;
21548
21671
  for (const file of walkMd(dir)) {
21549
- if (path65.basename(file) === "README.md") continue;
21550
- const lines = fs62.readFileSync(file, "utf8").split("\n");
21672
+ if (path67.basename(file) === "README.md") continue;
21673
+ const lines = fs63.readFileSync(file, "utf8").split("\n");
21551
21674
  const fmEnd = frontmatterEnd(lines);
21552
21675
  lines.forEach((line, i) => {
21553
21676
  if (!re.test(line)) return;
@@ -21563,12 +21686,12 @@ function findMentions(ids) {
21563
21686
  return hits;
21564
21687
  }
21565
21688
  function leavingCards(id, found2) {
21566
- const own = found2.kind === "group" ? path65.join(found2.target, "root.md") : found2.target;
21689
+ const own = found2.kind === "group" ? path67.join(found2.target, "root.md") : found2.target;
21567
21690
  const cards = [{ id, file: own }];
21568
21691
  if (found2.kind !== "group") return cards;
21569
21692
  for (const file of walkMd(found2.target)) {
21570
21693
  if (file === own) continue;
21571
- const n = idPrefix(path65.basename(file));
21694
+ const n = idPrefix(path67.basename(file));
21572
21695
  if (n !== null) cards.push({ id: n, file });
21573
21696
  }
21574
21697
  return cards;
@@ -21577,10 +21700,10 @@ function stampLeaving(cards, rejection) {
21577
21700
  const day = formatDay();
21578
21701
  const stamp4 = rejection && !rejection.discard ? { reason: rejection.reason, at: (/* @__PURE__ */ new Date()).toISOString(), by: insideRun() ? "agent" : "user" } : rejection && { reason: rejection.reason };
21579
21702
  for (const card of cards) {
21580
- if (!fs62.existsSync(card.file)) continue;
21581
- const text5 = fs62.readFileSync(card.file, "utf8");
21703
+ if (!fs63.existsSync(card.file)) continue;
21704
+ const text5 = fs63.readFileSync(card.file, "utf8");
21582
21705
  const stamped = stampLeavingIn(text5, day, stamp4);
21583
- if (stamped !== text5) fs62.writeFileSync(card.file, stamped);
21706
+ if (stamped !== text5) fs63.writeFileSync(card.file, stamped);
21584
21707
  }
21585
21708
  }
21586
21709
  function unfinishedDelivery(id) {
@@ -21596,7 +21719,7 @@ function unfinishedDelivery(id) {
21596
21719
  }[gap];
21597
21720
  return `#${id} is not finished: ${what}.`;
21598
21721
  }
21599
- const missing = planDeliverables(card.body).filter((src) => src.startsWith(".assets/")).find((src) => !fs62.existsSync(path65.join(ASSETS, src.slice(".assets/".length))));
21722
+ const missing = planDeliverables(card.body).filter((src) => src.startsWith(".assets/")).find((src) => !fs63.existsSync(path67.join(ASSETS, src.slice(".assets/".length))));
21600
21723
  return missing ? `#${id} is not finished: ${missing} is not on this machine.` : null;
21601
21724
  }
21602
21725
  function agentDecisions(text5) {
@@ -21623,8 +21746,8 @@ function removeCard2(id, metric, options) {
21623
21746
  const unfinished = metric === "completed" ? unfinishedDelivery(id) : null;
21624
21747
  if (unfinished) die(unfinished, { kind: "plan-delivery-unfinished" });
21625
21748
  const dest = options.cleanupDiscarded && locateArchived(id) ? null : archiveDest(found2);
21626
- const cardFile3 = found2.kind === "group" ? path65.join(found2.target, "root.md") : found2.target;
21627
- const cardText = fs62.existsSync(cardFile3) ? fs62.readFileSync(cardFile3, "utf8") : "";
21749
+ const cardFile3 = found2.kind === "group" ? path67.join(found2.target, "root.md") : found2.target;
21750
+ const cardText = fs63.existsSync(cardFile3) ? fs63.readFileSync(cardFile3, "utf8") : "";
21628
21751
  const { meta: cardMeta3 } = parseFrontmatter(cardText);
21629
21752
  const alsoRemoved = found2.kind === "group" ? walkMd(found2.target).filter((f) => f !== cardFile3).map((f) => rel(f)).sort() : [];
21630
21753
  const leftIds = leavingIds(id, found2);
@@ -21655,10 +21778,10 @@ function removeCard2(id, metric, options) {
21655
21778
  const groupRoot = found2.kind === "file" && !options.closing ? enclosingGroupRoot(found2.target) : null;
21656
21779
  if (dest) {
21657
21780
  stampLeaving(leaving, metric === "rejected" ? { reason: options.reason?.trim() ?? "", discard: options.discard === true } : null);
21658
- fs62.mkdirSync(ARCHIVE, { recursive: true });
21659
- fs62.renameSync(found2.target, dest);
21781
+ fs63.mkdirSync(ARCHIVE, { recursive: true });
21782
+ fs63.renameSync(found2.target, dest);
21660
21783
  } else {
21661
- fs62.rmSync(found2.target, { recursive: true, force: true });
21784
+ fs63.rmSync(found2.target, { recursive: true, force: true });
21662
21785
  }
21663
21786
  const removedRefs = stripReadmeRefs(found2);
21664
21787
  let marked2 = null;
@@ -21707,14 +21830,14 @@ function removeCard2(id, metric, options) {
21707
21830
  };
21708
21831
  }
21709
21832
  function closeGroup(rootFile) {
21710
- const rootId = idPrefix(path65.basename(path65.dirname(rootFile)));
21833
+ const rootId = idPrefix(path67.basename(path67.dirname(rootFile)));
21711
21834
  if (rootId === null) return null;
21712
21835
  if (runnerProject(KANBAN)) return null;
21713
21836
  if (!groupCloses(rootFile)) return null;
21714
21837
  say(`
21715
21838
  every subtask line on #${rootId} is resolved \u2014 closing the group:`);
21716
21839
  try {
21717
- const { meta, body } = parseFrontmatter(fs62.readFileSync(rootFile, "utf8"));
21840
+ const { meta, body } = parseFrontmatter(fs63.readFileSync(rootFile, "utf8"));
21718
21841
  const res = cmdRemove(rootId, "completed", { closing: true });
21719
21842
  return {
21720
21843
  id: rootId,
@@ -21771,7 +21894,7 @@ function cleanupDiscardedCards(sessionId) {
21771
21894
  if (card.pending) throw new Error(`Discarding #${card.id} did not finish. Retry the discard.`);
21772
21895
  while (locate(card.id)) cmdRemove(card.id, "rejected", { discard: true, cleanupDiscarded: true });
21773
21896
  dropCrossRefs(card.id);
21774
- stripReadmeRefs({ kind: "file", rel: path65.relative(TODO, path65.resolve(REPO_ROOT, card.path)).split(path65.sep).join("/") });
21897
+ stripReadmeRefs({ kind: "file", rel: path67.relative(TODO, path67.resolve(REPO_ROOT, card.path)).split(path67.sep).join("/") });
21775
21898
  }
21776
21899
  return discarded.map((c) => c.id);
21777
21900
  });
@@ -21857,8 +21980,8 @@ var init_memory_file = __esm({
21857
21980
  });
21858
21981
 
21859
21982
  // ../cli/src/commands/rule.ts
21860
- import fs63 from "node:fs";
21861
- import path66 from "node:path";
21983
+ import fs64 from "node:fs";
21984
+ import path68 from "node:path";
21862
21985
  function cmdRule(askedName, flags) {
21863
21986
  const agent = askedName.trim();
21864
21987
  if (!agentRoster().some((a) => a.name === agent)) {
@@ -21867,7 +21990,7 @@ function cmdRule(askedName, flags) {
21867
21990
  const rule = readRuleText(flags.file, flags.text);
21868
21991
  const res = setAgentRule(agent, rule);
21869
21992
  if (!res.ok) die(res.error);
21870
- const file = rel(path66.join(RULES, `${agent}.md`));
21993
+ const file = rel(path68.join(RULES, `${agent}.md`));
21871
21994
  say(rule ? `wrote the \`${agent}\` rule (${file})` : `cleared the \`${agent}\` rule`);
21872
21995
  return { agent, file, cleared: !rule };
21873
21996
  }
@@ -21875,7 +21998,7 @@ function readRuleText(file, text5) {
21875
21998
  if (file !== void 0 && text5 !== void 0) die("pass --file or --text, not both");
21876
21999
  if (file !== void 0) {
21877
22000
  try {
21878
- return fs63.readFileSync(file, "utf8").trim();
22001
+ return fs64.readFileSync(file, "utf8").trim();
21879
22002
  } catch {
21880
22003
  die(`can't read ${file} \u2014 write the rule to a file, then pass its path`);
21881
22004
  }
@@ -22002,22 +22125,22 @@ var init_storyboard = __esm({
22002
22125
  });
22003
22126
 
22004
22127
  // ../cli/src/lib/spec-contract.ts
22005
- import fs64 from "node:fs";
22006
- import path67 from "node:path";
22128
+ import fs65 from "node:fs";
22129
+ import path69 from "node:path";
22007
22130
  function snapshotSpecs() {
22008
22131
  const out = /* @__PURE__ */ new Map();
22009
- if (!fs64.existsSync(TODO)) return out;
22132
+ if (!fs65.existsSync(TODO)) return out;
22010
22133
  for (const file of walkMd(TODO)) {
22011
- if (isLegacyRecurring(path67.relative(TODO, file))) continue;
22012
- const id = idPrefix(path67.basename(file) === "root.md" ? path67.basename(path67.dirname(file)) : path67.basename(file));
22013
- if (id !== null) out.set(file, { id, text: fs64.readFileSync(file, "utf8") });
22134
+ if (isLegacyRecurring(path69.relative(TODO, file))) continue;
22135
+ const id = idPrefix(path69.basename(file) === "root.md" ? path69.basename(path69.dirname(file)) : path69.basename(file));
22136
+ if (id !== null) out.set(file, { id, text: fs65.readFileSync(file, "utf8") });
22014
22137
  }
22015
22138
  return out;
22016
22139
  }
22017
22140
  function enclosingGroups(file) {
22018
22141
  const ids = [];
22019
22142
  for (let root = enclosingGroupRoot(file); root; root = enclosingGroupRoot(root)) {
22020
- const id = idPrefix(path67.basename(path67.dirname(root)));
22143
+ const id = idPrefix(path69.basename(path69.dirname(root)));
22021
22144
  if (id !== null) ids.push(id);
22022
22145
  }
22023
22146
  return ids;
@@ -22186,9 +22309,9 @@ function validateSpec(file, text5, id) {
22186
22309
  }
22187
22310
  function inside(dir, name) {
22188
22311
  try {
22189
- const root = fs64.realpathSync(dir);
22190
- const real2 = fs64.realpathSync(path67.join(dir, name));
22191
- return real2.startsWith(root + path67.sep) && fs64.statSync(real2).isFile() ? real2 : null;
22312
+ const root = fs65.realpathSync(dir);
22313
+ const real2 = fs65.realpathSync(path69.join(dir, name));
22314
+ return real2.startsWith(root + path69.sep) && fs65.statSync(real2).isFile() ? real2 : null;
22192
22315
  } catch {
22193
22316
  return null;
22194
22317
  }
@@ -22206,7 +22329,7 @@ function variantsProblem(raw, src, id) {
22206
22329
  if (files2.length < 2 || !files2.every((f) => typeof f === "string" && f)) return `variants needs at least two labels, each naming a file. ${shape}`;
22207
22330
  if (!files2.includes(src)) return `src="${src}" is not one of the variants. Set src to the version shown first.`;
22208
22331
  if (id === void 0) return null;
22209
- const dir = path67.join(ASSETS, String(id));
22332
+ const dir = path69.join(ASSETS, String(id));
22210
22333
  for (const f of files2) {
22211
22334
  const parts = f.split("/");
22212
22335
  if (parts.length !== 3 || parts[0] !== ".assets" || parts[1] !== String(id) || !inside(dir, parts[2])) {
@@ -22216,7 +22339,7 @@ function variantsProblem(raw, src, id) {
22216
22339
  return null;
22217
22340
  }
22218
22341
  function compareProblem(attrs, id) {
22219
- const dir = path67.join(ASSETS, String(id));
22342
+ const dir = path69.join(ASSETS, String(id));
22220
22343
  const src = (message) => ({ rule: "compare-src", message });
22221
22344
  const shots = (message) => ({ rule: "compare-shots", message });
22222
22345
  for (const side of ["a", "b"]) {
@@ -22236,7 +22359,7 @@ function compareProblem(attrs, id) {
22236
22359
  const file = inside(dir, named2.name);
22237
22360
  if (!file) return { message: `Compare shots ${at} names no file in this card's asset folder. Write the JSON there or fix the path.` };
22238
22361
  try {
22239
- return { data: JSON.parse(fs64.readFileSync(file, "utf8")) };
22362
+ return { data: JSON.parse(fs65.readFileSync(file, "utf8")) };
22240
22363
  } catch {
22241
22364
  return { message: `${at} is not valid JSON.` };
22242
22365
  }
@@ -22256,7 +22379,7 @@ function compareProblem(attrs, id) {
22256
22379
  }
22257
22380
  function validateStoryboards(file, id, text5) {
22258
22381
  const errors = [];
22259
- const dir = path67.join(ASSETS, String(id));
22382
+ const dir = path69.join(ASSETS, String(id));
22260
22383
  for (const marker of storyboardMarkers(text5)) {
22261
22384
  const named2 = assetName(marker.src, id, ["json"]);
22262
22385
  const add = (message) => errors.push({ file: rel(file), line: marker.line, rule: "storyboard-src", message });
@@ -22405,8 +22528,8 @@ var init_mockups = __esm({
22405
22528
  });
22406
22529
 
22407
22530
  // ../cli/src/commands/spec-write.ts
22408
- import fs65 from "node:fs";
22409
- import path68 from "node:path";
22531
+ import fs66 from "node:fs";
22532
+ import path70 from "node:path";
22410
22533
  function cmdSpecWrite(id, askedName, flags) {
22411
22534
  const agent = findSpecAgent(askedName);
22412
22535
  if (!agent) die(notAnAgent(askedName), { kind: "no-such-spec-agent", specAgent: askedName });
@@ -22414,11 +22537,11 @@ function cmdSpecWrite(id, askedName, flags) {
22414
22537
  const section = readSection(flags.file, flags.text);
22415
22538
  const found2 = locate(id);
22416
22539
  if (!found2) die(`no task with id ${id} under ${rel(TODO)}`, { kind: "card-not-found", id });
22417
- const file = found2.kind === "group" ? path68.join(found2.target, "root.md") : found2.target;
22418
- const { meta, body } = parseFrontmatter(fs65.readFileSync(file, "utf8"));
22540
+ const file = found2.kind === "group" ? path70.join(found2.target, "root.md") : found2.target;
22541
+ const { meta, body } = parseFrontmatter(fs66.readFileSync(file, "utf8"));
22419
22542
  if (!meta) die(`${rel(file)} has no frontmatter \u2014 run \`migrate\` first`);
22420
22543
  const { body: next, replaced } = splice(body, name, section);
22421
- fs65.writeFileSync(file, serializeFrontmatter(meta) + "\n" + next);
22544
+ fs66.writeFileSync(file, serializeFrontmatter(meta) + "\n" + next);
22422
22545
  say(`${replaced ? "rewrote" : "wrote"} the \`${name}\` section on #${id} (${rel(file)})`);
22423
22546
  return { id, specAgent: name, replaced, file: rel(file) };
22424
22547
  }
@@ -22427,7 +22550,7 @@ function readSection(file, text5) {
22427
22550
  let raw;
22428
22551
  if (file !== void 0) {
22429
22552
  try {
22430
- raw = fs65.readFileSync(file, "utf8");
22553
+ raw = fs66.readFileSync(file, "utf8");
22431
22554
  } catch {
22432
22555
  die(`can't read ${file} \u2014 write your section to a file, then pass its path`);
22433
22556
  }
@@ -22621,7 +22744,7 @@ function localBoard() {
22621
22744
  const no = checkWrite({ board: true }, env2, boardRevision());
22622
22745
  if (no) return no;
22623
22746
  const res = await resumeDelivery(deliveryId);
22624
- if (!res.ok) return opRefused(new Error(res.error || "the delivery could not be carried on"));
22747
+ if (!res.ok) return { ...opRefused(new Error(res.error || "the delivery could not be carried on")), reason: res.reason, args: res.args };
22625
22748
  return opOk(boardRevision(), { deliveryId: res.deliveryId, landed: res.landed, carryOn: res.carryOn });
22626
22749
  },
22627
22750
  async discardDelivery(deliveryId, env2) {
@@ -22893,11 +23016,11 @@ var init_shared = __esm({
22893
23016
  });
22894
23017
 
22895
23018
  // ../cli/src/commands/goal.ts
22896
- import fs66 from "node:fs";
23019
+ import fs67 from "node:fs";
22897
23020
  function load() {
22898
23021
  const file = goalsFile();
22899
23022
  if (!file) die("Goals exist only inside a WildCrane goal discussion.");
22900
- const held4 = JSON.parse(fs66.readFileSync(file, "utf8"));
23023
+ const held4 = JSON.parse(fs67.readFileSync(file, "utf8"));
22901
23024
  if (held4.goals !== void 0 && held4.ideas !== void 0 && JSON.stringify(held4.goals) !== JSON.stringify(held4.ideas)) die("goals and ideas do not match.");
22902
23025
  const goals = held4.goals ?? held4.ideas;
22903
23026
  if (!Array.isArray(goals)) die("The goals array is missing or invalid.");
@@ -22922,7 +23045,7 @@ function splitBody2(text5) {
22922
23045
  }
22923
23046
  function readBody(bodyFile) {
22924
23047
  if (!bodyFile) die("--body-file is required; write the goal in a writable temporary file first.");
22925
- const text5 = fs66.readFileSync(bodyFile, "utf8");
23048
+ const text5 = fs67.readFileSync(bodyFile, "utf8");
22926
23049
  if (!text5.trim()) die("The goal must not be empty.");
22927
23050
  return splitBody2(text5);
22928
23051
  }
@@ -22934,7 +23057,7 @@ function named(held4, id) {
22934
23057
  function cmdGoal(args, opts) {
22935
23058
  const sub = args[0] ?? "";
22936
23059
  const { file, held: held4 } = load();
22937
- const write2 = () => fs66.writeFileSync(file, `${JSON.stringify({ workflows: held4.workflows, ideas: held4.goals }, null, 2)}
23060
+ const write2 = () => fs67.writeFileSync(file, `${JSON.stringify({ workflows: held4.workflows, ideas: held4.goals }, null, 2)}
22938
23061
  `);
22939
23062
  if (sub === "new") {
22940
23063
  const title = opts.title?.trim();
@@ -23004,8 +23127,8 @@ __export(board_exports, {
23004
23127
  buildBoardProgram: () => buildBoardProgram
23005
23128
  });
23006
23129
  import crypto4 from "node:crypto";
23007
- import fs67 from "node:fs";
23008
- import path69 from "node:path";
23130
+ import fs68 from "node:fs";
23131
+ import path71 from "node:path";
23009
23132
  async function dispatch(move2, cmd, args, opts, cli) {
23010
23133
  const ctx = ctxOf(cmd, cli.program);
23011
23134
  const data = await runAction(ctx, {}, async () => {
@@ -23044,7 +23167,7 @@ async function cloudCard(task, opts) {
23044
23167
  let request;
23045
23168
  if (typeof opts.bodyFile === "string") {
23046
23169
  try {
23047
- request = fs67.readFileSync(opts.bodyFile, "utf8");
23170
+ request = fs68.readFileSync(opts.bodyFile, "utf8");
23048
23171
  } catch {
23049
23172
  }
23050
23173
  }
@@ -23074,7 +23197,7 @@ async function cloudCard(task, opts) {
23074
23197
  const file = creationReceipt(`${checked.value}:${requestId}`, slot);
23075
23198
  const journal = readCreation(file, said3);
23076
23199
  if (!journal.made) {
23077
- const key = `akb-${path69.basename(file, ".json").slice(0, 40)}`;
23200
+ const key = `akb-${path71.basename(file, ".json").slice(0, 40)}`;
23078
23201
  const made = await createProjectTask(checked.value, journal.said, key);
23079
23202
  if (!made.ok) throw new BoardError(creationError(`Cloud did not make the task: ${made.error}`, triage), { kind: "refused" });
23080
23203
  journal.made = made.value;
@@ -23219,16 +23342,16 @@ var init_board2 = __esm({
23219
23342
  });
23220
23343
 
23221
23344
  // ../cli/src/lib/board-cli.ts
23222
- import fs68 from "node:fs";
23223
- import path70 from "node:path";
23345
+ import fs69 from "node:fs";
23346
+ import path72 from "node:path";
23224
23347
  function useBoard(found2, dirNamed) {
23225
- if (!found2.named && found2.board === path70.join(found2.root, "docs", "kanban")) setBoardRoot(found2.root, dirNamed);
23348
+ if (!found2.named && found2.board === path72.join(found2.root, "docs", "kanban")) setBoardRoot(found2.root, dirNamed);
23226
23349
  else setBoardDir(found2.board, found2.root);
23227
23350
  useProjectState();
23228
23351
  }
23229
23352
  function whole(found2, move2) {
23230
23353
  if (move2 === MAKES_A_BOARD) return found2;
23231
- if (!fs68.existsSync(path70.join(found2.board, "todo"))) {
23354
+ if (!fs69.existsSync(path72.join(found2.board, "todo"))) {
23232
23355
  die(`the board in ${found2.board} has no todo/ \u2014 run \`init\` to add what is missing.`, {
23233
23356
  kind: "board-incomplete",
23234
23357
  dir: found2.root
@@ -23237,13 +23360,13 @@ function whole(found2, move2) {
23237
23360
  return found2;
23238
23361
  }
23239
23362
  function findBoardUpward(from, move2) {
23240
- let dir = path70.resolve(from);
23363
+ let dir = path72.resolve(from);
23241
23364
  for (; ; ) {
23242
- if (hasBoard(dir)) return { root: dir, board: path70.join(dir, "docs", "kanban") };
23365
+ if (hasBoard(dir)) return { root: dir, board: path72.join(dir, "docs", "kanban") };
23243
23366
  if (isBoardDir(dir)) return { root: projectRootOf(dir), board: dir };
23244
- const kept2 = path70.join(dir, ".akb", "kanban");
23245
- if (move2 !== MAKES_A_BOARD && fs68.existsSync(path70.join(kept2, "todo"))) return { root: dir, board: kept2 };
23246
- const up = path70.dirname(dir);
23367
+ const kept2 = path72.join(dir, ".akb", "kanban");
23368
+ if (move2 !== MAKES_A_BOARD && fs69.existsSync(path72.join(kept2, "todo"))) return { root: dir, board: kept2 };
23369
+ const up = path72.dirname(dir);
23247
23370
  if (up === dir) return null;
23248
23371
  dir = up;
23249
23372
  }
@@ -23251,11 +23374,11 @@ function findBoardUpward(from, move2) {
23251
23374
  function resolveBoard(move2, { board: board2, dir, cwd, installHint }) {
23252
23375
  const named2 = board2 ?? boardFromEnv();
23253
23376
  if (named2) {
23254
- const folder = path70.resolve(cwd, named2);
23255
- if (!fs68.existsSync(folder) && move2 !== MAKES_A_BOARD) {
23377
+ const folder = path72.resolve(cwd, named2);
23378
+ if (!fs69.existsSync(folder) && move2 !== MAKES_A_BOARD) {
23256
23379
  die(`no such folder: ${folder}`, { kind: "no-such-folder", dir: folder });
23257
23380
  }
23258
- if (fs68.existsSync(folder) && !isBoardDir(folder) && move2 !== MAKES_A_BOARD) {
23381
+ if (fs69.existsSync(folder) && !isBoardDir(folder) && move2 !== MAKES_A_BOARD) {
23259
23382
  die(`${folder} is not a board \u2014 it has no todo/ and config.md. ${installHint}\u3002`, {
23260
23383
  kind: "no-board",
23261
23384
  dir: folder
@@ -23264,24 +23387,24 @@ function resolveBoard(move2, { board: board2, dir, cwd, installHint }) {
23264
23387
  return { root: projectRootOf(folder), board: folder, named: true };
23265
23388
  }
23266
23389
  if (dir !== null) {
23267
- const root = path70.resolve(dir);
23268
- if (!fs68.existsSync(root)) die(`no such folder: ${root}`, { kind: "no-such-folder", dir: root });
23269
- const kept2 = path70.join(root, ".akb", "kanban");
23270
- if (!hasBoard(root) && move2 !== MAKES_A_BOARD && fs68.existsSync(path70.join(kept2, "todo"))) return whole({ root, board: kept2 }, move2);
23390
+ const root = path72.resolve(dir);
23391
+ if (!fs69.existsSync(root)) die(`no such folder: ${root}`, { kind: "no-such-folder", dir: root });
23392
+ const kept2 = path72.join(root, ".akb", "kanban");
23393
+ if (!hasBoard(root) && move2 !== MAKES_A_BOARD && fs69.existsSync(path72.join(kept2, "todo"))) return whole({ root, board: kept2 }, move2);
23271
23394
  if (!hasBoard(root) && move2 !== MAKES_A_BOARD) {
23272
23395
  die(`no board in ${root} \u2014 it has no docs/kanban/. ${installHint}\u3002`, {
23273
23396
  kind: "no-board",
23274
23397
  dir: root
23275
23398
  });
23276
23399
  }
23277
- return whole({ root, board: path70.join(root, "docs", "kanban") }, move2);
23400
+ return whole({ root, board: path72.join(root, "docs", "kanban") }, move2);
23278
23401
  }
23279
23402
  const found2 = findBoardUpward(cwd, move2);
23280
23403
  if (found2) return whole(found2, move2);
23281
- if (move2 === MAKES_A_BOARD) return { root: path70.resolve(cwd), board: path70.join(path70.resolve(cwd), "docs", "kanban") };
23404
+ if (move2 === MAKES_A_BOARD) return { root: path72.resolve(cwd), board: path72.join(path72.resolve(cwd), "docs", "kanban") };
23282
23405
  die(
23283
- `no board here. Looked in ${path70.resolve(cwd)} and every folder above it for docs/kanban/. ${installHint}\uFF0C\u6216\u7528 --board \u6307\u5B9A\u5DF2\u6709\u770B\u677F\u3002`,
23284
- { kind: "no-board", dir: path70.resolve(cwd) }
23406
+ `no board here. Looked in ${path72.resolve(cwd)} and every folder above it for docs/kanban/. ${installHint}\uFF0C\u6216\u7528 --board \u6307\u5B9A\u5DF2\u6709\u770B\u677F\u3002`,
23407
+ { kind: "no-board", dir: path72.resolve(cwd) }
23285
23408
  );
23286
23409
  }
23287
23410
  function editDistance(a, b) {
@@ -23363,8 +23486,8 @@ var init_board_cli = __esm({
23363
23486
  init_io();
23364
23487
  init_pointer();
23365
23488
  MAKES_A_BOARD = "init";
23366
- hasBoard = (dir) => fs68.existsSync(path70.join(dir, "docs", "kanban"));
23367
- isBoardDir = (dir) => fs68.existsSync(path70.join(dir, "todo")) && fs68.existsSync(path70.join(dir, "config.md"));
23489
+ hasBoard = (dir) => fs69.existsSync(path72.join(dir, "docs", "kanban"));
23490
+ isBoardDir = (dir) => fs69.existsSync(path72.join(dir, "todo")) && fs69.existsSync(path72.join(dir, "config.md"));
23368
23491
  boardFromEnv = () => {
23369
23492
  const named2 = process.env.AI4KANBAN_BOARD?.trim();
23370
23493
  return named2 ? named2 : null;
@@ -23631,8 +23754,8 @@ var init_guide = __esm({
23631
23754
  });
23632
23755
 
23633
23756
  // ../cli/src/lib/agent/approve.ts
23634
- import fs74 from "node:fs";
23635
- import path76 from "node:path";
23757
+ import fs75 from "node:fs";
23758
+ import path78 from "node:path";
23636
23759
  function approvalRequest(req) {
23637
23760
  const id = req.id;
23638
23761
  const card = id === void 0 ? null : findCard(id);
@@ -23650,12 +23773,12 @@ function advancePhase(id) {
23650
23773
  const card = findCard(id);
23651
23774
  const found2 = locate(id);
23652
23775
  if (!card || !found2 || !awaitingApproval(card)) return { error: `#${id} is no longer waiting on approval.` };
23653
- const file = found2.kind === "group" ? path76.join(found2.target, "root.md") : found2.target;
23654
- const { meta, body } = parseFrontmatter(fs74.readFileSync(file, "utf8"));
23776
+ const file = found2.kind === "group" ? path78.join(found2.target, "root.md") : found2.target;
23777
+ const { meta, body } = parseFrontmatter(fs75.readFileSync(file, "utf8"));
23655
23778
  if (!meta) return { error: `#${id} has no frontmatter` };
23656
23779
  meta.phase = card.stage + 1;
23657
23780
  meta.status = "todo";
23658
- fs74.writeFileSync(file, serializeFrontmatter(meta) + "\n" + body);
23781
+ fs75.writeFileSync(file, serializeFrontmatter(meta) + "\n" + body);
23659
23782
  return null;
23660
23783
  }
23661
23784
  var init_approve = __esm({
@@ -23759,8 +23882,8 @@ function open2(req, sessionId) {
23759
23882
  markSpawned(run2.sessionId, pid);
23760
23883
  return { run: run2, spawned: pid !== void 0 };
23761
23884
  }
23762
- async function startResume(id, build) {
23763
- const opened = await openResume(id, build);
23885
+ async function startResume(id, options) {
23886
+ const opened = await openResume(id, options);
23764
23887
  if ("error" in opened) return opened;
23765
23888
  const pid = spawnWatcher(opened.run.sessionId);
23766
23889
  markSpawned(opened.run.sessionId, pid);
@@ -23786,16 +23909,16 @@ var init_start = __esm({
23786
23909
  });
23787
23910
 
23788
23911
  // ../cli/src/lib/agent/task-chats.ts
23789
- import fs75 from "node:fs";
23790
- import path77 from "node:path";
23912
+ import fs76 from "node:fs";
23913
+ import path79 from "node:path";
23791
23914
  function taskChatFile(task, home3) {
23792
23915
  if (!TASK.test(task)) throw new Error(`${task} is not a task id.`);
23793
- return path77.join(taskChatsDir(home3), `${task}.jsonl`);
23916
+ return path79.join(taskChatsDir(home3), `${task}.jsonl`);
23794
23917
  }
23795
23918
  function readTaskChat(task, home3) {
23796
23919
  let text5;
23797
23920
  try {
23798
- text5 = fs75.readFileSync(taskChatFile(task, home3), "utf8");
23921
+ text5 = fs76.readFileSync(taskChatFile(task, home3), "utf8");
23799
23922
  } catch {
23800
23923
  return [];
23801
23924
  }
@@ -23816,13 +23939,13 @@ var init_task_chats = __esm({
23816
23939
  "use strict";
23817
23940
  init_home();
23818
23941
  TASK = /^[\w-]{1,100}$/;
23819
- taskChatsDir = (home3 = machineHome()) => path77.join(home3, "chats");
23942
+ taskChatsDir = (home3 = machineHome()) => path79.join(home3, "chats");
23820
23943
  }
23821
23944
  });
23822
23945
 
23823
23946
  // ../cli/src/lib/agent/memory-review.ts
23824
- import fs76 from "node:fs";
23825
- import path78 from "node:path";
23947
+ import fs77 from "node:fs";
23948
+ import path80 from "node:path";
23826
23949
  function taskChatsToReview(tasks) {
23827
23950
  const out = [];
23828
23951
  for (const ref of [...new Set(tasks)]) {
@@ -23844,8 +23967,8 @@ function readArchived2(id) {
23844
23967
  const found2 = Number.isInteger(id) && id > 0 ? locateArchived(id) : null;
23845
23968
  if (!found2) return null;
23846
23969
  try {
23847
- const file = found2.kind === "group" ? path78.join(found2.target, "root.md") : found2.target;
23848
- const { meta, body } = parseFrontmatter(fs76.readFileSync(file, "utf8"));
23970
+ const file = found2.kind === "group" ? path80.join(found2.target, "root.md") : found2.target;
23971
+ const { meta, body } = parseFrontmatter(fs77.readFileSync(file, "utf8"));
23849
23972
  if (!meta) return null;
23850
23973
  return { id, title: meta.title, modules: meta.modules ?? [], rejected: meta.rejected, body };
23851
23974
  } catch {
@@ -23914,17 +24037,17 @@ var init_memory_review = __esm({
23914
24037
  });
23915
24038
 
23916
24039
  // ../cli/src/lib/agent/dismissal-review.ts
23917
- import fs77 from "node:fs";
23918
- import path79 from "node:path";
24040
+ import fs78 from "node:fs";
24041
+ import path81 from "node:path";
23919
24042
  function rejectionsToReview() {
23920
- const dir = path79.join(ARCHIVE, REJECTIONS_TO_REVIEW);
23921
- if (!fs77.existsSync(dir)) return [];
24043
+ const dir = path81.join(ARCHIVE, REJECTIONS_TO_REVIEW);
24044
+ if (!fs78.existsSync(dir)) return [];
23922
24045
  const found2 = [];
23923
24046
  for (const file of walkMd(dir)) {
23924
24047
  const id = archivedId(file);
23925
24048
  if (id === null) continue;
23926
24049
  try {
23927
- const { meta } = parseFrontmatter(fs77.readFileSync(file, "utf8"));
24050
+ const { meta } = parseFrontmatter(fs78.readFileSync(file, "utf8"));
23928
24051
  if (!meta?.rejected || meta.rejected_by !== "user" || !meta.rejected_reason.trim()) continue;
23929
24052
  found2.push({ id, title: meta.title, file: rel(file), reason: meta.rejected_reason.trim(), rejectedAt: meta.rejected_at });
23930
24053
  } catch {
@@ -23954,7 +24077,7 @@ function citedSources(text5) {
23954
24077
  function withdrawnSources() {
23955
24078
  const cited = [...new Set(plannerCopies("rejected.md").flatMap((file) => {
23956
24079
  try {
23957
- return citedSources(fs77.readFileSync(file, "utf8"));
24080
+ return citedSources(fs78.readFileSync(file, "utf8"));
23958
24081
  } catch {
23959
24082
  return [];
23960
24083
  }
@@ -23979,8 +24102,8 @@ var init_dismissal_review = __esm({
23979
24102
  });
23980
24103
 
23981
24104
  // ../cli/src/lib/agent/flow.ts
23982
- import fs78 from "node:fs";
23983
- import path80 from "node:path";
24105
+ import fs79 from "node:fs";
24106
+ import path82 from "node:path";
23984
24107
  function readCard2(id, home3 = "board") {
23985
24108
  const found2 = home3 === "archive" ? locateArchived(id) : locate(id);
23986
24109
  if (!found2) {
@@ -23989,10 +24112,10 @@ function readCard2(id, home3 = "board") {
23989
24112
  { kind: "card-not-found", id }
23990
24113
  );
23991
24114
  }
23992
- const file = found2.kind === "group" ? path80.join(found2.target, "root.md") : found2.target;
24115
+ const file = found2.kind === "group" ? path82.join(found2.target, "root.md") : found2.target;
23993
24116
  let text5;
23994
24117
  try {
23995
- text5 = fs78.readFileSync(file, "utf8");
24118
+ text5 = fs79.readFileSync(file, "utf8");
23996
24119
  } catch {
23997
24120
  die(`#${id} is on the board but ${rel(file)} can't be read.`, { kind: "card-unreadable", id });
23998
24121
  }
@@ -24116,7 +24239,7 @@ function workspaceField(delivery) {
24116
24239
  if (!delivery?.worktree) return [];
24117
24240
  return field("workspace", [
24118
24241
  `write code in ${delivery.worktree} \u2014 this delivery's own worktree, on branch ${delivery.branch}.`,
24119
- path80.resolve(process.cwd()).startsWith(worktreeDir(delivery.worktree)) ? `it is your working folder already; the board's own files are NOT in it and never go on that branch.` : `cd into it first \u2014 you are in the project checkout, and code written here is not part of this build. The board's own files are NOT in it and never go on that branch.`,
24242
+ path82.resolve(process.cwd()).startsWith(worktreeDir(delivery.worktree)) ? `it is your working folder already; the board's own files are NOT in it and never go on that branch.` : `cd into it first \u2014 you are in the project checkout, and code written here is not part of this build. The board's own files are NOT in it and never go on that branch.`,
24120
24243
  `every board command names the project's own copy: \`${boardCommandFor()} <command>\`.`,
24121
24244
  `${rel(REPO_ROOT)} is the project \u2014 the card, the memory files and the docs are changed there, not here.`
24122
24245
  ]);
@@ -24855,7 +24978,7 @@ __export(watch_exports, {
24855
24978
  watchRun: () => watchRun
24856
24979
  });
24857
24980
  import { spawn as spawn4 } from "node:child_process";
24858
- import fs79 from "node:fs";
24981
+ import fs80 from "node:fs";
24859
24982
  async function watchRun(sessionId, resume = startResume) {
24860
24983
  const spec = readSpec(sessionId);
24861
24984
  const run2 = peekRun(sessionId);
@@ -24891,11 +25014,11 @@ async function watchRun(sessionId, resume = startResume) {
24891
25014
  const record = claimed ?? run2;
24892
25015
  const sub = record.action === "sub";
24893
25016
  if (claim) await setCardStatus(claim.cardId, claim.status);
24894
- fs79.mkdirSync(SESSIONS_DIR, { recursive: true });
24895
- const logFile = fs79.createWriteStream(record.logPath, { flags: "a" });
25017
+ fs80.mkdirSync(SESSIONS_DIR, { recursive: true });
25018
+ const logFile = fs80.createWriteStream(record.logPath, { flags: "a" });
24896
25019
  logFile.on("error", () => {
24897
25020
  });
24898
- const eventsFile = fs79.createWriteStream(eventsPathOf(sessionId), { flags: "a" });
25021
+ const eventsFile = fs80.createWriteStream(eventsPathOf(sessionId), { flags: "a" });
24899
25022
  eventsFile.on("error", () => {
24900
25023
  });
24901
25024
  const event2 = (e) => eventsFile.write(`${JSON.stringify(e)}
@@ -25483,7 +25606,7 @@ var init_watch = __esm({
25483
25606
  });
25484
25607
 
25485
25608
  // ../cli/src/kanban.ts
25486
- import fs85 from "node:fs";
25609
+ import fs86 from "node:fs";
25487
25610
  import { fileURLToPath as fileURLToPath3 } from "node:url";
25488
25611
 
25489
25612
  // ../cli/src/lib/cli/agent.ts
@@ -25972,8 +26095,8 @@ async function testAgent(args) {
25972
26095
  }
25973
26096
 
25974
26097
  // ../cli/src/commands/cloud.ts
25975
- import fs70 from "node:fs";
25976
- import path72 from "node:path";
26098
+ import fs71 from "node:fs";
26099
+ import path74 from "node:path";
25977
26100
 
25978
26101
  // ../cli/src/lib/cloud/avatar.ts
25979
26102
  init_session();
@@ -26021,13 +26144,13 @@ init_config();
26021
26144
  init_home();
26022
26145
  init_config();
26023
26146
  init_session();
26024
- import fs69 from "node:fs";
26025
- import path71 from "node:path";
26147
+ import fs70 from "node:fs";
26148
+ import path73 from "node:path";
26026
26149
  var FRESH_MS = 10 * 6e4;
26027
26150
  var GRACE_MS = 7 * 24 * 60 * 6e4;
26028
- var heldFile = () => path71.join(machineHome(), "pro.json");
26151
+ var heldFile = () => path73.join(machineHome(), "pro.json");
26029
26152
  function forgetPro() {
26030
- fs69.rmSync(heldFile(), { force: true });
26153
+ fs70.rmSync(heldFile(), { force: true });
26031
26154
  }
26032
26155
 
26033
26156
  // ../cli/src/lib/cloud/account.ts
@@ -26204,10 +26327,10 @@ async function cloudImage(opts, program2) {
26204
26327
  const prompt = opts.prompt.trim();
26205
26328
  if (!prompt) usage("Missing the prompt.", line);
26206
26329
  const references = opts.refs.map((file) => {
26207
- const type = REF_TYPES[path72.extname(file).toLowerCase()];
26330
+ const type = REF_TYPES[path74.extname(file).toLowerCase()];
26208
26331
  if (!type) usage(`--ref ${file}: use a PNG, JPEG or WebP file.`, line);
26209
26332
  try {
26210
- return `data:${type};base64,${fs70.readFileSync(file).toString("base64")}`;
26333
+ return `data:${type};base64,${fs71.readFileSync(file).toString("base64")}`;
26211
26334
  } catch (e) {
26212
26335
  usage(`--ref ${file}: ${e instanceof Error ? e.message : String(e)}`, line);
26213
26336
  }
@@ -26313,31 +26436,31 @@ async function generate(g) {
26313
26436
  refuse(g.failed, g.fallback);
26314
26437
  }
26315
26438
  const out = g.outFor?.(res, g.out) ?? g.out;
26316
- fs70.mkdirSync(path72.dirname(path72.resolve(out)), { recursive: true });
26317
- fs70.writeFileSync(out, Buffer.from(await res.arrayBuffer()));
26439
+ fs71.mkdirSync(path74.dirname(path74.resolve(out)), { recursive: true });
26440
+ fs71.writeFileSync(out, Buffer.from(await res.arrayBuffer()));
26318
26441
  say(`${out} (${g.label(res)})`);
26319
26442
  return { out };
26320
26443
  }
26321
26444
 
26322
26445
  // ../cli/src/commands/cloud-import.ts
26323
26446
  import crypto5 from "node:crypto";
26324
- import fs72 from "node:fs";
26325
- import path74 from "node:path";
26447
+ import fs73 from "node:fs";
26448
+ import path76 from "node:path";
26326
26449
 
26327
26450
  // ../cloud/src/board-files.ts
26328
26451
  var ROOTS = ["todo/", ".archive/", "memory/", "workflows/", "triage/", "assets/", "rules/"];
26329
26452
  var SETTINGS = ["modules.md", "config.md"];
26330
- var isSettingsPath = (path86) => SETTINGS.includes(path86) || /^rules\/(?:workflows\/)?[^/]+\.md$/.test(path86);
26453
+ var isSettingsPath = (path88) => SETTINGS.includes(path88) || /^rules\/(?:workflows\/)?[^/]+\.md$/.test(path88);
26331
26454
  var BOARD_FILE_LIMIT = 256 * 1024;
26332
- function isBoardPath(path86) {
26333
- if (!path86 || path86.length > 500 || path86.startsWith("/") || path86.includes("\\") || path86.includes("\0")) return false;
26334
- if (path86.split("/").some((part) => !part || part === "." || part === "..")) return false;
26335
- if (path86 === "todo/README.md" || path86.split("/").includes(".sync")) return false;
26336
- if (path86.startsWith("workflows/")) return true;
26337
- if (SETTINGS.includes(path86)) return true;
26338
- if (path86.startsWith("rules/")) return isSettingsPath(path86);
26339
- if (path86.startsWith("assets/")) return path86.endsWith(".json");
26340
- return ROOTS.some((root) => path86.startsWith(root)) && path86.endsWith(".md");
26455
+ function isBoardPath(path88) {
26456
+ if (!path88 || path88.length > 500 || path88.startsWith("/") || path88.includes("\\") || path88.includes("\0")) return false;
26457
+ if (path88.split("/").some((part) => !part || part === "." || part === "..")) return false;
26458
+ if (path88 === "todo/README.md" || path88.split("/").includes(".sync")) return false;
26459
+ if (path88.startsWith("workflows/")) return true;
26460
+ if (SETTINGS.includes(path88)) return true;
26461
+ if (path88.startsWith("rules/")) return isSettingsPath(path88);
26462
+ if (path88.startsWith("assets/")) return path88.endsWith(".json");
26463
+ return ROOTS.some((root) => path88.startsWith(root)) && path88.endsWith(".md");
26341
26464
  }
26342
26465
  var workflowFolder = (id) => id.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
26343
26466
 
@@ -26441,8 +26564,8 @@ init_bundled();
26441
26564
  // ../cli/src/lib/agents/shared-memory.ts
26442
26565
  init_bundled();
26443
26566
  init_parse();
26444
- import fs71 from "node:fs";
26445
- import path73 from "node:path";
26567
+ import fs72 from "node:fs";
26568
+ import path75 from "node:path";
26446
26569
  function sharedMemory(board2) {
26447
26570
  const declared = /* @__PURE__ */ new Map();
26448
26571
  const take = (folder, text5, builtIn) => {
@@ -26454,16 +26577,16 @@ function sharedMemory(board2) {
26454
26577
  const at = /^([^/]+)\/AGENT\.md$/.exec(key);
26455
26578
  if (at) take(at[1], text5, true);
26456
26579
  }
26457
- const root = path73.join(board2, "agents");
26580
+ const root = path75.join(board2, "agents");
26458
26581
  let folders2 = [];
26459
26582
  try {
26460
- folders2 = fs71.readdirSync(root, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name);
26583
+ folders2 = fs72.readdirSync(root, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name);
26461
26584
  } catch {
26462
26585
  }
26463
26586
  for (const folder of folders2.sort()) {
26464
26587
  let text5 = null;
26465
26588
  try {
26466
- text5 = fs71.readFileSync(path73.join(root, folder, "AGENT.md"), "utf8");
26589
+ text5 = fs72.readFileSync(path75.join(root, folder, "AGENT.md"), "utf8");
26467
26590
  } catch {
26468
26591
  }
26469
26592
  take(folder, text5, false);
@@ -26552,24 +26675,24 @@ async function findProject(asked) {
26552
26675
  }
26553
26676
  throw new Refused(`No project of yours has the id or prefix "${asked}".`, "not_found");
26554
26677
  }
26555
- var read = (file) => fs72.readFileSync(file, "utf8");
26678
+ var read = (file) => fs73.readFileSync(file, "utf8");
26556
26679
  var bytes2 = (text5) => Buffer.byteLength(text5, "utf8");
26557
26680
  function files(dir) {
26558
- if (!fs72.existsSync(dir)) return [];
26681
+ if (!fs73.existsSync(dir)) return [];
26559
26682
  const out = [];
26560
- for (const entry of fs72.readdirSync(dir, { withFileTypes: true })) {
26683
+ for (const entry of fs73.readdirSync(dir, { withFileTypes: true })) {
26561
26684
  if (entry.name.startsWith(".")) continue;
26562
- const full = path74.join(dir, entry.name);
26685
+ const full = path76.join(dir, entry.name);
26563
26686
  if (entry.isDirectory()) out.push(...files(full));
26564
26687
  else if (entry.isFile()) out.push(full);
26565
26688
  }
26566
26689
  return out;
26567
26690
  }
26568
- var posix2 = (p) => p.split(path74.sep).join("/");
26691
+ var posix2 = (p) => p.split(path76.sep).join("/");
26569
26692
  function openCards() {
26570
26693
  const out = [];
26571
26694
  for (const file of walkMd(TODO)) {
26572
- const rel2 = posix2(path74.relative(TODO, file));
26695
+ const rel2 = posix2(path76.relative(TODO, file));
26573
26696
  if (rel2 === "README.md") continue;
26574
26697
  const parts = rel2.split("/");
26575
26698
  const text5 = read(file);
@@ -26603,14 +26726,14 @@ function bodyProblems(text5) {
26603
26726
  return out;
26604
26727
  }
26605
26728
  function packWorkflow(flow) {
26606
- const own = path74.join(WORKFLOWS, flow.id);
26729
+ const own = path76.join(WORKFLOWS, flow.id);
26607
26730
  const out = {};
26608
- for (const file of files(own)) out[posix2(path74.relative(own, file))] = read(file);
26731
+ for (const file of files(own)) out[posix2(path76.relative(own, file))] = read(file);
26609
26732
  const named2 = [...flow.previews, ...flow.land, ...flow.scheduled].map((h) => h.agent).filter((a) => !LAND_ROLES.includes(a));
26610
26733
  for (const agent of new Set(named2)) {
26611
- const dir = path74.join(AGENTS, agent);
26612
- if (fs72.existsSync(path74.join(dir, "AGENT.md"))) {
26613
- for (const file of files(dir)) out[`agents/${agent}/${posix2(path74.relative(dir, file))}`] = read(file);
26734
+ const dir = path76.join(AGENTS, agent);
26735
+ if (fs73.existsSync(path76.join(dir, "AGENT.md"))) {
26736
+ for (const file of files(dir)) out[`agents/${agent}/${posix2(path76.relative(dir, file))}`] = read(file);
26614
26737
  continue;
26615
26738
  }
26616
26739
  for (const [key, text5] of Object.entries(BUNDLED_AGENT_FILES)) {
@@ -26662,8 +26785,8 @@ function draftsOf(text5) {
26662
26785
  const at = /^\.(assets|mockups)\/(.+)$/.exec(name);
26663
26786
  if (!at) continue;
26664
26787
  const base = at[1] === "assets" ? ASSETS : MOCKUPS;
26665
- const file = path74.resolve(base, at[2]);
26666
- if (file.startsWith(base + path74.sep) && fs72.statSync(file, { throwIfNoEntry: false })?.isFile()) out.set(name, file);
26788
+ const file = path76.resolve(base, at[2]);
26789
+ if (file.startsWith(base + path76.sep) && fs73.statSync(file, { throwIfNoEntry: false })?.isFile()) out.set(name, file);
26667
26790
  }
26668
26791
  return [...out].map(([name, file]) => ({ name, file }));
26669
26792
  }
@@ -26697,9 +26820,9 @@ async function uploadDrafts(task, drafts) {
26697
26820
  const assets = {};
26698
26821
  for (const { name, file } of drafts) {
26699
26822
  if (!card.body.includes(name)) continue;
26700
- const ext = path74.extname(file).toLowerCase();
26823
+ const ext = path76.extname(file).toLowerCase();
26701
26824
  const type = TYPES[ext] ?? "application/octet-stream";
26702
- const data = fs72.readFileSync(file);
26825
+ const data = fs73.readFileSync(file);
26703
26826
  const artifact = await api("POST", `/v1/tasks/${task}/artifacts`, { type: kindOf(ext), name: name.slice(0, 200) });
26704
26827
  const upload = await api("POST", `/v1/artifacts/${artifact.id}/uploads`, { content_type: type, size: Math.max(data.length, 1) });
26705
26828
  await api("PUT", `/v1/uploads/${upload.id}`, void 0, { bytes: data, type });
@@ -26742,10 +26865,10 @@ usage: ${program2} cloud import --project <id or prefix> [--dry-run]`, { kind: "
26742
26865
  async function run(asked, dryRun) {
26743
26866
  const project = await findProject(asked);
26744
26867
  const cloudFlows = await all(`/v1/workflows?workspace=${encodeURIComponent(project.workspace)}`);
26745
- const board2 = crypto5.createHash("sha256").update(fs72.realpathSync(KANBAN)).digest("hex").slice(0, 16);
26868
+ const board2 = crypto5.createHash("sha256").update(fs73.realpathSync(KANBAN)).digest("hex").slice(0, 16);
26746
26869
  const cards = openCards();
26747
26870
  const problems = [];
26748
- const where = (file) => posix2(path74.relative(KANBAN, file));
26871
+ const where = (file) => posix2(path76.relative(KANBAN, file));
26749
26872
  const own = workflows().filter((w) => !w.builtIn);
26750
26873
  const packed = /* @__PURE__ */ new Map();
26751
26874
  for (const flow of own) {
@@ -26772,7 +26895,7 @@ async function run(asked, dryRun) {
26772
26895
  const root = `projects/${project.id}/`;
26773
26896
  const boardFiles = [];
26774
26897
  for (const file of [...files(MEMORY), ...files(RULES), CONFIG, MODULES_MD]) {
26775
- if (!fs72.existsSync(file)) continue;
26898
+ if (!fs73.existsSync(file)) continue;
26776
26899
  const rel2 = where(file);
26777
26900
  if (!isBoardPath(rel2)) continue;
26778
26901
  const content = read(file);
@@ -26883,16 +27006,16 @@ async function run(asked, dryRun) {
26883
27006
  }
26884
27007
  function leftLocal(own) {
26885
27008
  const out = [];
26886
- const count4 = (dir) => fs72.existsSync(dir) ? walkMd(dir).length : 0;
27009
+ const count4 = (dir) => fs73.existsSync(dir) ? walkMd(dir).length : 0;
26887
27010
  const archived = count4(ARCHIVE);
26888
27011
  if (archived) out.push(`.archive/ (${archived})`);
26889
27012
  const triage = count4(TRIAGE);
26890
27013
  if (triage) out.push(`triage/ (${triage})`);
26891
- if (fs72.existsSync(DELIVERIES)) out.push("deliveries/");
26892
- if (fs72.existsSync(path74.join(KANBAN, "metrics.csv"))) out.push("metrics.csv");
27014
+ if (fs73.existsSync(DELIVERIES)) out.push("deliveries/");
27015
+ if (fs73.existsSync(path76.join(KANBAN, "metrics.csv"))) out.push("metrics.csv");
26893
27016
  const named2 = new Set(own.flatMap((w) => [...w.previews, ...w.land, ...w.scheduled].map((h) => h.agent)));
26894
- if (fs72.existsSync(AGENTS)) {
26895
- for (const e of fs72.readdirSync(AGENTS, { withFileTypes: true })) {
27017
+ if (fs73.existsSync(AGENTS)) {
27018
+ for (const e of fs73.readdirSync(AGENTS, { withFileTypes: true })) {
26896
27019
  if (e.isDirectory() && !e.name.startsWith(".") && !named2.has(e.name)) out.push(`agents/${e.name}/`);
26897
27020
  }
26898
27021
  }
@@ -26910,8 +27033,8 @@ init_session();
26910
27033
  init_io();
26911
27034
  init_paths();
26912
27035
  import { createHash as createHash5 } from "node:crypto";
26913
- import fs73 from "node:fs";
26914
- import path75 from "node:path";
27036
+ import fs74 from "node:fs";
27037
+ import path77 from "node:path";
26915
27038
  var SLUG = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
26916
27039
  var CAPTURED = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:\d{2})$/;
26917
27040
  var MAX_BYTES3 = 50 * 1024 * 1024;
@@ -27010,10 +27133,10 @@ This template has no preview.`);
27010
27133
  const written = [];
27011
27134
  for (const p of previews) {
27012
27135
  const bytes3 = Buffer.from(await (await ask(`/${at}/previews/${route(p.path)}`, {}, WITHOUT)).arrayBuffer());
27013
- const file = path75.basename(p.path);
27014
- const out = path75.join(opts.to, file.startsWith(`${name}-`) ? file : `${name}-${file}`);
27015
- fs73.mkdirSync(opts.to, { recursive: true });
27016
- fs73.writeFileSync(out, bytes3);
27136
+ const file = path77.basename(p.path);
27137
+ const out = path77.join(opts.to, file.startsWith(`${name}-`) ? file : `${name}-${file}`);
27138
+ fs74.mkdirSync(opts.to, { recursive: true });
27139
+ fs74.writeFileSync(out, bytes3);
27017
27140
  written.push(out);
27018
27141
  say(out);
27019
27142
  }
@@ -27026,15 +27149,15 @@ async function templateGet(name, opts, program2) {
27026
27149
  const { template } = await asJson(await ask(`/${at}`, {}, WITHOUT));
27027
27150
  const fetched = [];
27028
27151
  for (const f of template.files) {
27029
- const out = path75.resolve(to, f.path);
27030
- if (!out.startsWith(path75.resolve(to) + path75.sep)) refuse2(T.corrupt, WITHOUT);
27152
+ const out = path77.resolve(to, f.path);
27153
+ if (!out.startsWith(path77.resolve(to) + path77.sep)) refuse2(T.corrupt, WITHOUT);
27031
27154
  const bytes3 = Buffer.from(await (await ask(`/${at}/files/${route(f.path)}`, {}, WITHOUT)).arrayBuffer());
27032
27155
  if (bytes3.byteLength !== f.size || createHash5("sha256").update(bytes3).digest("hex") !== f.sha256) {
27033
27156
  refuse2(T.corrupt, WITHOUT);
27034
27157
  }
27035
27158
  fetched.push({ out, bytes: bytes3 });
27036
27159
  }
27037
- const taken = fetched.filter((f) => fs73.existsSync(f.out)).map((f) => path75.relative(to, f.out));
27160
+ const taken = fetched.filter((f) => fs74.existsSync(f.out)).map((f) => path77.relative(to, f.out));
27038
27161
  if (taken.length > 0) {
27039
27162
  refuse2({
27040
27163
  zh: `\u76EE\u6807\u6587\u4EF6\u5939\u5DF2\u6709\u540C\u540D\u6587\u4EF6\uFF1A${taken.join("\u3001")}\u3002\u8BF7\u6362\u4E00\u4E2A\u6587\u4EF6\u5939\uFF0C\u6216\u5148\u79FB\u8D70\u8FD9\u4E9B\u6587\u4EF6\u3002`,
@@ -27042,8 +27165,8 @@ async function templateGet(name, opts, program2) {
27042
27165
  });
27043
27166
  }
27044
27167
  for (const f of fetched) {
27045
- fs73.mkdirSync(path75.dirname(f.out), { recursive: true });
27046
- fs73.writeFileSync(f.out, f.bytes);
27168
+ fs74.mkdirSync(path77.dirname(f.out), { recursive: true });
27169
+ fs74.writeFileSync(f.out, f.bytes);
27047
27170
  }
27048
27171
  say(`\u5DF2\u53D6\u56DE ${name}\uFF1A${fetched.length} \u4E2A\u6587\u4EF6\uFF0C\u4F4D\u4E8E ${to}
27049
27172
  Fetched ${name}: ${fetched.length} files into ${to}`);
@@ -27061,25 +27184,25 @@ async function templateSource(name, opts) {
27061
27184
  say(`evidence: ${e.path}`);
27062
27185
  continue;
27063
27186
  }
27064
- const out = path75.join(opts.to, `${name}-${path75.basename(e.path)}`);
27065
- fs73.mkdirSync(opts.to, { recursive: true });
27066
- fs73.writeFileSync(out, Buffer.from(await (await ask(`/${at}/evidence/${route(e.path)}`)).arrayBuffer()));
27187
+ const out = path77.join(opts.to, `${name}-${path77.basename(e.path)}`);
27188
+ fs74.mkdirSync(opts.to, { recursive: true });
27189
+ fs74.writeFileSync(out, Buffer.from(await (await ask(`/${at}/evidence/${route(e.path)}`)).arrayBuffer()));
27067
27190
  written.push(out);
27068
27191
  say(out);
27069
27192
  }
27070
27193
  return { name, source, evidence: written.length > 0 ? written : evidence.map((e) => e.path) };
27071
27194
  }
27072
27195
  async function templatePublish(dir, opts) {
27073
- const root = path75.resolve(dir);
27074
- const manifest = path75.join(root, "template.md");
27075
- if (!fs73.existsSync(manifest)) invalid("\u6587\u4EF6\u5939\u9876\u5C42\u7F3A\u5C11 template.md\u3002", "template.md is missing from the folder\u2019s top level.");
27076
- const meta = readTemplateMeta(fs73.readFileSync(manifest, "utf8"));
27196
+ const root = path77.resolve(dir);
27197
+ const manifest = path77.join(root, "template.md");
27198
+ if (!fs74.existsSync(manifest)) invalid("\u6587\u4EF6\u5939\u9876\u5C42\u7F3A\u5C11 template.md\u3002", "template.md is missing from the folder\u2019s top level.");
27199
+ const meta = readTemplateMeta(fs74.readFileSync(manifest, "utf8"));
27077
27200
  const files2 = walk2(root);
27078
- const total = files2.reduce((sum, f) => sum + fs73.statSync(path75.join(root, f)).size, 0);
27201
+ const total = files2.reduce((sum, f) => sum + fs74.statSync(path77.join(root, f)).size, 0);
27079
27202
  if (total > MAX_BYTES3) invalid("\u6A21\u677F\u6587\u4EF6\u5408\u8BA1\u8D85\u8FC7 50 MB\u3002", "the template\u2019s files exceed 50 MB.");
27080
27203
  for (const extra of [...opts.preview, ...opts.evidence]) {
27081
- if (!fs73.existsSync(extra)) invalid(`\u627E\u4E0D\u5230\u6587\u4EF6 ${extra}\u3002`, `${extra} does not exist.`);
27082
- if (path75.resolve(extra).startsWith(root + path75.sep)) {
27204
+ if (!fs74.existsSync(extra)) invalid(`\u627E\u4E0D\u5230\u6587\u4EF6 ${extra}\u3002`, `${extra} does not exist.`);
27205
+ if (path77.resolve(extra).startsWith(root + path77.sep)) {
27083
27206
  invalid(
27084
27207
  `${extra} \u5728\u6A21\u677F\u6587\u4EF6\u5939\u5185\uFF1B\u9884\u89C8\u56FE\u4E0E\u6765\u6E90\u51ED\u8BC1\u9700\u653E\u5728\u6A21\u677F\u6587\u4EF6\u5939\u4E4B\u5916\u3002`,
27085
27208
  `${extra} is inside the template folder; keep previews and evidence outside it.`
@@ -27091,10 +27214,10 @@ async function templatePublish(dir, opts) {
27091
27214
  }
27092
27215
  const form = new FormData();
27093
27216
  form.set("meta", JSON.stringify({ ...meta, replace: !!opts.replace }));
27094
- const add = (kind, name, file) => form.append(`${kind}:${name}`, new Blob([fs73.readFileSync(file)]), path75.basename(name));
27095
- for (const f of files2) add("files", f, path75.join(root, f));
27096
- for (const f of opts.preview) add("previews", path75.basename(f), f);
27097
- for (const f of opts.evidence) add("evidence", path75.basename(f), f);
27217
+ const add = (kind, name, file) => form.append(`${kind}:${name}`, new Blob([fs74.readFileSync(file)]), path77.basename(name));
27218
+ for (const f of files2) add("files", f, path77.join(root, f));
27219
+ for (const f of opts.preview) add("previews", path77.basename(f), f);
27220
+ for (const f of opts.evidence) add("evidence", path77.basename(f), f);
27098
27221
  const done2 = await asJson(await ask("", { method: "POST", body: form }));
27099
27222
  say(
27100
27223
  done2.replaced ? `\u5DF2\u66FF\u6362 ${done2.name}\uFF1A${files2.length} \u4E2A\u6587\u4EF6\u3002
@@ -27110,11 +27233,11 @@ Deleted ${name} from the catalog.`);
27110
27233
  return { name, deleted: true };
27111
27234
  }
27112
27235
  async function templateShowcase(name, dir) {
27113
- const file = path75.join(dir, "showcase.json");
27114
- if (!fs73.existsSync(file)) invalid(`${dir} \u4E2D\u7F3A\u5C11 showcase.json\u3002`, `showcase.json is missing from ${dir}.`);
27236
+ const file = path77.join(dir, "showcase.json");
27237
+ if (!fs74.existsSync(file)) invalid(`${dir} \u4E2D\u7F3A\u5C11 showcase.json\u3002`, `showcase.json is missing from ${dir}.`);
27115
27238
  let meta;
27116
27239
  try {
27117
- meta = JSON.parse(fs73.readFileSync(file, "utf8"));
27240
+ meta = JSON.parse(fs74.readFileSync(file, "utf8"));
27118
27241
  } catch {
27119
27242
  return invalid("showcase.json \u4E0D\u662F\u6709\u6548\u7684 JSON\u3002", "showcase.json is not valid JSON.");
27120
27243
  }
@@ -27126,9 +27249,9 @@ async function templateShowcase(name, dir) {
27126
27249
  const form = new FormData();
27127
27250
  form.set("meta", JSON.stringify(meta));
27128
27251
  for (const p of new Set(named2)) {
27129
- const at = path75.join(dir, p);
27130
- if (!fs73.existsSync(at)) invalid(`\u627E\u4E0D\u5230\u6587\u4EF6 ${at}\u3002`, `${at} does not exist.`);
27131
- form.append(`media:${p}`, new Blob([fs73.readFileSync(at)]), p);
27252
+ const at = path77.join(dir, p);
27253
+ if (!fs74.existsSync(at)) invalid(`\u627E\u4E0D\u5230\u6587\u4EF6 ${at}\u3002`, `${at} does not exist.`);
27254
+ form.append(`media:${p}`, new Blob([fs74.readFileSync(at)]), p);
27132
27255
  }
27133
27256
  const done2 = await asJson(await ask(`/${encodeURIComponent(name)}/showcase`, { method: "PUT", body: form }));
27134
27257
  say(`\u5DF2\u66F4\u65B0 ${name} \u7684\u5C55\u793A\u8D44\u6599\uFF1A${done2.files} \u4E2A\u6587\u4EF6\u3002
@@ -27137,7 +27260,7 @@ Updated ${name}'s showcase: ${done2.files} files.`);
27137
27260
  }
27138
27261
  function walk2(root, sub = "") {
27139
27262
  const out = [];
27140
- for (const entry of fs73.readdirSync(path75.join(root, sub), { withFileTypes: true })) {
27263
+ for (const entry of fs74.readdirSync(path77.join(root, sub), { withFileTypes: true })) {
27141
27264
  if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
27142
27265
  const rel2 = sub ? `${sub}/${entry.name}` : entry.name;
27143
27266
  if (entry.isDirectory()) out.push(...walk2(root, rel2));
@@ -27422,7 +27545,7 @@ init_opencode();
27422
27545
  init_locate();
27423
27546
  init_store();
27424
27547
  import { spawnSync as spawnSync5 } from "node:child_process";
27425
- import path81 from "node:path";
27548
+ import path83 from "node:path";
27426
27549
  var AGENTS2 = {
27427
27550
  claude: "claude-code",
27428
27551
  codex: "codex",
@@ -27437,10 +27560,10 @@ var AGENTS2 = {
27437
27560
  var RUNTIMES = /* @__PURE__ */ new Set(["node", "bun", "deno"]);
27438
27561
  function agentOf2(args) {
27439
27562
  const words = args.trim().split(/\s+/);
27440
- const first = path81.basename(words[0] ?? "");
27563
+ const first = path83.basename(words[0] ?? "");
27441
27564
  const script = RUNTIMES.has(first) ? words[1] ?? "" : words[0] ?? "";
27442
27565
  if (script.includes("/@anthropic-ai/claude-code/")) return "claude-code";
27443
- return AGENTS2[path81.basename(script).replace(/\.(m?js|cjs|exe)$/, "")];
27566
+ return AGENTS2[path83.basename(script).replace(/\.(m?js|cjs|exe)$/, "")];
27444
27567
  }
27445
27568
  function processChain() {
27446
27569
  if (process.platform === "win32") return [];
@@ -27504,8 +27627,8 @@ init_tasks();
27504
27627
  init_read();
27505
27628
  init_rules();
27506
27629
  init_board();
27507
- import fs80 from "node:fs";
27508
- import path82 from "node:path";
27630
+ import fs81 from "node:fs";
27631
+ import path84 from "node:path";
27509
27632
  var FOLLOW_MS = 400;
27510
27633
  async function cmdStartRun(action, args, opts, program2 = "akb") {
27511
27634
  const { req, follow: follow2, print } = readRequest(action, args, opts);
@@ -27705,13 +27828,13 @@ function readRequest(action, args, opts) {
27705
27828
  return { req, follow: follow2, print };
27706
27829
  }
27707
27830
  async function cmdResume(id, opts) {
27708
- const opened = await startResume(id ?? "last");
27831
+ const opened = await startResume(id ?? "last", opts.requireSession);
27709
27832
  if ("error" in opened) die(opened.error, { kind: "run-refused" });
27710
27833
  const { run: run2 } = opened;
27711
27834
  if (!opened.spawned) die(`couldn't start a process for run ${run2.sessionId}`, { kind: "spawn-failed" });
27712
27835
  const within = run2.deliveryId ? ` in delivery ${run2.deliveryId}` : "";
27713
27836
  say(run2.resumedFrom ? `continuing ${short(run2.resumedFrom)} \u2014 run ${run2.sessionId}${within}` : `trying again \u2014 run ${run2.sessionId}${within}`);
27714
- if (opts.follow === true) return await followStarted(run2.sessionId, "akb");
27837
+ if (opts.follow === true) return await followStarted(run2.sessionId, "akb", opts.events === true);
27715
27838
  return { sessionId: run2.sessionId, resumedFrom: run2.resumedFrom };
27716
27839
  }
27717
27840
  async function cmdCancel(named2) {
@@ -27725,7 +27848,10 @@ async function cmdCancel(named2) {
27725
27848
  }
27726
27849
  async function cmdResumeDelivery(named2) {
27727
27850
  const res = await resumeBoardDelivery(named2);
27728
- if (!res.ok) die(res.error ?? "that delivery could not be carried on", { kind: "run-refused" });
27851
+ if (!res.ok) die(res.error ?? "that delivery could not be carried on", {
27852
+ kind: "run-refused",
27853
+ ...res.kind === "refused" ? { reason: res.reason, args: res.args } : {}
27854
+ });
27729
27855
  if (res.landed) {
27730
27856
  say(`delivery ${res.deliveryId} is done \u2014 its changes were already on the target branch.`);
27731
27857
  return { deliveryId: res.deliveryId, landed: true };
@@ -27802,7 +27928,7 @@ async function cmdSubStart(words, opts) {
27802
27928
  let prompt = words.join(" ");
27803
27929
  if (opts.file) {
27804
27930
  try {
27805
- prompt = fs80.readFileSync(path82.resolve(opts.file), "utf8");
27931
+ prompt = fs81.readFileSync(path84.resolve(opts.file), "utf8");
27806
27932
  } catch {
27807
27933
  die(`--file: cannot read ${opts.file}`, { kind: "run-refused" });
27808
27934
  }
@@ -27916,7 +28042,7 @@ async function followRun(sessionId, already = "", program2 = "akb", events = fal
27916
28042
  last = await followOne(current, printed, events);
27917
28043
  seen.add(current.sessionId);
27918
28044
  printed = 0;
27919
- const deliveryId2 = last.deliveryId ?? first.deliveryId;
28045
+ const deliveryId2 = first.deliveryId ?? last.deliveryId;
27920
28046
  const next = nextInFlow(first, current.sessionId, seen) ?? (deliveryId2 ? await moveDelivery(deliveryId2, idle) : void 0);
27921
28047
  if (next === void 0) break;
27922
28048
  if (next === null) {
@@ -27937,7 +28063,7 @@ async function followRun(sessionId, already = "", program2 = "akb", events = fal
27937
28063
  }
27938
28064
  say("");
27939
28065
  say(runLine(last, program2));
27940
- const deliveryId = last.deliveryId ?? first.deliveryId;
28066
+ const deliveryId = first.deliveryId ?? last.deliveryId;
27941
28067
  const delivery = deliveryId ? deliveryOutcome(deliveryId) : void 0;
27942
28068
  if (delivery) say(`delivery ${delivery.id}: ${delivery.line}`);
27943
28069
  const failed = last.status !== "done" ? `run ${short(last.sessionId)} ended ${last.status}${last.error ? `: ${last.error}` : ""}` : delivery && delivery.status !== "finished" ? `delivery ${delivery.id} did not land${idle.timedOut ? ` \u2014 nothing moved it for ${ago(follow.idleMs)}` : ""}: ${delivery.line}` : void 0;
@@ -28003,7 +28129,7 @@ async function followOne(view, already, events) {
28003
28129
  }
28004
28130
  function nextInFlow(first, after2, seen) {
28005
28131
  const runs = readRuns();
28006
- const next = runs.filter((r) => !seen.has(r.sessionId) && r.action !== "sub" && r.startedAt >= first.startedAt).filter((r) => r.resumedFrom === after2 || !!first.flowId && r.flowId === first.flowId).sort((a, b) => a.startedAt - b.startedAt)[0];
28132
+ const next = runs.filter((r) => !seen.has(r.sessionId) && r.action !== "sub" && r.startedAt >= first.startedAt).filter((r) => !first.deliveryId || !r.deliveryId || r.deliveryId === first.deliveryId).filter((r) => r.resumedFrom === after2 || !!first.flowId && r.flowId === first.flowId).sort((a, b) => a.startedAt - b.startedAt)[0];
28007
28133
  return next ? { ...next } : void 0;
28008
28134
  }
28009
28135
  async function moveDelivery(deliveryId, idle) {
@@ -28023,7 +28149,7 @@ async function moveDelivery(deliveryId, idle) {
28023
28149
  const ask2 = await advanceLanding();
28024
28150
  if (!ask2) return null;
28025
28151
  const started = await startRun(ask2);
28026
- return "error" in started ? null : { ...started.run };
28152
+ return "error" in started || started.run.deliveryId !== deliveryId ? null : { ...started.run };
28027
28153
  }
28028
28154
  function deliveryOutcome(deliveryId) {
28029
28155
  const delivery = findDelivery(deliveryId);
@@ -28093,7 +28219,7 @@ async function cmdWatch(sessionId) {
28093
28219
  // ../cli/src/commands/triage.ts
28094
28220
  init_scheduled();
28095
28221
  init_workflows();
28096
- import fs82 from "node:fs";
28222
+ import fs83 from "node:fs";
28097
28223
 
28098
28224
  // ../cli/src/lib/signals/index.ts
28099
28225
  init_cards();
@@ -28103,16 +28229,16 @@ init_yaml();
28103
28229
  // ../cli/src/lib/signals/config.ts
28104
28230
  init_settings2();
28105
28231
  init_paths();
28106
- import fs81 from "node:fs";
28107
- import path83 from "node:path";
28232
+ import fs82 from "node:fs";
28233
+ import path85 from "node:path";
28108
28234
  var TOKEN_KEY = "TRIAGE_ENDPOINT_TOKEN";
28109
28235
  var ENDPOINT_SETTING = "Triage endpoint";
28110
28236
  var LINE2 = /^- \*\*Triage endpoint\*\*\s*[—-]\s*(.+)$/m;
28111
- var boardRel3 = (file) => rel(file).split(path83.sep).join("/");
28237
+ var boardRel3 = (file) => rel(file).split(path85.sep).join("/");
28112
28238
  function signalEndpoint() {
28113
28239
  let text5;
28114
28240
  try {
28115
- text5 = fs81.readFileSync(CONFIG, "utf8");
28241
+ text5 = fs82.readFileSync(CONFIG, "utf8");
28116
28242
  } catch {
28117
28243
  return "";
28118
28244
  }
@@ -28288,7 +28414,7 @@ async function cmdTriageAdd(opts) {
28288
28414
  let body = opts.text;
28289
28415
  if (opts.file !== void 0) {
28290
28416
  try {
28291
- body = fs82.readFileSync(opts.file, "utf8");
28417
+ body = fs83.readFileSync(opts.file, "utf8");
28292
28418
  } catch {
28293
28419
  die(`can't read ${opts.file} \u2014 write the item to a file, then pass its path`, { kind: "needs-input" });
28294
28420
  }
@@ -28720,7 +28846,7 @@ function declareRunning(run2, cli) {
28720
28846
  });
28721
28847
  verb("resume").argument("[id]", RUN_ID, String).summary("continue a run that failed, was cut off or was stopped").description(
28722
28848
  "The one command that reaches past the board: it picks the coding agent's own session back up \u2014 the conversation it was having, with everything it had already read \u2014 while the board counts the work as a fresh run. Before resuming a creation with no card, check sign-in, project access and connection. A refusal starts no run and leaves triage unchanged. Keep the triage source and request identity; reuse any task already created in Cloud. Repairing an existing card creates no other task. Example: akb run resume abc12345"
28723
- ).option("-f, --follow", "watch its log instead of returning").action(async function(...vals) {
28849
+ ).option("-f, --follow", "watch its log instead of returning").option("--events", "with --follow: print its structured events, one JSON object per line").option("--require-session", "refuse if the original conversation cannot be resumed").action(async function(...vals) {
28724
28850
  const [id] = positional(vals);
28725
28851
  await onBoard2(this, cli, () => cmdResume(id, this.opts()));
28726
28852
  });
@@ -28852,8 +28978,8 @@ function declareAgent(program2, cli) {
28852
28978
  }
28853
28979
 
28854
28980
  // ../cli/src/lib/cli/setup.ts
28855
- import fs84 from "node:fs";
28856
- import path85 from "node:path";
28981
+ import fs85 from "node:fs";
28982
+ import path87 from "node:path";
28857
28983
  import { fileURLToPath as fileURLToPath2 } from "node:url";
28858
28984
 
28859
28985
  // ../cli/src/commands/update.ts
@@ -28867,8 +28993,8 @@ init_paths();
28867
28993
  init_memory();
28868
28994
  init_yaml2();
28869
28995
  init_version();
28870
- import fs83 from "node:fs";
28871
- import path84 from "node:path";
28996
+ import fs84 from "node:fs";
28997
+ import path86 from "node:path";
28872
28998
  import { spawnSync as spawnSync6 } from "node:child_process";
28873
28999
  var NAME = "wildcrane";
28874
29000
  var REPO = "https://github.com/ai4kanban/ai4kanban";
@@ -28890,10 +29016,10 @@ var Report = class {
28890
29016
  for (const line of this.notes) say(` ! ${line}`);
28891
29017
  }
28892
29018
  };
28893
- var read3 = (file) => fs83.existsSync(file) ? fs83.readFileSync(file, "utf8") : null;
29019
+ var read3 = (file) => fs84.existsSync(file) ? fs84.readFileSync(file, "utf8") : null;
28894
29020
  function statOf(p) {
28895
29021
  try {
28896
- return fs83.lstatSync(p);
29022
+ return fs84.lstatSync(p);
28897
29023
  } catch {
28898
29024
  return null;
28899
29025
  }
@@ -28918,10 +29044,10 @@ async function cmdUpdate2(ctx) {
28918
29044
  return { updated: ctx.dir, from, to: SKILL_VERSION };
28919
29045
  }
28920
29046
  async function repairBoard(root, report3, named2) {
28921
- const legacy2 = path84.join(root, "docs", "kanban");
28922
- const kept2 = path84.join(root, ".akb", "kanban");
28923
- const board2 = named2 ?? (fs83.existsSync(legacy2) ? legacy2 : kept2);
28924
- if (!fs83.existsSync(board2)) {
29047
+ const legacy2 = path86.join(root, "docs", "kanban");
29048
+ const kept2 = path86.join(root, ".akb", "kanban");
29049
+ const board2 = named2 ?? (fs84.existsSync(legacy2) ? legacy2 : kept2);
29050
+ if (!fs84.existsSync(board2)) {
28925
29051
  report3.notes.push("\u672A\u627E\u5230\u770B\u677F\uFF0C\u8BF7\u901A\u8FC7\u63D2\u4EF6\u8FDE\u63A5\u4E91\u7AEF\u9879\u76EE\u3002");
28926
29052
  return false;
28927
29053
  }
@@ -28952,20 +29078,20 @@ function moveRuntimes(root, board2, report3) {
28952
29078
  }
28953
29079
  }
28954
29080
  function moveLegacyMemory(board2, report3) {
28955
- const memory = path84.join(board2, "memory");
29081
+ const memory = path86.join(board2, "memory");
28956
29082
  const moved = [];
28957
29083
  for (const name of MEMORY_FILES) {
28958
- const from = path84.join(board2, name);
29084
+ const from = path86.join(board2, name);
28959
29085
  const st = statOf(from);
28960
29086
  if (!st || !st.isFile()) continue;
28961
- fs83.mkdirSync(memory, { recursive: true });
28962
- const to = path84.join(memory, name);
29087
+ fs84.mkdirSync(memory, { recursive: true });
29088
+ const to = path86.join(memory, name);
28963
29089
  const there = read3(to);
28964
29090
  if (there === null) {
28965
- fs83.renameSync(from, to);
29091
+ fs84.renameSync(from, to);
28966
29092
  moved.push(name);
28967
29093
  } else if (there === read3(from)) {
28968
- fs83.rmSync(from);
29094
+ fs84.rmSync(from);
28969
29095
  moved.push(name);
28970
29096
  } else {
28971
29097
  report3.notes.push(`both docs/kanban/${name} and docs/kanban/memory/${name} exist and differ \u2014 merge them by hand`);
@@ -28974,23 +29100,23 @@ function moveLegacyMemory(board2, report3) {
28974
29100
  if (moved.length) report3.did.push(`moved the memory set into docs/kanban/memory/: ${moved.join(", ")}`);
28975
29101
  }
28976
29102
  function dropRecordFile(board2) {
28977
- const file = path84.join(board2, "record.csv");
28978
- if (!fs83.existsSync(file)) return null;
28979
- fs83.rmSync(file);
29103
+ const file = path86.join(board2, "record.csv");
29104
+ if (!fs84.existsSync(file)) return null;
29105
+ fs84.rmSync(file);
28980
29106
  return "removed docs/kanban/record.csv (the planning scores it fed are gone)";
28981
29107
  }
28982
29108
  function dropModuleGoals(board2, report3) {
28983
- const memory = path84.join(board2, "memory");
28984
- if (!fs83.existsSync(memory)) return;
28985
- const root = read3(path84.join(memory, "goal.md"));
29109
+ const memory = path86.join(board2, "memory");
29110
+ if (!fs84.existsSync(memory)) return;
29111
+ const root = read3(path86.join(memory, "goal.md"));
28986
29112
  let dropped = 0;
28987
- for (const entry of fs83.readdirSync(memory, { withFileTypes: true })) {
29113
+ for (const entry of fs84.readdirSync(memory, { withFileTypes: true })) {
28988
29114
  if (!entry.isDirectory()) continue;
28989
- const file = path84.join(memory, entry.name, "goal.md");
29115
+ const file = path86.join(memory, entry.name, "goal.md");
28990
29116
  const text5 = read3(file);
28991
29117
  if (text5 === null) continue;
28992
29118
  if (text5 === root || text5.includes(UNFILLED) || !goalWords(text5)) {
28993
- fs83.rmSync(file);
29119
+ fs84.rmSync(file);
28994
29120
  dropped++;
28995
29121
  } else {
28996
29122
  report3.notes.push(
@@ -29012,10 +29138,10 @@ var PLAN_LEADS = {
29012
29138
  var GONE_LEADS = ["software-planner", "planner"];
29013
29139
  function planLeads(board2) {
29014
29140
  const leads = { ...PLAN_LEADS };
29015
- const root = path84.join(board2, "workflows");
29016
- for (const entry of fs83.existsSync(root) ? fs83.readdirSync(root, { withFileTypes: true }) : []) {
29141
+ const root = path86.join(board2, "workflows");
29142
+ for (const entry of fs84.existsSync(root) ? fs84.readdirSync(root, { withFileTypes: true }) : []) {
29017
29143
  if (!entry.isDirectory()) continue;
29018
- const meta = splitFrontmatter(read3(path84.join(root, entry.name, "WORKFLOW.md")) ?? "").meta;
29144
+ const meta = splitFrontmatter(read3(path86.join(root, entry.name, "WORKFLOW.md")) ?? "").meta;
29019
29145
  const stages = meta === null ? void 0 : parseYamlBlock(meta).stages;
29020
29146
  const plan = stages && typeof stages === "object" && !Array.isArray(stages) ? stages.plan : void 0;
29021
29147
  const lead = plan && typeof plan === "object" && !Array.isArray(plan) && typeof plan.lead === "string" ? plan.lead.trim() : "";
@@ -29024,30 +29150,30 @@ function planLeads(board2) {
29024
29150
  return leads;
29025
29151
  }
29026
29152
  function movePlanRules(board2, report3) {
29027
- const rules = path84.join(board2, "rules");
29153
+ const rules = path86.join(board2, "rules");
29028
29154
  const moved = [];
29029
29155
  for (const [id, leads] of Object.entries(planLeads(board2))) {
29030
29156
  for (const lead of leads) {
29031
- const from = path84.join(rules, `${lead}.md`);
29157
+ const from = path86.join(rules, `${lead}.md`);
29032
29158
  const rule = read3(from)?.trim();
29033
29159
  if (!rule) continue;
29034
- const to = path84.join(rules, "workflows", `${id}.md`);
29160
+ const to = path86.join(rules, "workflows", `${id}.md`);
29035
29161
  const there = read3(to)?.trim() ?? "";
29036
29162
  if (!there.includes(rule)) {
29037
- fs83.mkdirSync(path84.dirname(to), { recursive: true });
29038
- fs83.writeFileSync(to, `${[there, rule].filter(Boolean).join("\n\n")}
29163
+ fs84.mkdirSync(path86.dirname(to), { recursive: true });
29164
+ fs84.writeFileSync(to, `${[there, rule].filter(Boolean).join("\n\n")}
29039
29165
  `);
29040
29166
  moved.push(`${lead} \u2192 ${id}`);
29041
29167
  }
29042
29168
  }
29043
29169
  }
29044
- for (const lead of GONE_LEADS) fs83.rmSync(path84.join(rules, `${lead}.md`), { force: true });
29170
+ for (const lead of GONE_LEADS) fs84.rmSync(path86.join(rules, `${lead}.md`), { force: true });
29045
29171
  if (moved.length) report3.did.push(`made each plan agent's rule its workflow's card rule (docs/kanban/rules/workflows/): ${moved.join(", ")}`);
29046
29172
  }
29047
29173
  function noteOldWorkflows(board2, report3) {
29048
29174
  const old = (dir, file, keys) => {
29049
- const root = path84.join(board2, dir);
29050
- return (fs83.existsSync(root) ? fs83.readdirSync(root, { withFileTypes: true }) : []).filter((e) => e.isDirectory() && keys.test(splitFrontmatter(read3(path84.join(root, e.name, file)) ?? "").meta ?? "")).map((e) => `docs/kanban/${dir}/${e.name}/${file}`);
29175
+ const root = path86.join(board2, dir);
29176
+ return (fs84.existsSync(root) ? fs84.readdirSync(root, { withFileTypes: true }) : []).filter((e) => e.isDirectory() && keys.test(splitFrontmatter(read3(path86.join(root, e.name, file)) ?? "").meta ?? "")).map((e) => `docs/kanban/${dir}/${e.name}/${file}`);
29051
29177
  };
29052
29178
  const files2 = [...old("workflows", "WORKFLOW.md", /^(stages|delivers):/m), ...old("agents", "AGENT.md", /^\s+(hook|lead):/m)];
29053
29179
  if (files2.length) {
@@ -29057,7 +29183,7 @@ function noteOldWorkflows(board2, report3) {
29057
29183
  }
29058
29184
  }
29059
29185
  function checkConfig(board2, report3) {
29060
- const current = read3(path84.join(board2, "config.md"));
29186
+ const current = read3(path86.join(board2, "config.md"));
29061
29187
  if (!current) return;
29062
29188
  const added = missingConfigKeys(current);
29063
29189
  if (added.length) {
@@ -29067,7 +29193,7 @@ function checkConfig(board2, report3) {
29067
29193
  }
29068
29194
  }
29069
29195
  function checkModules(board2, report3) {
29070
- const map2 = read3(path84.join(board2, "modules.md"));
29196
+ const map2 = read3(path86.join(board2, "modules.md"));
29071
29197
  if (map2 && map2.includes(UNFILLED)) {
29072
29198
  report3.notes.push(
29073
29199
  "docs/kanban/modules.md is still blank \u2014 write it from the repo (`akb guide module-map`)"
@@ -29121,14 +29247,14 @@ function declareSetup(program2, cli) {
29121
29247
  program2.version(SKILL_VERSION, "-v, --version", "print this version").addHelpText("beforeAll", spelled(cli.program));
29122
29248
  const dirOption = (cmd) => cmd.option("--dir <path>", "the project to work on (default: the current folder)").option("--json", "answer as one JSON object instead of prose");
29123
29249
  const where = (cmd) => {
29124
- const dir = path85.resolve(String(cmd.optsWithGlobals().dir ?? cli.cwd));
29125
- if (!fs84.existsSync(dir)) throw new BoardError(`no such folder: ${dir}`, { kind: "no-such-folder", dir });
29250
+ const dir = path87.resolve(String(cmd.optsWithGlobals().dir ?? cli.cwd));
29251
+ if (!fs85.existsSync(dir)) throw new BoardError(`no such folder: ${dir}`, { kind: "no-such-folder", dir });
29126
29252
  return dir;
29127
29253
  };
29128
29254
  dirOption(program2.command("update")).summary("\u4FEE\u590D\u5DF2\u6709\u770B\u677F\u5E76\u68C0\u67E5 CLI \u65B0\u7248\u672C").description("\u4FEE\u590D\u5DF2\u6709\u770B\u677F\u5E76\u63D0\u793A CLI \u5347\u7EA7\u547D\u4EE4\u3002\u6CA1\u6709\u770B\u677F\u65F6\uFF0C\u8BF7\u901A\u8FC7\u63D2\u4EF6\u8FDE\u63A5\u4E91\u7AEF\u9879\u76EE\u3002").action(async function() {
29129
29255
  const dir = where(this);
29130
29256
  const named2 = this.optsWithGlobals().board ?? process.env.AI4KANBAN_BOARD?.trim();
29131
- const board2 = named2 ? path85.resolve(cli.cwd, named2) : null;
29257
+ const board2 = named2 ? path87.resolve(cli.cwd, named2) : null;
29132
29258
  await runAction(ctxOf(this, cli.program), {}, () => cmdUpdate2({ dir, program: cli.program, board: board2 }));
29133
29259
  });
29134
29260
  program2.command("__rules", { hidden: true }).action(function() {
@@ -29172,7 +29298,7 @@ function invokedDirectly() {
29172
29298
  const entry = process.argv[1];
29173
29299
  if (!entry) return false;
29174
29300
  try {
29175
- return fs85.realpathSync(entry) === fs85.realpathSync(SELF2);
29301
+ return fs86.realpathSync(entry) === fs86.realpathSync(SELF2);
29176
29302
  } catch {
29177
29303
  return false;
29178
29304
  }