@phren/cli 0.0.28 → 0.0.32

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 (147) hide show
  1. package/mcp/dist/capabilities/cli.js +2 -5
  2. package/mcp/dist/capabilities/mcp.js +5 -8
  3. package/mcp/dist/capabilities/types.js +2 -5
  4. package/mcp/dist/capabilities/vscode.js +2 -5
  5. package/mcp/dist/capabilities/web-ui.js +2 -5
  6. package/mcp/dist/{cli-actions.js → cli/actions.js} +22 -21
  7. package/mcp/dist/{cli.js → cli/cli.js} +13 -13
  8. package/mcp/dist/{cli-config.js → cli/config.js} +9 -9
  9. package/mcp/dist/{cli-extract.js → cli/extract.js} +8 -8
  10. package/mcp/dist/{cli-govern.js → cli/govern.js} +10 -9
  11. package/mcp/dist/{cli-graph.js → cli/graph.js} +10 -9
  12. package/mcp/dist/{cli-hooks-citations.js → cli/hooks-citations.js} +2 -2
  13. package/mcp/dist/{cli-hooks-context.js → cli/hooks-context.js} +23 -23
  14. package/mcp/dist/{cli-hooks-globs.js → cli/hooks-globs.js} +4 -4
  15. package/mcp/dist/{cli-hooks-output.js → cli/hooks-output.js} +9 -10
  16. package/mcp/dist/{cli-hooks-session.js → cli/hooks-session.js} +42 -57
  17. package/mcp/dist/{cli-hooks.js → cli/hooks.js} +27 -26
  18. package/mcp/dist/{cli-namespaces.js → cli/namespaces.js} +25 -24
  19. package/mcp/dist/{cli-ops.js → cli/ops.js} +9 -9
  20. package/mcp/dist/{cli-search.js → cli/search.js} +8 -7
  21. package/mcp/dist/cli-hooks-git.js +243 -0
  22. package/mcp/dist/cli-hooks-prompt.js +319 -0
  23. package/mcp/dist/cli-hooks-session-handlers.js +349 -0
  24. package/mcp/dist/cli-hooks-stop.js +557 -0
  25. package/mcp/dist/{content-archive.js → content/archive.js} +8 -9
  26. package/mcp/dist/{content-citation.js → content/citation.js} +5 -5
  27. package/mcp/dist/{content-dedup.js → content/dedup.js} +9 -12
  28. package/mcp/dist/{content-learning.js → content/learning.js} +12 -12
  29. package/mcp/dist/{content-validate.js → content/validate.js} +5 -5
  30. package/mcp/dist/{core-finding.js → core/finding.js} +4 -4
  31. package/mcp/dist/{core-project.js → core/project.js} +4 -4
  32. package/mcp/dist/{core-search.js → core/search.js} +2 -2
  33. package/mcp/dist/{data-access.js → data/access.js} +131 -13
  34. package/mcp/dist/{data-tasks.js → data/tasks.js} +7 -5
  35. package/mcp/dist/embedding.js +9 -14
  36. package/mcp/dist/entrypoint.js +11 -11
  37. package/mcp/dist/{finding-context.js → finding/context.js} +2 -2
  38. package/mcp/dist/{finding-impact.js → finding/impact.js} +3 -3
  39. package/mcp/dist/{finding-journal.js → finding/journal.js} +4 -4
  40. package/mcp/dist/{finding-lifecycle.js → finding/lifecycle.js} +4 -4
  41. package/mcp/dist/{governance-audit.js → governance/audit.js} +2 -2
  42. package/mcp/dist/{governance-locks.js → governance/locks.js} +14 -9
  43. package/mcp/dist/{governance-policy.js → governance/policy.js} +10 -12
  44. package/mcp/dist/{governance-rbac.js → governance/rbac.js} +3 -3
  45. package/mcp/dist/{governance-scores.js → governance/scores.js} +8 -10
  46. package/mcp/dist/hooks.js +39 -31
  47. package/mcp/dist/index-query.js +4 -1
  48. package/mcp/dist/index.js +53 -29
  49. package/mcp/dist/{init-config.js → init/config.js} +6 -6
  50. package/mcp/dist/{init.js → init/init.js} +28 -29
  51. package/mcp/dist/{init-preferences.js → init/preferences.js} +3 -3
  52. package/mcp/dist/{init-setup.js → init/setup.js} +17 -19
  53. package/mcp/dist/{init-shared.js → init/shared.js} +3 -3
  54. package/mcp/dist/init-bootstrap.js +68 -0
  55. package/mcp/dist/init-detect.js +38 -0
  56. package/mcp/dist/init-dryrun.js +55 -0
  57. package/mcp/dist/init-env.js +114 -0
  58. package/mcp/dist/init-fresh.js +239 -0
  59. package/mcp/dist/init-hooks.js +26 -0
  60. package/mcp/dist/init-mcp.js +65 -0
  61. package/mcp/dist/init-migrate.js +51 -0
  62. package/mcp/dist/init-modes.js +135 -0
  63. package/mcp/dist/init-npm.js +37 -0
  64. package/mcp/dist/init-project-local.js +99 -0
  65. package/mcp/dist/init-semantic.js +48 -0
  66. package/mcp/dist/init-types.js +1 -0
  67. package/mcp/dist/init-uninstall.js +482 -0
  68. package/mcp/dist/init-update.js +96 -0
  69. package/mcp/dist/init-walkthrough-merge.js +90 -0
  70. package/mcp/dist/init-walkthrough.js +529 -0
  71. package/mcp/dist/{link-checksums.js → link/checksums.js} +5 -5
  72. package/mcp/dist/{link-context.js → link/context.js} +4 -4
  73. package/mcp/dist/{link-doctor.js → link/doctor.js} +20 -22
  74. package/mcp/dist/{link.js → link/link.js} +26 -31
  75. package/mcp/dist/{link-skills.js → link/skills.js} +10 -10
  76. package/mcp/dist/logger.js +11 -3
  77. package/mcp/dist/phren-art.js +0 -6
  78. package/mcp/dist/phren-paths.js +30 -12
  79. package/mcp/dist/proactivity.js +2 -2
  80. package/mcp/dist/profile-store.js +5 -6
  81. package/mcp/dist/project-config.js +2 -2
  82. package/mcp/dist/project-topics.js +1 -1
  83. package/mcp/dist/query-correlation.js +1 -1
  84. package/mcp/dist/{session-checkpoints.js → session/checkpoints.js} +3 -3
  85. package/mcp/dist/{session-utils.js → session/utils.js} +1 -1
  86. package/mcp/dist/{shared-content.js → shared/content.js} +7 -7
  87. package/mcp/dist/{shared-data-utils.js → shared/data-utils.js} +3 -3
  88. package/mcp/dist/{shared-embedding-cache.js → shared/embedding-cache.js} +3 -3
  89. package/mcp/dist/{shared-fragment-graph.js → shared/fragment-graph.js} +15 -24
  90. package/mcp/dist/shared/governance.js +4 -0
  91. package/mcp/dist/{shared-index.js → shared/index.js} +92 -123
  92. package/mcp/dist/{shared-ollama.js → shared/ollama.js} +2 -2
  93. package/mcp/dist/{shared-retrieval.js → shared/retrieval.js} +16 -21
  94. package/mcp/dist/{shared-search-fallback.js → shared/search-fallback.js} +17 -20
  95. package/mcp/dist/{shared-sqljs.js → shared/sqljs.js} +3 -3
  96. package/mcp/dist/{shared-vector-index.js → shared/vector-index.js} +3 -3
  97. package/mcp/dist/shared.js +4 -59
  98. package/mcp/dist/{shell-entry.js → shell/entry.js} +6 -6
  99. package/mcp/dist/{shell-input.js → shell/input.js} +13 -13
  100. package/mcp/dist/{shell-palette.js → shell/palette.js} +3 -3
  101. package/mcp/dist/{shell-render.js → shell/render.js} +1 -1
  102. package/mcp/dist/{shell.js → shell/shell.js} +11 -11
  103. package/mcp/dist/{shell-state-store.js → shell/state-store.js} +5 -5
  104. package/mcp/dist/{shell-view-list.js → shell/view-list.js} +1 -1
  105. package/mcp/dist/{shell-view.js → shell/view.js} +13 -13
  106. package/mcp/dist/{skill-files.js → skill/files.js} +9 -9
  107. package/mcp/dist/{skill-registry.js → skill/registry.js} +4 -4
  108. package/mcp/dist/{skill-state.js → skill/state.js} +1 -1
  109. package/mcp/dist/startup-embedding.js +2 -2
  110. package/mcp/dist/status.js +15 -14
  111. package/mcp/dist/{tasks-github.js → task/github.js} +2 -2
  112. package/mcp/dist/{task-hygiene.js → task/hygiene.js} +4 -4
  113. package/mcp/dist/{task-lifecycle.js → task/lifecycle.js} +7 -7
  114. package/mcp/dist/telemetry.js +3 -4
  115. package/mcp/dist/tool-registry.js +29 -17
  116. package/mcp/dist/tools/config.js +515 -0
  117. package/mcp/dist/{mcp-data.js → tools/data.js} +8 -10
  118. package/mcp/dist/{mcp-extract-facts.js → tools/extract-facts.js} +6 -6
  119. package/mcp/dist/{mcp-extract.js → tools/extract.js} +6 -6
  120. package/mcp/dist/{mcp-finding.js → tools/finding.js} +97 -124
  121. package/mcp/dist/{mcp-graph.js → tools/graph.js} +11 -14
  122. package/mcp/dist/{mcp-hooks.js → tools/hooks.js} +6 -6
  123. package/mcp/dist/{mcp-memory.js → tools/memory.js} +5 -5
  124. package/mcp/dist/{mcp-ops.js → tools/ops.js} +169 -71
  125. package/mcp/dist/{mcp-search.js → tools/search.js} +19 -23
  126. package/mcp/dist/{mcp-session.js → tools/session.js} +48 -23
  127. package/mcp/dist/{mcp-skills.js → tools/skills.js} +33 -35
  128. package/mcp/dist/{mcp-tasks.js → tools/tasks.js} +155 -282
  129. package/mcp/dist/{memory-ui-data.js → ui/data.js} +31 -17
  130. package/mcp/dist/{memory-ui.js → ui/memory-ui.js} +3 -3
  131. package/mcp/dist/{memory-ui-page.js → ui/page.js} +4 -6
  132. package/mcp/dist/{memory-ui-server.js → ui/server.js} +30 -22
  133. package/mcp/dist/update.js +2 -2
  134. package/mcp/dist/utils.js +51 -11
  135. package/package.json +2 -2
  136. package/scripts/preuninstall.mjs +31 -0
  137. package/starter/global/CLAUDE.md +3 -2
  138. package/mcp/dist/mcp-config.js +0 -551
  139. package/mcp/dist/shared-governance.js +0 -4
  140. /package/mcp/dist/{content-metadata.js → content/metadata.js} +0 -0
  141. /package/mcp/dist/{shared-stemmer.js → shared/stemmer.js} +0 -0
  142. /package/mcp/dist/{shell-types.js → shell/types.js} +0 -0
  143. /package/mcp/dist/{mcp-types.js → tools/types.js} +0 -0
  144. /package/mcp/dist/{memory-ui-assets.js → ui/assets.js} +0 -0
  145. /package/mcp/dist/{memory-ui-graph.js → ui/graph.js} +0 -0
  146. /package/mcp/dist/{memory-ui-scripts.js → ui/scripts.js} +0 -0
  147. /package/mcp/dist/{memory-ui-styles.js → ui/styles.js} +0 -0
