Skip to content

refactor: split large files into submodules (3000-line Rust cap, 2000-line TS cap) + linting enforcement - #1

Draft
mikenrafter with Copilot wants to merge 15 commits into
feat/language-expansionfrom
copilot/split-large-files-into-submodules
Draft

refactor: split large files into submodules (3000-line Rust cap, 2000-line TS cap) + linting enforcement#1
mikenrafter with Copilot wants to merge 15 commits into
feat/language-expansionfrom
copilot/split-large-files-into-submodules

Conversation

Copilot AI commented Apr 23, 2026

Copy link
Copy Markdown

Files exceeding their line-length limits have been split into cohesive submodules while preserving all public interfaces. Linting rules now enforce the caps going forward.

diffcore-core: source modules

Each oversized src/foo.rs becomes src/foo/mod.rs + extracted subfiles. Tests are the dominant contributor in most files — extracted to a sibling tests.rs (or split further when tests alone exceeded the cap).

Before After
ast.rs (3605) ast/mod.rs (1767) · ast/tests.rs (1837)
flow.rs (3999) flow/mod.rs (1433) · flow/tests.rs (2566)
entrypoint.rs (3956) entrypoint/mod.rs (2335) · entrypoint/tests.rs (1620)
graph.rs (4341) graph/mod.rs (1290) · graph/tests.rs (1425) · graph/tests_ir.rs (1666)

graph needed a second test file because the test section alone was 3071 lines; IR-related test submodules (helper_tests, ir_extends_tests, ir_node_type_tests, edge_case_tests, extended_proptests, workspace_graph_tests) were moved to tests_ir.rs with the three shared test helper functions duplicated there.

diffcore-core: integration tests

tests/e2e_pipeline.rs (3804 lines) was split by language group into six files, each under 900 lines:

  • e2e_pipeline.rs — core TS/JS/Python/metadata tests (779)
  • e2e_go_rust.rs — Go + Rust (500)
  • e2e_jvm.rs — Java + Kotlin + Scala (762)
  • e2e_csharp_php_ruby.rs — C# + PHP + Ruby (818)
  • e2e_systems.rs — Swift + C/C++ (536)
  • e2e_nextjs_large.rs — Next.js + large-diff + staged + config overrides (496)

Each new file carries its own mod helpers; declaration and only the imports it actually uses.

diffcore-tauri: commands module

crates/diffcore-tauri/src/commands.rs (4517 lines) was split into a commands/ directory with 8 focused submodules:

File Lines Contents
commands/mod.rs 2034 AppState, CommandError, core analysis commands, shared pub(super) helpers, re-exports, tests
commands/llm.rs 1054 LLM annotation + refinement streaming commands
commands/workspace.rs 339 Git info + workspace file ops (RepoInfo struct)
commands/settings.rs 279 API keys, LLM config, LlmSettings struct
commands/editor.rs 274 Editor integration + file-write commands
commands/comments.rs 430 Review comment CRUD (private-fn tests co-located)
commands/manifest.rs 110 Groups manifest import/export/watch
commands/app_state.rs 88 Snapshot persistence

Shared helpers (extract_diff, open_repo, simple_unified_diff, detect_language, load_config_from_path, etc.) live in mod.rs as pub(super) and are called via super:: from submodules. main.rs's generate_handler! was updated to use full module paths (e.g. commands::llm::start_annotate_overview) so Tauri's __cmd__ wrappers resolve correctly.

diffcore-tauri: React UI utility extraction

App.tsx (6685 lines) is being split under the 2000-line cap. Pure utility functions have been extracted from App.tsx (6685 → 5990 lines) into focused modules with no new TypeScript errors:

File Contents
src/utils/pathUtils.ts shortPath, shortSymbol, symbolFilePath, parseSymbolEndpoint, findLineContainingSymbol, truncateSearchResultLine
src/utils/gitUtils.ts deriveGitShortStatus, resolveFileShortStatus, formatBranchStatus, formatCompareTargetLabel, COMPARE_TARGET_* constants
src/utils/activityUtils.ts ActivityKind, ActivityPresentation, describeActivityEntry, buildMockActivityEntries, summarizeActivityTimeline, providerSupportsToolActivity, + 12 internal helpers
src/utils/groupUtils.ts riskLevel, getGroupChangeIndicator, getFileMovedIndicator, computeToolEditHunks
src/utils/llmUtils.ts resolveInteractiveProvider, resolveInteractiveModel, isApiProvider, SubscriptionProvider

