brainfile 0.19.0 → 0.21.0

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 (128) hide show
  1. package/README.md +18 -24
  2. package/dist/__tests__/helpers/mcp-wire.d.ts +45 -0
  3. package/dist/__tests__/helpers/mcp-wire.d.ts.map +1 -0
  4. package/dist/__tests__/helpers/mcp-wire.js +133 -0
  5. package/dist/__tests__/helpers/mcp-wire.js.map +1 -0
  6. package/dist/{chunk-YTFLC673.mjs → chunk-FOLBAGJS.mjs} +2483 -1075
  7. package/dist/cli.mjs +2526 -1656
  8. package/dist/commands/adr.d.ts +1 -9
  9. package/dist/commands/adr.d.ts.map +1 -1
  10. package/dist/commands/adr.js +11 -88
  11. package/dist/commands/adr.js.map +1 -1
  12. package/dist/commands/archive.d.ts +2 -5
  13. package/dist/commands/archive.d.ts.map +1 -1
  14. package/dist/commands/archive.js +43 -11
  15. package/dist/commands/archive.js.map +1 -1
  16. package/dist/commands/brief.d.ts +32 -0
  17. package/dist/commands/brief.d.ts.map +1 -0
  18. package/dist/commands/brief.js +161 -0
  19. package/dist/commands/brief.js.map +1 -0
  20. package/dist/commands/complete.js +1 -1
  21. package/dist/commands/complete.js.map +1 -1
  22. package/dist/commands/init.d.ts +14 -0
  23. package/dist/commands/init.d.ts.map +1 -1
  24. package/dist/commands/init.js +115 -15
  25. package/dist/commands/init.js.map +1 -1
  26. package/dist/commands/log.js +31 -18
  27. package/dist/commands/log.js.map +1 -1
  28. package/dist/commands/mcp.d.ts +9 -0
  29. package/dist/commands/mcp.d.ts.map +1 -1
  30. package/dist/commands/mcp.js +72 -17
  31. package/dist/commands/mcp.js.map +1 -1
  32. package/dist/commands/merge-driver.d.ts +26 -0
  33. package/dist/commands/merge-driver.d.ts.map +1 -0
  34. package/dist/commands/merge-driver.js +195 -0
  35. package/dist/commands/merge-driver.js.map +1 -0
  36. package/dist/commands/migrate-tracked.d.ts +8 -0
  37. package/dist/commands/migrate-tracked.d.ts.map +1 -0
  38. package/dist/commands/migrate-tracked.js +428 -0
  39. package/dist/commands/migrate-tracked.js.map +1 -0
  40. package/dist/commands/migrate.d.ts +6 -0
  41. package/dist/commands/migrate.d.ts.map +1 -1
  42. package/dist/commands/migrate.js +19 -12
  43. package/dist/commands/migrate.js.map +1 -1
  44. package/dist/commands/sync.d.ts +16 -0
  45. package/dist/commands/sync.d.ts.map +1 -0
  46. package/dist/commands/sync.js +152 -0
  47. package/dist/commands/sync.js.map +1 -0
  48. package/dist/commands/where.d.ts +21 -0
  49. package/dist/commands/where.d.ts.map +1 -0
  50. package/dist/commands/where.js +135 -0
  51. package/dist/commands/where.js.map +1 -0
  52. package/dist/mcp/helpers.d.ts +8 -0
  53. package/dist/mcp/helpers.d.ts.map +1 -1
  54. package/dist/mcp/helpers.js +44 -0
  55. package/dist/mcp/helpers.js.map +1 -1
  56. package/dist/mcp/schemas.d.ts +297 -0
  57. package/dist/mcp/schemas.d.ts.map +1 -0
  58. package/dist/mcp/schemas.js +328 -0
  59. package/dist/mcp/schemas.js.map +1 -0
  60. package/dist/mcp/tools/brief_tool.d.ts +14 -0
  61. package/dist/mcp/tools/brief_tool.d.ts.map +1 -0
  62. package/dist/mcp/tools/brief_tool.js +96 -0
  63. package/dist/mcp/tools/brief_tool.js.map +1 -0
  64. package/dist/mcp/tools/contract_tool.d.ts.map +1 -1
  65. package/dist/mcp/tools/contract_tool.js +43 -18
  66. package/dist/mcp/tools/contract_tool.js.map +1 -1
  67. package/dist/mcp/tools/get_task_tool.d.ts.map +1 -1
  68. package/dist/mcp/tools/get_task_tool.js +7 -2
  69. package/dist/mcp/tools/get_task_tool.js.map +1 -1
  70. package/dist/mcp/tools/list_tasks_tool.d.ts.map +1 -1
  71. package/dist/mcp/tools/list_tasks_tool.js +9 -3
  72. package/dist/mcp/tools/list_tasks_tool.js.map +1 -1
  73. package/dist/mcp/tools/search_tool.d.ts.map +1 -1
  74. package/dist/mcp/tools/search_tool.js +17 -4
  75. package/dist/mcp/tools/search_tool.js.map +1 -1
  76. package/dist/mcp/tools/subtask_tool.d.ts.map +1 -1
  77. package/dist/mcp/tools/subtask_tool.js +52 -17
  78. package/dist/mcp/tools/subtask_tool.js.map +1 -1
  79. package/dist/mcp/tools/task_add_tool.d.ts.map +1 -1
  80. package/dist/mcp/tools/task_add_tool.js +13 -7
  81. package/dist/mcp/tools/task_add_tool.js.map +1 -1
  82. package/dist/mcp/tools/task_complete_tool.d.ts.map +1 -1
  83. package/dist/mcp/tools/task_complete_tool.js +39 -10
  84. package/dist/mcp/tools/task_complete_tool.js.map +1 -1
  85. package/dist/mcp/tools/task_delete_tool.d.ts.map +1 -1
  86. package/dist/mcp/tools/task_delete_tool.js +12 -6
  87. package/dist/mcp/tools/task_delete_tool.js.map +1 -1
  88. package/dist/mcp/tools/task_move_tool.d.ts.map +1 -1
  89. package/dist/mcp/tools/task_move_tool.js +21 -11
  90. package/dist/mcp/tools/task_move_tool.js.map +1 -1
  91. package/dist/mcp/tools/task_patch_tool.d.ts.map +1 -1
  92. package/dist/mcp/tools/task_patch_tool.js +23 -10
  93. package/dist/mcp/tools/task_patch_tool.js.map +1 -1
  94. package/dist/schemas/board.json +1 -1
  95. package/dist/tui/theme.d.ts +98 -0
  96. package/dist/tui/theme.d.ts.map +1 -0
  97. package/dist/tui/theme.js +141 -0
  98. package/dist/tui/theme.js.map +1 -0
  99. package/dist/{tui-P56H4X7N.mjs → tui-M2KWIG2E.mjs} +1707 -2006
  100. package/dist/utils/board-autosync.d.ts +22 -0
  101. package/dist/utils/board-autosync.d.ts.map +1 -0
  102. package/dist/utils/board-autosync.js +166 -0
  103. package/dist/utils/board-autosync.js.map +1 -0
  104. package/dist/utils/board-commit-hooks.d.ts +12 -0
  105. package/dist/utils/board-commit-hooks.d.ts.map +1 -0
  106. package/dist/utils/board-commit-hooks.js +51 -0
  107. package/dist/utils/board-commit-hooks.js.map +1 -0
  108. package/dist/utils/board-repo.d.ts +108 -0
  109. package/dist/utils/board-repo.d.ts.map +1 -0
  110. package/dist/utils/board-repo.js +501 -0
  111. package/dist/utils/board-repo.js.map +1 -0
  112. package/dist/utils/board-sync.d.ts +111 -0
  113. package/dist/utils/board-sync.d.ts.map +1 -0
  114. package/dist/utils/board-sync.js +469 -0
  115. package/dist/utils/board-sync.js.map +1 -0
  116. package/dist/utils/brainfile-path.d.ts +11 -1
  117. package/dist/utils/brainfile-path.d.ts.map +1 -1
  118. package/dist/utils/brainfile-path.js +39 -2
  119. package/dist/utils/brainfile-path.js.map +1 -1
  120. package/dist/utils/cli-error.d.ts +20 -0
  121. package/dist/utils/cli-error.d.ts.map +1 -1
  122. package/dist/utils/cli-error.js +36 -0
  123. package/dist/utils/cli-error.js.map +1 -1
  124. package/package.json +3 -3
  125. package/dist/commands/rules.d.ts +0 -68
  126. package/dist/commands/rules.d.ts.map +0 -1
  127. package/dist/commands/rules.js +0 -322
  128. package/dist/commands/rules.js.map +0 -1
@@ -880,15 +880,15 @@ var require_route = __commonJS({
880
880
  };
881
881
  }
882
882
  function wrapConversion(toModel, graph) {
883
- const path7 = [graph[toModel].parent, toModel];
883
+ const path11 = [graph[toModel].parent, toModel];
884
884
  let fn = conversions[graph[toModel].parent][toModel];
885
885
  let cur = graph[toModel].parent;
886
886
  while (graph[cur].parent) {
887
- path7.unshift(graph[cur].parent);
887
+ path11.unshift(graph[cur].parent);
888
888
  fn = link(conversions[graph[cur].parent][cur], fn);
889
889
  cur = graph[cur].parent;
890
890
  }
891
- fn.conversion = path7;
891
+ fn.conversion = path11;
892
892
  return fn;
893
893
  }
894
894
  module.exports = function(fromModel) {
@@ -1003,7 +1003,7 @@ var require_ansi_styles = __commonJS({
1003
1003
  });
1004
1004
  };
1005
1005
  var colorConvert;
