@phren/cli 0.0.10 → 0.0.12

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 (100) hide show
  1. package/README.md +11 -17
  2. package/mcp/dist/capabilities/cli.js +1 -1
  3. package/mcp/dist/capabilities/mcp.js +1 -1
  4. package/mcp/dist/capabilities/vscode.js +1 -1
  5. package/mcp/dist/capabilities/web-ui.js +1 -1
  6. package/mcp/dist/cli-actions.js +58 -71
  7. package/mcp/dist/cli-config.js +337 -131
  8. package/mcp/dist/cli-extract.js +3 -2
  9. package/mcp/dist/cli-govern.js +35 -63
  10. package/mcp/dist/cli-graph.js +19 -4
  11. package/mcp/dist/cli-hooks-globs.js +2 -1
  12. package/mcp/dist/cli-hooks-output.js +4 -4
  13. package/mcp/dist/cli-hooks-session.js +1 -1
  14. package/mcp/dist/cli-hooks.js +44 -35
  15. package/mcp/dist/cli-namespaces.js +15 -5
  16. package/mcp/dist/cli-search.js +2 -2
  17. package/mcp/dist/cli.js +1 -1
  18. package/mcp/dist/content-archive.js +23 -14
  19. package/mcp/dist/content-citation.js +13 -2
  20. package/mcp/dist/content-dedup.js +9 -9
  21. package/mcp/dist/content-learning.js +6 -4
  22. package/mcp/dist/content-metadata.js +10 -0
  23. package/mcp/dist/core-finding.js +1 -1
  24. package/mcp/dist/data-access.js +10 -31
  25. package/mcp/dist/data-tasks.js +5 -26
  26. package/mcp/dist/embedding.js +7 -8
  27. package/mcp/dist/entrypoint.js +133 -102
  28. package/mcp/dist/finding-impact.js +1 -32
  29. package/mcp/dist/finding-journal.js +1 -1
  30. package/mcp/dist/finding-lifecycle.js +2 -7
  31. package/mcp/dist/governance-locks.js +12 -5
  32. package/mcp/dist/governance-policy.js +156 -9
  33. package/mcp/dist/governance-scores.js +4 -10
  34. package/mcp/dist/hooks.js +62 -18
  35. package/mcp/dist/index.js +4 -4
  36. package/mcp/dist/init-config.js +4 -25
  37. package/mcp/dist/init-preferences.js +1 -1
  38. package/mcp/dist/init-setup.js +6 -55
  39. package/mcp/dist/init-shared.js +53 -1
  40. package/mcp/dist/init.js +191 -29
  41. package/mcp/dist/link-checksums.js +3 -2
  42. package/mcp/dist/link-context.js +2 -2
  43. package/mcp/dist/link-doctor.js +14 -57
  44. package/mcp/dist/link-skills.js +98 -12
  45. package/mcp/dist/link.js +16 -75
  46. package/mcp/dist/machine-identity.js +1 -9
  47. package/mcp/dist/mcp-config.js +247 -42
  48. package/mcp/dist/mcp-data.js +9 -9
  49. package/mcp/dist/mcp-extract-facts.js +12 -7
  50. package/mcp/dist/mcp-extract.js +2 -2
  51. package/mcp/dist/mcp-finding.js +16 -20
  52. package/mcp/dist/mcp-graph.js +12 -12
  53. package/mcp/dist/mcp-hooks.js +1 -1
  54. package/mcp/dist/mcp-ops.js +18 -18
  55. package/mcp/dist/mcp-search.js +11 -16
  56. package/mcp/dist/mcp-session.js +12 -2
  57. package/mcp/dist/memory-ui-assets.js +1 -36
  58. package/mcp/dist/memory-ui-graph.js +152 -50
  59. package/mcp/dist/memory-ui-page.js +30 -5
  60. package/mcp/dist/memory-ui-scripts.js +252 -63
  61. package/mcp/dist/memory-ui-server.js +115 -3
  62. package/mcp/dist/phren-core.js +2 -0
  63. package/mcp/dist/phren-paths.js +8 -9
  64. package/mcp/dist/proactivity.js +5 -5
  65. package/mcp/dist/profile-store.js +2 -2
  66. package/mcp/dist/project-config.js +64 -17
  67. package/mcp/dist/provider-adapters.js +1 -1
  68. package/mcp/dist/query-correlation.js +22 -19
  69. package/mcp/dist/session-checkpoints.js +14 -14
  70. package/mcp/dist/session-utils.js +3 -2
  71. package/mcp/dist/shared-data-utils.js +28 -0
  72. package/mcp/dist/shared-fragment-graph.js +22 -21
  73. package/mcp/dist/shared-governance.js +1 -1
  74. package/mcp/dist/shared-index.js +144 -105
  75. package/mcp/dist/shared-retrieval.js +21 -23
  76. package/mcp/dist/shared-search-fallback.js +15 -25
  77. package/mcp/dist/shared-sqljs.js +3 -2
  78. package/mcp/dist/shared.js +5 -6
  79. package/mcp/dist/shell-entry.js +1 -1
  80. package/mcp/dist/shell-input.js +63 -53
  81. package/mcp/dist/shell-palette.js +6 -1
  82. package/mcp/dist/shell-render.js +9 -5
  83. package/mcp/dist/shell-state-store.js +2 -5
  84. package/mcp/dist/shell-view.js +7 -6
  85. package/mcp/dist/shell.js +5 -55
  86. package/mcp/dist/skill-files.js +4 -10
  87. package/mcp/dist/skill-registry.js +3 -0
  88. package/mcp/dist/status.js +43 -21
  89. package/mcp/dist/task-hygiene.js +1 -1
  90. package/mcp/dist/telemetry.js +5 -4
  91. package/mcp/dist/update.js +1 -1
  92. package/mcp/dist/utils.js +4 -4
  93. package/package.json +2 -3
  94. package/skills/docs.md +11 -11
  95. package/starter/README.md +1 -1
  96. package/starter/global/CLAUDE.md +2 -2
  97. package/starter/global/skills/audit.md +106 -0
  98. package/mcp/dist/cli-hooks-retrieval.js +0 -2
  99. package/mcp/dist/impact-scoring.js +0 -22
  100. package/mcp/dist/shared-paths.js +0 -1