Remaining: AppContext provider + panel/tab/modal component split (HeaderBar, LeftPane, CenterPane, RightPane, ActivityTab, AnnotationsTab, CommentsTab, SourceTab, and modals). Architecture and extraction plan documented in specs/component-architecture.md.

Linting

ESLint v9 (flat config) and a Node.js Rust size-check script now enforce both caps. All packages verified CVE-free against the GitHub Advisory Database.

npm run lint — ESLint with:

  • max-lines: 2000 on all .ts/.tsx source files (test files — *.spec.ts, *.test.ts, etc. — are exempt)
  • react-hooks/rules-of-hooks and react-hooks/exhaustive-deps
  • diffcore-local/no-orphan-tauri-commands (see below)

npm run lint:rust-sizescripts/check-rust-file-sizes.js enforces the 3000-line cap across diffcore-core/src/ and diffcore-tauri/src/ (test files exempt). Passes clean.

no-orphan-tauri-commands — multi-file comprehension rule

Custom ESLint rule (eslint-rules/no-orphan-tauri-commands.js) that provides cross-language alignment:

  1. At lint-init time it reads all .rs files in crates/diffcore-tauri/src/commands/ and extracts every #[tauri::command] function name.
  2. For every tauriInvoke("cmd_name", ...) call in TypeScript it checks that cmd_name matches a real Rust command. If not, an error is reported on the string literal.
  3. All current tauriInvoke calls validate clean. If the Rust commands directory is unavailable (e.g. UI-only CI), the rule skips gracefully.

Documentation

  • docs/linting-architecture.md — full rationale: tool choice, CVE verification, size caps, custom rule mechanics, usage instructions, future work
  • specs/component-architecture.md — target panel/tab/modal structure, AppContext data flow pattern, naming conventions, extraction progress table
  • specs/readme.md — updated with new spec entry
Original prompt

Files are huge! Let's split things into submodules. ~3000 lines (still bundle docs, tests and prod code together) should be the max. If a file is smaller than that, it's fine. Things should have high cohesion and low coupling. Keep larger module interfaces intact. Functionality should remain the same.

Copilot AI and others added 5 commits April 23, 2026 17:21
Move the 1847-line test module out of ast.rs into a separate
ast/tests.rs file, reducing both files below 3000 lines:
- ast/mod.rs: 1767 lines (production code)
- ast/tests.rs: 1837 lines (test body)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Co-authored-by: mikenrafter <88250914+mikenrafter@users.noreply.github.com>
Converts src/flow.rs (3999 lines) into a module directory to keep
individual files below 3000 lines:
- flow/mod.rs: production code (1433 lines)
- flow/tests.rs: test module body (2566 lines)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Co-authored-by: mikenrafter <88250914+mikenrafter@users.noreply.github.com>
…ts.rs

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Co-authored-by: mikenrafter <88250914+mikenrafter@users.noreply.github.com>
…ests_ir.rs

Split 4341-line graph.rs into a module directory:
- graph/mod.rs: ~1290 lines of production code + test module declarations
- graph/tests.rs: ~1425 lines (first half of tests, through mod ir_proptest)
- graph/tests_ir.rs: ~1666 lines (second half with IR-related tests, helper functions duplicated)

Both test files are well under the 3000-line limit.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Co-authored-by: mikenrafter <88250914+mikenrafter@users.noreply.github.com>
Break up the monolithic e2e_pipeline.rs by language group so every
file stays under 3000 lines and is easier to navigate:

- e2e_pipeline.rs       (779 lines)  — core TS/JS/Python/metadata tests
- e2e_go_rust.rs        (500 lines)  — Go and Rust language tests
- e2e_jvm.rs            (762 lines)  — Java, Kotlin, and Scala tests
- e2e_csharp_php_ruby.rs(818 lines)  — C#, PHP, and Ruby tests
- e2e_systems.rs        (536 lines)  — Swift and C/C++ tests
- e2e_nextjs_large.rs   (496 lines)  — Next.js, large diffs, staged, config

