@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.
@@ -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 = getProjectDirs(store.path);
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
- let health = null;
694
+ // Get last sync time from git log
695
+ let lastSync = null;
695
696
  try {
696
- const healthPath = path.join(store.path, ".runtime", "health.json");
697
- if (fs.existsSync(healthPath)) {
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: health,
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
- for (const proj of targetProjects) {
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(phrenPath, proj);
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(phrenPath, proj);
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(phrenPath, activeProject);
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(phrenPath, activeProject);
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(phrenPath, activeProject).slice(-10);
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(phrenPath, activeProject).slice(0, 3);
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(phrenPath, state.sessionId, endedState.project);
644
+ const trackedActiveTask = getActiveTaskForSession(projectStorePath, state.sessionId, endedState.project);
605
645
  const activeTask = trackedActiveTask ?? (() => {
606
- const tasks = readTasks(phrenPath, endedState.project);
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(phrenPath, endedState.project);
614
- const snapshotRoot = getProjectSourcePath(phrenPath, endedState.project, projectConfig) ||
615
- path.join(phrenPath, endedState.project);
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"}`,
@@ -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 result = readTasks(phrenPath, project);
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 result = readTasks(phrenPath, project);
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(phrenPath, "complete_task", project);
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 resolved = resolveTaskItem(phrenPath, project, match);
271
- return resolved.ok ? resolved.data : null;
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(phrenPath, project, item);
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(phrenPath, {
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(phrenPath, completed.length, sessionId, project);
296
+ incrementSessionTasksCompleted(targetPath, completed.length, sessionId, project);
292
297
  }
293
298
  if (completed.length > 0)
294
- refreshTaskIndex(updateFileInIndex, phrenPath, project);
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(phrenPath, project, item);
300
- const result = completeTaskStore(phrenPath, project, item);
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(phrenPath, {
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(phrenPath, 1, sessionId, project);
313
- refreshTaskIndex(updateFileInIndex, phrenPath, project);
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(phrenPath, "remove_task", project);
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(phrenPath, project, item);
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, phrenPath, project);
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(phrenPath, project, item);
353
+ const result = removeTaskStore(targetPath, project, item);
346
354
  if (!result.ok)
347
355
  return mcpResponse({ ok: false, error: result.error });
348
- refreshTaskIndex(updateFileInIndex, phrenPath, project);
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(phrenPath, "update_task", project);
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(phrenPath, project);
431
+ const result = workNextTask(targetPath, project);
421
432
  if (!result.ok)
422
433
  return mcpResponse({ ok: false, error: result.error });
423
- refreshTaskIndex(updateFileInIndex, phrenPath, project);
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(phrenPath, project, item);
439
+ const result = pinTask(targetPath, project, item);
429
440
  if (!result.ok)
430
441
  return mcpResponse({ ok: false, error: result.error });
431
- refreshTaskIndex(updateFileInIndex, phrenPath, project);
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(phrenPath, project, item, updates.move_to_active ?? false);
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, phrenPath, project);
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 resolved = resolveTaskItem(phrenPath, project, item);
448
- if (!resolved.ok)
449
- return mcpResponse({ ok: false, error: resolved.error });
450
- const repo = resolveProjectGithubRepo(phrenPath, project);
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: resolved.data.line.replace(/\s*\[(high|medium|low)\]\s*$/i, "").trim(),
460
- body: buildTaskIssueBody(project, resolved.data),
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(phrenPath, project, item, {
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, phrenPath, project);
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(phrenPath, project, item, {
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, phrenPath, project);
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(phrenPath, project, item, updates);
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, phrenPath, project);
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(phrenPath, project, keep ?? 30, dry_run ?? false);
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, phrenPath, project);
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
  });