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..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`)
@@ -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`)
@@ -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
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..89f811c20d
--- /dev/null
+++ b/get-shit-done/workflows/feedback.md
@@ -0,0 +1,166 @@
+
+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 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`):
+- 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 \
+ "${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" \
+ ".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")"
+ 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:-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}
+
+### State Snapshot (redacted)
+
+```json
+{STATE_SNAPSHOT_REDACTED}
+```
+````
+
+
+
+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..13599542d5
--- /dev/null
+++ b/get-shit-done/workflows/file-issue.md
@@ -0,0 +1,158 @@
+
+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.
+Also validate that `ISSUE_TYPE` is one of `bug`, `feature`, or `question`; if not, abort with a clear error.
+
+
+
+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 '.[] | select(.name == env.LABEL_NAME) | .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..545c5606d1 100644
--- a/get-shit-done/workflows/forensics.md
+++ b/get-shit-done/workflows/forensics.md
@@ -239,23 +239,42 @@ 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:** 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:
+
+- `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'
);
});