@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.
- package/mcp/dist/capabilities/cli.js +2 -5
- package/mcp/dist/capabilities/mcp.js +5 -8
- package/mcp/dist/capabilities/types.js +2 -5
- package/mcp/dist/capabilities/vscode.js +2 -5
- package/mcp/dist/capabilities/web-ui.js +2 -5
- package/mcp/dist/{cli-actions.js → cli/actions.js} +25 -21
- package/mcp/dist/{cli.js → cli/cli.js} +13 -13
- package/mcp/dist/{cli-config.js → cli/config.js} +12 -12
- package/mcp/dist/{cli-extract.js → cli/extract.js} +8 -8
- package/mcp/dist/{cli-govern.js → cli/govern.js} +28 -17
- package/mcp/dist/{cli-graph.js → cli/graph.js} +10 -9
- package/mcp/dist/{cli-hooks-citations.js → cli/hooks-citations.js} +2 -2
- package/mcp/dist/{cli-hooks-context.js → cli/hooks-context.js} +23 -23
- package/mcp/dist/{cli-hooks-globs.js → cli/hooks-globs.js} +4 -4
- package/mcp/dist/{cli-hooks-output.js → cli/hooks-output.js} +9 -10
- package/mcp/dist/{cli-hooks-session.js → cli/hooks-session.js} +58 -117
- package/mcp/dist/{cli-hooks.js → cli/hooks.js} +27 -26
- package/mcp/dist/{cli-namespaces.js → cli/namespaces.js} +25 -24
- package/mcp/dist/{cli-ops.js → cli/ops.js} +9 -9
- package/mcp/dist/{cli-search.js → cli/search.js} +12 -11
- package/mcp/dist/cli-hooks-git.js +243 -0
- package/mcp/dist/cli-hooks-prompt.js +323 -0
- package/mcp/dist/cli-hooks-session-handlers.js +337 -0
- package/mcp/dist/cli-hooks-stop.js +519 -0
- package/mcp/dist/{content-archive.js → content/archive.js} +16 -29
- package/mcp/dist/{content-citation.js → content/citation.js} +5 -5
- package/mcp/dist/{content-dedup.js → content/dedup.js} +9 -12
- package/mcp/dist/{content-learning.js → content/learning.js} +41 -20
- package/mcp/dist/{content-validate.js → content/validate.js} +5 -5
- package/mcp/dist/{core-finding.js → core/finding.js} +4 -4
- package/mcp/dist/{core-project.js → core/project.js} +4 -4
- package/mcp/dist/{core-search.js → core/search.js} +2 -2
- package/mcp/dist/{data-access.js → data/access.js} +142 -15
- package/mcp/dist/{data-tasks.js → data/tasks.js} +7 -5
- package/mcp/dist/embedding.js +9 -14
- package/mcp/dist/entrypoint.js +11 -11
- package/mcp/dist/{finding-context.js → finding/context.js} +2 -2
- package/mcp/dist/{finding-impact.js → finding/impact.js} +3 -3
- package/mcp/dist/{finding-journal.js → finding/journal.js} +4 -4
- package/mcp/dist/{finding-lifecycle.js → finding/lifecycle.js} +13 -7
- package/mcp/dist/governance/audit.js +30 -0
- package/mcp/dist/{governance-locks.js → governance/locks.js} +14 -9
- package/mcp/dist/{governance-policy.js → governance/policy.js} +23 -12
- package/mcp/dist/{governance-rbac.js → governance/rbac.js} +4 -4
- package/mcp/dist/{governance-scores.js → governance/scores.js} +10 -11
- package/mcp/dist/hooks.js +53 -37
- package/mcp/dist/index-query.js +4 -1
- package/mcp/dist/index.js +54 -30
- package/mcp/dist/{init-config.js → init/config.js} +6 -6
- package/mcp/dist/{init.js → init/init.js} +80 -69
- package/mcp/dist/{init-preferences.js → init/preferences.js} +3 -3
- package/mcp/dist/{init-setup.js → init/setup.js} +17 -19
- package/mcp/dist/{init-shared.js → init/shared.js} +4 -4
- package/mcp/dist/init-bootstrap.js +21 -0
- package/mcp/dist/init-detect.js +38 -0
- package/mcp/dist/init-env.js +114 -0
- package/mcp/dist/init-fresh.js +234 -0
- package/mcp/dist/init-hooks.js +26 -0
- package/mcp/dist/init-mcp.js +65 -0
- package/mcp/dist/init-modes.js +135 -0
- package/mcp/dist/init-npm.js +37 -0
- package/mcp/dist/init-project-local.js +99 -0
- package/mcp/dist/init-semantic.js +48 -0
- package/mcp/dist/init-types.js +1 -0
- package/mcp/dist/init-uninstall.js +504 -0
- package/mcp/dist/init-update.js +96 -0
- package/mcp/dist/init-walkthrough.js +524 -0
- package/mcp/dist/{link-checksums.js → link/checksums.js} +5 -5
- package/mcp/dist/{link-context.js → link/context.js} +4 -4
- package/mcp/dist/{link-doctor.js → link/doctor.js} +20 -22
- package/mcp/dist/{link.js → link/link.js} +26 -31
- package/mcp/dist/{link-skills.js → link/skills.js} +10 -10
- package/mcp/dist/logger.js +11 -3
- package/mcp/dist/package-metadata.js +1 -1
- package/mcp/dist/phren-art.js +4 -126
- package/mcp/dist/phren-paths.js +30 -12
- package/mcp/dist/proactivity.js +3 -3
- package/mcp/dist/profile-store.js +5 -6
- package/mcp/dist/project-config.js +2 -2
- package/mcp/dist/project-topics.js +17 -47
- package/mcp/dist/provider-adapters.js +1 -1
- package/mcp/dist/query-correlation.js +1 -1
- package/mcp/dist/runtime-profile.js +1 -1
- package/mcp/dist/{session-checkpoints.js → session/checkpoints.js} +3 -3
- package/mcp/dist/{session-utils.js → session/utils.js} +1 -1
- package/mcp/dist/{shared-content.js → shared/content.js} +7 -7
- package/mcp/dist/{shared-data-utils.js → shared/data-utils.js} +28 -3
- package/mcp/dist/{shared-embedding-cache.js → shared/embedding-cache.js} +3 -3
- package/mcp/dist/{shared-fragment-graph.js → shared/fragment-graph.js} +19 -42
- package/mcp/dist/shared/governance.js +4 -0
- package/mcp/dist/{shared-index.js → shared/index.js} +105 -132
- package/mcp/dist/{shared-ollama.js → shared/ollama.js} +25 -7
- package/mcp/dist/shared/process.js +24 -0
- package/mcp/dist/{shared-retrieval.js → shared/retrieval.js} +22 -24
- package/mcp/dist/{shared-search-fallback.js → shared/search-fallback.js} +18 -20
- package/mcp/dist/{shared-sqljs.js → shared/sqljs.js} +3 -3
- package/mcp/dist/{shared-vector-index.js → shared/vector-index.js} +3 -3
- package/mcp/dist/shared.js +6 -60
- package/mcp/dist/{shell-entry.js → shell/entry.js} +6 -6
- package/mcp/dist/{shell-input.js → shell/input.js} +13 -13
- package/mcp/dist/{shell-palette.js → shell/palette.js} +3 -3
- package/mcp/dist/{shell-render.js → shell/render.js} +2 -2
- package/mcp/dist/{shell.js → shell/shell.js} +11 -11
- package/mcp/dist/{shell-state-store.js → shell/state-store.js} +5 -5
- package/mcp/dist/{shell-view-list.js → shell/view-list.js} +1 -1
- package/mcp/dist/{shell-view.js → shell/view.js} +13 -13
- package/mcp/dist/{skill-files.js → skill/files.js} +9 -9
- package/mcp/dist/{skill-registry.js → skill/registry.js} +5 -5
- package/mcp/dist/{skill-state.js → skill/state.js} +1 -4
- package/mcp/dist/startup-embedding.js +2 -2
- package/mcp/dist/status.js +15 -14
- package/mcp/dist/{tasks-github.js → task/github.js} +3 -2
- package/mcp/dist/{task-hygiene.js → task/hygiene.js} +4 -4
- package/mcp/dist/{task-lifecycle.js → task/lifecycle.js} +8 -13
- package/mcp/dist/telemetry.js +3 -4
- package/mcp/dist/tool-registry.js +29 -17
- package/mcp/dist/tools/config.js +530 -0
- package/mcp/dist/{mcp-data.js → tools/data.js} +8 -10
- package/mcp/dist/{mcp-extract-facts.js → tools/extract-facts.js} +6 -6
- package/mcp/dist/{mcp-extract.js → tools/extract.js} +6 -6
- package/mcp/dist/tools/finding.js +584 -0
- package/mcp/dist/{mcp-graph.js → tools/graph.js} +11 -14
- package/mcp/dist/{mcp-hooks.js → tools/hooks.js} +6 -6
- package/mcp/dist/{mcp-memory.js → tools/memory.js} +5 -5
- package/mcp/dist/tools/ops.js +468 -0
- package/mcp/dist/tools/search.js +672 -0
- package/mcp/dist/{mcp-session.js → tools/session.js} +51 -25
- package/mcp/dist/{mcp-skills.js → tools/skills.js} +42 -35
- package/mcp/dist/{mcp-tasks.js → tools/tasks.js} +155 -282
- package/mcp/dist/{memory-ui-data.js → ui/data.js} +31 -17
- package/mcp/dist/{memory-ui.js → ui/memory-ui.js} +3 -3
- package/mcp/dist/{memory-ui-page.js → ui/page.js} +5 -7
- package/mcp/dist/ui/server.js +1024 -0
- package/mcp/dist/update.js +2 -2
- package/mcp/dist/utils.js +63 -19
- package/package.json +2 -2
- package/scripts/preuninstall.mjs +31 -0
- package/starter/global/CLAUDE.md +3 -2
- package/mcp/dist/governance-audit.js +0 -22
- package/mcp/dist/mcp-config.js +0 -551
- package/mcp/dist/mcp-finding.js +0 -594
- package/mcp/dist/mcp-ops.js +0 -363
- package/mcp/dist/mcp-search.js +0 -668
- package/mcp/dist/memory-ui-server.js +0 -1411
- package/mcp/dist/shared-governance.js +0 -4
- /package/mcp/dist/{content-metadata.js → content/metadata.js} +0 -0
- /package/mcp/dist/{shared-stemmer.js → shared/stemmer.js} +0 -0
- /package/mcp/dist/{shell-types.js → shell/types.js} +0 -0
- /package/mcp/dist/{mcp-types.js → tools/types.js} +0 -0
- /package/mcp/dist/{memory-ui-assets.js → ui/assets.js} +0 -0
- /package/mcp/dist/{memory-ui-graph.js → ui/graph.js} +0 -0
- /package/mcp/dist/{memory-ui-scripts.js → ui/scripts.js} +0 -0
- /package/mcp/dist/{memory-ui-styles.js → ui/styles.js} +0 -0
|
@@ -3,21 +3,22 @@ import * as path from "path";
|
|
|
3
3
|
import * as os from "os";
|
|
4
4
|
import * as crypto from "crypto";
|
|
5
5
|
import { globSync } from "glob";
|
|
6
|
-
import { debugLog, appendIndexEvent, getProjectDirs, collectNativeMemoryFiles, runtimeFile, homeDir, readRootManifest, } from "
|
|
7
|
-
import { getIndexPolicy, withFileLock } from "./
|
|
8
|
-
import { stripTaskDoneSection } from "./
|
|
9
|
-
import { isInactiveFindingLine } from "
|
|
10
|
-
import { invalidateDfCache } from "./
|
|
11
|
-
import { errorMessage } from "
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
|
|
18
|
-
export {
|
|
19
|
-
export {
|
|
20
|
-
export {
|
|
6
|
+
import { debugLog, appendIndexEvent, getProjectDirs, collectNativeMemoryFiles, runtimeFile, homeDir, readRootManifest, } from "../shared.js";
|
|
7
|
+
import { getIndexPolicy, withFileLock } from "./governance.js";
|
|
8
|
+
import { stripTaskDoneSection } from "./content.js";
|
|
9
|
+
import { isInactiveFindingLine } from "../finding/lifecycle.js";
|
|
10
|
+
import { invalidateDfCache } from "./search-fallback.js";
|
|
11
|
+
import { errorMessage } from "../utils.js";
|
|
12
|
+
import { logger } from "../logger.js";
|
|
13
|
+
import { beginUserFragmentBuildCache, endUserFragmentBuildCache, extractAndLinkFragments, ensureGlobalEntitiesTable, } from "./fragment-graph.js";
|
|
14
|
+
import { bootstrapSqlJs } from "./sqljs.js";
|
|
15
|
+
import { getProjectOwnershipMode, getProjectSourcePath, readProjectConfig } from "../project-config.js";
|
|
16
|
+
import { buildSourceDocKey, queryDocBySourceKey, queryDocRows, } from "../index-query.js";
|
|
17
|
+
import { classifyTopicForText, readProjectTopics, } from "../project-topics.js";
|
|
18
|
+
export { porterStem } from "./stemmer.js";
|
|
19
|
+
export { cosineFallback } from "./search-fallback.js";
|
|
20
|
+
export { queryFragmentLinks, getFragmentBoostDocs, ensureGlobalEntitiesTable, queryCrossProjectFragments, logFragmentMiss, extractFragmentNames, } from "./fragment-graph.js";
|
|
21
|
+
export { buildSourceDocKey, decodeFiniteNumber, decodeStringRow, extractSnippet, getDocSourceKey, normalizeMemoryId, queryDocBySourceKey, queryDocRows, queryRows, rowToDoc, rowToDocWithRowid, } from "../index-query.js";
|
|
21
22
|
// ── Async embedding queue ───────────────────────────────────────────────────
|
|
22
23
|
const _embQueue = new Map();
|
|
23
24
|
let _embTimer = null;
|
|
@@ -32,8 +33,8 @@ function scheduleEmbedding(phrenPath, docPath, content) {
|
|
|
32
33
|
async function _drainEmbQueue() {
|
|
33
34
|
if (_embQueue.size === 0)
|
|
34
35
|
return;
|
|
35
|
-
const { embedText, getEmbeddingModel } = await import("./
|
|
36
|
-
const { getEmbeddingCache } = await import("./
|
|
36
|
+
const { embedText, getEmbeddingModel } = await import("./ollama.js");
|
|
37
|
+
const { getEmbeddingCache } = await import("./embedding-cache.js");
|
|
37
38
|
const entries = [..._embQueue.entries()];
|
|
38
39
|
_embQueue.clear();
|
|
39
40
|
// Group by phrenPath so we flush each cache once after all its entries are set.
|
|
@@ -49,8 +50,7 @@ async function _drainEmbQueue() {
|
|
|
49
50
|
await cache.load();
|
|
50
51
|
}
|
|
51
52
|
catch (err) {
|
|
52
|
-
|
|
53
|
-
process.stderr.write(`[phren] embeddingQueue cacheLoad: ${errorMessage(err)}\n`);
|
|
53
|
+
logger.debug("embeddingQueue cacheLoad", errorMessage(err));
|
|
54
54
|
}
|
|
55
55
|
const model = getEmbeddingModel();
|
|
56
56
|
for (const { docPath, content } of docs) {
|
|
@@ -62,16 +62,14 @@ async function _drainEmbQueue() {
|
|
|
62
62
|
cache.set(docPath, getEmbeddingModel(), vec);
|
|
63
63
|
}
|
|
64
64
|
catch (err) {
|
|
65
|
-
|
|
66
|
-
process.stderr.write(`[phren] embeddingQueue embedText: ${errorMessage(err)}\n`);
|
|
65
|
+
logger.debug("embeddingQueue embedText", errorMessage(err));
|
|
67
66
|
}
|
|
68
67
|
}
|
|
69
68
|
try {
|
|
70
69
|
await cache.flush();
|
|
71
70
|
}
|
|
72
71
|
catch (err) {
|
|
73
|
-
|
|
74
|
-
process.stderr.write(`[phren] embeddingQueue cacheFlush: ${errorMessage(err)}\n`);
|
|
72
|
+
logger.debug("embeddingQueue cacheFlush", errorMessage(err));
|
|
75
73
|
}
|
|
76
74
|
}
|
|
77
75
|
}
|
|
@@ -133,8 +131,7 @@ function _resolveImportsRecursive(content, phrenPath, seen, depth) {
|
|
|
133
131
|
normalized = fs.realpathSync.native(resolved);
|
|
134
132
|
}
|
|
135
133
|
catch (err) {
|
|
136
|
-
|
|
137
|
-
process.stderr.write(`[phren] resolveImports realpath: ${errorMessage(err)}\n`);
|
|
134
|
+
logger.debug("resolveImports realpath", errorMessage(err));
|
|
138
135
|
return `<!-- @import not found: ${trimmed} -->`;
|
|
139
136
|
}
|
|
140
137
|
let normalizedGlobalRoot = globalRoot;
|
|
@@ -158,8 +155,7 @@ function _resolveImportsRecursive(content, phrenPath, seen, depth) {
|
|
|
158
155
|
return _resolveImportsRecursive(imported, phrenPath, childSeen, depth + 1);
|
|
159
156
|
}
|
|
160
157
|
catch (err) {
|
|
161
|
-
|
|
162
|
-
process.stderr.write(`[phren] resolveImports fileRead: ${errorMessage(err)}\n`);
|
|
158
|
+
logger.debug("resolveImports fileRead", errorMessage(err));
|
|
163
159
|
return `<!-- @import error: ${trimmed} -->`;
|
|
164
160
|
}
|
|
165
161
|
});
|
|
@@ -169,6 +165,7 @@ function _resolveImportsRecursive(content, phrenPath, seen, depth) {
|
|
|
169
165
|
* The import path is resolved relative to the phren root (e.g. `shared/foo.md` -> `~/.phren/global/shared/foo.md`).
|
|
170
166
|
* Circular imports are detected and skipped. Depth is capped to prevent runaway recursion.
|
|
171
167
|
*/
|
|
168
|
+
/** @internal Exported for tests. */
|
|
172
169
|
export function resolveImports(content, phrenPath) {
|
|
173
170
|
return _resolveImportsRecursive(content, phrenPath, new Set(), 0);
|
|
174
171
|
}
|
|
@@ -180,8 +177,7 @@ function touchSentinel(phrenPath) {
|
|
|
180
177
|
fs.writeFileSync(sentinelPath, Date.now().toString());
|
|
181
178
|
}
|
|
182
179
|
catch (err) {
|
|
183
|
-
|
|
184
|
-
process.stderr.write(`[phren] touchSentinel: ${errorMessage(err)}\n`);
|
|
180
|
+
logger.debug("touchSentinel", errorMessage(err));
|
|
185
181
|
}
|
|
186
182
|
}
|
|
187
183
|
function computePhrenHash(phrenPath, profile, preGlobbed) {
|
|
@@ -197,8 +193,7 @@ function computePhrenHash(phrenPath, profile, preGlobbed) {
|
|
|
197
193
|
hash.update(`${f}:${stat.mtimeMs}:${stat.size}`);
|
|
198
194
|
}
|
|
199
195
|
catch (err) {
|
|
200
|
-
|
|
201
|
-
process.stderr.write(`[phren] computePhrenHash skip: ${errorMessage(err)}\n`);
|
|
196
|
+
logger.debug("computePhrenHash skip", errorMessage(err));
|
|
202
197
|
}
|
|
203
198
|
}
|
|
204
199
|
for (const configPath of topicConfigEntries) {
|
|
@@ -207,8 +202,7 @@ function computePhrenHash(phrenPath, profile, preGlobbed) {
|
|
|
207
202
|
hash.update(`topic-config:${configPath}:${stat.mtimeMs}:${stat.size}`);
|
|
208
203
|
}
|
|
209
204
|
catch (err) {
|
|
210
|
-
|
|
211
|
-
process.stderr.write(`[phren] computePhrenHash topicConfig: ${errorMessage(err)}\n`);
|
|
205
|
+
logger.debug("computePhrenHash topicConfig", errorMessage(err));
|
|
212
206
|
}
|
|
213
207
|
}
|
|
214
208
|
}
|
|
@@ -239,8 +233,7 @@ function computePhrenHash(phrenPath, profile, preGlobbed) {
|
|
|
239
233
|
}
|
|
240
234
|
}
|
|
241
235
|
catch (err) {
|
|
242
|
-
|
|
243
|
-
process.stderr.write(`[phren] computePhrenHash globDir: ${errorMessage(err)}\n`);
|
|
236
|
+
logger.debug("computePhrenHash globDir", errorMessage(err));
|
|
244
237
|
}
|
|
245
238
|
}
|
|
246
239
|
files.sort();
|
|
@@ -250,8 +243,7 @@ function computePhrenHash(phrenPath, profile, preGlobbed) {
|
|
|
250
243
|
hash.update(`${f}:${stat.mtimeMs}:${stat.size}`);
|
|
251
244
|
}
|
|
252
245
|
catch (err) {
|
|
253
|
-
|
|
254
|
-
process.stderr.write(`[phren] computePhrenHash skip: ${errorMessage(err)}\n`);
|
|
246
|
+
logger.debug("computePhrenHash skip", errorMessage(err));
|
|
255
247
|
}
|
|
256
248
|
}
|
|
257
249
|
for (const configPath of topicConfigEntries) {
|
|
@@ -260,8 +252,7 @@ function computePhrenHash(phrenPath, profile, preGlobbed) {
|
|
|
260
252
|
hash.update(`topic-config:${configPath}:${stat.mtimeMs}:${stat.size}`);
|
|
261
253
|
}
|
|
262
254
|
catch (err) {
|
|
263
|
-
|
|
264
|
-
process.stderr.write(`[phren] computePhrenHash topicConfig: ${errorMessage(err)}\n`);
|
|
255
|
+
logger.debug("computePhrenHash topicConfig", errorMessage(err));
|
|
265
256
|
}
|
|
266
257
|
}
|
|
267
258
|
}
|
|
@@ -271,8 +262,7 @@ function computePhrenHash(phrenPath, profile, preGlobbed) {
|
|
|
271
262
|
hash.update(`native:${mem.fullPath}:${stat.mtimeMs}:${stat.size}`);
|
|
272
263
|
}
|
|
273
264
|
catch (err) {
|
|
274
|
-
|
|
275
|
-
process.stderr.write(`[phren] computePhrenHash skip: ${errorMessage(err)}\n`);
|
|
265
|
+
logger.debug("computePhrenHash skip", errorMessage(err));
|
|
276
266
|
}
|
|
277
267
|
}
|
|
278
268
|
// Include global/ files (pulled via @import) so changes invalidate the cache
|
|
@@ -286,8 +276,7 @@ function computePhrenHash(phrenPath, profile, preGlobbed) {
|
|
|
286
276
|
hash.update(`global:${f}:${stat.mtimeMs}:${stat.size}`);
|
|
287
277
|
}
|
|
288
278
|
catch (err) {
|
|
289
|
-
|
|
290
|
-
process.stderr.write(`[phren] computePhrenHash skip: ${errorMessage(err)}\n`);
|
|
279
|
+
logger.debug("computePhrenHash skip", errorMessage(err));
|
|
291
280
|
}
|
|
292
281
|
}
|
|
293
282
|
}
|
|
@@ -299,8 +288,7 @@ function computePhrenHash(phrenPath, profile, preGlobbed) {
|
|
|
299
288
|
hash.update(`manual-links:${stat.mtimeMs}:${stat.size}`);
|
|
300
289
|
}
|
|
301
290
|
catch (err) {
|
|
302
|
-
|
|
303
|
-
process.stderr.write(`[phren] computePhrenHash skip: ${errorMessage(err)}\n`);
|
|
291
|
+
logger.debug("computePhrenHash skip", errorMessage(err));
|
|
304
292
|
}
|
|
305
293
|
}
|
|
306
294
|
const indexPolicyPath = path.join(phrenPath, ".config", "index-policy.json");
|
|
@@ -310,8 +298,7 @@ function computePhrenHash(phrenPath, profile, preGlobbed) {
|
|
|
310
298
|
hash.update(`index-policy-file:${stat.mtimeMs}:${stat.size}`);
|
|
311
299
|
}
|
|
312
300
|
catch (err) {
|
|
313
|
-
|
|
314
|
-
process.stderr.write(`[phren] computePhrenHash skip: ${errorMessage(err)}\n`);
|
|
301
|
+
logger.debug("computePhrenHash skip", errorMessage(err));
|
|
315
302
|
}
|
|
316
303
|
}
|
|
317
304
|
if (profile)
|
|
@@ -334,12 +321,11 @@ function loadHashMap(phrenPath) {
|
|
|
334
321
|
}
|
|
335
322
|
}
|
|
336
323
|
catch (err) {
|
|
337
|
-
|
|
338
|
-
process.stderr.write(`[phren] loadHashMap: ${errorMessage(err)}\n`);
|
|
324
|
+
logger.debug("loadHashMap", errorMessage(err));
|
|
339
325
|
}
|
|
340
326
|
return { hashes: {} };
|
|
341
327
|
}
|
|
342
|
-
function saveHashMap(phrenPath, hashes) {
|
|
328
|
+
function saveHashMap(phrenPath, hashes, knownPaths) {
|
|
343
329
|
const runtimeDir = path.join(phrenPath, ".runtime");
|
|
344
330
|
try {
|
|
345
331
|
fs.mkdirSync(runtimeDir, { recursive: true });
|
|
@@ -355,13 +341,14 @@ function saveHashMap(phrenPath, hashes) {
|
|
|
355
341
|
existing = data.hashes;
|
|
356
342
|
}
|
|
357
343
|
catch (err) {
|
|
358
|
-
|
|
359
|
-
process.stderr.write(`[phren] saveHashMap readExisting: ${errorMessage(err)}\n`);
|
|
344
|
+
logger.debug("saveHashMap readExisting", errorMessage(err));
|
|
360
345
|
}
|
|
361
346
|
const merged = { ...existing, ...hashes };
|
|
362
|
-
// Remove entries for paths that no longer exist
|
|
347
|
+
// Remove entries for paths that no longer exist. When knownPaths is provided
|
|
348
|
+
// (build passes supply the full file list), use set membership instead of
|
|
349
|
+
// hitting the filesystem — avoids N sync stat calls inside the lock.
|
|
363
350
|
for (const filePath of Object.keys(merged)) {
|
|
364
|
-
if (!fs.existsSync(filePath)) {
|
|
351
|
+
if (knownPaths ? !knownPaths.has(filePath) : !fs.existsSync(filePath)) {
|
|
365
352
|
delete merged[filePath];
|
|
366
353
|
}
|
|
367
354
|
}
|
|
@@ -494,8 +481,7 @@ function insertFileIntoIndex(db, entry, phrenPath, opts) {
|
|
|
494
481
|
return true;
|
|
495
482
|
}
|
|
496
483
|
catch (err) {
|
|
497
|
-
|
|
498
|
-
process.stderr.write(`[phren] insertFileIntoIndex: ${errorMessage(err)}\n`);
|
|
484
|
+
logger.debug("insertFileIntoIndex", errorMessage(err));
|
|
499
485
|
return false;
|
|
500
486
|
}
|
|
501
487
|
}
|
|
@@ -575,8 +561,7 @@ function deleteEntityLinksForDocPath(db, phrenPath, docPath, fallbackProject, fa
|
|
|
575
561
|
db.run("DELETE FROM global_entities WHERE doc_key = ?", [sourceDoc]);
|
|
576
562
|
}
|
|
577
563
|
catch (err) {
|
|
578
|
-
|
|
579
|
-
process.stderr.write(`[phren] deleteEntityLinksForDocPath globalEntities: ${errorMessage(err)}\n`);
|
|
564
|
+
logger.debug("deleteEntityLinksForDocPath globalEntities", errorMessage(err));
|
|
580
565
|
}
|
|
581
566
|
}
|
|
582
567
|
/**
|
|
@@ -591,15 +576,13 @@ export function updateFileInIndex(db, filePath, phrenPath) {
|
|
|
591
576
|
deleteEntityLinksForDocPath(db, phrenPath, resolvedPath);
|
|
592
577
|
}
|
|
593
578
|
catch (err) {
|
|
594
|
-
|
|
595
|
-
process.stderr.write(`[phren] updateFileInIndex deleteEntityLinks: ${errorMessage(err)}\n`);
|
|
579
|
+
logger.debug("updateFileInIndex deleteEntityLinks", errorMessage(err));
|
|
596
580
|
}
|
|
597
581
|
try {
|
|
598
582
|
db.run("DELETE FROM docs WHERE path = ?", [resolvedPath]);
|
|
599
583
|
}
|
|
600
584
|
catch (err) {
|
|
601
|
-
|
|
602
|
-
process.stderr.write(`[phren] updateFileInIndex deleteDocs: ${errorMessage(err)}\n`);
|
|
585
|
+
logger.debug("updateFileInIndex deleteDocs", errorMessage(err));
|
|
603
586
|
}
|
|
604
587
|
// Re-insert if file still exists
|
|
605
588
|
if (fs.existsSync(resolvedPath)) {
|
|
@@ -618,8 +601,7 @@ export function updateFileInIndex(db, filePath, phrenPath) {
|
|
|
618
601
|
extractAndLinkFragments(db, content, getEntrySourceDocKey(entry, phrenPath), phrenPath);
|
|
619
602
|
}
|
|
620
603
|
catch (err) {
|
|
621
|
-
|
|
622
|
-
process.stderr.write(`[phren] updateFileInIndex entityExtraction: ${errorMessage(err)}\n`);
|
|
604
|
+
logger.debug("updateFileInIndex entityExtraction", errorMessage(err));
|
|
623
605
|
}
|
|
624
606
|
}
|
|
625
607
|
}
|
|
@@ -630,22 +612,20 @@ export function updateFileInIndex(db, filePath, phrenPath) {
|
|
|
630
612
|
saveHashMap(phrenPath, hashData.hashes);
|
|
631
613
|
}
|
|
632
614
|
catch (err) {
|
|
633
|
-
|
|
634
|
-
process.stderr.write(`[phren] updateFileInIndex hashMap: ${errorMessage(err)}\n`);
|
|
615
|
+
logger.debug("updateFileInIndex hashMap", errorMessage(err));
|
|
635
616
|
}
|
|
636
617
|
}
|
|
637
618
|
else {
|
|
638
619
|
// Remove stale embedding if file was deleted
|
|
639
620
|
void (async () => {
|
|
640
621
|
try {
|
|
641
|
-
const { getEmbeddingCache } = await import("./
|
|
622
|
+
const { getEmbeddingCache } = await import("./embedding-cache.js");
|
|
642
623
|
const c = getEmbeddingCache(phrenPath);
|
|
643
624
|
c.delete(resolvedPath);
|
|
644
625
|
await c.flush();
|
|
645
626
|
}
|
|
646
627
|
catch (err) {
|
|
647
|
-
|
|
648
|
-
process.stderr.write(`[phren] updateFileInIndex embeddingDelete: ${errorMessage(err)}\n`);
|
|
628
|
+
logger.debug("updateFileInIndex embeddingDelete", errorMessage(err));
|
|
649
629
|
}
|
|
650
630
|
})();
|
|
651
631
|
}
|
|
@@ -664,8 +644,7 @@ function readHashSentinel(phrenPath) {
|
|
|
664
644
|
}
|
|
665
645
|
}
|
|
666
646
|
catch (err) {
|
|
667
|
-
|
|
668
|
-
process.stderr.write(`[phren] readHashSentinel: ${errorMessage(err)}\n`);
|
|
647
|
+
logger.debug("readHashSentinel", errorMessage(err));
|
|
669
648
|
}
|
|
670
649
|
return null;
|
|
671
650
|
}
|
|
@@ -675,8 +654,7 @@ function writeHashSentinel(phrenPath, hash) {
|
|
|
675
654
|
fs.writeFileSync(sentinelPath, JSON.stringify({ hash, computedAt: Date.now() }));
|
|
676
655
|
}
|
|
677
656
|
catch (err) {
|
|
678
|
-
|
|
679
|
-
process.stderr.write(`[phren] writeHashSentinel: ${errorMessage(err)}\n`);
|
|
657
|
+
logger.debug("writeHashSentinel", errorMessage(err));
|
|
680
658
|
}
|
|
681
659
|
}
|
|
682
660
|
function isSentinelFresh(phrenPath, sentinel) {
|
|
@@ -693,8 +671,7 @@ function isSentinelFresh(phrenPath, sentinel) {
|
|
|
693
671
|
return false;
|
|
694
672
|
}
|
|
695
673
|
catch (err) {
|
|
696
|
-
|
|
697
|
-
process.stderr.write(`[phren] isSentinelFresh statDir: ${errorMessage(err)}\n`);
|
|
674
|
+
logger.debug("isSentinelFresh statDir", errorMessage(err));
|
|
698
675
|
}
|
|
699
676
|
}
|
|
700
677
|
return true;
|
|
@@ -715,8 +692,7 @@ function loadCachedEntityGraph(db, graphPath, allFiles, phrenPath) {
|
|
|
715
692
|
return fs.statSync(f.fullPath).mtimeMs > graphMtime;
|
|
716
693
|
}
|
|
717
694
|
catch (err) {
|
|
718
|
-
|
|
719
|
-
process.stderr.write(`[phren] loadCachedEntityGraph statFile: ${errorMessage(err)}\n`);
|
|
695
|
+
logger.debug("loadCachedEntityGraph statFile", errorMessage(err));
|
|
720
696
|
return true;
|
|
721
697
|
}
|
|
722
698
|
});
|
|
@@ -743,8 +719,7 @@ function loadCachedEntityGraph(db, graphPath, allFiles, phrenPath) {
|
|
|
743
719
|
db.run("INSERT OR IGNORE INTO global_entities (entity, project, doc_key) VALUES (?, ?, ?)", [entity, project, docKey]);
|
|
744
720
|
}
|
|
745
721
|
catch (err) {
|
|
746
|
-
|
|
747
|
-
process.stderr.write(`[phren] loadCachedEntityGraph globalEntitiesInsert2: ${errorMessage(err)}\n`);
|
|
722
|
+
logger.debug("loadCachedEntityGraph globalEntitiesInsert2", errorMessage(err));
|
|
748
723
|
}
|
|
749
724
|
}
|
|
750
725
|
}
|
|
@@ -762,23 +737,20 @@ function loadCachedEntityGraph(db, graphPath, allFiles, phrenPath) {
|
|
|
762
737
|
db.run("INSERT OR IGNORE INTO global_entities (entity, project, doc_key) VALUES (?, ?, ?)", [name, proj, sourceDoc]);
|
|
763
738
|
}
|
|
764
739
|
catch (err) {
|
|
765
|
-
|
|
766
|
-
process.stderr.write(`[phren] loadCachedEntityGraph globalEntitiesInsert: ${errorMessage(err)}\n`);
|
|
740
|
+
logger.debug("loadCachedEntityGraph globalEntitiesInsert", errorMessage(err));
|
|
767
741
|
}
|
|
768
742
|
}
|
|
769
743
|
}
|
|
770
744
|
}
|
|
771
745
|
catch (err) {
|
|
772
|
-
|
|
773
|
-
process.stderr.write(`[phren] entityGraph globalEntitiesRestore: ${errorMessage(err)}\n`);
|
|
746
|
+
logger.debug("entityGraph globalEntitiesRestore", errorMessage(err));
|
|
774
747
|
}
|
|
775
748
|
}
|
|
776
749
|
return true;
|
|
777
750
|
}
|
|
778
751
|
}
|
|
779
752
|
catch (err) {
|
|
780
|
-
|
|
781
|
-
process.stderr.write(`[phren] entityGraph cacheLoad: ${errorMessage(err)}\n`);
|
|
753
|
+
logger.debug("entityGraph cacheLoad", errorMessage(err));
|
|
782
754
|
}
|
|
783
755
|
return false;
|
|
784
756
|
}
|
|
@@ -797,8 +769,7 @@ function mergeManualLinks(db, phrenPath) {
|
|
|
797
769
|
// Validate: skip manual links whose sourceDoc no longer exists in the index
|
|
798
770
|
const docCheck = queryDocBySourceKey(db, phrenPath, link.sourceDoc);
|
|
799
771
|
if (!docCheck) {
|
|
800
|
-
|
|
801
|
-
process.stderr.write(`[phren] manualLinks: pruning stale link to "${link.sourceDoc}"\n`);
|
|
772
|
+
logger.debug("manualLinks", `pruning stale link to "${link.sourceDoc}"`);
|
|
802
773
|
pruned = true;
|
|
803
774
|
continue;
|
|
804
775
|
}
|
|
@@ -819,14 +790,12 @@ function mergeManualLinks(db, phrenPath) {
|
|
|
819
790
|
db.run("INSERT OR IGNORE INTO global_entities (entity, project, doc_key) VALUES (?, ?, ?)", [link.entity, projectMatch[1], link.sourceDoc]);
|
|
820
791
|
}
|
|
821
792
|
catch (err) {
|
|
822
|
-
|
|
823
|
-
process.stderr.write(`[phren] manualLinks globalEntities: ${errorMessage(err)}\n`);
|
|
793
|
+
logger.debug("manualLinks globalEntities", errorMessage(err));
|
|
824
794
|
}
|
|
825
795
|
}
|
|
826
796
|
}
|
|
827
797
|
catch (err) {
|
|
828
|
-
|
|
829
|
-
process.stderr.write(`[phren] manualLinks entry: ${errorMessage(err)}\n`);
|
|
798
|
+
logger.debug("manualLinks entry", errorMessage(err));
|
|
830
799
|
}
|
|
831
800
|
}
|
|
832
801
|
// Rewrite manual-links.json if stale entries were pruned
|
|
@@ -839,19 +808,18 @@ function mergeManualLinks(db, phrenPath) {
|
|
|
839
808
|
});
|
|
840
809
|
}
|
|
841
810
|
catch (err) {
|
|
842
|
-
|
|
843
|
-
process.stderr.write(`[phren] manualLinks prune write: ${errorMessage(err)}\n`);
|
|
811
|
+
logger.debug("manualLinks prune write", errorMessage(err));
|
|
844
812
|
}
|
|
845
813
|
}
|
|
846
814
|
}
|
|
847
815
|
catch (err) {
|
|
848
|
-
|
|
849
|
-
process.stderr.write(`[phren] mergeManualLinks: ${errorMessage(err)}\n`);
|
|
816
|
+
logger.debug("mergeManualLinks", errorMessage(err));
|
|
850
817
|
}
|
|
851
818
|
}
|
|
852
819
|
async function buildIndexImpl(phrenPath, profile) {
|
|
853
820
|
const t0 = Date.now();
|
|
854
|
-
|
|
821
|
+
const projectDirs = getProjectDirs(phrenPath, profile);
|
|
822
|
+
beginUserFragmentBuildCache(phrenPath, projectDirs.map(dir => path.basename(dir)));
|
|
855
823
|
try {
|
|
856
824
|
// ── Cache dir + hash sentinel ─────────────────────────────────────────────
|
|
857
825
|
let userSuffix;
|
|
@@ -859,8 +827,7 @@ async function buildIndexImpl(phrenPath, profile) {
|
|
|
859
827
|
userSuffix = String(os.userInfo().uid);
|
|
860
828
|
}
|
|
861
829
|
catch (err) {
|
|
862
|
-
|
|
863
|
-
process.stderr.write(`[phren] buildIndexImpl userInfo: ${errorMessage(err)}\n`);
|
|
830
|
+
logger.debug("buildIndexImpl userInfo", errorMessage(err));
|
|
864
831
|
userSuffix = crypto.createHash("sha1").update(homeDir()).digest("hex").slice(0, 12);
|
|
865
832
|
}
|
|
866
833
|
const cacheDir = path.join(os.tmpdir(), `phren-fts-${userSuffix}`);
|
|
@@ -965,8 +932,7 @@ async function buildIndexImpl(phrenPath, profile) {
|
|
|
965
932
|
}
|
|
966
933
|
}
|
|
967
934
|
catch (err) {
|
|
968
|
-
|
|
969
|
-
process.stderr.write(`[phren] buildIndex hashFile: ${errorMessage(err)}\n`);
|
|
935
|
+
logger.debug("buildIndex hashFile", errorMessage(err));
|
|
970
936
|
}
|
|
971
937
|
}
|
|
972
938
|
// Check for files missing from the index (deleted files)
|
|
@@ -1001,28 +967,24 @@ async function buildIndexImpl(phrenPath, profile) {
|
|
|
1001
967
|
deleteEntityLinksForDocPath(db, phrenPath, missingPath);
|
|
1002
968
|
}
|
|
1003
969
|
catch (err) {
|
|
1004
|
-
|
|
1005
|
-
process.stderr.write(`[phren] buildIndex deleteEntityLinksForMissing: ${errorMessage(err)}\n`);
|
|
970
|
+
logger.debug("buildIndex deleteEntityLinksForMissing", errorMessage(err));
|
|
1006
971
|
}
|
|
1007
972
|
try {
|
|
1008
973
|
db.run("DELETE FROM docs WHERE path = ?", [missingPath]);
|
|
1009
974
|
}
|
|
1010
975
|
catch (err) {
|
|
1011
|
-
|
|
1012
|
-
process.stderr.write(`[phren] buildIndex deleteDocForMissing: ${errorMessage(err)}\n`);
|
|
976
|
+
logger.debug("buildIndex deleteDocForMissing", errorMessage(err));
|
|
1013
977
|
}
|
|
1014
978
|
}
|
|
1015
979
|
db.run("COMMIT");
|
|
1016
980
|
}
|
|
1017
981
|
catch (err) {
|
|
1018
|
-
|
|
1019
|
-
process.stderr.write(`[phren] buildIndex incrementalDeleteCommit: ${errorMessage(err)}\n`);
|
|
982
|
+
logger.debug("buildIndex incrementalDeleteCommit", errorMessage(err));
|
|
1020
983
|
try {
|
|
1021
984
|
db.run("ROLLBACK");
|
|
1022
985
|
}
|
|
1023
986
|
catch (e2) {
|
|
1024
|
-
|
|
1025
|
-
process.stderr.write(`[phren] buildIndex incrementalDeleteRollback: ${e2 instanceof Error ? e2.message : String(e2)}\n`);
|
|
987
|
+
logger.debug("buildIndex incrementalDeleteRollback", e2 instanceof Error ? e2.message : String(e2));
|
|
1026
988
|
}
|
|
1027
989
|
}
|
|
1028
990
|
let updatedCount = 0;
|
|
@@ -1058,13 +1020,12 @@ async function buildIndexImpl(phrenPath, profile) {
|
|
|
1058
1020
|
db.run("ROLLBACK");
|
|
1059
1021
|
}
|
|
1060
1022
|
catch (e2) {
|
|
1061
|
-
|
|
1062
|
-
process.stderr.write(`[phren] buildIndex perFileRollback: ${e2 instanceof Error ? e2.message : String(e2)}\n`);
|
|
1023
|
+
logger.debug("buildIndex perFileRollback", e2 instanceof Error ? e2.message : String(e2));
|
|
1063
1024
|
}
|
|
1064
1025
|
throw err;
|
|
1065
1026
|
}
|
|
1066
1027
|
}
|
|
1067
|
-
saveHashMap(phrenPath, currentHashes);
|
|
1028
|
+
saveHashMap(phrenPath, currentHashes, new Set(Object.keys(currentHashes)));
|
|
1068
1029
|
touchSentinel(phrenPath);
|
|
1069
1030
|
invalidateDfCache();
|
|
1070
1031
|
// Save updated cache
|
|
@@ -1073,8 +1034,7 @@ async function buildIndexImpl(phrenPath, profile) {
|
|
|
1073
1034
|
fs.writeFileSync(cacheFile, db.export());
|
|
1074
1035
|
}
|
|
1075
1036
|
catch (err) {
|
|
1076
|
-
|
|
1077
|
-
process.stderr.write(`[phren] buildIndex incrementalCacheSave: ${errorMessage(err)}\n`);
|
|
1037
|
+
logger.debug("buildIndex incrementalCacheSave", errorMessage(err));
|
|
1078
1038
|
}
|
|
1079
1039
|
const incMs = Date.now() - t0;
|
|
1080
1040
|
debugLog(`Incremental FTS update: ${updatedCount} changed, ${missingFromIndex.length} removed in ${incMs}ms`);
|
|
@@ -1131,8 +1091,7 @@ async function buildIndexImpl(phrenPath, profile) {
|
|
|
1131
1091
|
newHashes[entry.fullPath] = hashFileContent(entry.fullPath);
|
|
1132
1092
|
}
|
|
1133
1093
|
catch (err) {
|
|
1134
|
-
|
|
1135
|
-
process.stderr.write(`[phren] computePhrenHash skip: ${errorMessage(err)}\n`);
|
|
1094
|
+
logger.debug("computePhrenHash skip", errorMessage(err));
|
|
1136
1095
|
}
|
|
1137
1096
|
if (insertFileIntoIndex(db, entry, phrenPath, { scheduleEmbeddings: true })) {
|
|
1138
1097
|
fileCount++;
|
|
@@ -1159,26 +1118,25 @@ async function buildIndexImpl(phrenPath, profile) {
|
|
|
1159
1118
|
fs.writeFileSync(graphPath, JSON.stringify({ entities: entityRows, links: linkRows, globalEntities: globalEntityRows, ts: Date.now() }));
|
|
1160
1119
|
}
|
|
1161
1120
|
catch (err) {
|
|
1162
|
-
|
|
1163
|
-
process.stderr.write(`[phren] buildIndex entityGraphPersist: ${errorMessage(err)}\n`);
|
|
1121
|
+
logger.debug("buildIndex entityGraphPersist", errorMessage(err));
|
|
1164
1122
|
}
|
|
1165
1123
|
}
|
|
1166
1124
|
// Always merge manual links (survive rebuild)
|
|
1167
1125
|
mergeManualLinks(db, phrenPath);
|
|
1168
1126
|
// ── Finalize: persist hashes, save cache, log ─────────────────────────────
|
|
1169
|
-
saveHashMap(phrenPath, newHashes);
|
|
1127
|
+
saveHashMap(phrenPath, newHashes, new Set(Object.keys(newHashes)));
|
|
1170
1128
|
touchSentinel(phrenPath);
|
|
1171
1129
|
invalidateDfCache();
|
|
1172
1130
|
const buildMs = Date.now() - t0;
|
|
1173
|
-
debugLog(`Built FTS index: ${fileCount} files from ${
|
|
1131
|
+
debugLog(`Built FTS index: ${fileCount} files from ${projectDirs.length} projects in ${buildMs}ms`);
|
|
1174
1132
|
if ((process.env.PHREN_DEBUG))
|
|
1175
|
-
console.error(`Indexed ${fileCount} files from ${
|
|
1133
|
+
console.error(`Indexed ${fileCount} files from ${projectDirs.length} projects`);
|
|
1176
1134
|
appendIndexEvent(phrenPath, {
|
|
1177
1135
|
event: "build_index",
|
|
1178
1136
|
cache: "miss",
|
|
1179
1137
|
hash: hash.slice(0, 12),
|
|
1180
1138
|
files: fileCount,
|
|
1181
|
-
projects:
|
|
1139
|
+
projects: projectDirs.length,
|
|
1182
1140
|
elapsedMs: buildMs,
|
|
1183
1141
|
profile: profile || "",
|
|
1184
1142
|
});
|
|
@@ -1192,8 +1150,7 @@ async function buildIndexImpl(phrenPath, profile) {
|
|
|
1192
1150
|
fs.unlinkSync(path.join(cacheDir, f));
|
|
1193
1151
|
}
|
|
1194
1152
|
catch (err) {
|
|
1195
|
-
|
|
1196
|
-
process.stderr.write(`[phren] buildIndex staleCacheCleanup: ${errorMessage(err)}\n`);
|
|
1153
|
+
logger.debug("buildIndex staleCacheCleanup", errorMessage(err));
|
|
1197
1154
|
}
|
|
1198
1155
|
}
|
|
1199
1156
|
debugLog(`Saved FTS index cache (${hash.slice(0, 8)}) — total ${Date.now() - t0}ms`);
|
|
@@ -1229,8 +1186,7 @@ function isRebuildLockHeld(phrenPath) {
|
|
|
1229
1186
|
return Date.now() - stat.mtimeMs <= staleThreshold;
|
|
1230
1187
|
}
|
|
1231
1188
|
catch (err) {
|
|
1232
|
-
|
|
1233
|
-
process.stderr.write(`[phren] isRebuildLockHeld stat: ${errorMessage(err)}\n`);
|
|
1189
|
+
logger.debug("isRebuildLockHeld stat", errorMessage(err));
|
|
1234
1190
|
return false;
|
|
1235
1191
|
}
|
|
1236
1192
|
}
|
|
@@ -1241,8 +1197,7 @@ async function loadIndexSnapshotOrEmpty(phrenPath, profile) {
|
|
|
1241
1197
|
userSuffix = String(os.userInfo().uid);
|
|
1242
1198
|
}
|
|
1243
1199
|
catch (err) {
|
|
1244
|
-
|
|
1245
|
-
process.stderr.write(`[phren] loadIndexSnapshotOrEmpty userInfo: ${errorMessage(err)}\n`);
|
|
1200
|
+
logger.debug("loadIndexSnapshotOrEmpty userInfo", errorMessage(err));
|
|
1246
1201
|
userSuffix = crypto.createHash("sha1").update(homeDir()).digest("hex").slice(0, 12);
|
|
1247
1202
|
}
|
|
1248
1203
|
const cacheDir = path.join(os.tmpdir(), `phren-fts-${userSuffix}`);
|
|
@@ -1257,7 +1212,27 @@ async function loadIndexSnapshotOrEmpty(phrenPath, profile) {
|
|
|
1257
1212
|
debugLog(`Failed to open cached FTS snapshot while rebuild lock held: ${errorMessage(err)}`);
|
|
1258
1213
|
}
|
|
1259
1214
|
}
|
|
1260
|
-
|
|
1215
|
+
// Before returning empty, try to load any stale-but-usable cache file
|
|
1216
|
+
try {
|
|
1217
|
+
if (fs.existsSync(cacheDir)) {
|
|
1218
|
+
const cacheFiles = fs.readdirSync(cacheDir)
|
|
1219
|
+
.filter(f => f.endsWith(".db"))
|
|
1220
|
+
.map(f => ({ name: f, mtime: fs.statSync(path.join(cacheDir, f)).mtimeMs }))
|
|
1221
|
+
.sort((a, b) => b.mtime - a.mtime);
|
|
1222
|
+
for (const cf of cacheFiles) {
|
|
1223
|
+
try {
|
|
1224
|
+
const staleDb = new SQL.Database(fs.readFileSync(path.join(cacheDir, cf.name)));
|
|
1225
|
+
debugLog(`FTS rebuild in progress; falling back to stale cache: ${cf.name}`);
|
|
1226
|
+
return staleDb;
|
|
1227
|
+
}
|
|
1228
|
+
catch { /* try next */ }
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
catch (err) {
|
|
1233
|
+
debugLog(`Failed to scan stale FTS caches: ${errorMessage(err)}`);
|
|
1234
|
+
}
|
|
1235
|
+
debugLog("FTS rebuild already in progress; no usable cache found, returning empty snapshot");
|
|
1261
1236
|
return createEmptyIndexDb(SQL);
|
|
1262
1237
|
}
|
|
1263
1238
|
// Serialize concurrent in-process buildIndex calls to prevent SQLite corruption
|
|
@@ -1342,8 +1317,7 @@ export function findFtsCacheForPath(phrenPath, profile) {
|
|
|
1342
1317
|
userSuffix = String(os.userInfo().uid);
|
|
1343
1318
|
}
|
|
1344
1319
|
catch (err) {
|
|
1345
|
-
|
|
1346
|
-
process.stderr.write(`[phren] findFtsCacheForPath userInfo: ${errorMessage(err)}\n`);
|
|
1320
|
+
logger.debug("findFtsCacheForPath userInfo", errorMessage(err));
|
|
1347
1321
|
userSuffix = crypto.createHash("sha1").update(homeDir()).digest("hex").slice(0, 12);
|
|
1348
1322
|
}
|
|
1349
1323
|
const cacheDir = path.join(os.tmpdir(), `phren-fts-${userSuffix}`);
|
|
@@ -1357,8 +1331,7 @@ export function findFtsCacheForPath(phrenPath, profile) {
|
|
|
1357
1331
|
}
|
|
1358
1332
|
}
|
|
1359
1333
|
catch (err) {
|
|
1360
|
-
|
|
1361
|
-
process.stderr.write(`[phren] findFtsCacheForPath: ${errorMessage(err)}\n`);
|
|
1334
|
+
logger.debug("findFtsCacheForPath", errorMessage(err));
|
|
1362
1335
|
}
|
|
1363
1336
|
return { exists: false };
|
|
1364
1337
|
}
|