@~/.claude/gsd-core/references/response-language-directive.md

<purpose>
Recommend a test strategy matched to the architecture: WHAT to test, at WHICH level, HOW MUCH. The test shape is an OUTPUT of the architecture decision (rich core → unit; CRUD-over-DB → integration), never a pyramid/diamond picked up front. Runs after recommend-architecture, before planning. Extends the project's existing TESTING-STANDARDS.md rigor — never weakens it. Produces `.planning/TEST-STRATEGY.md`, consumed by add-tests, execute-phase, and plan-phase.
</purpose>

<required_reading>
@~/.claude/gsd-core/references/test-strategy.md
@~/.claude/gsd-core/references/brownfield-adaptation.md
@~/.claude/gsd-core/templates/test-strategy.md
</required_reading>

<process>

## Step 1: Initialize

```bash
_GSD_SHIM_NAME="gsd-tools.cjs"; _GSD_RUNTIME_ROOT="${RUNTIME_DIR:-$(git rev-parse --show-toplevel 2>/dev/null || pwd)}"; GSD_TOOLS="${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}"; _gsd_at() { for _p; do if [ -f "$_p" ]; then GSD_TOOLS="$_p"; return 0; fi; done; return 1; }; if _gsd_at "${_GSD_RUNTIME_ROOT}/gsd-core/bin/${_GSD_SHIM_NAME}" "${_GSD_RUNTIME_ROOT}/.claude/gsd-core/bin/${_GSD_SHIM_NAME}" "${_GSD_RUNTIME_ROOT}/.codex/gsd-core/bin/${_GSD_SHIM_NAME}"; then gsd_run() { node "$GSD_TOOLS" "$@"; }; elif unset -f gsd_run; _G="$(command -v gsd_run)"; then GSD_TOOLS="$_G"; gsd_run() { "$GSD_TOOLS" "$@"; }; elif _gsd_at "${CLAUDE_CONFIG_DIR:-$HOME/.claude}/gsd-core/bin/${_GSD_SHIM_NAME}" "${HERMES_HOME:-$HOME/.hermes}/gsd-core/bin/${_GSD_SHIM_NAME}" "${CURSOR_CONFIG_DIR:-$HOME/.cursor}/gsd-core/bin/${_GSD_SHIM_NAME}" "${CODEX_HOME:-$HOME/.codex}/gsd-core/bin/${_GSD_SHIM_NAME}" "${GEMINI_CONFIG_DIR:-$HOME/.gemini}/gsd-core/bin/${_GSD_SHIM_NAME}" "${COPILOT_CONFIG_DIR:-$HOME/.copilot}/gsd-core/bin/${_GSD_SHIM_NAME}" "${WINDSURF_CONFIG_DIR:-$HOME/.codeium/windsurf}/gsd-core/bin/${_GSD_SHIM_NAME}" "${AUGMENT_CONFIG_DIR:-$HOME/.augment}/gsd-core/bin/${_GSD_SHIM_NAME}" "${TRAE_CONFIG_DIR:-$HOME/.trae}/gsd-core/bin/${_GSD_SHIM_NAME}" "${QWEN_CONFIG_DIR:-$HOME/.qwen}/gsd-core/bin/${_GSD_SHIM_NAME}" "${CODEBUDDY_CONFIG_DIR:-$HOME/.codebuddy}/gsd-core/bin/${_GSD_SHIM_NAME}" "${CLINE_CONFIG_DIR:-$HOME/.cline}/gsd-core/bin/${_GSD_SHIM_NAME}" "${GROK_AGENTS_HOME:-$HOME/.agents}/gsd-core/bin/${_GSD_SHIM_NAME}" "${ANTIGRAVITY_CONFIG_DIR:-$HOME/.gemini/antigravity}/gsd-core/bin/${_GSD_SHIM_NAME}" "${OPENCODE_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/opencode}/gsd-core/bin/${_GSD_SHIM_NAME}" "${KILO_CONFIG_DIR:-${XDG_CONFIG_HOME:-$HOME/.config}/kilo}/gsd-core/bin/${_GSD_SHIM_NAME}"; then gsd_run() { node "$GSD_TOOLS" "$@"; }; else echo "ERROR: gsd-tools.cjs not found at $GSD_TOOLS and gsd_run is not on PATH. Run: npx -y @therocketcode/gsd-core@latest --claude --local" >&2; exit 1; fi; GSD_IDENTITY_STATUS=unverified; case "$(gsd_run runtime-identity --raw 2>/dev/null || true)" in '{"packageName":"@therocketcode/gsd-core"'*'}') GSD_IDENTITY_STATUS=ok;; esac; export GSD_IDENTITY_STATUS; [ "$GSD_IDENTITY_STATUS" = ok ] || echo "WARNING: \"$GSD_TOOLS\" did not prove it is @therocketcode/gsd-core - it is either a different package or an @therocketcode/gsd-core older than the runtime-identity verb. See docs/how-to/diagnose-a-foreign-gsd-tools.md" >&2; if [ -n "${CLAUDE_ENV_FILE:-}" ] && [ -n "${GSD_TOOLS:-}" ]; then printf "export PATH='%s':\"\$PATH\"\n" "${GSD_TOOLS%/*}" >> "$CLAUDE_ENV_FILE" 2>/dev/null || true; fi
COMMIT_DOCS=$(gsd_run query config-get commit_docs --raw 2>/dev/null || echo "true")
RESPONSE_LANG=$(gsd_run query config-get response_language --raw 2>/dev/null || true)
TDD_MODE=$(gsd_run query config-get workflow.tdd_mode --raw 2>/dev/null || echo "false")
ls .planning/PROJECT.md >/dev/null 2>&1 && echo "PROJECT_FOUND" || echo "NO_PROJECT"
ls .planning/adr/*.md >/dev/null 2>&1 && echo "HAS_ADR" || echo "NO_ADR"
ls .planning/TEST-STRATEGY.md >/dev/null 2>&1 && echo "EXISTS" || echo "NEW"
```

