Files
msd-core/tests/bug-214-phase-researcher-write-truncation-contract.test.cjs
Tom Boucher 82fb847754 fix(#214): make gsd-phase-researcher survive OpenCode write-tool truncation (#598)
* chore: wire docs/agents config into AGENTS.md Agent skills section

Add the `## Agent skills` discovery block pointing the engineering
skills at the existing docs/agents/{issue-tracker,triage-labels,domain}.md
files (issue tracker, triage label mapping, single-context domain docs).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(#214): make gsd-phase-researcher survive OpenCode write-tool truncation

OpenCode caps model output at OUTPUT_TOKEN_MAX=32000 and the thinking
budget shares that pool (upstream opencode#18108). A single oversized
`write` tool call for RESEARCH.md is truncated mid-payload, yielding
`JSON Parse error: Expected '}'`, which OpenCode misclassifies and then
doom-loops retrying identically. Short content writes fine; long
content fails 100% (reproducible, OpenCode 1.15.10).

Add a Step 6 write contract to agents/gsd-phase-researcher.md: keep the
single-Write default (no behavior change for Claude Code and other
runtimes that don't truncate), but on a truncation/invalid-tool failure
build the file incrementally via a sentinel-based Write -> Read -> Edit
sequence so no single tool-call payload is large enough to truncate;
never silently fall back to returning content (which truncates
identically). This is the upstream-recommended mitigation (write in
smaller chunks; use edit for follow-on writes).

Locked with a prompt-contract regression test mirroring the bug-222
write-contract pattern.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* chore(#214): add changeset for OpenCode write-truncation fix

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-01 21:42:08 -04:00

62 lines
1.9 KiB
JavaScript

// allow-test-rule: source-text-is-the-product
'use strict';
const { test, describe } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('node:fs');
const path = require('node:path');
const REPO_ROOT = path.resolve(__dirname, '..');
const RESEARCHER_PATH = path.join(REPO_ROOT, 'agents', 'gsd-phase-researcher.md');
function readResearcherPrompt() {
return fs.readFileSync(RESEARCHER_PATH, 'utf8');
}
describe('bug #214: phase researcher must survive OpenCode write-tool truncation', () => {
test('Step 6 documents the large-file / truncation fallback write contract', () => {
const prompt = readResearcherPrompt();
assert.match(
prompt,
/truncat/i,
'Step 6 must name the truncation failure mode.'
);
assert.match(
prompt,
/incrementa/i,
'Step 6 must instruct incremental construction on large files.'
);
assert.match(
prompt,
/<!-- gsd:write-continue -->/,
'Step 6 must define the continuation sentinel for incremental writes.'
);
assert.match(
prompt,
/do NOT retry the same oversized call/i,
'Step 6 must forbid identical retry of the oversized write (doom-loop guard).'
);
assert.match(
prompt,
/do NOT silently fall back to returning content/i,
'Step 6 must forbid silent fallback to returning content.'
);
assert.match(
prompt,
/`Read` the file, then `Edit`/i,
'Step 6 must require Read before Edit (OpenCode edit requires a prior Read).'
);
assert.match(
prompt,
/no trailing sentinel/i,
'Step 6 must instruct removing the sentinel on the final section.'
);
assert.match(
prompt,
/write the whole file in a single `Write` call/i,
'Step 6 must keep single-Write as the default path (no regression for non-truncating runtimes).'
);
});
});