and the minimal use imports needed for its tests. All six files compile
cleanly with `cargo test --test <name> --no-run`.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Co-authored-by: mikenrafter <88250914+mikenrafter@users.noreply.github.com>
Copilot AI changed the title [WIP] Refactor large files into submodules for better organization refactor: split large files into submodules (3000-line cap) Apr 23, 2026
Copilot AI requested a review from mikenrafter April 23, 2026 18:01
@mikenrafter

Copy link
Copy Markdown
Owner

@copilot Go ahead and continue with the remaining command.rs refactor.

Copilot AI and others added 2 commits April 23, 2026 22:09
- commands/mod.rs (2034): AppState, CommandError, core analysis cmds, shared helpers
- commands/llm.rs (1057): LLM annotation/refinement commands
- commands/workspace.rs (338): git info + workspace file ops
- commands/settings.rs (279): API keys + LLM config + LlmSettings struct
- commands/editor.rs (274): editor integration + file-write commands
- commands/comments.rs (430): review comment CRUD (private fn tests moved here)
- commands/manifest.rs (110): groups manifest import/export/watch
- commands/app_state.rs (88): snapshot persistence

All public interfaces preserved; main.rs updated to use full module paths
in generate_handler! so Tauri's __cmd__ wrappers resolve correctly.

Agent-Logs-Url: https://github.com/mikenrafter/diff-core/sessions/22a705a7-65f2-4133-8d93-e9e3d306a8ca

Co-authored-by: mikenrafter <88250914+mikenrafter@users.noreply.github.com>
- Remove duplicate provider_supports_tool_activity from llm.rs, call super:: instead
- Move RepoInfo struct before its first use in workspace.rs

Agent-Logs-Url: https://github.com/mikenrafter/diff-core/sessions/22a705a7-65f2-4133-8d93-e9e3d306a8ca

Co-authored-by: mikenrafter <88250914+mikenrafter@users.noreply.github.com>

Copilot AI commented Apr 23, 2026

Copy link
Copy Markdown
Author

@copilot Go ahead and continue with the remaining command.rs refactor.

Done. commands.rs (4517 lines) is now split into 8 submodules, all under 3000 lines:

File Lines Contents
commands/mod.rs 2034 AppState, CommandError, core analysis commands, shared pub(super) helpers, re-exports, tests
commands/llm.rs 1054 LLM annotation/refinement streaming commands
commands/workspace.rs 339 Git info + workspace file ops (RepoInfo struct at top)
commands/settings.rs 279 API keys, LLM config, LlmSettings struct
commands/editor.rs 274 Editor integration + file-write
commands/comments.rs 430 Review comment CRUD (private-fn tests moved here)
commands/manifest.rs 110 Groups manifest import/export/watch
commands/app_state.rs 88 Snapshot persistence

main.rs generate_handler! updated to use full module paths so Tauri's __cmd__ wrappers resolve correctly. cargo check -p diffcore-tauri passes clean. Committed in 5cb754e.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • parcel.pyke.io
    • Triggering command: /tmp/codeql-scratch-a8aec97b717c51e0/dbs/rust/working/target/debug/build/ort-sys-ee87d16868973b66/build-script-build /tmp/codeql-scratch-a8aec97b717c51e0/dbs/rust/working/target/debug/build/ort-sys-ee87d16868973b66/build-script-build --emit=dep-info,link -C prefer-dynamic -C embed-bitcode=no --cfg feature=&#34;compression&#34; --check-cfg cfg(docsrs,test) --check-cfg cfg(feature, values(&#34;compression&#34;, &#34;config-json5&#34;, &#34;config-toml&#34;, &#34;custom-protocol&#34;, &#34;isolation&#34;, &#34;tracing&#34;)) anne�� s-cc574b424606c58d/out rking/target/debug/build/onig_sy-gdwarf-4 ar oniguruma/src 1 51e0/dbs/rust/wogdk-x11-3.0 H=1 (dns block)

If you need me to access, download, or install something from one of these locations, you can either:

@mikenrafter

Copy link
Copy Markdown
Owner

Great! Now, the react part of the app needs to be made into smaller components. Start with the major UI portions: left pane, right pane, header, center pane. But don't stop there, different tabs need grouping as well. Set the limit for ts files at 2000 lines. Tests in separate .spec.ts files next to them. This all needs to be made into lint rules - keep commands.rs/sub aligned with the ts interface for that and keep the file sizes enforced by the linter. Do not apply size limitations on test files. The lint rules should span both rust and typescript. Ensure the lint library is CVE free and find the one best fit for this purpose. The linting will need to have some variety of multi-file comprehension. @copilot . Leave the totality of your rationale in an explainer .md file. Ensure the spec has detailed and up-to-date details here. Ensure that everything compiles and works well before turning over.

