Skip to content

Organize src/core into real modules - #795

Merged
benvinegar merged 9 commits into
mainfrom
claude/module-dependency-graph-5bajgn
Aug 17, 2026
Merged

benvinegar merged 9 commits into
mainfrom
claude/module-dependency-graph-5bajgn

Conversation

@benvinegar

@benvinegar benvinegar commented Aug 17, 2026 •

Copy link
Copy Markdown
Member

Problem

src/core had become a pile of ~30 loose files that any code anywhere could reach into. The worst symptom was core/types.ts: 147 files imported it, it mixed four unrelated audiences (the changeset model, CLI inputs, app bootstrap, and re-exported extension contract types), and touching it invalidated a big chunk of the typecheck graph. There was no way to tell which files were "API" and which were internals — everything was public because nothing said otherwise.

flowchart TD
  ui["src/ui"] --> types
  app["src/app"] --> types
  session["src/session"] --> types
  ext["src/extensions"] --> types
  types["core/types.ts — 147 importing files<br/>changeset model + CLI inputs + bootstrap<br/>+ re-exported extension contract types"]
  types -.->|re-exports for| loose["~30 loose files at core/*<br/>config, loaders, terminal, pager, paths, errors, …<br/>all public, nothing says otherwise"]
  loose -.->|some import back| types
Loading

What this does

Core is now a set of directories named for what they hold, each with a declared public surface, and core/types.ts is gone — every file imports the module that actually declares the thing it wants, so the dependency graph shows real relationships instead of "everything depends on types.ts".

flowchart TD
  ui["src/ui"] --> changeset
  ui --> run
  ui --> process
  app["src/app"] --> bootstrap
  app --> changeset
  app --> run
  session["src/session"] --> changeset
  session --> run
  subgraph core["src/core"]
    bootstrap["bootstrap.ts<br/>AppBootstrap + ReloadContext<br/>28 importers, down from 147"]
    changeset["changeset/<br/>the model + how it loads<br/><i>internal: fromPatch, sidecar,<br/>fileLanguageLookup</i>"]
    run["run/<br/>what a run was asked to be:<br/>inputs, config, command catalog"]
    process["process/<br/>where it executes: terminal,<br/>pager, job control, shutdown"]
    existing["review/ vcs/ theme/<br/>watch/ patch/<br/><i>already coherent, unchanged</i>"]
  end
  bootstrap --> changeset
  bootstrap --> run
Loading

The module boundaries aren't just convention: a dependency-cruiser rule fails CI if anything outside a module imports its internals, and a reachability rule fails on any src/ file no entry point can reach — which is how core/review/address.ts (zero consumers) got deleted.

Reviewing this

It's a big diff but a shallow one: everything is git mv plus import-path updates. No exported symbol was renamed and no behavior changed — the published hunkdiff/extension and hunkdiff/opentui entry points are untouched. The commits are reviewable independently if the whole thing is too much at once.

Three judgment calls worth an opinion:

  • core/review/annotations.ts turned out to genuinely use AgentAnnotation from extension-api/types — the old shell was hiding that edge. The seam test now allows that single file as a containment target rather than pretending the dependency doesn't exist.
  • changeset/loaders.ts imports the bootstrap type it assembles (loadAppBootstrap), which is backwards for the layering. It's a named exception in the rules; moving that function up to src/app would retire it, left as a follow-up.
  • AgentCard.tsx + agentPopover.ts appear to be dead (nothing renders them since notes moved into the diff flow) but only their tests keep them alive. They're quarantined in the dead-module allowlist instead of deleted — happy to delete them here if you agree they're gone.

🤖 Generated with Claude Code

https://claude.ai/code/session_01XcuayyxJrwQd9EHpuxNef1

claude added 6 commits August 17, 2026 02:54
…interiors

