@phren/cli 0.0.28 → 0.0.33

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 (153) hide show
  1. package/mcp/dist/capabilities/cli.js +2 -5
  2. package/mcp/dist/capabilities/mcp.js +5 -8
  3. package/mcp/dist/capabilities/types.js +2 -5
  4. package/mcp/dist/capabilities/vscode.js +2 -5
  5. package/mcp/dist/capabilities/web-ui.js +2 -5
  6. package/mcp/dist/{cli-actions.js → cli/actions.js} +25 -21
  7. package/mcp/dist/{cli.js → cli/cli.js} +13 -13
  8. package/mcp/dist/{cli-config.js → cli/config.js} +12 -12
  9. package/mcp/dist/{cli-extract.js → cli/extract.js} +8 -8
  10. package/mcp/dist/{cli-govern.js → cli/govern.js} +28 -17
  11. package/mcp/dist/{cli-graph.js → cli/graph.js} +10 -9
  12. package/mcp/dist/{cli-hooks-citations.js → cli/hooks-citations.js} +2 -2
  13. package/mcp/dist/{cli-hooks-context.js → cli/hooks-context.js} +23 -23
  14. package/mcp/dist/{cli-hooks-globs.js → cli/hooks-globs.js} +4 -4
  15. package/mcp/dist/{cli-hooks-output.js → cli/hooks-output.js} +9 -10
  16. package/mcp/dist/{cli-hooks-session.js → cli/hooks-session.js} +58 -117
  17. package/mcp/dist/{cli-hooks.js → cli/hooks.js} +27 -26
  18. package/mcp/dist/{cli-namespaces.js → cli/namespaces.js} +25 -24
  19. package/mcp/dist/{cli-ops.js → cli/ops.js} +9 -9
  20. package/mcp/dist/{cli-search.js → cli/search.js} +12 -11
  21. package/mcp/dist/cli-hooks-git.js +243 -0
  22. package/mcp/dist/cli-hooks-prompt.js +323 -0
  23. package/mcp/dist/cli-hooks-session-handlers.js +337 -0
  24. package/mcp/dist/cli-hooks-stop.js +519 -0
  25. package/mcp/dist/{content-archive.js → content/archive.js} +16 -29
  26. package/mcp/dist/{content-citation.js → content/citation.js} +5 -5
  27. package/mcp/dist/{content-dedup.js → content/dedup.js} +9 -12
  28. package/mcp/dist/{content-learning.js → content/learning.js} +41 -20
  29. package/mcp/dist/{content-validate.js → content/validate.js} +5 -5
  30. package/mcp/dist/{core-finding.js → core/finding.js} +4 -4
  31. package/mcp/dist/{core-project.js → core/project.js} +4 -4
  32. package/mcp/dist/{core-search.js → core/search.js} +2 -2
  33. package/mcp/dist/{data-access.js → data/access.js} +142 -15
  34. package/mcp/dist/{data-tasks.js → data/tasks.js} +7 -5
  35. package/mcp/dist/embedding.js +9 -14
  36. package/mcp/dist/entrypoint.js +11 -11
  37. package/mcp/dist/{finding-context.js → finding/context.js} +2 -2
  38. package/mcp/dist/{finding-impact.js → finding/impact.js} +3 -3
  39. package/mcp/dist/{finding-journal.js → finding/journal.js} +4 -4
  40. package/mcp/dist/{finding-lifecycle.js → finding/lifecycle.js} +13 -7
  41. package/mcp/dist/governance/audit.js +30 -0
  42. package/mcp/dist/{governance-locks.js → governance/locks.js} +14 -9
  43. package/mcp/dist/{governance-policy.js → governance/policy.js} +23 -12
  44. package/mcp/dist/{governance-rbac.js → governance/rbac.js} +4 -4
  45. package/mcp/dist/{governance-scores.js → governance/scores.js} +10 -11
  46. package/mcp/dist/hooks.js +53 -37
  47. package/mcp/dist/index-query.js +4 -1
  48. package/mcp/dist/index.js +54 -30
  49. package/mcp/dist/{init-config.js → init/config.js} +6 -6
  50. package/mcp/dist/{init.js → init/init.js} +80 -69
  51. package/mcp/dist/{init-preferences.js → init/preferences.js} +3 -3
  52. package/mcp/dist/{init-setup.js → init/setup.js} +17 -19
  53. package/mcp/dist/{init-shared.js → init/shared.js} +4 -4
  54. package/mcp/dist/init-bootstrap.js +21 -0
  55. package/mcp/dist/init-detect.js +38 -0
  56. package/mcp/dist/init-env.js +114 -0
  57. package/mcp/dist/init-fresh.js +234 -0
  58. package/mcp/dist/init-hooks.js +26 -0
  59. package/mcp/dist/init-mcp.js +65 -0
  60. package/mcp/dist/init-modes.js +135 -0
  61. package/mcp/dist/init-npm.js +37 -0
  62. package/mcp/dist/init-project-local.js +99 -0
  63. package/mcp/dist/init-semantic.js +48 -0
  64. package/mcp/dist/init-types.js +1 -0
  65. package/mcp/dist/init-uninstall.js +504 -0
  66. package/mcp/dist/init-update.js +96 -0
  67. package/mcp/dist/init-walkthrough.js +524 -0
  68. package/mcp/dist/{link-checksums.js → link/checksums.js} +5 -5
  69. package/mcp/dist/{link-context.js → link/context.js} +4 -4
  70. package/mcp/dist/{link-doctor.js → link/doctor.js} +20 -22
  71. package/mcp/dist/{link.js → link/link.js} +26 -31
  72. package/mcp/dist/{link-skills.js → link/skills.js} +10 -10
  73. package/mcp/dist/logger.js +11 -3
  74. package/mcp/dist/package-metadata.js +1 -1
  75. package/mcp/dist/phren-art.js +4 -126
  76. package/mcp/dist/phren-paths.js +30 -12
  77. package/mcp/dist/proactivity.js +3 -3
  78. package/mcp/dist/profile-store.js +5 -6
  79. package/mcp/dist/project-config.js +2 -2
  80. package/mcp/dist/project-topics.js +17 -47
  81. package/mcp/dist/provider-adapters.js +1 -1
  82. package/mcp/dist/query-correlation.js +1 -1
  83. package/mcp/dist/runtime-profile.js +1 -1
  84. package/mcp/dist/{session-checkpoints.js → session/checkpoints.js} +3 -3
  85. package/mcp/dist/{session-utils.js → session/utils.js} +1 -1
  86. package/mcp/dist/{shared-content.js → shared/content.js} +7 -7
  87. package/mcp/dist/{shared-data-utils.js → shared/data-utils.js} +28 -3
  88. package/mcp/dist/{shared-embedding-cache.js → shared/embedding-cache.js} +3 -3
  89. package/mcp/dist/{shared-fragment-graph.js → shared/fragment-graph.js} +19 -42
  90. package/mcp/dist/shared/governance.js +4 -0
  91. package/mcp/dist/{shared-index.js → shared/index.js} +105 -132
  92. package/mcp/dist/{shared-ollama.js → shared/ollama.js} +25 -7
  93. package/mcp/dist/shared/process.js +24 -0
  94. package/mcp/dist/{shared-retrieval.js → shared/retrieval.js} +22 -24
  95. package/mcp/dist/{shared-search-fallback.js → shared/search-fallback.js} +18 -20
  96. package/mcp/dist/{shared-sqljs.js → shared/sqljs.js} +3 -3
  97. package/mcp/dist/{shared-vector-index.js → shared/vector-index.js} +3 -3
  98. package/mcp/dist/shared.js +6 -60
  99. package/mcp/dist/{shell-entry.js → shell/entry.js} +6 -6
  100. package/mcp/dist/{shell-input.js → shell/input.js} +13 -13
  101. package/mcp/dist/{shell-palette.js → shell/palette.js} +3 -3
  102. package/mcp/dist/{shell-render.js → shell/render.js} +2 -2
  103. package/mcp/dist/{shell.js → shell/shell.js} +11 -11
  104. package/mcp/dist/{shell-state-store.js → shell/state-store.js} +5 -5
  105. package/mcp/dist/{shell-view-list.js → shell/view-list.js} +1 -1
  106. package/mcp/dist/{shell-view.js → shell/view.js} +13 -13
  107. package/mcp/dist/{skill-files.js → skill/files.js} +9 -9
  108. package/mcp/dist/{skill-registry.js → skill/registry.js} +5 -5
  109. package/mcp/dist/{skill-state.js → skill/state.js} +1 -4
  110. package/mcp/dist/startup-embedding.js +2 -2
  111. package/mcp/dist/status.js +15 -14
  112. package/mcp/dist/{tasks-github.js → task/github.js} +3 -2
  113. package/mcp/dist/{task-hygiene.js → task/hygiene.js} +4 -4
  114. package/mcp/dist/{task-lifecycle.js → task/lifecycle.js} +8 -13
  115. package/mcp/dist/telemetry.js +3 -4
  116. package/mcp/dist/tool-registry.js +29 -17
  117. package/mcp/dist/tools/config.js +530 -0
  118. package/mcp/dist/{mcp-data.js → tools/data.js} +8 -10
  119. package/mcp/dist/{mcp-extract-facts.js → tools/extract-facts.js} +6 -6
  120. package/mcp/dist/{mcp-extract.js → tools/extract.js} +6 -6
  121. package/mcp/dist/tools/finding.js +584 -0
  122. package/mcp/dist/{mcp-graph.js → tools/graph.js} +11 -14
  123. package/mcp/dist/{mcp-hooks.js → tools/hooks.js} +6 -6
  124. package/mcp/dist/{mcp-memory.js → tools/memory.js} +5 -5
  125. package/mcp/dist/tools/ops.js +468 -0
  126. package/mcp/dist/tools/search.js +672 -0
  127. package/mcp/dist/{mcp-session.js → tools/session.js} +51 -25
  128. package/mcp/dist/{mcp-skills.js → tools/skills.js} +42 -35
  129. package/mcp/dist/{mcp-tasks.js → tools/tasks.js} +155 -282
  130. package/mcp/dist/{memory-ui-data.js → ui/data.js} +31 -17
  131. package/mcp/dist/{memory-ui.js → ui/memory-ui.js} +3 -3
  132. package/mcp/dist/{memory-ui-page.js → ui/page.js} +5 -7
  133. package/mcp/dist/ui/server.js +1024 -0
  134. package/mcp/dist/update.js +2 -2
  135. package/mcp/dist/utils.js +63 -19
  136. package/package.json +2 -2
  137. package/scripts/preuninstall.mjs +31 -0
  138. package/starter/global/CLAUDE.md +3 -2
  139. package/mcp/dist/governance-audit.js +0 -22
  140. package/mcp/dist/mcp-config.js +0 -551
  141. package/mcp/dist/mcp-finding.js +0 -594
  142. package/mcp/dist/mcp-ops.js +0 -363
  143. package/mcp/dist/mcp-search.js +0 -668
  144. package/mcp/dist/memory-ui-server.js +0 -1411
  145. package/mcp/dist/shared-governance.js +0 -4
  146. /package/mcp/dist/{content-metadata.js → content/metadata.js} +0 -0
  147. /package/mcp/dist/{shared-stemmer.js → shared/stemmer.js} +0 -0
  148. /package/mcp/dist/{shell-types.js → shell/types.js} +0 -0
  149. /package/mcp/dist/{mcp-types.js → tools/types.js} +0 -0
  150. /package/mcp/dist/{memory-ui-assets.js → ui/assets.js} +0 -0
  151. /package/mcp/dist/{memory-ui-graph.js → ui/graph.js} +0 -0
  152. /package/mcp/dist/{memory-ui-scripts.js → ui/scripts.js} +0 -0
  153. /package/mcp/dist/{memory-ui-styles.js → ui/styles.js} +0 -0
