brainfile 0.21.2 → 0.22.1

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 (233) hide show
  1. package/README.md +1 -1
  2. package/dist/__tests__/helpers/mcp-wire.d.ts +2 -0
  3. package/dist/__tests__/helpers/mcp-wire.d.ts.map +1 -1
  4. package/dist/__tests__/helpers/mcp-wire.js +4 -2
  5. package/dist/__tests__/helpers/mcp-wire.js.map +1 -1
  6. package/dist/chunk-CWUTU5VV.mjs +5585 -0
  7. package/dist/chunk-L4WSDMK3.mjs +1973 -0
  8. package/dist/{chunk-7KU2R7QC.mjs → chunk-XFDTTLSW.mjs} +3230 -4322
  9. package/dist/cli.js +7 -1
  10. package/dist/cli.mjs +8078 -5357
  11. package/dist/commands/add.d.ts +4 -0
  12. package/dist/commands/add.d.ts.map +1 -1
  13. package/dist/commands/add.js +68 -72
  14. package/dist/commands/add.js.map +1 -1
  15. package/dist/commands/boards.d.ts +18 -0
  16. package/dist/commands/boards.d.ts.map +1 -0
  17. package/dist/commands/boards.js +166 -0
  18. package/dist/commands/boards.js.map +1 -0
  19. package/dist/commands/brief.d.ts +17 -11
  20. package/dist/commands/brief.d.ts.map +1 -1
  21. package/dist/commands/brief.js +38 -35
  22. package/dist/commands/brief.js.map +1 -1
  23. package/dist/commands/check.d.ts +47 -0
  24. package/dist/commands/check.d.ts.map +1 -0
  25. package/dist/commands/check.js +134 -0
  26. package/dist/commands/check.js.map +1 -0
  27. package/dist/commands/columns.d.ts +48 -0
  28. package/dist/commands/columns.d.ts.map +1 -0
  29. package/dist/commands/columns.js +132 -0
  30. package/dist/commands/columns.js.map +1 -0
  31. package/dist/commands/complete.js +9 -4
  32. package/dist/commands/complete.js.map +1 -1
  33. package/dist/commands/decide.d.ts +72 -0
  34. package/dist/commands/decide.d.ts.map +1 -0
  35. package/dist/commands/decide.js +264 -0
  36. package/dist/commands/decide.js.map +1 -0
  37. package/dist/commands/hooks.d.ts +5 -1
  38. package/dist/commands/hooks.d.ts.map +1 -1
  39. package/dist/commands/hooks.js +10 -11
  40. package/dist/commands/hooks.js.map +1 -1
  41. package/dist/commands/init.d.ts.map +1 -1
  42. package/dist/commands/init.js +8 -0
  43. package/dist/commands/init.js.map +1 -1
  44. package/dist/commands/lint.d.ts +27 -0
  45. package/dist/commands/lint.d.ts.map +1 -1
  46. package/dist/commands/lint.js +127 -20
  47. package/dist/commands/lint.js.map +1 -1
  48. package/dist/commands/list.d.ts +6 -0
  49. package/dist/commands/list.d.ts.map +1 -1
  50. package/dist/commands/list.js +216 -7
  51. package/dist/commands/list.js.map +1 -1
  52. package/dist/commands/log.d.ts +51 -0
  53. package/dist/commands/log.d.ts.map +1 -1
  54. package/dist/commands/log.js +113 -84
  55. package/dist/commands/log.js.map +1 -1
  56. package/dist/commands/mcp.d.ts +5 -2
  57. package/dist/commands/mcp.d.ts.map +1 -1
  58. package/dist/commands/mcp.js +44 -9
  59. package/dist/commands/mcp.js.map +1 -1
  60. package/dist/commands/memory-index.d.ts +34 -0
  61. package/dist/commands/memory-index.d.ts.map +1 -0
  62. package/dist/commands/memory-index.js +135 -0
  63. package/dist/commands/memory-index.js.map +1 -0
  64. package/dist/commands/merge-driver.d.ts +1 -15
  65. package/dist/commands/merge-driver.d.ts.map +1 -1
  66. package/dist/commands/merge-driver.js +8 -134
  67. package/dist/commands/merge-driver.js.map +1 -1
  68. package/dist/commands/migrate-tracked.d.ts +1 -1
  69. package/dist/commands/migrate-tracked.d.ts.map +1 -1
  70. package/dist/commands/migrate-tracked.js +21 -10
  71. package/dist/commands/migrate-tracked.js.map +1 -1
  72. package/dist/commands/patch.d.ts +23 -4
  73. package/dist/commands/patch.d.ts.map +1 -1
  74. package/dist/commands/patch.js +149 -91
  75. package/dist/commands/patch.js.map +1 -1
  76. package/dist/commands/plan.d.ts.map +1 -1
  77. package/dist/commands/plan.js +11 -5
  78. package/dist/commands/plan.js.map +1 -1
  79. package/dist/commands/schema.d.ts +12 -0
  80. package/dist/commands/schema.d.ts.map +1 -1
  81. package/dist/commands/schema.js +28 -10
  82. package/dist/commands/schema.js.map +1 -1
  83. package/dist/commands/search.d.ts +46 -12
  84. package/dist/commands/search.d.ts.map +1 -1
  85. package/dist/commands/search.js +89 -44
  86. package/dist/commands/search.js.map +1 -1
  87. package/dist/commands/show.d.ts.map +1 -1
  88. package/dist/commands/show.js +175 -10
  89. package/dist/commands/show.js.map +1 -1
  90. package/dist/commands/template.d.ts +2 -0
  91. package/dist/commands/template.d.ts.map +1 -1
  92. package/dist/commands/template.js +2 -1
  93. package/dist/commands/template.js.map +1 -1
  94. package/dist/commands/types.d.ts +31 -1
  95. package/dist/commands/types.d.ts.map +1 -1
  96. package/dist/commands/types.js +122 -22
  97. package/dist/commands/types.js.map +1 -1
  98. package/dist/commands/where.d.ts +20 -1
  99. package/dist/commands/where.d.ts.map +1 -1
  100. package/dist/commands/where.js +56 -6
  101. package/dist/commands/where.js.map +1 -1
  102. package/dist/hook.mjs +15 -0
  103. package/dist/hooks/session-start.d.ts +39 -0
  104. package/dist/hooks/session-start.d.ts.map +1 -0
  105. package/dist/hooks/session-start.js +181 -0
  106. package/dist/hooks/session-start.js.map +1 -0
  107. package/dist/lib/contractRunner.d.ts.map +1 -1
  108. package/dist/lib/contractRunner.js +8 -0
  109. package/dist/lib/contractRunner.js.map +1 -1
  110. package/dist/mcp/helpers.d.ts +29 -1
  111. package/dist/mcp/helpers.d.ts.map +1 -1
  112. package/dist/mcp/helpers.js +57 -2
  113. package/dist/mcp/helpers.js.map +1 -1
  114. package/dist/mcp/instructions.d.ts +5 -0
  115. package/dist/mcp/instructions.d.ts.map +1 -0
  116. package/dist/mcp/instructions.js +57 -0
  117. package/dist/mcp/instructions.js.map +1 -0
  118. package/dist/mcp/resources.d.ts +9 -0
  119. package/dist/mcp/resources.d.ts.map +1 -0
  120. package/dist/mcp/resources.js +29 -0
  121. package/dist/mcp/resources.js.map +1 -0
  122. package/dist/mcp/schemas.d.ts +600 -9
  123. package/dist/mcp/schemas.d.ts.map +1 -1
  124. package/dist/mcp/schemas.js +374 -12
  125. package/dist/mcp/schemas.js.map +1 -1
  126. package/dist/mcp/tools/adr_promote_tool.d.ts +7 -0
  127. package/dist/mcp/tools/adr_promote_tool.d.ts.map +1 -0
  128. package/dist/mcp/tools/adr_promote_tool.js +37 -0
  129. package/dist/mcp/tools/adr_promote_tool.js.map +1 -0
  130. package/dist/mcp/tools/board_tool.d.ts +7 -0
  131. package/dist/mcp/tools/board_tool.d.ts.map +1 -0
  132. package/dist/mcp/tools/board_tool.js +61 -0
  133. package/dist/mcp/tools/board_tool.js.map +1 -0
  134. package/dist/mcp/tools/brief_tool.d.ts.map +1 -1
  135. package/dist/mcp/tools/brief_tool.js +13 -5
  136. package/dist/mcp/tools/brief_tool.js.map +1 -1
  137. package/dist/mcp/tools/check_tool.d.ts +6 -0
  138. package/dist/mcp/tools/check_tool.d.ts.map +1 -0
  139. package/dist/mcp/tools/check_tool.js +54 -0
  140. package/dist/mcp/tools/check_tool.js.map +1 -0
  141. package/dist/mcp/tools/columns_tool.d.ts +7 -0
  142. package/dist/mcp/tools/columns_tool.d.ts.map +1 -0
  143. package/dist/mcp/tools/columns_tool.js +94 -0
  144. package/dist/mcp/tools/columns_tool.js.map +1 -0
  145. package/dist/mcp/tools/decide_tool.d.ts +91 -0
  146. package/dist/mcp/tools/decide_tool.d.ts.map +1 -0
  147. package/dist/mcp/tools/decide_tool.js +141 -0
  148. package/dist/mcp/tools/decide_tool.js.map +1 -0
  149. package/dist/mcp/tools/get_task_tool.d.ts.map +1 -1
  150. package/dist/mcp/tools/get_task_tool.js +62 -4
  151. package/dist/mcp/tools/get_task_tool.js.map +1 -1
  152. package/dist/mcp/tools/lint_tool.d.ts +7 -0
  153. package/dist/mcp/tools/lint_tool.d.ts.map +1 -0
  154. package/dist/mcp/tools/lint_tool.js +48 -0
  155. package/dist/mcp/tools/lint_tool.js.map +1 -0
  156. package/dist/mcp/tools/list_tasks_tool.d.ts.map +1 -1
  157. package/dist/mcp/tools/list_tasks_tool.js +132 -15
  158. package/dist/mcp/tools/list_tasks_tool.js.map +1 -1
  159. package/dist/mcp/tools/log_tool.d.ts +4 -0
  160. package/dist/mcp/tools/log_tool.d.ts.map +1 -0
  161. package/dist/mcp/tools/log_tool.js +52 -0
  162. package/dist/mcp/tools/log_tool.js.map +1 -0
  163. package/dist/mcp/tools/note_tool.d.ts +7 -0
  164. package/dist/mcp/tools/note_tool.d.ts.map +1 -0
  165. package/dist/mcp/tools/note_tool.js +39 -0
  166. package/dist/mcp/tools/note_tool.js.map +1 -0
  167. package/dist/mcp/tools/retire_tool.d.ts +4 -0
  168. package/dist/mcp/tools/retire_tool.d.ts.map +1 -0
  169. package/dist/mcp/tools/retire_tool.js +81 -0
  170. package/dist/mcp/tools/retire_tool.js.map +1 -0
  171. package/dist/mcp/tools/search_tool.d.ts.map +1 -1
  172. package/dist/mcp/tools/search_tool.js +36 -19
  173. package/dist/mcp/tools/search_tool.js.map +1 -1
  174. package/dist/mcp/tools/supersede_tool.d.ts +4 -0
  175. package/dist/mcp/tools/supersede_tool.d.ts.map +1 -0
  176. package/dist/mcp/tools/supersede_tool.js +72 -0
  177. package/dist/mcp/tools/supersede_tool.js.map +1 -0
  178. package/dist/mcp/tools/sync_tool.d.ts +9 -0
  179. package/dist/mcp/tools/sync_tool.d.ts.map +1 -0
  180. package/dist/mcp/tools/sync_tool.js +87 -0
  181. package/dist/mcp/tools/sync_tool.js.map +1 -0
  182. package/dist/mcp/tools/task_add_tool.d.ts.map +1 -1
  183. package/dist/mcp/tools/task_add_tool.js +39 -17
  184. package/dist/mcp/tools/task_add_tool.js.map +1 -1
  185. package/dist/mcp/tools/task_complete_tool.d.ts.map +1 -1
  186. package/dist/mcp/tools/task_complete_tool.js +3 -2
  187. package/dist/mcp/tools/task_complete_tool.js.map +1 -1
  188. package/dist/mcp/tools/task_patch_tool.d.ts.map +1 -1
  189. package/dist/mcp/tools/task_patch_tool.js +30 -5
  190. package/dist/mcp/tools/task_patch_tool.js.map +1 -1
  191. package/dist/mcp/tools/template_tool.d.ts +4 -0
  192. package/dist/mcp/tools/template_tool.d.ts.map +1 -0
  193. package/dist/mcp/tools/template_tool.js +70 -0
  194. package/dist/mcp/tools/template_tool.js.map +1 -0
  195. package/dist/mcp/tools/types_tool.d.ts +4 -0
  196. package/dist/mcp/tools/types_tool.d.ts.map +1 -0
  197. package/dist/mcp/tools/types_tool.js +61 -0
  198. package/dist/mcp/tools/types_tool.js.map +1 -0
  199. package/dist/mcp/tools/where_tool.d.ts +4 -0
  200. package/dist/mcp/tools/where_tool.d.ts.map +1 -0
  201. package/dist/mcp/tools/where_tool.js +73 -0
  202. package/dist/mcp/tools/where_tool.js.map +1 -0
  203. package/dist/schemas/board.json +29 -8
  204. package/dist/{tui-3Z7ZJV7M.mjs → tui-D6RMFL6X.mjs} +994 -315
  205. package/dist/utils/board-autosync.d.ts +1 -10
  206. package/dist/utils/board-autosync.d.ts.map +1 -1
  207. package/dist/utils/board-autosync.js +11 -29
  208. package/dist/utils/board-autosync.js.map +1 -1
  209. package/dist/utils/board-commit-hooks.d.ts +8 -0
  210. package/dist/utils/board-commit-hooks.d.ts.map +1 -1
  211. package/dist/utils/board-commit-hooks.js +45 -1
  212. package/dist/utils/board-commit-hooks.js.map +1 -1
  213. package/dist/utils/board-repo.d.ts +1 -119
  214. package/dist/utils/board-repo.d.ts.map +1 -1
  215. package/dist/utils/board-repo.js +43 -571
  216. package/dist/utils/board-repo.js.map +1 -1
  217. package/dist/utils/board-sync.d.ts +3 -123
  218. package/dist/utils/board-sync.d.ts.map +1 -1
  219. package/dist/utils/board-sync.js +34 -551
  220. package/dist/utils/board-sync.js.map +1 -1
  221. package/dist/utils/graph.d.ts +20 -0
  222. package/dist/utils/graph.d.ts.map +1 -0
  223. package/dist/utils/graph.js +61 -0
  224. package/dist/utils/graph.js.map +1 -0
  225. package/dist/utils/related.d.ts +21 -0
  226. package/dist/utils/related.d.ts.map +1 -0
  227. package/dist/utils/related.js +36 -0
  228. package/dist/utils/related.js.map +1 -0
  229. package/package.json +2 -2
  230. package/dist/tui/theme.d.ts +0 -98
  231. package/dist/tui/theme.d.ts.map +0 -1
  232. package/dist/tui/theme.js +0 -141
  233. package/dist/tui/theme.js.map +0 -1
