diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json
index e8bb6e3..1ba9cd0 100644
--- a/.claude-plugin/plugin.json
+++ b/.claude-plugin/plugin.json
@@ -1,6 +1,6 @@
{
"name": "session-continuity",
- "version": "0.12.2",
+ "version": "0.12.3",
"description": "Cross-session memory for Claude Code projects via two in-repo docs: SESSION_PRIMER.md (current state) and LEARNINGS.md (hard-won bugs).",
"author": {
"name": "Tal Golan"
diff --git a/.session-continuity/SESSION_PRIMER.md b/.session-continuity/SESSION_PRIMER.md
index ce88499..2a9899f 100644
--- a/.session-continuity/SESSION_PRIMER.md
+++ b/.session-continuity/SESSION_PRIMER.md
@@ -74,6 +74,54 @@ No external credentials or costs.
## Current state
+- **v0.12.3 shipped** (branch `session-start-outstanding-items`, commit pushed,
+ tag `v0.12.3` pending). SessionStart hook now surfaces outstanding items from
+ the primer: extracts the "Outstanding items" section, lists the first line of
+ each numbered item (sub-bullets dropped), and injects into the SessionStart
+ reminder with an instruction asking which to tackle. When the section is empty
+ or missing, no block is added — the output remains identical to prior versions.
+ New hermetic smoke runner
+ `meta/superpowers/validation/2026-08-12-session-start-smoke.zsh` validates 11
+ test cases covering both paths (`.session-continuity/` and legacy `docs/`),
+ multi-line items, empty sections, and missing sections; all pass. Shellcheck
+ clean on the modified hook script. Also fixes a pre-existing `grep -c` exit-code
+ bug: `grep -c` exits 1 when no matches are found (even though it outputs "0"),
+ so a fallback `|| echo '0'` would output both the grep "0" and the fallback "0",
+ causing spurious output duplication in the status line. Fixed by using `|| true`
+ as the fallback and explicit empty-case handling. `plugin.json` 0.12.2→0.12.3,
+ CHANGELOG entry added.
+- **v0.12.2 shipped** (branch `fix/hook-json-escaping`, PR #11, tag `v0.12.2`
+ pushed, GitHub Release published, live plugin install refreshed and
+ verified). Fixes `proven-gate.sh` and `smoke-gate.sh` emitting malformed
+ JSON on deny: a reason string containing a literal `"` broke their
+ hand-built `printf` JSON, so the block worked but the reason never
+ parsed — undiagnosable rather than unsafe. Root cause: every existing
+ per-gate runner asserted with a substring match on `deny`, which passes on
+ malformed JSON too, so this shipped green. Fix, in order:
+ 1. New hermetic runner
+ `meta/superpowers/validation/2026-08-12-hook-json-contract-smoke.zsh`
+ parses each gate's deny output with a real JSON parser (`python3 -m
+ json`) instead of substring-matching it, and fails if any
+ `hooks/*-gate.sh` has no fixture — so a future gate can't skip the
+ check. Red on landing (4 failures: `proven-gate` + 3 `smoke-gate`
+ cases), 16/16 green after the fix.
+ 2. All six gates' `deny()` (not just the two broken ones) now route the
+ reason through a `json_escape()` helper: backslash-then-quote
+ escaping, full C0 control-byte range (`0x00`-`0x1F`, not just
+ `\n\t\r`) folded to a space. `smoke-gate.sh`'s now-redundant
+ `offender_esc` pre-escape removed.
+ 3. `.session-continuity/LEARNINGS.md` entry #1 gained a "Second trap"
+ addendum: a well-formed JSON *shape* isn't the same as parseable
+ JSON, and a substring assert can't tell the difference.
+ 4. `plugin.json` 0.12.1→0.12.2, CHANGELOG entry added.
+ Full validation suite 8 runners / 101 checks green; shellcheck clean on
+ all six gates. Built via `superpowers:subagent-driven-development`
+ (fresh implementer + reviewer per task, final whole-branch review on the
+ most capable model — 1 Minor finding, non-blocking: the new runner's
+ coverage check verifies gate-name list membership, not fixture
+ existence, so CHANGELOG/LEARNINGS phrasing slightly overstates what's
+ machine-enforced). Plan:
+ `meta/superpowers/plans/2026-08-12-hook-json-escaping-fix.md`.
- **v0.12.1 shipped** (branch `fix/smoke-gate-false-positive`). Fixes a
line-level false positive in `hooks/smoke-gate.sh`: the weak-smoke branch
denied any line where `smoke` co-occurred with a weak-word
@@ -150,11 +198,11 @@ No external credentials or costs.
**Current `git log --oneline -5` (primary branch):**
```
-986b22a feat(end-session): report outstanding-items verdicts in Step 3 checklist
-dd59d4a feat(end-session): verify outstanding items against code (Step 1)
-a822087 docs(plan): address review of outstanding-items verification plan
-eb4e7ba docs(plan): outstanding-items verification implementation plan
-1e8944e docs(spec): address review of outstanding-items verification
+(commit to be regenerated after merge)
+b75af34 feat(learnings): backfill Trigger lines, require them going forward
+8f05d0e fix(hooks): JSON-escape deny reasons (v0.12.2) (#11)
+4eb8d5c fix(hooks): scope smoke-gate weak-word to adjacency + honor MANDATORY (v0.12.1) (#10)
+5e3426c feat: outstanding-items code verification in end-session (v0.12.0) (#9)
```
Regenerate this block whenever you commit — see "Primer maintenance" below.
diff --git a/CHANGELOG.md b/CHANGELOG.md
index db88de3..2cc2d8a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,25 @@
All notable changes to this project are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/); this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
+## [0.12.3] — 2026-08-12
+
+### Added
+- **SessionStart hook surfaces outstanding items.** The `hooks/session-start.sh`
+ hook now extracts top-level numbered items from the primer's "Outstanding
+ items" section (first line only; sub-bullets dropped) and injects them into
+ the SessionStart `` block, followed by an instruction asking
+ the user which (if any) they want to tackle. When the section is empty or
+ missing, no block is added and the reminder remains identical to prior output.
+
+### Fixed
+- **Spurious output duplication from `grep -c` exit code.** The `status_learnings`
+ computation used `grep -cE ... || echo '0'` to count LEARNINGS entries. When
+ `grep` finds no matches, it outputs "0" but exits with code 1 (not 0), causing
+ the fallback `echo '0'` to also run. The command substitution captured both
+ outputs, resulting in "0\n0" being assigned to the variable and later printed
+ to the reminder. Fixed by using `|| true` as the fallback and adding explicit
+ empty-case handling with `${status_learnings:-0}`.
+
## [0.12.2] — 2026-08-12
### Fixed
diff --git a/hooks/session-start.sh b/hooks/session-start.sh
index e9ad69b..39a9f75 100755
--- a/hooks/session-start.sh
+++ b/hooks/session-start.sh
@@ -83,7 +83,25 @@ status_outstanding="$(awk '
inside && /^[0-9]+\. / { count++ }
END { print count+0 }
' "$cwd/$primer_path" 2>/dev/null || echo '?')"
-status_learnings="$(grep -cE '^### [0-9]+\.' "$cwd/$learnings_path" 2>/dev/null || echo '0')"
+status_learnings="$(grep -cE '^### [0-9]+\.' "$cwd/$learnings_path" 2>/dev/null || true)"
+status_learnings="${status_learnings:-0}"
+
+# Outstanding items: extract the first line only of each top-level numbered
+# item (sub-bullets and continuation lines are intentionally dropped — see
+# spec's Decision section for why no truncation heuristics are added).
+# Empty when the section is missing or has no numbered items, which keeps
+# the reminder identical to today's output in that case.
+outstanding_items="$(awk '
+ /^## Outstanding items/ { inside=1; next }
+ inside && /^## / { exit }
+ inside && /^[0-9]+\. / { print }
+' "$cwd/$primer_path" 2>/dev/null || true)"
+
+if [ -n "$outstanding_items" ]; then
+ outstanding_block=$'\nOutstanding items:\n'"$outstanding_items"$'\n\nAsk the user which of these (if any) they want to tackle this session.\n'
+else
+ outstanding_block=""
+fi
# Inject the reminder into Claude's SessionStart context. ``
# is the convention Claude Code uses for system-injected context that is
@@ -97,7 +115,7 @@ Primer status (auto):
- Last primer change: $status_mtime
- Outstanding items: $status_outstanding
- Learnings: $status_learnings
-
+${outstanding_block}
EOF
# Weekly freshness check (best-effort, silent on failure). Runs AFTER the
diff --git a/meta/superpowers/plans/2026-08-12-session-start-outstanding-items.md b/meta/superpowers/plans/2026-08-12-session-start-outstanding-items.md
new file mode 100644
index 0000000..5e1559c
--- /dev/null
+++ b/meta/superpowers/plans/2026-08-12-session-start-outstanding-items.md
@@ -0,0 +1,249 @@
+# SessionStart Outstanding-Items Surfacing Implementation Plan
+
+> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
+
+**Goal:** `hooks/session-start.sh` lists the primer's outstanding items (first line each) in its `` and instructs Claude to ask the user which one to tackle this session.
+
+**Architecture:** One new awk pass in the existing bash script extracts the first line of each top-level numbered item under `## Outstanding items`, builds a pre-formatted text block (empty string if no items), and splices it into the existing heredoc immediately before the closing `` tag — no items means no block, no extra blank line, identical output to today.
+
+**Tech Stack:** bash (`set -euo pipefail`), awk, zsh (test runner, matching existing `meta/superpowers/validation/*-smoke.zsh` convention), shellcheck.
+
+## Global Constraints
+
+- No new files besides the test runner — same plain-stdout contract, no JSON, no `hooks.json` change (per spec `## Implementation notes`).
+- First line only per item — no multi-line/sub-bullet capture (per spec `## Decision`).
+- Silent omission when zero outstanding items — no "Outstanding items: 0" noise, and no stray blank line in that case (per spec `## Decision` + review finding rolled into `## Testing`).
+- Both `.session-continuity/` and legacy `docs/` primer paths must get the feature; Task 1's smoke test covers both paths (per spec `## Implementation notes` + `## Testing`).
+- No trimming/reformatting of items whose first line ends mid-sentence (e.g. trailing colon) — accepted tradeoff, do not add heuristics (per spec `## Decision`).
+- Spec: `meta/superpowers/specs/2026-08-12-session-start-outstanding-items-design.md`.
+
+---
+
+### Task 1: Extract and surface outstanding items in `hooks/session-start.sh`
+
+**Files:**
+- Modify: `hooks/session-start.sh` (insert new awk pass + `outstanding_block` construction after the existing status computations at L86, splice into the heredoc at L91-100)
+- Create: `meta/superpowers/validation/2026-08-12-session-start-smoke.zsh`
+- Modify: `.claude-plugin/plugin.json`, `CHANGELOG.md` (version bump — see Step 6)
+
+**Interfaces:**
+- Consumes: nothing new from other tasks — this is the only task.
+- Produces: nothing consumed by later tasks — this is the only task.
+
+- [ ] **Step 1: Write the failing smoke test**
+
+Create `meta/superpowers/validation/2026-08-12-session-start-smoke.zsh`:
+
+```zsh
+#!/usr/bin/env zsh
+# Smoke runner for hooks/session-start.sh's outstanding-items surfacing.
+# Hermetic: builds a scratch fixture repo per case, feeds a synthetic
+# SessionStart payload on stdin, asserts on stdout. No live session, no
+# network (SESSION_CONTINUITY_SKIP_UPDATE_CHECK=1 short-circuits
+# version-check.sh's GitHub call).
+set -uo pipefail
+
+here="${0:A:h}"
+repo="${here:h:h:h}" # validation -> superpowers -> meta -> repo root
+hook="$repo/hooks/session-start.sh"
+
+export SESSION_CONTINUITY_SKIP_UPDATE_CHECK=1
+
+pass=0; fail=0
+ok() { print -P "%F{green}✓%f $1"; (( pass++ )); return 0; }
+bad() { print -P "%F{red}✗%f $1"; (( fail++ )); return 0; }
+
+# assert
+assert() {
+ local desc="$1" exp="$2" act="$3"
+ if [[ "$exp" == "EMPTY" ]]; then
+ [[ -z "$act" ]] && ok "$desc" || bad "$desc (expected empty, got: $act)"
+ else
+ [[ "$act" == *"$exp"* ]] && ok "$desc" || bad "$desc (expected '*$exp*', got: $act)"
+ fi
+}
+
+# assert_not
+assert_not() {
+ local desc="$1" forbidden="$2" act="$3"
+ [[ "$act" != *"$forbidden"* ]] && ok "$desc" || bad "$desc (found forbidden '*$forbidden*')"
+}
+
+# payload -> a SessionStart JSON payload naming that cwd
+payload() { printf '{"cwd":"%s"}' "$1"; }
+
+# --- Case set 1: canonical .session-continuity/ path, multi-line item ---
+d1="$(mktemp -d)"
+mkdir -p "$d1/.session-continuity"
+cat > "$d1/.session-continuity/SESSION_PRIMER.md" <<'PRIMER'
+# Session Primer
+
+## Outstanding items
+
+1. First item, single line.
+2. Second item header text: (rejected — details below)
+ - sub-bullet A
+ - sub-bullet B
+3. Third item, single line.
+
+## Workflow conventions
+PRIMER
+touch "$d1/.session-continuity/LEARNINGS.md"
+
+out1="$(payload "$d1" | bash "$hook")"
+assert "1a lists item 1 first line" '1. First item, single line.' "$out1"
+assert "1b lists item 2 first line only" '2. Second item header text: (rejected — details below)' "$out1"
+assert_not "1c drops item 2 sub-bullets" 'sub-bullet A' "$out1"
+assert "1d lists item 3 first line" '3. Third item, single line.' "$out1"
+assert "1e includes ask-the-user instruction" 'Ask the user which of these' "$out1"
+rm -rf "$d1"
+
+# --- Case set 2: legacy docs/ path gets the same treatment ---
+d2="$(mktemp -d)"
+mkdir -p "$d2/docs"
+cat > "$d2/docs/SESSION_PRIMER.md" <<'PRIMER'
+# Session Primer
+
+## Outstanding items
+
+1. Only item on the legacy path.
+
+## Workflow conventions
+PRIMER
+touch "$d2/docs/LEARNINGS.md"
+
+out2="$(payload "$d2" | bash "$hook")"
+assert "2a legacy docs/ path also lists items" '1. Only item on the legacy path.' "$out2"
+assert "2b legacy docs/ path also gets instruction" 'Ask the user which of these' "$out2"
+rm -rf "$d2"
+
+# --- Case set 3: empty Outstanding items section -> no block, no noise ---
+d3="$(mktemp -d)"
+mkdir -p "$d3/.session-continuity"
+cat > "$d3/.session-continuity/SESSION_PRIMER.md" <<'PRIMER'
+# Session Primer
+
+## Outstanding items
+
+## Workflow conventions
+PRIMER
+touch "$d3/.session-continuity/LEARNINGS.md"
+
+out3="$(payload "$d3" | bash "$hook")"
+assert_not "3a no Outstanding items: header block" 'Outstanding items:' "$out3"
+assert_not "3b no ask-the-user instruction" 'Ask the user which of these' "$out3"
+assert "3c closing tag immediately follows Learnings line (no stray blank line)" $'- Learnings: 0\n' "$out3"
+rm -rf "$d3"
+
+# --- Case set 4: missing Outstanding items section entirely -> no block ---
+d4="$(mktemp -d)"
+mkdir -p "$d4/.session-continuity"
+cat > "$d4/.session-continuity/SESSION_PRIMER.md" <<'PRIMER'
+# Session Primer
+
+## Workflow conventions
+PRIMER
+touch "$d4/.session-continuity/LEARNINGS.md"
+
+out4="$(payload "$d4" | bash "$hook")"
+assert_not "4a no Outstanding items: header block when section absent" 'Outstanding items:' "$out4"
+rm -rf "$d4"
+
+print ""
+print -P "Result: %F{green}$pass passed%f, %F{red}$fail failed%f"
+(( fail == 0 ))
+```
+
+Make it executable:
+
+```bash
+chmod +x meta/superpowers/validation/2026-08-12-session-start-smoke.zsh
+```
+
+- [ ] **Step 2: Run the test to check it fails as expected**
+
+Run: `zsh meta/superpowers/validation/2026-08-12-session-start-smoke.zsh`
+Expected: cases 1a, 1b, 1d, 1e, 2a, 2b FAIL (current script has no `Outstanding items:` section or instruction line at all). Cases 1c, 3a, 3b, 3c, 4a currently pass by accident (nothing to find/nothing to break yet) — Step 4 re-checks all of them together once the implementation lands.
+
+- [ ] **Step 3: Implement the awk pass and heredoc splice**
+
+Open `hooks/session-start.sh`. Locate the existing status computation block (currently lines 77-86, ending at `status_learnings=...`) and insert a new block immediately after it, before the `# Inject the reminder...` comment:
+
+```bash
+# Outstanding items: extract the first line only of each top-level numbered
+# item (sub-bullets and continuation lines are intentionally dropped — see
+# spec's Decision section for why no truncation heuristics are added).
+# Empty when the section is missing or has no numbered items, which keeps
+# the reminder identical to today's output in that case.
+outstanding_items="$(awk '
+ /^## Outstanding items/ { inside=1; next }
+ inside && /^## / { exit }
+ inside && /^[0-9]+\. / { print }
+' "$cwd/$primer_path" 2>/dev/null || true)"
+
+if [ -n "$outstanding_items" ]; then
+ outstanding_block=$'\nOutstanding items:\n'"$outstanding_items"$'\n\nAsk the user which of these (if any) they want to tackle this session.\n'
+else
+ outstanding_block=""
+fi
+```
+
+Then change the existing heredoc (currently ending):
+
+```bash
+cat <
+This project has $primer_path. Read it before any work — it's the fastest path to context. Also check $learnings_path if anything surprises you.
+
+Primer status (auto):
+- HEAD: $status_sha
+- Last primer change: $status_mtime
+- Outstanding items: $status_outstanding
+- Learnings: $status_learnings
+
+EOF
+```
+
+to:
+
+```bash
+cat <
+This project has $primer_path. Read it before any work — it's the fastest path to context. Also check $learnings_path if anything surprises you.
+
+Primer status (auto):
+- HEAD: $status_sha
+- Last primer change: $status_mtime
+- Outstanding items: $status_outstanding
+- Learnings: $status_learnings
+${outstanding_block}
+EOF
+```
+
+(Note: `${outstanding_block}` sits directly adjacent to `` in the template, with no line break between them. When `outstanding_block` is empty this collapses to exactly today's output. When non-empty, the block's own leading/trailing `\n` supply the blank-line spacing and the newline before the closing tag.)
+
+- [ ] **Step 4: Run the test and confirm all cases pass**
+
+Run: `zsh meta/superpowers/validation/2026-08-12-session-start-smoke.zsh`
+Expected: `Result: 12 passed, 0 failed` (all of 1a-1e, 2a-2b, 3a-3c, 4a).
+
+- [ ] **Step 5: shellcheck**
+
+Run: `shellcheck hooks/session-start.sh`
+Expected: no warnings. If shellcheck flags the `$'...'` ANSI-C quoting or the `${outstanding_block}` splice, fix the reported line directly — do not add a `# shellcheck disable` without first trying a straightforward fix.
+
+- [ ] **Step 6: Bump version and update the primer**
+
+Every prior hook-script change (v0.9.0 through v0.12.2) bumped `plugin.json` + added a `CHANGELOG.md` entry, including behavior-only tweaks like v0.12.1's smoke-gate scoping fix — no ambiguity here, this change follows the same precedent:
+
+1. In `.claude-plugin/plugin.json`, bump `"version"` from `"0.12.2"` to `"0.12.3"`.
+2. In `CHANGELOG.md`, add a `[0.12.3]` entry describing the SessionStart outstanding-items surfacing (mirror the format of the existing `[0.12.2]`/`[0.12.1]` entries).
+3. In `.session-continuity/SESSION_PRIMER.md`'s `## Outstanding items` section, remove item #5 ("SessionStart should restate outstanding items and ask which to work on") — it's done, and this repo's "Primer maintenance" convention requires removing finished items in the same commit as the change that finished them.
+4. Regenerate the `git log --oneline -5` block in the primer's `## Current state` section, and add a new `## Current state` bullet describing what shipped (mirror the style of the existing v0.12.x bullets), naming the `0.12.3` version bump.
+
+- [ ] **Step 7: Commit**
+
+```bash
+git add hooks/session-start.sh meta/superpowers/validation/2026-08-12-session-start-smoke.zsh .claude-plugin/plugin.json CHANGELOG.md .session-continuity/SESSION_PRIMER.md
+git commit -m "feat(hooks): surface outstanding items in SessionStart reminder (v0.12.3)"
+```
diff --git a/meta/superpowers/specs/2026-08-12-session-start-outstanding-items-design.md b/meta/superpowers/specs/2026-08-12-session-start-outstanding-items-design.md
new file mode 100644
index 0000000..e87dcf4
--- /dev/null
+++ b/meta/superpowers/specs/2026-08-12-session-start-outstanding-items-design.md
@@ -0,0 +1,106 @@
+# Design — SessionStart surfaces outstanding items
+
+Date: 2026-08-12
+Status: approved (brainstorming), pending implementation plan
+
+## Problem
+
+`hooks/session-start.sh` already computes an `Outstanding items: N` count in
+its 4-line status block, but never shows *what* those items are, and never
+prompts the user to pick one. Outstanding item #5 in the primer names this
+gap explicitly: SessionStart should restate the list and ask which (if any)
+to work on this session, instead of leaving that entirely to the model's
+judgment.
+
+## Decision
+
+Extend the existing `` block with a new "Outstanding items"
+section, appended *after* the existing 4-line status block, populated by a
+new awk pass that extracts the **first line only** of each top-level
+numbered item (`^[0-9]+\. `) inside `## Outstanding items` — same
+section-boundary logic already used for `status_outstanding`. Full
+multi-line detail (e.g. item 2's ten sub-bullets) is dropped; the first line
+alone is enough for the model to relay to the user and enough to keep the
+SessionStart context injection small. This mirrors the existing 4-line
+status block's philosophy: compact, best-effort, never crashes the hook.
+
+Accepted tradeoff: an item whose first line ends mid-sentence (e.g. item 2's
+"...deemed high-value):" — the colon dangles into the now-dropped sub-list)
+will render as a truncated-looking line. No trimming/reformatting logic is
+added for this — the model relaying the list to the user can paraphrase, and
+adding text-shape heuristics (detect trailing colon, etc.) is exactly the
+kind of speculative complexity this hook has avoided elsewhere.
+
+The new section is appended to the reminder text, followed by an explicit
+instruction line telling Claude to ask the user which item (if any) to
+tackle — closing the gap that item #5 identifies (nudge-to-read-primer
+today does not equal prompt-to-pick-work).
+
+If the primer has zero outstanding items (missing section, or section with
+no numbered lines), the new block is omitted entirely — no "Outstanding
+items: 0" noise, matching the silent-omit pattern already used elsewhere in
+the script (e.g. exit 0 on missing primer).
+
+## Output shape
+
+```
+
+This project has .session-continuity/SESSION_PRIMER.md. Read it before any work — it's the fastest path to context. Also check .session-continuity/LEARNINGS.md if anything surprises you.
+
+Primer status (auto):
+- HEAD: f9b75cd
+- Last primer change: 2026-08-12 11:59
+- Outstanding items: 5
+- Learnings: 7
+
+Outstanding items:
+1. Submit to the Anthropic marketplace.
+2. Deferred recommendations from `meta/superpowers/recommendations/improvements_20260521.md` (rejected or not-yet-prioritized — v0.5.1 + v0.6.0 shipped the items deemed high-value):
+3. Automated integration tests.
+4. Plan to drop the `docs/` fallback in hooks.
+5. SessionStart should restate outstanding items and ask which to work on.
+
+Ask the user which of these (if any) they want to tackle this session.
+
+```
+
+## Implementation notes
+
+- New awk block runs after the existing `status_outstanding` counter, same
+ section-boundary pattern (`/^## Outstanding items/ {inside=1}` /
+ `inside && /^## / {exit}` / `inside && /^[0-9]+\. /`), but prints the
+ matched line instead of counting it.
+- Guard: only emit the "Outstanding items:" sub-block (and its trailing
+ instruction line) if the extracted list is non-empty.
+- No change to the existing 4-line status block, no new files, no
+ `hooks.json` wiring change, no JSON — same plain-stdout contract.
+- `docs/` legacy-path branch gets the same treatment as `.session-continuity/`
+ (the script already branches on `primer_path`/`learnings_path` for both;
+ the new awk pass reads from whichever `primer_path` was resolved, so both
+ branches get the feature for free).
+
+## Testing
+
+No `session-start.sh` smoke runner exists yet (checked
+`meta/superpowers/validation/` — none named `session-start`). This is a
+**new** hermetic runner, not an extension, following the same pattern as
+`*-gate-smoke.zsh`: `meta/superpowers/validation/2026-08-12-session-start-smoke.zsh`.
+Cases:
+
+- Fixture primer at `.session-continuity/SESSION_PRIMER.md` with several
+ outstanding items, including one multi-line item — assert only the first
+ line is captured and the trailing instruction line is present.
+- Same fixture shape at the legacy `docs/SESSION_PRIMER.md` /
+ `docs/LEARNINGS.md` path — assert the feature works on that branch too,
+ not just asserted true in prose.
+- Primer with an empty/missing `## Outstanding items` section — assert no
+ "Outstanding items:" block and no instruction line appear.
+
+## Out of scope
+
+- Does not change `/session-continuity:end-session`'s existing
+ outstanding-items verification (v0.12.0) — that's a separate, unrelated
+ code path (Step 1 of end-session, not SessionStart).
+- Does not add interactivity to the hook itself — hooks only inject text;
+ the actual "ask the user" step happens in the model turn that follows,
+ same as today's primer-read nudge.
diff --git a/meta/superpowers/validation/2026-08-12-session-start-smoke.zsh b/meta/superpowers/validation/2026-08-12-session-start-smoke.zsh
new file mode 100755
index 0000000..bd1d76c
--- /dev/null
+++ b/meta/superpowers/validation/2026-08-12-session-start-smoke.zsh
@@ -0,0 +1,117 @@
+#!/usr/bin/env zsh
+# Smoke runner for hooks/session-start.sh's outstanding-items surfacing.
+# Hermetic: builds a scratch fixture repo per case, feeds a synthetic
+# SessionStart payload on stdin, asserts on stdout. No live session, no
+# network (SESSION_CONTINUITY_SKIP_UPDATE_CHECK=1 short-circuits
+# version-check.sh's GitHub call).
+set -uo pipefail
+
+here="${0:A:h}"
+repo="${here:h:h:h}" # validation -> superpowers -> meta -> repo root
+hook="$repo/hooks/session-start.sh"
+
+export SESSION_CONTINUITY_SKIP_UPDATE_CHECK=1
+
+pass=0; fail=0
+ok() { print -P "%F{green}✓%f $1"; (( pass++ )); return 0; }
+bad() { print -P "%F{red}✗%f $1"; (( fail++ )); return 0; }
+
+# assert
+assert() {
+ local desc="$1" exp="$2" act="$3"
+ if [[ "$exp" == "EMPTY" ]]; then
+ [[ -z "$act" ]] && ok "$desc" || bad "$desc (expected empty, got: $act)"
+ else
+ [[ "$act" == *"$exp"* ]] && ok "$desc" || bad "$desc (expected '*$exp*', got: $act)"
+ fi
+}
+
+# assert_not
+assert_not() {
+ local desc="$1" forbidden="$2" act="$3"
+ [[ "$act" != *"$forbidden"* ]] && ok "$desc" || bad "$desc (found forbidden '*$forbidden*')"
+}
+
+# payload -> a SessionStart JSON payload naming that cwd
+payload() { printf '{"cwd":"%s"}' "$1"; }
+
+# --- Case set 1: canonical .session-continuity/ path, multi-line item ---
+d1="$(mktemp -d)"
+mkdir -p "$d1/.session-continuity"
+cat > "$d1/.session-continuity/SESSION_PRIMER.md" <<'PRIMER'
+# Session Primer
+
+## Outstanding items
+
+1. First item, single line.
+2. Second item header text: (rejected — details below)
+ - sub-bullet A
+ - sub-bullet B
+3. Third item, single line.
+
+## Workflow conventions
+PRIMER
+touch "$d1/.session-continuity/LEARNINGS.md"
+
+out1="$(payload "$d1" | bash "$hook")"
+assert "1a lists item 1 first line" '1. First item, single line.' "$out1"
+assert "1b lists item 2 first line only" '2. Second item header text: (rejected — details below)' "$out1"
+assert_not "1c drops item 2 sub-bullets" 'sub-bullet A' "$out1"
+assert "1d lists item 3 first line" '3. Third item, single line.' "$out1"
+assert "1e includes ask-the-user instruction" 'Ask the user which of these' "$out1"
+rm -rf "$d1"
+
+# --- Case set 2: legacy docs/ path gets the same treatment ---
+d2="$(mktemp -d)"
+mkdir -p "$d2/docs"
+cat > "$d2/docs/SESSION_PRIMER.md" <<'PRIMER'
+# Session Primer
+
+## Outstanding items
+
+1. Only item on the legacy path.
+
+## Workflow conventions
+PRIMER
+touch "$d2/docs/LEARNINGS.md"
+
+out2="$(payload "$d2" | bash "$hook")"
+assert "2a legacy docs/ path also lists items" '1. Only item on the legacy path.' "$out2"
+assert "2b legacy docs/ path also gets instruction" 'Ask the user which of these' "$out2"
+rm -rf "$d2"
+
+# --- Case set 3: empty Outstanding items section -> no block, no noise ---
+d3="$(mktemp -d)"
+mkdir -p "$d3/.session-continuity"
+cat > "$d3/.session-continuity/SESSION_PRIMER.md" <<'PRIMER'
+# Session Primer
+
+## Outstanding items
+
+## Workflow conventions
+PRIMER
+touch "$d3/.session-continuity/LEARNINGS.md"
+
+out3="$(payload "$d3" | bash "$hook")"
+assert_not "3a no Outstanding items: header block" $'\nOutstanding items:\n' "$out3"
+assert_not "3b no ask-the-user instruction" 'Ask the user which of these' "$out3"
+assert "3c closing tag immediately follows Learnings line (no stray blank line)" $'- Learnings: 0\n' "$out3"
+rm -rf "$d3"
+
+# --- Case set 4: missing Outstanding items section entirely -> no block ---
+d4="$(mktemp -d)"
+mkdir -p "$d4/.session-continuity"
+cat > "$d4/.session-continuity/SESSION_PRIMER.md" <<'PRIMER'
+# Session Primer
+
+## Workflow conventions
+PRIMER
+touch "$d4/.session-continuity/LEARNINGS.md"
+
+out4="$(payload "$d4" | bash "$hook")"
+assert_not "4a no Outstanding items: header block when section absent" $'\nOutstanding items:\n' "$out4"
+rm -rf "$d4"
+
+print ""
+print -P "Result: %F{green}$pass passed%f, %F{red}$fail failed%f"
+(( fail == 0 ))