diff --git a/test/gh-aw-quality.test.ts b/test/gh-aw-quality.test.ts index 1d9abe10a..63976f3f9 100644 --- a/test/gh-aw-quality.test.ts +++ b/test/gh-aw-quality.test.ts @@ -594,3 +594,41 @@ describe('gh-aw: Plan Activate hardening behaviors', () => { expect(content).toMatch(/degrade gracefully|graceful/i); }); }); + +// --------------------------------------------------------------------------- +// Test: Plan Activate atomic task-call contract (#1678 / run 31555893180) +// --------------------------------------------------------------------------- + +describe('gh-aw: Plan Activate atomic task-call contract', () => { + const content = readFileSync(SQUAD_WORKFLOW, 'utf8'); + + it('2c contains explicit ATOMIC CONTRACT heading', () => { + expect(content).toMatch(/ATOMIC CONTRACT/i); + }); + + it('2c requires immediate call after each task body (one compose → one call)', () => { + // Must encode the sequential compose/call/verify pattern + expect(content).toMatch(/compose.*call.*verify|compose only that task.*call.*immediately/i); + }); + + it('2c explicitly forbids batching task bodies before calls', () => { + expect(content).toMatch(/DO NOT.*compose.*batch|DO NOT.*buffer|not.*compose.*multiple.*before/i); + }); + + it('2c task body is compact: one sentence scope, 1-2 acceptance criteria', () => { + expect(content).toMatch(/one sentence.*scope|1-2 acceptance criteria/i); + }); + + it('2d incomplete fallback calls report_incomplete with created/expected counts', () => { + expect(content).toContain('report_incomplete'); + expect(content).toMatch(/created.*expected|expected.*created/i); + }); + + it('2d incomplete fallback never noops on count mismatch', () => { + expect(content).toMatch(/never noop/i); + }); + + it('2d states re-run is idempotent via title match', () => { + expect(content).toMatch(/idempotent via title match/i); + }); +}); diff --git a/workflows/squad.md b/workflows/squad.md index a1889904e..36860d66f 100644 --- a/workflows/squad.md +++ b/workflows/squad.md @@ -743,14 +743,19 @@ Root → Epics → Tasks. Phase-specific: filter to matching phase heading. **⚠️ DO NOT STOP after epics. Tasks MUST follow immediately.** **2c. Create Task Issues:** `create-issue` per task in dependency order. + +> **⚠️ ATOMIC CONTRACT — strictly one task at a time:** +> For each task: compose ONLY that task's body → call `create-issue` immediately → verify the returned issue number → then move to the next task. +> **DO NOT** compose or buffer multiple task bodies before making calls. One compose → one call → one verify, repeated per task. + - Title: task title - Labels: `squad` (0075ca), `squad:{agent}` (e4e669). No `size:*` labels unless policy says so. -- Body: scope (2-3 sentences), acceptance criteria, context (parent epic, size, deps) +- Body: one sentence describing scope; 1-2 acceptance criteria; one compact context line (parent epic, size, deps) - Parent: sub-issue of EPIC (not root) - Milestone: same as parent epic - Size: Project field if available, else body line -**2d. Self-Validation:** Compare created counts vs expected. If mismatch: report partial activation with counts, note idempotent re-run. +**2d. Self-Validation:** Compare created/recognized task count vs expected. If created count is below expected: call `report_incomplete` immediately with `created={N}`, `expected={M}`, and the last verified issue number — never noop. Re-runs are idempotent via title match. Labels must have descriptions and intentional colors.