diff --git a/.claude/skills/agent-eval/corpus.json b/.claude/skills/agent-eval/corpus.json index 2cfedac4f..b7893a000 100644 --- a/.claude/skills/agent-eval/corpus.json +++ b/.claude/skills/agent-eval/corpus.json @@ -94,5 +94,10 @@ { "name": "react-native-segmented-control", "repo": "https://github.com/react-native-segmented-control/segmented-control", "size": "Small", "files": "~25", "question": "How does JSX `` reach the native onChange handler on iOS/Android?" }, { "name": "react-native-screens", "repo": "https://github.com/software-mansion/react-native-screens", "size": "Medium", "files": "~1200", "question": "How does JSX `` reach the native RNSScreenStackView component?" }, { "name": "react-native-skia", "repo": "https://github.com/Shopify/react-native-skia", "size": "Large", "files": "~1000", "question": "How does a `` JSX usage reach the iOS / Android native renderer?" } + ], + "Astro": [ + { "name": "astro-paper", "repo": "https://github.com/satnaing/astro-paper", "size": "Small", "files": "~175", "question": "How does a blog post's frontmatter data flow from the content collection to the SEO and layout components?" }, + { "name": "astrowind", "repo": "https://github.com/onwidget/astrowind", "size": "Medium", "files": "~176", "question": "How does the Hero component receive and pass its props down to child components?" }, + { "name": "astro-docs", "repo": "https://github.com/withastro/docs", "size": "Large", "files": "~3191", "question": "How does the navigation sidebar get populated from the content collections and page metadata?" } ] } diff --git a/CHANGELOG.md b/CHANGELOG.md index 09fbad95a..895f81211 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### New Features +- CodeGraph now indexes **Astro** (`.astro`) — components, functions, interfaces, and imports from the frontmatter block, plus component reference edges from the template. Works with `astro-paper`, `astrowind`, and the official Astro docs. - `codegraph init` now builds the initial index by default — you no longer need the `-i`/`--index` flag (it's still accepted, so existing commands and scripts keep working). (#483) - Go: Gin middleware chains now connect end-to-end in `codegraph_trace` and `codegraph_explore` — following a request reaches the middleware and route handlers registered via `.Use()` / `.GET()` instead of dead-ending where the framework dispatches the chain dynamically. - `codegraph_explore` now sizes its response to the *answer* instead of the file count: it shows the mechanism and the exact methods you asked about in full — even when they're buried deep in a large file — while collapsing the redundant interchangeable implementations of an interface (an HTTP interceptor chain, a query-compiler family) down to signatures. Fewer tokens for a more complete answer, so on the flows that used to occasionally cost more than plain grep/read it's now clearly cheaper — and the win holds across small, medium, and large codebases. Distinct, non-interchangeable code is shown in full as before. Disable with `CODEGRAPH_ADAPTIVE_EXPLORE=0`. diff --git a/README.md b/README.md index acdb726bf..4240d4f7e 100644 --- a/README.md +++ b/README.md @@ -204,7 +204,7 @@ CodeGraph cuts **cost, tokens, tool calls, and time on every repo** — across s | **Full-Text Search** | Find code by name instantly across your entire codebase, powered by FTS5 | | **Impact Analysis** | Trace callers, callees, and the full impact radius of any symbol before making changes | | **Always Fresh** | File watcher uses native OS events (FSEvents/inotify/ReadDirectoryChangesW) with debounced auto-sync — the graph stays current as you code, zero config | -| **20+ Languages** | TypeScript, JavaScript, Python, Go, Rust, Java, C#, PHP, Ruby, C, C++, Objective-C, Swift, Kotlin, Dart, Lua, Luau, Svelte, Liquid, Pascal/Delphi | +| **21+ Languages** | TypeScript, JavaScript, Python, Go, Rust, Java, C#, PHP, Ruby, C, C++, Objective-C, Swift, Kotlin, Dart, Lua, Luau, Astro, Svelte, Liquid, Pascal/Delphi | | **Framework-aware Routes** | Recognizes web-framework routing files and links URL patterns to their handlers across 14 frameworks | | **Mixed iOS / React Native / Expo** | Closes cross-language flows that static parsing misses: Swift ↔ ObjC bridging, React Native legacy bridge + TurboModules + Fabric view components, native → JS event emitters, Expo Modules | | **100% Local** | No data leaves your machine. No API keys. No external services. SQLite database only | @@ -592,6 +592,7 @@ is written): | Kotlin | `.kt`, `.kts` | Full support | | Scala | `.scala`, `.sc` | Full support (classes, traits, methods, type aliases, Scala 3 enums) | | Dart | `.dart` | Full support | +| Astro | `.astro` | Full support (frontmatter TypeScript extraction, component nodes, template call/component edges) | | Svelte | `.svelte` | Full support (script extraction, Svelte 5 runes, SvelteKit routes) | | Vue | `.vue` | Full support (script + script-setup extraction, Nuxt page/API/middleware routes) | | Liquid | `.liquid` | Full support | diff --git a/__tests__/extraction.test.ts b/__tests__/extraction.test.ts index b497af6a9..92f70b32c 100644 --- a/__tests__/extraction.test.ts +++ b/__tests__/extraction.test.ts @@ -4387,3 +4387,120 @@ void helperFunction(int count) { expect(getSupportedLanguages()).toContain('objc'); }); }); + +describe('Astro Extraction', () => { + it('should detect Astro files', () => { + expect(detectLanguage('src/pages/index.astro')).toBe('astro'); + expect(detectLanguage('components/Header.astro')).toBe('astro'); + expect(isLanguageSupported('astro')).toBe(true); + expect(getSupportedLanguages()).toContain('astro'); + }); + + it('should extract a component node for every .astro file', () => { + const code = `--- +const { title } = Astro.props; +--- +

{title}

+`; + const result = extractFromSource('Heading.astro', code); + const componentNode = result.nodes.find((n) => n.kind === 'component'); + expect(componentNode).toBeDefined(); + expect(componentNode?.name).toBe('Heading'); + expect(componentNode?.language).toBe('astro'); + expect(componentNode?.isExported).toBe(true); + }); + + it('should extract functions from the frontmatter block', () => { + const code = `--- +import Layout from './Layout.astro'; + +interface Props { + title: string; +} + +function formatDate(date: Date): string { + return date.toLocaleDateString(); +} + +const { title } = Astro.props; +--- + +

{formatDate(new Date())}

+
+`; + const result = extractFromSource('Page.astro', code); + + const componentNode = result.nodes.find((n) => n.kind === 'component'); + expect(componentNode).toBeDefined(); + expect(componentNode?.name).toBe('Page'); + + const funcNode = result.nodes.find((n) => n.kind === 'function' && n.name === 'formatDate'); + expect(funcNode).toBeDefined(); + expect(funcNode?.language).toBe('astro'); + }); + + it('should extract imports from the frontmatter block', () => { + const code = `--- +import Header from '../components/Header.astro'; +import { getCollection } from 'astro:content'; +--- +
+`; + const result = extractFromSource('Blog.astro', code); + + const importNodes = result.nodes.filter((n) => n.kind === 'import'); + expect(importNodes.length).toBeGreaterThanOrEqual(1); + + const refs = result.unresolvedReferences.filter((r) => r.referenceKind === 'imports'); + expect(refs.length).toBeGreaterThanOrEqual(1); + }); + + it('should capture child component references from the template', () => { + const code = `--- +import Header from './Header.astro'; +import Footer from './Footer.astro'; +--- + + +
+
+