Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
304f7b2
feat(replay): add guided pull request reconstruction
fcoury-oai Jul 27, 2026
7803b26
feat(replay): distinguish guide and source focus
fcoury-oai Jul 27, 2026
68cf129
feat(replay): apply hunks immediately with safe undo
fcoury-oai Jul 27, 2026
2ee7589
feat(replay): resolve feature branches from their merge base
fcoury-oai Jul 27, 2026
6924258
feat(replay): compile exact multi-file source exercises
fcoury-oai Jul 27, 2026
0c4347d
feat(replay): start reviews from GitHub or local branches
fcoury-oai Jul 27, 2026
a3859c1
fix(replay): show complete scratch worktree confirmation
fcoury-oai Jul 27, 2026
6f7506d
fix(replay): bound real pull request presentation
fcoury-oai Jul 27, 2026
e497b54
fix(replay): safely resume verified scratch worktrees
fcoury-oai Jul 27, 2026
2ec0d06
fix(replay): anchor repeated hunks to original lines
fcoury-oai Jul 27, 2026
59faf51
fix(replay): apply and undo exact cross-file hunks
fcoury-oai Jul 27, 2026
d5a97b2
fix(replay): track exact scratch files and undone steps
fcoury-oai Jul 27, 2026
bc3c4e1
fix(replay): focus each original hunk in source
fcoury-oai Jul 27, 2026
4d6f030
fix(replay): preserve plugin API compatibility
fcoury-oai Jul 27, 2026
ebafbdd
fix(replay): scope shortcuts to the focused guide
fcoury-oai Jul 27, 2026
068a539
docs(replay): explain the real review workflow
fcoury-oai Jul 27, 2026
b10db01
fix(replay): load review sources without freezing the editor
fcoury-oai Jul 27, 2026
f5859cb
fix(replay): preserve focused pane leader shortcuts
fcoury-oai Jul 27, 2026
6c6c64e
fix(replay): prevent filesystem monitor checkout hangs
fcoury-oai Jul 27, 2026
7c0e6c2
fix(replay): show scratch checkout progress and failures
fcoury-oai Jul 27, 2026
ff91eb9
fix(replay): recover interrupted scratch worktrees safely
fcoury-oai Jul 27, 2026
fdc245c
fix(replay): generate source-grounded learning steps
fcoury-oai Jul 27, 2026
ecf20da
feat(replay): persist reviewer progress and findings
fcoury-oai Jul 27, 2026
3f8c6d8
feat(replay): add source-grounded hints and file navigation
fcoury-oai Jul 27, 2026
aa887a4
feat(replay): add recoverable reviews picker
fcoury-oai Jul 27, 2026
d984392
feat(replay): add role-aware local review outbox
fcoury-oai Jul 27, 2026
ae3137c
fix(replay): restore cross-platform checks
fcoury-oai Jul 27, 2026
6ed3f81
fix(replay): isolate outbox actions and recover failed workers
fcoury-oai Jul 27, 2026
e3cb31c
fix(replay): clarify compact review and outbox states
fcoury-oai Jul 27, 2026
227c05e
feat(replay): add portable private review bundles
fcoury-oai Jul 27, 2026
5e11dc0
fix(replay): keep private save visible in narrow panes
fcoury-oai Jul 27, 2026
aaf6f26
fix(replay): restore Windows CI portability
fcoury-oai Jul 28, 2026
24e7935
feat(replay): submit explicitly approved GitHub reviews
fcoury-oai Jul 28, 2026
8cf2352
feat(replay): open verified original PR author worktrees
fcoury-oai Jul 28, 2026
96df240
fix(replay): restore author actions for recovered reviews
fcoury-oai Jul 28, 2026
fb2b0b8
fix(replay): keep author confirmations readable
fcoury-oai Jul 28, 2026
0241f39
feat(replay): refine responsive review pane
fcoury-oai Jul 28, 2026
656b7c9
feat(replay): synchronize and refine source-backed reviews
fcoury-oai Jul 28, 2026
a2a47c1
feat(replay): clarify scratch review and add pane zoom
fcoury-oai Jul 28, 2026
eeea22c
fix(replay): preserve scratch filenames in narrow panes
fcoury-oai Jul 28, 2026
3f37027
fix(replay): retain filenames in compact source bars
fcoury-oai Jul 28, 2026
9eb5b21
feat(replay): clarify review progress and applied hunks
fcoury-oai Jul 28, 2026
5b5cdbc
feat(replay): polish completion and hunk presentation
fcoury-oai Jul 28, 2026
478e0f2
fix(replay): keep completed review controls grouped
fcoury-oai Jul 28, 2026
7e99fcd
fix(replay): make review workflow state explicit
fcoury-oai Jul 28, 2026
a2fb50b
fix(replay): bound provider and git command execution
fcoury-oai Jul 28, 2026
9dc42d0
feat(replay): recover and reconcile github reviews safely
fcoury-oai Jul 28, 2026
6dafe19
fix(replay): show immediate review discovery progress
fcoury-oai Jul 28, 2026
5401492
feat(ui): animate loading spinners in asynchronous pickers
fcoury-oai Jul 28, 2026
57bdc46
feat(replay): add approval-gated codex assistance
fcoury-oai Jul 28, 2026
ac0cfe1
fix(replay): answer codex questions before drafting comments
fcoury-oai Jul 28, 2026
285bf9d
docs(replay): define product architecture and delivery roadmap
fcoury-oai Jul 29, 2026
9eacce1
feat(replay): add persistent codex companion pane
fcoury-oai Jul 29, 2026
aebdba0
fix(replay): allow longer git operations in large repositories
fcoury-oai Jul 29, 2026
9b9057d
chore: ignore .aidocs
fcoury-oai Jul 30, 2026
8989198
perf(replay): add navigation profiling and benchmark
fcoury-oai Jul 30, 2026
6502b83
perf(replay): cache highlighted hunks and coalesce redraws
fcoury-oai Jul 30, 2026
2d2d8a7
test(replay): benchmark restored real pull request reviews
fcoury-oai Jul 30, 2026
1d44089
fix(replay): tolerate advancing pull request base branches
fcoury-oai Jul 30, 2026
fe4bf7b
fix(replay): keep new reviews available while saved reviews load
fcoury-oai Jul 30, 2026
ac024d6
fix(git): coalesce status scans in large repositories
fcoury-oai Jul 30, 2026
6f73963
perf(replay): avoid duplicate git scans and eager language servers
fcoury-oai Jul 30, 2026
1c1fd01
feat(replay): regenerate or safely restart existing reviews
fcoury-oai Jul 30, 2026
82be970
feat(replay): group meaningful source-backed changes
fcoury-oai Jul 30, 2026
5298c15
fix(replay): ignore reviews missing original scratch buffers
fcoury-oai Jul 30, 2026
a05041d
Merge pull request #165 from codersauce/fcoury/pr-replay-semantic-cha…
fcoury-oai Jul 30, 2026
48d1e5f
fix(replay): derive truthful source-backed change descriptions
fcoury-oai Jul 30, 2026
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
/target
red.log
.DS_Store
.aidocs
2 changes: 2 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ ropey = "1.6.1"
serde = { version = "1.0.196", features = ["derive"] }
serde_json = "1.0.113"
semver = "1.0"
sha2 = "0.10.9"
similar = "2.6.0"
textwrap = "0.16"
tempfile = "3.15.0"
Expand All @@ -104,6 +105,7 @@ tree-sitter-typescript = "0.23.2"
tree-sitter-yaml = "0.7.2"
unicode-segmentation = "1.12.0"
unicode-width = "0.2"
url = "2.5"
uuid = { version = "1.12.0", features = ["v4", "v5"] }

