@kb-labs/mind-core 2.93.0 → 2.96.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/README.md +17 -179
- package/dist/index.d.ts +397 -332
- package/dist/index.js +1340 -515
- package/dist/index.js.map +1 -1
- package/dist/services-CdtgGHNc.d.ts +14 -0
- package/dist/testing.d.ts +68 -0
- package/dist/testing.js +169 -0
- package/dist/testing.js.map +1 -0
- package/package.json +13 -8
package/dist/index.d.ts
CHANGED
|
@@ -1,402 +1,467 @@
|
|
|
1
|
-
|
|
2
|
-
import {
|
|
1
|
+
import { AgentSourceKind, IndexRequest, IndexResponse, SearchRequest, SearchResponse, QueryRequest, AgentResponse, ExploreRequest, ExploreResponse, ReindexRequest, DropRequest, DropResponse, SyncResponse, SyncListResponse, SyncStatusResponse, StatusResponse, HealthResponse, MindConfig, StageTrace, Trace, AgentWarning, AgentQueryMode, SnippetMode, AgentSource, SearchResult } from '@kb-labs/mind-contracts';
|
|
2
|
+
import { M as MindServices } from './services-CdtgGHNc.js';
|
|
3
|
+
import { IStorage, ILLM, ICache } from '@kb-labs/sdk';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
* Adaptive source-file discovery via `globby` over the real filesystem.
|
|
7
|
+
*
|
|
8
|
+
* Reads the repo relative to `cwd` — NOT the platform `storage` adapter, which
|
|
9
|
+
* is a blob store that recurses into node_modules and chokes on pnpm symlink
|
|
10
|
+
* chains. Symlinks are not followed and dependency/build dirs are excluded.
|
|
11
|
+
*
|
|
12
|
+
* No language allowlist: we glob every file and KEEP whatever looks like source
|
|
13
|
+
* text, excluding only binaries, media, archives, data blobs, lockfiles and
|
|
14
|
+
* oversized/generated files. A new language (`.cs`, `.vue`, `.kt`, …) is indexed
|
|
15
|
+
* automatically — discovery adapts to the repo instead of being hand-curated.
|
|
16
|
+
*
|
|
17
|
+
* Returns paths relative to `cwd`.
|
|
18
|
+
*/
|
|
19
|
+
/** Scope the engine discovers against — config-driven include/exclude globs. */
|
|
20
|
+
interface DiscoverScope {
|
|
21
|
+
/** Repo-relative roots/globs to index. Empty/undefined ⇒ whole repo. */
|
|
22
|
+
include?: string[];
|
|
23
|
+
/** Globs subtracted from the include set (added to the ignore list). */
|
|
24
|
+
exclude?: string[];
|
|
13
25
|
}
|
|
14
|
-
/**
|
|
15
|
-
* Maps MindError codes to CLI exit codes
|
|
16
|
-
*/
|
|
17
|
-
declare function getExitCode(err: MindError): number;
|
|
18
|
-
/**
|
|
19
|
-
* Error codes with their standard hints
|
|
20
|
-
*/
|
|
21
|
-
declare const ERROR_HINTS: {
|
|
22
|
-
readonly MIND_NO_GIT: "Initialize git repository or run from a git repository";
|
|
23
|
-
readonly MIND_FS_TIMEOUT: "File system operation timed out - try increasing time budget";
|
|
24
|
-
readonly MIND_PARSE_ERROR: "Failed to parse file - check syntax and try again";
|
|
25
|
-
readonly MIND_PACK_BUDGET_EXCEEDED: "Context pack exceeds token budget - reduce content or increase budget";
|
|
26
|
-
readonly MIND_FORBIDDEN: "Operation not permitted - check file permissions";
|
|
27
|
-
readonly MIND_TIME_BUDGET: "Time budget exceeded - operation completed partially";
|
|
28
|
-
readonly MIND_BAD_FLAGS: "Invalid command line flags - check values and try again";
|
|
29
|
-
readonly MIND_INVALID_FLAG: "Invalid flag value - check format and try again";
|
|
30
|
-
readonly MIND_BUNDLE_TIMEOUT: "Bundle operation timed out - skipped bundle information";
|
|
31
|
-
readonly MIND_FEED_ERROR: "Mind feed operation failed - check logs for details";
|
|
32
|
-
readonly MIND_INIT_ERROR: "Mind initialization failed - check permissions and try again";
|
|
33
|
-
readonly MIND_UPDATE_ERROR: "Mind update operation failed - check logs for details";
|
|
34
|
-
readonly MIND_PACK_ERROR: "Mind pack operation failed - check logs for details";
|
|
35
|
-
readonly MIND_GIT_ERROR: "Git operation failed - check git repository status";
|
|
36
|
-
readonly MIND_INDEX_NOT_FOUND: "Mind indexes not found - run \"kb mind init\" first";
|
|
37
|
-
readonly MIND_INVALID_PATH: "Invalid file or directory path - check path exists and is accessible";
|
|
38
|
-
readonly MIND_DEPENDENCY_ERROR: "Dependency resolution failed - check package configuration";
|
|
39
|
-
readonly MIND_BUILD_ERROR: "Build operation failed - check configuration and try again";
|
|
40
|
-
};
|
|
41
|
-
type ErrorCode = keyof typeof ERROR_HINTS;
|
|
42
|
-
/**
|
|
43
|
-
* Create a MindError with standardized code and hint
|
|
44
|
-
*/
|
|
45
|
-
declare function createMindError(code: ErrorCode, message: string, meta?: unknown): MindError;
|
|
46
|
-
/**
|
|
47
|
-
* Create a MindError from a generic error
|
|
48
|
-
*/
|
|
49
|
-
declare function wrapError(error: unknown, code?: ErrorCode): MindError;
|
|
50
|
-
/**
|
|
51
|
-
* Check if an error is a MindError
|
|
52
|
-
*/
|
|
53
|
-
declare function isMindError(error: unknown): error is MindError;
|
|
54
26
|
|
|
55
27
|
/**
|
|
56
|
-
*
|
|
28
|
+
* Internal engine types (not wire contracts).
|
|
29
|
+
*
|
|
30
|
+
* A `Chunk` is the unit of indexing/retrieval. Chunks are stored two ways:
|
|
31
|
+
* - in the vector store (one `VectorRecord` per chunk, scoped by namespace=indexId)
|
|
32
|
+
* for semantic search;
|
|
33
|
+
* - in a per-index `chunks.json` via `IStorage` as the corpus for BM25 and
|
|
34
|
+
* listing/status.
|
|
57
35
|
*/
|
|
58
36
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
37
|
+
interface Chunk {
|
|
38
|
+
/** Stable id: `${path}#${startLine}-${endLine}`. */
|
|
39
|
+
id: string;
|
|
40
|
+
path: string;
|
|
41
|
+
startLine: number;
|
|
42
|
+
endLine: number;
|
|
43
|
+
text: string;
|
|
44
|
+
kind: AgentSourceKind;
|
|
64
45
|
}
|
|
65
|
-
|
|
66
|
-
interface
|
|
67
|
-
|
|
68
|
-
|
|
46
|
+
/** Metadata stored alongside each vector record. */
|
|
47
|
+
interface ChunkMeta extends Record<string, unknown> {
|
|
48
|
+
path: string;
|
|
49
|
+
startLine: number;
|
|
50
|
+
endLine: number;
|
|
51
|
+
text: string;
|
|
52
|
+
kind: AgentSourceKind;
|
|
69
53
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
54
|
+
/** Per-file bookkeeping. `hash` enables incremental (delta) re-indexing. */
|
|
55
|
+
interface FileEntry {
|
|
56
|
+
chunks: number;
|
|
57
|
+
indexedAt: string;
|
|
58
|
+
/** Content hash; unchanged hash ⇒ file skipped on re-index. */
|
|
59
|
+
hash: string;
|
|
73
60
|
}
|
|
61
|
+
/** Persisted per-index corpus (source of truth for BM25 + listing). */
|
|
62
|
+
interface IndexManifest {
|
|
63
|
+
indexId: string;
|
|
64
|
+
/** Chunks keyed by id. */
|
|
65
|
+
chunks: Chunk[];
|
|
66
|
+
/** Per-path bookkeeping for incremental sync. */
|
|
67
|
+
files: Record<string, FileEntry>;
|
|
68
|
+
updatedAt: string | null;
|
|
69
|
+
}
|
|
70
|
+
declare function chunkId(path: string, startLine: number, endLine: number): string;
|
|
71
|
+
/** Derive a source kind from a file path. */
|
|
72
|
+
declare function kindFromPath(path: string): AgentSourceKind;
|
|
74
73
|
|
|
75
74
|
/**
|
|
76
|
-
*
|
|
75
|
+
* Sliding-window chunker (Phase 2 baseline; AST chunking is layered in Phase 3).
|
|
76
|
+
*
|
|
77
|
+
* Splits a file into line-aligned chunks of roughly `maxTokens` tokens with
|
|
78
|
+
* `overlapTokens` of overlap. Token count is approximated by whitespace words —
|
|
79
|
+
* good enough for windowing; real tokenization is not needed here.
|
|
77
80
|
*/
|
|
78
81
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
*/
|
|
83
|
-
declare class DefaultTokenEstimator implements ITokenEstimator {
|
|
84
|
-
private readonly charsPerToken;
|
|
85
|
-
private readonly codeBonus;
|
|
86
|
-
private readonly punctuationWeight;
|
|
87
|
-
estimate(text: string): number;
|
|
88
|
-
truncate(text: string, maxTokens: number, mode: "start" | "middle" | "end"): string;
|
|
82
|
+
interface ChunkOptions {
|
|
83
|
+
maxTokens: number;
|
|
84
|
+
overlapTokens: number;
|
|
89
85
|
}
|
|
90
|
-
|
|
91
|
-
* Default token estimator instance
|
|
92
|
-
*/
|
|
93
|
-
declare const defaultTokenEstimator: DefaultTokenEstimator;
|
|
94
|
-
/**
|
|
95
|
-
* Estimate tokens using default strategy
|
|
96
|
-
*/
|
|
97
|
-
declare function estimateTokens(text: string): number;
|
|
98
|
-
/**
|
|
99
|
-
* Truncate text to token limit using default strategy
|
|
100
|
-
*/
|
|
101
|
-
declare function truncateToTokens(text: string, maxTokens: number, mode?: "start" | "middle" | "end"): string;
|
|
86
|
+
declare function slidingWindowChunks(path: string, content: string, opts: ChunkOptions): Chunk[];
|
|
102
87
|
|
|
103
88
|
/**
|
|
104
|
-
*
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
*
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Compute SHA256 hash for Buffer content
|
|
112
|
-
*/
|
|
113
|
-
declare function sha256Buffer(buffer: Buffer): string;
|
|
114
|
-
/**
|
|
115
|
-
* Compute SHA256 hash for file content (streaming for large files)
|
|
89
|
+
* Ingest pipeline: discover -> (hash-delta) -> chunk -> embed ->
|
|
90
|
+
* upsert(namespace=indexId), then persist the per-index manifest.
|
|
91
|
+
*
|
|
92
|
+
* Incremental by default: only files whose content hash changed since the last
|
|
93
|
+
* index are re-chunked/re-embedded; unchanged files keep their existing chunks
|
|
94
|
+
* (and vectors), removed files are pruned. `full` forces a clean rebuild.
|
|
116
95
|
*/
|
|
117
|
-
|
|
96
|
+
|
|
97
|
+
/** Staged progress events emitted during ingest (for CLI/REST progress UIs). */
|
|
98
|
+
type IngestProgress = {
|
|
99
|
+
stage: 'discover';
|
|
100
|
+
files: number;
|
|
101
|
+
} | {
|
|
102
|
+
stage: 'delta';
|
|
103
|
+
toIndex: number;
|
|
104
|
+
unchanged: number;
|
|
105
|
+
removed: number;
|
|
106
|
+
} | {
|
|
107
|
+
stage: 'chunk';
|
|
108
|
+
chunks: number;
|
|
109
|
+
} | {
|
|
110
|
+
stage: 'embed';
|
|
111
|
+
done: number;
|
|
112
|
+
total: number;
|
|
113
|
+
} | {
|
|
114
|
+
stage: 'upsert';
|
|
115
|
+
count: number;
|
|
116
|
+
} | {
|
|
117
|
+
stage: 'save';
|
|
118
|
+
};
|
|
119
|
+
interface IngestInput {
|
|
120
|
+
indexId: string;
|
|
121
|
+
/** Workspace root that source paths are resolved against. */
|
|
122
|
+
cwd: string;
|
|
123
|
+
/** Config-driven include/exclude globs for discovery. */
|
|
124
|
+
scope?: DiscoverScope;
|
|
125
|
+
chunk: ChunkOptions;
|
|
126
|
+
/** Use structure-aware chunking for code. */
|
|
127
|
+
ast: boolean;
|
|
128
|
+
/** Force a full rebuild instead of incremental delta. */
|
|
129
|
+
full?: boolean;
|
|
130
|
+
/** ISO timestamp for manifest bookkeeping (injectable for determinism). */
|
|
131
|
+
now: string;
|
|
132
|
+
/** Optional staged-progress sink (best-effort; never affects results). */
|
|
133
|
+
onProgress?: (event: IngestProgress) => void;
|
|
134
|
+
}
|
|
135
|
+
interface IngestResult {
|
|
136
|
+
filesIndexed: number;
|
|
137
|
+
chunks: number;
|
|
138
|
+
/** Files (re)embedded this run (new + changed). */
|
|
139
|
+
added: number;
|
|
140
|
+
updated: number;
|
|
141
|
+
/** Files removed from the index (gone from disk). */
|
|
142
|
+
removed: number;
|
|
143
|
+
/** Files skipped because their content hash was unchanged. */
|
|
144
|
+
unchanged: number;
|
|
145
|
+
}
|
|
146
|
+
declare function ingest(input: IngestInput, services: MindServices): Promise<IngestResult>;
|
|
118
147
|
|
|
119
148
|
/**
|
|
120
|
-
*
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
*
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Convert POSIX path back to platform-specific format
|
|
128
|
-
*/
|
|
129
|
-
declare function fromPosix(posixPath: string): string;
|
|
130
|
-
/**
|
|
131
|
-
* Find workspace root by looking for git repository or monorepo indicators
|
|
132
|
-
* Searches up the directory tree from cwd
|
|
149
|
+
* Mind facade — the single object the plugin (CLI + REST) talks to.
|
|
150
|
+
*
|
|
151
|
+
* `createMind(services, config)` returns verbs that both the CLI commands and
|
|
152
|
+
* REST handlers call identically, so behaviour is shared by construction.
|
|
153
|
+
* Phase 2 ships `index` / `search` / `status`; `ask`, `sync`, `reindex` are
|
|
154
|
+
* layered in later phases.
|
|
133
155
|
*/
|
|
134
|
-
|
|
156
|
+
|
|
157
|
+
interface Mind {
|
|
158
|
+
index(req: IndexRequest, onProgress?: (event: IngestProgress) => void): Promise<IndexResponse>;
|
|
159
|
+
search(req: SearchRequest): Promise<SearchResponse>;
|
|
160
|
+
ask(req: QueryRequest): Promise<AgentResponse>;
|
|
161
|
+
explore(req: ExploreRequest): Promise<ExploreResponse>;
|
|
162
|
+
reindex(req: ReindexRequest): Promise<IndexResponse>;
|
|
163
|
+
drop(req: DropRequest): Promise<DropResponse>;
|
|
164
|
+
syncAdd(paths: string[], indexId?: string): Promise<SyncResponse>;
|
|
165
|
+
syncUpdate(paths: string[], indexId?: string): Promise<SyncResponse>;
|
|
166
|
+
syncDelete(paths: string[], indexId?: string): Promise<SyncResponse>;
|
|
167
|
+
syncList(indexId?: string): Promise<SyncListResponse>;
|
|
168
|
+
syncStatus(indexId?: string): Promise<SyncStatusResponse>;
|
|
169
|
+
status(indexId?: string): Promise<StatusResponse>;
|
|
170
|
+
health(): Promise<HealthResponse>;
|
|
171
|
+
}
|
|
172
|
+
interface CreateMindOptions {
|
|
173
|
+
/** Workspace root that source paths are resolved against (default: process.cwd()). */
|
|
174
|
+
cwd?: string;
|
|
175
|
+
/** Injectable clock for deterministic tests. */
|
|
176
|
+
now?: () => number;
|
|
177
|
+
/** Injectable ISO timestamp source for manifest bookkeeping. */
|
|
178
|
+
isoNow?: () => string;
|
|
179
|
+
}
|
|
180
|
+
declare function createMind(services: MindServices, config: MindConfig, options?: CreateMindOptions): Mind;
|
|
181
|
+
|
|
135
182
|
/**
|
|
136
|
-
*
|
|
183
|
+
* Pipeline primitive.
|
|
184
|
+
*
|
|
185
|
+
* Every stage is a pure function `(input, services) => output`. A `Tracer`
|
|
186
|
+
* times each stage and collects `StageTrace` entries so the full query flow
|
|
187
|
+
* (retrieve → fuse → rerank → verify → compress → synthesize) is observable.
|
|
188
|
+
* Modes (instant/auto/thinking) are just configs deciding which stages run —
|
|
189
|
+
* never separate code paths.
|
|
137
190
|
*/
|
|
138
|
-
|
|
191
|
+
|
|
192
|
+
type Stage<I, O> = (input: I, services: MindServices) => Promise<O>;
|
|
193
|
+
/** Monotonic clock; injectable so tests stay deterministic. */
|
|
194
|
+
type Clock = () => number;
|
|
195
|
+
/**
|
|
196
|
+
* Collects per-stage traces for one request. `run` times a stage and records
|
|
197
|
+
* its duration + output size.
|
|
198
|
+
*/
|
|
199
|
+
declare class Tracer {
|
|
200
|
+
private readonly requestId;
|
|
201
|
+
private readonly mode;
|
|
202
|
+
private readonly clock;
|
|
203
|
+
private readonly stages;
|
|
204
|
+
constructor(requestId: string, mode: string, clock?: Clock);
|
|
205
|
+
run<I, O>(stage: string, input: I, services: MindServices, fn: Stage<I, O>): Promise<O>;
|
|
206
|
+
/** Record a stage trace manually (for stages not wrapped by `run`). */
|
|
207
|
+
record(trace: StageTrace): void;
|
|
208
|
+
build(totalMs: number): Trace;
|
|
209
|
+
}
|
|
210
|
+
|
|
139
211
|
/**
|
|
140
|
-
*
|
|
212
|
+
* Structure-aware chunking for code (AST-lite).
|
|
213
|
+
*
|
|
214
|
+
* Splits a source file at top-level declaration boundaries (functions, classes,
|
|
215
|
+
* interfaces, etc.) so chunks align with logical units instead of arbitrary
|
|
216
|
+
* windows. Oversized blocks fall back to the sliding window. This is a
|
|
217
|
+
* dependency-free approximation of AST chunking; a tree-sitter backend can
|
|
218
|
+
* replace it later behind the same `chunkFile` entry without touching callers.
|
|
141
219
|
*/
|
|
142
|
-
|
|
220
|
+
|
|
221
|
+
declare function structuralChunks(path: string, content: string, opts: ChunkOptions): Chunk[];
|
|
222
|
+
/** Dispatch: structure-aware for code when enabled, else sliding window. */
|
|
223
|
+
declare function chunkFile(path: string, content: string, opts: ChunkOptions, ast: boolean): Chunk[];
|
|
143
224
|
|
|
144
225
|
/**
|
|
145
|
-
*
|
|
226
|
+
* BM25 keyword ranking over the in-memory chunk corpus.
|
|
227
|
+
*
|
|
228
|
+
* Self-contained Okapi BM25. Tokenization lowercases and splits on
|
|
229
|
+
* non-alphanumerics, additionally splitting camelCase and snake_case so code
|
|
230
|
+
* identifiers (`getUserId`, `user_id`) match natural-language queries.
|
|
146
231
|
*/
|
|
232
|
+
|
|
233
|
+
interface Ranked {
|
|
234
|
+
id: string;
|
|
235
|
+
score: number;
|
|
236
|
+
}
|
|
237
|
+
declare function tokenize(text: string): string[];
|
|
238
|
+
declare function bm25Search(chunks: Chunk[], query: string, limit: number): Ranked[];
|
|
239
|
+
|
|
147
240
|
/**
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
* Cosine similarity measures the cosine of the angle between two vectors,
|
|
151
|
-
* producing a value between -1 and 1, where:
|
|
152
|
-
* - 1 means vectors point in the same direction (identical)
|
|
153
|
-
* - 0 means vectors are orthogonal (no similarity)
|
|
154
|
-
* - -1 means vectors point in opposite directions
|
|
155
|
-
*
|
|
156
|
-
* @param a - First vector (array of numbers)
|
|
157
|
-
* @param b - Second vector (array of numbers)
|
|
158
|
-
* @returns Similarity score [0-1], or 0 if vectors have different lengths or zero magnitudes
|
|
241
|
+
* Reciprocal Rank Fusion (RRF) with intent-adaptive weights.
|
|
159
242
|
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
* const similarity = cosineSimilarity([1, 2, 3], [4, 5, 6]);
|
|
163
|
-
* console.log(similarity); // ~0.974
|
|
164
|
-
* ```
|
|
243
|
+
* Fuses ranked lists by `weight * 1/(k + rank)`. Weights shift the balance
|
|
244
|
+
* between the semantic (vector) and keyword (BM25) lists based on query intent.
|
|
165
245
|
*/
|
|
166
|
-
|
|
246
|
+
|
|
247
|
+
type QueryIntent = 'lookup' | 'concept' | 'architecture';
|
|
248
|
+
/** Which retrieval signal surfaced a result — the "why" + proof-of-value vs grep. */
|
|
249
|
+
type MatchedBy = 'lexical' | 'semantic' | 'both';
|
|
250
|
+
interface WeightedList {
|
|
251
|
+
ranked: Ranked[];
|
|
252
|
+
weight: number;
|
|
253
|
+
/** Provenance label for this list (so the fused result knows where it came from). */
|
|
254
|
+
label?: Exclude<MatchedBy, 'both'>;
|
|
255
|
+
}
|
|
256
|
+
interface FusedRanked extends Ranked {
|
|
257
|
+
matchedBy: MatchedBy;
|
|
258
|
+
}
|
|
259
|
+
/** Vector/BM25 weighting per intent (vector favored for meaning, BM25 for exact terms). */
|
|
260
|
+
declare function intentWeights(intent?: QueryIntent): {
|
|
261
|
+
vector: number;
|
|
262
|
+
bm25: number;
|
|
263
|
+
};
|
|
264
|
+
declare function rrfFuse(lists: WeightedList[], k: number): FusedRanked[];
|
|
265
|
+
|
|
167
266
|
/**
|
|
168
|
-
*
|
|
169
|
-
*
|
|
170
|
-
* @param a - First vector
|
|
171
|
-
* @param b - Second vector
|
|
172
|
-
* @returns Dot product, or 0 if vectors have different lengths
|
|
173
|
-
*
|
|
174
|
-
* @example
|
|
175
|
-
* ```typescript
|
|
176
|
-
* const dot = dotProduct([1, 2, 3], [4, 5, 6]);
|
|
177
|
-
* console.log(dot); // 32
|
|
178
|
-
* ```
|
|
267
|
+
* Retrieval pipeline: BM25 (over manifest corpus) + vector (platform store),
|
|
268
|
+
* fused with intent-adaptive RRF, mapped back to chunks.
|
|
179
269
|
*/
|
|
180
|
-
|
|
270
|
+
|
|
271
|
+
interface RetrieveInput {
|
|
272
|
+
text: string;
|
|
273
|
+
indexId: string;
|
|
274
|
+
limit: number;
|
|
275
|
+
intent?: QueryIntent;
|
|
276
|
+
rrfK: number;
|
|
277
|
+
/** HyDE: embed an LLM-generated hypothetical doc for the vector search. */
|
|
278
|
+
hyde?: boolean;
|
|
279
|
+
/** Query expansion: append LLM-suggested related terms to the BM25 query. */
|
|
280
|
+
expand?: boolean;
|
|
281
|
+
}
|
|
282
|
+
interface RankedChunk {
|
|
283
|
+
chunk: Chunk;
|
|
284
|
+
/** Fused RRF score (best first). */
|
|
285
|
+
score: number;
|
|
286
|
+
/** Which signal surfaced this chunk (lexical/semantic/both) — provenance. */
|
|
287
|
+
matchedBy: MatchedBy;
|
|
288
|
+
}
|
|
289
|
+
interface RetrieveOutput {
|
|
290
|
+
/** Fused, ranked chunks (best first), truncated to `limit`. */
|
|
291
|
+
ranked: RankedChunk[];
|
|
292
|
+
/** Raw cosine confidence proxy from the vector list (top-3 avg). */
|
|
293
|
+
confidence: number;
|
|
294
|
+
/** Share of returned results found semantic-only (grep would miss them). */
|
|
295
|
+
semanticWinRate: number;
|
|
296
|
+
}
|
|
297
|
+
declare function retrieve(input: RetrieveInput, services: MindServices): Promise<RetrieveOutput>;
|
|
298
|
+
|
|
181
299
|
/**
|
|
182
|
-
*
|
|
300
|
+
* Heuristic reranking of fused candidates.
|
|
183
301
|
*
|
|
184
|
-
*
|
|
185
|
-
*
|
|
302
|
+
* Lightweight, deterministic signals layered on the fused RRF score:
|
|
303
|
+
* - exact query-term coverage in the chunk text (keyword precision)
|
|
304
|
+
* - identifier hit: a query token appearing verbatim (good for code lookups)
|
|
186
305
|
*
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
* const mag = magnitude([3, 4]);
|
|
190
|
-
* console.log(mag); // 5
|
|
191
|
-
* ```
|
|
306
|
+
* An optional LLM reranker can be layered later; this heuristic pass is always
|
|
307
|
+
* available and needs no model.
|
|
192
308
|
*/
|
|
193
|
-
|
|
309
|
+
|
|
310
|
+
declare function rerank(ranked: RankedChunk[], query: string): RankedChunk[];
|
|
311
|
+
|
|
194
312
|
/**
|
|
195
|
-
*
|
|
313
|
+
* Semantic-ish dedup of ranked chunks via token-set Jaccard similarity.
|
|
196
314
|
*
|
|
197
|
-
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
* @example
|
|
201
|
-
* ```typescript
|
|
202
|
-
* const normalized = normalize([3, 4]);
|
|
203
|
-
* console.log(normalized); // [0.6, 0.8]
|
|
204
|
-
* ```
|
|
315
|
+
* Keeps the highest-ranked chunk and drops later near-duplicates (e.g. the same
|
|
316
|
+
* code re-surfaced by both BM25 and vector lists, or overlapping windows).
|
|
317
|
+
* Operates on the small post-fusion list, so O(n²) is fine.
|
|
205
318
|
*/
|
|
206
|
-
|
|
319
|
+
|
|
320
|
+
declare function dedupRanked(ranked: RankedChunk[], threshold?: number): RankedChunk[];
|
|
207
321
|
|
|
208
322
|
/**
|
|
209
|
-
*
|
|
323
|
+
* Anti-hallucination verification + confidence stack.
|
|
210
324
|
*
|
|
211
|
-
*
|
|
212
|
-
*
|
|
325
|
+
* Each retrieved chunk is verified against the live source via `IStorage`:
|
|
326
|
+
* file exists (0.7) + snippet still present (0.3). The verification rate scales
|
|
327
|
+
* the retrieval confidence — a stale or fabricated source drags confidence down.
|
|
328
|
+
* Mirrors the legacy mind concept (source-verifier + confidence floor).
|
|
213
329
|
*/
|
|
214
330
|
|
|
215
|
-
interface
|
|
216
|
-
/**
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
filePrefix?: string;
|
|
226
|
-
/**
|
|
227
|
-
* Maximum number of records per file before rotation
|
|
228
|
-
* @default 1000
|
|
229
|
-
*/
|
|
230
|
-
maxRecordsPerFile?: number;
|
|
231
|
-
/**
|
|
232
|
-
* Maximum number of files to keep (oldest deleted first)
|
|
233
|
-
* @default 30
|
|
234
|
-
*/
|
|
235
|
-
maxFiles?: number;
|
|
331
|
+
interface VerificationResult {
|
|
332
|
+
/** 0..1 fraction of (weighted) verification across chunks. */
|
|
333
|
+
rate: number;
|
|
334
|
+
/** Per-chunk verification score (0..1), aligned with input order. */
|
|
335
|
+
perChunk: number[];
|
|
336
|
+
}
|
|
337
|
+
declare function verifySources(ranked: RankedChunk[], storage: IStorage): Promise<VerificationResult>;
|
|
338
|
+
interface ConfidenceResult {
|
|
339
|
+
confidence: number;
|
|
340
|
+
warnings: AgentWarning[];
|
|
236
341
|
}
|
|
342
|
+
/** Combine retrieval confidence with the verification rate; warn below floor. */
|
|
343
|
+
declare function computeConfidence(retrievalConfidence: number, verificationRate: number, floor: number): ConfidenceResult;
|
|
344
|
+
|
|
237
345
|
/**
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
* Features:
|
|
241
|
-
* - JSONL format (one JSON object per line)
|
|
242
|
-
* - Date-based file segmentation (YYYYMMDD-timestamp.jsonl)
|
|
243
|
-
* - Automatic rotation when maxRecordsPerFile reached
|
|
244
|
-
* - Automatic cleanup when maxFiles exceeded
|
|
245
|
-
* - Sorted file iteration (oldest to newest)
|
|
346
|
+
* Field-checker — an anti-hallucination signal ported from the legacy engine.
|
|
246
347
|
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
*
|
|
254
|
-
* async find(criteria: any): Promise<MyRecord[]> {
|
|
255
|
-
* return this.readRecords((rec) => rec.id === criteria.id);
|
|
256
|
-
* }
|
|
257
|
-
* }
|
|
258
|
-
* ```
|
|
348
|
+
* An LLM answer that cites code should only name symbols that actually appear
|
|
349
|
+
* in the retrieved sources. We extract code-like symbols from the answer
|
|
350
|
+
* (backtick spans, camelCase/dotted/underscored identifiers, file names) and
|
|
351
|
+
* check each against the retrieved chunks (text + path). The grounded fraction
|
|
352
|
+
* folds into the confidence stack, and any ungrounded terms are surfaced as a
|
|
353
|
+
* warning — a cheap, deterministic guard against fabricated APIs.
|
|
259
354
|
*/
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
* Automatically handles:
|
|
271
|
-
* - File rotation when maxRecordsPerFile exceeded
|
|
272
|
-
* - Cleanup when maxFiles exceeded
|
|
273
|
-
* - JSONL formatting
|
|
274
|
-
*
|
|
275
|
-
* @param record - Record to append
|
|
276
|
-
*/
|
|
277
|
-
protected appendRecord(record: TRecord): Promise<void>;
|
|
278
|
-
/**
|
|
279
|
-
* Read records from all files, optionally filtering
|
|
280
|
-
*
|
|
281
|
-
* @param filter - Optional filter function
|
|
282
|
-
* @param limit - Maximum number of records to return
|
|
283
|
-
* @returns Array of records matching filter
|
|
284
|
-
*/
|
|
285
|
-
protected readRecords(filter?: (record: TRecord) => boolean, limit?: number): Promise<TRecord[]>;
|
|
286
|
-
/**
|
|
287
|
-
* Get the current writable file path
|
|
288
|
-
*
|
|
289
|
-
* Creates a new segment if:
|
|
290
|
-
* - No files exist
|
|
291
|
-
* - Latest file has >= maxRecordsPerFile records
|
|
292
|
-
*
|
|
293
|
-
* @returns Path to writable file
|
|
294
|
-
*/
|
|
295
|
-
protected getWritableFile(): Promise<string>;
|
|
296
|
-
/**
|
|
297
|
-
* Get all store files sorted by timestamp (oldest to newest)
|
|
298
|
-
*
|
|
299
|
-
* @returns Sorted array of file paths
|
|
300
|
-
*/
|
|
301
|
-
protected getFilesSorted(): Promise<string[]>;
|
|
302
|
-
/**
|
|
303
|
-
* Generate a segment file path from timestamp
|
|
304
|
-
*
|
|
305
|
-
* Format: {filePrefix}YYYYMMDD-{timestamp}.jsonl
|
|
306
|
-
* Example: history-20251209-1733769000123.jsonl
|
|
307
|
-
*
|
|
308
|
-
* @param ts - Unix timestamp in milliseconds
|
|
309
|
-
* @returns Full file path
|
|
310
|
-
*/
|
|
311
|
-
protected segmentPath(ts: number): string;
|
|
312
|
-
/**
|
|
313
|
-
* Enforce file rotation by deleting oldest files if maxFiles exceeded
|
|
314
|
-
*/
|
|
315
|
-
protected enforceRotation(): Promise<void>;
|
|
316
|
-
/**
|
|
317
|
-
* Ensure path ends with trailing slash
|
|
318
|
-
*/
|
|
319
|
-
protected ensureTrailingSlash(p: string): string;
|
|
355
|
+
|
|
356
|
+
/** Extract code-like symbols from an answer (deduped, order-preserving). */
|
|
357
|
+
declare function extractSymbols(answer: string): string[];
|
|
358
|
+
interface FieldCheckResult {
|
|
359
|
+
/** 0..1 fraction of extracted symbols grounded in the sources. */
|
|
360
|
+
rate: number;
|
|
361
|
+
/** Symbols not found in any retrieved chunk. */
|
|
362
|
+
ungrounded: string[];
|
|
363
|
+
/** Number of symbols checked (0 → nothing to verify, rate defaults to 1). */
|
|
364
|
+
checked: number;
|
|
320
365
|
}
|
|
366
|
+
/** Check that symbols named in the answer appear in the retrieved sources. */
|
|
367
|
+
declare function checkFields(answer: string, ranked: RankedChunk[]): FieldCheckResult;
|
|
321
368
|
|
|
322
369
|
/**
|
|
323
|
-
*
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
*
|
|
327
|
-
|
|
328
|
-
declare function readJson<T = unknown>(filePath: string): Promise<T | null>;
|
|
329
|
-
/**
|
|
330
|
-
* Write JSON file atomically with sorted keys
|
|
331
|
-
*/
|
|
332
|
-
declare function writeJson<T>(filePath: string, data: T): Promise<void>;
|
|
333
|
-
/**
|
|
334
|
-
* Compute hash of JSON content
|
|
370
|
+
* Query decomposition (multi-step reasoning) for richer agent modes.
|
|
371
|
+
*
|
|
372
|
+
* Asks the LLM to split a complex question into focused sub-queries. Degrades
|
|
373
|
+
* gracefully: if decomposition is disabled or the LLM returns nothing usable,
|
|
374
|
+
* it falls back to the original query alone.
|
|
335
375
|
*/
|
|
336
|
-
|
|
376
|
+
|
|
377
|
+
declare function decompose(query: string, llm: ILLM, maxSubqueries: number): Promise<string[]>;
|
|
337
378
|
|
|
338
379
|
/**
|
|
339
|
-
*
|
|
340
|
-
* Mind index verification utilities
|
|
380
|
+
* Answer synthesis + frozen `AgentResponse` assembly.
|
|
341
381
|
*
|
|
342
|
-
*
|
|
382
|
+
* Produces the `agent-response-v1` contract consumed by CLAUDE.md and the
|
|
383
|
+
* task-rag skill. The response is validated against the contract zod before
|
|
384
|
+
* being returned, so a drift fails loudly here rather than silently downstream.
|
|
343
385
|
*/
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
386
|
+
|
|
387
|
+
declare function synthesizeAnswer(query: string, ranked: RankedChunk[], llm: ILLM, useLLM: boolean): Promise<string>;
|
|
388
|
+
interface ToSourcesOptions {
|
|
389
|
+
snippet: SnippetMode;
|
|
390
|
+
staleByFile: Map<string, boolean>;
|
|
391
|
+
}
|
|
392
|
+
declare function toSources(ranked: RankedChunk[], opts: ToSourcesOptions): AgentSource[];
|
|
393
|
+
interface BuildAgentResponseInput {
|
|
394
|
+
answer: string;
|
|
395
|
+
ranked: RankedChunk[];
|
|
396
|
+
confidence: number;
|
|
397
|
+
mode: AgentQueryMode;
|
|
398
|
+
requestId: string;
|
|
399
|
+
timingMs: number;
|
|
400
|
+
indexId: string;
|
|
401
|
+
floor: number;
|
|
402
|
+
snippet: SnippetMode;
|
|
403
|
+
staleByFile: Map<string, boolean>;
|
|
404
|
+
warnings?: AgentWarning[];
|
|
349
405
|
}
|
|
406
|
+
/** Assemble + validate the lean agent response. */
|
|
407
|
+
declare function buildAgentResponse(input: BuildAgentResponseInput): AgentResponse;
|
|
408
|
+
|
|
350
409
|
/**
|
|
351
|
-
*
|
|
352
|
-
*
|
|
353
|
-
* Checks:
|
|
354
|
-
* 1. Main index file exists (.kb/mind/index.json)
|
|
355
|
-
* 2. Individual file hashes match (api-index, deps, recent-diff)
|
|
356
|
-
* 3. Combined index checksum is valid
|
|
357
|
-
* 4. Required files are present
|
|
410
|
+
* Query-history feedback loop via the platform cache (sorted set).
|
|
358
411
|
*
|
|
359
|
-
*
|
|
360
|
-
*
|
|
361
|
-
*
|
|
362
|
-
* @example
|
|
363
|
-
* ```typescript
|
|
364
|
-
* const result = await verifyIndexes('/path/to/workspace');
|
|
365
|
-
* if (!result.ok) {
|
|
366
|
-
* console.error('Inconsistencies:', result.inconsistencies);
|
|
367
|
-
* console.log('Hint:', result.hint);
|
|
368
|
-
* }
|
|
369
|
-
* ```
|
|
412
|
+
* Records queries per index (scored by timestamp) so the engine can learn from
|
|
413
|
+
* usage over time. Best-effort: cache failures never break a query.
|
|
370
414
|
*/
|
|
371
|
-
declare function verifyIndexes(cwd: string): Promise<VerifyResult>;
|
|
372
415
|
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
*/
|
|
416
|
+
declare function recordQuery(cache: ICache, indexId: string, query: string, at: number): Promise<void>;
|
|
417
|
+
declare function recentQueries(cache: ICache, indexId: string, sinceMs?: number): Promise<string[]>;
|
|
376
418
|
|
|
377
419
|
/**
|
|
378
|
-
*
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
/**
|
|
382
|
-
* Default context preset
|
|
420
|
+
* Incremental document sync — add / update / delete specific paths in an index
|
|
421
|
+
* without a full rebuild. Operates on both the vector store (namespace=indexId)
|
|
422
|
+
* and the persisted manifest (BM25 corpus + bookkeeping).
|
|
383
423
|
*/
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
*/
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
424
|
+
|
|
425
|
+
interface SyncOptions {
|
|
426
|
+
indexId: string;
|
|
427
|
+
/** Workspace root that source paths are resolved against. */
|
|
428
|
+
cwd: string;
|
|
429
|
+
chunk: ChunkOptions;
|
|
430
|
+
ast: boolean;
|
|
431
|
+
now: string;
|
|
432
|
+
}
|
|
433
|
+
interface SyncCounts {
|
|
434
|
+
added: number;
|
|
435
|
+
updated: number;
|
|
436
|
+
deleted: number;
|
|
437
|
+
}
|
|
438
|
+
declare function syncAdd(paths: string[], services: MindServices, opts: SyncOptions): Promise<SyncCounts>;
|
|
439
|
+
declare function syncUpdate(paths: string[], services: MindServices, opts: SyncOptions): Promise<SyncCounts>;
|
|
440
|
+
declare function syncDelete(paths: string[], services: MindServices, opts: SyncOptions): Promise<SyncCounts>;
|
|
441
|
+
|
|
393
442
|
/**
|
|
394
|
-
*
|
|
443
|
+
* Shape ranked chunks into lean wire `SearchResult`s (pointers, not payloads).
|
|
444
|
+
*
|
|
445
|
+
* `snippet` rides only as much text as the caller asked for (`--snippet`):
|
|
446
|
+
* none → nothing; line → the first meaningful line; full → truncated chunk.
|
|
395
447
|
*/
|
|
396
|
-
|
|
448
|
+
|
|
449
|
+
interface ToResultsOptions {
|
|
450
|
+
snippet: SnippetMode;
|
|
451
|
+
/** file → stale (on-disk drift since indexing). */
|
|
452
|
+
staleByFile: Map<string, boolean>;
|
|
453
|
+
}
|
|
454
|
+
declare function toSearchResults(ranked: RankedChunk[], opts: ToResultsOptions): SearchResult[];
|
|
455
|
+
|
|
397
456
|
/**
|
|
398
|
-
*
|
|
457
|
+
* Per-index manifest persistence via `IStorage`.
|
|
458
|
+
*
|
|
459
|
+
* The manifest is the source of truth for the BM25 corpus and for
|
|
460
|
+
* listing/status. Vectors live in the vector store; the manifest holds chunk
|
|
461
|
+
* text + per-file bookkeeping. One manifest per index id.
|
|
399
462
|
*/
|
|
400
|
-
declare function getGenerator(): string;
|
|
401
463
|
|
|
402
|
-
|
|
464
|
+
declare function loadManifest(storage: IStorage, indexId: string): Promise<IndexManifest>;
|
|
465
|
+
declare function saveManifest(storage: IStorage, manifest: IndexManifest): Promise<void>;
|
|
466
|
+
|
|
467
|
+
export { type Chunk, type ChunkMeta, type Clock, type ConfidenceResult, type CreateMindOptions, type FieldCheckResult, type IndexManifest, type IngestInput, type IngestProgress, type IngestResult, type Mind, MindServices, type QueryIntent, type RankedChunk, type RetrieveInput, type RetrieveOutput, type Stage, type SyncCounts, type SyncOptions, Tracer, type VerificationResult, bm25Search, buildAgentResponse, checkFields, chunkFile, chunkId, computeConfidence, createMind, decompose, dedupRanked, extractSymbols, ingest, intentWeights, kindFromPath, loadManifest, recentQueries, recordQuery, rerank, retrieve, rrfFuse, saveManifest, slidingWindowChunks, structuralChunks, syncAdd, syncDelete, syncUpdate, synthesizeAnswer, toSearchResults, toSources, tokenize, verifySources };
|