**If `NO_PROJECT`:** Stop — "No project found. Run /gsd:new-project first." Exit.

**If `RESPONSE_LANG` non-empty:** all user-facing text in that language; keep technical terms, code, and level names (small/medium/large, unit/integration/e2e) in English.

**Text mode** (`--text` OR `workflow.text_mode: true`): replace every `AskUserQuestion` with a plain-text numbered list.

**If `--tune-up`:** this run is not strategy authoring — it is the **suite tune-up flow** (the repair half of suite health, reached from `transition`'s T1 todo, from the milestone-close todo it writes for T2–T4, or by hand). If `NEW` (no TEST-STRATEGY.md), stop: "No TEST-STRATEGY.md — run `/gsd:testing-strategy` first; there is no strategy to audit the suite against." Otherwise read and execute `@$HOME/.claude/gsd-core/workflows/testing-strategy/steps/suite-tune-up.md` end to end, then commit its `## Suite health` append with Step 8's mechanism (message: `docs: re-baseline suite health after tune-up`) and **EXIT** — Steps 2–7 and 9 do not run; a tune-up never rewrites the strategy.

**If `EXISTS` and not `--auto`:** ask Update / View / Skip (header "Strategy"). On Skip: exit ("Existing TEST-STRATEGY.md preserved."). On View: show then Update/Skip.

**On Update (and on `--auto` over an existing file):** read the existing file's `## Coverage debt` table before re-deriving anything. Those rows are the only record of where this project's pyramid actually leaks — each one is a behavior that reached UAT or certification unproven. Where they cluster (a subdomain, a level, a kind of behavior), raise that area's level emphasis in Step 3 and add it to Step 4's gnarly-bits list, and say in the wrap-up which rows drove a change. An empty table is the healthy state and needs no comment; an **absent** section is equally valid (every pre-certification-era file — the section is created only when `verify-work` writes its first row, never pre-created empty by an Update). Carry existing rows forward verbatim — this section is append-only and `verify-work` is its writer, not this skill. **Update mode also governs Step 7:** on an existing file, Step 7 is a section-merge, never a fresh render — the full preservation contract is stated there.

## Step 2: Load context

```bash
cat .planning/PROJECT.md 2>/dev/null || true
cat .planning/REQUIREMENTS.md 2>/dev/null || true
cat .planning/DOMAIN-MODEL.md 2>/dev/null || true
cat .planning/adr/*.md 2>/dev/null || true
cat TESTING-STANDARDS.md 2>/dev/null || true
cat .planning/codebase/TESTING.md 2>/dev/null || true
gsd_run query project mode 2>/dev/null   # Origin/Design/Code-quality — robust + placeholder-aware (is_placeholder distinguishes a filled value from the template stub)
ls .planning/codebase/TESTING.md >/dev/null 2>&1 && echo "HAS_MAPS" || echo "NO_MAPS"
```

**Read `@~/.claude/gsd-core/references/test-strategy.md` now** — it defines behavior-over-implementation, sociable-by-default, test-once-at-cheapest-level, shape-follows-architecture (size axis), the gnarly-bits list, persistent-vs-transient e2e, and coverage-as-floor + mutation.

**Brownfield mode (existing test suite / untested legacy).** Trigger when `## Mode` records Origin = brownfield-extend / rewrite-refactor (authoritative), or — when `## Mode` is absent — `HAS_MAPS`/existing code is present (and especially when Code-quality = vibe-coded-to-harden). Then **read `@~/.claude/gsd-core/references/brownfield-adaptation.md` now** and recommend the test strategy as an *evolution*, not a from-scratch shape: assess the current tests from TESTING.md (framework, structure, coverage) and frame Steps 3–6 as additions on top of what exists, keeping the existing framework/conventions unless there's a decision-card reason to change. Key rule: for **untested legacy you intend to change, write characterization tests first** (pin the *current* behavior, bugs included) — starting at the **highest churn × risk hotspots** (git history), as a *local* safety net around the change region; never block on a global coverage %. Surface each gap (missing level, weak assertions, money-as-float, etc.) as a **decision card** (current → target → gap cost → Follow / Improve / Refactor) and **default-select Improve**; gate any Refactor behind those characterization tests. Greenfield (no map, no code) keeps the from-scratch derivation below as the default.

**Grounding maturity governs elicitation depth.** When upstream artifacts (spec, ADR, strategies, research) already answer a step, draft-from-docs and present for confirmation — cite the source, don't re-interview. Reserve questions for genuine decision points and contradictions. Honor a posture stated in `$ARGUMENTS` without re-asking.


**If `NO_ADR`:** unless recommend-architecture is a ledgered skip (`gsd_run query project strategy-skipped recommend-architecture --raw` = `true` → note once, don't re-offer), tell the user "No architecture decision found — I'll ask briefly. (Consider `/gsd:recommend-architecture` first.)" Then, per major subdomain, get its rung (Transaction Script / Domain Model / Hexagonal / CQRS / Event Sourcing) and whether it's DB/integration-bound. Otherwise extract each subdomain's rung from the ADR.

## Step 3: Derive the shape FROM the architecture (per subdomain)

The shape is an **output**, never a target you pick. For each subdomain, map its architecture rung → primary test level (use the reference's table):
- **Domain Model / rich core** → more **small (unit)** tests of the domain logic through its public API; **sociable** (real collaborators), mock only at ports.
- **Transaction Script / CRUD-over-DB** → more **medium (integration)** tests against a real DB (see `test-containers` / `db-test-isolation`); few unit tests.
- **Hexagonal core** → pure domain needs no doubles; test the application core with in-memory **fakes** at its ports (see `test-doubles.md`); integration-test the adapters real.
- **Many external integrations** → medium integration tests at the ports; contract tests where a 3rd-party can't be seeded.
- **Bought / off-the-shelf (Generic)** → do NOT test the vendor's internals; thin integration smoke at your own adapter seam only.

Record subdomain → primary level + the rung that justifies it. Do NOT announce a chosen "pyramid/diamond" — let the distribution emerge. If the user asks to pick a shape, redirect: the architecture already determines where the behavior lives. If the user asks to mock the database or all collaborators, reject it: integration tests run against a **real** DB (see `test-containers` / `db-test-isolation`); mock ONLY at external ports — never the DB or in-process collaborators. If the user proposes mocking a 3rd-party API in integration tests and calling it covered, reject that too: a mock proves nothing about the real provider — use a verified contract, or, for vendors who won't run verification, the schema + recorded-fixtures + sandbox-smoke fallback (see `contract-testing.md`).

## Step 4: Gnarly bits + what NOT to test

From DOMAIN-MODEL + REQUIREMENTS — both optional; when either is absent, derive from PROJECT.md's requirements plus the ADR/architecture and the source itself, and say so in the doc — identify the **pure, logic-dense** code that earns unit tests: money/currency (**integer minor units or exact decimal — never float**), complex conditionals/**state machines**, **parsers**, **algorithms**, pure functions. List them as unit-test targets.

State what NOT to test: framework/library code, trivial getters/setters, mock behavior; and the rule — **each behavior tested once, at the cheapest level** (no duplicate unit+integration+e2e coverage of the same behavior).

If existing code already violates a standard you are recording (e.g. money stored as floats), flag it in TEST-STRATEGY.md's Notes as a **pre-test remediation task** (refactor first) — never write tests that enshrine the violating representation.

## Step 5: The top of the pyramid — gate vs certify

**The top of the pyramid is two different jobs.** **Gate** — deterministic, every commit, CI; scripts gate. **Certify** — realistic, per-change, agentic, human-adjacent; **never a CI gate**. A hermetic ephemeral-environment smoke run is a regression check, not a validation of the app in the world (see `certification.md § The doctrine`). The strategy records both jobs and never conflates them.

**The gate (scripted, agent-maintained).**
- **Elicit the smoke set.** Ask (AskUserQuestion, header "E2E", or a text list): "Which flows are so essential they must be smoke-tested on every CI run? (e.g., auth, payment, the core journey)." Capture 3–7 → the **persistent** smoke suite (keep it lean, <5 min).
- **Everything else is transient** — throwaway dev-loop e2e, demoted to integration once covered cheaper. If the user asks to e2e every feature/edge case, redirect: that is the ice-cream cone — cap the persistent suite at 3–7 critical journeys (the <5 min budget wins over any test count) and push edge cases down to unit/integration.
- **Record the gate's maintenance mechanism.** Where an authoring agent exists, the smoke set is agent-authored and agent-healed via the **planner → generator → healer** loop (Playwright ships all three first-party, `--loop=claude|codex`), with the healer's own guardrail recorded — it must refuse to "fix" a test when the app is **genuinely broken**. Hand-maintained smoke scripts are the fallback where no authoring agent exists.

**The certification mechanism (decided from Step 5.5's probe).** Every user-visible feature gets certified in real conditions at the tier the probe detects; on CERT-0 the requirement is satisfied by human UAT — the invariant is "certification happens," not "an agent does it." Record the mechanism in the template's `## Certification` section.

## Step 5.5: Certification capability detection (probe, don't survey)

**Read `@~/.claude/gsd-core/references/certification.md` now** — it defines the CERT-0/1/1-limited/2 ladder, the **surface types** (browser / cli / api / library), the probe procedure, the trust doctrine, the brief, and the substrate honesty tables.

**Surface type first — it decides which probe even applies.** From ARCHITECTURE and this milestone's deliverables, record the surface users touch: **browser** (running app in a browser), **cli** (a binary run from a shell), **api** (endpoints hit by a client), or **library** (no user-facing runtime surface). The browser probe below runs **only** for a browser surface. For a **library**, there is nothing to drive — record `no user-facing surface` and skip the probe. For **cli/api**, the probe is a *real-surface exercise*, not a browser probe (below).

Observable checks first — run them, don't ask. Discovery must look **where the trust doctrine puts a certifier**, not only on PATH — the doctrine installs third-party tools into an isolated sandbox HOME (`$HOME/.gsd-cert-sandbox`), never onto the real PATH, so a bare `command -v` alone would record CERT-0 for the *most* doctrine-compliant user while a probe inside the sandbox passes:

```bash
CERT_SANDBOX="${GSD_CERT_SANDBOX_HOME:-$HOME/.gsd-cert-sandbox}"
for tool in codex orca; do
  if command -v "$tool" >/dev/null 2>&1; then echo "$tool:present:path"
  elif [ -x "$CERT_SANDBOX/.local/bin/$tool" ] || ls "$CERT_SANDBOX"/**/"$tool" >/dev/null 2>&1; then echo "$tool:present:sandbox"
  else echo "$tool:missing"; fi
done
ls playwright.config.* >/dev/null 2>&1 && echo "playwright-config:present" || echo "playwright-config:missing"
grep -qi microsoft /proc/version 2>/dev/null && echo "env:wsl" || echo "env:not-wsl"
[ -n "${DISPLAY:-}${WAYLAND_DISPLAY:-}" ] && echo "display:present" || echo "display:headless"
```

`missing` on PATH is **not** `absent` — a certifier that lives in the sandbox by design, or on another machine, is discovered by the sandbox check and by the one question, never by PATH alone. Plus the in-session facts: MCP browser tools available (`mcp__playwright__*` responding), `claude` Chrome integration (not under WSL; disabled under API-key auth).

**Trust gate before any launch (sandbox-first).** The first launch of any third-party certifier **in this environment** — including the probe itself, including a bare `--version` — happens under an **isolated HOME** (`$HOME/.gsd-cert-sandbox`, never `/tmp`) with an **instrumentation audit** (*what did it write? which agent CLIs did it touch?*) before the tool touches the real environment (the reference carries the receipts). "Installed" is not "launched" — `command -v` finding a binary says nothing about what its first run will do to this HOME.

**A lead is not a capability.** Probe by surface type:
- **browser:** run the reference's **5-command live probe** against a throwaway page — goto → snapshot → fill → click round-trip (verify the click by its *effect*, never its return value; the fill precedes the click because click-after-fill is the failure that defines the limited tier) → screenshot — and record the **per-operation** verdicts.
- **cli:** run the binary for real with representative args and assert stdout + exit code + side-effects; a fresh agent driving the real binary is CERT-1 (CERT-2 when a separate certifier runs it).
- **api:** issue the real requests a client would, with a seeded token (§5.7 auth), asserting status/shape/auth-enforcement/persisted-effect against a real dependency; the live service driven for real is CERT-1 (CERT-2 handed off).
- **library:** no probe — `no user-facing surface`.

Then ask the **one question** nothing observable answers (header "Certify"): "Do you have Codex desktop, Claude Desktop, or onorca available for certification — on this machine or another?" (capability is a project fact, not a machine fact).

Map results → tier (CERT-0 / CERT-1 (limited) / CERT-1 / CERT-2) and record surface type + tier + probe rows + mechanism in `## Certification`. **A runnable cli/api surface is CERT-1, never CERT-0** — CERT-0 means the real surface cannot be exercised here, not "no browser". Never present CERT-0 apologetically — human UAT plus the scripted gate is the honest floor; record the deferred capability with the observable fact that would promote it (e.g. "full click-through — re-probe on a visible display session").

## Step 5.7: Certification substrate (four policies)

**Infer before asking.** SECURITY-STRATEGY.md (data classification, secrets floor) and INFRA-STRATEGY.md (environments, mail/LLM vendors) usually answer most of this — draft-from-docs and confirm; never re-interview. Decide the four policies and record them in `## Certification substrate`:

1. **Seed test accounts** — first-class artifacts: an idempotent seed script, role-tagged accounts documented in TEST-STRATEGY, credentials in the env/secret store (agent-usable, never in the repo). Real *seeded* accounts on a real environment — **never real user data** (`data-environments.md` governs).
2. **Email safety** — sandbox catcher (Mailpit-class) by default, or a **verified** provider test mode — the reference's vendor honesty table: the modes differ in kind (SendGrid validates free; Mailgun/Postmark still bill or count; **Resend has none** — magic recipients only). Real recipients only when deliverability IS the feature, recorded as such.
3. **LLM integrations** — real calls (the integration is the thing under test): dedicated test key with a hard spend cap, configurable pinned cheap-but-representative model, transcript captured as evidence, and **shape-not-content assertions** (Anthropic disclaims temperature-0 determinism). Stubs remain correct for rate-limited vendors and for deterministic unit/integration tiers.
4. **OAuth/auth** — a verified provider test mode where one exists (Clerk Testing Tokens, `+clerk_test`, code 424242); otherwise the formalized field pattern: **one-time human auth, persisted session** (gitignored storage state, hygiene per the reference) — an honest, first-class answer. Auth moments and CAPTCHAs always escalate to the human.

A capability the project does not have is recorded as `N/A — no {email|LLM|auth|data} surface (revisit when one appears)` — a first-class row value, never left blank and never a policy invented for a surface that does not exist. Anything here that requires a human in a dashboard (account creation, credential retrieval, catcher provisioning) becomes `{phase}-USER-SETUP.md` material via the `user_setup` PLAN frontmatter — see `@$HOME/.claude/gsd-core/templates/user-setup.md`.

## Step 6: Coverage / mutation / TDD stance

- **Coverage = floor, not a target.** Record that. If the user demands a coverage *target* (e.g. 100%), reframe it as a floor and warn that an excessively high floor forces low-value tests of trivial glue — the real quality signal is **mutation score** on the gnarly bits. **Mutation testing (Stryker)** applies to the critical modules (the gnarly bits from Step 4 + the core domain logic).
- **TDD stance:** mandate behavior-level tests + **small uniform increments** + a regression floor with a real RED step. Test-first vs test-after is the `workflow.tdd_mode` knob (currently **${TDD_MODE}**) — surface it; don't force test-first as dogma. **Exception:** where `TESTING-STANDARDS.md` mandates a red-first/test-first phase for a module, that project standard governs and overrides the knob there.
- **If `TESTING-STANDARDS.md` exists:** confirm its standards remain in force, and **carry any project-specific standards beyond the reference's defaults (e.g. clock-seam concurrency, no-elapsed-time assertions, delete-bad-tests, the `fast-check` property tier) into TEST-STRATEGY.md's Notes** so downstream skills see them — by **appending**; existing Notes content is user-extendable and is never overwritten. **If it's absent:** adopt the reference's defaults (real-code-only, no vacuous assertions, typed surface, `fast-check` property tier, Stryker ≥80 on critical modules) as the project baseline and **write them to `TESTING-STANDARDS.md` (create it)** — the standards file is their home; TEST-STRATEGY's Notes just points at it. Reference defaults are never written into an existing Notes section: on an Update that path clobbers the project's own recorded invariants with boilerplate.

## Step 6.5: Born fast + the suite-health baseline

Suites are **born fast by configuration**, not rescued later. Apply the born-fast defaults (`@$HOME/.claude/gsd-core/references/test-strategy.md` `§ Suite health` — class-based, explicitly non-exhaustive, current APIs): Testcontainers **reuse is local-only** ("not suited for CI usage" — their own docs; CI levers are singleton containers + image/layer caching); Vitest 4 top-level `maxWorkers`/`isolate` (`poolOptions` is removed); cargo nextest; pytest-xdist. **No invented per-test budgets** — none exist in primary sources; the trigger thresholds are GSD's own heuristics and say so.

Seed the `## Suite health` baseline — this is the table's **first row**, and Step 6.5 is the one writer allowed to create it (every later row is appended by the tune-up flow's fourth pass): if a test command exists, run it **once, timed with a real millisecond bracket** (`node -e 'Date.now()'` before/after, or the runner's own reported duration — never a plain `date +%s`, whose 1-second resolution renders every sub-second suite identical), and record `{test_count, wall_clock (integer milliseconds, minimum 1), containers_started}` with today's date (`containers_started` from Testcontainers/docker output where visible, else `—`; `ms/test` is derived at compare time, never recorded). This timed run is **the** measurement `/gsd:cicd-strategy`'s C1-a trigger reads — record it once here, never re-measure it there. Greenfield (no suite yet): record `unmeasured — baseline at the first milestone with a real suite`. The four triggers (T1–T4, reference table) govern re-evaluation: **T1** (dev-loop tier >~90 s local; PR gate >10 min = cicd's C1-a) fires **immediately**; T2/T3/T4 are compared at **milestone close** and schedule a tune-up there. Flat ms/test + rising total = volume, not regression → the remedy is tiering/sharding (C1), not tuning.

This baseline is read by machinery, not by hand: the executor records `suite-metrics:` into SUMMARY frontmatter each time it runs the whole suite (`@$HOME/.claude/gsd-core/workflows/execute-phase/steps/post-merge-gate.md` Step C), `transition`'s `suite_health_compare` step evaluates the newest measurement against this row, and whichever trigger fires attaches the tune-up flow (`/gsd:testing-strategy --tune-up` → the suite-tune-up step above), which appends the next row. Re-baselining is **append-only** — the history is the trend the triggers compare against.

<!-- FORK:context BEGIN -->
After each elicitation round, append it to `.planning/PROJECT-DISCUSSION-LOG.md` per `@$HOME/.claude/gsd-core/references/context-lifecycle.md` (skip if `context_lifecycle.discussion_logs` is disabled).
<!-- FORK:context END -->

## Step 7: Write TEST-STRATEGY.md

**Two modes, decided by Step 1.** Fresh render (no existing file): render the template
and fill as below. **Update (file exists): Step 7 is a section-merge, never a fresh
render** — the template supplies missing sections; existing content wins:

- **Preserved byte-intact** unless this run explicitly revisited them with the user:
  `## Notes`, every gnarly-bits entry, what-not-to-test entries, level-table
  rationale, the recorded CI execution map rows — including any `Merge to main` /
  `Nightly` stage rows an earlier run recorded (the do-not-pre-assert comment
  governs *first* renders; deleting a recorded stage silently reverts a recorded
  user decision), `## Suite health` (all rows — the history is the trend), and
  `## Coverage debt` (whole section; absent is a valid pre-certification state —
  never create it empty). New sections the template has and the file lacks are
  **added**, not swapped in via a re-render — inserted **after the last existing
  `## ` content section and before any trailing footer** (a closing `---` or
  end-of-file), in the template's own section order; never appended past the
  footer.
- **`## Certification` is the project fact.** The re-probe *informs* it: append a
  dated re-probe note; retain the prior dated rows and the `Launch conditions` row
  (that clean-audit receipt is what spares `verify-work` a re-audit). **Never
  silently downgrade the tier** — when today's probe supports a *lower* tier than
  recorded (this machine may simply not be the certifying machine — capability is
  a project fact, not a machine fact), present both and change the tier only with
  the user's explicit confirmation. Upgrades are offered, not forced.

Render `@~/.claude/gsd-core/templates/test-strategy.md` (fill `[DATE]`, `[PROJECT_TITLE]`, `[ADR-NNNN]`). Fill:
- the per-subdomain level table, the gnarly-bits list, what-not-to-test, and the integration note;
- the persistent/transient e2e split + the gate's maintenance mechanism;
- the **`## Certification`** section — tier + per-operation probe results + mechanism + brief source (rows from Step 5.5's probe, never from tool presence);
- the **`## Certification substrate`** — Step 5.7's four policies;
- coverage/mutation and the TDD stance (render `tdd_mode=false` as "off", `true` as "on");
- the **`## Suite health`** baseline row — Step 6.5's measurement, or `unmeasured`;
- the **CI execution map** — which tiers run at the PR gate, and which — if any — can't fit there and why; `/gsd:cicd-strategy` reads "Doesn't fit the PR gate" as its C1 input, so don't pre-assert extra stages here;
- certification goes on the template's own **"Not a pipeline tier"** line — never into "Doesn't fit the PR gate": it runs outside CI by design and must not be mapped to any CI stage.

Write to `.planning/TEST-STRATEGY.md`.

## Step 8: Commit

```bash
gsd_run project strategy-done testing-strategy 2>/dev/null || true  # flip the Strategy Plan row — the grounding gate keys on `done`
if [ "$COMMIT_DOCS" = "true" ]; then
  # The discussion log is part of the durable record — commit it with the docs it explains (empty when absent/disabled).
  DLOG=$([ -f .planning/PROJECT-DISCUSSION-LOG.md ] && echo ".planning/PROJECT-DISCUSSION-LOG.md")
  # A run that created TESTING-STANDARDS.md (reference defaults land there, never in Notes) commits it too.
  TSTD=$([ -f .planning/TESTING-STANDARDS.md ] && echo ".planning/TESTING-STANDARDS.md")
  gsd_run query commit "docs: add test strategy (shape follows architecture)" --files .planning/TEST-STRATEGY.md .planning/PROJECT.md $DLOG $TSTD
else
  echo "TEST-STRATEGY.md written but not committed (commit_docs is false)."
fi
```

## Step 9: Wrap up

Display:
```
TEST-STRATEGY.md written — test shape set to follow the architecture.

  Per-subdomain levels: [core → unit] · [crud → integration] ...
  Unit-test targets (gnarly bits): [N]
  Gate: [N] smoke flows (agent-healed | hand-maintained) · Certification: [CERT-N — mechanism]
  Substrate: [seed accounts · email catcher/mode · LLM policy · auth policy]
  Suite health baseline: [wall clock | unmeasured] — T1 checked now; T2–T4 compared by `transition` each phase (tune-up: /gsd:testing-strategy --tune-up)
  Coverage = floor; mutation on [critical modules]; TDD = behavior + small increments (test-first: ${TDD_MODE})

Next: /gsd:infrastructure-strategy   (where the system runs) → /gsd:cicd-strategy → /gsd:plan-phase. Skip infra + cicd straight to /gsd:plan-phase only for a local-only / no-deploy project. (plans + /gsd:add-tests will follow this strategy)
```

**Auto-advance (chain):** after this skill, follow `@~/.claude/gsd-core/workflows/strategy-chain/modes/advance.md` with `CURRENT=testing-strategy` — in `--auto` it dispatches the next `## Strategy Plan` step (honoring skips) onward to the build loop; interactive runs use the `Next:` pointer above.

**Roadmap reconciliation** (only when `.planning/ROADMAP.md` already exists — on a greenfield chain that reaches this step before a roadmap is written, skip silently: there is nothing to reconcile against)**:** ROADMAP.md predates this strategy. Scan it against the test shape — if a phase relies on test infrastructure or a test level this strategy invalidates or reshapes (e.g. a phase planning e2e for what's now demoted to integration, or one missing the test infra a gnarly-bit tier needs), SAY SO explicitly and offer `/gsd:phase --edit` (or a roadmap refresh — the roadmapper re-reads discovery artifacts). Never leave a known strategy↔roadmap contradiction unspoken.

</process>

<critical_rules>
- **Shape follows architecture.** Derive the level emphasis FROM the architecture rung per subdomain; never pick a pyramid/diamond/trophy as a target.
- **Behavior over implementation; sociable by default.** Test observable behavior through public APIs; mock ONLY at architectural boundaries (ports/external systems).
- **Test each behavior once, at the cheapest level.** No duplicate coverage across unit/integration/e2e.
- **Coverage is a floor, not a target.** Mutation testing proves assertion quality on critical modules.
- **TDD = behavior + small uniform increments + regression floor.** Test-first is a knob, not dogma; keep the RED step.
- **Scripts gate; agents author, heal, and certify.** The smoke gate stays deterministic CI (3–7 flows, <5 min); certification is real-conditions and never a CI gate. On CERT-0, human UAT satisfies it.
- **Probe capability, never merely find binaries.** The tier comes from per-operation probe results; third-party certifiers launch sandbox-first (isolated HOME + instrumentation audit — before the probe, before even `--version`).
- **Substrate before certification.** Seeded accounts (never real user data), transport-enforced email safety, spend-capped real LLM calls with shape-not-content assertions, honest auth (verified test mode or auth-once + persisted session).
- **Born fast, re-evaluated by trigger.** Container reuse local-only; baseline recorded (or `unmeasured`); T1 fires immediately, T2–T4 at milestone close.
- **Extend, don't replace** TESTING-STANDARDS.md. Respect `commit_docs` / `response_language`.
</critical_rules>

<success_criteria>
- ADR/SKELETON + DOMAIN-MODEL loaded (or their absence recorded and the PROJECT.md fallback used); shape derived FROM the architecture (not picked)
- Per-subdomain level emphasis recorded with the justifying rung
- Gnarly bits to unit-test identified; what-not-to-test stated; no duplicate coverage
- Persistent e2e smoke list set (with its maintenance mechanism); transient e2e distinguished
- Certification tier probed (per-operation results recorded, or CERT-0 stated honestly) + mechanism decided; substrate's four policies recorded
- Suite-health baseline recorded (measured and dated, or explicitly `unmeasured`); T1 evaluated immediately, T2–T4 compared at milestone close
- Coverage-as-floor + mutation targets + TDD stance recorded; TESTING-STANDARDS.md preserved
- TEST-STRATEGY.md written and committed (when commit_docs is true)
- User directed to /gsd:plan-phase
</success_criteria>
