brainfile 0.18.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -4
- package/dist/__tests__/helpers/mcp.d.ts +34 -0
- package/dist/__tests__/helpers/mcp.d.ts.map +1 -0
- package/dist/__tests__/helpers/mcp.js +37 -0
- package/dist/__tests__/helpers/mcp.js.map +1 -0
- package/dist/chunk-I5SJHUZK.mjs +52 -0
- package/dist/chunk-YTFLC673.mjs +12089 -0
- package/dist/cli.js +8 -479
- package/dist/cli.mjs +41194 -0
- package/dist/commands/add.d.ts.map +1 -1
- package/dist/commands/add.js +28 -37
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/archive.d.ts +3 -2
- package/dist/commands/archive.d.ts.map +1 -1
- package/dist/commands/archive.js +16 -205
- package/dist/commands/archive.js.map +1 -1
- package/dist/commands/complete.d.ts +0 -4
- package/dist/commands/complete.d.ts.map +1 -1
- package/dist/commands/complete.js +14 -168
- package/dist/commands/complete.js.map +1 -1
- package/dist/commands/contract.d.ts.map +1 -1
- package/dist/commands/contract.js +66 -331
- package/dist/commands/contract.js.map +1 -1
- package/dist/commands/delete.d.ts.map +1 -1
- package/dist/commands/delete.js +13 -55
- package/dist/commands/delete.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +3 -2
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/list.d.ts.map +1 -1
- package/dist/commands/list.js +3 -54
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/log.js +2 -8
- package/dist/commands/log.js.map +1 -1
- package/dist/commands/mcp.d.ts.map +1 -1
- package/dist/commands/mcp.js +22 -17
- package/dist/commands/mcp.js.map +1 -1
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/migrate.js +4 -3
- package/dist/commands/migrate.js.map +1 -1
- package/dist/commands/move.d.ts.map +1 -1
- package/dist/commands/move.js +31 -126
- package/dist/commands/move.js.map +1 -1
- package/dist/commands/patch.d.ts.map +1 -1
- package/dist/commands/patch.js +22 -92
- package/dist/commands/patch.js.map +1 -1
- package/dist/commands/plan.d.ts +61 -0
- package/dist/commands/plan.d.ts.map +1 -0
- package/dist/commands/plan.js +205 -0
- package/dist/commands/plan.js.map +1 -0
- package/dist/commands/search.d.ts.map +1 -1
- package/dist/commands/search.js +20 -146
- package/dist/commands/search.js.map +1 -1
- package/dist/commands/show.d.ts.map +1 -1
- package/dist/commands/show.js +5 -126
- package/dist/commands/show.js.map +1 -1
- package/dist/commands/subtask.d.ts.map +1 -1
- package/dist/commands/subtask.js +19 -135
- package/dist/commands/subtask.js.map +1 -1
- package/dist/commands/types.d.ts +2 -2
- package/dist/commands/types.d.ts.map +1 -1
- package/dist/commands/types.js +8 -30
- package/dist/commands/types.js.map +1 -1
- package/dist/devtools-NE5M3KPM.mjs +92 -0
- package/dist/lib/contractRunner.d.ts.map +1 -1
- package/dist/lib/contractRunner.js +19 -205
- package/dist/lib/contractRunner.js.map +1 -1
- package/dist/mcp/helpers.d.ts +11 -16
- package/dist/mcp/helpers.d.ts.map +1 -1
- package/dist/mcp/helpers.js +9 -46
- package/dist/mcp/helpers.js.map +1 -1
- package/dist/mcp/tools/contract_tool.d.ts +1 -1
- package/dist/mcp/tools/contract_tool.d.ts.map +1 -1
- package/dist/mcp/tools/contract_tool.js +23 -175
- package/dist/mcp/tools/contract_tool.js.map +1 -1
- package/dist/mcp/tools/get_task_tool.d.ts +1 -1
- package/dist/mcp/tools/get_task_tool.d.ts.map +1 -1
- package/dist/mcp/tools/get_task_tool.js +15 -34
- package/dist/mcp/tools/get_task_tool.js.map +1 -1
- package/dist/mcp/tools/list_tasks_tool.d.ts +1 -1
- package/dist/mcp/tools/list_tasks_tool.d.ts.map +1 -1
- package/dist/mcp/tools/list_tasks_tool.js +11 -48
- package/dist/mcp/tools/list_tasks_tool.js.map +1 -1
- package/dist/mcp/tools/search_tool.d.ts +1 -1
- package/dist/mcp/tools/search_tool.d.ts.map +1 -1
- package/dist/mcp/tools/search_tool.js +17 -98
- package/dist/mcp/tools/search_tool.js.map +1 -1
- package/dist/mcp/tools/subtask_tool.d.ts +1 -1
- package/dist/mcp/tools/subtask_tool.d.ts.map +1 -1
- package/dist/mcp/tools/subtask_tool.js +49 -224
- package/dist/mcp/tools/subtask_tool.js.map +1 -1
- package/dist/mcp/tools/task_add_tool.d.ts +1 -1
- package/dist/mcp/tools/task_add_tool.d.ts.map +1 -1
- package/dist/mcp/tools/task_add_tool.js +58 -154
- package/dist/mcp/tools/task_add_tool.js.map +1 -1
- package/dist/mcp/tools/task_complete_tool.d.ts +1 -1
- package/dist/mcp/tools/task_complete_tool.d.ts.map +1 -1
- package/dist/mcp/tools/task_complete_tool.js +28 -124
- package/dist/mcp/tools/task_complete_tool.js.map +1 -1
- package/dist/mcp/tools/task_delete_tool.d.ts +1 -1
- package/dist/mcp/tools/task_delete_tool.d.ts.map +1 -1
- package/dist/mcp/tools/task_delete_tool.js +14 -33
- package/dist/mcp/tools/task_delete_tool.js.map +1 -1
- package/dist/mcp/tools/task_move_tool.d.ts +1 -1
- package/dist/mcp/tools/task_move_tool.d.ts.map +1 -1
- package/dist/mcp/tools/task_move_tool.js +44 -150
- package/dist/mcp/tools/task_move_tool.js.map +1 -1
- package/dist/mcp/tools/task_patch_tool.d.ts +1 -1
- package/dist/mcp/tools/task_patch_tool.d.ts.map +1 -1
- package/dist/mcp/tools/task_patch_tool.js +37 -113
- package/dist/mcp/tools/task_patch_tool.js.map +1 -1
- package/dist/open-ZGSQIGEH.mjs +542 -0
- package/dist/tui-P56H4X7N.mjs +23715 -0
- package/dist/utils/dot-brainfile.d.ts +0 -4
- package/dist/utils/dot-brainfile.d.ts.map +1 -1
- package/dist/utils/dot-brainfile.js +0 -17
- package/dist/utils/dot-brainfile.js.map +1 -1
- package/dist/utils/errorHandler.d.ts +7 -11
- package/dist/utils/errorHandler.d.ts.map +1 -1
- package/dist/utils/errorHandler.js +8 -19
- package/dist/utils/errorHandler.js.map +1 -1
- package/package.json +24 -16
- package/dist/cli.d.ts +0 -3
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js.map +0 -1
- package/dist/commands/tui.d.ts +0 -6
- package/dist/commands/tui.d.ts.map +0 -1
- package/dist/commands/tui.js +0 -79
- package/dist/commands/tui.js.map +0 -1
- package/dist/tui/BrainfileTUI.d.ts +0 -4
- package/dist/tui/BrainfileTUI.d.ts.map +0 -1
- package/dist/tui/BrainfileTUI.js +0 -272
- package/dist/tui/BrainfileTUI.js.map +0 -1
- package/dist/tui/actions.d.ts +0 -84
- package/dist/tui/actions.d.ts.map +0 -1
- package/dist/tui/actions.js +0 -1251
- package/dist/tui/actions.js.map +0 -1
- package/dist/tui/components/ColumnTabs.d.ts +0 -9
- package/dist/tui/components/ColumnTabs.d.ts.map +0 -1
- package/dist/tui/components/ColumnTabs.js +0 -34
- package/dist/tui/components/ColumnTabs.js.map +0 -1
- package/dist/tui/components/Header.d.ts +0 -15
- package/dist/tui/components/Header.d.ts.map +0 -1
- package/dist/tui/components/Header.js +0 -48
- package/dist/tui/components/Header.js.map +0 -1
- package/dist/tui/components/HelpOverlay.d.ts +0 -9
- package/dist/tui/components/HelpOverlay.d.ts.map +0 -1
- package/dist/tui/components/HelpOverlay.js +0 -116
- package/dist/tui/components/HelpOverlay.js.map +0 -1
- package/dist/tui/components/LogsPanel.d.ts +0 -16
- package/dist/tui/components/LogsPanel.d.ts.map +0 -1
- package/dist/tui/components/LogsPanel.js +0 -115
- package/dist/tui/components/LogsPanel.js.map +0 -1
- package/dist/tui/components/MainPanelTabs.d.ts +0 -10
- package/dist/tui/components/MainPanelTabs.d.ts.map +0 -1
- package/dist/tui/components/MainPanelTabs.js +0 -56
- package/dist/tui/components/MainPanelTabs.js.map +0 -1
- package/dist/tui/components/ManualBorder.d.ts +0 -42
- package/dist/tui/components/ManualBorder.d.ts.map +0 -1
- package/dist/tui/components/ManualBorder.js +0 -56
- package/dist/tui/components/ManualBorder.js.map +0 -1
- package/dist/tui/components/Overlays.d.ts +0 -41
- package/dist/tui/components/Overlays.d.ts.map +0 -1
- package/dist/tui/components/Overlays.js +0 -127
- package/dist/tui/components/Overlays.js.map +0 -1
- package/dist/tui/components/ProgressBar.d.ts +0 -8
- package/dist/tui/components/ProgressBar.d.ts.map +0 -1
- package/dist/tui/components/ProgressBar.js +0 -35
- package/dist/tui/components/ProgressBar.js.map +0 -1
- package/dist/tui/components/RulesPanel.d.ts +0 -25
- package/dist/tui/components/RulesPanel.d.ts.map +0 -1
- package/dist/tui/components/RulesPanel.js +0 -139
- package/dist/tui/components/RulesPanel.js.map +0 -1
- package/dist/tui/components/SearchBar.d.ts +0 -8
- package/dist/tui/components/SearchBar.d.ts.map +0 -1
- package/dist/tui/components/SearchBar.js +0 -38
- package/dist/tui/components/SearchBar.js.map +0 -1
- package/dist/tui/components/StackedTaskList.d.ts +0 -27
- package/dist/tui/components/StackedTaskList.d.ts.map +0 -1
- package/dist/tui/components/StackedTaskList.js +0 -120
- package/dist/tui/components/StackedTaskList.js.map +0 -1
- package/dist/tui/components/StatusBar.d.ts +0 -14
- package/dist/tui/components/StatusBar.d.ts.map +0 -1
- package/dist/tui/components/StatusBar.js +0 -42
- package/dist/tui/components/StatusBar.js.map +0 -1
- package/dist/tui/components/TaskCard.d.ts +0 -25
- package/dist/tui/components/TaskCard.d.ts.map +0 -1
- package/dist/tui/components/TaskCard.js +0 -221
- package/dist/tui/components/TaskCard.js.map +0 -1
- package/dist/tui/components/TaskCardMeasure.d.ts +0 -31
- package/dist/tui/components/TaskCardMeasure.d.ts.map +0 -1
- package/dist/tui/components/TaskCardMeasure.js +0 -133
- package/dist/tui/components/TaskCardMeasure.js.map +0 -1
- package/dist/tui/components/TaskDetail.d.ts +0 -13
- package/dist/tui/components/TaskDetail.d.ts.map +0 -1
- package/dist/tui/components/TaskDetail.js +0 -64
- package/dist/tui/components/TaskDetail.js.map +0 -1
- package/dist/tui/components/TaskList.d.ts +0 -12
- package/dist/tui/components/TaskList.d.ts.map +0 -1
- package/dist/tui/components/TaskList.js +0 -75
- package/dist/tui/components/TaskList.js.map +0 -1
- package/dist/tui/components/index.d.ts +0 -31
- package/dist/tui/components/index.d.ts.map +0 -1
- package/dist/tui/components/index.js +0 -43
- package/dist/tui/components/index.js.map +0 -1
- package/dist/tui/hooks/index.d.ts +0 -3
- package/dist/tui/hooks/index.d.ts.map +0 -1
- package/dist/tui/hooks/index.js +0 -8
- package/dist/tui/hooks/index.js.map +0 -1
- package/dist/tui/hooks/useBrainfileLoader.d.ts +0 -5
- package/dist/tui/hooks/useBrainfileLoader.d.ts.map +0 -1
- package/dist/tui/hooks/useBrainfileLoader.js +0 -237
- package/dist/tui/hooks/useBrainfileLoader.js.map +0 -1
- package/dist/tui/hooks/useKeyboardNavigation.d.ts +0 -20
- package/dist/tui/hooks/useKeyboardNavigation.d.ts.map +0 -1
- package/dist/tui/hooks/useKeyboardNavigation.js +0 -806
- package/dist/tui/hooks/useKeyboardNavigation.js.map +0 -1
- package/dist/tui/index.d.ts +0 -10
- package/dist/tui/index.d.ts.map +0 -1
- package/dist/tui/index.js +0 -38
- package/dist/tui/index.js.map +0 -1
- package/dist/tui/theme.d.ts +0 -119
- package/dist/tui/theme.d.ts.map +0 -1
- package/dist/tui/theme.js +0 -138
- package/dist/tui/theme.js.map +0 -1
- package/dist/tui/types.d.ts +0 -53
- package/dist/tui/types.d.ts.map +0 -1
- package/dist/tui/types.js +0 -12
- package/dist/tui/types.js.map +0 -1
- package/dist/tui/utils.d.ts +0 -24
- package/dist/tui/utils.d.ts.map +0 -1
- package/dist/tui/utils.js +0 -161
- package/dist/tui/utils.js.map +0 -1
- package/dist/utils/board-types.d.ts +0 -13
- package/dist/utils/board-types.d.ts.map +0 -1
- package/dist/utils/board-types.js +0 -7
- package/dist/utils/board-types.js.map +0 -1
- package/dist/utils/contractSpec.d.ts +0 -13
- package/dist/utils/contractSpec.d.ts.map +0 -1
- package/dist/utils/contractSpec.js +0 -61
- package/dist/utils/contractSpec.js.map +0 -1
- package/dist/utils/strict-validation.d.ts +0 -8
- package/dist/utils/strict-validation.d.ts.map +0 -1
- package/dist/utils/strict-validation.js +0 -41
- package/dist/utils/strict-validation.js.map +0 -1
package/dist/tui/actions.js
DELETED
|
@@ -1,1251 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
-
exports.moveTaskAction = moveTaskAction;
|
|
37
|
-
exports.deleteTaskAction = deleteTaskAction;
|
|
38
|
-
exports.archiveTaskAction = archiveTaskAction;
|
|
39
|
-
exports.archiveTaskActionAsync = archiveTaskActionAsync;
|
|
40
|
-
exports.patchTaskAction = patchTaskAction;
|
|
41
|
-
exports.cyclePriorityAction = cyclePriorityAction;
|
|
42
|
-
exports.toggleSubtaskAction = toggleSubtaskAction;
|
|
43
|
-
exports.addTaskAction = addTaskAction;
|
|
44
|
-
exports.newTaskInEditor = newTaskInEditor;
|
|
45
|
-
exports.editTaskInEditor = editTaskInEditor;
|
|
46
|
-
exports.copyToClipboard = copyToClipboard;
|
|
47
|
-
exports.addRuleAction = addRuleAction;
|
|
48
|
-
exports.updateRuleAction = updateRuleAction;
|
|
49
|
-
exports.deleteRuleAction = deleteRuleAction;
|
|
50
|
-
exports.loadLogs = loadLogs;
|
|
51
|
-
exports.restoreTaskAction = restoreTaskAction;
|
|
52
|
-
exports.deleteArchivedTaskAction = deleteArchivedTaskAction;
|
|
53
|
-
/**
|
|
54
|
-
* TUI Actions - Wrappers around @brainfile/core operations
|
|
55
|
-
*
|
|
56
|
-
* All actions read/write the brainfile directly. The file watcher
|
|
57
|
-
* in useBrainfileLoader will detect changes and refresh the UI.
|
|
58
|
-
*/
|
|
59
|
-
const fs = __importStar(require("fs"));
|
|
60
|
-
const path = __importStar(require("path"));
|
|
61
|
-
const os = __importStar(require("os"));
|
|
62
|
-
const child_process_1 = require("child_process");
|
|
63
|
-
const core_1 = require("@brainfile/core");
|
|
64
|
-
const config_1 = require("../utils/config");
|
|
65
|
-
const v2_detect_1 = require("../utils/v2-detect");
|
|
66
|
-
const github_auth_1 = require("../utils/github-auth");
|
|
67
|
-
const linear_auth_1 = require("../utils/linear-auth");
|
|
68
|
-
/**
|
|
69
|
-
* Read and parse the brainfile
|
|
70
|
-
*/
|
|
71
|
-
function readBrainfile(filePath) {
|
|
72
|
-
try {
|
|
73
|
-
const content = fs.readFileSync(filePath, 'utf-8');
|
|
74
|
-
const result = core_1.Brainfile.parseWithErrors(content);
|
|
75
|
-
if (!result.board) {
|
|
76
|
-
return { board: null, content, error: result.error || 'Failed to parse brainfile' };
|
|
77
|
-
}
|
|
78
|
-
return { board: result.board, content };
|
|
79
|
-
}
|
|
80
|
-
catch (err) {
|
|
81
|
-
return { board: null, content: '', error: err instanceof Error ? err.message : String(err) };
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Write the board back to the brainfile
|
|
86
|
-
*/
|
|
87
|
-
function writeBrainfile(filePath, board) {
|
|
88
|
-
try {
|
|
89
|
-
const content = core_1.Brainfile.serialize(board);
|
|
90
|
-
fs.writeFileSync(filePath, content, 'utf-8');
|
|
91
|
-
return { success: true };
|
|
92
|
-
}
|
|
93
|
-
catch (err) {
|
|
94
|
-
return { success: false, error: err instanceof Error ? err.message : String(err) };
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
/**
|
|
98
|
-
* Read board with tasks populated (v1: embedded, v2: reconstructed from board/ files).
|
|
99
|
-
*/
|
|
100
|
-
function readBoardState(filePath) {
|
|
101
|
-
if ((0, v2_detect_1.isV2)(filePath)) {
|
|
102
|
-
try {
|
|
103
|
-
const content = fs.readFileSync(filePath, 'utf-8');
|
|
104
|
-
const board = (0, v2_detect_1.buildBoardFromV2)(filePath);
|
|
105
|
-
return { board, content };
|
|
106
|
-
}
|
|
107
|
-
catch (err) {
|
|
108
|
-
return { board: null, content: '', error: err instanceof Error ? err.message : String(err) };
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
return readBrainfile(filePath);
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Read board config only (for rules/settings changes that should not touch task files).
|
|
115
|
-
*/
|
|
116
|
-
function readBoardConfigOnly(filePath) {
|
|
117
|
-
if ((0, v2_detect_1.isV2)(filePath)) {
|
|
118
|
-
try {
|
|
119
|
-
const content = fs.readFileSync(filePath, 'utf-8');
|
|
120
|
-
const board = (0, v2_detect_1.readV2BoardConfig)(filePath);
|
|
121
|
-
return { board, content };
|
|
122
|
-
}
|
|
123
|
-
catch (err) {
|
|
124
|
-
return { board: null, content: '', error: err instanceof Error ? err.message : String(err) };
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
return readBrainfile(filePath);
|
|
128
|
-
}
|
|
129
|
-
/**
|
|
130
|
-
* Resolve a task file path in v2 board directory.
|
|
131
|
-
*/
|
|
132
|
-
function resolveV2TaskPath(filePath, taskId) {
|
|
133
|
-
const dirs = (0, v2_detect_1.getV2Dirs)(filePath);
|
|
134
|
-
const doc = (0, core_1.findTask)(dirs.boardDir, taskId);
|
|
135
|
-
if (!doc || !doc.filePath)
|
|
136
|
-
return null;
|
|
137
|
-
return { taskPath: doc.filePath, task: doc.task };
|
|
138
|
-
}
|
|
139
|
-
/**
|
|
140
|
-
* Move a task to a different column
|
|
141
|
-
*/
|
|
142
|
-
function moveTaskAction(filePath, taskId, targetColumnId) {
|
|
143
|
-
const { board, error } = readBoardState(filePath);
|
|
144
|
-
if (!board)
|
|
145
|
-
return { success: false, error };
|
|
146
|
-
const taskInfo = (0, core_1.findTaskById)(board, taskId);
|
|
147
|
-
if (!taskInfo)
|
|
148
|
-
return { success: false, error: `Task ${taskId} not found` };
|
|
149
|
-
const targetColumn = (0, core_1.findColumnById)(board, targetColumnId) || (0, core_1.findColumnByName)(board, targetColumnId);
|
|
150
|
-
if (!targetColumn)
|
|
151
|
-
return { success: false, error: `Column ${targetColumnId} not found` };
|
|
152
|
-
if (taskInfo.column.id === targetColumn.id) {
|
|
153
|
-
return { success: true, message: 'Task already in this column' };
|
|
154
|
-
}
|
|
155
|
-
if ((0, v2_detect_1.isV2)(filePath)) {
|
|
156
|
-
const located = resolveV2TaskPath(filePath, taskId);
|
|
157
|
-
if (!located)
|
|
158
|
-
return { success: false, error: `Task ${taskId} not found` };
|
|
159
|
-
const moveResult = (0, core_1.moveTaskFile)(located.taskPath, targetColumn.id, targetColumn.tasks.length);
|
|
160
|
-
if (!moveResult.success)
|
|
161
|
-
return { success: false, error: moveResult.error };
|
|
162
|
-
return { success: true, message: `Moved to ${targetColumn.title}` };
|
|
163
|
-
}
|
|
164
|
-
const result = (0, core_1.moveTask)(board, taskId, taskInfo.column.id, targetColumn.id, targetColumn.tasks.length);
|
|
165
|
-
if (!result.success)
|
|
166
|
-
return { success: false, error: result.error };
|
|
167
|
-
const writeResult = writeBrainfile(filePath, result.board);
|
|
168
|
-
if (!writeResult.success)
|
|
169
|
-
return writeResult;
|
|
170
|
-
return { success: true, message: `Moved to ${targetColumn.title}` };
|
|
171
|
-
}
|
|
172
|
-
/**
|
|
173
|
-
* Delete a task permanently
|
|
174
|
-
*/
|
|
175
|
-
function deleteTaskAction(filePath, taskId) {
|
|
176
|
-
const { board, error } = readBoardState(filePath);
|
|
177
|
-
if (!board)
|
|
178
|
-
return { success: false, error };
|
|
179
|
-
const taskInfo = (0, core_1.findTaskById)(board, taskId);
|
|
180
|
-
if (!taskInfo)
|
|
181
|
-
return { success: false, error: `Task ${taskId} not found` };
|
|
182
|
-
if ((0, v2_detect_1.isV2)(filePath)) {
|
|
183
|
-
const located = resolveV2TaskPath(filePath, taskId);
|
|
184
|
-
if (!located)
|
|
185
|
-
return { success: false, error: `Task ${taskId} not found` };
|
|
186
|
-
const result = (0, core_1.deleteTaskFile)(located.taskPath);
|
|
187
|
-
if (!result.success)
|
|
188
|
-
return { success: false, error: result.error };
|
|
189
|
-
return { success: true, message: `Deleted ${taskId}` };
|
|
190
|
-
}
|
|
191
|
-
const result = (0, core_1.deleteTask)(board, taskInfo.column.id, taskId);
|
|
192
|
-
if (!result.success)
|
|
193
|
-
return { success: false, error: result.error };
|
|
194
|
-
const writeResult = writeBrainfile(filePath, result.board);
|
|
195
|
-
if (!writeResult.success)
|
|
196
|
-
return writeResult;
|
|
197
|
-
return { success: true, message: `Deleted ${taskId}` };
|
|
198
|
-
}
|
|
199
|
-
/**
|
|
200
|
-
* Get the archive file path for a brainfile
|
|
201
|
-
* Archive lives in same directory: brainfile.md -> brainfile-archive.md
|
|
202
|
-
*/
|
|
203
|
-
function getArchivePath(filePath) {
|
|
204
|
-
const dir = path.dirname(filePath);
|
|
205
|
-
const filename = path.basename(filePath);
|
|
206
|
-
const archiveFilename = filename.replace(/\.md$/, '-archive.md');
|
|
207
|
-
return path.join(dir, archiveFilename);
|
|
208
|
-
}
|
|
209
|
-
/**
|
|
210
|
-
* Create an empty archive board structure
|
|
211
|
-
*/
|
|
212
|
-
function createEmptyArchiveBoard() {
|
|
213
|
-
return {
|
|
214
|
-
title: 'Logs',
|
|
215
|
-
columns: [],
|
|
216
|
-
archive: [],
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
/**
|
|
220
|
-
* Archive a task to a separate brainfile-archive.md file
|
|
221
|
-
* Per protocol spec, archived tasks go to a separate file, not inline archive array
|
|
222
|
-
*/
|
|
223
|
-
function archiveTaskAction(filePath, taskId) {
|
|
224
|
-
if ((0, v2_detect_1.isV2)(filePath)) {
|
|
225
|
-
const dirs = (0, v2_detect_1.getV2Dirs)(filePath);
|
|
226
|
-
const located = resolveV2TaskPath(filePath, taskId);
|
|
227
|
-
if (!located)
|
|
228
|
-
return { success: false, error: `Task ${taskId} not found` };
|
|
229
|
-
const result = (0, core_1.completeTaskFile)(located.taskPath, dirs.logsDir);
|
|
230
|
-
if (!result.success)
|
|
231
|
-
return { success: false, error: result.error };
|
|
232
|
-
return { success: true, message: `Moved ${taskId} to logs` };
|
|
233
|
-
}
|
|
234
|
-
const { board, error } = readBrainfile(filePath);
|
|
235
|
-
if (!board)
|
|
236
|
-
return { success: false, error };
|
|
237
|
-
const taskInfo = (0, core_1.findTaskById)(board, taskId);
|
|
238
|
-
if (!taskInfo)
|
|
239
|
-
return { success: false, error: `Task ${taskId} not found` };
|
|
240
|
-
const task = taskInfo.task;
|
|
241
|
-
const columnId = taskInfo.column.id;
|
|
242
|
-
// Remove task from the main board (don't use archiveTask - it puts task in inline archive)
|
|
243
|
-
const result = (0, core_1.deleteTask)(board, columnId, taskId);
|
|
244
|
-
if (!result.success)
|
|
245
|
-
return { success: false, error: result.error };
|
|
246
|
-
// Save the main board without the task
|
|
247
|
-
const writeResult = writeBrainfile(filePath, result.board);
|
|
248
|
-
if (!writeResult.success)
|
|
249
|
-
return writeResult;
|
|
250
|
-
// Now add the task to the separate archive file
|
|
251
|
-
const archivePath = getArchivePath(filePath);
|
|
252
|
-
// Read or create archive file
|
|
253
|
-
let archiveBoard;
|
|
254
|
-
if (fs.existsSync(archivePath)) {
|
|
255
|
-
try {
|
|
256
|
-
const archiveContent = fs.readFileSync(archivePath, 'utf-8');
|
|
257
|
-
const parseResult = core_1.Brainfile.parseWithErrors(archiveContent);
|
|
258
|
-
if (parseResult.board) {
|
|
259
|
-
archiveBoard = parseResult.board;
|
|
260
|
-
}
|
|
261
|
-
else {
|
|
262
|
-
archiveBoard = createEmptyArchiveBoard();
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
catch {
|
|
266
|
-
archiveBoard = createEmptyArchiveBoard();
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
else {
|
|
270
|
-
archiveBoard = createEmptyArchiveBoard();
|
|
271
|
-
}
|
|
272
|
-
// Add task to archive (at beginning)
|
|
273
|
-
if (!archiveBoard.archive) {
|
|
274
|
-
archiveBoard.archive = [];
|
|
275
|
-
}
|
|
276
|
-
archiveBoard.archive.unshift(task);
|
|
277
|
-
// Save archive file
|
|
278
|
-
try {
|
|
279
|
-
const archiveContent = core_1.Brainfile.serialize(archiveBoard);
|
|
280
|
-
fs.writeFileSync(archivePath, archiveContent, 'utf-8');
|
|
281
|
-
}
|
|
282
|
-
catch (err) {
|
|
283
|
-
// Task was removed from main board but archive write failed
|
|
284
|
-
// This is a partial failure state - log and report
|
|
285
|
-
return {
|
|
286
|
-
success: false,
|
|
287
|
-
error: `Task removed from board but failed to write logs: ${err instanceof Error ? err.message : String(err)}`,
|
|
288
|
-
};
|
|
289
|
-
}
|
|
290
|
-
return { success: true, message: `Moved ${taskId} to logs` };
|
|
291
|
-
}
|
|
292
|
-
/**
|
|
293
|
-
* Archive a task with support for external destinations (GitHub, Linear)
|
|
294
|
-
* Respects archive.destination from brainfile.md or global config
|
|
295
|
-
*/
|
|
296
|
-
async function archiveTaskActionAsync(filePath, taskId) {
|
|
297
|
-
const { board, error } = readBoardState(filePath);
|
|
298
|
-
if (!board)
|
|
299
|
-
return { success: false, error };
|
|
300
|
-
const taskInfo = (0, core_1.findTaskById)(board, taskId);
|
|
301
|
-
if (!taskInfo)
|
|
302
|
-
return { success: false, error: `Task ${taskId} not found` };
|
|
303
|
-
const task = taskInfo.task;
|
|
304
|
-
const columnId = taskInfo.column.id;
|
|
305
|
-
const columnTitle = taskInfo.column.title;
|
|
306
|
-
// Determine destination from brainfile or config
|
|
307
|
-
const brainfileDestination = board.archive?.destination;
|
|
308
|
-
const parsedDest = (0, config_1.getEffectiveDestination)(brainfileDestination);
|
|
309
|
-
const destination = parsedDest.type;
|
|
310
|
-
// Handle local archive (default behavior)
|
|
311
|
-
if (destination === 'local') {
|
|
312
|
-
return archiveTaskAction(filePath, taskId);
|
|
313
|
-
}
|
|
314
|
-
// Handle GitHub archive
|
|
315
|
-
if (destination === 'github') {
|
|
316
|
-
if (!(await (0, github_auth_1.isGitHubAuthenticated)())) {
|
|
317
|
-
return { success: false, error: 'Not authenticated with GitHub. Run: brainfile auth github' };
|
|
318
|
-
}
|
|
319
|
-
const config = (0, config_1.getArchiveConfig)();
|
|
320
|
-
const owner = parsedDest.owner || config.github?.owner;
|
|
321
|
-
const repo = parsedDest.repo || config.github?.repo;
|
|
322
|
-
if (!owner || !repo) {
|
|
323
|
-
return { success: false, error: 'GitHub owner/repo not configured. Set archive.destination: github:owner/repo' };
|
|
324
|
-
}
|
|
325
|
-
const payload = (0, core_1.formatTaskForGitHub)(task, {
|
|
326
|
-
includeMeta: true,
|
|
327
|
-
includeSubtasks: true,
|
|
328
|
-
includeRelatedFiles: true,
|
|
329
|
-
boardTitle: board.title,
|
|
330
|
-
fromColumn: columnTitle,
|
|
331
|
-
extraLabels: config.github?.labels,
|
|
332
|
-
});
|
|
333
|
-
const ghResult = await (0, github_auth_1.createGitHubIssue)({
|
|
334
|
-
owner,
|
|
335
|
-
repo,
|
|
336
|
-
title: payload.title,
|
|
337
|
-
body: payload.body,
|
|
338
|
-
labels: payload.labels,
|
|
339
|
-
state: 'closed',
|
|
340
|
-
});
|
|
341
|
-
if (!ghResult.success) {
|
|
342
|
-
return { success: false, error: `GitHub: ${ghResult.error}` };
|
|
343
|
-
}
|
|
344
|
-
// Remove task from board
|
|
345
|
-
if ((0, v2_detect_1.isV2)(filePath)) {
|
|
346
|
-
const located = resolveV2TaskPath(filePath, taskId);
|
|
347
|
-
if (located) {
|
|
348
|
-
(0, core_1.deleteTaskFile)(located.taskPath);
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
else {
|
|
352
|
-
const deleteResult = (0, core_1.deleteTask)(board, columnId, taskId);
|
|
353
|
-
if (deleteResult.success) {
|
|
354
|
-
writeBrainfile(filePath, deleteResult.board);
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
return { success: true, message: `Moved to logs via GitHub #${ghResult.issueNumber}` };
|
|
358
|
-
}
|
|
359
|
-
// Handle Linear archive
|
|
360
|
-
if (destination === 'linear') {
|
|
361
|
-
if (!(await (0, linear_auth_1.isLinearAuthenticated)())) {
|
|
362
|
-
return { success: false, error: 'Not authenticated with Linear. Run: brainfile auth linear --token <key>' };
|
|
363
|
-
}
|
|
364
|
-
const config = (0, config_1.getArchiveConfig)();
|
|
365
|
-
let teamId = config.linear?.teamId;
|
|
366
|
-
// Resolve teamKey to teamId if provided in destination
|
|
367
|
-
if (parsedDest.teamKey) {
|
|
368
|
-
const teams = await (0, linear_auth_1.getLinearTeams)();
|
|
369
|
-
const matchingTeam = teams.find((t) => t.key.toLowerCase() === parsedDest.teamKey.toLowerCase());
|
|
370
|
-
if (matchingTeam) {
|
|
371
|
-
teamId = matchingTeam.id;
|
|
372
|
-
}
|
|
373
|
-
else {
|
|
374
|
-
return { success: false, error: `Linear team "${parsedDest.teamKey}" not found` };
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
if (!teamId) {
|
|
378
|
-
// Try to auto-select if only one team
|
|
379
|
-
const teams = await (0, linear_auth_1.getLinearTeams)();
|
|
380
|
-
if (teams.length === 1) {
|
|
381
|
-
teamId = teams[0].id;
|
|
382
|
-
}
|
|
383
|
-
else if (teams.length > 1) {
|
|
384
|
-
return { success: false, error: 'Multiple Linear teams. Set archive.destination: linear:TEAM_KEY' };
|
|
385
|
-
}
|
|
386
|
-
else {
|
|
387
|
-
return { success: false, error: 'No Linear teams found' };
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
const payload = (0, core_1.formatTaskForLinear)(task, {
|
|
391
|
-
includeMeta: true,
|
|
392
|
-
includeSubtasks: true,
|
|
393
|
-
includeRelatedFiles: true,
|
|
394
|
-
boardTitle: board.title,
|
|
395
|
-
fromColumn: columnTitle,
|
|
396
|
-
stateName: 'Done',
|
|
397
|
-
});
|
|
398
|
-
const linearResult = await (0, linear_auth_1.createLinearIssue)({
|
|
399
|
-
teamId,
|
|
400
|
-
title: payload.title,
|
|
401
|
-
description: payload.description,
|
|
402
|
-
priority: payload.priority,
|
|
403
|
-
labelNames: payload.labelNames,
|
|
404
|
-
stateName: 'Done',
|
|
405
|
-
});
|
|
406
|
-
if (!linearResult.success) {
|
|
407
|
-
return { success: false, error: `Linear: ${linearResult.error}` };
|
|
408
|
-
}
|
|
409
|
-
// Remove task from board
|
|
410
|
-
if ((0, v2_detect_1.isV2)(filePath)) {
|
|
411
|
-
const located = resolveV2TaskPath(filePath, taskId);
|
|
412
|
-
if (located) {
|
|
413
|
-
(0, core_1.deleteTaskFile)(located.taskPath);
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
else {
|
|
417
|
-
const deleteResult = (0, core_1.deleteTask)(board, columnId, taskId);
|
|
418
|
-
if (deleteResult.success) {
|
|
419
|
-
writeBrainfile(filePath, deleteResult.board);
|
|
420
|
-
}
|
|
421
|
-
}
|
|
422
|
-
return { success: true, message: `Moved to logs via Linear ${linearResult.issueId}` };
|
|
423
|
-
}
|
|
424
|
-
return { success: false, error: `Unknown destination: ${destination}` };
|
|
425
|
-
}
|
|
426
|
-
/**
|
|
427
|
-
* Patch/update task fields
|
|
428
|
-
*/
|
|
429
|
-
function patchTaskAction(filePath, taskId, patch) {
|
|
430
|
-
if ((0, v2_detect_1.isV2)(filePath)) {
|
|
431
|
-
const located = resolveV2TaskPath(filePath, taskId);
|
|
432
|
-
if (!located)
|
|
433
|
-
return { success: false, error: `Task ${taskId} not found` };
|
|
434
|
-
const doc = (0, core_1.readTaskFile)(located.taskPath);
|
|
435
|
-
if (!doc)
|
|
436
|
-
return { success: false, error: `Failed to read task ${taskId}` };
|
|
437
|
-
const updatedTask = { ...doc.task };
|
|
438
|
-
if (patch.title !== undefined)
|
|
439
|
-
updatedTask.title = patch.title;
|
|
440
|
-
if (patch.description !== undefined) {
|
|
441
|
-
if (patch.description === null) {
|
|
442
|
-
delete updatedTask.description;
|
|
443
|
-
}
|
|
444
|
-
else {
|
|
445
|
-
updatedTask.description = patch.description;
|
|
446
|
-
}
|
|
447
|
-
}
|
|
448
|
-
if (patch.priority !== undefined) {
|
|
449
|
-
if (patch.priority === null) {
|
|
450
|
-
delete updatedTask.priority;
|
|
451
|
-
}
|
|
452
|
-
else {
|
|
453
|
-
updatedTask.priority = patch.priority;
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
if (patch.tags !== undefined) {
|
|
457
|
-
if (patch.tags === null) {
|
|
458
|
-
delete updatedTask.tags;
|
|
459
|
-
}
|
|
460
|
-
else {
|
|
461
|
-
updatedTask.tags = patch.tags;
|
|
462
|
-
}
|
|
463
|
-
}
|
|
464
|
-
if (patch.assignee !== undefined) {
|
|
465
|
-
if (patch.assignee === null) {
|
|
466
|
-
delete updatedTask.assignee;
|
|
467
|
-
}
|
|
468
|
-
else {
|
|
469
|
-
updatedTask.assignee = patch.assignee;
|
|
470
|
-
}
|
|
471
|
-
}
|
|
472
|
-
if (patch.dueDate !== undefined) {
|
|
473
|
-
if (patch.dueDate === null) {
|
|
474
|
-
delete updatedTask.dueDate;
|
|
475
|
-
}
|
|
476
|
-
else {
|
|
477
|
-
updatedTask.dueDate = patch.dueDate;
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
if (patch.relatedFiles !== undefined) {
|
|
481
|
-
if (patch.relatedFiles === null) {
|
|
482
|
-
delete updatedTask.relatedFiles;
|
|
483
|
-
}
|
|
484
|
-
else {
|
|
485
|
-
updatedTask.relatedFiles = patch.relatedFiles;
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
updatedTask.updatedAt = new Date().toISOString();
|
|
489
|
-
try {
|
|
490
|
-
(0, core_1.writeTaskFile)(located.taskPath, updatedTask, doc.body);
|
|
491
|
-
return { success: true, message: `Updated ${taskId}` };
|
|
492
|
-
}
|
|
493
|
-
catch (err) {
|
|
494
|
-
return { success: false, error: err instanceof Error ? err.message : String(err) };
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
const { board, error } = readBrainfile(filePath);
|
|
498
|
-
if (!board)
|
|
499
|
-
return { success: false, error };
|
|
500
|
-
const result = (0, core_1.patchTask)(board, taskId, patch);
|
|
501
|
-
if (!result.success)
|
|
502
|
-
return { success: false, error: result.error };
|
|
503
|
-
const writeResult = writeBrainfile(filePath, result.board);
|
|
504
|
-
if (!writeResult.success)
|
|
505
|
-
return writeResult;
|
|
506
|
-
return { success: true, message: `Updated ${taskId}` };
|
|
507
|
-
}
|
|
508
|
-
/**
|
|
509
|
-
* Cycle task priority: none -> low -> medium -> high -> critical -> none
|
|
510
|
-
*/
|
|
511
|
-
function cyclePriorityAction(filePath, taskId) {
|
|
512
|
-
const priorities = [undefined, 'low', 'medium', 'high', 'critical'];
|
|
513
|
-
if ((0, v2_detect_1.isV2)(filePath)) {
|
|
514
|
-
const located = resolveV2TaskPath(filePath, taskId);
|
|
515
|
-
if (!located)
|
|
516
|
-
return { success: false, error: `Task ${taskId} not found` };
|
|
517
|
-
const doc = (0, core_1.readTaskFile)(located.taskPath);
|
|
518
|
-
if (!doc)
|
|
519
|
-
return { success: false, error: `Failed to read task ${taskId}` };
|
|
520
|
-
const currentIndex = priorities.indexOf(doc.task.priority);
|
|
521
|
-
const nextIndex = (currentIndex + 1) % priorities.length;
|
|
522
|
-
const nextPriority = priorities[nextIndex];
|
|
523
|
-
const updatedTask = {
|
|
524
|
-
...doc.task,
|
|
525
|
-
...(nextPriority ? { priority: nextPriority } : {}),
|
|
526
|
-
updatedAt: new Date().toISOString(),
|
|
527
|
-
};
|
|
528
|
-
if (!nextPriority) {
|
|
529
|
-
delete updatedTask.priority;
|
|
530
|
-
}
|
|
531
|
-
try {
|
|
532
|
-
(0, core_1.writeTaskFile)(located.taskPath, updatedTask, doc.body);
|
|
533
|
-
return { success: true, message: `Priority: ${nextPriority || 'none'}` };
|
|
534
|
-
}
|
|
535
|
-
catch (err) {
|
|
536
|
-
return { success: false, error: err instanceof Error ? err.message : String(err) };
|
|
537
|
-
}
|
|
538
|
-
}
|
|
539
|
-
const { board, error } = readBrainfile(filePath);
|
|
540
|
-
if (!board)
|
|
541
|
-
return { success: false, error };
|
|
542
|
-
const taskInfo = (0, core_1.findTaskById)(board, taskId);
|
|
543
|
-
if (!taskInfo)
|
|
544
|
-
return { success: false, error: `Task ${taskId} not found` };
|
|
545
|
-
const currentIndex = priorities.indexOf(taskInfo.task.priority);
|
|
546
|
-
const nextIndex = (currentIndex + 1) % priorities.length;
|
|
547
|
-
const nextPriority = priorities[nextIndex];
|
|
548
|
-
const patch = { priority: nextPriority || null };
|
|
549
|
-
const result = (0, core_1.patchTask)(board, taskId, patch);
|
|
550
|
-
if (!result.success)
|
|
551
|
-
return { success: false, error: result.error };
|
|
552
|
-
const writeResult = writeBrainfile(filePath, result.board);
|
|
553
|
-
if (!writeResult.success)
|
|
554
|
-
return writeResult;
|
|
555
|
-
return { success: true, message: `Priority: ${nextPriority || 'none'}` };
|
|
556
|
-
}
|
|
557
|
-
/**
|
|
558
|
-
* Toggle a subtask's completion status
|
|
559
|
-
*/
|
|
560
|
-
function toggleSubtaskAction(filePath, taskId, subtaskId) {
|
|
561
|
-
if ((0, v2_detect_1.isV2)(filePath)) {
|
|
562
|
-
const located = resolveV2TaskPath(filePath, taskId);
|
|
563
|
-
if (!located)
|
|
564
|
-
return { success: false, error: `Task ${taskId} not found` };
|
|
565
|
-
const doc = (0, core_1.readTaskFile)(located.taskPath);
|
|
566
|
-
if (!doc)
|
|
567
|
-
return { success: false, error: `Failed to read task ${taskId}` };
|
|
568
|
-
if (!doc.task.subtasks || doc.task.subtasks.length === 0) {
|
|
569
|
-
return { success: false, error: `Task ${taskId} has no subtasks` };
|
|
570
|
-
}
|
|
571
|
-
const updatedSubtasks = doc.task.subtasks.map((subtask) => {
|
|
572
|
-
if (subtask.id !== subtaskId)
|
|
573
|
-
return subtask;
|
|
574
|
-
return { ...subtask, completed: !subtask.completed };
|
|
575
|
-
});
|
|
576
|
-
const found = updatedSubtasks.some((subtask) => subtask.id === subtaskId);
|
|
577
|
-
if (!found)
|
|
578
|
-
return { success: false, error: `Subtask ${subtaskId} not found` };
|
|
579
|
-
const updatedTask = {
|
|
580
|
-
...doc.task,
|
|
581
|
-
subtasks: updatedSubtasks,
|
|
582
|
-
updatedAt: new Date().toISOString(),
|
|
583
|
-
};
|
|
584
|
-
try {
|
|
585
|
-
(0, core_1.writeTaskFile)(located.taskPath, updatedTask, doc.body);
|
|
586
|
-
return { success: true, message: `Toggled ${subtaskId}` };
|
|
587
|
-
}
|
|
588
|
-
catch (err) {
|
|
589
|
-
return { success: false, error: err instanceof Error ? err.message : String(err) };
|
|
590
|
-
}
|
|
591
|
-
}
|
|
592
|
-
const { board, error } = readBrainfile(filePath);
|
|
593
|
-
if (!board)
|
|
594
|
-
return { success: false, error };
|
|
595
|
-
const result = (0, core_1.toggleSubtask)(board, taskId, subtaskId);
|
|
596
|
-
if (!result.success)
|
|
597
|
-
return { success: false, error: result.error };
|
|
598
|
-
const writeResult = writeBrainfile(filePath, result.board);
|
|
599
|
-
if (!writeResult.success)
|
|
600
|
-
return writeResult;
|
|
601
|
-
return { success: true, message: `Toggled ${subtaskId}` };
|
|
602
|
-
}
|
|
603
|
-
/**
|
|
604
|
-
* Add a new task to a column (quick add - title only)
|
|
605
|
-
*/
|
|
606
|
-
function addTaskAction(filePath, columnId, taskInput) {
|
|
607
|
-
const { board, error } = readBoardState(filePath);
|
|
608
|
-
if (!board)
|
|
609
|
-
return { success: false, error };
|
|
610
|
-
const column = (0, core_1.findColumnById)(board, columnId) || (0, core_1.findColumnByName)(board, columnId);
|
|
611
|
-
if (!column)
|
|
612
|
-
return { success: false, error: `Column ${columnId} not found` };
|
|
613
|
-
if ((0, v2_detect_1.isV2)(filePath)) {
|
|
614
|
-
const dirs = (0, v2_detect_1.getV2Dirs)(filePath);
|
|
615
|
-
const result = (0, core_1.addTaskFile)(dirs.boardDir, {
|
|
616
|
-
title: taskInput.title,
|
|
617
|
-
column: column.id,
|
|
618
|
-
description: taskInput.description,
|
|
619
|
-
priority: taskInput.priority,
|
|
620
|
-
tags: taskInput.tags,
|
|
621
|
-
assignee: taskInput.assignee,
|
|
622
|
-
dueDate: taskInput.dueDate,
|
|
623
|
-
relatedFiles: taskInput.relatedFiles,
|
|
624
|
-
}, taskInput.description ? `## Description\n${taskInput.description.trim()}\n` : '', dirs.logsDir);
|
|
625
|
-
if (!result.success)
|
|
626
|
-
return { success: false, error: result.error };
|
|
627
|
-
return { success: true, message: `Added ${result.task?.id || 'task'}` };
|
|
628
|
-
}
|
|
629
|
-
const result = (0, core_1.addTask)(board, column.id, taskInput);
|
|
630
|
-
if (!result.success)
|
|
631
|
-
return { success: false, error: result.error };
|
|
632
|
-
const writeResult = writeBrainfile(filePath, result.board);
|
|
633
|
-
if (!writeResult.success)
|
|
634
|
-
return writeResult;
|
|
635
|
-
// Get the new task ID
|
|
636
|
-
const newColumn = result.board.columns.find(c => c.id === column.id);
|
|
637
|
-
const newTask = newColumn?.tasks[newColumn.tasks.length - 1];
|
|
638
|
-
return { success: true, message: `Added ${newTask?.id || 'task'}` };
|
|
639
|
-
}
|
|
640
|
-
/**
|
|
641
|
-
* Create a new task using $EDITOR with full template
|
|
642
|
-
*/
|
|
643
|
-
function newTaskInEditor(filePath, columnId) {
|
|
644
|
-
const { board, error } = readBoardState(filePath);
|
|
645
|
-
if (!board)
|
|
646
|
-
return { success: false, error };
|
|
647
|
-
const column = (0, core_1.findColumnById)(board, columnId) || (0, core_1.findColumnByName)(board, columnId);
|
|
648
|
-
if (!column)
|
|
649
|
-
return { success: false, error: `Column ${columnId} not found` };
|
|
650
|
-
// Build template for new task
|
|
651
|
-
const template = buildNewTaskTemplate();
|
|
652
|
-
// Write to temp file
|
|
653
|
-
const tempDir = os.tmpdir();
|
|
654
|
-
const tempFile = path.join(tempDir, `brainfile-new-task.yaml`);
|
|
655
|
-
try {
|
|
656
|
-
fs.writeFileSync(tempFile, template, 'utf-8');
|
|
657
|
-
}
|
|
658
|
-
catch (err) {
|
|
659
|
-
return { success: false, error: `Failed to create temp file: ${err}` };
|
|
660
|
-
}
|
|
661
|
-
// Get editor from environment
|
|
662
|
-
const editor = process.env.EDITOR || process.env.VISUAL || 'nano';
|
|
663
|
-
// Use spawnSync to block and give full terminal control to editor
|
|
664
|
-
// 5 minute timeout to prevent indefinite hangs
|
|
665
|
-
const result = (0, child_process_1.spawnSync)(editor, [tempFile], {
|
|
666
|
-
stdio: 'inherit',
|
|
667
|
-
shell: true,
|
|
668
|
-
timeout: 300000,
|
|
669
|
-
});
|
|
670
|
-
// Reset terminal state after editor exits - hide cursor and clear line
|
|
671
|
-
// This fixes cursor blink artifact when returning to Ink TUI
|
|
672
|
-
process.stdout.write('\x1b[?25l'); // Hide cursor
|
|
673
|
-
process.stdout.write('\x1b[2K'); // Clear current line
|
|
674
|
-
if (result.error) {
|
|
675
|
-
cleanupTempFile(tempFile);
|
|
676
|
-
return { success: false, error: `Failed to open editor: ${result.error.message}` };
|
|
677
|
-
}
|
|
678
|
-
if (result.signal === 'SIGTERM') {
|
|
679
|
-
cleanupTempFile(tempFile);
|
|
680
|
-
return { success: false, error: 'Editor timed out (5 minute limit)' };
|
|
681
|
-
}
|
|
682
|
-
if (result.status !== 0) {
|
|
683
|
-
cleanupTempFile(tempFile);
|
|
684
|
-
return { success: false, error: `Editor exited with code ${result.status}` };
|
|
685
|
-
}
|
|
686
|
-
// Read edited content
|
|
687
|
-
let editedYaml;
|
|
688
|
-
try {
|
|
689
|
-
editedYaml = fs.readFileSync(tempFile, 'utf-8');
|
|
690
|
-
}
|
|
691
|
-
catch (err) {
|
|
692
|
-
cleanupTempFile(tempFile);
|
|
693
|
-
return { success: false, error: `Failed to read edited file: ${err}` };
|
|
694
|
-
}
|
|
695
|
-
cleanupTempFile(tempFile);
|
|
696
|
-
// Parse the new task
|
|
697
|
-
const parseResult = parseNewTaskYaml(editedYaml);
|
|
698
|
-
if (!parseResult.success) {
|
|
699
|
-
return { success: false, error: parseResult.error };
|
|
700
|
-
}
|
|
701
|
-
if (!parseResult.taskInput || !parseResult.taskInput.title) {
|
|
702
|
-
return { success: false, error: 'Task title is required' };
|
|
703
|
-
}
|
|
704
|
-
// Add the task
|
|
705
|
-
return addTaskAction(filePath, columnId, parseResult.taskInput);
|
|
706
|
-
}
|
|
707
|
-
/**
|
|
708
|
-
* Build template for a new task
|
|
709
|
-
*/
|
|
710
|
-
function buildNewTaskTemplate() {
|
|
711
|
-
return `# New Task
|
|
712
|
-
# Fill in the fields below. Save and close to create the task.
|
|
713
|
-
# Lines starting with # are ignored. Title is required.
|
|
714
|
-
|
|
715
|
-
title:
|
|
716
|
-
description:
|
|
717
|
-
priority:
|
|
718
|
-
tags:
|
|
719
|
-
assignee:
|
|
720
|
-
dueDate:
|
|
721
|
-
`;
|
|
722
|
-
}
|
|
723
|
-
/**
|
|
724
|
-
* Parse new task YAML into TaskInput
|
|
725
|
-
*/
|
|
726
|
-
function parseNewTaskYaml(yaml) {
|
|
727
|
-
try {
|
|
728
|
-
const lines = yaml.split('\n').filter(line => !line.startsWith('#') && line.trim());
|
|
729
|
-
const taskInput = { title: '' };
|
|
730
|
-
for (const line of lines) {
|
|
731
|
-
const colonIndex = line.indexOf(':');
|
|
732
|
-
if (colonIndex === -1)
|
|
733
|
-
continue;
|
|
734
|
-
const key = line.substring(0, colonIndex).trim();
|
|
735
|
-
const value = line.substring(colonIndex + 1).trim();
|
|
736
|
-
switch (key) {
|
|
737
|
-
case 'title':
|
|
738
|
-
if (value)
|
|
739
|
-
taskInput.title = value;
|
|
740
|
-
break;
|
|
741
|
-
case 'description':
|
|
742
|
-
if (value)
|
|
743
|
-
taskInput.description = value;
|
|
744
|
-
break;
|
|
745
|
-
case 'priority':
|
|
746
|
-
const validPriorities = ['low', 'medium', 'high', 'critical'];
|
|
747
|
-
if (validPriorities.includes(value)) {
|
|
748
|
-
taskInput.priority = value;
|
|
749
|
-
}
|
|
750
|
-
break;
|
|
751
|
-
case 'tags':
|
|
752
|
-
if (value) {
|
|
753
|
-
taskInput.tags = value.split(',').map(t => t.trim()).filter(Boolean);
|
|
754
|
-
}
|
|
755
|
-
break;
|
|
756
|
-
case 'assignee':
|
|
757
|
-
if (value)
|
|
758
|
-
taskInput.assignee = value;
|
|
759
|
-
break;
|
|
760
|
-
case 'dueDate':
|
|
761
|
-
if (value)
|
|
762
|
-
taskInput.dueDate = value;
|
|
763
|
-
break;
|
|
764
|
-
}
|
|
765
|
-
}
|
|
766
|
-
return { success: true, taskInput };
|
|
767
|
-
}
|
|
768
|
-
catch (err) {
|
|
769
|
-
return { success: false, error: `Failed to parse task: ${err}` };
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
/**
|
|
773
|
-
* Edit a task in the user's preferred editor
|
|
774
|
-
* Uses spawnSync to fully block and give terminal control to the editor
|
|
775
|
-
*/
|
|
776
|
-
function editTaskInEditor(filePath, taskId) {
|
|
777
|
-
const { board, error } = readBoardState(filePath);
|
|
778
|
-
if (!board) {
|
|
779
|
-
return { success: false, error };
|
|
780
|
-
}
|
|
781
|
-
const taskInfo = (0, core_1.findTaskById)(board, taskId);
|
|
782
|
-
if (!taskInfo) {
|
|
783
|
-
return { success: false, error: `Task ${taskId} not found` };
|
|
784
|
-
}
|
|
785
|
-
const task = taskInfo.task;
|
|
786
|
-
// Build YAML representation of just this task
|
|
787
|
-
const taskYaml = buildTaskYaml(task);
|
|
788
|
-
// Write to temp file
|
|
789
|
-
const tempDir = os.tmpdir();
|
|
790
|
-
const tempFile = path.join(tempDir, `brainfile-edit-${taskId}.yaml`);
|
|
791
|
-
try {
|
|
792
|
-
fs.writeFileSync(tempFile, taskYaml, 'utf-8');
|
|
793
|
-
}
|
|
794
|
-
catch (err) {
|
|
795
|
-
return { success: false, error: `Failed to create temp file: ${err}` };
|
|
796
|
-
}
|
|
797
|
-
// Get editor from environment
|
|
798
|
-
const editor = process.env.EDITOR || process.env.VISUAL || 'nano';
|
|
799
|
-
// Use spawnSync to block and give full terminal control to editor
|
|
800
|
-
// 5 minute timeout to prevent indefinite hangs
|
|
801
|
-
const result = (0, child_process_1.spawnSync)(editor, [tempFile], {
|
|
802
|
-
stdio: 'inherit',
|
|
803
|
-
shell: true,
|
|
804
|
-
timeout: 300000,
|
|
805
|
-
});
|
|
806
|
-
// Reset terminal state after editor exits - hide cursor and clear line
|
|
807
|
-
// This fixes cursor blink artifact when returning to Ink TUI
|
|
808
|
-
process.stdout.write('\x1b[?25l'); // Hide cursor
|
|
809
|
-
process.stdout.write('\x1b[2K'); // Clear current line
|
|
810
|
-
if (result.error) {
|
|
811
|
-
cleanupTempFile(tempFile);
|
|
812
|
-
return { success: false, error: `Failed to open editor: ${result.error.message}` };
|
|
813
|
-
}
|
|
814
|
-
if (result.signal === 'SIGTERM') {
|
|
815
|
-
cleanupTempFile(tempFile);
|
|
816
|
-
return { success: false, error: 'Editor timed out (5 minute limit)' };
|
|
817
|
-
}
|
|
818
|
-
if (result.status !== 0) {
|
|
819
|
-
cleanupTempFile(tempFile);
|
|
820
|
-
return { success: false, error: `Editor exited with code ${result.status}` };
|
|
821
|
-
}
|
|
822
|
-
// Read edited content
|
|
823
|
-
let editedYaml;
|
|
824
|
-
try {
|
|
825
|
-
editedYaml = fs.readFileSync(tempFile, 'utf-8');
|
|
826
|
-
}
|
|
827
|
-
catch (err) {
|
|
828
|
-
cleanupTempFile(tempFile);
|
|
829
|
-
return { success: false, error: `Failed to read edited file: ${err}` };
|
|
830
|
-
}
|
|
831
|
-
cleanupTempFile(tempFile);
|
|
832
|
-
// Parse edited YAML and apply as patch
|
|
833
|
-
const patchResult = parseEditedTaskYaml(editedYaml, task);
|
|
834
|
-
if (!patchResult.success) {
|
|
835
|
-
return { success: false, error: patchResult.error };
|
|
836
|
-
}
|
|
837
|
-
if (!patchResult.patch || Object.keys(patchResult.patch).length === 0) {
|
|
838
|
-
return { success: true, message: 'No changes made' };
|
|
839
|
-
}
|
|
840
|
-
// Apply the patch
|
|
841
|
-
return patchTaskAction(filePath, taskId, patchResult.patch);
|
|
842
|
-
}
|
|
843
|
-
/**
|
|
844
|
-
* Build YAML representation of a task for editing
|
|
845
|
-
*/
|
|
846
|
-
function buildTaskYaml(task) {
|
|
847
|
-
const lines = [
|
|
848
|
-
`# Edit task: ${task.id}`,
|
|
849
|
-
`# Save and close to apply changes. Delete a field to clear it.`,
|
|
850
|
-
`# Lines starting with # are ignored.`,
|
|
851
|
-
``,
|
|
852
|
-
`title: ${task.title}`,
|
|
853
|
-
];
|
|
854
|
-
if (task.description) {
|
|
855
|
-
// Handle multi-line descriptions
|
|
856
|
-
if (task.description.includes('\n')) {
|
|
857
|
-
lines.push(`description: |`);
|
|
858
|
-
task.description.split('\n').forEach(line => {
|
|
859
|
-
lines.push(` ${line}`);
|
|
860
|
-
});
|
|
861
|
-
}
|
|
862
|
-
else {
|
|
863
|
-
lines.push(`description: ${task.description}`);
|
|
864
|
-
}
|
|
865
|
-
}
|
|
866
|
-
else {
|
|
867
|
-
lines.push(`# description: `);
|
|
868
|
-
}
|
|
869
|
-
lines.push(`priority: ${task.priority || ''}`);
|
|
870
|
-
lines.push(`tags: ${task.tags?.join(', ') || ''}`);
|
|
871
|
-
lines.push(`assignee: ${task.assignee || ''}`);
|
|
872
|
-
lines.push(`dueDate: ${task.dueDate || ''}`);
|
|
873
|
-
if (task.subtasks && task.subtasks.length > 0) {
|
|
874
|
-
lines.push(`subtasks:`);
|
|
875
|
-
task.subtasks.forEach(st => {
|
|
876
|
-
lines.push(` - [${st.completed ? 'x' : ' '}] ${st.title}`);
|
|
877
|
-
});
|
|
878
|
-
}
|
|
879
|
-
if (task.relatedFiles && task.relatedFiles.length > 0) {
|
|
880
|
-
lines.push(`relatedFiles: ${task.relatedFiles.join(', ')}`);
|
|
881
|
-
}
|
|
882
|
-
return lines.join('\n') + '\n';
|
|
883
|
-
}
|
|
884
|
-
/**
|
|
885
|
-
* Parse edited YAML and return a TaskPatch
|
|
886
|
-
*/
|
|
887
|
-
function parseEditedTaskYaml(yaml, originalTask) {
|
|
888
|
-
const patch = {};
|
|
889
|
-
try {
|
|
890
|
-
const lines = yaml.split('\n').filter(line => !line.startsWith('#') && line.trim());
|
|
891
|
-
for (const line of lines) {
|
|
892
|
-
const colonIndex = line.indexOf(':');
|
|
893
|
-
if (colonIndex === -1)
|
|
894
|
-
continue;
|
|
895
|
-
const key = line.substring(0, colonIndex).trim();
|
|
896
|
-
const value = line.substring(colonIndex + 1).trim();
|
|
897
|
-
switch (key) {
|
|
898
|
-
case 'title':
|
|
899
|
-
if (value && value !== originalTask.title) {
|
|
900
|
-
patch.title = value;
|
|
901
|
-
}
|
|
902
|
-
break;
|
|
903
|
-
case 'description':
|
|
904
|
-
if (value !== (originalTask.description || '')) {
|
|
905
|
-
patch.description = value || undefined;
|
|
906
|
-
}
|
|
907
|
-
break;
|
|
908
|
-
case 'priority':
|
|
909
|
-
const validPriorities = ['low', 'medium', 'high', 'critical', ''];
|
|
910
|
-
if (validPriorities.includes(value) && value !== (originalTask.priority || '')) {
|
|
911
|
-
patch.priority = value || null;
|
|
912
|
-
}
|
|
913
|
-
break;
|
|
914
|
-
case 'tags':
|
|
915
|
-
const newTags = value ? value.split(',').map(t => t.trim()).filter(Boolean) : [];
|
|
916
|
-
const oldTags = originalTask.tags || [];
|
|
917
|
-
if (JSON.stringify(newTags) !== JSON.stringify(oldTags)) {
|
|
918
|
-
patch.tags = newTags.length > 0 ? newTags : null;
|
|
919
|
-
}
|
|
920
|
-
break;
|
|
921
|
-
case 'assignee':
|
|
922
|
-
if (value !== (originalTask.assignee || '')) {
|
|
923
|
-
patch.assignee = value || null;
|
|
924
|
-
}
|
|
925
|
-
break;
|
|
926
|
-
case 'dueDate':
|
|
927
|
-
if (value !== (originalTask.dueDate || '')) {
|
|
928
|
-
patch.dueDate = value || null;
|
|
929
|
-
}
|
|
930
|
-
break;
|
|
931
|
-
case 'relatedFiles':
|
|
932
|
-
const newFiles = value ? value.split(',').map(f => f.trim()).filter(Boolean) : [];
|
|
933
|
-
const oldFiles = originalTask.relatedFiles || [];
|
|
934
|
-
if (JSON.stringify(newFiles) !== JSON.stringify(oldFiles)) {
|
|
935
|
-
patch.relatedFiles = newFiles.length > 0 ? newFiles : null;
|
|
936
|
-
}
|
|
937
|
-
break;
|
|
938
|
-
}
|
|
939
|
-
}
|
|
940
|
-
return { success: true, patch };
|
|
941
|
-
}
|
|
942
|
-
catch (err) {
|
|
943
|
-
return { success: false, error: `Failed to parse edited YAML: ${err}` };
|
|
944
|
-
}
|
|
945
|
-
}
|
|
946
|
-
/**
|
|
947
|
-
* Clean up temp file
|
|
948
|
-
*/
|
|
949
|
-
function cleanupTempFile(filePath) {
|
|
950
|
-
try {
|
|
951
|
-
if (fs.existsSync(filePath)) {
|
|
952
|
-
fs.unlinkSync(filePath);
|
|
953
|
-
}
|
|
954
|
-
}
|
|
955
|
-
catch {
|
|
956
|
-
// Ignore cleanup errors
|
|
957
|
-
}
|
|
958
|
-
}
|
|
959
|
-
/**
|
|
960
|
-
* Copy text to clipboard (cross-platform)
|
|
961
|
-
* Uses spawnSync with input option to pipe text to clipboard command
|
|
962
|
-
*/
|
|
963
|
-
function copyToClipboard(text) {
|
|
964
|
-
try {
|
|
965
|
-
const platform = process.platform;
|
|
966
|
-
let command;
|
|
967
|
-
let args;
|
|
968
|
-
if (platform === 'darwin') {
|
|
969
|
-
command = 'pbcopy';
|
|
970
|
-
args = [];
|
|
971
|
-
}
|
|
972
|
-
else if (platform === 'linux') {
|
|
973
|
-
// Try xclip first, fall back to xsel
|
|
974
|
-
command = 'xclip';
|
|
975
|
-
args = ['-selection', 'clipboard'];
|
|
976
|
-
}
|
|
977
|
-
else if (platform === 'win32') {
|
|
978
|
-
command = 'clip';
|
|
979
|
-
args = [];
|
|
980
|
-
}
|
|
981
|
-
else {
|
|
982
|
-
return { success: false, error: 'Clipboard not supported on this platform' };
|
|
983
|
-
}
|
|
984
|
-
const result = (0, child_process_1.spawnSync)(command, args, {
|
|
985
|
-
input: text,
|
|
986
|
-
stdio: ['pipe', 'ignore', 'ignore'],
|
|
987
|
-
});
|
|
988
|
-
if (result.error) {
|
|
989
|
-
return { success: false, error: `Clipboard error: ${result.error.message}` };
|
|
990
|
-
}
|
|
991
|
-
return { success: true, message: 'Copied to clipboard' };
|
|
992
|
-
}
|
|
993
|
-
catch (err) {
|
|
994
|
-
return { success: false, error: `Failed to copy: ${err}` };
|
|
995
|
-
}
|
|
996
|
-
}
|
|
997
|
-
/**
|
|
998
|
-
* Add a new rule to a rule category
|
|
999
|
-
*/
|
|
1000
|
-
function addRuleAction(filePath, ruleType, ruleText) {
|
|
1001
|
-
const { board, error } = readBoardConfigOnly(filePath);
|
|
1002
|
-
if (!board)
|
|
1003
|
-
return { success: false, error };
|
|
1004
|
-
// Initialize rules if not present
|
|
1005
|
-
if (!board.rules) {
|
|
1006
|
-
board.rules = {
|
|
1007
|
-
always: [],
|
|
1008
|
-
never: [],
|
|
1009
|
-
prefer: [],
|
|
1010
|
-
context: [],
|
|
1011
|
-
};
|
|
1012
|
-
}
|
|
1013
|
-
// Ensure the rule type array exists
|
|
1014
|
-
if (!board.rules[ruleType]) {
|
|
1015
|
-
board.rules[ruleType] = [];
|
|
1016
|
-
}
|
|
1017
|
-
// Generate next rule ID
|
|
1018
|
-
const existingIds = board.rules[ruleType].map((r) => r.id);
|
|
1019
|
-
const maxId = existingIds.length > 0 ? Math.max(...existingIds) : 0;
|
|
1020
|
-
const newRuleId = maxId + 1;
|
|
1021
|
-
// Add the rule
|
|
1022
|
-
board.rules[ruleType].push({
|
|
1023
|
-
id: newRuleId,
|
|
1024
|
-
rule: ruleText.trim(),
|
|
1025
|
-
});
|
|
1026
|
-
const writeResult = writeBrainfile(filePath, board);
|
|
1027
|
-
if (!writeResult.success)
|
|
1028
|
-
return writeResult;
|
|
1029
|
-
return { success: true, message: `Added ${ruleType} rule #${newRuleId}` };
|
|
1030
|
-
}
|
|
1031
|
-
/**
|
|
1032
|
-
* Update an existing rule
|
|
1033
|
-
*/
|
|
1034
|
-
function updateRuleAction(filePath, ruleType, ruleId, ruleText) {
|
|
1035
|
-
const { board, error } = readBoardConfigOnly(filePath);
|
|
1036
|
-
if (!board)
|
|
1037
|
-
return { success: false, error };
|
|
1038
|
-
if (!board.rules || !board.rules[ruleType]) {
|
|
1039
|
-
return { success: false, error: `No ${ruleType} rules found` };
|
|
1040
|
-
}
|
|
1041
|
-
const rule = board.rules[ruleType].find((r) => r.id === ruleId);
|
|
1042
|
-
if (!rule) {
|
|
1043
|
-
return { success: false, error: `Rule #${ruleId} not found` };
|
|
1044
|
-
}
|
|
1045
|
-
rule.rule = ruleText.trim();
|
|
1046
|
-
const writeResult = writeBrainfile(filePath, board);
|
|
1047
|
-
if (!writeResult.success)
|
|
1048
|
-
return writeResult;
|
|
1049
|
-
return { success: true, message: `Updated ${ruleType} rule #${ruleId}` };
|
|
1050
|
-
}
|
|
1051
|
-
/**
|
|
1052
|
-
* Delete a rule
|
|
1053
|
-
*/
|
|
1054
|
-
function deleteRuleAction(filePath, ruleType, ruleId) {
|
|
1055
|
-
const { board, error } = readBoardConfigOnly(filePath);
|
|
1056
|
-
if (!board)
|
|
1057
|
-
return { success: false, error };
|
|
1058
|
-
if (!board.rules || !board.rules[ruleType]) {
|
|
1059
|
-
return { success: false, error: `No ${ruleType} rules found` };
|
|
1060
|
-
}
|
|
1061
|
-
const ruleIndex = board.rules[ruleType].findIndex((r) => r.id === ruleId);
|
|
1062
|
-
if (ruleIndex === -1) {
|
|
1063
|
-
return { success: false, error: `Rule #${ruleId} not found` };
|
|
1064
|
-
}
|
|
1065
|
-
board.rules[ruleType].splice(ruleIndex, 1);
|
|
1066
|
-
const writeResult = writeBrainfile(filePath, board);
|
|
1067
|
-
if (!writeResult.success)
|
|
1068
|
-
return writeResult;
|
|
1069
|
-
return { success: true, message: `Deleted ${ruleType} rule #${ruleId}` };
|
|
1070
|
-
}
|
|
1071
|
-
// =============================================================================
|
|
1072
|
-
// ARCHIVE OPERATIONS
|
|
1073
|
-
// =============================================================================
|
|
1074
|
-
/**
|
|
1075
|
-
* Load logs from the logs file
|
|
1076
|
-
*/
|
|
1077
|
-
function loadLogs(filePath) {
|
|
1078
|
-
if ((0, v2_detect_1.isV2)(filePath)) {
|
|
1079
|
-
try {
|
|
1080
|
-
const dirs = (0, v2_detect_1.getV2Dirs)(filePath);
|
|
1081
|
-
const docs = (0, core_1.readTasksDir)(dirs.logsDir);
|
|
1082
|
-
const logs = docs
|
|
1083
|
-
.map((doc) => ({ ...doc.task }))
|
|
1084
|
-
.sort((a, b) => {
|
|
1085
|
-
const aTime = a.completedAt ? Date.parse(a.completedAt) : 0;
|
|
1086
|
-
const bTime = b.completedAt ? Date.parse(b.completedAt) : 0;
|
|
1087
|
-
return bTime - aTime;
|
|
1088
|
-
});
|
|
1089
|
-
return { logs };
|
|
1090
|
-
}
|
|
1091
|
-
catch (err) {
|
|
1092
|
-
return { logs: [], error: err instanceof Error ? err.message : String(err) };
|
|
1093
|
-
}
|
|
1094
|
-
}
|
|
1095
|
-
const archivePath = getArchivePath(filePath);
|
|
1096
|
-
if (!fs.existsSync(archivePath)) {
|
|
1097
|
-
return { logs: [] };
|
|
1098
|
-
}
|
|
1099
|
-
try {
|
|
1100
|
-
const archiveContent = fs.readFileSync(archivePath, 'utf-8');
|
|
1101
|
-
const parseResult = core_1.Brainfile.parseWithErrors(archiveContent);
|
|
1102
|
-
if (parseResult.board && parseResult.board.archive) {
|
|
1103
|
-
return { logs: parseResult.board.archive };
|
|
1104
|
-
}
|
|
1105
|
-
return { logs: [] };
|
|
1106
|
-
}
|
|
1107
|
-
catch (err) {
|
|
1108
|
-
return { logs: [], error: err instanceof Error ? err.message : String(err) };
|
|
1109
|
-
}
|
|
1110
|
-
}
|
|
1111
|
-
/**
|
|
1112
|
-
* Restore a task from logs to a column
|
|
1113
|
-
*/
|
|
1114
|
-
function restoreTaskAction(filePath, taskId, toColumnId) {
|
|
1115
|
-
if ((0, v2_detect_1.isV2)(filePath)) {
|
|
1116
|
-
const dirs = (0, v2_detect_1.getV2Dirs)(filePath);
|
|
1117
|
-
const { board, error } = readBoardState(filePath);
|
|
1118
|
-
if (!board)
|
|
1119
|
-
return { success: false, error };
|
|
1120
|
-
const column = (0, core_1.findColumnById)(board, toColumnId) || (0, core_1.findColumnByName)(board, toColumnId);
|
|
1121
|
-
if (!column) {
|
|
1122
|
-
return { success: false, error: `Column ${toColumnId} not found` };
|
|
1123
|
-
}
|
|
1124
|
-
if ((0, core_1.findTask)(dirs.boardDir, taskId)) {
|
|
1125
|
-
return { success: false, error: `Task ${taskId} already exists in board` };
|
|
1126
|
-
}
|
|
1127
|
-
const logDoc = (0, core_1.findTask)(dirs.logsDir, taskId);
|
|
1128
|
-
if (!logDoc || !logDoc.filePath) {
|
|
1129
|
-
return { success: false, error: `Task ${taskId} not found in logs` };
|
|
1130
|
-
}
|
|
1131
|
-
const restoredTask = {
|
|
1132
|
-
...logDoc.task,
|
|
1133
|
-
column: column.id,
|
|
1134
|
-
updatedAt: new Date().toISOString(),
|
|
1135
|
-
};
|
|
1136
|
-
delete restoredTask.completedAt;
|
|
1137
|
-
const targetPath = path.join(dirs.boardDir, `${taskId}.md`);
|
|
1138
|
-
try {
|
|
1139
|
-
(0, core_1.writeTaskFile)(targetPath, restoredTask, logDoc.body);
|
|
1140
|
-
fs.unlinkSync(logDoc.filePath);
|
|
1141
|
-
return { success: true, message: `Restored ${taskId} to ${column.title}` };
|
|
1142
|
-
}
|
|
1143
|
-
catch (err) {
|
|
1144
|
-
return { success: false, error: `Failed to restore task: ${err instanceof Error ? err.message : String(err)}` };
|
|
1145
|
-
}
|
|
1146
|
-
}
|
|
1147
|
-
const archivePath = getArchivePath(filePath);
|
|
1148
|
-
// Read archive file
|
|
1149
|
-
if (!fs.existsSync(archivePath)) {
|
|
1150
|
-
return { success: false, error: 'Logs file not found' };
|
|
1151
|
-
}
|
|
1152
|
-
let archiveBoard;
|
|
1153
|
-
try {
|
|
1154
|
-
const archiveContent = fs.readFileSync(archivePath, 'utf-8');
|
|
1155
|
-
const parseResult = core_1.Brainfile.parseWithErrors(archiveContent);
|
|
1156
|
-
if (!parseResult.board) {
|
|
1157
|
-
return { success: false, error: 'Failed to parse logs file' };
|
|
1158
|
-
}
|
|
1159
|
-
archiveBoard = parseResult.board;
|
|
1160
|
-
}
|
|
1161
|
-
catch (err) {
|
|
1162
|
-
return { success: false, error: `Failed to read logs: ${err instanceof Error ? err.message : String(err)}` };
|
|
1163
|
-
}
|
|
1164
|
-
// Find task in archive
|
|
1165
|
-
if (!archiveBoard.archive || archiveBoard.archive.length === 0) {
|
|
1166
|
-
return { success: false, error: 'Logs are empty' };
|
|
1167
|
-
}
|
|
1168
|
-
const taskIndex = archiveBoard.archive.findIndex(t => t.id === taskId);
|
|
1169
|
-
if (taskIndex === -1) {
|
|
1170
|
-
return { success: false, error: `Task ${taskId} not found in logs` };
|
|
1171
|
-
}
|
|
1172
|
-
const task = archiveBoard.archive[taskIndex];
|
|
1173
|
-
// Read main board
|
|
1174
|
-
const { board: mainBoard, error: mainBoardError } = readBrainfile(filePath);
|
|
1175
|
-
if (!mainBoard)
|
|
1176
|
-
return { success: false, error: mainBoardError };
|
|
1177
|
-
// Find target column
|
|
1178
|
-
const column = (0, core_1.findColumnById)(mainBoard, toColumnId) || (0, core_1.findColumnByName)(mainBoard, toColumnId);
|
|
1179
|
-
if (!column) {
|
|
1180
|
-
return { success: false, error: `Column ${toColumnId} not found` };
|
|
1181
|
-
}
|
|
1182
|
-
// Add task to column (at beginning)
|
|
1183
|
-
column.tasks.unshift(task);
|
|
1184
|
-
// Remove from archive
|
|
1185
|
-
archiveBoard.archive.splice(taskIndex, 1);
|
|
1186
|
-
// Save both files
|
|
1187
|
-
const writeResult = writeBrainfile(filePath, mainBoard);
|
|
1188
|
-
if (!writeResult.success)
|
|
1189
|
-
return writeResult;
|
|
1190
|
-
try {
|
|
1191
|
-
const archiveContent = core_1.Brainfile.serialize(archiveBoard);
|
|
1192
|
-
fs.writeFileSync(archivePath, archiveContent, 'utf-8');
|
|
1193
|
-
}
|
|
1194
|
-
catch (err) {
|
|
1195
|
-
return { success: false, error: `Task restored but failed to update logs: ${err}` };
|
|
1196
|
-
}
|
|
1197
|
-
return { success: true, message: `Restored ${taskId} to ${column.title}` };
|
|
1198
|
-
}
|
|
1199
|
-
/**
|
|
1200
|
-
* Permanently delete a task from logs
|
|
1201
|
-
*/
|
|
1202
|
-
function deleteArchivedTaskAction(filePath, taskId) {
|
|
1203
|
-
if ((0, v2_detect_1.isV2)(filePath)) {
|
|
1204
|
-
const dirs = (0, v2_detect_1.getV2Dirs)(filePath);
|
|
1205
|
-
const logDoc = (0, core_1.findTask)(dirs.logsDir, taskId);
|
|
1206
|
-
if (!logDoc || !logDoc.filePath) {
|
|
1207
|
-
return { success: false, error: `Task ${taskId} not found in logs` };
|
|
1208
|
-
}
|
|
1209
|
-
const result = (0, core_1.deleteTaskFile)(logDoc.filePath);
|
|
1210
|
-
if (!result.success) {
|
|
1211
|
-
return { success: false, error: result.error || `Failed to delete ${taskId}` };
|
|
1212
|
-
}
|
|
1213
|
-
return { success: true, message: `Permanently deleted ${taskId}` };
|
|
1214
|
-
}
|
|
1215
|
-
const archivePath = getArchivePath(filePath);
|
|
1216
|
-
// Read archive file
|
|
1217
|
-
if (!fs.existsSync(archivePath)) {
|
|
1218
|
-
return { success: false, error: 'Logs file not found' };
|
|
1219
|
-
}
|
|
1220
|
-
let archiveBoard;
|
|
1221
|
-
try {
|
|
1222
|
-
const archiveContent = fs.readFileSync(archivePath, 'utf-8');
|
|
1223
|
-
const parseResult = core_1.Brainfile.parseWithErrors(archiveContent);
|
|
1224
|
-
if (!parseResult.board) {
|
|
1225
|
-
return { success: false, error: 'Failed to parse logs file' };
|
|
1226
|
-
}
|
|
1227
|
-
archiveBoard = parseResult.board;
|
|
1228
|
-
}
|
|
1229
|
-
catch (err) {
|
|
1230
|
-
return { success: false, error: `Failed to read logs: ${err instanceof Error ? err.message : String(err)}` };
|
|
1231
|
-
}
|
|
1232
|
-
// Find and remove task from archive
|
|
1233
|
-
if (!archiveBoard.archive || archiveBoard.archive.length === 0) {
|
|
1234
|
-
return { success: false, error: 'Logs are empty' };
|
|
1235
|
-
}
|
|
1236
|
-
const taskIndex = archiveBoard.archive.findIndex(t => t.id === taskId);
|
|
1237
|
-
if (taskIndex === -1) {
|
|
1238
|
-
return { success: false, error: `Task ${taskId} not found in logs` };
|
|
1239
|
-
}
|
|
1240
|
-
archiveBoard.archive.splice(taskIndex, 1);
|
|
1241
|
-
// Save archive file
|
|
1242
|
-
try {
|
|
1243
|
-
const archiveContent = core_1.Brainfile.serialize(archiveBoard);
|
|
1244
|
-
fs.writeFileSync(archivePath, archiveContent, 'utf-8');
|
|
1245
|
-
}
|
|
1246
|
-
catch (err) {
|
|
1247
|
-
return { success: false, error: `Failed to update logs: ${err}` };
|
|
1248
|
-
}
|
|
1249
|
-
return { success: true, message: `Permanently deleted ${taskId}` };
|
|
1250
|
-
}
|
|
1251
|
-
//# sourceMappingURL=actions.js.map
|