@phren/cli 0.0.42 → 0.0.44

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.
Files changed (41) hide show
  1. package/mcp/dist/cli/actions.js +24 -1
  2. package/mcp/dist/cli/cli.js +6 -1
  3. package/mcp/dist/cli/hooks-session.js +8 -8
  4. package/mcp/dist/cli/namespaces.js +3 -4
  5. package/mcp/dist/cli/team.js +301 -0
  6. package/mcp/dist/cli-hooks-session-handlers.js +26 -8
  7. package/mcp/dist/cli-hooks-stop.js +35 -5
  8. package/mcp/dist/content/dedup.js +5 -2
  9. package/mcp/dist/entrypoint.js +11 -3
  10. package/mcp/dist/finding/context.js +3 -2
  11. package/mcp/dist/init/config.js +1 -1
  12. package/mcp/dist/init/init-configure.js +1 -1
  13. package/mcp/dist/init/init-hooks-mode.js +1 -1
  14. package/mcp/dist/init/init-mcp-mode.js +2 -2
  15. package/mcp/dist/init/init-walkthrough.js +9 -9
  16. package/mcp/dist/init/init.js +8 -8
  17. package/mcp/dist/init/setup.js +46 -1
  18. package/mcp/dist/init-fresh.js +4 -4
  19. package/mcp/dist/init-hooks.js +1 -1
  20. package/mcp/dist/init-modes.js +3 -3
  21. package/mcp/dist/init-update.js +3 -3
  22. package/mcp/dist/init-walkthrough.js +9 -9
  23. package/mcp/dist/link/doctor.js +1 -1
  24. package/mcp/dist/link/link.js +1 -1
  25. package/mcp/dist/phren-paths.js +2 -2
  26. package/mcp/dist/profile-store.js +2 -2
  27. package/mcp/dist/shared/retrieval.js +9 -3
  28. package/mcp/dist/status.js +1 -1
  29. package/mcp/dist/store-registry.js +15 -1
  30. package/mcp/dist/tools/finding.js +114 -12
  31. package/mcp/dist/tools/memory.js +49 -4
  32. package/mcp/dist/tools/search.js +10 -1
  33. package/mcp/dist/tools/session.js +10 -4
  34. package/mcp/dist/tools/tasks.js +60 -1
  35. package/mcp/dist/tools/types.js +10 -0
  36. package/package.json +1 -1
  37. package/skills/sync/SKILL.md +1 -1
  38. package/starter/README.md +6 -6
  39. package/starter/machines.yaml +1 -1
  40. package/starter/my-first-project/tasks.md +1 -1
  41. package/starter/templates/README.md +1 -1
@@ -89,7 +89,7 @@ export function configureHooksIfEnabled(phrenPath, hooksEnabled, verb) {
89
89
  }
90
90
  }
91
91
  else {
92
- log(` Hooks are disabled by preference (run: npx phren hooks-mode on)`);
92
+ log(` Hooks are disabled by preference (run: phren hooks-mode on)`);
93
93
  }
94
94
  // Install phren CLI wrapper at ~/.local/bin/phren so the bare command works
95
95
  const wrapperInstalled = installPhrenCliWrapper(phrenPath);
