feat(deep-review): allow choice between pre-existing and isolated instances for Gemini and GH CLI

This commit is contained in:
mkorwel
2026-03-13 18:40:14 -07:00
parent 824225bfa3
commit d1aba65cb0
5 changed files with 237 additions and 53 deletions

View File

@@ -0,0 +1,148 @@
# --- CORE TOOLS ---
[[rule]]
toolName = "read_file"
decision = "allow"
priority = 100
[[rule]]
toolName = "write_file"
decision = "allow"
priority = 100
[[rule]]
toolName = "grep_search"
decision = "allow"
priority = 100
[[rule]]
toolName = "glob"
decision = "allow"
priority = 100
[[rule]]
toolName = "list_directory"
decision = "allow"
priority = 100
[[rule]]
toolName = "codebase_investigator"
decision = "allow"
priority = 100
# --- SHELL COMMANDS ---
# Git (Safe/Read-only)
[[rule]]
toolName = "run_shell_command"
commandPrefix = [
"git blame",
"git show",
"git grep",
"git show-ref",
"git ls-tree",
"git ls-remote",
"git reflog",
"git remote -v",
"git diff",
"git rev-list",
"git rev-parse",
"git merge-base",
"git cherry",
"git fetch",
"git status",
"git st",
"git branch",
"git br",
"git log",
"git --version"
]
decision = "allow"
priority = 100
# GitHub CLI (Read-only)
[[rule]]
toolName = "run_shell_command"
commandPrefix = [
"gh workflow list",
"gh auth status",
"gh checkout view",
"gh run view",
"gh run job view",
"gh run list",
"gh run --help",
"gh issue view",
"gh issue list",
"gh label list",
"gh pr diff",
"gh pr check",
"gh pr checks",
"gh pr view",
"gh pr list",
"gh pr status",
"gh repo view",
"gh job view",
"gh api",
"gh log"
]
decision = "allow"
priority = 100
# Node.js/NPM (Generic Tests, Checks, and Build)
[[rule]]
toolName = "run_shell_command"
commandPrefix = [
"npm run start",
"npm install",
"npm run",
"npm test",
"npm ci",
"npm list",
"npm --version"
]
decision = "allow"
priority = 100
# Core Utilities (Safe)
[[rule]]
toolName = "run_shell_command"
commandPrefix = [
"sleep",
"env",
"break",
"xargs",
"base64",
"uniq",
"sort",
"echo",
"which",
"ls",
"find",
"tail",
"head",
"cat",
"cd",
"grep",
"ps",
"pwd",
"wc",
"file",
"stat",
"diff",
"lsof",
"date",
"whoami",
"uname",
"du",
"cut",
"true",
"false",
"readlink",
"awk",
"jq",
"rg",
"less",
"more",
"tree"
]
decision = "allow"
priority = 100

View File