[target.'cfg(target_os = "macos")'.dependencies]
Expand Down
34 changes: 34 additions & 0 deletions default_config.toml
Original file line number Diff line number Diff line change
Expand Up @@ -307,6 +307,39 @@ Esc = { EnterMode = "Normal" }
"h" = { "s" = { PluginCommand = "GitHunkStage" }, "u" = { PluginCommand = "GitHunkUnstage" }, "r" = { PluginCommand = "GitHunkReset" } }
"c" = { "c" = { PluginCommand = "GitSubmitMessage" }, "q" = { PluginCommand = "GitCancelMessage" } }

[keys.normal." "."R"]
"?" = { PluginCommand = "ReplayHelp" }
"g" = { PluginCommand = "Replay" }
"A" = { PluginCommand = "ReplayReviewActions" }
"R" = { PluginCommand = "ReplayRegenerate" }
"D" = { PluginCommand = "ReplayRestart" }
"[" = { PluginCommand = "ReplayPreviousFile" }
"]" = { PluginCommand = "ReplayNextFile" }
"n" = { PluginCommand = "ReplayNext" }
"p" = { PluginCommand = "ReplayPrevious" }
"h" = { PluginCommand = "ReplayHint" }
"m" = { PluginCommand = "ReplayToggleMode" }
"i" = { PluginCommand = "ReplayEdit" }
"v" = { PluginCommand = "ReplayValidate" }
"a" = { PluginCommand = "ReplayApply" }
"z" = { PluginCommand = "ReplayZoom" }
"u" = "ReplayUndo"
"o" = { PluginCommand = "ReplayNote" }
"f" = { PluginCommand = "ReplayFindings" }
"c" = { PluginCommand = "ReplayComment" }
"x" = { PluginCommand = "ReplayAsk" }
"X" = { PluginCommand = "ReplayAskScope" }
"F" = { PluginCommand = "ReplayFix" }
"W" = { PluginCommand = "ReplayOriginalWorkspace" }
"r" = { PluginCommand = "ReplayOutbox" }
"s" = { PluginCommand = "ReplaySummary" }
"e" = { PluginCommand = "ReplayEditDraft" }
"d" = { PluginCommand = "ReplayDiscardDraft" }
"P" = { PluginCommand = "ReplayPublish" }
"S" = { PluginCommand = "ReplaySaveReview" }
"L" = { PluginCommand = "ReplayLoadReview" }
"q" = { PluginCommand = "ReplayClose" }

