diff --git a/.changeset/README.md b/.changeset/README.md index 8d816bd88..91da40838 100644 --- a/.changeset/README.md +++ b/.changeset/README.md @@ -14,7 +14,9 @@ Select `hunkdiff` and choose the semver bump that matches the shipped CLI/packag - `minor` for new user-facing features - `major` for breaking changes -`package.json` intentionally lists `"."` in `workspaces` so Changesets can discover the root `hunkdiff` package. Keep that entry unless Hunk moves the publishable package out of the repository root. +The private root workspace lists `packages/*`, and Changesets discovers the published `hunkdiff` +package at `packages/hunk/package.json`. Private implementation workspaces remain ignored and are +never published. For maintenance-only PRs that should not appear in release notes, create an empty changeset: @@ -28,6 +30,9 @@ Release prep runs: bun run release:version ``` -That consumes the pending `.changeset/*.md` files, updates `CHANGELOG.md`, and bumps package versions for the release commit. +`CHANGELOG.md` at the repository root is the canonical changelog consumed by the website and +release tooling. The version wrapper temporarily stages that history beside the package for +Changesets, then copies the generated release entry back to the root and removes the temporary +package changelog. It also bumps `packages/hunk/package.json` for the release commit. After the tag release publishes npm packages and GitHub release assets, verify Homebrew through `Homebrew/homebrew-core`. Hunk is on Homebrew's Autobump list, so do not open manual simple version-bump PRs. Wait for the automated `hunk ` PR, confirm it merges, then verify `brew install hunk` resolves to the released version. Use `brew bump-formula-pr hunk --version ` only if Homebrew maintainers ask for a manual bump or Autobump stalls unexpectedly. diff --git a/.changeset/package-first-phase-one.md b/.changeset/package-first-phase-one.md new file mode 100644 index 000000000..a845151cc --- /dev/null +++ b/.changeset/package-first-phase-one.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/.dependency-cruiser.cjs b/.dependency-cruiser.cjs index c20aae747..150226b52 100644 --- a/.dependency-cruiser.cjs +++ b/.dependency-cruiser.cjs @@ -1,5 +1,5 @@ /** - * Enforces module boundaries on the production import graph (`src/` plus `packages/`). + * Enforces module boundaries on the production import graph (`packages/hunk/src/` plus `packages/`). * * Each rule names one boundary of the target architecture described in * docs/module-boundaries.md. Pre-existing violations live in @@ -8,27 +8,27 @@ * `bun run deps:check` fails on any violation not in the baseline. */ -// UI files allowed to couple to src/app and src/session: the composition shell, the two +// UI files allowed to couple to packages/hunk/src/app and packages/hunk/src/session: the composition shell, the two // named session adapter hooks, and the session-navigation resolution helper those hooks -// share. Everything else in src/ui stays presentation-only. +// share. Everything else in packages/hunk/src/ui stays presentation-only. const UI_SESSION_ADAPTERS = [ - "^src/ui/App\\.tsx$", - "^src/ui/AppHost\\.tsx$", - "^src/ui/runInteractiveApp\\.tsx$", - "^src/ui/hooks/useHunkSessionBridge\\.ts$", - "^src/ui/hooks/useTerminalReview\\.ts$", - "^src/ui/lib/reviewState\\.ts$", + "^packages/hunk/src/ui/App\\.tsx$", + "^packages/hunk/src/ui/AppHost\\.tsx$", + "^packages/hunk/src/ui/runInteractiveApp\\.tsx$", + "^packages/hunk/src/ui/hooks/useHunkSessionBridge\\.ts$", + "^packages/hunk/src/ui/hooks/useTerminalReview\\.ts$", + "^packages/hunk/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, +// published facades, and the skill generator. A module under packages/hunk/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$", + "^packages/hunk/src/main\\.tsx$", + "^packages/hunk/src/highlightWorkerEntry\\.ts$", + "^packages/hunk/src/opentui/index\\.ts$", + "^packages/hunk/src/extension-api/index\\.ts$", + "^packages/hunk/src/hunk-review/skillDocument\\.ts$", ]; // Modules kept alive by tests alone. The cruise excludes tests, so these look unreachable @@ -37,12 +37,12 @@ const PRODUCTION_ENTRY_POINTS = [ // without the coverage to justify it. const TEST_ONLY_MODULES = [ // Note-height measurement exercised by the review-conformance corpus. - "^src/core/review/noteSize\\.ts$", + "^packages/hunk/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$", + "^packages/hunk/src/ui/components/panes/AgentCard\\.tsx$", + "^packages/hunk/src/ui/lib/agentPopover\\.ts$", ]; module.exports = { @@ -58,75 +58,78 @@ module.exports = { { name: "extension-api-is-import-free", comment: - "src/extension-api is the published contract; declaration emission publishes whatever it reaches (scripts/check-pack.ts gates the pack, this gates the graph).", + "packages/hunk/src/extension-api is the published contract; declaration emission publishes whatever it reaches (scripts/check-pack.ts gates the pack, this gates the graph).", severity: "error", - from: { path: "^src/extension-api/" }, - to: { path: "^(src|packages)/", pathNot: "^src/extension-api/" }, + from: { path: "^packages/hunk/src/extension-api/" }, + to: { path: "^packages/", pathNot: "^packages/hunk/src/extension-api/" }, }, { name: "lib-is-a-leaf", comment: - "src/lib holds dependency-free helpers usable from any tier; it may reach the import-free extension API contract and nothing else.", + "packages/hunk/src/lib holds dependency-free helpers usable from any tier; it may reach the import-free extension API contract and nothing else.", severity: "error", - from: { path: "^src/lib/" }, - to: { path: "^(src|packages)/", pathNot: "^src/(lib|extension-api)/" }, + from: { path: "^packages/hunk/src/lib/" }, + to: { path: "^packages/", pathNot: "^packages/hunk/src/(lib|extension-api)/" }, }, { name: "core-stays-domain", comment: - "src/core is the domain model. It may use src/lib and the extension-api contract, but never the UI, app composition, session brokering, extension host, or opentui facade above it.", + "packages/hunk/src/core is the domain model. It may use packages/hunk/src/lib and the extension-api contract, but never the UI, app composition, session brokering, extension host, or opentui facade above it.", severity: "error", - from: { path: "^src/core/" }, - to: { path: "^src/(ui|app|session|extensions|opentui)/" }, + from: { path: "^packages/hunk/src/core/" }, + to: { path: "^packages/hunk/src/(ui|app|session|extensions|opentui)/" }, }, { name: "extensions-host-stays-below-surfaces", comment: - "The extension host and bundled extensions sit below the surfaces that load them. The bundled UI tier (src/extensions/default/ui/) is exempt from the src/ui half by documented design: its dogfooding boundary is the published props contract — data, actions, theme — while rendering helpers are host code (see the sidebar module header).", + "The extension host and bundled extensions sit below the surfaces that load them. The bundled UI tier (packages/hunk/src/extensions/default/ui/) is exempt from the packages/hunk/src/ui half by documented design: its dogfooding boundary is the published props contract — data, actions, theme — while rendering helpers are host code (see the sidebar module header).", severity: "error", - from: { path: "^src/extensions/", pathNot: "^src/extensions/default/ui/" }, - to: { path: "^src/(ui|app|session|opentui)/" }, + from: { + path: "^packages/hunk/src/extensions/", + pathNot: "^packages/hunk/src/extensions/default/ui/", + }, + to: { path: "^packages/hunk/src/(ui|app|session|opentui)/" }, }, { name: "bundled-ui-extensions-render-only", comment: - "The bundled UI tier may consume src/ui rendering helpers as host code, but composition and session brokering stay out of reach — a pane gets its data and actions through the published props.", + "The bundled UI tier may consume packages/hunk/src/ui rendering helpers as host code, but composition and session brokering stay out of reach — a pane gets its data and actions through the published props.", severity: "error", - from: { path: "^src/extensions/default/ui/" }, - to: { path: "^src/(app|session|opentui)/" }, + from: { path: "^packages/hunk/src/extensions/default/ui/" }, + to: { path: "^packages/hunk/src/(app|session|opentui)/" }, }, { name: "session-stays-below-app-and-ui", comment: - "src/session brokers transport and protocol. It consumes core and packages; the app tier registers into it, not the other way round.", + "packages/hunk/src/session brokers transport and protocol. It consumes core and packages; the app tier registers into it, not the other way round.", severity: "error", - from: { path: "^src/session/" }, - to: { path: "^src/(ui|app|extensions|opentui)/" }, + from: { path: "^packages/hunk/src/session/" }, + to: { path: "^packages/hunk/src/(ui|app|extensions|opentui)/" }, }, { name: "app-composes-without-ui", comment: - "src/app wires core, extensions, and session together for startup; rendering stays in src/ui, which imports app — never the reverse.", + "packages/hunk/src/app wires core, extensions, and session together for startup; rendering stays in packages/hunk/src/ui, which imports app — never the reverse.", severity: "error", - from: { path: "^src/app/" }, - to: { path: "^src/(ui|opentui)/" }, + from: { path: "^packages/hunk/src/app/" }, + to: { path: "^packages/hunk/src/(ui|opentui)/" }, }, { name: "ui-couples-to-session-via-adapters", comment: - "Only the composition shell and the named session adapter hooks may import src/app or src/session; ordinary UI components and helpers stay presentation-only so the review surface can move to other hosts.", + "Only the composition shell and the named session adapter hooks may import packages/hunk/src/app or packages/hunk/src/session; ordinary UI components and helpers stay presentation-only so the review surface can move to other hosts.", severity: "error", - from: { path: "^src/ui/", pathNot: UI_SESSION_ADAPTERS }, - to: { path: "^src/(app|session)/" }, + from: { path: "^packages/hunk/src/ui/", pathNot: UI_SESSION_ADAPTERS }, + to: { path: "^packages/hunk/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.", + "Every module under packages/hunk/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/", + path: "^packages/hunk/src/", pathNot: [...PRODUCTION_ENTRY_POINTS, ...TEST_ONLY_MODULES], reachable: false, }, @@ -137,27 +140,27 @@ module.exports = { "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|install|review|vcs|watch|patch|theme)/", - pathNot: "^src/core/changeset/loaders\\.ts$", + path: "^packages/hunk/src/core/(changeset|run|process|install|review|vcs|watch|patch|theme)/", + pathNot: "^packages/hunk/src/core/changeset/loaders\\.ts$", }, - to: { path: "^src/core/bootstrap\\.ts$" }, + to: { path: "^packages/hunk/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).", + "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 packages/hunk/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$" }, + from: { path: "^packages/hunk/src/", pathNot: "^packages/hunk/src/core/review/" }, + to: { path: "^packages/hunk/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/" }, + from: { path: "^packages/hunk/src/", pathNot: "^packages/hunk/src/core/changeset/" }, to: { - path: "^src/core/changeset/(fromPatch|fileLanguageLookup|sidecar)\\.ts$", + path: "^packages/hunk/src/core/changeset/(fromPatch|fileLanguageLookup|sidecar)\\.ts$", }, }, { @@ -165,8 +168,8 @@ module.exports = { comment: "Workspace packages are standalone publishable units; they never import the app source tree.", severity: "error", - from: { path: "^packages/" }, - to: { path: "^src/" }, + from: { path: "^packages/(?!hunk/)" }, + to: { path: "^packages/hunk/src/" }, }, ], options: { diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9b7a3a389..307686b96 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -150,7 +150,7 @@ jobs: pkg_dir="$(mktemp -d)" install_dir="$(mktemp -d)" node_dir="$(dirname "$(command -v node)")" - npm pack --pack-destination "$pkg_dir" >/dev/null + npm pack ./packages/hunk --ignore-scripts --pack-destination "$pkg_dir" >/dev/null pkg="$(find "$pkg_dir" -maxdepth 1 -name 'hunkdiff-*.tgz' | head -n1)" npm install -g --prefix "$install_dir" "$pkg" PATH="$install_dir/bin:$node_dir:/usr/bin:/bin" diff --git a/.github/workflows/pr-ci.yml b/.github/workflows/pr-ci.yml index d665bb595..429c17a18 100644 --- a/.github/workflows/pr-ci.yml +++ b/.github/workflows/pr-ci.yml @@ -212,7 +212,7 @@ jobs: pkg_dir="$(mktemp -d)" install_dir="$(mktemp -d)" node_dir="$(dirname "$(command -v node)")" - npm pack --pack-destination "$pkg_dir" >/dev/null + npm pack ./packages/hunk --ignore-scripts --pack-destination "$pkg_dir" >/dev/null pkg="$(find "$pkg_dir" -maxdepth 1 -name 'hunkdiff-*.tgz' | head -n1)" npm install -g --prefix "$install_dir" "$pkg" PATH="$install_dir/bin:$node_dir:/usr/bin:/bin" diff --git a/AGENTS.md b/AGENTS.md index 1ffb65328..17c4d6d61 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -26,7 +26,7 @@ DiffFile[] -> projectReviewDocument -> ReviewDocumentV1 -> ReviewStore ReviewIntent + caller facts -> planReviewIntent -> ReviewAction[] -> reducer -> surface projection ``` -- **Model:** `src/core/review/{types,document,identity}.ts` owns the ordered, JSON-safe document. +- **Model:** `packages/hunk/src/core/review/{types,document,identity}.ts` owns the ordered, JSON-safe document. File order is review/sidebar order; use `key` (referenced as `fileKey` elsewhere), `contentIdentity`, and `sourceIdentity` (cached source text additionally requires `sourceAttested`) — not runtime IDs or indexes — across reloads/surfaces. @@ -53,9 +53,9 @@ ReviewIntent + caller facts -> planReviewIntent -> ReviewAction[] -> reducer -> repaid seam finding deletes copies, adds a file or banned-symbol tombstone and adversarial fixture, registers consumers, and updates `docs/browser-review-seam-audit.md`. -- Bundled VCS implementations live under `src/extensions/default/vcs//` and consume the - public extension contract; `src/app` composes their registrations into the provider-neutral - core VCS catalog. Do not add provider commands, spawning, or source readers under `src/core`. +- Bundled VCS implementations live under `packages/hunk/src/extensions/default/vcs//` and consume the + public extension contract; `packages/hunk/src/app` composes their registrations into the provider-neutral + core VCS catalog. Do not add provider commands, spawning, or source readers under `packages/hunk/src/core`. - `hunk daemon serve` is the one loopback daemon for all live sessions; sessions auto-start and register with it rather than opening per-TUI ports. Reuse `classifyReviewPublication` and `ReviewChunkAssembler` for publication ordering and bounded, digest-verified resources. Browser @@ -63,17 +63,17 @@ ReviewIntent + caller facts -> planReviewIntent -> ReviewAction[] -> reducer -> its digest. Transport semantics come from the browser-safe review protocol modules and the existing intent path. See `docs/browser-review-rebuild.md` and the relevant module headers. - User and bundled extensions share one API and registry. Shipped VCS backends and the built-in - sidebar register through the public contract. Keep `src/extension-api/types.ts` import-free, + sidebar register through the public contract. Keep `packages/hunk/src/extension-api/types.ts` import-free, bundled VCS renderer-free, repo-local extensions trust-gated, and bundled extensions active under `--no-extensions`. See `docs/extension-architecture.md`, `docs/extensions.md`, and - `skills/hunk-extensions/SKILL.md`. + `packages/hunk/skills/hunk-extensions/SKILL.md`. - Sidecar file order is intentional sidebar and review-stream order. - Derive shared rendering, navigation, scrolling, and note behavior from one planning layer. Make shared geometry explicit, and remove obsolete paths instead of retaining parallel implementations. ## architectural rules -- Import boundaries between `src/` top-level trees are enforced by `bun run deps:check` +- Import boundaries between `packages/hunk/src/` top-level trees are enforced by `bun run deps:check` (dependency-cruiser; rules in `.dependency-cruiser.cjs`, target tiers in `docs/module-boundaries.md`). The known-violations baseline is shrink-only: fix an edge, rerun `bun run deps:baseline`, never add to it. @@ -95,16 +95,16 @@ ReviewIntent + caller facts -> planReviewIntent -> ReviewAction[] -> reducer -> ## theme guidance -- Built-in theme ids and source metadata live in `src/core/theme/catalog.ts`; `src/ui/themes.ts` +- Built-in theme ids and source metadata live in `packages/hunk/src/core/theme/catalog.ts`; `packages/hunk/src/ui/themes.ts` derives Hunk's semantic `AppTheme` values. - When adding or renaming a built-in theme, update validation, public exports, docs/examples, the appropriate Changeset, and tests. Keep source palette tokens separate from semantic mappings and cover non-trivial derived colors. -- `BUNDLED_SHIKI_THEME_DIFF_COLORS` in `src/core/theme/catalog.ts` is generated. Edit the sourcing policy in `scripts/generate-theme-diff-colors.ts`, then run `bun run generate:theme-colors`. +- `BUNDLED_SHIKI_THEME_DIFF_COLORS` in `packages/hunk/src/core/theme/catalog.ts` is generated. Edit the sourcing policy in `scripts/generate-theme-diff-colors.ts`, then run `bun run generate:theme-colors`. ## testing -- Colocate unit tests with the code they cover (`src/core/foo.ts` + `src/core/foo.test.ts`, `src/ui/AppHost.*.test.tsx`, `src/ui/lib/*.test.ts`). +- Colocate unit tests with the code they cover (`packages/hunk/src/core/foo.ts` + `packages/hunk/src/core/foo.test.ts`, `packages/hunk/src/ui/AppHost.*.test.tsx`, `packages/hunk/src/ui/lib/*.test.ts`). - Put shared unit-test helpers in `test/helpers/`. - Name test helpers so they explicitly include `Test` and are clearly test-only (`createTestDiffFile`). - Use repo-level `test/` directories by intent: @@ -145,11 +145,11 @@ ReviewIntent + caller facts -> planReviewIntent -> ReviewAction[] -> reducer -> - Agent context belongs beside the code, not hidden in a separate mode or workflow. - Agent notes are hunk-specific: show notes for the selected hunk, render them in the diff flow near the annotated row, and keep a clear spatial relationship to the code they explain. - Keep note behavior explicit. If the UI intentionally prioritizes one note, one selection, or one active target, encode that as a named policy rather than scattering array-index assumptions through the codebase. -- STML markup notes (experimental) live in `src/ui/lib/stml/`. The layout engine is deliberately a deterministic line layout, not OpenTUI flexbox: the row-windowed review stream needs exact note heights before mount, so `(markup, width)` must always produce the same lines. Colors stay symbolic until render time so measurement never needs a theme. Do not "simplify" this into flexbox renderables, and keep note-card geometry in `agentNoteGeometry` as the single source for rendering, measurement, and agent-facing width reporting. +- STML markup notes (experimental) live in `packages/hunk/src/ui/lib/stml/`. The layout engine is deliberately a deterministic line layout, not OpenTUI flexbox: the row-windowed review stream needs exact note heights before mount, so `(markup, width)` must always produce the same lines. Colors stay symbolic until render time so measurement never needs a theme. Do not "simplify" this into flexbox renderables, and keep note-card geometry in `agentNoteGeometry` as the single source for rendering, measurement, and agent-facing width reporting. - Keep temporary sidecars concise and review-oriented. Their file order is intentional, while the visible note UI remains hunk-note driven rather than showing generic explainer cards. -- Agents review via `skills/hunk-review/SKILL.md` using `hunk session *` commands; do not run interactive TUI commands directly. -- `skills/hunk-review/SKILL.md` is generated. Edit `src/hunk-review/skillDocument.ts`, `src/session/agent/surface.ts`, or `src/session/agent/errors.ts`, then run `bun run generate:skill`; never hand-edit the skill file. +- Agents review via `packages/hunk/skills/hunk-review/SKILL.md` using `hunk session *` commands; do not run interactive TUI commands directly. +- `packages/hunk/skills/hunk-review/SKILL.md` is generated. Edit `packages/hunk/src/hunk-review/skillDocument.ts`, `packages/hunk/src/session/agent/surface.ts`, or `packages/hunk/src/session/agent/errors.ts`, then run `bun run generate:skill`; never hand-edit the skill file. ## binary notes diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 41094acba..3a042fbb9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -35,7 +35,7 @@ Please discuss a change before implementation when it introduces or substantiall Before adding a built-in workflow, integration, or alternate presentation, check whether it can be implemented as an extension. Extensions are usually the better home for opt-in behavior such as VCS integrations, sidebars, file views, commands, keyboard modes, line highlighters, and repository-specific review workflows. -Start with [`docs/extensions.md`](docs/extensions.md) and the checked-in [`examples/extensions/`](examples/extensions/). If you use a coding agent, [`skills/hunk-extensions/SKILL.md`](skills/hunk-extensions/SKILL.md) maps the public API and its implementation. A small prototype is often the fastest way to learn whether the current API is enough. +Start with [`docs/extensions.md`](docs/extensions.md) and the checked-in [`examples/extensions/`](examples/extensions/). If you use a coding agent, [`packages/hunk/skills/hunk-extensions/SKILL.md`](packages/hunk/skills/hunk-extensions/SKILL.md) maps the public API and its implementation. A small prototype is often the fastest way to learn whether the current API is enough. If the extension API cannot express the idea, do not immediately bypass it with feature-specific core code. Explain: @@ -80,7 +80,7 @@ Install dependencies and run Hunk from source: ```bash bun install -bun run src/main.tsx -- diff +bun run packages/hunk/src/main.tsx -- diff ``` Nix users can run `nix develop` or use [direnv](https://direnv.net/) to enter the development shell. diff --git a/benchmarks/bootstrap-load.ts b/benchmarks/bootstrap-load.ts index 95c709318..6da0bad5d 100644 --- a/benchmarks/bootstrap-load.ts +++ b/benchmarks/bootstrap-load.ts @@ -5,8 +5,8 @@ import { tmpdir } from "node:os"; 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/changeset/loaders"; +import { getBundledVcsCatalog } from "../packages/hunk/src/app/vcsCatalog"; +import { loadAppBootstrap } from "../packages/hunk/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 a090f999b..fdd642521 100644 --- a/benchmarks/changeset-parse.ts +++ b/benchmarks/changeset-parse.ts @@ -1,9 +1,9 @@ // 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/changeset/diffFile"; -import { findPatchChunk, splitPatchIntoFileChunks } from "../src/core/patch/chunks"; -import { sanitizePatchText } from "../src/core/patch/sanitize"; +import { buildDiffFile } from "../packages/hunk/src/core/changeset/diffFile"; +import { findPatchChunk, splitPatchIntoFileChunks } from "../packages/hunk/src/core/patch/chunks"; +import { sanitizePatchText } from "../packages/hunk/src/core/patch/sanitize"; import { createSyntheticPatch } from "./lib/fixtures"; interface Scenario { diff --git a/benchmarks/compact-highlight-payload.ts b/benchmarks/compact-highlight-payload.ts index f09189b59..674c611c1 100644 --- a/benchmarks/compact-highlight-payload.ts +++ b/benchmarks/compact-highlight-payload.ts @@ -3,20 +3,20 @@ // 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/changeset/model"; +import type { DiffFile } from "../packages/hunk/src/core/changeset/model"; import { buildSplitRows, loadHighlightedDiff, type HighlightedDiffCode, -} from "../src/ui/diff/diffRows"; +} from "../packages/hunk/src/ui/diff/diffRows"; import { compactHighlightRunsForLine, compactHighlightTransferList, compactHighlightedDiffByteLength, encodeCompactHighlightedDiff, validateCompactHighlightedDiff, -} from "../src/ui/diff/worker"; -import { resolveTheme } from "../src/ui/themes"; +} from "../packages/hunk/src/ui/diff/worker"; +import { resolveTheme } from "../packages/hunk/src/ui/themes"; const LINE_COUNT = Number(process.env.HUNK_COMPACT_HIGHLIGHT_LINES ?? 8_000); const SAMPLES = Number(process.env.HUNK_COMPACT_HIGHLIGHT_SAMPLES ?? 7); diff --git a/benchmarks/geometry-memory.ts b/benchmarks/geometry-memory.ts index 04061d66a..53f402157 100644 --- a/benchmarks/geometry-memory.ts +++ b/benchmarks/geometry-memory.ts @@ -1,8 +1,8 @@ // Track retained memory for the all-files geometry cache used by review scrolling/navigation. import { heapStats } from "bun:jsc"; import { performance } from "node:perf_hooks"; -import { measureDiffSectionGeometry } from "../src/ui/diff/diffSectionGeometry"; -import { resolveTheme } from "../src/ui/themes"; +import { measureDiffSectionGeometry } from "../packages/hunk/src/ui/diff/diffSectionGeometry"; +import { resolveTheme } from "../packages/hunk/src/ui/themes"; import { createGiantSingleDiffFile, createLargeSplitStreamBootstrap, diff --git a/benchmarks/highlight-cache-layers.ts b/benchmarks/highlight-cache-layers.ts index f77ad098b..4b165e2ad 100644 --- a/benchmarks/highlight-cache-layers.ts +++ b/benchmarks/highlight-cache-layers.ts @@ -1,10 +1,10 @@ // 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/changeset/model"; -import { resolveTheme } from "../src/ui/themes"; -import { disposeHighlightWorker } from "../src/ui/diff/worker/highlightWorkerClient"; -import { prefetchHighlightedDiff } from "../src/ui/diff/useHighlightedDiff"; +import type { DiffFile } from "../packages/hunk/src/core/changeset/model"; +import { resolveTheme } from "../packages/hunk/src/ui/themes"; +import { disposeHighlightWorker } from "../packages/hunk/src/ui/diff/worker/highlightWorkerClient"; +import { prefetchHighlightedDiff } from "../packages/hunk/src/ui/diff/useHighlightedDiff"; const lineCount = 8_000; const theme = resolveTheme("github-dark-default", null); diff --git a/benchmarks/highlight-prefetch.ts b/benchmarks/highlight-prefetch.ts index 1ccd7946c..340e14338 100644 --- a/benchmarks/highlight-prefetch.ts +++ b/benchmarks/highlight-prefetch.ts @@ -5,9 +5,9 @@ import React from "react"; 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 } from "../src/core/bootstrap"; -import type { DiffFile } from "../src/core/changeset/model"; +import { AppHost } from "../packages/hunk/src/ui/AppHost"; +import type { AppBootstrap } from "../packages/hunk/src/core/bootstrap"; +import type { DiffFile } from "../packages/hunk/src/core/changeset/model"; function createDiffFile(index: number, marker: string): DiffFile { const path = `src/example${index}.ts`; diff --git a/benchmarks/huge-stream.ts b/benchmarks/huge-stream.ts index c4c006ac2..1d96bc196 100644 --- a/benchmarks/huge-stream.ts +++ b/benchmarks/huge-stream.ts @@ -7,7 +7,7 @@ import { performance } from "node:perf_hooks"; import { testRender } from "@opentui/react/test-utils"; import React from "react"; -import { AppHost } from "../src/ui/AppHost"; +import { AppHost } from "../packages/hunk/src/ui/AppHost"; import { createHugeStreamBootstrap, GIANT_SINGLE_FILE_LINES, diff --git a/benchmarks/interaction-latency.ts b/benchmarks/interaction-latency.ts index d1c3ee983..6fc0ed7e8 100644 --- a/benchmarks/interaction-latency.ts +++ b/benchmarks/interaction-latency.ts @@ -4,7 +4,7 @@ import { performance } from "node:perf_hooks"; import { testRender } from "@opentui/react/test-utils"; import React from "react"; -import { AppHost } from "../src/ui/AppHost"; +import { AppHost } from "../packages/hunk/src/ui/AppHost"; import { createLargeSplitStreamBootstrap, DEFAULT_FILE_COUNT, diff --git a/benchmarks/large-stream-fixture.ts b/benchmarks/large-stream-fixture.ts index c55366de3..c6dd56af9 100644 --- a/benchmarks/large-stream-fixture.ts +++ b/benchmarks/large-stream-fixture.ts @@ -1,6 +1,6 @@ import { parseDiffFromFile, parsePatchFiles } from "@pierre/diffs"; -import type { AppBootstrap } from "../src/core/bootstrap"; -import type { DiffFile } from "../src/core/changeset/model"; +import type { AppBootstrap } from "../packages/hunk/src/core/bootstrap"; +import type { DiffFile } from "../packages/hunk/src/core/changeset/model"; export const DEFAULT_FILE_COUNT = 180; export const DEFAULT_LINES_PER_FILE = 120; diff --git a/benchmarks/large-stream-profile.ts b/benchmarks/large-stream-profile.ts index c5d1b8d97..eeaa64fc0 100644 --- a/benchmarks/large-stream-profile.ts +++ b/benchmarks/large-stream-profile.ts @@ -1,10 +1,10 @@ // Profile large split-mode review streams by timing the main pure planning stages // before the React tree and renderer get involved. import { performance } from "perf_hooks"; -import { buildSplitRows } from "../src/ui/diff/diffRows"; -import { buildReviewRenderPlan } from "../src/ui/diff/reviewRenderPlan"; -import { measureDiffSectionGeometry } from "../src/ui/diff/diffSectionGeometry"; -import { resolveTheme } from "../src/ui/themes"; +import { buildSplitRows } from "../packages/hunk/src/ui/diff/diffRows"; +import { buildReviewRenderPlan } from "../packages/hunk/src/ui/diff/reviewRenderPlan"; +import { measureDiffSectionGeometry } from "../packages/hunk/src/ui/diff/diffSectionGeometry"; +import { resolveTheme } from "../packages/hunk/src/ui/themes"; import { createLargeSplitStreamFiles, DEFAULT_FILE_COUNT, diff --git a/benchmarks/large-stream.ts b/benchmarks/large-stream.ts index 9a82c730b..1c460762d 100644 --- a/benchmarks/large-stream.ts +++ b/benchmarks/large-stream.ts @@ -2,8 +2,8 @@ import { performance } from "perf_hooks"; import React from "react"; import { testRender } from "@opentui/react/test-utils"; -import { AppHost } from "../src/ui/AppHost"; -import { VIEWPORT_READ_COALESCE_MS } from "../src/ui/lib/viewportTiming"; +import { AppHost } from "../packages/hunk/src/ui/AppHost"; +import { VIEWPORT_READ_COALESCE_MS } from "../packages/hunk/src/ui/lib/viewportTiming"; import { createLargeSplitStreamBootstrap, DEFAULT_FILE_COUNT, diff --git a/benchmarks/memory.ts b/benchmarks/memory.ts index cac06e3b3..34fb51d16 100644 --- a/benchmarks/memory.ts +++ b/benchmarks/memory.ts @@ -3,10 +3,10 @@ import { performance } from "perf_hooks"; import React from "react"; import { testRender } from "@opentui/react/test-utils"; import { act } from "react"; -import { buildSplitRows } from "../src/ui/diff/diffRows"; -import { buildReviewRenderPlan } from "../src/ui/diff/reviewRenderPlan"; -import { resolveTheme } from "../src/ui/themes"; -import { AppHost } from "../src/ui/AppHost"; +import { buildSplitRows } from "../packages/hunk/src/ui/diff/diffRows"; +import { buildReviewRenderPlan } from "../packages/hunk/src/ui/diff/reviewRenderPlan"; +import { resolveTheme } from "../packages/hunk/src/ui/themes"; +import { AppHost } from "../packages/hunk/src/ui/AppHost"; import { createLargeSplitStreamBootstrap } from "./large-stream-fixture"; const viewport = { width: 240, height: 28 } as const; diff --git a/benchmarks/navigation-memory.ts b/benchmarks/navigation-memory.ts index 18dcca633..a6193feec 100644 --- a/benchmarks/navigation-memory.ts +++ b/benchmarks/navigation-memory.ts @@ -3,7 +3,7 @@ import { testRender } from "@opentui/react/test-utils"; import { performance } from "node:perf_hooks"; import React from "react"; import { act } from "react"; -import { AppHost } from "../src/ui/AppHost"; +import { AppHost } from "../packages/hunk/src/ui/AppHost"; import { createLargeSplitStreamBootstrap } from "./large-stream-fixture"; type MemorySample = { diff --git a/benchmarks/non-ascii-stream.ts b/benchmarks/non-ascii-stream.ts index 9ce73db95..9c73ed441 100644 --- a/benchmarks/non-ascii-stream.ts +++ b/benchmarks/non-ascii-stream.ts @@ -5,7 +5,7 @@ import { performance } from "node:perf_hooks"; import { testRender } from "@opentui/react/test-utils"; import React from "react"; -import { AppHost } from "../src/ui/AppHost"; +import { AppHost } from "../packages/hunk/src/ui/AppHost"; import { createLargeSplitStreamBootstrap } from "./large-stream-fixture"; import { destroyRenderer, diff --git a/benchmarks/render-layout.ts b/benchmarks/render-layout.ts index d8c974151..6d5effc07 100644 --- a/benchmarks/render-layout.ts +++ b/benchmarks/render-layout.ts @@ -1,9 +1,9 @@ // Benchmark pure diff row/layout planning across split, stack, and size-shape cases. import { performance } from "perf_hooks"; -import { buildSplitRows, buildStackRows } from "../src/ui/diff/diffRows"; -import { buildReviewRenderPlan } from "../src/ui/diff/reviewRenderPlan"; -import { measureDiffSectionGeometry } from "../src/ui/diff/diffSectionGeometry"; -import { resolveTheme } from "../src/ui/themes"; +import { buildSplitRows, buildStackRows } from "../packages/hunk/src/ui/diff/diffRows"; +import { buildReviewRenderPlan } from "../packages/hunk/src/ui/diff/reviewRenderPlan"; +import { measureDiffSectionGeometry } from "../packages/hunk/src/ui/diff/diffSectionGeometry"; +import { resolveTheme } from "../packages/hunk/src/ui/themes"; import { createLargeSplitStreamFiles } from "./large-stream-fixture"; const theme = resolveTheme("midnight", null); diff --git a/benchmarks/resize-memory.ts b/benchmarks/resize-memory.ts index ca3b59e74..8e3840c19 100644 --- a/benchmarks/resize-memory.ts +++ b/benchmarks/resize-memory.ts @@ -4,7 +4,7 @@ import { mkdirSync, writeFileSync } from "node:fs"; import { dirname, resolve } from "node:path"; import { performance } from "node:perf_hooks"; import React from "react"; -import { AppHost } from "../src/ui/AppHost"; +import { AppHost } from "../packages/hunk/src/ui/AppHost"; import { createLargeSplitStreamBootstrap } from "./large-stream-fixture"; type MemorySample = { diff --git a/benchmarks/run.ts b/benchmarks/run.ts index d62688a25..9dd4e74cd 100644 --- a/benchmarks/run.ts +++ b/benchmarks/run.ts @@ -99,7 +99,9 @@ function gitSha() { async function packageVersion() { try { - const packageJson = JSON.parse(await Bun.file("package.json").text()) as { version?: string }; + const packageJson = JSON.parse(await Bun.file("packages/hunk/package.json").text()) as { + version?: string; + }; return packageJson.version; } catch { return undefined; diff --git a/benchmarks/terminal-width.ts b/benchmarks/terminal-width.ts index 012a323d5..53a32c565 100644 --- a/benchmarks/terminal-width.ts +++ b/benchmarks/terminal-width.ts @@ -1,7 +1,7 @@ // Benchmark Hunk's scalar fast path and cached complex-cluster path against string-width. import { performance } from "node:perf_hooks"; import stringWidth from "string-width"; -import { measureTextWidth } from "../src/ui/lib/text"; +import { measureTextWidth } from "../packages/hunk/src/ui/lib/text"; const ITERATIONS = 2_000; const WARMUP_ITERATIONS = 50; diff --git a/benchmarks/worker-highlight-cache.ts b/benchmarks/worker-highlight-cache.ts index 2e344423c..70a7d692b 100644 --- a/benchmarks/worker-highlight-cache.ts +++ b/benchmarks/worker-highlight-cache.ts @@ -4,8 +4,8 @@ import { parseDiffFromFile } from "@pierre/diffs"; import { disposeHighlightWorker, highlightDiffInWorker, -} from "../src/ui/diff/worker/highlightWorkerClient"; -import { compactHighlightedDiffByteLength } from "../src/ui/diff/worker/highlightCompact"; +} from "../packages/hunk/src/ui/diff/worker/highlightWorkerClient"; +import { compactHighlightedDiffByteLength } from "../packages/hunk/src/ui/diff/worker/highlightCompact"; const lineCount = 8_000; const additions = Array.from( diff --git a/benchmarks/working-tree-load.ts b/benchmarks/working-tree-load.ts index d8156cdef..285990174 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/changeset/loaders"; +import { getBundledVcsCatalog } from "../packages/hunk/src/app/vcsCatalog"; +import { loadAppBootstrap } from "../packages/hunk/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 760e441f5..efe32beb7 100644 --- a/benchmarks/wrapped-cjk.ts +++ b/benchmarks/wrapped-cjk.ts @@ -4,12 +4,12 @@ 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 } 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"; -import { resolveTheme } from "../src/ui/themes"; +import type { AppBootstrap } from "../packages/hunk/src/core/bootstrap"; +import type { DiffFile } from "../packages/hunk/src/core/changeset/model"; +import { AppHost } from "../packages/hunk/src/ui/AppHost"; +import { prefetchHighlightedDiff } from "../packages/hunk/src/ui/diff/useHighlightedDiff"; +import { VIEWPORT_READ_COALESCE_MS } from "../packages/hunk/src/ui/lib/viewportTiming"; +import { resolveTheme } from "../packages/hunk/src/ui/themes"; import { destroyRenderer, renderPass, diff --git a/bun.lock b/bun.lock index 8293d3e78..a22e14aa2 100644 --- a/bun.lock +++ b/bun.lock @@ -4,16 +4,6 @@ "workspaces": { "": { "name": "hunk", - "dependencies": { - "bun": "^1.4.2", - "chokidar": "^4.0.3", - "commander": "^14.0.3", - "diff": "^8.0.3", - "get-east-asian-width": "^1.5.0", - "shell-quote": "1.9.0", - "string-width": "^8.2.1", - "zod": "~4.4.3", - }, "devDependencies": { "@changesets/changelog-github": "^0.7.0", "@hunk/session-broker": "workspace:*", @@ -27,7 +17,9 @@ "@types/bun": "1.4.1", "@types/react": "^19.2.14", "@types/ws": "^8.18.1", + "bun": "^1.4.2", "dependency-cruiser": "18.2.0", + "diff": "^8.0.3", "knip": "^6.32.0", "lint-staged": "^16.4.0", "marked": "17.0.1", @@ -35,9 +27,38 @@ "oxlint": "^1.56.0", "react": "^19.2.4", "simple-git-hooks": "^2.14.0", + "string-width": "^8.2.1", "tuistory": "^0.11.0", "typescript": "^5.9.3", }, + }, + "packages/hunk": { + "name": "hunkdiff", + "version": "0.21.1", + "bin": { + "hunk": "./bin/hunk.cjs", + "hunkdiff": "./bin/hunk.cjs", + }, + "dependencies": { + "bun": "^1.4.2", + "chokidar": "^4.0.3", + "commander": "^14.0.3", + "diff": "^8.0.3", + "get-east-asian-width": "^1.5.0", + "shell-quote": "1.9.0", + "string-width": "^8.2.1", + "zod": "~4.4.3", + }, + "devDependencies": { + "@opentui/core": "^0.5.6", + "@opentui/react": "^0.5.6", + "@pierre/diffs": "1.3.5", + "@shikijs/themes": "3.23.0", + "@types/bun": "1.4.1", + "@types/react": "^19.2.14", + "react": "^19.2.4", + "typescript": "^5.9.3", + }, "peerDependencies": { "@opentui/core": "^0.5.6", "@opentui/react": "^0.5.6", @@ -487,6 +508,8 @@ "human-signals": ["human-signals@8.0.1", "", {}, "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ=="], + "hunkdiff": ["hunkdiff@workspace:packages/hunk"], + "ignore": ["ignore@7.0.6", "", {}, "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw=="], "ini": ["ini@4.1.1", "", {}, "sha512-QQnnxNyfvmHFIsj7gkPcYymR8Jdw/o7mp5ZFihxn6h8Ci6fh3Dx4E1gPjpQEpIuPo9XVNY/ZUwh4BPMjGyL01g=="], diff --git a/docs/agent-workflows.md b/docs/agent-workflows.md index 5020d45bc..318abc45b 100644 --- a/docs/agent-workflows.md +++ b/docs/agent-workflows.md @@ -8,7 +8,7 @@ Use Hunk with agents in two ways: ## Recommended workflow: steer a live Hunk window 1. Open Hunk in one terminal with a normal review command such as `hunk diff` or `hunk show`. -2. Load the Hunk review skill: [`skills/hunk-review/SKILL.md`](../skills/hunk-review/SKILL.md). +2. Load the Hunk review skill: [`packages/hunk/skills/hunk-review/SKILL.md`](../packages/hunk/skills/hunk-review/SKILL.md). 3. Ask the agent to use the skill and review the current session. A good generic prompt is: diff --git a/docs/browser-review-rebuild.md b/docs/browser-review-rebuild.md index 7d8d9ecce..d493893f6 100644 --- a/docs/browser-review-rebuild.md +++ b/docs/browser-review-rebuild.md @@ -15,20 +15,20 @@ Phase 6 carries the `minor` changeset announcing the feature. ## Phase 0 — seam contract and guardrails (this doc) -- Boundary gates for `src/core/review/` (the shared review model), `src/session/reviewProtocol.ts` - (the wire schema), and `src/web/` (the browser client). The gates tolerate absent trees, so +- Boundary gates for `packages/hunk/src/core/review/` (the shared review model), `packages/hunk/src/session/reviewProtocol.ts` + (the wire schema), and `packages/hunk/src/web/` (the browser client). The gates tolerate absent trees, so they land ahead of the code they constrain. - A shrink-only debt map for the Node-only primitives the prototype's model files still carry; each entry must be repaid with a platform-neutral implementation before a browser bundle may import that file. - The existing architecture boundaries stay at full strength. The prototype relocated bundled - VCS providers into `src/core/vcs/` and weakened this suite to compensate; that relocation must + VCS providers into `packages/hunk/src/core/vcs/` and weakened this suite to compensate; that relocation must not ride along with any rebuild phase — extraction PRs land against the restored gates. ## Phase 1 — review model + terminal adoption (three PRs) 1. **Review store**: `state / actions / reducer / store / intents / selectors` in - `src/core/review/`, with `useReviewController` / `App` / `AppHost` refactored onto it in the + `packages/hunk/src/core/review/`, with `useReviewController` / `App` / `AppHost` refactored onto it in the same PR. Behavior-neutral; existing PTY integration tests must pass untouched. 2. **Review document projection + diff geometry**: `document / identity / sourceIdentity / anchors / contentManifest / notes / expansion / reconcile / jsonStream` plus the geometry @@ -49,7 +49,7 @@ existing PTY suite passing untouched. ## Phase 2 — producer runtime -`src/app/reviewSessionRuntime.ts`: generations, snapshot serving, resource materialization, +`packages/hunk/src/app/reviewSessionRuntime.ts`: generations, snapshot serving, resource materialization, serving the existing `hunk session` surface only. Resource read failures map to distinct error codes (integrity failures are never collapsed into `unknown-resource`). @@ -176,7 +176,7 @@ browser mirrors the terminal theme. ## Commands and keyboard shortcuts in the browser -The terminal command system (`src/ui/lib/appCommands.ts`) fuses three separable things per +The terminal command system (`packages/hunk/src/ui/lib/appCommands.ts`) fuses three separable things per command: identity (id, title, chords), binding (terminal `KeyEvent` matching), and effect (closures over live App state). Making commands work in the browser means splitting them, not transporting them: diff --git a/docs/browser-review-seam-audit.md b/docs/browser-review-seam-audit.md index 485e4eb1d..555cd0188 100644 --- a/docs/browser-review-seam-audit.md +++ b/docs/browser-review-seam-audit.md @@ -50,8 +50,8 @@ whether a client needs more than that is Phase 5's first question. ## A. Diff geometry - **A1. Collapsed-gap line ranges — 3 implementations, terminal off by one.** Terminal - `src/ui/diff/pierre.ts` (`leadingCollapsedRanges`/`trailingCollapsedRanges`, ~:456-480) vs - core `src/core/review/expansion.ts` (`reviewGapAddress`); web already consumes core's gap + `packages/hunk/src/ui/diff/pierre.ts` (`leadingCollapsedRanges`/`trailingCollapsedRanges`, ~:456-480) vs + core `packages/hunk/src/core/review/expansion.ts` (`reviewGapAddress`); web already consumes core's gap objects. For a pure-insertion hunk the terminal's leading range is off by one against core's, and the trailing math diverges for zero-count sides; a note created on a terminal-expanded line can be rejected on reload because `intents.ts` validates against `reviewGapAddress`. @@ -63,7 +63,7 @@ whether a client needs more than that is Phase 5's first question. anchor side has zero rows, so `pure-deletion-hunk` pins lines 1–5 instead of preserving the former one-line undercount. A2's separate trailing-gap residual remains. - **A2. Trailing-context existence — 3 formulations.** `pierre.ts` `trailingCollapsedLines`, - producer `src/app/session/registration.ts` (~:131-139, boolean `hasTrailingContext`), core + producer `packages/hunk/src/app/session/registration.ts` (~:131-139, boolean `hasTrailingContext`), core `expansion.ts`. The browser can offer a "Trailing context" button whose expansion core then rejects (`gap-not-found`). Fix: one `reviewTrailingGap(file)` in core. _Repaid (Phase 1 PR 2)_: `reviewTrailingGap` in `core/review/expansion.ts`; @@ -137,7 +137,7 @@ whether a client needs more than that is Phase 5's first question. - **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). + `packages/hunk/src/web/main.tsx` (or fold registration into the shared model). Renderer-specific, do not unify: terminal row construction and measured-cell windowing (`rowWindowing.ts`, `diffSectionGeometry.ts`) vs browser IntersectionObserver windowing and @@ -251,7 +251,7 @@ duplication); hunk header text (browser delegates to Pierre separators); platfor decides whether the claim holds. The wire carries it as `expandedLineProof` on the two actions that can name a line (`notes/start-draft`'s target, and `notes/create-user`'s precondition on the draft it is saving), refusing evidence that accompanies no line, and - `src/app/session/reviewCommands.ts` checks it before planning. Where the resulting note hangs + `packages/hunk/src/app/session/reviewCommands.ts` checks it before planning. Where the resulting note hangs is deliberately _not_ decided there: it goes through `reviewLineAnchor`'s fallback owner exactly as a terminal note does, which `reviewCommands.test.ts` pins by asserting an empty intersection set and the declared owner. Fixtures `start-draft-on-an-expanded-line`, @@ -320,7 +320,7 @@ path suffixes, expansion retention, git-status badges). `test/review-conformance/orderingFixtures.ts` and cover both the classification and the transitions a real producer emits. Broker and browser sites close in Phases 3 and 5. _Repaid (Phase 3, broker site)_: the daemon's `ReviewMirror` - (`src/session/broker/reviewMirror.ts`) holds one publication per session and orders every + (`packages/hunk/src/session/broker/reviewMirror.ts`) holds one publication per session and orders every arriving one with a single `classifyReviewPublication` call — `accepted` advances the revision, `gap` replaces the generation and retires everything derived from the old one, `stale` is ignored. It has no comparison of its own, so the prototype's "accept equal @@ -328,7 +328,7 @@ path suffixes, expansion retention, git-status badges). new position is a replay, exactly as the contract says. The one non-ordering rule it does apply is stated as such — a later generation is adoptable only together with the catalog describing it, because a mirror holding a position whose resources it cannot name would - advertise reads nobody can serve. `src/app/session/reviewCommands.ts` makes the same one + advertise reads nobody can serve. `packages/hunk/src/app/session/reviewCommands.ts` makes the same one call for an action's `expectedStateRevision`, so "has the review moved past what this caller decided from" is the same question as "is this publication ahead". The mirror is registered against the Phase 2 fixtures as the `broker review mirror` ordering consumer, @@ -342,7 +342,7 @@ path suffixes, expansion retention, git-status badges). reservations, and concurrency tuning stay at the edges. _Repaid (Phase 2, producer site)_: `core/review/resources.ts` owns resource addressing, the chunk bound both ends validate against, and the failure vocabulary; - `src/app/review/resourceStore.ts` produces and serves the bytes. Single flight is + `packages/hunk/src/app/review/resourceStore.ts` produces and serves the bytes. Single flight is structural rather than a cache bolted on — a read reaches the underlying reader only through the in-flight map — and bulk loads run under an explicit concurrency limit instead of an unbounded `Promise.all`, which is the pair of defects the original review found. @@ -354,7 +354,7 @@ path suffixes, expansion retention, git-status badges). each chunk to start where the last ended, treats a chunk that neither advances nor ends as a failure, accepts a zero-length resource as one empty end-of-stream chunk, and verifies the assembled bytes against the digest with a `ReviewDigestFn` the caller injects. The - broker's load loop (`src/session/broker/state.ts`) does nothing but ask for the next window + broker's load loop (`packages/hunk/src/session/broker/state.ts`) does nothing but ask for the next window and decode it; there is one loop, where the prototype had two near-verbatim copies inside this file that already disagreed about progress and end-of-stream. Bounding is `reviewResourceCache.ts`: an LRU with a daemon-wide byte budget plus a reservation taken @@ -362,7 +362,7 @@ path suffixes, expansion retention, git-status badges). resource reserves one chunk and is resized to what the writer declares rather than reserving its kind's ceiling, which is what let a handful of ordinary patches serialize the parallel loads. Single flight is one map keyed by session, generation, and resource id; - concurrent callers await the same assembly. `src/session/broker/reviewResources.integration.test.ts` + concurrent callers await the same assembly. `packages/hunk/src/session/broker/reviewResources.integration.test.ts` drives the whole path with only the socket replaced. The browser's `apiClient` range loop closes in Phase 5. - **C3. Epoch/supersede/trailing-retry — 2 parallel machines.** Runtime reload queue @@ -373,9 +373,9 @@ path suffixes, expansion retention, git-status badges). begin/end envelopes, and the event-id grammar are built in `browserReviewServer.ts` and re-declared/regex-parsed in `mirror.ts`/`apiClient.ts`; client bounds (12 MiB / 1024 chunks) are unlinked from server bounds and only coincidentally compatible. Fix: - `src/session/reviewEventProtocol.ts` owning names, envelopes, id grammar, and bounds derived + `packages/hunk/src/session/reviewEventProtocol.ts` owning names, envelopes, id grammar, and bounds derived from `MAX_BROWSER_REVIEW_SNAPSHOT_BYTES`. - _Repaid (Phase 4, server side)_: `src/session/reviewEventProtocol.ts` owns the event + _Repaid (Phase 4, server side)_: `packages/hunk/src/session/reviewEventProtocol.ts` owns the event vocabulary, the frame names and their phases, the begin/chunk/end envelopes and their parsers, the event-id grammar, and every bound — `MAX_REVIEW_EVENT_PAYLOAD_BYTES` is the protocol's envelope bound, `REVIEW_EVENT_CHUNK_BYTES` is the shared resource chunk size, @@ -413,7 +413,7 @@ path suffixes, expansion retention, git-status badges). `test/review-conformance/noteSize.ts` pin the boundary the two prototype rules disagreed at, including a note whose summary, rationale, and markup each fit while the note itself is three times the bound. Wire and composer sites adopt it in Phases 3 and 5. - _Repaid (Phase 3, wire site)_: `isTransportableReviewNote` in `src/session/reviewProtocol.ts` + _Repaid (Phase 3, wire site)_: `isTransportableReviewNote` in `packages/hunk/src/session/reviewProtocol.ts` is `reviewNoteWithinSizeLimit` and nothing else — the wire has no per-field check any more, and declares no second bound. The protocol module is registered as a consumer of the note-size corpus, so `every-field-fits-but-the-note-does-not` — the note whose summary, rationale, and @@ -437,7 +437,7 @@ path suffixes, expansion retention, git-status badges). the prototype's failure impossible rather than merely fixed: its broker copy re-derived intersections, omitted the fallback branch, and rejected a legal expanded-gap note — and with it the whole registration. The case is pinned from the wire end in - `src/app/session/reviewCommands.test.ts`: a note created remotely on an expanded-gap line + `packages/hunk/src/app/session/reviewCommands.test.ts`: a note created remotely on an expanded-gap line ends up with an empty intersection set and the fallback owner the caller declared, which is exactly the shape the dropped branch produced. Web `pierreNoteAnchor` closes in Phase 5. - **D4. Canonical-file ↔ manifest consistency — 3 checks, 3 field lists.** Producer @@ -471,7 +471,7 @@ path suffixes, expansion retention, git-status badges). variant is what let a writer and a reader disagree — with `normalizeReviewDigest` for values arriving from outside and `reviewDigestsEqual` normalizing _both_ operands. Hashing itself is an injected `ReviewDigestFn` rather than inline `createHash` calls; the producer supplies - Node's at the edge (`src/core/reviewDigest.ts`), which is also what repaid the shared model's + Node's at the edge (`packages/hunk/src/core/reviewDigest.ts`), which is also what repaid the shared model's last node-debt entry. Resource bounds are constants in `core/review/resources.ts` that the producer imports rather than restates. Wire constants, the action-envelope parser, and the two note-filter namings are Phase 3. @@ -497,7 +497,7 @@ path suffixes, expansion retention, git-status badges). - **E1. File stat badges.** Terminal `ui/lib/files.ts` `formatSidebarStat` (zero-hiding, truncation marker) vs web inline `+${additions} −${deletions}` in `treeSource.ts`. One shared formatter. -- **E2. Theme.** Web hardcodes two standalone palettes disconnected from `src/ui/themes` and +- **E2. Theme.** Web hardcodes two standalone palettes disconnected from `packages/hunk/src/ui/themes` and the `AppTheme` mapping; whether the browser mirrors the terminal theme is an open product decision — decide before Phase 5, don't unify by default. @@ -508,14 +508,14 @@ like A–E; they are the copies the browser _would_ grow the moment shortcuts ar here so the extraction happens before the duplication exists. Design detail in `browser-review-rebuild.md` § "Commands and keyboard shortcuts in the browser". -- **F1. Command catalog fused with terminal binding and effects.** `src/ui/lib/appCommands.ts` +- **F1. Command catalog fused with terminal binding and effects.** `packages/hunk/src/ui/lib/appCommands.ts` couples identity (id, title, chords), binding (terminal `KeyEvent` matchers), and effect (closures over live App state) in one table; menus (`ui/lib/appMenus.ts`) and the help dialog render from it, so a browser palette or help screen would have to restate the list. 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/run/commandCatalog.ts` carries id, title, category, default + _Repaid (Phase 1 PR 3)_: `packages/hunk/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 @@ -624,13 +624,13 @@ implementation does. `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 + `packages/hunk/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`, `stale-generation`, resource integrity failures) would surface as ad-hoc strings invented in - `src/web`, drifting from what the terminal shows for the same failure. One error-code → + `packages/hunk/src/web`, drifting from what the terminal shows for the same failure. One error-code → user-message catalog beside the wire protocol, consumed by both clients (and reused by the agent surface where codes overlap). Phase 4 (codes stabilize) / Phase 5 (browser consumes). - _Repaid (Phase 4, catalog creation)_: `src/session/reviewErrorCatalog.ts` gives every code a + _Repaid (Phase 4, catalog creation)_: `packages/hunk/src/session/reviewErrorCatalog.ts` gives every code a statement and a remedy, in the agent surface's own pattern. Totality is mechanical rather than reviewed: the catalog is a `Record` over `HunkReviewClientErrorCodeV1`, itself _composed_ — resource plus request plus intent-planning plus the transport's own codes — diff --git a/docs/changelog-on-hunk-dev.md b/docs/changelog-on-hunk-dev.md index 73f663f92..4b186eeb4 100644 --- a/docs/changelog-on-hunk-dev.md +++ b/docs/changelog-on-hunk-dev.md @@ -134,6 +134,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/process/updateNotice.ts` tells users a new version exists without +- **The in-app update notice.** `packages/hunk/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 tracked separately. diff --git a/docs/extension-architecture.md b/docs/extension-architecture.md index ccf422cd3..f4f633929 100644 --- a/docs/extension-architecture.md +++ b/docs/extension-architecture.md @@ -9,13 +9,13 @@ exists so you know which module owns what. ## Tiers and loading Extensions come in two tiers running through the same per-extension API -object and registry collection (`src/extensions/runExtension.ts`): +object and registry collection (`packages/hunk/src/extensions/runExtension.ts`): - **User extensions** load at interactive-app startup, before - `loadAppBootstrap` (`src/extensions/startup.ts`, `src/extensions/host.ts`). - Discovery groups and trust gating: `src/extensions/discovery.ts`, - `src/extensions/trust.ts`. -- **Bundled extensions** live in `src/extensions/default/` and are compiled + `loadAppBootstrap` (`packages/hunk/src/extensions/startup.ts`, `packages/hunk/src/extensions/host.ts`). + Discovery groups and trust gating: `packages/hunk/src/extensions/discovery.ts`, + `packages/hunk/src/extensions/trust.ts`. +- **Bundled extensions** live in `packages/hunk/src/extensions/default/` and are compiled into the binary. `default/vcs/{git,jujutsu,sapling}` is statically imported by the app composition root (`app/vcsCatalog.ts`) and loaded synchronously before config resolution, so backends exist without making core import the @@ -39,15 +39,15 @@ reserved ids (`hunk`, plus the base catalog's bundled backend ids), ids outside `/^[A-Za-z0-9][A-Za-z0-9_-]*$/` (a dot or colon would make the composed ids unsplittable), and the later of two sources claiming one id; each refusal is a load issue and costs only that extension. The rules themselves are stated in -`src/extensions/extensionIds.ts`. +`packages/hunk/src/extensions/extensionIds.ts`. ## One registry, one apply path Registrations (session behavior, themes, file languages, VCS adapters, changeset transforms, panes, interactive commands, top-level CLI commands, lifecycle/UI events, and inter-extension bus listeners) collect into one -`ExtensionRegistry` (`src/extensions/types.ts`) and are resolved/applied -through `src/extensions/apply.ts` on both startup and reload. File-language registrations stay as +`ExtensionRegistry` (`packages/hunk/src/extensions/types.ts`) and are resolved/applied +through `packages/hunk/src/extensions/apply.ts` on both startup and reload. File-language registrations stay as declarative extension, filename, or glob selectors until `fileLanguageLookup.ts` resolves them; Hunk then pins that answer into Pierre's metadata so rendering cannot re-derive a conflicting language. A live reload replaces the compiled selector generation while preparing its changeset @@ -80,7 +80,7 @@ the handler releases I/O. ## Host-served runtime modules Extension files import `react`, `@opentui/*`, and `hunkdiff/extension` as -host-served runtime modules (`src/extensions/hostRuntimeModules.ts`): a +host-served runtime modules (`packages/hunk/src/extensions/hostRuntimeModules.ts`): a per-extension-directory Bun loader hook transpiles extension source and rewrites those specifiers to prefixed virtual modules backed by the host's own instances. That identity is what lets `registerPane` components @@ -91,14 +91,14 @@ commands never pay OpenTUI's native-library extraction). ## Four-edge pane system -`src/ui/lib/extensionPanes.ts` owns open state, availability, and one rectangle +`packages/hunk/src/ui/lib/extensionPanes.ts` owns open state, availability, and one rectangle plan for panes, dividers, and review bounds. Left/right panes consume columns; top/bottom panes consume rows from the central review column, outside review stream coordinates. Pane registrations may opt into a body-axis `fraction`; the planner resolves it to an integer target before applying bounds and lets a session-local divider drag override that automatic size. -`src/ui/components/panes/ExtensionPane.tsx` mounts panes with guarded actions, +`packages/hunk/src/ui/components/panes/ExtensionPane.tsx` mounts panes with guarded actions, immutable delegated review metadata, and failure containment. The fixed three-row `hunk:review-info` top pane uses one border row above two metadata rows and is available only for delegated change requests, so ordinary reviews spend no geometry on it. `DiffPane` exposes optional current-line paint — the row @@ -109,22 +109,22 @@ normalize into this same registry and layout path. ## File-view system File-view registrations are selected per file but remain inside the one -host-owned review stream. `src/ui/fileViews/useFileViews.ts` bounds asynchronous +host-owned review stream. `packages/hunk/src/ui/fileViews/useFileViews.ts` bounds asynchronous extension work and retains only immutable layouts accepted by -`src/ui/fileViews/layout.ts`; width and registration identity are part of that +`packages/hunk/src/ui/fileViews/layout.ts`; width and registration identity are part of that accepted geometry. A stateful view has no such identity to change, so `ctx.fileViews.refresh` bumps an invalidation epoch owned by -`src/ui/fileViews/useFilePresentationController.ts` and modeled in -`src/ui/fileViews/state.ts`. That epoch participates in the same retention key, +`packages/hunk/src/ui/fileViews/useFilePresentationController.ts` and modeled in +`packages/hunk/src/ui/fileViews/state.ts`. That epoch participates in the same retention key, re-preparing the files presenting that view while their current rows stay visible. One map counts both view-wide and per-file invalidation, and `fileViewLayoutEpoch` is the single place that composes them into the epoch a -`(file, view)` preparation is retained under. `src/ui/fileViews/renderPlan.ts` is the shared insertion +`(file, view)` preparation is retained under. `packages/hunk/src/ui/fileViews/renderPlan.ts` is the shared insertion plan for validated extension rows and host-owned inline notes. It resolves only unambiguous exact-source bindings and returns an explicit unresolved set, so `DiffPane` falls the complete file back to Pierre rather than guessing or -silently dropping review data. `src/ui/fileViews/geometry.ts` measures that same -plan, and `src/ui/components/panes/FileView.tsx` windows and paints it. Extension +silently dropping review data. `packages/hunk/src/ui/fileViews/geometry.ts` measures that same +plan, and `packages/hunk/src/ui/components/panes/FileView.tsx` windows and paints it. Extension components can paint only their fixed validated rectangles; note cards, scrolling, hunk bounds, and navigation remain host-owned. @@ -132,46 +132,46 @@ scrolling, hunk bounds, and navigation remain host-owned. Line highlighters mark character ranges inside Hunk's own diff rendering, so the system is deliberately split between a pull-based preparation half and a -paint-only application half. `src/ui/highlights/useLineHighlights.ts` bounds +paint-only application half. `packages/hunk/src/ui/highlights/useLineHighlights.ts` bounds asynchronous extension work with the same timeout/concurrency discipline as file views and retains only marks accepted by -`src/ui/highlights/validate.ts`; results cache under `(file, highlighter, +`packages/hunk/src/ui/highlights/validate.ts`; results cache under `(file, highlighter, epoch)`, and each file's merged mark array keeps a stable identity while its inputs are unchanged so row memoization can hold. The epoch is owned by -`src/ui/highlights/useLineHighlightsController.ts` behind +`packages/hunk/src/ui/highlights/useLineHighlightsController.ts` behind `ctx.highlights.refresh`, using the shared scoped-epoch policy in -`src/ui/lib/scopedEpochs.ts` — the same module `src/ui/fileViews/state.ts` +`packages/hunk/src/ui/lib/scopedEpochs.ts` — the same module `packages/hunk/src/ui/fileViews/state.ts` delegates to — and the shared bounded `readDocument` capability lives in -`src/ui/lib/extensionDocumentReader.ts`. +`packages/hunk/src/ui/lib/extensionDocumentReader.ts`. -Application is paint-time by construction. `src/ui/diff/lineHighlightPaint.ts` +Application is paint-time by construction. `packages/hunk/src/ui/diff/lineHighlightPaint.ts` owns the one mapping from source coordinates (raw code-unit offsets) to terminal columns — sanitize-aware, tab-aware, snapped outward to grapheme clusters, with context and gap lines sharing one range list under both side keys — and the one span transform that repaints backgrounds without changing -text. `src/ui/diff/rowStyle.ts` resolves tones against the actual line +text. `packages/hunk/src/ui/diff/rowStyle.ts` resolves tones against the actual line background with the word-diff minimum-contrast guarantee. -`src/ui/diff/CodeRowView.tsx` applies the transform through the cell painter, +`packages/hunk/src/ui/diff/CodeRowView.tsx` applies the transform through the cell painter, which keeps highlights out of `buildDiffSectionRowPlan`, its caches, and every geometry measurement: a highlight change is a repaint, never a re-plan. -`src/ui/diff/DiffRowView.tsx` remains only the memoized dispatch facade; raw-row +`packages/hunk/src/ui/diff/DiffRowView.tsx` remains only the memoized dispatch facade; raw-row adaptation there supports the public OpenTUI and extension current-line surfaces, while -`src/ui/diff/cursorHighlight.ts` owns stable-key cursor matching. The static pager never +`packages/hunk/src/ui/diff/cursorHighlight.ts` owns stable-key cursor matching. The static pager never runs extension code, so highlights are interactive-only. Agent attention marks (`hunk session highlight add` / `clear`) join this same pipeline rather than growing a second one: `useTerminalReview.ts` validates each daemon-pushed mark with the same `validate.ts` contract and caps, holds -them per file, and `src/ui/highlights/merge.ts` appends them after extension +them per file, and `packages/hunk/src/ui/highlights/merge.ts` appends them after extension marks in the one map `DiffPane` paints from — so agent marks share paint, contrast, and geometry guarantees, and win where ranges overlap. Unlike extension marks, nothing re-derives agent marks after a reload, so -`src/ui/highlights/reconcile.ts` carries them across a document replacement only +`packages/hunk/src/ui/highlights/reconcile.ts` carries them across a document replacement only for files whose `contentIdentity` is unchanged — those still show the same characters — and drops the rest. Line-target `session navigate` reuses the same `revealLine` landing policy `ctx.navigation.revealLine` gets. -`src/ui/fileViews/mode.ts` owns file-view mode activation, validity, and callback +`packages/hunk/src/ui/fileViews/mode.ts` owns file-view mode activation, validity, and callback containment. The presentation controller stores the active mode and funnels all exit paths through one teardown, including re-entrant handoffs. @@ -181,17 +181,17 @@ keyboard modes and app commands. `"handled"` and `"exit"` consume the key; Session-wide modes registered through `registerKeyboardMode` are resolved with the same extension ownership and first-registration rules as other surfaces. -`src/ui/keyboardModes/useKeyboardModeController.ts` owns the one active session +`packages/hunk/src/ui/keyboardModes/useKeyboardModeController.ts` owns the one active session mode, with eager ref state for input chunks, registry-generation authority, contained synchronous lifecycle callbacks, and one teardown used by Escape, status, menu, reload, and unmount. Mode controls are activation-scoped; `onEnter` and `onExit` cannot change ownership, while `onKey` may deliberately replace its activation without letting the outgoing callback defeat recovery or manipulate the replacement. -`src/ui/lib/extensionKeyEvent.ts` freezes the method-free public key snapshot +`packages/hunk/src/ui/lib/extensionKeyEvent.ts` freezes the method-free public key snapshot used by both session and file-view mode delivery, so OpenTUI events and their consumption methods never cross the extension boundary. Their shared -`src/ui/lib/synchronousExtensionCallback.ts` path contains lifecycle failures, +`packages/hunk/src/ui/lib/synchronousExtensionCallback.ts` path contains lifecycle failures, rejects thenables without leaving unhandled rejections, and normalizes key results; each mode module supplies only its context and attributed warnings. A focused file-view mode may overlap and temporarily outrank a session mode; @@ -200,20 +200,20 @@ leaving it resumes the session mode rather than destroying unrelated state. ## Command system Every app-level keyboard shortcut is a named command in one dispatch table -(`src/ui/lib/appCommands.ts`), each id under Hunk's reserved vendor namespace +(`packages/hunk/src/ui/lib/appCommands.ts`), each id under Hunk's reserved vendor namespace (`hunk.app.quit`, `hunk.review.nextHunk`) — which is what keeps built-in ids and extension-owned ids in disjoint spaces however either grows; modal surfaces (dialogs, menus, focused inputs) own their keys first and are deliberately not commands. Extension `registerCommand` entries join the same table via -`src/ui/lib/extensionCommands.ts` — built-ins win key conflicts, refused one +`packages/hunk/src/ui/lib/extensionCommands.ts` — built-ins win key conflicts, refused one chord at a time and detected by probing matchers with a synthesized event -(`src/lib/commandKeys.ts`). Command handlers receive pane controls and a selection snapshot from -`src/ui/lib/extensionSelection.ts`, derived from the same frozen file views the +(`packages/hunk/src/lib/commandKeys.ts`). Command handlers receive pane controls and a selection snapshot from +`packages/hunk/src/ui/lib/extensionSelection.ts`, derived from the same frozen file views the panes render plus a copied source address for the active current-line cursor. App reads it through a ref so the dispatch table stays stable while line navigation moves. `ctx.review.snapshot()` takes the complementary whole-review -path: `src/extensions/reviewSnapshot.ts` copies the active shared ReviewStore's +path: `packages/hunk/src/extensions/reviewSnapshot.ts` copies the active shared ReviewStore's document identities and complete saved-note collections, preserving core-owned anchors and reconciliation verdicts. App pairs that state with the producer's current generation under the same review capability lease, so retained controls @@ -221,7 +221,7 @@ return `null` after reload instead of reading replacement content. The extension projection is registered in `test/review-conformance/` as a real semantic consumer rather than rebuilding note placement in the command host. -`src/ui/lib/extensionNavigation.ts` mints the guarded navigation behind both +`packages/hunk/src/ui/lib/extensionNavigation.ts` mints the guarded navigation behind both `ctx.navigation` and a pane's `actions`, so a jump from either surface is validated, attributed, and reported the same way. It owns argument policy only — visible-file validation, hunk clamping, `revealLine`'s side and line-number @@ -243,12 +243,12 @@ modal keys also remain outside the table and therefore outside the event. `ctx.dialogs` is the one place extension code can interrupt the user, so its ordering and settlement live outside React in -`src/ui/lib/extensionDialogs.ts` — one FIFO queue per App instance, minting a +`packages/hunk/src/ui/lib/extensionDialogs.ts` — one FIFO queue per App instance, minting a per-extension `dialogs` object, normalizing (and sanitizing) extension-authored text into a request the host draws, and answering by request id so a duplicated Enter cannot spill onto whatever was queued behind. App subscribes with `useSyncExternalStore`, renders the current request through -`src/ui/components/chrome/ExtensionDialog.tsx` (confirm reuses `ConfirmDialog`; +`packages/hunk/src/ui/components/chrome/ExtensionDialog.tsx` (confirm reuses `ConfirmDialog`; select and input are `ModalFrame` surfaces), and unmount calls `shutdown()` so every pending and queued dialog resolves its cancel value instead of leaving a handler awaiting forever. Key precedence in `useAppKeyboardShortcuts` places @@ -260,7 +260,7 @@ frame carries an `ext ` attribution row — the toast marker — for every user-installed extension, because its title is extension-authored and a prompt must not be able to impersonate Hunk. The host derives the extension's trusted bundled origin from registry metadata and omits the redundant marker only for -Hunk-owned bundled UI. `src/ui/lib/modalGeometry.ts` clamps the frame before +Hunk-owned bundled UI. `packages/hunk/src/ui/lib/modalGeometry.ts` clamps the frame before extension text is wrapped or windowed, so measurement and rendering use the same terminal width; body/options yield rows to a pinned mouse-clickable action footer on short terminals. @@ -274,7 +274,7 @@ per-extension event-context provider, while `AppHost` publishes mounted lifecycle order (`startup`, then `changeset_loaded`; reloads add `session_reload`) only after the matching child commit. Headless or pre-mount delivery resolves dialogs to their cancel values and refuses navigation with a warning. -`src/ui/lib/extensionCapabilityLease.ts` binds retained pane, navigation, +`packages/hunk/src/ui/lib/extensionCapabilityLease.ts` binds retained pane, navigation, dialog, review-reload, and workspace controls to one App, extension registry, and review generation. Soft reload or registry retirement therefore makes old host-mediated capabilities @@ -284,7 +284,7 @@ inert before shutdown begins. Session behavior requests are registry data too: presentation view changes ephemeral without teaching `App` about an extension id. -`src/ui/lib/extensionWorkspace.ts` owns the policy for `ctx.workspace`. Reads +`packages/hunk/src/ui/lib/extensionWorkspace.ts` owns the policy for `ctx.workspace`. Reads resolve reviewed file ids through the existing source fetcher, which retains ownership of caching and size limits. Missing or unreadable sources become `null`. @@ -295,27 +295,27 @@ through refs so soft reloads update the policy inputs. The host verifies the filesystem target before and after consent, writes it, then calls `refreshCurrentInput`. Consent uses the existing extension-dialog queue. -Commands declare chords, not matchers: `src/ui/lib/keymap.ts` folds every +Commands declare chords, not matchers: `packages/hunk/src/ui/lib/keymap.ts` folds every command's `defaultKeys` against the user's `[keybindings]` table (user config layer only) into one id-to-chords answer, from which matchers, key labels, and conflict probes are all derived — a user-bound chord is exclusive, so whatever held it by default gives it up. The chord grammar itself lives in -`src/extension-api/keys.ts` because it is published as `hunkdiff/extension` +`packages/hunk/src/extension-api/keys.ts` because it is published as `hunkdiff/extension` (`matchesKey`, `parseKeyChord`, `matchesKeyChord`) for extension components -that need internal keys; `src/lib/commandKeys.ts` re-exports it inward and +that need internal keys; `packages/hunk/src/lib/commandKeys.ts` re-exports it inward and keeps the host-only pieces. The table is also the only description of what each action is called and which key runs it, so the mouse surfaces read from it rather than restating it: the -dropdown menus (`src/ui/lib/appMenus.ts`) declare items as command ids plus +dropdown menus (`packages/hunk/src/ui/lib/appMenus.ts`) declare items as command ids plus menu-specific wording and checkbox state, and the controls help dialog -(`src/ui/lib/helpContent.ts`) declares curated rows the same way — both render +(`packages/hunk/src/ui/lib/helpContent.ts`) declares curated rows the same way — both render their key text from resolved `keyLabels` and run entries through `executeAppCommand`. A few commands ship with `defaultKeys: []` because they exist for a menu item; they never match a key but remain bindable by id. Command handlers receive guarded `ctx.commands` controls built by -`src/ui/lib/extensionCommandControls.ts`. They resolve the live App command table on every call, +`packages/hunk/src/ui/lib/extensionCommandControls.ts`. They resolve the live App command table on every call, then expose only built-ins carrying explicit public metadata. Counted movement reaches the same command callback once with a normalized delta; it is never implemented as repeated synchronous dispatch. Current-line alignment is also semantic: App raises an alignment request and `DiffPane` @@ -325,23 +325,23 @@ Extension commands remain private to prevent recursion and cross-extension execu The **Extensions** menu is generated from the registered extension commands, one item per command grouped by extension, and is absent entirely when there are none — which is why the visible menu list is derived from the menus record -(`buildMenuSpecs` in `src/ui/components/chrome/menu.ts`) rather than fixed. +(`buildMenuSpecs` in `packages/hunk/src/ui/components/chrome/menu.ts`) rather than fixed. ## VCS adapters -`src/core/vcs/index.ts` owns provider-neutral catalog ordering, lookup, -detection, and operation dispatch. `src/app/vcsCatalog.ts` composes bundled -registrations, while `src/app/sessionBootstrap.ts` extends that catalog with +`packages/hunk/src/core/vcs/index.ts` owns provider-neutral catalog ordering, lookup, +detection, and operation dispatch. `packages/hunk/src/app/vcsCatalog.ts` composes bundled +registrations, while `packages/hunk/src/app/sessionBootstrap.ts` extends that catalog with accepted user adapters and threads the same value through loading, reload, and watch. Detection is uniform across tiers: nearest checkout wins, priority breaks equal-distance ties, and an explicit `vcs` id owned by the catalog wins. Provider implementations — command construction, spawning, error translation, and exact-source reading — live entirely under -`src/extensions/default/vcs//`. `src/extensions/vcsPatchResult.ts` is +`packages/hunk/src/extensions/default/vcs//`. `packages/hunk/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/process/projectRoot.ts` treats `.hunk` as a provider-independent +results. `packages/hunk/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. @@ -358,7 +358,7 @@ ordering before core or UI consumes it. ## Public contract rules The authoring surface is the `hunkdiff/extension` export — a façade over -internal types, declared in `src/extension-api/types.ts`. That module must +internal types, declared in `packages/hunk/src/extension-api/types.ts`. That module must stay import-free: declaration emission ships every module the entry reaches, so an import there publishes Hunk internals (`scripts/check-pack.ts` fails the pack when it does, and typechecks every `docs/extensions.md` example as diff --git a/docs/extension-system-exploration.md b/docs/extension-system-exploration.md index baa994f74..f5f7d9019 100644 --- a/docs/extension-system-exploration.md +++ b/docs/extension-system-exploration.md @@ -106,21 +106,21 @@ seams in very different states of readiness: **Nearly plugin-shaped already:** -- `vcsAdapters` (`src/core/vcs/index.ts`) — a real adapter pattern: detection, +- `vcsAdapters` (`packages/hunk/src/core/vcs/index.ts`) — a real adapter pattern: detection, per-operation handler maps, capability probing. Making the array appendable is almost the whole job for third-party VCS support. - The session broker (`packages/session-broker*`) is generic over session info/state/message types by design, and the app installs its command dispatcher at runtime via `createHunkSessionBridge(handlers)` + - `hostClient.setBridge(...)` (`src/app/session/bridge.ts`). That bridge is + `hostClient.setBridge(...)` (`packages/hunk/src/app/session/bridge.ts`). That bridge is the single clearest injection point for extension-provided session commands. - Dynamic theme registration exists (`ensureSyntaxHighlightThemeRegistered` → Pierre's `registerCustomTheme`), but the config layer caps custom themes at one `"custom"` slot. -- Pierre's `setCustomExtension` (`src/core/changeset/fileLanguage.ts`) is already used at +- Pierre's `setCustomExtension` (`packages/hunk/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 +- `StartupDeps` (`packages/hunk/src/app/startup.ts`) fully injects the startup pipeline (currently only tests use it), and unknown TOML keys are silently ignored, so an `[extensions]` config section is backward-compatible on day one. @@ -174,7 +174,7 @@ export default function (hunk: HunkExtensionAPI) { ```` - Discovery: `~/.config/hunk/extensions/*.ts` and `*/index.ts` (global, follows - our existing XDG path logic in `src/core/run/paths.ts`), `.hunk/extensions/` + our existing XDG path logic in `packages/hunk/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/extensions.md b/docs/extensions.md index 60af74dc1..4bc4875f1 100644 --- a/docs/extensions.md +++ b/docs/extensions.md @@ -193,7 +193,7 @@ run without installing anything. Every VCS backend Hunk ships — **Git, Jujutsu, and Sapling** — is an extension, and so is the **built-in file-navigation pane**. They live in -`src/extensions/default/`, are compiled into the binary, and register through +`packages/hunk/src/extensions/default/`, are compiled into the binary, and register through the same `hunk.registerVcsAdapter` and `hunk.registerPane` this guide documents. There is no private registration path. @@ -412,7 +412,7 @@ removes the patch on extension shutdown. Run it from this checkout with: ```bash -bun run src/main.tsx --extension ./examples/extensions/github-pr gh 123 +bun run packages/hunk/src/main.tsx --extension ./examples/extensions/github-pr gh 123 ``` ### `hunk.configureSession(options)` @@ -1053,7 +1053,7 @@ whatever version Hunk pins — a wider surface than `hunkdiff/extension` itself. The built-in files pane uses the same calls, so changes that break this contract break Hunk first. Keep scroll handling small and behind your own helpers. -Its implementation lives in `src/extensions/default/ui/sidebar/` and serves as +Its implementation lives in `packages/hunk/src/extensions/default/ui/sidebar/` and serves as the reference for third-party panes. #### Pane state from events diff --git a/docs/module-boundaries.md b/docs/module-boundaries.md index 68c4c9547..95df201bc 100644 --- a/docs/module-boundaries.md +++ b/docs/module-boundaries.md @@ -3,7 +3,7 @@ Defines the target import boundaries between Hunk's top-level source trees and records what the dependency graph actually looks like today. The boundaries are enforced by [dependency-cruiser](https://github.com/sverweij/dependency-cruiser) over the production import -graph (`src/` plus `packages/`, tests excluded): +graph (`packages/hunk/src/` plus `packages/`, tests excluded): - `bun run deps:check` — fails CI on any boundary violation not in the baseline. - `bun run deps:baseline` — regenerates `.dependency-cruiser-known-violations.json` after fixing @@ -20,28 +20,28 @@ tier-level complement, with real module resolution instead of regex import scann Tiers, bottom to top. A tier may import anything strictly below it and nothing above it: ```text -src/extension-api published contract; imports nothing -src/lib dependency-free helpers; may import extension-api only -src/core domain model (changesets, review, vcs catalog, config) +packages/hunk/src/extension-api published contract; imports nothing +packages/hunk/src/lib dependency-free helpers; may import extension-api only +packages/hunk/src/core domain model (changesets, review, vcs catalog, config) packages/* standalone publishable units (session broker, term-video); - never import src/; the per-app broker contract is in + never import packages/hunk/src/; the per-app broker contract is in docs/session-broker-sdk.md -src/extensions extension host + bundled extensions; consume core, never surfaces -src/session daemon/broker transport + protocol; consumes core and packages -src/app startup composition: CLI parsing plus the wiring of core, +packages/hunk/src/extensions extension host + bundled extensions; consume core, never surfaces +packages/hunk/src/session daemon/broker transport + protocol; consumes core and packages +packages/hunk/src/app startup composition: CLI parsing plus the wiring of core, extensions, and the session broker; no rendering -src/ui terminal surface; only the composition shell (App, AppHost, +packages/hunk/src/ui terminal surface; only the composition shell (App, AppHost, runInteractiveApp), the named session adapter hooks (useTerminalReview, useHunkSessionBridge), and their shared navigation helper (ui/lib/reviewState) may import app/session -src/opentui published facade re-exporting ui/core pieces for `hunkdiff/opentui` -src/main.tsx CLI entry +packages/hunk/src/opentui published facade re-exporting ui/core pieces for `hunkdiff/opentui` +packages/hunk/src/main.tsx CLI entry ``` Intentional exceptions, allowed by the rules: -- `src/opentui` imports `src/ui` internals: it is a packaging facade whose job is re-export. -- `src/hunk-review` imports `src/session/agent`: the skill document is generated from the agent +- `packages/hunk/src/opentui` imports `packages/hunk/src/ui` internals: it is a packaging facade whose job is re-export. +- `packages/hunk/src/hunk-review` imports `packages/hunk/src/session/agent`: the skill document is generated from the agent surface by design. - Tests are excluded: they are colocated and free to reach across boundaries. @@ -54,7 +54,7 @@ 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 +- **`no-dead-modules`** flags any module under `packages/hunk/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 @@ -69,7 +69,7 @@ Phase 0 (2026-08-17) established the mechanism: it deleted `core/review/address. 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 +pattern across `packages/hunk/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 @@ -133,7 +133,7 @@ model: `terminal`, `jobControl`, `shutdown`, and `updateNotice` serve the intera `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` +After this phase `packages/hunk/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 @@ -156,7 +156,7 @@ module that owns their behaviour, one home each: 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 +`AgentAnnotation`, which is declared in `packages/hunk/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 @@ -171,9 +171,9 @@ changeset and command-input models they now name. `core/bootstrap.ts` imports do 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. +`packages/hunk/src/app` retires the exception. -`src/core/` root now holds `bootstrap.ts`, `reviewDigest.ts`, and `liveComments.ts` beside the +`packages/hunk/src/core/` root now holds `bootstrap.ts`, `reviewDigest.ts`, and `liveComments.ts` beside the eight module directories. Phase 5 (2026-08-18) grouped **how this binary was installed and how it gets replaced** into @@ -206,29 +206,29 @@ rules: moved beside the client class it aliases; `CopySelectedRowRange` moved into `ui/lib/diffSpatial.ts`; `extensions/notifications.ts` now imports `ExtensionNotifyType` from its declaring module. -- **`src/core/cli.ts` → `src/app/cli.ts`.** CLI parsing that registers every tier's command +- **`packages/hunk/src/core/cli.ts` → `packages/hunk/src/app/cli.ts`.** CLI parsing that registers every tier's command surface (including `hunk session *` from `session/agent/surface.ts`) is composition, not domain — moving it made the core→session edges legal app→session edges. -- **`src/session/app/` → `src/app/session/`.** The mounted-review registration, bridge, and +- **`packages/hunk/src/session/app/` → `packages/hunk/src/app/session/`.** The mounted-review registration, bridge, and reload-authorization modules compose the app process with the session broker, and nothing - inside `src/session` imported them — they were app-tier code homed on the wrong side. + inside `packages/hunk/src/session` imported them — they were app-tier code homed on the wrong side. Moving the directory removed every session→app edge at once. -- **`src/lib/reviewDigest.ts` → `src/core/reviewDigest.ts`.** The Node digest implementation +- **`packages/hunk/src/lib/reviewDigest.ts` → `packages/hunk/src/core/reviewDigest.ts`.** The Node digest implementation is review-semantic and platform-bound; core root (Node-full, outside the platform-free `core/review/` seam) is its tier. - **`ui/lib/reviewState.ts`** resolves session-daemon navigation for the adapter hooks and is now a named entry in the adapter allowlist rather than an accidental reach-in. -- **The bundled sidebar's `src/ui` imports are documented design, not debt.** Its module +- **The bundled sidebar's `packages/hunk/src/ui` imports are documented design, not debt.** Its module header defines the dogfooding boundary as the published props contract (data, actions, theme); rendering helpers are host code. The rules now encode exactly that: - `src/extensions/default/ui/` may consume `src/ui`, and still may never touch - `src/app`/`src/session`. + `packages/hunk/src/extensions/default/ui/` may consume `packages/hunk/src/ui`, and still may never touch + `packages/hunk/src/app`/`packages/hunk/src/session`. ## After the baseline: next targets The tier rules now hold with no exceptions. Two follow-ups are worth doing next: -1. **Give `src/core` an interior.** _Done (phases 0–4, see Module interiors)._ Every group is a +1. **Give `packages/hunk/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 @@ -236,7 +236,7 @@ The tier rules now hold with no exceptions. Two follow-ups are worth doing next: `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 + `loadAppBootstrap` out of `core/changeset/loaders.ts` into `packages/hunk/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 diff --git a/docs/opentui-component.md b/docs/opentui-component.md index 9ea0c52f7..5cc67b5ae 100644 --- a/docs/opentui-component.md +++ b/docs/opentui-component.md @@ -231,4 +231,4 @@ If you need direct access to Pierre's parser, `parsePatchFiles(...)` is still re - Runnable demo overview: [`examples/README.md`](../examples/README.md) - Component demos: [`examples/7-opentui-component/README.md`](../examples/7-opentui-component/README.md) -The in-repo demos import from `../../src/opentui` so they run from source. Published consumers should import from `hunkdiff/opentui`. +The in-repo demos import from `../../packages/hunk/src/opentui` so they run from source. Published consumers should import from `hunkdiff/opentui`. diff --git a/docs/session-broker-sdk.md b/docs/session-broker-sdk.md index 14d273013..443a57423 100644 --- a/docs/session-broker-sdk.md +++ b/docs/session-broker-sdk.md @@ -40,7 +40,7 @@ all associated UI semantics. Publish one package, `@hunk/session-broker`, with protocol/state, daemon/connection, managed-host, and Node/Bun adapter boundaries kept as internal modules. Its only first-release entry point exports the shared primitives, broker APIs, supervision, managed producer API, and an automatically selected -`serveSessionBrokerDaemon`. Hunk composes the package; the package never imports `src/*`. +`serveSessionBrokerDaemon`. Hunk composes the package; the package never imports `packages/hunk/src/*`. Publication requires verified npm `@hunk` scope ownership and trusted publishing. An approved replacement name must not alter `appId`, wire identity, or runtime namespaces. diff --git a/docs/source-architecture.md b/docs/source-architecture.md index 20cbc37ea..2c85c9627 100644 --- a/docs/source-architecture.md +++ b/docs/source-architecture.md @@ -7,33 +7,33 @@ Use it when adding a new module or deciding where an existing responsibility bel ## Ownership ```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/ review model, patch handling, VCS contracts, configuration, and +packages/hunk/src/app/ executable composition: CLI parsing, startup plans, and shared session bootstrap +packages/hunk/src/app/session/ mounted-review registration, bridge, and reload authorization +packages/hunk/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, +packages/hunk/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 +packages/hunk/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, +packages/hunk/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 -src/extensions/ extension host, registry, trust, lifecycle, and bundled extensions -src/session/ shared session protocol, schemas, types, agent surface, and broker transport -src/session/client/ shared session-daemon HTTP and compatibility client support -src/session/agent/ agent-facing session CLI, command manifest, errors, and formatting -src/session/broker/ local daemon transport, launcher, Hunk broker state, wire parsing, projections -src/ui/ interactive review application, rendering, interaction, and chrome -src/extension-api/ public `hunkdiff/extension` declaration and runtime boundary -src/opentui/ public `hunkdiff/opentui` component boundary -src/lib/ small product-wide utilities with no feature ownership +packages/hunk/src/core/theme/ bundled theme metadata, custom-theme rules, and terminal theme detection +packages/hunk/src/core/watch/ input signatures, observation plans/backends, and refresh coordination +packages/hunk/src/core/vcs/ provider-neutral VCS catalog, contracts, operation dispatch, and host support +packages/hunk/src/extensions/ extension host, registry, trust, lifecycle, and bundled extensions +packages/hunk/src/session/ shared session protocol, schemas, types, agent surface, and broker transport +packages/hunk/src/session/client/ shared session-daemon HTTP and compatibility client support +packages/hunk/src/session/agent/ agent-facing session CLI, command manifest, errors, and formatting +packages/hunk/src/session/broker/ local daemon transport, launcher, Hunk broker state, wire parsing, projections +packages/hunk/src/ui/ interactive review application, rendering, interaction, and chrome +packages/hunk/src/extension-api/ public `hunkdiff/extension` declaration and runtime boundary +packages/hunk/src/opentui/ public `hunkdiff/opentui` component boundary +packages/hunk/src/lib/ small product-wide utilities with no feature ownership ``` -`src/app/` is intentionally small: it composes subsystems but does not become a second -application framework. `src/core/` remains the shared product layer, not a synonym for +`packages/hunk/src/app/` is intentionally small: it composes subsystems but does not become a second +application framework. `packages/hunk/src/core/` remains the shared product layer, not a synonym for "anything outside React". Put a module in a more specific existing subdirectory whenever one owns its behaviour. @@ -42,10 +42,10 @@ one owns its behaviour. - `app` may compose `core`, `extensions`, `session`, and `ui`. - `ui` may consume core models and the extension/session contracts; it owns terminal rendering. - `extensions` may consume provider-neutral core models and contracts, but bundled VCS provider - implementations must depend only on `hunkdiff/extension`, local modules, and `src/lib` utilities. + implementations must depend only on `hunkdiff/extension`, local modules, and `packages/hunk/src/lib` utilities. Renderer access remains limited to `extensions/default/ui/`, the bundled-sidebar boundary. - `core` must not import `ui` or `extensions`. Shared data needed by both belongs in core-owned - structural contracts or `src/lib`, never in a reverse dependency. + structural contracts or `packages/hunk/src/lib`, never in a reverse dependency. - `extension-api/types.ts` stays import-free. It is a published declaration boundary, enforced by the package checks. - `opentui` and `extension-api` are public entrypoint directories, not general internal buckets. diff --git a/docs/watch-benchmark-final.md b/docs/watch-benchmark-final.md index 629a2a9b9..23ff86b12 100644 --- a/docs/watch-benchmark-final.md +++ b/docs/watch-benchmark-final.md @@ -259,7 +259,7 @@ Fourteen commits implement the production change: - `c152100`, `0659a30`, `d36c33f`, `fd00dd6` add resource fallback, Git ignored-root pruning, native recursive selection, and a bounded readiness deadline - `f6165bd`, `4f35a0e` stabilize cross-platform and sidecar watch coverage -Production implementation/seams are in `src/core/watch/signature.ts`, `plan.ts`, `controller.ts`, `observer.ts`, `src/core/vcs/*`, loader/type plumbing, `src/ui/hooks/useWatchedInput.ts`, and the App/AppHost integration. Matching unit, filesystem, AppHost, helper, and PTY coverage lives beside those files and in `test/helpers/watchTest.ts` and `test/pty/watch.test.ts`. CI and PR CI add a compiled-binary watch PTY check. README documents continuous observation with fallback. +Production implementation/seams are in `packages/hunk/src/core/watch/signature.ts`, `plan.ts`, `controller.ts`, `observer.ts`, `packages/hunk/src/core/vcs/*`, loader/type plumbing, `packages/hunk/src/ui/hooks/useWatchedInput.ts`, and the App/AppHost integration. Matching unit, filesystem, AppHost, helper, and PTY coverage lives beside those files and in `test/helpers/watchTest.ts` and `test/pty/watch.test.ts`. CI and PR CI add a compiled-binary watch PTY check. README documents continuous observation with fallback. The only new runtime dependency is `chokidar@^4.0.3` (and transitive `readdirp@4.1.2`), recorded in `package.json`, `bun.lock`, and `nix/bun.lock.nix`. The two user-visible patch changesets are `.changeset/calm-files-watch.md` and `.changeset/native-recursive-watch.md`. Existing benchmark fixture imports received small type/data updates, but no benchmark runner is a production runtime dependency. @@ -275,7 +275,7 @@ Complete harness file inventory: - **Terminal, ConPTY, sampling, and Git instrumentation:** `terminal.ts`, `sampler.ts`, `git-log.ts`, `observer-probe.ts`, `artifacts.ts`, with `terminal.test.ts`, `sampler.test.ts`, `git-log.test.ts`, and `observer-probe.test.ts`. - **Freeze, provenance, transfer, build, and preflight:** `freeze.ts`, `stage.ts`, `build-host.ts`, `host-identity.ts`, `host-preflight.ts`, `prepare-preflight.ts`, with `stage.test.ts`, `build-host.test.ts`, `host-identity.test.ts`, `host-preflight.test.ts`, and shared `test-helpers.ts`. - **Package/build integration:** seven `bench:watch*` scripts in `package.json`; dev-only `@xterm/headless@5.5.0` and `ghostty-opentui@1.4.10` plus `bun.lock`; `scripts/build-bin.ts` reuses `process.execPath` to prevent ambient-Bun builds. -- **Benchmark-only production seams:** `src/core/watch/observer.ts` and `.test.ts` accept an injected `auto|native|chokidar` backend for forced probes; production remains `auto`. +- **Benchmark-only production seams:** `packages/hunk/src/core/watch/observer.ts` and `.test.ts` accept an injected `auto|native|chokidar` backend for forced probes; production remains `auto`. - **Workflow:** `.github/workflows/benchmarks.yml` contains the Windows adapter. - **Maintenance changesets:** `.changeset/watch-benchmark-harness.md` and `.changeset/witty-schools-sit.md` are empty/non-release changesets. diff --git a/examples/7-opentui-component/README.md b/examples/7-opentui-component/README.md index 2ab36d2ec..48a87ba89 100644 --- a/examples/7-opentui-component/README.md +++ b/examples/7-opentui-component/README.md @@ -19,4 +19,4 @@ bun run examples/7-opentui-component/from-patch.tsx - switching between split and stacked layouts with example shell controls - a scrollable terminal diff component that other OpenTUI apps can reuse -The in-repo demos import from `../../src/opentui` so they run from source. Published consumers should import from `hunkdiff/opentui` instead. +The in-repo demos import from `../../packages/hunk/src/opentui` so they run from source. Published consumers should import from `hunkdiff/opentui` instead. diff --git a/examples/7-opentui-component/from-files.tsx b/examples/7-opentui-component/from-files.tsx index 98e2fb63b..d6321097f 100644 --- a/examples/7-opentui-component/from-files.tsx +++ b/examples/7-opentui-component/from-files.tsx @@ -1,6 +1,6 @@ #!/usr/bin/env bun -import { parseDiffFromFile } from "../../src/opentui"; +import { parseDiffFromFile } from "../../packages/hunk/src/opentui"; import { readExampleFile, runExample } from "./support"; const path = "src/reviewSummary.ts"; diff --git a/examples/7-opentui-component/from-patch.tsx b/examples/7-opentui-component/from-patch.tsx index 56445c9bf..9fab70ef7 100644 --- a/examples/7-opentui-component/from-patch.tsx +++ b/examples/7-opentui-component/from-patch.tsx @@ -1,6 +1,6 @@ #!/usr/bin/env bun -import { parsePatchFiles } from "../../src/opentui"; +import { parsePatchFiles } from "../../packages/hunk/src/opentui"; import { readExampleFile, runExample } from "./support"; const patch = readExampleFile("change.patch"); diff --git a/examples/7-opentui-component/support.tsx b/examples/7-opentui-component/support.tsx index c01946dab..c1fe2c5e7 100644 --- a/examples/7-opentui-component/support.tsx +++ b/examples/7-opentui-component/support.tsx @@ -3,9 +3,9 @@ import path from "node:path"; import { createCliRenderer } from "@opentui/core"; import { createRoot, useTerminalDimensions } from "@opentui/react"; import { useState } from "react"; -import type { HunkDiffFile, HunkDiffLayout } from "../../src/opentui"; -import { HunkDiffView } from "../../src/opentui"; -import { fitText } from "../../src/ui/lib/text"; +import type { HunkDiffFile, HunkDiffLayout } from "../../packages/hunk/src/opentui"; +import { HunkDiffView } from "../../packages/hunk/src/opentui"; +import { fitText } from "../../packages/hunk/src/ui/lib/text"; interface ExampleProps { title: string; diff --git a/examples/8-opentui-primitives/README.md b/examples/8-opentui-primitives/README.md index 61374dae3..5a443929c 100644 --- a/examples/8-opentui-primitives/README.md +++ b/examples/8-opentui-primitives/README.md @@ -19,4 +19,4 @@ bun run examples/8-opentui-primitives/primitives-demo.tsx - Host-owned window borders/chrome around each primitive so you can inspect component boundaries - Host-owned state for selected file and split/stack layout -The in-repo demo imports from `../../src/opentui` so it runs from source. Published consumers should import from `hunkdiff/opentui` instead. +The in-repo demo imports from `../../packages/hunk/src/opentui` so it runs from source. Published consumers should import from `hunkdiff/opentui` instead. diff --git a/examples/8-opentui-primitives/primitives-demo.tsx b/examples/8-opentui-primitives/primitives-demo.tsx index 6098d4c32..2f193825d 100644 --- a/examples/8-opentui-primitives/primitives-demo.tsx +++ b/examples/8-opentui-primitives/primitives-demo.tsx @@ -10,8 +10,8 @@ import { HunkReviewStream, createHunkDiffFilesFromPatch, type HunkDiffLayout, -} from "../../src/opentui"; -import { fitText, padText } from "../../src/ui/lib/text"; +} from "../../packages/hunk/src/opentui"; +import { fitText, padText } from "../../packages/hunk/src/ui/lib/text"; const PATCH = `diff --git a/src/search.ts b/src/search.ts --- a/src/search.ts diff --git a/examples/extensions/cli-tools/README.md b/examples/extensions/cli-tools/README.md index af29dc753..8dab9cd7b 100644 --- a/examples/extensions/cli-tools/README.md +++ b/examples/extensions/cli-tools/README.md @@ -5,8 +5,8 @@ Demonstrates a generic top-level command tree. The handler owns every token belo Run it directly from this checkout: ```bash -bun run src/main.tsx --extension ./examples/extensions/cli-tools cli-tools status -bun run src/main.tsx --extension ./examples/extensions/cli-tools cli-tools review +bun run packages/hunk/src/main.tsx --extension ./examples/extensions/cli-tools cli-tools status +bun run packages/hunk/src/main.tsx --extension ./examples/extensions/cli-tools cli-tools review ``` `status` writes to stdout and exits. `review` performs signal-aware asynchronous preprocessing, writes progress to stderr, then delegates to `hunk diff`. A delegating handler must not write stdout or read stdin because the built-in command or TUI takes ownership of both. diff --git a/examples/extensions/github-pr/README.md b/examples/extensions/github-pr/README.md index 94d477fe1..642e585f1 100644 --- a/examples/extensions/github-pr/README.md +++ b/examples/extensions/github-pr/README.md @@ -13,7 +13,7 @@ The extension fetches the PR metadata and diff directly from GitHub's API, write Place `--extension` before the extension-owned command: ```bash -bun run src/main.tsx --extension ./examples/extensions/github-pr gh 123 +bun run packages/hunk/src/main.tsx --extension ./examples/extensions/github-pr gh 123 ``` A bare number infers `owner/repo` from the current checkout's GitHub `origin`. Explicit forms work outside a checkout and do not invoke Git: diff --git a/examples/extensions/inline-edit/README.md b/examples/extensions/inline-edit/README.md index 83bf95cdb..cf286fb7c 100644 --- a/examples/extensions/inline-edit/README.md +++ b/examples/extensions/inline-edit/README.md @@ -16,7 +16,7 @@ It exists to demonstrate that Hunk's interactive extension surfaces compose, so ## Try it from this checkout ```bash -bun run src/main.tsx -- diff --extension ./examples/extensions/inline-edit +bun run packages/hunk/src/main.tsx -- diff --extension ./examples/extensions/inline-edit ``` ## Install it globally diff --git a/examples/extensions/jsx-file-view-gallery/README.md b/examples/extensions/jsx-file-view-gallery/README.md index 174298bae..eb097c91b 100644 --- a/examples/extensions/jsx-file-view-gallery/README.md +++ b/examples/extensions/jsx-file-view-gallery/README.md @@ -7,7 +7,7 @@ Three opt-in presentations exercise the constrained React/OpenTUI row contract a Nested boxes, responsive meters, semantic color, and selected-hunk styling summarize a multi-hunk TypeScript refactor. It uses no source parser and works from public hunk/change metadata alone. ```bash -bun run src/main.tsx -- diff \ +bun run packages/hunk/src/main.tsx -- diff \ --extension ./examples/extensions/jsx-file-view-gallery \ --mode stack \ examples/extensions/jsx-file-view-gallery/fixtures/change-atlas/before.ts \ @@ -19,7 +19,7 @@ bun run src/main.tsx -- diff \ The extension lazily reads both exact documents, associates changed opaque three- or six-digit hexadecimal custom properties with each real diff hunk, and paints old/new terminal color swatches inside deterministic two-row rectangles. ```bash -bun run src/main.tsx -- diff \ +bun run packages/hunk/src/main.tsx -- diff \ --extension ./examples/extensions/jsx-file-view-gallery \ --mode stack \ examples/extensions/jsx-file-view-gallery/fixtures/css-palette/before.css \ @@ -31,7 +31,7 @@ bun run src/main.tsx -- diff \ A conservative package-file parser highlights only the changed semantic-version segment: patch-only changes emphasize the patch number, minor upgrades emphasize the minor number, and major upgrades emphasize the full old/new strings. It retains positional bounds for every parsed hunk; invalid JSON or unavailable source falls back to raw diff. ```bash -bun run src/main.tsx -- diff \ +bun run packages/hunk/src/main.tsx -- diff \ --extension ./examples/extensions/jsx-file-view-gallery \ --mode stack \ examples/extensions/jsx-file-view-gallery/fixtures/package-dependencies/before/package.json \ diff --git a/examples/extensions/jsx-file-view-gallery/mixed-review/run.ts b/examples/extensions/jsx-file-view-gallery/mixed-review/run.ts index 86518dd1c..f8a048379 100644 --- a/examples/extensions/jsx-file-view-gallery/mixed-review/run.ts +++ b/examples/extensions/jsx-file-view-gallery/mixed-review/run.ts @@ -73,7 +73,14 @@ try { const result = spawnSync( process.execPath, - [join(repoRoot, "src/main.tsx"), "diff", "--extension", galleryRoot, "--mode", "stack"], + [ + join(repoRoot, "packages/hunk/src/main.tsx"), + "diff", + "--extension", + galleryRoot, + "--mode", + "stack", + ], { cwd: demoRepo, stdio: "inherit", env: process.env }, ); if (result.error) throw result.error; diff --git a/examples/extensions/jsx-file-view/README.md b/examples/extensions/jsx-file-view/README.md index b90032124..82ebbdee4 100644 --- a/examples/extensions/jsx-file-view/README.md +++ b/examples/extensions/jsx-file-view/README.md @@ -5,7 +5,7 @@ An opt-in proof of concept for fixed-height React/OpenTUI rows in alternate file Run it from this checkout against a multi-hunk working-tree change: ```bash -bun run src/main.tsx -- diff --extension ./examples/extensions/jsx-file-view +bun run packages/hunk/src/main.tsx -- diff --extension ./examples/extensions/jsx-file-view ``` Choose **Extensions → Toggle JSX hunk cards (POC)**. The row component uses a React state hook and OpenTUI `box`/`text` elements. Its registered F8 command/menu item is the supported keyboard path. A cooperatively delivered, un-dragged left-button mouse-up toggles local detail and stops propagation; wheel, drag, and unhandled input remain host-owned. The row is a non-focusable paint surface, with no portal, renderer, focus, or input-delivery guarantee. Each component is a closure over the hunk summary; Hunk passes it only bounded paint props, including a live semantic theme palette that does not participate in layout. The `spans` on every row are the host-rendered fallback, clipped to the same declared fixed height if the component fails. Hook state survives selected-hunk updates while mounted, but is intentionally lost when windowing unmounts the row or a new layout generation replaces it. diff --git a/examples/extensions/pane-layout/README.md b/examples/extensions/pane-layout/README.md index f11d28be3..d559bd38b 100644 --- a/examples/extensions/pane-layout/README.md +++ b/examples/extensions/pane-layout/README.md @@ -5,7 +5,7 @@ Registers a resizable right pane and fixed two-row top and bottom panes. Run it from this checkout: ```bash -bun run src/main.tsx -- diff --extension ./examples/extensions/pane-layout +bun run packages/hunk/src/main.tsx -- diff --extension ./examples/extensions/pane-layout ``` Press `ctrl+p` or use the **Extensions** menu. Drag the right divider to resize. diff --git a/examples/extensions/rendered-markdown/README.md b/examples/extensions/rendered-markdown/README.md index 8b6b9411d..53883bcb7 100644 --- a/examples/extensions/rendered-markdown/README.md +++ b/examples/extensions/rendered-markdown/README.md @@ -9,7 +9,7 @@ This example is **not bundled or loaded by Hunk**. Install it explicitly if you The repository's root install supplies the example's development dependency: ```bash -bun run src/main.tsx -- diff \ +bun run packages/hunk/src/main.tsx -- diff \ --extension ./examples/extensions/rendered-markdown \ before.md after.md ``` diff --git a/examples/extensions/review-note-navigator/README.md b/examples/extensions/review-note-navigator/README.md index 79e302cea..3e215145b 100644 --- a/examples/extensions/review-note-navigator/README.md +++ b/examples/extensions/review-note-navigator/README.md @@ -5,7 +5,7 @@ Lists every note currently saved in Hunk's shared ReviewStore, then navigates to Run it directly from this checkout: ```bash -bun run src/main.tsx -- diff --extension ./examples/extensions/review-note-navigator +bun run packages/hunk/src/main.tsx -- diff --extension ./examples/extensions/review-note-navigator ``` Save one or more review notes, then run **Extensions → Navigate saved review note…** (`F8`). Each choice includes its reconciliation status, file, preferred line, side, and summary. diff --git a/examples/extensions/review-snapshot-export/README.md b/examples/extensions/review-snapshot-export/README.md index f831b6d75..3b792d431 100644 --- a/examples/extensions/review-snapshot-export/README.md +++ b/examples/extensions/review-snapshot-export/README.md @@ -5,7 +5,7 @@ Exports Hunk's authoritative saved review state as JSON. The example shows why ` Run it directly from this checkout: ```bash -bun run src/main.tsx -- diff --extension ./examples/extensions/review-snapshot-export +bun run packages/hunk/src/main.tsx -- diff --extension ./examples/extensions/review-snapshot-export ``` Add one or more review notes, then run **Extensions → Export review snapshot…** (`F9`) and choose a new output path. Relative paths resolve from the review's working directory; the example refuses to overwrite an existing file. diff --git a/examples/extensions/review-snapshot-export/index.test.ts b/examples/extensions/review-snapshot-export/index.test.ts index e0e481d62..bf73c685d 100644 --- a/examples/extensions/review-snapshot-export/index.test.ts +++ b/examples/extensions/review-snapshot-export/index.test.ts @@ -1,7 +1,7 @@ import { describe, expect, test } from "bun:test"; import { resolve } from "node:path"; import { resolveSnapshotExportPath, snapshotPositionMatches } from "./index"; -import type { ExtensionReviewSnapshot } from "../../../src/extension-api/types"; +import type { ExtensionReviewSnapshot } from "../../../packages/hunk/src/extension-api/types"; /** Build the minimal immutable snapshot position these helper tests compare. */ function createTestSnapshot( diff --git a/examples/extensions/review-triage/README.md b/examples/extensions/review-triage/README.md index 31d4f606b..55f4b1173 100644 --- a/examples/extensions/review-triage/README.md +++ b/examples/extensions/review-triage/README.md @@ -5,7 +5,7 @@ A session-local hunk review board for Hunk. It records which hunks you have visi Run it directly from this checkout: ```bash -bun run src/main.tsx -- diff --extension ./examples/extensions/review-triage +bun run packages/hunk/src/main.tsx -- diff --extension ./examples/extensions/review-triage ``` Or copy the directory to your Hunk extensions directory and keep its `package.json`; its manifest makes the folder a single `review-triage` extension. diff --git a/examples/extensions/vim-navigation/README.md b/examples/extensions/vim-navigation/README.md index 074c0bc4d..f67e46be3 100644 --- a/examples/extensions/vim-navigation/README.md +++ b/examples/extensions/vim-navigation/README.md @@ -7,7 +7,7 @@ This example is **not bundled or loaded by Hunk**. Install it explicitly if you ## Try it from this checkout ```bash -bun run src/main.tsx -- diff --extension ./examples/extensions/vim-navigation +bun run packages/hunk/src/main.tsx -- diff --extension ./examples/extensions/vim-navigation ``` Press `F6` or choose **Extensions → Toggle Vim navigation**. The persistent status badge shows when the mode owns review-level keys; click the badge, choose the host-owned exit menu item, or press `Esc` to leave. diff --git a/knip.json b/knip.json index ef1382aca..049c9ba80 100644 --- a/knip.json +++ b/knip.json @@ -3,29 +3,39 @@ "workspaces": { ".": { "entry": [ - "src/extension-api/index.ts", - "src/opentui/index.ts", - "src/**/*.test.{ts,tsx}", "scripts/**/*.test.ts", "test/**/*.test.{ts,tsx}", "test/cli/fixtures/compiled-opentui-positive-control.ts", + "test/cli/fixtures/compiled-highlight-worker-control.ts", + "test/cli/install-vm/validate-release-result.ts", + "test/session-broker-node/*.ts", + "test/session-broker-runtime/*.ts", "scripts/launch-video/*.{ts,mjs}", "scripts/probe-terminal-theme.ts", "scripts/test-large-untracked-render.tsx", "examples/extensions/**/index.{ts,tsx}", - "examples/extensions/**/run.ts" + "examples/extensions/**/run.ts", + "examples/**/*.test.ts" ], "project": [ - "src/**/*.{ts,tsx}", "scripts/**/*.{ts,tsx,mjs}", "test/**/*.{ts,tsx}", "benchmarks/**/*.ts", - "examples/extensions/**/*.{ts,tsx}", - "bin/**/*.cjs" + "examples/extensions/**/*.{ts,tsx}" ], "ignore": ["examples/extensions/**/fixtures/**"], "ignoreBinaries": ["nix"], - "ignoreDependencies": ["@changesets/changelog-github", "@shikijs/themes"], + "ignoreDependencies": ["@changesets/changelog-github", "@shikijs/themes", "playwright"], + "ignoreExportsUsedInFile": true + }, + "packages/hunk": { + "entry": [ + "src/highlightWorkerEntry.ts", + "src/extension-api/index.ts", + "src/opentui/index.ts", + "src/**/*.test.{ts,tsx}" + ], + "project": ["src/**/*.{ts,tsx}", "bin/**/*.cjs"], "ignoreExportsUsedInFile": true }, "packages/session-broker*": { @@ -39,7 +49,14 @@ } }, "ignoreIssues": { - "src/app/review/capability.ts": ["exports"], - "src/extension-api/types.ts": ["duplicates"] + "packages/hunk/src/app/review/capability.ts": ["exports"], + "packages/hunk/src/extension-api/types.ts": ["duplicates"], + "packages/hunk/src/core/install/latestRelease.ts": ["exports", "types"], + "packages/hunk/src/core/process/updateNotice.ts": ["exports", "types"], + "packages/hunk/src/extensions/types.ts": ["exports", "types"], + "packages/hunk/src/extensions/default/ui/sidebar/index.tsx": ["exports", "types"], + "packages/hunk/src/extensions/default/vcs/diffRange.ts": ["exports"], + "packages/hunk/src/session/broker/appContract.ts": ["exports", "types"], + "packages/hunk/src/ui/diff/worker/index.ts": ["exports", "types"] } } diff --git a/nix/bun.lock.nix b/nix/bun.lock.nix index 35fa53654..8db4df848 100644 --- a/nix/bun.lock.nix +++ b/nix/bun.lock.nix @@ -862,6 +862,7 @@ url = "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz"; hash = "sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ=="; }; + "hunkdiff" = copyPathToStore ../packages/hunk; "ignore@7.0.6" = fetchurl { url = "https://registry.npmjs.org/ignore/-/ignore-7.0.6.tgz"; hash = "sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw=="; diff --git a/nix/package.nix b/nix/package.nix index 13925e510..7ccaa846b 100644 --- a/nix/package.nix +++ b/nix/package.nix @@ -7,7 +7,7 @@ stdenv, ... }: let - packageJson = lib.importJSON ../package.json; + packageJson = lib.importJSON ../packages/hunk/package.json; bunVersion = lib.removePrefix "bun@" packageJson.packageManager; bunCompilerArchives = { "aarch64-darwin" = fetchurl { @@ -73,7 +73,7 @@ in BUN_INSTALL=$PWD/.bun-install \ "$bun_compiler" build --compile \ --no-compile-autoload-bunfig \ - "./src/main.tsx" \ + "./packages/hunk/src/main.tsx" \ --outfile "hunk-bin" runHook postBuild ''; @@ -82,7 +82,7 @@ in runHook preInstall mkdir -p $out/bin cp -p ./hunk-bin $out/bin/hunk - cp -r ./skills $out/ + cp -r ./packages/hunk/skills $out/ wrapProgram $out/bin/hunk --set HUNK_INSTALL_SOURCE nix runHook postInstall ''; diff --git a/package.json b/package.json index 1b761ece8..310a6ab93 100644 --- a/package.json +++ b/package.json @@ -1,58 +1,13 @@ { - "name": "hunkdiff", - "version": "0.21.1", - "description": "Desktop-inspired terminal diff viewer for understanding agent-authored changesets.", - "keywords": [ - "ai", - "code-review", - "diff", - "git", - "terminal", - "tui" - ], - "homepage": "https://hunk.dev", - "bugs": { - "url": "https://github.com/modem-dev/hunk/issues" - }, - "license": "MIT", - "repository": { - "type": "git", - "url": "git+https://github.com/modem-dev/hunk.git" - }, - "bin": { - "hunk": "./bin/hunk.cjs", - "hunkdiff": "./bin/hunk.cjs" - }, + "name": "@hunk/workspace", + "private": true, "workspaces": [ - ".", "packages/*" ], - "files": [ - "bin", - "dist/npm", - "skills/hunk-review", - "skills/hunk-extensions", - "README.md", - "LICENSE" - ], "type": "module", - "exports": { - "./extension": { - "types": "./dist/npm/extension/index.d.ts", - "import": "./dist/npm/extension/index.js" - }, - "./opentui": { - "types": "./dist/npm/opentui/index.d.ts", - "import": "./dist/npm/opentui/index.js" - }, - "./package.json": "./package.json" - }, - "publishConfig": { - "access": "public" - }, "scripts": { - "start": "bun run src/main.tsx", - "dev": "bun --watch src/main.tsx", + "start": "bun run packages/hunk/src/main.tsx", + "dev": "bun --watch packages/hunk/src/main.tsx", "build:npm": "bun run ./scripts/build-npm.ts", "build:bin": "bun run ./scripts/build-bin.ts", "build:prebuilt:npm": "bun run build:npm && bun run build:bin && bun run ./scripts/stage-prebuilt-npm.ts", @@ -77,14 +32,14 @@ "lint": "oxlint . --deny-warnings", "lint:fix": "oxlint . --fix", "knip": "knip", - "deps:check": "depcruise src packages --config .dependency-cruiser.cjs --ignore-known", - "deps:baseline": "depcruise src packages --config .dependency-cruiser.cjs --output-type baseline --output-to .dependency-cruiser-known-violations.json", + "deps:check": "depcruise packages --config .dependency-cruiser.cjs --ignore-known", + "deps:baseline": "depcruise packages --config .dependency-cruiser.cjs --output-type baseline --output-to .dependency-cruiser-known-violations.json", "changeset": "bunx @changesets/cli@2.31.0", "changeset:status": "bunx @changesets/cli@2.31.0 status", - "release:version": "bunx @changesets/cli@2.31.0 version", + "release:version": "bun run ./scripts/changeset-version.ts", "prepare": "simple-git-hooks", "test": "bun run ./scripts/run-test-suite.ts", - "test:theme-contrast": "bun test src/ui/themes.test.ts --test-name-pattern contrast", + "test:theme-contrast": "bun test packages/hunk/src/ui/themes.test.ts --test-name-pattern contrast", "test:integration": "\"${npm_execpath:-bun}\" test ./test/pty", "test:session-broker-node": "bun run ./scripts/test-session-broker-node.ts", "test:tty-smoke": "HUNK_RUN_TTY_SMOKE=1 \"${npm_execpath:-bun}\" test ./test/smoke", @@ -94,7 +49,6 @@ "check:prebuilt-pack": "bun run ./scripts/check-prebuilt-pack.ts", "smoke:prebuilt-install": "bun run ./scripts/smoke-prebuilt-install.ts", "publish:prebuilt:npm": "bun run ./scripts/publish-prebuilt-npm.ts", - "prepack": "bun run build:npm", "bench": "bun run benchmarks/run.ts", "bench:release": "bun run ./scripts/run-release-benchmark.ts", "bench:release:compare": "bun run ./scripts/compare-release-benchmarks.ts", @@ -121,16 +75,6 @@ "bench:competitors": "bun run benchmarks/competitors.ts", "nix:update-lock": "nix run .#update-bun-lock" }, - "dependencies": { - "bun": "^1.4.2", - "chokidar": "^4.0.3", - "commander": "^14.0.3", - "diff": "^8.0.3", - "get-east-asian-width": "^1.5.0", - "shell-quote": "1.9.0", - "string-width": "^8.2.1", - "zod": "~4.4.3" - }, "devDependencies": { "@changesets/changelog-github": "^0.7.0", "@hunk/session-broker": "workspace:*", @@ -144,7 +88,9 @@ "@types/bun": "1.4.1", "@types/react": "^19.2.14", "@types/ws": "^8.18.1", + "bun": "^1.4.2", "dependency-cruiser": "18.2.0", + "diff": "^8.0.3", "knip": "^6.32.0", "lint-staged": "^16.4.0", "marked": "17.0.1", @@ -152,20 +98,10 @@ "oxlint": "^1.56.0", "react": "^19.2.4", "simple-git-hooks": "^2.14.0", + "string-width": "^8.2.1", "tuistory": "^0.11.0", "typescript": "^5.9.3" }, - "peerDependencies": { - "@opentui/core": "^0.5.6", - "@opentui/react": "^0.5.6", - "@pierre/diffs": "1.3.5", - "react": "^19.2.4" - }, - "peerDependenciesMeta": { - "@pierre/diffs": { - "optional": true - } - }, "overrides": { "shell-quote": "1.9.0" }, @@ -178,7 +114,8 @@ "packageManager": "bun@1.4.2", "pi": { "skills": [ - "./skills" + "./skills", + "./packages/hunk/skills" ] } } diff --git a/packages/hunk/LICENSE b/packages/hunk/LICENSE new file mode 100644 index 000000000..21f202055 --- /dev/null +++ b/packages/hunk/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Modem Labs Inc. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/hunk/README.md b/packages/hunk/README.md new file mode 100644 index 000000000..c0275f47d --- /dev/null +++ b/packages/hunk/README.md @@ -0,0 +1,323 @@ +# hunk + +Hunk is a review-first terminal diff viewer for agent-authored changesets, built on [OpenTUI](https://github.com/anomalyco/opentui) and [Pierre diffs](https://www.npmjs.com/package/@pierre/diffs). + +**[hunk.dev](https://hunk.dev)** · [Documentation](https://hunk.dev/docs/) + +[![CI status](https://img.shields.io/github/actions/workflow/status/modem-dev/hunk/ci.yml?branch=main&style=for-the-badge&label=CI)](https://github.com/modem-dev/hunk/actions/workflows/ci.yml?branch=main) +[![Latest release](https://img.shields.io/github/v/release/modem-dev/hunk?style=for-the-badge)](https://github.com/modem-dev/hunk/releases) +[![MIT License](https://img.shields.io/badge/License-MIT-blue.svg?style=for-the-badge)](LICENSE) +[![Join the Discord community](https://img.shields.io/badge/Discord-Join%20community-5865F2?style=for-the-badge&logo=discord&logoColor=white)](https://discord.gg/WZFjaP6Gt8) + +- multi-file review stream with sidebar navigation +- inline AI and agent annotations beside the code +- split, stack, and responsive auto layouts +- watch mode for auto-reloading file and Git-backed reviews +- keyboard, mouse, pager, and Git difftool support + + + + + + +
+ image +
+ Split view with sidebar and inline AI notes +
+ image +
+ Stacked view and mouse-selectable menus +
+ +## Install + +The default installation method on macOS and Linux downloads a standalone binary and installs it into `~/.hunk`. It checks the archive against the release checksum when both `SHA256SUMS` and a supported checksum tool are available, and warns otherwise: + +```bash +curl -fsSL https://hunk.dev/install.sh | sh +``` + +Windows users can install with npm or mise. Other installation methods are also available: + +```bash +npm i -g hunkdiff # macOS, Linux, or Windows; requires Node.js 22+ +brew install hunk # macOS or Linux +mise use -g hunk # macOS, Linux, or Windows +``` + +> [!NOTE] +> If you previously installed hunk via `modem-dev/tap`, be sure to uninstall it first with `brew uninstall modem-dev/tap/hunk`. + +Windows requires mise 2026.8.6 or newer. Nix users can use the `default` package exported in `flake.nix`; see [nix/README.md](./nix/README.md) for details. Hunk also ships as a default tool in [Omarchy](https://omarchy.org), installed through mise. + +Requirements: + +- macOS, Linux, or Windows +- On x86-64, a CPU with SSE4.2 (Intel Nehalem 2008+, AMD Bulldozer 2011+); arm64 has no CPU feature floor +- Node.js 22+ for the npm install; the install script, Homebrew, mise, and Nix ship a standalone binary that does not require Node.js +- Git recommended for most workflows + +### Update Hunk + +Starting with Hunk 0.20, npm, Homebrew, and default install-script installs use Hunk’s canonical update command: + +```bash +hunk update # install the newest release +hunk update --check # check without installing +hunk update 0.20.0 # select an exact npm or default install-script release +``` + +On an older release, update once with the installer or package manager that installed Hunk, then use `hunk update` going forward. Custom `HUNK_INSTALL_DIR` installs must re-run the installer with the same directory; mise, Nix, and source installs use their owning tools instead. + +## Quick start + +```bash +hunk # show help +hunk --version # print the installed version +``` + +### Working with Git + +Hunk mirrors Git's diff-style commands, but opens the changeset in a review UI instead of plain text. + +```bash +hunk diff # review current repo changes, including untracked files +hunk --fast # experimentally offload eligible syntax highlighting +hunk diff --watch # auto-reload as the working tree changes +hunk show # review the latest commit +hunk show HEAD~1 # review an earlier commit +hunk log # browse history on a terminal; print when redirected +hunk log --static # force static output, paging when needed +``` + +`hunk log` is one auto-responsive, read-only history surface, not a repository manager. On a +terminal it opens the desktop history browser; pipes and redirects receive shell-native static +records automatically, and `--static` forces static output that pages only when needed. The selected VCS adapter +owns traversal, filtering, refs, and how a history item opens for review; the bundled Git and +Jujutsu adapters both implement that public capability. Static output keeps full commit, author, +date, message, branch/bookmark, remote, and tag details; `--oneline` provides compact records, and +`--theme` uses the same palette as Hunk review. Interactive rows adapt their information density to +the available width and keep commit ids right-aligned and clickable. After opening a commit, quit +its normal Hunk review to return to the same selection. + +### Working with Jujutsu and Sapling + +Hunk auto-detects Jujutsu and Sapling checkouts, so `hunk diff [revset]` and `hunk show [revset]` use native revsets inside jj or Sapling workspaces. `hunk log --vcs jj` also reads JJ history directly, including in a non-colocated workspace. To override VCS detection, set `vcs = "git"` or `vcs = "jj"` or `vcs = "sl"` in [config](#config). + +### Working with raw files and patches + +```bash +hunk diff --files before.ts after.ts # compare two files directly +hunk diff --files before.ts after.ts --watch # auto-reload when either file changes +git diff --no-color | hunk patch - # review a patch from stdin +``` + +Watch mode remains continuous. Direct-file and Git-backed reviews normally use filesystem observation to refresh promptly, with periodic polling retained as a fallback for missed events or unavailable watchers. Jujutsu and Sapling reviews currently use polling rather than filesystem observation. + +### Working with agents + +1. Open Hunk in another terminal with `hunk diff` or `hunk show`. +2. Tell your agent to add the skill file returned by `hunk skill path`. +3. Ask your agent to use the skill against the live Hunk session. + +A good generic prompt is: + +```text +Load the Hunk skill and use it for this review. Run `hunk skill path` to get the skill path. +``` + +For the full live-session and `--agent-context` workflow guide, see [docs/agent-workflows.md](docs/agent-workflows.md). Experimental rich STML note bodies require starting the review with `--experimental`; plain agent notes remain the default. + +## Feature comparison + +| Capability | [hunk](https://github.com/modem-dev/hunk) | [lumen](https://github.com/jnsahaj/lumen) | [difftastic](https://github.com/Wilfred/difftastic) | [delta](https://github.com/dandavison/delta) | [diff-so-fancy](https://github.com/so-fancy/diff-so-fancy) | [diff](https://www.gnu.org/software/diffutils/) | +| ---------------------------------- | ----------------------------------------- | ----------------------------------------- | --------------------------------------------------- | -------------------------------------------- | ---------------------------------------------------------- | ----------------------------------------------- | +| Review-first interactive UI | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | +| Multi-file review stream + sidebar | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | +| Inline agent / AI annotations | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | +| Responsive auto split/stack layout | ✅ | ❌ | ❌ | ❌ | ❌ | ❌ | +| Mouse support inside the viewer | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | +| Runtime view toggles | ✅ | ✅ | ❌ | ❌ | ❌ | ❌ | +| Syntax highlighting | ✅ | ✅ | ✅ | ✅ | ❌ | ❌ | +| Structural diffing | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | +| Pager-compatible mode | ✅ | ❌ | ✅ | ✅ | ✅ | ✅ | + +Hunk is optimized for reviewing a full changeset interactively. + +## Advanced + +### Config + +You can persist preferences to a config file: + +- `~/.config/hunk/config.toml` +- `.hunk/config.toml` + +Example: + +```toml +theme = "github-dark-default" # any built-in theme id, auto, or custom +mode = "auto" # auto, split, stack +vcs = "git" # git, jj, sl +watch = false +exclude_untracked = false +line_numbers = true +tab_width = 4 # tab stops, 1-16 +file_gap = 1 # rows between files, including the ─ rule; 0 hides it +hunk_gap = 0 # blank rows before later hunks +wrap_lines = false +menu_bar = true +sidebar = "auto" # "auto", true, false +agent_notes = false +prompt_save_view_preferences = true +transparent_background = false +``` + +Choose a built-in theme, `auto`, or a custom theme with `theme`. See +[docs/themes.md](docs/themes.md) for automatic selection, custom theme tables, +syntax scopes, and legacy syntax-table migration. + +`exclude_untracked` affects Git/Sapling working-tree `hunk diff` sessions only. +`tab_width` controls source-code tab stops and can be overridden with `-x4` or `--tab-width 4`. +`file_gap` is separator height between files, including the `─` rule; `hunk_gap` is blank rows before later hunks. +`prompt_save_view_preferences = false` disables the quit prompt for saving changed view preferences. +`transparent_background` can also be written as `transparentBackground`. + +### Keybindings + +Every keyboard shortcut is a named command, and a `[keybindings]` table in your +user config remaps command ids to the keys you want them on — several keys per +command, exclusive claims over defaults, and `false` to unbind. See +[docs/keybindings.md](docs/keybindings.md) for the rules, the chord grammar, +and the full table of built-in commands and their default keys. + +### Git integration + +Set Hunk as your Git pager so `git diff` and `git show` open in Hunk automatically: + +> [!NOTE] +> Untracked files are auto-included only for Hunk's own `hunk diff` working-tree loader. If you open `git diff` through `hunk pager`, Git still decides the patch contents, so untracked files will not appear there. + +```bash +git config --global core.pager "hunk pager" +``` + +Or in your Git config: + +```ini +[core] + pager = hunk pager +``` + +If you want to keep Git's default pager and add opt-in aliases instead: + +```bash +git config --global alias.hdiff "-c core.pager=\"hunk pager\" diff" +git config --global alias.hshow "-c core.pager=\"hunk pager\" show" +``` + +### Jujutsu pager integration + +To use Hunk as jj's pager, run `jj config edit --user` and update: + +```toml +[ui] +pager = ["hunk", "pager"] +diff-formatter = ":git" +``` + +### Sapling pager integration + +To use Hunk as Sapling's pager, run `sl config -u` and update: + +```ini +[pager] +pager = hunk pager +``` + +### Extensions (experimental) + +The extension API is experimental and may change in breaking ways between +minor releases while it stabilizes; breaking changes are called out in +release notes. + +Hunk loads plain TypeScript extensions from `~/.config/hunk/extensions/`, from a +repository's `.hunk/extensions/` (after you explicitly trust that repository), +and from `--extension ` for development. `--no-extensions` turns those off +for one run; Hunk's own bundled backends (Git, Jujutsu, and Sapling) stay loaded. + +An extension can add generic top-level CLI workflows, contribute themes and +file-extension → language mappings, add a VCS backend, rewrite the changeset +before review (collapse lockfiles, reorder files by review priority), replace +the file-navigation sidebar with its own React component, react to lifecycle +events, and show transient messages: + +```ts +// ~/.config/hunk/extensions/collapse-lockfiles.ts +import type { HunkExtensionAPI } from "hunkdiff/extension"; + +export default function (hunk: HunkExtensionAPI) { + hunk.transformChangeset((changeset, ctx) => { + const files = changeset.files.filter((file) => !file.path.endsWith(".lock")); + ctx.notify(`Collapsed ${changeset.files.length - files.length} lockfiles`); + return { ...changeset, files }; + }); +} +``` + +Extensions shared as git repositories install straight from their host, and a +`hunk-extension` GitHub topic marks community ones: + +```bash +hunk extension install acme/hunk-word-diff@v1.2.0 # or git:host/path, a URL, a local path +hunk extension list # then update [name] / remove +``` + +Browse community extensions at +[github.com/topics/hunk-extension](https://github.com/topics/hunk-extension); +publish yours by pushing the extension to a repository root and adding that +topic. + +See [docs/extensions.md](docs/extensions.md) for the full API, the trust model, +publishing guidance, and the `[extensions]` / `[extension.]` config reference. +Installable examples include a dependency-free +[`hunk gh 123` GitHub PR workflow](examples/extensions/github-pr/), +[review triage](examples/extensions/review-triage/), +[authoritative review snapshot export](examples/extensions/review-snapshot-export/), an optional +[rendered Markdown file view](examples/extensions/rendered-markdown/), and a +[Vim navigation mode](examples/extensions/vim-navigation/) built from public semantic commands. + +### OpenTUI component + +Hunk also publishes `HunkDiffView` and lower-level primitives from `hunkdiff/opentui` for embedding the same diff renderer in your own OpenTUI app. + +See [docs/opentui-component.md](docs/opentui-component.md) for install, API, and runnable examples. + +## Examples + +Ready-to-run demo diffs live in [`examples/`](examples/README.md). + +Each example includes the exact command to run from the repository root. + +## Contributing + +💬 _Chat with users/contributors on the [Modem Discord server](https://discord.gg/WZFjaP6Gt8)_ + +For source setup, tests, packaging checks, and repo architecture, see [CONTRIBUTING.md](CONTRIBUTING.md). + +## Sponsor + +Sponsored by [Modem](https://modem.dev?utm_source=github&utm_medium=oss&utm_campaign=oss_hunk&utm_content=readme_footer). + + + + + + Modem + + + +## License + +[MIT](LICENSE) diff --git a/bin/hunk.cjs b/packages/hunk/bin/hunk.cjs similarity index 100% rename from bin/hunk.cjs rename to packages/hunk/bin/hunk.cjs diff --git a/packages/hunk/package.json b/packages/hunk/package.json new file mode 100644 index 000000000..7260fe707 --- /dev/null +++ b/packages/hunk/package.json @@ -0,0 +1,92 @@ +{ + "name": "hunkdiff", + "version": "0.21.1", + "description": "Desktop-inspired terminal diff viewer for understanding agent-authored changesets.", + "keywords": [ + "ai", + "code-review", + "diff", + "git", + "terminal", + "tui" + ], + "homepage": "https://hunk.dev", + "bugs": { + "url": "https://github.com/modem-dev/hunk/issues" + }, + "license": "MIT", + "repository": { + "type": "git", + "url": "git+https://github.com/modem-dev/hunk.git" + }, + "bin": { + "hunk": "./bin/hunk.cjs", + "hunkdiff": "./bin/hunk.cjs" + }, + "files": [ + "bin", + "dist/npm", + "skills/hunk-review", + "skills/hunk-extensions", + "README.md", + "LICENSE" + ], + "type": "module", + "exports": { + "./extension": { + "types": "./dist/npm/extension/index.d.ts", + "import": "./dist/npm/extension/index.js" + }, + "./opentui": { + "types": "./dist/npm/opentui/index.d.ts", + "import": "./dist/npm/opentui/index.js" + }, + "./package.json": "./package.json" + }, + "publishConfig": { + "access": "public" + }, + "scripts": { + "prepack": "bun run ../../scripts/build-npm.ts" + }, + "dependencies": { + "bun": "^1.4.2", + "chokidar": "^4.0.3", + "commander": "^14.0.3", + "diff": "^8.0.3", + "get-east-asian-width": "^1.5.0", + "shell-quote": "1.9.0", + "string-width": "^8.2.1", + "zod": "~4.4.3" + }, + "devDependencies": { + "@opentui/core": "^0.5.6", + "@opentui/react": "^0.5.6", + "@pierre/diffs": "1.3.5", + "@shikijs/themes": "3.23.0", + "@types/bun": "1.4.1", + "@types/react": "^19.2.14", + "react": "^19.2.4", + "typescript": "^5.9.3" + }, + "peerDependencies": { + "@opentui/core": "^0.5.6", + "@opentui/react": "^0.5.6", + "@pierre/diffs": "1.3.5", + "react": "^19.2.4" + }, + "peerDependenciesMeta": { + "@pierre/diffs": { + "optional": true + } + }, + "engines": { + "node": ">=22" + }, + "packageManager": "bun@1.4.2", + "pi": { + "skills": [ + "./skills" + ] + } +} diff --git a/skills/hunk-extensions/SKILL.md b/packages/hunk/skills/hunk-extensions/SKILL.md similarity index 95% rename from skills/hunk-extensions/SKILL.md rename to packages/hunk/skills/hunk-extensions/SKILL.md index f4c8ae713..fd0f84784 100644 --- a/skills/hunk-extensions/SKILL.md +++ b/packages/hunk/skills/hunk-extensions/SKILL.md @@ -24,13 +24,13 @@ material before writing code. ## Sources of truth — read before writing -| Source | What it answers | -| --------------------------------------- | ------------------------------------------------------------ | -| `docs/extensions.md` | The authoring guide. Every call, every rule. Start here. | -| `src/extension-api/types.ts` | The contract — exact field names, optionality, doc comments. | -| `examples/extensions/*` | Working extensions. Copy these patterns rather than invent. | -| `docs/extension-architecture.md` | Hunk's internals. Needed only when changing the host. | -| `docs/keybindings.md`, `docs/themes.md` | Chord grammar and theme token rules that extensions inherit. | +| Source | What it answers | +| ------------------------------------------ | ------------------------------------------------------------ | +| `docs/extensions.md` | The authoring guide. Every call, every rule. Start here. | +| `packages/hunk/src/extension-api/types.ts` | The contract — exact field names, optionality, doc comments. | +| `examples/extensions/*` | Working extensions. Copy these patterns rather than invent. | +| `docs/extension-architecture.md` | Hunk's internals. Needed only when changing the host. | +| `docs/keybindings.md`, `docs/themes.md` | Chord grammar and theme token rules that extensions inherit. | Outside a Hunk checkout the guide is split across (discovery, trust, config) and its @@ -315,11 +315,11 @@ Practical checks, in order of cost: Only when the work is in the `hunk` repo rather than in a user extension: - Shipped VCS backends and the built-in files pane are **bundled extensions** in - `src/extensions/default/`, registering through the same public API. That + `packages/hunk/src/extensions/default/`, registering through the same public API. That dogfooding is deliberate — if the public contract cannot express something, that is a real gap, not a reason for a private path. `default/vcs/` loads from VCS adapter resolution and must stay renderer-free. -- `src/extension-api/types.ts` must stay **import-free**; declaration emission +- `packages/hunk/src/extension-api/types.ts` must stay **import-free**; declaration emission publishes whatever it reaches, and `scripts/check-pack.ts` fails the pack otherwise. Shapes shared with internal code are declared there and re-exported inward. diff --git a/skills/hunk-review/SKILL.md b/packages/hunk/skills/hunk-review/SKILL.md similarity index 100% rename from skills/hunk-review/SKILL.md rename to packages/hunk/skills/hunk-review/SKILL.md diff --git a/src/app/cli.test.ts b/packages/hunk/src/app/cli.test.ts similarity index 100% rename from src/app/cli.test.ts rename to packages/hunk/src/app/cli.test.ts diff --git a/src/app/cli.ts b/packages/hunk/src/app/cli.ts similarity index 100% rename from src/app/cli.ts rename to packages/hunk/src/app/cli.ts diff --git a/src/app/delegatedReview.test.ts b/packages/hunk/src/app/delegatedReview.test.ts similarity index 100% rename from src/app/delegatedReview.test.ts rename to packages/hunk/src/app/delegatedReview.test.ts diff --git a/src/app/delegatedReview.ts b/packages/hunk/src/app/delegatedReview.ts similarity index 100% rename from src/app/delegatedReview.ts rename to packages/hunk/src/app/delegatedReview.ts diff --git a/src/app/extensionBootstrap.test.ts b/packages/hunk/src/app/extensionBootstrap.test.ts similarity index 100% rename from src/app/extensionBootstrap.test.ts rename to packages/hunk/src/app/extensionBootstrap.test.ts diff --git a/src/app/extensionBootstrap.ts b/packages/hunk/src/app/extensionBootstrap.ts similarity index 100% rename from src/app/extensionBootstrap.ts rename to packages/hunk/src/app/extensionBootstrap.ts diff --git a/src/app/extensionCliBootstrap.test.ts b/packages/hunk/src/app/extensionCliBootstrap.test.ts similarity index 100% rename from src/app/extensionCliBootstrap.test.ts rename to packages/hunk/src/app/extensionCliBootstrap.test.ts diff --git a/src/app/extensionCliBootstrap.ts b/packages/hunk/src/app/extensionCliBootstrap.ts similarity index 100% rename from src/app/extensionCliBootstrap.ts rename to packages/hunk/src/app/extensionCliBootstrap.ts diff --git a/src/app/historyBootstrap.test.ts b/packages/hunk/src/app/historyBootstrap.test.ts similarity index 100% rename from src/app/historyBootstrap.test.ts rename to packages/hunk/src/app/historyBootstrap.test.ts diff --git a/src/app/historyBootstrap.ts b/packages/hunk/src/app/historyBootstrap.ts similarity index 100% rename from src/app/historyBootstrap.ts rename to packages/hunk/src/app/historyBootstrap.ts diff --git a/src/app/review/capability.ts b/packages/hunk/src/app/review/capability.ts similarity index 95% rename from src/app/review/capability.ts rename to packages/hunk/src/app/review/capability.ts index 901fcc7be..00047bb5a 100644 --- a/src/app/review/capability.ts +++ b/packages/hunk/src/app/review/capability.ts @@ -13,7 +13,7 @@ * that outlives its first minute needs. * * The grammar — token shape, fragment key, URL layout — is the shared browser-safe - * contract (`src/session/reviewHttpProtocol.ts`); this module only supplies the randomness + * contract (`packages/hunk/src/session/reviewHttpProtocol.ts`); this module only supplies the randomness * and the hashing that a platform has to provide. */ import { randomBytes } from "node:crypto"; diff --git a/src/app/review/producer.test.ts b/packages/hunk/src/app/review/producer.test.ts similarity index 99% rename from src/app/review/producer.test.ts rename to packages/hunk/src/app/review/producer.test.ts index aa05a8886..87c6329b2 100644 --- a/src/app/review/producer.test.ts +++ b/packages/hunk/src/app/review/producer.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test"; -import { createTestDiffFile, lines } from "../../../test/helpers/diff-helpers"; +import { createTestDiffFile, lines } from "../../../../../test/helpers/diff-helpers"; import { SourceTextTooLargeError } from "../../core/changeset/fileSource"; import { parseReviewGeneration } from "../../core/review/generationOrder"; import { diff --git a/src/app/review/producer.ts b/packages/hunk/src/app/review/producer.ts similarity index 100% rename from src/app/review/producer.ts rename to packages/hunk/src/app/review/producer.ts diff --git a/src/app/review/publication.ts b/packages/hunk/src/app/review/publication.ts similarity index 96% rename from src/app/review/publication.ts rename to packages/hunk/src/app/review/publication.ts index b9af070b7..5a8588d8c 100644 --- a/src/app/review/publication.ts +++ b/packages/hunk/src/app/review/publication.ts @@ -7,10 +7,10 @@ * mutating this one — which is what makes "which generation is this?" answerable rather * than a matter of timing. * - * The document itself knows nothing about any of this (`src/core/review/document.ts`); + * The document itself knows nothing about any of this (`packages/hunk/src/core/review/document.ts`); * publication is layered on top so the shared model stays a description of a review rather * than of a transport. The vocabulary for addressing and ordering those generations is - * `src/core/review/generationOrder.ts`. + * `packages/hunk/src/core/review/generationOrder.ts`. */ import { buildReviewContentManifest, diff --git a/src/app/review/resourceStore.ts b/packages/hunk/src/app/review/resourceStore.ts similarity index 100% rename from src/app/review/resourceStore.ts rename to packages/hunk/src/app/review/resourceStore.ts diff --git a/src/app/session/bridge.test.ts b/packages/hunk/src/app/session/bridge.test.ts similarity index 100% rename from src/app/session/bridge.test.ts rename to packages/hunk/src/app/session/bridge.test.ts diff --git a/src/app/session/bridge.ts b/packages/hunk/src/app/session/bridge.ts similarity index 100% rename from src/app/session/bridge.ts rename to packages/hunk/src/app/session/bridge.ts diff --git a/src/app/session/registration.test.ts b/packages/hunk/src/app/session/registration.test.ts similarity index 99% rename from src/app/session/registration.test.ts rename to packages/hunk/src/app/session/registration.test.ts index 6f3d41b3f..823c1b3c5 100644 --- a/src/app/session/registration.test.ts +++ b/packages/hunk/src/app/session/registration.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test"; -import { createTestDiffFile } from "../../../test/helpers/diff-helpers"; +import { createTestDiffFile } from "../../../../../test/helpers/diff-helpers"; import { reviewProcessCapability } from "../review/capability"; import { buildReviewPublication } from "../review/publication"; import type { AppBootstrap } from "../../core/bootstrap"; diff --git a/src/app/session/registration.ts b/packages/hunk/src/app/session/registration.ts similarity index 100% rename from src/app/session/registration.ts rename to packages/hunk/src/app/session/registration.ts diff --git a/src/app/session/reloadBounds.test.ts b/packages/hunk/src/app/session/reloadBounds.test.ts similarity index 100% rename from src/app/session/reloadBounds.test.ts rename to packages/hunk/src/app/session/reloadBounds.test.ts diff --git a/src/app/session/reloadBounds.ts b/packages/hunk/src/app/session/reloadBounds.ts similarity index 100% rename from src/app/session/reloadBounds.ts rename to packages/hunk/src/app/session/reloadBounds.ts diff --git a/src/app/session/reviewCommands.test.ts b/packages/hunk/src/app/session/reviewCommands.test.ts similarity index 99% rename from src/app/session/reviewCommands.test.ts rename to packages/hunk/src/app/session/reviewCommands.test.ts index e76d5afa9..4ec409263 100644 --- a/src/app/session/reviewCommands.test.ts +++ b/packages/hunk/src/app/session/reviewCommands.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test"; -import { createTestDiffFile } from "../../../test/helpers/diff-helpers"; +import { createTestDiffFile } from "../../../../../test/helpers/diff-helpers"; import { ReviewProducer } from "../review/producer"; import { reviewGapId } from "../../core/review/expansion"; import { reviewResourceId } from "../../core/review/resources"; diff --git a/src/app/session/reviewCommands.ts b/packages/hunk/src/app/session/reviewCommands.ts similarity index 97% rename from src/app/session/reviewCommands.ts rename to packages/hunk/src/app/session/reviewCommands.ts index a3e9107be..69c909f3c 100644 --- a/src/app/session/reviewCommands.ts +++ b/packages/hunk/src/app/session/reviewCommands.ts @@ -3,8 +3,8 @@ * * The daemon brokers two review commands to a live session: read one bounded slice of a * published resource, and apply one semantic action. Both are answered here, at the seam - * between the wire schema (`src/session/reviewProtocol.ts`) and the producer that owns - * the generation (`src/app/review/producer.ts`), so neither of those has to know about + * between the wire schema (`packages/hunk/src/session/reviewProtocol.ts`) and the producer that owns + * the generation (`packages/hunk/src/app/review/producer.ts`), so neither of those has to know about * the other's concerns. * * Three rules shape it: diff --git a/src/app/sessionBootstrap.test.ts b/packages/hunk/src/app/sessionBootstrap.test.ts similarity index 100% rename from src/app/sessionBootstrap.test.ts rename to packages/hunk/src/app/sessionBootstrap.test.ts diff --git a/src/app/sessionBootstrap.ts b/packages/hunk/src/app/sessionBootstrap.ts similarity index 100% rename from src/app/sessionBootstrap.ts rename to packages/hunk/src/app/sessionBootstrap.ts diff --git a/src/app/sessionSelector.test.ts b/packages/hunk/src/app/sessionSelector.test.ts similarity index 100% rename from src/app/sessionSelector.test.ts rename to packages/hunk/src/app/sessionSelector.test.ts diff --git a/src/app/sessionSelector.ts b/packages/hunk/src/app/sessionSelector.ts similarity index 100% rename from src/app/sessionSelector.ts rename to packages/hunk/src/app/sessionSelector.ts diff --git a/src/app/startup.test.ts b/packages/hunk/src/app/startup.test.ts similarity index 100% rename from src/app/startup.test.ts rename to packages/hunk/src/app/startup.test.ts diff --git a/src/app/startup.ts b/packages/hunk/src/app/startup.ts similarity index 100% rename from src/app/startup.ts rename to packages/hunk/src/app/startup.ts diff --git a/src/app/startup.vcsExtensions.test.ts b/packages/hunk/src/app/startup.vcsExtensions.test.ts similarity index 100% rename from src/app/startup.vcsExtensions.test.ts rename to packages/hunk/src/app/startup.vcsExtensions.test.ts diff --git a/src/app/types.ts b/packages/hunk/src/app/types.ts similarity index 100% rename from src/app/types.ts rename to packages/hunk/src/app/types.ts diff --git a/src/app/vcsCatalog.test.ts b/packages/hunk/src/app/vcsCatalog.test.ts similarity index 100% rename from src/app/vcsCatalog.test.ts rename to packages/hunk/src/app/vcsCatalog.test.ts diff --git a/src/app/vcsCatalog.ts b/packages/hunk/src/app/vcsCatalog.ts similarity index 100% rename from src/app/vcsCatalog.ts rename to packages/hunk/src/app/vcsCatalog.ts diff --git a/src/core/bootstrap.ts b/packages/hunk/src/core/bootstrap.ts similarity index 96% rename from src/core/bootstrap.ts rename to packages/hunk/src/core/bootstrap.ts index 4860fa1b8..80013de5d 100644 --- a/src/core/bootstrap.ts +++ b/packages/hunk/src/core/bootstrap.ts @@ -6,7 +6,7 @@ * 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 + * startup into the UI. The module directories under `packages/hunk/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. diff --git a/src/core/changeset/binary.test.ts b/packages/hunk/src/core/changeset/binary.test.ts similarity index 100% rename from src/core/changeset/binary.test.ts rename to packages/hunk/src/core/changeset/binary.test.ts diff --git a/src/core/changeset/binary.ts b/packages/hunk/src/core/changeset/binary.ts similarity index 100% rename from src/core/changeset/binary.ts rename to packages/hunk/src/core/changeset/binary.ts diff --git a/src/core/changeset/diffFile.test.ts b/packages/hunk/src/core/changeset/diffFile.test.ts similarity index 100% rename from src/core/changeset/diffFile.test.ts rename to packages/hunk/src/core/changeset/diffFile.test.ts diff --git a/src/core/changeset/diffFile.ts b/packages/hunk/src/core/changeset/diffFile.ts similarity index 100% rename from src/core/changeset/diffFile.ts rename to packages/hunk/src/core/changeset/diffFile.ts diff --git a/src/core/changeset/diffPaths.ts b/packages/hunk/src/core/changeset/diffPaths.ts similarity index 100% rename from src/core/changeset/diffPaths.ts rename to packages/hunk/src/core/changeset/diffPaths.ts diff --git a/src/core/changeset/fileLanguage.test.ts b/packages/hunk/src/core/changeset/fileLanguage.test.ts similarity index 100% rename from src/core/changeset/fileLanguage.test.ts rename to packages/hunk/src/core/changeset/fileLanguage.test.ts diff --git a/src/core/changeset/fileLanguage.ts b/packages/hunk/src/core/changeset/fileLanguage.ts similarity index 100% rename from src/core/changeset/fileLanguage.ts rename to packages/hunk/src/core/changeset/fileLanguage.ts diff --git a/src/core/changeset/fileLanguageLookup.ts b/packages/hunk/src/core/changeset/fileLanguageLookup.ts similarity index 100% rename from src/core/changeset/fileLanguageLookup.ts rename to packages/hunk/src/core/changeset/fileLanguageLookup.ts diff --git a/src/core/changeset/fileSource.test.ts b/packages/hunk/src/core/changeset/fileSource.test.ts similarity index 100% rename from src/core/changeset/fileSource.test.ts rename to packages/hunk/src/core/changeset/fileSource.test.ts diff --git a/src/core/changeset/fileSource.ts b/packages/hunk/src/core/changeset/fileSource.ts similarity index 100% rename from src/core/changeset/fileSource.ts rename to packages/hunk/src/core/changeset/fileSource.ts diff --git a/src/core/changeset/fromPatch.ts b/packages/hunk/src/core/changeset/fromPatch.ts similarity index 100% rename from src/core/changeset/fromPatch.ts rename to packages/hunk/src/core/changeset/fromPatch.ts diff --git a/src/core/changeset/hunkHeader.test.ts b/packages/hunk/src/core/changeset/hunkHeader.test.ts similarity index 100% rename from src/core/changeset/hunkHeader.test.ts rename to packages/hunk/src/core/changeset/hunkHeader.test.ts diff --git a/src/core/changeset/hunkHeader.ts b/packages/hunk/src/core/changeset/hunkHeader.ts similarity index 100% rename from src/core/changeset/hunkHeader.ts rename to packages/hunk/src/core/changeset/hunkHeader.ts diff --git a/src/core/changeset/hunkSummary.test.ts b/packages/hunk/src/core/changeset/hunkSummary.test.ts similarity index 95% rename from src/core/changeset/hunkSummary.test.ts rename to packages/hunk/src/core/changeset/hunkSummary.test.ts index 322985aea..7240d3743 100644 --- a/src/core/changeset/hunkSummary.test.ts +++ b/packages/hunk/src/core/changeset/hunkSummary.test.ts @@ -1,7 +1,7 @@ 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 { 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"; diff --git a/src/core/changeset/hunkSummary.ts b/packages/hunk/src/core/changeset/hunkSummary.ts similarity index 100% rename from src/core/changeset/hunkSummary.ts rename to packages/hunk/src/core/changeset/hunkSummary.ts diff --git a/src/core/changeset/loaders.ordering.test.ts b/packages/hunk/src/core/changeset/loaders.ordering.test.ts similarity index 96% rename from src/core/changeset/loaders.ordering.test.ts rename to packages/hunk/src/core/changeset/loaders.ordering.test.ts index 3143077b4..9651255d4 100644 --- a/src/core/changeset/loaders.ordering.test.ts +++ b/packages/hunk/src/core/changeset/loaders.ordering.test.ts @@ -1,7 +1,7 @@ import { describe, expect, test } from "bun:test"; import type { SidecarContext } from "./model"; import { orderDiffFiles } from "./loaders"; -import { createTestDiffFile } from "../../../test/helpers/diff-helpers"; +import { createTestDiffFile } from "../../../../../test/helpers/diff-helpers"; function sidecar(...paths: string[]): SidecarContext { return { diff --git a/src/core/changeset/loaders.test.ts b/packages/hunk/src/core/changeset/loaders.test.ts similarity index 100% rename from src/core/changeset/loaders.test.ts rename to packages/hunk/src/core/changeset/loaders.test.ts diff --git a/src/core/changeset/loaders.ts b/packages/hunk/src/core/changeset/loaders.ts similarity index 100% rename from src/core/changeset/loaders.ts rename to packages/hunk/src/core/changeset/loaders.ts diff --git a/src/core/changeset/model.ts b/packages/hunk/src/core/changeset/model.ts similarity index 100% rename from src/core/changeset/model.ts rename to packages/hunk/src/core/changeset/model.ts diff --git a/src/core/changeset/sidecar.test.ts b/packages/hunk/src/core/changeset/sidecar.test.ts similarity index 100% rename from src/core/changeset/sidecar.test.ts rename to packages/hunk/src/core/changeset/sidecar.test.ts diff --git a/src/core/changeset/sidecar.ts b/packages/hunk/src/core/changeset/sidecar.ts similarity index 98% rename from src/core/changeset/sidecar.ts rename to packages/hunk/src/core/changeset/sidecar.ts index 2540bd077..bb8c9ebb4 100644 --- a/src/core/changeset/sidecar.ts +++ b/packages/hunk/src/core/changeset/sidecar.ts @@ -4,7 +4,7 @@ * "Sidecar" names the file and its loader; "agent" names what the file carries. The notes * inside it stay `AgentAnnotation` / `AgentFileContext` because that is both the published * extension contract and the term the UI shows. Keeping the two apart leaves `agent` free to - * mean the coding-agent command surface in `src/session/agent/`. + * mean the coding-agent command surface in `packages/hunk/src/session/agent/`. */ import { resolve as resolvePath } from "node:path"; import type { AgentAnnotation, AgentFileContext } from "../../extension-api/types"; diff --git a/src/core/history/lanePlanner.test.ts b/packages/hunk/src/core/history/lanePlanner.test.ts similarity index 100% rename from src/core/history/lanePlanner.test.ts rename to packages/hunk/src/core/history/lanePlanner.test.ts diff --git a/src/core/history/lanePlanner.ts b/packages/hunk/src/core/history/lanePlanner.ts similarity index 100% rename from src/core/history/lanePlanner.ts rename to packages/hunk/src/core/history/lanePlanner.ts diff --git a/src/core/history/types.ts b/packages/hunk/src/core/history/types.ts similarity index 100% rename from src/core/history/types.ts rename to packages/hunk/src/core/history/types.ts diff --git a/src/core/install/installSource.test.ts b/packages/hunk/src/core/install/installSource.test.ts similarity index 100% rename from src/core/install/installSource.test.ts rename to packages/hunk/src/core/install/installSource.test.ts diff --git a/src/core/install/installSource.ts b/packages/hunk/src/core/install/installSource.ts similarity index 100% rename from src/core/install/installSource.ts rename to packages/hunk/src/core/install/installSource.ts diff --git a/src/core/install/latestRelease.test.ts b/packages/hunk/src/core/install/latestRelease.test.ts similarity index 100% rename from src/core/install/latestRelease.test.ts rename to packages/hunk/src/core/install/latestRelease.test.ts diff --git a/src/core/install/latestRelease.ts b/packages/hunk/src/core/install/latestRelease.ts similarity index 100% rename from src/core/install/latestRelease.ts rename to packages/hunk/src/core/install/latestRelease.ts diff --git a/src/core/install/selfUpdate.test.ts b/packages/hunk/src/core/install/selfUpdate.test.ts similarity index 100% rename from src/core/install/selfUpdate.test.ts rename to packages/hunk/src/core/install/selfUpdate.test.ts diff --git a/src/core/install/selfUpdate.ts b/packages/hunk/src/core/install/selfUpdate.ts similarity index 100% rename from src/core/install/selfUpdate.ts rename to packages/hunk/src/core/install/selfUpdate.ts diff --git a/src/core/liveComments.test.ts b/packages/hunk/src/core/liveComments.test.ts similarity index 98% rename from src/core/liveComments.test.ts rename to packages/hunk/src/core/liveComments.test.ts index c1a3a789b..3e1b1a129 100644 --- a/src/core/liveComments.test.ts +++ b/packages/hunk/src/core/liveComments.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test"; -import { createTestDiffFile, lines } from "../../test/helpers/diff-helpers"; +import { createTestDiffFile, lines } from "../../../../test/helpers/diff-helpers"; import { buildLiveComment, findDiffFileByPath, diff --git a/src/core/liveComments.ts b/packages/hunk/src/core/liveComments.ts similarity index 97% rename from src/core/liveComments.ts rename to packages/hunk/src/core/liveComments.ts index b4d431167..753994856 100644 --- a/src/core/liveComments.ts +++ b/packages/hunk/src/core/liveComments.ts @@ -21,7 +21,7 @@ export interface CommentTargetInput { line?: number; summary: string; rationale?: string; - /** Optional STML markup rendered as the note body (see src/ui/lib/stml). */ + /** Optional STML markup rendered as the note body (see packages/hunk/src/ui/lib/stml). */ markup?: string; author?: string; } diff --git a/src/core/patch/chunks.test.ts b/packages/hunk/src/core/patch/chunks.test.ts similarity index 100% rename from src/core/patch/chunks.test.ts rename to packages/hunk/src/core/patch/chunks.test.ts diff --git a/src/core/patch/chunks.ts b/packages/hunk/src/core/patch/chunks.ts similarity index 100% rename from src/core/patch/chunks.ts rename to packages/hunk/src/core/patch/chunks.ts diff --git a/src/core/patch/gitFormat.test.ts b/packages/hunk/src/core/patch/gitFormat.test.ts similarity index 100% rename from src/core/patch/gitFormat.test.ts rename to packages/hunk/src/core/patch/gitFormat.test.ts diff --git a/src/core/patch/gitFormat.ts b/packages/hunk/src/core/patch/gitFormat.ts similarity index 100% rename from src/core/patch/gitFormat.ts rename to packages/hunk/src/core/patch/gitFormat.ts diff --git a/src/core/patch/gitLog.test.ts b/packages/hunk/src/core/patch/gitLog.test.ts similarity index 100% rename from src/core/patch/gitLog.test.ts rename to packages/hunk/src/core/patch/gitLog.test.ts diff --git a/src/core/patch/gitLog.ts b/packages/hunk/src/core/patch/gitLog.ts similarity index 100% rename from src/core/patch/gitLog.ts rename to packages/hunk/src/core/patch/gitLog.ts diff --git a/src/core/patch/sanitize.test.ts b/packages/hunk/src/core/patch/sanitize.test.ts similarity index 100% rename from src/core/patch/sanitize.test.ts rename to packages/hunk/src/core/patch/sanitize.test.ts diff --git a/src/core/patch/sanitize.ts b/packages/hunk/src/core/patch/sanitize.ts similarity index 100% rename from src/core/patch/sanitize.ts rename to packages/hunk/src/core/patch/sanitize.ts diff --git a/src/core/patch/singleFile.ts b/packages/hunk/src/core/patch/singleFile.ts similarity index 100% rename from src/core/patch/singleFile.ts rename to packages/hunk/src/core/patch/singleFile.ts diff --git a/src/core/process/appStateFile.test.ts b/packages/hunk/src/core/process/appStateFile.test.ts similarity index 100% rename from src/core/process/appStateFile.test.ts rename to packages/hunk/src/core/process/appStateFile.test.ts diff --git a/src/core/process/appStateFile.ts b/packages/hunk/src/core/process/appStateFile.ts similarity index 100% rename from src/core/process/appStateFile.ts rename to packages/hunk/src/core/process/appStateFile.ts diff --git a/src/core/process/jobControl.test.ts b/packages/hunk/src/core/process/jobControl.test.ts similarity index 100% rename from src/core/process/jobControl.test.ts rename to packages/hunk/src/core/process/jobControl.test.ts diff --git a/src/core/process/jobControl.ts b/packages/hunk/src/core/process/jobControl.ts similarity index 100% rename from src/core/process/jobControl.ts rename to packages/hunk/src/core/process/jobControl.ts diff --git a/src/core/process/pager.test.ts b/packages/hunk/src/core/process/pager.test.ts similarity index 100% rename from src/core/process/pager.test.ts rename to packages/hunk/src/core/process/pager.test.ts diff --git a/src/core/process/pager.ts b/packages/hunk/src/core/process/pager.ts similarity index 100% rename from src/core/process/pager.ts rename to packages/hunk/src/core/process/pager.ts diff --git a/src/core/process/projectRoot.test.ts b/packages/hunk/src/core/process/projectRoot.test.ts similarity index 100% rename from src/core/process/projectRoot.test.ts rename to packages/hunk/src/core/process/projectRoot.test.ts diff --git a/src/core/process/projectRoot.ts b/packages/hunk/src/core/process/projectRoot.ts similarity index 100% rename from src/core/process/projectRoot.ts rename to packages/hunk/src/core/process/projectRoot.ts diff --git a/src/core/process/relaunch.test.ts b/packages/hunk/src/core/process/relaunch.test.ts similarity index 100% rename from src/core/process/relaunch.test.ts rename to packages/hunk/src/core/process/relaunch.test.ts diff --git a/src/core/process/relaunch.ts b/packages/hunk/src/core/process/relaunch.ts similarity index 100% rename from src/core/process/relaunch.ts rename to packages/hunk/src/core/process/relaunch.ts diff --git a/src/core/process/shutdown.test.ts b/packages/hunk/src/core/process/shutdown.test.ts similarity index 100% rename from src/core/process/shutdown.test.ts rename to packages/hunk/src/core/process/shutdown.test.ts diff --git a/src/core/process/shutdown.ts b/packages/hunk/src/core/process/shutdown.ts similarity index 100% rename from src/core/process/shutdown.ts rename to packages/hunk/src/core/process/shutdown.ts diff --git a/src/core/process/startupNotice.ts b/packages/hunk/src/core/process/startupNotice.ts similarity index 100% rename from src/core/process/startupNotice.ts rename to packages/hunk/src/core/process/startupNotice.ts diff --git a/src/core/process/stdout.test.ts b/packages/hunk/src/core/process/stdout.test.ts similarity index 100% rename from src/core/process/stdout.test.ts rename to packages/hunk/src/core/process/stdout.test.ts diff --git a/src/core/process/stdout.ts b/packages/hunk/src/core/process/stdout.ts similarity index 100% rename from src/core/process/stdout.ts rename to packages/hunk/src/core/process/stdout.ts diff --git a/src/core/process/terminal.test.ts b/packages/hunk/src/core/process/terminal.test.ts similarity index 100% rename from src/core/process/terminal.test.ts rename to packages/hunk/src/core/process/terminal.test.ts diff --git a/src/core/process/terminal.ts b/packages/hunk/src/core/process/terminal.ts similarity index 100% rename from src/core/process/terminal.ts rename to packages/hunk/src/core/process/terminal.ts diff --git a/src/core/process/updateNotice.test.ts b/packages/hunk/src/core/process/updateNotice.test.ts similarity index 100% rename from src/core/process/updateNotice.test.ts rename to packages/hunk/src/core/process/updateNotice.test.ts diff --git a/src/core/process/updateNotice.ts b/packages/hunk/src/core/process/updateNotice.ts similarity index 100% rename from src/core/process/updateNotice.ts rename to packages/hunk/src/core/process/updateNotice.ts diff --git a/src/core/review/actions.ts b/packages/hunk/src/core/review/actions.ts similarity index 100% rename from src/core/review/actions.ts rename to packages/hunk/src/core/review/actions.ts diff --git a/src/core/review/anchors.test.ts b/packages/hunk/src/core/review/anchors.test.ts similarity index 100% rename from src/core/review/anchors.test.ts rename to packages/hunk/src/core/review/anchors.test.ts diff --git a/src/core/review/anchors.ts b/packages/hunk/src/core/review/anchors.ts similarity index 100% rename from src/core/review/anchors.ts rename to packages/hunk/src/core/review/anchors.ts diff --git a/src/core/review/annotations.ts b/packages/hunk/src/core/review/annotations.ts similarity index 100% rename from src/core/review/annotations.ts rename to packages/hunk/src/core/review/annotations.ts diff --git a/src/core/review/canonicalFile.test.ts b/packages/hunk/src/core/review/canonicalFile.test.ts similarity index 97% rename from src/core/review/canonicalFile.test.ts rename to packages/hunk/src/core/review/canonicalFile.test.ts index d526b21d0..d40530db6 100644 --- a/src/core/review/canonicalFile.test.ts +++ b/packages/hunk/src/core/review/canonicalFile.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test"; -import { createTestReviewFile } from "../../../test/helpers/review-store-helpers"; +import { createTestReviewFile } from "../../../../../test/helpers/review-store-helpers"; import { buildReviewContentManifestFile } from "./contentManifest"; import { assertCanonicalFileMatchesManifest, diff --git a/src/core/review/canonicalFile.ts b/packages/hunk/src/core/review/canonicalFile.ts similarity index 100% rename from src/core/review/canonicalFile.ts rename to packages/hunk/src/core/review/canonicalFile.ts diff --git a/src/core/review/contentManifest.test.ts b/packages/hunk/src/core/review/contentManifest.test.ts similarity index 96% rename from src/core/review/contentManifest.test.ts rename to packages/hunk/src/core/review/contentManifest.test.ts index e6433367e..3a9291052 100644 --- a/src/core/review/contentManifest.test.ts +++ b/packages/hunk/src/core/review/contentManifest.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test"; -import { createTestDiffFile, lines } from "../../../test/helpers/diff-helpers"; +import { createTestDiffFile, lines } from "../../../../../test/helpers/diff-helpers"; import { buildReviewContentManifest } from "./contentManifest"; import { projectReviewDocument } from "./document"; diff --git a/src/core/review/contentManifest.ts b/packages/hunk/src/core/review/contentManifest.ts similarity index 100% rename from src/core/review/contentManifest.ts rename to packages/hunk/src/core/review/contentManifest.ts diff --git a/src/core/review/document.test.ts b/packages/hunk/src/core/review/document.test.ts similarity index 99% rename from src/core/review/document.test.ts rename to packages/hunk/src/core/review/document.test.ts index 003afd4d9..a78cab72b 100644 --- a/src/core/review/document.test.ts +++ b/packages/hunk/src/core/review/document.test.ts @@ -3,7 +3,7 @@ import { createTestDiffFile, createTestSourceFetcher, lines, -} from "../../../test/helpers/diff-helpers"; +} from "../../../../../test/helpers/diff-helpers"; import { projectReviewDocument, reviewEmptyDiffReason } from "./document"; import type { DiffFile } from "../changeset/model"; diff --git a/src/core/review/document.ts b/packages/hunk/src/core/review/document.ts similarity index 100% rename from src/core/review/document.ts rename to packages/hunk/src/core/review/document.ts diff --git a/src/core/review/expansion.test.ts b/packages/hunk/src/core/review/expansion.test.ts similarity index 99% rename from src/core/review/expansion.test.ts rename to packages/hunk/src/core/review/expansion.test.ts index 4f262bd9d..c238d8a9d 100644 --- a/src/core/review/expansion.test.ts +++ b/packages/hunk/src/core/review/expansion.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test"; -import { createTestReviewFile } from "../../../test/helpers/review-store-helpers"; +import { createTestReviewFile } from "../../../../../test/helpers/review-store-helpers"; import { parseReviewGapId, resolveReviewExpandedLine, diff --git a/src/core/review/expansion.ts b/packages/hunk/src/core/review/expansion.ts similarity index 100% rename from src/core/review/expansion.ts rename to packages/hunk/src/core/review/expansion.ts diff --git a/src/core/review/generationOrder.test.ts b/packages/hunk/src/core/review/generationOrder.test.ts similarity index 100% rename from src/core/review/generationOrder.test.ts rename to packages/hunk/src/core/review/generationOrder.test.ts diff --git a/src/core/review/generationOrder.ts b/packages/hunk/src/core/review/generationOrder.ts similarity index 100% rename from src/core/review/generationOrder.ts rename to packages/hunk/src/core/review/generationOrder.ts diff --git a/src/core/review/geometry.test.ts b/packages/hunk/src/core/review/geometry.test.ts similarity index 100% rename from src/core/review/geometry.test.ts rename to packages/hunk/src/core/review/geometry.test.ts diff --git a/src/core/review/geometry.ts b/packages/hunk/src/core/review/geometry.ts similarity index 100% rename from src/core/review/geometry.ts rename to packages/hunk/src/core/review/geometry.ts diff --git a/src/core/review/identity.test.ts b/packages/hunk/src/core/review/identity.test.ts similarity index 100% rename from src/core/review/identity.test.ts rename to packages/hunk/src/core/review/identity.test.ts diff --git a/src/core/review/identity.ts b/packages/hunk/src/core/review/identity.ts similarity index 100% rename from src/core/review/identity.ts rename to packages/hunk/src/core/review/identity.ts diff --git a/src/core/review/intents.test.ts b/packages/hunk/src/core/review/intents.test.ts similarity index 99% rename from src/core/review/intents.test.ts rename to packages/hunk/src/core/review/intents.test.ts index 05737999c..3ff752b75 100644 --- a/src/core/review/intents.test.ts +++ b/packages/hunk/src/core/review/intents.test.ts @@ -3,7 +3,7 @@ import { createTestReviewDocument, createTestReviewState, createTestStoredNote, -} from "../../../test/helpers/review-store-helpers"; +} from "../../../../../test/helpers/review-store-helpers"; import { applyReviewIntent, isBlankReviewNoteBody, diff --git a/src/core/review/intents.ts b/packages/hunk/src/core/review/intents.ts similarity index 99% rename from src/core/review/intents.ts rename to packages/hunk/src/core/review/intents.ts index 1c9da0bb8..8203de7c5 100644 --- a/src/core/review/intents.ts +++ b/packages/hunk/src/core/review/intents.ts @@ -120,7 +120,7 @@ export type ReviewIntent = * The wire vocabulary is derived from this list instead of restated beside it * (`docs/browser-review-seam-audit.md`, B12). The assertion below makes the list total — * adding a member to `ReviewIntent` without naming it here fails to typecheck — and - * `src/session/reviewProtocol.ts` subtracts a named exclusion list from it rather than + * `packages/hunk/src/session/reviewProtocol.ts` subtracts a named exclusion list from it rather than * writing its own. */ export const REVIEW_INTENT_TYPES = [ diff --git a/src/core/review/navigation.test.ts b/packages/hunk/src/core/review/navigation.test.ts similarity index 100% rename from src/core/review/navigation.test.ts rename to packages/hunk/src/core/review/navigation.test.ts diff --git a/src/core/review/navigation.ts b/packages/hunk/src/core/review/navigation.ts similarity index 100% rename from src/core/review/navigation.ts rename to packages/hunk/src/core/review/navigation.ts diff --git a/src/core/review/noteSize.test.ts b/packages/hunk/src/core/review/noteSize.test.ts similarity index 100% rename from src/core/review/noteSize.test.ts rename to packages/hunk/src/core/review/noteSize.test.ts diff --git a/src/core/review/noteSize.ts b/packages/hunk/src/core/review/noteSize.ts similarity index 100% rename from src/core/review/noteSize.ts rename to packages/hunk/src/core/review/noteSize.ts diff --git a/src/core/review/reducer.test.ts b/packages/hunk/src/core/review/reducer.test.ts similarity index 99% rename from src/core/review/reducer.test.ts rename to packages/hunk/src/core/review/reducer.test.ts index 37439f151..15f2b9ce4 100644 --- a/src/core/review/reducer.test.ts +++ b/packages/hunk/src/core/review/reducer.test.ts @@ -3,7 +3,7 @@ import { createTestReviewDocument, createTestReviewState, createTestStoredNote, -} from "../../../test/helpers/review-store-helpers"; +} from "../../../../../test/helpers/review-store-helpers"; import { reduceReviewState } from "./reducer"; import type { ReviewState } from "./state"; diff --git a/src/core/review/reducer.ts b/packages/hunk/src/core/review/reducer.ts similarity index 100% rename from src/core/review/reducer.ts rename to packages/hunk/src/core/review/reducer.ts diff --git a/src/core/review/resourceAssembly.test.ts b/packages/hunk/src/core/review/resourceAssembly.test.ts similarity index 100% rename from src/core/review/resourceAssembly.test.ts rename to packages/hunk/src/core/review/resourceAssembly.test.ts diff --git a/src/core/review/resourceAssembly.ts b/packages/hunk/src/core/review/resourceAssembly.ts similarity index 100% rename from src/core/review/resourceAssembly.ts rename to packages/hunk/src/core/review/resourceAssembly.ts diff --git a/src/core/review/resources.test.ts b/packages/hunk/src/core/review/resources.test.ts similarity index 100% rename from src/core/review/resources.test.ts rename to packages/hunk/src/core/review/resources.test.ts diff --git a/src/core/review/resources.ts b/packages/hunk/src/core/review/resources.ts similarity index 100% rename from src/core/review/resources.ts rename to packages/hunk/src/core/review/resources.ts diff --git a/src/core/review/selectors.test.ts b/packages/hunk/src/core/review/selectors.test.ts similarity index 98% rename from src/core/review/selectors.test.ts rename to packages/hunk/src/core/review/selectors.test.ts index dd6abd164..cda5d45ef 100644 --- a/src/core/review/selectors.test.ts +++ b/packages/hunk/src/core/review/selectors.test.ts @@ -2,8 +2,8 @@ import { describe, expect, test } from "bun:test"; import { createTestReviewState, createTestStoredNote, -} from "../../../test/helpers/review-store-helpers"; -import { createTestReviewFile } from "../../../test/helpers/review-store-helpers"; +} from "../../../../../test/helpers/review-store-helpers"; +import { createTestReviewFile } from "../../../../../test/helpers/review-store-helpers"; import { reduceReviewState } from "./reducer"; import { isReviewGapExpanded, diff --git a/src/core/review/selectors.ts b/packages/hunk/src/core/review/selectors.ts similarity index 100% rename from src/core/review/selectors.ts rename to packages/hunk/src/core/review/selectors.ts diff --git a/src/core/review/state.test.ts b/packages/hunk/src/core/review/state.test.ts similarity index 96% rename from src/core/review/state.test.ts rename to packages/hunk/src/core/review/state.test.ts index c09f7247f..ed7f05112 100644 --- a/src/core/review/state.test.ts +++ b/packages/hunk/src/core/review/state.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test"; -import { createTestReviewDocument } from "../../../test/helpers/review-store-helpers"; +import { createTestReviewDocument } from "../../../../../test/helpers/review-store-helpers"; import { createInitialReviewState, isRenderableStoredReviewNote, diff --git a/src/core/review/state.ts b/packages/hunk/src/core/review/state.ts similarity index 100% rename from src/core/review/state.ts rename to packages/hunk/src/core/review/state.ts diff --git a/src/core/review/stml.test.ts b/packages/hunk/src/core/review/stml.test.ts similarity index 100% rename from src/core/review/stml.test.ts rename to packages/hunk/src/core/review/stml.test.ts diff --git a/src/core/review/stml.ts b/packages/hunk/src/core/review/stml.ts similarity index 100% rename from src/core/review/stml.ts rename to packages/hunk/src/core/review/stml.ts diff --git a/src/core/review/store.test.ts b/packages/hunk/src/core/review/store.test.ts similarity index 97% rename from src/core/review/store.test.ts rename to packages/hunk/src/core/review/store.test.ts index 19a70242d..0e518c708 100644 --- a/src/core/review/store.test.ts +++ b/packages/hunk/src/core/review/store.test.ts @@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test"; import { createTestReviewDocument, createTestStoredNote, -} from "../../../test/helpers/review-store-helpers"; +} from "../../../../../test/helpers/review-store-helpers"; import { createReviewStore } from "./store"; describe("createReviewStore", () => { diff --git a/src/core/review/store.ts b/packages/hunk/src/core/review/store.ts similarity index 100% rename from src/core/review/store.ts rename to packages/hunk/src/core/review/store.ts diff --git a/src/core/review/types.ts b/packages/hunk/src/core/review/types.ts similarity index 100% rename from src/core/review/types.ts rename to packages/hunk/src/core/review/types.ts diff --git a/src/core/review/validation.test.ts b/packages/hunk/src/core/review/validation.test.ts similarity index 100% rename from src/core/review/validation.test.ts rename to packages/hunk/src/core/review/validation.test.ts diff --git a/src/core/review/validation.ts b/packages/hunk/src/core/review/validation.ts similarity index 100% rename from src/core/review/validation.ts rename to packages/hunk/src/core/review/validation.ts diff --git a/src/core/reviewDescriptor.test.ts b/packages/hunk/src/core/reviewDescriptor.test.ts similarity index 100% rename from src/core/reviewDescriptor.test.ts rename to packages/hunk/src/core/reviewDescriptor.test.ts diff --git a/src/core/reviewDescriptor.ts b/packages/hunk/src/core/reviewDescriptor.ts similarity index 100% rename from src/core/reviewDescriptor.ts rename to packages/hunk/src/core/reviewDescriptor.ts diff --git a/src/core/reviewDigest.ts b/packages/hunk/src/core/reviewDigest.ts similarity index 100% rename from src/core/reviewDigest.ts rename to packages/hunk/src/core/reviewDigest.ts diff --git a/src/core/run/cliCommandNames.ts b/packages/hunk/src/core/run/cliCommandNames.ts similarity index 100% rename from src/core/run/cliCommandNames.ts rename to packages/hunk/src/core/run/cliCommandNames.ts diff --git a/src/core/run/commandCatalog.test.ts b/packages/hunk/src/core/run/commandCatalog.test.ts similarity index 99% rename from src/core/run/commandCatalog.test.ts rename to packages/hunk/src/core/run/commandCatalog.test.ts index 2b04000d4..a0be55309 100644 --- a/src/core/run/commandCatalog.test.ts +++ b/packages/hunk/src/core/run/commandCatalog.test.ts @@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test"; import { createTestReviewState, createTestStoredNote, -} from "../../../test/helpers/review-store-helpers"; +} from "../../../../../test/helpers/review-store-helpers"; import { APP_COMMAND_CATALOG, appCommandCatalogEntry, diff --git a/src/core/run/commandCatalog.ts b/packages/hunk/src/core/run/commandCatalog.ts similarity index 99% rename from src/core/run/commandCatalog.ts rename to packages/hunk/src/core/run/commandCatalog.ts index 473151191..acfd9cf5d 100644 --- a/src/core/run/commandCatalog.ts +++ b/packages/hunk/src/core/run/commandCatalog.ts @@ -21,7 +21,7 @@ * (audit F4). * * This module is renderer-neutral and dependency-light: no OpenTUI, no React, no Node - * builtins, chords as plain strings. It is not part of `src/core/review` because it + * builtins, chords as plain strings. It is not part of `packages/hunk/src/core/review` because it * describes UI vocabulary rather than review semantics, and that module stays purely about * what a review *is*. */ diff --git a/src/core/run/commandInputs.ts b/packages/hunk/src/core/run/commandInputs.ts similarity index 100% rename from src/core/run/commandInputs.ts rename to packages/hunk/src/core/run/commandInputs.ts diff --git a/src/core/run/config.test.ts b/packages/hunk/src/core/run/config.test.ts similarity index 100% rename from src/core/run/config.test.ts rename to packages/hunk/src/core/run/config.test.ts diff --git a/src/core/run/config.ts b/packages/hunk/src/core/run/config.ts similarity index 99% rename from src/core/run/config.ts rename to packages/hunk/src/core/run/config.ts index 68ebc02b2..4ed7f695f 100644 --- a/src/core/run/config.ts +++ b/packages/hunk/src/core/run/config.ts @@ -67,7 +67,7 @@ export interface ExtensionsConfig { * 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`. + * `packages/hunk/src/ui/lib/keymap.ts`. */ export type UserKeyBinding = string | readonly string[] | false; diff --git a/src/core/run/errors.test.ts b/packages/hunk/src/core/run/errors.test.ts similarity index 100% rename from src/core/run/errors.test.ts rename to packages/hunk/src/core/run/errors.test.ts diff --git a/src/core/run/errors.ts b/packages/hunk/src/core/run/errors.ts similarity index 100% rename from src/core/run/errors.ts rename to packages/hunk/src/core/run/errors.ts diff --git a/src/core/run/experimental.test.ts b/packages/hunk/src/core/run/experimental.test.ts similarity index 92% rename from src/core/run/experimental.test.ts rename to packages/hunk/src/core/run/experimental.test.ts index d16c51ac3..59a12b5e5 100644 --- a/src/core/run/experimental.test.ts +++ b/packages/hunk/src/core/run/experimental.test.ts @@ -1,5 +1,8 @@ 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/run/experimental.ts b/packages/hunk/src/core/run/experimental.ts similarity index 100% rename from src/core/run/experimental.ts rename to packages/hunk/src/core/run/experimental.ts diff --git a/src/core/run/inputReload.ts b/packages/hunk/src/core/run/inputReload.ts similarity index 100% rename from src/core/run/inputReload.ts rename to packages/hunk/src/core/run/inputReload.ts diff --git a/src/core/run/paths.test.ts b/packages/hunk/src/core/run/paths.test.ts similarity index 100% rename from src/core/run/paths.test.ts rename to packages/hunk/src/core/run/paths.test.ts diff --git a/src/core/run/paths.ts b/packages/hunk/src/core/run/paths.ts similarity index 96% rename from src/core/run/paths.ts rename to packages/hunk/src/core/run/paths.ts index 7ded9f11c..33f61f850 100644 --- a/src/core/run/paths.ts +++ b/packages/hunk/src/core/run/paths.ts @@ -5,8 +5,8 @@ import { basename, dirname, join, resolve } from "node:path"; * Skills Hunk ships, in the order `hunk skill path` lists them. * * A skill is bundled only if it is in `package.json`'s `files` allowlist and the - * prebuilt artifact staging; `skills/` also holds maintainer-only documents that - * never ship, and naming them here would resolve paths users cannot have. + * prebuilt artifact staging. The repository-root `skills/` directory separately holds + * maintainer-only documents that never ship, and naming them here would resolve paths users cannot have. */ export const BUNDLED_SKILL_NAMES = ["hunk-review", "hunk-extensions"] as const; export type BundledSkillName = (typeof BUNDLED_SKILL_NAMES)[number]; diff --git a/src/core/run/reviewGap.test.ts b/packages/hunk/src/core/run/reviewGap.test.ts similarity index 100% rename from src/core/run/reviewGap.test.ts rename to packages/hunk/src/core/run/reviewGap.test.ts diff --git a/src/core/run/reviewGap.ts b/packages/hunk/src/core/run/reviewGap.ts similarity index 100% rename from src/core/run/reviewGap.ts rename to packages/hunk/src/core/run/reviewGap.ts diff --git a/src/core/run/tabWidth.ts b/packages/hunk/src/core/run/tabWidth.ts similarity index 100% rename from src/core/run/tabWidth.ts rename to packages/hunk/src/core/run/tabWidth.ts diff --git a/src/core/run/version.ts b/packages/hunk/src/core/run/version.ts similarity index 100% rename from src/core/run/version.ts rename to packages/hunk/src/core/run/version.ts diff --git a/src/core/theme/catalog.test.ts b/packages/hunk/src/core/theme/catalog.test.ts similarity index 100% rename from src/core/theme/catalog.test.ts rename to packages/hunk/src/core/theme/catalog.test.ts diff --git a/src/core/theme/catalog.ts b/packages/hunk/src/core/theme/catalog.ts similarity index 100% rename from src/core/theme/catalog.ts rename to packages/hunk/src/core/theme/catalog.ts diff --git a/src/core/theme/customThemes.test.ts b/packages/hunk/src/core/theme/customThemes.test.ts similarity index 100% rename from src/core/theme/customThemes.test.ts rename to packages/hunk/src/core/theme/customThemes.test.ts diff --git a/src/core/theme/customThemes.ts b/packages/hunk/src/core/theme/customThemes.ts similarity index 100% rename from src/core/theme/customThemes.ts rename to packages/hunk/src/core/theme/customThemes.ts diff --git a/src/core/theme/detection.test.ts b/packages/hunk/src/core/theme/detection.test.ts similarity index 100% rename from src/core/theme/detection.test.ts rename to packages/hunk/src/core/theme/detection.test.ts diff --git a/src/core/theme/detection.ts b/packages/hunk/src/core/theme/detection.ts similarity index 100% rename from src/core/theme/detection.ts rename to packages/hunk/src/core/theme/detection.ts diff --git a/src/core/theme/legacySyntaxScopes.test.ts b/packages/hunk/src/core/theme/legacySyntaxScopes.test.ts similarity index 100% rename from src/core/theme/legacySyntaxScopes.test.ts rename to packages/hunk/src/core/theme/legacySyntaxScopes.test.ts diff --git a/src/core/theme/legacySyntaxScopes.ts b/packages/hunk/src/core/theme/legacySyntaxScopes.ts similarity index 100% rename from src/core/theme/legacySyntaxScopes.ts rename to packages/hunk/src/core/theme/legacySyntaxScopes.ts diff --git a/src/core/vcs/index.test.ts b/packages/hunk/src/core/vcs/index.test.ts similarity index 100% rename from src/core/vcs/index.test.ts rename to packages/hunk/src/core/vcs/index.test.ts diff --git a/src/core/vcs/index.ts b/packages/hunk/src/core/vcs/index.ts similarity index 100% rename from src/core/vcs/index.ts rename to packages/hunk/src/core/vcs/index.ts diff --git a/src/core/vcs/types.ts b/packages/hunk/src/core/vcs/types.ts similarity index 100% rename from src/core/vcs/types.ts rename to packages/hunk/src/core/vcs/types.ts diff --git a/src/core/vcs/untracked.test.ts b/packages/hunk/src/core/vcs/untracked.test.ts similarity index 100% rename from src/core/vcs/untracked.test.ts rename to packages/hunk/src/core/vcs/untracked.test.ts diff --git a/src/core/vcs/untracked.ts b/packages/hunk/src/core/vcs/untracked.ts similarity index 100% rename from src/core/vcs/untracked.ts rename to packages/hunk/src/core/vcs/untracked.ts diff --git a/src/core/watch/controller.test.ts b/packages/hunk/src/core/watch/controller.test.ts similarity index 100% rename from src/core/watch/controller.test.ts rename to packages/hunk/src/core/watch/controller.test.ts diff --git a/src/core/watch/controller.ts b/packages/hunk/src/core/watch/controller.ts similarity index 100% rename from src/core/watch/controller.ts rename to packages/hunk/src/core/watch/controller.ts diff --git a/src/core/watch/observer.fs.test.ts b/packages/hunk/src/core/watch/observer.fs.test.ts similarity index 100% rename from src/core/watch/observer.fs.test.ts rename to packages/hunk/src/core/watch/observer.fs.test.ts diff --git a/src/core/watch/observer.test.ts b/packages/hunk/src/core/watch/observer.test.ts similarity index 98% rename from src/core/watch/observer.test.ts rename to packages/hunk/src/core/watch/observer.test.ts index 8372b040f..f909062b4 100644 --- a/src/core/watch/observer.test.ts +++ b/packages/hunk/src/core/watch/observer.test.ts @@ -1,7 +1,7 @@ import { describe, expect, test } from "bun:test"; import { join } from "node:path"; -import { createWatchTestClock } from "../../../test/helpers/watchTest"; +import { createWatchTestClock } from "../../../../../test/helpers/watchTest"; import { createWatchController, WATCH_EVENT_SOURCE_STARTUP_TIMEOUT_CODE } from "./controller"; import { createNativeTreeWatcher, diff --git a/src/core/watch/observer.ts b/packages/hunk/src/core/watch/observer.ts similarity index 100% rename from src/core/watch/observer.ts rename to packages/hunk/src/core/watch/observer.ts diff --git a/src/core/watch/plan.test.ts b/packages/hunk/src/core/watch/plan.test.ts similarity index 100% rename from src/core/watch/plan.test.ts rename to packages/hunk/src/core/watch/plan.test.ts diff --git a/src/core/watch/plan.ts b/packages/hunk/src/core/watch/plan.ts similarity index 100% rename from src/core/watch/plan.ts rename to packages/hunk/src/core/watch/plan.ts diff --git a/src/core/watch/runtime.test.ts b/packages/hunk/src/core/watch/runtime.test.ts similarity index 100% rename from src/core/watch/runtime.test.ts rename to packages/hunk/src/core/watch/runtime.test.ts diff --git a/src/core/watch/runtime.ts b/packages/hunk/src/core/watch/runtime.ts similarity index 100% rename from src/core/watch/runtime.ts rename to packages/hunk/src/core/watch/runtime.ts diff --git a/src/core/watch/signature.test.ts b/packages/hunk/src/core/watch/signature.test.ts similarity index 100% rename from src/core/watch/signature.test.ts rename to packages/hunk/src/core/watch/signature.test.ts diff --git a/src/core/watch/signature.ts b/packages/hunk/src/core/watch/signature.ts similarity index 100% rename from src/core/watch/signature.ts rename to packages/hunk/src/core/watch/signature.ts diff --git a/src/extension-api/index.ts b/packages/hunk/src/extension-api/index.ts similarity index 100% rename from src/extension-api/index.ts rename to packages/hunk/src/extension-api/index.ts diff --git a/src/extension-api/keys.test.ts b/packages/hunk/src/extension-api/keys.test.ts similarity index 98% rename from src/extension-api/keys.test.ts rename to packages/hunk/src/extension-api/keys.test.ts index 0d8812c6a..c75b9d22a 100644 --- a/src/extension-api/keys.test.ts +++ b/packages/hunk/src/extension-api/keys.test.ts @@ -10,7 +10,7 @@ import { /** * The published chord grammar. * - * These cover the grammar itself; `src/lib/commandKeys.test.ts` covers the + * These cover the grammar itself; `packages/hunk/src/lib/commandKeys.test.ts` covers the * internal-only pieces built on top of it. */ diff --git a/src/extension-api/keys.ts b/packages/hunk/src/extension-api/keys.ts similarity index 100% rename from src/extension-api/keys.ts rename to packages/hunk/src/extension-api/keys.ts diff --git a/src/extension-api/types.ts b/packages/hunk/src/extension-api/types.ts similarity index 100% rename from src/extension-api/types.ts rename to packages/hunk/src/extension-api/types.ts diff --git a/src/extensions/apply.test.ts b/packages/hunk/src/extensions/apply.test.ts similarity index 99% rename from src/extensions/apply.test.ts rename to packages/hunk/src/extensions/apply.test.ts index bff56e08e..74178273a 100644 --- a/src/extensions/apply.test.ts +++ b/packages/hunk/src/extensions/apply.test.ts @@ -2,7 +2,7 @@ import { afterEach, describe, expect, test } from "bun:test"; import { existsSync, mkdirSync, mkdtempSync, rmSync } from "node:fs"; import { tmpdir } from "node:os"; import { dirname, join, resolve } from "node:path"; -import { createTestDiffFile } from "../../test/helpers/diff-helpers"; +import { createTestDiffFile } from "../../../../test/helpers/diff-helpers"; import { HUNK_CORE_VCS_DETECTION_PRIORITY, HUNK_DEFAULT_VCS_DETECTION_PRIORITY, diff --git a/src/extensions/apply.ts b/packages/hunk/src/extensions/apply.ts similarity index 100% rename from src/extensions/apply.ts rename to packages/hunk/src/extensions/apply.ts diff --git a/src/extensions/cliCommandRuntime.test.ts b/packages/hunk/src/extensions/cliCommandRuntime.test.ts similarity index 100% rename from src/extensions/cliCommandRuntime.test.ts rename to packages/hunk/src/extensions/cliCommandRuntime.test.ts diff --git a/src/extensions/cliCommandRuntime.ts b/packages/hunk/src/extensions/cliCommandRuntime.ts similarity index 100% rename from src/extensions/cliCommandRuntime.ts rename to packages/hunk/src/extensions/cliCommandRuntime.ts diff --git a/src/extensions/cliCommands.test.ts b/packages/hunk/src/extensions/cliCommands.test.ts similarity index 100% rename from src/extensions/cliCommands.test.ts rename to packages/hunk/src/extensions/cliCommands.test.ts diff --git a/src/extensions/cliCommands.ts b/packages/hunk/src/extensions/cliCommands.ts similarity index 100% rename from src/extensions/cliCommands.ts rename to packages/hunk/src/extensions/cliCommands.ts diff --git a/src/extensions/default/ui/index.test.ts b/packages/hunk/src/extensions/default/ui/index.test.ts similarity index 100% rename from src/extensions/default/ui/index.test.ts rename to packages/hunk/src/extensions/default/ui/index.test.ts diff --git a/src/extensions/default/ui/index.ts b/packages/hunk/src/extensions/default/ui/index.ts similarity index 100% rename from src/extensions/default/ui/index.ts rename to packages/hunk/src/extensions/default/ui/index.ts diff --git a/src/extensions/default/ui/reviewInfo/index.test.tsx b/packages/hunk/src/extensions/default/ui/reviewInfo/index.test.tsx similarity index 97% rename from src/extensions/default/ui/reviewInfo/index.test.tsx rename to packages/hunk/src/extensions/default/ui/reviewInfo/index.test.tsx index 7728fb99f..228793e8a 100644 --- a/src/extensions/default/ui/reviewInfo/index.test.tsx +++ b/packages/hunk/src/extensions/default/ui/reviewInfo/index.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 { capturedTestColorToHex } from "../../../../../test/helpers/test-color-helpers"; +import { capturedTestColorToHex } from "../../../../../../../test/helpers/test-color-helpers"; import type { ExtensionPaneProps } from "../../../../extension-api/types"; import { resolveTheme } from "../../../../ui/themes"; import { ReviewInfoPane } from "."; diff --git a/src/extensions/default/ui/reviewInfo/index.tsx b/packages/hunk/src/extensions/default/ui/reviewInfo/index.tsx similarity index 100% rename from src/extensions/default/ui/reviewInfo/index.tsx rename to packages/hunk/src/extensions/default/ui/reviewInfo/index.tsx diff --git a/src/extensions/default/ui/reviewInfo/presentation.test.ts b/packages/hunk/src/extensions/default/ui/reviewInfo/presentation.test.ts similarity index 100% rename from src/extensions/default/ui/reviewInfo/presentation.test.ts rename to packages/hunk/src/extensions/default/ui/reviewInfo/presentation.test.ts diff --git a/src/extensions/default/ui/reviewInfo/presentation.ts b/packages/hunk/src/extensions/default/ui/reviewInfo/presentation.ts similarity index 100% rename from src/extensions/default/ui/reviewInfo/presentation.ts rename to packages/hunk/src/extensions/default/ui/reviewInfo/presentation.ts diff --git a/src/extensions/default/ui/sidebar/FileSidebars.tsx b/packages/hunk/src/extensions/default/ui/sidebar/FileSidebars.tsx similarity index 100% rename from src/extensions/default/ui/sidebar/FileSidebars.tsx rename to packages/hunk/src/extensions/default/ui/sidebar/FileSidebars.tsx diff --git a/src/extensions/default/ui/sidebar/index.test.tsx b/packages/hunk/src/extensions/default/ui/sidebar/index.test.tsx similarity index 100% rename from src/extensions/default/ui/sidebar/index.test.tsx rename to packages/hunk/src/extensions/default/ui/sidebar/index.test.tsx diff --git a/src/extensions/default/ui/sidebar/index.tsx b/packages/hunk/src/extensions/default/ui/sidebar/index.tsx similarity index 100% rename from src/extensions/default/ui/sidebar/index.tsx rename to packages/hunk/src/extensions/default/ui/sidebar/index.tsx diff --git a/src/extensions/default/vcs/asyncProcess.test.ts b/packages/hunk/src/extensions/default/vcs/asyncProcess.test.ts similarity index 100% rename from src/extensions/default/vcs/asyncProcess.test.ts rename to packages/hunk/src/extensions/default/vcs/asyncProcess.test.ts diff --git a/src/extensions/default/vcs/asyncProcess.ts b/packages/hunk/src/extensions/default/vcs/asyncProcess.ts similarity index 100% rename from src/extensions/default/vcs/asyncProcess.ts rename to packages/hunk/src/extensions/default/vcs/asyncProcess.ts diff --git a/src/extensions/default/vcs/diffRange.ts b/packages/hunk/src/extensions/default/vcs/diffRange.ts similarity index 100% rename from src/extensions/default/vcs/diffRange.ts rename to packages/hunk/src/extensions/default/vcs/diffRange.ts diff --git a/src/extensions/default/vcs/git/commands.test.ts b/packages/hunk/src/extensions/default/vcs/git/commands.test.ts similarity index 100% rename from src/extensions/default/vcs/git/commands.test.ts rename to packages/hunk/src/extensions/default/vcs/git/commands.test.ts diff --git a/src/extensions/default/vcs/git/commands.ts b/packages/hunk/src/extensions/default/vcs/git/commands.ts similarity index 96% rename from src/extensions/default/vcs/git/commands.ts rename to packages/hunk/src/extensions/default/vcs/git/commands.ts index 82d8ee873..0b6080bfd 100644 --- a/src/extensions/default/vcs/git/commands.ts +++ b/packages/hunk/src/extensions/default/vcs/git/commands.ts @@ -15,7 +15,7 @@ import { runAbortableCommand } from "../asyncProcess"; * Every Git command Hunk runs, and the failures they translate into. * * This is the implementation layer behind the bundled Git backend - * (`src/extensions/default/vcs/git/`), so nothing here reaches into core, the + * (`packages/hunk/src/extensions/default/vcs/git/`), so nothing here reaches into core, the * diff engine, or the adapter registry — user-facing failures are raised as the * published `HunkExtensionUserError`, which is exactly what a third-party * backend would throw. @@ -553,26 +553,6 @@ export async function runGitTextAsync(options: RunGitTextOptions): Promise = {}, -) { - const result = runGitCommand({ - input, - args: ["config", "--get", key], - ...options, - acceptedExitCodes: [0, 1], - }); - - if (result.exitCode !== 0) { - return undefined; - } - - return result.stdout.trim() || undefined; -} - /** Normalize Git's diff.colorMoved config into the mode Hunk should request from Git. */ function normalizeGitColorMovedMode(value: string | undefined) { if (!value) { @@ -591,31 +571,6 @@ function normalizeGitColorMovedMode(value: string | undefined) { return value; } -/** Resolve whether Hunk should ask Git to color moved lines for this patch command. */ -export function resolveGitColorMovedOptions( - input: GitBackedInput, - options: Omit = {}, -): GitColorMovedOptions | null { - const gitMode = normalizeGitColorMovedMode( - readOptionalGitConfig(input, "diff.colorMoved", options), - ); - - if (gitMode === null) { - return null; - } - - const mode = gitMode ?? (input.options.colorMoved ? "zebra" : undefined); - if (!mode) { - return null; - } - - const whitespaceMode = readOptionalGitConfig(input, "diff.colorMovedWS", options); - return { - mode, - whitespaceMode, - }; -} - /** Resolve moved-line configuration without blocking an embedded renderer. */ export async function resolveGitColorMovedOptionsAsync( input: GitBackedInput, diff --git a/src/extensions/default/vcs/git/history.test.ts b/packages/hunk/src/extensions/default/vcs/git/history.test.ts similarity index 100% rename from src/extensions/default/vcs/git/history.test.ts rename to packages/hunk/src/extensions/default/vcs/git/history.test.ts diff --git a/src/extensions/default/vcs/git/history.ts b/packages/hunk/src/extensions/default/vcs/git/history.ts similarity index 100% rename from src/extensions/default/vcs/git/history.ts rename to packages/hunk/src/extensions/default/vcs/git/history.ts diff --git a/src/extensions/default/vcs/git/index.test.ts b/packages/hunk/src/extensions/default/vcs/git/index.test.ts similarity index 100% rename from src/extensions/default/vcs/git/index.test.ts rename to packages/hunk/src/extensions/default/vcs/git/index.test.ts diff --git a/src/extensions/default/vcs/git/index.ts b/packages/hunk/src/extensions/default/vcs/git/index.ts similarity index 99% rename from src/extensions/default/vcs/git/index.ts rename to packages/hunk/src/extensions/default/vcs/git/index.ts index a6489daa1..cf9e96e6e 100644 --- a/src/extensions/default/vcs/git/index.ts +++ b/packages/hunk/src/extensions/default/vcs/git/index.ts @@ -44,7 +44,7 @@ import { * file sources, skipped-too-large placeholders, untracked files, watch plans, * rich failures — so it is deliberately written the way a third-party backend * would be: it sees only the published `hunkdiff/extension` contract plus - * implementation helpers owned by this extension directory and generic `src/lib` + * implementation helpers owned by this extension directory and generic `packages/hunk/src/lib` * utilities. Nothing here reaches into core, the diff engine, or the * adapter registry. If something Git needs cannot be said in these types, the * published contract is missing it, and that is the point of shipping it this diff --git a/src/extensions/default/vcs/git/source.test.ts b/packages/hunk/src/extensions/default/vcs/git/source.test.ts similarity index 100% rename from src/extensions/default/vcs/git/source.test.ts rename to packages/hunk/src/extensions/default/vcs/git/source.test.ts diff --git a/src/extensions/default/vcs/git/source.ts b/packages/hunk/src/extensions/default/vcs/git/source.ts similarity index 100% rename from src/extensions/default/vcs/git/source.ts rename to packages/hunk/src/extensions/default/vcs/git/source.ts diff --git a/src/extensions/default/vcs/index.test.ts b/packages/hunk/src/extensions/default/vcs/index.test.ts similarity index 100% rename from src/extensions/default/vcs/index.test.ts rename to packages/hunk/src/extensions/default/vcs/index.test.ts diff --git a/src/extensions/default/vcs/index.ts b/packages/hunk/src/extensions/default/vcs/index.ts similarity index 100% rename from src/extensions/default/vcs/index.ts rename to packages/hunk/src/extensions/default/vcs/index.ts diff --git a/src/extensions/default/vcs/jujutsu/commands.test.ts b/packages/hunk/src/extensions/default/vcs/jujutsu/commands.test.ts similarity index 100% rename from src/extensions/default/vcs/jujutsu/commands.test.ts rename to packages/hunk/src/extensions/default/vcs/jujutsu/commands.test.ts diff --git a/src/extensions/default/vcs/jujutsu/commands.ts b/packages/hunk/src/extensions/default/vcs/jujutsu/commands.ts similarity index 98% rename from src/extensions/default/vcs/jujutsu/commands.ts rename to packages/hunk/src/extensions/default/vcs/jujutsu/commands.ts index daa3989c6..917d6c816 100644 --- a/src/extensions/default/vcs/jujutsu/commands.ts +++ b/packages/hunk/src/extensions/default/vcs/jujutsu/commands.ts @@ -414,18 +414,6 @@ export async function resolveJjRangeEndpointsAsync( return { newCommitId: toCommitIds[0]!, oldCommitIds: [fromCommitIds[0]!] }; } -export function resolveJjRepoRoot( - input: JjBackedInput, - options: Omit = {}, -) { - const repoRoot = runJjText({ - input, - args: ["root"], - ...options, - }).trim(); - return normalizePathForOS(repoRoot); -} - /** Resolve the JJ repository root without blocking renderer input. */ export async function resolveJjRepoRootAsync( input: JjBackedInput, diff --git a/src/extensions/default/vcs/jujutsu/history.test.ts b/packages/hunk/src/extensions/default/vcs/jujutsu/history.test.ts similarity index 100% rename from src/extensions/default/vcs/jujutsu/history.test.ts rename to packages/hunk/src/extensions/default/vcs/jujutsu/history.test.ts diff --git a/src/extensions/default/vcs/jujutsu/history.ts b/packages/hunk/src/extensions/default/vcs/jujutsu/history.ts similarity index 100% rename from src/extensions/default/vcs/jujutsu/history.ts rename to packages/hunk/src/extensions/default/vcs/jujutsu/history.ts diff --git a/src/extensions/default/vcs/jujutsu/index.test.ts b/packages/hunk/src/extensions/default/vcs/jujutsu/index.test.ts similarity index 100% rename from src/extensions/default/vcs/jujutsu/index.test.ts rename to packages/hunk/src/extensions/default/vcs/jujutsu/index.test.ts diff --git a/src/extensions/default/vcs/jujutsu/index.ts b/packages/hunk/src/extensions/default/vcs/jujutsu/index.ts similarity index 100% rename from src/extensions/default/vcs/jujutsu/index.ts rename to packages/hunk/src/extensions/default/vcs/jujutsu/index.ts diff --git a/src/extensions/default/vcs/jujutsu/source.test.ts b/packages/hunk/src/extensions/default/vcs/jujutsu/source.test.ts similarity index 100% rename from src/extensions/default/vcs/jujutsu/source.test.ts rename to packages/hunk/src/extensions/default/vcs/jujutsu/source.test.ts diff --git a/src/extensions/default/vcs/jujutsu/source.ts b/packages/hunk/src/extensions/default/vcs/jujutsu/source.ts similarity index 100% rename from src/extensions/default/vcs/jujutsu/source.ts rename to packages/hunk/src/extensions/default/vcs/jujutsu/source.ts diff --git a/src/extensions/default/vcs/sapling/commands.test.ts b/packages/hunk/src/extensions/default/vcs/sapling/commands.test.ts similarity index 100% rename from src/extensions/default/vcs/sapling/commands.test.ts rename to packages/hunk/src/extensions/default/vcs/sapling/commands.test.ts diff --git a/src/extensions/default/vcs/sapling/commands.ts b/packages/hunk/src/extensions/default/vcs/sapling/commands.ts similarity index 100% rename from src/extensions/default/vcs/sapling/commands.ts rename to packages/hunk/src/extensions/default/vcs/sapling/commands.ts diff --git a/src/extensions/default/vcs/sapling/index.test.ts b/packages/hunk/src/extensions/default/vcs/sapling/index.test.ts similarity index 100% rename from src/extensions/default/vcs/sapling/index.test.ts rename to packages/hunk/src/extensions/default/vcs/sapling/index.test.ts diff --git a/src/extensions/default/vcs/sapling/index.ts b/packages/hunk/src/extensions/default/vcs/sapling/index.ts similarity index 100% rename from src/extensions/default/vcs/sapling/index.ts rename to packages/hunk/src/extensions/default/vcs/sapling/index.ts diff --git a/src/extensions/discovery.test.ts b/packages/hunk/src/extensions/discovery.test.ts similarity index 100% rename from src/extensions/discovery.test.ts rename to packages/hunk/src/extensions/discovery.test.ts diff --git a/src/extensions/discovery.ts b/packages/hunk/src/extensions/discovery.ts similarity index 100% rename from src/extensions/discovery.ts rename to packages/hunk/src/extensions/discovery.ts diff --git a/src/extensions/events.test.ts b/packages/hunk/src/extensions/events.test.ts similarity index 99% rename from src/extensions/events.test.ts rename to packages/hunk/src/extensions/events.test.ts index 8957295a9..b5086c644 100644 --- a/src/extensions/events.test.ts +++ b/packages/hunk/src/extensions/events.test.ts @@ -1,5 +1,5 @@ import { describe, expect, test } from "bun:test"; -import { createTestDiffFile } from "../../test/helpers/diff-helpers"; +import { createTestDiffFile } from "../../../../test/helpers/diff-helpers"; import { bindExtensionEventBus, emitExtensionCustomEvent, diff --git a/src/extensions/events.ts b/packages/hunk/src/extensions/events.ts similarity index 100% rename from src/extensions/events.ts rename to packages/hunk/src/extensions/events.ts diff --git a/src/extensions/extensionIds.ts b/packages/hunk/src/extensions/extensionIds.ts similarity index 95% rename from src/extensions/extensionIds.ts rename to packages/hunk/src/extensions/extensionIds.ts index cec899f29..65ba7898f 100644 --- a/src/extensions/extensionIds.ts +++ b/packages/hunk/src/extensions/extensionIds.ts @@ -15,7 +15,7 @@ * colon; no leading separator, so ids read as names. * * The rules are enforced once, where candidates become loadable extensions - * (`src/extensions/host.ts`); this module only states them, so keymap code can + * (`packages/hunk/src/extensions/host.ts`); this module only states them, so keymap code can * ask about the vendor namespace without pulling the loader in behind it. */ diff --git a/src/extensions/host.test.ts b/packages/hunk/src/extensions/host.test.ts similarity index 100% rename from src/extensions/host.test.ts rename to packages/hunk/src/extensions/host.test.ts diff --git a/src/extensions/host.ts b/packages/hunk/src/extensions/host.ts similarity index 100% rename from src/extensions/host.ts rename to packages/hunk/src/extensions/host.ts diff --git a/src/extensions/hostRuntimeModules.test.ts b/packages/hunk/src/extensions/hostRuntimeModules.test.ts similarity index 100% rename from src/extensions/hostRuntimeModules.test.ts rename to packages/hunk/src/extensions/hostRuntimeModules.test.ts diff --git a/src/extensions/hostRuntimeModules.ts b/packages/hunk/src/extensions/hostRuntimeModules.ts similarity index 100% rename from src/extensions/hostRuntimeModules.ts rename to packages/hunk/src/extensions/hostRuntimeModules.ts diff --git a/src/extensions/manage/cli.ts b/packages/hunk/src/extensions/manage/cli.ts similarity index 100% rename from src/extensions/manage/cli.ts rename to packages/hunk/src/extensions/manage/cli.ts diff --git a/src/extensions/manage/install.test.ts b/packages/hunk/src/extensions/manage/install.test.ts similarity index 100% rename from src/extensions/manage/install.test.ts rename to packages/hunk/src/extensions/manage/install.test.ts diff --git a/src/extensions/manage/install.ts b/packages/hunk/src/extensions/manage/install.ts similarity index 100% rename from src/extensions/manage/install.ts rename to packages/hunk/src/extensions/manage/install.ts diff --git a/src/extensions/manage/records.ts b/packages/hunk/src/extensions/manage/records.ts similarity index 100% rename from src/extensions/manage/records.ts rename to packages/hunk/src/extensions/manage/records.ts diff --git a/src/extensions/manage/source.test.ts b/packages/hunk/src/extensions/manage/source.test.ts similarity index 100% rename from src/extensions/manage/source.test.ts rename to packages/hunk/src/extensions/manage/source.test.ts diff --git a/src/extensions/manage/source.ts b/packages/hunk/src/extensions/manage/source.ts similarity index 100% rename from src/extensions/manage/source.ts rename to packages/hunk/src/extensions/manage/source.ts diff --git a/src/extensions/notifications.test.ts b/packages/hunk/src/extensions/notifications.test.ts similarity index 100% rename from src/extensions/notifications.test.ts rename to packages/hunk/src/extensions/notifications.test.ts diff --git a/src/extensions/notifications.ts b/packages/hunk/src/extensions/notifications.ts similarity index 100% rename from src/extensions/notifications.ts rename to packages/hunk/src/extensions/notifications.ts diff --git a/src/extensions/panes.ts b/packages/hunk/src/extensions/panes.ts similarity index 100% rename from src/extensions/panes.ts rename to packages/hunk/src/extensions/panes.ts diff --git a/src/extensions/publicApiRobustness.test.ts b/packages/hunk/src/extensions/publicApiRobustness.test.ts similarity index 99% rename from src/extensions/publicApiRobustness.test.ts rename to packages/hunk/src/extensions/publicApiRobustness.test.ts index 7ff7b09ce..961e51643 100644 --- a/src/extensions/publicApiRobustness.test.ts +++ b/packages/hunk/src/extensions/publicApiRobustness.test.ts @@ -3,7 +3,7 @@ import { collectSessionCustomThemes } from "../core/theme/customThemes"; 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"; +import { createTestDiffFile } from "../../../../test/helpers/diff-helpers"; import { applyExtensionChangesetTransforms, applyExtensionFileLanguages, diff --git a/src/extensions/reviewSnapshot.test.ts b/packages/hunk/src/extensions/reviewSnapshot.test.ts similarity index 99% rename from src/extensions/reviewSnapshot.test.ts rename to packages/hunk/src/extensions/reviewSnapshot.test.ts index a698069a0..512ea45e0 100644 --- a/src/extensions/reviewSnapshot.test.ts +++ b/packages/hunk/src/extensions/reviewSnapshot.test.ts @@ -2,7 +2,7 @@ import { describe, expect, test } from "bun:test"; import { createTestReviewState, createTestStoredNote, -} from "../../test/helpers/review-store-helpers"; +} from "../../../../test/helpers/review-store-helpers"; import { buildExtensionReviewSnapshot, diffExtensionReviewNotes, diff --git a/src/extensions/reviewSnapshot.ts b/packages/hunk/src/extensions/reviewSnapshot.ts similarity index 100% rename from src/extensions/reviewSnapshot.ts rename to packages/hunk/src/extensions/reviewSnapshot.ts diff --git a/src/extensions/runExtension.test.ts b/packages/hunk/src/extensions/runExtension.test.ts similarity index 100% rename from src/extensions/runExtension.test.ts rename to packages/hunk/src/extensions/runExtension.test.ts diff --git a/src/extensions/runExtension.ts b/packages/hunk/src/extensions/runExtension.ts similarity index 99% rename from src/extensions/runExtension.ts rename to packages/hunk/src/extensions/runExtension.ts index 8f69b6f7e..8f2e05d8a 100644 --- a/src/extensions/runExtension.ts +++ b/packages/hunk/src/extensions/runExtension.ts @@ -45,7 +45,7 @@ import { copyExtensionCliCommand } from "./cliCommands"; /** * Running one extension factory into the shared registry. * - * This module deliberately imports nothing from `src/core/vcs` beyond its + * This module deliberately imports nothing from `packages/hunk/src/core/vcs` beyond its * types: it is what the bundled tier (`./bundled`) uses, and that tier is * loaded *from* VCS adapter resolution. Keeping the dependency one-way means * bundled loading cannot deadlock on a half-initialized module. diff --git a/src/extensions/startup.test.ts b/packages/hunk/src/extensions/startup.test.ts similarity index 100% rename from src/extensions/startup.test.ts rename to packages/hunk/src/extensions/startup.test.ts diff --git a/src/extensions/startup.ts b/packages/hunk/src/extensions/startup.ts similarity index 100% rename from src/extensions/startup.ts rename to packages/hunk/src/extensions/startup.ts diff --git a/src/extensions/trust.test.ts b/packages/hunk/src/extensions/trust.test.ts similarity index 100% rename from src/extensions/trust.test.ts rename to packages/hunk/src/extensions/trust.test.ts diff --git a/src/extensions/trust.ts b/packages/hunk/src/extensions/trust.ts similarity index 100% rename from src/extensions/trust.ts rename to packages/hunk/src/extensions/trust.ts diff --git a/src/extensions/types.ts b/packages/hunk/src/extensions/types.ts similarity index 98% rename from src/extensions/types.ts rename to packages/hunk/src/extensions/types.ts index 4bace99a9..dbd6be259 100644 --- a/src/extensions/types.ts +++ b/packages/hunk/src/extensions/types.ts @@ -22,7 +22,7 @@ import type { import { createExtensionNotificationHub, type ExtensionNotificationHub } from "./notifications"; /** - * The authoring contract lives in `src/extension-api/types.ts` and is re-exported + * The authoring contract lives in `packages/hunk/src/extension-api/types.ts` and is re-exported * here so host code keeps one import site for both halves of the system. That * module is self-contained because its declarations are published; this one is * free to reference Hunk internals. @@ -93,7 +93,7 @@ export type { /** * Where one extension came from, which decides its trust posture. * - * `bundled` is Hunk's own compiled-in tier (`src/extensions/default/`): no + * `bundled` is Hunk's own compiled-in tier (`packages/hunk/src/extensions/default/`): no * discovery, no trust prompt, and never disabled by `--no-extensions`. Every * other origin is a user extension loaded from disk. */ diff --git a/src/extensions/vcsPatchResult.test.ts b/packages/hunk/src/extensions/vcsPatchResult.test.ts similarity index 100% rename from src/extensions/vcsPatchResult.test.ts rename to packages/hunk/src/extensions/vcsPatchResult.test.ts diff --git a/src/extensions/vcsPatchResult.ts b/packages/hunk/src/extensions/vcsPatchResult.ts similarity index 100% rename from src/extensions/vcsPatchResult.ts rename to packages/hunk/src/extensions/vcsPatchResult.ts diff --git a/src/highlightWorkerClient.ts b/packages/hunk/src/highlightWorkerClient.ts similarity index 100% rename from src/highlightWorkerClient.ts rename to packages/hunk/src/highlightWorkerClient.ts diff --git a/src/highlightWorkerEntry.ts b/packages/hunk/src/highlightWorkerEntry.ts similarity index 100% rename from src/highlightWorkerEntry.ts rename to packages/hunk/src/highlightWorkerEntry.ts diff --git a/src/hunk-review/skillDocument.test.ts b/packages/hunk/src/hunk-review/skillDocument.test.ts similarity index 90% rename from src/hunk-review/skillDocument.test.ts rename to packages/hunk/src/hunk-review/skillDocument.test.ts index e45f4175e..ac5eded55 100644 --- a/src/hunk-review/skillDocument.test.ts +++ b/packages/hunk/src/hunk-review/skillDocument.test.ts @@ -9,7 +9,15 @@ import { import { renderHunkReviewSkill } from "./skillDocument"; const SKILL_PATH = join(import.meta.dir, "..", "..", "skills", "hunk-review", "SKILL.md"); -const AGENT_WORKFLOWS_PATH = join(import.meta.dir, "..", "..", "docs", "agent-workflows.md"); +const AGENT_WORKFLOWS_PATH = join( + import.meta.dir, + "..", + "..", + "..", + "..", + "docs", + "agent-workflows.md", +); /** Every flag the agent-facing docs may reference: the session surface plus auxiliary options. */ const DOCUMENTED_AGENT_FLAGS = new Set([ @@ -32,7 +40,7 @@ describe("hunk-review skill document", () => { if (checkedIn !== rendered) { throw new Error( - "skills/hunk-review/SKILL.md is out of date. Run `bun run generate:skill` and commit the result.", + "packages/hunk/skills/hunk-review/SKILL.md is out of date. Run `bun run generate:skill` and commit the result.", ); } diff --git a/src/hunk-review/skillDocument.ts b/packages/hunk/src/hunk-review/skillDocument.ts similarity index 99% rename from src/hunk-review/skillDocument.ts rename to packages/hunk/src/hunk-review/skillDocument.ts index 79694d612..8a3983f07 100644 --- a/src/hunk-review/skillDocument.ts +++ b/packages/hunk/src/hunk-review/skillDocument.ts @@ -2,7 +2,7 @@ import { AGENT_ERROR_DOCS } from "../session/agent/errors"; import { SESSION_AGENT_COMMANDS, type AgentCommandSpec } from "../session/agent/surface"; /** - * Deterministic renderer for `skills/hunk-review/SKILL.md`. + * Deterministic renderer for `packages/hunk/skills/hunk-review/SKILL.md`. * * The command reference blocks and the "Common errors" section are derived from * `agentSurface.ts` and `agentErrors.ts` so the skill can never disagree with the parser or the diff --git a/src/lib/commandKeys.test.ts b/packages/hunk/src/lib/commandKeys.test.ts similarity index 94% rename from src/lib/commandKeys.test.ts rename to packages/hunk/src/lib/commandKeys.test.ts index 54c451581..ee4f053c4 100644 --- a/src/lib/commandKeys.test.ts +++ b/packages/hunk/src/lib/commandKeys.test.ts @@ -7,7 +7,7 @@ import { synthesizeKeyEvent } from "../ui/lib/syntheticKeyEvent"; * The internal-only pieces of chord handling. * * The grammar itself is published as `hunkdiff/extension` and covered by - * `src/extension-api/keys.test.ts`; what lives here is what only Hunk needs. + * `packages/hunk/src/extension-api/keys.test.ts`; what lives here is what only Hunk needs. */ function parsed(chord: string) { diff --git a/src/lib/commandKeys.ts b/packages/hunk/src/lib/commandKeys.ts similarity index 96% rename from src/lib/commandKeys.ts rename to packages/hunk/src/lib/commandKeys.ts index f9de7322f..15ece1cf7 100644 --- a/src/lib/commandKeys.ts +++ b/packages/hunk/src/lib/commandKeys.ts @@ -4,7 +4,7 @@ import { matchesKeyChord, parseKeyChord, type ParsedKeyChord } from "../extensio /** * Key-chord parsing and matching for the command registry. * - * The grammar itself lives in `src/extension-api/keys.ts` because extensions + * The grammar itself lives in `packages/hunk/src/extension-api/keys.ts` because extensions * need it too — it is published as part of `hunkdiff/extension` — and is * re-exported here so internal code keeps importing key handling from one * place. What stays local is what extensions have no use for: synthesizing the diff --git a/src/lib/largeFile.ts b/packages/hunk/src/lib/largeFile.ts similarity index 100% rename from src/lib/largeFile.ts rename to packages/hunk/src/lib/largeFile.ts diff --git a/src/lib/osPath.test.ts b/packages/hunk/src/lib/osPath.test.ts similarity index 100% rename from src/lib/osPath.test.ts rename to packages/hunk/src/lib/osPath.test.ts diff --git a/src/lib/osPath.ts b/packages/hunk/src/lib/osPath.ts similarity index 100% rename from src/lib/osPath.ts rename to packages/hunk/src/lib/osPath.ts diff --git a/src/lib/patchPath.ts b/packages/hunk/src/lib/patchPath.ts similarity index 100% rename from src/lib/patchPath.ts rename to packages/hunk/src/lib/patchPath.ts diff --git a/src/lib/sourceText.test.ts b/packages/hunk/src/lib/sourceText.test.ts similarity index 100% rename from src/lib/sourceText.test.ts rename to packages/hunk/src/lib/sourceText.test.ts diff --git a/src/lib/sourceText.ts b/packages/hunk/src/lib/sourceText.ts similarity index 100% rename from src/lib/sourceText.ts rename to packages/hunk/src/lib/sourceText.ts diff --git a/src/lib/terminalText.test.ts b/packages/hunk/src/lib/terminalText.test.ts similarity index 100% rename from src/lib/terminalText.test.ts rename to packages/hunk/src/lib/terminalText.test.ts diff --git a/src/lib/terminalText.ts b/packages/hunk/src/lib/terminalText.ts similarity index 100% rename from src/lib/terminalText.ts rename to packages/hunk/src/lib/terminalText.ts diff --git a/src/main.tsx b/packages/hunk/src/main.tsx similarity index 100% rename from src/main.tsx rename to packages/hunk/src/main.tsx diff --git a/src/opentui/HunkDiffBody.tsx b/packages/hunk/src/opentui/HunkDiffBody.tsx similarity index 100% rename from src/opentui/HunkDiffBody.tsx rename to packages/hunk/src/opentui/HunkDiffBody.tsx diff --git a/src/opentui/HunkDiffFileHeader.tsx b/packages/hunk/src/opentui/HunkDiffFileHeader.tsx similarity index 100% rename from src/opentui/HunkDiffFileHeader.tsx rename to packages/hunk/src/opentui/HunkDiffFileHeader.tsx diff --git a/src/opentui/HunkDiffView.test.tsx b/packages/hunk/src/opentui/HunkDiffView.test.tsx similarity index 100% rename from src/opentui/HunkDiffView.test.tsx rename to packages/hunk/src/opentui/HunkDiffView.test.tsx diff --git a/src/opentui/HunkDiffView.tsx b/packages/hunk/src/opentui/HunkDiffView.tsx similarity index 100% rename from src/opentui/HunkDiffView.tsx rename to packages/hunk/src/opentui/HunkDiffView.tsx diff --git a/src/opentui/HunkFileNav.tsx b/packages/hunk/src/opentui/HunkFileNav.tsx similarity index 100% rename from src/opentui/HunkFileNav.tsx rename to packages/hunk/src/opentui/HunkFileNav.tsx diff --git a/src/opentui/HunkReviewStream.tsx b/packages/hunk/src/opentui/HunkReviewStream.tsx similarity index 100% rename from src/opentui/HunkReviewStream.tsx rename to packages/hunk/src/opentui/HunkReviewStream.tsx diff --git a/src/opentui/index.ts b/packages/hunk/src/opentui/index.ts similarity index 100% rename from src/opentui/index.ts rename to packages/hunk/src/opentui/index.ts diff --git a/src/opentui/model.ts b/packages/hunk/src/opentui/model.ts similarity index 88% rename from src/opentui/model.ts rename to packages/hunk/src/opentui/model.ts index 533c1ad34..0073ce809 100644 --- a/src/opentui/model.ts +++ b/packages/hunk/src/opentui/model.ts @@ -1,16 +1,18 @@ -import { parsePatchFiles } from "@pierre/diffs"; +import { parsePatchFiles, type FileDiffMetadata } from "@pierre/diffs"; 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/changeset/model"; -import type { HunkDiffFile, HunkDiffFileInput } from "./types"; +import type { HunkDiffFile, HunkDiffFileInput, HunkDiffStats } from "./types"; const NORMALIZED_HUNK_DIFF_FILES = new WeakSet(); /** Count visible additions and deletions from Pierre metadata. */ -export const countHunkDiffStats = countDiffStats; +export function countHunkDiffStats(metadata: FileDiffMetadata): HunkDiffStats { + return countDiffStats(metadata); +} /** Build one public file while optionally preserving paths decoded exactly from Git quoting. */ function buildHunkDiffFile(input: HunkDiffFileInput, pathsAreExact: boolean): HunkDiffFile { @@ -48,7 +50,7 @@ function resolveHunkDiffFile(input: HunkDiffFileInput) { return createHunkDiffFile(input); } -/** Adapt the public OpenTUI file shape into Hunk's internal review file model. */ +/** @internal Adapt the public OpenTUI file shape into Hunk's internal review file model. */ export function toInternalDiffFile(diff: HunkDiffFileInput): DiffFile { const normalized = resolveHunkDiffFile(diff); const patch = normalized.patch ?? ""; @@ -93,7 +95,7 @@ export function createHunkDiffFilesFromPatch(patchText: string, sourceId = "patc }); } -/** Adapt a list of public OpenTUI files into Hunk's internal review file model. */ +/** @internal Adapt a list of public OpenTUI files into Hunk's internal review file model. */ export function toInternalDiffFiles(files: HunkDiffFileInput[]) { return files.map(toInternalDiffFile); } diff --git a/src/opentui/themes.ts b/packages/hunk/src/opentui/themes.ts similarity index 100% rename from src/opentui/themes.ts rename to packages/hunk/src/opentui/themes.ts diff --git a/src/opentui/types.ts b/packages/hunk/src/opentui/types.ts similarity index 100% rename from src/opentui/types.ts rename to packages/hunk/src/opentui/types.ts diff --git a/src/session/agent/cliClient.test.ts b/packages/hunk/src/session/agent/cliClient.test.ts similarity index 99% rename from src/session/agent/cliClient.test.ts rename to packages/hunk/src/session/agent/cliClient.test.ts index b58b0a9f9..7598c5a59 100644 --- a/src/session/agent/cliClient.test.ts +++ b/packages/hunk/src/session/agent/cliClient.test.ts @@ -8,7 +8,7 @@ import { createTestSessionReviewFile, createTestSessionReviewHunk, createTestSessionSnapshot, -} from "../../../test/helpers/session-daemon-fixtures"; +} from "../../../../../test/helpers/session-daemon-fixtures"; import type { SessionSelectorInput } from "../../core/run/commandInputs"; import { HUNK_SESSION_API_PATH, diff --git a/src/session/agent/cliClient.ts b/packages/hunk/src/session/agent/cliClient.ts similarity index 100% rename from src/session/agent/cliClient.ts rename to packages/hunk/src/session/agent/cliClient.ts diff --git a/src/session/agent/commands.daemon.test.ts b/packages/hunk/src/session/agent/commands.daemon.test.ts similarity index 98% rename from src/session/agent/commands.daemon.test.ts rename to packages/hunk/src/session/agent/commands.daemon.test.ts index dbb4c7f62..93f59b68d 100644 --- a/src/session/agent/commands.daemon.test.ts +++ b/packages/hunk/src/session/agent/commands.daemon.test.ts @@ -2,7 +2,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/run/commandInputs"; -import { createTestListedSession } from "../../../test/helpers/session-daemon-fixtures"; +import { createTestListedSession } from "../../../../../test/helpers/session-daemon-fixtures"; import { runSessionCommand, setSessionCommandTestHooks, diff --git a/src/session/agent/commands.test.ts b/packages/hunk/src/session/agent/commands.test.ts similarity index 99% rename from src/session/agent/commands.test.ts rename to packages/hunk/src/session/agent/commands.test.ts index 8b7d9e7b7..8470c8f5d 100644 --- a/src/session/agent/commands.test.ts +++ b/packages/hunk/src/session/agent/commands.test.ts @@ -6,7 +6,7 @@ import { createTestSessionFileSummary, createTestSessionReview as buildTestSessionReview, createTestSessionSnapshot, -} from "../../../test/helpers/session-daemon-fixtures"; +} from "../../../../../test/helpers/session-daemon-fixtures"; import type { SessionCommandInput, SessionSelectorInput } from "../../core/run/commandInputs"; import { runSessionCommand, diff --git a/src/session/agent/commands.ts b/packages/hunk/src/session/agent/commands.ts similarity index 100% rename from src/session/agent/commands.ts rename to packages/hunk/src/session/agent/commands.ts diff --git a/src/session/agent/errors.test.ts b/packages/hunk/src/session/agent/errors.test.ts similarity index 100% rename from src/session/agent/errors.test.ts rename to packages/hunk/src/session/agent/errors.test.ts diff --git a/src/session/agent/errors.ts b/packages/hunk/src/session/agent/errors.ts similarity index 97% rename from src/session/agent/errors.ts rename to packages/hunk/src/session/agent/errors.ts index dcba9dad4..b8db1f392 100644 --- a/src/session/agent/errors.ts +++ b/packages/hunk/src/session/agent/errors.ts @@ -1,7 +1,7 @@ /** * Agent-facing error messages for the `hunk session` surface. * - * Every message quoted by the generated `skills/hunk-review/SKILL.md` "Common errors" section is + * Every message quoted by the generated `packages/hunk/skills/hunk-review/SKILL.md` "Common errors" section is * defined (or contract-tested) here, so the skill can never quote wording the CLI no longer * throws. Throw sites import these builders instead of repeating string literals. */ diff --git a/src/session/agent/surface.test.ts b/packages/hunk/src/session/agent/surface.test.ts similarity index 100% rename from src/session/agent/surface.test.ts rename to packages/hunk/src/session/agent/surface.test.ts diff --git a/src/session/agent/surface.ts b/packages/hunk/src/session/agent/surface.ts similarity index 98% rename from src/session/agent/surface.ts rename to packages/hunk/src/session/agent/surface.ts index 80007bc85..eb2c056e8 100644 --- a/src/session/agent/surface.ts +++ b/packages/hunk/src/session/agent/surface.ts @@ -4,8 +4,8 @@ import type { SessionDaemonAction } from "../protocol"; * Declarative description of the agent-facing `hunk session` command surface. * * This module is the single source of truth for what agents can invoke: the Commander commands in - * `src/app/cli.ts`, the `hunk session --help` usage text, and the generated - * `skills/hunk-review/SKILL.md` reference sections are all derived from these specs, so the parser + * `packages/hunk/src/app/cli.ts`, the `hunk session --help` usage text, and the generated + * `packages/hunk/skills/hunk-review/SKILL.md` reference sections are all derived from these specs, so the parser * and the docs cannot drift apart. Keep it pure data with no runtime dependencies. */ @@ -67,7 +67,7 @@ type OptionValue