From ca330638cc5102b03622814b1bbe0b0cc555d18b Mon Sep 17 00:00:00 2001 From: mark-dingwall Date: Thu, 9 Apr 2026 12:44:16 +1000 Subject: [PATCH 1/2] fix: mkdir -p output directory before writing report/document When --output points to a path whose parent directory doesn't exist, the cluster fails at completion. Create the directory tree first. Co-Authored-By: Claude Opus 4.6 (1M context) --- scripts/assemble-doc.js | 1 + scripts/write-review-report.js | 1 + 2 files changed, 2 insertions(+) diff --git a/scripts/assemble-doc.js b/scripts/assemble-doc.js index 9005e48..b233360 100644 --- a/scripts/assemble-doc.js +++ b/scripts/assemble-doc.js @@ -194,6 +194,7 @@ async function main() { const filepath = process.env.ZEROSHOT_OUTPUT_FILE || path.join(process.cwd(), `${docType}_${clusterId}.md`); + fs.mkdirSync(path.dirname(filepath), { recursive: true }); fs.writeFileSync(filepath, markdown, 'utf-8'); console.log(`Document written: ${filepath}`); } finally { diff --git a/scripts/write-review-report.js b/scripts/write-review-report.js index b10581b..9653481 100644 --- a/scripts/write-review-report.js +++ b/scripts/write-review-report.js @@ -183,6 +183,7 @@ async function main() { const filepath = process.env.ZEROSHOT_OUTPUT_FILE || path.join(process.cwd(), `${assessment}_${clusterId}.md`); + fs.mkdirSync(path.dirname(filepath), { recursive: true }); fs.writeFileSync(filepath, markdown, 'utf-8'); console.log(`Report written: ${filepath}`); } From 28a2a76fff3a670b7b57e894522c28ff492e685e Mon Sep 17 00:00:00 2001 From: mark-dingwall Date: Thu, 9 Apr 2026 12:44:34 +1000 Subject: [PATCH 2/2] fix: stub execSync in execute_system_command tests to prevent CI EPIPE flake MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The two tests calling real shell commands (cat, echo) intermittently fail with spawnSync EPIPE in CI. Stub safeExec.execSync and assert on the args/env passed instead — matches the pattern used by the other tests in the same file. Co-Authored-By: Claude Opus 4.6 (1M context) --- tests/execute-system-command-trigger.test.js | 56 ++++++++++++-------- 1 file changed, 34 insertions(+), 22 deletions(-) diff --git a/tests/execute-system-command-trigger.test.js b/tests/execute-system-command-trigger.test.js index 0ad2c7a..7412c94 100644 --- a/tests/execute-system-command-trigger.test.js +++ b/tests/execute-system-command-trigger.test.js @@ -29,46 +29,58 @@ describe('execute_system_command trigger action', function () { it('should pipe message.content as JSON to stdin', async function () { const agent = createMockAgent(); const content = { text: 'hello', data: { foo: 'bar' } }; + const stub = sinon.stub(safeExec, 'execSync').returns('stubbed\n'); - // Use a command that reads stdin and echoes it const trigger = { action: 'execute_system_command', config: { command: 'cat' }, }; const message = { content, topic: 'TEST' }; - await executeTriggerAction(agent, trigger, message); + try { + await executeTriggerAction(agent, trigger, message); - // cat echoes stdin to stdout, which gets logged - assert.ok( - agent.logs.some((l) => l.includes(JSON.stringify(content))), - `Expected logs to contain stringified content. Logs: ${agent.logs.join('\n')}` - ); + assert.ok(stub.calledOnce, 'execSync should be called once'); + const opts = stub.firstCall.args[1]; + assert.strictEqual( + opts.input, + JSON.stringify(content), + 'stdin should be JSON-serialized content' + ); + } finally { + stub.restore(); + } }); it('should set ZEROSHOT_ROOT and CLUSTER_ID env vars', async function () { const agent = createMockAgent(); + const stub = sinon.stub(safeExec, 'execSync').returns('ok\n'); + const trigger = { action: 'execute_system_command', - config: { - command: 'echo "ROOT=$ZEROSHOT_ROOT CLUSTER=$CLUSTER_ID"', - }, + config: { command: 'echo test' }, }; const message = { content: { text: 'test' }, topic: 'TEST' }; - await executeTriggerAction(agent, trigger, message); + try { + await executeTriggerAction(agent, trigger, message); - const expectedRoot = path.join(__dirname, '..'); - const outputLog = agent.logs.find((l) => l.includes('System command output:')); - assert.ok(outputLog, `Expected output log. Logs: ${agent.logs.join('\n')}`); - assert.ok( - outputLog.includes(`ROOT=${expectedRoot}`), - `Expected ZEROSHOT_ROOT in output: ${outputLog}` - ); - assert.ok( - outputLog.includes('CLUSTER=test-cluster-123'), - `Expected CLUSTER_ID in output: ${outputLog}` - ); + assert.ok(stub.calledOnce, 'execSync should be called once'); + const opts = stub.firstCall.args[1]; + const expectedRoot = path.join(__dirname, '..'); + assert.strictEqual( + opts.env.ZEROSHOT_ROOT, + expectedRoot, + 'ZEROSHOT_ROOT should be package root' + ); + assert.strictEqual( + opts.env.CLUSTER_ID, + 'test-cluster-123', + 'CLUSTER_ID should match cluster id' + ); + } finally { + stub.restore(); + } }); describe('success-path state transitions (stubbed execSync)', function () {