@@ -1,550 +1,56 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || (function () {
19
- var ownKeys = function(o) {
20
- ownKeys = Object.getOwnPropertyNames || function (o) {
21
- var ar = [];
22
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
- return ar;
24
- };
25
- return ownKeys(o);
26
- };
27
- return function (mod) {
28
- if (mod && mod.__esModule) return mod;
29
- var result = {};
30
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
- __setModuleDefault(result, mod);
32
- return result;
33
- };
34
- })();
35
2
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.DEFAULT_BOARD_NAME = exports.LEGACY_REMOTE_BRANCH_CONFIG_KEY = exports.BOARD_NAME_CONFIG_KEY = exports.BOARD_REF_NAMESPACE = exports.BOARD_BRANCH_CONFIG_KEY = exports.DEFAULT_BOARD_BRANCH = void 0;
37
- exports.remoteBoardRef = remoteBoardRef;
38
- exports.boardTrackingRef = boardTrackingRef;
39
- exports.gitNetwork = gitNetwork;
40
- exports.git = git;
41
- exports.gitToplevel = gitToplevel;
42
- exports.gitCommonDir = gitCommonDir;
43
- exports.listWorktrees = listWorktrees;
44
- exports.boardBranch = boardBranch;
45
- exports.boardRepoKind = boardRepoKind;
46
- exports.isTrackedBoard = isTrackedBoard;
47
- exports.findTrackedBoardDir = findTrackedBoardDir;
48
- exports.listRemoteBoards = listRemoteBoards;
49
- exports.materializeBoardWorktree = materializeBoardWorktree;
50
- exports.ensureExcludeEntry = ensureExcludeEntry;
51
- exports.removeExcludeEntry = removeExcludeEntry;
52
- exports.createOrphanBoardWorktree = createOrphanBoardWorktree;
53
- exports.initStandaloneBoardRepo = initStandaloneBoardRepo;
54
- exports.boardIsDirty = boardIsDirty;
55
- exports.boardHasCommits = boardHasCommits;
56
- exports.unmergedFiles = unmergedFiles;
57
- exports.boardDirtyFiles = boardDirtyFiles;
58
- exports.onBoardCommitted = onBoardCommitted;
59
- exports.ensureBoardDirs = ensureBoardDirs;
60
- exports.commitBoard = commitBoard;
61
- exports.commitHandEdits = commitHandEdits;
62
- exports.homeBoardDir = homeBoardDir;
3
+ exports.flushBoardCommits = exports.scheduleBoardCommit = exports.homeBoardDir = exports.commitHandEdits = exports.commitBoard = exports.ensureBoardDirs = exports.onBoardCommitted = exports.boardDirtyFiles = exports.unmergedFiles = exports.boardHasCommits = exports.boardIsDirty = exports.initStandaloneBoardRepo = exports.createOrphanBoardWorktree = exports.removeExcludeEntry = exports.ensureExcludeEntry = exports.materializeBoardWorktree = exports.pruneLostBoardCheckouts = exports.listRemoteBoards = exports.findTrackedBoardDir = exports.isTrackedBoard = exports.boardRepoKind = exports.boardBranch = exports.listWorktrees = exports.gitCommonDir = exports.gitToplevel = exports.git = exports.gitNetwork = exports.boardTrackingRef = exports.remoteBoardRef = exports.DEFAULT_BOARD_NAME = exports.LEGACY_REMOTE_BRANCH_CONFIG_KEY = exports.BOARD_NAME_CONFIG_KEY = exports.BOARD_REF_NAMESPACE = exports.BOARD_BRANCH_CONFIG_KEY = exports.DEFAULT_BOARD_BRANCH = void 0;
63
4
  exports.isGlobalBoardRequested = isGlobalBoardRequested;
