opencode-rag-plugin 1.12.17 → 1.14.6
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/ReadMe.md +15 -13
- package/dist/api.d.ts +54 -2
- package/dist/api.js +41 -1
- package/dist/api.js.map +1 -1
- package/dist/chunker/base.js +2 -0
- package/dist/chunker/base.js.map +1 -1
- package/dist/chunker/bash.d.ts +5 -0
- package/dist/chunker/bash.js +5 -0
- package/dist/chunker/bash.js.map +1 -1
- package/dist/chunker/c.d.ts +6 -0
- package/dist/chunker/c.js +6 -0
- package/dist/chunker/c.js.map +1 -1
- package/dist/chunker/cpp.d.ts +6 -0
- package/dist/chunker/cpp.js +6 -0
- package/dist/chunker/cpp.js.map +1 -1
- package/dist/chunker/csharp.d.ts +6 -0
- package/dist/chunker/csharp.js +6 -0
- package/dist/chunker/csharp.js.map +1 -1
- package/dist/chunker/css.d.ts +6 -0
- package/dist/chunker/css.js +6 -0
- package/dist/chunker/css.js.map +1 -1
- package/dist/chunker/doc.d.ts +18 -0
- package/dist/chunker/doc.js +18 -0
- package/dist/chunker/doc.js.map +1 -1
- package/dist/chunker/dockerfile.d.ts +6 -0
- package/dist/chunker/dockerfile.js +6 -0
- package/dist/chunker/dockerfile.js.map +1 -1
- package/dist/chunker/docx.d.ts +18 -0
- package/dist/chunker/docx.js +18 -0
- package/dist/chunker/docx.js.map +1 -1
- package/dist/chunker/excel.d.ts +19 -0
- package/dist/chunker/excel.js +19 -0
- package/dist/chunker/excel.js.map +1 -1
- package/dist/chunker/go.d.ts +5 -0
- package/dist/chunker/go.js +5 -0
- package/dist/chunker/go.js.map +1 -1
- package/dist/chunker/grammar.d.ts +62 -0
- package/dist/chunker/grammar.js +133 -0
- package/dist/chunker/grammar.js.map +1 -1
- package/dist/chunker/html.d.ts +5 -0
- package/dist/chunker/html.js +5 -0
- package/dist/chunker/html.js.map +1 -1
- package/dist/chunker/image.d.ts +31 -0
- package/dist/chunker/image.js +50 -4
- package/dist/chunker/image.js.map +1 -1
- package/dist/chunker/ini.d.ts +5 -0
- package/dist/chunker/ini.js +5 -0
- package/dist/chunker/ini.js.map +1 -1
- package/dist/chunker/java.d.ts +5 -0
- package/dist/chunker/java.js +5 -0
- package/dist/chunker/java.js.map +1 -1
- package/dist/chunker/javascript.d.ts +6 -0
- package/dist/chunker/javascript.js +6 -0
- package/dist/chunker/javascript.js.map +1 -1
- package/dist/chunker/json.d.ts +5 -0
- package/dist/chunker/json.js +5 -0
- package/dist/chunker/json.js.map +1 -1
- package/dist/chunker/kotlin.d.ts +6 -0
- package/dist/chunker/kotlin.js +6 -0
- package/dist/chunker/kotlin.js.map +1 -1
- package/dist/chunker/loader.d.ts +11 -0
- package/dist/chunker/loader.js +20 -0
- package/dist/chunker/loader.js.map +1 -1
- package/dist/chunker/markdown.d.ts +5 -0
- package/dist/chunker/markdown.js +5 -0
- package/dist/chunker/markdown.js.map +1 -1
- package/dist/chunker/pdf.d.ts +19 -0
- package/dist/chunker/pdf.js +30 -0
- package/dist/chunker/pdf.js.map +1 -1
- package/dist/chunker/php.d.ts +5 -0
- package/dist/chunker/php.js +5 -0
- package/dist/chunker/php.js.map +1 -1
- package/dist/chunker/powershell.d.ts +5 -0
- package/dist/chunker/powershell.js +5 -0
- package/dist/chunker/powershell.js.map +1 -1
- package/dist/chunker/python.d.ts +5 -0
- package/dist/chunker/python.js +5 -0
- package/dist/chunker/python.js.map +1 -1
- package/dist/chunker/razor.d.ts +12 -0
- package/dist/chunker/razor.js +25 -0
- package/dist/chunker/razor.js.map +1 -1
- package/dist/chunker/ruby.d.ts +5 -0
- package/dist/chunker/ruby.js +5 -0
- package/dist/chunker/ruby.js.map +1 -1
- package/dist/chunker/rust.d.ts +6 -0
- package/dist/chunker/rust.js +6 -0
- package/dist/chunker/rust.js.map +1 -1
- package/dist/chunker/sln.d.ts +11 -0
- package/dist/chunker/sln.js +11 -0
- package/dist/chunker/sln.js.map +1 -1
- package/dist/chunker/sql.d.ts +5 -0
- package/dist/chunker/sql.js +5 -0
- package/dist/chunker/sql.js.map +1 -1
- package/dist/chunker/ssl.d.ts +12 -0
- package/dist/chunker/ssl.js +21 -2
- package/dist/chunker/ssl.js.map +1 -1
- package/dist/chunker/swift.d.ts +5 -0
- package/dist/chunker/swift.js +5 -0
- package/dist/chunker/swift.js.map +1 -1
- package/dist/chunker/tex.d.ts +12 -0
- package/dist/chunker/tex.js +12 -0
- package/dist/chunker/tex.js.map +1 -1
- package/dist/chunker/toml.d.ts +5 -0
- package/dist/chunker/toml.js +5 -0
- package/dist/chunker/toml.js.map +1 -1
- package/dist/chunker/typescript.d.ts +6 -0
- package/dist/chunker/typescript.js +6 -0
- package/dist/chunker/typescript.js.map +1 -1
- package/dist/chunker/xml.d.ts +5 -0
- package/dist/chunker/xml.js +5 -0
- package/dist/chunker/xml.js.map +1 -1
- package/dist/chunker/yaml.d.ts +5 -0
- package/dist/chunker/yaml.js +5 -0
- package/dist/chunker/yaml.js.map +1 -1
- package/dist/cli/commands/clear.d.ts +12 -0
- package/dist/cli/commands/clear.js +81 -0
- package/dist/cli/commands/clear.js.map +1 -0
- package/dist/cli/commands/describe-image.d.ts +14 -0
- package/dist/cli/commands/describe-image.js +68 -0
- package/dist/cli/commands/describe-image.js.map +1 -0
- package/dist/cli/commands/dump.d.ts +13 -0
- package/dist/cli/commands/dump.js +55 -0
- package/dist/cli/commands/dump.js.map +1 -0
- package/dist/cli/commands/eval.d.ts +32 -0
- package/dist/cli/commands/eval.js +158 -0
- package/dist/cli/commands/eval.js.map +1 -0
- package/dist/cli/commands/index-command.d.ts +17 -0
- package/dist/cli/commands/index-command.js +167 -0
- package/dist/cli/commands/index-command.js.map +1 -0
- package/dist/cli/commands/index.d.ts +16 -0
- package/dist/cli/commands/index.js +17 -0
- package/dist/cli/commands/index.js.map +1 -0
- package/dist/cli/commands/init-helpers.d.ts +101 -0
- package/dist/cli/commands/init-helpers.js +397 -0
- package/dist/cli/commands/init-helpers.js.map +1 -0
- package/dist/cli/commands/init.d.ts +19 -0
- package/dist/cli/commands/init.js +251 -0
- package/dist/cli/commands/init.js.map +1 -0
- package/dist/cli/commands/list.d.ts +13 -0
- package/dist/cli/commands/list.js +46 -0
- package/dist/cli/commands/list.js.map +1 -0
- package/dist/cli/commands/mcp.d.ts +14 -0
- package/dist/cli/commands/mcp.js +35 -0
- package/dist/cli/commands/mcp.js.map +1 -0
- package/dist/cli/commands/query.d.ts +14 -0
- package/dist/cli/commands/query.js +86 -0
- package/dist/cli/commands/query.js.map +1 -0
- package/dist/cli/commands/show.d.ts +13 -0
- package/dist/cli/commands/show.js +51 -0
- package/dist/cli/commands/show.js.map +1 -0
- package/dist/cli/commands/status.d.ts +14 -0
- package/dist/cli/commands/status.js +104 -0
- package/dist/cli/commands/status.js.map +1 -0
- package/dist/cli/commands/ui.d.ts +14 -0
- package/dist/cli/commands/ui.js +69 -0
- package/dist/cli/commands/ui.js.map +1 -0
- package/dist/cli/commands/update.d.ts +14 -0
- package/dist/cli/commands/update.js +76 -0
- package/dist/cli/commands/update.js.map +1 -0
- package/dist/cli/format.d.ts +112 -0
- package/dist/cli/format.js +184 -0
- package/dist/cli/format.js.map +1 -0
- package/dist/cli/helpers.d.ts +51 -0
- package/dist/cli/helpers.js +86 -0
- package/dist/cli/helpers.js.map +1 -0
- package/dist/cli/index.d.ts +28 -0
- package/dist/cli/index.js +104 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/progress.d.ts +39 -0
- package/dist/cli/progress.js +137 -0
- package/dist/cli/progress.js.map +1 -0
- package/dist/cli/types.d.ts +40 -0
- package/dist/cli/types.js +3 -0
- package/dist/cli/types.js.map +1 -0
- package/dist/cli.d.ts +11 -4
- package/dist/cli.js +9 -1253
- package/dist/cli.js.map +1 -1
- package/dist/content/doc.d.ts +1 -1
- package/dist/content/doc.js +1 -1
- package/dist/content/doc.js.map +1 -1
- package/dist/content/docx.d.ts +1 -1
- package/dist/content/docx.js +1 -1
- package/dist/content/docx.js.map +1 -1
- package/dist/content/excel.d.ts +1 -1
- package/dist/content/excel.js +1 -1
- package/dist/content/excel.js.map +1 -1
- package/dist/content/pdf.d.ts +1 -1
- package/dist/content/pdf.js +1 -1
- package/dist/content/pdf.js.map +1 -1
- package/dist/content/reader.d.ts +3 -1
- package/dist/content/reader.js +42 -10
- package/dist/content/reader.js.map +1 -1
- package/dist/core/bootstrap.d.ts +18 -0
- package/dist/core/bootstrap.js +9 -15
- package/dist/core/bootstrap.js.map +1 -1
- package/dist/core/config.d.ts +144 -0
- package/dist/core/config.js +65 -14
- package/dist/core/config.js.map +1 -1
- package/dist/core/doc-progress.d.ts +15 -0
- package/dist/core/doc-progress.js +61 -0
- package/dist/core/doc-progress.js.map +1 -0
- package/dist/core/fileLogger.d.ts +7 -0
- package/dist/core/fileLogger.js +2 -0
- package/dist/core/fileLogger.js.map +1 -1
- package/dist/core/interfaces.d.ts +52 -1
- package/dist/core/manifest.d.ts +25 -0
- package/dist/core/manifest.js +15 -1
- package/dist/core/manifest.js.map +1 -1
- package/dist/core/provider-defaults.d.ts +8 -0
- package/dist/core/provider-defaults.js +3 -0
- package/dist/core/provider-defaults.js.map +1 -1
- package/dist/core/rag-injection-flag.d.ts +3 -0
- package/dist/core/rag-injection-flag.js +2 -0
- package/dist/core/rag-injection-flag.js.map +1 -1
- package/dist/core/resolve-api-key.d.ts +1 -0
- package/dist/core/resolve-api-key.js +1 -0
- package/dist/core/resolve-api-key.js.map +1 -1
- package/dist/core/runtime-overrides.d.ts +4 -0
- package/dist/core/runtime-overrides.js +4 -1
- package/dist/core/runtime-overrides.js.map +1 -1
- package/dist/describer/anthropic.d.ts +22 -2
- package/dist/describer/anthropic.js +42 -110
- package/dist/describer/anthropic.js.map +1 -1
- package/dist/describer/describer.d.ts +21 -4
- package/dist/describer/describer.js +52 -111
- package/dist/describer/describer.js.map +1 -1
- package/dist/describer/gemini.d.ts +1 -2
- package/dist/describer/gemini.js +20 -113
- package/dist/describer/gemini.js.map +1 -1
- package/dist/describer/shared.d.ts +3 -0
- package/dist/describer/shared.js +19 -0
- package/dist/describer/shared.js.map +1 -0
- package/dist/embedder/factory.d.ts +25 -0
- package/dist/embedder/factory.js +25 -0
- package/dist/embedder/factory.js.map +1 -1
- package/dist/embedder/health.d.ts +10 -0
- package/dist/embedder/health.js +26 -14
- package/dist/embedder/health.js.map +1 -1
- package/dist/embedder/http.d.ts +47 -0
- package/dist/embedder/http.js +61 -3
- package/dist/embedder/http.js.map +1 -1
- package/dist/eval/index.d.ts +6 -0
- package/dist/eval/index.js.map +1 -1
- package/dist/eval/run-token-test.js +1 -5
- package/dist/eval/run-token-test.js.map +1 -1
- package/dist/eval/session-logger.d.ts +5 -0
- package/dist/eval/session-logger.js +4 -0
- package/dist/eval/session-logger.js.map +1 -1
- package/dist/eval/storage.d.ts +9 -0
- package/dist/eval/storage.js +9 -5
- package/dist/eval/storage.js.map +1 -1
- package/dist/eval/token-analysis.d.ts +8 -0
- package/dist/eval/token-analysis.js +6 -2
- package/dist/eval/token-analysis.js.map +1 -1
- package/dist/eval/token-counter.d.ts +1 -3
- package/dist/eval/token-counter.js +4 -5
- package/dist/eval/token-counter.js.map +1 -1
- package/dist/eval/types.d.ts +6 -0
- package/dist/eval/types.js +2 -0
- package/dist/eval/types.js.map +1 -1
- package/dist/index.d.ts +11 -0
- package/dist/index.js +12 -3
- package/dist/index.js.map +1 -1
- package/dist/indexer/description-stage.js +11 -2
- package/dist/indexer/description-stage.js.map +1 -1
- package/dist/indexer/embed-stage.js +0 -8
- package/dist/indexer/embed-stage.js.map +1 -1
- package/dist/indexer/pipeline.d.ts +27 -3
- package/dist/indexer/pipeline.js +314 -143
- package/dist/indexer/pipeline.js.map +1 -1
- package/dist/indexer/stats.d.ts +35 -0
- package/dist/indexer/stats.js +9 -0
- package/dist/indexer/stats.js.map +1 -1
- package/dist/indexer/watch.d.ts +25 -0
- package/dist/indexer/watch.js +21 -0
- package/dist/indexer/watch.js.map +1 -1
- package/dist/indexer/worker.d.ts +90 -2
- package/dist/indexer/worker.js +109 -24
- package/dist/indexer/worker.js.map +1 -1
- package/dist/indexer.d.ts +11 -1
- package/dist/indexer.js +9 -0
- package/dist/indexer.js.map +1 -1
- package/dist/mcp/cli.d.ts +3 -0
- package/dist/mcp/cli.js +1 -0
- package/dist/mcp/cli.js.map +1 -1
- package/dist/mcp/handlers.d.ts +41 -0
- package/dist/mcp/handlers.js +40 -1
- package/dist/mcp/handlers.js.map +1 -1
- package/dist/mcp/server.d.ts +8 -0
- package/dist/mcp/server.js +18 -1
- package/dist/mcp/server.js.map +1 -1
- package/dist/opencode/create-read-tool.js +1 -0
- package/dist/opencode/create-read-tool.js.map +1 -1
- package/dist/opencode/tools.d.ts +8 -17
- package/dist/opencode/tools.js +74 -109
- package/dist/opencode/tools.js.map +1 -1
- package/dist/plugin-entry.d.ts +6 -0
- package/dist/plugin-entry.js +6 -0
- package/dist/plugin-entry.js.map +1 -1
- package/dist/plugin.d.ts +37 -0
- package/dist/plugin.js +265 -40
- package/dist/plugin.js.map +1 -1
- package/dist/retriever/keyword-index.js +9 -9
- package/dist/retriever/keyword-index.js.map +1 -1
- package/dist/retriever/retriever.js +14 -4
- package/dist/retriever/retriever.js.map +1 -1
- package/dist/tui.d.ts +5 -0
- package/dist/tui.js +100 -12
- package/dist/tui.js.map +1 -1
- package/dist/types/opencode-plugin.d.ts +4 -1
- package/dist/updater.d.ts +23 -1
- package/dist/updater.js +55 -16
- package/dist/updater.js.map +1 -1
- package/dist/vectorstore/factory.d.ts +13 -0
- package/dist/vectorstore/factory.js +13 -0
- package/dist/vectorstore/factory.js.map +1 -1
- package/dist/vectorstore/lancedb.d.ts +78 -1
- package/dist/vectorstore/lancedb.js +195 -21
- package/dist/vectorstore/lancedb.js.map +1 -1
- package/dist/vectorstore/memory.d.ts +5 -0
- package/dist/vectorstore/memory.js +11 -0
- package/dist/vectorstore/memory.js.map +1 -1
- package/dist/watcher.d.ts +24 -0
- package/dist/watcher.js +17 -35
- package/dist/watcher.js.map +1 -1
- package/dist/web/api.d.ts +37 -0
- package/dist/web/api.js +54 -0
- package/dist/web/api.js.map +1 -1
- package/dist/web/server.d.ts +15 -0
- package/dist/web/server.js +14 -0
- package/dist/web/server.js.map +1 -1
- package/dist/web/static.d.ts +8 -0
- package/dist/web/static.js +8 -0
- package/dist/web/static.js.map +1 -1
- package/package.json +11 -15
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"embed-stage.js","sourceRoot":"","sources":["../../src/indexer/embed-stage.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"embed-stage.js","sourceRoot":"","sources":["../../src/indexer/embed-stage.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;AASpD,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,EAChC,MAAM,EACN,QAAQ,EACR,SAAS,EACT,WAAW,GACQ;IACnB,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAEnC,MAAM,YAAY,GAAa,EAAE,CAAC;IAClC,MAAM,YAAY,GAAa,EAAE,CAAC;IAElC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACvC,MAAM,KAAK,GAAG,MAAM,CAAC,CAAC,CAAE,CAAC;QACzB,IAAI,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC;YAAE,SAAS;QAC5D,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;QACjC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACvB,CAAC;IAED,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC5B,MAAM,UAAU,GAAG,MAAM,UAAU,CAAC,QAAQ,EAAE,YAAY,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,CAAC,CAAC;QAEhG,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,YAAY,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YAC7C,MAAM,QAAQ,GAAG,YAAY,CAAC,CAAC,CAAE,CAAC;YAClC,MAAM,GAAG,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;YAC1B,IAAI,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,OAAO,GAAG,CAAC,CAAC,CAAC,KAAK,QAAQ,EAAE,CAAC;gBACvE,MAAM,CAAC,QAAQ,CAAE,CAAC,SAAS,GAAG,GAAe,CAAC;YAChD,CAAC;QACH,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -1,23 +1,47 @@
|
|
|
1
1
|
import type { RagConfig } from "../core/config.js";
|
|
2
|
-
import type { DescriptionProvider, EmbeddingProvider, KeywordIndex, VectorStore } from "../core/interfaces.js";
|
|
2
|
+
import type { DescriptionProvider, EmbeddingProvider, IndexProgress, KeywordIndex, VectorStore } from "../core/interfaces.js";
|
|
3
3
|
import { type IndexRunStats, type IndexStatusSummary } from "./stats.js";
|
|
4
4
|
export type { WatchPassScheduler } from "./watch.js";
|
|
5
5
|
export { createWatchPassScheduler, createWatchIgnore } from "./watch.js";
|
|
6
|
+
/** Options for configuring a single index pass. */
|
|
6
7
|
export interface RunIndexPassOptions {
|
|
8
|
+
/** Workspace root directory. */
|
|
7
9
|
cwd: string;
|
|
10
|
+
/** Path to the vector store data directory. */
|
|
8
11
|
storePath: string;
|
|
12
|
+
/** Full RAG configuration for the workspace. */
|
|
9
13
|
config: RagConfig;
|
|
14
|
+
/** Vector store instance for persisting chunks. */
|
|
10
15
|
store: VectorStore;
|
|
16
|
+
/** Embedding provider for generating vector representations. */
|
|
11
17
|
embedder: EmbeddingProvider;
|
|
18
|
+
/** When true, ignore the existing manifest and re-index everything. */
|
|
12
19
|
force?: boolean;
|
|
20
|
+
/** Optional partial logger (missing methods default to no-ops). */
|
|
13
21
|
logger?: Partial<Logger>;
|
|
22
|
+
/** Optional keyword index to populate during the pass. */
|
|
14
23
|
keywordIndex?: KeywordIndex;
|
|
24
|
+
/** Optional provider for AI-generated chunk descriptions. */
|
|
15
25
|
descriptionProvider?: DescriptionProvider;
|
|
26
|
+
/** Optional progress tracker for reporting file-level progress. */
|
|
27
|
+
progress?: IndexProgress;
|
|
28
|
+
/** Optional abort signal – when fired, the pass finishes the current file then stops. */
|
|
29
|
+
abortSignal?: AbortSignal;
|
|
30
|
+
/** Embedding vector dimension — needed when creating a temporary store for atomic rebuilds. */
|
|
31
|
+
dimension?: number;
|
|
16
32
|
}
|
|
17
|
-
interface Logger {
|
|
33
|
+
export interface Logger {
|
|
18
34
|
info(message: string): void;
|
|
19
35
|
warn(message: string): void;
|
|
20
36
|
debug(message: string): void;
|
|
21
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* Execute a full index pass: scan workspace, prepare files, generate embeddings
|
|
40
|
+
* (and optionally AI descriptions), and store results into the vector store.
|
|
41
|
+
* Uses a lock file to prevent concurrent passes.
|
|
42
|
+
*
|
|
43
|
+
* @param options - Configuration for the index pass.
|
|
44
|
+
* @returns Aggregate statistics for the pass.
|
|
45
|
+
*/
|
|
22
46
|
export declare function runIndexPass(options: RunIndexPassOptions): Promise<IndexRunStats>;
|
|
23
|
-
export declare function getIndexStatusSummary(cwd: string, storePath: string, config: RagConfig, store: VectorStore): Promise<IndexStatusSummary>;
|
|
47
|
+
export declare function getIndexStatusSummary(cwd: string, storePath: string, config: RagConfig, store: VectorStore, skipScan?: boolean): Promise<IndexStatusSummary>;
|
package/dist/indexer/pipeline.js
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
1
2
|
import path from "node:path";
|
|
2
3
|
import pLimit from "p-limit";
|
|
3
4
|
import { scanWorkspaceFiles } from "../content/reader.js";
|
|
4
5
|
import { loadManifest, saveManifest, normalizeFilePath } from "../core/manifest.js";
|
|
5
6
|
import { embedBatch } from "../embedder/factory.js";
|
|
7
|
+
import { createVectorStore } from "../vectorstore/factory.js";
|
|
8
|
+
import { swapStoreDirectories } from "../vectorstore/lancedb.js";
|
|
6
9
|
import { createIndexStats } from "./stats.js";
|
|
7
|
-
import { prepareFile } from "./worker.js";
|
|
10
|
+
import { prepareFile, buildTextsToEmbed, storeFileChunks } from "./worker.js";
|
|
8
11
|
import { getCurrentCommit, getChangedFilesSince, getUntrackedFiles, getRepoRoot } from "./git-diff.js";
|
|
9
12
|
export { createWatchPassScheduler, createWatchIgnore } from "./watch.js";
|
|
10
13
|
function createLogger(logger) {
|
|
@@ -29,17 +32,77 @@ function tryUpdateLastGitCommit(cwd, manifest) {
|
|
|
29
32
|
return false;
|
|
30
33
|
}
|
|
31
34
|
}
|
|
35
|
+
const LOCK_FILE = "index.lock";
|
|
36
|
+
const LOCK_MAX_AGE_MS = 5 * 60 * 1000;
|
|
37
|
+
function isPidAlive(pid) {
|
|
38
|
+
try {
|
|
39
|
+
process.kill(pid, 0);
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return false;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Execute a full index pass: scan workspace, prepare files, generate embeddings
|
|
48
|
+
* (and optionally AI descriptions), and store results into the vector store.
|
|
49
|
+
* Uses a lock file to prevent concurrent passes.
|
|
50
|
+
*
|
|
51
|
+
* @param options - Configuration for the index pass.
|
|
52
|
+
* @returns Aggregate statistics for the pass.
|
|
53
|
+
*/
|
|
32
54
|
export async function runIndexPass(options) {
|
|
33
55
|
const logger = createLogger(options.logger);
|
|
56
|
+
const lockPath = path.join(options.storePath, LOCK_FILE);
|
|
57
|
+
try {
|
|
58
|
+
const raw = await fs.readFile(lockPath, "utf-8");
|
|
59
|
+
const lock = JSON.parse(raw);
|
|
60
|
+
const age = lock.startedAt ? Date.now() - lock.startedAt : Infinity;
|
|
61
|
+
if (lock.pid && !isPidAlive(lock.pid)) {
|
|
62
|
+
logger.debug(`Stale lock from dead process ${lock.pid} — continuing`);
|
|
63
|
+
}
|
|
64
|
+
else if (lock.startedAt && age < LOCK_MAX_AGE_MS) {
|
|
65
|
+
logger.warn(`Another index pass is running (PID ${lock.pid ?? "unknown"}). Skipping.`);
|
|
66
|
+
return createIndexStats(0, "missing");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
// No lock file — proceed
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
await fs.writeFile(lockPath, JSON.stringify({ pid: process.pid, startedAt: Date.now() }), "utf-8");
|
|
74
|
+
}
|
|
75
|
+
catch {
|
|
76
|
+
// Best-effort lock
|
|
77
|
+
}
|
|
78
|
+
try {
|
|
79
|
+
return await runIndexPassInner(options, logger);
|
|
80
|
+
}
|
|
81
|
+
finally {
|
|
82
|
+
try {
|
|
83
|
+
await fs.unlink(lockPath);
|
|
84
|
+
}
|
|
85
|
+
catch { }
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
async function runIndexPassInner(options, logger) {
|
|
34
89
|
const loadResult = await loadManifest(options.storePath);
|
|
35
90
|
const manifest = loadResult.manifest;
|
|
36
91
|
let manifestStatus = loadResult.status;
|
|
37
92
|
let rebuildPerformed = false;
|
|
38
|
-
logger.
|
|
93
|
+
logger.info(`Manifest loaded: ${manifestStatus}, ${Object.keys(manifest.files).length} entries`);
|
|
39
94
|
if (options.force) {
|
|
40
95
|
manifestStatus = "missing";
|
|
41
96
|
logger.debug("Force mode: ignoring manifest");
|
|
42
97
|
}
|
|
98
|
+
// Clear files that had description failures so they are fully re-indexed
|
|
99
|
+
const descFailedPaths = Object.keys(manifest.files).filter((p) => manifest.files[p]?.descriptionFailed);
|
|
100
|
+
if (descFailedPaths.length > 0) {
|
|
101
|
+
logger.info(` ${descFailedPaths.length} file(s) marked as description-failed — re-indexing`);
|
|
102
|
+
for (const p of descFailedPaths) {
|
|
103
|
+
delete manifest.files[p];
|
|
104
|
+
}
|
|
105
|
+
}
|
|
43
106
|
let filterPaths;
|
|
44
107
|
let gitDeletedPaths = [];
|
|
45
108
|
if (!options.force && manifestStatus === "ok" && manifest.lastGitCommit) {
|
|
@@ -59,8 +122,10 @@ export async function runIndexPass(options) {
|
|
|
59
122
|
}
|
|
60
123
|
}
|
|
61
124
|
}
|
|
125
|
+
const scanStart = Date.now();
|
|
62
126
|
const workspaceFiles = await scanWorkspaceFiles(options.cwd, options.config, logger, options.force ? undefined : manifest, filterPaths);
|
|
63
|
-
|
|
127
|
+
const scanSec = ((Date.now() - scanStart) / 1000).toFixed(1);
|
|
128
|
+
logger.info(`Workspace scan complete: ${workspaceFiles.length} files in ${scanSec}s`);
|
|
64
129
|
const existingCount = await options.store.count();
|
|
65
130
|
// Detect data loss: if the store has far fewer chunks than the manifest expects,
|
|
66
131
|
// treat it as a corrupt store (e.g. schema migration dropped the old table).
|
|
@@ -76,8 +141,10 @@ export async function runIndexPass(options) {
|
|
|
76
141
|
manifestStatus = "missing";
|
|
77
142
|
}
|
|
78
143
|
}
|
|
144
|
+
// Effective store used throughout the pass — may be a temp store for atomic rebuild.
|
|
145
|
+
let effectiveStore = options.store;
|
|
146
|
+
let tempStorePath;
|
|
79
147
|
if (options.force || (manifestStatus !== "ok" && existingCount > 0)) {
|
|
80
|
-
await options.store.clear();
|
|
81
148
|
options.keywordIndex?.clear();
|
|
82
149
|
for (const key of Object.keys(manifest.files)) {
|
|
83
150
|
delete manifest.files[key];
|
|
@@ -88,6 +155,23 @@ export async function runIndexPass(options) {
|
|
|
88
155
|
logger.warn("Manifest missing or corrupt; rebuilding full index.");
|
|
89
156
|
}
|
|
90
157
|
manifestStatus = options.force ? "missing" : manifestStatus;
|
|
158
|
+
// Build the new index into a temporary store first, then atomically
|
|
159
|
+
// swap on completion. Original data stays untouched if the process
|
|
160
|
+
// is aborted (Ctrl+C, crash) before the swap completes.
|
|
161
|
+
if (options.dimension) {
|
|
162
|
+
tempStorePath = options.storePath + "_tmp";
|
|
163
|
+
try {
|
|
164
|
+
await fs.rm(tempStorePath, { recursive: true, force: true });
|
|
165
|
+
}
|
|
166
|
+
catch { /* may not exist */ }
|
|
167
|
+
effectiveStore = createVectorStore(options.config, tempStorePath, options.dimension);
|
|
168
|
+
logger.debug(`Rebuilding index in temporary store at ${tempStorePath}`);
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
// Fallback — in-memory store or no dimension: clear in place.
|
|
172
|
+
logger.warn("No embedding dimension available; falling back to in-place clear.");
|
|
173
|
+
await options.store.clear();
|
|
174
|
+
}
|
|
91
175
|
}
|
|
92
176
|
const stats = createIndexStats(workspaceFiles.length, manifestStatus);
|
|
93
177
|
stats.rebuildPerformed = rebuildPerformed;
|
|
@@ -103,7 +187,8 @@ export async function runIndexPass(options) {
|
|
|
103
187
|
const currentPaths = new Set(workspaceFiles.map((file) => file.normalizedPath));
|
|
104
188
|
let stalePaths;
|
|
105
189
|
if (filterPaths) {
|
|
106
|
-
|
|
190
|
+
const repoRoot = getRepoRoot(options.cwd) ?? options.cwd;
|
|
191
|
+
stalePaths = gitDeletedPaths.map((p) => normalizeFilePath(path.resolve(repoRoot, p)));
|
|
107
192
|
}
|
|
108
193
|
else {
|
|
109
194
|
stalePaths = Object.keys(manifest.files).filter((p) => !currentPaths.has(p));
|
|
@@ -112,7 +197,7 @@ export async function runIndexPass(options) {
|
|
|
112
197
|
logger.debug(`Removing ${stalePaths.length} stale files from index...`);
|
|
113
198
|
const deleteLimit = pLimit(options.config.indexing.concurrency);
|
|
114
199
|
await Promise.all(stalePaths.map((p) => deleteLimit(async () => {
|
|
115
|
-
await
|
|
200
|
+
await effectiveStore.deleteByFilePath(p);
|
|
116
201
|
options.keywordIndex?.removeByFilePath(p);
|
|
117
202
|
delete manifest.files[p];
|
|
118
203
|
stats.deletedFiles++;
|
|
@@ -136,143 +221,236 @@ export async function runIndexPass(options) {
|
|
|
136
221
|
}
|
|
137
222
|
}
|
|
138
223
|
if (stats.deletedFiles > 0) {
|
|
139
|
-
logger.
|
|
224
|
+
logger.debug(`Removed ${stats.deletedFiles} deleted files from index.`);
|
|
140
225
|
}
|
|
141
|
-
logger.
|
|
142
|
-
// ── Phase 1: prepare all files (chunk +
|
|
226
|
+
logger.debug(`Processing ${workspaceFiles.length} files (${newCount} new, ${modifiedCount} modified, ${unchangedCount} unchanged)...`);
|
|
227
|
+
// ── Phase 1: prepare all files (chunk + keyword, defer descriptions if provider present) ──
|
|
143
228
|
const limit = pLimit(options.config.indexing.concurrency);
|
|
144
|
-
|
|
145
|
-
let started = 0;
|
|
146
|
-
const total = workspaceFiles.length;
|
|
229
|
+
const deferDescriptions = !!options.descriptionProvider;
|
|
147
230
|
const prepared = await Promise.all(workspaceFiles.map((file) => limit(async () => {
|
|
148
|
-
const fileLabel = file.normalizedPath;
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
if (total > 20 && completed % 50 === 0) {
|
|
154
|
-
logger.info(` Preparing file ${completed}/${total}...`);
|
|
231
|
+
const fileLabel = path.relative(options.cwd, file.normalizedPath).replace(/\\/g, "/");
|
|
232
|
+
const isActive = !file.isEmpty && !file.isTooSmall &&
|
|
233
|
+
(!manifest.files[file.normalizedPath] || manifest.files[file.normalizedPath].hash !== file.hash);
|
|
234
|
+
if (isActive) {
|
|
235
|
+
options.progress?.startFile(fileLabel);
|
|
155
236
|
}
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
const deletionLimit = pLimit(options.config.indexing.concurrency);
|
|
160
|
-
await Promise.all(prepared.map((prep) => deletionLimit(async () => {
|
|
161
|
-
if (prep.earlyResult?.isRemoved) {
|
|
162
|
-
await options.store.deleteByFilePath(prep.normalizedPath);
|
|
163
|
-
options.keywordIndex?.removeByFilePath(prep.normalizedPath);
|
|
164
|
-
return;
|
|
165
|
-
}
|
|
166
|
-
if (prep.isModified && prep.chunks && prep.chunks.length > 0) {
|
|
167
|
-
await options.store.deleteByFilePath(prep.normalizedPath);
|
|
168
|
-
options.keywordIndex?.removeByFilePath(prep.normalizedPath);
|
|
237
|
+
const prep = await prepareFile(file, options.cwd, manifest.files[file.normalizedPath], options.config, options.keywordIndex, options.descriptionProvider, logger, deferDescriptions);
|
|
238
|
+
if (prep.earlyResult && isActive) {
|
|
239
|
+
options.progress?.finishFile(fileLabel);
|
|
169
240
|
}
|
|
241
|
+
return prep;
|
|
170
242
|
})));
|
|
171
|
-
// ── Phase
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
243
|
+
// ── Phase 1.5: global description generation (single pool, no nested concurrency) ──
|
|
244
|
+
if (deferDescriptions) {
|
|
245
|
+
const deferredPreps = prepared.filter((p) => p.chunks && p.chunks.length > 0 && p.relPath !== undefined);
|
|
246
|
+
if (deferredPreps.length > 0) {
|
|
247
|
+
const allChunks = [];
|
|
248
|
+
for (const prep of deferredPreps) {
|
|
249
|
+
for (const chunk of prep.chunks) {
|
|
250
|
+
if (chunk.metadata.contentType !== "image") {
|
|
251
|
+
allChunks.push(chunk);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
// Advance progress to Description stage before descriptions start
|
|
256
|
+
for (const prep of deferredPreps) {
|
|
257
|
+
options.progress?.finishStage(prep.fileLabel);
|
|
258
|
+
}
|
|
259
|
+
if (allChunks.length > 0) {
|
|
260
|
+
try {
|
|
261
|
+
const batchResult = await options.descriptionProvider.generateBatchDescriptions(allChunks, (msg) => logger.debug(msg));
|
|
262
|
+
for (const chunk of allChunks) {
|
|
263
|
+
const desc = batchResult.get(chunk.id);
|
|
264
|
+
if (desc && desc.trim().length > 0) {
|
|
265
|
+
chunk.description = desc;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
catch (err) {
|
|
270
|
+
logger.warn(` Global description generation failed: ${err.message}`);
|
|
271
|
+
for (const prep of deferredPreps) {
|
|
272
|
+
if (prep.chunks.some((c) => c.metadata.contentType !== "image")) {
|
|
273
|
+
prep.descriptionFailed = true;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
// Phase 1.6: build textToEmbed with descriptions injected
|
|
279
|
+
for (const prep of deferredPreps) {
|
|
280
|
+
prep.textToEmbed = buildTextsToEmbed(prep.chunks, prep.relPath, prep.metaHeader ?? "", prep.docPrefix ?? "", prep.isImageFile ?? false);
|
|
180
281
|
}
|
|
181
282
|
}
|
|
182
283
|
}
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
284
|
+
// ── Phase 2+3: per-file delete, embed + store (in parallel) ──
|
|
285
|
+
// Deletions for modified/re-removed files happen per-worker, right before
|
|
286
|
+
// embedding, so that an abort mid-embed doesn't orphan old entries.
|
|
287
|
+
const isOllama = options.embedder.name === "ollama";
|
|
288
|
+
const ollamaMaxBatch = options.config.indexing.ollamaMaxBatchSize ?? 4000;
|
|
289
|
+
const defaultBatchSize = options.config.indexing.embedBatchSize;
|
|
290
|
+
const defaultConcurrency = options.config.indexing.embedConcurrency ?? 1;
|
|
291
|
+
// File metadata look-up for manifest entries
|
|
292
|
+
const fileMeta = new Map(workspaceFiles.map((f) => [f.normalizedPath, { mtime: f.mtime, size: f.size }]));
|
|
293
|
+
// Serialised manifest-save queue — prevents concurrent write races and acts
|
|
294
|
+
// as a checkpoint for Ctrl+C resilience. Each worker appends to this chain
|
|
295
|
+
// after a successful store, so previously completed files are never lost.
|
|
296
|
+
let manifestSaveChain = Promise.resolve(undefined);
|
|
297
|
+
function enqueueManifestSave() {
|
|
298
|
+
manifestSaveChain = manifestSaveChain.then(() => saveManifest(options.storePath, manifest));
|
|
189
299
|
}
|
|
190
|
-
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
embedByPrep.set(prepIdx, []);
|
|
197
|
-
}
|
|
198
|
-
if (emb) {
|
|
199
|
-
embedByPrep.get(prepIdx).push(emb);
|
|
300
|
+
const aborted = () => options.abortSignal?.aborted ?? false;
|
|
301
|
+
const embedStoreLimit = pLimit(options.config.indexing.concurrency);
|
|
302
|
+
const workerResults = await Promise.all(prepared.map((prep) => embedStoreLimit(async () => {
|
|
303
|
+
// ── Check abort signal before any work ──
|
|
304
|
+
if (aborted()) {
|
|
305
|
+
return { normalizedPath: prep.normalizedPath, skipped: true };
|
|
200
306
|
}
|
|
201
|
-
|
|
202
|
-
// ── Phase 3: store all files with their embeddings (batched) ──
|
|
203
|
-
logger.debug(`Phase 3: storing chunks for ${prepared.length} files...`);
|
|
204
|
-
const allValidChunks = [];
|
|
205
|
-
const workerResults = prepared.map((prep, idx) => {
|
|
307
|
+
// ── Early results (empty / too-small / unchanged / chunking failure) ──
|
|
206
308
|
if (prep.earlyResult) {
|
|
309
|
+
// Remove stale manifest/store entries for files that no longer produce chunks
|
|
310
|
+
if (prep.earlyResult.isRemoved) {
|
|
311
|
+
await effectiveStore.deleteByFilePath(prep.normalizedPath);
|
|
312
|
+
options.keywordIndex?.removeByFilePath(prep.normalizedPath);
|
|
313
|
+
delete manifest.files[prep.normalizedPath];
|
|
314
|
+
enqueueManifestSave();
|
|
315
|
+
}
|
|
207
316
|
return prep.earlyResult;
|
|
208
317
|
}
|
|
209
|
-
if (!prep.chunks || !prep.textToEmbed) {
|
|
318
|
+
if (!prep.chunks || !prep.textToEmbed || prep.textToEmbed.length === 0) {
|
|
319
|
+
options.progress?.finishFile(prep.fileLabel);
|
|
210
320
|
return {
|
|
211
|
-
normalizedPath: prep.normalizedPath, hash: prep.hash, chunkCount: 0,
|
|
212
|
-
|
|
321
|
+
normalizedPath: prep.normalizedPath, hash: prep.hash, chunkCount: 0,
|
|
322
|
+
fileLabel: prep.fileLabel,
|
|
323
|
+
isNew: false, isModified: false, isUnchanged: false, isEmpty: false,
|
|
324
|
+
isTooSmall: false, isRemoved: true, hadChunks: false,
|
|
325
|
+
descriptionFailed: prep.descriptionFailed,
|
|
213
326
|
};
|
|
214
327
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
328
|
+
// ── Embed ──
|
|
329
|
+
const batchSize = isOllama
|
|
330
|
+
? Math.min(prep.textToEmbed.length, ollamaMaxBatch)
|
|
331
|
+
: defaultBatchSize;
|
|
332
|
+
const concurrency = isOllama ? 1 : defaultConcurrency;
|
|
333
|
+
options.progress?.finishStage(prep.fileLabel);
|
|
334
|
+
let embeddings;
|
|
335
|
+
try {
|
|
336
|
+
embeddings = await embedBatch(options.embedder, prep.textToEmbed, batchSize, "document", concurrency);
|
|
337
|
+
}
|
|
338
|
+
catch (err) {
|
|
339
|
+
logger.warn(` ${prep.fileLabel} (embedding failed: ${err.message})`);
|
|
340
|
+
options.progress?.failFile(prep.fileLabel);
|
|
341
|
+
return {
|
|
342
|
+
normalizedPath: prep.normalizedPath, hash: prep.hash, chunkCount: 0,
|
|
343
|
+
fileLabel: prep.fileLabel,
|
|
344
|
+
isNew: false, isModified: false, isUnchanged: false, isEmpty: false,
|
|
345
|
+
isTooSmall: false, isRemoved: true, hadChunks: false,
|
|
346
|
+
descriptionFailed: prep.descriptionFailed,
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
// ── Store (new data first; orphan cleanup is handled inside the store) ──
|
|
350
|
+
const result = await storeFileChunks(prep, embeddings, effectiveStore, logger);
|
|
351
|
+
// ── Update manifest in-memory and enqueue an atomic save ──
|
|
352
|
+
if (result.chunkCount > 0 && !result.isRemoved) {
|
|
353
|
+
const meta = fileMeta.get(result.normalizedPath);
|
|
354
|
+
manifest.files[result.normalizedPath] = {
|
|
355
|
+
hash: result.hash,
|
|
356
|
+
chunkCount: result.chunkCount,
|
|
357
|
+
indexedAt: Date.now(),
|
|
358
|
+
mtime: meta?.mtime,
|
|
359
|
+
size: meta?.size,
|
|
360
|
+
descriptionFailed: result.descriptionFailed,
|
|
361
|
+
};
|
|
362
|
+
enqueueManifestSave();
|
|
363
|
+
}
|
|
364
|
+
else if (result.isRemoved) {
|
|
365
|
+
delete manifest.files[result.normalizedPath];
|
|
366
|
+
enqueueManifestSave();
|
|
367
|
+
}
|
|
368
|
+
options.progress?.finishFile(prep.fileLabel);
|
|
369
|
+
return result;
|
|
370
|
+
})));
|
|
371
|
+
// Strip skipped sentinels
|
|
372
|
+
const finalResults = [];
|
|
373
|
+
for (const r of workerResults) {
|
|
374
|
+
if (r.skipped)
|
|
375
|
+
break;
|
|
376
|
+
finalResults.push(r);
|
|
377
|
+
}
|
|
378
|
+
// Drain any in-flight manifest saves so all file entries are durable
|
|
379
|
+
await manifestSaveChain;
|
|
380
|
+
// Update mtime/size for unchanged files (speeds up the next scan)
|
|
381
|
+
for (const { normalizedPath, mtime, size } of workspaceFiles) {
|
|
382
|
+
const entry = manifest.files[normalizedPath];
|
|
383
|
+
if (entry && (mtime !== undefined || size !== undefined)) {
|
|
384
|
+
entry.mtime = mtime;
|
|
385
|
+
entry.size = size;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
// ── Aggregate stats ──
|
|
389
|
+
aggregateStats(stats, finalResults);
|
|
390
|
+
// Update timestamps; advance lastGitCommit ONLY on a complete pass
|
|
391
|
+
manifest.lastIndexedAt = Date.now();
|
|
392
|
+
if (!aborted()) {
|
|
393
|
+
tryUpdateLastGitCommit(options.cwd, manifest);
|
|
394
|
+
}
|
|
395
|
+
// ── Atomically promote temp store if a full rebuild was performed ──
|
|
396
|
+
if (tempStorePath) {
|
|
397
|
+
if (!aborted()) {
|
|
398
|
+
try {
|
|
399
|
+
await effectiveStore.close();
|
|
400
|
+
await options.store.close();
|
|
401
|
+
// Swap the newly-built temp directory into the real path
|
|
402
|
+
await swapStoreDirectories(tempStorePath, options.storePath);
|
|
403
|
+
// Re-open the original store handle so callers can search the new data
|
|
404
|
+
if (typeof options.store.reopen === "function") {
|
|
405
|
+
await options.store.reopen(options.storePath);
|
|
406
|
+
}
|
|
407
|
+
logger.debug(`Promoted temporary store ${tempStorePath} → ${options.storePath}`);
|
|
220
408
|
}
|
|
221
|
-
|
|
222
|
-
|
|
409
|
+
catch (err) {
|
|
410
|
+
logger.warn(`Could not promote temporary store: ${err.message}. ` +
|
|
411
|
+
`Original data preserved at ${options.storePath}`);
|
|
412
|
+
try {
|
|
413
|
+
await fs.rm(tempStorePath, { recursive: true, force: true });
|
|
414
|
+
}
|
|
415
|
+
catch { }
|
|
223
416
|
}
|
|
224
417
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
isNew: !prep.isModified,
|
|
234
|
-
isModified: prep.isModified,
|
|
235
|
-
isUnchanged: false,
|
|
236
|
-
isEmpty: false,
|
|
237
|
-
isTooSmall: false,
|
|
238
|
-
isRemoved: false,
|
|
239
|
-
hadChunks: prep.chunks.length > 0,
|
|
240
|
-
};
|
|
241
|
-
});
|
|
242
|
-
if (allValidChunks.length > 0) {
|
|
243
|
-
const STORE_BATCH = 1000;
|
|
244
|
-
for (let i = 0; i < allValidChunks.length; i += STORE_BATCH) {
|
|
245
|
-
const batch = allValidChunks.slice(i, i + STORE_BATCH);
|
|
246
|
-
await options.store.addChunks(batch);
|
|
418
|
+
else {
|
|
419
|
+
// Aborted — discard temp, keep original data intact
|
|
420
|
+
effectiveStore.close().catch(() => { });
|
|
421
|
+
try {
|
|
422
|
+
await fs.rm(tempStorePath, { recursive: true, force: true });
|
|
423
|
+
}
|
|
424
|
+
catch { }
|
|
425
|
+
logger.debug("Index pass cancelled; discarded temporary store.");
|
|
247
426
|
}
|
|
248
|
-
logger.debug(` Stored ${allValidChunks.length} chunks in batched writes`);
|
|
249
427
|
}
|
|
250
|
-
//
|
|
251
|
-
|
|
252
|
-
manifest.lastIndexedAt = Date.now();
|
|
253
|
-
tryUpdateLastGitCommit(options.cwd, manifest);
|
|
428
|
+
// Save manifest and keyword index (always to the real store path — after
|
|
429
|
+
// a successful swap this points to the new data; after an abort it's the old).
|
|
254
430
|
await saveManifest(options.storePath, manifest);
|
|
255
431
|
await options.keywordIndex?.save(options.storePath);
|
|
256
|
-
|
|
432
|
+
// Count from the store — after a successful swap, the original handle has
|
|
433
|
+
// been reopened pointing to the new directory.
|
|
434
|
+
try {
|
|
435
|
+
stats.finalCount = await options.store.count();
|
|
436
|
+
}
|
|
437
|
+
catch {
|
|
438
|
+
stats.finalCount = (tempStorePath ? 0 : stats.totalChunks);
|
|
439
|
+
}
|
|
257
440
|
return stats;
|
|
258
441
|
}
|
|
259
|
-
function aggregateStats(stats, results
|
|
260
|
-
const fileMeta = new Map(workspaceFiles.map((f) => [f.normalizedPath, { mtime: f.mtime, size: f.size }]));
|
|
442
|
+
function aggregateStats(stats, results) {
|
|
261
443
|
for (const result of results) {
|
|
262
444
|
if (result.isEmpty) {
|
|
263
445
|
stats.skippedEmptyFiles++;
|
|
264
|
-
if (result.isRemoved)
|
|
265
|
-
delete manifest.files[result.normalizedPath];
|
|
446
|
+
if (result.isRemoved)
|
|
266
447
|
stats.removedFiles++;
|
|
267
|
-
}
|
|
268
448
|
continue;
|
|
269
449
|
}
|
|
270
450
|
if (result.isTooSmall) {
|
|
271
451
|
stats.skippedSmallFiles++;
|
|
272
|
-
if (result.isRemoved)
|
|
273
|
-
delete manifest.files[result.normalizedPath];
|
|
452
|
+
if (result.isRemoved)
|
|
274
453
|
stats.removedFiles++;
|
|
275
|
-
}
|
|
276
454
|
continue;
|
|
277
455
|
}
|
|
278
456
|
if (result.isUnchanged) {
|
|
@@ -280,7 +458,6 @@ function aggregateStats(stats, results, manifest, workspaceFiles) {
|
|
|
280
458
|
continue;
|
|
281
459
|
}
|
|
282
460
|
if (result.isRemoved) {
|
|
283
|
-
delete manifest.files[result.normalizedPath];
|
|
284
461
|
stats.removedFiles++;
|
|
285
462
|
continue;
|
|
286
463
|
}
|
|
@@ -291,65 +468,59 @@ function aggregateStats(stats, results, manifest, workspaceFiles) {
|
|
|
291
468
|
stats.newFiles++;
|
|
292
469
|
}
|
|
293
470
|
if (result.chunkCount > 0) {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
chunkCount: result.chunkCount,
|
|
298
|
-
indexedAt: Date.now(),
|
|
299
|
-
mtime: meta?.mtime,
|
|
300
|
-
size: meta?.size,
|
|
301
|
-
};
|
|
471
|
+
if (result.descriptionFailed) {
|
|
472
|
+
stats.descriptionFailedFiles++;
|
|
473
|
+
}
|
|
302
474
|
stats.totalChunks += result.chunkCount;
|
|
303
475
|
stats.batchesFlushed++;
|
|
304
476
|
}
|
|
305
477
|
}
|
|
306
|
-
for (const { normalizedPath, mtime, size } of workspaceFiles) {
|
|
307
|
-
const entry = manifest.files[normalizedPath];
|
|
308
|
-
if (entry && (mtime !== undefined || size !== undefined)) {
|
|
309
|
-
entry.mtime = mtime;
|
|
310
|
-
entry.size = size;
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
478
|
}
|
|
314
|
-
export async function getIndexStatusSummary(cwd, storePath, config, store) {
|
|
479
|
+
export async function getIndexStatusSummary(cwd, storePath, config, store, skipScan) {
|
|
315
480
|
const loadResult = await loadManifest(storePath);
|
|
316
481
|
const manifest = loadResult.manifest;
|
|
317
|
-
const workspaceFiles = await scanWorkspaceFiles(cwd, config, undefined, manifest);
|
|
318
482
|
const storeCount = await store.count();
|
|
319
483
|
if (loadResult.status !== "ok") {
|
|
320
484
|
return {
|
|
321
485
|
manifestStatus: loadResult.status,
|
|
322
486
|
manifestEntries: 0,
|
|
323
487
|
upToDateFiles: 0,
|
|
324
|
-
pendingFiles:
|
|
488
|
+
pendingFiles: 0,
|
|
325
489
|
rebuildRequired: storeCount > 0,
|
|
326
490
|
storeChunkCount: storeCount,
|
|
327
491
|
manifestExpectedChunks: 0,
|
|
328
492
|
};
|
|
329
493
|
}
|
|
330
|
-
const currentPaths = new Set(workspaceFiles.map((file) => file.normalizedPath));
|
|
331
494
|
let upToDateFiles = 0;
|
|
332
495
|
let pendingFiles = 0;
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
496
|
+
let manifestExpectedChunks = 0;
|
|
497
|
+
if (!skipScan) {
|
|
498
|
+
const workspaceFiles = await scanWorkspaceFiles(cwd, config, undefined, manifest);
|
|
499
|
+
const currentPaths = new Set(workspaceFiles.map((file) => file.normalizedPath));
|
|
500
|
+
for (const file of workspaceFiles) {
|
|
501
|
+
const previous = manifest.files[file.normalizedPath];
|
|
502
|
+
if (file.isEmpty || file.isTooSmall) {
|
|
503
|
+
if (previous)
|
|
504
|
+
pendingFiles++;
|
|
505
|
+
continue;
|
|
506
|
+
}
|
|
507
|
+
if (previous && previous.hash === file.hash) {
|
|
508
|
+
upToDateFiles++;
|
|
509
|
+
}
|
|
510
|
+
else {
|
|
337
511
|
pendingFiles++;
|
|
338
|
-
|
|
339
|
-
}
|
|
340
|
-
if (previous && previous.hash === file.hash) {
|
|
341
|
-
upToDateFiles++;
|
|
512
|
+
}
|
|
342
513
|
}
|
|
343
|
-
|
|
344
|
-
|
|
514
|
+
for (const indexedPath of Object.keys(manifest.files)) {
|
|
515
|
+
if (!currentPaths.has(indexedPath)) {
|
|
516
|
+
pendingFiles++;
|
|
517
|
+
}
|
|
345
518
|
}
|
|
519
|
+
manifestExpectedChunks = Object.values(manifest.files).reduce((sum, entry) => sum + entry.chunkCount, 0);
|
|
346
520
|
}
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
pendingFiles++;
|
|
350
|
-
}
|
|
521
|
+
else {
|
|
522
|
+
manifestExpectedChunks = Object.values(manifest.files).reduce((sum, entry) => sum + entry.chunkCount, 0);
|
|
351
523
|
}
|
|
352
|
-
const manifestExpectedChunks = Object.values(manifest.files).reduce((sum, entry) => sum + entry.chunkCount, 0);
|
|
353
524
|
return {
|
|
354
525
|
manifestStatus: loadResult.status,
|
|
355
526
|
manifestEntries: Object.keys(manifest.files).length,
|