@intentic/iq-engine 1.15.1

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 (120) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +10 -0
  3. package/dist/embed/embedder.d.ts +9 -0
  4. package/dist/embed/embedder.d.ts.map +1 -0
  5. package/dist/embed/embedder.js +25 -0
  6. package/dist/embed/embedder.js.map +1 -0
  7. package/dist/embed/reranker.d.ts +6 -0
  8. package/dist/embed/reranker.d.ts.map +1 -0
  9. package/dist/embed/reranker.js +24 -0
  10. package/dist/embed/reranker.js.map +1 -0
  11. package/dist/engines/astq.d.ts +3 -0
  12. package/dist/engines/astq.d.ts.map +1 -0
  13. package/dist/engines/astq.js +34 -0
  14. package/dist/engines/astq.js.map +1 -0
  15. package/dist/engines/bm25.d.ts +6 -0
  16. package/dist/engines/bm25.d.ts.map +1 -0
  17. package/dist/engines/bm25.js +65 -0
  18. package/dist/engines/bm25.js.map +1 -0
  19. package/dist/engines/files.d.ts +4 -0
  20. package/dist/engines/files.d.ts.map +1 -0
  21. package/dist/engines/files.js +58 -0
  22. package/dist/engines/files.js.map +1 -0
  23. package/dist/engines/git.d.ts +23 -0
  24. package/dist/engines/git.d.ts.map +1 -0
  25. package/dist/engines/git.js +154 -0
  26. package/dist/engines/git.js.map +1 -0
  27. package/dist/engines/lexical.d.ts +13 -0
  28. package/dist/engines/lexical.d.ts.map +1 -0
  29. package/dist/engines/lexical.js +68 -0
  30. package/dist/engines/lexical.js.map +1 -0
  31. package/dist/engines/semantic.d.ts +6 -0
  32. package/dist/engines/semantic.d.ts.map +1 -0
  33. package/dist/engines/semantic.js +44 -0
  34. package/dist/engines/semantic.js.map +1 -0
  35. package/dist/engines/symbols.d.ts +15 -0
  36. package/dist/engines/symbols.d.ts.map +1 -0
  37. package/dist/engines/symbols.js +91 -0
  38. package/dist/engines/symbols.js.map +1 -0
  39. package/dist/features.d.ts +5 -0
  40. package/dist/features.d.ts.map +1 -0
  41. package/dist/features.js +24 -0
  42. package/dist/features.js.map +1 -0
  43. package/dist/index.d.ts +22 -0
  44. package/dist/index.d.ts.map +1 -0
  45. package/dist/index.js +104 -0
  46. package/dist/index.js.map +1 -0
  47. package/dist/indexer/chunker.d.ts +3 -0
  48. package/dist/indexer/chunker.d.ts.map +1 -0
  49. package/dist/indexer/chunker.js +51 -0
  50. package/dist/indexer/chunker.js.map +1 -0
  51. package/dist/indexer/indexer.d.ts +14 -0
  52. package/dist/indexer/indexer.d.ts.map +1 -0
  53. package/dist/indexer/indexer.js +55 -0
  54. package/dist/indexer/indexer.js.map +1 -0
  55. package/dist/indexer/languages.d.ts +13 -0
  56. package/dist/indexer/languages.d.ts.map +1 -0
  57. package/dist/indexer/languages.js +87 -0
  58. package/dist/indexer/languages.js.map +1 -0
  59. package/dist/indexer/symbols.d.ts +3 -0
  60. package/dist/indexer/symbols.d.ts.map +1 -0
  61. package/dist/indexer/symbols.js +115 -0
  62. package/dist/indexer/symbols.js.map +1 -0
  63. package/dist/plan/classify.d.ts +3 -0
  64. package/dist/plan/classify.d.ts.map +1 -0
  65. package/dist/plan/classify.js +24 -0
  66. package/dist/plan/classify.js.map +1 -0
  67. package/dist/plan/fuse.d.ts +10 -0
  68. package/dist/plan/fuse.d.ts.map +1 -0
  69. package/dist/plan/fuse.js +85 -0
  70. package/dist/plan/fuse.js.map +1 -0
  71. package/dist/render/budget.d.ts +2 -0
  72. package/dist/render/budget.d.ts.map +1 -0
  73. package/dist/render/budget.js +2 -0
  74. package/dist/render/budget.js.map +1 -0
  75. package/dist/render/cursor.d.ts +19 -0
  76. package/dist/render/cursor.d.ts.map +1 -0
  77. package/dist/render/cursor.js +35 -0
  78. package/dist/render/cursor.js.map +1 -0
  79. package/dist/render/text.d.ts +31 -0
  80. package/dist/render/text.d.ts.map +1 -0
  81. package/dist/render/text.js +134 -0
  82. package/dist/render/text.js.map +1 -0
  83. package/dist/store/db.d.ts +10 -0
  84. package/dist/store/db.d.ts.map +1 -0
  85. package/dist/store/db.js +95 -0
  86. package/dist/store/db.js.map +1 -0
  87. package/dist/store/index-store.d.ts +27 -0
  88. package/dist/store/index-store.d.ts.map +1 -0
  89. package/dist/store/index-store.js +47 -0
  90. package/dist/store/index-store.js.map +1 -0
  91. package/dist/testing.d.ts +5 -0
  92. package/dist/testing.d.ts.map +1 -0
  93. package/dist/testing.js +31 -0
  94. package/dist/testing.js.map +1 -0
  95. package/dist/types.d.ts +100 -0
  96. package/dist/types.d.ts.map +1 -0
  97. package/dist/types.js +2 -0
  98. package/dist/types.js.map +1 -0
  99. package/dist/verbs/context.d.ts +13 -0
  100. package/dist/verbs/context.d.ts.map +1 -0
  101. package/dist/verbs/context.js +68 -0
  102. package/dist/verbs/context.js.map +1 -0
  103. package/dist/verbs/dispatch.d.ts +18 -0
  104. package/dist/verbs/dispatch.d.ts.map +1 -0
  105. package/dist/verbs/dispatch.js +486 -0
  106. package/dist/verbs/dispatch.js.map +1 -0
  107. package/dist/workspace/floor.d.ts +3 -0
  108. package/dist/workspace/floor.d.ts.map +1 -0
  109. package/dist/workspace/floor.js +3 -0
  110. package/dist/workspace/floor.js.map +1 -0
  111. package/dist/workspace/glob.d.ts +2 -0
  112. package/dist/workspace/glob.d.ts.map +1 -0
  113. package/dist/workspace/glob.js +53 -0
  114. package/dist/workspace/glob.js.map +1 -0
  115. package/dist/workspace/scan.d.ts +7 -0
  116. package/dist/workspace/scan.d.ts.map +1 -0
  117. package/dist/workspace/scan.js +104 -0
  118. package/dist/workspace/scan.js.map +1 -0
  119. package/package.json +68 -0
  120. package/scripts/fetch-model.mjs +18 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Artur Kurowski
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,10 @@
1
+ # @intentic/iq-engine
2
+
3
+ The search engine behind the [`iq`](../../_apps/iq) CLI: a disk index (`node:sqlite`) fused across
4
+ **lexical** (ripgrep), **structural** (ast-grep), **semantic** (local embed + rerank), and **git** engines,
5
+ with rank fusion and a token-budget renderer. This is the heavy lib of the iq cluster.
6
+
7
+ **Part of the iq dependency island** — imported only by `@intentic/iq` and `@intentic/iq-bench`. iq is
8
+ invoked as a subprocess (the daemon's `/workspace/search` route shells `iq --json`; the agent runs it as a
9
+ CLI), so **no app or daemon code imports this package**, and none should. Its `Iq*`-shaped results reach the
10
+ web only through the `sandbox-contract` `WorkspaceSearch*` schemas, never by import.
@@ -0,0 +1,9 @@
1
+ export declare const MODEL_ID = "Xenova/bge-small-en-v1.5";
2
+ export declare const EMBEDDING_DIM = 384;
3
+ export interface Embedder {
4
+ readonly modelId: string;
5
+ embedBatch(texts: readonly string[]): Promise<Float32Array[]>;
6
+ embedQuery(query: string): Promise<Float32Array>;
7
+ }
8
+ export declare const loadEmbedder: (modelDir: string | undefined) => Promise<Embedder | undefined>;
9
+ //# sourceMappingURL=embedder.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embedder.d.ts","sourceRoot":"","sources":["../../src/embed/embedder.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,QAAQ,6BAA6B,CAAC;AACnD,eAAO,MAAM,aAAa,MAAM,CAAC;AAKjC,MAAM,WAAW,QAAQ;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,UAAU,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;IAC9D,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC,CAAC;CACpD;AAID,eAAO,MAAM,YAAY,GAAU,UAAU,MAAM,GAAG,SAAS,KAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAmB7F,CAAC"}
@@ -0,0 +1,25 @@
1
+ import { existsSync } from "node:fs";
2
+ export const MODEL_ID = "Xenova/bge-small-en-v1.5";
3
+ export const EMBEDDING_DIM = 384;
4
+ const QUERY_PREFIX = "Represent this sentence for searching relevant passages: ";
5
+ export const loadEmbedder = async (modelDir) => {
6
+ if (modelDir === undefined || modelDir === "" || !existsSync(modelDir)) {
7
+ return undefined;
8
+ }
9
+ const { env, pipeline } = await import("@huggingface/transformers");
10
+ env.localModelPath = modelDir;
11
+ env.cacheDir = modelDir;
12
+ env.allowRemoteModels = false;
13
+ const extractor = await pipeline("feature-extraction", MODEL_ID, { dtype: "q8" });
14
+ const embed = async (texts) => {
15
+ const tensor = await extractor([...texts], { pooling: "mean", normalize: true });
16
+ const data = tensor.data;
17
+ return texts.map((_, i) => new Float32Array(data.buffer, data.byteOffset + i * EMBEDDING_DIM * 4, EMBEDDING_DIM));
18
+ };
19
+ return {
20
+ modelId: MODEL_ID,
21
+ embedBatch: embed,
22
+ embedQuery: async (query) => (await embed([`${QUERY_PREFIX}${query}`]))[0],
23
+ };
24
+ };
25
+ //# sourceMappingURL=embedder.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"embedder.js","sourceRoot":"","sources":["../../src/embed/embedder.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAErC,MAAM,CAAC,MAAM,QAAQ,GAAG,0BAA0B,CAAC;AACnD,MAAM,CAAC,MAAM,aAAa,GAAG,GAAG,CAAC;AAGjC,MAAM,YAAY,GAAG,2DAA2D,CAAC;AAUjF,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,EAAE,QAA4B,EAAiC,EAAE;IAC9F,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,EAAE,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QACrE,OAAO,SAAS,CAAC;IACrB,CAAC;IACD,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,GAAG,MAAM,MAAM,CAAC,2BAA2B,CAAC,CAAC;IACpE,GAAG,CAAC,cAAc,GAAG,QAAQ,CAAC;IAC9B,GAAG,CAAC,QAAQ,GAAG,QAAQ,CAAC;IACxB,GAAG,CAAC,iBAAiB,GAAG,KAAK,CAAC;IAC9B,MAAM,SAAS,GAAG,MAAM,QAAQ,CAAC,oBAAoB,EAAE,QAAQ,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAClF,MAAM,KAAK,GAAG,KAAK,EAAE,KAAwB,EAA2B,EAAE;QACtE,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC,CAAC,GAAG,KAAK,CAAC,EAAE,EAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACjF,MAAM,IAAI,GAAG,MAAM,CAAC,IAAoB,CAAC;QACzC,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,IAAI,YAAY,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,UAAU,GAAG,CAAC,GAAG,aAAa,GAAG,CAAC,EAAE,aAAa,CAAC,CAAC,CAAC;IACtH,CAAC,CAAC;IACF,OAAO;QACH,OAAO,EAAE,QAAQ;QACjB,UAAU,EAAE,KAAK;QACjB,UAAU,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,GAAG,YAAY,GAAG,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAE;KAC9E,CAAC;AACN,CAAC,CAAC"}
@@ -0,0 +1,6 @@
1
+ export declare const RERANKER_ID = "Xenova/ms-marco-MiniLM-L-6-v2";
2
+ export interface Reranker {
3
+ rerank(query: string, passages: readonly string[]): Promise<number[]>;
4
+ }
5
+ export declare const loadReranker: (modelDir: string | undefined) => Promise<Reranker | undefined>;
6
+ //# sourceMappingURL=reranker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reranker.d.ts","sourceRoot":"","sources":["../../src/embed/reranker.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,WAAW,kCAAkC,CAAC;AAE3D,MAAM,WAAW,QAAQ;IAErB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;CACzE;AAID,eAAO,MAAM,YAAY,GAAU,UAAU,MAAM,GAAG,SAAS,KAAG,OAAO,CAAC,QAAQ,GAAG,SAAS,CAuB7F,CAAC"}
@@ -0,0 +1,24 @@
1
+ import { existsSync } from "node:fs";
2
+ export const RERANKER_ID = "Xenova/ms-marco-MiniLM-L-6-v2";
3
+ export const loadReranker = async (modelDir) => {
4
+ if (modelDir === undefined || modelDir === "" || !existsSync(modelDir)) {
5
+ return undefined;
6
+ }
7
+ const { AutoModelForSequenceClassification, AutoTokenizer, env } = await import("@huggingface/transformers");
8
+ env.localModelPath = modelDir;
9
+ env.cacheDir = modelDir;
10
+ env.allowRemoteModels = false;
11
+ const tokenizer = await AutoTokenizer.from_pretrained(RERANKER_ID);
12
+ const model = await AutoModelForSequenceClassification.from_pretrained(RERANKER_ID, { dtype: "q8" });
13
+ return {
14
+ async rerank(query, passages) {
15
+ if (passages.length === 0) {
16
+ return [];
17
+ }
18
+ const inputs = tokenizer(passages.map(() => query), { text_pair: [...passages], padding: true, truncation: true });
19
+ const { logits } = (await model(inputs));
20
+ return [...logits.data];
21
+ },
22
+ };
23
+ };
24
+ //# sourceMappingURL=reranker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reranker.js","sourceRoot":"","sources":["../../src/embed/reranker.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAErC,MAAM,CAAC,MAAM,WAAW,GAAG,+BAA+B,CAAC;AAS3D,MAAM,CAAC,MAAM,YAAY,GAAG,KAAK,EAAE,QAA4B,EAAiC,EAAE;IAC9F,IAAI,QAAQ,KAAK,SAAS,IAAI,QAAQ,KAAK,EAAE,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QACrE,OAAO,SAAS,CAAC;IACrB,CAAC;IACD,MAAM,EAAE,kCAAkC,EAAE,aAAa,EAAE,GAAG,EAAE,GAAG,MAAM,MAAM,CAAC,2BAA2B,CAAC,CAAC;IAC7G,GAAG,CAAC,cAAc,GAAG,QAAQ,CAAC;IAC9B,GAAG,CAAC,QAAQ,GAAG,QAAQ,CAAC;IACxB,GAAG,CAAC,iBAAiB,GAAG,KAAK,CAAC;IAC9B,MAAM,SAAS,GAAG,MAAM,aAAa,CAAC,eAAe,CAAC,WAAW,CAAC,CAAC;IACnE,MAAM,KAAK,GAAG,MAAM,kCAAkC,CAAC,eAAe,CAAC,WAAW,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACrG,OAAO;QACH,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,QAAQ;YACxB,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;gBACxB,OAAO,EAAE,CAAC;YACd,CAAC;YACD,MAAM,MAAM,GAAG,SAAS,CACpB,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,EACzB,EAAE,SAAS,EAAE,CAAC,GAAG,QAAQ,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,CAChE,CAAC;YACF,MAAM,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,KAAK,CAAC,MAAM,CAAC,CAAuC,CAAC;YAC/E,OAAO,CAAC,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC;QAC5B,CAAC;KACJ,CAAC;AACN,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { EngineHit, FileEntry } from "../types.js";
2
+ export declare const astSearch: (pattern: string, lang: string, entries: readonly FileEntry[]) => Promise<EngineHit[]>;
3
+ //# sourceMappingURL=astq.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"astq.d.ts","sourceRoot":"","sources":["../../src/engines/astq.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAQxD,eAAO,MAAM,SAAS,GAAU,SAAS,MAAM,EAAE,MAAM,MAAM,EAAE,SAAS,SAAS,SAAS,EAAE,KAAG,OAAO,CAAC,SAAS,EAAE,CA4BjH,CAAC"}
@@ -0,0 +1,34 @@
1
+ import { readFile } from "node:fs/promises";
2
+ import { LANGUAGES, parseLang } from "../indexer/languages.js";
3
+ import { langOf } from "../workspace/scan.js";
4
+ const MAX_FILE_BYTES = 1024 * 1024;
5
+ export const astSearch = async (pattern, lang, entries) => {
6
+ if (LANGUAGES[lang] === undefined) {
7
+ throw new Error(`iq ast: unsupported --lang ${lang} (supported: ${Object.keys(LANGUAGES).join(", ")})`);
8
+ }
9
+ const hits = [];
10
+ for (const entry of entries) {
11
+ if (langOf(entry.path) !== lang || entry.size > MAX_FILE_BYTES) {
12
+ continue;
13
+ }
14
+ const content = await readFile(entry.abs, "utf8").catch(() => undefined);
15
+ if (content === undefined) {
16
+ continue;
17
+ }
18
+ const root = parseLang(lang, content)?.root();
19
+ if (root === undefined) {
20
+ continue;
21
+ }
22
+ for (const node of root.findAll(pattern)) {
23
+ const range = node.range();
24
+ hits.push({
25
+ path: entry.path,
26
+ line: range.start.line + 1,
27
+ text: node.text().split("\n", 1)[0].trim(),
28
+ tags: [{ kind: "text" }],
29
+ });
30
+ }
31
+ }
32
+ return hits.toSorted((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : a.line - b.line));
33
+ };
34
+ //# sourceMappingURL=astq.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"astq.js","sourceRoot":"","sources":["../../src/engines/astq.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,yBAAyB,CAAC;AAC/D,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAE9C,MAAM,cAAc,GAAG,IAAI,GAAG,IAAI,CAAC;AAInC,MAAM,CAAC,MAAM,SAAS,GAAG,KAAK,EAAE,OAAe,EAAE,IAAY,EAAE,OAA6B,EAAwB,EAAE;IAClH,IAAI,SAAS,CAAC,IAAI,CAAC,KAAK,SAAS,EAAE,CAAC;QAChC,MAAM,IAAI,KAAK,CAAC,8BAA8B,IAAI,gBAAgB,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC5G,CAAC;IACD,MAAM,IAAI,GAAgB,EAAE,CAAC;IAC7B,KAAK,MAAM,KAAK,IAAI,OAAO,EAAE,CAAC;QAC1B,IAAI,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,IAAI,IAAI,KAAK,CAAC,IAAI,GAAG,cAAc,EAAE,CAAC;YAC7D,SAAS;QACb,CAAC;QACD,MAAM,OAAO,GAAG,MAAM,QAAQ,CAAC,KAAK,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QACzE,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;YACxB,SAAS;QACb,CAAC;QACD,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,IAAI,EAAE,CAAC;QAC9C,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;YACrB,SAAS;QACb,CAAC;QACD,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;YACvC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC;YAC3B,IAAI,CAAC,IAAI,CAAC;gBACN,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,IAAI,GAAG,CAAC;gBAC1B,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,CAAE,CAAC,IAAI,EAAE;gBAC3C,IAAI,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;aAC3B,CAAC,CAAC;QACP,CAAC;IACL,CAAC;IACD,OAAO,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;AACnG,CAAC,CAAC"}
@@ -0,0 +1,6 @@
1
+ import type { IndexDb } from "../store/db.js";
2
+ import type { EngineHit } from "../types.js";
3
+ export declare const toMatch: (query: string) => string | undefined;
4
+ export declare const prfTerms: (db: IndexDb, query: string) => string[];
5
+ export declare const bm25Search: (db: IndexDb, query: string, allowed: ReadonlySet<string>) => EngineHit[];
6
+ //# sourceMappingURL=bm25.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bm25.d.ts","sourceRoot":"","sources":["../../src/engines/bm25.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAY7C,eAAO,MAAM,OAAO,GAAI,OAAO,MAAM,KAAG,MAAM,GAAG,SAMhD,CAAC;AAOF,eAAO,MAAM,QAAQ,GAAI,IAAI,OAAO,EAAE,OAAO,MAAM,KAAG,MAAM,EA6B3D,CAAC;AAIF,eAAO,MAAM,UAAU,GAAI,IAAI,OAAO,EAAE,OAAO,MAAM,EAAE,SAAS,WAAW,CAAC,MAAM,CAAC,KAAG,SAAS,EA0B9F,CAAC"}
@@ -0,0 +1,65 @@
1
+ const TOP_K = 50;
2
+ const STOPWORDS = new Set("a an and are as at be but by do does for from has have how i in is it of on or that the this to was we what when where which who why with you".split(" "));
3
+ export const toMatch = (query) => {
4
+ const terms = [...new Set(query.toLowerCase().match(/[a-z0-9_$][\w$]+/g) ?? [])].filter((term) => !STOPWORDS.has(term));
5
+ if (terms.length === 0) {
6
+ return undefined;
7
+ }
8
+ return terms.map((term) => `"${term.replaceAll('"', "")}"`).join(" OR ");
9
+ };
10
+ const PRF_DOCS = 10;
11
+ const PRF_TERMS = 8;
12
+ export const prfTerms = (db, query) => {
13
+ const match = toMatch(query);
14
+ if (match === undefined) {
15
+ return [];
16
+ }
17
+ const queryTerms = new Set(match.replaceAll('"', "").toLowerCase().split(" OR "));
18
+ const total = Number(db.get("SELECT COUNT(*) AS n FROM chunks")?.["n"] ?? 0);
19
+ if (total === 0) {
20
+ return [];
21
+ }
22
+ const tf = new Map();
23
+ for (const row of db.all(`SELECT text FROM chunks_fts WHERE chunks_fts MATCH ? ORDER BY bm25(chunks_fts) LIMIT ?`, match, PRF_DOCS)) {
24
+ for (const term of row["text"].toLowerCase().match(/[a-z_$][\w$]{2,}/g) ?? []) {
25
+ if (!queryTerms.has(term) && !STOPWORDS.has(term)) {
26
+ tf.set(term, (tf.get(term) ?? 0) + 1);
27
+ }
28
+ }
29
+ }
30
+ const scored = [];
31
+ for (const [term, frequency] of tf) {
32
+ const df = Number(db.get("SELECT COUNT(*) AS n FROM chunks_fts WHERE chunks_fts MATCH ?", `"${term}"`)?.["n"] ?? 0);
33
+ if (df > 0 && df < total / 4) {
34
+ scored.push({ term, score: frequency * Math.log(total / df) });
35
+ }
36
+ }
37
+ return scored
38
+ .toSorted((a, b) => b.score - a.score || (a.term < b.term ? -1 : 1))
39
+ .slice(0, PRF_TERMS)
40
+ .map(({ term }) => term);
41
+ };
42
+ export const bm25Search = (db, query, allowed) => {
43
+ const match = toMatch(query);
44
+ if (match === undefined) {
45
+ return [];
46
+ }
47
+ const rows = db.all(`SELECT f.path, c.start_line, c.text, bm25(chunks_fts) AS score
48
+ FROM chunks_fts JOIN chunks c ON c.id = chunks_fts.rowid JOIN files f ON f.id = c.file_id
49
+ WHERE chunks_fts MATCH ? ORDER BY bm25(chunks_fts), f.path, c.start_line LIMIT ?`, match, TOP_K * 2);
50
+ const hits = [];
51
+ for (const row of rows) {
52
+ const path = row["path"];
53
+ if (!allowed.has(path)) {
54
+ continue;
55
+ }
56
+ const text = row["text"].split("\n")[1]?.trim() ?? row["text"].split(" § ")[1] ?? "";
57
+ const score = Math.round(Math.min(1, -Number(row["score"]) / 10) * 100) / 100;
58
+ hits.push({ path, line: Number(row["start_line"]), text, tags: [{ kind: "bm25", score }] });
59
+ if (hits.length >= TOP_K) {
60
+ break;
61
+ }
62
+ }
63
+ return hits;
64
+ };
65
+ //# sourceMappingURL=bm25.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bm25.js","sourceRoot":"","sources":["../../src/engines/bm25.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ import type { EngineHit } from "../types.js";
2
+ export declare const fuzzyScore: (needle: string, path: string) => number | undefined;
3
+ export declare const fileSearch: (pattern: string, paths: readonly string[], glob: boolean) => EngineHit[];
4
+ //# sourceMappingURL=files.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"files.d.ts","sourceRoot":"","sources":["../../src/engines/files.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAO7C,eAAO,MAAM,UAAU,GAAI,QAAQ,MAAM,EAAE,MAAM,MAAM,KAAG,MAAM,GAAG,SAmClE,CAAC;AAIF,eAAO,MAAM,UAAU,GAAI,SAAS,MAAM,EAAE,OAAO,SAAS,MAAM,EAAE,EAAE,MAAM,OAAO,KAAG,SAAS,EAoB9F,CAAC"}
@@ -0,0 +1,58 @@
1
+ import { globToRegExp } from "../workspace/glob.js";
2
+ const BOUNDARY = new Set(["/", ".", "_", "-"]);
3
+ export const fuzzyScore = (needle, path) => {
4
+ const n = needle.toLowerCase();
5
+ const h = path.toLowerCase();
6
+ if (n.length === 0 || n.length > h.length) {
7
+ return undefined;
8
+ }
9
+ const at = h.indexOf(n);
10
+ if (at !== -1) {
11
+ const inBasename = at >= h.lastIndexOf("/") + 1;
12
+ return 0.75 + (inBasename ? 0.2 : 0) + 5 / h.length;
13
+ }
14
+ let score = 0;
15
+ let hi = 0;
16
+ let previousHit = -2;
17
+ for (let ni = 0; ni < n.length; ni++) {
18
+ const ch = n[ni];
19
+ let found = -1;
20
+ while (hi < h.length) {
21
+ if (h[hi] === ch) {
22
+ found = hi;
23
+ break;
24
+ }
25
+ hi++;
26
+ }
27
+ if (found === -1) {
28
+ return undefined;
29
+ }
30
+ const boundary = found === 0 || BOUNDARY.has(h[found - 1]) || (path[found] !== undefined && path[found] >= "A" && path[found] <= "Z");
31
+ score += 1 + (found === previousHit + 1 ? 0.8 : 0) + (boundary ? 0.6 : 0);
32
+ previousHit = found;
33
+ hi = found + 1;
34
+ }
35
+ return (score / (n.length * 2.4)) * 0.7 * Math.min(1, 20 / Math.max(20, h.length - n.length));
36
+ };
37
+ export const fileSearch = (pattern, paths, glob) => {
38
+ if (glob) {
39
+ const re = globToRegExp(pattern);
40
+ return paths.filter((path) => re.test(path)).map((path) => ({ path, line: 1, text: path, tags: [{ kind: "path" }] }));
41
+ }
42
+ const scored = [];
43
+ for (const path of paths) {
44
+ const score = fuzzyScore(pattern, path);
45
+ if (score !== undefined) {
46
+ scored.push({ path, score });
47
+ }
48
+ }
49
+ return scored
50
+ .toSorted((a, b) => b.score - a.score || (a.path < b.path ? -1 : 1))
51
+ .map(({ path, score }) => ({
52
+ path,
53
+ line: 1,
54
+ text: path,
55
+ tags: [{ kind: "fuzzy", score: Math.round(Math.min(1, score) * 100) / 100 }],
56
+ }));
57
+ };
58
+ //# sourceMappingURL=files.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"files.js","sourceRoot":"","sources":["../../src/engines/files.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import type { FileEntry, RankedGroup } from "../types.js";
2
+ export declare const parseSince: (since: string) => number;
3
+ export declare const agoText: (ms: number) => string;
4
+ export declare const reposOf: (entries: readonly FileEntry[]) => string[];
5
+ export interface RecentOptions {
6
+ readonly since?: string;
7
+ readonly author?: string;
8
+ readonly pattern?: string;
9
+ }
10
+ export declare const recentFiles: (root: string, entries: readonly FileEntry[], options: RecentOptions) => Promise<RankedGroup[]>;
11
+ export interface LogOptions {
12
+ readonly regex?: boolean;
13
+ readonly since?: string;
14
+ readonly author?: string;
15
+ readonly path?: string;
16
+ }
17
+ export declare const logSearch: (root: string, entries: readonly FileEntry[], pattern: string, options: LogOptions) => Promise<RankedGroup[]>;
18
+ export declare const whoAnchor: (root: string, entries: readonly FileEntry[], anchor: {
19
+ path: string;
20
+ line: number;
21
+ endLine?: number;
22
+ }) => Promise<RankedGroup[]>;
23
+ //# sourceMappingURL=git.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../src/engines/git.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAa,MAAM,aAAa,CAAC;AAQrE,eAAO,MAAM,UAAU,GAAI,OAAO,MAAM,KAAG,MAO1C,CAAC;AAEF,eAAO,MAAM,OAAO,GAAI,IAAI,MAAM,KAAG,MAWpC,CAAC;AAEF,eAAO,MAAM,OAAO,GAAI,SAAS,SAAS,SAAS,EAAE,KAAG,MAAM,EAE7D,CAAC;AASF,MAAM,WAAW,aAAa;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;CAC7B;AAYD,eAAO,MAAM,WAAW,GAAU,MAAM,MAAM,EAAE,SAAS,SAAS,SAAS,EAAE,EAAE,SAAS,aAAa,KAAG,OAAO,CAAC,WAAW,EAAE,CAiD5H,CAAC;AAEF,MAAM,WAAW,UAAU;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CAC1B;AAID,eAAO,MAAM,SAAS,GAAU,MAAM,MAAM,EAAE,SAAS,SAAS,SAAS,EAAE,EAAE,SAAS,MAAM,EAAE,SAAS,UAAU,KAAG,OAAO,CAAC,WAAW,EAAE,CAuBxI,CAAC;AAIF,eAAO,MAAM,SAAS,GAClB,MAAM,MAAM,EACZ,SAAS,SAAS,SAAS,EAAE,EAC7B,QAAQ;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,KACzD,OAAO,CAAC,WAAW,EAAE,CA2CvB,CAAC"}
@@ -0,0 +1,154 @@
1
+ import { execFile } from "node:child_process";
2
+ import { join } from "node:path";
3
+ import { promisify } from "node:util";
4
+ import { isIqDenied } from "../workspace/floor.js";
5
+ const exec = promisify(execFile);
6
+ const DEFAULT_SINCE = "7d";
7
+ export const parseSince = (since) => {
8
+ const match = /^(\d+)([hdwm])$/.exec(since);
9
+ if (match === null) {
10
+ throw new Error(`iq: --since expects e.g. 12h, 2d, 1w, 3m — got: ${since}`);
11
+ }
12
+ const n = Number(match[1]);
13
+ return n * { h: 3_600_000, d: 86_400_000, w: 604_800_000, m: 2_592_000_000 }[match[2]];
14
+ };
15
+ export const agoText = (ms) => {
16
+ if (ms < 60_000) {
17
+ return "just now";
18
+ }
19
+ if (ms < 3_600_000) {
20
+ return `${Math.round(ms / 60_000)}m ago`;
21
+ }
22
+ if (ms < 86_400_000) {
23
+ return `${Math.round(ms / 3_600_000)}h ago`;
24
+ }
25
+ return `${Math.round(ms / 86_400_000)}d ago`;
26
+ };
27
+ export const reposOf = (entries) => [
28
+ ...new Set(entries.map((entry) => entry.repo).filter((repo) => repo !== undefined)),
29
+ ];
30
+ const git = async (root, repo, args) => {
31
+ const { stdout } = await exec("git", ["-C", join(root, repo), ...args], { maxBuffer: 16 * 1024 * 1024 }).catch(() => ({ stdout: "" }));
32
+ return stdout;
33
+ };
34
+ const toWorkspacePath = (repo, repoRel) => (repo === "" ? repoRel : `${repo}/${repoRel}`);
35
+ export const recentFiles = async (root, entries, options) => {
36
+ const windowMs = parseSince(options.since ?? DEFAULT_SINCE);
37
+ const now = Date.now();
38
+ const allowed = new Set(entries.map((entry) => entry.path));
39
+ const activity = new Map();
40
+ for (const repo of reposOf(entries)) {
41
+ const args = ["log", `--since=${Math.floor((now - windowMs) / 1000)}`, "--numstat", "--format=commit:%ct"];
42
+ if (options.author !== undefined) {
43
+ args.push(`--author=${options.author}`);
44
+ }
45
+ let commitMs = 0;
46
+ for (const line of (await git(root, repo, args)).split("\n")) {
47
+ if (line.startsWith("commit:")) {
48
+ commitMs = Number(line.slice(7)) * 1000;
49
+ continue;
50
+ }
51
+ const match = /^(\d+|-)\t(\d+|-)\t(.+)$/.exec(line);
52
+ if (match === null) {
53
+ continue;
54
+ }
55
+ const path = toWorkspacePath(repo, match[3]);
56
+ if (!allowed.has(path) || isIqDenied(path)) {
57
+ continue;
58
+ }
59
+ const entry = activity.get(path) ?? { latestMs: 0, commits: 0, adds: 0, dels: 0, uncommitted: false };
60
+ entry.commits++;
61
+ entry.adds += match[1] === "-" ? 0 : Number(match[1]);
62
+ entry.dels += match[2] === "-" ? 0 : Number(match[2]);
63
+ entry.latestMs = Math.max(entry.latestMs, commitMs);
64
+ activity.set(path, entry);
65
+ }
66
+ }
67
+ for (const entry of entries) {
68
+ if (now - entry.mtimeMs <= windowMs && !activity.has(entry.path)) {
69
+ activity.set(entry.path, { latestMs: entry.mtimeMs, commits: 0, adds: 0, dels: 0, uncommitted: true });
70
+ }
71
+ }
72
+ const pattern = options.pattern !== undefined && options.pattern !== "" ? options.pattern.toLowerCase() : undefined;
73
+ return [...activity.entries()]
74
+ .filter(([path]) => pattern === undefined || path.toLowerCase().includes(pattern))
75
+ .toSorted((a, b) => b[1].latestMs - a[1].latestMs || (a[0] < b[0] ? -1 : 1))
76
+ .map(([path, info], rank) => {
77
+ const summary = info.uncommitted
78
+ ? `${agoText(now - info.latestMs)} uncommitted`
79
+ : `${agoText(now - info.latestMs)} +${info.adds} -${info.dels} (${info.commits} commit${info.commits === 1 ? "" : "s"})`;
80
+ return { path, score: 1 / (rank + 1), hits: [{ path, line: 1, text: summary, tags: [], score: 1 / (rank + 1) }] };
81
+ });
82
+ };
83
+ export const logSearch = async (root, entries, pattern, options) => {
84
+ const groups = [];
85
+ for (const repo of reposOf(entries)) {
86
+ const args = ["log", options.regex === true ? `-G${pattern}` : `-S${pattern}`, "--format=%h%x09%as%x09%an%x09%s"];
87
+ if (options.since !== undefined) {
88
+ args.push(`--since=${Math.floor((Date.now() - parseSince(options.since)) / 1000)}`);
89
+ }
90
+ if (options.author !== undefined) {
91
+ args.push(`--author=${options.author}`);
92
+ }
93
+ if (options.path !== undefined) {
94
+ const inRepo = options.path.startsWith(`${repo}/`) ? options.path.slice(repo.length + 1) : options.path;
95
+ args.push("--", inRepo);
96
+ }
97
+ const hits = (await git(root, repo, args))
98
+ .split("\n")
99
+ .filter((line) => line !== "")
100
+ .map((line, i) => ({ path: repo, line: i + 1, text: line.replaceAll("\t", " "), tags: [], score: 1 }));
101
+ if (hits.length > 0) {
102
+ groups.push({ path: repo, score: 1 / (groups.length + 1), hits });
103
+ }
104
+ }
105
+ return groups;
106
+ };
107
+ export const whoAnchor = async (root, entries, anchor) => {
108
+ const entry = entries.find((candidate) => candidate.path === anchor.path);
109
+ if (entry?.repo === undefined) {
110
+ throw new Error(`iq who: ${anchor.path} is not inside a git repo the sweep admits`);
111
+ }
112
+ const repoRel = anchor.path.slice(entry.repo.length + 1);
113
+ const to = anchor.endLine ?? anchor.line;
114
+ const stdout = await git(root, entry.repo, ["blame", "-L", `${anchor.line},${to}`, "--line-porcelain", "--", repoRel]);
115
+ if (stdout === "") {
116
+ return [];
117
+ }
118
+ const commits = new Map();
119
+ let current;
120
+ for (const line of stdout.split("\n")) {
121
+ if (/^[0-9a-f]{40} /.test(line)) {
122
+ current = { hash: line.slice(0, 7) };
123
+ continue;
124
+ }
125
+ if (current === undefined) {
126
+ continue;
127
+ }
128
+ if (line.startsWith("author ")) {
129
+ current.author = line.slice(7);
130
+ }
131
+ else if (line.startsWith("author-time ")) {
132
+ current.date = new Date(Number(line.slice(12)) * 1000).toISOString().slice(0, 10);
133
+ }
134
+ else if (line.startsWith("summary ")) {
135
+ current.summary = line.slice(8);
136
+ }
137
+ else if (line.startsWith("\t")) {
138
+ const existing = commits.get(current.hash) ?? {
139
+ author: current.author ?? "?",
140
+ date: current.date ?? "?",
141
+ summary: current.summary ?? "",
142
+ lines: [],
143
+ };
144
+ existing.lines.push(line.slice(1));
145
+ commits.set(current.hash, existing);
146
+ }
147
+ }
148
+ const hits = [...commits.entries()].flatMap(([hash, info], i) => [
149
+ { path: anchor.path, line: i * 2 + 1, text: `${hash} ${info.date} ${info.author} ${info.summary}`, tags: [], score: 1 },
150
+ { path: anchor.path, line: i * 2 + 2, text: `line: ${info.lines.join(" ⏎ ")}`, tags: [], score: 1 },
151
+ ]);
152
+ return [{ path: anchor.path, score: 1, hits }];
153
+ };
154
+ //# sourceMappingURL=git.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git.js","sourceRoot":"","sources":["../../src/engines/git.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ import type { EngineHit } from "../types.js";
2
+ export interface RgOptions {
3
+ readonly root: string;
4
+ readonly pattern: string;
5
+ readonly literal?: boolean;
6
+ readonly word?: boolean;
7
+ readonly caseSensitive?: boolean;
8
+ readonly ignored?: boolean;
9
+ readonly allowed: ReadonlySet<string>;
10
+ readonly rgPath?: string;
11
+ }
12
+ export declare const rgSearch: (options: RgOptions) => Promise<EngineHit[]>;
13
+ //# sourceMappingURL=lexical.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lexical.d.ts","sourceRoot":"","sources":["../../src/engines/lexical.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAU7C,MAAM,WAAW,SAAS;IACtB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,aAAa,CAAC,EAAE,OAAO,CAAC;IACjC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC;IAE3B,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACtC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC5B;AAmBD,eAAO,MAAM,QAAQ,GAAU,SAAS,SAAS,KAAG,OAAO,CAAC,SAAS,EAAE,CAwDtE,CAAC"}
@@ -0,0 +1,68 @@
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ import { IGNORED_DIRS } from "@intentic/workspace-ignore";
4
+ import { IQ_DIR } from "../workspace/floor.js";
5
+ const exec = promisify(execFile);
6
+ const MAX_PER_FILE = 50;
7
+ const SNIPPET_MAX = 200;
8
+ const SNIPPET_LEAD = 40;
9
+ const window = (line, start, end) => {
10
+ if (line.length <= SNIPPET_MAX) {
11
+ return { text: line, start, end };
12
+ }
13
+ const from = Math.max(0, start - SNIPPET_LEAD);
14
+ return { text: line.slice(from, from + SNIPPET_MAX), start: start - from, end: Math.min(end - from, SNIPPET_MAX) };
15
+ };
16
+ export const rgSearch = async (options) => {
17
+ const args = ["--json", "--hidden", "--max-filesize", "1M", "--max-count", String(MAX_PER_FILE), "--no-config", "--no-messages"];
18
+ for (const dir of options.ignored ? [".git"] : [...IGNORED_DIRS, ".git"]) {
19
+ args.push("-g", `!**/${dir}`);
20
+ }
21
+ args.push("-g", `!${IQ_DIR}`);
22
+ if (options.ignored) {
23
+ args.push("--no-ignore");
24
+ }
25
+ if (options.literal) {
26
+ args.push("-F");
27
+ }
28
+ if (options.word) {
29
+ args.push("-w");
30
+ }
31
+ args.push(options.caseSensitive ? "-s" : "-S");
32
+ args.push("-e", options.pattern, "./");
33
+ const { stdout } = await exec(options.rgPath ?? "rg", args, { cwd: options.root, maxBuffer: 64 * 1024 * 1024 }).catch((error) => {
34
+ if (error.code === 1) {
35
+ return { stdout: error.stdout ?? "" };
36
+ }
37
+ if (typeof error.code === "number") {
38
+ throw new Error(`ripgrep: ${(error.stderr ?? "").trim() || "search failed"}`);
39
+ }
40
+ if (error.code === "ENOENT") {
41
+ throw new Error("iq: ripgrep (rg) not found on PATH — install ripgrep or set IQ_RG_PATH");
42
+ }
43
+ throw error;
44
+ });
45
+ const hits = [];
46
+ for (const line of stdout.split("\n")) {
47
+ if (line === "") {
48
+ continue;
49
+ }
50
+ const event = JSON.parse(line);
51
+ if (event.type !== "match") {
52
+ continue;
53
+ }
54
+ const path = event.data.path.text.replace(/^\.\//, "");
55
+ if (!options.allowed.has(path)) {
56
+ continue;
57
+ }
58
+ const text = event.data.lines.text?.replace(/\r?\n$/, "");
59
+ const sub = event.data.submatches[0];
60
+ if (text === undefined || sub === undefined) {
61
+ continue;
62
+ }
63
+ const snippet = window(text, sub.start, sub.end);
64
+ hits.push({ path, line: event.data.line_number, text: snippet.text, start: snippet.start, end: snippet.end, tags: [{ kind: "text" }] });
65
+ }
66
+ return hits.toSorted((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : a.line - b.line));
67
+ };
68
+ //# sourceMappingURL=lexical.js.map