Phase 0 of giving src/core an interior. Deletes core/review/address.ts
(a speculative primitive whose only importer was its own test) and adds
three boundary rules: no-dead-modules (any src/ module unreachable from
a production entry point fails the check, with a shrink-only test-only
allowlist), core-leaves-never-reimport-types (freezes the cycle fix so
the extracted leaves can never import core/types.ts back), and the first
per-module interior rule making core/review/reducer.ts importable only
from within the review model. Also fixes core/watch/signature.ts to
import CliInput from its leaf home, caught by the new freeze rule.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XcuayyxJrwQd9EHpuxNef1
…eset/

Phase 1 of giving src/core an interior. Twelve root files — the model,
its patch-text builder, the loaders, and the file-level helpers — become
one module with a declared surface: model, loaders, diffFile,
fileSource, fileLanguage, binary, diffPaths, hunkHeader, and hunkSummary
stay public, while fromPatch, sidecar, and fileLanguageLookup are
enforced module-internal. Pure move: no exported symbol renamed, and
core/types.ts keeps re-exporting the model for existing import sites.
Also repoints the leaf-freeze rule at the moved paths so it keeps
protecting the cycle fix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XcuayyxJrwQd9EHpuxNef1
Phase 2 of giving src/core an interior. The nine files describing how a
run is asked for — command inputs, config, the command catalog,
invocation errors, experimental flags, paths, tab width, reload
capability, and version — become core/invocation/. The CLI-input type
definitions (session/markup/extension command inputs and ParsedCliInput)
move out of core/types.ts into invocation/commandInputs.ts, with
core/types re-exporting the names still consumed elsewhere so no import
site changes. The leaf-freeze rule follows commandInputs to its new
path; all nine files have outside consumers, so this module declares no
interior yet.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XcuayyxJrwQd9EHpuxNef1
Phase 3 of giving src/core an interior. terminal, pager, jobControl,
shutdown, projectRoot, appStateFile, updateNotice, and startupNotice
move from core root into core/runtime/. Pure move with no symbol
renames; all eight have outside consumers, so the module declares no
interior. core root now holds only types.ts, reviewDigest.ts, and
liveComments.ts beside the module directories.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XcuayyxJrwQd9EHpuxNef1
Phase 4, the last of giving src/core an interior. core/types.ts had 147
importing files bound to it for names that phases 1-3 had already moved;
every import site now names the declaring module directly (changeset
model, invocation inputs, extension-api contract, runtime notices), the
re-exports are deleted, and the file becomes core/bootstrap.ts holding
only the AppBootstrap/ReloadContext contract - 28 importers instead of
147, so editing it no longer invalidates half the typecheck graph.
Stragglers rehomed by role: TerminalThemeMode to theme/detection,
ExtensionsConfig/UserKeyBinding/PersistedViewPreferences to
invocation/config, UserNoteLineTarget to liveComments. The leaf-freeze
rule becomes core-leaves-stay-below-bootstrap across all eight core
module dirs, with one named exception (changeset/loaders.ts assembles
the bootstrap value) recorded for a future move to src/app. Internal
import paths only; the published extension and opentui entry points are
unchanged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XcuayyxJrwQd9EHpuxNef1
The pair now tells one story in the repo's own vocabulary: core/run/ is
what a Hunk run was asked to be (inputs, config, catalog, invocation
errors), and core/process/ is the OS process and terminal it executes in
(terminal, pager, job control, shutdown, notices). "invocation" was
formal, and "runtime" one letter from colliding with "run"; renaming
both keeps the distinction memorable. Directory rename only — no file
or symbol renamed, boundary rules repointed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XcuayyxJrwQd9EHpuxNef1
@vercel

vercel Bot commented Aug 17, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
hunk-web Ignored Ignored Preview Aug 17, 2026 5:49pm

Request Review

