@phren/cli 0.0.9 → 0.0.11

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 (67) hide show
  1. package/README.md +2 -8
  2. package/mcp/dist/cli-actions.js +5 -5
  3. package/mcp/dist/cli-config.js +334 -127
  4. package/mcp/dist/cli-govern.js +140 -3
  5. package/mcp/dist/cli-graph.js +3 -2
  6. package/mcp/dist/cli-hooks-globs.js +2 -1
  7. package/mcp/dist/cli-hooks-output.js +3 -3
  8. package/mcp/dist/cli-hooks.js +41 -34
  9. package/mcp/dist/cli-namespaces.js +15 -5
  10. package/mcp/dist/cli-search.js +2 -2
  11. package/mcp/dist/content-archive.js +2 -2
  12. package/mcp/dist/content-citation.js +12 -22
  13. package/mcp/dist/content-dedup.js +9 -9
  14. package/mcp/dist/data-access.js +1 -1
  15. package/mcp/dist/data-tasks.js +23 -0
  16. package/mcp/dist/embedding.js +7 -7
  17. package/mcp/dist/entrypoint.js +129 -102
  18. package/mcp/dist/governance-locks.js +6 -5
  19. package/mcp/dist/governance-policy.js +155 -2
  20. package/mcp/dist/governance-scores.js +3 -3
  21. package/mcp/dist/hooks.js +39 -18
  22. package/mcp/dist/index.js +4 -4
  23. package/mcp/dist/init-config.js +3 -24
  24. package/mcp/dist/init-setup.js +5 -5
  25. package/mcp/dist/init.js +170 -23
  26. package/mcp/dist/link-checksums.js +3 -2
  27. package/mcp/dist/link-context.js +1 -1
  28. package/mcp/dist/link-doctor.js +3 -3
  29. package/mcp/dist/link-skills.js +98 -12
  30. package/mcp/dist/link.js +17 -27
  31. package/mcp/dist/machine-identity.js +1 -9
  32. package/mcp/dist/mcp-config.js +247 -42
  33. package/mcp/dist/mcp-data.js +9 -9
  34. package/mcp/dist/mcp-extract-facts.js +1 -1
  35. package/mcp/dist/mcp-extract.js +2 -2
  36. package/mcp/dist/mcp-finding.js +6 -6
  37. package/mcp/dist/mcp-graph.js +11 -11
  38. package/mcp/dist/mcp-ops.js +18 -18
  39. package/mcp/dist/mcp-search.js +8 -8
  40. package/mcp/dist/mcp-tasks.js +21 -1
  41. package/mcp/dist/memory-ui-page.js +23 -0
  42. package/mcp/dist/memory-ui-scripts.js +210 -27
  43. package/mcp/dist/memory-ui-server.js +115 -3
  44. package/mcp/dist/phren-paths.js +7 -7
  45. package/mcp/dist/profile-store.js +2 -2
  46. package/mcp/dist/project-config.js +63 -16
  47. package/mcp/dist/session-utils.js +3 -2
  48. package/mcp/dist/shared-fragment-graph.js +22 -21
  49. package/mcp/dist/shared-index.js +144 -105
  50. package/mcp/dist/shared-retrieval.js +22 -56
  51. package/mcp/dist/shared-search-fallback.js +13 -13
  52. package/mcp/dist/shared-sqljs.js +3 -2
  53. package/mcp/dist/shared.js +3 -3
  54. package/mcp/dist/shell-input.js +1 -1
  55. package/mcp/dist/shell-state-store.js +1 -1
  56. package/mcp/dist/shell-view.js +3 -2
  57. package/mcp/dist/shell.js +1 -1
  58. package/mcp/dist/skill-files.js +4 -10
  59. package/mcp/dist/skill-registry.js +3 -0
  60. package/mcp/dist/status.js +41 -13
  61. package/mcp/dist/task-hygiene.js +1 -1
  62. package/mcp/dist/telemetry.js +5 -4
  63. package/mcp/dist/update.js +1 -1
  64. package/mcp/dist/utils.js +3 -3
  65. package/package.json +2 -2
  66. package/starter/global/skills/audit.md +106 -0
  67. package/mcp/dist/shared-paths.js +0 -1
@@ -1,27 +1,38 @@
1
1
  import { mcpResponse } from "./mcp-types.js";
2
2
  import { z } from "zod";
