@phren/cli 0.0.28 → 0.0.33

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 (153) 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} +25 -21
  7. package/mcp/dist/{cli.js → cli/cli.js} +13 -13
  8. package/mcp/dist/{cli-config.js → cli/config.js} +12 -12
  9. package/mcp/dist/{cli-extract.js → cli/extract.js} +8 -8
  10. package/mcp/dist/{cli-govern.js → cli/govern.js} +28 -17
  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} +58 -117
  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} +12 -11
  21. package/mcp/dist/cli-hooks-git.js +243 -0
  22. package/mcp/dist/cli-hooks-prompt.js +323 -0
  23. package/mcp/dist/cli-hooks-session-handlers.js +337 -0
  24. package/mcp/dist/cli-hooks-stop.js +519 -0
  25. package/mcp/dist/{content-archive.js → content/archive.js} +16 -29
  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} +41 -20
  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} +142 -15
  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} +13 -7
  41. package/mcp/dist/governance/audit.js +30 -0
  42. package/mcp/dist/{governance-locks.js → governance/locks.js} +14 -9
  43. package/mcp/dist/{governance-policy.js → governance/policy.js} +23 -12
  44. package/mcp/dist/{governance-rbac.js → governance/rbac.js} +4 -4
  45. package/mcp/dist/{governance-scores.js → governance/scores.js} +10 -11
  46. package/mcp/dist/hooks.js +53 -37
  47. package/mcp/dist/index-query.js +4 -1
  48. package/mcp/dist/index.js +54 -30
  49. package/mcp/dist/{init-config.js → init/config.js} +6 -6
  50. package/mcp/dist/{init.js → init/init.js} +80 -69
  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} +4 -4
  54. package/mcp/dist/init-bootstrap.js +21 -0
  55. package/mcp/dist/init-detect.js +38 -0
  56. package/mcp/dist/init-env.js +114 -0
  57. package/mcp/dist/init-fresh.js +234 -0
  58. package/mcp/dist/init-hooks.js +26 -0
  59. package/mcp/dist/init-mcp.js +65 -0
  60. package/mcp/dist/init-modes.js +135 -0
  61. package/mcp/dist/init-npm.js +37 -0
  62. package/mcp/dist/init-project-local.js +99 -0
  63. package/mcp/dist/init-semantic.js +48 -0
  64. package/mcp/dist/init-types.js +1 -0
  65. package/mcp/dist/init-uninstall.js +504 -0
  66. package/mcp/dist/init-update.js +96 -0
  67. package/mcp/dist/init-walkthrough.js +524 -0
  68. package/mcp/dist/{link-checksums.js → link/checksums.js} +5 -5
  69. package/mcp/dist/{link-context.js → link/context.js} +4 -4
  70. package/mcp/dist/{link-doctor.js → link/doctor.js} +20 -22
  71. package/mcp/dist/{link.js → link/link.js} +26 -31
  72. package/mcp/dist/{link-skills.js → link/skills.js} +10 -10
  73. package/mcp/dist/logger.js +11 -3
  74. package/mcp/dist/package-metadata.js +1 -1
  75. package/mcp/dist/phren-art.js +4 -126
  76. package/mcp/dist/phren-paths.js +30 -12
  77. package/mcp/dist/proactivity.js +3 -3
  78. package/mcp/dist/profile-store.js +5 -6
  79. package/mcp/dist/project-config.js +2 -2
  80. package/mcp/dist/project-topics.js +17 -47
  81. package/mcp/dist/provider-adapters.js +1 -1
  82. package/mcp/dist/query-correlation.js +1 -1
  83. package/mcp/dist/runtime-profile.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} +28 -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} +19 -42
  90. package/mcp/dist/shared/governance.js +4 -0
  91. package/mcp/dist/{shared-index.js → shared/index.js} +105 -132
  92. package/mcp/dist/{shared-ollama.js → shared/ollama.js} +25 -7
  93. package/mcp/dist/shared/process.js +24 -0
  94. package/mcp/dist/{shared-retrieval.js → shared/retrieval.js} +22 -24
  95. package/mcp/dist/{shared-search-fallback.js → shared/search-fallback.js} +18 -20
  96. package/mcp/dist/{shared-sqljs.js → shared/sqljs.js} +3 -3
  97. package/mcp/dist/{shared-vector-index.js → shared/vector-index.js} +3 -3
  98. package/mcp/dist/shared.js +6 -60
  99. package/mcp/dist/{shell-entry.js → shell/entry.js} +6 -6
  100. package/mcp/dist/{shell-input.js → shell/input.js} +13 -13
  101. package/mcp/dist/{shell-palette.js → shell/palette.js} +3 -3
  102. package/mcp/dist/{shell-render.js → shell/render.js} +2 -2
  103. package/mcp/dist/{shell.js → shell/shell.js} +11 -11
  104. package/mcp/dist/{shell-state-store.js → shell/state-store.js} +5 -5
  105. package/mcp/dist/{shell-view-list.js → shell/view-list.js} +1 -1
  106. package/mcp/dist/{shell-view.js → shell/view.js} +13 -13
  107. package/mcp/dist/{skill-files.js → skill/files.js} +9 -9
  108. package/mcp/dist/{skill-registry.js → skill/registry.js} +5 -5
  109. package/mcp/dist/{skill-state.js → skill/state.js} +1 -4
  110. package/mcp/dist/startup-embedding.js +2 -2
  111. package/mcp/dist/status.js +15 -14
  112. package/mcp/dist/{tasks-github.js → task/github.js} +3 -2
  113. package/mcp/dist/{task-hygiene.js → task/hygiene.js} +4 -4
  114. package/mcp/dist/{task-lifecycle.js → task/lifecycle.js} +8 -13
  115. package/mcp/dist/telemetry.js +3 -4
  116. package/mcp/dist/tool-registry.js +29 -17
  117. package/mcp/dist/tools/config.js +530 -0
  118. package/mcp/dist/{mcp-data.js → tools/data.js} +8 -10
  119. package/mcp/dist/{mcp-extract-facts.js → tools/extract-facts.js} +6 -6
  120. package/mcp/dist/{mcp-extract.js → tools/extract.js} +6 -6
  121. package/mcp/dist/tools/finding.js +584 -0
  122. package/mcp/dist/{mcp-graph.js → tools/graph.js} +11 -14
  123. package/mcp/dist/{mcp-hooks.js → tools/hooks.js} +6 -6
  124. package/mcp/dist/{mcp-memory.js → tools/memory.js} +5 -5
  125. package/mcp/dist/tools/ops.js +468 -0
  126. package/mcp/dist/tools/search.js +672 -0
  127. package/mcp/dist/{mcp-session.js → tools/session.js} +51 -25
  128. package/mcp/dist/{mcp-skills.js → tools/skills.js} +42 -35
  129. package/mcp/dist/{mcp-tasks.js → tools/tasks.js} +155 -282
  130. package/mcp/dist/{memory-ui-data.js → ui/data.js} +31 -17
  131. package/mcp/dist/{memory-ui.js → ui/memory-ui.js} +3 -3
  132. package/mcp/dist/{memory-ui-page.js → ui/page.js} +5 -7
  133. package/mcp/dist/ui/server.js +1024 -0
  134. package/mcp/dist/update.js +2 -2
  135. package/mcp/dist/utils.js +63 -19
  136. package/package.json +2 -2
  137. package/scripts/preuninstall.mjs +31 -0
  138. package/starter/global/CLAUDE.md +3 -2
  139. package/mcp/dist/governance-audit.js +0 -22
  140. package/mcp/dist/mcp-config.js +0 -551
  141. package/mcp/dist/mcp-finding.js +0 -594
  142. package/mcp/dist/mcp-ops.js +0 -363
  143. package/mcp/dist/mcp-search.js +0 -668
  144. package/mcp/dist/memory-ui-server.js +0 -1411
  145. package/mcp/dist/shared-governance.js +0 -4
  146. /package/mcp/dist/{content-metadata.js → content/metadata.js} +0 -0
  147. /package/mcp/dist/{shared-stemmer.js → shared/stemmer.js} +0 -0
  148. /package/mcp/dist/{shell-types.js → shell/types.js} +0 -0
  149. /package/mcp/dist/{mcp-types.js → tools/types.js} +0 -0
  150. /package/mcp/dist/{memory-ui-assets.js → ui/assets.js} +0 -0
  151. /package/mcp/dist/{memory-ui-graph.js → ui/graph.js} +0 -0
  152. /package/mcp/dist/{memory-ui-scripts.js → ui/scripts.js} +0 -0
  153. /package/mcp/dist/{memory-ui-styles.js → ui/styles.js} +0 -0
