* 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:
5
.changeset/566-spawn-liveness-banner.md
Normal file
5
.changeset/566-spawn-liveness-banner.md
Normal 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`.
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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}",
|
||||
|
||||
@@ -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="
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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="""
|
||||
|
||||
@@ -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}",
|
||||
|
||||
@@ -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="""
|
||||
|
||||
@@ -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:
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
---
|
||||
```
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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({
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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:**
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
```
|
||||
|
||||
```
|
||||
|
||||
@@ -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}",
|
||||
|
||||
@@ -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" +
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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" +
|
||||
|
||||
@@ -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`
|
||||
|
||||
110
tests/spawn-liveness-banner.test.cjs
Normal file
110
tests/spawn-liveness-banner.test.cjs
Normal 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'
|
||||
);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user