1006
- var makeDynamicStyles = (wrap, targetSpace, identity, isBackground) => {
1006
+ var makeDynamicStyles = (wrap, targetSpace, identity2, isBackground) => {
1007
1007
  if (colorConvert === void 0) {
1008
1008
  colorConvert = require_color_convert();
1009
1009
  }
@@ -1012,7 +1012,7 @@ var require_ansi_styles = __commonJS({
1012
1012
  for (const [sourceSpace, suite] of Object.entries(colorConvert)) {
1013
1013
  const name = sourceSpace === "ansi16" ? "ansi" : sourceSpace;
1014
1014
  if (sourceSpace === targetSpace) {
1015
- styles[name] = wrap(identity, offset);
1015
+ styles[name] = wrap(identity2, offset);
1016
1016
  } else if (typeof suite === "object") {
1017
1017
  styles[name] = wrap(suite[targetSpace], offset);
1018
1018
  }
@@ -1128,7 +1128,7 @@ var require_has_flag = __commonJS({
1128
1128
  var require_supports_color = __commonJS({
1129
1129
  "../node_modules/supports-color/index.js"(exports, module) {
1130
1130
  "use strict";
1131
- var os2 = __require("os");
1131
+ var os5 = __require("os");
1132
1132
  var tty = __require("tty");
1133
1133
  var hasFlag = require_has_flag();
1134
1134
  var { env } = process;
@@ -1176,7 +1176,7 @@ var require_supports_color = __commonJS({
1176
1176
  return min;
1177
1177
  }
1178
1178
  if (process.platform === "win32") {
1179
- const osRelease = os2.release().split(".");
1179
+ const osRelease = os5.release().split(".");
1180
1180
  if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
1181
1181
  return Number(osRelease[2]) >= 14931 ? 3 : 2;
1182
1182
  }
@@ -1327,14 +1327,14 @@ var require_templates = __commonJS({
1327
1327
  }
1328
1328
  return results;
1329
1329
  }
1330
- function buildStyle(chalk4, styles) {
1330
+ function buildStyle(chalk5, styles) {
1331
1331
  const enabled = {};
1332
1332
  for (const layer of styles) {
1333
1333
  for (const style of layer.styles) {
1334
1334
  enabled[style[0]] = layer.inverse ? null : style.slice(1);
1335
1335
  }
1336
1336
  }
1337
- let current = chalk4;
1337
+ let current = chalk5;
1338
1338
  for (const [styleName, styles2] of Object.entries(enabled)) {
1339
1339
  if (!Array.isArray(styles2)) {
1340
1340
  continue;
@@ -1346,7 +1346,7 @@ var require_templates = __commonJS({
1346
1346
  }
1347
1347
  return current;
1348
1348
  }
1349
- module.exports = (chalk4, temporary) => {
1349
+ module.exports = (chalk5, temporary) => {
1350
1350
  const styles = [];
1351
1351
  const chunks = [];
1352
1352
  let chunk = [];
@@ -1356,13 +1356,13 @@ var require_templates = __commonJS({
1356
1356
  } else if (style) {
1357
1357
  const string = chunk.join("");
1358
1358
  chunk = [];
1359
- chunks.push(styles.length === 0 ? string : buildStyle(chalk4, styles)(string));
1359
+ chunks.push(styles.length === 0 ? string : buildStyle(chalk5, styles)(string));
1360
1360
  styles.push({ inverse, styles: parseStyle(style) });
1361
1361
  } else if (close) {
1362
1362
  if (styles.length === 0) {
1363
1363
  throw new Error("Found extraneous } in Chalk template literal");
1364
1364
  }
1365
- chunks.push(buildStyle(chalk4, styles)(chunk.join("")));
1365
+ chunks.push(buildStyle(chalk5, styles)(chunk.join("")));
1366
1366
  chunk = [];
1367
1367
  styles.pop();
1368
1368
  } else {
@@ -1410,16 +1410,16 @@ var require_source = __commonJS({
1410
1410
  }
1411
1411
  };
1412
1412
  var chalkFactory = (options) => {
1413
- const chalk5 = {};
1414
- applyOptions(chalk5, options);
1415
- chalk5.template = (...arguments_) => chalkTag(chalk5.template, ...arguments_);
1416
- Object.setPrototypeOf(chalk5, Chalk.prototype);
1417
- Object.setPrototypeOf(chalk5.template, chalk5);
1418
- chalk5.template.constructor = () => {
1413
+ const chalk6 = {};
1414
+ applyOptions(chalk6, options);
1415
+ chalk6.template = (...arguments_) => chalkTag(chalk6.template, ...arguments_);
1416
+ Object.setPrototypeOf(chalk6, Chalk.prototype);
1417
+ Object.setPrototypeOf(chalk6.template, chalk6);
1418
+ chalk6.template.constructor = () => {
1419
1419
  throw new Error("`chalk.constructor()` is deprecated. Use `new chalk.Instance()` instead.");
1420
1420
  };
1421
- chalk5.template.Instance = ChalkClass;
1422
- return chalk5.template;
1421
+ chalk6.template.Instance = ChalkClass;
1422
+ return chalk6.template;
1423
1423
  };
1424
1424
  function Chalk(options) {
1425
1425
  return chalkFactory(options);
@@ -1530,7 +1530,7 @@ var require_source = __commonJS({
1530
1530
  return openAll + string + closeAll;
1531
1531
  };
1532
1532
  var template;
1533
- var chalkTag = (chalk5, ...strings) => {
1533
+ var chalkTag = (chalk6, ...strings) => {
1534
1534
  const [firstString] = strings;
1535
1535
  if (!isArray(firstString) || !isArray(firstString.raw)) {
1536
1536
  return strings.join(" ");
@@ -1546,14 +1546,14 @@ var require_source = __commonJS({
1546
1546
  if (template === void 0) {
1547
1547
  template = require_templates();
1548
1548
  }
1549
- return template(chalk5, parts.join(""));
1549
+ return template(chalk6, parts.join(""));
1550
1550
  };
1551
1551
  Object.defineProperties(Chalk.prototype, styles);
1552
- var chalk4 = Chalk();
1553
- chalk4.supportsColor = stdoutColor;
1554
- chalk4.stderr = Chalk({ level: stderrColor ? stderrColor.level : 0 });
1555
- chalk4.stderr.supportsColor = stderrColor;
1556
- module.exports = chalk4;
1552
+ var chalk5 = Chalk();
1553
+ chalk5.supportsColor = stdoutColor;
1554
+ chalk5.stderr = Chalk({ level: stderrColor ? stderrColor.level : 0 });
1555
+ chalk5.stderr.supportsColor = stderrColor;
1556
+ module.exports = chalk5;
1557
1557
  }
1558
1558
  });
1559
1559
 
@@ -2086,12 +2086,12 @@ var require_contractSpec = __commonJS({
2086
2086
  const type = raw.slice(0, firstColon).trim();
2087
2087
  const rest = raw.slice(firstColon + 1);
2088
2088
  const secondColon = rest.indexOf(":");
2089
- const path7 = (secondColon === -1 ? rest : rest.slice(0, secondColon)).trim();
2089
+ const path11 = (secondColon === -1 ? rest : rest.slice(0, secondColon)).trim();
2090
2090
  const description = (secondColon === -1 ? "" : rest.slice(secondColon + 1)).trim();
2091
2091
  if (!type) {
2092
2092
  throw new Error(`Invalid deliverable format: "${spec}". Expected "type:path:description" with non-empty type.`);
2093
2093
  }
2094
- if (!path7) {
2094
+ if (!path11) {
2095
2095
  throw new Error(`Invalid deliverable format: "${spec}". Expected "type:path:description" with non-empty path.`);
2096
2096
  }
2097
2097
  const typeLower = type.toLowerCase();
@@ -2100,7 +2100,7 @@ var require_contractSpec = __commonJS({
2100
2100
  }
2101
2101
  const deliverable = {
2102
2102
  type: typeLower,
2103
- path: path7,
2103
+ path: path11,
2104
2104
  ...description ? { description } : {}
2105
2105
  };
2106
2106
  return deliverable;
@@ -5421,61 +5421,6 @@ var require_parser = __commonJS({
5421
5421
  }
5422
5422
  return null;
5423
5423
  }
5424
- /**
5425
- * Find the line number of a rule in the YAML frontmatter
5426
- * @param content - The markdown content
5427
- * @param ruleId - The rule ID to find
5428
- * @param ruleType - The type of rule (always, never, prefer, context)
5429
- * @returns Line and column location or null if not found
5430
- */
5431
- static findRuleLocation(content, ruleId, ruleType) {
5432
- const lines = content.split("\n");
5433
- let inFrontmatter = false;
5434
- let inRulesSection = false;
5435
- let inRuleTypeSection = false;
5436
- for (let i = 0; i < lines.length; i++) {
5437
- const line = lines[i];
5438
- const trimmedLine = line.trim();
5439
- if (trimmedLine === "---") {
5440
- if (!inFrontmatter) {
5441
- inFrontmatter = true;
5442
- continue;
5443
- } else {
5444
- break;
5445
- }
5446
- }
5447
- if (!inFrontmatter)
5448
- continue;
5449
- if (trimmedLine === "rules:") {
5450
- inRulesSection = true;
5451
- continue;
5452
- }
5453
- if (inRulesSection && trimmedLine === `${ruleType}:`) {
5454
- inRuleTypeSection = true;
5455
- continue;
5456
- }
5457
- if (inRulesSection && line.match(/^[a-z]+:/) && !line.match(/^\s/)) {
5458
- inRulesSection = false;
5459
- inRuleTypeSection = false;
5460
- }
5461
- if (inRuleTypeSection) {
5462
- if (line.match(/^\s{2}[a-z]+:/) && !line.includes(`${ruleType}:`)) {
5463
- inRuleTypeSection = false;
5464
- continue;
5465
- }
5466
- if (line.includes(`id: ${ruleId}`)) {
5467
- if (line.match(/^\s*-\s+id:\s+/)) {
5468
- return { line: i + 1, column: 0 };
5469
- }
5470
- if (i > 0 && lines[i - 1].match(/^\s*-\s*$/)) {
5471
- return { line: i, column: 0 };
5472
- }
5473
- return { line: i + 1, column: 0 };
5474
- }
5475
- }
5476
- }
5477
- return null;
5478
- }
5479
5424
  };
5480
5425
  exports.BrainfileParser = BrainfileParser;
5481
5426
  }
@@ -5625,6 +5570,104 @@ ${yamlContent}---
5625
5570
  }
5626
5571
  });
5627
5572
 
5573
+ // ../core/dist/frontmatter.js
5574
+ var require_frontmatter = __commonJS({
5575
+ "../core/dist/frontmatter.js"(exports) {
5576
+ "use strict";
5577
+ var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
5578
+ if (k2 === void 0) k2 = k;
5579
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5580
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
5581
+ desc = { enumerable: true, get: function() {
5582
+ return m[k];
5583
+ } };
5584
+ }
5585
+ Object.defineProperty(o, k2, desc);
5586
+ }) : (function(o, m, k, k2) {
5587
+ if (k2 === void 0) k2 = k;
5588
+ o[k2] = m[k];
5589
+ }));
5590
+ var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? (function(o, v) {
5591
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
5592
+ }) : function(o, v) {
5593
+ o["default"] = v;
5594
+ });
5595
+ var __importStar = exports && exports.__importStar || /* @__PURE__ */ (function() {
5596
+ var ownKeys = function(o) {
5597
+ ownKeys = Object.getOwnPropertyNames || function(o2) {
5598
+ var ar = [];
5599
+ for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
5600
+ return ar;
5601
+ };
5602
+ return ownKeys(o);
5603
+ };
5604
+ return function(mod) {
5605
+ if (mod && mod.__esModule) return mod;
5606
+ var result = {};
5607
+ if (mod != null) {
5608
+ for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
5609
+ }
5610
+ __setModuleDefault(result, mod);
5611
+ return result;
5612
+ };
5613
+ })();
5614
+ Object.defineProperty(exports, "__esModule", { value: true });
5615
+ exports.parseFrontmatter = parseFrontmatter2;
5616
+ exports.serializeFrontmatter = serializeFrontmatter2;
5617
+ var yaml = __importStar(require_js_yaml());
5618
+ function parseFrontmatter2(content) {
5619
+ const lines = content.split("\n");
5620
+ if (!lines[0] || lines[0].trim() !== "---") {
5621
+ return null;
5622
+ }
5623
+ let endIndex = -1;
5624
+ for (let i = 1; i < lines.length; i++) {
5625
+ if (lines[i].trim() === "---") {
5626
+ endIndex = i;
5627
+ break;
5628
+ }
5629
+ }
5630
+ if (endIndex === -1) {
5631
+ return null;
5632
+ }
5633
+ const yamlContent = lines.slice(1, endIndex).join("\n");
5634
+ const bodyContent = lines.slice(endIndex + 1).join("\n");
5635
+ let parsed;
5636
+ try {
5637
+ parsed = yaml.load(yamlContent);
5638
+ } catch {
5639
+ return null;
5640
+ }
5641
+ if (!parsed || typeof parsed !== "object") {
5642
+ return null;
5643
+ }
5644
+ return {
5645
+ data: parsed,
5646
+ body: bodyContent.replace(/^\n/, "")
5647
+ };
5648
+ }
5649
+ function serializeFrontmatter2(data, body = "") {
5650
+ const yamlContent = yaml.dump(data, {
5651
+ indent: 2,
5652
+ lineWidth: -1,
5653
+ noRefs: true,
5654
+ sortKeys: false,
5655
+ quotingType: '"',
5656
+ forceQuotes: false
5657
+ });
5658
+ const parts = ["---\n", yamlContent, "---\n"];
5659
+ if (body.length > 0) {
5660
+ parts.push("\n");
5661
+ parts.push(body);
5662
+ if (!body.endsWith("\n")) {
5663
+ parts.push("\n");
5664
+ }
5665
+ }
5666
+ return parts.join("");
5667
+ }
5668
+ }
5669
+ });
5670
+
5628
5671
  // ../core/dist/validator.js
5629
5672
  var require_validator = __commonJS({
5630
5673
  "../core/dist/validator.js"(exports) {
@@ -5656,10 +5699,6 @@ var require_validator = __commonJS({
5656
5699
  errors.push(...columnErrors);
5657
5700
  });
5658
5701
  }
5659
- if (board.rules !== void 0) {
5660
- const rulesErrors = this.validateRules(board.rules, "rules");
5661
- errors.push(...rulesErrors);
5662
- }
5663
5702
  if (board.archive !== void 0) {
5664
5703
  if (!Array.isArray(board.archive)) {
5665
5704
  errors.push({ path: "archive", message: "Archive must be an array" });
@@ -5689,23 +5728,23 @@ var require_validator = __commonJS({
5689
5728
  * @param path - The path for error reporting
5690
5729
  * @returns Array of validation errors
5691
5730
  */
5692
- static validateColumn(column, path7) {
5731
+ static validateColumn(column, path11) {
5693
5732
  const errors = [];
5694
5733
  if (!column) {
5695
- errors.push({ path: path7, message: "Column is null or undefined" });
5734
+ errors.push({ path: path11, message: "Column is null or undefined" });
5696
5735
  return errors;
5697
5736
  }
5698
5737
  if (typeof column.id !== "string" || column.id.trim() === "") {
5699
- errors.push({ path: `${path7}.id`, message: "Column id must be a non-empty string" });
5738
+ errors.push({ path: `${path11}.id`, message: "Column id must be a non-empty string" });
5700
5739
  }
5701
5740
  if (typeof column.title !== "string" || column.title.trim() === "") {
5702
- errors.push({ path: `${path7}.title`, message: "Column title must be a non-empty string" });
5741
+ errors.push({ path: `${path11}.title`, message: "Column title must be a non-empty string" });
5703
5742
  }
5704
5743
  if (column.tasks !== void 0 && !Array.isArray(column.tasks)) {
5705
- errors.push({ path: `${path7}.tasks`, message: "Column tasks must be an array" });
5744
+ errors.push({ path: `${path11}.tasks`, message: "Column tasks must be an array" });
5706
5745
  } else if (Array.isArray(column.tasks)) {
5707
5746
  column.tasks.forEach((task, index) => {
5708
- const taskErrors = this.validateTask(task, `${path7}.tasks[${index}]`);
5747
+ const taskErrors = this.validateTask(task, `${path11}.tasks[${index}]`);
5709
5748
  errors.push(...taskErrors);
5710
5749
  });
5711
5750
  }
@@ -5717,23 +5756,23 @@ var require_validator = __commonJS({
5717
5756
  * @param path - The path for error reporting
5718
5757
  * @returns Array of validation errors
5719
5758
  */
5720
- static validateTask(task, path7) {
5759
+ static validateTask(task, path11) {
5721
5760
  const errors = [];
5722
5761
  if (!task) {
5723
- errors.push({ path: path7, message: "Task is null or undefined" });
5762
+ errors.push({ path: path11, message: "Task is null or undefined" });
5724
5763
  return errors;
5725
5764
  }
5726
5765
  if (typeof task.id !== "string" || task.id.trim() === "") {
5727
- errors.push({ path: `${path7}.id`, message: "Task id must be a non-empty string" });
5766
+ errors.push({ path: `${path11}.id`, message: "Task id must be a non-empty string" });
5728
5767
  }
5729
5768
  if (typeof task.title !== "string" || task.title.trim() === "") {
5730
- errors.push({ path: `${path7}.title`, message: "Task title must be a non-empty string" });
5769
+ errors.push({ path: `${path11}.title`, message: "Task title must be a non-empty string" });
5731
5770
  }
5732
5771
  if (task.priority !== void 0) {
5733
5772
  const validPriorities = ["low", "medium", "high", "critical"];
5734
5773
  if (!validPriorities.includes(task.priority)) {
5735
5774
  errors.push({
5736
- path: `${path7}.priority`,
5775
+ path: `${path11}.priority`,
5737
5776
  message: `Task priority must be one of: ${validPriorities.join(", ")}`
5738
5777
  });
5739
5778
  }
@@ -5742,23 +5781,23 @@ var require_validator = __commonJS({
5742
5781
  const validTemplates = ["bug", "feature", "refactor"];
5743
5782
  if (!validTemplates.includes(task.template)) {
5744
5783
  errors.push({
5745
- path: `${path7}.template`,
5784
+ path: `${path11}.template`,
5746
5785
  message: `Task template must be one of: ${validTemplates.join(", ")}`
5747
5786
  });
5748
5787
  }
5749
5788
  }
5750
5789
  if (task.tags !== void 0 && !Array.isArray(task.tags)) {
5751
- errors.push({ path: `${path7}.tags`, message: "Task tags must be an array" });
5790
+ errors.push({ path: `${path11}.tags`, message: "Task tags must be an array" });
5752
5791
  }
5753
5792
  if (task.relatedFiles !== void 0 && !Array.isArray(task.relatedFiles)) {
5754
- errors.push({ path: `${path7}.relatedFiles`, message: "Task relatedFiles must be an array" });
5793
+ errors.push({ path: `${path11}.relatedFiles`, message: "Task relatedFiles must be an array" });
5755
5794
  }
5756
5795
  if (task.subtasks !== void 0) {
5757
5796
  if (!Array.isArray(task.subtasks)) {
5758
- errors.push({ path: `${path7}.subtasks`, message: "Task subtasks must be an array" });
5797
+ errors.push({ path: `${path11}.subtasks`, message: "Task subtasks must be an array" });
5759
5798
  } else {
5760
5799
  task.subtasks.forEach((subtask, index) => {
5761
- const subtaskErrors = this.validateSubtask(subtask, `${path7}.subtasks[${index}]`);
5800
+ const subtaskErrors = this.validateSubtask(subtask, `${path11}.subtasks[${index}]`);
5762
5801
  errors.push(...subtaskErrors);
5763
5802
  });
5764
5803
  }
@@ -5771,67 +5810,20 @@ var require_validator = __commonJS({
5771
5810
  * @param path - The path for error reporting
5772
5811
  * @returns Array of validation errors
5773
5812
  */
5774
- static validateSubtask(subtask, path7) {
5813
+ static validateSubtask(subtask, path11) {
5775
5814
  const errors = [];
5776
5815
  if (!subtask) {
5777
- errors.push({ path: path7, message: "Subtask is null or undefined" });
5816
+ errors.push({ path: path11, message: "Subtask is null or undefined" });
5778
5817
  return errors;
5779
5818
  }
5780
5819
  if (typeof subtask.id !== "string" || subtask.id.trim() === "") {
5781
- errors.push({ path: `${path7}.id`, message: "Subtask id must be a non-empty string" });
5820
+ errors.push({ path: `${path11}.id`, message: "Subtask id must be a non-empty string" });
5782
5821
  }
5783
5822
  if (typeof subtask.title !== "string" || subtask.title.trim() === "") {
5784
- errors.push({ path: `${path7}.title`, message: "Subtask title must be a non-empty string" });
5823
+ errors.push({ path: `${path11}.title`, message: "Subtask title must be a non-empty string" });
5785
5824
  }
5786
5825
  if (typeof subtask.completed !== "boolean") {
5787
- errors.push({ path: `${path7}.completed`, message: "Subtask completed must be a boolean" });
5788
- }
5789
- return errors;
5790
- }
5791
- /**
5792
- * Validate Rules object
5793
- * @param rules - The rules to validate
5794
- * @param path - The path for error reporting
5795
- * @returns Array of validation errors
5796
- */
5797
- static validateRules(rules, path7) {
5798
- const errors = [];
5799
- if (!rules) {
5800
- errors.push({ path: path7, message: "Rules is null or undefined" });
5801
- return errors;
5802
- }
5803
- const ruleTypes = ["always", "never", "prefer", "context"];
5804
- ruleTypes.forEach((ruleType) => {
5805
- if (rules[ruleType] !== void 0) {
5806
- if (!Array.isArray(rules[ruleType])) {
5807
- errors.push({ path: `${path7}.${ruleType}`, message: `Rules ${ruleType} must be an array` });
5808
- } else {
5809
- rules[ruleType].forEach((rule, index) => {
5810
- const ruleErrors = this.validateRule(rule, `${path7}.${ruleType}[${index}]`);
5811
- errors.push(...ruleErrors);
5812
- });
5813
- }
5814
- }
5815
- });
5816
- return errors;
5817
- }
5818
- /**
5819
- * Validate a Rule object
5820
- * @param rule - The rule to validate
5821
- * @param path - The path for error reporting
5822
- * @returns Array of validation errors
5823
- */
5824
- static validateRule(rule, path7) {
5825
- const errors = [];
5826
- if (!rule) {
5827
- errors.push({ path: path7, message: "Rule is null or undefined" });
5828
- return errors;
5829
- }
5830
- if (typeof rule.id !== "number") {
5831
- errors.push({ path: `${path7}.id`, message: "Rule id must be a number" });
5832
- }
5833
- if (typeof rule.rule !== "string" || rule.rule.trim() === "") {
5834
- errors.push({ path: `${path7}.rule`, message: "Rule rule must be a non-empty string" });
5826
+ errors.push({ path: `${path11}.completed`, message: "Subtask completed must be a boolean" });
5835
5827
  }
5836
5828
  return errors;
5837
5829
  }
@@ -5886,10 +5878,6 @@ var require_validator = __commonJS({
5886
5878
  errors.push(...entryErrors);
5887
5879
  });
5888
5880
  }
5889
- if (journal.rules !== void 0) {
5890
- const rulesErrors = this.validateRules(journal.rules, "rules");
5891
- errors.push(...rulesErrors);
5892
- }
5893
5881
  return { valid: errors.length === 0, errors };
5894
5882
  }
5895
5883
  /**
@@ -5898,32 +5886,32 @@ var require_validator = __commonJS({
5898
5886
  * @param path - The path for error reporting
5899
5887
  * @returns Array of validation errors
5900
5888
  */
5901
- static validateJournalEntry(entry, path7) {
5889
+ static validateJournalEntry(entry, path11) {
5902
5890
  const errors = [];
5903
5891
  if (!entry) {
5904
- errors.push({ path: path7, message: "Entry is null or undefined" });
5892
+ errors.push({ path: path11, message: "Entry is null or undefined" });
5905
5893
  return errors;
5906
5894
  }
5907
5895
  if (typeof entry.id !== "string" || entry.id.trim() === "") {
5908
- errors.push({ path: `${path7}.id`, message: "Entry id must be a non-empty string" });
5896
+ errors.push({ path: `${path11}.id`, message: "Entry id must be a non-empty string" });
5909
5897
  }
5910
5898
  if (typeof entry.title !== "string" || entry.title.trim() === "") {
5911
- errors.push({ path: `${path7}.title`, message: "Entry title must be a non-empty string" });
5899
+ errors.push({ path: `${path11}.title`, message: "Entry title must be a non-empty string" });
5912
5900
  }
5913
5901
  if (typeof entry.createdAt !== "string" || entry.createdAt.trim() === "") {
5914
- errors.push({ path: `${path7}.createdAt`, message: "Entry createdAt must be an ISO 8601 timestamp string" });
5902
+ errors.push({ path: `${path11}.createdAt`, message: "Entry createdAt must be an ISO 8601 timestamp string" });
5915
5903
  }
5916
5904
  if (entry.content !== void 0 && typeof entry.content !== "string") {
5917
- errors.push({ path: `${path7}.content`, message: "Entry content must be a string" });
5905
+ errors.push({ path: `${path11}.content`, message: "Entry content must be a string" });
5918
5906
  }
5919
5907
  if (entry.summary !== void 0 && typeof entry.summary !== "string") {
5920
- errors.push({ path: `${path7}.summary`, message: "Entry summary must be a string" });
5908
+ errors.push({ path: `${path11}.summary`, message: "Entry summary must be a string" });
5921
5909
  }
5922
5910
  if (entry.mood !== void 0 && typeof entry.mood !== "string") {
5923
- errors.push({ path: `${path7}.mood`, message: "Entry mood must be a string" });
5911
+ errors.push({ path: `${path11}.mood`, message: "Entry mood must be a string" });
5924
5912
  }
5925
5913
  if (entry.tags !== void 0 && !Array.isArray(entry.tags)) {
5926
- errors.push({ path: `${path7}.tags`, message: "Entry tags must be an array" });
5914
+ errors.push({ path: `${path11}.tags`, message: "Entry tags must be an array" });
5927
5915
  }
5928
5916
  return errors;
5929
5917
  }
@@ -5974,10 +5962,13 @@ var require_linter = __commonJS({
5974
5962
  };
5975
5963
  })();
5976
5964
  Object.defineProperty(exports, "__esModule", { value: true });
5977
- exports.BrainfileLinter = void 0;
5965
+ exports.BrainfileLinter = exports.LEGACY_RULES_MESSAGE = void 0;
5978
5966
  var yaml = __importStar(require_js_yaml());
5979
5967
  var parser_1 = require_parser();
5980
5968
  var validator_1 = require_validator();
5969
+ var frontmatter_1 = require_frontmatter();
5970
+ exports.LEGACY_RULES_MESSAGE = "rules is removed (adr-2); fold into agent.instructions";
5971
+ var RULE_CATEGORY_ORDER = ["always", "never", "prefer", "context"];
5981
5972
  var BrainfileLinter = class {
5982
5973
  /**
5983
5974
  * Lint a brainfile.md content string
@@ -6004,6 +5995,21 @@ var require_linter = __commonJS({
6004
5995
  fixedContent = this.fixUnquotedStrings(content, quotableStrings);
6005
5996
  }
6006
5997
  }
5998
+ const legacyRules = this.findLegacyRules(fixedContent);
5999
+ if (legacyRules) {
6000
+ issues.push({
6001
+ type: "warning",
6002
+ message: exports.LEGACY_RULES_MESSAGE,
6003
+ line: legacyRules.line,
6004
+ fixable: true,
6005
+ code: "LEGACY_RULES"
6006
+ });
6007
+ if (options.autoFix) {
6008
+ const folded = this.foldLegacyRules(fixedContent);
6009
+ if (folded !== null)
6010
+ fixedContent = folded;
6011
+ }
6012
+ }
6007
6013
  const contentToValidate = options.autoFix && fixedContent !== content ? fixedContent : content;
6008
6014
  const yamlIssues = this.checkYAMLSyntax(contentToValidate);
6009
6015
  if (options.autoFix && fixedContent !== content) {
@@ -6064,6 +6070,62 @@ var require_linter = __commonJS({
6064
6070
  board: board || void 0
6065
6071
  };
6066
6072
  }
6073
+ /**
6074
+ * Locate a legacy top-level `rules:` block in the frontmatter.
6075
+ *
6076
+ * Deliberately textual rather than YAML-object based: this must report a
6077
+ * usable line number, and it must still fire on a file whose frontmatter
6078
+ * fails to `yaml.load` for some unrelated reason.
6079
+ */
6080
+ static findLegacyRules(content) {
6081
+ const lines = content.split("\n");
6082
+ if (!lines[0] || lines[0].trim() !== "---")
6083
+ return null;
6084
+ for (let i = 1; i < lines.length; i++) {
6085
+ if (lines[i].trim() === "---")
6086
+ return null;
6087
+ if (/^rules:\s*$/.test(lines[i]) || /^rules:\s+\S/.test(lines[i])) {
6088
+ return { line: i + 1 };
6089
+ }
6090
+ }
6091
+ return null;
6092
+ }
6093
+ /**
6094
+ * Fold a legacy `rules:` block into `agent.instructions` and remove it.
6095
+ *
6096
+ * Each rule becomes `"<category>: <text>"` (adr-2's stated shape), appended
6097
+ * in category order after any instructions already present. Returns null if
6098
+ * the frontmatter cannot be round-tripped, in which case the caller keeps
6099
+ * the original content and the warning stands unfixed.
6100
+ */
6101
+ static foldLegacyRules(content) {
6102
+ const doc = (0, frontmatter_1.parseFrontmatter)(content);
6103
+ if (!doc)
6104
+ return null;
6105
+ const data = doc.data;
6106
+ const rules = data.rules;
6107
+ if (!rules || typeof rules !== "object" || Array.isArray(rules))
6108
+ return null;
6109
+ const folded = [];
6110
+ for (const category of RULE_CATEGORY_ORDER) {
6111
+ const entries = rules[category];
6112
+ if (!Array.isArray(entries))
6113
+ continue;
6114
+ for (const entry of entries) {
6115
+ const text = typeof entry === "string" ? entry : typeof entry?.rule === "string" ? entry.rule : null;
6116
+ if (text && text.trim())
6117
+ folded.push(`${category}: ${text.trim()}`);
6118
+ }
6119
+ }
6120
+ delete data.rules;
6121
+ if (folded.length > 0) {
6122
+ const agent = data.agent && typeof data.agent === "object" && !Array.isArray(data.agent) ? data.agent : {};
6123
+ const existing = Array.isArray(agent.instructions) ? agent.instructions.filter((i) => typeof i === "string") : [];
6124
+ agent.instructions = [...existing, ...folded];
6125
+ data.agent = agent;
6126
+ }
6127
+ return (0, frontmatter_1.serializeFrontmatter)(data, doc.body);
6128
+ }
6067
6129
  /**
6068
6130
  * Check YAML syntax by attempting to parse
6069
6131
  */
@@ -6212,14 +6274,12 @@ var require_realtimeDiff = __commonJS({
6212
6274
  protocolVersion: previous.protocolVersion,
6213
6275
  schema: previous.schema,
6214
6276
  agent: previous.agent,
6215
- rules: previous.rules,
6216
6277
  statsConfig: previous.statsConfig
6217
6278
  }, {
6218
6279
  title: next.title,
6219
6280
  protocolVersion: next.protocolVersion,
6220
6281
  schema: next.schema,
6221
6282
  agent: next.agent,
6222
- rules: next.rules,
6223
6283
  statsConfig: next.statsConfig
6224
6284
  });
6225
6285
  const columnIdToPrev = indexColumns(previous.columns);
@@ -6678,8 +6738,6 @@ var require_operations = __commonJS({
6678
6738
  exports.updateSubtask = updateSubtask;
6679
6739
  exports.setSubtasksCompleted = setSubtasksCompleted;
6680
6740
  exports.setAllSubtasksCompleted = setAllSubtasksCompleted;
6681
- exports.addRule = addRule;
6682
- exports.deleteRule = deleteRule;
6683
6741
  exports.moveTasks = moveTasks;
6684
6742
  exports.patchTasks = patchTasks;
6685
6743
  exports.deleteTasks = deleteTasks;
@@ -7162,55 +7220,6 @@ var require_operations = __commonJS({
7162
7220
  };
7163
7221
  return { success: true, board: newBoard };
7164
7222
  }
7165
- function addRule(board, ruleType, ruleText) {
7166
- if (!ruleText || ruleText.trim() === "") {
7167
- return { success: false, error: "Rule text is required" };
7168
- }
7169
- const trimmedRule = ruleText.trim();
7170
- const existingRules = board.rules?.[ruleType] ?? [];
7171
- const nextId = existingRules.reduce((maxId, rule) => Math.max(maxId, rule.id), 0) + 1;
7172
- const newRule = {
7173
- id: nextId,
7174
- rule: trimmedRule
7175
- };
7176
- const updatedRulesForType = [...existingRules, newRule];
7177
- const updatedRules = {
7178
- ...board.rules ?? {},
7179
- [ruleType]: updatedRulesForType
7180
- };
7181
- return {
7182
- success: true,
7183
- board: {
7184
- ...board,
7185
- rules: updatedRules
7186
- }
7187
- };
7188
- }
7189
- function deleteRule(board, ruleType, ruleId) {
7190
- const rulesForType = board.rules?.[ruleType];
7191
- if (!rulesForType || rulesForType.length === 0) {
7192
- return { success: false, error: `Rule type ${ruleType} has no entries` };
7193
- }
7194
- const exists = rulesForType.some((rule) => rule.id === ruleId);
7195
- if (!exists) {
7196
- return { success: false, error: `Rule ${ruleId} not found in ${ruleType}` };
7197
- }
7198
- const updatedRulesForType = rulesForType.filter((rule) => rule.id !== ruleId);
7199
- const updatedRules = { ...board.rules ?? {} };
7200
- if (updatedRulesForType.length > 0) {
7201
- updatedRules[ruleType] = updatedRulesForType;
7202
- } else {
7203
- delete updatedRules[ruleType];
7204
- }
7205
- const hasAnyRules = Object.values(updatedRules).some((collection) => collection !== void 0 && collection.length > 0);
7206
- return {
7207
- success: true,
7208
- board: {
7209
- ...board,
7210
- rules: hasAnyRules ? updatedRules : void 0
7211
- }
7212
- };
7213
- }
7214
7223
  function moveTasks(board, taskIds, toColumnId) {
7215
7224
  const results = [];
7216
7225
  let currentBoard = board;
@@ -7434,8 +7443,8 @@ var require_discovery = __commonJS({
7434
7443
  exports.findPrimaryBrainfile = findPrimaryBrainfile;
7435
7444
  exports.findNearestBrainfile = findNearestBrainfile;
7436
7445
  exports.watchBrainfiles = watchBrainfiles;
7437
- var fs7 = __importStar(__require("fs"));
7438
- var path7 = __importStar(__require("path"));
7446
+ var fs11 = __importStar(__require("fs"));
7447
+ var path11 = __importStar(__require("path"));
7439
7448
  var parser_1 = require_parser();
7440
7449
  var query_1 = require_query();
7441
7450
  exports.BRAINFILE_PATTERNS = [
@@ -7471,7 +7480,7 @@ var require_discovery = __commonJS({
7471
7480
  "vendor"
7472
7481
  ];
7473
7482
  function isBrainfileName(filename) {
7474
- const name = path7.basename(filename).toLowerCase();
7483
+ const name = path11.basename(filename).toLowerCase();
7475
7484
  if (name === "brainfile.md" || name === ".brainfile.md" || name === ".bb.md") {
7476
7485
  return true;
7477
7486
  }
@@ -7481,11 +7490,11 @@ var require_discovery = __commonJS({
7481
7490
  return false;
7482
7491
  }
7483
7492
  function shouldExclude(filePath, excludeDirs) {
7484
- const parts = filePath.split(path7.sep);
7493
+ const parts = filePath.split(path11.sep);
7485
7494
  return parts.some((part) => excludeDirs.includes(part));
7486
7495
  }
7487
7496
  function extractBrainfileSuffix(filename) {
7488
- const name = path7.basename(filename).toLowerCase();
7497
+ const name = path11.basename(filename).toLowerCase();
7489
7498
  if (name.startsWith("brainfile.") && name.endsWith(".md") && name !== "brainfile.md") {
7490
7499
  const withoutPrefix = name.slice("brainfile.".length);
7491
7500
  const suffix = withoutPrefix.slice(0, -".md".length);
@@ -7505,10 +7514,10 @@ var require_discovery = __commonJS({
7505
7514
  }
7506
7515
  function parseFileMetadata(absolutePath, relativePath) {
7507
7516
  try {
7508
- const content = fs7.readFileSync(absolutePath, "utf-8");
7517
+ const content = fs11.readFileSync(absolutePath, "utf-8");
7509
7518
  const board = parser_1.BrainfileParser.parse(content);
7510
- const stats = fs7.statSync(absolutePath);
7511
- const filename = path7.basename(absolutePath);
7519
+ const stats = fs11.statSync(absolutePath);
7520
+ const filename = path11.basename(absolutePath);
7512
7521
  const isHidden = filename.startsWith(".");
7513
7522
  if (board) {
7514
7523
  return {
@@ -7542,10 +7551,10 @@ var require_discovery = __commonJS({
7542
7551
  return results;
7543
7552
  }
7544
7553
  try {
7545
- const entries = fs7.readdirSync(dir, { withFileTypes: true });
7554
+ const entries = fs11.readdirSync(dir, { withFileTypes: true });
7546
7555
  for (const entry of entries) {
7547
- const fullPath = path7.join(dir, entry.name);
7548
- const relativePath = path7.relative(rootDir, fullPath);
7556
+ const fullPath = path11.join(dir, entry.name);
7557
+ const relativePath = path11.relative(rootDir, fullPath);
7549
7558
  if (entry.isDirectory()) {
7550
7559
  if (shouldExclude(entry.name, options.excludeDirs)) {
7551
7560
  continue;
@@ -7577,11 +7586,11 @@ var require_discovery = __commonJS({
7577
7586
  maxDepth: options.maxDepth ?? 10,
7578
7587
  excludeDirs: options.excludeDirs ?? [...exports.EXCLUDE_DIRS]
7579
7588
  };
7580
- const absoluteRoot = path7.resolve(rootDir);
7589
+ const absoluteRoot = path11.resolve(rootDir);
7581
7590
  const files = findBrainfilesRecursive(absoluteRoot, absoluteRoot, opts);
7582
7591
  files.sort((a, b) => {
7583
- const aDepth = a.relativePath.split(path7.sep).length;
7584
- const bDepth = b.relativePath.split(path7.sep).length;
7592
+ const aDepth = a.relativePath.split(path11.sep).length;
7593
+ const bDepth = b.relativePath.split(path11.sep).length;
7585
7594
  if (aDepth !== bDepth) {
7586
7595
  return aDepth - bDepth;
7587
7596
  }
@@ -7595,17 +7604,17 @@ var require_discovery = __commonJS({
7595
7604
  };
7596
7605
  }
7597
7606
  function findPrimaryBrainfile(rootDir) {
7598
- const absoluteRoot = path7.resolve(rootDir);
7599
- const preferred = path7.join(absoluteRoot, ".brainfile", "brainfile.md");
7600
- if (fs7.existsSync(preferred)) {
7601
- const metadata = parseFileMetadata(preferred, path7.join(".brainfile", "brainfile.md"));
7607
+ const absoluteRoot = path11.resolve(rootDir);
7608
+ const preferred = path11.join(absoluteRoot, ".brainfile", "brainfile.md");
7609
+ if (fs11.existsSync(preferred)) {
7610
+ const metadata = parseFileMetadata(preferred, path11.join(".brainfile", "brainfile.md"));
7602
7611
  if (metadata)
7603
7612
  return metadata;
7604
7613
  }
7605
7614
  const priorityNames = ["brainfile.md", ".brainfile.md", ".bb.md"];
7606
7615
  for (const name of priorityNames) {
7607
- const fullPath = path7.join(absoluteRoot, name);
7608
- if (fs7.existsSync(fullPath)) {
7616
+ const fullPath = path11.join(absoluteRoot, name);
7617
+ if (fs11.existsSync(fullPath)) {
7609
7618
  const metadata = parseFileMetadata(fullPath, name);
7610
7619
  if (metadata) {
7611
7620
  return metadata;
@@ -7613,11 +7622,11 @@ var require_discovery = __commonJS({
7613
7622
  }
7614
7623
  }
7615
7624
  try {
7616
- const entries = fs7.readdirSync(absoluteRoot);
7625
+ const entries = fs11.readdirSync(absoluteRoot);
7617
7626
  for (const entry of entries) {
7618
7627
  if (isBrainfileName(entry) && !priorityNames.includes(entry.toLowerCase())) {
7619
- const fullPath = path7.join(absoluteRoot, entry);
7620
- const stats = fs7.statSync(fullPath);
7628
+ const fullPath = path11.join(absoluteRoot, entry);
7629
+ const stats = fs11.statSync(fullPath);
7621
7630
  if (stats.isFile()) {
7622
7631
  const metadata = parseFileMetadata(fullPath, entry);
7623
7632
  if (metadata) {
@@ -7631,14 +7640,14 @@ var require_discovery = __commonJS({
7631
7640
  return null;
7632
7641
  }
7633
7642
  function findNearestBrainfile(startDir) {
7634
- let currentDir = path7.resolve(startDir || process.cwd());
7635
- const root = path7.parse(currentDir).root;
7643
+ let currentDir = path11.resolve(startDir || process.cwd());
7644
+ const root = path11.parse(currentDir).root;
7636
7645
  while (currentDir !== root) {
7637
7646
  const found = findPrimaryBrainfile(currentDir);
7638
7647
  if (found) {
7639
7648
  return found;
7640
7649
  }
7641
- const parentDir = path7.dirname(currentDir);
7650
+ const parentDir = path11.dirname(currentDir);
7642
7651
  if (parentDir === currentDir) {
7643
7652
  break;
7644
7653
  }
@@ -7651,11 +7660,11 @@ var require_discovery = __commonJS({
7651
7660
  return null;
7652
7661
  }
7653
7662
  function watchBrainfiles(rootDir, callback, onError) {
7654
- const absoluteRoot = path7.resolve(rootDir);
7663
+ const absoluteRoot = path11.resolve(rootDir);
7655
7664
  let watcher = null;
7656
7665
  let active = false;
7657
7666
  try {
7658
- const stats = fs7.statSync(absoluteRoot);
7667
+ const stats = fs11.statSync(absoluteRoot);
7659
7668
  if (!stats.isDirectory()) {
7660
7669
  return {
7661
7670
  success: false,
@@ -7684,13 +7693,13 @@ var require_discovery = __commonJS({
7684
7693
  };
7685
7694
  }
7686
7695
  try {
7687
- watcher = fs7.watch(absoluteRoot, (eventType, filename) => {
7696
+ watcher = fs11.watch(absoluteRoot, (eventType, filename) => {
7688
7697
  if (!active || !filename || !isBrainfileName(filename))
7689
7698
  return;
7690
- const fullPath = path7.join(absoluteRoot, filename);
7699
+ const fullPath = path11.join(absoluteRoot, filename);
7691
7700
  try {
7692
7701
  if (eventType === "rename") {
7693
- if (fs7.existsSync(fullPath)) {
7702
+ if (fs11.existsSync(fullPath)) {
7694
7703
  const metadata = parseFileMetadata(fullPath, filename);
7695
7704
  if (metadata) {
7696
7705
  callback("add", metadata);
@@ -7797,7 +7806,7 @@ var require_files = __commonJS({
7797
7806
  };
7798
7807
  })();
7799
7808
  Object.defineProperty(exports, "__esModule", { value: true });
7800
- exports.DOT_BRAINFILE_GITIGNORE_BASENAME = exports.BRAINFILE_STATE_BASENAME = exports.BRAINFILE_BASENAME = exports.DOT_BRAINFILE_DIRNAME = void 0;
7809
+ exports.DOT_BRAINFILE_STATE_IGNORE_ENTRY = exports.DOT_BRAINFILE_STATE_DIRNAME = exports.DOT_BRAINFILE_GITIGNORE_BASENAME = exports.BRAINFILE_STATE_BASENAME = exports.BRAINFILE_BASENAME = exports.DOT_BRAINFILE_DIRNAME = void 0;
7801
7810
  exports.findBrainfile = findBrainfile2;
7802
7811
  exports.resolveBrainfilePath = resolveBrainfilePath2;
7803
7812
  exports.getBrainfileStateDir = getBrainfileStateDir;
@@ -7805,45 +7814,53 @@ var require_files = __commonJS({
7805
7814
  exports.getDotBrainfileGitignorePath = getDotBrainfileGitignorePath;
7806
7815
  exports.ensureDotBrainfileDir = ensureDotBrainfileDir;
7807
7816
  exports.ensureDotBrainfileGitignore = ensureDotBrainfileGitignore;
7808
- var fs7 = __importStar(__require("fs"));
7809
- var path7 = __importStar(__require("path"));
7817
+ exports.atomicWriteFileSync = atomicWriteFileSync;
7818
+ var fs11 = __importStar(__require("fs"));
7819
+ var path11 = __importStar(__require("path"));
7810
7820
  exports.DOT_BRAINFILE_DIRNAME = ".brainfile";
7811
7821
  exports.BRAINFILE_BASENAME = "brainfile.md";
7812
7822
  exports.BRAINFILE_STATE_BASENAME = "state.json";
7813
7823
  exports.DOT_BRAINFILE_GITIGNORE_BASENAME = ".gitignore";
7824
+ exports.DOT_BRAINFILE_STATE_DIRNAME = "state";
7825
+ exports.DOT_BRAINFILE_STATE_IGNORE_ENTRY = "state/";
7814
7826
  function toAbsolute(p) {
7815
- return path7.isAbsolute(p) ? p : path7.resolve(p);
7827
+ return path11.isAbsolute(p) ? p : path11.resolve(p);
7816
7828
  }
7817
7829
  function existsFile2(p) {
7818
7830
  try {
7819
- return fs7.statSync(p).isFile();
7831
+ return fs11.statSync(p).isFile();
7820
7832
  } catch {
7821
7833
  return false;
7822
7834
  }
7823
7835
  }
7824
- function findBrainfile2(startDir = process.cwd()) {
7825
- let currentDir = path7.resolve(startDir);
7826
- const root = path7.parse(currentDir).root;
7836
+ function isGitBoundary(dir) {
7837
+ return fs11.existsSync(path11.join(dir, ".git"));
7838
+ }
7839
+ function findBrainfile2(startDir = process.cwd(), options = {}) {
7840
+ let currentDir = path11.resolve(startDir);
7841
+ const root = path11.parse(currentDir).root;
7827
7842
  while (true) {
7828
- const preferred = path7.join(currentDir, exports.DOT_BRAINFILE_DIRNAME, exports.BRAINFILE_BASENAME);
7843
+ const preferred = path11.join(currentDir, exports.DOT_BRAINFILE_DIRNAME, exports.BRAINFILE_BASENAME);
7829
7844
  if (existsFile2(preferred)) {
7830
7845
  return { absolutePath: preferred, projectRoot: currentDir, kind: "dotdir" };
7831
7846
  }
7832
- const legacy = path7.join(currentDir, exports.BRAINFILE_BASENAME);
7847
+ const legacy = path11.join(currentDir, exports.BRAINFILE_BASENAME);
7833
7848
  if (existsFile2(legacy)) {
7834
7849
  return { absolutePath: legacy, projectRoot: currentDir, kind: "root" };
7835
7850
  }
7836
- const hiddenLegacy = path7.join(currentDir, ".brainfile.md");
7851
+ const hiddenLegacy = path11.join(currentDir, ".brainfile.md");
7837
7852
  if (existsFile2(hiddenLegacy)) {
7838
7853
  return { absolutePath: hiddenLegacy, projectRoot: currentDir, kind: "hidden" };
7839
7854
  }
7840
- const bbLegacy = path7.join(currentDir, ".bb.md");
7855
+ const bbLegacy = path11.join(currentDir, ".bb.md");
7841
7856
  if (existsFile2(bbLegacy)) {
7842
7857
  return { absolutePath: bbLegacy, projectRoot: currentDir, kind: "bb" };
7843
7858
  }
7859
+ if (options.stopAtGitRoot && isGitBoundary(currentDir))
7860
+ break;
7844
7861
  if (currentDir === root)
7845
7862
  break;
7846
- const parent = path7.dirname(currentDir);
7863
+ const parent = path11.dirname(currentDir);
7847
7864
  if (parent === currentDir)
7848
7865
  break;
7849
7866
  currentDir = parent;
@@ -7851,192 +7868,99 @@ var require_files = __commonJS({
7851
7868
  return null;
7852
7869
  }
7853
7870
  function resolveBrainfilePath2(options = {}) {
7854
- const startDir = options.startDir ? path7.resolve(options.startDir) : process.cwd();
7871
+ const startDir = options.startDir ? path11.resolve(options.startDir) : process.cwd();
7855
7872
  const filePath = options.filePath;
7856
7873
  const isDefaultPlaceholder = filePath === void 0 || filePath === exports.BRAINFILE_BASENAME || filePath === `./${exports.BRAINFILE_BASENAME}`;
7857
7874
  if (isDefaultPlaceholder) {
7858
- const found = findBrainfile2(startDir);
7875
+ const found = findBrainfile2(startDir, { stopAtGitRoot: options.stopAtGitRoot });
7859
7876
  if (found)
7860
7877
  return found.absolutePath;
7861
7878
  return toAbsolute(filePath ?? exports.BRAINFILE_BASENAME);
7862
7879
  }
7863
- return toAbsolute(path7.isAbsolute(filePath) ? filePath : path7.resolve(startDir, filePath));
7880
+ return toAbsolute(path11.isAbsolute(filePath) ? filePath : path11.resolve(startDir, filePath));
7864
7881
  }
7865
7882
  function getBrainfileStateDir(brainfilePath) {
7866
7883
  const abs = toAbsolute(brainfilePath);
7867
- const dir = path7.dirname(abs);
7868
- if (path7.basename(dir) === exports.DOT_BRAINFILE_DIRNAME)
7884
+ const dir = path11.dirname(abs);
7885
+ if (path11.basename(dir) === exports.DOT_BRAINFILE_DIRNAME)
7869
7886
  return dir;
7870
- return path7.join(dir, exports.DOT_BRAINFILE_DIRNAME);
7887
+ return path11.join(dir, exports.DOT_BRAINFILE_DIRNAME);
7871
7888
  }
7872
7889
  function getBrainfileStatePath(brainfilePath) {
7873
- return path7.join(getBrainfileStateDir(brainfilePath), exports.BRAINFILE_STATE_BASENAME);
7890
+ return path11.join(getBrainfileStateDir(brainfilePath), exports.BRAINFILE_STATE_BASENAME);
7874
7891
  }
7875
7892
  function getDotBrainfileGitignorePath(brainfilePath) {
7876
- return path7.join(getBrainfileStateDir(brainfilePath), exports.DOT_BRAINFILE_GITIGNORE_BASENAME);
7893
+ return path11.join(getBrainfileStateDir(brainfilePath), exports.DOT_BRAINFILE_GITIGNORE_BASENAME);
7877
7894
  }
7878
7895
  function ensureDotBrainfileDir(brainfilePath) {
7879
7896
  const dir = getBrainfileStateDir(brainfilePath);
7880
- fs7.mkdirSync(dir, { recursive: true });
7897
+ fs11.mkdirSync(dir, { recursive: true });
7881
7898
  return dir;
7882
7899
  }
7883
7900
  function ensureDotBrainfileGitignore(brainfilePath) {
7884
7901
  ensureDotBrainfileDir(brainfilePath);
7885
7902
  const gitignorePath = getDotBrainfileGitignorePath(brainfilePath);
7886
- const existing = fs7.existsSync(gitignorePath) ? fs7.readFileSync(gitignorePath, "utf-8") : "";
7887
- const filtered = existing.split(/\r?\n/).filter((line) => line.trim() !== exports.BRAINFILE_STATE_BASENAME).join("\n").trimEnd();
7888
- fs7.writeFileSync(gitignorePath, filtered.length > 0 ? `${filtered}
7903
+ const existing = fs11.existsSync(gitignorePath) ? fs11.readFileSync(gitignorePath, "utf-8") : "";
7904
+ const lines = existing.split(/\r?\n/).filter((line) => line.trim() !== exports.BRAINFILE_STATE_BASENAME);
7905
+ const hasStateDir = lines.some((line) => line.trim() === exports.DOT_BRAINFILE_STATE_IGNORE_ENTRY);
7906
+ if (!hasStateDir) {
7907
+ lines.push(exports.DOT_BRAINFILE_STATE_IGNORE_ENTRY);
7908
+ }
7909
+ const filtered = lines.join("\n").trim();
7910
+ fs11.writeFileSync(gitignorePath, filtered.length > 0 ? `${filtered}
7889
7911
  ` : "", "utf-8");
7890
7912
  }
7913
+ function atomicWriteFileSync(filePath, content) {
7914
+ const abs = toAbsolute(filePath);
7915
+ const dir = path11.dirname(abs);
7916
+ fs11.mkdirSync(dir, { recursive: true });
7917
+ const tmp = path11.join(dir, `.${path11.basename(abs)}.tmp-${process.pid}-${Date.now()}`);
7918
+ try {
7919
+ fs11.writeFileSync(tmp, content, "utf-8");
7920
+ fs11.renameSync(tmp, abs);
7921
+ } catch (err) {
7922
+ try {
7923
+ if (fs11.existsSync(tmp))
7924
+ fs11.unlinkSync(tmp);
7925
+ } catch {
7926
+ }
7927
+ throw err;
7928
+ }
7929
+ }
7891
7930
  }
7892
7931
  });
7893
7932
 
7894
- // ../core/dist/formatters.js
7895
- var require_formatters = __commonJS({
7896
- "../core/dist/formatters.js"(exports) {
7933
+ // ../core/dist/taskContent.js
7934
+ var require_taskContent = __commonJS({
7935
+ "../core/dist/taskContent.js"(exports) {
7897
7936
  "use strict";
7898
7937
  Object.defineProperty(exports, "__esModule", { value: true });
7899
- exports.formatTaskForGitHub = formatTaskForGitHub;
7900
- exports.formatTaskForLinear = formatTaskForLinear;
7901
- function formatTaskForGitHub(task, options = {}) {
7902
- const { includeMeta = true, includeSubtasks = true, includeRelatedFiles = true, resolvedBy, resolvedByPR, fromColumn, boardTitle, extraLabels = [], includeTaskId = true } = options;
7903
- const title = includeTaskId ? `[${task.id}] ${task.title}` : task.title;
7904
- const sections = [];
7905
- if (task.description) {
7906
- sections.push(task.description);
7907
- }
7908
- if (includeSubtasks && task.subtasks && task.subtasks.length > 0) {
7909
- sections.push(formatSubtasksMarkdown(task.subtasks));
7910
- }
7911
- if (includeMeta) {
7912
- const meta = formatMetadataSection(task, { fromColumn, boardTitle });
7913
- if (meta) {
7914
- sections.push(meta);
7915
- }
7916
- }
7917
- if (includeRelatedFiles && task.relatedFiles && task.relatedFiles.length > 0) {
7918
- sections.push(formatRelatedFilesSection(task.relatedFiles));
7919
- }
7920
- if (resolvedBy || resolvedByPR) {
7921
- sections.push(formatResolutionSection(resolvedBy, resolvedByPR));
7922
- }
7923
- sections.push("---\n*Archived from brainfile.md*");
7924
- const labels = [...task.tags || [], ...extraLabels];
7925
- if (task.priority) {
7926
- labels.push(`priority:${task.priority}`);
7938
+ exports.parseTaskContent = parseTaskContent;
7939
+ exports.serializeTaskContent = serializeTaskContent;
7940
+ var frontmatter_1 = require_frontmatter();
7941
+ function parseTaskContent(content) {
7942
+ const parsed = (0, frontmatter_1.parseFrontmatter)(content);
7943
+ if (!parsed) {
7944
+ return null;
7927
7945
  }
7928
- if (task.template) {
7929
- labels.push(task.template);
7946
+ const task = parsed.data;
7947
+ if (!task.id || !task.title) {
7948
+ return null;
7930
7949
  }
7931
7950
  return {
7932
- title,
7933
- body: sections.join("\n\n"),
7934
- labels: labels.length > 0 ? labels : void 0,
7935
- state: "closed"
7951
+ task,
7952
+ body: parsed.body
7936
7953
  };
7937
7954
  }
7938
- function mapPriorityToLinear(priority) {
7939
- switch (priority) {
7940
- case "critical":
7941
- return 1;
7942
- case "high":
7943
- return 2;
7944
- case "medium":
7945
- return 3;
7946
- case "low":
7947
- return 4;
7948
- default:
7949
- return void 0;
7950
- }
7951
- }
7952
- function formatTaskForLinear(task, options = {}) {
7953
- const { includeMeta = true, includeSubtasks = true, includeRelatedFiles = true, resolvedBy, resolvedByPR, fromColumn, boardTitle, stateName = "Done", includeTaskId = false } = options;
7954
- const title = includeTaskId ? `[${task.id}] ${task.title}` : task.title;
7955
- const sections = [];
7956
- if (task.description) {
7957
- sections.push(task.description);
7958
- }
7959
- if (includeSubtasks && task.subtasks && task.subtasks.length > 0) {
7960
- sections.push(formatSubtasksMarkdown(task.subtasks));
7961
- }
7962
- if (includeMeta) {
7963
- const meta = formatMetadataSection(task, { fromColumn, boardTitle });
7964
- if (meta) {
7965
- sections.push(meta);
7966
- }
7967
- }
7968
- if (includeRelatedFiles && task.relatedFiles && task.relatedFiles.length > 0) {
7969
- sections.push(formatRelatedFilesSection(task.relatedFiles));
7970
- }
7971
- if (resolvedBy || resolvedByPR) {
7972
- sections.push(formatResolutionSection(resolvedBy, resolvedByPR));
7973
- }
7974
- sections.push("---\n*Archived from brainfile.md*");
7975
- return {
7976
- title,
7977
- description: sections.join("\n\n"),
7978
- priority: mapPriorityToLinear(task.priority),
7979
- labelNames: task.tags && task.tags.length > 0 ? task.tags : void 0,
7980
- stateName
7981
- };
7982
- }
7983
- function formatSubtasksMarkdown(subtasks) {
7984
- const items = subtasks.map((st) => `- [${st.completed ? "x" : " "}] ${st.title}`);
7985
- return `## Subtasks
7986
-
7987
- ${items.join("\n")}`;
7988
- }
7989
- function formatMetadataSection(task, context) {
7990
- const lines = [];
7991
- if (context.boardTitle) {
7992
- lines.push(`**Board:** ${context.boardTitle}`);
7993
- }
7994
- if (context.fromColumn) {
7995
- lines.push(`**Column:** ${context.fromColumn}`);
7996
- }
7997
- if (task.priority) {
7998
- lines.push(`**Priority:** ${task.priority}`);
7999
- }
8000
- if (task.assignee) {
8001
- lines.push(`**Assignee:** ${task.assignee}`);
8002
- }
8003
- if (task.dueDate) {
8004
- lines.push(`**Due Date:** ${task.dueDate}`);
8005
- }
8006
- if (task.createdAt) {
8007
- lines.push(`**Created:** ${task.createdAt}`);
8008
- }
8009
- if (lines.length === 0) {
8010
- return null;
8011
- }
8012
- return `## Details
8013
-
8014
- ${lines.join("\n")}`;
8015
- }
8016
- function formatRelatedFilesSection(files) {
8017
- const items = files.map((f) => `- \`${f}\``);
8018
- return `## Related Files
8019
-
8020
- ${items.join("\n")}`;
8021
- }
8022
- function formatResolutionSection(resolvedBy, resolvedByPR) {
8023
- const lines = ["## Resolution"];
8024
- if (resolvedByPR) {
8025
- lines.push(`
8026
- **Pull Request:** ${resolvedByPR}`);
8027
- }
8028
- if (resolvedBy) {
8029
- lines.push(`
8030
- **Commit:** ${resolvedBy}`);
8031
- }
8032
- return lines.join("");
7955
+ function serializeTaskContent(task, body = "") {
7956
+ return (0, frontmatter_1.serializeFrontmatter)(task, body);
8033
7957
  }
8034
7958
  }
8035
7959
  });
8036
7960
 
8037
- // ../core/dist/frontmatter.js
8038
- var require_frontmatter = __commonJS({
8039
- "../core/dist/frontmatter.js"(exports) {
7961
+ // ../core/dist/taskFile.js
7962
+ var require_taskFile = __commonJS({
7963
+ "../core/dist/taskFile.js"(exports) {
8040
7964
  "use strict";
8041
7965
  var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
8042
7966
  if (k2 === void 0) k2 = k;
@@ -8076,89 +8000,89 @@ var require_frontmatter = __commonJS({
8076
8000
  };
8077
8001
  })();
8078
8002
  Object.defineProperty(exports, "__esModule", { value: true });
8079
- exports.parseFrontmatter = parseFrontmatter;
8080
- exports.serializeFrontmatter = serializeFrontmatter;
8081
- var yaml = __importStar(require_js_yaml());
8082
- function parseFrontmatter(content) {
8083
- const lines = content.split("\n");
8084
- if (!lines[0] || lines[0].trim() !== "---") {
8085
- return null;
8003
+ exports.serializeTaskContent = exports.parseTaskContent = void 0;
8004
+ exports.readTaskFile = readTaskFile;
8005
+ exports.writeTaskFile = writeTaskFile;
8006
+ exports.readTasksDir = readTasksDir;
8007
+ exports.taskFileName = taskFileName;
8008
+ var fs11 = __importStar(__require("fs"));
8009
+ var path11 = __importStar(__require("path"));
8010
+ var taskContent_1 = require_taskContent();
8011
+ var taskContent_2 = require_taskContent();
8012
+ Object.defineProperty(exports, "parseTaskContent", { enumerable: true, get: function() {
8013
+ return taskContent_2.parseTaskContent;
8014
+ } });
8015
+ Object.defineProperty(exports, "serializeTaskContent", { enumerable: true, get: function() {
8016
+ return taskContent_2.serializeTaskContent;
8017
+ } });
8018
+ function isUnsafeTaskId(taskId) {
8019
+ if (!taskId || taskId.trim() === "") {
8020
+ return true;
8086
8021
  }
8087
- let endIndex = -1;
8088
- for (let i = 1; i < lines.length; i++) {
8089
- if (lines[i].trim() === "---") {
8090
- endIndex = i;
8091
- break;
8092
- }
8022
+ const trimmed = taskId.trim();
8023
+ if (trimmed.includes("/") || trimmed.includes("\\") || trimmed.includes("..")) {
8024
+ return true;
8093
8025
  }
8094
- if (endIndex === -1) {
8095
- return null;
8026
+ if (path11.isAbsolute(trimmed)) {
8027
+ return true;
8096
8028
  }
8097
- const yamlContent = lines.slice(1, endIndex).join("\n");
8098
- const bodyContent = lines.slice(endIndex + 1).join("\n");
8099
- let parsed;
8029
+ return path11.basename(trimmed) !== trimmed;
8030
+ }
8031
+ function readTaskFile(filePath) {
8032
+ let content;
8100
8033
  try {
8101
- parsed = yaml.load(yamlContent);
8034
+ content = fs11.readFileSync(filePath, "utf-8");
8102
8035
  } catch {
8103
8036
  return null;
8104
8037
  }
8105
- if (!parsed || typeof parsed !== "object") {
8038
+ const parsed = (0, taskContent_1.parseTaskContent)(content);
8039
+ if (!parsed) {
8106
8040
  return null;
8107
8041
  }
8108
8042
  return {
8109
- data: parsed,
8110
- body: bodyContent.replace(/^\n/, "")
8043
+ task: parsed.task,
8044
+ body: parsed.body,
8045
+ filePath: path11.resolve(filePath)
8111
8046
  };
8112
8047
  }
8113
- function serializeFrontmatter(data, body = "") {
8114
- const yamlContent = yaml.dump(data, {
8115
- indent: 2,
8116
- lineWidth: -1,
8117
- noRefs: true,
8118
- sortKeys: false,
8119
- quotingType: '"',
8120
- forceQuotes: false
8121
- });
8122
- const parts = ["---\n", yamlContent, "---\n"];
8123
- if (body.length > 0) {
8124
- parts.push("\n");
8125
- parts.push(body);
8126
- if (!body.endsWith("\n")) {
8127
- parts.push("\n");
8048
+ function writeTaskFile(filePath, task, body = "") {
8049
+ const dir = path11.dirname(filePath);
8050
+ fs11.mkdirSync(dir, { recursive: true });
8051
+ const content = (0, taskContent_1.serializeTaskContent)(task, body);
8052
+ fs11.writeFileSync(filePath, content, "utf-8");
8053
+ }
8054
+ function readTasksDir(dirPath) {
8055
+ let entries;
8056
+ try {
8057
+ entries = fs11.readdirSync(dirPath, { withFileTypes: true });
8058
+ } catch {
8059
+ return [];
8060
+ }
8061
+ const docs = [];
8062
+ for (const entry of entries) {
8063
+ if (!entry.isFile() || !entry.name.endsWith(".md")) {
8064
+ continue;
8065
+ }
8066
+ const filePath = path11.join(dirPath, entry.name);
8067
+ const doc = readTaskFile(filePath);
8068
+ if (doc) {
8069
+ docs.push(doc);
8128
8070
  }
8129
8071
  }
8130
- return parts.join("");
8072
+ return docs;
8131
8073
  }
8132
- }
8133
- });
8134
-
8135
- // ../core/dist/boardContent.js
8136
- var require_boardContent = __commonJS({
8137
- "../core/dist/boardContent.js"(exports) {
8138
- "use strict";
8139
- Object.defineProperty(exports, "__esModule", { value: true });
8140
- exports.parseBoardConfig = parseBoardConfig;
8141
- exports.serializeBoardConfig = serializeBoardConfig;
8142
- var frontmatter_1 = require_frontmatter();
8143
- function parseBoardConfig(content) {
8144
- const parsed = (0, frontmatter_1.parseFrontmatter)(content);
8145
- if (!parsed) {
8146
- return null;
8074
+ function taskFileName(taskId) {
8075
+ if (isUnsafeTaskId(taskId)) {
8076
+ throw new Error(`Invalid task ID: ${taskId}`);
8147
8077
  }
8148
- return {
8149
- config: parsed.data,
8150
- body: parsed.body
8151
- };
8152
- }
8153
- function serializeBoardConfig(config, body = "") {
8154
- return (0, frontmatter_1.serializeFrontmatter)(config, body);
8078
+ return `${taskId.trim()}.md`;
8155
8079
  }
8156
8080
  }
8157
8081
  });
8158
8082
 
8159
- // ../core/dist/boardFile.js
8160
- var require_boardFile = __commonJS({
8161
- "../core/dist/boardFile.js"(exports) {
8083
+ // ../core/dist/ledger.js
8084
+ var require_ledger2 = __commonJS({
8085
+ "../core/dist/ledger.js"(exports) {
8162
8086
  "use strict";
8163
8087
  var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
8164
8088
  if (k2 === void 0) k2 = k;
@@ -8198,256 +8122,25 @@ var require_boardFile = __commonJS({
8198
8122
  };
8199
8123
  })();
8200
8124
  Object.defineProperty(exports, "__esModule", { value: true });
8201
- exports.serializeBoardConfig = exports.parseBoardConfig = void 0;
8202
- exports.readBoardConfig = readBoardConfig;
8203
- exports.writeBoardConfig = writeBoardConfig;
8204
- var fs7 = __importStar(__require("fs"));
8205
- var path7 = __importStar(__require("path"));
8206
- var boardContent_1 = require_boardContent();
8207
- var boardContent_2 = require_boardContent();
8208
- Object.defineProperty(exports, "parseBoardConfig", { enumerable: true, get: function() {
8209
- return boardContent_2.parseBoardConfig;
8210
- } });
8211
- Object.defineProperty(exports, "serializeBoardConfig", { enumerable: true, get: function() {
8212
- return boardContent_2.serializeBoardConfig;
8213
- } });
8214
- function readBoardConfig(filePath) {
8215
- let content;
8216
- try {
8217
- content = fs7.readFileSync(filePath, "utf-8");
8218
- } catch {
8219
- return null;
8220
- }
8221
- const parsed = (0, boardContent_1.parseBoardConfig)(content);
8222
- if (!parsed) {
8223
- return null;
8224
- }
8225
- return {
8226
- config: parsed.config,
8227
- body: parsed.body,
8228
- filePath: path7.resolve(filePath)
8229
- };
8125
+ exports.normalizePathValue = normalizePathValue;
8126
+ exports.isLedgerContractStatus = isLedgerContractStatus;
8127
+ exports.buildLedgerRecord = buildLedgerRecord;
8128
+ exports.appendLedgerRecord = appendLedgerRecord;
8129
+ exports.readLedger = readLedger;
8130
+ exports.queryLedger = queryLedger;
8131
+ exports.getFileHistory = getFileHistory;
8132
+ exports.getTaskContext = getTaskContext;
8133
+ var fs11 = __importStar(__require("fs"));
8134
+ var path11 = __importStar(__require("path"));
8135
+ var taskFile_1 = require_taskFile();
8136
+ var ledger_1 = require_ledger();
8137
+ var LEDGER_FILE_NAME = "ledger.jsonl";
8138
+ var LEGACY_WARNING_TRACKER = /* @__PURE__ */ new Set();
8139
+ function getLedgerPath(logsDir) {
8140
+ return path11.join(logsDir, LEDGER_FILE_NAME);
8230
8141
  }
8231
- function writeBoardConfig(filePath, config, body = "") {
8232
- const dir = path7.dirname(filePath);
8233
- fs7.mkdirSync(dir, { recursive: true });
8234
- const content = (0, boardContent_1.serializeBoardConfig)(config, body);
8235
- fs7.writeFileSync(filePath, content, "utf-8");
8236
- }
8237
- }
8238
- });
8239
-
8240
- // ../core/dist/taskContent.js
8241
- var require_taskContent = __commonJS({
8242
- "../core/dist/taskContent.js"(exports) {
8243
- "use strict";
8244
- Object.defineProperty(exports, "__esModule", { value: true });
8245
- exports.parseTaskContent = parseTaskContent;
8246
- exports.serializeTaskContent = serializeTaskContent;
8247
- var frontmatter_1 = require_frontmatter();
8248
- function parseTaskContent(content) {
8249
- const parsed = (0, frontmatter_1.parseFrontmatter)(content);
8250
- if (!parsed) {
8251
- return null;
8252
- }
8253
- const task = parsed.data;
8254
- if (!task.id || !task.title) {
8255
- return null;
8256
- }
8257
- return {
8258
- task,
8259
- body: parsed.body
8260
- };
8261
- }
8262
- function serializeTaskContent(task, body = "") {
8263
- return (0, frontmatter_1.serializeFrontmatter)(task, body);
8264
- }
8265
- }
8266
- });
8267
-
8268
- // ../core/dist/taskFile.js
8269
- var require_taskFile = __commonJS({
8270
- "../core/dist/taskFile.js"(exports) {
8271
- "use strict";
8272
- var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
8273
- if (k2 === void 0) k2 = k;
8274
- var desc = Object.getOwnPropertyDescriptor(m, k);
8275
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
8276
- desc = { enumerable: true, get: function() {
8277
- return m[k];
8278
- } };
8279
- }
8280
- Object.defineProperty(o, k2, desc);
8281
- }) : (function(o, m, k, k2) {
8282
- if (k2 === void 0) k2 = k;
8283
- o[k2] = m[k];
8284
- }));
8285
- var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? (function(o, v) {
8286
- Object.defineProperty(o, "default", { enumerable: true, value: v });
8287
- }) : function(o, v) {
8288
- o["default"] = v;
8289
- });
8290
- var __importStar = exports && exports.__importStar || /* @__PURE__ */ (function() {
8291
- var ownKeys = function(o) {
8292
- ownKeys = Object.getOwnPropertyNames || function(o2) {
8293
- var ar = [];
8294
- for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
8295
- return ar;
8296
- };
8297
- return ownKeys(o);
8298
- };
8299
- return function(mod) {
8300
- if (mod && mod.__esModule) return mod;
8301
- var result = {};
8302
- if (mod != null) {
8303
- for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
8304
- }
8305
- __setModuleDefault(result, mod);
8306
- return result;
8307
- };
8308
- })();
8309
- Object.defineProperty(exports, "__esModule", { value: true });
8310
- exports.serializeTaskContent = exports.parseTaskContent = void 0;
8311
- exports.readTaskFile = readTaskFile;
8312
- exports.writeTaskFile = writeTaskFile;
8313
- exports.readTasksDir = readTasksDir;
8314
- exports.taskFileName = taskFileName;
8315
- var fs7 = __importStar(__require("fs"));
8316
- var path7 = __importStar(__require("path"));
8317
- var taskContent_1 = require_taskContent();
8318
- var taskContent_2 = require_taskContent();
8319
- Object.defineProperty(exports, "parseTaskContent", { enumerable: true, get: function() {
8320
- return taskContent_2.parseTaskContent;
8321
- } });
8322
- Object.defineProperty(exports, "serializeTaskContent", { enumerable: true, get: function() {
8323
- return taskContent_2.serializeTaskContent;
8324
- } });
8325
- function isUnsafeTaskId(taskId) {
8326
- if (!taskId || taskId.trim() === "") {
8327
- return true;
8328
- }
8329
- const trimmed = taskId.trim();
8330
- if (trimmed.includes("/") || trimmed.includes("\\") || trimmed.includes("..")) {
8331
- return true;
8332
- }
8333
- if (path7.isAbsolute(trimmed)) {
8334
- return true;
8335
- }
8336
- return path7.basename(trimmed) !== trimmed;
8337
- }
8338
- function readTaskFile(filePath) {
8339
- let content;
8340
- try {
8341
- content = fs7.readFileSync(filePath, "utf-8");
8342
- } catch {
8343
- return null;
8344
- }
8345
- const parsed = (0, taskContent_1.parseTaskContent)(content);
8346
- if (!parsed) {
8347
- return null;
8348
- }
8349
- return {
8350
- task: parsed.task,
8351
- body: parsed.body,
8352
- filePath: path7.resolve(filePath)
8353
- };
8354
- }
8355
- function writeTaskFile(filePath, task, body = "") {
8356
- const dir = path7.dirname(filePath);
8357
- fs7.mkdirSync(dir, { recursive: true });
8358
- const content = (0, taskContent_1.serializeTaskContent)(task, body);
8359
- fs7.writeFileSync(filePath, content, "utf-8");
8360
- }
8361
- function readTasksDir(dirPath) {
8362
- let entries;
8363
- try {
8364
- entries = fs7.readdirSync(dirPath, { withFileTypes: true });
8365
- } catch {
8366
- return [];
8367
- }
8368
- const docs = [];
8369
- for (const entry of entries) {
8370
- if (!entry.isFile() || !entry.name.endsWith(".md")) {
8371
- continue;
8372
- }
8373
- const filePath = path7.join(dirPath, entry.name);
8374
- const doc = readTaskFile(filePath);
8375
- if (doc) {
8376
- docs.push(doc);
8377
- }
8378
- }
8379
- return docs;
8380
- }
8381
- function taskFileName(taskId) {
8382
- if (isUnsafeTaskId(taskId)) {
8383
- throw new Error(`Invalid task ID: ${taskId}`);
8384
- }
8385
- return `${taskId.trim()}.md`;
8386
- }
8387
- }
8388
- });
8389
-
8390
- // ../core/dist/ledger.js
8391
- var require_ledger2 = __commonJS({
8392
- "../core/dist/ledger.js"(exports) {
8393
- "use strict";
8394
- var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
8395
- if (k2 === void 0) k2 = k;
8396
- var desc = Object.getOwnPropertyDescriptor(m, k);
8397
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
8398
- desc = { enumerable: true, get: function() {
8399
- return m[k];
8400
- } };
8401
- }
8402
- Object.defineProperty(o, k2, desc);
8403
- }) : (function(o, m, k, k2) {
8404
- if (k2 === void 0) k2 = k;
8405
- o[k2] = m[k];
8406
- }));
8407
- var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? (function(o, v) {
8408
- Object.defineProperty(o, "default", { enumerable: true, value: v });
8409
- }) : function(o, v) {
8410
- o["default"] = v;
8411
- });
8412
- var __importStar = exports && exports.__importStar || /* @__PURE__ */ (function() {
8413
- var ownKeys = function(o) {
8414
- ownKeys = Object.getOwnPropertyNames || function(o2) {
8415
- var ar = [];
8416
- for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
8417
- return ar;
8418
- };
8419
- return ownKeys(o);
8420
- };
8421
- return function(mod) {
8422
- if (mod && mod.__esModule) return mod;
8423
- var result = {};
8424
- if (mod != null) {
8425
- for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
8426
- }
8427
- __setModuleDefault(result, mod);
8428
- return result;
8429
- };
8430
- })();
8431
- Object.defineProperty(exports, "__esModule", { value: true });
8432
- exports.normalizePathValue = normalizePathValue;
8433
- exports.isLedgerContractStatus = isLedgerContractStatus;
8434
- exports.buildLedgerRecord = buildLedgerRecord;
8435
- exports.appendLedgerRecord = appendLedgerRecord;
8436
- exports.readLedger = readLedger;
8437
- exports.queryLedger = queryLedger;
8438
- exports.getFileHistory = getFileHistory;
8439
- exports.getTaskContext = getTaskContext;
8440
- var fs7 = __importStar(__require("fs"));
8441
- var path7 = __importStar(__require("path"));
8442
- var taskFile_1 = require_taskFile();
8443
- var ledger_1 = require_ledger();
8444
- var LEDGER_FILE_NAME = "ledger.jsonl";
8445
- var LEGACY_WARNING_TRACKER = /* @__PURE__ */ new Set();
8446
- function getLedgerPath(logsDir) {
8447
- return path7.join(logsDir, LEDGER_FILE_NAME);
8448
- }
8449
- function normalizePathValue(value) {
8450
- return value.replace(/\\/g, "/").replace(/^\.\//, "").trim();
8142
+ function normalizePathValue(value) {
8143
+ return value.replace(/\\/g, "/").replace(/^\.\//, "").trim();
8451
8144
  }
8452
8145
  function toUniqueStrings(values) {
8453
8146
  if (!values) {
@@ -8662,7 +8355,7 @@ var require_ledger2 = __commonJS({
8662
8355
  return parsed;
8663
8356
  }
8664
8357
  function shouldWarnLegacyFallback(logsDir) {
8665
- const key = path7.resolve(logsDir);
8358
+ const key = path11.resolve(logsDir);
8666
8359
  if (LEGACY_WARNING_TRACKER.has(key)) {
8667
8360
  return false;
8668
8361
  }
@@ -8748,18 +8441,18 @@ var require_ledger2 = __commonJS({
8748
8441
  return record;
8749
8442
  }
8750
8443
  function appendLedgerRecord(logsDir, record) {
8751
- fs7.mkdirSync(logsDir, { recursive: true });
8444
+ fs11.mkdirSync(logsDir, { recursive: true });
8752
8445
  const ledgerPath = getLedgerPath(logsDir);
8753
- fs7.appendFileSync(ledgerPath, `${JSON.stringify(record)}
8446
+ fs11.appendFileSync(ledgerPath, `${JSON.stringify(record)}
8754
8447
  `, "utf-8");
8755
8448
  return ledgerPath;
8756
8449
  }
8757
8450
  function readLedger(logsDir) {
8758
8451
  const ledgerPath = getLedgerPath(logsDir);
8759
- if (!fs7.existsSync(ledgerPath)) {
8452
+ if (!fs11.existsSync(ledgerPath)) {
8760
8453
  return readLegacyMarkdownLedger(logsDir);
8761
8454
  }
8762
- const content = fs7.readFileSync(ledgerPath, "utf-8");
8455
+ const content = fs11.readFileSync(ledgerPath, "utf-8");
8763
8456
  const lines = content.split("\n");
8764
8457
  const records = [];
8765
8458
  for (let index = 0; index < lines.length; index++) {
@@ -8808,29 +8501,903 @@ var require_ledger2 = __commonJS({
8808
8501
  if (options.dateRange) {
8809
8502
  records = records.filter((record) => matchesDateRange(record.completedAt, options.dateRange));
8810
8503
  }
8811
- records.sort((a, b) => timestampOr(b.completedAt, 0) - timestampOr(a.completedAt, 0));
8812
- if (options.limit !== void 0 && options.limit > 0) {
8813
- return records.slice(0, options.limit);
8504
+ records.sort((a, b) => timestampOr(b.completedAt, 0) - timestampOr(a.completedAt, 0));
8505
+ if (options.limit !== void 0 && options.limit > 0) {
8506
+ return records.slice(0, options.limit);
8507
+ }
8508
+ return records;
8509
+ }
8510
+ function getTaskContext(logsDir, relatedFiles, deliverables, options = {}) {
8511
+ const scopeFiles = toUniquePaths([
8512
+ ...relatedFiles,
8513
+ ...collectDeliverableInputPaths(deliverables)
8514
+ ]);
8515
+ if (scopeFiles.length === 0) {
8516
+ return [];
8517
+ }
8518
+ let entries = readLedger(logsDir).filter((record) => matchesDateRange(record.completedAt, options.dateRange)).map((record) => {
8519
+ const matchedFiles = matchedFilesForScope(scopeFiles, collectRecordFiles(record));
8520
+ return { record, matchedFiles };
8521
+ }).filter((entry) => entry.matchedFiles.length > 0);
8522
+ entries.sort((a, b) => timestampOr(b.record.completedAt, 0) - timestampOr(a.record.completedAt, 0));
8523
+ if (options.limit !== void 0 && options.limit > 0) {
8524
+ entries = entries.slice(0, options.limit);
8525
+ }
8526
+ return entries;
8527
+ }
8528
+ }
8529
+ });
8530
+
8531
+ // ../core/dist/workspace.js
8532
+ var require_workspace = __commonJS({
8533
+ "../core/dist/workspace.js"(exports) {
8534
+ "use strict";
8535
+ var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
8536
+ if (k2 === void 0) k2 = k;
8537
+ var desc = Object.getOwnPropertyDescriptor(m, k);
8538
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
8539
+ desc = { enumerable: true, get: function() {
8540
+ return m[k];
8541
+ } };
8542
+ }
8543
+ Object.defineProperty(o, k2, desc);
8544
+ }) : (function(o, m, k, k2) {
8545
+ if (k2 === void 0) k2 = k;
8546
+ o[k2] = m[k];
8547
+ }));
8548
+ var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? (function(o, v) {
8549
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
8550
+ }) : function(o, v) {
8551
+ o["default"] = v;
8552
+ });
8553
+ var __importStar = exports && exports.__importStar || /* @__PURE__ */ (function() {
8554
+ var ownKeys = function(o) {
8555
+ ownKeys = Object.getOwnPropertyNames || function(o2) {
8556
+ var ar = [];
8557
+ for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
8558
+ return ar;
8559
+ };
8560
+ return ownKeys(o);
8561
+ };
8562
+ return function(mod) {
8563
+ if (mod && mod.__esModule) return mod;
8564
+ var result = {};
8565
+ if (mod != null) {
8566
+ for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
8567
+ }
8568
+ __setModuleDefault(result, mod);
8569
+ return result;
8570
+ };
8571
+ })();
8572
+ Object.defineProperty(exports, "__esModule", { value: true });
8573
+ exports.getV2Dirs = getV2Dirs2;
8574
+ exports.isV2 = isV25;
8575
+ exports.ensureV2Dirs = ensureV2Dirs2;
8576
+ exports.getTaskFilePath = getTaskFilePath2;
8577
+ exports.getLogFilePath = getLogFilePath2;
8578
+ exports.findV2Task = findV2Task2;
8579
+ exports.extractDescription = extractDescription2;
8580
+ exports.extractLog = extractLog2;
8581
+ exports.composeBody = composeBody2;
8582
+ exports.readV2BoardConfig = readV2BoardConfig2;
8583
+ exports.buildBoardFromV2 = buildBoardFromV22;
8584
+ var fs11 = __importStar(__require("fs"));
8585
+ var path11 = __importStar(__require("path"));
8586
+ var parser_1 = require_parser();
8587
+ var taskFile_1 = require_taskFile();
8588
+ function getV2Dirs2(brainfilePath) {
8589
+ const resolvedPath = path11.resolve(brainfilePath);
8590
+ const dotDir = path11.dirname(resolvedPath);
8591
+ return {
8592
+ dotDir,
8593
+ boardDir: path11.join(dotDir, "board"),
8594
+ logsDir: path11.join(dotDir, "logs"),
8595
+ brainfilePath: resolvedPath
8596
+ };
8597
+ }
8598
+ function isV25(brainfilePath) {
8599
+ const { boardDir } = getV2Dirs2(brainfilePath);
8600
+ return fs11.existsSync(boardDir);
8601
+ }
8602
+ function ensureV2Dirs2(brainfilePath) {
8603
+ const dirs = getV2Dirs2(brainfilePath);
8604
+ fs11.mkdirSync(dirs.boardDir, { recursive: true });
8605
+ fs11.mkdirSync(dirs.logsDir, { recursive: true });
8606
+ return dirs;
8607
+ }
8608
+ function getTaskFilePath2(boardDir, taskId) {
8609
+ return path11.join(boardDir, (0, taskFile_1.taskFileName)(taskId));
8610
+ }
8611
+ function getLogFilePath2(logsDir, taskId) {
8612
+ return path11.join(logsDir, (0, taskFile_1.taskFileName)(taskId));
8613
+ }
8614
+ function findV2Task2(dirs, taskId, searchLogs = false) {
8615
+ const taskPath = getTaskFilePath2(dirs.boardDir, taskId);
8616
+ const taskDoc = (0, taskFile_1.readTaskFile)(taskPath);
8617
+ if (taskDoc && taskDoc.task.id === taskId) {
8618
+ return { doc: taskDoc, filePath: taskPath, isLog: false };
8619
+ }
8620
+ if (searchLogs) {
8621
+ const logPath = getLogFilePath2(dirs.logsDir, taskId);
8622
+ const logDoc = (0, taskFile_1.readTaskFile)(logPath);
8623
+ if (logDoc && logDoc.task.id === taskId) {
8624
+ return { doc: logDoc, filePath: logPath, isLog: true };
8625
+ }
8626
+ }
8627
+ const boardDocs = (0, taskFile_1.readTasksDir)(dirs.boardDir);
8628
+ const boardMatch = boardDocs.find((d) => d.task.id === taskId);
8629
+ if (boardMatch) {
8630
+ return {
8631
+ doc: boardMatch,
8632
+ filePath: boardMatch.filePath || taskPath,
8633
+ isLog: false
8634
+ };
8635
+ }
8636
+ if (searchLogs) {
8637
+ const logDocs = (0, taskFile_1.readTasksDir)(dirs.logsDir);
8638
+ const logMatch = logDocs.find((d) => d.task.id === taskId);
8639
+ if (logMatch) {
8640
+ return {
8641
+ doc: logMatch,
8642
+ filePath: logMatch.filePath || getLogFilePath2(dirs.logsDir, taskId),
8643
+ isLog: true
8644
+ };
8645
+ }
8646
+ }
8647
+ return null;
8648
+ }
8649
+ function extractDescription2(body) {
8650
+ const match = body.match(/## Description\n([\s\S]*?)(?=\n## |\n*$)/);
8651
+ return match ? match[1].trim() || void 0 : void 0;
8652
+ }
8653
+ function extractLog2(body) {
8654
+ const match = body.match(/## Log\n([\s\S]*?)(?=\n## |\n*$)/);
8655
+ return match ? match[1].trim() || void 0 : void 0;
8656
+ }
8657
+ function composeBody2(description, log) {
8658
+ const sections = [];
8659
+ if (description && description.trim()) {
8660
+ sections.push(`## Description
8661
+ ${description.trim()}`);
8662
+ }
8663
+ if (log && log.trim()) {
8664
+ sections.push(`## Log
8665
+ ${log.trim()}`);
8666
+ }
8667
+ if (sections.length === 0)
8668
+ return "";
8669
+ return `${sections.join("\n\n")}
8670
+ `;
8671
+ }
8672
+ function readV2BoardConfig2(brainfilePath) {
8673
+ const content = fs11.readFileSync(brainfilePath, "utf-8");
8674
+ const result = parser_1.BrainfileParser.parseWithErrors(content);
8675
+ if (!result.board) {
8676
+ throw new Error(`Failed to parse brainfile: ${result.error}`);
8677
+ }
8678
+ const board = result.board;
8679
+ for (const col of board.columns) {
8680
+ if (!col.tasks)
8681
+ col.tasks = [];
8682
+ }
8683
+ return board;
8684
+ }
8685
+ function buildBoardFromV22(brainfilePath) {
8686
+ const dirs = getV2Dirs2(brainfilePath);
8687
+ const board = readV2BoardConfig2(brainfilePath);
8688
+ const taskDocs = (0, taskFile_1.readTasksDir)(dirs.boardDir);
8689
+ const tasksByColumn = /* @__PURE__ */ new Map();
8690
+ for (const doc of taskDocs) {
8691
+ const colId = doc.task.column || "todo";
8692
+ if (!tasksByColumn.has(colId)) {
8693
+ tasksByColumn.set(colId, []);
8694
+ }
8695
+ tasksByColumn.get(colId).push(doc);
8696
+ }
8697
+ for (const col of board.columns) {
8698
+ const colTasks = tasksByColumn.get(col.id) || [];
8699
+ colTasks.sort((a, b) => {
8700
+ const posA = a.task.position ?? Number.MAX_SAFE_INTEGER;
8701
+ const posB = b.task.position ?? Number.MAX_SAFE_INTEGER;
8702
+ if (posA !== posB)
8703
+ return posA - posB;
8704
+ return a.task.id.localeCompare(b.task.id);
8705
+ });
8706
+ col.tasks = colTasks.map((doc) => {
8707
+ const task = { ...doc.task };
8708
+ if (!task.description) {
8709
+ const description = extractDescription2(doc.body);
8710
+ if (description)
8711
+ task.description = description;
8712
+ }
8713
+ return task;
8714
+ });
8715
+ }
8716
+ return board;
8717
+ }
8718
+ }
8719
+ });
8720
+
8721
+ // ../core/dist/brief.js
8722
+ var require_brief = __commonJS({
8723
+ "../core/dist/brief.js"(exports) {
8724
+ "use strict";
8725
+ var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
8726
+ if (k2 === void 0) k2 = k;
8727
+ var desc = Object.getOwnPropertyDescriptor(m, k);
8728
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
8729
+ desc = { enumerable: true, get: function() {
8730
+ return m[k];
8731
+ } };
8732
+ }
8733
+ Object.defineProperty(o, k2, desc);
8734
+ }) : (function(o, m, k, k2) {
8735
+ if (k2 === void 0) k2 = k;
8736
+ o[k2] = m[k];
8737
+ }));
8738
+ var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? (function(o, v) {
8739
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
8740
+ }) : function(o, v) {
8741
+ o["default"] = v;
8742
+ });
8743
+ var __importStar = exports && exports.__importStar || /* @__PURE__ */ (function() {
8744
+ var ownKeys = function(o) {
8745
+ ownKeys = Object.getOwnPropertyNames || function(o2) {
8746
+ var ar = [];
8747
+ for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
8748
+ return ar;
8749
+ };
8750
+ return ownKeys(o);
8751
+ };
8752
+ return function(mod) {
8753
+ if (mod && mod.__esModule) return mod;
8754
+ var result = {};
8755
+ if (mod != null) {
8756
+ for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
8757
+ }
8758
+ __setModuleDefault(result, mod);
8759
+ return result;
8760
+ };
8761
+ })();
8762
+ Object.defineProperty(exports, "__esModule", { value: true });
8763
+ exports.parseNoteLine = parseNoteLine;
8764
+ exports.parseNotes = parseNotes;
8765
+ exports.relativeTime = relativeTime;
8766
+ exports.buildBrief = buildBrief;
8767
+ exports.isEmptyBrief = isEmptyBrief;
8768
+ var fs11 = __importStar(__require("fs"));
8769
+ var taskFile_1 = require_taskFile();
8770
+ var ledger_1 = require_ledger2();
8771
+ var workspace_1 = require_workspace();
8772
+ var FULL_MODE_COMPLETION_LIMIT = 10;
8773
+ var ISO_TIMESTAMP_SHAPE = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}/;
8774
+ function parseNoteLine(line) {
8775
+ const match = /^-\s+(\S+)/.exec(line);
8776
+ if (!match)
8777
+ return null;
8778
+ let token = match[1];
8779
+ if (token.endsWith(":"))
8780
+ token = token.slice(0, -1);
8781
+ if (!ISO_TIMESTAMP_SHAPE.test(token))
8782
+ return null;
8783
+ const parsed = Date.parse(token);
8784
+ if (Number.isNaN(parsed))
8785
+ return null;
8786
+ let rest = line.slice(match[0].length);
8787
+ let agent;
8788
+ const takeBracket = () => {
8789
+ const bracket = /^\s*\[([^\]]+)\]/.exec(rest);
8790
+ if (bracket) {
8791
+ agent = bracket[1].trim() || void 0;
8792
+ rest = rest.slice(bracket[0].length);
8793
+ }
8794
+ };
8795
+ takeBracket();
8796
+ rest = rest.replace(/^\s*:/, "");
8797
+ if (!agent)
8798
+ takeBracket();
8799
+ return {
8800
+ at: new Date(parsed).toISOString(),
8801
+ text: rest.trim(),
8802
+ agent
8803
+ };
8804
+ }
8805
+ function parseNotes(body) {
8806
+ const log = (0, workspace_1.extractLog)(body);
8807
+ if (!log)
8808
+ return [];
8809
+ const notes = [];
8810
+ for (const line of log.split("\n")) {
8811
+ const parsed = parseNoteLine(line);
8812
+ if (parsed)
8813
+ notes.push(parsed);
8814
+ }
8815
+ return notes.sort((a, b) => Date.parse(b.at) - Date.parse(a.at));
8816
+ }
8817
+ function lastRawLogLine(body) {
8818
+ const log = (0, workspace_1.extractLog)(body);
8819
+ if (!log)
8820
+ return null;
8821
+ const lines = log.split("\n").map((l) => l.trim()).filter((l) => l.length > 0);
8822
+ if (lines.length === 0)
8823
+ return null;
8824
+ return lines[lines.length - 1].replace(/^-\s+/, "");
8825
+ }
8826
+ function parseMs(value) {
8827
+ if (!value)
8828
+ return null;
8829
+ const ms = Date.parse(value);
8830
+ return Number.isNaN(ms) ? null : ms;
8831
+ }
8832
+ function isAssignedTo(assignee, agentName) {
8833
+ if (!assignee)
8834
+ return false;
8835
+ return assignee.trim().toLowerCase() === agentName.trim().toLowerCase();
8836
+ }
8837
+ function relativeTime(from, to) {
8838
+ const fromMs = parseMs(from);
8839
+ const toMs = parseMs(to);
8840
+ if (fromMs === null || toMs === null)
8841
+ return "unknown";
8842
+ const deltaSec = Math.max(0, Math.round((toMs - fromMs) / 1e3));
8843
+ if (deltaSec < 60)
8844
+ return "just now";
8845
+ const min = Math.floor(deltaSec / 60);
8846
+ if (min < 60)
8847
+ return `${min}m`;
8848
+ const hours = Math.floor(min / 60);
8849
+ if (hours < 24)
8850
+ return `${hours}h`;
8851
+ const days = Math.floor(hours / 24);
8852
+ return `${days}d`;
8853
+ }
8854
+ function agoPhrase(from, to) {
8855
+ const rel = relativeTime(from, to);
8856
+ return rel === "just now" || rel === "unknown" ? rel : `${rel} ago`;
8857
+ }
8858
+ function taskStateWhy(task) {
8859
+ const parts = [task.column || "unknown"];
8860
+ if (task.priority)
8861
+ parts.push(task.priority);
8862
+ if (task.contract?.status)
8863
+ parts.push(`contract:${task.contract.status}`);
8864
+ return parts.join(" \xB7 ");
8865
+ }
8866
+ function completionWhy(record) {
8867
+ const cycle = typeof record.cycleTimeHours === "number" ? `${record.cycleTimeHours}h cycle` : "cycle unknown";
8868
+ return `completed \xB7 ${cycle}`;
8869
+ }
8870
+ function boardMtimeMs(brainfilePath) {
8871
+ try {
8872
+ return fs11.statSync(brainfilePath).mtimeMs;
8873
+ } catch {
8874
+ return null;
8875
+ }
8876
+ }
8877
+ function completionsFor(dirs, agentName, lastBriefAtMs) {
8878
+ let records;
8879
+ try {
8880
+ records = (0, ledger_1.queryLedger)(dirs.logsDir, {});
8881
+ } catch {
8882
+ return [];
8883
+ }
8884
+ const matching = records.filter((record) => {
8885
+ if (!isAssignedTo(record.assignee, agentName))
8886
+ return false;
8887
+ if (lastBriefAtMs === null)
8888
+ return true;
8889
+ const completedMs = parseMs(record.completedAt);
8890
+ return completedMs !== null && completedMs > lastBriefAtMs;
8891
+ });
8892
+ return matching.sort((a, b) => (parseMs(b.completedAt) ?? 0) - (parseMs(a.completedAt) ?? 0));
8893
+ }
8894
+ function readBoardConfig(brainfilePath) {
8895
+ try {
8896
+ return (0, workspace_1.readV2BoardConfig)(brainfilePath);
8897
+ } catch {
8898
+ return null;
8899
+ }
8900
+ }
8901
+ function buildBrief(dirs, agentName, opts) {
8902
+ const generatedAt = opts.now ?? (/* @__PURE__ */ new Date()).toISOString();
8903
+ const lastBriefAt = opts.lastBriefAt;
8904
+ const lastBriefAtMs = parseMs(lastBriefAt);
8905
+ const mode = lastBriefAtMs === null ? "full" : "delta";
8906
+ const assigned = (0, taskFile_1.readTasksDir)(dirs.boardDir).filter((doc) => isAssignedTo(doc.task.assignee, agentName));
8907
+ const lanes = mode === "full" ? fullLanes(dirs, agentName, assigned, generatedAt) : deltaLanes(dirs, agentName, assigned, generatedAt, lastBriefAtMs);
8908
+ return {
8909
+ agent: agentName,
8910
+ mode,
8911
+ generatedAt,
8912
+ // Normalize away an unparseable stored checkpoint rather than echoing it.
8913
+ lastBriefAt: lastBriefAtMs === null ? null : lastBriefAt,
8914
+ lanes
8915
+ };
8916
+ }
8917
+ function sortAssigned(docs) {
8918
+ return [...docs].sort((a, b) => {
8919
+ const aMs = parseMs(a.task.updatedAt) ?? parseMs(a.task.createdAt) ?? 0;
8920
+ const bMs = parseMs(b.task.updatedAt) ?? parseMs(b.task.createdAt) ?? 0;
8921
+ return bMs - aMs;
8922
+ });
8923
+ }
8924
+ function acceptedAdrs(dirs) {
8925
+ const ACCEPTED = /* @__PURE__ */ new Set(["accepted", "promoted"]);
8926
+ const docs = [];
8927
+ for (const dir of [dirs.boardDir, dirs.logsDir]) {
8928
+ let entries;
8929
+ try {
8930
+ entries = (0, taskFile_1.readTasksDir)(dir);
8931
+ } catch {
8932
+ continue;
8933
+ }
8934
+ for (const doc of entries) {
8935
+ const task = doc.task;
8936
+ if ((task.type || "").toLowerCase() !== "adr")
8937
+ continue;
8938
+ if (!ACCEPTED.has((task.status || "").toLowerCase()))
8939
+ continue;
8940
+ docs.push(doc);
8941
+ }
8942
+ }
8943
+ return docs.sort((a, b) => a.task.id.localeCompare(b.task.id, void 0, { numeric: true }));
8944
+ }
8945
+ function fullLanes(dirs, agentName, assigned, generatedAt) {
8946
+ const orientation = [];
8947
+ const board = readBoardConfig(dirs.brainfilePath);
8948
+ if (board?.title) {
8949
+ orientation.push({ text: board.title, why: "board" });
8950
+ }
8951
+ for (const instruction of board?.agent?.instructions ?? []) {
8952
+ orientation.push({ text: instruction, why: "agent instructions" });
8953
+ }
8954
+ for (const doc of acceptedAdrs(dirs)) {
8955
+ orientation.push({ taskId: doc.task.id, text: doc.task.title, why: "accepted adr" });
8956
+ }
8957
+ const assignedItems = sortAssigned(assigned).map((doc) => ({
8958
+ taskId: doc.task.id,
8959
+ text: doc.task.title,
8960
+ why: taskStateWhy(doc.task),
8961
+ ...doc.task.updatedAt ? { at: doc.task.updatedAt } : {}
8962
+ }));
8963
+ const noteItems = [];
8964
+ for (const doc of assigned) {
8965
+ const notes = parseNotes(doc.body);
8966
+ if (notes.length > 0) {
8967
+ const latest = notes[0];
8968
+ noteItems.push({
8969
+ taskId: doc.task.id,
8970
+ text: latest.text,
8971
+ why: `logged ${agoPhrase(latest.at, generatedAt)}`,
8972
+ at: latest.at
8973
+ });
8974
+ continue;
8975
+ }
8976
+ const raw = lastRawLogLine(doc.body);
8977
+ if (raw) {
8978
+ noteItems.push({ taskId: doc.task.id, text: raw, why: "unstamped entry" });
8979
+ }
8980
+ }
8981
+ noteItems.sort((a, b) => (parseMs(b.at) ?? 0) - (parseMs(a.at) ?? 0));
8982
+ const completionItems = completionsFor(dirs, agentName, null).slice(0, FULL_MODE_COMPLETION_LIMIT).map((record) => ({
8983
+ taskId: record.id,
8984
+ text: record.title,
8985
+ why: completionWhy(record),
8986
+ at: record.completedAt
8987
+ }));
8988
+ return [
8989
+ { id: "orientation", label: "Board & Decisions", items: orientation },
8990
+ { id: "assigned", label: "Your Tasks", items: assignedItems },
8991
+ { id: "notes", label: "Latest Notes", items: noteItems },
8992
+ { id: "completions", label: "Recently Completed (yours)", items: completionItems }
8993
+ ];
8994
+ }
8995
+ function deltaLanes(dirs, agentName, assigned, generatedAt, lastBriefAtMs) {
8996
+ const noteItems = [];
8997
+ for (const doc of assigned) {
8998
+ for (const note of parseNotes(doc.body)) {
8999
+ const noteMs = parseMs(note.at);
9000
+ if (noteMs === null || noteMs <= lastBriefAtMs)
9001
+ continue;
9002
+ noteItems.push({
9003
+ taskId: doc.task.id,
9004
+ text: note.agent ? `[${note.agent}] ${note.text}` : note.text,
9005
+ why: agoPhrase(note.at, generatedAt),
9006
+ at: note.at
9007
+ });
9008
+ }
9009
+ }
9010
+ noteItems.sort((a, b) => (parseMs(b.at) ?? 0) - (parseMs(a.at) ?? 0));
9011
+ const changeItems = [];
9012
+ for (const doc of sortAssigned(assigned)) {
9013
+ const createdMs = parseMs(doc.task.createdAt);
9014
+ const updatedMs = parseMs(doc.task.updatedAt);
9015
+ const isNew = createdMs !== null && createdMs > lastBriefAtMs;
9016
+ const isUpdated = updatedMs !== null && updatedMs > lastBriefAtMs;
9017
+ if (!isNew && !isUpdated)
9018
+ continue;
9019
+ const stamp2 = isNew ? doc.task.createdAt : doc.task.updatedAt;
9020
+ const verb = isNew ? "created" : "updated";
9021
+ changeItems.push({
9022
+ taskId: doc.task.id,
9023
+ text: doc.task.title,
9024
+ why: `${verb} ${agoPhrase(stamp2, generatedAt)} \xB7 ${taskStateWhy(doc.task)}`,
9025
+ ...stamp2 ? { at: stamp2 } : {}
9026
+ });
9027
+ }
9028
+ const completionItems = completionsFor(dirs, agentName, lastBriefAtMs).map((record) => ({
9029
+ taskId: record.id,
9030
+ text: record.title,
9031
+ why: completionWhy(record),
9032
+ at: record.completedAt
9033
+ }));
9034
+ const configItems = [];
9035
+ const mtime = boardMtimeMs(dirs.brainfilePath);
9036
+ if (mtime !== null && mtime > lastBriefAtMs) {
9037
+ const at = new Date(mtime).toISOString();
9038
+ configItems.push({
9039
+ text: "Board config changed",
9040
+ why: "brainfile.md changed since last brief",
9041
+ at
9042
+ });
9043
+ }
9044
+ return [
9045
+ { id: "notes", label: "New Notes", items: noteItems },
9046
+ { id: "changes", label: "Task Changes", items: changeItems },
9047
+ { id: "completions", label: "Completed Since Last Brief", items: completionItems },
9048
+ { id: "config", label: "Board Config", items: configItems }
9049
+ ];
9050
+ }
9051
+ function isEmptyBrief(result) {
9052
+ return result.lanes.every((lane) => lane.items.length === 0);
9053
+ }
9054
+ }
9055
+ });
9056
+
9057
+ // ../core/dist/briefState.js
9058
+ var require_briefState = __commonJS({
9059
+ "../core/dist/briefState.js"(exports) {
9060
+ "use strict";
9061
+ var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
9062
+ if (k2 === void 0) k2 = k;
9063
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9064
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9065
+ desc = { enumerable: true, get: function() {
9066
+ return m[k];
9067
+ } };
9068
+ }
9069
+ Object.defineProperty(o, k2, desc);
9070
+ }) : (function(o, m, k, k2) {
9071
+ if (k2 === void 0) k2 = k;
9072
+ o[k2] = m[k];
9073
+ }));
9074
+ var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? (function(o, v) {
9075
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
9076
+ }) : function(o, v) {
9077
+ o["default"] = v;
9078
+ });
9079
+ var __importStar = exports && exports.__importStar || /* @__PURE__ */ (function() {
9080
+ var ownKeys = function(o) {
9081
+ ownKeys = Object.getOwnPropertyNames || function(o2) {
9082
+ var ar = [];
9083
+ for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
9084
+ return ar;
9085
+ };
9086
+ return ownKeys(o);
9087
+ };
9088
+ return function(mod) {
9089
+ if (mod && mod.__esModule) return mod;
9090
+ var result = {};
9091
+ if (mod != null) {
9092
+ for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
9093
+ }
9094
+ __setModuleDefault(result, mod);
9095
+ return result;
9096
+ };
9097
+ })();
9098
+ Object.defineProperty(exports, "__esModule", { value: true });
9099
+ exports.BRIEF_STATE_VERSION = void 0;
9100
+ exports.sanitizeAgentFilename = sanitizeAgentFilename;
9101
+ exports.getBriefStateDir = getBriefStateDir;
9102
+ exports.getBriefStatePath = getBriefStatePath;
9103
+ exports.readBriefState = readBriefState;
9104
+ exports.writeBriefState = writeBriefState;
9105
+ var fs11 = __importStar(__require("fs"));
9106
+ var path11 = __importStar(__require("path"));
9107
+ var files_1 = require_files();
9108
+ exports.BRIEF_STATE_VERSION = 1;
9109
+ function sanitizeAgentFilename(agentName) {
9110
+ const trimmed = agentName.trim().toLowerCase();
9111
+ let cleaned = "";
9112
+ for (const ch of trimmed) {
9113
+ if (/[a-z0-9._-]/.test(ch)) {
9114
+ cleaned += ch;
9115
+ } else {
9116
+ cleaned += `_${ch.charCodeAt(0).toString(16).padStart(2, "0")}`;
9117
+ }
9118
+ }
9119
+ if (cleaned.length === 0 || cleaned === "." || cleaned === "..")
9120
+ return "_";
9121
+ return cleaned;
9122
+ }
9123
+ function getBriefStateDir(brainfilePath) {
9124
+ return path11.join((0, files_1.getBrainfileStateDir)(brainfilePath), files_1.DOT_BRAINFILE_STATE_DIRNAME);
9125
+ }
9126
+ function getBriefStatePath(brainfilePath, agentName) {
9127
+ return path11.join(getBriefStateDir(brainfilePath), `${sanitizeAgentFilename(agentName)}.json`);
9128
+ }
9129
+ function readBriefState(brainfilePath, agentName) {
9130
+ const statePath = getBriefStatePath(brainfilePath, agentName);
9131
+ const fallback = { version: exports.BRIEF_STATE_VERSION, agent: agentName, lastBriefAt: null };
9132
+ if (!fs11.existsSync(statePath))
9133
+ return fallback;
9134
+ try {
9135
+ const parsed = JSON.parse(fs11.readFileSync(statePath, "utf-8"));
9136
+ const lastBriefAt = typeof parsed.lastBriefAt === "string" ? parsed.lastBriefAt : null;
9137
+ return {
9138
+ version: typeof parsed.version === "number" ? parsed.version : exports.BRIEF_STATE_VERSION,
9139
+ agent: typeof parsed.agent === "string" ? parsed.agent : agentName,
9140
+ lastBriefAt
9141
+ };
9142
+ } catch {
9143
+ console.warn(`Warning: unreadable brief state at ${statePath}; treating as first brief`);
9144
+ return fallback;
9145
+ }
9146
+ }
9147
+ function writeBriefState(brainfilePath, agentName, lastBriefAt) {
9148
+ (0, files_1.ensureDotBrainfileGitignore)(brainfilePath);
9149
+ const state = { version: exports.BRIEF_STATE_VERSION, agent: agentName, lastBriefAt };
9150
+ (0, files_1.atomicWriteFileSync)(getBriefStatePath(brainfilePath, agentName), `${JSON.stringify(state, null, 2)}
9151
+ `);
9152
+ return state;
9153
+ }
9154
+ }
9155
+ });
9156
+
9157
+ // ../core/dist/formatters.js
9158
+ var require_formatters = __commonJS({
9159
+ "../core/dist/formatters.js"(exports) {
9160
+ "use strict";
9161
+ Object.defineProperty(exports, "__esModule", { value: true });
9162
+ exports.formatTaskForGitHub = formatTaskForGitHub;
9163
+ exports.formatTaskForLinear = formatTaskForLinear;
9164
+ function formatTaskForGitHub(task, options = {}) {
9165
+ const { includeMeta = true, includeSubtasks = true, includeRelatedFiles = true, resolvedBy, resolvedByPR, fromColumn, boardTitle, extraLabels = [], includeTaskId = true } = options;
9166
+ const title = includeTaskId ? `[${task.id}] ${task.title}` : task.title;
9167
+ const sections = [];
9168
+ if (task.description) {
9169
+ sections.push(task.description);
9170
+ }
9171
+ if (includeSubtasks && task.subtasks && task.subtasks.length > 0) {
9172
+ sections.push(formatSubtasksMarkdown(task.subtasks));
9173
+ }
9174
+ if (includeMeta) {
9175
+ const meta = formatMetadataSection(task, { fromColumn, boardTitle });
9176
+ if (meta) {
9177
+ sections.push(meta);
9178
+ }
9179
+ }
9180
+ if (includeRelatedFiles && task.relatedFiles && task.relatedFiles.length > 0) {
9181
+ sections.push(formatRelatedFilesSection(task.relatedFiles));
9182
+ }
9183
+ if (resolvedBy || resolvedByPR) {
9184
+ sections.push(formatResolutionSection(resolvedBy, resolvedByPR));
9185
+ }
9186
+ sections.push("---\n*Archived from brainfile.md*");
9187
+ const labels = [...task.tags || [], ...extraLabels];
9188
+ if (task.priority) {
9189
+ labels.push(`priority:${task.priority}`);
9190
+ }
9191
+ if (task.template) {
9192
+ labels.push(task.template);
9193
+ }
9194
+ return {
9195
+ title,
9196
+ body: sections.join("\n\n"),
9197
+ labels: labels.length > 0 ? labels : void 0,
9198
+ state: "closed"
9199
+ };
9200
+ }
9201
+ function mapPriorityToLinear(priority) {
9202
+ switch (priority) {
9203
+ case "critical":
9204
+ return 1;
9205
+ case "high":
9206
+ return 2;
9207
+ case "medium":
9208
+ return 3;
9209
+ case "low":
9210
+ return 4;
9211
+ default:
9212
+ return void 0;
9213
+ }
9214
+ }
9215
+ function formatTaskForLinear(task, options = {}) {
9216
+ const { includeMeta = true, includeSubtasks = true, includeRelatedFiles = true, resolvedBy, resolvedByPR, fromColumn, boardTitle, stateName = "Done", includeTaskId = false } = options;
9217
+ const title = includeTaskId ? `[${task.id}] ${task.title}` : task.title;
9218
+ const sections = [];
9219
+ if (task.description) {
9220
+ sections.push(task.description);
9221
+ }
9222
+ if (includeSubtasks && task.subtasks && task.subtasks.length > 0) {
9223
+ sections.push(formatSubtasksMarkdown(task.subtasks));
9224
+ }
9225
+ if (includeMeta) {
9226
+ const meta = formatMetadataSection(task, { fromColumn, boardTitle });
9227
+ if (meta) {
9228
+ sections.push(meta);
9229
+ }
9230
+ }
9231
+ if (includeRelatedFiles && task.relatedFiles && task.relatedFiles.length > 0) {
9232
+ sections.push(formatRelatedFilesSection(task.relatedFiles));
9233
+ }
9234
+ if (resolvedBy || resolvedByPR) {
9235
+ sections.push(formatResolutionSection(resolvedBy, resolvedByPR));
9236
+ }
9237
+ sections.push("---\n*Archived from brainfile.md*");
9238
+ return {
9239
+ title,
9240
+ description: sections.join("\n\n"),
9241
+ priority: mapPriorityToLinear(task.priority),
9242
+ labelNames: task.tags && task.tags.length > 0 ? task.tags : void 0,
9243
+ stateName
9244
+ };
9245
+ }
9246
+ function formatSubtasksMarkdown(subtasks) {
9247
+ const items = subtasks.map((st) => `- [${st.completed ? "x" : " "}] ${st.title}`);
9248
+ return `## Subtasks
9249
+
9250
+ ${items.join("\n")}`;
9251
+ }
9252
+ function formatMetadataSection(task, context) {
9253
+ const lines = [];
9254
+ if (context.boardTitle) {
9255
+ lines.push(`**Board:** ${context.boardTitle}`);
9256
+ }
9257
+ if (context.fromColumn) {
9258
+ lines.push(`**Column:** ${context.fromColumn}`);
9259
+ }
9260
+ if (task.priority) {
9261
+ lines.push(`**Priority:** ${task.priority}`);
9262
+ }
9263
+ if (task.assignee) {
9264
+ lines.push(`**Assignee:** ${task.assignee}`);
9265
+ }
9266
+ if (task.dueDate) {
9267
+ lines.push(`**Due Date:** ${task.dueDate}`);
9268
+ }
9269
+ if (task.createdAt) {
9270
+ lines.push(`**Created:** ${task.createdAt}`);
9271
+ }
9272
+ if (lines.length === 0) {
9273
+ return null;
9274
+ }
9275
+ return `## Details
9276
+
9277
+ ${lines.join("\n")}`;
9278
+ }
9279
+ function formatRelatedFilesSection(files) {
9280
+ const items = files.map((f) => `- \`${f}\``);
9281
+ return `## Related Files
9282
+
9283
+ ${items.join("\n")}`;
9284
+ }
9285
+ function formatResolutionSection(resolvedBy, resolvedByPR) {
9286
+ const lines = ["## Resolution"];
9287
+ if (resolvedByPR) {
9288
+ lines.push(`
9289
+ **Pull Request:** ${resolvedByPR}`);
9290
+ }
9291
+ if (resolvedBy) {
9292
+ lines.push(`
9293
+ **Commit:** ${resolvedBy}`);
9294
+ }
9295
+ return lines.join("");
9296
+ }
9297
+ }
9298
+ });
9299
+
9300
+ // ../core/dist/boardContent.js
9301
+ var require_boardContent = __commonJS({
9302
+ "../core/dist/boardContent.js"(exports) {
9303
+ "use strict";
9304
+ Object.defineProperty(exports, "__esModule", { value: true });
9305
+ exports.parseBoardConfig = parseBoardConfig;
9306
+ exports.serializeBoardConfig = serializeBoardConfig;
9307
+ var frontmatter_1 = require_frontmatter();
9308
+ function parseBoardConfig(content) {
9309
+ const parsed = (0, frontmatter_1.parseFrontmatter)(content);
9310
+ if (!parsed) {
9311
+ return null;
9312
+ }
9313
+ return {
9314
+ config: parsed.data,
9315
+ body: parsed.body
9316
+ };
9317
+ }
9318
+ function serializeBoardConfig(config, body = "") {
9319
+ return (0, frontmatter_1.serializeFrontmatter)(config, body);
9320
+ }
9321
+ }
9322
+ });
9323
+
9324
+ // ../core/dist/boardFile.js
9325
+ var require_boardFile = __commonJS({
9326
+ "../core/dist/boardFile.js"(exports) {
9327
+ "use strict";
9328
+ var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
9329
+ if (k2 === void 0) k2 = k;
9330
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9331
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9332
+ desc = { enumerable: true, get: function() {
9333
+ return m[k];
9334
+ } };
9335
+ }
9336
+ Object.defineProperty(o, k2, desc);
9337
+ }) : (function(o, m, k, k2) {
9338
+ if (k2 === void 0) k2 = k;
9339
+ o[k2] = m[k];
9340
+ }));
9341
+ var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? (function(o, v) {
9342
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
9343
+ }) : function(o, v) {
9344
+ o["default"] = v;
9345
+ });
9346
+ var __importStar = exports && exports.__importStar || /* @__PURE__ */ (function() {
9347
+ var ownKeys = function(o) {
9348
+ ownKeys = Object.getOwnPropertyNames || function(o2) {
9349
+ var ar = [];
9350
+ for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
9351
+ return ar;
9352
+ };
9353
+ return ownKeys(o);
9354
+ };
9355
+ return function(mod) {
9356
+ if (mod && mod.__esModule) return mod;
9357
+ var result = {};
9358
+ if (mod != null) {
9359
+ for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
9360
+ }
9361
+ __setModuleDefault(result, mod);
9362
+ return result;
9363
+ };
9364
+ })();
9365
+ Object.defineProperty(exports, "__esModule", { value: true });
9366
+ exports.serializeBoardConfig = exports.parseBoardConfig = void 0;
9367
+ exports.readBoardConfig = readBoardConfig;
9368
+ exports.writeBoardConfig = writeBoardConfig;
9369
+ var fs11 = __importStar(__require("fs"));
9370
+ var path11 = __importStar(__require("path"));
9371
+ var boardContent_1 = require_boardContent();
9372
+ var boardContent_2 = require_boardContent();
9373
+ Object.defineProperty(exports, "parseBoardConfig", { enumerable: true, get: function() {
9374
+ return boardContent_2.parseBoardConfig;
9375
+ } });
9376
+ Object.defineProperty(exports, "serializeBoardConfig", { enumerable: true, get: function() {
9377
+ return boardContent_2.serializeBoardConfig;
9378
+ } });
9379
+ function readBoardConfig(filePath) {
9380
+ let content;
9381
+ try {
9382
+ content = fs11.readFileSync(filePath, "utf-8");
9383
+ } catch {
9384
+ return null;
9385
+ }
9386
+ const parsed = (0, boardContent_1.parseBoardConfig)(content);
9387
+ if (!parsed) {
9388
+ return null;
8814
9389
  }
8815
- return records;
9390
+ return {
9391
+ config: parsed.config,
9392
+ body: parsed.body,
9393
+ filePath: path11.resolve(filePath)
9394
+ };
8816
9395
  }
8817
- function getTaskContext(logsDir, relatedFiles, deliverables, options = {}) {
8818
- const scopeFiles = toUniquePaths([
8819
- ...relatedFiles,
8820
- ...collectDeliverableInputPaths(deliverables)
8821
- ]);
8822
- if (scopeFiles.length === 0) {
8823
- return [];
8824
- }
8825
- let entries = readLedger(logsDir).filter((record) => matchesDateRange(record.completedAt, options.dateRange)).map((record) => {
8826
- const matchedFiles = matchedFilesForScope(scopeFiles, collectRecordFiles(record));
8827
- return { record, matchedFiles };
8828
- }).filter((entry) => entry.matchedFiles.length > 0);
8829
- entries.sort((a, b) => timestampOr(b.record.completedAt, 0) - timestampOr(a.record.completedAt, 0));
8830
- if (options.limit !== void 0 && options.limit > 0) {
8831
- entries = entries.slice(0, options.limit);
8832
- }
8833
- return entries;
9396
+ function writeBoardConfig(filePath, config, body = "") {
9397
+ const dir = path11.dirname(filePath);
9398
+ fs11.mkdirSync(dir, { recursive: true });
9399
+ const content = (0, boardContent_1.serializeBoardConfig)(config, body);
9400
+ fs11.writeFileSync(filePath, content, "utf-8");
8834
9401
  }
8835
9402
  }
8836
9403
  });
@@ -8902,8 +9469,8 @@ var require_taskOperations = __commonJS({
8902
9469
  exports.activateTaskContract = activateTaskContract;
8903
9470
  exports.activateTaskContractsByParent = activateTaskContractsByParent;
8904
9471
  exports.getEffectiveState = getEffectiveState;
8905
- var fs7 = __importStar(__require("fs"));
8906
- var path7 = __importStar(__require("path"));
9472
+ var fs11 = __importStar(__require("fs"));
9473
+ var path11 = __importStar(__require("path"));
8907
9474
  var boardValidation_1 = require_boardValidation();
8908
9475
  var contractSpec_1 = require_contractSpec();
8909
9476
  var idGen_1 = require_idGen();
@@ -9005,52 +9572,29 @@ ${section}
9005
9572
  return lines.join("\n");
9006
9573
  }
9007
9574
  function writeTaskFileExclusive(filePath, task, body) {
9008
- const dir = path7.dirname(filePath);
9009
- fs7.mkdirSync(dir, { recursive: true });
9575
+ const dir = path11.dirname(filePath);
9576
+ fs11.mkdirSync(dir, { recursive: true });
9010
9577
  const content = (0, taskFile_1.serializeTaskContent)(task, body);
9011
- fs7.writeFileSync(filePath, content, { encoding: "utf-8", flag: "wx" });
9578
+ fs11.writeFileSync(filePath, content, { encoding: "utf-8", flag: "wx" });
9012
9579
  }
9013
9580
  function rollbackLedgerAppend(logsDir, appendedRecord) {
9014
- const ledgerPath = path7.join(logsDir, "ledger.jsonl");
9581
+ const ledgerPath = path11.join(logsDir, "ledger.jsonl");
9015
9582
  const appendedLine = `${JSON.stringify(appendedRecord)}
9016
9583
  `;
9017
9584
  const appendedBytes = Buffer.byteLength(appendedLine, "utf-8");
9018
9585
  try {
9019
- const stat = fs7.statSync(ledgerPath);
9586
+ const stat = fs11.statSync(ledgerPath);
9020
9587
  const newSize = stat.size - appendedBytes;
9021
9588
  if (newSize >= 0) {
9022
- fs7.truncateSync(ledgerPath, newSize);
9589
+ fs11.truncateSync(ledgerPath, newSize);
9023
9590
  }
9024
9591
  } catch {
9025
9592
  }
9026
9593
  }
9027
- function completeTaskFileLegacy(taskPath, logsDir, doc, completedTask, childStates) {
9028
- const baseName = path7.basename(taskPath);
9029
- const destPath = path7.join(logsDir, baseName);
9030
- let completedBody = doc.body;
9031
- if (doc.task.type === "epic") {
9032
- const childTasksSection = buildChildTasksSection(childStates ?? []);
9033
- completedBody = appendBodySection(doc.body, childTasksSection);
9034
- }
9035
- if (fs7.existsSync(destPath)) {
9036
- return { success: false, error: `Task already exists in logs: ${doc.task.id}` };
9037
- }
9038
- try {
9039
- fs7.mkdirSync(logsDir, { recursive: true });
9040
- writeTaskFileExclusive(destPath, completedTask, completedBody);
9041
- } catch (err) {
9042
- return { success: false, error: `Failed to complete task: ${err}` };
9043
- }
9044
- try {
9045
- fs7.unlinkSync(taskPath);
9046
- return { success: true, task: completedTask, filePath: destPath };
9047
- } catch (err) {
9048
- try {
9049
- fs7.unlinkSync(destPath);
9050
- } catch {
9051
- }
9052
- return { success: false, error: `Failed to finalize completion: ${err}` };
9053
- }
9594
+ function archivedBody(doc, childStates) {
9595
+ if (doc.task.type !== "epic")
9596
+ return doc.body;
9597
+ return appendBodySection(doc.body, buildChildTasksSection(childStates ?? []));
9054
9598
  }
9055
9599
  function generateNextFileTaskId(boardDir, logsDir, typePrefix = "task") {
9056
9600
  let maxNum = 0;
@@ -9124,7 +9668,7 @@ ${section}
9124
9668
  };
9125
9669
  let filePath;
9126
9670
  try {
9127
- filePath = path7.join(boardDir, (0, taskFile_1.taskFileName)(taskId));
9671
+ filePath = path11.join(boardDir, (0, taskFile_1.taskFileName)(taskId));
9128
9672
  } catch (err) {
9129
9673
  return { success: false, error: err.message };
9130
9674
  }
@@ -9178,7 +9722,7 @@ ${section}
9178
9722
  if (!doc) {
9179
9723
  return { success: false, error: `Task not found: ${taskId}` };
9180
9724
  }
9181
- let filePath = doc.filePath ?? path7.join(dirs.boardDir, (0, taskFile_1.taskFileName)(taskId));
9725
+ let filePath = doc.filePath ?? path11.join(dirs.boardDir, (0, taskFile_1.taskFileName)(taskId));
9182
9726
  if (doc.task.column === resolvedColumn.id) {
9183
9727
  return { success: true, task: doc.task, filePath, column: resolvedColumn, noop: true };
9184
9728
  }
@@ -9198,7 +9742,7 @@ ${section}
9198
9742
  if (!shouldAutoComplete) {
9199
9743
  return { success: true, task, filePath, column: resolvedColumn };
9200
9744
  }
9201
- const completeResult = completeTaskFile(filePath, dirs.logsDir, { legacyMode: true });
9745
+ const completeResult = completeTaskFile(filePath, dirs.logsDir);
9202
9746
  if (!completeResult.success || !completeResult.task) {
9203
9747
  return {
9204
9748
  success: false,
@@ -9394,7 +9938,7 @@ ${section}
9394
9938
  let childStates;
9395
9939
  let incompleteChildren;
9396
9940
  if (doc.task.type === "epic") {
9397
- const boardDir = path7.dirname(taskPath);
9941
+ const boardDir = path11.dirname(taskPath);
9398
9942
  childStates = resolveEpicChildStates(doc.task, boardDir, logsDir);
9399
9943
  const incomplete = childStates.filter((child) => child.status === "active").map((child) => ({ id: child.id, title: child.title }));
9400
9944
  if (incomplete.length > 0) {
@@ -9415,32 +9959,43 @@ ${section}
9415
9959
  completedAt: now,
9416
9960
  updatedAt: now
9417
9961
  };
9418
- if (options.legacyMode) {
9419
- const legacyResult = completeTaskFileLegacy(taskPath, logsDir, doc, completedTask, childStates);
9420
- return incompleteChildren && legacyResult.success ? { ...legacyResult, incompleteChildren } : legacyResult;
9962
+ const destPath = path11.join(logsDir, path11.basename(taskPath));
9963
+ if (fs11.existsSync(destPath)) {
9964
+ return { success: false, error: `Task already exists in logs: ${doc.task.id}` };
9421
9965
  }
9422
- const record = (0, ledger_2.buildLedgerRecord)(completedTask, doc.body, {
9966
+ const completedBody = archivedBody(doc, childStates);
9967
+ const record = (0, ledger_2.buildLedgerRecord)(completedTask, completedBody, {
9423
9968
  summary: options.summary,
9424
9969
  filesChanged: options.filesChanged,
9425
9970
  completedAt: now,
9426
9971
  columnHistory: options.columnHistory,
9427
9972
  validationAttempts: options.validationAttempts
9428
9973
  });
9429
- let ledgerPath;
9430
9974
  try {
9431
- ledgerPath = (0, ledger_2.appendLedgerRecord)(logsDir, record);
9975
+ (0, ledger_2.appendLedgerRecord)(logsDir, record);
9432
9976
  } catch (err) {
9433
9977
  return { success: false, error: `Failed to append ledger record: ${err}` };
9434
9978
  }
9435
9979
  try {
9436
- fs7.unlinkSync(taskPath);
9980
+ fs11.mkdirSync(logsDir, { recursive: true });
9981
+ writeTaskFileExclusive(destPath, completedTask, completedBody);
9982
+ } catch (err) {
9983
+ rollbackLedgerAppend(logsDir, record);
9984
+ return { success: false, error: `Failed to complete task: ${err}` };
9985
+ }
9986
+ try {
9987
+ fs11.unlinkSync(taskPath);
9437
9988
  return {
9438
9989
  success: true,
9439
9990
  task: completedTask,
9440
- filePath: ledgerPath,
9991
+ filePath: destPath,
9441
9992
  ...incompleteChildren && { incompleteChildren }
9442
9993
  };
9443
9994
  } catch (err) {
9995
+ try {
9996
+ fs11.unlinkSync(destPath);
9997
+ } catch {
9998
+ }
9444
9999
  rollbackLedgerAppend(logsDir, record);
9445
10000
  return { success: false, error: `Failed to finalize completion: ${err}` };
9446
10001
  }
@@ -9451,7 +10006,7 @@ ${section}
9451
10006
  return { success: false, error: `Failed to read task file: ${taskPath}` };
9452
10007
  }
9453
10008
  try {
9454
- fs7.unlinkSync(taskPath);
10009
+ fs11.unlinkSync(taskPath);
9455
10010
  return { success: true, task: doc.task };
9456
10011
  } catch (err) {
9457
10012
  return { success: false, error: `Failed to delete task file: ${err}` };
@@ -9526,7 +10081,7 @@ ${section}
9526
10081
  }
9527
10082
  function findTask(boardDir, taskId) {
9528
10083
  try {
9529
- const directPath = path7.join(boardDir, (0, taskFile_1.taskFileName)(taskId));
10084
+ const directPath = path11.join(boardDir, (0, taskFile_1.taskFileName)(taskId));
9530
10085
  const directDoc = (0, taskFile_1.readTaskFile)(directPath);
9531
10086
  if (directDoc && directDoc.task.id === taskId) {
9532
10087
  return directDoc;
@@ -9759,7 +10314,7 @@ ${section}
9759
10314
  if (doc.task.contract?.status !== "draft")
9760
10315
  continue;
9761
10316
  const task = applyContractActivation(doc.task, (/* @__PURE__ */ new Date()).toISOString());
9762
- const taskPath = doc.filePath ?? path7.join(boardDir, (0, taskFile_1.taskFileName)(task.id));
10317
+ const taskPath = doc.filePath ?? path11.join(boardDir, (0, taskFile_1.taskFileName)(task.id));
9763
10318
  (0, taskFile_1.writeTaskFile)(taskPath, task, doc.body);
9764
10319
  activated.push(task.id);
9765
10320
  }
@@ -9775,196 +10330,6 @@ ${section}
9775
10330
  }
9776
10331
  });
9777
10332
 
9778
- // ../core/dist/workspace.js
9779
- var require_workspace = __commonJS({
9780
- "../core/dist/workspace.js"(exports) {
9781
- "use strict";
9782
- var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
9783
- if (k2 === void 0) k2 = k;
9784
- var desc = Object.getOwnPropertyDescriptor(m, k);
9785
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
9786
- desc = { enumerable: true, get: function() {
9787
- return m[k];
9788
- } };
9789
- }
9790
- Object.defineProperty(o, k2, desc);
9791
- }) : (function(o, m, k, k2) {
9792
- if (k2 === void 0) k2 = k;
9793
- o[k2] = m[k];
9794
- }));
9795
- var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? (function(o, v) {
9796
- Object.defineProperty(o, "default", { enumerable: true, value: v });
9797
- }) : function(o, v) {
9798
- o["default"] = v;
9799
- });
9800
- var __importStar = exports && exports.__importStar || /* @__PURE__ */ (function() {
9801
- var ownKeys = function(o) {
9802
- ownKeys = Object.getOwnPropertyNames || function(o2) {
9803
- var ar = [];
9804
- for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
9805
- return ar;
9806
- };
9807
- return ownKeys(o);
9808
- };
9809
- return function(mod) {
9810
- if (mod && mod.__esModule) return mod;
9811
- var result = {};
9812
- if (mod != null) {
9813
- for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
9814
- }
9815
- __setModuleDefault(result, mod);
9816
- return result;
9817
- };
9818
- })();
9819
- Object.defineProperty(exports, "__esModule", { value: true });
9820
- exports.getV2Dirs = getV2Dirs2;
9821
- exports.isV2 = isV25;
9822
- exports.ensureV2Dirs = ensureV2Dirs2;
9823
- exports.getTaskFilePath = getTaskFilePath2;
9824
- exports.getLogFilePath = getLogFilePath2;
9825
- exports.findV2Task = findV2Task2;
9826
- exports.extractDescription = extractDescription2;
9827
- exports.extractLog = extractLog2;
9828
- exports.composeBody = composeBody2;
9829
- exports.readV2BoardConfig = readV2BoardConfig2;
9830
- exports.buildBoardFromV2 = buildBoardFromV22;
9831
- var fs7 = __importStar(__require("fs"));
9832
- var path7 = __importStar(__require("path"));
9833
- var parser_1 = require_parser();
9834
- var taskFile_1 = require_taskFile();
9835
- function getV2Dirs2(brainfilePath) {
9836
- const resolvedPath = path7.resolve(brainfilePath);
9837
- const dotDir = path7.dirname(resolvedPath);
9838
- return {
9839
- dotDir,
9840
- boardDir: path7.join(dotDir, "board"),
9841
- logsDir: path7.join(dotDir, "logs"),
9842
- brainfilePath: resolvedPath
9843
- };
9844
- }
9845
- function isV25(brainfilePath) {
9846
- const { boardDir } = getV2Dirs2(brainfilePath);
9847
- return fs7.existsSync(boardDir);
9848
- }
9849
- function ensureV2Dirs2(brainfilePath) {
9850
- const dirs = getV2Dirs2(brainfilePath);
9851
- fs7.mkdirSync(dirs.boardDir, { recursive: true });
9852
- fs7.mkdirSync(dirs.logsDir, { recursive: true });
9853
- return dirs;
9854
- }
9855
- function getTaskFilePath2(boardDir, taskId) {
9856
- return path7.join(boardDir, (0, taskFile_1.taskFileName)(taskId));
9857
- }
9858
- function getLogFilePath2(logsDir, taskId) {
9859
- return path7.join(logsDir, (0, taskFile_1.taskFileName)(taskId));
9860
- }
9861
- function findV2Task2(dirs, taskId, searchLogs = false) {
9862
- const taskPath = getTaskFilePath2(dirs.boardDir, taskId);
9863
- const taskDoc = (0, taskFile_1.readTaskFile)(taskPath);
9864
- if (taskDoc && taskDoc.task.id === taskId) {
9865
- return { doc: taskDoc, filePath: taskPath, isLog: false };
9866
- }
9867
- if (searchLogs) {
9868
- const logPath = getLogFilePath2(dirs.logsDir, taskId);
9869
- const logDoc = (0, taskFile_1.readTaskFile)(logPath);
9870
- if (logDoc && logDoc.task.id === taskId) {
9871
- return { doc: logDoc, filePath: logPath, isLog: true };
9872
- }
9873
- }
9874
- const boardDocs = (0, taskFile_1.readTasksDir)(dirs.boardDir);
9875
- const boardMatch = boardDocs.find((d) => d.task.id === taskId);
9876
- if (boardMatch) {
9877
- return {
9878
- doc: boardMatch,
9879
- filePath: boardMatch.filePath || taskPath,
9880
- isLog: false
9881
- };
9882
- }
9883
- if (searchLogs) {
9884
- const logDocs = (0, taskFile_1.readTasksDir)(dirs.logsDir);
9885
- const logMatch = logDocs.find((d) => d.task.id === taskId);
9886
- if (logMatch) {
9887
- return {
9888
- doc: logMatch,
9889
- filePath: logMatch.filePath || getLogFilePath2(dirs.logsDir, taskId),
9890
- isLog: true
9891
- };
9892
- }
9893
- }
9894
- return null;
9895
- }
9896
- function extractDescription2(body) {
9897
- const match = body.match(/## Description\n([\s\S]*?)(?=\n## |\n*$)/);
9898
- return match ? match[1].trim() || void 0 : void 0;
9899
- }
9900
- function extractLog2(body) {
9901
- const match = body.match(/## Log\n([\s\S]*?)(?=\n## |\n*$)/);
9902
- return match ? match[1].trim() || void 0 : void 0;
9903
- }
9904
- function composeBody2(description, log) {
9905
- const sections = [];
9906
- if (description && description.trim()) {
9907
- sections.push(`## Description
9908
- ${description.trim()}`);
9909
- }
9910
- if (log && log.trim()) {
9911
- sections.push(`## Log
9912
- ${log.trim()}`);
9913
- }
9914
- if (sections.length === 0)
9915
- return "";
9916
- return `${sections.join("\n\n")}
9917
- `;
9918
- }
9919
- function readV2BoardConfig2(brainfilePath) {
9920
- const content = fs7.readFileSync(brainfilePath, "utf-8");
9921
- const result = parser_1.BrainfileParser.parseWithErrors(content);
9922
- if (!result.board) {
9923
- throw new Error(`Failed to parse brainfile: ${result.error}`);
9924
- }
9925
- const board = result.board;
9926
- for (const col of board.columns) {
9927
- if (!col.tasks)
9928
- col.tasks = [];
9929
- }
9930
- return board;
9931
- }
9932
- function buildBoardFromV22(brainfilePath) {
9933
- const dirs = getV2Dirs2(brainfilePath);
9934
- const board = readV2BoardConfig2(brainfilePath);
9935
- const taskDocs = (0, taskFile_1.readTasksDir)(dirs.boardDir);
9936
- const tasksByColumn = /* @__PURE__ */ new Map();
9937
- for (const doc of taskDocs) {
9938
- const colId = doc.task.column || "todo";
9939
- if (!tasksByColumn.has(colId)) {
9940
- tasksByColumn.set(colId, []);
9941
- }
9942
- tasksByColumn.get(colId).push(doc);
9943
- }
9944
- for (const col of board.columns) {
9945
- const colTasks = tasksByColumn.get(col.id) || [];
9946
- colTasks.sort((a, b) => {
9947
- const posA = a.task.position ?? Number.MAX_SAFE_INTEGER;
9948
- const posB = b.task.position ?? Number.MAX_SAFE_INTEGER;
9949
- if (posA !== posB)
9950
- return posA - posB;
9951
- return a.task.id.localeCompare(b.task.id);
9952
- });
9953
- col.tasks = colTasks.map((doc) => {
9954
- const task = { ...doc.task };
9955
- if (!task.description) {
9956
- const description = extractDescription2(doc.body);
9957
- if (description)
9958
- task.description = description;
9959
- }
9960
- return task;
9961
- });
9962
- }
9963
- return board;
9964
- }
9965
- }
9966
- });
9967
-
9968
10333
  // ../core/dist/search.js
9969
10334
  var require_search = __commonJS({
9970
10335
  "../core/dist/search.js"(exports) {
@@ -10309,10 +10674,10 @@ var require_dist = __commonJS({
10309
10674
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports2, p)) __createBinding(exports2, m, p);
10310
10675
  };
10311
10676
  Object.defineProperty(exports, "__esModule", { value: true });
10312
- exports.archiveTasks = exports.deleteTasks = exports.patchTasks = exports.moveTasks = exports.setAllSubtasksCompleted = exports.setSubtasksCompleted = exports.updateSubtask = exports.deleteSubtask = exports.addSubtask = exports.patchTask = exports.restoreTask = exports.archiveTask = exports.updateStatsConfig = exports.updateBoardTitle = exports.toggleSubtask = exports.deleteTask = exports.updateTask = exports.addTask = exports.moveTask = exports.getAllTemplateIds = exports.getTemplateById = exports.processTemplate = exports.generateSubtaskId = exports.generateTaskId = exports.BUILT_IN_TEMPLATES = exports.hashBoardContent = exports.hashBoard = exports.diffBoards = exports.BrainfileLinter = exports.BrainfileValidator = exports.BrainfileSerializer = exports.loadSchemaHints = exports.parseSchemaHints = exports.inferRenderer = exports.inferType = exports.BrainfileParser = exports.buildContract = exports.parseDeliverableSpec = exports.normalizeToArray = exports.ALLOWED_DELIVERABLE_TYPES = exports.removeTaskContractConstraint = exports.addTaskContractConstraint = exports.removeTaskContractValidationCommand = exports.addTaskContractValidationCommand = exports.removeTaskContractDeliverable = exports.addTaskContractDeliverable = exports.patchTaskContract = exports.setTaskContractStatus = exports.clearTaskContract = exports.setTaskContract = void 0;
10313
- exports.BRAINFILE_STATE_BASENAME = exports.BRAINFILE_BASENAME = exports.DOT_BRAINFILE_DIRNAME = exports.ensureDotBrainfileGitignore = exports.ensureDotBrainfileDir = exports.getDotBrainfileGitignorePath = exports.getBrainfileStatePath = exports.getBrainfileStateDir = exports.resolveBrainfilePath = exports.findBrainfile = exports.EXCLUDE_DIRS = exports.BRAINFILE_GLOBS = exports.BRAINFILE_PATTERNS = exports.extractBrainfileSuffix = exports.isBrainfileName = exports.watchBrainfiles = exports.findNearestBrainfile = exports.findPrimaryBrainfile = exports.discover = exports.getParentTaskId = exports.isValidSubtaskId = exports.isValidTaskId = exports.generateNextSubtaskId = exports.generateSubtaskIdFromIndex = exports.generateNextTaskId = exports.getMaxTaskIdNumber = exports.extractTaskIdNumber = exports.getOverdueTasks = exports.getIncompleteSubtasksSummary = exports.getTasksWithIncompleteSubtasks = exports.isCompletionColumn = exports.findCompletionColumn = exports.columnExists = exports.getTotalTaskCount = exports.getColumnTaskCount = exports.searchTasks = exports.getTasksByAssignee = exports.getTasksByPriority = exports.getTasksByTag = exports.getAllTasks = exports.taskIdExists = exports.findTaskById = exports.findColumnByName = exports.findColumnById = exports.validateColumn = exports.validateType = exports.isTypeCompletable = exports.getBoardTypes = exports.deleteRule = exports.addRule = void 0;
10314
- exports.isLedgerContractStatus = exports.normalizePathValue = exports.getTaskContext = exports.getFileHistory = exports.queryLedger = exports.readLedger = exports.appendLedgerRecord = exports.buildLedgerRecord = exports.searchTasksRanked = exports.scoreTaskDocument = exports.taskMatchesFilters = exports.parseSearchQuery = exports.getEffectiveState = exports.activateTaskContractsByParent = exports.activateTaskContract = exports.attachTaskContract = exports.failTaskContract = exports.completeTaskContract = exports.deliverTaskContract = exports.pickupTaskContract = exports.updateSubtasksInFile = exports.toggleSubtasksInFile = exports.deleteSubtasksFromFile = exports.addSubtasksToFile = exports.searchLogs = exports.searchTaskFiles = exports.findTask = exports.listTasks = exports.appendLog = exports.deleteTaskFile = exports.completeTaskFile = exports.patchTaskFile = exports.moveTaskFileToColumn = exports.moveTaskFile = exports.addTaskFile = exports.generateNextFileTaskId = exports.DEFAULT_CONTRACT_COLUMN_MAP = exports.taskFileName = exports.readTasksDir = exports.writeTaskFile = exports.readTaskFile = exports.serializeTaskContent = exports.parseTaskContent = exports.writeBoardConfig = exports.readBoardConfig = exports.serializeBoardConfig = exports.parseBoardConfig = exports.formatTaskForLinear = exports.formatTaskForGitHub = exports.DOT_BRAINFILE_GITIGNORE_BASENAME = void 0;
10315
- exports.Brainfile = exports.buildBoardFromV2 = exports.readV2BoardConfig = exports.composeBody = exports.extractLog = exports.extractDescription = exports.findV2Task = exports.getLogFilePath = exports.getTaskFilePath = exports.ensureV2Dirs = exports.isV2 = exports.getV2Dirs = exports.topologicalSort = exports.DependencyCycleError = exports.MissingDependencyError = exports.ledgerRecordToCompletedRef = exports.taskToCompletedRef = void 0;
10677
+ exports.patchTasks = exports.moveTasks = exports.setAllSubtasksCompleted = exports.setSubtasksCompleted = exports.updateSubtask = exports.deleteSubtask = exports.addSubtask = exports.patchTask = exports.restoreTask = exports.archiveTask = exports.updateStatsConfig = exports.updateBoardTitle = exports.toggleSubtask = exports.deleteTask = exports.updateTask = exports.addTask = exports.moveTask = exports.getAllTemplateIds = exports.getTemplateById = exports.processTemplate = exports.generateSubtaskId = exports.generateTaskId = exports.BUILT_IN_TEMPLATES = exports.hashBoardContent = exports.hashBoard = exports.diffBoards = exports.BrainfileLinter = exports.BrainfileValidator = exports.serializeFrontmatter = exports.parseFrontmatter = exports.BrainfileSerializer = exports.loadSchemaHints = exports.parseSchemaHints = exports.inferRenderer = exports.inferType = exports.BrainfileParser = exports.buildContract = exports.parseDeliverableSpec = exports.normalizeToArray = exports.ALLOWED_DELIVERABLE_TYPES = exports.removeTaskContractConstraint = exports.addTaskContractConstraint = exports.removeTaskContractValidationCommand = exports.addTaskContractValidationCommand = exports.removeTaskContractDeliverable = exports.addTaskContractDeliverable = exports.patchTaskContract = exports.setTaskContractStatus = exports.clearTaskContract = exports.setTaskContract = void 0;
10678
+ exports.BRAINFILE_BASENAME = exports.DOT_BRAINFILE_DIRNAME = exports.atomicWriteFileSync = exports.ensureDotBrainfileGitignore = exports.ensureDotBrainfileDir = exports.getDotBrainfileGitignorePath = exports.getBrainfileStatePath = exports.getBrainfileStateDir = exports.resolveBrainfilePath = exports.findBrainfile = exports.EXCLUDE_DIRS = exports.BRAINFILE_GLOBS = exports.BRAINFILE_PATTERNS = exports.extractBrainfileSuffix = exports.isBrainfileName = exports.watchBrainfiles = exports.findNearestBrainfile = exports.findPrimaryBrainfile = exports.discover = exports.getParentTaskId = exports.isValidSubtaskId = exports.isValidTaskId = exports.generateNextSubtaskId = exports.generateSubtaskIdFromIndex = exports.generateNextTaskId = exports.getMaxTaskIdNumber = exports.extractTaskIdNumber = exports.getOverdueTasks = exports.getIncompleteSubtasksSummary = exports.getTasksWithIncompleteSubtasks = exports.isCompletionColumn = exports.findCompletionColumn = exports.columnExists = exports.getTotalTaskCount = exports.getColumnTaskCount = exports.searchTasks = exports.getTasksByAssignee = exports.getTasksByPriority = exports.getTasksByTag = exports.getAllTasks = exports.taskIdExists = exports.findTaskById = exports.findColumnByName = exports.findColumnById = exports.validateColumn = exports.validateType = exports.isTypeCompletable = exports.getBoardTypes = exports.archiveTasks = exports.deleteTasks = void 0;
10679
+ exports.activateTaskContract = exports.attachTaskContract = exports.failTaskContract = exports.completeTaskContract = exports.deliverTaskContract = exports.pickupTaskContract = exports.updateSubtasksInFile = exports.toggleSubtasksInFile = exports.deleteSubtasksFromFile = exports.addSubtasksToFile = exports.searchLogs = exports.searchTaskFiles = exports.findTask = exports.listTasks = exports.appendLog = exports.deleteTaskFile = exports.completeTaskFile = exports.patchTaskFile = exports.moveTaskFileToColumn = exports.moveTaskFile = exports.addTaskFile = exports.generateNextFileTaskId = exports.DEFAULT_CONTRACT_COLUMN_MAP = exports.taskFileName = exports.readTasksDir = exports.writeTaskFile = exports.readTaskFile = exports.serializeTaskContent = exports.parseTaskContent = exports.writeBoardConfig = exports.readBoardConfig = exports.serializeBoardConfig = exports.parseBoardConfig = exports.formatTaskForLinear = exports.formatTaskForGitHub = exports.BRIEF_STATE_VERSION = exports.sanitizeAgentFilename = exports.getBriefStatePath = exports.getBriefStateDir = exports.writeBriefState = exports.readBriefState = exports.relativeTime = exports.parseNotes = exports.parseNoteLine = exports.isEmptyBrief = exports.buildBrief = exports.DOT_BRAINFILE_STATE_IGNORE_ENTRY = exports.DOT_BRAINFILE_STATE_DIRNAME = exports.DOT_BRAINFILE_GITIGNORE_BASENAME = exports.BRAINFILE_STATE_BASENAME = void 0;
10680
+ exports.Brainfile = exports.buildBoardFromV2 = exports.readV2BoardConfig = exports.composeBody = exports.extractLog = exports.extractDescription = exports.findV2Task = exports.getLogFilePath = exports.getTaskFilePath = exports.ensureV2Dirs = exports.isV2 = exports.getV2Dirs = exports.topologicalSort = exports.DependencyCycleError = exports.MissingDependencyError = exports.ledgerRecordToCompletedRef = exports.taskToCompletedRef = exports.isLedgerContractStatus = exports.normalizePathValue = exports.getTaskContext = exports.getFileHistory = exports.queryLedger = exports.readLedger = exports.appendLedgerRecord = exports.buildLedgerRecord = exports.searchTasksRanked = exports.scoreTaskDocument = exports.taskMatchesFilters = exports.parseSearchQuery = exports.getEffectiveState = exports.activateTaskContractsByParent = void 0;
10316
10681
  __exportStar(require_types2(), exports);
10317
10682
  __exportStar(require_contract(), exports);
10318
10683
  var contract_1 = require_contract2();
@@ -10381,6 +10746,13 @@ var require_dist = __commonJS({
10381
10746
  Object.defineProperty(exports, "BrainfileSerializer", { enumerable: true, get: function() {
10382
10747
  return serializer_1.BrainfileSerializer;
10383
10748
  } });
10749
+ var frontmatter_1 = require_frontmatter();
10750
+ Object.defineProperty(exports, "parseFrontmatter", { enumerable: true, get: function() {
10751
+ return frontmatter_1.parseFrontmatter;
10752
+ } });
10753
+ Object.defineProperty(exports, "serializeFrontmatter", { enumerable: true, get: function() {
10754
+ return frontmatter_1.serializeFrontmatter;
10755
+ } });
10384
10756
  var validator_1 = require_validator();
10385
10757
  Object.defineProperty(exports, "BrainfileValidator", { enumerable: true, get: function() {
10386
10758
  return validator_1.BrainfileValidator;
@@ -10476,12 +10848,6 @@ var require_dist = __commonJS({
10476
10848
  Object.defineProperty(exports, "archiveTasks", { enumerable: true, get: function() {
10477
10849
  return operations_1.archiveTasks;
10478
10850
  } });
10479
- Object.defineProperty(exports, "addRule", { enumerable: true, get: function() {
10480
- return operations_1.addRule;
10481
- } });
10482
- Object.defineProperty(exports, "deleteRule", { enumerable: true, get: function() {
10483
- return operations_1.deleteRule;
10484
- } });
10485
10851
  var boardValidation_1 = require_boardValidation();
10486
10852
  Object.defineProperty(exports, "getBoardTypes", { enumerable: true, get: function() {
10487
10853
  return boardValidation_1.getBoardTypes;
@@ -10622,6 +10988,9 @@ var require_dist = __commonJS({
10622
10988
  Object.defineProperty(exports, "ensureDotBrainfileGitignore", { enumerable: true, get: function() {
10623
10989
  return files_1.ensureDotBrainfileGitignore;
10624
10990
  } });
10991
+ Object.defineProperty(exports, "atomicWriteFileSync", { enumerable: true, get: function() {
10992
+ return files_1.atomicWriteFileSync;
10993
+ } });
10625
10994
  Object.defineProperty(exports, "DOT_BRAINFILE_DIRNAME", { enumerable: true, get: function() {
10626
10995
  return files_1.DOT_BRAINFILE_DIRNAME;
10627
10996
  } });
@@ -10634,6 +11003,47 @@ var require_dist = __commonJS({
10634
11003
  Object.defineProperty(exports, "DOT_BRAINFILE_GITIGNORE_BASENAME", { enumerable: true, get: function() {
10635
11004
  return files_1.DOT_BRAINFILE_GITIGNORE_BASENAME;
10636
11005
  } });
11006
+ Object.defineProperty(exports, "DOT_BRAINFILE_STATE_DIRNAME", { enumerable: true, get: function() {
11007
+ return files_1.DOT_BRAINFILE_STATE_DIRNAME;
11008
+ } });
11009
+ Object.defineProperty(exports, "DOT_BRAINFILE_STATE_IGNORE_ENTRY", { enumerable: true, get: function() {
11010
+ return files_1.DOT_BRAINFILE_STATE_IGNORE_ENTRY;
11011
+ } });
11012
+ var brief_1 = require_brief();
11013
+ Object.defineProperty(exports, "buildBrief", { enumerable: true, get: function() {
11014
+ return brief_1.buildBrief;
11015
+ } });
11016
+ Object.defineProperty(exports, "isEmptyBrief", { enumerable: true, get: function() {
11017
+ return brief_1.isEmptyBrief;
11018
+ } });
11019
+ Object.defineProperty(exports, "parseNoteLine", { enumerable: true, get: function() {
11020
+ return brief_1.parseNoteLine;
11021
+ } });
11022
+ Object.defineProperty(exports, "parseNotes", { enumerable: true, get: function() {
11023
+ return brief_1.parseNotes;
11024
+ } });
11025
+ Object.defineProperty(exports, "relativeTime", { enumerable: true, get: function() {
11026
+ return brief_1.relativeTime;
11027
+ } });
11028
+ var briefState_1 = require_briefState();
11029
+ Object.defineProperty(exports, "readBriefState", { enumerable: true, get: function() {
11030
+ return briefState_1.readBriefState;
11031
+ } });
11032
+ Object.defineProperty(exports, "writeBriefState", { enumerable: true, get: function() {
11033
+ return briefState_1.writeBriefState;
11034
+ } });
11035
+ Object.defineProperty(exports, "getBriefStateDir", { enumerable: true, get: function() {
11036
+ return briefState_1.getBriefStateDir;
11037
+ } });
11038
+ Object.defineProperty(exports, "getBriefStatePath", { enumerable: true, get: function() {
11039
+ return briefState_1.getBriefStatePath;
11040
+ } });
11041
+ Object.defineProperty(exports, "sanitizeAgentFilename", { enumerable: true, get: function() {
11042
+ return briefState_1.sanitizeAgentFilename;
11043
+ } });
11044
+ Object.defineProperty(exports, "BRIEF_STATE_VERSION", { enumerable: true, get: function() {
11045
+ return briefState_1.BRIEF_STATE_VERSION;
11046
+ } });
10637
11047
  var formatters_1 = require_formatters();
10638
11048
  Object.defineProperty(exports, "formatTaskForGitHub", { enumerable: true, get: function() {
10639
11049
  return formatters_1.formatTaskForGitHub;
@@ -10918,16 +11328,6 @@ var require_dist = __commonJS({
10918
11328
  static findTaskLocation(content, taskId) {
10919
11329
  return parser_2.BrainfileParser.findTaskLocation(content, taskId);
10920
11330
  }
10921
- /**
10922
- * Find the location of a rule in the file content
10923
- * @param content - The markdown content
10924
- * @param ruleId - The rule ID to find
10925
- * @param ruleType - The type of rule
10926
- * @returns Line and column location or null if not found
10927
- */
10928
- static findRuleLocation(content, ruleId, ruleType) {
10929
- return parser_2.BrainfileParser.findRuleLocation(content, ruleId, ruleType);
10930
- }
10931
11331
  };
10932
11332
  exports.Brainfile = Brainfile2;
10933
11333
  var parser_2 = require_parser();
@@ -10938,10 +11338,8 @@ var require_dist = __commonJS({
10938
11338
  }
10939
11339
  });
10940
11340
 
10941
- // src/utils/brainfile-path.ts
10942
- var import_core3 = __toESM(require_dist());
10943
- import * as fs2 from "fs";
10944
- import * as path2 from "path";
11341
+ // src/utils/cli-error.ts
11342
+ var import_chalk2 = __toESM(require_source());
10945
11343
 
10946
11344
  // src/utils/errorHandler.ts
10947
11345
  var import_chalk = __toESM(require_source());
@@ -11107,6 +11505,23 @@ ${availableTasks.map((t) => ` - ${t}`).join("\n")}`;
11107
11505
  function validationError2(message) {
11108
11506
  return new CLIError(`Validation error: ${message}`, ExitCode.USER_ERROR);
11109
11507
  }
11508
+ function renderCliError(error) {
11509
+ if (error instanceof CLIError) {
11510
+ console.error(import_chalk2.default.red(`Error: ${error.message}`));
11511
+ if (error.details) {
11512
+ console.error("");
11513
+ console.error(import_chalk2.default.gray(error.details));
11514
+ }
11515
+ process.exit(error.exitCode);
11516
+ }
11517
+ console.error(error instanceof Error ? error.stack : String(error));
11518
+ process.exit(ExitCode.USER_ERROR);
11519
+ }
11520
+
11521
+ // src/utils/brainfile-path.ts
11522
+ var import_core3 = __toESM(require_dist());
11523
+ import * as fs3 from "fs";
11524
+ import * as path3 from "path";
11110
11525
 
11111
11526
  // src/utils/v2-only.ts
11112
11527
  var import_core2 = __toESM(require_dist());
@@ -11127,78 +11542,415 @@ function assertV2Brainfile(brainfilePath) {
11127
11542
  }
11128
11543
  }
11129
11544
 
11545
+ // src/utils/board-repo.ts
11546
+ import * as fs2 from "fs";
11547
+ import * as os from "os";
11548
+ import * as path2 from "path";
11549
+ import { spawnSync } from "child_process";
11550
+ var DEFAULT_BOARD_BRANCH = "brainfile";
11551
+ var BOARD_BRANCH_CONFIG_KEY = "brainfile.branch";
11552
+ var EXCLUDE_ENTRY = ".brainfile/";
11553
+ var EXCLUDE_MARKER = "# brainfile: board worktree (managed by `brainfile`)";
11554
+ var FALLBACK_IDENTITY_NAME = "brainfile";
11555
+ var FALLBACK_IDENTITY_EMAIL = "brainfile@localhost";
11556
+ var MAX_MESSAGE_LENGTH = 72;
11557
+ var BOARD_SUBDIRS = ["board", "logs"];
11558
+ function git(args, cwd, env) {
11559
+ const result = spawnSync("git", args, {
11560
+ cwd,
11561
+ encoding: "utf-8",
11562
+ env: env ? { ...process.env, ...env } : process.env,
11563
+ stdio: ["ignore", "pipe", "pipe"]
11564
+ });
11565
+ return {
11566
+ ok: result.status === 0,
11567
+ status: result.status,
11568
+ stdout: (result.stdout ?? "").toString().trim(),
11569
+ stderr: (result.stderr ?? "").toString().trim()
11570
+ };
11571
+ }
11572
+ function gitToplevel(cwd) {
11573
+ const r = git(["rev-parse", "--show-toplevel"], cwd);
11574
+ return r.ok && r.stdout ? path2.resolve(r.stdout) : null;
11575
+ }
11576
+ function gitCommonDir(cwd) {
11577
+ const r = git(["rev-parse", "--git-common-dir"], cwd);
11578
+ if (!r.ok || !r.stdout) return null;
11579
+ return path2.resolve(cwd, r.stdout);
11580
+ }
11581
+ function listWorktrees(cwd) {
11582
+ const r = git(["worktree", "list", "--porcelain"], cwd);
11583
+ if (!r.ok) return [];
11584
+ const entries = [];
11585
+ let current = null;
11586
+ for (const line of r.stdout.split("\n")) {
11587
+ if (line.startsWith("worktree ")) {
11588
+ current = { path: path2.resolve(line.slice("worktree ".length)), bare: false, detached: false };
11589
+ entries.push(current);
11590
+ } else if (!current) {
11591
+ continue;
11592
+ } else if (line.startsWith("HEAD ")) {
11593
+ current.head = line.slice(5);
11594
+ } else if (line.startsWith("branch ")) {
11595
+ current.branch = line.slice(7);
11596
+ } else if (line === "bare") {
11597
+ current.bare = true;
11598
+ } else if (line === "detached") {
11599
+ current.detached = true;
11600
+ }
11601
+ }
11602
+ return entries;
11603
+ }
11604
+ function boardBranch(cwd) {
11605
+ const r = git(["config", "--get", BOARD_BRANCH_CONFIG_KEY], cwd);
11606
+ return r.ok && r.stdout ? r.stdout : DEFAULT_BOARD_BRANCH;
11607
+ }
11608
+ function boardRepoKind(dotDir) {
11609
+ try {
11610
+ const st = fs2.statSync(path2.join(dotDir, ".git"));
11611
+ if (st.isFile()) return "linked";
11612
+ if (st.isDirectory()) return "standalone";
11613
+ } catch {
11614
+ }
11615
+ return null;
11616
+ }
11617
+ function isTrackedBoard(dotDir) {
11618
+ return boardRepoKind(dotDir) !== null;
11619
+ }
11620
+ function findTrackedBoardDir(cwd) {
11621
+ const worktrees = listWorktrees(cwd);
11622
+ if (worktrees.length === 0) return null;
11623
+ const wanted = `refs/heads/${boardBranch(cwd)}`;
11624
+ const hit = worktrees.find((w) => w.branch === wanted);
11625
+ if (!hit) return null;
11626
+ return fs2.existsSync(path2.join(hit.path, "brainfile.md")) ? hit.path : null;
11627
+ }
11628
+ function materializeBoardWorktree(cwd) {
11629
+ const branch = boardBranch(cwd);
11630
+ const hasLocal = git(["rev-parse", "--verify", "--quiet", `refs/heads/${branch}`], cwd).ok;
11631
+ const configuredRemote = git(["config", "--get", "brainfile.remote"], cwd).stdout || null;
11632
+ const configuredBranch = git(["config", "--get", "brainfile.remoteBranch"], cwd).stdout || branch;
11633
+ let upstream = null;
11634
+ if (!hasLocal) {
11635
+ if (configuredRemote) {
11636
+ const ref = `${configuredRemote}/${configuredBranch}`;
11637
+ if (!git(["rev-parse", "--verify", "--quiet", `refs/remotes/${ref}`], cwd).ok) {
11638
+ git(["fetch", "--quiet", configuredRemote, configuredBranch], cwd);
11639
+ }
11640
+ if (git(["rev-parse", "--verify", "--quiet", `refs/remotes/${ref}`], cwd).ok) upstream = ref;
11641
+ }
11642
+ if (!upstream && git(["rev-parse", "--verify", "--quiet", `refs/remotes/origin/${branch}`], cwd).ok) {
11643
+ upstream = `origin/${branch}`;
11644
+ }
11645
+ }
11646
+ if (!hasLocal && !upstream) return null;
11647
+ const worktrees = listWorktrees(cwd);
11648
+ if (worktrees.length === 0) return null;
11649
+ if (worktrees.some((w) => w.branch === `refs/heads/${branch}`)) return null;
11650
+ const main = worktrees[0];
11651
+ let target;
11652
+ if (main.bare) {
11653
+ const common = gitCommonDir(cwd);
11654
+ if (!common) return null;
11655
+ target = path2.join(common, "brainfile");
11656
+ } else {
11657
+ target = path2.join(main.path, ".brainfile");
11658
+ }
11659
+ if (fs2.existsSync(target)) return null;
11660
+ const r = hasLocal ? git(["worktree", "add", "--quiet", target, branch], cwd) : git(["worktree", "add", "--quiet", "--track", "-b", branch, target, upstream], cwd);
11661
+ if (!r.ok) return null;
11662
+ ensureExcludeEntry(cwd);
11663
+ for (const sub of BOARD_SUBDIRS) fs2.mkdirSync(path2.join(target, sub), { recursive: true });
11664
+ const cameFrom = upstream ? upstream.slice(0, upstream.indexOf("/")) : null;
11665
+ if (cameFrom && !configuredRemote) git(["config", "brainfile.remote", cameFrom], cwd);
11666
+ const relative3 = path2.relative(process.cwd(), target) || ".";
11667
+ process.stderr.write(
11668
+ cameFrom ? `Checked out the shared board from ${cameFrom} into ${relative3}/
11669
+ ` : `Checked out the board from the '${branch}' branch into ${relative3}/
11670
+ `
11671
+ );
11672
+ return target;
11673
+ }
11674
+ function ensureExcludeEntry(cwd) {
11675
+ const common = gitCommonDir(cwd);
11676
+ if (!common) return false;
11677
+ const file = path2.join(common, "info", "exclude");
11678
+ const existing = fs2.existsSync(file) ? fs2.readFileSync(file, "utf-8") : "";
11679
+ const present = existing.split(/\r?\n/).some((line) => line.trim() === EXCLUDE_ENTRY || line.trim() === ".brainfile");
11680
+ if (present) return false;
11681
+ fs2.mkdirSync(path2.dirname(file), { recursive: true });
11682
+ const separator = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
11683
+ fs2.appendFileSync(file, `${separator}${EXCLUDE_MARKER}
11684
+ ${EXCLUDE_ENTRY}
11685
+ `, "utf-8");
11686
+ return true;
11687
+ }
11688
+ function removeExcludeEntry(cwd) {
11689
+ const common = gitCommonDir(cwd);
11690
+ if (!common) return false;
11691
+ const file = path2.join(common, "info", "exclude");
11692
+ if (!fs2.existsSync(file)) return false;
11693
+ const lines = fs2.readFileSync(file, "utf-8").split(/\r?\n/);
11694
+ const at = lines.indexOf(EXCLUDE_MARKER);
11695
+ if (at < 0) return false;
11696
+ const removeCount = lines[at + 1]?.trim() === EXCLUDE_ENTRY ? 2 : 1;
11697
+ lines.splice(at, removeCount);
11698
+ fs2.writeFileSync(file, lines.join("\n"), "utf-8");
11699
+ return true;
11700
+ }
11701
+ function createOrphanBoardWorktree(repoCwd, dotDir, branch) {
11702
+ const r = git(["worktree", "add", "--quiet", "--orphan", "-b", branch, dotDir], repoCwd);
11703
+ if (!r.ok) {
11704
+ throw new Error(
11705
+ `Could not create the board worktree on branch '${branch}' (git >= 2.42 is required for --orphan): ${r.stderr}`
11706
+ );
11707
+ }
11708
+ }
11709
+ function initStandaloneBoardRepo(dotDir) {
11710
+ const parent = path2.dirname(dotDir);
11711
+ let r = git(["init", "--quiet", "-b", "main", dotDir], parent);
11712
+ if (!r.ok) r = git(["init", "--quiet", dotDir], parent);
11713
+ if (!r.ok) throw new Error(`Could not initialize a git repository in ${dotDir}: ${r.stderr}`);
11714
+ }
11715
+ function boardIsDirty(dotDir) {
11716
+ const r = git(["status", "--porcelain"], dotDir);
11717
+ return r.ok && r.stdout.length > 0;
11718
+ }
11719
+ function unmergedFiles(dotDir) {
11720
+ const r = git(["diff", "--name-only", "--diff-filter=U"], dotDir);
11721
+ return r.ok ? r.stdout.split("\n").map((l) => l.trim()).filter(Boolean) : [];
11722
+ }
11723
+ function boardDirtyFiles(dotDir) {
11724
+ const r = git(["status", "--porcelain"], dotDir);
11725
+ if (!r.ok || !r.stdout) return [];
11726
+ return r.stdout.split("\n").map((line) => line.replace(/^[ MADRCU?!]{1,2}\s+/, "").replace(/^.* -> /, "").trim()).filter(Boolean);
11727
+ }
11728
+ function identityArgs(dotDir) {
11729
+ const args = [];
11730
+ if (!git(["config", "--get", "user.name"], dotDir).stdout) args.push("-c", `user.name=${FALLBACK_IDENTITY_NAME}`);
11731
+ if (!git(["config", "--get", "user.email"], dotDir).stdout) args.push("-c", `user.email=${FALLBACK_IDENTITY_EMAIL}`);
11732
+ return args;
11733
+ }
11734
+ function sanitizeAgent(agent) {
11735
+ return agent.replace(/[<>\r\n]/g, "").trim();
11736
+ }
11737
+ var commitListeners = [];
11738
+ function onBoardCommitted(listener) {
11739
+ if (!commitListeners.includes(listener)) commitListeners.push(listener);
11740
+ }
11741
+ function notifyCommitted(dotDir) {
11742
+ for (const listener of commitListeners) {
11743
+ try {
11744
+ listener(dotDir);
11745
+ } catch {
11746
+ }
11747
+ }
11748
+ }
11749
+ function ensureBoardDirs(dotDir) {
11750
+ let created = false;
11751
+ for (const sub of BOARD_SUBDIRS) {
11752
+ const dir = path2.join(dotDir, sub);
11753
+ try {
11754
+ fs2.mkdirSync(dir, { recursive: true });
11755
+ const keep = path2.join(dir, ".gitkeep");
11756
+ if (!fs2.existsSync(keep)) {
11757
+ fs2.writeFileSync(keep, "", "utf-8");
11758
+ created = true;
11759
+ }
11760
+ } catch {
11761
+ }
11762
+ }
11763
+ return created;
11764
+ }
11765
+ function commitBoard(dotDir, options) {
11766
+ if (!isTrackedBoard(dotDir)) return false;
11767
+ ensureBoardDirs(dotDir);
11768
+ if (unmergedFiles(dotDir).length > 0) return false;
11769
+ if (!git(["add", "-A"], dotDir).ok) return false;
11770
+ if (!boardIsDirty(dotDir)) return false;
11771
+ const env = {};
11772
+ const agent = options.agent ? sanitizeAgent(options.agent) : "";
11773
+ if (agent) {
11774
+ env.GIT_AUTHOR_NAME = agent;
11775
+ env.GIT_AUTHOR_EMAIL = `${agent}@brainfile.local`;
11776
+ }
11777
+ const message = oneLine(options.message);
11778
+ const r = git(
11779
+ [...identityArgs(dotDir), "-c", "commit.gpgsign=false", "commit", "--quiet", "--no-verify", "-m", message],
11780
+ dotDir,
11781
+ env
11782
+ );
11783
+ if (r.ok) notifyCommitted(dotDir);
11784
+ return r.ok;
11785
+ }
11786
+ function commitHandEdits(dotDir) {
11787
+ if (!isTrackedBoard(dotDir)) return false;
11788
+ const keepOnly = (f) => f.endsWith(".gitkeep") || BOARD_SUBDIRS.some((sub) => f === `${sub}/`);
11789
+ if (ensureBoardDirs(dotDir) && boardDirtyFiles(dotDir).every(keepOnly)) {
11790
+ commitBoard(dotDir, { message: "chore: keep board/ and logs/ in git" });
11791
+ }
11792
+ const files = boardDirtyFiles(dotDir);
11793
+ if (files.length === 0) return false;
11794
+ const summary = files.length <= 3 ? files.join(", ") : `${files.length} files`;
11795
+ return commitBoard(dotDir, { message: `edit: ${summary}` });
11796
+ }
11797
+ function resolveAgentName(argv = process.argv) {
11798
+ if (process.env.BRAINFILE_AGENT) return process.env.BRAINFILE_AGENT;
11799
+ const flag = argv.indexOf("--agent");
11800
+ if (flag >= 0 && argv[flag + 1] && !argv[flag + 1].startsWith("-")) return argv[flag + 1];
11801
+ const inline = argv.find((a) => a.startsWith("--agent="));
11802
+ return inline ? inline.slice("--agent=".length) : null;
11803
+ }
11804
+ function homeBoardDir() {
11805
+ return path2.join(os.homedir(), ".brainfile");
11806
+ }
11807
+ function isGlobalBoardRequested(argv = process.argv) {
11808
+ if (process.env.BRAINFILE_GLOBAL === "1") return true;
11809
+ return argv.includes("-g") || argv.includes("--global");
11810
+ }
11811
+ function oneLine(message) {
11812
+ const flat = message.replace(/\s+/g, " ").trim();
11813
+ return flat.length > MAX_MESSAGE_LENGTH ? `${flat.slice(0, MAX_MESSAGE_LENGTH - 1)}\u2026` : flat;
11814
+ }
11815
+ function describeCommandForCommit(command) {
11816
+ const names = [];
11817
+ let cursor = command;
11818
+ while (cursor && cursor.parent) {
11819
+ names.unshift(cursor.name());
11820
+ cursor = cursor.parent;
11821
+ }
11822
+ const opts = command.opts();
11823
+ const idValue = opts.task ?? opts.taskId;
11824
+ const id = Array.isArray(idValue) ? idValue.join(",") : typeof idValue === "string" ? idValue : "";
11825
+ const positional = command.args.filter((a) => typeof a === "string" && a.length > 0);
11826
+ const detail = [opts.title, opts.column, positional.join(" ")].find((v) => typeof v === "string" && v.length > 0) ?? "";
11827
+ const head = `${names.join(" ")}${id ? ` ${id}` : ""}`;
11828
+ return oneLine(detail ? `${head}: ${detail}` : head);
11829
+ }
11830
+ var pendingCommits = /* @__PURE__ */ new Map();
11831
+ var flushTimer = null;
11832
+ var exitHookInstalled = false;
11833
+ function scheduleBoardCommit(brainfilePath, message, delayMs = 2e3) {
11834
+ const dotDir = path2.dirname(path2.resolve(brainfilePath));
11835
+ if (!isTrackedBoard(dotDir)) return;
11836
+ const entry = pendingCommits.get(dotDir) ?? { messages: [], agent: resolveAgentName() };
11837
+ entry.messages.push(message);
11838
+ pendingCommits.set(dotDir, entry);
11839
+ if (flushTimer) clearTimeout(flushTimer);
11840
+ flushTimer = setTimeout(flushBoardCommits, delayMs);
11841
+ flushTimer.unref?.();
11842
+ if (!exitHookInstalled) {
11843
+ exitHookInstalled = true;
11844
+ process.once("exit", flushBoardCommits);
11845
+ }
11846
+ }
11847
+ function flushBoardCommits() {
11848
+ if (flushTimer) {
11849
+ clearTimeout(flushTimer);
11850
+ flushTimer = null;
11851
+ }
11852
+ for (const [dotDir, entry] of pendingCommits) {
11853
+ const [first, ...rest] = entry.messages;
11854
+ const message = rest.length === 0 ? first : `${first} (+${rest.length} more)`;
11855
+ commitBoard(dotDir, { message, agent: entry.agent });
11856
+ }
11857
+ pendingCommits.clear();
11858
+ }
11859
+
11130
11860
  // src/utils/brainfile-path.ts
11131
11861
  function isMigrationCommand() {
11132
11862
  return process.argv.includes("migrate");
11133
11863
  }
11134
11864
  function migrationHintForPath2(brainfilePath) {
11135
- const absolute = path2.resolve(brainfilePath);
11136
- const dir = path2.basename(path2.dirname(absolute)) === ".brainfile" ? path2.dirname(path2.dirname(absolute)) : path2.dirname(absolute);
11865
+ const absolute = path3.resolve(brainfilePath);
11866
+ const dir = path3.basename(path3.dirname(absolute)) === ".brainfile" ? path3.dirname(path3.dirname(absolute)) : path3.dirname(absolute);
11137
11867
  return `Run: brainfile migrate --dir ${dir}`;
11138
11868
  }
11139
11869
  function rejectLegacyRuntimePath(resolvedPath) {
11140
- if (!isMigrationCommand() && fs2.existsSync(resolvedPath) && !(0, import_core3.isV2)(resolvedPath)) {
11870
+ if (!isMigrationCommand() && fs3.existsSync(resolvedPath) && !(0, import_core3.isV2)(resolvedPath)) {
11141
11871
  throw new CLIError(V1_UNSUPPORTED_MESSAGE, void 0, migrationHintForPath2(resolvedPath));
11142
11872
  }
11143
11873
  return resolvedPath;
11144
11874
  }
11875
+ function isPlaceholder(filePath) {
11876
+ return filePath === void 0 || filePath === import_core3.BRAINFILE_BASENAME || filePath === `./${import_core3.BRAINFILE_BASENAME}`;
11877
+ }
11145
11878
  function resolveCliBrainfilePath(filePath) {
11879
+ if (isPlaceholder(filePath) && isGlobalBoardRequested()) {
11880
+ return rejectLegacyRuntimePath(path3.join(homeBoardDir(), import_core3.BRAINFILE_BASENAME));
11881
+ }
11146
11882
  if (filePath) {
11147
- const resolved = path2.resolve(process.cwd(), filePath);
11883
+ const resolved = path3.resolve(process.cwd(), filePath);
11148
11884
  try {
11149
- if (fs2.statSync(resolved).isDirectory()) {
11885
+ if (fs3.statSync(resolved).isDirectory()) {
11150
11886
  const found = (0, import_core3.findBrainfile)(resolved);
11151
11887
  if (found && found.projectRoot === resolved) {
11152
11888
  return rejectLegacyRuntimePath(found.absolutePath);
11153
11889
  }
11154
- const dotDir = path2.join(resolved, ".brainfile", "brainfile.md");
11890
+ const dotDir = path3.join(resolved, ".brainfile", "brainfile.md");
11155
11891
  return rejectLegacyRuntimePath(dotDir);
11156
11892
  }
11157
11893
  } catch {
11158
11894
  }
11159
11895
  }
11160
- return rejectLegacyRuntimePath((0, import_core3.resolveBrainfilePath)({ filePath, startDir: process.cwd() }));
11896
+ const cwd = process.cwd();
11897
+ if (isPlaceholder(filePath)) {
11898
+ const found = (0, import_core3.findBrainfile)(cwd, { stopAtGitRoot: true });
11899
+ if (found) return rejectLegacyRuntimePath(found.absolutePath);
11900
+ const tracked = findTrackedBoardDir(cwd) ?? materializeBoardWorktree(cwd);
11901
+ if (tracked) return rejectLegacyRuntimePath(path3.join(tracked, import_core3.BRAINFILE_BASENAME));
11902
+ }
11903
+ return rejectLegacyRuntimePath((0, import_core3.resolveBrainfilePath)({ filePath, startDir: cwd, stopAtGitRoot: true }));
11904
+ }
11905
+ function tryResolveBoardDir(filePath) {
11906
+ try {
11907
+ const resolved = resolveCliBrainfilePath(filePath);
11908
+ if (!fs3.existsSync(resolved)) return null;
11909
+ return path3.dirname(resolved);
11910
+ } catch {
11911
+ return null;
11912
+ }
11161
11913
  }
11162
11914
 
11163
11915
  // src/utils/v2-detect.ts
11164
11916
  var import_core4 = __toESM(require_dist());
11165
11917
  var import_core5 = __toESM(require_dist());
11166
11918
  var import_core6 = __toESM(require_dist());
11167
- import * as fs4 from "fs";
11168
- import * as path4 from "path";
11919
+ import * as fs5 from "fs";
11920
+ import * as path5 from "path";
11169
11921
 
11170
11922
  // src/utils/workspace-format.ts
11171
- import * as fs3 from "fs";
11172
- import * as path3 from "path";
11923
+ import * as fs4 from "fs";
11924
+ import * as path4 from "path";
11173
11925
  function existsFile(filePath) {
11174
11926
  try {
11175
- return fs3.statSync(filePath).isFile();
11927
+ return fs4.statSync(filePath).isFile();
11176
11928
  } catch {
11177
11929
  return false;
11178
11930
  }
11179
11931
  }
11180
11932
  function existsDir(dirPath) {
11181
11933
  try {
11182
- return fs3.statSync(dirPath).isDirectory();
11934
+ return fs4.statSync(dirPath).isDirectory();
11183
11935
  } catch {
11184
11936
  return false;
11185
11937
  }
11186
11938
  }
11187
11939
  function workspaceRootFromBrainfilePath(brainfilePath) {
11188
- const absolute = path3.resolve(brainfilePath);
11189
- const dir = path3.dirname(absolute);
11190
- return path3.basename(dir) === ".brainfile" ? path3.dirname(dir) : dir;
11940
+ const absolute = path4.resolve(brainfilePath);
11941
+ const dir = path4.dirname(absolute);
11942
+ return path4.basename(dir) === ".brainfile" ? path4.dirname(dir) : dir;
11191
11943
  }
11192
11944
  function probeWorkspaceFormat(rootDir = process.cwd()) {
11193
- const resolvedRoot = path3.resolve(rootDir);
11194
- const dotDir = path3.join(resolvedRoot, ".brainfile");
11945
+ const resolvedRoot = path4.resolve(rootDir);
11946
+ const dotDir = path4.join(resolvedRoot, ".brainfile");
11195
11947
  const paths = {
11196
11948
  rootDir: resolvedRoot,
11197
- rootBrainfilePath: path3.join(resolvedRoot, "brainfile.md"),
11949
+ rootBrainfilePath: path4.join(resolvedRoot, "brainfile.md"),
11198
11950
  dotDir,
11199
- dotBrainfilePath: path3.join(dotDir, "brainfile.md"),
11200
- boardDir: path3.join(dotDir, "board"),
11201
- logsDir: path3.join(dotDir, "logs")
11951
+ dotBrainfilePath: path4.join(dotDir, "brainfile.md"),
11952
+ boardDir: path4.join(dotDir, "board"),
11953
+ logsDir: path4.join(dotDir, "logs")
11202
11954
  };
11203
11955
  const presence = {
11204
11956
  rootBrainfile: existsFile(paths.rootBrainfilePath),
@@ -11232,15 +11984,15 @@ function shouldSuggestMigration(probe) {
11232
11984
  // src/utils/v2-detect.ts
11233
11985
  var v2MigrationHintShown = /* @__PURE__ */ new Set();
11234
11986
  function shouldSuggestV2Migration(brainfilePath) {
11235
- const resolved = path4.resolve(brainfilePath);
11987
+ const resolved = path5.resolve(brainfilePath);
11236
11988
  if (v2MigrationHintShown.has(resolved)) return false;
11237
11989
  const probe = probeWorkspaceForBrainfile(resolved);
11238
11990
  if (shouldSuggestMigration(probe)) {
11239
11991
  return true;
11240
11992
  }
11241
- if (!(0, import_core6.isV2)(resolved) && fs4.existsSync(resolved)) {
11993
+ if (!(0, import_core6.isV2)(resolved) && fs5.existsSync(resolved)) {
11242
11994
  try {
11243
- const content = fs4.readFileSync(resolved, "utf-8");
11995
+ const content = fs5.readFileSync(resolved, "utf-8");
11244
11996
  const parsed = import_core4.Brainfile.parseWithErrors(content);
11245
11997
  if (parsed.board) return true;
11246
11998
  } catch {
@@ -11249,42 +12001,568 @@ function shouldSuggestV2Migration(brainfilePath) {
11249
12001
  return false;
11250
12002
  }
11251
12003
  function markV2MigrationHintShown(brainfilePath) {
11252
- v2MigrationHintShown.add(path4.resolve(brainfilePath));
12004
+ v2MigrationHintShown.add(path5.resolve(brainfilePath));
12005
+ }
12006
+
12007
+ // src/commands/merge-driver.ts
12008
+ var import_core7 = __toESM(require_dist());
12009
+ import * as fs6 from "fs";
12010
+ import * as os2 from "os";
12011
+ import * as path6 from "path";
12012
+ import { spawnSync as spawnSync2 } from "child_process";
12013
+ function parseDoc(content) {
12014
+ const parsed = (0, import_core7.parseFrontmatter)(content);
12015
+ if (!parsed) return null;
12016
+ return { data: parsed.data, body: parsed.body };
12017
+ }
12018
+ function same(a, b) {
12019
+ return JSON.stringify(a ?? null) === JSON.stringify(b ?? null);
12020
+ }
12021
+ function stamp(data) {
12022
+ const value = data.updatedAt;
12023
+ return typeof value === "string" ? value : "";
12024
+ }
12025
+ function mergeFields(base, ours, theirs) {
12026
+ const keys = [];
12027
+ for (const key of [...Object.keys(ours), ...Object.keys(theirs), ...Object.keys(base)]) {
12028
+ if (!keys.includes(key)) keys.push(key);
12029
+ }
12030
+ const oursWins = stamp(ours) > stamp(theirs);
12031
+ const out = {};
12032
+ for (const key of keys) {
12033
+ const b = base[key];
12034
+ const o = ours[key];
12035
+ const t = theirs[key];
12036
+ let pick;
12037
+ if (same(o, t)) pick = o;
12038
+ else if (same(b, o)) pick = t;
12039
+ else if (same(b, t)) pick = o;
12040
+ else if (key === "updatedAt") pick = String(o ?? "") > String(t ?? "") ? o : t;
12041
+ else pick = oursWins ? o : t;
12042
+ if (pick !== void 0) out[key] = pick;
12043
+ }
12044
+ return out;
12045
+ }
12046
+ var CONFLICT = /^<{7}[^\n]*\n([\s\S]*?)^={7}\n([\s\S]*?)^>{7}[^\n]*\n?/gm;
12047
+ var LIST_ITEM = /^\s*[-*]\s+/;
12048
+ var LEADING_STAMP = /^\s*[-*]\s+(\d{4}-\d{2}-\d{2}T[0-9:.]+Z?)\b/;
12049
+ function unionLists(ours, theirs) {
12050
+ const a = ours.split("\n").filter((l) => l.trim().length > 0);
12051
+ const b = theirs.split("\n").filter((l) => l.trim().length > 0);
12052
+ if (![...a, ...b].every((l) => LIST_ITEM.test(l))) return null;
12053
+ const merged = [...a];
12054
+ for (const line of b) if (!merged.includes(line)) merged.push(line);
12055
+ if (merged.every((l) => LEADING_STAMP.test(l))) {
12056
+ merged.sort((x, y) => {
12057
+ const sx = LEADING_STAMP.exec(x)?.[1] ?? "";
12058
+ const sy = LEADING_STAMP.exec(y)?.[1] ?? "";
12059
+ return sx < sy ? -1 : sx > sy ? 1 : 0;
12060
+ });
12061
+ }
12062
+ return `${merged.join("\n")}
12063
+ `;
12064
+ }
12065
+ function mergeBodies(base, ours, theirs) {
12066
+ if (ours === theirs) return { content: ours, clean: true };
12067
+ if (base === ours) return { content: theirs, clean: true };
12068
+ if (base === theirs) return { content: ours, clean: true };
12069
+ const dir = fs6.mkdtempSync(path6.join(os2.tmpdir(), "brainfile-merge-"));
12070
+ try {
12071
+ const files = { base: path6.join(dir, "base"), ours: path6.join(dir, "ours"), theirs: path6.join(dir, "theirs") };
12072
+ fs6.writeFileSync(files.base, base);
12073
+ fs6.writeFileSync(files.ours, ours);
12074
+ fs6.writeFileSync(files.theirs, theirs);
12075
+ const r = spawnSync2("git", ["merge-file", "-p", "-L", "ours", "-L", "base", "-L", "theirs", files.ours, files.base, files.theirs], {
12076
+ encoding: "utf-8",
12077
+ stdio: ["ignore", "pipe", "pipe"]
12078
+ });
12079
+ if (r.status === null || r.status < 0) {
12080
+ return { content: `<<<<<<< ours
12081
+ ${ours}=======
12082
+ ${theirs}>>>>>>> theirs
12083
+ `, clean: false };
12084
+ }
12085
+ if (r.status === 0) return { content: r.stdout, clean: true };
12086
+ let clean = true;
12087
+ const content = r.stdout.replace(CONFLICT, (whole, oursHunk, theirsHunk) => {
12088
+ const union = unionLists(oursHunk, theirsHunk);
12089
+ if (union === null) {
12090
+ clean = false;
12091
+ return whole;
12092
+ }
12093
+ return union;
12094
+ });
12095
+ return { content, clean };
12096
+ } finally {
12097
+ fs6.rmSync(dir, { recursive: true, force: true });
12098
+ }
12099
+ }
12100
+ function mergeBoardDocuments(base, ours, theirs) {
12101
+ const b = parseDoc(base);
12102
+ const o = parseDoc(ours);
12103
+ const t = parseDoc(theirs);
12104
+ if (!o || !t) return mergeBodies(base, ours, theirs);
12105
+ const data = mergeFields(b?.data ?? {}, o.data, t.data);
12106
+ const body = mergeBodies(b?.body ?? "", o.body, t.body);
12107
+ return { content: (0, import_core7.serializeFrontmatter)(data, body.content), clean: body.clean };
12108
+ }
12109
+ function summarizeFieldChanges(before, after) {
12110
+ const b = parseDoc(before)?.data ?? {};
12111
+ const a = parseDoc(after)?.data ?? {};
12112
+ const parts = [];
12113
+ for (const key of Object.keys(a)) {
12114
+ if (key === "updatedAt" || same(b[key], a[key])) continue;
12115
+ const render = (v) => typeof v === "string" ? v : JSON.stringify(v);
12116
+ parts.push(`${key}: ${b[key] === void 0 ? "\u2205" : render(b[key])} \u2192 ${render(a[key])}`);
12117
+ }
12118
+ return parts.join("; ");
12119
+ }
12120
+ function mergeDriverCommand(options) {
12121
+ const read = (p) => fs6.existsSync(p) ? fs6.readFileSync(p, "utf-8") : "";
12122
+ if (!options.ours || !options.theirs) return 2;
12123
+ const outcome = mergeBoardDocuments(read(options.base), read(options.ours), read(options.theirs));
12124
+ fs6.writeFileSync(options.ours, outcome.content, "utf-8");
12125
+ return outcome.clean ? 0 : 1;
12126
+ }
12127
+
12128
+ // src/utils/board-sync.ts
12129
+ import * as fs7 from "fs";
12130
+ import * as os3 from "os";
12131
+ import * as path7 from "path";
12132
+ var REMOTE_CONFIG_KEY = "brainfile.remote";
12133
+ var REMOTE_BRANCH_CONFIG_KEY = "brainfile.remoteBranch";
12134
+ var AUTOSYNC_CONFIG_KEY = "brainfile.autosync";
12135
+ var URL_REMOTE_NAME = "board";
12136
+ var MERGE_DRIVER_NAME = "brainfile";
12137
+ var DEFAULT_MERGE_DRIVER_COMMAND = "brainfile merge-driver %O %A %B";
12138
+ var SYNC_STATE_FILE = path7.join("state", "sync.json");
12139
+ var BOARD_GITATTRIBUTES = [
12140
+ "logs/ledger.jsonl merge=union",
12141
+ "board/*.md merge=brainfile",
12142
+ "logs/*.md merge=brainfile",
12143
+ "brainfile.md merge=brainfile"
12144
+ ];
12145
+ function configGet(dotDir, key) {
12146
+ const r = git(["config", "--get", key], dotDir);
12147
+ return r.ok && r.stdout ? r.stdout : null;
12148
+ }
12149
+ function configSet(dotDir, key, value) {
12150
+ return git(["config", key, value], dotDir).ok;
12151
+ }
12152
+ function boardRemote(dotDir) {
12153
+ return configGet(dotDir, REMOTE_CONFIG_KEY);
12154
+ }
12155
+ function boardRemoteBranch(dotDir) {
12156
+ const configured = configGet(dotDir, REMOTE_BRANCH_CONFIG_KEY);
12157
+ if (configured) return configured;
12158
+ if (path7.resolve(dotDir) === path7.resolve(homeBoardDir())) return "home";
12159
+ if (boardRepoKind(dotDir) === "standalone" && path7.basename(path7.resolve(dotDir)) === ".brainfile") {
12160
+ return path7.basename(path7.dirname(path7.resolve(dotDir))) || boardBranch(dotDir);
12161
+ }
12162
+ return boardBranch(dotDir);
12163
+ }
12164
+ function boardAutosync(dotDir) {
12165
+ const value = configGet(dotDir, AUTOSYNC_CONFIG_KEY);
12166
+ if (value === "off" || value === "push" || value === "full") return value;
12167
+ return boardRemote(dotDir) ? "push" : "off";
12168
+ }
12169
+ function setBoardAutosync(dotDir, mode) {
12170
+ return configSet(dotDir, AUTOSYNC_CONFIG_KEY, mode);
12171
+ }
12172
+ function looksLikeRemoteUrl(target) {
12173
+ if (/^[a-z][a-z0-9+.-]*:\/\//i.test(target)) return true;
12174
+ if (/^[^/\s]+@[^/\s]+:/.test(target)) return true;
12175
+ if (target.startsWith("/") || target.startsWith(".") || target.startsWith("~")) return true;
12176
+ if (target.includes("/") && fs7.existsSync(target)) return true;
12177
+ return false;
12178
+ }
12179
+ function setBoardRemote(cwd, target, remoteBranch) {
12180
+ const trimmed = target.trim();
12181
+ if (!trimmed) throw new Error("remote is required");
12182
+ let remote;
12183
+ if (looksLikeRemoteUrl(trimmed)) {
12184
+ remote = URL_REMOTE_NAME;
12185
+ const url = trimmed.startsWith("~/") ? path7.join(os3.homedir(), trimmed.slice(2)) : trimmed;
12186
+ const exists = git(["remote", "get-url", remote], cwd).ok;
12187
+ const r = exists ? git(["remote", "set-url", remote, url], cwd) : git(["remote", "add", remote, url], cwd);
12188
+ if (!r.ok) throw new Error(r.stderr || `could not register remote '${remote}'`);
12189
+ } else {
12190
+ remote = trimmed;
12191
+ if (!git(["remote", "get-url", remote], cwd).ok) {
12192
+ const known = git(["remote"], cwd).stdout.split("\n").filter(Boolean);
12193
+ const hint = known.length ? ` Known remotes: ${known.join(", ")}.` : "";
12194
+ throw new Error(`'${remote}' is not a git remote here.${hint} Pass a URL to add one.`);
12195
+ }
12196
+ }
12197
+ configSet(cwd, REMOTE_CONFIG_KEY, remote);
12198
+ if (remoteBranch) configSet(cwd, REMOTE_BRANCH_CONFIG_KEY, remoteBranch);
12199
+ registerMergeDriver(cwd);
12200
+ return {
12201
+ remote,
12202
+ url: git(["remote", "get-url", remote], cwd).stdout || null,
12203
+ remoteBranch: remoteBranch ?? boardRemoteBranch(cwd)
12204
+ };
12205
+ }
12206
+ function registerMergeDriver(cwd, command) {
12207
+ const current = configGet(cwd, `merge.${MERGE_DRIVER_NAME}.driver`);
12208
+ if (command === void 0) {
12209
+ if (current) return false;
12210
+ command = DEFAULT_MERGE_DRIVER_COMMAND;
12211
+ }
12212
+ if (current === command) return false;
12213
+ configSet(cwd, `merge.${MERGE_DRIVER_NAME}.name`, "brainfile board merge (field-level, last writer wins)");
12214
+ return configSet(cwd, `merge.${MERGE_DRIVER_NAME}.driver`, command);
12215
+ }
12216
+ function ensureBoardAttributes(dotDir) {
12217
+ const file = path7.join(dotDir, ".gitattributes");
12218
+ let existing = "";
12219
+ try {
12220
+ existing = fs7.readFileSync(file, "utf-8");
12221
+ } catch {
12222
+ }
12223
+ const lines = existing.split("\n").map((l) => l.trim());
12224
+ const missing = BOARD_GITATTRIBUTES.filter((l) => !lines.includes(l));
12225
+ if (missing.length === 0) return false;
12226
+ const prefix = existing.length > 0 && !existing.endsWith("\n") ? `${existing}
12227
+ ` : existing;
12228
+ fs7.writeFileSync(file, `${prefix}${missing.join("\n")}
12229
+ `, "utf-8");
12230
+ return true;
12231
+ }
12232
+ function readSyncState(dotDir) {
12233
+ try {
12234
+ const parsed = JSON.parse(fs7.readFileSync(path7.join(dotDir, SYNC_STATE_FILE), "utf-8"));
12235
+ return typeof parsed?.at === "string" ? parsed : null;
12236
+ } catch {
12237
+ return null;
12238
+ }
12239
+ }
12240
+ function writeSyncState(dotDir, state) {
12241
+ try {
12242
+ fs7.mkdirSync(path7.join(dotDir, "state"), { recursive: true });
12243
+ fs7.writeFileSync(path7.join(dotDir, SYNC_STATE_FILE), `${JSON.stringify(state, null, 2)}
12244
+ `, "utf-8");
12245
+ } catch {
12246
+ }
12247
+ }
12248
+ function firstLine(text) {
12249
+ return text.split("\n").map((l) => l.trim()).find(Boolean) ?? "";
12250
+ }
12251
+ function isMissingRemoteRef(stderr) {
12252
+ return /couldn't find remote ref|Remote branch .* not found|invalid refspec|no such ref/i.test(stderr);
12253
+ }
12254
+ function commitCount(dotDir, range) {
12255
+ const r = git(["rev-list", "--count", range], dotDir);
12256
+ return r.ok ? Number.parseInt(r.stdout, 10) || 0 : 0;
12257
+ }
12258
+ function identity(dotDir, agent) {
12259
+ const args = ["-c", "commit.gpgsign=false"];
12260
+ if (!git(["config", "--get", "user.email"], dotDir).ok) {
12261
+ args.push("-c", "user.name=brainfile", "-c", "user.email=brainfile@localhost");
12262
+ }
12263
+ const env = {};
12264
+ const name = agent?.replace(/[\r\n<>]/g, "").trim();
12265
+ if (name) {
12266
+ env.GIT_AUTHOR_NAME = name;
12267
+ env.GIT_AUTHOR_EMAIL = `${name}@brainfile.local`;
12268
+ }
12269
+ return { args, env };
12270
+ }
12271
+ function resolveCompleteVersusEdit(dotDir) {
12272
+ const status = git(["status", "--porcelain", "-z"], dotDir);
12273
+ if (!status.ok) return [];
12274
+ const resolved = [];
12275
+ for (const entry of status.stdout.split("\0")) {
12276
+ if (entry.length < 4) continue;
12277
+ const xy = entry.slice(0, 2);
12278
+ const file = entry.slice(3);
12279
+ if (xy !== "DU" && xy !== "UD") continue;
12280
+ if (!file.startsWith("board/")) continue;
12281
+ const editedStage = xy === "DU" ? 3 : 2;
12282
+ const base = git(["show", `:1:${file}`], dotDir);
12283
+ const edited = git(["show", `:${editedStage}:${file}`], dotDir);
12284
+ const archived = path7.join(dotDir, "logs", path7.basename(file));
12285
+ if (edited.ok && fs7.existsSync(archived)) {
12286
+ const summary = summarizeFieldChanges(base.ok ? base.stdout : "", edited.stdout);
12287
+ const entryLine = `- ${(/* @__PURE__ */ new Date()).toISOString()}: [sync] edited elsewhere after completion${summary ? ` (${summary})` : ""}`;
12288
+ let content = fs7.readFileSync(archived, "utf-8");
12289
+ if (/^## Log\s*$/m.test(content)) {
12290
+ content = `${content.replace(/\s+$/, "")}
12291
+ ${entryLine}
12292
+ `;
12293
+ } else {
12294
+ content = `${content.replace(/\s+$/, "")}
12295
+
12296
+ ## Log
12297
+ ${entryLine}
12298
+ `;
12299
+ }
12300
+ fs7.writeFileSync(archived, content, "utf-8");
12301
+ git(["add", "--", path7.relative(dotDir, archived)], dotDir);
12302
+ }
12303
+ if (git(["rm", "--quiet", "--cached", "--", file], dotDir).ok) {
12304
+ try {
12305
+ fs7.rmSync(path7.join(dotDir, file), { force: true });
12306
+ } catch {
12307
+ }
12308
+ resolved.push(file);
12309
+ }
12310
+ }
12311
+ return resolved;
12312
+ }
12313
+ function syncBoard(dotDir, options = {}) {
12314
+ const pull = options.pull ?? true;
12315
+ const push = options.push ?? true;
12316
+ const result = { ok: true, pulled: 0, pushed: 0, conflicts: [] };
12317
+ if (!isTrackedBoard(dotDir)) return { ...result, skipped: "not-tracked" };
12318
+ const remote = boardRemote(dotDir);
12319
+ if (!remote) return { ...result, skipped: "no-remote" };
12320
+ const remoteBranch = boardRemoteBranch(dotDir);
12321
+ result.remote = remote;
12322
+ result.remoteBranch = remoteBranch;
12323
+ const stuck = unmergedFiles(dotDir);
12324
+ if (stuck.length > 0) {
12325
+ return finish(dotDir, { ...result, ok: false, failure: "conflict", conflicts: stuck, warning: `unresolved conflicts: ${stuck.join(", ")}` });
12326
+ }
12327
+ commitHandEdits(dotDir);
12328
+ if (ensureBoardAttributes(dotDir)) commitBoard(dotDir, { message: "chore: merge attributes" });
12329
+ registerMergeDriver(dotDir);
12330
+ let fetched = false;
12331
+ if (pull) {
12332
+ const fetch2 = git(["fetch", "--quiet", remote, remoteBranch], dotDir);
12333
+ if (fetch2.ok) {
12334
+ fetched = true;
12335
+ } else if (!isMissingRemoteRef(fetch2.stderr)) {
12336
+ return finish(dotDir, { ...result, ok: false, failure: "offline", warning: `fetch from ${remote} failed: ${firstLine(fetch2.stderr)}` });
12337
+ }
12338
+ if (fetched && !git(["merge-base", "--is-ancestor", "FETCH_HEAD", "HEAD"], dotDir).ok) {
12339
+ const before = git(["rev-parse", "HEAD"], dotDir).stdout;
12340
+ const id = identity(dotDir, options.agent);
12341
+ const merge = git([...id.args, "merge", "--quiet", "--no-edit", "FETCH_HEAD"], dotDir, id.env);
12342
+ if (!merge.ok) {
12343
+ resolveCompleteVersusEdit(dotDir);
12344
+ const remaining = unmergedFiles(dotDir);
12345
+ if (remaining.length > 0) {
12346
+ return finish(dotDir, {
12347
+ ...result,
12348
+ ok: false,
12349
+ failure: "conflict",
12350
+ conflicts: remaining,
12351
+ warning: `merge left conflicts in ${remaining.join(", ")} \u2014 resolve them in ${dotDir}, then run sync again`
12352
+ });
12353
+ }
12354
+ const commit = git([...id.args, "commit", "--quiet", "--no-edit", "--no-verify"], dotDir, id.env);
12355
+ if (!commit.ok) {
12356
+ return finish(dotDir, { ...result, ok: false, failure: "error", warning: `merge commit failed: ${firstLine(commit.stderr)}` });
12357
+ }
12358
+ }
12359
+ result.pulled = commitCount(dotDir, `${before}..FETCH_HEAD`);
12360
+ }
12361
+ }
12362
+ if (push) {
12363
+ const ahead = fetched ? commitCount(dotDir, "FETCH_HEAD..HEAD") : pendingChanges(dotDir);
12364
+ if (ahead > 0) {
12365
+ const pushed = git(["push", "--quiet", remote, `HEAD:refs/heads/${remoteBranch}`], dotDir);
12366
+ if (!pushed.ok) {
12367
+ const rejected = /rejected|non-fast-forward|fetch first/i.test(pushed.stderr);
12368
+ return finish(dotDir, {
12369
+ ...result,
12370
+ ok: false,
12371
+ failure: rejected ? "rejected" : "offline",
12372
+ warning: rejected ? `push to ${remote} rejected (the remote moved) \u2014 run sync again` : `push to ${remote} failed: ${firstLine(pushed.stderr)}`
12373
+ });
12374
+ }
12375
+ result.pushed = ahead;
12376
+ }
12377
+ }
12378
+ return finish(dotDir, result);
12379
+ }
12380
+ function finish(dotDir, result) {
12381
+ writeSyncState(dotDir, {
12382
+ at: (/* @__PURE__ */ new Date()).toISOString(),
12383
+ remote: result.remote ?? "",
12384
+ remoteBranch: result.remoteBranch ?? "",
12385
+ ok: result.ok,
12386
+ ...result.warning ? { warning: result.warning } : {}
12387
+ });
12388
+ return result;
12389
+ }
12390
+ function secondsSinceSync(dotDir, now = Date.now()) {
12391
+ const state = readSyncState(dotDir);
12392
+ if (!state) return null;
12393
+ const at = Date.parse(state.at);
12394
+ return Number.isNaN(at) ? null : Math.max(0, Math.round((now - at) / 1e3));
12395
+ }
12396
+ function syncStatusLabel(dotDir, now = Date.now()) {
12397
+ if (!isTrackedBoard(dotDir)) return null;
12398
+ if (!boardRemote(dotDir)) return "local only";
12399
+ const state = readSyncState(dotDir);
12400
+ if (!state) return "not synced yet";
12401
+ if (!state.ok) return "not synced \xB7 saved locally";
12402
+ const seconds = secondsSinceSync(dotDir, now) ?? 0;
12403
+ const age = seconds < 60 ? `${seconds}s` : seconds < 3600 ? `${Math.round(seconds / 60)}m` : `${Math.round(seconds / 3600)}h`;
12404
+ return `synced ${age} ago`;
12405
+ }
12406
+ function pendingChanges(dotDir) {
12407
+ const remote = boardRemote(dotDir);
12408
+ if (!remote) return 0;
12409
+ const tracking = `refs/remotes/${remote}/${boardRemoteBranch(dotDir)}`;
12410
+ const known = git(["rev-parse", "--verify", "--quiet", tracking], dotDir).ok;
12411
+ return commitCount(dotDir, known ? `${tracking}..HEAD` : "HEAD");
12412
+ }
12413
+ var plural = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
12414
+ function syncMessage(result) {
12415
+ if (result.skipped === "not-tracked") {
12416
+ return { tone: "warn", text: "This board is plain files on this machine and is not shared. To share it: brainfile migrate --to-branch" };
12417
+ }
12418
+ if (result.skipped === "no-remote") {
12419
+ return { tone: "warn", text: "This board is only on this machine. To share it: brainfile sync --set-remote origin" };
12420
+ }
12421
+ const remote = result.remote ?? "the remote";
12422
+ if (!result.ok) {
12423
+ switch (result.failure) {
12424
+ case "conflict":
12425
+ return {
12426
+ tone: "warn",
12427
+ text: `Two edits collided in ${result.conflicts.join(", ")}. Open the file in .brainfile/, keep the right version, then run brainfile sync. Nothing else is shared until then.`
12428
+ };
12429
+ case "rejected":
12430
+ return { tone: "warn", text: `Someone shared changes through ${remote} at the same moment. Run brainfile sync again to combine them. Nothing was lost.` };
12431
+ case "offline":
12432
+ return {
12433
+ tone: "warn",
12434
+ text: `Couldn't reach ${remote}. Nothing was lost: your changes are saved on this machine and will be sent on the next sync.`,
12435
+ detail: result.warning
12436
+ };
12437
+ default:
12438
+ return { tone: "warn", text: `Sync with ${remote} stopped. Your changes are saved on this machine.`, detail: result.warning };
12439
+ }
12440
+ }
12441
+ const parts = [];
12442
+ if (result.pulled > 0) parts.push(`received ${plural(result.pulled, "change")}`);
12443
+ if (result.pushed > 0) parts.push(`sent ${plural(result.pushed, "change")}`);
12444
+ return { tone: "ok", text: parts.length ? `Shared through ${remote}: ${parts.join(", ")}.` : `Up to date with ${remote}.` };
12445
+ }
12446
+
12447
+ // src/utils/board-autosync.ts
12448
+ import * as fs8 from "fs";
12449
+ import * as path8 from "path";
12450
+ import { spawn } from "child_process";
12451
+ var AUTOSYNC_DELAY_MS = 5e3;
12452
+ var FETCH_MAX_AGE_SECONDS = 60;
12453
+ var LOCK_FILE = path8.join("state", "autosync.lock");
12454
+ var LOCK_STALE_MS = 6e4;
12455
+ function effectiveAutosync(dotDir) {
12456
+ const override = process.env.BRAINFILE_AUTOSYNC;
12457
+ if (override === "off" || override === "push" || override === "full") return override;
12458
+ return boardAutosync(dotDir);
12459
+ }
12460
+ function autosyncCliPath() {
12461
+ const candidates = [
12462
+ path8.join(__dirname, "cli.js"),
12463
+ path8.join(__dirname, "..", "cli.js"),
12464
+ path8.join(__dirname, "..", "..", "dist", "cli.js")
12465
+ ];
12466
+ return candidates.find((p) => fs8.existsSync(p)) ?? null;
12467
+ }
12468
+ function pidAlive(pid) {
12469
+ try {
12470
+ process.kill(pid, 0);
12471
+ return true;
12472
+ } catch {
12473
+ return false;
12474
+ }
12475
+ }
12476
+ function pushPending(dotDir) {
12477
+ const lock = path8.join(dotDir, LOCK_FILE);
12478
+ try {
12479
+ const st = fs8.statSync(lock);
12480
+ if (Date.now() - st.mtimeMs > LOCK_STALE_MS) return false;
12481
+ const pid = Number.parseInt(fs8.readFileSync(lock, "utf-8"), 10);
12482
+ return Number.isFinite(pid) && pidAlive(pid);
12483
+ } catch {
12484
+ return false;
12485
+ }
12486
+ }
12487
+ function clearPushLock(dotDir) {
12488
+ try {
12489
+ fs8.rmSync(path8.join(dotDir, LOCK_FILE), { force: true });
12490
+ } catch {
12491
+ }
12492
+ }
12493
+ function schedulePush(dotDir, delayMs = AUTOSYNC_DELAY_MS) {
12494
+ if (!isTrackedBoard(dotDir)) return false;
12495
+ const mode = effectiveAutosync(dotDir);
12496
+ if (mode === "off" || !boardRemote(dotDir)) return false;
12497
+ if (pushPending(dotDir)) return false;
12498
+ const cli = autosyncCliPath();
12499
+ if (!cli) return false;
12500
+ const delay = process.env.BRAINFILE_AUTOSYNC_DELAY_MS ? Number(process.env.BRAINFILE_AUTOSYNC_DELAY_MS) : delayMs;
12501
+ try {
12502
+ const child = spawn(
12503
+ process.execPath,
12504
+ [cli, "sync", "--push", "--file", path8.join(dotDir, "brainfile.md"), "--wait", String(delay)],
12505
+ { cwd: dotDir, detached: true, stdio: "ignore", env: { ...process.env, BRAINFILE_AUTOSYNC: "off" } }
12506
+ );
12507
+ child.unref();
12508
+ if (child.pid) {
12509
+ fs8.mkdirSync(path8.join(dotDir, "state"), { recursive: true });
12510
+ fs8.writeFileSync(path8.join(dotDir, LOCK_FILE), String(child.pid), "utf-8");
12511
+ }
12512
+ return true;
12513
+ } catch {
12514
+ return false;
12515
+ }
12516
+ }
12517
+ function fetchBeforeRead(dotDir, maxAgeSeconds = FETCH_MAX_AGE_SECONDS) {
12518
+ if (!isTrackedBoard(dotDir)) return null;
12519
+ if (effectiveAutosync(dotDir) !== "full") return null;
12520
+ const age = secondsSinceSync(dotDir);
12521
+ if (age !== null && age < maxAgeSeconds) return null;
12522
+ return syncBoard(dotDir, { pull: true, push: false });
12523
+ }
12524
+ var installed = false;
12525
+ function installAutosync() {
12526
+ if (installed) return;
12527
+ installed = true;
12528
+ onBoardCommitted((dotDir) => {
12529
+ schedulePush(dotDir);
12530
+ });
11253
12531
  }
11254
12532
 
11255
12533
  // src/utils/config.ts
11256
- import * as fs5 from "fs";
11257
- import * as path5 from "path";
11258
- import * as os from "os";
12534
+ import * as fs9 from "fs";
12535
+ import * as path9 from "path";
12536
+ import * as os4 from "os";
11259
12537
  function resolveHomeDir() {
11260
12538
  const envHome = process.env.XDG_CONFIG_HOME?.trim();
11261
12539
  if (envHome) {
11262
12540
  return envHome;
11263
12541
  }
11264
12542
  const home = process.env.HOME?.trim();
11265
- return home || os.homedir();
12543
+ return home || os4.homedir();
11266
12544
  }
11267
12545
  function getConfigDir() {
11268
12546
  const xdgConfigHome = process.env.XDG_CONFIG_HOME?.trim();
11269
12547
  if (xdgConfigHome) {
11270
- return path5.join(xdgConfigHome, "brainfile");
12548
+ return path9.join(xdgConfigHome, "brainfile");
11271
12549
  }
11272
- return path5.join(resolveHomeDir(), ".config", "brainfile");
12550
+ return path9.join(resolveHomeDir(), ".config", "brainfile");
11273
12551
  }
11274
12552
  function getConfigPath() {
11275
- return path5.join(getConfigDir(), "config.json");
12553
+ return path9.join(getConfigDir(), "config.json");
11276
12554
  }
11277
12555
  function ensureConfigDir() {
11278
12556
  const configDir = getConfigDir();
11279
- if (!fs5.existsSync(configDir)) {
11280
- fs5.mkdirSync(configDir, { recursive: true, mode: 448 });
12557
+ if (!fs9.existsSync(configDir)) {
12558
+ fs9.mkdirSync(configDir, { recursive: true, mode: 448 });
11281
12559
  }
11282
12560
  }
11283
12561
  function loadConfig() {
11284
12562
  const configFile = getConfigPath();
11285
12563
  try {
11286
- if (fs5.existsSync(configFile)) {
11287
- const content = fs5.readFileSync(configFile, "utf-8");
12564
+ if (fs9.existsSync(configFile)) {
12565
+ const content = fs9.readFileSync(configFile, "utf-8");
11288
12566
  return JSON.parse(content);
11289
12567
  }
11290
12568
  } catch (error) {
@@ -11295,7 +12573,7 @@ function loadConfig() {
11295
12573
  function saveConfig(config) {
11296
12574
  const configFile = getConfigPath();
11297
12575
  ensureConfigDir();
11298
- fs5.writeFileSync(configFile, JSON.stringify(config, null, 2), {
12576
+ fs9.writeFileSync(configFile, JSON.stringify(config, null, 2), {
11299
12577
  encoding: "utf-8",
11300
12578
  mode: 384
11301
12579
  // Owner read/write only
@@ -11370,11 +12648,11 @@ function isValidDestination(dest) {
11370
12648
  }
11371
12649
 
11372
12650
  // src/utils/auth-store.ts
11373
- import * as fs6 from "fs";
11374
- import * as path6 from "path";
12651
+ import * as fs10 from "fs";
12652
+ import * as path10 from "path";
11375
12653
  import { execSync } from "child_process";
11376
12654
  var SERVICE_NAME = "brainfile";
11377
- var AUTH_FILE = path6.join(getConfigDir(), "auth.json");
12655
+ var AUTH_FILE = path10.join(getConfigDir(), "auth.json");
11378
12656
  var ENV_VAR_MAP = {
11379
12657
  github: ["GITHUB_TOKEN", "GH_TOKEN"],
11380
12658
  linear: ["LINEAR_API_KEY", "LINEAR_TOKEN"]
@@ -11544,8 +12822,8 @@ var AuthStore = class {
11544
12822
  }
11545
12823
  loadAuthFile() {
11546
12824
  try {
11547
- if (fs6.existsSync(AUTH_FILE)) {
11548
- const content = fs6.readFileSync(AUTH_FILE, "utf-8");
12825
+ if (fs10.existsSync(AUTH_FILE)) {
12826
+ const content = fs10.readFileSync(AUTH_FILE, "utf-8");
11549
12827
  return JSON.parse(content);
11550
12828
  }
11551
12829
  } catch {
@@ -11554,7 +12832,7 @@ var AuthStore = class {
11554
12832
  }
11555
12833
  saveAuthFile(data) {
11556
12834
  ensureConfigDir();
11557
- fs6.writeFileSync(AUTH_FILE, JSON.stringify(data, null, 2), {
12835
+ fs10.writeFileSync(AUTH_FILE, JSON.stringify(data, null, 2), {
11558
12836
  encoding: "utf-8",
11559
12837
  mode: 384
11560
12838
  // Owner read/write only
@@ -11578,7 +12856,7 @@ var AuthStore = class {
11578
12856
  var authStore = new AuthStore();
11579
12857
 
11580
12858
  // src/utils/github-auth.ts
11581
- var import_chalk2 = __toESM(require_source());
12859
+ var import_chalk3 = __toESM(require_source());
11582
12860
  var GITHUB_CLIENT_ID = process.env.BRAINFILE_GITHUB_CLIENT_ID || "Ov23liYourClientId";
11583
12861
  var GITHUB_DEVICE_CODE_URL = "https://github.com/login/device/code";
11584
12862
  var GITHUB_TOKEN_URL = "https://github.com/login/oauth/access_token";
@@ -11601,13 +12879,13 @@ async function authenticateGitHub(options) {
11601
12879
  const ghToken = getGitHubCLIToken();
11602
12880
  if (ghToken) {
11603
12881
  if (!silent) {
11604
- console.log(import_chalk2.default.green("\u2713") + " Found existing GitHub CLI authentication");
12882
+ console.log(import_chalk3.default.green("\u2713") + " Found existing GitHub CLI authentication");
11605
12883
  }
11606
12884
  const user = await getGitHubUser(ghToken);
11607
12885
  if (user) {
11608
12886
  await authStore.set("github", ghToken, { username: user.login });
11609
12887
  if (!silent) {
11610
- console.log(import_chalk2.default.green("\u2713") + ` Authenticated as ${import_chalk2.default.cyan("@" + user.login)}`);
12888
+ console.log(import_chalk3.default.green("\u2713") + ` Authenticated as ${import_chalk3.default.cyan("@" + user.login)}`);
11611
12889
  }
11612
12890
  return { success: true, username: user.login };
11613
12891
  }
@@ -11622,8 +12900,8 @@ async function verifyAndStoreToken(token, silent) {
11622
12900
  }
11623
12901
  await authStore.set("github", token, { username: user.login });
11624
12902
  if (!silent) {
11625
- console.log(import_chalk2.default.green("\u2713") + " Token verified");
11626
- console.log(import_chalk2.default.green("\u2713") + ` Authenticated as ${import_chalk2.default.cyan("@" + user.login)}`);
12903
+ console.log(import_chalk3.default.green("\u2713") + " Token verified");
12904
+ console.log(import_chalk3.default.green("\u2713") + ` Authenticated as ${import_chalk3.default.cyan("@" + user.login)}`);
11627
12905
  }
11628
12906
  return { success: true, username: user.login };
11629
12907
  }
@@ -11636,16 +12914,16 @@ async function deviceFlowAuth(silent) {
11636
12914
  try {
11637
12915
  const deviceCode = await requestDeviceCode();
11638
12916
  if (!silent) {
11639
- console.log(import_chalk2.default.yellow("!") + ` First, copy your one-time code: ${import_chalk2.default.bold.cyan(deviceCode.user_code)}`);
12917
+ console.log(import_chalk3.default.yellow("!") + ` First, copy your one-time code: ${import_chalk3.default.bold.cyan(deviceCode.user_code)}`);
11640
12918
  console.log("");
11641
- console.log(`Then visit: ${import_chalk2.default.underline(deviceCode.verification_uri)}`);
12919
+ console.log(`Then visit: ${import_chalk3.default.underline(deviceCode.verification_uri)}`);
11642
12920
  console.log("");
11643
12921
  }
11644
12922
  try {
11645
12923
  const open = await import("./open-ZGSQIGEH.mjs");
11646
12924
  await open.default(deviceCode.verification_uri);
11647
12925
  if (!silent) {
11648
- console.log(import_chalk2.default.gray("(Opening browser...)"));
12926
+ console.log(import_chalk3.default.gray("(Opening browser...)"));
11649
12927
  }
11650
12928
  } catch {
11651
12929
  }
@@ -11802,11 +13080,11 @@ async function logoutGitHub() {
11802
13080
  await authStore.clear("github");
11803
13081
  }
11804
13082
  function sleep(ms) {
11805
- return new Promise((resolve5) => setTimeout(resolve5, ms));
13083
+ return new Promise((resolve7) => setTimeout(resolve7, ms));
11806
13084
  }
11807
13085
 
11808
13086
  // src/utils/linear-auth.ts
11809
- var import_chalk3 = __toESM(require_source());
13087
+ var import_chalk4 = __toESM(require_source());
11810
13088
  var LINEAR_API_URL = "https://api.linear.app/graphql";
11811
13089
  async function isLinearAuthenticated() {
11812
13090
  return await authStore.isAuthenticated("linear");
@@ -11821,7 +13099,7 @@ async function authenticateLinear(options) {
11821
13099
  console.log("");
11822
13100
  console.log("To authenticate with Linear, you need an API key.");
11823
13101
  console.log("");
11824
- console.log(`Get your API key from: ${import_chalk3.default.underline("https://linear.app/settings/api")}`);
13102
+ console.log(`Get your API key from: ${import_chalk4.default.underline("https://linear.app/settings/api")}`);
11825
13103
  console.log("");
11826
13104
  }
11827
13105
  return { success: false, error: "Linear API key required. Use --token flag." };
@@ -11837,8 +13115,8 @@ async function authenticateLinear(options) {
11837
13115
  workspace
11838
13116
  });
11839
13117
  if (!silent) {
11840
- console.log(import_chalk3.default.green("\u2713") + " API key verified");
11841
- console.log(import_chalk3.default.green("\u2713") + ` Authenticated as ${import_chalk3.default.cyan(viewer.name)} in ${import_chalk3.default.cyan(workspace)}`);
13118
+ console.log(import_chalk4.default.green("\u2713") + " API key verified");
13119
+ console.log(import_chalk4.default.green("\u2713") + ` Authenticated as ${import_chalk4.default.cyan(viewer.name)} in ${import_chalk4.default.cyan(workspace)}`);
11842
13120
  }
11843
13121
  return { success: true, workspace };
11844
13122
  }
@@ -12039,6 +13317,91 @@ async function logoutLinear() {
12039
13317
  await authStore.clear("linear");
12040
13318
  }
12041
13319
 
13320
+ // src/tui/theme.ts
13321
+ var RAW_PALETTE = {
13322
+ // Text hierarchy
13323
+ text: "#ffffff",
13324
+ textSecondary: "#a0a0a0",
13325
+ textMuted: "#606060",
13326
+ textDim: "#404040",
13327
+ // Priority signal
13328
+ critical: "#ef4444",
13329
+ high: "#f59e0b",
13330
+ medium: "#3b82f6",
13331
+ low: "#6b7280",
13332
+ // Contract-state signal (design §3: draft dim · ready cyan · in_progress
13333
+ // yellow · delivered magenta · done green · failed/blocked red)
13334
+ contractDraft: "#606060",
13335
+ contractReady: "#06b6d4",
13336
+ contractInProgress: "#eab308",
13337
+ contractDelivered: "#c026d3",
13338
+ contractDone: "#22c55e",
13339
+ contractFailed: "#ef4444",
13340
+ // Feedback
13341
+ success: "#22c55e",
13342
+ warning: "#eab308",
13343
+ error: "#ef4444",
13344
+ info: "#3b82f6",
13345
+ accent: "#7c3aed"
13346
+ };
13347
+ function isNoColor(env = process.env) {
13348
+ return Boolean(env.NO_COLOR);
13349
+ }
13350
+ function makePalette(noColor) {
13351
+ if (!noColor) return RAW_PALETTE;
13352
+ return new Proxy(RAW_PALETTE, { get: () => void 0 });
13353
+ }
13354
+ var NO_COLOR = isNoColor();
13355
+ var PALETTE = makePalette(NO_COLOR);
13356
+ var RULE = "\u2500";
13357
+ var TYPE_GLYPHS = {
13358
+ epic: "\u25B8",
13359
+ spec: "\u25C6",
13360
+ adr: "\u25CF",
13361
+ plan: "\u2398",
13362
+ task: ""
13363
+ };
13364
+ function getTypeGlyph(type) {
13365
+ if (!type) return "";
13366
+ return TYPE_GLYPHS[type.toLowerCase()] ?? "";
13367
+ }
13368
+ var GLYPHS = {
13369
+ pointer: "\u25B8",
13370
+ cursor: "\u258C",
13371
+ collapsed: "\u25B6",
13372
+ success: "\u2713",
13373
+ error: "\u2717",
13374
+ warning: "\u26A0",
13375
+ live: "\u25CF",
13376
+ subtaskOpen: "\u25FB",
13377
+ subtaskDone: "\u2611",
13378
+ orphanParent: "\u2190",
13379
+ /** Board-list row glyph for a parent-with-children that is expanded (§A1). */
13380
+ parentExpanded: "\u25BE",
13381
+ /** Board-list row glyph for a parent-with-children that is collapsed (§A1). */
13382
+ parentCollapsed: "\u25B8",
13383
+ /** Detail body scroll indicator (§B2), e.g. `↕ 2/9`. */
13384
+ scroll: "\u2195"
13385
+ };
13386
+ function getContractStateColor(state) {
13387
+ switch (state?.toLowerCase()) {
13388
+ case "ready":
13389
+ return PALETTE.contractReady;
13390
+ case "in_progress":
13391
+ return PALETTE.contractInProgress;
13392
+ case "delivered":
13393
+ return PALETTE.contractDelivered;
13394
+ case "done":
13395
+ return PALETTE.contractDone;
13396
+ case "failed":
13397
+ case "blocked":
13398
+ return PALETTE.contractFailed;
13399
+ case "draft":
13400
+ default:
13401
+ return PALETTE.contractDraft;
13402
+ }
13403
+ }
13404
+
12042
13405
  export {
12043
13406
  require_source,
12044
13407
  require_dist,
@@ -12061,14 +13424,54 @@ export {
12061
13424
  columnNotFound,
12062
13425
  taskNotFound,
12063
13426
  validationError2,
13427
+ renderCliError,
12064
13428
  V1_UNSUPPORTED_MESSAGE,
12065
13429
  assertV2Brainfile,
13430
+ git,
13431
+ gitToplevel,
13432
+ listWorktrees,
13433
+ boardBranch,
13434
+ boardRepoKind,
13435
+ isTrackedBoard,
13436
+ findTrackedBoardDir,
13437
+ materializeBoardWorktree,
13438
+ ensureExcludeEntry,
13439
+ removeExcludeEntry,
13440
+ createOrphanBoardWorktree,
13441
+ initStandaloneBoardRepo,
13442
+ boardIsDirty,
13443
+ commitBoard,
13444
+ commitHandEdits,
13445
+ resolveAgentName,
13446
+ homeBoardDir,
13447
+ isGlobalBoardRequested,
13448
+ describeCommandForCommit,
13449
+ scheduleBoardCommit,
12066
13450
  resolveCliBrainfilePath,
13451
+ tryResolveBoardDir,
12067
13452
  workspaceRootFromBrainfilePath,
12068
13453
  probeWorkspaceFormat,
12069
13454
  shouldSuggestV2Migration,
12070
13455
  markV2MigrationHintShown,
12071
13456
  import_core5 as import_core,
13457
+ mergeDriverCommand,
13458
+ boardRemote,
13459
+ boardRemoteBranch,
13460
+ boardAutosync,
13461
+ setBoardAutosync,
13462
+ setBoardRemote,
13463
+ registerMergeDriver,
13464
+ ensureBoardAttributes,
13465
+ readSyncState,
13466
+ syncBoard,
13467
+ secondsSinceSync,
13468
+ syncStatusLabel,
13469
+ pendingChanges,
13470
+ syncMessage,
13471
+ effectiveAutosync,
13472
+ clearPushLock,
13473
+ fetchBeforeRead,
13474
+ installAutosync,
12072
13475
  getConfigPath,
12073
13476
  loadConfig,
12074
13477
  saveConfig,
@@ -12085,5 +13488,10 @@ export {
12085
13488
  authenticateLinear,
12086
13489
  getLinearTeams,
12087
13490
  createLinearIssue,
12088
- logoutLinear
13491
+ logoutLinear,
13492
+ PALETTE,
13493
+ RULE,
13494
+ getTypeGlyph,
13495
+ GLYPHS,
13496
+ getContractStateColor
12089
13497
  };