@equinor/fusion-framework-cli-plugin-ai-index 3.0.6 → 3.0.7
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/CHANGELOG.md +32 -0
- package/dist/esm/bin/apply-metadata.js +13 -5
- package/dist/esm/bin/apply-metadata.js.map +1 -1
- package/dist/esm/bin/apply-schema.js +7 -1
- package/dist/esm/bin/apply-schema.js.map +1 -1
- package/dist/esm/bin/apply-schema.test.js +2 -1
- package/dist/esm/bin/apply-schema.test.js.map +1 -1
- package/dist/esm/bin/{delete-removed-files.js → create-delete-removed-files-stream.js} +12 -1
- package/dist/esm/bin/create-delete-removed-files-stream.js.map +1 -0
- package/dist/esm/bin/embed.js +260 -97
- package/dist/esm/bin/embed.js.map +1 -1
- package/dist/esm/bin/execute-pipeline.js +5 -0
- package/dist/esm/bin/execute-pipeline.js.map +1 -1
- package/dist/esm/bin/get-diff.js +6 -0
- package/dist/esm/bin/get-diff.js.map +1 -1
- package/dist/esm/{embeddings-command.options.js → command-options-schema.js} +1 -1
- package/dist/esm/command-options-schema.js.map +1 -0
- package/dist/esm/{embeddings-command.js → command.js} +4 -2
- package/dist/esm/command.js.map +1 -0
- package/dist/esm/{create-command.js → create-index-command.js} +13 -5
- package/dist/esm/create-index-command.js.map +1 -0
- package/dist/esm/{schema.js → define-index-schema.js} +1 -1
- package/dist/esm/define-index-schema.js.map +1 -0
- package/dist/esm/delete-command.js +11 -2
- package/dist/esm/delete-command.js.map +1 -1
- package/dist/esm/delete-index-command.js +4 -0
- package/dist/esm/delete-index-command.js.map +1 -1
- package/dist/esm/{delete-command.options.js → delete-options-schema.js} +1 -1
- package/dist/esm/delete-options-schema.js.map +1 -0
- package/dist/esm/embed-command.js +3 -4
- package/dist/esm/embed-command.js.map +1 -1
- package/dist/esm/index.js +3 -3
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/search-command.js +20 -0
- package/dist/esm/search-command.js.map +1 -1
- package/dist/esm/utils/git/{metadata.js → extract-git-metadata.js} +4 -2
- package/dist/esm/utils/git/extract-git-metadata.js.map +1 -0
- package/dist/esm/utils/git/get-changed-files.js +87 -0
- package/dist/esm/utils/git/get-changed-files.js.map +1 -0
- package/dist/esm/utils/git/{file-changes.js → get-file-status.js} +9 -95
- package/dist/esm/utils/git/get-file-status.js.map +1 -0
- package/dist/esm/utils/git/{status.js → get-git-status.js} +5 -2
- package/dist/esm/utils/git/get-git-status.js.map +1 -0
- package/dist/esm/utils/git/get-git.js +30 -0
- package/dist/esm/utils/git/get-git.js.map +1 -0
- package/dist/esm/utils/git/index.js +7 -4
- package/dist/esm/utils/git/index.js.map +1 -1
- package/dist/esm/utils/git/is-file-changed.js +19 -0
- package/dist/esm/utils/git/is-file-changed.js.map +1 -0
- package/dist/esm/utils/git/resolve-project-root.js +22 -0
- package/dist/esm/utils/git/resolve-project-root.js.map +1 -0
- package/dist/esm/utils/markdown/index.js +3 -1
- package/dist/esm/utils/markdown/index.js.map +1 -1
- package/dist/esm/utils/markdown/is-markdown-file.js +10 -0
- package/dist/esm/utils/markdown/is-markdown-file.js.map +1 -0
- package/dist/esm/utils/markdown/parse-markdown-file.js +29 -0
- package/dist/esm/utils/markdown/parse-markdown-file.js.map +1 -0
- package/dist/esm/utils/markdown/{parser.js → parse-markdown.js} +8 -37
- package/dist/esm/utils/markdown/parse-markdown.js.map +1 -0
- package/dist/esm/utils/{embedding-dimensions.js → resolve-embedding-dimensions.js} +3 -1
- package/dist/esm/utils/resolve-embedding-dimensions.js.map +1 -0
- package/dist/esm/utils/{package-resolver.js → resolve-package.js} +4 -1
- package/dist/esm/utils/resolve-package.js.map +1 -0
- package/dist/esm/utils/ts-doc/create-typescript-document.js +27 -0
- package/dist/esm/utils/ts-doc/create-typescript-document.js.map +1 -0
- package/dist/esm/utils/ts-doc/extract-document-from-class-node.js +86 -0
- package/dist/esm/utils/ts-doc/extract-document-from-class-node.js.map +1 -0
- package/dist/esm/utils/ts-doc/extract-document-from-node.js +82 -0
- package/dist/esm/utils/ts-doc/extract-document-from-node.js.map +1 -0
- package/dist/esm/utils/ts-doc/index.js +3 -1
- package/dist/esm/utils/ts-doc/index.js.map +1 -1
- package/dist/esm/utils/ts-doc/is-typescript-file.js +10 -0
- package/dist/esm/utils/ts-doc/is-typescript-file.js.map +1 -0
- package/dist/esm/utils/ts-doc/{constants.js → node-kinds.js} +1 -1
- package/dist/esm/utils/ts-doc/node-kinds.js.map +1 -0
- package/dist/esm/utils/ts-doc/parse-ts-doc-from-file-sync.js +23 -0
- package/dist/esm/utils/ts-doc/parse-ts-doc-from-file-sync.js.map +1 -0
- package/dist/esm/utils/ts-doc/parse-ts-doc-sync.js +18 -0
- package/dist/esm/utils/ts-doc/parse-ts-doc-sync.js.map +1 -0
- package/dist/esm/utils/ts-doc/process-source-file.js +22 -0
- package/dist/esm/utils/ts-doc/process-source-file.js.map +1 -0
- package/dist/esm/utils/zod-to-azure-fields.js +7 -0
- package/dist/esm/utils/zod-to-azure-fields.js.map +1 -1
- package/dist/esm/utils/zod-to-azure-fields.test.js +8 -7
- package/dist/esm/utils/zod-to-azure-fields.test.js.map +1 -1
- package/dist/esm/version.js +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/dist/types/bin/apply-schema.d.ts +1 -1
- package/dist/types/config.d.ts +1 -1
- package/dist/types/index.d.ts +1 -1
- package/dist/types/utils/git/get-changed-files.d.ts +13 -0
- package/dist/types/utils/git/get-file-status.d.ts +13 -0
- package/dist/types/utils/git/{git-client.d.ts → get-git.d.ts} +0 -10
- package/dist/types/utils/git/index.d.ts +7 -4
- package/dist/types/utils/git/is-file-changed.d.ts +12 -0
- package/dist/types/utils/git/resolve-project-root.d.ts +10 -0
- package/dist/types/utils/markdown/index.d.ts +3 -1
- package/dist/types/utils/markdown/is-markdown-file.d.ts +7 -0
- package/dist/types/utils/markdown/parse-markdown-file.d.ts +14 -0
- package/dist/types/utils/markdown/parse-markdown.d.ts +14 -0
- package/dist/types/utils/ts-doc/create-typescript-document.d.ts +12 -0
- package/dist/types/utils/ts-doc/extract-document-from-class-node.d.ts +15 -0
- package/dist/types/utils/ts-doc/extract-document-from-node.d.ts +20 -0
- package/dist/types/utils/ts-doc/index.d.ts +3 -1
- package/dist/types/utils/ts-doc/is-typescript-file.d.ts +7 -0
- package/dist/types/utils/ts-doc/parse-ts-doc-from-file-sync.d.ts +14 -0
- package/dist/types/utils/ts-doc/parse-ts-doc-sync.d.ts +12 -0
- package/dist/types/utils/ts-doc/process-source-file.d.ts +11 -0
- package/dist/types/version.d.ts +1 -1
- package/package.json +8 -8
- package/src/bin/apply-metadata.ts +69 -57
- package/src/bin/apply-schema.test.ts +2 -1
- package/src/bin/apply-schema.ts +6 -1
- package/src/bin/{delete-removed-files.ts → create-delete-removed-files-stream.ts} +12 -1
- package/src/bin/embed.ts +389 -145
- package/src/bin/execute-pipeline.ts +5 -0
- package/src/bin/get-diff.ts +7 -1
- package/src/bin/types.ts +1 -1
- package/src/{embeddings-command.ts → command.ts} +3 -1
- package/src/config.ts +1 -1
- package/src/{create-command.ts → create-index-command.ts} +15 -5
- package/src/delete-command.ts +11 -2
- package/src/delete-index-command.ts +4 -0
- package/src/embed-command.ts +3 -6
- package/src/index.ts +3 -3
- package/src/search-command.ts +20 -0
- package/src/utils/git/{metadata.ts → extract-git-metadata.ts} +3 -1
- package/src/utils/git/get-changed-files.ts +93 -0
- package/src/utils/git/{file-changes.ts → get-file-status.ts} +9 -103
- package/src/utils/git/{status.ts → get-git-status.ts} +4 -1
- package/src/utils/git/{git-client.ts → get-git.ts} +4 -22
- package/src/utils/git/index.ts +7 -4
- package/src/utils/git/is-file-changed.ts +21 -0
- package/src/utils/git/resolve-project-root.ts +22 -0
- package/src/utils/markdown/index.ts +3 -1
- package/src/utils/markdown/is-markdown-file.ts +9 -0
- package/src/utils/markdown/parse-markdown-file.ts +36 -0
- package/src/utils/markdown/{parser.ts → parse-markdown.ts} +16 -51
- package/src/utils/{embedding-dimensions.ts → resolve-embedding-dimensions.ts} +2 -0
- package/src/utils/{package-resolver.ts → resolve-package.ts} +3 -0
- package/src/utils/ts-doc/create-typescript-document.ts +36 -0
- package/src/utils/ts-doc/extract-document-from-class-node.ts +106 -0
- package/src/utils/ts-doc/extract-document-from-node.ts +120 -0
- package/src/utils/ts-doc/index.ts +3 -1
- package/src/utils/ts-doc/is-typescript-file.ts +9 -0
- package/src/utils/ts-doc/parse-ts-doc-from-file-sync.ts +32 -0
- package/src/utils/ts-doc/parse-ts-doc-sync.ts +23 -0
- package/src/utils/ts-doc/process-source-file.ts +30 -0
- package/src/utils/zod-to-azure-fields.test.ts +10 -7
- package/src/utils/zod-to-azure-fields.ts +7 -0
- package/src/version.ts +1 -1
- package/dist/esm/bin/delete-removed-files.js.map +0 -1
- package/dist/esm/bin/file-stream.js +0 -22
- package/dist/esm/bin/file-stream.js.map +0 -1
- package/dist/esm/create-command.js.map +0 -1
- package/dist/esm/delete-command.options.js.map +0 -1
- package/dist/esm/embeddings-command.js.map +0 -1
- package/dist/esm/embeddings-command.options.js.map +0 -1
- package/dist/esm/schema.js.map +0 -1
- package/dist/esm/utils/embedding-dimensions.js.map +0 -1
- package/dist/esm/utils/git/file-changes.js.map +0 -1
- package/dist/esm/utils/git/git-client.js +0 -48
- package/dist/esm/utils/git/git-client.js.map +0 -1
- package/dist/esm/utils/git/metadata.js.map +0 -1
- package/dist/esm/utils/git/status.js.map +0 -1
- package/dist/esm/utils/markdown/parser.js.map +0 -1
- package/dist/esm/utils/package-resolver.js.map +0 -1
- package/dist/esm/utils/ts-doc/constants.js.map +0 -1
- package/dist/esm/utils/ts-doc/extractors.js +0 -189
- package/dist/esm/utils/ts-doc/extractors.js.map +0 -1
- package/dist/esm/utils/ts-doc/parser.js +0 -46
- package/dist/esm/utils/ts-doc/parser.js.map +0 -1
- package/dist/types/bin/file-stream.d.ts +0 -1
- package/dist/types/utils/git/file-changes.d.ts +0 -36
- package/dist/types/utils/markdown/parser.d.ts +0 -34
- package/dist/types/utils/ts-doc/extractors.d.ts +0 -42
- package/dist/types/utils/ts-doc/parser.d.ts +0 -32
- package/src/bin/file-stream.ts +0 -34
- package/src/utils/ts-doc/extractors.ts +0 -260
- package/src/utils/ts-doc/parser.ts +0 -60
- /package/dist/types/bin/{delete-removed-files.d.ts → create-delete-removed-files-stream.d.ts} +0 -0
- /package/dist/types/{embeddings-command.options.d.ts → command-options-schema.d.ts} +0 -0
- /package/dist/types/{embeddings-command.d.ts → command.d.ts} +0 -0
- /package/dist/types/{create-command.d.ts → create-index-command.d.ts} +0 -0
- /package/dist/types/{schema.d.ts → define-index-schema.d.ts} +0 -0
- /package/dist/types/{delete-command.options.d.ts → delete-options-schema.d.ts} +0 -0
- /package/dist/types/utils/git/{metadata.d.ts → extract-git-metadata.d.ts} +0 -0
- /package/dist/types/utils/git/{status.d.ts → get-git-status.d.ts} +0 -0
- /package/dist/types/utils/{embedding-dimensions.d.ts → resolve-embedding-dimensions.d.ts} +0 -0
- /package/dist/types/utils/{package-resolver.d.ts → resolve-package.d.ts} +0 -0
- /package/dist/types/utils/ts-doc/{constants.d.ts → node-kinds.d.ts} +0 -0
- /package/src/{embeddings-command.options.ts → command-options-schema.ts} +0 -0
- /package/src/{schema.ts → define-index-schema.ts} +0 -0
- /package/src/{delete-command.options.ts → delete-options-schema.ts} +0 -0
- /package/src/utils/ts-doc/{constants.ts → node-kinds.ts} +0 -0
package/src/bin/embed.ts
CHANGED
|
@@ -20,16 +20,22 @@ import { getFileStatus, resolveProjectRoot } from '../utils/git/index.js';
|
|
|
20
20
|
import { isTypescriptFile, parseTsDocFromFileSync } from '../utils/ts-doc/index.js';
|
|
21
21
|
|
|
22
22
|
import { getDiff } from './get-diff.js';
|
|
23
|
-
import { createDeleteRemovedFilesStream } from './delete-removed-files.js';
|
|
23
|
+
import { createDeleteRemovedFilesStream } from './create-delete-removed-files-stream.js';
|
|
24
24
|
import { applyMetadata } from './apply-metadata.js';
|
|
25
25
|
import { applySchema } from './apply-schema.js';
|
|
26
26
|
import type {
|
|
27
27
|
DocumentEntry,
|
|
28
28
|
EmbeddingsBinOptions,
|
|
29
|
+
DeleteRemovedFilesResult,
|
|
29
30
|
ProcessedFile,
|
|
30
31
|
UpdateVectorStoreResult,
|
|
31
32
|
} from './types.js';
|
|
33
|
+
import type { ChangedFile } from '../utils/git/index.js';
|
|
34
|
+
import type { CommandOptions } from '../command-options-schema.js';
|
|
35
|
+
import type { FusionAIConfigWithIndex } from '../config.js';
|
|
36
|
+
import type { FrameworkInstance } from '@equinor/fusion-framework-cli-plugin-ai-base';
|
|
32
37
|
import type { VectorStoreDocument } from '@equinor/fusion-framework-module-ai/lib';
|
|
38
|
+
import type { Observable } from 'rxjs';
|
|
33
39
|
import { readFileSync } from 'node:fs';
|
|
34
40
|
import { generateChunkId } from '../utils/generate-chunk-id.js';
|
|
35
41
|
|
|
@@ -61,12 +67,16 @@ class ProgressDisplay {
|
|
|
61
67
|
/** Minimum interval (ms) between CI progress lines for the same line slot. */
|
|
62
68
|
private static CI_LOG_INTERVAL_MS = 15_000;
|
|
63
69
|
|
|
64
|
-
/**
|
|
70
|
+
/**
|
|
71
|
+
* Register the line labels up front and print empty placeholders.
|
|
72
|
+
* @param count - Number of progress lines to reserve.
|
|
73
|
+
*/
|
|
65
74
|
start(count: number): void {
|
|
66
75
|
this.lines = new Array<string>(count).fill('');
|
|
67
76
|
this.spinning = new Array<boolean>(count).fill(false);
|
|
68
77
|
this.lastCiLog = new Array<number>(count).fill(0);
|
|
69
78
|
|
|
79
|
+
// Skip the ANSI cursor block in CI — plain console.log lines are used instead
|
|
70
80
|
if (!IS_CI) {
|
|
71
81
|
// Print placeholder lines so the cursor block exists
|
|
72
82
|
for (let i = 0; i < count; i++) {
|
|
@@ -78,13 +88,20 @@ class ProgressDisplay {
|
|
|
78
88
|
this.started = true;
|
|
79
89
|
}
|
|
80
90
|
|
|
81
|
-
/**
|
|
91
|
+
/**
|
|
92
|
+
* Update a specific line (0-indexed) without touching the others.
|
|
93
|
+
* @param line - Index of the progress line to update.
|
|
94
|
+
* @param message - Message to display on that line.
|
|
95
|
+
*/
|
|
82
96
|
update(line: number, message: string): void {
|
|
97
|
+
// Nothing to update before the display has been started
|
|
83
98
|
if (!this.started) return;
|
|
84
99
|
this.lines[line] = message;
|
|
85
100
|
this.spinning[line] = true;
|
|
101
|
+
// In CI, throttle to plain log lines instead of redrawing the cursor block
|
|
86
102
|
if (IS_CI) {
|
|
87
103
|
const now = Date.now();
|
|
104
|
+
// Only log again once the throttle interval has elapsed for this line
|
|
88
105
|
if (now - this.lastCiLog[line] >= ProgressDisplay.CI_LOG_INTERVAL_MS) {
|
|
89
106
|
this.lastCiLog[line] = now;
|
|
90
107
|
console.log(`⏳ ${message}`);
|
|
@@ -94,12 +111,18 @@ class ProgressDisplay {
|
|
|
94
111
|
this.render(line);
|
|
95
112
|
}
|
|
96
113
|
|
|
97
|
-
/**
|
|
114
|
+
/**
|
|
115
|
+
* Mark a line as completed — stops its spinner and shows a checkmark.
|
|
116
|
+
* @param line - Index of the progress line to complete.
|
|
117
|
+
* @param message - Final message to display on that line.
|
|
118
|
+
*/
|
|
98
119
|
succeed(line: number, message: string): void {
|
|
120
|
+
// Nothing to update before the display has been started
|
|
99
121
|
if (!this.started) return;
|
|
100
122
|
const text = `✅ ${message}`;
|
|
101
123
|
this.lines[line] = text;
|
|
102
124
|
this.spinning[line] = false;
|
|
125
|
+
// In CI, print the final line directly instead of redrawing the cursor block
|
|
103
126
|
if (IS_CI) {
|
|
104
127
|
console.log(text);
|
|
105
128
|
return;
|
|
@@ -109,8 +132,11 @@ class ProgressDisplay {
|
|
|
109
132
|
|
|
110
133
|
/** Clear all progress lines and leave the cursor on a clean line. */
|
|
111
134
|
clear(): void {
|
|
135
|
+
// Nothing to clear before the display has been started
|
|
112
136
|
if (!this.started) return;
|
|
137
|
+
// Stop the spinner tick before tearing down the display
|
|
113
138
|
if (this.timer) clearInterval(this.timer);
|
|
139
|
+
// Skip cursor cleanup entirely in CI, where no cursor block was drawn
|
|
114
140
|
if (!IS_CI) {
|
|
115
141
|
// Move up to the first progress line and clear each one
|
|
116
142
|
for (let i = 0; i < this.lines.length; i++) {
|
|
@@ -126,14 +152,19 @@ class ProgressDisplay {
|
|
|
126
152
|
/** Advance the spinner frame and re-render all spinning lines. */
|
|
127
153
|
private tick(): void {
|
|
128
154
|
this.frame = (this.frame + 1) % SPINNER_FRAMES.length;
|
|
155
|
+
// Re-render every line, skipping ones that aren't currently spinning
|
|
129
156
|
for (let i = 0; i < this.lines.length; i++) {
|
|
157
|
+
// Only re-render lines that are still actively spinning
|
|
130
158
|
if (this.spinning[i] && this.lines[i]) {
|
|
131
159
|
this.render(i);
|
|
132
160
|
}
|
|
133
161
|
}
|
|
134
162
|
}
|
|
135
163
|
|
|
136
|
-
/**
|
|
164
|
+
/**
|
|
165
|
+
* Render a single line at its position.
|
|
166
|
+
* @param line - Index of the progress line to render.
|
|
167
|
+
*/
|
|
137
168
|
private render(line: number): void {
|
|
138
169
|
const linesUp = this.lines.length - line;
|
|
139
170
|
const prefix = this.spinning[line] ? SPINNER_FRAMES[this.frame] : '';
|
|
@@ -198,78 +229,120 @@ const EMBED_BUFFER_FLUSH_MS = 10_000;
|
|
|
198
229
|
const MAX_RETRIES = 4;
|
|
199
230
|
|
|
200
231
|
/**
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
* @
|
|
232
|
+
* Prints resolved run configuration for `--debug` troubleshooting.
|
|
233
|
+
*
|
|
234
|
+
* @param options - Resolved command options for this run.
|
|
235
|
+
* @param filePatterns - Glob patterns used to discover files to index.
|
|
236
|
+
* @param config - Fusion AI index configuration for this run.
|
|
204
237
|
*/
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
238
|
+
function logDebugConfig(
|
|
239
|
+
options: CommandOptions,
|
|
240
|
+
filePatterns: string[],
|
|
241
|
+
config: FusionAIConfigWithIndex,
|
|
242
|
+
): void {
|
|
243
|
+
console.debug('[debug] Embed model:', options.embedModel);
|
|
244
|
+
console.debug('[debug] File patterns:', filePatterns);
|
|
245
|
+
console.debug(
|
|
246
|
+
'[debug] Allowed patterns:',
|
|
247
|
+
config.index?.patterns ?? ['**/*.ts', '**/*.tsx', '**/*.md', '**/*.mdx'],
|
|
248
|
+
);
|
|
249
|
+
console.debug('[debug] Raw patterns:', config.index?.rawPatterns ?? []);
|
|
250
|
+
console.debug('[debug] Ignore patterns:', config.index?.ignore ?? defaultIgnore);
|
|
251
|
+
console.debug('[debug] Diff mode:', options.diff);
|
|
252
|
+
console.debug('[debug] Dry run:', options.dryRun);
|
|
253
|
+
console.debug('[debug] Clean:', options.clean);
|
|
254
|
+
}
|
|
210
255
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
256
|
+
/**
|
|
257
|
+
* Deletes all existing documents from the vector store when `--clean` is set.
|
|
258
|
+
* No-op in dry-run mode or when `--clean` was not requested.
|
|
259
|
+
*
|
|
260
|
+
* @param framework - Framework instance used to access the configured vector store.
|
|
261
|
+
* @param options - Resolved command options for this run.
|
|
262
|
+
*/
|
|
263
|
+
async function cleanVectorStoreIfRequested(
|
|
264
|
+
framework: FrameworkInstance,
|
|
265
|
+
options: CommandOptions,
|
|
266
|
+
): Promise<void> {
|
|
267
|
+
// Only wipe the index when explicitly requested and not in dry-run mode
|
|
268
|
+
if (!options.clean || options.dryRun) {
|
|
269
|
+
return;
|
|
223
270
|
}
|
|
224
|
-
|
|
225
|
-
const progress = new ProgressDisplay();
|
|
226
|
-
|
|
227
|
-
// Handle clean operation (destructive - deletes all existing documents)
|
|
271
|
+
console.log('🧹 Cleaning vector store: deleting all existing documents...');
|
|
228
272
|
const vectorStoreService = framework.ai.useIndex(options.indexName);
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
console.log('✅ Vector store cleaned successfully');
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// Handle diff-based processing (workflow mode)
|
|
239
|
-
const changedFiles = options.diff ? await getDiff(options) : [];
|
|
273
|
+
// OData filter: delete all documents with non-empty source (all indexed docs)
|
|
274
|
+
await vectorStoreService.deleteDocuments({
|
|
275
|
+
filter: { filterExpression: "metadata/source ne ''" },
|
|
276
|
+
});
|
|
277
|
+
console.log('✅ Vector store cleaned successfully');
|
|
278
|
+
}
|
|
240
279
|
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
280
|
+
/**
|
|
281
|
+
* Creates the raw file stream: diff mode reuses already-resolved changed
|
|
282
|
+
* files, normal mode globs the filesystem and resolves git status per match.
|
|
283
|
+
*
|
|
284
|
+
* @param options - Resolved command options for this run.
|
|
285
|
+
* @param config - Fusion AI index configuration for this run.
|
|
286
|
+
* @param filePatterns - Glob patterns used to discover files to index.
|
|
287
|
+
* @param changedFiles - Pre-resolved changed files, used when `options.diff` is set.
|
|
288
|
+
* @returns A stream emitting each matched {@link ChangedFile}.
|
|
289
|
+
*/
|
|
290
|
+
function createFileStream$(
|
|
291
|
+
options: CommandOptions,
|
|
292
|
+
config: FusionAIConfigWithIndex,
|
|
293
|
+
filePatterns: string[],
|
|
294
|
+
changedFiles: ChangedFile[],
|
|
295
|
+
): Observable<ChangedFile> {
|
|
296
|
+
// Diff mode reuses the already-resolved changed files instead of globbing.
|
|
297
|
+
if (options.diff) {
|
|
298
|
+
return from(changedFiles);
|
|
299
|
+
}
|
|
246
300
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
301
|
+
// Directories to skip before expensive git operations.
|
|
302
|
+
// Note: Even with gitignore: true, globby still traverses ignored directories when .gitignore
|
|
303
|
+
// contains negation patterns (like !.yarn/releases), so we add explicit ignore patterns
|
|
304
|
+
// to prevent traversing these directories entirely.
|
|
305
|
+
const ignore = config.index?.ignore ?? defaultIgnore;
|
|
252
306
|
|
|
253
|
-
|
|
254
|
-
|
|
307
|
+
// Respect .gitignore by default; configs targeting build artifacts can opt out.
|
|
308
|
+
const gitignore = config.index?.gitignore ?? true;
|
|
255
309
|
|
|
256
|
-
|
|
310
|
+
return (
|
|
311
|
+
from(
|
|
257
312
|
globbyStream(filePatterns, {
|
|
258
313
|
ignore,
|
|
259
314
|
onlyFiles: true,
|
|
260
315
|
gitignore,
|
|
261
316
|
absolute: true,
|
|
262
317
|
}),
|
|
263
|
-
)
|
|
264
|
-
//
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
318
|
+
)
|
|
319
|
+
// Resolve git status for each matched file, then flatten to individual files
|
|
320
|
+
.pipe(
|
|
321
|
+
// Get git status concurrently (capped to avoid spawning too many git processes)
|
|
322
|
+
mergeMap((path) => getFileStatus(path), GIT_CONCURRENCY),
|
|
323
|
+
concatMap((files) => from(files)),
|
|
324
|
+
// Share stream for multiple subscribers (removedFiles$ and indexFiles$)
|
|
325
|
+
shareReplay({ refCount: true }),
|
|
326
|
+
)
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Enriches each file with its project-relative path and filters out files
|
|
332
|
+
* that don't match the configured index patterns.
|
|
333
|
+
*
|
|
334
|
+
* @param files$ - Raw file stream from {@link createFileStream$}.
|
|
335
|
+
* @param config - Fusion AI index configuration for this run.
|
|
336
|
+
* @param debug - Whether to log skipped files for troubleshooting.
|
|
337
|
+
* @param progress - Progress display used to report parsing progress.
|
|
338
|
+
* @returns A stream of {@link ProcessedFile} entries matching the allowed patterns.
|
|
339
|
+
*/
|
|
340
|
+
function createProcessedFilesStream$(
|
|
341
|
+
files$: Observable<ChangedFile>,
|
|
342
|
+
config: FusionAIConfigWithIndex,
|
|
343
|
+
debug: boolean,
|
|
344
|
+
progress: ProgressDisplay,
|
|
345
|
+
): Observable<ProcessedFile> {
|
|
273
346
|
const allowedFilePatterns = config.index?.patterns ?? [
|
|
274
347
|
'**/*.ts',
|
|
275
348
|
'**/*.tsx',
|
|
@@ -277,9 +350,9 @@ export async function embed(binOptions: EmbeddingsBinOptions): Promise<void> {
|
|
|
277
350
|
'**/*.mdx',
|
|
278
351
|
];
|
|
279
352
|
|
|
280
|
-
// Process files: enrich with metadata and filter by allowed patterns
|
|
281
353
|
let fileCount = 0;
|
|
282
|
-
|
|
354
|
+
// Resolve each file's project-relative path and filter by allowed patterns
|
|
355
|
+
return files$.pipe(
|
|
283
356
|
map((file) => {
|
|
284
357
|
const { filepath, status } = file;
|
|
285
358
|
const projectRoot = resolveProjectRoot(filepath);
|
|
@@ -294,6 +367,7 @@ export async function embed(binOptions: EmbeddingsBinOptions): Promise<void> {
|
|
|
294
367
|
}),
|
|
295
368
|
filter((file) => {
|
|
296
369
|
const matches = multimatch(file.relativePath, allowedFilePatterns);
|
|
370
|
+
// Surface skipped files in debug output for troubleshooting pattern config
|
|
297
371
|
if (debug && matches.length === 0) {
|
|
298
372
|
console.debug('[debug] Skipped (no pattern match):', file.relativePath);
|
|
299
373
|
}
|
|
@@ -307,22 +381,27 @@ export async function embed(binOptions: EmbeddingsBinOptions): Promise<void> {
|
|
|
307
381
|
// Share for multiple subscribers (removedFiles$, markdown$, typescript$)
|
|
308
382
|
shareReplay({ refCount: true }),
|
|
309
383
|
);
|
|
384
|
+
}
|
|
310
385
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
386
|
+
/**
|
|
387
|
+
* Parses new/modified files (raw, markdown, or TypeScript) into document
|
|
388
|
+
* entries ready for metadata enrichment.
|
|
389
|
+
*
|
|
390
|
+
* @param indexFiles$ - Stream of new/modified files to parse.
|
|
391
|
+
* @param config - Fusion AI index configuration for this run.
|
|
392
|
+
* @param debug - Whether to log per-file chunk counts for troubleshooting.
|
|
393
|
+
* @param progress - Progress display used to report parsing progress.
|
|
394
|
+
* @returns A stream of {@link DocumentEntry} objects, one per parsed file.
|
|
395
|
+
*/
|
|
396
|
+
function createParsedDocumentsStream$(
|
|
397
|
+
indexFiles$: Observable<ProcessedFile>,
|
|
398
|
+
config: FusionAIConfigWithIndex,
|
|
399
|
+
debug: boolean,
|
|
400
|
+
progress: ProgressDisplay,
|
|
401
|
+
): Observable<DocumentEntry> {
|
|
324
402
|
const isRawFile = (file: ProcessedFile): boolean => {
|
|
325
403
|
const matches = multimatch(file.relativePath, config.index?.rawPatterns ?? []);
|
|
404
|
+
// A file is "raw" when it matches any configured raw pattern
|
|
326
405
|
if (matches.length > 0) {
|
|
327
406
|
return true;
|
|
328
407
|
}
|
|
@@ -330,6 +409,8 @@ export async function embed(binOptions: EmbeddingsBinOptions): Promise<void> {
|
|
|
330
409
|
};
|
|
331
410
|
|
|
332
411
|
let docCount = 0;
|
|
412
|
+
|
|
413
|
+
// Read raw-pattern files verbatim as a single document, skipping markdown/TS parsing
|
|
333
414
|
const rawFiles$ = indexFiles$.pipe(
|
|
334
415
|
filter(isRawFile),
|
|
335
416
|
map((file): DocumentEntry => {
|
|
@@ -347,12 +428,14 @@ export async function embed(binOptions: EmbeddingsBinOptions): Promise<void> {
|
|
|
347
428
|
}),
|
|
348
429
|
);
|
|
349
430
|
|
|
431
|
+
// Parse markdown files not already handled as raw content
|
|
350
432
|
const markdown$ = indexFiles$.pipe(
|
|
351
433
|
filter((x) => !isRawFile(x)),
|
|
352
434
|
filter((file) => isMarkdownFile(file.path)),
|
|
353
435
|
mergeMap(async (file) => {
|
|
354
436
|
const documents = await parseMarkdownFile(file);
|
|
355
437
|
docCount++;
|
|
438
|
+
// Surface per-file chunk counts in debug output
|
|
356
439
|
if (debug) {
|
|
357
440
|
console.debug(`[debug] Markdown ${file.relativePath} → ${documents.length} chunk(s)`);
|
|
358
441
|
}
|
|
@@ -361,12 +444,14 @@ export async function embed(binOptions: EmbeddingsBinOptions): Promise<void> {
|
|
|
361
444
|
}),
|
|
362
445
|
);
|
|
363
446
|
|
|
447
|
+
// Parse TypeScript files not already handled as raw content
|
|
364
448
|
const typescript$ = indexFiles$.pipe(
|
|
365
449
|
filter((x) => !isRawFile(x)),
|
|
366
450
|
filter((file) => isTypescriptFile(file.path)),
|
|
367
451
|
map((file) => {
|
|
368
452
|
const documents = parseTsDocFromFileSync(file);
|
|
369
453
|
docCount++;
|
|
454
|
+
// Surface per-file chunk counts in debug output
|
|
370
455
|
if (debug) {
|
|
371
456
|
console.debug(`[debug] TypeScript ${file.relativePath} → ${documents.length} chunk(s)`);
|
|
372
457
|
}
|
|
@@ -376,36 +461,66 @@ export async function embed(binOptions: EmbeddingsBinOptions): Promise<void> {
|
|
|
376
461
|
);
|
|
377
462
|
|
|
378
463
|
// Merge parsed streams and signal when all parsing is done
|
|
379
|
-
|
|
464
|
+
return merge(rawFiles$, markdown$, typescript$).pipe(
|
|
380
465
|
finalize(() => {
|
|
381
466
|
progress.succeed(LINE_PARSE, `📄 Parsed ${docCount} files`);
|
|
382
467
|
}),
|
|
383
468
|
);
|
|
469
|
+
}
|
|
384
470
|
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
471
|
+
/** Mutable progress counters shared between a pipeline stage and the final reporter. */
|
|
472
|
+
interface StageProgressState {
|
|
473
|
+
count: number;
|
|
474
|
+
done: boolean;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
/**
|
|
478
|
+
* Applies metadata enrichment to parsed documents and tracks progress.
|
|
479
|
+
*
|
|
480
|
+
* @param parsed$ - Stream of parsed document entries.
|
|
481
|
+
* @param config - Fusion AI index configuration for this run.
|
|
482
|
+
* @param progress - Progress display used to report metadata progress.
|
|
483
|
+
* @returns The enriched document stream along with its progress state.
|
|
484
|
+
*/
|
|
485
|
+
function createMetadataStream$(
|
|
486
|
+
parsed$: Observable<DocumentEntry>,
|
|
487
|
+
config: FusionAIConfigWithIndex,
|
|
488
|
+
progress: ProgressDisplay,
|
|
489
|
+
): { stream$: Observable<VectorStoreDocument[]>; state: StageProgressState } {
|
|
490
|
+
const state: StageProgressState = { count: 0, done: false };
|
|
491
|
+
// Track metadata progress via the callback, then mark the stage done on completion
|
|
492
|
+
const stream$ = applyMetadata(parsed$, config.index, (source) => {
|
|
493
|
+
state.count++;
|
|
494
|
+
progress.update(LINE_META, `🏷️ Metadata [${state.count}] ${source}`);
|
|
391
495
|
}).pipe(
|
|
392
496
|
finalize(() => {
|
|
393
|
-
|
|
394
|
-
progress.succeed(LINE_META, `🏷️ Metadata ${
|
|
497
|
+
state.done = true;
|
|
498
|
+
progress.succeed(LINE_META, `🏷️ Metadata ${state.count} documents`);
|
|
395
499
|
}),
|
|
396
500
|
);
|
|
501
|
+
return { stream$, state };
|
|
502
|
+
}
|
|
397
503
|
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
504
|
+
/**
|
|
505
|
+
* Generates embeddings in batches with retry on rate-limit (429) errors.
|
|
506
|
+
*
|
|
507
|
+
* @param documents$ - Stream of schema-resolved document batches to embed.
|
|
508
|
+
* @param embeddingService - Embedding client used to compute document vectors.
|
|
509
|
+
* @param debug - Whether to log batch sizes for troubleshooting.
|
|
510
|
+
* @param progress - Progress display used to report embedding progress.
|
|
511
|
+
* @param metadataState - Progress state from {@link createMetadataStream$}, used as the denominator.
|
|
512
|
+
* @returns The embedded document stream along with its progress state.
|
|
513
|
+
*/
|
|
514
|
+
function createEmbeddingStream$(
|
|
515
|
+
documents$: Observable<VectorStoreDocument[]>,
|
|
516
|
+
embeddingService: ReturnType<FrameworkInstance['ai']['useEmbed']>,
|
|
517
|
+
debug: boolean,
|
|
518
|
+
progress: ProgressDisplay,
|
|
519
|
+
metadataState: StageProgressState,
|
|
520
|
+
): { stream$: Observable<VectorStoreDocument[]>; state: StageProgressState } {
|
|
521
|
+
const state: StageProgressState = { count: 0, done: false };
|
|
522
|
+
// Buffer, embed with retry, then re-attach embeddings onto each document
|
|
523
|
+
const stream$ = documents$.pipe(
|
|
409
524
|
// Flatten all documents from file-level batches, then re-batch for the API
|
|
410
525
|
concatMap((documents) => from(documents)),
|
|
411
526
|
// Flush when EMBED_BATCH_SIZE docs accumulate OR after EMBED_BUFFER_FLUSH_MS,
|
|
@@ -413,72 +528,106 @@ export async function embed(binOptions: EmbeddingsBinOptions): Promise<void> {
|
|
|
413
528
|
bufferTime(EMBED_BUFFER_FLUSH_MS, null, EMBED_BATCH_SIZE),
|
|
414
529
|
filter((batch) => batch.length > 0),
|
|
415
530
|
mergeMap((batch) => {
|
|
531
|
+
// Surface batch sizes in debug output for troubleshooting throughput
|
|
416
532
|
if (debug) {
|
|
417
533
|
console.debug(`[debug] Embedding batch of ${batch.length} documents`);
|
|
418
534
|
}
|
|
419
|
-
return
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
)
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
535
|
+
return (
|
|
536
|
+
from(
|
|
537
|
+
embeddingService.embedDocuments(
|
|
538
|
+
batch
|
|
539
|
+
// Extract each document's page content for the embedding API call
|
|
540
|
+
.map((d) => d.pageContent),
|
|
541
|
+
),
|
|
542
|
+
)
|
|
543
|
+
// Retry transient/rate-limit errors, then attach embeddings back onto each document
|
|
544
|
+
.pipe(
|
|
545
|
+
retry({
|
|
546
|
+
count: MAX_RETRIES,
|
|
547
|
+
delay: (error, retryIndex) => {
|
|
548
|
+
// Auth errors are terminal — abort immediately with actionable message
|
|
549
|
+
if (error?.name === 'NoAccountsError') {
|
|
550
|
+
console.error(
|
|
551
|
+
'\n🔒 Authentication expired. Run `ffc auth login` then retry with `--diff`.',
|
|
552
|
+
);
|
|
553
|
+
throw error;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
const retryAfterSec =
|
|
557
|
+
error?.response?.headers?.get?.('retry-after') ??
|
|
558
|
+
error?.responseHeaders?.['retry-after'];
|
|
559
|
+
const retryAfterMs = retryAfterSec ? Number(retryAfterSec) * 1000 : 0;
|
|
560
|
+
|
|
561
|
+
const backoffMs = 2 ** retryIndex * 1000;
|
|
562
|
+
const delayMs = Math.max(backoffMs, retryAfterMs);
|
|
563
|
+
|
|
564
|
+
console.warn(
|
|
565
|
+
`\n⏳ Retry ${retryIndex}/${MAX_RETRIES} for batch of ${batch.length} in ${delayMs}ms`,
|
|
566
|
+
);
|
|
567
|
+
return timer(delayMs);
|
|
568
|
+
},
|
|
569
|
+
}),
|
|
570
|
+
map((allEmbeddings) => {
|
|
571
|
+
// Re-attach the corresponding embedding onto each document in the batch
|
|
572
|
+
return batch.map((document, i) => {
|
|
573
|
+
state.count++;
|
|
574
|
+
const total = metadataState.done ? metadataState.count : 0;
|
|
575
|
+
const pct = total > 0 ? ` ${Math.round((state.count / total) * 100)}%` : '';
|
|
576
|
+
const denominator = total > 0 ? `/${total}` : '';
|
|
577
|
+
progress.update(
|
|
578
|
+
LINE_EMBED,
|
|
579
|
+
`🧠 Embedding [${state.count}${denominator}]${pct} — ${document.metadata.source}`,
|
|
580
|
+
);
|
|
581
|
+
const metadata = { ...document.metadata, embedding: allEmbeddings[i] };
|
|
582
|
+
return { ...document, metadata };
|
|
583
|
+
});
|
|
584
|
+
}),
|
|
585
|
+
)
|
|
459
586
|
);
|
|
460
587
|
}, EMBED_BATCH_CONCURRENCY),
|
|
461
588
|
finalize(() => {
|
|
462
|
-
|
|
463
|
-
progress.succeed(LINE_EMBED, `🧠 Embedded ${
|
|
589
|
+
state.done = true;
|
|
590
|
+
progress.succeed(LINE_EMBED, `🧠 Embedded ${state.count} documents`);
|
|
464
591
|
}),
|
|
465
592
|
);
|
|
593
|
+
return { stream$, state };
|
|
594
|
+
}
|
|
466
595
|
|
|
467
|
-
|
|
468
|
-
|
|
596
|
+
/**
|
|
597
|
+
* Batches embedded documents and upserts them into the vector store.
|
|
598
|
+
*
|
|
599
|
+
* @param embedding$ - Stream of embedded document batches.
|
|
600
|
+
* @param framework - Framework instance used to access the configured vector store.
|
|
601
|
+
* @param options - Resolved command options for this run.
|
|
602
|
+
* @param debug - Whether to log upserted document IDs for troubleshooting.
|
|
603
|
+
* @returns A stream emitting the result of each batch upsert.
|
|
604
|
+
*/
|
|
605
|
+
function createUpsertStream$(
|
|
606
|
+
embedding$: Observable<VectorStoreDocument[]>,
|
|
607
|
+
framework: FrameworkInstance,
|
|
608
|
+
options: CommandOptions,
|
|
609
|
+
debug: boolean,
|
|
610
|
+
): Observable<UpdateVectorStoreResult> {
|
|
611
|
+
// Re-batch embedded documents into fixed-size groups for bulk upsert
|
|
612
|
+
return embedding$.pipe(
|
|
469
613
|
// Flatten file-level batches, then re-batch into groups of 20 for bulk upsert
|
|
470
614
|
concatMap((documents) => from(documents)),
|
|
471
615
|
bufferCount(20),
|
|
472
616
|
mergeMap(async (documents) => {
|
|
473
617
|
const vectorStoreService = framework.ai.useIndex(options.indexName);
|
|
618
|
+
// Nothing to upsert when the batch is empty
|
|
474
619
|
if (documents.length === 0) {
|
|
475
620
|
return undefined;
|
|
476
621
|
}
|
|
622
|
+
// Skip the actual vector store mutation when running in dry-run mode
|
|
477
623
|
if (!options.dryRun) {
|
|
624
|
+
// Surface upserted document IDs in debug output for troubleshooting
|
|
478
625
|
if (debug) {
|
|
479
626
|
console.debug(
|
|
480
627
|
`[debug] Upserting batch of ${documents.length} documents:`,
|
|
481
|
-
documents
|
|
628
|
+
documents
|
|
629
|
+
// Log just the IDs to keep debug output concise
|
|
630
|
+
.map((d) => d.id),
|
|
482
631
|
);
|
|
483
632
|
}
|
|
484
633
|
await vectorStoreService.addDocuments(documents);
|
|
@@ -490,8 +639,23 @@ export async function embed(binOptions: EmbeddingsBinOptions): Promise<void> {
|
|
|
490
639
|
}, UPSERT_CONCURRENCY),
|
|
491
640
|
filter((result): result is UpdateVectorStoreResult => Boolean(result)),
|
|
492
641
|
);
|
|
642
|
+
}
|
|
493
643
|
|
|
494
|
-
|
|
644
|
+
/**
|
|
645
|
+
* Runs the deletion/upsert pipeline to completion, reporting progress and
|
|
646
|
+
* a final summary, then exits the process.
|
|
647
|
+
*
|
|
648
|
+
* @param delete$ - Stream of removed-file deletion results.
|
|
649
|
+
* @param upsert$ - Stream of document upsert results.
|
|
650
|
+
* @param progress - Progress display used to report indexing progress.
|
|
651
|
+
* @param embeddingState - Progress state from {@link createEmbeddingStream$}, used as the denominator.
|
|
652
|
+
*/
|
|
653
|
+
function runIndexingPipeline(
|
|
654
|
+
delete$: Observable<DeleteRemovedFilesResult>,
|
|
655
|
+
upsert$: Observable<UpdateVectorStoreResult>,
|
|
656
|
+
progress: ProgressDisplay,
|
|
657
|
+
embeddingState: StageProgressState,
|
|
658
|
+
): void {
|
|
495
659
|
// Track indexing results for reporting: deleted file paths and added document IDs
|
|
496
660
|
let indexedCount = 0;
|
|
497
661
|
const indexingResults: { deleted: string[]; added: { source: string; id: string }[] } = {
|
|
@@ -507,10 +671,12 @@ export async function embed(binOptions: EmbeddingsBinOptions): Promise<void> {
|
|
|
507
671
|
next: (result) => {
|
|
508
672
|
// Track deleted files by relative path
|
|
509
673
|
if (result.status === 'deleted') {
|
|
674
|
+
// Reduce each deleted file entry down to its relative path for reporting.
|
|
510
675
|
indexingResults.deleted.push(...result.files.map((file) => file.relativePath));
|
|
511
676
|
}
|
|
512
677
|
// Track added documents with source and ID (one file can produce multiple IDs)
|
|
513
678
|
else if (result.status === 'added') {
|
|
679
|
+
// Reduce each added document down to its source path and id for reporting.
|
|
514
680
|
indexingResults.added.push(
|
|
515
681
|
...result.documents.map((document) => ({
|
|
516
682
|
source: document.metadata.source,
|
|
@@ -519,7 +685,7 @@ export async function embed(binOptions: EmbeddingsBinOptions): Promise<void> {
|
|
|
519
685
|
);
|
|
520
686
|
indexedCount += result.documents.length;
|
|
521
687
|
// Use embeddedCount as denominator — only show % once embedding is done
|
|
522
|
-
const total =
|
|
688
|
+
const total = embeddingState.done ? embeddingState.count : 0;
|
|
523
689
|
const pct = total > 0 ? ` ${Math.round((indexedCount / total) * 100)}%` : '';
|
|
524
690
|
const denominator = total > 0 ? `/${total}` : '';
|
|
525
691
|
progress.update(LINE_INDEX, `📤 Indexed [${indexedCount}${denominator}]${pct}`);
|
|
@@ -536,6 +702,7 @@ export async function embed(binOptions: EmbeddingsBinOptions): Promise<void> {
|
|
|
536
702
|
if (indexingResults.deleted.length > 0) {
|
|
537
703
|
console.log(`🗑️ Deleted: ${indexingResults.deleted.length} files`);
|
|
538
704
|
}
|
|
705
|
+
// Only print an "indexed" summary line when documents were actually added
|
|
539
706
|
if (indexingResults.added.length > 0) {
|
|
540
707
|
console.log(`📥 Indexed: ${indexingResults.added.length} documents`);
|
|
541
708
|
}
|
|
@@ -544,3 +711,80 @@ export async function embed(binOptions: EmbeddingsBinOptions): Promise<void> {
|
|
|
544
711
|
},
|
|
545
712
|
});
|
|
546
713
|
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Main entry point for the embeddings bin.
|
|
717
|
+
* Orchestrates the entire embeddings generation pipeline.
|
|
718
|
+
*
|
|
719
|
+
* @param binOptions - Framework, resolved options, config, and file patterns for this run.
|
|
720
|
+
* @throws {Error} Propagates unrecoverable embedding/vector-store errors from the pipeline.
|
|
721
|
+
* @internal
|
|
722
|
+
*/
|
|
723
|
+
export async function embed(binOptions: EmbeddingsBinOptions): Promise<void> {
|
|
724
|
+
const { framework, options, config, filePatterns } = binOptions;
|
|
725
|
+
const debug = options.debug ?? false;
|
|
726
|
+
|
|
727
|
+
console.log(`📇 Index: ${options.indexName}`);
|
|
728
|
+
|
|
729
|
+
// Print resolved run configuration when --debug is set
|
|
730
|
+
if (debug) {
|
|
731
|
+
logDebugConfig(options, filePatterns, config);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
const progress = new ProgressDisplay();
|
|
735
|
+
|
|
736
|
+
await cleanVectorStoreIfRequested(framework, options);
|
|
737
|
+
|
|
738
|
+
// Handle diff-based processing (workflow mode)
|
|
739
|
+
const changedFiles = options.diff ? await getDiff(options) : [];
|
|
740
|
+
|
|
741
|
+
// Create file stream: diff mode uses git changes, normal mode uses globby
|
|
742
|
+
const files$ = createFileStream$(options, config, filePatterns, changedFiles);
|
|
743
|
+
|
|
744
|
+
// Enrich files with metadata and filter by allowed patterns
|
|
745
|
+
const processedFiles$ = createProcessedFilesStream$(files$, config, debug, progress);
|
|
746
|
+
|
|
747
|
+
// Split stream: removed files for deletion, new/modified for indexing
|
|
748
|
+
const removedFiles$ = processedFiles$.pipe(filter((file) => file.status === 'removed'));
|
|
749
|
+
|
|
750
|
+
// Create processing streams
|
|
751
|
+
const delete$ = createDeleteRemovedFilesStream(removedFiles$, framework, options);
|
|
752
|
+
|
|
753
|
+
// New/modified files for indexing
|
|
754
|
+
const indexFiles$ = processedFiles$.pipe(
|
|
755
|
+
filter((file) => file.status === 'new' || file.status === 'modified'),
|
|
756
|
+
// Share for markdown$ and typescript$ pipelines
|
|
757
|
+
shareReplay({ refCount: true }),
|
|
758
|
+
);
|
|
759
|
+
|
|
760
|
+
// Parse raw/markdown/TypeScript files into document entries
|
|
761
|
+
const parsed$ = createParsedDocumentsStream$(indexFiles$, config, debug, progress);
|
|
762
|
+
|
|
763
|
+
// Apply metadata to documents
|
|
764
|
+
const { stream$: applyMetadata$, state: metadataState } = createMetadataStream$(
|
|
765
|
+
parsed$,
|
|
766
|
+
config,
|
|
767
|
+
progress,
|
|
768
|
+
);
|
|
769
|
+
|
|
770
|
+
// Resolve promoted schema fields (if schema is configured) — runs after
|
|
771
|
+
// metadata enrichment so the resolver has access to git, package, and
|
|
772
|
+
// custom attributes from attributeProcessor
|
|
773
|
+
const applySchema$ = applySchema(applyMetadata$, config.index?.schema);
|
|
774
|
+
|
|
775
|
+
// Generate embeddings in batches with retry on rate-limit (429) errors
|
|
776
|
+
const embeddingService = framework.ai.useEmbed(options.embedModel);
|
|
777
|
+
const { stream$: applyEmbedding$, state: embeddingState } = createEmbeddingStream$(
|
|
778
|
+
applySchema$,
|
|
779
|
+
embeddingService,
|
|
780
|
+
debug,
|
|
781
|
+
progress,
|
|
782
|
+
metadataState,
|
|
783
|
+
);
|
|
784
|
+
|
|
785
|
+
// Update vector store — batch documents and upsert concurrently
|
|
786
|
+
const upsert$ = createUpsertStream$(applyEmbedding$, framework, options, debug);
|
|
787
|
+
|
|
788
|
+
// Execute pipeline and report final results
|
|
789
|
+
runIndexingPipeline(delete$, upsert$, progress, embeddingState);
|
|
790
|
+
}
|