@phren/cli 0.0.1
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.
- package/LICENSE +21 -0
- package/README.md +590 -0
- package/mcp/dist/capabilities/cli.js +61 -0
- package/mcp/dist/capabilities/index.js +15 -0
- package/mcp/dist/capabilities/mcp.js +61 -0
- package/mcp/dist/capabilities/types.js +57 -0
- package/mcp/dist/capabilities/vscode.js +61 -0
- package/mcp/dist/capabilities/web-ui.js +61 -0
- package/mcp/dist/cli-actions.js +302 -0
- package/mcp/dist/cli-config.js +580 -0
- package/mcp/dist/cli-extract.js +305 -0
- package/mcp/dist/cli-govern.js +371 -0
- package/mcp/dist/cli-graph.js +169 -0
- package/mcp/dist/cli-hooks-citations.js +44 -0
- package/mcp/dist/cli-hooks-context.js +56 -0
- package/mcp/dist/cli-hooks-globs.js +83 -0
- package/mcp/dist/cli-hooks-output.js +130 -0
- package/mcp/dist/cli-hooks-retrieval.js +2 -0
- package/mcp/dist/cli-hooks-session.js +1402 -0
- package/mcp/dist/cli-hooks.js +350 -0
- package/mcp/dist/cli-namespaces.js +989 -0
- package/mcp/dist/cli-ops.js +253 -0
- package/mcp/dist/cli-search.js +407 -0
- package/mcp/dist/cli.js +108 -0
- package/mcp/dist/content-archive.js +278 -0
- package/mcp/dist/content-citation.js +391 -0
- package/mcp/dist/content-dedup.js +622 -0
- package/mcp/dist/content-learning.js +472 -0
- package/mcp/dist/content-metadata.js +186 -0
- package/mcp/dist/content-validate.js +462 -0
- package/mcp/dist/core-finding.js +54 -0
- package/mcp/dist/core-project.js +36 -0
- package/mcp/dist/core-search.js +50 -0
- package/mcp/dist/data-access.js +400 -0
- package/mcp/dist/data-tasks.js +821 -0
- package/mcp/dist/embedding.js +344 -0
- package/mcp/dist/entrypoint.js +387 -0
- package/mcp/dist/finding-context.js +172 -0
- package/mcp/dist/finding-impact.js +181 -0
- package/mcp/dist/finding-journal.js +122 -0
- package/mcp/dist/finding-lifecycle.js +259 -0
- package/mcp/dist/governance-audit.js +22 -0
- package/mcp/dist/governance-locks.js +96 -0
- package/mcp/dist/governance-policy.js +648 -0
- package/mcp/dist/governance-scores.js +355 -0
- package/mcp/dist/hooks.js +449 -0
- package/mcp/dist/impact-scoring.js +22 -0
- package/mcp/dist/index-query.js +168 -0
- package/mcp/dist/index.js +205 -0
- package/mcp/dist/init-config.js +336 -0
- package/mcp/dist/init-preferences.js +62 -0
- package/mcp/dist/init-setup.js +1305 -0
- package/mcp/dist/init-shared.js +29 -0
- package/mcp/dist/init.js +1730 -0
- package/mcp/dist/link-checksums.js +62 -0
- package/mcp/dist/link-context.js +257 -0
- package/mcp/dist/link-doctor.js +591 -0
- package/mcp/dist/link-skills.js +212 -0
- package/mcp/dist/link.js +596 -0
- package/mcp/dist/logger.js +15 -0
- package/mcp/dist/machine-identity.js +38 -0
- package/mcp/dist/mcp-config.js +254 -0
- package/mcp/dist/mcp-data.js +315 -0
- package/mcp/dist/mcp-extract-facts.js +78 -0
- package/mcp/dist/mcp-extract.js +133 -0
- package/mcp/dist/mcp-finding.js +557 -0
- package/mcp/dist/mcp-graph.js +339 -0
- package/mcp/dist/mcp-hooks.js +256 -0
- package/mcp/dist/mcp-memory.js +58 -0
- package/mcp/dist/mcp-ops.js +328 -0
- package/mcp/dist/mcp-search.js +628 -0
- package/mcp/dist/mcp-session.js +651 -0
- package/mcp/dist/mcp-skills.js +189 -0
- package/mcp/dist/mcp-tasks.js +551 -0
- package/mcp/dist/mcp-types.js +7 -0
- package/mcp/dist/memory-ui-assets.js +6 -0
- package/mcp/dist/memory-ui-data.js +513 -0
- package/mcp/dist/memory-ui-graph.js +1910 -0
- package/mcp/dist/memory-ui-page.js +353 -0
- package/mcp/dist/memory-ui-scripts.js +1387 -0
- package/mcp/dist/memory-ui-server.js +1218 -0
- package/mcp/dist/memory-ui-styles.js +555 -0
- package/mcp/dist/memory-ui.js +9 -0
- package/mcp/dist/package-metadata.js +13 -0
- package/mcp/dist/phren-art.js +52 -0
- package/mcp/dist/phren-core.js +108 -0
- package/mcp/dist/phren-dotenv.js +67 -0
- package/mcp/dist/phren-paths.js +476 -0
- package/mcp/dist/proactivity.js +172 -0
- package/mcp/dist/profile-store.js +228 -0
- package/mcp/dist/project-config.js +85 -0
- package/mcp/dist/project-locator.js +25 -0
- package/mcp/dist/project-topics.js +1134 -0
- package/mcp/dist/provider-adapters.js +176 -0
- package/mcp/dist/runtime-profile.js +18 -0
- package/mcp/dist/session-checkpoints.js +131 -0
- package/mcp/dist/session-utils.js +68 -0
- package/mcp/dist/shared-content.js +8 -0
- package/mcp/dist/shared-embedding-cache.js +143 -0
- package/mcp/dist/shared-fragment-graph.js +456 -0
- package/mcp/dist/shared-governance.js +4 -0
- package/mcp/dist/shared-index.js +1334 -0
- package/mcp/dist/shared-ollama.js +192 -0
- package/mcp/dist/shared-paths.js +1 -0
- package/mcp/dist/shared-retrieval.js +796 -0
- package/mcp/dist/shared-search-fallback.js +375 -0
- package/mcp/dist/shared-sqljs.js +42 -0
- package/mcp/dist/shared-stemmer.js +171 -0
- package/mcp/dist/shared-vector-index.js +199 -0
- package/mcp/dist/shared.js +114 -0
- package/mcp/dist/shell-entry.js +209 -0
- package/mcp/dist/shell-input.js +943 -0
- package/mcp/dist/shell-palette.js +119 -0
- package/mcp/dist/shell-render.js +252 -0
- package/mcp/dist/shell-state-store.js +81 -0
- package/mcp/dist/shell-types.js +13 -0
- package/mcp/dist/shell-view-list.js +14 -0
- package/mcp/dist/shell-view.js +707 -0
- package/mcp/dist/shell.js +352 -0
- package/mcp/dist/skill-files.js +117 -0
- package/mcp/dist/skill-registry.js +279 -0
- package/mcp/dist/skill-state.js +28 -0
- package/mcp/dist/startup-embedding.js +57 -0
- package/mcp/dist/status.js +323 -0
- package/mcp/dist/synonyms.json +670 -0
- package/mcp/dist/task-hygiene.js +251 -0
- package/mcp/dist/task-lifecycle.js +347 -0
- package/mcp/dist/tasks-github.js +76 -0
- package/mcp/dist/telemetry.js +165 -0
- package/mcp/dist/test-global-setup.js +37 -0
- package/mcp/dist/tool-registry.js +104 -0
- package/mcp/dist/update.js +97 -0
- package/mcp/dist/utils.js +543 -0
- package/package.json +67 -0
- package/skills/README.md +7 -0
- package/skills/consolidate/SKILL.md +152 -0
- package/skills/discover/SKILL.md +175 -0
- package/skills/init/SKILL.md +216 -0
- package/skills/profiles/SKILL.md +121 -0
- package/skills/sync/SKILL.md +261 -0
- package/starter/README.md +74 -0
- package/starter/global/CLAUDE.md +89 -0
- package/starter/global/skills/humanize.md +30 -0
- package/starter/global/skills/pipeline.md +35 -0
- package/starter/global/skills/release.md +35 -0
- package/starter/machines.yaml +8 -0
- package/starter/my-api/.claude/skills/README.md +7 -0
- package/starter/my-api/CLAUDE.md +33 -0
- package/starter/my-api/FINDINGS.md +9 -0
- package/starter/my-api/summary.md +7 -0
- package/starter/my-api/tasks.md +7 -0
- package/starter/my-first-project/.claude/skills/README.md +7 -0
- package/starter/my-first-project/CLAUDE.md +49 -0
- package/starter/my-first-project/FINDINGS.md +24 -0
- package/starter/my-first-project/summary.md +11 -0
- package/starter/my-first-project/tasks.md +25 -0
- package/starter/my-frontend/.claude/skills/README.md +7 -0
- package/starter/my-frontend/CLAUDE.md +33 -0
- package/starter/my-frontend/FINDINGS.md +9 -0
- package/starter/my-frontend/summary.md +7 -0
- package/starter/my-frontend/tasks.md +7 -0
- package/starter/profiles/default.yaml +4 -0
- package/starter/profiles/personal.yaml +4 -0
- package/starter/profiles/work.yaml +4 -0
- package/starter/templates/README.md +7 -0
- package/starter/templates/frontend/CLAUDE.md +23 -0
- package/starter/templates/frontend/FINDINGS.md +7 -0
- package/starter/templates/frontend/reference/README.md +4 -0
- package/starter/templates/frontend/summary.md +7 -0
- package/starter/templates/frontend/tasks.md +11 -0
- package/starter/templates/library/CLAUDE.md +22 -0
- package/starter/templates/library/FINDINGS.md +7 -0
- package/starter/templates/library/reference/README.md +4 -0
- package/starter/templates/library/summary.md +7 -0
- package/starter/templates/library/tasks.md +11 -0
- package/starter/templates/monorepo/CLAUDE.md +21 -0
- package/starter/templates/monorepo/FINDINGS.md +7 -0
- package/starter/templates/monorepo/reference/README.md +4 -0
- package/starter/templates/monorepo/summary.md +7 -0
- package/starter/templates/monorepo/tasks.md +11 -0
- package/starter/templates/python-project/CLAUDE.md +21 -0
- package/starter/templates/python-project/FINDINGS.md +7 -0
- package/starter/templates/python-project/reference/README.md +4 -0
- package/starter/templates/python-project/summary.md +7 -0
- package/starter/templates/python-project/tasks.md +10 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
import { mcpResponse } from "./mcp-types.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
import * as fs from "fs";
|
|
4
|
+
import * as path from "path";
|
|
5
|
+
import { runtimeFile, getProjectDirs } from "./shared.js";
|
|
6
|
+
import { findFtsCacheForPath } from "./shared-index.js";
|
|
7
|
+
import { isValidProjectName } from "./utils.js";
|
|
8
|
+
import { readReviewQueue, readReviewQueueAcrossProjects } from "./data-access.js";
|
|
9
|
+
import { addProjectFromPath } from "./core-project.js";
|
|
10
|
+
import { PROJECT_OWNERSHIP_MODES, parseProjectOwnershipMode } from "./project-config.js";
|
|
11
|
+
import { resolveRuntimeProfile } from "./runtime-profile.js";
|
|
12
|
+
import { getMachineName } from "./machine-identity.js";
|
|
13
|
+
import { getProjectConsolidationStatus, CONSOLIDATION_ENTRY_THRESHOLD } from "./content-validate.js";
|
|
14
|
+
/** Translate a PhrenResult<string> into a standard McpToolResult shape. */
|
|
15
|
+
function phrenResultToMcp(result) {
|
|
16
|
+
if (result.ok) {
|
|
17
|
+
return { ok: true, message: result.data };
|
|
18
|
+
}
|
|
19
|
+
return { ok: false, error: result.error, errorCode: result.code };
|
|
20
|
+
}
|
|
21
|
+
export function register(server, ctx) {
|
|
22
|
+
const { phrenPath, profile, withWriteQueue, updateFileInIndex } = ctx;
|
|
23
|
+
// ── get_consolidation_status ───────────────────────────────────────────────
|
|
24
|
+
server.registerTool("add_project", {
|
|
25
|
+
title: "◆ phren · add project",
|
|
26
|
+
description: "Bootstrap a project into phren from a repo or working directory. " +
|
|
27
|
+
"Copies or creates CLAUDE.md/summary/tasks/findings under ~/.phren/<project> and adds the project to the active profile.",
|
|
28
|
+
inputSchema: z.object({
|
|
29
|
+
path: z.string().describe("Project path to import. Pass the current repo path explicitly."),
|
|
30
|
+
profile: z.string().optional().describe("Profile to update. Defaults to the active profile."),
|
|
31
|
+
ownership: z.enum(PROJECT_OWNERSHIP_MODES).optional()
|
|
32
|
+
.describe("How Phren should treat repo-facing instruction files: phren-managed, detached, or repo-managed."),
|
|
33
|
+
}),
|
|
34
|
+
}, async ({ path: targetPath, profile: requestedProfile, ownership }) => {
|
|
35
|
+
return withWriteQueue(async () => {
|
|
36
|
+
try {
|
|
37
|
+
const added = addProjectFromPath(phrenPath, targetPath, requestedProfile || profile || undefined, parseProjectOwnershipMode(ownership) ?? undefined);
|
|
38
|
+
if (!added.ok) {
|
|
39
|
+
return mcpResponse({
|
|
40
|
+
ok: false,
|
|
41
|
+
error: added.error,
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
await ctx.rebuildIndex();
|
|
45
|
+
return mcpResponse({
|
|
46
|
+
ok: true,
|
|
47
|
+
message: `Added project "${added.data.project}" (${added.data.ownership}) from ${added.data.path}.`,
|
|
48
|
+
data: added.data,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
catch (err) {
|
|
52
|
+
return mcpResponse({
|
|
53
|
+
ok: false,
|
|
54
|
+
error: err instanceof Error ? err.message : String(err),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
server.registerTool("get_consolidation_status", {
|
|
60
|
+
title: "◆ phren · consolidation status",
|
|
61
|
+
description: "Check whether a project's FINDINGS.md needs consolidation. " +
|
|
62
|
+
"Returns entry count since last consolidation, threshold, and recommendation.",
|
|
63
|
+
inputSchema: z.object({
|
|
64
|
+
project: z.string().optional().describe("Project name. If omitted, checks all projects."),
|
|
65
|
+
}),
|
|
66
|
+
}, async ({ project }) => {
|
|
67
|
+
const projectDirs = project
|
|
68
|
+
? (() => {
|
|
69
|
+
if (!isValidProjectName(project))
|
|
70
|
+
return [];
|
|
71
|
+
const dir = path.join(phrenPath, project);
|
|
72
|
+
return fs.existsSync(dir) ? [dir] : [];
|
|
73
|
+
})()
|
|
74
|
+
: getProjectDirs(phrenPath, profile);
|
|
75
|
+
if (project && projectDirs.length === 0) {
|
|
76
|
+
return mcpResponse({ ok: false, error: `Project "${project}" not found.` });
|
|
77
|
+
}
|
|
78
|
+
const results = [];
|
|
79
|
+
for (const dir of projectDirs) {
|
|
80
|
+
const status = getProjectConsolidationStatus(dir);
|
|
81
|
+
if (!status)
|
|
82
|
+
continue;
|
|
83
|
+
results.push({ ...status, threshold: CONSOLIDATION_ENTRY_THRESHOLD });
|
|
84
|
+
}
|
|
85
|
+
if (results.length === 0) {
|
|
86
|
+
return mcpResponse({ ok: true, message: "No FINDINGS.md files found.", data: { results: [] } });
|
|
87
|
+
}
|
|
88
|
+
const lines = results.map(r => `${r.project}: ${r.entriesSince} entries since${r.lastConsolidated ? ` ${r.lastConsolidated}` : " (never consolidated)"}` +
|
|
89
|
+
`${r.recommended ? " — consolidation recommended" : ""}`);
|
|
90
|
+
return mcpResponse({
|
|
91
|
+
ok: true,
|
|
92
|
+
message: lines.join("\n"),
|
|
93
|
+
data: { results },
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
// ── health_check ───────────────────────────────────────────────────────────
|
|
97
|
+
server.registerTool("health_check", {
|
|
98
|
+
title: "◆ phren · health",
|
|
99
|
+
description: "Return phren health status: version, FTS index status, hook registration, and profile/machine info.",
|
|
100
|
+
inputSchema: z.object({}),
|
|
101
|
+
}, async () => {
|
|
102
|
+
const activeProfile = (() => {
|
|
103
|
+
try {
|
|
104
|
+
return resolveRuntimeProfile(phrenPath);
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
return profile || "";
|
|
108
|
+
}
|
|
109
|
+
})();
|
|
110
|
+
// Version
|
|
111
|
+
let version = "unknown";
|
|
112
|
+
try {
|
|
113
|
+
const pkgPath = path.join(path.dirname(new URL(import.meta.url).pathname), "..", "..", "package.json");
|
|
114
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
115
|
+
version = pkg.version || "unknown";
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
|
|
119
|
+
process.stderr.write(`[phren] healthCheck version: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
120
|
+
}
|
|
121
|
+
// FTS index (lives in /tmpphren-fts-*/, not .runtime/)
|
|
122
|
+
let indexStatus = { exists: false };
|
|
123
|
+
try {
|
|
124
|
+
indexStatus = findFtsCacheForPath(phrenPath, activeProfile);
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
|
|
128
|
+
process.stderr.write(`[phren] healthCheck ftsCacheCheck: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
129
|
+
}
|
|
130
|
+
// Hook registration
|
|
131
|
+
let hooksEnabled = false;
|
|
132
|
+
try {
|
|
133
|
+
const { getHooksEnabledPreference } = await import("./init-preferences.js");
|
|
134
|
+
hooksEnabled = getHooksEnabledPreference(phrenPath);
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
|
|
138
|
+
process.stderr.write(`[phren] healthCheck hooksEnabled: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
139
|
+
}
|
|
140
|
+
let mcpEnabled = false;
|
|
141
|
+
try {
|
|
142
|
+
const { getMcpEnabledPreference } = await import("./init-preferences.js");
|
|
143
|
+
mcpEnabled = getMcpEnabledPreference(phrenPath);
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
|
|
147
|
+
process.stderr.write(`[phren] healthCheck mcpEnabled: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
148
|
+
}
|
|
149
|
+
// Profile/machine info
|
|
150
|
+
const machineName = (() => {
|
|
151
|
+
try {
|
|
152
|
+
return getMachineName();
|
|
153
|
+
}
|
|
154
|
+
catch (err) {
|
|
155
|
+
if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
|
|
156
|
+
process.stderr.write(`[phren] healthCheck machineName: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
157
|
+
}
|
|
158
|
+
return undefined;
|
|
159
|
+
})();
|
|
160
|
+
const projectCount = getProjectDirs(phrenPath, activeProfile).length;
|
|
161
|
+
// Proactivity and taskMode
|
|
162
|
+
let proactivity = "high";
|
|
163
|
+
let taskMode = "auto";
|
|
164
|
+
try {
|
|
165
|
+
const { getWorkflowPolicy } = await import("./governance-policy.js");
|
|
166
|
+
const workflowPolicy = getWorkflowPolicy(phrenPath);
|
|
167
|
+
taskMode = workflowPolicy.taskMode;
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
|
|
171
|
+
process.stderr.write(`[phren] healthCheck taskMode: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
172
|
+
}
|
|
173
|
+
try {
|
|
174
|
+
const { readInstallPreferences } = await import("./init-preferences.js");
|
|
175
|
+
const prefs = readInstallPreferences(phrenPath);
|
|
176
|
+
proactivity = prefs.proactivity || "high";
|
|
177
|
+
}
|
|
178
|
+
catch (err) {
|
|
179
|
+
if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
|
|
180
|
+
process.stderr.write(`[phren] healthCheck proactivity: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
181
|
+
}
|
|
182
|
+
const lines = [
|
|
183
|
+
`Phren v${version}`,
|
|
184
|
+
`Profile: ${activeProfile || "(default)"}`,
|
|
185
|
+
machineName ? `Machine: ${machineName}` : null,
|
|
186
|
+
`Projects: ${projectCount}`,
|
|
187
|
+
`FTS index: ${indexStatus.exists ? `ok (${Math.round((indexStatus.sizeBytes ?? 0) / 1024)} KB)` : "missing"}`,
|
|
188
|
+
`MCP: ${mcpEnabled ? "enabled" : "disabled"}`,
|
|
189
|
+
`Hooks: ${hooksEnabled ? "enabled" : "disabled"}`,
|
|
190
|
+
`Proactivity: ${proactivity}`,
|
|
191
|
+
`Task mode: ${taskMode}`,
|
|
192
|
+
`Path: ${phrenPath}`,
|
|
193
|
+
].filter(Boolean);
|
|
194
|
+
return mcpResponse({
|
|
195
|
+
ok: true,
|
|
196
|
+
message: lines.join("\n"),
|
|
197
|
+
data: {
|
|
198
|
+
version,
|
|
199
|
+
profile: activeProfile || "(default)",
|
|
200
|
+
machine: machineName ?? null,
|
|
201
|
+
projectCount,
|
|
202
|
+
index: indexStatus,
|
|
203
|
+
mcpEnabled,
|
|
204
|
+
hooksEnabled,
|
|
205
|
+
proactivity,
|
|
206
|
+
taskMode,
|
|
207
|
+
phrenPath,
|
|
208
|
+
},
|
|
209
|
+
});
|
|
210
|
+
});
|
|
211
|
+
// ── doctor_fix ─────────────────────────────────────────────────────────────
|
|
212
|
+
server.registerTool("doctor_fix", {
|
|
213
|
+
title: "◆ phren · doctor fix",
|
|
214
|
+
description: "Run phren doctor with --fix: re-links hooks, symlinks, context, and memory pointers. " +
|
|
215
|
+
"Returns the list of checks and repair actions taken.",
|
|
216
|
+
inputSchema: z.object({
|
|
217
|
+
check_data: z.boolean().optional()
|
|
218
|
+
.describe("Also validate data files (tasks, findings, governance). Default false."),
|
|
219
|
+
}),
|
|
220
|
+
}, async ({ check_data }) => {
|
|
221
|
+
const { runDoctor } = await import("./link-doctor.js");
|
|
222
|
+
const result = await runDoctor(phrenPath, true, check_data ?? false);
|
|
223
|
+
const lines = result.checks.map((c) => `${c.ok ? "ok" : "FAIL"} ${c.name}: ${c.detail}`);
|
|
224
|
+
return mcpResponse({
|
|
225
|
+
ok: result.ok,
|
|
226
|
+
message: result.ok
|
|
227
|
+
? `Doctor fix complete: all ${result.checks.length} checks passed`
|
|
228
|
+
: `Doctor fix complete: ${result.checks.filter((c) => !c.ok).length} issue(s) remain`,
|
|
229
|
+
data: {
|
|
230
|
+
machine: result.machine,
|
|
231
|
+
profile: result.profile,
|
|
232
|
+
checks: result.checks,
|
|
233
|
+
summary: lines.join("\n"),
|
|
234
|
+
},
|
|
235
|
+
});
|
|
236
|
+
});
|
|
237
|
+
// ── list_hook_errors ───────────────────────────────────────────────────────
|
|
238
|
+
server.registerTool("list_hook_errors", {
|
|
239
|
+
title: "◆ phren · hook errors",
|
|
240
|
+
description: "List recent error entries from phren hook-errors.log and debug.log. " +
|
|
241
|
+
"Useful for diagnosing hook or index failures.",
|
|
242
|
+
inputSchema: z.object({
|
|
243
|
+
limit: z.number().int().min(1).max(200).optional()
|
|
244
|
+
.describe("Max error entries to return (default 20)."),
|
|
245
|
+
}),
|
|
246
|
+
}, async ({ limit }) => {
|
|
247
|
+
const maxEntries = limit ?? 20;
|
|
248
|
+
const ERROR_PATTERNS = [
|
|
249
|
+
/\berror\b/i,
|
|
250
|
+
/\bfail(ed|ure|s)?\b/i,
|
|
251
|
+
/\bcrash(ed)?\b/i,
|
|
252
|
+
/\btimeout\b/i,
|
|
253
|
+
/\bEXCEPTION\b/i,
|
|
254
|
+
/\bEACCES\b/,
|
|
255
|
+
/\bENOENT\b/,
|
|
256
|
+
/\bEPERM\b/,
|
|
257
|
+
/\bENOSPC\b/,
|
|
258
|
+
];
|
|
259
|
+
function readErrorLines(filePath, filterPatterns) {
|
|
260
|
+
try {
|
|
261
|
+
if (!fs.existsSync(filePath))
|
|
262
|
+
return [];
|
|
263
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
264
|
+
const lines = content.split("\n").filter(l => l.trim());
|
|
265
|
+
if (!filterPatterns)
|
|
266
|
+
return lines; // hook-errors.log: every line is an error
|
|
267
|
+
return lines.filter(line => ERROR_PATTERNS.some(p => p.test(line)));
|
|
268
|
+
}
|
|
269
|
+
catch (err) {
|
|
270
|
+
if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
|
|
271
|
+
process.stderr.write(`[phren] readErrorLines: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
272
|
+
return [];
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
// hook-errors.log contains only hook failure lines (no filtering needed)
|
|
276
|
+
const hookErrors = readErrorLines(runtimeFile(phrenPath, "hook-errors.log"), false);
|
|
277
|
+
// debug.log may contain non-error lines, so filter
|
|
278
|
+
const debugErrors = readErrorLines(runtimeFile(phrenPath, "debug.log"), true);
|
|
279
|
+
const allErrors = [...hookErrors, ...debugErrors];
|
|
280
|
+
if (allErrors.length === 0) {
|
|
281
|
+
return mcpResponse({
|
|
282
|
+
ok: true,
|
|
283
|
+
message: "No error entries found. Hook errors go to hook-errors.log; general errors require PHREN_DEBUG=1.",
|
|
284
|
+
data: { errors: [], total: 0 },
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
const recent = allErrors.slice(-maxEntries);
|
|
288
|
+
return mcpResponse({
|
|
289
|
+
ok: true,
|
|
290
|
+
message: `Found ${allErrors.length} error(s), showing last ${recent.length}:\n\n${recent.join("\n")}`,
|
|
291
|
+
data: { errors: recent, total: allErrors.length, sources: { hookErrors: hookErrors.length, debugErrors: debugErrors.length } },
|
|
292
|
+
});
|
|
293
|
+
});
|
|
294
|
+
// ── get_review_queue ─────────────────────────────────────────────────────
|
|
295
|
+
server.registerTool("get_review_queue", {
|
|
296
|
+
title: "◆ phren · get review queue",
|
|
297
|
+
description: "List all items in a project's memory review queue (MEMORY_QUEUE.md), or across all projects when omitted. " +
|
|
298
|
+
"Returns items with their id, section (Review/Stale/Conflicts), date, text, confidence, and risky flag.",
|
|
299
|
+
inputSchema: z.object({
|
|
300
|
+
project: z.string().optional().describe("Project name. Omit to read the review queue across all projects in the active profile."),
|
|
301
|
+
}),
|
|
302
|
+
}, async ({ project }) => {
|
|
303
|
+
if (project && !isValidProjectName(project)) {
|
|
304
|
+
return mcpResponse({ ok: false, error: `Invalid project name: "${project}".` });
|
|
305
|
+
}
|
|
306
|
+
if (project) {
|
|
307
|
+
const result = readReviewQueue(phrenPath, project);
|
|
308
|
+
if (!result.ok) {
|
|
309
|
+
return mcpResponse({ ok: false, error: result.error, errorCode: result.code });
|
|
310
|
+
}
|
|
311
|
+
const items = result.data.map((item) => ({ ...item, project }));
|
|
312
|
+
return mcpResponse({
|
|
313
|
+
ok: true,
|
|
314
|
+
message: `${items.length} queue item(s) for "${project}".`,
|
|
315
|
+
data: { items },
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
const result = readReviewQueueAcrossProjects(phrenPath, profile);
|
|
319
|
+
if (!result.ok) {
|
|
320
|
+
return mcpResponse({ ok: false, error: result.error, errorCode: result.code });
|
|
321
|
+
}
|
|
322
|
+
return mcpResponse({
|
|
323
|
+
ok: true,
|
|
324
|
+
message: `${result.data.length} queue item(s) across all projects.`,
|
|
325
|
+
data: { items: result.data },
|
|
326
|
+
});
|
|
327
|
+
});
|
|
328
|
+
}
|