@zvec/zvec-grep 0.1.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.
Files changed (201) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +222 -0
  3. package/README_CN.md +222 -0
  4. package/dist/cli/args.d.ts +2 -0
  5. package/dist/cli/args.js +611 -0
  6. package/dist/cli/args.js.map +1 -0
  7. package/dist/cli/commands.d.ts +2 -0
  8. package/dist/cli/commands.js +270 -0
  9. package/dist/cli/commands.js.map +1 -0
  10. package/dist/cli/errors.d.ts +6 -0
  11. package/dist/cli/errors.js +97 -0
  12. package/dist/cli/errors.js.map +1 -0
  13. package/dist/cli/format/context.d.ts +5 -0
  14. package/dist/cli/format/context.js +490 -0
  15. package/dist/cli/format/context.js.map +1 -0
  16. package/dist/cli/format/debug.d.ts +4 -0
  17. package/dist/cli/format/debug.js +42 -0
  18. package/dist/cli/format/debug.js.map +1 -0
  19. package/dist/cli/format/highlight.d.ts +3 -0
  20. package/dist/cli/format/highlight.js +106 -0
  21. package/dist/cli/format/highlight.js.map +1 -0
  22. package/dist/cli/format/progress.d.ts +9 -0
  23. package/dist/cli/format/progress.js +92 -0
  24. package/dist/cli/format/progress.js.map +1 -0
  25. package/dist/cli/format/range.d.ts +3 -0
  26. package/dist/cli/format/range.js +24 -0
  27. package/dist/cli/format/range.js.map +1 -0
  28. package/dist/cli/format/status.d.ts +7 -0
  29. package/dist/cli/format/status.js +200 -0
  30. package/dist/cli/format/status.js.map +1 -0
  31. package/dist/cli/help.d.ts +1 -0
  32. package/dist/cli/help.js +88 -0
  33. package/dist/cli/help.js.map +1 -0
  34. package/dist/cli/index.d.ts +2 -0
  35. package/dist/cli/index.js +32 -0
  36. package/dist/cli/index.js.map +1 -0
  37. package/dist/cli/types.d.ts +56 -0
  38. package/dist/cli/types.js +10 -0
  39. package/dist/cli/types.js.map +1 -0
  40. package/dist/cli/version.d.ts +1 -0
  41. package/dist/cli/version.js +11 -0
  42. package/dist/cli/version.js.map +1 -0
  43. package/dist/engine/collection/index.d.ts +50 -0
  44. package/dist/engine/collection/index.js +388 -0
  45. package/dist/engine/collection/index.js.map +1 -0
  46. package/dist/engine/errors/details.d.ts +6 -0
  47. package/dist/engine/errors/details.js +27 -0
  48. package/dist/engine/errors/details.js.map +1 -0
  49. package/dist/engine/errors/index.d.ts +14 -0
  50. package/dist/engine/errors/index.js +16 -0
  51. package/dist/engine/errors/index.js.map +1 -0
  52. package/dist/engine/models/catalog.d.ts +47 -0
  53. package/dist/engine/models/catalog.js +68 -0
  54. package/dist/engine/models/catalog.js.map +1 -0
  55. package/dist/engine/models/embeddings.d.ts +25 -0
  56. package/dist/engine/models/embeddings.js +100 -0
  57. package/dist/engine/models/embeddings.js.map +1 -0
  58. package/dist/engine/models/factory.d.ts +5 -0
  59. package/dist/engine/models/factory.js +66 -0
  60. package/dist/engine/models/factory.js.map +1 -0
  61. package/dist/engine/models/index.d.ts +6 -0
  62. package/dist/engine/models/index.js +5 -0
  63. package/dist/engine/models/index.js.map +1 -0
  64. package/dist/engine/models/providers/index.d.ts +2 -0
  65. package/dist/engine/models/providers/index.js +3 -0
  66. package/dist/engine/models/providers/index.js.map +1 -0
  67. package/dist/engine/models/providers/llama-cpp/embedding.d.ts +94 -0
  68. package/dist/engine/models/providers/llama-cpp/embedding.js +459 -0
  69. package/dist/engine/models/providers/llama-cpp/embedding.js.map +1 -0
  70. package/dist/engine/models/providers/llama-cpp/index.d.ts +1 -0
  71. package/dist/engine/models/providers/llama-cpp/index.js +2 -0
  72. package/dist/engine/models/providers/llama-cpp/index.js.map +1 -0
  73. package/dist/engine/models/providers/qwen/embedding.d.ts +38 -0
  74. package/dist/engine/models/providers/qwen/embedding.js +322 -0
  75. package/dist/engine/models/providers/qwen/embedding.js.map +1 -0
  76. package/dist/engine/models/providers/qwen/index.d.ts +1 -0
  77. package/dist/engine/models/providers/qwen/index.js +2 -0
  78. package/dist/engine/models/providers/qwen/index.js.map +1 -0
  79. package/dist/engine/models/ranking.d.ts +15 -0
  80. package/dist/engine/models/ranking.js +2 -0
  81. package/dist/engine/models/ranking.js.map +1 -0
  82. package/dist/engine/models/types.d.ts +30 -0
  83. package/dist/engine/models/types.js +2 -0
  84. package/dist/engine/models/types.js.map +1 -0
  85. package/dist/engine/pipeline/indexing/extractor/code/adapter.d.ts +19 -0
  86. package/dist/engine/pipeline/indexing/extractor/code/adapter.js +24 -0
  87. package/dist/engine/pipeline/indexing/extractor/code/adapter.js.map +1 -0
  88. package/dist/engine/pipeline/indexing/extractor/code/extractor.d.ts +14 -0
  89. package/dist/engine/pipeline/indexing/extractor/code/extractor.js +594 -0
  90. package/dist/engine/pipeline/indexing/extractor/code/extractor.js.map +1 -0
  91. package/dist/engine/pipeline/indexing/extractor/code/families/c-family.d.ts +2 -0
  92. package/dist/engine/pipeline/indexing/extractor/code/families/c-family.js +116 -0
  93. package/dist/engine/pipeline/indexing/extractor/code/families/c-family.js.map +1 -0
  94. package/dist/engine/pipeline/indexing/extractor/code/families/js-ts.d.ts +12 -0
  95. package/dist/engine/pipeline/indexing/extractor/code/families/js-ts.js +112 -0
  96. package/dist/engine/pipeline/indexing/extractor/code/families/js-ts.js.map +1 -0
  97. package/dist/engine/pipeline/indexing/extractor/code/families/metadata.d.ts +8 -0
  98. package/dist/engine/pipeline/indexing/extractor/code/families/metadata.js +83 -0
  99. package/dist/engine/pipeline/indexing/extractor/code/families/metadata.js.map +1 -0
  100. package/dist/engine/pipeline/indexing/extractor/code/families/name-field.d.ts +3 -0
  101. package/dist/engine/pipeline/indexing/extractor/code/families/name-field.js +22 -0
  102. package/dist/engine/pipeline/indexing/extractor/code/families/name-field.js.map +1 -0
  103. package/dist/engine/pipeline/indexing/extractor/code/languages/c.d.ts +1 -0
  104. package/dist/engine/pipeline/indexing/extractor/code/languages/c.js +15 -0
  105. package/dist/engine/pipeline/indexing/extractor/code/languages/c.js.map +1 -0
  106. package/dist/engine/pipeline/indexing/extractor/code/languages/cpp.d.ts +1 -0
  107. package/dist/engine/pipeline/indexing/extractor/code/languages/cpp.js +18 -0
  108. package/dist/engine/pipeline/indexing/extractor/code/languages/cpp.js.map +1 -0
  109. package/dist/engine/pipeline/indexing/extractor/code/languages/go.d.ts +2 -0
  110. package/dist/engine/pipeline/indexing/extractor/code/languages/go.js +72 -0
  111. package/dist/engine/pipeline/indexing/extractor/code/languages/go.js.map +1 -0
  112. package/dist/engine/pipeline/indexing/extractor/code/languages/java.d.ts +1 -0
  113. package/dist/engine/pipeline/indexing/extractor/code/languages/java.js +17 -0
  114. package/dist/engine/pipeline/indexing/extractor/code/languages/java.js.map +1 -0
  115. package/dist/engine/pipeline/indexing/extractor/code/languages/javascript.d.ts +2 -0
  116. package/dist/engine/pipeline/indexing/extractor/code/languages/javascript.js +25 -0
  117. package/dist/engine/pipeline/indexing/extractor/code/languages/javascript.js.map +1 -0
  118. package/dist/engine/pipeline/indexing/extractor/code/languages/python.d.ts +2 -0
  119. package/dist/engine/pipeline/indexing/extractor/code/languages/python.js +53 -0
  120. package/dist/engine/pipeline/indexing/extractor/code/languages/python.js.map +1 -0
  121. package/dist/engine/pipeline/indexing/extractor/code/languages/rust.d.ts +2 -0
  122. package/dist/engine/pipeline/indexing/extractor/code/languages/rust.js +29 -0
  123. package/dist/engine/pipeline/indexing/extractor/code/languages/rust.js.map +1 -0
  124. package/dist/engine/pipeline/indexing/extractor/code/languages/typescript.d.ts +2 -0
  125. package/dist/engine/pipeline/indexing/extractor/code/languages/typescript.js +37 -0
  126. package/dist/engine/pipeline/indexing/extractor/code/languages/typescript.js.map +1 -0
  127. package/dist/engine/pipeline/indexing/extractor/index.d.ts +44 -0
  128. package/dist/engine/pipeline/indexing/extractor/index.js +281 -0
  129. package/dist/engine/pipeline/indexing/extractor/index.js.map +1 -0
  130. package/dist/engine/pipeline/indexing/extractor/markdown/extractor.d.ts +13 -0
  131. package/dist/engine/pipeline/indexing/extractor/markdown/extractor.js +339 -0
  132. package/dist/engine/pipeline/indexing/extractor/markdown/extractor.js.map +1 -0
  133. package/dist/engine/pipeline/indexing/extractor/tree-sitter/grammar.d.ts +4 -0
  134. package/dist/engine/pipeline/indexing/extractor/tree-sitter/grammar.js +49 -0
  135. package/dist/engine/pipeline/indexing/extractor/tree-sitter/grammar.js.map +1 -0
  136. package/dist/engine/pipeline/indexing/extractor/tree-sitter/nodes.d.ts +3 -0
  137. package/dist/engine/pipeline/indexing/extractor/tree-sitter/nodes.js +26 -0
  138. package/dist/engine/pipeline/indexing/extractor/tree-sitter/nodes.js.map +1 -0
  139. package/dist/engine/pipeline/indexing/extractor/tree-sitter/parser.d.ts +2 -0
  140. package/dist/engine/pipeline/indexing/extractor/tree-sitter/parser.js +31 -0
  141. package/dist/engine/pipeline/indexing/extractor/tree-sitter/parser.js.map +1 -0
  142. package/dist/engine/pipeline/indexing/ids.d.ts +1 -0
  143. package/dist/engine/pipeline/indexing/ids.js +5 -0
  144. package/dist/engine/pipeline/indexing/ids.js.map +1 -0
  145. package/dist/engine/pipeline/indexing/index.d.ts +12 -0
  146. package/dist/engine/pipeline/indexing/index.js +538 -0
  147. package/dist/engine/pipeline/indexing/index.js.map +1 -0
  148. package/dist/engine/pipeline/indexing/root-paths.d.ts +7 -0
  149. package/dist/engine/pipeline/indexing/root-paths.js +151 -0
  150. package/dist/engine/pipeline/indexing/root-paths.js.map +1 -0
  151. package/dist/engine/pipeline/indexing/scanner/index.d.ts +5 -0
  152. package/dist/engine/pipeline/indexing/scanner/index.js +504 -0
  153. package/dist/engine/pipeline/indexing/scanner/index.js.map +1 -0
  154. package/dist/engine/pipeline/search/index.d.ts +12 -0
  155. package/dist/engine/pipeline/search/index.js +761 -0
  156. package/dist/engine/pipeline/search/index.js.map +1 -0
  157. package/dist/engine/service/index.d.ts +2 -0
  158. package/dist/engine/service/index.js +2 -0
  159. package/dist/engine/service/index.js.map +1 -0
  160. package/dist/engine/service/lexical.d.ts +29 -0
  161. package/dist/engine/service/lexical.js +482 -0
  162. package/dist/engine/service/lexical.js.map +1 -0
  163. package/dist/engine/service/root.d.ts +15 -0
  164. package/dist/engine/service/root.js +44 -0
  165. package/dist/engine/service/root.js.map +1 -0
  166. package/dist/engine/service/types.d.ts +157 -0
  167. package/dist/engine/service/types.js +2 -0
  168. package/dist/engine/service/types.js.map +1 -0
  169. package/dist/engine/service/zvec-grep.d.ts +2 -0
  170. package/dist/engine/service/zvec-grep.js +935 -0
  171. package/dist/engine/service/zvec-grep.js.map +1 -0
  172. package/dist/engine/storage/index.d.ts +42 -0
  173. package/dist/engine/storage/index.js +2 -0
  174. package/dist/engine/storage/index.js.map +1 -0
  175. package/dist/engine/storage/zvec.d.ts +51 -0
  176. package/dist/engine/storage/zvec.js +706 -0
  177. package/dist/engine/storage/zvec.js.map +1 -0
  178. package/dist/engine/types.d.ts +266 -0
  179. package/dist/engine/types.js +14 -0
  180. package/dist/engine/types.js.map +1 -0
  181. package/dist/engine/utils/glob.d.ts +6 -0
  182. package/dist/engine/utils/glob.js +110 -0
  183. package/dist/engine/utils/glob.js.map +1 -0
  184. package/dist/engine/utils/hash.d.ts +2 -0
  185. package/dist/engine/utils/hash.js +8 -0
  186. package/dist/engine/utils/hash.js.map +1 -0
  187. package/dist/engine/utils/json.d.ts +4 -0
  188. package/dist/engine/utils/json.js +45 -0
  189. package/dist/engine/utils/json.js.map +1 -0
  190. package/dist/engine/utils/lock.d.ts +19 -0
  191. package/dist/engine/utils/lock.js +254 -0
  192. package/dist/engine/utils/lock.js.map +1 -0
  193. package/dist/engine/utils/path.d.ts +4 -0
  194. package/dist/engine/utils/path.js +18 -0
  195. package/dist/engine/utils/path.js.map +1 -0
  196. package/dist/index.d.ts +6 -0
  197. package/dist/index.js +4 -0
  198. package/dist/index.js.map +1 -0
  199. package/package.json +47 -0
  200. package/skills/zvec-grep/SKILL.md +141 -0
  201. package/skills/zvec-grep/agents/openai.yaml +4 -0