@@ -3,27 +3,28 @@ import * as path from "path";
3
3
  import * as readline from "readline";
4
4
  import * as yaml from "js-yaml";
5
5
  import { execFileSync } from "child_process";
6
- import { ROOT } from "./package-metadata.js";
7
- import { configureMcpTargets, ensureGovernanceFiles, getHooksEnabledPreference, getMcpEnabledPreference, isVersionNewer, patchJsonFile, setMcpEnabledPreference, } from "./init.js";
8
- import { configureAllHooks, detectInstalledTools } from "./hooks.js";
9
- import { getMachineName, persistMachineName } from "./machine-identity.js";
10
- import { debugLog, EXEC_TIMEOUT_MS, EXEC_TIMEOUT_QUICK_MS, isRecord, homePath, hookConfigPath, installPreferencesFile, atomicWriteText, } from "./shared.js";
11
- import { errorMessage } from "./utils.js";
12
- import { log } from "./init-shared.js";
13
- import { listMachines as listMachinesShared, listProfiles as listProfilesShared, setMachineProfile, } from "./profile-store.js";
14
- import { writeSkillMd, isManagedSymlink } from "./link-skills.js";
15
- import { syncScopeSkillsToDir } from "./skill-files.js";
16
- import { renderSkillInstructionsSection } from "./skill-registry.js";
17
- import { findProjectDir } from "./project-locator.js";
18
- import { getProjectOwnershipMode, readProjectConfig, } from "./project-config.js";
19
- import { writeContextDefault, writeContextDebugging, writeContextPlanning, writeContextClean, readBackNativeMemory, rebuildMemory, } from "./link-context.js";
6
+ import { ROOT } from "../package-metadata.js";
7
+ import { configureMcpTargets, ensureGovernanceFiles, getHooksEnabledPreference, getMcpEnabledPreference, isVersionNewer, patchJsonFile, setMcpEnabledPreference, } from "../init/init.js";
8
+ import { configureAllHooks, detectInstalledTools } from "../hooks.js";
9
+ import { getMachineName, persistMachineName } from "../machine-identity.js";
10
+ import { debugLog, EXEC_TIMEOUT_MS, EXEC_TIMEOUT_QUICK_MS, isRecord, homePath, hookConfigPath, installPreferencesFile, atomicWriteText, } from "../shared.js";
11
+ import { errorMessage } from "../utils.js";
12
+ import { log } from "../init/shared.js";
13
+ import { listMachines as listMachinesShared, listProfiles as listProfilesShared, setMachineProfile, } from "../profile-store.js";
14
+ import { writeSkillMd, isManagedSymlink } from "./skills.js";
15
+ import { syncScopeSkillsToDir } from "../skill/files.js";
16
+ import { renderSkillInstructionsSection } from "../skill/registry.js";
17
+ import { findProjectDir } from "../project-locator.js";
18
+ import { getProjectOwnershipMode, readProjectConfig, } from "../project-config.js";
19
+ import { writeContextDefault, writeContextDebugging, writeContextPlanning, writeContextClean, readBackNativeMemory, rebuildMemory, } from "./context.js";
20
+ import { logger } from "../logger.js";
20
21
  // Re-export sub-modules so existing imports from "./link.js" continue to work
