@brainfile/cli 0.15.1 → 0.16.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/dist/cli.js +51 -3
- package/dist/cli.js.map +1 -1
- package/dist/commands/adr.d.ts.map +1 -1
- package/dist/commands/adr.js +16 -3
- package/dist/commands/adr.js.map +1 -1
- package/dist/commands/complete.d.ts +7 -5
- package/dist/commands/complete.d.ts.map +1 -1
- package/dist/commands/complete.js +141 -77
- package/dist/commands/complete.js.map +1 -1
- package/dist/commands/context.d.ts +19 -0
- package/dist/commands/context.d.ts.map +1 -0
- package/dist/commands/context.js +103 -0
- package/dist/commands/context.js.map +1 -0
- package/dist/commands/contract.d.ts.map +1 -1
- package/dist/commands/contract.js +34 -9
- package/dist/commands/contract.js.map +1 -1
- package/dist/commands/delete.d.ts.map +1 -1
- package/dist/commands/delete.js +19 -5
- package/dist/commands/delete.js.map +1 -1
- package/dist/commands/history.d.ts +18 -0
- package/dist/commands/history.d.ts.map +1 -0
- package/dist/commands/history.js +92 -0
- package/dist/commands/history.js.map +1 -0
- package/dist/commands/ledger-rebuild.d.ts +17 -0
- package/dist/commands/ledger-rebuild.d.ts.map +1 -0
- package/dist/commands/ledger-rebuild.js +287 -0
- package/dist/commands/ledger-rebuild.js.map +1 -0
- package/dist/commands/ledger.d.ts +20 -0
- package/dist/commands/ledger.d.ts.map +1 -0
- package/dist/commands/ledger.js +111 -0
- package/dist/commands/ledger.js.map +1 -0
- package/dist/commands/mcp.d.ts.map +1 -1
- package/dist/commands/mcp.js +371 -4
- package/dist/commands/mcp.js.map +1 -1
- package/dist/commands/migrate-ledger.d.ts +17 -0
- package/dist/commands/migrate-ledger.d.ts.map +1 -0
- package/dist/commands/migrate-ledger.js +127 -0
- package/dist/commands/migrate-ledger.js.map +1 -0
- package/dist/commands/migrate.d.ts +2 -0
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/migrate.js +97 -32
- package/dist/commands/migrate.js.map +1 -1
- package/dist/commands/move.d.ts.map +1 -1
- package/dist/commands/move.js +16 -3
- package/dist/commands/move.js.map +1 -1
- package/dist/commands/patch.d.ts.map +1 -1
- package/dist/commands/patch.js +19 -10
- package/dist/commands/patch.js.map +1 -1
- package/dist/commands/stats.d.ts +20 -0
- package/dist/commands/stats.d.ts.map +1 -0
- package/dist/commands/stats.js +151 -0
- package/dist/commands/stats.js.map +1 -0
- package/dist/lib/contractRunner.js +1 -1
- package/dist/lib/contractRunner.js.map +1 -1
- package/dist/utils/date-helpers.d.ts +20 -0
- package/dist/utils/date-helpers.d.ts.map +1 -0
- package/dist/utils/date-helpers.js +54 -0
- package/dist/utils/date-helpers.js.map +1 -0
- package/package.json +2 -2
package/dist/commands/mcp.js
CHANGED
|
@@ -39,6 +39,7 @@ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
|
|
|
39
39
|
const zod_1 = require("zod");
|
|
40
40
|
const fs = __importStar(require("fs"));
|
|
41
41
|
const path = __importStar(require("path"));
|
|
42
|
+
const child_process_1 = require("child_process");
|
|
42
43
|
const core_1 = require("@brainfile/core");
|
|
43
44
|
const errorHandler_1 = require("../utils/errorHandler");
|
|
44
45
|
const contractSpec_1 = require("../utils/contractSpec");
|
|
@@ -50,6 +51,7 @@ const core_2 = require("@brainfile/core");
|
|
|
50
51
|
const contractRunner_1 = require("../lib/contractRunner");
|
|
51
52
|
const archive_1 = require("../utils/archive");
|
|
52
53
|
const core_3 = require("@brainfile/core");
|
|
54
|
+
const dist_1 = require("../../../core/dist");
|
|
53
55
|
const v2_detect_1 = require("../utils/v2-detect");
|
|
54
56
|
function sanitizeTypesConfig(raw) {
|
|
55
57
|
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
@@ -129,6 +131,80 @@ function findGitRoot(startDir) {
|
|
|
129
131
|
}
|
|
130
132
|
return null;
|
|
131
133
|
}
|
|
134
|
+
const LEDGER_CONTRACT_STATUSES = [
|
|
135
|
+
'ready',
|
|
136
|
+
'in_progress',
|
|
137
|
+
'delivered',
|
|
138
|
+
'done',
|
|
139
|
+
'failed',
|
|
140
|
+
'blocked',
|
|
141
|
+
];
|
|
142
|
+
function resolveLedgerLogsDir(filePath) {
|
|
143
|
+
if ((0, v2_detect_1.isV2)(filePath)) {
|
|
144
|
+
return (0, v2_detect_1.getV2Dirs)(filePath).logsDir;
|
|
145
|
+
}
|
|
146
|
+
const absolutePath = path.resolve(filePath);
|
|
147
|
+
const parentDir = path.dirname(absolutePath);
|
|
148
|
+
const stateDir = path.basename(parentDir) === '.brainfile'
|
|
149
|
+
? parentDir
|
|
150
|
+
: path.join(parentDir, '.brainfile');
|
|
151
|
+
return path.join(stateDir, 'logs');
|
|
152
|
+
}
|
|
153
|
+
function normalizePositiveInt(value, fallback) {
|
|
154
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value <= 0) {
|
|
155
|
+
return fallback;
|
|
156
|
+
}
|
|
157
|
+
return Math.floor(value);
|
|
158
|
+
}
|
|
159
|
+
function normalizeNonNegativeInt(value, fallback) {
|
|
160
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {
|
|
161
|
+
return fallback;
|
|
162
|
+
}
|
|
163
|
+
return Math.floor(value);
|
|
164
|
+
}
|
|
165
|
+
function toIsoDaysAgo(days) {
|
|
166
|
+
const now = Date.now();
|
|
167
|
+
const msPerDay = 24 * 60 * 60 * 1000;
|
|
168
|
+
return new Date(now - (days * msPerDay)).toISOString();
|
|
169
|
+
}
|
|
170
|
+
function inferFilesChangedFromGit(filePath) {
|
|
171
|
+
const repoRoot = findGitRoot(path.dirname(path.resolve(filePath)));
|
|
172
|
+
if (!repoRoot) {
|
|
173
|
+
return [];
|
|
174
|
+
}
|
|
175
|
+
const inferred = new Set();
|
|
176
|
+
const commands = ['git diff --name-only -- .', 'git diff --name-only --cached -- .'];
|
|
177
|
+
for (const command of commands) {
|
|
178
|
+
try {
|
|
179
|
+
const stdout = (0, child_process_1.execSync)(command, {
|
|
180
|
+
cwd: repoRoot,
|
|
181
|
+
encoding: 'utf-8',
|
|
182
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
183
|
+
});
|
|
184
|
+
for (const line of stdout.split('\n')) {
|
|
185
|
+
const trimmed = line.trim();
|
|
186
|
+
if (trimmed) {
|
|
187
|
+
inferred.add(trimmed.replace(/\\/g, '/'));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
catch {
|
|
192
|
+
// Ignore git inference errors and fall back to core defaults.
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return Array.from(inferred);
|
|
196
|
+
}
|
|
197
|
+
function median(values) {
|
|
198
|
+
if (values.length === 0) {
|
|
199
|
+
return 0;
|
|
200
|
+
}
|
|
201
|
+
const sorted = [...values].sort((a, b) => a - b);
|
|
202
|
+
const mid = Math.floor(sorted.length / 2);
|
|
203
|
+
const rawMedian = sorted.length % 2 === 0
|
|
204
|
+
? (sorted[mid - 1] + sorted[mid]) / 2
|
|
205
|
+
: sorted[mid];
|
|
206
|
+
return Number(rawMedian.toFixed(3));
|
|
207
|
+
}
|
|
132
208
|
async function mcpCommand(options) {
|
|
133
209
|
// Auto-discover brainfile if not specified
|
|
134
210
|
let defaultFile = options.file;
|
|
@@ -681,7 +757,7 @@ async function mcpCommand(options) {
|
|
|
681
757
|
(0, core_3.writeTaskFile)(taskPath, doc.task, doc.body);
|
|
682
758
|
const shouldAutoComplete = resolvedTargetColumn.completionColumn === true && isTaskCompletable(doc.task.type, board.types);
|
|
683
759
|
if (shouldAutoComplete) {
|
|
684
|
-
const completeResult = (0,
|
|
760
|
+
const completeResult = (0, dist_1.completeTaskFile)(taskPath, dirs.logsDir);
|
|
685
761
|
if (!completeResult.success) {
|
|
686
762
|
return { content: [{ type: 'text', text: `Error: ${completeResult.error || `Failed to complete task: ${task}`}` }], isError: true };
|
|
687
763
|
}
|
|
@@ -1815,7 +1891,12 @@ async function mcpCommand(options) {
|
|
|
1815
1891
|
if ('error' in result) {
|
|
1816
1892
|
return { content: [{ type: 'text', text: `Error: ${result.error}` }], isError: true };
|
|
1817
1893
|
}
|
|
1818
|
-
return {
|
|
1894
|
+
return {
|
|
1895
|
+
content: [{
|
|
1896
|
+
type: 'text',
|
|
1897
|
+
text: `Contract delivered: ${task}\nReminder: prepare summary and filesChanged for the upcoming complete_task call.`
|
|
1898
|
+
}]
|
|
1899
|
+
};
|
|
1819
1900
|
});
|
|
1820
1901
|
server.registerTool('contract_validate', {
|
|
1821
1902
|
title: 'Contract Validate',
|
|
@@ -1999,6 +2080,251 @@ async function mcpCommand(options) {
|
|
|
1999
2080
|
};
|
|
2000
2081
|
});
|
|
2001
2082
|
// ==========================================================================
|
|
2083
|
+
// LEDGER
|
|
2084
|
+
// ==========================================================================
|
|
2085
|
+
server.registerTool('get_task_context', {
|
|
2086
|
+
title: 'Get Task Context',
|
|
2087
|
+
description: 'Get scoped recent ledger history for a task based on relatedFiles and contract deliverables',
|
|
2088
|
+
inputSchema: {
|
|
2089
|
+
file: zod_1.z.string().optional().describe('Path to brainfile.md (default: brainfile.md)'),
|
|
2090
|
+
taskId: zod_1.z.string().optional().describe('Task ID to scope context for'),
|
|
2091
|
+
maxEntries: zod_1.z.number().optional().describe('Maximum matching entries to return (default: 5)'),
|
|
2092
|
+
maxAgeDays: zod_1.z.number().optional().describe('Maximum age window in days (default: 90)'),
|
|
2093
|
+
task_id: zod_1.z.string().optional().describe('Alias of taskId'),
|
|
2094
|
+
max_entries: zod_1.z.number().optional().describe('Alias of maxEntries'),
|
|
2095
|
+
max_age_days: zod_1.z.number().optional().describe('Alias of maxAgeDays'),
|
|
2096
|
+
}
|
|
2097
|
+
}, async ({ file, taskId, maxEntries, maxAgeDays, task_id, max_entries, max_age_days }) => {
|
|
2098
|
+
const filePath = file || defaultFile;
|
|
2099
|
+
const resolvedTaskId = taskId || task_id;
|
|
2100
|
+
if (!resolvedTaskId) {
|
|
2101
|
+
return { content: [{ type: 'text', text: 'Error: taskId is required' }], isError: true };
|
|
2102
|
+
}
|
|
2103
|
+
let relatedFiles = [];
|
|
2104
|
+
let deliverables;
|
|
2105
|
+
if ((0, v2_detect_1.isV2)(filePath)) {
|
|
2106
|
+
const dirs = (0, v2_detect_1.getV2Dirs)(filePath);
|
|
2107
|
+
const found = (0, v2_detect_1.findV2Task)(dirs, resolvedTaskId, true);
|
|
2108
|
+
if (!found) {
|
|
2109
|
+
return { content: [{ type: 'text', text: `Error: Task not found: ${resolvedTaskId}` }], isError: true };
|
|
2110
|
+
}
|
|
2111
|
+
relatedFiles = found.doc.task.relatedFiles || [];
|
|
2112
|
+
deliverables = found.doc.task.contract?.deliverables;
|
|
2113
|
+
}
|
|
2114
|
+
else {
|
|
2115
|
+
const boardResult = readBoard(filePath);
|
|
2116
|
+
if ('error' in boardResult) {
|
|
2117
|
+
return { content: [{ type: 'text', text: `Error: ${boardResult.error}` }], isError: true };
|
|
2118
|
+
}
|
|
2119
|
+
const taskInfo = (0, core_1.findTaskById)(boardResult.board, resolvedTaskId);
|
|
2120
|
+
if (!taskInfo) {
|
|
2121
|
+
return { content: [{ type: 'text', text: `Error: Task not found: ${resolvedTaskId}` }], isError: true };
|
|
2122
|
+
}
|
|
2123
|
+
relatedFiles = taskInfo.task.relatedFiles || [];
|
|
2124
|
+
deliverables = taskInfo.task.contract?.deliverables;
|
|
2125
|
+
}
|
|
2126
|
+
const limit = normalizePositiveInt(maxEntries ?? max_entries, 5);
|
|
2127
|
+
const ageDays = normalizeNonNegativeInt(maxAgeDays ?? max_age_days, 90);
|
|
2128
|
+
const since = toIsoDaysAgo(ageDays);
|
|
2129
|
+
const logsDir = resolveLedgerLogsDir(filePath);
|
|
2130
|
+
const entries = (0, dist_1.getTaskContext)(logsDir, relatedFiles, deliverables, {
|
|
2131
|
+
limit,
|
|
2132
|
+
dateRange: { from: since },
|
|
2133
|
+
});
|
|
2134
|
+
const scopeFiles = new Set();
|
|
2135
|
+
for (const value of relatedFiles) {
|
|
2136
|
+
const trimmed = value.trim();
|
|
2137
|
+
if (trimmed) {
|
|
2138
|
+
scopeFiles.add(trimmed);
|
|
2139
|
+
}
|
|
2140
|
+
}
|
|
2141
|
+
for (const deliverable of deliverables || []) {
|
|
2142
|
+
const rawPath = typeof deliverable === 'string' ? deliverable : deliverable.path;
|
|
2143
|
+
const trimmed = rawPath.trim();
|
|
2144
|
+
if (trimmed) {
|
|
2145
|
+
scopeFiles.add(trimmed);
|
|
2146
|
+
}
|
|
2147
|
+
}
|
|
2148
|
+
const output = {
|
|
2149
|
+
taskId: resolvedTaskId,
|
|
2150
|
+
count: entries.length,
|
|
2151
|
+
maxEntries: limit,
|
|
2152
|
+
maxAgeDays: ageDays,
|
|
2153
|
+
since,
|
|
2154
|
+
scopeFiles: Array.from(scopeFiles),
|
|
2155
|
+
entries,
|
|
2156
|
+
};
|
|
2157
|
+
return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }] };
|
|
2158
|
+
});
|
|
2159
|
+
server.registerTool('query_ledger', {
|
|
2160
|
+
title: 'Query Ledger',
|
|
2161
|
+
description: 'Query ledger records by assignee, tags, date range, files, and contract status',
|
|
2162
|
+
inputSchema: {
|
|
2163
|
+
file: zod_1.z.string().optional().describe('Path to brainfile.md (default: brainfile.md)'),
|
|
2164
|
+
assignee: zod_1.z.string().optional().describe('Filter by assignee'),
|
|
2165
|
+
tags: zod_1.z.array(zod_1.z.string()).optional().describe('Filter by tags (OR match)'),
|
|
2166
|
+
since: zod_1.z.string().optional().describe('Filter completions since this ISO date/time'),
|
|
2167
|
+
until: zod_1.z.string().optional().describe('Filter completions until this ISO date/time'),
|
|
2168
|
+
files: zod_1.z.array(zod_1.z.string()).optional().describe('Filter by file paths touched'),
|
|
2169
|
+
contractStatus: zod_1.z.union([
|
|
2170
|
+
zod_1.z.enum(LEDGER_CONTRACT_STATUSES),
|
|
2171
|
+
zod_1.z.array(zod_1.z.enum(LEDGER_CONTRACT_STATUSES)),
|
|
2172
|
+
]).optional().describe('Filter by contract status'),
|
|
2173
|
+
contract_status: zod_1.z.union([
|
|
2174
|
+
zod_1.z.enum(LEDGER_CONTRACT_STATUSES),
|
|
2175
|
+
zod_1.z.array(zod_1.z.enum(LEDGER_CONTRACT_STATUSES)),
|
|
2176
|
+
]).optional().describe('Alias of contractStatus'),
|
|
2177
|
+
}
|
|
2178
|
+
}, async ({ file, assignee, tags, since, until, files, contractStatus, contract_status }) => {
|
|
2179
|
+
const filePath = file || defaultFile;
|
|
2180
|
+
const logsDir = resolveLedgerLogsDir(filePath);
|
|
2181
|
+
const contractFilter = contractStatus ?? contract_status;
|
|
2182
|
+
const dateRange = (since || until) ? { from: since, to: until } : undefined;
|
|
2183
|
+
const records = (0, dist_1.queryLedger)(logsDir, {
|
|
2184
|
+
assignee,
|
|
2185
|
+
tags,
|
|
2186
|
+
dateRange,
|
|
2187
|
+
files,
|
|
2188
|
+
contractStatus: contractFilter,
|
|
2189
|
+
});
|
|
2190
|
+
const output = {
|
|
2191
|
+
count: records.length,
|
|
2192
|
+
filters: {
|
|
2193
|
+
assignee,
|
|
2194
|
+
tags,
|
|
2195
|
+
since,
|
|
2196
|
+
until,
|
|
2197
|
+
files,
|
|
2198
|
+
contractStatus: contractFilter,
|
|
2199
|
+
},
|
|
2200
|
+
records,
|
|
2201
|
+
};
|
|
2202
|
+
return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }] };
|
|
2203
|
+
});
|
|
2204
|
+
server.registerTool('get_stats', {
|
|
2205
|
+
title: 'Get Stats',
|
|
2206
|
+
description: 'Get aggregate completion analytics from the ledger with optional filters',
|
|
2207
|
+
inputSchema: {
|
|
2208
|
+
file: zod_1.z.string().optional().describe('Path to brainfile.md (default: brainfile.md)'),
|
|
2209
|
+
assignee: zod_1.z.string().optional().describe('Filter analytics by assignee'),
|
|
2210
|
+
since: zod_1.z.string().optional().describe('Only include completions since this ISO date/time'),
|
|
2211
|
+
tag: zod_1.z.string().optional().describe('Filter analytics by a single tag'),
|
|
2212
|
+
}
|
|
2213
|
+
}, async ({ file, assignee, since, tag }) => {
|
|
2214
|
+
const filePath = file || defaultFile;
|
|
2215
|
+
const logsDir = resolveLedgerLogsDir(filePath);
|
|
2216
|
+
const records = (0, dist_1.queryLedger)(logsDir, {
|
|
2217
|
+
assignee,
|
|
2218
|
+
tags: tag ? [tag] : undefined,
|
|
2219
|
+
dateRange: since ? { from: since } : undefined,
|
|
2220
|
+
});
|
|
2221
|
+
const cycleTimes = records
|
|
2222
|
+
.map((record) => record.cycleTimeHours)
|
|
2223
|
+
.filter((hours) => typeof hours === 'number' && Number.isFinite(hours) && hours >= 0);
|
|
2224
|
+
const cycleTimeTotal = Number(cycleTimes.reduce((sum, value) => sum + value, 0).toFixed(3));
|
|
2225
|
+
const cycleTimeAverage = cycleTimes.length > 0
|
|
2226
|
+
? Number((cycleTimeTotal / cycleTimes.length).toFixed(3))
|
|
2227
|
+
: 0;
|
|
2228
|
+
const cycleTimeMin = cycleTimes.length > 0 ? Number(Math.min(...cycleTimes).toFixed(3)) : 0;
|
|
2229
|
+
const cycleTimeMax = cycleTimes.length > 0 ? Number(Math.max(...cycleTimes).toFixed(3)) : 0;
|
|
2230
|
+
const byType = {};
|
|
2231
|
+
const byContractStatus = {};
|
|
2232
|
+
const byAssignee = {};
|
|
2233
|
+
const byTag = {};
|
|
2234
|
+
const fileCounts = new Map();
|
|
2235
|
+
let firstCompletionAt = null;
|
|
2236
|
+
let lastCompletionAt = null;
|
|
2237
|
+
let firstCompletionMs = null;
|
|
2238
|
+
let lastCompletionMs = null;
|
|
2239
|
+
for (const record of records) {
|
|
2240
|
+
byType[record.type] = (byType[record.type] || 0) + 1;
|
|
2241
|
+
if (record.contractStatus) {
|
|
2242
|
+
byContractStatus[record.contractStatus] = (byContractStatus[record.contractStatus] || 0) + 1;
|
|
2243
|
+
}
|
|
2244
|
+
if (record.assignee) {
|
|
2245
|
+
byAssignee[record.assignee] = (byAssignee[record.assignee] || 0) + 1;
|
|
2246
|
+
}
|
|
2247
|
+
for (const recordTag of record.tags || []) {
|
|
2248
|
+
byTag[recordTag] = (byTag[recordTag] || 0) + 1;
|
|
2249
|
+
}
|
|
2250
|
+
for (const changedFile of record.filesChanged || []) {
|
|
2251
|
+
fileCounts.set(changedFile, (fileCounts.get(changedFile) || 0) + 1);
|
|
2252
|
+
}
|
|
2253
|
+
const completedMs = Date.parse(record.completedAt);
|
|
2254
|
+
if (Number.isFinite(completedMs)) {
|
|
2255
|
+
if (firstCompletionMs === null || completedMs < firstCompletionMs) {
|
|
2256
|
+
firstCompletionMs = completedMs;
|
|
2257
|
+
firstCompletionAt = record.completedAt;
|
|
2258
|
+
}
|
|
2259
|
+
if (lastCompletionMs === null || completedMs > lastCompletionMs) {
|
|
2260
|
+
lastCompletionMs = completedMs;
|
|
2261
|
+
lastCompletionAt = record.completedAt;
|
|
2262
|
+
}
|
|
2263
|
+
}
|
|
2264
|
+
}
|
|
2265
|
+
const topFiles = Array.from(fileCounts.entries())
|
|
2266
|
+
.sort((a, b) => b[1] - a[1])
|
|
2267
|
+
.slice(0, 10)
|
|
2268
|
+
.map(([filePath, count]) => ({ filePath, count }));
|
|
2269
|
+
const output = {
|
|
2270
|
+
filters: { assignee, since, tag },
|
|
2271
|
+
totals: {
|
|
2272
|
+
completed: records.length,
|
|
2273
|
+
uniqueFilesTouched: fileCounts.size,
|
|
2274
|
+
},
|
|
2275
|
+
cycleTimeHours: {
|
|
2276
|
+
total: cycleTimeTotal,
|
|
2277
|
+
average: cycleTimeAverage,
|
|
2278
|
+
median: median(cycleTimes),
|
|
2279
|
+
min: cycleTimeMin,
|
|
2280
|
+
max: cycleTimeMax,
|
|
2281
|
+
},
|
|
2282
|
+
breakdown: {
|
|
2283
|
+
byType,
|
|
2284
|
+
byContractStatus,
|
|
2285
|
+
byAssignee,
|
|
2286
|
+
byTag,
|
|
2287
|
+
},
|
|
2288
|
+
topFiles,
|
|
2289
|
+
window: {
|
|
2290
|
+
firstCompletionAt,
|
|
2291
|
+
lastCompletionAt,
|
|
2292
|
+
},
|
|
2293
|
+
};
|
|
2294
|
+
return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }] };
|
|
2295
|
+
});
|
|
2296
|
+
server.registerTool('get_file_history', {
|
|
2297
|
+
title: 'Get File History',
|
|
2298
|
+
description: 'Get recent completed records that touched a specific file path',
|
|
2299
|
+
inputSchema: {
|
|
2300
|
+
file: zod_1.z.string().optional().describe('Path to brainfile.md (default: brainfile.md)'),
|
|
2301
|
+
filePath: zod_1.z.string().optional().describe('File path to query'),
|
|
2302
|
+
last: zod_1.z.number().optional().describe('Maximum records to return (default: 10)'),
|
|
2303
|
+
since: zod_1.z.string().optional().describe('Only include completions since this ISO date/time'),
|
|
2304
|
+
file_path: zod_1.z.string().optional().describe('Alias of filePath'),
|
|
2305
|
+
}
|
|
2306
|
+
}, async ({ file, filePath: targetFilePath, last, since, file_path }) => {
|
|
2307
|
+
const filePath = file || defaultFile;
|
|
2308
|
+
const resolvedFilePath = targetFilePath || file_path;
|
|
2309
|
+
if (!resolvedFilePath) {
|
|
2310
|
+
return { content: [{ type: 'text', text: 'Error: filePath is required' }], isError: true };
|
|
2311
|
+
}
|
|
2312
|
+
const logsDir = resolveLedgerLogsDir(filePath);
|
|
2313
|
+
const limit = normalizePositiveInt(last, 10);
|
|
2314
|
+
const records = (0, dist_1.getFileHistory)(logsDir, resolvedFilePath, {
|
|
2315
|
+
limit,
|
|
2316
|
+
dateRange: since ? { from: since } : undefined,
|
|
2317
|
+
});
|
|
2318
|
+
const output = {
|
|
2319
|
+
filePath: resolvedFilePath,
|
|
2320
|
+
count: records.length,
|
|
2321
|
+
last: limit,
|
|
2322
|
+
since,
|
|
2323
|
+
records,
|
|
2324
|
+
};
|
|
2325
|
+
return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }] };
|
|
2326
|
+
});
|
|
2327
|
+
// ==========================================================================
|
|
2002
2328
|
// V2 NEW TOOLS: complete_task, search_logs, append_log
|
|
2003
2329
|
// ==========================================================================
|
|
2004
2330
|
// Complete task tool
|
|
@@ -2008,14 +2334,55 @@ async function mcpCommand(options) {
|
|
|
2008
2334
|
inputSchema: {
|
|
2009
2335
|
file: zod_1.z.string().optional().describe('Path to brainfile.md (default: brainfile.md)'),
|
|
2010
2336
|
task: zod_1.z.string().describe('Task ID to complete'),
|
|
2337
|
+
summary: zod_1.z.string().optional().describe('Completion summary (default: Completed: {title})'),
|
|
2338
|
+
filesChanged: zod_1.z.array(zod_1.z.string()).optional().describe('Files changed during implementation (optional; inferred from git diff if omitted)'),
|
|
2339
|
+
files_changed: zod_1.z.array(zod_1.z.string()).optional().describe('Alias of filesChanged'),
|
|
2011
2340
|
}
|
|
2012
|
-
}, async ({ file, task }) => {
|
|
2341
|
+
}, async ({ file, task, summary, filesChanged, files_changed }) => {
|
|
2013
2342
|
const filePath = file || defaultFile;
|
|
2343
|
+
if ((0, v2_detect_1.isV2)(filePath)) {
|
|
2344
|
+
const dirs = (0, v2_detect_1.getV2Dirs)(filePath);
|
|
2345
|
+
const taskPath = path.join(dirs.boardDir, (0, core_3.taskFileName)(task));
|
|
2346
|
+
const doc = (0, core_3.readTaskFile)(taskPath);
|
|
2347
|
+
if (!doc) {
|
|
2348
|
+
return { content: [{ type: 'text', text: `Error: Task not found: ${task}` }], isError: true };
|
|
2349
|
+
}
|
|
2350
|
+
const resolvedSummary = summary?.trim() || `Completed: ${doc.task.title}`;
|
|
2351
|
+
const rawFiles = filesChanged ?? files_changed ?? [];
|
|
2352
|
+
const providedFiles = rawFiles
|
|
2353
|
+
.map((entry) => entry.trim())
|
|
2354
|
+
.filter((entry) => entry.length > 0);
|
|
2355
|
+
const inferredFiles = providedFiles.length > 0 ? providedFiles : inferFilesChangedFromGit(filePath);
|
|
2356
|
+
const filesSource = providedFiles.length > 0
|
|
2357
|
+
? 'input'
|
|
2358
|
+
: inferredFiles.length > 0
|
|
2359
|
+
? 'git_diff'
|
|
2360
|
+
: 'core_default';
|
|
2361
|
+
const completeResult = (0, dist_1.completeTaskFile)(taskPath, dirs.logsDir, {
|
|
2362
|
+
summary: resolvedSummary,
|
|
2363
|
+
filesChanged: inferredFiles.length > 0 ? inferredFiles : undefined,
|
|
2364
|
+
});
|
|
2365
|
+
if (!completeResult.success || !completeResult.task) {
|
|
2366
|
+
return { content: [{ type: 'text', text: `Error: ${completeResult.error || `Failed to complete task: ${task}`}` }], isError: true };
|
|
2367
|
+
}
|
|
2368
|
+
const output = {
|
|
2369
|
+
task,
|
|
2370
|
+
completedAt: completeResult.task.completedAt,
|
|
2371
|
+
summary: resolvedSummary,
|
|
2372
|
+
filesChanged: inferredFiles,
|
|
2373
|
+
filesSource,
|
|
2374
|
+
};
|
|
2375
|
+
return { content: [{ type: 'text', text: JSON.stringify(output, null, 2) }] };
|
|
2376
|
+
}
|
|
2014
2377
|
try {
|
|
2015
2378
|
const { completeCommand } = await Promise.resolve().then(() => __importStar(require('./complete')));
|
|
2016
2379
|
const result = completeCommand({ file: filePath, task }, { log: () => { }, warn: () => { }, error: () => { }, info: () => { } });
|
|
2380
|
+
const hasLedgerOnlyParams = Boolean(summary) || ((filesChanged ?? files_changed)?.length || 0) > 0;
|
|
2017
2381
|
return {
|
|
2018
|
-
content: [{
|
|
2382
|
+
content: [{
|
|
2383
|
+
type: 'text',
|
|
2384
|
+
text: `Task ${task} completed at ${result.completedAt}${hasLedgerOnlyParams ? `\nNote: summary/filesChanged are only applied in v2 ledger mode.` : ''}`
|
|
2385
|
+
}]
|
|
2019
2386
|
};
|
|
2020
2387
|
}
|
|
2021
2388
|
catch (e) {
|