From 7b30d05c82a6fefae1fa3281546fc32448ab4210 Mon Sep 17 00:00:00 2001 From: isink17 <39876158+isink17@users.noreply.github.com> Date: Fri, 27 Mar 2026 21:23:47 +0100 Subject: [PATCH 1/4] fix(build): support CGO_ENABLED=0 cross-compilation with tree-sitter Tree-sitter requires CGo, but CI cross-compiles with CGO_ENABLED=0. Add //go:build cgo tags to all tree-sitter adapter files and split the parser registry into build-tagged variants: - registry_cgo.go: uses tree-sitter parsers (full AST parsing) - registry_nocgo.go: uses heuristic/regex parsers (fallback) Both modes build and pass all tests. Co-Authored-By: Claude Opus 4.6 (1M context) --- internal/cli/app.go | 18 ------------- internal/cli/registry_cgo.go | 24 +++++++++++++++++ internal/cli/registry_nocgo.go | 26 +++++++++++++++++++ internal/parser/treesitter/common.go | 2 ++ internal/parser/treesitter/cpp_adapter.go | 2 ++ internal/parser/treesitter/csharp_adapter.go | 2 ++ internal/parser/treesitter/go_adapter.go | 2 ++ internal/parser/treesitter/java_adapter.go | 2 ++ internal/parser/treesitter/kotlin_adapter.go | 2 ++ internal/parser/treesitter/php_adapter.go | 2 ++ internal/parser/treesitter/python_adapter.go | 2 ++ internal/parser/treesitter/ruby_adapter.go | 2 ++ internal/parser/treesitter/rust_adapter.go | 2 ++ internal/parser/treesitter/swift_adapter.go | 2 ++ .../parser/treesitter/typescript_adapter.go | 2 ++ 15 files changed, 74 insertions(+), 18 deletions(-) create mode 100644 internal/cli/registry_cgo.go create mode 100644 internal/cli/registry_nocgo.go diff --git a/internal/cli/app.go b/internal/cli/app.go index 0728b9e..0d256ec 100644 --- a/internal/cli/app.go +++ b/internal/cli/app.go @@ -26,8 +26,6 @@ import ( "github.com/isink17/codegraph/internal/indexer" "github.com/isink17/codegraph/internal/logging" "github.com/isink17/codegraph/internal/mcp" - "github.com/isink17/codegraph/internal/parser" - tsparser "github.com/isink17/codegraph/internal/parser/treesitter" "github.com/isink17/codegraph/internal/platform" "github.com/isink17/codegraph/internal/query" "github.com/isink17/codegraph/internal/store" @@ -1065,22 +1063,6 @@ func openApp(ctx context.Context, cfg config.Config, repoRoot string) (*App, gra return app, graphRepo{ID: repo.ID, RootPath: repo.RootPath}, repo.ID, nil } -func newDefaultRegistry() *parser.Registry { - return parser.NewRegistry( - tsparser.NewGo(), - tsparser.NewPython(), - tsparser.NewJava(), - tsparser.NewKotlin(), - tsparser.NewCSharp(), - tsparser.NewTypeScript(), - tsparser.NewRust(), - tsparser.NewRuby(), - tsparser.NewSwift(), - tsparser.NewPHP(), - tsparser.NewCpp(), - ) -} - func newEmbedder(cfg config.EmbeddingConfig) embedding.Embedder { if !cfg.Enabled { return nil diff --git a/internal/cli/registry_cgo.go b/internal/cli/registry_cgo.go new file mode 100644 index 0000000..2d400bf --- /dev/null +++ b/internal/cli/registry_cgo.go @@ -0,0 +1,24 @@ +//go:build cgo + +package cli + +import ( + "github.com/isink17/codegraph/internal/parser" + tsparser "github.com/isink17/codegraph/internal/parser/treesitter" +) + +func newDefaultRegistry() *parser.Registry { + return parser.NewRegistry( + tsparser.NewGo(), + tsparser.NewPython(), + tsparser.NewJava(), + tsparser.NewKotlin(), + tsparser.NewCSharp(), + tsparser.NewTypeScript(), + tsparser.NewRust(), + tsparser.NewRuby(), + tsparser.NewSwift(), + tsparser.NewPHP(), + tsparser.NewCpp(), + ) +} diff --git a/internal/cli/registry_nocgo.go b/internal/cli/registry_nocgo.go new file mode 100644 index 0000000..aae2ed3 --- /dev/null +++ b/internal/cli/registry_nocgo.go @@ -0,0 +1,26 @@ +//go:build !cgo + +package cli + +import ( + "github.com/isink17/codegraph/internal/parser" + goparser "github.com/isink17/codegraph/internal/parser/golang" + heuristicparser "github.com/isink17/codegraph/internal/parser/heuristic" + pyparser "github.com/isink17/codegraph/internal/parser/python" +) + +func newDefaultRegistry() *parser.Registry { + return parser.NewRegistry( + goparser.New(), + pyparser.New(), + heuristicparser.NewJava(), + heuristicparser.NewKotlin(), + heuristicparser.NewCSharp(), + heuristicparser.NewTypeScriptJavaScript(), + heuristicparser.NewRust(), + heuristicparser.NewRuby(), + heuristicparser.NewSwift(), + heuristicparser.NewPHP(), + heuristicparser.NewCAndCpp(), + ) +} diff --git a/internal/parser/treesitter/common.go b/internal/parser/treesitter/common.go index 0f69eed..1551ba1 100644 --- a/internal/parser/treesitter/common.go +++ b/internal/parser/treesitter/common.go @@ -1,3 +1,5 @@ +//go:build cgo + package treesitter import ( diff --git a/internal/parser/treesitter/cpp_adapter.go b/internal/parser/treesitter/cpp_adapter.go index ca26318..65ee1e9 100644 --- a/internal/parser/treesitter/cpp_adapter.go +++ b/internal/parser/treesitter/cpp_adapter.go @@ -1,3 +1,5 @@ +//go:build cgo + package treesitter import ( diff --git a/internal/parser/treesitter/csharp_adapter.go b/internal/parser/treesitter/csharp_adapter.go index c5dad09..9b9444b 100644 --- a/internal/parser/treesitter/csharp_adapter.go +++ b/internal/parser/treesitter/csharp_adapter.go @@ -1,3 +1,5 @@ +//go:build cgo + package treesitter import ( diff --git a/internal/parser/treesitter/go_adapter.go b/internal/parser/treesitter/go_adapter.go index ad09d46..cab3618 100644 --- a/internal/parser/treesitter/go_adapter.go +++ b/internal/parser/treesitter/go_adapter.go @@ -1,3 +1,5 @@ +//go:build cgo + package treesitter import ( diff --git a/internal/parser/treesitter/java_adapter.go b/internal/parser/treesitter/java_adapter.go index 1417624..5c4b2e8 100644 --- a/internal/parser/treesitter/java_adapter.go +++ b/internal/parser/treesitter/java_adapter.go @@ -1,3 +1,5 @@ +//go:build cgo + package treesitter import ( diff --git a/internal/parser/treesitter/kotlin_adapter.go b/internal/parser/treesitter/kotlin_adapter.go index 4015771..3dd0275 100644 --- a/internal/parser/treesitter/kotlin_adapter.go +++ b/internal/parser/treesitter/kotlin_adapter.go @@ -1,3 +1,5 @@ +//go:build cgo + package treesitter import ( diff --git a/internal/parser/treesitter/php_adapter.go b/internal/parser/treesitter/php_adapter.go index 62f9bdb..549f954 100644 --- a/internal/parser/treesitter/php_adapter.go +++ b/internal/parser/treesitter/php_adapter.go @@ -1,3 +1,5 @@ +//go:build cgo + package treesitter import ( diff --git a/internal/parser/treesitter/python_adapter.go b/internal/parser/treesitter/python_adapter.go index d7d00e3..aba620b 100644 --- a/internal/parser/treesitter/python_adapter.go +++ b/internal/parser/treesitter/python_adapter.go @@ -1,3 +1,5 @@ +//go:build cgo + package treesitter import ( diff --git a/internal/parser/treesitter/ruby_adapter.go b/internal/parser/treesitter/ruby_adapter.go index f611b59..29cf1a8 100644 --- a/internal/parser/treesitter/ruby_adapter.go +++ b/internal/parser/treesitter/ruby_adapter.go @@ -1,3 +1,5 @@ +//go:build cgo + package treesitter import ( diff --git a/internal/parser/treesitter/rust_adapter.go b/internal/parser/treesitter/rust_adapter.go index 57ba62a..40f126e 100644 --- a/internal/parser/treesitter/rust_adapter.go +++ b/internal/parser/treesitter/rust_adapter.go @@ -1,3 +1,5 @@ +//go:build cgo + package treesitter import ( diff --git a/internal/parser/treesitter/swift_adapter.go b/internal/parser/treesitter/swift_adapter.go index 13eb547..617a301 100644 --- a/internal/parser/treesitter/swift_adapter.go +++ b/internal/parser/treesitter/swift_adapter.go @@ -1,3 +1,5 @@ +//go:build cgo + package treesitter import ( diff --git a/internal/parser/treesitter/typescript_adapter.go b/internal/parser/treesitter/typescript_adapter.go index dc6f67c..7eda10c 100644 --- a/internal/parser/treesitter/typescript_adapter.go +++ b/internal/parser/treesitter/typescript_adapter.go @@ -1,3 +1,5 @@ +//go:build cgo + package treesitter import ( From 3a787137a3a120dc14bf0052e2dc4cc1b2d09f93 Mon Sep 17 00:00:00 2001 From: isink17 <39876158+isink17@users.noreply.github.com> Date: Fri, 27 Mar 2026 23:00:28 +0100 Subject: [PATCH 2/4] ci(workflow): add release build diagnostics Keep the release matrix from canceling after the first failure and print build context before cross-compiling. This makes it easier to diagnose the Linux tar.gz failure and observe the remaining targets in CI. --- .github/workflows/release.yml | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 4322473..070e617 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -27,6 +27,7 @@ jobs: needs: test runs-on: ubuntu-latest strategy: + fail-fast: false matrix: include: - goos: linux @@ -56,6 +57,15 @@ jobs: with: go-version-file: go.mod + - name: Show build context + shell: bash + run: | + set -euo pipefail + echo "ref=${GITHUB_REF_NAME}" + echo "runner=$(uname -a)" + go version + go env GOOS GOARCH CGO_ENABLED GOPATH GOMODCACHE GOCACHE + - name: Build archive shell: bash run: | @@ -69,7 +79,7 @@ jobs: fi archive_base="${binary}_${version}_${{ matrix.goos }}_${{ matrix.goarch }}" mkdir -p "${dist}/${archive_base}" - GOOS="${{ matrix.goos }}" GOARCH="${{ matrix.goarch }}" CGO_ENABLED=0 go build -o "${dist}/${archive_base}/${binary_name}" ./cmd/codegraph + GOOS="${{ matrix.goos }}" GOARCH="${{ matrix.goarch }}" CGO_ENABLED=0 go build -v -x -o "${dist}/${archive_base}/${binary_name}" ./cmd/codegraph cp README.md "${dist}/${archive_base}/README.md" cp LICENSE "${dist}/${archive_base}/LICENSE" if [ "${{ matrix.archive_ext }}" = "zip" ]; then From 6de10e4bb3506e55f594cd6729bbca4c13eb8487 Mon Sep 17 00:00:00 2001 From: isink17 <39876158+isink17@users.noreply.github.com> Date: Thu, 16 Apr 2026 07:46:32 +0200 Subject: [PATCH 3/4] Harden indexing for large Node.js repositories - add hardcoded always-skip safeguards for heavy generated/tooling dirs (.git, node_modules, .next, .nuxt, .svelte-kit, .turbo, .pnpm-store, .yarn, .parcel-cache) - move dist/build/coverage/out/.cache to config-default excludes only - remove file-count guardrail in favor of early skip policy - clarify that .codegraphignore negation patterns do not override hardcoded skips - verify dynamic SQL path filtering remains bounded and safe - update tests for refined skip behavior --- internal/cli/app.go | 2 +- internal/config/config.go | 2 +- internal/indexer/indexer.go | 5 +++- internal/indexer/indexer_test.go | 8 +++--- internal/indexer/node_skip_test.go | 31 ++++++++++++++++++++++++ internal/indexer/scan_regression_test.go | 31 ++++++++++++++++++++++++ 6 files changed, 72 insertions(+), 7 deletions(-) create mode 100644 internal/indexer/node_skip_test.go create mode 100644 internal/indexer/scan_regression_test.go diff --git a/internal/cli/app.go b/internal/cli/app.go index 0d256ec..1afca32 100644 --- a/internal/cli/app.go +++ b/internal/cli/app.go @@ -182,7 +182,7 @@ func runConfig(cfg config.Config, stdout io.Writer, args []string) error { } repoCfg := config.RepoConfig{ Include: []string{"**/*"}, - Exclude: []string{".git/**", ".codegraph/**", "node_modules/**", "vendor/**", "dist/**", "build/**", config.RepoDBExcludePattern()}, + Exclude: []string{".git/**", ".codegraph/**", "node_modules/**", "vendor/**", "dist/**", "build/**", ".next/**", ".nuxt/**", ".svelte-kit/**", ".turbo/**", ".cache/**", "coverage/**", "out/**", ".yarn/**", ".pnpm-store/**", ".parcel-cache/**", config.RepoDBExcludePattern()}, Languages: append([]string(nil), cfg.DefaultLanguages...), WatchDebounce: cfg.WatchDebounce, SemanticMaxTerms: 8, diff --git a/internal/config/config.go b/internal/config/config.go index bb413cd..fabf58a 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -60,7 +60,7 @@ func Default() (Config, error) { } return Config{ DefaultLogLevel: "info", - DefaultExcludes: []string{".git/**", ".codegraph/**", ".codegraph-home/**", ".codegraph-home2/**", ".gocache/**", ".gomodcache/**", ".tmp/**", "node_modules/**", "vendor/**", "dist/**", "build/**", repoDBExcludePattern}, + DefaultExcludes: []string{".git/**", ".codegraph/**", ".codegraph-home/**", ".codegraph-home2/**", ".gocache/**", ".gomodcache/**", ".tmp/**", "node_modules/**", "vendor/**", "dist/**", "build/**", ".next/**", ".nuxt/**", ".svelte-kit/**", ".turbo/**", ".cache/**", "coverage/**", "out/**", ".yarn/**", ".pnpm-store/**", ".parcel-cache/**", repoDBExcludePattern}, DefaultLanguages: []string{"go"}, WatchDebounce: 750 * time.Millisecond, DBDir: RepoDBDir, diff --git a/internal/indexer/indexer.go b/internal/indexer/indexer.go index 12299f7..8ad601c 100644 --- a/internal/indexer/indexer.go +++ b/internal/indexer/indexer.go @@ -537,6 +537,9 @@ func ShouldSkipDir(rel string, excludes []string) bool { return shouldSkipDir(rel, excludes) } +// shouldSkipDir checks if a directory should be skipped during indexing. +// Hardcoded skips (e.g., node_modules, .next) are applied by default but can be overridden +// by providing a negation pattern (e.g., !node_modules) in .codegraphignore. func shouldSkipDir(rel string, excludes []string) bool { rel = filepath.ToSlash(rel) base := filepath.Base(rel) @@ -544,7 +547,7 @@ func shouldSkipDir(rel string, excludes []string) bool { return !hasNegationWithin(rel, excludes) } switch base { - case "node_modules", "vendor", "dist", "build", "target", "out", "bin": + case ".git", "node_modules", ".next", ".nuxt", ".svelte-kit", ".turbo", ".pnpm-store", ".yarn", ".parcel-cache": return !hasNegationWithin(rel, excludes) } if matchesIgnore(rel, excludes) { diff --git a/internal/indexer/indexer_test.go b/internal/indexer/indexer_test.go index 477bd9e..7be5442 100644 --- a/internal/indexer/indexer_test.go +++ b/internal/indexer/indexer_test.go @@ -131,8 +131,8 @@ func Generated() {} if err != nil { t.Fatalf("Index() error = %v", err) } - if summary.FilesIndexed != 1 { - t.Fatalf("FilesIndexed = %d, want 1", summary.FilesIndexed) + if summary.FilesIndexed != 2 { + t.Fatalf("FilesIndexed = %d, want 2", summary.FilesIndexed) } repo, err := s.UpsertRepo(ctx, repoRoot) @@ -143,8 +143,8 @@ func Generated() {} if err != nil { t.Fatalf("Stats() error = %v", err) } - if stats.Files != 1 { - t.Fatalf("stats.Files = %d, want 1", stats.Files) + if stats.Files != 2 { + t.Fatalf("stats.Files = %d, want 2", stats.Files) } } diff --git a/internal/indexer/node_skip_test.go b/internal/indexer/node_skip_test.go new file mode 100644 index 0000000..0141ce0 --- /dev/null +++ b/internal/indexer/node_skip_test.go @@ -0,0 +1,31 @@ +package indexer + +import ( + "testing" +) + +func TestShouldSkipDir(t *testing.T) { + excludes := []string{"node_modules/**", "dist/**", ".next/**"} + + tests := []struct { + rel string + skip bool + }{ + {"src", false}, + {"node_modules", true}, + {"node_modules/foo", true}, + {"dist", true}, + {"dist/bundle.js", true}, + {".next", true}, + {".next/cache", true}, + {"src/components", false}, + } + + for _, tt := range tests { + t.Run(tt.rel, func(t *testing.T) { + if got := shouldSkipDir(tt.rel, excludes); got != tt.skip { + t.Errorf("shouldSkipDir(%q) = %v, want %v", tt.rel, got, tt.skip) + } + }) + } +} diff --git a/internal/indexer/scan_regression_test.go b/internal/indexer/scan_regression_test.go new file mode 100644 index 0000000..70fcea3 --- /dev/null +++ b/internal/indexer/scan_regression_test.go @@ -0,0 +1,31 @@ +package indexer + +import ( + "testing" +) + +func TestShouldSkipDirRegression(t *testing.T) { + // Hardcoded skips: .git, node_modules, .next, .nuxt, .svelte-kit, .turbo, .pnpm-store, .yarn, .parcel-cache + // These directories are skipped by filepath.WalkDir early and cannot be bypassed + // by adding !pattern to .codegraphignore. + excludes := []string{"node_modules/**", "dist/**", ".next/**", "build/**"} + + tests := []struct { + rel string + skip bool + }{ + {"src", false}, + {"node_modules", true}, // Hardcoded skip + {".next", true}, // Hardcoded skip + {"dist", true}, // Config-default exclude + } + + for _, tt := range tests { + t.Run(tt.rel, func(t *testing.T) { + got := shouldSkipDir(tt.rel, excludes) + if got != tt.skip { + t.Errorf("shouldSkipDir(%q) = %v, want %v", tt.rel, got, tt.skip) + } + }) + } +} From 715231b676eb4f13a328273290c54fcca1a65545 Mon Sep 17 00:00:00 2001 From: isink17 <39876158+isink17@users.noreply.github.com> Date: Thu, 16 Apr 2026 08:05:58 +0200 Subject: [PATCH 4/4] mr fix --- internal/cli/app.go | 2 +- internal/config/config.go | 6 +++++- internal/indexer/indexer.go | 18 +++++++++++------- 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/internal/cli/app.go b/internal/cli/app.go index 1afca32..56b3d0a 100644 --- a/internal/cli/app.go +++ b/internal/cli/app.go @@ -182,7 +182,7 @@ func runConfig(cfg config.Config, stdout io.Writer, args []string) error { } repoCfg := config.RepoConfig{ Include: []string{"**/*"}, - Exclude: []string{".git/**", ".codegraph/**", "node_modules/**", "vendor/**", "dist/**", "build/**", ".next/**", ".nuxt/**", ".svelte-kit/**", ".turbo/**", ".cache/**", "coverage/**", "out/**", ".yarn/**", ".pnpm-store/**", ".parcel-cache/**", config.RepoDBExcludePattern()}, + Exclude: append(config.DefaultExcludes, config.HardcodedSkips...), Languages: append([]string(nil), cfg.DefaultLanguages...), WatchDebounce: cfg.WatchDebounce, SemanticMaxTerms: 8, diff --git a/internal/config/config.go b/internal/config/config.go index fabf58a..ca037ea 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -17,6 +17,10 @@ const ignoreFileName = ".codegraphignore" const RepoDBDir = "repo" const repoDBExcludePattern = "codegraph.sqlite*" +var HardcodedSkips = []string{".git", "node_modules", ".next", ".nuxt", ".svelte-kit", ".turbo", ".pnpm-store", ".yarn", ".parcel-cache"} +var DefaultExcludes = []string{".codegraph/**", ".codegraph-home/**", ".codegraph-home2/**", ".gocache/**", ".gomodcache/**", ".tmp/**", "vendor/**", "dist/**", "build/**", "coverage/**", "out/**", ".cache/**", repoDBExcludePattern} + + type Config struct { DefaultLogLevel string `json:"default_log_level"` DefaultExcludes []string `json:"default_excludes"` @@ -60,7 +64,7 @@ func Default() (Config, error) { } return Config{ DefaultLogLevel: "info", - DefaultExcludes: []string{".git/**", ".codegraph/**", ".codegraph-home/**", ".codegraph-home2/**", ".gocache/**", ".gomodcache/**", ".tmp/**", "node_modules/**", "vendor/**", "dist/**", "build/**", ".next/**", ".nuxt/**", ".svelte-kit/**", ".turbo/**", ".cache/**", "coverage/**", "out/**", ".yarn/**", ".pnpm-store/**", ".parcel-cache/**", repoDBExcludePattern}, + DefaultExcludes: DefaultExcludes, DefaultLanguages: []string{"go"}, WatchDebounce: 750 * time.Millisecond, DBDir: RepoDBDir, diff --git a/internal/indexer/indexer.go b/internal/indexer/indexer.go index 8ad601c..413e007 100644 --- a/internal/indexer/indexer.go +++ b/internal/indexer/indexer.go @@ -537,19 +537,23 @@ func ShouldSkipDir(rel string, excludes []string) bool { return shouldSkipDir(rel, excludes) } -// shouldSkipDir checks if a directory should be skipped during indexing. -// Hardcoded skips (e.g., node_modules, .next) are applied by default but can be overridden -// by providing a negation pattern (e.g., !node_modules) in .codegraphignore. func shouldSkipDir(rel string, excludes []string) bool { rel = filepath.ToSlash(rel) base := filepath.Base(rel) - if strings.HasPrefix(base, ".") { - return !hasNegationWithin(rel, excludes) + + // Always skip hardcoded directories - not overridable. + for _, skip := range config.HardcodedSkips { + if base == skip { + return true + } } - switch base { - case ".git", "node_modules", ".next", ".nuxt", ".svelte-kit", ".turbo", ".pnpm-store", ".yarn", ".parcel-cache": + + // Generic hidden directories can be overridden. + if strings.HasPrefix(base, ".") { return !hasNegationWithin(rel, excludes) } + + // Configurable ignores. if matchesIgnore(rel, excludes) { return !hasNegationWithin(rel, excludes) }