@getfoyer/review-core 0.1.0 → 0.2.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 (49) hide show
  1. package/dist/fileReader.d.ts +17 -0
  2. package/dist/fileReader.d.ts.map +1 -0
  3. package/dist/fileReader.js +2 -0
  4. package/dist/fileReader.js.map +1 -0
  5. package/dist/index.d.ts +3 -0
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +1 -0
  8. package/dist/index.js.map +1 -1
  9. package/dist/plan-context.d.ts +14 -0
  10. package/dist/plan-context.d.ts.map +1 -0
  11. package/dist/plan-context.js +133 -0
  12. package/dist/plan-context.js.map +1 -0
  13. package/dist/semantic/cache.d.ts +31 -0
  14. package/dist/semantic/cache.d.ts.map +1 -0
  15. package/dist/semantic/cache.js +50 -0
  16. package/dist/semantic/cache.js.map +1 -0
  17. package/dist/semantic/contextBuilder.d.ts +17 -0
  18. package/dist/semantic/contextBuilder.d.ts.map +1 -0
  19. package/dist/semantic/contextBuilder.js +92 -0
  20. package/dist/semantic/contextBuilder.js.map +1 -0
  21. package/dist/semantic/diffParse.d.ts +30 -0
  22. package/dist/semantic/diffParse.d.ts.map +1 -0
  23. package/dist/semantic/diffParse.js +48 -0
  24. package/dist/semantic/diffParse.js.map +1 -0
  25. package/dist/semantic/imports.d.ts +16 -0
  26. package/dist/semantic/imports.d.ts.map +1 -0
  27. package/dist/semantic/imports.js +102 -0
  28. package/dist/semantic/imports.js.map +1 -0
  29. package/dist/semantic/index.d.ts +45 -0
  30. package/dist/semantic/index.d.ts.map +1 -0
  31. package/dist/semantic/index.js +78 -0
  32. package/dist/semantic/index.js.map +1 -0
  33. package/dist/semantic/languages.d.ts +14 -0
  34. package/dist/semantic/languages.d.ts.map +1 -0
  35. package/dist/semantic/languages.js +40 -0
  36. package/dist/semantic/languages.js.map +1 -0
  37. package/dist/semantic/parser.d.ts +15 -0
  38. package/dist/semantic/parser.d.ts.map +1 -0
  39. package/dist/semantic/parser.js +83 -0
  40. package/dist/semantic/parser.js.map +1 -0
  41. package/dist/semantic/symbols.d.ts +54 -0
  42. package/dist/semantic/symbols.d.ts.map +1 -0
  43. package/dist/semantic/symbols.js +287 -0
  44. package/dist/semantic/symbols.js.map +1 -0
  45. package/dist/semantic/types.d.ts +43 -0
  46. package/dist/semantic/types.d.ts.map +1 -0
  47. package/dist/semantic/types.js +2 -0
  48. package/dist/semantic/types.js.map +1 -0
  49. package/package.json +20 -4
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Import-edge resolution.
3
+ *
4
+ * Given an import like `import { foo, bar } from './utils'` in
5
+ * `src/services/billing.ts`, resolve `./utils` to a real repo path, fetch
6
+ * that file via the FileReader, parse it, and extract the signatures of
7
+ * `foo` and `bar`.
8
+ *
9
+ * Resolution rules (intentionally cheap):
10
+ * - Relative paths only (`.` or `..`). Bare-module specifiers (`react`,
11
+ * `lodash`) are skipped — we'd need package.json + node_modules walking
12
+ * to handle those, and signatures of third-party deps rarely catch bugs
13
+ * the diff alone misses.
14
+ * - Try the literal path, then `<path>.ts`, `.tsx`, `.js`, `.jsx`,
15
+ * `.py`, then `<path>/index.ts` / `.js`. Skip the rest. The plan calls
16
+ * this "1-hop"; we explicitly do NOT recurse.
17
+ *
18
+ * Returns at most `cap` imported symbols total across the call to keep the
19
+ * rendered block bounded.
20
+ */
21
+ import { dirname, posix } from 'node:path';
22
+ import { languageForPath } from './languages.js';
23
+ import { parse } from './parser.js';
24
+ import { topLevelDefinitions, symbolName } from './symbols.js';
25
+ const RELATIVE_CANDIDATE_SUFFIXES = ['', '.ts', '.tsx', '.js', '.jsx', '.py', '.rb'];
26
+ const INDEX_SUFFIXES = ['/index.ts', '/index.tsx', '/index.js', '/index.jsx', '/__init__.py'];
27
+ function isRelative(spec) {
28
+ return spec.startsWith('./') || spec.startsWith('../') || spec === '.' || spec === '..';
29
+ }
30
+ function candidatePaths(fromFile, spec) {
31
+ const baseDir = dirname(fromFile);
32
+ const joined = posix.normalize(posix.join(baseDir, spec));
33
+ const out = [];
34
+ // TS/ESM convention: source imports `./foo.js` but the actual file on disk
35
+ // is `./foo.ts`. Try the TS-equivalent before the literal so common cases
36
+ // resolve on the first hit. Also handles `.jsx → .tsx`, `.mjs → .mts`, etc.
37
+ const jsToTs = {
38
+ '.js': '.ts',
39
+ '.jsx': '.tsx',
40
+ '.mjs': '.mts',
41
+ '.cjs': '.cts',
42
+ };
43
+ const extMatch = /(\.[a-z]+)$/i.exec(joined);
44
+ if (extMatch && jsToTs[extMatch[1]]) {
45
+ out.push(joined.slice(0, -extMatch[1].length) + jsToTs[extMatch[1]]);
46
+ }
47
+ for (const sfx of RELATIVE_CANDIDATE_SUFFIXES) {
48
+ out.push(joined + sfx);
49
+ }
50
+ for (const sfx of INDEX_SUFFIXES) {
51
+ out.push(joined + sfx);
52
+ }
53
+ return out;
54
+ }
55
+ async function resolveImport(reader, fromFile, spec) {
56
+ if (!isRelative(spec))
57
+ return null;
58
+ for (const candidate of candidatePaths(fromFile, spec)) {
59
+ const content = await reader.read(candidate);
60
+ if (content !== null)
61
+ return { path: candidate, content };
62
+ }
63
+ return null;
64
+ }
65
+ export async function resolveImports(input) {
66
+ const collected = [];
67
+ for (const edge of input.edges) {
68
+ if (collected.length >= input.cap)
69
+ break;
70
+ if (!isRelative(edge.source))
71
+ continue;
72
+ const resolved = await resolveImport(input.reader, input.fromFile, edge.source);
73
+ if (!resolved)
74
+ continue;
75
+ const lang = languageForPath(resolved.path);
76
+ if (!lang)
77
+ continue;
78
+ const parsed = await parse(resolved.content, lang);
79
+ if (!parsed)
80
+ continue;
81
+ const defs = topLevelDefinitions(parsed, lang);
82
+ const wanted = new Set(edge.names);
83
+ const wantsAll = wanted.has('*');
84
+ for (const def of defs) {
85
+ if (collected.length >= input.cap)
86
+ break;
87
+ const nm = symbolName(def);
88
+ if (!wantsAll && !wanted.has(nm))
89
+ continue;
90
+ // Signature = first line of the body. For TS interfaces / type aliases
91
+ // the "first line" is the only meaningful part anyway.
92
+ const firstLine = def.text.split('\n', 1)[0].trim();
93
+ collected.push({
94
+ name: nm,
95
+ fromFile: resolved.path,
96
+ signature: firstLine,
97
+ });
98
+ }
99
+ }
100
+ return collected;
101
+ }
102
+ //# sourceMappingURL=imports.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"imports.js","sourceRoot":"","sources":["../../src/semantic/imports.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Public entry point: build a `<semantic_context>` block to inline next to
3
+ * the diff in the code-review LLM user message.
4
+ *
5
+ * Never throws. Any failure (grammar load, file fetch, parse) returns an
6
+ * empty `SemanticBlock` and the caller proceeds with diff-only — same as
7
+ * Foyer behaves today.
8
+ *
9
+ * Timing: this runs in the pre-sandbox static pass alongside `code_review`.
10
+ * It uses `FileReader` (Octokit-backed) rather than a `SandboxHandle`,
11
+ * because at this point in the orchestrator no workspace exists yet.
12
+ * Cross-file caller search would require a workspace and is deferred —
13
+ * see `~/.claude/plans/implement-ast-parsing-for-scalable-charm.md`.
14
+ */
15
+ import type { FileReader } from '../fileReader.js';
16
+ import { type SemanticBlockCache } from './cache.js';
17
+ import type { SemanticBlock } from './types.js';
18
+ export interface BuildSemanticContextOptions {
19
+ /** Hard cap on the rendered block (bytes). Default: 30_000. */
20
+ maxBytes?: number;
21
+ /** Cap on bytes per individual symbol body before slicing. Default: 4_000. */
22
+ bodyCapBytes?: number;
23
+ /** Cap on imported-symbol entries across the whole block. Default: 30. */
24
+ importedCap?: number;
25
+ /** Cap on files we'll parse in one call. Default: 20. Protects against
26
+ * mega-PRs that touch hundreds of files. */
27
+ fileCap?: number;
28
+ }
29
+ export declare function buildSemanticContext(diff: string, reader: FileReader, options?: BuildSemanticContextOptions): Promise<SemanticBlock>;
30
+ /**
31
+ * Cached variant: same `(repoFullName, headSha)` resolves through a shared
32
+ * LRU. The two consumers (`code_review`, `intent_fidelity`) hit the same key
33
+ * and the second one returns instantly. Falls through to a fresh build on
34
+ * miss, then stores. Pass `cache` to override the shared singleton in tests.
35
+ */
36
+ export declare function buildSemanticContextCached(input: {
37
+ diff: string;
38
+ reader: FileReader;
39
+ repoFullName: string;
40
+ headSha: string;
41
+ }, options?: BuildSemanticContextOptions, cache?: SemanticBlockCache): Promise<SemanticBlock>;
42
+ export type { SemanticBlock, TouchedSymbol, ImportedSymbol } from './types.js';
43
+ export { getSharedSemanticBlockCache, _resetSharedCacheForTests } from './cache.js';
44
+ export type { SemanticBlockCache } from './cache.js';
45
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/semantic/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAOnD,OAAO,EAGL,KAAK,kBAAkB,EACxB,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,aAAa,EAAiC,MAAM,YAAY,CAAC;AAE/E,MAAM,WAAW,2BAA2B;IAC1C,+DAA+D;IAC/D,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,8EAA8E;IAC9E,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,0EAA0E;IAC1E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;iDAC6C;IAC7C,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AASD,wBAAsB,oBAAoB,CACxC,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,UAAU,EAClB,OAAO,GAAE,2BAAgC,GACxC,OAAO,CAAC,aAAa,CAAC,CA8CxB;AAED;;;;;GAKG;AACH,wBAAsB,0BAA0B,CAC9C,KAAK,EAAE;IACL,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,UAAU,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;CACjB,EACD,OAAO,GAAE,2BAAgC,EACzC,KAAK,GAAE,kBAAkD,GACxD,OAAO,CAAC,aAAa,CAAC,CASxB;AAED,YAAY,EAAE,aAAa,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAC/E,OAAO,EAAE,2BAA2B,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AACpF,YAAY,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,78 @@
1
+ import { touchedFiles } from './diffParse.js';
2
+ import { languageForPath } from './languages.js';
3
+ import { parse } from './parser.js';
4
+ import { findTouchedSymbols, findImports } from './symbols.js';
5
+ import { resolveImports } from './imports.js';
6
+ import { renderSemanticBlock } from './contextBuilder.js';
7
+ import { cacheKey, getSharedSemanticBlockCache, } from './cache.js';
8
+ const DEFAULT_OPTS = {
9
+ maxBytes: 30_000,
10
+ bodyCapBytes: 4_000,
11
+ importedCap: 30,
12
+ fileCap: 20,
13
+ };
14
+ export async function buildSemanticContext(diff, reader, options = {}) {
15
+ const opts = { ...DEFAULT_OPTS, ...options };
16
+ if (!diff || diff.trim().length === 0) {
17
+ return { rendered: '', stats: { touchedFiles: 0, touchedSymbols: 0, importedSymbols: 0, bytes: 0, truncated: false } };
18
+ }
19
+ try {
20
+ const files = touchedFiles(diff).slice(0, opts.fileCap);
21
+ if (files.length === 0) {
22
+ return { rendered: '', stats: { touchedFiles: 0, touchedSymbols: 0, importedSymbols: 0, bytes: 0, truncated: false } };
23
+ }
24
+ const touched = [];
25
+ const imported = [];
26
+ const remainingImportBudget = () => opts.importedCap - imported.length;
27
+ for (const file of files) {
28
+ const lang = languageForPath(file.path);
29
+ if (!lang)
30
+ continue;
31
+ const source = await reader.read(file.path);
32
+ if (source === null)
33
+ continue;
34
+ const parsed = await parse(source, lang);
35
+ if (!parsed)
36
+ continue;
37
+ const fileSymbols = findTouchedSymbols(parsed, lang, file.path, file.hunks, opts.bodyCapBytes);
38
+ touched.push(...fileSymbols);
39
+ // Only bother resolving imports for files that actually have touched
40
+ // symbols — if the diff is a config edit, the imports aren't useful.
41
+ if (fileSymbols.length > 0 && remainingImportBudget() > 0) {
42
+ const edges = findImports(parsed, lang);
43
+ const resolved = await resolveImports({
44
+ fromFile: file.path,
45
+ edges,
46
+ reader,
47
+ cap: remainingImportBudget(),
48
+ });
49
+ imported.push(...resolved);
50
+ }
51
+ }
52
+ return renderSemanticBlock({ touched, imported, maxBytes: opts.maxBytes });
53
+ }
54
+ catch {
55
+ // Never block a review on enrichment failure.
56
+ return { rendered: '', stats: { touchedFiles: 0, touchedSymbols: 0, importedSymbols: 0, bytes: 0, truncated: false } };
57
+ }
58
+ }
59
+ /**
60
+ * Cached variant: same `(repoFullName, headSha)` resolves through a shared
61
+ * LRU. The two consumers (`code_review`, `intent_fidelity`) hit the same key
62
+ * and the second one returns instantly. Falls through to a fresh build on
63
+ * miss, then stores. Pass `cache` to override the shared singleton in tests.
64
+ */
65
+ export async function buildSemanticContextCached(input, options = {}, cache = getSharedSemanticBlockCache()) {
66
+ const key = cacheKey(input.repoFullName, input.headSha);
67
+ const cached = cache.get(key);
68
+ if (cached)
69
+ return cached;
70
+ const fresh = await buildSemanticContext(input.diff, input.reader, options);
71
+ // Only cache non-empty blocks. Empty results from a transient failure
72
+ // (e.g. parser cold-start race) should be retryable on the next consumer.
73
+ if (fresh.rendered.length > 0)
74
+ cache.set(key, fresh);
75
+ return fresh;
76
+ }
77
+ export { getSharedSemanticBlockCache, _resetSharedCacheForTests } from './cache.js';
78
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/semantic/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Extension → grammar mapping. Unknown extensions resolve to null and the
3
+ * semantic builder silently skips the file (degrades to diff-only for that
4
+ * path). Adding a language = one entry here + a new WASM in tree-sitter-wasms.
5
+ */
6
+ import type { LanguageId } from './types.js';
7
+ export declare function languageForPath(path: string): LanguageId | null;
8
+ /**
9
+ * Filename of the WASM grammar inside the `tree-sitter-wasms` package. Both
10
+ * `tsx` and `typescript` ship as separate WASMs because the TypeScript grammar
11
+ * upstream is split that way.
12
+ */
13
+ export declare function wasmFileForLanguage(lang: LanguageId): string;
14
+ //# sourceMappingURL=languages.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"languages.d.ts","sourceRoot":"","sources":["../../src/semantic/languages.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAmB7C,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAK/D;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,UAAU,GAAG,MAAM,CAU5D"}
@@ -0,0 +1,40 @@
1
+ const EXTENSION_TO_LANGUAGE = {
2
+ ts: 'typescript',
3
+ cts: 'typescript',
4
+ mts: 'typescript',
5
+ tsx: 'tsx',
6
+ js: 'javascript',
7
+ cjs: 'javascript',
8
+ mjs: 'javascript',
9
+ jsx: 'javascript',
10
+ py: 'python',
11
+ pyi: 'python',
12
+ go: 'go',
13
+ rs: 'rust',
14
+ rb: 'ruby',
15
+ rake: 'ruby',
16
+ };
17
+ export function languageForPath(path) {
18
+ const idx = path.lastIndexOf('.');
19
+ if (idx < 0)
20
+ return null;
21
+ const ext = path.slice(idx + 1).toLowerCase();
22
+ return EXTENSION_TO_LANGUAGE[ext] ?? null;
23
+ }
24
+ /**
25
+ * Filename of the WASM grammar inside the `tree-sitter-wasms` package. Both
26
+ * `tsx` and `typescript` ship as separate WASMs because the TypeScript grammar
27
+ * upstream is split that way.
28
+ */
29
+ export function wasmFileForLanguage(lang) {
30
+ switch (lang) {
31
+ case 'typescript': return 'tree-sitter-typescript.wasm';
32
+ case 'tsx': return 'tree-sitter-tsx.wasm';
33
+ case 'javascript': return 'tree-sitter-javascript.wasm';
34
+ case 'python': return 'tree-sitter-python.wasm';
35
+ case 'go': return 'tree-sitter-go.wasm';
36
+ case 'rust': return 'tree-sitter-rust.wasm';
37
+ case 'ruby': return 'tree-sitter-ruby.wasm';
38
+ }
39
+ }
40
+ //# sourceMappingURL=languages.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"languages.js","sourceRoot":"","sources":["../../src/semantic/languages.ts"],"names":[],"mappings":"AAOA,MAAM,qBAAqB,GAA+B;IACxD,EAAE,EAAE,YAAY;IAChB,GAAG,EAAE,YAAY;IACjB,GAAG,EAAE,YAAY;IACjB,GAAG,EAAE,KAAK;IACV,EAAE,EAAE,YAAY;IAChB,GAAG,EAAE,YAAY;IACjB,GAAG,EAAE,YAAY;IACjB,GAAG,EAAE,YAAY;IACjB,EAAE,EAAE,QAAQ;IACZ,GAAG,EAAE,QAAQ;IACb,EAAE,EAAE,IAAI;IACR,EAAE,EAAE,MAAM;IACV,EAAE,EAAE,MAAM;IACV,IAAI,EAAE,MAAM;CACb,CAAC;AAEF,MAAM,UAAU,eAAe,CAAC,IAAY;IAC1C,MAAM,GAAG,GAAG,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IAClC,IAAI,GAAG,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACzB,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IAC9C,OAAO,qBAAqB,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC;AAC5C,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAgB;IAClD,QAAQ,IAAI,EAAE,CAAC;QACb,KAAK,YAAY,CAAC,CAAC,OAAO,6BAA6B,CAAC;QACxD,KAAK,KAAK,CAAC,CAAQ,OAAO,sBAAsB,CAAC;QACjD,KAAK,YAAY,CAAC,CAAC,OAAO,6BAA6B,CAAC;QACxD,KAAK,QAAQ,CAAC,CAAK,OAAO,yBAAyB,CAAC;QACpD,KAAK,IAAI,CAAC,CAAS,OAAO,qBAAqB,CAAC;QAChD,KAAK,MAAM,CAAC,CAAO,OAAO,uBAAuB,CAAC;QAClD,KAAK,MAAM,CAAC,CAAO,OAAO,uBAAuB,CAAC;IACpD,CAAC;AACH,CAAC"}
@@ -0,0 +1,15 @@
1
+ import { type Tree } from 'web-tree-sitter';
2
+ import type { LanguageId } from './types.js';
3
+ export interface ParseResult {
4
+ tree: Tree;
5
+ source: string;
6
+ }
7
+ /**
8
+ * Parse `source` as `lang`. Returns null when the grammar can't be loaded or
9
+ * when parsing throws. The caller treats null as "skip this file" and the
10
+ * semantic block degrades silently for that path.
11
+ */
12
+ export declare function parse(source: string, lang: LanguageId): Promise<ParseResult | null>;
13
+ /** Test seam: drop caches so a unit test can simulate a cold start. */
14
+ export declare function _resetForTests(): void;
15
+ //# sourceMappingURL=parser.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.d.ts","sourceRoot":"","sources":["../../src/semantic/parser.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAoB,KAAK,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAE9D,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AA0C7C,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,IAAI,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;GAIG;AACH,wBAAsB,KAAK,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,GAAG,OAAO,CAAC,WAAW,GAAG,IAAI,CAAC,CAYzF;AAED,uEAAuE;AACvE,wBAAgB,cAAc,IAAI,IAAI,CAGrC"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Lazy, process-singleton wrapper around web-tree-sitter.
3
+ *
4
+ * Loading WASM is async and not free (~5MB per grammar), so we initialise the
5
+ * runtime once and cache loaded grammars by `LanguageId`. Callers do not need
6
+ * to know about WASM — they ask for a `Tree` and either get one or `null` if
7
+ * the grammar fails to load (graceful degradation: the semantic block just
8
+ * skips that file).
9
+ *
10
+ * We intentionally don't expose Parser/Language to callers — every consumer
11
+ * just wants `parse(source, lang)`.
12
+ */
13
+ import { readFile } from 'node:fs/promises';
14
+ import { dirname, resolve } from 'node:path';
15
+ import { createRequire } from 'node:module';
16
+ import { Parser, Language } from 'web-tree-sitter';
17
+ import { wasmFileForLanguage } from './languages.js';
18
+ const require_ = createRequire(import.meta.url);
19
+ let initPromise = null;
20
+ const languageCache = new Map();
21
+ async function ensureInit() {
22
+ if (initPromise)
23
+ return initPromise;
24
+ initPromise = (async () => {
25
+ // web-tree-sitter looks for `tree-sitter.wasm` next to its main module by
26
+ // default. We override `locateFile` to point at the package directly so
27
+ // node ESM resolution doesn't trip over import.meta.url plumbing.
28
+ const wtsMain = require_.resolve('web-tree-sitter');
29
+ const wtsDir = dirname(wtsMain);
30
+ await Parser.init({
31
+ locateFile: (name) => resolve(wtsDir, name),
32
+ });
33
+ })().catch((err) => {
34
+ // Reset on failure so a transient error doesn't poison the singleton.
35
+ initPromise = null;
36
+ throw err;
37
+ });
38
+ return initPromise;
39
+ }
40
+ async function loadGrammar(lang) {
41
+ if (languageCache.has(lang))
42
+ return languageCache.get(lang) ?? null;
43
+ try {
44
+ await ensureInit();
45
+ const wasmsDir = dirname(require_.resolve('tree-sitter-wasms/package.json'));
46
+ const wasmPath = resolve(wasmsDir, 'out', wasmFileForLanguage(lang));
47
+ const bytes = await readFile(wasmPath);
48
+ const language = await Language.load(bytes);
49
+ languageCache.set(lang, language);
50
+ return language;
51
+ }
52
+ catch {
53
+ languageCache.set(lang, null);
54
+ return null;
55
+ }
56
+ }
57
+ /**
58
+ * Parse `source` as `lang`. Returns null when the grammar can't be loaded or
59
+ * when parsing throws. The caller treats null as "skip this file" and the
60
+ * semantic block degrades silently for that path.
61
+ */
62
+ export async function parse(source, lang) {
63
+ const language = await loadGrammar(lang);
64
+ if (!language)
65
+ return null;
66
+ try {
67
+ const parser = new Parser();
68
+ parser.setLanguage(language);
69
+ const tree = parser.parse(source);
70
+ if (!tree)
71
+ return null;
72
+ return { tree, source };
73
+ }
74
+ catch {
75
+ return null;
76
+ }
77
+ }
78
+ /** Test seam: drop caches so a unit test can simulate a cold start. */
79
+ export function _resetForTests() {
80
+ initPromise = null;
81
+ languageCache.clear();
82
+ }
83
+ //# sourceMappingURL=parser.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"parser.js","sourceRoot":"","sources":["../../src/semantic/parser.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAE5C,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAC7C,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAa,MAAM,iBAAiB,CAAC;AAC9D,OAAO,EAAE,mBAAmB,EAAE,MAAM,gBAAgB,CAAC;AAGrD,MAAM,QAAQ,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAEhD,IAAI,WAAW,GAAyB,IAAI,CAAC;AAC7C,MAAM,aAAa,GAAG,IAAI,GAAG,EAA+B,CAAC;AAE7D,KAAK,UAAU,UAAU;IACvB,IAAI,WAAW;QAAE,OAAO,WAAW,CAAC;IACpC,WAAW,GAAG,CAAC,KAAK,IAAI,EAAE;QACxB,0EAA0E;QAC1E,wEAAwE;QACxE,kEAAkE;QAClE,MAAM,OAAO,GAAG,QAAQ,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC;QACpD,MAAM,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;QAChC,MAAM,MAAM,CAAC,IAAI,CAAC;YAChB,UAAU,EAAE,CAAC,IAAY,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,IAAI,CAAC;SACf,CAAC,CAAC;IAC1C,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;QACjB,sEAAsE;QACtE,WAAW,GAAG,IAAI,CAAC;QACnB,MAAM,GAAG,CAAC;IACZ,CAAC,CAAC,CAAC;IACH,OAAO,WAAW,CAAC;AACrB,CAAC;AAED,KAAK,UAAU,WAAW,CAAC,IAAgB;IACzC,IAAI,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC;QAAE,OAAO,aAAa,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;IACpE,IAAI,CAAC;QACH,MAAM,UAAU,EAAE,CAAC;QACnB,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,gCAAgC,CAAC,CAAC,CAAC;QAC7E,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,EAAE,KAAK,EAAE,mBAAmB,CAAC,IAAI,CAAC,CAAC,CAAC;QACrE,MAAM,KAAK,GAAG,MAAM,QAAQ,CAAC,QAAQ,CAAC,CAAC;QACvC,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC5C,aAAa,CAAC,GAAG,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QAClC,OAAO,QAAQ,CAAC;IAClB,CAAC;IAAC,MAAM,CAAC;QACP,aAAa,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAC9B,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAOD;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,KAAK,CAAC,MAAc,EAAE,IAAgB;IAC1D,MAAM,QAAQ,GAAG,MAAM,WAAW,CAAC,IAAI,CAAC,CAAC;IACzC,IAAI,CAAC,QAAQ;QAAE,OAAO,IAAI,CAAC;IAC3B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;QAC5B,MAAM,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAC;QAC7B,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QAClC,IAAI,CAAC,IAAI;YAAE,OAAO,IAAI,CAAC;QACvB,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,CAAC;IAC1B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,uEAAuE;AACvE,MAAM,UAAU,cAAc;IAC5B,WAAW,GAAG,IAAI,CAAC;IACnB,aAAa,CAAC,KAAK,EAAE,CAAC;AACxB,CAAC"}
@@ -0,0 +1,54 @@
1
+ /**
2
+ * Symbol extraction from a parsed AST.
3
+ *
4
+ * Two responsibilities:
5
+ * 1. Given a parsed file + a set of changed line ranges, return the top-level
6
+ * symbols (functions, classes, methods) whose body overlaps any of those
7
+ * ranges. Those are the "touched symbols" the reviewer should know about
8
+ * in full, not just by diff hunks.
9
+ * 2. Given a parsed file, return its import edges: which modules it imports
10
+ * and which named bindings it pulls in. The imports module resolves those
11
+ * to files; the resolver lives outside so this stays AST-only.
12
+ *
13
+ * The grammars provide the node type strings; the per-language differences
14
+ * are isolated in `topLevelTypes` and `importQuery` below.
15
+ */
16
+ import type { Node } from 'web-tree-sitter';
17
+ import type { ParseResult } from './parser.js';
18
+ import type { LanguageId, TouchedSymbol } from './types.js';
19
+ /**
20
+ * Top-level definitions in order. We walk the root node's direct children
21
+ * (and one level into `export_statement` / `decorated_definition` wrappers).
22
+ */
23
+ export declare function topLevelDefinitions(parsed: ParseResult, lang: LanguageId): Node[];
24
+ /**
25
+ * Heuristic name extraction. Tree-sitter exposes a `name` field for most
26
+ * declaration types; for lexical_declaration we walk into the first
27
+ * variable_declarator and read its `name` field. Returns "<anonymous>" rather
28
+ * than null so the rendered block always has something printable.
29
+ */
30
+ export declare function symbolName(node: Node): string;
31
+ export declare function symbolKind(node: Node): TouchedSymbol['kind'];
32
+ /**
33
+ * Return the symbols whose source range overlaps any of the supplied
34
+ * post-image line ranges. Lines are 1-based; tree-sitter positions are
35
+ * 0-based, so we shift by one when comparing.
36
+ */
37
+ export declare function findTouchedSymbols(parsed: ParseResult, lang: LanguageId, file: string, hunks: {
38
+ newStart: number;
39
+ newLines: number;
40
+ }[], bodyCapBytes: number): TouchedSymbol[];
41
+ export interface ImportEdge {
42
+ /** Module specifier as it appears in source (e.g. `./foo`, `react`). */
43
+ source: string;
44
+ /** Local names imported. For `import * as X from ...` this is `['*']`. */
45
+ names: string[];
46
+ }
47
+ /**
48
+ * Extract import edges. JS/TS use `import_statement`; Python uses
49
+ * `import_from_statement` / `import_statement`. We surface only the cheap
50
+ * cases — full semantics (re-exports, computed paths) would require a real
51
+ * compiler, which is out of scope per the plan.
52
+ */
53
+ export declare function findImports(parsed: ParseResult, lang: LanguageId): ImportEdge[];
54
+ //# sourceMappingURL=symbols.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"symbols.d.ts","sourceRoot":"","sources":["../../src/semantic/symbols.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAC;AAC5C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AA4D5D;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,GAAG,IAAI,EAAE,CAoBjF;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,MAAM,CA4B7C;AAED,wBAAgB,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,aAAa,CAAC,MAAM,CAAC,CA8B5D;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,UAAU,EAChB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,EAAE,EAC/C,YAAY,EAAE,MAAM,GACnB,aAAa,EAAE,CA4BjB;AAMD,MAAM,WAAW,UAAU;IACzB,wEAAwE;IACxE,MAAM,EAAE,MAAM,CAAC;IACf,0EAA0E;IAC1E,KAAK,EAAE,MAAM,EAAE,CAAC;CACjB;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,UAAU,GAAG,UAAU,EAAE,CAsE/E"}