@wrongstack/tools 0.68.0 → 0.73.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.
- package/dist/{codebase-stats-tool-C8ApERbn.d.ts → background-indexer-sbsseCCC.d.ts} +49 -1
- package/dist/builtin.js +122 -54
- package/dist/builtin.js.map +1 -1
- package/dist/codebase-index/index.d.ts +35 -4
- package/dist/codebase-index/index.js +140 -12
- package/dist/codebase-index/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +145 -13
- package/dist/index.js.map +1 -1
- package/dist/pack.js +122 -54
- package/dist/pack.js.map +1 -1
- package/dist/tool-search.js +5 -1
- package/dist/tool-search.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,6 +1,29 @@
|
|
|
1
|
-
import { d as Symbol, F as FileMeta, e as SymbolKind, f as SymbolLang, c as SearchResult, b as IndexStats, R as Ref } from '../
|
|
2
|
-
export { a as FileSymbols,
|
|
3
|
-
import '@wrongstack/core';
|
|
1
|
+
import { I as IndexResult, d as Symbol, F as FileMeta, e as SymbolKind, f as SymbolLang, c as SearchResult, b as IndexStats, R as Ref } from '../background-indexer-sbsseCCC.js';
|
|
2
|
+
export { a as FileSymbols, S as SCHEMA_VERSION, g as cancelPendingReindexes, h as codebaseIndexTool, i as codebaseSearchTool, j as codebaseStatsTool, k as enqueueReindex, l as isIndexableFile, r as runStartupIndex } from '../background-indexer-sbsseCCC.js';
|
|
3
|
+
import { Context } from '@wrongstack/core';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Main indexing orchestrator.
|
|
7
|
+
*
|
|
8
|
+
* Given a project root and a list of files:
|
|
9
|
+
* 1. Parse each file with the appropriate parser (TS, Go, Python, Rust, JSON, YAML)
|
|
10
|
+
* 2. Delete old symbols for changed/deleted files
|
|
11
|
+
* 3. Insert new symbols
|
|
12
|
+
* 4. Update file metadata
|
|
13
|
+
* 5. Return index statistics
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
interface IndexerOptions {
|
|
17
|
+
projectRoot: string;
|
|
18
|
+
files?: string[];
|
|
19
|
+
force?: boolean;
|
|
20
|
+
langs?: string[];
|
|
21
|
+
ignore?: string[];
|
|
22
|
+
/** Override the index directory (default: the global per-project dir). */
|
|
23
|
+
indexDir?: string;
|
|
24
|
+
}
|
|
25
|
+
/** Run a full or incremental index and return statistics. */
|
|
26
|
+
declare function runIndexer(_ctx: Context, opts: IndexerOptions): Promise<IndexResult>;
|
|
4
27
|
|
|
5
28
|
/**
|
|
6
29
|
* SQLite storage layer for the codebase index.
|
|
@@ -50,6 +73,14 @@ declare class IndexStore {
|
|
|
50
73
|
id: number;
|
|
51
74
|
text: string;
|
|
52
75
|
}>;
|
|
76
|
+
/**
|
|
77
|
+
* Largest symbol id currently in the table (0 when empty). New ids must be
|
|
78
|
+
* allocated from this, NOT from `COUNT(*)`: incremental reindexes delete a
|
|
79
|
+
* changed file's rows, so the row count drops below the max id and a
|
|
80
|
+
* count-based id would collide with a surviving row (UNIQUE constraint on
|
|
81
|
+
* `symbols.id`). Ids may have gaps — that is fine.
|
|
82
|
+
*/
|
|
83
|
+
getMaxSymbolId(): number;
|
|
53
84
|
getStats(): IndexStats;
|
|
54
85
|
setLastIndexed(ts: number): void;
|
|
55
86
|
clearAll(): void;
|
|
@@ -137,4 +168,4 @@ declare function lspKindToInternalKind(k: number): SymbolKind | null;
|
|
|
137
168
|
*/
|
|
138
169
|
declare function internalKindToLspKind(k: SymbolKind): number | null;
|
|
139
170
|
|
|
140
|
-
export { FileMeta, IndexStats, IndexStore, SearchResult, Symbol, SymbolKind, SymbolLang, buildBm25Index, buildIndexableText, codebaseIndexDirOverride, internalKindToLspKind, lspKindToInternalKind, resolveIndexDir, tokenise };
|
|
171
|
+
export { FileMeta, IndexResult, IndexStats, IndexStore, SearchResult, Symbol, SymbolKind, SymbolLang, buildBm25Index, buildIndexableText, codebaseIndexDirOverride, internalKindToLspKind, lspKindToInternalKind, resolveIndexDir, runIndexer, tokenise };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as
|
|
1
|
+
import * as fs3 from 'node:fs/promises';
|
|
2
2
|
import * as path4 from 'node:path';
|
|
3
3
|
import { resolveWstackPaths, compileGlob } from '@wrongstack/core';
|
|
4
4
|
import { createRequire } from 'node:module';
|
|
@@ -290,6 +290,17 @@ var IndexStore = class {
|
|
|
290
290
|
({ id, text }) => ({ id, text })
|
|
291
291
|
);
|
|
292
292
|
}
|
|
293
|
+
/**
|
|
294
|
+
* Largest symbol id currently in the table (0 when empty). New ids must be
|
|
295
|
+
* allocated from this, NOT from `COUNT(*)`: incremental reindexes delete a
|
|
296
|
+
* changed file's rows, so the row count drops below the max id and a
|
|
297
|
+
* count-based id would collide with a surviving row (UNIQUE constraint on
|
|
298
|
+
* `symbols.id`). Ids may have gaps — that is fine.
|
|
299
|
+
*/
|
|
300
|
+
getMaxSymbolId() {
|
|
301
|
+
const rows = this.db.prepare("SELECT MAX(id) AS m FROM symbols").all();
|
|
302
|
+
return rows[0]?.m ?? 0;
|
|
303
|
+
}
|
|
293
304
|
// ─── Stats ───────────────────────────────────────────────────────────────────
|
|
294
305
|
getStats() {
|
|
295
306
|
const sizeBytes = this.sizeBytes();
|
|
@@ -1608,6 +1619,56 @@ function makeSymbol2(opts) {
|
|
|
1608
1619
|
text: `${opts.name} ${opts.signature}`.trim()
|
|
1609
1620
|
};
|
|
1610
1621
|
}
|
|
1622
|
+
function globBody(glob) {
|
|
1623
|
+
return compileGlob(glob).source.replace(/^\^/, "").replace(/\$$/, "");
|
|
1624
|
+
}
|
|
1625
|
+
function compileGitignore(lines) {
|
|
1626
|
+
const rules = [];
|
|
1627
|
+
for (const raw of lines) {
|
|
1628
|
+
let line = raw.replace(/\r$/, "");
|
|
1629
|
+
if (!line.trim() || line.trimStart().startsWith("#")) continue;
|
|
1630
|
+
line = line.trim();
|
|
1631
|
+
let negated = false;
|
|
1632
|
+
if (line.startsWith("!")) {
|
|
1633
|
+
negated = true;
|
|
1634
|
+
line = line.slice(1);
|
|
1635
|
+
}
|
|
1636
|
+
let dirOnly = false;
|
|
1637
|
+
if (line.endsWith("/")) {
|
|
1638
|
+
dirOnly = true;
|
|
1639
|
+
line = line.slice(0, -1);
|
|
1640
|
+
}
|
|
1641
|
+
if (!line) continue;
|
|
1642
|
+
const anchored = line.startsWith("/") || line.includes("/");
|
|
1643
|
+
if (line.startsWith("/")) line = line.slice(1);
|
|
1644
|
+
const body = globBody(line);
|
|
1645
|
+
const prefix = anchored ? "^" : "(?:^|.*/)";
|
|
1646
|
+
rules.push({
|
|
1647
|
+
eqOrUnder: new RegExp(`${prefix}${body}(?:/.*)?$`),
|
|
1648
|
+
under: new RegExp(`${prefix}${body}/.*$`),
|
|
1649
|
+
negated,
|
|
1650
|
+
dirOnly
|
|
1651
|
+
});
|
|
1652
|
+
}
|
|
1653
|
+
return (relPath, isDir) => {
|
|
1654
|
+
const p = relPath.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
1655
|
+
let ignored = false;
|
|
1656
|
+
for (const r of rules) {
|
|
1657
|
+
const re = r.dirOnly && !isDir ? r.under : r.eqOrUnder;
|
|
1658
|
+
if (re.test(p)) ignored = !r.negated;
|
|
1659
|
+
}
|
|
1660
|
+
return ignored;
|
|
1661
|
+
};
|
|
1662
|
+
}
|
|
1663
|
+
async function loadGitignoreMatcher(projectRoot) {
|
|
1664
|
+
let lines = [];
|
|
1665
|
+
try {
|
|
1666
|
+
const raw = await fs3.readFile(path4.join(projectRoot, ".gitignore"), "utf8");
|
|
1667
|
+
lines = raw.split("\n");
|
|
1668
|
+
} catch {
|
|
1669
|
+
}
|
|
1670
|
+
return compileGitignore(lines);
|
|
1671
|
+
}
|
|
1611
1672
|
|
|
1612
1673
|
// src/codebase-index/indexer.ts
|
|
1613
1674
|
var DEFAULT_IGNORE = [
|
|
@@ -1621,7 +1682,7 @@ var DEFAULT_IGNORE = [
|
|
|
1621
1682
|
"__snapshots__",
|
|
1622
1683
|
".nyc_output"
|
|
1623
1684
|
];
|
|
1624
|
-
async function findSourceFiles(projectRoot, ignore) {
|
|
1685
|
+
async function findSourceFiles(projectRoot, ignore, isGitIgnored) {
|
|
1625
1686
|
const results = [];
|
|
1626
1687
|
const ignoreSet = /* @__PURE__ */ new Set([...DEFAULT_IGNORE, ...ignore]);
|
|
1627
1688
|
const globs = [
|
|
@@ -1639,17 +1700,19 @@ async function findSourceFiles(projectRoot, ignore) {
|
|
|
1639
1700
|
const walk = async (dir) => {
|
|
1640
1701
|
let entries;
|
|
1641
1702
|
try {
|
|
1642
|
-
entries = await
|
|
1703
|
+
entries = await fs3.readdir(dir, { withFileTypes: true });
|
|
1643
1704
|
} catch {
|
|
1644
1705
|
return;
|
|
1645
1706
|
}
|
|
1646
1707
|
for (const e of entries) {
|
|
1647
1708
|
if (ignoreSet.has(e.name)) continue;
|
|
1648
1709
|
const full = path4.join(dir, e.name);
|
|
1710
|
+
const rel = path4.relative(projectRoot, full).replace(/\\/g, "/");
|
|
1649
1711
|
if (e.isDirectory()) {
|
|
1712
|
+
if (isGitIgnored(rel, true)) continue;
|
|
1650
1713
|
await walk(full);
|
|
1651
1714
|
} else if (e.isFile()) {
|
|
1652
|
-
|
|
1715
|
+
if (isGitIgnored(rel, false)) continue;
|
|
1653
1716
|
const ext = path4.extname(e.name);
|
|
1654
1717
|
for (const { ext: extName, pat } of globs) {
|
|
1655
1718
|
if (ext === extName && (pat.test(rel) || pat.test(e.name))) {
|
|
@@ -1692,11 +1755,12 @@ async function runIndexer(_ctx, opts) {
|
|
|
1692
1755
|
const langStats = {};
|
|
1693
1756
|
let filesIndexed = 0;
|
|
1694
1757
|
let symbolsIndexed = 0;
|
|
1758
|
+
const isGitIgnored = await loadGitignoreMatcher(projectRoot);
|
|
1695
1759
|
let files;
|
|
1696
1760
|
if (opts.files && opts.files.length > 0) {
|
|
1697
|
-
files = opts.files.map((f) => path4.resolve(projectRoot, f));
|
|
1761
|
+
files = opts.files.map((f) => path4.resolve(projectRoot, f)).filter((f) => !isGitIgnored(path4.relative(projectRoot, f).replace(/\\/g, "/"), false));
|
|
1698
1762
|
} else {
|
|
1699
|
-
files = await findSourceFiles(projectRoot, ignore);
|
|
1763
|
+
files = await findSourceFiles(projectRoot, ignore, isGitIgnored);
|
|
1700
1764
|
}
|
|
1701
1765
|
if (langs && langs.length > 0) {
|
|
1702
1766
|
const langSet = new Set(langs);
|
|
@@ -1713,7 +1777,7 @@ async function runIndexer(_ctx, opts) {
|
|
|
1713
1777
|
for (const file of files) {
|
|
1714
1778
|
let stat2;
|
|
1715
1779
|
try {
|
|
1716
|
-
stat2 = await
|
|
1780
|
+
stat2 = await fs3.stat(file);
|
|
1717
1781
|
} catch {
|
|
1718
1782
|
store.deleteFile(file);
|
|
1719
1783
|
continue;
|
|
@@ -1728,11 +1792,11 @@ async function runIndexer(_ctx, opts) {
|
|
|
1728
1792
|
filesIndexed++;
|
|
1729
1793
|
continue;
|
|
1730
1794
|
}
|
|
1731
|
-
store.deleteSymbolsForFile(file);
|
|
1732
1795
|
store.deleteRefsForFile(file);
|
|
1796
|
+
store.deleteSymbolsForFile(file);
|
|
1733
1797
|
let content;
|
|
1734
1798
|
try {
|
|
1735
|
-
content = await
|
|
1799
|
+
content = await fs3.readFile(file, "utf8");
|
|
1736
1800
|
} catch (e) {
|
|
1737
1801
|
errors.push(`read error: ${file}: ${e instanceof Error ? e.message : String(e)}`);
|
|
1738
1802
|
continue;
|
|
@@ -1755,7 +1819,7 @@ async function runIndexer(_ctx, opts) {
|
|
|
1755
1819
|
filesIndexed++;
|
|
1756
1820
|
continue;
|
|
1757
1821
|
}
|
|
1758
|
-
const nextId = store.
|
|
1822
|
+
const nextId = store.getMaxSymbolId() + 1;
|
|
1759
1823
|
const symbolsWithIds = parsed.symbols.map((s, i) => ({ ...s, id: nextId + i }));
|
|
1760
1824
|
store.insertSymbols(symbolsWithIds, nextId);
|
|
1761
1825
|
const count = symbolsWithIds.length;
|
|
@@ -1782,7 +1846,7 @@ async function runIndexer(_ctx, opts) {
|
|
|
1782
1846
|
}
|
|
1783
1847
|
for (const [file_] of existingMeta) {
|
|
1784
1848
|
try {
|
|
1785
|
-
await
|
|
1849
|
+
await fs3.stat(file_);
|
|
1786
1850
|
} catch {
|
|
1787
1851
|
store.deleteFile(file_);
|
|
1788
1852
|
}
|
|
@@ -2041,6 +2105,70 @@ var codebaseStatsTool = {
|
|
|
2041
2105
|
}
|
|
2042
2106
|
};
|
|
2043
2107
|
|
|
2044
|
-
|
|
2108
|
+
// src/codebase-index/background-indexer.ts
|
|
2109
|
+
function stubCtx(projectRoot) {
|
|
2110
|
+
return {
|
|
2111
|
+
projectRoot,
|
|
2112
|
+
cwd: projectRoot,
|
|
2113
|
+
messages: [],
|
|
2114
|
+
todos: [],
|
|
2115
|
+
readFiles: /* @__PURE__ */ new Set(),
|
|
2116
|
+
fileMtimes: /* @__PURE__ */ new Map()
|
|
2117
|
+
};
|
|
2118
|
+
}
|
|
2119
|
+
var chain = Promise.resolve();
|
|
2120
|
+
function withMutex(job) {
|
|
2121
|
+
const run = chain.then(job, job);
|
|
2122
|
+
chain = run.then(
|
|
2123
|
+
() => void 0,
|
|
2124
|
+
() => void 0
|
|
2125
|
+
);
|
|
2126
|
+
return run;
|
|
2127
|
+
}
|
|
2128
|
+
var DEFAULT_DEBOUNCE_MS = 400;
|
|
2129
|
+
var debounceTimers = /* @__PURE__ */ new Map();
|
|
2130
|
+
function debounceKey(indexDir, file) {
|
|
2131
|
+
return `${indexDir ?? ""}|${file}`;
|
|
2132
|
+
}
|
|
2133
|
+
function isIndexableFile(filePath) {
|
|
2134
|
+
return detectLang(filePath) !== null;
|
|
2135
|
+
}
|
|
2136
|
+
function runStartupIndex(opts) {
|
|
2137
|
+
return withMutex(
|
|
2138
|
+
() => runIndexer(stubCtx(opts.projectRoot), {
|
|
2139
|
+
projectRoot: opts.projectRoot,
|
|
2140
|
+
indexDir: opts.indexDir,
|
|
2141
|
+
force: opts.force
|
|
2142
|
+
})
|
|
2143
|
+
);
|
|
2144
|
+
}
|
|
2145
|
+
function enqueueReindex(opts) {
|
|
2146
|
+
const files = opts.files.filter(isIndexableFile);
|
|
2147
|
+
if (files.length === 0) return;
|
|
2148
|
+
const ms = opts.debounceMs ?? DEFAULT_DEBOUNCE_MS;
|
|
2149
|
+
for (const file of files) {
|
|
2150
|
+
const key = debounceKey(opts.indexDir, file);
|
|
2151
|
+
const existing = debounceTimers.get(key);
|
|
2152
|
+
if (existing) clearTimeout(existing);
|
|
2153
|
+
const timer = setTimeout(() => {
|
|
2154
|
+
debounceTimers.delete(key);
|
|
2155
|
+
void withMutex(
|
|
2156
|
+
() => runIndexer(stubCtx(opts.projectRoot), {
|
|
2157
|
+
projectRoot: opts.projectRoot,
|
|
2158
|
+
files: [file],
|
|
2159
|
+
indexDir: opts.indexDir
|
|
2160
|
+
})
|
|
2161
|
+
).catch((err) => opts.onError?.(err));
|
|
2162
|
+
}, ms);
|
|
2163
|
+
timer.unref?.();
|
|
2164
|
+
debounceTimers.set(key, timer);
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2167
|
+
function cancelPendingReindexes() {
|
|
2168
|
+
for (const t of debounceTimers.values()) clearTimeout(t);
|
|
2169
|
+
debounceTimers.clear();
|
|
2170
|
+
}
|
|
2171
|
+
|
|
2172
|
+
export { IndexStore, SCHEMA_VERSION, buildBm25Index, buildIndexableText, cancelPendingReindexes, codebaseIndexDirOverride, codebaseIndexTool, codebaseSearchTool, codebaseStatsTool, enqueueReindex, internalKindToLspKind, isIndexableFile, lspKindToInternalKind, resolveIndexDir, runIndexer, runStartupIndex, tokenise };
|
|
2045
2173
|
//# sourceMappingURL=index.js.map
|
|
2046
2174
|
//# sourceMappingURL=index.js.map
|