@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.
Files changed (63) hide show
  1. package/README.md +2 -8
  2. package/mcp/dist/cli-actions.js +5 -5
  3. package/mcp/dist/cli-config.js +334 -127
  4. package/mcp/dist/cli-govern.js +35 -63
  5. package/mcp/dist/cli-graph.js +3 -2
  6. package/mcp/dist/cli-hooks-globs.js +2 -1
  7. package/mcp/dist/cli-hooks-output.js +3 -3
  8. package/mcp/dist/cli-hooks.js +39 -32
  9. package/mcp/dist/cli-namespaces.js +15 -5
  10. package/mcp/dist/cli-search.js +2 -2
  11. package/mcp/dist/content-archive.js +2 -2
  12. package/mcp/dist/content-dedup.js +9 -9
  13. package/mcp/dist/embedding.js +7 -7
  14. package/mcp/dist/entrypoint.js +129 -102
  15. package/mcp/dist/governance-locks.js +6 -5
  16. package/mcp/dist/governance-policy.js +155 -2
  17. package/mcp/dist/governance-scores.js +3 -3
  18. package/mcp/dist/hooks.js +39 -18
  19. package/mcp/dist/index.js +4 -4
  20. package/mcp/dist/init-config.js +3 -24
  21. package/mcp/dist/init-setup.js +5 -5
  22. package/mcp/dist/init.js +170 -23
  23. package/mcp/dist/link-checksums.js +3 -2
  24. package/mcp/dist/link-context.js +1 -1
  25. package/mcp/dist/link-doctor.js +3 -3
  26. package/mcp/dist/link-skills.js +98 -12
  27. package/mcp/dist/link.js +17 -27
  28. package/mcp/dist/machine-identity.js +1 -9
  29. package/mcp/dist/mcp-config.js +247 -42
  30. package/mcp/dist/mcp-data.js +9 -9
  31. package/mcp/dist/mcp-extract-facts.js +1 -1
  32. package/mcp/dist/mcp-extract.js +2 -2
  33. package/mcp/dist/mcp-finding.js +6 -6
  34. package/mcp/dist/mcp-graph.js +11 -11
  35. package/mcp/dist/mcp-ops.js +18 -18
  36. package/mcp/dist/mcp-search.js +8 -8
  37. package/mcp/dist/memory-ui-page.js +23 -0
  38. package/mcp/dist/memory-ui-scripts.js +210 -27
  39. package/mcp/dist/memory-ui-server.js +115 -3
  40. package/mcp/dist/phren-paths.js +7 -7
  41. package/mcp/dist/profile-store.js +2 -2
  42. package/mcp/dist/project-config.js +63 -16
  43. package/mcp/dist/session-utils.js +3 -2
  44. package/mcp/dist/shared-fragment-graph.js +22 -21
  45. package/mcp/dist/shared-index.js +144 -105
  46. package/mcp/dist/shared-retrieval.js +19 -13
  47. package/mcp/dist/shared-search-fallback.js +13 -13
  48. package/mcp/dist/shared-sqljs.js +3 -2
  49. package/mcp/dist/shared.js +3 -3
  50. package/mcp/dist/shell-input.js +1 -1
  51. package/mcp/dist/shell-state-store.js +1 -1
  52. package/mcp/dist/shell-view.js +3 -2
  53. package/mcp/dist/shell.js +1 -1
  54. package/mcp/dist/skill-files.js +4 -10
  55. package/mcp/dist/skill-registry.js +3 -0
  56. package/mcp/dist/status.js +41 -13
  57. package/mcp/dist/task-hygiene.js +1 -1
  58. package/mcp/dist/telemetry.js +5 -4
  59. package/mcp/dist/update.js +1 -1
  60. package/mcp/dist/utils.js +3 -3
  61. package/package.json +2 -2
  62. package/starter/global/skills/audit.md +106 -0
  63. package/mcp/dist/shared-paths.js +0 -1