64
5
  exports.describeCommandForCommit = describeCommandForCommit;
65
- exports.scheduleBoardCommit = scheduleBoardCommit;
66
- exports.flushBoardCommits = flushBoardCommits;
67
- /**
68
- * Board-on-a-branch storage (spec-9, phase 1).
69
- *
70
- * A *tracked* board is a `.brainfile/` directory that is itself a git
71
- * repository: inside a project repo it is a linked worktree checked out on the
72
- * board branch (default `brainfile`, an orphan branch with its own history);
73
- * outside any repo it is a standalone repository. Either way every mutation
74
- * becomes one commit, authored by the git user, and the board is shared
75
- * by every worktree of the project because linked worktrees share one git
76
- * directory.
77
- *
78
- * Core stays git-free: everything that shells out to git lives here.
79
- */
80
- const fs = __importStar(require("fs"));
81
- const os = __importStar(require("os"));
82
- const path = __importStar(require("path"));
83
- const child_process_1 = require("child_process");
84
- exports.DEFAULT_BOARD_BRANCH = 'brainfile';
85
- exports.BOARD_BRANCH_CONFIG_KEY = 'brainfile.branch';
86
- /**
87
- * On a remote, a board is stored under `refs/brainfile/<name>` rather than as
88
- * a branch: hosts list, badge and offer pull requests for branches only, so
89
- * the board stays out of the project's branch list while still being pushed,
90
- * fetched and kept like any other ref. Locally the last-known remote position
91
- * lives under `refs/brainfile/remotes/<remote>/<name>`, outside `refs/remotes/`
92
- * so a user's `git fetch --prune` never deletes it.
93
- */
94
- exports.BOARD_REF_NAMESPACE = 'refs/brainfile';
95
- exports.BOARD_NAME_CONFIG_KEY = 'brainfile.boardName';
96
- /** 0.21.0 key: the branch the board was pushed to. Still read as the name. */
97
- exports.LEGACY_REMOTE_BRANCH_CONFIG_KEY = 'brainfile.remoteBranch';
98
- /** Default name for a board that lives in a code repository. */
99
- exports.DEFAULT_BOARD_NAME = 'board';
100
- function remoteBoardRef(name) {
101
- return `${exports.BOARD_REF_NAMESPACE}/${name}`;
102
- }
103
- function boardTrackingRef(remote, name) {
104
- return `${exports.BOARD_REF_NAMESPACE}/remotes/${remote}/${name}`;
105
- }
106
- /** A network git call that never prompts and gives up instead of hanging. */
107
- const NETWORK_TIMEOUT_MS = 8000;
108
- const NO_PROMPT_ENV = { GIT_TERMINAL_PROMPT: '0', GIT_ASKPASS: 'echo', SSH_ASKPASS: 'echo' };
109
- function gitNetwork(args, cwd) {
110
- return git(args, cwd, NO_PROMPT_ENV, NETWORK_TIMEOUT_MS);
111
- }
112
- const EXCLUDE_ENTRY = '.brainfile/';
113
- const EXCLUDE_MARKER = '# brainfile: board worktree (managed by `brainfile`)';
114
- const FALLBACK_IDENTITY_NAME = 'brainfile';
115
- const FALLBACK_IDENTITY_EMAIL = 'brainfile@localhost';
116
- const MAX_MESSAGE_LENGTH = 72;
117
- const BOARD_SUBDIRS = ['board', 'logs'];
118
- /** Run git synchronously. Never throws; a missing git binary is `ok: false`. */
119
- function git(args, cwd, env, timeoutMs) {
120
- const result = (0, child_process_1.spawnSync)('git', args, {
121
- cwd,
122
- encoding: 'utf-8',
123
- env: env ? { ...process.env, ...env } : process.env,
124
- stdio: ['ignore', 'pipe', 'pipe'],
125
- ...(timeoutMs ? { timeout: timeoutMs, killSignal: 'SIGKILL' } : {}),
126
- });
127
- return {
128
- ok: result.status === 0,
129
- status: result.status,
130
- stdout: (result.stdout ?? '').toString().trim(),
131
- stderr: (result.stderr ?? '').toString().trim(),
132
- };
133
- }
134
- /** Toplevel of the working tree containing `cwd`, or null outside any repo. */
135
- function gitToplevel(cwd) {
136
- const r = git(['rev-parse', '--show-toplevel'], cwd);
137
- return r.ok && r.stdout ? path.resolve(r.stdout) : null;
138
- }
139
- /** The git directory shared by every worktree of the repo containing `cwd`. */
140
- function gitCommonDir(cwd) {
141
- const r = git(['rev-parse', '--git-common-dir'], cwd);
142
- if (!r.ok || !r.stdout)
143
- return null;
144
- return path.resolve(cwd, r.stdout);
145
- }
146
- /** `git worktree list --porcelain`, parsed. Empty outside a repo. */
147
- function listWorktrees(cwd) {
148
- const r = git(['worktree', 'list', '--porcelain'], cwd);
149
- if (!r.ok)
150
- return [];
151
- const entries = [];
152
- let current = null;
153
- for (const line of r.stdout.split('\n')) {
154
- if (line.startsWith('worktree ')) {
155
- current = { path: path.resolve(line.slice('worktree '.length)), bare: false, detached: false };
156
- entries.push(current);
157
- }
158
- else if (!current) {
159
- continue;
160
- }
161
- else if (line.startsWith('HEAD ')) {
162
- current.head = line.slice(5);
163
- }
164
- else if (line.startsWith('branch ')) {
165
- current.branch = line.slice(7);
166
- }
167
- else if (line === 'bare') {
168
- current.bare = true;
169
- }
170
- else if (line === 'detached') {
171
- current.detached = true;
172
- }
173
- }
174
- return entries;
175
- }
176
- /** Name of the board branch for the repo containing `cwd` (`brainfile.branch`, default `brainfile`). */
177
- function boardBranch(cwd) {
178
- const r = git(['config', '--get', exports.BOARD_BRANCH_CONFIG_KEY], cwd);
179
- return r.ok && r.stdout ? r.stdout : exports.DEFAULT_BOARD_BRANCH;
180
- }
181
- /**
182
- * How the board directory is tracked: a linked worktree (`.git` is a gitdir
183
- * pointer file), a standalone repository (`.git` is a directory), or null for
184
- * a plain directory.
185
- */
186
- function boardRepoKind(dotDir) {
187
- try {
188
- const st = fs.statSync(path.join(dotDir, '.git'));
189
- if (st.isFile())
190
- return 'linked';
191
- if (st.isDirectory())
192
- return 'standalone';
193
- }
194
- catch {
195
- /* plain directory */
196
- }
197
- return null;
198
- }
199
- function isTrackedBoard(dotDir) {
200
- return boardRepoKind(dotDir) !== null;
201
- }
202
- /**
203
- * Resolution step 3: from anywhere inside a repo (including a linked code
204
- * worktree that has no `.brainfile/` of its own), find the worktree checked
205
- * out on the board branch.
206
- */
207
- function findTrackedBoardDir(cwd) {
208
- const worktrees = listWorktrees(cwd);
209
- if (worktrees.length === 0)
210
- return null;
211
- const wanted = `refs/heads/${boardBranch(cwd)}`;
212
- const hit = worktrees.find((w) => w.branch === wanted);
213
- if (!hit)
214
- return null;
215
- return fs.existsSync(path.join(hit.path, 'brainfile.md')) ? hit.path : null;
216
- }
217
- /** How long a failed lookup for a board on the remote is remembered. */
218
- const PROBE_MISS_TTL_MS = 10 * 60000;
219
- function probeMissFile(cwd) {
220
- const common = gitCommonDir(cwd);
221
- return common ? path.join(common, 'brainfile-probe.json') : null;
222
- }
223
- function recentProbeMiss(cwd, remote) {
224
- const file = probeMissFile(cwd);
225
- if (!file)
226
- return false;
227
- try {
228
- const miss = JSON.parse(fs.readFileSync(file, 'utf-8'));
229
- return miss.remote === remote && typeof miss.at === 'number' && Date.now() - miss.at < PROBE_MISS_TTL_MS;
230
- }
231
- catch {
232
- return false;
233
- }
234
- }
235
- function recordProbeMiss(cwd, remote) {
236
- const file = probeMissFile(cwd);
237
- if (!file)
238
- return;
239
- try {
240
- fs.writeFileSync(file, JSON.stringify({ remote, at: Date.now() }), 'utf-8');
241
- }
242
- catch {
243
- /* only a cache */
244
- }
245
- }
246
- function clearProbeMiss(cwd) {
247
- const file = probeMissFile(cwd);
248
- if (file)
249
- fs.rmSync(file, { force: true });
250
- }
251
- /**
252
- * Board names published on `remote` (`refs/brainfile/<name>`), or null when
253
- * the remote could not be asked. One network round trip.
254
- */
255
- function listRemoteBoards(cwd, remote) {
256
- const r = gitNetwork(['ls-remote', '--quiet', remote, `${exports.BOARD_REF_NAMESPACE}/*`], cwd);
257
- if (!r.ok)
258
- return null;
259
- const names = [];
260
- for (const line of r.stdout.split('\n')) {
261
- const ref = line.split('\t')[1] ?? '';
262
- const name = ref.slice(exports.BOARD_REF_NAMESPACE.length + 1);
263
- if (ref.startsWith(`${exports.BOARD_REF_NAMESPACE}/`) && name && !name.includes('/'))
264
- names.push(name);
265
- }
266
- return names;
267
- }
268
- /**
269
- * Where to start a board that is not checked out yet: the local branch, the
270
- * board published on the remote (`refs/brainfile/<name>`, fetched on
271
- * demand), or a board a 0.21.0 machine pushed as a plain branch.
272
- */
273
- function findBoardStart(cwd, branch, options) {
274
- if (git(['rev-parse', '--verify', '--quiet', `refs/heads/${branch}`], cwd).ok)
275
- return { ref: branch, remote: null };
276
- const configuredRemote = git(['config', '--get', 'brainfile.remote'], cwd).stdout || null;
277
- const remote = configuredRemote ?? (git(['remote', 'get-url', 'origin'], cwd).ok ? 'origin' : null);
278
- if (!remote)
279
- return null;
280
- const configuredName = git(['config', '--get', exports.BOARD_NAME_CONFIG_KEY], cwd).stdout ||
281
- git(['config', '--get', exports.LEGACY_REMOTE_BRANCH_CONFIG_KEY], cwd).stdout ||
282
- null;
283
- // Already fetched once (a previous attempt, or sync), no network needed.
284
- const knownName = configuredName ?? exports.DEFAULT_BOARD_NAME;
285
- const known = boardTrackingRef(remote, knownName);
286
- if (git(['rev-parse', '--verify', '--quiet', known], cwd).ok)
287
- return { ref: known, remote, name: knownName };
288
- if (options.fresh || !recentProbeMiss(cwd, remote)) {
289
- const names = listRemoteBoards(cwd, remote);
290
- const name = names === null
291
- ? null
292
- : configuredName
293
- ? (names.includes(configuredName) ? configuredName : null)
294
- : names.includes(exports.DEFAULT_BOARD_NAME)
295
- ? exports.DEFAULT_BOARD_NAME
296
- : names.length === 1 ? names[0] : null;
297
- if (name) {
298
- const tracking = boardTrackingRef(remote, name);
299
- if (gitNetwork(['fetch', '--quiet', '--no-tags', remote, `+${remoteBoardRef(name)}:${tracking}`], cwd).ok) {
300
- clearProbeMiss(cwd);
301
- return { ref: tracking, remote, name };
302
- }
303
- }
304
- }
305
- // A board shared by 0.21.0 is a branch on the remote; a clone already has it.
306
- const legacyBranch = configuredName ?? branch;
307
- const legacy = `refs/remotes/${remote}/${legacyBranch}`;
308
- if (!git(['rev-parse', '--verify', '--quiet', legacy], cwd).ok && configuredRemote && (options.fresh || !recentProbeMiss(cwd, remote))) {
309
- gitNetwork(['fetch', '--quiet', '--no-tags', remote, `+refs/heads/${legacyBranch}:${legacy}`], cwd);
310
- }
311
- if (git(['rev-parse', '--verify', '--quiet', legacy], cwd).ok)
312
- return { ref: legacy, remote };
313
- recordProbeMiss(cwd, remote);
314
- return null;
315
- }
316
- /**
317
- * Resolution step 4: the board is not checked out anywhere (a fresh clone,
318
- * or the directory was removed). Create the worktree at
319
- * `<main worktree>/.brainfile` — or inside the common git dir for bare
320
- * layouts — and hide it from the code branch. Returns the new board
321
- * directory, or null when there is nothing to materialize.
322
- */
323
- function materializeBoardWorktree(cwd, options = {}) {
324
- const branch = boardBranch(cwd);
325
- const worktrees = listWorktrees(cwd);
326
- if (worktrees.length === 0)
327
- return null;
328
- if (worktrees.some((w) => w.branch === `refs/heads/${branch}`))
329
- return null;
330
- const main = worktrees[0];
331
- let target;
332
- if (main.bare) {
333
- const common = gitCommonDir(cwd);
334
- if (!common)
335
- return null;
336
- target = path.join(common, 'brainfile');
337
- }
338
- else {
339
- target = path.join(main.path, '.brainfile');
340
- }
341
- if (fs.existsSync(target))
342
- return null;
343
- const start = findBoardStart(cwd, branch, options);
344
- if (!start)
345
- return null;
346
- const r = start.remote
347
- ? git(['worktree', 'add', '--quiet', '--no-track', '-b', branch, target, start.ref], cwd)
348
- : git(['worktree', 'add', '--quiet', target, branch], cwd);
349
- if (!r.ok)
350
- return null;
351
- ensureExcludeEntry(cwd);
352
- // Boards shared before empty directories were kept arrive without them.
353
- for (const sub of BOARD_SUBDIRS)
354
- fs.mkdirSync(path.join(target, sub), { recursive: true });
355
- if (start.remote) {
356
- // The board arrived through this remote, so that is where it goes back to.
357
- if (!git(['config', '--get', 'brainfile.remote'], cwd).stdout)
358
- git(['config', 'brainfile.remote', start.remote], cwd);
359
- if (start.name && start.name !== exports.DEFAULT_BOARD_NAME && !git(['config', '--get', exports.BOARD_NAME_CONFIG_KEY], cwd).stdout) {
360
- git(['config', exports.BOARD_NAME_CONFIG_KEY, start.name], cwd);
361
- }
362
- }
363
- const relative = path.relative(process.cwd(), target) || '.';
364
- process.stderr.write(start.remote
365
- ? `Checked out the shared board from ${start.remote} into ${relative}/\n`
366
- : `Checked out the board from the '${branch}' branch into ${relative}/\n`);
367
- return target;
368
- }
369
- /** Hide `.brainfile/` from the code branch without touching `.gitignore`. Returns true when an entry was added. */
370
- function ensureExcludeEntry(cwd) {
371
- const common = gitCommonDir(cwd);
372
- if (!common)
373
- return false;
374
- const file = path.join(common, 'info', 'exclude');
375
- const existing = fs.existsSync(file) ? fs.readFileSync(file, 'utf-8') : '';
376
- const present = existing
377
- .split(/\r?\n/)
378
- .some((line) => line.trim() === EXCLUDE_ENTRY || line.trim() === '.brainfile');
379
- if (present)
380
- return false;
381
- fs.mkdirSync(path.dirname(file), { recursive: true });
382
- const separator = existing.length > 0 && !existing.endsWith('\n') ? '\n' : '';
383
- fs.appendFileSync(file, `${separator}${EXCLUDE_MARKER}\n${EXCLUDE_ENTRY}\n`, 'utf-8');
384
- return true;
385
- }
386
- /** Remove the exclude entry, but only the one we wrote (identified by its marker). */
387
- function removeExcludeEntry(cwd) {
388
- const common = gitCommonDir(cwd);
389
- if (!common)
390
- return false;
391
- const file = path.join(common, 'info', 'exclude');
392
- if (!fs.existsSync(file))
393
- return false;
394
- const lines = fs.readFileSync(file, 'utf-8').split(/\r?\n/);
395
- const at = lines.indexOf(EXCLUDE_MARKER);
396
- if (at < 0)
397
- return false;
398
- const removeCount = lines[at + 1]?.trim() === EXCLUDE_ENTRY ? 2 : 1;
399
- lines.splice(at, removeCount);
400
- fs.writeFileSync(file, lines.join('\n'), 'utf-8');
401
- return true;
402
- }
403
- /** `git worktree add --orphan -b <branch> <dotDir>` (git >= 2.42). */
404
- function createOrphanBoardWorktree(repoCwd, dotDir, branch) {
405
- const r = git(['worktree', 'add', '--quiet', '--orphan', '-b', branch, dotDir], repoCwd);
406
- if (!r.ok) {
407
- throw new Error(`Could not create the board worktree on branch '${branch}' (git >= 2.42 is required for --orphan): ${r.stderr}`);
408
- }
409
- }
410
- /** `git init` inside the board directory for boards outside any repo. */
411
- function initStandaloneBoardRepo(dotDir) {
412
- const parent = path.dirname(dotDir);
413
- let r = git(['init', '--quiet', '-b', 'main', dotDir], parent);
414
- if (!r.ok)
415
- r = git(['init', '--quiet', dotDir], parent);
416
- if (!r.ok)
417
- throw new Error(`Could not initialize a git repository in ${dotDir}: ${r.stderr}`);
418
- }
419
- function boardIsDirty(dotDir) {
420
- const r = git(['status', '--porcelain'], dotDir);
421
- return r.ok && r.stdout.length > 0;
422
- }
423
- function boardHasCommits(dotDir) {
424
- return git(['rev-parse', '--verify', '--quiet', 'HEAD'], dotDir).ok;
425
- }
426
- /** Files with uncommitted changes, relative to the board directory. */
427
- /** Paths still carrying conflict markers from an unfinished merge. */
428
- function unmergedFiles(dotDir) {
429
- const r = git(['diff', '--name-only', '--diff-filter=U'], dotDir);
430
- return r.ok ? r.stdout.split('\n').map((l) => l.trim()).filter(Boolean) : [];
431
- }
432
- function boardDirtyFiles(dotDir) {
433
- const r = git(['status', '--porcelain'], dotDir);
434
- if (!r.ok || !r.stdout)
435
- return [];
436
- // `git()` trims stdout, so the first line may have lost its leading status
437
- // column; strip the status letters and whitespace rather than a fixed width.
438
- return r.stdout
439
- .split('\n')
440
- .map((line) => line.replace(/^[ MADRCU?!]{1,2}\s+/, '').replace(/^.* -> /, '').trim())
441
- .filter(Boolean);
442
- }
443
- function identityArgs(dotDir) {
444
- const args = [];
445
- if (!git(['config', '--get', 'user.name'], dotDir).stdout)
446
- args.push('-c', `user.name=${FALLBACK_IDENTITY_NAME}`);
447
- if (!git(['config', '--get', 'user.email'], dotDir).stdout)
448
- args.push('-c', `user.email=${FALLBACK_IDENTITY_EMAIL}`);
449
- return args;
450
- }
451
- const commitListeners = [];
452
- /** Run `listener` after every successful board commit made by this process. */
453
- function onBoardCommitted(listener) {
454
- if (!commitListeners.includes(listener))
455
- commitListeners.push(listener);
456
- }
457
- function notifyCommitted(dotDir) {
458
- for (const listener of commitListeners) {
459
- try {
460
- listener(dotDir);
461
- }
462
- catch {
463
- /* listeners are best-effort */
464
- }
465
- }
466
- }
467
- /**
468
- * Stage everything in the board directory and commit it. Returns true when a
469
- * commit was made, false when the board is plain or nothing changed. Never
470
- * throws: a failed commit must not fail the mutation it records.
471
- */
472
- /**
473
- * git does not store empty directories, and a board without `board/` reads
474
- * as legacy on the other side. Keep both alive with a `.gitkeep`.
475
- */
476
- function ensureBoardDirs(dotDir) {
477
- let created = false;
478
- for (const sub of BOARD_SUBDIRS) {
479
- const dir = path.join(dotDir, sub);
480
- try {
481
- fs.mkdirSync(dir, { recursive: true });
482
- const keep = path.join(dir, '.gitkeep');
483
- if (!fs.existsSync(keep)) {
484
- fs.writeFileSync(keep, '', 'utf-8');
485
- created = true;
486
- }
487
- }
488
- catch {
489
- /* best effort */
490
- }
491
- }
492
- return created;
493
- }
494
- function commitBoard(dotDir, options) {
495
- if (!isTrackedBoard(dotDir))
496
- return false;
497
- ensureBoardDirs(dotDir);
498
- // A merge a human still owns must not be committed with its markers.
499
- if (unmergedFiles(dotDir).length > 0)
500
- return false;
501
- if (!git(['add', '-A'], dotDir).ok)
502
- return false;
503
- if (!boardIsDirty(dotDir))
504
- return false;
505
- const agent = options.agent?.replace(/[[\]<>\r\n]/g, '').trim();
506
- const suffix = agent ? ` [${agent}]` : '';
507
- const message = oneLine(options.message, suffix);
508
- const r = git([...identityArgs(dotDir), '-c', 'commit.gpgsign=false', 'commit', '--quiet', '--no-verify', '-m', message], dotDir);
509
- if (r.ok)
510
- notifyCommitted(dotDir);
511
- return r.ok;
512
- }
513
6
  /**
514
- * Commit outstanding hand edits (files changed outside the CLI) before a
515
- * command mutates, so they are never folded into that command's commit.
7
+ * Board-on-a-branch storage (spec-9) lives in `@brainfile/core/repo` so every
8
+ * frontend commits the same way; this module re-exports it and adds what
9
+ * only the CLI needs: `-g` detection and commit messages built from a
10
+ * commander invocation.
516
11
  */
