brainfile 0.21.0 → 0.21.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/{chunk-FOLBAGJS.mjs → chunk-7KU2R7QC.mjs} +245 -101
  2. package/dist/cli.mjs +324 -207
  3. package/dist/commands/brief.js +1 -1
  4. package/dist/commands/brief.js.map +1 -1
  5. package/dist/commands/log.d.ts.map +1 -1
  6. package/dist/commands/log.js +4 -1
  7. package/dist/commands/log.js.map +1 -1
  8. package/dist/commands/sync.d.ts +3 -1
  9. package/dist/commands/sync.d.ts.map +1 -1
  10. package/dist/commands/sync.js +23 -14
  11. package/dist/commands/sync.js.map +1 -1
  12. package/dist/commands/where.d.ts +4 -1
  13. package/dist/commands/where.d.ts.map +1 -1
  14. package/dist/commands/where.js +9 -3
  15. package/dist/commands/where.js.map +1 -1
  16. package/dist/mcp/helpers.d.ts +3 -2
  17. package/dist/mcp/helpers.d.ts.map +1 -1
  18. package/dist/mcp/helpers.js +5 -6
  19. package/dist/mcp/helpers.js.map +1 -1
  20. package/dist/mcp/tools/brief_tool.js +1 -1
  21. package/dist/mcp/tools/brief_tool.js.map +1 -1
  22. package/dist/{tui-M2KWIG2E.mjs → tui-3Z7ZJV7M.mjs} +1 -1
  23. package/dist/utils/actor.d.ts +22 -0
  24. package/dist/utils/actor.d.ts.map +1 -0
  25. package/dist/utils/actor.js +200 -0
  26. package/dist/utils/actor.js.map +1 -0
  27. package/dist/utils/board-commit-hooks.d.ts.map +1 -1
  28. package/dist/utils/board-commit-hooks.js +3 -2
  29. package/dist/utils/board-commit-hooks.js.map +1 -1
  30. package/dist/utils/board-repo.d.ts +33 -9
  31. package/dist/utils/board-repo.d.ts.map +1 -1
  32. package/dist/utils/board-repo.js +162 -61
  33. package/dist/utils/board-repo.js.map +1 -1
  34. package/dist/utils/board-sync.d.ts +26 -11
  35. package/dist/utils/board-sync.d.ts.map +1 -1
  36. package/dist/utils/board-sync.js +141 -56
  37. package/dist/utils/board-sync.js.map +1 -1
  38. package/package.json +2 -2