@@ -4,7 +4,7 @@ import { queryDocRows, queryRows, cosineFallback, extractSnippet, getDocSourceKe
4
4
  import { filterTrustedFindingsDetailed, } from "./shared-content.js";
5
5
  import { parseCitationComment } from "./content-citation.js";
6
6
  import { getHighImpactFindings } from "./finding-impact.js";
7
- import { buildFtsQueryVariants, buildRelaxedFtsQuery, isFeatureEnabled, STOP_WORDS } from "./utils.js";
7
+ import { buildFtsQueryVariants, buildRelaxedFtsQuery, isFeatureEnabled, STOP_WORDS, errorMessage } from "./utils.js";
8
8
  import * as fs from "fs";
9
9
  import * as path from "path";
10
10
  import { getProjectGlobBoost } from "./cli-hooks-globs.js";
@@ -34,17 +34,23 @@ const TASK_RESCUE_SCORE_MARGIN = 0.6;
34
34
  /** Fraction of bullets that must be low-value before applying the low-value penalty. */
35
35
  const LOW_VALUE_BULLET_FRACTION = 0.5;
36
36
  // ── Intent and scoring helpers ───────────────────────────────────────────────
37
+ const INTENT_SKILL_CMD_RE = /(?:^|\s)\/(?!(?:home|usr|var|tmp|etc|opt|api|mnt)\b)[a-z][\w-]*\b/;
38
+ const INTENT_SKILL_KW_RE = /\bskill\b/;
39
+ const INTENT_DEBUG_RE = /(bug|error|fix|broken|regression|fail|stack trace)/;
40
+ const INTENT_REVIEW_RE = /(review|audit|pr|pull request|nit|refactor)/;
41
+ const INTENT_BUILD_RE = /(build|deploy|release|ci|workflow|pipeline|test)/;
42
+ const INTENT_DOCS_RE = /\b(doc|docs|readme|explain|guide|instructions?)\b/;
37
43
  export function detectTaskIntent(prompt) {
38
44
  const p = prompt.toLowerCase();
39
- if (/(?:^|\s)\/(?!(?:home|usr|var|tmp|etc|opt|api|mnt)\b)[a-z][\w-]*\b/.test(p) || /\bskill\b/.test(p))
45
+ if (INTENT_SKILL_CMD_RE.test(p) || INTENT_SKILL_KW_RE.test(p))
40
46
  return "skill";
41
- if (/(bug|error|fix|broken|regression|fail|stack trace)/.test(p))
47
+ if (INTENT_DEBUG_RE.test(p))
42
48
  return "debug";
43
- if (/(review|audit|pr|pull request|nit|refactor)/.test(p))
49
+ if (INTENT_REVIEW_RE.test(p))
44
50
  return "review";
45
- if (/(build|deploy|release|ci|workflow|pipeline|test)/.test(p))
51
+ if (INTENT_BUILD_RE.test(p))
46
52
  return "build";
47
- if (/\b(doc|docs|readme|explain|guide|instructions?)\b/.test(p))
53
+ if (INTENT_DOCS_RE.test(p))
48
54
  return "docs";
49
55
  return "general";
50
56
  }
@@ -385,8 +391,8 @@ export async function searchDocumentsAsync(db, safeQuery, prompt, keywords, dete
385
391
  }
386
392
  catch (err) {
387
393
  // Vector search failure is non-fatal — return sync result
388
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
389
- process.stderr.write(`[phren] hybridSearch vectorFallback: ${err instanceof Error ? err.message : String(err)}\n`);
394
+ if ((process.env.PHREN_DEBUG))
395
+ process.stderr.write(`[phren] hybridSearch vectorFallback: ${errorMessage(err)}\n`);
390
396
  return syncResult;
391
397
  }
392
398
  }
@@ -445,7 +451,7 @@ export async function searchKnowledgeRows(db, options) {
445
451
  }
446
452
  }
447
453
  catch (err) {
448
- debugLog(`rowid dedup query failed: ${err instanceof Error ? err.message : String(err)}`);
454
+ debugLog(`rowid dedup query failed: ${errorMessage(err)}`);
449
455
  }
450
456
  const cosineResults = cosineFallback(db, query, ftsRowids, maxResults - rows.length)
451
457
  .filter((doc) => (!filterProject || doc.project === filterProject) && (!filterType || doc.type === filterType));
@@ -485,8 +491,8 @@ export async function searchKnowledgeRows(db, options) {
485
491
  }
486
492
  }
487
493
  catch (err) {
488
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG)) {
489
- process.stderr.write(`[phren] vectorFallback: ${err instanceof Error ? err.message : String(err)}\n`);
494
+ if ((process.env.PHREN_DEBUG)) {
495
+ process.stderr.write(`[phren] vectorFallback: ${errorMessage(err)}\n`);
490
496
  }
491
497
  }
492
498
  }
@@ -732,8 +738,8 @@ export function markStaleCitations(snippet) {
732
738
  }
733
739
  }
734
740
  catch (err) {
735
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
736
- process.stderr.write(`[phren] applyCitationAnnotations fileRead: ${err instanceof Error ? err.message : String(err)}\n`);
741
+ if ((process.env.PHREN_DEBUG))
742
+ process.stderr.write(`[phren] applyCitationAnnotations fileRead: ${errorMessage(err)}\n`);
737
743
  stale = true;
738
744
  }
739
745
  }
@@ -1,6 +1,6 @@
1
1
  import { createHash } from "crypto";
2
2
  import { debugLog } from "./shared.js";
3
- import { STOP_WORDS } from "./utils.js";
3
+ import { STOP_WORDS, errorMessage } from "./utils.js";
4
4
  import { porterStem } from "./shared-stemmer.js";
5
5
  import { classifyFile, normalizeIndexedContent, rowToDocWithRowid } from "./shared-index.js";
6
6
  import { embedText, cosineSimilarity, getEmbeddingModel, getOllamaUrl, getCloudEmbeddingUrl } from "./shared-ollama.js";
@@ -191,8 +191,8 @@ export function cosineFallback(db, query, excludeRowids, limit) {
191
191
  }
