Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .changeset/lazy-grapes-divide.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
66 changes: 66 additions & 0 deletions .dependency-cruiser.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,31 @@ const UI_SESSION_ADAPTERS = [
"^src/ui/lib/reviewState\\.ts$",
];

// Every way the shipped product is entered: the CLI, the highlight worker thread, the two
// published facades, and the skill generator. A module under src/ that no entry reaches,
// directly or transitively, is not in the product.
const PRODUCTION_ENTRY_POINTS = [
"^src/main\\.tsx$",
"^src/highlightWorkerEntry\\.ts$",
"^src/opentui/index\\.ts$",
"^src/extension-api/index\\.ts$",
"^src/hunk-review/skillDocument\\.ts$",
];

// Modules kept alive by tests alone. The cruise excludes tests, so these look unreachable
// from the entry points while real coverage still depends on them. Shrink-only: an entry
// leaves when production reaches the module or the module goes; nothing is ever added
// without the coverage to justify it.
const TEST_ONLY_MODULES = [
// Note-height measurement exercised by the review-conformance corpus.
"^src/core/review/noteSize\\.ts$",
// The floating agent-note popover and its measurement helper. Nothing renders them since
// notes moved into the diff flow as STML cards; their unit tests are the only consumers
// left, so they are quarantined here until that call is made rather than deleted blind.
"^src/ui/components/panes/AgentCard\\.tsx$",
"^src/ui/lib/agentPopover\\.ts$",
];

