wildcrane-runner 0.6.13 → 0.6.15

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- // ai4kanban 0.11.10 — built by runner/scripts/build.mjs. Do not edit.
2
+ // ai4kanban 0.11.12 — built by runner/scripts/build.mjs. Do not edit.
3
3
  import { createRequire as __ai4kanbanRequire } from 'node:module'
4
4
  const require = __ai4kanbanRequire(import.meta.url)
5
5
  var __create = Object.create;
@@ -1167,8 +1167,8 @@ var require_command = __commonJS({
1167
1167
  "../cli/node_modules/commander/lib/command.js"(exports) {
1168
1168
  var EventEmitter = __require("node:events").EventEmitter;
1169
1169
  var childProcess = __require("node:child_process");
1170
- var path85 = __require("node:path");
1171
- var fs86 = __require("node:fs");
1170
+ var path87 = __require("node:path");
1171
+ var fs87 = __require("node:fs");
1172
1172
  var process2 = __require("node:process");
1173
1173
  var { Argument: Argument2, humanReadableArgName } = require_argument();
1174
1174
  var { CommanderError: CommanderError2 } = require_error();
@@ -2149,7 +2149,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2149
2149
  * @param {string} subcommandName
2150
2150
  */
2151
2151
  _checkForMissingExecutable(executableFile, executableDir, subcommandName) {
2152
- if (fs86.existsSync(executableFile)) return;
2152
+ if (fs87.existsSync(executableFile)) return;
2153
2153
  const executableDirMessage = executableDir ? `searched for local subcommand relative to directory '${executableDir}'` : "no directory for search for local subcommand, use .executableDir() to supply a custom directory";
2154
2154
  const executableMissing = `'${executableFile}' does not exist
2155
2155
  - if '${subcommandName}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
@@ -2167,11 +2167,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
2167
2167
  let launchWithNode = false;
2168
2168
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
2169
2169
  function findFile(baseDir, baseName) {
2170
- const localBin = path85.resolve(baseDir, baseName);
2171
- if (fs86.existsSync(localBin)) return localBin;
2172
- if (sourceExt.includes(path85.extname(baseName))) return void 0;
2170
+ const localBin = path87.resolve(baseDir, baseName);
2171
+ if (fs87.existsSync(localBin)) return localBin;
2172
+ if (sourceExt.includes(path87.extname(baseName))) return void 0;
2173
2173
  const foundExt = sourceExt.find(
2174
- (ext) => fs86.existsSync(`${localBin}${ext}`)
2174
+ (ext) => fs87.existsSync(`${localBin}${ext}`)
2175
2175
  );
2176
2176
  if (foundExt) return `${localBin}${foundExt}`;
2177
2177
  return void 0;
@@ -2183,21 +2183,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
2183
2183
  if (this._scriptPath) {
2184
2184
  let resolvedScriptPath;
2185
2185
  try {
2186
- resolvedScriptPath = fs86.realpathSync(this._scriptPath);
2186
+ resolvedScriptPath = fs87.realpathSync(this._scriptPath);
2187
2187
  } catch {
2188
2188
  resolvedScriptPath = this._scriptPath;
2189
2189
  }
2190
- executableDir = path85.resolve(
2191
- path85.dirname(resolvedScriptPath),
2190
+ executableDir = path87.resolve(
2191
+ path87.dirname(resolvedScriptPath),
2192
2192
  executableDir
2193
2193
  );
2194
2194
  }
2195
2195
  if (executableDir) {
2196
2196
  let localFile = findFile(executableDir, executableFile);
2197
2197
  if (!localFile && !subcommand._executableFile && this._scriptPath) {
2198
- const legacyName = path85.basename(
2198
+ const legacyName = path87.basename(
2199
2199
  this._scriptPath,
2200
- path85.extname(this._scriptPath)
2200
+ path87.extname(this._scriptPath)
2201
2201
  );
2202
2202
  if (legacyName !== this._name) {
2203
2203
  localFile = findFile(
@@ -2208,7 +2208,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2208
2208
  }
2209
2209
  executableFile = localFile || executableFile;
2210
2210
  }
2211
- launchWithNode = sourceExt.includes(path85.extname(executableFile));
2211
+ launchWithNode = sourceExt.includes(path87.extname(executableFile));
2212
2212
  let proc;
2213
2213
  if (process2.platform !== "win32") {
2214
2214
  if (launchWithNode) {
@@ -3055,7 +3055,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
3055
3055
  * @return {Command}
3056
3056
  */
3057
3057
  nameFromFilename(filename) {
3058
- this._name = path85.basename(filename, path85.extname(filename));
3058
+ this._name = path87.basename(filename, path87.extname(filename));
3059
3059
  return this;
3060
3060
  }
3061
3061
  /**
@@ -3069,9 +3069,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
3069
3069
  * @param {string} [path]
3070
3070
  * @return {(string|null|Command)}
3071
3071
  */
3072
- executableDir(path86) {
3073
- if (path86 === void 0) return this._executableDir;
3074
- this._executableDir = path86;
3072
+ executableDir(path88) {
3073
+ if (path88 === void 0) return this._executableDir;
3074
+ this._executableDir = path88;
3075
3075
  return this;
3076
3076
  }
3077
3077
  /**
@@ -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
  }
@@ -4057,42 +3871,42 @@ async function takeLock() {
4057
3871
  const until = Date.now() + LOCK_WAIT_MS;
4058
3872
  for (; ; ) {
4059
3873
  try {
4060
- fs5.mkdirSync(machineHome(), { recursive: true, mode: 448 });
4061
- fs5.mkdirSync(lockDir());
3874
+ fs4.mkdirSync(machineHome(), { recursive: true, mode: 448 });
3875
+ fs4.mkdirSync(lockDir());
4062
3876
  try {
4063
- fs5.writeFileSync(lockOwner(), `${process.pid}
3877
+ fs4.writeFileSync(lockOwner(), `${process.pid}
4064
3878
  `);
4065
3879
  } catch {
4066
3880
  }
4067
3881
  return () => {
4068
3882
  try {
4069
- fs5.rmSync(lockDir(), { recursive: true, force: true });
3883
+ fs4.rmSync(lockDir(), { recursive: true, force: true });
4070
3884
  } catch {
4071
3885
  }
4072
3886
  };
4073
3887
  } catch (err) {
4074
3888
  if (err.code !== "EEXIST") throw err;
4075
3889
  if (stale()) {
4076
- fs5.rmSync(lockDir(), { recursive: true, force: true });
3890
+ fs4.rmSync(lockDir(), { recursive: true, force: true });
4077
3891
  continue;
4078
3892
  }
4079
3893
  if (Date.now() > until) return () => {
4080
3894
  };
4081
- await sleep2(LOCK_RETRY_MS);
3895
+ await sleep(LOCK_RETRY_MS);
4082
3896
  }
4083
3897
  }
4084
3898
  }
4085
3899
  function stale() {
4086
3900
  let held4;
4087
3901
  try {
4088
- const pid = Number(fs5.readFileSync(lockOwner(), "utf8").trim());
3902
+ const pid = Number(fs4.readFileSync(lockOwner(), "utf8").trim());
4089
3903
  held4 = Number.isInteger(pid) && pid > 0 ? pid : void 0;
4090
3904
  } catch {
4091
3905
  held4 = void 0;
4092
3906
  }
4093
3907
  if (held4 !== void 0 && !alive(held4)) return true;
4094
3908
  try {
4095
- return Date.now() - fs5.statSync(lockDir()).mtimeMs > LOCK_STALE_MS;
3909
+ return Date.now() - fs4.statSync(lockDir()).mtimeMs > LOCK_STALE_MS;
4096
3910
  } catch {
4097
3911
  return false;
4098
3912
  }
@@ -4105,7 +3919,7 @@ function alive(pid) {
4105
3919
  return e.code === "EPERM";
4106
3920
  }
4107
3921
  }
