Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
98456ad
Remove benchmark/eval harnesses and orphaned langfuse compose
tae2089 Jul 10, 2026
0c2e9a9
ci: run go test in CI
tae2089 Jul 10, 2026
444d0d2
Stop full-namespace scan on every retrieve_docs query
tae2089 Jul 10, 2026
8390f07
Rank search-engine hits above scan supplements in retrieve_docs
tae2089 Jul 10, 2026
dcf4c54
Add pg_trgm fuzzy symbol fallback to Postgres search
tae2089 Jul 10, 2026
a610422
Extract shared path-safety mechanics into internal/safepath
tae2089 Jul 10, 2026
61a01fe
Make pg_trgm fuzzy indexable, precise, and corpus-scoped
tae2089 Jul 10, 2026
72bee1e
Log fallback-scan truncation and drop dead blank-assignment block
tae2089 Jul 10, 2026
448485d
Block git option injection via base ref and handle non-ASCII paths
tae2089 Jul 10, 2026
b332f51
Require bearer auth on /status
tae2089 Jul 10, 2026
2b2d3ae
Confine wiki doc API to the docs subtree, not the working directory
tae2089 Jul 10, 2026
3ab9d09
Map the default namespace to the shared docs root in get_doc_content
tae2089 Jul 10, 2026
a2782ef
Skip dangling fallback edges instead of dropping the whole page
tae2089 Jul 10, 2026
54ef7c5
Enforce namespace ownership on annotation create
tae2089 Jul 10, 2026
8f1ef58
Repair broken and flaky tests exposed by live-Postgres and load runs
tae2089 Jul 10, 2026
b05b3af
Remove dead code and fix gofmt drift
tae2089 Jul 10, 2026
836b701
Only classify test-named functions as test nodes
tae2089 Jul 10, 2026
cb7bcd8
Fix non-transactional incremental update delete protocol
tae2089 Jul 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ jobs:
- name: Run go vet
run: make vet

- name: Run go test
run: CGO_ENABLED=1 go test -tags "fts5" ./... -count=1

- name: Build release binaries
run: make build

Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ pyproject.toml
/web/wiki/dist/
/web/wiki/node_modules/
search-retrieval.md
_workspace/
4 changes: 2 additions & 2 deletions cmd/ccg/main_postgres_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ func TestRunMigrations_PostgresDownRestoresNullableColumns(t *testing.T) {
t.Fatalf("run down migration: %v", err)
}

var version migrateSchemaVersion
var version migration.MigrationSchemaVersion
if err := db.Table("schema_migrations").First(&version).Error; err != nil {
t.Fatalf("load schema version: %v", err)
}
Expand Down Expand Up @@ -190,7 +190,7 @@ func TestRunMigrations_PostgresDownFromVersionThreeDropsPolicyTables(t *testing.
t.Fatalf("run down migration: %v", err)
}

var version migrateSchemaVersion
var version migration.MigrationSchemaVersion
if err := db.Table("schema_migrations").First(&version).Error; err != nil {
t.Fatalf("load schema version: %v", err)
}
Expand Down
178 changes: 0 additions & 178 deletions docker-compose.langfuse.yaml

This file was deleted.