@@ -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,12 +3,14 @@ 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";
13
+ import { RESET, BOLD, DIM, GREEN, YELLOW, RED, CYAN } from "./shell-render.js";
12
14
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
13
15
  function readPackageVersion() {
14
16
  try {
@@ -17,18 +19,11 @@ function readPackageVersion() {
17
19
  return typeof pkg.version === "string" ? pkg.version : "unknown";
18
20
  }
19
21
  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`);
22
+ if ((process.env.PHREN_DEBUG))
23
+ process.stderr.write(`[phren] readPackageVersion: ${errorMessage(err)}\n`);
22
24
  return "unknown";
23
25
  }
24
26
  }
25
- const RESET = "\x1b[0m";
26
- const BOLD = "\x1b[1m";
27
- const DIM = "\x1b[2m";
28
- const GREEN = "\x1b[32m";
29
- const YELLOW = "\x1b[33m";
30
- const RED = "\x1b[31m";
31
- const CYAN = "\x1b[36m";
32
27
  function check(ok) {
33
28
  return ok ? `${GREEN}ok${RESET}` : `${RED}missing${RESET}`;
34
29
  }
@@ -71,6 +66,33 @@ export async function runStatus() {
71
66
  // Active project
72
67
  if (activeProject) {
73
68
  console.log(` ${DIM}project${RESET} ${activeProject}`);
69
+ // Effective config for this project
70
+ try {
71
+ const resolved = mergeConfig(phrenPath, activeProject);
72
+ const globalWorkflow = getWorkflowPolicy(phrenPath);
73
+ const projectSensitivity = resolved.findingSensitivity !== globalWorkflow.findingSensitivity
74
+ ? `${resolved.findingSensitivity} ${DIM}(project override)${RESET}`
75
+ : resolved.findingSensitivity;
76
+ const projectTaskMode = resolved.taskMode !== globalWorkflow.taskMode
77
+ ? `${resolved.taskMode} ${DIM}(project override)${RESET}`
78
+ : resolved.taskMode;
79
+ console.log(` ${DIM}sensitivity${RESET} ${projectSensitivity}`);
80
+ console.log(` ${DIM}task mode${RESET} ${projectTaskMode}`);
81
+ if (resolved.proactivity.base || resolved.proactivity.findings || resolved.proactivity.tasks) {
82
+ const parts = [];
83
+ if (resolved.proactivity.base)
84
+ parts.push(`base:${resolved.proactivity.base}`);
85
+ if (resolved.proactivity.findings)
86
+ parts.push(`findings:${resolved.proactivity.findings}`);
87
+ if (resolved.proactivity.tasks)
88
+ parts.push(`tasks:${resolved.proactivity.tasks}`);
89
+ console.log(` ${DIM}proactivity${RESET} ${parts.join(" ")} ${DIM}(project override)${RESET}`);
90
+ }
91
+ }
92
+ catch (err) {
93
+ if ((process.env.PHREN_DEBUG))
94
+ process.stderr.write(`[phren] statusConfig: ${errorMessage(err)}\n`);
95
+ }
74
96
  }
75
97
  // Phren path and config
76
98
  console.log(` ${DIM}path${RESET} ${phrenPath}`);
@@ -102,8 +124,8 @@ export async function runStatus() {
102
124
  mcpConfigured = Boolean(servers?.phren || servers?.phren);
103
125
  }
104
126
  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`);
127
+ if ((process.env.PHREN_DEBUG))
128
+ process.stderr.write(`[phren] statusWorkspaceMcp parse: ${errorMessage(err)}\n`);
107
129
  }
