* 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>
This commit is contained in:
5
.changeset/graceful-quails-hop.md
Normal file
5
.changeset/graceful-quails-hop.md
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
type: Fixed
|
||||
pr: 598
|
||||
---
|
||||
**`gsd-phase-researcher` no longer fails to write `RESEARCH.md` on OpenCode** — large research files that previously hit `JSON parsing failed: Expected '}'` and a retry doom-loop now write reliably. The agent keeps its single-write default and falls back to incremental section-by-section writes only when a runtime truncates an oversized tool call.
|
||||
14
AGENTS.md
14
AGENTS.md
@@ -39,3 +39,17 @@ Every PR must link an approved or confirmed issue with `Closes #123`, `Fixes #12
|
||||
## Security & Configuration Tips
|
||||
|
||||
Do not commit secrets, local config, or generated worktree artifacts. Before release-facing changes, run the relevant scan scripts in `scripts/`, especially `secret-scan.sh`, `base64-scan.sh`, and `prompt-injection-scan.sh`.
|
||||
|
||||
## Agent skills
|
||||
|
||||
### Issue tracker
|
||||
|
||||
Issues live in GitHub Issues at `open-gsd/gsd-core` (via the `gh` CLI, always with `--repo open-gsd/gsd-core`). See `docs/agents/issue-tracker.md`.
|
||||
|
||||
### Triage labels
|
||||
|
||||
Five canonical triage roles mapped to this repo's labels — `needs-info`→`needs-reproduction`, `ready-for-agent`→`confirmed`, `ready-for-human`→`approved-enhancement`/`approved-feature`, others default. See `docs/agents/triage-labels.md`.
|
||||
|
||||
### Domain docs
|
||||
|
||||
Single-context — `CONTEXT.md` (domain glossary + recurring PR rules) and `docs/adr/` at the repo root. See `docs/agents/domain.md`.
|
||||
|
||||
@@ -804,6 +804,19 @@ List missing test files, framework config, or shared fixtures needed before impl
|
||||
|
||||
Use the Write tool to create files — never use `Bash(cat << 'EOF')` or heredoc commands for file creation. This rule applies regardless of `commit_docs` setting.
|
||||
|
||||
**Write contract (hard rules — must follow):**
|
||||
|
||||
This file is the canonical output of this agent. The orchestrator reads `$PHASE_DIR/$PADDED_PHASE-RESEARCH.md` from disk after you return; it does NOT read your return message for the file content.
|
||||
|
||||
1. **Default: write the whole file in a single `Write` call.** On most runtimes this is correct and reliable — do this unless rule 4 applies.
|
||||
2. **Do NOT return the RESEARCH.md content in your response.** Your return message is a brief confirmation (see `<structured_returns>`); the content lives on disk.
|
||||
3. **Do NOT use `Bash(cat << 'EOF')` or heredoc** for file creation. Use the `Write` tool.
|
||||
4. **Large-file / truncation fallback.** Some runtimes (e.g. OpenCode) cap tool-call output, and a single oversized `Write` is truncated mid-payload — surfacing a tool error such as `JSON Parse error: Expected '}'`. If a `Write` fails with a truncation / invalid-tool error, **do NOT retry the same oversized call** (that loops forever). Instead build the file incrementally so no single tool call carries the whole payload:
|
||||
- `Write` the file with only the first section, ending with the sentinel line `<!-- gsd:write-continue -->`.
|
||||
- `Read` the file, then `Edit` it, replacing `<!-- gsd:write-continue -->` with the next section followed by the sentinel again. Repeat, one section per `Edit`.
|
||||
- On the final section, replace the sentinel with the closing content and no trailing sentinel.
|
||||
5. **If writing still fails, surface the actual error in your return message.** **Do NOT silently fall back to returning content** — that hides the failure from the orchestrator and truncates identically.
|
||||
|
||||
**If CONTEXT.md exists, FIRST content section MUST be `<user_constraints>`:**
|
||||
|
||||
```markdown
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
// 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).'
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user