feat: implement pre-cognitive agent with shadow worktrees#6
feat: implement pre-cognitive agent with shadow worktrees#6OctavianTocan wants to merge 3 commits intomainfrom
Conversation
- Replaced 140+ `gh` CLI subprocess calls with native `fetch` via GitHub API - Added in-memory OAuth token caching via `gh auth token` - Added local SQLite persistence using `bun:sqlite` for zero-latency PR lists - Optimized React rendering cascade with `useDeferredValue` and `React.memo` - Implemented `PanelSkeleton` for smooth, tab-aware loading transitions - Batched GraphQL PR details queries into chunks to prevent rate limits
Reviewer's GuideRefactors GitHub integration away from the gh CLI to direct REST/GraphQL calls, adds a local SQLite-backed cache and background daemon that generates/apply AI fixes via shadow git worktrees, upgrades the ls UI to prioritize auto-fixable PRs and unify Space as the main action key, and introduces split-branch visualization, semantic diff grouping, and richer panel skeletons/perf tweaks. Sequence diagram for background daemon generating AI fixes in shadow worktreessequenceDiagram
participant Ls as LsCommand
participant D as runBackgroundDaemon
participant Cache as PRCache
participant DB as SQLite
participant GH as GitHub_APIs
participant Shadow as shadow_worktree
participant AI as generateFix
participant Repo as RepoRoot
Ls->>D: runBackgroundDaemon(prs, detailsMap, onFixGenerated)
activate D
D->>Repo: getRepoRoot()
Note over D,Repo: If no repoRoot, daemon exits
loop for each PR
D->>Cache: details = detailsMap.get(pr.url)
alt has details
D->>D: state = detectPRState(pr, details)
alt state == FIX_REVIEW
D->>GH: fetchReviewThreads(repo, prNumber)
GH-->>D: unresolvedThreads
loop each unresolved thread
D->>DB: existingFixes = getGeneratedFixes(pr.url)
alt no existing fix for thread
D->>Shadow: wtDir = prepareShadowWorktree(repoRoot, pr.number)
Shadow-->>D: wtDir
D->>AI: fix = generateFix(thread, wtDir)
alt fix generated
D->>DB: cacheGeneratedFix(pr.url, thread.id, fix)
D->>Ls: onFixGenerated(pr.url, thread.id)
end
end
end
else state == FIX_CI
D->>DB: existingFixes = getGeneratedFixes(pr.url)
alt no existing CI fix
D->>Shadow: wtDir = prepareShadowWorktree(repoRoot, pr.number)
Shadow-->>D: wtDir
D->>AI: generateCIFix(pr, wtDir)
AI-->>D: ciFix or null
alt ciFix
D->>DB: cacheGeneratedFix(pr.url, "ci", ciFix)
D->>Ls: onFixGenerated(pr.url, "ci")
end
end
end
end
end
deactivate D
Sequence diagram for Space key applying an AI fix via shadow worktreesequenceDiagram
actor User
participant Ls as LsCommand
participant DB as SQLite
participant Fix as applyFix
participant Shadow as shadow_worktree
participant Git as git
participant GH as GitHub_remote
participant Repo as Repo
participant FS as FileSystem
participant Clipboard
User->>Ls: press Space on selected PR
Ls->>DB: selectedFixes = getGeneratedFixes(pr.url)
alt has auto-fix
Ls->>Ls: showFlash("Applying AI fix...")
Ls->>Repo: root = getRepoRoot()
alt root found
Ls->>Fix: applyFix(fix, pr, root)
activate Fix
Fix->>Shadow: wtDir = prepareShadowWorktree(root, pr.number)
Shadow-->>Fix: wtDir
Fix->>FS: write modifiedContent to wtDir/path
Fix->>Git: git add path (cwd=wtDir)
Fix->>Git: git commit -m "fix: address review comment..." (cwd=wtDir)
Fix->>Git: git push origin raft-shadow/pr-n:headRefName (cwd=wtDir)
Git-->>GH: update PR branch
Fix->>Shadow: cleanupShadowWorktree(root, pr.number)
deactivate Fix
Ls->>DB: clearGeneratedFix(pr.url, fix.threadId)
Ls->>Ls: increment fixUpdateTrigger
Ls->>Ls: showFlash("Fix pushed to PR branch!")
else no repo root
Ls->>Ls: showFlash("Error: Not in a local git repo.")
end
else no auto-fix
alt lifecycle MERGE_NOW
User->>Ls: Space
Ls->>Git: runGhMerge(repo, prNumber)
else lifecycle FIX_REVIEW
Ls->>Ls: open panel code tab
else lifecycle PING_REVIEWERS
Ls->>Clipboard: copy PR URL
else
Ls->>Ls: move selection to next PR
end
end
ER diagram for new SQLite cache and generated fixes tableserDiagram
pull_requests {
TEXT url PK
JSON data
DATETIME updated_at
}
pr_details {
TEXT url PK
JSON data
DATETIME updated_at
}
pr_panel_data {
TEXT url PK
JSON data
DATETIME updated_at
}
generated_fixes {
TEXT id PK
TEXT pr_url
TEXT thread_id
JSON data
DATETIME updated_at
}
pull_requests ||--|| pr_details : same_pr_url
pull_requests ||--|| pr_panel_data : same_pr_url
pull_requests ||--o{ generated_fixes : has_fixes
Class diagram for caching, daemon, shadow worktrees, and split stateclassDiagram
class PRCache {
-details Map~string, PRDetails~
-panelData Map~string, PRPanelData~
+getDetails(url string) PRDetails
+setDetails(url string, data PRDetails) void
+hasDetails(url string) boolean
+getPanelData(url string) PRPanelData
+setPanelData(url string, data PRPanelData) void
+hasPanelData(url string) boolean
}
class DBModule {
+db Database
+getCachedPRs() PullRequest[]
+cachePRs(prs PullRequest[]) void
+getCachedPRDetails(url string) PRDetails
+cachePRDetails(url string, details PRDetails) void
+getCachedPRPanelData(url string) PRPanelData
+cachePRPanelData(url string, panelData PRPanelData) void
+getGeneratedFixes(prUrl string) any[]
+cacheGeneratedFix(prUrl string, threadId string, fix any) void
+clearGeneratedFix(prUrl string, threadId string) void
}
class DaemonModule {
+runBackgroundDaemon(prs PullRequest[], detailsMap Map~string, PRDetails~, onFixGenerated function) void
}
class ShadowModule {
+prepareShadowWorktree(repoRoot string, prNumber number) string
+cleanupShadowWorktree(repoRoot string, prNumber number) void
}
class AIFixModule {
+applyFix(fix ProposedFix, pr PullRequest, repoRoot string) void
+generateFix(thread ReviewThread, wtDir string) ProposedFix
}
class GithubAuth {
-cachedToken string
+getGithubToken() Promise~string~
}
class GithubLib {
+fetchGh(endpoint string, options RequestInit) any
+fetchGhGraphql(query string, variables any) any
+parseSearchResults(items any[]) PullRequest[]
+fetchOpenPRs(author string, onProgress function) PullRequest[]
+fetchRepoPRs(repo string) PullRequest[]
+updatePRTitle(repo string, prNumber number, title string) void
+fetchPRDetails(repo string, prNumber number) PRDetails
+batchFetchPRDetails(prs any[]) Map~string, PRDetails~
+fetchPRPanelData(repo string, prNumber number) PRPanelData
+fetchReviewThreads(repo string, prNumber number) ReviewThread[]
+resolveReviewThread(threadId string) void
+fetchCIStatus(repo string, ref string) string
+fetchHasConflicts(repo string, prNumber number) boolean
+getCurrentRepo() string
}
class SplitEntry {
+number number
+name string
+branch string
+files string[]
+lines number
+dependsOn number[]
+prNumber number
+prUrl string
+status SplitEntryStatus
}
class SplitState {
+version number
+originalBranch string
+targetBranch string
+strategy string
+createdAt string
+status SplitPhase
+topology string
+splits SplitEntry[]
}
class SplitStateModule {
+readSplitState(repoRoot string) SplitState
+writeSplitState(repoRoot string, state SplitState) void
+formatSplitTopology(state SplitState) string[]
}
class SplitCommand {
+SplitCommand(props SplitCommandProps)
}
class SplitCommandProps {
+repo string
}
class DiffUtils {
+getFileIntent(filename string) FileIntent
}
class FileIntent
PRCache --> DBModule : uses
DaemonModule --> PRCache : reads_writes
DaemonModule --> ShadowModule : manages_worktrees
DaemonModule --> AIFixModule : generateFix
DaemonModule --> DBModule : cacheGeneratedFix
AIFixModule --> ShadowModule : prepare_cleanup
AIFixModule --> GithubLib : uses_PR_metadata
GithubLib --> GithubAuth : uses_token
SplitCommand --> SplitStateModule : read_split_state
SplitStateModule --> SplitState : constructs
SplitState --> SplitEntry : aggregates
DiffUtils ..> FileIntent : defines
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. ℹ️ Review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (22)
✨ Finishing Touches🧪 Generate unit tests (beta)
📝 Coding Plan
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Hey - I've found 3 issues, and left some high level feedback:
- The background daemon and fix-generation paths create shadow worktrees via
prepareShadowWorktreebut never clean them up on completion or error (onlyapplyFixcallscleanupShadowWorktree); consider ensuring every daemon path (includinggenerateFix/generateCIFix) pairs worktree creation with cleanup to avoid accumulating stale worktrees/branches. - There are now two separate implementations of
getRepoRoot(ingit-utils.tsand inline insplit.tsx); it would be cleaner and less error-prone to reuse the shared helper instead of spawninggit rev-parsein multiple places.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The background daemon and fix-generation paths create shadow worktrees via `prepareShadowWorktree` but never clean them up on completion or error (only `applyFix` calls `cleanupShadowWorktree`); consider ensuring every daemon path (including `generateFix`/`generateCIFix`) pairs worktree creation with cleanup to avoid accumulating stale worktrees/branches.
- There are now two separate implementations of `getRepoRoot` (in `git-utils.ts` and inline in `split.tsx`); it would be cleaner and less error-prone to reuse the shared helper instead of spawning `git rev-parse` in multiple places.
## Individual Comments
### Comment 1
<location path="src/lib/daemon.ts" line_range="44-45" />
<code_context>
+
+ // Generate fix
+ try {
+ const wtDir = await prepareShadowWorktree(repoRoot, pr.number);
+ const fix = await generateFix(thread, wtDir);
+ if (fix) {
+ cacheGeneratedFix(pr.url, thread.id, fix);
</code_context>
<issue_to_address>
**issue (bug_risk):** Shadow worktrees created by the daemon are never cleaned up, which can leak worktrees/branches over time.
This code calls `prepareShadowWorktree` for every fix generation but never calls `cleanupShadowWorktree`, so `raft-shadow/pr-*` branches and worktrees will accumulate on disk as more PRs/threads are processed. Since `applyFix` already has its own prepare/cleanup cycle, consider either reusing a shared worktree instead of creating new branches each time, or explicitly invoking `cleanupShadowWorktree` when a PR’s processing (success or failure) is complete.
</issue_to_address>
### Comment 2
<location path="src/lib/ai-fix.ts" line_range="137" />
<code_context>
+export async function applyFix(fix: ProposedFix, pr: PullRequest, repoRoot: string): Promise<void> {
+ const wtDir = await prepareShadowWorktree(repoRoot, pr.number)
+
+ const filePath = `${wtDir}/${fix.path}`
await Bun.write(filePath, fix.modifiedContent)
+
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Prefer path.join over manual string concatenation for building file paths.
The hard-coded `/` assumes POSIX paths and may break on platforms like Windows or if `fix.path` includes leading slashes. Using `path.join(wtDir, fix.path)` (or an equivalent helper) will be more robust and portable.
Suggested implementation:
```typescript
import * as path from "node:path"
import { safeSpawn, buildCleanEnv } from "./process"
```
```typescript
const wtDir = await prepareShadowWorktree(repoRoot, pr.number)
const filePath = path.join(wtDir, fix.path)
```
</issue_to_address>
### Comment 3
<location path="src/commands/split.tsx" line_range="11-18" />
<code_context>
+ repo?: string
+}
+
+async function getRepoRoot(): Promise<string | null> {
+ const proc = Bun.spawn(["git", "rev-parse", "--show-toplevel"], {
+ stdout: "pipe",
+ stderr: "pipe",
+ })
+ const stdout = await new Response(proc.stdout).text()
+ const code = await proc.exited
+ return code === 0 ? stdout.trim() : null
+}
+
</code_context>
<issue_to_address>
**suggestion:** Avoid duplicating repo-root discovery logic that already exists in git-utils.
Please use the existing `getRepoRoot` helper in `lib/git-utils.ts` instead of reimplementing this. It already wraps this command via `safeSpawn` and provides consistent environment and error handling.
Suggested implementation:
```typescript
import { useState, useEffect, useCallback } from "react"
import { useKeyboard, useRenderer } from "@opentui/react"
import { Spinner } from "../components/spinner"
import { readSplitState, formatSplitTopology } from "../lib/split-state"
import { getRepoRoot } from "../lib/git-utils"
```
```typescript
interface SplitCommandProps {
repo?: string
}
```
If the `getRepoRoot` exported from `../lib/git-utils` has a different signature (e.g. accepts a `cwd` parameter or returns a non-nullable string), adjust the call sites in this file accordingly to pass the appropriate arguments and handle its return type consistently with the rest of the codebase.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| const wtDir = await prepareShadowWorktree(repoRoot, pr.number); | ||
| const fix = await generateFix(thread, wtDir); |
There was a problem hiding this comment.
issue (bug_risk): Shadow worktrees created by the daemon are never cleaned up, which can leak worktrees/branches over time.
This code calls prepareShadowWorktree for every fix generation but never calls cleanupShadowWorktree, so raft-shadow/pr-* branches and worktrees will accumulate on disk as more PRs/threads are processed. Since applyFix already has its own prepare/cleanup cycle, consider either reusing a shared worktree instead of creating new branches each time, or explicitly invoking cleanupShadowWorktree when a PR’s processing (success or failure) is complete.
| export async function applyFix(fix: ProposedFix, pr: PullRequest, repoRoot: string): Promise<void> { | ||
| const wtDir = await prepareShadowWorktree(repoRoot, pr.number) | ||
|
|
||
| const filePath = `${wtDir}/${fix.path}` |
There was a problem hiding this comment.
suggestion (bug_risk): Prefer path.join over manual string concatenation for building file paths.
The hard-coded / assumes POSIX paths and may break on platforms like Windows or if fix.path includes leading slashes. Using path.join(wtDir, fix.path) (or an equivalent helper) will be more robust and portable.
Suggested implementation:
import * as path from "node:path"
import { safeSpawn, buildCleanEnv } from "./process" const wtDir = await prepareShadowWorktree(repoRoot, pr.number)
const filePath = path.join(wtDir, fix.path)| async function getRepoRoot(): Promise<string | null> { | ||
| const proc = Bun.spawn(["git", "rev-parse", "--show-toplevel"], { | ||
| stdout: "pipe", | ||
| stderr: "pipe", | ||
| }) | ||
| const stdout = await new Response(proc.stdout).text() | ||
| const code = await proc.exited | ||
| return code === 0 ? stdout.trim() : null |
There was a problem hiding this comment.
suggestion: Avoid duplicating repo-root discovery logic that already exists in git-utils.
Please use the existing getRepoRoot helper in lib/git-utils.ts instead of reimplementing this. It already wraps this command via safeSpawn and provides consistent environment and error handling.
Suggested implementation:
import { useState, useEffect, useCallback } from "react"
import { useKeyboard, useRenderer } from "@opentui/react"
import { Spinner } from "../components/spinner"
import { readSplitState, formatSplitTopology } from "../lib/split-state"
import { getRepoRoot } from "../lib/git-utils"interface SplitCommandProps {
repo?: string
}If the getRepoRoot exported from ../lib/git-utils has a different signature (e.g. accepts a cwd parameter or returns a non-nullable string), adjust the call sites in this file accordingly to pass the appropriate arguments and handle its return type consistently with the rest of the codebase.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1497779ff9
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const CHUNK_SIZE = 20; | ||
| for (let i = 0; i < prs.length; i += CHUNK_SIZE) { | ||
| const chunk = prs.slice(i, i + CHUNK_SIZE); | ||
| let query = "query {\\n"; |
There was a problem hiding this comment.
Build GraphQL batch query with real newlines
The query prefix is initialized as "query {\\n", which inserts a literal backslash-n sequence into the GraphQL document rather than an actual newline. GraphQL parsers treat \ outside strings as invalid syntax, so batchFetchPRDetails can fail for every chunk; because the error is caught and only logged later, callers silently get empty detail maps and lose lifecycle/urgency data.
Useful? React with 👍 / 👎.
| await safeSpawn(["git", "add", fix.path], { cwd: wtDir }) | ||
| await safeSpawn(["git", "commit", "-m", commitMsg], { cwd: wtDir }) |
There was a problem hiding this comment.
Propagate git command failures when applying fixes
applyFix awaits safeSpawn but never checks exitCode; safeSpawn returns non-zero exits instead of throwing. If git add/commit/push fails (e.g., empty commit, auth failure, protected branch), this function still resolves and the UI reports success, which can falsely mark a fix as applied and clear queued fixes even though nothing was pushed.
Useful? React with 👍 / 👎.
| // Assuming 'origin' is the remote name. In a more robust setup we might need to parse remote. | ||
| // But for now, we'll fetch from origin or try to fetch the PR head directly. | ||
| try { | ||
| await safeSpawn(["git", "fetch", "origin", `pull/${prNumber}/head:${branchName}`], { cwd: repoRoot }); |
There was a problem hiding this comment.
Validate git fetch/worktree exit status in shadow setup
prepareShadowWorktree assumes safeSpawn throws on command failure and wraps calls in try/catch, but non-zero exits are returned in-band. A failed git fetch or git worktree add can therefore be treated as success, causing the daemon to proceed with a non-existent or stale worktree path and generate no fixes without surfacing the real git error.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Pull request overview
This PR introduces a background “pre-cognitive” workflow that provisions shadow git worktrees for PRs, generates AI fixes in the background, and integrates persistent caching + UI affordances to prioritize and apply those fixes. It also refactors GitHub integration away from gh JSON output toward direct REST/GraphQL API calls using an auth token sourced from gh auth token.
Changes:
- Add shadow worktree management + a background daemon that generates and caches AI fixes.
- Replace most
ghCLI-based GitHub data fetching with REST/GraphQL fetch helpers + token auth. - Add SQLite-backed caching and update UI/commands (Decision Queue behavior, split view, panel skeletons, grouped files, Space action).
Reviewed changes
Copilot reviewed 22 out of 22 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| src/lib/split-state.ts | Adds split-branch state model + formatting helpers |
| src/lib/shadow.ts | Implements shadow worktree fetch/add/remove helpers |
| src/lib/github.ts | Replaces gh CLI execution with REST/GraphQL fetch helpers |
| src/lib/git-utils.ts | Adds getRepoRoot() helper |
| src/lib/diff-utils.ts | Adds file “intent” classification for grouping |
| src/lib/db.ts | Adds SQLite persistence for PRs/details/panel data/fixes |
| src/lib/daemon.ts | Adds background daemon to generate/cached fixes |
| src/lib/cache.ts | Extends PRCache to read/write through SQLite |
| src/lib/auth.ts | Adds GitHub token retrieval via gh auth token |
| src/lib/ai-fix.ts | Applies fixes via shadow worktree commit + push |
| src/lib/tests/github.test.ts | Updates parseSearchResults tests for REST shape |
| src/lib/tests/cache.test.ts | Adjusts test to account for persistent cache |
| src/index.tsx | Adds split command entry |
| src/hooks/usePanel.ts | Tightens state setter typings |
| src/components/status-view.tsx | Surfaces “Auto-Fix Ready” and Space hint |
| src/components/skeleton.tsx | Adds panel skeleton content |
| src/components/preview-panel.tsx | Uses skeleton while loading panel data |
| src/components/pr-table.tsx | Memoizes PRRow |
| src/components/panel-files.tsx | Groups files by intent + boilerplate toggle |
| src/commands/split.tsx | New split state UI command |
| src/commands/ls.tsx | Adds caching, daemon, fix prioritization, Space action |
| src/tests/integration.test.ts | Updates target repo for integration tests |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| export function writeSplitState(repoRoot: string, state: SplitState): void { | ||
| const path = `${repoRoot}/.raft-split.json` | ||
| Bun.write(path, JSON.stringify(state, null, 2) + "\n") |
| // Initialize database in ~/.config/raft/raft.sqlite | ||
| const configDir = join(homedir(), ".config", "raft"); | ||
| mkdirSync(configDir, { recursive: true }); | ||
|
|
||
| const dbPath = join(configDir, "raft.sqlite"); |
|
|
||
| interface SplitCommandProps { | ||
| repo?: string | ||
| } | ||
|
|
||
| async function getRepoRoot(): Promise<string | null> { | ||
| const proc = Bun.spawn(["git", "rev-parse", "--show-toplevel"], { | ||
| stdout: "pipe", | ||
| stderr: "pipe", | ||
| }) | ||
| const stdout = await new Response(proc.stdout).text() | ||
| const code = await proc.exited | ||
| return code === 0 ? stdout.trim() : null | ||
| } | ||
|
|
| // We can get the PR's remote branch using the GitHub API or the PR metadata. | ||
| // The PR object has `headRefName` which is the remote branch name. | ||
| // Let's assume the remote is 'origin'. | ||
| const branchName = `raft-shadow/pr-${pr.number}` |
| const wtDir = await prepareShadowWorktree(repoRoot, pr.number) | ||
|
|
||
| const filePath = `${wtDir}/${fix.path}` | ||
| await Bun.write(filePath, fix.modifiedContent) | ||
|
|
||
| const commitMsg = `fix: address review comment by @${fix.reviewer}\n\nComment: ${fix.comment}` | ||
|
|
||
| await safeSpawn(["git", "add", fix.path], { cwd: wtDir }) | ||
| await safeSpawn(["git", "commit", "-m", commitMsg], { cwd: wtDir }) | ||
|
|
||
| // The branch name is raft-shadow/pr-{number} | ||
| // We need to push this to the PR's actual remote branch. | ||
| // We can get the PR's remote branch using the GitHub API or the PR metadata. | ||
| // The PR object has `headRefName` which is the remote branch name. | ||
| // Let's assume the remote is 'origin'. | ||
| const branchName = `raft-shadow/pr-${pr.number}` | ||
| await safeSpawn(["git", "push", "origin", `${branchName}:${pr.headRefName}`], { cwd: wtDir }) | ||
|
|
||
| await cleanupShadowWorktree(repoRoot, pr.number) |
| onProgress?.("Fetching PRs..."); | ||
| const json = await fetchGh("search/issues?q=is:pr+is:open+author:@me&per_page=100"); | ||
| return parseSearchResults(json.items); | ||
| } |
| // Ensure .raft-shadow exists and is ignored | ||
| try { | ||
| await Bun.write(join(shadowDir, ".gitignore"), "*\n"); | ||
| } catch {} |
| try { | ||
| const wtDir = await prepareShadowWorktree(repoRoot, pr.number); | ||
| const fix = await generateCIFix(pr, wtDir); | ||
| if (fix) { |
| const urgencyMap = useMemo(() => { | ||
| const map = new Map<string, number>() | ||
| for (const pr of filteredPRs) { | ||
| const details = detailsMap.get(pr.url) ?? null | ||
| const state = detectPRState(pr, details) | ||
| let urgency = state.urgency | ||
|
|
||
| // Auto-fixes are the absolute highest priority (urgency 110) | ||
| if (getGeneratedFixes(pr.url).length > 0) { | ||
| urgency = 110 | ||
| } | ||
|
|
||
| map.set(pr.url, urgency) | ||
| } | ||
| return map | ||
| }, [filteredPRs, detailsMap, fixUpdateTrigger]) |
| // Fetch the PR head | ||
| // Assuming 'origin' is the remote name. In a more robust setup we might need to parse remote. | ||
| // But for now, we'll fetch from origin or try to fetch the PR head directly. | ||
| try { | ||
| await safeSpawn(["git", "fetch", "origin", `pull/${prNumber}/head:${branchName}`], { cwd: repoRoot }); | ||
| } catch (e) { | ||
| // If origin fails, try upstream or just generic fetch? | ||
| // Let's assume origin for now. | ||
| throw new Error(`Failed to fetch PR ${prNumber} head: ${e}`); | ||
| } | ||
|
|
||
| // Check if worktree already exists | ||
| const { stdout: wtList } = await safeSpawn(["git", "worktree", "list"], { cwd: repoRoot }); | ||
| if (wtList.includes(wtDir)) { | ||
| // Worktree already exists, just reset it to the fetched branch | ||
| await safeSpawn(["git", "reset", "--hard", branchName], { cwd: wtDir }); |
Summary
.raft-shadow).Spacebaras the deterministically optimal action key (view/apply AI fixes, or squash & merge).Summary by Sourcery
Replace GitHub CLI shell integration with direct GitHub API usage, add a local SQLite-backed cache and background daemon for AI-generated fixes, and extend the UI with split-branch visualization and richer PR browsing controls.
New Features:
Enhancements:
Tests: