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.
- package/README.md +1 -1
- package/dist/__tests__/helpers/mcp-wire.d.ts +2 -0
- package/dist/__tests__/helpers/mcp-wire.d.ts.map +1 -1
- package/dist/__tests__/helpers/mcp-wire.js +4 -2
- package/dist/__tests__/helpers/mcp-wire.js.map +1 -1
- package/dist/chunk-CWUTU5VV.mjs +5585 -0
- package/dist/chunk-L4WSDMK3.mjs +1973 -0
- package/dist/{chunk-7KU2R7QC.mjs → chunk-XFDTTLSW.mjs} +3230 -4322
- package/dist/cli.js +7 -1
- package/dist/cli.mjs +8078 -5357
- package/dist/commands/add.d.ts +4 -0
- package/dist/commands/add.d.ts.map +1 -1
- package/dist/commands/add.js +68 -72
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/boards.d.ts +18 -0
- package/dist/commands/boards.d.ts.map +1 -0
- package/dist/commands/boards.js +166 -0
- package/dist/commands/boards.js.map +1 -0
- package/dist/commands/brief.d.ts +17 -11
- package/dist/commands/brief.d.ts.map +1 -1
- package/dist/commands/brief.js +38 -35
- package/dist/commands/brief.js.map +1 -1
- package/dist/commands/check.d.ts +47 -0
- package/dist/commands/check.d.ts.map +1 -0
- package/dist/commands/check.js +134 -0
- package/dist/commands/check.js.map +1 -0
- package/dist/commands/columns.d.ts +48 -0
- package/dist/commands/columns.d.ts.map +1 -0
- package/dist/commands/columns.js +132 -0
- package/dist/commands/columns.js.map +1 -0
- package/dist/commands/complete.js +9 -4
- package/dist/commands/complete.js.map +1 -1
- package/dist/commands/decide.d.ts +72 -0
- package/dist/commands/decide.d.ts.map +1 -0
- package/dist/commands/decide.js +264 -0
- package/dist/commands/decide.js.map +1 -0
- package/dist/commands/hooks.d.ts +5 -1
- package/dist/commands/hooks.d.ts.map +1 -1
- package/dist/commands/hooks.js +10 -11
- package/dist/commands/hooks.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +8 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/lint.d.ts +27 -0
- package/dist/commands/lint.d.ts.map +1 -1
- package/dist/commands/lint.js +127 -20
- package/dist/commands/lint.js.map +1 -1
- package/dist/commands/list.d.ts +6 -0
- package/dist/commands/list.d.ts.map +1 -1
- package/dist/commands/list.js +216 -7
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/log.d.ts +51 -0
- package/dist/commands/log.d.ts.map +1 -1
- package/dist/commands/log.js +113 -84
- package/dist/commands/log.js.map +1 -1
- package/dist/commands/mcp.d.ts +5 -2
- package/dist/commands/mcp.d.ts.map +1 -1
- package/dist/commands/mcp.js +44 -9
- package/dist/commands/mcp.js.map +1 -1
- package/dist/commands/memory-index.d.ts +34 -0
- package/dist/commands/memory-index.d.ts.map +1 -0
- package/dist/commands/memory-index.js +135 -0
- package/dist/commands/memory-index.js.map +1 -0
- package/dist/commands/merge-driver.d.ts +1 -15
- package/dist/commands/merge-driver.d.ts.map +1 -1
- package/dist/commands/merge-driver.js +8 -134
- package/dist/commands/merge-driver.js.map +1 -1
- package/dist/commands/migrate-tracked.d.ts +1 -1
- package/dist/commands/migrate-tracked.d.ts.map +1 -1
- package/dist/commands/migrate-tracked.js +21 -10
- package/dist/commands/migrate-tracked.js.map +1 -1
- package/dist/commands/patch.d.ts +23 -4
- package/dist/commands/patch.d.ts.map +1 -1
- package/dist/commands/patch.js +149 -91
- package/dist/commands/patch.js.map +1 -1
- package/dist/commands/plan.d.ts.map +1 -1
- package/dist/commands/plan.js +11 -5
- package/dist/commands/plan.js.map +1 -1
- package/dist/commands/schema.d.ts +12 -0
- package/dist/commands/schema.d.ts.map +1 -1
- package/dist/commands/schema.js +28 -10
- package/dist/commands/schema.js.map +1 -1
- package/dist/commands/search.d.ts +46 -12
- package/dist/commands/search.d.ts.map +1 -1
- package/dist/commands/search.js +89 -44
- package/dist/commands/search.js.map +1 -1
- package/dist/commands/show.d.ts.map +1 -1
- package/dist/commands/show.js +175 -10
- package/dist/commands/show.js.map +1 -1
- package/dist/commands/template.d.ts +2 -0
- package/dist/commands/template.d.ts.map +1 -1
- package/dist/commands/template.js +2 -1
- package/dist/commands/template.js.map +1 -1
- package/dist/commands/types.d.ts +31 -1
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/commands/types.js +122 -22
- package/dist/commands/types.js.map +1 -1
- package/dist/commands/where.d.ts +20 -1
- package/dist/commands/where.d.ts.map +1 -1
- package/dist/commands/where.js +56 -6
- package/dist/commands/where.js.map +1 -1
- package/dist/hook.mjs +15 -0
- package/dist/hooks/session-start.d.ts +39 -0
- package/dist/hooks/session-start.d.ts.map +1 -0
- package/dist/hooks/session-start.js +181 -0
- package/dist/hooks/session-start.js.map +1 -0
- package/dist/lib/contractRunner.d.ts.map +1 -1
- package/dist/lib/contractRunner.js +8 -0
- package/dist/lib/contractRunner.js.map +1 -1
- package/dist/mcp/helpers.d.ts +29 -1
- package/dist/mcp/helpers.d.ts.map +1 -1
- package/dist/mcp/helpers.js +57 -2
- package/dist/mcp/helpers.js.map +1 -1
- package/dist/mcp/instructions.d.ts +5 -0
- package/dist/mcp/instructions.d.ts.map +1 -0
- package/dist/mcp/instructions.js +57 -0
- package/dist/mcp/instructions.js.map +1 -0
- package/dist/mcp/resources.d.ts +9 -0
- package/dist/mcp/resources.d.ts.map +1 -0
- package/dist/mcp/resources.js +29 -0
- package/dist/mcp/resources.js.map +1 -0
- package/dist/mcp/schemas.d.ts +600 -9
- package/dist/mcp/schemas.d.ts.map +1 -1
- package/dist/mcp/schemas.js +374 -12
- package/dist/mcp/schemas.js.map +1 -1
- package/dist/mcp/tools/adr_promote_tool.d.ts +7 -0
- package/dist/mcp/tools/adr_promote_tool.d.ts.map +1 -0
- package/dist/mcp/tools/adr_promote_tool.js +37 -0
- package/dist/mcp/tools/adr_promote_tool.js.map +1 -0
- package/dist/mcp/tools/board_tool.d.ts +7 -0
- package/dist/mcp/tools/board_tool.d.ts.map +1 -0
- package/dist/mcp/tools/board_tool.js +61 -0
- package/dist/mcp/tools/board_tool.js.map +1 -0
- package/dist/mcp/tools/brief_tool.d.ts.map +1 -1
- package/dist/mcp/tools/brief_tool.js +13 -5
- package/dist/mcp/tools/brief_tool.js.map +1 -1
- package/dist/mcp/tools/check_tool.d.ts +6 -0
- package/dist/mcp/tools/check_tool.d.ts.map +1 -0
- package/dist/mcp/tools/check_tool.js +54 -0
- package/dist/mcp/tools/check_tool.js.map +1 -0
- package/dist/mcp/tools/columns_tool.d.ts +7 -0
- package/dist/mcp/tools/columns_tool.d.ts.map +1 -0
- package/dist/mcp/tools/columns_tool.js +94 -0
- package/dist/mcp/tools/columns_tool.js.map +1 -0
- package/dist/mcp/tools/decide_tool.d.ts +91 -0
- package/dist/mcp/tools/decide_tool.d.ts.map +1 -0
- package/dist/mcp/tools/decide_tool.js +141 -0
- package/dist/mcp/tools/decide_tool.js.map +1 -0
- package/dist/mcp/tools/get_task_tool.d.ts.map +1 -1
- package/dist/mcp/tools/get_task_tool.js +62 -4
- package/dist/mcp/tools/get_task_tool.js.map +1 -1
- package/dist/mcp/tools/lint_tool.d.ts +7 -0
- package/dist/mcp/tools/lint_tool.d.ts.map +1 -0
- package/dist/mcp/tools/lint_tool.js +48 -0
- package/dist/mcp/tools/lint_tool.js.map +1 -0
- package/dist/mcp/tools/list_tasks_tool.d.ts.map +1 -1
- package/dist/mcp/tools/list_tasks_tool.js +132 -15
- package/dist/mcp/tools/list_tasks_tool.js.map +1 -1
- package/dist/mcp/tools/log_tool.d.ts +4 -0
- package/dist/mcp/tools/log_tool.d.ts.map +1 -0
- package/dist/mcp/tools/log_tool.js +52 -0
- package/dist/mcp/tools/log_tool.js.map +1 -0
- package/dist/mcp/tools/note_tool.d.ts +7 -0
- package/dist/mcp/tools/note_tool.d.ts.map +1 -0
- package/dist/mcp/tools/note_tool.js +39 -0
- package/dist/mcp/tools/note_tool.js.map +1 -0
- package/dist/mcp/tools/retire_tool.d.ts +4 -0
- package/dist/mcp/tools/retire_tool.d.ts.map +1 -0
- package/dist/mcp/tools/retire_tool.js +81 -0
- package/dist/mcp/tools/retire_tool.js.map +1 -0
- package/dist/mcp/tools/search_tool.d.ts.map +1 -1
- package/dist/mcp/tools/search_tool.js +36 -19
- package/dist/mcp/tools/search_tool.js.map +1 -1
- package/dist/mcp/tools/supersede_tool.d.ts +4 -0
- package/dist/mcp/tools/supersede_tool.d.ts.map +1 -0
- package/dist/mcp/tools/supersede_tool.js +72 -0
- package/dist/mcp/tools/supersede_tool.js.map +1 -0
- package/dist/mcp/tools/sync_tool.d.ts +9 -0
- package/dist/mcp/tools/sync_tool.d.ts.map +1 -0
- package/dist/mcp/tools/sync_tool.js +87 -0
- package/dist/mcp/tools/sync_tool.js.map +1 -0
- package/dist/mcp/tools/task_add_tool.d.ts.map +1 -1
- package/dist/mcp/tools/task_add_tool.js +39 -17
- package/dist/mcp/tools/task_add_tool.js.map +1 -1
- package/dist/mcp/tools/task_complete_tool.d.ts.map +1 -1
- package/dist/mcp/tools/task_complete_tool.js +3 -2
- package/dist/mcp/tools/task_complete_tool.js.map +1 -1
- package/dist/mcp/tools/task_patch_tool.d.ts.map +1 -1
- package/dist/mcp/tools/task_patch_tool.js +30 -5
- package/dist/mcp/tools/task_patch_tool.js.map +1 -1
- package/dist/mcp/tools/template_tool.d.ts +4 -0
- package/dist/mcp/tools/template_tool.d.ts.map +1 -0
- package/dist/mcp/tools/template_tool.js +70 -0
- package/dist/mcp/tools/template_tool.js.map +1 -0
- package/dist/mcp/tools/types_tool.d.ts +4 -0
- package/dist/mcp/tools/types_tool.d.ts.map +1 -0
- package/dist/mcp/tools/types_tool.js +61 -0
- package/dist/mcp/tools/types_tool.js.map +1 -0
- package/dist/mcp/tools/where_tool.d.ts +4 -0
- package/dist/mcp/tools/where_tool.d.ts.map +1 -0
- package/dist/mcp/tools/where_tool.js +73 -0
- package/dist/mcp/tools/where_tool.js.map +1 -0
- package/dist/schemas/board.json +29 -8
- package/dist/{tui-3Z7ZJV7M.mjs → tui-D6RMFL6X.mjs} +994 -315
- package/dist/utils/board-autosync.d.ts +1 -10
- package/dist/utils/board-autosync.d.ts.map +1 -1
- package/dist/utils/board-autosync.js +11 -29
- package/dist/utils/board-autosync.js.map +1 -1
- package/dist/utils/board-commit-hooks.d.ts +8 -0
- package/dist/utils/board-commit-hooks.d.ts.map +1 -1
- package/dist/utils/board-commit-hooks.js +45 -1
- package/dist/utils/board-commit-hooks.js.map +1 -1
- package/dist/utils/board-repo.d.ts +1 -119
- package/dist/utils/board-repo.d.ts.map +1 -1
- package/dist/utils/board-repo.js +43 -571
- package/dist/utils/board-repo.js.map +1 -1
- package/dist/utils/board-sync.d.ts +3 -123
- package/dist/utils/board-sync.d.ts.map +1 -1
- package/dist/utils/board-sync.js +34 -551
- package/dist/utils/board-sync.js.map +1 -1
- package/dist/utils/graph.d.ts +20 -0
- package/dist/utils/graph.d.ts.map +1 -0
- package/dist/utils/graph.js +61 -0
- package/dist/utils/graph.js.map +1 -0
- package/dist/utils/related.d.ts +21 -0
- package/dist/utils/related.d.ts.map +1 -0
- package/dist/utils/related.js +36 -0
- package/dist/utils/related.js.map +1 -0
- package/package.json +2 -2
- package/dist/tui/theme.d.ts +0 -98
- package/dist/tui/theme.d.ts.map +0 -1
- package/dist/tui/theme.js +0 -141
- package/dist/tui/theme.js.map +0 -1
package/dist/utils/board-repo.js
CHANGED
|
@@ -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
|
-
*
|
|
515
|
-
*
|
|
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
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
function
|
|
535
|
-
|
|
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
|