21
- export { runDoctor } from "./link-doctor.js";
22
- export { updateFileChecksums, verifyFileChecksums } from "./link-checksums.js";
23
- export { findProjectDir } from "./project-locator.js";
24
- export { parseSkillFrontmatter, validateSkillFrontmatter, validateSkillsDir, readSkillManifestHooks, } from "./link-skills.js";
22
+ export { runDoctor } from "./doctor.js";
23
+ export { updateFileChecksums, verifyFileChecksums } from "./checksums.js";
24
+ export { findProjectDir } from "../project-locator.js";
25
+ export { parseSkillFrontmatter, validateSkillFrontmatter, validateSkillsDir, readSkillManifestHooks, } from "./skills.js";
25
26
  // ── Helpers (exported for link-doctor) ──────────────────────────────────────
26
- export { getMachineName } from "./machine-identity.js";
27
+ export { getMachineName } from "../machine-identity.js";
27
28
  export function lookupProfile(phrenPath, machine) {
28
29
  const listed = listMachinesShared(phrenPath);
29
30
  if (!listed.ok)
@@ -115,8 +116,7 @@ function setupSparseCheckout(phrenPath, projects) {
115
116
  execFileSync("git", ["rev-parse", "--git-dir"], { cwd: phrenPath, stdio: "ignore", timeout: EXEC_TIMEOUT_QUICK_MS });
116
117
  }
117
118
  catch (err) {
118
- if ((process.env.PHREN_DEBUG))
119
- process.stderr.write(`[phren] setupSparseCheckout notAGitRepo: ${errorMessage(err)}\n`);
119
+ logger.debug("link", `setupSparseCheckout notAGitRepo: ${errorMessage(err)}`);
120
120
  return;
121
121
  }
122
122
  const alwaysInclude = ["profiles", "machines.yaml", "global", "scripts", "link.sh", "README.md", ".gitignore"];
@@ -268,8 +268,7 @@ function linkGlobal(phrenPath, tools) {
268
268
  symlinkFile(globalClaude, path.join(copilotInstrDir, "copilot-instructions.md"), phrenPath);
269
269
  }
270
270
  catch (err) {
271
- if ((process.env.PHREN_DEBUG))
272
- process.stderr.write(`[phren] linkGlobal copilotInstructions: ${errorMessage(err)}\n`);
271
+ logger.debug("link", `linkGlobal copilotInstructions: ${errorMessage(err)}`);
273
272
  }
274
273
  }
275
274
  }
@@ -314,8 +313,7 @@ function linkProject(phrenPath, project, tools) {
314
313
  symlinkFile(src, path.join(copilotDir, "copilot-instructions.md"), phrenPath);
315
314
  }
316
315
  catch (err) {
317
- if ((process.env.PHREN_DEBUG))
318
- process.stderr.write(`[phren] linkProject copilotInstructions: ${errorMessage(err)}\n`);
316
+ logger.debug("link", `linkProject copilotInstructions: ${errorMessage(err)}`);
319
317
  }
320
318
  }
321
319
  }
@@ -338,8 +336,7 @@ function linkProject(phrenPath, project, tools) {
338
336
  addTokenAnnotation(claudeFile);
339
337
  }
340
338
  catch (err) {
341
- if ((process.env.PHREN_DEBUG))
342
- process.stderr.write(`[phren] linkProject tokenAnnotation: ${errorMessage(err)}\n`);
339
+ logger.debug("link", `linkProject tokenAnnotation: ${errorMessage(err)}`);
343
340
  }
344
341
  }
345
342
  // Project-level skills
@@ -355,8 +352,7 @@ function linkProject(phrenPath, project, tools) {
355
352
  excludeEntries.push("AGENTS.md");
356
353
  }
