enhancement(#558): add liveness hints to all GSD spawn announcements (#566)

* enhancement(#558): add liveness hints to all GSD spawn announcements

Append '(runs in a subagent — no output until it returns, ~1–5 min; expected,
not a freeze)' inline to every ◆ Spawning… banner and subagent dispatch
instruction across 26 workflows. Silent subagents look identical to frozen
sessions — this note sets the expectation so users wait instead of killing
healthy in-progress work.

Changes:
- references/ui-brand.md: document liveness convention under Spawning Indicators
- 10 banner workflows: append liveness note to ◆ Spawning… lines in-place
- 18 subagent-only workflows: add print instruction with liveness phrase
- tests/spawn-liveness-banner.test.cjs: new test; fails if any workflow with
  subagent_type omits 'runs in a subagent'
- docs/USER-GUIDE.md: troubleshooting entry for frozen-looking spawns
- .changeset/558-spawn-liveness-banner.md: changeset fragment

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(#558): add missing pr field to changeset fragment

The changeset lint requires pr: <NNN> in frontmatter; the fragment was
written without it, causing parse.cjs to reject it.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(#558): address codex review — missed spawns and tighten test

- plan-phase.md: add liveness note to chunked outline planner and
  per-plan chunked planner banners (two missed ◆ Spawning… lines)
- quick.md: add liveness note to research banner and add missing
  display line before planner spawn in Step 5
- plan-review-convergence.md: add liveness note to initial planning
  and review-agent spawn Display lines
- docs-update.md: add Print instructions with liveness note before
  gsd-doc-verifier spawns in Phase 1 and Phase 2
- autonomous.md: add Print instruction with liveness note before
  background plan-phase agent dispatch in step 3b
- tests/spawn-liveness-banner.test.cjs: replace single file-level
  check with two assertions:
  (1) every ◆ Spawning… banner line carries the phrase on that line
  (2) every file with subagent_type contains the phrase somewhere
  The tighter test would have caught all five missed spawns.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(#558): tighten spawn-liveness test regex to catch spawn-word-anywhere variants

Previous SPAWN_BANNER_RE only matched ◆ immediately followed by Spawning|spawning.
Replace with /◆[^\n]*\bspawning?\b/i which matches the spawn word anywhere on the
◆ line — catching "◆ Chunked mode: spawning outline planner..." and similar.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* fix(#558): rename changeset to PR number 566 and correct pr field

Changeset was filed as 558-spawn-liveness-banner.md (issue#) but the
convention is the PR number. Renamed to 566-spawn-liveness-banner.md
and updated pr: 558 → pr: 566 so release notes link to the right PR.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Tom Boucher
2026-05-31 23:33:49 -04:00
committed by GitHub
parent 0a12b06381
commit e3bc53f835
34 changed files with 197 additions and 43 deletions

View File

@@ -0,0 +1,5 @@
---
type: Changed
pr: 566
---
**Spawn announcements now include a liveness note** — every `◆ Spawning …` line and subagent dispatch instruction across all 26+ GSD workflows now carries the canonical phrase `runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze`. Silent subagents are visually identical to frozen sessions; this inline note sets the expectation so users wait instead of interrupting. Documented in `references/ui-brand.md § Spawning Indicators`, enforced by `tests/spawn-liveness-banner.test.cjs`, and explained in `docs/USER-GUIDE.md § Troubleshooting`.

View File

@@ -1126,6 +1126,14 @@ Always read a file before editing it. Never call Edit or Write on a file you hav
You ran `/gsd-new-project` but `.planning/PROJECT.md` already exists. This is a safety check. If you want to start over, delete the `.planning/` directory first.
### A Command Looks Frozen After "Spawning..."
If you see `◆ Spawning researcher...` (or any "Spawning…" line) and then nothing — no output, no spinner — for 1–5 minutes, **this is normal**. GSD subagents run in a separate context window; their work is invisible to the parent session while in progress. The liveness note on the spawn line confirms this: "(runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)".
**Do not interrupt the session.** Killing it discards the in-progress subagent work and forces you to restart that step. Wait for the result to appear. Research and planning agents routinely take 1–5 minutes; verification agents can take longer on large phases.
**If it truly seems stuck** (>10 minutes with no result): check whether Claude Code's agent task is still active in the sidebar. If the task shows as completed but no output appeared, the result may have been lost in a context switch — run the command again.
### Context Degradation During Long Sessions
Clear your context window between major commands: `/clear` in Claude Code. GSD is designed around fresh contexts -- every subagent gets a clean 200K window. If quality is dropping in the main session, clear and use `/gsd-resume-work` or `/gsd-progress` to restore state.

View File

@@ -83,10 +83,12 @@ Plans: 3/5 complete
## Spawning Indicators
```
◆ Spawning researcher...
**Liveness convention:** Every spawn announcement must carry the canonical phrase `runs in a subagent` inline so users know that silence during a subagent run is expected. Without this, a healthy 1–5 minute agent looks identical to a frozen session. Single spawns use the singular form; parallel spawns use the plural form.
◆ Spawning 4 researchers in parallel...
```
◆ Spawning researcher... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
◆ Spawning 4 researchers in parallel... (each runs in a subagent — no output until they return, ~1–5 min; expected, not a freeze)
→ Stack research
→ Features research
→ Architecture research

View File

@@ -94,7 +94,7 @@ final output — do not proceed to fixing.
<step name="fix-loop">
For each **auto-fixable** finding (up to `--max`, ordered by severity desc):
**a. Spawn executor agent:**
**a. Spawn executor agent** (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)**:**
```
Agent(
prompt="Fix finding {ID}: {description}. Files: {file_refs}. Make the minimal change to resolve this specific finding. Do not refactor surrounding code.",

View File

@@ -67,6 +67,8 @@ With phase context collected:
Extract `MILESTONE_REQ_IDS` from REQUIREMENTS.md traceability table — all REQ-IDs assigned to phases in this milestone.
Print: "Spawning integration checker (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)"
```
Agent(
prompt="Check cross-phase integration and E2E flows.

View File

@@ -324,6 +324,8 @@ UI_SPEC_FILE=$(ls "${PHASE_DIR}"/*-UI-SPEC.md 2>/dev/null | head -1)
**If `INTERACTIVE` is set:** Dispatch plan as a background agent to keep the main context lean. While plan runs, the workflow can immediately start discussing the next phase (see step 4).
Print: `◆ Spawning background planner for phase ${PHASE_NUM}... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)`
```
Agent(
description="Plan phase ${PHASE_NUM}: ${PHASE_NAME}",

View File

@@ -179,7 +179,7 @@ If REVIEW.md contains a `files_reviewed_list` frontmatter field, use that as the
</step>
<step name="spawn_fixer">
Spawn the gsd-code-fixer agent with config:
Spawn the gsd-code-fixer agent with config (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze):
```bash
# Build config for agent
@@ -271,7 +271,7 @@ if [ "$AUTO_MODE" = "true" ]; then
done
fi
# Spawn gsd-code-reviewer agent to re-review
# Spawn gsd-code-reviewer agent to re-review (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
# (This overwrites REVIEW_PATH with latest review state)
Agent(subagent_type="gsd-code-reviewer", prompt="
<config>
@@ -304,7 +304,7 @@ Do NOT commit the output — the orchestrator handles that.
break
fi
# Still has issues — spawn fixer again
# Still has issues — spawn fixer again (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
echo "Issues remain. Applying fixes for iteration ${ITERATION}..."
Agent(subagent_type="gsd-code-fixer", prompt="

View File

@@ -441,6 +441,8 @@ fi
Spawn the gsd-code-reviewer agent:
Print: `◆ Spawning code reviewer... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)`
```
Agent(subagent_type="gsd-code-reviewer", prompt="
<files_to_read>

View File

@@ -101,7 +101,7 @@ Eliminated: {count}
Surface to user. Then delegate directly to the session manager (skip Steps 2 and 3 — pass `symptoms_prefilled: true` and set the slug from SLUG variable). The existing file IS the context.
Print before spawning:
Print before spawning (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze):
```
[debug] Session: .planning/debug/{SLUG}.md
[debug] Status: {status}
@@ -190,6 +190,11 @@ Create `.planning/debug/{slug}.md` with initial state using the Write tool (neve
After initial context setup, spawn the session manager to handle the full checkpoint/continuation loop. The session manager handles specialist_hint dispatch internally: when gsd-debugger returns ROOT CAUSE FOUND it extracts the specialist_hint field and invokes the matching skill (e.g. typescript-expert, swift-concurrency) before offering fix options.
Print before spawning (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze):
```
[debug] Delegating loop to session manager...
```
```
Agent(
prompt="""

View File

@@ -96,6 +96,8 @@ EXPECTED_BASE=$(git rev-parse HEAD)
For each gap, fill the debug-subagent-prompt template and spawn:
Print: `◆ Spawning diagnostics agent... (each runs in a subagent — no output until they return, ~1–5 min; expected, not a freeze)`
```
Agent(
prompt=filled_debug_subagent_prompt + "\n\n<worktree_branch_check>\nFIRST ACTION: assert this is a disposable worktree branch before any repair. Run:\n```bash\nHEAD_REF=$(git symbolic-ref --quiet HEAD || echo \"DETACHED\")\nACTUAL_BRANCH=$(git rev-parse --abbrev-ref HEAD)\nif [ \"$HEAD_REF\" = \"DETACHED\" ] || echo \"$ACTUAL_BRANCH\" | grep -Eq '^(main|master|develop|trunk|release/.*)$'; then\n echo \"FATAL: diagnose worktree HEAD on '$ACTUAL_BRANCH'; refusing reset --hard on a protected branch.\" >&2\n exit 1\nfi\nif ! echo \"$ACTUAL_BRANCH\" | grep -Eq '^worktree-agent-[A-Za-z0-9._/-]+$'; then\n echo \"FATAL: diagnose worktree HEAD '$ACTUAL_BRANCH' is not in the worktree-agent-* namespace; refusing reset --hard.\" >&2\n exit 1\nfi\nACTUAL_BASE=$(git merge-base HEAD {EXPECTED_BASE})\nif [ \"$ACTUAL_BASE\" != \"{EXPECTED_BASE}\" ]; then\n git reset --hard {EXPECTED_BASE}\n [ \"$(git rev-parse HEAD)\" != \"{EXPECTED_BASE}\" ] && { echo \"ERROR: Could not correct worktree base\"; exit 1; }\nfi\n```\nFixes EnterWorktree creating branches from main on all platforms while preventing protected-branch data loss.\n</worktree_branch_check>\n\n<files_to_read>\n- {phase_dir}/{phase_num}-UAT.md\n- .planning/STATE.md\n</files_to_read>\n${AGENT_SKILLS_DEBUGGER}",

View File

@@ -252,7 +252,7 @@ Map to calibration tier:
If no USER-PROFILE.md: calibration_tier = "standard"
**Spawn Explore subagent:**
**Spawn Explore subagent** (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)**:**
```
Agent(subagent_type="gsd-assumptions-analyzer", prompt="""
@@ -310,7 +310,7 @@ Parse the subagent's response. Extract:
<step name="external_research">
**Skip if:** `needs_research` from deep_codebase_analysis is empty.
If research topics were flagged, spawn a general-purpose research agent:
If research topics were flagged, spawn a general-purpose research agent (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze):
```
Agent(subagent_type="general-purpose", prompt="""

View File

@@ -86,7 +86,7 @@ This reframing applies to:
After the user selects gray areas in `present_gray_areas`, spawn parallel
research agents.
1. Display brief status: `Researching {N} areas...`
1. Display brief status: `Researching {N} areas...` (each runs in a subagent — no output until they return, ~1–5 min; expected, not a freeze)
2. For EACH user-selected gray area, spawn a `Agent()` in parallel:

View File

@@ -374,7 +374,7 @@ After all decisions recorded, continue to detect_runtime_capabilities.
<step name="dispatch_wave_1" condition="Task tool is available">
**Read the work manifest first:** `Read .planning/tmp/docs-work-manifest.json` — use `canonical_queue` items with `wave: 1` for this step.
Spawn 3 parallel gsd-doc-writer agents for Wave 1 docs: README, ARCHITECTURE, CONFIGURATION.
Spawn 3 parallel gsd-doc-writer agents for Wave 1 docs: README, ARCHITECTURE, CONFIGURATION (each runs in a subagent — no output until they return, ~1–5 min; expected, not a freeze).
These are foundational docs with no cross-references needed, making them ideal for parallel generation.
@@ -831,7 +831,8 @@ Extract `canonical_queue` (items with `status: "completed"`) and `review_queue`
For each doc in `canonical_queue` that was successfully written to disk:
1. Spawn the `gsd-doc-verifier` agent (or invoke sequentially if Task tool is unavailable) with a `<verify_assignment>` block:
1. Print: `◆ Spawning doc verifier for {doc_path}... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)`
Spawn the `gsd-doc-verifier` agent (or invoke sequentially if Task tool is unavailable) with a `<verify_assignment>` block:
```xml
<verify_assignment>
doc_path: {relative path to the doc file, e.g. README.md}
@@ -849,7 +850,8 @@ This is NOT optional. Every doc in `review_queue` MUST be verified.
For each doc in `review_queue` from the manifest:
1. Spawn the `gsd-doc-verifier` agent with the same `<verify_assignment>` block as above.
1. Print: `◆ Spawning doc verifier for {doc_path}... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)`
Spawn the `gsd-doc-verifier` agent with the same `<verify_assignment>` block as above.
2. Read the result JSON from `.planning/tmp/verify-{doc_filename}.json`.
3. Update the manifest: set `status: "verified"` for each review_queue doc processed.

View File

@@ -73,7 +73,7 @@ Build file list for auditor:
## 3. Spawn gsd-eval-auditor
```
◆ Spawning eval auditor...
◆ Spawning eval auditor... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
Build prompt:

View File

@@ -491,7 +491,7 @@ increases monotonically across waves. `{status}` is `complete` (success),
**{Plan ID}: {Plan Name}**
{2-3 sentences: what this builds, technical approach, why it matters}
Spawning {count} agent(s)...
Spawning {count} agent(s)... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
---
```

View File

@@ -49,7 +49,7 @@ _GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-pars
AGENT_SKILLS_MAPPER=$(gsd_run query agent-skills gsd-codebase-mapper)
```
Then spawn `gsd-codebase-mapper` agents with the `--paths` hint:
Then spawn `gsd-codebase-mapper` agents with the `--paths` hint (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze):
```text
Agent(

View File

@@ -92,7 +92,7 @@ Otherwise: Apply checkpoint-based routing below.
| Verify-only | B (segmented) | Segments between checkpoints. After none/human-verify → SUBAGENT. After decision/human-action → MAIN |
| Decision | C (main) | Execute entirely in main context |
**Pattern A:** init_agent_tracking → capture `EXPECTED_BASE=$(git rev-parse HEAD)` → spawn Agent(subagent_type="gsd-executor", model=executor_model) with prompt: execute plan at [path], autonomous, all tasks + SUMMARY + commit, follow deviation/auth rules, report: plan name, tasks, SUMMARY path, commit hash → track agent_id → wait → update tracking → report. **Include `isolation="worktree"` only if `workflow.use_worktrees` is not `false`** (read via `config-get workflow.use_worktrees`). **When using `isolation="worktree"`, include a `<worktree_branch_check>` block in the prompt** instructing the executor to: (1) FIRST assert `git symbolic-ref HEAD` resolves to a per-agent branch (NOT a protected ref like `main`/`master`/`develop`/`trunk`/`release/*`) and HALT with a blocker if not — never self-recover via `git update-ref refs/heads/<protected>` (#2924); (2) only after that assertion passes, run `git merge-base HEAD {EXPECTED_BASE}` and, if the result differs from `{EXPECTED_BASE}`, hard-reset the branch with `git reset --hard {EXPECTED_BASE}` before starting work, then verify with `[ "$(git rev-parse HEAD)" != "{EXPECTED_BASE}" ] && exit 1`. The HEAD assertion (Step 1) MUST run before any reset/checkout. This corrects a known issue where `EnterWorktree` creates branches from `main` instead of the feature branch HEAD (affects all platforms — #2015) and prevents the destructive HEAD-on-master self-recovery path (#2924).
**Pattern A:** init_agent_tracking → capture `EXPECTED_BASE=$(git rev-parse HEAD)` → print `Spawning executor agent (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)` → spawn Agent(subagent_type="gsd-executor", model=executor_model) with prompt: execute plan at [path], autonomous, all tasks + SUMMARY + commit, follow deviation/auth rules, report: plan name, tasks, SUMMARY path, commit hash → track agent_id → wait → update tracking → report. **Include `isolation="worktree"` only if `workflow.use_worktrees` is not `false`** (read via `config-get workflow.use_worktrees`). **When using `isolation="worktree"`, include a `<worktree_branch_check>` block in the prompt** instructing the executor to: (1) FIRST assert `git symbolic-ref HEAD` resolves to a per-agent branch (NOT a protected ref like `main`/`master`/`develop`/`trunk`/`release/*`) and HALT with a blocker if not — never self-recover via `git update-ref refs/heads/<protected>` (#2924); (2) only after that assertion passes, run `git merge-base HEAD {EXPECTED_BASE}` and, if the result differs from `{EXPECTED_BASE}`, hard-reset the branch with `git reset --hard {EXPECTED_BASE}` before starting work, then verify with `[ "$(git rev-parse HEAD)" != "{EXPECTED_BASE}" ] && exit 1`. The HEAD assertion (Step 1) MUST run before any reset/checkout. This corrects a known issue where `EnterWorktree` creates branches from `main` instead of the feature branch HEAD (affects all platforms — #2015) and prevents the destructive HEAD-on-master self-recovery path (#2924).
**Pattern B:** Execute segment-by-segment. Autonomous segments: spawn subagent for assigned tasks only (no SUMMARY/commit). Checkpoints: main context. After all segments: aggregate, create SUMMARY, commit. See segment_execution.

View File

@@ -59,6 +59,8 @@ This would take ~30 seconds and might surface useful context.
```
If yes, spawn a research agent:
Print: `◆ Spawning explorer... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)`
```
Agent(
prompt="Quick research: {specific_question}. Return 3-5 key findings, no more than 200 words.",

View File

@@ -197,6 +197,8 @@ Write the PLAN.md file to the target directory.
Delegate validation to gsd-plan-checker:
Print: "Delegating to gsd-plan-checker (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)"
```
Agent({
subagent_type: "gsd-plan-checker",

View File

@@ -180,7 +180,7 @@ Collect the one-line confirmations from each classifier. If any classifier error
<step name="synthesize">
Spawn `gsd-doc-synthesizer` once:
Spawn `gsd-doc-synthesizer` once (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze):
```
Agent({
@@ -247,7 +247,7 @@ Proceed to routing silently, or optionally display `GSD > No conflicts. Auto-res
Audit PROJECT.md field requirements that `gsd-roadmapper` expects. For fields derivable from `.planning/intel/SYNTHESIS.md` (project scope, goals/non-goals, constraints, locked decisions), synthesize from the intel. For fields NOT derivable (project name, developer-facing success metric, target runtime), prompt via `AskUserQuestion` one at a time — minimal question set, no interrogation.
Delegate to `gsd-roadmapper`:
Delegate to `gsd-roadmapper` (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze):
```
Agent({

View File

@@ -269,7 +269,7 @@ Important: You are running in the background. Do NOT use AskUserQuestion — mak
Display:
```
◆ Spawning planner for Phase {N}: {phase_name}...
◆ Spawning planner for Phase {N}: {phase_name}... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
Loop back to dashboard step.
@@ -303,7 +303,7 @@ Important: You are running in the background. Do NOT use AskUserQuestion — mak
Display:
```
◆ Spawning executor for Phase {N}: {phase_name}...
◆ Spawning executor for Phase {N}: {phase_name}... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
Loop back to dashboard step.

View File

@@ -144,6 +144,8 @@ Use Agent tool with `subagent_type="gsd-codebase-mapper"`, `model="{mapper_model
**CRITICAL:** Use the dedicated `gsd-codebase-mapper` agent, NOT `Explore` or `browser_subagent`. The mapper agent writes documents directly.
Print: "Spawning 4 parallel codebase mapper agents (each runs in a subagent — no output until they return, ~1–5 min; expected, not a freeze)"
**Agent 1: Tech Focus**
```text

View File

@@ -287,7 +287,7 @@ AskUserQuestion: "Research the domain ecosystem for new features before defining
GSD ► RESEARCHING
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
◆ Spawning 4 researchers in parallel...
◆ Spawning 4 researchers in parallel... (each runs in a subagent — no output until they return, ~1–5 min; expected, not a freeze)
→ Stack, Features, Architecture, Pitfalls
```
@@ -455,7 +455,7 @@ gsd_run query commit "docs: define milestone v[X.Y] requirements" --files .plann
GSD ► CREATING ROADMAP
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
◆ Spawning roadmapper...
◆ Spawning roadmapper... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
**Starting phase number:**

View File

@@ -869,7 +869,7 @@ Check if this is greenfield or subsequent milestone:
Display spawning indicator:
```
◆ Spawning 4 researchers in parallel...
◆ Spawning 4 researchers in parallel... (each runs in a subagent — no output until they return, ~1–5 min; expected, not a freeze)
→ Stack research
→ Features research
→ Architecture research
@@ -1265,7 +1265,7 @@ Display stage banner:
GSD ► CREATING ROADMAP
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
◆ Spawning roadmapper...
◆ Spawning roadmapper... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
**ROADMAP.md template — mode-aware emit.** When generating the initial ROADMAP.md:

View File

@@ -480,7 +480,7 @@ Display banner:
GSD ► RESEARCHING PHASE {X}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
◆ Spawning researcher...
◆ Spawning researcher... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
### Spawn gsd-phase-researcher
@@ -815,7 +815,7 @@ Display banner:
GSD ► PATTERN MAPPING PHASE {X}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
◆ Spawning pattern mapper...
◆ Spawning pattern mapper... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
Pattern mapper prompt:
@@ -877,7 +877,7 @@ Display banner:
GSD ► PLANNING PHASE {X}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
◆ Spawning planner...
◆ Spawning planner... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
Planner prompt:
@@ -1040,7 +1040,7 @@ fi
Display:
```text
◆ Chunked mode: spawning outline planner...
◆ Chunked mode: spawning outline planner... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
Spawn the planner in **outline-only** mode — it must write only the outline manifest, not any
@@ -1087,7 +1087,7 @@ For each plan entry extracted from `PLAN-OUTLINE.md`:
2. Display:
```text
◆ Chunked mode: planning {plan_id} ({k}/{N})...
◆ Chunked mode: planning {plan_id} ({k}/{N})... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
3. Spawn the planner in **single-plan** mode — it must write exactly one PLAN.md file:
@@ -1225,7 +1225,7 @@ Display banner:
GSD ► VERIFYING PLANS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
◆ Spawning plan checker...
◆ Spawning plan checker... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
Checker prompt:

View File

@@ -98,7 +98,7 @@ Display startup banner:
**If `has_plans` is false:**
Display: `◆ No plans found — spawning initial planning agent...`
Display: `◆ No plans found — spawning initial planning agent... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)`
```text
Agent(
@@ -134,7 +134,7 @@ prev_high_count = Infinity
Increment `cycle`.
Display: `◆ Cycle {cycle}/{MAX_CYCLES} — spawning review agent...`
Display: `◆ Cycle {cycle}/{MAX_CYCLES} — spawning review agent... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)`
```text
Agent(
@@ -308,7 +308,7 @@ Exit workflow.
Update `prev_high_count = HIGH_COUNT`.
Display: `◆ Spawning replan agent with review feedback...`
Display: `◆ Spawning replan agent with review feedback... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)`
```text
Agent(

View File

@@ -392,7 +392,7 @@ Display banner:
GSD ► RESEARCHING QUICK TASK
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
◆ Investigating approaches for: ${DESCRIPTION}
◆ Investigating approaches for: ${DESCRIPTION} (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
Spawn a single focused researcher (not 4 parallel researchers like full phases — quick tasks need targeted research, not broad domain surveys):
@@ -455,6 +455,8 @@ If research file not found, warn but continue: "Research agent did not produce o
**If NOT `$VALIDATE_MODE`:** Use standard `quick` mode.
Display: `◆ Spawning planner... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)`
```
Agent(
prompt="
@@ -518,7 +520,7 @@ Display banner:
GSD ► CHECKING PLAN
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
◆ Spawning plan checker...
◆ Spawning plan checker... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
Checker prompt:
@@ -855,7 +857,7 @@ Display banner:
GSD ► VERIFYING RESULTS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
◆ Spawning verifier...
◆ Spawning verifier... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
```

View File

@@ -72,6 +72,8 @@ mkdir -p .planning/codebase
Spawn a single `gsd-codebase-mapper` agent with the selected focus area:
Print: `◆ Spawning scanner... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)`
```
Agent(
prompt="Scan this codebase with focus: {focus}. Write results to .planning/codebase/. Produce only: {document_list}",

View File

@@ -93,6 +93,8 @@ Call AskUserQuestion with threat table and options:
- `register_authored_at_plan_time: true` — **Verify mitigations exist** — do not scan for new threats. The register is complete; verify each threat's mitigation is present in the implementation.
- `register_authored_at_plan_time: false` (retroactive-STRIDE mode) — **Retroactive-STRIDE: build a STRIDE register from implementation files first, then verify mitigations.** The phase was authored before formal threat modelling; the auditor must construct the register from scratch before verifying.
Print: `◆ Spawning security auditor... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)`
```
Agent(
prompt="Read ~/.claude/agents/gsd-security-auditor.md for instructions.\n\n" +

View File

@@ -118,7 +118,7 @@ Display:
GSD ► UI DESIGN CONTRACT — PHASE {N}
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
◆ Spawning UI researcher...
◆ Spawning UI researcher... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
Build prompt:
@@ -183,7 +183,7 @@ Display:
GSD ► VERIFYING UI-SPEC
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
◆ Spawning UI checker...
◆ Spawning UI checker... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
Build prompt:

View File

@@ -68,7 +68,7 @@ Build file list for auditor:
## 3. Spawn gsd-ui-auditor
```
◆ Spawning UI auditor...
◆ Spawning UI auditor... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
Build prompt:

View File

@@ -93,6 +93,8 @@ Call AskUserQuestion with gap table and options:
## 5. Spawn gsd-nyquist-auditor
Print: `◆ Spawning nyquist auditor... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)`
```
Agent(
prompt="Read ~/.claude/agents/gsd-nyquist-auditor.md for instructions.\n\n" +

View File

@@ -552,7 +552,7 @@ Display:
GSD ► PLANNING FIXES
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
◆ Spawning planner for gap closure...
◆ Spawning planner for gap closure... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
Spawn gsd-planner in --gaps mode:
@@ -602,7 +602,7 @@ Display:
GSD ► VERIFYING FIX PLANS
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
◆ Spawning plan checker...
◆ Spawning plan checker... (runs in a subagent — no output until it returns, ~1–5 min; expected, not a freeze)
```
Initialize: `iteration_count = 1`

View File

@@ -0,0 +1,110 @@
// allow-test-rule: source-text-is-the-product
// Tests that every GSD workflow that spawns a subagent carries the liveness phrase
// "runs in a subagent" on its spawn announcement lines.
// Canonical phrase defined in get-shit-done/references/ui-brand.md § Spawning Indicators.
// Regression test for https://github.com/open-gsd/gsd-core/issues/558.
//
// TEST STRATEGY: Two complementary assertions.
//
// 1. SPAWN-BANNER CHECK — any ◆ display line that contains the word "spawn" or "spawning"
// (case-insensitive, anywhere on the line) must carry the liveness phrase. This is more
// rigorous than a simple /◆\s+[Ss]pawning/ prefix match and catches all variants:
// "◆ Spawning researcher..." (spawn word right after ◆)
// "◆ Chunked mode: spawning planner..." (spawn word after prefix text)
// It does NOT match ◆ status/error lines that say "planner returned" or "checker wrote"
// (those don't contain the word "spawn"), so it has no false positives.
//
// 2. PRESENCE CHECK (coarser fallback) — if a file contains `subagent_type`, it must
// contain the liveness phrase at least once. Catches workflows that dispatch subagents
// without any ◆ spawn banner (e.g. prose-only "Spawn X" instructions).
//
// Together the two assertions are strictly stronger than the original file-level check:
// a file with 10 spawns where only one ◆ Spawning banner got the note still fails #1.
// A file with subagent_type but no banner at all still fails #2.
const { test, describe } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const path = require('path');
const WORKFLOWS_DIR = path.join(__dirname, '..', 'get-shit-done', 'workflows');
const LIVENESS_PHRASE = 'runs in a subagent';
// Matches any ◆ line that contains "spawn" or "spawning" (case-insensitive, word anywhere).
// This covers:
// "◆ Spawning researcher..." → matched
// "◆ Chunked mode: spawning X..." → matched
// But NOT:
// "◆ Planner wrote N plan(s)..." → not matched (no "spawn" word)
// "◆ Research phase enabled" → not matched (no "spawn" word)
const SPAWN_BANNER_RE = /◆[^\n]*\bspawning?\b/i;
function findMdFiles(dir) {
const entries = fs.readdirSync(dir, { withFileTypes: true });
const files = [];
for (const entry of entries) {
const full = path.join(dir, entry.name);
if (entry.isDirectory()) {
files.push(...findMdFiles(full));
} else if (entry.isFile() && entry.name.endsWith('.md')) {
files.push(full);
}
}
return files;
}
describe('spawn-liveness-banner', () => {
test('every ◆ spawn announcement line carries the liveness phrase "runs in a subagent"', () => {
const mdFiles = findMdFiles(WORKFLOWS_DIR);
const bannerViolations = [];
for (const filePath of mdFiles) {
const content = fs.readFileSync(filePath, 'utf-8');
const lines = content.split('\n');
const rel = path.relative(WORKFLOWS_DIR, filePath);
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (SPAWN_BANNER_RE.test(line) && !line.includes(LIVENESS_PHRASE)) {
bannerViolations.push(`${rel}:${i + 1}: ${line.trim()}`);
}
}
}
assert.deepStrictEqual(
bannerViolations,
[],
`The following ◆ spawn announcement lines are missing the liveness phrase "${LIVENESS_PHRASE}":\n` +
bannerViolations.map(v => ` - ${v}`).join('\n') +
'\n\nPer get-shit-done/references/ui-brand.md § "Spawning Indicators":\n' +
'every ◆ spawn announcement must carry "runs in a subagent" so users know\n' +
'that silence during a subagent run is expected and do not kill a healthy agent.\n' +
'See https://github.com/open-gsd/gsd-core/issues/558'
);
});
test('every workflow that dispatches a subagent contains the liveness phrase somewhere', () => {
const mdFiles = findMdFiles(WORKFLOWS_DIR);
const presenceViolations = [];
for (const filePath of mdFiles) {
const content = fs.readFileSync(filePath, 'utf-8');
const rel = path.relative(WORKFLOWS_DIR, filePath);
if (content.includes('subagent_type') && !content.includes(LIVENESS_PHRASE)) {
presenceViolations.push(rel);
}
}
assert.deepStrictEqual(
presenceViolations,
[],
`The following workflow files contain "subagent_type" but are missing the liveness phrase "${LIVENESS_PHRASE}" anywhere in the file:\n` +
presenceViolations.map(f => ` - ${f}`).join('\n') +
'\n\nPer get-shit-done/references/ui-brand.md § "Spawning Indicators":\n' +
'every workflow that spawns a subagent must carry "runs in a subagent" so users know\n' +
'that silence during a subagent run is expected and do not kill a healthy agent.\n' +
'See https://github.com/open-gsd/gsd-core/issues/558'
);
});
});