diff --git a/.gemini/skills/deep-review/SKILL.md b/.gemini/skills/deep-review/SKILL.md new file mode 100644 index 0000000000..4c95572393 --- /dev/null +++ b/.gemini/skills/deep-review/SKILL.md @@ -0,0 +1,56 @@ +# 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/deep-review/scripts/check.ts b/.gemini/skills/deep-review/scripts/check.ts new file mode 100644 index 0000000000..e0868117c0 --- /dev/null +++ b/.gemini/skills/deep-review/scripts/check.ts @@ -0,0 +1,58 @@ +/** + * Universal Deep Review Checker (Local) + * + * Polls the remote machine for task status. + */ +import { spawnSync } from 'child_process'; +import path from 'path'; +import fs from 'fs'; +import { fileURLToPath } from 'url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const REPO_ROOT = path.resolve(__dirname, '../../../..'); + +async function main() { + const prNumber = process.argv[2]; + if (!prNumber) { + console.error('Usage: npm run review:check '); + process.exit(1); + } + + const settingsPath = path.join(REPO_ROOT, '.gemini/settings.json'); + const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); + const { remoteHost, remoteWorkDir } = settings.maintainer.deepReview; + + console.log(`๐Ÿ” Checking remote status for PR #${prNumber} on ${remoteHost}...`); + + const branchView = spawnSync('gh', ['pr', 'view', prNumber, '--json', 'headRefName', '-q', '.headRefName'], { shell: true }); + const branchName = branchView.stdout.toString().trim(); + const logDir = `${remoteWorkDir}/${branchName}/.gemini/logs/review-${prNumber}`; + + const tasks = ['build', 'ci', 'review', 'verify']; + let allDone = true; + + console.log('\n--- Task Status ---'); + for (const task of tasks) { + const checkExit = spawnSync('ssh', [remoteHost, `cat ${logDir}/${task}.exit 2>/dev/null`], { shell: true }); + if (checkExit.status === 0) { + const code = checkExit.stdout.toString().trim(); + console.log(` ${code === '0' ? 'โœ…' : 'โŒ'} ${task.padEnd(10)}: ${code === '0' ? 'SUCCESS' : `FAILED (exit ${code})`}`); + } else { + const checkRunning = spawnSync('ssh', [remoteHost, `[ -f ${logDir}/${task}.log ]`], { shell: true }); + if (checkRunning.status === 0) { + console.log(` โณ ${task.padEnd(10)}: RUNNING`); + } else { + console.log(` ๐Ÿ’ค ${task.padEnd(10)}: PENDING`); + } + allDone = false; + } + } + + if (allDone) { + console.log('\nโœจ All remote tasks complete. You can now synthesize the results.'); + } else { + console.log('\nโณ Some tasks are still in progress. Check again in a few minutes.'); + } +} + +main().catch(console.error); diff --git a/.gemini/skills/deep-review/scripts/clean.ts b/.gemini/skills/deep-review/scripts/clean.ts new file mode 100644 index 0000000000..3afd78ca02 --- /dev/null +++ b/.gemini/skills/deep-review/scripts/clean.ts @@ -0,0 +1,64 @@ +/** + * Universal Deep Review Cleanup (Local) + */ +import { spawnSync } from 'child_process'; +import path from 'path'; +import fs from 'fs'; +import { fileURLToPath } from 'url'; +import readline from 'readline'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const REPO_ROOT = path.resolve(__dirname, '../../../..'); + +async function confirm(question: string): Promise { + const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); + return new Promise((resolve) => { + rl.question(`${question} (y/n): `, (answer) => { + rl.close(); + resolve(answer.trim().toLowerCase() === 'y'); + }); + }); +} + +async function main() { + const settingsPath = path.join(REPO_ROOT, '.gemini/settings.json'); + if (!fs.existsSync(settingsPath)) { + console.error('โŒ Settings not found. Run "npm run review:setup" first.'); + process.exit(1); + } + + const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); + const config = settings.maintainer?.deepReview; + + if (!config) { + console.error('โŒ Deep Review configuration not found.'); + process.exit(1); + } + + const { remoteHost, remoteWorkDir } = config; + + console.log(`๐Ÿงน Starting cleanup for ${remoteHost}:${remoteWorkDir}...`); + + // 1. Standard Cleanup + console.log(' - Killing remote tmux sessions...'); + spawnSync('ssh', [remoteHost, 'tmux kill-server'], { shell: true }); + + console.log(' - Removing PR directories...'); + // Find all directories in the work dir that aren't .gemini and delete them + const dirCleanup = `find ${remoteWorkDir} -mindepth 1 -maxdepth 1 -type d ! -name ".gemini" -exec rm -rf {} +`; + spawnSync('ssh', [remoteHost, dirCleanup], { shell: true }); + + console.log('โœ… Standard cleanup complete.'); + + // 2. Full Wipe Option + const shouldWipe = await confirm('\nWould you like to COMPLETELY remove the work directory from the remote machine?'); + + if (shouldWipe) { + console.log(`๐Ÿ”ฅ Wiping ${remoteWorkDir}...`); + const wipeCmd = `rm -rf ${remoteWorkDir}`; + spawnSync('ssh', [remoteHost, wipeCmd], { stdio: 'inherit', shell: true }); + console.log('โœ… Remote directory wiped.'); + } +} + +main().catch(console.error); diff --git a/.gemini/skills/deep-review/scripts/entrypoint.ts b/.gemini/skills/deep-review/scripts/entrypoint.ts new file mode 100644 index 0000000000..b832e5878a --- /dev/null +++ b/.gemini/skills/deep-review/scripts/entrypoint.ts @@ -0,0 +1,59 @@ +/** + * Deep Review Entrypoint (Remote) + * + * This script is the single command executed by the remote tmux session. + * It handles environment loading and sequence orchestration. + */ +import { spawnSync } from 'child_process'; +import path from 'path'; +import fs from 'fs'; +import { fileURLToPath } from 'url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const prNumber = process.argv[2]; +const branchName = process.argv[3]; +const ISOLATED_CONFIG = process.env.GEMINI_CLI_HOME || path.join(process.env.HOME || '', '.gemini-deep-review'); + +async function main() { + if (!prNumber || !branchName) { + console.error('Usage: tsx entrypoint.ts '); + process.exit(1); + } + + const workDir = process.cwd(); // This is remoteWorkDir as set in review.ts + const targetDir = path.join(workDir, branchName); + + // Path to the locally installed binaries in the work directory + const tsxBin = path.join(workDir, 'node_modules/.bin/tsx'); + const geminiBin = path.join(workDir, 'node_modules/.bin/gemini'); + + // 1. Run the Parallel Worker + console.log('๐Ÿš€ Launching Parallel Review Worker...'); + const workerResult = spawnSync(tsxBin, [path.join(__dirname, 'worker.ts'), prNumber, branchName], { + stdio: 'inherit', + env: { ...process.env, GEMINI_CLI_HOME: ISOLATED_CONFIG } + }); + + if (workerResult.status !== 0) { + console.error('โŒ Worker failed. Check the logs above.'); + } + + // 2. Launch the Interactive Gemini Session (Local Nightly) + console.log('\nโœจ Verification complete. Joining interactive session...'); + + // Use the mirrored policy if available + const policyFile = path.join(ISOLATED_CONFIG, 'policies/policy.toml'); + const geminiArgs = []; + if (fs.existsSync(policyFile)) { + geminiArgs.push('--policy', policyFile); + } + geminiArgs.push('-p', `Review for PR #${prNumber} is complete. Read the logs in .gemini/logs/review-${prNumber}/ and synthesize your findings.`); + + process.chdir(targetDir); + spawnSync(geminiBin, geminiArgs, { + stdio: 'inherit', + env: { ...process.env, GEMINI_CLI_HOME: ISOLATED_CONFIG } + }); +} + +main().catch(console.error); diff --git a/.gemini/skills/deep-review/scripts/review.ts b/.gemini/skills/deep-review/scripts/review.ts new file mode 100644 index 0000000000..70682d0482 --- /dev/null +++ b/.gemini/skills/deep-review/scripts/review.ts @@ -0,0 +1,126 @@ +/** + * Universal Deep Review Orchestrator (Local) + */ +import { spawnSync } from 'child_process'; +import path from 'path'; +import fs from 'fs'; +import { fileURLToPath } from 'url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const REPO_ROOT = path.resolve(__dirname, '../../../..'); + +const q = (str: string) => `'${str.replace(/'/g, "'\\''")}'`; + +async function main() { + const prNumber = process.argv[2]; + if (!prNumber) { + console.error('Usage: npm run review '); + process.exit(1); + } + + // Load Settings + const settingsPath = path.join(REPO_ROOT, '.gemini/settings.json'); + let settings: any = {}; + if (fs.existsSync(settingsPath)) { + try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); } catch (e) {} + } + + 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' }); + if (setupResult.status !== 0) { + console.error('โŒ Setup failed. Please run "npm run review:setup" manually.'); + process.exit(1); + } + // Reload settings after setup + settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); + config = settings.maintainer.deepReview; + } + + const { remoteHost, remoteWorkDir, terminalType, syncAuth } = config; + + console.log(`๐Ÿ” Fetching metadata for PR #${prNumber}...`); + const ghView = spawnSync('gh', ['pr', 'view', prNumber, '--json', 'headRefName', '-q', '.headRefName'], { shell: true }); + const branchName = ghView.stdout.toString().trim(); + if (!branchName) { + console.error('โŒ Failed to resolve PR branch.'); + process.exit(1); + } + + const sessionName = `${prNumber}-${branchName.replace(/[^a-zA-Z0-9]/g, '_')}`; + + // 2. Sync Configuration Mirror (Isolated Profile) + const ISOLATED_CONFIG = '~/.gemini-deep-review'; + console.log(`๐Ÿ“ก Mirroring environment to ${remoteHost}...`); + spawnSync('ssh', [remoteHost, `mkdir -p ${remoteWorkDir}/.gemini/skills/deep-review/scripts/ ${ISOLATED_CONFIG}/policies/`]); + + spawnSync('rsync', ['-avz', '--delete', path.join(REPO_ROOT, '.gemini/skills/deep-review/scripts/'), `${remoteHost}:${remoteWorkDir}/.gemini/skills/deep-review/scripts/`]); + + if (syncAuth) { + const homeDir = process.env.HOME || ''; + const localGeminiDir = path.join(homeDir, '.gemini'); + const syncFiles = ['google_accounts.json', 'settings.json']; + for (const f of syncFiles) { + const lp = path.join(localGeminiDir, f); + if (fs.existsSync(lp)) spawnSync('rsync', ['-avz', lp, `${remoteHost}:${ISOLATED_CONFIG}/${f}`]); + } + const localPolicies = path.join(localGeminiDir, 'policies/'); + if (fs.existsSync(localPolicies)) spawnSync('rsync', ['-avz', '--delete', localPolicies, `${remoteHost}:${ISOLATED_CONFIG}/policies/`]); + const localEnv = path.join(REPO_ROOT, '.env'); + if (fs.existsSync(localEnv)) spawnSync('rsync', ['-avz', localEnv, `${remoteHost}:${remoteWorkDir}/.env`]); + } + + // 3. Construct Clean Command + // We use a single entrypoint script to avoid quoting nightmares + const envLoader = 'export NVM_DIR="$HOME/.nvm"; [ -s "$NVM_DIR/nvm.sh" ] && \\. "$NVM_DIR/nvm.sh"'; + // Use the locally installed tsx binary + const entryCmd = `cd ${remoteWorkDir} && ${envLoader} && export GEMINI_CLI_HOME=${ISOLATED_CONFIG} && ./node_modules/.bin/tsx .gemini/skills/deep-review/scripts/entrypoint.ts ${prNumber} ${branchName}`; + + const tmuxCmd = `$SHELL -ic ${q(entryCmd)}; 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)}`; + + // 4. Smart Context Execution + // If run from within a Gemini CLI session, we pop a new window. + // Otherwise (running directly in shell), we execute in-place. + const isWithinGemini = !!process.env.GEMINI_SESSION_ID || !!process.env.GCLI_SESSION_ID; + + if (isWithinGemini) { + if (process.platform === 'darwin' && terminalType !== 'none') { + // macOS: Use Window Automation + 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') { + 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; + } + } + + // Cross-Platform Background Mode (within Gemini session) + console.log(`๐Ÿ“ก Launching remote verification in background mode...`); + // Run SSH in background, redirecting output to a session log + const logFile = path.join(REPO_ROOT, `.gemini/logs/review-${prNumber}/background.log`); + fs.mkdirSync(path.dirname(logFile), { recursive: true }); + + const backgroundCmd = `ssh ${remoteHost} ${q(entryCmd)} > ${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`); + return; + } + + // Direct Shell Mode: Execute SSH in-place + console.log(`๐Ÿš€ Launching review session in current terminal...`); + const result = spawnSync(sshCmd, { stdio: 'inherit', shell: true }); + process.exit(result.status || 0); +} + +main().catch(console.error); diff --git a/.gemini/skills/deep-review/scripts/setup.ts b/.gemini/skills/deep-review/scripts/setup.ts new file mode 100644 index 0000000000..1a88141e14 --- /dev/null +++ b/.gemini/skills/deep-review/scripts/setup.ts @@ -0,0 +1,103 @@ +/** + * Universal Deep Review Onboarding (Local) + */ +import { spawnSync } from 'child_process'; +import path from 'path'; +import fs from 'fs'; +import { fileURLToPath } from 'url'; +import readline from 'readline'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const REPO_ROOT = path.resolve(__dirname, '../../../..'); + +const q = (str: string) => `'${str.replace(/'/g, "'\\''")}'`; + +async function prompt(question: string, defaultValue: string): Promise { + const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); + return new Promise((resolve) => { + rl.question(`${question} (default: ${defaultValue}, to use default): `, (answer) => { + rl.close(); + resolve(answer.trim() || defaultValue); + }); + }); +} + +async function confirm(question: string): Promise { + const rl = readline.createInterface({ input: process.stdin, output: process.stdout }); + return new Promise((resolve) => { + rl.question(`${question} (y/n): `, (answer) => { + rl.close(); + resolve(answer.trim().toLowerCase() === 'y'); + }); + }); +} + +async function main() { + console.log('\n๐ŸŒŸ Initializing Deep Review Skill Settings...'); + + const remoteHost = await prompt('Remote SSH Host', 'cli'); + const remoteWorkDir = await prompt('Remote Work Directory', '~/gcli/deepreview'); + + console.log(`๐Ÿ” Checking state of ${remoteHost}...`); + const ghCheck = spawnSync('ssh', [remoteHost, 'command -v gh'], { shell: true }); + + if (ghCheck.status !== 0) { + console.log('\n๐Ÿ“ฅ System Requirements Check:'); + console.log(' โŒ GitHub CLI (gh) is not installed on remote.'); + + const shouldProvision = await confirm('\nWould you like Gemini to automatically provision gh?'); + + if (shouldProvision) { + console.log(`๐Ÿš€ Attempting to install gh on ${remoteHost}...`); + const osCheck = spawnSync('ssh', [remoteHost, 'uname -s'], { shell: true }); + const os = osCheck.stdout.toString().trim(); + let installCmd = os === 'Linux' ? 'sudo apt update && sudo apt install -y gh' : (os === 'Darwin' ? 'brew install gh' : ''); + if (installCmd) { + spawnSync('ssh', ['-t', remoteHost, installCmd], { stdio: 'inherit', shell: true }); + } + } else { + console.log('โš ๏ธ Please ensure gh is installed before running again.'); + process.exit(1); + } + } + + // Ensure remote work dir exists + spawnSync('ssh', [remoteHost, `mkdir -p ${remoteWorkDir}`], { shell: true }); + + // Identity Synchronization Onboarding + console.log('\n๐Ÿ” Identity & Authentication:'); + const homeDir = process.env.HOME || ''; + const localAuth = path.join(homeDir, '.gemini/google_accounts.json'); + const localEnv = path.join(REPO_ROOT, '.env'); + const hasAuth = fs.existsSync(localAuth); + const hasEnv = fs.existsSync(localEnv); + + let syncAuth = false; + if (hasAuth || hasEnv) { + console.log(` ๐Ÿ” Found local identity files: ${[hasAuth ? 'Google Account' : '', hasEnv ? '.env' : ''].filter(Boolean).join(', ')}`); + syncAuth = await confirm(' Would you like Gemini to automatically sync your local identity to the remote workstation for seamless authentication?'); + } + + const terminalType = await prompt('\nTerminal Automation (iterm2 / terminal / none)', 'iterm2'); + + // Local Dependencies Install (Isolated) + console.log(`\n๐Ÿ“ฆ Installing isolated dependencies (nightly CLI & tsx) in ${remoteWorkDir}...`); + const envLoader = 'export NVM_DIR="$HOME/.nvm"; [ -s "$NVM_DIR/nvm.sh" ] && \\. "$NVM_DIR/nvm.sh"; [ -s "$NVM_DIR/bash_completion" ] && \\. "$NVM_DIR/bash_completion"'; + // Note: we create a package.json first to prevent npm from walking up the tree looking for one + const installCmd = `${envLoader} && cd ${remoteWorkDir} && npm init -y > /dev/null && npm install tsx @google/gemini-cli@nightly`; + spawnSync('ssh', [remoteHost, q(installCmd)], { stdio: 'inherit', shell: true }); + + // Save Settings + const settingsPath = path.join(REPO_ROOT, '.gemini/settings.json'); + let settings: any = {}; + if (fs.existsSync(settingsPath)) { + try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); } catch (e) {} + } + settings.maintainer = settings.maintainer || {}; + settings.maintainer.deepReview = { remoteHost, remoteWorkDir, terminalType, syncAuth }; + fs.mkdirSync(path.dirname(settingsPath), { recursive: true }); + fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2)); + console.log('\nโœ… Onboarding complete! Settings saved to .gemini/settings.json'); +} + +main().catch(console.error); diff --git a/.gemini/skills/deep-review/scripts/setup.zsh b/.gemini/skills/deep-review/scripts/setup.zsh new file mode 100644 index 0000000000..6b3836d30c --- /dev/null +++ b/.gemini/skills/deep-review/scripts/setup.zsh @@ -0,0 +1,68 @@ +# Gemini CLI Maintainer Setup +# This script should be sourced in your .zshrc + +# Find the repo root relative to this script +export GEMINI_REPO_ROOT=$(cd "$(dirname "$0")/../../.." && pwd) + +# --- Worktree-based Project Management --- +function rswitch() { + local branch=$1 + local base_dir="$GEMINI_REPO_ROOT" + local target_dir="$(dirname "$base_dir")/$branch" + + if [[ "$branch" == "main" ]]; then + cd "$base_dir" && git pull + return + fi + + if [[ ! -d "$target_dir" ]]; then + echo "๐ŸŒฟ Creating worktree for $branch..." + cd "$base_dir" + git fetch origin + if git show-ref --verify --quiet "refs/remotes/origin/$branch"; then + git worktree add "$target_dir" "origin/$branch" + else + git worktree add -b "$branch" "$target_dir" + fi + fi + cd "$target_dir" +} + +# --- PR Review Workflows --- +function review() { + local pr_number=$1 + local branch_name=$2 + [[ -z "$pr_number" ]] && { echo "Usage: review "; return 1; } + + cd "$GEMINI_REPO_ROOT" + if [[ -z "$branch_name" ]]; then + branch_name=$(gh pr view $pr_number --json headRefName -q .headRefName 2>/dev/null) + [[ -z "$branch_name" ]] && branch_name="pr-$pr_number" + fi + + local target_dir="$(dirname "$GEMINI_REPO_ROOT")/$branch_name" + git fetch origin "pull/$pr_number/head:refs/pull/$pr_number/head" + [[ -d "$target_dir" ]] || git worktree add "$target_dir" "refs/pull/$pr_number/head" + cd "$target_dir" + + npx tsx "$GEMINI_REPO_ROOT/.gemini/skills/deep-review/scripts/worker.ts" "$pr_number" + gnightly "PR #$pr_number verification complete. Synthesize results from .gemini/logs/review-$pr_number/" +} + +function rreview() { + local pr_number=$1 + [[ -z "$pr_number" ]] && { echo "Usage: rreview "; return 1; } + npx tsx "$GEMINI_REPO_ROOT/.gemini/skills/deep-review/scripts/review.ts" "$pr_number" +} + +# --- Helper Functions --- +function gnightly() { "$GEMINI_REPO_ROOT/bundle/gemini.js" "$@"; } +function gemini() { gnightly "$@"; } +alias gr='go-remote' + +function go-remote() { + local branch="${1:-main}" + local session_name="${branch//./_}" + local remote_host=${GEMINI_REMOTE_HOST:-cli} + ssh -t $remote_host "tmux attach-session -t $session_name 2>/dev/null || tmux new-session -s $session_name 'zsh -ic \"rswitch $branch && gemini; zsh\"'" +} diff --git a/.gemini/skills/deep-review/scripts/sync-review.sh b/.gemini/skills/deep-review/scripts/sync-review.sh new file mode 100755 index 0000000000..50fd2f6e38 --- /dev/null +++ b/.gemini/skills/deep-review/scripts/sync-review.sh @@ -0,0 +1,106 @@ +#!/bin/bash + +# sync-review.sh - Optimized Parallel PR verification +# Focuses on building for runtime and deferring others to CI status. + +pr_number=$1 +if [[ -z "$pr_number" ]]; then + echo "Usage: sync-review " + exit 1 +fi + +log_dir=".gemini/logs/review-$pr_number" +mkdir -p "$log_dir" + +GEMINI_CMD=$(which gemini || echo "$HOME/.gcli/nightly/node_modules/.bin/gemini") +POLICY_PATH="$HOME/dev/main/.gemini/skills/async-pr-review/policy.toml" +[[ -f "$POLICY_PATH" ]] || POLICY_PATH="" + +repo_url=$(gh repo view --json url -q .url 2>/dev/null) +pr_url="${repo_url}/pull/$pr_number" + +echo "==================================================" +echo "๐Ÿš€ Optimized Parallel Review for PR #$pr_number" +echo "๐Ÿ”— URL: $pr_url" +echo "==================================================" + +# 1. Essential Build (for npm run start / behavioral tests) +rm -f "$log_dir/build.exit" +{ { npm ci && npm run build; } > "$log_dir/build.log" 2>&1; echo $? > "$log_dir/build.exit"; } & + +# 2. CI Status Check +rm -f "$log_dir/ci-status.exit" +{ gh pr checks "$pr_number" > "$log_dir/ci-checks.log" 2>&1; echo $? > "$log_dir/ci-status.exit"; } & + +# 3. Gemini Code Review (Starts immediately) +rm -f "$log_dir/review.exit" +{ "$GEMINI_CMD" ${POLICY_PATH:+--policy "$POLICY_PATH"} -p "/review-frontend $pr_number" > "$log_dir/review.md" 2>&1; echo $? > "$log_dir/review.exit"; } & + +# 4. Behavioral Verification (Depends on Build) +rm -f "$log_dir/test-execution.exit" +{ + while [ ! -f "$log_dir/build.exit" ]; do sleep 1; done + if [ "$(cat "$log_dir/build.exit")" == "0" ]; then + "$GEMINI_CMD" ${POLICY_PATH:+--policy "$POLICY_PATH"} -p "Analyze the diff for PR $pr_number. Physically exercise the new code in the terminal (e.g. write a temp script or use the CLI). Verify it works. Do not modify source code." > "$log_dir/test-execution.log" 2>&1; echo $? > "$log_dir/test-execution.exit" + else + echo "โŒ Skipped verification due to build failure" > "$log_dir/test-execution.log" + echo 1 > "$log_dir/test-execution.exit" + fi +} & + +# 5. Conditional Local Diagnostics (Only if CI fails) +rm -f "$log_dir/diagnostics.exit" +{ + while [ ! -f "$log_dir/ci-status.exit" ]; do sleep 1; done + if [ "$(cat "$log_dir/ci-status.exit")" != "0" ]; then + echo "๐Ÿ” CI Failed. Running local diagnostics (lint/typecheck)..." > "$log_dir/diagnostics.log" + { npm run lint:ci && npm run typecheck; } >> "$log_dir/diagnostics.log" 2>&1 + echo $? > "$log_dir/diagnostics.exit" + else + echo "โœ… CI Passed. Skipping local diagnostics." > "$log_dir/diagnostics.log" + echo 0 > "$log_dir/diagnostics.exit" + fi +} & + +# Polling loop +tasks=("build" "ci-status" "review" "test-execution" "diagnostics") +log_files=("build.log" "ci-checks.log" "review.md" "test-execution.log" "diagnostics.log") + +all_done=0 +while [[ $all_done -eq 0 ]]; do + clear + echo "==================================================" + echo "๐Ÿš€ Parallel Review Status for PR #$pr_number" + echo "==================================================" + + all_done=1 + for i in "${!tasks[@]}"; do + t="${tasks[$i]}" + if [[ -f "$log_dir/$t.exit" ]]; then + exit_code=$(cat "$log_dir/$t.exit") + [[ "$exit_code" == "0" ]] && echo " โœ… $t: SUCCESS" || echo " โŒ $t: FAILED" + else + echo " โณ $t: RUNNING" + all_done=0 + fi + done + + echo "" + echo "๐Ÿ“ Live Logs:" + for i in "${!tasks[@]}"; do + t="${tasks[$i]}" + [[ ! -f "$log_dir/$t.exit" ]] && [[ -f "$log_dir/${log_files[$i]}" ]] && (echo "--- $t ---"; tail -n 3 "$log_dir/${log_files[$i]}") + done + + # Special condition: We can hand off to Gemini as soon as the core review and build are done + if [[ -f "$log_dir/build.exit" && -f "$log_dir/review.exit" ]]; then + echo "" + echo "๐Ÿ’ก Core build and review are ready! Launching Gemini soon..." + fi + + [[ $all_done -eq 0 ]] && sleep 3 +done + +echo "" +echo "โœ… All parallel tasks complete!" +echo "==================================================" diff --git a/.gemini/skills/deep-review/scripts/worker.ts b/.gemini/skills/deep-review/scripts/worker.ts new file mode 100644 index 0000000000..1beb6794fa --- /dev/null +++ b/.gemini/skills/deep-review/scripts/worker.ts @@ -0,0 +1,104 @@ +/** + * Universal Deep Review Worker (Remote) + */ +import { spawn, spawnSync } from 'child_process'; +import path from 'path'; +import fs from 'fs'; +import { fileURLToPath } from 'url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const workDir = process.cwd(); +const prNumber = process.argv[2]; +const branchName = process.argv[3]; + +async function main() { + if (!prNumber || !branchName) { + console.error('Usage: tsx worker.ts '); + process.exit(1); + } + + const targetDir = path.join(workDir, branchName); + + // 1. Provision PR Directory (Fast Blobless Clone) + if (!fs.existsSync(targetDir)) { + console.log(`๐ŸŒฟ Provisioning PR #${prNumber} into ${branchName}...`); + // Blobless clone: downloads history but no file contents until checked out. Extremely fast. + const cloneCmd = `git clone --filter=blob:none https://github.com/google-gemini/gemini-cli.git ${targetDir}`; + spawnSync(cloneCmd, { stdio: 'inherit', shell: true }); + + process.chdir(targetDir); + spawnSync('gh', ['pr', 'checkout', prNumber], { stdio: 'inherit' }); + } else { + process.chdir(targetDir); + } + + const logDir = path.join(targetDir, `.gemini/logs/review-${prNumber}`); + fs.mkdirSync(logDir, { recursive: true }); + + const GEMINI_CMD = path.join(workDir, 'node_modules/.bin/gemini'); + + // Use mirrored policy if available + const policyFile = path.join(process.env.HOME || '', '.gemini/policies/policy.toml'); + const policyFlag = fs.existsSync(policyFile) ? `--policy ${policyFile}` : ''; + + // 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}` }, + // Point the analysis at the PR directory specifically + { id: 'review', name: 'Gemini Analysis', cmd: `${GEMINI_CMD} ${policyFlag} --cwd ${targetDir} -p "/review-frontend ${prNumber}"` }, + { id: 'verify', name: 'Behavioral Proof', cmd: `${GEMINI_CMD} ${policyFlag} --cwd ${targetDir} -p "Analyze the code in ${targetDir} and exercise it."`, dep: 'build' } + ]; + + const state: Record = {}; + tasks.forEach(t => state[t.id] = { status: 'PENDING' }); + + function runTask(task: any) { + if (task.dep && state[task.dep].status !== 'SUCCESS') { + setTimeout(() => runTask(task), 1000); + return; + } + + state[task.id].status = 'RUNNING'; + const proc = spawn(task.cmd, { shell: true, env: { ...process.env, FORCE_COLOR: '1' } }); + const logStream = fs.createWriteStream(path.join(logDir, `${task.id}.log`)); + proc.stdout.pipe(logStream); + proc.stderr.pipe(logStream); + + proc.on('close', (code) => { + const exitCode = code ?? 0; + state[task.id].status = exitCode === 0 ? 'SUCCESS' : 'FAILED'; + // Write exit code for remote polling + fs.writeFileSync(path.join(logDir, `${task.id}.exit`), exitCode.toString()); + render(); + }); + } + + function render() { + console.clear(); + console.log(`==================================================`); + console.log(`๐Ÿš€ Deep Review | PR #${prNumber} | ${branchName}`); + console.log(`๐Ÿ“‚ PR Target: ${targetDir}`); + console.log(`==================================================\n`); + + tasks.forEach(t => { + const s = state[t.id]; + const icon = s.status === 'SUCCESS' ? 'โœ…' : s.status === 'FAILED' ? 'โŒ' : s.status === 'RUNNING' ? 'โณ' : '๐Ÿ’ค'; + console.log(` ${icon} ${t.name.padEnd(20)}: ${s.status}`); + }); + + const allDone = tasks.every(t => ['SUCCESS', 'FAILED'].includes(state[t.id].status)); + if (allDone) { + console.log(`\nโœจ Verification complete. Launching interactive session...`); + // cd into the targetDir for the final interactive session + process.chdir(targetDir); + process.exit(0); + } + } + + tasks.filter(t => !t.dep).forEach(runTask); + tasks.filter(t => t.dep).forEach(runTask); + setInterval(render, 1500); +} + +main().catch(console.error); diff --git a/package.json b/package.json index ca1b15ba41..5dccd10fa8 100644 --- a/package.json +++ b/package.json @@ -63,6 +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", "pre-commit": "node scripts/pre-commit.js" }, "overrides": {