module.exports = {
forbidden: [
{
Expand Down Expand Up @@ -94,6 +119,47 @@ module.exports = {
from: { path: "^src/ui/", pathNot: UI_SESSION_ADAPTERS },
to: { path: "^src/(app|session)/" },
},
{
name: "no-dead-modules",
comment:
"Every module under src/ earns its place by being reachable from an entry point. Dead files are worse than clutter: they still import, so they hold boundaries hostage and answer questions nobody asks. `orphan` only catches fully disconnected files, which misses dead code that still has dependencies — reachability catches both. A flagged module is either deleted or, if tests are its only real consumer, listed in TEST_ONLY_MODULES with a reason.",
severity: "error",
from: { path: PRODUCTION_ENTRY_POINTS },
to: {
path: "^src/",
pathNot: [...PRODUCTION_ENTRY_POINTS, ...TEST_ONLY_MODULES],
reachable: false,
},
},
{
name: "core-leaves-stay-below-bootstrap",
comment:
"core/bootstrap.ts composes the leaves: it names the changeset, the parsed input, the resolved preferences, and the detected theme mode to describe one launch. A module directory importing it back would invert that layering and rebuild the grab-bag cycle the 2026-08 phases dismantled. core/changeset/loaders.ts is the single exception — loadAppBootstrap assembles the value, so it names the shape it returns; its natural home is the app tier, and moving it there retires this exception.",
severity: "error",
from: {
path: "^src/core/(changeset|run|process|review|vcs|watch|patch|theme)/",
pathNot: "^src/core/changeset/loaders\\.ts$",
},
to: { path: "^src/core/bootstrap\\.ts$" },
},
{
name: "review-reducer-is-module-internal",
comment:
"The review reducer applies actions; callers state intent instead, so surfaces cannot reach past planReviewIntent into the transition table. First of the per-module interior rules — this establishes the mechanism later phases extend to the rest of src/core (identity.ts and the other named model modules stay public by design).",
severity: "error",
from: { path: "^src/", pathNot: "^src/core/review/" },
to: { path: "^src/core/review/reducer\\.ts$" },
},
{
name: "changeset-internals-stay-in-module",
comment:
"core/changeset owns the changeset model and the pipeline that acquires one. Outsiders name the model, the loaders, and the per-file helpers they build on (model, loaders, diffFile, fileSource, fileLanguage, binary, diffPaths, hunkHeader, hunkSummary); the patch-to-model parse, the Pierre extension-table lookup, and the sidecar reader are steps inside that pipeline, reached through the loaders instead.",
severity: "error",
from: { path: "^src/", pathNot: "^src/core/changeset/" },
to: {
path: "^src/core/changeset/(fromPatch|fileLanguageLookup|sidecar)\\.ts$",
},
},
{
name: "packages-stay-standalone",
comment:
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/bootstrap-load.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { join } from "node:path";
import { performance } from "perf_hooks";
import { parsePatchFiles } from "@pierre/diffs";
import { getBundledVcsCatalog } from "../src/app/vcsCatalog";
import { loadAppBootstrap } from "../src/core/changesetLoaders";
import { loadAppBootstrap } from "../src/core/changeset/loaders";

const FILE_COUNT = 64;
const LINES_PER_FILE = 420;
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/changeset-parse.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Benchmark raw patch parsing and sanitized DiffFile construction for several diff shapes.
import { performance } from "perf_hooks";
import { parsePatchFiles } from "@pierre/diffs";
import { buildDiffFile } from "../src/core/diffFile";
import { buildDiffFile } from "../src/core/changeset/diffFile";
import { findPatchChunk, splitPatchIntoFileChunks } from "../src/core/patch/chunks";
import { sanitizePatchText } from "../src/core/patch/sanitize";
import { createSyntheticPatch } from "./lib/fixtures";
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/compact-highlight-payload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// normalizes it, and the terminal paint seam remains a later milestone.
import { performance } from "node:perf_hooks";
import { cleanLastNewline, parseDiffFromFile } from "@pierre/diffs";
import type { DiffFile } from "../src/core/types";
import type { DiffFile } from "../src/core/changeset/model";
import {
buildSplitRows,
loadHighlightedDiff,
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/highlight-cache-layers.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Compare a resident main-process cache hit with a worker-LRU revisit after main-cache eviction.
import { performance } from "node:perf_hooks";
import { parseDiffFromFile } from "@pierre/diffs";
import type { DiffFile } from "../src/core/types";
import type { DiffFile } from "../src/core/changeset/model";
import { resolveTheme } from "../src/ui/themes";
import { disposeHighlightWorker } from "../src/ui/diff/worker/highlightWorkerClient";
import { prefetchHighlightedDiff } from "../src/ui/diff/useHighlightedDiff";
Expand Down
3 changes: 2 additions & 1 deletion benchmarks/highlight-prefetch.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@ import { testRender } from "@opentui/react/test-utils";
import { parseDiffFromFile } from "@pierre/diffs";
import { act } from "react";
import { AppHost } from "../src/ui/AppHost";
import type { AppBootstrap, DiffFile } from "../src/core/types";
import type { AppBootstrap } from "../src/core/bootstrap";
import type { DiffFile } from "../src/core/changeset/model";

function createDiffFile(index: number, marker: string): DiffFile {
const path = `src/example${index}.ts`;
Expand Down
3 changes: 2 additions & 1 deletion benchmarks/large-stream-fixture.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { parseDiffFromFile, parsePatchFiles } from "@pierre/diffs";
import type { AppBootstrap, DiffFile } from "../src/core/types";
import type { AppBootstrap } from "../src/core/bootstrap";
import type { DiffFile } from "../src/core/changeset/model";

export const DEFAULT_FILE_COUNT = 180;
export const DEFAULT_LINES_PER_FILE = 120;
Expand Down
2 changes: 1 addition & 1 deletion benchmarks/working-tree-load.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// Benchmark git-backed working-tree loading, including untracked file handling.
import { performance } from "perf_hooks";
import { getBundledVcsCatalog } from "../src/app/vcsCatalog";
import { loadAppBootstrap } from "../src/core/changesetLoaders";
import { loadAppBootstrap } from "../src/core/changeset/loaders";
import { addUntrackedFiles, createChangedRepo } from "./lib/fixtures";

interface Scenario {
Expand Down
3 changes: 2 additions & 1 deletion benchmarks/wrapped-cjk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import { performance } from "node:perf_hooks";
import { parsePatchFiles } from "@pierre/diffs";
import { testRender } from "@opentui/react/test-utils";
import React, { act } from "react";
import type { AppBootstrap, DiffFile } from "../src/core/types";
import type { AppBootstrap } from "../src/core/bootstrap";
import type { DiffFile } from "../src/core/changeset/model";
import { AppHost } from "../src/ui/AppHost";
import { prefetchHighlightedDiff } from "../src/ui/diff/useHighlightedDiff";
import { VIEWPORT_READ_COALESCE_MS } from "../src/ui/lib/viewportTiming";
Expand Down
8 changes: 6 additions & 2 deletions docs/browser-review-seam-audit.md
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ whether a client needs more than that is Phase 5's first question.
_Repaid (Phase 1 PR 2)_: `reviewDefaultHunkLineTarget` in `core/review/geometry.ts`;
`firstCommentTargetForHunk` deleted and both terminal callers converted; every geometry fixture
pins the target, `pure-deletion-hunk` and `hunk-with-leading-context` adversarially.
- **A11. Language registration side effect missing in browser.** `core/fileLanguage.ts`
- **A11. Language registration side effect missing in browser.** `core/changeset/fileLanguage.ts`
registers `.mts`/`.cts`; the web bundle never imports it, so Pierre's own inference runs
unregistered for files without an explicit `language`. Fix: side-effect import in
`src/web/main.tsx` (or fold registration into the shared model).
Expand Down Expand Up @@ -515,7 +515,7 @@ here so the extraction happens before the duplication exists. Design detail in
Fix: extract a renderer-neutral catalog (id, title, category, default chords, resolution
locus — semantic / client-local / host-only); terminal keeps matchers and handlers, browser
adds its own, both render menus/help/palette from the catalog.
_Repaid (Phase 1 PR 3)_: `src/core/commandCatalog.ts` carries id, title, category, default
_Repaid (Phase 1 PR 3)_: `src/core/run/commandCatalog.ts` carries id, title, category, default
chords, resolution locus, extension visibility, and menu-closing behavior for all 44 built-ins.
`ui/lib/appCommands.ts` builds its dispatch table from it — the handler map is keyed by
`AppCommandId`, so a catalogued command with no terminal handler fails to typecheck — and
Expand Down Expand Up @@ -619,6 +619,10 @@ implementation does.
carrying separators, percent signs, and non-ASCII characters, and strict rejection of anything
outside the grammar. No consumers yet, by design — browser deep links are Phase 5 and opener
fragments Phase 6, which is when this finding closes.
_Reopened (2026-08-17, module-boundaries phase 0)_: `core/review/address.ts` is deleted. A
primitive with no consumers cannot be checked against what its consumers need, and the
`no-dead-modules` rule now flags exactly that shape. The grammar above still stands as the
design; write it again beside the first consumer that addresses a review across a boundary.
- **G4. User-facing error catalog.** The repo already solves this once for agents:
`src/session/agent/errors.ts` single-sources every message the generated skill quotes, with
contract tests. The browser has no equivalent — action rejections (`invalid-action`,
Expand Down
2 changes: 1 addition & 1 deletion docs/changelog-on-hunk-dev.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,6 @@ none), and patch chips appear only when a series has more than one release.
launch-video pipeline keeps generated media out of Git.
- **Contributor lists.** The GitHub release bodies name first-time contributors, which is community
goodwill and organic links. `CHANGELOG.md` does not carry authors, so this needs a second input.
- **The in-app update notice.** `src/core/updateNotice.ts` tells users a new version exists without
- **The in-app update notice.** `src/core/process/updateNotice.ts` tells users a new version exists without
linking what changed. Appending `hunk.dev/changelog/<minor>` is the highest-intent entry point
available and is deliberately left as a separate change.
2 changes: 1 addition & 1 deletion docs/extension-architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -300,7 +300,7 @@ and exact-source reading — live entirely under
`src/extensions/default/vcs/<provider>/`. `src/extensions/vcsPatchResult.ts` is
the one conversion boundary where a published `ExtensionVcsPatchResult`
becomes Hunk's internal diff model, including structural `too-large` source
results. `src/core/projectRoot.ts` treats `.hunk` as a provider-independent
results. `src/core/process/projectRoot.ts` treats `.hunk` as a provider-independent
bootstrap marker and also consults the available catalog; startup performs a
second root/config pass when a global, config-path, or CLI adapter recognizes a
repository unavailable to the bundled catalog.
Expand Down
4 changes: 2 additions & 2 deletions docs/extension-system-exploration.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ seams in very different states of readiness:
- Dynamic theme registration exists (`ensureSyntaxHighlightThemeRegistered` →
Pierre's `registerCustomTheme`), but the config layer caps custom themes at
one `"custom"` slot.
- Pierre's `setCustomExtension` (`src/core/fileLanguage.ts`) is already used at
- Pierre's `setCustomExtension` (`src/core/changeset/fileLanguage.ts`) is already used at
import time for `.mts`/`.cts`; extending it to plugin-declared mappings is
trivial.
- `StartupDeps` (`src/app/startup.ts`) fully injects the startup pipeline
Expand Down Expand Up @@ -171,7 +171,7 @@ export default function (hunk: HunkExtensionAPI) {
````

- Discovery: `~/.config/hunk/extensions/*.ts` and `*/index.ts` (global, follows
our existing XDG path logic in `src/core/paths.ts`), `.hunk/extensions/`
our existing XDG path logic in `src/core/run/paths.ts`), `.hunk/extensions/`
(repo-local, **trust-gated**, same posture as pi's project trust), explicit
`[extensions] paths = [...]` in `config.toml`, and a `--extension <path>`
dev flag. `--no-extensions` for a clean run and for bug triage.
Expand Down
Loading
Loading