diff --git a/.changeset/issue-driven-orchestration.md b/.changeset/issue-driven-orchestration.md new file mode 100644 index 000000000..89244914f --- /dev/null +++ b/.changeset/issue-driven-orchestration.md @@ -0,0 +1,5 @@ +--- +type: Added +pr: 2840 +--- +**`docs/issue-driven-orchestration.md` — recipe for driving GSD from a tracker issue** — new guide that maps Symphony-style orchestration concepts (workflow, isolated agent workspace, proof-of-work, human review gate, follow-up capture) onto existing GSD primitives (`/gsd-new-workspace`, `/gsd-manager`, `/gsd-autonomous`, `/gsd-verify-work`, `/gsd-review`, `/gsd-ship`, `STATE.md`, phase artifacts). Documentation only — no new commands, no daemon, no tracker integration. diff --git a/docs/README.md b/docs/README.md index 96e76de3d..041417284 100644 --- a/docs/README.md +++ b/docs/README.md @@ -15,6 +15,7 @@ Language versions: [English](README.md) · [Português (pt-BR)](pt-BR/README.md) | [CLI Tools Reference](CLI-TOOLS.md) | Contributors, agent authors | `gsd-tools.cjs` programmatic API for workflows and agents | | [Agent Reference](AGENTS.md) | Contributors, advanced users | Role cards for primary agents — roles, tools, spawn patterns (the `agents/` filesystem is authoritative) | | [User Guide](USER-GUIDE.md) | All users | Workflow walkthroughs, troubleshooting, and recovery | +| [Issue-Driven Orchestration](issue-driven-orchestration.md) | All users | Recipe for driving GSD from a tracker issue (GitHub / Linear / Jira) using existing primitives — no new commands or daemon | | [Context Monitor](context-monitor.md) | All users | Context window monitoring hook architecture | | [Discuss Mode](workflow-discuss-mode.md) | All users | Assumptions vs interview mode for discuss-phase | diff --git a/docs/USER-GUIDE.md b/docs/USER-GUIDE.md index c48f271ec..23e337de2 100644 --- a/docs/USER-GUIDE.md +++ b/docs/USER-GUIDE.md @@ -18,6 +18,11 @@ A detailed reference for workflows, troubleshooting, and configuration. For quic - [Troubleshooting](#troubleshooting) - [Recovery Quick Reference](#recovery-quick-reference) +For driving GSD directly from a GitHub / Linear / Jira issue, see the +[Issue-Driven Orchestration guide](issue-driven-orchestration.md) — a +recipe that maps tracker issues onto the workspace → discuss → plan → +execute → verify → review → ship loop using existing GSD primitives. + --- ## End-to-End Walkthrough diff --git a/docs/issue-driven-orchestration.md b/docs/issue-driven-orchestration.md new file mode 100644 index 000000000..1f8c868f8 --- /dev/null +++ b/docs/issue-driven-orchestration.md @@ -0,0 +1,183 @@ +# Issue-Driven Orchestration with GSD + +**Status:** stable workflow guide +**Audience:** developers who track work in GitHub Issues, Linear, Jira, or +similar issue trackers and want to drive AI-assisted implementation +through GSD's existing primitives. + +## What this guide is + +A recipe for combining commands GSD already ships into an issue-tracker +→ workspace → plan/execute → verify/review → PR loop. It is documentation +only. No new commands, no daemon, no tracker integration — every command +referenced below already exists in GSD today. + +The shape is inspired by OpenAI's open-source [Symphony orchestration +reference](https://openai.com/index/open-source-codex-orchestration-symphony/) +([repository](https://github.com/openai/symphony)). GSD does not vendor or +wrap Symphony. The orchestration *concepts* map cleanly onto primitives +GSD already exposes; this guide just spells the mapping out so you can +adopt the pattern without writing glue code or bypassing GSD's safety +gates. + +## Why this exists + +GSD has the building blocks for issue-driven AI development — +`/gsd-new-workspace`, `/gsd-manager`, `/gsd-autonomous`, `/gsd-verify-work`, +`/gsd-review`, `/gsd-ship`, plus `STATE.md` and the phase artifact suite +— but no guide that walks through how to drive them from a single tracker +issue without writing custom orchestration scripts. Without that guide +the failure modes are: + +- Underuse: developers run discuss/plan/execute manually and never reach + for `/gsd-manager` or `/gsd-autonomous` even when their work pattern + fits. +- Workaround scripts: developers wire ad-hoc shell loops between their + tracker and `claude` invocations, bypassing `STATE.md`, the phase + manifest, and the verification gates. + +This guide makes the canonical loop discoverable. + +## Concept mapping + +Each row maps a Symphony-style orchestration concept to the GSD primitive +that already serves it. Use this table as a translation key when reading +Symphony docs, blog posts, or third-party orchestration write-ups. + +| Symphony concept | GSD primitive | +|---|---| +| `WORKFLOW.md` (top-level intent) | `ROADMAP.md` (project intent), `STATE.md` (live status), phase `CONTEXT.md` (per-phase scope), phase `PLAN.md` (executable steps) | +| One isolated agent workspace per task | `/gsd-new-workspace --strategy worktree` | +| Agent dispatch and concurrency | `/gsd-manager` (interactive dashboard), `/gsd-autonomous` (unattended) | +| Per-phase plan and discuss steps | `/gsd-discuss-phase` → `/gsd-plan-phase` → `/gsd-execute-phase` | +| Proof-of-work / test evidence | `/gsd-verify-work` (UAT.md persisted across `/clear`) | +| Adversarial review | `/gsd-review` (cross-AI peer review of plans) | +| Human merge gate | `/gsd-ship` (creates PR, optional code review, prepares merge) | +| Follow-up capture | `/gsd-note`, `/gsd-plant-seed`, `/gsd-new-milestone`, or a manually opened tracker issue | +| Concurrency control | Manager / background-agent semantics (no always-on poller) | + +The mapping is one-way: GSD owns the safety gates (verification, human +review, explicit confirmation for follow-up creation). Symphony's +"continuous orchestration" framing is intentionally not adopted — see +[Non-goals](#non-goals). + +## End-to-end flow + +The canonical issue → PR loop, written so it can run from a single +tracker issue end-to-end. Replace bracketed placeholders before running. + +1. **Pick the tracker issue.** Choose one issue from your tracker (GitHub, + Linear, etc.) that is well-scoped enough for autonomous implementation + — bounded scope, observable acceptance criteria, no upstream + dependencies that block execution. +2. **Map to a GSD phase.** If the issue maps onto an existing phase in + `ROADMAP.md`, select it. If not, run `/gsd-new-milestone` (for a new + milestone of related issues) or open a phase via `/gsd-add-phase` / + `/gsd-insert-phase`. Capture the tracker issue URL in the phase's + `CONTEXT.md` so traceability survives compaction. +3. **Create an isolated workspace.** Run + `/gsd-new-workspace --strategy worktree ` to spin up a git + worktree with an independent `.planning/` directory. The worktree is + the safety boundary: any exploration, partial commits, or aborted + plans stay outside `main`. +4. **Run discuss → plan → execute through GSD.** From inside the + workspace, run `/gsd-discuss-phase` to clarify ambiguities, + `/gsd-plan-phase` to produce `PLAN.md`, and either `/gsd-manager` + (interactive dashboard) or `/gsd-execute-phase` / `/gsd-autonomous` + (unattended) to implement. Avoid driving raw `claude` invocations + from outside GSD — that bypasses `STATE.md` updates and the phase + manifest. +5. **Demand proof-of-work.** Run `/gsd-verify-work` to walk the user + through UAT against the phase's acceptance criteria. Tests, + screenshots, log captures, and config diffs are all recorded in + `UAT.md`, which persists across `/clear` and feeds gaps into + `/gsd-plan-phase --gaps` when verification surfaces missed scope. +6. **Pass through the review and ship gates.** Run `/gsd-review` to get + adversarial peer review of the plan from independent AI CLIs (catches + blind spots model-by-model), then `/gsd-ship` to open the PR with a + rich body assembled from the planning artifacts. Both gates require a + human decision before anything reaches the remote. +7. **Capture follow-up work explicitly.** Use `/gsd-note` for inline + notes, `/gsd-plant-seed` for ideas worth a future phase, or + `/gsd-new-milestone` for a coherent group of follow-ups. Creating a + tracker issue from a discovered follow-up requires explicit user + confirmation — GSD does not post to remote trackers automatically. + +When the PR merges, the loop closes. Auto-close keywords in the PR body +(`Closes #NNN` / `Fixes #NNN`) close the tracker issue at merge time. + +## Safety boundaries + +The loop is safe because four invariants hold by construction: + +- **Isolated worktrees.** Every issue runs in a `/gsd-new-workspace` + worktree, so partial work, aborted plans, and exploratory commits + never touch `main`. `gsd-local-patches/` is the recovery surface if a + worktree's hand-edits need to come back across an update. +- **Explicit human review.** `/gsd-review` and `/gsd-ship` both stop for + human approval. There is no auto-merge and no auto-PR-from-execution + path. If you want to remove the human gate for a specific repository, + that is your branch-protection / merge-queue policy decision, not + something GSD opts into for you. +- **No automatic public posting.** GSD never opens, comments on, or + closes a tracker issue without an explicit user-initiated command. + Follow-up capture defaults to local artifacts (notes, seeds, + milestones); pushing back to the tracker is a separate manual step. +- **Verification before ship.** `/gsd-verify-work`'s UAT.md must record + evidence before `/gsd-ship` is run. The recommended discipline is to + treat `verification_failed` as a blocker even when the implementation + looks correct — the failure usually surfaces a missed acceptance + criterion, not a flaky test. + +If any of these invariants is bypassed (e.g. running `claude` directly +against the worktree, skipping `/gsd-verify-work`, or scripting issue +creation through the tracker API without user confirmation), the +guarantees of this guide do not apply. + +## Non-goals + +This guide deliberately does **not** propose any of the following. They +are listed here so future contributors don't re-litigate them in code +review: + +- **No vendoring or copying Symphony code.** GSD reuses its own + primitives. The mapping above is conceptual; no Symphony-derived + source ships in this repo. +- **No long-running daemon.** GSD does not poll GitHub or Linear. The + manager and autonomous workflows handle concurrency through + background-agent semantics, not a daemon. +- **No mandatory tracker dependency.** The loop works without any + tracker integration. The "tracker issue" step is a *human input* — + the URL goes into `CONTEXT.md`. GSD has no opinion about which + tracker you use, or whether you use one at all. +- **No bypass of verification, review, or human decision gates.** Even + when running `/gsd-autonomous`, the verification and review gates + still fire. The "autonomous" label refers to phase-to-phase + progression, not to skipping human approval. +- **No expansion of the default skill / command surface.** Every + command referenced in this guide already exists. This guide is a + documentation surface, not a feature surface. + +## Possible future follow-up + +If maintainer experience with this loop justifies it, a separate +approved-enhancement could later add a *minimal* tracker bridge: + +- Importing one GitHub or Linear issue into a GSD workspace / phase. +- Exporting `UAT.md` evidence as a comment on the source issue. +- Generating follow-up tracker issues from `/gsd-plant-seed` output. + +Each of those would be its own enhancement proposal because each adds +integration surface and ongoing maintenance burden. They are out of +scope for this guide. + +## Related + +- [docs/USER-GUIDE.md](USER-GUIDE.md) — task-oriented walkthroughs of + individual commands referenced above. +- [docs/COMMANDS.md](COMMANDS.md) — full reference for `/gsd-*` + commands. +- [docs/FEATURES.md](FEATURES.md) — feature-level capability matrix + (workspaces, manager, autonomous, verify, review, ship). +- [docs/ARCHITECTURE.md](ARCHITECTURE.md) — phase-artifact lifecycle + and `STATE.md` mechanics. diff --git a/tests/feat-2840-issue-driven-orchestration-guide.test.cjs b/tests/feat-2840-issue-driven-orchestration-guide.test.cjs new file mode 100644 index 000000000..9f5413968 --- /dev/null +++ b/tests/feat-2840-issue-driven-orchestration-guide.test.cjs @@ -0,0 +1,320 @@ +/** + * Tests for docs/issue-driven-orchestration.md (#2840). + * + * Structural-IR assertions per CONTRIBUTING.md "Prohibited: Raw Text Matching + * on Test Outputs": parse the guide into a typed record and assert on + * semantic flags, not regex on prose. The guide is rebuildable as long as + * the structural invariants survive — section-level rewording is fine. + * + * Acceptance criteria from issue #2840: + * - One guide explaining issue-driven orchestration using existing GSD + * commands. + * - Concrete end-to-end issue → workspace → plan/execute → verify/review + * → PR flow. + * - Explicitly documents safety boundaries: isolated worktrees, explicit + * human review, no automatic public posting by default. + * - Adds no runtime dependencies / no new command, daemon, or tracker + * integration. (Test-enforced via concept-mapping audit.) + */ + +// allow-test-rule: structural-IR parser for a docs guide. The .includes() +// calls below build a typed record (commandsPresent flags, conceptPairs +// flags, nonGoalFlags, safetyFlags); assertions run on those booleans, not +// on raw text. This is the documented escape hatch in +// scripts/lint-no-source-grep.cjs for doc-shape tests. + +'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 GUIDE_PATH = path.join(__dirname, '..', 'docs', 'issue-driven-orchestration.md'); + +// ─── Helpers ──────────────────────────────────────────────────────────────── + +/** + * Extract a section starting at a given heading. Returns the body up to (but + * not including) the next heading at the same or shallower depth, or null if + * the heading isn't found. + */ +function extractSection(content, heading) { + const lines = content.split('\n'); + const headingRe = new RegExp(`^(#+)\\s+${heading.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\$&')}\\s*$`); + let start = -1; + let depth = 0; + for (let i = 0; i < lines.length; i++) { + const m = lines[i].match(headingRe); + if (m) { + start = i + 1; + depth = m[1].length; + break; + } + } + if (start < 0) return null; + let end = lines.length; + for (let i = start; i < lines.length; i++) { + const m = lines[i].match(/^(#+)\s+/); + if (m && m[1].length <= depth) { + end = i; + break; + } + } + return lines.slice(start, end).join('\n'); +} + +/** + * Parse the guide into a typed record. Returns null when the guide is + * missing so the file-presence test can name the actual problem instead of + * cascading TypeErrors. + */ +function parseGuide() { + if (!fs.existsSync(GUIDE_PATH)) return null; + const content = fs.readFileSync(GUIDE_PATH, 'utf8'); + // Strip inline emphasis but NOT underscores (snake_case identifiers like + // gsd-new-workspace, .planning/, etc. must survive). + const stripped = content.replace(/\*{1,3}|~{2}/g, ''); + + // Concept-mapping table: rows that pair a Symphony-style concept with a + // GSD primitive. Test asserts on presence of each required pair, not on + // exact prose ordering. + const conceptMappingSection = extractSection(content, 'Concept mapping'); + const endToEndSection = extractSection(content, 'End-to-end flow') || + extractSection(content, 'End-to-end issue → PR flow') || + extractSection(content, 'End-to-end orchestration loop'); + const safetySection = extractSection(content, 'Safety boundaries') || + extractSection(content, 'Safety'); + const nonGoalsSection = extractSection(content, 'Non-goals') || + extractSection(content, 'What this guide does not do'); + + // Track which referenced commands appear at least once anywhere in the + // guide. This prevents drift if /gsd-* command names are renamed. + const requiredCommands = [ + '/gsd-new-workspace', + '/gsd-manager', + '/gsd-autonomous', + '/gsd-discuss-phase', + '/gsd-plan-phase', + '/gsd-execute-phase', + '/gsd-verify-work', + '/gsd-review', + '/gsd-ship', + ]; + const commandsPresent = Object.fromEntries( + requiredCommands.map((c) => [c, content.includes(c)]) + ); + + // Concept-mapping invariants — keys are concept slugs, values are the + // GSD primitive that must appear in the same paragraph/row of the + // concept-mapping section. + const conceptPairs = conceptMappingSection + ? { + roadmap: /ROADMAP\.md/.test(conceptMappingSection), + statemd: /STATE\.md/.test(conceptMappingSection), + contextmd: /CONTEXT\.md/.test(conceptMappingSection), + planmd: /PLAN\.md/.test(conceptMappingSection), + workspaceCommand: /\/gsd-new-workspace/.test(conceptMappingSection), + executionCommand: + /\/gsd-manager/.test(conceptMappingSection) || + /\/gsd-autonomous/.test(conceptMappingSection), + verifyCommand: /\/gsd-verify-work/.test(conceptMappingSection), + reviewCommand: /\/gsd-review/.test(conceptMappingSection), + shipCommand: /\/gsd-ship/.test(conceptMappingSection), + } + : null; + + // Non-goals required by the issue: must explicitly disclaim all four. + const nonGoalFlags = nonGoalsSection + ? { + noVendoring: /vendor|copy/i.test(nonGoalsSection), + noDaemon: /daemon|polling/i.test(nonGoalsSection), + noTrackerDependency: /tracker.*depend|mandatory.*track/i.test(nonGoalsSection), + noBypassReview: /bypass|review|verification|human.*decision|human gate/i.test(nonGoalsSection), + } + : null; + + // Safety boundaries — required disclaimers about how the loop stays safe. + const safetyFlags = safetySection + ? { + isolatedWorktrees: /worktree|isolated/i.test(safetySection), + explicitReview: /review|human.*gate|human.*approval/i.test(safetySection), + noAutoPosting: /not.*automatic|no.*auto|explicit.*confirm|user.*confirm|human.*confirm/i.test(safetySection), + } + : null; + + // End-to-end flow must enumerate at least the seven step sequence the + // acceptance criteria call out. We assert on numbered list items so the + // narrative can be reworded freely. + const numberedSteps = endToEndSection + ? (endToEndSection.match(/^\s*\d+\.\s+/gm) || []).length + : 0; + + // Strip markdown emphasis when checking for snake_case-sensitive content + // in section bodies (per the markdown-aware matching pattern). + const strippedConceptMapping = conceptMappingSection + ? conceptMappingSection.replace(/\*{1,3}|~{2}/g, '') + : null; + + return { + raw: content, + stripped, + conceptMappingSection, + strippedConceptMapping, + endToEndSection, + safetySection, + nonGoalsSection, + commandsPresent, + conceptPairs, + nonGoalFlags, + safetyFlags, + numberedSteps, + }; +} + +// ─── Tests ────────────────────────────────────────────────────────────────── + +describe('issue-driven-orchestration guide (#2840)', () => { + test('docs/issue-driven-orchestration.md exists', () => { + assert.ok( + fs.existsSync(GUIDE_PATH), + `Guide must live at docs/issue-driven-orchestration.md per #2840` + ); + }); + + test('every required GSD command is referenced at least once', () => { + const ir = parseGuide(); + assert.ok(ir, 'parseGuide returned null — guide is missing'); + for (const [cmd, present] of Object.entries(ir.commandsPresent)) { + assert.ok(present, `guide must reference ${cmd}`); + } + }); + + test('concept mapping section exists and pairs Symphony concepts with GSD primitives', () => { + const ir = parseGuide(); + assert.ok(ir, 'guide must be present'); + assert.ok( + ir.conceptMappingSection, + 'guide must contain a "Concept mapping" section' + ); + const expected = { + roadmap: 'ROADMAP.md must appear in the concept mapping', + statemd: 'STATE.md must appear in the concept mapping', + contextmd: 'CONTEXT.md must appear in the concept mapping', + planmd: 'PLAN.md must appear in the concept mapping', + workspaceCommand: '/gsd-new-workspace must appear in the concept mapping', + executionCommand: + '/gsd-manager or /gsd-autonomous must appear in the concept mapping', + verifyCommand: '/gsd-verify-work must appear in the concept mapping', + reviewCommand: '/gsd-review must appear in the concept mapping', + shipCommand: '/gsd-ship must appear in the concept mapping', + }; + for (const [flag, msg] of Object.entries(expected)) { + assert.equal(ir.conceptPairs[flag], true, msg); + } + }); + + test('safety boundaries section names isolation, review, and non-auto-posting', () => { + const ir = parseGuide(); + assert.ok(ir, 'guide must be present'); + assert.ok( + ir.safetySection, + 'guide must contain a "Safety boundaries" or "Safety" section' + ); + assert.equal( + ir.safetyFlags.isolatedWorktrees, + true, + 'safety section must mention isolated worktrees' + ); + assert.equal( + ir.safetyFlags.explicitReview, + true, + 'safety section must require explicit human review' + ); + assert.equal( + ir.safetyFlags.noAutoPosting, + true, + 'safety section must disclaim automatic public posting' + ); + }); + + test('non-goals section disclaims vendoring, daemon, tracker dependency, and gate-bypass', () => { + const ir = parseGuide(); + assert.ok(ir, 'guide must be present'); + assert.ok( + ir.nonGoalsSection, + 'guide must contain a "Non-goals" section' + ); + const expected = { + noVendoring: 'must disclaim copying/vendoring Symphony', + noDaemon: 'must disclaim a long-running daemon', + noTrackerDependency: 'must disclaim mandatory tracker dependency', + noBypassReview: 'must disclaim bypassing review/verification gates', + }; + for (const [flag, msg] of Object.entries(expected)) { + assert.equal(ir.nonGoalFlags[flag], true, msg); + } + }); + + test('end-to-end flow enumerates at least 7 numbered steps (per acceptance criteria)', () => { + const ir = parseGuide(); + assert.ok(ir, 'guide must be present'); + assert.ok( + ir.endToEndSection, + 'guide must contain an "End-to-end flow" (or equivalent) section' + ); + assert.ok( + ir.numberedSteps >= 7, + `end-to-end section must enumerate ≥7 numbered steps; found ${ir.numberedSteps}` + ); + }); + + test('every fenced code block has a language tag (markdownlint MD040)', () => { + const ir = parseGuide(); + assert.ok(ir, 'guide must be present'); + // Pair fence opens; flag any opener with no language tag. + const fences = ir.raw.match(/^```.*$/gm) || []; + const openers = []; + for (let i = 0; i < fences.length; i++) { + // Even index = opener, odd = closer. An opener with empty trailing + // text is MD040. + if (i % 2 === 0) openers.push(fences[i]); + } + const bare = openers.filter((f) => /^```\s*$/.test(f)); + assert.equal( + bare.length, + 0, + `MD040: ${bare.length} fenced block(s) lack a language tag` + ); + }); + + test('cross-linked from docs/README.md', () => { + const readme = path.join(__dirname, '..', 'docs', 'README.md'); + if (!fs.existsSync(readme)) { + // docs/README.md is the discovery surface. Without a cross-link, the + // guide is invisible to users browsing docs/. + return; // tolerate absence; test below ensures FEATURES.md anchor. + } + const txt = fs.readFileSync(readme, 'utf8'); + assert.ok( + /issue-driven-orchestration/.test(txt), + 'docs/README.md must link to the new guide' + ); + }); + + test('cross-linked from docs/USER-GUIDE.md', () => { + const guide = path.join(__dirname, '..', 'docs', 'USER-GUIDE.md'); + // Mirror the null-guard pattern from the README test above: a missing + // file must produce a meaningful assertion message, not a cryptic + // ENOENT stack trace. (CR #3036.) + assert.ok( + fs.existsSync(guide), + 'docs/USER-GUIDE.md must exist for cross-link validation' + ); + const txt = fs.readFileSync(guide, 'utf8'); + assert.ok( + /issue-driven-orchestration/.test(txt), + 'docs/USER-GUIDE.md must link to the new guide' + ); + }); +});