23 changes: 21 additions & 2 deletions internal/analysis/changes/git.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@ func NewExecGitClient() *ExecGitClient {
// @requires repoDir points to a valid git working tree
// @ensures returned file paths are trimmed and exclude blank lines
func (g *ExecGitClient) ChangedFiles(ctx context.Context, repoDir, baseRef string) ([]string, error) {
out, err := runGitLimited(ctx, repoDir, []string{"diff", "--name-only", baseRef})
if err := validateBaseRef(baseRef); err != nil {
return nil, err
}
out, err := runGitLimited(ctx, repoDir, []string{"-c", "core.quotePath=false", "diff", "--name-only", baseRef, "--"})
if err != nil {
return nil, trace.Wrap(err, "git diff --name-only")
}
Expand Down Expand Up @@ -66,7 +69,10 @@ func (g *ExecGitClient) ChangedFiles(ctx context.Context, repoDir, baseRef strin
// @requires repoDir points to a valid git working tree
// @ensures each returned hunk has a file path and inclusive start/end lines
func (g *ExecGitClient) DiffHunks(ctx context.Context, repoDir, baseRef string, paths []string) ([]Hunk, error) {
args := []string{"diff", "-U0", baseRef, "--"}
if err := validateBaseRef(baseRef); err != nil {
return nil, err
}
args := []string{"-c", "core.quotePath=false", "diff", "-U0", baseRef, "--"}
args = append(args, paths...)
out, err := runGitLimited(ctx, repoDir, args)
if err != nil {
Expand Down Expand Up @@ -98,6 +104,19 @@ func (g *ExecGitClient) DiffHunks(ctx context.Context, repoDir, baseRef string,
return hunks, nil
}

// validateBaseRef rejects base revisions that git would parse as command options.
// @intent block git argument injection through the caller-supplied base ref, which sits
// before the "--" separator and would otherwise be interpreted as a diff flag.
func validateBaseRef(baseRef string) error {
if baseRef == "" {
return fmt.Errorf("base ref must not be empty")
}
if strings.HasPrefix(baseRef, "-") {
return fmt.Errorf("invalid base ref %q: must not start with '-'", baseRef)
}
return nil
}

// runGitLimited runs a git subcommand with the default output size cap.
// @intent share a single bounded git invocation helper across diff operations
func runGitLimited(ctx context.Context, repoDir string, args []string) ([]byte, error) {
Expand Down
40 changes: 40 additions & 0 deletions internal/analysis/changes/git_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,46 @@ func TestGitClient_ChangedFiles(t *testing.T) {
}
}

func TestGitClient_RejectsOptionLikeBaseRef(t *testing.T) {
dir := initTestRepo(t)
writeFile(t, dir, "hello.go", "package main\n")
gitCommit(t, dir, "initial")

git := NewExecGitClient()
for _, base := range []string{"--output=/tmp/pwned", "-U9999", "--no-index"} {
if _, err := git.ChangedFiles(context.Background(), dir, base); err == nil {
t.Errorf("ChangedFiles accepted option-like base %q", base)
}
if _, err := git.DiffHunks(context.Background(), dir, base, []string{"hello.go"}); err == nil {
t.Errorf("DiffHunks accepted option-like base %q", base)
}
}
}

func TestGitClient_ChangedFiles_NonASCIIPath(t *testing.T) {
dir := initTestRepo(t)
writeFile(t, dir, "한글.go", "package main\n")
gitCommit(t, dir, "initial")
writeFile(t, dir, "한글.go", "package main\n\nfunc Hello() {}\n")

git := NewExecGitClient()
files, err := git.ChangedFiles(context.Background(), dir, "HEAD")
if err != nil {
t.Fatal(err)
}
if len(files) != 1 || files[0] != "한글.go" {
t.Fatalf("expected unquoted 한글.go, got %v", files)
}

hunks, err := git.DiffHunks(context.Background(), dir, "HEAD", []string{"한글.go"})
if err != nil {
t.Fatal(err)
}
if len(hunks) == 0 || hunks[0].FilePath != "한글.go" {
t.Fatalf("expected hunk for unquoted 한글.go, got %+v", hunks)
}
}

func TestGitClient_DiffHunks(t *testing.T) {
dir := initTestRepo(t)
writeFile(t, dir, "hello.go", "package main\n\nfunc Old() {}\n")
Expand Down
8 changes: 6 additions & 2 deletions internal/analysis/fallback/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,13 +86,17 @@ func (s *Service) FindSuspectsPage(ctx context.Context, opts Options) (Result, e
results := make([]SuspectEdge, 0, len(edges))
for _, edge := range edges {
source, err := s.store.GetNodeByID(ctx, edge.FromNodeID)
if err != nil || source == nil {
if err != nil {
return Result{}, err
}
target, err := s.store.GetNodeByID(ctx, edge.ToNodeID)
if err != nil || target == nil {
if err != nil {
return Result{}, err
}
// A dangling edge (endpoint node missing) must not silently drop the whole page.
if source == nil || target == nil {
continue
}
sourceAnn, err := s.store.GetAnnotation(ctx, source.ID)
if err != nil {
return Result{}, err
Expand Down
38 changes: 38 additions & 0 deletions internal/analysis/fallback/service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,44 @@ func TestSuspectFallbackEdges_FlagsDisjointIntentAndDomainRule(t *testing.T) {
}
}

func TestSuspectFallbackEdges_SkipsEdgeWithMissingEndpointInsteadOfDroppingPage(t *testing.T) {
db := setupFallbackDB(t)
ctx := context.Background()
store := gormstore.New(db)

source := model.Node{QualifiedName: "pkg.A", Kind: model.NodeKindFunction, Name: "A", FilePath: "a.go", StartLine: 1, EndLine: 2, Language: "go"}
target := model.Node{QualifiedName: "pkg.B", Kind: model.NodeKindFunction, Name: "B", FilePath: "b.go", StartLine: 1, EndLine: 2, Language: "go"}
if err := store.UpsertNodes(ctx, []model.Node{source, target}); err != nil {
t.Fatal(err)
}
sourceNode, _ := store.GetNode(ctx, "pkg.A")
targetNode, _ := store.GetNode(ctx, "pkg.B")
if err := store.UpsertEdges(ctx, []model.Edge{
// dangling edge: endpoint node id does not exist
{FromNodeID: 99999, ToNodeID: targetNode.ID, Kind: model.EdgeKindFallbackCalls, Fingerprint: "a-dangling"},
{FromNodeID: sourceNode.ID, ToNodeID: targetNode.ID, Kind: model.EdgeKindFallbackCalls, Fingerprint: "b-valid"},
}); err != nil {
t.Fatal(err)
}
if err := store.UpsertAnnotation(ctx, &model.Annotation{NodeID: sourceNode.ID, Summary: "auth", Tags: []model.DocTag{{Kind: model.TagIntent, Value: "verify credentials", Ordinal: 0}}}); err != nil {
t.Fatal(err)
}
if err := store.UpsertAnnotation(ctx, &model.Annotation{NodeID: targetNode.ID, Summary: "billing", Tags: []model.DocTag{{Kind: model.TagIntent, Value: "render invoice", Ordinal: 0}}}); err != nil {
t.Fatal(err)
}

results, err := New(db, store).FindSuspects(ctx, Options{})
if err != nil {
t.Fatal(err)
}
if len(results) != 1 {
t.Fatalf("dangling edge must be skipped, not drop the whole page: got %d results", len(results))
}
if results[0].Source.QualifiedName != "pkg.A" {
t.Fatalf("expected surviving valid edge, got %+v", results[0])
}
}

func TestSuspectFallbackEdges_IgnoresOverlappingAnnotationContext(t *testing.T) {
db := setupFallbackDB(t)
ctx := context.Background()
Expand Down
2 changes: 1 addition & 1 deletion internal/analysis/flows/flows.go
Original file line number Diff line number Diff line change
Expand Up @@ -143,7 +143,7 @@ func (t *Tracer) TraceFlowBounded(ctx context.Context, startNodeID uint, opts Tr
Truncated: truncated,
MaxNodes: opts.MaxNodes,
ReturnedNodes: len(members),
ContainsFallbackCalls: containsFallbackCalls,
ContainsFallbackCalls: containsFallbackCalls,
FallbackEdgesCount: fallbackEdges,
}, nil
}
2 changes: 1 addition & 1 deletion internal/annotation/normalizer.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ func stripBlockDelimiters(text string, language string) string {
}

// stripPythonDocstringDelimiters removes triple-quote wrappers from a Python docstring body.
// @intent expose the raw docstring text by trying both """ and ''' triple-quote forms.
// @intent expose the raw docstring text by trying both """ and ' triple-quote forms.
func stripPythonDocstringDelimiters(text string) (string, bool) {
for _, quote := range []string{"\"\"\"", "'''"} {
if stripped, ok := stripPythonQuotedString(text, quote); ok {
Expand Down
Loading
Loading