@@ -1,22 +1,23 @@
1
- import { mcpResponse } from "./mcp-types.js";
1
+ import { mcpResponse } from "./types.js";
2
2
  import { z } from "zod";
3
3
  import * as fs from "fs";
4
4
  import * as path from "path";
5
5
  import * as crypto from "crypto";
6
6
  import { execFileSync } from "child_process";
7
- import { debugLog, isMemoryScopeVisible, normalizeMemoryScope } from "./shared.js";
8
- import { withFileLock } from "./shared-governance.js";
9
- import { isValidProjectName, errorMessage } from "./utils.js";
10
- import { runCustomHooks } from "./hooks.js";
11
- import { readExtractedFacts } from "./mcp-extract-facts.js";
12
- import { resolveFindingSessionId } from "./finding-context.js";
13
- import { readTasks } from "./data-tasks.js";
14
- import { readFindings } from "./data-access.js";
15
- import { getProjectDirs } from "./shared.js";
16
- import { getActiveTaskForSession } from "./task-lifecycle.js";
17
- import { listTaskCheckpoints, writeTaskCheckpoint } from "./session-checkpoints.js";
18
- import { markImpactEntriesCompletedForSession } from "./finding-impact.js";
19
- import { atomicWriteJson, debugError, scanSessionFiles } from "./session-utils.js";
7
+ import { debugLog, isMemoryScopeVisible, normalizeMemoryScope } from "../shared.js";
8
+ import { withFileLock } from "../shared/governance.js";
9
+ import { isValidProjectName, errorMessage } from "../utils.js";
10
+ import { runCustomHooks } from "../hooks.js";
11
+ import { readExtractedFacts } from "./extract-facts.js";
12
+ import { resolveFindingSessionId } from "../finding/context.js";
13
+ import { readTasks } from "../data/tasks.js";
14
+ import { readFindings } from "../data/access.js";
15
+ import { getProjectDirs } from "../shared.js";
16
+ import { getActiveTaskForSession } from "../task/lifecycle.js";
17
+ import { listTaskCheckpoints, writeTaskCheckpoint } from "../session/checkpoints.js";
18
+ import { markImpactEntriesCompletedForSession } from "../finding/impact.js";
19
+ import { atomicWriteJson, debugError, scanSessionFiles } from "../session/utils.js";
20
+ import { getRuntimeHealth } from "../governance/policy.js";
20
21
  const STALE_SESSION_MS = 24 * 60 * 60 * 1000; // 24 hours