@@ -1,8 +1,13 @@
1
- import { debugLog } from "./shared.js";
1
+ import { debugLog } from "../shared.js";
2
2
  const DEFAULT_OLLAMA_URL = "http://localhost:11434";
3
3
  const DEFAULT_EMBEDDING_MODEL = "nomic-embed-text";
4
4
  const DEFAULT_EXTRACT_MODEL = "llama3.2";
5
5
  const MAX_EMBED_INPUT_CHARS = 6000;
6
+ const CLOUD_EMBEDDING_TIMEOUT_MS = 15_000;
7
+ const OLLAMA_HEALTH_TIMEOUT_MS = 2_000;
8
+ const OLLAMA_EMBEDDING_TIMEOUT_MS = 10_000;
9
+ const OLLAMA_GENERATE_TIMEOUT_MS = 60_000;
10
+ /** @internal Exported for tests. */
6
11
  export function prepareEmbeddingInput(text) {
7
12
  return text
8
13
  .replace(/<!--[\s\S]*?-->/g, " ")
@@ -44,7 +49,7 @@ async function embedTextCloud(input, baseUrl, model, apiKey) {
44
49
  headers["Authorization"] = `Bearer ${apiKey}`;
45
50
  try {
46
51
  const controller = new AbortController();
47
- const id = setTimeout(() => controller.abort(), 15000);
52
+ const id = setTimeout(() => controller.abort(), CLOUD_EMBEDDING_TIMEOUT_MS);
48
53
  const res = await fetch(`${baseUrl}/embeddings`, {
49
54
  method: "POST",
50
55
  headers,
@@ -85,7 +90,7 @@ export async function checkOllamaAvailable(url) {
85
90
  return false;
86
91
  try {
87
92
  const controller = new AbortController();
88
- const id = setTimeout(() => controller.abort(), 2000);
93
+ const id = setTimeout(() => controller.abort(), OLLAMA_HEALTH_TIMEOUT_MS);
89
94
  const res = await fetch(`${baseUrl}/api/tags`, { signal: controller.signal });
90
95
  clearTimeout(id);
91
96
  return res.ok;
@@ -104,7 +109,7 @@ export async function checkModelAvailable(model, url) {
104
109
  const modelName = model ?? getEmbeddingModel();
105
110
  try {
106
111
  const controller = new AbortController();
107
- const id = setTimeout(() => controller.abort(), 2000);
112
+ const id = setTimeout(() => controller.abort(), OLLAMA_HEALTH_TIMEOUT_MS);
108
113
  const res = await fetch(`${baseUrl}/api/tags`, { signal: controller.signal });
109
114
  clearTimeout(id);
110
115
  if (!res.ok)
@@ -131,7 +136,7 @@ export async function embedText(text, model, url) {
131
136
  return null;
132
137
  try {
133
138
  const controller = new AbortController();
134
- const id = setTimeout(() => controller.abort(), 10000);
139
+ const id = setTimeout(() => controller.abort(), OLLAMA_EMBEDDING_TIMEOUT_MS);
135
140
  const res = await fetch(`${baseUrl}/api/embed`, {
136
141
  method: "POST",
137
142
  headers: { "Content-Type": "application/json" },
@@ -158,7 +163,7 @@ export async function generateText(prompt, model, url) {
158
163
  const modelName = model ?? getExtractModel();
159
164
  try {
160
165
  const controller = new AbortController();
161
- const id = setTimeout(() => controller.abort(), 60000);
166
+ const id = setTimeout(() => controller.abort(), OLLAMA_GENERATE_TIMEOUT_MS);
162
167
  const res = await fetch(`${baseUrl}/api/generate`, {
163
168
  method: "POST",
164
169
  headers: { "Content-Type": "application/json" },
@@ -178,4 +183,17 @@ export async function generateText(prompt, model, url) {
178
183
  return null;
179
184
  }
180
185
  }
181
- export { cosineSimilarity } from "./embedding.js";
186
+ /**
187
+ * Probe Ollama availability and model readiness in one call.
188
+ * Returns a status enum so callers can branch on it without repeating the check logic.
189
+ */
190
+ export async function checkOllamaStatus() {
191
+ if (!getOllamaUrl())
192
+ return "disabled";
193
+ const ollamaUp = await checkOllamaAvailable();
194
+ if (!ollamaUp)
195
+ return "not_running";
196
+ const modelReady = await checkModelAvailable();
197
+ return modelReady ? "ready" : "no_model";
198
+ }
199
+ export { cosineSimilarity } from "../embedding.js";
@@ -0,0 +1,24 @@
1
+ /**
2
+ * Shared process helpers for spawning detached child processes.
3
+ */
4
+ import { spawn } from "child_process";
5
+ import { resolveRuntimeProfile } from "../runtime-profile.js";
6
+ /**
7
+ * Spawn a detached child process with the standard phren environment.
8
+ * When logFd is provided, stdout/stderr are redirected to that fd.
9
+ * When omitted, all stdio is ignored.
10
+ * Returns the ChildProcess so callers can attach `.unref()` or `.on("exit", ...)`.
11
+ */
12
+ export function spawnDetachedChild(args, opts) {
13
+ return spawn(process.execPath, args, {
14
+ cwd: opts.cwd ?? process.cwd(),
15
+ detached: true,
16
+ stdio: opts.logFd !== undefined ? ["ignore", opts.logFd, opts.logFd] : "ignore",
17
+ env: {
18
+ ...process.env,
19
+ PHREN_PATH: opts.phrenPath,
20
+ PHREN_PROFILE: resolveRuntimeProfile(opts.phrenPath),
21
+ ...opts.extraEnv,
22
+ },
23
+ });
24
+ }
@@ -1,17 +1,18 @@
1
1
  // shared-retrieval.ts — shared retrieval core used by hooks and MCP search.
2
- import { getQualityMultiplier, entryScoreKey, } from "./shared-governance.js";
3
- import { queryDocRows, queryRows, cosineFallback, extractSnippet, getDocSourceKey, getEntityBoostDocs, decodeFiniteNumber, rowToDocWithRowid, buildIndex, } from "./shared-index.js";
4
- import { filterTrustedFindingsDetailed, } from "./shared-content.js";
5
- import { parseCitationComment } from "./content-citation.js";
6
- import { getHighImpactFindings } from "./finding-impact.js";
7
- import { buildFtsQueryVariants, buildRelaxedFtsQuery, isFeatureEnabled, STOP_WORDS, errorMessage } from "./utils.js";
2
+ import { getQualityMultiplier, entryScoreKey, } from "./governance.js";
3
+ import { queryDocRows, queryRows, cosineFallback, extractSnippet, getDocSourceKey, getFragmentBoostDocs, decodeFiniteNumber, rowToDocWithRowid, buildIndex, } from "./index.js";
4
+ import { filterTrustedFindingsDetailed, } from "./content.js";
5
+ import { parseCitationComment } from "../content/citation.js";
6
+ import { getHighImpactFindings } from "../finding/impact.js";
7
+ import { buildFtsQueryVariants, buildRelaxedFtsQuery, isFeatureEnabled, STOP_WORDS, errorMessage } from "../utils.js";
8
+ import { logger } from "../logger.js";
8
9
  import * as fs from "fs";
9
10
  import * as path from "path";
10
- import { getProjectGlobBoost } from "./cli-hooks-globs.js";
11
- import { vectorFallback, deterministicSeed } from "./shared-search-fallback.js";
12
- import { getOllamaUrl, getCloudEmbeddingUrl } from "./shared-ollama.js";
13
- import { keywordFallbackSearch } from "./core-search.js";
14
- import { debugLog } from "./shared.js";
11
+ import { getProjectGlobBoost } from "../cli/hooks-globs.js";
12
+ import { vectorFallback, deterministicSeed } from "./search-fallback.js";
13
+ import { getOllamaUrl, getCloudEmbeddingUrl } from "./ollama.js";
14
+ import { keywordFallbackSearch } from "../core/search.js";
15
+ import { debugLog } from "../shared.js";
15
16
  // ── Scoring constants ─────────────────────────────────────────────────────────
16
17
  /** Number of docs sampled for token-overlap semantic fallback search. */
17
18
  const SEMANTIC_FALLBACK_SAMPLE_LIMIT = 100;
@@ -182,6 +183,7 @@ const RRF_K = 60;
182
183
  * Documents appearing in multiple tiers get a higher combined score.
183
184
  * Formula: score(d) = Σ 1/(k + rank_i) for each tier i containing d, where k=60 (standard).
184
185
  */
186
+ /** @internal Exported for tests. */
185
187
  export function rrfMerge(tiers, k = RRF_K) {
186
188
  const scores = new Map();
187
189
  const docs = new Map();
@@ -266,6 +268,7 @@ function semanticFallbackDocs(db, prompt, project) {
266
268
  .map((x) => x.doc);
267
269
  return scored;
268
270
  }
271
+ /** @internal Exported for tests. */
269
272
  export function shouldRunVectorExpansion(rows, prompt, desiredResults = VECTOR_FALLBACK_SKIP_COUNT) {
270
273
  if (!rows || rows.length === 0)
271
274
  return true;
@@ -383,8 +386,7 @@ export async function searchDocumentsAsync(db, safeQuery, prompt, keywords, dete
383
386
  }
384
387
  catch (err) {
385
388
  // Vector search failure is non-fatal — return sync result
386
- if ((process.env.PHREN_DEBUG))
387
- process.stderr.write(`[phren] hybridSearch vectorFallback: ${errorMessage(err)}\n`);
389
+ logger.debug("hybridSearch vectorFallback", errorMessage(err));
388
390
  return syncResult;
389
391
  }
390
392
  }
@@ -483,9 +485,7 @@ export async function searchKnowledgeRows(db, options) {
483
485
  }
484
486
  }
485
487
  catch (err) {
486
- if ((process.env.PHREN_DEBUG)) {
487
- process.stderr.write(`[phren] vectorFallback: ${errorMessage(err)}\n`);
488
- }
488
+ logger.debug("vectorFallback", errorMessage(err));
489
489
  }
490
490
  }
491
491
  return { safeQuery, rows, usedFallback };
@@ -494,7 +494,7 @@ export async function searchKnowledgeRows(db, options) {
494
494
  * Parse PHREN_FEDERATION_PATHS env var and return valid, distinct paths.
495
495
  * Paths are colon-separated. The local phrenPath is excluded to avoid duplicate results.
496
496
  */
497
- export function parseFederationPaths(localPhrenPath) {
497
+ function parseFederationPaths(localPhrenPath) {
498
498
  const raw = process.env.PHREN_FEDERATION_PATHS ?? "";
499
499
  if (!raw.trim())
500
500
  return [];
@@ -523,9 +523,7 @@ export async function searchFederatedStores(localPhrenPath, options) {
523
523
  }
524
524
  }
525
525
  catch (err) {
526
- if (process.env.PHREN_DEBUG) {
527
- process.stderr.write(`[phren] federatedSearch storePath=${storePath}: ${errorMessage(err)}\n`);
528
- }
526
+ logger.debug(`federatedSearch storePath=${storePath}`, errorMessage(err));
529
527
  // Federation errors are non-fatal — continue with other stores
530
528
  }
531
529
  }
@@ -628,7 +626,7 @@ export function rankResults(rows, intent, gitCtx, detectedProject, phrenPathLoca
628
626
  if (canonicalRows)
629
627
  ranked = [...canonicalRows, ...ranked];
630
628
  }
631
- const entityBoost = query ? getEntityBoostDocs(db, query) : new Set();
629
+ const entityBoost = query ? getFragmentBoostDocs(db, query) : new Set();
632
630
  const entityBoostPaths = new Set();
633
631
  for (const doc of ranked) {
634
632
  // Use getDocSourceKey to build the full project/relFile key, matching what
@@ -739,7 +737,8 @@ export function rankResults(rows, intent, gitCtx, detectedProject, phrenPathLoca
739
737
  }
740
738
  return ranked;
741
739
  }
742
- /** Mark snippet lines with stale citations (cited file missing or line content changed). */
740
+ /** Mark snippet lines with stale citations (cited file missing or line content changed).
741
+ * @internal Exported for tests. */
743
742
  export function markStaleCitations(snippet) {
744
743
  const lines = snippet.split("\n");
745
744
  const result = [];
@@ -771,8 +770,7 @@ export function markStaleCitations(snippet) {
771
770
  }
772
771
  }
773
772
  catch (err) {
774
- if ((process.env.PHREN_DEBUG))
775
- process.stderr.write(`[phren] applyCitationAnnotations fileRead: ${errorMessage(err)}\n`);
773
+ logger.debug("applyCitationAnnotations fileRead", errorMessage(err));
776
774
  stale = true;
777
775
  }
778
776
  }
@@ -1,11 +1,12 @@
1
1
  import { createHash } from "crypto";
2
- import { debugLog } from "./shared.js";
3
- import { STOP_WORDS, errorMessage } from "./utils.js";
4
- import { porterStem } from "./shared-stemmer.js";
5
- import { classifyFile, normalizeIndexedContent, rowToDocWithRowid } from "./shared-index.js";
6
- import { embedText, cosineSimilarity, getEmbeddingModel, getOllamaUrl, getCloudEmbeddingUrl } from "./shared-ollama.js";
7
- import { getEmbeddingCache } from "./shared-embedding-cache.js";
8
- import { getPersistentVectorIndex } from "./shared-vector-index.js";
2
+ import { debugLog } from "../shared.js";
3
+ import { logger } from "../logger.js";
4
+ import { STOP_WORDS, errorMessage } from "../utils.js";
5
+ import { porterStem } from "./stemmer.js";
6
+ import { classifyFile, normalizeIndexedContent, rowToDocWithRowid } from "./index.js";
7
+ import { embedText, cosineSimilarity, getEmbeddingModel, getOllamaUrl, getCloudEmbeddingUrl } from "./ollama.js";
8
+ import { getEmbeddingCache } from "./embedding-cache.js";
9
+ import { getPersistentVectorIndex } from "./vector-index.js";
9
10
  import * as fs from "fs";
10
11
  import * as path from "path";
11
12
  const HYBRID_SEARCH_FLAG = "PHREN_FEATURE_HYBRID_SEARCH";
@@ -16,6 +17,7 @@ const COSINE_WINDOW_COUNT = 4;
16
17
  function splitPathSegments(filePath) {
17
18
  return filePath.split(/[\\/]+/).filter(Boolean);
18
19
  }
20
+ /** @internal Exported for tests. */
19
21
  export function deriveVectorDocIdentity(phrenPath, fullPath) {
20
22
  const normalizedPhrenPath = phrenPath.replace(/[\\/]+/g, "/").replace(/\/+$/, "");
21
23
  const normalizedFullPath = fullPath.replace(/[\\/]+/g, "/");
@@ -123,8 +125,10 @@ function tfidfCosine(docs, query, corpusN) {
123
125
  const N = corpusN ?? docs.length;
124
126
  // Compute document frequency for each term, keyed by a fingerprint of the candidate doc set
125
127
  // so that different subsets and incremental index mutations get distinct cache entries.
128
+ const corpusSize = docTokenLists.length;
129
+ const corpusLengthSum = docTokenLists.reduce((sum, tl) => sum + tl.length, 0);
126
130
  const candidateFingerprint = docTokenLists.map(tl => tl.slice(0, 4).join(",")).join("|").slice(0, 128);
127
- const cacheKey = `fp:${candidateFingerprint}`;
131
+ const cacheKey = `fp:${corpusSize}:${corpusLengthSum}:${candidateFingerprint}`;
128
132
  const cachedDf = dfCache.get(cacheKey);
129
133
  const df = cachedDf ?? new Map();
130
134
  // Compute DF for any terms not yet in cache
@@ -181,8 +185,7 @@ export function cosineFallback(db, query, excludeRowids, limit) {
181
185
  }
182
186
  }
183
187
  catch (err) {
184
- if ((process.env.PHREN_DEBUG))
185
- process.stderr.write(`[phren] cosineFallback count: ${errorMessage(err)}\n`);
188
+ logger.debug("cosineFallback count", errorMessage(err));
186
189
  return [];
187
190
  }
188
191
  if (totalDocs > COSINE_MAX_CORPUS) {
@@ -210,8 +213,7 @@ export function cosineFallback(db, query, excludeRowids, limit) {
210
213
  ftsRows.push(...ftsRes[0].values);
211
214
  }
212
215
  catch (err) {
213
- if ((process.env.PHREN_DEBUG))
214
- process.stderr.write(`[phren] cosineFallback FTS pre-filter: ${errorMessage(err)}\n`);
216
+ logger.debug("cosineFallback FTS pre-filter", errorMessage(err));
215
217
  }
216
218
  }
217
219
  // If FTS gave fewer than cap, supplement with deterministic rowid windows.
@@ -248,8 +250,7 @@ export function cosineFallback(db, query, excludeRowids, limit) {
248
250
  }
249
251
  }
250
252
  catch (err) {
251
- if ((process.env.PHREN_DEBUG))
252
- process.stderr.write(`[phren] cosineFallback deterministicSample: ${errorMessage(err)}\n`);
253
+ logger.debug("cosineFallback deterministicSample", errorMessage(err));
253
254
  }
254
255
  }
255
256
  if (ftsRows.length === 0)
@@ -259,8 +260,7 @@ export function cosineFallback(db, query, excludeRowids, limit) {
259
260
  }
260
261
  }
261
262
  catch (err) {
262
- if ((process.env.PHREN_DEBUG))
263
- process.stderr.write(`[phren] cosineFallback loadDocs: ${errorMessage(err)}\n`);
263
+ logger.debug("cosineFallback loadDocs", errorMessage(err));
264
264
  return [];
265
265
  }
266
266
  // Separate rowids, DocRows, and content strings for scoring
@@ -305,8 +305,7 @@ export async function vectorFallback(phrenPath, query, excludePaths, limit, proj
305
305
  await cache.load();
306
306
  }
307
307
  catch (err) {
308
- if ((process.env.PHREN_DEBUG))
309
- process.stderr.write(`[phren] vectorFallback cacheLoad: ${errorMessage(err)}\n`);
308
+ logger.debug("vectorFallback cacheLoad", errorMessage(err));
310
309
  }
311
310
  }
312
311
  if (cache.size() === 0)
@@ -357,8 +356,7 @@ export async function vectorFallback(phrenPath, query, excludePaths, limit, proj
357
356
  }
358
357
  }
359
358
  catch (err) {
360
- if ((process.env.PHREN_DEBUG))
361
- process.stderr.write(`[phren] vectorFallback fileRead: ${errorMessage(err)}\n`);
359
+ logger.debug("vectorFallback fileRead", errorMessage(err));
362
360
  }
363
361
  return { project: entryProject, filename, type, content, path: e.path };
364
362
  });
@@ -2,7 +2,8 @@ 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
+ import { errorMessage } from "../utils.js";
6
+ import { logger } from "../logger.js";
6
7
  const require = createRequire(import.meta.url);
7
8
  /**
8
9
  * Locate the sql.js-fts5 WASM binary by require.resolve with path-probe fallback.
@@ -15,8 +16,7 @@ function findWasmBinary() {
15
16
  return fs.readFileSync(resolved);
16
17
  }
17
18
  catch (err) {
18
- if ((process.env.PHREN_DEBUG))
19
- process.stderr.write(`[phren] findWasmBinary requireResolve: ${errorMessage(err)}\n`);
19
+ logger.debug("sqljs", `findWasmBinary requireResolve: ${errorMessage(err)}`);
20
20
  // fall through to path probing
21
21
  }
22
22
  const __filename = fileURLToPath(import.meta.url);
@@ -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
  const VECTOR_INDEX_VERSION = 1;
7
7
  const VECTOR_INDEX_TABLE_COUNT = 4;
8
8
  const VECTOR_INDEX_BITS_PER_TABLE = 12;
@@ -2,6 +2,8 @@ import * as fs from "fs";
2
2
  import * as path from "path";
3
3
  import { debugLog, runtimeFile } from "./phren-paths.js";
4
4
  import { errorMessage } from "./utils.js";
5
+ import { withFileLock } from "./governance/locks.js";
6
+ const MAX_LOG_LINES = 1000;
5
7
  export { HOOK_TOOL_NAMES, hookConfigPath } from "./provider-adapters.js";
6
8
  export { EXEC_TIMEOUT_MS, EXEC_TIMEOUT_QUICK_MS, PhrenError, phrenOk, phrenErr, forwardErr, parsePhrenErrorCode, isRecord, withDefaults, FINDING_TYPES, FINDING_TAGS, KNOWN_OBSERVATION_TAGS, DOC_TYPES, capCache, RESERVED_PROJECT_DIR_NAMES, } from "./phren-core.js";
7
9
  export { ROOT_MANIFEST_FILENAME, homeDir, homePath, expandHomePath, defaultPhrenPath, rootManifestPath, readRootManifest, writeRootManifest, resolveInstallContext, findNearestPhrenPath, isProjectLocalMode, runtimeDir, tryUnlink, sessionsDir, runtimeFile, installPreferencesFile, runtimeHealthFile, shellStateFile, sessionMetricsFile, memoryScoresFile, memoryUsageLogFile, sessionMarker, debugLog, appendIndexEvent, resolveFindingsPath, findPhrenPath, ensurePhrenPath, findPhrenPathWithArg, normalizeProjectNameForCreate, findProjectNameCaseInsensitive, findArchivedProjectNameCaseInsensitive, getProjectDirs, collectNativeMemoryFiles, computePhrenLiveStateToken, getPhrenPath, qualityMarkers, atomicWriteText, } from "./phren-paths.js";
@@ -30,75 +32,19 @@ export function impactLogFile(phrenPath) {
30
32
  export function appendAuditLog(phrenPath, event, details) {
31
33
  const logPath = runtimeFile(phrenPath, "audit.log");
32
34
  const line = `[${new Date().toISOString()}] ${event} ${details}\n`;
33
- const lockPath = logPath + ".lock";
34
- const maxWait = 5000;
35
- const pollMs = 50;
36
- const staleMs = 30_000;
37
- const waiter = new Int32Array(new SharedArrayBuffer(4));
38
- // Q82: use an inline lock (same protocol as withFileLock) to guard the
39
- // append + conditional rotation so concurrent processes don't read the same
40
- // old content and race to write a truncated version each.
41
- let waited = 0;
42
- let hasLock = false;
43
35
  try {
44
- fs.mkdirSync(path.dirname(lockPath), { recursive: true });
45
- while (waited < maxWait) {
46
- try {
47
- fs.writeFileSync(lockPath, `${process.pid}\n${Date.now()}`, { flag: "wx" });
48
- hasLock = true;
49
- break;
50
- }
51
- catch (err) {
52
- if ((process.env.PHREN_DEBUG))
53
- process.stderr.write(`[phren] appendAuditLog lockWrite: ${errorMessage(err)}\n`);
54
- try {
55
- const stat = fs.statSync(lockPath);
56
- if (Date.now() - stat.mtimeMs > staleMs) {
57
- try {
58
- fs.unlinkSync(lockPath);
59
- }
60
- catch {
61
- // Another process may have claimed or removed the stale lock between
62
- // statSync and unlinkSync. Sleep before retrying to avoid a spin loop.
63
- Atomics.wait(waiter, 0, 0, pollMs);
64
- waited += pollMs;
65
- }
66
- continue;
67
- }
68
- }
69
- catch (statErr) {
70
- if ((process.env.PHREN_DEBUG))
71
- process.stderr.write(`[phren] appendAuditLog staleStat: ${errorMessage(statErr)}\n`);
72
- }
73
- Atomics.wait(waiter, 0, 0, pollMs);
74
- waited += pollMs;
75
- }
76
- }
77
- if (hasLock) {
36
+ fs.mkdirSync(path.dirname(logPath), { recursive: true });
37
+ withFileLock(logPath, () => {
78
38
  fs.appendFileSync(logPath, line);
79
39
  const stat = fs.statSync(logPath);
80
40
  if (stat.size > 1_000_000) {
81
41
  const content = fs.readFileSync(logPath, "utf8");
82
42
  const lines = content.split("\n");
83
- fs.writeFileSync(logPath, lines.slice(-500).join("\n") + "\n");
43
+ fs.writeFileSync(logPath, lines.slice(-MAX_LOG_LINES).join("\n") + "\n");
84
44
  }
85
- }
86
- else {
87
- debugLog(`Audit log skipped (lock timeout): ${event} ${details}`);
88
- }
45
+ });
89
46
  }
90
47
  catch (err) {
91
48
  debugLog(`Audit log write failed: ${errorMessage(err)}`);
92
49
  }
93
- finally {
94
- if (hasLock) {
95
- try {
96
- fs.unlinkSync(lockPath);
97
- }
98
- catch (err) {
99
- if ((process.env.PHREN_DEBUG))
100
- process.stderr.write(`[phren] appendAuditLog unlock: ${errorMessage(err)}\n`);
101
- }
102
- }
103
- }
104
50
  }
@@ -3,12 +3,12 @@
3
3
  * Extracted from shell.ts to keep the orchestrator under 300 lines.
4
4
  */
5
5
  import { PhrenShell } from "./shell.js";
6
- import { style, clearScreen, clearToEnd, shellStartupFrames, gradient, badge } from "./shell-render.js";
7
- import { createPhrenAnimator } from "./phren-art.js";
8
- import { errorMessage } from "./utils.js";
9
- import { computePhrenLiveStateToken } from "./shared.js";
10
- import { VERSION } from "./init-shared.js";
11
- import { loadShellState, saveShellState } from "./shell-state-store.js";
6
+ import { style, clearScreen, clearToEnd, shellStartupFrames, gradient, badge } from "./render.js";
7
+ import { createPhrenAnimator } from "../phren-art.js";
8
+ import { errorMessage } from "../utils.js";
9
+ import { computePhrenLiveStateToken } from "../shared.js";
10
+ import { VERSION } from "../init/shared.js";
11
+ import { loadShellState, saveShellState } from "./state-store.js";
12
12
  const LIVE_STATE_POLL_MS = 2000;
13
13
  function renderIntroFrame(frame, footer) {
14
14
  clearScreen();
@@ -5,17 +5,18 @@
5
5
  import { execFileSync } from "child_process";
6
6
  import * as fs from "fs";
7
7
  import * as path from "path";
8
- import { addTask, addFinding, addProjectToProfile, completeTask, listProjectCards, pinTask, readTasks, readFindings, readReviewQueue, removeFinding, removeProjectFromProfile, resetShellState, saveShellState, setMachineProfile, tidyDoneTasks, canonicalTaskFilePath, unpinTask, updateTask, workNextTask, loadShellState, resolveTaskFilePath, } from "./data-access.js";
9
- import { runtimeFile } from "./shared.js";
10
- import { handleGovernMemories } from "./cli-govern.js";
11
- import { runSearch } from "./cli-search.js";
12
- import { consolidateProjectFindings } from "./governance-policy.js";
13
- import { style } from "./shell-render.js";
14
- import { SUB_VIEWS, TAB_ICONS } from "./shell-types.js";
15
- import { getProjectSkills, getHookEntries, writeInstallPreferences } from "./shell-view.js";
16
- import { removeSkillPath, setSkillEnabledAndSync } from "./skill-files.js";
17
- import { resultMsg, editDistance, tokenize, expandIds, normalizeSection, tasksByFilter, queueByFilter, } from "./shell-palette.js";
18
- import { errorMessage } from "./utils.js";
8
+ import { addTask, addFinding, addProjectToProfile, completeTask, listProjectCards, pinTask, readTasks, readFindings, readReviewQueue, removeFinding, removeProjectFromProfile, resetShellState, saveShellState, setMachineProfile, tidyDoneTasks, canonicalTaskFilePath, unpinTask, updateTask, workNextTask, loadShellState, resolveTaskFilePath, } from "../data/access.js";
9
+ import { runtimeFile } from "../shared.js";
10
+ import { handleGovernMemories } from "../cli/govern.js";
11
+ import { runSearch } from "../cli/search.js";
12
+ import { consolidateProjectFindings } from "../governance/policy.js";
13
+ import { style } from "./render.js";
14
+ import { SUB_VIEWS, TAB_ICONS } from "./types.js";
15
+ import { getProjectSkills, getHookEntries, writeInstallPreferences } from "./view.js";
16
+ import { removeSkillPath, setSkillEnabledAndSync } from "../skill/files.js";
17
+ import { resultMsg, editDistance, tokenize, expandIds, normalizeSection, tasksByFilter, queueByFilter, } from "./palette.js";
18
+ import { errorMessage } from "../utils.js";
19
+ import { logger } from "../logger.js";
19
20
  function taskFileForProject(phrenPath, project) {
20
21
  return resolveTaskFilePath(phrenPath, project)
21
22
  ?? canonicalTaskFilePath(phrenPath, project)
@@ -379,8 +380,7 @@ export async function executePalette(host, input) {
379
380
  }
380
381
  }
381
382
  catch (err) {
382
- if ((process.env.PHREN_DEBUG))
383
- process.stderr.write(`[phren] shell status gitStatus: ${errorMessage(err)}\n`);
383
+ logger.debug("shell-input", `shell status gitStatus: ${errorMessage(err)}`);
384
384
  }
385
385
  const auditPathNew = runtimeFile(host.phrenPath, "audit.log");
386
386
  const auditPathLegacy = path.join(host.phrenPath, ".config", "audit.log");
@@ -1,9 +1,9 @@
1
1
  import { execFileSync } from "child_process";
2
2
  import * as path from "path";
3
3
  import { fileURLToPath } from "url";
4
- import { runLink } from "./link.js";
5
- import { runPhrenUpdate } from "./update.js";
6
- import { EXEC_TIMEOUT_MS, } from "./shared.js";
4
+ import { runLink } from "../link/link.js";
5
+ import { runPhrenUpdate } from "../update.js";
6
+ import { EXEC_TIMEOUT_MS, } from "../shared.js";
7
7
  export function resultMsg(r) {
8
8
  if (!r.ok)
9
9
  return r.error;
@@ -37,7 +37,7 @@ export function separator(width = 50) {
37
37
  export function stripAnsi(s) {
38
38
  return s.replace(/\x1b\[[0-9;?]*[ -/]*[@-~]/g, "");
39
39
  }
40
- export function visibleWidth(s) {
40
+ function visibleWidth(s) {
41
41
  return stripAnsi(s).length;
42
42
  }
43
43
  export function padToWidth(s, width) {
@@ -150,7 +150,7 @@ const PHREN_LOGO = [
150
150
  "██║ ██║ ██║██║ ██║███████╗██║ ╚████║",
151
151
  "╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚══════╝╚═╝ ╚═══╝",
152
152
  ];
153
- import { PHREN_ART as PHREN_STARTUP_ART } from "./phren-art.js";
153
+ import { PHREN_ART as PHREN_STARTUP_ART } from "../phren-art.js";
154
154
  // ── Line-based viewport: edge-triggered scroll (stable, no jumpiness) ─────────
155
155
  export function lineViewport(allLines, cursorFirstLine, cursorLastLine, height, prevStart) {
156
156
  if (allLines.length === 0 || height <= 0)
@@ -1,13 +1,14 @@
1
1
  import * as fs from "fs";
2
2
  import * as path from "path";
3
- import { addTask, addFinding, loadShellState, saveShellState, } from "./data-access.js";
4
- import { style } from "./shell-render.js";
5
- import { MAX_UNDO_STACK, } from "./shell-types.js";
6
- import { resultMsg, defaultRunHooks, defaultRunUpdate, defaultRunRelink, } from "./shell-palette.js";
7
- import { runDoctor } from "./link.js";
8
- import { renderShell, } from "./shell-view.js";
9
- import { executePalette, completeInput as completeInputFn, getListItems, handleNavigateKey, applyViewShortcut, } from "./shell-input.js";
10
- import { errorMessage } from "./utils.js";
3
+ import { addTask, addFinding, loadShellState, saveShellState, } from "../data/access.js";
4
+ import { style } from "./render.js";
5
+ import { MAX_UNDO_STACK, } from "./types.js";
6
+ import { logger } from "../logger.js";
7
+ import { resultMsg, defaultRunHooks, defaultRunUpdate, defaultRunRelink, } from "./palette.js";
8
+ import { runDoctor } from "../link/link.js";
9
+ import { renderShell, } from "./view.js";
10
+ import { executePalette, completeInput as completeInputFn, getListItems, handleNavigateKey, applyViewShortcut, } from "./input.js";
11
+ import { errorMessage } from "../utils.js";
11
12
  // ── Shell class ──────────────────────────────────────────────────────────────
12
13
  export class PhrenShell {
13
14
  phrenPath;
@@ -72,8 +73,7 @@ export class PhrenShell {
72
73
  }
73
74
  }
74
75
  catch (err) {
75
- if ((process.env.PHREN_DEBUG))
76
- process.stderr.write(`[phren] shell pushUndo: ${errorMessage(err)}\n`);
76
+ logger.debug("shell", `shell pushUndo: ${errorMessage(err)}`);
77
77
  }
78
78
  }
79
79
  popUndo() {
@@ -294,4 +294,4 @@ export class PhrenShell {
294
294
  return completeInputFn(line, this.phrenPath, this.profile, this.state);
295
295
  }
296
296
  }
297
- export { startShell } from "./shell-entry.js";
297
+ export { startShell } from "./entry.js";
@@ -1,8 +1,9 @@
1
1
  import * as fs from "fs";
2
2
  import * as path from "path";
3
- import { phrenOk, shellStateFile } from "./shared.js";
4
- import { withSafeLock } from "./shared-data-utils.js";
5
- import { errorMessage } from "./utils.js";
3
+ import { phrenOk, shellStateFile } from "../shared.js";
4
+ import { withSafeLock } from "../shared/data-utils.js";
5
+ import { errorMessage } from "../utils.js";
6
+ import { logger } from "../logger.js";
6
7
  const SHELL_STATE_VERSION = 3;
7
8
  const VALID_VIEWS = new Set(["Projects", "Tasks", "Findings", "Review Queue", "Skills", "Hooks", "Machines/Profiles", "Health"]);
8
9
  export function loadShellState(phrenPath) {
@@ -33,8 +34,7 @@ export function loadShellState(phrenPath) {
33
34
  };
34
35
  }
35
36
  catch (err) {
36
- if ((process.env.PHREN_DEBUG))
37
- process.stderr.write(`[phren] loadShellState parse: ${errorMessage(err)}\n`);
37
+ logger.debug("shell-state", `loadShellState parse: ${errorMessage(err)}`);
38
38
  return fallback;
39
39
  }
40
40
  }
@@ -1,4 +1,4 @@
1
- import { RESET, padToWidth, truncateLine, lineViewport, style } from "./shell-render.js";
1
+ import { RESET, padToWidth, truncateLine, lineViewport, style } from "./render.js";
2
2
  export function formatSelectableLine(line, cols, selected) {
3
3
  return selected
4
4
  ? `\x1b[7m${padToWidth(line, cols)}${RESET}`