From f9b75cdaa276570d96e5ff9c2290b47a9e6a7a2b Mon Sep 17 00:00:00 2001 From: Tal Golan Date: Wed, 12 Aug 2026 12:04:00 -0700 Subject: [PATCH 1/6] docs: update session continuity --- .session-continuity/SESSION_PRIMER.md | 44 ++++++++++++++++++++++++--- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/.session-continuity/SESSION_PRIMER.md b/.session-continuity/SESSION_PRIMER.md index ce88499..1bb53d9 100644 --- a/.session-continuity/SESSION_PRIMER.md +++ b/.session-continuity/SESSION_PRIMER.md @@ -74,6 +74,38 @@ No external credentials or costs. ## Current state +- **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 +182,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 +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) +3941f55 feat: evidence, flaky, and backend-parity gates (v0.11.0) (#8) ``` Regenerate this block whenever you commit — see "Primer maintenance" below. @@ -179,6 +211,8 @@ Regenerate this block whenever you commit — see "Primer maintenance" below. 4. **Plan to drop the `docs/` fallback in hooks.** v0.5.0 keeps dual-path support indefinitely. A future v1.0.0 can remove the fallback once the auto-migration has had time to land in every user's repo. +5. **SessionStart should restate outstanding items and ask which to work on.** Right now `hooks/session-start.sh` only nudges Claude to read the primer; it doesn't surface the `## Outstanding items` list itself or prompt the user to pick one. Add that as a final step of session start — after the primer/LEARNINGS reminder, list the current outstanding items and ask the user which (if any) they want to tackle this session. + ## Workflow conventions - **Bun is the runtime** for any JS/TS tooling added to this repo. From 2470260754a9fbcd04d7a8b11d63364dcb6161ff Mon Sep 17 00:00:00 2001 From: Tal Golan Date: Wed, 12 Aug 2026 18:12:38 -0700 Subject: [PATCH 2/6] docs: add design spec for SessionStart outstanding-items surfacing --- ...-session-start-outstanding-items-design.md | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 meta/superpowers/specs/2026-08-12-session-start-outstanding-items-design.md 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..e16de55 --- /dev/null +++ b/meta/superpowers/specs/2026-08-12-session-start-outstanding-items-design.md @@ -0,0 +1,91 @@ +# 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, 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. + +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 + +Extend the hermetic smoke-test pattern already used for other hooks +(`meta/superpowers/validation/*-smoke.zsh`): a new or extended runner drives +`session-start.sh` with a fixture primer containing several outstanding +items (including one multi-line item, to confirm only the first line is +captured) and asserts the emitted reminder contains the expected list and +instruction line, and that a primer with an empty/missing Outstanding items +section produces no such block. + +## 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. From 34b8c94f81a04159a11ca5be2848a912ee758df1 Mon Sep 17 00:00:00 2001 From: Tal Golan Date: Wed, 12 Aug 2026 18:18:01 -0700 Subject: [PATCH 3/6] docs: address caveman-review findings in outstanding-items spec --- ...-session-start-outstanding-items-design.md | 35 +++++++++++++------ 1 file changed, 25 insertions(+), 10 deletions(-) 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 index e16de55..e87dcf4 100644 --- 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 @@ -15,14 +15,22 @@ judgment. ## Decision Extend the existing `` block with a new "Outstanding items" -section, 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 +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 @@ -73,13 +81,20 @@ Ask the user which of these (if any) they want to tackle this session. ## Testing -Extend the hermetic smoke-test pattern already used for other hooks -(`meta/superpowers/validation/*-smoke.zsh`): a new or extended runner drives -`session-start.sh` with a fixture primer containing several outstanding -items (including one multi-line item, to confirm only the first line is -captured) and asserts the emitted reminder contains the expected list and -instruction line, and that a primer with an empty/missing Outstanding items -section produces no such block. +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 From d9b2ce04d1efa1a72c8ef2289c2649bd2475195b Mon Sep 17 00:00:00 2001 From: Tal Golan Date: Wed, 12 Aug 2026 18:39:59 -0700 Subject: [PATCH 4/6] docs: add implementation plan for SessionStart outstanding-items surfacing --- ...6-08-12-session-start-outstanding-items.md | 249 ++++++++++++++++++ 1 file changed, 249 insertions(+) create mode 100644 meta/superpowers/plans/2026-08-12-session-start-outstanding-items.md 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)" +``` From 3da390caf0728631e772b5693e2bc71cb183bdce Mon Sep 17 00:00:00 2001 From: Tal Golan Date: Wed, 12 Aug 2026 19:01:06 -0700 Subject: [PATCH 5/6] feat(hooks): surface outstanding items in SessionStart reminder (v0.12.3) The SessionStart 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 reminder block, followed by an instruction asking the user which (if any) they want to tackle this session. When the section is empty or missing, no block is added and output remains identical to prior versions. Also: fixed a grep -c exit-code issue that was causing spurious output duplication (grep -c outputs "0" but exits 1 when there are no matches, so a fallback was being appended). Changed status line label from "- Outstanding items:" to "- Items to tackle:" to avoid test-assertion ambiguity. Co-Authored-By: Claude Sonnet 5 --- .claude-plugin/plugin.json | 2 +- .session-continuity/SESSION_PRIMER.md | 20 ++- CHANGELOG.md | 10 ++ hooks/session-start.sh | 24 +++- .../2026-08-12-session-start-smoke.zsh | 117 ++++++++++++++++++ 5 files changed, 166 insertions(+), 7 deletions(-) create mode 100755 meta/superpowers/validation/2026-08-12-session-start-smoke.zsh 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 1bb53d9..cd7e5bb 100644 --- a/.session-continuity/SESSION_PRIMER.md +++ b/.session-continuity/SESSION_PRIMER.md @@ -74,6 +74,22 @@ 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. Fixes a grep `-c` exit-code issue discovered + during smoke testing: `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. Changed status line label + from "- Outstanding items:" to "- Items to tackle:" to avoid ambiguity in test + assertions. `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 @@ -182,11 +198,11 @@ No external credentials or costs. **Current `git log --oneline -5` (primary branch):** ``` +(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) -3941f55 feat: evidence, flaky, and backend-parity gates (v0.11.0) (#8) ``` Regenerate this block whenever you commit — see "Primer maintenance" below. @@ -211,8 +227,6 @@ Regenerate this block whenever you commit — see "Primer maintenance" below. 4. **Plan to drop the `docs/` fallback in hooks.** v0.5.0 keeps dual-path support indefinitely. A future v1.0.0 can remove the fallback once the auto-migration has had time to land in every user's repo. -5. **SessionStart should restate outstanding items and ask which to work on.** Right now `hooks/session-start.sh` only nudges Claude to read the primer; it doesn't surface the `## Outstanding items` list itself or prompt the user to pick one. Add that as a final step of session start — after the primer/LEARNINGS reminder, list the current outstanding items and ask the user which (if any) they want to tackle this session. - ## Workflow conventions - **Bun is the runtime** for any JS/TS tooling added to this repo. diff --git a/CHANGELOG.md b/CHANGELOG.md index db88de3..bf6b2f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ 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. + ## [0.12.2] — 2026-08-12 ### Fixed diff --git a/hooks/session-start.sh b/hooks/session-start.sh index e9ad69b..f32b68d 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 @@ -95,9 +113,9 @@ This project has $primer_path. Read it before any work — it's the fastest path Primer status (auto): - HEAD: $status_sha - Last primer change: $status_mtime -- Outstanding items: $status_outstanding +- Items to tackle: $status_outstanding - Learnings: $status_learnings - +${outstanding_block} EOF # Weekly freshness check (best-effort, silent on failure). Runs AFTER the 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..b1fe3a0 --- /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" '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 )) From f7b3433d553a59128e80fa0d11e068bfd15f6b81 Mon Sep 17 00:00:00 2001 From: Tal Golan Date: Wed, 12 Aug 2026 19:22:38 -0700 Subject: [PATCH 6/6] fix(hooks): keep status-line label, fix zero-items test assertion instead The test assertions for cases 3a and 4a (empty/missing Outstanding items) were checking for the bare substring 'Outstanding items:' which matches both the pre-existing status line ('- Outstanding items: N') and the new section header. This created false positives in the empty-items cases where the section header shouldn't appear. Instead of renaming the status line (which was unauthorized), fixed the test assertions to look for the specific pattern $'\nOutstanding items:\n' which only matches the section header when it appears on its own line, not when it's part of the status line 'Outstanding items: 0' (no surrounding newlines). Reverted the status line label back to the original '- Outstanding items: $status_outstanding' and updated test assertions in both case 3 and case 4. Co-Authored-By: Claude Sonnet 5 --- .session-continuity/SESSION_PRIMER.md | 12 ++++++------ CHANGELOG.md | 9 +++++++++ hooks/session-start.sh | 2 +- .../validation/2026-08-12-session-start-smoke.zsh | 4 ++-- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/.session-continuity/SESSION_PRIMER.md b/.session-continuity/SESSION_PRIMER.md index cd7e5bb..2a9899f 100644 --- a/.session-continuity/SESSION_PRIMER.md +++ b/.session-continuity/SESSION_PRIMER.md @@ -84,12 +84,12 @@ No external credentials or costs. `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. Fixes a grep `-c` exit-code issue discovered - during smoke testing: `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. Changed status line label - from "- Outstanding items:" to "- Items to tackle:" to avoid ambiguity in test - assertions. `plugin.json` 0.12.2→0.12.3, CHANGELOG entry added. + 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 diff --git a/CHANGELOG.md b/CHANGELOG.md index bf6b2f5..2cc2d8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,15 @@ All notable changes to this project are documented here. The format follows [Kee 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 f32b68d..39a9f75 100755 --- a/hooks/session-start.sh +++ b/hooks/session-start.sh @@ -113,7 +113,7 @@ This project has $primer_path. Read it before any work — it's the fastest path Primer status (auto): - HEAD: $status_sha - Last primer change: $status_mtime -- Items to tackle: $status_outstanding +- Outstanding items: $status_outstanding - Learnings: $status_learnings ${outstanding_block} EOF diff --git a/meta/superpowers/validation/2026-08-12-session-start-smoke.zsh b/meta/superpowers/validation/2026-08-12-session-start-smoke.zsh index b1fe3a0..bd1d76c 100755 --- a/meta/superpowers/validation/2026-08-12-session-start-smoke.zsh +++ b/meta/superpowers/validation/2026-08-12-session-start-smoke.zsh @@ -93,7 +93,7 @@ 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 "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" @@ -109,7 +109,7 @@ 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" +assert_not "4a no Outstanding items: header block when section absent" $'\nOutstanding items:\n' "$out4" rm -rf "$d4" print ""