[keys.normal." "."d"]
"b" = "DumpBuffer"
"i" = "DumpDiagnostics"
Expand Down Expand Up @@ -363,6 +396,7 @@ inlay_hints = "inlay_hints.hk"
lsp_symbols = "lsp_symbols.hk"
neotree = "neotree.hk"
project_search = "project_search.hk"
replay = "replay.hk"
theme_browser = "theme_browser.hk"

# Breadcrumb text uses theme colors; file icons use nvim-web-devicons' light/dark palette.
Expand Down
186 changes: 183 additions & 3 deletions docs/PLUGIN_API.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Husk plugin compatibility

Red host API version `0.4.0` is defined by
Red host API version `0.5.1` is defined by
[`src/plugin/host_api.json`](../src/plugin/host_api.json). That file is the canonical,
machine-readable list of execute actions, request actions, signatures, and introduction
versions. Runtime dispatch and the bundled-plugin corpus are checked against it in tests.
Expand All @@ -15,6 +15,12 @@ unrelated plugins continue. While Red is pre-1.0:
- removing or incompatibly changing a call requires a host-API minor bump, a change
manifest entry, and a migration note.

Host API `0.5.1` preserves the complete `0.4.0` contract. Existing filesystem
plugins declaring `"red_api_version": "^0.4.0"` therefore continue to load
without editing their metadata; plugins that require the new Replay host calls
must explicitly declare the `0.5.x` version that introduced those calls. Older
unsupported minor ranges and future incompatible versions remain quarantined.

