@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,551 +0,0 @@
1
- import * as fs from "fs";
2
- import * as path from "path";
3
- import { mcpResponse } from "./mcp-types.js";
4
- import { z } from "zod";
5
- import { getRetentionPolicy, updateRetentionPolicy, getWorkflowPolicy, updateWorkflowPolicy, getIndexPolicy, updateIndexPolicy, mergeConfig, VALID_TASK_MODES, VALID_FINDING_SENSITIVITY, } from "./shared-governance.js";
6
- import { PROACTIVITY_LEVELS, } from "./proactivity.js";
7
- import { writeGovernanceInstallPreferences, } from "./init-preferences.js";
8
- import { FINDING_SENSITIVITY_CONFIG, buildProactivitySnapshot, checkProjectInProfile } from "./cli-config.js";
9
- import { readProjectConfig, updateProjectConfigOverrides, } from "./project-config.js";
10
- import { isValidProjectName, safeProjectPath } from "./utils.js";
11
- import { readProjectTopics, writeProjectTopics, } from "./project-topics.js";
12
- // ── Helpers ─────────────────────────────────────────────────────────────────
13
- function proactivitySnapshot(phrenPath) {
14
- const snap = buildProactivitySnapshot(phrenPath);
15
- return { configured: snap.configured, effective: snap.effective };
16
- }
17
- function validateProject(project) {
18
- if (!isValidProjectName(project))
19
- return `Invalid project name: "${project}".`;
20
- return null;
21
- }
22
- function checkProjectRegistered(phrenPath, project) {
23
- const warning = checkProjectInProfile(phrenPath, project);
24
- if (warning) {
25
- return `Project '${project}' is not registered in your active profile. Config was written but won't take effect until you run 'phren add' to register the project.`;
26
- }
27
- return null;
28
- }
29
- function normalizeProjectOverrides(raw) {
30
- return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
31
- }
32
- function getProjectOverrides(phrenPath, project) {
33
- return normalizeProjectOverrides(readProjectConfig(phrenPath, project).config);
34
- }
35
- function hasOwnOverride(overrides, key) {
36
- return Object.prototype.hasOwnProperty.call(overrides, key);
37
- }
38
- const projectParam = z.string().optional().describe("Project name. When provided, writes to that project's phren.project.yaml instead of global .config/.");
39
- // ── Registration ────────────────────────────────────────────────────────────
40
- export function register(server, ctx) {
41
- const { phrenPath } = ctx;
42
- // ── get_config ────────────────────────────────────────────────────────────
43
- server.registerTool("get_config", {
44
- title: "◆ phren · get config",
45
- description: "Read current configuration for one or all config domains: proactivity, taskMode, " +
46
- "findingSensitivity, retention (policy), workflow, access, index. " +
47
- "Returns both configured and effective values. When project is provided, returns " +
48
- "the merged view with project overrides applied and _source annotations.",
49
- inputSchema: z.object({
50
- domain: z
51
- .enum(["proactivity", "taskMode", "findingSensitivity", "retention", "workflow", "access", "index", "all"])
52
- .optional()
53
- .describe("Config domain to read. Defaults to 'all'."),
54
- project: projectParam,
55
- }),
56
- }, async ({ domain, project }) => {
57
- const d = domain ?? "all";
58
- if (project) {
59
- const err = validateProject(project);
60
- if (err)
61
- return mcpResponse({ ok: false, error: err });
62
- const resolved = mergeConfig(phrenPath, project);
63
- const projectOverrides = getProjectOverrides(phrenPath, project);
64
- function src(key) {
65
- return hasOwnOverride(projectOverrides, key) ? "project" : "global";
66
- }
67
- const result = {
68
- _project: project,
69
- _note: "Values marked _source=project override the global default.",
70
- };
71
- if (d === "all" || d === "findingSensitivity") {
72
- const level = resolved.findingSensitivity;
73
- result.findingSensitivity = {
74
- level,
75
- ...FINDING_SENSITIVITY_CONFIG[level],
76
- _source: src("findingSensitivity"),
77
- };
78
- }
79
- if (d === "all" || d === "taskMode") {
80
- result.taskMode = { taskMode: resolved.taskMode, _source: src("taskMode") };
81
- }
82
- if (d === "all" || d === "retention") {
83
- result.retention = {
84
- ...resolved.retentionPolicy,
85
- _source: hasOwnOverride(projectOverrides, "retentionPolicy") ? "project" : "global",
86
- };
87
- }
88
- if (d === "all" || d === "workflow") {
89
- result.workflow = {
90
- ...resolved.workflowPolicy,
91
- _source: hasOwnOverride(projectOverrides, "workflowPolicy") ? "project" : "global",
92
- };
93
- }
94
- if (d === "all" || d === "proactivity") {
95
- const globalSnapshot = proactivitySnapshot(phrenPath).effective;
96
- const base = resolved.proactivity.base ?? globalSnapshot.proactivity;
97
- const findings = resolved.proactivity.findings ?? resolved.proactivity.base ?? globalSnapshot.proactivityFindings;
98
- const tasks = resolved.proactivity.tasks ?? resolved.proactivity.base ?? globalSnapshot.proactivityTask;
99
- result.proactivity = {
100
- base,
101
- findings,
102
- tasks,
103
- _source: {
104
- base: hasOwnOverride(projectOverrides, "proactivity") ? "project" : "global",
105
- findings: hasOwnOverride(projectOverrides, "proactivityFindings")
106
- ? "project"
107
- : hasOwnOverride(projectOverrides, "proactivity")
108
- ? "project"
109
- : "global",
110
- tasks: hasOwnOverride(projectOverrides, "proactivityTask")
111
- ? "project"
112
- : hasOwnOverride(projectOverrides, "proactivity")
113
- ? "project"
114
- : "global",
115
- },
116
- };
117
- }
118
- if (d === "all" || d === "index") {
119
- result.index = getIndexPolicy(phrenPath);
120
- }
121
- return mcpResponse({
122
- ok: true,
123
- message: `Config for ${d === "all" ? "all domains" : d} (project: ${project}).`,
124
- data: result,
125
- });
126
- }
127
- const result = {};
128
- if (d === "all" || d === "proactivity") {
129
- result.proactivity = proactivitySnapshot(phrenPath);
130
- }
131
- if (d === "all" || d === "taskMode") {
132
- const wf = getWorkflowPolicy(phrenPath);
133
- result.taskMode = { taskMode: wf.taskMode };
134
- }
135
- if (d === "all" || d === "findingSensitivity") {
136
- const wf = getWorkflowPolicy(phrenPath);
137
- const level = wf.findingSensitivity;
138
- const config = FINDING_SENSITIVITY_CONFIG[level];
139
- result.findingSensitivity = { level, ...config };
140
- }
141
- if (d === "all" || d === "retention") {
142
- result.retention = getRetentionPolicy(phrenPath);
143
- }
144
- if (d === "all" || d === "workflow") {
145
- result.workflow = getWorkflowPolicy(phrenPath);
146
- }
147
- if (d === "all" || d === "index") {
148
- result.index = getIndexPolicy(phrenPath);
149
- }
150
- return mcpResponse({
151
- ok: true,
152
- message: `Config for ${d === "all" ? "all domains" : d}.`,
153
- data: result,
154
- });
155
- });
156
- // ── set_proactivity ───────────────────────────────────────────────────────
157
- server.registerTool("set_proactivity", {
158
- title: "◆ phren · set proactivity",
159
- description: "Set the proactivity level for auto-capture. Controls how aggressively phren " +
160
- "captures findings and tasks. Supports base level, findings-specific, and task-specific overrides. " +
161
- "When project is provided, writes to that project's phren.project.yaml.",
162
- inputSchema: z.object({
163
- level: z.enum(PROACTIVITY_LEVELS).describe("Proactivity level: high, medium, or low."),
164
- scope: z
165
- .enum(["base", "findings", "tasks"])
166
- .optional()
167
- .describe("Which proactivity to set. Defaults to 'base'."),
168
- project: projectParam,
169
- }),
170
- }, async ({ level, scope, project }) => {
171
- const s = scope ?? "base";
172
- if (project) {
173
- const err = validateProject(project);
174
- if (err)
175
- return mcpResponse({ ok: false, error: err });
176
- const warning = checkProjectRegistered(phrenPath, project);
177
- const key = s === "base" ? "proactivity" : s === "findings" ? "proactivityFindings" : "proactivityTask";
178
- updateProjectConfigOverrides(phrenPath, project, (current) => ({
179
- ...current,
180
- [key]: level,
181
- }));
182
- return mcpResponse({
183
- ok: true,
184
- message: warning
185
- ? `Proactivity ${s} set to ${level} for project "${project}". WARNING: ${warning}`
186
- : `Proactivity ${s} set to ${level} for project "${project}".`,
187
- data: { project, scope: s, level, ...(warning ? { warning } : {}) },
188
- });
189
- }
190
- const patch = {};
191
- if (s === "base")
192
- patch.proactivity = level;
193
- else if (s === "findings")
194
- patch.proactivityFindings = level;
195
- else if (s === "tasks")
196
- patch.proactivityTask = level;
197
- writeGovernanceInstallPreferences(phrenPath, patch);
198
- return mcpResponse({
199
- ok: true,
200
- message: `Proactivity ${s} set to ${level}.`,
201
- data: proactivitySnapshot(phrenPath),
202
- });
203
- });
204
- // ── set_task_mode ─────────────────────────────────────────────────────────
205
- server.registerTool("set_task_mode", {
206
- title: "◆ phren · set task mode",
207
- description: "Set the task automation mode: off (no auto-tasks), manual (user creates), " +
208
- "suggest (phren suggests, user approves), auto (phren creates automatically). " +
209
- "When project is provided, writes to that project's phren.project.yaml.",
210
- inputSchema: z.object({
211
- mode: z.enum(VALID_TASK_MODES).describe("Task mode: off, manual, suggest, or auto."),
212
- project: projectParam,
213
- }),
214
- }, async ({ mode, project }) => {
215
- if (project) {
216
- const err = validateProject(project);
217
- if (err)
218
- return mcpResponse({ ok: false, error: err });
219
- const warning = checkProjectRegistered(phrenPath, project);
220
- updateProjectConfigOverrides(phrenPath, project, (current) => ({
221
- ...current,
222
- taskMode: mode,
223
- }));
224
- return mcpResponse({
225
- ok: true,
226
- message: warning
227
- ? `Task mode set to ${mode} for project "${project}". WARNING: ${warning}`
228
- : `Task mode set to ${mode} for project "${project}".`,
229
- data: { project, taskMode: mode, ...(warning ? { warning } : {}) },
230
- });
231
- }
232
- const result = updateWorkflowPolicy(phrenPath, { taskMode: mode });
233
- if (!result.ok) {
234
- return mcpResponse({ ok: false, error: result.error, errorCode: result.code });
235
- }
236
- return mcpResponse({
237
- ok: true,
238
- message: `Task mode set to ${mode}.`,
239
- data: { taskMode: mode },
240
- });
241
- });
242
- // ── set_finding_sensitivity ───────────────────────────────────────────────
243
- server.registerTool("set_finding_sensitivity", {
244
- title: "◆ phren · set finding sensitivity",
245
- description: "Set the finding capture sensitivity level. Controls how many findings phren captures per session. " +
246
- "minimal: only explicit asks. conservative: decisions/pitfalls only. " +
247
- "balanced: non-obvious patterns. aggressive: capture everything. " +
248
- "When project is provided, writes to that project's phren.project.yaml.",
249
- inputSchema: z.object({
250
- level: z.enum(VALID_FINDING_SENSITIVITY).describe("Sensitivity level."),
251
- project: projectParam,
252
- }),
253
- }, async ({ level, project }) => {
254
- if (project) {
255
- const err = validateProject(project);
256
- if (err)
257
- return mcpResponse({ ok: false, error: err });
258
- const warning = checkProjectRegistered(phrenPath, project);
259
- updateProjectConfigOverrides(phrenPath, project, (current) => ({
260
- ...current,
261
- findingSensitivity: level,
262
- }));
263
- const config = FINDING_SENSITIVITY_CONFIG[level];
264
- return mcpResponse({
265
- ok: true,
266
- message: warning
267
- ? `Finding sensitivity set to ${level} for project "${project}". WARNING: ${warning}`
268
- : `Finding sensitivity set to ${level} for project "${project}".`,
269
- data: { project, level, ...config, ...(warning ? { warning } : {}) },
270
- });
271
- }
272
- const result = updateWorkflowPolicy(phrenPath, { findingSensitivity: level });
273
- if (!result.ok) {
274
- return mcpResponse({ ok: false, error: result.error, errorCode: result.code });
275
- }
276
- const config = FINDING_SENSITIVITY_CONFIG[level];
277
- return mcpResponse({
278
- ok: true,
279
- message: `Finding sensitivity set to ${level}.`,
280
- data: { level, ...config },
281
- });
282
- });
283
- // ── set_retention_policy ──────────────────────────────────────────────────
284
- server.registerTool("set_retention_policy", {
285
- title: "◆ phren · set retention policy",
286
- description: "Update memory retention policy: TTL, retention days, auto-accept threshold, " +
287
- "minimum injection confidence, and decay curve. " +
288
- "When project is provided, writes to that project's phren.project.yaml.",
289
- inputSchema: z.object({
290
- ttlDays: z.number().int().min(1).optional().describe("Days before a finding is considered for expiry."),
291
- retentionDays: z.number().int().min(1).optional().describe("Hard retention limit in days."),
292
- autoAcceptThreshold: z.number().min(0).max(1).optional().describe("Score threshold (0-1) for auto-accepting extracted memories."),
293
- minInjectConfidence: z.number().min(0).max(1).optional().describe("Minimum confidence (0-1) to inject a finding into context."),
294
- decay: z
295
- .object({
296
- d30: z.number().min(0).max(1).optional(),
297
- d60: z.number().min(0).max(1).optional(),
298
- d90: z.number().min(0).max(1).optional(),
299
- d120: z.number().min(0).max(1).optional(),
300
- })
301
- .optional()
302
- .describe("Decay multipliers at 30/60/90/120 day marks."),
303
- project: projectParam,
304
- }),
305
- }, async ({ ttlDays, retentionDays, autoAcceptThreshold, minInjectConfidence, decay, project }) => {
306
- if (project) {
307
- const err = validateProject(project);
308
- if (err)
309
- return mcpResponse({ ok: false, error: err });
310
- const warning = checkProjectRegistered(phrenPath, project);
311
- const next = updateProjectConfigOverrides(phrenPath, project, (current) => {
312
- const existingRetention = current.retentionPolicy ?? {};
313
- const retentionPatch = { ...existingRetention };
314
- if (ttlDays !== undefined)
315
- retentionPatch.ttlDays = ttlDays;
316
- if (retentionDays !== undefined)
317
- retentionPatch.retentionDays = retentionDays;
318
- if (autoAcceptThreshold !== undefined)
319
- retentionPatch.autoAcceptThreshold = autoAcceptThreshold;
320
- if (minInjectConfidence !== undefined)
321
- retentionPatch.minInjectConfidence = minInjectConfidence;
322
- if (decay !== undefined)
323
- retentionPatch.decay = { ...(existingRetention.decay ?? {}), ...decay };
324
- return { ...current, retentionPolicy: retentionPatch };
325
- });
326
- return mcpResponse({
327
- ok: true,
328
- message: warning
329
- ? `Retention policy updated for project "${project}". WARNING: ${warning}`
330
- : `Retention policy updated for project "${project}".`,
331
- data: { project, retentionPolicy: next.config?.retentionPolicy ?? {}, ...(warning ? { warning } : {}) },
332
- });
333
- }
334
- const globalPatch = {};
335
- if (ttlDays !== undefined)
336
- globalPatch.ttlDays = ttlDays;
337
- if (retentionDays !== undefined)
338
- globalPatch.retentionDays = retentionDays;
339
- if (autoAcceptThreshold !== undefined)
340
- globalPatch.autoAcceptThreshold = autoAcceptThreshold;
341
- if (minInjectConfidence !== undefined)
342
- globalPatch.minInjectConfidence = minInjectConfidence;
343
- if (decay !== undefined)
344
- globalPatch.decay = decay;
345
- const result = updateRetentionPolicy(phrenPath, globalPatch);
346
- if (!result.ok) {
347
- return mcpResponse({ ok: false, error: result.error, errorCode: result.code });
348
- }
349
- return mcpResponse({
350
- ok: true,
351
- message: "Retention policy updated.",
352
- data: result.data,
353
- });
354
- });
355
- // ── set_workflow_policy ───────────────────────────────────────────────────
356
- server.registerTool("set_workflow_policy", {
357
- title: "◆ phren · set workflow policy",
358
- description: "Update workflow policy: low-confidence threshold, " +
359
- "risky sections list, task mode, and finding sensitivity. " +
360
- "When project is provided, writes to that project's phren.project.yaml.",
361
- inputSchema: z.object({
362
- lowConfidenceThreshold: z.number().min(0).max(1).optional()
363
- .describe("Confidence below which items are flagged as low-confidence."),
364
- riskySections: z.array(z.enum(["Review", "Stale", "Conflicts"])).optional()
365
- .describe("Which queue sections are considered risky."),
366
- taskMode: z.enum(VALID_TASK_MODES).optional()
367
- .describe("Task automation mode."),
368
- findingSensitivity: z.enum(VALID_FINDING_SENSITIVITY).optional()
369
- .describe("Finding capture sensitivity."),
370
- project: projectParam,
371
- }),
372
- }, async ({ lowConfidenceThreshold, riskySections, taskMode, findingSensitivity, project }) => {
373
- if (project) {
374
- const err = validateProject(project);
375
- if (err)
376
- return mcpResponse({ ok: false, error: err });
377
- const warning = checkProjectRegistered(phrenPath, project);
378
- const next = updateProjectConfigOverrides(phrenPath, project, (current) => {
379
- const nextConfig = { ...current };
380
- const shouldUpdateWorkflowPolicy = (lowConfidenceThreshold !== undefined
381
- || riskySections !== undefined
382
- || current.workflowPolicy !== undefined);
383
- if (shouldUpdateWorkflowPolicy) {
384
- const existingWorkflow = current.workflowPolicy ?? {};
385
- nextConfig.workflowPolicy = {
386
- ...existingWorkflow,
387
- ...(lowConfidenceThreshold !== undefined ? { lowConfidenceThreshold } : {}),
388
- ...(riskySections !== undefined ? { riskySections } : {}),
389
- };
390
- }
391
- if (taskMode !== undefined)
392
- nextConfig.taskMode = taskMode;
393
- if (findingSensitivity !== undefined)
394
- nextConfig.findingSensitivity = findingSensitivity;
395
- return nextConfig;
396
- });
397
- return mcpResponse({
398
- ok: true,
399
- message: warning
400
- ? `Workflow policy updated for project "${project}". WARNING: ${warning}`
401
- : `Workflow policy updated for project "${project}".`,
402
- data: { project, config: next.config ?? {}, ...(warning ? { warning } : {}) },
403
- });
404
- }
405
- const patch = {};
406
- if (lowConfidenceThreshold !== undefined)
407
- patch.lowConfidenceThreshold = lowConfidenceThreshold;
408
- if (riskySections !== undefined)
409
- patch.riskySections = riskySections;
410
- if (taskMode !== undefined)
411
- patch.taskMode = taskMode;
412
- if (findingSensitivity !== undefined)
413
- patch.findingSensitivity = findingSensitivity;
414
- const result = updateWorkflowPolicy(phrenPath, patch);
415
- if (!result.ok) {
416
- return mcpResponse({ ok: false, error: result.error, errorCode: result.code });
417
- }
418
- return mcpResponse({
419
- ok: true,
420
- message: "Workflow policy updated.",
421
- data: result.data,
422
- });
423
- });
424
- // ── set_index_policy ──────────────────────────────────────────────────────
425
- server.registerTool("set_index_policy", {
426
- title: "◆ phren · set index policy",
427
- description: "Update the FTS indexer policy: include/exclude glob patterns and hidden file inclusion.",
428
- inputSchema: z.object({
429
- includeGlobs: z.array(z.string()).optional()
430
- .describe("Glob patterns for files to include in the index."),
431
- excludeGlobs: z.array(z.string()).optional()
432
- .describe("Glob patterns for files to exclude from the index."),
433
- includeHidden: z.boolean().optional()
434
- .describe("Whether to index hidden (dot-prefixed) files."),
435
- }),
436
- }, async ({ includeGlobs, excludeGlobs, includeHidden }) => {
437
- const patch = {};
438
- if (includeGlobs !== undefined)
439
- patch.includeGlobs = includeGlobs;
440
- if (excludeGlobs !== undefined)
441
- patch.excludeGlobs = excludeGlobs;
442
- if (includeHidden !== undefined)
443
- patch.includeHidden = includeHidden;
444
- const result = updateIndexPolicy(phrenPath, patch);
445
- if (!result.ok) {
446
- return mcpResponse({ ok: false, error: result.error, errorCode: result.code });
447
- }
448
- return mcpResponse({
449
- ok: true,
450
- message: "Index policy updated.",
451
- data: result.data,
452
- });
453
- });
454
- // ── get_topic_config ──────────────────────────────────────────────────────
455
- server.registerTool("get_topic_config", {
456
- title: "◆ phren · get topic config",
457
- description: "Read the topic-config.json for a project. Returns the list of topics, domain, " +
458
- "and pinned topics. When no config exists, returns the built-in default topics for the project.",
459
- inputSchema: z.object({
460
- project: z.string().describe("Project name to read topic config from."),
461
- }),
462
- }, async ({ project }) => {
463
- const err = validateProject(project);
464
- if (err)
465
- return mcpResponse({ ok: false, error: err });
466
- const projectDir = safeProjectPath(phrenPath, project);
467
- if (!projectDir || !fs.existsSync(projectDir)) {
468
- return mcpResponse({ ok: false, error: `Project "${project}" not found in phren.` });
469
- }
470
- const result = readProjectTopics(phrenPath, project);
471
- const configPath = path.join(projectDir, "topic-config.json");
472
- const raw = fs.existsSync(configPath)
473
- ? (() => { try {
474
- return JSON.parse(fs.readFileSync(configPath, "utf8"));
475
- }
476
- catch {
477
- return null;
478
- } })()
479
- : null;
480
- return mcpResponse({
481
- ok: true,
482
- message: `Topic config for "${project}" (source: ${result.source}).`,
483
- data: {
484
- project,
485
- source: result.source,
486
- domain: result.domain ?? raw?.domain ?? null,
487
- topics: result.topics,
488
- pinnedTopics: raw?.pinnedTopics ?? [],
489
- },
490
- });
491
- });
492
- // ── set_topic_config ──────────────────────────────────────────────────────
493
- server.registerTool("set_topic_config", {
494
- title: "◆ phren · set topic config",
495
- description: "Write the topic-config.json for a project. Accepts a list of topics with slug, label, " +
496
- "description, and keywords. Merges with any existing pinnedTopics and domain.",
497
- inputSchema: z.object({
498
- project: z.string().describe("Project name to write topic config for."),
499
- topics: z.array(z.object({
500
- slug: z.string().describe("Topic slug (lowercase, hyphens allowed)."),
501
- label: z.string().describe("Human-readable label."),
502
- description: z.string().optional().describe("Short description of what goes in this topic."),
503
- keywords: z.array(z.string()).optional().describe("Keywords used for auto-classification."),
504
- })).describe("Topic list to write."),
505
- domain: z.string().optional().describe("Optional domain label (e.g. 'software', 'music')."),
506
- }),
507
- }, async ({ project, topics, domain }) => {
508
- const err = validateProject(project);
509
- if (err)
510
- return mcpResponse({ ok: false, error: err });
511
- const projectDir = safeProjectPath(phrenPath, project);
512
- if (!projectDir || !fs.existsSync(projectDir)) {
513
- return mcpResponse({ ok: false, error: `Project "${project}" not found in phren.` });
514
- }
515
- const normalized = topics.map((t) => ({
516
- slug: t.slug,
517
- label: t.label,
518
- description: t.description ?? "",
519
- keywords: t.keywords ?? [],
520
- }));
521
- // If a domain is provided, patch it onto the existing file before writing topics
522
- if (domain) {
523
- const configPath = path.join(projectDir, "topic-config.json");
524
- if (fs.existsSync(configPath)) {
525
- try {
526
- const existing = JSON.parse(fs.readFileSync(configPath, "utf8"));
527
- if (existing && typeof existing === "object") {
528
- existing.domain = domain;
529
- fs.writeFileSync(configPath, JSON.stringify(existing, null, 2) + "\n");
530
- }
531
- }
532
- catch {
533
- // ignore read errors; writeProjectTopics will still succeed
534
- }
535
- }
536
- else {
537
- fs.mkdirSync(projectDir, { recursive: true });
538
- fs.writeFileSync(configPath, JSON.stringify({ version: 1, domain, topics: [] }, null, 2) + "\n");
539
- }
540
- }
541
- const result = writeProjectTopics(phrenPath, project, normalized);
542
- if (!result.ok) {
543
- return mcpResponse({ ok: false, error: result.error });
544
- }
545
- return mcpResponse({
546
- ok: true,
547
- message: `Topic config written for "${project}" (${result.topics.length} topics).`,
548
- data: { project, topics: result.topics, domain: domain ?? null },
549
- });
550
- });
551
- }