357
354
  catch (err) {
358
- if ((process.env.PHREN_DEBUG))
359
- process.stderr.write(`[phren] linkProject agentsMd: ${errorMessage(err)}\n`);
355
+ logger.debug("link", `linkProject agentsMd: ${errorMessage(err)}`);
360
356
  }
361
357
  }
362
358
  // Auto-exclude phren-managed files from git status
@@ -507,8 +503,7 @@ export async function runLink(phrenPath, opts = {}) {
507
503
  log(` phren.SKILL.md written (agentskills-compatible tools)`);
508
504
  }
509
505
  catch (err) {
510
- if ((process.env.PHREN_DEBUG))
511
- process.stderr.write(`[phren] link writeSkillMd: ${errorMessage(err)}\n`);
506
+ logger.debug("link", `link writeSkillMd: ${errorMessage(err)}`);
512
507
  }
513
508
  log("");
514
509
  // Step 7: Context file
@@ -1,12 +1,13 @@
1
1
  import * as fs from "fs";
2
2
  import * as path from "path";
3
3
  import * as yaml from "js-yaml";
4
- import { debugLog } from "./shared.js";
5
- import { errorMessage } from "./utils.js";
6
- import { buildSharedLifecycleCommands } from "./hooks.js";
7
- import { VERSION } from "./package-metadata.js";
8
- import { getToolCount, renderToolCatalogMarkdown } from "./tool-registry.js";
9
- import { isSkillEnabled } from "./skill-state.js";
4
+ import { debugLog } from "../shared.js";
5
+ import { errorMessage } from "../utils.js";
6
+ import { buildSharedLifecycleCommands } from "../hooks.js";
7
+ import { VERSION } from "../package-metadata.js";
8
+ import { getToolCount, renderToolCatalogMarkdown } from "../tool-registry.js";
9
+ import { isSkillEnabled } from "../skill/state.js";
10
+ import { logger } from "../logger.js";
10
11
  const REQUIRED_SKILL_FIELDS = ["name", "description"];
11
12
  export function parseSkillFrontmatter(rawContent) {
12
13
  // Normalize UTF-8 BOM and Windows-style CRLF line endings before matching frontmatter
@@ -190,8 +191,7 @@ function cleanupManagedSkillLinks(destDir, expectedNames, managedRoot) {
190
191
  fs.unlinkSync(destPath);
191
192
  }
192
193
  catch (err) {
193
- if ((process.env.PHREN_DEBUG))
194
- process.stderr.write(`[phren] cleanupManagedSkillLinks: ${errorMessage(err)}\n`);
194
+ logger.debug("link-skills", `cleanupManagedSkillLinks: ${errorMessage(err)}`);
195
195
  }
196
196
  }
197
197
  }
@@ -217,7 +217,7 @@ export function linkSkillsDir(srcDir, destDir, managedRoot, symlinkFile, opts) {
217
217
  message: `Skipping skill '${skillName}' — user skill already exists at ${destPath}. To use phren's version, rename or remove your skill first.`,
218
218
  };
219
219
  collisions.push(collision);
220
- process.stderr.write(`[phren] ${collision.message}\n`);
220
+ logger.warn("link-skills", collision.message);
221
221
  continue;
222
222
  }
223
223
  expectedNames.add(entry);
@@ -234,7 +234,7 @@ export function linkSkillsDir(srcDir, destDir, managedRoot, symlinkFile, opts) {
234
234
  message: `Skipping skill '${skillName}' — user skill already exists at ${destPath}. To use phren's version, rename or remove your skill first.`,
235
235
  };
236
236
  collisions.push(collision);
237
- process.stderr.write(`[phren] ${collision.message}\n`);
237
+ logger.warn("link-skills", collision.message);
238
238
  continue;
239
239
  }
240
240
  expectedNames.add(entry);
@@ -1,11 +1,13 @@
1
1
  import * as fs from "fs";
2
2
  import { runtimeFile, findPhrenPath } from "./shared.js";
3
+ let _cachedPhrenPath;
3
4
  export function log(level, tool, message, extra) {
4
5
  try {
5
- const phrenPath = findPhrenPath();
6
- if (!phrenPath)
6
+ if (_cachedPhrenPath === undefined)
7
+ _cachedPhrenPath = findPhrenPath();
8
+ if (!_cachedPhrenPath)
7
9
  return;
8
- const logPath = runtimeFile(phrenPath, "debug.log");
10
+ const logPath = runtimeFile(_cachedPhrenPath, "debug.log");
9
11
  const line = JSON.stringify({ ts: new Date().toISOString(), level, tool, message, ...extra });
10
12
  fs.appendFileSync(logPath, line + "\n");
11
13
  }
@@ -13,3 +15,9 @@ export function log(level, tool, message, extra) {
13
15
  // Logging must never throw
14
16
  }
15
17
  }
18
+ export const logger = {
19
+ debug: (tool, message, extra) => log("debug", tool, message, extra),
20
+ info: (tool, message, extra) => log("info", tool, message, extra),
21
+ warn: (tool, message, extra) => log("warn", tool, message, extra),
22
+ error: (tool, message, extra) => log("error", tool, message, extra),
23
+ };
@@ -13,11 +13,7 @@ const RESET = `${ESC}0m`;
13
13
  const PURPLE = `${ESC}35m`; // magenta — body
14
14
  const BRIGHT_PURPLE = `${ESC}95m`; // bright magenta — highlights
15
15
  const CYAN = `${ESC}96m`; // bright cyan — sparkle
16
- const DIM = `${ESC}2m`;
17
16
  const DARK_PURPLE = `${ESC}38;5;57m`; // deep purple — shadow/outline
18
- const LIGHT_PURPLE = `${ESC}38;5;141m`; // lavender — brain highlights
19
- const MID_PURPLE = `${ESC}38;5;98m`; // mid tone
20
- const NAVY = `${ESC}38;5;18m`; // darkest outline
21
17
  // ── Art constants (24px wide, truecolor half-blocks) ─────────────────────────
22
18
  /**
23
19
  * Phren truecolor art (24px wide, generated from phren-transparent.png).
@@ -38,8 +34,6 @@ export const PHREN_ART = [
38
34
  " ",
39
35
  " ",
40
36
  ];
41
- /** The art width in visible columns */
42
- const ART_WIDTH = 24;
43
37
  // ── Sparkle row: the cyan pixels at row 2 ────────────────────────────────────
