Skip to content

feat(reflect): add /retro — unconditional session reflection + once-per-session nudge - #49

Merged
HumanBean17 merged 7 commits into
mainfrom
feat/retro-reflection
Jul 29, 2026
Merged

feat(reflect): add /retro — unconditional session reflection + once-per-session nudge#49
HumanBean17 merged 7 commits into
mainfrom
feat/retro-reflection

Conversation

@HumanBean17

Copy link
Copy Markdown
Owner

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 /reflect is detector-gated: it only fires when harnessgap's signals trip. Pre-calibration the detector's recall/precision is too weak to be the trigger for reflection — it misses most real friction. /retro decouples 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 /retro command. 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 via stop_hook_active + a marker keyed by session_id/transcript_path. Mirrors the fail-open discipline of harnessgap-stop-hook.js (src/init/claude.ts). .cjs because this repo is ESM ("type":"module") — .js+require crashes (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.json is 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 into harnessgap init claude|qwen|gigacode (alongside /reflect, with two-Stop-hook coexistence) is tracked in #48.

Verification

  • npx vitest run test/retro-hook.test.ts7/7 pass (and tsc builds clean).
  • Live end-to-end: the hook fired at session end, surfaced the nudge, and the agent ran /retro and appended real proposals — confirming the block-reason path works in Claude Code, not just in unit tests.

Relationship to existing work

🤖 Generated with Claude Code

HumanBean17 and others added 2 commits July 28, 2026 17:55
…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>
HumanBean17 and others added 5 commits July 28, 2026 18:18
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>
@HumanBean17
HumanBean17 merged commit 40ffefc into main Jul 29, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant