From 7d85e84d17e4e53f6a68d2f5fd77564fb68152da Mon Sep 17 00:00:00 2001 From: mkorwel Date: Fri, 13 Mar 2026 19:03:30 -0700 Subject: [PATCH] refactor: rename deep-review to offload and generalize capabilities --- .gemini/skills/deep-review/README.md | 150 ------------------ .gemini/skills/deep-review/SKILL.md | 56 ------- .gemini/skills/offload/README.md | 76 +++++++++ .gemini/skills/offload/SKILL.md | 37 +++++ .../{deep-review => offload}/policy.toml | 0 .../{deep-review => offload}/scripts/check.ts | 0 .../{deep-review => offload}/scripts/clean.ts | 0 .../scripts/entrypoint.ts | 0 .../scripts/orchestrator.ts} | 70 ++++---- .../{deep-review => offload}/scripts/setup.ts | 0 .../scripts/worker.ts | 57 ++++--- .../tests/orchestration.test.ts | 60 ++++--- package.json | 8 +- 13 files changed, 222 insertions(+), 292 deletions(-) delete mode 100644 .gemini/skills/deep-review/README.md delete mode 100644 .gemini/skills/deep-review/SKILL.md create mode 100644 .gemini/skills/offload/README.md create mode 100644 .gemini/skills/offload/SKILL.md rename .gemini/skills/{deep-review => offload}/policy.toml (100%) rename .gemini/skills/{deep-review => offload}/scripts/check.ts (100%) rename .gemini/skills/{deep-review => offload}/scripts/clean.ts (100%) rename .gemini/skills/{deep-review => offload}/scripts/entrypoint.ts (100%) rename .gemini/skills/{deep-review/scripts/review.ts => offload/scripts/orchestrator.ts} (57%) rename .gemini/skills/{deep-review => offload}/scripts/setup.ts (100%) rename .gemini/skills/{deep-review => offload}/scripts/worker.ts (58%) rename .gemini/skills/{deep-review => offload}/tests/orchestration.test.ts (86%) diff --git a/.gemini/skills/deep-review/README.md b/.gemini/skills/deep-review/README.md deleted file mode 100644 index e517be4b76..0000000000 --- a/.gemini/skills/deep-review/README.md +++ /dev/null @@ -1,150 +0,0 @@ -# Deep review maintainer skill - -The deep review skill provides a high-performance, parallelized workflow for -reviewing pull requests on a remote workstation. It leverages a Node.js -orchestrator to offload intensive builds and automated testing to parallel -background processes, showing progress in a dedicated terminal window. - -## Why use deep review? - -Standard code reviews are often constrained by the serial nature of human-AI -interaction and local machine performance. This skill addresses those bottlenecks -by implementing a "Remote Offloading" and "Parallel Verification" strategy. - -- **Protect local resources**: Heavy build and lint suites are offloaded to a - beefy remote workstation, keeping your local machine responsive for multi-tasking. -- **Context efficiency**: The main Gemini session remains interactive and focused - on high-level reasoning. You don't have to wait for a build to finish before - asking your next question. -- **True parallelism**: Infrastructure validation (build), CI checks, static - analysis, and behavioral proofs run simultaneously in an external window. - This compresses a 15-minute sequential process into a 3-minute parallel one. -- **Behavioral verification**: Instead of just reading code, the remote worker - physically exercises the new features in a live environment, providing - empirical proof that the code actually works before you approve it. - -## Scenarios and workflows - -The following scenarios outline how to use the deep review skill effectively. - -### First-time setup - -Before running your first deep review, you must configure your remote -workstation. - -1. Run the setup command: - ```bash - npm run review:setup - ``` -2. Follow the interactive prompts to specify: - - **Remote SSH Host**: The alias for your remote machine (default: `cli`). - - **Remote Work Directory**: Where PR worktrees will be provisioned. - - **Identity Synchronization**: Whether to mirror your local `.gemini` - credentials to the remote host. - - **Terminal Automation**: Your preferred terminal emulator (for example, - `iterm2` or `terminal`). - -**Pre-existing environments**: If your remote host is already set up for Gemini -CLI development, you can point the **Remote Work Directory** to your existing -repository instance. The skill will detect your existing GitHub CLI and Gemini -CLI installations and use them directly without redundant setup. - -**Isolation and safety**: By default, the skill attempts to isolate its -configuration using a dedicated profile (`~/.gemini-deep-review`) on the remote -host. This ensures that the automated verification tasks do not impact your -primary Gemini CLI environment or machine state, following development best -practices. - -### Launching a review - -To start a deep review for a specific pull request, use the `review` script. - -1. Execute the review command with the PR number: - ```bash - npm run review - ``` -2. The orchestrator performs the following actions: - - Fetches PR metadata and branch information. - - Synchronizes the latest review scripts to your remote host. - - Mirrors your local environment and credentials (if configured). - - Opens a new terminal window and connects to a remote `tmux` session. - -### Remote parallel execution - -Once the remote session starts, the worker process automatically provisions a -blobless git clone for the PR and launches four tasks in parallel: - -- **Fast Build**: Executes `npm ci && npm run build` to prepare the environment. -- **CI Checks**: Monitors the status of GitHub Actions for the PR. -- **Gemini Analysis**: Performs a static analysis using the `/review-frontend` - command. -- **Behavioral Proof**: Exercises the new code in the terminal to verify - functionality. This task waits for the **Fast Build** to complete. - -You can monitor the live status and logs for all these tasks in the dedicated -terminal window. - -### Monitoring and synthesis - -While verification is running, your main Gemini CLI session remains interactive. - -1. To check progress from your local shell without switching windows: - ```bash - npm run review:check - ``` -2. Once all tasks show as **SUCCESS**, the remote session automatically - launches an interactive Gemini session. -3. Gemini CLI reads the captured logs from `.gemini/logs/review-/` - and presents a synthesized final assessment. - -### Cleanup - -To keep your remote workstation tidy, you can wipe old PR directories and kill -inactive sessions. - -1. Run the cleanup command: - ```bash - npm run review:clean - ``` -2. You can choose to perform a standard cleanup (remove worktrees) or a full - wipe of the remote work directory. - -## Best practices - -Adhere to these best practices when using the deep review skill. - -- **Trust the worker**: Avoid running manual builds or linting in your main - session. The parallel worker is more efficient and captures structured logs - for the agent to read later. -- **Verify behavior**: Pay close attention to the `test-execution.log`. It - provides the physical proof that the feature actually works in a live - environment. -- **Use isolated profiles**: The skill uses `~/.gemini-deep-review` on the - remote host to avoid interfering with your primary Gemini CLI configuration. - -## Technical details - -This skill uses an isolated Gemini profile on the remote host (`~/.gemini-deep-review`) to ensure that verification tasks do not interfere with your primary configuration. - -### Directory structure -- `scripts/review.ts`: Local orchestrator (syncs scripts and pops terminal). -- `scripts/worker.ts`: Remote engine (provisions worktree and runs parallel tasks). -- `scripts/check.ts`: Local status poller. -- `scripts/clean.ts`: Remote cleanup utility. -- `SKILL.md`: Instructional body used by the Gemini CLI agent. - -## Contributing - -If you want to improve this skill: -1. Modify the TypeScript scripts in `scripts/`. -2. Update `SKILL.md` if the agent's instructions need to change. -3. Test your changes locally using `npm run review `. - -## Testing - -The orchestration logic for this skill is fully tested. To run the tests: -```bash -npx vitest .gemini/skills/deep-review/tests/orchestration.test.ts -``` -These tests mock the external environment (SSH, GitHub CLI, and the file system) to ensure that the orchestration scripts generate the correct commands and handle environment isolation accurately. - diff --git a/.gemini/skills/deep-review/SKILL.md b/.gemini/skills/deep-review/SKILL.md deleted file mode 100644 index 4c95572393..0000000000 --- a/.gemini/skills/deep-review/SKILL.md +++ /dev/null @@ -1,56 +0,0 @@ -# Deep Review Skill - -This skill provides a high-performance, parallelized workflow for reviewing Pull Requests on a remote workstation. It leverages a Node.js orchestrator to offload intensive builds and automated testing to parallel background processes with a dedicated status dashboard in a separate terminal window. - -## Overview - -The Deep Review workflow follows a "Verify then Synthesize" pattern: -1. **Orchestration**: Launch the local Node.js orchestrator which handles remote synchronization and terminal automation. -2. **Parallel Verification**: Monitor the 5 parallel tasks (Build, CI Check, Static Analysis, Behavioral Testing, and Diagnostics) in the dedicated review window. -3. **Synthesis**: Interactively analyze the results generated by the worker to provide a final recommendation. - -## Workflows - -### 1. Setup (First-time use) -When the user wants to enable deep reviews or if the configuration is missing: -* **Action**: Execute the setup command locally: - `npm run review:setup` -* **Response**: The agent will interactively provision the remote workstation and save settings. - -### 2. Starting the Review -When the user asks to "Deep review" a PR: -* **Command**: `npm run review ` -* **Action**: This will sync scripts, provision a worktree, and pop a new terminal window. -* **Response**: Inform the user that the review is starting in a new window. -### 3. Monitoring and Synthesis -Once the review is launched, the agent should monitor its progress. -* **Action**: Execute the status check command locally: - `npm run review:check ` -* **Evaluation**: - * If tasks are `RUNNING`, tell the user the review is still in progress. - * If all tasks are `SUCCESS`, prepare for synthesis by reading the logs. - -#### Reading Results -To provide the final assessment, the agent must fetch the following files from the remote host using `ssh cli "cat ..."`: -* `review.md` (Static analysis findings). -* `build.log` (Build and runtime environment status). -* `test-execution.log` (Behavioral proof results). -* `ci.exit` (GitHub Check status). - -### 4. Cleanup -(existing content...) -When the user asks to "cleanup" or "start fresh" on the remote machine: -* **Action**: Execute the following cleanup command locally: - `npm run review:clean` -* **Response**: Confirm that the remote workstation (tmux sessions and worktrees) has been cleared based on the project settings. - -### 5. Final Recommendation -Provide a structured assessment based on the physical proof and logs: -* **Status**: Does it build? Does it pass CI? Did the behavioral tests prove the feature works? -* **Findings**: Categorize by Critical, Improvements, or Nitpicks. -* **Conclusion**: Approve or Request Changes. - -## Best Practices -* **Trust the Worker**: Do not re-run build or lint tasks manually in the main Gemini window; the parallel worker handles this more efficiently. -* **Be Behavioral**: Focus on the results in `test-execution.log` to prove the code actually works in a live environment. -* **Acknowledge the Window**: Remind the user they can see real-time progress and logs in the separate terminal window we opened for them. diff --git a/.gemini/skills/offload/README.md b/.gemini/skills/offload/README.md new file mode 100644 index 0000000000..91aba9da4e --- /dev/null +++ b/.gemini/skills/offload/README.md @@ -0,0 +1,76 @@ +# Offload maintainer skill + +The `offload` skill provides a high-performance, parallelized workflow for +offloading intensive developer tasks to a remote workstation. It leverages a +Node.js orchestrator to run complex validation playbooks concurrently in a +dedicated terminal window. + +## Why use offload? + +- **Protect local resources**: Heavy build and lint suites are offloaded to a + beefy remote workstation. +- **Context efficiency**: The main Gemini session remains interactive while + background tasks provide high-fidelity feedback. +- **True parallelism**: Infrastructure validation, CI checks, and behavioral + proofs run simultaneously, compressing a 15-minute process into 3 minutes. +- **Behavioral verification**: The worker physically exercises the new code + to provide empirical proof that it works. + +## Playbooks + +- **`review`** (default): Build, CI check, static analysis, and behavioral proofs. +- **`fix`**: Iterative fixing of CI failures and review comments. +- **`ready`**: Final full validation (clean install + preflight) before merge. +- **`open`**: Provision a worktree and drop directly into a remote tmux session. + +## Scenarios and workflows + +### First-time setup +Run the setup command once to configure your remote environment: +```bash +npm run offload:setup +``` + +### Offloading a task +To start an offload task for a pull request: +```bash +npm run offload [action] +``` + +### Monitoring progress +Check status from your local shell without switching windows: +```bash +npm run offload:check +``` + +### Cleanup +Wipe old PR worktrees and kill inactive sessions: +```bash +npm run offload:clean +``` + +## Technical details + +This skill uses an isolated Gemini profile on the remote host (`~/.gemini-deep-review`) to ensure that verification tasks do not interfere with your primary configuration. + +### Directory structure +- `scripts/orchestrator.ts`: Local orchestrator (syncs scripts and pops terminal). +- `scripts/worker.ts`: Remote engine (provisions worktree and runs playbooks). +- `scripts/check.ts`: Local status poller. +- `scripts/clean.ts`: Remote cleanup utility. +- `SKILL.md`: Instructional body used by the Gemini CLI agent. + +## Contributing + +If you want to improve this skill: +1. Modify the TypeScript scripts in `scripts/`. +2. Update `SKILL.md` if the agent's instructions need to change. +3. Test your changes locally using `npm run offload `. + +## Testing + +The orchestration logic for this skill is fully tested. To run the tests: +```bash +npx vitest .gemini/skills/offload/tests/orchestration.test.ts +``` +These tests mock the external environment (SSH, GitHub CLI, and the file system) to ensure that the orchestration scripts generate the correct commands and handle environment isolation accurately. diff --git a/.gemini/skills/offload/SKILL.md b/.gemini/skills/offload/SKILL.md new file mode 100644 index 0000000000..fdae14888a --- /dev/null +++ b/.gemini/skills/offload/SKILL.md @@ -0,0 +1,37 @@ +# Offload Skill + +This skill provides a high-performance, parallelized workflow for offloading intensive developer tasks (PR reviews, fixing CI, preparing merges) to a remote workstation. It leverages a Node.js orchestration engine to run complex validation playbooks concurrently in a dedicated terminal window. + +## Playbooks + +The `offload` skill supports the following specialized playbooks: + +- **`review`** (default): Clean build, CI status check, static analysis, and behavioral proofs. +- **`fix`**: Build + Log analysis of CI failures + Iterative Gemini-led fixing and pushing. +- **`ready`**: Final full validation (clean install, full preflight, and conflict checks). +- **`open`**: Provision a worktree and drop the user directly into a remote shell/tmux session. +- **`implement`**: Read an issue → Research → Implement → Verify → Create PR. + +## Workflow + +### 1. Initializing an Offload Task +When the user asks to offload a task (e.g., "Offload PR 123 fix" or "Make PR 123 ready"), use the `run_shell_command` tool to execute the orchestrator: +* **Command**: `npm run offload [action]` +* **Action**: This will sync scripts to the remote host, provision a worktree, and pop a new terminal window for the playbook dashboard. +* **Response**: Inform the user which playbook has been launched. + +### 2. Monitoring and Synthesis +The remote worker saves all results into `.gemini/logs/offload-/`. Once the playbook finishes, the agent should synthesize the results: +* Read logs corresponding to the playbook tasks (e.g., `build.log`, `review.md`, `test-execution.log`, `diagnostics.log`). +* Check the `.exit` files to confirm success of each parallel stage. + +### 3. Final Recommendation +Provide a structured assessment based on the physical proof and logs: +* **Status**: PASS / FAIL / NEEDS_WORK. +* **Findings**: Categorized by Critical, Improvements, or Nitpicks. +* **Conclusion**: A clear next step for the maintainer. + +## Best Practices +* **Isolation First**: Always respect the user's isolation choices (`~/.gemini-deep-review`). +* **Be Behavioral**: Prioritize results from live execution (behavioral proofs) over static reading. +* **Multi-tasking**: Remind the user they can continue chatting in the main window while the heavy offloaded task runs in the separate window. diff --git a/.gemini/skills/deep-review/policy.toml b/.gemini/skills/offload/policy.toml similarity index 100% rename from .gemini/skills/deep-review/policy.toml rename to .gemini/skills/offload/policy.toml diff --git a/.gemini/skills/deep-review/scripts/check.ts b/.gemini/skills/offload/scripts/check.ts similarity index 100% rename from .gemini/skills/deep-review/scripts/check.ts rename to .gemini/skills/offload/scripts/check.ts diff --git a/.gemini/skills/deep-review/scripts/clean.ts b/.gemini/skills/offload/scripts/clean.ts similarity index 100% rename from .gemini/skills/deep-review/scripts/clean.ts rename to .gemini/skills/offload/scripts/clean.ts diff --git a/.gemini/skills/deep-review/scripts/entrypoint.ts b/.gemini/skills/offload/scripts/entrypoint.ts similarity index 100% rename from .gemini/skills/deep-review/scripts/entrypoint.ts rename to .gemini/skills/offload/scripts/entrypoint.ts diff --git a/.gemini/skills/deep-review/scripts/review.ts b/.gemini/skills/offload/scripts/orchestrator.ts similarity index 57% rename from .gemini/skills/deep-review/scripts/review.ts rename to .gemini/skills/offload/scripts/orchestrator.ts index 9a85080b95..2dd8513441 100644 --- a/.gemini/skills/deep-review/scripts/review.ts +++ b/.gemini/skills/offload/scripts/orchestrator.ts @@ -1,5 +1,5 @@ /** - * Universal Deep Review Orchestrator (Local) + * Universal Offload Orchestrator (Local) */ import { spawnSync } from 'child_process'; import path from 'path'; @@ -13,8 +13,10 @@ const q = (str: string) => `'${str.replace(/'/g, "'\\''")}'`; export async function runOrchestrator(args: string[], env: NodeJS.ProcessEnv = process.env) { const prNumber = args[0]; + const action = args[1] || 'review'; // Default action is review + if (!prNumber) { - console.error('Usage: npm run review '); + console.error('Usage: npm run offload [action]'); return 1; } @@ -27,10 +29,10 @@ export async function runOrchestrator(args: string[], env: NodeJS.ProcessEnv = p let config = settings.maintainer?.deepReview; if (!config) { - console.log('⚠️ Deep Review configuration not found. Launching setup...'); - const setupResult = spawnSync('npm', ['run', 'review:setup'], { stdio: 'inherit' }); + console.log('⚠️ Offload configuration not found. Launching setup...'); + const setupResult = spawnSync('npm', ['run', 'offload:setup'], { stdio: 'inherit' }); if (setupResult.status !== 0) { - console.error('❌ Setup failed. Please run "npm run review:setup" manually.'); + console.error('❌ Setup failed. Please run "npm run offload:setup" manually.'); return 1; } // Reload settings after setup @@ -56,12 +58,12 @@ export async function runOrchestrator(args: string[], env: NodeJS.ProcessEnv = p const remotePolicyPath = `${ISOLATED_GEMINI}/policies/deep-review-policy.toml`; console.log(`📡 Mirroring environment to ${remoteHost}...`); - spawnSync('ssh', [remoteHost, `mkdir -p ${remoteWorkDir}/.gemini/skills/deep-review/scripts/ ${ISOLATED_GEMINI}/policies/`]); + spawnSync('ssh', [remoteHost, `mkdir -p ${remoteWorkDir}/.gemini/skills/offload/scripts/ ${ISOLATED_GEMINI}/policies/`]); // Sync the policy file specifically - spawnSync('rsync', ['-avz', path.join(REPO_ROOT, '.gemini/skills/deep-review/policy.toml'), `${remoteHost}:${remotePolicyPath}`]); + spawnSync('rsync', ['-avz', path.join(REPO_ROOT, '.gemini/skills/offload/policy.toml'), `${remoteHost}:${remotePolicyPath}`]); - spawnSync('rsync', ['-avz', '--delete', path.join(REPO_ROOT, '.gemini/skills/deep-review/scripts/'), `${remoteHost}:${remoteWorkDir}/.gemini/skills/deep-review/scripts/`]); + spawnSync('rsync', ['-avz', '--delete', path.join(REPO_ROOT, '.gemini/skills/offload/scripts/'), `${remoteHost}:${remoteWorkDir}/.gemini/skills/offload/scripts/`]); if (syncAuth) { const homeDir = env.HOME || ''; @@ -76,54 +78,48 @@ export async function runOrchestrator(args: string[], env: NodeJS.ProcessEnv = p const localEnv = path.join(REPO_ROOT, '.env'); if (fs.existsSync(localEnv)) spawnSync('rsync', ['-avz', localEnv, `${remoteHost}:${remoteWorkDir}/.env`]); } -// 4. Construct Clean Command -const envLoader = 'export NVM_DIR="$HOME/.nvm"; [ -s "$NVM_DIR/nvm.sh" ] && \\. "$NVM_DIR/nvm.sh"'; -// Set FORCE_LOCAL_OPEN=1 to signal the worker to use OSC 1337 for links -const remoteWorker = `export FORCE_LOCAL_OPEN=1 && export GEMINI_CLI_HOME=${ISOLATED_GEMINI} && export GH_CONFIG_DIR=${ISOLATED_GH} && ./node_modules/.bin/tsx .gemini/skills/deep-review/scripts/entrypoint.ts ${prNumber} ${branchName} ${remotePolicyPath}`; -const tmuxCmd = `cd ${remoteWorkDir} && ${envLoader} && ${remoteWorker}; exec $SHELL`; -const sshInternal = `tmux attach-session -t ${sessionName} 2>/dev/null || tmux new-session -s ${sessionName} -n ${q(branchName)} ${q(tmuxCmd)}`; -const sshCmd = `ssh -t ${remoteHost} ${q(sshInternal)}`; + // 3. Construct Clean Command + const envLoader = 'export NVM_DIR="$HOME/.nvm"; [ -s "$NVM_DIR/nvm.sh" ] && \\. "$NVM_DIR/nvm.sh"'; + // Set FORCE_LOCAL_OPEN=1 to signal the worker to use OSC 1337 for links + const remoteWorker = `export FORCE_LOCAL_OPEN=1 && export GEMINI_CLI_HOME=${ISOLATED_GEMINI} && export GH_CONFIG_DIR=${ISOLATED_GH} && ./node_modules/.bin/tsx .gemini/skills/offload/scripts/entrypoint.ts ${prNumber} ${branchName} ${remotePolicyPath} ${action}`; + + const tmuxCmd = `cd ${remoteWorkDir} && ${envLoader} && ${remoteWorker}; exec $SHELL`; + const sshInternal = `tmux attach-session -t ${sessionName} 2>/dev/null || tmux new-session -s ${sessionName} -n ${q(branchName)} ${q(tmuxCmd)}`; + const sshCmd = `ssh -t ${remoteHost} ${q(sshInternal)}`; -// 5. Smart Context Execution -// Check if we are running inside a Gemini CLI session or a plain shell -const isWithinGemini = !!env.GEMINI_SESSION_ID || !!env.GCLI_SESSION_ID; -const forceBackground = args.includes('--background'); + // 4. Smart Context Execution + const isWithinGemini = !!env.GEMINI_SESSION_ID || !!env.GCLI_SESSION_ID; + const forceBackground = args.includes('--background'); -if (isWithinGemini || forceBackground) { - if (process.platform === 'darwin' && terminalType !== 'none' && !forceBackground) { - // macOS: Use Window Automation (Interactive Mode) - - let appleScript = ''; - if (terminalType === 'iterm2') { - appleScript = `on run argv\n set theCommand to item 1 of argv\n tell application "iTerm"\n set newWindow to (create window with default profile)\n tell current session of newWindow\n write text theCommand\n end tell\n activate\n end tell\n end run`; - } else if (terminalType === 'terminal') { + if (isWithinGemini || forceBackground) { + if (process.platform === 'darwin' && terminalType !== 'none' && !forceBackground) { + // macOS: Use Window Automation + let appleScript = `on run argv\n set theCommand to item 1 of argv\n tell application "iTerm"\n set newWindow to (create window with default profile)\n tell current session of newWindow\n write text theCommand\n end tell\n activate\n end tell\n end run`; + if (terminalType === 'terminal') { appleScript = `on run argv\n set theCommand to item 1 of argv\n tell application "Terminal"\n do script theCommand\n activate\n end tell\n end run`; } - if (appleScript) { - spawnSync('osascript', ['-', sshCmd], { input: appleScript }); - console.log(`✅ ${terminalType.toUpperCase()} window opened for verification.`); - return 0; - } + spawnSync('osascript', ['-', sshCmd], { input: appleScript }); + console.log(`✅ ${terminalType.toUpperCase()} window opened for verification.`); + return 0; } - // Cross-Platform Background Mode (within Gemini session) + // Cross-Platform Background Mode console.log(`📡 Launching remote verification in background mode...`); - const logFile = path.join(REPO_ROOT, `.gemini/logs/review-${prNumber}/background.log`); + const logFile = path.join(REPO_ROOT, `.gemini/logs/offload-${prNumber}/background.log`); fs.mkdirSync(path.dirname(logFile), { recursive: true }); - // Use tmuxCmd for background mode to ensure persistence even in background const backgroundCmd = `ssh ${remoteHost} ${q(tmuxCmd)} > ${q(logFile)} 2>&1 &`; spawnSync(backgroundCmd, { shell: true }); console.log(`⏳ Remote worker started in background.`); - console.log(`📄 Tailing logs to: .gemini/logs/review-${prNumber}/background.log`); + console.log(`📄 Tailing logs to: .gemini/logs/offload-${prNumber}/background.log`); return 0; } // Direct Shell Mode: Execute SSH in-place - console.log(`🚀 Launching review session in current terminal...`); + console.log(`🚀 Launching offload session in current terminal...`); const result = spawnSync(sshCmd, { stdio: 'inherit', shell: true }); return result.status || 0; } diff --git a/.gemini/skills/deep-review/scripts/setup.ts b/.gemini/skills/offload/scripts/setup.ts similarity index 100% rename from .gemini/skills/deep-review/scripts/setup.ts rename to .gemini/skills/offload/scripts/setup.ts diff --git a/.gemini/skills/deep-review/scripts/worker.ts b/.gemini/skills/offload/scripts/worker.ts similarity index 58% rename from .gemini/skills/deep-review/scripts/worker.ts rename to .gemini/skills/offload/scripts/worker.ts index 5bbdbab030..34631b6ecb 100644 --- a/.gemini/skills/deep-review/scripts/worker.ts +++ b/.gemini/skills/offload/scripts/worker.ts @@ -1,7 +1,7 @@ /** - * Universal Deep Review Worker (Remote) + * Universal Offload Worker (Remote) * - * Handles worktree provisioning and parallel task execution. + * Handles worktree provisioning and parallel task execution based on 'playbooks'. */ import { spawn, spawnSync } from 'child_process'; import path from 'path'; @@ -10,21 +10,18 @@ import fs from 'fs'; const prNumber = process.argv[2]; const branchName = process.argv[3]; const policyPath = process.argv[4]; +const action = process.argv[5] || 'review'; -export async function runWorker(args: string[]) { - const prNumber = args[0]; - const branchName = args[1]; - const policyPath = args[2]; - +async function main() { if (!prNumber || !branchName || !policyPath) { - console.error('Usage: tsx worker.ts '); + console.error('Usage: tsx worker.ts [action]'); return 1; } const workDir = process.cwd(); // This is remoteWorkDir const targetDir = path.join(workDir, branchName); - // 1. Provision PR Directory (Fast Blobless Clone) + // 1. Provision PR Directory if (!fs.existsSync(targetDir)) { console.log(`🌿 Provisioning PR #${prNumber} into ${branchName}...`); const cloneCmd = `git clone --filter=blob:none https://github.com/google-gemini/gemini-cli.git ${targetDir}`; @@ -36,18 +33,37 @@ export async function runWorker(args: string[]) { process.chdir(targetDir); } - const logDir = path.join(targetDir, `.gemini/logs/review-${prNumber}`); + const logDir = path.join(targetDir, `.gemini/logs/offload-${prNumber}`); fs.mkdirSync(logDir, { recursive: true }); const geminiBin = path.join(workDir, 'node_modules/.bin/gemini'); - // 2. Define Parallel Tasks - const tasks = [ - { id: 'build', name: 'Fast Build', cmd: `cd ${targetDir} && npm ci && npm run build` }, - { id: 'ci', name: 'CI Checks', cmd: `gh pr checks ${prNumber}` }, - { id: 'review', name: 'Gemini Analysis', cmd: `${geminiBin} --policy ${policyPath} --cwd ${targetDir} -p "/review-frontend ${prNumber}"` }, - { id: 'verify', name: 'Behavioral Proof', cmd: `${geminiBin} --policy ${policyPath} --cwd ${targetDir} -p "Analyze the code in ${targetDir} and exercise it to prove it works."`, dep: 'build' } - ]; + // 2. Define Playbooks + let tasks: any[] = []; + + if (action === 'review') { + tasks = [ + { id: 'build', name: 'Fast Build', cmd: `cd ${targetDir} && npm ci && npm run build` }, + { id: 'ci', name: 'CI Checks', cmd: `gh pr checks ${prNumber}` }, + { id: 'review', name: 'Gemini Analysis', cmd: `${geminiBin} --policy ${policyPath} --cwd ${targetDir} -p "/review-frontend ${prNumber}"` }, + { id: 'verify', name: 'Behavioral Proof', cmd: `${geminiBin} --policy ${policyPath} --cwd ${targetDir} -p "Analyze the code in ${targetDir} and exercise it to prove it works."`, dep: 'build' } + ]; + } else if (action === 'fix') { + tasks = [ + { id: 'build', name: 'Fast Build', cmd: `cd ${targetDir} && npm ci && npm run build` }, + { id: 'failures', name: 'Find Failures', cmd: `gh run view --log-failed` }, + { id: 'fix', name: 'Iterative Fix', cmd: `${geminiBin} --policy ${policyPath} --cwd ${targetDir} -p "Address review comments and fix failing tests for PR ${prNumber}. Repeat until CI is green."`, dep: 'build' } + ]; + } else if (action === 'ready') { + tasks = [ + { id: 'clean', name: 'Clean Install', cmd: `npm run clean && npm ci` }, + { id: 'preflight', name: 'Full Preflight', cmd: `npm run preflight`, dep: 'clean' }, + { id: 'conflicts', name: 'Conflict Check', cmd: `git fetch origin main && git merge-base --is-ancestor origin/main HEAD || echo "CONFLICT"` } + ]; + } else if (action === 'open') { + console.log(`🚀 Dropping into manual session for ${branchName}...`); + process.exit(0); + } const state: Record = {}; tasks.forEach(t => state[t.id] = { status: 'PENDING' }); @@ -76,8 +92,8 @@ export async function runWorker(args: string[]) { function render() { console.clear(); console.log(`==================================================`); - console.log(`🚀 Deep Review | PR #${prNumber} | ${branchName}`); - console.log(`📂 PR Target: ${targetDir}`); + console.log(`🚀 Offload | ${action.toUpperCase()} | PR #${prNumber}`); + console.log(`📂 Worktree: ${targetDir}`); console.log(`==================================================\n`); tasks.forEach(t => { @@ -88,7 +104,7 @@ export async function runWorker(args: string[]) { const allDone = tasks.every(t => ['SUCCESS', 'FAILED'].includes(state[t.id].status)); if (allDone) { - console.log(`\n✨ Verification complete. Launching interactive session...`); + console.log(`\n✨ Playbook complete. Launching interactive session...`); resolve(0); } } @@ -97,7 +113,6 @@ export async function runWorker(args: string[]) { tasks.filter(t => t.dep).forEach(runTask); const intervalId = setInterval(render, 1500); - // Ensure the promise resolves and the interval is cleared when all done const checkAllDone = setInterval(() => { if (tasks.every(t => ['SUCCESS', 'FAILED'].includes(state[t.id].status))) { clearInterval(intervalId); diff --git a/.gemini/skills/deep-review/tests/orchestration.test.ts b/.gemini/skills/offload/tests/orchestration.test.ts similarity index 86% rename from .gemini/skills/deep-review/tests/orchestration.test.ts rename to .gemini/skills/offload/tests/orchestration.test.ts index f98e1e82ba..8d7c912f3e 100644 --- a/.gemini/skills/deep-review/tests/orchestration.test.ts +++ b/.gemini/skills/offload/tests/orchestration.test.ts @@ -2,7 +2,7 @@ import { describe, it, expect, vi, beforeEach } from 'vitest'; import { spawnSync, spawn } from 'child_process'; import fs from 'fs'; import readline from 'readline'; -import { runOrchestrator } from '../scripts/review.ts'; +import { runOrchestrator } from '../scripts/orchestrator.ts'; import { runSetup } from '../scripts/setup.ts'; import { runWorker } from '../scripts/worker.ts'; import { runChecker } from '../scripts/check.ts'; @@ -233,32 +233,44 @@ describe('Deep Review Orchestration', () => { const savedSettings = JSON.parse(writeCall![1] as string); expect(savedSettings.maintainer.deepReview.geminiSetup).toBe('isolated'); expect(savedSettings.maintainer.deepReview.ghSetup).toBe('preexisting'); - expect(savedSettings.maintainer.deepReview.syncAuth).toBe(true); - }); - }); + describe('orchestrator.ts (offload)', () => { + it('should default to review action and pass it to remote', async () => { + await runOrchestrator(['123'], {}); - describe('worker.ts', () => { - it('should launch parallel tasks and write exit codes', async () => { - // Mock targetDir existing - vi.mocked(fs.existsSync).mockImplementation((p) => p.toString().includes('test-branch')); - - const workerPromise = runWorker(['123', 'test-branch', '/test-policy.toml']); - - // Since worker uses setInterval/setTimeout, we might need to advance timers - // or ensure the close event triggers everything - await workerPromise; + const spawnCalls = vi.mocked(spawnSync).mock.calls; + const sshCall = spawnCalls.find(call => typeof call[0] === 'string' && call[0].includes('entrypoint.ts 123')); + expect(sshCall![0]).toContain('review'); // Default action + }); - const spawnCalls = vi.mocked(spawn).mock.calls; - expect(spawnCalls.length).toBeGreaterThanOrEqual(4); // build, ci, review, verify - - const buildCall = spawnCalls.find(call => call[0].includes('npm ci')); - expect(buildCall).toBeDefined(); + it('should pass explicit actions (like fix) to remote', async () => { + await runOrchestrator(['123', 'fix'], {}); - const writeCalls = vi.mocked(fs.writeFileSync).mock.calls; - const exitFileCall = writeCalls.find(call => call[0].toString().includes('build.exit')); - expect(exitFileCall).toBeDefined(); - expect(exitFileCall![1]).toBe('0'); - }); + const spawnCalls = vi.mocked(spawnSync).mock.calls; + const sshCall = spawnCalls.find(call => typeof call[0] === 'string' && call[0].includes('entrypoint.ts 123')); + expect(sshCall![0]).toContain('fix'); + }); + + it('should construct the correct tmux session name from branch', async () => { + ... + describe('worker.ts (playbooks)', () => { + it('should launch the review playbook by default', async () => { + vi.mocked(fs.existsSync).mockReturnValue(true); + await runWorker(['123', 'test-branch', '/test-policy.toml', 'review']); + + const spawnCalls = vi.mocked(spawn).mock.calls; + const analysisCall = spawnCalls.find(call => call[0].includes('/review-frontend')); + expect(analysisCall).toBeDefined(); + }); + + it('should launch the fix playbook when requested', async () => { + vi.mocked(fs.existsSync).mockReturnValue(true); + await runWorker(['123', 'test-branch', '/test-policy.toml', 'fix']); + + const spawnCalls = vi.mocked(spawn).mock.calls; + const fixCall = spawnCalls.find(call => call[0].includes('Address review comments')); + expect(fixCall).toBeDefined(); + }); + }); }); describe('check.ts', () => { diff --git a/package.json b/package.json index 5dccd10fa8..a452992eab 100644 --- a/package.json +++ b/package.json @@ -63,10 +63,10 @@ "telemetry": "node scripts/telemetry.js", "check:lockfile": "node scripts/check-lockfile.js", "clean": "node scripts/clean.js", - "review": "tsx .gemini/skills/deep-review/scripts/review.ts", - "review:setup": "tsx .gemini/skills/deep-review/scripts/setup.ts", - "review:check": "tsx .gemini/skills/deep-review/scripts/check.ts", - "review:clean": "tsx .gemini/skills/deep-review/scripts/clean.ts", + "offload": "tsx .gemini/skills/offload/scripts/orchestrator.ts", + "offload:setup": "tsx .gemini/skills/offload/scripts/setup.ts", + "offload:check": "tsx .gemini/skills/offload/scripts/check.ts", + "offload:clean": "tsx .gemini/skills/offload/scripts/clean.ts", "pre-commit": "node scripts/pre-commit.js" }, "overrides": {