192
192
  }
193
193
  catch (err) {
194
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
195
- process.stderr.write(`[phren] cosineFallback count: ${err instanceof Error ? err.message : String(err)}\n`);
194
+ if ((process.env.PHREN_DEBUG))
195
+ process.stderr.write(`[phren] cosineFallback count: ${errorMessage(err)}\n`);
196
196
  return [];
197
197
  }
198
198
  if (totalDocs > COSINE_MAX_CORPUS) {
@@ -220,8 +220,8 @@ export function cosineFallback(db, query, excludeRowids, limit) {
220
220
  ftsRows.push(...ftsRes[0].values);
221
221
  }
222
222
  catch (err) {
223
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
224
- process.stderr.write(`[phren] cosineFallback FTS pre-filter: ${err instanceof Error ? err.message : String(err)}\n`);
223
+ if ((process.env.PHREN_DEBUG))
224
+ process.stderr.write(`[phren] cosineFallback FTS pre-filter: ${errorMessage(err)}\n`);
225
225
  }
226
226
  }
227
227
  // If FTS gave fewer than cap, supplement with deterministic rowid windows.
@@ -258,8 +258,8 @@ export function cosineFallback(db, query, excludeRowids, limit) {
258
258
  }
259
259
  }
260
260
  catch (err) {
261
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
262
- process.stderr.write(`[phren] cosineFallback deterministicSample: ${err instanceof Error ? err.message : String(err)}\n`);
261
+ if ((process.env.PHREN_DEBUG))
262
+ process.stderr.write(`[phren] cosineFallback deterministicSample: ${errorMessage(err)}\n`);
263
263
  }
264
264
  }
265
265
  if (ftsRows.length === 0)
@@ -269,8 +269,8 @@ export function cosineFallback(db, query, excludeRowids, limit) {
269
269
  }
270
270
  }
271
271
  catch (err) {
272
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
273
- process.stderr.write(`[phren] cosineFallback loadDocs: ${err instanceof Error ? err.message : String(err)}\n`);
272
+ if ((process.env.PHREN_DEBUG))
273
+ process.stderr.write(`[phren] cosineFallback loadDocs: ${errorMessage(err)}\n`);
274
274
  return [];
275
275
  }
276
276
  // Separate rowids, DocRows, and content strings for scoring
@@ -315,8 +315,8 @@ export async function vectorFallback(phrenPath, query, excludePaths, limit, proj
315
315
  await cache.load();
316
316
  }
317
317
  catch (err) {
318
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
319
- process.stderr.write(`[phren] vectorFallback cacheLoad: ${err instanceof Error ? err.message : String(err)}\n`);
318
+ if ((process.env.PHREN_DEBUG))
319
+ process.stderr.write(`[phren] vectorFallback cacheLoad: ${errorMessage(err)}\n`);
320
320
  }
321
321
  }
322
322
  if (cache.size() === 0)
@@ -367,8 +367,8 @@ export async function vectorFallback(phrenPath, query, excludePaths, limit, proj
367
367
  }
368
368
  }
369
369
  catch (err) {
370
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
371
- process.stderr.write(`[phren] vectorFallback fileRead: ${err instanceof Error ? err.message : String(err)}\n`);
370
+ if ((process.env.PHREN_DEBUG))
371
+ process.stderr.write(`[phren] vectorFallback fileRead: ${errorMessage(err)}\n`);
372
372
  }
373
373
  return { project: entryProject, filename, type, content, path: e.path };
374
374
  });
@@ -2,6 +2,7 @@ import * as fs from "fs";
2
2
  import * as path from "path";
3
3
  import { fileURLToPath } from "url";
4
4
  import { createRequire } from "module";
5
+ import { errorMessage } from "./utils.js";
5
6
  const require = createRequire(import.meta.url);
