@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/README.md
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
<p align="center">
|
|
6
6
|
<a href="https://www.npmjs.com/package/@phren/cli"><img src="https://img.shields.io/npm/v/%40phren%2Fcli?style=flat&labelColor=0D0D0D&color=7C3AED" alt="npm version"></a>
|
|
7
7
|
<a href="https://github.com/alaarab/phren/blob/main/LICENSE"><img src="https://img.shields.io/github/license/alaarab/phren?style=flat&labelColor=0D0D0D&color=7C3AED" alt="license"></a>
|
|
8
|
+
<a href="https://alaarab.github.io/phren/"><img src="https://img.shields.io/badge/docs-alaarab.github.io%2Fphren-7C3AED?style=flat&labelColor=0D0D0D" alt="docs"></a>
|
|
9
|
+
<a href="https://alaarab.github.io/phren/whitepaper.pdf"><img src="https://img.shields.io/badge/whitepaper-PDF-7C3AED?style=flat&labelColor=0D0D0D" alt="whitepaper"></a>
|
|
8
10
|
</p>
|
|
9
11
|
|
|
10
12
|
<p align="center">
|
|
@@ -47,14 +49,6 @@ Init detects your tools, registers MCP servers, and installs lifecycle hooks. Af
|
|
|
47
49
|
|
|
48
50
|
To add a project later, run `phren add` from that directory. To browse what phren knows, run `phren` to open the interactive shell.
|
|
49
51
|
|
|
50
|
-
## Learn more
|
|
51
|
-
|
|
52
|
-
- [Documentation site](https://alaarab.github.io/phren/)
|
|
53
|
-
- [Whitepaper (PDF)](https://alaarab.github.io/phren/whitepaper.pdf)
|
|
54
|
-
- [Architecture](docs/architecture.md) — how retrieval, governance, and persistence fit together
|
|
55
|
-
- [Contributing](CONTRIBUTING.md) — how to add tools, skills, and tests
|
|
56
|
-
- [Security](SECURITY.md)
|
|
57
|
-
|
|
58
52
|
---
|
|
59
53
|
|
|
60
54
|
MIT License. Made by [Ala Arab](https://github.com/alaarab).
|
package/mcp/dist/cli-actions.js
CHANGED
|
@@ -58,7 +58,7 @@ export async function handleAddFinding(project, learning) {
|
|
|
58
58
|
console.log(result.message);
|
|
59
59
|
}
|
|
60
60
|
catch (err) {
|
|
61
|
-
console.error(
|
|
61
|
+
console.error(errorMessage(err));
|
|
62
62
|
process.exit(1);
|
|
63
63
|
}
|
|
64
64
|
}
|
|
@@ -164,7 +164,7 @@ export async function handleDoctor(args) {
|
|
|
164
164
|
}
|
|
165
165
|
}
|
|
166
166
|
catch (err) {
|
|
167
|
-
if ((process.env.PHREN_DEBUG
|
|
167
|
+
if ((process.env.PHREN_DEBUG))
|
|
168
168
|
process.stderr.write(`[phren] doctor searchMissParse: ${errorMessage(err)}\n`);
|
|
169
169
|
}
|
|
170
170
|
}
|
|
@@ -181,7 +181,7 @@ export async function handleDoctor(args) {
|
|
|
181
181
|
}
|
|
182
182
|
}
|
|
183
183
|
catch (err) {
|
|
184
|
-
if ((process.env.PHREN_DEBUG
|
|
184
|
+
if ((process.env.PHREN_DEBUG))
|
|
185
185
|
process.stderr.write(`[phren] doctor searchMissAnalysis: ${errorMessage(err)}\n`);
|
|
186
186
|
}
|
|
187
187
|
try {
|
|
@@ -215,7 +215,7 @@ export async function handleDoctor(args) {
|
|
|
215
215
|
}
|
|
216
216
|
}
|
|
217
217
|
catch (err) {
|
|
218
|
-
if ((process.env.PHREN_DEBUG
|
|
218
|
+
if ((process.env.PHREN_DEBUG))
|
|
219
219
|
process.stderr.write(`[phren] doctor ollamaStatus: ${errorMessage(err)}\n`);
|
|
220
220
|
}
|
|
221
221
|
process.exit(result.ok ? 0 : 1);
|
|
@@ -257,7 +257,7 @@ export async function handleStatus() {
|
|
|
257
257
|
console.log(`semantic-search: ${model} ready, ${formatEmbeddingCoverage(coverage)}`);
|
|
258
258
|
}
|
|
259
259
|
catch (err) {
|
|
260
|
-
if ((process.env.PHREN_DEBUG
|
|
260
|
+
if ((process.env.PHREN_DEBUG))
|
|
261
261
|
process.stderr.write(`[phren] handleStatus semanticSearch: ${errorMessage(err)}\n`);
|
|
262
262
|
}
|
|
263
263
|
}
|
package/mcp/dist/cli-config.js
CHANGED
|
@@ -1,17 +1,132 @@
|
|
|
1
1
|
import { getPhrenPath, readRootManifest } from "./shared.js";
|
|
2
2
|
import { installPreferencesFile } from "./phren-paths.js";
|
|
3
|
-
import { getIndexPolicy, updateIndexPolicy, getRetentionPolicy, updateRetentionPolicy, getWorkflowPolicy, updateWorkflowPolicy, } from "./shared-governance.js";
|
|
4
|
-
import { listMachines as listMachinesStore, listProfiles as listProfilesStore } from "./data-access.js";
|
|
3
|
+
import { getIndexPolicy, updateIndexPolicy, getRetentionPolicy, updateRetentionPolicy, getWorkflowPolicy, updateWorkflowPolicy, mergeConfig, VALID_TASK_MODES, VALID_FINDING_SENSITIVITY, } from "./shared-governance.js";
|
|
4
|
+
import { listMachines as listMachinesStore, listProfiles as listProfilesStore, listProfiles } from "./data-access.js";
|
|
5
5
|
import { setTelemetryEnabled, getTelemetrySummary, resetTelemetry } from "./telemetry.js";
|
|
6
6
|
import { governanceInstallPreferencesFile, readInstallPreferences, readGovernanceInstallPreferences, writeInstallPreferences, writeGovernanceInstallPreferences, } from "./init-preferences.js";
|
|
7
7
|
import { PROACTIVITY_LEVELS, getProactivityLevel, getProactivityLevelForTask, getProactivityLevelForFindings, } from "./proactivity.js";
|
|
8
|
-
import { PROJECT_OWNERSHIP_MODES, getProjectOwnershipDefault, parseProjectOwnershipMode, } from "./project-config.js";
|
|
8
|
+
import { PROJECT_OWNERSHIP_MODES, getProjectOwnershipDefault, parseProjectOwnershipMode, updateProjectConfigOverrides, } from "./project-config.js";
|
|
9
9
|
import { isValidProjectName, learnedSynonymsPath, learnSynonym, loadLearnedSynonyms, removeLearnedSynonym, } from "./utils.js";
|
|
10
|
+
// ── Shared helpers ────────────────────────────────────────────────────────────
|
|
11
|
+
export function parseProjectArg(args) {
|
|
12
|
+
const project = args.find((a) => a.startsWith("--project="))?.slice("--project=".length)
|
|
13
|
+
?? (args.indexOf("--project") !== -1 ? args[args.indexOf("--project") + 1] : undefined);
|
|
14
|
+
const rest = args.filter((a, i) => a !== "--project" && !a.startsWith("--project=") && args[i - 1] !== "--project");
|
|
15
|
+
return { project, rest };
|
|
16
|
+
}
|
|
17
|
+
export function checkProjectInProfile(phrenPath, project) {
|
|
18
|
+
const profiles = listProfiles(phrenPath);
|
|
19
|
+
if (profiles.ok) {
|
|
20
|
+
const registered = profiles.data.some((entry) => entry.projects.includes(project));
|
|
21
|
+
if (!registered) {
|
|
22
|
+
return `Warning: Project '${project}' not found in active profile. Run 'phren add /path/to/${project}' first.\n Config was written to ${phrenPath}/${project}/phren.project.yaml but won't be used until the project is registered.`;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
function warnIfUnregistered(phrenPath, project) {
|
|
28
|
+
const warning = checkProjectInProfile(phrenPath, project);
|
|
29
|
+
if (warning)
|
|
30
|
+
console.error(warning);
|
|
31
|
+
}
|
|
32
|
+
export function buildProactivitySnapshot(phrenPath) {
|
|
33
|
+
const prefs = readGovernanceInstallPreferences(phrenPath);
|
|
34
|
+
return {
|
|
35
|
+
path: governanceInstallPreferencesFile(phrenPath),
|
|
36
|
+
configured: {
|
|
37
|
+
proactivity: prefs.proactivity ?? null,
|
|
38
|
+
proactivityFindings: prefs.proactivityFindings ?? null,
|
|
39
|
+
proactivityTask: prefs.proactivityTask ?? null,
|
|
40
|
+
},
|
|
41
|
+
effective: {
|
|
42
|
+
proactivity: getProactivityLevel(phrenPath),
|
|
43
|
+
proactivityFindings: getProactivityLevelForFindings(phrenPath),
|
|
44
|
+
proactivityTask: getProactivityLevelForTask(phrenPath),
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
// ── Config show ───────────────────────────────────────────────────────────────
|
|
49
|
+
function formatConfigAsTable(label, rows) {
|
|
50
|
+
const maxKey = Math.max(...rows.map(([k]) => k.length));
|
|
51
|
+
console.log(`\n${label}`);
|
|
52
|
+
for (const [k, v] of rows) {
|
|
53
|
+
console.log(` ${k.padEnd(maxKey)} ${v}`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export function handleConfigShow(args) {
|
|
57
|
+
const phrenPath = getPhrenPath();
|
|
58
|
+
const { project: projectArg } = parseProjectArg(args);
|
|
59
|
+
if (projectArg) {
|
|
60
|
+
if (!isValidProjectName(projectArg)) {
|
|
61
|
+
console.error(`Invalid project name: "${projectArg}"`);
|
|
62
|
+
process.exit(1);
|
|
63
|
+
}
|
|
64
|
+
const resolved = mergeConfig(phrenPath, projectArg);
|
|
65
|
+
const inProfile = checkProjectInProfile(phrenPath, projectArg);
|
|
66
|
+
console.log(`\nConfig for project: ${projectArg}${inProfile ? "" : " (not in active profile)"}`);
|
|
67
|
+
formatConfigAsTable("Finding capture", [
|
|
68
|
+
["sensitivity", resolved.findingSensitivity],
|
|
69
|
+
["proactivity", resolved.proactivity.base ?? "(global)"],
|
|
70
|
+
["proactivity.findings", resolved.proactivity.findings ?? "(global)"],
|
|
71
|
+
["proactivity.tasks", resolved.proactivity.tasks ?? "(global)"],
|
|
72
|
+
]);
|
|
73
|
+
formatConfigAsTable("Task automation", [
|
|
74
|
+
["taskMode", resolved.taskMode],
|
|
75
|
+
]);
|
|
76
|
+
formatConfigAsTable("Retention policy", [
|
|
77
|
+
["ttlDays", String(resolved.retentionPolicy.ttlDays)],
|
|
78
|
+
["retentionDays", String(resolved.retentionPolicy.retentionDays)],
|
|
79
|
+
["autoAcceptThreshold", String(resolved.retentionPolicy.autoAcceptThreshold)],
|
|
80
|
+
["minInjectConfidence", String(resolved.retentionPolicy.minInjectConfidence)],
|
|
81
|
+
["decay.d30", String(resolved.retentionPolicy.decay.d30)],
|
|
82
|
+
["decay.d60", String(resolved.retentionPolicy.decay.d60)],
|
|
83
|
+
["decay.d90", String(resolved.retentionPolicy.decay.d90)],
|
|
84
|
+
["decay.d120", String(resolved.retentionPolicy.decay.d120)],
|
|
85
|
+
]);
|
|
86
|
+
formatConfigAsTable("Workflow policy", [
|
|
87
|
+
["lowConfidenceThreshold", String(resolved.workflowPolicy.lowConfidenceThreshold)],
|
|
88
|
+
["riskySections", resolved.workflowPolicy.riskySections.join(", ")],
|
|
89
|
+
]);
|
|
90
|
+
if (!inProfile) {
|
|
91
|
+
console.error(`\nRun 'phren add /path/to/${projectArg}' to register this project.`);
|
|
92
|
+
}
|
|
93
|
+
console.log("");
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
// Global config — no project
|
|
97
|
+
const retention = getRetentionPolicy(phrenPath);
|
|
98
|
+
const workflow = getWorkflowPolicy(phrenPath);
|
|
99
|
+
console.log("\nGlobal config (applies to all projects unless overridden)");
|
|
100
|
+
formatConfigAsTable("Finding capture", [
|
|
101
|
+
["findingSensitivity", workflow.findingSensitivity],
|
|
102
|
+
]);
|
|
103
|
+
formatConfigAsTable("Task automation", [
|
|
104
|
+
["taskMode", workflow.taskMode],
|
|
105
|
+
]);
|
|
106
|
+
formatConfigAsTable("Retention policy", [
|
|
107
|
+
["ttlDays", String(retention.ttlDays)],
|
|
108
|
+
["retentionDays", String(retention.retentionDays)],
|
|
109
|
+
["autoAcceptThreshold", String(retention.autoAcceptThreshold)],
|
|
110
|
+
["minInjectConfidence", String(retention.minInjectConfidence)],
|
|
111
|
+
["decay.d30", String(retention.decay.d30)],
|
|
112
|
+
["decay.d60", String(retention.decay.d60)],
|
|
113
|
+
["decay.d90", String(retention.decay.d90)],
|
|
114
|
+
["decay.d120", String(retention.decay.d120)],
|
|
115
|
+
]);
|
|
116
|
+
formatConfigAsTable("Workflow policy", [
|
|
117
|
+
["lowConfidenceThreshold", String(workflow.lowConfidenceThreshold)],
|
|
118
|
+
["riskySections", workflow.riskySections.join(", ")],
|
|
119
|
+
]);
|
|
120
|
+
console.log(`\nUse '--project <name>' to see merged config for a specific project.`);
|
|
121
|
+
console.log("");
|
|
122
|
+
}
|
|
10
123
|
// ── Config router ────────────────────────────────────────────────────────────
|
|
11
124
|
export async function handleConfig(args) {
|
|
12
125
|
const sub = args[0];
|
|
13
126
|
const rest = args.slice(1);
|
|
14
127
|
switch (sub) {
|
|
128
|
+
case "show":
|
|
129
|
+
return handleConfigShow(rest);
|
|
15
130
|
case "policy":
|
|
16
131
|
return handleRetentionPolicy(rest);
|
|
17
132
|
case "workflow":
|
|
@@ -42,6 +157,7 @@ export async function handleConfig(args) {
|
|
|
42
157
|
console.log(`phren config - manage settings and policies
|
|
43
158
|
|
|
44
159
|
Subcommands:
|
|
160
|
+
phren config show [--project <name>] Full merged config summary (what's actually active)
|
|
45
161
|
phren config policy [get|set ...] Memory retention, TTL, confidence, decay
|
|
46
162
|
phren config workflow [get|set ...] Risky-memory thresholds, task automation mode
|
|
47
163
|
phren config index [get|set ...] Indexer include/exclude globs
|
|
@@ -137,30 +253,30 @@ function handleConfigSynonyms(args) {
|
|
|
137
253
|
printSynonymsUsage();
|
|
138
254
|
process.exit(1);
|
|
139
255
|
}
|
|
140
|
-
|
|
141
|
-
const prefs = readGovernanceInstallPreferences(phrenPath);
|
|
142
|
-
return {
|
|
143
|
-
path: governanceInstallPreferencesFile(phrenPath),
|
|
144
|
-
configured: {
|
|
145
|
-
proactivity: prefs.proactivity ?? null,
|
|
146
|
-
proactivityFindings: prefs.proactivityFindings ?? null,
|
|
147
|
-
proactivityTask: prefs.proactivityTask ?? null,
|
|
148
|
-
},
|
|
149
|
-
effective: {
|
|
150
|
-
proactivity: getProactivityLevel(phrenPath),
|
|
151
|
-
proactivityFindings: getProactivityLevelForFindings(phrenPath),
|
|
152
|
-
proactivityTask: getProactivityLevelForTask(phrenPath),
|
|
153
|
-
},
|
|
154
|
-
};
|
|
155
|
-
}
|
|
256
|
+
const proactivityConfigSnapshot = buildProactivitySnapshot;
|
|
156
257
|
function handleConfigProactivity(subcommand, args) {
|
|
157
258
|
const phrenPath = getPhrenPath();
|
|
158
|
-
const
|
|
259
|
+
const { project: projectArg, rest: filteredArgs } = parseProjectArg(args);
|
|
260
|
+
const value = filteredArgs[0];
|
|
159
261
|
if (value === undefined) {
|
|
262
|
+
if (projectArg) {
|
|
263
|
+
if (!isValidProjectName(projectArg)) {
|
|
264
|
+
console.error(`Invalid project name: "${projectArg}"`);
|
|
265
|
+
process.exit(1);
|
|
266
|
+
}
|
|
267
|
+
const resolved = mergeConfig(phrenPath, projectArg);
|
|
268
|
+
console.log(JSON.stringify({
|
|
269
|
+
_project: projectArg,
|
|
270
|
+
base: resolved.proactivity.base ?? null,
|
|
271
|
+
findings: resolved.proactivity.findings ?? null,
|
|
272
|
+
tasks: resolved.proactivity.tasks ?? null,
|
|
273
|
+
}, null, 2));
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
160
276
|
console.log(JSON.stringify(proactivityConfigSnapshot(phrenPath), null, 2));
|
|
161
277
|
return;
|
|
162
278
|
}
|
|
163
|
-
if (
|
|
279
|
+
if (filteredArgs.length !== 1) {
|
|
164
280
|
printProactivityUsage(subcommand);
|
|
165
281
|
process.exit(1);
|
|
166
282
|
}
|
|
@@ -169,6 +285,25 @@ function handleConfigProactivity(subcommand, args) {
|
|
|
169
285
|
printProactivityUsage(subcommand);
|
|
170
286
|
process.exit(1);
|
|
171
287
|
}
|
|
288
|
+
if (projectArg) {
|
|
289
|
+
if (!isValidProjectName(projectArg)) {
|
|
290
|
+
console.error(`Invalid project name: "${projectArg}"`);
|
|
291
|
+
process.exit(1);
|
|
292
|
+
}
|
|
293
|
+
warnIfUnregistered(phrenPath, projectArg);
|
|
294
|
+
const key = subcommand === "proactivity" ? "proactivity"
|
|
295
|
+
: subcommand === "proactivity.findings" ? "proactivityFindings"
|
|
296
|
+
: "proactivityTask";
|
|
297
|
+
updateProjectConfigOverrides(phrenPath, projectArg, (current) => ({ ...current, [key]: level }));
|
|
298
|
+
const resolved = mergeConfig(phrenPath, projectArg);
|
|
299
|
+
console.log(JSON.stringify({
|
|
300
|
+
_project: projectArg,
|
|
301
|
+
base: resolved.proactivity.base ?? null,
|
|
302
|
+
findings: resolved.proactivity.findings ?? null,
|
|
303
|
+
tasks: resolved.proactivity.tasks ?? null,
|
|
304
|
+
}, null, 2));
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
172
307
|
switch (subcommand) {
|
|
173
308
|
case "proactivity":
|
|
174
309
|
writeGovernanceInstallPreferences(phrenPath, { proactivity: level });
|
|
@@ -213,61 +348,7 @@ function handleConfigProjectOwnership(args) {
|
|
|
213
348
|
writeInstallPreferences(phrenPath, { projectOwnershipDefault: ownership });
|
|
214
349
|
console.log(JSON.stringify(projectOwnershipConfigSnapshot(phrenPath), null, 2));
|
|
215
350
|
}
|
|
216
|
-
// ──
|
|
217
|
-
const TASK_MODES = ["off", "manual", "suggest", "auto"];
|
|
218
|
-
function normalizeTaskMode(raw) {
|
|
219
|
-
if (!raw)
|
|
220
|
-
return undefined;
|
|
221
|
-
const normalized = raw.trim().toLowerCase();
|
|
222
|
-
return TASK_MODES.includes(normalized) ? normalized : undefined;
|
|
223
|
-
}
|
|
224
|
-
function taskModeConfigSnapshot(phrenPath) {
|
|
225
|
-
const policy = getWorkflowPolicy(phrenPath);
|
|
226
|
-
return {
|
|
227
|
-
taskMode: policy.taskMode,
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
function handleConfigTaskMode(args) {
|
|
231
|
-
const phrenPath = getPhrenPath();
|
|
232
|
-
const action = args[0];
|
|
233
|
-
if (!action || action === "get") {
|
|
234
|
-
console.log(JSON.stringify(taskModeConfigSnapshot(phrenPath), null, 2));
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
if (action === "set") {
|
|
238
|
-
const mode = normalizeTaskMode(args[1]);
|
|
239
|
-
if (!mode) {
|
|
240
|
-
console.error(`Usage: phren config task-mode set [${TASK_MODES.join("|")}]`);
|
|
241
|
-
process.exit(1);
|
|
242
|
-
}
|
|
243
|
-
const result = updateWorkflowPolicy(phrenPath, { taskMode: mode });
|
|
244
|
-
if (!result.ok) {
|
|
245
|
-
console.error(result.error);
|
|
246
|
-
if (result.code === "PERMISSION_DENIED")
|
|
247
|
-
process.exit(1);
|
|
248
|
-
return;
|
|
249
|
-
}
|
|
250
|
-
console.log(JSON.stringify(taskModeConfigSnapshot(phrenPath), null, 2));
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
// Bare value: phren config task-mode auto
|
|
254
|
-
const mode = normalizeTaskMode(action);
|
|
255
|
-
if (mode) {
|
|
256
|
-
const result = updateWorkflowPolicy(phrenPath, { taskMode: mode });
|
|
257
|
-
if (!result.ok) {
|
|
258
|
-
console.error(result.error);
|
|
259
|
-
if (result.code === "PERMISSION_DENIED")
|
|
260
|
-
process.exit(1);
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
263
|
-
console.log(JSON.stringify(taskModeConfigSnapshot(phrenPath), null, 2));
|
|
264
|
-
return;
|
|
265
|
-
}
|
|
266
|
-
console.error(`Usage: phren config task-mode [get|set <mode>|<mode>] — modes: ${TASK_MODES.join("|")}`);
|
|
267
|
-
process.exit(1);
|
|
268
|
-
}
|
|
269
|
-
// ── Finding sensitivity ───────────────────────────────────────────────────────
|
|
270
|
-
const FINDING_SENSITIVITY_LEVELS = ["minimal", "conservative", "balanced", "aggressive"];
|
|
351
|
+
// ── Finding sensitivity config ────────────────────────────────────────────────
|
|
271
352
|
export const FINDING_SENSITIVITY_CONFIG = {
|
|
272
353
|
minimal: {
|
|
273
354
|
sessionCap: 0,
|
|
@@ -290,59 +371,94 @@ export const FINDING_SENSITIVITY_CONFIG = {
|
|
|
290
371
|
agentInstruction: "Save everything worth remembering — err on the side of capturing.",
|
|
291
372
|
},
|
|
292
373
|
};
|
|
293
|
-
function
|
|
294
|
-
if (!v)
|
|
295
|
-
return null;
|
|
296
|
-
const lower = v.toLowerCase();
|
|
297
|
-
if (FINDING_SENSITIVITY_LEVELS.includes(lower))
|
|
298
|
-
return lower;
|
|
299
|
-
return null;
|
|
300
|
-
}
|
|
301
|
-
function findingSensitivityConfigSnapshot(phrenPath) {
|
|
302
|
-
const policy = getWorkflowPolicy(phrenPath);
|
|
303
|
-
const level = policy.findingSensitivity;
|
|
304
|
-
const config = FINDING_SENSITIVITY_CONFIG[level];
|
|
305
|
-
return { level, ...config };
|
|
306
|
-
}
|
|
307
|
-
function handleConfigFindingSensitivity(args) {
|
|
374
|
+
function handleWorkflowField(args, opts) {
|
|
308
375
|
const phrenPath = getPhrenPath();
|
|
309
|
-
const
|
|
376
|
+
const { project: projectArg, rest: filteredArgs } = parseProjectArg(args);
|
|
377
|
+
const action = filteredArgs[0];
|
|
310
378
|
if (!action || action === "get") {
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
if (action === "set") {
|
|
315
|
-
const level = normalizeFindingSensitivity(args[1]);
|
|
316
|
-
if (!level) {
|
|
317
|
-
console.error(`Usage: phren config finding-sensitivity set [${FINDING_SENSITIVITY_LEVELS.join("|")}]`);
|
|
318
|
-
process.exit(1);
|
|
319
|
-
}
|
|
320
|
-
const result = updateWorkflowPolicy(phrenPath, { findingSensitivity: level });
|
|
321
|
-
if (!result.ok) {
|
|
322
|
-
console.error(result.error);
|
|
323
|
-
if (result.code === "PERMISSION_DENIED")
|
|
379
|
+
if (projectArg) {
|
|
380
|
+
if (!isValidProjectName(projectArg)) {
|
|
381
|
+
console.error(`Invalid project name: "${projectArg}"`);
|
|
324
382
|
process.exit(1);
|
|
383
|
+
}
|
|
384
|
+
const resolved = mergeConfig(phrenPath, projectArg);
|
|
385
|
+
const value = opts.getProjectValue(resolved);
|
|
386
|
+
console.log(JSON.stringify(opts.formatProjectOutput(projectArg, value), null, 2));
|
|
325
387
|
return;
|
|
326
388
|
}
|
|
327
|
-
console.log(JSON.stringify(
|
|
389
|
+
console.log(JSON.stringify(opts.getSnapshot(phrenPath), null, 2));
|
|
328
390
|
return;
|
|
329
391
|
}
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
392
|
+
const applyValue = (value) => {
|
|
393
|
+
if (projectArg) {
|
|
394
|
+
if (!isValidProjectName(projectArg)) {
|
|
395
|
+
console.error(`Invalid project name: "${projectArg}"`);
|
|
396
|
+
process.exit(1);
|
|
397
|
+
}
|
|
398
|
+
warnIfUnregistered(phrenPath, projectArg);
|
|
399
|
+
updateProjectConfigOverrides(phrenPath, projectArg, (current) => ({ ...current, [opts.projectOverrideKey]: value }));
|
|
400
|
+
const resolved = mergeConfig(phrenPath, projectArg);
|
|
401
|
+
const eff = opts.getProjectValue(resolved);
|
|
402
|
+
console.log(JSON.stringify(opts.formatProjectOutput(projectArg, eff), null, 2));
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
const result = updateWorkflowPolicy(phrenPath, { [opts.workflowPatchKey]: value });
|
|
334
406
|
if (!result.ok) {
|
|
335
407
|
console.error(result.error);
|
|
336
408
|
if (result.code === "PERMISSION_DENIED")
|
|
337
409
|
process.exit(1);
|
|
338
410
|
return;
|
|
339
411
|
}
|
|
340
|
-
console.log(JSON.stringify(
|
|
341
|
-
|
|
412
|
+
console.log(JSON.stringify(opts.getSnapshot(phrenPath), null, 2));
|
|
413
|
+
};
|
|
414
|
+
if (action === "set") {
|
|
415
|
+
const value = opts.normalize(filteredArgs[1]);
|
|
416
|
+
if (!value) {
|
|
417
|
+
console.error(`Usage: phren config ${opts.fieldName} set [${opts.validValues.join("|")}]`);
|
|
418
|
+
process.exit(1);
|
|
419
|
+
}
|
|
420
|
+
return applyValue(value);
|
|
342
421
|
}
|
|
343
|
-
|
|
422
|
+
const value = opts.normalize(action);
|
|
423
|
+
if (value)
|
|
424
|
+
return applyValue(value);
|
|
425
|
+
console.error(`Usage: phren config ${opts.fieldName} [--project <name>] [get|set <value>|<value>] — values: ${opts.validValues.join("|")}`);
|
|
344
426
|
process.exit(1);
|
|
345
427
|
}
|
|
428
|
+
function normalizeFromList(raw, validValues) {
|
|
429
|
+
if (!raw)
|
|
430
|
+
return null;
|
|
431
|
+
const lower = raw.trim().toLowerCase();
|
|
432
|
+
return validValues.includes(lower) ? lower : null;
|
|
433
|
+
}
|
|
434
|
+
function handleConfigTaskMode(args) {
|
|
435
|
+
handleWorkflowField(args, {
|
|
436
|
+
fieldName: "task-mode",
|
|
437
|
+
validValues: VALID_TASK_MODES,
|
|
438
|
+
normalize: (raw) => normalizeFromList(raw, VALID_TASK_MODES),
|
|
439
|
+
getSnapshot: (phrenPath) => ({ taskMode: getWorkflowPolicy(phrenPath).taskMode }),
|
|
440
|
+
getProjectValue: (resolved) => resolved.taskMode,
|
|
441
|
+
formatProjectOutput: (proj, value) => ({ _project: proj, taskMode: value }),
|
|
442
|
+
workflowPatchKey: "taskMode",
|
|
443
|
+
projectOverrideKey: "taskMode",
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
function handleConfigFindingSensitivity(args) {
|
|
447
|
+
handleWorkflowField(args, {
|
|
448
|
+
fieldName: "finding-sensitivity",
|
|
449
|
+
validValues: VALID_FINDING_SENSITIVITY,
|
|
450
|
+
normalize: (raw) => normalizeFromList(raw, VALID_FINDING_SENSITIVITY),
|
|
451
|
+
getSnapshot: (phrenPath) => {
|
|
452
|
+
const policy = getWorkflowPolicy(phrenPath);
|
|
453
|
+
const level = policy.findingSensitivity;
|
|
454
|
+
return { level, ...FINDING_SENSITIVITY_CONFIG[level] };
|
|
455
|
+
},
|
|
456
|
+
getProjectValue: (resolved) => resolved.findingSensitivity,
|
|
457
|
+
formatProjectOutput: (proj, value) => ({ _project: proj, level: value, ...FINDING_SENSITIVITY_CONFIG[value] }),
|
|
458
|
+
workflowPatchKey: "findingSensitivity",
|
|
459
|
+
projectOverrideKey: "findingSensitivity",
|
|
460
|
+
});
|
|
461
|
+
}
|
|
346
462
|
// ── LLM config ───────────────────────────────────────────────────────────────
|
|
347
463
|
const EXPENSIVE_MODEL_RE = /opus|sonnet|gpt-4(?!o-mini)/i;
|
|
348
464
|
const DEFAULT_LLM_MODEL = "gpt-4o-mini / claude-haiku-4-5-20251001";
|
|
@@ -442,10 +558,20 @@ export async function handleIndexPolicy(args) {
|
|
|
442
558
|
}
|
|
443
559
|
// ── Memory policy ────────────────────────────────────────────────────────────
|
|
444
560
|
export async function handleRetentionPolicy(args) {
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
561
|
+
const phrenPath = getPhrenPath();
|
|
562
|
+
const { project: projectArg, rest: filteredArgs } = parseProjectArg(args);
|
|
563
|
+
if (!filteredArgs.length || filteredArgs[0] === "get") {
|
|
564
|
+
if (projectArg) {
|
|
565
|
+
if (!isValidProjectName(projectArg)) {
|
|
566
|
+
console.error(`Invalid project name: "${projectArg}"`);
|
|
567
|
+
process.exit(1);
|
|
568
|
+
}
|
|
569
|
+
const resolved = mergeConfig(phrenPath, projectArg);
|
|
570
|
+
console.log(JSON.stringify({ _project: projectArg, ...resolved.retentionPolicy }, null, 2));
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
console.log(JSON.stringify(getRetentionPolicy(phrenPath), null, 2));
|
|
574
|
+
const conflictOn = process.env.PHREN_FEATURE_SEMANTIC_CONFLICT === "1";
|
|
449
575
|
process.stderr.write(`\nDedup: free Jaccard similarity scan on every add_finding (no API key needed).\n`);
|
|
450
576
|
process.stderr.write(` Near-matches (30–55% overlap) are returned in the response for the agent to decide.\n`);
|
|
451
577
|
if (conflictOn) {
|
|
@@ -458,9 +584,40 @@ export async function handleRetentionPolicy(args) {
|
|
|
458
584
|
}
|
|
459
585
|
return;
|
|
460
586
|
}
|
|
461
|
-
if (
|
|
587
|
+
if (filteredArgs[0] === "set") {
|
|
588
|
+
if (projectArg) {
|
|
589
|
+
if (!isValidProjectName(projectArg)) {
|
|
590
|
+
console.error(`Invalid project name: "${projectArg}"`);
|
|
591
|
+
process.exit(1);
|
|
592
|
+
}
|
|
593
|
+
warnIfUnregistered(phrenPath, projectArg);
|
|
594
|
+
updateProjectConfigOverrides(phrenPath, projectArg, (current) => {
|
|
595
|
+
const existingRetention = current.retentionPolicy ?? {};
|
|
596
|
+
const retentionPatch = { ...existingRetention };
|
|
597
|
+
for (const arg of filteredArgs.slice(1)) {
|
|
598
|
+
if (!arg.startsWith("--"))
|
|
599
|
+
continue;
|
|
600
|
+
const [k, v] = arg.slice(2).split("=");
|
|
601
|
+
if (!k || v === undefined)
|
|
602
|
+
continue;
|
|
603
|
+
const num = Number(v);
|
|
604
|
+
const value = Number.isNaN(num) ? v : num;
|
|
605
|
+
if (k.startsWith("decay.")) {
|
|
606
|
+
retentionPatch.decay = { ...(retentionPatch.decay ?? {}) };
|
|
607
|
+
retentionPatch.decay[k.slice("decay.".length)] = value;
|
|
608
|
+
}
|
|
609
|
+
else {
|
|
610
|
+
retentionPatch[k] = value;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
return { ...current, retentionPolicy: retentionPatch };
|
|
614
|
+
});
|
|
615
|
+
const resolved = mergeConfig(phrenPath, projectArg);
|
|
616
|
+
console.log(JSON.stringify({ _project: projectArg, ...resolved.retentionPolicy }, null, 2));
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
462
619
|
const patch = {};
|
|
463
|
-
for (const arg of
|
|
620
|
+
for (const arg of filteredArgs.slice(1)) {
|
|
464
621
|
if (!arg.startsWith("--"))
|
|
465
622
|
continue;
|
|
466
623
|
const [k, v] = arg.slice(2).split("=");
|
|
@@ -476,7 +633,7 @@ export async function handleRetentionPolicy(args) {
|
|
|
476
633
|
patch[k] = value;
|
|
477
634
|
}
|
|
478
635
|
}
|
|
479
|
-
const result = updateRetentionPolicy(
|
|
636
|
+
const result = updateRetentionPolicy(phrenPath, patch);
|
|
480
637
|
if (!result.ok) {
|
|
481
638
|
console.log(result.error);
|
|
482
639
|
if (result.code === "PERMISSION_DENIED")
|
|
@@ -486,18 +643,68 @@ export async function handleRetentionPolicy(args) {
|
|
|
486
643
|
console.log(JSON.stringify(result.data, null, 2));
|
|
487
644
|
return;
|
|
488
645
|
}
|
|
489
|
-
console.error("Usage: phren config policy [get|set --ttlDays=120 --retentionDays=365 --autoAcceptThreshold=0.75 --minInjectConfidence=0.35 --decay.d30=1 --decay.d60=0.85 --decay.d90=0.65 --decay.d120=0.45]");
|
|
646
|
+
console.error("Usage: phren config policy [--project <name>] [get|set --ttlDays=120 --retentionDays=365 --autoAcceptThreshold=0.75 --minInjectConfidence=0.35 --decay.d30=1 --decay.d60=0.85 --decay.d90=0.65 --decay.d120=0.45]");
|
|
490
647
|
process.exit(1);
|
|
491
648
|
}
|
|
492
649
|
// ── Memory workflow ──────────────────────────────────────────────────────────
|
|
493
650
|
export async function handleWorkflowPolicy(args) {
|
|
494
|
-
|
|
495
|
-
|
|
651
|
+
const phrenPath = getPhrenPath();
|
|
652
|
+
const { project: projectArg, rest: filteredArgs } = parseProjectArg(args);
|
|
653
|
+
if (!filteredArgs.length || filteredArgs[0] === "get") {
|
|
654
|
+
if (projectArg) {
|
|
655
|
+
if (!isValidProjectName(projectArg)) {
|
|
656
|
+
console.error(`Invalid project name: "${projectArg}"`);
|
|
657
|
+
process.exit(1);
|
|
658
|
+
}
|
|
659
|
+
const resolved = mergeConfig(phrenPath, projectArg);
|
|
660
|
+
console.log(JSON.stringify({ _project: projectArg, ...resolved.workflowPolicy }, null, 2));
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
console.log(JSON.stringify(getWorkflowPolicy(phrenPath), null, 2));
|
|
496
664
|
return;
|
|
497
665
|
}
|
|
498
|
-
if (
|
|
666
|
+
if (filteredArgs[0] === "set") {
|
|
667
|
+
if (projectArg) {
|
|
668
|
+
if (!isValidProjectName(projectArg)) {
|
|
669
|
+
console.error(`Invalid project name: "${projectArg}"`);
|
|
670
|
+
process.exit(1);
|
|
671
|
+
}
|
|
672
|
+
warnIfUnregistered(phrenPath, projectArg);
|
|
673
|
+
updateProjectConfigOverrides(phrenPath, projectArg, (current) => {
|
|
674
|
+
const nextConfig = { ...current };
|
|
675
|
+
const existingWorkflow = current.workflowPolicy ?? {};
|
|
676
|
+
const workflowPatch = { ...existingWorkflow };
|
|
677
|
+
for (const arg of filteredArgs.slice(1)) {
|
|
678
|
+
if (!arg.startsWith("--"))
|
|
679
|
+
continue;
|
|
680
|
+
const [k, v] = arg.slice(2).split("=");
|
|
681
|
+
if (!k || v === undefined)
|
|
682
|
+
continue;
|
|
683
|
+
if (k === "riskySections") {
|
|
684
|
+
workflowPatch.riskySections = v.split(",").map((s) => s.trim()).filter(Boolean);
|
|
685
|
+
}
|
|
686
|
+
else if (k === "taskMode") {
|
|
687
|
+
nextConfig.taskMode = v;
|
|
688
|
+
}
|
|
689
|
+
else if (k === "findingSensitivity") {
|
|
690
|
+
nextConfig.findingSensitivity = v;
|
|
691
|
+
}
|
|
692
|
+
else {
|
|
693
|
+
const num = Number(v);
|
|
694
|
+
workflowPatch[k] = Number.isNaN(num) ? v : num;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
if (Object.keys(workflowPatch).length > 0 || existingWorkflow !== current.workflowPolicy) {
|
|
698
|
+
nextConfig.workflowPolicy = workflowPatch;
|
|
699
|
+
}
|
|
700
|
+
return nextConfig;
|
|
701
|
+
});
|
|
702
|
+
const resolved = mergeConfig(phrenPath, projectArg);
|
|
703
|
+
console.log(JSON.stringify({ _project: projectArg, ...resolved.workflowPolicy }, null, 2));
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
499
706
|
const patch = {};
|
|
500
|
-
for (const arg of
|
|
707
|
+
for (const arg of filteredArgs.slice(1)) {
|
|
501
708
|
if (!arg.startsWith("--"))
|
|
502
709
|
continue;
|
|
503
710
|
const [k, v] = arg.slice(2).split("=");
|
|
@@ -511,7 +718,7 @@ export async function handleWorkflowPolicy(args) {
|
|
|
511
718
|
patch[k] = Number.isNaN(num) ? v : num;
|
|
512
719
|
}
|
|
513
720
|
}
|
|
514
|
-
const result = updateWorkflowPolicy(
|
|
721
|
+
const result = updateWorkflowPolicy(phrenPath, patch);
|
|
515
722
|
if (!result.ok) {
|
|
516
723
|
console.log(result.error);
|
|
517
724
|
if (result.code === "PERMISSION_DENIED")
|
|
@@ -521,7 +728,7 @@ export async function handleWorkflowPolicy(args) {
|
|
|
521
728
|
console.log(JSON.stringify(result.data, null, 2));
|
|
522
729
|
return;
|
|
523
730
|
}
|
|
524
|
-
console.error("Usage: phren config workflow [get|set --lowConfidenceThreshold=0.7 --riskySections=Stale,Conflicts --taskMode=manual]");
|
|
731
|
+
console.error("Usage: phren config workflow [--project <name>] [get|set --lowConfidenceThreshold=0.7 --riskySections=Stale,Conflicts --taskMode=manual]");
|
|
525
732
|
process.exit(1);
|
|
526
733
|
}
|
|
527
734
|
// ── Machines and profiles ────────────────────────────────────────────────────
|