@phren/cli 0.0.10 → 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.
- package/README.md +2 -8
- package/mcp/dist/cli-actions.js +5 -5
- package/mcp/dist/cli-config.js +334 -127
- package/mcp/dist/cli-govern.js +35 -63
- package/mcp/dist/cli-graph.js +3 -2
- package/mcp/dist/cli-hooks-globs.js +2 -1
- package/mcp/dist/cli-hooks-output.js +3 -3
- package/mcp/dist/cli-hooks.js +39 -32
- package/mcp/dist/cli-namespaces.js +15 -5
- package/mcp/dist/cli-search.js +2 -2
- package/mcp/dist/content-archive.js +2 -2
- package/mcp/dist/content-dedup.js +9 -9
- package/mcp/dist/embedding.js +7 -7
- package/mcp/dist/entrypoint.js +129 -102
- package/mcp/dist/governance-locks.js +6 -5
- package/mcp/dist/governance-policy.js +155 -2
- package/mcp/dist/governance-scores.js +3 -3
- package/mcp/dist/hooks.js +39 -18
- package/mcp/dist/index.js +4 -4
- package/mcp/dist/init-config.js +3 -24
- package/mcp/dist/init-setup.js +5 -5
- package/mcp/dist/init.js +170 -23
- package/mcp/dist/link-checksums.js +3 -2
- package/mcp/dist/link-context.js +1 -1
- package/mcp/dist/link-doctor.js +3 -3
- package/mcp/dist/link-skills.js +98 -12
- package/mcp/dist/link.js +17 -27
- package/mcp/dist/machine-identity.js +1 -9
- package/mcp/dist/mcp-config.js +247 -42
- package/mcp/dist/mcp-data.js +9 -9
- package/mcp/dist/mcp-extract-facts.js +1 -1
- package/mcp/dist/mcp-extract.js +2 -2
- package/mcp/dist/mcp-finding.js +6 -6
- package/mcp/dist/mcp-graph.js +11 -11
- package/mcp/dist/mcp-ops.js +18 -18
- package/mcp/dist/mcp-search.js +8 -8
- package/mcp/dist/memory-ui-page.js +23 -0
- package/mcp/dist/memory-ui-scripts.js +210 -27
- package/mcp/dist/memory-ui-server.js +115 -3
- package/mcp/dist/phren-paths.js +7 -7
- package/mcp/dist/profile-store.js +2 -2
- package/mcp/dist/project-config.js +63 -16
- package/mcp/dist/session-utils.js +3 -2
- package/mcp/dist/shared-fragment-graph.js +22 -21
- package/mcp/dist/shared-index.js +144 -105
- package/mcp/dist/shared-retrieval.js +19 -13
- package/mcp/dist/shared-search-fallback.js +13 -13
- package/mcp/dist/shared-sqljs.js +3 -2
- package/mcp/dist/shared.js +3 -3
- package/mcp/dist/shell-input.js +1 -1
- package/mcp/dist/shell-state-store.js +1 -1
- package/mcp/dist/shell-view.js +3 -2
- package/mcp/dist/shell.js +1 -1
- package/mcp/dist/skill-files.js +4 -10
- package/mcp/dist/skill-registry.js +3 -0
- package/mcp/dist/status.js +41 -13
- package/mcp/dist/task-hygiene.js +1 -1
- package/mcp/dist/telemetry.js +5 -4
- package/mcp/dist/update.js +1 -1
- package/mcp/dist/utils.js +3 -3
- package/package.json +2 -2
- package/starter/global/skills/audit.md +106 -0
- package/mcp/dist/shared-paths.js +0 -1
package/mcp/dist/link.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
2
|
import * as path from "path";
|
|
3
|
-
import * as crypto from "crypto";
|
|
4
3
|
import * as readline from "readline";
|
|
5
4
|
import * as yaml from "js-yaml";
|
|
6
5
|
import { execFileSync } from "child_process";
|
|
7
|
-
import {
|
|
6
|
+
import { ROOT } from "./package-metadata.js";
|
|
8
7
|
import { configureClaude, configureCodexMcp, configureCopilotMcp, configureCursorMcp, configureVSCode, ensureGovernanceFiles, getHooksEnabledPreference, getMcpEnabledPreference, isVersionNewer, logMcpTargetStatus, patchJsonFile, setMcpEnabledPreference, } from "./init.js";
|
|
9
8
|
import { configureAllHooks, detectInstalledTools } from "./hooks.js";
|
|
10
9
|
import { getMachineName, persistMachineName } from "./machine-identity.js";
|
|
11
|
-
import { debugLog, EXEC_TIMEOUT_MS, EXEC_TIMEOUT_QUICK_MS, isRecord, homePath, hookConfigPath, installPreferencesFile, } from "./shared.js";
|
|
10
|
+
import { debugLog, EXEC_TIMEOUT_MS, EXEC_TIMEOUT_QUICK_MS, isRecord, homePath, hookConfigPath, installPreferencesFile, atomicWriteText, } from "./shared.js";
|
|
12
11
|
import { errorMessage } from "./utils.js";
|
|
12
|
+
import { log } from "./init-shared.js";
|
|
13
13
|
import { listMachines as listMachinesShared, listProfiles as listProfilesShared, setMachineProfile, } from "./profile-store.js";
|
|
14
|
-
import { writeSkillMd } from "./link-skills.js";
|
|
14
|
+
import { writeSkillMd, isManagedSymlink } from "./link-skills.js";
|
|
15
15
|
import { syncScopeSkillsToDir } from "./skill-files.js";
|
|
16
16
|
import { renderSkillInstructionsSection } from "./skill-registry.js";
|
|
17
17
|
import { findProjectDir } from "./project-locator.js";
|
|
@@ -23,14 +23,6 @@ export { updateFileChecksums, verifyFileChecksums } from "./link-checksums.js";
|
|
|
23
23
|
export { findProjectDir } from "./project-locator.js";
|
|
24
24
|
export { parseSkillFrontmatter, validateSkillFrontmatter, validateSkillsDir, readSkillManifestHooks, } from "./link-skills.js";
|
|
25
25
|
// ── Helpers (exported for link-doctor) ──────────────────────────────────────
|
|
26
|
-
const ROOT = path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
27
|
-
function log(msg) { process.stdout.write(msg + "\n"); }
|
|
28
|
-
function atomicWriteText(filePath, content) {
|
|
29
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
30
|
-
const tmpPath = `${filePath}.tmp-${crypto.randomUUID()}`;
|
|
31
|
-
fs.writeFileSync(tmpPath, content);
|
|
32
|
-
fs.renameSync(tmpPath, filePath);
|
|
33
|
-
}
|
|
34
26
|
export { getMachineName } from "./machine-identity.js";
|
|
35
27
|
export function lookupProfile(phrenPath, machine) {
|
|
36
28
|
const listed = listMachinesShared(phrenPath);
|
|
@@ -123,7 +115,7 @@ function setupSparseCheckout(phrenPath, projects) {
|
|
|
123
115
|
execFileSync("git", ["rev-parse", "--git-dir"], { cwd: phrenPath, stdio: "ignore", timeout: EXEC_TIMEOUT_QUICK_MS });
|
|
124
116
|
}
|
|
125
117
|
catch (err) {
|
|
126
|
-
if ((process.env.PHREN_DEBUG
|
|
118
|
+
if ((process.env.PHREN_DEBUG))
|
|
127
119
|
process.stderr.write(`[phren] setupSparseCheckout notAGitRepo: ${errorMessage(err)}\n`);
|
|
128
120
|
return;
|
|
129
121
|
}
|
|
@@ -184,10 +176,9 @@ function symlinkFile(src, dest, managedRoot) {
|
|
|
184
176
|
if (stat.isSymbolicLink()) {
|
|
185
177
|
const currentTarget = fs.readlinkSync(dest);
|
|
186
178
|
const resolvedTarget = path.resolve(path.dirname(dest), currentTarget);
|
|
187
|
-
const managedPrefix = path.resolve(managedRoot) + path.sep;
|
|
188
179
|
if (resolvedTarget === path.resolve(src))
|
|
189
180
|
return true;
|
|
190
|
-
if (!
|
|
181
|
+
if (!isManagedSymlink(dest, managedRoot)) {
|
|
191
182
|
log(` preserve existing symlink: ${dest}`);
|
|
192
183
|
return false;
|
|
193
184
|
}
|
|
@@ -238,8 +229,7 @@ function writeManagedAgentsFile(src, dest, content, managedRoot) {
|
|
|
238
229
|
if (stat.isSymbolicLink()) {
|
|
239
230
|
const currentTarget = fs.readlinkSync(dest);
|
|
240
231
|
const resolvedTarget = path.resolve(path.dirname(dest), currentTarget);
|
|
241
|
-
|
|
242
|
-
if (resolvedTarget === path.resolve(src) || resolvedTarget.startsWith(managedPrefix)) {
|
|
232
|
+
if (resolvedTarget === path.resolve(src) || isManagedSymlink(dest, managedRoot)) {
|
|
243
233
|
fs.unlinkSync(dest);
|
|
244
234
|
}
|
|
245
235
|
else {
|
|
@@ -278,7 +268,7 @@ function linkGlobal(phrenPath, tools) {
|
|
|
278
268
|
symlinkFile(globalClaude, path.join(copilotInstrDir, "copilot-instructions.md"), phrenPath);
|
|
279
269
|
}
|
|
280
270
|
catch (err) {
|
|
281
|
-
if ((process.env.PHREN_DEBUG
|
|
271
|
+
if ((process.env.PHREN_DEBUG))
|
|
282
272
|
process.stderr.write(`[phren] linkGlobal copilotInstructions: ${errorMessage(err)}\n`);
|
|
283
273
|
}
|
|
284
274
|
}
|
|
@@ -324,7 +314,7 @@ function linkProject(phrenPath, project, tools) {
|
|
|
324
314
|
symlinkFile(src, path.join(copilotDir, "copilot-instructions.md"), phrenPath);
|
|
325
315
|
}
|
|
326
316
|
catch (err) {
|
|
327
|
-
if ((process.env.PHREN_DEBUG
|
|
317
|
+
if ((process.env.PHREN_DEBUG))
|
|
328
318
|
process.stderr.write(`[phren] linkProject copilotInstructions: ${errorMessage(err)}\n`);
|
|
329
319
|
}
|
|
330
320
|
}
|
|
@@ -348,7 +338,7 @@ function linkProject(phrenPath, project, tools) {
|
|
|
348
338
|
addTokenAnnotation(claudeFile);
|
|
349
339
|
}
|
|
350
340
|
catch (err) {
|
|
351
|
-
if ((process.env.PHREN_DEBUG
|
|
341
|
+
if ((process.env.PHREN_DEBUG))
|
|
352
342
|
process.stderr.write(`[phren] linkProject tokenAnnotation: ${errorMessage(err)}\n`);
|
|
353
343
|
}
|
|
354
344
|
}
|
|
@@ -365,7 +355,7 @@ function linkProject(phrenPath, project, tools) {
|
|
|
365
355
|
excludeEntries.push("AGENTS.md");
|
|
366
356
|
}
|
|
367
357
|
catch (err) {
|
|
368
|
-
if ((process.env.PHREN_DEBUG
|
|
358
|
+
if ((process.env.PHREN_DEBUG))
|
|
369
359
|
process.stderr.write(`[phren] linkProject agentsMd: ${errorMessage(err)}\n`);
|
|
370
360
|
}
|
|
371
361
|
}
|
|
@@ -506,7 +496,7 @@ export async function runLink(phrenPath, opts = {}) {
|
|
|
506
496
|
mcpStatus = configureClaude(phrenPath, { mcpEnabled, hooksEnabled }) ?? "installed";
|
|
507
497
|
}
|
|
508
498
|
catch (err) {
|
|
509
|
-
if ((process.env.PHREN_DEBUG
|
|
499
|
+
if ((process.env.PHREN_DEBUG))
|
|
510
500
|
process.stderr.write(`[phren] link configureClaude: ${errorMessage(err)}\n`);
|
|
511
501
|
}
|
|
512
502
|
logMcpTargetStatus("Claude", mcpStatus);
|
|
@@ -515,7 +505,7 @@ export async function runLink(phrenPath, opts = {}) {
|
|
|
515
505
|
vsStatus = configureVSCode(phrenPath, { mcpEnabled }) ?? "no_vscode";
|
|
516
506
|
}
|
|
517
507
|
catch (err) {
|
|
518
|
-
if ((process.env.PHREN_DEBUG
|
|
508
|
+
if ((process.env.PHREN_DEBUG))
|
|
519
509
|
process.stderr.write(`[phren] link configureVSCode: ${errorMessage(err)}\n`);
|
|
520
510
|
}
|
|
521
511
|
logMcpTargetStatus("VS Code", vsStatus);
|
|
@@ -524,7 +514,7 @@ export async function runLink(phrenPath, opts = {}) {
|
|
|
524
514
|
cursorStatus = configureCursorMcp(phrenPath, { mcpEnabled }) ?? "no_cursor";
|
|
525
515
|
}
|
|
526
516
|
catch (err) {
|
|
527
|
-
if ((process.env.PHREN_DEBUG
|
|
517
|
+
if ((process.env.PHREN_DEBUG))
|
|
528
518
|
process.stderr.write(`[phren] link configureCursorMcp: ${errorMessage(err)}\n`);
|
|
529
519
|
}
|
|
530
520
|
logMcpTargetStatus("Cursor", cursorStatus);
|
|
@@ -533,7 +523,7 @@ export async function runLink(phrenPath, opts = {}) {
|
|
|
533
523
|
copilotStatus = configureCopilotMcp(phrenPath, { mcpEnabled }) ?? "no_copilot";
|
|
534
524
|
}
|
|
535
525
|
catch (err) {
|
|
536
|
-
if ((process.env.PHREN_DEBUG
|
|
526
|
+
if ((process.env.PHREN_DEBUG))
|
|
537
527
|
process.stderr.write(`[phren] link configureCopilotMcp: ${errorMessage(err)}\n`);
|
|
538
528
|
}
|
|
539
529
|
logMcpTargetStatus("Copilot CLI", copilotStatus);
|
|
@@ -542,7 +532,7 @@ export async function runLink(phrenPath, opts = {}) {
|
|
|
542
532
|
codexStatus = configureCodexMcp(phrenPath, { mcpEnabled }) ?? "no_codex";
|
|
543
533
|
}
|
|
544
534
|
catch (err) {
|
|
545
|
-
if ((process.env.PHREN_DEBUG
|
|
535
|
+
if ((process.env.PHREN_DEBUG))
|
|
546
536
|
process.stderr.write(`[phren] link configureCodexMcp: ${errorMessage(err)}\n`);
|
|
547
537
|
}
|
|
548
538
|
logMcpTargetStatus("Codex", codexStatus);
|
|
@@ -566,7 +556,7 @@ export async function runLink(phrenPath, opts = {}) {
|
|
|
566
556
|
log(` phren.SKILL.md written (agentskills-compatible tools)`);
|
|
567
557
|
}
|
|
568
558
|
catch (err) {
|
|
569
|
-
if ((process.env.PHREN_DEBUG
|
|
559
|
+
if ((process.env.PHREN_DEBUG))
|
|
570
560
|
process.stderr.write(`[phren] link writeSkillMd: ${errorMessage(err)}\n`);
|
|
571
561
|
}
|
|
572
562
|
log("");
|
|
@@ -1,17 +1,9 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
2
|
import * as os from "os";
|
|
3
|
-
import
|
|
4
|
-
import * as crypto from "crypto";
|
|
5
|
-
import { homePath } from "./shared.js";
|
|
3
|
+
import { homePath, atomicWriteText } from "./shared.js";
|
|
6
4
|
function phrenMachineFilePath() {
|
|
7
5
|
return homePath(".phren", ".machine-id");
|
|
8
6
|
}
|
|
9
|
-
function atomicWriteText(filePath, content) {
|
|
10
|
-
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
11
|
-
const tmpPath = `${filePath}.tmp-${crypto.randomUUID()}`;
|
|
12
|
-
fs.writeFileSync(tmpPath, content);
|
|
13
|
-
fs.renameSync(tmpPath, filePath);
|
|
14
|
-
}
|
|
15
7
|
export function machineFilePath() {
|
|
16
8
|
return phrenMachineFilePath();
|
|
17
9
|
}
|
package/mcp/dist/mcp-config.js
CHANGED
|
@@ -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,
|
|
5
|
-
import {
|
|
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
|
|
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
|
-
|
|
24
|
-
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
333
|
+
globalPatch.ttlDays = ttlDays;
|
|
163
334
|
if (retentionDays !== undefined)
|
|
164
|
-
|
|
335
|
+
globalPatch.retentionDays = retentionDays;
|
|
165
336
|
if (autoAcceptThreshold !== undefined)
|
|
166
|
-
|
|
337
|
+
globalPatch.autoAcceptThreshold = autoAcceptThreshold;
|
|
167
338
|
if (minInjectConfidence !== undefined)
|
|
168
|
-
|
|
339
|
+
globalPatch.minInjectConfidence = minInjectConfidence;
|
|
169
340
|
if (decay !== undefined)
|
|
170
|
-
|
|
171
|
-
const result = updateRetentionPolicy(phrenPath,
|
|
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(
|
|
363
|
+
taskMode: z.enum(VALID_TASK_MODES).optional()
|
|
192
364
|
.describe("Task automation mode."),
|
|
193
|
-
findingSensitivity: z.enum(
|
|
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;
|
package/mcp/dist/mcp-data.js
CHANGED
|
@@ -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
|
|
79
|
-
process.stderr.write(`[phren] import_project jsonParse: ${
|
|
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
|
|
245
|
-
process.stderr.write(`[phren] import_project backupRestore: ${
|
|
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
|
|
265
|
-
process.stderr.write(`[phren] import_project backupCleanup: ${
|
|
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: ${
|
|
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: ${
|
|
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
|
|
27
|
+
if ((process.env.PHREN_DEBUG))
|
|
28
28
|
process.stderr.write(`[phren] readExtractedFacts: ${errorMessage(err)}\n`);
|
|
29
29
|
return [];
|
|
30
30
|
}
|
package/mcp/dist/mcp-extract.js
CHANGED
|
@@ -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)} (${
|
|
37
|
+
debugLog(`auto_extract: failed to parse LLM output as JSON: ${cleaned.slice(0, 200)} (${errorMessage(err)})`);
|
|
38
38
|
}
|
|
39
39
|
return [];
|
|
40
40
|
}
|