44
38
  // Sparkle uses ▄ half-blocks with cyan truecolor. For animation we cycle through
45
39
  // decorative unicode characters at different brightness levels.
@@ -7,6 +7,10 @@ import { bootstrapPhrenDotEnv } from "./phren-dotenv.js";
7
7
  import { PhrenError, isRecord, RESERVED_PROJECT_DIR_NAMES } from "./phren-core.js";
8
8
  import { errorMessage, isValidProjectName, safeProjectPath } from "./utils.js";
9
9
  bootstrapPhrenDotEnv();
10
+ function stderrLog(msg) { try {
11
+ process.stderr.write("[phren] " + msg + "\n");
12
+ }
13
+ catch { } }
10
14
  export const ROOT_MANIFEST_FILENAME = "phren.root.yaml";
11
15
  export function homeDir() {
12
16
  return process.env.HOME || process.env.USERPROFILE || os.homedir();
@@ -75,7 +79,7 @@ export function readRootManifest(phrenPath) {
75
79
  }
76
80
  catch (err) {
77
81
  if ((process.env.PHREN_DEBUG))
78
- process.stderr.write(`[phren] readRootManifest: ${errorMessage(err)}\n`);
82
+ stderrLog(`readRootManifest: ${errorMessage(err)}`);
79
83
  return null;
80
84
  }
81
85
  }
@@ -263,6 +267,20 @@ export function debugLog(msg) {
263
267
  // debug log is best-effort; logging errors about logging would recurse
264
268
  }
265
269
  }
270
+ /** Always-on structured error log (no PHREN_DEBUG gate). */
271
+ export function errorLog(tool, msg) {
272
+ try {
273
+ const phrenPath = findPhrenPath();
274
+ if (!phrenPath)
275
+ return;
276
+ const logFile = runtimeFile(phrenPath, "debug.log");
277
+ const line = JSON.stringify({ ts: new Date().toISOString(), level: "error", tool, message: msg });
278
+ fs.appendFileSync(logFile, line + "\n");
279
+ }
280
+ catch {
281
+ // Logging must never throw
282
+ }
283
+ }
266
284
  export function appendIndexEvent(phrenPath, event) {
267
285
  try {
268
286
  const file = runtimeFile(phrenPath, "index-events.jsonl");
@@ -270,7 +288,7 @@ export function appendIndexEvent(phrenPath, event) {
270
288
  }
271
289
  catch (err) {
272
290
  if ((process.env.PHREN_DEBUG))
273
- process.stderr.write(`[phren] appendIndexEvent: ${errorMessage(err)}\n`);
291
+ stderrLog(`appendIndexEvent: ${errorMessage(err)}`);
274
292
  }
275
293
  }
276
294
  /** Resolve the canonical findings file for a project directory. */
@@ -301,7 +319,7 @@ export function findProjectNameCaseInsensitive(phrenPath, name) {
301
319
  }
302
320
  catch (err) {
303
321
  if ((process.env.PHREN_DEBUG))
304
- process.stderr.write(`[phren] findProjectNameCaseInsensitive: ${errorMessage(err)}\n`);
322
+ stderrLog(`findProjectNameCaseInsensitive: ${errorMessage(err)}`);
305
323
  }
306
324
  return null;
307
325
  }
@@ -318,7 +336,7 @@ export function findArchivedProjectNameCaseInsensitive(phrenPath, name) {
318
336
  }
319
337
  catch (err) {
320
338
  if ((process.env.PHREN_DEBUG))
321
- process.stderr.write(`[phren] findArchivedProjectNameCaseInsensitive: ${errorMessage(err)}\n`);
339
+ stderrLog(`findArchivedProjectNameCaseInsensitive: ${errorMessage(err)}`);
322
340
  }
323
341
  return null;
324
342
  }
@@ -342,26 +360,26 @@ export function getProjectDirs(phrenPath, profile) {
342
360
  }
