pindex 1.2.3 → 1.4.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 (71) hide show
  1. package/README.md +18 -5
  2. package/dist/db/migrations.d.ts +4 -1
  3. package/dist/db/migrations.d.ts.map +1 -1
  4. package/dist/db/migrations.js +9 -3
  5. package/dist/db/migrations.js.map +1 -1
  6. package/dist/db/queries.d.ts.map +1 -1
  7. package/dist/db/queries.js +21 -17
  8. package/dist/db/queries.js.map +1 -1
  9. package/dist/gui/server.d.ts.map +1 -1
  10. package/dist/gui/server.js +14 -5
  11. package/dist/gui/server.js.map +1 -1
  12. package/dist/index.js +14 -0
  13. package/dist/index.js.map +1 -1
  14. package/dist/indexer/index.d.ts +23 -3
  15. package/dist/indexer/index.d.ts.map +1 -1
  16. package/dist/indexer/index.js +146 -62
  17. package/dist/indexer/index.js.map +1 -1
  18. package/dist/indexer/lsp-mapper.d.ts +6 -0
  19. package/dist/indexer/lsp-mapper.d.ts.map +1 -0
  20. package/dist/indexer/lsp-mapper.js +40 -0
  21. package/dist/indexer/lsp-mapper.js.map +1 -0
  22. package/dist/indexer/lsp-python.d.ts +43 -0
  23. package/dist/indexer/lsp-python.d.ts.map +1 -0
  24. package/dist/indexer/lsp-python.js +234 -0
  25. package/dist/indexer/lsp-python.js.map +1 -0
  26. package/dist/indexer/parse-pool.d.ts +26 -0
  27. package/dist/indexer/parse-pool.d.ts.map +1 -0
  28. package/dist/indexer/parse-pool.js +201 -0
  29. package/dist/indexer/parse-pool.js.map +1 -0
  30. package/dist/indexer/parse-types.d.ts +33 -0
  31. package/dist/indexer/parse-types.d.ts.map +1 -0
  32. package/dist/indexer/parse-types.js +2 -0
  33. package/dist/indexer/parse-types.js.map +1 -0
  34. package/dist/indexer/parse-worker.d.ts +2 -0
  35. package/dist/indexer/parse-worker.d.ts.map +1 -0
  36. package/dist/indexer/parse-worker.js +39 -0
  37. package/dist/indexer/parse-worker.js.map +1 -0
  38. package/dist/indexer/parser.d.ts.map +1 -1
  39. package/dist/indexer/parser.js +10 -4
  40. package/dist/indexer/parser.js.map +1 -1
  41. package/dist/indexer/summarizer.d.ts +18 -7
  42. package/dist/indexer/summarizer.d.ts.map +1 -1
  43. package/dist/indexer/summarizer.js +110 -13
  44. package/dist/indexer/summarizer.js.map +1 -1
  45. package/dist/indexer/watcher.d.ts.map +1 -1
  46. package/dist/indexer/watcher.js +2 -0
  47. package/dist/indexer/watcher.js.map +1 -1
  48. package/dist/memory/observer.d.ts.map +1 -1
  49. package/dist/memory/observer.js +20 -4
  50. package/dist/memory/observer.js.map +1 -1
  51. package/dist/monitoring/server.d.ts.map +1 -1
  52. package/dist/monitoring/server.js +5 -1
  53. package/dist/monitoring/server.js.map +1 -1
  54. package/dist/server.d.ts.map +1 -1
  55. package/dist/server.js +32 -16
  56. package/dist/server.js.map +1 -1
  57. package/dist/tools/get_context.d.ts.map +1 -1
  58. package/dist/tools/get_context.js +4 -2
  59. package/dist/tools/get_context.js.map +1 -1
  60. package/dist/tools/schemas.d.ts +75 -0
  61. package/dist/tools/schemas.d.ts.map +1 -0
  62. package/dist/tools/schemas.js +79 -0
  63. package/dist/tools/schemas.js.map +1 -0
  64. package/dist/tools/search_symbols.d.ts.map +1 -1
  65. package/dist/tools/search_symbols.js +8 -6
  66. package/dist/tools/search_symbols.js.map +1 -1
  67. package/dist/util/paths.d.ts +9 -0
  68. package/dist/util/paths.d.ts.map +1 -0
  69. package/dist/util/paths.js +18 -0
  70. package/dist/util/paths.js.map +1 -0
  71. package/package.json +15 -7
