Repository navigation
feat(history): bounded-memory history core, parallel burndown, UAST spill, codefang mcp - #29
Merged
Merged
Conversation
…pill, codefang mcp Analyzer audit fixes plus the bounded-memory history pipeline (FRD-bounded-memory-history, FRD-parallel-burndown, FRD-history-spill, BUG-history-shotness-nested-entities). CHANGELOG.md [Unreleased] lists every change; the highlights: - Every history walk folds through a worker pool: workers claim runs of consecutive commits from a shared cursor, steal from idle peers, and run at most one chunk ahead of the in-order fold; free heap pages go back to the OS after every chunk. Output is byte-identical for every chunk size. - --memory-budget / --chunk-size (and pipeline.memory_budget / pipeline.chunk_size): each walk plans its chunk and worker count from its share and measured per-walk costs; libgit2 gets a quarter of the budget for its object cache and 7.5 % for mapped pack windows; -v prints plans. - The UAST analyzers parse one change at a time and run as one memoized pass; their finished results spill to an unlinked temp file while other history walks run. - history/burndown prepares its per-change work on the pool and reuses the pre-pass tree diffs of heavy commits. - history/shotness: --shotness-keep-ended keeps ended entities, marked. - codefang mcp serves codefang_analyze, uast_parse and codefang_history over stdio; every tool result is the CLI's own report. - The retired Go differential gate and oracle data are removed. Measured on 31,923 kubernetes commits, history/* --format json, output byte-identical to the previous build: previous build 399.8 s 12.9 GiB automatic budget 334.4 s 3.56 GiB --memory-budget 4GB 327.8 s 3.79 GiB Gates: cargo fmt --check, make lint (0), make deadcode (0), 3138 tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KmiE8YcZ3GtgQW2bDbcgXo
Hard tabs in the Go examples become spaces, code spans with a trailing space are reworded, the global-flags heading follows its parent, a table cell no longer contains a literal pipe, and the people-dict example fence names its language. markdownlint-cli2 over the CI globs: 0 errors. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KmiE8YcZ3GtgQW2bDbcgXo
Full-report validation over 100 popular open-source repositories found two report-validity defects. - The schema registry described fields the reports did not emit for history/typos and history/imports, and omitted fields they did emit for history/burndown (`interactions`) and static/clones (`message`). The registry now declares exactly the report keys, matching site/analyzers. A new e2e contract test asserts schema keys equal report keys for every analyzer of a full run. - A shallow clone yields an empty history walk, so every history analyzer reported empty results with exit 0 and no explanation. The run still succeeds with the same empty report, and now prints one `warning: shallow clone` line naming `git fetch --unshallow`. Refs: site/how-to/run-in-ci.md, site/release-notes/unreleased.md Claude-Session: https://claude.ai/code/session_01G4vrFqHuccLUdQpFcRCQRx
…e size Full-report memory on large repositories was dominated by a few large data files. Alacritty's tests/ref fixtures include a 19 MB JSON file that needed over 3 GB to parse, and the full report was OOM-killed. - The function analyzers (complexity, halstead, cohesion, clones) no longer build a tree for data and config formats (JSON, YAML, TOML, INI, CSV, PSV, dotenv, XML, git and ssh config). Those files have no functions, so an empty file root gives the same zero-function report. Verified byte-identical per-file output against the previous binary on a fixture of every such format and on 12 real repositories (48 runs). alacritty's static/complexity dropped from 4.7 GB and 46 s to 36 MB and 2 s. - The UAST parse size limit is now 8 MiB by default (was 64 MiB). Larger source files are skipped with a warning that names the limit. The new --max-parse-file-size flag (MiB, >= 1) raises it. - StaticFilter::default() now carries the default limit; the derived default would have set it to zero and skipped every file. Refs: site/release-notes/unreleased.md Claude-Session: https://claude.ai/code/session_01G4vrFqHuccLUdQpFcRCQRx
…nd the mapping The skip for data files used a hardcoded list of format names. Replace it with two data-driven checks: - enry's Linguist language type (data, programming, markup, prose), vendored from enry v2.1.0's data.LanguagesType as data/enry-types-v2.1.0.tsv and exposed as cf_langpath::language_type. The table is generated by tools/enrydump, which now writes both tables and reproduces the existing lookup table byte for byte. - cf_uast::Parser::yields_functions: whether any rule of the file's mapping can produce a function node (type Function or Method, or the Function and Declaration roles), the same criteria the complexity analyzer uses. A function parse is skipped only when enry types the file as data AND the mapping cannot yield a function. enry's type alone is not enough: SQL and GraphQL are data-typed but their mappings declare functions (CREATE FUNCTION, query operations), so the type alone would change their reports. Verified: function counts for SQL (1) and GraphQL (2) are unchanged, and the four function analyzers match the previous binary byte-for-byte on the data fixture and on 12 real repositories (48 runs). alacritty static/complexity drops from 84 s to 2.3 s. .gitignore: the new table is listed beside the other vendored tables, since the blanket *.tsv rule would otherwise leave it untracked. Refs: site/guide/cli-reference.md, site/release-notes/unreleased.md Claude-Session: https://claude.ai/code/session_01G4vrFqHuccLUdQpFcRCQRx
The import analyzer now uses the same data-file skip as the function analyzers. A file is not parsed when enry types it as data AND its mapping cannot yield an Import or Call node, the only nodes the import rules start from (cf_imports::IMPORT_TRIGGER_TYPES). Protocol Buffers is data-typed but its mapping yields imports, so it is still parsed and its imports reported. static/comments is deliberately not skipped: YAML, TOML, and INI comments are real parsed nodes that are counted, so an empty tree would change the report. The predicate is generalized to Parser::may_yield(file, wanted), with yields_functions as one caller. cf-commands has a single parse_unless_data used by the function and import analyzers. Verified byte-identical output against the previous binary for static/imports on the data fixtures and on 12 real repositories (60 comparisons across the five analyzers), and comments and complexity on the fixtures. Refs: site/guide/cli-reference.md, site/release-notes/unreleased.md Claude-Session: https://claude.ai/code/session_01G4vrFqHuccLUdQpFcRCQRx
The Format check in CI failed on the formatting of the new e2e tests and the cf-uast re-exports. This is a formatting-only change; no code changed. Claude-Session: https://claude.ai/code/session_01G4vrFqHuccLUdQpFcRCQRx
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.
Scope
Analyzer audit fixes, plus the restoration of bounded-memory history processing that the Go → Rust rewrite lost.
CHANGELOG.md[Unreleased]lists every change.history/pool.rs). Workers claim runs of consecutive commits from a shared cursor, steal from idle peers, and run at most one chunk ahead of the in-order fold.cf_meminfo::release_free_heap).--memory-budget/--chunk-size, also aspipeline.memory_budget/pipeline.chunk_size.-vprints each plan.history/burndownprepares its per-change work on the pool and reuses the pre-pass tree diffs of heavy commits: 59.4 s → 25.9 s.--shotness-keep-ended(opt-in) keeps ended shotness entities, markedended. The default report is unchanged.codefang mcpservescodefang_analyze,uast_parseandcodefang_historyover stdio. Each tool result is the CLI's own report (cf_commands::render_run).Measurements
Workload: 31,923 kubernetes commits,
codefang run -a 'history/*' --format json, 24 cores, quiet machine. Output is byte-identical to the previous build (md515fbc154f5393227f96f87ad6165cb4a, 538 MB).--memory-budget 4GBTest matrix
cargo fmt --check,make lint(0 findings),make deadcode(0 findings), 3138 tests passed.bins/codefang/tests/chunked_history_e2e.rschecks 10 history analyzers × 9 formats × chunk sizes {1, 2, 7, 1000}. It also covers the co-selected UAST pass, budget invariance, the plan lines and the spill path.mcp_e2e.rs: the handshake, tool listing, and all three tools against the CLI reports.shotness_e2e.rs: both modes.config_e2e.rs: the new keys.Risks
cf_gitlib::limit_native_memory).unsafecalls, each behind a safe wrapper with a test:malloc_trim(0)and libgit2's mapped-pack and object-cache limits./tmp. On a tmpfs/tmpthe spill leaves the process but not RAM;TMPDIRpoints it at a disk.--shotness-keep-endedon long histories: the shotness report grows 3.4× (256 MB → 859 MB on the window above).Rollback
--chunk-size 1000000reproduces the unbounded walk.🤖 Generated with Claude Code
https://claude.ai/code/session_01KmiE8YcZ3GtgQW2bDbcgXo