343
361
  if (profile) {
344
362
  if (!isValidProjectName(profile)) {
345
- console.error(`${PhrenError.VALIDATION_ERROR}: Invalid PHREN_PROFILE value: ${profile}`);
363
+ errorLog("getProjectDirs", `${PhrenError.VALIDATION_ERROR}: Invalid PHREN_PROFILE value: ${profile}`);
346
364
  return [];
347
365
  }
348
366
  const profilePath = path.join(phrenPath, "profiles", `${profile}.yaml`);
349
367
  if (!fs.existsSync(profilePath)) {
350
- console.error(`${PhrenError.FILE_NOT_FOUND}: Profile file not found: ${profilePath}`);
368
+ errorLog("getProjectDirs", `${PhrenError.FILE_NOT_FOUND}: Profile file not found: ${profilePath}`);
351
369
  return [];
352
370
  }
353
371
  try {
354
372
  const data = yaml.load(fs.readFileSync(profilePath, "utf-8"), { schema: yaml.CORE_SCHEMA });
355
373
  const projects = isRecord(data) ? data.projects : undefined;
356
374
  if (!Array.isArray(projects)) {
357
- console.error(`${PhrenError.MALFORMED_YAML}: Profile YAML missing valid "projects" array: ${profilePath}`);
375
+ errorLog("getProjectDirs", `${PhrenError.MALFORMED_YAML}: Profile YAML missing valid "projects" array: ${profilePath}`);
358
376
  return [];
359
377
  }
360
378
  const listed = projects
361
379
  .map((p) => {
362
380
  const name = String(p);
363
381
  if (!isValidProjectName(name)) {
364
- console.error(`${PhrenError.VALIDATION_ERROR}: Skipping invalid project name in profile: ${name}`);
382
+ errorLog("getProjectDirs", `${PhrenError.VALIDATION_ERROR}: Skipping invalid project name in profile: ${name}`);
365
383
  return null;
366
384
  }
367
385
  return safeProjectPath(phrenPath, name);
@@ -374,8 +392,8 @@ export function getProjectDirs(phrenPath, profile) {
374
392
  }
375
393
  catch (err) {
376
394
  if ((process.env.PHREN_DEBUG))
377
- process.stderr.write(`[phren] getProjectDirs yamlParse: ${errorMessage(err)}\n`);
378
- console.error(`${PhrenError.MALFORMED_YAML}: Malformed profile YAML: ${profilePath}`);
395
+ stderrLog(`getProjectDirs yamlParse: ${errorMessage(err)}`);
396
+ errorLog("getProjectDirs", `${PhrenError.MALFORMED_YAML}: Malformed profile YAML: ${profilePath}`);
379
397
  return [];
380
398
  }
381
399
  }
@@ -386,7 +404,7 @@ export function getProjectDirs(phrenPath, profile) {
386
404
  }
387
405
  catch (err) {
388
406
  if ((process.env.PHREN_DEBUG))
389
- process.stderr.write(`[phren] getProjectDirs: ${errorMessage(err)}\n`);
407
+ stderrLog(`getProjectDirs: ${errorMessage(err)}`);
390
408
  return [];
391
409
  }
392
410
  }
@@ -413,7 +431,7 @@ export function collectNativeMemoryFiles() {
413
431
  }
414
432
  catch (err) {
415
433
  if ((process.env.PHREN_DEBUG))
416
- process.stderr.write(`[phren] collectNativeMemoryFiles: ${errorMessage(err)}\n`);
434
+ stderrLog(`collectNativeMemoryFiles: ${errorMessage(err)}`);
417
435
  }
418
436
  return results;
419
437
  }
@@ -1,9 +1,9 @@
1
1
  import * as fs from "fs";
2
2
  import { bootstrapPhrenDotEnv } from "./phren-dotenv.js";
3
3
  import { debugLog, findPhrenPath } from "./phren-paths.js";
4
- import { governanceInstallPreferencesFile, readInstallPreferences } from "./init-preferences.js";
4
+ import { governanceInstallPreferencesFile, readInstallPreferences } from "./init/preferences.js";
5
5
  import { errorMessage } from "./utils.js";
6
- import { getWorkflowPolicy } from "./governance-policy.js";
6
+ import { getWorkflowPolicy } from "./governance/policy.js";
7
7
  export const PROACTIVITY_LEVELS = ["high", "medium", "low"];
8
8
  const DEFAULT_PROACTIVITY_LEVEL = "high";
9
9
  const EXPLICIT_FINDING_SIGNAL_PATTERN = /\b(add finding|worth remembering)\b/i;
@@ -5,8 +5,9 @@ import * as yaml from "js-yaml";
5
5
  import { phrenErr, PhrenError, phrenOk, forwardErr, getProjectDirs, readRootManifest, } from "./shared.js";
6
6
  import { defaultMachineName, getMachineName } from "./machine-identity.js";
7
7
  import { errorMessage, isValidProjectName } from "./utils.js";
8
- import { TASK_FILE_ALIASES } from "./data-tasks.js";
9
- import { withSafeLock } from "./shared-data-utils.js";
8
+ import { TASK_FILE_ALIASES } from "./data/tasks.js";
9
+ import { withSafeLock } from "./shared/data-utils.js";
10
+ import { logger } from "./logger.js";
10
11
  export function resolveActiveProfile(phrenPath, requestedProfile) {
11
12
  const manifest = readRootManifest(phrenPath);
12
13
  if (manifest?.installMode === "project-local") {
@@ -61,8 +62,7 @@ export function listMachines(phrenPath) {
61
62
  return phrenOk(cleaned);
62
63
  }
63
64
  catch (err) {
64
- if ((process.env.PHREN_DEBUG))
65
- process.stderr.write(`[phren] listMachines yaml parse: ${errorMessage(err)}\n`);
65
+ logger.debug("profile-store", `listMachines yaml parse: ${errorMessage(err)}`);
66
66
  return phrenErr(`Could not parse machines.yaml. Check the file for syntax errors or run 'phren doctor --fix'.`, PhrenError.MALFORMED_YAML);
67
67
  }
68
68
  }
@@ -226,8 +226,7 @@ export function listProfiles(phrenPath) {
226
226
  profiles.push({ name, file: full, projects, ...(defaults ? { defaults } : {}) });
227
227
  }
228
228
  catch (err) {
229
- if ((process.env.PHREN_DEBUG))
230
- process.stderr.write(`[phren] listProfiles yamlParse: ${errorMessage(err)}\n`);
229
+ logger.debug("profile-store", `listProfiles yamlParse: ${errorMessage(err)}`);
231
230
  return phrenErr(`profiles/${file}`, PhrenError.MALFORMED_YAML);
232
231
  }
233
232
  }
@@ -2,10 +2,10 @@ import * as crypto from "crypto";
2
2
  import * as fs from "fs";
3
3
  import * as path from "path";
4
4
  import * as yaml from "js-yaml";
5
- import { readInstallPreferences } from "./init-preferences.js";
5
+ import { readInstallPreferences } from "./init/preferences.js";
6
6
  import { debugLog } from "./shared.js";
7
7
  import { errorMessage, safeProjectPath } from "./utils.js";
8
- import { withFileLock } from "./shared-governance.js";
8
+ import { withFileLock } from "./shared/governance.js";
9
9
  export const PROJECT_OWNERSHIP_MODES = ["phren-managed", "detached", "repo-managed"];
10
10
  export const PROJECT_HOOK_EVENTS = ["UserPromptSubmit", "Stop", "SessionStart", "PostToolUse"];
11
11
  export function parseProjectOwnershipMode(raw) {
@@ -2,7 +2,7 @@ import * as crypto from "crypto";
2
2
  import * as fs from "fs";
3
3
  import * as path from "path";
4
4
  import { debugLog } from "./shared.js";
5
- import { withFileLock } from "./shared-governance.js";
5
+ import { withFileLock } from "./shared/governance.js";
6
6
  import { STOP_WORDS, errorMessage, extractKeywords, isValidProjectName, safeProjectPath } from "./utils.js";
7
7
  const TOPIC_CONFIG_FILENAME = "topic-config.json";
8
8
  const AUTO_TOPIC_MARKER_RE = /^<!--\s*phren:auto-topic(?:\s+slug=([a-z0-9_-]+))?\s*-->$/;
@@ -7,7 +7,7 @@
7
7
  import * as fs from "fs";
8
8
  import { runtimeFile, debugLog } from "./shared.js";
9
9
  import { isFeatureEnabled, errorMessage } from "./utils.js";
10
- import { withFileLock } from "./shared-governance.js";
10
+ import { withFileLock } from "./shared/governance.js";
11
11
  const CORRELATION_FILENAME = "query-correlations.jsonl";
12
12
  const RECENT_WINDOW = 500;
13
13
  const MIN_TOKEN_OVERLAP = 2;
@@ -1,8 +1,8 @@
1
1
  import * as fs from "fs";
2
2
  import * as path from "path";
3
- import { errorMessage } from "./utils.js";
4
- import { debugLog, sessionMarker } from "./shared.js";
5
- import { atomicWriteJson } from "./session-utils.js";
3
+ import { errorMessage } from "../utils.js";
4
+ import { debugLog, sessionMarker } from "../shared.js";
5
+ import { atomicWriteJson } from "./utils.js";
6
6
  function sanitizeFileSegment(value) {
7
7
  const trimmed = value.trim();
8
8
  const safe = trimmed.replace(/[^a-zA-Z0-9._-]+/g, "_");
@@ -1,6 +1,6 @@
1
1
  import * as fs from "fs";
2
2
  import * as path from "path";
3
- import { errorMessage } from "./utils.js";
3
+ import { errorMessage } from "../utils.js";
4
4
  /**
5
5
  * Write JSON to a file atomically using temp-file + rename.
6
6
  * Ensures the parent directory exists before writing.
@@ -1,8 +1,8 @@
1
1
  // Barrel re-export. Internal code imports from the specific modules directly.
2
- export { checkConsolidationNeeded, validateFindingsFormat, stripTaskDoneSection, validateTaskFormat, extractConflictVersions, mergeFindings, mergeTask, autoMergeConflicts, } from "./content-validate.js";
3
- export { filterTrustedFindings, filterTrustedFindingsDetailed, } from "./content-citation.js";
4
- export { scanForSecrets, resolveCoref, isDuplicateFinding, detectConflicts, extractDynamicEntities, checkSemanticDedup, checkSemanticConflicts, } from "./content-dedup.js";
5
- export { countActiveFindings, autoArchiveToReference, } from "./content-archive.js";
6
- export { upsertCanonical, addFindingToFile, addFindingsToFile, autoDetectFindingType, } from "./content-learning.js";
7
- export { FINDING_LIFECYCLE_STATUSES, FINDING_TYPE_DECAY, extractFindingType, parseFindingLifecycle, buildLifecycleComments, isInactiveFindingLine, } from "./finding-lifecycle.js";
8
- export { METADATA_REGEX, parseStatus, parseStatusField, parseSupersession, parseSupersedesRef, parseContradiction, parseAllContradictions, parseFindingId, parseCreatedDate, isCitationLine, isArchiveStart, isArchiveEnd, stripLifecycleMetadata, stripRelationMetadata, stripAllMetadata, stripComments, addMetadata, } from "./content-metadata.js";
2
+ export { checkConsolidationNeeded, validateFindingsFormat, stripTaskDoneSection, validateTaskFormat, extractConflictVersions, mergeFindings, mergeTask, autoMergeConflicts, } from "../content/validate.js";
3
+ export { filterTrustedFindings, filterTrustedFindingsDetailed, } from "../content/citation.js";
4
+ export { scanForSecrets, resolveCoref, isDuplicateFinding, detectConflicts, extractDynamicEntities, checkSemanticDedup, checkSemanticConflicts, } from "../content/dedup.js";
5
+ export { countActiveFindings, autoArchiveToReference, } from "../content/archive.js";
6
+ export { upsertCanonical, addFindingToFile, addFindingsToFile, autoDetectFindingType, } from "../content/learning.js";
7
+ export { FINDING_LIFECYCLE_STATUSES, FINDING_TYPE_DECAY, extractFindingType, parseFindingLifecycle, buildLifecycleComments, isInactiveFindingLine, } from "../finding/lifecycle.js";
8
+ export { METADATA_REGEX, parseStatus, parseStatusField, parseSupersession, parseSupersedesRef, parseContradiction, parseAllContradictions, parseFindingId, parseCreatedDate, isCitationLine, isArchiveStart, isArchiveEnd, stripLifecycleMetadata, stripRelationMetadata, stripAllMetadata, stripComments, addMetadata, } from "../content/metadata.js";
@@ -1,8 +1,8 @@
1
1
  import * as fs from "fs";
2
2
  import * as path from "path";
3
- import { phrenErr, PhrenError, phrenOk, } from "./shared.js";
4
- import { withFileLock as withFileLockRaw } from "./shared-governance.js";
5
- import { isValidProjectName, safeProjectPath, errorMessage } from "./utils.js";
3
+ import { phrenErr, PhrenError, phrenOk, } from "../shared.js";
4
+ import { withFileLock as withFileLockRaw } from "./governance.js";
5
+ import { isValidProjectName, safeProjectPath, errorMessage } from "../utils.js";
6
6
  export function withSafeLock(filePath, fn) {
7
7
  try {
8
8
  return withFileLockRaw(filePath, fn);
@@ -1,8 +1,8 @@
1
1
  import * as fs from "fs";
2
2
  import * as crypto from "crypto";
3
- import { runtimeFile, debugLog } from "./shared.js";
4
- import { withFileLock } from "./shared-governance.js";
5
- import { errorMessage } from "./utils.js";
3
+ import { runtimeFile, debugLog } from "../shared.js";
4
+ import { withFileLock } from "./governance.js";
5
+ import { errorMessage } from "../utils.js";
6
6
  function isEmbeddingEntry(value) {
7
7
  if (!value || typeof value !== "object" || Array.isArray(value))
8
8
  return false;
@@ -1,8 +1,9 @@
1
- import { decodeStringRow } from "./shared-index.js";
1
+ import { decodeStringRow } from "./index.js";
2
2
  import * as fs from "fs";
3
- import { runtimeFile } from "./shared.js";
4
- import { UNIVERSAL_TECH_TERMS_RE } from "./phren-core.js";
5
- import { errorMessage } from "./utils.js";
3
+ import { runtimeFile } from "../shared.js";
4
+ import { logger } from "../logger.js";
5
+ import { UNIVERSAL_TECH_TERMS_RE } from "../phren-core.js";
6
+ import { errorMessage } from "../utils.js";
6
7
  export function escapeRegex(s) { return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }
7
8
  /** Escape SQL LIKE wildcard characters so user input is treated literally. */
8
9
  export function escapeLike(s) { return s.replace(/[%_\\]/g, '\\$&'); }
@@ -116,8 +117,7 @@ function getOrCreateFragment(db, name, type) {
116
117
  db.run("INSERT OR IGNORE INTO entities (name, type, first_seen_at) VALUES (?, ?, ?)", [name, type, new Date().toISOString().slice(0, 10)]);
117
118
  }
118
119
  catch (err) {
119
- if (process.env.PHREN_DEBUG)
120
- process.stderr.write(`[phren] fragmentInsert: ${errorMessage(err)}\n`);
120
+ logger.debug("fragmentInsert", errorMessage(err));
121
121
  }
122
122
  const result = db.exec("SELECT id FROM entities WHERE name = ? AND type = ?", [name, type]);
123
123
  if (result?.length && result[0]?.values?.length) {
@@ -139,8 +139,7 @@ export function ensureGlobalEntitiesTable(db) {
139
139
  )`);
140
140
  }
141
141
  catch (err) {
142
- if (process.env.PHREN_DEBUG)
143
- process.stderr.write(`[phren] ensureGlobalEntitiesTable: ${errorMessage(err)}\n`);
142
+ logger.debug("ensureGlobalEntitiesTable", errorMessage(err));
144
143
  }
145
144
  }
146
145
  /**
@@ -186,8 +185,7 @@ export function beginUserFragmentBuildCache(phrenPath, projects) {
186
185
  _buildUserFragmentCache.set(cacheKey, loaded.fragments);
187
186
  }
188
187
  catch (err) {
189
- if (process.env.PHREN_DEBUG)
190
- process.stderr.write(`[phren] beginUserFragmentBuildCache: ${errorMessage(err)}\n`);
188
+ logger.debug("beginUserFragmentBuildCache", errorMessage(err));
191
189
  _buildUserFragmentCache.set(cacheKey, []);
192
190
  }
193
191
  }
@@ -225,8 +223,7 @@ function parseUserDefinedFragments(phrenPath, project) {
225
223
  }
226
224
  }
227
225
  catch (err) {
228
- if (process.env.PHREN_DEBUG)
229
- process.stderr.write(`[phren] parseUserDefinedFragments statCheck: ${errorMessage(err)}\n`);
226
+ logger.debug("parseUserDefinedFragments statCheck", errorMessage(err));
230
227
  }
231
228
  }
232
229
  const loaded = readUserDefinedFragmentsFromDisk(claudeMdPath);
@@ -236,8 +233,7 @@ function parseUserDefinedFragments(phrenPath, project) {
236
233
  return loaded.fragments;
237
234
  }
238
235
  catch (err) {
239
- if (process.env.PHREN_DEBUG)
240
- process.stderr.write(`[phren] parseUserDefinedFragments: ${errorMessage(err)}\n`);
236
+ logger.debug("parseUserDefinedFragments", errorMessage(err));
241
237
  return [];
242
238
  }
243
239
  }
@@ -353,8 +349,7 @@ export function extractAndLinkFragments(db, content, sourceDoc, phrenPath) {
353
349
  db.run("INSERT OR IGNORE INTO entity_links (source_id, target_id, rel_type, source_doc) VALUES (?, ?, ?, ?)", [docFragmentId, fragmentId, "mentions", sourceDoc]);
354
350
  }
355
351
  catch (err) {
356
- if (process.env.PHREN_DEBUG)
357
- process.stderr.write(`[phren] fragmentLinksInsert: ${errorMessage(err)}\n`);
352
+ logger.debug("fragmentLinksInsert", errorMessage(err));
358
353
  }
359
354
  // Write to global_entities for cross-project queries
360
355
  if (project) {
@@ -362,8 +357,7 @@ export function extractAndLinkFragments(db, content, sourceDoc, phrenPath) {
362
357
  db.run("INSERT OR IGNORE INTO global_entities (entity, project, doc_key) VALUES (?, ?, ?)", [name, project, sourceDoc]);
363
358
  }
364
359
  catch (err) {
365
- if (process.env.PHREN_DEBUG)
366
- process.stderr.write(`[phren] globalFragmentsInsert: ${errorMessage(err)}\n`);
360
+ logger.debug("globalFragmentsInsert", errorMessage(err));
367
361
  }
368
362
  }
369
363
  }
@@ -391,8 +385,7 @@ export function queryFragmentLinks(db, name) {
391
385
  }
392
386
  }
393
387
  catch (err) {
394
- if (process.env.PHREN_DEBUG)
395
- process.stderr.write(`[phren] queryFragmentLinks: ${errorMessage(err)}\n`);
388
+ logger.debug("queryFragmentLinks", errorMessage(err));
396
389
  }
397
390
  return { related };
398
391
  }
@@ -427,8 +420,7 @@ export function queryCrossProjectFragments(db, fragmentName, excludeProject) {
427
420
  }
428
421
  }
429
422
  catch (err) {
430
- if (process.env.PHREN_DEBUG)
431
- process.stderr.write(`[phren] queryCrossProjectFragments: ${errorMessage(err)}\n`);
423
+ logger.debug("queryCrossProjectFragments", errorMessage(err));
432
424
  }
433
425
  return results;
434
426
  }
@@ -448,8 +440,7 @@ export function getFragmentBoostDocs(db, query) {
448
440
  return boostDocs;
449
441
  }
450
442
  catch (err) {
451
- if (process.env.PHREN_DEBUG)
452
- process.stderr.write(`[phren] getFragmentBoostDocs: ${errorMessage(err)}\n`);
443
+ logger.debug("getFragmentBoostDocs", errorMessage(err));
453
444
  return new Set();
454
445
  }
455
446
  }
@@ -0,0 +1,4 @@
1
+ export * from '../governance/policy.js';
2
+ export * from '../governance/scores.js';
3
+ export * from '../governance/audit.js';
4
+ export { withFileLock, isFiniteNumber, hasValidSchemaVersion } from '../governance/locks.js';