@@ -0,0 +1,234 @@
1
+ import { spawn as nodeSpawn } from 'node:child_process';
2
+ import { existsSync } from 'node:fs';
3
+ import { resolve, join } from 'node:path';
4
+ import { pathToFileURL } from 'node:url';
5
+ import { StreamMessageReader, StreamMessageWriter, createMessageConnection, } from 'vscode-jsonrpc/node.js';
6
+ import { mapDocumentSymbols } from './lsp-mapper.js';
7
+ import { parseFile } from './parser.js';
8
+ export class LspPythonClient {
9
+ _state = 'idle';
10
+ enabled;
11
+ projectRoot;
12
+ timeoutMs;
13
+ spawnImpl;
14
+ resolveImpl;
15
+ proc = null;
16
+ connection = null;
17
+ static warnedMissing = false;
18
+ pendingQueue = Promise.resolve();
19
+ restartAttempted = false;
20
+ consecutiveTimeouts = 0;
21
+ constructor(options) {
22
+ this.enabled = options.enabled;
23
+ this.projectRoot = options.projectRoot;
24
+ this.timeoutMs = options.timeoutMs ?? 5000;
25
+ this.spawnImpl =
26
+ options._spawn ??
27
+ ((path) => nodeSpawn(path, ['--stdio'], { stdio: ['pipe', 'pipe', 'pipe'] }));
28
+ this.resolveImpl = options._resolveServerPath ?? (() => resolvePyrightLangserver());
29
+ }
30
+ /** Test-only: resets module-level warning guard so tests can observe
31
+ * stderr output repeatedly. NOT for production use. */
32
+ static _resetWarnedMissingForTest() {
33
+ LspPythonClient.warnedMissing = false;
34
+ }
35
+ get state() {
36
+ return this._state;
37
+ }
38
+ get ready() {
39
+ return this._state === 'ready';
40
+ }
41
+ async start() {
42
+ if (this._state !== 'idle')
43
+ return;
44
+ if (!this.enabled) {
45
+ this._state = 'failed';
46
+ return;
47
+ }
48
+ const path = this.resolveImpl();
49
+ if (!path) {
50
+ if (!LspPythonClient.warnedMissing) {
51
+ process.stderr.write(`[pindex] LSP: pyright-langserver not found on PATH or in node_modules; ` +
52
+ `falling back to regex parsing. Install with "npm install pyright" or ` +
53
+ `set PINDEX_LSP=false to silence this.\n`);
54
+ LspPythonClient.warnedMissing = true;
55
+ }
56
+ this._state = 'failed';
57
+ return;
58
+ }
59
+ this._state = 'starting';
60
+ try {
61
+ this.proc = this.spawnImpl(path);
62
+ }
63
+ catch (err) {
64
+ process.stderr.write(`[pindex] LSP: spawn failed: ${String(err)}\n`);
65
+ this._state = 'failed';
66
+ return;
67
+ }
68
+ // exitReject lets us abort the initialize handshake when the process exits early.
69
+ let exitReject;
70
+ const exitPromise = new Promise((_, reject) => { exitReject = reject; });
71
+ this.proc.on('exit', (code) => {
72
+ if (this._state === 'closed')
73
+ return;
74
+ process.stderr.write(`[pindex] LSP: pyright-langserver exited (code ${code})\n`);
75
+ if (!this.restartAttempted && this._state === 'ready') {
76
+ this.restartAttempted = true;
77
+ process.stderr.write(`[pindex] LSP: attempting one restart\n`);
78
+ this._state = 'idle';
79
+ this.proc = null;
80
+ this.connection?.dispose();
81
+ this.connection = null;
82
+ // Fire and forget — the restart is opportunistic.
83
+ this.start().catch(() => {
84
+ /* swallow — state already reflects any failure */
85
+ });
86
+ return;
87
+ }
88
+ this._state = 'failed';
89
+ exitReject(new Error(`pyright-langserver exited with code ${String(code)}`));
90
+ });
91
+ this.connection = createMessageConnection(new StreamMessageReader(this.proc.stdout), new StreamMessageWriter(this.proc.stdin));
92
+ this.connection.listen();
93
+ try {
94
+ await Promise.race([
95
+ this.connection.sendRequest('initialize', {
96
+ processId: process.pid,
97
+ rootUri: pathToFileURL(resolve(this.projectRoot)).href,
98
+ capabilities: { textDocument: { documentSymbol: { hierarchicalDocumentSymbolSupport: true } } },
99
+ }),
100
+ exitPromise,
101
+ new Promise((_, reject) => setTimeout(() => reject(new Error('initialize timeout')), this.timeoutMs)),
102
+ ]);
103
+ this.connection.sendNotification('initialized', {});
104
+ this._state = 'ready';
105
+ }
106
+ catch (err) {
107
+ // The exit handler may have already set _state to 'failed' via the event loop.
108
+ const currentState = this._state;
109
+ if (currentState !== 'failed' && currentState !== 'closed') {
110
+ process.stderr.write(`[pindex] LSP: initialize failed: ${String(err)}\n`);
111
+ this._state = 'failed';
112
+ }
113
+ }
114
+ }
115
+ async getDocumentSymbols(relPath, content) {
116
+ if (this._state !== 'ready' || !this.connection)
117
+ return null;
118
+ // Serialise concurrent callers so the LSP sees one request at a time.
119
+ const task = this.pendingQueue.then(() => this.runRequest(relPath, content));
120
+ this.pendingQueue = task.catch(() => undefined);
121
+ return task;
122
+ }
123
+ async runRequest(relPath, content) {
124
+ if (!this.connection || this._state !== 'ready')
125
+ return null;
126
+ const uri = pathToFileURL(resolve(this.projectRoot, relPath)).href;
127
+ try {
128
+ this.connection.sendNotification('textDocument/didOpen', {
129
+ textDocument: { uri, languageId: 'python', version: 1, text: content },
130
+ });
131
+ let timedOut = false;
132
+ let response;
133
+ try {
134
+ response = (await Promise.race([
135
+ this.connection.sendRequest('textDocument/documentSymbol', {
136
+ textDocument: { uri },
137
+ }),
138
+ new Promise((_, reject) => setTimeout(() => {
139
+ timedOut = true;
140
+ reject(new Error('request timeout'));
141
+ }, this.timeoutMs)),
142
+ ]));
143
+ }
144
+ catch (err) {
145
+ if (timedOut) {
146
+ this.consecutiveTimeouts++;
147
+ if (this.consecutiveTimeouts >= 3) {
148
+ process.stderr.write(`[pindex] LSP: 3 consecutive timeouts, treating as crash\n`);
149
+ this.simulateCrash();
150
+ }
151
+ return null;
152
+ }
153
+ throw err;
154
+ }
155
+ this.connection.sendNotification('textDocument/didClose', {
156
+ textDocument: { uri },
157
+ });
158
+ if (!response || !Array.isArray(response)) {
159
+ return { symbols: [], imports: extractImportsFromContent(content) };
160
+ }
161
+ this.consecutiveTimeouts = 0;
162
+ return {
163
+ symbols: mapDocumentSymbols(response),
164
+ imports: extractImportsFromContent(content),
165
+ };
166
+ }
167
+ catch (err) {
168
+ process.stderr.write(`[pindex] LSP: documentSymbol failed for ${relPath}: ${String(err)}\n`);
169
+ return null;
170
+ }
171
+ }
172
+ /** Manually triggers the same restart-or-fail path the exit handler uses.
173
+ * Used when the subprocess is unresponsive (repeated request timeouts). */
174
+ simulateCrash() {
175
+ if (this._state !== 'ready')
176
+ return;
177
+ this._state = 'idle';
178
+ try {
179
+ this.proc?.kill('SIGKILL');
180
+ }
181
+ catch { /* process already dead */ }
182
+ this.proc = null;
183
+ this.connection?.dispose();
184
+ this.connection = null;
185
+ if (!this.restartAttempted) {
186
+ this.restartAttempted = true;
187
+ this.start().catch(() => { });
188
+ }
189
+ else {
190
+ this._state = 'failed';
191
+ }
192
+ }
193
+ async close() {
194
+ this._state = 'closed';
195
+ if (this.connection) {
196
+ const conn = this.connection;
197
+ this.connection = null;
198
+ try {
199
+ await Promise.race([
200
+ conn.sendRequest('shutdown', null),
201
+ new Promise((_, reject) => setTimeout(() => reject(new Error('shutdown timeout')), 1000)),
202
+ ]);
203
+ conn.sendNotification('exit', null);
204
+ }
205
+ catch { /* subprocess may already be dead */ }
206
+ conn.dispose();
207
+ }
208
+ if (this.proc) {
209
+ try {
210
+ this.proc.kill('SIGTERM');
211
+ }
212
+ catch { /* ignore */ }
213
+ this.proc = null;
214
+ }
215
+ }
216
+ }
217
+ /** Resolves the pyright-langserver binary path by checking node_modules/.bin.
218
+ * Returns null if not found; PATH-based discovery is intentionally not
219
+ * implemented (would require spawn + stderr capture to detect failure, which
220
+ * is a larger change and not worth the complexity for the optionalDependency
221
+ * install path where node_modules is the canonical location). */
222
+ function resolvePyrightLangserver() {
223
+ const local = join(process.cwd(), 'node_modules', '.bin', 'pyright-langserver');
224
+ if (existsSync(local))
225
+ return local;
226
+ return null;
227
+ }
228
+ /** Reuses the existing Python regex import extractor from parseFile(). Pyright's
229
+ * documentSymbol does not expose import statements; the regex is reliable
230
+ * enough for `import X` / `from Y import Z`. */
231
+ function extractImportsFromContent(content) {
232
+ return parseFile('pseudo.py', content).imports;
233
+ }
234
+ //# sourceMappingURL=lsp-python.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lsp-python.js","sourceRoot":"","sources":["../../src/indexer/lsp-python.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,26 @@
1
+ import type { ParseJobInput, ParseJobResult, ParsePoolOptions } from './parse-types.js';
2
+ export declare class ParsePool {
3
+ private readonly maxWorkers;
4
+ private readonly maxFileSize;
5
+ private workers;
6
+ private idleWorkers;
7
+ private workerJobs;
8
+ private pending;
9
+ private closed;
10
+ constructor(options: ParsePoolOptions);
11
+ parseMany(jobs: ParseJobInput[]): AsyncGenerator<ParseJobResult>;
12
+ close(): Promise<void>;
13
+ private spawnWorker;
14
+ private enqueue;
15
+ private drainQueue;
16
+ /** Picks a default worker count based on the host CPU count and an env
17
+ * override. Capped to leave at least one CPU for the main thread. Forces
18
+ * 0 (sync fallback) when VITEST is running, so the global tree-sitter
19
+ * mock in tests/setup.ts continues to apply. */
20
+ static pickDefaultWorkerCount(explicit: number | undefined): number;
21
+ /** Picks the effective worker count for a given job batch. Small batches
22
+ * downshift to avoid the worker startup overhead exceeding the gain. */
23
+ static pickEffectiveWorkerCount(jobCount: number, configured: number): number;
24
+ private runJobSync;
25
+ }
26
+ //# sourceMappingURL=parse-pool.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-pool.d.ts","sourceRoot":"","sources":["../../src/indexer/parse-pool.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EACV,aAAa,EACb,cAAc,EACd,gBAAgB,EACjB,MAAM,kBAAkB,CAAC;AAgB1B,qBAAa,SAAS;IACpB,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,OAAO,CAAgB;IAC/B,OAAO,CAAC,WAAW,CAAgB;IACnC,OAAO,CAAC,UAAU,CAAiC;IACnD,OAAO,CAAC,OAAO,CAAoB;IACnC,OAAO,CAAC,MAAM,CAAS;gBAEX,OAAO,EAAE,gBAAgB;IAQ9B,SAAS,CAAC,IAAI,EAAE,aAAa,EAAE,GAAG,cAAc,CAAC,cAAc,CAAC;IAyCjE,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA0B5B,OAAO,CAAC,WAAW;IAsCnB,OAAO,CAAC,OAAO;IAKf,OAAO,CAAC,UAAU;IAYlB;;;qDAGiD;IACjD,MAAM,CAAC,sBAAsB,CAAC,QAAQ,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM;IAYnE;6EACyE;IACzE,MAAM,CAAC,wBAAwB,CAAC,QAAQ,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,MAAM;YAO/D,UAAU;CAwBzB"}
@@ -0,0 +1,201 @@
1
+ import { readFile, stat } from 'node:fs/promises';
2
+ import { cpus } from 'node:os';
3
+ import { Worker } from 'node:worker_threads';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { dirname, resolve } from 'node:path';
6
+ import { parseFile, hashContent } from './parser.js';
7
+ const DEFAULT_MAX_FILE_SIZE = 1024 * 1024;
8
+ const MAX_RETRIES = 1;
9
+ // Resolve the compiled worker script relative to this module.
10
+ // After `npm run build`, both files live in dist/indexer/.
11
+ const __dirname = dirname(fileURLToPath(import.meta.url));
12
+ const WORKER_URL = resolve(__dirname, 'parse-worker.js');
13
+ export class ParsePool {
14
+ maxWorkers;
15
+ maxFileSize;
16
+ workers = [];
17
+ idleWorkers = [];
18
+ workerJobs = new Map();
19
+ pending = [];
20
+ closed = false;
21
+ constructor(options) {
22
+ this.maxWorkers = Math.max(0, options.maxWorkers);
23
+ this.maxFileSize = options.maxFileSize ?? DEFAULT_MAX_FILE_SIZE;
24
+ for (let i = 0; i < this.maxWorkers; i++) {
25
+ this.spawnWorker();
26
+ }
27
+ }
28
+ async *parseMany(jobs) {
29
+ if (this.closed)
30
+ throw new Error('ParsePool is closed');
31
+ if (this.maxWorkers === 0) {
32
+ for (const job of jobs) {
33
+ yield await this.runJobSync(job);
34
+ }
35
+ return;
36
+ }
37
+ // Submit all jobs; drain results in completion order via a shared buffer.
38
+ const buffer = [];
39
+ let notify = null;
40
+ let remaining = jobs.length;
41
+ for (const job of jobs) {
42
+ this.enqueue({
43
+ job,
44
+ attempts: 0,
45
+ resolve: (result) => {
46
+ buffer.push(result);
47
+ remaining--;
48
+ notify?.();
49
+ },
50
+ });
51
+ }
52
+ while (remaining > 0 || buffer.length > 0) {
53
+ if (buffer.length === 0) {
54
+ await new Promise((r) => {
55
+ notify = () => {
56
+ notify = null;
57
+ r();
58
+ };
59
+ });
60
+ continue;
61
+ }
62
+ yield buffer.shift();
63
+ }
64
+ }
65
+ async close() {
66
+ this.closed = true;
67
+ // Drain the pending queue: every waiting job gets a terminal result so the
68
+ // AsyncGenerator in parseMany can exit instead of hanging.
69
+ const cancelResult = (job) => ({
70
+ status: 'error',
71
+ relativePath: job.relativePath,
72
+ error: 'ParsePool closed',
73
+ });
74
+ for (const p of this.pending) {
75
+ p.resolve(cancelResult(p.job));
76
+ }
77
+ this.pending = [];
78
+ // Resolve any in-flight jobs BEFORE terminate, so the generator notifies
79
+ // and wakes before we tear down the workers.
80
+ for (const [, p] of this.workerJobs) {
81
+ p.resolve(cancelResult(p.job));
82
+ }
83
+ this.workerJobs.clear();
84
+ await Promise.all(this.workers.map((w) => w.terminate().then(() => undefined)));
85
+ this.workers = [];
86
+ this.idleWorkers = [];
87
+ }
88
+ // ─── Internal ────────────────────────────────────────────────────────────────
89
+ spawnWorker() {
90
+ const worker = new Worker(WORKER_URL);
91
+ worker.on('message', (msg) => {
92
+ const pending = this.workerJobs.get(worker);
93
+ this.workerJobs.delete(worker);
94
+ this.idleWorkers.push(worker);
95
+ pending?.resolve(msg.result);
96
+ this.drainQueue();
97
+ });
98
+ worker.on('error', (err) => {
99
+ process.stderr.write(`[pindex] ParsePool worker error: ${String(err)}\n`);
100
+ });
101
+ worker.on('exit', (code) => {
102
+ if (code === 0 || this.closed)
103
+ return;
104
+ // Unexpected crash. If a job was in flight, requeue once; then replace.
105
+ const pending = this.workerJobs.get(worker);
106
+ this.workerJobs.delete(worker);
107
+ this.workers = this.workers.filter((w) => w !== worker);
108
+ this.idleWorkers = this.idleWorkers.filter((w) => w !== worker);
109
+ if (pending) {
110
+ if (pending.attempts < MAX_RETRIES) {
111
+ pending.attempts++;
112
+ this.pending.unshift(pending);
113
+ }
114
+ else {
115
+ pending.resolve({
116
+ status: 'error',
117
+ relativePath: pending.job.relativePath,
118
+ error: `Worker exited (code ${code}) after ${pending.attempts + 1} attempts`,
119
+ });
120
+ }
121
+ }
122
+ if (!this.closed)
123
+ this.spawnWorker();
124
+ this.drainQueue();
125
+ });
126
+ this.workers.push(worker);
127
+ this.idleWorkers.push(worker);
128
+ }
129
+ enqueue(pending) {
130
+ this.pending.push(pending);
131
+ this.drainQueue();
132
+ }
133
+ drainQueue() {
134
+ while (this.pending.length > 0 && this.idleWorkers.length > 0) {
135
+ const pending = this.pending.shift();
136
+ const worker = this.idleWorkers.shift();
137
+ this.workerJobs.set(worker, pending);
138
+ worker.postMessage({
139
+ job: pending.job,
140
+ maxFileSize: this.maxFileSize,
141
+ });
142
+ }
143
+ }
144
+ /** Picks a default worker count based on the host CPU count and an env
145
+ * override. Capped to leave at least one CPU for the main thread. Forces
146
+ * 0 (sync fallback) when VITEST is running, so the global tree-sitter
147
+ * mock in tests/setup.ts continues to apply. */
148
+ static pickDefaultWorkerCount(explicit) {
149
+ if (explicit !== undefined)
150
+ return Math.max(0, explicit);
151
+ const fromEnv = process.env.PINDEX_PARSE_WORKERS;
152
+ if (fromEnv !== undefined && fromEnv !== '') {
153
+ const n = parseInt(fromEnv, 10);
154
+ if (!Number.isNaN(n) && n >= 0)
155
+ return n;
156
+ }
157
+ if (process.env.VITEST === 'true')
158
+ return 0;
159
+ const cpuCount = cpus().length;
160
+ return Math.max(1, cpuCount - 1);
161
+ }
162
+ /** Picks the effective worker count for a given job batch. Small batches
163
+ * downshift to avoid the worker startup overhead exceeding the gain. */
164
+ static pickEffectiveWorkerCount(jobCount, configured) {
165
+ if (configured === 0)
166
+ return 0;
167
+ if (jobCount < 10)
168
+ return 0;
169
+ if (jobCount < configured * 2)
170
+ return 1;
171
+ return configured;
172
+ }
173
+ async runJobSync(job) {
174
+ try {
175
+ const st = await stat(job.absolutePath);
176
+ if (st.size > this.maxFileSize) {
177
+ return { status: 'skipped', relativePath: job.relativePath, reason: 'too_large' };
178
+ }
179
+ }
180
+ catch (err) {
181
+ process.stderr.write(`[pindex] ParsePool: stat failed for ${job.relativePath}: ${String(err)}\n`);
182
+ return { status: 'skipped', relativePath: job.relativePath, reason: 'not_found' };
183
+ }
184
+ let content;
185
+ try {
186
+ content = await readFile(job.absolutePath, 'utf-8');
187
+ }
188
+ catch (err) {
189
+ return { status: 'error', relativePath: job.relativePath, error: String(err) };
190
+ }
191
+ try {
192
+ const parsed = parseFile(job.absolutePath, content);
193
+ const hash = hashContent(content);
194
+ return { status: 'ok', relativePath: job.relativePath, parsed, hash, content };
195
+ }
196
+ catch (err) {
197
+ return { status: 'error', relativePath: job.relativePath, error: String(err) };
198
+ }
199
+ }
200
+ }
201
+ //# sourceMappingURL=parse-pool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-pool.js","sourceRoot":"","sources":["../../src/indexer/parse-pool.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ import type { ParsedFile, ParsedImport } from '../types.js';
2
+ export type { ParsedFile, ParsedImport };
3
+ /** Job submitted to the pool. Paths are absolute for the worker's readFile,
4
+ * and project-relative for the main thread's DB lookups. */
5
+ export interface ParseJobInput {
6
+ absolutePath: string;
7
+ relativePath: string;
8
+ }
9
+ export type ParseJobResult = {
10
+ status: 'ok';
11
+ relativePath: string;
12
+ parsed: ParsedFile;
13
+ hash: string;
14
+ /** File content, sent back so the main thread can slice snippets /
15
+ * summarise without a second disk read. */
16
+ content: string;
17
+ } | {
18
+ status: 'skipped';
19
+ relativePath: string;
20
+ reason: 'too_large' | 'not_found';
21
+ } | {
22
+ status: 'error';
23
+ relativePath: string;
24
+ error: string;
25
+ };
26
+ export interface ParsePoolOptions {
27
+ /** 0 = run synchronously in the calling thread (test mode).
28
+ * N > 0 = spawn N worker_threads. */
29
+ maxWorkers: number;
30
+ /** Max file size in bytes; larger files are returned as 'skipped'. Defaults to 1 MB. */
31
+ maxFileSize?: number;
32
+ }
33
+ //# sourceMappingURL=parse-types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-types.d.ts","sourceRoot":"","sources":["../../src/indexer/parse-types.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE5D,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,CAAC;AAEzC;6DAC6D;AAC7D,MAAM,WAAW,aAAa;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,MAAM,cAAc,GACtB;IACE,MAAM,EAAE,IAAI,CAAC;IACb,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,UAAU,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb;gDAC4C;IAC5C,OAAO,EAAE,MAAM,CAAC;CACjB,GACD;IACE,MAAM,EAAE,SAAS,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,WAAW,GAAG,WAAW,CAAC;CACnC,GACD;IACE,MAAM,EAAE,OAAO,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEN,MAAM,WAAW,gBAAgB;IAC/B;0CACsC;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,wFAAwF;IACxF,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=parse-types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-types.js","sourceRoot":"","sources":["../../src/indexer/parse-types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=parse-worker.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-worker.d.ts","sourceRoot":"","sources":["../../src/indexer/parse-worker.ts"],"names":[],"mappings":""}
@@ -0,0 +1,39 @@
1
+ // src/indexer/parse-worker.ts
2
+ import { readFileSync, statSync } from 'node:fs';
3
+ import { parentPort } from 'node:worker_threads';
4
+ import { parseFile, hashContent } from './parser.js';
5
+ if (!parentPort) {
6
+ throw new Error('parse-worker.ts must be loaded as a worker_threads script');
7
+ }
8
+ parentPort.on('message', (msg) => {
9
+ const result = runJob(msg.job, msg.maxFileSize);
10
+ parentPort.postMessage({ result });
11
+ });
12
+ function runJob(job, maxFileSize) {
13
+ try {
14
+ const st = statSync(job.absolutePath);
15
+ if (st.size > maxFileSize) {
16
+ return { status: 'skipped', relativePath: job.relativePath, reason: 'too_large' };
17
+ }
18
+ }
19
+ catch (err) {
20
+ process.stderr.write(`[pindex] parse-worker: stat failed for ${job.relativePath}: ${String(err)}\n`);
21
+ return { status: 'skipped', relativePath: job.relativePath, reason: 'not_found' };
22
+ }
23
+ let content;
24
+ try {
25
+ content = readFileSync(job.absolutePath, 'utf-8');
26
+ }
27
+ catch (err) {
28
+ return { status: 'error', relativePath: job.relativePath, error: String(err) };
29
+ }
30
+ try {
31
+ const parsed = parseFile(job.absolutePath, content);
32
+ const hash = hashContent(content);
33
+ return { status: 'ok', relativePath: job.relativePath, parsed, hash, content };
34
+ }
35
+ catch (err) {
36
+ return { status: 'error', relativePath: job.relativePath, error: String(err) };
37
+ }
38
+ }
39
+ //# sourceMappingURL=parse-worker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parse-worker.js","sourceRoot":"","sources":["../../src/indexer/parse-worker.ts"],"names":[],"mappings":"AAAA,8BAA8B;AAC9B,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACjD,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAGrD,IAAI,CAAC,UAAU,EAAE,CAAC;IAChB,MAAM,IAAI,KAAK,CAAC,2DAA2D,CAAC,CAAC;AAC/E,CAAC;AAOD,UAAU,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,GAAkB,EAAE,EAAE;IAC9C,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,GAAG,CAAC,WAAW,CAAC,CAAC;IAChD,UAAW,CAAC,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC,CAAC;AACtC,CAAC,CAAC,CAAC;AAEH,SAAS,MAAM,CAAC,GAAkB,EAAE,WAAmB;IACrD,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,QAAQ,CAAC,GAAG,CAAC,YAAY,CAAC,CAAC;QACtC,IAAI,EAAE,CAAC,IAAI,GAAG,WAAW,EAAE,CAAC;YAC1B,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,YAAY,EAAE,GAAG,CAAC,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;QACpF,CAAC;IACH,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,0CAA0C,GAAG,CAAC,YAAY,KAAK,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;QACrG,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,YAAY,EAAE,GAAG,CAAC,YAAY,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC;IACpF,CAAC;IAED,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,GAAG,YAAY,CAAC,GAAG,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;IACpD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,CAAC,YAAY,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;IACjF,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;QACpD,MAAM,IAAI,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;QAClC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY,EAAE,GAAG,CAAC,YAAY,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IACjF,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,YAAY,EAAE,GAAG,CAAC,YAAY,EAAE,KAAK,EAAE,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;IACjF,CAAC;AACH,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/indexer/parser.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,YAAY,EAAc,cAAc,EAAiB,MAAM,aAAa,CAAC;AAkC9H,uFAAuF;AACvF,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAE5D;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGvD;AAID,gEAAgE;AAChE,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAGtD;AAID,kEAAkE;AAClE,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAiClE;AAuJD,wDAAwD;AACxD,wBAAgB,cAAc,CAAC,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,YAAY,EAAE,CAmClF;AAID,kEAAkE;AAClE,wBAAgB,cAAc,CAAC,QAAQ,EAAE,OAAO,GAAG,YAAY,EAAE,CAsBhE;AA8KD;kDACkD;AAClD,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,CA0EvE;AAyDD,4DAA4D;AAC5D,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,cAAc,CAkB/E;AAED,qEAAqE;AACrE,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEnD"}
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/indexer/parser.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,YAAY,EAAc,cAAc,EAAiB,MAAM,aAAa,CAAC;AAkC9H,uFAAuF;AACvF,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAE5D;AAED,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAGvD;AAID,gEAAgE;AAChE,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAGtD;AAID,kEAAkE;AAClE,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAiClE;AAuJD,wDAAwD;AACxD,wBAAgB,cAAc,CAAC,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,GAAG,YAAY,EAAE,CAmClF;AAID,kEAAkE;AAClE,wBAAgB,cAAc,CAAC,QAAQ,EAAE,OAAO,GAAG,YAAY,EAAE,CAsBhE;AA8KD;kDACkD;AAClD,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,CAkFvE;AAyDD,4DAA4D;AAC5D,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,cAAc,CAkB/E;AAED,qEAAqE;AACrE,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAEnD"}
@@ -482,9 +482,15 @@ export function parseFile(filePath, content) {
482
482
  return { language, symbols, imports, rawTokenEstimate };
483
483
  }
