fix(review): keep notebook snippets parseable and File nodes honest

Parse Python snippets whose path is still .ipynb, treat the extract cache as an LRU, and stop expecting skipped notebooks to lack File nodes.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Gergo Magyar 2026-09-25 18:24:36 +00:00
parent 5bf6dd8d99
commit 353c30d33f
4 changed files with 15 additions and 13 deletions

View file

@ -43,11 +43,14 @@ export const ensureAndParse = async (content: string, filePath: string): Promise
// concatenated code-cell Python (same as the parse worker).
const provider = getProvider(language);
if (isNotebookPath(filePath)) {
const extracted = extractNotebookPython(content);
if (!extracted) return null;
const parseContent =
provider.preprocessSource?.(extracted.pythonSource, filePath) ?? extracted.pythonSource;
return parseSourceSafe(parserInstance, parseContent);
const body = content.charCodeAt(0) === 0xfeff ? content.slice(1) : content;
if (body.trimStart().startsWith('{')) {
const extracted = extractNotebookPython(content);
if (!extracted) return null;
const parseContent =
provider.preprocessSource?.(extracted.pythonSource, filePath) ?? extracted.pythonSource;
return parseSourceSafe(parserInstance, parseContent);
}
}
const parseContent = provider.preprocessSource?.(content, filePath) ?? content;

View file

@ -647,7 +647,11 @@ export function extractNotebookPythonCached(
content: string,
): NotebookPythonExtraction | null {
const hit = extractCache.get(filePath);
if (hit && hit.content === content) return hit.result;
if (hit && hit.content === content) {
extractCache.delete(filePath);
extractCache.set(filePath, hit);
return hit.result;
}
const result = extractNotebookPython(content);
if (extractCache.size >= EXTRACT_CACHE_LIMIT && !extractCache.has(filePath)) {
const oldest = extractCache.keys().next().value;

View file

@ -12,7 +12,7 @@
* binding, and `__init__` assignments from annotated parameters emit
* class-scoped instance-field bindings (see `receiver-binding.ts`).
*
* Pure given the input source text. No I/O, no globals consulted.
* No I/O. A `.ipynb` path also depends on `filePath` and `sourceMeta`, not only the source text.
*/
import type { Capture, CaptureMatch, Range } from 'gitnexus-shared';

View file

@ -82,12 +82,7 @@ describe('Jupyter notebook Python pipeline', () => {
),
).toHaveLength(0);
expect(
getNodesByLabelFull(result, 'File').filter((n) =>
n.properties.filePath.replace(/\\/g, '/').endsWith('julia.ipynb'),
),
).toHaveLength(0);
expect(
getNodesByLabelFull(result, 'File').filter((n) =>
getNodesByLabelFull(result, 'Function').filter((n) =>
n.properties.filePath.replace(/\\/g, '/').endsWith('broken.ipynb'),
),
).toHaveLength(0);