517
- function commitHandEdits(dotDir) {
518
- if (!isTrackedBoard(dotDir))
519
- return false;
520
- // Boards created before the .gitkeep files existed get them in their own
521
- // commit, not folded into whatever command happens to run next.
522
- // git lists a directory holding only new files as `board/`, not its contents.
523
- const keepOnly = (f) => f.endsWith('.gitkeep') || BOARD_SUBDIRS.some((sub) => f === `${sub}/`);
524
- if (ensureBoardDirs(dotDir) && boardDirtyFiles(dotDir).every(keepOnly)) {
525
- commitBoard(dotDir, { message: 'chore: keep board/ and logs/ in git' });
526
- }
527
- const files = boardDirtyFiles(dotDir);
528
- if (files.length === 0)
529
- return false;
530
- const summary = files.length <= 3 ? files.join(', ') : `${files.length} files`;
531
- return commitBoard(dotDir, { message: `edit: ${summary}` });
532
- }
533
- /** The home board: `~/.brainfile`, addressed with `-g` / `--global` or `BRAINFILE_GLOBAL=1`. */
534
- function homeBoardDir() {
535
- return path.join(os.homedir(), '.brainfile');
536
- }
12
+ const repo_1 = require("@brainfile/core/repo");
13
+ var repo_2 = require("@brainfile/core/repo");
14
+ Object.defineProperty(exports, "DEFAULT_BOARD_BRANCH", { enumerable: true, get: function () { return repo_2.DEFAULT_BOARD_BRANCH; } });
15
+ Object.defineProperty(exports, "BOARD_BRANCH_CONFIG_KEY", { enumerable: true, get: function () { return repo_2.BOARD_BRANCH_CONFIG_KEY; } });
16
+ Object.defineProperty(exports, "BOARD_REF_NAMESPACE", { enumerable: true, get: function () { return repo_2.BOARD_REF_NAMESPACE; } });
17
+ Object.defineProperty(exports, "BOARD_NAME_CONFIG_KEY", { enumerable: true, get: function () { return repo_2.BOARD_NAME_CONFIG_KEY; } });
18
+ Object.defineProperty(exports, "LEGACY_REMOTE_BRANCH_CONFIG_KEY", { enumerable: true, get: function () { return repo_2.LEGACY_REMOTE_BRANCH_CONFIG_KEY; } });
19
+ Object.defineProperty(exports, "DEFAULT_BOARD_NAME", { enumerable: true, get: function () { return repo_2.DEFAULT_BOARD_NAME; } });
20
+ Object.defineProperty(exports, "remoteBoardRef", { enumerable: true, get: function () { return repo_2.remoteBoardRef; } });
21
+ Object.defineProperty(exports, "boardTrackingRef", { enumerable: true, get: function () { return repo_2.boardTrackingRef; } });
22
+ Object.defineProperty(exports, "gitNetwork", { enumerable: true, get: function () { return repo_2.gitNetwork; } });
23
+ Object.defineProperty(exports, "git", { enumerable: true, get: function () { return repo_2.git; } });
24
+ Object.defineProperty(exports, "gitToplevel", { enumerable: true, get: function () { return repo_2.gitToplevel; } });
25
+ Object.defineProperty(exports, "gitCommonDir", { enumerable: true, get: function () { return repo_2.gitCommonDir; } });
26
+ Object.defineProperty(exports, "listWorktrees", { enumerable: true, get: function () { return repo_2.listWorktrees; } });
27
+ Object.defineProperty(exports, "boardBranch", { enumerable: true, get: function () { return repo_2.boardBranch; } });
28
+ Object.defineProperty(exports, "boardRepoKind", { enumerable: true, get: function () { return repo_2.boardRepoKind; } });
29
+ Object.defineProperty(exports, "isTrackedBoard", { enumerable: true, get: function () { return repo_2.isTrackedBoard; } });
30
+ Object.defineProperty(exports, "findTrackedBoardDir", { enumerable: true, get: function () { return repo_2.findTrackedBoardDir; } });
31
+ Object.defineProperty(exports, "listRemoteBoards", { enumerable: true, get: function () { return repo_2.listRemoteBoards; } });
32
+ Object.defineProperty(exports, "pruneLostBoardCheckouts", { enumerable: true, get: function () { return repo_2.pruneLostBoardCheckouts; } });
33
+ Object.defineProperty(exports, "materializeBoardWorktree", { enumerable: true, get: function () { return repo_2.materializeBoardWorktree; } });
34
+ Object.defineProperty(exports, "ensureExcludeEntry", { enumerable: true, get: function () { return repo_2.ensureExcludeEntry; } });
35
+ Object.defineProperty(exports, "removeExcludeEntry", { enumerable: true, get: function () { return repo_2.removeExcludeEntry; } });
36
+ Object.defineProperty(exports, "createOrphanBoardWorktree", { enumerable: true, get: function () { return repo_2.createOrphanBoardWorktree; } });
37
+ Object.defineProperty(exports, "initStandaloneBoardRepo", { enumerable: true, get: function () { return repo_2.initStandaloneBoardRepo; } });
38
+ Object.defineProperty(exports, "boardIsDirty", { enumerable: true, get: function () { return repo_2.boardIsDirty; } });
39
+ Object.defineProperty(exports, "boardHasCommits", { enumerable: true, get: function () { return repo_2.boardHasCommits; } });
40
+ Object.defineProperty(exports, "unmergedFiles", { enumerable: true, get: function () { return repo_2.unmergedFiles; } });
41
+ Object.defineProperty(exports, "boardDirtyFiles", { enumerable: true, get: function () { return repo_2.boardDirtyFiles; } });
42
+ Object.defineProperty(exports, "onBoardCommitted", { enumerable: true, get: function () { return repo_2.onBoardCommitted; } });
43
+ Object.defineProperty(exports, "ensureBoardDirs", { enumerable: true, get: function () { return repo_2.ensureBoardDirs; } });
44
+ Object.defineProperty(exports, "commitBoard", { enumerable: true, get: function () { return repo_2.commitBoard; } });
45
+ Object.defineProperty(exports, "commitHandEdits", { enumerable: true, get: function () { return repo_2.commitHandEdits; } });
46
+ Object.defineProperty(exports, "homeBoardDir", { enumerable: true, get: function () { return repo_2.homeBoardDir; } });
47
+ Object.defineProperty(exports, "scheduleBoardCommit", { enumerable: true, get: function () { return repo_2.scheduleBoardCommit; } });
48
+ Object.defineProperty(exports, "flushBoardCommits", { enumerable: true, get: function () { return repo_2.flushBoardCommits; } });
537
49
  function isGlobalBoardRequested(argv = process.argv) {
538
50
  if (process.env.BRAINFILE_GLOBAL === '1')
539
51
  return true;
540
52
  return argv.includes('-g') || argv.includes('--global');
541
53
  }