@@ -14,7 +14,7 @@ export async function runHooksMode(modeArg) {
14
14
  if (!normalizedArg || normalizedArg === "status") {
15
15
  const current = getHooksEnabledPreference(phrenPath);
16
16
  log(`Hooks mode: ${current ? "on (active)" : "off (disabled)"}`);
17
- log(`Change mode: npx phren hooks-mode on|off`);
17
+ log(`Change mode: phren hooks-mode on|off`);
18
18
  return;
19
19
  }
20
20
  const mode = parseMcpMode(normalizedArg);
@@ -15,8 +15,8 @@ export async function runMcpMode(modeArg) {
15
15
  const hooks = getHooksEnabledPreference(phrenPath);
16
16
  log(`MCP mode: ${current ? "on (recommended)" : "off (hooks-only fallback)"}`);
17
17
  log(`Hooks mode: ${hooks ? "on (active)" : "off (disabled)"}`);
18
- log(`Change mode: npx phren mcp-mode on|off`);
19
- log(`Hooks toggle: npx phren hooks-mode on|off`);
18
+ log(`Change mode: phren mcp-mode on|off`);
19
+ log(`Hooks toggle: phren hooks-mode on|off`);
20
20
  return;
21
21
  }
22
22
  const mode = parseMcpMode(normalizedArg);
@@ -280,7 +280,7 @@ export async function runWalkthrough(phrenPath, options) {
280
280
  log(" phren-managed: Phren may mirror CLAUDE.md / AGENTS.md into the repo");
281
281
  log(" detached: Phren keeps its own docs but does not write into the repo");
282
282
  log(" repo-managed: keep the repo's existing CLAUDE/AGENTS files as canonical");
283
- log(" Change later: npx phren config project-ownership <mode>");
283
+ log(" Change later: phren config project-ownership <mode>");
284
284
  const projectOwnershipDefault = await prompts.select("Default project ownership", [
285
285
  { value: "detached", name: "detached (default)" },
286
286
  { value: "phren-managed", name: "phren-managed" },
@@ -291,7 +291,7 @@ export async function runWalkthrough(phrenPath, options) {
291
291
  log("directly: search memory, manage tasks, save findings, etc.");
292
292
  log(" Recommended for: Claude Code, Cursor, Copilot CLI, Codex");
293
293
  log(" Alternative: hooks-only mode (read-only context injection, any agent)");
294
- log(" Change later: npx phren mcp-mode on|off");
294
+ log(" Change later: phren mcp-mode on|off");
295
295
  const mcp = (await prompts.confirm("Enable MCP?", true)) ? "on" : "off";
296
296
  printSection("Hooks");
297
297
  log("Hooks run shell commands at session start, prompt submit, and session end.");
@@ -299,7 +299,7 @@ export async function runWalkthrough(phrenPath, options) {
299
299
  log(" - UserPromptSubmit: searches phren and injects relevant context");
300
300
  log(" - Stop: commits and pushes any new findings after each response");
301
301
  log(" What they touch: ~/.claude/settings.json (hooks section only)");
302
- log(" Change later: npx phren hooks-mode on|off");
302
+ log(" Change later: phren hooks-mode on|off");
303
303
  const hooks = (await prompts.confirm("Enable hooks?", true)) ? "on" : "off";
304
304
  printSection("Semantic Search (Optional)");
305
305
  log("Phren can use a local embedding model for semantic (fuzzy) search via Ollama.");
@@ -347,7 +347,7 @@ export async function runWalkthrough(phrenPath, options) {
347
347
  let findingsProactivity = "high";
348
348
  if (autoCaptureEnabled) {
349
349
  log(" Findings capture level controls how eager phren is to save lessons automatically.");
350
- log(" Change later: npx phren config proactivity.findings <high|medium|low>");
350
+ log(" Change later: phren config proactivity.findings <high|medium|low>");
351
351
  findingsProactivity = await prompts.select("Findings capture level", [
352
352
  { value: "high", name: "high (recommended)" },
353
353
  { value: "medium", name: "medium" },
@@ -363,7 +363,7 @@ export async function runWalkthrough(phrenPath, options) {
363
363
  log(" suggest: proposes tasks but waits for approval before writing");
364
364
  log(" manual: tasks are fully manual — you add them yourself");
365
365
  log(" off: never touch tasks automatically");
366
- log(" Change later: npx phren config workflow set --taskMode=<mode>");
366
+ log(" Change later: phren config workflow set --taskMode=<mode>");
367
367
  const taskMode = await prompts.select("Task mode", [
368
368
  { value: "auto", name: "auto (recommended)" },
369
369
  { value: "suggest", name: "suggest" },
@@ -376,7 +376,7 @@ export async function runWalkthrough(phrenPath, options) {
376
376
  log(" high (recommended): captures tasks as they come up naturally");
377
377
  log(" medium: only when you explicitly mention a task");
378
378
  log(" low: minimal auto-capture");
379
- log(" Change later: npx phren config proactivity.tasks <high|medium|low>");
379
+ log(" Change later: phren config proactivity.tasks <high|medium|low>");
380
380
  taskProactivity = await prompts.select("Task proactivity", [
381
381
  { value: "high", name: "high (recommended)" },
382
382
  { value: "medium", name: "medium" },
@@ -387,7 +387,7 @@ export async function runWalkthrough(phrenPath, options) {
387
387
  log("Choose how strict review gates should be for risky or low-confidence writes.");
388
388
  log(" lowConfidenceThreshold: confidence cutoff used to mark writes as risky");
389
389
  log(" riskySections: sections always treated as risky");
390
- log(" Change later: npx phren config workflow set --lowConfidenceThreshold=0.7 --riskySections=Stale,Conflicts");
390
+ log(" Change later: phren config workflow set --lowConfidenceThreshold=0.7 --riskySections=Stale,Conflicts");
391
391
  const thresholdAnswer = await prompts.input("Low-confidence threshold [0.0-1.0]", "0.7");
392
392
  const lowConfidenceThreshold = parseLowConfidenceThreshold(thresholdAnswer, 0.7);
393
393
  const riskySectionsAnswer = await prompts.input("Risky sections [Review,Stale,Conflicts]", "Stale,Conflicts");
@@ -437,7 +437,7 @@ export async function runWalkthrough(phrenPath, options) {
437
437
  log(" conservative — decisions and pitfalls only");
438
438
  log(" balanced — non-obvious patterns, decisions, pitfalls, bugs (recommended)");
439
439
  log(" aggressive — everything worth remembering, err on the side of capturing");
440
- log(" Change later: npx phren config finding-sensitivity <level>");
440
+ log(" Change later: phren config finding-sensitivity <level>");
441
441
  const findingSensitivity = await prompts.select("Finding sensitivity", [
442
442
  { value: "balanced", name: "balanced (recommended)" },
443
443
  { value: "conservative", name: "conservative" },
@@ -466,7 +466,7 @@ export async function runWalkthrough(phrenPath, options) {
466
466
  bootstrapCurrentProject = await prompts.confirm("Add this project to phren now?", true);
467
467
  if (!bootstrapCurrentProject) {
468
468
  bootstrapCurrentProject = false;
469
- log(style.warning(` Skipped. Later: cd ${detectedProject} && npx phren add`));
469
+ log(style.warning(` Skipped. Later: cd ${detectedProject} && phren add`));
470
470
  }
471
471
  else {
472
472
  bootstrapOwnership = await prompts.select("Ownership for detected project", [
@@ -260,7 +260,7 @@ export async function runInit(opts = {}) {
260
260
  shouldBootstrapCurrentProject = await prompts.confirm("Add this project to phren now?", true);
261
261
  if (!shouldBootstrapCurrentProject) {
262
262
  shouldBootstrapCurrentProject = false;
263
- log(style.warning(` Skipped. Later: cd ${pendingBootstrap.path} && npx phren add`));
263
+ log(style.warning(` Skipped. Later: cd ${pendingBootstrap.path} && phren add`));
264
264
  }
265
265
  else {
266
266
  bootstrapOwnership = await prompts.select("Ownership for detected project", [
@@ -352,7 +352,7 @@ export async function runInit(opts = {}) {
352
352
  const previousVersion = prefs.installedVersion;
353
353
  if (isVersionNewer(VERSION, previousVersion)) {
354
354
  log(`\n Starter template update available: v${previousVersion} -> v${VERSION}`);
355
- log(` Run \`npx phren init --apply-starter-update\` to refresh global/CLAUDE.md and global skills.`);
355
+ log(` Run \`phren init --apply-starter-update\` to refresh global/CLAUDE.md and global skills.`);
356
356
  }
357
357
  if (opts.applyStarterUpdate) {
358
358
  const updated = applyStarterTemplateUpdates(phrenPath);
@@ -395,8 +395,8 @@ export async function runInit(opts = {}) {
395
395
  log(`\n\x1b[95m◆\x1b[0m phren updated successfully`);
396
396
  log(`\nNext steps:`);
397
397
  log(` 1. Start a new Claude session in your project directory — phren injects context automatically`);
398
- log(` 2. Run \`npx phren doctor\` to verify everything is wired correctly`);
399
- log(` 3. Change defaults anytime: \`npx phren config project-ownership\`, \`npx phren config workflow\`, \`npx phren config proactivity.findings\`, \`npx phren config proactivity.tasks\``);
398
+ log(` 2. Run \`phren doctor\` to verify everything is wired correctly`);
399
+ log(` 3. Change defaults anytime: \`phren config project-ownership\`, \`phren config workflow\`, \`phren config proactivity.findings\`, \`phren config proactivity.tasks\``);
400
400
  log(` 4. After your first week, run phren-discover to surface gaps in your project knowledge`);
401
401
  log(` 5. After working across projects, run phren-consolidate to find cross-project patterns`);
402
402
  log(``);
@@ -581,8 +581,8 @@ export async function runInit(opts = {}) {
581
581
  log(`\nNext steps:`);
582
582
  let step = 1;
583
583
  log(` ${step++}. Start a new Claude session in your project directory — phren injects context automatically`);
584
- log(` ${step++}. Run \`npx phren doctor\` to verify everything is wired correctly`);
585
- log(` ${step++}. Change defaults anytime: \`npx phren config project-ownership\`, \`npx phren config workflow\`, \`npx phren config proactivity.findings\`, \`npx phren config proactivity.tasks\``);
584
+ log(` ${step++}. Run \`phren doctor\` to verify everything is wired correctly`);
585
+ log(` ${step++}. Change defaults anytime: \`phren config project-ownership\`, \`phren config workflow\`, \`phren config proactivity.findings\`, \`phren config proactivity.tasks\``);
586
586
  const gh = opts._walkthroughGithub;
587
587
  if (gh) {
588
588
  const remote = gh.username
@@ -607,9 +607,9 @@ export async function runInit(opts = {}) {
607
607
  log(` git remote add origin git@github.com:YOUR_USERNAME/my-phren.git`);
608
608
  log(` git push -u origin main`);
609
609
  }
610
- log(` ${step++}. Add more projects: cd ~/your-project && npx phren add`);
610
+ log(` ${step++}. Add more projects: cd ~/your-project && phren add`);
611
611
  if (!mcpEnabled) {
612
- log(` ${step++}. Turn MCP on: npx phren mcp-mode on`);
612
+ log(` ${step++}. Turn MCP on: phren mcp-mode on`);
613
613
  }
614
614
  log(` ${step++}. After your first week, run phren-discover to surface gaps in your project knowledge`);
615
615
  log(` ${step++}. After working across projects, run phren-consolidate to find cross-project patterns`);
@@ -278,6 +278,7 @@ export function repairPreexistingInstall(phrenPath) {
278
278
  const profileRepair = pruneLegacySampleProjectsFromProfiles(phrenPath);
279
279
  const preferredHome = resolvePreferredHomeDir(phrenPath);
280
280
  const createdSkillArtifacts = ensureGeneratedSkillArtifacts(phrenPath, preferredHome);
281
+ const repairedGlobalSymlink = repairGlobalClaudeSymlink(phrenPath);
281
282
  return {
282
283
  profileFilesUpdated: profileRepair.filesUpdated,
283
284
  removedLegacyProjects: profileRepair.removed,
@@ -287,8 +288,46 @@ export function repairPreexistingInstall(phrenPath) {
287
288
  createdRuntimeAssets,
288
289
  createdFeatureDefaults,
289
290
  createdSkillArtifacts,
291
+ repairedGlobalSymlink,
290
292
  };
291
293
  }
294
+ /** Re-create ~/.claude/CLAUDE.md symlink if the source exists but the link is missing/broken. */
295
+ function repairGlobalClaudeSymlink(phrenPath) {
296
+ const src = path.join(phrenPath, "global", "CLAUDE.md");
297
+ if (!fs.existsSync(src))
298
+ return false;
299
+ const dest = homePath(".claude", "CLAUDE.md");
300
+ try {
301
+ const stat = fs.lstatSync(dest);
302
+ if (stat.isSymbolicLink()) {
303
+ const target = path.resolve(path.dirname(dest), fs.readlinkSync(dest));
304
+ if (target === path.resolve(src))
305
+ return false; // already correct
306
+ // Stale symlink pointing elsewhere — managed by phren, safe to replace
307
+ if (target.includes(".phren"))
308
+ fs.unlinkSync(dest);
309
+ else
310
+ return false; // not ours, don't touch
311
+ }
312
+ else {
313
+ return false; // regular file exists, don't clobber
314
+ }
315
+ }
316
+ catch (err) {
317
+ if (err.code !== "ENOENT")
318
+ return false;
319
+ }
320
+ try {
321
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
322
+ fs.symlinkSync(src, dest);
323
+ debugLog(`repaired global CLAUDE.md symlink: ${dest} -> ${src}`);
324
+ return true;
325
+ }
326
+ catch (err) {
327
+ debugLog(`failed to repair global CLAUDE.md symlink: ${errorMessage(err)}`);
328
+ return false;
329
+ }
330
+ }
292
331
  function isExpectedVerifyFailure(phrenPath, check) {
293
332
  if (check.ok)
294
333
  return false;
@@ -1089,9 +1128,15 @@ export function updateMachinesYaml(phrenPath, machine, profile) {
1089
1128
  */
1090
1129
  export function detectProjectDir(dir, phrenPath) {
1091
1130
  const home = os.homedir();
1131
+ const tmpRoot = path.resolve(os.tmpdir());
1092
1132
  const resolvedPhrenPath = path.resolve(phrenPath);
1093
1133
  let current = path.resolve(dir);
1094
1134
  while (true) {
1135
+ // Never treat the shared OS temp root itself as a project. Tools may drop
1136
+ // global instruction files there, which would otherwise hijack detection
1137
+ // for arbitrary temp subdirectories.
1138
+ if (current === tmpRoot)
1139
+ return null;
1095
1140
  if (current === home || current === resolvedPhrenPath)
1096
1141
  return null;
1097
1142
  if (current.startsWith(resolvedPhrenPath + path.sep))
@@ -1306,7 +1351,7 @@ export function runPostInitVerify(phrenPath) {
1306
1351
  ok: true, // always pass — wrapper is optional (global install or npx work too)
1307
1352
  detail: cliWrapperOk
1308
1353
  ? `CLI wrapper exists: ${cliWrapperPath}`
1309
- : `CLI wrapper not found (optional — use 'npm i -g @phren/cli' or 'npx @phren/cli' instead)`,
1354
+ : `CLI wrapper not found (optional — use 'npx @phren/cli' instead)`,
1310
1355
  });
1311
1356
  const ok = checks.every((c) => c.ok);
1312
1357
  return { ok, checks };
@@ -197,8 +197,8 @@ export async function runFreshInstall(phrenPath, opts, params) {
197
197
  log(`\nNext steps:`);
198
198
  let step = 1;
199
199
  log(` ${step++}. Start a new Claude session in your project directory — phren injects context automatically`);
200
- log(` ${step++}. Run \`npx phren doctor\` to verify everything is wired correctly`);
201
- log(` ${step++}. Change defaults anytime: \`npx phren config project-ownership\`, \`npx phren config workflow\`, \`npx phren config proactivity.findings\`, \`npx phren config proactivity.tasks\``);
200
+ log(` ${step++}. Run \`phren doctor\` to verify everything is wired correctly`);
201
+ log(` ${step++}. Change defaults anytime: \`phren config project-ownership\`, \`phren config workflow\`, \`phren config proactivity.findings\`, \`phren config proactivity.tasks\``);
202
202
  const gh = opts._walkthroughGithub;
203
203
  if (gh) {
204
204
  const remote = gh.username
@@ -223,9 +223,9 @@ export async function runFreshInstall(phrenPath, opts, params) {
223
223
  log(` git remote add origin git@github.com:YOUR_USERNAME/my-phren.git`);
224
224
  log(` git push -u origin main`);
225
225
  }
226
- log(` ${step++}. Add more projects: cd ~/your-project && npx phren add`);
226
+ log(` ${step++}. Add more projects: cd ~/your-project && phren add`);
227
227
  if (!mcpEnabled) {
228
- log(` ${step++}. Turn MCP on: npx phren mcp-mode on`);
228
+ log(` ${step++}. Turn MCP on: phren mcp-mode on`);
229
229
  }
230
230
  log(` ${step++}. After your first week, run phren-discover to surface gaps in your project knowledge`);
231
231
  log(` ${step++}. After working across projects, run phren-consolidate to find cross-project patterns`);
@@ -21,6 +21,6 @@ export function configureHooksIfEnabled(phrenPath, hooksEnabled, verb) {
21
21
  }
22
22
  }
23
23
  else {
24
- log(` Hooks are disabled by preference (run: npx phren hooks-mode on)`);
24
+ log(` Hooks are disabled by preference (run: phren hooks-mode on)`);
25
25
  }
26
26
  }
@@ -24,8 +24,8 @@ export async function runMcpMode(modeArg) {
24
24
  const hooks = getHooksEnabledPreference(phrenPath);
25
25
  log(`MCP mode: ${current ? "on (recommended)" : "off (hooks-only fallback)"}`);
26
26
  log(`Hooks mode: ${hooks ? "on (active)" : "off (disabled)"}`);
27
- log(`Change mode: npx phren mcp-mode on|off`);
28
- log(`Hooks toggle: npx phren hooks-mode on|off`);
27
+ log(`Change mode: phren mcp-mode on|off`);
28
+ log(`Hooks toggle: phren hooks-mode on|off`);
29
29
  return;
30
30
  }
31
31
  const mode = parseMcpMode(normalizedArg);
@@ -93,7 +93,7 @@ export async function runHooksMode(modeArg) {
93
93
  if (!normalizedArg || normalizedArg === "status") {
94
94
  const current = getHooksEnabledPreference(phrenPath);
95
95
  log(`Hooks mode: ${current ? "on (active)" : "off (disabled)"}`);
96
- log(`Change mode: npx phren hooks-mode on|off`);
96
+ log(`Change mode: phren hooks-mode on|off`);
97
97
  return;
98
98
  }
99
99
  const mode = parseMcpMode(normalizedArg);
@@ -53,7 +53,7 @@ export async function runExistingInstallUpdate(phrenPath, opts, params) {
53
53
  const previousVersion = prefs.installedVersion;
54
54
  if (isVersionNewer(VERSION, previousVersion)) {
55
55
  log(`\n Starter template update available: v${previousVersion} -> v${VERSION}`);
56
- log(` Run \`npx phren init --apply-starter-update\` to refresh global/CLAUDE.md and global skills.`);
56
+ log(` Run \`phren init --apply-starter-update\` to refresh global/CLAUDE.md and global skills.`);
57
57
  }
58
58
  if (opts.applyStarterUpdate) {
59
59
  const updated = applyStarterTemplateUpdates(phrenPath);
@@ -88,8 +88,8 @@ export async function runExistingInstallUpdate(phrenPath, opts, params) {
88
88
  log(`\n\x1b[95m◆\x1b[0m phren updated successfully`);
89
89
  log(`\nNext steps:`);
90
90
  log(` 1. Start a new Claude session in your project directory — phren injects context automatically`);
91
- log(` 2. Run \`npx phren doctor\` to verify everything is wired correctly`);
92
- log(` 3. Change defaults anytime: \`npx phren config project-ownership\`, \`npx phren config workflow\`, \`npx phren config proactivity.findings\`, \`npx phren config proactivity.tasks\``);
91
+ log(` 2. Run \`phren doctor\` to verify everything is wired correctly`);
92
+ log(` 3. Change defaults anytime: \`phren config project-ownership\`, \`phren config workflow\`, \`phren config proactivity.findings\`, \`phren config proactivity.tasks\``);
93
93
  log(` 4. After your first week, run phren-discover to surface gaps in your project knowledge`);
94
94
  log(` 5. After working across projects, run phren-consolidate to find cross-project patterns`);
95
95
  log(``);
@@ -269,7 +269,7 @@ export async function runWalkthrough(phrenPath) {
269
269
  log(" phren-managed: Phren may mirror CLAUDE.md / AGENTS.md into the repo");
270
270
  log(" detached: Phren keeps its own docs but does not write into the repo");
271
271
  log(" repo-managed: keep the repo's existing CLAUDE/AGENTS files as canonical");
272
- log(" Change later: npx phren config project-ownership <mode>");
272
+ log(" Change later: phren config project-ownership <mode>");
273
273
  const projectOwnershipDefault = await prompts.select("Default project ownership", [
274
274
  { value: "detached", name: "detached (default)" },
275
275
  { value: "phren-managed", name: "phren-managed" },
@@ -280,7 +280,7 @@ export async function runWalkthrough(phrenPath) {
280
280
  log("directly: search memory, manage tasks, save findings, etc.");
281
281
  log(" Recommended for: Claude Code, Cursor, Copilot CLI, Codex");
282
282
  log(" Alternative: hooks-only mode (read-only context injection, any agent)");
283
- log(" Change later: npx phren mcp-mode on|off");
283
+ log(" Change later: phren mcp-mode on|off");
284
284
  const mcp = (await prompts.confirm("Enable MCP?", true)) ? "on" : "off";
285
285
  printSection("Hooks");
286
286
  log("Hooks run shell commands at session start, prompt submit, and session end.");
@@ -288,7 +288,7 @@ export async function runWalkthrough(phrenPath) {
288
288
  log(" - UserPromptSubmit: searches phren and injects relevant context");
289
289
  log(" - Stop: commits and pushes any new findings after each response");
290
290
  log(" What they touch: ~/.claude/settings.json (hooks section only)");
291
- log(" Change later: npx phren hooks-mode on|off");
291
+ log(" Change later: phren hooks-mode on|off");
292
292
  const hooks = (await prompts.confirm("Enable hooks?", true)) ? "on" : "off";
293
293
  printSection("Semantic Search (Optional)");
294
294
  log("Phren can use a local embedding model for semantic (fuzzy) search via Ollama.");
@@ -336,7 +336,7 @@ export async function runWalkthrough(phrenPath) {
336
336
  let findingsProactivity = "high";
337
337
  if (autoCaptureEnabled) {
338
338
  log(" Findings capture level controls how eager phren is to save lessons automatically.");
339
- log(" Change later: npx phren config proactivity.findings <high|medium|low>");
339
+ log(" Change later: phren config proactivity.findings <high|medium|low>");
340
340
  findingsProactivity = await prompts.select("Findings capture level", [
341
341
  { value: "high", name: "high (recommended)" },
342
342
  { value: "medium", name: "medium" },
@@ -352,7 +352,7 @@ export async function runWalkthrough(phrenPath) {
352
352
  log(" suggest: proposes tasks but waits for approval before writing");
353
353
  log(" manual: tasks are fully manual — you add them yourself");
354
354
  log(" off: never touch tasks automatically");
355
- log(" Change later: npx phren config workflow set --taskMode=<mode>");
355
+ log(" Change later: phren config workflow set --taskMode=<mode>");
356
356
  const taskMode = await prompts.select("Task mode", [
357
357
  { value: "auto", name: "auto (recommended)" },
358
358
  { value: "suggest", name: "suggest" },
@@ -365,7 +365,7 @@ export async function runWalkthrough(phrenPath) {
365
365
  log(" high (recommended): captures tasks as they come up naturally");
366
366
  log(" medium: only when you explicitly mention a task");
367
367
  log(" low: minimal auto-capture");
368
- log(" Change later: npx phren config proactivity.tasks <high|medium|low>");
368
+ log(" Change later: phren config proactivity.tasks <high|medium|low>");
369
369
  taskProactivity = await prompts.select("Task proactivity", [
370
370
  { value: "high", name: "high (recommended)" },
371
371
  { value: "medium", name: "medium" },
@@ -376,7 +376,7 @@ export async function runWalkthrough(phrenPath) {
376
376
  log("Choose how strict review gates should be for risky or low-confidence writes.");
377
377
  log(" lowConfidenceThreshold: confidence cutoff used to mark writes as risky");
378
378
  log(" riskySections: sections always treated as risky");
379
- log(" Change later: npx phren config workflow set --lowConfidenceThreshold=0.7 --riskySections=Stale,Conflicts");
379
+ log(" Change later: phren config workflow set --lowConfidenceThreshold=0.7 --riskySections=Stale,Conflicts");
380
380
  const thresholdAnswer = await prompts.input("Low-confidence threshold [0.0-1.0]", "0.7");
381
381
  const lowConfidenceThreshold = parseLowConfidenceThreshold(thresholdAnswer, 0.7);
382
382
  const riskySectionsAnswer = await prompts.input("Risky sections [Review,Stale,Conflicts]", "Stale,Conflicts");
@@ -426,7 +426,7 @@ export async function runWalkthrough(phrenPath) {
426
426
  log(" conservative — decisions and pitfalls only");
427
427
  log(" balanced — non-obvious patterns, decisions, pitfalls, bugs (recommended)");
428
428
  log(" aggressive — everything worth remembering, err on the side of capturing");
429
- log(" Change later: npx phren config finding-sensitivity <level>");
429
+ log(" Change later: phren config finding-sensitivity <level>");
430
430
  const findingSensitivity = await prompts.select("Finding sensitivity", [
431
431
  { value: "balanced", name: "balanced (recommended)" },
432
432
  { value: "conservative", name: "conservative" },
@@ -455,7 +455,7 @@ export async function runWalkthrough(phrenPath) {
455
455
  bootstrapCurrentProject = await prompts.confirm("Add this project to phren now?", true);
456
456
  if (!bootstrapCurrentProject) {
457
457
  bootstrapCurrentProject = false;
458
- log(style.warning(` Skipped. Later: cd ${detectedProject} && npx phren add`));
458
+ log(style.warning(` Skipped. Later: cd ${detectedProject} && phren add`));
459
459
  }
460
460
  else {
461
461
  bootstrapOwnership = await prompts.select("Ownership for detected project", [
@@ -409,7 +409,7 @@ export async function runDoctor(phrenPath, fix = false, checkData = false) {
409
409
  ok: phrenCliActive,
410
410
  detail: phrenCliActive
411
411
  ? "phren CLI wrapper active via ~/.local/bin/phren"
412
- : "phren CLI wrapper missing — run init to install, or npm i -g @phren/cli",
412
+ : "phren CLI wrapper missing — run 'npx @phren/cli init' to install",
413
413
  });
414
414
  if (fix) {
415
415
  const repaired = repairPreexistingInstall(phrenPath);
@@ -76,7 +76,7 @@ function maybeOfferStarterTemplateUpdate(phrenPath) {
76
76
  const prefs = JSON.parse(fs.readFileSync(prefsPath, "utf8"));
77
77
  if (isVersionNewer(current, prefs.installedVersion)) {
78
78
  log(` Starter template update available: v${prefs.installedVersion} -> v${current}`);
79
- log(` Run \`npx phren init --apply-starter-update\` to refresh global/CLAUDE.md and global skills.`);
79
+ log(` Run \`phren init --apply-starter-update\` to refresh global/CLAUDE.md and global skills.`);
80
80
  }
81
81
  }
82
82
  catch (err) {
@@ -209,7 +209,7 @@ export function findPhrenPathWithArg(arg) {
209
209
  const existing = findPhrenPath();
210
210
  if (existing)
211
211
  return existing;
212
- throw new Error(`${PhrenError.NOT_FOUND}: phren root not found. Run 'npx phren init'.`);
212
+ throw new Error(`${PhrenError.NOT_FOUND}: phren root not found. Run 'phren init'.`);
213
213
  }
214
214
  export function isProjectLocalMode(phrenPath) {
215
215
  try {
@@ -513,7 +513,7 @@ export function getPhrenPath() {
513
513
  if (!lazyPhrenPath) {
514
514
  const existing = findPhrenPath();
515
515
  if (!existing)
516
- throw new Error(`${PhrenError.NOT_FOUND}: phren root not found. Run 'npx phren init'.`);
516
+ throw new Error(`${PhrenError.NOT_FOUND}: phren root not found. Run 'phren init'.`);
517
517
  lazyPhrenPath = existing;
518
518
  }
519
519
  return lazyPhrenPath;
@@ -45,7 +45,7 @@ export function resolveActiveProfile(phrenPath, requestedProfile) {
45
45
  export function listMachines(phrenPath) {
46
46
  const machinesPath = path.join(phrenPath, "machines.yaml");
47
47
  if (!fs.existsSync(machinesPath))
48
- return phrenErr(`machines.yaml not found. Run 'npx phren init' to set up your phren.`, PhrenError.FILE_NOT_FOUND);
48
+ return phrenErr(`machines.yaml not found. Run 'phren init' to set up your phren.`, PhrenError.FILE_NOT_FOUND);
49
49
  try {
50
50
  const raw = fs.readFileSync(machinesPath, "utf8");
51
51
  const parsed = yaml.load(raw, { schema: yaml.CORE_SCHEMA });
@@ -205,7 +205,7 @@ export function getActiveProfileDefaults(phrenPath, profile) {
205
205
  export function listProfiles(phrenPath) {
206
206
  const profilesDir = path.join(phrenPath, "profiles");
207
207
  if (!fs.existsSync(profilesDir))
208
- return phrenErr(`No profiles/ directory found. Run 'npx phren init' to set up your phren.`, PhrenError.FILE_NOT_FOUND);
208
+ return phrenErr(`No profiles/ directory found. Run 'phren init' to set up your phren.`, PhrenError.FILE_NOT_FOUND);
209
209
  const files = fs.readdirSync(profilesDir).filter((file) => file.endsWith(".yaml")).sort();
210
210
  const profiles = [];
211
211
  for (const file of files) {
@@ -627,9 +627,15 @@ export function rankResults(rows, intent, gitCtx, detectedProject, phrenPathLoca
627
627
  }
628
628
  return false;
629
629
  });
630
- const canonicalRows = queryDocRows(db, "SELECT project, filename, type, content, path FROM docs WHERE project = ? AND type = 'canonical' LIMIT 1", [detectedProject]);
631
- if (canonicalRows)
632
- ranked = [...canonicalRows, ...ranked];
630
+ // Always-inject: truths for detected project are prepended regardless of search
631
+ // relevance. Dedup against rows already in the result set.
632
+ const canonicalRows = queryDocRows(db, "SELECT project, filename, type, content, path FROM docs WHERE project = ? AND type = 'canonical'", [detectedProject]);
633
+ if (canonicalRows) {
634
+ const existingPaths = new Set(ranked.map((r) => r.path));
635
+ const newCanonicals = canonicalRows.filter((r) => !existingPaths.has(r.path));
636
+ if (newCanonicals.length > 0)
637
+ ranked = [...newCanonicals, ...ranked];
638
+ }
633
639
  }
634
640
  const entityBoost = query ? getFragmentBoostDocs(db, query) : new Set();
635
641
  const entityBoostPaths = new Set();
@@ -53,7 +53,7 @@ function hasCommandHook(value) {
53
53
  export async function runStatus() {
54
54
  const phrenPath = findPhrenPath();
55
55
  if (!phrenPath) {
56
- console.log(`${RED}phren not found${RESET}. Run ${CYAN}npx phren init${RESET} to set up.`);
56
+ console.log(`${RED}phren not found${RESET}. Run ${CYAN}npx @phren/cli init${RESET} to set up.`);
57
57
  process.exit(1);
58
58
  }
59
59
  const cwd = process.cwd();
@@ -165,6 +165,19 @@ export function removeStoreFromRegistry(phrenPath, name) {
165
165
  return entry;
166
166
  });
167
167
  }
168
+ /** Update the projects[] claim list for a store. Uses file locking. */
169
+ export function updateStoreProjects(phrenPath, storeName, projects) {
170
+ withFileLock(storesFilePath(phrenPath), () => {
171
+ const registry = readStoreRegistry(phrenPath);
172
+ if (!registry)
173
+ throw new Error(`${PhrenError.FILE_NOT_FOUND}: No stores.yaml found`);
174
+ const store = registry.stores.find((s) => s.name === storeName);
175
+ if (!store)
176
+ throw new Error(`${PhrenError.NOT_FOUND}: Store "${storeName}" not found`);
177
+ store.projects = projects.length > 0 ? projects : undefined;
178
+ writeStoreRegistry(phrenPath, registry);
179
+ });
180
+ }
168
181
  // ── Validation ───────────────────────────────────────────────────────────────
169
182
  function validateRegistry(registry) {
170
183
  if (registry.version !== 1)
@@ -247,8 +260,9 @@ function parseFederationPathsEnv(localPhrenPath) {
247
260
  const raw = process.env.PHREN_FEDERATION_PATHS ?? "";
248
261
  if (!raw.trim())
249
262
  return [];
263
+ // Use path.delimiter (';' on Windows, ':' on Unix) so Windows drive letters aren't split
250
264
  return raw
251
- .split(":")
265
+ .split(path.delimiter)
252
266
  .map((p) => p.trim())
253
267
  .filter((p) => p.length > 0)
254
268
  .map((p) => path.resolve(expandHomePath(p)))