3
- import { getRetentionPolicy, updateRetentionPolicy, getWorkflowPolicy, updateWorkflowPolicy, getIndexPolicy, updateIndexPolicy, } from "./shared-governance.js";
4
- import { PROACTIVITY_LEVELS, getProactivityLevel, getProactivityLevelForFindings, getProactivityLevelForTask, } from "./proactivity.js";
5
- import { readGovernanceInstallPreferences, writeGovernanceInstallPreferences, } from "./init-preferences.js";
6
- import { FINDING_SENSITIVITY_CONFIG } from "./cli-config.js";
3
+ import { getRetentionPolicy, updateRetentionPolicy, getWorkflowPolicy, updateWorkflowPolicy, getIndexPolicy, updateIndexPolicy, mergeConfig, VALID_TASK_MODES, VALID_FINDING_SENSITIVITY, } from "./shared-governance.js";
4
+ import { PROACTIVITY_LEVELS, } from "./proactivity.js";
5
+ import { writeGovernanceInstallPreferences, } from "./init-preferences.js";
6
+ import { FINDING_SENSITIVITY_CONFIG, buildProactivitySnapshot, checkProjectInProfile } from "./cli-config.js";
7
+ import { readProjectConfig, updateProjectConfigOverrides, } from "./project-config.js";
8
+ import { isValidProjectName } from "./utils.js";
7
9
  // ── Helpers ─────────────────────────────────────────────────────────────────
8
10
  function proactivitySnapshot(phrenPath) {
9
- const prefs = readGovernanceInstallPreferences(phrenPath);
10
- return {
11
- configured: {
12
- proactivity: prefs.proactivity ?? null,
13
- proactivityFindings: prefs.proactivityFindings ?? null,
14
- proactivityTask: prefs.proactivityTask ?? null,
15
- },
16
- effective: {
17
- proactivity: getProactivityLevel(phrenPath),
18
- proactivityFindings: getProactivityLevelForFindings(phrenPath),
19
- proactivityTask: getProactivityLevelForTask(phrenPath),
20
- },
21
- };
11
+ const snap = buildProactivitySnapshot(phrenPath);
12
+ return { configured: snap.configured, effective: snap.effective };
22
13
  }
