@phren/cli 0.0.17 → 0.0.18

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.
@@ -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
- profiles.push({ name, file: full, projects });
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))
@@ -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, ".governance", "index-policy.json");
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, ".governance"),
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
@@ -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, ".governance", "audit.log");
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")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@phren/cli",
3
- "version": "0.0.17",
3
+ "version": "0.0.18",
4
4
  "description": "Knowledge layer for AI agents. Phren learns and recalls.",
5
5
  "type": "module",
6
6
  "bin": {