test(#1973): consolidate 48 workflow regression tests into workflow-aspect suites
Fold 48 issue-named workflow-markdown regression files into the canonical test that owns each workflow aspect (execute-phase-*, plan-phase-*, quick-*, discuss-*, worktree-cleanup, secure-phase, verify, update, settings, etc.), across 32 existing suites. Verbatim block-scoped describe wrappers; 281 subtests conserved 1:1. No new test files. Host-env pre-check: only gsd-settings-advanced spawns CLI and it sets no GSD_WORKSTREAM/GSD_PROJECT value — no leak risk. Regenerates regression-name allowlist (222->181), ratchets file-count allowlist (verify 11->10), makes 30 relocated allow-test-rule exemptions issue-ref-compliant (ADR-456; prunes 30 stale ids). lint:ci green. Part of epic #1969. Closes #1973. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -186,3 +186,128 @@ describe('research-agent-profiles: checkAgent malformed profile', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
// ────────────────────────────────────────────────────────────────────────
|
||||
// Folded from tests/bug-222-research-synthesizer-write-contract.test.cjs — consolidation epic #1969 (B4 #1973)
|
||||
// ────────────────────────────────────────────────────────────────────────
|
||||
{
|
||||
const { describe: __foldDescribe } = require('node:test');
|
||||
__foldDescribe("folded:bug-222-research-synthesizer-write-contract (consolidation epic #1969 B4 #1973)", () => {
|
||||
// allow-test-rule: source-text-is-the-product (see #222)
|
||||
'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 SYNTHESIZER_PATH = path.join(REPO_ROOT, 'agents', 'gsd-research-synthesizer.md');
|
||||
|
||||
function readSynthesizerPrompt() {
|
||||
return fs.readFileSync(SYNTHESIZER_PATH, 'utf8');
|
||||
}
|
||||
|
||||
describe('bug #222: research synthesizer must write SUMMARY.md via Write tool', () => {
|
||||
test('step 6 has explicit hard-rule block forbidding return-message content fallback', () => {
|
||||
const prompt = readSynthesizerPrompt();
|
||||
|
||||
assert.match(
|
||||
prompt,
|
||||
/canonical output of this agent[\s\S]*existing on disk after you return/i,
|
||||
'Step 6 must define SUMMARY.md-on-disk as canonical output.'
|
||||
);
|
||||
assert.match(
|
||||
prompt,
|
||||
/Hard rules \(must follow\):/i,
|
||||
'Step 6 must contain explicit hard rules block.'
|
||||
);
|
||||
assert.match(
|
||||
prompt,
|
||||
/Use the `Write` tool[\s\S]*there are no restrictions/i,
|
||||
'Rule 1 must force Write tool usage and reject hallucinated restrictions.'
|
||||
);
|
||||
assert.match(
|
||||
prompt,
|
||||
/Do NOT return the SUMMARY\.md content in your response/i,
|
||||
'Rule 2 must forbid returning SUMMARY content in the response body.'
|
||||
);
|
||||
assert.match(
|
||||
prompt,
|
||||
/Do NOT ask permission to write/i,
|
||||
'Rule 3 must forbid write-permission asks for this agent.'
|
||||
);
|
||||
assert.match(
|
||||
prompt,
|
||||
/Do NOT use `Bash\(cat << 'EOF'\)` or heredoc/i,
|
||||
'Rule 4 must forbid heredoc/Bash file creation fallback.'
|
||||
);
|
||||
assert.match(
|
||||
prompt,
|
||||
/If the Write tool errors[\s\S]*Do not silently fall back to returning content/i,
|
||||
'Rule 5 must force explicit error reporting for Write failures.'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('bug #222 recurrence: orchestrator self-heals when synthesizer returns SUMMARY.md inline', () => {
|
||||
const WORKFLOWS = [
|
||||
path.join(REPO_ROOT, 'gsd-core', 'workflows', 'new-project.md'),
|
||||
path.join(REPO_ROOT, 'gsd-core', 'workflows', 'new-milestone.md'),
|
||||
];
|
||||
|
||||
for (const wf of WORKFLOWS) {
|
||||
const name = path.basename(wf);
|
||||
|
||||
test(`${name} has the #222 synthesizer SUMMARY.md self-heal guard`, () => {
|
||||
const text = fs.readFileSync(wf, 'utf8');
|
||||
|
||||
// Marker tying the guard to the issue
|
||||
assert.match(text, /#222[^\n]*self-heal|self-heal[^\n]*#222/i,
|
||||
`${name} must contain a #222-tagged self-heal guard after the synthesizer returns.`);
|
||||
|
||||
// Verifies the file exists AND is substantive/non-empty
|
||||
assert.match(text, /SUMMARY\.md[\s\S]{0,120}?(non-empty|substantive|exists)/i,
|
||||
`${name} must verify .planning/research/SUMMARY.md exists AND is substantive — non-empty.`);
|
||||
|
||||
// Truncation/validator guard: references the continuation sentinel OR the verify-summary CLI
|
||||
assert.match(text, /gsd:write-continue|verify-summary/i,
|
||||
`${name} must guard against truncated/invalid SUMMARY.md (sentinel or verify-summary).`);
|
||||
|
||||
// Self-heal must commit ALL research artifacts, not just SUMMARY.md
|
||||
assert.match(text, /--files \.planning\/research\//,
|
||||
`${name} self-heal must commit ALL research artifacts, not just SUMMARY.md.`);
|
||||
|
||||
// Persists inline-returned document via Write rather than trusting the agent
|
||||
assert.match(text, /returned[\s\S]{0,200}?document[\s\S]{0,200}?Write tool/i,
|
||||
`${name} must instruct the orchestrator to persist inline-returned document with the Write tool.`);
|
||||
|
||||
// Must not proceed to roadmapper against a missing or incomplete SUMMARY.md
|
||||
assert.match(text, /gsd-roadmapper[\s\S]{0,200}?(missing|incomplete|do NOT)/i,
|
||||
`${name} must block spawning gsd-roadmapper when SUMMARY.md is missing or incomplete.`);
|
||||
|
||||
// Must name the FULL SUMMARY template markers so the orchestrator persists the real
|
||||
// document, not the brief structured return (resolves the HIGH finding).
|
||||
assert.match(text, /# Project Research Summary[\s\S]{0,260}?## Sources/,
|
||||
`${name}: self-heal must name the full SUMMARY.md template markers (# Project Research Summary … ## Sources).`);
|
||||
// Must reference the brief confirmation marker it must NOT mistake for file content.
|
||||
assert.match(text, /## SYNTHESIS COMPLETE/,
|
||||
`${name}: self-heal must distinguish the brief ## SYNTHESIS COMPLETE confirmation from the real document.`);
|
||||
});
|
||||
|
||||
test(`${name} runs the #222 self-heal AFTER the synthesizer and BEFORE gsd-roadmapper`, () => {
|
||||
const text = fs.readFileSync(wf, 'utf8');
|
||||
const synthIdx = text.indexOf('subagent_type="gsd-research-synthesizer"');
|
||||
const healIdx = text.indexOf('Synthesizer output self-heal (#222)');
|
||||
const roadIdx = text.indexOf('subagent_type="gsd-roadmapper"');
|
||||
assert.ok(synthIdx >= 0, `${name}: synthesizer dispatch not found`);
|
||||
assert.ok(healIdx >= 0, `${name}: #222 self-heal block not found`);
|
||||
assert.ok(roadIdx >= 0, `${name}: gsd-roadmapper dispatch not found`);
|
||||
assert.ok(healIdx > synthIdx, `${name}: self-heal must come AFTER the synthesizer dispatch`);
|
||||
assert.ok(roadIdx > healIdx, `${name}: self-heal must come BEFORE the gsd-roadmapper dispatch`);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user