ctxman 3.0.0
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/.ctxman/custom-profiles.example.json +20 -0
- package/.ctxman/llm-profiles.json +131 -0
- package/.ctxman/logs/ctxman-2026-07-02.log +156 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/0-b3dd1558-f95b-44b9-a154-18e8c330ae7a.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/1-44bff6d4-e552-4010-a769-e09ad822698d.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/10-37260794-5947-4ecb-b4c8-d732f58f40f1.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/11-0d52a883-5518-463c-9b6d-986c3af89dfd.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/12-ae354646-52f2-49c9-90fb-2e6ed8932663.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/13-5d7d5e57-27da-498b-b734-091b9d38da13.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/14-558ce928-810b-4252-a364-fdd1ee6f8a25.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/15-27e3a58f-8b64-43b8-a9f4-e3369446968b.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/16-a110c390-e5c8-4464-b976-bc7f5195fa15.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/17-97513225-53de-4216-b61a-6337bd0cc4ad.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/18-5ba450f0-c527-459c-bf4f-6d79b5e4285f.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/19-600d3a63-5225-49e9-b8b7-30e037b564ab.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/2-48a61b4e-92cf-4342-8376-914c1b99a9f9.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/20-121dd912-5410-410e-84ff-82c7a23604a0.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/3-bc6844ab-7ea3-4bb3-9ee1-538ce5664a9c.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/4-d5e1754f-dbb5-454e-a9be-2a7da8d7bcf9.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/5-4fae93df-0568-4b6f-8acf-8bef4bba6047.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/6-4b1b8b87-068e-49a3-ba2b-d8dae628b252.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/7-63d040fb-d2fa-4c0f-bed0-53411918a7b0.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/8-c5628cf6-264c-420f-aab8-bc96a9083591.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/9-c94570aa-5e53-4fcb-971f-cecc1d1d5492.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/1.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/10.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/11.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/12.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/13.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/14.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/15.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/16.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/17.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/18.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/19.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/2.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/20.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/21.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/3.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/4.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/5.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/6.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/7.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/8.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/9.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/000010010011011101000010de8621405d9d5cb06540dbf02c.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/0000101001011100011000003fb62a4aa8978974075c144f1c.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/000011010000100111001011c6cc824d628f3bb1f4e04cd5d0.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/001000001000011001011100d8f74349ada090b332cd1df669.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/01000001111010001000010045ab44467bb1e7e4d8ae06bd58.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/01000101101110111110011131ef904241a89e1ffb6ddaf8e3.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/010010110000010000011010fbfcc145c6a8a827b28057a07e.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1000011000101010001110003fb4f045c3835982efa1405324.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/100111011101110111101011f4396b410fa06af3b028d204e6.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/100111100111101000001100596b174e00995d6cc2969cc8dc.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1010001100011000110010102ea16b484d887c7c9725feffd6.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/101101100010101111110111c5230944d9b3af7301b6392dda.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1011011100100111111110105892bf41b5a530fedd7cf516f1.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/10111110000010001011110164e8774742bade87b483c10f36.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1100000100010110001110103ffaca40a2a42c85067ebaf9ae.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/110000010001110110101110664bde4c76a78dfc14b05e83b8.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/11001100111000010100110050645c4281b1f35b2022234f7f.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1101111011001100110111017214924a368f5719eab2246619.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1110000111001010001000002150c24dcab653d39d07032e63.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1110100000000100000011014d20534967a18b5c87721659ff.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/11110101111010000101011135a0114ef2b35dc6f1297a3381.lance +0 -0
- package/.ctxman/wizard-profiles/code-review/.contextignore +41 -0
- package/.ctxman/wizard-profiles/code-review/.contextinclude +30 -0
- package/.ctxman/wizard-profiles/code-review/.methodignore +26 -0
- package/.ctxman/wizard-profiles/code-review/.methodinclude +47 -0
- package/.ctxman/wizard-profiles/code-review/profile.json +26 -0
- package/.ctxman/wizard-profiles/documentation/.contextignore +21 -0
- package/.ctxman/wizard-profiles/documentation/.contextinclude +24 -0
- package/.ctxman/wizard-profiles/documentation/.methodignore +19 -0
- package/.ctxman/wizard-profiles/documentation/.methodinclude +44 -0
- package/.ctxman/wizard-profiles/documentation/profile.json +27 -0
- package/.ctxman/wizard-profiles/full/.contextignore +23 -0
- package/.ctxman/wizard-profiles/full/.contextinclude +38 -0
- package/.ctxman/wizard-profiles/full/.methodignore +5 -0
- package/.ctxman/wizard-profiles/full/.methodinclude +5 -0
- package/.ctxman/wizard-profiles/full/profile.json +28 -0
- package/.ctxman/wizard-profiles/llm-explain/.contextignore +25 -0
- package/.ctxman/wizard-profiles/llm-explain/.contextinclude +33 -0
- package/.ctxman/wizard-profiles/llm-explain/.methodignore +19 -0
- package/.ctxman/wizard-profiles/llm-explain/.methodinclude +48 -0
- package/.ctxman/wizard-profiles/llm-explain/profile.json +27 -0
- package/.ctxman/wizard-profiles/minimal/.contextignore +27 -0
- package/.ctxman/wizard-profiles/minimal/.contextinclude +14 -0
- package/.ctxman/wizard-profiles/minimal/.methodignore +22 -0
- package/.ctxman/wizard-profiles/minimal/.methodinclude +18 -0
- package/.ctxman/wizard-profiles/minimal/profile.json +26 -0
- package/.ctxman/wizard-profiles/security-audit/.contextignore +36 -0
- package/.ctxman/wizard-profiles/security-audit/.contextinclude +37 -0
- package/.ctxman/wizard-profiles/security-audit/.methodignore +29 -0
- package/.ctxman/wizard-profiles/security-audit/.methodinclude +60 -0
- package/.ctxman/wizard-profiles/security-audit/profile.json +34 -0
- package/README.md +1167 -0
- package/bin/cli.js +650 -0
- package/bin/cm-ask.js +95 -0
- package/bin/cm-gitingest.js +134 -0
- package/bin/cm-update.js +175 -0
- package/bin/mcp-server.js +31 -0
- package/ctxman.js +236 -0
- package/examples/README.md +298 -0
- package/examples/custom-llm-profiles.example.json +19 -0
- package/examples/wizard-profiles/code-review/.contextignore +41 -0
- package/examples/wizard-profiles/code-review/.contextinclude +30 -0
- package/examples/wizard-profiles/code-review/.methodignore +26 -0
- package/examples/wizard-profiles/code-review/.methodinclude +47 -0
- package/examples/wizard-profiles/code-review/profile.json +26 -0
- package/examples/wizard-profiles/documentation/.contextignore +21 -0
- package/examples/wizard-profiles/documentation/.contextinclude +24 -0
- package/examples/wizard-profiles/documentation/.methodignore +19 -0
- package/examples/wizard-profiles/documentation/.methodinclude +44 -0
- package/examples/wizard-profiles/documentation/profile.json +27 -0
- package/examples/wizard-profiles/full/.contextignore +23 -0
- package/examples/wizard-profiles/full/.contextinclude +38 -0
- package/examples/wizard-profiles/full/.methodignore +5 -0
- package/examples/wizard-profiles/full/.methodinclude +5 -0
- package/examples/wizard-profiles/full/profile.json +28 -0
- package/examples/wizard-profiles/llm-explain/.contextignore +25 -0
- package/examples/wizard-profiles/llm-explain/.contextinclude +33 -0
- package/examples/wizard-profiles/llm-explain/.methodignore +19 -0
- package/examples/wizard-profiles/llm-explain/.methodinclude +48 -0
- package/examples/wizard-profiles/llm-explain/profile.json +27 -0
- package/examples/wizard-profiles/minimal/.contextignore +27 -0
- package/examples/wizard-profiles/minimal/.contextinclude +14 -0
- package/examples/wizard-profiles/minimal/.methodignore +22 -0
- package/examples/wizard-profiles/minimal/.methodinclude +18 -0
- package/examples/wizard-profiles/minimal/profile.json +26 -0
- package/examples/wizard-profiles/security-audit/.contextignore +36 -0
- package/examples/wizard-profiles/security-audit/.contextinclude +37 -0
- package/examples/wizard-profiles/security-audit/.methodignore +29 -0
- package/examples/wizard-profiles/security-audit/.methodinclude +60 -0
- package/examples/wizard-profiles/security-audit/profile.json +34 -0
- package/index.js +68 -0
- package/lib/agents/pr-failure-agent.js +201 -0
- package/lib/analyzers/go-method-analyzer.js +126 -0
- package/lib/analyzers/method-analyzer.js +457 -0
- package/lib/analyzers/token-calculator.js +577 -0
- package/lib/api/mcp/prompts.js +319 -0
- package/lib/api/mcp/resources.js +269 -0
- package/lib/api/mcp/server.js +339 -0
- package/lib/api/mcp/transports/HttpSseTransport.js +63 -0
- package/lib/api/rest/server.js +405 -0
- package/lib/cache/CacheManager.js +388 -0
- package/lib/core/Analyzer.js +180 -0
- package/lib/core/ContextBuilder.js +325 -0
- package/lib/core/Reporter.js +270 -0
- package/lib/core/Scanner.js +129 -0
- package/lib/formatters/format-registry.js +419 -0
- package/lib/formatters/gitingest-formatter.js +746 -0
- package/lib/formatters/toon-formatter-v1.3.js +731 -0
- package/lib/formatters/toon-formatter.js +291 -0
- package/lib/integrations/git/BlameTracker.js +142 -0
- package/lib/integrations/git/DiffAnalyzer.js +260 -0
- package/lib/integrations/git/GitClient.js +336 -0
- package/lib/parsers/gitignore-parser.js +127 -0
- package/lib/parsers/method-filter-parser.js +50 -0
- package/lib/plugins/ExporterPlugin.js +102 -0
- package/lib/plugins/LanguagePlugin.js +100 -0
- package/lib/plugins/PluginManager.js +302 -0
- package/lib/rag/DataSourcePlugin.js +31 -0
- package/lib/rag/EmbeddingProvider.js +75 -0
- package/lib/rag/Indexer.js +83 -0
- package/lib/rag/VectorStoreFactory.js +24 -0
- package/lib/rag/sources/FileDataSource.js +70 -0
- package/lib/rag/stores/BaseVectorStore.js +43 -0
- package/lib/rag/stores/ChromaDBStore.js +85 -0
- package/lib/rag/stores/LanceDBStore.js +109 -0
- package/lib/rag/stores/PineconeStore.js +74 -0
- package/lib/ui/dashboard.js +109 -0
- package/lib/ui/index.js +15 -0
- package/lib/ui/progress-bar.js +110 -0
- package/lib/ui/select-input.js +51 -0
- package/lib/ui/wizard.js +359 -0
- package/lib/utils/clipboard-utils.js +66 -0
- package/lib/utils/config-utils.js +94 -0
- package/lib/utils/error-handler.js +145 -0
- package/lib/utils/file-utils.js +89 -0
- package/lib/utils/format-converter.js +325 -0
- package/lib/utils/git-utils.js +337 -0
- package/lib/utils/llm-detector.js +409 -0
- package/lib/utils/logger.js +278 -0
- package/lib/utils/token-utils.js +200 -0
- package/lib/utils/tokenizer-adapter.js +454 -0
- package/lib/utils/updater.js +565 -0
- package/lib/watch/FileWatcher.js +208 -0
- package/lib/watch/IncrementalAnalyzer.js +206 -0
- package/package.json +118 -0
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PluginManager - Plugin System Core
|
|
3
|
+
* v3.0.0 - Plugin architecture
|
|
4
|
+
*
|
|
5
|
+
* Responsibilities:
|
|
6
|
+
* - Load and manage plugins
|
|
7
|
+
* - Plugin lifecycle management
|
|
8
|
+
* - Plugin dependency resolution
|
|
9
|
+
* - Event-driven plugin communication
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import fs from 'fs';
|
|
13
|
+
import path from 'path';
|
|
14
|
+
import { EventEmitter } from 'events';
|
|
15
|
+
import { getLogger } from '../utils/logger.js';
|
|
16
|
+
|
|
17
|
+
const logger = getLogger('PluginManager');
|
|
18
|
+
|
|
19
|
+
export class PluginManager extends EventEmitter {
|
|
20
|
+
constructor(options = {}) {
|
|
21
|
+
super();
|
|
22
|
+
|
|
23
|
+
this.options = {
|
|
24
|
+
pluginPaths: [
|
|
25
|
+
'./lib/languages',
|
|
26
|
+
'./lib/exporters',
|
|
27
|
+
'./plugins'
|
|
28
|
+
],
|
|
29
|
+
autoLoad: true,
|
|
30
|
+
lazy: true, // Lazy load plugins
|
|
31
|
+
...options
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
this.plugins = new Map();
|
|
35
|
+
this.loaded = new Map();
|
|
36
|
+
this.stats = {
|
|
37
|
+
registered: 0,
|
|
38
|
+
loaded: 0,
|
|
39
|
+
errors: 0
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Initialize plugin manager
|
|
45
|
+
*/
|
|
46
|
+
async initialize() {
|
|
47
|
+
logger.info('Initializing plugin manager...');
|
|
48
|
+
|
|
49
|
+
if (this.options.autoLoad) {
|
|
50
|
+
await this.discoverPlugins();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
this.emit('initialized');
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Discover plugins from plugin paths
|
|
58
|
+
*/
|
|
59
|
+
async discoverPlugins() {
|
|
60
|
+
for (const pluginPath of this.options.pluginPaths) {
|
|
61
|
+
const fullPath = path.resolve(pluginPath);
|
|
62
|
+
|
|
63
|
+
if (!fs.existsSync(fullPath)) {
|
|
64
|
+
logger.debug(`Plugin path not found: ${pluginPath}`);
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
await this.discoverFromDirectory(fullPath);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
logger.info(`Discovered ${this.plugins.size} plugins`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Discover plugins from a directory
|
|
76
|
+
* @param {string} dirPath
|
|
77
|
+
*/
|
|
78
|
+
async discoverFromDirectory(dirPath) {
|
|
79
|
+
try {
|
|
80
|
+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
81
|
+
|
|
82
|
+
for (const entry of entries) {
|
|
83
|
+
if (entry.isFile() && entry.name.endsWith('.js')) {
|
|
84
|
+
const pluginPath = path.join(dirPath, entry.name);
|
|
85
|
+
await this.registerPluginFromFile(pluginPath);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
} catch (error) {
|
|
89
|
+
logger.error(`Error discovering plugins in ${dirPath}: ${error.message}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Register plugin from file
|
|
95
|
+
* @param {string} pluginPath
|
|
96
|
+
*/
|
|
97
|
+
async registerPluginFromFile(pluginPath) {
|
|
98
|
+
try {
|
|
99
|
+
// Register without loading (lazy)
|
|
100
|
+
const pluginName = path.basename(pluginPath, '.js');
|
|
101
|
+
|
|
102
|
+
this.plugins.set(pluginName, {
|
|
103
|
+
name: pluginName,
|
|
104
|
+
path: pluginPath,
|
|
105
|
+
loaded: false,
|
|
106
|
+
instance: null
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
this.stats.registered++;
|
|
110
|
+
logger.debug(`Registered plugin: ${pluginName}`);
|
|
111
|
+
|
|
112
|
+
} catch (error) {
|
|
113
|
+
logger.error(`Failed to register plugin ${pluginPath}: ${error.message}`);
|
|
114
|
+
this.stats.errors++;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Load a plugin
|
|
120
|
+
* @param {string} pluginName
|
|
121
|
+
* @returns {Promise<Plugin>}
|
|
122
|
+
*/
|
|
123
|
+
async load(pluginName) {
|
|
124
|
+
const plugin = this.plugins.get(pluginName);
|
|
125
|
+
|
|
126
|
+
if (!plugin) {
|
|
127
|
+
throw new Error(`Plugin not found: ${pluginName}`);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Return cached instance if already loaded
|
|
131
|
+
if (plugin.loaded && plugin.instance) {
|
|
132
|
+
logger.debug(`Plugin already loaded: ${pluginName}`);
|
|
133
|
+
return plugin.instance;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Load plugin
|
|
137
|
+
logger.info(`Loading plugin: ${pluginName}`);
|
|
138
|
+
const startTime = Date.now();
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
const module = await import(plugin.path);
|
|
142
|
+
const PluginClass = module.default || module[Object.keys(module)[0]];
|
|
143
|
+
|
|
144
|
+
let instance;
|
|
145
|
+
if (typeof PluginClass === 'function') {
|
|
146
|
+
instance = new PluginClass();
|
|
147
|
+
} else {
|
|
148
|
+
instance = PluginClass;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
plugin.instance = instance;
|
|
152
|
+
plugin.loaded = true;
|
|
153
|
+
|
|
154
|
+
const elapsed = Date.now() - startTime;
|
|
155
|
+
this.stats.loaded++;
|
|
156
|
+
|
|
157
|
+
logger.info(`Plugin loaded: ${pluginName} (${elapsed}ms)`);
|
|
158
|
+
this.emit('plugin:loaded', { name: pluginName, elapsed });
|
|
159
|
+
|
|
160
|
+
return instance;
|
|
161
|
+
|
|
162
|
+
} catch (error) {
|
|
163
|
+
logger.error(`Failed to load plugin ${pluginName}: ${error.message}`);
|
|
164
|
+
this.stats.errors++;
|
|
165
|
+
this.emit('plugin:error', { name: pluginName, error });
|
|
166
|
+
throw error;
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Load multiple plugins
|
|
172
|
+
* @param {Array<string>} pluginNames
|
|
173
|
+
* @returns {Promise<Array<Plugin>>}
|
|
174
|
+
*/
|
|
175
|
+
async loadMultiple(pluginNames) {
|
|
176
|
+
const promises = pluginNames.map(name => this.load(name));
|
|
177
|
+
return Promise.all(promises);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Get loaded plugin
|
|
182
|
+
* @param {string} pluginName
|
|
183
|
+
* @returns {Plugin|null}
|
|
184
|
+
*/
|
|
185
|
+
get(pluginName) {
|
|
186
|
+
const plugin = this.plugins.get(pluginName);
|
|
187
|
+
return plugin?.instance || null;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Check if plugin is loaded
|
|
192
|
+
* @param {string} pluginName
|
|
193
|
+
* @returns {boolean}
|
|
194
|
+
*/
|
|
195
|
+
isLoaded(pluginName) {
|
|
196
|
+
const plugin = this.plugins.get(pluginName);
|
|
197
|
+
return plugin?.loaded || false;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Unload a plugin
|
|
202
|
+
* @param {string} pluginName
|
|
203
|
+
*/
|
|
204
|
+
unload(pluginName) {
|
|
205
|
+
const plugin = this.plugins.get(pluginName);
|
|
206
|
+
|
|
207
|
+
if (!plugin || !plugin.loaded) {
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Call cleanup if available
|
|
212
|
+
if (plugin.instance && typeof plugin.instance.cleanup === 'function') {
|
|
213
|
+
plugin.instance.cleanup();
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
plugin.loaded = false;
|
|
217
|
+
plugin.instance = null;
|
|
218
|
+
this.stats.loaded--;
|
|
219
|
+
|
|
220
|
+
logger.info(`Plugin unloaded: ${pluginName}`);
|
|
221
|
+
this.emit('plugin:unloaded', { name: pluginName });
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Unload all plugins
|
|
226
|
+
*/
|
|
227
|
+
unloadAll() {
|
|
228
|
+
for (const [name, plugin] of this.plugins) {
|
|
229
|
+
if (plugin.loaded) {
|
|
230
|
+
this.unload(name);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* List all registered plugins
|
|
237
|
+
* @returns {Array<string>}
|
|
238
|
+
*/
|
|
239
|
+
list() {
|
|
240
|
+
return Array.from(this.plugins.keys());
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* List loaded plugins
|
|
245
|
+
* @returns {Array<string>}
|
|
246
|
+
*/
|
|
247
|
+
listLoaded() {
|
|
248
|
+
return Array.from(this.plugins.entries())
|
|
249
|
+
.filter(([, plugin]) => plugin.loaded)
|
|
250
|
+
.map(([name]) => name);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Get plugin info
|
|
255
|
+
* @param {string} pluginName
|
|
256
|
+
* @returns {object|null}
|
|
257
|
+
*/
|
|
258
|
+
getInfo(pluginName) {
|
|
259
|
+
const plugin = this.plugins.get(pluginName);
|
|
260
|
+
|
|
261
|
+
if (!plugin) {
|
|
262
|
+
return null;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return {
|
|
266
|
+
name: plugin.name,
|
|
267
|
+
loaded: plugin.loaded,
|
|
268
|
+
path: plugin.path,
|
|
269
|
+
hasInstance: plugin.instance !== null
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Get statistics
|
|
275
|
+
* @returns {object}
|
|
276
|
+
*/
|
|
277
|
+
getStats() {
|
|
278
|
+
return { ...this.stats };
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Register plugin programmatically
|
|
283
|
+
* @param {string} name
|
|
284
|
+
* @param {Plugin} instance
|
|
285
|
+
*/
|
|
286
|
+
register(name, instance) {
|
|
287
|
+
this.plugins.set(name, {
|
|
288
|
+
name,
|
|
289
|
+
path: null,
|
|
290
|
+
loaded: true,
|
|
291
|
+
instance
|
|
292
|
+
});
|
|
293
|
+
|
|
294
|
+
this.stats.registered++;
|
|
295
|
+
this.stats.loaded++;
|
|
296
|
+
|
|
297
|
+
logger.info(`Plugin registered: ${name}`);
|
|
298
|
+
this.emit('plugin:registered', { name });
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
export default PluginManager;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base class for all data source plugins (e.g., GitHub, Slack, Linear)
|
|
3
|
+
*/
|
|
4
|
+
export class DataSourcePlugin {
|
|
5
|
+
/**
|
|
6
|
+
* @param {string} name - Unique name of the data source
|
|
7
|
+
*/
|
|
8
|
+
constructor(name) {
|
|
9
|
+
this.name = name;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Validates if the necessary configuration (tokens, urls) is present.
|
|
14
|
+
* @param {Object} config
|
|
15
|
+
* @returns {Promise<boolean>}
|
|
16
|
+
*/
|
|
17
|
+
async validateConfig(config) {
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Collects data from the source.
|
|
23
|
+
* Generator function that yields documents to be indexed.
|
|
24
|
+
*
|
|
25
|
+
* @param {Object} options
|
|
26
|
+
* @yields {import('./Indexer').IndexableDocument}
|
|
27
|
+
*/
|
|
28
|
+
async *collect(options = {}) {
|
|
29
|
+
throw new Error('Not implemented');
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { pipeline } from '@xenova/transformers';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Abstract base class for embedding providers
|
|
5
|
+
*/
|
|
6
|
+
export class EmbeddingProvider {
|
|
7
|
+
/**
|
|
8
|
+
* Generates an embedding for the given text.
|
|
9
|
+
* @param {string} text
|
|
10
|
+
* @returns {Promise<number[]>}
|
|
11
|
+
*/
|
|
12
|
+
async embed(text) {
|
|
13
|
+
throw new Error('Not implemented');
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Uses @xenova/transformers to generate embeddings locally using ONNX models.
|
|
19
|
+
* Default model: Xenova/all-MiniLM-L6-v2 (384 dimensions)
|
|
20
|
+
*/
|
|
21
|
+
export class TransformersEmbeddingProvider extends EmbeddingProvider {
|
|
22
|
+
constructor(modelName = 'Xenova/all-MiniLM-L6-v2') {
|
|
23
|
+
super();
|
|
24
|
+
this.modelName = modelName;
|
|
25
|
+
this.pipe = null;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async init() {
|
|
29
|
+
if (!this.pipe) {
|
|
30
|
+
// Load the pipeline. This will download the model if not present.
|
|
31
|
+
this.pipe = await pipeline('feature-extraction', this.modelName);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async embed(text) {
|
|
36
|
+
if (!this.pipe) {
|
|
37
|
+
await this.init();
|
|
38
|
+
}
|
|
39
|
+
// pooling: 'mean' and normalize: true are standard for sentence embeddings
|
|
40
|
+
const result = await this.pipe(text, { pooling: 'mean', normalize: true });
|
|
41
|
+
return Array.from(result.data);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Mock provider for testing without loading heavy models.
|
|
47
|
+
*/
|
|
48
|
+
export class MockEmbeddingProvider extends EmbeddingProvider {
|
|
49
|
+
constructor(dimension = 384) {
|
|
50
|
+
super();
|
|
51
|
+
this.dimension = dimension;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async embed(text) {
|
|
55
|
+
// Generate a deterministic-ish vector based on text length for consistent testing
|
|
56
|
+
// or just random if text is ignored.
|
|
57
|
+
// Let's make it pseudo-random based on text hash to be stable.
|
|
58
|
+
let hash = 0;
|
|
59
|
+
for (let i = 0; i < text.length; i++) {
|
|
60
|
+
hash = ((hash << 5) - hash) + text.charCodeAt(i);
|
|
61
|
+
hash |= 0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const vector = [];
|
|
65
|
+
for (let i = 0; i < this.dimension; i++) {
|
|
66
|
+
// Simple pseudo-random generator
|
|
67
|
+
const val = Math.sin(hash + i);
|
|
68
|
+
vector.push(val);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Normalize
|
|
72
|
+
const norm = Math.sqrt(vector.reduce((sum, val) => sum + val * val, 0));
|
|
73
|
+
return vector.map(val => val / norm);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { getLogger } from '../utils/logger.js';
|
|
2
|
+
|
|
3
|
+
const logger = getLogger('Indexer');
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @typedef {Object} IndexableDocument
|
|
7
|
+
* @property {string} text - Content to embed
|
|
8
|
+
* @property {string} id - Unique ID from source
|
|
9
|
+
* @property {Object} metadata - Additional context (source, author, url, etc.)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
export class Indexer {
|
|
13
|
+
/**
|
|
14
|
+
* @param {import('./stores/BaseVectorStore').BaseVectorStore} vectorStore
|
|
15
|
+
*/
|
|
16
|
+
constructor(vectorStore) {
|
|
17
|
+
this.vectorStore = vectorStore;
|
|
18
|
+
this.sources = new Map();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Register a data source plugin
|
|
23
|
+
* @param {import('./DataSourcePlugin').DataSourcePlugin} sourcePlugin
|
|
24
|
+
*/
|
|
25
|
+
registerSource(sourcePlugin) {
|
|
26
|
+
this.sources.set(sourcePlugin.name, sourcePlugin);
|
|
27
|
+
logger.info(`Registered data source: ${sourcePlugin.name}`);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Run indexing for a specific source or all sources
|
|
32
|
+
* @param {string} [sourceName] - Optional source name to run specific indexer
|
|
33
|
+
* @param {Object} options - Options passed to collect method
|
|
34
|
+
*/
|
|
35
|
+
async index(sourceName, options = {}) {
|
|
36
|
+
const sourcesToRun = sourceName
|
|
37
|
+
? [this.sources.get(sourceName)].filter(Boolean)
|
|
38
|
+
: Array.from(this.sources.values());
|
|
39
|
+
|
|
40
|
+
if (sourcesToRun.length === 0) {
|
|
41
|
+
logger.warn(`No sources found to index (requested: ${sourceName || 'all'})`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
for (const source of sourcesToRun) {
|
|
46
|
+
logger.info(`Starting indexing for source: ${source.name}`);
|
|
47
|
+
let count = 0;
|
|
48
|
+
const batch = [];
|
|
49
|
+
const BATCH_SIZE = 10; // Process in small batches
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
for await (const doc of source.collect(options)) {
|
|
53
|
+
batch.push({
|
|
54
|
+
text: doc.text,
|
|
55
|
+
metadata: {
|
|
56
|
+
...doc.metadata,
|
|
57
|
+
sourceId: doc.id,
|
|
58
|
+
source: source.name,
|
|
59
|
+
indexedAt: new Date().toISOString()
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
if (batch.length >= BATCH_SIZE) {
|
|
64
|
+
await this.vectorStore.addDocuments([...batch]);
|
|
65
|
+
count += batch.length;
|
|
66
|
+
batch.length = 0; // Clear batch
|
|
67
|
+
logger.debug(`Indexed ${count} documents from ${source.name}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Flush remaining
|
|
72
|
+
if (batch.length > 0) {
|
|
73
|
+
await this.vectorStore.addDocuments(batch);
|
|
74
|
+
count += batch.length;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
logger.info(`Completed indexing ${source.name}. Total documents: ${count}`);
|
|
78
|
+
} catch (error) {
|
|
79
|
+
logger.error(`Error indexing ${source.name}: ${error.message}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { LanceDBStore } from './stores/LanceDBStore.js';
|
|
2
|
+
import { ChromaDBStore } from './stores/ChromaDBStore.js';
|
|
3
|
+
import { PineconeStore } from './stores/PineconeStore.js';
|
|
4
|
+
|
|
5
|
+
export class VectorStoreFactory {
|
|
6
|
+
/**
|
|
7
|
+
* @param {string} type
|
|
8
|
+
* @param {import('./EmbeddingProvider').EmbeddingProvider} embeddingProvider
|
|
9
|
+
* @param {Object} config
|
|
10
|
+
*/
|
|
11
|
+
static create(type, embeddingProvider, config = {}) {
|
|
12
|
+
switch (type.toLowerCase()) {
|
|
13
|
+
case 'lancedb':
|
|
14
|
+
return new LanceDBStore(embeddingProvider, config.path);
|
|
15
|
+
case 'chroma':
|
|
16
|
+
case 'chromadb':
|
|
17
|
+
return new ChromaDBStore(embeddingProvider, config);
|
|
18
|
+
case 'pinecone':
|
|
19
|
+
return new PineconeStore(embeddingProvider, config);
|
|
20
|
+
default:
|
|
21
|
+
throw new Error(`Unknown VectorStore type: ${type}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { DataSourcePlugin } from '../DataSourcePlugin.js';
|
|
4
|
+
import { TokenizerManager } from '../../utils/tokenizer-adapter.js';
|
|
5
|
+
|
|
6
|
+
export class FileDataSource extends DataSourcePlugin {
|
|
7
|
+
constructor() {
|
|
8
|
+
super('local-files');
|
|
9
|
+
this.tokenizer = new TokenizerManager();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async validateConfig(config) {
|
|
13
|
+
// Requires a target directory
|
|
14
|
+
return !!config.targetDir;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async *collect(options = {}) {
|
|
18
|
+
const targetDir = options.targetDir || process.cwd();
|
|
19
|
+
const maxDepth = options.maxDepth || 5;
|
|
20
|
+
const include = options.include || ['.js', '.md', '.ts', '.json'];
|
|
21
|
+
const exclude = options.exclude || ['node_modules', '.git', 'dist', 'coverage'];
|
|
22
|
+
|
|
23
|
+
yield* this.walkDirectory(targetDir, maxDepth, include, exclude);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async *walkDirectory(dir, depth, include, exclude) {
|
|
27
|
+
if (depth < 0) return;
|
|
28
|
+
|
|
29
|
+
let entries;
|
|
30
|
+
try {
|
|
31
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.warn(`[FileDataSource] Skipping ${dir}: ${error.message}`);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
for (const entry of entries) {
|
|
38
|
+
const fullPath = path.join(dir, entry.name);
|
|
39
|
+
|
|
40
|
+
if (exclude.some(ex => fullPath.includes(ex))) continue;
|
|
41
|
+
|
|
42
|
+
if (entry.isDirectory()) {
|
|
43
|
+
yield* this.walkDirectory(fullPath, depth - 1, include, exclude);
|
|
44
|
+
} else if (entry.isFile()) {
|
|
45
|
+
const ext = path.extname(entry.name);
|
|
46
|
+
if (include.length > 0 && !include.includes(ext)) continue;
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const content = await fs.readFile(fullPath, 'utf-8');
|
|
50
|
+
// Basic text check (skip binaries)
|
|
51
|
+
if (content.includes('\0')) continue;
|
|
52
|
+
|
|
53
|
+
yield {
|
|
54
|
+
id: fullPath,
|
|
55
|
+
text: content,
|
|
56
|
+
metadata: {
|
|
57
|
+
source: 'file-system',
|
|
58
|
+
path: fullPath,
|
|
59
|
+
extension: ext,
|
|
60
|
+
filename: entry.name,
|
|
61
|
+
size: content.length
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
} catch (error) {
|
|
65
|
+
console.warn(`[FileDataSource] Failed to read ${fullPath}: ${error.message}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @typedef {Object} VectorDocument
|
|
3
|
+
* @property {string} text
|
|
4
|
+
* @property {Array<number>} vector
|
|
5
|
+
* @property {string} id
|
|
6
|
+
* @property {Object} metadata
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* @typedef {Object} SearchResult
|
|
11
|
+
* @property {string} text
|
|
12
|
+
* @property {number} score
|
|
13
|
+
* @property {Object} metadata
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
export class BaseVectorStore {
|
|
17
|
+
/**
|
|
18
|
+
* @param {import('../EmbeddingProvider').EmbeddingProvider} embeddingProvider
|
|
19
|
+
*/
|
|
20
|
+
constructor(embeddingProvider) {
|
|
21
|
+
this.embeddingProvider = embeddingProvider;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async init() {
|
|
25
|
+
throw new Error('Method not implemented');
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* @param {Array<{text: string, metadata: Object}>} docs
|
|
30
|
+
*/
|
|
31
|
+
async addDocuments(docs) {
|
|
32
|
+
throw new Error('Method not implemented');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param {string} queryText
|
|
37
|
+
* @param {number} limit
|
|
38
|
+
* @returns {Promise<Array<SearchResult>>}
|
|
39
|
+
*/
|
|
40
|
+
async search(queryText, limit = 5) {
|
|
41
|
+
throw new Error('Method not implemented');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { BaseVectorStore } from './BaseVectorStore.js';
|
|
2
|
+
|
|
3
|
+
export class ChromaDBStore extends BaseVectorStore {
|
|
4
|
+
/**
|
|
5
|
+
* @param {import('../EmbeddingProvider').EmbeddingProvider} embeddingProvider
|
|
6
|
+
* @param {Object} config
|
|
7
|
+
* @param {string} [config.collectionName]
|
|
8
|
+
* @param {string} [config.url]
|
|
9
|
+
*/
|
|
10
|
+
constructor(embeddingProvider, config = {}) {
|
|
11
|
+
super(embeddingProvider);
|
|
12
|
+
this.collectionName = config.collectionName || 'ctxman-docs';
|
|
13
|
+
this.url = config.url || 'http://localhost:8000';
|
|
14
|
+
this.client = null;
|
|
15
|
+
this.collection = null;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async init() {
|
|
19
|
+
if (this.client) return;
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const { ChromaClient } = await import('chromadb');
|
|
23
|
+
this.client = new ChromaClient({ path: this.url });
|
|
24
|
+
|
|
25
|
+
this.collection = await this.client.getOrCreateCollection({
|
|
26
|
+
name: this.collectionName,
|
|
27
|
+
});
|
|
28
|
+
} catch (error) {
|
|
29
|
+
if (error.code === 'ERR_MODULE_NOT_FOUND') {
|
|
30
|
+
throw new Error('Please install chromadb to use ChromaDBStore: npm install chromadb');
|
|
31
|
+
}
|
|
32
|
+
throw error;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async addDocuments(docs) {
|
|
37
|
+
if (!this.collection) await this.init();
|
|
38
|
+
if (docs.length === 0) return;
|
|
39
|
+
|
|
40
|
+
const ids = docs.map(d => d.id || crypto.randomUUID());
|
|
41
|
+
const metadatas = docs.map(d => d.metadata || {});
|
|
42
|
+
const documents = docs.map(d => d.text);
|
|
43
|
+
|
|
44
|
+
// Chroma can handle embeddings, but we have a provider.
|
|
45
|
+
// If we want to use our provider:
|
|
46
|
+
const embeddings = [];
|
|
47
|
+
for (const doc of docs) {
|
|
48
|
+
embeddings.push(await this.embeddingProvider.embed(doc.text));
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
await this.collection.add({
|
|
52
|
+
ids,
|
|
53
|
+
embeddings,
|
|
54
|
+
metadatas,
|
|
55
|
+
documents
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async search(queryText, limit = 5) {
|
|
60
|
+
if (!this.collection) await this.init();
|
|
61
|
+
|
|
62
|
+
const queryEmbedding = await this.embeddingProvider.embed(queryText);
|
|
63
|
+
|
|
64
|
+
const results = await this.collection.query({
|
|
65
|
+
queryEmbeddings: [queryEmbedding],
|
|
66
|
+
nResults: limit
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Chroma returns arrays of arrays
|
|
70
|
+
if (!results.ids || results.ids.length === 0) return [];
|
|
71
|
+
|
|
72
|
+
const hits = [];
|
|
73
|
+
const count = results.ids[0].length;
|
|
74
|
+
|
|
75
|
+
for (let i = 0; i < count; i++) {
|
|
76
|
+
hits.push({
|
|
77
|
+
text: results.documents[0][i],
|
|
78
|
+
score: results.distances ? results.distances[0][i] : 0,
|
|
79
|
+
metadata: results.metadatas[0][i]
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return hits;
|
|
84
|
+
}
|
|
85
|
+
}
|