23
- const TASK_MODES = ["off", "manual", "suggest", "auto"];
24
- const FINDING_SENSITIVITY_LEVELS = ["minimal", "conservative", "balanced", "aggressive"];
14
+ function validateProject(project) {
15
+ if (!isValidProjectName(project))
16
+ return `Invalid project name: "${project}".`;
17
+ return null;
18
+ }
19
+ function checkProjectRegistered(phrenPath, project) {
20
+ const warning = checkProjectInProfile(phrenPath, project);
21
+ if (warning) {
22
+ 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.`;
23
+ }
24
+ return null;
25
+ }
26
+ function normalizeProjectOverrides(raw) {
27
+ return raw && typeof raw === "object" && !Array.isArray(raw) ? raw : {};
28
+ }
29
+ function getProjectOverrides(phrenPath, project) {
30
+ return normalizeProjectOverrides(readProjectConfig(phrenPath, project).config);
31
+ }
32
+ function hasOwnOverride(overrides, key) {
33
+ return Object.prototype.hasOwnProperty.call(overrides, key);
34
+ }
35
+ const projectParam = z.string().optional().describe("Project name. When provided, writes to that project's phren.project.yaml instead of global .governance/.");
25
36
  // ── Registration ────────────────────────────────────────────────────────────
26
37
  export function register(server, ctx) {
27
38
  const { phrenPath } = ctx;
@@ -30,15 +41,86 @@ export function register(server, ctx) {
30
41
  title: "◆ phren · get config",
31
42
  description: "Read current configuration for one or all config domains: proactivity, taskMode, " +
32
43
  "findingSensitivity, retention (policy), workflow, access, index. " +
33
- "Returns both configured and effective values.",
44
+ "Returns both configured and effective values. When project is provided, returns " +
45
+ "the merged view with project overrides applied and _source annotations.",
34
46
  inputSchema: z.object({
35
47
  domain: z
36
48
  .enum(["proactivity", "taskMode", "findingSensitivity", "retention", "workflow", "access", "index", "all"])
37
49
  .optional()
38
50
  .describe("Config domain to read. Defaults to 'all'."),
51
+ project: projectParam,
39
52
  }),
40
- }, async ({ domain }) => {
53
+ }, async ({ domain, project }) => {
41
54
  const d = domain ?? "all";
55
+ if (project) {
56
+ const err = validateProject(project);
57
+ if (err)
58
+ return mcpResponse({ ok: false, error: err });
59
+ const resolved = mergeConfig(phrenPath, project);
60
+ const projectOverrides = getProjectOverrides(phrenPath, project);
61
+ function src(key) {
62
+ return hasOwnOverride(projectOverrides, key) ? "project" : "global";
63
+ }
64
+ const result = {
65
+ _project: project,
66
+ _note: "Values marked _source=project override the global default.",
67
+ };
68
+ if (d === "all" || d === "findingSensitivity") {
69
+ const level = resolved.findingSensitivity;
70
+ result.findingSensitivity = {
71
+ level,
72
+ ...FINDING_SENSITIVITY_CONFIG[level],
73
+ _source: src("findingSensitivity"),
74
+ };
75
+ }
76
+ if (d === "all" || d === "taskMode") {
77
+ result.taskMode = { taskMode: resolved.taskMode, _source: src("taskMode") };
78
+ }
79
+ if (d === "all" || d === "retention") {
80
+ result.retention = {
81
+ ...resolved.retentionPolicy,
82
+ _source: hasOwnOverride(projectOverrides, "retentionPolicy") ? "project" : "global",
83
+ };
84
+ }
85
+ if (d === "all" || d === "workflow") {
86
+ result.workflow = {
87
+ ...resolved.workflowPolicy,
88
+ _source: hasOwnOverride(projectOverrides, "workflowPolicy") ? "project" : "global",
89
+ };
90
+ }
91
+ if (d === "all" || d === "proactivity") {
92
+ const globalSnapshot = proactivitySnapshot(phrenPath).effective;
93
+ const base = resolved.proactivity.base ?? globalSnapshot.proactivity;
94
+ const findings = resolved.proactivity.findings ?? resolved.proactivity.base ?? globalSnapshot.proactivityFindings;
95
+ const tasks = resolved.proactivity.tasks ?? resolved.proactivity.base ?? globalSnapshot.proactivityTask;
96
+ result.proactivity = {
97
+ base,
98
+ findings,
99
+ tasks,
100
+ _source: {
101
+ base: hasOwnOverride(projectOverrides, "proactivity") ? "project" : "global",
102
+ findings: hasOwnOverride(projectOverrides, "proactivityFindings")
103
+ ? "project"
104
+ : hasOwnOverride(projectOverrides, "proactivity")
105
+ ? "project"
106
+ : "global",
107
+ tasks: hasOwnOverride(projectOverrides, "proactivityTask")
108
+ ? "project"
109
+ : hasOwnOverride(projectOverrides, "proactivity")
110
+ ? "project"
111
+ : "global",
112
+ },
113
+ };
114
+ }
115
+ if (d === "all" || d === "index") {
116
+ result.index = getIndexPolicy(phrenPath);
117
+ }
118
+ return mcpResponse({
119
+ ok: true,
120
+ message: `Config for ${d === "all" ? "all domains" : d} (project: ${project}).`,
121
+ data: result,
122
+ });
123
+ }
42
124
  const result = {};
43
125
  if (d === "all" || d === "proactivity") {
44
126
  result.proactivity = proactivitySnapshot(phrenPath);
@@ -72,16 +154,36 @@ export function register(server, ctx) {
72
154
  server.registerTool("set_proactivity", {
73
155
  title: "◆ phren · set proactivity",
74
156
  description: "Set the proactivity level for auto-capture. Controls how aggressively phren " +
75
- "captures findings and tasks. Supports base level, findings-specific, and task-specific overrides.",
157
+ "captures findings and tasks. Supports base level, findings-specific, and task-specific overrides. " +
158
+ "When project is provided, writes to that project's phren.project.yaml.",
76
159
  inputSchema: z.object({
77
160
  level: z.enum(PROACTIVITY_LEVELS).describe("Proactivity level: high, medium, or low."),
78
161
  scope: z
79
162
  .enum(["base", "findings", "tasks"])
80
163
  .optional()
81
164
  .describe("Which proactivity to set. Defaults to 'base'."),
165
+ project: projectParam,
82
166
  }),
83
- }, async ({ level, scope }) => {
167
+ }, async ({ level, scope, project }) => {
84
168
  const s = scope ?? "base";
169
+ if (project) {
170
+ const err = validateProject(project);
171
+ if (err)
172
+ return mcpResponse({ ok: false, error: err });
173
+ const warning = checkProjectRegistered(phrenPath, project);
174
+ const key = s === "base" ? "proactivity" : s === "findings" ? "proactivityFindings" : "proactivityTask";
175
+ updateProjectConfigOverrides(phrenPath, project, (current) => ({
176
+ ...current,
177
+ [key]: level,
178
+ }));
179
+ return mcpResponse({
180
+ ok: true,
181
+ message: warning
182
+ ? `Proactivity ${s} set to ${level} for project "${project}". WARNING: ${warning}`
183
+ : `Proactivity ${s} set to ${level} for project "${project}".`,
184
+ data: { project, scope: s, level, ...(warning ? { warning } : {}) },
185
+ });
186
+ }
85
187
  const patch = {};
86
188
  if (s === "base")
87
189
  patch.proactivity = level;
@@ -100,11 +202,30 @@ export function register(server, ctx) {
100
202
  server.registerTool("set_task_mode", {
101
203
  title: "◆ phren · set task mode",
102
204
  description: "Set the task automation mode: off (no auto-tasks), manual (user creates), " +
103
- "suggest (phren suggests, user approves), auto (phren creates automatically).",
205
+ "suggest (phren suggests, user approves), auto (phren creates automatically). " +
206
+ "When project is provided, writes to that project's phren.project.yaml.",
104
207
  inputSchema: z.object({
105
- mode: z.enum(TASK_MODES).describe("Task mode: off, manual, suggest, or auto."),
208
+ mode: z.enum(VALID_TASK_MODES).describe("Task mode: off, manual, suggest, or auto."),
209
+ project: projectParam,
106
210
  }),
107
- }, async ({ mode }) => {
211
+ }, async ({ mode, project }) => {
212
+ if (project) {
213
+ const err = validateProject(project);
214
+ if (err)
215
+ return mcpResponse({ ok: false, error: err });
216
+ const warning = checkProjectRegistered(phrenPath, project);
217
+ updateProjectConfigOverrides(phrenPath, project, (current) => ({
218
+ ...current,
219
+ taskMode: mode,
220
+ }));
221
+ return mcpResponse({
222
+ ok: true,
223
+ message: warning
224
+ ? `Task mode set to ${mode} for project "${project}". WARNING: ${warning}`
225
+ : `Task mode set to ${mode} for project "${project}".`,
226
+ data: { project, taskMode: mode, ...(warning ? { warning } : {}) },
227
+ });
228
+ }
108
229
  const result = updateWorkflowPolicy(phrenPath, { taskMode: mode });
109
230
  if (!result.ok) {
110
231
  return mcpResponse({ ok: false, error: result.error, errorCode: result.code });
@@ -120,11 +241,31 @@ export function register(server, ctx) {
120
241
  title: "◆ phren · set finding sensitivity",
121
242
  description: "Set the finding capture sensitivity level. Controls how many findings phren captures per session. " +
122
243
  "minimal: only explicit asks. conservative: decisions/pitfalls only. " +
123
- "balanced: non-obvious patterns. aggressive: capture everything.",
244
+ "balanced: non-obvious patterns. aggressive: capture everything. " +
245
+ "When project is provided, writes to that project's phren.project.yaml.",
124
246
  inputSchema: z.object({
125
- level: z.enum(FINDING_SENSITIVITY_LEVELS).describe("Sensitivity level."),
247
+ level: z.enum(VALID_FINDING_SENSITIVITY).describe("Sensitivity level."),
248
+ project: projectParam,
126
249
  }),
127
- }, async ({ level }) => {
250
+ }, async ({ level, project }) => {
251
+ if (project) {
252
+ const err = validateProject(project);
253
+ if (err)
254
+ return mcpResponse({ ok: false, error: err });
255
+ const warning = checkProjectRegistered(phrenPath, project);
256
+ updateProjectConfigOverrides(phrenPath, project, (current) => ({
257
+ ...current,
258
+ findingSensitivity: level,
259
+ }));
260
+ const config = FINDING_SENSITIVITY_CONFIG[level];
261
+ return mcpResponse({
262
+ ok: true,
263
+ message: warning
264
+ ? `Finding sensitivity set to ${level} for project "${project}". WARNING: ${warning}`
265
+ : `Finding sensitivity set to ${level} for project "${project}".`,
266
+ data: { project, level, ...config, ...(warning ? { warning } : {}) },
267
+ });
268
+ }
128
269
  const result = updateWorkflowPolicy(phrenPath, { findingSensitivity: level });
129
270
  if (!result.ok) {
130
271
  return mcpResponse({ ok: false, error: result.error, errorCode: result.code });
@@ -140,7 +281,8 @@ export function register(server, ctx) {
140
281
  server.registerTool("set_retention_policy", {
141
282
  title: "◆ phren · set retention policy",
142
283
  description: "Update memory retention policy: TTL, retention days, auto-accept threshold, " +
143
- "minimum injection confidence, and decay curve.",
284
+ "minimum injection confidence, and decay curve. " +
285
+ "When project is provided, writes to that project's phren.project.yaml.",
144
286
  inputSchema: z.object({
145
287
  ttlDays: z.number().int().min(1).optional().describe("Days before a finding is considered for expiry."),
146
288
  retentionDays: z.number().int().min(1).optional().describe("Hard retention limit in days."),
@@ -155,20 +297,49 @@ export function register(server, ctx) {
155
297
  })
156
298
  .optional()
157
299
  .describe("Decay multipliers at 30/60/90/120 day marks."),
300
+ project: projectParam,
158
301
  }),
159
- }, async ({ ttlDays, retentionDays, autoAcceptThreshold, minInjectConfidence, decay }) => {
160
- const patch = {};
302
+ }, async ({ ttlDays, retentionDays, autoAcceptThreshold, minInjectConfidence, decay, project }) => {
303
+ if (project) {
304
+ const err = validateProject(project);
305
+ if (err)
306
+ return mcpResponse({ ok: false, error: err });
307
+ const warning = checkProjectRegistered(phrenPath, project);
308
+ const next = updateProjectConfigOverrides(phrenPath, project, (current) => {
309
+ const existingRetention = current.retentionPolicy ?? {};
310
+ const retentionPatch = { ...existingRetention };
311
+ if (ttlDays !== undefined)
312
+ retentionPatch.ttlDays = ttlDays;
313
+ if (retentionDays !== undefined)
314
+ retentionPatch.retentionDays = retentionDays;
315
+ if (autoAcceptThreshold !== undefined)
316
+ retentionPatch.autoAcceptThreshold = autoAcceptThreshold;
317
+ if (minInjectConfidence !== undefined)
318
+ retentionPatch.minInjectConfidence = minInjectConfidence;
319
+ if (decay !== undefined)
320
+ retentionPatch.decay = { ...(existingRetention.decay ?? {}), ...decay };
321
+ return { ...current, retentionPolicy: retentionPatch };
322
+ });
323
+ return mcpResponse({
324
+ ok: true,
325
+ message: warning
326
+ ? `Retention policy updated for project "${project}". WARNING: ${warning}`
327
+ : `Retention policy updated for project "${project}".`,
328
+ data: { project, retentionPolicy: next.config?.retentionPolicy ?? {}, ...(warning ? { warning } : {}) },
329
+ });
330
+ }
331
+ const globalPatch = {};
161
332
  if (ttlDays !== undefined)
162
- patch.ttlDays = ttlDays;
333
+ globalPatch.ttlDays = ttlDays;
163
334
  if (retentionDays !== undefined)
164
- patch.retentionDays = retentionDays;
335
+ globalPatch.retentionDays = retentionDays;
165
336
  if (autoAcceptThreshold !== undefined)
166
- patch.autoAcceptThreshold = autoAcceptThreshold;
337
+ globalPatch.autoAcceptThreshold = autoAcceptThreshold;
167
338
  if (minInjectConfidence !== undefined)
168
- patch.minInjectConfidence = minInjectConfidence;
339
+ globalPatch.minInjectConfidence = minInjectConfidence;
169
340
  if (decay !== undefined)
170
- patch.decay = decay;
171
- const result = updateRetentionPolicy(phrenPath, patch);
341
+ globalPatch.decay = decay;
342
+ const result = updateRetentionPolicy(phrenPath, globalPatch);
172
343
  if (!result.ok) {
173
344
  return mcpResponse({ ok: false, error: result.error, errorCode: result.code });
174
345
  }
@@ -182,18 +353,52 @@ export function register(server, ctx) {
182
353
  server.registerTool("set_workflow_policy", {
183
354
  title: "◆ phren · set workflow policy",
184
355
  description: "Update workflow policy: low-confidence threshold, " +
185
- "risky sections list, task mode, and finding sensitivity.",
356
+ "risky sections list, task mode, and finding sensitivity. " +
357
+ "When project is provided, writes to that project's phren.project.yaml.",
186
358
  inputSchema: z.object({
187
359
  lowConfidenceThreshold: z.number().min(0).max(1).optional()
188
360
  .describe("Confidence below which items are flagged as low-confidence."),
189
361
  riskySections: z.array(z.enum(["Review", "Stale", "Conflicts"])).optional()
190
362
  .describe("Which queue sections are considered risky."),
191
- taskMode: z.enum(TASK_MODES).optional()
363
+ taskMode: z.enum(VALID_TASK_MODES).optional()
192
364
  .describe("Task automation mode."),
193
- findingSensitivity: z.enum(FINDING_SENSITIVITY_LEVELS).optional()
365
+ findingSensitivity: z.enum(VALID_FINDING_SENSITIVITY).optional()
194
366
  .describe("Finding capture sensitivity."),
367
+ project: projectParam,
195
368
  }),
196
- }, async ({ lowConfidenceThreshold, riskySections, taskMode, findingSensitivity }) => {
369
+ }, async ({ lowConfidenceThreshold, riskySections, taskMode, findingSensitivity, project }) => {
370
+ if (project) {
371
+ const err = validateProject(project);
372
+ if (err)
373
+ return mcpResponse({ ok: false, error: err });
374
+ const warning = checkProjectRegistered(phrenPath, project);
375
+ const next = updateProjectConfigOverrides(phrenPath, project, (current) => {
376
+ const nextConfig = { ...current };
377
+ const shouldUpdateWorkflowPolicy = (lowConfidenceThreshold !== undefined
378
+ || riskySections !== undefined
379
+ || current.workflowPolicy !== undefined);
380
+ if (shouldUpdateWorkflowPolicy) {
381
+ const existingWorkflow = current.workflowPolicy ?? {};
382
+ nextConfig.workflowPolicy = {
383
+ ...existingWorkflow,
384
+ ...(lowConfidenceThreshold !== undefined ? { lowConfidenceThreshold } : {}),
385
+ ...(riskySections !== undefined ? { riskySections } : {}),
386
+ };
387
+ }
388
+ if (taskMode !== undefined)
389
+ nextConfig.taskMode = taskMode;
390
+ if (findingSensitivity !== undefined)
391
+ nextConfig.findingSensitivity = findingSensitivity;
392
+ return nextConfig;
393
+ });
394
+ return mcpResponse({
395
+ ok: true,
396
+ message: warning
397
+ ? `Workflow policy updated for project "${project}". WARNING: ${warning}`
398
+ : `Workflow policy updated for project "${project}".`,
399
+ data: { project, config: next.config ?? {}, ...(warning ? { warning } : {}) },
400
+ });
401
+ }
197
402
  const patch = {};
198
403
  if (lowConfidenceThreshold !== undefined)
199
404
  patch.lowConfidenceThreshold = lowConfidenceThreshold;
@@ -2,7 +2,7 @@ import { mcpResponse } from "./mcp-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 } from "./utils.js";
5
+ import { isValidProjectName, errorMessage } from "./utils.js";
6
6
  import { readFindings, readTasks, resolveTaskFilePath, TASKS_FILENAME } from "./data-access.js";
7
7
  import { debugLog, findProjectNameCaseInsensitive, normalizeProjectNameForCreate } from "./shared.js";
8
8
  const importPayloadSchema = z.object({
@@ -75,8 +75,8 @@ export function register(server, ctx) {
75
75
  decoded = JSON.parse(rawData);
76
76
  }
77
77
  catch (err) {
78
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
79
- process.stderr.write(`[phren] import_project jsonParse: ${err instanceof Error ? err.message : String(err)}\n`);
78
+ if ((process.env.PHREN_DEBUG))
79
+ process.stderr.write(`[phren] import_project jsonParse: ${errorMessage(err)}\n`);
80
80
  return mcpResponse({ ok: false, error: "Invalid JSON input." });
81
81
  }
82
82
  const parsedResult = importPayloadSchema.safeParse(decoded);
@@ -241,8 +241,8 @@ export function register(server, ctx) {
241
241
  }
242
242
  }
243
243
  catch (err) {
244
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
245
- process.stderr.write(`[phren] import_project backupRestore: ${err instanceof Error ? err.message : String(err)}\n`);
244
+ if ((process.env.PHREN_DEBUG))
245
+ process.stderr.write(`[phren] import_project backupRestore: ${errorMessage(err)}\n`);
246
246
  }
247
247
  }
248
248
  return mcpResponse({
@@ -261,8 +261,8 @@ export function register(server, ctx) {
261
261
  }
262
262
  }
263
263
  catch (err) {
264
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
265
- process.stderr.write(`[phren] import_project backupCleanup: ${err instanceof Error ? err.message : String(err)}\n`);
264
+ if ((process.env.PHREN_DEBUG))
265
+ process.stderr.write(`[phren] import_project backupCleanup: ${errorMessage(err)}\n`);
266
266
  }
267
267
  }
268
268
  return mcpResponse({
@@ -299,7 +299,7 @@ export function register(server, ctx) {
299
299
  }
300
300
  catch (err) {
301
301
  fs.renameSync(archiveDir, projectDir);
302
- return mcpResponse({ ok: false, error: `Index rebuild failed after archive rename, rolled back: ${err instanceof Error ? err.message : String(err)}` });
302
+ return mcpResponse({ ok: false, error: `Index rebuild failed after archive rename, rolled back: ${errorMessage(err)}` });
303
303
  }
304
304
  return mcpResponse({
305
305
  ok: true,
@@ -322,7 +322,7 @@ export function register(server, ctx) {
322
322
  }
323
323
  catch (err) {
324
324
  fs.renameSync(projectDir, archiveDir);
325
- return mcpResponse({ ok: false, error: `Index rebuild failed after unarchive rename, rolled back: ${err instanceof Error ? err.message : String(err)}` });
325
+ return mcpResponse({ ok: false, error: `Index rebuild failed after unarchive rename, rolled back: ${errorMessage(err)}` });
326
326
  }
327
327
  return mcpResponse({
328
328
  ok: true,
@@ -24,7 +24,7 @@ export function readExtractedFacts(phrenPath, project) {
24
24
  return Array.isArray(data) ? data : [];
25
25
  }
26
26
  catch (err) {
27
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
27
+ if ((process.env.PHREN_DEBUG))
28
28
  process.stderr.write(`[phren] readExtractedFacts: ${errorMessage(err)}\n`);
29
29
  return [];
30
30
  }
@@ -1,6 +1,6 @@
1
1
  import { mcpResponse } from "./mcp-types.js";
2
2
  import { z } from "zod";
3
- import { isValidProjectName, safeProjectPath } from "./utils.js";
3
+ import { isValidProjectName, safeProjectPath, errorMessage } from "./utils.js";
4
4
  import { addFindingsToFile } from "./shared-content.js";
5
5
  import { checkOllamaAvailable, checkModelAvailable, generateText, getOllamaUrl, getExtractModel } from "./shared-ollama.js";
6
6
  import { debugLog } from "./shared.js";
@@ -34,7 +34,7 @@ function parseFindings(raw) {
34
34
  }
35
35
  }
36
36
  catch (err) {
37
- debugLog(`auto_extract: failed to parse LLM output as JSON: ${cleaned.slice(0, 200)} (${err instanceof Error ? err.message : String(err)})`);
37
+ debugLog(`auto_extract: failed to parse LLM output as JSON: ${cleaned.slice(0, 200)} (${errorMessage(err)})`);
38
38
  }
39
39
  return [];
40
40
  }
@@ -236,7 +236,7 @@ export function register(server, ctx) {
236
236
  extraAnnotationsByFinding.push(conflicts.checked && conflicts.annotations.length > 0 ? conflicts.annotations : []);
237
237
  }
238
238
  catch (err) {
239
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
239
+ if ((process.env.PHREN_DEBUG))
240
240
  process.stderr.write(`[phren] add_findings semanticConflict: ${errorMessage(err)}\n`);
241
241
  extraAnnotationsByFinding.push([]);
242
242
  }
@@ -499,7 +499,7 @@ export function register(server, ctx) {
499
499
  hasRemote = remotes.length > 0;
500
500
  }
501
501
  catch (err) {
502
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
502
+ if ((process.env.PHREN_DEBUG))
503
503
  process.stderr.write(`[phren] push_changes remoteCheck: ${errorMessage(err)}\n`);
504
504
  }
505
505
  if (!hasRemote) {
@@ -523,7 +523,7 @@ export function register(server, ctx) {
523
523
  runGit(["pull", "--rebase", "--quiet"], { timeout: 15000 });
524
524
  }
525
525
  catch (pullErr) {
526
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
526
+ if ((process.env.PHREN_DEBUG))
527
527
  process.stderr.write(`[phren] push_changes pullRebase: ${pullErr instanceof Error ? pullErr.message : String(pullErr)}\n`);
528
528
  const resolved = autoMergeConflicts(phrenPath);
529
529
  if (resolved) {
@@ -534,13 +534,13 @@ export function register(server, ctx) {
534
534
  });
535
535
  }
536
536
  catch (continueErr) {
537
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
537
+ if ((process.env.PHREN_DEBUG))
538
538
  process.stderr.write(`[phren] push_changes rebaseContinue: ${continueErr instanceof Error ? continueErr.message : String(continueErr)}\n`);
539
539
  try {
540
540
  runGit(["rebase", "--abort"]);
541
541
  }
542
542
  catch (abortErr) {
543
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
543
+ if ((process.env.PHREN_DEBUG))
544
544
  process.stderr.write(`[phren] push_changes rebaseAbort: ${abortErr instanceof Error ? abortErr.message : String(abortErr)}\n`);
545
545
  }
546
546
  break;
@@ -551,7 +551,7 @@ export function register(server, ctx) {
551
551
  runGit(["rebase", "--abort"]);
552
552
  }
553
553
  catch (abortErr) {
554
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
554
+ if ((process.env.PHREN_DEBUG))
555
555
  process.stderr.write(`[phren] push_changes rebaseAbort2: ${abortErr instanceof Error ? abortErr.message : String(abortErr)}\n`);
556
556
  }
557
557
  break;
@@ -2,7 +2,7 @@ import { mcpResponse } from "./mcp-types.js";
2
2
  import { z } from "zod";
3
3
  import * as fs from "fs";
4
4
  import * as crypto from "crypto";
5
- import { isValidProjectName } from "./utils.js";
5
+ import { isValidProjectName, errorMessage } from "./utils.js";
6
6
  import { queryDocBySourceKey, queryRows, queryFragmentLinks, queryCrossProjectFragments, ensureGlobalEntitiesTable, logFragmentMiss } from "./shared-index.js";
7
7
  import { runtimeFile } from "./shared.js";
8
8
  import { withFileLock } from "./shared-governance.js";
@@ -209,8 +209,8 @@ export function register(server, ctx) {
209
209
  db.run("INSERT OR IGNORE INTO entities (name, type, first_seen_at) VALUES (?, ?, ?)", [fragmentName, resolvedFragmentType, new Date().toISOString().slice(0, 10)]);
210
210
  }
211
211
  catch (err) {
212
- if (process.env.PHREN_DEBUG || (process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
213
- process.stderr.write(`[phren] link_findings fragmentInsert: ${err instanceof Error ? err.message : String(err)}\n`);
212
+ if (process.env.PHREN_DEBUG || (process.env.PHREN_DEBUG))
213
+ process.stderr.write(`[phren] link_findings fragmentInsert: ${errorMessage(err)}\n`);
214
214
  }
215
215
  const fragmentResult = db.exec("SELECT id FROM entities WHERE name = ? AND type = ?", [fragmentName, resolvedFragmentType]);
216
216
  if (!fragmentResult?.length || !fragmentResult[0]?.values?.length) {
@@ -232,8 +232,8 @@ export function register(server, ctx) {
232
232
  db.run("INSERT OR IGNORE INTO entities (name, type, first_seen_at) VALUES (?, ?, ?)", [sourceDoc, "document", new Date().toISOString().slice(0, 10)]);
233
233
  }
234
234
  catch (err) {
235
- if (process.env.PHREN_DEBUG || (process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
236
- process.stderr.write(`[phren] link_findings docFragmentInsert: ${err instanceof Error ? err.message : String(err)}\n`);
235
+ if (process.env.PHREN_DEBUG || (process.env.PHREN_DEBUG))
236
+ process.stderr.write(`[phren] link_findings docFragmentInsert: ${errorMessage(err)}\n`);
237
237
  }
238
238
  const docFragmentResult = db.exec("SELECT id FROM entities WHERE name = ? AND type = ?", [sourceDoc, "document"]);
239
239
  if (!docFragmentResult?.length || !docFragmentResult[0]?.values?.length) {
@@ -245,8 +245,8 @@ export function register(server, ctx) {
245
245
  db.run("INSERT OR IGNORE INTO entity_links (source_id, target_id, rel_type, source_doc) VALUES (?, ?, ?, ?)", [sourceId, targetId, relType, sourceDoc]);
246
246
  }
247
247
  catch (err) {
248
- if (process.env.PHREN_DEBUG || (process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
249
- process.stderr.write(`[phren] link_findings linkInsert: ${err instanceof Error ? err.message : String(err)}\n`);
248
+ if (process.env.PHREN_DEBUG || (process.env.PHREN_DEBUG))
249
+ process.stderr.write(`[phren] link_findings linkInsert: ${errorMessage(err)}\n`);
250
250
  return mcpResponse({ ok: false, error: "Failed to insert fragment link." });
251
251
  }
252
252
  // 4a. Also populate global_entities so manual links appear in cross_project_fragments
@@ -255,8 +255,8 @@ export function register(server, ctx) {
255
255
  db.run("INSERT OR IGNORE INTO global_entities (entity, project, doc_key) VALUES (?, ?, ?)", [fragmentName, project, sourceDoc]);
256
256
  }
257
257
  catch (err) {
258
- if (process.env.PHREN_DEBUG || (process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
259
- process.stderr.write(`[phren] link_findings globalFragments: ${err instanceof Error ? err.message : String(err)}\n`);
258
+ if (process.env.PHREN_DEBUG || (process.env.PHREN_DEBUG))
259
+ process.stderr.write(`[phren] link_findings globalFragments: ${errorMessage(err)}\n`);
260
260
  }
261
261
  // 4b. Persist manual link so it survives index rebuilds (mandatory — failure aborts the operation)
262
262
  const manualLinksPath = runtimeFile(ctx.phrenPath, "manual-links.json");
@@ -268,8 +268,8 @@ export function register(server, ctx) {
268
268
  existing = JSON.parse(fs.readFileSync(manualLinksPath, "utf8"));
269
269
  }
270
270
  catch (err) {
271
- if (process.env.PHREN_DEBUG || (process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
272
- process.stderr.write(`[phren] link_findings manualLinksRead: ${err instanceof Error ? err.message : String(err)}\n`);
271
+ if (process.env.PHREN_DEBUG || (process.env.PHREN_DEBUG))
272
+ process.stderr.write(`[phren] link_findings manualLinksRead: ${errorMessage(err)}\n`);
273
273
  }
274
274
  }
275
275
  const newEntry = { entity: fragmentName, entityType: resolvedFragmentType, sourceDoc, relType };