claude added 2 commits August 17, 2026 12:54
Merges main, whose new cache-layer benchmark (#791) landed importing the
core/types shell that this branch melts; DiffFile now comes from its
declaring module.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XcuayyxJrwQd9EHpuxNef1
@benvinegar benvinegar changed the title Give src/core an interior: module directories, enforced surfaces, and a melted types shell Organize src/core into real modules Aug 17, 2026
@benvinegar

Copy link
Copy Markdown
Member Author

@greptile-apps review

@greptile-apps

greptile-apps Bot commented Aug 17, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR reorganizes the loose src/core files into changeset, run, and process modules, retargets consumers to the declaring modules, and adds dependency rules that enforce module interiors and production reachability.

  • Splits the former core/types.ts surface among bootstrap and domain-owned modules.
  • Moves changeset acquisition, run configuration, and process-lifecycle files into coherent directories.
  • Adds dependency-cruiser checks for dead modules and prohibited interior imports.
  • Deletes an unused review-address implementation and documents the resulting boundaries.

Confidence Score: 4/5

The PR appears safe to merge, with only non-blocking stale module names in the architecture documentation.

The declaration split, import rewrites, published entrypoints, and type-only bootstrap dependency remain coherent; the accepted issue is limited to documentation that still references superseded directory names.

Files Needing Attention: docs/module-boundaries.md

Important Files Changed

Filename Overview
.dependency-cruiser.cjs Adds production-reachability and core-interior dependency constraints with explicit entrypoint and test-only allowlists.
src/core/bootstrap.ts Retains the launch-composition types formerly declared by the broad core types module.
src/core/run/commandInputs.ts Consolidates CLI and session command input contracts without an identified behavioral change.
src/core/changeset/loaders.ts Relocates changeset loading and retains only a type-level dependency on the bootstrap contract.
docs/module-boundaries.md Documents the new architecture comprehensively but retains several stale invocation/runtime directory names.
docs/source-architecture.md Updates the ownership map to include changeset, run, and process modules.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
  App[src/app] --> Bootstrap[src/core/bootstrap.ts]
  App --> Changeset[src/core/changeset]
  App --> Run[src/core/run]
  App --> Process[src/core/process]
  Bootstrap --> Changeset
  Bootstrap --> Run
  Bootstrap --> Process
  UI[src/ui] --> Changeset
  UI --> Run
  UI --> Process
  Rules[dependency-cruiser rules] -. enforce interiors .-> Changeset
  Rules -. enforce reachability .-> App
Loading
Prompt To Fix All With AI
### Issue 1
docs/module-boundaries.md:168-169
**Stale core module names**

The final bootstrap dependency list still names `invocation` and `runtime`, although this PR establishes `run` and `process`; these stale paths direct maintainers toward nonexistent module names, and the same terminology remains in the final module inventory below.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Reviews (1): Last reviewed commit: "fix(bench): repoint highlight-cache-laye..." | Re-trigger Greptile

Comment thread docs/module-boundaries.md Outdated
Comment on lines +168 to +169
`changeset/model`, `invocation/commandInputs`, `invocation/config`, `runtime/startupNotice`,
`theme/detection`, and `vcs/types`, and `core-leaves-stay-below-bootstrap` forbids the reverse

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Stale core module names

The final bootstrap dependency list still names invocation and runtime, although this PR establishes run and process; these stale paths direct maintainers toward nonexistent module names, and the same terminology remains in the final module inventory below.

Prompt To Fix With AI
This is a comment left during a code review.
Path: docs/module-boundaries.md
Line: 168-169

Comment:
**Stale core module names**

The final bootstrap dependency list still names `invocation` and `runtime`, although this PR establishes `run` and `process`; these stale paths direct maintainers toward nonexistent module names, and the same terminology remains in the final module inventory below.

---

For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.

Greptile caught six references in docs/module-boundaries.md still naming
invocation/ and runtime/ — the rename sweep matched path-like forms and
skipped these prose mentions. Genuine English uses of "invocation" and
"runtime" stay.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XcuayyxJrwQd9EHpuxNef1
@benvinegar
benvinegar merged commit 753dc37 into main Aug 17, 2026
12 checks passed
@kaynetik kaynetik mentioned this pull request Aug 29, 2026
7 of 13 tasks
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