package/dist/cli.mjs CHANGED
@@ -18,8 +18,9 @@ import {
18
18
  boardBranch,
19
19
  boardIsDirty,
20
20
  boardRemote,
21
- boardRemoteBranch,
21
+ boardRemoteRef,
22
22
  boardRepoKind,
23
+ boardWebUrl,
23
24
  clearPushLock,
24
25
  columnNotFound,
25
26
  columnNotFoundError,
@@ -74,7 +75,6 @@ import {
74
75
  renderCliError,
75
76
  require_dist,
76
77
  require_source,
77
- resolveAgentName,
78
78
  resolveCliBrainfilePath,
79
79
  saveConfig,
80
80
  secondsSinceSync,
@@ -91,7 +91,7 @@ import {
91
91
  validationError,
92
92
  validationError2,
93
93
  workspaceRootFromBrainfilePath
94
- } from "./chunk-FOLBAGJS.mjs";
94
+ } from "./chunk-7KU2R7QC.mjs";
95
95
  import {
96
96
  __commonJS,
97
97
  __export,
@@ -1263,7 +1263,7 @@ var require_command = __commonJS({
1263
1263
  var EventEmitter = __require("node:events").EventEmitter;
1264
1264
  var childProcess = __require("node:child_process");
1265
1265
  var path27 = __require("node:path");
1266
- var fs24 = __require("node:fs");
1266
+ var fs25 = __require("node:fs");
1267
1267
  var process4 = __require("node:process");
1268
1268
  var { Argument: Argument2, humanReadableArgName } = require_argument();
1269
1269
  var { CommanderError: CommanderError2 } = require_error();
@@ -2257,7 +2257,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2257
2257
  * @param {string} subcommandName
2258
2258
  */
2259
2259
  _checkForMissingExecutable(executableFile, executableDir, subcommandName) {
2260
- if (fs24.existsSync(executableFile)) return;
2260
+ if (fs25.existsSync(executableFile)) return;
2261
2261
  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";
2262
2262
  const executableMissing = `'${executableFile}' does not exist
2263
2263
  - if '${subcommandName}' is not meant to be an executable command, remove description parameter from '.command()' and use '.description()' instead
@@ -2276,10 +2276,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
2276
2276
  const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
2277
2277
  function findFile(baseDir, baseName) {
2278
2278
  const localBin = path27.resolve(baseDir, baseName);
2279
- if (fs24.existsSync(localBin)) return localBin;
2279
+ if (fs25.existsSync(localBin)) return localBin;
2280
2280
  if (sourceExt.includes(path27.extname(baseName))) return void 0;
2281
2281
  const foundExt = sourceExt.find(
2282
- (ext) => fs24.existsSync(`${localBin}${ext}`)
2282
+ (ext) => fs25.existsSync(`${localBin}${ext}`)
2283
2283
  );
2284
2284
  if (foundExt) return `${localBin}${foundExt}`;
2285
2285
  return void 0;
@@ -2291,7 +2291,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
2291
2291
  if (this._scriptPath) {
2292
2292
  let resolvedScriptPath;
2293
2293
  try {
2294
- resolvedScriptPath = fs24.realpathSync(this._scriptPath);
2294
+ resolvedScriptPath = fs25.realpathSync(this._scriptPath);
2295
2295
  } catch {
2296
2296
  resolvedScriptPath = this._scriptPath;
2297
2297
  }
@@ -3530,7 +3530,7 @@ var require_package = __commonJS({
3530
3530
  "package.json"(exports, module) {
3531
3531
  module.exports = {
3532
3532
  name: "brainfile",
3533
- version: "0.21.0",
3533
+ version: "0.21.2",
3534
3534
  description: "Command-line interface for Brainfile task management",
3535
3535
  main: "dist/index.js",
3536
3536
  types: "dist/index.d.ts",
@@ -3567,7 +3567,7 @@ var require_package = __commonJS({
3567
3567
  access: "public"
3568
3568
  },
3569
3569
  dependencies: {
3570
- "@brainfile/core": "^0.21.0",
3570
+ "@brainfile/core": "^0.21.2",
3571
3571
  chalk: "^4.1.2",
3572
3572
  chokidar: "^4.0.3",
3573
3573
  commander: "^14.0.3",
@@ -3626,7 +3626,7 @@ var {
3626
3626
  } = import_index.default;
3627
3627
 
3628
3628
  // src/cli.ts
3629
- import { readFileSync as readFileSync10, existsSync as existsSync19 } from "fs";
3629
+ import { readFileSync as readFileSync11, existsSync as existsSync20 } from "fs";
3630
3630
  import { join as join18, sep as sep3 } from "path";
3631
3631
 
3632
3632
  // src/commands/list.ts
@@ -4139,6 +4139,111 @@ function logAddSuccess(options, taskId, columnTitle, logger, createdChildren = [
4139
4139
  }
4140
4140
  }
4141
4141
 
4142
+ // src/utils/actor.ts
4143
+ import * as fs from "fs";
4144
+ import { spawnSync } from "child_process";
4145
+ var AGENT_BINARIES = {
4146
+ claude: "claude",
4147
+ codex: "codex",
4148
+ "cursor-agent": "cursor",
4149
+ gemini: "gemini",
4150
+ opencode: "opencode",
4151
+ goose: "goose",
4152
+ droid: "droid",
4153
+ crush: "crush",
4154
+ aider: "aider",
4155
+ cline: "cline",
4156
+ amp: "amp",
4157
+ qwen: "qwen",
4158
+ copilot: "copilot"
4159
+ };
4160
+ var MAX_DEPTH = 32;
4161
+ function sanitize(name) {
4162
+ return name.replace(/[[\]<>\r\n]/g, "").trim();
4163
+ }
4164
+ function agentFromCommand(words) {
4165
+ for (const word of words.slice(0, 2)) {
4166
+ const base = word.split("/").pop()?.replace(/\.(c|m)?js$/, "") ?? "";
4167
+ if (AGENT_BINARIES[base]) return AGENT_BINARIES[base];
4168
+ }
4169
+ return null;
4170
+ }
4171
+ function linuxProc(pid) {
4172
+ try {
4173
+ const stat = fs.readFileSync(`/proc/${pid}/stat`, "utf-8");
4174
+ const ppid = Number.parseInt(stat.slice(stat.lastIndexOf(")") + 2).split(" ")[1], 10);
4175
+ const comm = stat.slice(stat.indexOf("(") + 1, stat.lastIndexOf(")"));
4176
+ const cmdline = fs.readFileSync(`/proc/${pid}/cmdline`, "utf-8").split("\0").filter(Boolean);
4177
+ return { ppid, words: [comm, ...cmdline] };
4178
+ } catch {
4179
+ return null;
4180
+ }
4181
+ }
4182
+ function psTable() {
4183
+ const r = spawnSync("ps", ["-A", "-o", "pid=,ppid=,args="], { encoding: "utf-8", timeout: 2e3 });
4184
+ if (r.status !== 0 || !r.stdout) return null;
4185
+ const table = /* @__PURE__ */ new Map();
4186
+ for (const line of r.stdout.split("\n")) {
4187
+ const m = /^\s*(\d+)\s+(\d+)\s+(.*)$/.exec(line);
4188
+ if (m) table.set(Number(m[1]), { ppid: Number(m[2]), words: m[3].split(/\s+/) });
4189
+ }
4190
+ return table;
4191
+ }
4192
+ function agentFromProcessTree(startPid = process.ppid) {
4193
+ const useProc = process.platform === "linux" && fs.existsSync("/proc/self/stat");
4194
+ const table = useProc ? null : process.platform === "win32" ? null : psTable();
4195
+ if (!useProc && !table) return null;
4196
+ let pid = startPid;
4197
+ for (let depth = 0; depth < MAX_DEPTH && pid > 1; depth++) {
4198
+ const info = useProc ? linuxProc(pid) : table?.get(pid) ?? null;
4199
+ if (!info) return null;
4200
+ const agent = agentFromCommand(info.words);
4201
+ if (agent) return agent;
4202
+ pid = info.ppid;
4203
+ }
4204
+ return null;
4205
+ }
4206
+ function agentFromEnvironment(env = process.env) {
4207
+ if (env.CODEX_THREAD_ID || env.CODEX_SESSION_ID) return "codex";
4208
+ if (env.GEMINI_CLI === "1") return "gemini";
4209
+ if (env.CLAUDECODE === "1") return "claude";
4210
+ const generic = env.AI_AGENT?.split("_")[0];
4211
+ if (generic) return generic === "claude-code" ? "claude" : sanitize(generic) || null;
4212
+ return null;
4213
+ }
4214
+ function flagValue(argv) {
4215
+ const at = argv.indexOf("--agent");
4216
+ if (at >= 0 && argv[at + 1] && !argv[at + 1].startsWith("-")) return argv[at + 1];
4217
+ const inline = argv.find((a) => a.startsWith("--agent="));
4218
+ return inline ? inline.slice("--agent=".length) : null;
4219
+ }
4220
+ var detected = null;
4221
+ function detectAgent(argv = process.argv, env = process.env) {
4222
+ const flag = sanitize(flagValue(argv) ?? "");
4223
+ if (flag) return { name: flag, source: "flag" };
4224
+ const named = sanitize(env.BRAINFILE_AGENT ?? "");
4225
+ if (named) return { name: named, source: "env" };
4226
+ if (env.BRAINFILE_DETECT_AGENT === "off") return null;
4227
+ if (!detected) {
4228
+ let value = null;
4229
+ try {
4230
+ value = agentFromProcessTree();
4231
+ } catch {
4232
+ value = null;
4233
+ }
4234
+ detected = { value };
4235
+ }
4236
+ if (detected.value) return { name: detected.value, source: "process" };
4237
+ const fingerprint = agentFromEnvironment(env);
4238
+ return fingerprint ? { name: fingerprint, source: "fingerprint" } : null;
4239
+ }
4240
+ function resolveActor(cwd = process.cwd(), argv = process.argv) {
4241
+ const agent = detectAgent(argv);
4242
+ if (agent) return agent;
4243
+ const user = sanitize(git(["config", "--get", "user.name"], cwd).stdout);
4244
+ return user ? { name: user, source: "git" } : null;
4245
+ }
4246
+
4142
4247
  // src/utils/board-commit-hooks.ts
4143
4248
  var SKIP_COMMANDS = /* @__PURE__ */ new Set(["mcp", "tui", "hooks", "auth", "sync", "merge-driver", "where"]);
4144
4249
  function topLevelCommandName(command) {
@@ -4167,7 +4272,7 @@ function beforeCommand(command) {
4167
4272
  function afterCommand(command) {
4168
4273
  const dotDir = boardDirForCommand(command);
4169
4274
  if (!dotDir) return false;
4170
- return commitBoard(dotDir, { message: describeCommandForCommit(command), agent: resolveAgentName() });
4275
+ return commitBoard(dotDir, { message: describeCommandForCommit(command), agent: detectAgent()?.name });
4171
4276
  }
4172
4277
 
4173
4278
  // src/commands/move.ts
@@ -4254,7 +4359,7 @@ function moveCommandV2(options, filePath, logger) {
4254
4359
 
4255
4360
  // src/commands/template.ts
4256
4361
  var import_core7 = __toESM(require_dist());
4257
- import * as fs from "fs";
4362
+ import * as fs2 from "fs";
4258
4363
  import * as path4 from "path";
4259
4364
  function templateCommand(options, logger = defaultLogger) {
4260
4365
  if (options.list) {
@@ -4282,7 +4387,7 @@ function templateCommand(options, logger = defaultLogger) {
4282
4387
  throw missingRequired("--title", 'brainfile template --use <template-id> --title "Task title"');
4283
4388
  }
4284
4389
  const filePath = resolveCliBrainfilePath(options.file);
4285
- if (!fs.existsSync(filePath)) {
4390
+ if (!fs2.existsSync(filePath)) {
4286
4391
  throw fileNotFound(filePath);
4287
4392
  }
4288
4393
  const board = (0, import_core.readV2BoardConfig)(filePath);
@@ -4358,13 +4463,13 @@ ${availableTemplates}`
4358
4463
  // src/commands/lint.ts
4359
4464
  var import_core8 = __toESM(require_dist());
4360
4465
  var import_chalk4 = __toESM(require_source());
4361
- import * as fs2 from "fs";
4466
+ import * as fs3 from "fs";
4362
4467
  function lintCommand(options, logger = defaultLogger) {
4363
4468
  const filePath = resolveCliBrainfilePath(options.file);
4364
- if (!fs2.existsSync(filePath)) {
4469
+ if (!fs3.existsSync(filePath)) {
4365
4470
  throw fileNotFound(filePath);
4366
4471
  }
4367
- const content = fs2.readFileSync(filePath, "utf-8");
4472
+ const content = fs3.readFileSync(filePath, "utf-8");
4368
4473
  logger.log(import_chalk4.default.blue(`
4369
4474
  \u{1F50D} Linting: ${filePath}
4370
4475
  `));
@@ -4395,7 +4500,7 @@ function lintCommand(options, logger = defaultLogger) {
4395
4500
  let fixedCount = 0;
4396
4501
  let fixed = false;
4397
4502
  if (options.fix && result.fixedContent) {
4398
- fs2.writeFileSync(filePath, result.fixedContent, "utf-8");
4503
+ fs3.writeFileSync(filePath, result.fixedContent, "utf-8");
4399
4504
  fixedCount = fixable.length;
4400
4505
  fixed = true;
4401
4506
  logger.log(import_chalk4.default.green(`\u2713 Fixed ${fixable.length} issue${fixable.length > 1 ? "s" : ""}!
@@ -4430,11 +4535,11 @@ function lintCommand(options, logger = defaultLogger) {
4430
4535
  // src/commands/init.ts
4431
4536
  var import_chalk5 = __toESM(require_source());
4432
4537
  var import_core9 = __toESM(require_dist());
4433
- import * as fs4 from "fs";
4538
+ import * as fs5 from "fs";
4434
4539
  import * as path6 from "path";
4435
4540
 
4436
4541
  // src/utils/dot-brainfile.ts
4437
- import * as fs3 from "fs";
4542
+ import * as fs4 from "fs";
4438
4543
  import * as path5 from "path";
4439
4544
  function getDotBrainfileDir(brainfilePath) {
4440
4545
  const abs = path5.resolve(brainfilePath);
@@ -4445,8 +4550,8 @@ function getDotBrainfileDir(brainfilePath) {
4445
4550
  function removeLegacyStateFile(brainfilePath) {
4446
4551
  const dotDir = getDotBrainfileDir(brainfilePath);
4447
4552
  const statePath = path5.join(dotDir, "state.json");
4448
- if (fs3.existsSync(statePath)) {
4449
- fs3.rmSync(statePath, { force: true });
4553
+ if (fs4.existsSync(statePath)) {
4554
+ fs4.rmSync(statePath, { force: true });
4450
4555
  }
4451
4556
  return statePath;
4452
4557
  }
@@ -4514,7 +4619,7 @@ function planInit(options) {
4514
4619
  }
4515
4620
  function isNonEmptyDir(dir) {
4516
4621
  try {
4517
- return fs4.statSync(dir).isDirectory() && fs4.readdirSync(dir).length > 0;
4622
+ return fs5.statSync(dir).isDirectory() && fs5.readdirSync(dir).length > 0;
4518
4623
  } catch {
4519
4624
  return false;
4520
4625
  }
@@ -4531,11 +4636,11 @@ function initCommand(options) {
4531
4636
  console.log(import_chalk5.default.gray("Run ") + import_chalk5.default.cyan("brainfile migrate") + import_chalk5.default.gray(" before running init."));
4532
4637
  process.exit(1);
4533
4638
  }
4534
- if (probe.format === "v2" && fs4.existsSync(filePath) && !options.force) {
4639
+ if (probe.format === "v2" && fs5.existsSync(filePath) && !options.force) {
4535
4640
  (0, import_core9.ensureDotBrainfileGitignore)(filePath);
4536
4641
  removeLegacyStateFile(filePath);
4537
- fs4.mkdirSync(path6.join(dotDir, "board"), { recursive: true });
4538
- fs4.mkdirSync(path6.join(dotDir, "logs"), { recursive: true });
4642
+ fs5.mkdirSync(path6.join(dotDir, "board"), { recursive: true });
4643
+ fs5.mkdirSync(path6.join(dotDir, "logs"), { recursive: true });
4539
4644
  console.log(import_chalk5.default.green(`A board already exists at ${displayPath(dotDir)}/`));
4540
4645
  if (mode !== "plain" && boardRepoKind(dotDir) === null) {
4541
4646
  console.log(import_chalk5.default.gray(" It is plain files. To keep it on its own branch and share it: ") + import_chalk5.default.cyan("brainfile migrate --to-branch"));
@@ -4543,7 +4648,7 @@ function initCommand(options) {
4543
4648
  console.log(import_chalk5.default.gray(" Where it lives and who has it: ") + import_chalk5.default.cyan("brainfile where"));
4544
4649
  return;
4545
4650
  }
4546
- if (fs4.existsSync(filePath) && !options.force) {
4651
+ if (fs5.existsSync(filePath) && !options.force) {
4547
4652
  console.error(import_chalk5.default.red(`Error: File already exists: ${filePath}`));
4548
4653
  console.log(import_chalk5.default.gray("Use --force to overwrite"));
4549
4654
  process.exit(1);
@@ -4557,12 +4662,12 @@ function initCommand(options) {
4557
4662
  createOrphanBoardWorktree(plan.repoRoot, dotDir, boardBranch(plan.repoRoot));
4558
4663
  }
4559
4664
  (0, import_core9.ensureDotBrainfileGitignore)(filePath);
4560
- fs4.mkdirSync(dotDir, { recursive: true });
4665
+ fs5.mkdirSync(dotDir, { recursive: true });
4561
4666
  const boardDir = path6.join(dotDir, "board");
4562
4667
  const logsDir = path6.join(dotDir, "logs");
4563
- fs4.mkdirSync(boardDir, { recursive: true });
4564
- fs4.mkdirSync(logsDir, { recursive: true });
4565
- fs4.writeFileSync(filePath, DEFAULT_BRAINFILE_V2, "utf-8");
4668
+ fs5.mkdirSync(boardDir, { recursive: true });
4669
+ fs5.mkdirSync(logsDir, { recursive: true });
4670
+ fs5.writeFileSync(filePath, DEFAULT_BRAINFILE_V2, "utf-8");
4566
4671
  removeLegacyStateFile(filePath);
4567
4672
  if (mode === "standalone") {
4568
4673
  initStandaloneBoardRepo(dotDir);
@@ -4614,16 +4719,16 @@ var import_chalk7 = __toESM(require_source());
4614
4719
  var import_core11 = __toESM(require_dist());
4615
4720
  var import_core12 = __toESM(require_dist());
4616
4721
  var import_core13 = __toESM(require_dist());
4617
- import * as fs6 from "fs";
4722
+ import * as fs7 from "fs";
4618
4723
  import * as path8 from "path";
4619
4724
 
4620
4725
  // src/commands/migrate-tracked.ts
4621
4726
  var import_chalk6 = __toESM(require_source());
4622
4727
  var import_core10 = __toESM(require_dist());
4623
4728
  import * as crypto2 from "crypto";
4624
- import * as fs5 from "fs";
4729
+ import * as fs6 from "fs";
4625
4730
  import * as path7 from "path";
4626
- import { spawnSync } from "child_process";
4731
+ import { spawnSync as spawnSync2 } from "child_process";
4627
4732
  var SKIP_TOP_LEVEL = /* @__PURE__ */ new Set([".git"]);
4628
4733
  var STATE_DIR = "state";
4629
4734
  function locateBoard(rootDir) {
@@ -4634,28 +4739,28 @@ function locateBoard(rootDir) {
4634
4739
  return path7.dirname(found.absolutePath);
4635
4740
  }
4636
4741
  function copyBoardContents(from, to) {
4637
- for (const entry of fs5.readdirSync(from)) {
4742
+ for (const entry of fs6.readdirSync(from)) {
4638
4743
  if (SKIP_TOP_LEVEL.has(entry)) continue;
4639
- fs5.cpSync(path7.join(from, entry), path7.join(to, entry), { recursive: true, force: true, verbatimSymlinks: true });
4744
+ fs6.cpSync(path7.join(from, entry), path7.join(to, entry), { recursive: true, force: true, verbatimSymlinks: true });
4640
4745
  }
4641
4746
  }
4642
4747
  function mirrorBoardContents(from, to) {
4643
- for (const entry of fs5.readdirSync(to)) {
4748
+ for (const entry of fs6.readdirSync(to)) {
4644
4749
  if (SKIP_TOP_LEVEL.has(entry)) continue;
4645
- fs5.rmSync(path7.join(to, entry), { recursive: true, force: true });
4750
+ fs6.rmSync(path7.join(to, entry), { recursive: true, force: true });
4646
4751
  }
4647
4752
  copyBoardContents(from, to);
4648
4753
  }
4649
4754
  function hashBoardFiles(dir) {
4650
4755
  const out = /* @__PURE__ */ new Map();
4651
4756
  const walk = (current, prefix) => {
4652
- for (const entry of fs5.readdirSync(current, { withFileTypes: true })) {
4757
+ for (const entry of fs6.readdirSync(current, { withFileTypes: true })) {
4653
4758
  if (prefix === "" && (SKIP_TOP_LEVEL.has(entry.name) || entry.name === STATE_DIR)) continue;
4654
4759
  const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
4655
4760
  const full = path7.join(current, entry.name);
4656
4761
  if (entry.isDirectory()) walk(full, rel);
4657
- else if (entry.isSymbolicLink()) out.set(rel, `link:${fs5.readlinkSync(full)}`);
4658
- else out.set(rel, crypto2.createHash("sha256").update(fs5.readFileSync(full)).digest("hex"));
4762
+ else if (entry.isSymbolicLink()) out.set(rel, `link:${fs6.readlinkSync(full)}`);
4763
+ else out.set(rel, crypto2.createHash("sha256").update(fs6.readFileSync(full)).digest("hex"));
4659
4764
  }
4660
4765
  };
4661
4766
  walk(dir, "");
@@ -4663,12 +4768,12 @@ function hashBoardFiles(dir) {
4663
4768
  }
4664
4769
  function asidePath(dotDir) {
4665
4770
  const candidate = `${dotDir}.migrating`;
4666
- if (fs5.existsSync(candidate)) throw new Error(`${candidate} already exists; remove it before migrating.`);
4771
+ if (fs6.existsSync(candidate)) throw new Error(`${candidate} already exists; remove it before migrating.`);
4667
4772
  return candidate;
4668
4773
  }
4669
4774
  function restoreAside(aside, dotDir) {
4670
- if (fs5.existsSync(dotDir)) fs5.rmSync(dotDir, { recursive: true, force: true });
4671
- fs5.renameSync(aside, dotDir);
4775
+ if (fs6.existsSync(dotDir)) fs6.rmSync(dotDir, { recursive: true, force: true });
4776
+ fs6.renameSync(aside, dotDir);
4672
4777
  }
4673
4778
  function verifyBoardContents(aside, dotDir) {
4674
4779
  const before = hashBoardFiles(aside);
@@ -4692,16 +4797,16 @@ function verifyBoardContents(aside, dotDir) {
4692
4797
  }
4693
4798
  }
4694
4799
  function attributesExtend(aside, dotDir) {
4695
- const lines = (dir) => fs5.readFileSync(path7.join(dir, ".gitattributes"), "utf-8").split("\n").map((l) => l.trim());
4800
+ const lines = (dir) => fs6.readFileSync(path7.join(dir, ".gitattributes"), "utf-8").split("\n").map((l) => l.trim());
4696
4801
  const after = new Set(lines(dotDir));
4697
4802
  return lines(aside).every((line) => after.has(line));
4698
4803
  }
4699
4804
  function stagedPatch(relPath, repoRoot) {
4700
- const r = spawnSync("git", ["diff", "--cached", "--binary", "--", relPath], { cwd: repoRoot, encoding: "utf-8" });
4805
+ const r = spawnSync2("git", ["diff", "--cached", "--binary", "--", relPath], { cwd: repoRoot, encoding: "utf-8" });
4701
4806
  return r.status === 0 ? r.stdout : "";
4702
4807
  }
4703
4808
  function applyToIndex(patch, repoRoot) {
4704
- const r = spawnSync("git", ["apply", "--cached", "--binary", "-"], { cwd: repoRoot, input: patch, encoding: "utf-8" });
4809
+ const r = spawnSync2("git", ["apply", "--cached", "--binary", "-"], { cwd: repoRoot, input: patch, encoding: "utf-8" });
4705
4810
  return r.status === 0;
4706
4811
  }
4707
4812
  var MigrationRollback = class {
@@ -4726,7 +4831,7 @@ var MigrationRollback = class {
4726
4831
  this.indexChanged = true;
4727
4832
  }
4728
4833
  moveAside() {
4729
- fs5.renameSync(this.dotDir, this.aside);
4834
+ fs6.renameSync(this.dotDir, this.aside);
4730
4835
  this.movedAside = true;
4731
4836
  }
4732
4837
  /** Reverse every recorded step. Returns what could not be undone (empty when fully restored). */
@@ -4742,7 +4847,7 @@ var MigrationRollback = class {
4742
4847
  if (this.movedAside && boardRepoKind(this.dotDir) === "linked") {
4743
4848
  attempt("remove the board worktree", () => {
4744
4849
  const removed = git(["worktree", "remove", "--force", this.dotDir], this.repoRoot);
4745
- if (!removed.ok) fs5.rmSync(this.dotDir, { recursive: true, force: true });
4850
+ if (!removed.ok) fs6.rmSync(this.dotDir, { recursive: true, force: true });
4746
4851
  });
4747
4852
  }
4748
4853
  if (this.movedAside) attempt(`move ${this.aside} back`, () => restoreAside(this.aside, this.dotDir));
@@ -4864,10 +4969,10 @@ function migrateToBranch(options = {}) {
4864
4969
  const message = error48 instanceof Error ? error48.message : String(error48);
4865
4970
  const leftovers = rollback.undo();
4866
4971
  throw new Error(
4867
- leftovers.length === 0 ? `${message} Nothing was changed: the board is back where it was.` : `${message} Rolling back was incomplete; could not ${leftovers.join("; ")}. The original board is in ${fs5.existsSync(aside) ? aside : dotDir}.`
4972
+ leftovers.length === 0 ? `${message} Nothing was changed: the board is back where it was.` : `${message} Rolling back was incomplete; could not ${leftovers.join("; ")}. The original board is in ${fs6.existsSync(aside) ? aside : dotDir}.`
4868
4973
  );
4869
4974
  }
4870
- fs5.rmSync(aside, { recursive: true, force: true });
4975
+ fs6.rmSync(aside, { recursive: true, force: true });
4871
4976
  ensureExcludeEntry(repoRoot);
4872
4977
  console.log(import_chalk6.default.green(done));
4873
4978
  if (uncommittedImported) {
@@ -4909,7 +5014,7 @@ function migrateToPlain(options = {}) {
4909
5014
  return;
4910
5015
  }
4911
5016
  if (kind === "standalone") {
4912
- fs5.rmSync(path7.join(dotDir, ".git"), { recursive: true, force: true });
5017
+ fs6.rmSync(path7.join(dotDir, ".git"), { recursive: true, force: true });
4913
5018
  console.log(import_chalk6.default.green("Board is now a plain directory (its git history was removed)."));
4914
5019
  return;
4915
5020
  }
@@ -4917,14 +5022,14 @@ function migrateToPlain(options = {}) {
4917
5022
  if (!repoRoot) throw new Error("Could not find the repository that owns this board worktree.");
4918
5023
  const branch = boardBranch(repoRoot);
4919
5024
  const aside = asidePath(dotDir);
4920
- fs5.mkdirSync(aside);
5025
+ fs6.mkdirSync(aside);
4921
5026
  copyBoardContents(dotDir, aside);
4922
5027
  const remove = git(["worktree", "remove", "--force", dotDir], repoRoot);
4923
5028
  if (!remove.ok) {
4924
- fs5.rmSync(aside, { recursive: true, force: true });
5029
+ fs6.rmSync(aside, { recursive: true, force: true });
4925
5030
  throw new Error(`git worktree remove failed: ${remove.stderr}`);
4926
5031
  }
4927
- fs5.renameSync(aside, dotDir);
5032
+ fs6.renameSync(aside, dotDir);
4928
5033
  removeExcludeEntry(repoRoot);
4929
5034
  console.log(import_chalk6.default.green("Board is now a plain directory."));
4930
5035
  console.log(import_chalk6.default.gray(` Branch '${branch}' still holds its history. Delete it with: git branch -D ${branch}`));
@@ -5008,42 +5113,42 @@ function migrateMixedWorkspace(probe, options) {
5008
5113
  }
5009
5114
  function migrateRootBrainfileToDotDir(probe, options) {
5010
5115
  const { rootBrainfilePath, dotDir, dotBrainfilePath } = probe.paths;
5011
- if (!fs6.existsSync(rootBrainfilePath)) {
5116
+ if (!fs7.existsSync(rootBrainfilePath)) {
5012
5117
  throw new Error(`File not found: ${rootBrainfilePath}`);
5013
5118
  }
5014
- if (fs6.existsSync(dotBrainfilePath) && !options.force) {
5119
+ if (fs7.existsSync(dotBrainfilePath) && !options.force) {
5015
5120
  throw new Error(
5016
5121
  `Target already exists: ${dotBrainfilePath}. Run \`brainfile migrate\` again after resolving conflicts, or use --force.`
5017
5122
  );
5018
5123
  }
5019
- fs6.mkdirSync(dotDir, { recursive: true });
5020
- if (fs6.existsSync(dotBrainfilePath) && options.force) {
5021
- fs6.rmSync(dotBrainfilePath, { force: true });
5124
+ fs7.mkdirSync(dotDir, { recursive: true });
5125
+ if (fs7.existsSync(dotBrainfilePath) && options.force) {
5126
+ fs7.rmSync(dotBrainfilePath, { force: true });
5022
5127
  }
5023
5128
  try {
5024
- fs6.renameSync(rootBrainfilePath, dotBrainfilePath);
5129
+ fs7.renameSync(rootBrainfilePath, dotBrainfilePath);
5025
5130
  } catch {
5026
- const contents = fs6.readFileSync(rootBrainfilePath);
5027
- fs6.writeFileSync(dotBrainfilePath, contents);
5028
- fs6.rmSync(rootBrainfilePath, { force: true });
5131
+ const contents = fs7.readFileSync(rootBrainfilePath);
5132
+ fs7.writeFileSync(dotBrainfilePath, contents);
5133
+ fs7.rmSync(rootBrainfilePath, { force: true });
5029
5134
  }
5030
5135
  (0, import_core13.ensureDotBrainfileGitignore)(dotBrainfilePath);
5031
5136
  removeLegacyStateFile(dotBrainfilePath);
5032
5137
  console.log(import_chalk7.default.gray(`Moved: ${rootBrainfilePath} -> ${dotBrainfilePath}`));
5033
5138
  }
5034
5139
  function migrateBrainfileToV2(brainfilePath, options) {
5035
- if (!fs6.existsSync(brainfilePath)) {
5140
+ if (!fs7.existsSync(brainfilePath)) {
5036
5141
  throw new Error(`File not found: ${brainfilePath}`);
5037
5142
  }
5038
- const content = fs6.readFileSync(brainfilePath, "utf-8");
5143
+ const content = fs7.readFileSync(brainfilePath, "utf-8");
5039
5144
  const parsed = import_core11.Brainfile.parseWithErrors(content);
5040
5145
  if (!parsed.board) {
5041
5146
  throw new Error(`Failed to parse brainfile: ${parsed.error}`);
5042
5147
  }
5043
5148
  const board = parsed.board;
5044
5149
  const dotDir = path8.dirname(path8.resolve(brainfilePath));
5045
- const hasBoardDir = fs6.existsSync(path8.join(dotDir, "board"));
5046
- const hasLogsDir = fs6.existsSync(path8.join(dotDir, "logs"));
5150
+ const hasBoardDir = fs7.existsSync(path8.join(dotDir, "board"));
5151
+ const hasLogsDir = fs7.existsSync(path8.join(dotDir, "logs"));
5047
5152
  const hasEmbeddedColumnTasks = board.columns.some((column) => column.tasks.length > 0);
5048
5153
  const hasEmbeddedArchiveTasks = Array.isArray(board.archive) && (board.archive?.length ?? 0) > 0;
5049
5154
  if (hasBoardDir && hasLogsDir && !hasEmbeddedColumnTasks && !hasEmbeddedArchiveTasks) {
@@ -5051,15 +5156,15 @@ function migrateBrainfileToV2(brainfilePath, options) {
5051
5156
  return;
5052
5157
  }
5053
5158
  const backupPath = brainfilePath + ".v1.bak";
5054
- if (!fs6.existsSync(backupPath) || options.force) {
5055
- fs6.copyFileSync(brainfilePath, backupPath);
5159
+ if (!fs7.existsSync(backupPath) || options.force) {
5160
+ fs7.copyFileSync(brainfilePath, backupPath);
5056
5161
  console.log(import_chalk7.default.gray(`Backup: ${backupPath}`));
5057
5162
  } else {
5058
5163
  console.log(import_chalk7.default.gray(`Backup already exists: ${backupPath}`));
5059
5164
  }
5060
5165
  const dirs = (0, import_core.ensureV2Dirs)(brainfilePath);
5061
- const existingTaskFiles = fs6.existsSync(dirs.boardDir) ? fs6.readdirSync(dirs.boardDir).filter((f) => f.endsWith(".md")) : [];
5062
- const existingLogFiles = fs6.existsSync(dirs.logsDir) ? fs6.readdirSync(dirs.logsDir).filter((f) => f.endsWith(".md")) : [];
5166
+ const existingTaskFiles = fs7.existsSync(dirs.boardDir) ? fs7.readdirSync(dirs.boardDir).filter((f) => f.endsWith(".md")) : [];
5167
+ const existingLogFiles = fs7.existsSync(dirs.logsDir) ? fs7.readdirSync(dirs.logsDir).filter((f) => f.endsWith(".md")) : [];
5063
5168
  if ((existingTaskFiles.length > 0 || existingLogFiles.length > 0) && !options.force) {
5064
5169
  throw new Error(
5065
5170
  "board/ or logs/ already contains task files. Use --force to replace existing .md files in those directories."
@@ -5067,10 +5172,10 @@ function migrateBrainfileToV2(brainfilePath, options) {
5067
5172
  }
5068
5173
  if (options.force) {
5069
5174
  for (const name of existingTaskFiles) {
5070
- fs6.rmSync(path8.join(dirs.boardDir, name), { force: true });
5175
+ fs7.rmSync(path8.join(dirs.boardDir, name), { force: true });
5071
5176
  }
5072
5177
  for (const name of existingLogFiles) {
5073
- fs6.rmSync(path8.join(dirs.logsDir, name), { force: true });
5178
+ fs7.rmSync(path8.join(dirs.logsDir, name), { force: true });
5074
5179
  }
5075
5180
  }
5076
5181
  let activeCount = 0;
@@ -5131,7 +5236,7 @@ function migrateBrainfileToV2(brainfilePath, options) {
5131
5236
  delete configBoard.rules;
5132
5237
  configBoard.schema = "https://brainfile.md/v2/board.json";
5133
5238
  const configContent = import_core11.Brainfile.serialize(configBoard);
5134
- fs6.writeFileSync(brainfilePath, configContent, "utf-8");
5239
+ fs7.writeFileSync(brainfilePath, configContent, "utf-8");
5135
5240
  (0, import_core13.ensureDotBrainfileGitignore)(brainfilePath);
5136
5241
  removeLegacyStateFile(brainfilePath);
5137
5242
  console.log(import_chalk7.default.green("Migration to v2 complete!"));
@@ -5154,7 +5259,7 @@ function migrateLogsToLedger(options) {
5154
5259
  return;
5155
5260
  }
5156
5261
  const { logsDir, boardDir } = probe.paths;
5157
- if (!fs6.existsSync(logsDir)) {
5262
+ if (!fs7.existsSync(logsDir)) {
5158
5263
  console.log(import_chalk7.default.gray("No logs/ directory found. Nothing to migrate."));
5159
5264
  return;
5160
5265
  }
@@ -5165,8 +5270,8 @@ function migrateLogsToLedger(options) {
5165
5270
  }
5166
5271
  const ledgerPath = path8.join(logsDir, "ledger.jsonl");
5167
5272
  const existingLedgerIds = /* @__PURE__ */ new Set();
5168
- if (fs6.existsSync(ledgerPath)) {
5169
- const lines = fs6.readFileSync(ledgerPath, "utf-8").split("\n");
5273
+ if (fs7.existsSync(ledgerPath)) {
5274
+ const lines = fs7.readFileSync(ledgerPath, "utf-8").split("\n");
5170
5275
  for (const line of lines) {
5171
5276
  const trimmed = line.trim();
5172
5277
  if (!trimmed) continue;
@@ -5177,7 +5282,7 @@ function migrateLogsToLedger(options) {
5177
5282
  }
5178
5283
  }
5179
5284
  }
5180
- const boardDocs = fs6.existsSync(boardDir) ? (0, import_core12.readTasksDir)(boardDir) : [];
5285
+ const boardDocs = fs7.existsSync(boardDir) ? (0, import_core12.readTasksDir)(boardDir) : [];
5181
5286
  const boardIdMap = new Map(boardDocs.map((d) => [d.task.id, d]));
5182
5287
  let migratedCount = 0;
5183
5288
  let skippedCount = 0;
@@ -5202,7 +5307,7 @@ function migrateLogsToLedger(options) {
5202
5307
  const renamedTask = { ...boardDoc.task, id: newId };
5203
5308
  const newBoardPath = path8.join(boardDir, (0, import_core12.taskFileName)(newId));
5204
5309
  (0, import_core12.writeTaskFile)(newBoardPath, renamedTask, boardDoc.body);
5205
- fs6.rmSync(boardDoc.filePath, { force: true });
5310
+ fs7.rmSync(boardDoc.filePath, { force: true });
5206
5311
  console.log(import_chalk7.default.yellow(` Conflict: board/${taskId} \u2192 ${newId} (completed task takes precedence)`));
5207
5312
  conflictCount++;
5208
5313
  }
@@ -5220,19 +5325,19 @@ function migrateLogsToLedger(options) {
5220
5325
  }
5221
5326
  function backupAndRemoveLegacyRoot(rootBrainfilePath, dotDir) {
5222
5327
  const backupPath = uniquePath(path8.join(dotDir, "brainfile.root.legacy.bak"));
5223
- fs6.mkdirSync(dotDir, { recursive: true });
5224
- fs6.copyFileSync(rootBrainfilePath, backupPath);
5225
- fs6.rmSync(rootBrainfilePath, { force: true });
5328
+ fs7.mkdirSync(dotDir, { recursive: true });
5329
+ fs7.copyFileSync(rootBrainfilePath, backupPath);
5330
+ fs7.rmSync(rootBrainfilePath, { force: true });
5226
5331
  return backupPath;
5227
5332
  }
5228
5333
  function uniquePath(basePath) {
5229
- if (!fs6.existsSync(basePath)) return basePath;
5334
+ if (!fs7.existsSync(basePath)) return basePath;
5230
5335
  const ext = path8.extname(basePath);
5231
5336
  const stem = basePath.slice(0, basePath.length - ext.length);
5232
5337
  let i = 1;
5233
5338
  while (true) {
5234
5339
  const candidate = `${stem}.${i}${ext}`;
5235
- if (!fs6.existsSync(candidate)) return candidate;
5340
+ if (!fs7.existsSync(candidate)) return candidate;
5236
5341
  i++;
5237
5342
  }
5238
5343
  }
@@ -5240,21 +5345,27 @@ function uniquePath(basePath) {
5240
5345
  // src/commands/sync.ts
5241
5346
  var import_chalk8 = __toESM(require_source());
5242
5347
  var import_core14 = __toESM(require_dist());
5243
- import * as fs7 from "fs";
5348
+ import * as fs8 from "fs";
5244
5349
  import * as path9 from "path";
5245
5350
  var SYNC_COMMAND_HELP = `
5246
5351
  Examples:
5247
- brainfile sync Fetch, merge and push the board branch
5352
+ brainfile sync Fetch, merge and push the board
5248
5353
  brainfile sync --pull Bring in other machines' changes only
5249
5354
  brainfile sync --set-remote origin Share the board through the code remote
5250
- brainfile sync --set-remote git@host:me/boards.git --remote-branch myproject
5355
+ brainfile sync --set-remote git@host:me/boards.git --board-name myproject
5251
5356
  brainfile sync --autosync full Also fetch before reads (default: push after writes)
5252
5357
 
5253
- Nothing leaves this machine until you choose a remote. \`brainfile where\` shows
5254
- where the board lives and who has it.`;
5358
+ Nothing leaves this machine until you choose a remote. On the remote the board
5359
+ is stored as refs/brainfile/<name>, not as a branch, so it never appears in the
5360
+ branch list or pull requests. \`brainfile where\` shows where the board lives
5361
+ and who has it.`;
5255
5362
  function describe(result) {
5256
5363
  const message = syncMessage(result);
5257
- const text = message.tone === "ok" ? import_chalk8.default.green(message.text) : import_chalk8.default.yellow(message.text);
5364
+ let text = message.tone === "ok" ? import_chalk8.default.green(message.text) : import_chalk8.default.yellow(message.text);
5365
+ if (result.movedFromBranch && result.remoteRef) {
5366
+ text += `
5367
+ ${import_chalk8.default.gray(` Moved the board off the '${result.movedFromBranch}' branch on ${result.remote}; it now lives in ${result.remoteRef}, outside your branches.`)}`;
5368
+ }
5258
5369
  return message.detail ? `${text}
5259
5370
  ${import_chalk8.default.gray(` (${message.detail})`)}` : text;
5260
5371
  }
@@ -5266,6 +5377,7 @@ function syncCommand(options, logger = defaultLogger) {
5266
5377
  if (Number.isFinite(wait) && wait > 0) sleep(wait);
5267
5378
  let dotDir = tryResolveBoardDir(options.file);
5268
5379
  if (dotDir && options.wait !== void 0) clearPushLock(dotDir);
5380
+ const boardName = options.boardName ?? options.remoteBranch;
5269
5381
  if (options.setRemote !== void 0) {
5270
5382
  const cwd = dotDir ?? gitToplevel(process.cwd());
5271
5383
  if (!cwd) {
@@ -5273,25 +5385,28 @@ function syncCommand(options, logger = defaultLogger) {
5273
5385
  }
5274
5386
  let setting;
5275
5387
  try {
5276
- setting = setBoardRemote(cwd, options.setRemote, options.remoteBranch);
5388
+ setting = setBoardRemote(cwd, options.setRemote, boardName);
5277
5389
  } catch (error48) {
5278
5390
  throw validationError2(error48 instanceof Error ? error48.message : String(error48));
5279
5391
  }
5280
- logger.log(import_chalk8.default.green(`This board is now shared through ${setting.remote}${setting.url ? ` (${setting.url})` : ""}, branch '${setting.remoteBranch}'.`));
5392
+ logger.log(import_chalk8.default.green(`This board is now shared through ${setting.remote}${setting.url ? ` (${setting.url})` : ""}.`));
5393
+ logger.log(import_chalk8.default.gray(` It is stored there as ${setting.remoteRef}, not a branch, so it stays out of branch lists and pull requests.`));
5281
5394
  logger.log(import_chalk8.default.gray(` Changes are sent automatically after each edit. Anyone who can read ${setting.remote} can read the board.`));
5395
+ const web = boardWebUrl(setting.url, setting.remoteRef);
5396
+ if (web) logger.log(import_chalk8.default.gray(` See it on the web: ${web}`));
5282
5397
  if (!dotDir) {
5283
- dotDir = materializeBoardWorktree(cwd);
5398
+ dotDir = materializeBoardWorktree(cwd, { fresh: true });
5284
5399
  if (!dotDir) {
5285
- logger.log(import_chalk8.default.yellow(`There is no board on ${setting.remote} yet (looked for branch '${setting.remoteBranch}').`) + import_chalk8.default.gray(" Start one with: brainfile init"));
5400
+ logger.log(import_chalk8.default.yellow(`There is no board on ${setting.remote} yet (looked for ${setting.remoteRef}).`) + import_chalk8.default.gray(" Start one with: brainfile init"));
5286
5401
  return void 0;
5287
5402
  }
5288
5403
  }
5289
- } else if (options.remoteBranch !== void 0) {
5290
- throw validationError2("--remote-branch needs --set-remote");
5404
+ } else if (boardName !== void 0) {
5405
+ throw validationError2("--board-name needs --set-remote");
5291
5406
  }
5292
5407
  if (!dotDir) {
5293
5408
  const resolved = resolveCliBrainfilePath(options.file);
5294
- if (!fs7.existsSync(resolved)) throw operationFailed(`No board found (looked for ${path9.join(".brainfile", import_core14.BRAINFILE_BASENAME)}). Run: brainfile init`);
5409
+ if (!fs8.existsSync(resolved)) throw operationFailed(`No board found (looked for ${path9.join(".brainfile", import_core14.BRAINFILE_BASENAME)}). Run: brainfile init`);
5295
5410
  dotDir = path9.dirname(resolved);
5296
5411
  }
5297
5412
  if (options.autosync !== void 0) {
@@ -5305,14 +5420,13 @@ function syncCommand(options, logger = defaultLogger) {
5305
5420
  const pushOnly = options.push === true && options.pull !== true;
5306
5421
  const result = syncBoard(dotDir, {
5307
5422
  pull: !pushOnly,
5308
- push: !pullOnly,
5309
- agent: resolveAgentName()
5423
+ push: !pullOnly
5310
5424
  });
5311
5425
  if (options.json) {
5312
5426
  logger.log(JSON.stringify({
5313
5427
  ...result,
5314
5428
  remote: result.remote ?? boardRemote(dotDir),
5315
- remoteBranch: result.remoteBranch ?? boardRemoteBranch(dotDir),
5429
+ remoteRef: result.remoteRef ?? boardRemoteRef(dotDir),
5316
5430
  autosync: boardAutosync(dotDir)
5317
5431
  }, null, 2));
5318
5432
  } else {
@@ -5324,7 +5438,7 @@ function syncCommand(options, logger = defaultLogger) {
5324
5438
 
5325
5439
  // src/commands/where.ts
5326
5440
  var import_chalk9 = __toESM(require_source());
5327
- import * as fs8 from "fs";
5441
+ import * as fs9 from "fs";
5328
5442
  import * as path10 from "path";
5329
5443
  function ago(seconds) {
5330
5444
  if (seconds < 60) return `${seconds}s ago`;
@@ -5337,14 +5451,17 @@ function whereReport(dotDir) {
5337
5451
  const storage = kind === "linked" ? "branch" : kind === "standalone" ? "repository" : "plain";
5338
5452
  const remote = kind ? boardRemote(dotDir) : null;
5339
5453
  const state = remote ? readSyncState(dotDir) : null;
5454
+ const remoteUrl = remote ? git(["remote", "get-url", remote], dotDir).stdout || null : null;
5455
+ const remoteRef = remote ? boardRemoteRef(dotDir) : null;
5340
5456
  return {
5341
5457
  path: dotDir,
5342
5458
  storage,
5343
5459
  branch: kind === "linked" ? boardBranch(dotDir) : null,
5344
5460
  repository: kind === "linked" ? gitToplevel(path10.dirname(dotDir)) : null,
5345
5461
  remote,
5346
- remoteUrl: remote ? git(["remote", "get-url", remote], dotDir).stdout || null : null,
5347
- remoteBranch: remote ? boardRemoteBranch(dotDir) : null,
5462
+ remoteUrl,
5463
+ remoteRef,
5464
+ webUrl: remoteRef ? boardWebUrl(remoteUrl, remoteRef) : null,
5348
5465
  autosync: remote ? effectiveAutosync(dotDir) : null,
5349
5466
  lastSyncSecondsAgo: remote ? secondsSinceSync(dotDir) : null,
5350
5467
  lastSyncOk: state ? state.ok : null,
@@ -5353,7 +5470,7 @@ function whereReport(dotDir) {
5353
5470
  }
5354
5471
  function whereCommand(options, logger = defaultLogger) {
5355
5472
  const filePath = resolveCliBrainfilePath(options.file);
5356
- if (!fs8.existsSync(filePath)) throw fileNotFound(filePath);
5473
+ if (!fs9.existsSync(filePath)) throw fileNotFound(filePath);
5357
5474
  const dotDir = path10.dirname(filePath);
5358
5475
  const report = whereReport(dotDir);
5359
5476
  if (options.json) {
@@ -5379,7 +5496,9 @@ function whereCommand(options, logger = defaultLogger) {
5379
5496
  return report;
5380
5497
  }
5381
5498
  const url2 = report.remoteUrl ? ` (${report.remoteUrl})` : "";
5382
- logger.log(`${label("Shared")} through ${report.remote}${url2}, branch '${report.remoteBranch}'`);
5499
+ logger.log(`${label("Shared")} through ${report.remote}${url2}`);
5500
+ more(import_chalk9.default.gray(`Stored there as ${report.remoteRef}, not a branch, so it stays out of branch lists and pull requests.`));
5501
+ if (report.webUrl) more(import_chalk9.default.gray(`On the web: ${report.webUrl}`));
5383
5502
  const last = report.lastSyncSecondsAgo === null ? "" : report.lastSyncOk === false ? import_chalk9.default.yellow(`Last sync failed ${ago(report.lastSyncSecondsAgo)}; your changes are safe on this machine.`) : `Last synced ${ago(report.lastSyncSecondsAgo)}.`;
5384
5503
  const waiting = report.pendingChanges > 0 ? `${report.pendingChanges} change${report.pendingChanges === 1 ? "" : "s"} waiting to be sent.` : "Nothing waiting to be sent.";
5385
5504
  more([last, waiting].filter(Boolean).join(" "));
@@ -5500,7 +5619,7 @@ function patchCommand(options) {
5500
5619
 
5501
5620
  // src/commands/delete.ts
5502
5621
  var import_chalk11 = __toESM(require_source());
5503
- import * as fs9 from "fs";
5622
+ import * as fs10 from "fs";
5504
5623
  import * as path12 from "path";
5505
5624
  function isUnsafeTaskId2(taskId) {
5506
5625
  const trimmed = taskId.trim();
@@ -5540,7 +5659,7 @@ function deleteCommand(options) {
5540
5659
  console.log(import_chalk11.default.gray('Use --force to confirm deletion, or use "brainfile archive" to archive instead.'));
5541
5660
  process.exit(0);
5542
5661
  }
5543
- fs9.unlinkSync(taskPath);
5662
+ fs10.unlinkSync(taskPath);
5544
5663
  console.log(import_chalk11.default.green("Task deleted successfully!"));
5545
5664
  console.log("");
5546
5665
  console.log(import_chalk11.default.gray(` Task: ${task.id} - ${task.title}`));
@@ -5553,7 +5672,7 @@ function deleteCommand(options) {
5553
5672
  // src/commands/archive.ts
5554
5673
  var import_core16 = __toESM(require_dist());
5555
5674
  var import_chalk12 = __toESM(require_source());
5556
- import * as fs10 from "fs";
5675
+ import * as fs11 from "fs";
5557
5676
  import * as path13 from "path";
5558
5677
  async function archiveCommand(options) {
5559
5678
  try {
@@ -5636,7 +5755,7 @@ function completeLocalV2(filePath, taskId, dryRun) {
5636
5755
  async function archiveSingleFromLogsV2(filePath, board, taskId, destination, dryRun, parsedDest) {
5637
5756
  const dirs = (0, import_core.getV2Dirs)(filePath);
5638
5757
  const logPath = path13.join(dirs.logsDir, (0, import_core16.taskFileName)(taskId));
5639
- if (!fs10.existsSync(logPath)) {
5758
+ if (!fs11.existsSync(logPath)) {
5640
5759
  operationError(`Task not found in logs: ${taskId}`);
5641
5760
  return;
5642
5761
  }
@@ -5654,10 +5773,10 @@ async function archiveSingleFromLogsV2(filePath, board, taskId, destination, dry
5654
5773
  }
5655
5774
  if (destination === "github") {
5656
5775
  await archiveToGitHub(board, task, "Completed", dryRun, parsedDest);
5657
- fs10.unlinkSync(logPath);
5776
+ fs11.unlinkSync(logPath);
5658
5777
  } else if (destination === "linear") {
5659
5778
  await archiveToLinear(board, task, "Completed", dryRun, parsedDest);
5660
- fs10.unlinkSync(logPath);
5779
+ fs11.unlinkSync(logPath);
5661
5780
  }
5662
5781
  }
5663
5782
  async function archiveAllFromLogsV2(filePath, board, destination, dryRun) {
@@ -5707,7 +5826,7 @@ async function archiveAllFromLogsV2(filePath, board, destination, dryRun) {
5707
5826
  });
5708
5827
  if (result.success) {
5709
5828
  const logPath = path13.join(dirs.logsDir, (0, import_core16.taskFileName)(task.id));
5710
- fs10.unlinkSync(logPath);
5829
+ fs11.unlinkSync(logPath);
5711
5830
  console.log(import_chalk12.default.green(" OK") + ` #${result.issueNumber}`);
5712
5831
  successCount++;
5713
5832
  } else {
@@ -5736,7 +5855,7 @@ async function archiveAllFromLogsV2(filePath, board, destination, dryRun) {
5736
5855
  });
5737
5856
  if (result.success) {
5738
5857
  const logPath = path13.join(dirs.logsDir, (0, import_core16.taskFileName)(task.id));
5739
- fs10.unlinkSync(logPath);
5858
+ fs11.unlinkSync(logPath);
5740
5859
  console.log(import_chalk12.default.green(" OK") + ` ${result.issueId}`);
5741
5860
  successCount++;
5742
5861
  } else {
@@ -5898,12 +6017,12 @@ async function archiveToLinear(board, task, columnTitle, dryRun, parsedDest) {
5898
6017
  // src/commands/restore.ts
5899
6018
  var import_core18 = __toESM(require_dist());
5900
6019
  var import_chalk13 = __toESM(require_source());
5901
- import * as fs12 from "fs";
6020
+ import * as fs13 from "fs";
5902
6021
  import * as path15 from "path";
5903
6022
 
5904
6023
  // src/utils/archive.ts
5905
6024
  var import_core17 = __toESM(require_dist());
5906
- import * as fs11 from "fs";
6025
+ import * as fs12 from "fs";
5907
6026
  import * as path14 from "path";
5908
6027
  function getArchivePath(filePath) {
5909
6028
  const dir = path14.dirname(filePath);
@@ -5913,11 +6032,11 @@ function getArchivePath(filePath) {
5913
6032
  }
5914
6033
  function loadArchivedTasks(filePath) {
5915
6034
  const archivePath = getArchivePath(filePath);
5916
- if (!fs11.existsSync(archivePath)) {
6035
+ if (!fs12.existsSync(archivePath)) {
5917
6036
  return { tasks: [], archivePath };
5918
6037
  }
5919
6038
  try {
5920
- const archiveContent = fs11.readFileSync(archivePath, "utf-8");
6039
+ const archiveContent = fs12.readFileSync(archivePath, "utf-8");
5921
6040
  const parseResult = import_core17.Brainfile.parseWithErrors(archiveContent);
5922
6041
  if (parseResult.board && Array.isArray(parseResult.board.archive)) {
5923
6042
  return { tasks: parseResult.board.archive, archivePath };
@@ -5933,12 +6052,12 @@ function loadArchivedTasks(filePath) {
5933
6052
  }
5934
6053
  function restoreFromArchive(filePath, taskId, toColumnId) {
5935
6054
  const archivePath = getArchivePath(filePath);
5936
- if (!fs11.existsSync(archivePath)) {
6055
+ if (!fs12.existsSync(archivePath)) {
5937
6056
  return { success: false, error: "Archive file not found" };
5938
6057
  }
5939
6058
  let archiveBoard;
5940
6059
  try {
5941
- const archiveContent = fs11.readFileSync(archivePath, "utf-8");
6060
+ const archiveContent = fs12.readFileSync(archivePath, "utf-8");
5942
6061
  const parseResult = import_core17.Brainfile.parseWithErrors(archiveContent);
5943
6062
  if (!parseResult.board) {
5944
6063
  return { success: false, error: "Failed to parse archive file" };
@@ -5956,7 +6075,7 @@ function restoreFromArchive(filePath, taskId, toColumnId) {
5956
6075
  }
5957
6076
  let board;
5958
6077
  try {
5959
- const mainContent = fs11.readFileSync(filePath, "utf-8");
6078
+ const mainContent = fs12.readFileSync(filePath, "utf-8");
5960
6079
  const parseResult = import_core17.Brainfile.parseWithErrors(mainContent);
5961
6080
  if (!parseResult.board) {
5962
6081
  return { success: false, error: "Failed to parse main brainfile" };
@@ -5976,9 +6095,9 @@ function restoreFromArchive(filePath, taskId, toColumnId) {
5976
6095
  archiveBoard.archive = archiveBoard.archive.filter((t) => t.id !== taskId);
5977
6096
  try {
5978
6097
  const mainContent = import_core17.Brainfile.serialize(board);
5979
- fs11.writeFileSync(filePath, mainContent, "utf-8");
6098
+ fs12.writeFileSync(filePath, mainContent, "utf-8");
5980
6099
  const archiveContent = import_core17.Brainfile.serialize(archiveBoard);
5981
- fs11.writeFileSync(archivePath, archiveContent, "utf-8");
6100
+ fs12.writeFileSync(archivePath, archiveContent, "utf-8");
5982
6101
  } catch (err) {
5983
6102
  return {
5984
6103
  success: false,
@@ -5998,10 +6117,10 @@ function restoreCommand(options) {
5998
6117
  missingRequiredError("--column", "brainfile restore --task <task-id> --column <column-name>");
5999
6118
  }
6000
6119
  const filePath = resolveCliBrainfilePath(options.file);
6001
- if (!fs12.existsSync(filePath)) {
6120
+ if (!fs13.existsSync(filePath)) {
6002
6121
  fileNotFoundError(filePath);
6003
6122
  }
6004
- const content = fs12.readFileSync(filePath, "utf-8");
6123
+ const content = fs13.readFileSync(filePath, "utf-8");
6005
6124
  const result = import_core18.Brainfile.parseWithErrors(content);
6006
6125
  if (!result.board) {
6007
6126
  parseError(result.error);
@@ -6110,12 +6229,12 @@ function completeV2(filePath, taskId, force, logger) {
6110
6229
 
6111
6230
  // src/commands/log.ts
6112
6231
  var import_chalk15 = __toESM(require_source());
6113
- import * as fs13 from "fs";
6232
+ import * as fs14 from "fs";
6114
6233
  import * as path17 from "path";
6115
6234
  var import_core20 = __toESM(require_dist());
6116
6235
  function logCommand(options, logger = defaultLogger) {
6117
6236
  const filePath = resolveCliBrainfilePath(options.file);
6118
- if (!fs13.existsSync(filePath)) {
6237
+ if (!fs14.existsSync(filePath)) {
6119
6238
  throw fileNotFound(filePath);
6120
6239
  }
6121
6240
  if (!(0, import_core.isV2)(filePath)) {
@@ -6138,7 +6257,7 @@ function logNoteCommand(options, logger = defaultLogger) {
6138
6257
  throw missingRequired("message", 'brainfile log note --task <task-id> "message"');
6139
6258
  }
6140
6259
  const filePath = resolveCliBrainfilePath(options.file);
6141
- if (!fs13.existsSync(filePath)) {
6260
+ if (!fs14.existsSync(filePath)) {
6142
6261
  throw fileNotFound(filePath);
6143
6262
  }
6144
6263
  if (!(0, import_core.isV2)(filePath)) {
@@ -6151,7 +6270,8 @@ function logNoteCommand(options, logger = defaultLogger) {
6151
6270
  }
6152
6271
  const { doc, filePath: taskPath } = found;
6153
6272
  const timestamp2 = (/* @__PURE__ */ new Date()).toISOString();
6154
- const agentPrefix = options.agent ? `[${options.agent}] ` : "";
6273
+ const actor = options.agent?.trim() || resolveActor(path17.dirname(filePath))?.name;
6274
+ const agentPrefix = actor ? `[${actor}] ` : "";
6155
6275
  const entry = `- ${timestamp2}: ${agentPrefix}${options.message}`;
6156
6276
  const existingDesc = (0, import_core.extractDescription)(doc.body);
6157
6277
  const existingLog = (0, import_core.extractLog)(doc.body);
@@ -6248,8 +6368,8 @@ function listRecentLogs(dirs, logger) {
6248
6368
  const seenIds = new Set(logDocs.map((d) => d.task.id));
6249
6369
  const entries = logDocs.map((d) => ({ id: d.task.id, title: d.task.title, completedAt: d.task.completedAt }));
6250
6370
  const ledgerPath = path17.join(dirs.logsDir, "ledger.jsonl");
6251
- if (fs13.existsSync(ledgerPath)) {
6252
- for (const line of fs13.readFileSync(ledgerPath, "utf-8").split("\n")) {
6371
+ if (fs14.existsSync(ledgerPath)) {
6372
+ for (const line of fs14.readFileSync(ledgerPath, "utf-8").split("\n")) {
6253
6373
  const trimmed = line.trim();
6254
6374
  if (!trimmed) continue;
6255
6375
  try {
@@ -6287,7 +6407,7 @@ function listRecentLogs(dirs, logger) {
6287
6407
  // src/commands/brief.ts
6288
6408
  var import_chalk16 = __toESM(require_source());
6289
6409
  var import_core21 = __toESM(require_dist());
6290
- import * as fs14 from "fs";
6410
+ import * as fs15 from "fs";
6291
6411
  import * as path18 from "path";
6292
6412
  var BRIEF_JSON_VERSION = "0.1";
6293
6413
  function briefCommand(options, logger = defaultLogger) {
@@ -6327,7 +6447,7 @@ function runBrief(options) {
6327
6447
  throw missingRequired("--agent", "brainfile brief --agent <name>");
6328
6448
  }
6329
6449
  const filePath = resolveCliBrainfilePath(options.file);
6330
- if (!fs14.existsSync(filePath)) {
6450
+ if (!fs15.existsSync(filePath)) {
6331
6451
  throw fileNotFound(filePath);
6332
6452
  }
6333
6453
  if (!(0, import_core.isV2)(filePath)) {
@@ -6336,7 +6456,7 @@ function runBrief(options) {
6336
6456
  );
6337
6457
  }
6338
6458
  if (!options.offline) {
6339
- const sync = syncBoard(path18.dirname(filePath), { agent });
6459
+ const sync = syncBoard(path18.dirname(filePath));
6340
6460
  if (!sync.skipped && !sync.ok) {
6341
6461
  process.stderr.write(`${syncMessage(sync).text}
6342
6462
  `);
@@ -32992,11 +33112,11 @@ var require_dist2 = /* @__PURE__ */ __commonJSMin(((exports, module) => {
32992
33112
  if (!f) throw new Error(`Unknown format "${name}"`);
32993
33113
  return f;
32994
33114
  };
32995
- function addFormats2(ajv, list, fs24, exportName) {
33115
+ function addFormats2(ajv, list, fs25, exportName) {
32996
33116
  var _a2;
32997
33117
  var _b;
32998
33118
  (_a2 = (_b = ajv.opts.code).formats) !== null && _a2 !== void 0 || (_b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`);
32999
- for (const f of list) ajv.addFormat(f, fs24[f]);
33119
+ for (const f of list) ajv.addFormat(f, fs25[f]);
33000
33120
  }
33001
33121
  module.exports = exports = formatsPlugin;
33002
33122
  Object.defineProperty(exports, "__esModule", { value: true });
@@ -35089,14 +35209,14 @@ import * as path23 from "path";
35089
35209
 
35090
35210
  // src/mcp/helpers.ts
35091
35211
  var import_core31 = __toESM(require_dist());
35092
- import * as fs15 from "fs";
35212
+ import * as fs16 from "fs";
35093
35213
  import * as path19 from "path";
35094
35214
  function resolveBrainfile(filePath) {
35095
35215
  return path19.resolve(filePath);
35096
35216
  }
35097
35217
  function requireV2(filePath) {
35098
35218
  const resolvedPath = resolveBrainfile(filePath);
35099
- if (!fs15.existsSync(resolvedPath)) {
35219
+ if (!fs16.existsSync(resolvedPath)) {
35100
35220
  return { content: [{ type: "text", text: `Error: File not found: ${resolvedPath}` }], isError: true };
35101
35221
  }
35102
35222
  if (!(0, import_core31.isV2)(resolvedPath)) {
@@ -35124,7 +35244,7 @@ function findGitRoot(startDir) {
35124
35244
  const root = path19.parse(currentDir).root;
35125
35245
  while (currentDir !== root) {
35126
35246
  const gitPath = path19.join(currentDir, ".git");
35127
- if (fs15.existsSync(gitPath)) {
35247
+ if (fs16.existsSync(gitPath)) {
35128
35248
  return currentDir;
35129
35249
  }
35130
35250
  const parentDir = path19.dirname(currentDir);
@@ -35151,10 +35271,7 @@ function withBoardCommit(toolName, defaultFile, handler) {
35151
35271
  const filePath = typeof input.file === "string" && input.file ? input.file : defaultFile;
35152
35272
  const dotDir = path19.dirname(resolveBrainfile(filePath));
35153
35273
  if (isTrackedBoard(dotDir)) {
35154
- commitBoard(dotDir, {
35155
- message: describeToolCall(toolName, input),
35156
- agent: process.env.BRAINFILE_AGENT ?? null
35157
- });
35274
+ commitBoard(dotDir, { message: describeToolCall(toolName, input), agent: detectAgent()?.name });
35158
35275
  }
35159
35276
  }
35160
35277
  } catch {
@@ -35859,7 +35976,7 @@ function registerTaskPatchTool(server, defaultFile) {
35859
35976
  }
35860
35977
 
35861
35978
  // src/mcp/tools/task_delete_tool.ts
35862
- import * as fs16 from "fs";
35979
+ import * as fs17 from "fs";
35863
35980
  function registerTaskDeleteTool(server, defaultFile) {
35864
35981
  server.registerTool(
35865
35982
  "task_delete",
@@ -35885,7 +36002,7 @@ function registerTaskDeleteTool(server, defaultFile) {
35885
36002
  return { content: [{ type: "text", text: `Error: Task not found: ${task}` }], isError: true };
35886
36003
  }
35887
36004
  try {
35888
- fs16.unlinkSync(found.filePath);
36005
+ fs17.unlinkSync(found.filePath);
35889
36006
  return {
35890
36007
  content: [{ type: "text", text: `Task ${task} deleted successfully` }],
35891
36008
  structuredContent: { id: task, deleted: true }
@@ -36079,9 +36196,9 @@ var import_core43 = __toESM(require_dist());
36079
36196
 
36080
36197
  // src/lib/contractRunner.ts
36081
36198
  var import_core40 = __toESM(require_dist());
36082
- import * as fs17 from "fs";
36199
+ import * as fs18 from "fs";
36083
36200
  import * as path21 from "path";
36084
- import { spawnSync as spawnSync2 } from "child_process";
36201
+ import { spawnSync as spawnSync3 } from "child_process";
36085
36202
  function detectDirectoryChangeWarning(command) {
36086
36203
  const patterns = [
36087
36204
  /\bcd\s+[^\s;|&]+/,
@@ -36099,7 +36216,7 @@ function detectDirectoryChangeWarning(command) {
36099
36216
  return void 0;
36100
36217
  }
36101
36218
  function requireV2Runner(resolvedFilePath) {
36102
- if (!fs17.existsSync(resolvedFilePath)) {
36219
+ if (!fs18.existsSync(resolvedFilePath)) {
36103
36220
  return { error: `File not found: ${resolvedFilePath}` };
36104
36221
  }
36105
36222
  if (!(0, import_core.isV2)(resolvedFilePath)) {
@@ -36259,7 +36376,7 @@ function validateContractV2(ctx, resolvedFilePath) {
36259
36376
  continue;
36260
36377
  }
36261
36378
  const resolved = path21.isAbsolute(normalized.value) ? normalized.value : path21.join(baseDir, normalized.value);
36262
- if (!fs17.existsSync(resolved)) {
36379
+ if (!fs18.existsSync(resolved)) {
36263
36380
  deliverableChecks.push({ deliverable: d, ok: false, resolvedPath: resolved, error: "File not found" });
36264
36381
  } else {
36265
36382
  deliverableChecks.push({ deliverable: d, ok: true, resolvedPath: resolved });
@@ -36282,7 +36399,7 @@ function validateContractV2(ctx, resolvedFilePath) {
36282
36399
  if (dirWarning) {
36283
36400
  warnings.push({ command: normalized.value, message: dirWarning });
36284
36401
  }
36285
- const res = spawnSync2(normalized.value, {
36402
+ const res = spawnSync3(normalized.value, {
36286
36403
  shell: true,
36287
36404
  cwd: baseDir,
36288
36405
  encoding: "utf-8"
@@ -36937,7 +37054,7 @@ function registerContractTool(server, defaultFile) {
36937
37054
 
36938
37055
  // src/mcp/tools/task_complete_tool.ts
36939
37056
  var import_core44 = __toESM(require_dist());
36940
- import * as fs18 from "fs";
37057
+ import * as fs19 from "fs";
36941
37058
  function registerTaskCompleteTool(server, defaultFile) {
36942
37059
  server.registerTool(
36943
37060
  "task_complete",
@@ -37029,7 +37146,7 @@ ${result.incompleteChildren.map((c) => ` - ${c.id}: ${c.title}`).join("\n")}` :
37029
37146
  if (!ghResult.success) {
37030
37147
  return { content: [{ type: "text", text: `Error creating GitHub issue: ${ghResult.error}` }], isError: true };
37031
37148
  }
37032
- fs18.unlinkSync(found.filePath);
37149
+ fs19.unlinkSync(found.filePath);
37033
37150
  return {
37034
37151
  content: [{ type: "text", text: `Task ${task} archived to GitHub Issue #${ghResult.issueNumber} (closed)
37035
37152
 
@@ -37081,7 +37198,7 @@ ${teamList}` }], isError: true };
37081
37198
  if (!linearResult.success) {
37082
37199
  return { content: [{ type: "text", text: `Error creating Linear issue: ${linearResult.error}` }], isError: true };
37083
37200
  }
37084
- fs18.unlinkSync(found.filePath);
37201
+ fs19.unlinkSync(found.filePath);
37085
37202
  return {
37086
37203
  content: [{ type: "text", text: `Task ${task} archived to Linear Issue ${linearResult.issueId} (Done)
37087
37204
 
@@ -37130,7 +37247,7 @@ function registerBriefTool(server, defaultFile) {
37130
37247
  };
37131
37248
  }
37132
37249
  const resolvedPath = resolveBrainfile(filePath);
37133
- if (!offline) syncBoard(path22.dirname(resolvedPath), { agent: agentName });
37250
+ if (!offline) syncBoard(path22.dirname(resolvedPath));
37134
37251
  const dirs = (0, import_core.getV2Dirs)(resolvedPath);
37135
37252
  const isPeek = peek === true;
37136
37253
  const state = (0, import_core45.readBriefState)(resolvedPath, agentName);
@@ -37234,7 +37351,7 @@ var import_chalk20 = __toESM(require_source());
37234
37351
  import * as readline2 from "readline";
37235
37352
 
37236
37353
  // src/utils/hook-settings.ts
37237
- import * as fs19 from "fs";
37354
+ import * as fs20 from "fs";
37238
37355
  import * as path24 from "path";
37239
37356
  import * as os from "os";
37240
37357
  function resolveHomeDir() {
@@ -37266,24 +37383,24 @@ function getSettingsPath(tool, scope) {
37266
37383
  function readToolSettings(tool, scope) {
37267
37384
  const settingsPath = getSettingsPath(tool, scope);
37268
37385
  if (tool === "cline") {
37269
- if (!fs19.existsSync(settingsPath)) {
37386
+ if (!fs20.existsSync(settingsPath)) {
37270
37387
  return { hooks: {} };
37271
37388
  }
37272
37389
  const hooks = {};
37273
37390
  const hookFiles = ["PostToolUse", "UserPromptSubmit", "TaskStart"];
37274
37391
  for (const hookFile of hookFiles) {
37275
37392
  const hookPath = path24.join(settingsPath, hookFile);
37276
- if (fs19.existsSync(hookPath)) {
37393
+ if (fs20.existsSync(hookPath)) {
37277
37394
  hooks[hookFile] = true;
37278
37395
  }
37279
37396
  }
37280
37397
  return { hooks };
37281
37398
  }
37282
- if (!fs19.existsSync(settingsPath)) {
37399
+ if (!fs20.existsSync(settingsPath)) {
37283
37400
  return tool === "cursor" ? { version: 1, hooks: {} } : {};
37284
37401
  }
37285
37402
  try {
37286
- const content = fs19.readFileSync(settingsPath, "utf-8");
37403
+ const content = fs20.readFileSync(settingsPath, "utf-8");
37287
37404
  return JSON.parse(content);
37288
37405
  } catch {
37289
37406
  return tool === "cursor" ? { version: 1, hooks: {} } : {};
@@ -37292,10 +37409,10 @@ function readToolSettings(tool, scope) {
37292
37409
  function writeToolSettings(tool, scope, settings) {
37293
37410
  const settingsPath = getSettingsPath(tool, scope);
37294
37411
  const settingsDir = path24.dirname(settingsPath);
37295
- if (!fs19.existsSync(settingsDir)) {
37296
- fs19.mkdirSync(settingsDir, { recursive: true });
37412
+ if (!fs20.existsSync(settingsDir)) {
37413
+ fs20.mkdirSync(settingsDir, { recursive: true });
37297
37414
  }
37298
- fs19.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
37415
+ fs20.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), "utf-8");
37299
37416
  }
37300
37417
  function createClineHookScript(hookType) {
37301
37418
  const commandMap = {
@@ -37345,8 +37462,8 @@ function installBrainfileHooks(tool, scope) {
37345
37462
  const settings = readToolSettings(tool, scope);
37346
37463
  if (tool === "cline") {
37347
37464
  const hooksDir = getSettingsPath(tool, scope);
37348
- if (!fs19.existsSync(hooksDir)) {
37349
- fs19.mkdirSync(hooksDir, { recursive: true });
37465
+ if (!fs20.existsSync(hooksDir)) {
37466
+ fs20.mkdirSync(hooksDir, { recursive: true });
37350
37467
  }
37351
37468
  const hookTypes = [
37352
37469
  "PostToolUse",
@@ -37356,7 +37473,7 @@ function installBrainfileHooks(tool, scope) {
37356
37473
  for (const hookType of hookTypes) {
37357
37474
  const hookPath = path24.join(hooksDir, hookType);
37358
37475
  const scriptContent = createClineHookScript(hookType);
37359
- fs19.writeFileSync(hookPath, scriptContent, { mode: 493 });
37476
+ fs20.writeFileSync(hookPath, scriptContent, { mode: 493 });
37360
37477
  }
37361
37478
  return;
37362
37479
  }
@@ -37446,16 +37563,16 @@ function installBrainfileHooks(tool, scope) {
37446
37563
  function uninstallBrainfileHooks(tool, scope) {
37447
37564
  if (tool === "cline") {
37448
37565
  const hooksDir = getSettingsPath(tool, scope);
37449
- if (!fs19.existsSync(hooksDir)) {
37566
+ if (!fs20.existsSync(hooksDir)) {
37450
37567
  return;
37451
37568
  }
37452
37569
  const hookFiles = ["PostToolUse", "UserPromptSubmit", "TaskStart"];
37453
37570
  for (const hookFile of hookFiles) {
37454
37571
  const hookPath = path24.join(hooksDir, hookFile);
37455
- if (fs19.existsSync(hookPath)) {
37456
- const content = fs19.readFileSync(hookPath, "utf-8");
37572
+ if (fs20.existsSync(hookPath)) {
37573
+ const content = fs20.readFileSync(hookPath, "utf-8");
37457
37574
  if (content.includes("brainfile hooks")) {
37458
- fs19.unlinkSync(hookPath);
37575
+ fs20.unlinkSync(hookPath);
37459
37576
  }
37460
37577
  }
37461
37578
  }
@@ -37502,15 +37619,15 @@ function uninstallBrainfileHooks(tool, scope) {
37502
37619
  }
37503
37620
  function areBrainfileHooksInstalled(tool, scope) {
37504
37621
  const settingsPath = getSettingsPath(tool, scope);
37505
- if (!fs19.existsSync(settingsPath)) {
37622
+ if (!fs20.existsSync(settingsPath)) {
37506
37623
  return false;
37507
37624
  }
37508
37625
  if (tool === "cline") {
37509
37626
  const hookFiles = ["PostToolUse", "UserPromptSubmit", "TaskStart"];
37510
37627
  for (const hookFile of hookFiles) {
37511
37628
  const hookPath = path24.join(settingsPath, hookFile);
37512
- if (fs19.existsSync(hookPath)) {
37513
- const content = fs19.readFileSync(hookPath, "utf-8");
37629
+ if (fs20.existsSync(hookPath)) {
37630
+ const content = fs20.readFileSync(hookPath, "utf-8");
37514
37631
  if (content.includes("brainfile hooks")) {
37515
37632
  return true;
37516
37633
  }
@@ -37550,7 +37667,7 @@ function areBrainfileHooksInstalled(tool, scope) {
37550
37667
 
37551
37668
  // src/utils/brainfile-finder.ts
37552
37669
  var import_core47 = __toESM(require_dist());
37553
- import * as fs20 from "fs";
37670
+ import * as fs21 from "fs";
37554
37671
  function findBrainfile3() {
37555
37672
  const found = (0, import_core47.findBrainfile)(process.cwd());
37556
37673
  if (found && found.kind !== "dotdir") return null;
@@ -37558,7 +37675,7 @@ function findBrainfile3() {
37558
37675
  }
37559
37676
  function getFileAgeMinutes(filePath) {
37560
37677
  try {
37561
- const stats = fs20.statSync(filePath);
37678
+ const stats = fs21.statSync(filePath);
37562
37679
  const now = Date.now();
37563
37680
  const modifiedTime = stats.mtimeMs;
37564
37681
  const ageMs = now - modifiedTime;
@@ -38021,17 +38138,17 @@ function flattenConfig(obj, prefix = "") {
38021
38138
 
38022
38139
  // src/commands/schema.ts
38023
38140
  var import_chalk22 = __toESM(require_source());
38024
- import * as fs21 from "fs";
38141
+ import * as fs22 from "fs";
38025
38142
  import * as path25 from "path";
38026
38143
  var SCHEMA_BASE_URL = "https://brainfile.md/v1";
38027
38144
  var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
38028
38145
  function getBundledSchemasDir() {
38029
38146
  const distPath = path25.join(__dirname, "..", "schemas");
38030
38147
  const srcPath = path25.join(__dirname, "..", "..", "src", "schemas");
38031
- if (fs21.existsSync(distPath)) {
38148
+ if (fs22.existsSync(distPath)) {
38032
38149
  return distPath;
38033
38150
  }
38034
- if (fs21.existsSync(srcPath)) {
38151
+ if (fs22.existsSync(srcPath)) {
38035
38152
  return srcPath;
38036
38153
  }
38037
38154
  return path25.join(__dirname, "schemas");
@@ -38117,14 +38234,14 @@ function schemaShowCommand(name, options, logger) {
38117
38234
  }
38118
38235
  const schemasDir = getBundledSchemasDir();
38119
38236
  const schemaPath = path25.join(schemasDir, schemaInfo.file);
38120
- if (!fs21.existsSync(schemaPath)) {
38237
+ if (!fs22.existsSync(schemaPath)) {
38121
38238
  throw new CLIError(
38122
38239
  `Schema file not found: ${schemaInfo.file}`,
38123
38240
  ExitCode.USER_ERROR,
38124
38241
  `Expected at: ${schemaPath}`
38125
38242
  );
38126
38243
  }
38127
- const content = fs21.readFileSync(schemaPath, "utf-8");
38244
+ const content = fs22.readFileSync(schemaPath, "utf-8");
38128
38245
  const schema2 = JSON.parse(content);
38129
38246
  if (options.json) {
38130
38247
  logger.log(JSON.stringify(schema2, null, 2));
@@ -38190,14 +38307,14 @@ async function schemaUpdateAsync(options, logger) {
38190
38307
  const remoteContent = await response.text();
38191
38308
  const remoteSchema = JSON.parse(remoteContent);
38192
38309
  let localSchema = null;
38193
- if (fs21.existsSync(localPath)) {
38194
- const localContent = fs21.readFileSync(localPath, "utf-8");
38310
+ if (fs22.existsSync(localPath)) {
38311
+ const localContent = fs22.readFileSync(localPath, "utf-8");
38195
38312
  localSchema = JSON.parse(localContent);
38196
38313
  }
38197
38314
  const remoteStr = JSON.stringify(remoteSchema);
38198
38315
  const localStr = localSchema ? JSON.stringify(localSchema) : "";
38199
38316
  if (remoteStr !== localStr) {
38200
- fs21.writeFileSync(localPath, JSON.stringify(remoteSchema, null, 2));
38317
+ fs22.writeFileSync(localPath, JSON.stringify(remoteSchema, null, 2));
38201
38318
  results.push({ id: schemaInfo.id, status: "updated" });
38202
38319
  } else {
38203
38320
  results.push({ id: schemaInfo.id, status: "current" });
@@ -38274,8 +38391,8 @@ async function checkForUpdatesAsync(logger) {
38274
38391
  }
38275
38392
  const remoteContent = await response.text();
38276
38393
  const remoteSchema = JSON.parse(remoteContent);
38277
- if (fs21.existsSync(localPath)) {
38278
- const localContent = fs21.readFileSync(localPath, "utf-8");
38394
+ if (fs22.existsSync(localPath)) {
38395
+ const localContent = fs22.readFileSync(localPath, "utf-8");
38279
38396
  const localSchema = JSON.parse(localContent);
38280
38397
  const remoteStr = JSON.stringify(remoteSchema);
38281
38398
  const localStr = JSON.stringify(localSchema);
@@ -38299,7 +38416,7 @@ async function checkForUpdatesAsync(logger) {
38299
38416
  // src/commands/adr.ts
38300
38417
  var import_chalk23 = __toESM(require_source());
38301
38418
  var import_core48 = __toESM(require_dist());
38302
- import * as fs22 from "fs";
38419
+ import * as fs23 from "fs";
38303
38420
  import * as path26 from "path";
38304
38421
  var ADR_COMMAND_HELP = `
38305
38422
  Examples:
@@ -38326,7 +38443,7 @@ function adrPromoteCommand(options, logger = defaultLogger) {
38326
38443
  );
38327
38444
  }
38328
38445
  const resolvedPath = resolveCliBrainfilePath(options.file);
38329
- if (!fs22.existsSync(resolvedPath)) {
38446
+ if (!fs23.existsSync(resolvedPath)) {
38330
38447
  throw fileNotFound(resolvedPath);
38331
38448
  }
38332
38449
  if (!(0, import_core.isV2)(resolvedPath)) {
@@ -38353,13 +38470,13 @@ function adrPromoteCommand(options, logger = defaultLogger) {
38353
38470
  };
38354
38471
  delete promotedTask.column;
38355
38472
  delete promotedTask.position;
38356
- fs22.mkdirSync(dirs.logsDir, { recursive: true });
38473
+ fs23.mkdirSync(dirs.logsDir, { recursive: true });
38357
38474
  const logPath = path26.join(dirs.logsDir, (0, import_core48.taskFileName)(task.id));
38358
- if (fs22.existsSync(logPath)) {
38475
+ if (fs23.existsSync(logPath)) {
38359
38476
  throw operationFailed(`Log already exists for ADR: ${task.id}`);
38360
38477
  }
38361
38478
  (0, import_core48.writeTaskFile)(logPath, promotedTask, doc.body);
38362
- fs22.unlinkSync(taskPath);
38479
+ fs23.unlinkSync(taskPath);
38363
38480
  logger.log(import_chalk23.default.green("ADR promoted!"));
38364
38481
  logger.log("");
38365
38482
  logger.log(import_chalk23.default.gray(` ADR: ${task.id} - ${task.title}`));
@@ -38546,7 +38663,7 @@ function planLinkCommand(options, logger = defaultLogger) {
38546
38663
  }
38547
38664
 
38548
38665
  // src/commands/types.ts
38549
- import * as fs23 from "fs";
38666
+ import * as fs24 from "fs";
38550
38667
 
38551
38668
  // ../node_modules/js-yaml/dist/js-yaml.mjs
38552
38669
  function getDefaultExportFromCjs(x) {
@@ -40315,7 +40432,7 @@ function requireLoader() {
40315
40432
  const _tag = state.tag;
40316
40433
  const _anchor = state.anchor;
40317
40434
  const _result = [];
40318
- let detected = false;
40435
+ let detected2 = false;
40319
40436
  if (state.firstTabInLine !== -1) return false;
40320
40437
  if (state.anchor !== null) {
40321
40438
  storeAnchor(state, state.anchor, _result);
@@ -40333,7 +40450,7 @@ function requireLoader() {
40333
40450
  if (!isWsOrEol(following)) {
40334
40451
  break;
40335
40452
  }
40336
- detected = true;
40453
+ detected2 = true;
40337
40454
  state.position++;
40338
40455
  if (skipSeparationSpace(state, true, -1)) {
40339
40456
  if (state.lineIndent <= nodeIndent) {
@@ -40353,7 +40470,7 @@ function requireLoader() {
40353
40470
  break;
40354
40471
  }
40355
40472
  }
40356
- if (detected) {
40473
+ if (detected2) {
40357
40474
  state.tag = _tag;
40358
40475
  state.anchor = _anchor;
40359
40476
  state.kind = "sequence";
@@ -40375,7 +40492,7 @@ function requireLoader() {
40375
40492
  let keyNode = null;
40376
40493
  let valueNode = null;
40377
40494
  let atExplicitKey = false;
40378
- let detected = false;
40495
+ let detected2 = false;
40379
40496
  if (state.firstTabInLine !== -1) return false;
40380
40497
  if (state.anchor !== null) {
40381
40498
  storeAnchor(state, state.anchor, _result);
@@ -40394,7 +40511,7 @@ function requireLoader() {
40394
40511
  storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, null, _keyLine, _keyLineStart, _keyPos);
40395
40512
  keyTag = keyNode = valueNode = null;
40396
40513
  }
40397
- detected = true;
40514
+ detected2 = true;
40398
40515
  atExplicitKey = true;
40399
40516
  allowCompact = true;
40400
40517
  } else if (atExplicitKey) {
@@ -40426,19 +40543,19 @@ function requireLoader() {
40426
40543
  storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, null, _keyLine, _keyLineStart, _keyPos);
40427
40544
  keyTag = keyNode = valueNode = null;
40428
40545
  }
40429
- detected = true;
40546
+ detected2 = true;
40430
40547
  atExplicitKey = false;
40431
40548
  allowCompact = false;
40432
40549
  keyTag = state.tag;
40433
40550
  keyNode = state.result;
40434
- } else if (detected) {
40551
+ } else if (detected2) {
40435
40552
  throwError(state, "can not read an implicit mapping pair; a colon is missed");
40436
40553
  } else {
40437
40554
  state.tag = _tag;
40438
40555
  state.anchor = _anchor;
40439
40556
  return true;
40440
40557
  }
40441
- } else if (detected) {
40558
+ } else if (detected2) {
40442
40559
  throwError(state, "can not read a block mapping entry; a multiline key may not be an implicit key");
40443
40560
  } else {
40444
40561
  state.tag = _tag;
@@ -40475,13 +40592,13 @@ function requireLoader() {
40475
40592
  if (atExplicitKey) {
40476
40593
  storeMappingPair(state, _result, overridableKeys, keyTag, keyNode, null, _keyLine, _keyLineStart, _keyPos);
40477
40594
  }
40478
- if (detected) {
40595
+ if (detected2) {
40479
40596
  state.tag = _tag;
40480
40597
  state.anchor = _anchor;
40481
40598
  state.kind = "mapping";
40482
40599
  state.result = _result;
40483
40600
  }
40484
- return detected;
40601
+ return detected2;
40485
40602
  }
40486
40603
  function readTagProperty(state) {
40487
40604
  let isVerbatim = false;
@@ -41647,11 +41764,11 @@ ${yamlContent}---
41647
41764
  ${doc.body}`;
41648
41765
  }
41649
41766
  function readFrontmatter(filePath) {
41650
- const content = fs23.readFileSync(filePath, "utf-8");
41767
+ const content = fs24.readFileSync(filePath, "utf-8");
41651
41768
  return parseFrontmatter(content);
41652
41769
  }
41653
41770
  function writeFrontmatter(filePath, doc) {
41654
- fs23.writeFileSync(filePath, serializeFrontmatter(doc), "utf-8");
41771
+ fs24.writeFileSync(filePath, serializeFrontmatter(doc), "utf-8");
41655
41772
  }
41656
41773
  function sanitizeTypesConfig(raw) {
41657
41774
  if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
@@ -41773,11 +41890,11 @@ function typesAddCommand(options, logger = defaultLogger) {
41773
41890
 
41774
41891
  // src/cli.ts
41775
41892
  async function runTui(options) {
41776
- const { tuiCommand } = await import("./tui-M2KWIG2E.mjs");
41893
+ const { tuiCommand } = await import("./tui-3Z7ZJV7M.mjs");
41777
41894
  return tuiCommand(options);
41778
41895
  }
41779
41896
  var packageJson = JSON.parse(
41780
- readFileSync10(join18(__dirname, "..", "package.json"), "utf8")
41897
+ readFileSync11(join18(__dirname, "..", "package.json"), "utf8")
41781
41898
  );
41782
41899
  var SUBCOMMANDS = ["init", "migrate", "list", "show", "add", "move", "patch", "delete", "archive", "restore", "complete", "log", "note", "brief", "search", "subtask", "template", "lint", "tui", "hooks", "mcp", "auth", "config", "contract", "schema", "adr", "plan", "types", "sync", "where", "merge-driver", "help"];
41783
41900
  function shouldLaunchTUI() {
@@ -41795,7 +41912,7 @@ function shouldLaunchTUI() {
41795
41912
  if (SUBCOMMANDS.includes(firstArg)) {
41796
41913
  return { launch: false, file: "" };
41797
41914
  }
41798
- if (firstArg.includes(".") || firstArg.includes("/") || firstArg.endsWith(sep3) || existsSync19(firstArg)) {
41915
+ if (firstArg.includes(".") || firstArg.includes("/") || firstArg.endsWith(sep3) || existsSync20(firstArg)) {
41799
41916
  return { launch: true, file: firstArg };
41800
41917
  }
41801
41918
  return { launch: false, file: "" };
@@ -41892,13 +42009,13 @@ ${ADD_COMMAND_HELP}`);
41892
42009
  program2.command("log").description("View, search, and manage completed task logs (v2 only)").option("-f, --file <path>", "Path to brainfile file (auto-detect by default)", "brainfile.md").option("-t, --task <id>", "View a specific task log").option("-s, --search <query>", "Search across all logs").option("--recent", "List recently completed tasks").action((options) => {
41893
42010
  logCommand(options);
41894
42011
  });
41895
- program2.command("note").description("Append a timestamped note to a task log").option("-f, --file <path>", "Path to brainfile file (auto-detect by default)", "brainfile.md").option("-t, --task <id>", "Task ID to add note to (required)").option("--agent <name>", "Agent name for attribution").argument("[message]", "Log message to append").action((message, options) => {
42012
+ program2.command("note").description("Append a timestamped note to a task log").option("-f, --file <path>", "Path to brainfile file (auto-detect by default)", "brainfile.md").option("-t, --task <id>", "Task ID to add note to (required)").option("--agent <name>", "Who wrote it (default: the detected agent, else your git user.name)").argument("[message]", "Log message to append").action((message, options) => {
41896
42013
  logNoteCommand({ ...options, message });
41897
42014
  });
41898
42015
  program2.command("brief").description("Per-agent brief: what changed since this agent last checked in").option("-f, --file <path>", "Path to brainfile file (auto-detect by default)", "brainfile.md").option("--agent <name>", "Agent identifier (required \u2014 brief state is per-agent)").option("--peek", "Read the brief without marking it as seen").option("--offline", "Skip syncing a shared board before the brief").option("--json", "Output as JSON").action((options) => {
41899
42016
  briefCommand(options);
41900
42017
  });
41901
- const syncCmd = program2.command("sync").description("Share a tracked board through a git remote: fetch, merge, push").option("-f, --file <path>", "Path to brainfile file (auto-detect by default)", "brainfile.md").option("--pull", "Fetch and merge only").option("--push", "Push only").option("--set-remote <name|url>", 'Remote to sync with: an existing remote name, or a URL registered as remote "board"').option("--remote-branch <name>", 'Branch on the remote (default: board branch; folder name for a standalone board; "home" for -g)').option("--autosync <mode>", "off | push (after writes, default once a remote is set) | full (also fetch before reads)").option("--json", "Output as JSON").addOption(new Option("--wait <ms>", "Sleep before syncing (autosync child)").hideHelp()).action((options) => {
42018
+ const syncCmd = program2.command("sync").description("Share a tracked board through a git remote: fetch, merge, push").option("-f, --file <path>", "Path to brainfile file (auto-detect by default)", "brainfile.md").option("--pull", "Fetch and merge only").option("--push", "Push only").option("--set-remote <name|url>", 'Remote to sync with: an existing remote name, or a URL registered as remote "board"').option("--board-name <name>", 'Name on the remote, stored as refs/brainfile/<name> (default: "board"; folder name for a standalone board; "home" for -g)').addOption(new Option("--remote-branch <name>", "0.21.0 spelling of --board-name").hideHelp()).option("--autosync <mode>", "off | push (after writes, default once a remote is set) | full (also fetch before reads)").option("--json", "Output as JSON").addOption(new Option("--wait <ms>", "Sleep before syncing (autosync child)").hideHelp()).action((options) => {
41902
42019
  syncCommand(options);
41903
42020
  });
41904
42021
  syncCmd.addHelpText("after", `