Load runs parse, name resolution, and type checking against Red's host declarations
before activation. Diagnostics retain source spans and use stable families:
`HUSK-P0001` for parsing, `HUSK-T0001` for semantic/type errors, and `HUSK-A0001` for a
Expand All @@ -23,6 +29,176 @@ required/optional arity (`HUSK-A0002`) and obvious literal argument types
(`HUSK-A0003`) against the machine-readable signature. `--no-typecheck` is an unsupported
development escape hatch; compatibility guarantees do not apply while it is enabled.

## Pull request replay preview

Host API `0.5.0` introduces an editor-owned, in-memory PR Replay preview.
`ReplayDemoPlan(callback)` returns the original mock PR metadata and complete,
source-linked unified hunks. `ReplayDemoOpenWorkspace(callback)` opens only the
Rust-owned, editable, fileless scratch source. The bundled replay plugin uses the
existing `CreateTextPanel`, `UpdateTextPanel`, `FocusPanel`, and
`SetPanelVisible` host calls to render a separate read-only Replay coach. The
coach is a real plugin panel, never a Markdown file or an editable editor
buffer. `Ctrl-w H`, `Ctrl-w J`, `Ctrl-w K`, and `Ctrl-w L` move a focused text
panel to the left, bottom, top, or right dock while preserving its content,
scroll state, and stable identity.

The coach uses the structured `replay` text-panel block format. Its JSON model
retains the complete original unified patch, PR metadata, step progress,
learning mode, local observations, and completion state. The editor validates
the model and its source path before rendering the hunk. Removed and added source
are independently Tree-sitter highlighted against the old and new file
projections, then combined with theme-derived Git colors and source line
numbers. PR context, reconstruction steps, and responsive actions remain pinned;
only the actual source hunk scrolls.

`ReplayDemoFocusSource(workspace_id)` restores the original scratch-source
window. Hiding the coach preserves both the panel and replay session.

`ReplayValidateStep(callback, workspace_id, step_id)` checks the actual
in-memory scratch source against the Rust-owned original hunk.
`ReplayApplyStep(callback, workspace_id, step_id, revision)` rejects a stale
workspace, changed source, nested user transaction, or nonmatching pre-image.
Its `revision` is a nonnegative, full-width `i64`; it is never narrowed to a
32-bit integer before reaching the editor's checked buffer revision.
Successful application becomes exactly one attributed, undoable editor
transaction. These preview calls never create files or branches, fetch GitHub,
stage changes, save buffers, or submit reviews.

The original `ReplayDemoValidateStep` and `ReplayDemoApplyStep` names remain
supported as backwards-compatible aliases. The production names apply equally
to the safe in-memory demo and real source-backed Replay workspaces.

`ReplayReconcileReview(callback, workspace_id)` performs a bounded, read-only
lookup for one previously approved uncertain review or imported unverified
receipt. The editor compares the original PR, reviewer, commit, outcome, body,
and inline diff coordinates before returning a verified receipt. It never
submits a review, starts an agent, mutates a Git ref, or grants a plugin shell
or GitHub credentials.

Plugins declaring a host API requirement for these calls should use
`"red_api_version": "^0.5.0"`.

## Source-backed pull request replay

Host API `0.5.1` adds real, editor-owned GitHub and local-branch review without
changing or invalidating the `0.5.0` preview contract. Existing plugins that
declare `^0.5.0` remain compatible.

`ReplayResolvePullRequest(callback, input)` accepts a positive PR number or a
canonical HTTPS pull-request URL for the current repository. The editor reads
bounded `gh pr view` metadata, validates the repository and original author
head, pins both Git object identities, and reports missing objects without
implicitly fetching. `ReplayFetchPullRequestObjects(callback, source_id,
confirmed)` fetches only verified, Replay-namespaced refs and refuses to run
without explicit user confirmation. The real PR diff is produced from the
original pinned merge base and target commit.

