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);