@getfoyer/review-core 0.2.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 (45) 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/semantic/cache.d.ts +31 -0
  10. package/dist/semantic/cache.d.ts.map +1 -0
  11. package/dist/semantic/cache.js +50 -0
  12. package/dist/semantic/cache.js.map +1 -0
  13. package/dist/semantic/contextBuilder.d.ts +17 -0
  14. package/dist/semantic/contextBuilder.d.ts.map +1 -0
  15. package/dist/semantic/contextBuilder.js +92 -0
  16. package/dist/semantic/contextBuilder.js.map +1 -0
  17. package/dist/semantic/diffParse.d.ts +30 -0
  18. package/dist/semantic/diffParse.d.ts.map +1 -0
  19. package/dist/semantic/diffParse.js +48 -0
  20. package/dist/semantic/diffParse.js.map +1 -0
  21. package/dist/semantic/imports.d.ts +16 -0
  22. package/dist/semantic/imports.d.ts.map +1 -0
  23. package/dist/semantic/imports.js +102 -0
  24. package/dist/semantic/imports.js.map +1 -0
  25. package/dist/semantic/index.d.ts +45 -0
  26. package/dist/semantic/index.d.ts.map +1 -0
  27. package/dist/semantic/index.js +78 -0
  28. package/dist/semantic/index.js.map +1 -0
  29. package/dist/semantic/languages.d.ts +14 -0
  30. package/dist/semantic/languages.d.ts.map +1 -0
  31. package/dist/semantic/languages.js +40 -0
  32. package/dist/semantic/languages.js.map +1 -0
  33. package/dist/semantic/parser.d.ts +15 -0
  34. package/dist/semantic/parser.d.ts.map +1 -0
  35. package/dist/semantic/parser.js +83 -0
  36. package/dist/semantic/parser.js.map +1 -0
  37. package/dist/semantic/symbols.d.ts +54 -0
  38. package/dist/semantic/symbols.d.ts.map +1 -0
  39. package/dist/semantic/symbols.js +287 -0
  40. package/dist/semantic/symbols.js.map +1 -0
  41. package/dist/semantic/types.d.ts +43 -0
  42. package/dist/semantic/types.d.ts.map +1 -0
  43. package/dist/semantic/types.js +2 -0
  44. package/dist/semantic/types.js.map +1 -0
  45. package/package.json +13 -5
