@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,580 @@
|
|
|
1
|
+
import { getPhrenPath, readRootManifest } from "./shared.js";
|
|
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";
|
|
5
|
+
import { setTelemetryEnabled, getTelemetrySummary, resetTelemetry } from "./telemetry.js";
|
|
6
|
+
import { governanceInstallPreferencesFile, readInstallPreferences, readGovernanceInstallPreferences, writeInstallPreferences, writeGovernanceInstallPreferences, } from "./init-preferences.js";
|
|
7
|
+
import { PROACTIVITY_LEVELS, getProactivityLevel, getProactivityLevelForTask, getProactivityLevelForFindings, } from "./proactivity.js";
|
|
8
|
+
import { PROJECT_OWNERSHIP_MODES, getProjectOwnershipDefault, parseProjectOwnershipMode, } from "./project-config.js";
|
|
9
|
+
import { isValidProjectName, learnedSynonymsPath, learnSynonym, loadLearnedSynonyms, removeLearnedSynonym, } from "./utils.js";
|
|
10
|
+
// ── Config router ────────────────────────────────────────────────────────────
|
|
11
|
+
export async function handleConfig(args) {
|
|
12
|
+
const sub = args[0];
|
|
13
|
+
const rest = args.slice(1);
|
|
14
|
+
switch (sub) {
|
|
15
|
+
case "policy":
|
|
16
|
+
return handleRetentionPolicy(rest);
|
|
17
|
+
case "workflow":
|
|
18
|
+
return handleWorkflowPolicy(rest);
|
|
19
|
+
case "index":
|
|
20
|
+
return handleIndexPolicy(rest);
|
|
21
|
+
case "machines":
|
|
22
|
+
return handleConfigMachines();
|
|
23
|
+
case "profiles":
|
|
24
|
+
return handleConfigProfiles();
|
|
25
|
+
case "telemetry":
|
|
26
|
+
return handleConfigTelemetry(rest);
|
|
27
|
+
case "proactivity":
|
|
28
|
+
case "proactivity.findings":
|
|
29
|
+
case "proactivity.tasks":
|
|
30
|
+
return handleConfigProactivity(sub, rest);
|
|
31
|
+
case "project-ownership":
|
|
32
|
+
return handleConfigProjectOwnership(rest);
|
|
33
|
+
case "task-mode":
|
|
34
|
+
return handleConfigTaskMode(rest);
|
|
35
|
+
case "finding-sensitivity":
|
|
36
|
+
return handleConfigFindingSensitivity(rest);
|
|
37
|
+
case "llm":
|
|
38
|
+
return handleConfigLlm(rest);
|
|
39
|
+
case "synonyms":
|
|
40
|
+
return handleConfigSynonyms(rest);
|
|
41
|
+
default:
|
|
42
|
+
console.log(`phren config - manage settings and policies
|
|
43
|
+
|
|
44
|
+
Subcommands:
|
|
45
|
+
phren config policy [get|set ...] Memory retention, TTL, confidence, decay
|
|
46
|
+
phren config workflow [get|set ...] Risky-memory thresholds, task automation mode
|
|
47
|
+
phren config index [get|set ...] Indexer include/exclude globs
|
|
48
|
+
phren config proactivity [level] Base auto-capture level (high|medium|low)
|
|
49
|
+
phren config proactivity.findings [level]
|
|
50
|
+
Findings-specific auto-capture level override
|
|
51
|
+
phren config proactivity.tasks [level]
|
|
52
|
+
Task-specific auto-capture level override
|
|
53
|
+
phren config task-mode [get|set <mode>]
|
|
54
|
+
Task automation mode (off|manual|suggest|auto)
|
|
55
|
+
phren config finding-sensitivity [get|set <level>]
|
|
56
|
+
Finding capture level (minimal|conservative|balanced|aggressive)
|
|
57
|
+
phren config project-ownership [mode]
|
|
58
|
+
Default ownership for future project enrollments
|
|
59
|
+
phren config llm [get|set model|endpoint|key]
|
|
60
|
+
LLM config for semantic dedup/conflict features
|
|
61
|
+
phren config synonyms [list|add|remove] ...
|
|
62
|
+
Manage project learned synonyms
|
|
63
|
+
phren config machines Registered machines and profiles
|
|
64
|
+
phren config profiles All profiles and their projects
|
|
65
|
+
phren config telemetry [on|off|reset] Local usage stats (opt-in, no external reporting)`);
|
|
66
|
+
if (sub) {
|
|
67
|
+
console.error(`\nUnknown config subcommand: "${sub}"`);
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function normalizeProactivityLevel(raw) {
|
|
73
|
+
if (!raw)
|
|
74
|
+
return undefined;
|
|
75
|
+
const normalized = raw.trim().toLowerCase();
|
|
76
|
+
return PROACTIVITY_LEVELS.includes(normalized)
|
|
77
|
+
? normalized
|
|
78
|
+
: undefined;
|
|
79
|
+
}
|
|
80
|
+
function printProactivityUsage(subcommand) {
|
|
81
|
+
console.error(`Usage: phren config ${subcommand} [high|medium|low]`);
|
|
82
|
+
}
|
|
83
|
+
function printSynonymsUsage() {
|
|
84
|
+
console.error("Usage: phren config synonyms list <project>");
|
|
85
|
+
console.error(" phren config synonyms add <project> <term> <syn1,syn2,...>");
|
|
86
|
+
console.error(" phren config synonyms remove <project> <term> [syn1,syn2,...]");
|
|
87
|
+
}
|
|
88
|
+
function parseSynonymItems(raw) {
|
|
89
|
+
if (!raw)
|
|
90
|
+
return [];
|
|
91
|
+
return raw
|
|
92
|
+
.split(",")
|
|
93
|
+
.map((item) => item.trim())
|
|
94
|
+
.filter(Boolean);
|
|
95
|
+
}
|
|
96
|
+
function handleConfigSynonyms(args) {
|
|
97
|
+
const phrenPath = getPhrenPath();
|
|
98
|
+
let action = args[0] || "list";
|
|
99
|
+
let project = args[1];
|
|
100
|
+
if (action !== "list" && action !== "add" && action !== "remove" && isValidProjectName(action)) {
|
|
101
|
+
project = action;
|
|
102
|
+
action = "list";
|
|
103
|
+
}
|
|
104
|
+
if (!project || !isValidProjectName(project)) {
|
|
105
|
+
printSynonymsUsage();
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
if (action === "list") {
|
|
109
|
+
console.log(JSON.stringify({
|
|
110
|
+
project,
|
|
111
|
+
path: learnedSynonymsPath(phrenPath, project),
|
|
112
|
+
synonyms: loadLearnedSynonyms(project, phrenPath),
|
|
113
|
+
}, null, 2));
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (action === "add") {
|
|
117
|
+
const term = args[2];
|
|
118
|
+
const synonyms = parseSynonymItems(args[3]);
|
|
119
|
+
if (!term || synonyms.length === 0) {
|
|
120
|
+
printSynonymsUsage();
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
const updated = learnSynonym(phrenPath, project, term, synonyms);
|
|
124
|
+
console.log(JSON.stringify({ project, term, synonyms: updated[term.toLowerCase()] ?? [], updated }, null, 2));
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
if (action === "remove") {
|
|
128
|
+
const term = args[2];
|
|
129
|
+
if (!term) {
|
|
130
|
+
printSynonymsUsage();
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
const updated = removeLearnedSynonym(phrenPath, project, term, parseSynonymItems(args[3]));
|
|
134
|
+
console.log(JSON.stringify({ project, term: term.toLowerCase(), updated }, null, 2));
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
printSynonymsUsage();
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
140
|
+
function proactivityConfigSnapshot(phrenPath) {
|
|
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
|
+
}
|
|
156
|
+
function handleConfigProactivity(subcommand, args) {
|
|
157
|
+
const phrenPath = getPhrenPath();
|
|
158
|
+
const value = args[0];
|
|
159
|
+
if (value === undefined) {
|
|
160
|
+
console.log(JSON.stringify(proactivityConfigSnapshot(phrenPath), null, 2));
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
if (args.length !== 1) {
|
|
164
|
+
printProactivityUsage(subcommand);
|
|
165
|
+
process.exit(1);
|
|
166
|
+
}
|
|
167
|
+
const level = normalizeProactivityLevel(value);
|
|
168
|
+
if (!level) {
|
|
169
|
+
printProactivityUsage(subcommand);
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
switch (subcommand) {
|
|
173
|
+
case "proactivity":
|
|
174
|
+
writeGovernanceInstallPreferences(phrenPath, { proactivity: level });
|
|
175
|
+
break;
|
|
176
|
+
case "proactivity.findings":
|
|
177
|
+
writeGovernanceInstallPreferences(phrenPath, { proactivityFindings: level });
|
|
178
|
+
break;
|
|
179
|
+
case "proactivity.tasks":
|
|
180
|
+
writeGovernanceInstallPreferences(phrenPath, { proactivityTask: level });
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
console.log(JSON.stringify(proactivityConfigSnapshot(phrenPath), null, 2));
|
|
184
|
+
}
|
|
185
|
+
function projectOwnershipConfigSnapshot(phrenPath) {
|
|
186
|
+
const prefs = readInstallPreferences(phrenPath);
|
|
187
|
+
return {
|
|
188
|
+
path: installPreferencesFile(phrenPath),
|
|
189
|
+
configured: {
|
|
190
|
+
projectOwnershipDefault: prefs.projectOwnershipDefault ?? null,
|
|
191
|
+
},
|
|
192
|
+
effective: {
|
|
193
|
+
projectOwnershipDefault: getProjectOwnershipDefault(phrenPath),
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
function handleConfigProjectOwnership(args) {
|
|
198
|
+
const phrenPath = getPhrenPath();
|
|
199
|
+
const value = args[0];
|
|
200
|
+
if (value === undefined) {
|
|
201
|
+
console.log(JSON.stringify(projectOwnershipConfigSnapshot(phrenPath), null, 2));
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (args.length !== 1) {
|
|
205
|
+
console.error(`Usage: phren config project-ownership [${PROJECT_OWNERSHIP_MODES.join("|")}]`);
|
|
206
|
+
process.exit(1);
|
|
207
|
+
}
|
|
208
|
+
const ownership = parseProjectOwnershipMode(value);
|
|
209
|
+
if (!ownership) {
|
|
210
|
+
console.error(`Usage: phren config project-ownership [${PROJECT_OWNERSHIP_MODES.join("|")}]`);
|
|
211
|
+
process.exit(1);
|
|
212
|
+
}
|
|
213
|
+
writeInstallPreferences(phrenPath, { projectOwnershipDefault: ownership });
|
|
214
|
+
console.log(JSON.stringify(projectOwnershipConfigSnapshot(phrenPath), null, 2));
|
|
215
|
+
}
|
|
216
|
+
// ── Task mode ─────────────────────────────────────────────────────────────────
|
|
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"];
|
|
271
|
+
export const FINDING_SENSITIVITY_CONFIG = {
|
|
272
|
+
minimal: {
|
|
273
|
+
sessionCap: 0,
|
|
274
|
+
proactivityFindings: "low",
|
|
275
|
+
agentInstruction: "Only save findings when the user explicitly asks you to remember something.",
|
|
276
|
+
},
|
|
277
|
+
conservative: {
|
|
278
|
+
sessionCap: 3,
|
|
279
|
+
proactivityFindings: "medium",
|
|
280
|
+
agentInstruction: "Save decisions and pitfalls only — skip patterns and observations.",
|
|
281
|
+
},
|
|
282
|
+
balanced: {
|
|
283
|
+
sessionCap: 10,
|
|
284
|
+
proactivityFindings: "high",
|
|
285
|
+
agentInstruction: "Save non-obvious patterns, decisions, pitfalls, and bugs worth remembering next session.",
|
|
286
|
+
},
|
|
287
|
+
aggressive: {
|
|
288
|
+
sessionCap: 20,
|
|
289
|
+
proactivityFindings: "high",
|
|
290
|
+
agentInstruction: "Save everything worth remembering — err on the side of capturing.",
|
|
291
|
+
},
|
|
292
|
+
};
|
|
293
|
+
function normalizeFindingSensitivity(v) {
|
|
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) {
|
|
308
|
+
const phrenPath = getPhrenPath();
|
|
309
|
+
const action = args[0];
|
|
310
|
+
if (!action || action === "get") {
|
|
311
|
+
console.log(JSON.stringify(findingSensitivityConfigSnapshot(phrenPath), null, 2));
|
|
312
|
+
return;
|
|
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")
|
|
324
|
+
process.exit(1);
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
console.log(JSON.stringify(findingSensitivityConfigSnapshot(phrenPath), null, 2));
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
// Bare value: phren config finding-sensitivity balanced
|
|
331
|
+
const level = normalizeFindingSensitivity(action);
|
|
332
|
+
if (level) {
|
|
333
|
+
const result = updateWorkflowPolicy(phrenPath, { findingSensitivity: level });
|
|
334
|
+
if (!result.ok) {
|
|
335
|
+
console.error(result.error);
|
|
336
|
+
if (result.code === "PERMISSION_DENIED")
|
|
337
|
+
process.exit(1);
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
console.log(JSON.stringify(findingSensitivityConfigSnapshot(phrenPath), null, 2));
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
console.error(`Usage: phren config finding-sensitivity [get|set <level>|<level>] — levels: ${FINDING_SENSITIVITY_LEVELS.join("|")}`);
|
|
344
|
+
process.exit(1);
|
|
345
|
+
}
|
|
346
|
+
// ── LLM config ───────────────────────────────────────────────────────────────
|
|
347
|
+
const EXPENSIVE_MODEL_RE = /opus|sonnet|gpt-4(?!o-mini)/i;
|
|
348
|
+
const DEFAULT_LLM_MODEL = "gpt-4o-mini / claude-haiku-4-5-20251001";
|
|
349
|
+
export function printSemanticCostNotice(model) {
|
|
350
|
+
const effectiveModel = model || process.env.PHREN_LLM_MODEL || DEFAULT_LLM_MODEL;
|
|
351
|
+
console.log(` Note: Each semantic check is ~80 input + ~5 output tokens (one call per 'maybe' pair, cached 24h).`);
|
|
352
|
+
console.log(` Current model: ${effectiveModel}`);
|
|
353
|
+
if (model && EXPENSIVE_MODEL_RE.test(model)) {
|
|
354
|
+
console.log(` Warning: This model is 20x more expensive than Haiku for yes/no checks.`);
|
|
355
|
+
console.log(` Consider: PHREN_LLM_MODEL=claude-haiku-4-5-20251001`);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
function llmConfigSnapshot() {
|
|
359
|
+
return {
|
|
360
|
+
model: process.env.PHREN_LLM_MODEL || null,
|
|
361
|
+
endpoint: process.env.PHREN_LLM_ENDPOINT || null,
|
|
362
|
+
keySet: Boolean(process.env.PHREN_LLM_KEY || process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY),
|
|
363
|
+
note: `Set via environment variables. Each semantic check: ~80 input + ~5 output tokens. Default model: ${DEFAULT_LLM_MODEL}.`,
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
function handleConfigLlm(args) {
|
|
367
|
+
const action = args[0];
|
|
368
|
+
if (!action || action === "get") {
|
|
369
|
+
const snapshot = llmConfigSnapshot();
|
|
370
|
+
console.log(JSON.stringify(snapshot, null, 2));
|
|
371
|
+
const model = process.env.PHREN_LLM_MODEL;
|
|
372
|
+
if (model && EXPENSIVE_MODEL_RE.test(model)) {
|
|
373
|
+
process.stderr.write(`\nWarning: PHREN_LLM_MODEL=${model} is expensive for yes/no semantic checks.\n`);
|
|
374
|
+
process.stderr.write(`Consider: PHREN_LLM_MODEL=claude-haiku-4-5-20251001\n`);
|
|
375
|
+
}
|
|
376
|
+
return;
|
|
377
|
+
}
|
|
378
|
+
if (action === "set") {
|
|
379
|
+
const key = args[1];
|
|
380
|
+
const value = args[2];
|
|
381
|
+
if (!key || !value) {
|
|
382
|
+
console.error("Usage: phren config llm set model <name>");
|
|
383
|
+
console.error(" phren config llm set endpoint <url>");
|
|
384
|
+
console.error(" phren config llm set key <api-key>");
|
|
385
|
+
process.exit(1);
|
|
386
|
+
}
|
|
387
|
+
const envMap = {
|
|
388
|
+
model: "PHREN_LLM_MODEL",
|
|
389
|
+
endpoint: "PHREN_LLM_ENDPOINT",
|
|
390
|
+
key: "PHREN_LLM_KEY",
|
|
391
|
+
};
|
|
392
|
+
const envVar = envMap[key];
|
|
393
|
+
if (!envVar) {
|
|
394
|
+
console.error(`Unknown setting "${key}". Valid: model, endpoint, key`);
|
|
395
|
+
process.exit(1);
|
|
396
|
+
}
|
|
397
|
+
console.log(`Set ${envVar}=${value} in your shell or ~/.phren/.env`);
|
|
398
|
+
if (key === "model") {
|
|
399
|
+
printSemanticCostNotice(value);
|
|
400
|
+
}
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
console.error("Usage: phren config llm [get|set model <name>|set endpoint <url>|set key <api-key>]");
|
|
404
|
+
process.exit(1);
|
|
405
|
+
}
|
|
406
|
+
// ── Index policy ─────────────────────────────────────────────────────────────
|
|
407
|
+
export async function handleIndexPolicy(args) {
|
|
408
|
+
if (!args.length || args[0] === "get") {
|
|
409
|
+
console.log(JSON.stringify(getIndexPolicy(getPhrenPath()), null, 2));
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
if (args[0] === "set") {
|
|
413
|
+
const patch = {};
|
|
414
|
+
for (const arg of args.slice(1)) {
|
|
415
|
+
if (!arg.startsWith("--"))
|
|
416
|
+
continue;
|
|
417
|
+
const [k, v] = arg.slice(2).split("=");
|
|
418
|
+
if (!k || v === undefined)
|
|
419
|
+
continue;
|
|
420
|
+
if (k === "include") {
|
|
421
|
+
patch.includeGlobs = v.split(",").map((s) => s.trim()).filter(Boolean);
|
|
422
|
+
}
|
|
423
|
+
else if (k === "exclude") {
|
|
424
|
+
patch.excludeGlobs = v.split(",").map((s) => s.trim()).filter(Boolean);
|
|
425
|
+
}
|
|
426
|
+
else if (k === "includeHidden") {
|
|
427
|
+
patch.includeHidden = /^(1|true|yes|on)$/i.test(v);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
const result = updateIndexPolicy(getPhrenPath(), patch);
|
|
431
|
+
if (!result.ok) {
|
|
432
|
+
console.log(result.error);
|
|
433
|
+
if (result.code === "PERMISSION_DENIED")
|
|
434
|
+
process.exit(1);
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
console.log(JSON.stringify(result.data, null, 2));
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
440
|
+
console.error("Usage: phren index-policy [get|set --include=**/*.md,**/skills/**/*.md,.claude/skills/**/*.md --exclude=**/node_modules/**,**/.git/** --includeHidden=false]");
|
|
441
|
+
process.exit(1);
|
|
442
|
+
}
|
|
443
|
+
// ── Memory policy ────────────────────────────────────────────────────────────
|
|
444
|
+
export async function handleRetentionPolicy(args) {
|
|
445
|
+
if (!args.length || args[0] === "get") {
|
|
446
|
+
console.log(JSON.stringify(getRetentionPolicy(getPhrenPath()), null, 2));
|
|
447
|
+
const dedupOn = (process.env.PHREN_FEATURE_SEMANTIC_DEDUP ?? (process.env.PHREN_FEATURE_SEMANTIC_DEDUP || process.env.PHREN_FEATURE_SEMANTIC_DEDUP)) === "1";
|
|
448
|
+
const conflictOn = (process.env.PHREN_FEATURE_SEMANTIC_CONFLICT ?? (process.env.PHREN_FEATURE_SEMANTIC_CONFLICT || process.env.PHREN_FEATURE_SEMANTIC_CONFLICT)) === "1";
|
|
449
|
+
process.stderr.write(`\nDedup: free Jaccard similarity scan on every add_finding (no API key needed).\n`);
|
|
450
|
+
process.stderr.write(` Near-matches (30–55% overlap) are returned in the response for the agent to decide.\n`);
|
|
451
|
+
if (conflictOn) {
|
|
452
|
+
process.stderr.write(`\nConflict detection (PHREN_FEATURE_SEMANTIC_CONFLICT=1): active.\n`);
|
|
453
|
+
process.stderr.write(` Uses an LLM for batch conflict checks. See: phren config llm\n`);
|
|
454
|
+
}
|
|
455
|
+
else {
|
|
456
|
+
process.stderr.write(`\nConflict detection: disabled (set PHREN_FEATURE_SEMANTIC_CONFLICT=1 to enable for batch ops).\n`);
|
|
457
|
+
process.stderr.write(` LLM needed only for batch operations (phren maintain consolidate/extract).\n`);
|
|
458
|
+
}
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
if (args[0] === "set") {
|
|
462
|
+
const patch = {};
|
|
463
|
+
for (const arg of args.slice(1)) {
|
|
464
|
+
if (!arg.startsWith("--"))
|
|
465
|
+
continue;
|
|
466
|
+
const [k, v] = arg.slice(2).split("=");
|
|
467
|
+
if (!k || v === undefined)
|
|
468
|
+
continue;
|
|
469
|
+
const num = Number(v);
|
|
470
|
+
const value = Number.isNaN(num) ? v : num;
|
|
471
|
+
if (k.startsWith("decay.")) {
|
|
472
|
+
patch.decay = patch.decay || {};
|
|
473
|
+
patch.decay[k.slice("decay.".length)] = value;
|
|
474
|
+
}
|
|
475
|
+
else {
|
|
476
|
+
patch[k] = value;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
const result = updateRetentionPolicy(getPhrenPath(), patch);
|
|
480
|
+
if (!result.ok) {
|
|
481
|
+
console.log(result.error);
|
|
482
|
+
if (result.code === "PERMISSION_DENIED")
|
|
483
|
+
process.exit(1);
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
console.log(JSON.stringify(result.data, null, 2));
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
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]");
|
|
490
|
+
process.exit(1);
|
|
491
|
+
}
|
|
492
|
+
// ── Memory workflow ──────────────────────────────────────────────────────────
|
|
493
|
+
export async function handleWorkflowPolicy(args) {
|
|
494
|
+
if (!args.length || args[0] === "get") {
|
|
495
|
+
console.log(JSON.stringify(getWorkflowPolicy(getPhrenPath()), null, 2));
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
if (args[0] === "set") {
|
|
499
|
+
const patch = {};
|
|
500
|
+
for (const arg of args.slice(1)) {
|
|
501
|
+
if (!arg.startsWith("--"))
|
|
502
|
+
continue;
|
|
503
|
+
const [k, v] = arg.slice(2).split("=");
|
|
504
|
+
if (!k || v === undefined)
|
|
505
|
+
continue;
|
|
506
|
+
if (k === "riskySections") {
|
|
507
|
+
patch.riskySections = v.split(",").map((s) => s.trim()).filter(Boolean);
|
|
508
|
+
}
|
|
509
|
+
else {
|
|
510
|
+
const num = Number(v);
|
|
511
|
+
patch[k] = Number.isNaN(num) ? v : num;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
const result = updateWorkflowPolicy(getPhrenPath(), patch);
|
|
515
|
+
if (!result.ok) {
|
|
516
|
+
console.log(result.error);
|
|
517
|
+
if (result.code === "PERMISSION_DENIED")
|
|
518
|
+
process.exit(1);
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
console.log(JSON.stringify(result.data, null, 2));
|
|
522
|
+
return;
|
|
523
|
+
}
|
|
524
|
+
console.error("Usage: phren config workflow [get|set --lowConfidenceThreshold=0.7 --riskySections=Stale,Conflicts --taskMode=manual]");
|
|
525
|
+
process.exit(1);
|
|
526
|
+
}
|
|
527
|
+
// ── Machines and profiles ────────────────────────────────────────────────────
|
|
528
|
+
function handleConfigMachines() {
|
|
529
|
+
const manifest = readRootManifest(getPhrenPath());
|
|
530
|
+
if (manifest?.installMode === "project-local") {
|
|
531
|
+
console.log("config machines is shared-mode only");
|
|
532
|
+
return;
|
|
533
|
+
}
|
|
534
|
+
const result = listMachinesStore(getPhrenPath());
|
|
535
|
+
if (!result.ok) {
|
|
536
|
+
console.log(result.error);
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
const lines = Object.entries(result.data).map(([machine, prof]) => ` ${machine}: ${prof}`);
|
|
540
|
+
console.log(`Registered Machines\n${lines.join("\n")}`);
|
|
541
|
+
}
|
|
542
|
+
function handleConfigProfiles() {
|
|
543
|
+
const manifest = readRootManifest(getPhrenPath());
|
|
544
|
+
if (manifest?.installMode === "project-local") {
|
|
545
|
+
console.log("config profiles is shared-mode only");
|
|
546
|
+
return;
|
|
547
|
+
}
|
|
548
|
+
const result = listProfilesStore(getPhrenPath());
|
|
549
|
+
if (!result.ok) {
|
|
550
|
+
console.log(result.error);
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
for (const p of result.data) {
|
|
554
|
+
console.log(`\n${p.name}`);
|
|
555
|
+
for (const proj of p.projects)
|
|
556
|
+
console.log(` - ${proj}`);
|
|
557
|
+
if (!p.projects.length)
|
|
558
|
+
console.log(" (no projects)");
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
function handleConfigTelemetry(args) {
|
|
562
|
+
const action = args[0];
|
|
563
|
+
switch (action) {
|
|
564
|
+
case "on":
|
|
565
|
+
setTelemetryEnabled(getPhrenPath(), true);
|
|
566
|
+
console.log("Telemetry enabled. Local usage stats will be collected.");
|
|
567
|
+
console.log("No data is sent externally. Stats are stored in .runtime/telemetry.json.");
|
|
568
|
+
return;
|
|
569
|
+
case "off":
|
|
570
|
+
setTelemetryEnabled(getPhrenPath(), false);
|
|
571
|
+
console.log("Telemetry disabled.");
|
|
572
|
+
return;
|
|
573
|
+
case "reset":
|
|
574
|
+
resetTelemetry(getPhrenPath());
|
|
575
|
+
console.log("Telemetry stats reset.");
|
|
576
|
+
return;
|
|
577
|
+
default:
|
|
578
|
+
console.log(getTelemetrySummary(getPhrenPath()));
|
|
579
|
+
}
|
|
580
|
+
}
|