diff --git a/.changeset/lazy-grapes-divide.md b/.changeset/lazy-grapes-divide.md new file mode 100644 index 000000000..a845151cc --- /dev/null +++ b/.changeset/lazy-grapes-divide.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/.dependency-cruiser.cjs b/.dependency-cruiser.cjs index 4500df97e..9d12579a0 100644 --- a/.dependency-cruiser.cjs +++ b/.dependency-cruiser.cjs @@ -20,6 +20,31 @@ const UI_SESSION_ADAPTERS = [ "^src/ui/lib/reviewState\\.ts$", ]; +// Every way the shipped product is entered: the CLI, the highlight worker thread, the two +// published facades, and the skill generator. A module under src/ that no entry reaches, +// directly or transitively, is not in the product. +const PRODUCTION_ENTRY_POINTS = [ + "^src/main\\.tsx$", + "^src/highlightWorkerEntry\\.ts$", + "^src/opentui/index\\.ts$", + "^src/extension-api/index\\.ts$", + "^src/hunk-review/skillDocument\\.ts$", +]; + +// Modules kept alive by tests alone. The cruise excludes tests, so these look unreachable +// from the entry points while real coverage still depends on them. Shrink-only: an entry +// leaves when production reaches the module or the module goes; nothing is ever added +// without the coverage to justify it. +const TEST_ONLY_MODULES = [ + // Note-height measurement exercised by the review-conformance corpus. + "^src/core/review/noteSize\\.ts$", + // The floating agent-note popover and its measurement helper. Nothing renders them since + // notes moved into the diff flow as STML cards; their unit tests are the only consumers + // left, so they are quarantined here until that call is made rather than deleted blind. + "^src/ui/components/panes/AgentCard\\.tsx$", + "^src/ui/lib/agentPopover\\.ts$", +]; + module.exports = { forbidden: [ { @@ -94,6 +119,47 @@ module.exports = { from: { path: "^src/ui/", pathNot: UI_SESSION_ADAPTERS }, to: { path: "^src/(app|session)/" }, }, + { + name: "no-dead-modules", + comment: + "Every module under src/ earns its place by being reachable from an entry point. Dead files are worse than clutter: they still import, so they hold boundaries hostage and answer questions nobody asks. `orphan` only catches fully disconnected files, which misses dead code that still has dependencies — reachability catches both. A flagged module is either deleted or, if tests are its only real consumer, listed in TEST_ONLY_MODULES with a reason.", + severity: "error", + from: { path: PRODUCTION_ENTRY_POINTS }, + to: { + path: "^src/", + pathNot: [...PRODUCTION_ENTRY_POINTS, ...TEST_ONLY_MODULES], + reachable: false, + }, + }, + { + name: "core-leaves-stay-below-bootstrap", + comment: + "core/bootstrap.ts composes the leaves: it names the changeset, the parsed input, the resolved preferences, and the detected theme mode to describe one launch. A module directory importing it back would invert that layering and rebuild the grab-bag cycle the 2026-08 phases dismantled. core/changeset/loaders.ts is the single exception — loadAppBootstrap assembles the value, so it names the shape it returns; its natural home is the app tier, and moving it there retires this exception.", + severity: "error", + from: { + path: "^src/core/(changeset|run|process|review|vcs|watch|patch|theme)/", + pathNot: "^src/core/changeset/loaders\\.ts$", + }, + to: { path: "^src/core/bootstrap\\.ts$" }, + }, + { + name: "review-reducer-is-module-internal", + comment: + "The review reducer applies actions; callers state intent instead, so surfaces cannot reach past planReviewIntent into the transition table. First of the per-module interior rules — this establishes the mechanism later phases extend to the rest of src/core (identity.ts and the other named model modules stay public by design).", + severity: "error", + from: { path: "^src/", pathNot: "^src/core/review/" }, + to: { path: "^src/core/review/reducer\\.ts$" }, + }, + { + name: "changeset-internals-stay-in-module", + comment: + "core/changeset owns the changeset model and the pipeline that acquires one. Outsiders name the model, the loaders, and the per-file helpers they build on (model, loaders, diffFile, fileSource, fileLanguage, binary, diffPaths, hunkHeader, hunkSummary); the patch-to-model parse, the Pierre extension-table lookup, and the sidecar reader are steps inside that pipeline, reached through the loaders instead.", + severity: "error", + from: { path: "^src/", pathNot: "^src/core/changeset/" }, + to: { + path: "^src/core/changeset/(fromPatch|fileLanguageLookup|sidecar)\\.ts$", + }, + }, { name: "packages-stay-standalone", comment: diff --git a/benchmarks/bootstrap-load.ts b/benchmarks/bootstrap-load.ts index ddf33fff7..95c709318 100644 --- a/benchmarks/bootstrap-load.ts +++ b/benchmarks/bootstrap-load.ts @@ -6,7 +6,7 @@ import { join } from "node:path"; import { performance } from "perf_hooks"; import { parsePatchFiles } from "@pierre/diffs"; import { getBundledVcsCatalog } from "../src/app/vcsCatalog"; -import { loadAppBootstrap } from "../src/core/changesetLoaders"; +import { loadAppBootstrap } from "../src/core/changeset/loaders"; const FILE_COUNT = 64; const LINES_PER_FILE = 420; diff --git a/benchmarks/changeset-parse.ts b/benchmarks/changeset-parse.ts index add4a8b8a..a090f999b 100644 --- a/benchmarks/changeset-parse.ts +++ b/benchmarks/changeset-parse.ts @@ -1,7 +1,7 @@ // Benchmark raw patch parsing and sanitized DiffFile construction for several diff shapes. import { performance } from "perf_hooks"; import { parsePatchFiles } from "@pierre/diffs"; -import { buildDiffFile } from "../src/core/diffFile"; +import { buildDiffFile } from "../src/core/changeset/diffFile"; import { findPatchChunk, splitPatchIntoFileChunks } from "../src/core/patch/chunks"; import { sanitizePatchText } from "../src/core/patch/sanitize"; import { createSyntheticPatch } from "./lib/fixtures"; diff --git a/benchmarks/compact-highlight-payload.ts b/benchmarks/compact-highlight-payload.ts index 004178312..f09189b59 100644 --- a/benchmarks/compact-highlight-payload.ts +++ b/benchmarks/compact-highlight-payload.ts @@ -3,7 +3,7 @@ // normalizes it, and the terminal paint seam remains a later milestone. import { performance } from "node:perf_hooks"; import { cleanLastNewline, parseDiffFromFile } from "@pierre/diffs"; -import type { DiffFile } from "../src/core/types"; +import type { DiffFile } from "../src/core/changeset/model"; import { buildSplitRows, loadHighlightedDiff, diff --git a/benchmarks/highlight-cache-layers.ts b/benchmarks/highlight-cache-layers.ts index 502cdc0e4..f77ad098b 100644 --- a/benchmarks/highlight-cache-layers.ts +++ b/benchmarks/highlight-cache-layers.ts @@ -1,7 +1,7 @@ // Compare a resident main-process cache hit with a worker-LRU revisit after main-cache eviction. import { performance } from "node:perf_hooks"; import { parseDiffFromFile } from "@pierre/diffs"; -import type { DiffFile } from "../src/core/types"; +import type { DiffFile } from "../src/core/changeset/model"; import { resolveTheme } from "../src/ui/themes"; import { disposeHighlightWorker } from "../src/ui/diff/worker/highlightWorkerClient"; import { prefetchHighlightedDiff } from "../src/ui/diff/useHighlightedDiff"; diff --git a/benchmarks/highlight-prefetch.ts b/benchmarks/highlight-prefetch.ts index e5af16233..1ccd7946c 100644 --- a/benchmarks/highlight-prefetch.ts +++ b/benchmarks/highlight-prefetch.ts @@ -6,7 +6,8 @@ import { testRender } from "@opentui/react/test-utils"; import { parseDiffFromFile } from "@pierre/diffs"; import { act } from "react"; import { AppHost } from "../src/ui/AppHost"; -import type { AppBootstrap, DiffFile } from "../src/core/types"; +import type { AppBootstrap } from "../src/core/bootstrap"; +import type { DiffFile } from "../src/core/changeset/model"; function createDiffFile(index: number, marker: string): DiffFile { const path = `src/example${index}.ts`; diff --git a/benchmarks/large-stream-fixture.ts b/benchmarks/large-stream-fixture.ts index 82d70a667..c55366de3 100644 --- a/benchmarks/large-stream-fixture.ts +++ b/benchmarks/large-stream-fixture.ts @@ -1,5 +1,6 @@ import { parseDiffFromFile, parsePatchFiles } from "@pierre/diffs"; -import type { AppBootstrap, DiffFile } from "../src/core/types"; +import type { AppBootstrap } from "../src/core/bootstrap"; +import type { DiffFile } from "../src/core/changeset/model"; export const DEFAULT_FILE_COUNT = 180; export const DEFAULT_LINES_PER_FILE = 120; diff --git a/benchmarks/working-tree-load.ts b/benchmarks/working-tree-load.ts index 7d700ca80..d8156cdef 100644 --- a/benchmarks/working-tree-load.ts +++ b/benchmarks/working-tree-load.ts @@ -1,7 +1,7 @@ // Benchmark git-backed working-tree loading, including untracked file handling. import { performance } from "perf_hooks"; import { getBundledVcsCatalog } from "../src/app/vcsCatalog"; -import { loadAppBootstrap } from "../src/core/changesetLoaders"; +import { loadAppBootstrap } from "../src/core/changeset/loaders"; import { addUntrackedFiles, createChangedRepo } from "./lib/fixtures"; interface Scenario { diff --git a/benchmarks/wrapped-cjk.ts b/benchmarks/wrapped-cjk.ts index 30ad79e28..760e441f5 100644 --- a/benchmarks/wrapped-cjk.ts +++ b/benchmarks/wrapped-cjk.ts @@ -4,7 +4,8 @@ import { performance } from "node:perf_hooks"; import { parsePatchFiles } from "@pierre/diffs"; import { testRender } from "@opentui/react/test-utils"; import React, { act } from "react"; -import type { AppBootstrap, DiffFile } from "../src/core/types"; +import type { AppBootstrap } from "../src/core/bootstrap"; +import type { DiffFile } from "../src/core/changeset/model"; import { AppHost } from "../src/ui/AppHost"; import { prefetchHighlightedDiff } from "../src/ui/diff/useHighlightedDiff"; import { VIEWPORT_READ_COALESCE_MS } from "../src/ui/lib/viewportTiming"; diff --git a/docs/browser-review-seam-audit.md b/docs/browser-review-seam-audit.md index daaefdb2b..ba63fdd05 100644 --- a/docs/browser-review-seam-audit.md +++ b/docs/browser-review-seam-audit.md @@ -134,7 +134,7 @@ whether a client needs more than that is Phase 5's first question. _Repaid (Phase 1 PR 2)_: `reviewDefaultHunkLineTarget` in `core/review/geometry.ts`; `firstCommentTargetForHunk` deleted and both terminal callers converted; every geometry fixture pins the target, `pure-deletion-hunk` and `hunk-with-leading-context` adversarially. -- **A11. Language registration side effect missing in browser.** `core/fileLanguage.ts` +- **A11. Language registration side effect missing in browser.** `core/changeset/fileLanguage.ts` registers `.mts`/`.cts`; the web bundle never imports it, so Pierre's own inference runs unregistered for files without an explicit `language`. Fix: side-effect import in `src/web/main.tsx` (or fold registration into the shared model). @@ -515,7 +515,7 @@ here so the extraction happens before the duplication exists. Design detail in Fix: extract a renderer-neutral catalog (id, title, category, default chords, resolution locus — semantic / client-local / host-only); terminal keeps matchers and handlers, browser adds its own, both render menus/help/palette from the catalog. - _Repaid (Phase 1 PR 3)_: `src/core/commandCatalog.ts` carries id, title, category, default + _Repaid (Phase 1 PR 3)_: `src/core/run/commandCatalog.ts` carries id, title, category, default chords, resolution locus, extension visibility, and menu-closing behavior for all 44 built-ins. `ui/lib/appCommands.ts` builds its dispatch table from it — the handler map is keyed by `AppCommandId`, so a catalogued command with no terminal handler fails to typecheck — and @@ -619,6 +619,10 @@ implementation does. carrying separators, percent signs, and non-ASCII characters, and strict rejection of anything outside the grammar. No consumers yet, by design — browser deep links are Phase 5 and opener fragments Phase 6, which is when this finding closes. + _Reopened (2026-08-17, module-boundaries phase 0)_: `core/review/address.ts` is deleted. A + primitive with no consumers cannot be checked against what its consumers need, and the + `no-dead-modules` rule now flags exactly that shape. The grammar above still stands as the + design; write it again beside the first consumer that addresses a review across a boundary. - **G4. User-facing error catalog.** The repo already solves this once for agents: `src/session/agent/errors.ts` single-sources every message the generated skill quotes, with contract tests. The browser has no equivalent — action rejections (`invalid-action`, diff --git a/docs/changelog-on-hunk-dev.md b/docs/changelog-on-hunk-dev.md index f5f20094e..bad888e78 100644 --- a/docs/changelog-on-hunk-dev.md +++ b/docs/changelog-on-hunk-dev.md @@ -127,6 +127,6 @@ none), and patch chips appear only when a series has more than one release. launch-video pipeline keeps generated media out of Git. - **Contributor lists.** The GitHub release bodies name first-time contributors, which is community goodwill and organic links. `CHANGELOG.md` does not carry authors, so this needs a second input. -- **The in-app update notice.** `src/core/updateNotice.ts` tells users a new version exists without +- **The in-app update notice.** `src/core/process/updateNotice.ts` tells users a new version exists without linking what changed. Appending `hunk.dev/changelog/` is the highest-intent entry point available and is deliberately left as a separate change. diff --git a/docs/extension-architecture.md b/docs/extension-architecture.md index 1fe9600e1..e7f6c778c 100644 --- a/docs/extension-architecture.md +++ b/docs/extension-architecture.md @@ -300,7 +300,7 @@ and exact-source reading — live entirely under `src/extensions/default/vcs//`. `src/extensions/vcsPatchResult.ts` is the one conversion boundary where a published `ExtensionVcsPatchResult` becomes Hunk's internal diff model, including structural `too-large` source -results. `src/core/projectRoot.ts` treats `.hunk` as a provider-independent +results. `src/core/process/projectRoot.ts` treats `.hunk` as a provider-independent bootstrap marker and also consults the available catalog; startup performs a second root/config pass when a global, config-path, or CLI adapter recognizes a repository unavailable to the bundled catalog. diff --git a/docs/extension-system-exploration.md b/docs/extension-system-exploration.md index e8adeed38..f660f5b18 100644 --- a/docs/extension-system-exploration.md +++ b/docs/extension-system-exploration.md @@ -117,7 +117,7 @@ seams in very different states of readiness: - Dynamic theme registration exists (`ensureSyntaxHighlightThemeRegistered` → Pierre's `registerCustomTheme`), but the config layer caps custom themes at one `"custom"` slot. -- Pierre's `setCustomExtension` (`src/core/fileLanguage.ts`) is already used at +- Pierre's `setCustomExtension` (`src/core/changeset/fileLanguage.ts`) is already used at import time for `.mts`/`.cts`; extending it to plugin-declared mappings is trivial. - `StartupDeps` (`src/app/startup.ts`) fully injects the startup pipeline @@ -171,7 +171,7 @@ export default function (hunk: HunkExtensionAPI) { ```` - Discovery: `~/.config/hunk/extensions/*.ts` and `*/index.ts` (global, follows - our existing XDG path logic in `src/core/paths.ts`), `.hunk/extensions/` + our existing XDG path logic in `src/core/run/paths.ts`), `.hunk/extensions/` (repo-local, **trust-gated**, same posture as pi's project trust), explicit `[extensions] paths = [...]` in `config.toml`, and a `--extension ` dev flag. `--no-extensions` for a clean run and for bug triage. diff --git a/docs/module-boundaries.md b/docs/module-boundaries.md index 54e4a58a4..732dc52b0 100644 --- a/docs/module-boundaries.md +++ b/docs/module-boundaries.md @@ -44,17 +44,154 @@ Intentional exceptions, allowed by the rules: surface by design. - Tests are excluded: they are colocated and free to reach across boundaries. +## Module interiors + +Tier rules say which trees may reach each other; interior rules say which _files_ in a tree +outsiders may name. A module's interior is enforced the same way as a tier — one rule per +protected file, `from` everything outside the module's directory, `to` the file — so an +accidental reach-in fails `bun run deps:check` instead of quietly becoming API. + +Two supporting rules keep the interiors honest: + +- **`no-dead-modules`** flags any module under `src/` that no entry point reaches + (`main.tsx`, `highlightWorkerEntry.ts`, the `opentui` and `extension-api` facades, and the + skill generator). It uses `reachable: false` rather than `orphan`, which only catches fully + disconnected files and so misses dead code that still imports. A hit is deleted, or — when + tests are its only genuine consumer — listed in the rule's `TEST_ONLY_MODULES` allowlist + with the reason. That allowlist is **shrink-only**, like the baseline. +- **`core-leaves-stay-below-bootstrap`** freezes the cycle fix below: `core/bootstrap.ts` + composes the module directories to describe one launch, so none of them may import it back. + (Through phase 3 this rule was `core-leaves-never-reimport-types` and guarded + `core/types.ts`; phase 4 melted that shell and repointed the rule at what replaced it.) + +Phase 0 (2026-08-17) established the mechanism: it deleted `core/review/address.ts` (a +speculative primitive with no consumers), added the two rules above, and froze the first +interior — `core/review/reducer.ts` is importable only from within `core/review/`, because +callers state intent and `planReviewIntent` owns the transition. Later phases extend the same +pattern across `src/core` as its subdirectories take shape; the review model's named modules +(`document`, `identity`, `geometry`, `state`, …) stay public by design. + +Phase 1 (2026-08-17) grouped the changeset model and its acquisition pipeline — twelve loose +files at `core/*` root — into `core/changeset/`, with the surface split enforced by +`changeset-internals-stay-in-module`: + +- **Public:** `model` (the `Changeset` / `DiffFile` / `SidecarContext` shapes), `loaders` + (every input source, plus the app bootstrap built around one), `diffFile`, `fileSource`, + `fileLanguage`, `binary`, `diffPaths`, `hunkHeader`, `hunkSummary`. +- **Interior:** `fromPatch` turns patch text into the model and is reached through `loaders`; + `fileLanguageLookup` is the only reader/writer of Pierre's process-global extension table + and the import that drags in the diff engine; `sidecar` reads `--agent-context` as one step + of acquiring a changeset. + +The renames are path-only — `changeset.ts` → `changeset/model.ts`, `changesetLoaders.ts` → +`changeset/loaders.ts`, `changesetFromPatch.ts` → `changeset/fromPatch.ts`, the rest keep their +basenames — and no exported symbol changed. `core/types.ts` still re-exports the changeset and +sidecar shapes for legacy import sites (since phase 4: those sites name `changeset/model` +directly); it names `changeset/model`, which is public, so the interior rule needs no +exception. `core/patch/` stayed where it is: it was already coherent. + +Phase 2 (2026-08-17) grouped **how a run is asked for** into `core/run/`: the command +inputs (`commandInputs`), the layered config resolver (`config`), the app command catalog +(`commandCatalog`), the invocation errors (`errors` — "a failure Hunk raises because of how it +was invoked"), launch-scoped experimental features (`experimental`), XDG/app paths (`paths`), +tab-width validation (`tabWidth`), reload eligibility (`inputReload`), and the CLI version +(`version`). The move is path-only; no exported symbol changed. + +Every one of the nine is **public**: each has production importers outside the module, so this +phase adds no `run-internals-stay-in-module` rule. Its value is the grouping plus +extending the freeze: `core-leaves-never-reimport-types` now names +`core/run/commandInputs.ts` in place of the old root path. Only `commandInputs` is a +types-leaf — `config`, `experimental`, and `inputReload` import `core/types` legally, since +`core/types` re-exports from `commandInputs` and never the other way round. (Since phase 4 +there is no shell to import: those three name `commandInputs` directly, and `config` declares +the config-owned shapes itself.) + +`commandInputs` also absorbed the CLI-input half that was still declared in `core/types.ts`: +`HelpCommandInput`, `PagerCommandInput`, `DaemonServeCommandInput`, the whole +`Session*CommandInput` family with `SessionSelectorInput` / `SessionCommandOutput` / +`SessionCommentApplyItemInput`, `MarkupRenderCommandInput`, `MarkupGuideCommandInput`, the +`Extension*CommandInput` family, and `ParsedCliInput`. Two small aliases came with them because +those inputs name them and the leaf may not import `core/types` back: +`SessionCommentListType` and the `ReviewNoteSource` it unions over. `core/types.ts` re-exports +all of them, so no import site changed at the time; phase 4 moved the import sites onto +`commandInputs` and deleted the re-exports. + +Phase 3 (2026-08-17) grouped **the process and terminal a run lives in** into `core/process/`: +TTY capability detection and runtime CLI-input resolution (`terminal`), the external pager and +its plain-text fallback (`pager`), SIGTSTP/SIGINT job control (`jobControl`), ordered session +teardown (`shutdown`), `.hunk`/VCS project-root discovery (`projectRoot`), the atomically +written app-state file (`appStateFile`), the version-check notice built on it (`updateNotice`), +and the startup-notice shape every tier reports through (`startupNotice`). The move is +path-only; no exported symbol changed, and `core/types.ts` now re-exports `StartupNotice` from +`process/startupNotice` (a re-export nothing ever imported, deleted unused in phase 4). + +All eight are **public** — each has production importers outside the module — so this phase adds +no `process-internals-stay-in-module` rule; the grouping is its value. The audiences are worth +naming, because they are why these files never belonged at `core/*` root together with the review +model: `terminal`, `jobControl`, `shutdown`, and `updateNotice` serve the interactive surface; +`pager` serves the CLI entry and the startup plan; `projectRoot` and `appStateFile` serve the +extension host and config resolution. + +After this phase `src/core/` root holds only `types.ts`, `reviewDigest.ts`, and `liveComments.ts` +beside the seven subdirectories. Phase 4 melts what is left of `core/types.ts`. + +Phase 4 (2026-08-17) melted that shell. `core/types.ts` had stopped declaring most of what it +exported: 147 files imported it, almost all for names phases 1–3 had already moved elsewhere, +and the re-export list was the only thing holding those import sites to a module that no longer +owned the answer. A grab-bag that re-exports is still a grab-bag — every importer binds to it, +so nothing downstream reveals which module it actually depends on. + +Every import site was retargeted at the declaring module (mixed statements split one target per +module), the re-exports were deleted, and the file was renamed `core/types.ts` → +`core/bootstrap.ts` for what is genuinely left: `AppBootstrap` and `ReloadContext`, the contract +a composed launch hands the interactive shell. The stragglers it still declared went to the +module that owns their behaviour, one home each: + +- `TerminalThemeMode` → `core/theme/detection.ts`, which probes the terminal for it and had + been re-exporting the name from the shell. +- `ExtensionsConfig`, `UserKeyBinding`, `PersistedViewPreferences` → `core/run/config.ts`, + which resolves `[extensions]`, `[keybindings]`, and the persisted view options. +- `UserNoteLineTarget` → `core/liveComments.ts`, beside `DiffSide` and `CommentTargetInput`: it + is the line a user note hangs on, and every consumer reaches it through note code. + +Deleting the re-exports made one hidden dependency visible: `core/review/annotations.ts` names +`AgentAnnotation`, which is declared in `src/extension-api/types.ts` because it is +simultaneously an internal model type and part of the published contract. Routing that through +`core/types.ts` had disguised it as a core-local import, and `scripts/source-boundaries.test.ts` +("keeps the review model contained in core") caught it the moment the disguise came off. The +allowance is now explicit and narrow — that one file, not the tree — and it cannot widen the +seam, since `extension-api-is-import-free` forbids `extension-api/types.ts` any import at all. + +Fan-in tells the story: 147 importing files became 28 (13 outside tests) — the review stream, +the diff renderer, and the session surfaces never needed the bootstrap contract, only the +changeset and command-input models they now name. `core/bootstrap.ts` imports downward into +`changeset/model`, `run/commandInputs`, `run/config`, `process/startupNotice`, +`theme/detection`, and `vcs/types`, and `core-leaves-stay-below-bootstrap` forbids the reverse +edge from every module directory. One exception is carved out and named in the rule: +`core/changeset/loaders.ts` returns an `AppBootstrap` from `loadAppBootstrap`, so it names the +shape it assembles; that function is composition living in the domain tier, and moving it to +`src/app` retires the exception. + +`src/core/` root now holds `bootstrap.ts`, `reviewDigest.ts`, and `liveComments.ts` beside the +eight module directories. + ## Snapshot (2026-08-17, v0.19.0) -332 production modules, 1283 internal edges, **zero boundary violations and zero import -cycles** — the baseline is empty. The initial audit (2026-08-16) found 28 violations in five +331 production modules, 1322 internal edges, **zero boundary violations and zero import +cycles** — the baseline is empty. (The edge count grew from 1282 in phase 4: import sites that +used to funnel through one re-export shell now name the modules they actually depend on, so the +same dependencies are finally visible in the graph.) The initial audit (2026-08-16) found 28 +violations in five clusters and 5 file-level cycles; all were repaid in the same change series that introduced the rules: - **Cycles.** Each cycle was a type-only back-edge from a lower module into a grab-bag above - it. The cuts: `core/types.ts` gave its changeset model to `core/changeset.ts` and its - command-input model to `core/commandInputs.ts` (re-exported from `core/types` so import - sites keep working); the diff row model moved to `ui/diff/diffRowModel.ts`; the worker's + it. The cuts: `core/types.ts` gave its changeset model to `core/changeset.ts` (since phase 1, + `core/changeset/model.ts`) and its command-input model to `core/commandInputs.ts` (since phase + 2, `core/run/commandInputs.ts`; re-exported from `core/types` so import sites kept + working, until phase 4 melted that shell into `core/bootstrap.ts` and moved the sites onto the + declaring modules); the diff row model moved to + `ui/diff/diffRowModel.ts`; the worker's compact encoder was retyped structurally (`HighlightedHastLines`); `HunkSessionBrokerClient` moved beside the client class it aliases; `CopySelectedRowRange` moved into `ui/lib/diffSpatial.ts`; `extensions/notifications.ts` now imports `ExtensionNotifyType` @@ -81,12 +218,20 @@ rules: The tier rules now hold with no exceptions. Two follow-ups are worth doing next: -1. **Give `src/core` an interior.** The subdirectories (`review/`, `vcs/`, `theme/`, `watch/`, - `patch/`) are already coherent modules; the ~40 loose files at `core/*` root are the - grab-bag. Group them by audience (changeset model, config/CLI, process/runtime concerns) and - then add per-module rules restricting which files other tiers may import — the review seam's - named modules (`document`, `geometry`, `state`, …) stay public; their helpers become - internal. +1. **Give `src/core` an interior.** _Done (phases 0–4, see Module interiors)._ Every group is a + module directory — `review/`, `vcs/`, `theme/`, `watch/`, `patch/`, `changeset/`, + `run/`, `process/` — and `core/*` root is down to `bootstrap.ts`, `reviewDigest.ts`, + and `liveComments.ts`, with no grab-bag left to import. What remains is per-file public + surfaces for the modules that never got one: `changeset` has + `changeset-internals-stay-in-module` and `review` has `review-reducer-is-module-internal`, + while `run`, `process`, `theme`, `vcs`, `watch`, and `patch` are still public in full + because every file in them has an outside importer today. Two named follow-ups: move + `loadAppBootstrap` out of `core/changeset/loaders.ts` into `src/app` (it is composition, and + it is the one exception `core-leaves-stay-below-bootstrap` has to carve out), and split + `core/run/config.ts`, whose readers reach it for three unrelated reasons — the + resolved `HunkConfigResolution`, the persisted view preferences, and the extension/keybinding + tables. The review seam's named modules (`document`, `geometry`, `state`, …) stay public; + their helpers become internal. 2. **Tighten the adapter allowlist.** `ui-couples-to-session-via-adapters` currently allowlists six files. As session coupling consolidates into `useTerminalReview` / `useHunkSessionBridge`, shrink the list. diff --git a/docs/source-architecture.md b/docs/source-architecture.md index bb7498d7f..20cbc37ea 100644 --- a/docs/source-architecture.md +++ b/docs/source-architecture.md @@ -9,8 +9,15 @@ Use it when adding a new module or deciding where an existing responsibility bel ```text src/app/ executable composition: CLI parsing, startup plans, and shared session bootstrap src/app/session/ mounted-review registration, bridge, and reload authorization -src/core/ normalized review model, loading, patch handling, VCS contracts, - configuration, and runtime primitives +src/core/ review model, patch handling, VCS contracts, configuration, and + runtime primitives +src/core/changeset/ the changeset model and the pipeline that acquires one: loaders, + per-file construction, sidecar/source reads, and hunk formatting +src/core/run/ how a run is asked for: command inputs, layered configuration, the + command catalog, user-facing errors, paths, and version +src/core/process/ the process and terminal a run lives in: TTY capabilities, the pager, + job control, shutdown, project-root discovery, persisted app state, + and startup/update notices src/core/theme/ bundled theme metadata, custom-theme rules, and terminal theme detection src/core/watch/ input signatures, observation plans/backends, and refresh coordination src/core/vcs/ provider-neutral VCS catalog, contracts, operation dispatch, and host support diff --git a/scripts/build-prebuilt-artifact.test.ts b/scripts/build-prebuilt-artifact.test.ts index 7bbd530a3..847c61b4f 100644 --- a/scripts/build-prebuilt-artifact.test.ts +++ b/scripts/build-prebuilt-artifact.test.ts @@ -2,7 +2,7 @@ import { existsSync, mkdtempSync, mkdirSync, rmSync, statSync, writeFileSync } f import os from "node:os"; import path from "node:path"; import { afterEach, describe, expect, test } from "bun:test"; -import { BUNDLED_SKILL_NAMES } from "../src/core/paths"; +import { BUNDLED_SKILL_NAMES } from "../src/core/run/paths"; import { stagePrebuiltArtifact } from "./build-prebuilt-artifact"; import { binaryFilenameForSpec, getHostPlatformPackageSpec } from "./prebuilt-package-helpers"; diff --git a/scripts/build-prebuilt-artifact.ts b/scripts/build-prebuilt-artifact.ts index 454e2e814..c5a109831 100644 --- a/scripts/build-prebuilt-artifact.ts +++ b/scripts/build-prebuilt-artifact.ts @@ -2,7 +2,7 @@ import { chmodSync, cpSync, existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs"; import path from "node:path"; -import { BUNDLED_SKILL_NAMES } from "../src/core/paths"; +import { BUNDLED_SKILL_NAMES } from "../src/core/run/paths"; import { binaryFilenameForSpec, getHostPlatformPackageSpec, diff --git a/scripts/generate-docs.test.ts b/scripts/generate-docs.test.ts index 84f9536db..2a0c0793a 100644 --- a/scripts/generate-docs.test.ts +++ b/scripts/generate-docs.test.ts @@ -2,7 +2,7 @@ import { afterEach, describe, expect, test } from "bun:test"; import { mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { tmpdir } from "node:os"; -import { CONFIG_REFERENCE_OPTIONS } from "../src/core/config"; +import { CONFIG_REFERENCE_OPTIONS } from "../src/core/run/config"; import { renderHunkReviewSkill } from "../src/hunk-review/skillDocument"; import { SESSION_AGENT_COMMAND_LIST } from "../src/session/agent/surface"; import { diff --git a/scripts/generate-docs.ts b/scripts/generate-docs.ts index 7022ef8ef..8832d4ae8 100644 --- a/scripts/generate-docs.ts +++ b/scripts/generate-docs.ts @@ -13,7 +13,7 @@ import { CONFIG_REFERENCE_CUSTOM_THEME, CONFIG_REFERENCE_EXTENSIONS, CONFIG_REFERENCE_OPTIONS, -} from "../src/core/config"; +} from "../src/core/run/config"; import { renderHunkReviewSkill } from "../src/hunk-review/skillDocument"; import { type AgentCommandOption, SESSION_AGENT_COMMAND_LIST } from "../src/session/agent/surface"; import { @@ -276,7 +276,7 @@ description: Exhaustive generated reference for Hunk TOML keys, defaults, aliase ${GENERATED_NOTICE} -Hunk reads TOML preferences from the user config and an optional repository config. This reference is generated from the same catalog that \`src/core/config.ts\` uses to parse preference keys. +Hunk reads TOML preferences from the user config and an optional repository config. This reference is generated from the same catalog that \`src/core/run/config.ts\` uses to parse preference keys. ## Resolution and scope diff --git a/scripts/source-boundaries.test.ts b/scripts/source-boundaries.test.ts index f22bffe3a..a0835172e 100644 --- a/scripts/source-boundaries.test.ts +++ b/scripts/source-boundaries.test.ts @@ -8,6 +8,12 @@ const CORE_ROOT = join(SRC_ROOT, "core"); const EXTENSIONS_ROOT = join(SRC_ROOT, "extensions"); const BUNDLED_PROVIDER_ROOT = join(EXTENSIONS_ROOT, "default", "vcs"); const REVIEW_MODEL_ROOT = join(CORE_ROOT, "review"); +// The published extension contract, which the review model may name for the annotation shapes +// that are simultaneously internal model types and part of `hunkdiff/extension`. It cannot widen +// the seam: `extension-api-is-import-free` (.dependency-cruiser.cjs) forbids it any import at +// all, so it can never carry a renderer or a platform runtime in. Only this one file is allowed, +// not the tree — `extension-api/index.ts` is the runtime boundary and imports freely. +const EXTENSION_API_TYPES_PATH = join(SRC_ROOT, "extension-api", "types.ts"); const REVIEW_PROTOCOL_PATH = join(SRC_ROOT, "session", "reviewProtocol.ts"); const WEB_CLIENT_ROOT = join(SRC_ROOT, "web"); @@ -291,7 +297,7 @@ describe("shared review primitives seam", () => { } test("keeps the review model contained in core", () => { - expect(escapingImports(REVIEW_MODEL_ROOT, [CORE_ROOT])).toEqual([]); + expect(escapingImports(REVIEW_MODEL_ROOT, [CORE_ROOT, EXTENSION_API_TYPES_PATH])).toEqual([]); }); test("keeps rendering and platform runtimes out of the review model", () => { diff --git a/scripts/test-large-untracked-render.tsx b/scripts/test-large-untracked-render.tsx index c35034441..fecb6ce89 100644 --- a/scripts/test-large-untracked-render.tsx +++ b/scripts/test-large-untracked-render.tsx @@ -3,7 +3,7 @@ import { mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { act } from "react"; -import { loadAppBootstrap } from "../src/core/changesetLoaders"; +import { loadAppBootstrap } from "../src/core/changeset/loaders"; import { AppHost } from "../src/ui/AppHost"; function runGit(cwd: string, ...args: string[]) { diff --git a/src/app/cli.test.ts b/src/app/cli.test.ts index d9d734f3f..c3f3dcf53 100644 --- a/src/app/cli.test.ts +++ b/src/app/cli.test.ts @@ -9,7 +9,7 @@ import { parseCli, WATCH_OPTION, } from "./cli"; -import { resolveCliVersion } from "../core/version"; +import { resolveCliVersion } from "../core/run/version"; const tempDirs: string[] = []; diff --git a/src/app/cli.ts b/src/app/cli.ts index 18fb8fdf6..43b6a8252 100644 --- a/src/app/cli.ts +++ b/src/app/cli.ts @@ -12,13 +12,13 @@ import type { ParsedCliInput, SessionCommentListType, SessionCommentApplyItemInput, -} from "../core/types"; +} from "../core/run/commandInputs"; import { BUNDLED_SKILL_NAMES, resolveBundledSkillName, resolveBundledSkillPath, type BundledSkillName, -} from "../core/paths"; +} from "../core/run/paths"; import { type AgentCommandConstraint, type AgentCommandSpec, @@ -42,8 +42,8 @@ import { HIGHLIGHT_RANGE_MESSAGE, RELOAD_SEPARATOR_MESSAGE, } from "../session/agent/errors"; -import { DEFAULT_TAB_WIDTH, parseTabWidth } from "../core/tabWidth"; -import { resolveCliVersion } from "../core/version"; +import { DEFAULT_TAB_WIDTH, parseTabWidth } from "../core/run/tabWidth"; +import { resolveCliVersion } from "../core/run/version"; /** Structured option metadata shared by Commander registration and generated CLI docs. */ export interface CliReferenceOption { diff --git a/src/app/extensionBootstrap.test.ts b/src/app/extensionBootstrap.test.ts index 8c5a5bc8f..692d87fa7 100644 --- a/src/app/extensionBootstrap.test.ts +++ b/src/app/extensionBootstrap.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "bun:test"; -import type { HunkConfigResolution } from "../core/config"; -import type { CliInput } from "../core/types"; +import type { HunkConfigResolution } from "../core/run/config"; +import type { CliInput } from "../core/run/commandInputs"; import { createEmptyExtensionLoadResult } from "../extensions/types"; import { resolveConfiguredExtensions } from "./extensionBootstrap"; import { getBundledVcsCatalog } from "./vcsCatalog"; diff --git a/src/app/extensionBootstrap.ts b/src/app/extensionBootstrap.ts index d076ca458..1a10fc502 100644 --- a/src/app/extensionBootstrap.ts +++ b/src/app/extensionBootstrap.ts @@ -1,6 +1,6 @@ -import { resolveConfiguredCliInput, type HunkConfigResolution } from "../core/config"; -import { findProjectRootCandidate } from "../core/projectRoot"; -import type { CliInput } from "../core/types"; +import { resolveConfiguredCliInput, type HunkConfigResolution } from "../core/run/config"; +import { findProjectRootCandidate } from "../core/process/projectRoot"; +import type { CliInput } from "../core/run/commandInputs"; import { extendVcsCatalog } from "../core/vcs"; import type { VcsCatalog } from "../core/vcs/types"; import { resolveExtensionVcsAdapters } from "../extensions/apply"; diff --git a/src/app/review/producer.test.ts b/src/app/review/producer.test.ts index c9baeaac4..57fb45769 100644 --- a/src/app/review/producer.test.ts +++ b/src/app/review/producer.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "bun:test"; import { createTestDiffFile, lines } from "../../../test/helpers/diff-helpers"; -import { SourceTextTooLargeError } from "../../core/fileSource"; +import { SourceTextTooLargeError } from "../../core/changeset/fileSource"; import { parseReviewGeneration } from "../../core/review/generationOrder"; import { MAX_REVIEW_SOURCE_RESOURCE_BYTES, @@ -8,7 +8,7 @@ import { reviewResourceId, } from "../../core/review/resources"; import { createReviewStore } from "../../core/review/store"; -import type { DiffFile } from "../../core/types"; +import type { DiffFile } from "../../core/changeset/model"; import { parseReadReviewResourceRequest } from "../../core/review/resources"; import { ReviewProducer } from "./producer"; diff --git a/src/app/review/producer.ts b/src/app/review/producer.ts index 8353397d1..2073bcc2c 100644 --- a/src/app/review/producer.ts +++ b/src/app/review/producer.ts @@ -41,7 +41,7 @@ import { } from "../../core/review/resources"; import type { ReviewDigestFn } from "../../core/review/validation"; import type { ReviewStore } from "../../core/review/store"; -import type { DiffFile } from "../../core/types"; +import type { DiffFile } from "../../core/changeset/model"; import { nodeReviewDigest } from "../../core/reviewDigest"; import { buildReviewPublication, type ReviewPublication } from "./publication"; import { ReviewResourceStore, type ReviewResourceFailure } from "./resourceStore"; diff --git a/src/app/review/publication.ts b/src/app/review/publication.ts index 515d07d7a..b9af070b7 100644 --- a/src/app/review/publication.ts +++ b/src/app/review/publication.ts @@ -26,7 +26,7 @@ import { type ReviewResourceDescriptorV1, } from "../../core/review/resources"; import type { ReviewDocumentV1, ReviewFileV1 } from "../../core/review/types"; -import type { DiffFile } from "../../core/types"; +import type { DiffFile } from "../../core/changeset/model"; export interface ReviewPublication { /** Serialized generation identity; every descriptor below repeats it. */ diff --git a/src/app/review/resourceStore.ts b/src/app/review/resourceStore.ts index 236d95861..423e5a3d5 100644 --- a/src/app/review/resourceStore.ts +++ b/src/app/review/resourceStore.ts @@ -18,7 +18,7 @@ * The store belongs to one publication, so a new generation starts with nothing cached and * the previous generation's bytes become collectable as soon as it is retired. */ -import { SourceTextTooLargeError } from "../../core/fileSource"; +import { SourceTextTooLargeError } from "../../core/changeset/fileSource"; import { isMaterializedReviewResource, isReviewResourceRange, diff --git a/src/app/session/registration.test.ts b/src/app/session/registration.test.ts index 5700efbdd..e5e607f08 100644 --- a/src/app/session/registration.test.ts +++ b/src/app/session/registration.test.ts @@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test"; import { createTestDiffFile } from "../../../test/helpers/diff-helpers"; import { reviewProcessCapability } from "../review/capability"; import { buildReviewPublication } from "../review/publication"; -import type { AppBootstrap } from "../../core/types"; +import type { AppBootstrap } from "../../core/bootstrap"; import { SESSION_BROKER_REGISTRATION_VERSION } from "@hunk/session-broker-core"; import { createInitialSessionSnapshot, diff --git a/src/app/session/registration.ts b/src/app/session/registration.ts index 933c93bbf..1460d69a8 100644 --- a/src/app/session/registration.ts +++ b/src/app/session/registration.ts @@ -1,12 +1,12 @@ import { randomUUID } from "node:crypto"; import { spawnSync } from "node:child_process"; -import { resolveExperimentalFeatures } from "../../core/experimental"; +import { resolveExperimentalFeatures } from "../../core/run/experimental"; import { isVcsReviewInput } from "../../core/vcs"; -import { summarizeHunk } from "../../core/hunkSummary"; +import { summarizeHunk } from "../../core/changeset/hunkSummary"; import { reviewHunkRanges } from "../../core/review/geometry"; import { reviewProcessCapability } from "../review/capability"; import type { ReviewPublication } from "../review/publication"; -import type { AppBootstrap } from "../../core/types"; +import type { AppBootstrap } from "../../core/bootstrap"; import { SESSION_BROKER_REGISTRATION_VERSION, resolveSessionTerminalMetadata, diff --git a/src/app/session/reloadBounds.test.ts b/src/app/session/reloadBounds.test.ts index c6210c20d..d3caa2ec7 100644 --- a/src/app/session/reloadBounds.test.ts +++ b/src/app/session/reloadBounds.test.ts @@ -3,7 +3,8 @@ import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; import { describe, expect, test } from "bun:test"; import { getBundledVcsCatalog } from "../vcsCatalog"; -import type { AppBootstrap, CliInput } from "../../core/types"; +import type { AppBootstrap } from "../../core/bootstrap"; +import type { CliInput } from "../../core/run/commandInputs"; import { createSessionReloadBounds, validateSessionReloadWithinBounds } from "./reloadBounds"; /** Resolve expected paths the same way production bounds do, including Windows long names. */ diff --git a/src/app/session/reloadBounds.ts b/src/app/session/reloadBounds.ts index 23caaab8e..73ed4dc52 100644 --- a/src/app/session/reloadBounds.ts +++ b/src/app/session/reloadBounds.ts @@ -1,7 +1,8 @@ import { isAbsolute, relative, resolve } from "node:path"; -import { resolveCanonicalPath } from "../../core/paths"; -import { findProjectRootCandidate } from "../../core/projectRoot"; -import type { AppBootstrap, CliInput, CommonOptions } from "../../core/types"; +import { resolveCanonicalPath } from "../../core/run/paths"; +import { findProjectRootCandidate } from "../../core/process/projectRoot"; +import type { AppBootstrap } from "../../core/bootstrap"; +import type { CliInput, CommonOptions } from "../../core/run/commandInputs"; import type { VcsCatalog } from "../../core/vcs/types"; /** diff --git a/src/app/sessionBootstrap.test.ts b/src/app/sessionBootstrap.test.ts index 861b2c067..06035ce98 100644 --- a/src/app/sessionBootstrap.test.ts +++ b/src/app/sessionBootstrap.test.ts @@ -1,6 +1,7 @@ import { describe, expect, test } from "bun:test"; -import type { HunkConfigResolution } from "../core/config"; -import type { AppBootstrap, CliInput } from "../core/types"; +import type { HunkConfigResolution } from "../core/run/config"; +import type { AppBootstrap } from "../core/bootstrap"; +import type { CliInput } from "../core/run/commandInputs"; import { createEmptyExtensionLoadResult } from "../extensions/types"; import { loadConfiguredSessionBootstrap } from "./sessionBootstrap"; diff --git a/src/app/sessionBootstrap.ts b/src/app/sessionBootstrap.ts index 4ec391c03..2c998dbed 100644 --- a/src/app/sessionBootstrap.ts +++ b/src/app/sessionBootstrap.ts @@ -1,10 +1,10 @@ -import type { HunkConfigResolution } from "../core/config"; +import type { HunkConfigResolution } from "../core/run/config"; import { isVcsReviewInput } from "../core/vcs"; import type { VcsCatalog } from "../core/vcs/types"; import { getBundledVcsCatalog } from "./vcsCatalog"; import { collectSessionCustomThemes } from "../core/theme/customThemes"; -import { loadAppBootstrap } from "../core/changesetLoaders"; -import type { CliInput } from "../core/types"; +import { loadAppBootstrap } from "../core/changeset/loaders"; +import type { CliInput } from "../core/run/commandInputs"; import type { AppBootstrap } from "./types"; import { applyExtensionChangesetTransforms, diff --git a/src/app/sessionSelector.ts b/src/app/sessionSelector.ts index f590fecf2..9d660d7f0 100644 --- a/src/app/sessionSelector.ts +++ b/src/app/sessionSelector.ts @@ -1,5 +1,5 @@ -import { findProjectRootCandidate } from "../core/projectRoot"; -import type { SessionSelectorInput } from "../core/types"; +import { findProjectRootCandidate } from "../core/process/projectRoot"; +import type { SessionSelectorInput } from "../core/run/commandInputs"; import type { VcsCatalog } from "../core/vcs/types"; /** Attach the nearest known project boundary to one repo-path session selector. */ diff --git a/src/app/startup.test.ts b/src/app/startup.test.ts index bdf35d560..608499117 100644 --- a/src/app/startup.test.ts +++ b/src/app/startup.test.ts @@ -1,9 +1,11 @@ import { describe, expect, test } from "bun:test"; import { createEmptyExtensionLoadResult } from "../extensions/types"; -import type { HunkConfigResolution } from "../core/config"; -import { HunkUserError } from "../core/errors"; +import type { HunkConfigResolution } from "../core/run/config"; +import { HunkUserError } from "../core/run/errors"; import { prepareStartupPlan } from "./startup"; -import type { AppBootstrap, CliInput, NamedCustomThemeConfig, ParsedCliInput } from "../core/types"; +import type { AppBootstrap } from "../core/bootstrap"; +import type { CliInput, ParsedCliInput } from "../core/run/commandInputs"; +import type { NamedCustomThemeConfig } from "../extension-api/types"; /** * Build a config resolution for tests that are not exercising config layering. diff --git a/src/app/startup.ts b/src/app/startup.ts index d927b13c4..3303bb45d 100644 --- a/src/app/startup.ts +++ b/src/app/startup.ts @@ -1,17 +1,17 @@ import type { SessionBootstrapResult } from "./sessionBootstrap"; import { createExtensionApplyNotices, createUnknownVcsNotice } from "../extensions/apply"; import type { loadStartupExtensions } from "../extensions/startup"; -import { resolveConfiguredCliInput } from "../core/config"; -import { HunkUserError } from "../core/errors"; -import type { loadAppBootstrap } from "../core/changesetLoaders"; -import { looksLikePatchInput } from "../core/pager"; +import { resolveConfiguredCliInput } from "../core/run/config"; +import { HunkUserError } from "../core/run/errors"; +import type { loadAppBootstrap } from "../core/changeset/loaders"; +import { looksLikePatchInput } from "../core/process/pager"; import { detectTerminalThemeModeFromBackground } from "../core/theme/detection"; import { openControllingTerminal, resolveRuntimeCliInput, usesPipedPatchInput, type ControllingTerminal, -} from "../core/terminal"; +} from "../core/process/terminal"; import type { AppBootstrap } from "./types"; import type { CliInput, @@ -19,8 +19,8 @@ import type { MarkupRenderCommandInput, ParsedCliInput, SessionCommandInput, -} from "../core/types"; -import { canReloadInput } from "../core/inputReload"; +} from "../core/run/commandInputs"; +import { canReloadInput } from "../core/run/inputReload"; import { parseCli } from "./cli"; import { resolveSessionSelectorBoundary } from "./sessionSelector"; import type { VcsCatalog } from "../core/vcs/types"; @@ -318,7 +318,7 @@ export async function prepareStartupPlan( import("../extensions/startup"), ]); const loadAppBootstrapImpl = - deps.loadAppBootstrapImpl ?? (await import("../core/changesetLoaders")).loadAppBootstrap; + deps.loadAppBootstrapImpl ?? (await import("../core/changeset/loaders")).loadAppBootstrap; const loadStartupExtensionsImpl = deps.loadStartupExtensionsImpl ?? startupExtensions.loadStartupExtensions; diff --git a/src/app/startup.vcsExtensions.test.ts b/src/app/startup.vcsExtensions.test.ts index 18bab27fa..099b2e0d1 100644 --- a/src/app/startup.vcsExtensions.test.ts +++ b/src/app/startup.vcsExtensions.test.ts @@ -3,8 +3,8 @@ import { mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSy import { tmpdir } from "node:os"; import { join } from "node:path"; import { prepareStartupPlan } from "./startup"; -import { resolveConfiguredCliInput } from "../core/config"; -import type { CliInput, ParsedCliInput } from "../core/types"; +import { resolveConfiguredCliInput } from "../core/run/config"; +import type { CliInput, ParsedCliInput } from "../core/run/commandInputs"; const tempDirs: string[] = []; const initialCwd = process.cwd(); diff --git a/src/app/types.ts b/src/app/types.ts index da9e30cf8..201814135 100644 --- a/src/app/types.ts +++ b/src/app/types.ts @@ -1,4 +1,4 @@ -import type { AppBootstrap as CoreAppBootstrap } from "../core/types"; +import type { AppBootstrap as CoreAppBootstrap } from "../core/bootstrap"; import type { ExtensionLoadResult } from "../extensions/types"; /** Interactive app bootstrap specialized with the extension host's session state. */ diff --git a/src/core/bootstrap.ts b/src/core/bootstrap.ts new file mode 100644 index 000000000..bb54be443 --- /dev/null +++ b/src/core/bootstrap.ts @@ -0,0 +1,61 @@ +/** + * Declares the app bootstrap contract: everything a composed launch hands the + * interactive shell, plus the context that shell needs to reload the review it + * was given. + * + * This module names shapes the tiers below it already own — the changeset, the + * parsed command input, the resolved view and keybinding preferences, the + * detected theme mode — and composes them into the one value that crosses from + * startup into the UI. The module directories under `src/core` do not import it + * back — composition sits above the leaves it composes — with one exception the + * boundary rules name: `changeset/loaders.ts` assembles this value in + * `loadAppBootstrap`, so it has to name the shape it returns. + */ +import type { NamedCustomThemeConfig } from "../extension-api/types"; +import type { Changeset } from "./changeset/model"; +import type { CliInput, CursorLine, LayoutMode, SidebarVisibility } from "./run/commandInputs"; +import type { UserKeyBinding } from "./run/config"; +import type { StartupNotice } from "./process/startupNotice"; +import type { TerminalThemeMode } from "./theme/detection"; +import type { VcsCatalog } from "./vcs/types"; + +/** Where a review was loaded from, retained so the session can reload and watch it. */ +export interface ReloadContext { + cwd: string; + repoRoot?: string; + initialWatchSignature?: string; + /** Complete catalog used to load this review, retained for reload and watch. */ + vcsCatalog?: VcsCatalog; +} + +/** + * One fully resolved launch: the changeset to review, the input that asked for it, + * and every view option settled before the first frame. `initial*` fields are + * starting values the shell then owns — the shell, not this record, is where they + * change during the session. + */ +export interface AppBootstrap { + input: CliInput; + reloadContext: ReloadContext; + changeset: Changeset; + initialMode: LayoutMode; + initialTheme?: string; + initialThemeMode?: TerminalThemeMode; + /** Selectable custom themes for this session, in menu order. */ + customThemes?: readonly NamedCustomThemeConfig[]; + initialShowLineNumbers?: boolean; + initialTabWidth?: number; + initialWrapLines?: boolean; + initialShowHunkHeaders?: boolean; + initialShowMenuBar?: boolean; + initialSidebar?: SidebarVisibility; + initialShowAgentNotes?: boolean; + initialCopyDecorations?: boolean; + initialCursorLine?: CursorLine; + startupNotices?: readonly StartupNotice[]; + viewPreferencesConfigPath?: string; + /** The user's `[keybindings]` table, resolved against command defaults in App. */ + keybindings?: Record; + /** App-owned extension state carried without coupling core to the extension host. */ + extensions?: ExtensionState; +} diff --git a/src/core/binary.test.ts b/src/core/changeset/binary.test.ts similarity index 100% rename from src/core/binary.test.ts rename to src/core/changeset/binary.test.ts diff --git a/src/core/binary.ts b/src/core/changeset/binary.ts similarity index 100% rename from src/core/binary.ts rename to src/core/changeset/binary.ts diff --git a/src/core/diffFile.test.ts b/src/core/changeset/diffFile.test.ts similarity index 100% rename from src/core/diffFile.test.ts rename to src/core/changeset/diffFile.test.ts diff --git a/src/core/diffFile.ts b/src/core/changeset/diffFile.ts similarity index 99% rename from src/core/diffFile.ts rename to src/core/changeset/diffFile.ts index ce477aae3..cd944c9aa 100644 --- a/src/core/diffFile.ts +++ b/src/core/changeset/diffFile.ts @@ -4,7 +4,7 @@ import { patchLooksBinary } from "./binary"; import { fileLanguageForPath } from "./fileLanguageLookup"; import { normalizeDiffMetadataPaths, normalizeDiffPath } from "./diffPaths"; import type { FileSourceFetcher } from "./fileSource"; -import type { DiffFile, DiffLineMoveKinds, SidecarContext } from "./changeset"; +import type { DiffFile, DiffLineMoveKinds, SidecarContext } from "./model"; /** Count visible additions and deletions from parsed diff metadata. */ export function countDiffStats(metadata: FileDiffMetadata) { diff --git a/src/core/diffPaths.ts b/src/core/changeset/diffPaths.ts similarity index 100% rename from src/core/diffPaths.ts rename to src/core/changeset/diffPaths.ts diff --git a/src/core/fileLanguage.test.ts b/src/core/changeset/fileLanguage.test.ts similarity index 100% rename from src/core/fileLanguage.test.ts rename to src/core/changeset/fileLanguage.test.ts diff --git a/src/core/fileLanguage.ts b/src/core/changeset/fileLanguage.ts similarity index 100% rename from src/core/fileLanguage.ts rename to src/core/changeset/fileLanguage.ts diff --git a/src/core/fileLanguageLookup.ts b/src/core/changeset/fileLanguageLookup.ts similarity index 100% rename from src/core/fileLanguageLookup.ts rename to src/core/changeset/fileLanguageLookup.ts diff --git a/src/core/fileSource.test.ts b/src/core/changeset/fileSource.test.ts similarity index 100% rename from src/core/fileSource.test.ts rename to src/core/changeset/fileSource.test.ts diff --git a/src/core/fileSource.ts b/src/core/changeset/fileSource.ts similarity index 96% rename from src/core/fileSource.ts rename to src/core/changeset/fileSource.ts index a2e28486b..b654fdfd8 100644 --- a/src/core/fileSource.ts +++ b/src/core/changeset/fileSource.ts @@ -1,6 +1,6 @@ -import { DEFAULT_SOURCE_TEXT_MAX_BYTES, readFileTextWithLimit } from "../lib/sourceText"; +import { DEFAULT_SOURCE_TEXT_MAX_BYTES, readFileTextWithLimit } from "../../lib/sourceText"; -export { DEFAULT_SOURCE_TEXT_MAX_BYTES } from "../lib/sourceText"; +export { DEFAULT_SOURCE_TEXT_MAX_BYTES } from "../../lib/sourceText"; /** * Generic full-file source fetcher primitives used by input loaders and VCS adapters. diff --git a/src/core/changesetFromPatch.ts b/src/core/changeset/fromPatch.ts similarity index 95% rename from src/core/changesetFromPatch.ts rename to src/core/changeset/fromPatch.ts index ff388fb5e..b07a1c354 100644 --- a/src/core/changesetFromPatch.ts +++ b/src/core/changeset/fromPatch.ts @@ -3,7 +3,7 @@ * * Every input mode converges here: a VCS spawn, a two-file comparison, and a patch read from * disk or stdin all end up as text, and this module is the one that turns text into - * `DiffFile`s. Keeping it apart from `changesetLoaders.ts` keeps the parse pure — the + * `DiffFile`s. Keeping it apart from `loaders.ts` keeps the parse pure — the * loaders own the I/O, this owns the model. * * Moved-line capture has to run before sanitizing: Git marks moved lines only through SGR @@ -11,9 +11,9 @@ */ import { parsePatchFiles } from "@pierre/diffs"; import { buildDiffFile, type BuildDiffFileOptions } from "./diffFile"; -import { splitPatchIntoFileChunks, findPatchChunk } from "./patch/chunks"; -import { sanitizePatch, stripTerminalControl } from "./patch/sanitize"; -import type { Changeset, DiffLineMoveKind, DiffLineMoveKinds, SidecarContext } from "./types"; +import { splitPatchIntoFileChunks, findPatchChunk } from "../patch/chunks"; +import { sanitizePatch, stripTerminalControl } from "../patch/sanitize"; +import type { Changeset, DiffLineMoveKind, DiffLineMoveKinds, SidecarContext } from "./model"; /** Return SGR parameter strings that Git emitted before one diff line marker. */ function leadingSgrParameters(rawLine: string, expectedSign: "+" | "-") { diff --git a/src/core/hunkHeader.test.ts b/src/core/changeset/hunkHeader.test.ts similarity index 100% rename from src/core/hunkHeader.test.ts rename to src/core/changeset/hunkHeader.test.ts diff --git a/src/core/hunkHeader.ts b/src/core/changeset/hunkHeader.ts similarity index 94% rename from src/core/hunkHeader.ts rename to src/core/changeset/hunkHeader.ts index 4c4a6da80..e73a3a5c7 100644 --- a/src/core/hunkHeader.ts +++ b/src/core/changeset/hunkHeader.ts @@ -1,4 +1,4 @@ -import type { ReviewHunkSpan } from "./review/geometry"; +import type { ReviewHunkSpan } from "../review/geometry"; /** * The facts a hunk header is built from. diff --git a/src/core/hunkSummary.test.ts b/src/core/changeset/hunkSummary.test.ts similarity index 91% rename from src/core/hunkSummary.test.ts rename to src/core/changeset/hunkSummary.test.ts index 10160be66..322985aea 100644 --- a/src/core/hunkSummary.test.ts +++ b/src/core/changeset/hunkSummary.test.ts @@ -1,12 +1,12 @@ import { describe, expect, test } from "bun:test"; import type { Hunk } from "@pierre/diffs"; -import { createJsxFileViewLayout } from "../../examples/extensions/jsx-file-view"; -import { createTestDiffFile } from "../../test/helpers/diff-helpers"; -import { createFileViewInput } from "../ui/fileViews/host"; -import { validateFileViewLayout } from "../ui/fileViews/layout"; +import { createJsxFileViewLayout } from "../../../examples/extensions/jsx-file-view"; +import { createTestDiffFile } from "../../../test/helpers/diff-helpers"; +import { createFileViewInput } from "../../ui/fileViews/host"; +import { validateFileViewLayout } from "../../ui/fileViews/layout"; import { formatHunkHeader } from "./hunkHeader"; import { summarizeHunk } from "./hunkSummary"; -import { reviewHunkRanges } from "./review/geometry"; +import { reviewHunkRanges } from "../review/geometry"; describe("summarizeHunk", () => { test("summarizes every hunk Pierre parses with its header and inclusive spans", () => { diff --git a/src/core/hunkSummary.ts b/src/core/changeset/hunkSummary.ts similarity index 92% rename from src/core/hunkSummary.ts rename to src/core/changeset/hunkSummary.ts index 6a305c6cf..135cabe7d 100644 --- a/src/core/hunkSummary.ts +++ b/src/core/changeset/hunkSummary.ts @@ -1,6 +1,6 @@ -import type { ExtensionDiffHunk } from "../extension-api/types"; +import type { ExtensionDiffHunk } from "../../extension-api/types"; import { formatHunkHeader, type ReviewHunkHeaderSource } from "./hunkHeader"; -import { reviewHunkRanges } from "./review/geometry"; +import { reviewHunkRanges } from "../review/geometry"; /** Report whether one hunk carries the numeric header fields ranges derive from. */ function hasLineNumbers(hunk: ReviewHunkHeaderSource) { diff --git a/src/core/changesetLoaders.ordering.test.ts b/src/core/changeset/loaders.ordering.test.ts similarity index 92% rename from src/core/changesetLoaders.ordering.test.ts rename to src/core/changeset/loaders.ordering.test.ts index 165ca6384..3143077b4 100644 --- a/src/core/changesetLoaders.ordering.test.ts +++ b/src/core/changeset/loaders.ordering.test.ts @@ -1,7 +1,7 @@ import { describe, expect, test } from "bun:test"; -import type { SidecarContext } from "./types"; -import { orderDiffFiles } from "./changesetLoaders"; -import { createTestDiffFile } from "../../test/helpers/diff-helpers"; +import type { SidecarContext } from "./model"; +import { orderDiffFiles } from "./loaders"; +import { createTestDiffFile } from "../../../test/helpers/diff-helpers"; function sidecar(...paths: string[]): SidecarContext { return { diff --git a/src/core/changesetLoaders.test.ts b/src/core/changeset/loaders.test.ts similarity index 99% rename from src/core/changesetLoaders.test.ts rename to src/core/changeset/loaders.test.ts index afb4f3bd6..f09a7249d 100644 --- a/src/core/changesetLoaders.test.ts +++ b/src/core/changeset/loaders.test.ts @@ -11,17 +11,14 @@ import { import { platform, tmpdir } from "node:os"; import { join } from "node:path"; import { SourceTextTooLargeError } from "./fileSource"; -import { getBundledVcsCatalog } from "../app/vcsCatalog"; -import { createGitVcsAdapter } from "../extensions/default/vcs/git"; -import { toInternalVcsAdapter } from "../extensions/runExtension"; -import { createVcsCatalog } from "./vcs"; -import { - loadAppBootstrap as loadCoreAppBootstrap, - type LoadAppBootstrapOptions, -} from "./changesetLoaders"; -import type { CliInput } from "./types"; -import type { VcsAdapter } from "./vcs/types"; -import { computeWatchSignature } from "./watch/signature"; +import { getBundledVcsCatalog } from "../../app/vcsCatalog"; +import { createGitVcsAdapter } from "../../extensions/default/vcs/git"; +import { toInternalVcsAdapter } from "../../extensions/runExtension"; +import { createVcsCatalog } from "../vcs"; +import { loadAppBootstrap as loadCoreAppBootstrap, type LoadAppBootstrapOptions } from "./loaders"; +import type { CliInput } from "../run/commandInputs"; +import type { VcsAdapter } from "../vcs/types"; +import { computeWatchSignature } from "../watch/signature"; const tempDirs: string[] = []; diff --git a/src/core/changesetLoaders.ts b/src/core/changeset/loaders.ts similarity index 95% rename from src/core/changesetLoaders.ts rename to src/core/changeset/loaders.ts index 7d1ea27df..e979d3d0c 100644 --- a/src/core/changesetLoaders.ts +++ b/src/core/changeset/loaders.ts @@ -13,32 +13,30 @@ import { findSidecarFileContext, loadSidecarContext } from "./sidecar"; import { createSkippedBinaryMetadata, isProbablyBinaryFile } from "./binary"; import { buildDiffFile, type BuildDiffFileOptions, type DiffFileSourceContext } from "./diffFile"; import { createFileSourceFetcher, type FileSourceSpec } from "./fileSource"; -import { changesetFromPatch } from "./changesetFromPatch"; +import { changesetFromPatch } from "./fromPatch"; -import { DEFAULT_TAB_WIDTH } from "./tabWidth"; +import { DEFAULT_TAB_WIDTH } from "../run/tabWidth"; import { getConfiguredVcsAdapter, isVcsReviewInput, loadVcsReview, operationFromInput, -} from "./vcs"; -import type { VcsCatalog } from "./vcs/types"; -import { buildFilesystemUntrackedDiffFile } from "./vcs/untracked"; -import { computeWatchSignature } from "./watch/signature"; +} from "../vcs"; +import type { VcsCatalog } from "../vcs/types"; +import { buildFilesystemUntrackedDiffFile } from "../vcs/untracked"; +import { computeWatchSignature } from "../watch/signature"; +import type { NamedCustomThemeConfig } from "../../extension-api/types"; +import type { AppBootstrap } from "../bootstrap"; import type { - AppBootstrap, - SidecarContext, - Changeset, CliInput, - NamedCustomThemeConfig, - DiffFile, DiffToolCommandInput, FileCommandInput, PatchCommandInput, VcsShowCommandInput, VcsDiffCommandInput, VcsStashShowCommandInput, -} from "./types"; +} from "../run/commandInputs"; +import type { SidecarContext, Changeset, DiffFile } from "./model"; export interface LoadAppBootstrapOptions { cwd?: string; diff --git a/src/core/changeset.ts b/src/core/changeset/model.ts similarity index 95% rename from src/core/changeset.ts rename to src/core/changeset/model.ts index c3ca8fe6d..2fa4c8750 100644 --- a/src/core/changeset.ts +++ b/src/core/changeset/model.ts @@ -8,7 +8,7 @@ * app-facing types above them. */ import type { FileDiffMetadata } from "@pierre/diffs"; -import type { AgentFileContext } from "../extension-api/types"; +import type { AgentFileContext } from "../../extension-api/types"; import type { FileSourceFetcher } from "./fileSource"; /** One loaded review sidecar: the changeset summary plus every annotated file it names. */ diff --git a/src/core/sidecar.test.ts b/src/core/changeset/sidecar.test.ts similarity index 100% rename from src/core/sidecar.test.ts rename to src/core/changeset/sidecar.test.ts diff --git a/src/core/sidecar.ts b/src/core/changeset/sidecar.ts similarity index 97% rename from src/core/sidecar.ts rename to src/core/changeset/sidecar.ts index f949074e4..2540bd077 100644 --- a/src/core/sidecar.ts +++ b/src/core/changeset/sidecar.ts @@ -7,8 +7,8 @@ * mean the coding-agent command surface in `src/session/agent/`. */ import { resolve as resolvePath } from "node:path"; -import type { AgentAnnotation, AgentFileContext } from "../extension-api/types"; -import type { SidecarContext } from "./changeset"; +import type { AgentAnnotation, AgentFileContext } from "../../extension-api/types"; +import type { SidecarContext } from "./model"; interface SidecarLoadOptions { cwd?: string; diff --git a/src/core/commandInputs.ts b/src/core/commandInputs.ts deleted file mode 100644 index 0b8bfbaa1..000000000 --- a/src/core/commandInputs.ts +++ /dev/null @@ -1,97 +0,0 @@ -/** - * Declares the review-launching command inputs and the resolved view options - * they carry: everything a `hunk diff`/`show`/`stash show`/`patch`/`difftool` - * invocation normalizes into before a changeset is loaded. - * - * Kept as a leaf module so the VCS contract and watch planning can name these - * inputs without importing `core/types`, which layers the app-facing types - * above them. - */ -import type { - ExtensionVcsDiffInput, - ExtensionVcsShowInput, - ExtensionVcsStashShowInput, -} from "../extension-api/types"; - -export type LayoutMode = "auto" | "split" | "stack"; -export type CursorLine = "row" | "number" | "off"; -export type SidebarVisibility = boolean | "auto"; -export type VcsMode = string; - -export interface CommonOptions { - mode?: LayoutMode; - cursorLine?: CursorLine; - vcs?: VcsMode; - theme?: string; - agentContext?: string; - pager?: boolean; - watch?: boolean; - /** Enable launch-scoped experimental review features. */ - experimental?: boolean; - /** Offload eligible large-diff highlighting for this launch. */ - fast?: boolean; - excludeUntracked?: boolean; - lineNumbers?: boolean; - tabWidth?: number; - wrapLines?: boolean; - hunkHeaders?: boolean; - menuBar?: boolean; - sidebar?: SidebarVisibility; - agentNotes?: boolean; - copyDecorations?: boolean; - promptSaveViewPreferences?: boolean; - transparentBackground?: boolean; - colorMoved?: boolean; - /** False only when `--no-extensions` disables user extension loading for this run. */ - extensions?: boolean; - /** Entry paths from repeated `--extension` flags, for development and testing. */ - extensionPaths?: string[]; -} - -/** - * Review requests extend the published input views rather than restating them, - * so an adapter written against the extension contract accepts the exact values - * Hunk's commands produce. `options` is the internal half: resolved CLI and - * config state that no adapter — bundled or third-party — needs to see. - */ -export interface VcsDiffCommandInput extends ExtensionVcsDiffInput { - options: CommonOptions; -} - -export interface VcsShowCommandInput extends ExtensionVcsShowInput { - options: CommonOptions; -} - -export interface VcsStashShowCommandInput extends ExtensionVcsStashShowInput { - options: CommonOptions; -} - -export interface FileCommandInput { - kind: "diff"; - left: string; - right: string; - options: CommonOptions; -} - -export interface PatchCommandInput { - kind: "patch"; - file?: string; - text?: string; - options: CommonOptions; -} - -export interface DiffToolCommandInput { - kind: "difftool"; - left: string; - right: string; - path?: string; - options: CommonOptions; -} - -export type CliInput = - | VcsDiffCommandInput - | VcsShowCommandInput - | VcsStashShowCommandInput - | FileCommandInput - | PatchCommandInput - | DiffToolCommandInput; diff --git a/src/core/liveComments.ts b/src/core/liveComments.ts index 7c68698d2..b4d431167 100644 --- a/src/core/liveComments.ts +++ b/src/core/liveComments.ts @@ -1,8 +1,19 @@ +import type { AgentAnnotation } from "../extension-api/types"; +import type { DiffFile } from "./changeset/model"; import { reviewDefaultHunkLineTarget, reviewHunkIndexForLine } from "./review/geometry"; -import type { AgentAnnotation, DiffFile } from "./types"; export type DiffSide = "old" | "new"; +/** + * The one diff line a user note hangs on: which side of the hunk, and the line + * number on that side. Surfaces resolve a click or cursor position into this + * before asking for a note, so the note's anchor never depends on rendered rows. + */ +export interface UserNoteLineTarget { + side: DiffSide; + line: number; +} + export interface CommentTargetInput { filePath: string; hunkIndex?: number; diff --git a/src/core/patch/chunks.ts b/src/core/patch/chunks.ts index c735cd6f3..309d96381 100644 --- a/src/core/patch/chunks.ts +++ b/src/core/patch/chunks.ts @@ -1,5 +1,5 @@ import type { FileDiffMetadata } from "@pierre/diffs"; -import { normalizeDiffPath } from "../diffPaths"; +import { normalizeDiffPath } from "../changeset/diffPaths"; /** Remove git-style a/ and b/ prefixes before matching diff paths. */ function stripPrefixes(path: string) { diff --git a/src/core/patch/sanitize.ts b/src/core/patch/sanitize.ts index e96958416..79500efea 100644 --- a/src/core/patch/sanitize.ts +++ b/src/core/patch/sanitize.ts @@ -3,7 +3,7 @@ * * Patch text reaches Hunk colored by a pager, CRLF-terminated, or wrapped in `git log` * metadata, and the parser accepts none of that. "Sanitize" is the rewrite of that text; - * "normalize" stays reserved for canonicalizing paths (`core/diffPaths.ts`), so a reader can + * "normalize" stays reserved for canonicalizing paths (`core/changeset/diffPaths.ts`), so a reader can * tell the two apart by name. * * Callers that need moved-line markers must read Git's SGR colors before calling this — the diff --git a/src/core/appStateFile.test.ts b/src/core/process/appStateFile.test.ts similarity index 100% rename from src/core/appStateFile.test.ts rename to src/core/process/appStateFile.test.ts diff --git a/src/core/appStateFile.ts b/src/core/process/appStateFile.ts similarity index 100% rename from src/core/appStateFile.ts rename to src/core/process/appStateFile.ts diff --git a/src/core/jobControl.test.ts b/src/core/process/jobControl.test.ts similarity index 100% rename from src/core/jobControl.test.ts rename to src/core/process/jobControl.test.ts diff --git a/src/core/jobControl.ts b/src/core/process/jobControl.ts similarity index 100% rename from src/core/jobControl.ts rename to src/core/process/jobControl.ts diff --git a/src/core/pager.test.ts b/src/core/process/pager.test.ts similarity index 100% rename from src/core/pager.test.ts rename to src/core/process/pager.test.ts diff --git a/src/core/pager.ts b/src/core/process/pager.ts similarity index 97% rename from src/core/pager.ts rename to src/core/process/pager.ts index 7b2d4d5bc..4b2be67b1 100644 --- a/src/core/pager.ts +++ b/src/core/process/pager.ts @@ -1,7 +1,7 @@ import { spawn, type ChildProcess, type SpawnOptions } from "node:child_process"; import { parse as parseShellCommand, type ParseEntry } from "shell-quote"; -import { stripTerminalControl } from "./patch/sanitize"; -import { sanitizeTerminalText } from "../lib/terminalText"; +import { stripTerminalControl } from "../patch/sanitize"; +import { sanitizeTerminalText } from "../../lib/terminalText"; /** Detect whether generic pager stdin looks like a diff/patch that Hunk should review. */ export function looksLikePatchInput(text: string) { diff --git a/src/core/projectRoot.test.ts b/src/core/process/projectRoot.test.ts similarity index 96% rename from src/core/projectRoot.test.ts rename to src/core/process/projectRoot.test.ts index d542b4402..5fb56513d 100644 --- a/src/core/projectRoot.test.ts +++ b/src/core/process/projectRoot.test.ts @@ -3,8 +3,8 @@ import { existsSync, mkdirSync, mkdtempSync, realpathSync, rmSync, writeFileSync import { tmpdir } from "node:os"; import { dirname, join } from "node:path"; import { findProjectRootCandidate } from "./projectRoot"; -import { createVcsCatalog } from "./vcs"; -import type { VcsAdapter } from "./vcs/types"; +import { createVcsCatalog } from "../vcs"; +import type { VcsAdapter } from "../vcs/types"; const tempDirs: string[] = []; diff --git a/src/core/projectRoot.ts b/src/core/process/projectRoot.ts similarity index 95% rename from src/core/projectRoot.ts rename to src/core/process/projectRoot.ts index ef301e454..4589ff9c8 100644 --- a/src/core/projectRoot.ts +++ b/src/core/process/projectRoot.ts @@ -1,6 +1,6 @@ import fs from "node:fs"; import { dirname, join, resolve } from "node:path"; -import type { VcsCatalog } from "./vcs/types"; +import type { VcsCatalog } from "../vcs/types"; /** Return whether one path is a `.hunk` project directory, following directory symlinks. */ function isHunkProjectDirectory(path: string) { diff --git a/src/core/shutdown.test.ts b/src/core/process/shutdown.test.ts similarity index 100% rename from src/core/shutdown.test.ts rename to src/core/process/shutdown.test.ts diff --git a/src/core/shutdown.ts b/src/core/process/shutdown.ts similarity index 100% rename from src/core/shutdown.ts rename to src/core/process/shutdown.ts diff --git a/src/core/startupNotice.ts b/src/core/process/startupNotice.ts similarity index 100% rename from src/core/startupNotice.ts rename to src/core/process/startupNotice.ts diff --git a/src/core/terminal.test.ts b/src/core/process/terminal.test.ts similarity index 98% rename from src/core/terminal.test.ts rename to src/core/process/terminal.test.ts index 67a3e7284..f916d2e18 100644 --- a/src/core/terminal.test.ts +++ b/src/core/process/terminal.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test"; -import type { CliInput } from "./types"; +import type { CliInput } from "../run/commandInputs"; import { openControllingTerminal, resolveRuntimeCliInput, diff --git a/src/core/terminal.ts b/src/core/process/terminal.ts similarity index 97% rename from src/core/terminal.ts rename to src/core/process/terminal.ts index ccfd28d3d..24986f2f8 100644 --- a/src/core/terminal.ts +++ b/src/core/process/terminal.ts @@ -1,6 +1,6 @@ import fs from "node:fs"; import tty from "node:tty"; -import type { CliInput } from "./types"; +import type { CliInput } from "../run/commandInputs"; export interface AppMouseOptions { stdinIsTTY?: boolean; diff --git a/src/core/updateNotice.test.ts b/src/core/process/updateNotice.test.ts similarity index 100% rename from src/core/updateNotice.test.ts rename to src/core/process/updateNotice.test.ts diff --git a/src/core/updateNotice.ts b/src/core/process/updateNotice.ts similarity index 98% rename from src/core/updateNotice.ts rename to src/core/process/updateNotice.ts index 7d8865b19..1c5b98203 100644 --- a/src/core/updateNotice.ts +++ b/src/core/process/updateNotice.ts @@ -1,8 +1,8 @@ import { posix, win32 } from "node:path"; import { readAppStateRecord, updateAppStateRecord } from "./appStateFile"; -import { resolveAppStatePath } from "./paths"; +import { resolveAppStatePath } from "../run/paths"; import type { StartupNotice } from "./startupNotice"; -import { resolveCliVersion, UNKNOWN_CLI_VERSION } from "./version"; +import { resolveCliVersion, UNKNOWN_CLI_VERSION } from "../run/version"; const DIST_TAGS_URL = "https://registry.npmjs.org/-/package/hunkdiff/dist-tags"; const STABLE_SEMVER_PATTERN = /^\d+\.\d+\.\d+$/; diff --git a/src/core/review/address.test.ts b/src/core/review/address.test.ts deleted file mode 100644 index 62f3a7e35..000000000 --- a/src/core/review/address.test.ts +++ /dev/null @@ -1,99 +0,0 @@ -import { describe, expect, test } from "bun:test"; -import { formatReviewAddress, parseReviewAddress, type ReviewAddress } from "./address"; - -/** Identifiers that have broken naive string formats: separators, percents, unicode. */ -const AWKWARD_IDENTIFIERS = [ - "sha256:abc123", - "user:1717171717-4", - "src/nested/path.ts#2", - "key with spaces", - "100% coverage", - "ünïcøde-ключ", - "a/b/c/d", - "?query=1&other=2", -]; - -describe("review address grammar", () => { - // Intent: the canonical form is stable, so a link a client wrote stays readable. - test("serializes each address kind to its documented form", () => { - expect(formatReviewAddress({ kind: "file", fileKey: "abc" })).toBe("file/abc"); - expect(formatReviewAddress({ kind: "hunk", fileKey: "abc", hunkIndex: 2 })).toBe( - "file/abc/hunk/2", - ); - expect(formatReviewAddress({ kind: "line", fileKey: "abc", side: "old", line: 41 })).toBe( - "file/abc/line/old/41", - ); - expect(formatReviewAddress({ kind: "note", fileKey: "abc", noteId: "user:1" })).toBe( - "file/abc/note/user%3A1", - ); - }); - - // Intent: identifiers are opaque, so no key or note id can break out of the grammar. - test("round-trips every address kind through awkward identifiers", () => { - for (const identifier of AWKWARD_IDENTIFIERS) { - const addresses: ReviewAddress[] = [ - { kind: "file", fileKey: identifier }, - { kind: "hunk", fileKey: identifier, hunkIndex: 0 }, - { kind: "hunk", fileKey: identifier, hunkIndex: 17 }, - { kind: "line", fileKey: identifier, side: "old", line: 1 }, - { kind: "line", fileKey: identifier, side: "new", line: 9001 }, - { kind: "note", fileKey: identifier, noteId: identifier }, - ]; - - for (const address of addresses) { - expect(parseReviewAddress(formatReviewAddress(address))).toEqual(address); - } - } - }); - - // Intent: canonical serialization must never produce text the strict parser rejects. - test("refuses values the address grammar cannot represent", () => { - const invalid: ReviewAddress[] = [ - { kind: "file", fileKey: "" }, - { kind: "hunk", fileKey: "abc", hunkIndex: -1 }, - { kind: "hunk", fileKey: "abc", hunkIndex: 1.5 }, - { kind: "hunk", fileKey: "abc", hunkIndex: Number.POSITIVE_INFINITY }, - { kind: "line", fileKey: "abc", side: "new", line: 0 }, - { kind: "line", fileKey: "abc", side: "new", line: -1 }, - { kind: "line", fileKey: "abc", side: "new", line: 1.5 }, - { kind: "line", fileKey: "abc", side: "left" as never, line: 1 }, - { kind: "note", fileKey: "abc", noteId: "" }, - ]; - - for (const address of invalid) { - expect(() => formatReviewAddress(address)).toThrow(); - } - expect(() => formatReviewAddress({ kind: "file", fileKey: "\ud800" })).toThrow( - "must be valid Unicode", - ); - }); - - // Intent: an address is untrusted input; a half-understood one must not navigate. - test("rejects anything that is not exactly the grammar", () => { - const rejected = [ - "", - "abc", - "file", - "file/", - "files/abc", - "file/abc/", - "file/abc/hunk", - "file/abc/hunk/", - "file/abc/hunk/-1", - "file/abc/hunk/1.5", - "file/abc/hunk/1/2", - "file/abc/line/new", - "file/abc/line/left/3", - "file/abc/line/new/0", - "file/abc/line/new/x", - "file/abc/note", - "file/abc/note/", - "file/abc/row/4", - "file/%zz/hunk/0", - ]; - - for (const text of rejected) { - expect(parseReviewAddress(text)).toBeUndefined(); - } - }); -}); diff --git a/src/core/review/address.ts b/src/core/review/address.ts deleted file mode 100644 index 9df0ebd95..000000000 --- a/src/core/review/address.ts +++ /dev/null @@ -1,142 +0,0 @@ -/** - * Semantic addresses: one grammar for pointing at a place in a review. - * - * Three consumers need to name a location across a boundary — a browser deep link and its - * history entries, a terminal "copy link" command, and agent surfaces that already address - * targets by file and hunk (`docs/browser-review-seam-audit.md`, G3). Without one grammar - * each would invent its own string format and they would stop understanding each other. - * - * Addresses are built from semantic keys only: a file key, a hunk index, a side and line, - * a note id. Never an index into rendered rows — those depend on layout, expansion state, - * and window width, so an address built from them means something different in the next - * client, or in the same client one keypress later. - * - * The serialized form is a slash-separated path with percent-encoded identifier segments, - * which makes it safe inside a URL fragment without further escaping. - */ -import type { ReviewSide } from "./types"; - -/** - * One addressable thing in a review, at the granularity the caller means. - * - * `file` and `hunk` name structure; `line` names a position in the content by side, which - * exists whether or not anything is anchored there; `note` names one anchored note by its - * id, which outlives the line it currently hangs from. - */ -export type ReviewAddress = - | { kind: "file"; fileKey: string } - | { kind: "hunk"; fileKey: string; hunkIndex: number } - | { kind: "line"; fileKey: string; side: ReviewSide; line: number } - | { kind: "note"; fileKey: string; noteId: string }; - -/** Encode one required identifier, rejecting values the address grammar cannot represent. */ -function encodeIdentifier(value: string, label: "file key" | "note id") { - if (value.length === 0) { - throw new TypeError(`Review address ${label} must not be empty.`); - } - - try { - return encodeURIComponent(value); - } catch { - // A lone surrogate cannot be percent-encoded into a URL-safe identifier. - throw new TypeError(`Review address ${label} must be valid Unicode.`); - } -} - -/** Format one integer segment the parser will accept. */ -function formatIndex(value: number, label: "hunk index" | "line", minimum: number) { - if (!Number.isSafeInteger(value) || value < minimum) { - throw new RangeError(`Review address ${label} must be an integer at least ${minimum}.`); - } - return String(value); -} - -/** Validate the line side that remains a runtime value at this public boundary. */ -function formatSide(side: ReviewSide) { - if (side !== "old" && side !== "new") { - throw new TypeError("Review address line side must be old or new."); - } - return side; -} - -/** Serialize one valid address into its canonical string form. */ -export function formatReviewAddress(address: ReviewAddress): string { - const file = `file/${encodeIdentifier(address.fileKey, "file key")}`; - switch (address.kind) { - case "file": - return file; - case "hunk": - return `${file}/hunk/${formatIndex(address.hunkIndex, "hunk index", 0)}`; - case "line": - return `${file}/line/${formatSide(address.side)}/${formatIndex(address.line, "line", 1)}`; - case "note": - return `${file}/note/${encodeIdentifier(address.noteId, "note id")}`; - } -} - -/** Decode one identifier segment, rejecting an empty or malformed one. */ -function decodeSegment(segment: string | undefined) { - if (!segment) { - return undefined; - } - try { - const decoded = decodeURIComponent(segment); - return decoded.length > 0 ? decoded : undefined; - } catch { - // A stray percent sign is a malformed address, not a key containing one. - return undefined; - } -} - -/** Parse one non-negative integer segment, rejecting anything else. */ -function parseIndex(segment: string | undefined, minimum: number) { - if (segment === undefined || !/^\d+$/.test(segment)) { - return undefined; - } - const value = Number(segment); - return Number.isSafeInteger(value) && value >= minimum ? value : undefined; -} - -/** - * Parse one address, or report that the text is not one. - * - * Deliberately strict: an address that arrived from a link, a fragment, or an agent - * command is untrusted input, and a half-understood one would silently navigate somewhere - * other than where it points. Anything that is not exactly this grammar is rejected. - */ -export function parseReviewAddress(text: string): ReviewAddress | undefined { - const segments = text.split("/"); - if (segments[0] !== "file") { - return undefined; - } - - const fileKey = decodeSegment(segments[1]); - if (fileKey === undefined) { - return undefined; - } - - if (segments.length === 2) { - return { kind: "file", fileKey }; - } - - switch (segments[2]) { - case "hunk": { - const hunkIndex = segments.length === 4 ? parseIndex(segments[3], 0) : undefined; - return hunkIndex === undefined ? undefined : { kind: "hunk", fileKey, hunkIndex }; - } - case "line": { - const side = segments[3]; - // Lines are 1-based everywhere in the model, so line 0 is not an address. - const line = segments.length === 5 ? parseIndex(segments[4], 1) : undefined; - return line === undefined || (side !== "old" && side !== "new") - ? undefined - : { kind: "line", fileKey, side, line }; - } - case "note": { - const noteId = segments.length === 4 ? decodeSegment(segments[3]) : undefined; - return noteId === undefined ? undefined : { kind: "note", fileKey, noteId }; - } - default: - return undefined; - } -} diff --git a/src/core/review/annotations.ts b/src/core/review/annotations.ts index 622508154..4c2f61031 100644 --- a/src/core/review/annotations.ts +++ b/src/core/review/annotations.ts @@ -14,7 +14,8 @@ */ import { reviewHunkRanges, reviewRangesOverlap, type ReviewHunkSpan } from "./geometry"; import type { ReviewAnnotationIndex } from "./navigation"; -import type { AgentAnnotation, DiffFile } from "../types"; +import type { AgentAnnotation } from "../../extension-api/types"; +import type { DiffFile } from "../changeset/model"; /** Whether one annotation lands inside a hunk's visible span on either side. */ export function reviewAnnotationOverlapsHunk(annotation: AgentAnnotation, hunk: ReviewHunkSpan) { diff --git a/src/core/review/document.test.ts b/src/core/review/document.test.ts index ff503f99b..003afd4d9 100644 --- a/src/core/review/document.test.ts +++ b/src/core/review/document.test.ts @@ -5,7 +5,7 @@ import { lines, } from "../../../test/helpers/diff-helpers"; import { projectReviewDocument, reviewEmptyDiffReason } from "./document"; -import type { DiffFile } from "../types"; +import type { DiffFile } from "../changeset/model"; /** Build one small parsed file, optionally with expandable source. */ function testFile( diff --git a/src/core/review/document.ts b/src/core/review/document.ts index 29dc87743..d9147d89c 100644 --- a/src/core/review/document.ts +++ b/src/core/review/document.ts @@ -10,7 +10,7 @@ * Publication concerns (generations, resource descriptors, byte digests) do not appear: * they belong to the producer runtime that serves a document, not to the document itself. */ -import type { DiffFile } from "../types"; +import type { DiffFile } from "../changeset/model"; import { reviewFileContentIdentity, reviewFileKey, diff --git a/src/core/review/selectors.ts b/src/core/review/selectors.ts index ddb2968d3..699879d27 100644 --- a/src/core/review/selectors.ts +++ b/src/core/review/selectors.ts @@ -6,7 +6,7 @@ * differently. Selectors stay pure functions of state, and the ones that encode a rule * rather than a lookup say so by name. */ -import { normalizeDiffPath } from "../diffPaths"; +import { normalizeDiffPath } from "../changeset/diffPaths"; import { reviewGapId, reviewGapSourceForFile, diff --git a/src/core/review/types.ts b/src/core/review/types.ts index 0e5804841..ab8735310 100644 --- a/src/core/review/types.ts +++ b/src/core/review/types.ts @@ -9,7 +9,7 @@ * resource descriptors, digests — belong to the producer runtime and are absent here, so * nothing in this file implies a transport. */ -import type { ReviewNoteSource } from "../types"; +import type { ReviewNoteSource } from "../run/commandInputs"; export type ReviewSide = "old" | "new"; export type ReviewLineRange = readonly [number, number]; diff --git a/src/core/commandCatalog.test.ts b/src/core/run/commandCatalog.test.ts similarity index 98% rename from src/core/commandCatalog.test.ts rename to src/core/run/commandCatalog.test.ts index fe4d5f86f..649a9a835 100644 --- a/src/core/commandCatalog.test.ts +++ b/src/core/run/commandCatalog.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test"; -import { createTestReviewState } from "../../test/helpers/review-store-helpers"; +import { createTestReviewState } from "../../../test/helpers/review-store-helpers"; import { APP_COMMAND_CATALOG, appCommandCatalogEntry, diff --git a/src/core/commandCatalog.ts b/src/core/run/commandCatalog.ts similarity index 98% rename from src/core/commandCatalog.ts rename to src/core/run/commandCatalog.ts index 27f0431a9..0011247a4 100644 --- a/src/core/commandCatalog.ts +++ b/src/core/run/commandCatalog.ts @@ -25,11 +25,11 @@ * describes UI vocabulary rather than review semantics, and that module stays purely about * what a review *is*. */ -import type { ReviewIntent } from "./review/intents"; -import type { ReviewSelectionScope } from "./review/navigation"; -import { selectNormalizedSelection, selectReviewGapForSelection } from "./review/selectors"; -import type { ReviewState } from "./review/state"; -import type { ReviewLineAddressV1 } from "./review/types"; +import type { ReviewIntent } from "../review/intents"; +import type { ReviewSelectionScope } from "../review/navigation"; +import { selectNormalizedSelection, selectReviewGapForSelection } from "../review/selectors"; +import type { ReviewState } from "../review/state"; +import type { ReviewLineAddressV1 } from "../review/types"; /** Where one command's effect resolves, and therefore who may invoke it. */ export type AppCommandLocus = "semantic" | "client-local" | "host-only"; diff --git a/src/core/types.ts b/src/core/run/commandInputs.ts similarity index 57% rename from src/core/types.ts rename to src/core/run/commandInputs.ts index e67727b20..66b095ef6 100644 --- a/src/core/types.ts +++ b/src/core/run/commandInputs.ts @@ -1,107 +1,110 @@ -import type { NamedCustomThemeConfig } from "../extension-api/types"; -import type { Changeset, DiffFile } from "./changeset"; -import type { - CliInput, - CommonOptions, - CursorLine, - LayoutMode, - SidebarVisibility, -} from "./commandInputs"; -import type { StartupNotice } from "./startupNotice"; -import type { VcsCatalog } from "./vcs/types"; - /** - * Shapes that are simultaneously internal model types and part of the published - * extension contract are declared once in `src/extension-api/types.ts` — the - * module whose declarations ship — and re-exported here so internal code keeps - * importing them from `core/types`. + * Declares every shape a parsed `hunk` invocation can take: the review-launching + * inputs and the view options they carry, plus the non-review commands — help, + * pager, `daemon serve`, `session *`, `markup *`, and `extension *` — that + * `ParsedCliInput` unions together. + * + * Kept as a leaf module so the VCS contract, watch planning, and the session + * surfaces can name these inputs without importing `core/types`, which layers + * the app-facing types above them. */ -export type { - AgentAnnotation, - AgentFileContext, - CustomSyntaxColorsConfig, - CustomSyntaxScopesConfig, - CustomThemeConfig, - NamedCustomThemeConfig, -} from "../extension-api/types"; +import type { + ExtensionVcsDiffInput, + ExtensionVcsShowInput, + ExtensionVcsStashShowInput, +} from "../../extension-api/types"; + +export type LayoutMode = "auto" | "split" | "stack"; +export type CursorLine = "row" | "number" | "off"; +export type SidebarVisibility = boolean | "auto"; +export type VcsMode = string; + +export interface CommonOptions { + mode?: LayoutMode; + cursorLine?: CursorLine; + vcs?: VcsMode; + theme?: string; + agentContext?: string; + pager?: boolean; + watch?: boolean; + /** Enable launch-scoped experimental review features. */ + experimental?: boolean; + /** Offload eligible large-diff highlighting for this launch. */ + fast?: boolean; + excludeUntracked?: boolean; + lineNumbers?: boolean; + tabWidth?: number; + wrapLines?: boolean; + hunkHeaders?: boolean; + menuBar?: boolean; + sidebar?: SidebarVisibility; + agentNotes?: boolean; + copyDecorations?: boolean; + promptSaveViewPreferences?: boolean; + transparentBackground?: boolean; + colorMoved?: boolean; + /** False only when `--no-extensions` disables user extension loading for this run. */ + extensions?: boolean; + /** Entry paths from repeated `--extension` flags, for development and testing. */ + extensionPaths?: string[]; +} /** - * The changeset model and command-input shapes moved to leaf modules so the - * VCS contract and watch planning can import them without this module's - * app-facing layer; they are re-exported here to keep one import site. + * Review requests extend the published input views rather than restating them, + * so an adapter written against the extension contract accepts the exact values + * Hunk's commands produce. `options` is the internal half: resolved CLI and + * config state that no adapter — bundled or third-party — needs to see. */ -export type { - Changeset, - DiffFile, - DiffLineMoveKind, - DiffLineMoveKinds, - SidecarContext, -} from "./changeset"; -export type { - CliInput, - CommonOptions, - CursorLine, - DiffToolCommandInput, - FileCommandInput, - LayoutMode, - PatchCommandInput, - SidebarVisibility, - VcsDiffCommandInput, - VcsMode, - VcsShowCommandInput, - VcsStashShowCommandInput, -} from "./commandInputs"; - -export type TerminalThemeMode = "light" | "dark"; +export interface VcsDiffCommandInput extends ExtensionVcsDiffInput { + options: CommonOptions; +} -export type ReviewNoteSource = "ai" | "agent" | "user"; -export type SessionCommentListType = "live" | "all" | ReviewNoteSource; +export interface VcsShowCommandInput extends ExtensionVcsShowInput { + options: CommonOptions; +} -export interface UserNoteLineTarget { - side: "old" | "new"; - line: number; +export interface VcsStashShowCommandInput extends ExtensionVcsStashShowInput { + options: CommonOptions; } -/** Resolved `[extensions]` and `[extension.]` configuration for one invocation. */ -export interface ExtensionsConfig { - /** - * False when `--no-extensions` or `[extensions] enabled = false` disables loading. - * - * Scoped to user extensions. Hunk's bundled tier — the Jujutsu and Sapling - * backends — always loads: these switches exist to triage extensions you - * installed, not to drop VCS support. - */ - enabled: boolean; - /** Explicit entry paths from the user config layer. */ - paths: string[]; - /** Explicit entry paths contributed by the repo config layer; trust-gated like `.hunk/extensions`. */ - repoPaths: string[]; - /** Per-extension config tables, keyed by extension id. */ - extensionConfigs: Record>; +export interface FileCommandInput { + kind: "diff"; + left: string; + right: string; + options: CommonOptions; } -/** - * One `[keybindings]` entry: the chord(s) to bind a command to, or `false` to unbind it. - * - * Command ids are the ones the dispatch table declares — `"hunk.app.quit"`, - * `"hunk.review.nextHunk"`, or `"."` for an extension - * command. Resolution against each command's defaults lives in - * `src/ui/lib/keymap.ts`. - */ -export type UserKeyBinding = string | readonly string[] | false; +export interface PatchCommandInput { + kind: "patch"; + file?: string; + text?: string; + options: CommonOptions; +} -export interface PersistedViewPreferences { - mode: LayoutMode; - theme?: string; - showLineNumbers: boolean; - wrapLines: boolean; - showHunkHeaders: boolean; - showMenuBar: boolean; - showAgentNotes: boolean; - copyDecorations: boolean; - cursorLine: CursorLine; +export interface DiffToolCommandInput { + kind: "difftool"; + left: string; + right: string; + path?: string; + options: CommonOptions; } +export type CliInput = + | VcsDiffCommandInput + | VcsShowCommandInput + | VcsStashShowCommandInput + | FileCommandInput + | PatchCommandInput + | DiffToolCommandInput; + +/** + * Note provenance, and the filter `hunk session comment-list --type` accepts. + * Declared here because the session command inputs name them; `core/types` + * re-exports both for the review model and the session surfaces. + */ +export type ReviewNoteSource = "ai" | "agent" | "user"; +export type SessionCommentListType = "live" | "all" | ReviewNoteSource; + export interface HelpCommandInput { kind: "help"; text: string; @@ -326,37 +329,3 @@ export type ParsedCliInput = | MarkupRenderCommandInput | MarkupGuideCommandInput | ExtensionManageCommandInput; - -export interface ReloadContext { - cwd: string; - repoRoot?: string; - initialWatchSignature?: string; - /** Complete catalog used to load this review, retained for reload and watch. */ - vcsCatalog?: VcsCatalog; -} - -export interface AppBootstrap { - input: CliInput; - reloadContext: ReloadContext; - changeset: Changeset; - initialMode: LayoutMode; - initialTheme?: string; - initialThemeMode?: TerminalThemeMode; - /** Selectable custom themes for this session, in menu order. */ - customThemes?: readonly NamedCustomThemeConfig[]; - initialShowLineNumbers?: boolean; - initialTabWidth?: number; - initialWrapLines?: boolean; - initialShowHunkHeaders?: boolean; - initialShowMenuBar?: boolean; - initialSidebar?: SidebarVisibility; - initialShowAgentNotes?: boolean; - initialCopyDecorations?: boolean; - initialCursorLine?: CursorLine; - startupNotices?: readonly StartupNotice[]; - viewPreferencesConfigPath?: string; - /** The user's `[keybindings]` table, resolved against command defaults in App. */ - keybindings?: Record; - /** App-owned extension state carried without coupling core to the extension host. */ - extensions?: ExtensionState; -} diff --git a/src/core/config.test.ts b/src/core/run/config.test.ts similarity index 99% rename from src/core/config.test.ts rename to src/core/run/config.test.ts index 53736d4ab..5ebe1adc0 100644 --- a/src/core/config.test.ts +++ b/src/core/run/config.test.ts @@ -2,16 +2,19 @@ import { afterEach, describe, expect, test } from "bun:test"; import { mkdtempSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { getBundledVcsCatalog } from "../app/vcsCatalog"; -import type { CliInput } from "./types"; +import { getBundledVcsCatalog } from "../../app/vcsCatalog"; +import type { CliInput } from "./commandInputs"; import { diffPersistedViewPreferences, resolveConfiguredCliInput, saveGlobalViewPreferences, saveViewPreferencesPromptPreference, } from "./config"; -import { loadAppBootstrap } from "./changesetLoaders"; -import { LEGACY_CUSTOM_SYNTAX_NOTICE, LEGACY_CUSTOM_SYNTAX_NOTICES } from "./startupNotice"; +import { loadAppBootstrap } from "../changeset/loaders"; +import { + LEGACY_CUSTOM_SYNTAX_NOTICE, + LEGACY_CUSTOM_SYNTAX_NOTICES, +} from "../process/startupNotice"; const tempDirs: string[] = []; diff --git a/src/core/config.ts b/src/core/run/config.ts similarity index 95% rename from src/core/config.ts rename to src/core/run/config.ts index 850a560a8..d04fc614d 100644 --- a/src/core/config.ts +++ b/src/core/run/config.ts @@ -1,7 +1,7 @@ import fs from "node:fs"; import { dirname, join } from "node:path"; -import { sanitizeTerminalLine } from "../lib/terminalText"; -import { BUNDLED_SHIKI_THEME_IDS, LEGACY_THEME_ID_ALIASES } from "./theme/catalog"; +import { sanitizeTerminalLine } from "../../lib/terminalText"; +import { BUNDLED_SHIKI_THEME_IDS, LEGACY_THEME_ID_ALIASES } from "../theme/catalog"; import { createInvalidThemeIdNotice, createThemeCollisionNotice, @@ -11,31 +11,71 @@ import { LEGACY_CUSTOM_THEME_ID, normalizeThemeColorValue, resolveThemeBase, -} from "./theme/customThemes"; +} from "../theme/customThemes"; import { LEGACY_CUSTOM_SYNTAX_COLOR_KEYS, resolveSyntaxScopeOverrides, -} from "./theme/legacySyntaxScopes"; +} from "../theme/legacySyntaxScopes"; import { resolveGlobalConfigPath } from "./paths"; -import { LEGACY_CUSTOM_SYNTAX_NOTICES, type StartupNotice } from "./startupNotice"; +import { LEGACY_CUSTOM_SYNTAX_NOTICES, type StartupNotice } from "../process/startupNotice"; import { DEFAULT_TAB_WIDTH, validateTabWidth } from "./tabWidth"; -import { findProjectRootCandidate } from "./projectRoot"; -import { createVcsCatalog, detectVcs } from "./vcs"; -import type { VcsCatalog } from "./vcs/types"; +import { findProjectRootCandidate } from "../process/projectRoot"; +import { createVcsCatalog, detectVcs } from "../vcs"; +import type { VcsCatalog } from "../vcs/types"; +import type { + CustomSyntaxColorsConfig, + CustomSyntaxScopesConfig, + NamedCustomThemeConfig, +} from "../../extension-api/types"; import type { CliInput, CommonOptions, CursorLine, - CustomSyntaxColorsConfig, - CustomSyntaxScopesConfig, - ExtensionsConfig, LayoutMode, - NamedCustomThemeConfig, - PersistedViewPreferences, SidebarVisibility, - UserKeyBinding, VcsMode, -} from "./types"; +} from "./commandInputs"; + +/** Resolved `[extensions]` and `[extension.]` configuration for one invocation. */ +export interface ExtensionsConfig { + /** + * False when `--no-extensions` or `[extensions] enabled = false` disables loading. + * + * Scoped to user extensions. Hunk's bundled tier — the Jujutsu and Sapling + * backends — always loads: these switches exist to triage extensions you + * installed, not to drop VCS support. + */ + enabled: boolean; + /** Explicit entry paths from the user config layer. */ + paths: string[]; + /** Explicit entry paths contributed by the repo config layer; trust-gated like `.hunk/extensions`. */ + repoPaths: string[]; + /** Per-extension config tables, keyed by extension id. */ + extensionConfigs: Record>; +} + +/** + * One `[keybindings]` entry: the chord(s) to bind a command to, or `false` to unbind it. + * + * Command ids are the ones the dispatch table declares — `"hunk.app.quit"`, + * `"hunk.review.nextHunk"`, or `"."` for an extension + * command. Resolution against each command's defaults lives in + * `src/ui/lib/keymap.ts`. + */ +export type UserKeyBinding = string | readonly string[] | false; + +/** The view options a session persists back to config when the reader saves them. */ +export interface PersistedViewPreferences { + mode: LayoutMode; + theme?: string; + showLineNumbers: boolean; + wrapLines: boolean; + showHunkHeaders: boolean; + showMenuBar: boolean; + showAgentNotes: boolean; + copyDecorations: boolean; + cursorLine: CursorLine; +} export const BUILT_IN_THEME_IDS = BUNDLED_SHIKI_THEME_IDS; // Widen the large literal tuple before formatting it, avoiding TypeScript's deep tuple inference. diff --git a/src/core/errors.test.ts b/src/core/run/errors.test.ts similarity index 96% rename from src/core/errors.test.ts rename to src/core/run/errors.test.ts index 8242177b5..fc6744691 100644 --- a/src/core/errors.test.ts +++ b/src/core/run/errors.test.ts @@ -1,6 +1,6 @@ import { afterEach, describe, expect, test } from "bun:test"; import { formatCliError, HunkUserError, isUserFacingError } from "./errors"; -import { HunkExtensionUserError } from "../extension-api/types"; +import { HunkExtensionUserError } from "../../extension-api/types"; const originalDebug = process.env.HUNK_DEBUG; diff --git a/src/core/errors.ts b/src/core/run/errors.ts similarity index 98% rename from src/core/errors.ts rename to src/core/run/errors.ts index bc9ac9222..8e96f7003 100644 --- a/src/core/errors.ts +++ b/src/core/run/errors.ts @@ -1,4 +1,4 @@ -import { HUNK_EXTENSION_USER_ERROR_NAME, HunkExtensionUserError } from "../extension-api/types"; +import { HUNK_EXTENSION_USER_ERROR_NAME, HunkExtensionUserError } from "../../extension-api/types"; /** * A failure Hunk raises because of how it was invoked, not because of a bug. diff --git a/src/core/experimental.test.ts b/src/core/run/experimental.test.ts similarity index 97% rename from src/core/experimental.test.ts rename to src/core/run/experimental.test.ts index a0d034b21..d16c51ac3 100644 --- a/src/core/experimental.test.ts +++ b/src/core/run/experimental.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test"; -import { createTestAgentFileContext, createTestDiffFile } from "../../test/helpers/diff-helpers"; +import { createTestAgentFileContext, createTestDiffFile } from "../../../test/helpers/diff-helpers"; import { resolveExperimentalDiffFiles, resolveExperimentalFeatures } from "./experimental"; describe("experimental review features", () => { diff --git a/src/core/experimental.ts b/src/core/run/experimental.ts similarity index 89% rename from src/core/experimental.ts rename to src/core/run/experimental.ts index 8a4362988..785416e92 100644 --- a/src/core/experimental.ts +++ b/src/core/run/experimental.ts @@ -1,4 +1,6 @@ -import type { AgentFileContext, CommonOptions, DiffFile } from "./types"; +import type { AgentFileContext } from "../../extension-api/types"; +import type { DiffFile } from "../changeset/model"; +import type { CommonOptions } from "./commandInputs"; export const EXPERIMENTAL_FEATURES = ["stml"] as const; export type ExperimentalFeature = (typeof EXPERIMENTAL_FEATURES)[number]; diff --git a/src/core/inputReload.ts b/src/core/run/inputReload.ts similarity index 93% rename from src/core/inputReload.ts rename to src/core/run/inputReload.ts index 136025a10..5df22ea3c 100644 --- a/src/core/inputReload.ts +++ b/src/core/run/inputReload.ts @@ -8,7 +8,7 @@ * signature machinery so those callers can ask it without pulling either in. */ -import type { CliInput } from "./types"; +import type { CliInput } from "./commandInputs"; /** Return whether the current input can be rebuilt from files or VCS state without rereading stdin. */ export function canReloadInput(input: CliInput) { diff --git a/src/core/paths.test.ts b/src/core/run/paths.test.ts similarity index 100% rename from src/core/paths.test.ts rename to src/core/run/paths.test.ts diff --git a/src/core/paths.ts b/src/core/run/paths.ts similarity index 100% rename from src/core/paths.ts rename to src/core/run/paths.ts diff --git a/src/core/tabWidth.ts b/src/core/run/tabWidth.ts similarity index 100% rename from src/core/tabWidth.ts rename to src/core/run/tabWidth.ts diff --git a/src/core/version.ts b/src/core/run/version.ts similarity index 83% rename from src/core/version.ts rename to src/core/run/version.ts index 795778a40..69ae8f439 100644 --- a/src/core/version.ts +++ b/src/core/run/version.ts @@ -1,4 +1,4 @@ -import packageJson from "../../package.json" with { type: "json" }; +import packageJson from "../../../package.json" with { type: "json" }; export const UNKNOWN_CLI_VERSION = "0.0.0-unknown"; diff --git a/src/core/theme/customThemes.test.ts b/src/core/theme/customThemes.test.ts index b36ca4dd0..72e87dd98 100644 --- a/src/core/theme/customThemes.test.ts +++ b/src/core/theme/customThemes.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test"; -import type { NamedCustomThemeConfig } from "../types"; +import type { NamedCustomThemeConfig } from "../../extension-api/types"; import type { RegisteredCustomTheme } from "./customThemes"; import { collectSessionCustomThemes, describeCustomThemeIdIssue } from "./customThemes"; diff --git a/src/core/theme/customThemes.ts b/src/core/theme/customThemes.ts index d479a0f5e..0cfb1ab70 100644 --- a/src/core/theme/customThemes.ts +++ b/src/core/theme/customThemes.ts @@ -1,5 +1,5 @@ -import type { StartupNotice } from "../startupNotice"; -import type { NamedCustomThemeConfig } from "../types"; +import type { StartupNotice } from "../process/startupNotice"; +import type { NamedCustomThemeConfig } from "../../extension-api/types"; import { BUNDLED_SHIKI_THEME_IDS, resolveBundledShikiThemeId } from "./catalog"; import { LEGACY_CUSTOM_SYNTAX_COLOR_KEYS } from "./legacySyntaxScopes"; diff --git a/src/core/theme/detection.ts b/src/core/theme/detection.ts index c7a458a36..b6e2d35ad 100644 --- a/src/core/theme/detection.ts +++ b/src/core/theme/detection.ts @@ -1,6 +1,8 @@ -import type { TerminalThemeMode } from "../types"; - -export type { TerminalThemeMode } from "../types"; +/** + * Which of the two terminal backgrounds a session is drawn against. Probing the + * terminal answers it here; the app carries the answer into theme selection. + */ +export type TerminalThemeMode = "light" | "dark"; export interface RgbColor { red: number; diff --git a/src/core/theme/legacySyntaxScopes.ts b/src/core/theme/legacySyntaxScopes.ts index 9f60eb03f..ae182f35a 100644 --- a/src/core/theme/legacySyntaxScopes.ts +++ b/src/core/theme/legacySyntaxScopes.ts @@ -1,4 +1,4 @@ -import type { CustomSyntaxColorsConfig, CustomSyntaxScopesConfig } from "../types"; +import type { CustomSyntaxColorsConfig, CustomSyntaxScopesConfig } from "../../extension-api/types"; /** Deprecated role keys accepted only during the temporary configuration migration window. */ export const LEGACY_CUSTOM_SYNTAX_COLOR_KEYS = [ diff --git a/src/core/vcs/index.test.ts b/src/core/vcs/index.test.ts index 1a5179bab..b1d337c45 100644 --- a/src/core/vcs/index.test.ts +++ b/src/core/vcs/index.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test"; -import { HunkUserError } from "../errors"; +import { HunkUserError } from "../run/errors"; import { createUnsupportedVcsOperationError, createVcsCatalog, diff --git a/src/core/vcs/index.ts b/src/core/vcs/index.ts index b15a89ec8..53113bc91 100644 --- a/src/core/vcs/index.ts +++ b/src/core/vcs/index.ts @@ -1,7 +1,7 @@ import { relative, resolve } from "node:path"; import { HUNK_DEFAULT_VCS_DETECTION_PRIORITY } from "../../extension-api/types"; -import { HunkUserError } from "../errors"; -import type { CliInput } from "../commandInputs"; +import { HunkUserError } from "../run/errors"; +import type { CliInput } from "../run/commandInputs"; import type { VcsAdapter, VcsCatalog, diff --git a/src/core/vcs/types.ts b/src/core/vcs/types.ts index 5c62503e9..bb056911c 100644 --- a/src/core/vcs/types.ts +++ b/src/core/vcs/types.ts @@ -1,11 +1,11 @@ import type { ExtensionVcsWatchPlan } from "../../extension-api/types"; -import type { DiffFile } from "../changeset"; +import type { DiffFile } from "../changeset/model"; import type { VcsDiffCommandInput, VcsShowCommandInput, VcsStashShowCommandInput, -} from "../commandInputs"; -import type { BuildDiffFileOptions } from "../diffFile"; +} from "../run/commandInputs"; +import type { BuildDiffFileOptions } from "../changeset/diffFile"; export type VcsId = string; diff --git a/src/core/vcs/untracked.ts b/src/core/vcs/untracked.ts index d8fb64657..ea5187aa7 100644 --- a/src/core/vcs/untracked.ts +++ b/src/core/vcs/untracked.ts @@ -1,8 +1,8 @@ import fs from "node:fs"; import { join } from "node:path"; -import { createSkippedBinaryMetadata, isProbablyBinaryFile } from "../binary"; -import { buildDiffFile, createSkippedLargeMetadata } from "../diffFile"; -import { createFileSourceFetcher } from "../fileSource"; +import { createSkippedBinaryMetadata, isProbablyBinaryFile } from "../changeset/binary"; +import { buildDiffFile, createSkippedLargeMetadata } from "../changeset/diffFile"; +import { createFileSourceFetcher } from "../changeset/fileSource"; import { inspectLargeUntrackedFile } from "../../lib/largeFile"; import { escapeUntrackedPatchPath } from "../../lib/patchPath"; import { parseSingleFilePatch } from "../patch/singleFile"; diff --git a/src/core/watch/plan.test.ts b/src/core/watch/plan.test.ts index e8fdffd16..d894cdad6 100644 --- a/src/core/watch/plan.test.ts +++ b/src/core/watch/plan.test.ts @@ -1,7 +1,7 @@ import { describe, expect, test } from "bun:test"; import { posix, win32 } from "node:path"; import { getBundledVcsCatalog } from "../../app/vcsCatalog"; -import type { CliInput } from "../types"; +import type { CliInput } from "../run/commandInputs"; import { createVcsCatalog } from "../vcs"; import type { VcsAdapter } from "../vcs/types"; import { resolveWatchPlan as resolveCoreWatchPlan, type WatchPlanContext } from "./plan"; diff --git a/src/core/watch/plan.ts b/src/core/watch/plan.ts index e31be886c..902dd869c 100644 --- a/src/core/watch/plan.ts +++ b/src/core/watch/plan.ts @@ -7,7 +7,7 @@ import type { ExtensionVcsWatchTargetSource, } from "../../extension-api/types"; import { normalizePathForOS } from "../../lib/osPath"; -import type { CliInput } from "../commandInputs"; +import type { CliInput } from "../run/commandInputs"; import { createVcsWatchPlan, getConfiguredVcsAdapter, operationFromInput } from "../vcs"; import type { VcsCatalog } from "../vcs/types"; diff --git a/src/core/watch/signature.test.ts b/src/core/watch/signature.test.ts index c023c25ae..0d68a22be 100644 --- a/src/core/watch/signature.test.ts +++ b/src/core/watch/signature.test.ts @@ -3,7 +3,7 @@ import { mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { getBundledVcsCatalog } from "../../app/vcsCatalog"; -import type { CliInput } from "../types"; +import type { CliInput } from "../run/commandInputs"; import { createVcsCatalog } from "../vcs"; import type { VcsAdapter } from "../vcs/types"; import { diff --git a/src/core/watch/signature.ts b/src/core/watch/signature.ts index 682645cbb..bea1c3a87 100644 --- a/src/core/watch/signature.ts +++ b/src/core/watch/signature.ts @@ -1,7 +1,7 @@ import fs from "node:fs"; import { resolve } from "node:path"; import { createVcsWatchSignature, getConfiguredVcsAdapter, operationFromInput } from "../vcs"; -import type { CliInput } from "../types"; +import type { CliInput } from "../run/commandInputs"; import type { VcsCatalog } from "../vcs/types"; /** Format one file stat into a stable signature fragment, or mark the path missing. */ diff --git a/src/extensions/apply.test.ts b/src/extensions/apply.test.ts index 2be6cf2a7..2380bdf61 100644 --- a/src/extensions/apply.test.ts +++ b/src/extensions/apply.test.ts @@ -7,7 +7,7 @@ import { HUNK_CORE_VCS_DETECTION_PRIORITY, HUNK_DEFAULT_VCS_DETECTION_PRIORITY, } from "../extension-api/types"; -import type { Changeset, DiffFile } from "../core/types"; +import type { Changeset, DiffFile } from "../core/changeset/model"; import { extendVcsCatalog } from "../core/vcs"; import type { VcsAdapter } from "../core/vcs/types"; import { getBundledVcsCatalog } from "../app/vcsCatalog"; @@ -107,7 +107,7 @@ describe("extension file languages", () => { }); test("lets the last extension registration win for the same file extension", async () => { - const { fileLanguageForPath } = await import("../core/fileLanguageLookup"); + const { fileLanguageForPath } = await import("../core/changeset/fileLanguageLookup"); const { result } = createTestLoadResult(); result.registry.fileLanguages.push( { extensionId: "first", extension: "hunkfixture", language: "python" }, diff --git a/src/extensions/apply.ts b/src/extensions/apply.ts index 6bce86d50..3542ed889 100644 --- a/src/extensions/apply.ts +++ b/src/extensions/apply.ts @@ -1,6 +1,9 @@ -import { BUILT_IN_FILE_LANGUAGE_EXTENSIONS, registerFileLanguage } from "../core/fileLanguage"; -import type { StartupNotice } from "../core/startupNotice"; -import type { Changeset } from "../core/types"; +import { + BUILT_IN_FILE_LANGUAGE_EXTENSIONS, + registerFileLanguage, +} from "../core/changeset/fileLanguage"; +import type { StartupNotice } from "../core/process/startupNotice"; +import type { Changeset } from "../core/changeset/model"; import { detectVcs, extendVcsCatalog, getDefaultVcsAdapter } from "../core/vcs"; import type { VcsAdapter, VcsCatalog } from "../core/vcs/types"; import { sanitizeTerminalLine } from "../lib/terminalText"; diff --git a/src/extensions/discovery.ts b/src/extensions/discovery.ts index 0c58ce53b..7806ec6ab 100644 --- a/src/extensions/discovery.ts +++ b/src/extensions/discovery.ts @@ -1,8 +1,8 @@ import fs from "node:fs"; import { homedir } from "node:os"; import { basename, isAbsolute, join, resolve } from "node:path"; -import { INSTALLED_EXTENSIONS_DIR_NAME, resolveGlobalExtensionsDir } from "../core/paths"; -import { findProjectRootCandidate } from "../core/projectRoot"; +import { INSTALLED_EXTENSIONS_DIR_NAME, resolveGlobalExtensionsDir } from "../core/run/paths"; +import { findProjectRootCandidate } from "../core/process/projectRoot"; import { deriveExtensionId, type ExtensionCandidate, type ExtensionOrigin } from "./types"; /** Entry-file suffixes Hunk will import directly, in preference order. */ diff --git a/src/extensions/events.ts b/src/extensions/events.ts index 8709c5b2c..4d3bfa636 100644 --- a/src/extensions/events.ts +++ b/src/extensions/events.ts @@ -14,7 +14,7 @@ import type { ExtensionReviewNavigation, ExtensionVcsFileChangeType, } from "../extension-api/types"; -import { summarizeHunk } from "../core/hunkSummary"; +import { summarizeHunk } from "../core/changeset/hunkSummary"; /** * How long `shutdown` handlers may run before Hunk exits anyway. diff --git a/src/extensions/host.test.ts b/src/extensions/host.test.ts index bfdbedf8d..9eb55bce9 100644 --- a/src/extensions/host.test.ts +++ b/src/extensions/host.test.ts @@ -2,7 +2,7 @@ import { afterEach, describe, expect, test } from "bun:test"; import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import type { Changeset } from "../core/types"; +import type { Changeset } from "../core/changeset/model"; import { getVcsOperation } from "../core/vcs"; import type { VcsAdapter } from "../core/vcs/types"; import { discoverExtensions } from "./discovery"; diff --git a/src/extensions/host.ts b/src/extensions/host.ts index a5704d1cd..b2197b5bf 100644 --- a/src/extensions/host.ts +++ b/src/extensions/host.ts @@ -1,5 +1,5 @@ import { pathToFileURL } from "node:url"; -import { findProjectRootCandidate } from "../core/projectRoot"; +import { findProjectRootCandidate } from "../core/process/projectRoot"; import { EXTENSION_ID_RULE, HUNK_VENDOR_EXTENSION_ID, isValidExtensionId } from "./extensionIds"; import { bindExtensionEventBus } from "./events"; import { registerHostRuntimeModules } from "./hostRuntimeModules"; diff --git a/src/extensions/manage/cli.ts b/src/extensions/manage/cli.ts index 42f1af87f..1bc9728af 100644 --- a/src/extensions/manage/cli.ts +++ b/src/extensions/manage/cli.ts @@ -1,6 +1,6 @@ -import { HunkUserError } from "../../core/errors"; -import { resolveInstalledExtensionsRoot } from "../../core/paths"; -import type { ExtensionManageCommandInput } from "../../core/types"; +import { HunkUserError } from "../../core/run/errors"; +import { resolveInstalledExtensionsRoot } from "../../core/run/paths"; +import type { ExtensionManageCommandInput } from "../../core/run/commandInputs"; import { installExtension, listExtensions, diff --git a/src/extensions/manage/install.ts b/src/extensions/manage/install.ts index 7c8558c14..08beb135f 100644 --- a/src/extensions/manage/install.ts +++ b/src/extensions/manage/install.ts @@ -1,6 +1,6 @@ import { existsSync, mkdirSync, readFileSync, renameSync, rmSync } from "node:fs"; import { basename, dirname, join } from "node:path"; -import { HunkUserError } from "../../core/errors"; +import { HunkUserError } from "../../core/run/errors"; import { directoryContainsExtensionEntries } from "../discovery"; import { readInstallRecords, writeInstallRecords, type ExtensionInstallRecord } from "./records"; import type { ExtensionInstallSource } from "./source"; diff --git a/src/extensions/manage/records.ts b/src/extensions/manage/records.ts index c0e3188a5..015bc2990 100644 --- a/src/extensions/manage/records.ts +++ b/src/extensions/manage/records.ts @@ -1,5 +1,5 @@ import { join } from "node:path"; -import { readAppStateRecord, writeAppStateRecord } from "../../core/appStateFile"; +import { readAppStateRecord, writeAppStateRecord } from "../../core/process/appStateFile"; /** * What `hunk extension install` remembers about one managed install. diff --git a/src/extensions/publicApiRobustness.test.ts b/src/extensions/publicApiRobustness.test.ts index 6469d0332..486e634ba 100644 --- a/src/extensions/publicApiRobustness.test.ts +++ b/src/extensions/publicApiRobustness.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "bun:test"; import { collectSessionCustomThemes } from "../core/theme/customThemes"; -import type { Changeset } from "../core/types"; +import type { Changeset } from "../core/changeset/model"; import { detectVcs, extendVcsCatalog } from "../core/vcs"; import { getBundledVcsCatalog } from "../app/vcsCatalog"; import { createTestDiffFile } from "../../test/helpers/diff-helpers"; diff --git a/src/extensions/runExtension.ts b/src/extensions/runExtension.ts index 517510366..3fdb00b58 100644 --- a/src/extensions/runExtension.ts +++ b/src/extensions/runExtension.ts @@ -22,7 +22,7 @@ import { type HunkExtensionAPI, } from "./types"; import { parseKeyChord, toKeyChordList } from "../lib/commandKeys"; -import { toUserFacingError } from "../core/errors"; +import { toUserFacingError } from "../core/run/errors"; import { toInternalVcsPatchResult } from "./vcsPatchResult"; import type { ExtensionVcsOperation } from "../extension-api/types"; import type { VcsAdapter, VcsOperation, VcsReviewInput } from "../core/vcs/types"; diff --git a/src/extensions/startup.test.ts b/src/extensions/startup.test.ts index 10fb9b205..1e12af04c 100644 --- a/src/extensions/startup.test.ts +++ b/src/extensions/startup.test.ts @@ -2,7 +2,7 @@ import { afterEach, describe, expect, test } from "bun:test"; import { mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import type { ExtensionsConfig } from "../core/types"; +import type { ExtensionsConfig } from "../core/run/config"; import { createExtensionLoadNotices, loadStartupExtensions, mergeStartupNotices } from "./startup"; import { createEmptyExtensionLoadResult } from "./types"; diff --git a/src/extensions/startup.ts b/src/extensions/startup.ts index 09d38b6fd..c46d8e19b 100644 --- a/src/extensions/startup.ts +++ b/src/extensions/startup.ts @@ -1,6 +1,6 @@ import { isDeepStrictEqual } from "node:util"; -import type { StartupNotice } from "../core/startupNotice"; -import type { ExtensionsConfig } from "../core/types"; +import type { StartupNotice } from "../core/process/startupNotice"; +import type { ExtensionsConfig } from "../core/run/config"; import { sanitizeTerminalText } from "../lib/terminalText"; import { discoverExtensions } from "./discovery"; import { retireExtensionLoadResult } from "./events"; diff --git a/src/extensions/trust.test.ts b/src/extensions/trust.test.ts index 014d806fb..f0a90f330 100644 --- a/src/extensions/trust.test.ts +++ b/src/extensions/trust.test.ts @@ -1,7 +1,7 @@ import { afterEach, describe, expect, test } from "bun:test"; import { mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; -import { resolveCanonicalPath } from "../core/paths"; +import { resolveCanonicalPath } from "../core/run/paths"; import { join, resolve } from "node:path"; import { loadExtensions } from "./host"; import { readExtensionTrust, resolveRepoTrust, writeExtensionTrust } from "./trust"; diff --git a/src/extensions/trust.ts b/src/extensions/trust.ts index 2ee40a02c..9d7421d75 100644 --- a/src/extensions/trust.ts +++ b/src/extensions/trust.ts @@ -1,6 +1,6 @@ import { resolve } from "node:path"; -import { readAppStateRecord, updateAppStateRecord } from "../core/appStateFile"; -import { resolveCanonicalPath, resolveAppStatePath } from "../core/paths"; +import { readAppStateRecord, updateAppStateRecord } from "../core/process/appStateFile"; +import { resolveCanonicalPath, resolveAppStatePath } from "../core/run/paths"; /** * Repo-local extensions run arbitrary code from the repository under review, diff --git a/src/extensions/vcsPatchResult.test.ts b/src/extensions/vcsPatchResult.test.ts index e28f25fd8..fa22cc56a 100644 --- a/src/extensions/vcsPatchResult.test.ts +++ b/src/extensions/vcsPatchResult.test.ts @@ -1,8 +1,8 @@ import { describe, expect, test } from "bun:test"; import { toInternalVcsPatchResult } from "./vcsPatchResult"; import { HunkExtensionUserError } from "../extension-api/types"; -import { HunkUserError, toUserFacingError } from "../core/errors"; -import { SourceTextTooLargeError } from "../core/fileSource"; +import { HunkUserError, toUserFacingError } from "../core/run/errors"; +import { SourceTextTooLargeError } from "../core/changeset/fileSource"; import { toInternalVcsAdapter } from "./runExtension"; import type { ExtensionVcsFileSourceRequest, diff --git a/src/extensions/vcsPatchResult.ts b/src/extensions/vcsPatchResult.ts index 8ccae2c83..540697358 100644 --- a/src/extensions/vcsPatchResult.ts +++ b/src/extensions/vcsPatchResult.ts @@ -2,14 +2,14 @@ import { buildDiffFile, createSkippedLargeMetadata, type BuildDiffFileOptions, -} from "../core/diffFile"; +} from "../core/changeset/diffFile"; import { parseSingleFilePatch } from "../core/patch/singleFile"; import { DEFAULT_SOURCE_TEXT_MAX_BYTES, SourceTextTooLargeError, type FileSourceSide, -} from "../core/fileSource"; -import type { DiffFile } from "../core/types"; +} from "../core/changeset/fileSource"; +import type { DiffFile } from "../core/changeset/model"; import type { VcsPatchResult } from "../core/vcs/types"; import type { ExtensionVcsExtraFile, diff --git a/src/main.tsx b/src/main.tsx index 069d08d43..be09e1146 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,7 +1,7 @@ #!/usr/bin/env bun -import { formatCliError } from "./core/errors"; -import { pagePlainText } from "./core/pager"; +import { formatCliError } from "./core/run/errors"; +import { pagePlainText } from "./core/process/pager"; import { prepareStartupPlan } from "./app/startup"; import { sanitizeTerminalText } from "./lib/terminalText"; import { serveSessionBrokerDaemon } from "./session/broker/brokerServer"; diff --git a/src/opentui/HunkDiffBody.tsx b/src/opentui/HunkDiffBody.tsx index 0d20be36b..b21bca762 100644 --- a/src/opentui/HunkDiffBody.tsx +++ b/src/opentui/HunkDiffBody.tsx @@ -1,5 +1,5 @@ import { useMemo } from "react"; -import { DEFAULT_TAB_WIDTH } from "../core/tabWidth"; +import { DEFAULT_TAB_WIDTH } from "../core/run/tabWidth"; import { findMaxLineNumber } from "../ui/diff/codeColumns"; import { buildSplitRows, buildStackRows } from "../ui/diff/diffRows"; import { diffMessage, DiffRowView, fitText } from "../ui/diff/renderRows"; diff --git a/src/opentui/model.ts b/src/opentui/model.ts index 4cae34488..533c1ad34 100644 --- a/src/opentui/model.ts +++ b/src/opentui/model.ts @@ -1,10 +1,10 @@ import { parsePatchFiles } from "@pierre/diffs"; -import { patchLooksBinary } from "../core/binary"; -import { normalizeDiffMetadataPaths, normalizeDiffPath } from "../core/diffPaths"; -import { countDiffStats } from "../core/diffFile"; +import { patchLooksBinary } from "../core/changeset/binary"; +import { normalizeDiffMetadataPaths, normalizeDiffPath } from "../core/changeset/diffPaths"; +import { countDiffStats } from "../core/changeset/diffFile"; import { splitPatchIntoFileChunks, findPatchChunk } from "../core/patch/chunks"; import { sanitizePatch } from "../core/patch/sanitize"; -import type { DiffFile } from "../core/types"; +import type { DiffFile } from "../core/changeset/model"; import type { HunkDiffFile, HunkDiffFileInput } from "./types"; const NORMALIZED_HUNK_DIFF_FILES = new WeakSet(); diff --git a/src/session/agent/cliClient.test.ts b/src/session/agent/cliClient.test.ts index f3f026dbe..b0b1f7b45 100644 --- a/src/session/agent/cliClient.test.ts +++ b/src/session/agent/cliClient.test.ts @@ -9,7 +9,7 @@ import { createTestSessionReviewHunk, createTestSessionSnapshot, } from "../../../test/helpers/session-daemon-fixtures"; -import type { SessionSelectorInput } from "../../core/types"; +import type { SessionSelectorInput } from "../../core/run/commandInputs"; import { HUNK_SESSION_API_PATH, HUNK_SESSION_API_VERSION, diff --git a/src/session/agent/cliClient.ts b/src/session/agent/cliClient.ts index 998971b8a..62c8d14b6 100644 --- a/src/session/agent/cliClient.ts +++ b/src/session/agent/cliClient.ts @@ -38,7 +38,7 @@ import type { SessionReloadCommandInput, SessionReviewCommandInput, SessionSelectorInput, -} from "../../core/types"; +} from "../../core/run/commandInputs"; import { describeSessionSelector } from "@hunk/session-broker-core"; export interface HunkSessionCliClient { diff --git a/src/session/agent/commands.daemon.test.ts b/src/session/agent/commands.daemon.test.ts index a067f7d85..f5c240a4c 100644 --- a/src/session/agent/commands.daemon.test.ts +++ b/src/session/agent/commands.daemon.test.ts @@ -1,7 +1,7 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test"; import { createServer } from "node:net"; import { platform } from "node:os"; -import type { SessionCommandInput } from "../../core/types"; +import type { SessionCommandInput } from "../../core/run/commandInputs"; import { createTestListedSession } from "../../../test/helpers/session-daemon-fixtures"; import { runSessionCommand, diff --git a/src/session/agent/commands.test.ts b/src/session/agent/commands.test.ts index 44b6c7297..6eda84832 100644 --- a/src/session/agent/commands.test.ts +++ b/src/session/agent/commands.test.ts @@ -7,7 +7,7 @@ import { createTestSessionReview as buildTestSessionReview, createTestSessionSnapshot, } from "../../../test/helpers/session-daemon-fixtures"; -import type { SessionCommandInput, SessionSelectorInput } from "../../core/types"; +import type { SessionCommandInput, SessionSelectorInput } from "../../core/run/commandInputs"; import { runSessionCommand, setSessionCommandTestHooks, diff --git a/src/session/agent/commands.ts b/src/session/agent/commands.ts index 6e8011e59..e5cfca35f 100644 --- a/src/session/agent/commands.ts +++ b/src/session/agent/commands.ts @@ -2,7 +2,7 @@ import type { SessionCommandInput, SessionCommandOutput, SessionSelectorInput, -} from "../../core/types"; +} from "../../core/run/commandInputs"; import type { SessionLiveCommentSummary, SessionReviewNoteSummary } from "../types"; import { NO_ACTIVE_SESSIONS_MESSAGE } from "./errors"; import { diff --git a/src/session/broker/wire.ts b/src/session/broker/wire.ts index cd270845a..4ff45ce24 100644 --- a/src/session/broker/wire.ts +++ b/src/session/broker/wire.ts @@ -1,5 +1,5 @@ -import { EXPERIMENTAL_FEATURES, type ExperimentalFeature } from "../../core/experimental"; -import type { CliInput } from "../../core/types"; +import { EXPERIMENTAL_FEATURES, type ExperimentalFeature } from "../../core/run/experimental"; +import type { CliInput } from "../../core/run/commandInputs"; import { MAX_REGISTRATION_FILES, MAX_REGISTRATION_HUNKS_PER_FILE, diff --git a/src/session/client/daemonHttp.ts b/src/session/client/daemonHttp.ts index 9b834abc3..854425050 100644 --- a/src/session/client/daemonHttp.ts +++ b/src/session/client/daemonHttp.ts @@ -1,4 +1,4 @@ -import { HunkUserError } from "../../core/errors"; +import { HunkUserError } from "../../core/run/errors"; import { resolveSessionBrokerConfig, type ResolvedSessionBrokerConfig, diff --git a/src/session/protocol.ts b/src/session/protocol.ts index f20ade4ce..bdc3a9c52 100644 --- a/src/session/protocol.ts +++ b/src/session/protocol.ts @@ -10,7 +10,7 @@ import type { SessionReloadCommandInput, SessionReviewCommandInput, SessionSelectorInput, -} from "../core/types"; +} from "../core/run/commandInputs"; import type { AppliedCommentBatchResult, AppliedCommentResult, diff --git a/src/session/protocolSchemas.ts b/src/session/protocolSchemas.ts index f4eea82a9..2de226cc9 100644 --- a/src/session/protocolSchemas.ts +++ b/src/session/protocolSchemas.ts @@ -1,5 +1,5 @@ import { z } from "zod"; -import type { CliInput } from "../core/types"; +import type { CliInput } from "../core/run/commandInputs"; import type { SessionDaemonRequest } from "./protocol"; /** diff --git a/src/session/types.ts b/src/session/types.ts index 381b388e9..2bda421db 100644 --- a/src/session/types.ts +++ b/src/session/types.ts @@ -1,8 +1,8 @@ -import type { ExperimentalFeature } from "../core/experimental"; +import type { ExperimentalFeature } from "../core/run/experimental"; import type { ExtensionLineHighlightTone, SessionReloadReason } from "../extension-api/types"; import type { CommentTargetInput, DiffSide } from "../core/liveComments"; import type { ReviewPublicationAddress } from "../core/review/generationOrder"; -import type { CliInput, ReviewNoteSource } from "../core/types"; +import type { CliInput, ReviewNoteSource } from "../core/run/commandInputs"; import type { HunkReviewActionEnvelopeV1, HunkReviewResourceCatalogV1, diff --git a/src/ui/App.tsx b/src/ui/App.tsx index 718682816..811aae0b4 100644 --- a/src/ui/App.tsx +++ b/src/ui/App.tsx @@ -19,19 +19,15 @@ import { diffPersistedViewPreferences, saveGlobalViewPreferences, saveViewPreferencesPromptPreference, -} from "../core/config"; -import { experimentalFeatureEnabled, resolveExperimentalDiffFiles } from "../core/experimental"; -import { DEFAULT_TAB_WIDTH } from "../core/tabWidth"; + type PersistedViewPreferences, +} from "../core/run/config"; +import { experimentalFeatureEnabled, resolveExperimentalDiffFiles } from "../core/run/experimental"; +import { DEFAULT_TAB_WIDTH } from "../core/run/tabWidth"; import { isVcsReviewInput } from "../core/vcs"; -import type { - AppBootstrap, - CliInput, - CursorLine, - LayoutMode, - PersistedViewPreferences, - UserNoteLineTarget, -} from "../core/types"; -import { canReloadInput } from "../core/inputReload"; +import type { AppBootstrap } from "../core/bootstrap"; +import type { CliInput, CursorLine, LayoutMode } from "../core/run/commandInputs"; +import type { UserNoteLineTarget } from "../core/liveComments"; +import { canReloadInput } from "../core/run/inputReload"; import { sanitizeTerminalLine } from "../lib/terminalText"; import { resolveExtensionCommands, diff --git a/src/ui/AppHost.cursor-line.test.tsx b/src/ui/AppHost.cursor-line.test.tsx index d5c7d8ef5..7de451afc 100644 --- a/src/ui/AppHost.cursor-line.test.tsx +++ b/src/ui/AppHost.cursor-line.test.tsx @@ -1,7 +1,7 @@ import { describe, expect, test } from "bun:test"; import { testRender } from "@opentui/react/test-utils"; import { act } from "react"; -import type { CursorLine } from "../core/types"; +import type { CursorLine } from "../core/run/commandInputs"; import { createTestVcsAppBootstrap } from "../../test/helpers/app-bootstrap"; import { createTestDiffFile, lines } from "../../test/helpers/diff-helpers"; import { AppHost } from "./AppHost"; diff --git a/src/ui/AppHost.edit-in-editor.test.tsx b/src/ui/AppHost.edit-in-editor.test.tsx index b36cadbe0..d4f5a3d91 100644 --- a/src/ui/AppHost.edit-in-editor.test.tsx +++ b/src/ui/AppHost.edit-in-editor.test.tsx @@ -4,7 +4,7 @@ import { mkdtempSync, realpathSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { act } from "react"; -import type { AppBootstrap } from "../core/types"; +import type { AppBootstrap } from "../core/bootstrap"; import { createTestVcsAppBootstrap } from "../../test/helpers/app-bootstrap"; import { createTestDiffFile, lines } from "../../test/helpers/diff-helpers"; diff --git a/src/ui/AppHost.extension-dialogs.test.tsx b/src/ui/AppHost.extension-dialogs.test.tsx index eb391b815..7df60410d 100644 --- a/src/ui/AppHost.extension-dialogs.test.tsx +++ b/src/ui/AppHost.extension-dialogs.test.tsx @@ -6,11 +6,11 @@ import { afterEach, describe, expect, test } from "bun:test"; import { testRender } from "@opentui/react/test-utils"; import { act } from "react"; import { removeTestDirectory } from "../../test/helpers/filesystem"; -import { loadAppBootstrap as loadCoreAppBootstrap } from "../core/changesetLoaders"; +import { loadAppBootstrap as loadCoreAppBootstrap } from "../core/changeset/loaders"; import type { AppBootstrap } from "../app/types"; import { getBundledVcsCatalog } from "../app/vcsCatalog"; -import type { CliInput } from "../core/types"; +import type { CliInput } from "../core/run/commandInputs"; import type { HunkSessionBrokerClient } from "../session/broker/brokerClient"; import { loadStartupExtensions } from "../extensions/startup"; import { AppHost } from "./AppHost"; diff --git a/src/ui/AppHost.extension-navigation.test.tsx b/src/ui/AppHost.extension-navigation.test.tsx index 64743d5a5..a75a067c9 100644 --- a/src/ui/AppHost.extension-navigation.test.tsx +++ b/src/ui/AppHost.extension-navigation.test.tsx @@ -6,7 +6,7 @@ import { afterEach, describe, expect, test } from "bun:test"; import { testRender } from "@opentui/react/test-utils"; import { act } from "react"; import { removeTestDirectory } from "../../test/helpers/filesystem"; -import { loadAppBootstrap as loadCoreAppBootstrap } from "../core/changesetLoaders"; +import { loadAppBootstrap as loadCoreAppBootstrap } from "../core/changeset/loaders"; import type { AppBootstrap } from "../app/types"; import { getBundledVcsCatalog } from "../app/vcsCatalog"; diff --git a/src/ui/AppHost.extension-sidebar.test.tsx b/src/ui/AppHost.extension-sidebar.test.tsx index dceea2b7a..f2fdebaa1 100644 --- a/src/ui/AppHost.extension-sidebar.test.tsx +++ b/src/ui/AppHost.extension-sidebar.test.tsx @@ -7,7 +7,7 @@ import { KeyEvent, type ParsedKey } from "@opentui/core"; import { testRender } from "@opentui/react/test-utils"; import { act } from "react"; import { removeTestDirectory } from "../../test/helpers/filesystem"; -import { loadAppBootstrap as loadCoreAppBootstrap } from "../core/changesetLoaders"; +import { loadAppBootstrap as loadCoreAppBootstrap } from "../core/changeset/loaders"; import type { AppBootstrap } from "../app/types"; import { getBundledVcsCatalog } from "../app/vcsCatalog"; diff --git a/src/ui/AppHost.extensions.test.tsx b/src/ui/AppHost.extensions.test.tsx index b4fe3c2d9..124afab5f 100644 --- a/src/ui/AppHost.extensions.test.tsx +++ b/src/ui/AppHost.extensions.test.tsx @@ -16,8 +16,8 @@ import { removeTestDirectory } from "../../test/helpers/filesystem"; import { ReviewProducer } from "../app/review/producer"; import type { AppBootstrap } from "../app/types"; import { getBundledVcsCatalog } from "../app/vcsCatalog"; -import { loadAppBootstrap as loadCoreAppBootstrap } from "../core/changesetLoaders"; -import type { CliInput } from "../core/types"; +import { loadAppBootstrap as loadCoreAppBootstrap } from "../core/changeset/loaders"; +import type { CliInput } from "../core/run/commandInputs"; import type { HunkSessionBrokerClient } from "../session/broker/brokerClient"; import { diff --git a/src/ui/AppHost.file-view-modes.test.tsx b/src/ui/AppHost.file-view-modes.test.tsx index 919586ffa..f23fa7a72 100644 --- a/src/ui/AppHost.file-view-modes.test.tsx +++ b/src/ui/AppHost.file-view-modes.test.tsx @@ -8,7 +8,7 @@ import { act } from "react"; import { createTestVcsAppBootstrap } from "../../test/helpers/app-bootstrap"; import { createTestDiffFile } from "../../test/helpers/diff-helpers"; import { createWatchTestRuntime } from "../../test/helpers/watchTest"; -import { loadAppBootstrap } from "../core/changesetLoaders"; +import { loadAppBootstrap } from "../core/changeset/loaders"; import { loadStartupExtensions } from "../extensions/startup"; import { AppHost } from "./AppHost"; diff --git a/src/ui/AppHost.interactions.test.tsx b/src/ui/AppHost.interactions.test.tsx index 35752d597..3b62c08fb 100644 --- a/src/ui/AppHost.interactions.test.tsx +++ b/src/ui/AppHost.interactions.test.tsx @@ -11,8 +11,9 @@ import type { HunkSessionServerMessage, HunkSessionSnapshot, } from "../session/types"; -import { LEGACY_CUSTOM_SYNTAX_NOTICE } from "../core/startupNotice"; -import type { AppBootstrap, LayoutMode } from "../core/types"; +import { LEGACY_CUSTOM_SYNTAX_NOTICE } from "../core/process/startupNotice"; +import type { AppBootstrap } from "../core/bootstrap"; +import type { LayoutMode } from "../core/run/commandInputs"; import { createTestVcsAppBootstrap } from "../../test/helpers/app-bootstrap"; import { capturedTestColorToHex } from "../../test/helpers/test-color-helpers"; import { createTestDiffFile as buildTestDiffFile, lines } from "../../test/helpers/diff-helpers"; @@ -20,7 +21,7 @@ import { createEmptyExtensionLoadResult } from "../extensions/types"; import { AGENT_SKILL_COMMAND, AGENT_SKILL_PROMPT } from "./components/chrome/AgentSkillDialog"; import { resolveTheme } from "./themes"; -const { loadAppBootstrap } = await import("../core/changesetLoaders"); +const { loadAppBootstrap } = await import("../core/changeset/loaders"); const { AppHost } = await import("./AppHost"); const TEST_KEY_PAGE_UP = "\x1B[5~"; diff --git a/src/ui/AppHost.key-routing.test.tsx b/src/ui/AppHost.key-routing.test.tsx index ec5a7be9c..3cc571e23 100644 --- a/src/ui/AppHost.key-routing.test.tsx +++ b/src/ui/AppHost.key-routing.test.tsx @@ -5,7 +5,7 @@ import { describe, expect, mock, test } from "bun:test"; import { ScrollBoxRenderable, type Renderable } from "@opentui/core"; import { testRender } from "@opentui/react/test-utils"; import { act } from "react"; -import type { AppBootstrap } from "../core/types"; +import type { AppBootstrap } from "../core/bootstrap"; import { createTestVcsAppBootstrap } from "../../test/helpers/app-bootstrap"; import { createTestDiffFile } from "../../test/helpers/diff-helpers"; import { loadStartupExtensions } from "../extensions/startup"; diff --git a/src/ui/AppHost.keybindings.test.tsx b/src/ui/AppHost.keybindings.test.tsx index 986f5359b..1416daf55 100644 --- a/src/ui/AppHost.keybindings.test.tsx +++ b/src/ui/AppHost.keybindings.test.tsx @@ -6,10 +6,10 @@ import { afterEach, describe, expect, test } from "bun:test"; import { testRender } from "@opentui/react/test-utils"; import { act } from "react"; import { removeTestDirectory } from "../../test/helpers/filesystem"; -import { resolveConfiguredCliInput } from "../core/config"; +import { resolveConfiguredCliInput } from "../core/run/config"; import { getBundledVcsCatalog } from "../app/vcsCatalog"; -import { loadAppBootstrap } from "../core/changesetLoaders"; -import type { AppBootstrap } from "../core/types"; +import { loadAppBootstrap } from "../core/changeset/loaders"; +import type { AppBootstrap } from "../core/bootstrap"; import { createEmptyExtensionLoadResult } from "../extensions/types"; import { AppHost } from "./AppHost"; diff --git a/src/ui/AppHost.reload.test.tsx b/src/ui/AppHost.reload.test.tsx index daa57dbae..a29becce6 100644 --- a/src/ui/AppHost.reload.test.tsx +++ b/src/ui/AppHost.reload.test.tsx @@ -15,7 +15,7 @@ import type { } from "../session/types"; const { getBundledVcsCatalog } = await import("../app/vcsCatalog"); -const { loadAppBootstrap } = await import("../core/changesetLoaders"); +const { loadAppBootstrap } = await import("../core/changeset/loaders"); const { AppHost } = await import("./AppHost"); /** Stand in for the session daemon so a test can send the commands agents send. */ diff --git a/src/ui/AppHost.responsive.test.tsx b/src/ui/AppHost.responsive.test.tsx index 2958da6aa..a344d5a62 100644 --- a/src/ui/AppHost.responsive.test.tsx +++ b/src/ui/AppHost.responsive.test.tsx @@ -1,7 +1,8 @@ import { describe, expect, mock, test } from "bun:test"; import { testRender } from "@opentui/react/test-utils"; import { act } from "react"; -import type { AppBootstrap, LayoutMode } from "../core/types"; +import type { AppBootstrap } from "../core/bootstrap"; +import type { LayoutMode } from "../core/run/commandInputs"; import { createTestVcsAppBootstrap } from "../../test/helpers/app-bootstrap"; import { createTestDiffFile } from "../../test/helpers/diff-helpers"; diff --git a/src/ui/AppHost.scroll-regression.test.tsx b/src/ui/AppHost.scroll-regression.test.tsx index a39e7dcbd..bb0e7f99b 100644 --- a/src/ui/AppHost.scroll-regression.test.tsx +++ b/src/ui/AppHost.scroll-regression.test.tsx @@ -1,7 +1,7 @@ import { describe, expect, mock, test } from "bun:test"; import { testRender } from "@opentui/react/test-utils"; import { act } from "react"; -import type { AppBootstrap } from "../core/types"; +import type { AppBootstrap } from "../core/bootstrap"; import { createTestVcsAppBootstrap } from "../../test/helpers/app-bootstrap"; import { createTestDiffFile } from "../../test/helpers/diff-helpers"; diff --git a/src/ui/AppHost.selection.test.tsx b/src/ui/AppHost.selection.test.tsx index 416b6fb66..b73054c97 100644 --- a/src/ui/AppHost.selection.test.tsx +++ b/src/ui/AppHost.selection.test.tsx @@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test"; import { testRender } from "@opentui/react/test-utils"; import { MouseButtons } from "@opentui/core/testing"; import { act } from "react"; -import type { AppBootstrap } from "../core/types"; +import type { AppBootstrap } from "../core/bootstrap"; import { createTestVcsAppBootstrap } from "../../test/helpers/app-bootstrap"; import { createTestDiffFile, lines } from "../../test/helpers/diff-helpers"; import { measureTextWidth } from "./lib/text"; diff --git a/src/ui/AppHost.sidebar-resize.test.tsx b/src/ui/AppHost.sidebar-resize.test.tsx index 856a7a368..808d00ab0 100644 --- a/src/ui/AppHost.sidebar-resize.test.tsx +++ b/src/ui/AppHost.sidebar-resize.test.tsx @@ -1,7 +1,7 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test"; import { testRender } from "@opentui/react/test-utils"; import { act } from "react"; -import type { AppBootstrap } from "../core/types"; +import type { AppBootstrap } from "../core/bootstrap"; import { createTestVcsAppBootstrap } from "../../test/helpers/app-bootstrap"; import { createTestDiffFile as buildTestDiffFile, lines } from "../../test/helpers/diff-helpers"; import { createEmptyExtensionLoadResult } from "../extensions/types"; diff --git a/src/ui/AppHost.sidebar-visibility.test.tsx b/src/ui/AppHost.sidebar-visibility.test.tsx index a37ab88d8..13a71d019 100644 --- a/src/ui/AppHost.sidebar-visibility.test.tsx +++ b/src/ui/AppHost.sidebar-visibility.test.tsx @@ -1,7 +1,8 @@ import { afterEach, beforeEach, describe, expect, test } from "bun:test"; import { testRender } from "@opentui/react/test-utils"; import { act } from "react"; -import type { AppBootstrap, SidebarVisibility } from "../core/types"; +import type { AppBootstrap } from "../core/bootstrap"; +import type { SidebarVisibility } from "../core/run/commandInputs"; import { createTestVcsAppBootstrap } from "../../test/helpers/app-bootstrap"; import { createTestDiffFile as buildTestDiffFile, lines } from "../../test/helpers/diff-helpers"; import { HUNK_FILES_PANE_KEY } from "../extensions/extensionIds"; diff --git a/src/ui/AppHost.tsx b/src/ui/AppHost.tsx index 54bee9241..e2359fc8c 100644 --- a/src/ui/AppHost.tsx +++ b/src/ui/AppHost.tsx @@ -3,10 +3,11 @@ import { resolveConfiguredExtensions } from "../app/extensionBootstrap"; import { ReviewProducer } from "../app/review/producer"; import { loadConfiguredSessionBootstrap } from "../app/sessionBootstrap"; import { getBundledVcsCatalog } from "../app/vcsCatalog"; -import { resolveConfiguredCliInput } from "../core/config"; -import { resolveRuntimeCliInput } from "../core/terminal"; -import type { StartupNotice } from "../core/startupNotice"; -import type { AppBootstrap, CliInput } from "../core/types"; +import { resolveConfiguredCliInput } from "../core/run/config"; +import { resolveRuntimeCliInput } from "../core/process/terminal"; +import type { StartupNotice } from "../core/process/startupNotice"; +import type { AppBootstrap } from "../core/bootstrap"; +import type { CliInput } from "../core/run/commandInputs"; import type { ExtensionLoadResult } from "../extensions/types"; import { createUnknownVcsNotice, diff --git a/src/ui/AppHost.watch.test.tsx b/src/ui/AppHost.watch.test.tsx index d20f06e27..1fd75a50d 100644 --- a/src/ui/AppHost.watch.test.tsx +++ b/src/ui/AppHost.watch.test.tsx @@ -5,7 +5,7 @@ import { testRender } from "@opentui/react/test-utils"; import { act } from "react"; import { capturedTestColorToHex } from "../../test/helpers/test-color-helpers"; import { createWatchTestRuntime } from "../../test/helpers/watchTest"; -import { loadAppBootstrap } from "../core/changesetLoaders"; +import { loadAppBootstrap } from "../core/changeset/loaders"; import { AppHost } from "./AppHost"; import { resolveTheme } from "./themes"; diff --git a/src/ui/AppHost.workspace.test.tsx b/src/ui/AppHost.workspace.test.tsx index 2778b7927..f79c70a8d 100644 --- a/src/ui/AppHost.workspace.test.tsx +++ b/src/ui/AppHost.workspace.test.tsx @@ -13,11 +13,11 @@ import { afterEach, describe, expect, test } from "bun:test"; import { testRender } from "@opentui/react/test-utils"; import { act } from "react"; import { removeTestDirectory } from "../../test/helpers/filesystem"; -import { loadAppBootstrap as loadCoreAppBootstrap } from "../core/changesetLoaders"; +import { loadAppBootstrap as loadCoreAppBootstrap } from "../core/changeset/loaders"; import type { AppBootstrap } from "../app/types"; import { getBundledVcsCatalog } from "../app/vcsCatalog"; -import type { CliInput } from "../core/types"; +import type { CliInput } from "../core/run/commandInputs"; import { loadStartupExtensions } from "../extensions/startup"; import type { HunkSessionBrokerClient } from "../session/broker/brokerClient"; import type { WorkspaceFileWriter } from "./App"; diff --git a/src/ui/components/panes/AgentInlineNote.tsx b/src/ui/components/panes/AgentInlineNote.tsx index ae72488e3..1c1d57b91 100644 --- a/src/ui/components/panes/AgentInlineNote.tsx +++ b/src/ui/components/panes/AgentInlineNote.tsx @@ -5,7 +5,9 @@ import { type TextareaRenderable, } from "@opentui/core"; import { useLayoutEffect, useRef, type ReactNode } from "react"; -import type { AgentAnnotation, DiffFile, LayoutMode } from "../../../core/types"; +import type { DiffFile } from "../../../core/changeset/model"; +import type { LayoutMode } from "../../../core/run/commandInputs"; +import type { AgentAnnotation } from "../../../extension-api/types"; import { agentNoteBoxLayout } from "../../lib/agentNoteGeometry"; import { annotationRangeLabel, reviewNoteSource } from "../../lib/agentAnnotations"; import { wrapText } from "../../lib/text"; diff --git a/src/ui/components/panes/DiffFileHeaderRow.tsx b/src/ui/components/panes/DiffFileHeaderRow.tsx index 80aea2c8c..e563d5e97 100644 --- a/src/ui/components/panes/DiffFileHeaderRow.tsx +++ b/src/ui/components/panes/DiffFileHeaderRow.tsx @@ -1,4 +1,4 @@ -import type { DiffFile } from "../../../core/types"; +import type { DiffFile } from "../../../core/changeset/model"; import { fileHeaderStats, fitFileHeaderLabel } from "../../lib/fileHeader"; import type { AppTheme } from "../../themes"; diff --git a/src/ui/components/panes/DiffPane.tsx b/src/ui/components/panes/DiffPane.tsx index d02823dae..af9be9276 100644 --- a/src/ui/components/panes/DiffPane.tsx +++ b/src/ui/components/panes/DiffPane.tsx @@ -13,14 +13,11 @@ import { useState, type RefObject, } from "react"; -import { DEFAULT_TAB_WIDTH } from "../../../core/tabWidth"; -import type { - AgentAnnotation, - CursorLine, - DiffFile, - LayoutMode, - UserNoteLineTarget, -} from "../../../core/types"; +import { DEFAULT_TAB_WIDTH } from "../../../core/run/tabWidth"; +import type { DiffFile } from "../../../core/changeset/model"; +import type { CursorLine, LayoutMode } from "../../../core/run/commandInputs"; +import type { UserNoteLineTarget } from "../../../core/liveComments"; +import type { AgentAnnotation } from "../../../extension-api/types"; import { resolveReviewRevealNoteId } from "../../../core/review/selectors"; import { reviewNoteAnchorLine, diff --git a/src/ui/components/panes/DiffSection.tsx b/src/ui/components/panes/DiffSection.tsx index 8ffb4c9f5..3302365a5 100644 --- a/src/ui/components/panes/DiffSection.tsx +++ b/src/ui/components/panes/DiffSection.tsx @@ -1,5 +1,7 @@ import { memo } from "react"; -import type { DiffFile, LayoutMode, UserNoteLineTarget } from "../../../core/types"; +import type { DiffFile } from "../../../core/changeset/model"; +import type { LayoutMode } from "../../../core/run/commandInputs"; +import type { UserNoteLineTarget } from "../../../core/liveComments"; import type { FileSourceStatus } from "../../diff/expandCollapsedRows"; import { DiffSectionBody, type ActiveAddNoteAffordance } from "../../diff/DiffSectionBody"; import type { CursorHighlight } from "../../diff/renderRows"; diff --git a/src/ui/components/panes/ExtensionPane.tsx b/src/ui/components/panes/ExtensionPane.tsx index 3689fd34c..b4911ef0e 100644 --- a/src/ui/components/panes/ExtensionPane.tsx +++ b/src/ui/components/panes/ExtensionPane.tsx @@ -7,7 +7,7 @@ import type { ExtensionPaneProps, ExtensionCurrentLinePaint, } from "../../../extension-api/types"; -import type { DiffFile } from "../../../core/types"; +import type { DiffFile } from "../../../core/changeset/model"; import { paneKey } from "../../../extensions/apply"; import { BuiltInSidebarView } from "../../../extensions/default/ui/sidebar"; import { HUNK_FILES_PANE_KEY } from "../../../extensions/extensionIds"; diff --git a/src/ui/components/panes/FileView.tsx b/src/ui/components/panes/FileView.tsx index 771027dbc..39601e231 100644 --- a/src/ui/components/panes/FileView.tsx +++ b/src/ui/components/panes/FileView.tsx @@ -1,6 +1,6 @@ import { TextAttributes } from "@opentui/core"; import { Component, memo, useMemo, type ReactNode } from "react"; -import type { DiffFile } from "../../../core/types"; +import type { DiffFile } from "../../../core/changeset/model"; import type { ExtensionFileViewLayout, ExtensionFileViewRow, diff --git a/src/ui/components/panes/copySelection.test.ts b/src/ui/components/panes/copySelection.test.ts index b6114b370..88f8d516b 100644 --- a/src/ui/components/panes/copySelection.test.ts +++ b/src/ui/components/panes/copySelection.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "bun:test"; import { parseDiffFromFile } from "@pierre/diffs"; -import type { DiffFile } from "../../../core/types"; +import type { DiffFile } from "../../../core/changeset/model"; import { resolveTheme } from "../../themes"; import { measureDiffSectionGeometry } from "../../diff/diffSectionGeometry"; import { buildFileSectionLayouts } from "../../lib/fileSectionLayout"; diff --git a/src/ui/components/panes/copySelection.ts b/src/ui/components/panes/copySelection.ts index 9e2a31c01..11774f9d8 100644 --- a/src/ui/components/panes/copySelection.ts +++ b/src/ui/components/panes/copySelection.ts @@ -1,4 +1,5 @@ -import type { DiffFile, LayoutMode } from "../../../core/types"; +import type { DiffFile } from "../../../core/changeset/model"; +import type { LayoutMode } from "../../../core/run/commandInputs"; import { DIFF_RAIL_PREFIX_WIDTH, resolveSplitCellGeometry, diff --git a/src/ui/components/scrollbar/VerticalScrollbar.test.tsx b/src/ui/components/scrollbar/VerticalScrollbar.test.tsx index 12eadd314..702b535d4 100644 --- a/src/ui/components/scrollbar/VerticalScrollbar.test.tsx +++ b/src/ui/components/scrollbar/VerticalScrollbar.test.tsx @@ -3,7 +3,8 @@ import { testRender } from "@opentui/react/test-utils"; import { parseDiffFromFile } from "@pierre/diffs"; import { act, createRef } from "react"; import { capturedTestColorToHex } from "../../../../test/helpers/test-color-helpers"; -import type { AppBootstrap, DiffFile } from "../../../core/types"; +import type { AppBootstrap } from "../../../core/bootstrap"; +import type { DiffFile } from "../../../core/changeset/model"; import { resolveTheme } from "../../themes"; import { VerticalScrollbar, type VerticalScrollbarHandle } from "./VerticalScrollbar"; diff --git a/src/ui/components/ui-components.test.tsx b/src/ui/components/ui-components.test.tsx index c175ea7de..75bead05f 100644 --- a/src/ui/components/ui-components.test.tsx +++ b/src/ui/components/ui-components.test.tsx @@ -2,7 +2,8 @@ import { describe, expect, mock, spyOn, test } from "bun:test"; import type { ScrollBoxRenderable } from "@opentui/core"; import { testRender } from "@opentui/react/test-utils"; import { act, createRef, useCallback, useEffect, useRef, useState, type ReactNode } from "react"; -import type { AppBootstrap, DiffFile } from "../../core/types"; +import type { AppBootstrap } from "../../core/bootstrap"; +import type { DiffFile } from "../../core/changeset/model"; import { createTestVcsAppBootstrap } from "../../../test/helpers/app-bootstrap"; import { capturedTestColorToHex } from "../../../test/helpers/test-color-helpers"; import { diff --git a/src/ui/diff/DiffSectionBody.tsx b/src/ui/diff/DiffSectionBody.tsx index 2e21d1fca..76291bc39 100644 --- a/src/ui/diff/DiffSectionBody.tsx +++ b/src/ui/diff/DiffSectionBody.tsx @@ -6,8 +6,10 @@ */ import { useRenderer } from "@opentui/react"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import { DEFAULT_TAB_WIDTH } from "../../core/tabWidth"; -import type { DiffFile, LayoutMode, UserNoteLineTarget } from "../../core/types"; +import { DEFAULT_TAB_WIDTH } from "../../core/run/tabWidth"; +import type { DiffFile } from "../../core/changeset/model"; +import type { LayoutMode } from "../../core/run/commandInputs"; +import type { UserNoteLineTarget } from "../../core/liveComments"; import { AgentInlineNote } from "../components/panes/AgentInlineNote"; import type { VisibleAgentNote } from "../lib/agentAnnotations"; import type { CopySelectedRowRange } from "../lib/diffSpatial"; diff --git a/src/ui/diff/codeColumns.test.ts b/src/ui/diff/codeColumns.test.ts index 63ee5963d..9314b1f56 100644 --- a/src/ui/diff/codeColumns.test.ts +++ b/src/ui/diff/codeColumns.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test"; -import type { DiffFile } from "../../core/types"; +import type { DiffFile } from "../../core/changeset/model"; import { expandDiffTabs, findMaxLineNumberInRows, diff --git a/src/ui/diff/codeColumns.ts b/src/ui/diff/codeColumns.ts index f56070ffb..c9866a18a 100644 --- a/src/ui/diff/codeColumns.ts +++ b/src/ui/diff/codeColumns.ts @@ -1,5 +1,6 @@ -import { DEFAULT_TAB_WIDTH, validateTabWidth } from "../../core/tabWidth"; -import type { DiffFile, LayoutMode } from "../../core/types"; +import { DEFAULT_TAB_WIDTH, validateTabWidth } from "../../core/run/tabWidth"; +import type { DiffFile } from "../../core/changeset/model"; +import type { LayoutMode } from "../../core/run/commandInputs"; import { measureTextWidth } from "../lib/text"; import type { DiffRow } from "./diffRowModel"; diff --git a/src/ui/diff/diffRowModel.ts b/src/ui/diff/diffRowModel.ts index c69d32c31..83c23e41d 100644 --- a/src/ui/diff/diffRowModel.ts +++ b/src/ui/diff/diffRowModel.ts @@ -7,7 +7,7 @@ * the builders themselves. */ import type { ReviewGapPosition } from "../../core/review/expansion"; -import type { DiffLineMoveKind } from "../../core/types"; +import type { DiffLineMoveKind } from "../../core/changeset/model"; export interface RenderSpan { text: string; diff --git a/src/ui/diff/diffRows.test.ts b/src/ui/diff/diffRows.test.ts index 1c84c75bc..3707acff8 100644 --- a/src/ui/diff/diffRows.test.ts +++ b/src/ui/diff/diffRows.test.ts @@ -1,7 +1,7 @@ import { describe, expect, test } from "bun:test"; import { parseDiffFromFile, parsePatchFiles } from "@pierre/diffs"; import { createTwoFilesPatch } from "diff"; -import type { DiffFile } from "../../core/types"; +import type { DiffFile } from "../../core/changeset/model"; import { buildSplitRows, buildStackRows, diff --git a/src/ui/diff/diffRows.ts b/src/ui/diff/diffRows.ts index 6caae323f..467be3751 100644 --- a/src/ui/diff/diffRows.ts +++ b/src/ui/diff/diffRows.ts @@ -14,14 +14,14 @@ import { type FileContents, type FileDiffMetadata, } from "@pierre/diffs"; -import { formatHunkHeader } from "../../core/hunkHeader"; +import { formatHunkHeader } from "../../core/changeset/hunkHeader"; import { reviewLeadingGap, reviewTrailingGap, type ReviewGapAddress, } from "../../core/review/expansion"; -import { DEFAULT_TAB_WIDTH } from "../../core/tabWidth"; -import type { DiffFile, DiffLineMoveKind } from "../../core/types"; +import { DEFAULT_TAB_WIDTH } from "../../core/run/tabWidth"; +import type { DiffFile, DiffLineMoveKind } from "../../core/changeset/model"; import { blendHex, hexColorDistance } from "../lib/color"; import { measureTextWidth } from "../lib/text"; import { sanitizeTerminalLine } from "../../lib/terminalText"; diff --git a/src/ui/diff/diffSectionGeometry.ts b/src/ui/diff/diffSectionGeometry.ts index 402324fe8..3cde94944 100644 --- a/src/ui/diff/diffSectionGeometry.ts +++ b/src/ui/diff/diffSectionGeometry.ts @@ -1,5 +1,6 @@ -import { DEFAULT_TAB_WIDTH } from "../../core/tabWidth"; -import type { DiffFile, LayoutMode } from "../../core/types"; +import { DEFAULT_TAB_WIDTH } from "../../core/run/tabWidth"; +import type { DiffFile } from "../../core/changeset/model"; +import type { LayoutMode } from "../../core/run/commandInputs"; import { measureAgentInlineNoteHeight } from "../components/panes/AgentInlineNote"; import type { VisibleAgentNote } from "../lib/agentAnnotations"; import type { SectionGeometry, VerticalBounds } from "../lib/diffSpatial"; diff --git a/src/ui/diff/diffSectionRowPlan.ts b/src/ui/diff/diffSectionRowPlan.ts index c07beaa15..53c98ada1 100644 --- a/src/ui/diff/diffSectionRowPlan.ts +++ b/src/ui/diff/diffSectionRowPlan.ts @@ -1,6 +1,7 @@ import { reviewExpansionSide } from "../../core/review/expansion"; -import { DEFAULT_TAB_WIDTH } from "../../core/tabWidth"; -import type { DiffFile, LayoutMode } from "../../core/types"; +import { DEFAULT_TAB_WIDTH } from "../../core/run/tabWidth"; +import type { DiffFile } from "../../core/changeset/model"; +import type { LayoutMode } from "../../core/run/commandInputs"; import type { VisibleAgentNote } from "../lib/agentAnnotations"; import type { AppTheme } from "../themes"; import { findMaxLineNumber, findMaxLineNumberInRows } from "./codeColumns"; diff --git a/src/ui/diff/expandCollapsedRows.ts b/src/ui/diff/expandCollapsedRows.ts index e02f70727..e4bfc5088 100644 --- a/src/ui/diff/expandCollapsedRows.ts +++ b/src/ui/diff/expandCollapsedRows.ts @@ -1,6 +1,6 @@ import { reviewGapId } from "../../core/review/expansion"; import { normalizedReviewSourceLines } from "../../core/review/geometry"; -import { DEFAULT_TAB_WIDTH } from "../../core/tabWidth"; +import { DEFAULT_TAB_WIDTH } from "../../core/run/tabWidth"; import { sanitizeTerminalLine, sanitizeTerminalSpans } from "../../lib/terminalText"; import { expandDiffTabs } from "./codeColumns"; import type { diff --git a/src/ui/diff/lineHighlightPaint.test.ts b/src/ui/diff/lineHighlightPaint.test.ts index 8cd2bfc4f..f3a927a30 100644 --- a/src/ui/diff/lineHighlightPaint.test.ts +++ b/src/ui/diff/lineHighlightPaint.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "bun:test"; import { createTestDiffFile, lines } from "../../../test/helpers/diff-helpers"; -import { DEFAULT_TAB_WIDTH } from "../../core/tabWidth"; +import { DEFAULT_TAB_WIDTH } from "../../core/run/tabWidth"; import type { ValidatedLineHighlight } from "../highlights/validate"; import { measureTextWidth } from "../lib/text"; import { expandDiffTabs } from "./codeColumns"; diff --git a/src/ui/diff/lineHighlightPaint.ts b/src/ui/diff/lineHighlightPaint.ts index 2f7d5e098..446e1a082 100644 --- a/src/ui/diff/lineHighlightPaint.ts +++ b/src/ui/diff/lineHighlightPaint.ts @@ -18,8 +18,8 @@ import { type ReviewGapAddress, } from "../../core/review/expansion"; import { normalizedReviewSourceLines } from "../../core/review/geometry"; -import { DEFAULT_TAB_WIDTH } from "../../core/tabWidth"; -import type { DiffFile } from "../../core/types"; +import { DEFAULT_TAB_WIDTH } from "../../core/run/tabWidth"; +import type { DiffFile } from "../../core/changeset/model"; import type { ExtensionLineHighlightTone } from "../../extension-api/types"; import { sanitizeTerminalLine } from "../../lib/terminalText"; import type { ValidatedLineHighlight } from "../highlights/validate"; diff --git a/src/ui/diff/renderRows.tsx b/src/ui/diff/renderRows.tsx index 191816d1d..d8433f674 100644 --- a/src/ui/diff/renderRows.tsx +++ b/src/ui/diff/renderRows.tsx @@ -1,6 +1,7 @@ import { Fragment, isValidElement, memo, type ReactNode } from "react"; import { parseColor, StyledText, type TextChunk } from "@opentui/core"; -import type { DiffFile, UserNoteLineTarget } from "../../core/types"; +import type { DiffFile } from "../../core/changeset/model"; +import type { UserNoteLineTarget } from "../../core/liveComments"; import type { AppTheme } from "../themes"; import { resolveSplitCellGeometry, @@ -46,7 +47,7 @@ import { wrapSanitizedTextByWidth, } from "../lib/text"; import type { CopySelectedRowRange } from "../lib/diffSpatial"; -import type { CursorLine } from "../../core/types"; +import type { CursorLine } from "../../core/run/commandInputs"; export interface CursorHighlight { /** The render plan anchor of the row the cursor rests on, shared with reveal lookups. */ diff --git a/src/ui/diff/reviewRenderPlan.test.ts b/src/ui/diff/reviewRenderPlan.test.ts index 99361e22e..68deae23f 100644 --- a/src/ui/diff/reviewRenderPlan.test.ts +++ b/src/ui/diff/reviewRenderPlan.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "bun:test"; import { parseDiffFromFile } from "@pierre/diffs"; -import type { DiffFile } from "../../core/types"; +import type { DiffFile } from "../../core/changeset/model"; import { createVisibleAgentNote } from "../lib/agentAnnotations"; import { expandCollapsedRows } from "./expandCollapsedRows"; import { diff --git a/src/ui/diff/reviewRenderPlan.ts b/src/ui/diff/reviewRenderPlan.ts index 7667a0144..71f409377 100644 --- a/src/ui/diff/reviewRenderPlan.ts +++ b/src/ui/diff/reviewRenderPlan.ts @@ -1,6 +1,7 @@ import { reviewNoteAnchorLine, reviewNoteOwnerHunkIndex } from "../../core/review/state"; import type { ReviewRangeAnchorV1 } from "../../core/review/types"; -import type { AgentAnnotation, UserNoteLineTarget } from "../../core/types"; +import type { UserNoteLineTarget } from "../../core/liveComments"; +import type { AgentAnnotation } from "../../extension-api/types"; import type { VisibleAgentNote } from "../lib/agentAnnotations"; import { diffHunkId } from "../lib/ids"; import type { DiffRow } from "./diffRows"; diff --git a/src/ui/diff/reviewRowGeometry.ts b/src/ui/diff/reviewRowGeometry.ts index c70dcf92e..48cdc4b83 100644 --- a/src/ui/diff/reviewRowGeometry.ts +++ b/src/ui/diff/reviewRowGeometry.ts @@ -5,7 +5,7 @@ * where each hunk's visible extent falls. Heights resolve before mount, which is why note * markup has to lay out deterministically from `(markup, width)` alone. */ -import type { LayoutMode } from "../../core/types"; +import type { LayoutMode } from "../../core/run/commandInputs"; import { measureAgentInlineNoteHeight } from "../components/panes/AgentInlineNote"; import type { SectionGeometry, VerticalBounds } from "../lib/diffSpatial"; import { reviewRowId } from "../lib/ids"; diff --git a/src/ui/diff/useHighlightedDiff.ts b/src/ui/diff/useHighlightedDiff.ts index fac87bdcb..9eee5b7a6 100644 --- a/src/ui/diff/useHighlightedDiff.ts +++ b/src/ui/diff/useHighlightedDiff.ts @@ -1,5 +1,5 @@ import { useLayoutEffect, useState } from "react"; -import type { DiffFile } from "../../core/types"; +import type { DiffFile } from "../../core/changeset/model"; import type { AppTheme } from "../themes"; import { loadHighlightedDiff, type HighlightedDiffCode } from "./diffRows"; import { createHighlightedDiffCache } from "./highlightedDiffCache"; diff --git a/src/ui/diff/useHighlightedSource.ts b/src/ui/diff/useHighlightedSource.ts index c4cd93ca9..52e7de382 100644 --- a/src/ui/diff/useHighlightedSource.ts +++ b/src/ui/diff/useHighlightedSource.ts @@ -1,5 +1,5 @@ import { useLayoutEffect, useMemo, useState } from "react"; -import type { DiffFile } from "../../core/types"; +import type { DiffFile } from "../../core/changeset/model"; import type { AppTheme } from "../themes"; import { loadHighlightedSourceLines, type HighlightedSourceCode } from "./diffRows"; import { syntaxHighlightThemeName } from "./syntaxHighlightTheme"; diff --git a/src/ui/diff/worker/highlightCompact.test.ts b/src/ui/diff/worker/highlightCompact.test.ts index 21771f552..fa8d639e1 100644 --- a/src/ui/diff/worker/highlightCompact.test.ts +++ b/src/ui/diff/worker/highlightCompact.test.ts @@ -1,6 +1,6 @@ import { describe, expect, test } from "bun:test"; import { cleanLastNewline, parseDiffFromFile, parsePatchFiles } from "@pierre/diffs"; -import type { DiffFile } from "../../../core/types"; +import type { DiffFile } from "../../../core/changeset/model"; import { loadHighlightedDiff, type HighlightedDiffCode } from "../diffRows"; import { COMPACT_HIGHLIGHT_FLAG_WORD_DIFF, diff --git a/src/ui/fileViews/host.ts b/src/ui/fileViews/host.ts index 8ac817f0a..4ae226d9b 100644 --- a/src/ui/fileViews/host.ts +++ b/src/ui/fileViews/host.ts @@ -1,4 +1,4 @@ -import type { DiffFile } from "../../core/types"; +import type { DiffFile } from "../../core/changeset/model"; import type { ExtensionDiffFile, ExtensionFileChangeRange, diff --git a/src/ui/fileViews/renderPlan.ts b/src/ui/fileViews/renderPlan.ts index 15bc06ee4..52a3f527e 100644 --- a/src/ui/fileViews/renderPlan.ts +++ b/src/ui/fileViews/renderPlan.ts @@ -1,4 +1,4 @@ -import type { AgentAnnotation } from "../../core/types"; +import type { AgentAnnotation } from "../../extension-api/types"; import type { ExtensionFileViewLayout, ExtensionFileViewRow } from "../../extension-api/types"; import { inlineNoteStableKey, lineStableKey } from "../diff/reviewRenderPlan"; import { annotationAnchor, type VisibleAgentNote } from "../lib/agentAnnotations"; diff --git a/src/ui/fileViews/useFilePresentationController.test.tsx b/src/ui/fileViews/useFilePresentationController.test.tsx index c4a783ea3..663979523 100644 --- a/src/ui/fileViews/useFilePresentationController.test.tsx +++ b/src/ui/fileViews/useFilePresentationController.test.tsx @@ -1,7 +1,7 @@ import { describe, expect, test } from "bun:test"; import { testRender } from "@opentui/react/test-utils"; import { act, useCallback, useMemo, useRef, useState } from "react"; -import type { DiffFile } from "../../core/types"; +import type { DiffFile } from "../../core/changeset/model"; import { toReadOnlyFileViews } from "../../extensions/events"; import type { RegisteredFileView } from "../../extensions/types"; import { createTestDiffFile } from "../../../test/helpers/diff-helpers"; diff --git a/src/ui/fileViews/useFilePresentationController.ts b/src/ui/fileViews/useFilePresentationController.ts index 86689cc98..be9db0c00 100644 --- a/src/ui/fileViews/useFilePresentationController.ts +++ b/src/ui/fileViews/useFilePresentationController.ts @@ -1,5 +1,5 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; -import type { DiffFile } from "../../core/types"; +import type { DiffFile } from "../../core/changeset/model"; import type { ExtensionContext, ExtensionDiffFile, diff --git a/src/ui/fileViews/useFilePresentationRendering.ts b/src/ui/fileViews/useFilePresentationRendering.ts index 9f53235a1..a358cc549 100644 --- a/src/ui/fileViews/useFilePresentationRendering.ts +++ b/src/ui/fileViews/useFilePresentationRendering.ts @@ -1,5 +1,5 @@ import { useCallback, useEffect, useRef } from "react"; -import type { DiffFile } from "../../core/types"; +import type { DiffFile } from "../../core/changeset/model"; import type { RegisteredFileView } from "../../extensions/types"; import type { FileViewEpochState, FileViewSelectionState } from "./state"; import type { FileViewRowFailure } from "./types"; diff --git a/src/ui/fileViews/useFileViews.ts b/src/ui/fileViews/useFileViews.ts index 1e1f93080..07560ca0a 100644 --- a/src/ui/fileViews/useFileViews.ts +++ b/src/ui/fileViews/useFileViews.ts @@ -1,5 +1,5 @@ import { useEffect, useMemo, useRef, useState } from "react"; -import type { DiffFile } from "../../core/types"; +import type { DiffFile } from "../../core/changeset/model"; import type { RegisteredFileView } from "../../extensions/types"; import { fileViewHunkCount, diff --git a/src/ui/highlights/useLineHighlights.ts b/src/ui/highlights/useLineHighlights.ts index e0f5b2826..5488cdaae 100644 --- a/src/ui/highlights/useLineHighlights.ts +++ b/src/ui/highlights/useLineHighlights.ts @@ -1,5 +1,5 @@ import { useEffect, useMemo, useRef, useState } from "react"; -import type { DiffFile } from "../../core/types"; +import type { DiffFile } from "../../core/changeset/model"; import type { ExtensionLineHighlightInput } from "../../extension-api/types"; import { toReadOnlyFileViews } from "../../extensions/events"; import type { RegisteredLineHighlighter } from "../../extensions/types"; diff --git a/src/ui/highlights/useLineHighlightsController.test.tsx b/src/ui/highlights/useLineHighlightsController.test.tsx index 2228c0bdd..9ad051580 100644 --- a/src/ui/highlights/useLineHighlightsController.test.tsx +++ b/src/ui/highlights/useLineHighlightsController.test.tsx @@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test"; import { testRender } from "@opentui/react/test-utils"; import { act, createElement, useState } from "react"; import { createTestDiffFile } from "../../../test/helpers/diff-helpers"; -import type { DiffFile } from "../../core/types"; +import type { DiffFile } from "../../core/changeset/model"; import type { RegisteredLineHighlighter } from "../../extensions/types"; import { registeredLineHighlighterKey } from "./state"; import { scopedEpoch } from "../lib/scopedEpochs"; diff --git a/src/ui/highlights/useLineHighlightsController.ts b/src/ui/highlights/useLineHighlightsController.ts index dc4f59231..588206e9f 100644 --- a/src/ui/highlights/useLineHighlightsController.ts +++ b/src/ui/highlights/useLineHighlightsController.ts @@ -1,6 +1,6 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import type { ExtensionLineHighlightControls } from "../../extension-api/types"; -import type { DiffFile } from "../../core/types"; +import type { DiffFile } from "../../core/changeset/model"; import type { RegisteredLineHighlighter } from "../../extensions/types"; import { bumpScopedEpoch, reconcileScopedEpochs } from "../lib/scopedEpochs"; import { diff --git a/src/ui/hooks/useHunkSessionBridge.ts b/src/ui/hooks/useHunkSessionBridge.ts index fdb9daf56..727255d90 100644 --- a/src/ui/hooks/useHunkSessionBridge.ts +++ b/src/ui/hooks/useHunkSessionBridge.ts @@ -1,6 +1,7 @@ import { useEffect, useMemo } from "react"; import type { ReviewProducer } from "../../app/review/producer"; -import type { CliInput, DiffFile } from "../../core/types"; +import type { DiffFile } from "../../core/changeset/model"; +import type { CliInput } from "../../core/run/commandInputs"; import { reviewHunkRanges } from "../../core/review/geometry"; import { createHunkSessionBridge } from "../../app/session/bridge"; import type { HunkSessionBrokerClient } from "../../session/broker/brokerClient"; diff --git a/src/ui/hooks/useStartupNotices.ts b/src/ui/hooks/useStartupNotices.ts index 2b18f7bfe..a1de79291 100644 --- a/src/ui/hooks/useStartupNotices.ts +++ b/src/ui/hooks/useStartupNotices.ts @@ -1,5 +1,5 @@ import { useEffect, useRef, useState } from "react"; -import type { StartupNotice } from "../../core/startupNotice"; +import type { StartupNotice } from "../../core/process/startupNotice"; const DEFAULT_STARTUP_NOTICE_DELAY_MS = 1200; const DEFAULT_STARTUP_NOTICE_DURATION_MS = 7000; diff --git a/src/ui/hooks/useTerminalReview.test.tsx b/src/ui/hooks/useTerminalReview.test.tsx index 89b14c817..e806af6fa 100644 --- a/src/ui/hooks/useTerminalReview.test.tsx +++ b/src/ui/hooks/useTerminalReview.test.tsx @@ -1,9 +1,9 @@ import { describe, expect, spyOn, test } from "bun:test"; import { testRender } from "@opentui/react/test-utils"; import { act, StrictMode, useEffect, useRef, useState } from "react"; -import { builtinAppCommand } from "../../core/commandCatalog"; -import { SourceTextTooLargeError } from "../../core/fileSource"; -import type { DiffFile } from "../../core/types"; +import { builtinAppCommand } from "../../core/run/commandCatalog"; +import { SourceTextTooLargeError } from "../../core/changeset/fileSource"; +import type { DiffFile } from "../../core/changeset/model"; import { createTestDeferred, createTestDiffFile, diff --git a/src/ui/hooks/useTerminalReview.ts b/src/ui/hooks/useTerminalReview.ts index 7f928cec2..41dc9e7ea 100644 --- a/src/ui/hooks/useTerminalReview.ts +++ b/src/ui/hooks/useTerminalReview.ts @@ -23,14 +23,15 @@ import { buildLiveComment, findDiffFileByPath, resolveCommentTarget, + type UserNoteLineTarget, } from "../../core/liveComments"; import { builtinAppCommand, lowerAppCommandToReviewIntent, type AppCommandId, type AppCommandLoweringContext, -} from "../../core/commandCatalog"; -import { SourceTextTooLargeError } from "../../core/fileSource"; +} from "../../core/run/commandCatalog"; +import { SourceTextTooLargeError } from "../../core/changeset/fileSource"; import { applyReviewIntent, ReviewIntentPlanningError, @@ -49,7 +50,9 @@ import { import { REVIEW_VIEWPORT_ANCHOR_REVEAL, type ReviewRevealRequest } from "../../core/review/state"; import { createReviewStore, type ReviewStore } from "../../core/review/store"; import { noDiffFileMatchesMessage } from "../../session/agent/errors"; -import type { AgentAnnotation, DiffFile, LayoutMode, UserNoteLineTarget } from "../../core/types"; +import type { DiffFile } from "../../core/changeset/model"; +import type { LayoutMode } from "../../core/run/commandInputs"; +import type { AgentAnnotation } from "../../extension-api/types"; import type { AppliedCommentBatchResult, AppliedCommentResult, diff --git a/src/ui/hooks/useWatchedInput.ts b/src/ui/hooks/useWatchedInput.ts index 18db2c5a7..2f6cb69a6 100644 --- a/src/ui/hooks/useWatchedInput.ts +++ b/src/ui/hooks/useWatchedInput.ts @@ -7,7 +7,8 @@ import { import { createWatchEventSource } from "../../core/watch/observer"; import { resolveWatchPlan, type WatchPlan } from "../../core/watch/plan"; import { computeWatchSignature } from "../../core/watch/signature"; -import type { CliInput, ReloadContext } from "../../core/types"; +import type { ReloadContext } from "../../core/bootstrap"; +import type { CliInput } from "../../core/run/commandInputs"; export interface WatchedInputRuntime { clock?: WatchControllerClock; diff --git a/src/ui/lib/agentAnnotations.ts b/src/ui/lib/agentAnnotations.ts index cc7fd634d..b22505d60 100644 --- a/src/ui/lib/agentAnnotations.ts +++ b/src/ui/lib/agentAnnotations.ts @@ -1,5 +1,7 @@ import type { Hunk } from "@pierre/diffs"; -import type { AgentAnnotation, DiffFile, ReviewNoteSource } from "../../core/types"; +import type { DiffFile } from "../../core/changeset/model"; +import type { ReviewNoteSource } from "../../core/run/commandInputs"; +import type { AgentAnnotation } from "../../extension-api/types"; import { reviewAnnotationOverlapsHunk } from "../../core/review/annotations"; import { resolveReviewNoteAnchor, reviewGapOwnerHunkIndex } from "../../core/review/anchors"; import type { ReviewHunkSpan } from "../../core/review/geometry"; diff --git a/src/ui/lib/agentNoteGeometry.ts b/src/ui/lib/agentNoteGeometry.ts index dbb1f3b88..5edcfb4de 100644 --- a/src/ui/lib/agentNoteGeometry.ts +++ b/src/ui/lib/agentNoteGeometry.ts @@ -6,7 +6,7 @@ // width their STML will actually be laid out at — all three must agree or // note heights and agent feedback drift from what the terminal shows. -import type { LayoutMode } from "../../core/types"; +import type { LayoutMode } from "../../core/run/commandInputs"; import { resolveSplitPaneWidths } from "../diff/codeColumns"; export interface AgentNoteGeometryInput { diff --git a/src/ui/lib/appCommands.test.ts b/src/ui/lib/appCommands.test.ts index 55947e5c4..188538cfa 100644 --- a/src/ui/lib/appCommands.test.ts +++ b/src/ui/lib/appCommands.test.ts @@ -12,7 +12,7 @@ import { type BuildAppCommandsOptions, type ResolvedCommandKeys, } from "./appCommands"; -import { APP_COMMAND_CATALOG } from "../../core/commandCatalog"; +import { APP_COMMAND_CATALOG } from "../../core/run/commandCatalog"; import { buildAppMenus } from "./appMenus"; import { buildHelpSections, HELP_COMMAND_IDS } from "./helpContent"; import { resolveCommandKeys } from "./keymap"; diff --git a/src/ui/lib/appCommands.ts b/src/ui/lib/appCommands.ts index 036d8f7bb..87fed7b70 100644 --- a/src/ui/lib/appCommands.ts +++ b/src/ui/lib/appCommands.ts @@ -4,9 +4,9 @@ import { type AppCommandCatalogEntry, type AppCommandId, type VerticalCommandDirection, -} from "../../core/commandCatalog"; +} from "../../core/run/commandCatalog"; import type { ReviewSelectionScope } from "../../core/review/navigation"; -import type { CursorLine, LayoutMode } from "../../core/types"; +import type { CursorLine, LayoutMode } from "../../core/run/commandInputs"; import type { ExtensionCommandExecutionOptions } from "../../extension-api/types"; import { matchesAnyKeyChord, diff --git a/src/ui/lib/appMenus.ts b/src/ui/lib/appMenus.ts index 0a9e5498e..0614b6c00 100644 --- a/src/ui/lib/appMenus.ts +++ b/src/ui/lib/appMenus.ts @@ -1,4 +1,4 @@ -import type { CursorLine, LayoutMode } from "../../core/types"; +import type { CursorLine, LayoutMode } from "../../core/run/commandInputs"; import type { AppMenus, MenuEntry, MenuId } from "../components/chrome/menu"; import { executeAppCommand, isCommandEnabled, type AppCommand } from "./appCommands"; diff --git a/src/ui/lib/extensionDocumentReader.ts b/src/ui/lib/extensionDocumentReader.ts index 508432c44..cc2bab539 100644 --- a/src/ui/lib/extensionDocumentReader.ts +++ b/src/ui/lib/extensionDocumentReader.ts @@ -1,4 +1,4 @@ -import type { DiffFile } from "../../core/types"; +import type { DiffFile } from "../../core/changeset/model"; import type { ExtensionFileSide } from "../../extension-api/types"; /** Abort one caller's wait without cancelling the host's shared source read. */ diff --git a/src/ui/lib/extensionWorkspace.test.ts b/src/ui/lib/extensionWorkspace.test.ts index 1bc49f1e9..c907371fc 100644 --- a/src/ui/lib/extensionWorkspace.test.ts +++ b/src/ui/lib/extensionWorkspace.test.ts @@ -1,6 +1,6 @@ import { join, resolve, sep } from "node:path"; import { describe, expect, test } from "bun:test"; -import type { CliInput, CommonOptions } from "../../core/types"; +import type { CliInput, CommonOptions } from "../../core/run/commandInputs"; import { normalizeWorkspaceWriteRequest, resolveExtensionWorkspaceRead, diff --git a/src/ui/lib/extensionWorkspace.ts b/src/ui/lib/extensionWorkspace.ts index ed10f2f8e..e2d69b440 100644 --- a/src/ui/lib/extensionWorkspace.ts +++ b/src/ui/lib/extensionWorkspace.ts @@ -18,10 +18,10 @@ */ import { isAbsolute, relative, resolve, sep } from "node:path"; -import { normalizeDiffPath } from "../../core/diffPaths"; -import type { FileSourceSide } from "../../core/fileSource"; -import { canReloadInput } from "../../core/inputReload"; -import type { CliInput } from "../../core/types"; +import { normalizeDiffPath } from "../../core/changeset/diffPaths"; +import type { FileSourceSide } from "../../core/changeset/fileSource"; +import { canReloadInput } from "../../core/run/inputReload"; +import type { CliInput } from "../../core/run/commandInputs"; import { readMetadataChangeType } from "../../extensions/events"; /** diff --git a/src/ui/lib/fileHeader.ts b/src/ui/lib/fileHeader.ts index bde64defc..9f04b5e68 100644 --- a/src/ui/lib/fileHeader.ts +++ b/src/ui/lib/fileHeader.ts @@ -1,4 +1,4 @@ -import type { DiffFile } from "../../core/types"; +import type { DiffFile } from "../../core/changeset/model"; import { fileLabelParts } from "./files"; import { fitText, measureTextWidth } from "./text"; diff --git a/src/ui/lib/fileRenderWindow.test.ts b/src/ui/lib/fileRenderWindow.test.ts index da0a9a167..edf18c5dd 100644 --- a/src/ui/lib/fileRenderWindow.test.ts +++ b/src/ui/lib/fileRenderWindow.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test"; -import type { DiffFile } from "../../core/types"; +import type { DiffFile } from "../../core/changeset/model"; import { buildFileSectionLayouts } from "./fileSectionLayout"; import { buildFileRenderWindow, type FileRenderWindowItem } from "./fileRenderWindow"; diff --git a/src/ui/lib/fileSectionLayout.ts b/src/ui/lib/fileSectionLayout.ts index 7d5979484..f248a534d 100644 --- a/src/ui/lib/fileSectionLayout.ts +++ b/src/ui/lib/fileSectionLayout.ts @@ -1,4 +1,4 @@ -import type { DiffFile } from "../../core/types"; +import type { DiffFile } from "../../core/changeset/model"; /** Stream geometry for one file section in the main review pane. */ export interface FileSectionLayout { diff --git a/src/ui/lib/files.ts b/src/ui/lib/files.ts index 38cc77b79..e6be07480 100644 --- a/src/ui/lib/files.ts +++ b/src/ui/lib/files.ts @@ -1,7 +1,8 @@ import { basename, dirname } from "node:path/posix"; import type { FileDiffMetadata } from "@pierre/diffs"; -import { normalizeDiffPath } from "../../core/diffPaths"; -import type { AgentAnnotation, DiffFile } from "../../core/types"; +import { normalizeDiffPath } from "../../core/changeset/diffPaths"; +import type { DiffFile } from "../../core/changeset/model"; +import type { AgentAnnotation } from "../../extension-api/types"; import { readMetadataChangeType } from "../../extensions/events"; import { formatTerminalPath } from "../../lib/terminalText"; diff --git a/src/ui/lib/keymap.ts b/src/ui/lib/keymap.ts index b0fbb2fdf..6368e9edc 100644 --- a/src/ui/lib/keymap.ts +++ b/src/ui/lib/keymap.ts @@ -1,4 +1,4 @@ -import type { UserKeyBinding } from "../../core/types"; +import type { UserKeyBinding } from "../../core/run/config"; import type { ExtensionPaneKeybindings } from "../../extension-api/types"; import { HUNK_VENDOR_EXTENSION_ID } from "../../extensions/extensionIds"; import { matchesKeyChord, parseKeyChord, type ParsedKeyChord } from "../../lib/commandKeys"; diff --git a/src/ui/lib/lineCursors.test.ts b/src/ui/lib/lineCursors.test.ts index 6a44b858e..f88eda01e 100644 --- a/src/ui/lib/lineCursors.test.ts +++ b/src/ui/lib/lineCursors.test.ts @@ -4,7 +4,8 @@ import { createTestHeaderOnlyDiffFile, lines, } from "../../../test/helpers/diff-helpers"; -import type { DiffFile, LayoutMode } from "../../core/types"; +import type { DiffFile } from "../../core/changeset/model"; +import type { LayoutMode } from "../../core/run/commandInputs"; import { reviewGapId } from "../../core/review/expansion"; import { measureDiffSectionGeometry } from "../diff/diffSectionGeometry"; import { resolveTheme } from "../themes"; diff --git a/src/ui/lib/lineCursors.ts b/src/ui/lib/lineCursors.ts index 9329dce0a..510c576c9 100644 --- a/src/ui/lib/lineCursors.ts +++ b/src/ui/lib/lineCursors.ts @@ -5,7 +5,8 @@ * active layout draws and carry the plan anchor rendering, reveal, and note placement already use. */ -import type { DiffFile, UserNoteLineTarget } from "../../core/types"; +import type { DiffFile } from "../../core/changeset/model"; +import type { UserNoteLineTarget } from "../../core/liveComments"; import type { DiffSectionGeometry, DiffSectionRowBounds } from "../diff/diffSectionGeometry"; import { contextLineStableKeySides, diff --git a/src/ui/lib/openInEditor.ts b/src/ui/lib/openInEditor.ts index 4b9f1ef29..21f0ed2dc 100644 --- a/src/ui/lib/openInEditor.ts +++ b/src/ui/lib/openInEditor.ts @@ -1,7 +1,7 @@ import { existsSync } from "node:fs"; import { basename, resolve, win32 } from "node:path"; import type { CliRenderer } from "@opentui/core"; -import type { DiffFile } from "../../core/types"; +import type { DiffFile } from "../../core/changeset/model"; import type { LineCursor } from "./lineCursors"; export interface EditorCommand { diff --git a/src/ui/lib/responsive.ts b/src/ui/lib/responsive.ts index 5422c70e6..a8805c97e 100644 --- a/src/ui/lib/responsive.ts +++ b/src/ui/lib/responsive.ts @@ -1,4 +1,4 @@ -import type { LayoutMode } from "../../core/types"; +import type { LayoutMode } from "../../core/run/commandInputs"; export type ResponsiveViewport = "full" | "medium" | "tight"; diff --git a/src/ui/lib/reviewNoteMapping.ts b/src/ui/lib/reviewNoteMapping.ts index d64c313f5..2f762e0bd 100644 --- a/src/ui/lib/reviewNoteMapping.ts +++ b/src/ui/lib/reviewNoteMapping.ts @@ -21,7 +21,8 @@ import { type ReviewStoredNote, } from "../../core/review/state"; import type { ReviewNoteV1 } from "../../core/review/types"; -import type { AgentAnnotation, DiffFile } from "../../core/types"; +import type { DiffFile } from "../../core/changeset/model"; +import type { AgentAnnotation } from "../../extension-api/types"; import { reviewNoteSource } from "./agentAnnotations"; /** One reviewer-authored note as the terminal review stream renders it. */ diff --git a/src/ui/lib/reviewState.ts b/src/ui/lib/reviewState.ts index 96a9c7790..5ab8129b9 100644 --- a/src/ui/lib/reviewState.ts +++ b/src/ui/lib/reviewState.ts @@ -16,7 +16,8 @@ import { reviewHunkRanges } from "../../core/review/geometry"; import { reviewFileMatchesFilter, selectNormalizedSelection } from "../../core/review/selectors"; import type { ReviewState } from "../../core/review/state"; import { noDiffFileMatchesMessage } from "../../session/agent/errors"; -import type { AgentAnnotation, DiffFile } from "../../core/types"; +import type { DiffFile } from "../../core/changeset/model"; +import type { AgentAnnotation } from "../../extension-api/types"; import type { NavigateToHunkToolInput, SelectedHunkSummary } from "../../session/types"; import { mergeFileAnnotationsByFileId } from "./files"; diff --git a/src/ui/lib/stml/cli.ts b/src/ui/lib/stml/cli.ts index 54541e0ef..6a9eb0b75 100644 --- a/src/ui/lib/stml/cli.ts +++ b/src/ui/lib/stml/cli.ts @@ -2,7 +2,7 @@ // main.tsx so the command behavior is directly testable. import { resolve as resolvePath } from "node:path"; -import type { MarkupRenderCommandInput } from "../../../core/types"; +import type { MarkupRenderCommandInput } from "../../../core/run/commandInputs"; import { resolveTheme } from "../../themes"; import { STML_GUIDE } from "./guide"; import { renderStmlToAnsi, renderStmlToText } from "./render"; diff --git a/src/ui/lib/ui-lib.test.ts b/src/ui/lib/ui-lib.test.ts index 6cb0b02f7..acca7ca2d 100644 --- a/src/ui/lib/ui-lib.test.ts +++ b/src/ui/lib/ui-lib.test.ts @@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test"; import { parseDiffFromFile } from "@pierre/diffs"; import type { KeyEvent } from "@opentui/core"; import stringWidth from "string-width"; -import type { DiffFile } from "../../core/types"; +import type { DiffFile } from "../../core/changeset/model"; import { buildMenuSpecs, menuBarTitleWidth, diff --git a/src/ui/lib/viewportAnchor.ts b/src/ui/lib/viewportAnchor.ts index 72875a219..4e39884cd 100644 --- a/src/ui/lib/viewportAnchor.ts +++ b/src/ui/lib/viewportAnchor.ts @@ -1,4 +1,4 @@ -import type { DiffFile } from "../../core/types"; +import type { DiffFile } from "../../core/changeset/model"; import type { DiffSectionGeometry, DiffSectionRowBounds } from "../diff/diffSectionGeometry"; import { buildFileSectionLayouts } from "./fileSectionLayout"; diff --git a/src/ui/lib/viewportSelection.ts b/src/ui/lib/viewportSelection.ts index 750abb1c8..ae790c639 100644 --- a/src/ui/lib/viewportSelection.ts +++ b/src/ui/lib/viewportSelection.ts @@ -1,4 +1,4 @@ -import type { DiffFile } from "../../core/types"; +import type { DiffFile } from "../../core/changeset/model"; import type { DiffSectionGeometry } from "../diff/diffSectionGeometry"; import { findFileSectionAtOffset, type FileSectionLayout } from "./fileSectionLayout"; diff --git a/src/ui/runInteractiveApp.tsx b/src/ui/runInteractiveApp.tsx index 8ffd4180b..12aa16c80 100644 --- a/src/ui/runInteractiveApp.tsx +++ b/src/ui/runInteractiveApp.tsx @@ -5,11 +5,11 @@ import { installJobControlSuspendSupport, type JobControlInterruptSupport, type JobControlSuspendSupport, -} from "../core/jobControl"; -import { shutdownSession } from "../core/shutdown"; -import { shouldUseMouseForApp, type ControllingTerminal } from "../core/terminal"; -import type { AppBootstrap } from "../core/types"; -import { resolveStartupUpdateNotice } from "../core/updateNotice"; +} from "../core/process/jobControl"; +import { shutdownSession } from "../core/process/shutdown"; +import { shouldUseMouseForApp, type ControllingTerminal } from "../core/process/terminal"; +import type { AppBootstrap } from "../core/bootstrap"; +import { resolveStartupUpdateNotice } from "../core/process/updateNotice"; import { ReviewProducer } from "../app/review/producer"; import { createInitialSessionSnapshot, diff --git a/src/ui/staticDiffPager.ts b/src/ui/staticDiffPager.ts index a58edb871..9b4e00a24 100644 --- a/src/ui/staticDiffPager.ts +++ b/src/ui/staticDiffPager.ts @@ -14,10 +14,12 @@ * here. If the static renderer cannot parse or render safely, callers fall back to the original patch * text so pager pipelines keep working. */ -import { loadAppBootstrap } from "../core/changesetLoaders"; +import { loadAppBootstrap } from "../core/changeset/loaders"; import { reviewEmptyDiffReason, type ReviewEmptyDiffReason } from "../core/review/document"; -import { DEFAULT_TAB_WIDTH } from "../core/tabWidth"; -import type { CommonOptions, DiffFile, NamedCustomThemeConfig } from "../core/types"; +import { DEFAULT_TAB_WIDTH } from "../core/run/tabWidth"; +import type { DiffFile } from "../core/changeset/model"; +import type { CommonOptions } from "../core/run/commandInputs"; +import type { NamedCustomThemeConfig } from "../extension-api/types"; import { buildSplitRows, buildStackRows, diff --git a/src/ui/themes.ts b/src/ui/themes.ts index de1475c01..28df368ff 100644 --- a/src/ui/themes.ts +++ b/src/ui/themes.ts @@ -1,7 +1,7 @@ import type { ThemeMode } from "@opentui/core"; import { LEGACY_CUSTOM_THEME_ID } from "../core/theme/customThemes"; import { resolveSyntaxScopeOverrides } from "../core/theme/legacySyntaxScopes"; -import type { NamedCustomThemeConfig } from "../core/types"; +import type { NamedCustomThemeConfig } from "../extension-api/types"; import { blendHex, contrastRatio, relativeLuminance } from "./lib/color"; import { BUNDLED_SHIKI_THEME_IDS, diff --git a/test/helpers/app-bootstrap.ts b/test/helpers/app-bootstrap.ts index e5397280a..b79a3744f 100644 --- a/test/helpers/app-bootstrap.ts +++ b/test/helpers/app-bootstrap.ts @@ -1,4 +1,6 @@ -import type { AppBootstrap, DiffFile, VcsDiffCommandInput, LayoutMode } from "../../src/core/types"; +import type { AppBootstrap } from "../../src/core/bootstrap"; +import type { DiffFile } from "../../src/core/changeset/model"; +import type { VcsDiffCommandInput, LayoutMode } from "../../src/core/run/commandInputs"; export function createTestVcsAppBootstrap({ agentSummary, diff --git a/test/helpers/diff-helpers.ts b/test/helpers/diff-helpers.ts index d7f0dc308..93b626de8 100644 --- a/test/helpers/diff-helpers.ts +++ b/test/helpers/diff-helpers.ts @@ -1,6 +1,7 @@ import { parseDiffFromFile } from "@pierre/diffs"; -import type { FileSourceFetcher, FileSourceSide } from "../../src/core/fileSource"; -import type { AgentAnnotation, AgentFileContext, DiffFile } from "../../src/core/types"; +import type { FileSourceFetcher, FileSourceSide } from "../../src/core/changeset/fileSource"; +import type { DiffFile } from "../../src/core/changeset/model"; +import type { AgentAnnotation, AgentFileContext } from "../../src/extension-api/types"; function collectChangeStats(metadata: DiffFile["metadata"]) { let additions = 0; diff --git a/test/helpers/review-session-harness.ts b/test/helpers/review-session-harness.ts index af930cb70..61c2c8994 100644 --- a/test/helpers/review-session-harness.ts +++ b/test/helpers/review-session-harness.ts @@ -19,7 +19,8 @@ import { } from "../../src/app/session/registration"; import { HunkSessionBrokerState } from "../../src/session/broker/state"; import { ReviewResourceCache } from "../../src/session/broker/reviewResourceCache"; -import type { AppBootstrap, DiffFile } from "../../src/core/types"; +import type { AppBootstrap } from "../../src/core/bootstrap"; +import type { DiffFile } from "../../src/core/changeset/model"; import type { HunkSessionRegistration, HunkSessionServerMessage } from "../../src/session/types"; import { createTestDiffFile } from "./diff-helpers"; diff --git a/test/helpers/theme-helpers.ts b/test/helpers/theme-helpers.ts index d9f9c1289..c3d44104e 100644 --- a/test/helpers/theme-helpers.ts +++ b/test/helpers/theme-helpers.ts @@ -1,4 +1,4 @@ -import type { CustomThemeConfig, NamedCustomThemeConfig } from "../../src/core/types"; +import type { CustomThemeConfig, NamedCustomThemeConfig } from "../../src/extension-api/types"; /** * Name one custom palette so it can be passed to the theme APIs, which take the diff --git a/test/review-conformance/consumers/terminalRenderPlan.ts b/test/review-conformance/consumers/terminalRenderPlan.ts index 257c5c7ac..8d9247d13 100644 --- a/test/review-conformance/consumers/terminalRenderPlan.ts +++ b/test/review-conformance/consumers/terminalRenderPlan.ts @@ -9,7 +9,7 @@ */ import { resolveCommentTarget } from "../../../src/core/liveComments"; import { reviewGapId } from "../../../src/core/review/expansion"; -import type { DiffFile } from "../../../src/core/types"; +import type { DiffFile } from "../../../src/core/changeset/model"; import { buildDiffSectionRowPlan } from "../../../src/ui/diff/diffSectionRowPlan"; import { DIFF_MESSAGES, diffMessage } from "../../../src/ui/diff/renderRows"; import type { DiffRow } from "../../../src/ui/diff/diffRows"; diff --git a/test/review-conformance/geometryFixtures.ts b/test/review-conformance/geometryFixtures.ts index e90ed0013..bd0c21e32 100644 --- a/test/review-conformance/geometryFixtures.ts +++ b/test/review-conformance/geometryFixtures.ts @@ -7,7 +7,7 @@ * they are the inputs the deleted copies got wrong. */ import { createTestDiffFile, lines } from "../helpers/diff-helpers"; -import type { DiffFile } from "../../src/core/types"; +import type { DiffFile } from "../../src/core/changeset/model"; import type { ReviewGeometryFixture } from "./types"; /** Twelve numbered lines, the base every geometry fixture edits. */ diff --git a/test/review-conformance/navigationFixtures.ts b/test/review-conformance/navigationFixtures.ts index b1b417aa6..e84d242d5 100644 --- a/test/review-conformance/navigationFixtures.ts +++ b/test/review-conformance/navigationFixtures.ts @@ -8,7 +8,7 @@ * disagreed about, so a captured expectation would preserve the disagreement. */ import { createTestDiffFile, lines } from "../helpers/diff-helpers"; -import type { DiffFile } from "../../src/core/types"; +import type { DiffFile } from "../../src/core/changeset/model"; import type { ReviewNavigationFixture } from "./types"; /** Twelve numbered lines, the base every navigation fixture edits. */ diff --git a/test/review-conformance/types.ts b/test/review-conformance/types.ts index 7a7e97cc1..77ed4b26f 100644 --- a/test/review-conformance/types.ts +++ b/test/review-conformance/types.ts @@ -24,7 +24,7 @@ import type { ReviewIntent } from "../../src/core/review/intents"; import type { ReviewSelectionScope } from "../../src/core/review/navigation"; import type { ReviewNoteV1 } from "../../src/core/review/types"; import type { HunkReviewPublicationBodyV1 } from "../../src/session/reviewHttpProtocol"; -import type { DiffFile } from "../../src/core/types"; +import type { DiffFile } from "../../src/core/changeset/model"; export interface ConformanceGap { gapId: string; diff --git a/website/src/content/docs/docs/reference/config.md b/website/src/content/docs/docs/reference/config.md index f099a5dad..75769e6b1 100644 --- a/website/src/content/docs/docs/reference/config.md +++ b/website/src/content/docs/docs/reference/config.md @@ -5,7 +5,7 @@ description: Exhaustive generated reference for Hunk TOML keys, defaults, aliase -Hunk reads TOML preferences from the user config and an optional repository config. This reference is generated from the same catalog that `src/core/config.ts` uses to parse preference keys. +Hunk reads TOML preferences from the user config and an optional repository config. This reference is generated from the same catalog that `src/core/run/config.ts` uses to parse preference keys. ## Resolution and scope