Skip to content

Tooling: revisit repowise integration once its C++ symbol graph is trustworthy #815

Open

Description

@drsnuggles8

Evaluated repowise 0.42.0 (self-hosted, AGPL, pip install repowise) against this repo on 2026-08-15, including a full-history index (all 3,464 commits), health, the web dashboard, and a real coverage ingestion. Decision: not integrating now. Revisit when the C++ symbol graph is trustworthy. This issue holds the evidence so the evaluation is not repeated from scratch.

What works well enough to use today

  • Co-change analysis — the one that paid for the exercise. It surfaced that OloEditor/src/Panels/SceneHierarchyPanel.cpp carries 95 hand-maintained DrawComponent<T> rows + 94 DisplayAddComponentEntry<T> entries and was missing from CLAUDE.md's cross-binding checklist (it was mentioned only in the undo/redo section). Skipping it is a silent failure — the component round-trips through scene YAML and save-games perfectly and is simply invisible in the editor. Checklist since fixed.
  • Code health (CCN / nesting / NLOC) — computed per-function from the AST, so none of the graph problems below apply. It carries its own predictive-validity check, which measured 19/20 lowest-health files had a bug fix in the last 6 months, 3.19× the 30% baseline, after coverage was ingested.
  • Duplication / Extract-Helper plans — concrete line ranges, cheap to verify.

What is not usable on this codebase

  • dead-code / get_dead_code — sampled 40 findings at confidence 1.00 with safe_to_delete: true; 40/40 were false positives, six hand-verified. It reported SAVE_COMPONENT (118 real call sites) as safe to delete, plus every Handle_* MCP tool handler and the entire C# ScriptGlue interop layer.
  • The symbol graph itself — both incomplete and unsound. It misses real edges (a call to a header-declared function attaches to the declaration, orphaning the .cpp definition) and invents false ones (a static TU-local function reported as used_by two files containing zero occurrences of its name).
  • bug_magnets — effectively a 2026-only signal: is_fix is detected from commit messages, and 386 of 430 detected fix-commits are in 2026, when conventional-commit prefixes were adopted. Extending to full history added churn but almost no new fix signal.
  • hotspots — recency-biased; files with 3 commits get flagged at churn percentile ~0.89.

Why not wire it into the agent loop

Of its ten MCP tools, the ones an agent naturally reaches for (get_dead_code, get_context, get_symbol, get_risk) are graph-derived, and the graph fabricates edges. A missing capability makes an agent grep; a confidently-wrong one makes it reason from fiction. Given this repo already has a deep CLAUDE.md, the docs/agent-rules/ corpus, a working MCP diagnostics server and SonarQube, the marginal value is low and the downside is real.

Revisit trigger

Filed upstream with minimal repros; re-check these before re-evaluating:

Re-evaluate when #1601 and #1602 are released, since together they cover the bulk of the false positives. #1600 is cheap for them and would also unblock coverage for our 13 generated .inl files.

If revisited, the shape to aim for

  • Keep it a CLI / dashboard tool invoked deliberately; do not register the MCP server in .mcp.json until the graph is trustworthy.
  • Re-index per-milestone, not continuously — co-change and health go stale slowly.
  • Do not run it in worktrees: .repowise/ is ~390 MB per checkout and auto-seeds into each new worktree, against ~48 GB free on E:.
  • .repowise/ and coverage/ are gitignored as of this issue.

Incidental win, independent of the decision

The evaluation produced a working C++ coverage pipeline: OpenCppCoverage (no instrumented rebuild — it reads the existing Debug PDBs) run over the full 5,811-test suite in six resumable chunks, merged to Cobertura. Baseline 59.6% line coverage (108,713 / 182,316 lines, 1,255 files). Script at scripts/ is not committed yet — worth doing separately if we want this repeatable, since nothing in the repo could answer the coverage question before.

Score

capability: 3
craft: 1
stability: 3
decay: 1
effort: 3
confidence: 0.5
learning: 3
fun: 3
kano: delighter
blocked_by: []
blocks: []
blocked_by_external: ["repowise-dev/repowise#1655 - used_by built from header/impl pairing (open)", "repowise-dev/repowise#1601 member-function call edges (comment 5313639721)"]
scored: 2026-10-15

Rated per issue-scoring · score = confidence × (capability + craft + stability + decay) / effort, derived by the picker.

Scoring notes: decay 1 because waiting is actively cheaper here — the blocker is upstream maturity, so the cost of delay falls rather than rises. confidence 0.5 because delivery depends entirely on fixes outside our control. blocked_by deliberately left empty rather than pointing at cross-repo issues, since the rank renders it literally and never rechecks it.