`ReplayResolveLocalBranch(callback, head, base)` resolves a locally present
feature branch without checking it out. Pass an empty base to prefer the
locally present `origin/HEAD` target, followed by `origin/main`,
`origin/master`, `main`, and `master`. Resolution pins both references and
computes their actual merge base; unrelated later changes on the default
branch are excluded. Neither source-resolution call creates a worktree.

Both resolution calls provide a durable sibling-worktree preview.
`ReplayCreateWorkspace(callback, source_id, confirmed)` creates the displayed
local scratch branch only after the reviewer explicitly confirms. Its response
contains a bounded presentation plan, complete original per-step unified
hunks, hunk-local original source images, and editable scratch-file identities.
Full scratch-file images remain in editor-owned buffers; a large file is never
copied into every plugin-visible step. `ReplayFocusStepSource(workspace_id,
step_id)` switches the existing source window to the exact scratch file for a
multi-file step without turning the dedicated guide into an editor buffer.
`ReplayToggleZoom(workspace_id)` temporarily enlarges the focused Replay guide
or its verified scratch-source window, then restores the exact original pane
geometry when called again. It does not change source buffers, create a split,
or write to a review workspace.

`ReplayActiveSession(callback)` returns the authoritative recovered source,
bounded original presentation, selected hunk, learning mode, completed
exercises, private observations, authenticated review role, exact original PR
head, and local review outbox. A GitHub review is classified as `author` only
when a separate, bounded, read-only GraphQL response verifies that the
authenticated viewer matches the author of the exact same repository, pull
request, head branch, and immutable head commit. Otherwise it remains
`reviewer`; repository write permission alone never grants author authority.
The bundled coach requests this snapshot on `editor:ready`, so `--resume`
restores the dedicated guide and local drafts without fetching, creating
another worktree, or exposing reusable application tokens.
`ReplayListReviews(callback)` discovers editor-owned and safely identified
legacy scratch reviews in a bounded background worker, returning provenance,
completion, note counts, and unsaved or active state without exposing source
buffers. `ReplayResumeReview(callback, review_id)` rechecks the selected
snapshot, immutable source, and exact original scratch worktree before opening
its guide. Reopening never creates a branch or discards unrelated dirty buffers.
`ReplayRegenerateReview(callback, workspace_id)` rebuilds the current review's
derived source presentation in a bounded background worker without changing its
pinned source, scratch buffers, completion, notes, drafts, or receipts.
`ReplayRestartReview(callback, workspace_id, preview_digest, confirmed)` first
returns a content-pinned preview when `confirmed` is false. Only an explicitly
confirmed request with the identical preview digest recreates the independently
verified Replay scratch worktree and discards its local review generation. The
original repository branch, author worktree, and published provider reviews are
never removal targets. Uncertain review submissions must be reconciled first.
`ReplayAddNote(callback, workspace_id, step_id, category, text)` validates and
stores a reviewer observation against the exact original author commit and
source hunk.

`ReplayAddDraft(callback, workspace_id, step_id, kind, text)` creates a durable
local review outcome. An `inline_comment` is anchored to the original changed
path, full head commit, hunk digest, original GitHub `left` or `right` diff
side, and exact one-based changed-line range. A `code_fix` receives the same
source anchor and is accepted only for the verified original author; it records
a proposal and does not modify the PR branch. A `review_summary` uses an empty
`step_id` and never claims inline coordinates. `ReplayUpdateDraft(callback,
workspace_id, draft_id, text)` preserves the original anchor while editing a
local draft. `ReplayRemoveDraft(callback, workspace_id, draft_id)` removes only
the specified local draft. All draft mutations advance recoverable editor
state, enforce bounded reviewer text, and reject foreign or stale original
hunks.