108
130
  }
109
131
  }
@@ -120,8 +142,8 @@ export async function runStatus() {
120
142
  hooksInstalled = hookEvents.every((event) => hasCommandHook(hooks?.[event]));
121
143
  }
122
144
  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`);
145
+ if ((process.env.PHREN_DEBUG))
146
+ process.stderr.write(`[phren] statusHooks settingsParse: ${errorMessage(err)}\n`);
125
147
  }
126
148
  }
127
149
  }
@@ -151,8 +173,8 @@ export async function runStatus() {
151
173
  }
152
174
  }
153
175
  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`);
176
+ if ((process.env.PHREN_DEBUG))
177
+ process.stderr.write(`[phren] statusFtsIndex: ${errorMessage(err)}\n`);
156
178
  }
157
179
  const ftsLabel = ftsIndexOk
158
180
  ? `${GREEN}ok${RESET} ${DIM}(${ftsIndexSize > 0 ? `${(ftsIndexSize / 1024).toFixed(0)} KB` : `${ftsDocCount ?? 0} docs`})${RESET}`
@@ -186,8 +208,8 @@ export async function runStatus() {
186
208
  }
187
209
  }
188
210
  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`);
211
+ if ((process.env.PHREN_DEBUG))
212
+ process.stderr.write(`[phren] statusSemantic: ${errorMessage(err)}\n`);
191
213
  }
192
214
  // Agent integration status
193
215
  function hasPhrenEntry(filePath) {
@@ -198,8 +220,8 @@ export async function runStatus() {
198
220
  return raw.includes('"phren"') || raw.includes("'phren'") || raw.includes('"phren"') || raw.includes("'phren'");
199
221
  }
200
222
  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`);
223
+ if ((process.env.PHREN_DEBUG))
224
+ process.stderr.write(`[phren] hasPhrenEntry: ${errorMessage(err)}\n`);
203
225
  return false;
204
226
  }
205
227
  }
@@ -274,7 +296,7 @@ export async function runStatus() {
274
296
  totalTask += countBullets(taskPath);
275
297
  totalQueue += countQueueItems(phrenPath, projName);
276
298
  }
277
- console.log(`\n ${DIM}phren holds${RESET} ${projectDirs.length} projects, ${totalFindings} fragments, ${totalTask} tasks, ${totalQueue} queued`);
299
+ console.log(`\n ${DIM}phren holds${RESET} ${projectDirs.length} projects, ${totalFindings} findings, ${totalTask} tasks, ${totalQueue} queued`);
278
300
  const gitTarget = manifest?.installMode === "project-local" && manifest.workspaceRoot ? manifest.workspaceRoot : phrenPath;