484
484
  let lang;
485
- if (language === 'typescript' || language === 'tsx') {
485
+ if (language === 'typescript' ||
486
+ language === 'tsx' ||
487
+ language === 'javascript' ||
488
+ language === 'jsx') {
489
+ // The tree-sitter-typescript grammar is a TS-superset parser that also
490
+ // handles plain JavaScript and JSX correctly. Using it for JS files
491
+ // gives real AST-based symbol extraction instead of a silent empty result.
486
492
  const tsLangs = _require('tree-sitter-typescript');
487
- lang = language === 'tsx' ? tsLangs.tsx : tsLangs.typescript;
493
+ lang = language === 'tsx' || language === 'jsx' ? tsLangs.tsx : tsLangs.typescript;
488
494
  }
489
495
  else {
490
496
  return { language, symbols: [], imports: [], rawTokenEstimate };
@@ -495,8 +501,8 @@ export function parseFile(filePath, content) {
495
501
  const imports = extractImports(tree.rootNode);
496
502
  return { language, symbols, imports, rawTokenEstimate };
497
503
  }
498
- catch {
499
- // tree-sitter not available or failed – return metadata only
504
+ catch (err) {
505
+ process.stderr.write(`[pindex] Parse error for ${filePath}: ${err}\n`);
500
506
  return { language, symbols: [], imports: [], rawTokenEstimate };
501
507
  }
502
508
  }