diff --git a/index.js b/index.js index de6a2f3..9c74c2a 100644 --- a/index.js +++ b/index.js @@ -25,24 +25,13 @@ function readJsonSafe(p) { } function rejectPendingRun(statePath) { - try { - const { getRepoRoot } = require('./src/gep/paths'); - const { execSync } = require('child_process'); - const repoRoot = getRepoRoot(); - - execSync('git checkout -- .', { cwd: repoRoot, encoding: 'utf8', timeout: 30000 }); - execSync('git clean -fd', { cwd: repoRoot, encoding: 'utf8', timeout: 30000 }); - } catch (e) { - console.warn('[Loop] Pending run rollback failed: ' + (e.message || e)); - } - try { const state = readJsonSafe(statePath); if (state && state.last_run && state.last_run.run_id) { state.last_solidify = { run_id: state.last_run.run_id, rejected: true, - reason: 'loop_bridge_disabled_autoreject', + reason: 'loop_bridge_disabled_autoreject_no_rollback', timestamp: new Date().toISOString(), }; fs.writeFileSync(statePath, JSON.stringify(state, null, 2) + '\n', 'utf8'); @@ -184,7 +173,7 @@ async function main() { if (isPendingSolidify(stAfterRun)) { const cleared = rejectPendingRun(solidifyStatePath); if (cleared) { - console.warn('[Loop] Auto-rejected pending run because bridge is disabled in loop mode.'); + console.warn('[Loop] Auto-rejected pending run because bridge is disabled in loop mode (state only, no rollback).'); } } } @@ -528,3 +517,10 @@ async function main() { if (require.main === module) { main(); } + +module.exports = { + main, + readJsonSafe, + rejectPendingRun, + isPendingSolidify, +}; diff --git a/test/loopMode.test.js b/test/loopMode.test.js new file mode 100644 index 0000000..293b38d --- /dev/null +++ b/test/loopMode.test.js @@ -0,0 +1,70 @@ +const { describe, it, beforeEach, afterEach } = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('fs'); +const os = require('os'); +const path = require('path'); +const { rejectPendingRun } = require('../index.js'); + +describe('loop-mode auto reject', () => { + var tmpDir; + var originalRepoRoot; + var originalWorkspaceRoot; + var originalEvDir; + var originalMemoryDir; + var originalA2aHubUrl; + var originalHeartbeatMs; + var originalWorkerEnabled; + + beforeEach(() => { + tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'evolver-loop-test-')); + originalRepoRoot = process.env.EVOLVER_REPO_ROOT; + originalWorkspaceRoot = process.env.OPENCLAW_WORKSPACE; + originalEvDir = process.env.EVOLUTION_DIR; + originalMemoryDir = process.env.MEMORY_DIR; + originalA2aHubUrl = process.env.A2A_HUB_URL; + originalHeartbeatMs = process.env.HEARTBEAT_INTERVAL_MS; + originalWorkerEnabled = process.env.WORKER_ENABLED; + process.env.EVOLVER_REPO_ROOT = tmpDir; + process.env.OPENCLAW_WORKSPACE = tmpDir; + process.env.EVOLUTION_DIR = path.join(tmpDir, 'memory', 'evolution'); + process.env.MEMORY_DIR = path.join(tmpDir, 'memory'); + process.env.A2A_HUB_URL = ''; + process.env.HEARTBEAT_INTERVAL_MS = '3600000'; + delete process.env.WORKER_ENABLED; + }); + + afterEach(() => { + if (originalRepoRoot === undefined) delete process.env.EVOLVER_REPO_ROOT; + else process.env.EVOLVER_REPO_ROOT = originalRepoRoot; + if (originalWorkspaceRoot === undefined) delete process.env.OPENCLAW_WORKSPACE; + else process.env.OPENCLAW_WORKSPACE = originalWorkspaceRoot; + if (originalEvDir === undefined) delete process.env.EVOLUTION_DIR; + else process.env.EVOLUTION_DIR = originalEvDir; + if (originalMemoryDir === undefined) delete process.env.MEMORY_DIR; + else process.env.MEMORY_DIR = originalMemoryDir; + if (originalA2aHubUrl === undefined) delete process.env.A2A_HUB_URL; + else process.env.A2A_HUB_URL = originalA2aHubUrl; + if (originalHeartbeatMs === undefined) delete process.env.HEARTBEAT_INTERVAL_MS; + else process.env.HEARTBEAT_INTERVAL_MS = originalHeartbeatMs; + if (originalWorkerEnabled === undefined) delete process.env.WORKER_ENABLED; + else process.env.WORKER_ENABLED = originalWorkerEnabled; + fs.rmSync(tmpDir, { recursive: true, force: true }); + }); + + it('marks pending runs rejected without deleting untracked files', () => { + const stateDir = path.join(tmpDir, 'memory', 'evolution'); + fs.mkdirSync(stateDir, { recursive: true }); + fs.writeFileSync(path.join(stateDir, 'evolution_solidify_state.json'), JSON.stringify({ + last_run: { run_id: 'run_123' } + }, null, 2)); + fs.writeFileSync(path.join(tmpDir, 'PR_BODY.md'), 'keep me\n'); + const changed = rejectPendingRun(path.join(stateDir, 'evolution_solidify_state.json')); + + const state = JSON.parse(fs.readFileSync(path.join(stateDir, 'evolution_solidify_state.json'), 'utf8')); + assert.equal(changed, true); + assert.equal(state.last_solidify.run_id, 'run_123'); + assert.equal(state.last_solidify.rejected, true); + assert.equal(state.last_solidify.reason, 'loop_bridge_disabled_autoreject_no_rollback'); + assert.equal(fs.readFileSync(path.join(tmpDir, 'PR_BODY.md'), 'utf8'), 'keep me\n'); + }); +});