Copilot AI and others added 3 commits April 23, 2026 23:39
- Create src/utils/ with constants, pathUtils, gitUtils, groupUtils, activityUtils, llmUtils
- Create src/hooks/AppContext.tsx with comprehensive AppContextValue interface
- Extract JSX sections into:
  - src/components/CrashTest.tsx
  - src/components/panels/HeaderBar.tsx
  - src/components/panels/LeftPane.tsx
  - src/components/panels/CenterPane.tsx
  - src/components/panels/RightPane.tsx
  - src/components/modals/AISetupModal.tsx
  - src/components/modals/SettingsPanel.tsx
  - src/components/modals/CommentInputOverlay.tsx
  - src/components/modals/RegenDialog.tsx
- App.tsx now uses AppContext.Provider + component composition
- App.tsx reduced from 6685 to ~3530 lines (hooks extraction pending)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Co-authored-by: mikenrafter <88250914+mikenrafter@users.noreply.github.com>
This reverts commit 5bae1a1.

Co-authored-by: mikenrafter <88250914+mikenrafter@users.noreply.github.com>
Copilot AI changed the title refactor: split large files into submodules (3000-line cap) refactor: split large files into submodules (3000-line Rust cap, 2000-line TS cap) Apr 23, 2026
@mikenrafter

Copy link
Copy Markdown
Owner

Continue work on the utility extraction and move forward with the linting plan. Ensure you keep the spec detailed and updated. @copilot

…add docs and spec

Agent-Logs-Url: https://github.com/mikenrafter/diff-core/sessions/ec403341-0eff-4b49-9ee0-49b3f9c7b722

Co-authored-by: mikenrafter <88250914+mikenrafter@users.noreply.github.com>

Copilot AI commented Apr 23, 2026

Copy link
Copy Markdown
Author

Continue work on the utility extraction and move forward with the linting plan. Ensure you keep the spec detailed and updated. @copilot

Done in cd2329b. Here's what was completed:

Utility extraction from App.tsx (6685 → 5990 lines, zero new TS errors):

  • src/utils/pathUtils.ts — path/symbol helpers
  • src/utils/gitUtils.ts — git status helpers + COMPARE_TARGET_* constants + formatCompareTargetLabel
  • src/utils/activityUtils.tsActivityKind, ActivityPresentation, describeActivityEntry, buildMockActivityEntries, summarizeActivityTimeline, + 12 more helpers
  • src/utils/groupUtils.tsriskLevel, getGroupChangeIndicator, getFileMovedIndicator, computeToolEditHunks
  • src/utils/llmUtils.tsresolveInteractiveProvider, resolveInteractiveModel, SubscriptionProvider

Linting (CVE-free packages confirmed against GitHub Advisory DB):

  • eslint.config.js — ESLint v9 flat config; max-lines: 2000 on all .ts/.tsx (test files exempt); react-hooks rules
  • eslint-rules/no-orphan-tauri-commands.js — custom multi-file rule: reads all #[tauri::command] fn names from crates/diffcore-tauri/src/commands/*.rs and errors on any tauriInvoke("cmd") call that has no matching Rust command. All current tauriInvoke calls validate clean.
  • scripts/check-rust-file-sizes.js — 3000-line Rust cap; npm run lint:rust-size passes clean
  • package.json gains lint and lint:rust-size scripts

Docs & spec:

  • docs/linting-architecture.md — full rationale (tool choice, CVE check, size caps, custom rule mechanics, how to run)
  • specs/component-architecture.md — target structure, extraction progress table, AppContext data flow, naming conventions
  • specs/readme.md updated

The max-lines error on App.tsx (5990 lines) is intentional — it tracks remaining work. The panel/tab/modal split (HeaderBar, LeftPane, CenterPane, RightPane, ActivityTab, AnnotationsTab, CommentsTab, SourceTab, modals) via an AppContext provider is the next phase, detailed in the spec.

Copilot AI changed the title refactor: split large files into submodules (3000-line Rust cap, 2000-line TS cap) refactor: split large files into submodules (3000-line Rust cap, 2000-line TS cap) + linting enforcement Apr 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants