From 353c30d33f02cc8d05efa4c6422233d52d33ddc7 Mon Sep 17 00:00:00 2001 From: Gergo Magyar Date: Fri, 25 Sep 2026 18:24:36 +0000 Subject: [PATCH] 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 --- gitnexus/src/core/embeddings/ast-utils.ts | 13 ++++++++----- gitnexus/src/core/ingestion/ipynb-extractor.ts | 6 +++++- .../src/core/ingestion/languages/python/captures.ts | 2 +- .../resolvers/ipynb-python-pipeline.test.ts | 7 +------ 4 files changed, 15 insertions(+), 13 deletions(-) diff --git a/gitnexus/src/core/embeddings/ast-utils.ts b/gitnexus/src/core/embeddings/ast-utils.ts index 0fa85d18f..f0478b452 100644 --- a/gitnexus/src/core/embeddings/ast-utils.ts +++ b/gitnexus/src/core/embeddings/ast-utils.ts @@ -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; diff --git a/gitnexus/src/core/ingestion/ipynb-extractor.ts b/gitnexus/src/core/ingestion/ipynb-extractor.ts index d6a8f9478..b4e6dadda 100644 --- a/gitnexus/src/core/ingestion/ipynb-extractor.ts +++ b/gitnexus/src/core/ingestion/ipynb-extractor.ts @@ -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; diff --git a/gitnexus/src/core/ingestion/languages/python/captures.ts b/gitnexus/src/core/ingestion/languages/python/captures.ts index 717140305..b37850366 100644 --- a/gitnexus/src/core/ingestion/languages/python/captures.ts +++ b/gitnexus/src/core/ingestion/languages/python/captures.ts @@ -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'; diff --git a/gitnexus/test/integration/resolvers/ipynb-python-pipeline.test.ts b/gitnexus/test/integration/resolvers/ipynb-python-pipeline.test.ts index c0a6fd3d7..b03845dac 100644 --- a/gitnexus/test/integration/resolvers/ipynb-python-pipeline.test.ts +++ b/gitnexus/test/integration/resolvers/ipynb-python-pipeline.test.ts @@ -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);