21
22
  function collectGitStatusSnapshot(cwd) {
22
23
  try {
@@ -165,7 +166,8 @@ function writeLastSummary(phrenPath, summary, sessionId, project) {
165
166
  debugError("writeLastSummary", err);
166
167
  }
167
168
  }
168
- /** Find the most recent session with a summary (including ended sessions). */
169
+ /** Find the most recent session with a summary (including ended sessions).
170
+ * @internal Exported for tests. */
169
171
  export function findMostRecentSummary(phrenPath) {
170
172
  return findMostRecentSummaryWithProject(phrenPath).summary;
171
173
  }
@@ -215,6 +217,10 @@ function cleanupStaleSessions(phrenPath) {
215
217
  let cleaned = 0;
216
218
  for (const { fullPath, data: state } of results) {
217
219
  try {
220
+ // Only clean up sessions that have ended (have endedAt). Active sessions
221
+ // (no endedAt) should never be removed regardless of age.
222
+ if (state && !state.endedAt)
223
+ continue;
218
224
  // prefer startedAt from the JSON content over mtime (reliable on noatime mounts)
219
225
  const ageMs = state?.startedAt
220
226
  ? Date.now() - new Date(state.startedAt).getTime()
@@ -347,7 +353,7 @@ function hasCompletedTasksInSession(phrenPath, sessionId, project) {
347
353
  return artifacts.tasks.some((task) => task.section === "Done" && task.checked);
348
354
  }
349
355
  /** Compute what changed since the last session ended. */
350
- export function computeSessionDiff(phrenPath, project, lastSessionEnd) {
356
+ function computeSessionDiff(phrenPath, project, lastSessionEnd) {
351
357
  const projectDir = path.join(phrenPath, project);
352
358
  const findingsPath = path.join(projectDir, "FINDINGS.md");
353
359
  if (!fs.existsSync(findingsPath))
@@ -403,16 +409,15 @@ export function register(server, ctx) {
403
409
  if (agentScope !== undefined && !normalizedAgentScope) {
404
410
  return mcpResponse({ ok: false, error: `Invalid agentScope: "${agentScope}". Use lowercase letters/numbers with '-' or '_' (max 64 chars).` });
405
411
  }
406
- // Find most recent prior session for context
412
+ // Find most recent prior session for context.
413
+ // When no explicit project is provided, prefer the last ENDED session's
414
+ // project (completed context) over an active session from a different client.
415
+ const priorEnded = findMostRecentSummaryWithProject(phrenPath);
407
416
  const priorResult = findMostRecentSession(phrenPath);
408
417
  const prior = priorResult?.state ?? null;
409
- // Also check ended sessions for summaries and project context.
410
- // findMostRecentSession skips ended sessions, so we need a separate lookup
411
- // to restore project context after a normal session_end.
412
- const priorEnded = prior ? null : findMostRecentSummaryWithProject(phrenPath);
413
- const priorSummary = prior?.summary ?? priorEnded?.summary ?? null;
414
- const priorProject = prior?.project ?? priorEnded?.project;
415
- const priorEndedAt = prior?.endedAt ?? priorEnded?.endedAt;
418
+ const priorSummary = priorEnded?.summary ?? prior?.summary ?? null;
419
+ const priorProject = priorEnded?.project ?? prior?.project;
420
+ const priorEndedAt = priorEnded?.endedAt ?? prior?.endedAt;
416
421
  // Create new session with unique ID in its own file
417
422
  const sessionId = crypto.randomUUID();
418
423
  const next = {
@@ -521,10 +526,31 @@ export function register(server, ctx) {
521
526
  debugError("session_start contextDiff", err);
522
527
  }
523
528
  }
529
+ // ── Surface sync/health warnings ────────────────────────────────────
530
+ const sessionWarnings = [];
531
+ try {
532
+ const health = getRuntimeHealth(phrenPath);
533
+ if (health.lastAutoSave?.status === "error") {
534
+ sessionWarnings.push(`Last auto-save failed: ${health.lastAutoSave.detail ?? "unknown"}`);
535
+ }
536
+ if (health.lastSync?.lastPushStatus === "error") {
537
+ sessionWarnings.push(`Last push failed: ${health.lastSync.lastPushDetail ?? "unknown"}`);
538
+ }
539
+ const unsynced = health.lastSync?.unsyncedCommits;
540
+ if (typeof unsynced === "number" && unsynced > 0) {
541
+ sessionWarnings.push(`${unsynced} unsynced commit${unsynced === 1 ? "" : "s"} — run 'phren doctor' or check git remote`);
542
+ }
543
+ }
544
+ catch (err) {
545
+ debugError("session_start runtimeHealth", err);
546
+ }
547
+ if (sessionWarnings.length > 0) {
548
+ parts.push(`## Sync warnings\n${sessionWarnings.map(w => `- ${w}`).join("\n")}`);
549
+ }
524
550
  const message = parts.length > 0
525
551
  ? `Session started (${sessionId.slice(0, 8)}).\n\n${parts.join("\n\n")}`
526
552
  : `Session started (${sessionId.slice(0, 8)}). No prior context found.`;
527
- return mcpResponse({ ok: true, message, data: { sessionId, project: activeProject, agentScope: activeScope } });
553
+ return mcpResponse({ ok: true, message, data: { sessionId, project: activeProject, agentScope: activeScope, warnings: sessionWarnings.length > 0 ? sessionWarnings : undefined } });
528
554
  });
529
555
  server.registerTool("session_end", {
530
556
  title: "◆ phren · session end",
@@ -1,11 +1,11 @@
1
- import { mcpResponse } from "./mcp-types.js";
1
+ import { mcpResponse } from "./types.js";
2
2
  import { z } from "zod";
3
3
  import * as fs from "fs";
4
4
  import * as path from "path";
5
- import { isValidProjectName, safeProjectPath } from "./utils.js";
6
- import { parseSkillFrontmatter, validateSkillFrontmatter } from "./link-skills.js";
7
- import { removeSkillPath, setSkillEnabledAndSync } from "./skill-files.js";
8
- import { buildSkillManifest, findLocalSkill, findSkill, getAllSkills } from "./skill-registry.js";
5
+ import { isValidProjectName, safeProjectPath } from "../utils.js";
6
+ import { parseSkillFrontmatter, validateSkillFrontmatter } from "../link/skills.js";
7
+ import { removeSkillPath, setSkillEnabledAndSync } from "../skill/files.js";
8
+ import { buildSkillManifest, findLocalSkill, findSkill, getAllSkills } from "../skill/registry.js";
9
9
  export function register(server, ctx) {
10
10
  const { phrenPath, profile, withWriteQueue, updateFileInIndex } = ctx;
11
11
  // ── list_skills ──────────────────────────────────────────────────────────
@@ -80,6 +80,15 @@ export function register(server, ctx) {
80
80
  if ("error" in result) {
81
81
  return mcpResponse({ ok: false, error: result.error });
82
82
  }
83
+ // Verify skill path doesn't escape phren via symlink
84
+ try {
85
+ const realPath = fs.realpathSync(result.path);
86
+ const phrenReal = fs.realpathSync(phrenPath);
87
+ if (!realPath.startsWith(phrenReal + path.sep) && !realPath.startsWith(path.dirname(phrenReal) + path.sep)) {
88
+ return mcpResponse({ ok: false, error: `Skill path resolves outside phren store.` });
89
+ }
90
+ }
91
+ catch { /* path doesn't exist or can't resolve — let readFileSync handle it */ }
83
92
  const content = fs.readFileSync(result.path, "utf8");
84
93
  const { frontmatter, body } = parseSkillFrontmatter(content);
85
94
  const { valid, errors } = validateSkillFrontmatter(content, result.path);
@@ -165,36 +174,34 @@ export function register(server, ctx) {
165
174
  return mcpResponse({ ok: true, message: `Removed skill "${name}" (${removedPath}).`, data: { path: removedPath } });
166
175
  });
167
176
  });
168
- for (const action of [
169
- { tool: "enable_skill", enabled: true, verb: "Enable" },
170
- { tool: "disable_skill", enabled: false, verb: "Disable" },
171
- ]) {
172
- server.registerTool(action.tool, {
173
- title: `◆ phren · ${action.enabled ? "enable" : "disable"} skill`,
174
- description: `${action.verb} a skill without deleting its file.`,
175
- inputSchema: z.object({
176
- name: z.string().describe("Skill name (without .md)."),
177
- project: z.string().describe("Project scope or 'global'."),
178
- }),
179
- }, async ({ name, project }) => {
180
- if (project.toLowerCase() !== "global" && !isValidProjectName(project)) {
181
- return mcpResponse({ ok: false, error: `Invalid project name: "${project}"` });
182
- }
183
- const result = findSkill(phrenPath, profile, project, name);
184
- if (!result) {
185
- return mcpResponse({ ok: false, error: `Skill "${name}" not found in "${project}".` });
186
- }
187
- if ("error" in result) {
188
- return mcpResponse({ ok: false, error: result.error });
189
- }
190
- return withWriteQueue(async () => {
191
- setSkillEnabledAndSync(phrenPath, project, result.name, action.enabled);
192
- return mcpResponse({
193
- ok: true,
194
- message: `${action.verb}d skill "${result.name}" in ${project}.`,
195
- data: { name: result.name, project, enabled: action.enabled },
196
- });
177
+ // ── toggle_skill ─────────────────────────────────────────────────────
178
+ server.registerTool("toggle_skill", {
179
+ title: " phren · toggle skill",
180
+ description: "Enable or disable a skill without deleting its file.",
181
+ inputSchema: z.object({
182
+ name: z.string().describe("Skill name (without .md)."),
183
+ enabled: z.boolean().describe("true to enable, false to disable."),
184
+ project: z.string().describe("Project scope or 'global'."),
185
+ }),
186
+ }, async ({ name, enabled, project }) => {
187
+ if (project.toLowerCase() !== "global" && !isValidProjectName(project)) {
188
+ return mcpResponse({ ok: false, error: `Invalid project name: "${project}"` });
189
+ }
190
+ const result = findSkill(phrenPath, profile, project, name);
191
+ if (!result) {
192
+ return mcpResponse({ ok: false, error: `Skill "${name}" not found in "${project}".` });
193
+ }
194
+ if ("error" in result) {
195
+ return mcpResponse({ ok: false, error: result.error });
196
+ }
197
+ const verb = enabled ? "Enable" : "Disable";
198
+ return withWriteQueue(async () => {
199
+ setSkillEnabledAndSync(phrenPath, project, result.name, enabled);
200
+ return mcpResponse({
201
+ ok: true,
202
+ message: `${verb}d skill "${result.name}" in ${project}.`,
203
+ data: { name: result.name, project, enabled },
197
204
  });
198
205
  });
199
- }
206
+ });
200
207
  }