Activity

  1. added
    toolingTier 1: MCP, dev tooling, codegen, CI, test infra. Freeze-exempt; start-work Tier-1 slot
    on Aug 16, 2026
  2. coderabbitai commented on Aug 16, 2026

    @coderabbitai
    Contributor
    🔗 Related PRs

    #334 - MCP: per-worktree discovery-file isolation + skill auto-attach (#316 … [merged]
    #419 - chore(sonar): fix analysis scope (#411 items 2,4) + code-review rule alignment [merged]
    #678 - feat(mcp): audience-tagged content blocks + Tier 3 runtime-endpoint ADR (#673) [merged]
    #788 - docs+workflow: halve CLAUDE.md, index agent-rules by failure mode, batch the PR loop, add a build mutex [merged]
    #790 - feat(mcp): dual-era outbound client + a logging-deprecation offramp carrier (#777) [merged]


    🧪 Issue enrichment is currently in open beta.

    To disable automatic issue enrichment, add the following to your .coderabbit.yaml:

    issue_enrichment:
      auto_enrich:
        enabled: false

    💬 Have feedback or questions? Drop into our discord!

  3. added
    architectureEngine architecture / cross-cutting design
    robustnessProduction hardening / shipping robustness
    on Aug 16, 2026
  4. drsnuggles8 commented on Aug 16, 2026

    @drsnuggles8
    OwnerAuthor

    Looks like master/main fixed most of my initial problems, should revisit it tomorrow

  5. drsnuggles8 commented on Aug 17, 2026

    @drsnuggles8
    OwnerAuthor

    Re-evaluation, 2026-08-17 — still deferred; the revisit trigger needs rewriting

    Re-ran the evaluation against upstream main. Verdict unchanged: do not integrate, do not register the MCP server. The headline false positives from the original evaluation are gone from the output, but they left for the wrong reason, and the bug that actually blocked us is untouched.

    The trigger fired on paper, not in fact

    All three C++ graph issues are fixed and merged, but not released:

    Upstream Fix PR Merged
    #1601 header declaration steals the call edge #1626 2026-08-16 12:20Z
    #1602 function-pointer / macro-argument refs not counted #1627 2026-08-16 12:21Z
    #1600 .inl / .ipp / .tpp not C++ #1625 2026-08-16 12:01Z

    v0.43.0 shipped 2026-08-15 17:50Z — before all three. main is 35 commits ahead of the tag. So this was tested by installing git+https://github.com/repowise-dev/repowise@main into a throwaway venv, not a release. --no-editor-setup now exists (the #1603 opt-out), and it worked: no tracked file was modified.

    Index: 2,665 files, 39,483 symbols, 124,116 edges, 10m27s (--mode fast).

    Sampled false-positive rate: 30/40, down from 40/40

    Random sample (seed 815) of 40 findings with safe_to_delete: true and confidence: 1.00, from a population of 460. Each hand-verified.

    Residual FP class Count
    Cross-file member-function call missed (the #1601 shape) 19
    A type used as a type in its own file (return type, local, template arg, map value) 8
    Function-pointer reference inside a function body 3
    Total false positives 30 / 40 (75%)

    The 10 true positives were all genuine "declared in the header, defined in the .cpp, never called" — e.g. SoundGraphCache::Has, ReadU32Array, four FGenericPlatformMemory platform stubs.

    So #1601 appears fixed for free functions and still broken for member functions, which is where most of our code lives. s_ClientDriver.AttachTo(*client, *scene) in NetworkManager.cpp:556 is engine code, not a test, and is still not counted.

    The improvement is not what it looks like

    The two cases named in the original evaluation — SAVE_COMPONENT and the Handle_* MCP handlers — are gone from the dead-code output. Neither is a real fix:

    • SAVE_COMPONENT appears in 9 files, including SaveGameSerializer.cpp and the generated SaveGameComponentCapture.Generated.inl. repowise reports exactly one used_by: SaveGameSerializer.h, which contains zero occurrences. All 9 real sites are still missed.
    • Handle_AssetsList is used at McpToolsAssets.cpp:157 (tool.Handler = Handle_AssetsList;), in its own file. repowise misses that and reports used_by: McpServer.cpp, which contains zero occurrences.

    Both dropped off the safe_to_delete list because a fabricated edge made them look reachable. The FP rate improved partly by the graph being wrong in the opposite direction, which makes the rate itself a poor measure of progress.

    The blocker was never filed — and it reproduces 9/9

    #815 originally recorded that the graph "invents false ones (a static TU-local function reported as used_by two files containing zero occurrences of its name)." That was never filed upstream; #1600–#1602 are all about missing uses, not invented ones.

    Every used_by edge checked this run is fabricated — 9 for 9, zero correct:

    Symbol claimed used_by occurrences in that file
    VkCheck VulkanRendererAPI.h, VulkanBufferResources.h, VulkanBindingState.h 0, 0, 0
    PerezRow ProceduralSky.h 0
    SingleChannelImage Model.h, ModelImporter.h 0, 0
    WorldStateHash GoapPlanner.h 0
    SAVE_COMPONENT SaveGameSerializer.h 0
    Handle_AssetsList McpServer.cpp 0

    The shape is consistent: a use is attributed to the header paired with the defining .cpp (or to a file that includes it), whether or not the symbol is named there. This is the mirror image of #1601 — that one moved the edge onto the declaration, this one attributes it at file granularity via the include graph rather than by an actual reference.

    This is the bug that made get_context / get_symbol / get_dead_code unsafe for an agent, and it is exactly as bad as it was in August. A missing edge makes an agent grep; a fabricated one makes it reason from fiction.

    Revised revisit trigger

    The old trigger ("re-evaluate when #1601 and #1602 are released") is now wrong — it would have told us to integrate. Replace it with all of:

    1. The fabricated-used_by bug is filed and fixed. Not yet filed — needs an isolated repro first, per our own standing rule.
    2. Build Game: choose and preserve the authored renderer path in Runtime #1601 extended to member functions, not just free functions.
    3. A released version carries all of the above (nothing before v0.44.0 can).

    Until then: unchanged guidance — CLI/dashboard only if used at all, no .mcp.json registration, don't run it in worktrees. Co-change and per-function health remain the sound parts; this run reproduced the health signal at 20/20 lowest-health files had a bug fix in the last 6 months, 3.4× the 29% baseline.

    Local index left at .repowise/ (344 MB, gitignored) for follow-up; safe to delete.

  6. drsnuggles8 commented on Aug 17, 2026

    @drsnuggles8
    OwnerAuthor

    Correction + upstream filings (minimal repros built)

    Built minimal repros before filing, per our standing rule. That exercise corrected one claim in my previous comment and sharpened the rest.

    Retracted: "the headline wins are fabricated edges masking the FPs"

    I wrote that SAVE_COMPONENT and the Handle_* handlers left the dead-code list because a fabricated used_by made them look reachable. That was wrong. The repro disproves it directly:

    • A genuinely-unused free function carries a fabricated used_by: <paired header> and is still correctly reported as dead. So a fabricated edge does not suppress a finding.
    • Macros are eligible for findings too (an unused #define is reported), so SAVE_COMPONENT was not skipped for being a macro.
    • A dispatch-table assignment (tool.Handler = Handle_Alpha;) does now resolve — the run logs Reference edges resolved total=1 and the handler is not flagged.

    So SAVE_COMPONENT and Handle_* cleared because #1600 and #1602 genuinely fixed them. Credit where due; my mechanism claim was unfounded. The 40/40 → 30/40 improvement is real, not an artifact.

    The rest of the previous comment stands: 30/40 sampled findings are still false positives, and the graph still fabricates used_by edges 9 for 9.

    Filed upstream

    Upstream Repro
    used_by built from the header/impl pairing, not from resolved call edges — names a file that never references the symbol, omits the one that does #1655 3 files
    A type used only inside its own TU gets no type_use edge → reported safe_to_delete #1656 3 files
    Cross-file member calls still unresolved (#1601 fixed free functions only) comment on #1601 3 files

    All three reproduce in ≤6 files with no build system, verified compiling and linking with clang++ before filing. The mechanism for #1655 is named in repowise's own debug log (header_pair_edges added=N, tracking the number of header/impl pairs), and for the member-call gap the log shows Call edges resolved total=1 against two real cross-file calls.

    Two of the three findings are things the repro sharpened rather than confirmed: the fabricated-edge bug turned out to be a used_by construction problem rather than a general graph-soundness problem (dead-code itself is unaffected), and the member-function gap turned out to be a clean one-declaration/one-definition case rather than the overload ambiguity the maintainer had already documented as out of scope.

    Revisit trigger, restated

    Unchanged in substance from the previous comment, now pointing at real issue numbers — revisit when all of:

    1. Tests: VulkanParallelRecordingDevice bucket replay crashes after the GL renderer and async-compute suites ran in the same process #1655 fixed (the blocker for context / get_context / get_symbol, i.e. for ever registering the MCP server)
    2. Build Game: choose and preserve the authored renderer path in Runtime #1601 extended to member functions — 19 of our 30 sampled FPs
    3. feat(vulkan): content-driven PSO precaching with first-use hitch accounting (#1345) #1656 fixed — a further 8 of the 30
    4. A release carries all of the above (nothing before v0.44.0 can)

    Items 2 and 3 together account for 27 of the 30 false positives, so if both land the dead-code FP rate should fall to roughly 3/40 rather than 30/40. That is the point at which dead-code becomes worth re-sampling; #1655 is the separate gate for the MCP server.

  7. drsnuggles8 commented on Oct 10, 2026

    @drsnuggles8
    OwnerAuthor

    Upstream status 2026-10-10: repowise#1656 closed 2026-08-22; #1655 (used_by built from the header/impl pairing) is still open; latest release is v0.55.0 (2026-10-04). Under the trigger restated on 2026-08-17 (#1655 + member-function #1601 + #1656 + a release), the gate has not fired. The body's original trigger (#1601/#1602 released) and the score's blocked_by_external are superseded by that comment.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    architectureEngine architecture / cross-cutting designtoolingTier 1: MCP, dev tooling, codegen, CI, test infra. Freeze-exempt; start-work Tier-1 slot

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions