brainfile 0.22.0 → 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.
@@ -1608,7 +1608,7 @@ var require_boardRepo = __commonJS({
1608
1608
  };
1609
1609
  })();
1610
1610
  Object.defineProperty(exports, "__esModule", { value: true });
1611
- 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;
1611
+ exports.IGNORE_STATE_MESSAGE = exports.UNTRACK_STATE_MESSAGE = exports.BOARD_STATE_DIR = 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;
1612
1612
  exports.remoteBoardRef = remoteBoardRef2;
1613
1613
  exports.boardTrackingRef = boardTrackingRef2;
1614
1614
  exports.gitNetwork = gitNetwork2;
@@ -1633,6 +1633,8 @@ var require_boardRepo = __commonJS({
1633
1633
  exports.boardDirtyFiles = boardDirtyFiles2;
1634
1634
  exports.onBoardCommitted = onBoardCommitted2;
1635
1635
  exports.ensureBoardDirs = ensureBoardDirs2;
1636
+ exports.unstageBoardState = unstageBoardState;
1637
+ exports.keepBoardStateLocal = keepBoardStateLocal;
1636
1638
  exports.commitBoard = commitBoard2;
1637
1639
  exports.commitHandEdits = commitHandEdits2;
1638
1640
  exports.homeBoardDir = homeBoardDir2;
@@ -1666,6 +1668,11 @@ var require_boardRepo = __commonJS({
1666
1668
  var FALLBACK_IDENTITY_EMAIL = "brainfile@localhost";
1667
1669
  var MAX_MESSAGE_LENGTH = 72;
1668
1670
  var BOARD_SUBDIRS = ["board", "logs"];
1671
+ exports.BOARD_STATE_DIR = "state";
1672
+ var BOARD_PATHSPEC = ["--", ".", `:(exclude)${exports.BOARD_STATE_DIR}`];
1673
+ var STATE_IGNORE_LINES = ["state/", "state", "/state/", "/state"];
1674
+ exports.UNTRACK_STATE_MESSAGE = "untrack state/: this machine's sync bookkeeping never travels";
1675
+ exports.IGNORE_STATE_MESSAGE = "chore: keep state/ out of git";
1669
1676
  function git2(args, cwd, env, timeoutMs) {
1670
1677
  const result = (0, child_process_1.spawnSync)("git", args, {
1671
1678
  cwd,
@@ -1943,7 +1950,7 @@ ${EXCLUDE_ENTRY}
1943
1950
  throw new Error(`Could not initialize a git repository in ${dotDir}: ${r.stderr}`);
1944
1951
  }
1945
1952
  function boardIsDirty2(dotDir) {
1946
- const r = git2(["status", "--porcelain"], dotDir);
1953
+ const r = git2(["status", "--porcelain", ...BOARD_PATHSPEC], dotDir);
1947
1954
  return r.ok && r.stdout.length > 0;
1948
1955
  }
1949
1956
  function boardHasCommits2(dotDir) {
@@ -1954,7 +1961,7 @@ ${EXCLUDE_ENTRY}
1954
1961
  return r.ok ? r.stdout.split("\n").map((l) => l.trim()).filter(Boolean) : [];
1955
1962
  }
1956
1963
  function boardDirtyFiles2(dotDir) {
1957
- const r = git2(["status", "--porcelain"], dotDir);
1964
+ const r = git2(["status", "--porcelain", ...BOARD_PATHSPEC], dotDir);
1958
1965
  if (!r.ok || !r.stdout)
1959
1966
  return [];
1960
1967
  return r.stdout.split("\n").map((line) => line.replace(/^[ MADRCU?!]{1,2}\s+/, "").replace(/^.* -> /, "").trim()).filter(Boolean);
@@ -1996,27 +2003,74 @@ ${EXCLUDE_ENTRY}
1996
2003
  }
1997
2004
  return created;
1998
2005
  }
2006
+ function commitIndex(dotDir, message) {
2007
+ const r = git2([...identityArgs(dotDir), "-c", "commit.gpgsign=false", "commit", "--quiet", "--no-verify", "-m", message], dotDir);
2008
+ if (r.ok)
2009
+ notifyCommitted(dotDir);
2010
+ return r.ok;
2011
+ }
2012
+ function ensureStateIgnored(dotDir) {
2013
+ const file = path8.join(dotDir, ".gitignore");
2014
+ let existing = "";
2015
+ try {
2016
+ existing = fs8.readFileSync(file, "utf-8");
2017
+ } catch {
2018
+ }
2019
+ if (existing.split(/\r?\n/).some((line) => STATE_IGNORE_LINES.includes(line.trim())))
2020
+ return false;
2021
+ const separator = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
2022
+ try {
2023
+ fs8.writeFileSync(file, `${existing}${separator}${STATE_IGNORE_LINES[0]}
2024
+ `, "utf-8");
2025
+ return true;
2026
+ } catch {
2027
+ return false;
2028
+ }
2029
+ }
2030
+ function unstageBoardState(dotDir, env) {
2031
+ return git2(["rm", "-r", "--cached", "--quiet", "--ignore-unmatch", "--", exports.BOARD_STATE_DIR], dotDir, env).ok;
2032
+ }
2033
+ function keepBoardStateLocal(dotDir) {
2034
+ if (!isTrackedBoard2(dotDir))
2035
+ return false;
2036
+ if (unmergedFiles2(dotDir).length > 0)
2037
+ return false;
2038
+ if (git2(["rev-parse", "--verify", "--quiet", "MERGE_HEAD"], dotDir).ok)
2039
+ return false;
2040
+ const staged = git2(["ls-files", "--cached", "--", exports.BOARD_STATE_DIR], dotDir).stdout.length > 0;
2041
+ const committed = git2(["rev-parse", "--verify", "--quiet", `HEAD:${exports.BOARD_STATE_DIR}`], dotDir).ok;
2042
+ if (!staged && !committed && !fs8.existsSync(path8.join(dotDir, exports.BOARD_STATE_DIR)))
2043
+ return false;
2044
+ const ignoreChanged = ensureStateIgnored(dotDir);
2045
+ if (staged && !unstageBoardState(dotDir))
2046
+ return false;
2047
+ if (!committed && !ignoreChanged || !boardHasCommits2(dotDir))
2048
+ return false;
2049
+ if (ignoreChanged && !git2(["add", "--", ".gitignore"], dotDir).ok)
2050
+ return false;
2051
+ return commitIndex(dotDir, committed ? exports.UNTRACK_STATE_MESSAGE : exports.IGNORE_STATE_MESSAGE);
2052
+ }
1999
2053
  function commitBoard2(dotDir, options) {
2000
2054
  if (!isTrackedBoard2(dotDir))
2001
2055
  return false;
2002
2056
  ensureBoardDirs2(dotDir);
2003
2057
  if (unmergedFiles2(dotDir).length > 0)
2004
2058
  return false;
2059
+ const kept = keepBoardStateLocal(dotDir);
2005
2060
  if (!git2(["add", "-A"], dotDir).ok)
2006
- return false;
2061
+ return kept;
2062
+ if (!unstageBoardState(dotDir))
2063
+ return kept;
2007
2064
  if (!boardIsDirty2(dotDir))
2008
- return false;
2065
+ return kept;
2009
2066
  const agent = options.agent?.replace(/[[\]<>\r\n]/g, "").trim();
2010
2067
  const suffix = agent ? ` [${agent}]` : "";
2011
- const message = oneLine2(options.message, suffix);
2012
- const r = git2([...identityArgs(dotDir), "-c", "commit.gpgsign=false", "commit", "--quiet", "--no-verify", "-m", message], dotDir);
2013
- if (r.ok)
2014
- notifyCommitted(dotDir);
2015
- return r.ok;
2068
+ return commitIndex(dotDir, oneLine2(options.message, suffix)) || kept;
2016
2069
  }
2017
2070
  function commitHandEdits2(dotDir) {
2018
2071
  if (!isTrackedBoard2(dotDir))
2019
2072
  return false;
2073
+ keepBoardStateLocal(dotDir);
2020
2074
  const keepOnly = (f) => f.endsWith(".gitkeep") || BOARD_SUBDIRS.some((sub) => f === `${sub}/`);
2021
2075
  if (ensureBoardDirs2(dotDir) && boardDirtyFiles2(dotDir).every(keepOnly)) {
2022
2076
  commitBoard2(dotDir, { message: "chore: keep board/ and logs/ in git" });
@@ -2318,7 +2372,7 @@ var require_boardSync = __commonJS({
2318
2372
  exports.URL_REMOTE_NAME = "board";
2319
2373
  exports.MERGE_DRIVER_NAME = "brainfile";
2320
2374
  exports.DEFAULT_MERGE_DRIVER_COMMAND = "brainfile merge-driver %O %A %B";
2321
- var SYNC_STATE_FILE = path8.join("state", "sync.json");
2375
+ var SYNC_STATE_FILE = path8.join(boardRepo_1.BOARD_STATE_DIR, "sync.json");
2322
2376
  exports.BOARD_GITATTRIBUTES = [
2323
2377
  "logs/ledger.jsonl merge=union",
2324
2378
  "board/*.md merge=brainfile",
@@ -2447,7 +2501,7 @@ var require_boardSync = __commonJS({
2447
2501
  }
2448
2502
  function writeSyncState(dotDir, state) {
2449
2503
  try {
2450
- fs8.mkdirSync(path8.join(dotDir, "state"), { recursive: true });
2504
+ fs8.mkdirSync(path8.join(dotDir, boardRepo_1.BOARD_STATE_DIR), { recursive: true });
2451
2505
  fs8.writeFileSync(path8.join(dotDir, SYNC_STATE_FILE), `${JSON.stringify(state, null, 2)}
2452
2506
  `, "utf-8");
2453
2507
  } catch {
@@ -2516,14 +2570,53 @@ ${entryLine}
2516
2570
  }
2517
2571
  return resolved;
2518
2572
  }
2573
+ function saveLocalState(dotDir, incoming) {
2574
+ const saved = /* @__PURE__ */ new Map();
2575
+ const r = (0, boardRepo_1.git)(["diff", "--name-only", "-z", "--no-renames", `HEAD...${incoming}`, "--", boardRepo_1.BOARD_STATE_DIR], dotDir);
2576
+ if (!r.ok)
2577
+ return saved;
2578
+ for (const rel of r.stdout.split("\0").filter(Boolean)) {
2579
+ try {
2580
+ saved.set(rel, fs8.readFileSync(path8.join(dotDir, rel)));
2581
+ } catch {
2582
+ saved.set(rel, null);
2583
+ }
2584
+ }
2585
+ return saved;
2586
+ }
2587
+ function restoreLocalState(dotDir, saved) {
2588
+ for (const [rel, content] of saved) {
2589
+ const file = path8.join(dotDir, rel);
2590
+ try {
2591
+ if (content === null) {
2592
+ fs8.rmSync(file, { force: true });
2593
+ } else {
2594
+ fs8.mkdirSync(path8.dirname(file), { recursive: true });
2595
+ fs8.writeFileSync(file, content);
2596
+ }
2597
+ } catch {
2598
+ }
2599
+ }
2600
+ }
2519
2601
  function mergeIncoming(dotDir, incoming, result) {
2520
2602
  if ((0, boardRepo_1.git)(["merge-base", "--is-ancestor", incoming, "HEAD"], dotDir).ok)
2521
2603
  return { ok: true, pulled: 0 };
2604
+ const saved = saveLocalState(dotDir, incoming);
2605
+ try {
2606
+ return mergeIncomingInto(dotDir, incoming, result);
2607
+ } finally {
2608
+ restoreLocalState(dotDir, saved);
2609
+ }
2610
+ }
2611
+ function mergeIncomingInto(dotDir, incoming, result) {
2522
2612
  const before = (0, boardRepo_1.git)(["rev-parse", "HEAD"], dotDir).stdout;
2523
2613
  const id = identityArgs(dotDir);
2524
2614
  const merge = (0, boardRepo_1.git)([...id, "merge", "--quiet", "--no-edit", incoming], dotDir);
2525
2615
  if (!merge.ok) {
2526
2616
  resolveCompleteVersusEdit2(dotDir);
2617
+ if ((0, boardRepo_1.git)(["rev-parse", "--verify", "--quiet", "MERGE_HEAD"], dotDir).ok) {
2618
+ (0, boardRepo_1.unstageBoardState)(dotDir);
2619
+ }
2527
2620
  const remaining = (0, boardRepo_1.unmergedFiles)(dotDir);
2528
2621
  if (remaining.length > 0) {
2529
2622
  return {
@@ -2599,6 +2692,7 @@ ${entryLine}
2599
2692
  return finish(dotDir, merged.result);
2600
2693
  result.pulled += merged.pulled;
2601
2694
  }
2695
+ (0, boardRepo_1.keepBoardStateLocal)(dotDir);
2602
2696
  }
2603
2697
  if (push) {
2604
2698
  const known = (0, boardRepo_1.git)(["rev-parse", "--verify", "--quiet", tracking], dotDir).ok;
@@ -3693,7 +3787,9 @@ var require_consolidate = __commonJS({
3693
3787
  const index = path8.join(ctx.common, `brainfile-index-${process.pid}-${Date.now()}`);
3694
3788
  const env = { GIT_DIR: ctx.common, GIT_WORK_TREE: dir, GIT_INDEX_FILE: index };
3695
3789
  try {
3696
- must((0, boardRepo_1.git)(["add", "-A", "--", ".", ":(exclude)state", ":(exclude).git"], dir, env), `read ${dir}`);
3790
+ must((0, boardRepo_1.git)(["add", "-A", "--", ".", ":(exclude).git"], dir, env), `read ${dir}`);
3791
+ if (!(0, boardRepo_1.unstageBoardState)(dir, env))
3792
+ throw new Error(`read ${dir}: could not leave out state/`);
3697
3793
  return must((0, boardRepo_1.git)(["write-tree"], dir, env), `snapshot ${dir}`);
3698
3794
  } finally {
3699
3795
  fs8.rmSync(index, { force: true });
@@ -3843,7 +3939,7 @@ var require_consolidate = __commonJS({
3843
3939
  }
3844
3940
  for (const c of present) {
3845
3941
  (0, boardRepo_1.commitHandEdits)(c.path);
3846
- if (real(c.path) !== ctx.home && (0, boardRepo_1.git)(["status", "--porcelain"], c.path).stdout) {
3942
+ if (real(c.path) !== ctx.home && (0, boardRepo_1.boardIsDirty)(c.path)) {
3847
3943
  throw new Error(`The board branch is also checked out at ${c.path} with files git could not commit. Clear them there first. Nothing else was changed.`);
3848
3944
  }
3849
3945
  }
package/dist/cli.mjs CHANGED
@@ -65,7 +65,7 @@ import {
65
65
  validationError,
66
66
  validationError2,
67
67
  workspaceRootFromBrainfilePath
68
- } from "./chunk-BNE7R5GK.mjs";
68
+ } from "./chunk-CWUTU5VV.mjs";
69
69
  import {
70
70
  require_dist
71
71
  } from "./chunk-XFDTTLSW.mjs";
@@ -3507,7 +3507,7 @@ var require_package = __commonJS({
3507
3507
  "package.json"(exports, module) {
3508
3508
  module.exports = {
3509
3509
  name: "brainfile",
3510
- version: "0.22.0",
3510
+ version: "0.22.1",
3511
3511
  description: "Command-line interface for Brainfile task management",
3512
3512
  main: "dist/index.js",
3513
3513
  types: "dist/index.d.ts",
@@ -3544,7 +3544,7 @@ var require_package = __commonJS({
3544
3544
  access: "public"
3545
3545
  },
3546
3546
  dependencies: {
3547
- "@brainfile/core": "^0.22.0",
3547
+ "@brainfile/core": "^0.22.1",
3548
3548
  chalk: "^4.1.2",
3549
3549
  chokidar: "^4.0.3",
3550
3550
  commander: "^14.0.3",
@@ -44537,7 +44537,7 @@ function columnsRemoveCommand(column, options, logger = defaultLogger) {
44537
44537
 
44538
44538
  // src/program.ts
44539
44539
  async function runTui(options) {
44540
- const { tuiCommand } = await import("./tui-IR3JKS4Y.mjs");
44540
+ const { tuiCommand } = await import("./tui-D6RMFL6X.mjs");
44541
44541
  return tuiCommand(options);
44542
44542
  }
44543
44543
  var packageJson = JSON.parse(
@@ -21,7 +21,7 @@ import {
21
21
  require_source,
22
22
  resolveCliBrainfilePath,
23
23
  shouldSuggestV2Migration
24
- } from "./chunk-BNE7R5GK.mjs";
24
+ } from "./chunk-CWUTU5VV.mjs";
25
25
  import {
26
26
  require_dist
27
27
  } from "./chunk-XFDTTLSW.mjs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "brainfile",
3
- "version": "0.22.0",
3
+ "version": "0.22.1",
4
4
  "description": "Command-line interface for Brainfile task management",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -37,7 +37,7 @@
37
37
  "access": "public"
38
38
  },
39
39
  "dependencies": {
40
- "@brainfile/core": "^0.22.0",
40
+ "@brainfile/core": "^0.22.1",
41
41
  "chalk": "^4.1.2",
42
42
  "chokidar": "^4.0.3",
43
43
  "commander": "^14.0.3",