279
301
  const isGitRepo = runGit(gitTarget, ["rev-parse", "--is-inside-work-tree"]) === "true";
280
302
  const hasOriginRemote = isGitRepo && Boolean(runGit(gitTarget, ["remote", "get-url", "origin"]));
@@ -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
  }
@@ -241,7 +241,7 @@ export function sanitizeFts5Query(raw) {
241
241
  if (raw.length > 500)
242
242
  raw = raw.slice(0, 500);
243
243
  // Whitelist approach: only allow alphanumeric, spaces, hyphens, apostrophes, double quotes, asterisks
244
- let q = raw.replace(/[^a-zA-Z0-9 \-'"*]/g, " ");
244
+ let q = raw.replace(/[^a-zA-Z0-9 \-"*]/g, " ");
245
245
  q = q.replace(/\s+/g, " ");
246
246
  q = q.trim();
247
247
  // Q83: FTS5 only accepts * as a prefix operator directly attached to a token
@@ -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.12",
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"
@@ -18,7 +18,6 @@
18
18
  "glob": "^13.0.6",
19
19
  "inquirer": "^12.10.0",
20
20
  "js-yaml": "^4.1.1",
21
- "sharp": "^0.34.5",
22
21
  "sql.js-fts5": "^1.4.0",
23
22
  "zod": "^4.3.6"
24
23
  },
package/skills/docs.md CHANGED
@@ -74,10 +74,10 @@ These numbers appear in multiple files and must all agree:
74
74
 
75
75
  | Item | Where to verify |
76
76
  |------|----------------|
77
- | MCP tool count | `mcp/src/index.ts` count `server.tool(` calls |
78
- | CLI subcommand count | `mcp/src/cli.ts` count `program.command(` calls |
77
+ | MCP tool count | `mcp/src/index.ts`: count `server.tool(` calls |
78
+ | CLI subcommand count | `mcp/src/cli.ts`: count `program.command(` calls |
79
79
  | Version | `package.json` → `version` field |
80
- | Hook events | `mcp/src/init.ts` look for hook registration |
80
+ | Hook events | `mcp/src/init.ts`: look for hook registration |
81
81
 
82
82
  Run `/parity` if it is installed to automate numeric cross-checking across surfaces.
83
83
 
@@ -95,7 +95,7 @@ Sections to verify and update:
95
95
  - **Tool count** in any summary sentence (e.g. "65 MCP tools")
96
96
 
97
97
  When updating the HTML:
98
- - Keep existing structure and CSS classes do not restructure the page
98
+ - Keep existing structure and CSS classes. Do not restructure the page
99
99
  - Update text content only; do not rewrite layout
100
100
  - Preserve the `<details>` blocks for env var categories if they exist
101
101
 
@@ -103,12 +103,12 @@ When updating the HTML:
103
103
 
104
104
  These files are consumed by LLMs directly. Keep them plain text with no HTML.
105
105
 
106
- `llms.txt` short summary (under 60 lines):
106
+ `llms.txt`, short summary (under 60 lines):
107
107
  - Tool count
108
108
  - Install command
109
109
  - One-line description of what phren does
110
110
 
111
- `llms-full.txt` comprehensive reference:
111
+ `llms-full.txt`, comprehensive reference:
112
112
  - All MCP tool signatures with descriptions
113
113
  - All CLI commands
114
114
  - All environment variables
@@ -122,7 +122,7 @@ The CLI commands block must list every top-level command. Add or remove lines to
122
122
 
123
123
  ### 7. Sync version references
124
124
 
125
- `CHANGELOG.md`: the top entry's version must match `package.json`. If a new version was bumped but the changelog has no entry yet, note it do not fabricate one.
125
+ `CHANGELOG.md`: the top entry's version must match `package.json`. If a new version was bumped but the changelog has no entry yet, note it. Do not fabricate one.
126
126
 
127
127
  `README.md` and `docs/index.html`: update any hardcoded version strings (badges, `npm install @phren/cli@X.Y.Z`, etc.).
128
128
 
@@ -150,18 +150,18 @@ No changes needed:
150
150
  - mcp/README.md: tool count correct
151
151
 
152
152
  Warnings:
153
- - docs/faq.md: references `phren init` command was renamed to `phren link` in v0.0.7
153
+ - docs/faq.md: references `phren init`, command was renamed to `phren link` in v0.0.7
154
154
  ```
155
155
 
156
156
  List every file checked. Be specific about what changed and what line/section.
157
157
 
158
158
  ## What not to do
159
159
 
160
- - Do not restructure or reformat documentation for style only fix accuracy
160
+ - Do not restructure or reformat documentation for style. Only fix accuracy
161
161
  - Do not add new sections or features to the docs without user direction
162
162
  - Do not fabricate changelog entries for unreleased versions
163
- - Do not silently skip a surface because it looks "probably fine" check all of them
164
- - Do not guess tool counts always derive from `grep` on the source file
163
+ - Do not silently skip a surface because it looks "probably fine". Check all of them
164
+ - Do not guess tool counts. Always derive from `grep` on the source file
165
165
 
166
166
  ## Related skills
167
167
 
package/starter/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # My Phren
2
2
 
3
- Your personal project store for [phren](https://github.com/alaarab/phren). Phren is your project's memory keeper he holds what your agents learn and surfaces it when it matters, across sessions and machines.
3
+ Your personal project store for [phren](https://github.com/alaarab/phren). Phren is your project's memory keeper. He holds what your agents learn and surfaces it when it matters, across sessions and machines.
4
4
 
5
5
  ## Structure
6
6
 
@@ -56,7 +56,7 @@ Run `/phren-sync` to pull everything down or push changes back.
56
56
 
57
57
  ## MCP tools
58
58
 
59
- The phren MCP server is running. Phren already knows a lot ask him before asking the user to repeat themselves.
59
+ The phren MCP server is running. Phren already knows a lot. Ask him before asking the user to repeat themselves.
60
60
 
61
61
  - **At session start:** ask phren what's active: `list_projects()`, then `get_project_summary(name)` for the relevant project
62
62
  - **When the user mentions a project, codebase, or task:** ask phren first: `search_knowledge(query)` before asking questions
@@ -86,4 +86,4 @@ Read `~/.phren-context.md` at the start of every session for machine-specific co
86
86
 
87
87
  ## Without MCP server
88
88
 
89
- If the MCP server isn't available, phren still helps. Claude reads `~/.phren-context.md` and per-project memory files from `~/.claude/projects/` for context, then fetches details directly from `~/.phren/project-name/` as needed. No MCP required phren just works a bit more quietly.
89
+ If the MCP server isn't available, phren still helps. Claude reads `~/.phren-context.md` and per-project memory files from `~/.claude/projects/` for context, then fetches details directly from `~/.phren/project-name/` as needed. No MCP required, phren just works a bit more quietly.
@@ -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,2 +0,0 @@
1
- // cli-hooks-retrieval.ts — re-export surface for the shared retrieval core.
2
- export * from "./shared-retrieval.js";
@@ -1,22 +0,0 @@
1
- // Backward-compatible wrapper around finding-impact.
2
- import { findingIdFromLine, extractFindingIdsFromSnippet, logImpact, getHighImpactFindings, markImpactEntriesCompletedForSession, } from "./finding-impact.js";
3
- export function impactEntryKey(project, findingId) {
4
- return `${project}\u0000${findingId}`;
5
- }
6
- export { findingIdFromLine, extractFindingIdsFromSnippet, markImpactEntriesCompletedForSession, };
7
- export function appendImpactEntries(phrenPath, entries) {
8
- const pending = entries.filter((entry) => !entry.taskCompleted);
9
- if (pending.length === 0)
10
- return;
11
- logImpact(phrenPath, pending.map((entry) => ({
12
- findingId: entry.findingId,
13
- project: entry.project,
14
- sessionId: entry.sessionId,
15
- })));
16
- }
17
- export function getHighImpactFindingKeys(phrenPath, minSuccessCount = 3) {
18
- const findingIds = getHighImpactFindings(phrenPath, minSuccessCount);
19
- // Legacy API encoded project+findingId; new API tracks finding ID globally.
20
- // Return IDs as-is to preserve compatibility where only membership checks are used.
21
- return findingIds;
22
- }
@@ -1 +0,0 @@
1
- export * from "./phren-paths.js";