@phren/cli 0.0.17 → 0.0.19
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/mcp/dist/capabilities/cli.js +1 -1
- package/mcp/dist/capabilities/mcp.js +1 -1
- package/mcp/dist/capabilities/vscode.js +1 -1
- package/mcp/dist/capabilities/web-ui.js +1 -1
- package/mcp/dist/cli-hooks-session.js +3 -3
- package/mcp/dist/cli-namespaces.js +1 -1
- package/mcp/dist/data-tasks.js +1 -1
- package/mcp/dist/entrypoint.js +1 -1
- package/mcp/dist/governance-policy.js +76 -32
- package/mcp/dist/governance-rbac.js +152 -0
- package/mcp/dist/init-preferences.js +1 -1
- package/mcp/dist/init-setup.js +7 -7
- package/mcp/dist/init.js +2 -2
- package/mcp/dist/link-checksums.js +1 -1
- package/mcp/dist/link-doctor.js +1 -1
- package/mcp/dist/mcp-config.js +102 -2
- package/mcp/dist/mcp-finding.js +16 -0
- package/mcp/dist/mcp-search.js +44 -5
- package/mcp/dist/mcp-session.js +4 -0
- package/mcp/dist/mcp-tasks.js +22 -0
- package/mcp/dist/memory-ui-assets.js +2 -2
- package/mcp/dist/memory-ui-graph.js +9 -19
- package/mcp/dist/memory-ui-page.js +26 -43
- package/mcp/dist/memory-ui-scripts.js +198 -76
- package/mcp/dist/memory-ui-server.js +57 -2
- package/mcp/dist/phren-core.js +1 -1
- package/mcp/dist/phren-paths.js +2 -2
- package/mcp/dist/proactivity.js +47 -0
- package/mcp/dist/profile-store.js +100 -1
- package/mcp/dist/shared-index.js +2 -2
- package/mcp/dist/shared-retrieval.js +42 -1
- package/mcp/dist/shell-input.js +1 -1
- package/package.json +1 -1
package/mcp/dist/phren-paths.js
CHANGED
|
@@ -108,7 +108,7 @@ function hasRootManifest(candidate) {
|
|
|
108
108
|
}
|
|
109
109
|
function hasInstallMarkers(candidate) {
|
|
110
110
|
return fs.existsSync(path.join(candidate, "machines.yaml"))
|
|
111
|
-
|| fs.existsSync(path.join(candidate, ".
|
|
111
|
+
|| fs.existsSync(path.join(candidate, ".config"))
|
|
112
112
|
|| fs.existsSync(path.join(candidate, "global"));
|
|
113
113
|
}
|
|
114
114
|
function isPhrenRootCandidate(candidate) {
|
|
@@ -460,7 +460,7 @@ export function computePhrenLiveStateToken(phrenPath) {
|
|
|
460
460
|
pushDirTokens(parts, path.join(phrenPath, "profiles"));
|
|
461
461
|
}
|
|
462
462
|
pushDirTokens(parts, path.join(phrenPath, "global", "skills"));
|
|
463
|
-
pushFileToken(parts, path.join(phrenPath, ".
|
|
463
|
+
pushFileToken(parts, path.join(phrenPath, ".config", "access-control.json"));
|
|
464
464
|
pushFileToken(parts, rootManifestPath(phrenPath));
|
|
465
465
|
pushFileToken(parts, runtimeHealthFile(phrenPath));
|
|
466
466
|
pushFileToken(parts, runtimeFile(phrenPath, "audit.log"));
|
package/mcp/dist/proactivity.js
CHANGED
|
@@ -20,6 +20,37 @@ function parseProactivityLevel(raw) {
|
|
|
20
20
|
function resolveProactivityPhrenPath(explicitPhrenPath) {
|
|
21
21
|
return explicitPhrenPath ?? findPhrenPath();
|
|
22
22
|
}
|
|
23
|
+
/** Read per-user preferences from ~/.phren/.users/<actor>/preferences.json. Actor from PHREN_ACTOR env var. */
|
|
24
|
+
export function readUserPreferences(explicitPhrenPath) {
|
|
25
|
+
const phrenPath = resolveProactivityPhrenPath(explicitPhrenPath);
|
|
26
|
+
if (!phrenPath)
|
|
27
|
+
return {};
|
|
28
|
+
const actor = (process.env.PHREN_ACTOR || "").trim();
|
|
29
|
+
if (!actor || !/^[a-zA-Z0-9_@.-]{1,128}$/.test(actor))
|
|
30
|
+
return {};
|
|
31
|
+
// Sanitize actor name to safe path component (no path traversal)
|
|
32
|
+
const safeActor = actor.replace(/[^a-zA-Z0-9_@.-]/g, "_");
|
|
33
|
+
const prefsFile = `${phrenPath}/.users/${safeActor}/preferences.json`;
|
|
34
|
+
if (!fs.existsSync(prefsFile))
|
|
35
|
+
return {};
|
|
36
|
+
try {
|
|
37
|
+
const parsed = JSON.parse(fs.readFileSync(prefsFile, "utf8"));
|
|
38
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
|
|
39
|
+
return {};
|
|
40
|
+
return {
|
|
41
|
+
proactivity: parseProactivityLevel(parsed.proactivity),
|
|
42
|
+
proactivityFindings: parseProactivityLevel(parsed.proactivityFindings),
|
|
43
|
+
proactivityTask: parseProactivityLevel(parsed.proactivityTask),
|
|
44
|
+
findingSensitivity: ["minimal", "conservative", "balanced", "aggressive"].includes(String(parsed.findingSensitivity))
|
|
45
|
+
? parsed.findingSensitivity
|
|
46
|
+
: undefined,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
debugLog(`readUserPreferences: failed to parse ${prefsFile}: ${errorMessage(err)}`);
|
|
51
|
+
return {};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
23
54
|
function readGovernanceProactivityPreferences(explicitPhrenPath) {
|
|
24
55
|
const phrenPath = resolveProactivityPhrenPath(explicitPhrenPath);
|
|
25
56
|
if (!phrenPath)
|
|
@@ -41,6 +72,10 @@ function readGovernanceProactivityPreferences(explicitPhrenPath) {
|
|
|
41
72
|
return {};
|
|
42
73
|
}
|
|
43
74
|
function getConfiguredProactivityDefault(explicitPhrenPath) {
|
|
75
|
+
// Resolution chain: user prefs (highest) → governance prefs → install prefs → default
|
|
76
|
+
const userPrefs = readUserPreferences(explicitPhrenPath);
|
|
77
|
+
if (userPrefs.proactivity)
|
|
78
|
+
return userPrefs.proactivity;
|
|
44
79
|
const governancePreference = readGovernanceProactivityPreferences(explicitPhrenPath).proactivity;
|
|
45
80
|
if (governancePreference)
|
|
46
81
|
return governancePreference;
|
|
@@ -75,6 +110,12 @@ function getWorkflowPolicySensitivityLevel(explicitPhrenPath) {
|
|
|
75
110
|
}
|
|
76
111
|
}
|
|
77
112
|
function getConfiguredProactivityLevelForFindingsDefault(explicitPhrenPath) {
|
|
113
|
+
// User prefs take priority over governance prefs
|
|
114
|
+
const userPrefs = readUserPreferences(explicitPhrenPath);
|
|
115
|
+
if (userPrefs.proactivityFindings)
|
|
116
|
+
return userPrefs.proactivityFindings;
|
|
117
|
+
if (userPrefs.proactivity)
|
|
118
|
+
return userPrefs.proactivity;
|
|
78
119
|
const prefs = readGovernanceProactivityPreferences(explicitPhrenPath);
|
|
79
120
|
return prefs.proactivityFindings
|
|
80
121
|
?? prefs.proactivity
|
|
@@ -82,6 +123,12 @@ function getConfiguredProactivityLevelForFindingsDefault(explicitPhrenPath) {
|
|
|
82
123
|
?? getConfiguredProactivityDefault(explicitPhrenPath);
|
|
83
124
|
}
|
|
84
125
|
function getConfiguredProactivityLevelForTaskDefault(explicitPhrenPath) {
|
|
126
|
+
// User prefs take priority over governance prefs
|
|
127
|
+
const userPrefs = readUserPreferences(explicitPhrenPath);
|
|
128
|
+
if (userPrefs.proactivityTask)
|
|
129
|
+
return userPrefs.proactivityTask;
|
|
130
|
+
if (userPrefs.proactivity)
|
|
131
|
+
return userPrefs.proactivity;
|
|
85
132
|
const prefs = readGovernanceProactivityPreferences(explicitPhrenPath);
|
|
86
133
|
return prefs.proactivityTask
|
|
87
134
|
?? prefs.proactivity
|
|
@@ -116,6 +116,104 @@ export function setMachineProfile(phrenPath, machine, profile) {
|
|
|
116
116
|
return phrenOk(`Mapped machine ${machine} -> ${profile}.`);
|
|
117
117
|
});
|
|
118
118
|
}
|
|
119
|
+
const VALID_FINDING_SENSITIVITY_VALS = ["minimal", "conservative", "balanced", "aggressive"];
|
|
120
|
+
const VALID_PROACTIVITY_VALS = ["high", "medium", "low"];
|
|
121
|
+
const VALID_TASK_MODE_VALS = ["off", "manual", "suggest", "auto"];
|
|
122
|
+
const VALID_RISKY_SECTION_VALS = ["Review", "Stale", "Conflicts"];
|
|
123
|
+
function pickEnumVal(value, allowed) {
|
|
124
|
+
return typeof value === "string" && allowed.includes(value) ? value : undefined;
|
|
125
|
+
}
|
|
126
|
+
function pickFiniteNum(value) {
|
|
127
|
+
return typeof value === "number" && isFinite(value) ? value : undefined;
|
|
128
|
+
}
|
|
129
|
+
function parseProfilePolicyDefaults(raw) {
|
|
130
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw))
|
|
131
|
+
return undefined;
|
|
132
|
+
const data = raw;
|
|
133
|
+
const retentionRaw = data.retentionPolicy && typeof data.retentionPolicy === "object" && !Array.isArray(data.retentionPolicy)
|
|
134
|
+
? data.retentionPolicy
|
|
135
|
+
: undefined;
|
|
136
|
+
const decayRaw = retentionRaw?.decay && typeof retentionRaw.decay === "object" && !Array.isArray(retentionRaw.decay)
|
|
137
|
+
? retentionRaw.decay
|
|
138
|
+
: undefined;
|
|
139
|
+
const workflowRaw = data.workflowPolicy && typeof data.workflowPolicy === "object" && !Array.isArray(data.workflowPolicy)
|
|
140
|
+
? data.workflowPolicy
|
|
141
|
+
: undefined;
|
|
142
|
+
const result = {};
|
|
143
|
+
const fs_ = pickEnumVal(data.findingSensitivity, VALID_FINDING_SENSITIVITY_VALS);
|
|
144
|
+
if (fs_)
|
|
145
|
+
result.findingSensitivity = fs_;
|
|
146
|
+
const pr = pickEnumVal(data.proactivity, VALID_PROACTIVITY_VALS);
|
|
147
|
+
if (pr)
|
|
148
|
+
result.proactivity = pr;
|
|
149
|
+
const prf = pickEnumVal(data.proactivityFindings, VALID_PROACTIVITY_VALS);
|
|
150
|
+
if (prf)
|
|
151
|
+
result.proactivityFindings = prf;
|
|
152
|
+
const prt = pickEnumVal(data.proactivityTask, VALID_PROACTIVITY_VALS);
|
|
153
|
+
if (prt)
|
|
154
|
+
result.proactivityTask = prt;
|
|
155
|
+
const tm = pickEnumVal(data.taskMode, VALID_TASK_MODE_VALS);
|
|
156
|
+
if (tm)
|
|
157
|
+
result.taskMode = tm;
|
|
158
|
+
if (retentionRaw) {
|
|
159
|
+
const ret = {};
|
|
160
|
+
const ttlDays = pickFiniteNum(retentionRaw.ttlDays);
|
|
161
|
+
if (ttlDays !== undefined)
|
|
162
|
+
ret.ttlDays = ttlDays;
|
|
163
|
+
const retentionDays = pickFiniteNum(retentionRaw.retentionDays);
|
|
164
|
+
if (retentionDays !== undefined)
|
|
165
|
+
ret.retentionDays = retentionDays;
|
|
166
|
+
const aat = pickFiniteNum(retentionRaw.autoAcceptThreshold);
|
|
167
|
+
if (aat !== undefined)
|
|
168
|
+
ret.autoAcceptThreshold = aat;
|
|
169
|
+
const mic = pickFiniteNum(retentionRaw.minInjectConfidence);
|
|
170
|
+
if (mic !== undefined)
|
|
171
|
+
ret.minInjectConfidence = mic;
|
|
172
|
+
if (decayRaw) {
|
|
173
|
+
const decay = {};
|
|
174
|
+
const d30 = pickFiniteNum(decayRaw.d30);
|
|
175
|
+
if (d30 !== undefined)
|
|
176
|
+
decay.d30 = d30;
|
|
177
|
+
const d60 = pickFiniteNum(decayRaw.d60);
|
|
178
|
+
if (d60 !== undefined)
|
|
179
|
+
decay.d60 = d60;
|
|
180
|
+
const d90 = pickFiniteNum(decayRaw.d90);
|
|
181
|
+
if (d90 !== undefined)
|
|
182
|
+
decay.d90 = d90;
|
|
183
|
+
const d120 = pickFiniteNum(decayRaw.d120);
|
|
184
|
+
if (d120 !== undefined)
|
|
185
|
+
decay.d120 = d120;
|
|
186
|
+
if (Object.keys(decay).length > 0)
|
|
187
|
+
ret.decay = decay;
|
|
188
|
+
}
|
|
189
|
+
if (Object.keys(ret).length > 0)
|
|
190
|
+
result.retentionPolicy = ret;
|
|
191
|
+
}
|
|
192
|
+
if (workflowRaw) {
|
|
193
|
+
const wf = {};
|
|
194
|
+
const lct = pickFiniteNum(workflowRaw.lowConfidenceThreshold);
|
|
195
|
+
if (lct !== undefined)
|
|
196
|
+
wf.lowConfidenceThreshold = lct;
|
|
197
|
+
if (Array.isArray(workflowRaw.riskySections)) {
|
|
198
|
+
const rs = workflowRaw.riskySections
|
|
199
|
+
.filter((s) => typeof s === "string" && VALID_RISKY_SECTION_VALS.includes(s));
|
|
200
|
+
if (rs.length > 0)
|
|
201
|
+
wf.riskySections = rs;
|
|
202
|
+
}
|
|
203
|
+
if (Object.keys(wf).length > 0)
|
|
204
|
+
result.workflowPolicy = wf;
|
|
205
|
+
}
|
|
206
|
+
return Object.keys(result).length > 0 ? result : undefined;
|
|
207
|
+
}
|
|
208
|
+
export function getActiveProfileDefaults(phrenPath, profile) {
|
|
209
|
+
const profiles = listProfiles(phrenPath);
|
|
210
|
+
if (!profiles.ok)
|
|
211
|
+
return undefined;
|
|
212
|
+
const activeName = profile ?? profiles.data[0]?.name;
|
|
213
|
+
if (!activeName)
|
|
214
|
+
return undefined;
|
|
215
|
+
return profiles.data.find((p) => p.name === activeName)?.defaults;
|
|
216
|
+
}
|
|
119
217
|
export function listProfiles(phrenPath) {
|
|
120
218
|
const profilesDir = path.join(phrenPath, "profiles");
|
|
121
219
|
if (!fs.existsSync(profilesDir))
|
|
@@ -136,7 +234,8 @@ export function listProfiles(phrenPath) {
|
|
|
136
234
|
const projects = Array.isArray(data?.projects)
|
|
137
235
|
? data.projects.map((project) => String(project)).filter(Boolean)
|
|
138
236
|
: [];
|
|
139
|
-
|
|
237
|
+
const defaults = parseProfilePolicyDefaults(data?.defaults);
|
|
238
|
+
profiles.push({ name, file: full, projects, ...(defaults ? { defaults } : {}) });
|
|
140
239
|
}
|
|
141
240
|
catch (err) {
|
|
142
241
|
if ((process.env.PHREN_DEBUG))
|
package/mcp/dist/shared-index.js
CHANGED
|
@@ -303,7 +303,7 @@ function computePhrenHash(phrenPath, profile, preGlobbed) {
|
|
|
303
303
|
process.stderr.write(`[phren] computePhrenHash skip: ${errorMessage(err)}\n`);
|
|
304
304
|
}
|
|
305
305
|
}
|
|
306
|
-
const indexPolicyPath = path.join(phrenPath, ".
|
|
306
|
+
const indexPolicyPath = path.join(phrenPath, ".config", "index-policy.json");
|
|
307
307
|
if (fs.existsSync(indexPolicyPath)) {
|
|
308
308
|
try {
|
|
309
309
|
const stat = fs.statSync(indexPolicyPath);
|
|
@@ -683,7 +683,7 @@ function isSentinelFresh(phrenPath, sentinel) {
|
|
|
683
683
|
// Check mtime of key directories — if any are newer than the sentinel, it's stale
|
|
684
684
|
const dirsToCheck = [
|
|
685
685
|
phrenPath,
|
|
686
|
-
path.join(phrenPath, ".
|
|
686
|
+
path.join(phrenPath, ".config"),
|
|
687
687
|
path.join(phrenPath, ".runtime"),
|
|
688
688
|
];
|
|
689
689
|
for (const dir of dirsToCheck) {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// shared-retrieval.ts — shared retrieval core used by hooks and MCP search.
|
|
2
2
|
import { getQualityMultiplier, entryScoreKey, } from "./shared-governance.js";
|
|
3
|
-
import { queryDocRows, queryRows, cosineFallback, extractSnippet, getDocSourceKey, getEntityBoostDocs, decodeFiniteNumber, rowToDocWithRowid, } from "./shared-index.js";
|
|
3
|
+
import { queryDocRows, queryRows, cosineFallback, extractSnippet, getDocSourceKey, getEntityBoostDocs, decodeFiniteNumber, rowToDocWithRowid, buildIndex, } from "./shared-index.js";
|
|
4
4
|
import { filterTrustedFindingsDetailed, } from "./shared-content.js";
|
|
5
5
|
import { parseCitationComment } from "./content-citation.js";
|
|
6
6
|
import { getHighImpactFindings } from "./finding-impact.js";
|
|
@@ -490,6 +490,47 @@ export async function searchKnowledgeRows(db, options) {
|
|
|
490
490
|
}
|
|
491
491
|
return { safeQuery, rows, usedFallback };
|
|
492
492
|
}
|
|
493
|
+
/**
|
|
494
|
+
* Parse PHREN_FEDERATION_PATHS env var and return valid, distinct paths.
|
|
495
|
+
* Paths are colon-separated. The local phrenPath is excluded to avoid duplicate results.
|
|
496
|
+
*/
|
|
497
|
+
export function parseFederationPaths(localPhrenPath) {
|
|
498
|
+
const raw = process.env.PHREN_FEDERATION_PATHS ?? "";
|
|
499
|
+
if (!raw.trim())
|
|
500
|
+
return [];
|
|
501
|
+
return raw
|
|
502
|
+
.split(":")
|
|
503
|
+
.map((p) => p.trim())
|
|
504
|
+
.filter((p) => p.length > 0 && p !== localPhrenPath && fs.existsSync(p));
|
|
505
|
+
}
|
|
506
|
+
/**
|
|
507
|
+
* Search additional phren stores defined in PHREN_FEDERATION_PATHS.
|
|
508
|
+
* Returns an array of results tagged with their source store. Read-only — no mutations.
|
|
509
|
+
*/
|
|
510
|
+
export async function searchFederatedStores(localPhrenPath, options) {
|
|
511
|
+
const federationPaths = parseFederationPaths(localPhrenPath);
|
|
512
|
+
if (federationPaths.length === 0)
|
|
513
|
+
return [];
|
|
514
|
+
const allRows = [];
|
|
515
|
+
for (const storePath of federationPaths) {
|
|
516
|
+
try {
|
|
517
|
+
const federatedDb = await buildIndex(storePath);
|
|
518
|
+
const result = await searchKnowledgeRows(federatedDb, { ...options, phrenPath: storePath });
|
|
519
|
+
if (result.rows && result.rows.length > 0) {
|
|
520
|
+
for (const row of result.rows) {
|
|
521
|
+
allRows.push({ ...row, federationSource: storePath });
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
catch (err) {
|
|
526
|
+
if (process.env.PHREN_DEBUG) {
|
|
527
|
+
process.stderr.write(`[phren] federatedSearch storePath=${storePath}: ${errorMessage(err)}\n`);
|
|
528
|
+
}
|
|
529
|
+
// Federation errors are non-fatal — continue with other stores
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
return allRows;
|
|
533
|
+
}
|
|
493
534
|
// ── Trust filter ─────────────────────────────────────────────────────────────
|
|
494
535
|
const TRUST_FILTERED_TYPES = new Set(["findings", "reference", "knowledge"]);
|
|
495
536
|
/** Apply trust filter to rows. Returns filtered rows plus any queue/audit items to be written
|
package/mcp/dist/shell-input.js
CHANGED
|
@@ -383,7 +383,7 @@ export async function executePalette(host, input) {
|
|
|
383
383
|
process.stderr.write(`[phren] shell status gitStatus: ${errorMessage(err)}\n`);
|
|
384
384
|
}
|
|
385
385
|
const auditPathNew = runtimeFile(host.phrenPath, "audit.log");
|
|
386
|
-
const auditPathLegacy = path.join(host.phrenPath, ".
|
|
386
|
+
const auditPathLegacy = path.join(host.phrenPath, ".config", "audit.log");
|
|
387
387
|
const auditPath = fs.existsSync(auditPathNew) ? auditPathNew : auditPathLegacy;
|
|
388
388
|
if (fs.existsSync(auditPath)) {
|
|
389
389
|
const auditLines = fs.readFileSync(auditPath, "utf8").split("\n")
|