From 0b563b6378553b97f7da633470cfc2251c4ad1c7 Mon Sep 17 00:00:00 2001 From: Elliot Drel Date: Fri, 17 Apr 2026 10:42:13 -0400 Subject: [PATCH 1/8] feat(feedback): add /gsd-feedback command with shared issue-filing pipeline (#2331) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add `/gsd-feedback` for filing bugs, feature requests, and questions with auto-attached diagnostics. Extract issue submission into shared `file-issue.md` workflow used by both `/gsd-feedback` and `/gsd-forensics`: - New `commands/gsd/feedback.md` — slash command entry point - New `workflows/feedback.md` — intake, diagnostics, optional forensics enrichment - New `workflows/file-issue.md` — shared gh submit → URL fallback → raw markdown - Update `workflows/forensics.md` — delegate issue filing to file-issue.md, add auto/custom title prompt with feedback intake cross-call - Add error-hint pointers in review/ship/update workflows - Tests: 44 passing across feedback, file-issue, and forensics suites Closes #2331 Co-Authored-By: Claude Opus 4.6 (1M context) --- README.md | 1 + commands/gsd/feedback.md | 55 +++++++++ docs/ARCHITECTURE.md | 2 +- docs/COMMANDS.md | 18 ++- get-shit-done/workflows/feedback.md | 162 ++++++++++++++++++++++++++ get-shit-done/workflows/file-issue.md | 157 +++++++++++++++++++++++++ get-shit-done/workflows/forensics.md | 42 ++++--- get-shit-done/workflows/help.md | 11 ++ get-shit-done/workflows/review.md | 2 + get-shit-done/workflows/ship.md | 3 + get-shit-done/workflows/update.md | 3 + tests/feedback.test.cjs | 93 +++++++++++++++ tests/file-issue.test.cjs | 119 +++++++++++++++++++ tests/forensics.test.cjs | 22 +++- 14 files changed, 671 insertions(+), 19 deletions(-) create mode 100644 commands/gsd/feedback.md create mode 100644 get-shit-done/workflows/feedback.md create mode 100644 get-shit-done/workflows/file-issue.md create mode 100644 tests/feedback.test.cjs create mode 100644 tests/file-issue.test.cjs diff --git a/README.md b/README.md index 2a13efc69a..68d279680e 100644 --- a/README.md +++ b/README.md @@ -610,6 +610,7 @@ You're never locked in. The system adapts. | `/gsd-next` | Auto-detect state and run the next step | | `/gsd-help` | Show all commands and usage guide | | `/gsd-update` | Update GSD with changelog preview | +| `/gsd-feedback [text]` | File a bug, feature request, or question with diagnostics attached | | `/gsd-join-discord` | Join the GSD Discord community | | `/gsd-manager` | Interactive command center for managing multiple phases | diff --git a/commands/gsd/feedback.md b/commands/gsd/feedback.md new file mode 100644 index 0000000000..3b7cb78e7f --- /dev/null +++ b/commands/gsd/feedback.md @@ -0,0 +1,55 @@ +--- +type: prompt +name: gsd:feedback +description: File GSD bugs, feature requests, or questions with diagnostics auto-attached +argument-hint: "[freeform description]" +allowed-tools: + - Read + - Bash + - AskUserQuestion +--- + + +Collect user feedback from inside a GSD session, attach actionable diagnostics automatically, +and file a GitHub issue against `gsd-build/get-shit-done`. + +Preferred path: create the issue with `gh`. +Fallback path: open a prefilled GitHub issue URL. +Final fallback: always return the full markdown body for manual paste. + + + +@~/.claude/get-shit-done/workflows/feedback.md + + + +**Issue types:** +- `bug` +- `feature` +- `question` + +**Repo target:** +- `gsd-build/get-shit-done` + +**Input:** +- `$ARGUMENTS` may contain a freeform seed for the title and description + + + +Read and execute the feedback workflow from @~/.claude/get-shit-done/workflows/feedback.md end-to-end. + + + +- User intent captured as `bug`, `feature`, or `question` +- Diagnostics attached from current runtime and project state +- `gh issue create` attempted first when available +- Prefilled GitHub issue URL generated as fallback +- Full markdown body returned for manual filing even if submission fails + + + +- Prefer machine-readable diagnostics from GSD helpers over ad hoc parsing +- Do not discard `$ARGUMENTS`; use it as a seed whenever possible +- Always return enough information for manual filing +- Never block on browser launch failure + diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 6ed365ba04..1b18fb2413 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -124,7 +124,7 @@ Orchestration logic that commands reference. Contains the step-by-step process i - State update patterns - Error handling and recovery -**Total workflows:** 72 +**Total workflows:** 74 ### Agents (`agents/*.md`) diff --git a/docs/COMMANDS.md b/docs/COMMANDS.md index b34ac49d36..a46d5b512e 100644 --- a/docs/COMMANDS.md +++ b/docs/COMMANDS.md @@ -825,7 +825,8 @@ Post-mortem investigation of failed or stuck GSD workflows. - STATE.md anomalies and session history - Uncommitted work, conflicts, abandoned changes - At least 4 anomaly types checked (stuck loop, missing artifacts, abandoned work, crash/interruption) -- GitHub issue creation offered if actionable findings exist +- GitHub issue creation offered if actionable findings exist (delegates to shared `file-issue.md` workflow — same submission path as `/gsd-feedback`) +- Auto-derived or custom title when filing (custom path uses `/gsd-feedback` intake) ```bash /gsd-forensics # Interactive — prompted for problem @@ -1020,6 +1021,21 @@ Update GSD with changelog preview. /gsd-update # Check for updates and install ``` +### `/gsd-feedback [text]` + +File a GSD bug, feature request, or question without leaving the current session. + +**Behavior:** +- Collects a short intake for `bug`, `feature`, or `question` +- Attaches runtime and GSD state diagnostics automatically +- For `bug` type, offers optional `/gsd-forensics` enrichment (runs investigation steps 2–4 and attaches findings) +- Delegates issue filing to shared `file-issue.md` workflow (`gh issue create` → prefilled URL → raw markdown fallback) + +```bash +/gsd-feedback # Guided feedback intake +/gsd-feedback planner skipped my context file # Seed title/description from text +``` + ### `/gsd-reapply-patches` Restore local modifications after a GSD update. diff --git a/get-shit-done/workflows/feedback.md b/get-shit-done/workflows/feedback.md new file mode 100644 index 0000000000..84e77ad715 --- /dev/null +++ b/get-shit-done/workflows/feedback.md @@ -0,0 +1,162 @@ + +Collect structured feedback, attach diagnostics from the current GSD session, and file a GitHub issue against `gsd-build/get-shit-done`. Issue submission is delegated to the shared `file-issue.md` workflow so that `/gsd-feedback` and `/gsd-forensics` use one canonical filing path. + + + +Read all files referenced by the invoking prompt's execution_context before starting. + + + + + +Start from `$ARGUMENTS` if present. + +If type is not already clear, ask the user to choose one: +- `bug` +- `feature` +- `question` + +Then collect: +- `ISSUE_TITLE` +- `ISSUE_DESCRIPTION` + +If `$ARGUMENTS` already contains enough detail, refine it instead of re-asking from scratch. + +Keep the intake short. The goal is to get a fileable issue, not run a long interview. + +### Optional forensics enrichment (bug type only) + +If `ISSUE_TYPE` is `bug`, ask the user: + +> "Want me to run a quick forensics investigation first? This checks git history, planning state, and artifacts for anomalies that might explain the bug. [Y/n]" + +If yes: +1. Execute Steps 2–4 from `@~/.claude/get-shit-done/workflows/forensics.md` (Gather Evidence → Detect Anomalies → Generate Report). Skip Steps 5–8 (presentation, interactive investigation, issue creation, STATE update — those are handled here). +2. Read the generated report from `.planning/forensics/report-{timestamp}.md`. +3. Set `INVESTIGATION_FINDINGS` to the redacted report contents. + +If no (or if `ISSUE_TYPE` is `feature` or `question`): +- Set `INVESTIGATION_FINDINGS` to empty string. + + + +Gather diagnostics from existing GSD helpers first. + +**1. Version and runtime context** +Derive the invoking config dir from the execution context path when possible and prefer its sibling `get-shit-done/VERSION` file: + +```bash +PREFERRED_CONFIG_DIR="" +case "$PWD" in + *"/get-shit-done/workflows"*) + PREFERRED_CONFIG_DIR="${PWD%/get-shit-done/workflows*}" + ;; +esac + +GSD_VERSION="" +for candidate in \ + "$PREFERRED_CONFIG_DIR/get-shit-done/VERSION" \ + "$HOME/.claude/get-shit-done/VERSION" \ + "$HOME/.codex/get-shit-done/VERSION" \ + ".claude/get-shit-done/VERSION" \ + ".codex/get-shit-done/VERSION" +do + if [ -n "$candidate" ] && [ -f "$candidate" ]; then + GSD_VERSION="$(cat "$candidate")" + break + fi +done + +if [ -z "$GSD_VERSION" ] && [ -f package.json ]; then + GSD_VERSION=$(node -e "const fs=require('fs'); const pkg=JSON.parse(fs.readFileSync('package.json','utf8')); console.log(pkg.version || '')" 2>/dev/null) +fi + +NODE_VERSION=$(node -v 2>/dev/null || echo "unknown") +OS_NAME=$(uname -s 2>/dev/null || echo "$OS") +ARCH_NAME=$(uname -m 2>/dev/null || node -p "process.arch" 2>/dev/null || echo "unknown") +CURRENT_DIR=$(pwd) +ACTIVE_WORKSTREAM=${GSD_WORKSTREAM:-$(gsd-sdk query workstream get 2>/dev/null || echo "none")} +``` + +**2. State and phase diagnostics** +```bash +STATE_JSON=$(gsd-sdk query state.json 2>/dev/null || echo "{}") +STATE_SNAPSHOT=$(gsd-sdk query state-snapshot 2>/dev/null || echo "{}") + +CURRENT_PHASE=$(printf '%s' "$STATE_SNAPSHOT" | jq -r '.current_phase // "none"' 2>/dev/null || echo "none") +CURRENT_PHASE_NAME=$(printf '%s' "$STATE_SNAPSHOT" | jq -r '.current_phase_name // "none"' 2>/dev/null || echo "none") +STATUS=$(printf '%s' "$STATE_SNAPSHOT" | jq -r '.status // "unknown"' 2>/dev/null || echo "unknown") +LAST_ACTIVITY=$(printf '%s' "$STATE_SNAPSHOT" | jq -r '.last_activity // "unknown"' 2>/dev/null || echo "unknown") +LAST_ACTIVITY_DESC=$(printf '%s' "$STATE_SNAPSHOT" | jq -r '.last_activity_desc // "unknown"' 2>/dev/null || echo "unknown") +RESUME_FILE=$(printf '%s' "$STATE_SNAPSHOT" | jq -r '.session.resume_file // "none"' 2>/dev/null || echo "none") +``` + +**3. Small config excerpt** +Prefer direct GSD queries: + +```bash +MODEL_PROFILE=$(gsd-sdk query config-get model_profile 2>/dev/null || echo "unset") +BASE_BRANCH=$(gsd-sdk query config-get git.base_branch 2>/dev/null || echo "unset") +USE_WORKTREES=$(gsd-sdk query config-get workflow.use_worktrees 2>/dev/null || echo "unset") +COMMIT_DOCS=$(gsd-sdk query config-get commit_docs 2>/dev/null || echo "unset") +``` + +If `.planning/config.json` exists, include only a short relevant snippet rather than dumping the whole file. + + + +Render the gathered diagnostics into `DIAGNOSTICS_MARKDOWN`: + +````markdown +- GSD version: {GSD_VERSION} +- Node: {NODE_VERSION} +- OS / arch: {OS_NAME} / {ARCH_NAME} +- Working directory: {CURRENT_DIR} +- Workstream: {ACTIVE_WORKSTREAM} +- Current phase: {CURRENT_PHASE} - {CURRENT_PHASE_NAME} +- Status: {STATUS} +- Last activity: {LAST_ACTIVITY} +- Last activity detail: {LAST_ACTIVITY_DESC} +- Resume file: {RESUME_FILE} +- Config excerpt: + - model_profile: {MODEL_PROFILE} + - git.base_branch: {BASE_BRANCH} + - workflow.use_worktrees: {USE_WORKTREES} + - commit_docs: {COMMIT_DOCS} + +### Raw State JSON + +```json +{STATE_JSON} +``` + +### Raw State Snapshot + +```json +{STATE_SNAPSHOT} +``` +```` + + + +Set the variables required by the shared issue-filing workflow, then delegate: + +- `ISSUE_TYPE` — from step 1 +- `ISSUE_TITLE` — from step 1 +- `ISSUE_DESCRIPTION` — from step 1 +- `DIAGNOSTICS_MARKDOWN` — from step 3 +- `INVESTIGATION_FINDINGS` — from step 1 forensics enrichment (empty if skipped) +- `REPO` — `gsd-build/get-shit-done` + +Execute `@~/.claude/get-shit-done/workflows/file-issue.md` end-to-end. It handles rendering the final issue body, `gh issue create`, URL fallback, and raw markdown fallback. + + + + + +- Intake completes with type, title, and description +- Diagnostics come from `gsd-sdk query` helpers where available +- Bug-type issues offer optional forensics enrichment via `/gsd-forensics` steps 2–4 +- Issue filing delegates to `@~/.claude/get-shit-done/workflows/file-issue.md` +- `DIAGNOSTICS_MARKDOWN` and `INVESTIGATION_FINDINGS` are set before delegation + diff --git a/get-shit-done/workflows/file-issue.md b/get-shit-done/workflows/file-issue.md new file mode 100644 index 0000000000..4419ad9e5a --- /dev/null +++ b/get-shit-done/workflows/file-issue.md @@ -0,0 +1,157 @@ + +Render a GitHub issue body from caller-provided inputs, submit via `gh issue create` when possible, and always fall back to a prefilled URL plus raw markdown so the user can file manually. This is the shared terminal path for `/gsd-feedback` and `/gsd-forensics` — neither workflow should file issues directly. + + + +Read all files referenced by the invoking prompt's execution_context before starting. + + + +Callers set these variables before delegating to this workflow: + +- `ISSUE_TYPE` — one of `bug`, `feature`, `question` (required) +- `ISSUE_TITLE` — user-visible issue title (required) +- `ISSUE_DESCRIPTION` — main body / summary from the user (required) +- `DIAGNOSTICS_MARKDOWN` — pre-rendered diagnostics block from the caller (required; may be `"(none)"` if the caller has no diagnostics) +- `INVESTIGATION_FINDINGS` — optional markdown block with forensic findings; empty string or unset when absent +- `REPO` — target repo slug; default `gsd-build/get-shit-done` + + + + + +```bash +: "${REPO:=gsd-build/get-shit-done}" +: "${INVESTIGATION_FINDINGS:=}" +: "${DIAGNOSTICS_MARKDOWN:=(none)}" +``` + +Validate required inputs. If `ISSUE_TYPE`, `ISSUE_TITLE`, or `ISSUE_DESCRIPTION` is missing, abort with a clear error — callers must satisfy the contract. + + + +Map labels: +- `bug` -> `bug` +- `feature` -> `enhancement` +- `question` -> `question` + +Render the issue body as markdown. The investigation section is included only when `INVESTIGATION_FINDINGS` is non-empty. + +````markdown +## Summary + +**Type:** {ISSUE_TYPE} + +## What happened / what you want + +{ISSUE_DESCRIPTION} + +{if INVESTIGATION_FINDINGS non-empty:} +## Investigation Findings + +{INVESTIGATION_FINDINGS} +{end if} + +
+Diagnostic Info + +{DIAGNOSTICS_MARKDOWN} + +
+```` + +Write the rendered body to a temp file (`ISSUE_BODY_FILE`) so it can be reused by both `gh issue create` and the URL fallback without double-escaping. +
+ + +Attempt GitHub CLI submission first. + +Resolve the mapped label only if it exists in the target repo (avoid hard failures when the repo is missing the label): + +```bash +LABEL_NAME="{mapped label from ISSUE_TYPE}" +EXISTING_LABEL=$(gh label list --repo "$REPO" --search "^${LABEL_NAME}$" --json name -q '.[0].name' 2>/dev/null || true) + +LABEL_FLAG="" +if [ -n "$EXISTING_LABEL" ]; then + LABEL_FLAG="--label $EXISTING_LABEL" +fi +``` + +Create the issue: + +```bash +gh issue create \ + --repo "$REPO" \ + --title "$ISSUE_TITLE" \ + $LABEL_FLAG \ + --body-file "$ISSUE_BODY_FILE" +``` + +If this succeeds, capture the returned issue URL and skip to `final_output`. + + + +If `gh` is unavailable, unauthenticated, or issue creation fails, build a prefilled GitHub issue URL: + +```bash +TITLE_ENC=$(node -e "console.log(encodeURIComponent(process.argv[1]))" "$ISSUE_TITLE") +BODY_ENC=$(node -e "console.log(encodeURIComponent(require('fs').readFileSync(process.argv[1], 'utf8')))" "$ISSUE_BODY_FILE") +LABEL_ENC=$(node -e "console.log(encodeURIComponent(process.argv[1]))" "{mapped label from ISSUE_TYPE}") +PREFILLED_URL="https://github.com/${REPO}/issues/new?title=${TITLE_ENC}&body=${BODY_ENC}&labels=${LABEL_ENC}" +``` + +Try to open the URL automatically, but do not block on failure: + +```bash +if command -v open >/dev/null 2>&1; then + open "$PREFILLED_URL" >/dev/null 2>&1 || true +elif command -v xdg-open >/dev/null 2>&1; then + xdg-open "$PREFILLED_URL" >/dev/null 2>&1 || true +elif command -v powershell >/dev/null 2>&1; then + powershell -NoProfile -Command "Start-Process '$PREFILLED_URL'" >/dev/null 2>&1 || true +elif command -v cmd.exe >/dev/null 2>&1; then + cmd.exe /c start "" "$PREFILLED_URL" >/dev/null 2>&1 || true +fi +``` + +Always print the prefilled URL even if browser launch fails. + + + +Always return: +- the issue type +- the final title +- the rendered markdown body (in a fenced block so the user can paste directly) + +If `gh issue create` succeeded: +```text +Issue created: {ISSUE_URL} +``` + +If `gh issue create` failed but the prefilled URL was generated: +```text +Open this URL to file the issue: +{PREFILLED_URL} +``` + +If both automated paths fail, return: +```text +Manual fallback: paste the markdown body into a new GitHub issue at https://github.com/${REPO}/issues/new +``` + +The raw markdown body must always be present in the output so the user has an escape hatch regardless of which path succeeded. + + +
+ + +- Required inputs validated before any submission attempt +- Label mapping respects `bug`/`feature`/`question` → `bug`/`enhancement`/`question` +- Label existence checked against `$REPO` before applying +- `gh issue create` is attempted before fallback paths +- Prefilled `issues/new?title=...&body=...&labels=...` URL generated when gh submission is unavailable +- Cross-platform browser open attempted (open, xdg-open, Start-Process, cmd.exe /c start) +- Raw markdown body always returned regardless of submission outcome +- Investigation section included only when `INVESTIGATION_FINDINGS` is non-empty + diff --git a/get-shit-done/workflows/forensics.md b/get-shit-done/workflows/forensics.md index caa5d765e1..16b3c1c287 100644 --- a/get-shit-done/workflows/forensics.md +++ b/get-shit-done/workflows/forensics.md @@ -239,23 +239,37 @@ Read additional files only if specifically needed. If actionable anomalies were found (HIGH or MEDIUM confidence): -> "Want me to create a GitHub issue for this? I'll format the findings and redact paths." +> "Want me to create a GitHub issue for this? I'll format the findings and redact paths. [Y/n]" -If confirmed: -```bash -# Check if "bug" label exists before using it -BUG_LABEL=$(gh label list --search "bug" --json name -q '.[0].name' 2>/dev/null) -LABEL_FLAG="" -if [ -n "$BUG_LABEL" ]; then - LABEL_FLAG="--label bug" -fi - -gh issue create \ - --title "bug: {concise description from anomaly}" \ - $LABEL_FLAG \ - --body "{formatted findings from report}" +If confirmed, ask: + +> "Use the auto-derived title from the investigation, or customize it? [auto/custom]" + +**Auto path:** Set `ISSUE_TITLE` to `"bug: {concise description from anomaly}"` and `ISSUE_DESCRIPTION` to a brief summary derived from the Root Cause Hypothesis section of the report. + +**Custom path:** Run the intake from Step 1 of `@~/.claude/get-shit-done/workflows/feedback.md` (collect_feedback) to let the user author their own `ISSUE_TITLE` and `ISSUE_DESCRIPTION`. Pre-seed `$ARGUMENTS` with the anomaly summary so the user can refine rather than start from scratch. + +Then prepare variables for the shared filing workflow: + +- `ISSUE_TYPE` = `bug` +- `ISSUE_TITLE` — from auto or custom path above +- `ISSUE_DESCRIPTION` — from auto or custom path above +- `INVESTIGATION_FINDINGS` — the redacted forensic report contents (from Step 4) +- `DIAGNOSTICS_MARKDOWN` — render from evidence already gathered in Step 2: + +```markdown +- Git activity: {commit count} commits over {time span} +- Last commit: {date} — "{message}" +- Uncommitted changes: {yes/no} +- Active worktrees: {count} +- Current phase: {phase number} — {phase name} — {status} +- Blockers: {any from STATE.md} ``` +- `REPO` = `gsd-build/get-shit-done` + +Execute `@~/.claude/get-shit-done/workflows/file-issue.md` end-to-end. It handles rendering the final issue body, GitHub submission, URL fallback, and raw markdown fallback. + ## Step 8: Update STATE.md ```bash diff --git a/get-shit-done/workflows/help.md b/get-shit-done/workflows/help.md index e23b131444..98f7914ff9 100644 --- a/get-shit-done/workflows/help.md +++ b/get-shit-done/workflows/help.md @@ -458,6 +458,17 @@ Update GSD to latest version with changelog preview. Usage: `/gsd-update` +**`/gsd-feedback [text]`** +File a GSD bug, feature request, or question with diagnostics auto-attached. + +- Collects a short guided intake +- Tries `gh issue create` first +- Falls back to a prefilled GitHub issue URL +- Always returns the full markdown body for manual paste + +Usage: `/gsd-feedback` +Usage: `/gsd-feedback planner skipped my context file` + **`/gsd-join-discord`** Join the GSD Discord community. diff --git a/get-shit-done/workflows/review.md b/get-shit-done/workflows/review.md index d9c9911d0c..ae1ab48a37 100644 --- a/get-shit-done/workflows/review.md +++ b/get-shit-done/workflows/review.md @@ -46,6 +46,8 @@ No external AI CLIs found. Install at least one: - cursor: https://cursor.com (Cursor IDE agent mode) Then run /gsd-review again. + +If this failure is unexpected or unclear, run `/gsd-feedback` to file a GitHub issue with diagnostics attached. ``` Exit. diff --git a/get-shit-done/workflows/ship.md b/get-shit-done/workflows/ship.md index 947c64cec4..c74e5c453a 100644 --- a/get-shit-done/workflows/ship.md +++ b/get-shit-done/workflows/ship.md @@ -64,11 +64,14 @@ Verify the work is ready to ship: ``` Detect `origin` remote. If no remote: error — can't create PR. + If this failure is unexpected or unclear, run `/gsd-feedback` to file a GitHub issue with diagnostics attached. + 5. **`gh` CLI available?** ```bash which gh && gh auth status 2>&1 ``` If `gh` not found or not authenticated: provide setup instructions and exit. + If this failure is unexpected or unclear, run `/gsd-feedback` to file a GitHub issue with diagnostics attached. diff --git a/get-shit-done/workflows/update.md b/get-shit-done/workflows/update.md index d67b4f5c0d..2d0a92b373 100644 --- a/get-shit-done/workflows/update.md +++ b/get-shit-done/workflows/update.md @@ -280,6 +280,8 @@ npm view get-shit-done-cc version 2>/dev/null Couldn't check for updates (offline or npm unavailable). To update manually: `npx get-shit-done-cc --global` + +If this failure is unexpected or unclear, run `/gsd-feedback` to file a GitHub issue with diagnostics attached. ``` Exit. @@ -485,6 +487,7 @@ npx -y get-shit-done-cc@latest --claude --global ``` Capture output. If install fails, show error and exit. +If this failure is unexpected or unclear, run `/gsd-feedback` to file a GitHub issue with diagnostics attached. Clear the update cache so statusline indicator disappears: diff --git a/tests/feedback.test.cjs b/tests/feedback.test.cjs new file mode 100644 index 0000000000..0773db7b94 --- /dev/null +++ b/tests/feedback.test.cjs @@ -0,0 +1,93 @@ +const { test, describe } = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('fs'); +const path = require('path'); + +const repoRoot = path.resolve(__dirname, '..'); +const commandPath = path.join(repoRoot, 'commands', 'gsd', 'feedback.md'); +const workflowPath = path.join(repoRoot, 'get-shit-done', 'workflows', 'feedback.md'); + +describe('feedback command', () => { + test('command file exists', () => { + assert.ok(fs.existsSync(commandPath), 'commands/gsd/feedback.md should exist'); + }); + + test('command frontmatter includes expected fields', () => { + const content = fs.readFileSync(commandPath, 'utf8'); + assert.ok(content.includes('name: gsd:feedback'), 'should have correct command name'); + assert.ok(content.includes('description:'), 'should have description'); + assert.ok(content.includes('argument-hint:'), 'should have argument hint'); + assert.ok(content.includes('AskUserQuestion'), 'should allow AskUserQuestion'); + assert.ok(content.includes('workflows/feedback.md'), 'should reference feedback workflow'); + }); +}); + +describe('feedback workflow', () => { + test('workflow file exists', () => { + assert.ok(fs.existsSync(workflowPath), 'get-shit-done/workflows/feedback.md should exist'); + }); + + test('workflow collects diagnostics from existing gsd helpers', () => { + const content = fs.readFileSync(workflowPath, 'utf8'); + assert.ok(content.includes('gsd-sdk query state.json'), 'should read state json'); + assert.ok(content.includes('gsd-sdk query state-snapshot'), 'should read state snapshot'); + assert.ok(content.includes('gsd-sdk query config-get'), 'should read config via gsd-sdk query'); + assert.ok(content.includes('VERSION'), 'should check VERSION file'); + assert.ok(content.includes('package.json'), 'should fall back to package.json version'); + }); + + test('workflow delegates issue filing to shared file-issue workflow', () => { + const content = fs.readFileSync(workflowPath, 'utf8'); + assert.ok( + content.includes('workflows/file-issue.md'), + 'should delegate to file-issue.md workflow' + ); + assert.ok( + content.includes('DIAGNOSTICS_MARKDOWN'), + 'should set DIAGNOSTICS_MARKDOWN before delegation' + ); + assert.ok( + content.includes('INVESTIGATION_FINDINGS'), + 'should set INVESTIGATION_FINDINGS before delegation' + ); + }); + + test('workflow offers optional forensics enrichment on bug type', () => { + const content = fs.readFileSync(workflowPath, 'utf8'); + assert.ok( + content.includes('forensics investigation'), + 'should offer forensics enrichment for bug reports' + ); + assert.ok( + content.includes('workflows/forensics.md'), + 'should reference forensics workflow for enrichment' + ); + }); +}); + +describe('feedback discoverability docs', () => { + test('README references /gsd-feedback', () => { + const content = fs.readFileSync(path.join(repoRoot, 'README.md'), 'utf8'); + assert.ok(content.includes('/gsd-feedback'), 'README.md should mention /gsd-feedback'); + }); + + test('COMMANDS.md references /gsd-feedback', () => { + const content = fs.readFileSync(path.join(repoRoot, 'docs', 'COMMANDS.md'), 'utf8'); + assert.ok(content.includes('/gsd-feedback'), 'docs/COMMANDS.md should mention /gsd-feedback'); + }); + + test('help reference mentions /gsd-feedback', () => { + const content = fs.readFileSync(path.join(repoRoot, 'get-shit-done', 'workflows', 'help.md'), 'utf8'); + assert.ok(content.includes('/gsd-feedback'), 'help workflow should mention /gsd-feedback'); + }); + + test('high-value failure surfaces point to /gsd-feedback', () => { + const review = fs.readFileSync(path.join(repoRoot, 'get-shit-done', 'workflows', 'review.md'), 'utf8'); + const ship = fs.readFileSync(path.join(repoRoot, 'get-shit-done', 'workflows', 'ship.md'), 'utf8'); + const update = fs.readFileSync(path.join(repoRoot, 'get-shit-done', 'workflows', 'update.md'), 'utf8'); + + assert.ok(review.includes('/gsd-feedback'), 'review workflow should mention /gsd-feedback'); + assert.ok(ship.includes('/gsd-feedback'), 'ship workflow should mention /gsd-feedback'); + assert.ok(update.includes('/gsd-feedback'), 'update workflow should mention /gsd-feedback'); + }); +}); diff --git a/tests/file-issue.test.cjs b/tests/file-issue.test.cjs new file mode 100644 index 0000000000..3f949a3723 --- /dev/null +++ b/tests/file-issue.test.cjs @@ -0,0 +1,119 @@ +/** + * GSD file-issue Tests + * + * Validates the shared issue-filing workflow used by both + * `/gsd-feedback` and `/gsd-forensics`. + */ + +const { test, describe } = require('node:test'); +const assert = require('node:assert/strict'); +const fs = require('fs'); +const path = require('path'); + +const repoRoot = path.resolve(__dirname, '..'); +const workflowPath = path.join(repoRoot, 'get-shit-done', 'workflows', 'file-issue.md'); + +describe('file-issue workflow', () => { + test('workflow file exists', () => { + assert.ok(fs.existsSync(workflowPath), 'workflows/file-issue.md should exist'); + }); + + test('declares input contract for caller variables', () => { + const content = fs.readFileSync(workflowPath, 'utf8'); + const inputs = [ + 'ISSUE_TYPE', + 'ISSUE_TITLE', + 'ISSUE_DESCRIPTION', + 'DIAGNOSTICS_MARKDOWN', + 'INVESTIGATION_FINDINGS', + 'REPO', + ]; + for (const v of inputs) { + assert.ok(content.includes(v), `workflow should document input: ${v}`); + } + }); + + test('defaults REPO to gsd-build/get-shit-done', () => { + const content = fs.readFileSync(workflowPath, 'utf8'); + assert.ok( + content.includes('gsd-build/get-shit-done'), + 'should default REPO to gsd-build/get-shit-done' + ); + }); + + test('maps issue types to labels', () => { + const content = fs.readFileSync(workflowPath, 'utf8'); + assert.ok(content.includes('`bug` -> `bug`'), 'bug type maps to bug label'); + assert.ok(content.includes('`feature` -> `enhancement`'), 'feature maps to enhancement'); + assert.ok(content.includes('`question` -> `question`'), 'question maps to question'); + }); + + test('checks label existence before applying', () => { + const content = fs.readFileSync(workflowPath, 'utf8'); + assert.ok(content.includes('gh label list'), 'should probe label existence'); + assert.ok(content.includes('LABEL_FLAG'), 'should conditionally set label flag'); + }); + + test('renders optional investigation findings section', () => { + const content = fs.readFileSync(workflowPath, 'utf8'); + assert.ok( + content.includes('Investigation Findings'), + 'should render investigation section when findings provided' + ); + assert.ok( + content.includes('INVESTIGATION_FINDINGS non-empty'), + 'should gate investigation section on non-empty findings' + ); + }); + + test('includes diagnostics block via caller input', () => { + const content = fs.readFileSync(workflowPath, 'utf8'); + assert.ok(content.includes('
'), 'should wrap diagnostics in details block'); + assert.ok(content.includes('Diagnostic Info'), 'should label diagnostics summary'); + assert.ok( + content.includes('{DIAGNOSTICS_MARKDOWN}'), + 'should slot caller-provided diagnostics markdown' + ); + }); + + test('attempts gh issue create first', () => { + const content = fs.readFileSync(workflowPath, 'utf8'); + assert.ok(content.includes('gh issue create'), 'should invoke gh issue create'); + assert.ok(content.includes('--body-file'), 'should use body-file to avoid escaping issues'); + }); + + test('builds prefilled URL fallback', () => { + const content = fs.readFileSync(workflowPath, 'utf8'); + assert.ok( + content.includes('issues/new?title='), + 'should build prefilled GitHub issue URL' + ); + assert.ok(content.includes('encodeURIComponent'), 'should URL-encode title and body'); + assert.ok(content.includes('&labels='), 'should include labels in URL'); + }); + + test('opens URL across all supported platforms', () => { + const content = fs.readFileSync(workflowPath, 'utf8'); + const openers = ['open ', 'xdg-open', 'Start-Process', 'cmd.exe /c start']; + for (const opener of openers) { + assert.ok(content.includes(opener), `should support ${opener} for URL open`); + } + }); + + test('always returns raw markdown body for manual paste', () => { + const content = fs.readFileSync(workflowPath, 'utf8'); + assert.ok( + content.includes('raw markdown body') || content.includes('rendered markdown body'), + 'should always print the rendered body' + ); + assert.ok( + content.includes('Manual fallback'), + 'should describe the manual paste fallback' + ); + }); + + test('includes success_criteria section', () => { + const content = fs.readFileSync(workflowPath, 'utf8'); + assert.ok(content.includes(''), 'should declare success_criteria'); + }); +}); diff --git a/tests/forensics.test.cjs b/tests/forensics.test.cjs index d0caf75a37..1be0677c38 100644 --- a/tests/forensics.test.cjs +++ b/tests/forensics.test.cjs @@ -130,11 +130,27 @@ describe('forensics workflow', () => { ); }); - test('workflow offers GitHub issue creation', () => { + test('workflow delegates issue filing to shared file-issue workflow', () => { const content = fs.readFileSync(workflowPath, 'utf-8'); assert.ok( - content.includes('gh issue create'), - 'should offer to create GitHub issue from findings' + content.includes('workflows/file-issue.md'), + 'should delegate issue filing to file-issue.md' + ); + assert.ok( + !content.includes('gh issue create'), + 'should NOT contain inline gh issue create (moved to file-issue.md)' + ); + }); + + test('workflow offers auto or custom title for issue filing', () => { + const content = fs.readFileSync(workflowPath, 'utf-8'); + assert.ok( + content.includes('auto/custom'), + 'should prompt for auto-derived or custom title' + ); + assert.ok( + content.includes('workflows/feedback.md'), + 'custom path should reference feedback workflow for user intake' ); }); From 473222ceab36ed1ec4250ac58e88623848d74ead Mon Sep 17 00:00:00 2001 From: Elliot Drel <156480527+ElliotDrel@users.noreply.github.com> Date: Fri, 17 Apr 2026 10:50:39 -0400 Subject: [PATCH 2/8] fix: update ARCHITECTURE.md command count from 75 to 76 --- docs/ARCHITECTURE.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 1b18fb2413..b2efdb6b53 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -113,7 +113,7 @@ User-facing entry points. Each file contains YAML frontmatter (name, description - **Copilot:** Slash commands (`/gsd-command-name`) - **Antigravity:** Skills -**Total commands:** 75 +**Total commands:** 76 ### Workflows (`get-shit-done/workflows/*.md`) @@ -409,7 +409,7 @@ UI-SPEC.md (per phase) ─────────────────── ``` ~/.claude/ # Claude Code (global install) -├── commands/gsd/*.md # 75 slash commands +├── commands/gsd/*.md # 76 slash commands ├── get-shit-done/ │ ├── bin/gsd-tools.cjs # CLI utility │ ├── bin/lib/*.cjs # 19 domain modules From facaa91c1dfb3394b26519a48516b7a17bf1e206 Mon Sep 17 00:00:00 2001 From: Elliot Drel <156480527+ElliotDrel@users.noreply.github.com> Date: Fri, 17 Apr 2026 10:54:53 -0400 Subject: [PATCH 3/8] fix: use GSD_WORKSTREAM env var instead of unsupported workstream get query --- get-shit-done/workflows/feedback.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/get-shit-done/workflows/feedback.md b/get-shit-done/workflows/feedback.md index 84e77ad715..48789ad1ac 100644 --- a/get-shit-done/workflows/feedback.md +++ b/get-shit-done/workflows/feedback.md @@ -75,7 +75,7 @@ NODE_VERSION=$(node -v 2>/dev/null || echo "unknown") OS_NAME=$(uname -s 2>/dev/null || echo "$OS") ARCH_NAME=$(uname -m 2>/dev/null || node -p "process.arch" 2>/dev/null || echo "unknown") CURRENT_DIR=$(pwd) -ACTIVE_WORKSTREAM=${GSD_WORKSTREAM:-$(gsd-sdk query workstream get 2>/dev/null || echo "none")} +ACTIVE_WORKSTREAM=${GSD_WORKSTREAM:-none} ``` **2. State and phase diagnostics** From 163848cf5891168bc917bdceb02fb4f30f2feecc Mon Sep 17 00:00:00 2001 From: Elliot Drel <156480527+ElliotDrel@users.noreply.github.com> Date: Fri, 17 Apr 2026 10:54:54 -0400 Subject: [PATCH 4/8] fix: use plain-text label search with jq exact-match instead of regex anchors --- get-shit-done/workflows/file-issue.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/get-shit-done/workflows/file-issue.md b/get-shit-done/workflows/file-issue.md index 4419ad9e5a..e165ba5074 100644 --- a/get-shit-done/workflows/file-issue.md +++ b/get-shit-done/workflows/file-issue.md @@ -70,7 +70,7 @@ Resolve the mapped label only if it exists in the target repo (avoid hard failur ```bash LABEL_NAME="{mapped label from ISSUE_TYPE}" -EXISTING_LABEL=$(gh label list --repo "$REPO" --search "^${LABEL_NAME}$" --json name -q '.[0].name' 2>/dev/null || true) +EXISTING_LABEL=$(gh label list --repo "$REPO" --search "$LABEL_NAME" --json name -q '.[] | select(.name == env.LABEL_NAME) | .name' 2>/dev/null || true) LABEL_FLAG="" if [ -n "$EXISTING_LABEL" ]; then From 73ecb0555eb879577e4880b0982074d874d4be86 Mon Sep 17 00:00:00 2001 From: Elliot Drel <156480527+ElliotDrel@users.noreply.github.com> Date: Fri, 17 Apr 2026 10:54:56 -0400 Subject: [PATCH 5/8] fix: inline custom title prompt instead of delegating to feedback.md to avoid loop --- get-shit-done/workflows/forensics.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/get-shit-done/workflows/forensics.md b/get-shit-done/workflows/forensics.md index 16b3c1c287..dad1e05a45 100644 --- a/get-shit-done/workflows/forensics.md +++ b/get-shit-done/workflows/forensics.md @@ -247,7 +247,13 @@ If confirmed, ask: **Auto path:** Set `ISSUE_TITLE` to `"bug: {concise description from anomaly}"` and `ISSUE_DESCRIPTION` to a brief summary derived from the Root Cause Hypothesis section of the report. -**Custom path:** Run the intake from Step 1 of `@~/.claude/get-shit-done/workflows/feedback.md` (collect_feedback) to let the user author their own `ISSUE_TITLE` and `ISSUE_DESCRIPTION`. Pre-seed `$ARGUMENTS` with the anomaly summary so the user can refine rather than start from scratch. +**Custom path:** Prompt the user directly for a title and description, pre-seeding the anomaly summary as a starting point: + +> "Suggest a title for this issue (or press Enter to use the auto-derived one): {auto title}" + +> "Add a description (or press Enter to use the investigation summary):" + +Set `ISSUE_TITLE` and `ISSUE_DESCRIPTION` from the user's responses (falling back to the auto-derived values if blank). Do not invoke `feedback.md` — forensics enrichment is already complete. Then prepare variables for the shared filing workflow: From 8590801e2b0dc52f594bd8cf157d086e899ee19d Mon Sep 17 00:00:00 2001 From: Elliot Drel <156480527+ElliotDrel@users.noreply.github.com> Date: Fri, 17 Apr 2026 15:35:21 -0400 Subject: [PATCH 6/8] fix: clarify forensics report path, expand version discovery, redact state dumps --- get-shit-done/workflows/feedback.md | 40 ++++++++++++++++------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/get-shit-done/workflows/feedback.md b/get-shit-done/workflows/feedback.md index 48789ad1ac..89f811c20d 100644 --- a/get-shit-done/workflows/feedback.md +++ b/get-shit-done/workflows/feedback.md @@ -31,8 +31,8 @@ If `ISSUE_TYPE` is `bug`, ask the user: > "Want me to run a quick forensics investigation first? This checks git history, planning state, and artifacts for anomalies that might explain the bug. [Y/n]" If yes: -1. Execute Steps 2–4 from `@~/.claude/get-shit-done/workflows/forensics.md` (Gather Evidence → Detect Anomalies → Generate Report). Skip Steps 5–8 (presentation, interactive investigation, issue creation, STATE update — those are handled here). -2. Read the generated report from `.planning/forensics/report-{timestamp}.md`. +1. Execute Steps 2–4 from `@~/.claude/get-shit-done/workflows/forensics.md` (Gather Evidence → Detect Anomalies → Generate Report). Skip Steps 5–8 (presentation, interactive investigation, issue creation, STATE update — those are handled here). +2. Read the most recently generated report from `.planning/forensics/report-*.md` (the file created by the just-completed run). 3. Set `INVESTIGATION_FINDINGS` to the redacted report contents. If no (or if `ISSUE_TYPE` is `feature` or `question`): @@ -55,11 +55,21 @@ esac GSD_VERSION="" for candidate in \ + "${CLAUDE_CONFIG_DIR:-}/get-shit-done/VERSION" \ "$PREFERRED_CONFIG_DIR/get-shit-done/VERSION" \ "$HOME/.claude/get-shit-done/VERSION" \ + "$HOME/.gemini/get-shit-done/VERSION" \ + "$HOME/.config/kilo/get-shit-done/VERSION" \ + "$HOME/.kilo/get-shit-done/VERSION" \ + "$HOME/.config/opencode/get-shit-done/VERSION" \ + "$HOME/.opencode/get-shit-done/VERSION" \ "$HOME/.codex/get-shit-done/VERSION" \ ".claude/get-shit-done/VERSION" \ - ".codex/get-shit-done/VERSION" + ".gemini/get-shit-done/VERSION" \ + ".config/kilo/get-shit-done/VERSION" \ + ".kilo/get-shit-done/VERSION" \ + ".config/opencode/get-shit-done/VERSION" \ + ".opencode/get-shit-done/VERSION" \ do if [ -n "$candidate" ] && [ -f "$candidate" ]; then GSD_VERSION="$(cat "$candidate")" @@ -124,16 +134,10 @@ Render the gathered diagnostics into `DIAGNOSTICS_MARKDOWN`: - workflow.use_worktrees: {USE_WORKTREES} - commit_docs: {COMMIT_DOCS} -### Raw State JSON +### State Snapshot (redacted) ```json -{STATE_JSON} -``` - -### Raw State Snapshot - -```json -{STATE_SNAPSHOT} +{STATE_SNAPSHOT_REDACTED} ``` ```` @@ -141,12 +145,12 @@ Render the gathered diagnostics into `DIAGNOSTICS_MARKDOWN`: Set the variables required by the shared issue-filing workflow, then delegate: -- `ISSUE_TYPE` — from step 1 -- `ISSUE_TITLE` — from step 1 -- `ISSUE_DESCRIPTION` — from step 1 -- `DIAGNOSTICS_MARKDOWN` — from step 3 -- `INVESTIGATION_FINDINGS` — from step 1 forensics enrichment (empty if skipped) -- `REPO` — `gsd-build/get-shit-done` +- `ISSUE_TYPE` — from step 1 +- `ISSUE_TITLE` — from step 1 +- `ISSUE_DESCRIPTION` — from step 1 +- `DIAGNOSTICS_MARKDOWN` — from step 3 +- `INVESTIGATION_FINDINGS` — from step 1 forensics enrichment (empty if skipped) +- `REPO` — `gsd-build/get-shit-done` Execute `@~/.claude/get-shit-done/workflows/file-issue.md` end-to-end. It handles rendering the final issue body, `gh issue create`, URL fallback, and raw markdown fallback. @@ -156,7 +160,7 @@ Execute `@~/.claude/get-shit-done/workflows/file-issue.md` end-to-end. It handle - Intake completes with type, title, and description - Diagnostics come from `gsd-sdk query` helpers where available -- Bug-type issues offer optional forensics enrichment via `/gsd-forensics` steps 2–4 +- Bug-type issues offer optional forensics enrichment via `/gsd-forensics` steps 2–4 - Issue filing delegates to `@~/.claude/get-shit-done/workflows/file-issue.md` - `DIAGNOSTICS_MARKDOWN` and `INVESTIGATION_FINDINGS` are set before delegation From 435c6e527d96a91304c178ddb3fdb8564b837f90 Mon Sep 17 00:00:00 2001 From: Elliot Drel <156480527+ElliotDrel@users.noreply.github.com> Date: Fri, 17 Apr 2026 15:35:23 -0400 Subject: [PATCH 7/8] fix: add ISSUE_TYPE enum validation (bug|feature|question) --- get-shit-done/workflows/file-issue.md | 1 + 1 file changed, 1 insertion(+) diff --git a/get-shit-done/workflows/file-issue.md b/get-shit-done/workflows/file-issue.md index e165ba5074..13599542d5 100644 --- a/get-shit-done/workflows/file-issue.md +++ b/get-shit-done/workflows/file-issue.md @@ -27,6 +27,7 @@ Callers set these variables before delegating to this workflow: ``` Validate required inputs. If `ISSUE_TYPE`, `ISSUE_TITLE`, or `ISSUE_DESCRIPTION` is missing, abort with a clear error — callers must satisfy the contract. +Also validate that `ISSUE_TYPE` is one of `bug`, `feature`, or `question`; if not, abort with a clear error. From 274fe35b707a51801102074c48fa55c7f67a16ec Mon Sep 17 00:00:00 2001 From: Elliot Drel <156480527+ElliotDrel@users.noreply.github.com> Date: Fri, 17 Apr 2026 15:38:34 -0400 Subject: [PATCH 8/8] fix: remove blank line between blockquotes (MD028) --- get-shit-done/workflows/forensics.md | 1 - 1 file changed, 1 deletion(-) diff --git a/get-shit-done/workflows/forensics.md b/get-shit-done/workflows/forensics.md index dad1e05a45..545c5606d1 100644 --- a/get-shit-done/workflows/forensics.md +++ b/get-shit-done/workflows/forensics.md @@ -250,7 +250,6 @@ If confirmed, ask: **Custom path:** Prompt the user directly for a title and description, pre-seeding the anomaly summary as a starting point: > "Suggest a title for this issue (or press Enter to use the auto-derived one): {auto title}" - > "Add a description (or press Enter to use the investigation summary):" Set `ISSUE_TITLE` and `ISSUE_DESCRIPTION` from the user's responses (falling back to the auto-derived values if blank). Do not invoke `feedback.md` — forensics enrichment is already complete.