`ReplayAgentStart(workspace_id, step_id, scope, prompt)` starts an isolated
Codex turn owned by the exact Replay session. The `current_change` and
`pull_request` scopes stream direct, private answers into the dedicated Replay
pane. The explicitly selected `inline_comment` and `review_summary` scopes
instead generate review-draft suggestions. All four scopes are enforced as
read-only: their dynamic-tool host rejects source proposals and editor
mutations, and generated text remains transient. Only the reviewer's explicit
decision to promote and accept an answer or suggestion may call
`ReplayAcceptAgentDraft(callback, workspace_id, step_id, kind, text)`, which
creates an original-source-anchored local draft marked with `agent` provenance.
PR-level summaries pass an empty step identity. Agent-generated source fixes
cannot enter the comment outbox.

The `author_fix` scope is available only to the verified original GitHub PR
author after the exact, separately confirmed original-head worktree has been
opened. Codex may inspect the whole repository and stage normal reviewable
source proposals in that worktree, but it cannot write source files directly.
`ReplayAgentOpenProposals(workspace_id, session_id)` verifies the same original
head and opens Red's existing per-hunk agent approval surface without creating
a conversation pane. Accepting a hunk creates an ordinary undoable editor
transaction; saving, committing, pushing, and GitHub review submission never
happen automatically.

`ReplaySetMode(callback, workspace_id, mode)` records the selected Challenge or
Snippet mode in the same editor-owned session. These additive calls belong to
the unreleased source-backed `0.5.1` contract.

Automatic step application remains a single revision- and pre-image-checked
editor transaction; `a` requires no additional modal, and Replay undo refuses
to discard newer reviewer-authored changes. After a successful Replay undo, the
editor sends `replay:undone` with the original workspace and step identities,
allowing the coach to distinguish restored source from a new manual edit. No
Replay host call automatically
saves, commits, pushes, posts a comment, creates a GitHub pending review, or
submits a GitHub review. Cross-computer draft saving, GitHub review publication,
and agent-generated suggestions each have their own explicit human approval
boundary; committing or pushing an original author worktree is never an
implicit effect of the local outbox or proposal acceptance.

Plugins requiring these additive source-backed calls should declare
`"red_api_version": "^0.5.1"`.

## Workspace file operations

`FileOperation(callback: fn(Json), operation: Json)` applies a structured filesystem
Expand Down Expand Up @@ -70,7 +246,7 @@ effective configuration. Existing two-argument registrations continue to work.
New pickers should use
`OpenPicker(title: String, items: [PickerItem], options: PickerOptions, handlers: PickerHandlers)`.
The host returns an opaque integer handle that may be passed to `UpdatePickerItems`,
`UpdatePickerQuery`, `UpdatePickerStatus`, and `ClosePicker`. Plugins
`UpdatePickerQuery`, `UpdatePickerStatus`, `UpdatePickerBusy`, and `ClosePicker`. Plugins
must not assign or interpret this handle.

```husk
Expand All @@ -90,13 +266,17 @@ that picker before invoking the terminal callback. Closing or replacing the dial
reloading its plugin, or unloading its plugin also releases the handlers. Stale handles
are ignored.

Set `busy: true` in `PickerOptions` to display an animated Braille spinner before the
picker status. Call `UpdatePickerBusy(handle, false)` when the asynchronous operation
finishes; the editor owns spinner timing and redraws, so plugins do not need timers.

Callbacks are retained by the runtime and delivered only to the plugin that opened the
picker. They do not use global `picker:*:<id>` subscriptions. Picker items and callback
payloads use the declared `PickerItem`, `PickerCancelled`, and `PickerActionEvent` records;
the `PickerItem.data` field remains `Json` so a plugin can attach its own payload.

`OpenPicker` was added in host API `0.3.0`. Plugins targeting this Red release should
declare `"red_api_version": "^0.4.0"`. The numeric-ID `OpenDynamicPicker` API remains
declare `"red_api_version": "^0.5.0"`. The numeric-ID `OpenDynamicPicker` API remains
available for compatibility, but new plugins should not use it.

## Agent composer
Expand Down
Loading
Loading