feat: cross-namespace refs — materialized ccg:// links, federated reads, cross-repo analysis#31
Merged
Merged
Conversation
Pull the node-matching query into ccgRefNodeQuery so lint validation and the upcoming cross-ref materialization judge ccg:// targets identically. No behavior change. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add a cross_refs table (migration 000015, SQLite and PostgreSQL) that stores every @see ccg://{namespace}/{path}#{symbol} annotation tag as a queryable row with a symbolic target plus derived resolution state (resolved_node_id, status resolved|dead, source annotation). A new crossref.Service syncs the table after every ingest commit: outbound rows are rebuilt from the namespace's current doc tags and inbound rows are re-resolved because replace-style builds regenerate node ids. The sync hook is optional on workflow.Service and wired in CLI build/update, the MCP build tool, and the webhook server runtime. Resolution reuses the matcher shared with lint (ccgRefNodeExists) so dead-ref findings and cross_refs status never disagree. Namespace-scope refs resolve without a node target; path-scope refs prefer the file node; malformed refs are skipped (lint owns reporting). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… across namespaces Add an optional 'namespaces' array parameter to the four read tools. Federation fans out per namespace at the handler level so every store query path keeps its single-namespace invariant: - search merges per-namespace candidate pools, reranks once, and labels each item with its namespace (single-namespace responses are byte-identical to before). - query_graph runs the extracted per-namespace body for each namespace and groups outcomes; a missing target in one namespace becomes a per-namespace error instead of failing the call. - list_graph_stats and search_docs return per-namespace groups without merging counts across unrelated graphs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add a namespace-agnostic CrossNamespaceReader that reads nodes and edges by globally-unique id and merges resolved cross_refs rows into traversal as synthetic cross_ref edges. Existing BFS and flow-trace algorithms run unchanged on top of it. - get_impact_radius and trace_flow accept cross_namespace: true and traverse resolved ccg:// refs in both directions; impact results carry a namespace label in cross mode only. - New list_cross_refs tool exposes the repository-level dependency map (direction outbound/inbound/both, optional status filter). MCP tool count goes from 17 to 18. - The synthetic cross_ref edge kind is never persisted; the flow tracer accepts it as a continuation because regular stores never emit it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…refs Update English and Korean guides (mcp-tools, annotations, architecture, counts in README/development/runtime-layout), CLAUDE.md, and the ccg-analyze / ccg-namespace skills for the new cross-namespace surface. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Remove leftover // test comment after the import block in internal/app/analyze/ports.go that failed gofmt's blank-line separation. Comment-only change; gofmt now passes and internal/app/analyze tests are green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The multi-owner collision guard added in 5404e24 rejects wildcard owners in --allow-repo, so the integration compose file's '*/*' rule made ccg-server refuse to start and the suite fail before any test phase. Pin the rule to the suite's Gitea admin account (testadmin/*) and ignore the suite's default artifacts/ output directory. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add Phase 13: push a ccg:// annotation from sample-go targeting sample-calc through the real Gitea webhook pipeline, then assert list_cross_refs (outbound resolved + inbound), cross-namespace impact radius with namespace labels, default namespace-scoped impact, and federated search labeling — all against PostgreSQL. The phase polls list_cross_refs directly because wait_for_webhook_sync returns as soon as a namespace has any nodes, which is immediate on a re-push. Banner updated to 16/18 verified tools. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The caller's limit was applied to the globally reranked merged pool, so one namespace with high-scoring hits could push every other namespace out of the response entirely. Rank globally without truncation, then select with a per-namespace quota (limit/namespaces, minimum one) and fill the remaining slots in global rank order. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Flow memberships were stamped with the start namespace, so members reached through a cross_ref edge carried an unusable label and clients could not resolve foreign node ids. The tracer now batch-loads member nodes when the reader supports it and stamps each membership with the node's stored namespace; the MCP response exposes it only in cross-namespace mode, keeping single-namespace payloads unchanged. The persisted-flow rebuild path is unaffected because FlowRebuildStore has no batch node read. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Turns annotation-declared
ccg://references into a first-class cross-repository (cross-namespace) system, in three layers:cross_refstable (migration 000015, SQLite + PostgreSQL). Every@see ccg://{namespace}/{path}#{symbol}tag becomes a row with a symbolic target plus derived state (resolved_node_id,status: resolved|dead,source). A sync hook after every build/update rebuilds outbound rows and re-resolves inbound rows, so links survive replace-style rebuilds (webhookReplace: trueregenerates node ids), godeadwhen the target symbol disappears, and recover when it returns. Resolution shares one matcher withccg lint, sodead-reffindings andcross_refsstatus can never disagree.search,query_graph,list_graph_stats,search_docsacceptnamespaces: []and fan out per namespace at the handler layer. Results are labeled per namespace; single-namespace responses are byte-identical to before. Store query paths keep their single-namespace invariant.get_impact_radius/trace_flowacceptcross_namespace: true: a namespace-agnostic reader merges resolved refs into traversal as syntheticcross_refedges (never persisted), so the existing BFS/trace algorithms run unchanged across repo boundaries. Newlist_cross_refstool exposes the repository-level dependency map (tool count 17 → 18).Also fixes the Postgres integration suite, broken on main since 5404e24: the compose file's
--allow-repo=*/*is rejected by that commit's multi-owner collision guard, soccg-serverrefused to start. Pinned totestadmin/*(the suite's Gitea admin).Why
Namespaces fully isolate per-repository graphs; the only cross-namespace primitive was
list_namespaces. Cross-repo dependencies declared via@see ccg://were plain tag text — not queryable, not traversable, no reverse lookup. Annotation refs were chosen over import-based linking because they are language-agnostic (polyglot service boundaries have no imports) and reuse the existing parser/lint/wiki infrastructure. Thesourcecolumn leaves room for future import-derived signals without schema rework.Reviewer notes
Commits are split Tidy First: one structural refactor (shared ref matcher extraction), then three behavioral commits (materialization → federated reads → analysis), docs, and the integration fix.
cross_refsis derived state: fully rebuildable from annotations, deleted/re-created per source namespace inside one transaction. No raw SQL outside migration files; both backends get the same 000015.The synthetic
cross_refedge kind never reaches theedgestable; the flow tracer accepts it as a continuation only because regular stores never emit it.query_graphfederation isolates per-namespacenode not foundas a per-namespace error entry instead of failing the whole call;file_summarystill returns a zero summary for missing files (pre-existing behavior).The integration suite gained Phase 13: a real webhook push of a
ccg://annotation, thenlist_cross_refs(outbound/inbound),cross_namespace: trueimpact with namespace labels, default-scoped impact, and federated search — all verified against PostgreSQL. Banner now reports 16/18.Known tradeoff (reviewed, deliberately kept): cross-ref sync runs post-commit and fails hard. A sync failure after a committed build makes
build_or_update_graphreport total failure (skipping FTS/flow postprocess and cache flush) and skips webhook cache invalidation. Accepted because the failure is rare, cross_refs is self-healing derived state (webhook retry / cache TTL / next build), and softening it would change the Build/Update result contract late in the PR. Revisit if sync failures are observed in practice.Verification
CGO_ENABLED=1 go test -tags "fts5" ./... -count=1— all 43 packages green. Includes an end-to-end test: Go source comment@see ccg://…→ tree-sitter build → resolvedcross_refsrow → target rebuild → id remap / dead / revive../scripts/integration-test.sh(Gitea + PostgreSQL + ccg Docker) — PASSED after the allow-repo fix: webhook push → clone → build → Postgres store pipeline, migrations incl. 000015, MCP tool phases.🤖 Generated with Claude Code