@@ -0,0 +1,935 @@
1
+ import { mkdirSync, readdirSync, readFileSync, statSync, unlinkSync, writeFileSync } from "node:fs";
2
+ import { createHash } from "node:crypto";
3
+ import { dirname, join, relative, resolve } from "node:path";
4
+ import { Collection, CollectionRegistry, } from "../collection/index.js";
5
+ import { collectionDetail, detail, EngineError, errorDetails, } from "../errors/index.js";
6
+ import { createEmbeddingModel, createEmbeddingModelFromReference, } from "../models/index.js";
7
+ import { ANONYMOUS_COLLECTION_NAME, anonymousCollectionPath, anonymousHome, anonymousIndexLocation, findNearestAnonymousIndex, resolveZvecGrepRoot, } from "./root.js";
8
+ import { runLexicalFallback, runRgSearch } from "./lexical.js";
9
+ import { defaultHome } from "../utils/path.js";
10
+ import { acquireReadWriteLock, assertNoWriteLock } from "../utils/lock.js";
11
+ const DEFAULT_CONTEXT_LIMIT = 10;
12
+ const DEFAULT_CONTEXT_TOTAL_LIMIT = 30;
13
+ const DEFAULT_LOCAL_EMBEDDING = "local/embeddinggemma-300m";
14
+ export async function createZvecGrep(options = {}) {
15
+ return new ZvecGrepService(options);
16
+ }
17
+ class ZvecGrepService {
18
+ options;
19
+ root;
20
+ collections;
21
+ embeddingModel;
22
+ embeddingModelExplicit;
23
+ recoveredEmbeddingModels = new Map();
24
+ closed = false;
25
+ constructor(options) {
26
+ this.options = options;
27
+ this.root = resolveZvecGrepRoot(options.root);
28
+ this.embeddingModelExplicit = hasExplicitEmbeddingModel(options);
29
+ this.embeddingModel = resolveEmbeddingModel(options, this.root);
30
+ this.collections = {
31
+ list: () => this.listCollections(),
32
+ info: (name) => this.collectionInfo(name),
33
+ status: (name) => this.collectionStatus(name),
34
+ index: (name, paths, indexOptions) => this.indexCollection(name, paths, indexOptions),
35
+ remove: (name) => this.removeCollection(name),
36
+ };
37
+ }
38
+ async init(options = {}) {
39
+ this.ensureOpen();
40
+ const root = resolveZvecGrepRoot(options.root ?? this.root);
41
+ const location = anonymousIndexLocation(root);
42
+ return await withHomeWriteLock(location.home, options.rebuild ? "init.rebuild" : "init", async () => {
43
+ const existing = readCollectionInfo(location.home, ANONYMOUS_COLLECTION_NAME);
44
+ const embeddingModel = this.embeddingModelForIndex(existing, location.home, "init");
45
+ const registry = new CollectionRegistry(location.home, embeddingModel);
46
+ try {
47
+ const rootPaths = resolveIndexRootPaths(existing, options.rootPaths, root, {
48
+ resetPaths: options.resetPaths === true,
49
+ includePaths: options.includePaths,
50
+ excludePaths: options.excludePaths,
51
+ });
52
+ if (options.rebuild) {
53
+ registry.remove(ANONYMOUS_COLLECTION_NAME);
54
+ }
55
+ if (registry.has(ANONYMOUS_COLLECTION_NAME)) {
56
+ assertCollectionEmbeddingMatchesCurrentModel(registry.get(ANONYMOUS_COLLECTION_NAME), embeddingModel, "zg --init --rebuild");
57
+ registry.updateRootPaths(ANONYMOUS_COLLECTION_NAME, rootPaths);
58
+ }
59
+ else {
60
+ registry.create(ANONYMOUS_COLLECTION_NAME, rootPaths, anonymousCollectionPath(root));
61
+ }
62
+ return await registry.open(ANONYMOUS_COLLECTION_NAME).index({
63
+ rebuild: false,
64
+ embeddingConcurrency: options.embeddingConcurrency,
65
+ onProgress: options.onProgress,
66
+ });
67
+ }
68
+ finally {
69
+ registry.close();
70
+ }
71
+ });
72
+ }
73
+ async context(options) {
74
+ this.ensureOpen();
75
+ const request = normalizeContextRequest(options);
76
+ if (options.collection) {
77
+ return this.contextFromNamedCollection(request, options);
78
+ }
79
+ const startRoot = resolveZvecGrepRoot(options.root ?? this.root);
80
+ if (options.rg) {
81
+ return this.contextFromRg(startRoot, request, options);
82
+ }
83
+ assertNearestAnonymousHomeUnlocked(startRoot, "context");
84
+ const location = findNearestAnonymousIndex(startRoot);
85
+ if (location) {
86
+ if (options.autoUpdate !== false) {
87
+ await this.refreshAnonymousIndexForContext(location, options);
88
+ }
89
+ return await withHomeReadLock(location.home, "context", () => this.contextFromAnonymousIndex(location, request, options));
90
+ }
91
+ throw anonymousIndexNotFoundError(startRoot);
92
+ }
93
+ async info(options = {}) {
94
+ this.ensureOpen();
95
+ const startRoot = resolveZvecGrepRoot(options.root ?? this.root);
96
+ assertNearestAnonymousHomeUnlocked(startRoot, "info");
97
+ const nearest = findNearestAnonymousIndex(startRoot);
98
+ if (!nearest) {
99
+ const location = anonymousIndexLocation(startRoot);
100
+ return {
101
+ root: startRoot,
102
+ initialized: false,
103
+ home: location.home,
104
+ indexPath: location.collectionPath,
105
+ source: "uninitialized",
106
+ suggestion: "zg --init",
107
+ };
108
+ }
109
+ return await withHomeReadLock(nearest.home, "info", async () => {
110
+ const registry = new CollectionRegistry(nearest.home, undefined, true);
111
+ try {
112
+ const collection = registry.get(ANONYMOUS_COLLECTION_NAME);
113
+ return {
114
+ root: nearest.root,
115
+ initialized: collection !== null,
116
+ home: nearest.home,
117
+ indexPath: nearest.collectionPath,
118
+ source: collection ? "index" : "uninitialized",
119
+ collection: collection ?? undefined,
120
+ status: collection ? await registry.status(ANONYMOUS_COLLECTION_NAME) : null,
121
+ suggestion: collection ? undefined : "zg --init",
122
+ };
123
+ }
124
+ finally {
125
+ registry.close();
126
+ }
127
+ });
128
+ }
129
+ async close() {
130
+ const models = new Set([
131
+ ...(this.embeddingModel ? [this.embeddingModel] : []),
132
+ ...this.recoveredEmbeddingModels.values(),
133
+ ]);
134
+ this.recoveredEmbeddingModels.clear();
135
+ for (const model of models) {
136
+ await model.dispose();
137
+ }
138
+ this.closed = true;
139
+ }
140
+ async listCollections() {
141
+ this.ensureOpen();
142
+ return await withHomeReadLock(serviceHome(this.options), "collections.list", async () => {
143
+ const registry = this.createRegistry(true);
144
+ try {
145
+ return registry.list();
146
+ }
147
+ finally {
148
+ registry.close();
149
+ }
150
+ });
151
+ }
152
+ async collectionInfo(name) {
153
+ this.ensureOpen();
154
+ return await withHomeReadLock(serviceHome(this.options), "collections.info", async () => {
155
+ const registry = this.createRegistry(true);
156
+ try {
157
+ return registry.get(name);
158
+ }
159
+ finally {
160
+ registry.close();
161
+ }
162
+ });
163
+ }
164
+ async collectionStatus(name) {
165
+ this.ensureOpen();
166
+ return await withHomeReadLock(serviceHome(this.options), "collections.status", async () => {
167
+ const registry = this.createRegistry(true);
168
+ try {
169
+ return await registry.status(name);
170
+ }
171
+ finally {
172
+ registry.close();
173
+ }
174
+ });
175
+ }
176
+ async indexCollection(name, paths, options = {}) {
177
+ this.ensureOpen();
178
+ const home = serviceHome(this.options);
179
+ return await withHomeWriteLock(home, options.rebuild ? "collections.index.rebuild" : "collections.index", async () => {
180
+ const existing = readCollectionInfo(home, name);
181
+ const embeddingModel = this.embeddingModelForIndex(existing, home, "collections.index");
182
+ const registry = this.createRegistry(false, embeddingModel);
183
+ try {
184
+ const requestedRootPaths = paths === undefined
185
+ ? undefined
186
+ : Array.isArray(paths)
187
+ ? paths
188
+ : [paths];
189
+ const rootPaths = resolveIndexRootPaths(existing, requestedRootPaths, this.root, {
190
+ resetPaths: options.resetPaths === true,
191
+ includePaths: options.includePaths,
192
+ excludePaths: options.excludePaths,
193
+ });
194
+ if (options.rebuild) {
195
+ registry.remove(name);
196
+ }
197
+ if (registry.has(name)) {
198
+ assertCollectionEmbeddingMatchesCurrentModel(registry.get(name), embeddingModel, "zg --collections index <name> --rebuild");
199
+ registry.updateRootPaths(name, rootPaths);
200
+ }
201
+ else {
202
+ registry.create(name, rootPaths);
203
+ }
204
+ return await registry.open(name).index({
205
+ embeddingConcurrency: options.embeddingConcurrency,
206
+ onProgress: options.onProgress,
207
+ });
208
+ }
209
+ finally {
210
+ registry.close();
211
+ }
212
+ });
213
+ }
214
+ async removeCollection(name) {
215
+ this.ensureOpen();
216
+ return await withHomeWriteLock(serviceHome(this.options), "collections.remove", async () => {
217
+ const registry = this.createRegistry(false);
218
+ try {
219
+ return registry.remove(name);
220
+ }
221
+ finally {
222
+ registry.close();
223
+ }
224
+ });
225
+ }
226
+ async contextFromAnonymousIndex(location, request, options) {
227
+ const registry = new CollectionRegistry(location.home, undefined, true);
228
+ try {
229
+ const info = registry.get(ANONYMOUS_COLLECTION_NAME);
230
+ if (!info) {
231
+ throw new EngineError("Collection not found", {
232
+ code: "ZVEC_GREP.ENGINE.COLLECTION.NOT_FOUND",
233
+ });
234
+ }
235
+ const collection = this.openCollectionForSearch(info, request, location.home);
236
+ try {
237
+ return await this.contextFromCollection({
238
+ root: location.root,
239
+ request,
240
+ collection,
241
+ anonymous: true,
242
+ options,
243
+ });
244
+ }
245
+ finally {
246
+ collection.close();
247
+ }
248
+ }
249
+ finally {
250
+ registry.close();
251
+ }
252
+ }
253
+ async refreshAnonymousIndexForContext(location, options) {
254
+ const needsRefresh = await withHomeReadLock(location.home, "context.status", async () => {
255
+ const registry = new CollectionRegistry(location.home, undefined, true);
256
+ try {
257
+ const status = await registry.status(ANONYMOUS_COLLECTION_NAME);
258
+ return status ? collectionIndexStatusNeedsRefresh(status) : false;
259
+ }
260
+ finally {
261
+ registry.close();
262
+ }
263
+ });
264
+ if (!needsRefresh) {
265
+ return;
266
+ }
267
+ await withHomeWriteLock(location.home, "context.refresh", async () => {
268
+ const existing = readCollectionInfo(location.home, ANONYMOUS_COLLECTION_NAME);
269
+ if (!existing) {
270
+ return;
271
+ }
272
+ const stillNeedsRefresh = await collectionNeedsRefresh(location.home, ANONYMOUS_COLLECTION_NAME);
273
+ if (!stillNeedsRefresh) {
274
+ return;
275
+ }
276
+ const embeddingModel = this.embeddingModelForIndex(existing, location.home, "context.refresh");
277
+ assertCollectionEmbeddingMatchesCurrentModel(existing, embeddingModel, "zg --init --rebuild");
278
+ const registry = new CollectionRegistry(location.home, embeddingModel);
279
+ try {
280
+ await registry.open(ANONYMOUS_COLLECTION_NAME).index({
281
+ embeddingConcurrency: options.embeddingConcurrency,
282
+ onProgress: options.onAutoUpdateProgress,
283
+ });
284
+ }
285
+ finally {
286
+ registry.close();
287
+ }
288
+ });
289
+ }
290
+ async contextFromNamedCollection(request, options) {
291
+ const collectionName = options.collection;
292
+ return await withHomeReadLock(serviceHome(this.options), "collection.context", async () => {
293
+ const registry = this.createRegistry(true, undefined);
294
+ try {
295
+ const info = registry.get(collectionName);
296
+ if (!info) {
297
+ throw new EngineError("Collection not found", {
298
+ code: "ZVEC_GREP.ENGINE.COLLECTION.NOT_FOUND",
299
+ context: errorDetails([collectionDetail(collectionName)]),
300
+ });
301
+ }
302
+ const collection = this.openCollectionForSearch(info, request, registry.home);
303
+ const root = collection.info.rootPaths[0]?.absolutePath ?? resolveZvecGrepRoot(options.root ?? this.root);
304
+ try {
305
+ return await this.contextFromCollection({
306
+ root,
307
+ request,
308
+ collection,
309
+ anonymous: false,
310
+ options: {
311
+ ...options,
312
+ fallback: "disabled",
313
+ },
314
+ });
315
+ }
316
+ finally {
317
+ collection.close();
318
+ }
319
+ }
320
+ finally {
321
+ registry.close();
322
+ }
323
+ });
324
+ }
325
+ async contextFromCollection(input) {
326
+ const searches = [];
327
+ const limit = contextGroupLimit(input.options.limit, input.request.groups.length);
328
+ for (const group of input.request.groups) {
329
+ searches.push(await input.collection.searchPlan({
330
+ routes: group.routes,
331
+ limit,
332
+ trace: input.options.trace,
333
+ preferSymbol: input.options.preferSymbol,
334
+ symbolTypes: input.options.symbolTypes,
335
+ includePaths: input.options.includePaths,
336
+ excludePaths: input.options.excludePaths,
337
+ modifiedAfter: input.options.modifiedAfter,
338
+ modifiedBefore: input.options.modifiedBefore,
339
+ }));
340
+ }
341
+ const items = dedupeAndRerankContextItems(searches.flatMap((search) => searchPlanToContextItems(search, input.root)));
342
+ return {
343
+ query: input.request.displayQuery,
344
+ root: input.root,
345
+ source: "index",
346
+ coverage: "ranked_sample",
347
+ collection: {
348
+ id: input.collection.info.id,
349
+ name: input.collection.info.name,
350
+ path: input.collection.info.path,
351
+ anonymous: input.anonymous,
352
+ },
353
+ items,
354
+ diagnostics: {
355
+ emptyReason: items.length === 0 ? "no_matches" : undefined,
356
+ index: {
357
+ hitsReturned: items.length,
358
+ routes: searches.flatMap((search) => search.plan.routes),
359
+ },
360
+ },
361
+ };
362
+ }
363
+ async contextFromLexicalFallback(root, request, options) {
364
+ const fallbackResults = [];
365
+ const limit = contextGroupLimit(options.limit, request.groups.length);
366
+ for (const query of request.fallbackQueries) {
367
+ fallbackResults.push(await runLexicalFallback({
368
+ root,
369
+ query,
370
+ limit,
371
+ includePaths: options.includePaths,
372
+ excludePaths: options.excludePaths,
373
+ modifiedAfter: options.modifiedAfter,
374
+ modifiedBefore: options.modifiedBefore,
375
+ rgOptions: options.rgOptions,
376
+ }));
377
+ }
378
+ const items = dedupeAndRerankContextItems(fallbackResults.flatMap((fallback) => fallback.items));
379
+ const diagnostics = mergeFallbackDiagnostics(fallbackResults);
380
+ return {
381
+ query: request.displayQuery,
382
+ root,
383
+ source: "lexical_fallback",
384
+ coverage: diagnostics.truncated ? "lexical_truncated" : "lexical_exhaustive",
385
+ items,
386
+ diagnostics: {
387
+ emptyReason: items.length === 0 ? "no_matches" : undefined,
388
+ fallback: diagnostics,
389
+ },
390
+ };
391
+ }
392
+ async contextFromRg(root, request, options) {
393
+ let rgResult;
394
+ try {
395
+ rgResult = await runRgSearch({
396
+ root,
397
+ patterns: request.fallbackQueries,
398
+ paths: options.rgPaths,
399
+ limit: options.limit,
400
+ includePaths: options.includePaths,
401
+ excludePaths: options.excludePaths,
402
+ modifiedAfter: options.modifiedAfter,
403
+ modifiedBefore: options.modifiedBefore,
404
+ rgOptions: options.rgOptions,
405
+ });
406
+ }
407
+ catch (cause) {
408
+ throw new EngineError("Search failed", {
409
+ code: "ZVEC_GREP.ENGINE.SEARCH.FAILED",
410
+ context: errorDetails([
411
+ detail("source", "rg"),
412
+ detail("root", root),
413
+ ]),
414
+ cause,
415
+ });
416
+ }
417
+ const items = dedupeAndRerankContextItems(rgResult.items);
418
+ const emptyReason = items.length === 0
419
+ ? rgEmptyReason(rgResult.diagnostics)
420
+ : undefined;
421
+ return {
422
+ query: request.displayQuery,
423
+ root,
424
+ source: "rg",
425
+ coverage: rgResult.diagnostics.truncated ? "lexical_truncated" : "lexical_exhaustive",
426
+ items,
427
+ diagnostics: {
428
+ emptyReason,
429
+ fallback: rgResult.diagnostics,
430
+ },
431
+ };
432
+ }
433
+ createRegistry(readOnly, embeddingModel = this.embeddingModel) {
434
+ return new CollectionRegistry(this.options.home, embeddingModel, readOnly);
435
+ }
436
+ openCollectionForSearch(info, request, registryHome) {
437
+ return new Collection(info, this.embeddingModelForSearch(info.embedding, request, registryHome), true);
438
+ }
439
+ embeddingModelForSearch(schema, request, registryHome) {
440
+ if (!request.routes.some((route) => route.mode === "vector")) {
441
+ return undefined;
442
+ }
443
+ if (this.embeddingModel) {
444
+ return this.embeddingModel;
445
+ }
446
+ return this.recoverEmbeddingModel(schema, registryHome);
447
+ }
448
+ embeddingModelForIndex(existing, registryHome, operation) {
449
+ if (existing && !this.embeddingModelExplicit) {
450
+ return this.recoverEmbeddingModel(existing.embedding, registryHome);
451
+ }
452
+ return this.requireEmbeddingModel(operation);
453
+ }
454
+ recoverEmbeddingModel(schema, registryHome) {
455
+ const key = `${schema.provider}/${schema.model}/${modelCacheDir(this.options, this.root, registryHome)}`;
456
+ const cached = this.recoveredEmbeddingModels.get(key);
457
+ if (cached) {
458
+ return cached;
459
+ }
460
+ const model = createEmbeddingModel({
461
+ provider: schema.provider,
462
+ model: schema.model,
463
+ }, providerOptions(this.options, this.root, registryHome));
464
+ this.recoveredEmbeddingModels.set(key, model);
465
+ return model;
466
+ }
467
+ requireEmbeddingModel(operation) {
468
+ if (!this.embeddingModel) {
469
+ throw new EngineError("zvec-grep operation requires an embedding model", {
470
+ code: "ZVEC_GREP.ENGINE.SERVICE.EMBEDDING_MODEL_REQUIRED",
471
+ context: errorDetails([
472
+ detail("operation", operation),
473
+ detail("hint", 'Pass "--embedding <model>" or set ZVEC_GREP_EMBEDDING. Existing indexes can rerun init without --embedding to reuse the stored schema.'),
474
+ detail("examples", "local/embeddinggemma-300m, qwen/text-embedding-v4"),
475
+ ]),
476
+ });
477
+ }
478
+ return this.embeddingModel;
479
+ }
480
+ ensureOpen() {
481
+ if (this.closed) {
482
+ throw new EngineError("zvec-grep service is already closed", {
483
+ code: "ZVEC_GREP.ENGINE.SERVICE.CLOSED",
484
+ });
485
+ }
486
+ }
487
+ }
488
+ async function withHomeReadLock(home, operation, task) {
489
+ const lock = acquireHomeLock(home, "read", operation);
490
+ try {
491
+ return await task();
492
+ }
493
+ finally {
494
+ lock.release();
495
+ }
496
+ }
497
+ async function withHomeWriteLock(home, operation, task) {
498
+ const lock = acquireHomeLock(home, "write", operation);
499
+ try {
500
+ return await task();
501
+ }
502
+ finally {
503
+ lock.release();
504
+ }
505
+ }
506
+ async function collectionNeedsRefresh(home, collectionName) {
507
+ const registry = new CollectionRegistry(home, undefined, true);
508
+ try {
509
+ const status = await registry.status(collectionName);
510
+ return status ? collectionIndexStatusNeedsRefresh(status) : false;
511
+ }
512
+ finally {
513
+ registry.close();
514
+ }
515
+ }
516
+ function collectionIndexStatusNeedsRefresh(status) {
517
+ return status.filesAdded > 0
518
+ || status.filesModified > 0
519
+ || status.filesDeleted > 0
520
+ || status.filesPending > 0
521
+ || status.filesFailed > 0;
522
+ }
523
+ function acquireHomeLock(home, mode, operation) {
524
+ return acquireReadWriteLock(homeLockPath(home), mode, { operation });
525
+ }
526
+ function assertHomeUnlocked(home, operation) {
527
+ assertNoWriteLock(homeLockPath(home), operation);
528
+ }
529
+ function assertNearestAnonymousHomeUnlocked(start, operation) {
530
+ let current = resolve(start);
531
+ while (true) {
532
+ assertHomeUnlocked(anonymousHome(current), operation);
533
+ const parent = dirname(current);
534
+ if (parent === current) {
535
+ return;
536
+ }
537
+ current = parent;
538
+ }
539
+ }
540
+ function homeLockPath(home) {
541
+ return join(home, "locks", "home");
542
+ }
543
+ function serviceHome(options) {
544
+ return options.home ?? defaultHome();
545
+ }
546
+ function rgEmptyReason(diagnostics) {
547
+ return diagnostics.missingPaths && diagnostics.missingPaths.length > 0
548
+ && diagnostics.searchedPaths
549
+ && diagnostics.searchedPaths.length === 0
550
+ ? "no_searchable_files"
551
+ : "no_matches";
552
+ }
553
+ function anonymousIndexNotFoundError(root) {
554
+ return new EngineError("No anonymous zvec-grep index found for this workspace", {
555
+ code: "ZVEC_GREP.ENGINE.SERVICE.ANONYMOUS_INDEX_NOT_FOUND",
556
+ context: errorDetails([
557
+ detail("root", root),
558
+ detail("hint", 'Run "zg --init" first, or use "zg --rg" for explicit literal/regex search.'),
559
+ ]),
560
+ });
561
+ }
562
+ function resolveIndexRootPaths(existing, requested, fallbackRoot, options) {
563
+ let rootPaths = requested
564
+ ?? existing?.rootPaths
565
+ ?? [fallbackRoot];
566
+ if (options.resetPaths) {
567
+ rootPaths = rootPaths.map(resetRootPathFilters);
568
+ }
569
+ if (options.includePaths !== undefined || options.excludePaths !== undefined) {
570
+ rootPaths = rootPaths.map((rootPath) => applyRootPathFilterOverrides(rootPath, options.includePaths, options.excludePaths));
571
+ }
572
+ return rootPaths;
573
+ }
574
+ function resetRootPathFilters(rootPath) {
575
+ if (typeof rootPath === "string") {
576
+ return rootPath;
577
+ }
578
+ return {
579
+ absolutePath: rootPath.absolutePath,
580
+ recursive: rootPath.recursive,
581
+ };
582
+ }
583
+ function applyRootPathFilterOverrides(rootPath, include, exclude) {
584
+ const normalized = typeof rootPath === "string"
585
+ ? {
586
+ absolutePath: rootPath,
587
+ recursive: true,
588
+ }
589
+ : rootPath;
590
+ return {
591
+ ...normalized,
592
+ include: include ?? normalized.include,
593
+ exclude: exclude ?? normalized.exclude,
594
+ };
595
+ }
596
+ function resolveEmbeddingModel(options, root) {
597
+ if (options.embeddingModel) {
598
+ return options.embeddingModel;
599
+ }
600
+ if (options.embedding) {
601
+ return createEmbeddingModelFromReference(options.embedding, providerOptions(options, root));
602
+ }
603
+ if (options.defaultEmbedding === true) {
604
+ return createEmbeddingModelFromReference(DEFAULT_LOCAL_EMBEDDING, providerOptions(options, root));
605
+ }
606
+ return undefined;
607
+ }
608
+ function hasExplicitEmbeddingModel(options) {
609
+ return options.embeddingModel !== undefined
610
+ || options.embedding !== undefined;
611
+ }
612
+ function readCollectionInfo(home, name) {
613
+ const registry = new CollectionRegistry(home, undefined, true);
614
+ try {
615
+ return registry.get(name);
616
+ }
617
+ finally {
618
+ registry.close();
619
+ }
620
+ }
621
+ function providerOptions(options, root, registryHome) {
622
+ return {
623
+ apiKey: options.apiKey ?? "",
624
+ endpoint: options.endpoint,
625
+ modelCacheDir: modelCacheDir(options, root, registryHome),
626
+ llamaGpu: options.llamaGpu,
627
+ embeddingParallelism: options.embeddingParallelism,
628
+ };
629
+ }
630
+ function assertCollectionEmbeddingMatchesCurrentModel(info, model, rebuildCommand) {
631
+ if (!info) {
632
+ return;
633
+ }
634
+ const expected = info.embedding;
635
+ const changed = expected.provider !== model.ref.provider
636
+ || expected.model !== model.ref.model
637
+ || expected.dimension !== model.dimension
638
+ || expected.metric !== model.metric;
639
+ if (!changed) {
640
+ return;
641
+ }
642
+ throw new EngineError("Existing zvec-grep index uses a different embedding model", {
643
+ code: "ZVEC_GREP.ENGINE.SERVICE.EMBEDDING_SCHEMA_CHANGE_REQUIRES_REBUILD",
644
+ context: errorDetails([
645
+ collectionDetail(info.name),
646
+ detail("existing", `${expected.provider}/${expected.model}`),
647
+ detail("requested", `${model.ref.provider}/${model.ref.model}`),
648
+ detail("hint", `Run "${rebuildCommand}" to rebuild this index with the requested embedding model.`),
649
+ ]),
650
+ });
651
+ }
652
+ function modelCacheDir(options, root, registryHome) {
653
+ if (options.modelCacheDir || process.env.ZVEC_GREP_MODEL_CACHE) {
654
+ return options.modelCacheDir ?? process.env.ZVEC_GREP_MODEL_CACHE;
655
+ }
656
+ const globalCache = join(options.home ?? defaultHome(), "models");
657
+ const legacyCaches = [
658
+ registryHome ? join(registryHome, "models") : undefined,
659
+ join(anonymousHome(root), "models"),
660
+ ];
661
+ const globalHasModel = directoryHasGguf(globalCache);
662
+ for (const legacyCache of legacyCaches) {
663
+ if (legacyCache && directoryHasGguf(legacyCache) && !globalHasModel) {
664
+ return legacyCache;
665
+ }
666
+ }
667
+ if (globalHasModel || canWriteCacheDirectory(globalCache)) {
668
+ return globalCache;
669
+ }
670
+ return join(anonymousHome(root), "models");
671
+ }
672
+ function directoryHasGguf(path) {
673
+ try {
674
+ return readdirSync(path, { withFileTypes: true }).some((entry) => entry.isFile() && entry.name.endsWith(".gguf"));
675
+ }
676
+ catch {
677
+ return false;
678
+ }
679
+ }
680
+ function canWriteCacheDirectory(path) {
681
+ const probe = join(path, `.zvec-grep-write-test-${Date.now()}-${Math.random().toString(16).slice(2)}`);
682
+ try {
683
+ mkdirSync(path, { recursive: true });
684
+ writeFileSync(probe, "");
685
+ unlinkSync(probe);
686
+ return true;
687
+ }
688
+ catch {
689
+ return false;
690
+ }
691
+ }
692
+ function contextGroupLimit(limit, groupCount) {
693
+ if (limit !== undefined) {
694
+ return limit;
695
+ }
696
+ const safeGroupCount = Math.max(1, groupCount);
697
+ if (safeGroupCount <= 3) {
698
+ return DEFAULT_CONTEXT_LIMIT;
699
+ }
700
+ return Math.max(1, Math.ceil(DEFAULT_CONTEXT_TOTAL_LIMIT / safeGroupCount));
701
+ }
702
+ function normalizeContextRequest(options) {
703
+ const primaryQueries = normalizePrimaryQueries(options.query, options.queries);
704
+ const extraRoutes = normalizeContextRoutes(options.routes ?? []);
705
+ if (primaryQueries.length === 0 && extraRoutes.length === 0) {
706
+ throw new EngineError("zvec-grep context requires a non-empty query or route", {
707
+ code: "ZVEC_GREP.ENGINE.SERVICE.EMPTY_QUERY",
708
+ });
709
+ }
710
+ const groups = contextGroups(primaryQueries, extraRoutes);
711
+ const routes = groups.flatMap((group) => group.routes);
712
+ const fallbackQueries = groups.map((group) => group.fallbackQuery);
713
+ const displayQuery = primaryQueries.length > 0
714
+ ? primaryQueries.join(" | ")
715
+ : extraRoutes.map((route) => route.query).join(" | ");
716
+ return {
717
+ displayQuery,
718
+ fallbackQueries,
719
+ routes,
720
+ groups,
721
+ };
722
+ }
723
+ function normalizePrimaryQueries(query, queries) {
724
+ return [
725
+ query,
726
+ ...(queries ?? []),
727
+ ].flatMap((value) => {
728
+ const normalized = normalizeOptionalQuery(value);
729
+ return normalized ? [normalized] : [];
730
+ });
731
+ }
732
+ function normalizeOptionalQuery(query) {
733
+ const normalized = query?.trim() ?? "";
734
+ return normalized.length > 0 ? normalized : undefined;
735
+ }
736
+ function normalizeContextRoutes(routes) {
737
+ return routes.map((route, index) => {
738
+ if (route.mode !== "fts" && route.mode !== "vector") {
739
+ throw new EngineError("zvec-grep context route has an unsupported mode", {
740
+ code: "ZVEC_GREP.ENGINE.SERVICE.INVALID_ROUTE_MODE",
741
+ context: `routeIndex=${index} mode=${String(route.mode)}`,
742
+ });
743
+ }
744
+ const query = route.query.trim();
745
+ if (query.length === 0) {
746
+ throw new EngineError("zvec-grep context route requires a non-empty query", {
747
+ code: "ZVEC_GREP.ENGINE.SERVICE.EMPTY_ROUTE_QUERY",
748
+ context: `routeIndex=${index} mode=${route.mode}`,
749
+ });
750
+ }
751
+ return {
752
+ mode: route.mode,
753
+ query,
754
+ };
755
+ });
756
+ }
757
+ function contextGroups(primaryQueries, extraRoutes) {
758
+ return [
759
+ ...primaryQueries.map((query) => ({
760
+ routes: [
761
+ { mode: "fts", query },
762
+ { mode: "vector", query },
763
+ ],
764
+ fallbackQuery: query,
765
+ })),
766
+ ...extraRoutes.map((route) => ({
767
+ routes: [route],
768
+ fallbackQuery: route.query,
769
+ })),
770
+ ];
771
+ }
772
+ function searchPlanToContextItems(result, root) {
773
+ return result.hits.map((hit) => {
774
+ const target = contextItemTarget(hit);
775
+ return {
776
+ kind: "indexed_entity",
777
+ rank: hit.rank,
778
+ file: {
779
+ absolutePath: hit.file.absolutePath,
780
+ relativePath: hit.file.relativePath || relative(root, hit.file.absolutePath) || ".",
781
+ rootPath: hit.file.rootPath,
782
+ },
783
+ range: hit.entity.range,
784
+ excerptRange: target.excerptRange,
785
+ content: contentToText(target.content),
786
+ contentRole: target.contentRole,
787
+ outline: target.outline,
788
+ status: fileFreshnessStatus(hit.file),
789
+ score: hit.score,
790
+ matchedBy: hit.matchedBy,
791
+ metadata: hit.entity.metadata,
792
+ entityId: hit.entity.id,
793
+ trace: hit.trace,
794
+ };
795
+ });
796
+ }
797
+ function dedupeAndRerankContextItems(items) {
798
+ const seen = new Set();
799
+ const deduped = [];
800
+ for (const item of items) {
801
+ const key = contextItemDedupeKey(item);
802
+ if (seen.has(key)) {
803
+ continue;
804
+ }
805
+ seen.add(key);
806
+ deduped.push({
807
+ ...item,
808
+ rank: deduped.length + 1,
809
+ });
810
+ }
811
+ return deduped;
812
+ }
813
+ function contextItemDedupeKey(item) {
814
+ if (item.entityId) {
815
+ return `entity:${item.entityId}`;
816
+ }
817
+ return [
818
+ "range",
819
+ item.file.absolutePath,
820
+ JSON.stringify(item.range),
821
+ ].join(":");
822
+ }
823
+ function mergeFallbackDiagnostics(results) {
824
+ const [first] = results;
825
+ if (!first) {
826
+ throw new EngineError("zvec-grep lexical fallback requires at least one query", {
827
+ code: "ZVEC_GREP.ENGINE.SERVICE.EMPTY_QUERY",
828
+ });
829
+ }
830
+ return {
831
+ ...first.diagnostics,
832
+ truncated: results.some((result) => result.diagnostics.truncated),
833
+ };
834
+ }
835
+ function contextItemTarget(hit) {
836
+ const evidence = hit.evidence.find((item) => !item.isEntity);
837
+ const hasSeparateEvidence = evidence && !sameDisplayedContent(hit.entity, evidence);
838
+ const content = hasSeparateEvidence ? evidence.content : hit.entity.content;
839
+ const contentRole = hasSeparateEvidence || entityContentLooksLikeSource(hit.entity)
840
+ ? "source"
841
+ : "outline";
842
+ const excerptRange = hasSeparateEvidence ? evidence.range : undefined;
843
+ const outline = contentRole === "source" ? contextItemOutline(hit, evidence) : undefined;
844
+ return {
845
+ content,
846
+ contentRole,
847
+ excerptRange,
848
+ outline,
849
+ };
850
+ }
851
+ function entityContentLooksLikeSource(entity) {
852
+ if (entity.content.kind !== "text" || entity.range.kind !== "text") {
853
+ return true;
854
+ }
855
+ const expectedLines = entity.range.endLine - entity.range.startLine + 1;
856
+ const actualLines = entity.content.text.split(/\r?\n/).length;
857
+ return actualLines >= expectedLines;
858
+ }
859
+ function contextItemOutline(hit, evidence) {
860
+ if (hit.entity.content.kind !== "text") {
861
+ return undefined;
862
+ }
863
+ const outline = hit.entity.content.text.trim();
864
+ if (outline.length === 0) {
865
+ return undefined;
866
+ }
867
+ if (!evidence) {
868
+ return undefined;
869
+ }
870
+ if (evidence && sameDisplayedContent(hit.entity, evidence)) {
871
+ return undefined;
872
+ }
873
+ if (!isUsefulOutline(hit.entity.metadata, outline)) {
874
+ return undefined;
875
+ }
876
+ if (evidence?.content.kind === "text" && evidence.content.text.trim() === outline) {
877
+ return undefined;
878
+ }
879
+ return outline;
880
+ }
881
+ function isUsefulOutline(metadata, outline) {
882
+ if (!metadata || metadata.kind !== "code") {
883
+ return false;
884
+ }
885
+ return metadata.symbolType === "class"
886
+ || metadata.symbolType === "interface"
887
+ || metadata.symbolType === "module"
888
+ || outline.includes("\ncalls:");
889
+ }
890
+ function sameDisplayedContent(entity, evidence) {
891
+ return JSON.stringify(entity.range) === JSON.stringify(evidence.range)
892
+ && contentEquals(entity.content, evidence.content);
893
+ }
894
+ function contentEquals(left, right) {
895
+ if (left.kind !== right.kind) {
896
+ return false;
897
+ }
898
+ if (left.kind === "image") {
899
+ return right.kind === "image"
900
+ && left.format === right.format
901
+ && left.data.byteLength === right.data.byteLength;
902
+ }
903
+ return right.kind === "text" && left.text === right.text;
904
+ }
905
+ function fileFreshnessStatus(file) {
906
+ if (!file.indexStatus?.indexedTime) {
907
+ return "possibly_stale";
908
+ }
909
+ try {
910
+ const info = statSync(file.absolutePath, { throwIfNoEntry: false });
911
+ if (!info || !info.isFile()) {
912
+ return "possibly_stale";
913
+ }
914
+ if (file.indexStatus.indexedTime >= info.mtimeMs) {
915
+ return "fresh";
916
+ }
917
+ if (file.contentHash && sha256File(file.absolutePath) === file.contentHash) {
918
+ return "fresh";
919
+ }
920
+ }
921
+ catch {
922
+ return "possibly_stale";
923
+ }
924
+ return "possibly_stale";
925
+ }
926
+ function sha256File(path) {
927
+ return createHash("sha256").update(readFileSync(path)).digest("hex");
928
+ }
929
+ function contentToText(content) {
930
+ if (content.kind === "text") {
931
+ return content.text;
932
+ }
933
+ return `[image:${content.format} bytes=${content.data.byteLength}]`;
934
+ }
935
+ //# sourceMappingURL=zvec-grep.js.map