6
7
  /**
7
8
  * Locate the sql.js-fts5 WASM binary by require.resolve with path-probe fallback.
@@ -14,8 +15,8 @@ function findWasmBinary() {
14
15
  return fs.readFileSync(resolved);
15
16
  }
16
17
  catch (err) {
17
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
18
- process.stderr.write(`[phren] findWasmBinary requireResolve: ${err instanceof Error ? err.message : String(err)}\n`);
18
+ if ((process.env.PHREN_DEBUG))
19
+ process.stderr.write(`[phren] findWasmBinary requireResolve: ${errorMessage(err)}\n`);
19
20
  // fall through to path probing
20
21
  }
21
22
  const __filename = fileURLToPath(import.meta.url);
@@ -50,7 +50,7 @@ export function appendAuditLog(phrenPath, event, details) {
50
50
  break;
51
51
  }
52
52
  catch (err) {
53
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
53
+ if ((process.env.PHREN_DEBUG))
54
54
  process.stderr.write(`[phren] appendAuditLog lockWrite: ${errorMessage(err)}\n`);
55
55
  try {
56
56
  const stat = fs.statSync(lockPath);
@@ -68,7 +68,7 @@ export function appendAuditLog(phrenPath, event, details) {
68
68
  }
69
69
  }
70
70
  catch (statErr) {
71
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
71
+ if ((process.env.PHREN_DEBUG))
72
72
  process.stderr.write(`[phren] appendAuditLog staleStat: ${errorMessage(statErr)}\n`);
73
73
  }
74
74
  Atomics.wait(waiter, 0, 0, pollMs);
@@ -97,7 +97,7 @@ export function appendAuditLog(phrenPath, event, details) {
97
97
  fs.unlinkSync(lockPath);
98
98
  }
99
99
  catch (err) {
100
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
100
+ if ((process.env.PHREN_DEBUG))
101
101
  process.stderr.write(`[phren] appendAuditLog unlock: ${errorMessage(err)}\n`);
102
102
  }
103
103
  }
@@ -379,7 +379,7 @@ export async function executePalette(host, input) {
379
379
  }
380
380
  }
381
381
  catch (err) {
382
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
382
+ if ((process.env.PHREN_DEBUG))
383
383
  process.stderr.write(`[phren] shell status gitStatus: ${errorMessage(err)}\n`);
384
384
  }
385
385
  const auditPathNew = runtimeFile(host.phrenPath, "audit.log");
@@ -45,7 +45,7 @@ export function loadShellState(phrenPath) {
45
45
  };
46
46
  }
47
47
  catch (err) {
48
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
48
+ if ((process.env.PHREN_DEBUG))
49
49
  process.stderr.write(`[phren] loadShellState parse: ${errorMessage(err)}\n`);
50
50
  return fallback;
51
51
  }
@@ -13,6 +13,7 @@ import { listMachines, listProfiles, } from "./data-access.js";
13
13
  import { readInstallPreferences } from "./init-preferences.js";
14
14
  import { isProjectHookEnabled, readProjectConfig } from "./project-config.js";
15
15
  import { getScopedSkills } from "./skill-registry.js";
16
+ import { errorMessage } from "./utils.js";
16
17
  // ── Tab bar ────────────────────────────────────────────────────────────────
17
18
  function renderTabBar(state) {
18
19
  const cols = renderWidth();
@@ -248,8 +249,8 @@ function parseSubsections(taskPath, project, cache) {
248
249
  }
249
250
  }
250
251
  catch (err) {
251
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
252
- process.stderr.write(`[phren] buildSubsectionMap: ${err instanceof Error ? err.message : String(err)}\n`);
252
+ if ((process.env.PHREN_DEBUG))
253
+ process.stderr.write(`[phren] buildSubsectionMap: ${errorMessage(err)}\n`);
253
254
  }
254
255
  const newCache = { project, map };
255
256
  return { map, cache: newCache };
package/mcp/dist/shell.js CHANGED
@@ -72,7 +72,7 @@ export class PhrenShell {
72
72
  }
73
73
  }
74
74
  catch (err) {
75
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
75
+ if ((process.env.PHREN_DEBUG))
76
76
  process.stderr.write(`[phren] shell pushUndo: ${errorMessage(err)}\n`);
77
77
  }
78
78
  }
@@ -5,6 +5,7 @@ import { findProjectDir } from "./project-locator.js";
5
5
  import { buildSkillManifest } from "./skill-registry.js";
6
6
  import { setSkillEnabled } from "./skill-state.js";
7
7
  import { errorMessage } from "./utils.js";
8
+ import { isManagedSymlink } from "./link-skills.js";
8
9
  function normalizeSkillRemovalTarget(skillPath) {
9
10
  if (!skillPath)
10
11
  return skillPath;
@@ -19,10 +20,9 @@ function symlinkManagedSkill(src, dest, managedRoot) {
19
20
  if (stat.isSymbolicLink()) {
20
21
  const currentTarget = fs.readlinkSync(dest);
21
22
  const resolvedTarget = path.resolve(path.dirname(dest), currentTarget);
22
- const managedPrefix = path.resolve(managedRoot) + path.sep;
23
23
  if (resolvedTarget === path.resolve(src))
24
24
  return;
25
- if (!resolvedTarget.startsWith(managedPrefix))
25
+ if (!isManagedSymlink(dest, managedRoot))
26
26
  return;
27
27
  fs.unlinkSync(dest);
28
28
  }
@@ -39,18 +39,12 @@ function symlinkManagedSkill(src, dest, managedRoot) {
39
39
  }
40
40
  function removeManagedSkillLink(dest, managedRoot) {
41
41
  try {
42
- const stat = fs.lstatSync(dest);
43
- if (!stat.isSymbolicLink())
44
- return;
45
- const currentTarget = fs.readlinkSync(dest);
46
- const resolvedTarget = path.resolve(path.dirname(dest), currentTarget);
47
- const managedPrefix = path.resolve(managedRoot) + path.sep;
48
- if (!resolvedTarget.startsWith(managedPrefix))
42
+ if (!isManagedSymlink(dest, managedRoot))
49
43
  return;
50
44
  fs.unlinkSync(dest);
51
45
  }
52
46
  catch (err) {
53
- if (err.code !== "ENOENT" && (process.env.PHREN_DEBUG || process.env.PHREN_DEBUG)) {
47
+ if (err.code !== "ENOENT" && (process.env.PHREN_DEBUG)) {
54
48
  process.stderr.write(`[phren] removeManagedSkillLink: ${errorMessage(err)}\n`);
55
49
  }
56
50
  }
@@ -64,6 +64,9 @@ function getProjectLocalSkills(phrenPath, project) {
64
64
  return collectSkills(phrenPath, path.join(projectDir, "skills"), project, "project", "canonical", seen);
65
65
  }
66
66
  function skillPriority(skill) {
67
+ // "user" scope (priority 500) reserved for skills the user marks as untouchable —
68
+ // not yet wired to a scopeType value, but the priority slot is established here
69
+ // so the ordering is stable when we add it.
67
70
  if (skill.scopeType === "project" && skill.sourceKind === "canonical")
68
71
  return 400;
69
72
  if (skill.scopeType === "global" && skill.sourceKind === "canonical")
@@ -3,9 +3,10 @@ import * as path from "path";
3
3
  import { fileURLToPath } from "url";
4
4
  import { findPhrenPath, getProjectDirs, EXEC_TIMEOUT_QUICK_MS, debugLog, isRecord, hookConfigPath, homeDir, readRootManifest, } from "./shared.js";
5
5
  import { buildIndex, detectProject, findFtsCacheForPath, listIndexedDocumentPaths, queryRows } from "./shared-index.js";
6
+ import { mergeConfig, getWorkflowPolicy } from "./shared-governance.js";
6
7
  import { getMcpEnabledPreference, getHooksEnabledPreference } from "./init.js";
7
8
  import { getTelemetrySummary } from "./telemetry.js";
8
- import { runGit as runGitShared } from "./utils.js";
9
+ import { runGit as runGitShared, errorMessage } from "./utils.js";
9
10
  import { readRuntimeHealth, resolveTaskFilePath } from "./data-access.js";
10
11
  import { resolveRuntimeProfile } from "./runtime-profile.js";
11
12
  import { renderPhrenArt } from "./phren-art.js";
@@ -17,8 +18,8 @@ function readPackageVersion() {
17
18
  return typeof pkg.version === "string" ? pkg.version : "unknown";
18
19
  }
19
20
  catch (err) {
20
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
21
- process.stderr.write(`[phren] readPackageVersion: ${err instanceof Error ? err.message : String(err)}\n`);
21
+ if ((process.env.PHREN_DEBUG))
22
+ process.stderr.write(`[phren] readPackageVersion: ${errorMessage(err)}\n`);
22
23
  return "unknown";
23
24
  }
24
25
  }
@@ -71,6 +72,33 @@ export async function runStatus() {
71
72
  // Active project
72
73
  if (activeProject) {
73
74
  console.log(` ${DIM}project${RESET} ${activeProject}`);
75
+ // Effective config for this project
76
+ try {
77
+ const resolved = mergeConfig(phrenPath, activeProject);
78
+ const globalWorkflow = getWorkflowPolicy(phrenPath);
79
+ const projectSensitivity = resolved.findingSensitivity !== globalWorkflow.findingSensitivity
80
+ ? `${resolved.findingSensitivity} ${DIM}(project override)${RESET}`
81
+ : resolved.findingSensitivity;
82
+ const projectTaskMode = resolved.taskMode !== globalWorkflow.taskMode
83
+ ? `${resolved.taskMode} ${DIM}(project override)${RESET}`
84
+ : resolved.taskMode;
85
+ console.log(` ${DIM}sensitivity${RESET} ${projectSensitivity}`);
86
+ console.log(` ${DIM}task mode${RESET} ${projectTaskMode}`);
87
+ if (resolved.proactivity.base || resolved.proactivity.findings || resolved.proactivity.tasks) {
88
+ const parts = [];
89
+ if (resolved.proactivity.base)
90
+ parts.push(`base:${resolved.proactivity.base}`);
91
+ if (resolved.proactivity.findings)
92
+ parts.push(`findings:${resolved.proactivity.findings}`);
93
+ if (resolved.proactivity.tasks)
94
+ parts.push(`tasks:${resolved.proactivity.tasks}`);
95
+ console.log(` ${DIM}proactivity${RESET} ${parts.join(" ")} ${DIM}(project override)${RESET}`);
96
+ }
97
+ }
98
+ catch (err) {
99
+ if ((process.env.PHREN_DEBUG))
100
+ process.stderr.write(`[phren] statusConfig: ${errorMessage(err)}\n`);
101
+ }
74
102
  }
75
103
  // Phren path and config
76
104
  console.log(` ${DIM}path${RESET} ${phrenPath}`);
@@ -102,8 +130,8 @@ export async function runStatus() {
102
130
  mcpConfigured = Boolean(servers?.phren || servers?.phren);
103
131
  }
104
132
  catch (err) {
105
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
106
- process.stderr.write(`[phren] statusWorkspaceMcp parse: ${err instanceof Error ? err.message : String(err)}\n`);
133
+ if ((process.env.PHREN_DEBUG))
134
+ process.stderr.write(`[phren] statusWorkspaceMcp parse: ${errorMessage(err)}\n`);
107
135
  }
108
136
  }
109
137
  }
@@ -120,8 +148,8 @@ export async function runStatus() {
120
148
  hooksInstalled = hookEvents.every((event) => hasCommandHook(hooks?.[event]));
121
149
  }
122
150
  catch (err) {
123
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
124
- process.stderr.write(`[phren] statusHooks settingsParse: ${err instanceof Error ? err.message : String(err)}\n`);
151
+ if ((process.env.PHREN_DEBUG))
152
+ process.stderr.write(`[phren] statusHooks settingsParse: ${errorMessage(err)}\n`);
125
153
  }
126
154
  }
127
155
  }
@@ -151,8 +179,8 @@ export async function runStatus() {
151
179
  }
152
180
  }
153
181
  catch (err) {
154
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
155
- process.stderr.write(`[phren] statusFtsIndex: ${err instanceof Error ? err.message : String(err)}\n`);
182
+ if ((process.env.PHREN_DEBUG))
183
+ process.stderr.write(`[phren] statusFtsIndex: ${errorMessage(err)}\n`);
156
184
  }
157
185
  const ftsLabel = ftsIndexOk
158
186
  ? `${GREEN}ok${RESET} ${DIM}(${ftsIndexSize > 0 ? `${(ftsIndexSize / 1024).toFixed(0)} KB` : `${ftsDocCount ?? 0} docs`})${RESET}`
@@ -186,8 +214,8 @@ export async function runStatus() {
186
214
  }
187
215
  }
188
216
  catch (err) {
189
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
190
- process.stderr.write(`[phren] statusSemantic: ${err instanceof Error ? err.message : String(err)}\n`);
217
+ if ((process.env.PHREN_DEBUG))
218
+ process.stderr.write(`[phren] statusSemantic: ${errorMessage(err)}\n`);
191
219
  }
192
220
  // Agent integration status
193
221
  function hasPhrenEntry(filePath) {
@@ -198,8 +226,8 @@ export async function runStatus() {
198
226
  return raw.includes('"phren"') || raw.includes("'phren'") || raw.includes('"phren"') || raw.includes("'phren'");
199
227
  }
200
228
  catch (err) {
201
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
202
- process.stderr.write(`[phren] hasPhrenEntry: ${err instanceof Error ? err.message : String(err)}\n`);
229
+ if ((process.env.PHREN_DEBUG))
230
+ process.stderr.write(`[phren] hasPhrenEntry: ${errorMessage(err)}\n`);
203
231
  return false;
204
232
  }
205
233
  }
@@ -141,7 +141,7 @@ function collectCorpus(root) {
141
141
  texts.push(fs.readFileSync(fullPath, "utf8").slice(0, MAX_TEXT_BYTES).toLowerCase());
142
142
  }
143
143
  catch (err) {
144
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
144
+ if ((process.env.PHREN_DEBUG))
145
145
  process.stderr.write(`[phren] task hygiene read ${fullPath}: ${errorMessage(err)}\n`);
146
146
  }
147
147
  if (filesSeen >= MAX_FILES_PER_ROOT)
@@ -1,6 +1,7 @@
1
1
  import * as fs from "fs";
2
2
  import * as path from "path";
3
3
  import { runtimeDir } from "./shared.js";
4
+ import { errorMessage } from "./utils.js";
4
5
  // In-memory buffers keyed by phrenPath to batch disk writes
5
6
  // Keeping per-path buffers avoids silently losing events when the active path changes.
6
7
  const buffers = new Map();
@@ -25,8 +26,8 @@ function loadFromDisk(phrenPath) {
25
26
  };
26
27
  }
27
28
  catch (err) {
28
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
29
- process.stderr.write(`[phren] telemetry loadFromDisk: ${err instanceof Error ? err.message : String(err)}\n`);
29
+ if ((process.env.PHREN_DEBUG))
30
+ process.stderr.write(`[phren] telemetry loadFromDisk: ${errorMessage(err)}\n`);
30
31
  return defaults;
31
32
  }
32
33
  }
@@ -57,8 +58,8 @@ function flushTelemetryForPath(phrenPath) {
57
58
  fs.writeFileSync(file, JSON.stringify(data, null, 2) + "\n");
58
59
  }
59
60
  catch (err) {
60
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
61
- process.stderr.write(`[phren] telemetry flush: ${err instanceof Error ? err.message : String(err)}\n`);
61
+ if ((process.env.PHREN_DEBUG))
62
+ process.stderr.write(`[phren] telemetry flush: ${errorMessage(err)}\n`);
62
63
  }
63
64
  pendingCounts.set(phrenPath, 0);
64
65
  }
@@ -63,7 +63,7 @@ export async function runPhrenUpdate(opts = {}) {
63
63
  }
64
64
  }
65
65
  catch (err) {
66
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
66
+ if ((process.env.PHREN_DEBUG))
67
67
  process.stderr.write(`[phren] runPhrenUpdate gitStatus: ${errorMessage(err)}\n`);
68
68
  }
69
69
  const pull = run("git", ["pull", "--rebase", "--autostash"], root);
package/mcp/dist/utils.js CHANGED
@@ -12,7 +12,7 @@ function loadSynonymsJson(fileName) {
12
12
  return JSON.parse(fs.readFileSync(filePath, "utf8"));
13
13
  }
14
14
  catch (err) {
15
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
15
+ if ((process.env.PHREN_DEBUG))
16
16
  process.stderr.write(`[phren] ${fileName} load failed: ${err instanceof Error ? err.message : String(err)}\n`);
17
17
  return {};
18
18
  }
@@ -276,7 +276,7 @@ function parseSynonymsYaml(filePath) {
276
276
  return loaded;
277
277
  }
278
278
  catch (err) {
279
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
279
+ if ((process.env.PHREN_DEBUG))
280
280
  process.stderr.write(`[phren] synonyms.yaml parse failed (${filePath}): ${err instanceof Error ? err.message : String(err)}\n`);
281
281
  return {};
282
282
  }
@@ -315,7 +315,7 @@ function parseLearnedSynonymsJson(filePath) {
315
315
  return loaded;
316
316
  }
317
317
  catch (err) {
318
- if ((process.env.PHREN_DEBUG || process.env.PHREN_DEBUG))
318
+ if ((process.env.PHREN_DEBUG))
319
319
  process.stderr.write(`[phren] learned-synonyms parse failed (${filePath}): ${err instanceof Error ? err.message : String(err)}\n`);
320
320
  return {};
321
321
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@phren/cli",
3
- "version": "0.0.10",
4
- "description": "Knowledge layer for AI agents. Claude remembers you. Phren remembers your work.",
3
+ "version": "0.0.11",
4
+ "description": "Knowledge layer for AI agents. Phren learns and recalls.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "phren": "mcp/dist/index.js"
@@ -0,0 +1,106 @@
1
+ ---
2
+ name: audit
3
+ description: Full codebase audit — dead code, security, dependencies, performance, optimization. Not a diff review — scans everything.
4
+ ---
5
+ # /audit - Full Codebase Audit
6
+
7
+ Unlike `/simplify` (which reviews your last diff), this scans the entire codebase. Run it when you want to clean house.
8
+
9
+ ## What It Does
10
+
11
+ Launch 5 parallel agents. Each one scans the full codebase for a different class of problem. When they're done, aggregate findings and fix what's fixable.
12
+
13
+ ## Phase 1: Discover the Codebase
14
+
15
+ Before launching agents, understand the project:
16
+
17
+ ```
18
+ 1. Read package.json (or pyproject.toml, Cargo.toml, go.mod — whatever applies)
19
+ 2. Find the source directories (src/, lib/, app/, etc.)
20
+ 3. Count files by extension to understand the stack
21
+ 4. Check for existing lint/test configs
22
+ ```
23
+
24
+ Pass this context to every agent so they know where to look.
25
+
26
+ ## Phase 2: Launch 5 Agents in Parallel
27
+
28
+ Use the Agent tool to launch all five concurrently in a single message. Give each agent the project context from Phase 1.
29
+
30
+ ### Agent 1: Dead Code & Unused Exports
31
+
32
+ Find code that exists but isn't used:
33
+
34
+ 1. **Unused exports.** For every `export` in the codebase, check if it's imported anywhere. Flag exports that are only used in their own file or not used at all. Exclude entry points and public API surfaces.
35
+ 2. **Unused dependencies.** Cross-reference `package.json` dependencies against actual imports in source files. Flag packages that are installed but never imported. Check devDependencies too — are test utilities actually used in tests?
36
+ 3. **Dead functions.** Functions defined but never called. Methods on classes that nothing invokes. Event handlers registered but for events that are never emitted.
37
+ 4. **Orphan files.** Files that nothing imports. Test files for source files that no longer exist. Config files for tools that aren't in the project.
38
+ 5. **Feature flags that resolved.** Environment variable checks where one branch is clearly dead. TODO/FIXME/HACK comments older than 6 months.
39
+ 6. **Stale type definitions.** Interfaces or types that nothing references. Generic type parameters that are always the same concrete type.
40
+
41
+ ### Agent 2: Security Scan
42
+
43
+ Look for vulnerabilities in the actual code (not just `npm audit`):
44
+
45
+ 1. **Injection vectors.** Shell commands built from user input without sanitization. SQL queries with string concatenation. HTML built from unescaped variables. Regex built from user input (ReDoS).
46
+ 2. **Path traversal.** Any file operation where the path comes from user input or external data without validation. Check for `../` normalization and symlink following.
47
+ 3. **Secret exposure.** Hardcoded API keys, tokens, passwords. Environment variables logged or included in error messages. Secrets in URLs or query parameters.
48
+ 4. **Insecure defaults.** TLS verification disabled. CORS set to `*`. Cookies without secure/httponly/samesite flags. Debug modes that shouldn't ship.
49
+ 5. **Dependency vulnerabilities.** Run `npm audit` (or equivalent). Check for known CVEs. Flag dependencies that haven't been updated in 12+ months.
50
+ 6. **Auth and access control.** Endpoints or functions that should check permissions but don't. Token validation that's incomplete. Rate limiting gaps.
51
+ 7. **SSRF and network.** Outbound requests where the URL comes from user input. Webhook/callback URLs that aren't validated against internal networks.
52
+
53
+ ### Agent 3: Performance & Efficiency
54
+
55
+ Find code that wastes time or resources:
56
+
57
+ 1. **Startup cost.** What runs at import/require time? Top-level await, synchronous file reads, heavy initialization that could be lazy.
58
+ 2. **N+1 patterns.** Loops that make a network/disk/DB call per iteration instead of batching.
59
+ 3. **Redundant computation.** The same value computed multiple times in a hot path. Missing memoization where inputs rarely change. Expensive operations inside loops that could be hoisted.
60
+ 4. **Blocking operations.** Synchronous file I/O on async paths. `JSON.parse` on large payloads without streaming. CPU-heavy work on the event loop.
61
+ 5. **Unbounded growth.** Caches without eviction. Arrays that grow without limits. Event listeners that are added but never removed. Intervals that are set but never cleared.
62
+ 6. **Over-fetching.** Loading entire files when you need one field. Importing a whole library for one function. Reading all records when filtering for a subset.
63
+ 7. **Missed parallelism.** Sequential `await` calls that are independent and could use `Promise.all`. File operations that could be batched.
64
+
65
+ ### Agent 4: Code Quality & Simplification
66
+
67
+ Find code that's more complicated than it needs to be:
68
+
69
+ 1. **Abstraction debt.** Functions over 80 lines. Files over 500 lines. Classes that do too many things. Deeply nested conditionals (3+ levels).
70
+ 2. **Copy-paste code.** Near-duplicate blocks across files. Similar switch/case statements that should share logic. Functions that differ by one parameter.
71
+ 3. **Unnecessary indirection.** Wrapper functions that add nothing. Abstract classes with one implementation. Factory patterns for objects that are only created once.
72
+ 4. **Type complexity.** Union types with 5+ members. Generic types nested 3+ levels deep. Type assertions (`as`) that could be avoided with better typing.
73
+ 5. **Error handling.** Empty catch blocks. Catch-and-rethrow without adding context. Inconsistent error types across similar operations.
74
+ 6. **Naming.** Boolean variables that don't read as questions. Functions whose names don't describe what they return. Abbreviations that only the author understands.
75
+ 7. **Stale patterns.** Callbacks where promises/async would be cleaner. Manual iteration where array methods would work. Hand-rolled utilities where the language or a dependency provides it.
76
+
77
+ ### Agent 5: Dependency Health
78
+
79
+ Audit the dependency tree:
80
+
81
+ 1. **Outdated packages.** Run `npm outdated` (or equivalent). Flag major version bumps that are available. Note any breaking changes.
82
+ 2. **Heavy dependencies.** Check bundle/install size. Flag packages over 1MB that could be replaced with lighter alternatives or native APIs.
83
+ 3. **Duplicate functionality.** Multiple packages that do the same thing (e.g., both `lodash` and `underscore`, or `axios` and `node-fetch`).
84
+ 4. **License issues.** Check for GPL or other copyleft licenses in a non-GPL project. Flag any "unknown" licenses.
85
+ 5. **Abandoned packages.** Dependencies with no commits in 2+ years, or archived repos.
86
+ 6. **Phantom dependencies.** Imports that resolve only because a parent dependency installs them (not in your own package.json).
87
+
88
+ ## Phase 3: Triage and Fix
89
+
90
+ Wait for all agents. Then:
91
+
92
+ 1. **Deduplicate.** Multiple agents may flag the same issue from different angles. Merge them.
93
+ 2. **Prioritize.** Security issues first. Then dead code (easy wins). Then performance. Then quality.
94
+ 3. **Fix directly.** Don't just report — fix what you can. For things that need the user's input (like removing a dependency that might be used in a way you can't see), ask.
95
+ 4. **Summarize.** Report what was found, what was fixed, and what needs the user's decision.
96
+
97
+ ## Options
98
+
99
+ The user can scope the audit:
100
+
101
+ - `/audit` — full audit, all 5 agents
102
+ - `/audit security` — just the security agent
103
+ - `/audit dead-code` — just dead code detection
104
+ - `/audit performance` — just performance
105
+ - `/audit deps` — just dependency health
106
+ - `/audit quality` — just code quality
@@ -1 +0,0 @@
1
- export * from "./phren-paths.js";