4108
- var REFRESH_MARGIN_MS, LOCK_STALE_MS, LOCK_WAIT_MS, LOCK_RETRY_MS, sessionFile, lockDir, lockOwner, expiringSoon, sleep2;
3922
+ var REFRESH_MARGIN_MS, LOCK_STALE_MS, LOCK_WAIT_MS, LOCK_RETRY_MS, sessionFile, lockDir, lockOwner, expiringSoon, sleep;
4109
3923
  var init_session = __esm({
4110
3924
  "../cli/src/lib/cloud/session.ts"() {
4111
3925
  "use strict";
@@ -4115,11 +3929,234 @@ var init_session = __esm({
4115
3929
  LOCK_STALE_MS = 3e4;
4116
3930
  LOCK_WAIT_MS = 2e4;
4117
3931
  LOCK_RETRY_MS = 50;
4118
- sessionFile = () => path7.join(machineHome(), "session.json");
4119
- lockDir = () => path7.join(machineHome(), "session.lock");
4120
- lockOwner = () => path7.join(lockDir(), "owner");
3932
+ sessionFile = () => path5.join(machineHome(), "session.json");
3933
+ lockDir = () => path5.join(machineHome(), "session.lock");
3934
+ lockOwner = () => path5.join(lockDir(), "owner");
4121
3935
  expiringSoon = (session) => session.expiresAt - REFRESH_MARGIN_MS <= Date.now();
4122
- sleep2 = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
3936
+ sleep = (ms) => new Promise((resolve2) => setTimeout(resolve2, ms));
3937
+ }
3938
+ });
3939
+
3940
+ // ../cli/src/lib/cloud/execution.ts
3941
+ function executionOf(value = process.env.AI4KANBAN_EXECUTION) {
3942
+ try {
3943
+ const held4 = typeof value === "string" ? JSON.parse(value) : value;
3944
+ if (held4 && UUID.test(held4.job) && UUID.test(held4.machine)) return { job: held4.job, machine: held4.machine };
3945
+ } catch {
3946
+ }
3947
+ return void 0;
3948
+ }
3949
+ async function executionHeld(execution) {
3950
+ if (!execution) return true;
3951
+ try {
3952
+ const auth = await accessToken();
3953
+ if (!auth.ok) return false;
3954
+ const response = await fetch(`${cloudEndpoints().api}/v1/runner/jobs/${execution.job}/renew`, {
3955
+ method: "POST",
3956
+ headers: { authorization: `Bearer ${auth.token}`, "content-type": "application/json" },
3957
+ body: JSON.stringify({ machineId: execution.machine }),
3958
+ signal: AbortSignal.timeout(2e4)
3959
+ });
3960
+ if (!response.ok) return false;
3961
+ const held4 = await response.json();
3962
+ return held4.renewed === true && held4.stop !== true;
3963
+ } catch {
3964
+ return false;
3965
+ }
3966
+ }
3967
+ var UUID;
3968
+ var init_execution = __esm({
3969
+ "../cli/src/lib/cloud/execution.ts"() {
3970
+ "use strict";
3971
+ init_config();
3972
+ init_session();
3973
+ UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
3974
+ }
3975
+ });
3976
+
3977
+ // ../cli/src/lib/lock.ts
3978
+ import { randomUUID } from "node:crypto";
3979
+ import fs5 from "node:fs";
3980
+ import path6 from "node:path";
3981
+ function sleep2(ms) {
3982
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
3983
+ }
3984
+ function ageOf(dir) {
3985
+ try {
3986
+ return Date.now() - fs5.statSync(dir).mtimeMs;
3987
+ } catch {
3988
+ return 0;
3989
+ }
3990
+ }
3991
+ function pidAlive(pid) {
3992
+ if (!pid) return false;
3993
+ try {
3994
+ process.kill(pid, 0);
3995
+ return true;
3996
+ } catch (e) {
3997
+ return e.code === "EPERM";
3998
+ }
3999
+ }
4000
+ function ownerOf(dir) {
4001
+ try {
4002
+ const pid = Number(fs5.readFileSync(path6.join(dir, OWNER), "utf8").trim());
4003
+ return Number.isInteger(pid) && pid > 0 ? pid : void 0;
4004
+ } catch {
4005
+ return void 0;
4006
+ }
4007
+ }
4008
+ function claimBreak(dir) {
4009
+ try {
4010
+ fs5.mkdirSync(path6.join(dir, BREAKING));
4011
+ return true;
4012
+ } catch {
4013
+ return false;
4014
+ }
4015
+ }
4016
+ function removeClaim(dir, token3) {
4017
+ if (token3) {
4018
+ try {
4019
+ fs5.unlinkSync(path6.join(dir, token3));
4020
+ } catch (err) {
4021
+ if (err.code !== "ENOENT") throw err;
4022
+ }
4023
+ }
4024
+ try {
4025
+ fs5.rmdirSync(dir);
4026
+ return true;
4027
+ } catch (err) {
4028
+ const code = err.code;
4029
+ if (code === "ENOENT") return true;
4030
+ if (code === "ENOTEMPTY" || code === "EEXIST") return false;
4031
+ throw err;
4032
+ }
4033
+ }
4034
+ function tryLock(dir) {
4035
+ fs5.mkdirSync(path6.dirname(dir), { recursive: true });
4036
+ const token3 = `${process.pid}-${randomUUID()}`;
4037
+ const candidate = fs5.mkdtempSync(`${dir}.`);
4038
+ try {
4039
+ fs5.writeFileSync(path6.join(candidate, token3), "");
4040
+ for (; ; ) {
4041
+ try {
4042
+ fs5.renameSync(candidate, dir);
4043
+ return () => {
4044
+ removeClaim(dir, token3);
4045
+ };
4046
+ } catch (err) {
4047
+ const code = err.code;
4048
+ if (code !== "ENOTEMPTY" && code !== "EEXIST") throw err;
4049
+ }
4050
+ let entries;
4051
+ try {
4052
+ entries = fs5.readdirSync(dir);
4053
+ } catch (err) {
4054
+ if (err.code === "ENOENT") continue;
4055
+ throw err;
4056
+ }
4057
+ if (entries.length) {
4058
+ if (entries.length !== 1 || !/^\d+-[a-f0-9-]{36}$/.test(entries[0])) return void 0;
4059
+ const owner = Number(entries[0].split("-")[0]);
4060
+ if (pidAlive(owner)) return void 0;
4061
+ }
4062
+ if (!removeClaim(dir, entries[0])) return void 0;
4063
+ }
4064
+ } finally {
4065
+ fs5.rmSync(candidate, { recursive: true, force: true });
4066
+ }
4067
+ }
4068
+ function withLock(dir, what, fn) {
4069
+ const until = Date.now() + WAIT_MS;
4070
+ let held4;
4071
+ for (; ; ) {
4072
+ try {
4073
+ fs5.mkdirSync(dir, { recursive: false });
4074
+ try {
4075
+ fs5.writeFileSync(path6.join(dir, OWNER), `${process.pid}
4076
+ `);
4077
+ } catch {
4078
+ }
4079
+ break;
4080
+ } catch (err) {
4081
+ const code = err.code;
4082
+ if (code === "ENOENT") {
4083
+ fs5.mkdirSync(path6.dirname(dir), { recursive: true });
4084
+ continue;
4085
+ }
4086
+ if (code !== "EEXIST") throw err;
4087
+ if (ageOf(dir) > STALE_MS) {
4088
+ fs5.rmSync(dir, { recursive: true, force: true });
4089
+ continue;
4090
+ }
4091
+ held4 = ownerOf(dir);
4092
+ if (held4 !== void 0 && !pidAlive(held4) && claimBreak(dir)) {
4093
+ fs5.rmSync(dir, { recursive: true, force: true });
4094
+ continue;
4095
+ }
4096
+ if (Date.now() > until) {
4097
+ die(
4098
+ `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.`,
4099
+ "board-busy"
4100
+ );
4101
+ }
4102
+ sleep2(RETRY_MS);
4103
+ }
4104
+ }
4105
+ try {
4106
+ return fn();
4107
+ } finally {
4108
+ const owner = ownerOf(dir);
4109
+ if (owner === void 0 || owner === process.pid) fs5.rmSync(dir, { recursive: true, force: true });
4110
+ }
4111
+ }
4112
+ function withBoardLock(fn) {
4113
+ if (!fs5.existsSync(KANBAN)) return fn();
4114
+ if (depth > 0) return fn();
4115
+ try {
4116
+ ensureAkbDir();
4117
+ } catch {
4118
+ }
4119
+ depth++;
4120
+ try {
4121
+ return withLock(LOCK, "writing this board", fn);
4122
+ } finally {
4123
+ depth--;
4124
+ }
4125
+ }
4126
+ var WAIT_MS, STALE_MS, RETRY_MS, OWNER, BREAKING, depth;
4127
+ var init_lock = __esm({
4128
+ "../cli/src/lib/lock.ts"() {
4129
+ "use strict";
4130
+ init_paths();
4131
+ WAIT_MS = 1e4;
4132
+ STALE_MS = 6e4;
4133
+ RETRY_MS = 25;
4134
+ OWNER = "owner";
4135
+ BREAKING = "breaking";
4136
+ depth = 0;
4137
+ }
4138
+ });
4139
+
4140
+ // ../cli/src/lib/agent/creation-lock.ts
4141
+ import path7 from "node:path";
4142
+ function withCreationLock(fn) {
4143
+ if (depth2) return fn();
4144
+ return withLock(path7.join(SESSIONS_DIR, "creation.lock"), "changing card creation", () => {
4145
+ depth2++;
4146
+ try {
4147
+ return fn();
4148
+ } finally {
4149
+ depth2--;
4150
+ }
4151
+ });
4152
+ }
4153
+ var depth2;
4154
+ var init_creation_lock = __esm({
4155
+ "../cli/src/lib/agent/creation-lock.ts"() {
4156
+ "use strict";
4157
+ init_lock();
4158
+ init_paths();
4159
+ depth2 = 0;
4123
4160
  }
4124
4161
  });
4125
4162
 
@@ -5477,7 +5514,7 @@ var init_package = __esm({
5477
5514
  "../cli/package.json"() {
5478
5515
  package_default = {
5479
5516
  name: "wildcrane",
5480
- version: "0.11.10",
5517
+ version: "0.11.12",
5481
5518
  akbContract: 5,
5482
5519
  description: "WildCrane's `akb` command \u2014 scaffold the Markdown board under docs/kanban/, put an agent on a card, and carry the flows the agent works by.",
5483
5520
  keywords: [
@@ -8316,10 +8353,10 @@ function findRollout(threadId) {
8316
8353
  }
8317
8354
  for (const name of names4) {
8318
8355
  if (!name.startsWith("rollout-") || !name.endsWith(tail2)) continue;
8319
- const path85 = join(dir, name);
8356
+ const path87 = join(dir, name);
8320
8357
  try {
8321
- const at = statSync(path85).mtimeMs;
8322
- if (!newest2 || at > newest2.at) newest2 = { path: path85, at };
8358
+ const at = statSync(path87).mtimeMs;
8359
+ if (!newest2 || at > newest2.at) newest2 = { path: path87, at };
8323
8360
  } catch {
8324
8361
  }
8325
8362
  }
@@ -8327,11 +8364,11 @@ function findRollout(threadId) {
8327
8364
  return newest2?.path;
8328
8365
  }
8329
8366
  function readCodexRunFacts(threadId) {
8330
- const path85 = findRollout(threadId);
8331
- if (!path85) return void 0;
8367
+ const path87 = findRollout(threadId);
8368
+ if (!path87) return void 0;
8332
8369
  let raw;
8333
8370
  try {
8334
- raw = readFileSync(path85, "utf8");
8371
+ raw = readFileSync(path87, "utf8");
8335
8372
  } catch {
8336
8373
  return void 0;
8337
8374
  }
@@ -8459,8 +8496,8 @@ function renderCompleted(item) {
8459
8496
  const changes = Array.isArray(item.changes) ? item.changes : [];
8460
8497
  return changes.map((raw) => {
8461
8498
  const change2 = obj(raw);
8462
- const path85 = str2(change2.path);
8463
- return path85 ? `\u23FA ${str2(change2.kind) || "change"}(${path85})
8499
+ const path87 = str2(change2.path);
8500
+ return path87 ? `\u23FA ${str2(change2.kind) || "change"}(${path87})
8464
8501
  ` : "";
8465
8502
  }).join("");
8466
8503
  }
@@ -8719,10 +8756,10 @@ function readKimiRunFacts(sessionId) {
8719
8756
  const total = { input: 0, cacheCreation: 0, cacheRead: 0, output: 0 };
8720
8757
  let model;
8721
8758
  let holding = 0;
8722
- for (const { path: path85, main } of wireFiles(entry.sessionDir)) {
8759
+ for (const { path: path87, main } of wireFiles(entry.sessionDir)) {
8723
8760
  let raw;
8724
8761
  try {
8725
- raw = readFileSync2(path85, "utf8");
8762
+ raw = readFileSync2(path87, "utf8");
8726
8763
  } catch {
8727
8764
  continue;
8728
8765
  }
@@ -9003,8 +9040,8 @@ var init_tail = __esm({
9003
9040
  import fs24 from "node:fs";
9004
9041
  function whereHint(update2, title) {
9005
9042
  const locations = Array.isArray(update2.locations) ? update2.locations : [];
9006
- const path85 = str2(obj(locations[0]).path);
9007
- return path85 && !title.includes(path85) ? hint(path85) : "";
9043
+ const path87 = str2(obj(locations[0]).path);
9044
+ return path87 && !title.includes(path87) ? hint(path87) : "";
9008
9045
  }
9009
9046
  function saidBy(update2) {
9010
9047
  const content = Array.isArray(update2.content) ? update2.content : [];
@@ -9947,27 +9984,40 @@ import crypto3 from "node:crypto";
9947
9984
  import fs25 from "node:fs";
9948
9985
  import os4 from "node:os";
9949
9986
  import path28 from "node:path";
9987
+ function spawnGit(args, options) {
9988
+ const out = spawnSync3("git", args, {
9989
+ ...options,
9990
+ encoding: "utf8",
9991
+ stdio: ["ignore", "pipe", "pipe"],
9992
+ timeout: gitLimits.timeoutMs,
9993
+ killSignal: "SIGKILL"
9994
+ });
9995
+ if (out.error?.code === "ETIMEDOUT") throw new GitTimeoutError(`Git ${args[0]} timed out after ${gitLimits.timeoutMs / 1e3}s; the command was killed.`);
9996
+ return out;
9997
+ }
9950
9998
  function git(args, cwd = REPO_ROOT) {
9951
9999
  try {
9952
- const out = spawnSync3("git", args, { cwd, encoding: "utf8", maxBuffer: MAX_OUTPUT, windowsHide: true });
10000
+ const out = spawnGit(args, { cwd, encoding: "utf8", maxBuffer: MAX_OUTPUT, windowsHide: true });
9953
10001
  if (out.error || out.status !== 0) return null;
9954
10002
  return out.stdout;
9955
- } catch {
10003
+ } catch (error) {
10004
+ if (error instanceof GitTimeoutError) throw error;
9956
10005
  return null;
9957
10006
  }
9958
10007
  }
9959
10008
  function gitDiff(args, cwd = REPO_ROOT) {
9960
10009
  try {
9961
- const out = spawnSync3("git", args, { cwd, encoding: "utf8", maxBuffer: MAX_OUTPUT, windowsHide: true });
10010
+ const out = spawnGit(args, { cwd, encoding: "utf8", maxBuffer: MAX_OUTPUT, windowsHide: true });
9962
10011
  if (out.error || out.status !== 0 && out.status !== 1) return null;
9963
10012
  return out.stdout;
9964
- } catch {
10013
+ } catch (error) {
10014
+ if (error instanceof GitTimeoutError) throw error;
9965
10015
  return null;
9966
10016
  }
9967
10017
  }
9968
10018
  function tryGit(args, cwd = REPO_ROOT, env2) {
9969
10019
  try {
9970
- const out = spawnSync3("git", args, {
10020
+ const out = spawnGit(args, {
9971
10021
  cwd,
9972
10022
  encoding: "utf8",
9973
10023
  maxBuffer: MAX_OUTPUT,
@@ -9978,6 +10028,7 @@ function tryGit(args, cwd = REPO_ROOT, env2) {
9978
10028
  if (out.status !== 0) return { ok: false, why: (out.stderr || out.stdout || "").trim() };
9979
10029
  return { ok: true };
9980
10030
  } catch (e) {
10031
+ if (e instanceof GitTimeoutError) throw e;
9981
10032
  return { ok: false, why: e instanceof Error ? e.message : String(e) };
9982
10033
  }
9983
10034
  }
@@ -9986,7 +10037,7 @@ function boardManagedPaths() {
9986
10037
  }
9987
10038
  function outsideBoardForAdd(cwd = REPO_ROOT) {
9988
10039
  const kept2 = boardManagedPaths().filter(
9989
- (p) => spawnSync3("git", ["check-ignore", "-q", p], { cwd, windowsHide: true }).status !== 0
10040
+ (p) => spawnGit(["check-ignore", "-q", p], { cwd, windowsHide: true }).status !== 0
9990
10041
  );
9991
10042
  return ["--", ".", ...kept2.map((p) => `:(exclude)${p}`)];
9992
10043
  }
@@ -10106,10 +10157,10 @@ function treeMark(base, cwd = REPO_ROOT) {
10106
10157
  const index = path28.join(ensureAkbDir(), `index-${process.pid}-${Date.now()}`);
10107
10158
  try {
10108
10159
  const env2 = { ...process.env, GIT_INDEX_FILE: index };
10109
- const run2 = (args) => spawnSync3("git", args, { cwd, encoding: "utf8", maxBuffer: MAX_OUTPUT, windowsHide: true, env: env2 }).status === 0;
10160
+ const run2 = (args) => spawnGit(args, { cwd, encoding: "utf8", maxBuffer: MAX_OUTPUT, windowsHide: true, env: env2 }).status === 0;
10110
10161
  if (!run2(["read-tree", base])) return null;
10111
10162
  if (!run2(["add", "-A", ...outsideBoardForAdd(cwd)])) return null;
10112
- const tree = spawnSync3("git", ["write-tree"], {
10163
+ const tree = spawnGit(["write-tree"], {
10113
10164
  cwd,
10114
10165
  encoding: "utf8",
10115
10166
  maxBuffer: MAX_OUTPUT,
@@ -10117,7 +10168,8 @@ function treeMark(base, cwd = REPO_ROOT) {
10117
10168
  env: env2
10118
10169
  });
10119
10170
  return tree.status === 0 ? tree.stdout.trim() || null : null;
10120
- } catch {
10171
+ } catch (error) {
10172
+ if (error instanceof GitTimeoutError) throw error;
10121
10173
  return null;
10122
10174
  } finally {
10123
10175
  try {
@@ -10128,23 +10180,26 @@ function treeMark(base, cwd = REPO_ROOT) {
10128
10180
  }
10129
10181
  function reverseApplies(patch2, ref, cwd = REPO_ROOT) {
10130
10182
  if (!patch2.trim()) return false;
10131
- const index = path28.join(os4.tmpdir(), `akb-apply-${process.pid}-${Date.now()}.index`);
10183
+ const folder = fs25.mkdtempSync(path28.join(os4.tmpdir(), `akb-apply-${process.pid}-`));
10184
+ const index = path28.join(folder, "index");
10185
+ const patchFile = path28.join(folder, "patch");
10132
10186
  const env2 = { GIT_INDEX_FILE: index };
10133
10187
  try {
10188
+ fs25.writeFileSync(patchFile, patch2);
10134
10189
  if (!tryGit(["read-tree", ref], cwd, env2).ok) return false;
10135
- const out = spawnSync3("git", ["apply", "--cached", "--reverse", "--check", "-"], {
10190
+ const out = spawnGit(["apply", "--cached", "--reverse", "--check", patchFile], {
10136
10191
  cwd,
10137
- input: patch2,
10138
10192
  encoding: "utf8",
10139
10193
  maxBuffer: MAX_OUTPUT,
10140
10194
  windowsHide: true,
10141
10195
  env: { ...process.env, ...env2 }
10142
10196
  });
10143
10197
  return !out.error && out.status === 0;
10144
- } catch {
10198
+ } catch (error) {
10199
+ if (error instanceof GitTimeoutError) throw error;
10145
10200
  return false;
10146
10201
  } finally {
10147
- fs25.rmSync(index, { force: true });
10202
+ fs25.rmSync(folder, { recursive: true, force: true });
10148
10203
  }
10149
10204
  }
10150
10205
  function squashOnto(dir, base, message) {
@@ -10226,12 +10281,15 @@ function moveBranchRef(branch, commit, expect) {
10226
10281
  if (branchTip(branch) !== expect) return { moved: true };
10227
10282
  return { ok: false, error: `couldn't move ${branch} to ${commit.slice(0, 12)}: ${done2.why ?? "git refused"}` };
10228
10283
  }
10229
- var MAX_OUTPUT, scripted, posix, outsideBoard, deliveryBranch, deliveryWorktree, worktreeDir, inGitRepo, headCommit, worktreeExists, branchExists, pruneWorktreeMetadata, branchTip, isAncestor, changedPaths, branchPatch, lastCommitTouching, continueRebase, abortRebase, conflictedPaths, headingKind;
10284
+ var MAX_OUTPUT, gitLimits, GitTimeoutError, scripted, posix, outsideBoard, deliveryBranch, deliveryWorktree, worktreeDir, inGitRepo, headCommit, worktreeExists, branchExists, pruneWorktreeMetadata, branchTip, isAncestor, changedPaths, branchPatch, lastCommitTouching, continueRebase, abortRebase, conflictedPaths, headingKind;
10230
10285
  var init_worktree = __esm({
10231
10286
  "../cli/src/lib/agent/worktree.ts"() {
10232
10287
  "use strict";
10233
10288
  init_paths();
10234
10289
  MAX_OUTPUT = 64 * 1024 * 1024;
10290
+ gitLimits = { timeoutMs: 3e4 };
10291
+ GitTimeoutError = class extends Error {
10292
+ };
10235
10293
  scripted = (args, cwd) => tryGit(["-c", "core.hooksPath=/dev/null", ...args], cwd, { GIT_EDITOR: "true", GIT_SEQUENCE_EDITOR: "true" });
10236
10294
  posix = (p) => p.split(path28.sep).join("/");
10237
10295
  outsideBoard = () => [
@@ -10248,7 +10306,7 @@ var init_worktree = __esm({
10248
10306
  branchExists = (branch) => !!branch && git(["rev-parse", "--verify", "--quiet", `refs/heads/${branch}`]) !== null;
10249
10307
  pruneWorktreeMetadata = () => void git(["worktree", "prune"]);
10250
10308
  branchTip = (branch) => git(["rev-parse", "--verify", "--quiet", `refs/heads/${branch}`])?.trim() || null;
10251
- isAncestor = (older, newer, cwd = REPO_ROOT) => spawnSync3("git", ["merge-base", "--is-ancestor", older, newer], { cwd, windowsHide: true }).status === 0;
10309
+ isAncestor = (older, newer, cwd = REPO_ROOT) => spawnGit(["merge-base", "--is-ancestor", older, newer], { cwd, windowsHide: true }).status === 0;
10252
10310
  changedPaths = (base, branch, cwd = REPO_ROOT) => {
10253
10311
  const out = git(["diff", "--name-only", base, branch, ...outsideBoard()], cwd);
10254
10312
  return out === null ? null : out.split("\n").filter(Boolean);
@@ -11556,49 +11614,12 @@ function withSystemLanguage(harness, argv, env2, line) {
11556
11614
  }
11557
11615
  var init_run_settings = __esm({
11558
11616
  "../cli/src/lib/agent/run-settings.ts"() {
11559
- "use strict";
11560
- init_argv();
11561
- init_env();
11562
- init_language();
11563
- init_types2();
11564
- init_providers();
11565
- }
11566
- });
11567
-
11568
- // ../cli/src/lib/cloud/execution.ts
11569
- function executionOf(value = process.env.AI4KANBAN_EXECUTION) {
11570
- try {
11571
- const held4 = typeof value === "string" ? JSON.parse(value) : value;
11572
- if (held4 && UUID.test(held4.job) && UUID.test(held4.machine)) return { job: held4.job, machine: held4.machine };
11573
- } catch {
11574
- }
11575
- return void 0;
11576
- }
11577
- async function executionHeld(execution) {
11578
- if (!execution) return true;
11579
- try {
11580
- const auth = await accessToken();
11581
- if (!auth.ok) return false;
11582
- const response = await fetch(`${cloudEndpoints().api}/v1/runner/jobs/${execution.job}/renew`, {
11583
- method: "POST",
11584
- headers: { authorization: `Bearer ${auth.token}`, "content-type": "application/json" },
11585
- body: JSON.stringify({ machineId: execution.machine }),
11586
- signal: AbortSignal.timeout(2e4)
11587
- });
11588
- if (!response.ok) return false;
11589
- const held4 = await response.json();
11590
- return held4.renewed === true && held4.stop !== true;
11591
- } catch {
11592
- return false;
11593
- }
11594
- }
11595
- var UUID;
11596
- var init_execution = __esm({
11597
- "../cli/src/lib/cloud/execution.ts"() {
11598
- "use strict";
11599
- init_config();
11600
- init_session();
11601
- UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
11617
+ "use strict";
11618
+ init_argv();
11619
+ init_env();
11620
+ init_language();
11621
+ init_types2();
11622
+ init_providers();
11602
11623
  }
11603
11624
  });
11604
11625
 
@@ -15546,6 +15567,252 @@ var init_complete = __esm({
15546
15567
  }
15547
15568
  });
15548
15569
 
15570
+ // ../cli/src/lib/agent/stop.ts
15571
+ import { spawnSync as spawnSync4 } from "node:child_process";
15572
+ import { randomUUID as randomUUID5 } from "node:crypto";
15573
+ import fs39 from "node:fs";
15574
+ import os6 from "node:os";
15575
+ import path43 from "node:path";
15576
+ function markedInProc(entry, strict = false) {
15577
+ const pids = [];
15578
+ for (const name of fs39.readdirSync("/proc")) {
15579
+ if (!/^\d+$/.test(name)) continue;
15580
+ try {
15581
+ if (fs39.readFileSync(`/proc/${name}/environ`, "utf8").split("\0").includes(entry)) pids.push(Number(name));
15582
+ } catch (error) {
15583
+ if (strict && !["ENOENT", "ESRCH"].includes(error.code ?? "")) {
15584
+ const stat = fs39.statSync(`/proc/${name}`, { throwIfNoEntry: false });
15585
+ if (stat?.uid === process.getuid?.()) throw error;
15586
+ }
15587
+ }
15588
+ }
15589
+ return pids;
15590
+ }
15591
+ function markedInPs(entry, roots) {
15592
+ const ps = spawnSync4("ps", ["-A", "-E", "-ww", "-o", "pid=,ppid=,command="], {
15593
+ encoding: "utf8",
15594
+ maxBuffer: 64 * 1024 * 1024,
15595
+ timeout: 5e3
15596
+ });
15597
+ if (ps.status !== 0 || !ps.stdout) throw new Error("\u65E0\u6CD5\u67E5\u8BE2\u8FDB\u7A0B\u6811\u3002");
15598
+ const children = /* @__PURE__ */ new Map();
15599
+ const listed2 = /* @__PURE__ */ new Set();
15600
+ const found2 = /* @__PURE__ */ new Set();
15601
+ for (const line of ps.stdout.split("\n")) {
15602
+ const m = /^\s*(\d+)\s+(\d+)\s(.*)$/.exec(line);
15603
+ if (!m) continue;
15604
+ const pid = Number(m[1]);
15605
+ const ppid = Number(m[2]);
15606
+ listed2.add(pid);
15607
+ children.set(ppid, [...children.get(ppid) ?? [], pid]);
15608
+ if (`${m[3]} `.includes(` ${entry} `)) found2.add(pid);
15609
+ }
15610
+ const queue = [...found2, ...roots.filter((pid) => listed2.has(pid))];
15611
+ for (const pid of queue) {
15612
+ found2.add(pid);
15613
+ for (const child of children.get(pid) ?? []) if (!found2.has(child)) queue.push(child);
15614
+ }
15615
+ return [...found2];
15616
+ }
15617
+ function marked(mark, roots = []) {
15618
+ if (WINDOWS3 || !mark.value) return [];
15619
+ const entry = `${mark.name}=${mark.value}`;
15620
+ try {
15621
+ const pids = process.platform === "linux" ? markedInProc(entry) : markedInPs(entry, roots);
15622
+ return pids.filter((pid) => pid !== process.pid);
15623
+ } catch {
15624
+ return [];
15625
+ }
15626
+ }
15627
+ function signal(pids, sig) {
15628
+ for (const pid of pids) {
15629
+ try {
15630
+ process.kill(pid, sig);
15631
+ } catch {
15632
+ }
15633
+ }
15634
+ }
15635
+ function stopWatchers(sessions, onlyPid, stillOwns = () => true, startedBefore) {
15636
+ if (sessions ? !sessions.length : onlyPid === void 0) return [];
15637
+ const command = WINDOWS3 ? "powershell.exe" : "ps";
15638
+ const args = WINDOWS3 ? ["-NoProfile", "-NonInteractive", "-Command", "Get-CimInstance Win32_Process | Select-Object ProcessId,CommandLine,@{Name='StartedAt';Expression={([DateTimeOffset]$_.CreationDate).ToUnixTimeMilliseconds()}} | ConvertTo-Json -Compress"] : ["-A", "-ww", "-o", "pid=,lstart=,command="];
15639
+ const out = spawnSync4(command, args, { encoding: "utf8", timeout: 5e3, maxBuffer: 16 * 1024 * 1024, windowsHide: true, env: { ...process.env, LC_ALL: "C" } });
15640
+ if (out.status !== 0) return [];
15641
+ let procs;
15642
+ try {
15643
+ procs = WINDOWS3 ? [].concat(JSON.parse(out.stdout)).map((p) => ({ pid: p.ProcessId, command: p.CommandLine ?? "", startedAt: p.StartedAt })) : out.stdout.split("\n").flatMap((line) => {
15644
+ const m = /^\s*(\d+)\s+(\S+\s+\S+\s+\d+\s+\d+:\d+:\d+\s+\d{4})\s+(.*)$/.exec(line);
15645
+ return m ? [{ pid: Number(m[1]), command: m[3], startedAt: Date.parse(m[2]) }] : [];
15646
+ });
15647
+ } catch {
15648
+ return [];
15649
+ }
15650
+ const known2 = sessions && new Set(sessions);
15651
+ const stopped2 = [];
15652
+ for (const proc of procs) {
15653
+ if (proc.pid === process.pid || onlyPid !== void 0 && proc.pid !== onlyPid) continue;
15654
+ if (startedBefore !== void 0 && (!Number.isFinite(proc.startedAt) || proc.startedAt > startedBefore)) continue;
15655
+ const match = /^"?[^\n]*?\bnode(?:\.exe)?"?\s+(?:"[^\n"]+\.mjs"|\S+\.mjs)\s+__watch\s+(\S+)(?:\s|$)/.exec(proc.command);
15656
+ const args2 = `${proc.command} `;
15657
+ const names4 = (flag, value) => args2.includes(` ${flag} ${value} `) || args2.includes(` ${flag} "${value}" `);
15658
+ const board2 = names4("--board", KANBAN) || KANBAN === path43.join(REPO_ROOT, "docs", "kanban") && names4("--dir", REPO_ROOT);
15659
+ if (!match || known2 && !known2.has(match[1]) || !board2 || !stillOwns()) continue;
15660
+ if (WINDOWS3) {
15661
+ if (killTreeOnWindows(proc.pid)) stopped2.push(proc.pid);
15662
+ } else {
15663
+ try {
15664
+ process.kill(-proc.pid, "SIGKILL");
15665
+ stopped2.push(proc.pid);
15666
+ } catch {
15667
+ }
15668
+ }
15669
+ }
15670
+ return stopped2;
15671
+ }
15672
+ function startedUnder(list, root, since) {
15673
+ const self = list.procs.find((p) => p.pid === root);
15674
+ const from = since ?? self?.startedAt;
15675
+ if (from === void 0 || list.bootedAt > from) return [];
15676
+ if (self && self.startedAt > from) return [];
15677
+ const found2 = self ? [root] : [];
15678
+ const seen = /* @__PURE__ */ new Set([root]);
15679
+ const queue = [{ pid: root, startedAt: from }];
15680
+ for (const parent of queue) {
15681
+ for (const p of list.procs) {
15682
+ if (p.ppid !== parent.pid || seen.has(p.pid) || p.startedAt < parent.startedAt) continue;
15683
+ seen.add(p.pid);
15684
+ found2.push(p.pid);
15685
+ queue.push(p);
15686
+ }
15687
+ }
15688
+ return found2.reverse();
15689
+ }
15690
+ function readProcList(out) {
15691
+ const procs = [];
15692
+ let bootedAt;
15693
+ for (const line of out.split(/\r?\n/)) {
15694
+ const boot = /^boot (\d+)$/.exec(line.trim());
15695
+ if (boot) bootedAt = Number(boot[1]);
15696
+ const m = /^(\d+) (\d+) (\d+)$/.exec(line.trim());
15697
+ if (m) procs.push({ pid: Number(m[1]), ppid: Number(m[2]), startedAt: Number(m[3]) });
15698
+ }
15699
+ return bootedAt !== void 0 && procs.length ? { procs, bootedAt } : void 0;
15700
+ }
15701
+ function procsOnWindows() {
15702
+ const ps = spawnSync4("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", LIST_PROCS], {
15703
+ encoding: "utf8",
15704
+ maxBuffer: 64 * 1024 * 1024,
15705
+ timeout: 5e3,
15706
+ windowsHide: true
15707
+ });
15708
+ return ps.status === 0 && ps.stdout ? readProcList(ps.stdout) : void 0;
15709
+ }
15710
+ function killUnderOnWindows(pid, since) {
15711
+ if (!WINDOWS3 || !pid) return;
15712
+ const list = procsOnWindows();
15713
+ if (!list) return;
15714
+ const pids = startedUnder(list, pid, since).filter((p) => p !== process.pid);
15715
+ if (!pids.length) return;
15716
+ spawnSync4("taskkill", ["/F", ...pids.flatMap((p) => ["/PID", String(p)])], { stdio: "ignore", windowsHide: true, timeout: 1e4 });
15717
+ }
15718
+ function killMarked(mark, agent) {
15719
+ if (WINDOWS3) {
15720
+ if (agent) killUnderOnWindows(agent.pid, agent.since);
15721
+ return;
15722
+ }
15723
+ signal(marked(mark), "SIGKILL");
15724
+ }
15725
+ function killTreeOnWindows(pid) {
15726
+ if (!WINDOWS3 || !pid) return false;
15727
+ const out = spawnSync4("taskkill", ["/PID", String(pid), "/T", "/F"], { stdio: "ignore", windowsHide: true, timeout: 1e4 });
15728
+ return out.status === 0;
15729
+ }
15730
+ function later(ms, fn) {
15731
+ const t = setTimeout(fn, ms);
15732
+ if (typeof t.unref === "function") t.unref();
15733
+ }
15734
+ function endAgent(child, mark, graceMs) {
15735
+ const term = (sig) => {
15736
+ try {
15737
+ child.kill(sig);
15738
+ } catch {
15739
+ }
15740
+ };
15741
+ if (killTreeOnWindows(child.pid)) return;
15742
+ killUnderOnWindows(child.pid);
15743
+ const seen = new Set(marked(mark, child.pid ? [child.pid] : []));
15744
+ term("SIGTERM");
15745
+ signal([...seen].filter((pid) => pid !== child.pid), "SIGTERM");
15746
+ const round = (left) => {
15747
+ const pids = marked(mark, [...seen]);
15748
+ signal(pids, "SIGKILL");
15749
+ for (const pid of pids) seen.add(pid);
15750
+ if (pids.length && left > 1) later(KILL_ROUND_MS, () => round(left - 1));
15751
+ };
15752
+ later(graceMs, () => {
15753
+ term("SIGKILL");
15754
+ round(KILL_ROUNDS);
15755
+ });
15756
+ }
15757
+ function killRunning() {
15758
+ for (const [child, mark] of running) {
15759
+ if (killTreeOnWindows(child.pid)) continue;
15760
+ killUnderOnWindows(child.pid);
15761
+ signal(marked(mark, child.pid ? [child.pid] : []), "SIGKILL");
15762
+ try {
15763
+ child.kill("SIGKILL");
15764
+ } catch {
15765
+ }
15766
+ }
15767
+ running.clear();
15768
+ }
15769
+ function unlisten() {
15770
+ for (const [sig, fn] of listening) process.removeListener(sig, fn);
15771
+ listening.clear();
15772
+ }
15773
+ function onQuit(sig) {
15774
+ killRunning();
15775
+ unlisten();
15776
+ if (process.listenerCount(sig) === 0) process.exit(128 + os6.constants.signals[sig]);
15777
+ }
15778
+ function trackAgent(child, mark) {
15779
+ if (!child.pid) return;
15780
+ running.set(child, mark);
15781
+ child.once("close", () => {
15782
+ running.delete(child);
15783
+ if (!running.size) unlisten();
15784
+ });
15785
+ for (const sig of QUIT_SIGNALS) {
15786
+ if (listening.has(sig) || sig === "SIGINT" && interruptIsTheCallers) continue;
15787
+ const fn = () => onQuit(sig);
15788
+ listening.set(sig, fn);
15789
+ process.on(sig, fn);
15790
+ }
15791
+ }
15792
+ var runMark, stopMark, markEnv, WINDOWS3, KILL_ROUND_MS, KILL_ROUNDS, LIST_PROCS, running, QUIT_SIGNALS, listening, interruptIsTheCallers;
15793
+ var init_stop = __esm({
15794
+ "../cli/src/lib/agent/stop.ts"() {
15795
+ "use strict";
15796
+ init_env();
15797
+ init_paths();
15798
+ runMark = (sessionId) => ({ name: RUN_ENV, value: sessionId });
15799
+ stopMark = () => ({ name: STOP_ENV, value: randomUUID5() });
15800
+ markEnv = (env2, mark) => ({ ...env2, [mark.name]: mark.value });
15801
+ WINDOWS3 = process.platform === "win32";
15802
+ KILL_ROUND_MS = 300;
15803
+ KILL_ROUNDS = 3;
15804
+ LIST_PROCS = [
15805
+ "$ErrorActionPreference = 'Stop'",
15806
+ "Get-CimInstance Win32_Process | ForEach-Object { if ($_.CreationDate) { '{0} {1} {2}' -f $_.ProcessId, $_.ParentProcessId, ([DateTimeOffset]$_.CreationDate).ToUnixTimeMilliseconds() } }",
15807
+ "'boot {0}' -f ([DateTimeOffset](Get-CimInstance Win32_OperatingSystem).LastBootUpTime).ToUnixTimeMilliseconds()"
15808
+ ].join("; ");
15809
+ running = /* @__PURE__ */ new Map();
15810
+ QUIT_SIGNALS = ["SIGTERM", "SIGINT", "SIGHUP"];
15811
+ listening = /* @__PURE__ */ new Map();
15812
+ interruptIsTheCallers = false;
15813
+ }
15814
+ });
15815
+
15549
15816
  // ../cli/src/lib/agent/pause.ts
15550
15817
  function deliveryState(delivery, questions) {
15551
15818
  const landing = delivery.landing;
@@ -15734,12 +16001,12 @@ var init_runner = __esm({
15734
16001
  });
15735
16002
 
15736
16003
  // ../cli/src/lib/agent/rules.ts
15737
- import fs39 from "node:fs";
15738
- import path43 from "node:path";
16004
+ import fs40 from "node:fs";
16005
+ import path44 from "node:path";
15739
16006
  function ruleFile(agent) {
15740
16007
  if (!agent || hiddenRole(agent)) return "";
15741
16008
  try {
15742
- return fs39.readFileSync(rulePath(agent), "utf8").trim();
16009
+ return fs40.readFileSync(rulePath(agent), "utf8").trim();
15743
16010
  } catch {
15744
16011
  return "";
15745
16012
  }
@@ -15754,11 +16021,11 @@ function setAgentRule(agent, text5) {
15754
16021
  const rule = String(text5 ?? "").trim();
15755
16022
  try {
15756
16023
  if (!rule) {
15757
- fs39.rmSync(rulePath(agent), { force: true });
16024
+ fs40.rmSync(rulePath(agent), { force: true });
15758
16025
  return { ok: true };
15759
16026
  }
15760
- fs39.mkdirSync(RULES, { recursive: true });
15761
- fs39.writeFileSync(rulePath(agent), `${rule}
16027
+ fs40.mkdirSync(RULES, { recursive: true });
16028
+ fs40.writeFileSync(rulePath(agent), `${rule}
15762
16029
  `);
15763
16030
  return { ok: true };
15764
16031
  } catch (err) {
@@ -15806,7 +16073,7 @@ function migrateFlowRules() {
15806
16073
  adoptRenamedRules();
15807
16074
  let here;
15808
16075
  try {
15809
- here = new Set(fs39.readdirSync(RULES));
16076
+ here = new Set(fs40.readdirSync(RULES));
15810
16077
  } catch {
15811
16078
  return [];
15812
16079
  }
@@ -15817,9 +16084,9 @@ function migrateFlowRules() {
15817
16084
  if (!from.length) continue;
15818
16085
  const parts = [ruleFile(owner.name), ...from.map(ruleFile)].filter(Boolean);
15819
16086
  try {
15820
- if (parts.length) fs39.writeFileSync(rulePath(owner.name), `${parts.join("\n\n")}
16087
+ if (parts.length) fs40.writeFileSync(rulePath(owner.name), `${parts.join("\n\n")}
15821
16088
  `);
15822
- for (const flow of from) fs39.rmSync(rulePath(flow), { force: true });
16089
+ for (const flow of from) fs40.rmSync(rulePath(flow), { force: true });
15823
16090
  } catch (err) {
15824
16091
  notes.push(`could not move the ${from.join(", ")} rule(s) onto \`${owner.name}\`: ${String(err)}`);
15825
16092
  continue;
@@ -15833,7 +16100,7 @@ function migrateFlowRules() {
15833
16100
  function adoptRenamedRules() {
15834
16101
  let here;
15835
16102
  try {
15836
- here = fs39.readdirSync(RULES);
16103
+ here = fs40.readdirSync(RULES);
15837
16104
  } catch {
15838
16105
  return;
15839
16106
  }
@@ -15843,13 +16110,13 @@ function adoptRenamedRules() {
15843
16110
  const now = RENAMED_ROLES[was] ?? canonicalSpecAgent(was);
15844
16111
  if (now === was) continue;
15845
16112
  try {
15846
- if (!fs39.existsSync(rulePath(now))) fs39.renameSync(rulePath(was), rulePath(now));
16113
+ if (!fs40.existsSync(rulePath(now))) fs40.renameSync(rulePath(was), rulePath(now));
15847
16114
  else if (FOLDED_SPEC_AGENTS.has(was)) {
15848
16115
  const kept2 = ruleFile(now);
15849
16116
  const carried = ruleFile(was);
15850
- if (carried && !kept2.includes(carried)) fs39.writeFileSync(rulePath(now), `${[kept2, carried].filter(Boolean).join("\n\n")}
16117
+ if (carried && !kept2.includes(carried)) fs40.writeFileSync(rulePath(now), `${[kept2, carried].filter(Boolean).join("\n\n")}
15851
16118
  `);
15852
- fs39.rmSync(rulePath(was), { force: true });
16119
+ fs40.rmSync(rulePath(was), { force: true });
15853
16120
  }
15854
16121
  } catch {
15855
16122
  }
@@ -15866,7 +16133,7 @@ var init_rules2 = __esm({
15866
16133
  init_runner();
15867
16134
  init_workflows();
15868
16135
  init_types3();
15869
- rulePath = (agent) => path43.join(RULES, `${agent}.md`);
16136
+ rulePath = (agent) => path44.join(RULES, `${agent}.md`);
15870
16137
  notOnTheRoster = (name) => `"${name}" is not an agent on this board. It has: ${agentNames().join(", ")}.`;
15871
16138
  flowForRequest = (req) => req.refineRound !== void 0 && REFINE_ACTIONS.has(req.action) ? flowByCommand("refine") : flowByAction(req.action);
15872
16139
  ruleOwnerSays = (owner) => `\`${owner.name}\` agent carries one rule of its own${owner.role ? ", on every flow it runs" : ""}`;
@@ -15951,8 +16218,8 @@ var init_source = __esm({
15951
16218
  });
15952
16219
 
15953
16220
  // ../cli/src/lib/card-sources.ts
15954
- import fs40 from "node:fs";
15955
- import path44 from "node:path";
16221
+ import fs41 from "node:fs";
16222
+ import path45 from "node:path";
15956
16223
  function planById(id) {
15957
16224
  const found2 = readPlan(`plans/${id}-.md`);
15958
16225
  return found2?.text.trim() ? found2 : null;
@@ -15962,8 +16229,8 @@ function cardLink(id) {
15962
16229
  const found2 = open3 ?? locateArchived(id);
15963
16230
  if (!found2) return null;
15964
16231
  try {
15965
- const file = found2.kind === "group" ? path44.join(found2.target, "root.md") : found2.target;
15966
- const title = parseFrontmatter(fs40.readFileSync(file, "utf8")).meta?.title ?? "";
16232
+ const file = found2.kind === "group" ? path45.join(found2.target, "root.md") : found2.target;
16233
+ const title = parseFrontmatter(fs41.readFileSync(file, "utf8")).meta?.title ?? "";
15967
16234
  return { kind: "card", ref: String(id), title, ...open3 ? {} : { archived: true } };
15968
16235
  } catch {
15969
16236
  return null;
@@ -15988,7 +16255,7 @@ function link(ref, items) {
15988
16255
  if (ref.kind === "plan") return planLink(ref.id);
15989
16256
  if (ref.kind === "card") return cardLink(ref.id);
15990
16257
  if (ref.kind === "url") return urlLink(ref.url);
15991
- const item = items().find((signal2) => path44.basename(signal2.relPath) === ref.file);
16258
+ const item = items().find((signal2) => path45.basename(signal2.relPath) === ref.file);
15992
16259
  return item ? itemLink(item) : null;
15993
16260
  }
15994
16261
  function cardSources(meta, body) {
@@ -16029,12 +16296,12 @@ var init_card_sources = __esm({
16029
16296
  });
16030
16297
 
16031
16298
  // ../cli/src/lib/view/read.ts
16032
- import fs41 from "node:fs";
16033
- import path45 from "node:path";
16299
+ import fs42 from "node:fs";
16300
+ import path46 from "node:path";
16034
16301
  function readCard(file, relFromTodo) {
16035
- const id = idPrefix(path45.basename(relFromTodo));
16302
+ const id = idPrefix(path46.basename(relFromTodo));
16036
16303
  if (id === null) {
16037
- const parts = relFromTodo.split(path45.sep);
16304
+ const parts = relFromTodo.split(path46.sep);
16038
16305
  const folderId = idPrefix(parts[parts.length - 2] || "");
16039
16306
  if (folderId === null) return null;
16040
16307
  return buildCard(folderId, file, relFromTodo);
@@ -16042,10 +16309,10 @@ function readCard(file, relFromTodo) {
16042
16309
  return buildCard(id, file, relFromTodo);
16043
16310
  }
16044
16311
  function buildCard(id, file, relFromTodo) {
16045
- const text5 = fs41.readFileSync(file, "utf8");
16312
+ const text5 = fs42.readFileSync(file, "utf8");
16046
16313
  const { meta, body } = parseFrontmatter(text5);
16047
16314
  if (!meta) return null;
16048
- const relPath = relFromTodo.split(path45.sep).join("/");
16315
+ const relPath = relFromTodo.split(path46.sep).join("/");
16049
16316
  const flow = workflowFor(meta.workflow);
16050
16317
  const plan = !!meta.workflow && archivesOnApproval(flow);
16051
16318
  const stages = cardStages(meta, flow).length;
@@ -16054,7 +16321,7 @@ function buildCard(id, file, relFromTodo) {
16054
16321
  // Derived from the file exactly as it is on disk (lib/board/revision.ts), so a card
16055
16322
  // read here and a card read by the contract agree on what version this is.
16056
16323
  revision: revisionOf(text5),
16057
- updatedAt: fs41.statSync(file).mtime.toISOString(),
16324
+ updatedAt: fs42.statSync(file).mtime.toISOString(),
16058
16325
  relPath,
16059
16326
  title: meta.title,
16060
16327
  priority: meta.priority,
@@ -16075,22 +16342,22 @@ function buildCard(id, file, relFromTodo) {
16075
16342
  };
16076
16343
  }
16077
16344
  function readGroup2(folderName) {
16078
- const groupDir = path45.join(TODO, folderName);
16079
- const rootFile = path45.join(groupDir, "root.md");
16080
- if (!fs41.existsSync(rootFile)) return null;
16081
- const root = readCard(rootFile, path45.join(folderName, "root.md"));
16345
+ const groupDir = path46.join(TODO, folderName);
16346
+ const rootFile = path46.join(groupDir, "root.md");
16347
+ if (!fs42.existsSync(rootFile)) return null;
16348
+ const root = readCard(rootFile, path46.join(folderName, "root.md"));
16082
16349
  if (!root) return null;
16083
16350
  root.isGroup = true;
16084
16351
  const { total, resolved: resolved2 } = subtaskLines(root.body);
16085
16352
  root.subtaskLines = { total, resolved: resolved2 };
16086
16353
  const subCards = [];
16087
16354
  const recurse = (dir, relDir) => {
16088
- for (const entry of fs41.readdirSync(dir, { withFileTypes: true })) {
16089
- const childRel = path45.join(relDir, entry.name);
16355
+ for (const entry of fs42.readdirSync(dir, { withFileTypes: true })) {
16356
+ const childRel = path46.join(relDir, entry.name);
16090
16357
  if (entry.isDirectory()) {
16091
- recurse(path45.join(dir, entry.name), childRel);
16358
+ recurse(path46.join(dir, entry.name), childRel);
16092
16359
  } else if (entry.name.endsWith(".md") && entry.name !== "README.md" && entry.name !== "root.md") {
16093
- const c = readCard(path45.join(dir, entry.name), childRel);
16360
+ const c = readCard(path46.join(dir, entry.name), childRel);
16094
16361
  if (c) {
16095
16362
  c.parent = { id: root.id, title: root.title };
16096
16363
  subCards.push(c);
@@ -16111,12 +16378,12 @@ function readGroup2(folderName) {
16111
16378
  return { root, subCards };
16112
16379
  }
16113
16380
  function standaloneCards(dirRel) {
16114
- const dir = dirRel ? path45.join(TODO, dirRel) : TODO;
16115
- if (!fs41.existsSync(dir)) return [];
16381
+ const dir = dirRel ? path46.join(TODO, dirRel) : TODO;
16382
+ if (!fs42.existsSync(dir)) return [];
16116
16383
  const cards = [];
16117
- for (const entry of fs41.readdirSync(dir, { withFileTypes: true })) {
16384
+ for (const entry of fs42.readdirSync(dir, { withFileTypes: true })) {
16118
16385
  if (entry.isFile() && entry.name.endsWith(".md") && entry.name !== "README.md") {
16119
- const c = readCard(path45.join(dir, entry.name), path45.join(dirRel, entry.name));
16386
+ const c = readCard(path46.join(dir, entry.name), path46.join(dirRel, entry.name));
16120
16387
  if (c) cards.push(c);
16121
16388
  }
16122
16389
  }
@@ -16128,7 +16395,7 @@ function collectCards() {
16128
16395
  const top = standaloneCards("");
16129
16396
  board2.push(...top);
16130
16397
  every.push(...top);
16131
- for (const entry of fs41.readdirSync(TODO, { withFileTypes: true })) {
16398
+ for (const entry of fs42.readdirSync(TODO, { withFileTypes: true })) {
16132
16399
  if (!entry.isDirectory()) continue;
16133
16400
  if (isGroupFolder(entry.name)) {
16134
16401
  const g = readGroup2(entry.name);
@@ -16160,7 +16427,7 @@ function findCard(id) {
16160
16427
  }
16161
16428
  function attachSources(card) {
16162
16429
  try {
16163
- const { meta, body } = parseFrontmatter(fs41.readFileSync(path45.join(TODO, card.relPath), "utf8"));
16430
+ const { meta, body } = parseFrontmatter(fs42.readFileSync(path46.join(TODO, card.relPath), "utf8"));
16164
16431
  const sources = meta ? cardSources(meta, body) : [];
16165
16432
  if (sources.length) card.sources = sources;
16166
16433
  } catch {
@@ -16348,8 +16615,8 @@ var init_review = __esm({
16348
16615
  });
16349
16616
 
16350
16617
  // ../cli/src/lib/agent/deliveries.ts
16351
- import fs42 from "node:fs";
16352
- import path46 from "node:path";
16618
+ import fs43 from "node:fs";
16619
+ import path47 from "node:path";
16353
16620
  function writeAudit(delivery, runs) {
16354
16621
  const known2 = new Map(readAudit(delivery.deliveryId)?.sessions.map((s) => [s.sessionId, s]) ?? []);
16355
16622
  const sessions = delivery.sessions.flatMap((id) => {
@@ -16377,10 +16644,10 @@ function writeAudit(delivery, runs) {
16377
16644
  }
16378
16645
  function writeAuditFile(record) {
16379
16646
  try {
16380
- fs42.mkdirSync(DELIVERIES, { recursive: true });
16647
+ fs43.mkdirSync(DELIVERIES, { recursive: true });
16381
16648
  const tmp = `${auditPath(record.deliveryId)}.tmp`;
16382
- fs42.writeFileSync(tmp, JSON.stringify(record, null, 2) + "\n");
16383
- fs42.renameSync(tmp, auditPath(record.deliveryId));
16649
+ fs43.writeFileSync(tmp, JSON.stringify(record, null, 2) + "\n");
16650
+ fs43.renameSync(tmp, auditPath(record.deliveryId));
16384
16651
  return true;
16385
16652
  } catch {
16386
16653
  return false;
@@ -16388,7 +16655,7 @@ function writeAuditFile(record) {
16388
16655
  }
16389
16656
  function readAudit(deliveryId) {
16390
16657
  try {
16391
- const data = JSON.parse(fs42.readFileSync(auditPath(deliveryId), "utf8"));
16658
+ const data = JSON.parse(fs43.readFileSync(auditPath(deliveryId), "utf8"));
16392
16659
  return { sessions: Array.isArray(data?.sessions) ? data.sessions : [] };
16393
16660
  } catch {
16394
16661
  return null;
@@ -16403,7 +16670,7 @@ function syncAudit(deliveryId, just) {
16403
16670
  }
16404
16671
  function wholeAudit(file) {
16405
16672
  try {
16406
- const data = JSON.parse(fs42.readFileSync(file, "utf8"));
16673
+ const data = JSON.parse(fs43.readFileSync(file, "utf8"));
16407
16674
  return typeof data?.deliveryId === "string" && Array.isArray(data.sessions) ? data : null;
16408
16675
  } catch {
16409
16676
  return null;
@@ -16416,14 +16683,14 @@ function activeAudit(file) {
16416
16683
  function allAudits() {
16417
16684
  let names4;
16418
16685
  try {
16419
- names4 = fs42.readdirSync(DELIVERIES);
16686
+ names4 = fs43.readdirSync(DELIVERIES);
16420
16687
  } catch {
16421
16688
  return [];
16422
16689
  }
16423
16690
  const out = [];
16424
16691
  for (const name of names4) {
16425
16692
  if (!name.endsWith(".json")) continue;
16426
- const audit = wholeAudit(path46.join(DELIVERIES, name));
16693
+ const audit = wholeAudit(path47.join(DELIVERIES, name));
16427
16694
  if (audit) out.push(audit);
16428
16695
  }
16429
16696
  return out;
@@ -16534,11 +16801,11 @@ function resumeRefusal(delivery, store) {
16534
16801
  );
16535
16802
  }
16536
16803
  if (!worktreeExists(delivery.worktree)) {
16537
- const path85 = delivery.worktree ?? "";
16804
+ const path87 = delivery.worktree ?? "";
16538
16805
  return refusal(
16539
16806
  "deliveryWorktreeGone",
16540
- `delivery ${id}'s worktree ${path85} is gone, so there is nothing to carry on. ${rebuild}`,
16541
- { id, task, path: path85, command: discard }
16807
+ `delivery ${id}'s worktree ${path87} is gone, so there is nothing to carry on. ${rebuild}`,
16808
+ { id, task, path: path87, command: discard }
16542
16809
  );
16543
16810
  }
16544
16811
  if (!branchExists(delivery.branch)) {
@@ -16591,11 +16858,12 @@ function tidyCheckout(deliveryId) {
16591
16858
  const store = readStore();
16592
16859
  const row = store.deliveries.find((d) => d.deliveryId === deliveryId) ?? auditOnly(deliveryId);
16593
16860
  if (!row || row.status === "active") return NOTHING;
16594
- if (!row.worktree && !row.branch) return NOTHING;
16595
- if (isResumable(row)) return { removed: false, kept: true };
16596
16861
  if (store.runs.some((r) => r.deliveryId === deliveryId && runIsLive(r))) {
16597
16862
  return { removed: false, kept: false, error: `a run of delivery ${deliveryId} is still going`, reason: "deliveryRunGoing", args: { id: deliveryId } };
16598
16863
  }
16864
+ if (row.status !== "finished" || Date.now() - (row.endedAt ?? 0) > 3e4) stopWatchers(row.sessions);
16865
+ if (!row.worktree && !row.branch) return NOTHING;
16866
+ if (isResumable(row)) return { removed: false, kept: true };
16599
16867
  const removed = removeWorktree(row.worktree, row.branch, true);
16600
16868
  if (!removed.ok) return { removed: false, kept: false, error: removed.error };
16601
16869
  forgetCheckout(deliveryId);
@@ -16618,7 +16886,10 @@ function forgetCheckout(deliveryId) {
16618
16886
  function sweepCheckouts() {
16619
16887
  const seen = /* @__PURE__ */ new Set();
16620
16888
  const complaints = [];
16621
- const rows = [...readStore().deliveries, ...allAudits().flatMap((a) => auditRow(a) ?? [])];
16889
+ const store = readStore();
16890
+ const rows = [...store.deliveries, ...allAudits().flatMap((a) => auditRow(a) ?? [])];
16891
+ const ended = rows.filter((row) => row.status !== "active" && (row.status !== "finished" || Date.now() - (row.endedAt ?? 0) > 3e4) && !store.runs.some((r) => r.deliveryId === row.deliveryId && runIsLive(r)));
16892
+ stopWatchers([...new Set(ended.flatMap((row) => row.sessions))]);
16622
16893
  for (const row of rows) {
16623
16894
  if (seen.has(row.deliveryId) || row.status === "active" || !row.worktree) continue;
16624
16895
  seen.add(row.deliveryId);
@@ -16628,21 +16899,27 @@ function sweepCheckouts() {
16628
16899
  dropEmptyWorktreeFolders();
16629
16900
  return complaints;
16630
16901
  }
16631
- function resumeRecord(id) {
16632
- const found2 = endedDelivery(id);
16902
+ function resumeRecord(id, execution) {
16903
+ const continueActive = !!execution;
16904
+ const found2 = (continueActive ? namedDelivery(id) : void 0) ?? endedDelivery(id);
16633
16905
  if (!found2) return { ok: false, ...unknownDelivery(id) };
16634
16906
  const refused = resumeRefusal(found2);
16635
- if (refused) return { ok: false, ...refused };
16907
+ if (refused && !(continueActive && found2.status === "active")) return { ok: false, ...refused };
16636
16908
  const out = withStore((store) => {
16637
16909
  const audit = wholeAudit(auditPath(found2.deliveryId));
16638
- const row = (audit && auditRow(audit)) ?? store.deliveries.find((d) => d.deliveryId === found2.deliveryId);
16910
+ const live = store.deliveries.find((d) => d.deliveryId === found2.deliveryId);
16911
+ const row = continueActive && live?.status === "active" ? live : (audit && auditRow(audit)) ?? live;
16639
16912
  if (!row) return { ok: false, ...unknownDelivery(id) };
16640
16913
  const again = resumeRefusal(row, store);
16641
- if (again) return { ok: false, ...again };
16914
+ if (continueActive && row.status === "active") {
16915
+ if (store.runs.some((r) => r.deliveryId === row.deliveryId && runIsLive(r))) {
16916
+ return { ok: false, ...refusal("deliveryRunGoing", "the delivery still has a running agent; stop it before continuing") };
16917
+ }
16918
+ } else if (again) return { ok: false, ...again };
16642
16919
  const at = Date.now();
16643
16920
  const resumed = {
16644
16921
  ...row,
16645
- cloudExecution: executionOf() ?? row.cloudExecution,
16922
+ cloudExecution: execution ?? executionOf() ?? row.cloudExecution,
16646
16923
  status: "active",
16647
16924
  endedAt: void 0,
16648
16925
  steps: [...row.steps, { step: "resume", at }],
@@ -16671,10 +16948,10 @@ function carryOnFrom(deliveryId) {
16671
16948
  function approvedRequirements(cardId2) {
16672
16949
  const found2 = locate(cardId2);
16673
16950
  if (!found2) return "";
16674
- const file = found2.kind === "group" ? path46.join(found2.target, "root.md") : found2.target;
16951
+ const file = found2.kind === "group" ? path47.join(found2.target, "root.md") : found2.target;
16675
16952
  let text5;
16676
16953
  try {
16677
- text5 = fs42.readFileSync(file, "utf8");
16954
+ text5 = fs43.readFileSync(file, "utf8");
16678
16955
  } catch {
16679
16956
  return "";
16680
16957
  }
@@ -16704,8 +16981,8 @@ function cardMeta2(cardId2) {
16704
16981
  try {
16705
16982
  const found2 = locate(cardId2);
16706
16983
  if (!found2) return null;
16707
- const file = found2.kind === "group" ? path46.join(found2.target, "root.md") : found2.target;
16708
- return parseFrontmatter(fs42.readFileSync(file, "utf8")).meta;
16984
+ const file = found2.kind === "group" ? path47.join(found2.target, "root.md") : found2.target;
16985
+ return parseFrontmatter(fs43.readFileSync(file, "utf8")).meta;
16709
16986
  } catch {
16710
16987
  return null;
16711
16988
  }
@@ -16954,6 +17231,7 @@ var init_deliveries = __esm({
16954
17231
  init_complete();
16955
17232
  init_outputs();
16956
17233
  init_env();
17234
+ init_stop();
16957
17235
  init_flows();
16958
17236
  init_pause();
16959
17237
  init_rules2();
@@ -16963,7 +17241,7 @@ var init_deliveries = __esm({
16963
17241
  init_store();
16964
17242
  init_types3();
16965
17243
  init_worktree();
16966
- auditPath = (deliveryId) => path46.join(DELIVERIES, `${deliveryId}.json`);
17244
+ auditPath = (deliveryId) => path47.join(DELIVERIES, `${deliveryId}.json`);
16967
17245
  activeAudits = () => allAudits().filter((a) => a.status === "active");
16968
17246
  auditRow = (audit) => readDeliveryRow({ ...audit, sessions: audit.sessions.map((s) => s.sessionId) });
16969
17247
  lastSessionRunning = (audit) => audit.sessions[audit.sessions.length - 1]?.status === "running";
@@ -16991,6 +17269,41 @@ var init_deliveries = __esm({
16991
17269
  }
16992
17270
  });
16993
17271
 
17272
+ // ../cli/src/lib/agent/landing-lock.ts
17273
+ import fs44 from "node:fs";
17274
+ import path48 from "node:path";
17275
+ function lockLanding() {
17276
+ const dir = path48.join(SESSIONS_DIR, ".landing.lock");
17277
+ try {
17278
+ const entries = fs44.readdirSync(dir);
17279
+ const token3 = entries.length === 1 ? entries[0] : "";
17280
+ const match = /^(\d+)-[a-f0-9-]{36}$/.exec(token3);
17281
+ const stat = fs44.statSync(dir);
17282
+ if (match && Date.now() - stat.mtimeMs > landingLimits.staleMs) {
17283
+ stopWatchers(void 0, Number(match[1]), () => {
17284
+ const now = fs44.readdirSync(dir);
17285
+ return now.length === 1 && now[0] === token3 && Date.now() - fs44.statSync(dir).mtimeMs > landingLimits.staleMs;
17286
+ }, stat.birthtimeMs || stat.mtimeMs);
17287
+ }
17288
+ } catch {
17289
+ }
17290
+ return tryLock(dir);
17291
+ }
17292
+ function renewLandingLock() {
17293
+ const now = /* @__PURE__ */ new Date();
17294
+ fs44.utimesSync(path48.join(SESSIONS_DIR, ".landing.lock"), now, now);
17295
+ }
17296
+ var landingLimits;
17297
+ var init_landing_lock = __esm({
17298
+ "../cli/src/lib/agent/landing-lock.ts"() {
17299
+ "use strict";
17300
+ init_lock();
17301
+ init_paths();
17302
+ init_stop();
17303
+ landingLimits = { staleMs: 5 * 6e4 };
17304
+ }
17305
+ });
17306
+
16994
17307
  // ../cli/src/lib/agent/answers.ts
16995
17308
  function answerOutcome(delivery) {
16996
17309
  return pendingAnswers(delivery).some((a) => a.outcome === "changed") ? "changed" : "unchanged";
@@ -17055,8 +17368,8 @@ var init_retry = __esm({
17055
17368
  });
17056
17369
 
17057
17370
  // ../cli/src/lib/agent/landing.ts
17058
- import fs43 from "node:fs";
17059
- import path47 from "node:path";
17371
+ import fs45 from "node:fs";
17372
+ import path49 from "node:path";
17060
17373
  function waitingOut(delivery, now = Date.now()) {
17061
17374
  const landing = delivery.landing;
17062
17375
  if (!landing) return false;
@@ -17192,8 +17505,9 @@ function noteQueue(held4) {
17192
17505
  }
17193
17506
  async function advanceLanding() {
17194
17507
  let release;
17508
+ let current;
17195
17509
  try {
17196
- release = tryLock(path47.join(SESSIONS_DIR, ".landing.lock"));
17510
+ release = lockLanding();
17197
17511
  if (!release) return null;
17198
17512
  const owed = await owedRestart();
17199
17513
  if (owed) return owed;
@@ -17208,11 +17522,19 @@ async function advanceLanding() {
17208
17522
  return null;
17209
17523
  }
17210
17524
  tried.add(picked.deliveryId);
17525
+ current = picked;
17526
+ renewLandingLock();
17211
17527
  const step = await landStep(picked);
17212
17528
  if (step.start) return step.start;
17213
17529
  if (!step.done) return null;
17214
17530
  }
17215
- } catch {
17531
+ } catch (error) {
17532
+ if (current && error instanceof GitTimeoutError) {
17533
+ giveUpSlot(current, error.message);
17534
+ patchLanding(current.deliveryId, (landing) => {
17535
+ landing.retryAt = Date.now() + 3e4;
17536
+ });
17537
+ }
17216
17538
  return null;
17217
17539
  } finally {
17218
17540
  release?.();
@@ -17529,7 +17851,7 @@ async function settleAlreadyLanded(delivery) {
17529
17851
  return true;
17530
17852
  }
17531
17853
  function repairLanding() {
17532
- const release = tryLock(path47.join(SESSIONS_DIR, ".landing.lock"));
17854
+ const release = lockLanding();
17533
17855
  if (!release) return [];
17534
17856
  try {
17535
17857
  return repairIdleLanding();
@@ -17562,7 +17884,7 @@ var init_landing = __esm({
17562
17884
  init_execution();
17563
17885
  init_board();
17564
17886
  init_io();
17565
- init_lock();
17887
+ init_landing_lock();
17566
17888
  init_paths();
17567
17889
  init_answers();
17568
17890
  init_commit_mode();
@@ -17603,9 +17925,9 @@ var init_landing = __esm({
17603
17925
  sameDir = (a, b) => {
17604
17926
  const real2 = (dir) => {
17605
17927
  try {
17606
- return fs43.realpathSync(dir);
17928
+ return fs45.realpathSync(dir);
17607
17929
  } catch {
17608
- return path47.resolve(dir);
17930
+ return path49.resolve(dir);
17609
17931
  }
17610
17932
  };
17611
17933
  return real2(a) === real2(b);
@@ -17614,35 +17936,35 @@ var init_landing = __esm({
17614
17936
  });
17615
17937
 
17616
17938
  // ../cli/src/lib/agent/pictures.ts
17617
- import fs44 from "node:fs";
17618
- import path48 from "node:path";
17939
+ import fs46 from "node:fs";
17940
+ import path50 from "node:path";
17619
17941
  function claimRunPictures(box2, sessionId, names4 = []) {
17620
17942
  if (!box2 || !freeBox(box2) || names4.length === 0) return [];
17621
17943
  const from = pictureBox(box2);
17622
17944
  const to = pictureBox(sessionId);
17623
17945
  try {
17624
- fs44.mkdirSync(SESSIONS_DIR, { recursive: true });
17625
- fs44.renameSync(from, to);
17946
+ fs46.mkdirSync(SESSIONS_DIR, { recursive: true });
17947
+ fs46.renameSync(from, to);
17626
17948
  } catch {
17627
17949
  return [];
17628
17950
  }
17629
- return names4.filter(pictureName).map((name) => path48.join(to, name)).filter((file) => fs44.existsSync(file));
17951
+ return names4.filter(pictureName).map((name) => path50.join(to, name)).filter((file) => fs46.existsSync(file));
17630
17952
  }
17631
17953
  function returnRunPictures(sessionId, box2) {
17632
17954
  if (!box2 || !boxId(box2)) return;
17633
17955
  try {
17634
- fs44.renameSync(pictureBox(sessionId), pictureBox(box2));
17956
+ fs46.renameSync(pictureBox(sessionId), pictureBox(box2));
17635
17957
  } catch {
17636
17958
  }
17637
17959
  }
17638
17960
  function moveRunPictures(from, to, files2 = []) {
17639
17961
  if (files2.length === 0) return [];
17640
17962
  try {
17641
- fs44.renameSync(pictureBox(from), pictureBox(to));
17963
+ fs46.renameSync(pictureBox(from), pictureBox(to));
17642
17964
  } catch {
17643
17965
  return files2;
17644
17966
  }
17645
- return files2.map((file) => path48.join(pictureBox(to), path48.basename(file)));
17967
+ return files2.map((file) => path50.join(pictureBox(to), path50.basename(file)));
17646
17968
  }
17647
17969
  var pictureName, boxId, freeBox, pictureBox;
17648
17970
  var init_pictures = __esm({
@@ -17651,14 +17973,14 @@ var init_pictures = __esm({
17651
17973
  init_paths();
17652
17974
  pictureName = (name) => /^[0-9a-f-]{36}\.[a-z0-9]{2,5}$/.test(name);
17653
17975
  boxId = (box2) => /^[0-9a-f-]{36}$/.test(box2);
17654
- freeBox = (box2) => boxId(box2) && !fs44.existsSync(path48.join(SESSIONS_DIR, `${box2}.log`));
17655
- pictureBox = (box2) => path48.join(SESSIONS_DIR, `${box2}.images`);
17976
+ freeBox = (box2) => boxId(box2) && !fs46.existsSync(path50.join(SESSIONS_DIR, `${box2}.log`));
17977
+ pictureBox = (box2) => path50.join(SESSIONS_DIR, `${box2}.images`);
17656
17978
  }
17657
17979
  });
17658
17980
 
17659
17981
  // ../cli/src/lib/memories.ts
17660
- import fs45 from "node:fs";
17661
- import path49 from "node:path";
17982
+ import fs47 from "node:fs";
17983
+ import path51 from "node:path";
17662
17984
  function readMemory(text5, builtIn, folder) {
17663
17985
  const { meta, body } = splitFrontmatter(text5);
17664
17986
  if (meta === null) return null;
@@ -17678,7 +18000,7 @@ function readMemory(text5, builtIn, folder) {
17678
18000
  rules: body.trim(),
17679
18001
  text: text5,
17680
18002
  builtIn,
17681
- dir: path49.join(MEMORY, folder)
18003
+ dir: path51.join(MEMORY, folder)
17682
18004
  };
17683
18005
  }
17684
18006
  function globalMemories() {
@@ -17689,14 +18011,14 @@ function globalMemories() {
17689
18011
  }
17690
18012
  let folders2 = [];
17691
18013
  try {
17692
- folders2 = fs45.readdirSync(MEMORY, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).sort();
18014
+ folders2 = fs47.readdirSync(MEMORY, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).sort();
17693
18015
  } catch {
17694
18016
  }
17695
18017
  for (const name of folders2) {
17696
18018
  if (name in BUNDLED_MEMORY_FILES || name === RESERVED_MEMORY_DIR) continue;
17697
- const file = path49.join(MEMORY, name, GLOBAL_MEMORY_FILE);
17698
- if (!fs45.existsSync(file)) continue;
17699
- const memory = readMemory(fs45.readFileSync(file, "utf8"), false, name);
18019
+ const file = path51.join(MEMORY, name, GLOBAL_MEMORY_FILE);
18020
+ if (!fs47.existsSync(file)) continue;
18021
+ const memory = readMemory(fs47.readFileSync(file, "utf8"), false, name);
17700
18022
  if (memory) out.push(memory);
17701
18023
  }
17702
18024
  return out;
@@ -17752,16 +18074,16 @@ var init_facts = __esm({
17752
18074
  });
17753
18075
 
17754
18076
  // ../cli/src/lib/view/archive.ts
17755
- import fs46 from "node:fs";
17756
- import path50 from "node:path";
18077
+ import fs48 from "node:fs";
18078
+ import path52 from "node:path";
17757
18079
  function archivedId(file) {
17758
- const own = idPrefix(path50.basename(file));
17759
- return own !== null ? own : idPrefix(path50.basename(path50.dirname(file)));
18080
+ const own = idPrefix(path52.basename(file));
18081
+ return own !== null ? own : idPrefix(path52.basename(path52.dirname(file)));
17760
18082
  }
17761
18083
  function readFile(file) {
17762
18084
  const id = archivedId(file);
17763
18085
  if (id === null) return null;
17764
- const { meta, body } = parseFrontmatter(fs46.readFileSync(file, "utf8"));
18086
+ const { meta, body } = parseFrontmatter(fs48.readFileSync(file, "utf8"));
17765
18087
  if (!meta) return null;
17766
18088
  return {
17767
18089
  row: {
@@ -17769,15 +18091,15 @@ function readFile(file) {
17769
18091
  title: meta.title,
17770
18092
  archived: meta.archived,
17771
18093
  rejected: meta.rejected,
17772
- relPath: rel(file).split(path50.sep).join("/")
18094
+ relPath: rel(file).split(path52.sep).join("/")
17773
18095
  },
17774
18096
  body: body.replace(/^\n+/, "").replace(/\s+$/, ""),
17775
18097
  meta
17776
18098
  };
17777
18099
  }
17778
18100
  function readArchive() {
17779
- const relPath = rel(ARCHIVE).split(path50.sep).join("/");
17780
- if (!fs46.existsSync(ARCHIVE)) return { relPath, cards: [] };
18101
+ const relPath = rel(ARCHIVE).split(path52.sep).join("/");
18102
+ if (!fs48.existsSync(ARCHIVE)) return { relPath, cards: [] };
17781
18103
  const cards = walkMd(ARCHIVE).map(readFile).filter((found2) => found2 !== null).map((found2) => found2.row);
17782
18104
  cards.sort((a, b) => b.id - a.id);
17783
18105
  return { relPath, cards };
@@ -17792,7 +18114,7 @@ var init_archive = __esm({
17792
18114
  });
17793
18115
 
17794
18116
  // ../cli/src/lib/agent/prompts.ts
17795
- import path51 from "node:path";
18117
+ import path53 from "node:path";
17796
18118
  function resumePrompt(deliveryId, cardId2, action) {
17797
18119
  if (!deliveryId) return RESUME_PROMPT;
17798
18120
  if (action === "conflict") {
@@ -17836,7 +18158,7 @@ function buildAsk(rawReq, notes = []) {
17836
18158
  }
17837
18159
  function cardFileOf(id) {
17838
18160
  const found2 = id === void 0 ? null : locate(id);
17839
- return found2 ? found2.kind === "group" ? path51.join(found2.target, "root.md") : found2.target : "";
18161
+ return found2 ? found2.kind === "group" ? path53.join(found2.target, "root.md") : found2.target : "";
17840
18162
  }
17841
18163
  function realPaths(text5, req) {
17842
18164
  if (req.action !== "scheduled" && req.action !== "sub") return text5;
@@ -17929,7 +18251,7 @@ function frozenRules(req) {
17929
18251
  function archivedCardFile(id) {
17930
18252
  const found2 = locateArchived(id);
17931
18253
  if (!found2) return `${rel(ARCHIVE)}/`;
17932
- return rel(found2.kind === "group" ? path51.join(found2.target, "root.md") : found2.target);
18254
+ return rel(found2.kind === "group" ? path53.join(found2.target, "root.md") : found2.target);
17933
18255
  }
17934
18256
  function shippedLines(cardId2) {
17935
18257
  const delivery = endedDelivery(`${cardId2}`);
@@ -18100,7 +18422,7 @@ function actionPrompt(req, command, notes) {
18100
18422
  case "spec": {
18101
18423
  const agent = findSpecAgent(req.specAgent ?? "");
18102
18424
  const found2 = req.id === void 0 ? null : locate(req.id);
18103
- const cardFile3 = found2 ? rel(found2.kind === "group" ? path51.join(found2.target, "root.md") : found2.target) : `task #${req.id}`;
18425
+ const cardFile3 = found2 ? rel(found2.kind === "group" ? path53.join(found2.target, "root.md") : found2.target) : `task #${req.id}`;
18104
18426
  const own = agent ? specAgentInstructions(agent) : null;
18105
18427
  if (own) notes.push(...own.notes);
18106
18428
  const memory = agent ? agentMemoryBlock(agent) : "";
@@ -18212,289 +18534,82 @@ ${shared}` : ""
18212
18534
  `${kb}. Task ${req.id} ${named2} has been approved: write the previews that were waiting on that Approve from the card as it stands.`,
18213
18535
  req.notes ? `Extra notes: ${req.notes}` : "",
18214
18536
  `Don't ask me questions with human-in-the-loop. Leave any questions as open questions.`
18215
- ].filter(Boolean).join(" ");
18216
- case "resolve":
18217
- return [
18218
- `${kb}. Apply my answers to the open questions on task ${req.id} ${named2} following \`akb guide resolve\`.`,
18219
- carriedNote(req),
18220
- answeredNote(req.id, command),
18221
- req.notes ? `Extra notes: ${req.notes}` : ""
18222
- ].filter(Boolean).join(" ");
18223
- }
18224
- }
18225
- function discardedCardsPrompt(cards = []) {
18226
- if (!cards.length) return "";
18227
- return "The user discarded the cards listed below. Their absence is intentional. Do not restore or recreate them, including under new IDs or filenames. Continue the remaining work from the current board, preserving the other cards.\n\n" + cards.map((c) => `- #${c.id}: ${c.path}`).join("\n");
18228
- }
18229
- var RESUME_PROMPT, DELIVERY_RESUME, DELIVERY_RESUME_CARD, RESTARTABLE, RESTART_LEAD, restartable, NO_IMPLEMENT, BOARD_FOLDER, CARD_FILE, PROJECT_FOLDER, PLANNING_FOR, SPEC_SELECTOR_FOR, reflectedCards;
18230
- var init_prompts = __esm({
18231
- "../cli/src/lib/agent/prompts.ts"() {
18232
- "use strict";
18233
- init_cards();
18234
- init_memory();
18235
- init_memories();
18236
- init_paths();
18237
- init_agents();
18238
- init_command();
18239
- init_deliveries();
18240
- init_env();
18241
- init_facts();
18242
- init_outputs();
18243
- init_archive();
18244
- init_flows();
18245
- init_resolve();
18246
- init_runner();
18247
- init_workflows();
18248
- init_catalog2();
18249
- init_roles();
18250
- init_rules2();
18251
- init_settings2();
18252
- init_types3();
18253
- RESUME_PROMPT = [
18254
- `Continue. The previous run ended before it finished the task.`,
18255
- `Pick up where you left off and carry it through.`
18256
- ].join(" ");
18257
- DELIVERY_RESUME = [
18258
- `Continue delivery %s. The previous run ended before the delivery finished.`,
18259
- `Re-enter the flow from the top and check each step's precondition before you do it \u2014 work that is already done is done, so do not repeat it.`
18260
- ].join(" ");
18261
- DELIVERY_RESUME_CARD = `Build the card as the delivery holds it, not as the file reads now: \`%c\` prints the approved copy.`;
18262
- RESTARTABLE = /* @__PURE__ */ new Set([
18263
- "implement",
18264
- "clarify",
18265
- "resolve",
18266
- "approve",
18267
- "archive",
18268
- "spec",
18269
- "conflict",
18270
- "scheduled"
18271
- ]);
18272
- RESTART_LEAD = [
18273
- `The session you were continuing is gone, and this one holds nothing of it.`,
18274
- `Do the task below from the top, checking each step's precondition before you do it \u2014 work an earlier run already finished is done, so don't repeat it.`
18275
- ].join(" ");
18276
- restartable = (run2) => (run2.cardId !== null || !!run2.deliveryId) && RESTARTABLE.has(run2.action);
18277
- NO_IMPLEMENT = `(Unless the request explicitly asks for implementation, don't implement it; revising the card's drafts is not implementing.)`;
18278
- BOARD_FOLDER = "<board folder>";
18279
- CARD_FILE = "<card file>";
18280
- PROJECT_FOLDER = "<project folder>";
18281
- PLANNING_FOR = /* @__PURE__ */ new Set(["create", "clarify", "resolve", "approve", "edit"]);
18282
- SPEC_SELECTOR_FOR = /* @__PURE__ */ new Set(["clarify", "resolve", "approve", "edit"]);
18283
- reflectedCards = (req) => req.cards ?? (req.id !== void 0 ? [req.id] : []);
18284
- }
18285
- });
18286
-
18287
- // ../cli/src/lib/agent/stop.ts
18288
- import { spawnSync as spawnSync4 } from "node:child_process";
18289
- import { randomUUID as randomUUID5 } from "node:crypto";
18290
- import fs47 from "node:fs";
18291
- import os6 from "node:os";
18292
- function markedInProc(entry, strict = false) {
18293
- const pids = [];
18294
- for (const name of fs47.readdirSync("/proc")) {
18295
- if (!/^\d+$/.test(name)) continue;
18296
- try {
18297
- if (fs47.readFileSync(`/proc/${name}/environ`, "utf8").split("\0").includes(entry)) pids.push(Number(name));
18298
- } catch (error) {
18299
- if (strict && !["ENOENT", "ESRCH"].includes(error.code ?? "")) {
18300
- const stat = fs47.statSync(`/proc/${name}`, { throwIfNoEntry: false });
18301
- if (stat?.uid === process.getuid?.()) throw error;
18302
- }
18303
- }
18304
- }
18305
- return pids;
18306
- }
18307
- function markedInPs(entry, roots) {
18308
- const ps = spawnSync4("ps", ["-A", "-E", "-ww", "-o", "pid=,ppid=,command="], {
18309
- encoding: "utf8",
18310
- maxBuffer: 64 * 1024 * 1024,
18311
- timeout: 5e3
18312
- });
18313
- if (ps.status !== 0 || !ps.stdout) throw new Error("\u65E0\u6CD5\u67E5\u8BE2\u8FDB\u7A0B\u6811\u3002");
18314
- const children = /* @__PURE__ */ new Map();
18315
- const listed2 = /* @__PURE__ */ new Set();
18316
- const found2 = /* @__PURE__ */ new Set();
18317
- for (const line of ps.stdout.split("\n")) {
18318
- const m = /^\s*(\d+)\s+(\d+)\s(.*)$/.exec(line);
18319
- if (!m) continue;
18320
- const pid = Number(m[1]);
18321
- const ppid = Number(m[2]);
18322
- listed2.add(pid);
18323
- children.set(ppid, [...children.get(ppid) ?? [], pid]);
18324
- if (`${m[3]} `.includes(` ${entry} `)) found2.add(pid);
18325
- }
18326
- const queue = [...found2, ...roots.filter((pid) => listed2.has(pid))];
18327
- for (const pid of queue) {
18328
- found2.add(pid);
18329
- for (const child of children.get(pid) ?? []) if (!found2.has(child)) queue.push(child);
18330
- }
18331
- return [...found2];
18332
- }
18333
- function marked(mark, roots = []) {
18334
- if (WINDOWS3 || !mark.value) return [];
18335
- const entry = `${mark.name}=${mark.value}`;
18336
- try {
18337
- const pids = process.platform === "linux" ? markedInProc(entry) : markedInPs(entry, roots);
18338
- return pids.filter((pid) => pid !== process.pid);
18339
- } catch {
18340
- return [];
18341
- }
18342
- }
18343
- function signal(pids, sig) {
18344
- for (const pid of pids) {
18345
- try {
18346
- process.kill(pid, sig);
18347
- } catch {
18348
- }
18349
- }
18350
- }
18351
- function startedUnder(list, root, since) {
18352
- const self = list.procs.find((p) => p.pid === root);
18353
- const from = since ?? self?.startedAt;
18354
- if (from === void 0 || list.bootedAt > from) return [];
18355
- if (self && self.startedAt > from) return [];
18356
- const found2 = self ? [root] : [];
18357
- const seen = /* @__PURE__ */ new Set([root]);
18358
- const queue = [{ pid: root, startedAt: from }];
18359
- for (const parent of queue) {
18360
- for (const p of list.procs) {
18361
- if (p.ppid !== parent.pid || seen.has(p.pid) || p.startedAt < parent.startedAt) continue;
18362
- seen.add(p.pid);
18363
- found2.push(p.pid);
18364
- queue.push(p);
18365
- }
18366
- }
18367
- return found2.reverse();
18368
- }
18369
- function readProcList(out) {
18370
- const procs = [];
18371
- let bootedAt;
18372
- for (const line of out.split(/\r?\n/)) {
18373
- const boot = /^boot (\d+)$/.exec(line.trim());
18374
- if (boot) bootedAt = Number(boot[1]);
18375
- const m = /^(\d+) (\d+) (\d+)$/.exec(line.trim());
18376
- if (m) procs.push({ pid: Number(m[1]), ppid: Number(m[2]), startedAt: Number(m[3]) });
18377
- }
18378
- return bootedAt !== void 0 && procs.length ? { procs, bootedAt } : void 0;
18379
- }
18380
- function procsOnWindows() {
18381
- const ps = spawnSync4("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", LIST_PROCS], {
18382
- encoding: "utf8",
18383
- maxBuffer: 64 * 1024 * 1024,
18384
- timeout: 5e3,
18385
- windowsHide: true
18386
- });
18387
- return ps.status === 0 && ps.stdout ? readProcList(ps.stdout) : void 0;
18388
- }
18389
- function killUnderOnWindows(pid, since) {
18390
- if (!WINDOWS3 || !pid) return;
18391
- const list = procsOnWindows();
18392
- if (!list) return;
18393
- const pids = startedUnder(list, pid, since).filter((p) => p !== process.pid);
18394
- if (!pids.length) return;
18395
- spawnSync4("taskkill", ["/F", ...pids.flatMap((p) => ["/PID", String(p)])], { stdio: "ignore", windowsHide: true, timeout: 1e4 });
18396
- }
18397
- function killMarked(mark, agent) {
18398
- if (WINDOWS3) {
18399
- if (agent) killUnderOnWindows(agent.pid, agent.since);
18400
- return;
18401
- }
18402
- signal(marked(mark), "SIGKILL");
18403
- }
18404
- function killTreeOnWindows(pid) {
18405
- if (!WINDOWS3 || !pid) return false;
18406
- const out = spawnSync4("taskkill", ["/PID", String(pid), "/T", "/F"], { stdio: "ignore", windowsHide: true, timeout: 1e4 });
18407
- return out.status === 0;
18408
- }
18409
- function later(ms, fn) {
18410
- const t = setTimeout(fn, ms);
18411
- if (typeof t.unref === "function") t.unref();
18412
- }
18413
- function endAgent(child, mark, graceMs) {
18414
- const term = (sig) => {
18415
- try {
18416
- child.kill(sig);
18417
- } catch {
18418
- }
18419
- };
18420
- if (killTreeOnWindows(child.pid)) return;
18421
- killUnderOnWindows(child.pid);
18422
- const seen = new Set(marked(mark, child.pid ? [child.pid] : []));
18423
- term("SIGTERM");
18424
- signal([...seen].filter((pid) => pid !== child.pid), "SIGTERM");
18425
- const round = (left) => {
18426
- const pids = marked(mark, [...seen]);
18427
- signal(pids, "SIGKILL");
18428
- for (const pid of pids) seen.add(pid);
18429
- if (pids.length && left > 1) later(KILL_ROUND_MS, () => round(left - 1));
18430
- };
18431
- later(graceMs, () => {
18432
- term("SIGKILL");
18433
- round(KILL_ROUNDS);
18434
- });
18435
- }
18436
- function killRunning() {
18437
- for (const [child, mark] of running) {
18438
- if (killTreeOnWindows(child.pid)) continue;
18439
- killUnderOnWindows(child.pid);
18440
- signal(marked(mark, child.pid ? [child.pid] : []), "SIGKILL");
18441
- try {
18442
- child.kill("SIGKILL");
18443
- } catch {
18444
- }
18537
+ ].filter(Boolean).join(" ");
18538
+ case "resolve":
18539
+ return [
18540
+ `${kb}. Apply my answers to the open questions on task ${req.id} ${named2} following \`akb guide resolve\`.`,
18541
+ carriedNote(req),
18542
+ answeredNote(req.id, command),
18543
+ req.notes ? `Extra notes: ${req.notes}` : ""
18544
+ ].filter(Boolean).join(" ");
18445
18545
  }
18446
- running.clear();
18447
- }
18448
- function unlisten() {
18449
- for (const [sig, fn] of listening) process.removeListener(sig, fn);
18450
- listening.clear();
18451
- }
18452
- function onQuit(sig) {
18453
- killRunning();
18454
- unlisten();
18455
- if (process.listenerCount(sig) === 0) process.exit(128 + os6.constants.signals[sig]);
18456
18546
  }
18457
- function trackAgent(child, mark) {
18458
- if (!child.pid) return;
18459
- running.set(child, mark);
18460
- child.once("close", () => {
18461
- running.delete(child);
18462
- if (!running.size) unlisten();
18463
- });
18464
- for (const sig of QUIT_SIGNALS) {
18465
- if (listening.has(sig) || sig === "SIGINT" && interruptIsTheCallers) continue;
18466
- const fn = () => onQuit(sig);
18467
- listening.set(sig, fn);
18468
- process.on(sig, fn);
18469
- }
18547
+ function discardedCardsPrompt(cards = []) {
18548
+ if (!cards.length) return "";
18549
+ return "The user discarded the cards listed below. Their absence is intentional. Do not restore or recreate them, including under new IDs or filenames. Continue the remaining work from the current board, preserving the other cards.\n\n" + cards.map((c) => `- #${c.id}: ${c.path}`).join("\n");
18470
18550
  }
18471
- var runMark, stopMark, markEnv, WINDOWS3, KILL_ROUND_MS, KILL_ROUNDS, LIST_PROCS, running, QUIT_SIGNALS, listening, interruptIsTheCallers;
18472
- var init_stop = __esm({
18473
- "../cli/src/lib/agent/stop.ts"() {
18551
+ 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;
18552
+ var init_prompts = __esm({
18553
+ "../cli/src/lib/agent/prompts.ts"() {
18474
18554
  "use strict";
18555
+ init_cards();
18556
+ init_memory();
18557
+ init_memories();
18558
+ init_paths();
18559
+ init_agents();
18560
+ init_command();
18561
+ init_deliveries();
18475
18562
  init_env();
18476
- runMark = (sessionId) => ({ name: RUN_ENV, value: sessionId });
18477
- stopMark = () => ({ name: STOP_ENV, value: randomUUID5() });
18478
- markEnv = (env2, mark) => ({ ...env2, [mark.name]: mark.value });
18479
- WINDOWS3 = process.platform === "win32";
18480
- KILL_ROUND_MS = 300;
18481
- KILL_ROUNDS = 3;
18482
- LIST_PROCS = [
18483
- "$ErrorActionPreference = 'Stop'",
18484
- "Get-CimInstance Win32_Process | ForEach-Object { if ($_.CreationDate) { '{0} {1} {2}' -f $_.ProcessId, $_.ParentProcessId, ([DateTimeOffset]$_.CreationDate).ToUnixTimeMilliseconds() } }",
18485
- "'boot {0}' -f ([DateTimeOffset](Get-CimInstance Win32_OperatingSystem).LastBootUpTime).ToUnixTimeMilliseconds()"
18486
- ].join("; ");
18487
- running = /* @__PURE__ */ new Map();
18488
- QUIT_SIGNALS = ["SIGTERM", "SIGINT", "SIGHUP"];
18489
- listening = /* @__PURE__ */ new Map();
18490
- interruptIsTheCallers = false;
18563
+ init_facts();
18564
+ init_outputs();
18565
+ init_archive();
18566
+ init_flows();
18567
+ init_resolve();
18568
+ init_runner();
18569
+ init_workflows();
18570
+ init_catalog2();
18571
+ init_roles();
18572
+ init_rules2();
18573
+ init_settings2();
18574
+ init_types3();
18575
+ RESUME_PROMPT = [
18576
+ `Continue. The previous run ended before it finished the task.`,
18577
+ `Pick up where you left off and carry it through.`
18578
+ ].join(" ");
18579
+ DELIVERY_RESUME = [
18580
+ `Continue delivery %s. The previous run ended before the delivery finished.`,
18581
+ `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.`
18582
+ ].join(" ");
18583
+ DELIVERY_RESUME_CARD = `Build the card as the delivery holds it, not as the file reads now: \`%c\` prints the approved copy.`;
18584
+ RESTARTABLE = /* @__PURE__ */ new Set([
18585
+ "implement",
18586
+ "clarify",
18587
+ "resolve",
18588
+ "approve",
18589
+ "archive",
18590
+ "spec",
18591
+ "conflict",
18592
+ "scheduled"
18593
+ ]);
18594
+ RESTART_LEAD = [
18595
+ `The session you were continuing is gone, and this one holds nothing of it.`,
18596
+ `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.`
18597
+ ].join(" ");
18598
+ restartable = (run2) => (run2.cardId !== null || !!run2.deliveryId) && RESTARTABLE.has(run2.action);
18599
+ NO_IMPLEMENT = `(Unless the request explicitly asks for implementation, don't implement it; revising the card's drafts is not implementing.)`;
18600
+ BOARD_FOLDER = "<board folder>";
18601
+ CARD_FILE = "<card file>";
18602
+ PROJECT_FOLDER = "<project folder>";
18603
+ PLANNING_FOR = /* @__PURE__ */ new Set(["create", "clarify", "resolve", "approve", "edit"]);
18604
+ SPEC_SELECTOR_FOR = /* @__PURE__ */ new Set(["clarify", "resolve", "approve", "edit"]);
18605
+ reflectedCards = (req) => req.cards ?? (req.id !== void 0 ? [req.id] : []);
18491
18606
  }
18492
18607
  });
18493
18608
 
18494
18609
  // ../cli/src/lib/agent/sessions.ts
18495
18610
  import { randomUUID as randomUUID6 } from "node:crypto";
18496
- import fs48 from "node:fs";
18497
- import path52 from "node:path";
18611
+ import fs49 from "node:fs";
18612
+ import path54 from "node:path";
18498
18613
  function reap(runs, reaped = [], restore = []) {
18499
18614
  let changed = false;
18500
18615
  const now = Date.now();
@@ -18514,7 +18629,7 @@ function reap(runs, reaped = [], restore = []) {
18514
18629
  }
18515
18630
  function stampDuration(run2, endedAt) {
18516
18631
  try {
18517
- fs48.appendFileSync(run2.logPath, durationLine(endedAt - run2.startedAt));
18632
+ fs49.appendFileSync(run2.logPath, durationLine(endedAt - run2.startedAt));
18518
18633
  } catch {
18519
18634
  }
18520
18635
  }
@@ -18528,7 +18643,7 @@ function cardNow(cardId2) {
18528
18643
  try {
18529
18644
  const file = cardFile(cardId2);
18530
18645
  if (!file) return null;
18531
- const { meta } = parseFrontmatter(fs48.readFileSync(file, "utf8"));
18646
+ const { meta } = parseFrontmatter(fs49.readFileSync(file, "utf8"));
18532
18647
  if (!meta) return null;
18533
18648
  return { status: meta.status || "todo", questions: openOf(meta.questions).length, title: meta.title };
18534
18649
  } catch {
@@ -18665,9 +18780,9 @@ function leftBoardOnLanding(id) {
18665
18780
  function archivedRejected(id) {
18666
18781
  const filed = locateArchived(id);
18667
18782
  if (!filed) return null;
18668
- const file = filed.kind === "group" ? path52.join(filed.target, "root.md") : filed.target;
18783
+ const file = filed.kind === "group" ? path54.join(filed.target, "root.md") : filed.target;
18669
18784
  try {
18670
- return !!parseFrontmatter(fs48.readFileSync(file, "utf8")).meta?.rejected;
18785
+ return !!parseFrontmatter(fs49.readFileSync(file, "utf8")).meta?.rejected;
18671
18786
  } catch {
18672
18787
  return false;
18673
18788
  }
@@ -18924,7 +19039,7 @@ function openRun(req, prompt, notes = [], sessionId = randomUUID6(), said3) {
18924
19039
  return { run: record, spec };
18925
19040
  }
18926
19041
  function planningSession(cardId2, cwd = REPO_ROOT) {
18927
- const here = (dir) => !dir || path52.resolve(dir) === cwd;
19042
+ const here = (dir) => !dir || path54.resolve(dir) === cwd;
18928
19043
  const ended = readRuns().filter((r) => r.status === "done").sort((a, b) => b.startedAt - a.startedAt);
18929
19044
  const of = (run3, fork) => {
18930
19045
  if (!here(run3.cwd)) return { none: "elsewhere" };
@@ -19146,8 +19261,8 @@ async function resumeHeld(prev, plan, sessionId, resuming, fresh) {
19146
19261
  clearAsks(prev.sessionId);
19147
19262
  try {
19148
19263
  if (prev.action !== "create") {
19149
- fs48.unlinkSync(prev.logPath);
19150
- fs48.rmSync(eventsPathOf(prev.sessionId), { force: true });
19264
+ fs49.unlinkSync(prev.logPath);
19265
+ fs49.rmSync(eventsPathOf(prev.sessionId), { force: true });
19151
19266
  }
19152
19267
  } catch {
19153
19268
  }
@@ -19171,8 +19286,8 @@ function forgetRun(sessionId) {
19171
19286
  if (!gone) return;
19172
19287
  clearAsks(sessionId);
19173
19288
  try {
19174
- fs48.unlinkSync(gone.logPath);
19175
- fs48.rmSync(eventsPathOf(gone.sessionId), { force: true });
19289
+ fs49.unlinkSync(gone.logPath);
19290
+ fs49.rmSync(eventsPathOf(gone.sessionId), { force: true });
19176
19291
  } catch {
19177
19292
  }
19178
19293
  }
@@ -19201,14 +19316,14 @@ function noteSpecRefusals(sessionId, refused) {
19201
19316
  }
19202
19317
  function clearAsks(sessionId) {
19203
19318
  try {
19204
- fs48.unlinkSync(asksPathOf(sessionId));
19319
+ fs49.unlinkSync(asksPathOf(sessionId));
19205
19320
  } catch {
19206
19321
  }
19207
19322
  }
19208
19323
  function readAsks(sessionId) {
19209
19324
  let data;
19210
19325
  try {
19211
- data = JSON.parse(fs48.readFileSync(asksPathOf(sessionId), "utf8"));
19326
+ data = JSON.parse(fs49.readFileSync(asksPathOf(sessionId), "utf8"));
19212
19327
  } catch {
19213
19328
  return { asks: [], refines: [], refused: [] };
19214
19329
  }
@@ -19235,10 +19350,10 @@ function readAsks(sessionId) {
19235
19350
  return { asks: asks.filter((a) => !discarded.has(a.cardId)), refines: refines.filter((a) => !discarded.has(a.cardId)), refused };
19236
19351
  }
19237
19352
  function writeAsks(sessionId, file) {
19238
- fs48.mkdirSync(SESSIONS_DIR, { recursive: true });
19353
+ fs49.mkdirSync(SESSIONS_DIR, { recursive: true });
19239
19354
  const tmp = `${asksPathOf(sessionId)}.tmp`;
19240
- fs48.writeFileSync(tmp, JSON.stringify(file, null, 2) + "\n");
19241
- fs48.renameSync(tmp, asksPathOf(sessionId));
19355
+ fs49.writeFileSync(tmp, JSON.stringify(file, null, 2) + "\n");
19356
+ fs49.renameSync(tmp, asksPathOf(sessionId));
19242
19357
  }
19243
19358
  function markSpawned(sessionId, pid) {
19244
19359
  patch(sessionId, (r) => {
@@ -19326,7 +19441,10 @@ async function cancelDelivery(id) {
19326
19441
  if (!id.trim()) return { ok: false, ...refusal("deliveryUnnamed", "name the delivery to cancel", { action: "cancel" }) };
19327
19442
  const delivery = namedDelivery(id);
19328
19443
  if (!delivery) return { ok: false, ...unknownDelivery(id) };
19329
- if (delivery.status !== "active") return { ok: true, deliveryId: delivery.deliveryId };
19444
+ if (delivery.status !== "active") {
19445
+ tidyCheckout(delivery.deliveryId);
19446
+ return { ok: true, deliveryId: delivery.deliveryId };
19447
+ }
19330
19448
  endDelivery(delivery.deliveryId, "cancelled");
19331
19449
  const live = readRuns().find((r) => r.status === "running" && r.deliveryId === delivery.deliveryId && r.action !== "sub");
19332
19450
  if (live) await stopRun(live.sessionId);
@@ -19351,7 +19469,9 @@ async function runIsDown(sessionId) {
19351
19469
  }
19352
19470
  async function resumeDelivery(id) {
19353
19471
  if (!id.trim()) return { ok: false, ...refusal("deliveryUnnamed", "name the delivery to carry on", { action: "resume" }) };
19354
- const put = resumeRecord(id);
19472
+ const execution = executionOf();
19473
+ 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" };
19474
+ const put = resumeRecord(id, execution);
19355
19475
  if (!put.ok) return put;
19356
19476
  const delivery = put.delivery;
19357
19477
  if (await settleAlreadyLanded(delivery)) {
@@ -19392,12 +19512,12 @@ async function discardDelivery(id) {
19392
19512
  return { ok: true, deliveryId: delivery.deliveryId };
19393
19513
  }
19394
19514
  function writeSpec(spec) {
19395
- fs48.mkdirSync(SESSIONS_DIR, { recursive: true });
19396
- fs48.writeFileSync(specPathOf(spec.sessionId), JSON.stringify(spec, null, 2) + "\n");
19515
+ fs49.mkdirSync(SESSIONS_DIR, { recursive: true });
19516
+ fs49.writeFileSync(specPathOf(spec.sessionId), JSON.stringify(spec, null, 2) + "\n");
19397
19517
  }
19398
19518
  function readSpec(sessionId) {
19399
19519
  try {
19400
- const spec = JSON.parse(fs48.readFileSync(specPathOf(sessionId), "utf8"));
19520
+ const spec = JSON.parse(fs49.readFileSync(specPathOf(sessionId), "utf8"));
19401
19521
  return spec && Array.isArray(spec.plan?.argv) ? spec : null;
19402
19522
  } catch {
19403
19523
  return null;
@@ -19405,28 +19525,28 @@ function readSpec(sessionId) {
19405
19525
  }
19406
19526
  function dropSpec(sessionId) {
19407
19527
  try {
19408
- fs48.unlinkSync(specPathOf(sessionId));
19528
+ fs49.unlinkSync(specPathOf(sessionId));
19409
19529
  } catch {
19410
19530
  }
19411
19531
  }
19412
19532
  async function acquireIndexLock(giveUp) {
19413
19533
  const until = Date.now() + INDEX_WAIT_MS;
19414
- const holder = path52.join(INDEX_LOCK2, "holder");
19534
+ const holder = path54.join(INDEX_LOCK2, "holder");
19415
19535
  for (; ; ) {
19416
19536
  if (giveUp()) return null;
19417
19537
  try {
19418
- fs48.mkdirSync(INDEX_LOCK2, { recursive: false });
19419
- fs48.writeFileSync(holder, String(process.pid));
19420
- return () => fs48.rmSync(INDEX_LOCK2, { recursive: true, force: true });
19538
+ fs49.mkdirSync(INDEX_LOCK2, { recursive: false });
19539
+ fs49.writeFileSync(holder, String(process.pid));
19540
+ return () => fs49.rmSync(INDEX_LOCK2, { recursive: true, force: true });
19421
19541
  } catch (err) {
19422
19542
  const code = err.code;
19423
19543
  if (code === "ENOENT") {
19424
- fs48.mkdirSync(path52.dirname(INDEX_LOCK2), { recursive: true });
19544
+ fs49.mkdirSync(path54.dirname(INDEX_LOCK2), { recursive: true });
19425
19545
  continue;
19426
19546
  }
19427
19547
  if (code !== "EEXIST") throw err;
19428
19548
  if (!pidAlive(Number(readHolder(holder)))) {
19429
- fs48.rmSync(INDEX_LOCK2, { recursive: true, force: true });
19549
+ fs49.rmSync(INDEX_LOCK2, { recursive: true, force: true });
19430
19550
  continue;
19431
19551
  }
19432
19552
  if (Date.now() > until) return null;
@@ -19436,7 +19556,7 @@ async function acquireIndexLock(giveUp) {
19436
19556
  }
19437
19557
  function readHolder(file) {
19438
19558
  try {
19439
- return fs48.readFileSync(file, "utf8").trim();
19559
+ return fs49.readFileSync(file, "utf8").trim();
19440
19560
  } catch {
19441
19561
  return String(process.pid);
19442
19562
  }
@@ -19445,6 +19565,7 @@ var INDEX_ACTIONS, SINGLETON_ACTIONS, VERB, SINGLETON_BUSY, RUN_STATUS, specPath
19445
19565
  var init_sessions = __esm({
19446
19566
  "../cli/src/lib/agent/sessions.ts"() {
19447
19567
  "use strict";
19568
+ init_execution();
19448
19569
  init_creation();
19449
19570
  init_cards();
19450
19571
  init_frontmatter();
@@ -19527,8 +19648,8 @@ var init_sessions = __esm({
19527
19648
  conflict: "implementing",
19528
19649
  hook: "implementing"
19529
19650
  };
19530
- specPathOf = (sessionId) => path52.join(SESSIONS_DIR, `${sessionId}.plan.json`);
19531
- asksPathOf = (sessionId) => path52.join(SESSIONS_DIR, `${sessionId}.asks.json`);
19651
+ specPathOf = (sessionId) => path54.join(SESSIONS_DIR, `${sessionId}.plan.json`);
19652
+ asksPathOf = (sessionId) => path54.join(SESSIONS_DIR, `${sessionId}.asks.json`);
19532
19653
  carriesSession = (r) => !!(r.resumedFrom || r.continues && !r.continues.fork);
19533
19654
  canPickUp = (r) => (r.status === "error" || r.status === "interrupted" || r.status === "stopped") && !(r.action === "create" && readRuns().some((next) => next.resumedFrom === r.sessionId));
19534
19655
  neverStarted = (r) => r.unspawned === true || !!r.errorWhy?.some((why) => why.kind === "notInstalled");
@@ -19662,8 +19783,8 @@ var init_questions = __esm({
19662
19783
  });
19663
19784
 
19664
19785
  // ../cli/src/lib/agent/workflow-cards.ts
19665
- import fs49 from "node:fs";
19666
- import path53 from "node:path";
19786
+ import fs50 from "node:fs";
19787
+ import path55 from "node:path";
19667
19788
  function cardsOnWorkflow(id) {
19668
19789
  const wanted = id || DEFAULT_WORKFLOW;
19669
19790
  const ids = [];
@@ -19674,12 +19795,12 @@ function cardsOnWorkflow(id) {
19674
19795
  return [];
19675
19796
  }
19676
19797
  for (const file of files2) {
19677
- const base = path53.basename(file);
19678
- const card = base === "root.md" ? idPrefix(path53.basename(path53.dirname(file))) : idPrefix(base);
19798
+ const base = path55.basename(file);
19799
+ const card = base === "root.md" ? idPrefix(path55.basename(path55.dirname(file))) : idPrefix(base);
19679
19800
  if (card === null) continue;
19680
19801
  let meta;
19681
19802
  try {
19682
- meta = parseFrontmatter(fs49.readFileSync(file, "utf8")).meta;
19803
+ meta = parseFrontmatter(fs50.readFileSync(file, "utf8")).meta;
19683
19804
  } catch {
19684
19805
  continue;
19685
19806
  }
@@ -19737,12 +19858,12 @@ var init_workflow_cards = __esm({
19737
19858
  });
19738
19859
 
19739
19860
  // ../cli/src/lib/readme.ts
19740
- import fs50 from "node:fs";
19741
- import path54 from "node:path";
19861
+ import fs51 from "node:fs";
19862
+ import path56 from "node:path";
19742
19863
  function stripReadmeRefs(target) {
19743
- if (!fs50.existsSync(README)) return [];
19864
+ if (!fs51.existsSync(README)) return [];
19744
19865
  const needle = target.kind === "group" ? `](${target.rel}/` : `](${target.rel})`;
19745
- const lines = fs50.readFileSync(README, "utf8").split("\n");
19866
+ const lines = fs51.readFileSync(README, "utf8").split("\n");
19746
19867
  const out = [];
19747
19868
  const removed = [];
19748
19869
  let i = 0;
@@ -19764,20 +19885,20 @@ function stripReadmeRefs(target) {
19764
19885
  out.push(line);
19765
19886
  i++;
19766
19887
  }
19767
- if (removed.length) fs50.writeFileSync(README, out.join("\n"));
19888
+ if (removed.length) fs51.writeFileSync(README, out.join("\n"));
19768
19889
  return removed;
19769
19890
  }
19770
19891
  function addReadmeRef(id, title, relPath) {
19771
- if (!fs50.existsSync(README)) return false;
19772
- const link2 = relPath.split(path54.sep).join("/");
19892
+ if (!fs51.existsSync(README)) return false;
19893
+ const link2 = relPath.split(path56.sep).join("/");
19773
19894
  const bullet = `- [#${id} ${title}](${link2})`;
19774
19895
  const heading = `## ${TASKS_HEADING}`;
19775
- let lines = fs50.readFileSync(README, "utf8").split("\n");
19896
+ let lines = fs51.readFileSync(README, "utf8").split("\n");
19776
19897
  const hi = lines.findIndex((l) => l.trim().toLowerCase() === heading.toLowerCase());
19777
19898
  if (hi === -1) {
19778
19899
  while (lines.length && lines[lines.length - 1].trim() === "") lines.pop();
19779
19900
  lines.push("", heading, "", bullet);
19780
- fs50.writeFileSync(README, lines.join("\n") + "\n");
19901
+ fs51.writeFileSync(README, lines.join("\n") + "\n");
19781
19902
  return true;
19782
19903
  }
19783
19904
  let end2 = hi + 1;
@@ -19791,15 +19912,15 @@ function addReadmeRef(id, title, relPath) {
19791
19912
  const at = lastBullet !== -1 ? lastBullet + 1 : lines[hi + 1] === "" ? hi + 2 : hi + 1;
19792
19913
  lines.splice(at, 0, bullet);
19793
19914
  }
19794
- fs50.writeFileSync(README, lines.join("\n"));
19915
+ fs51.writeFileSync(README, lines.join("\n"));
19795
19916
  return true;
19796
19917
  }
19797
19918
  function repointReadmeLink(id, oldRel, newRel, title) {
19798
- if (!fs50.existsSync(README)) return false;
19799
- const oldLink = oldRel.split(path54.sep).join("/");
19800
- const newLink = newRel.split(path54.sep).join("/");
19919
+ if (!fs51.existsSync(README)) return false;
19920
+ const oldLink = oldRel.split(path56.sep).join("/");
19921
+ const newLink = newRel.split(path56.sep).join("/");
19801
19922
  const linkRe = new RegExp(`\\[#${id}\\b[^\\]]*\\]\\(${escapeRegex(oldLink)}\\)`);
19802
- const lines = fs50.readFileSync(README, "utf8").split("\n");
19923
+ const lines = fs51.readFileSync(README, "utf8").split("\n");
19803
19924
  let changed = false;
19804
19925
  for (let i = 0; i < lines.length; i++) {
19805
19926
  if (!lines[i].includes(`](${oldLink})`)) continue;
@@ -19809,7 +19930,7 @@ function repointReadmeLink(id, oldRel, newRel, title) {
19809
19930
  changed = true;
19810
19931
  }
19811
19932
  }
19812
- if (changed) fs50.writeFileSync(README, lines.join("\n"));
19933
+ if (changed) fs51.writeFileSync(README, lines.join("\n"));
19813
19934
  return changed;
19814
19935
  }
19815
19936
  var isTableRow, isBulletStart, bulletIndent, TASKS_HEADING, escapeRegex;
@@ -19826,14 +19947,14 @@ var init_readme = __esm({
19826
19947
  });
19827
19948
 
19828
19949
  // ../cli/src/lib/reconcile.ts
19829
- import fs51 from "node:fs";
19830
- import path55 from "node:path";
19950
+ import fs52 from "node:fs";
19951
+ import path57 from "node:path";
19831
19952
  function liveIds() {
19832
19953
  const ids = /* @__PURE__ */ new Set();
19833
19954
  for (const file of walkMd(TODO)) {
19834
- const base = path55.basename(file);
19955
+ const base = path57.basename(file);
19835
19956
  if (base === "README.md") continue;
19836
- const id = base === "root.md" ? idPrefix(path55.basename(path55.dirname(file))) : idPrefix(base);
19957
+ const id = base === "root.md" ? idPrefix(path57.basename(path57.dirname(file))) : idPrefix(base);
19837
19958
  if (id != null) ids.add(id);
19838
19959
  }
19839
19960
  return ids;
@@ -19841,16 +19962,16 @@ function liveIds() {
19841
19962
  function indexableCards() {
19842
19963
  const out = [];
19843
19964
  for (const dir of walkDirs(TODO)) {
19844
- const id = idPrefix(path55.basename(dir));
19845
- if (id != null && fs51.existsSync(path55.join(dir, "root.md"))) {
19846
- out.push({ id, rel: path55.join(path55.relative(TODO, dir), "root.md").split(path55.sep).join("/") });
19965
+ const id = idPrefix(path57.basename(dir));
19966
+ if (id != null && fs52.existsSync(path57.join(dir, "root.md"))) {
19967
+ out.push({ id, rel: path57.join(path57.relative(TODO, dir), "root.md").split(path57.sep).join("/") });
19847
19968
  }
19848
19969
  }
19849
19970
  for (const file of walkMd(TODO)) {
19850
- const base = path55.basename(file);
19971
+ const base = path57.basename(file);
19851
19972
  if (base === "README.md" || base === "root.md") continue;
19852
- const relTODO = path55.relative(TODO, file);
19853
- const segs = relTODO.split(path55.sep);
19973
+ const relTODO = path57.relative(TODO, file);
19974
+ const segs = relTODO.split(path57.sep);
19854
19975
  if (segs.length !== 1) continue;
19855
19976
  const id = idPrefix(base);
19856
19977
  if (id != null) out.push({ id, rel: segs.join("/") });
@@ -19859,7 +19980,7 @@ function indexableCards() {
19859
19980
  }
19860
19981
  function readmeComplaints(fix) {
19861
19982
  const said3 = [];
19862
- const lines = fs51.readFileSync(README, "utf8").split("\n");
19983
+ const lines = fs52.readFileSync(README, "utf8").split("\n");
19863
19984
  const indexed = /* @__PURE__ */ new Set();
19864
19985
  let fixed = 0;
19865
19986
  for (let i = 0; i < lines.length; i++) {
@@ -19868,13 +19989,13 @@ function readmeComplaints(fix) {
19868
19989
  const id = Number(m[1]);
19869
19990
  const linkPath = m[2];
19870
19991
  indexed.add(id);
19871
- if (fs51.existsSync(path55.join(TODO, linkPath))) continue;
19992
+ if (fs52.existsSync(path57.join(TODO, linkPath))) continue;
19872
19993
  const found2 = locate(id);
19873
19994
  if (!found2) {
19874
19995
  said3.push(`README links #${id} \u2192 ${linkPath}, but no card with that id exists (removed by hand?). Drop the entry or restore the file.`);
19875
19996
  continue;
19876
19997
  }
19877
- const want = (found2.kind === "group" ? path55.join(found2.rel, "root.md") : found2.rel).split(path55.sep).join("/");
19998
+ const want = (found2.kind === "group" ? path57.join(found2.rel, "root.md") : found2.rel).split(path57.sep).join("/");
19878
19999
  if (want === linkPath) continue;
19879
20000
  if (!fix) {
19880
20001
  said3.push(`README links #${id} \u2192 ${linkPath}, which isn't there any more; the card is at ${want}.`);
@@ -19884,7 +20005,7 @@ function readmeComplaints(fix) {
19884
20005
  said3.push(`README link #${id} pointed at missing ${linkPath} \u2192 repointed to ${want}.`);
19885
20006
  fixed++;
19886
20007
  }
19887
- if (fixed) fs51.writeFileSync(README, lines.join("\n"));
20008
+ if (fixed) fs52.writeFileSync(README, lines.join("\n"));
19888
20009
  for (const c of indexableCards()) {
19889
20010
  if (!indexed.has(c.id)) {
19890
20011
  said3.push(`card ${c.rel} (#${c.id}) is not in the README index. Add it under "## ${TASKS_HEADING}".`);
@@ -19896,11 +20017,11 @@ function crossRefComplaints() {
19896
20017
  const said3 = [];
19897
20018
  const live = liveIds();
19898
20019
  for (const file of walkMd(TODO)) {
19899
- const base = path55.basename(file);
20020
+ const base = path57.basename(file);
19900
20021
  if (base === "README.md") continue;
19901
- const ownerId = base === "root.md" ? idPrefix(path55.basename(path55.dirname(file))) : idPrefix(base);
20022
+ const ownerId = base === "root.md" ? idPrefix(path57.basename(path57.dirname(file))) : idPrefix(base);
19902
20023
  if (ownerId == null) continue;
19903
- const { meta } = parseFrontmatter(fs51.readFileSync(file, "utf8"));
20024
+ const { meta } = parseFrontmatter(fs52.readFileSync(file, "utf8"));
19904
20025
  if (!meta) continue;
19905
20026
  for (const field2 of ["blocked_by", "related"]) {
19906
20027
  for (const ref of meta[field2] || []) {
@@ -19913,11 +20034,11 @@ function crossRefComplaints() {
19913
20034
  return said3;
19914
20035
  }
19915
20036
  function reconcileBoard() {
19916
- if (!fs51.existsSync(README)) return;
20037
+ if (!fs52.existsSync(README)) return;
19917
20038
  for (const line of [...readmeComplaints(true), ...crossRefComplaints()]) warn2(line);
19918
20039
  }
19919
20040
  function boardComplaints() {
19920
- if (!fs51.existsSync(README)) return [];
20041
+ if (!fs52.existsSync(README)) return [];
19921
20042
  try {
19922
20043
  return [...readmeComplaints(false), ...crossRefComplaints()];
19923
20044
  } catch {
@@ -19937,8 +20058,8 @@ var init_reconcile = __esm({
19937
20058
  });
19938
20059
 
19939
20060
  // ../cli/src/commands/card.ts
19940
- import fs52 from "node:fs";
19941
- import path56 from "node:path";
20061
+ import fs53 from "node:fs";
20062
+ import path58 from "node:path";
19942
20063
  function defaultBody() {
19943
20064
  return [
19944
20065
  "<\u7528\u4E00\u5C0F\u6BB5\u8BDD\u5148\u8BB2\u53D1\u751F\u4E86\u4EC0\u4E48\u3001\u7528\u6237\u9047\u5230\u4EC0\u4E48\uFF0C\u518D\u8BB2\u8FD9\u5F20\u5361\u8981\u8FBE\u5230\u7684\u7ED3\u679C\u3002>",
@@ -19964,7 +20085,7 @@ function bodyFromFile(opts) {
19964
20085
  if (opts.body === false) die("pass --body-file or --no-body, not both");
19965
20086
  let text5;
19966
20087
  try {
19967
- text5 = fs52.readFileSync(opts.bodyFile, "utf8");
20088
+ text5 = fs53.readFileSync(opts.bodyFile, "utf8");
19968
20089
  } catch {
19969
20090
  die(`can't read ${opts.bodyFile} \u2014 write the body to a file, then pass its path`, { kind: "needs-input" });
19970
20091
  }
@@ -19996,20 +20117,20 @@ function cmdCreate(opts) {
19996
20117
  const start = opts.id;
19997
20118
  const slug2 = slugify(opts.slug !== void 0 ? opts.slug : title);
19998
20119
  const fileRel = `${start}-${slug2}.md`;
19999
- const file = path56.join(TODO, fileRel);
20120
+ const file = path58.join(TODO, fileRel);
20000
20121
  const held4 = locate(start) ?? locateArchived(start);
20001
- if (held4 && path56.basename(held4.target) === `${start}-${slug2}.md`) {
20122
+ if (held4 && path58.basename(held4.target) === `${start}-${slug2}.md`) {
20002
20123
  say(start);
20003
20124
  say(` ${rel(held4.target)} was already written`);
20004
20125
  return { id: start, ids: [start], title, file: rel(held4.target), indexed: false };
20005
20126
  }
20006
20127
  if (held4) die(`#${start} is already a card here (${rel(held4.target)})`, { kind: "id-taken", id: start });
20007
- if (fs52.existsSync(file)) die(`${rel(file)} already exists \u2014 pick a different --slug`);
20128
+ if (fs53.existsSync(file)) die(`${rel(file)} already exists \u2014 pick a different --slug`);
20008
20129
  countBoardEvent(triage ? "cards_created_proposed" : "cards_created_asked");
20009
20130
  const meta = { title, priority, status: "todo", blocked_by, related, modules, workflow, triage, source, questions };
20010
20131
  const scaffolded = !written && opts.body !== false;
20011
20132
  const body = written ?? (scaffolded ? defaultBody() : "");
20012
- fs52.writeFileSync(file, serializeFrontmatter(meta) + "\n\n" + body);
20133
+ fs53.writeFileSync(file, serializeFrontmatter(meta) + "\n\n" + body);
20013
20134
  const indexed = addReadmeRef(start, title, fileRel);
20014
20135
  say(start);
20015
20136
  say(
@@ -20047,8 +20168,8 @@ function cmdUpdate(id, flags) {
20047
20168
  if (creating) die(creating, { kind: "card-being-created", id });
20048
20169
  const found2 = locate(id);
20049
20170
  if (!found2) die(`no task with id ${id} under ${rel(TODO)}`, { kind: "card-not-found", id });
20050
- const file = found2.kind === "group" ? path56.join(found2.target, "root.md") : found2.target;
20051
- const { meta, body } = parseFrontmatter(fs52.readFileSync(file, "utf8"));
20171
+ const file = found2.kind === "group" ? path58.join(found2.target, "root.md") : found2.target;
20172
+ const { meta, body } = parseFrontmatter(fs53.readFileSync(file, "utf8"));
20052
20173
  if (!meta) die(`${rel(file)} has no frontmatter \u2014 run \`migrate\` first`);
20053
20174
  if (flags.blockedBy !== void 0 && flags.addBlockedBy !== void 0) die("--blocked-by replaces the list and --add-blocked-by appends to it \u2014 pass one");
20054
20175
  if (flags.related !== void 0 && flags.addRelated !== void 0) die("--related replaces the list and --add-related appends to it \u2014 pass one");
@@ -20128,26 +20249,26 @@ function cmdUpdate(id, flags) {
20128
20249
  meta.status = "todo";
20129
20250
  changes.push("status\u2192todo (open questions)");
20130
20251
  }
20131
- const curRel = path56.relative(TODO, file);
20252
+ const curRel = path58.relative(TODO, file);
20132
20253
  const isSubtask = found2.kind === "file" && enclosingGroupRoot(file) !== null;
20133
- let base = path56.basename(file);
20254
+ let base = path58.basename(file);
20134
20255
  if (flags.slug !== void 0) {
20135
20256
  if (found2.kind === "group") die("renaming a group root by script is not supported");
20136
20257
  base = `${id}-${slugify(flags.slug)}.md`;
20137
20258
  }
20138
- const destRel = path56.join(path56.dirname(curRel), base);
20139
- const dest = path56.join(TODO, destRel);
20259
+ const destRel = path58.join(path58.dirname(curRel), base);
20260
+ const dest = path58.join(TODO, destRel);
20140
20261
  const moving = dest !== file;
20141
- if (moving && fs52.existsSync(dest)) die(`${rel(dest)} already exists`);
20142
- fs52.writeFileSync(file, serializeFrontmatter(meta) + "\n" + body);
20143
- if (moving) fs52.renameSync(file, dest);
20262
+ if (moving && fs53.existsSync(dest)) die(`${rel(dest)} already exists`);
20263
+ fs53.writeFileSync(file, serializeFrontmatter(meta) + "\n" + body);
20264
+ if (moving) fs53.renameSync(file, dest);
20144
20265
  if (isSubtask) {
20145
20266
  if (moving || changes.includes("title")) repointReadmeLink(id, curRel, destRel, meta.title);
20146
- if (moving) changes.push(`renamed \u2192 ${destRel.split(path56.sep).join("/")}`);
20267
+ if (moving) changes.push(`renamed \u2192 ${destRel.split(path58.sep).join("/")}`);
20147
20268
  } else if (moving) {
20148
20269
  stripReadmeRefs({ kind: "file", rel: curRel });
20149
20270
  addReadmeRef(id, meta.title, destRel);
20150
- changes.push(`renamed \u2192 ${destRel.split(path56.sep).join("/")}`);
20271
+ changes.push(`renamed \u2192 ${destRel.split(path58.sep).join("/")}`);
20151
20272
  } else if (changes.includes("title")) {
20152
20273
  stripReadmeRefs({ kind: "file", rel: curRel });
20153
20274
  addReadmeRef(id, meta.title, curRel);
@@ -20159,8 +20280,8 @@ function cmdUpdateQuestions(id, input) {
20159
20280
  const ops = readQuestionOps(input.ops ?? []);
20160
20281
  const found2 = locate(id);
20161
20282
  if (!found2) die(`no task with id ${id} under ${rel(TODO)}`, { kind: "card-not-found", id });
20162
- const file = found2.kind === "group" ? path56.join(found2.target, "root.md") : found2.target;
20163
- const { meta, body } = parseFrontmatter(fs52.readFileSync(file, "utf8"));
20283
+ const file = found2.kind === "group" ? path58.join(found2.target, "root.md") : found2.target;
20284
+ const { meta, body } = parseFrontmatter(fs53.readFileSync(file, "utf8"));
20164
20285
  if (!meta) die(`${rel(file)} has no frontmatter \u2014 run \`migrate\` first`);
20165
20286
  if (insideRun() && ops.some((op) => op.kind === "skip" || op.kind === "unskip")) {
20166
20287
  die("only the user skips or reopens a question \u2014 a run leaves questions for them to answer");
@@ -20233,7 +20354,7 @@ function cmdUpdateQuestions(id, input) {
20233
20354
  meta.status = "todo";
20234
20355
  changes.push("status\u2192todo (open questions)");
20235
20356
  }
20236
- fs52.writeFileSync(file, serializeFrontmatter(meta) + "\n" + body);
20357
+ fs53.writeFileSync(file, serializeFrontmatter(meta) + "\n" + body);
20237
20358
  for (const kind of closed2) countBoardEvent(kind);
20238
20359
  settleDelivery2(id, skipped, unskipped);
20239
20360
  say(
@@ -20307,13 +20428,13 @@ var init_config_template = __esm({
20307
20428
  });
20308
20429
 
20309
20430
  // ../cli/src/lib/agent/local.ts
20310
- import fs53 from "node:fs";
20311
- import path57 from "node:path";
20431
+ import fs54 from "node:fs";
20432
+ import path59 from "node:path";
20312
20433
  function takeLocalModels(board2) {
20313
- const file = path57.join(board2, ".local.json");
20434
+ const file = path59.join(board2, ".local.json");
20314
20435
  let held4;
20315
20436
  try {
20316
- held4 = configBlock(JSON.parse(fs53.readFileSync(file, "utf8")));
20437
+ held4 = configBlock(JSON.parse(fs54.readFileSync(file, "utf8")));
20317
20438
  } catch {
20318
20439
  return {};
20319
20440
  }
@@ -20331,7 +20452,7 @@ function takeLocalModels(board2) {
20331
20452
  }
20332
20453
  if (!Object.keys(agents).length) return out;
20333
20454
  delete held4.agents;
20334
- fs53.writeFileSync(file, JSON.stringify(held4, null, 2) + "\n");
20455
+ fs54.writeFileSync(file, JSON.stringify(held4, null, 2) + "\n");
20335
20456
  return out;
20336
20457
  }
20337
20458
  var LOCAL_IGNORE_LINE;
@@ -20392,12 +20513,12 @@ var init_identity2 = __esm({
20392
20513
  });
20393
20514
 
20394
20515
  // ../cli/src/lib/signals/migrate.ts
20395
- import fs54 from "node:fs";
20396
- import path58 from "node:path";
20516
+ import fs55 from "node:fs";
20517
+ import path60 from "node:path";
20397
20518
  function readHandled() {
20398
20519
  let text5;
20399
20520
  try {
20400
- text5 = fs54.readFileSync(SIGNALS_OLD_HANDLED, "utf8");
20521
+ text5 = fs55.readFileSync(SIGNALS_OLD_HANDLED, "utf8");
20401
20522
  } catch {
20402
20523
  return [];
20403
20524
  }
@@ -20414,57 +20535,57 @@ function readHandled() {
20414
20535
  return held4;
20415
20536
  }
20416
20537
  function lift(from, into) {
20417
- fs54.mkdirSync(into, { recursive: true });
20418
- fs54.renameSync(from, path58.join(into, freeName(into, path58.basename(from))));
20538
+ fs55.mkdirSync(into, { recursive: true });
20539
+ fs55.renameSync(from, path60.join(into, freeName(into, path60.basename(from))));
20419
20540
  }
20420
20541
  function repoint(dir, from, into) {
20421
20542
  let entries;
20422
20543
  try {
20423
- entries = fs54.readdirSync(dir, { withFileTypes: true });
20544
+ entries = fs55.readdirSync(dir, { withFileTypes: true });
20424
20545
  } catch {
20425
20546
  return;
20426
20547
  }
20427
20548
  for (const entry of entries) {
20428
20549
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
20429
- const file = path58.join(dir, entry.name);
20430
- const text5 = fs54.readFileSync(file, "utf8");
20431
- if (text5.includes(from)) fs54.writeFileSync(file, text5.split(from).join(into));
20550
+ const file = path60.join(dir, entry.name);
20551
+ const text5 = fs55.readFileSync(file, "utf8");
20552
+ if (text5.includes(from)) fs55.writeFileSync(file, text5.split(from).join(into));
20432
20553
  }
20433
20554
  }
20434
20555
  function dropIfEmpty2(dir) {
20435
20556
  try {
20436
- fs54.rmdirSync(dir);
20557
+ fs55.rmdirSync(dir);
20437
20558
  } catch {
20438
20559
  }
20439
20560
  }
20440
20561
  function liftInbox() {
20441
- if (!fs54.existsSync(SIGNALS_OLD_INBOX)) return;
20442
- const oldFiles = path58.join(SIGNALS_OLD_INBOX, "files");
20443
- if (fs54.existsSync(oldFiles)) {
20444
- for (const entry of fs54.readdirSync(oldFiles, { withFileTypes: true })) {
20445
- if (entry.isFile()) lift(path58.join(oldFiles, entry.name), SIGNALS_FILES);
20562
+ if (!fs55.existsSync(SIGNALS_OLD_INBOX)) return;
20563
+ const oldFiles = path60.join(SIGNALS_OLD_INBOX, "files");
20564
+ if (fs55.existsSync(oldFiles)) {
20565
+ for (const entry of fs55.readdirSync(oldFiles, { withFileTypes: true })) {
20566
+ if (entry.isFile()) lift(path60.join(oldFiles, entry.name), SIGNALS_FILES);
20446
20567
  }
20447
20568
  dropIfEmpty2(oldFiles);
20448
20569
  }
20449
20570
  const held4 = new Set(readFolder(TRIAGE).map((signal2) => signal2.sourceId));
20450
- for (const entry of fs54.readdirSync(SIGNALS_OLD_INBOX, { withFileTypes: true })) {
20571
+ for (const entry of fs55.readdirSync(SIGNALS_OLD_INBOX, { withFileTypes: true })) {
20451
20572
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
20452
- const from = path58.join(SIGNALS_OLD_INBOX, entry.name);
20573
+ const from = path60.join(SIGNALS_OLD_INBOX, entry.name);
20453
20574
  const item = parse(from);
20454
20575
  if (item && held4.has(item.sourceId)) {
20455
- fs54.rmSync(from, { force: true });
20576
+ fs55.rmSync(from, { force: true });
20456
20577
  continue;
20457
20578
  }
20458
20579
  if (item) held4.add(item.sourceId);
20459
20580
  lift(from, TRIAGE);
20460
20581
  }
20461
- const wasAt = asWritten(path58.join(SIGNALS_OLD_INBOX, "files"));
20582
+ const wasAt = asWritten(path60.join(SIGNALS_OLD_INBOX, "files"));
20462
20583
  const nowAt = asWritten(SIGNALS_FILES);
20463
20584
  for (const dir of [TRIAGE, SIGNALS_ARCHIVED, SIGNALS_DISMISSED]) repoint(dir, wasAt, nowAt);
20464
20585
  dropIfEmpty2(SIGNALS_OLD_INBOX);
20465
20586
  }
20466
20587
  function liftHandled() {
20467
- if (!fs54.existsSync(SIGNALS_OLD_HANDLED)) return;
20588
+ if (!fs55.existsSync(SIGNALS_OLD_HANDLED)) return;
20468
20589
  const lines = readHandled();
20469
20590
  const held4 = /* @__PURE__ */ new Set([
20470
20591
  ...readFolder(TRIAGE).map((signal2) => signal2.sourceId),
@@ -20474,7 +20595,7 @@ function liftHandled() {
20474
20595
  for (const { sourceId, when } of lines) {
20475
20596
  if (held4.has(sourceId)) continue;
20476
20597
  held4.add(sourceId);
20477
- fs54.mkdirSync(SIGNALS_DISMISSED, { recursive: true });
20598
+ fs55.mkdirSync(SIGNALS_DISMISSED, { recursive: true });
20478
20599
  const name = freeName(SIGNALS_DISMISSED, fileName("", sourceId));
20479
20600
  const front = [
20480
20601
  "---",
@@ -20484,13 +20605,13 @@ function liftHandled() {
20484
20605
  "---",
20485
20606
  ""
20486
20607
  ];
20487
- fs54.writeFileSync(path58.join(SIGNALS_DISMISSED, name), `${front.join("\n")}
20608
+ fs55.writeFileSync(path60.join(SIGNALS_DISMISSED, name), `${front.join("\n")}
20488
20609
  `);
20489
20610
  }
20490
- fs54.rmSync(SIGNALS_OLD_HANDLED, { force: true });
20611
+ fs55.rmSync(SIGNALS_OLD_HANDLED, { force: true });
20491
20612
  }
20492
20613
  function migrateTriage() {
20493
- if (!fs54.existsSync(SIGNALS_OLD_INBOX) && !fs54.existsSync(SIGNALS_OLD_HANDLED)) return;
20614
+ if (!fs55.existsSync(SIGNALS_OLD_INBOX) && !fs55.existsSync(SIGNALS_OLD_HANDLED)) return;
20494
20615
  try {
20495
20616
  liftInbox();
20496
20617
  liftHandled();
@@ -20505,13 +20626,13 @@ var init_migrate = __esm({
20505
20626
  init_yaml();
20506
20627
  init_inbox();
20507
20628
  HANDLED_LINE = /^-\s+(.+?)\s+—\s+(.+)$/;
20508
- asWritten = (dir) => `${rel(dir).split(path58.sep).join("/")}/`;
20629
+ asWritten = (dir) => `${rel(dir).split(path60.sep).join("/")}/`;
20509
20630
  }
20510
20631
  });
20511
20632
 
20512
20633
  // ../cli/src/lib/signals/add.ts
20513
- import fs55 from "node:fs";
20514
- import path59 from "node:path";
20634
+ import fs56 from "node:fs";
20635
+ import path61 from "node:path";
20515
20636
  import { createHash as createHash3 } from "node:crypto";
20516
20637
  function linkTitle(url) {
20517
20638
  let at;
@@ -20524,7 +20645,7 @@ function linkTitle(url) {
20524
20645
  return oneLine(where) || url.slice(0, TITLE_MAX);
20525
20646
  }
20526
20647
  function asText(file) {
20527
- const ext = path59.extname(file.name).toLowerCase();
20648
+ const ext = path61.extname(file.name).toLowerCase();
20528
20649
  if (!file.type.startsWith("text/") && file.type !== "application/json" && !TEXT_EXTENSIONS.has(ext)) return null;
20529
20650
  try {
20530
20651
  const text5 = new TextDecoder("utf-8", { fatal: true }).decode(file.data);
@@ -20534,9 +20655,9 @@ function asText(file) {
20534
20655
  }
20535
20656
  }
20536
20657
  function fileName2(name, data) {
20537
- const base = path59.basename(name);
20538
- const ext = path59.extname(base).toLowerCase().slice(0, 12);
20539
- const word = path59.basename(base, path59.extname(base)).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40) || "file";
20658
+ const base = path61.basename(name);
20659
+ const ext = path61.extname(base).toLowerCase().slice(0, 12);
20660
+ const word = path61.basename(base, path61.extname(base)).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 40) || "file";
20540
20661
  const hash2 = createHash3("sha256").update(data).digest("hex").slice(0, 8);
20541
20662
  return `${word}-${hash2}${ext}`;
20542
20663
  }
@@ -20545,9 +20666,9 @@ function addToInbox(drop) {
20545
20666
  const typed = (drop.text ?? "").trim();
20546
20667
  const file = drop.file;
20547
20668
  if (!typed && !file) return { ok: false, error: "nothing to add \u2014 paste a link or some text, or drop a file." };
20548
- if (file && file.data.length === 0) return { ok: false, error: `${path59.basename(file.name)} is empty.` };
20669
+ if (file && file.data.length === 0) return { ok: false, error: `${path61.basename(file.name)} is empty.` };
20549
20670
  if (file && file.data.length > MAX_BYTES) {
20550
- return { ok: false, error: `${path59.basename(file.name)} is over ${MAX_BYTES / 1024 / 1024} MB \u2014 too big for the board.` };
20671
+ return { ok: false, error: `${path61.basename(file.name)} is over ${MAX_BYTES / 1024 / 1024} MB \u2014 too big for the board.` };
20551
20672
  }
20552
20673
  const read4 = describe(typed, file);
20553
20674
  const keep2 = read4.keep;
@@ -20569,8 +20690,8 @@ ${incoming.summary}`);
20569
20690
  if (seen.status === "pending" || seen.status === "archived") return { ok: false, error: `${sayHit(seen)}.` };
20570
20691
  try {
20571
20692
  if (keep2) {
20572
- fs55.mkdirSync(SIGNALS_FILES, { recursive: true });
20573
- fs55.writeFileSync(keep2.at, keep2.data);
20693
+ fs56.mkdirSync(SIGNALS_FILES, { recursive: true });
20694
+ fs56.writeFileSync(keep2.at, keep2.data);
20574
20695
  }
20575
20696
  return { ok: true, signal: writeSignal({ ...incoming, sourceId }, formatStamp(/* @__PURE__ */ new Date()), drop.slug) };
20576
20697
  } catch (e) {
@@ -20584,7 +20705,7 @@ function describe(typed, file) {
20584
20705
  return only ? [only] : [];
20585
20706
  };
20586
20707
  if (file) {
20587
- const name = path59.basename(file.name);
20708
+ const name = path61.basename(file.name);
20588
20709
  const text5 = asText(file);
20589
20710
  const note = typed ? `
20590
20711
 
@@ -20596,7 +20717,7 @@ ${typed}` : "";
20596
20717
  const at = keepAt(file);
20597
20718
  return {
20598
20719
  incoming: {
20599
- title: path59.basename(name, path59.extname(name)) || name,
20720
+ title: path61.basename(name, path61.extname(name)) || name,
20600
20721
  summary: `${name} \u2014 ${boardRel2(at)}${note}`,
20601
20722
  ...named2
20602
20723
  },
@@ -20634,73 +20755,73 @@ var init_add = __esm({
20634
20755
  MAX_BYTES = 25 * 1024 * 1024;
20635
20756
  TITLE_MAX = 120;
20636
20757
  TEXT_EXTENSIONS = /* @__PURE__ */ new Set([".md", ".markdown", ".txt", ".text", ".csv", ".json", ".yml", ".yaml", ".log"]);
20637
- boardRel2 = (file) => rel(file).split(path59.sep).join("/");
20758
+ boardRel2 = (file) => rel(file).split(path61.sep).join("/");
20638
20759
  oneLine = (text5) => {
20639
20760
  const first = text5.split("\n").find((line) => line.trim()) ?? "";
20640
20761
  const clean = first.replace(/^#+\s*/, "").trim();
20641
20762
  return clean.length > TITLE_MAX ? `${clean.slice(0, TITLE_MAX - 1).trimEnd()}\u2026` : clean;
20642
20763
  };
20643
- keepAt = (file) => path59.join(SIGNALS_FILES, fileName2(file.name, file.data));
20764
+ keepAt = (file) => path61.join(SIGNALS_FILES, fileName2(file.name, file.data));
20644
20765
  }
20645
20766
  });
20646
20767
 
20647
20768
  // ../cli/src/lib/recurring.ts
20648
- import fs56 from "node:fs";
20649
- import path60 from "node:path";
20769
+ import fs57 from "node:fs";
20770
+ import path62 from "node:path";
20650
20771
  function migratePruneMemoryCard() {
20651
20772
  const dir = recurringDir();
20652
20773
  let names4;
20653
20774
  try {
20654
- names4 = fs56.readdirSync(dir);
20775
+ names4 = fs57.readdirSync(dir);
20655
20776
  } catch {
20656
20777
  return null;
20657
20778
  }
20658
20779
  const name = names4.find((entry) => entry.endsWith(`-${PRUNE_SLUG}.md`));
20659
20780
  if (!name) return null;
20660
- const file = path60.join(dir, name);
20781
+ const file = path62.join(dir, name);
20661
20782
  try {
20662
- const { meta } = parseFrontmatter(fs56.readFileSync(file, "utf8"));
20783
+ const { meta } = parseFrontmatter(fs57.readFileSync(file, "utf8"));
20663
20784
  const cadence = meta?.cadence;
20664
20785
  if (typeof cadence === "string") adoptMemoryPruneCadence(cadence);
20665
20786
  } catch {
20666
20787
  }
20667
- fs56.rmSync(file, { force: true });
20788
+ fs57.rmSync(file, { force: true });
20668
20789
  return rel(file);
20669
20790
  }
20670
20791
  function legacyCards() {
20671
20792
  const dir = recurringDir();
20672
- if (!fs56.existsSync(dir)) return [];
20793
+ if (!fs57.existsSync(dir)) return [];
20673
20794
  const cards = [];
20674
- for (const entry of fs56.readdirSync(dir, { withFileTypes: true })) {
20795
+ for (const entry of fs57.readdirSync(dir, { withFileTypes: true })) {
20675
20796
  const id = idPrefix(entry.name);
20676
20797
  if (id === null) continue;
20677
- const full = path60.join(dir, entry.name);
20798
+ const full = path62.join(dir, entry.name);
20678
20799
  const slug2 = entry.name.replace(/\.md$/, "").replace(/^\d+-?/, "");
20679
20800
  if (entry.isFile() && entry.name.endsWith(".md")) cards.push({ id, slug: slug2, file: full });
20680
- else if (entry.isDirectory() && fs56.existsSync(path60.join(full, "root.md"))) {
20681
- cards.push({ id, slug: slug2, file: path60.join(full, "root.md"), folder: full });
20801
+ else if (entry.isDirectory() && fs57.existsSync(path62.join(full, "root.md"))) {
20802
+ cards.push({ id, slug: slug2, file: path62.join(full, "root.md"), folder: full });
20682
20803
  }
20683
20804
  }
20684
20805
  return cards.sort((a, b) => a.id - b.id);
20685
20806
  }
20686
20807
  function mergeInto2(from, to, skip = []) {
20687
- fs56.mkdirSync(to, { recursive: true });
20688
- for (const entry of fs56.readdirSync(from, { withFileTypes: true })) {
20808
+ fs57.mkdirSync(to, { recursive: true });
20809
+ for (const entry of fs57.readdirSync(from, { withFileTypes: true })) {
20689
20810
  if (skip.includes(entry.name)) continue;
20690
- const source = path60.join(from, entry.name);
20691
- const target = path60.join(to, entry.name);
20692
- if (!fs56.existsSync(target)) fs56.renameSync(source, target);
20693
- else if (entry.isDirectory() && fs56.statSync(target).isDirectory()) mergeInto2(source, target);
20811
+ const source = path62.join(from, entry.name);
20812
+ const target = path62.join(to, entry.name);
20813
+ if (!fs57.existsSync(target)) fs57.renameSync(source, target);
20814
+ else if (entry.isDirectory() && fs57.statSync(target).isDirectory()) mergeInto2(source, target);
20694
20815
  }
20695
- if (isEmpty(from)) fs56.rmSync(from, { recursive: true, force: true });
20816
+ if (isEmpty(from)) fs57.rmSync(from, { recursive: true, force: true });
20696
20817
  }
20697
20818
  function moveSideFolders() {
20698
20819
  const dir = recurringDir();
20699
- for (const entry of fs56.readdirSync(dir, { withFileTypes: true })) {
20820
+ for (const entry of fs57.readdirSync(dir, { withFileTypes: true })) {
20700
20821
  if (!entry.isDirectory() || idPrefix(entry.name) !== null) continue;
20701
- const from = path60.join(dir, entry.name);
20702
- if (isEmpty(from)) fs56.rmSync(from, { recursive: true, force: true });
20703
- else mergeInto2(from, path60.join(AGENTS, entry.name));
20822
+ const from = path62.join(dir, entry.name);
20823
+ if (isEmpty(from)) fs57.rmSync(from, { recursive: true, force: true });
20824
+ else mergeInto2(from, path62.join(AGENTS, entry.name));
20704
20825
  }
20705
20826
  }
20706
20827
  function sections(body) {
@@ -20750,8 +20871,8 @@ function agentName(card, text5) {
20750
20871
  const known2 = /* @__PURE__ */ new Set([...specAgentCatalog().agents.map((a) => a.name), ...ROLE_NAMES]);
20751
20872
  for (const name of [slug2, `${slug2}-${card.id}`]) {
20752
20873
  if (!AGENT_NAME.test(name)) continue;
20753
- const file = path60.join(AGENTS, name, AGENT_FILE3);
20754
- const written = fs56.existsSync(file) ? fs56.readFileSync(file, "utf8") : null;
20874
+ const file = path62.join(AGENTS, name, AGENT_FILE3);
20875
+ const written = fs57.existsSync(file) ? fs57.readFileSync(file, "utf8") : null;
20755
20876
  if (written === text5(name)) return name;
20756
20877
  if (written === null && !known2.has(name)) return name;
20757
20878
  }
@@ -20768,11 +20889,11 @@ ${q.options.map((o) => `- ${o}`).join("\n")}` : "";
20768
20889
  function removeCard(card) {
20769
20890
  dropCrossRefs(card.id);
20770
20891
  const target = card.folder ?? card.file;
20771
- stripReadmeRefs({ kind: card.folder ? "group" : "file", rel: path60.relative(TODO, target).split(path60.sep).join("/") });
20772
- fs56.rmSync(target, { recursive: true, force: true });
20892
+ stripReadmeRefs({ kind: card.folder ? "group" : "file", rel: path62.relative(TODO, target).split(path62.sep).join("/") });
20893
+ fs57.rmSync(target, { recursive: true, force: true });
20773
20894
  }
20774
20895
  function migrateCard(card) {
20775
- const { meta, body } = parseFrontmatter(fs56.readFileSync(card.file, "utf8"));
20896
+ const { meta, body } = parseFrontmatter(fs57.readFileSync(card.file, "utf8"));
20776
20897
  if (!meta) throw new Error("its frontmatter cannot be read");
20777
20898
  const raw = meta;
20778
20899
  const said3 = (key) => typeof raw[key] === "string" ? raw[key].trim() : "";
@@ -20782,19 +20903,19 @@ function migrateCard(card) {
20782
20903
  if (!flow) throw new Error("this board has no workflow to put it in");
20783
20904
  const text5 = (name2) => agentText(name2, meta.title || card.slug, body);
20784
20905
  const name = agentName(card, text5);
20785
- const dir = path60.join(AGENTS, name);
20786
- const file = path60.join(dir, AGENT_FILE3);
20787
- const madeDir = !fs56.existsSync(dir);
20788
- const wrote = !fs56.existsSync(file);
20906
+ const dir = path62.join(AGENTS, name);
20907
+ const file = path62.join(dir, AGENT_FILE3);
20908
+ const madeDir = !fs57.existsSync(dir);
20909
+ const wrote = !fs57.existsSync(file);
20789
20910
  try {
20790
- fs56.mkdirSync(dir, { recursive: true });
20791
- if (wrote) fs56.writeFileSync(file, text5(name));
20911
+ fs57.mkdirSync(dir, { recursive: true });
20912
+ if (wrote) fs57.writeFileSync(file, text5(name));
20792
20913
  const adopted = adoptWorkflowScheduled(flow.id, name, { cadence, lastRun });
20793
20914
  if (!adopted.ok) throw new Error(adopted.error);
20794
20915
  questionsToTriage(meta, name);
20795
20916
  } catch (e) {
20796
- if (madeDir) fs56.rmSync(dir, { recursive: true, force: true });
20797
- else if (wrote) fs56.rmSync(file, { force: true });
20917
+ if (madeDir) fs57.rmSync(dir, { recursive: true, force: true });
20918
+ else if (wrote) fs57.rmSync(file, { force: true });
20798
20919
  throw e;
20799
20920
  }
20800
20921
  if (card.folder) mergeInto2(card.folder, dir, ["root.md"]);
@@ -20804,7 +20925,7 @@ function migrateCard(card) {
20804
20925
  function migrateRecurringCards() {
20805
20926
  const done2 = { agents: [], removed: [], failed: [] };
20806
20927
  const dir = recurringDir();
20807
- if (!fs56.existsSync(dir)) return done2;
20928
+ if (!fs57.existsSync(dir)) return done2;
20808
20929
  migratePruneMemoryCard();
20809
20930
  moveSideFolders();
20810
20931
  for (const card of legacyCards()) {
@@ -20817,7 +20938,7 @@ function migrateRecurringCards() {
20817
20938
  done2.failed.push({ card: rel(card.file), why: e instanceof Error ? e.message : String(e) });
20818
20939
  }
20819
20940
  }
20820
- if (isEmpty(dir)) fs56.rmSync(dir, { recursive: true, force: true });
20941
+ if (isEmpty(dir)) fs57.rmSync(dir, { recursive: true, force: true });
20821
20942
  return done2;
20822
20943
  }
20823
20944
  function recurringMigrationLines(done2) {
@@ -20850,14 +20971,14 @@ var init_recurring = __esm({
20850
20971
  PRUNE_SLUG = "prune-the-memory";
20851
20972
  FETCH_SLUG = "fetch-triage-items";
20852
20973
  AGENT_FILE3 = "AGENT.md";
20853
- recurringDir = () => path60.join(TODO, LEGACY_RECURRING);
20854
- isEmpty = (dir) => fs56.readdirSync(dir).every((name) => name === ".DS_Store");
20974
+ recurringDir = () => path62.join(TODO, LEGACY_RECURRING);
20975
+ isEmpty = (dir) => fs57.readdirSync(dir).every((name) => name === ".DS_Store");
20855
20976
  SECTION = /^##\s+(.+?)\s*$/;
20856
20977
  }
20857
20978
  });
20858
20979
 
20859
20980
  // ../cli/src/commands/init.ts
20860
- import fs57 from "node:fs";
20981
+ import fs58 from "node:fs";
20861
20982
  function boardReadme() {
20862
20983
  return `# Board
20863
20984
 
@@ -20870,20 +20991,20 @@ _(none)_
20870
20991
  `;
20871
20992
  }
20872
20993
  function writeConfigIfMissing() {
20873
- if (fs57.existsSync(CONFIG)) return false;
20874
- fs57.mkdirSync(KANBAN, { recursive: true });
20875
- fs57.writeFileSync(CONFIG, CONFIG_TEMPLATE);
20994
+ if (fs58.existsSync(CONFIG)) return false;
20995
+ fs58.mkdirSync(KANBAN, { recursive: true });
20996
+ fs58.writeFileSync(CONFIG, CONFIG_TEMPLATE);
20876
20997
  return true;
20877
20998
  }
20878
20999
  function writeModulesIfMissing() {
20879
- if (fs57.existsSync(MODULES_MD)) return false;
20880
- fs57.mkdirSync(KANBAN, { recursive: true });
20881
- fs57.writeFileSync(MODULES_MD, MODULES_TEMPLATE);
21000
+ if (fs58.existsSync(MODULES_MD)) return false;
21001
+ fs58.mkdirSync(KANBAN, { recursive: true });
21002
+ fs58.writeFileSync(MODULES_MD, MODULES_TEMPLATE);
20882
21003
  return true;
20883
21004
  }
20884
21005
  function writeGitignoreIfMissing() {
20885
- fs57.mkdirSync(KANBAN, { recursive: true });
20886
- const text5 = fs57.existsSync(KANBAN_GITIGNORE) ? fs57.readFileSync(KANBAN_GITIGNORE, "utf8") : "";
21006
+ fs58.mkdirSync(KANBAN, { recursive: true });
21007
+ const text5 = fs58.existsSync(KANBAN_GITIGNORE) ? fs58.readFileSync(KANBAN_GITIGNORE, "utf8") : "";
20887
21008
  const has = (rule) => text5.split("\n").some((line) => line.trim() === rule.line);
20888
21009
  const missing = IGNORE_RULES.filter((rule) => !has(rule));
20889
21010
  if (!missing.length) return false;
@@ -20891,37 +21012,37 @@ function writeGitignoreIfMissing() {
20891
21012
  ${rule.line}
20892
21013
  `).join("");
20893
21014
  const separator = !text5 || text5.endsWith("\n") ? "" : "\n";
20894
- fs57.writeFileSync(KANBAN_GITIGNORE, `${text5}${separator}${block}`);
21015
+ fs58.writeFileSync(KANBAN_GITIGNORE, `${text5}${separator}${block}`);
20895
21016
  return true;
20896
21017
  }
20897
21018
  function writeSkeletonIfMissing() {
20898
21019
  const added = [];
20899
- if (!fs57.existsSync(TODO)) {
20900
- fs57.mkdirSync(TODO, { recursive: true });
21020
+ if (!fs58.existsSync(TODO)) {
21021
+ fs58.mkdirSync(TODO, { recursive: true });
20901
21022
  added.push(`${rel(TODO)}/`);
20902
21023
  }
20903
- if (!fs57.existsSync(README)) {
20904
- fs57.writeFileSync(README, boardReadme());
21024
+ if (!fs58.existsSync(README)) {
21025
+ fs58.writeFileSync(README, boardReadme());
20905
21026
  added.push(rel(README));
20906
21027
  }
20907
21028
  return added;
20908
21029
  }
20909
21030
  function retireReleases() {
20910
21031
  const done2 = [];
20911
- if (fs57.existsSync(RELEASES)) {
20912
- fs57.rmSync(RELEASES);
21032
+ if (fs58.existsSync(RELEASES)) {
21033
+ fs58.rmSync(RELEASES);
20913
21034
  done2.push(`deleted ${rel(RELEASES)}`);
20914
21035
  }
20915
21036
  let cards = 0;
20916
21037
  for (const dir of [TODO, ARCHIVE]) {
20917
- if (!fs57.existsSync(dir)) continue;
21038
+ if (!fs58.existsSync(dir)) continue;
20918
21039
  for (const file of walkMd(dir)) {
20919
- const text5 = fs57.readFileSync(file, "utf8");
21040
+ const text5 = fs58.readFileSync(file, "utf8");
20920
21041
  const end2 = text5.startsWith("---\n") ? text5.indexOf("\n---", 3) : -1;
20921
21042
  if (end2 < 0) continue;
20922
21043
  const head = text5.slice(0, end2).replace(/\nrelease:[^\n]*/, "");
20923
21044
  if (head.length === end2) continue;
20924
- fs57.writeFileSync(file, head + text5.slice(end2));
21045
+ fs58.writeFileSync(file, head + text5.slice(end2));
20925
21046
  cards++;
20926
21047
  }
20927
21048
  }
@@ -20929,7 +21050,7 @@ function retireReleases() {
20929
21050
  return done2.join(", ");
20930
21051
  }
20931
21052
  function cmdInit() {
20932
- if (fs57.existsSync(KANBAN)) {
21053
+ if (fs58.existsSync(KANBAN)) {
20933
21054
  const added = [
20934
21055
  ...writeSkeletonIfMissing(),
20935
21056
  writeConfigIfMissing() && rel(CONFIG),
@@ -20964,8 +21085,8 @@ function cmdInit() {
20964
21085
  }
20965
21086
  return { board: rel(KANBAN), created: false, added, memory_paths: scaffolded.map((s) => rel(s.dir)) };
20966
21087
  }
20967
- fs57.mkdirSync(TODO, { recursive: true });
20968
- fs57.writeFileSync(README, boardReadme());
21088
+ fs58.mkdirSync(TODO, { recursive: true });
21089
+ fs58.writeFileSync(README, boardReadme());
20969
21090
  writeConfigIfMissing();
20970
21091
  scaffoldProjectMemory();
20971
21092
  writeModulesIfMissing();
@@ -21054,25 +21175,25 @@ var init_scheduled = __esm({
21054
21175
  });
21055
21176
 
21056
21177
  // ../cli/src/lib/card-age.ts
21057
- import fs58 from "node:fs";
21058
- import path61 from "node:path";
21178
+ import fs59 from "node:fs";
21179
+ import path63 from "node:path";
21059
21180
  function staleAfterIn(config) {
21060
21181
  const days = Number(LINE.exec(config)?.[1]);
21061
21182
  return Number.isInteger(days) && days > 0 ? days : STALE_AFTER_DEFAULT;
21062
21183
  }
21063
21184
  function staleAfter() {
21064
- if (!fs58.existsSync(CONFIG)) return STALE_AFTER_DEFAULT;
21065
- return staleAfterIn(fs58.readFileSync(CONFIG, "utf8"));
21185
+ if (!fs59.existsSync(CONFIG)) return STALE_AFTER_DEFAULT;
21186
+ return staleAfterIn(fs59.readFileSync(CONFIG, "utf8"));
21066
21187
  }
21067
21188
  function todoPathspec(top) {
21068
- const from = path61.relative(real(top), real(TODO));
21069
- if (!from || from.startsWith("..") || path61.isAbsolute(from)) return null;
21070
- return from.split(path61.sep).join("/");
21189
+ const from = path63.relative(real(top), real(TODO));
21190
+ if (!from || from.startsWith("..") || path63.isAbsolute(from)) return null;
21191
+ return from.split(path63.sep).join("/");
21071
21192
  }
21072
21193
  function cardFile2(spec, printed) {
21073
21194
  const p = unquotePath(printed);
21074
21195
  if (!p.startsWith(`${spec}/`)) return null;
21075
- return path61.join(TODO, ...p.slice(spec.length + 1).split("/"));
21196
+ return path63.join(TODO, ...p.slice(spec.length + 1).split("/"));
21076
21197
  }
21077
21198
  function dirtyCards(top, spec) {
21078
21199
  const out = git(["status", "--porcelain", "--untracked-files=all", "--", spec], top);
@@ -21119,9 +21240,9 @@ var init_card_age = __esm({
21119
21240
  gitTop = () => git(["rev-parse", "--show-toplevel"])?.trim() || null;
21120
21241
  real = (p) => {
21121
21242
  try {
21122
- return fs58.realpathSync(p);
21243
+ return fs59.realpathSync(p);
21123
21244
  } catch {
21124
- return path61.resolve(p);
21245
+ return path63.resolve(p);
21125
21246
  }
21126
21247
  };
21127
21248
  unquotePath = (line) => {
@@ -21156,8 +21277,8 @@ var init_card_holds = __esm({
21156
21277
  });
21157
21278
 
21158
21279
  // ../cli/src/commands/list.ts
21159
- import fs59 from "node:fs";
21160
- import path62 from "node:path";
21280
+ import fs60 from "node:fs";
21281
+ import path64 from "node:path";
21161
21282
  function summaryLine(body) {
21162
21283
  const lines = body.split("\n");
21163
21284
  let i = 0;
@@ -21173,13 +21294,13 @@ function summaryLine(body) {
21173
21294
  function openRows() {
21174
21295
  const rows = [];
21175
21296
  for (const file of walkMd(TODO)) {
21176
- const base = path62.basename(file);
21297
+ const base = path64.basename(file);
21177
21298
  if (base === "README.md") continue;
21178
21299
  const isRoot = base === "root.md";
21179
- const id = isRoot ? idPrefix(path62.basename(path62.dirname(file))) : idPrefix(base);
21300
+ const id = isRoot ? idPrefix(path64.basename(path64.dirname(file))) : idPrefix(base);
21180
21301
  if (id == null) continue;
21181
- if (isLegacyRecurring(path62.relative(TODO, file))) continue;
21182
- const { meta, body } = parseFrontmatter(fs59.readFileSync(file, "utf8"));
21302
+ if (isLegacyRecurring(path64.relative(TODO, file))) continue;
21303
+ const { meta, body } = parseFrontmatter(fs60.readFileSync(file, "utf8"));
21183
21304
  rows.push({
21184
21305
  id,
21185
21306
  file,
@@ -21221,15 +21342,15 @@ function cmdStale(rows, all2, scope, mod) {
21221
21342
  }
21222
21343
  function archivedFiles() {
21223
21344
  const files2 = /* @__PURE__ */ new Map();
21224
- if (!fs59.existsSync(ARCHIVE)) return files2;
21345
+ if (!fs60.existsSync(ARCHIVE)) return files2;
21225
21346
  for (const file of walkMd(ARCHIVE)) {
21226
- const id = idPrefix(path62.basename(file));
21347
+ const id = idPrefix(path64.basename(file));
21227
21348
  if (id !== null) files2.set(id, file);
21228
21349
  }
21229
21350
  for (const dir of walkDirs(ARCHIVE)) {
21230
- const id = idPrefix(path62.basename(dir));
21231
- const root = path62.join(dir, "root.md");
21232
- if (id !== null && fs59.existsSync(root)) files2.set(id, root);
21351
+ const id = idPrefix(path64.basename(dir));
21352
+ const root = path64.join(dir, "root.md");
21353
+ if (id !== null && fs60.existsSync(root)) files2.set(id, root);
21233
21354
  }
21234
21355
  return files2;
21235
21356
  }
@@ -21340,14 +21461,14 @@ var init_list = __esm({
21340
21461
  });
21341
21462
 
21342
21463
  // ../cli/src/commands/misc.ts
21343
- import fs60 from "node:fs";
21344
- import path63 from "node:path";
21464
+ import fs61 from "node:fs";
21465
+ import path65 from "node:path";
21345
21466
  function extractOldMeta(text5, file) {
21346
21467
  const grab = (re) => {
21347
21468
  const m = text5.match(re);
21348
21469
  return m ? m[1].trim() : null;
21349
21470
  };
21350
- const title = grab(/^#\s+(.+)$/m) || slugify(path63.basename(file, ".md").replace(/^\d+-/, "")).replace(/-/g, " ");
21471
+ const title = grab(/^#\s+(.+)$/m) || slugify(path65.basename(file, ".md").replace(/^\d+-/, "")).replace(/-/g, " ");
21351
21472
  const norm = (raw) => {
21352
21473
  const v = (raw || "").toLowerCase();
21353
21474
  return LEVELS.includes(v) ? v : "med";
@@ -21374,11 +21495,11 @@ function stripOldHeader(text5) {
21374
21495
  }
21375
21496
  function cmdMigrate(opts) {
21376
21497
  const dry = opts.dryRun === true;
21377
- const files2 = walkMd(TODO).filter((f) => path63.basename(f) !== "README.md");
21498
+ const files2 = walkMd(TODO).filter((f) => path65.basename(f) !== "README.md");
21378
21499
  let changed = 0;
21379
21500
  let skipped = 0;
21380
21501
  for (const file of files2) {
21381
- const text5 = fs60.readFileSync(file, "utf8");
21502
+ const text5 = fs61.readFileSync(file, "utf8");
21382
21503
  if (text5.trimStart().startsWith("---")) {
21383
21504
  skipped++;
21384
21505
  continue;
@@ -21387,7 +21508,7 @@ function cmdMigrate(opts) {
21387
21508
  const out = serializeFrontmatter(meta) + "\n\n" + stripOldHeader(text5).replace(/^\n+/, "");
21388
21509
  if (dry) say(`would migrate ${rel(file)} (priority=${meta.priority})`);
21389
21510
  else {
21390
- fs60.writeFileSync(file, out.endsWith("\n") ? out : out + "\n");
21511
+ fs61.writeFileSync(file, out.endsWith("\n") ? out : out + "\n");
21391
21512
  say(`migrated ${rel(file)}`);
21392
21513
  }
21393
21514
  changed++;
@@ -21408,10 +21529,10 @@ var init_misc = __esm({
21408
21529
  });
21409
21530
 
21410
21531
  // ../cli/src/lib/group-close.ts
21411
- import fs61 from "node:fs";
21532
+ import fs62 from "node:fs";
21412
21533
  function groupCloses(rootFile) {
21413
21534
  try {
21414
- return groupCloseOf(fs61.readFileSync(rootFile, "utf8"));
21535
+ return groupCloseOf(fs62.readFileSync(rootFile, "utf8"));
21415
21536
  } catch {
21416
21537
  return false;
21417
21538
  }
@@ -21424,12 +21545,12 @@ var init_group_close = __esm({
21424
21545
  });
21425
21546
 
21426
21547
  // ../cli/src/commands/remove.ts
21427
- import fs62 from "node:fs";
21428
- import path64 from "node:path";
21548
+ import fs63 from "node:fs";
21549
+ import path66 from "node:path";
21429
21550
  function leavingIds(id, found2) {
21430
21551
  const ids = /* @__PURE__ */ new Set([id]);
21431
21552
  if (found2.kind === "group") {
21432
- const names4 = [...walkMd(found2.target), ...walkDirs(found2.target)].map((f) => path64.basename(f));
21553
+ const names4 = [...walkMd(found2.target), ...walkDirs(found2.target)].map((f) => path66.basename(f));
21433
21554
  for (const name of names4) {
21434
21555
  const n = idPrefix(name);
21435
21556
  if (n !== null) ids.add(n);
@@ -21441,10 +21562,10 @@ function findMentions(ids) {
21441
21562
  const hits = [];
21442
21563
  const re = new RegExp(ids.map((id) => `#${id}(?!\\d)`).join("|"));
21443
21564
  for (const dir of [TODO, MEMORY]) {
21444
- if (!fs62.existsSync(dir)) continue;
21565
+ if (!fs63.existsSync(dir)) continue;
21445
21566
  for (const file of walkMd(dir)) {
21446
- if (path64.basename(file) === "README.md") continue;
21447
- const lines = fs62.readFileSync(file, "utf8").split("\n");
21567
+ if (path66.basename(file) === "README.md") continue;
21568
+ const lines = fs63.readFileSync(file, "utf8").split("\n");
21448
21569
  const fmEnd = frontmatterEnd(lines);
21449
21570
  lines.forEach((line, i) => {
21450
21571
  if (!re.test(line)) return;
@@ -21460,12 +21581,12 @@ function findMentions(ids) {
21460
21581
  return hits;
21461
21582
  }
21462
21583
  function leavingCards(id, found2) {
21463
- const own = found2.kind === "group" ? path64.join(found2.target, "root.md") : found2.target;
21584
+ const own = found2.kind === "group" ? path66.join(found2.target, "root.md") : found2.target;
21464
21585
  const cards = [{ id, file: own }];
21465
21586
  if (found2.kind !== "group") return cards;
21466
21587
  for (const file of walkMd(found2.target)) {
21467
21588
  if (file === own) continue;
21468
- const n = idPrefix(path64.basename(file));
21589
+ const n = idPrefix(path66.basename(file));
21469
21590
  if (n !== null) cards.push({ id: n, file });
21470
21591
  }
21471
21592
  return cards;
@@ -21474,10 +21595,10 @@ function stampLeaving(cards, rejection) {
21474
21595
  const day = formatDay();
21475
21596
  const stamp4 = rejection && !rejection.discard ? { reason: rejection.reason, at: (/* @__PURE__ */ new Date()).toISOString(), by: insideRun() ? "agent" : "user" } : rejection && { reason: rejection.reason };
21476
21597
  for (const card of cards) {
21477
- if (!fs62.existsSync(card.file)) continue;
21478
- const text5 = fs62.readFileSync(card.file, "utf8");
21598
+ if (!fs63.existsSync(card.file)) continue;
21599
+ const text5 = fs63.readFileSync(card.file, "utf8");
21479
21600
  const stamped = stampLeavingIn(text5, day, stamp4);
21480
- if (stamped !== text5) fs62.writeFileSync(card.file, stamped);
21601
+ if (stamped !== text5) fs63.writeFileSync(card.file, stamped);
21481
21602
  }
21482
21603
  }
21483
21604
  function unfinishedDelivery(id) {
@@ -21493,7 +21614,7 @@ function unfinishedDelivery(id) {
21493
21614
  }[gap];
21494
21615
  return `#${id} is not finished: ${what}.`;
21495
21616
  }
21496
- const missing = planDeliverables(card.body).filter((src) => src.startsWith(".assets/")).find((src) => !fs62.existsSync(path64.join(ASSETS, src.slice(".assets/".length))));
21617
+ const missing = planDeliverables(card.body).filter((src) => src.startsWith(".assets/")).find((src) => !fs63.existsSync(path66.join(ASSETS, src.slice(".assets/".length))));
21497
21618
  return missing ? `#${id} is not finished: ${missing} is not on this machine.` : null;
21498
21619
  }
21499
21620
  function agentDecisions(text5) {
@@ -21520,8 +21641,8 @@ function removeCard2(id, metric, options) {
21520
21641
  const unfinished = metric === "completed" ? unfinishedDelivery(id) : null;
21521
21642
  if (unfinished) die(unfinished, { kind: "plan-delivery-unfinished" });
21522
21643
  const dest = options.cleanupDiscarded && locateArchived(id) ? null : archiveDest(found2);
21523
- const cardFile3 = found2.kind === "group" ? path64.join(found2.target, "root.md") : found2.target;
21524
- const cardText = fs62.existsSync(cardFile3) ? fs62.readFileSync(cardFile3, "utf8") : "";
21644
+ const cardFile3 = found2.kind === "group" ? path66.join(found2.target, "root.md") : found2.target;
21645
+ const cardText = fs63.existsSync(cardFile3) ? fs63.readFileSync(cardFile3, "utf8") : "";
21525
21646
  const { meta: cardMeta3 } = parseFrontmatter(cardText);
21526
21647
  const alsoRemoved = found2.kind === "group" ? walkMd(found2.target).filter((f) => f !== cardFile3).map((f) => rel(f)).sort() : [];
21527
21648
  const leftIds = leavingIds(id, found2);
@@ -21552,10 +21673,10 @@ function removeCard2(id, metric, options) {
21552
21673
  const groupRoot = found2.kind === "file" && !options.closing ? enclosingGroupRoot(found2.target) : null;
21553
21674
  if (dest) {
21554
21675
  stampLeaving(leaving, metric === "rejected" ? { reason: options.reason?.trim() ?? "", discard: options.discard === true } : null);
21555
- fs62.mkdirSync(ARCHIVE, { recursive: true });
21556
- fs62.renameSync(found2.target, dest);
21676
+ fs63.mkdirSync(ARCHIVE, { recursive: true });
21677
+ fs63.renameSync(found2.target, dest);
21557
21678
  } else {
21558
- fs62.rmSync(found2.target, { recursive: true, force: true });
21679
+ fs63.rmSync(found2.target, { recursive: true, force: true });
21559
21680
  }
21560
21681
  const removedRefs = stripReadmeRefs(found2);
21561
21682
  let marked2 = null;
@@ -21604,14 +21725,14 @@ function removeCard2(id, metric, options) {
21604
21725
  };
21605
21726
  }
21606
21727
  function closeGroup(rootFile) {
21607
- const rootId = idPrefix(path64.basename(path64.dirname(rootFile)));
21728
+ const rootId = idPrefix(path66.basename(path66.dirname(rootFile)));
21608
21729
  if (rootId === null) return null;
21609
21730
  if (runnerProject(KANBAN)) return null;
21610
21731
  if (!groupCloses(rootFile)) return null;
21611
21732
  say(`
21612
21733
  every subtask line on #${rootId} is resolved \u2014 closing the group:`);
21613
21734
  try {
21614
- const { meta, body } = parseFrontmatter(fs62.readFileSync(rootFile, "utf8"));
21735
+ const { meta, body } = parseFrontmatter(fs63.readFileSync(rootFile, "utf8"));
21615
21736
  const res = cmdRemove(rootId, "completed", { closing: true });
21616
21737
  return {
21617
21738
  id: rootId,
@@ -21668,7 +21789,7 @@ function cleanupDiscardedCards(sessionId) {
21668
21789
  if (card.pending) throw new Error(`Discarding #${card.id} did not finish. Retry the discard.`);
21669
21790
  while (locate(card.id)) cmdRemove(card.id, "rejected", { discard: true, cleanupDiscarded: true });
21670
21791
  dropCrossRefs(card.id);
21671
- stripReadmeRefs({ kind: "file", rel: path64.relative(TODO, path64.resolve(REPO_ROOT, card.path)).split(path64.sep).join("/") });
21792
+ stripReadmeRefs({ kind: "file", rel: path66.relative(TODO, path66.resolve(REPO_ROOT, card.path)).split(path66.sep).join("/") });
21672
21793
  }
21673
21794
  return discarded.map((c) => c.id);
21674
21795
  });
@@ -21754,8 +21875,8 @@ var init_memory_file = __esm({
21754
21875
  });
21755
21876
 
21756
21877
  // ../cli/src/commands/rule.ts
21757
- import fs63 from "node:fs";
21758
- import path65 from "node:path";
21878
+ import fs64 from "node:fs";
21879
+ import path67 from "node:path";
21759
21880
  function cmdRule(askedName, flags) {
21760
21881
  const agent = askedName.trim();
21761
21882
  if (!agentRoster().some((a) => a.name === agent)) {
@@ -21764,7 +21885,7 @@ function cmdRule(askedName, flags) {
21764
21885
  const rule = readRuleText(flags.file, flags.text);
21765
21886
  const res = setAgentRule(agent, rule);
21766
21887
  if (!res.ok) die(res.error);
21767
- const file = rel(path65.join(RULES, `${agent}.md`));
21888
+ const file = rel(path67.join(RULES, `${agent}.md`));
21768
21889
  say(rule ? `wrote the \`${agent}\` rule (${file})` : `cleared the \`${agent}\` rule`);
21769
21890
  return { agent, file, cleared: !rule };
21770
21891
  }
@@ -21772,7 +21893,7 @@ function readRuleText(file, text5) {
21772
21893
  if (file !== void 0 && text5 !== void 0) die("pass --file or --text, not both");
21773
21894
  if (file !== void 0) {
21774
21895
  try {
21775
- return fs63.readFileSync(file, "utf8").trim();
21896
+ return fs64.readFileSync(file, "utf8").trim();
21776
21897
  } catch {
21777
21898
  die(`can't read ${file} \u2014 write the rule to a file, then pass its path`);
21778
21899
  }
@@ -21899,22 +22020,22 @@ var init_storyboard = __esm({
21899
22020
  });
21900
22021
 
21901
22022
  // ../cli/src/lib/spec-contract.ts
21902
- import fs64 from "node:fs";
21903
- import path66 from "node:path";
22023
+ import fs65 from "node:fs";
22024
+ import path68 from "node:path";
21904
22025
  function snapshotSpecs() {
21905
22026
  const out = /* @__PURE__ */ new Map();
21906
- if (!fs64.existsSync(TODO)) return out;
22027
+ if (!fs65.existsSync(TODO)) return out;
21907
22028
  for (const file of walkMd(TODO)) {
21908
- if (isLegacyRecurring(path66.relative(TODO, file))) continue;
21909
- const id = idPrefix(path66.basename(file) === "root.md" ? path66.basename(path66.dirname(file)) : path66.basename(file));
21910
- if (id !== null) out.set(file, { id, text: fs64.readFileSync(file, "utf8") });
22029
+ if (isLegacyRecurring(path68.relative(TODO, file))) continue;
22030
+ const id = idPrefix(path68.basename(file) === "root.md" ? path68.basename(path68.dirname(file)) : path68.basename(file));
22031
+ if (id !== null) out.set(file, { id, text: fs65.readFileSync(file, "utf8") });
21911
22032
  }
21912
22033
  return out;
21913
22034
  }
21914
22035
  function enclosingGroups(file) {
21915
22036
  const ids = [];
21916
22037
  for (let root = enclosingGroupRoot(file); root; root = enclosingGroupRoot(root)) {
21917
- const id = idPrefix(path66.basename(path66.dirname(root)));
22038
+ const id = idPrefix(path68.basename(path68.dirname(root)));
21918
22039
  if (id !== null) ids.push(id);
21919
22040
  }
21920
22041
  return ids;
@@ -22083,9 +22204,9 @@ function validateSpec(file, text5, id) {
22083
22204
  }
22084
22205
  function inside(dir, name) {
22085
22206
  try {
22086
- const root = fs64.realpathSync(dir);
22087
- const real2 = fs64.realpathSync(path66.join(dir, name));
22088
- return real2.startsWith(root + path66.sep) && fs64.statSync(real2).isFile() ? real2 : null;
22207
+ const root = fs65.realpathSync(dir);
22208
+ const real2 = fs65.realpathSync(path68.join(dir, name));
22209
+ return real2.startsWith(root + path68.sep) && fs65.statSync(real2).isFile() ? real2 : null;
22089
22210
  } catch {
22090
22211
  return null;
22091
22212
  }
@@ -22103,7 +22224,7 @@ function variantsProblem(raw, src, id) {
22103
22224
  if (files2.length < 2 || !files2.every((f) => typeof f === "string" && f)) return `variants needs at least two labels, each naming a file. ${shape}`;
22104
22225
  if (!files2.includes(src)) return `src="${src}" is not one of the variants. Set src to the version shown first.`;
22105
22226
  if (id === void 0) return null;
22106
- const dir = path66.join(ASSETS, String(id));
22227
+ const dir = path68.join(ASSETS, String(id));
22107
22228
  for (const f of files2) {
22108
22229
  const parts = f.split("/");
22109
22230
  if (parts.length !== 3 || parts[0] !== ".assets" || parts[1] !== String(id) || !inside(dir, parts[2])) {
@@ -22113,7 +22234,7 @@ function variantsProblem(raw, src, id) {
22113
22234
  return null;
22114
22235
  }
22115
22236
  function compareProblem(attrs, id) {
22116
- const dir = path66.join(ASSETS, String(id));
22237
+ const dir = path68.join(ASSETS, String(id));
22117
22238
  const src = (message) => ({ rule: "compare-src", message });
22118
22239
  const shots = (message) => ({ rule: "compare-shots", message });
22119
22240
  for (const side of ["a", "b"]) {
@@ -22133,7 +22254,7 @@ function compareProblem(attrs, id) {
22133
22254
  const file = inside(dir, named2.name);
22134
22255
  if (!file) return { message: `Compare shots ${at} names no file in this card's asset folder. Write the JSON there or fix the path.` };
22135
22256
  try {
22136
- return { data: JSON.parse(fs64.readFileSync(file, "utf8")) };
22257
+ return { data: JSON.parse(fs65.readFileSync(file, "utf8")) };
22137
22258
  } catch {
22138
22259
  return { message: `${at} is not valid JSON.` };
22139
22260
  }
@@ -22153,7 +22274,7 @@ function compareProblem(attrs, id) {
22153
22274
  }
22154
22275
  function validateStoryboards(file, id, text5) {
22155
22276
  const errors = [];
22156
- const dir = path66.join(ASSETS, String(id));
22277
+ const dir = path68.join(ASSETS, String(id));
22157
22278
  for (const marker of storyboardMarkers(text5)) {
22158
22279
  const named2 = assetName(marker.src, id, ["json"]);
22159
22280
  const add = (message) => errors.push({ file: rel(file), line: marker.line, rule: "storyboard-src", message });
@@ -22302,8 +22423,8 @@ var init_mockups = __esm({
22302
22423
  });
22303
22424
 
22304
22425
  // ../cli/src/commands/spec-write.ts
22305
- import fs65 from "node:fs";
22306
- import path67 from "node:path";
22426
+ import fs66 from "node:fs";
22427
+ import path69 from "node:path";
22307
22428
  function cmdSpecWrite(id, askedName, flags) {
22308
22429
  const agent = findSpecAgent(askedName);
22309
22430
  if (!agent) die(notAnAgent(askedName), { kind: "no-such-spec-agent", specAgent: askedName });
@@ -22311,11 +22432,11 @@ function cmdSpecWrite(id, askedName, flags) {
22311
22432
  const section = readSection(flags.file, flags.text);
22312
22433
  const found2 = locate(id);
22313
22434
  if (!found2) die(`no task with id ${id} under ${rel(TODO)}`, { kind: "card-not-found", id });
22314
- const file = found2.kind === "group" ? path67.join(found2.target, "root.md") : found2.target;
22315
- const { meta, body } = parseFrontmatter(fs65.readFileSync(file, "utf8"));
22435
+ const file = found2.kind === "group" ? path69.join(found2.target, "root.md") : found2.target;
22436
+ const { meta, body } = parseFrontmatter(fs66.readFileSync(file, "utf8"));
22316
22437
  if (!meta) die(`${rel(file)} has no frontmatter \u2014 run \`migrate\` first`);
22317
22438
  const { body: next, replaced } = splice(body, name, section);
22318
- fs65.writeFileSync(file, serializeFrontmatter(meta) + "\n" + next);
22439
+ fs66.writeFileSync(file, serializeFrontmatter(meta) + "\n" + next);
22319
22440
  say(`${replaced ? "rewrote" : "wrote"} the \`${name}\` section on #${id} (${rel(file)})`);
22320
22441
  return { id, specAgent: name, replaced, file: rel(file) };
22321
22442
  }
@@ -22324,7 +22445,7 @@ function readSection(file, text5) {
22324
22445
  let raw;
22325
22446
  if (file !== void 0) {
22326
22447
  try {
22327
- raw = fs65.readFileSync(file, "utf8");
22448
+ raw = fs66.readFileSync(file, "utf8");
22328
22449
  } catch {
22329
22450
  die(`can't read ${file} \u2014 write your section to a file, then pass its path`);
22330
22451
  }
@@ -22790,11 +22911,11 @@ var init_shared = __esm({
22790
22911
  });
22791
22912
 
22792
22913
  // ../cli/src/commands/goal.ts
22793
- import fs66 from "node:fs";
22914
+ import fs67 from "node:fs";
22794
22915
  function load() {
22795
22916
  const file = goalsFile();
22796
22917
  if (!file) die("Goals exist only inside a WildCrane goal discussion.");
22797
- const held4 = JSON.parse(fs66.readFileSync(file, "utf8"));
22918
+ const held4 = JSON.parse(fs67.readFileSync(file, "utf8"));
22798
22919
  if (held4.goals !== void 0 && held4.ideas !== void 0 && JSON.stringify(held4.goals) !== JSON.stringify(held4.ideas)) die("goals and ideas do not match.");
22799
22920
  const goals = held4.goals ?? held4.ideas;
22800
22921
  if (!Array.isArray(goals)) die("The goals array is missing or invalid.");
@@ -22819,7 +22940,7 @@ function splitBody2(text5) {
22819
22940
  }
22820
22941
  function readBody(bodyFile) {
22821
22942
  if (!bodyFile) die("--body-file is required; write the goal in a writable temporary file first.");
22822
- const text5 = fs66.readFileSync(bodyFile, "utf8");
22943
+ const text5 = fs67.readFileSync(bodyFile, "utf8");
22823
22944
  if (!text5.trim()) die("The goal must not be empty.");
22824
22945
  return splitBody2(text5);
22825
22946
  }
@@ -22831,7 +22952,7 @@ function named(held4, id) {
22831
22952
  function cmdGoal(args, opts) {
22832
22953
  const sub = args[0] ?? "";
22833
22954
  const { file, held: held4 } = load();
22834
- const write2 = () => fs66.writeFileSync(file, `${JSON.stringify({ workflows: held4.workflows, ideas: held4.goals }, null, 2)}
22955
+ const write2 = () => fs67.writeFileSync(file, `${JSON.stringify({ workflows: held4.workflows, ideas: held4.goals }, null, 2)}
22835
22956
  `);
22836
22957
  if (sub === "new") {
22837
22958
  const title = opts.title?.trim();
@@ -22901,8 +23022,8 @@ __export(board_exports, {
22901
23022
  buildBoardProgram: () => buildBoardProgram
22902
23023
  });
22903
23024
  import crypto4 from "node:crypto";
22904
- import fs67 from "node:fs";
22905
- import path68 from "node:path";
23025
+ import fs68 from "node:fs";
23026
+ import path70 from "node:path";
22906
23027
  async function dispatch(move2, cmd, args, opts, cli) {
22907
23028
  const ctx = ctxOf(cmd, cli.program);
22908
23029
  const data = await runAction(ctx, {}, async () => {
@@ -22941,7 +23062,7 @@ async function cloudCard(task, opts) {
22941
23062
  let request;
22942
23063
  if (typeof opts.bodyFile === "string") {
22943
23064
  try {
22944
- request = fs67.readFileSync(opts.bodyFile, "utf8");
23065
+ request = fs68.readFileSync(opts.bodyFile, "utf8");
22945
23066
  } catch {
22946
23067
  }
22947
23068
  }
@@ -22971,7 +23092,7 @@ async function cloudCard(task, opts) {
22971
23092
  const file = creationReceipt(`${checked.value}:${requestId}`, slot);
22972
23093
  const journal = readCreation(file, said3);
22973
23094
  if (!journal.made) {
22974
- const key = `akb-${path68.basename(file, ".json").slice(0, 40)}`;
23095
+ const key = `akb-${path70.basename(file, ".json").slice(0, 40)}`;
22975
23096
  const made = await createProjectTask(checked.value, journal.said, key);
22976
23097
  if (!made.ok) throw new BoardError(creationError(`Cloud did not make the task: ${made.error}`, triage), { kind: "refused" });
22977
23098
  journal.made = made.value;
@@ -23116,16 +23237,16 @@ var init_board2 = __esm({
23116
23237
  });
23117
23238
 
23118
23239
  // ../cli/src/lib/board-cli.ts
23119
- import fs68 from "node:fs";
23120
- import path69 from "node:path";
23240
+ import fs69 from "node:fs";
23241
+ import path71 from "node:path";
23121
23242
  function useBoard(found2, dirNamed) {
23122
- if (!found2.named && found2.board === path69.join(found2.root, "docs", "kanban")) setBoardRoot(found2.root, dirNamed);
23243
+ if (!found2.named && found2.board === path71.join(found2.root, "docs", "kanban")) setBoardRoot(found2.root, dirNamed);
23123
23244
  else setBoardDir(found2.board, found2.root);
23124
23245
  useProjectState();
23125
23246
  }
23126
23247
  function whole(found2, move2) {
23127
23248
  if (move2 === MAKES_A_BOARD) return found2;
23128
- if (!fs68.existsSync(path69.join(found2.board, "todo"))) {
23249
+ if (!fs69.existsSync(path71.join(found2.board, "todo"))) {
23129
23250
  die(`the board in ${found2.board} has no todo/ \u2014 run \`init\` to add what is missing.`, {
23130
23251
  kind: "board-incomplete",
23131
23252
  dir: found2.root
@@ -23134,13 +23255,13 @@ function whole(found2, move2) {
23134
23255
  return found2;
23135
23256
  }
23136
23257
  function findBoardUpward(from, move2) {
23137
- let dir = path69.resolve(from);
23258
+ let dir = path71.resolve(from);
23138
23259
  for (; ; ) {
23139
- if (hasBoard(dir)) return { root: dir, board: path69.join(dir, "docs", "kanban") };
23260
+ if (hasBoard(dir)) return { root: dir, board: path71.join(dir, "docs", "kanban") };
23140
23261
  if (isBoardDir(dir)) return { root: projectRootOf(dir), board: dir };
23141
- const kept2 = path69.join(dir, ".akb", "kanban");
23142
- if (move2 !== MAKES_A_BOARD && fs68.existsSync(path69.join(kept2, "todo"))) return { root: dir, board: kept2 };
23143
- const up = path69.dirname(dir);
23262
+ const kept2 = path71.join(dir, ".akb", "kanban");
23263
+ if (move2 !== MAKES_A_BOARD && fs69.existsSync(path71.join(kept2, "todo"))) return { root: dir, board: kept2 };
23264
+ const up = path71.dirname(dir);
23144
23265
  if (up === dir) return null;
23145
23266
  dir = up;
23146
23267
  }
@@ -23148,11 +23269,11 @@ function findBoardUpward(from, move2) {
23148
23269
  function resolveBoard(move2, { board: board2, dir, cwd, installHint }) {
23149
23270
  const named2 = board2 ?? boardFromEnv();
23150
23271
  if (named2) {
23151
- const folder = path69.resolve(cwd, named2);
23152
- if (!fs68.existsSync(folder) && move2 !== MAKES_A_BOARD) {
23272
+ const folder = path71.resolve(cwd, named2);
23273
+ if (!fs69.existsSync(folder) && move2 !== MAKES_A_BOARD) {
23153
23274
  die(`no such folder: ${folder}`, { kind: "no-such-folder", dir: folder });
23154
23275
  }
23155
- if (fs68.existsSync(folder) && !isBoardDir(folder) && move2 !== MAKES_A_BOARD) {
23276
+ if (fs69.existsSync(folder) && !isBoardDir(folder) && move2 !== MAKES_A_BOARD) {
23156
23277
  die(`${folder} is not a board \u2014 it has no todo/ and config.md. ${installHint}\u3002`, {
23157
23278
  kind: "no-board",
23158
23279
  dir: folder
@@ -23161,24 +23282,24 @@ function resolveBoard(move2, { board: board2, dir, cwd, installHint }) {
23161
23282
  return { root: projectRootOf(folder), board: folder, named: true };
23162
23283
  }
23163
23284
  if (dir !== null) {
23164
- const root = path69.resolve(dir);
23165
- if (!fs68.existsSync(root)) die(`no such folder: ${root}`, { kind: "no-such-folder", dir: root });
23166
- const kept2 = path69.join(root, ".akb", "kanban");
23167
- if (!hasBoard(root) && move2 !== MAKES_A_BOARD && fs68.existsSync(path69.join(kept2, "todo"))) return whole({ root, board: kept2 }, move2);
23285
+ const root = path71.resolve(dir);
23286
+ if (!fs69.existsSync(root)) die(`no such folder: ${root}`, { kind: "no-such-folder", dir: root });
23287
+ const kept2 = path71.join(root, ".akb", "kanban");
23288
+ if (!hasBoard(root) && move2 !== MAKES_A_BOARD && fs69.existsSync(path71.join(kept2, "todo"))) return whole({ root, board: kept2 }, move2);
23168
23289
  if (!hasBoard(root) && move2 !== MAKES_A_BOARD) {
23169
23290
  die(`no board in ${root} \u2014 it has no docs/kanban/. ${installHint}\u3002`, {
23170
23291
  kind: "no-board",
23171
23292
  dir: root
23172
23293
  });
23173
23294
  }
23174
- return whole({ root, board: path69.join(root, "docs", "kanban") }, move2);
23295
+ return whole({ root, board: path71.join(root, "docs", "kanban") }, move2);
23175
23296
  }
23176
23297
  const found2 = findBoardUpward(cwd, move2);
23177
23298
  if (found2) return whole(found2, move2);
23178
- if (move2 === MAKES_A_BOARD) return { root: path69.resolve(cwd), board: path69.join(path69.resolve(cwd), "docs", "kanban") };
23299
+ if (move2 === MAKES_A_BOARD) return { root: path71.resolve(cwd), board: path71.join(path71.resolve(cwd), "docs", "kanban") };
23179
23300
  die(
23180
- `no board here. Looked in ${path69.resolve(cwd)} and every folder above it for docs/kanban/. ${installHint}\uFF0C\u6216\u7528 --board \u6307\u5B9A\u5DF2\u6709\u770B\u677F\u3002`,
23181
- { kind: "no-board", dir: path69.resolve(cwd) }
23301
+ `no board here. Looked in ${path71.resolve(cwd)} and every folder above it for docs/kanban/. ${installHint}\uFF0C\u6216\u7528 --board \u6307\u5B9A\u5DF2\u6709\u770B\u677F\u3002`,
23302
+ { kind: "no-board", dir: path71.resolve(cwd) }
23182
23303
  );
23183
23304
  }
23184
23305
  function editDistance(a, b) {
@@ -23260,8 +23381,8 @@ var init_board_cli = __esm({
23260
23381
  init_io();
23261
23382
  init_pointer();
23262
23383
  MAKES_A_BOARD = "init";
23263
- hasBoard = (dir) => fs68.existsSync(path69.join(dir, "docs", "kanban"));
23264
- isBoardDir = (dir) => fs68.existsSync(path69.join(dir, "todo")) && fs68.existsSync(path69.join(dir, "config.md"));
23384
+ hasBoard = (dir) => fs69.existsSync(path71.join(dir, "docs", "kanban"));
23385
+ isBoardDir = (dir) => fs69.existsSync(path71.join(dir, "todo")) && fs69.existsSync(path71.join(dir, "config.md"));
23265
23386
  boardFromEnv = () => {
23266
23387
  const named2 = process.env.AI4KANBAN_BOARD?.trim();
23267
23388
  return named2 ? named2 : null;
@@ -23528,8 +23649,8 @@ var init_guide = __esm({
23528
23649
  });
23529
23650
 
23530
23651
  // ../cli/src/lib/agent/approve.ts
23531
- import fs74 from "node:fs";
23532
- import path75 from "node:path";
23652
+ import fs75 from "node:fs";
23653
+ import path77 from "node:path";
23533
23654
  function approvalRequest(req) {
23534
23655
  const id = req.id;
23535
23656
  const card = id === void 0 ? null : findCard(id);
@@ -23547,12 +23668,12 @@ function advancePhase(id) {
23547
23668
  const card = findCard(id);
23548
23669
  const found2 = locate(id);
23549
23670
  if (!card || !found2 || !awaitingApproval(card)) return { error: `#${id} is no longer waiting on approval.` };
23550
- const file = found2.kind === "group" ? path75.join(found2.target, "root.md") : found2.target;
23551
- const { meta, body } = parseFrontmatter(fs74.readFileSync(file, "utf8"));
23671
+ const file = found2.kind === "group" ? path77.join(found2.target, "root.md") : found2.target;
23672
+ const { meta, body } = parseFrontmatter(fs75.readFileSync(file, "utf8"));
23552
23673
  if (!meta) return { error: `#${id} has no frontmatter` };
23553
23674
  meta.phase = card.stage + 1;
23554
23675
  meta.status = "todo";
23555
- fs74.writeFileSync(file, serializeFrontmatter(meta) + "\n" + body);
23676
+ fs75.writeFileSync(file, serializeFrontmatter(meta) + "\n" + body);
23556
23677
  return null;
23557
23678
  }
23558
23679
  var init_approve = __esm({
@@ -23676,16 +23797,16 @@ var init_start = __esm({
23676
23797
  });
23677
23798
 
23678
23799
  // ../cli/src/lib/agent/task-chats.ts
23679
- import fs75 from "node:fs";
23680
- import path76 from "node:path";
23800
+ import fs76 from "node:fs";
23801
+ import path78 from "node:path";
23681
23802
  function taskChatFile(task, home3) {
23682
23803
  if (!TASK.test(task)) throw new Error(`${task} is not a task id.`);
23683
- return path76.join(taskChatsDir(home3), `${task}.jsonl`);
23804
+ return path78.join(taskChatsDir(home3), `${task}.jsonl`);
23684
23805
  }
23685
23806
  function readTaskChat(task, home3) {
23686
23807
  let text5;
23687
23808
  try {
23688
- text5 = fs75.readFileSync(taskChatFile(task, home3), "utf8");
23809
+ text5 = fs76.readFileSync(taskChatFile(task, home3), "utf8");
23689
23810
  } catch {
23690
23811
  return [];
23691
23812
  }
@@ -23706,13 +23827,13 @@ var init_task_chats = __esm({
23706
23827
  "use strict";
23707
23828
  init_home();
23708
23829
  TASK = /^[\w-]{1,100}$/;
23709
- taskChatsDir = (home3 = machineHome()) => path76.join(home3, "chats");
23830
+ taskChatsDir = (home3 = machineHome()) => path78.join(home3, "chats");
23710
23831
  }
23711
23832
  });
23712
23833
 
23713
23834
  // ../cli/src/lib/agent/memory-review.ts
23714
- import fs76 from "node:fs";
23715
- import path77 from "node:path";
23835
+ import fs77 from "node:fs";
23836
+ import path79 from "node:path";
23716
23837
  function taskChatsToReview(tasks) {
23717
23838
  const out = [];
23718
23839
  for (const ref of [...new Set(tasks)]) {
@@ -23734,8 +23855,8 @@ function readArchived2(id) {
23734
23855
  const found2 = Number.isInteger(id) && id > 0 ? locateArchived(id) : null;
23735
23856
  if (!found2) return null;
23736
23857
  try {
23737
- const file = found2.kind === "group" ? path77.join(found2.target, "root.md") : found2.target;
23738
- const { meta, body } = parseFrontmatter(fs76.readFileSync(file, "utf8"));
23858
+ const file = found2.kind === "group" ? path79.join(found2.target, "root.md") : found2.target;
23859
+ const { meta, body } = parseFrontmatter(fs77.readFileSync(file, "utf8"));
23739
23860
  if (!meta) return null;
23740
23861
  return { id, title: meta.title, modules: meta.modules ?? [], rejected: meta.rejected, body };
23741
23862
  } catch {
@@ -23804,17 +23925,17 @@ var init_memory_review = __esm({
23804
23925
  });
23805
23926
 
23806
23927
  // ../cli/src/lib/agent/dismissal-review.ts
23807
- import fs77 from "node:fs";
23808
- import path78 from "node:path";
23928
+ import fs78 from "node:fs";
23929
+ import path80 from "node:path";
23809
23930
  function rejectionsToReview() {
23810
- const dir = path78.join(ARCHIVE, REJECTIONS_TO_REVIEW);
23811
- if (!fs77.existsSync(dir)) return [];
23931
+ const dir = path80.join(ARCHIVE, REJECTIONS_TO_REVIEW);
23932
+ if (!fs78.existsSync(dir)) return [];
23812
23933
  const found2 = [];
23813
23934
  for (const file of walkMd(dir)) {
23814
23935
  const id = archivedId(file);
23815
23936
  if (id === null) continue;
23816
23937
  try {
23817
- const { meta } = parseFrontmatter(fs77.readFileSync(file, "utf8"));
23938
+ const { meta } = parseFrontmatter(fs78.readFileSync(file, "utf8"));
23818
23939
  if (!meta?.rejected || meta.rejected_by !== "user" || !meta.rejected_reason.trim()) continue;
23819
23940
  found2.push({ id, title: meta.title, file: rel(file), reason: meta.rejected_reason.trim(), rejectedAt: meta.rejected_at });
23820
23941
  } catch {
@@ -23844,7 +23965,7 @@ function citedSources(text5) {
23844
23965
  function withdrawnSources() {
23845
23966
  const cited = [...new Set(plannerCopies("rejected.md").flatMap((file) => {
23846
23967
  try {
23847
- return citedSources(fs77.readFileSync(file, "utf8"));
23968
+ return citedSources(fs78.readFileSync(file, "utf8"));
23848
23969
  } catch {
23849
23970
  return [];
23850
23971
  }
@@ -23869,8 +23990,8 @@ var init_dismissal_review = __esm({
23869
23990
  });
23870
23991
 
23871
23992
  // ../cli/src/lib/agent/flow.ts
23872
- import fs78 from "node:fs";
23873
- import path79 from "node:path";
23993
+ import fs79 from "node:fs";
23994
+ import path81 from "node:path";
23874
23995
  function readCard2(id, home3 = "board") {
23875
23996
  const found2 = home3 === "archive" ? locateArchived(id) : locate(id);
23876
23997
  if (!found2) {
@@ -23879,10 +24000,10 @@ function readCard2(id, home3 = "board") {
23879
24000
  { kind: "card-not-found", id }
23880
24001
  );
23881
24002
  }
23882
- const file = found2.kind === "group" ? path79.join(found2.target, "root.md") : found2.target;
24003
+ const file = found2.kind === "group" ? path81.join(found2.target, "root.md") : found2.target;
23883
24004
  let text5;
23884
24005
  try {
23885
- text5 = fs78.readFileSync(file, "utf8");
24006
+ text5 = fs79.readFileSync(file, "utf8");
23886
24007
  } catch {
23887
24008
  die(`#${id} is on the board but ${rel(file)} can't be read.`, { kind: "card-unreadable", id });
23888
24009
  }
@@ -24006,7 +24127,7 @@ function workspaceField(delivery) {
24006
24127
  if (!delivery?.worktree) return [];
24007
24128
  return field("workspace", [
24008
24129
  `write code in ${delivery.worktree} \u2014 this delivery's own worktree, on branch ${delivery.branch}.`,
24009
- path79.resolve(process.cwd()).startsWith(worktreeDir(delivery.worktree)) ? `it is your working folder already; the board's own files are NOT in it and never go on that branch.` : `cd into it first \u2014 you are in the project checkout, and code written here is not part of this build. The board's own files are NOT in it and never go on that branch.`,
24130
+ path81.resolve(process.cwd()).startsWith(worktreeDir(delivery.worktree)) ? `it is your working folder already; the board's own files are NOT in it and never go on that branch.` : `cd into it first \u2014 you are in the project checkout, and code written here is not part of this build. The board's own files are NOT in it and never go on that branch.`,
24010
24131
  `every board command names the project's own copy: \`${boardCommandFor()} <command>\`.`,
24011
24132
  `${rel(REPO_ROOT)} is the project \u2014 the card, the memory files and the docs are changed there, not here.`
24012
24133
  ]);
@@ -24750,7 +24871,7 @@ __export(watch_exports, {
24750
24871
  watchRun: () => watchRun
24751
24872
  });
24752
24873
  import { spawn as spawn4 } from "node:child_process";
24753
- import fs79 from "node:fs";
24874
+ import fs80 from "node:fs";
24754
24875
  async function watchRun(sessionId, resume = startResume) {
24755
24876
  const spec = readSpec(sessionId);
24756
24877
  const run2 = peekRun(sessionId);
@@ -24786,11 +24907,11 @@ async function watchRun(sessionId, resume = startResume) {
24786
24907
  const record = claimed ?? run2;
24787
24908
  const sub = record.action === "sub";
24788
24909
  if (claim) await setCardStatus(claim.cardId, claim.status);
24789
- fs79.mkdirSync(SESSIONS_DIR, { recursive: true });
24790
- const logFile = fs79.createWriteStream(record.logPath, { flags: "a" });
24910
+ fs80.mkdirSync(SESSIONS_DIR, { recursive: true });
24911
+ const logFile = fs80.createWriteStream(record.logPath, { flags: "a" });
24791
24912
  logFile.on("error", () => {
24792
24913
  });
24793
- const eventsFile = fs79.createWriteStream(eventsPathOf(sessionId), { flags: "a" });
24914
+ const eventsFile = fs80.createWriteStream(eventsPathOf(sessionId), { flags: "a" });
24794
24915
  eventsFile.on("error", () => {
24795
24916
  });
24796
24917
  const event2 = (e) => eventsFile.write(`${JSON.stringify(e)}
@@ -25379,7 +25500,7 @@ var init_watch = __esm({
25379
25500
  });
25380
25501
 
25381
25502
  // ../cli/src/kanban.ts
25382
- import fs85 from "node:fs";
25503
+ import fs86 from "node:fs";
25383
25504
  import { fileURLToPath as fileURLToPath3 } from "node:url";
25384
25505
 
25385
25506
  // ../cli/src/lib/cli/agent.ts
@@ -25868,8 +25989,8 @@ async function testAgent(args) {
25868
25989
  }
25869
25990
 
25870
25991
  // ../cli/src/commands/cloud.ts
25871
- import fs70 from "node:fs";
25872
- import path71 from "node:path";
25992
+ import fs71 from "node:fs";
25993
+ import path73 from "node:path";
25873
25994
 
25874
25995
  // ../cli/src/lib/cloud/avatar.ts
25875
25996
  init_session();
@@ -25917,13 +26038,13 @@ init_config();
25917
26038
  init_home();
25918
26039
  init_config();
25919
26040
  init_session();
25920
- import fs69 from "node:fs";
25921
- import path70 from "node:path";
26041
+ import fs70 from "node:fs";
26042
+ import path72 from "node:path";
25922
26043
  var FRESH_MS = 10 * 6e4;
25923
26044
  var GRACE_MS = 7 * 24 * 60 * 6e4;
25924
- var heldFile = () => path70.join(machineHome(), "pro.json");
26045
+ var heldFile = () => path72.join(machineHome(), "pro.json");
25925
26046
  function forgetPro() {
25926
- fs69.rmSync(heldFile(), { force: true });
26047
+ fs70.rmSync(heldFile(), { force: true });
25927
26048
  }
25928
26049
 
25929
26050
  // ../cli/src/lib/cloud/account.ts
@@ -26100,10 +26221,10 @@ async function cloudImage(opts, program2) {
26100
26221
  const prompt = opts.prompt.trim();
26101
26222
  if (!prompt) usage("Missing the prompt.", line);
26102
26223
  const references = opts.refs.map((file) => {
26103
- const type = REF_TYPES[path71.extname(file).toLowerCase()];
26224
+ const type = REF_TYPES[path73.extname(file).toLowerCase()];
26104
26225
  if (!type) usage(`--ref ${file}: use a PNG, JPEG or WebP file.`, line);
26105
26226
  try {
26106
- return `data:${type};base64,${fs70.readFileSync(file).toString("base64")}`;
26227
+ return `data:${type};base64,${fs71.readFileSync(file).toString("base64")}`;
26107
26228
  } catch (e) {
26108
26229
  usage(`--ref ${file}: ${e instanceof Error ? e.message : String(e)}`, line);
26109
26230
  }
@@ -26209,31 +26330,31 @@ async function generate(g) {
26209
26330
  refuse(g.failed, g.fallback);
26210
26331
  }
26211
26332
  const out = g.outFor?.(res, g.out) ?? g.out;
26212
- fs70.mkdirSync(path71.dirname(path71.resolve(out)), { recursive: true });
26213
- fs70.writeFileSync(out, Buffer.from(await res.arrayBuffer()));
26333
+ fs71.mkdirSync(path73.dirname(path73.resolve(out)), { recursive: true });
26334
+ fs71.writeFileSync(out, Buffer.from(await res.arrayBuffer()));
26214
26335
  say(`${out} (${g.label(res)})`);
26215
26336
  return { out };
26216
26337
  }
26217
26338
 
26218
26339
  // ../cli/src/commands/cloud-import.ts
26219
26340
  import crypto5 from "node:crypto";
26220
- import fs72 from "node:fs";
26221
- import path73 from "node:path";
26341
+ import fs73 from "node:fs";
26342
+ import path75 from "node:path";
26222
26343
 
26223
26344
  // ../cloud/src/board-files.ts
26224
26345
  var ROOTS = ["todo/", ".archive/", "memory/", "workflows/", "triage/", "assets/", "rules/"];
26225
26346
  var SETTINGS = ["modules.md", "config.md"];
26226
- var isSettingsPath = (path85) => SETTINGS.includes(path85) || /^rules\/(?:workflows\/)?[^/]+\.md$/.test(path85);
26347
+ var isSettingsPath = (path87) => SETTINGS.includes(path87) || /^rules\/(?:workflows\/)?[^/]+\.md$/.test(path87);
26227
26348
  var BOARD_FILE_LIMIT = 256 * 1024;
26228
- function isBoardPath(path85) {
26229
- if (!path85 || path85.length > 500 || path85.startsWith("/") || path85.includes("\\") || path85.includes("\0")) return false;
26230
- if (path85.split("/").some((part) => !part || part === "." || part === "..")) return false;
26231
- if (path85 === "todo/README.md" || path85.split("/").includes(".sync")) return false;
26232
- if (path85.startsWith("workflows/")) return true;
26233
- if (SETTINGS.includes(path85)) return true;
26234
- if (path85.startsWith("rules/")) return isSettingsPath(path85);
26235
- if (path85.startsWith("assets/")) return path85.endsWith(".json");
26236
- return ROOTS.some((root) => path85.startsWith(root)) && path85.endsWith(".md");
26349
+ function isBoardPath(path87) {
26350
+ if (!path87 || path87.length > 500 || path87.startsWith("/") || path87.includes("\\") || path87.includes("\0")) return false;
26351
+ if (path87.split("/").some((part) => !part || part === "." || part === "..")) return false;
26352
+ if (path87 === "todo/README.md" || path87.split("/").includes(".sync")) return false;
26353
+ if (path87.startsWith("workflows/")) return true;
26354
+ if (SETTINGS.includes(path87)) return true;
26355
+ if (path87.startsWith("rules/")) return isSettingsPath(path87);
26356
+ if (path87.startsWith("assets/")) return path87.endsWith(".json");
26357
+ return ROOTS.some((root) => path87.startsWith(root)) && path87.endsWith(".md");
26237
26358
  }
26238
26359
  var workflowFolder = (id) => id.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
26239
26360
 
@@ -26337,8 +26458,8 @@ init_bundled();
26337
26458
  // ../cli/src/lib/agents/shared-memory.ts
26338
26459
  init_bundled();
26339
26460
  init_parse();
26340
- import fs71 from "node:fs";
26341
- import path72 from "node:path";
26461
+ import fs72 from "node:fs";
26462
+ import path74 from "node:path";
26342
26463
  function sharedMemory(board2) {
26343
26464
  const declared = /* @__PURE__ */ new Map();
26344
26465
  const take = (folder, text5, builtIn) => {
@@ -26350,16 +26471,16 @@ function sharedMemory(board2) {
26350
26471
  const at = /^([^/]+)\/AGENT\.md$/.exec(key);
26351
26472
  if (at) take(at[1], text5, true);
26352
26473
  }
26353
- const root = path72.join(board2, "agents");
26474
+ const root = path74.join(board2, "agents");
26354
26475
  let folders2 = [];
26355
26476
  try {
26356
- folders2 = fs71.readdirSync(root, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name);
26477
+ folders2 = fs72.readdirSync(root, { withFileTypes: true }).filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name);
26357
26478
  } catch {
26358
26479
  }
26359
26480
  for (const folder of folders2.sort()) {
26360
26481
  let text5 = null;
26361
26482
  try {
26362
- text5 = fs71.readFileSync(path72.join(root, folder, "AGENT.md"), "utf8");
26483
+ text5 = fs72.readFileSync(path74.join(root, folder, "AGENT.md"), "utf8");
26363
26484
  } catch {
26364
26485
  }
26365
26486
  take(folder, text5, false);
@@ -26448,24 +26569,24 @@ async function findProject(asked) {
26448
26569
  }
26449
26570
  throw new Refused(`No project of yours has the id or prefix "${asked}".`, "not_found");
26450
26571
  }
26451
- var read = (file) => fs72.readFileSync(file, "utf8");
26572
+ var read = (file) => fs73.readFileSync(file, "utf8");
26452
26573
  var bytes2 = (text5) => Buffer.byteLength(text5, "utf8");
26453
26574
  function files(dir) {
26454
- if (!fs72.existsSync(dir)) return [];
26575
+ if (!fs73.existsSync(dir)) return [];
26455
26576
  const out = [];
26456
- for (const entry of fs72.readdirSync(dir, { withFileTypes: true })) {
26577
+ for (const entry of fs73.readdirSync(dir, { withFileTypes: true })) {
26457
26578
  if (entry.name.startsWith(".")) continue;
26458
- const full = path73.join(dir, entry.name);
26579
+ const full = path75.join(dir, entry.name);
26459
26580
  if (entry.isDirectory()) out.push(...files(full));
26460
26581
  else if (entry.isFile()) out.push(full);
26461
26582
  }
26462
26583
  return out;
26463
26584
  }
26464
- var posix2 = (p) => p.split(path73.sep).join("/");
26585
+ var posix2 = (p) => p.split(path75.sep).join("/");
26465
26586
  function openCards() {
26466
26587
  const out = [];
26467
26588
  for (const file of walkMd(TODO)) {
26468
- const rel2 = posix2(path73.relative(TODO, file));
26589
+ const rel2 = posix2(path75.relative(TODO, file));
26469
26590
  if (rel2 === "README.md") continue;
26470
26591
  const parts = rel2.split("/");
26471
26592
  const text5 = read(file);
@@ -26499,14 +26620,14 @@ function bodyProblems(text5) {
26499
26620
  return out;
26500
26621
  }
26501
26622
  function packWorkflow(flow) {
26502
- const own = path73.join(WORKFLOWS, flow.id);
26623
+ const own = path75.join(WORKFLOWS, flow.id);
26503
26624
  const out = {};
26504
- for (const file of files(own)) out[posix2(path73.relative(own, file))] = read(file);
26625
+ for (const file of files(own)) out[posix2(path75.relative(own, file))] = read(file);
26505
26626
  const named2 = [...flow.previews, ...flow.land, ...flow.scheduled].map((h) => h.agent).filter((a) => !LAND_ROLES.includes(a));
26506
26627
  for (const agent of new Set(named2)) {
26507
- const dir = path73.join(AGENTS, agent);
26508
- if (fs72.existsSync(path73.join(dir, "AGENT.md"))) {
26509
- for (const file of files(dir)) out[`agents/${agent}/${posix2(path73.relative(dir, file))}`] = read(file);
26628
+ const dir = path75.join(AGENTS, agent);
26629
+ if (fs73.existsSync(path75.join(dir, "AGENT.md"))) {
26630
+ for (const file of files(dir)) out[`agents/${agent}/${posix2(path75.relative(dir, file))}`] = read(file);
26510
26631
  continue;
26511
26632
  }
26512
26633
  for (const [key, text5] of Object.entries(BUNDLED_AGENT_FILES)) {
@@ -26558,8 +26679,8 @@ function draftsOf(text5) {
26558
26679
  const at = /^\.(assets|mockups)\/(.+)$/.exec(name);
26559
26680
  if (!at) continue;
26560
26681
  const base = at[1] === "assets" ? ASSETS : MOCKUPS;
26561
- const file = path73.resolve(base, at[2]);
26562
- if (file.startsWith(base + path73.sep) && fs72.statSync(file, { throwIfNoEntry: false })?.isFile()) out.set(name, file);
26682
+ const file = path75.resolve(base, at[2]);
26683
+ if (file.startsWith(base + path75.sep) && fs73.statSync(file, { throwIfNoEntry: false })?.isFile()) out.set(name, file);
26563
26684
  }
26564
26685
  return [...out].map(([name, file]) => ({ name, file }));
26565
26686
  }
@@ -26593,9 +26714,9 @@ async function uploadDrafts(task, drafts) {
26593
26714
  const assets = {};
26594
26715
  for (const { name, file } of drafts) {
26595
26716
  if (!card.body.includes(name)) continue;
26596
- const ext = path73.extname(file).toLowerCase();
26717
+ const ext = path75.extname(file).toLowerCase();
26597
26718
  const type = TYPES[ext] ?? "application/octet-stream";
26598
- const data = fs72.readFileSync(file);
26719
+ const data = fs73.readFileSync(file);
26599
26720
  const artifact = await api("POST", `/v1/tasks/${task}/artifacts`, { type: kindOf(ext), name: name.slice(0, 200) });
26600
26721
  const upload = await api("POST", `/v1/artifacts/${artifact.id}/uploads`, { content_type: type, size: Math.max(data.length, 1) });
26601
26722
  await api("PUT", `/v1/uploads/${upload.id}`, void 0, { bytes: data, type });
@@ -26638,10 +26759,10 @@ usage: ${program2} cloud import --project <id or prefix> [--dry-run]`, { kind: "
26638
26759
  async function run(asked, dryRun) {
26639
26760
  const project = await findProject(asked);
26640
26761
  const cloudFlows = await all(`/v1/workflows?workspace=${encodeURIComponent(project.workspace)}`);
26641
- const board2 = crypto5.createHash("sha256").update(fs72.realpathSync(KANBAN)).digest("hex").slice(0, 16);
26762
+ const board2 = crypto5.createHash("sha256").update(fs73.realpathSync(KANBAN)).digest("hex").slice(0, 16);
26642
26763
  const cards = openCards();
26643
26764
  const problems = [];
26644
- const where = (file) => posix2(path73.relative(KANBAN, file));
26765
+ const where = (file) => posix2(path75.relative(KANBAN, file));
26645
26766
  const own = workflows().filter((w) => !w.builtIn);
26646
26767
  const packed = /* @__PURE__ */ new Map();
26647
26768
  for (const flow of own) {
@@ -26668,7 +26789,7 @@ async function run(asked, dryRun) {
26668
26789
  const root = `projects/${project.id}/`;
26669
26790
  const boardFiles = [];
26670
26791
  for (const file of [...files(MEMORY), ...files(RULES), CONFIG, MODULES_MD]) {
26671
- if (!fs72.existsSync(file)) continue;
26792
+ if (!fs73.existsSync(file)) continue;
26672
26793
  const rel2 = where(file);
26673
26794
  if (!isBoardPath(rel2)) continue;
26674
26795
  const content = read(file);
@@ -26779,16 +26900,16 @@ async function run(asked, dryRun) {
26779
26900
  }
26780
26901
  function leftLocal(own) {
26781
26902
  const out = [];
26782
- const count4 = (dir) => fs72.existsSync(dir) ? walkMd(dir).length : 0;
26903
+ const count4 = (dir) => fs73.existsSync(dir) ? walkMd(dir).length : 0;
26783
26904
  const archived = count4(ARCHIVE);
26784
26905
  if (archived) out.push(`.archive/ (${archived})`);
26785
26906
  const triage = count4(TRIAGE);
26786
26907
  if (triage) out.push(`triage/ (${triage})`);
26787
- if (fs72.existsSync(DELIVERIES)) out.push("deliveries/");
26788
- if (fs72.existsSync(path73.join(KANBAN, "metrics.csv"))) out.push("metrics.csv");
26908
+ if (fs73.existsSync(DELIVERIES)) out.push("deliveries/");
26909
+ if (fs73.existsSync(path75.join(KANBAN, "metrics.csv"))) out.push("metrics.csv");
26789
26910
  const named2 = new Set(own.flatMap((w) => [...w.previews, ...w.land, ...w.scheduled].map((h) => h.agent)));
26790
- if (fs72.existsSync(AGENTS)) {
26791
- for (const e of fs72.readdirSync(AGENTS, { withFileTypes: true })) {
26911
+ if (fs73.existsSync(AGENTS)) {
26912
+ for (const e of fs73.readdirSync(AGENTS, { withFileTypes: true })) {
26792
26913
  if (e.isDirectory() && !e.name.startsWith(".") && !named2.has(e.name)) out.push(`agents/${e.name}/`);
26793
26914
  }
26794
26915
  }
@@ -26806,8 +26927,8 @@ init_session();
26806
26927
  init_io();
26807
26928
  init_paths();
26808
26929
  import { createHash as createHash4 } from "node:crypto";
26809
- import fs73 from "node:fs";
26810
- import path74 from "node:path";
26930
+ import fs74 from "node:fs";
26931
+ import path76 from "node:path";
26811
26932
  var SLUG = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
26812
26933
  var CAPTURED = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d+)?)?(?:Z|[+-]\d{2}:\d{2})$/;
26813
26934
  var MAX_BYTES3 = 50 * 1024 * 1024;
@@ -26906,10 +27027,10 @@ This template has no preview.`);
26906
27027
  const written = [];
26907
27028
  for (const p of previews) {
26908
27029
  const bytes3 = Buffer.from(await (await ask(`/${at}/previews/${route(p.path)}`, {}, WITHOUT)).arrayBuffer());
26909
- const file = path74.basename(p.path);
26910
- const out = path74.join(opts.to, file.startsWith(`${name}-`) ? file : `${name}-${file}`);
26911
- fs73.mkdirSync(opts.to, { recursive: true });
26912
- fs73.writeFileSync(out, bytes3);
27030
+ const file = path76.basename(p.path);
27031
+ const out = path76.join(opts.to, file.startsWith(`${name}-`) ? file : `${name}-${file}`);
27032
+ fs74.mkdirSync(opts.to, { recursive: true });
27033
+ fs74.writeFileSync(out, bytes3);
26913
27034
  written.push(out);
26914
27035
  say(out);
26915
27036
  }
@@ -26922,15 +27043,15 @@ async function templateGet(name, opts, program2) {
26922
27043
  const { template } = await asJson(await ask(`/${at}`, {}, WITHOUT));
26923
27044
  const fetched = [];
26924
27045
  for (const f of template.files) {
26925
- const out = path74.resolve(to, f.path);
26926
- if (!out.startsWith(path74.resolve(to) + path74.sep)) refuse2(T.corrupt, WITHOUT);
27046
+ const out = path76.resolve(to, f.path);
27047
+ if (!out.startsWith(path76.resolve(to) + path76.sep)) refuse2(T.corrupt, WITHOUT);
26927
27048
  const bytes3 = Buffer.from(await (await ask(`/${at}/files/${route(f.path)}`, {}, WITHOUT)).arrayBuffer());
26928
27049
  if (bytes3.byteLength !== f.size || createHash4("sha256").update(bytes3).digest("hex") !== f.sha256) {
26929
27050
  refuse2(T.corrupt, WITHOUT);
26930
27051
  }
26931
27052
  fetched.push({ out, bytes: bytes3 });
26932
27053
  }
26933
- const taken = fetched.filter((f) => fs73.existsSync(f.out)).map((f) => path74.relative(to, f.out));
27054
+ const taken = fetched.filter((f) => fs74.existsSync(f.out)).map((f) => path76.relative(to, f.out));
26934
27055
  if (taken.length > 0) {
26935
27056
  refuse2({
26936
27057
  zh: `\u76EE\u6807\u6587\u4EF6\u5939\u5DF2\u6709\u540C\u540D\u6587\u4EF6\uFF1A${taken.join("\u3001")}\u3002\u8BF7\u6362\u4E00\u4E2A\u6587\u4EF6\u5939\uFF0C\u6216\u5148\u79FB\u8D70\u8FD9\u4E9B\u6587\u4EF6\u3002`,
@@ -26938,8 +27059,8 @@ async function templateGet(name, opts, program2) {
26938
27059
  });
26939
27060
  }
26940
27061
  for (const f of fetched) {
26941
- fs73.mkdirSync(path74.dirname(f.out), { recursive: true });
26942
- fs73.writeFileSync(f.out, f.bytes);
27062
+ fs74.mkdirSync(path76.dirname(f.out), { recursive: true });
27063
+ fs74.writeFileSync(f.out, f.bytes);
26943
27064
  }
26944
27065
  say(`\u5DF2\u53D6\u56DE ${name}\uFF1A${fetched.length} \u4E2A\u6587\u4EF6\uFF0C\u4F4D\u4E8E ${to}
26945
27066
  Fetched ${name}: ${fetched.length} files into ${to}`);
@@ -26957,25 +27078,25 @@ async function templateSource(name, opts) {
26957
27078
  say(`evidence: ${e.path}`);
26958
27079
  continue;
26959
27080
  }
26960
- const out = path74.join(opts.to, `${name}-${path74.basename(e.path)}`);
26961
- fs73.mkdirSync(opts.to, { recursive: true });
26962
- fs73.writeFileSync(out, Buffer.from(await (await ask(`/${at}/evidence/${route(e.path)}`)).arrayBuffer()));
27081
+ const out = path76.join(opts.to, `${name}-${path76.basename(e.path)}`);
27082
+ fs74.mkdirSync(opts.to, { recursive: true });
27083
+ fs74.writeFileSync(out, Buffer.from(await (await ask(`/${at}/evidence/${route(e.path)}`)).arrayBuffer()));
26963
27084
  written.push(out);
26964
27085
  say(out);
26965
27086
  }
26966
27087
  return { name, source, evidence: written.length > 0 ? written : evidence.map((e) => e.path) };
26967
27088
  }
26968
27089
  async function templatePublish(dir, opts) {
26969
- const root = path74.resolve(dir);
26970
- const manifest = path74.join(root, "template.md");
26971
- if (!fs73.existsSync(manifest)) invalid("\u6587\u4EF6\u5939\u9876\u5C42\u7F3A\u5C11 template.md\u3002", "template.md is missing from the folder\u2019s top level.");
26972
- const meta = readTemplateMeta(fs73.readFileSync(manifest, "utf8"));
27090
+ const root = path76.resolve(dir);
27091
+ const manifest = path76.join(root, "template.md");
27092
+ if (!fs74.existsSync(manifest)) invalid("\u6587\u4EF6\u5939\u9876\u5C42\u7F3A\u5C11 template.md\u3002", "template.md is missing from the folder\u2019s top level.");
27093
+ const meta = readTemplateMeta(fs74.readFileSync(manifest, "utf8"));
26973
27094
  const files2 = walk2(root);
26974
- const total = files2.reduce((sum, f) => sum + fs73.statSync(path74.join(root, f)).size, 0);
27095
+ const total = files2.reduce((sum, f) => sum + fs74.statSync(path76.join(root, f)).size, 0);
26975
27096
  if (total > MAX_BYTES3) invalid("\u6A21\u677F\u6587\u4EF6\u5408\u8BA1\u8D85\u8FC7 50 MB\u3002", "the template\u2019s files exceed 50 MB.");
26976
27097
  for (const extra of [...opts.preview, ...opts.evidence]) {
26977
- if (!fs73.existsSync(extra)) invalid(`\u627E\u4E0D\u5230\u6587\u4EF6 ${extra}\u3002`, `${extra} does not exist.`);
26978
- if (path74.resolve(extra).startsWith(root + path74.sep)) {
27098
+ if (!fs74.existsSync(extra)) invalid(`\u627E\u4E0D\u5230\u6587\u4EF6 ${extra}\u3002`, `${extra} does not exist.`);
27099
+ if (path76.resolve(extra).startsWith(root + path76.sep)) {
26979
27100
  invalid(
26980
27101
  `${extra} \u5728\u6A21\u677F\u6587\u4EF6\u5939\u5185\uFF1B\u9884\u89C8\u56FE\u4E0E\u6765\u6E90\u51ED\u8BC1\u9700\u653E\u5728\u6A21\u677F\u6587\u4EF6\u5939\u4E4B\u5916\u3002`,
26981
27102
  `${extra} is inside the template folder; keep previews and evidence outside it.`
@@ -26987,10 +27108,10 @@ async function templatePublish(dir, opts) {
26987
27108
  }
26988
27109
  const form = new FormData();
26989
27110
  form.set("meta", JSON.stringify({ ...meta, replace: !!opts.replace }));
26990
- const add = (kind, name, file) => form.append(`${kind}:${name}`, new Blob([fs73.readFileSync(file)]), path74.basename(name));
26991
- for (const f of files2) add("files", f, path74.join(root, f));
26992
- for (const f of opts.preview) add("previews", path74.basename(f), f);
26993
- for (const f of opts.evidence) add("evidence", path74.basename(f), f);
27111
+ const add = (kind, name, file) => form.append(`${kind}:${name}`, new Blob([fs74.readFileSync(file)]), path76.basename(name));
27112
+ for (const f of files2) add("files", f, path76.join(root, f));
27113
+ for (const f of opts.preview) add("previews", path76.basename(f), f);
27114
+ for (const f of opts.evidence) add("evidence", path76.basename(f), f);
26994
27115
  const done2 = await asJson(await ask("", { method: "POST", body: form }));
26995
27116
  say(
26996
27117
  done2.replaced ? `\u5DF2\u66FF\u6362 ${done2.name}\uFF1A${files2.length} \u4E2A\u6587\u4EF6\u3002
@@ -27006,11 +27127,11 @@ Deleted ${name} from the catalog.`);
27006
27127
  return { name, deleted: true };
27007
27128
  }
27008
27129
  async function templateShowcase(name, dir) {
27009
- const file = path74.join(dir, "showcase.json");
27010
- if (!fs73.existsSync(file)) invalid(`${dir} \u4E2D\u7F3A\u5C11 showcase.json\u3002`, `showcase.json is missing from ${dir}.`);
27130
+ const file = path76.join(dir, "showcase.json");
27131
+ if (!fs74.existsSync(file)) invalid(`${dir} \u4E2D\u7F3A\u5C11 showcase.json\u3002`, `showcase.json is missing from ${dir}.`);
27011
27132
  let meta;
27012
27133
  try {
27013
- meta = JSON.parse(fs73.readFileSync(file, "utf8"));
27134
+ meta = JSON.parse(fs74.readFileSync(file, "utf8"));
27014
27135
  } catch {
27015
27136
  return invalid("showcase.json \u4E0D\u662F\u6709\u6548\u7684 JSON\u3002", "showcase.json is not valid JSON.");
27016
27137
  }
@@ -27022,9 +27143,9 @@ async function templateShowcase(name, dir) {
27022
27143
  const form = new FormData();
27023
27144
  form.set("meta", JSON.stringify(meta));
27024
27145
  for (const p of new Set(named2)) {
27025
- const at = path74.join(dir, p);
27026
- if (!fs73.existsSync(at)) invalid(`\u627E\u4E0D\u5230\u6587\u4EF6 ${at}\u3002`, `${at} does not exist.`);
27027
- form.append(`media:${p}`, new Blob([fs73.readFileSync(at)]), p);
27146
+ const at = path76.join(dir, p);
27147
+ if (!fs74.existsSync(at)) invalid(`\u627E\u4E0D\u5230\u6587\u4EF6 ${at}\u3002`, `${at} does not exist.`);
27148
+ form.append(`media:${p}`, new Blob([fs74.readFileSync(at)]), p);
27028
27149
  }
27029
27150
  const done2 = await asJson(await ask(`/${encodeURIComponent(name)}/showcase`, { method: "PUT", body: form }));
27030
27151
  say(`\u5DF2\u66F4\u65B0 ${name} \u7684\u5C55\u793A\u8D44\u6599\uFF1A${done2.files} \u4E2A\u6587\u4EF6\u3002
@@ -27033,7 +27154,7 @@ Updated ${name}'s showcase: ${done2.files} files.`);
27033
27154
  }
27034
27155
  function walk2(root, sub = "") {
27035
27156
  const out = [];
27036
- for (const entry of fs73.readdirSync(path74.join(root, sub), { withFileTypes: true })) {
27157
+ for (const entry of fs74.readdirSync(path76.join(root, sub), { withFileTypes: true })) {
27037
27158
  if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
27038
27159
  const rel2 = sub ? `${sub}/${entry.name}` : entry.name;
27039
27160
  if (entry.isDirectory()) out.push(...walk2(root, rel2));
@@ -27318,7 +27439,7 @@ init_opencode();
27318
27439
  init_locate();
27319
27440
  init_store();
27320
27441
  import { spawnSync as spawnSync5 } from "node:child_process";
27321
- import path80 from "node:path";
27442
+ import path82 from "node:path";
27322
27443
  var AGENTS2 = {
27323
27444
  claude: "claude-code",
27324
27445
  codex: "codex",
@@ -27333,10 +27454,10 @@ var AGENTS2 = {
27333
27454
  var RUNTIMES = /* @__PURE__ */ new Set(["node", "bun", "deno"]);
27334
27455
  function agentOf2(args) {
27335
27456
  const words = args.trim().split(/\s+/);
27336
- const first = path80.basename(words[0] ?? "");
27457
+ const first = path82.basename(words[0] ?? "");
27337
27458
  const script = RUNTIMES.has(first) ? words[1] ?? "" : words[0] ?? "";
27338
27459
  if (script.includes("/@anthropic-ai/claude-code/")) return "claude-code";
27339
- return AGENTS2[path80.basename(script).replace(/\.(m?js|cjs|exe)$/, "")];
27460
+ return AGENTS2[path82.basename(script).replace(/\.(m?js|cjs|exe)$/, "")];
27340
27461
  }
27341
27462
  function processChain() {
27342
27463
  if (process.platform === "win32") return [];
@@ -27400,8 +27521,8 @@ init_tasks();
27400
27521
  init_read();
27401
27522
  init_rules();
27402
27523
  init_board();
27403
- import fs80 from "node:fs";
27404
- import path81 from "node:path";
27524
+ import fs81 from "node:fs";
27525
+ import path83 from "node:path";
27405
27526
  var FOLLOW_MS = 400;
27406
27527
  async function cmdStartRun(action, args, opts, program2 = "akb") {
27407
27528
  const { req, follow: follow2, print } = readRequest(action, args, opts);
@@ -27691,7 +27812,7 @@ async function cmdSubStart(words, opts) {
27691
27812
  let prompt = words.join(" ");
27692
27813
  if (opts.file) {
27693
27814
  try {
27694
- prompt = fs80.readFileSync(path81.resolve(opts.file), "utf8");
27815
+ prompt = fs81.readFileSync(path83.resolve(opts.file), "utf8");
27695
27816
  } catch {
27696
27817
  die(`--file: cannot read ${opts.file}`, { kind: "run-refused" });
27697
27818
  }
@@ -27982,7 +28103,7 @@ async function cmdWatch(sessionId) {
27982
28103
  // ../cli/src/commands/triage.ts
27983
28104
  init_scheduled();
27984
28105
  init_workflows();
27985
- import fs82 from "node:fs";
28106
+ import fs83 from "node:fs";
27986
28107
 
27987
28108
  // ../cli/src/lib/signals/index.ts
27988
28109
  init_cards();
@@ -27992,16 +28113,16 @@ init_yaml();
27992
28113
  // ../cli/src/lib/signals/config.ts
27993
28114
  init_settings2();
27994
28115
  init_paths();
27995
- import fs81 from "node:fs";
27996
- import path82 from "node:path";
28116
+ import fs82 from "node:fs";
28117
+ import path84 from "node:path";
27997
28118
  var TOKEN_KEY = "TRIAGE_ENDPOINT_TOKEN";
27998
28119
  var ENDPOINT_SETTING = "Triage endpoint";
27999
28120
  var LINE2 = /^- \*\*Triage endpoint\*\*\s*[—-]\s*(.+)$/m;
28000
- var boardRel3 = (file) => rel(file).split(path82.sep).join("/");
28121
+ var boardRel3 = (file) => rel(file).split(path84.sep).join("/");
28001
28122
  function signalEndpoint() {
28002
28123
  let text5;
28003
28124
  try {
28004
- text5 = fs81.readFileSync(CONFIG, "utf8");
28125
+ text5 = fs82.readFileSync(CONFIG, "utf8");
28005
28126
  } catch {
28006
28127
  return "";
28007
28128
  }
@@ -28177,7 +28298,7 @@ async function cmdTriageAdd(opts) {
28177
28298
  let body = opts.text;
28178
28299
  if (opts.file !== void 0) {
28179
28300
  try {
28180
- body = fs82.readFileSync(opts.file, "utf8");
28301
+ body = fs83.readFileSync(opts.file, "utf8");
28181
28302
  } catch {
28182
28303
  die(`can't read ${opts.file} \u2014 write the item to a file, then pass its path`, { kind: "needs-input" });
28183
28304
  }
@@ -28741,8 +28862,8 @@ function declareAgent(program2, cli) {
28741
28862
  }
28742
28863
 
28743
28864
  // ../cli/src/lib/cli/setup.ts
28744
- import fs84 from "node:fs";
28745
- import path84 from "node:path";
28865
+ import fs85 from "node:fs";
28866
+ import path86 from "node:path";
28746
28867
  import { fileURLToPath as fileURLToPath2 } from "node:url";
28747
28868
 
28748
28869
  // ../cli/src/commands/update.ts
@@ -28756,8 +28877,8 @@ init_paths();
28756
28877
  init_memory();
28757
28878
  init_yaml2();
28758
28879
  init_version();
28759
- import fs83 from "node:fs";
28760
- import path83 from "node:path";
28880
+ import fs84 from "node:fs";
28881
+ import path85 from "node:path";
28761
28882
  import { spawnSync as spawnSync6 } from "node:child_process";
28762
28883
  var NAME = "wildcrane";
28763
28884
  var REPO = "https://github.com/ai4kanban/ai4kanban";
@@ -28779,10 +28900,10 @@ var Report = class {
28779
28900
  for (const line of this.notes) say(` ! ${line}`);
28780
28901
  }
28781
28902
  };
28782
- var read3 = (file) => fs83.existsSync(file) ? fs83.readFileSync(file, "utf8") : null;
28903
+ var read3 = (file) => fs84.existsSync(file) ? fs84.readFileSync(file, "utf8") : null;
28783
28904
  function statOf(p) {
28784
28905
  try {
28785
- return fs83.lstatSync(p);
28906
+ return fs84.lstatSync(p);
28786
28907
  } catch {
28787
28908
  return null;
28788
28909
  }
@@ -28807,10 +28928,10 @@ async function cmdUpdate2(ctx) {
28807
28928
  return { updated: ctx.dir, from, to: SKILL_VERSION };
28808
28929
  }
28809
28930
  async function repairBoard(root, report3, named2) {
28810
- const legacy = path83.join(root, "docs", "kanban");
28811
- const kept2 = path83.join(root, ".akb", "kanban");
28812
- const board2 = named2 ?? (fs83.existsSync(legacy) ? legacy : kept2);
28813
- if (!fs83.existsSync(board2)) {
28931
+ const legacy = path85.join(root, "docs", "kanban");
28932
+ const kept2 = path85.join(root, ".akb", "kanban");
28933
+ const board2 = named2 ?? (fs84.existsSync(legacy) ? legacy : kept2);
28934
+ if (!fs84.existsSync(board2)) {
28814
28935
  report3.notes.push("\u672A\u627E\u5230\u770B\u677F\uFF0C\u8BF7\u901A\u8FC7\u63D2\u4EF6\u8FDE\u63A5\u4E91\u7AEF\u9879\u76EE\u3002");
28815
28936
  return false;
28816
28937
  }
@@ -28841,20 +28962,20 @@ function moveRuntimes(root, board2, report3) {
28841
28962
  }
28842
28963
  }
28843
28964
  function moveLegacyMemory(board2, report3) {
28844
- const memory = path83.join(board2, "memory");
28965
+ const memory = path85.join(board2, "memory");
28845
28966
  const moved = [];
28846
28967
  for (const name of MEMORY_FILES) {
28847
- const from = path83.join(board2, name);
28968
+ const from = path85.join(board2, name);
28848
28969
  const st = statOf(from);
28849
28970
  if (!st || !st.isFile()) continue;
28850
- fs83.mkdirSync(memory, { recursive: true });
28851
- const to = path83.join(memory, name);
28971
+ fs84.mkdirSync(memory, { recursive: true });
28972
+ const to = path85.join(memory, name);
28852
28973
  const there = read3(to);
28853
28974
  if (there === null) {
28854
- fs83.renameSync(from, to);
28975
+ fs84.renameSync(from, to);
28855
28976
  moved.push(name);
28856
28977
  } else if (there === read3(from)) {
28857
- fs83.rmSync(from);
28978
+ fs84.rmSync(from);
28858
28979
  moved.push(name);
28859
28980
  } else {
28860
28981
  report3.notes.push(`both docs/kanban/${name} and docs/kanban/memory/${name} exist and differ \u2014 merge them by hand`);
@@ -28863,23 +28984,23 @@ function moveLegacyMemory(board2, report3) {
28863
28984
  if (moved.length) report3.did.push(`moved the memory set into docs/kanban/memory/: ${moved.join(", ")}`);
28864
28985
  }
28865
28986
  function dropRecordFile(board2) {
28866
- const file = path83.join(board2, "record.csv");
28867
- if (!fs83.existsSync(file)) return null;
28868
- fs83.rmSync(file);
28987
+ const file = path85.join(board2, "record.csv");
28988
+ if (!fs84.existsSync(file)) return null;
28989
+ fs84.rmSync(file);
28869
28990
  return "removed docs/kanban/record.csv (the planning scores it fed are gone)";
28870
28991
  }
28871
28992
  function dropModuleGoals(board2, report3) {
28872
- const memory = path83.join(board2, "memory");
28873
- if (!fs83.existsSync(memory)) return;
28874
- const root = read3(path83.join(memory, "goal.md"));
28993
+ const memory = path85.join(board2, "memory");
28994
+ if (!fs84.existsSync(memory)) return;
28995
+ const root = read3(path85.join(memory, "goal.md"));
28875
28996
  let dropped = 0;
28876
- for (const entry of fs83.readdirSync(memory, { withFileTypes: true })) {
28997
+ for (const entry of fs84.readdirSync(memory, { withFileTypes: true })) {
28877
28998
  if (!entry.isDirectory()) continue;
28878
- const file = path83.join(memory, entry.name, "goal.md");
28999
+ const file = path85.join(memory, entry.name, "goal.md");
28879
29000
  const text5 = read3(file);
28880
29001
  if (text5 === null) continue;
28881
29002
  if (text5 === root || text5.includes(UNFILLED) || !goalWords(text5)) {
28882
- fs83.rmSync(file);
29003
+ fs84.rmSync(file);
28883
29004
  dropped++;
28884
29005
  } else {
28885
29006
  report3.notes.push(
@@ -28901,10 +29022,10 @@ var PLAN_LEADS = {
28901
29022
  var GONE_LEADS = ["software-planner", "planner"];
28902
29023
  function planLeads(board2) {
28903
29024
  const leads = { ...PLAN_LEADS };
28904
- const root = path83.join(board2, "workflows");
28905
- for (const entry of fs83.existsSync(root) ? fs83.readdirSync(root, { withFileTypes: true }) : []) {
29025
+ const root = path85.join(board2, "workflows");
29026
+ for (const entry of fs84.existsSync(root) ? fs84.readdirSync(root, { withFileTypes: true }) : []) {
28906
29027
  if (!entry.isDirectory()) continue;
28907
- const meta = splitFrontmatter(read3(path83.join(root, entry.name, "WORKFLOW.md")) ?? "").meta;
29028
+ const meta = splitFrontmatter(read3(path85.join(root, entry.name, "WORKFLOW.md")) ?? "").meta;
28908
29029
  const stages = meta === null ? void 0 : parseYamlBlock(meta).stages;
28909
29030
  const plan = stages && typeof stages === "object" && !Array.isArray(stages) ? stages.plan : void 0;
28910
29031
  const lead = plan && typeof plan === "object" && !Array.isArray(plan) && typeof plan.lead === "string" ? plan.lead.trim() : "";
@@ -28913,30 +29034,30 @@ function planLeads(board2) {
28913
29034
  return leads;
28914
29035
  }
28915
29036
  function movePlanRules(board2, report3) {
28916
- const rules = path83.join(board2, "rules");
29037
+ const rules = path85.join(board2, "rules");
28917
29038
  const moved = [];
28918
29039
  for (const [id, leads] of Object.entries(planLeads(board2))) {
28919
29040
  for (const lead of leads) {
28920
- const from = path83.join(rules, `${lead}.md`);
29041
+ const from = path85.join(rules, `${lead}.md`);
28921
29042
  const rule = read3(from)?.trim();
28922
29043
  if (!rule) continue;
28923
- const to = path83.join(rules, "workflows", `${id}.md`);
29044
+ const to = path85.join(rules, "workflows", `${id}.md`);
28924
29045
  const there = read3(to)?.trim() ?? "";
28925
29046
  if (!there.includes(rule)) {
28926
- fs83.mkdirSync(path83.dirname(to), { recursive: true });
28927
- fs83.writeFileSync(to, `${[there, rule].filter(Boolean).join("\n\n")}
29047
+ fs84.mkdirSync(path85.dirname(to), { recursive: true });
29048
+ fs84.writeFileSync(to, `${[there, rule].filter(Boolean).join("\n\n")}
28928
29049
  `);
28929
29050
  moved.push(`${lead} \u2192 ${id}`);
28930
29051
  }
28931
29052
  }
28932
29053
  }
28933
- for (const lead of GONE_LEADS) fs83.rmSync(path83.join(rules, `${lead}.md`), { force: true });
29054
+ for (const lead of GONE_LEADS) fs84.rmSync(path85.join(rules, `${lead}.md`), { force: true });
28934
29055
  if (moved.length) report3.did.push(`made each plan agent's rule its workflow's card rule (docs/kanban/rules/workflows/): ${moved.join(", ")}`);
28935
29056
  }
28936
29057
  function noteOldWorkflows(board2, report3) {
28937
29058
  const old = (dir, file, keys) => {
28938
- const root = path83.join(board2, dir);
28939
- return (fs83.existsSync(root) ? fs83.readdirSync(root, { withFileTypes: true }) : []).filter((e) => e.isDirectory() && keys.test(splitFrontmatter(read3(path83.join(root, e.name, file)) ?? "").meta ?? "")).map((e) => `docs/kanban/${dir}/${e.name}/${file}`);
29059
+ const root = path85.join(board2, dir);
29060
+ return (fs84.existsSync(root) ? fs84.readdirSync(root, { withFileTypes: true }) : []).filter((e) => e.isDirectory() && keys.test(splitFrontmatter(read3(path85.join(root, e.name, file)) ?? "").meta ?? "")).map((e) => `docs/kanban/${dir}/${e.name}/${file}`);
28940
29061
  };
28941
29062
  const files2 = [...old("workflows", "WORKFLOW.md", /^(stages|delivers):/m), ...old("agents", "AGENT.md", /^\s+(hook|lead):/m)];
28942
29063
  if (files2.length) {
@@ -28946,7 +29067,7 @@ function noteOldWorkflows(board2, report3) {
28946
29067
  }
28947
29068
  }
28948
29069
  function checkConfig(board2, report3) {
28949
- const current = read3(path83.join(board2, "config.md"));
29070
+ const current = read3(path85.join(board2, "config.md"));
28950
29071
  if (!current) return;
28951
29072
  const added = missingConfigKeys(current);
28952
29073
  if (added.length) {
@@ -28956,7 +29077,7 @@ function checkConfig(board2, report3) {
28956
29077
  }
28957
29078
  }
28958
29079
  function checkModules(board2, report3) {
28959
- const map2 = read3(path83.join(board2, "modules.md"));
29080
+ const map2 = read3(path85.join(board2, "modules.md"));
28960
29081
  if (map2 && map2.includes(UNFILLED)) {
28961
29082
  report3.notes.push(
28962
29083
  "docs/kanban/modules.md is still blank \u2014 write it from the repo (`akb guide module-map`)"
@@ -29010,14 +29131,14 @@ function declareSetup(program2, cli) {
29010
29131
  program2.version(SKILL_VERSION, "-v, --version", "print this version").addHelpText("beforeAll", spelled(cli.program));
29011
29132
  const dirOption = (cmd) => cmd.option("--dir <path>", "the project to work on (default: the current folder)").option("--json", "answer as one JSON object instead of prose");
29012
29133
  const where = (cmd) => {
29013
- const dir = path84.resolve(String(cmd.optsWithGlobals().dir ?? cli.cwd));
29014
- if (!fs84.existsSync(dir)) throw new BoardError(`no such folder: ${dir}`, { kind: "no-such-folder", dir });
29134
+ const dir = path86.resolve(String(cmd.optsWithGlobals().dir ?? cli.cwd));
29135
+ if (!fs85.existsSync(dir)) throw new BoardError(`no such folder: ${dir}`, { kind: "no-such-folder", dir });
29015
29136
  return dir;
29016
29137
  };
29017
29138
  dirOption(program2.command("update")).summary("\u4FEE\u590D\u5DF2\u6709\u770B\u677F\u5E76\u68C0\u67E5 CLI \u65B0\u7248\u672C").description("\u4FEE\u590D\u5DF2\u6709\u770B\u677F\u5E76\u63D0\u793A CLI \u5347\u7EA7\u547D\u4EE4\u3002\u6CA1\u6709\u770B\u677F\u65F6\uFF0C\u8BF7\u901A\u8FC7\u63D2\u4EF6\u8FDE\u63A5\u4E91\u7AEF\u9879\u76EE\u3002").action(async function() {
29018
29139
  const dir = where(this);
29019
29140
  const named2 = this.optsWithGlobals().board ?? process.env.AI4KANBAN_BOARD?.trim();
29020
- const board2 = named2 ? path84.resolve(cli.cwd, named2) : null;
29141
+ const board2 = named2 ? path86.resolve(cli.cwd, named2) : null;
29021
29142
  await runAction(ctxOf(this, cli.program), {}, () => cmdUpdate2({ dir, program: cli.program, board: board2 }));
29022
29143
  });
29023
29144
  program2.command("__rules", { hidden: true }).action(function() {
@@ -29061,7 +29182,7 @@ function invokedDirectly() {
29061
29182
  const entry = process.argv[1];
29062
29183
  if (!entry) return false;
29063
29184
  try {
29064
- return fs85.realpathSync(entry) === fs85.realpathSync(SELF2);
29185
+ return fs86.realpathSync(entry) === fs86.realpathSync(SELF2);
29065
29186
  } catch {
29066
29187
  return false;
29067
29188
  }