diff --git a/.gemini/skills/offload/README.md b/.gemini/skills/offload/README.md index 662f456ab6..fe1f2677a6 100644 --- a/.gemini/skills/offload/README.md +++ b/.gemini/skills/offload/README.md @@ -51,7 +51,7 @@ 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. +This skill uses an isolated Gemini profile on the remote host (`~/.offload/gemini-cli-config`) to ensure that verification tasks do not interfere with your primary configuration. ### Directory structure - `scripts/orchestrator.ts`: Local orchestrator (syncs scripts and pops terminal). diff --git a/.gemini/skills/offload/SKILL.md b/.gemini/skills/offload/SKILL.md index fdae14888a..69fdf2d6f3 100644 --- a/.gemini/skills/offload/SKILL.md +++ b/.gemini/skills/offload/SKILL.md @@ -32,6 +32,6 @@ Provide a structured assessment based on the physical proof and logs: * **Conclusion**: A clear next step for the maintainer. ## Best Practices -* **Isolation First**: Always respect the user's isolation choices (`~/.gemini-deep-review`). +* **Isolation First**: Always respect the user's isolation choices (`~/.offload/gemini-cli-config`). * **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/offload/scripts/entrypoint.ts b/.gemini/skills/offload/scripts/entrypoint.ts index 2cc01161e3..65b5a95933 100644 --- a/.gemini/skills/offload/scripts/entrypoint.ts +++ b/.gemini/skills/offload/scripts/entrypoint.ts @@ -13,7 +13,7 @@ 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'); +const ISOLATED_CONFIG = process.env.GEMINI_CLI_HOME || path.join(process.env.HOME || '', '.offload/gemini-cli-config'); async function main() { if (!prNumber || !branchName || !policyPath) { diff --git a/.gemini/skills/offload/scripts/orchestrator.ts b/.gemini/skills/offload/scripts/orchestrator.ts index 8c32f36998..f48ebab184 100644 --- a/.gemini/skills/offload/scripts/orchestrator.ts +++ b/.gemini/skills/offload/scripts/orchestrator.ts @@ -51,9 +51,9 @@ export async function runOrchestrator(args: string[], env: NodeJS.ProcessEnv = p const sessionName = `offload-${prNumber}-${branchName.replace(/[^a-zA-Z0-9]/g, '-')}`; // 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`; + const ISOLATED_GEMINI = geminiSetup === 'isolated' ? '~/.offload/gemini-cli-config' : '~/.gemini'; + const ISOLATED_GH = ghSetup === 'isolated' ? '~/.offload/gh-cli-config' : '~/.config/gh'; + const remotePolicyPath = `${ISOLATED_GEMINI}/policies/offload-policy.toml`; console.log(`📡 Mirroring environment to ${remoteHost}...`); spawnSync('ssh', [remoteHost, `mkdir -p ${remoteWorkDir}/.gemini/skills/offload/scripts/ ${ISOLATED_GEMINI}/policies/`]); diff --git a/.gemini/skills/offload/scripts/setup.ts b/.gemini/skills/offload/scripts/setup.ts index f13195db7c..3b2248be2b 100644 --- a/.gemini/skills/offload/scripts/setup.ts +++ b/.gemini/skills/offload/scripts/setup.ts @@ -48,8 +48,8 @@ export async function runSetup(env: NodeJS.ProcessEnv = process.env) { 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'; + const ISOLATED_GEMINI_CONFIG = '~/.offload/gemini-cli-config'; + const ISOLATED_GH_CONFIG = '~/.offload/gh-cli-config'; console.log(`🔍 Checking state of ${remoteHost}...`); // Use a login shell to ensure the same PATH as the interactive user diff --git a/.gemini/skills/offload/tests/matrix.test.ts b/.gemini/skills/offload/tests/matrix.test.ts index a5c724b0ee..2c8c021da0 100644 --- a/.gemini/skills/offload/tests/matrix.test.ts +++ b/.gemini/skills/offload/tests/matrix.test.ts @@ -65,37 +65,17 @@ describe('Offload Tooling Matrix', () => { }); }); - describe('Fix Loop', () => { - it('should iterate until CI passes', async () => { - let checkAttempts = 0; - vi.mocked(spawnSync).mockImplementation((cmd: any, args: any) => { - // Correctly check command AND args - const isCheck = (typeof cmd === 'string' && cmd.includes('pr checks')) || - (Array.isArray(args) && args.includes('checks')); - - if (isCheck) { - checkAttempts++; - return { status: 0, stdout: Buffer.from(checkAttempts === 1 ? 'fail' : 'success') } as any; - } - return { status: 0, stdout: Buffer.from('test-branch\n') } as any; - }); - - vi.useFakeTimers(); + describe('Fix Playbook', () => { + it('should launch the agentic fix-pr skill', async () => { + vi.mocked(fs.existsSync).mockReturnValue(true); - const workerPromise = runWorker(['123', 'test-branch', '/path/policy', 'fix']); + await runWorker(['123', 'test-branch', '/path/policy', 'fix']); - // Multi-stage timer flush to get through TaskRunner cycles and the polling loop - for(let i=0; i<10; i++) { - await vi.advanceTimersByTimeAsync(2000); - } - - await vi.advanceTimersByTimeAsync(40000); // 1st fail - for(let i=0; i<10; i++) { await vi.advanceTimersByTimeAsync(2000); } - await vi.advanceTimersByTimeAsync(40000); // 2nd pass - - await workerPromise; - expect(checkAttempts).toBe(2); - vi.useRealTimers(); + const spawnSyncCalls = vi.mocked(spawnSync).mock.calls; + const fixCall = spawnSyncCalls.find(call => + JSON.stringify(call).includes("activate the 'fix-pr' skill") + ); + expect(fixCall).toBeDefined(); }); }); }); diff --git a/.gemini/skills/offload/tests/orchestration.test.ts b/.gemini/skills/offload/tests/orchestration.test.ts index eccc145131..4c385c4d41 100644 --- a/.gemini/skills/offload/tests/orchestration.test.ts +++ b/.gemini/skills/offload/tests/orchestration.test.ts @@ -78,9 +78,32 @@ describe('Offload Orchestration', () => { await runOrchestrator(['123'], {}); const spawnCalls = vi.mocked(spawnSync).mock.calls; const sshCall = spawnCalls.find(call => typeof call[0] === 'string' && call[0].includes('tmux new-session')); - // Match the new 'offload-123-test-branch' format expect(sshCall![0]).toContain('offload-123-test-branch'); }); + + it('should use isolated config path when geminiSetup is isolated', async () => { + const isolatedSettings = { + ...mockSettings, + maintainer: { + ...mockSettings.maintainer, + deepReview: { + ...mockSettings.maintainer.deepReview, + geminiSetup: 'isolated' + } + } + }; + vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(isolatedSettings)); + + await runOrchestrator(['123'], {}); + + const spawnCalls = vi.mocked(spawnSync).mock.calls; + const sshCall = spawnCalls.find(call => { + const cmdStr = typeof call[0] === 'string' ? call[0] : ''; + return cmdStr.includes('GEMINI_CLI_HOME=~/.offload/gemini-cli-config'); + }); + + expect(sshCall).toBeDefined(); + }); }); describe('setup.ts', () => { @@ -99,6 +122,8 @@ describe('Offload Orchestration', () => { const remoteCmd = args[1]; if (remoteCmd.includes('[ -d ~/test-dir/.git ]')) return { status: 0 } as any; if (remoteCmd.includes('command -v')) return { status: 0 } as any; + if (remoteCmd.includes('gh auth status')) return { status: 0 } as any; + if (remoteCmd.includes('google_accounts.json')) return { status: 0 } as any; } return { status: 0, stdout: Buffer.from(''), stderr: Buffer.from('') } as any; }); @@ -122,15 +147,15 @@ describe('Offload Orchestration', () => { vi.mocked(fs.existsSync).mockReturnValue(true); await runWorker(['123', 'test-branch', '/test-policy.toml', 'review']); const spawnCalls = vi.mocked(spawn).mock.calls; - expect(spawnCalls.some(c => c[0].includes('/review-frontend'))).toBe(true); + expect(spawnCalls.some(c => c[0].includes("activate the 'review-pr' skill"))).toBe(true); }); 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; - // Match the updated prompt string in fix.ts - expect(spawnCalls.some(c => c[0].toLowerCase().includes('analyze current failures'))).toBe(true); + // runFixPlaybook uses spawnSync + const spawnSyncCalls = vi.mocked(spawnSync).mock.calls; + expect(spawnSyncCalls.some(c => JSON.stringify(c).includes("activate the 'fix-pr' skill"))).toBe(true); }); });