feat(reflect): add /retro — unconditional session reflection + once-per-session nudge - #49
Merged
Merged
Conversation
…er-session nudge
Detector-independent complement to the detector-gated /reflect. /retro reflects
on the session the agent just lived — grounded in concrete evidence first — then
emits 1-5 low-commitment one-liner proposals ("it was not obvious that …; would
have helped to know …") and accumulates them in a committable backlog
(docs/_reflections/log.md), where recurrence — not a single detector trip — is
the signal to act on. The detector becomes optional seasoning, not the trigger,
which matters pre-calibration when it misses most real friction.
A fail-open, once-per-session Stop hook nudges the agent to run /retro (or
acknowledge a clean session). Loop-guarded via stop_hook_active + a session-keyed
marker.
- .claude/commands/retro.md /retro command (grounded reflection)
- .claude/harnessgap-retro-hook.cjs fail-open once/session Stop-hook wrapper
(.cjs because the repo is ESM)
- test/retro-hook.test.ts 7 contract tests (block-once, loop-guard,
fail-open, session-keyed marker)
- docs/_reflections/log.md accumulating friction backlog (template)
Mirrors the fail-open discipline of harnessgap-stop-hook.js (src/init/claude.ts).
Wiring into init + portable registration tracked in #48; related ESM-target init
bug in #47.
Co-Authored-By: Claude <noreply@anthropic.com>
…currence Review findings from three subagent reviewers on PR #49. Correctness (merge-blocking): - Hook: marker-write failure now fails OPEN (was: block) — the prior path inverted the fail-open contract and would re-nudge every stop on a persistent tmpdir failure. Added test 9. - Tests now assert exit 0 on every path via run() (was: only 4 of 7). Thesis (merge-blocking): - Unify the proposal tag to a single "(→ <kind>)" suffix across the command, backlog format, and entry examples (was: three divergent conventions that would corrupt the backlog and break recurrence matching). - Recurrence is now concrete: fixed <area> vocabulary + a grep step + cap at 3 proposals (was: hand-wavy "skim" with unbounded tags and a 1-5 cap). Other: - Hook: best-effort 30-day marker pruning; REASON no longer contradicts the prompt's clean-session logging; "not yet wired into init" note. - Command: bad->good example, $ARGUMENTS empty-markdown fix, "don't edit config" guard moved before the file-path instruction, empty-session guard, /reflect dedup note, minimal-stub bootstrap. - Tests: default-path coverage, non-object JSON, strict-equality on stop_hook_active, marker-prune test. 7 -> 11 tests, all passing. Deferred to #48 (flagged in a comment there): two-Stop-hook ordering hazard, marker TTL for long/resumed sessions, README privacy sentence, docs/_reflections exclusion from --diagnose doc-grounding, frontmatter convention, and a possible harnessgap subcommand to compute backlog recurrence. Co-Authored-By: Claude <noreply@anthropic.com>
The shared afterEach did rmSync(DEFAULT_DIR), which erased OTHER live sessions' markers — including the one for the session running the tests, which spuriously re-triggered the Stop-hook nudge mid-session (caught by dogfooding: the hook re-fired right after a test run). Test 10 (the only test using the real default path) now cleans up only its own marker via try/finally. Verified empirically: the live session's marker survives a test run. Co-Authored-By: Claude <noreply@anthropic.com>
…t standalone The command text no longer mentions or references harnessgap: dropped the "/reflect" contrast and "harnessgap's detector" framing, the harnessgap scan/ reflect / ReflectFrame seasoning clause (genericized), the repo-specific test/init.test.ts example paths, and the "/reflect vs /retro split" bootstrap note. The command now describes itself: unconditional, grounded, local-only reflection that appends to docs/_reflections/log.md. The wrapper hook and the backlog doc still reference harnessgap (they're the integration surface, tracked in #48) — this commit only decouples the command itself. Co-Authored-By: Claude <noreply@anthropic.com>
…standalone Completes the standalone-ization of /retro alongside the prior command decoupling (21f094a). Hook (.claude/harnessgap-retro-hook.cjs): - Comments no longer reference harnessgap init / issue #48 / the sibling harnessgap-stop-hook.js. Activation note is now generic ("register it in settings.json as a Stop hook"). - Renamed the marker-dir env override HARNESSGAP_RETRO_DIR -> RETRO_MARKERS_DIR and the default dir os.tmpdir()/harnessgap-retro -> retro-markers, so the decoupled comments stay truthful (and the test was updated to match). Backlog (docs/_reflections/log.md): - Removed the "How this relates to the rest of harnessgap" section (it referenced /reflect and docs/_proposals). The file is now a standalone template. - Retired the one local entry — its friction was already promoted to the test isolation fix (b4fefc1), per the loop's own retire step. 11/11 tests pass. The live session marker was migrated to the new dir name so the rename doesn't cause a spurious re-nudge. NOTE: the hook *filename* (harnessgap-retro-hook.cjs) and the local settings.json command still contain "harnessgap". Renaming the file touches the registration path, so it's left for an explicit decision rather than rolled in here. Co-Authored-By: Claude <noreply@anthropic.com>
…ness)
Preserves harnessgap's multi-harness rule: /retro is now installed by
`harnessgap init claude|qwen|gigacode` alongside /reflect, instead of being
shipped as a Claude-only committed artifact.
- src/init/claude.ts: add exported buildRetroWrapperSource() (String.raw — the
wrapper source has a regex + \n escapes) and buildRetroCommandSource({memoryFile})
(parameterized CLAUDE.md/QWEN.md/GIGACODE.md; genericized tool names; no
frontmatter / $ARGUMENTS so it's portable across harnesses). initClaude writes
5 artifacts (reflect + retro wrappers, their commands, settings.json) and
installs BOTH Stop hooks — reflect first, retro second. mergeStopHook dedups by
exact command string, so the two distinct wrappers coexist idempotently.
- src/init/qwen.ts: installStopHook mirrors the same for qwen + giga (artifacts
3 -> 5), using the threaded memoryFile (QWEN.md / GIGACODE.md).
- src/adapter/index.ts: claudeInstallHook maps the 5 artifacts into InitResult.
- Removed committed .claude/commands/retro.md + .claude/harnessgap-retro-hook.cjs
— they are now init-generated (untracked), matching reflect's pattern.
- Tests: retro-hook.test.ts drives buildRetroWrapperSource (baked temp file, no
hardcoded .claude path); init/init-qwen/cli-harness assert 5 artifacts, 2 Stop
entries (reflect first), per-harness memory files, and idempotency of BOTH hooks.
49 pass + 2 todo.
End-to-end verified: init claude/qwen/gigacode each produce 5 artifacts + 2 Stop
entries; retro.md references the correct memory file; the installed retro wrapper
blocks once per session then {}.
Co-Authored-By: Claude <noreply@anthropic.com>
CI caught a test the targeted exploration missed: CLAUDE_SPEC.installHook now returns 5 artifacts (reflect + retro), not 3. Update the count, destructure, and on-disk assertions. Full suite now passes locally (737 + 2 todo). Co-Authored-By: Claude <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Adds
/retro— a detector-independent session-reflection command, a fail-open once-per-session Stop hook that nudges the agent to run it, and a committable backlog to accumulate proposals.Why
The existing
/reflectis detector-gated: it only fires whenharnessgap's signals trip. Pre-calibration the detector's recall/precision is too weak to be the trigger for reflection — it misses most real friction./retrodecouples reflection from detection: the agent reflects on the session it just lived (the session is in context, so it's not reconstructing from memory), grounds in concrete evidence, and emits low-commitment one-liners a human can later triage into the harness. Recurrence across the backlog — not a single detector trip — becomes the signal to act on. The detector becomes optional seasoning, not the gate.Files
.claude/commands/retro.md— the/retrocommand. Grounds in evidence first (files reread, commands retried, wrong assumptions, detours), bans generic lessons, enforces the "it was not obvious that …; would have helped to know …" seed shape + a*(→ candidate change)*tag, appends to the backlog, and flags recurrence. Never edits harness config..claude/harnessgap-retro-hook.cjs— fail-open, once-per-session Stop-hook wrapper. Loop-guarded viastop_hook_active+ a marker keyed bysession_id/transcript_path. Mirrors the fail-open discipline ofharnessgap-stop-hook.js(src/init/claude.ts)..cjsbecause this repo is ESM ("type":"module") —.js+requirecrashes (see bug(init): Stop-hook wrapper crashes in ESM target repos ("type": "module") #47).test/retro-hook.test.ts— 7 contract tests: block-once, once-per-session via marker, independent sessions, loop-guard, malformed-stdin fail-open, transcript_path keying, no-key fail-open. All passing.docs/_reflections/log.md— accumulating friction backlog (template + the capture → recurrence → triage → retire loop).How to activate (not committed — local/machine-specific)
.claude/settings.jsonis deliberately not in this PR (it carries a machine-specific absolute path during dogfooding). Register the hook with:{ "hooks": { "Stop": [ { "matcher": "", "hooks": [ { "type": "command", "command": "node \"$CLAUDE_PROJECT_DIR/.claude/harnessgap-retro-hook.cjs\"" } ] } ] } }Portable
$CLAUDE_PROJECT_DIR-based registration + wiring intoharnessgap init claude|qwen|gigacode(alongside/reflect, with two-Stop-hook coexistence) is tracked in #48.Verification
npx vitest run test/retro-hook.test.ts→ 7/7 pass (andtscbuilds clean)./retroand appended real proposals — confirming the block-reason path works in Claude Code, not just in unit tests.Relationship to existing work
/reflect(detector-gated, init-installed) is untouched./retrois its complement: confirmed-friction (/reflect) vs unconditional (/retro)./retroeventually supersede detector-gating for reflection?init-emitted reflect wrapper has the same.js/ESM crash on ESM target repos).🤖 Generated with Claude Code