brainfile 0.20.0 → 0.21.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/dist/{chunk-UOCQBEEW.mjs → chunk-MUV7XTFA.mjs} +1345 -316
- package/dist/cli.mjs +2196 -1349
- package/dist/commands/brief.d.ts +1 -0
- package/dist/commands/brief.d.ts.map +1 -1
- package/dist/commands/brief.js +10 -0
- package/dist/commands/brief.js.map +1 -1
- package/dist/commands/init.d.ts +14 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +115 -15
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/log.d.ts.map +1 -1
- package/dist/commands/log.js +4 -1
- package/dist/commands/log.js.map +1 -1
- package/dist/commands/mcp.d.ts.map +1 -1
- package/dist/commands/mcp.js +44 -0
- package/dist/commands/mcp.js.map +1 -1
- package/dist/commands/merge-driver.d.ts +26 -0
- package/dist/commands/merge-driver.d.ts.map +1 -0
- package/dist/commands/merge-driver.js +195 -0
- package/dist/commands/merge-driver.js.map +1 -0
- package/dist/commands/migrate-tracked.d.ts +8 -0
- package/dist/commands/migrate-tracked.d.ts.map +1 -0
- package/dist/commands/migrate-tracked.js +428 -0
- package/dist/commands/migrate-tracked.js.map +1 -0
- package/dist/commands/migrate.d.ts +6 -0
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/migrate.js +9 -0
- package/dist/commands/migrate.js.map +1 -1
- package/dist/commands/sync.d.ts +18 -0
- package/dist/commands/sync.d.ts.map +1 -0
- package/dist/commands/sync.js +161 -0
- package/dist/commands/sync.js.map +1 -0
- package/dist/commands/where.d.ts +24 -0
- package/dist/commands/where.d.ts.map +1 -0
- package/dist/commands/where.js +141 -0
- package/dist/commands/where.js.map +1 -0
- package/dist/mcp/helpers.d.ts +9 -0
- package/dist/mcp/helpers.d.ts.map +1 -1
- package/dist/mcp/helpers.js +43 -0
- package/dist/mcp/helpers.js.map +1 -1
- package/dist/mcp/tools/brief_tool.d.ts.map +1 -1
- package/dist/mcp/tools/brief_tool.js +39 -1
- package/dist/mcp/tools/brief_tool.js.map +1 -1
- package/dist/mcp/tools/contract_tool.d.ts.map +1 -1
- package/dist/mcp/tools/contract_tool.js +6 -5
- package/dist/mcp/tools/contract_tool.js.map +1 -1
- package/dist/mcp/tools/subtask_tool.d.ts.map +1 -1
- package/dist/mcp/tools/subtask_tool.js +5 -4
- package/dist/mcp/tools/subtask_tool.js.map +1 -1
- package/dist/mcp/tools/task_add_tool.d.ts.map +1 -1
- package/dist/mcp/tools/task_add_tool.js +6 -5
- 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 +5 -4
- package/dist/mcp/tools/task_complete_tool.js.map +1 -1
- package/dist/mcp/tools/task_delete_tool.d.ts.map +1 -1
- package/dist/mcp/tools/task_delete_tool.js +5 -4
- package/dist/mcp/tools/task_delete_tool.js.map +1 -1
- package/dist/mcp/tools/task_move_tool.d.ts.map +1 -1
- package/dist/mcp/tools/task_move_tool.js +6 -5
- package/dist/mcp/tools/task_move_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 +5 -4
- package/dist/mcp/tools/task_patch_tool.js.map +1 -1
- package/dist/{tui-H3RI22S4.mjs → tui-5TFQR53I.mjs} +295 -253
- package/dist/utils/actor.d.ts +22 -0
- package/dist/utils/actor.d.ts.map +1 -0
- package/dist/utils/actor.js +200 -0
- package/dist/utils/actor.js.map +1 -0
- package/dist/utils/board-autosync.d.ts +22 -0
- package/dist/utils/board-autosync.d.ts.map +1 -0
- package/dist/utils/board-autosync.js +166 -0
- package/dist/utils/board-autosync.js.map +1 -0
- package/dist/utils/board-commit-hooks.d.ts +12 -0
- package/dist/utils/board-commit-hooks.d.ts.map +1 -0
- package/dist/utils/board-commit-hooks.js +52 -0
- package/dist/utils/board-commit-hooks.js.map +1 -0
- package/dist/utils/board-repo.d.ts +132 -0
- package/dist/utils/board-repo.d.ts.map +1 -0
- package/dist/utils/board-repo.js +602 -0
- package/dist/utils/board-repo.js.map +1 -0
- package/dist/utils/board-sync.d.ts +126 -0
- package/dist/utils/board-sync.d.ts.map +1 -0
- package/dist/utils/board-sync.js +554 -0
- package/dist/utils/board-sync.js.map +1 -0
- package/dist/utils/brainfile-path.d.ts +11 -1
- package/dist/utils/brainfile-path.d.ts.map +1 -1
- package/dist/utils/brainfile-path.js +39 -2
- package/dist/utils/brainfile-path.js.map +1 -1
- package/package.json +2 -2
|
@@ -880,15 +880,15 @@ var require_route = __commonJS({
|
|
|
880
880
|
};
|
|
881
881
|
}
|
|
882
882
|
function wrapConversion(toModel, graph) {
|
|
883
|
-
const
|
|
883
|
+
const path11 = [graph[toModel].parent, toModel];
|
|
884
884
|
let fn = conversions[graph[toModel].parent][toModel];
|
|
885
885
|
let cur = graph[toModel].parent;
|
|
886
886
|
while (graph[cur].parent) {
|
|
887
|
-
|
|
887
|
+
path11.unshift(graph[cur].parent);
|
|
888
888
|
fn = link(conversions[graph[cur].parent][cur], fn);
|
|
889
889
|
cur = graph[cur].parent;
|
|
890
890
|
}
|
|
891
|
-
fn.conversion =
|
|
891
|
+
fn.conversion = path11;
|
|
892
892
|
return fn;
|
|
893
893
|
}
|
|
894
894
|
module.exports = function(fromModel) {
|
|
@@ -1128,7 +1128,7 @@ var require_has_flag = __commonJS({
|
|
|
1128
1128
|
var require_supports_color = __commonJS({
|
|
1129
1129
|
"../node_modules/supports-color/index.js"(exports, module) {
|
|
1130
1130
|
"use strict";
|
|
1131
|
-
var
|
|
1131
|
+
var os5 = __require("os");
|
|
1132
1132
|
var tty = __require("tty");
|
|
1133
1133
|
var hasFlag = require_has_flag();
|
|
1134
1134
|
var { env } = process;
|
|
@@ -1176,7 +1176,7 @@ var require_supports_color = __commonJS({
|
|
|
1176
1176
|
return min;
|
|
1177
1177
|
}
|
|
1178
1178
|
if (process.platform === "win32") {
|
|
1179
|
-
const osRelease =
|
|
1179
|
+
const osRelease = os5.release().split(".");
|
|
1180
1180
|
if (Number(osRelease[0]) >= 10 && Number(osRelease[2]) >= 10586) {
|
|
1181
1181
|
return Number(osRelease[2]) >= 14931 ? 3 : 2;
|
|
1182
1182
|
}
|
|
@@ -2086,12 +2086,12 @@ var require_contractSpec = __commonJS({
|
|
|
2086
2086
|
const type = raw.slice(0, firstColon).trim();
|
|
2087
2087
|
const rest = raw.slice(firstColon + 1);
|
|
2088
2088
|
const secondColon = rest.indexOf(":");
|
|
2089
|
-
const
|
|
2089
|
+
const path11 = (secondColon === -1 ? rest : rest.slice(0, secondColon)).trim();
|
|
2090
2090
|
const description = (secondColon === -1 ? "" : rest.slice(secondColon + 1)).trim();
|
|
2091
2091
|
if (!type) {
|
|
2092
2092
|
throw new Error(`Invalid deliverable format: "${spec}". Expected "type:path:description" with non-empty type.`);
|
|
2093
2093
|
}
|
|
2094
|
-
if (!
|
|
2094
|
+
if (!path11) {
|
|
2095
2095
|
throw new Error(`Invalid deliverable format: "${spec}". Expected "type:path:description" with non-empty path.`);
|
|
2096
2096
|
}
|
|
2097
2097
|
const typeLower = type.toLowerCase();
|
|
@@ -2100,7 +2100,7 @@ var require_contractSpec = __commonJS({
|
|
|
2100
2100
|
}
|
|
2101
2101
|
const deliverable = {
|
|
2102
2102
|
type: typeLower,
|
|
2103
|
-
path:
|
|
2103
|
+
path: path11,
|
|
2104
2104
|
...description ? { description } : {}
|
|
2105
2105
|
};
|
|
2106
2106
|
return deliverable;
|
|
@@ -5570,6 +5570,104 @@ ${yamlContent}---
|
|
|
5570
5570
|
}
|
|
5571
5571
|
});
|
|
5572
5572
|
|
|
5573
|
+
// ../core/dist/frontmatter.js
|
|
5574
|
+
var require_frontmatter = __commonJS({
|
|
5575
|
+
"../core/dist/frontmatter.js"(exports) {
|
|
5576
|
+
"use strict";
|
|
5577
|
+
var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
|
|
5578
|
+
if (k2 === void 0) k2 = k;
|
|
5579
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5580
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
5581
|
+
desc = { enumerable: true, get: function() {
|
|
5582
|
+
return m[k];
|
|
5583
|
+
} };
|
|
5584
|
+
}
|
|
5585
|
+
Object.defineProperty(o, k2, desc);
|
|
5586
|
+
}) : (function(o, m, k, k2) {
|
|
5587
|
+
if (k2 === void 0) k2 = k;
|
|
5588
|
+
o[k2] = m[k];
|
|
5589
|
+
}));
|
|
5590
|
+
var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? (function(o, v) {
|
|
5591
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
5592
|
+
}) : function(o, v) {
|
|
5593
|
+
o["default"] = v;
|
|
5594
|
+
});
|
|
5595
|
+
var __importStar = exports && exports.__importStar || /* @__PURE__ */ (function() {
|
|
5596
|
+
var ownKeys = function(o) {
|
|
5597
|
+
ownKeys = Object.getOwnPropertyNames || function(o2) {
|
|
5598
|
+
var ar = [];
|
|
5599
|
+
for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
|
|
5600
|
+
return ar;
|
|
5601
|
+
};
|
|
5602
|
+
return ownKeys(o);
|
|
5603
|
+
};
|
|
5604
|
+
return function(mod) {
|
|
5605
|
+
if (mod && mod.__esModule) return mod;
|
|
5606
|
+
var result = {};
|
|
5607
|
+
if (mod != null) {
|
|
5608
|
+
for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
5609
|
+
}
|
|
5610
|
+
__setModuleDefault(result, mod);
|
|
5611
|
+
return result;
|
|
5612
|
+
};
|
|
5613
|
+
})();
|
|
5614
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5615
|
+
exports.parseFrontmatter = parseFrontmatter2;
|
|
5616
|
+
exports.serializeFrontmatter = serializeFrontmatter2;
|
|
5617
|
+
var yaml = __importStar(require_js_yaml());
|
|
5618
|
+
function parseFrontmatter2(content) {
|
|
5619
|
+
const lines = content.split("\n");
|
|
5620
|
+
if (!lines[0] || lines[0].trim() !== "---") {
|
|
5621
|
+
return null;
|
|
5622
|
+
}
|
|
5623
|
+
let endIndex = -1;
|
|
5624
|
+
for (let i = 1; i < lines.length; i++) {
|
|
5625
|
+
if (lines[i].trim() === "---") {
|
|
5626
|
+
endIndex = i;
|
|
5627
|
+
break;
|
|
5628
|
+
}
|
|
5629
|
+
}
|
|
5630
|
+
if (endIndex === -1) {
|
|
5631
|
+
return null;
|
|
5632
|
+
}
|
|
5633
|
+
const yamlContent = lines.slice(1, endIndex).join("\n");
|
|
5634
|
+
const bodyContent = lines.slice(endIndex + 1).join("\n");
|
|
5635
|
+
let parsed;
|
|
5636
|
+
try {
|
|
5637
|
+
parsed = yaml.load(yamlContent);
|
|
5638
|
+
} catch {
|
|
5639
|
+
return null;
|
|
5640
|
+
}
|
|
5641
|
+
if (!parsed || typeof parsed !== "object") {
|
|
5642
|
+
return null;
|
|
5643
|
+
}
|
|
5644
|
+
return {
|
|
5645
|
+
data: parsed,
|
|
5646
|
+
body: bodyContent.replace(/^\n/, "")
|
|
5647
|
+
};
|
|
5648
|
+
}
|
|
5649
|
+
function serializeFrontmatter2(data, body = "") {
|
|
5650
|
+
const yamlContent = yaml.dump(data, {
|
|
5651
|
+
indent: 2,
|
|
5652
|
+
lineWidth: -1,
|
|
5653
|
+
noRefs: true,
|
|
5654
|
+
sortKeys: false,
|
|
5655
|
+
quotingType: '"',
|
|
5656
|
+
forceQuotes: false
|
|
5657
|
+
});
|
|
5658
|
+
const parts = ["---\n", yamlContent, "---\n"];
|
|
5659
|
+
if (body.length > 0) {
|
|
5660
|
+
parts.push("\n");
|
|
5661
|
+
parts.push(body);
|
|
5662
|
+
if (!body.endsWith("\n")) {
|
|
5663
|
+
parts.push("\n");
|
|
5664
|
+
}
|
|
5665
|
+
}
|
|
5666
|
+
return parts.join("");
|
|
5667
|
+
}
|
|
5668
|
+
}
|
|
5669
|
+
});
|
|
5670
|
+
|
|
5573
5671
|
// ../core/dist/validator.js
|
|
5574
5672
|
var require_validator = __commonJS({
|
|
5575
5673
|
"../core/dist/validator.js"(exports) {
|
|
@@ -5630,23 +5728,23 @@ var require_validator = __commonJS({
|
|
|
5630
5728
|
* @param path - The path for error reporting
|
|
5631
5729
|
* @returns Array of validation errors
|
|
5632
5730
|
*/
|
|
5633
|
-
static validateColumn(column,
|
|
5731
|
+
static validateColumn(column, path11) {
|
|
5634
5732
|
const errors = [];
|
|
5635
5733
|
if (!column) {
|
|
5636
|
-
errors.push({ path:
|
|
5734
|
+
errors.push({ path: path11, message: "Column is null or undefined" });
|
|
5637
5735
|
return errors;
|
|
5638
5736
|
}
|
|
5639
5737
|
if (typeof column.id !== "string" || column.id.trim() === "") {
|
|
5640
|
-
errors.push({ path: `${
|
|
5738
|
+
errors.push({ path: `${path11}.id`, message: "Column id must be a non-empty string" });
|
|
5641
5739
|
}
|
|
5642
5740
|
if (typeof column.title !== "string" || column.title.trim() === "") {
|
|
5643
|
-
errors.push({ path: `${
|
|
5741
|
+
errors.push({ path: `${path11}.title`, message: "Column title must be a non-empty string" });
|
|
5644
5742
|
}
|
|
5645
5743
|
if (column.tasks !== void 0 && !Array.isArray(column.tasks)) {
|
|
5646
|
-
errors.push({ path: `${
|
|
5744
|
+
errors.push({ path: `${path11}.tasks`, message: "Column tasks must be an array" });
|
|
5647
5745
|
} else if (Array.isArray(column.tasks)) {
|
|
5648
5746
|
column.tasks.forEach((task, index) => {
|
|
5649
|
-
const taskErrors = this.validateTask(task, `${
|
|
5747
|
+
const taskErrors = this.validateTask(task, `${path11}.tasks[${index}]`);
|
|
5650
5748
|
errors.push(...taskErrors);
|
|
5651
5749
|
});
|
|
5652
5750
|
}
|
|
@@ -5658,23 +5756,23 @@ var require_validator = __commonJS({
|
|
|
5658
5756
|
* @param path - The path for error reporting
|
|
5659
5757
|
* @returns Array of validation errors
|
|
5660
5758
|
*/
|
|
5661
|
-
static validateTask(task,
|
|
5759
|
+
static validateTask(task, path11) {
|
|
5662
5760
|
const errors = [];
|
|
5663
5761
|
if (!task) {
|
|
5664
|
-
errors.push({ path:
|
|
5762
|
+
errors.push({ path: path11, message: "Task is null or undefined" });
|
|
5665
5763
|
return errors;
|
|
5666
5764
|
}
|
|
5667
5765
|
if (typeof task.id !== "string" || task.id.trim() === "") {
|
|
5668
|
-
errors.push({ path: `${
|
|
5766
|
+
errors.push({ path: `${path11}.id`, message: "Task id must be a non-empty string" });
|
|
5669
5767
|
}
|
|
5670
5768
|
if (typeof task.title !== "string" || task.title.trim() === "") {
|
|
5671
|
-
errors.push({ path: `${
|
|
5769
|
+
errors.push({ path: `${path11}.title`, message: "Task title must be a non-empty string" });
|
|
5672
5770
|
}
|
|
5673
5771
|
if (task.priority !== void 0) {
|
|
5674
5772
|
const validPriorities = ["low", "medium", "high", "critical"];
|
|
5675
5773
|
if (!validPriorities.includes(task.priority)) {
|
|
5676
5774
|
errors.push({
|
|
5677
|
-
path: `${
|
|
5775
|
+
path: `${path11}.priority`,
|
|
5678
5776
|
message: `Task priority must be one of: ${validPriorities.join(", ")}`
|
|
5679
5777
|
});
|
|
5680
5778
|
}
|
|
@@ -5683,23 +5781,23 @@ var require_validator = __commonJS({
|
|
|
5683
5781
|
const validTemplates = ["bug", "feature", "refactor"];
|
|
5684
5782
|
if (!validTemplates.includes(task.template)) {
|
|
5685
5783
|
errors.push({
|
|
5686
|
-
path: `${
|
|
5784
|
+
path: `${path11}.template`,
|
|
5687
5785
|
message: `Task template must be one of: ${validTemplates.join(", ")}`
|
|
5688
5786
|
});
|
|
5689
5787
|
}
|
|
5690
5788
|
}
|
|
5691
5789
|
if (task.tags !== void 0 && !Array.isArray(task.tags)) {
|
|
5692
|
-
errors.push({ path: `${
|
|
5790
|
+
errors.push({ path: `${path11}.tags`, message: "Task tags must be an array" });
|
|
5693
5791
|
}
|
|
5694
5792
|
if (task.relatedFiles !== void 0 && !Array.isArray(task.relatedFiles)) {
|
|
5695
|
-
errors.push({ path: `${
|
|
5793
|
+
errors.push({ path: `${path11}.relatedFiles`, message: "Task relatedFiles must be an array" });
|
|
5696
5794
|
}
|
|
5697
5795
|
if (task.subtasks !== void 0) {
|
|
5698
5796
|
if (!Array.isArray(task.subtasks)) {
|
|
5699
|
-
errors.push({ path: `${
|
|
5797
|
+
errors.push({ path: `${path11}.subtasks`, message: "Task subtasks must be an array" });
|
|
5700
5798
|
} else {
|
|
5701
5799
|
task.subtasks.forEach((subtask, index) => {
|
|
5702
|
-
const subtaskErrors = this.validateSubtask(subtask, `${
|
|
5800
|
+
const subtaskErrors = this.validateSubtask(subtask, `${path11}.subtasks[${index}]`);
|
|
5703
5801
|
errors.push(...subtaskErrors);
|
|
5704
5802
|
});
|
|
5705
5803
|
}
|
|
@@ -5712,20 +5810,20 @@ var require_validator = __commonJS({
|
|
|
5712
5810
|
* @param path - The path for error reporting
|
|
5713
5811
|
* @returns Array of validation errors
|
|
5714
5812
|
*/
|
|
5715
|
-
static validateSubtask(subtask,
|
|
5813
|
+
static validateSubtask(subtask, path11) {
|
|
5716
5814
|
const errors = [];
|
|
5717
5815
|
if (!subtask) {
|
|
5718
|
-
errors.push({ path:
|
|
5816
|
+
errors.push({ path: path11, message: "Subtask is null or undefined" });
|
|
5719
5817
|
return errors;
|
|
5720
5818
|
}
|
|
5721
5819
|
if (typeof subtask.id !== "string" || subtask.id.trim() === "") {
|
|
5722
|
-
errors.push({ path: `${
|
|
5820
|
+
errors.push({ path: `${path11}.id`, message: "Subtask id must be a non-empty string" });
|
|
5723
5821
|
}
|
|
5724
5822
|
if (typeof subtask.title !== "string" || subtask.title.trim() === "") {
|
|
5725
|
-
errors.push({ path: `${
|
|
5823
|
+
errors.push({ path: `${path11}.title`, message: "Subtask title must be a non-empty string" });
|
|
5726
5824
|
}
|
|
5727
5825
|
if (typeof subtask.completed !== "boolean") {
|
|
5728
|
-
errors.push({ path: `${
|
|
5826
|
+
errors.push({ path: `${path11}.completed`, message: "Subtask completed must be a boolean" });
|
|
5729
5827
|
}
|
|
5730
5828
|
return errors;
|
|
5731
5829
|
}
|
|
@@ -5788,32 +5886,32 @@ var require_validator = __commonJS({
|
|
|
5788
5886
|
* @param path - The path for error reporting
|
|
5789
5887
|
* @returns Array of validation errors
|
|
5790
5888
|
*/
|
|
5791
|
-
static validateJournalEntry(entry,
|
|
5889
|
+
static validateJournalEntry(entry, path11) {
|
|
5792
5890
|
const errors = [];
|
|
5793
5891
|
if (!entry) {
|
|
5794
|
-
errors.push({ path:
|
|
5892
|
+
errors.push({ path: path11, message: "Entry is null or undefined" });
|
|
5795
5893
|
return errors;
|
|
5796
5894
|
}
|
|
5797
5895
|
if (typeof entry.id !== "string" || entry.id.trim() === "") {
|
|
5798
|
-
errors.push({ path: `${
|
|
5896
|
+
errors.push({ path: `${path11}.id`, message: "Entry id must be a non-empty string" });
|
|
5799
5897
|
}
|
|
5800
5898
|
if (typeof entry.title !== "string" || entry.title.trim() === "") {
|
|
5801
|
-
errors.push({ path: `${
|
|
5899
|
+
errors.push({ path: `${path11}.title`, message: "Entry title must be a non-empty string" });
|
|
5802
5900
|
}
|
|
5803
5901
|
if (typeof entry.createdAt !== "string" || entry.createdAt.trim() === "") {
|
|
5804
|
-
errors.push({ path: `${
|
|
5902
|
+
errors.push({ path: `${path11}.createdAt`, message: "Entry createdAt must be an ISO 8601 timestamp string" });
|
|
5805
5903
|
}
|
|
5806
5904
|
if (entry.content !== void 0 && typeof entry.content !== "string") {
|
|
5807
|
-
errors.push({ path: `${
|
|
5905
|
+
errors.push({ path: `${path11}.content`, message: "Entry content must be a string" });
|
|
5808
5906
|
}
|
|
5809
5907
|
if (entry.summary !== void 0 && typeof entry.summary !== "string") {
|
|
5810
|
-
errors.push({ path: `${
|
|
5908
|
+
errors.push({ path: `${path11}.summary`, message: "Entry summary must be a string" });
|
|
5811
5909
|
}
|
|
5812
5910
|
if (entry.mood !== void 0 && typeof entry.mood !== "string") {
|
|
5813
|
-
errors.push({ path: `${
|
|
5911
|
+
errors.push({ path: `${path11}.mood`, message: "Entry mood must be a string" });
|
|
5814
5912
|
}
|
|
5815
5913
|
if (entry.tags !== void 0 && !Array.isArray(entry.tags)) {
|
|
5816
|
-
errors.push({ path: `${
|
|
5914
|
+
errors.push({ path: `${path11}.tags`, message: "Entry tags must be an array" });
|
|
5817
5915
|
}
|
|
5818
5916
|
return errors;
|
|
5819
5917
|
}
|
|
@@ -5822,104 +5920,6 @@ var require_validator = __commonJS({
|
|
|
5822
5920
|
}
|
|
5823
5921
|
});
|
|
5824
5922
|
|
|
5825
|
-
// ../core/dist/frontmatter.js
|
|
5826
|
-
var require_frontmatter = __commonJS({
|
|
5827
|
-
"../core/dist/frontmatter.js"(exports) {
|
|
5828
|
-
"use strict";
|
|
5829
|
-
var __createBinding = exports && exports.__createBinding || (Object.create ? (function(o, m, k, k2) {
|
|
5830
|
-
if (k2 === void 0) k2 = k;
|
|
5831
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5832
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
5833
|
-
desc = { enumerable: true, get: function() {
|
|
5834
|
-
return m[k];
|
|
5835
|
-
} };
|
|
5836
|
-
}
|
|
5837
|
-
Object.defineProperty(o, k2, desc);
|
|
5838
|
-
}) : (function(o, m, k, k2) {
|
|
5839
|
-
if (k2 === void 0) k2 = k;
|
|
5840
|
-
o[k2] = m[k];
|
|
5841
|
-
}));
|
|
5842
|
-
var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? (function(o, v) {
|
|
5843
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
5844
|
-
}) : function(o, v) {
|
|
5845
|
-
o["default"] = v;
|
|
5846
|
-
});
|
|
5847
|
-
var __importStar = exports && exports.__importStar || /* @__PURE__ */ (function() {
|
|
5848
|
-
var ownKeys = function(o) {
|
|
5849
|
-
ownKeys = Object.getOwnPropertyNames || function(o2) {
|
|
5850
|
-
var ar = [];
|
|
5851
|
-
for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
|
|
5852
|
-
return ar;
|
|
5853
|
-
};
|
|
5854
|
-
return ownKeys(o);
|
|
5855
|
-
};
|
|
5856
|
-
return function(mod) {
|
|
5857
|
-
if (mod && mod.__esModule) return mod;
|
|
5858
|
-
var result = {};
|
|
5859
|
-
if (mod != null) {
|
|
5860
|
-
for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
5861
|
-
}
|
|
5862
|
-
__setModuleDefault(result, mod);
|
|
5863
|
-
return result;
|
|
5864
|
-
};
|
|
5865
|
-
})();
|
|
5866
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5867
|
-
exports.parseFrontmatter = parseFrontmatter;
|
|
5868
|
-
exports.serializeFrontmatter = serializeFrontmatter;
|
|
5869
|
-
var yaml = __importStar(require_js_yaml());
|
|
5870
|
-
function parseFrontmatter(content) {
|
|
5871
|
-
const lines = content.split("\n");
|
|
5872
|
-
if (!lines[0] || lines[0].trim() !== "---") {
|
|
5873
|
-
return null;
|
|
5874
|
-
}
|
|
5875
|
-
let endIndex = -1;
|
|
5876
|
-
for (let i = 1; i < lines.length; i++) {
|
|
5877
|
-
if (lines[i].trim() === "---") {
|
|
5878
|
-
endIndex = i;
|
|
5879
|
-
break;
|
|
5880
|
-
}
|
|
5881
|
-
}
|
|
5882
|
-
if (endIndex === -1) {
|
|
5883
|
-
return null;
|
|
5884
|
-
}
|
|
5885
|
-
const yamlContent = lines.slice(1, endIndex).join("\n");
|
|
5886
|
-
const bodyContent = lines.slice(endIndex + 1).join("\n");
|
|
5887
|
-
let parsed;
|
|
5888
|
-
try {
|
|
5889
|
-
parsed = yaml.load(yamlContent);
|
|
5890
|
-
} catch {
|
|
5891
|
-
return null;
|
|
5892
|
-
}
|
|
5893
|
-
if (!parsed || typeof parsed !== "object") {
|
|
5894
|
-
return null;
|
|
5895
|
-
}
|
|
5896
|
-
return {
|
|
5897
|
-
data: parsed,
|
|
5898
|
-
body: bodyContent.replace(/^\n/, "")
|
|
5899
|
-
};
|
|
5900
|
-
}
|
|
5901
|
-
function serializeFrontmatter(data, body = "") {
|
|
5902
|
-
const yamlContent = yaml.dump(data, {
|
|
5903
|
-
indent: 2,
|
|
5904
|
-
lineWidth: -1,
|
|
5905
|
-
noRefs: true,
|
|
5906
|
-
sortKeys: false,
|
|
5907
|
-
quotingType: '"',
|
|
5908
|
-
forceQuotes: false
|
|
5909
|
-
});
|
|
5910
|
-
const parts = ["---\n", yamlContent, "---\n"];
|
|
5911
|
-
if (body.length > 0) {
|
|
5912
|
-
parts.push("\n");
|
|
5913
|
-
parts.push(body);
|
|
5914
|
-
if (!body.endsWith("\n")) {
|
|
5915
|
-
parts.push("\n");
|
|
5916
|
-
}
|
|
5917
|
-
}
|
|
5918
|
-
return parts.join("");
|
|
5919
|
-
}
|
|
5920
|
-
}
|
|
5921
|
-
});
|
|
5922
|
-
|
|
5923
5923
|
// ../core/dist/linter.js
|
|
5924
5924
|
var require_linter = __commonJS({
|
|
5925
5925
|
"../core/dist/linter.js"(exports) {
|
|
@@ -7443,8 +7443,8 @@ var require_discovery = __commonJS({
|
|
|
7443
7443
|
exports.findPrimaryBrainfile = findPrimaryBrainfile;
|
|
7444
7444
|
exports.findNearestBrainfile = findNearestBrainfile;
|
|
7445
7445
|
exports.watchBrainfiles = watchBrainfiles;
|
|
7446
|
-
var
|
|
7447
|
-
var
|
|
7446
|
+
var fs11 = __importStar(__require("fs"));
|
|
7447
|
+
var path11 = __importStar(__require("path"));
|
|
7448
7448
|
var parser_1 = require_parser();
|
|
7449
7449
|
var query_1 = require_query();
|
|
7450
7450
|
exports.BRAINFILE_PATTERNS = [
|
|
@@ -7480,7 +7480,7 @@ var require_discovery = __commonJS({
|
|
|
7480
7480
|
"vendor"
|
|
7481
7481
|
];
|
|
7482
7482
|
function isBrainfileName(filename) {
|
|
7483
|
-
const name =
|
|
7483
|
+
const name = path11.basename(filename).toLowerCase();
|
|
7484
7484
|
if (name === "brainfile.md" || name === ".brainfile.md" || name === ".bb.md") {
|
|
7485
7485
|
return true;
|
|
7486
7486
|
}
|
|
@@ -7490,11 +7490,11 @@ var require_discovery = __commonJS({
|
|
|
7490
7490
|
return false;
|
|
7491
7491
|
}
|
|
7492
7492
|
function shouldExclude(filePath, excludeDirs) {
|
|
7493
|
-
const parts = filePath.split(
|
|
7493
|
+
const parts = filePath.split(path11.sep);
|
|
7494
7494
|
return parts.some((part) => excludeDirs.includes(part));
|
|
7495
7495
|
}
|
|
7496
7496
|
function extractBrainfileSuffix(filename) {
|
|
7497
|
-
const name =
|
|
7497
|
+
const name = path11.basename(filename).toLowerCase();
|
|
7498
7498
|
if (name.startsWith("brainfile.") && name.endsWith(".md") && name !== "brainfile.md") {
|
|
7499
7499
|
const withoutPrefix = name.slice("brainfile.".length);
|
|
7500
7500
|
const suffix = withoutPrefix.slice(0, -".md".length);
|
|
@@ -7514,10 +7514,10 @@ var require_discovery = __commonJS({
|
|
|
7514
7514
|
}
|
|
7515
7515
|
function parseFileMetadata(absolutePath, relativePath) {
|
|
7516
7516
|
try {
|
|
7517
|
-
const content =
|
|
7517
|
+
const content = fs11.readFileSync(absolutePath, "utf-8");
|
|
7518
7518
|
const board = parser_1.BrainfileParser.parse(content);
|
|
7519
|
-
const stats =
|
|
7520
|
-
const filename =
|
|
7519
|
+
const stats = fs11.statSync(absolutePath);
|
|
7520
|
+
const filename = path11.basename(absolutePath);
|
|
7521
7521
|
const isHidden = filename.startsWith(".");
|
|
7522
7522
|
if (board) {
|
|
7523
7523
|
return {
|
|
@@ -7551,10 +7551,10 @@ var require_discovery = __commonJS({
|
|
|
7551
7551
|
return results;
|
|
7552
7552
|
}
|
|
7553
7553
|
try {
|
|
7554
|
-
const entries =
|
|
7554
|
+
const entries = fs11.readdirSync(dir, { withFileTypes: true });
|
|
7555
7555
|
for (const entry of entries) {
|
|
7556
|
-
const fullPath =
|
|
7557
|
-
const relativePath =
|
|
7556
|
+
const fullPath = path11.join(dir, entry.name);
|
|
7557
|
+
const relativePath = path11.relative(rootDir, fullPath);
|
|
7558
7558
|
if (entry.isDirectory()) {
|
|
7559
7559
|
if (shouldExclude(entry.name, options.excludeDirs)) {
|
|
7560
7560
|
continue;
|
|
@@ -7586,11 +7586,11 @@ var require_discovery = __commonJS({
|
|
|
7586
7586
|
maxDepth: options.maxDepth ?? 10,
|
|
7587
7587
|
excludeDirs: options.excludeDirs ?? [...exports.EXCLUDE_DIRS]
|
|
7588
7588
|
};
|
|
7589
|
-
const absoluteRoot =
|
|
7589
|
+
const absoluteRoot = path11.resolve(rootDir);
|
|
7590
7590
|
const files = findBrainfilesRecursive(absoluteRoot, absoluteRoot, opts);
|
|
7591
7591
|
files.sort((a, b) => {
|
|
7592
|
-
const aDepth = a.relativePath.split(
|
|
7593
|
-
const bDepth = b.relativePath.split(
|
|
7592
|
+
const aDepth = a.relativePath.split(path11.sep).length;
|
|
7593
|
+
const bDepth = b.relativePath.split(path11.sep).length;
|
|
7594
7594
|
if (aDepth !== bDepth) {
|
|
7595
7595
|
return aDepth - bDepth;
|
|
7596
7596
|
}
|
|
@@ -7604,17 +7604,17 @@ var require_discovery = __commonJS({
|
|
|
7604
7604
|
};
|
|
7605
7605
|
}
|
|
7606
7606
|
function findPrimaryBrainfile(rootDir) {
|
|
7607
|
-
const absoluteRoot =
|
|
7608
|
-
const preferred =
|
|
7609
|
-
if (
|
|
7610
|
-
const metadata = parseFileMetadata(preferred,
|
|
7607
|
+
const absoluteRoot = path11.resolve(rootDir);
|
|
7608
|
+
const preferred = path11.join(absoluteRoot, ".brainfile", "brainfile.md");
|
|
7609
|
+
if (fs11.existsSync(preferred)) {
|
|
7610
|
+
const metadata = parseFileMetadata(preferred, path11.join(".brainfile", "brainfile.md"));
|
|
7611
7611
|
if (metadata)
|
|
7612
7612
|
return metadata;
|
|
7613
7613
|
}
|
|
7614
7614
|
const priorityNames = ["brainfile.md", ".brainfile.md", ".bb.md"];
|
|
7615
7615
|
for (const name of priorityNames) {
|
|
7616
|
-
const fullPath =
|
|
7617
|
-
if (
|
|
7616
|
+
const fullPath = path11.join(absoluteRoot, name);
|
|
7617
|
+
if (fs11.existsSync(fullPath)) {
|
|
7618
7618
|
const metadata = parseFileMetadata(fullPath, name);
|
|
7619
7619
|
if (metadata) {
|
|
7620
7620
|
return metadata;
|
|
@@ -7622,11 +7622,11 @@ var require_discovery = __commonJS({
|
|
|
7622
7622
|
}
|
|
7623
7623
|
}
|
|
7624
7624
|
try {
|
|
7625
|
-
const entries =
|
|
7625
|
+
const entries = fs11.readdirSync(absoluteRoot);
|
|
7626
7626
|
for (const entry of entries) {
|
|
7627
7627
|
if (isBrainfileName(entry) && !priorityNames.includes(entry.toLowerCase())) {
|
|
7628
|
-
const fullPath =
|
|
7629
|
-
const stats =
|
|
7628
|
+
const fullPath = path11.join(absoluteRoot, entry);
|
|
7629
|
+
const stats = fs11.statSync(fullPath);
|
|
7630
7630
|
if (stats.isFile()) {
|
|
7631
7631
|
const metadata = parseFileMetadata(fullPath, entry);
|
|
7632
7632
|
if (metadata) {
|
|
@@ -7640,14 +7640,14 @@ var require_discovery = __commonJS({
|
|
|
7640
7640
|
return null;
|
|
7641
7641
|
}
|
|
7642
7642
|
function findNearestBrainfile(startDir) {
|
|
7643
|
-
let currentDir =
|
|
7644
|
-
const root =
|
|
7643
|
+
let currentDir = path11.resolve(startDir || process.cwd());
|
|
7644
|
+
const root = path11.parse(currentDir).root;
|
|
7645
7645
|
while (currentDir !== root) {
|
|
7646
7646
|
const found = findPrimaryBrainfile(currentDir);
|
|
7647
7647
|
if (found) {
|
|
7648
7648
|
return found;
|
|
7649
7649
|
}
|
|
7650
|
-
const parentDir =
|
|
7650
|
+
const parentDir = path11.dirname(currentDir);
|
|
7651
7651
|
if (parentDir === currentDir) {
|
|
7652
7652
|
break;
|
|
7653
7653
|
}
|
|
@@ -7660,11 +7660,11 @@ var require_discovery = __commonJS({
|
|
|
7660
7660
|
return null;
|
|
7661
7661
|
}
|
|
7662
7662
|
function watchBrainfiles(rootDir, callback, onError) {
|
|
7663
|
-
const absoluteRoot =
|
|
7663
|
+
const absoluteRoot = path11.resolve(rootDir);
|
|
7664
7664
|
let watcher = null;
|
|
7665
7665
|
let active = false;
|
|
7666
7666
|
try {
|
|
7667
|
-
const stats =
|
|
7667
|
+
const stats = fs11.statSync(absoluteRoot);
|
|
7668
7668
|
if (!stats.isDirectory()) {
|
|
7669
7669
|
return {
|
|
7670
7670
|
success: false,
|
|
@@ -7693,13 +7693,13 @@ var require_discovery = __commonJS({
|
|
|
7693
7693
|
};
|
|
7694
7694
|
}
|
|
7695
7695
|
try {
|
|
7696
|
-
watcher =
|
|
7696
|
+
watcher = fs11.watch(absoluteRoot, (eventType, filename) => {
|
|
7697
7697
|
if (!active || !filename || !isBrainfileName(filename))
|
|
7698
7698
|
return;
|
|
7699
|
-
const fullPath =
|
|
7699
|
+
const fullPath = path11.join(absoluteRoot, filename);
|
|
7700
7700
|
try {
|
|
7701
7701
|
if (eventType === "rename") {
|
|
7702
|
-
if (
|
|
7702
|
+
if (fs11.existsSync(fullPath)) {
|
|
7703
7703
|
const metadata = parseFileMetadata(fullPath, filename);
|
|
7704
7704
|
if (metadata) {
|
|
7705
7705
|
callback("add", metadata);
|
|
@@ -7815,8 +7815,8 @@ var require_files = __commonJS({
|
|
|
7815
7815
|
exports.ensureDotBrainfileDir = ensureDotBrainfileDir;
|
|
7816
7816
|
exports.ensureDotBrainfileGitignore = ensureDotBrainfileGitignore;
|
|
7817
7817
|
exports.atomicWriteFileSync = atomicWriteFileSync;
|
|
7818
|
-
var
|
|
7819
|
-
var
|
|
7818
|
+
var fs11 = __importStar(__require("fs"));
|
|
7819
|
+
var path11 = __importStar(__require("path"));
|
|
7820
7820
|
exports.DOT_BRAINFILE_DIRNAME = ".brainfile";
|
|
7821
7821
|
exports.BRAINFILE_BASENAME = "brainfile.md";
|
|
7822
7822
|
exports.BRAINFILE_STATE_BASENAME = "state.json";
|
|
@@ -7824,38 +7824,43 @@ var require_files = __commonJS({
|
|
|
7824
7824
|
exports.DOT_BRAINFILE_STATE_DIRNAME = "state";
|
|
7825
7825
|
exports.DOT_BRAINFILE_STATE_IGNORE_ENTRY = "state/";
|
|
7826
7826
|
function toAbsolute(p) {
|
|
7827
|
-
return
|
|
7827
|
+
return path11.isAbsolute(p) ? p : path11.resolve(p);
|
|
7828
7828
|
}
|
|
7829
7829
|
function existsFile2(p) {
|
|
7830
7830
|
try {
|
|
7831
|
-
return
|
|
7831
|
+
return fs11.statSync(p).isFile();
|
|
7832
7832
|
} catch {
|
|
7833
7833
|
return false;
|
|
7834
7834
|
}
|
|
7835
7835
|
}
|
|
7836
|
-
function
|
|
7837
|
-
|
|
7838
|
-
|
|
7836
|
+
function isGitBoundary(dir) {
|
|
7837
|
+
return fs11.existsSync(path11.join(dir, ".git"));
|
|
7838
|
+
}
|
|
7839
|
+
function findBrainfile2(startDir = process.cwd(), options = {}) {
|
|
7840
|
+
let currentDir = path11.resolve(startDir);
|
|
7841
|
+
const root = path11.parse(currentDir).root;
|
|
7839
7842
|
while (true) {
|
|
7840
|
-
const preferred =
|
|
7843
|
+
const preferred = path11.join(currentDir, exports.DOT_BRAINFILE_DIRNAME, exports.BRAINFILE_BASENAME);
|
|
7841
7844
|
if (existsFile2(preferred)) {
|
|
7842
7845
|
return { absolutePath: preferred, projectRoot: currentDir, kind: "dotdir" };
|
|
7843
7846
|
}
|
|
7844
|
-
const legacy =
|
|
7847
|
+
const legacy = path11.join(currentDir, exports.BRAINFILE_BASENAME);
|
|
7845
7848
|
if (existsFile2(legacy)) {
|
|
7846
7849
|
return { absolutePath: legacy, projectRoot: currentDir, kind: "root" };
|
|
7847
7850
|
}
|
|
7848
|
-
const hiddenLegacy =
|
|
7851
|
+
const hiddenLegacy = path11.join(currentDir, ".brainfile.md");
|
|
7849
7852
|
if (existsFile2(hiddenLegacy)) {
|
|
7850
7853
|
return { absolutePath: hiddenLegacy, projectRoot: currentDir, kind: "hidden" };
|
|
7851
7854
|
}
|
|
7852
|
-
const bbLegacy =
|
|
7855
|
+
const bbLegacy = path11.join(currentDir, ".bb.md");
|
|
7853
7856
|
if (existsFile2(bbLegacy)) {
|
|
7854
7857
|
return { absolutePath: bbLegacy, projectRoot: currentDir, kind: "bb" };
|
|
7855
7858
|
}
|
|
7859
|
+
if (options.stopAtGitRoot && isGitBoundary(currentDir))
|
|
7860
|
+
break;
|
|
7856
7861
|
if (currentDir === root)
|
|
7857
7862
|
break;
|
|
7858
|
-
const parent =
|
|
7863
|
+
const parent = path11.dirname(currentDir);
|
|
7859
7864
|
if (parent === currentDir)
|
|
7860
7865
|
break;
|
|
7861
7866
|
currentDir = parent;
|
|
@@ -7863,60 +7868,60 @@ var require_files = __commonJS({
|
|
|
7863
7868
|
return null;
|
|
7864
7869
|
}
|
|
7865
7870
|
function resolveBrainfilePath2(options = {}) {
|
|
7866
|
-
const startDir = options.startDir ?
|
|
7871
|
+
const startDir = options.startDir ? path11.resolve(options.startDir) : process.cwd();
|
|
7867
7872
|
const filePath = options.filePath;
|
|
7868
7873
|
const isDefaultPlaceholder = filePath === void 0 || filePath === exports.BRAINFILE_BASENAME || filePath === `./${exports.BRAINFILE_BASENAME}`;
|
|
7869
7874
|
if (isDefaultPlaceholder) {
|
|
7870
|
-
const found = findBrainfile2(startDir);
|
|
7875
|
+
const found = findBrainfile2(startDir, { stopAtGitRoot: options.stopAtGitRoot });
|
|
7871
7876
|
if (found)
|
|
7872
7877
|
return found.absolutePath;
|
|
7873
7878
|
return toAbsolute(filePath ?? exports.BRAINFILE_BASENAME);
|
|
7874
7879
|
}
|
|
7875
|
-
return toAbsolute(
|
|
7880
|
+
return toAbsolute(path11.isAbsolute(filePath) ? filePath : path11.resolve(startDir, filePath));
|
|
7876
7881
|
}
|
|
7877
7882
|
function getBrainfileStateDir(brainfilePath) {
|
|
7878
7883
|
const abs = toAbsolute(brainfilePath);
|
|
7879
|
-
const dir =
|
|
7880
|
-
if (
|
|
7884
|
+
const dir = path11.dirname(abs);
|
|
7885
|
+
if (path11.basename(dir) === exports.DOT_BRAINFILE_DIRNAME)
|
|
7881
7886
|
return dir;
|
|
7882
|
-
return
|
|
7887
|
+
return path11.join(dir, exports.DOT_BRAINFILE_DIRNAME);
|
|
7883
7888
|
}
|
|
7884
7889
|
function getBrainfileStatePath(brainfilePath) {
|
|
7885
|
-
return
|
|
7890
|
+
return path11.join(getBrainfileStateDir(brainfilePath), exports.BRAINFILE_STATE_BASENAME);
|
|
7886
7891
|
}
|
|
7887
7892
|
function getDotBrainfileGitignorePath(brainfilePath) {
|
|
7888
|
-
return
|
|
7893
|
+
return path11.join(getBrainfileStateDir(brainfilePath), exports.DOT_BRAINFILE_GITIGNORE_BASENAME);
|
|
7889
7894
|
}
|
|
7890
7895
|
function ensureDotBrainfileDir(brainfilePath) {
|
|
7891
7896
|
const dir = getBrainfileStateDir(brainfilePath);
|
|
7892
|
-
|
|
7897
|
+
fs11.mkdirSync(dir, { recursive: true });
|
|
7893
7898
|
return dir;
|
|
7894
7899
|
}
|
|
7895
7900
|
function ensureDotBrainfileGitignore(brainfilePath) {
|
|
7896
7901
|
ensureDotBrainfileDir(brainfilePath);
|
|
7897
7902
|
const gitignorePath = getDotBrainfileGitignorePath(brainfilePath);
|
|
7898
|
-
const existing =
|
|
7903
|
+
const existing = fs11.existsSync(gitignorePath) ? fs11.readFileSync(gitignorePath, "utf-8") : "";
|
|
7899
7904
|
const lines = existing.split(/\r?\n/).filter((line) => line.trim() !== exports.BRAINFILE_STATE_BASENAME);
|
|
7900
7905
|
const hasStateDir = lines.some((line) => line.trim() === exports.DOT_BRAINFILE_STATE_IGNORE_ENTRY);
|
|
7901
7906
|
if (!hasStateDir) {
|
|
7902
7907
|
lines.push(exports.DOT_BRAINFILE_STATE_IGNORE_ENTRY);
|
|
7903
7908
|
}
|
|
7904
7909
|
const filtered = lines.join("\n").trim();
|
|
7905
|
-
|
|
7910
|
+
fs11.writeFileSync(gitignorePath, filtered.length > 0 ? `${filtered}
|
|
7906
7911
|
` : "", "utf-8");
|
|
7907
7912
|
}
|
|
7908
7913
|
function atomicWriteFileSync(filePath, content) {
|
|
7909
7914
|
const abs = toAbsolute(filePath);
|
|
7910
|
-
const dir =
|
|
7911
|
-
|
|
7912
|
-
const tmp =
|
|
7915
|
+
const dir = path11.dirname(abs);
|
|
7916
|
+
fs11.mkdirSync(dir, { recursive: true });
|
|
7917
|
+
const tmp = path11.join(dir, `.${path11.basename(abs)}.tmp-${process.pid}-${Date.now()}`);
|
|
7913
7918
|
try {
|
|
7914
|
-
|
|
7915
|
-
|
|
7919
|
+
fs11.writeFileSync(tmp, content, "utf-8");
|
|
7920
|
+
fs11.renameSync(tmp, abs);
|
|
7916
7921
|
} catch (err) {
|
|
7917
7922
|
try {
|
|
7918
|
-
if (
|
|
7919
|
-
|
|
7923
|
+
if (fs11.existsSync(tmp))
|
|
7924
|
+
fs11.unlinkSync(tmp);
|
|
7920
7925
|
} catch {
|
|
7921
7926
|
}
|
|
7922
7927
|
throw err;
|
|
@@ -8000,8 +8005,8 @@ var require_taskFile = __commonJS({
|
|
|
8000
8005
|
exports.writeTaskFile = writeTaskFile;
|
|
8001
8006
|
exports.readTasksDir = readTasksDir;
|
|
8002
8007
|
exports.taskFileName = taskFileName;
|
|
8003
|
-
var
|
|
8004
|
-
var
|
|
8008
|
+
var fs11 = __importStar(__require("fs"));
|
|
8009
|
+
var path11 = __importStar(__require("path"));
|
|
8005
8010
|
var taskContent_1 = require_taskContent();
|
|
8006
8011
|
var taskContent_2 = require_taskContent();
|
|
8007
8012
|
Object.defineProperty(exports, "parseTaskContent", { enumerable: true, get: function() {
|
|
@@ -8018,15 +8023,15 @@ var require_taskFile = __commonJS({
|
|
|
8018
8023
|
if (trimmed.includes("/") || trimmed.includes("\\") || trimmed.includes("..")) {
|
|
8019
8024
|
return true;
|
|
8020
8025
|
}
|
|
8021
|
-
if (
|
|
8026
|
+
if (path11.isAbsolute(trimmed)) {
|
|
8022
8027
|
return true;
|
|
8023
8028
|
}
|
|
8024
|
-
return
|
|
8029
|
+
return path11.basename(trimmed) !== trimmed;
|
|
8025
8030
|
}
|
|
8026
8031
|
function readTaskFile(filePath) {
|
|
8027
8032
|
let content;
|
|
8028
8033
|
try {
|
|
8029
|
-
content =
|
|
8034
|
+
content = fs11.readFileSync(filePath, "utf-8");
|
|
8030
8035
|
} catch {
|
|
8031
8036
|
return null;
|
|
8032
8037
|
}
|
|
@@ -8037,19 +8042,19 @@ var require_taskFile = __commonJS({
|
|
|
8037
8042
|
return {
|
|
8038
8043
|
task: parsed.task,
|
|
8039
8044
|
body: parsed.body,
|
|
8040
|
-
filePath:
|
|
8045
|
+
filePath: path11.resolve(filePath)
|
|
8041
8046
|
};
|
|
8042
8047
|
}
|
|
8043
8048
|
function writeTaskFile(filePath, task, body = "") {
|
|
8044
|
-
const dir =
|
|
8045
|
-
|
|
8049
|
+
const dir = path11.dirname(filePath);
|
|
8050
|
+
fs11.mkdirSync(dir, { recursive: true });
|
|
8046
8051
|
const content = (0, taskContent_1.serializeTaskContent)(task, body);
|
|
8047
|
-
|
|
8052
|
+
fs11.writeFileSync(filePath, content, "utf-8");
|
|
8048
8053
|
}
|
|
8049
8054
|
function readTasksDir(dirPath) {
|
|
8050
8055
|
let entries;
|
|
8051
8056
|
try {
|
|
8052
|
-
entries =
|
|
8057
|
+
entries = fs11.readdirSync(dirPath, { withFileTypes: true });
|
|
8053
8058
|
} catch {
|
|
8054
8059
|
return [];
|
|
8055
8060
|
}
|
|
@@ -8058,7 +8063,7 @@ var require_taskFile = __commonJS({
|
|
|
8058
8063
|
if (!entry.isFile() || !entry.name.endsWith(".md")) {
|
|
8059
8064
|
continue;
|
|
8060
8065
|
}
|
|
8061
|
-
const filePath =
|
|
8066
|
+
const filePath = path11.join(dirPath, entry.name);
|
|
8062
8067
|
const doc = readTaskFile(filePath);
|
|
8063
8068
|
if (doc) {
|
|
8064
8069
|
docs.push(doc);
|
|
@@ -8125,14 +8130,14 @@ var require_ledger2 = __commonJS({
|
|
|
8125
8130
|
exports.queryLedger = queryLedger;
|
|
8126
8131
|
exports.getFileHistory = getFileHistory;
|
|
8127
8132
|
exports.getTaskContext = getTaskContext;
|
|
8128
|
-
var
|
|
8129
|
-
var
|
|
8133
|
+
var fs11 = __importStar(__require("fs"));
|
|
8134
|
+
var path11 = __importStar(__require("path"));
|
|
8130
8135
|
var taskFile_1 = require_taskFile();
|
|
8131
8136
|
var ledger_1 = require_ledger();
|
|
8132
8137
|
var LEDGER_FILE_NAME = "ledger.jsonl";
|
|
8133
8138
|
var LEGACY_WARNING_TRACKER = /* @__PURE__ */ new Set();
|
|
8134
8139
|
function getLedgerPath(logsDir) {
|
|
8135
|
-
return
|
|
8140
|
+
return path11.join(logsDir, LEDGER_FILE_NAME);
|
|
8136
8141
|
}
|
|
8137
8142
|
function normalizePathValue(value) {
|
|
8138
8143
|
return value.replace(/\\/g, "/").replace(/^\.\//, "").trim();
|
|
@@ -8350,7 +8355,7 @@ var require_ledger2 = __commonJS({
|
|
|
8350
8355
|
return parsed;
|
|
8351
8356
|
}
|
|
8352
8357
|
function shouldWarnLegacyFallback(logsDir) {
|
|
8353
|
-
const key =
|
|
8358
|
+
const key = path11.resolve(logsDir);
|
|
8354
8359
|
if (LEGACY_WARNING_TRACKER.has(key)) {
|
|
8355
8360
|
return false;
|
|
8356
8361
|
}
|
|
@@ -8436,18 +8441,18 @@ var require_ledger2 = __commonJS({
|
|
|
8436
8441
|
return record;
|
|
8437
8442
|
}
|
|
8438
8443
|
function appendLedgerRecord(logsDir, record) {
|
|
8439
|
-
|
|
8444
|
+
fs11.mkdirSync(logsDir, { recursive: true });
|
|
8440
8445
|
const ledgerPath = getLedgerPath(logsDir);
|
|
8441
|
-
|
|
8446
|
+
fs11.appendFileSync(ledgerPath, `${JSON.stringify(record)}
|
|
8442
8447
|
`, "utf-8");
|
|
8443
8448
|
return ledgerPath;
|
|
8444
8449
|
}
|
|
8445
8450
|
function readLedger(logsDir) {
|
|
8446
8451
|
const ledgerPath = getLedgerPath(logsDir);
|
|
8447
|
-
if (!
|
|
8452
|
+
if (!fs11.existsSync(ledgerPath)) {
|
|
8448
8453
|
return readLegacyMarkdownLedger(logsDir);
|
|
8449
8454
|
}
|
|
8450
|
-
const content =
|
|
8455
|
+
const content = fs11.readFileSync(ledgerPath, "utf-8");
|
|
8451
8456
|
const lines = content.split("\n");
|
|
8452
8457
|
const records = [];
|
|
8453
8458
|
for (let index = 0; index < lines.length; index++) {
|
|
@@ -8576,35 +8581,35 @@ var require_workspace = __commonJS({
|
|
|
8576
8581
|
exports.composeBody = composeBody2;
|
|
8577
8582
|
exports.readV2BoardConfig = readV2BoardConfig2;
|
|
8578
8583
|
exports.buildBoardFromV2 = buildBoardFromV22;
|
|
8579
|
-
var
|
|
8580
|
-
var
|
|
8584
|
+
var fs11 = __importStar(__require("fs"));
|
|
8585
|
+
var path11 = __importStar(__require("path"));
|
|
8581
8586
|
var parser_1 = require_parser();
|
|
8582
8587
|
var taskFile_1 = require_taskFile();
|
|
8583
8588
|
function getV2Dirs2(brainfilePath) {
|
|
8584
|
-
const resolvedPath =
|
|
8585
|
-
const dotDir =
|
|
8589
|
+
const resolvedPath = path11.resolve(brainfilePath);
|
|
8590
|
+
const dotDir = path11.dirname(resolvedPath);
|
|
8586
8591
|
return {
|
|
8587
8592
|
dotDir,
|
|
8588
|
-
boardDir:
|
|
8589
|
-
logsDir:
|
|
8593
|
+
boardDir: path11.join(dotDir, "board"),
|
|
8594
|
+
logsDir: path11.join(dotDir, "logs"),
|
|
8590
8595
|
brainfilePath: resolvedPath
|
|
8591
8596
|
};
|
|
8592
8597
|
}
|
|
8593
8598
|
function isV25(brainfilePath) {
|
|
8594
8599
|
const { boardDir } = getV2Dirs2(brainfilePath);
|
|
8595
|
-
return
|
|
8600
|
+
return fs11.existsSync(boardDir);
|
|
8596
8601
|
}
|
|
8597
8602
|
function ensureV2Dirs2(brainfilePath) {
|
|
8598
8603
|
const dirs = getV2Dirs2(brainfilePath);
|
|
8599
|
-
|
|
8600
|
-
|
|
8604
|
+
fs11.mkdirSync(dirs.boardDir, { recursive: true });
|
|
8605
|
+
fs11.mkdirSync(dirs.logsDir, { recursive: true });
|
|
8601
8606
|
return dirs;
|
|
8602
8607
|
}
|
|
8603
8608
|
function getTaskFilePath2(boardDir, taskId) {
|
|
8604
|
-
return
|
|
8609
|
+
return path11.join(boardDir, (0, taskFile_1.taskFileName)(taskId));
|
|
8605
8610
|
}
|
|
8606
8611
|
function getLogFilePath2(logsDir, taskId) {
|
|
8607
|
-
return
|
|
8612
|
+
return path11.join(logsDir, (0, taskFile_1.taskFileName)(taskId));
|
|
8608
8613
|
}
|
|
8609
8614
|
function findV2Task2(dirs, taskId, searchLogs = false) {
|
|
8610
8615
|
const taskPath = getTaskFilePath2(dirs.boardDir, taskId);
|
|
@@ -8665,7 +8670,7 @@ ${log.trim()}`);
|
|
|
8665
8670
|
`;
|
|
8666
8671
|
}
|
|
8667
8672
|
function readV2BoardConfig2(brainfilePath) {
|
|
8668
|
-
const content =
|
|
8673
|
+
const content = fs11.readFileSync(brainfilePath, "utf-8");
|
|
8669
8674
|
const result = parser_1.BrainfileParser.parseWithErrors(content);
|
|
8670
8675
|
if (!result.board) {
|
|
8671
8676
|
throw new Error(`Failed to parse brainfile: ${result.error}`);
|
|
@@ -8760,7 +8765,7 @@ var require_brief = __commonJS({
|
|
|
8760
8765
|
exports.relativeTime = relativeTime;
|
|
8761
8766
|
exports.buildBrief = buildBrief;
|
|
8762
8767
|
exports.isEmptyBrief = isEmptyBrief;
|
|
8763
|
-
var
|
|
8768
|
+
var fs11 = __importStar(__require("fs"));
|
|
8764
8769
|
var taskFile_1 = require_taskFile();
|
|
8765
8770
|
var ledger_1 = require_ledger2();
|
|
8766
8771
|
var workspace_1 = require_workspace();
|
|
@@ -8864,7 +8869,7 @@ var require_brief = __commonJS({
|
|
|
8864
8869
|
}
|
|
8865
8870
|
function boardMtimeMs(brainfilePath) {
|
|
8866
8871
|
try {
|
|
8867
|
-
return
|
|
8872
|
+
return fs11.statSync(brainfilePath).mtimeMs;
|
|
8868
8873
|
} catch {
|
|
8869
8874
|
return null;
|
|
8870
8875
|
}
|
|
@@ -9011,13 +9016,13 @@ var require_brief = __commonJS({
|
|
|
9011
9016
|
const isUpdated = updatedMs !== null && updatedMs > lastBriefAtMs;
|
|
9012
9017
|
if (!isNew && !isUpdated)
|
|
9013
9018
|
continue;
|
|
9014
|
-
const
|
|
9019
|
+
const stamp2 = isNew ? doc.task.createdAt : doc.task.updatedAt;
|
|
9015
9020
|
const verb = isNew ? "created" : "updated";
|
|
9016
9021
|
changeItems.push({
|
|
9017
9022
|
taskId: doc.task.id,
|
|
9018
9023
|
text: doc.task.title,
|
|
9019
|
-
why: `${verb} ${agoPhrase(
|
|
9020
|
-
...
|
|
9024
|
+
why: `${verb} ${agoPhrase(stamp2, generatedAt)} \xB7 ${taskStateWhy(doc.task)}`,
|
|
9025
|
+
...stamp2 ? { at: stamp2 } : {}
|
|
9021
9026
|
});
|
|
9022
9027
|
}
|
|
9023
9028
|
const completionItems = completionsFor(dirs, agentName, lastBriefAtMs).map((record) => ({
|
|
@@ -9097,8 +9102,8 @@ var require_briefState = __commonJS({
|
|
|
9097
9102
|
exports.getBriefStatePath = getBriefStatePath;
|
|
9098
9103
|
exports.readBriefState = readBriefState;
|
|
9099
9104
|
exports.writeBriefState = writeBriefState;
|
|
9100
|
-
var
|
|
9101
|
-
var
|
|
9105
|
+
var fs11 = __importStar(__require("fs"));
|
|
9106
|
+
var path11 = __importStar(__require("path"));
|
|
9102
9107
|
var files_1 = require_files();
|
|
9103
9108
|
exports.BRIEF_STATE_VERSION = 1;
|
|
9104
9109
|
function sanitizeAgentFilename(agentName) {
|
|
@@ -9116,18 +9121,18 @@ var require_briefState = __commonJS({
|
|
|
9116
9121
|
return cleaned;
|
|
9117
9122
|
}
|
|
9118
9123
|
function getBriefStateDir(brainfilePath) {
|
|
9119
|
-
return
|
|
9124
|
+
return path11.join((0, files_1.getBrainfileStateDir)(brainfilePath), files_1.DOT_BRAINFILE_STATE_DIRNAME);
|
|
9120
9125
|
}
|
|
9121
9126
|
function getBriefStatePath(brainfilePath, agentName) {
|
|
9122
|
-
return
|
|
9127
|
+
return path11.join(getBriefStateDir(brainfilePath), `${sanitizeAgentFilename(agentName)}.json`);
|
|
9123
9128
|
}
|
|
9124
9129
|
function readBriefState(brainfilePath, agentName) {
|
|
9125
9130
|
const statePath = getBriefStatePath(brainfilePath, agentName);
|
|
9126
9131
|
const fallback = { version: exports.BRIEF_STATE_VERSION, agent: agentName, lastBriefAt: null };
|
|
9127
|
-
if (!
|
|
9132
|
+
if (!fs11.existsSync(statePath))
|
|
9128
9133
|
return fallback;
|
|
9129
9134
|
try {
|
|
9130
|
-
const parsed = JSON.parse(
|
|
9135
|
+
const parsed = JSON.parse(fs11.readFileSync(statePath, "utf-8"));
|
|
9131
9136
|
const lastBriefAt = typeof parsed.lastBriefAt === "string" ? parsed.lastBriefAt : null;
|
|
9132
9137
|
return {
|
|
9133
9138
|
version: typeof parsed.version === "number" ? parsed.version : exports.BRIEF_STATE_VERSION,
|
|
@@ -9361,8 +9366,8 @@ var require_boardFile = __commonJS({
|
|
|
9361
9366
|
exports.serializeBoardConfig = exports.parseBoardConfig = void 0;
|
|
9362
9367
|
exports.readBoardConfig = readBoardConfig;
|
|
9363
9368
|
exports.writeBoardConfig = writeBoardConfig;
|
|
9364
|
-
var
|
|
9365
|
-
var
|
|
9369
|
+
var fs11 = __importStar(__require("fs"));
|
|
9370
|
+
var path11 = __importStar(__require("path"));
|
|
9366
9371
|
var boardContent_1 = require_boardContent();
|
|
9367
9372
|
var boardContent_2 = require_boardContent();
|
|
9368
9373
|
Object.defineProperty(exports, "parseBoardConfig", { enumerable: true, get: function() {
|
|
@@ -9374,7 +9379,7 @@ var require_boardFile = __commonJS({
|
|
|
9374
9379
|
function readBoardConfig(filePath) {
|
|
9375
9380
|
let content;
|
|
9376
9381
|
try {
|
|
9377
|
-
content =
|
|
9382
|
+
content = fs11.readFileSync(filePath, "utf-8");
|
|
9378
9383
|
} catch {
|
|
9379
9384
|
return null;
|
|
9380
9385
|
}
|
|
@@ -9385,14 +9390,14 @@ var require_boardFile = __commonJS({
|
|
|
9385
9390
|
return {
|
|
9386
9391
|
config: parsed.config,
|
|
9387
9392
|
body: parsed.body,
|
|
9388
|
-
filePath:
|
|
9393
|
+
filePath: path11.resolve(filePath)
|
|
9389
9394
|
};
|
|
9390
9395
|
}
|
|
9391
9396
|
function writeBoardConfig(filePath, config, body = "") {
|
|
9392
|
-
const dir =
|
|
9393
|
-
|
|
9397
|
+
const dir = path11.dirname(filePath);
|
|
9398
|
+
fs11.mkdirSync(dir, { recursive: true });
|
|
9394
9399
|
const content = (0, boardContent_1.serializeBoardConfig)(config, body);
|
|
9395
|
-
|
|
9400
|
+
fs11.writeFileSync(filePath, content, "utf-8");
|
|
9396
9401
|
}
|
|
9397
9402
|
}
|
|
9398
9403
|
});
|
|
@@ -9464,8 +9469,8 @@ var require_taskOperations = __commonJS({
|
|
|
9464
9469
|
exports.activateTaskContract = activateTaskContract;
|
|
9465
9470
|
exports.activateTaskContractsByParent = activateTaskContractsByParent;
|
|
9466
9471
|
exports.getEffectiveState = getEffectiveState;
|
|
9467
|
-
var
|
|
9468
|
-
var
|
|
9472
|
+
var fs11 = __importStar(__require("fs"));
|
|
9473
|
+
var path11 = __importStar(__require("path"));
|
|
9469
9474
|
var boardValidation_1 = require_boardValidation();
|
|
9470
9475
|
var contractSpec_1 = require_contractSpec();
|
|
9471
9476
|
var idGen_1 = require_idGen();
|
|
@@ -9567,21 +9572,21 @@ ${section}
|
|
|
9567
9572
|
return lines.join("\n");
|
|
9568
9573
|
}
|
|
9569
9574
|
function writeTaskFileExclusive(filePath, task, body) {
|
|
9570
|
-
const dir =
|
|
9571
|
-
|
|
9575
|
+
const dir = path11.dirname(filePath);
|
|
9576
|
+
fs11.mkdirSync(dir, { recursive: true });
|
|
9572
9577
|
const content = (0, taskFile_1.serializeTaskContent)(task, body);
|
|
9573
|
-
|
|
9578
|
+
fs11.writeFileSync(filePath, content, { encoding: "utf-8", flag: "wx" });
|
|
9574
9579
|
}
|
|
9575
9580
|
function rollbackLedgerAppend(logsDir, appendedRecord) {
|
|
9576
|
-
const ledgerPath =
|
|
9581
|
+
const ledgerPath = path11.join(logsDir, "ledger.jsonl");
|
|
9577
9582
|
const appendedLine = `${JSON.stringify(appendedRecord)}
|
|
9578
9583
|
`;
|
|
9579
9584
|
const appendedBytes = Buffer.byteLength(appendedLine, "utf-8");
|
|
9580
9585
|
try {
|
|
9581
|
-
const stat =
|
|
9586
|
+
const stat = fs11.statSync(ledgerPath);
|
|
9582
9587
|
const newSize = stat.size - appendedBytes;
|
|
9583
9588
|
if (newSize >= 0) {
|
|
9584
|
-
|
|
9589
|
+
fs11.truncateSync(ledgerPath, newSize);
|
|
9585
9590
|
}
|
|
9586
9591
|
} catch {
|
|
9587
9592
|
}
|
|
@@ -9663,7 +9668,7 @@ ${section}
|
|
|
9663
9668
|
};
|
|
9664
9669
|
let filePath;
|
|
9665
9670
|
try {
|
|
9666
|
-
filePath =
|
|
9671
|
+
filePath = path11.join(boardDir, (0, taskFile_1.taskFileName)(taskId));
|
|
9667
9672
|
} catch (err) {
|
|
9668
9673
|
return { success: false, error: err.message };
|
|
9669
9674
|
}
|
|
@@ -9717,7 +9722,7 @@ ${section}
|
|
|
9717
9722
|
if (!doc) {
|
|
9718
9723
|
return { success: false, error: `Task not found: ${taskId}` };
|
|
9719
9724
|
}
|
|
9720
|
-
let filePath = doc.filePath ??
|
|
9725
|
+
let filePath = doc.filePath ?? path11.join(dirs.boardDir, (0, taskFile_1.taskFileName)(taskId));
|
|
9721
9726
|
if (doc.task.column === resolvedColumn.id) {
|
|
9722
9727
|
return { success: true, task: doc.task, filePath, column: resolvedColumn, noop: true };
|
|
9723
9728
|
}
|
|
@@ -9933,7 +9938,7 @@ ${section}
|
|
|
9933
9938
|
let childStates;
|
|
9934
9939
|
let incompleteChildren;
|
|
9935
9940
|
if (doc.task.type === "epic") {
|
|
9936
|
-
const boardDir =
|
|
9941
|
+
const boardDir = path11.dirname(taskPath);
|
|
9937
9942
|
childStates = resolveEpicChildStates(doc.task, boardDir, logsDir);
|
|
9938
9943
|
const incomplete = childStates.filter((child) => child.status === "active").map((child) => ({ id: child.id, title: child.title }));
|
|
9939
9944
|
if (incomplete.length > 0) {
|
|
@@ -9954,8 +9959,8 @@ ${section}
|
|
|
9954
9959
|
completedAt: now,
|
|
9955
9960
|
updatedAt: now
|
|
9956
9961
|
};
|
|
9957
|
-
const destPath =
|
|
9958
|
-
if (
|
|
9962
|
+
const destPath = path11.join(logsDir, path11.basename(taskPath));
|
|
9963
|
+
if (fs11.existsSync(destPath)) {
|
|
9959
9964
|
return { success: false, error: `Task already exists in logs: ${doc.task.id}` };
|
|
9960
9965
|
}
|
|
9961
9966
|
const completedBody = archivedBody(doc, childStates);
|
|
@@ -9972,14 +9977,14 @@ ${section}
|
|
|
9972
9977
|
return { success: false, error: `Failed to append ledger record: ${err}` };
|
|
9973
9978
|
}
|
|
9974
9979
|
try {
|
|
9975
|
-
|
|
9980
|
+
fs11.mkdirSync(logsDir, { recursive: true });
|
|
9976
9981
|
writeTaskFileExclusive(destPath, completedTask, completedBody);
|
|
9977
9982
|
} catch (err) {
|
|
9978
9983
|
rollbackLedgerAppend(logsDir, record);
|
|
9979
9984
|
return { success: false, error: `Failed to complete task: ${err}` };
|
|
9980
9985
|
}
|
|
9981
9986
|
try {
|
|
9982
|
-
|
|
9987
|
+
fs11.unlinkSync(taskPath);
|
|
9983
9988
|
return {
|
|
9984
9989
|
success: true,
|
|
9985
9990
|
task: completedTask,
|
|
@@ -9988,7 +9993,7 @@ ${section}
|
|
|
9988
9993
|
};
|
|
9989
9994
|
} catch (err) {
|
|
9990
9995
|
try {
|
|
9991
|
-
|
|
9996
|
+
fs11.unlinkSync(destPath);
|
|
9992
9997
|
} catch {
|
|
9993
9998
|
}
|
|
9994
9999
|
rollbackLedgerAppend(logsDir, record);
|
|
@@ -10001,7 +10006,7 @@ ${section}
|
|
|
10001
10006
|
return { success: false, error: `Failed to read task file: ${taskPath}` };
|
|
10002
10007
|
}
|
|
10003
10008
|
try {
|
|
10004
|
-
|
|
10009
|
+
fs11.unlinkSync(taskPath);
|
|
10005
10010
|
return { success: true, task: doc.task };
|
|
10006
10011
|
} catch (err) {
|
|
10007
10012
|
return { success: false, error: `Failed to delete task file: ${err}` };
|
|
@@ -10076,7 +10081,7 @@ ${section}
|
|
|
10076
10081
|
}
|
|
10077
10082
|
function findTask(boardDir, taskId) {
|
|
10078
10083
|
try {
|
|
10079
|
-
const directPath =
|
|
10084
|
+
const directPath = path11.join(boardDir, (0, taskFile_1.taskFileName)(taskId));
|
|
10080
10085
|
const directDoc = (0, taskFile_1.readTaskFile)(directPath);
|
|
10081
10086
|
if (directDoc && directDoc.task.id === taskId) {
|
|
10082
10087
|
return directDoc;
|
|
@@ -10309,7 +10314,7 @@ ${section}
|
|
|
10309
10314
|
if (doc.task.contract?.status !== "draft")
|
|
10310
10315
|
continue;
|
|
10311
10316
|
const task = applyContractActivation(doc.task, (/* @__PURE__ */ new Date()).toISOString());
|
|
10312
|
-
const taskPath = doc.filePath ??
|
|
10317
|
+
const taskPath = doc.filePath ?? path11.join(boardDir, (0, taskFile_1.taskFileName)(task.id));
|
|
10313
10318
|
(0, taskFile_1.writeTaskFile)(taskPath, task, doc.body);
|
|
10314
10319
|
activated.push(task.id);
|
|
10315
10320
|
}
|
|
@@ -10669,10 +10674,10 @@ var require_dist = __commonJS({
|
|
|
10669
10674
|
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports2, p)) __createBinding(exports2, m, p);
|
|
10670
10675
|
};
|
|
10671
10676
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10672
|
-
exports.
|
|
10673
|
-
exports.
|
|
10674
|
-
exports.
|
|
10675
|
-
exports.Brainfile = exports.buildBoardFromV2 = exports.readV2BoardConfig = exports.composeBody = exports.extractLog = exports.extractDescription = exports.findV2Task = exports.getLogFilePath = exports.getTaskFilePath = exports.ensureV2Dirs = exports.isV2 = exports.getV2Dirs = exports.topologicalSort = exports.DependencyCycleError = exports.MissingDependencyError = exports.ledgerRecordToCompletedRef = exports.taskToCompletedRef = exports.isLedgerContractStatus = exports.normalizePathValue = exports.getTaskContext = exports.getFileHistory = exports.queryLedger = exports.readLedger = exports.appendLedgerRecord = exports.buildLedgerRecord = exports.searchTasksRanked = exports.scoreTaskDocument = exports.taskMatchesFilters = exports.parseSearchQuery = void 0;
|
|
10677
|
+
exports.patchTasks = exports.moveTasks = exports.setAllSubtasksCompleted = exports.setSubtasksCompleted = exports.updateSubtask = exports.deleteSubtask = exports.addSubtask = exports.patchTask = exports.restoreTask = exports.archiveTask = exports.updateStatsConfig = exports.updateBoardTitle = exports.toggleSubtask = exports.deleteTask = exports.updateTask = exports.addTask = exports.moveTask = exports.getAllTemplateIds = exports.getTemplateById = exports.processTemplate = exports.generateSubtaskId = exports.generateTaskId = exports.BUILT_IN_TEMPLATES = exports.hashBoardContent = exports.hashBoard = exports.diffBoards = exports.BrainfileLinter = exports.BrainfileValidator = exports.serializeFrontmatter = exports.parseFrontmatter = exports.BrainfileSerializer = exports.loadSchemaHints = exports.parseSchemaHints = exports.inferRenderer = exports.inferType = exports.BrainfileParser = exports.buildContract = exports.parseDeliverableSpec = exports.normalizeToArray = exports.ALLOWED_DELIVERABLE_TYPES = exports.removeTaskContractConstraint = exports.addTaskContractConstraint = exports.removeTaskContractValidationCommand = exports.addTaskContractValidationCommand = exports.removeTaskContractDeliverable = exports.addTaskContractDeliverable = exports.patchTaskContract = exports.setTaskContractStatus = exports.clearTaskContract = exports.setTaskContract = void 0;
|
|
10678
|
+
exports.BRAINFILE_BASENAME = exports.DOT_BRAINFILE_DIRNAME = exports.atomicWriteFileSync = exports.ensureDotBrainfileGitignore = exports.ensureDotBrainfileDir = exports.getDotBrainfileGitignorePath = exports.getBrainfileStatePath = exports.getBrainfileStateDir = exports.resolveBrainfilePath = exports.findBrainfile = exports.EXCLUDE_DIRS = exports.BRAINFILE_GLOBS = exports.BRAINFILE_PATTERNS = exports.extractBrainfileSuffix = exports.isBrainfileName = exports.watchBrainfiles = exports.findNearestBrainfile = exports.findPrimaryBrainfile = exports.discover = exports.getParentTaskId = exports.isValidSubtaskId = exports.isValidTaskId = exports.generateNextSubtaskId = exports.generateSubtaskIdFromIndex = exports.generateNextTaskId = exports.getMaxTaskIdNumber = exports.extractTaskIdNumber = exports.getOverdueTasks = exports.getIncompleteSubtasksSummary = exports.getTasksWithIncompleteSubtasks = exports.isCompletionColumn = exports.findCompletionColumn = exports.columnExists = exports.getTotalTaskCount = exports.getColumnTaskCount = exports.searchTasks = exports.getTasksByAssignee = exports.getTasksByPriority = exports.getTasksByTag = exports.getAllTasks = exports.taskIdExists = exports.findTaskById = exports.findColumnByName = exports.findColumnById = exports.validateColumn = exports.validateType = exports.isTypeCompletable = exports.getBoardTypes = exports.archiveTasks = exports.deleteTasks = void 0;
|
|
10679
|
+
exports.activateTaskContract = exports.attachTaskContract = exports.failTaskContract = exports.completeTaskContract = exports.deliverTaskContract = exports.pickupTaskContract = exports.updateSubtasksInFile = exports.toggleSubtasksInFile = exports.deleteSubtasksFromFile = exports.addSubtasksToFile = exports.searchLogs = exports.searchTaskFiles = exports.findTask = exports.listTasks = exports.appendLog = exports.deleteTaskFile = exports.completeTaskFile = exports.patchTaskFile = exports.moveTaskFileToColumn = exports.moveTaskFile = exports.addTaskFile = exports.generateNextFileTaskId = exports.DEFAULT_CONTRACT_COLUMN_MAP = exports.taskFileName = exports.readTasksDir = exports.writeTaskFile = exports.readTaskFile = exports.serializeTaskContent = exports.parseTaskContent = exports.writeBoardConfig = exports.readBoardConfig = exports.serializeBoardConfig = exports.parseBoardConfig = exports.formatTaskForLinear = exports.formatTaskForGitHub = exports.BRIEF_STATE_VERSION = exports.sanitizeAgentFilename = exports.getBriefStatePath = exports.getBriefStateDir = exports.writeBriefState = exports.readBriefState = exports.relativeTime = exports.parseNotes = exports.parseNoteLine = exports.isEmptyBrief = exports.buildBrief = exports.DOT_BRAINFILE_STATE_IGNORE_ENTRY = exports.DOT_BRAINFILE_STATE_DIRNAME = exports.DOT_BRAINFILE_GITIGNORE_BASENAME = exports.BRAINFILE_STATE_BASENAME = void 0;
|
|
10680
|
+
exports.Brainfile = exports.buildBoardFromV2 = exports.readV2BoardConfig = exports.composeBody = exports.extractLog = exports.extractDescription = exports.findV2Task = exports.getLogFilePath = exports.getTaskFilePath = exports.ensureV2Dirs = exports.isV2 = exports.getV2Dirs = exports.topologicalSort = exports.DependencyCycleError = exports.MissingDependencyError = exports.ledgerRecordToCompletedRef = exports.taskToCompletedRef = exports.isLedgerContractStatus = exports.normalizePathValue = exports.getTaskContext = exports.getFileHistory = exports.queryLedger = exports.readLedger = exports.appendLedgerRecord = exports.buildLedgerRecord = exports.searchTasksRanked = exports.scoreTaskDocument = exports.taskMatchesFilters = exports.parseSearchQuery = exports.getEffectiveState = exports.activateTaskContractsByParent = void 0;
|
|
10676
10681
|
__exportStar(require_types2(), exports);
|
|
10677
10682
|
__exportStar(require_contract(), exports);
|
|
10678
10683
|
var contract_1 = require_contract2();
|
|
@@ -10741,6 +10746,13 @@ var require_dist = __commonJS({
|
|
|
10741
10746
|
Object.defineProperty(exports, "BrainfileSerializer", { enumerable: true, get: function() {
|
|
10742
10747
|
return serializer_1.BrainfileSerializer;
|
|
10743
10748
|
} });
|
|
10749
|
+
var frontmatter_1 = require_frontmatter();
|
|
10750
|
+
Object.defineProperty(exports, "parseFrontmatter", { enumerable: true, get: function() {
|
|
10751
|
+
return frontmatter_1.parseFrontmatter;
|
|
10752
|
+
} });
|
|
10753
|
+
Object.defineProperty(exports, "serializeFrontmatter", { enumerable: true, get: function() {
|
|
10754
|
+
return frontmatter_1.serializeFrontmatter;
|
|
10755
|
+
} });
|
|
10744
10756
|
var validator_1 = require_validator();
|
|
10745
10757
|
Object.defineProperty(exports, "BrainfileValidator", { enumerable: true, get: function() {
|
|
10746
10758
|
return validator_1.BrainfileValidator;
|
|
@@ -11508,8 +11520,8 @@ function renderCliError(error) {
|
|
|
11508
11520
|
|
|
11509
11521
|
// src/utils/brainfile-path.ts
|
|
11510
11522
|
var import_core3 = __toESM(require_dist());
|
|
11511
|
-
import * as
|
|
11512
|
-
import * as
|
|
11523
|
+
import * as fs3 from "fs";
|
|
11524
|
+
import * as path3 from "path";
|
|
11513
11525
|
|
|
11514
11526
|
// src/utils/v2-only.ts
|
|
11515
11527
|
var import_core2 = __toESM(require_dist());
|
|
@@ -11530,78 +11542,473 @@ function assertV2Brainfile(brainfilePath) {
|
|
|
11530
11542
|
}
|
|
11531
11543
|
}
|
|
11532
11544
|
|
|
11545
|
+
// src/utils/board-repo.ts
|
|
11546
|
+
import * as fs2 from "fs";
|
|
11547
|
+
import * as os from "os";
|
|
11548
|
+
import * as path2 from "path";
|
|
11549
|
+
import { spawnSync } from "child_process";
|
|
11550
|
+
var DEFAULT_BOARD_BRANCH = "brainfile";
|
|
11551
|
+
var BOARD_BRANCH_CONFIG_KEY = "brainfile.branch";
|
|
11552
|
+
var BOARD_REF_NAMESPACE = "refs/brainfile";
|
|
11553
|
+
var BOARD_NAME_CONFIG_KEY = "brainfile.boardName";
|
|
11554
|
+
var LEGACY_REMOTE_BRANCH_CONFIG_KEY = "brainfile.remoteBranch";
|
|
11555
|
+
var DEFAULT_BOARD_NAME = "board";
|
|
11556
|
+
function remoteBoardRef(name) {
|
|
11557
|
+
return `${BOARD_REF_NAMESPACE}/${name}`;
|
|
11558
|
+
}
|
|
11559
|
+
function boardTrackingRef(remote, name) {
|
|
11560
|
+
return `${BOARD_REF_NAMESPACE}/remotes/${remote}/${name}`;
|
|
11561
|
+
}
|
|
11562
|
+
var NETWORK_TIMEOUT_MS = 8e3;
|
|
11563
|
+
var NO_PROMPT_ENV = { GIT_TERMINAL_PROMPT: "0", GIT_ASKPASS: "echo", SSH_ASKPASS: "echo" };
|
|
11564
|
+
function gitNetwork(args, cwd) {
|
|
11565
|
+
return git(args, cwd, NO_PROMPT_ENV, NETWORK_TIMEOUT_MS);
|
|
11566
|
+
}
|
|
11567
|
+
var EXCLUDE_ENTRY = ".brainfile/";
|
|
11568
|
+
var EXCLUDE_MARKER = "# brainfile: board worktree (managed by `brainfile`)";
|
|
11569
|
+
var FALLBACK_IDENTITY_NAME = "brainfile";
|
|
11570
|
+
var FALLBACK_IDENTITY_EMAIL = "brainfile@localhost";
|
|
11571
|
+
var MAX_MESSAGE_LENGTH = 72;
|
|
11572
|
+
var BOARD_SUBDIRS = ["board", "logs"];
|
|
11573
|
+
function git(args, cwd, env, timeoutMs) {
|
|
11574
|
+
const result = spawnSync("git", args, {
|
|
11575
|
+
cwd,
|
|
11576
|
+
encoding: "utf-8",
|
|
11577
|
+
env: env ? { ...process.env, ...env } : process.env,
|
|
11578
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
11579
|
+
...timeoutMs ? { timeout: timeoutMs, killSignal: "SIGKILL" } : {}
|
|
11580
|
+
});
|
|
11581
|
+
return {
|
|
11582
|
+
ok: result.status === 0,
|
|
11583
|
+
status: result.status,
|
|
11584
|
+
stdout: (result.stdout ?? "").toString().trim(),
|
|
11585
|
+
stderr: (result.stderr ?? "").toString().trim()
|
|
11586
|
+
};
|
|
11587
|
+
}
|
|
11588
|
+
function gitToplevel(cwd) {
|
|
11589
|
+
const r = git(["rev-parse", "--show-toplevel"], cwd);
|
|
11590
|
+
return r.ok && r.stdout ? path2.resolve(r.stdout) : null;
|
|
11591
|
+
}
|
|
11592
|
+
function gitCommonDir(cwd) {
|
|
11593
|
+
const r = git(["rev-parse", "--git-common-dir"], cwd);
|
|
11594
|
+
if (!r.ok || !r.stdout) return null;
|
|
11595
|
+
return path2.resolve(cwd, r.stdout);
|
|
11596
|
+
}
|
|
11597
|
+
function listWorktrees(cwd) {
|
|
11598
|
+
const r = git(["worktree", "list", "--porcelain"], cwd);
|
|
11599
|
+
if (!r.ok) return [];
|
|
11600
|
+
const entries = [];
|
|
11601
|
+
let current = null;
|
|
11602
|
+
for (const line of r.stdout.split("\n")) {
|
|
11603
|
+
if (line.startsWith("worktree ")) {
|
|
11604
|
+
current = { path: path2.resolve(line.slice("worktree ".length)), bare: false, detached: false };
|
|
11605
|
+
entries.push(current);
|
|
11606
|
+
} else if (!current) {
|
|
11607
|
+
continue;
|
|
11608
|
+
} else if (line.startsWith("HEAD ")) {
|
|
11609
|
+
current.head = line.slice(5);
|
|
11610
|
+
} else if (line.startsWith("branch ")) {
|
|
11611
|
+
current.branch = line.slice(7);
|
|
11612
|
+
} else if (line === "bare") {
|
|
11613
|
+
current.bare = true;
|
|
11614
|
+
} else if (line === "detached") {
|
|
11615
|
+
current.detached = true;
|
|
11616
|
+
}
|
|
11617
|
+
}
|
|
11618
|
+
return entries;
|
|
11619
|
+
}
|
|
11620
|
+
function boardBranch(cwd) {
|
|
11621
|
+
const r = git(["config", "--get", BOARD_BRANCH_CONFIG_KEY], cwd);
|
|
11622
|
+
return r.ok && r.stdout ? r.stdout : DEFAULT_BOARD_BRANCH;
|
|
11623
|
+
}
|
|
11624
|
+
function boardRepoKind(dotDir) {
|
|
11625
|
+
try {
|
|
11626
|
+
const st = fs2.statSync(path2.join(dotDir, ".git"));
|
|
11627
|
+
if (st.isFile()) return "linked";
|
|
11628
|
+
if (st.isDirectory()) return "standalone";
|
|
11629
|
+
} catch {
|
|
11630
|
+
}
|
|
11631
|
+
return null;
|
|
11632
|
+
}
|
|
11633
|
+
function isTrackedBoard(dotDir) {
|
|
11634
|
+
return boardRepoKind(dotDir) !== null;
|
|
11635
|
+
}
|
|
11636
|
+
function findTrackedBoardDir(cwd) {
|
|
11637
|
+
const worktrees = listWorktrees(cwd);
|
|
11638
|
+
if (worktrees.length === 0) return null;
|
|
11639
|
+
const wanted = `refs/heads/${boardBranch(cwd)}`;
|
|
11640
|
+
const hit = worktrees.find((w) => w.branch === wanted);
|
|
11641
|
+
if (!hit) return null;
|
|
11642
|
+
return fs2.existsSync(path2.join(hit.path, "brainfile.md")) ? hit.path : null;
|
|
11643
|
+
}
|
|
11644
|
+
var PROBE_MISS_TTL_MS = 10 * 6e4;
|
|
11645
|
+
function probeMissFile(cwd) {
|
|
11646
|
+
const common = gitCommonDir(cwd);
|
|
11647
|
+
return common ? path2.join(common, "brainfile-probe.json") : null;
|
|
11648
|
+
}
|
|
11649
|
+
function recentProbeMiss(cwd, remote) {
|
|
11650
|
+
const file = probeMissFile(cwd);
|
|
11651
|
+
if (!file) return false;
|
|
11652
|
+
try {
|
|
11653
|
+
const miss = JSON.parse(fs2.readFileSync(file, "utf-8"));
|
|
11654
|
+
return miss.remote === remote && typeof miss.at === "number" && Date.now() - miss.at < PROBE_MISS_TTL_MS;
|
|
11655
|
+
} catch {
|
|
11656
|
+
return false;
|
|
11657
|
+
}
|
|
11658
|
+
}
|
|
11659
|
+
function recordProbeMiss(cwd, remote) {
|
|
11660
|
+
const file = probeMissFile(cwd);
|
|
11661
|
+
if (!file) return;
|
|
11662
|
+
try {
|
|
11663
|
+
fs2.writeFileSync(file, JSON.stringify({ remote, at: Date.now() }), "utf-8");
|
|
11664
|
+
} catch {
|
|
11665
|
+
}
|
|
11666
|
+
}
|
|
11667
|
+
function clearProbeMiss(cwd) {
|
|
11668
|
+
const file = probeMissFile(cwd);
|
|
11669
|
+
if (file) fs2.rmSync(file, { force: true });
|
|
11670
|
+
}
|
|
11671
|
+
function listRemoteBoards(cwd, remote) {
|
|
11672
|
+
const r = gitNetwork(["ls-remote", "--quiet", remote, `${BOARD_REF_NAMESPACE}/*`], cwd);
|
|
11673
|
+
if (!r.ok) return null;
|
|
11674
|
+
const names = [];
|
|
11675
|
+
for (const line of r.stdout.split("\n")) {
|
|
11676
|
+
const ref = line.split(" ")[1] ?? "";
|
|
11677
|
+
const name = ref.slice(BOARD_REF_NAMESPACE.length + 1);
|
|
11678
|
+
if (ref.startsWith(`${BOARD_REF_NAMESPACE}/`) && name && !name.includes("/")) names.push(name);
|
|
11679
|
+
}
|
|
11680
|
+
return names;
|
|
11681
|
+
}
|
|
11682
|
+
function findBoardStart(cwd, branch, options) {
|
|
11683
|
+
if (git(["rev-parse", "--verify", "--quiet", `refs/heads/${branch}`], cwd).ok) return { ref: branch, remote: null };
|
|
11684
|
+
const configuredRemote = git(["config", "--get", "brainfile.remote"], cwd).stdout || null;
|
|
11685
|
+
const remote = configuredRemote ?? (git(["remote", "get-url", "origin"], cwd).ok ? "origin" : null);
|
|
11686
|
+
if (!remote) return null;
|
|
11687
|
+
const configuredName = git(["config", "--get", BOARD_NAME_CONFIG_KEY], cwd).stdout || git(["config", "--get", LEGACY_REMOTE_BRANCH_CONFIG_KEY], cwd).stdout || null;
|
|
11688
|
+
const knownName = configuredName ?? DEFAULT_BOARD_NAME;
|
|
11689
|
+
const known = boardTrackingRef(remote, knownName);
|
|
11690
|
+
if (git(["rev-parse", "--verify", "--quiet", known], cwd).ok) return { ref: known, remote, name: knownName };
|
|
11691
|
+
if (options.fresh || !recentProbeMiss(cwd, remote)) {
|
|
11692
|
+
const names = listRemoteBoards(cwd, remote);
|
|
11693
|
+
const name = names === null ? null : configuredName ? names.includes(configuredName) ? configuredName : null : names.includes(DEFAULT_BOARD_NAME) ? DEFAULT_BOARD_NAME : names.length === 1 ? names[0] : null;
|
|
11694
|
+
if (name) {
|
|
11695
|
+
const tracking = boardTrackingRef(remote, name);
|
|
11696
|
+
if (gitNetwork(["fetch", "--quiet", "--no-tags", remote, `+${remoteBoardRef(name)}:${tracking}`], cwd).ok) {
|
|
11697
|
+
clearProbeMiss(cwd);
|
|
11698
|
+
return { ref: tracking, remote, name };
|
|
11699
|
+
}
|
|
11700
|
+
}
|
|
11701
|
+
}
|
|
11702
|
+
const legacyBranch = configuredName ?? branch;
|
|
11703
|
+
const legacy = `refs/remotes/${remote}/${legacyBranch}`;
|
|
11704
|
+
if (!git(["rev-parse", "--verify", "--quiet", legacy], cwd).ok && configuredRemote && (options.fresh || !recentProbeMiss(cwd, remote))) {
|
|
11705
|
+
gitNetwork(["fetch", "--quiet", "--no-tags", remote, `+refs/heads/${legacyBranch}:${legacy}`], cwd);
|
|
11706
|
+
}
|
|
11707
|
+
if (git(["rev-parse", "--verify", "--quiet", legacy], cwd).ok) return { ref: legacy, remote };
|
|
11708
|
+
recordProbeMiss(cwd, remote);
|
|
11709
|
+
return null;
|
|
11710
|
+
}
|
|
11711
|
+
function materializeBoardWorktree(cwd, options = {}) {
|
|
11712
|
+
const branch = boardBranch(cwd);
|
|
11713
|
+
const worktrees = listWorktrees(cwd);
|
|
11714
|
+
if (worktrees.length === 0) return null;
|
|
11715
|
+
if (worktrees.some((w) => w.branch === `refs/heads/${branch}`)) return null;
|
|
11716
|
+
const main = worktrees[0];
|
|
11717
|
+
let target;
|
|
11718
|
+
if (main.bare) {
|
|
11719
|
+
const common = gitCommonDir(cwd);
|
|
11720
|
+
if (!common) return null;
|
|
11721
|
+
target = path2.join(common, "brainfile");
|
|
11722
|
+
} else {
|
|
11723
|
+
target = path2.join(main.path, ".brainfile");
|
|
11724
|
+
}
|
|
11725
|
+
if (fs2.existsSync(target)) return null;
|
|
11726
|
+
const start = findBoardStart(cwd, branch, options);
|
|
11727
|
+
if (!start) return null;
|
|
11728
|
+
const r = start.remote ? git(["worktree", "add", "--quiet", "--no-track", "-b", branch, target, start.ref], cwd) : git(["worktree", "add", "--quiet", target, branch], cwd);
|
|
11729
|
+
if (!r.ok) return null;
|
|
11730
|
+
ensureExcludeEntry(cwd);
|
|
11731
|
+
for (const sub of BOARD_SUBDIRS) fs2.mkdirSync(path2.join(target, sub), { recursive: true });
|
|
11732
|
+
if (start.remote) {
|
|
11733
|
+
if (!git(["config", "--get", "brainfile.remote"], cwd).stdout) git(["config", "brainfile.remote", start.remote], cwd);
|
|
11734
|
+
if (start.name && start.name !== DEFAULT_BOARD_NAME && !git(["config", "--get", BOARD_NAME_CONFIG_KEY], cwd).stdout) {
|
|
11735
|
+
git(["config", BOARD_NAME_CONFIG_KEY, start.name], cwd);
|
|
11736
|
+
}
|
|
11737
|
+
}
|
|
11738
|
+
const relative3 = path2.relative(process.cwd(), target) || ".";
|
|
11739
|
+
process.stderr.write(
|
|
11740
|
+
start.remote ? `Checked out the shared board from ${start.remote} into ${relative3}/
|
|
11741
|
+
` : `Checked out the board from the '${branch}' branch into ${relative3}/
|
|
11742
|
+
`
|
|
11743
|
+
);
|
|
11744
|
+
return target;
|
|
11745
|
+
}
|
|
11746
|
+
function ensureExcludeEntry(cwd) {
|
|
11747
|
+
const common = gitCommonDir(cwd);
|
|
11748
|
+
if (!common) return false;
|
|
11749
|
+
const file = path2.join(common, "info", "exclude");
|
|
11750
|
+
const existing = fs2.existsSync(file) ? fs2.readFileSync(file, "utf-8") : "";
|
|
11751
|
+
const present = existing.split(/\r?\n/).some((line) => line.trim() === EXCLUDE_ENTRY || line.trim() === ".brainfile");
|
|
11752
|
+
if (present) return false;
|
|
11753
|
+
fs2.mkdirSync(path2.dirname(file), { recursive: true });
|
|
11754
|
+
const separator = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
|
|
11755
|
+
fs2.appendFileSync(file, `${separator}${EXCLUDE_MARKER}
|
|
11756
|
+
${EXCLUDE_ENTRY}
|
|
11757
|
+
`, "utf-8");
|
|
11758
|
+
return true;
|
|
11759
|
+
}
|
|
11760
|
+
function removeExcludeEntry(cwd) {
|
|
11761
|
+
const common = gitCommonDir(cwd);
|
|
11762
|
+
if (!common) return false;
|
|
11763
|
+
const file = path2.join(common, "info", "exclude");
|
|
11764
|
+
if (!fs2.existsSync(file)) return false;
|
|
11765
|
+
const lines = fs2.readFileSync(file, "utf-8").split(/\r?\n/);
|
|
11766
|
+
const at = lines.indexOf(EXCLUDE_MARKER);
|
|
11767
|
+
if (at < 0) return false;
|
|
11768
|
+
const removeCount = lines[at + 1]?.trim() === EXCLUDE_ENTRY ? 2 : 1;
|
|
11769
|
+
lines.splice(at, removeCount);
|
|
11770
|
+
fs2.writeFileSync(file, lines.join("\n"), "utf-8");
|
|
11771
|
+
return true;
|
|
11772
|
+
}
|
|
11773
|
+
function createOrphanBoardWorktree(repoCwd, dotDir, branch) {
|
|
11774
|
+
const r = git(["worktree", "add", "--quiet", "--orphan", "-b", branch, dotDir], repoCwd);
|
|
11775
|
+
if (!r.ok) {
|
|
11776
|
+
throw new Error(
|
|
11777
|
+
`Could not create the board worktree on branch '${branch}' (git >= 2.42 is required for --orphan): ${r.stderr}`
|
|
11778
|
+
);
|
|
11779
|
+
}
|
|
11780
|
+
}
|
|
11781
|
+
function initStandaloneBoardRepo(dotDir) {
|
|
11782
|
+
const parent = path2.dirname(dotDir);
|
|
11783
|
+
let r = git(["init", "--quiet", "-b", "main", dotDir], parent);
|
|
11784
|
+
if (!r.ok) r = git(["init", "--quiet", dotDir], parent);
|
|
11785
|
+
if (!r.ok) throw new Error(`Could not initialize a git repository in ${dotDir}: ${r.stderr}`);
|
|
11786
|
+
}
|
|
11787
|
+
function boardIsDirty(dotDir) {
|
|
11788
|
+
const r = git(["status", "--porcelain"], dotDir);
|
|
11789
|
+
return r.ok && r.stdout.length > 0;
|
|
11790
|
+
}
|
|
11791
|
+
function unmergedFiles(dotDir) {
|
|
11792
|
+
const r = git(["diff", "--name-only", "--diff-filter=U"], dotDir);
|
|
11793
|
+
return r.ok ? r.stdout.split("\n").map((l) => l.trim()).filter(Boolean) : [];
|
|
11794
|
+
}
|
|
11795
|
+
function boardDirtyFiles(dotDir) {
|
|
11796
|
+
const r = git(["status", "--porcelain"], dotDir);
|
|
11797
|
+
if (!r.ok || !r.stdout) return [];
|
|
11798
|
+
return r.stdout.split("\n").map((line) => line.replace(/^[ MADRCU?!]{1,2}\s+/, "").replace(/^.* -> /, "").trim()).filter(Boolean);
|
|
11799
|
+
}
|
|
11800
|
+
function identityArgs(dotDir) {
|
|
11801
|
+
const args = [];
|
|
11802
|
+
if (!git(["config", "--get", "user.name"], dotDir).stdout) args.push("-c", `user.name=${FALLBACK_IDENTITY_NAME}`);
|
|
11803
|
+
if (!git(["config", "--get", "user.email"], dotDir).stdout) args.push("-c", `user.email=${FALLBACK_IDENTITY_EMAIL}`);
|
|
11804
|
+
return args;
|
|
11805
|
+
}
|
|
11806
|
+
var commitListeners = [];
|
|
11807
|
+
function onBoardCommitted(listener) {
|
|
11808
|
+
if (!commitListeners.includes(listener)) commitListeners.push(listener);
|
|
11809
|
+
}
|
|
11810
|
+
function notifyCommitted(dotDir) {
|
|
11811
|
+
for (const listener of commitListeners) {
|
|
11812
|
+
try {
|
|
11813
|
+
listener(dotDir);
|
|
11814
|
+
} catch {
|
|
11815
|
+
}
|
|
11816
|
+
}
|
|
11817
|
+
}
|
|
11818
|
+
function ensureBoardDirs(dotDir) {
|
|
11819
|
+
let created = false;
|
|
11820
|
+
for (const sub of BOARD_SUBDIRS) {
|
|
11821
|
+
const dir = path2.join(dotDir, sub);
|
|
11822
|
+
try {
|
|
11823
|
+
fs2.mkdirSync(dir, { recursive: true });
|
|
11824
|
+
const keep = path2.join(dir, ".gitkeep");
|
|
11825
|
+
if (!fs2.existsSync(keep)) {
|
|
11826
|
+
fs2.writeFileSync(keep, "", "utf-8");
|
|
11827
|
+
created = true;
|
|
11828
|
+
}
|
|
11829
|
+
} catch {
|
|
11830
|
+
}
|
|
11831
|
+
}
|
|
11832
|
+
return created;
|
|
11833
|
+
}
|
|
11834
|
+
function commitBoard(dotDir, options) {
|
|
11835
|
+
if (!isTrackedBoard(dotDir)) return false;
|
|
11836
|
+
ensureBoardDirs(dotDir);
|
|
11837
|
+
if (unmergedFiles(dotDir).length > 0) return false;
|
|
11838
|
+
if (!git(["add", "-A"], dotDir).ok) return false;
|
|
11839
|
+
if (!boardIsDirty(dotDir)) return false;
|
|
11840
|
+
const agent = options.agent?.replace(/[[\]<>\r\n]/g, "").trim();
|
|
11841
|
+
const suffix = agent ? ` [${agent}]` : "";
|
|
11842
|
+
const message = oneLine(options.message, suffix);
|
|
11843
|
+
const r = git(
|
|
11844
|
+
[...identityArgs(dotDir), "-c", "commit.gpgsign=false", "commit", "--quiet", "--no-verify", "-m", message],
|
|
11845
|
+
dotDir
|
|
11846
|
+
);
|
|
11847
|
+
if (r.ok) notifyCommitted(dotDir);
|
|
11848
|
+
return r.ok;
|
|
11849
|
+
}
|
|
11850
|
+
function commitHandEdits(dotDir) {
|
|
11851
|
+
if (!isTrackedBoard(dotDir)) return false;
|
|
11852
|
+
const keepOnly = (f) => f.endsWith(".gitkeep") || BOARD_SUBDIRS.some((sub) => f === `${sub}/`);
|
|
11853
|
+
if (ensureBoardDirs(dotDir) && boardDirtyFiles(dotDir).every(keepOnly)) {
|
|
11854
|
+
commitBoard(dotDir, { message: "chore: keep board/ and logs/ in git" });
|
|
11855
|
+
}
|
|
11856
|
+
const files = boardDirtyFiles(dotDir);
|
|
11857
|
+
if (files.length === 0) return false;
|
|
11858
|
+
const summary = files.length <= 3 ? files.join(", ") : `${files.length} files`;
|
|
11859
|
+
return commitBoard(dotDir, { message: `edit: ${summary}` });
|
|
11860
|
+
}
|
|
11861
|
+
function homeBoardDir() {
|
|
11862
|
+
return path2.join(os.homedir(), ".brainfile");
|
|
11863
|
+
}
|
|
11864
|
+
function isGlobalBoardRequested(argv = process.argv) {
|
|
11865
|
+
if (process.env.BRAINFILE_GLOBAL === "1") return true;
|
|
11866
|
+
return argv.includes("-g") || argv.includes("--global");
|
|
11867
|
+
}
|
|
11868
|
+
function oneLine(message, suffix = "") {
|
|
11869
|
+
const flat = message.replace(/\s+/g, " ").trim();
|
|
11870
|
+
const room = Math.max(1, MAX_MESSAGE_LENGTH - suffix.length);
|
|
11871
|
+
return `${flat.length > room ? `${flat.slice(0, room - 1)}\u2026` : flat}${suffix}`;
|
|
11872
|
+
}
|
|
11873
|
+
function describeCommandForCommit(command) {
|
|
11874
|
+
const names = [];
|
|
11875
|
+
let cursor = command;
|
|
11876
|
+
while (cursor && cursor.parent) {
|
|
11877
|
+
names.unshift(cursor.name());
|
|
11878
|
+
cursor = cursor.parent;
|
|
11879
|
+
}
|
|
11880
|
+
const opts = command.opts();
|
|
11881
|
+
const idValue = opts.task ?? opts.taskId;
|
|
11882
|
+
const id = Array.isArray(idValue) ? idValue.join(",") : typeof idValue === "string" ? idValue : "";
|
|
11883
|
+
const positional = command.args.filter((a) => typeof a === "string" && a.length > 0);
|
|
11884
|
+
const detail = [opts.title, opts.column, positional.join(" ")].find((v) => typeof v === "string" && v.length > 0) ?? "";
|
|
11885
|
+
const head = `${names.join(" ")}${id ? ` ${id}` : ""}`;
|
|
11886
|
+
return oneLine(detail ? `${head}: ${detail}` : head);
|
|
11887
|
+
}
|
|
11888
|
+
var pendingCommits = /* @__PURE__ */ new Map();
|
|
11889
|
+
var flushTimer = null;
|
|
11890
|
+
var exitHookInstalled = false;
|
|
11891
|
+
function scheduleBoardCommit(brainfilePath, message, delayMs = 2e3) {
|
|
11892
|
+
const dotDir = path2.dirname(path2.resolve(brainfilePath));
|
|
11893
|
+
if (!isTrackedBoard(dotDir)) return;
|
|
11894
|
+
const entry = pendingCommits.get(dotDir) ?? { messages: [] };
|
|
11895
|
+
entry.messages.push(message);
|
|
11896
|
+
pendingCommits.set(dotDir, entry);
|
|
11897
|
+
if (flushTimer) clearTimeout(flushTimer);
|
|
11898
|
+
flushTimer = setTimeout(flushBoardCommits, delayMs);
|
|
11899
|
+
flushTimer.unref?.();
|
|
11900
|
+
if (!exitHookInstalled) {
|
|
11901
|
+
exitHookInstalled = true;
|
|
11902
|
+
process.once("exit", flushBoardCommits);
|
|
11903
|
+
}
|
|
11904
|
+
}
|
|
11905
|
+
function flushBoardCommits() {
|
|
11906
|
+
if (flushTimer) {
|
|
11907
|
+
clearTimeout(flushTimer);
|
|
11908
|
+
flushTimer = null;
|
|
11909
|
+
}
|
|
11910
|
+
for (const [dotDir, entry] of pendingCommits) {
|
|
11911
|
+
const [first, ...rest] = entry.messages;
|
|
11912
|
+
const message = rest.length === 0 ? first : `${first} (+${rest.length} more)`;
|
|
11913
|
+
commitBoard(dotDir, { message });
|
|
11914
|
+
}
|
|
11915
|
+
pendingCommits.clear();
|
|
11916
|
+
}
|
|
11917
|
+
|
|
11533
11918
|
// src/utils/brainfile-path.ts
|
|
11534
11919
|
function isMigrationCommand() {
|
|
11535
11920
|
return process.argv.includes("migrate");
|
|
11536
11921
|
}
|
|
11537
11922
|
function migrationHintForPath2(brainfilePath) {
|
|
11538
|
-
const absolute =
|
|
11539
|
-
const dir =
|
|
11923
|
+
const absolute = path3.resolve(brainfilePath);
|
|
11924
|
+
const dir = path3.basename(path3.dirname(absolute)) === ".brainfile" ? path3.dirname(path3.dirname(absolute)) : path3.dirname(absolute);
|
|
11540
11925
|
return `Run: brainfile migrate --dir ${dir}`;
|
|
11541
11926
|
}
|
|
11542
11927
|
function rejectLegacyRuntimePath(resolvedPath) {
|
|
11543
|
-
if (!isMigrationCommand() &&
|
|
11928
|
+
if (!isMigrationCommand() && fs3.existsSync(resolvedPath) && !(0, import_core3.isV2)(resolvedPath)) {
|
|
11544
11929
|
throw new CLIError(V1_UNSUPPORTED_MESSAGE, void 0, migrationHintForPath2(resolvedPath));
|
|
11545
11930
|
}
|
|
11546
11931
|
return resolvedPath;
|
|
11547
11932
|
}
|
|
11933
|
+
function isPlaceholder(filePath) {
|
|
11934
|
+
return filePath === void 0 || filePath === import_core3.BRAINFILE_BASENAME || filePath === `./${import_core3.BRAINFILE_BASENAME}`;
|
|
11935
|
+
}
|
|
11548
11936
|
function resolveCliBrainfilePath(filePath) {
|
|
11937
|
+
if (isPlaceholder(filePath) && isGlobalBoardRequested()) {
|
|
11938
|
+
return rejectLegacyRuntimePath(path3.join(homeBoardDir(), import_core3.BRAINFILE_BASENAME));
|
|
11939
|
+
}
|
|
11549
11940
|
if (filePath) {
|
|
11550
|
-
const resolved =
|
|
11941
|
+
const resolved = path3.resolve(process.cwd(), filePath);
|
|
11551
11942
|
try {
|
|
11552
|
-
if (
|
|
11943
|
+
if (fs3.statSync(resolved).isDirectory()) {
|
|
11553
11944
|
const found = (0, import_core3.findBrainfile)(resolved);
|
|
11554
11945
|
if (found && found.projectRoot === resolved) {
|
|
11555
11946
|
return rejectLegacyRuntimePath(found.absolutePath);
|
|
11556
11947
|
}
|
|
11557
|
-
const dotDir =
|
|
11948
|
+
const dotDir = path3.join(resolved, ".brainfile", "brainfile.md");
|
|
11558
11949
|
return rejectLegacyRuntimePath(dotDir);
|
|
11559
11950
|
}
|
|
11560
11951
|
} catch {
|
|
11561
11952
|
}
|
|
11562
11953
|
}
|
|
11563
|
-
|
|
11954
|
+
const cwd = process.cwd();
|
|
11955
|
+
if (isPlaceholder(filePath)) {
|
|
11956
|
+
const found = (0, import_core3.findBrainfile)(cwd, { stopAtGitRoot: true });
|
|
11957
|
+
if (found) return rejectLegacyRuntimePath(found.absolutePath);
|
|
11958
|
+
const tracked = findTrackedBoardDir(cwd) ?? materializeBoardWorktree(cwd);
|
|
11959
|
+
if (tracked) return rejectLegacyRuntimePath(path3.join(tracked, import_core3.BRAINFILE_BASENAME));
|
|
11960
|
+
}
|
|
11961
|
+
return rejectLegacyRuntimePath((0, import_core3.resolveBrainfilePath)({ filePath, startDir: cwd, stopAtGitRoot: true }));
|
|
11962
|
+
}
|
|
11963
|
+
function tryResolveBoardDir(filePath) {
|
|
11964
|
+
try {
|
|
11965
|
+
const resolved = resolveCliBrainfilePath(filePath);
|
|
11966
|
+
if (!fs3.existsSync(resolved)) return null;
|
|
11967
|
+
return path3.dirname(resolved);
|
|
11968
|
+
} catch {
|
|
11969
|
+
return null;
|
|
11970
|
+
}
|
|
11564
11971
|
}
|
|
11565
11972
|
|
|
11566
11973
|
// src/utils/v2-detect.ts
|
|
11567
11974
|
var import_core4 = __toESM(require_dist());
|
|
11568
11975
|
var import_core5 = __toESM(require_dist());
|
|
11569
11976
|
var import_core6 = __toESM(require_dist());
|
|
11570
|
-
import * as
|
|
11571
|
-
import * as
|
|
11977
|
+
import * as fs5 from "fs";
|
|
11978
|
+
import * as path5 from "path";
|
|
11572
11979
|
|
|
11573
11980
|
// src/utils/workspace-format.ts
|
|
11574
|
-
import * as
|
|
11575
|
-
import * as
|
|
11981
|
+
import * as fs4 from "fs";
|
|
11982
|
+
import * as path4 from "path";
|
|
11576
11983
|
function existsFile(filePath) {
|
|
11577
11984
|
try {
|
|
11578
|
-
return
|
|
11985
|
+
return fs4.statSync(filePath).isFile();
|
|
11579
11986
|
} catch {
|
|
11580
11987
|
return false;
|
|
11581
11988
|
}
|
|
11582
11989
|
}
|
|
11583
11990
|
function existsDir(dirPath) {
|
|
11584
11991
|
try {
|
|
11585
|
-
return
|
|
11992
|
+
return fs4.statSync(dirPath).isDirectory();
|
|
11586
11993
|
} catch {
|
|
11587
11994
|
return false;
|
|
11588
11995
|
}
|
|
11589
11996
|
}
|
|
11590
11997
|
function workspaceRootFromBrainfilePath(brainfilePath) {
|
|
11591
|
-
const absolute =
|
|
11592
|
-
const dir =
|
|
11593
|
-
return
|
|
11998
|
+
const absolute = path4.resolve(brainfilePath);
|
|
11999
|
+
const dir = path4.dirname(absolute);
|
|
12000
|
+
return path4.basename(dir) === ".brainfile" ? path4.dirname(dir) : dir;
|
|
11594
12001
|
}
|
|
11595
12002
|
function probeWorkspaceFormat(rootDir = process.cwd()) {
|
|
11596
|
-
const resolvedRoot =
|
|
11597
|
-
const dotDir =
|
|
12003
|
+
const resolvedRoot = path4.resolve(rootDir);
|
|
12004
|
+
const dotDir = path4.join(resolvedRoot, ".brainfile");
|
|
11598
12005
|
const paths = {
|
|
11599
12006
|
rootDir: resolvedRoot,
|
|
11600
|
-
rootBrainfilePath:
|
|
12007
|
+
rootBrainfilePath: path4.join(resolvedRoot, "brainfile.md"),
|
|
11601
12008
|
dotDir,
|
|
11602
|
-
dotBrainfilePath:
|
|
11603
|
-
boardDir:
|
|
11604
|
-
logsDir:
|
|
12009
|
+
dotBrainfilePath: path4.join(dotDir, "brainfile.md"),
|
|
12010
|
+
boardDir: path4.join(dotDir, "board"),
|
|
12011
|
+
logsDir: path4.join(dotDir, "logs")
|
|
11605
12012
|
};
|
|
11606
12013
|
const presence = {
|
|
11607
12014
|
rootBrainfile: existsFile(paths.rootBrainfilePath),
|
|
@@ -11635,15 +12042,15 @@ function shouldSuggestMigration(probe) {
|
|
|
11635
12042
|
// src/utils/v2-detect.ts
|
|
11636
12043
|
var v2MigrationHintShown = /* @__PURE__ */ new Set();
|
|
11637
12044
|
function shouldSuggestV2Migration(brainfilePath) {
|
|
11638
|
-
const resolved =
|
|
12045
|
+
const resolved = path5.resolve(brainfilePath);
|
|
11639
12046
|
if (v2MigrationHintShown.has(resolved)) return false;
|
|
11640
12047
|
const probe = probeWorkspaceForBrainfile(resolved);
|
|
11641
12048
|
if (shouldSuggestMigration(probe)) {
|
|
11642
12049
|
return true;
|
|
11643
12050
|
}
|
|
11644
|
-
if (!(0, import_core6.isV2)(resolved) &&
|
|
12051
|
+
if (!(0, import_core6.isV2)(resolved) && fs5.existsSync(resolved)) {
|
|
11645
12052
|
try {
|
|
11646
|
-
const content =
|
|
12053
|
+
const content = fs5.readFileSync(resolved, "utf-8");
|
|
11647
12054
|
const parsed = import_core4.Brainfile.parseWithErrors(content);
|
|
11648
12055
|
if (parsed.board) return true;
|
|
11649
12056
|
} catch {
|
|
@@ -11652,42 +12059,625 @@ function shouldSuggestV2Migration(brainfilePath) {
|
|
|
11652
12059
|
return false;
|
|
11653
12060
|
}
|
|
11654
12061
|
function markV2MigrationHintShown(brainfilePath) {
|
|
11655
|
-
v2MigrationHintShown.add(
|
|
12062
|
+
v2MigrationHintShown.add(path5.resolve(brainfilePath));
|
|
12063
|
+
}
|
|
12064
|
+
|
|
12065
|
+
// src/commands/merge-driver.ts
|
|
12066
|
+
var import_core7 = __toESM(require_dist());
|
|
12067
|
+
import * as fs6 from "fs";
|
|
12068
|
+
import * as os2 from "os";
|
|
12069
|
+
import * as path6 from "path";
|
|
12070
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
12071
|
+
function parseDoc(content) {
|
|
12072
|
+
const parsed = (0, import_core7.parseFrontmatter)(content);
|
|
12073
|
+
if (!parsed) return null;
|
|
12074
|
+
return { data: parsed.data, body: parsed.body };
|
|
12075
|
+
}
|
|
12076
|
+
function same(a, b) {
|
|
12077
|
+
return JSON.stringify(a ?? null) === JSON.stringify(b ?? null);
|
|
12078
|
+
}
|
|
12079
|
+
function stamp(data) {
|
|
12080
|
+
const value = data.updatedAt;
|
|
12081
|
+
return typeof value === "string" ? value : "";
|
|
12082
|
+
}
|
|
12083
|
+
function mergeFields(base, ours, theirs) {
|
|
12084
|
+
const keys = [];
|
|
12085
|
+
for (const key of [...Object.keys(ours), ...Object.keys(theirs), ...Object.keys(base)]) {
|
|
12086
|
+
if (!keys.includes(key)) keys.push(key);
|
|
12087
|
+
}
|
|
12088
|
+
const oursWins = stamp(ours) > stamp(theirs);
|
|
12089
|
+
const out = {};
|
|
12090
|
+
for (const key of keys) {
|
|
12091
|
+
const b = base[key];
|
|
12092
|
+
const o = ours[key];
|
|
12093
|
+
const t = theirs[key];
|
|
12094
|
+
let pick;
|
|
12095
|
+
if (same(o, t)) pick = o;
|
|
12096
|
+
else if (same(b, o)) pick = t;
|
|
12097
|
+
else if (same(b, t)) pick = o;
|
|
12098
|
+
else if (key === "updatedAt") pick = String(o ?? "") > String(t ?? "") ? o : t;
|
|
12099
|
+
else pick = oursWins ? o : t;
|
|
12100
|
+
if (pick !== void 0) out[key] = pick;
|
|
12101
|
+
}
|
|
12102
|
+
return out;
|
|
12103
|
+
}
|
|
12104
|
+
var CONFLICT = /^<{7}[^\n]*\n([\s\S]*?)^={7}\n([\s\S]*?)^>{7}[^\n]*\n?/gm;
|
|
12105
|
+
var LIST_ITEM = /^\s*[-*]\s+/;
|
|
12106
|
+
var LEADING_STAMP = /^\s*[-*]\s+(\d{4}-\d{2}-\d{2}T[0-9:.]+Z?)\b/;
|
|
12107
|
+
function unionLists(ours, theirs) {
|
|
12108
|
+
const a = ours.split("\n").filter((l) => l.trim().length > 0);
|
|
12109
|
+
const b = theirs.split("\n").filter((l) => l.trim().length > 0);
|
|
12110
|
+
if (![...a, ...b].every((l) => LIST_ITEM.test(l))) return null;
|
|
12111
|
+
const merged = [...a];
|
|
12112
|
+
for (const line of b) if (!merged.includes(line)) merged.push(line);
|
|
12113
|
+
if (merged.every((l) => LEADING_STAMP.test(l))) {
|
|
12114
|
+
merged.sort((x, y) => {
|
|
12115
|
+
const sx = LEADING_STAMP.exec(x)?.[1] ?? "";
|
|
12116
|
+
const sy = LEADING_STAMP.exec(y)?.[1] ?? "";
|
|
12117
|
+
return sx < sy ? -1 : sx > sy ? 1 : 0;
|
|
12118
|
+
});
|
|
12119
|
+
}
|
|
12120
|
+
return `${merged.join("\n")}
|
|
12121
|
+
`;
|
|
12122
|
+
}
|
|
12123
|
+
function mergeBodies(base, ours, theirs) {
|
|
12124
|
+
if (ours === theirs) return { content: ours, clean: true };
|
|
12125
|
+
if (base === ours) return { content: theirs, clean: true };
|
|
12126
|
+
if (base === theirs) return { content: ours, clean: true };
|
|
12127
|
+
const dir = fs6.mkdtempSync(path6.join(os2.tmpdir(), "brainfile-merge-"));
|
|
12128
|
+
try {
|
|
12129
|
+
const files = { base: path6.join(dir, "base"), ours: path6.join(dir, "ours"), theirs: path6.join(dir, "theirs") };
|
|
12130
|
+
fs6.writeFileSync(files.base, base);
|
|
12131
|
+
fs6.writeFileSync(files.ours, ours);
|
|
12132
|
+
fs6.writeFileSync(files.theirs, theirs);
|
|
12133
|
+
const r = spawnSync2("git", ["merge-file", "-p", "-L", "ours", "-L", "base", "-L", "theirs", files.ours, files.base, files.theirs], {
|
|
12134
|
+
encoding: "utf-8",
|
|
12135
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
12136
|
+
});
|
|
12137
|
+
if (r.status === null || r.status < 0) {
|
|
12138
|
+
return { content: `<<<<<<< ours
|
|
12139
|
+
${ours}=======
|
|
12140
|
+
${theirs}>>>>>>> theirs
|
|
12141
|
+
`, clean: false };
|
|
12142
|
+
}
|
|
12143
|
+
if (r.status === 0) return { content: r.stdout, clean: true };
|
|
12144
|
+
let clean = true;
|
|
12145
|
+
const content = r.stdout.replace(CONFLICT, (whole, oursHunk, theirsHunk) => {
|
|
12146
|
+
const union = unionLists(oursHunk, theirsHunk);
|
|
12147
|
+
if (union === null) {
|
|
12148
|
+
clean = false;
|
|
12149
|
+
return whole;
|
|
12150
|
+
}
|
|
12151
|
+
return union;
|
|
12152
|
+
});
|
|
12153
|
+
return { content, clean };
|
|
12154
|
+
} finally {
|
|
12155
|
+
fs6.rmSync(dir, { recursive: true, force: true });
|
|
12156
|
+
}
|
|
12157
|
+
}
|
|
12158
|
+
function mergeBoardDocuments(base, ours, theirs) {
|
|
12159
|
+
const b = parseDoc(base);
|
|
12160
|
+
const o = parseDoc(ours);
|
|
12161
|
+
const t = parseDoc(theirs);
|
|
12162
|
+
if (!o || !t) return mergeBodies(base, ours, theirs);
|
|
12163
|
+
const data = mergeFields(b?.data ?? {}, o.data, t.data);
|
|
12164
|
+
const body = mergeBodies(b?.body ?? "", o.body, t.body);
|
|
12165
|
+
return { content: (0, import_core7.serializeFrontmatter)(data, body.content), clean: body.clean };
|
|
12166
|
+
}
|
|
12167
|
+
function summarizeFieldChanges(before, after) {
|
|
12168
|
+
const b = parseDoc(before)?.data ?? {};
|
|
12169
|
+
const a = parseDoc(after)?.data ?? {};
|
|
12170
|
+
const parts = [];
|
|
12171
|
+
for (const key of Object.keys(a)) {
|
|
12172
|
+
if (key === "updatedAt" || same(b[key], a[key])) continue;
|
|
12173
|
+
const render = (v) => typeof v === "string" ? v : JSON.stringify(v);
|
|
12174
|
+
parts.push(`${key}: ${b[key] === void 0 ? "\u2205" : render(b[key])} \u2192 ${render(a[key])}`);
|
|
12175
|
+
}
|
|
12176
|
+
return parts.join("; ");
|
|
12177
|
+
}
|
|
12178
|
+
function mergeDriverCommand(options) {
|
|
12179
|
+
const read = (p) => fs6.existsSync(p) ? fs6.readFileSync(p, "utf-8") : "";
|
|
12180
|
+
if (!options.ours || !options.theirs) return 2;
|
|
12181
|
+
const outcome = mergeBoardDocuments(read(options.base), read(options.ours), read(options.theirs));
|
|
12182
|
+
fs6.writeFileSync(options.ours, outcome.content, "utf-8");
|
|
12183
|
+
return outcome.clean ? 0 : 1;
|
|
12184
|
+
}
|
|
12185
|
+
|
|
12186
|
+
// src/utils/board-sync.ts
|
|
12187
|
+
import * as fs7 from "fs";
|
|
12188
|
+
import * as os3 from "os";
|
|
12189
|
+
import * as path7 from "path";
|
|
12190
|
+
var REMOTE_CONFIG_KEY = "brainfile.remote";
|
|
12191
|
+
var LEGACY_CHECKED_CONFIG_KEY = "brainfile.legacyBranchChecked";
|
|
12192
|
+
var AUTOSYNC_CONFIG_KEY = "brainfile.autosync";
|
|
12193
|
+
var URL_REMOTE_NAME = "board";
|
|
12194
|
+
var MERGE_DRIVER_NAME = "brainfile";
|
|
12195
|
+
var DEFAULT_MERGE_DRIVER_COMMAND = "brainfile merge-driver %O %A %B";
|
|
12196
|
+
var SYNC_STATE_FILE = path7.join("state", "sync.json");
|
|
12197
|
+
var BOARD_GITATTRIBUTES = [
|
|
12198
|
+
"logs/ledger.jsonl merge=union",
|
|
12199
|
+
"board/*.md merge=brainfile",
|
|
12200
|
+
"logs/*.md merge=brainfile",
|
|
12201
|
+
"brainfile.md merge=brainfile"
|
|
12202
|
+
];
|
|
12203
|
+
function configGet(dotDir, key) {
|
|
12204
|
+
const r = git(["config", "--get", key], dotDir);
|
|
12205
|
+
return r.ok && r.stdout ? r.stdout : null;
|
|
12206
|
+
}
|
|
12207
|
+
function configSet(dotDir, key, value) {
|
|
12208
|
+
return git(["config", key, value], dotDir).ok;
|
|
12209
|
+
}
|
|
12210
|
+
function boardRemote(dotDir) {
|
|
12211
|
+
return configGet(dotDir, REMOTE_CONFIG_KEY);
|
|
12212
|
+
}
|
|
12213
|
+
function boardRemoteName(dotDir) {
|
|
12214
|
+
const configured = configGet(dotDir, BOARD_NAME_CONFIG_KEY) ?? configGet(dotDir, LEGACY_REMOTE_BRANCH_CONFIG_KEY);
|
|
12215
|
+
if (configured) return configured;
|
|
12216
|
+
return defaultName(dotDir, DEFAULT_BOARD_NAME);
|
|
12217
|
+
}
|
|
12218
|
+
function defaultName(dotDir, inRepo) {
|
|
12219
|
+
if (path7.resolve(dotDir) === path7.resolve(homeBoardDir())) return "home";
|
|
12220
|
+
if (boardRepoKind(dotDir) === "standalone" && path7.basename(path7.resolve(dotDir)) === ".brainfile") {
|
|
12221
|
+
return path7.basename(path7.dirname(path7.resolve(dotDir))) || inRepo;
|
|
12222
|
+
}
|
|
12223
|
+
return inRepo;
|
|
12224
|
+
}
|
|
12225
|
+
function boardRemoteRef(dotDir) {
|
|
12226
|
+
return remoteBoardRef(boardRemoteName(dotDir));
|
|
12227
|
+
}
|
|
12228
|
+
function legacyRemoteBranch(dotDir) {
|
|
12229
|
+
return configGet(dotDir, LEGACY_REMOTE_BRANCH_CONFIG_KEY) ?? defaultName(dotDir, boardBranch(dotDir));
|
|
12230
|
+
}
|
|
12231
|
+
function boardAutosync(dotDir) {
|
|
12232
|
+
const value = configGet(dotDir, AUTOSYNC_CONFIG_KEY);
|
|
12233
|
+
if (value === "off" || value === "push" || value === "full") return value;
|
|
12234
|
+
return boardRemote(dotDir) ? "push" : "off";
|
|
12235
|
+
}
|
|
12236
|
+
function setBoardAutosync(dotDir, mode) {
|
|
12237
|
+
return configSet(dotDir, AUTOSYNC_CONFIG_KEY, mode);
|
|
12238
|
+
}
|
|
12239
|
+
function looksLikeRemoteUrl(target) {
|
|
12240
|
+
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(target)) return true;
|
|
12241
|
+
if (/^[^/\s]+@[^/\s]+:/.test(target)) return true;
|
|
12242
|
+
if (target.startsWith("/") || target.startsWith(".") || target.startsWith("~")) return true;
|
|
12243
|
+
if (target.includes("/") && fs7.existsSync(target)) return true;
|
|
12244
|
+
return false;
|
|
12245
|
+
}
|
|
12246
|
+
function setBoardRemote(cwd, target, boardName) {
|
|
12247
|
+
const trimmed = target.trim();
|
|
12248
|
+
if (!trimmed) throw new Error("remote is required");
|
|
12249
|
+
let remote;
|
|
12250
|
+
if (looksLikeRemoteUrl(trimmed)) {
|
|
12251
|
+
remote = URL_REMOTE_NAME;
|
|
12252
|
+
const url = trimmed.startsWith("~/") ? path7.join(os3.homedir(), trimmed.slice(2)) : trimmed;
|
|
12253
|
+
const exists = git(["remote", "get-url", remote], cwd).ok;
|
|
12254
|
+
const r = exists ? git(["remote", "set-url", remote, url], cwd) : git(["remote", "add", remote, url], cwd);
|
|
12255
|
+
if (!r.ok) throw new Error(r.stderr || `could not register remote '${remote}'`);
|
|
12256
|
+
} else {
|
|
12257
|
+
remote = trimmed;
|
|
12258
|
+
if (!git(["remote", "get-url", remote], cwd).ok) {
|
|
12259
|
+
const known = git(["remote"], cwd).stdout.split("\n").filter(Boolean);
|
|
12260
|
+
const hint = known.length ? ` Known remotes: ${known.join(", ")}.` : "";
|
|
12261
|
+
throw new Error(`'${remote}' is not a git remote here.${hint} Pass a URL to add one.`);
|
|
12262
|
+
}
|
|
12263
|
+
}
|
|
12264
|
+
if (boardName !== void 0 && !/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(boardName)) {
|
|
12265
|
+
throw new Error(`'${boardName}' is not a usable board name: use letters, digits, '.', '_' or '-'.`);
|
|
12266
|
+
}
|
|
12267
|
+
configSet(cwd, REMOTE_CONFIG_KEY, remote);
|
|
12268
|
+
if (boardName) configSet(cwd, BOARD_NAME_CONFIG_KEY, boardName);
|
|
12269
|
+
registerMergeDriver(cwd);
|
|
12270
|
+
return {
|
|
12271
|
+
remote,
|
|
12272
|
+
url: git(["remote", "get-url", remote], cwd).stdout || null,
|
|
12273
|
+
remoteRef: boardRemoteRef(cwd)
|
|
12274
|
+
};
|
|
12275
|
+
}
|
|
12276
|
+
function registerMergeDriver(cwd, command) {
|
|
12277
|
+
const current = configGet(cwd, `merge.${MERGE_DRIVER_NAME}.driver`);
|
|
12278
|
+
if (command === void 0) {
|
|
12279
|
+
if (current) return false;
|
|
12280
|
+
command = DEFAULT_MERGE_DRIVER_COMMAND;
|
|
12281
|
+
}
|
|
12282
|
+
if (current === command) return false;
|
|
12283
|
+
configSet(cwd, `merge.${MERGE_DRIVER_NAME}.name`, "brainfile board merge (field-level, last writer wins)");
|
|
12284
|
+
return configSet(cwd, `merge.${MERGE_DRIVER_NAME}.driver`, command);
|
|
12285
|
+
}
|
|
12286
|
+
function ensureBoardAttributes(dotDir) {
|
|
12287
|
+
const file = path7.join(dotDir, ".gitattributes");
|
|
12288
|
+
let existing = "";
|
|
12289
|
+
try {
|
|
12290
|
+
existing = fs7.readFileSync(file, "utf-8");
|
|
12291
|
+
} catch {
|
|
12292
|
+
}
|
|
12293
|
+
const lines = existing.split("\n").map((l) => l.trim());
|
|
12294
|
+
const missing = BOARD_GITATTRIBUTES.filter((l) => !lines.includes(l));
|
|
12295
|
+
if (missing.length === 0) return false;
|
|
12296
|
+
const prefix = existing.length > 0 && !existing.endsWith("\n") ? `${existing}
|
|
12297
|
+
` : existing;
|
|
12298
|
+
fs7.writeFileSync(file, `${prefix}${missing.join("\n")}
|
|
12299
|
+
`, "utf-8");
|
|
12300
|
+
return true;
|
|
12301
|
+
}
|
|
12302
|
+
function readSyncState(dotDir) {
|
|
12303
|
+
try {
|
|
12304
|
+
const parsed = JSON.parse(fs7.readFileSync(path7.join(dotDir, SYNC_STATE_FILE), "utf-8"));
|
|
12305
|
+
return typeof parsed?.at === "string" ? parsed : null;
|
|
12306
|
+
} catch {
|
|
12307
|
+
return null;
|
|
12308
|
+
}
|
|
12309
|
+
}
|
|
12310
|
+
function writeSyncState(dotDir, state) {
|
|
12311
|
+
try {
|
|
12312
|
+
fs7.mkdirSync(path7.join(dotDir, "state"), { recursive: true });
|
|
12313
|
+
fs7.writeFileSync(path7.join(dotDir, SYNC_STATE_FILE), `${JSON.stringify(state, null, 2)}
|
|
12314
|
+
`, "utf-8");
|
|
12315
|
+
} catch {
|
|
12316
|
+
}
|
|
12317
|
+
}
|
|
12318
|
+
function firstLine(text) {
|
|
12319
|
+
return text.split("\n").map((l) => l.trim()).find(Boolean) ?? "";
|
|
12320
|
+
}
|
|
12321
|
+
function isMissingRemoteRef(stderr) {
|
|
12322
|
+
return /couldn't find remote ref|Remote branch .* not found|invalid refspec|no such ref/i.test(stderr);
|
|
12323
|
+
}
|
|
12324
|
+
function commitCount(dotDir, range) {
|
|
12325
|
+
const r = git(["rev-list", "--count", range], dotDir);
|
|
12326
|
+
return r.ok ? Number.parseInt(r.stdout, 10) || 0 : 0;
|
|
12327
|
+
}
|
|
12328
|
+
function identityArgs2(dotDir) {
|
|
12329
|
+
const args = ["-c", "commit.gpgsign=false"];
|
|
12330
|
+
if (!git(["config", "--get", "user.email"], dotDir).ok) {
|
|
12331
|
+
args.push("-c", "user.name=brainfile", "-c", "user.email=brainfile@localhost");
|
|
12332
|
+
}
|
|
12333
|
+
return args;
|
|
12334
|
+
}
|
|
12335
|
+
function resolveCompleteVersusEdit(dotDir) {
|
|
12336
|
+
const status = git(["status", "--porcelain", "-z"], dotDir);
|
|
12337
|
+
if (!status.ok) return [];
|
|
12338
|
+
const resolved = [];
|
|
12339
|
+
for (const entry of status.stdout.split("\0")) {
|
|
12340
|
+
if (entry.length < 4) continue;
|
|
12341
|
+
const xy = entry.slice(0, 2);
|
|
12342
|
+
const file = entry.slice(3);
|
|
12343
|
+
if (xy !== "DU" && xy !== "UD") continue;
|
|
12344
|
+
if (!file.startsWith("board/")) continue;
|
|
12345
|
+
const editedStage = xy === "DU" ? 3 : 2;
|
|
12346
|
+
const base = git(["show", `:1:${file}`], dotDir);
|
|
12347
|
+
const edited = git(["show", `:${editedStage}:${file}`], dotDir);
|
|
12348
|
+
const archived = path7.join(dotDir, "logs", path7.basename(file));
|
|
12349
|
+
if (edited.ok && fs7.existsSync(archived)) {
|
|
12350
|
+
const summary = summarizeFieldChanges(base.ok ? base.stdout : "", edited.stdout);
|
|
12351
|
+
const entryLine = `- ${(/* @__PURE__ */ new Date()).toISOString()}: [sync] edited elsewhere after completion${summary ? ` (${summary})` : ""}`;
|
|
12352
|
+
let content = fs7.readFileSync(archived, "utf-8");
|
|
12353
|
+
if (/^## Log\s*$/m.test(content)) {
|
|
12354
|
+
content = `${content.replace(/\s+$/, "")}
|
|
12355
|
+
${entryLine}
|
|
12356
|
+
`;
|
|
12357
|
+
} else {
|
|
12358
|
+
content = `${content.replace(/\s+$/, "")}
|
|
12359
|
+
|
|
12360
|
+
## Log
|
|
12361
|
+
${entryLine}
|
|
12362
|
+
`;
|
|
12363
|
+
}
|
|
12364
|
+
fs7.writeFileSync(archived, content, "utf-8");
|
|
12365
|
+
git(["add", "--", path7.relative(dotDir, archived)], dotDir);
|
|
12366
|
+
}
|
|
12367
|
+
if (git(["rm", "--quiet", "--cached", "--", file], dotDir).ok) {
|
|
12368
|
+
try {
|
|
12369
|
+
fs7.rmSync(path7.join(dotDir, file), { force: true });
|
|
12370
|
+
} catch {
|
|
12371
|
+
}
|
|
12372
|
+
resolved.push(file);
|
|
12373
|
+
}
|
|
12374
|
+
}
|
|
12375
|
+
return resolved;
|
|
12376
|
+
}
|
|
12377
|
+
function mergeIncoming(dotDir, incoming, result) {
|
|
12378
|
+
if (git(["merge-base", "--is-ancestor", incoming, "HEAD"], dotDir).ok) return { ok: true, pulled: 0 };
|
|
12379
|
+
const before = git(["rev-parse", "HEAD"], dotDir).stdout;
|
|
12380
|
+
const id = identityArgs2(dotDir);
|
|
12381
|
+
const merge = git([...id, "merge", "--quiet", "--no-edit", incoming], dotDir);
|
|
12382
|
+
if (!merge.ok) {
|
|
12383
|
+
resolveCompleteVersusEdit(dotDir);
|
|
12384
|
+
const remaining = unmergedFiles(dotDir);
|
|
12385
|
+
if (remaining.length > 0) {
|
|
12386
|
+
return {
|
|
12387
|
+
ok: false,
|
|
12388
|
+
result: {
|
|
12389
|
+
...result,
|
|
12390
|
+
ok: false,
|
|
12391
|
+
failure: "conflict",
|
|
12392
|
+
conflicts: remaining,
|
|
12393
|
+
warning: `merge left conflicts in ${remaining.join(", ")} \u2014 resolve them in ${dotDir}, then run sync again`
|
|
12394
|
+
}
|
|
12395
|
+
};
|
|
12396
|
+
}
|
|
12397
|
+
const commit = git([...id, "commit", "--quiet", "--no-edit", "--no-verify"], dotDir);
|
|
12398
|
+
if (!commit.ok) {
|
|
12399
|
+
return { ok: false, result: { ...result, ok: false, failure: "error", warning: `merge commit failed: ${firstLine(commit.stderr)}` } };
|
|
12400
|
+
}
|
|
12401
|
+
}
|
|
12402
|
+
return { ok: true, pulled: commitCount(dotDir, `${before}..${incoming}`) };
|
|
12403
|
+
}
|
|
12404
|
+
function legacyChecked(dotDir, remote) {
|
|
12405
|
+
return configGet(dotDir, LEGACY_CHECKED_CONFIG_KEY) === remote;
|
|
12406
|
+
}
|
|
12407
|
+
function syncBoard(dotDir, options = {}) {
|
|
12408
|
+
const push = options.push ?? true;
|
|
12409
|
+
const result = { ok: true, pulled: 0, pushed: 0, conflicts: [] };
|
|
12410
|
+
if (!isTrackedBoard(dotDir)) return { ...result, skipped: "not-tracked" };
|
|
12411
|
+
const remote = boardRemote(dotDir);
|
|
12412
|
+
if (!remote) return { ...result, skipped: "no-remote" };
|
|
12413
|
+
const name = boardRemoteName(dotDir);
|
|
12414
|
+
const remoteRef = remoteBoardRef(name);
|
|
12415
|
+
const tracking = boardTrackingRef(remote, name);
|
|
12416
|
+
result.remote = remote;
|
|
12417
|
+
result.remoteRef = remoteRef;
|
|
12418
|
+
const checkLegacy = !legacyChecked(dotDir, remote);
|
|
12419
|
+
const pull = (options.pull ?? true) || checkLegacy;
|
|
12420
|
+
const stuck = unmergedFiles(dotDir);
|
|
12421
|
+
if (stuck.length > 0) {
|
|
12422
|
+
return finish(dotDir, { ...result, ok: false, failure: "conflict", conflicts: stuck, warning: `unresolved conflicts: ${stuck.join(", ")}` });
|
|
12423
|
+
}
|
|
12424
|
+
commitHandEdits(dotDir);
|
|
12425
|
+
if (ensureBoardAttributes(dotDir)) commitBoard(dotDir, { message: "chore: merge attributes" });
|
|
12426
|
+
registerMergeDriver(dotDir);
|
|
12427
|
+
let fetched = false;
|
|
12428
|
+
let legacy = null;
|
|
12429
|
+
let legacyAbsent = false;
|
|
12430
|
+
if (pull) {
|
|
12431
|
+
const fetch2 = git(["fetch", "--quiet", "--no-tags", remote, `+${remoteRef}:${tracking}`], dotDir);
|
|
12432
|
+
if (fetch2.ok) {
|
|
12433
|
+
fetched = true;
|
|
12434
|
+
} else if (!isMissingRemoteRef(fetch2.stderr)) {
|
|
12435
|
+
return finish(dotDir, { ...result, ok: false, failure: "offline", warning: `fetch from ${remote} failed: ${firstLine(fetch2.stderr)}` });
|
|
12436
|
+
} else {
|
|
12437
|
+
git(["update-ref", "-d", tracking], dotDir);
|
|
12438
|
+
}
|
|
12439
|
+
if (checkLegacy) {
|
|
12440
|
+
const branch = legacyRemoteBranch(dotDir);
|
|
12441
|
+
const old = git(["fetch", "--quiet", "--no-tags", remote, `refs/heads/${branch}`], dotDir);
|
|
12442
|
+
if (old.ok) {
|
|
12443
|
+
legacy = { branch, sha: git(["rev-parse", "FETCH_HEAD"], dotDir).stdout };
|
|
12444
|
+
} else if (isMissingRemoteRef(old.stderr)) {
|
|
12445
|
+
legacyAbsent = true;
|
|
12446
|
+
}
|
|
12447
|
+
}
|
|
12448
|
+
for (const incoming of [fetched ? tracking : null, legacy?.sha ?? null]) {
|
|
12449
|
+
if (!incoming) continue;
|
|
12450
|
+
const merged = mergeIncoming(dotDir, incoming, result);
|
|
12451
|
+
if (!merged.ok) return finish(dotDir, merged.result);
|
|
12452
|
+
result.pulled += merged.pulled;
|
|
12453
|
+
}
|
|
12454
|
+
}
|
|
12455
|
+
if (push) {
|
|
12456
|
+
const known = git(["rev-parse", "--verify", "--quiet", tracking], dotDir).ok;
|
|
12457
|
+
const ahead = commitCount(dotDir, known ? `${tracking}..HEAD` : "HEAD");
|
|
12458
|
+
if (ahead > 0) {
|
|
12459
|
+
const pushed = git(["push", "--quiet", remote, `HEAD:${remoteRef}`], dotDir);
|
|
12460
|
+
if (!pushed.ok) {
|
|
12461
|
+
const rejected = /rejected|non-fast-forward|fetch first/i.test(pushed.stderr);
|
|
12462
|
+
return finish(dotDir, {
|
|
12463
|
+
...result,
|
|
12464
|
+
ok: false,
|
|
12465
|
+
failure: rejected ? "rejected" : "offline",
|
|
12466
|
+
warning: rejected ? `push to ${remote} rejected (the remote moved) \u2014 run sync again` : `push to ${remote} failed: ${firstLine(pushed.stderr)}`
|
|
12467
|
+
});
|
|
12468
|
+
}
|
|
12469
|
+
git(["update-ref", tracking, "HEAD"], dotDir);
|
|
12470
|
+
result.pushed = ahead;
|
|
12471
|
+
}
|
|
12472
|
+
if (legacy && git(["merge-base", "--is-ancestor", legacy.sha, tracking], dotDir).ok) {
|
|
12473
|
+
const removed = git(
|
|
12474
|
+
["push", "--quiet", `--force-with-lease=refs/heads/${legacy.branch}:${legacy.sha}`, remote, `:refs/heads/${legacy.branch}`],
|
|
12475
|
+
dotDir
|
|
12476
|
+
);
|
|
12477
|
+
if (removed.ok) {
|
|
12478
|
+
git(["update-ref", "-d", `refs/remotes/${remote}/${legacy.branch}`], dotDir);
|
|
12479
|
+
result.movedFromBranch = legacy.branch;
|
|
12480
|
+
configSet(dotDir, LEGACY_CHECKED_CONFIG_KEY, remote);
|
|
12481
|
+
}
|
|
12482
|
+
}
|
|
12483
|
+
}
|
|
12484
|
+
if (legacyAbsent) {
|
|
12485
|
+
git(["update-ref", "-d", `refs/remotes/${remote}/${legacyRemoteBranch(dotDir)}`], dotDir);
|
|
12486
|
+
configSet(dotDir, LEGACY_CHECKED_CONFIG_KEY, remote);
|
|
12487
|
+
}
|
|
12488
|
+
return finish(dotDir, result);
|
|
12489
|
+
}
|
|
12490
|
+
function finish(dotDir, result) {
|
|
12491
|
+
writeSyncState(dotDir, {
|
|
12492
|
+
at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
12493
|
+
remote: result.remote ?? "",
|
|
12494
|
+
remoteRef: result.remoteRef ?? "",
|
|
12495
|
+
ok: result.ok,
|
|
12496
|
+
...result.warning ? { warning: result.warning } : {}
|
|
12497
|
+
});
|
|
12498
|
+
return result;
|
|
12499
|
+
}
|
|
12500
|
+
function secondsSinceSync(dotDir, now = Date.now()) {
|
|
12501
|
+
const state = readSyncState(dotDir);
|
|
12502
|
+
if (!state) return null;
|
|
12503
|
+
const at = Date.parse(state.at);
|
|
12504
|
+
return Number.isNaN(at) ? null : Math.max(0, Math.round((now - at) / 1e3));
|
|
12505
|
+
}
|
|
12506
|
+
function syncStatusLabel(dotDir, now = Date.now()) {
|
|
12507
|
+
if (!isTrackedBoard(dotDir)) return null;
|
|
12508
|
+
if (!boardRemote(dotDir)) return "local only";
|
|
12509
|
+
const state = readSyncState(dotDir);
|
|
12510
|
+
if (!state) return "not synced yet";
|
|
12511
|
+
if (!state.ok) return "not synced \xB7 saved locally";
|
|
12512
|
+
const seconds = secondsSinceSync(dotDir, now) ?? 0;
|
|
12513
|
+
const age = seconds < 60 ? `${seconds}s` : seconds < 3600 ? `${Math.round(seconds / 60)}m` : `${Math.round(seconds / 3600)}h`;
|
|
12514
|
+
return `synced ${age} ago`;
|
|
12515
|
+
}
|
|
12516
|
+
function pendingChanges(dotDir) {
|
|
12517
|
+
const remote = boardRemote(dotDir);
|
|
12518
|
+
if (!remote) return 0;
|
|
12519
|
+
const tracking = boardTrackingRef(remote, boardRemoteName(dotDir));
|
|
12520
|
+
const known = git(["rev-parse", "--verify", "--quiet", tracking], dotDir).ok;
|
|
12521
|
+
return commitCount(dotDir, known ? `${tracking}..HEAD` : "HEAD");
|
|
12522
|
+
}
|
|
12523
|
+
function boardWebUrl(remoteUrl, remoteRef) {
|
|
12524
|
+
if (!remoteUrl) return null;
|
|
12525
|
+
const m = /^(?:https:\/\/(?:[^@/]+@)?github\.com\/|git@github\.com:|ssh:\/\/git@github\.com\/)([^/]+)\/([^/]+?)(?:\.git)?\/?$/.exec(remoteUrl);
|
|
12526
|
+
return m ? `https://github.com/${m[1]}/${m[2]}/commits/${remoteRef}` : null;
|
|
12527
|
+
}
|
|
12528
|
+
var plural = (n, word) => `${n} ${word}${n === 1 ? "" : "s"}`;
|
|
12529
|
+
function syncMessage(result) {
|
|
12530
|
+
if (result.skipped === "not-tracked") {
|
|
12531
|
+
return { tone: "warn", text: "This board is plain files on this machine and is not shared. To share it: brainfile migrate --to-branch" };
|
|
12532
|
+
}
|
|
12533
|
+
if (result.skipped === "no-remote") {
|
|
12534
|
+
return { tone: "warn", text: "This board is only on this machine. To share it: brainfile sync --set-remote origin" };
|
|
12535
|
+
}
|
|
12536
|
+
const remote = result.remote ?? "the remote";
|
|
12537
|
+
if (!result.ok) {
|
|
12538
|
+
switch (result.failure) {
|
|
12539
|
+
case "conflict":
|
|
12540
|
+
return {
|
|
12541
|
+
tone: "warn",
|
|
12542
|
+
text: `Two edits collided in ${result.conflicts.join(", ")}. Open the file in .brainfile/, keep the right version, then run brainfile sync. Nothing else is shared until then.`
|
|
12543
|
+
};
|
|
12544
|
+
case "rejected":
|
|
12545
|
+
return { tone: "warn", text: `Someone shared changes through ${remote} at the same moment. Run brainfile sync again to combine them. Nothing was lost.` };
|
|
12546
|
+
case "offline":
|
|
12547
|
+
return {
|
|
12548
|
+
tone: "warn",
|
|
12549
|
+
text: `Couldn't reach ${remote}. Nothing was lost: your changes are saved on this machine and will be sent on the next sync.`,
|
|
12550
|
+
detail: result.warning
|
|
12551
|
+
};
|
|
12552
|
+
default:
|
|
12553
|
+
return { tone: "warn", text: `Sync with ${remote} stopped. Your changes are saved on this machine.`, detail: result.warning };
|
|
12554
|
+
}
|
|
12555
|
+
}
|
|
12556
|
+
const parts = [];
|
|
12557
|
+
if (result.pulled > 0) parts.push(`received ${plural(result.pulled, "change")}`);
|
|
12558
|
+
if (result.pushed > 0) parts.push(`sent ${plural(result.pushed, "change")}`);
|
|
12559
|
+
return { tone: "ok", text: parts.length ? `Shared through ${remote}: ${parts.join(", ")}.` : `Up to date with ${remote}.` };
|
|
12560
|
+
}
|
|
12561
|
+
|
|
12562
|
+
// src/utils/board-autosync.ts
|
|
12563
|
+
import * as fs8 from "fs";
|
|
12564
|
+
import * as path8 from "path";
|
|
12565
|
+
import { spawn } from "child_process";
|
|
12566
|
+
var AUTOSYNC_DELAY_MS = 5e3;
|
|
12567
|
+
var FETCH_MAX_AGE_SECONDS = 60;
|
|
12568
|
+
var LOCK_FILE = path8.join("state", "autosync.lock");
|
|
12569
|
+
var LOCK_STALE_MS = 6e4;
|
|
12570
|
+
function effectiveAutosync(dotDir) {
|
|
12571
|
+
const override = process.env.BRAINFILE_AUTOSYNC;
|
|
12572
|
+
if (override === "off" || override === "push" || override === "full") return override;
|
|
12573
|
+
return boardAutosync(dotDir);
|
|
12574
|
+
}
|
|
12575
|
+
function autosyncCliPath() {
|
|
12576
|
+
const candidates = [
|
|
12577
|
+
path8.join(__dirname, "cli.js"),
|
|
12578
|
+
path8.join(__dirname, "..", "cli.js"),
|
|
12579
|
+
path8.join(__dirname, "..", "..", "dist", "cli.js")
|
|
12580
|
+
];
|
|
12581
|
+
return candidates.find((p) => fs8.existsSync(p)) ?? null;
|
|
12582
|
+
}
|
|
12583
|
+
function pidAlive(pid) {
|
|
12584
|
+
try {
|
|
12585
|
+
process.kill(pid, 0);
|
|
12586
|
+
return true;
|
|
12587
|
+
} catch {
|
|
12588
|
+
return false;
|
|
12589
|
+
}
|
|
12590
|
+
}
|
|
12591
|
+
function pushPending(dotDir) {
|
|
12592
|
+
const lock = path8.join(dotDir, LOCK_FILE);
|
|
12593
|
+
try {
|
|
12594
|
+
const st = fs8.statSync(lock);
|
|
12595
|
+
if (Date.now() - st.mtimeMs > LOCK_STALE_MS) return false;
|
|
12596
|
+
const pid = Number.parseInt(fs8.readFileSync(lock, "utf-8"), 10);
|
|
12597
|
+
return Number.isFinite(pid) && pidAlive(pid);
|
|
12598
|
+
} catch {
|
|
12599
|
+
return false;
|
|
12600
|
+
}
|
|
12601
|
+
}
|
|
12602
|
+
function clearPushLock(dotDir) {
|
|
12603
|
+
try {
|
|
12604
|
+
fs8.rmSync(path8.join(dotDir, LOCK_FILE), { force: true });
|
|
12605
|
+
} catch {
|
|
12606
|
+
}
|
|
12607
|
+
}
|
|
12608
|
+
function schedulePush(dotDir, delayMs = AUTOSYNC_DELAY_MS) {
|
|
12609
|
+
if (!isTrackedBoard(dotDir)) return false;
|
|
12610
|
+
const mode = effectiveAutosync(dotDir);
|
|
12611
|
+
if (mode === "off" || !boardRemote(dotDir)) return false;
|
|
12612
|
+
if (pushPending(dotDir)) return false;
|
|
12613
|
+
const cli = autosyncCliPath();
|
|
12614
|
+
if (!cli) return false;
|
|
12615
|
+
const delay = process.env.BRAINFILE_AUTOSYNC_DELAY_MS ? Number(process.env.BRAINFILE_AUTOSYNC_DELAY_MS) : delayMs;
|
|
12616
|
+
try {
|
|
12617
|
+
const child = spawn(
|
|
12618
|
+
process.execPath,
|
|
12619
|
+
[cli, "sync", "--push", "--file", path8.join(dotDir, "brainfile.md"), "--wait", String(delay)],
|
|
12620
|
+
{ cwd: dotDir, detached: true, stdio: "ignore", env: { ...process.env, BRAINFILE_AUTOSYNC: "off" } }
|
|
12621
|
+
);
|
|
12622
|
+
child.unref();
|
|
12623
|
+
if (child.pid) {
|
|
12624
|
+
fs8.mkdirSync(path8.join(dotDir, "state"), { recursive: true });
|
|
12625
|
+
fs8.writeFileSync(path8.join(dotDir, LOCK_FILE), String(child.pid), "utf-8");
|
|
12626
|
+
}
|
|
12627
|
+
return true;
|
|
12628
|
+
} catch {
|
|
12629
|
+
return false;
|
|
12630
|
+
}
|
|
12631
|
+
}
|
|
12632
|
+
function fetchBeforeRead(dotDir, maxAgeSeconds = FETCH_MAX_AGE_SECONDS) {
|
|
12633
|
+
if (!isTrackedBoard(dotDir)) return null;
|
|
12634
|
+
if (effectiveAutosync(dotDir) !== "full") return null;
|
|
12635
|
+
const age = secondsSinceSync(dotDir);
|
|
12636
|
+
if (age !== null && age < maxAgeSeconds) return null;
|
|
12637
|
+
return syncBoard(dotDir, { pull: true, push: false });
|
|
12638
|
+
}
|
|
12639
|
+
var installed = false;
|
|
12640
|
+
function installAutosync() {
|
|
12641
|
+
if (installed) return;
|
|
12642
|
+
installed = true;
|
|
12643
|
+
onBoardCommitted((dotDir) => {
|
|
12644
|
+
schedulePush(dotDir);
|
|
12645
|
+
});
|
|
11656
12646
|
}
|
|
11657
12647
|
|
|
11658
12648
|
// src/utils/config.ts
|
|
11659
|
-
import * as
|
|
11660
|
-
import * as
|
|
11661
|
-
import * as
|
|
12649
|
+
import * as fs9 from "fs";
|
|
12650
|
+
import * as path9 from "path";
|
|
12651
|
+
import * as os4 from "os";
|
|
11662
12652
|
function resolveHomeDir() {
|
|
11663
12653
|
const envHome = process.env.XDG_CONFIG_HOME?.trim();
|
|
11664
12654
|
if (envHome) {
|
|
11665
12655
|
return envHome;
|
|
11666
12656
|
}
|
|
11667
12657
|
const home = process.env.HOME?.trim();
|
|
11668
|
-
return home ||
|
|
12658
|
+
return home || os4.homedir();
|
|
11669
12659
|
}
|
|
11670
12660
|
function getConfigDir() {
|
|
11671
12661
|
const xdgConfigHome = process.env.XDG_CONFIG_HOME?.trim();
|
|
11672
12662
|
if (xdgConfigHome) {
|
|
11673
|
-
return
|
|
12663
|
+
return path9.join(xdgConfigHome, "brainfile");
|
|
11674
12664
|
}
|
|
11675
|
-
return
|
|
12665
|
+
return path9.join(resolveHomeDir(), ".config", "brainfile");
|
|
11676
12666
|
}
|
|
11677
12667
|
function getConfigPath() {
|
|
11678
|
-
return
|
|
12668
|
+
return path9.join(getConfigDir(), "config.json");
|
|
11679
12669
|
}
|
|
11680
12670
|
function ensureConfigDir() {
|
|
11681
12671
|
const configDir = getConfigDir();
|
|
11682
|
-
if (!
|
|
11683
|
-
|
|
12672
|
+
if (!fs9.existsSync(configDir)) {
|
|
12673
|
+
fs9.mkdirSync(configDir, { recursive: true, mode: 448 });
|
|
11684
12674
|
}
|
|
11685
12675
|
}
|
|
11686
12676
|
function loadConfig() {
|
|
11687
12677
|
const configFile = getConfigPath();
|
|
11688
12678
|
try {
|
|
11689
|
-
if (
|
|
11690
|
-
const content =
|
|
12679
|
+
if (fs9.existsSync(configFile)) {
|
|
12680
|
+
const content = fs9.readFileSync(configFile, "utf-8");
|
|
11691
12681
|
return JSON.parse(content);
|
|
11692
12682
|
}
|
|
11693
12683
|
} catch (error) {
|
|
@@ -11698,7 +12688,7 @@ function loadConfig() {
|
|
|
11698
12688
|
function saveConfig(config) {
|
|
11699
12689
|
const configFile = getConfigPath();
|
|
11700
12690
|
ensureConfigDir();
|
|
11701
|
-
|
|
12691
|
+
fs9.writeFileSync(configFile, JSON.stringify(config, null, 2), {
|
|
11702
12692
|
encoding: "utf-8",
|
|
11703
12693
|
mode: 384
|
|
11704
12694
|
// Owner read/write only
|
|
@@ -11773,11 +12763,11 @@ function isValidDestination(dest) {
|
|
|
11773
12763
|
}
|
|
11774
12764
|
|
|
11775
12765
|
// src/utils/auth-store.ts
|
|
11776
|
-
import * as
|
|
11777
|
-
import * as
|
|
12766
|
+
import * as fs10 from "fs";
|
|
12767
|
+
import * as path10 from "path";
|
|
11778
12768
|
import { execSync } from "child_process";
|
|
11779
12769
|
var SERVICE_NAME = "brainfile";
|
|
11780
|
-
var AUTH_FILE =
|
|
12770
|
+
var AUTH_FILE = path10.join(getConfigDir(), "auth.json");
|
|
11781
12771
|
var ENV_VAR_MAP = {
|
|
11782
12772
|
github: ["GITHUB_TOKEN", "GH_TOKEN"],
|
|
11783
12773
|
linear: ["LINEAR_API_KEY", "LINEAR_TOKEN"]
|
|
@@ -11947,8 +12937,8 @@ var AuthStore = class {
|
|
|
11947
12937
|
}
|
|
11948
12938
|
loadAuthFile() {
|
|
11949
12939
|
try {
|
|
11950
|
-
if (
|
|
11951
|
-
const content =
|
|
12940
|
+
if (fs10.existsSync(AUTH_FILE)) {
|
|
12941
|
+
const content = fs10.readFileSync(AUTH_FILE, "utf-8");
|
|
11952
12942
|
return JSON.parse(content);
|
|
11953
12943
|
}
|
|
11954
12944
|
} catch {
|
|
@@ -11957,7 +12947,7 @@ var AuthStore = class {
|
|
|
11957
12947
|
}
|
|
11958
12948
|
saveAuthFile(data) {
|
|
11959
12949
|
ensureConfigDir();
|
|
11960
|
-
|
|
12950
|
+
fs10.writeFileSync(AUTH_FILE, JSON.stringify(data, null, 2), {
|
|
11961
12951
|
encoding: "utf-8",
|
|
11962
12952
|
mode: 384
|
|
11963
12953
|
// Owner read/write only
|
|
@@ -12205,7 +13195,7 @@ async function logoutGitHub() {
|
|
|
12205
13195
|
await authStore.clear("github");
|
|
12206
13196
|
}
|
|
12207
13197
|
function sleep(ms) {
|
|
12208
|
-
return new Promise((
|
|
13198
|
+
return new Promise((resolve7) => setTimeout(resolve7, ms));
|
|
12209
13199
|
}
|
|
12210
13200
|
|
|
12211
13201
|
// src/utils/linear-auth.ts
|
|
@@ -12552,12 +13542,51 @@ export {
|
|
|
12552
13542
|
renderCliError,
|
|
12553
13543
|
V1_UNSUPPORTED_MESSAGE,
|
|
12554
13544
|
assertV2Brainfile,
|
|
13545
|
+
git,
|
|
13546
|
+
gitToplevel,
|
|
13547
|
+
listWorktrees,
|
|
13548
|
+
boardBranch,
|
|
13549
|
+
boardRepoKind,
|
|
13550
|
+
isTrackedBoard,
|
|
13551
|
+
findTrackedBoardDir,
|
|
13552
|
+
materializeBoardWorktree,
|
|
13553
|
+
ensureExcludeEntry,
|
|
13554
|
+
removeExcludeEntry,
|
|
13555
|
+
createOrphanBoardWorktree,
|
|
13556
|
+
initStandaloneBoardRepo,
|
|
13557
|
+
boardIsDirty,
|
|
13558
|
+
commitBoard,
|
|
13559
|
+
commitHandEdits,
|
|
13560
|
+
homeBoardDir,
|
|
13561
|
+
isGlobalBoardRequested,
|
|
13562
|
+
describeCommandForCommit,
|
|
13563
|
+
scheduleBoardCommit,
|
|
12555
13564
|
resolveCliBrainfilePath,
|
|
13565
|
+
tryResolveBoardDir,
|
|
12556
13566
|
workspaceRootFromBrainfilePath,
|
|
12557
13567
|
probeWorkspaceFormat,
|
|
12558
13568
|
shouldSuggestV2Migration,
|
|
12559
13569
|
markV2MigrationHintShown,
|
|
12560
13570
|
import_core5 as import_core,
|
|
13571
|
+
mergeDriverCommand,
|
|
13572
|
+
boardRemote,
|
|
13573
|
+
boardRemoteRef,
|
|
13574
|
+
boardAutosync,
|
|
13575
|
+
setBoardAutosync,
|
|
13576
|
+
setBoardRemote,
|
|
13577
|
+
registerMergeDriver,
|
|
13578
|
+
ensureBoardAttributes,
|
|
13579
|
+
readSyncState,
|
|
13580
|
+
syncBoard,
|
|
13581
|
+
secondsSinceSync,
|
|
13582
|
+
syncStatusLabel,
|
|
13583
|
+
pendingChanges,
|
|
13584
|
+
boardWebUrl,
|
|
13585
|
+
syncMessage,
|
|
13586
|
+
effectiveAutosync,
|
|
13587
|
+
clearPushLock,
|
|
13588
|
+
fetchBeforeRead,
|
|
13589
|
+
installAutosync,
|
|
12561
13590
|
getConfigPath,
|
|
12562
13591
|
loadConfig,
|
|
12563
13592
|
saveConfig,
|