@@ -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":"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"}
@@ -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"}
@@ -0,0 +1,287 @@
1
+ const TOP_LEVEL_DEFINITION_TYPES = {
2
+ typescript: new Set([
3
+ 'function_declaration',
4
+ 'class_declaration',
5
+ 'method_definition',
6
+ 'interface_declaration',
7
+ 'type_alias_declaration',
8
+ 'lexical_declaration', // `const foo = (...) => {...}`
9
+ 'export_statement',
10
+ ]),
11
+ tsx: new Set([
12
+ 'function_declaration',
13
+ 'class_declaration',
14
+ 'method_definition',
15
+ 'interface_declaration',
16
+ 'type_alias_declaration',
17
+ 'lexical_declaration',
18
+ 'export_statement',
19
+ ]),
20
+ javascript: new Set([
21
+ 'function_declaration',
22
+ 'class_declaration',
23
+ 'method_definition',
24
+ 'lexical_declaration',
25
+ 'export_statement',
26
+ ]),
27
+ python: new Set([
28
+ 'function_definition',
29
+ 'class_definition',
30
+ 'decorated_definition',
31
+ ]),
32
+ go: new Set([
33
+ 'function_declaration',
34
+ 'method_declaration',
35
+ 'type_declaration',
36
+ 'var_declaration',
37
+ 'const_declaration',
38
+ ]),
39
+ rust: new Set([
40
+ 'function_item',
41
+ 'struct_item',
42
+ 'enum_item',
43
+ 'trait_item',
44
+ 'impl_item',
45
+ 'mod_item',
46
+ 'const_item',
47
+ 'static_item',
48
+ 'type_item',
49
+ ]),
50
+ ruby: new Set([
51
+ 'method',
52
+ 'singleton_method',
53
+ 'class',
54
+ 'module',
55
+ 'assignment', // constants — uppercase LHS resolved in symbolName
56
+ ]),
57
+ };
58
+ /**
59
+ * Top-level definitions in order. We walk the root node's direct children
60
+ * (and one level into `export_statement` / `decorated_definition` wrappers).
61
+ */
62
+ export function topLevelDefinitions(parsed, lang) {
63
+ const wantedTypes = TOP_LEVEL_DEFINITION_TYPES[lang];
64
+ const out = [];
65
+ for (const child of parsed.tree.rootNode.namedChildren) {
66
+ if (!child)
67
+ continue;
68
+ if (wantedTypes.has(child.type)) {
69
+ // Unwrap `export_statement` so we report the inner declaration. The
70
+ // body line range is unchanged either way.
71
+ if (child.type === 'export_statement') {
72
+ const inner = child.namedChildren.find((c) => !!c && wantedTypes.has(c.type));
73
+ out.push(inner ?? child);
74
+ }
75
+ else if (child.type === 'decorated_definition') {
76
+ const inner = child.namedChildren.find((c) => !!c && (c.type === 'function_definition' || c.type === 'class_definition'));
77
+ out.push(inner ?? child);
78
+ }
79
+ else {
80
+ out.push(child);
81
+ }
82
+ }
83
+ }
84
+ return out;
85
+ }
86
+ /**
87
+ * Heuristic name extraction. Tree-sitter exposes a `name` field for most
88
+ * declaration types; for lexical_declaration we walk into the first
89
+ * variable_declarator and read its `name` field. Returns "<anonymous>" rather
90
+ * than null so the rendered block always has something printable.
91
+ */
92
+ export function symbolName(node) {
93
+ const named = node.childForFieldName('name');
94
+ if (named?.text)
95
+ return named.text;
96
+ if (node.type === 'lexical_declaration') {
97
+ const decl = node.namedChildren.find((c) => !!c && c.type === 'variable_declarator');
98
+ const name = decl?.childForFieldName('name');
99
+ if (name?.text)
100
+ return name.text;
101
+ }
102
+ // Go `type_declaration` / `var_declaration` / `const_declaration` wrap an
103
+ // inner spec node that carries the name field.
104
+ if (node.type === 'type_declaration' || node.type === 'var_declaration' || node.type === 'const_declaration') {
105
+ const spec = node.namedChildren.find((c) => !!c && (c.type === 'type_spec' || c.type === 'var_spec' || c.type === 'const_spec'));
106
+ const nm = spec?.childForFieldName('name');
107
+ if (nm?.text)
108
+ return nm.text;
109
+ }
110
+ // Ruby `assignment` for top-level constants: LHS is an `identifier` or
111
+ // `constant`. We surface only constants (uppercase start) — lowercase locals
112
+ // aren't useful in review context.
113
+ if (node.type === 'assignment') {
114
+ const lhs = node.childForFieldName('left');
115
+ if (lhs?.text && /^[A-Z]/.test(lhs.text))
116
+ return lhs.text;
117
+ }
118
+ // Rust `impl_item` exposes the implemented type via the `type` field.
119
+ if (node.type === 'impl_item') {
120
+ const ty = node.childForFieldName('type');
121
+ if (ty?.text)
122
+ return `impl ${ty.text}`;
123
+ }
124
+ return '<anonymous>';
125
+ }
126
+ export function symbolKind(node) {
127
+ switch (node.type) {
128
+ case 'function_declaration':
129
+ case 'function_definition':
130
+ case 'function_item':
131
+ case 'method':
132
+ case 'singleton_method':
133
+ case 'method_declaration':
134
+ return node.type.startsWith('method') ? 'method' : 'function';
135
+ case 'class_declaration':
136
+ case 'class_definition':
137
+ case 'class':
138
+ case 'struct_item':
139
+ case 'enum_item':
140
+ case 'trait_item':
141
+ case 'module':
142
+ case 'mod_item':
143
+ return 'class';
144
+ case 'method_definition':
145
+ return 'method';
146
+ case 'lexical_declaration':
147
+ case 'const_declaration':
148
+ case 'const_item':
149
+ case 'static_item':
150
+ case 'var_declaration':
151
+ case 'assignment':
152
+ return 'const';
153
+ default:
154
+ return 'unknown';
155
+ }
156
+ }
157
+ /**
158
+ * Return the symbols whose source range overlaps any of the supplied
159
+ * post-image line ranges. Lines are 1-based; tree-sitter positions are
160
+ * 0-based, so we shift by one when comparing.
161
+ */
162
+ export function findTouchedSymbols(parsed, lang, file, hunks, bodyCapBytes) {
163
+ if (hunks.length === 0)
164
+ return [];
165
+ const defs = topLevelDefinitions(parsed, lang);
166
+ const seen = new Set();
167
+ const out = [];
168
+ for (const def of defs) {
169
+ const startLine = def.startPosition.row + 1;
170
+ const endLine = def.endPosition.row + 1;
171
+ const overlaps = hunks.some((h) => {
172
+ const hStart = h.newStart;
173
+ const hEnd = h.newStart + Math.max(0, h.newLines - 1);
174
+ return hEnd >= startLine && hStart <= endLine;
175
+ });
176
+ if (!overlaps)
177
+ continue;
178
+ const name = symbolName(def);
179
+ const key = `${name}@${startLine}`;
180
+ if (seen.has(key))
181
+ continue;
182
+ seen.add(key);
183
+ const body = def.text.length > bodyCapBytes ? def.text.slice(0, bodyCapBytes) + '\n/* …truncated… */' : def.text;
184
+ out.push({
185
+ file,
186
+ name,
187
+ kind: symbolKind(def),
188
+ body,
189
+ line: startLine,
190
+ });
191
+ }
192
+ return out;
193
+ }
194
+ /**
195
+ * Extract import edges. JS/TS use `import_statement`; Python uses
196
+ * `import_from_statement` / `import_statement`. We surface only the cheap
197
+ * cases — full semantics (re-exports, computed paths) would require a real
198
+ * compiler, which is out of scope per the plan.
199
+ */
200
+ export function findImports(parsed, lang) {
201
+ const edges = [];
202
+ // Go imports name packages, not files; Rust `use` paths resolve through a
203
+ // module tree we don't model. Skip both — touched-symbol bodies still ship.
204
+ if (lang === 'go' || lang === 'rust')
205
+ return edges;
206
+ for (const child of parsed.tree.rootNode.namedChildren) {
207
+ if (!child)
208
+ continue;
209
+ if (lang === 'ruby') {
210
+ // Ruby `require_relative "./foo"` appears as a `call` with method
211
+ // `require_relative` and a string-literal argument. We surface the
212
+ // bare path; the resolver maps it to a `.rb` file.
213
+ if (child.type !== 'call')
214
+ continue;
215
+ const method = child.childForFieldName('method')?.text;
216
+ if (method !== 'require_relative' && method !== 'require')
217
+ continue;
218
+ const args = child.childForFieldName('arguments');
219
+ const strArg = args?.namedChildren.find((c) => !!c && c.type === 'string');
220
+ if (!strArg)
221
+ continue;
222
+ const raw = strArg.text.replace(/^['"]|['"]$/g, '');
223
+ // For `require_relative`, treat as relative; for plain `require`, only
224
+ // surface when the path looks relative (rare but harmless).
225
+ if (method === 'require_relative' || raw.startsWith('./') || raw.startsWith('../')) {
226
+ edges.push({ source: raw.startsWith('.') ? raw : `./${raw}`, names: ['*'] });
227
+ }
228
+ continue;
229
+ }
230
+ if (lang === 'python') {
231
+ if (child.type === 'import_from_statement') {
232
+ const moduleName = child.childForFieldName('module_name')?.text;
233
+ if (!moduleName)
234
+ continue;
235
+ const names = [];
236
+ for (const n of child.namedChildren) {
237
+ if (n && (n.type === 'dotted_name' || n.type === 'aliased_import')) {
238
+ const txt = n.childForFieldName('name')?.text ?? n.text;
239
+ if (txt)
240
+ names.push(txt);
241
+ }
242
+ }
243
+ if (names.length > 0)
244
+ edges.push({ source: moduleName, names });
245
+ }
246
+ continue;
247
+ }
248
+ if (child.type !== 'import_statement')
249
+ continue;
250
+ const sourceNode = child.childForFieldName('source');
251
+ if (!sourceNode)
252
+ continue;
253
+ // `source` text is the raw quoted string. Strip quotes.
254
+ const source = sourceNode.text.replace(/^['"]|['"]$/g, '');
255
+ const names = [];
256
+ // Walk import_clause → named_imports / namespace_import / identifier.
257
+ for (const c of child.namedChildren) {
258
+ if (!c)
259
+ continue;
260
+ if (c.type === 'import_clause') {
261
+ for (const inner of c.namedChildren) {
262
+ if (!inner)
263
+ continue;
264
+ if (inner.type === 'identifier') {
265
+ names.push(inner.text);
266
+ }
267
+ else if (inner.type === 'namespace_import') {
268
+ names.push('*');
269
+ }
270
+ else if (inner.type === 'named_imports') {
271
+ for (const spec of inner.namedChildren) {
272
+ if (!spec || spec.type !== 'import_specifier')
273
+ continue;
274
+ const nm = spec.childForFieldName('name')?.text;
275
+ if (nm)
276
+ names.push(nm);
277
+ }
278
+ }
279
+ }
280
+ }
281
+ }
282
+ if (source)
283
+ edges.push({ source, names });
284
+ }
285
+ return edges;
286
+ }
287
+ //# sourceMappingURL=symbols.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"symbols.js","sourceRoot":"","sources":["../../src/semantic/symbols.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Shared types for the semantic-context module.
3
+ *
4
+ * Purpose: enrich the diff fed to the code-review LLM with definitions of the
5
+ * symbols the diff touches and the symbols those touched files import. The
6
+ * diff says *what changed*; this block says *what those changes refer to*.
7
+ */
8
+ export type LanguageId = 'typescript' | 'tsx' | 'javascript' | 'python' | 'go' | 'rust' | 'ruby';
9
+ export interface TouchedSymbol {
10
+ /** Repo-relative file path from the diff `+++ b/` header. */
11
+ file: string;
12
+ /** Symbol name (function, class, method, top-level const with function value). */
13
+ name: string;
14
+ kind: 'function' | 'class' | 'method' | 'const' | 'unknown';
15
+ /** Full source text of the symbol's definition (not just diff hunks). Capped
16
+ * upstream by FileReader's per-file byte cap. */
17
+ body: string;
18
+ /** 1-based line where the symbol starts in the post-image file. */
19
+ line: number;
20
+ }
21
+ export interface ImportedSymbol {
22
+ /** Local name as used in the importing file. */
23
+ name: string;
24
+ /** Repo-relative path of the file the symbol was resolved to. */
25
+ fromFile: string;
26
+ /** Signature line(s) — the first line of the definition. Cheaper than the
27
+ * full body and usually enough to catch arg-shape mismatches. */
28
+ signature: string;
29
+ }
30
+ export interface SemanticBlock {
31
+ /** Rendered markdown block ready to inline in the LLM user message. Empty
32
+ * string when nothing useful was extracted. */
33
+ rendered: string;
34
+ /** Diagnostic counts for logging. */
35
+ stats: {
36
+ touchedFiles: number;
37
+ touchedSymbols: number;
38
+ importedSymbols: number;
39
+ bytes: number;
40
+ truncated: boolean;
41
+ };
42
+ }
43
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/semantic/types.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,MAAM,UAAU,GAClB,YAAY,GACZ,KAAK,GACL,YAAY,GACZ,QAAQ,GACR,IAAI,GACJ,MAAM,GACN,MAAM,CAAC;AAEX,MAAM,WAAW,aAAa;IAC5B,6DAA6D;IAC7D,IAAI,EAAE,MAAM,CAAC;IACb,kFAAkF;IAClF,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,UAAU,GAAG,OAAO,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;IAC5D;sDACkD;IAClD,IAAI,EAAE,MAAM,CAAC;IACb,mEAAmE;IACnE,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,cAAc;IAC7B,gDAAgD;IAChD,IAAI,EAAE,MAAM,CAAC;IACb,iEAAiE;IACjE,QAAQ,EAAE,MAAM,CAAC;IACjB;sEACkE;IAClE,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B;oDACgD;IAChD,QAAQ,EAAE,MAAM,CAAC;IACjB,qCAAqC;IACrC,KAAK,EAAE;QACL,YAAY,EAAE,MAAM,CAAC;QACrB,cAAc,EAAE,MAAM,CAAC;QACvB,eAAe,EAAE,MAAM,CAAC;QACxB,KAAK,EAAE,MAAM,CAAC;QACd,SAAS,EAAE,OAAO,CAAC;KACpB,CAAC;CACH"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/semantic/types.ts"],"names":[],"mappings":""}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@getfoyer/review-core",
3
- "version": "0.2.0",
4
- "description": "Frozen contract for Foyer run artifacts (RunArtifact v0.1) — the shared shape consumed by the GitHub App, the CLI, the hosted report, and PR-comment writers.",
3
+ "version": "0.2.1",
4
+ "description": "Frozen contract for Foyer run artifacts (RunArtifact v0.1) — the shared shape consumed by the GitHub App, the CLI, the hosted report, and PR-comment writers. Also ships the cross-cutting semantic-context builder used by code review.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -17,6 +17,10 @@
17
17
  "./plan-context": {
18
18
  "types": "./dist/plan-context.d.ts",
19
19
  "import": "./dist/plan-context.js"
20
+ },
21
+ "./semantic": {
22
+ "types": "./dist/semantic/index.d.ts",
23
+ "import": "./dist/semantic/index.js"
20
24
  }
21
25
  },
22
26
  "files": [
@@ -30,13 +34,17 @@
30
34
  },
31
35
  "scripts": {
32
36
  "build": "tsc",
33
- "typecheck": "tsc --noEmit"
37
+ "typecheck": "tsc --noEmit",
38
+ "test": "vitest run"
34
39
  },
35
40
  "dependencies": {
36
- "@getfoyer/client": "0.1.3"
41
+ "@getfoyer/client": "portal:../client",
42
+ "tree-sitter-wasms": "^0.1.12",
43
+ "web-tree-sitter": "^0.25.0"
37
44
  },
38
45
  "devDependencies": {
39
46
  "@types/node": "^22.0.0",
40
- "typescript": "^5.5.0"
47
+ "typescript": "^5.5.0",
48
+ "vitest": "^4.1.2"
41
49
  }
42
50
  }