@@ -12,11 +12,12 @@ import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const prNumber = process.argv[2];
const branchName = process.argv[3];
const policyPath = process.argv[4];
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 <PR_NUMBER> <BRANCH_NAME>');
if (!prNumber || !branchName || !policyPath) {
console.error('Usage: tsx entrypoint.ts <PR_NUMBER> <BRANCH_NAME> <POLICY_PATH>');
process.exit(1);
}
@@ -27,9 +28,9 @@ async function main() {
const tsxBin = path.join(workDir, 'node_modules/.bin/tsx');
const geminiBin = path.join(workDir, 'node_modules/.bin/gemini');
// 1. Run the Parallel Worker
// 1. Run the Parallel Reviewer
console.log('🚀 Launching Parallel Review Worker...');
const workerResult = spawnSync(tsxBin, [path.join(__dirname, 'worker.ts'), prNumber, branchName], {
const workerResult = spawnSync(tsxBin, [path.join(__dirname, 'worker.ts'), prNumber, branchName, policyPath], {
stdio: 'inherit',
env: { ...process.env, GEMINI_CLI_HOME: ISOLATED_CONFIG }
});
@@ -41,12 +42,7 @@ async function main() {
// 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);
}
const geminiArgs = ['--policy', policyPath];
geminiArgs.push('-p', `Review for PR #${prNumber} is complete. Read the logs in .gemini/logs/review-${prNumber}/ and synthesize your findings.`);
process.chdir(targetDir);

View File

@@ -38,7 +38,7 @@ async function main() {
config = settings.maintainer.deepReview;
}
const { remoteHost, remoteWorkDir, terminalType, syncAuth } = config;
const { remoteHost, remoteWorkDir, terminalType, syncAuth, geminiSetup, ghSetup } = config;
console.log(`🔍 Fetching metadata for PR #${prNumber}...`);
const ghView = spawnSync('gh', ['pr', 'view', prNumber, '--json', 'headRefName', '-q', '.headRefName'], { shell: true });
@@ -50,11 +50,17 @@ async function main() {
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/`]);
// 2. Sync Configuration Mirror (Isolated Profiles)
const ISOLATED_GEMINI = geminiSetup === 'isolated' ? '~/.gemini-deep-review' : '~/.gemini';
const ISOLATED_GH = ghSetup === 'isolated' ? '~/.gh-deep-review' : '~/.config/gh';
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/`]);
// Sync the policy file specifically
spawnSync('rsync', ['-avz', path.join(REPO_ROOT, '.gemini/skills/deep-review/policy.toml'), `${remoteHost}:${remotePolicyPath}`]);
spawnSync('rsync', ['-avz', '--delete', path.join(REPO_ROOT, '.gemini/skills/deep-review/scripts/'), `${remoteHost}:${remoteWorkDir}/.gemini/skills/deep-review/scripts/`]);
if (syncAuth) {
@@ -63,19 +69,17 @@ async function main() {
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}`]);
if (fs.existsSync(lp)) spawnSync('rsync', ['-avz', lp, `${remoteHost}:${ISOLATED_GEMINI}/${f}`]);
}
const localPolicies = path.join(localGeminiDir, 'policies/');
if (fs.existsSync(localPolicies)) spawnSync('rsync', ['-avz', '--delete', localPolicies, `${remoteHost}:${ISOLATED_CONFIG}/policies/`]);
if (fs.existsSync(localPolicies)) spawnSync('rsync', ['-avz', '--delete', localPolicies, `${remoteHost}:${ISOLATED_GEMINI}/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 entryCmd = `cd ${remoteWorkDir} && ${envLoader} && 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 = `$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)}`;

View File

@@ -39,21 +39,32 @@ async function main() {
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 });
// 1. Gemini CLI Isolation Choice
const geminiChoice = await prompt('\nGemini CLI Setup: Use [p]re-existing instance or [i]solated sandbox instance? (Isolated is recommended)', 'i');
const geminiSetup = geminiChoice.toLowerCase() === 'p' ? 'preexisting' : 'isolated';
// 2. GitHub CLI Isolation Choice
const ghChoice = await prompt('GitHub CLI Setup: Use [p]re-existing instance or [i]solated sandbox instance? (Isolated is recommended)', 'i');
const ghSetup = ghChoice.toLowerCase() === 'p' ? 'preexisting' : 'isolated';
const ISOLATED_GEMINI_CONFIG = '~/.gemini-deep-review';
const ISOLATED_GH_CONFIG = '~/.gh-deep-review';
// System Requirements Check
const ghCheck = spawnSync('ssh', [remoteHost, 'sh -lc "command -v gh"'], { stdio: 'pipe' });
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 osCheck = spawnSync('ssh', [remoteHost, 'uname -s'], { stdio: 'pipe' });
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 });
spawnSync('ssh', ['-t', remoteHost, installCmd], { stdio: 'inherit' });
}
} else {
console.log('⚠️ Please ensure gh is installed before running again.');
@@ -61,21 +72,55 @@ async function main() {
}
}
// Ensure remote work dir exists
spawnSync('ssh', [remoteHost, `mkdir -p ${remoteWorkDir}`], { shell: true });
// Ensure remote work dir and isolated config dirs exist
spawnSync('ssh', [remoteHost, `mkdir -p ${remoteWorkDir} ${ISOLATED_GEMINI_CONFIG}/policies/ ${ISOLATED_GH_CONFIG}`], { stdio: 'pipe' });
// 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);
// GH Auth Check
const ghAuthCmd = ghSetup === 'isolated' ? `export GH_CONFIG_DIR=${ISOLATED_GH_CONFIG} && gh auth status` : 'gh auth status';
const remoteGHAuth = spawnSync('ssh', [remoteHost, `sh -lc "${ghAuthCmd}"`], { stdio: 'pipe' });
const isGHAuthRemote = remoteGHAuth.status === 0;
if (isGHAuthRemote) {
console.log(` ✅ GitHub CLI is already authenticated on remote (${ghSetup}).`);
} else {
console.log(` ❌ GitHub CLI is NOT authenticated on remote (${ghSetup}).`);
// If it's isolated but global is authenticated, offer to sync
if (ghSetup === 'isolated') {
const globalGHAuth = spawnSync('ssh', [remoteHost, 'sh -lc "gh auth status"'], { stdio: 'pipe' });
if (globalGHAuth.status === 0) {
if (await confirm(' Global GH auth found. Sync it to isolated instance?')) {
spawnSync('ssh', [remoteHost, `cp -r ~/.config/gh/* ${ISOLATED_GH_CONFIG}/`]);
console.log(' ✅ GH Auth synced.');
}
}
}
if (!isGHAuthRemote) console.log(' You may need to run "gh auth login" on the remote machine later.');
}
// Gemini Auth Check
const geminiAuthCheck = geminiSetup === 'isolated'
? `[ -f ${ISOLATED_GEMINI_CONFIG}/google_accounts.json ]`
: '[ -f ~/.gemini/google_accounts.json ]';
const remoteGeminiAuth = spawnSync('ssh', [remoteHost, `sh -lc "${geminiAuthCheck}"`], { stdio: 'pipe' });
const isGeminiAuthRemote = remoteGeminiAuth.status === 0;
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?');
if (isGeminiAuthRemote) {
console.log(` ✅ Gemini CLI is already authenticated on remote (${geminiSetup}).`);
} else {
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);
if (hasAuth || hasEnv) {
console.log(` 🔍 Found local Gemini CLI credentials: ${[hasAuth ? 'Google Account' : '', hasEnv ? '.env' : ''].filter(Boolean).join(', ')}`);
syncAuth = await confirm(' Would you like Gemini to automatically sync your local credentials to the remote workstation for seamless authentication?');
}
}
const terminalType = await prompt('\nTerminal Automation (iterm2 / terminal / none)', 'iterm2');
@@ -84,7 +129,6 @@ async function main() {
const envLoader = 'export NVM_DIR="$HOME/.nvm"; [ -s "$NVM_DIR/nvm.sh" ] && \\. "$NVM_DIR/nvm.sh"';
console.log(`\n📦 Checking isolated dependencies in ${remoteWorkDir}...`);
// Use a single string for ssh command to avoid quoting issues with spawnSync shell:true
const checkCmd = `ssh ${remoteHost} ${q(`${envLoader} && [ -x ${remoteWorkDir}/node_modules/.bin/tsx ] && [ -x ${remoteWorkDir}/node_modules/.bin/gemini ]`)}`;
const depCheck = spawnSync(checkCmd, { shell: true });
@@ -103,7 +147,7 @@ async function main() {
try { settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8')); } catch (e) {}
}
settings.maintainer = settings.maintainer || {};
settings.maintainer.deepReview = { remoteHost, remoteWorkDir, terminalType, syncAuth };
settings.maintainer.deepReview = { remoteHost, remoteWorkDir, terminalType, syncAuth, geminiSetup, ghSetup };
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');

View File

@@ -1,28 +1,28 @@
/**
* Universal Deep Review Worker (Remote)
*
* Handles worktree provisioning and parallel task execution.
*/
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];
const policyPath = process.argv[4];
async function main() {
if (!prNumber || !branchName) {
console.error('Usage: tsx worker.ts <PR_NUMBER> <BRANCH_NAME>');
if (!prNumber || !branchName || !policyPath) {
console.error('Usage: tsx worker.ts <PR_NUMBER> <BRANCH_NAME> <POLICY_PATH>');
process.exit(1);
}
const workDir = process.cwd(); // This is remoteWorkDir
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 });
@@ -35,19 +35,14 @@ async function main() {
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}` : '';
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}` },
// 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' }
{ 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' }
];
const state: Record<string, any> = {};
@@ -68,7 +63,6 @@ async function main() {
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();
});
@@ -90,8 +84,6 @@ async function main() {
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);
}
}