542
- /** One line of at most MAX_MESSAGE_LENGTH, keeping `suffix` whole at the end. */
543
- function oneLine(message, suffix = '') {
544
- const flat = message.replace(/\s+/g, ' ').trim();
545
- const room = Math.max(1, MAX_MESSAGE_LENGTH - suffix.length);
546
- return `${flat.length > room ? `${flat.slice(0, room - 1)}…` : flat}${suffix}`;
547
- }
548
54
  /**
549
55
  * Commit message for a CLI invocation: `<verb> <id>: <detail>`, e.g.
550
56
  * `move task-3: in-progress`, `add: Fix login bug`, `contract pickup task-2`.
@@ -563,40 +69,6 @@ function describeCommandForCommit(command) {
563
69
  const detail = [opts.title, opts.column, positional.join(' ')]
564
70
  .find((v) => typeof v === 'string' && v.length > 0) ?? '';
565
71
  const head = `${names.join(' ')}${id ? ` ${id}` : ''}`;
566
- return oneLine(detail ? `${head}: ${detail}` : head);
567
- }
568
- // ── Debounced commits for long-running frontends (TUI) ──────────────────────
569
- const pendingCommits = new Map();
570
- let flushTimer = null;
571
- let exitHookInstalled = false;
572
- /** Queue a commit for a board; bursts of edits within `delayMs` become one commit. */
573
- function scheduleBoardCommit(brainfilePath, message, delayMs = 2000) {
574
- const dotDir = path.dirname(path.resolve(brainfilePath));
575
- if (!isTrackedBoard(dotDir))
576
- return;
577
- const entry = pendingCommits.get(dotDir) ?? { messages: [] };
578
- entry.messages.push(message);
579
- pendingCommits.set(dotDir, entry);
580
- if (flushTimer)
581
- clearTimeout(flushTimer);
582
- flushTimer = setTimeout(flushBoardCommits, delayMs);
583
- flushTimer.unref?.();
584
- if (!exitHookInstalled) {
585
- exitHookInstalled = true;
586
- process.once('exit', flushBoardCommits);
587
- }
588
- }
589
- /** Commit every queued board now. Safe to call from an `exit` handler (synchronous). */
590
- function flushBoardCommits() {
591
- if (flushTimer) {
592
- clearTimeout(flushTimer);
593
- flushTimer = null;
594
- }
595
- for (const [dotDir, entry] of pendingCommits) {
596
- const [first, ...rest] = entry.messages;
597
- const message = rest.length === 0 ? first : `${first} (+${rest.length} more)`;
598
- commitBoard(dotDir, { message });
599
- }
600
- pendingCommits.clear();
72
+ return (0, repo_1.oneLine)(detail ? `${head}: ${detail}` : head);
601
73
  }
602
74
  //# sourceMappingURL=board-repo.js.map