mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-05-12 21:03:05 -07:00
feat(workspaces): transform offload into repository-agnostic Gemini Workspaces
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
import { spawnSync } from 'child_process';
|
||||
import path from 'path';
|
||||
|
||||
export async function runFixPlaybook(prNumber: string, targetDir: string, policyPath: string, geminiBin: string) {
|
||||
console.log(`🚀 Workspace | FIX | PR #${prNumber}`);
|
||||
console.log('Switching to agentic fix loop inside Gemini CLI...');
|
||||
|
||||
// Use the nightly gemini binary to activate the fix-pr skill and iterate
|
||||
const result = spawnSync(geminiBin, [
|
||||
'--policy', policyPath,
|
||||
'--cwd', targetDir,
|
||||
'-p', `Please activate the 'fix-pr' skill and use it to iteratively fix PR #${prNumber}.
|
||||
Ensure you handle CI failures, merge conflicts, and unaddressed review comments
|
||||
until the PR is fully passing and mergeable.`
|
||||
], { stdio: 'inherit' });
|
||||
|
||||
return result?.status ?? 1;
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
import { TaskRunner } from '../TaskRunner.js';
|
||||
import path from 'path';
|
||||
import { spawnSync } from 'child_process';
|
||||
|
||||
import { TaskRunner } from '../TaskRunner.js';
|
||||
import path from 'path';
|
||||
import { spawnSync } from 'child_process';
|
||||
import fs from 'fs';
|
||||
|
||||
export async function runImplementPlaybook(issueNumber: string, workDir: string, policyPath: string, geminiBin: string) {
|
||||
console.log(`🚀 Workspace | IMPLEMENT (Supervisor Loop) | Issue #${issueNumber}`);
|
||||
|
||||
const ghView = spawnSync('gh', ['issue', 'view', issueNumber, '--json', 'title,body', '-q', '{title:.title,body:.body}'], { shell: true });
|
||||
const meta = JSON.parse(ghView.stdout.toString());
|
||||
const branchName = `impl/${issueNumber}-${meta.title.toLowerCase().replace(/[^a-z0-9]/g, '-')}`.slice(0, 50);
|
||||
|
||||
// 1. Initial Research & Test Creation
|
||||
console.log('\n🧠 Phase 1: Research & Reproduction...');
|
||||
spawnSync(geminiBin, [
|
||||
'--policy', policyPath, '--cwd', workDir,
|
||||
'-p', `Research Issue #${issueNumber}: "${meta.title}".
|
||||
Description: ${meta.body}.
|
||||
ACTION: Create a NEW Vitest test file in 'tests/repro_issue_${issueNumber}.test.ts' that demonstrates the issue or feature.
|
||||
Ensure this test fails currently.`
|
||||
], { stdio: 'inherit' });
|
||||
|
||||
// 2. The Self-Healing Loop
|
||||
let attempts = 0;
|
||||
const maxAttempts = 5;
|
||||
let success = false;
|
||||
|
||||
console.log('\n🛠️ Phase 2: Implementation Loop...');
|
||||
while (attempts < maxAttempts && !success) {
|
||||
attempts++;
|
||||
console.log(`\n👉 Attempt ${attempts}/${maxAttempts}...`);
|
||||
|
||||
// Run the specific repro test
|
||||
const testRun = spawnSync('npx', ['vitest', 'run', `tests/repro_issue_${issueNumber}.test.ts`], { cwd: workDir });
|
||||
|
||||
if (testRun.status === 0) {
|
||||
console.log('✅ Reproduction test PASSED!');
|
||||
success = true;
|
||||
break;
|
||||
}
|
||||
|
||||
console.log('❌ Test failed. Asking Gemini to fix the implementation...');
|
||||
const testError = testRun.stdout.toString() + testRun.stderr.toString();
|
||||
|
||||
spawnSync(geminiBin, [
|
||||
'--policy', policyPath, '--cwd', workDir,
|
||||
'-p', `The reproduction test for Issue #${issueNumber} is still failing.
|
||||
ERROR OUTPUT:
|
||||
${testError.slice(-2000)}
|
||||
|
||||
ACTION: Modify the source code to fix this error and make the test pass.
|
||||
Do not modify the test itself unless it has a syntax error.`
|
||||
], { stdio: 'inherit' });
|
||||
}
|
||||
|
||||
// 3. Final Verification
|
||||
if (success) {
|
||||
console.log('\n🧪 Phase 3: Final Verification...');
|
||||
const finalCheck = spawnSync('npm', ['test'], { cwd: workDir, stdio: 'inherit' });
|
||||
if (finalCheck.status === 0) {
|
||||
console.log('\n🎉 Implementation complete and verified!');
|
||||
spawnSync('git', ['add', '.'], { cwd: workDir });
|
||||
spawnSync('git', ['commit', '-m', `feat: implement issue #${issueNumber}`], { cwd: workDir });
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
console.error('\n❌ Supervisor: Failed to reach a passing state within retry limit.');
|
||||
return 1;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { TaskRunner } from '../TaskRunner.ts';
|
||||
import path from 'path';
|
||||
|
||||
export async function runReadyPlaybook(prNumber: string, targetDir: string, policyPath: string, geminiBin: string) {
|
||||
const runner = new TaskRunner(
|
||||
path.join(targetDir, `.gemini/logs/workspace-${prNumber}`),
|
||||
`🚀 Workspace | READY | PR #${prNumber}`
|
||||
);
|
||||
|
||||
runner.register([
|
||||
{ id: 'clean', name: 'Clean Workspace', cmd: `npm run clean && npm ci` },
|
||||
{ id: 'preflight', name: 'Full Preflight', cmd: `npm run preflight`, dep: 'clean' },
|
||||
{ id: 'conflicts', name: 'Main Conflict Check', cmd: `git fetch origin main && git merge-base --is-ancestor origin/main HEAD` }
|
||||
]);
|
||||
|
||||
return runner.run();
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import { TaskRunner } from '../TaskRunner.ts';
|
||||
import path from 'path';
|
||||
|
||||
export async function runReviewPlaybook(prNumber: string, targetDir: string, policyPath: string, geminiBin: string) {
|
||||
const runner = new TaskRunner(
|
||||
path.join(targetDir, `.gemini/logs/workspace-${prNumber}`),
|
||||
`🚀 Workspace | REVIEW | PR #${prNumber}`
|
||||
);
|
||||
|
||||
runner.register([
|
||||
{ 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: 'Workspaceed Review', cmd: `${geminiBin} --policy ${policyPath} --cwd ${targetDir} -p "Please activate the 'review-pr' skill and use it to conduct a behavioral review of PR #${prNumber}."` }
|
||||
]);
|
||||
|
||||
return runner.run();
|
||||
}
|
||||
Reference in New Issue
Block a user