@phren/cli 0.0.50 → 0.0.53
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 +11 -0
- package/mcp/dist/cli/actions.js +17 -2
- package/mcp/dist/cli/cli.js +1 -1
- package/mcp/dist/cli/namespaces.js +77 -12
- package/mcp/dist/cli/ops.js +2 -2
- package/mcp/dist/content/validate.js +17 -0
- package/mcp/dist/data/access.js +26 -0
- package/mcp/dist/data/tasks.js +27 -2
- package/mcp/dist/generated/memory-ui-graph.browser.js +29 -26
- package/mcp/dist/governance/policy.js +3 -1
- package/mcp/dist/memory-ui-graph.runtime.js +29 -26
- package/mcp/dist/phren-core.js +1 -1
- package/mcp/dist/profile-store.js +19 -0
- package/mcp/dist/project-config.js +26 -0
- package/mcp/dist/shared/index.js +2 -2
- package/mcp/dist/shell/view.js +32 -12
- package/mcp/dist/store-registry.js +41 -0
- package/mcp/dist/store-routing.js +2 -2
- package/mcp/dist/task/lifecycle.js +11 -0
- package/mcp/dist/tools/config.js +23 -5
- package/mcp/dist/tools/data.js +25 -14
- package/mcp/dist/tools/extract.js +8 -5
- package/mcp/dist/tools/finding.js +14 -9
- package/mcp/dist/tools/graph.js +12 -3
- package/mcp/dist/tools/hooks.js +15 -2
- package/mcp/dist/tools/search.js +8 -9
- package/mcp/dist/tools/session.js +58 -18
- package/mcp/dist/tools/tasks.js +58 -44
- package/mcp/dist/ui/data.js +62 -21
- package/mcp/dist/ui/server.js +2 -1
- package/package.json +1 -1
package/mcp/dist/tools/search.js
CHANGED
|
@@ -3,6 +3,7 @@ import { z } from "zod";
|
|
|
3
3
|
import * as fs from "fs";
|
|
4
4
|
import * as path from "path";
|
|
5
5
|
import { createHash } from "crypto";
|
|
6
|
+
import { execFileSync } from "child_process";
|
|
6
7
|
import { isValidProjectName, errorMessage } from "../utils.js";
|
|
7
8
|
import { resolveAllStores } from "../store-registry.js";
|
|
8
9
|
import { readFindings } from "../data/access.js";
|
|
@@ -529,14 +530,13 @@ async function handleListProjects(ctx, { page, page_size }) {
|
|
|
529
530
|
? projectRows.map(row => decodeStringRow(row, 1, "list_projects.projects")[0])
|
|
530
531
|
: [];
|
|
531
532
|
// Gather projects from non-primary stores
|
|
532
|
-
const { getNonPrimaryStores } = await import("../store-registry.js");
|
|
533
|
-
const { getProjectDirs } = await import("../phren-paths.js");
|
|
533
|
+
const { getNonPrimaryStores, getStoreProjectDirs } = await import("../store-registry.js");
|
|
534
534
|
const nonPrimaryStores = getNonPrimaryStores(phrenPath);
|
|
535
535
|
const storeProjects = [];
|
|
536
536
|
for (const store of nonPrimaryStores) {
|
|
537
537
|
if (!fs.existsSync(store.path))
|
|
538
538
|
continue;
|
|
539
|
-
const dirs =
|
|
539
|
+
const dirs = getStoreProjectDirs(store);
|
|
540
540
|
for (const dir of dirs) {
|
|
541
541
|
const projName = path.basename(dir);
|
|
542
542
|
storeProjects.push({ name: projName, store: store.name });
|
|
@@ -691,12 +691,11 @@ async function handleStoreList(ctx) {
|
|
|
691
691
|
const { phrenPath } = ctx;
|
|
692
692
|
const stores = resolveAllStores(phrenPath);
|
|
693
693
|
const storeData = stores.map((store) => {
|
|
694
|
-
|
|
694
|
+
// Get last sync time from git log
|
|
695
|
+
let lastSync = null;
|
|
695
696
|
try {
|
|
696
|
-
const
|
|
697
|
-
|
|
698
|
-
health = JSON.parse(fs.readFileSync(healthPath, "utf8"))?.lastSync ?? null;
|
|
699
|
-
}
|
|
697
|
+
const result = execFileSync("git", ["log", "-1", "--format=%ci"], { cwd: store.path, encoding: "utf8", timeout: 3000 }).trim();
|
|
698
|
+
lastSync = result || null;
|
|
700
699
|
}
|
|
701
700
|
catch { /* non-critical */ }
|
|
702
701
|
return {
|
|
@@ -708,7 +707,7 @@ async function handleStoreList(ctx) {
|
|
|
708
707
|
remote: store.remote ?? null,
|
|
709
708
|
exists: fs.existsSync(store.path),
|
|
710
709
|
projects: store.projects ?? null,
|
|
711
|
-
lastSync
|
|
710
|
+
lastSync,
|
|
712
711
|
};
|
|
713
712
|
});
|
|
714
713
|
const lines = storeData.map((s) => {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mcpResponse } from "./types.js";
|
|
1
|
+
import { mcpResponse, resolveStoreForProject } from "./types.js";
|
|
2
2
|
import { z } from "zod";
|
|
3
3
|
import * as fs from "fs";
|
|
4
4
|
import * as path from "path";
|
|
@@ -305,16 +305,21 @@ export function listAllSessions(phrenPath, limit = 50) {
|
|
|
305
305
|
entries.sort((a, b) => new Date(b.startedAt).getTime() - new Date(a.startedAt).getTime());
|
|
306
306
|
return entries;
|
|
307
307
|
}
|
|
308
|
-
export function getSessionArtifacts(phrenPath, sessionId, project) {
|
|
308
|
+
export async function getSessionArtifacts(phrenPath, sessionId, project) {
|
|
309
309
|
const findings = [];
|
|
310
310
|
const tasks = [];
|
|
311
311
|
const shortId = sessionId.slice(0, 8);
|
|
312
312
|
try {
|
|
313
|
+
// Primary store projects
|
|
313
314
|
const projectDirs = getProjectDirs(phrenPath);
|
|
314
315
|
const targetProjects = project ? [project] : projectDirs.map((d) => path.basename(d));
|
|
315
|
-
|
|
316
|
+
const seen = new Set();
|
|
317
|
+
const readProjectArtifacts = (storePath, proj) => {
|
|
318
|
+
if (seen.has(proj))
|
|
319
|
+
return;
|
|
320
|
+
seen.add(proj);
|
|
316
321
|
// Findings with matching sessionId
|
|
317
|
-
const findingsResult = readFindings(
|
|
322
|
+
const findingsResult = readFindings(storePath, proj);
|
|
318
323
|
if (findingsResult.ok) {
|
|
319
324
|
for (const f of findingsResult.data) {
|
|
320
325
|
if (f.sessionId && (f.sessionId === sessionId || f.sessionId.startsWith(shortId))) {
|
|
@@ -328,7 +333,7 @@ export function getSessionArtifacts(phrenPath, sessionId, project) {
|
|
|
328
333
|
}
|
|
329
334
|
}
|
|
330
335
|
// Tasks with matching sessionId
|
|
331
|
-
const tasksResult = readTasks(
|
|
336
|
+
const tasksResult = readTasks(storePath, proj);
|
|
332
337
|
if (tasksResult.ok) {
|
|
333
338
|
for (const section of ["Active", "Queue", "Done"]) {
|
|
334
339
|
for (const t of tasksResult.data.items[section]) {
|
|
@@ -344,15 +349,32 @@ export function getSessionArtifacts(phrenPath, sessionId, project) {
|
|
|
344
349
|
}
|
|
345
350
|
}
|
|
346
351
|
}
|
|
352
|
+
};
|
|
353
|
+
for (const proj of targetProjects) {
|
|
354
|
+
readProjectArtifacts(phrenPath, proj);
|
|
355
|
+
}
|
|
356
|
+
// Team store projects
|
|
357
|
+
try {
|
|
358
|
+
const { getNonPrimaryStores, getStoreProjectDirs } = await import("../store-registry.js");
|
|
359
|
+
for (const store of getNonPrimaryStores(phrenPath)) {
|
|
360
|
+
if (!fs.existsSync(store.path))
|
|
361
|
+
continue;
|
|
362
|
+
const storeDirs = getStoreProjectDirs(store).map((d) => path.basename(d)).filter((p) => p !== "global");
|
|
363
|
+
const storeTargetProjects = project ? (storeDirs.includes(project) ? [project] : []) : storeDirs;
|
|
364
|
+
for (const proj of storeTargetProjects) {
|
|
365
|
+
readProjectArtifacts(store.path, proj);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
347
368
|
}
|
|
369
|
+
catch { /* store-registry not available */ }
|
|
348
370
|
}
|
|
349
371
|
catch (err) {
|
|
350
372
|
debugLog(`getSessionArtifacts error: ${errorMessage(err)}`);
|
|
351
373
|
}
|
|
352
374
|
return { findings, tasks };
|
|
353
375
|
}
|
|
354
|
-
function hasCompletedTasksInSession(phrenPath, sessionId, project) {
|
|
355
|
-
const artifacts = getSessionArtifacts(phrenPath, sessionId, project);
|
|
376
|
+
async function hasCompletedTasksInSession(phrenPath, sessionId, project) {
|
|
377
|
+
const artifacts = await getSessionArtifacts(phrenPath, sessionId, project);
|
|
356
378
|
return artifacts.tasks.some((task) => task.section === "Done" && task.checked);
|
|
357
379
|
}
|
|
358
380
|
/** Compute what changed since the last session ended. */
|
|
@@ -448,8 +470,16 @@ export function register(server, ctx) {
|
|
|
448
470
|
const activeProject = project ?? priorProject;
|
|
449
471
|
const activeScope = normalizedAgentScope;
|
|
450
472
|
if (activeProject && isValidProjectName(activeProject)) {
|
|
473
|
+
const projectStorePath = (() => {
|
|
474
|
+
try {
|
|
475
|
+
return resolveStoreForProject(ctx, activeProject).phrenPath;
|
|
476
|
+
}
|
|
477
|
+
catch {
|
|
478
|
+
return phrenPath;
|
|
479
|
+
}
|
|
480
|
+
})();
|
|
451
481
|
try {
|
|
452
|
-
const findings = readFindings(
|
|
482
|
+
const findings = readFindings(projectStorePath, activeProject);
|
|
453
483
|
if (findings.ok) {
|
|
454
484
|
const bullets = findings.data
|
|
455
485
|
.filter((entry) => isMemoryScopeVisible(normalizeMemoryScope(entry.scope), activeScope))
|
|
@@ -464,7 +494,7 @@ export function register(server, ctx) {
|
|
|
464
494
|
debugError("session_start findingsRead", err);
|
|
465
495
|
}
|
|
466
496
|
try {
|
|
467
|
-
const tasks = readTasks(
|
|
497
|
+
const tasks = readTasks(projectStorePath, activeProject);
|
|
468
498
|
if (tasks.ok) {
|
|
469
499
|
const activeItems = tasks.data.items.Active
|
|
470
500
|
.filter((entry) => isMemoryScopeVisible(normalizeMemoryScope(entry.scope), activeScope))
|
|
@@ -480,7 +510,7 @@ export function register(server, ctx) {
|
|
|
480
510
|
}
|
|
481
511
|
// Surface extracted preferences/facts for this project
|
|
482
512
|
try {
|
|
483
|
-
const facts = readExtractedFacts(
|
|
513
|
+
const facts = readExtractedFacts(projectStorePath, activeProject).slice(-10);
|
|
484
514
|
if (facts.length > 0) {
|
|
485
515
|
parts.push(`## Preferences (${activeProject})\n${facts.map(f => `- ${f.fact}`).join("\n")}`);
|
|
486
516
|
}
|
|
@@ -489,7 +519,7 @@ export function register(server, ctx) {
|
|
|
489
519
|
debugError("session_start factsRead", err);
|
|
490
520
|
}
|
|
491
521
|
try {
|
|
492
|
-
const checkpoints = listTaskCheckpoints(
|
|
522
|
+
const checkpoints = listTaskCheckpoints(projectStorePath, activeProject).slice(0, 3);
|
|
493
523
|
if (checkpoints.length > 0) {
|
|
494
524
|
const lines = [];
|
|
495
525
|
for (const checkpoint of checkpoints) {
|
|
@@ -600,19 +630,29 @@ export function register(server, ctx) {
|
|
|
600
630
|
writeLastSummary(phrenPath, effectiveSummary, state.sessionId, endedState.project);
|
|
601
631
|
}
|
|
602
632
|
if (endedState.project && isValidProjectName(endedState.project)) {
|
|
633
|
+
const projectStorePath = (() => {
|
|
634
|
+
if (!endedState.project)
|
|
635
|
+
return phrenPath;
|
|
636
|
+
try {
|
|
637
|
+
return resolveStoreForProject(ctx, endedState.project).phrenPath;
|
|
638
|
+
}
|
|
639
|
+
catch {
|
|
640
|
+
return phrenPath;
|
|
641
|
+
}
|
|
642
|
+
})();
|
|
603
643
|
try {
|
|
604
|
-
const trackedActiveTask = getActiveTaskForSession(
|
|
644
|
+
const trackedActiveTask = getActiveTaskForSession(projectStorePath, state.sessionId, endedState.project);
|
|
605
645
|
const activeTask = trackedActiveTask ?? (() => {
|
|
606
|
-
const tasks = readTasks(
|
|
646
|
+
const tasks = readTasks(projectStorePath, endedState.project);
|
|
607
647
|
if (!tasks.ok)
|
|
608
648
|
return null;
|
|
609
649
|
return tasks.data.items.Active[0] ?? null;
|
|
610
650
|
})();
|
|
611
651
|
if (activeTask) {
|
|
612
652
|
const taskId = activeTask.stableId || activeTask.id;
|
|
613
|
-
const projectConfig = readProjectConfig(
|
|
614
|
-
const snapshotRoot = getProjectSourcePath(
|
|
615
|
-
path.join(
|
|
653
|
+
const projectConfig = readProjectConfig(projectStorePath, endedState.project);
|
|
654
|
+
const snapshotRoot = getProjectSourcePath(projectStorePath, endedState.project, projectConfig) ||
|
|
655
|
+
path.join(projectStorePath, endedState.project);
|
|
616
656
|
const { gitStatus, editedFiles } = collectGitStatusSnapshot(snapshotRoot);
|
|
617
657
|
const resumptionHint = extractResumptionHint(effectiveSummary, activeTask.line, activeTask.context || "No prior attempt captured");
|
|
618
658
|
writeTaskCheckpoint(phrenPath, {
|
|
@@ -635,7 +675,7 @@ export function register(server, ctx) {
|
|
|
635
675
|
}
|
|
636
676
|
try {
|
|
637
677
|
const tasksCompleted = Number.isFinite(endedState.tasksCompleted) ? endedState.tasksCompleted : 0;
|
|
638
|
-
if (tasksCompleted > 0 || hasCompletedTasksInSession(phrenPath, state.sessionId, endedState.project)) {
|
|
678
|
+
if (tasksCompleted > 0 || await hasCompletedTasksInSession(phrenPath, state.sessionId, endedState.project)) {
|
|
639
679
|
markImpactEntriesCompletedForSession(phrenPath, state.sessionId, endedState.project);
|
|
640
680
|
}
|
|
641
681
|
}
|
|
@@ -707,7 +747,7 @@ export function register(server, ctx) {
|
|
|
707
747
|
const session = sessions.find(s => s.sessionId === targetSessionId || s.sessionId.startsWith(targetSessionId));
|
|
708
748
|
if (!session)
|
|
709
749
|
return mcpResponse({ ok: false, error: `Session ${targetSessionId} not found.` });
|
|
710
|
-
const artifacts = getSessionArtifacts(phrenPath, session.sessionId, project);
|
|
750
|
+
const artifacts = await getSessionArtifacts(phrenPath, session.sessionId, project);
|
|
711
751
|
const parts = [
|
|
712
752
|
`Session: ${session.sessionId.slice(0, 8)}`,
|
|
713
753
|
`Project: ${session.project ?? "none"}`,
|
package/mcp/dist/tools/tasks.js
CHANGED
|
@@ -109,7 +109,8 @@ export function register(server, ctx) {
|
|
|
109
109
|
return mcpResponse({ ok: false, error: "Provide `project` when looking up a single item." });
|
|
110
110
|
if (!isValidProjectName(project))
|
|
111
111
|
return mcpResponse({ ok: false, error: `Invalid project name: "${project}"` });
|
|
112
|
-
const
|
|
112
|
+
const resolvedPath = resolveStoreForProject(ctx, project).phrenPath;
|
|
113
|
+
const result = readTasks(resolvedPath, project);
|
|
113
114
|
if (!result.ok)
|
|
114
115
|
return mcpResponse({ ok: false, error: result.error });
|
|
115
116
|
const doc = result.data;
|
|
@@ -141,7 +142,8 @@ export function register(server, ctx) {
|
|
|
141
142
|
if (project) {
|
|
142
143
|
if (!isValidProjectName(project))
|
|
143
144
|
return mcpResponse({ ok: false, error: `Invalid project name: "${project}"` });
|
|
144
|
-
const
|
|
145
|
+
const resolvedPath = resolveStoreForProject(ctx, project).phrenPath;
|
|
146
|
+
const result = readTasks(resolvedPath, project);
|
|
145
147
|
if (!result.ok)
|
|
146
148
|
return mcpResponse({ ok: false, error: result.error });
|
|
147
149
|
const doc = result.data;
|
|
@@ -257,21 +259,24 @@ export function register(server, ctx) {
|
|
|
257
259
|
]).describe("The task(s) to complete. Pass a string for one, or an array for bulk."),
|
|
258
260
|
sessionId: z.string().optional().describe("Optional session ID from session_start. Pass this to track per-session task completion metrics."),
|
|
259
261
|
}),
|
|
260
|
-
}, async ({ project, item, sessionId }) => {
|
|
262
|
+
}, async ({ project: projectInput, item, sessionId }) => {
|
|
263
|
+
const resolved = resolveStoreForProject(ctx, projectInput);
|
|
264
|
+
const project = resolved.project;
|
|
265
|
+
const targetPath = resolved.phrenPath;
|
|
261
266
|
if (!isValidProjectName(project))
|
|
262
267
|
return mcpResponse({ ok: false, error: `Invalid project name: "${project}"` });
|
|
263
|
-
const completeTaskDenied = permissionDeniedError(
|
|
268
|
+
const completeTaskDenied = permissionDeniedError(targetPath, "complete_task", project);
|
|
264
269
|
if (completeTaskDenied)
|
|
265
270
|
return mcpResponse({ ok: false, error: completeTaskDenied });
|
|
266
271
|
if (Array.isArray(item)) {
|
|
267
272
|
return withWriteQueue(async () => {
|
|
268
273
|
const resolvedItems = item
|
|
269
274
|
.map((match) => {
|
|
270
|
-
const
|
|
271
|
-
return
|
|
275
|
+
const resolvedItem = resolveTaskItem(targetPath, project, match);
|
|
276
|
+
return resolvedItem.ok ? resolvedItem.data : null;
|
|
272
277
|
})
|
|
273
278
|
.filter((task) => task !== null);
|
|
274
|
-
const result = completeTasksBatch(
|
|
279
|
+
const result = completeTasksBatch(targetPath, project, item);
|
|
275
280
|
if (!result.ok)
|
|
276
281
|
return mcpResponse({ ok: false, error: result.error });
|
|
277
282
|
const { completed, errors } = result.data;
|
|
@@ -280,7 +285,7 @@ export function register(server, ctx) {
|
|
|
280
285
|
for (const task of resolvedItems) {
|
|
281
286
|
if (!completedSet.has(task.line))
|
|
282
287
|
continue;
|
|
283
|
-
clearTaskCheckpoint(
|
|
288
|
+
clearTaskCheckpoint(targetPath, {
|
|
284
289
|
project,
|
|
285
290
|
taskId: task.stableId ?? task.id,
|
|
286
291
|
stableId: task.stableId,
|
|
@@ -288,20 +293,20 @@ export function register(server, ctx) {
|
|
|
288
293
|
taskLine: task.line,
|
|
289
294
|
});
|
|
290
295
|
}
|
|
291
|
-
incrementSessionTasksCompleted(
|
|
296
|
+
incrementSessionTasksCompleted(targetPath, completed.length, sessionId, project);
|
|
292
297
|
}
|
|
293
298
|
if (completed.length > 0)
|
|
294
|
-
refreshTaskIndex(updateFileInIndex,
|
|
299
|
+
refreshTaskIndex(updateFileInIndex, targetPath, project);
|
|
295
300
|
return mcpResponse({ ok: completed.length > 0, ...(completed.length === 0 ? { error: `No tasks completed: ${errors.join("; ")}` } : {}), message: `Completed ${completed.length}/${item.length} items`, data: { project, completed, errors } });
|
|
296
301
|
});
|
|
297
302
|
}
|
|
298
303
|
return withWriteQueue(async () => {
|
|
299
|
-
const before = resolveTaskItem(
|
|
300
|
-
const result = completeTaskStore(
|
|
304
|
+
const before = resolveTaskItem(targetPath, project, item);
|
|
305
|
+
const result = completeTaskStore(targetPath, project, item);
|
|
301
306
|
if (!result.ok)
|
|
302
307
|
return mcpResponse({ ok: false, error: result.error });
|
|
303
308
|
if (before.ok) {
|
|
304
|
-
clearTaskCheckpoint(
|
|
309
|
+
clearTaskCheckpoint(targetPath, {
|
|
305
310
|
project,
|
|
306
311
|
taskId: before.data.stableId ?? before.data.id,
|
|
307
312
|
stableId: before.data.stableId,
|
|
@@ -309,8 +314,8 @@ export function register(server, ctx) {
|
|
|
309
314
|
taskLine: before.data.line,
|
|
310
315
|
});
|
|
311
316
|
}
|
|
312
|
-
incrementSessionTasksCompleted(
|
|
313
|
-
refreshTaskIndex(updateFileInIndex,
|
|
317
|
+
incrementSessionTasksCompleted(targetPath, 1, sessionId, project);
|
|
318
|
+
refreshTaskIndex(updateFileInIndex, targetPath, project);
|
|
314
319
|
return mcpResponse({ ok: true, message: result.data, data: { project, item } });
|
|
315
320
|
});
|
|
316
321
|
});
|
|
@@ -324,28 +329,31 @@ export function register(server, ctx) {
|
|
|
324
329
|
z.array(z.string()).describe("List of partial item texts or IDs to remove."),
|
|
325
330
|
]).describe("The task(s) to remove. Pass a string for one, or an array for bulk."),
|
|
326
331
|
}),
|
|
327
|
-
}, async ({ project, item }) => {
|
|
332
|
+
}, async ({ project: projectInput, item }) => {
|
|
333
|
+
const resolved = resolveStoreForProject(ctx, projectInput);
|
|
334
|
+
const project = resolved.project;
|
|
335
|
+
const targetPath = resolved.phrenPath;
|
|
328
336
|
if (!isValidProjectName(project))
|
|
329
337
|
return mcpResponse({ ok: false, error: `Invalid project name: "${project}"` });
|
|
330
|
-
const removeTaskDenied = permissionDeniedError(
|
|
338
|
+
const removeTaskDenied = permissionDeniedError(targetPath, "remove_task", project);
|
|
331
339
|
if (removeTaskDenied)
|
|
332
340
|
return mcpResponse({ ok: false, error: removeTaskDenied });
|
|
333
341
|
if (Array.isArray(item)) {
|
|
334
342
|
return withWriteQueue(async () => {
|
|
335
|
-
const result = removeTasksBatch(
|
|
343
|
+
const result = removeTasksBatch(targetPath, project, item);
|
|
336
344
|
if (!result.ok)
|
|
337
345
|
return mcpResponse({ ok: false, error: result.error });
|
|
338
346
|
const { removed, errors } = result.data;
|
|
339
347
|
if (removed.length > 0)
|
|
340
|
-
refreshTaskIndex(updateFileInIndex,
|
|
348
|
+
refreshTaskIndex(updateFileInIndex, targetPath, project);
|
|
341
349
|
return mcpResponse({ ok: removed.length > 0, ...(removed.length === 0 ? { error: `No tasks removed: ${errors.join("; ")}` } : {}), message: `Removed ${removed.length}/${item.length} items`, data: { project, removed, errors } });
|
|
342
350
|
});
|
|
343
351
|
}
|
|
344
352
|
return withWriteQueue(async () => {
|
|
345
|
-
const result = removeTaskStore(
|
|
353
|
+
const result = removeTaskStore(targetPath, project, item);
|
|
346
354
|
if (!result.ok)
|
|
347
355
|
return mcpResponse({ ok: false, error: result.error });
|
|
348
|
-
refreshTaskIndex(updateFileInIndex,
|
|
356
|
+
refreshTaskIndex(updateFileInIndex, targetPath, project);
|
|
349
357
|
return mcpResponse({ ok: true, message: result.data, data: { project, item } });
|
|
350
358
|
});
|
|
351
359
|
});
|
|
@@ -373,10 +381,13 @@ export function register(server, ctx) {
|
|
|
373
381
|
work_next: z.boolean().optional().describe("If true, pick the highest-priority Queue item and move it to Active. Ignores item param."),
|
|
374
382
|
}).describe("Fields to update. All are optional."),
|
|
375
383
|
}),
|
|
376
|
-
}, async ({ project, item, updates }) => {
|
|
384
|
+
}, async ({ project: projectInput, item, updates }) => {
|
|
385
|
+
const resolved = resolveStoreForProject(ctx, projectInput);
|
|
386
|
+
const project = resolved.project;
|
|
387
|
+
const targetPath = resolved.phrenPath;
|
|
377
388
|
if (!isValidProjectName(project))
|
|
378
389
|
return mcpResponse({ ok: false, error: `Invalid project name: "${project}"` });
|
|
379
|
-
const updateTaskDenied = permissionDeniedError(
|
|
390
|
+
const updateTaskDenied = permissionDeniedError(targetPath, "update_task", project);
|
|
380
391
|
if (updateTaskDenied)
|
|
381
392
|
return mcpResponse({ ok: false, error: updateTaskDenied });
|
|
382
393
|
// Runtime validation: item is required unless work_next is true
|
|
@@ -417,26 +428,26 @@ export function register(server, ctx) {
|
|
|
417
428
|
return withWriteQueue(async () => {
|
|
418
429
|
// Handle work_next: pick highest-priority Queue item, move to Active
|
|
419
430
|
if (updates.work_next) {
|
|
420
|
-
const result = workNextTask(
|
|
431
|
+
const result = workNextTask(targetPath, project);
|
|
421
432
|
if (!result.ok)
|
|
422
433
|
return mcpResponse({ ok: false, error: result.error });
|
|
423
|
-
refreshTaskIndex(updateFileInIndex,
|
|
434
|
+
refreshTaskIndex(updateFileInIndex, targetPath, project);
|
|
424
435
|
return mcpResponse({ ok: true, message: result.data, data: { project } });
|
|
425
436
|
}
|
|
426
437
|
// Handle pin
|
|
427
438
|
if (updates.pin) {
|
|
428
|
-
const result = pinTask(
|
|
439
|
+
const result = pinTask(targetPath, project, item);
|
|
429
440
|
if (!result.ok)
|
|
430
441
|
return mcpResponse({ ok: false, error: result.error });
|
|
431
|
-
refreshTaskIndex(updateFileInIndex,
|
|
442
|
+
refreshTaskIndex(updateFileInIndex, targetPath, project);
|
|
432
443
|
return mcpResponse({ ok: true, message: result.data, data: { project, item } });
|
|
433
444
|
}
|
|
434
445
|
// Handle promote (clear speculative flag)
|
|
435
446
|
if (updates.promote) {
|
|
436
|
-
const result = promoteTask(
|
|
447
|
+
const result = promoteTask(targetPath, project, item, updates.move_to_active ?? false);
|
|
437
448
|
if (!result.ok)
|
|
438
449
|
return mcpResponse({ ok: false, error: result.error });
|
|
439
|
-
refreshTaskIndex(updateFileInIndex,
|
|
450
|
+
refreshTaskIndex(updateFileInIndex, targetPath, project);
|
|
440
451
|
return mcpResponse({
|
|
441
452
|
ok: true,
|
|
442
453
|
message: `Promoted task "${result.data.line}" in ${project}${updates.move_to_active ? " (moved to Active)" : ""}.`,
|
|
@@ -444,10 +455,10 @@ export function register(server, ctx) {
|
|
|
444
455
|
});
|
|
445
456
|
}
|
|
446
457
|
if (updates.create_issue) {
|
|
447
|
-
const
|
|
448
|
-
if (!
|
|
449
|
-
return mcpResponse({ ok: false, error:
|
|
450
|
-
const repo = resolveProjectGithubRepo(
|
|
458
|
+
const resolvedItem = resolveTaskItem(targetPath, project, item);
|
|
459
|
+
if (!resolvedItem.ok)
|
|
460
|
+
return mcpResponse({ ok: false, error: resolvedItem.error });
|
|
461
|
+
const repo = resolveProjectGithubRepo(targetPath, project);
|
|
451
462
|
if (!repo) {
|
|
452
463
|
return mcpResponse({
|
|
453
464
|
ok: false,
|
|
@@ -456,18 +467,18 @@ export function register(server, ctx) {
|
|
|
456
467
|
}
|
|
457
468
|
const created = createGithubIssueForTask({
|
|
458
469
|
repo,
|
|
459
|
-
title:
|
|
460
|
-
body: buildTaskIssueBody(project,
|
|
470
|
+
title: resolvedItem.data.line.replace(/\s*\[(high|medium|low)\]\s*$/i, "").trim(),
|
|
471
|
+
body: buildTaskIssueBody(project, resolvedItem.data),
|
|
461
472
|
});
|
|
462
473
|
if (!created.ok)
|
|
463
474
|
return mcpResponse({ ok: false, error: created.error, errorCode: created.code });
|
|
464
|
-
const linked = linkTaskIssue(
|
|
475
|
+
const linked = linkTaskIssue(targetPath, project, item, {
|
|
465
476
|
github_issue: created.data.issueNumber,
|
|
466
477
|
github_url: created.data.url,
|
|
467
478
|
});
|
|
468
479
|
if (!linked.ok)
|
|
469
480
|
return mcpResponse({ ok: false, error: linked.error, errorCode: linked.code });
|
|
470
|
-
refreshTaskIndex(updateFileInIndex,
|
|
481
|
+
refreshTaskIndex(updateFileInIndex, targetPath, project);
|
|
471
482
|
return mcpResponse({
|
|
472
483
|
ok: true,
|
|
473
484
|
message: `Created GitHub issue ${created.data.issueNumber ? `#${created.data.issueNumber}` : created.data.url} for ${project} task.`,
|
|
@@ -487,14 +498,14 @@ export function register(server, ctx) {
|
|
|
487
498
|
if (updates.unlink_github && (updates.github_issue !== undefined || updates.github_url)) {
|
|
488
499
|
return mcpResponse({ ok: false, error: "Use either unlink_github=true or github_issue/github_url, not both." });
|
|
489
500
|
}
|
|
490
|
-
const result = linkTaskIssue(
|
|
501
|
+
const result = linkTaskIssue(targetPath, project, item, {
|
|
491
502
|
github_issue: updates.github_issue,
|
|
492
503
|
github_url: updates.github_url,
|
|
493
504
|
unlink: updates.unlink_github ?? false,
|
|
494
505
|
});
|
|
495
506
|
if (!result.ok)
|
|
496
507
|
return mcpResponse({ ok: false, error: result.error, errorCode: result.code });
|
|
497
|
-
refreshTaskIndex(updateFileInIndex,
|
|
508
|
+
refreshTaskIndex(updateFileInIndex, targetPath, project);
|
|
498
509
|
return mcpResponse({
|
|
499
510
|
ok: true,
|
|
500
511
|
message: updates.unlink_github
|
|
@@ -510,10 +521,10 @@ export function register(server, ctx) {
|
|
|
510
521
|
});
|
|
511
522
|
}
|
|
512
523
|
// Standard update path
|
|
513
|
-
const result = updateTaskStore(
|
|
524
|
+
const result = updateTaskStore(targetPath, project, item, updates);
|
|
514
525
|
if (!result.ok)
|
|
515
526
|
return mcpResponse({ ok: false, error: result.error });
|
|
516
|
-
refreshTaskIndex(updateFileInIndex,
|
|
527
|
+
refreshTaskIndex(updateFileInIndex, targetPath, project);
|
|
517
528
|
return mcpResponse({ ok: true, message: result.data, data: { project, item, updates } });
|
|
518
529
|
});
|
|
519
530
|
});
|
|
@@ -525,15 +536,18 @@ export function register(server, ctx) {
|
|
|
525
536
|
keep: z.number().optional().describe("Number of recent Done items to keep. Default 30."),
|
|
526
537
|
dry_run: z.boolean().optional().describe("If true, preview changes without writing."),
|
|
527
538
|
}),
|
|
528
|
-
}, async ({ project, keep, dry_run }) => {
|
|
539
|
+
}, async ({ project: projectInput, keep, dry_run }) => {
|
|
540
|
+
const resolved = resolveStoreForProject(ctx, projectInput);
|
|
541
|
+
const project = resolved.project;
|
|
542
|
+
const targetPath = resolved.phrenPath;
|
|
529
543
|
if (!isValidProjectName(project))
|
|
530
544
|
return mcpResponse({ ok: false, error: `Invalid project name: "${project}"` });
|
|
531
545
|
return withWriteQueue(async () => {
|
|
532
|
-
const result = tidyDoneTasks(
|
|
546
|
+
const result = tidyDoneTasks(targetPath, project, keep ?? 30, dry_run ?? false);
|
|
533
547
|
if (!result.ok)
|
|
534
548
|
return mcpResponse({ ok: false, error: result.error });
|
|
535
549
|
if (!dry_run)
|
|
536
|
-
refreshTaskIndex(updateFileInIndex,
|
|
550
|
+
refreshTaskIndex(updateFileInIndex, targetPath, project);
|
|
537
551
|
return mcpResponse({ ok: true, message: result.data, data: { project, keep: keep ?? 30, dryRun: dry_run ?? false } });
|
|
538
552
|
});
|
|
539
553
|
});
|