@windyroad/retrospective 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,5 @@
1
+ {
2
+ "name": "wr-retrospective",
3
+ "version": "0.1.0",
4
+ "description": "Session retrospective reminders and plan review for Claude Code"
5
+ }
@@ -0,0 +1,65 @@
1
+ #!/bin/bash
2
+ # Shared dependency checker for windyroad-plugins marketplace.
3
+ # Called by each plugin's SessionStart hook with a list of required plugins.
4
+ #
5
+ # Usage: check-deps.sh <this-plugin-name> <required-plugin-1> [required-plugin-2] ...
6
+ #
7
+ # Checks if required sibling plugins are installed by looking for their
8
+ # .claude-plugin/plugin.json in the plugin cache. Outputs a warning to
9
+ # stderr (which surfaces as hook output) if any are missing.
10
+
11
+ set -euo pipefail
12
+
13
+ PLUGIN_NAME="${1:?Usage: check-deps.sh <plugin-name> <dep1> [dep2] ...}"
14
+ shift
15
+
16
+ MISSING=()
17
+
18
+ for DEP in "$@"; do
19
+ # Check if the dependency plugin is installed by looking for its marker.
20
+ # Installed plugins have their hooks loaded, so we check if the dep's
21
+ # hooks are present in the session. Simplest check: look for the plugin
22
+ # name in the installed plugins list.
23
+ FOUND=false
24
+
25
+ # Method 1: Check installed_plugins.json
26
+ if [ -f "$HOME/.claude/plugins/installed_plugins.json" ]; then
27
+ if python3 -c "
28
+ import json, sys
29
+ data = json.load(open('$HOME/.claude/plugins/installed_plugins.json'))
30
+ plugins = data.get('plugins', {})
31
+ for key in plugins:
32
+ if key.startswith('${DEP}@'):
33
+ sys.exit(0)
34
+ sys.exit(1)
35
+ " 2>/dev/null; then
36
+ FOUND=true
37
+ fi
38
+ fi
39
+
40
+ # Method 2: Check if plugin dir exists in cache
41
+ if [ "$FOUND" = false ]; then
42
+ for dir in "$HOME/.claude/plugins/cache/"*/"$DEP"/*/; do
43
+ if [ -f "${dir}.claude-plugin/plugin.json" ] 2>/dev/null; then
44
+ FOUND=true
45
+ break
46
+ fi
47
+ done
48
+ fi
49
+
50
+ # Method 3: Check if loaded via --plugin-dir (plugin hooks would be active)
51
+ # We can't easily check this, so we rely on methods 1 and 2.
52
+
53
+ if [ "$FOUND" = false ]; then
54
+ MISSING+=("$DEP")
55
+ fi
56
+ done
57
+
58
+ if [ ${#MISSING[@]} -gt 0 ]; then
59
+ echo ""
60
+ echo "WARNING: Plugin '$PLUGIN_NAME' requires the following plugins that may not be installed:"
61
+ for m in "${MISSING[@]}"; do
62
+ echo " - $m (install with: /plugin install $m@windyroad-plugins)"
63
+ done
64
+ echo ""
65
+ fi
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { resolve, dirname } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+ const utils = await import(resolve(__dirname, "../../shared/install-utils.mjs"));
8
+
9
+ const PLUGIN = "wr-retrospective";
10
+ const DEPS = ["wr-problem", "wr-risk-scorer"];
11
+
12
+ const flags = utils.parseStandardArgs(process.argv);
13
+
14
+ if (flags.help) {
15
+ console.log(`
16
+ Usage: npx @windyroad/retrospective [options]
17
+
18
+ Session retrospectives that update briefings and create problem tickets
19
+
20
+ Options:
21
+ --update Update this plugin and its skills
22
+ --uninstall Remove this plugin
23
+ --dry-run Show what would be done without executing
24
+ --help, -h Show this help
25
+ `);
26
+ process.exit(0);
27
+ }
28
+
29
+ if (flags.dryRun) {
30
+ utils.setDryRun(true);
31
+ console.log("[dry-run mode — no commands will be executed]\n");
32
+ }
33
+
34
+ utils.checkPrerequisites();
35
+
36
+ if (flags.uninstall) {
37
+ utils.uninstallPackage(PLUGIN);
38
+ } else if (flags.update) {
39
+ utils.updatePackage(PLUGIN);
40
+ } else {
41
+ utils.installPackage(PLUGIN, { deps: DEPS });
42
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "hooks": {
3
+ "SessionStart": [
4
+ { "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/bin/check-deps.sh wr-retrospective wr-problem wr-risk-scorer" }] }
5
+ ],
6
+ "PreToolUse": [
7
+ { "matcher": "ExitPlanMode", "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/review-plan-enforce.sh" }] }
8
+ ],
9
+ "Stop": [
10
+ { "hooks": [{ "type": "command", "command": "${CLAUDE_PLUGIN_ROOT}/hooks/retrospective-reminder.sh" }] }
11
+ ]
12
+ }
13
+ }
@@ -0,0 +1,174 @@
1
+ #!/bin/bash
2
+ # Shared portable helpers for gate enforcement hooks.
3
+ # Sourced by architect-gate.sh, risk-gate.sh, and all hook scripts.
4
+ # Provides: _mtime, _hashcmd, _doc_exclusions, _err_trap, _get_*
5
+
6
+ # ---------------------------------------------------------------------------
7
+ # Portable utilities
8
+ # ---------------------------------------------------------------------------
9
+
10
+ # Portable mtime: tries GNU stat, falls back to macOS stat
11
+ _mtime() { stat -c%Y "$1" 2>/dev/null || /usr/bin/stat -f%m "$1" 2>/dev/null || echo 0; }
12
+
13
+ # Portable hash: tries md5sum, falls back to md5 -r, then shasum
14
+ _hashcmd() { md5sum 2>/dev/null || md5 -r 2>/dev/null || shasum 2>/dev/null; }
15
+
16
+ # Paths excluded from pipeline state hashing and docs-only detection.
17
+ _doc_exclusions() {
18
+ echo ':!docs/' ':!.risk-reports/' ':!.changeset/' ':!governance/' ':!.claude/plans/' ':!CLAUDE.md' ':!AGENTS.md' ':!PRINCIPLES.md' ':!DECISION-MANAGEMENT.md' ':!AGENTIC_RISK_REGISTER.md' ':!PROBLEM-MANAGEMENT.md'
19
+ }
20
+
21
+ # ---------------------------------------------------------------------------
22
+ # ERR trap: outputs diagnostic JSON on hook errors (P010)
23
+ # Usage: source gate-helpers.sh at top of hook, then call _enable_err_trap
24
+ # ---------------------------------------------------------------------------
25
+
26
+ _enable_err_trap() {
27
+ trap '_err_trap_handler "$BASH_SOURCE" "$LINENO" "$BASH_COMMAND"' ERR
28
+ }
29
+
30
+ _err_trap_handler() {
31
+ local script="$1" line="$2" cmd="$3"
32
+ local name
33
+ name=$(basename "$script" 2>/dev/null || echo "$script")
34
+ # Output diagnostic as systemMessage so it's visible in conversation
35
+ cat <<EOF
36
+ {
37
+ "systemMessage": "Hook error in ${name} at line ${line}: ${cmd}"
38
+ }
39
+ EOF
40
+ }
41
+
42
+ # ---------------------------------------------------------------------------
43
+ # JSON input parsing: standardised helpers replacing inline python3
44
+ # Each reads from _HOOK_INPUT (set by the hook before calling these)
45
+ # ---------------------------------------------------------------------------
46
+
47
+ # Store hook input for reuse by parsing helpers
48
+ _HOOK_INPUT=""
49
+
50
+ _parse_input() {
51
+ _HOOK_INPUT=$(cat)
52
+ }
53
+
54
+ _get_tool_name() {
55
+ echo "$_HOOK_INPUT" | python3 -c "
56
+ import sys, json
57
+ try:
58
+ data = json.load(sys.stdin)
59
+ print(data.get('tool_name', ''))
60
+ except:
61
+ print('')
62
+ " 2>/dev/null || echo ""
63
+ }
64
+
65
+ _get_session_id() {
66
+ echo "$_HOOK_INPUT" | python3 -c "
67
+ import sys, json
68
+ try:
69
+ data = json.load(sys.stdin)
70
+ print(data.get('session_id', ''))
71
+ except:
72
+ print('')
73
+ " 2>/dev/null || echo ""
74
+ }
75
+
76
+ _get_command() {
77
+ echo "$_HOOK_INPUT" | python3 -c "
78
+ import sys, json
79
+ try:
80
+ data = json.load(sys.stdin)
81
+ print(data.get('tool_input', {}).get('command', ''))
82
+ except:
83
+ print('')
84
+ " 2>/dev/null || echo ""
85
+ }
86
+
87
+ _get_file_path() {
88
+ echo "$_HOOK_INPUT" | python3 -c "
89
+ import sys, json
90
+ try:
91
+ data = json.load(sys.stdin)
92
+ ti = data.get('tool_input', {})
93
+ print(ti.get('file_path', ti.get('path', '')))
94
+ except:
95
+ print('')
96
+ " 2>/dev/null || echo ""
97
+ }
98
+
99
+ _get_subagent_type() {
100
+ echo "$_HOOK_INPUT" | python3 -c "
101
+ import sys, json
102
+ try:
103
+ data = json.load(sys.stdin)
104
+ print(data.get('tool_input', {}).get('subagent_type', ''))
105
+ except:
106
+ print('')
107
+ " 2>/dev/null || echo ""
108
+ }
109
+
110
+ _get_user_prompt() {
111
+ echo "$_HOOK_INPUT" | python3 -c "
112
+ import sys, json
113
+ try:
114
+ data = json.load(sys.stdin)
115
+ print(data.get('user_prompt', ''))
116
+ except:
117
+ print('')
118
+ " 2>/dev/null || echo ""
119
+ }
120
+
121
+ _get_tool_output() {
122
+ echo "$_HOOK_INPUT" | python3 -c "
123
+ import sys, json
124
+ try:
125
+ data = json.load(sys.stdin)
126
+ # PostToolUse provides tool_response (dict with content array), not tool_output
127
+ tr = data.get('tool_response', {})
128
+ if isinstance(tr, dict):
129
+ content = tr.get('content', [])
130
+ if isinstance(content, list):
131
+ texts = [c.get('text', '') for c in content if isinstance(c, dict) and c.get('type') == 'text']
132
+ if texts:
133
+ print('\n'.join(texts))
134
+ sys.exit(0)
135
+ # Fallback for older/different hook formats
136
+ print(data.get('tool_output', ''))
137
+ except:
138
+ print('')
139
+ " 2>/dev/null || echo ""
140
+ }
141
+
142
+ # ---------------------------------------------------------------------------
143
+ # Session-scoped tmp directory for risk files
144
+ # ---------------------------------------------------------------------------
145
+
146
+ # Returns the session-scoped directory for risk temp files.
147
+ # Creates the directory if it doesn't exist.
148
+ # Usage: DIR=$(_risk_dir "$SESSION_ID"); echo "1" > "$DIR/commit"
149
+ _risk_dir() {
150
+ local sid="$1"
151
+ local dir="${TMPDIR:-/tmp}/claude-risk-${sid}"
152
+ mkdir -p "$dir"
153
+ echo "$dir"
154
+ }
155
+
156
+ # ---------------------------------------------------------------------------
157
+ # Non-doc file detection for WIP gating
158
+ # ---------------------------------------------------------------------------
159
+
160
+ _is_doc_file() {
161
+ local file_path="$1"
162
+ local EXCL
163
+ EXCL=$(_doc_exclusions)
164
+ for pattern in $EXCL; do
165
+ local clean="${pattern#:!}"
166
+ case "$file_path" in
167
+ *"$clean"*) return 0 ;;
168
+ esac
169
+ done
170
+ case "$file_path" in
171
+ *.claude/*|*.risk-reports/*|*RISK-POLICY.md) return 0 ;;
172
+ esac
173
+ return 1
174
+ }
@@ -0,0 +1,102 @@
1
+ #!/bin/bash
2
+ # Shared gate logic for review enforcement hooks (a11y, voice-tone, style-guide).
3
+ # Sourced by *-enforce-edit.sh hooks and review-plan-enforce.sh.
4
+ # Provides: check_review_gate, review_gate_deny, review_gate_parse_error
5
+
6
+ # Source shared portable helpers (_mtime, _hashcmd)
7
+ _REVIEW_GATE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
+ source "$_REVIEW_GATE_DIR/gate-helpers.sh"
9
+
10
+ # Check review gate marker. Returns 0 if marker is valid (allow), 1 if invalid (deny).
11
+ # Sets REVIEW_GATE_REASON on failure.
12
+ # Usage: check_review_gate "$SESSION_ID" "style-guide" "docs/STYLE-GUIDE.md"
13
+ check_review_gate() {
14
+ local SESSION_ID="$1"
15
+ local SYSTEM="$2" # e.g., "a11y", "voice-tone", "style-guide"
16
+ local POLICY_FILE="$3" # e.g., "docs/STYLE-GUIDE.md"
17
+ local MARKER="/tmp/${SYSTEM}-reviewed-${SESSION_ID}"
18
+ local HASH_FILE="/tmp/${SYSTEM}-reviewed-${SESSION_ID}.hash"
19
+ local TTL_SECONDS="${REVIEW_TTL:-600}"
20
+
21
+ # 1. Marker must exist
22
+ if [ ! -f "$MARKER" ]; then
23
+ REVIEW_GATE_REASON="No ${SYSTEM} review marker found. The ${SYSTEM} agent must review first."
24
+ return 1
25
+ fi
26
+
27
+ # 2. TTL check — marker mtime must be within TTL
28
+ local NOW=$(date +%s)
29
+ local MARKER_TIME=$(_mtime "$MARKER")
30
+ local AGE=$(( NOW - MARKER_TIME ))
31
+ if [ "$AGE" -ge "$TTL_SECONDS" ]; then
32
+ rm -f "$MARKER" "$HASH_FILE"
33
+ REVIEW_GATE_REASON="${SYSTEM} review expired (${AGE}s old, TTL ${TTL_SECONDS}s). Re-run the ${SYSTEM} agent."
34
+ return 1
35
+ fi
36
+
37
+ # 3. Drift detection — policy file hash must match
38
+ if [ -f "$HASH_FILE" ] && [ -n "$POLICY_FILE" ]; then
39
+ local STORED_HASH=$(cat "$HASH_FILE")
40
+ local CURRENT_HASH=""
41
+ if [ -f "$POLICY_FILE" ]; then
42
+ CURRENT_HASH=$(cat "$POLICY_FILE" | _hashcmd | cut -d' ' -f1)
43
+ elif [ -d "$POLICY_FILE" ]; then
44
+ # Directory (e.g., docs/decisions/) — hash all .md files
45
+ CURRENT_HASH=$(find "$POLICY_FILE" -name '*.md' -not -name 'README.md' -print0 | sort -z | xargs -0 cat 2>/dev/null | _hashcmd | cut -d' ' -f1)
46
+ else
47
+ CURRENT_HASH="missing"
48
+ fi
49
+ if [ "$STORED_HASH" != "$CURRENT_HASH" ]; then
50
+ rm -f "$MARKER" "$HASH_FILE"
51
+ REVIEW_GATE_REASON="${SYSTEM} policy file changed since last review. Re-run the ${SYSTEM} agent."
52
+ return 1
53
+ fi
54
+ fi
55
+
56
+ # Slide TTL window forward
57
+ touch "$MARKER"
58
+ return 0
59
+ }
60
+
61
+ # Store policy file hash after a successful review.
62
+ # Usage: store_review_hash "$SESSION_ID" "style-guide" "docs/STYLE-GUIDE.md"
63
+ store_review_hash() {
64
+ local SESSION_ID="$1"
65
+ local SYSTEM="$2"
66
+ local POLICY_FILE="$3"
67
+ local HASH_FILE="/tmp/${SYSTEM}-reviewed-${SESSION_ID}.hash"
68
+
69
+ if [ -n "$POLICY_FILE" ]; then
70
+ local HASH=""
71
+ if [ -f "$POLICY_FILE" ]; then
72
+ HASH=$(cat "$POLICY_FILE" | _hashcmd | cut -d' ' -f1)
73
+ elif [ -d "$POLICY_FILE" ]; then
74
+ HASH=$(find "$POLICY_FILE" -name '*.md' -not -name 'README.md' -print0 | sort -z | xargs -0 cat 2>/dev/null | _hashcmd | cut -d' ' -f1)
75
+ else
76
+ HASH="missing"
77
+ fi
78
+ echo "$HASH" > "$HASH_FILE"
79
+ fi
80
+ }
81
+
82
+ # Emit fail-closed deny JSON for PreToolUse hooks.
83
+ review_gate_deny() {
84
+ local REASON="$1"
85
+ cat <<EOF
86
+ {
87
+ "hookSpecificOutput": {
88
+ "hookEventName": "PreToolUse",
89
+ "permissionDecision": "deny",
90
+ "permissionDecisionReason": "$REASON"
91
+ }
92
+ }
93
+ EOF
94
+ }
95
+
96
+ # Emit fail-closed deny JSON for parse failures.
97
+ review_gate_parse_error() {
98
+ cat <<'EOF'
99
+ { "hookSpecificOutput": { "hookEventName": "PreToolUse", "permissionDecision": "deny",
100
+ "permissionDecisionReason": "BLOCKED: Could not parse hook input. Gate is fail-closed." } }
101
+ EOF
102
+ }
@@ -0,0 +1,9 @@
1
+ #!/bin/bash
2
+ # Stop hook: Reminds the user to run /retrospective before ending the session.
3
+
4
+ cat <<'EOF'
5
+ {
6
+ "stopReason": "Before ending: run /retrospective to capture session learnings, update docs/BRIEFING.md, and use the /problem skill to create problem tickets for anything that failed or was harder than it should have been."
7
+ }
8
+ EOF
9
+ exit 0
@@ -0,0 +1,73 @@
1
+ #!/bin/bash
2
+ # PreToolUse hook: Denies ExitPlanMode until review specialists have
3
+ # reviewed the plan. Skips UI specialists (a11y, voice-tone, style-guide,
4
+ # jtbd) when the plan only touches non-UI files (P008 optimization).
5
+
6
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
7
+ source "$SCRIPT_DIR/lib/review-gate.sh"
8
+ source "$SCRIPT_DIR/lib/gate-helpers.sh"
9
+
10
+ INPUT=$(cat)
11
+
12
+ SESSION_ID=$(echo "$INPUT" | jq -r '.session_id // empty') || true
13
+
14
+ if [ -z "$SESSION_ID" ]; then
15
+ review_gate_parse_error
16
+ exit 0
17
+ fi
18
+
19
+ # Detect if the plan touches UI files by checking uncommitted changes
20
+ # UI patterns: *.html, *.jsx, *.tsx, *.vue, *.svelte, *.astro, *.css, *.scss
21
+ HAS_UI_FILES=false
22
+ UI_PATTERNS='\.html$|\.jsx$|\.tsx$|\.vue$|\.svelte$|\.astro$|\.css$|\.scss$|\.ejs$|\.hbs$|\.erb$|\.leaf$'
23
+ if git diff --cached --name-only 2>/dev/null | grep -qE "$UI_PATTERNS"; then
24
+ HAS_UI_FILES=true
25
+ elif git diff --name-only 2>/dev/null | grep -qE "$UI_PATTERNS"; then
26
+ HAS_UI_FILES=true
27
+ elif git ls-files --others --exclude-standard 2>/dev/null | grep -qE "$UI_PATTERNS"; then
28
+ HAS_UI_FILES=true
29
+ fi
30
+
31
+ # Also check the plan file itself for mentions of UI files
32
+ PLAN_DIR="$HOME/.claude/plans"
33
+ if [ -d "$PLAN_DIR" ] && [ "$HAS_UI_FILES" = false ]; then
34
+ LATEST_PLAN=$(ls -t "$PLAN_DIR"/*.md 2>/dev/null | head -1)
35
+ if [ -n "$LATEST_PLAN" ] && grep -qiE '\.html|\.jsx|\.tsx|\.vue|\.svelte|\.css|component|page|form|modal|dialog' "$LATEST_PLAN" 2>/dev/null; then
36
+ HAS_UI_FILES=true
37
+ fi
38
+ fi
39
+
40
+ MISSING=""
41
+
42
+ if [ "$HAS_UI_FILES" = true ]; then
43
+ # UI files detected — require all specialists
44
+ for SYSTEM in a11y voice-tone style-guide jtbd; do
45
+ MARKER="/tmp/${SYSTEM}-plan-reviewed-${SESSION_ID}"
46
+ if [ ! -f "$MARKER" ]; then
47
+ case "$SYSTEM" in
48
+ a11y) AGENT="accessibility-agents:accessibility-lead" ;;
49
+ voice-tone) AGENT="voice-and-tone-lead" ;;
50
+ style-guide) AGENT="style-guide-lead" ;;
51
+ jtbd) AGENT="jtbd-lead" ;;
52
+ esac
53
+ if [ -z "$MISSING" ]; then
54
+ MISSING="$AGENT"
55
+ else
56
+ MISSING="${MISSING}, ${AGENT}"
57
+ fi
58
+ fi
59
+ done
60
+ else
61
+ # No UI files — skip a11y, voice-tone, style-guide, jtbd
62
+ # Auto-create their markers so the gate passes
63
+ for SYSTEM in a11y voice-tone style-guide jtbd; do
64
+ touch "/tmp/${SYSTEM}-plan-reviewed-${SESSION_ID}"
65
+ done
66
+ fi
67
+
68
+ if [ -n "$MISSING" ]; then
69
+ review_gate_deny "BLOCKED: Cannot approve plan without specialist review. Missing: ${MISSING}. Delegate to each agent to review the plan."
70
+ exit 0
71
+ fi
72
+
73
+ exit 0
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@windyroad/retrospective",
3
+ "version": "0.2.0",
4
+ "description": "Session retrospectives that update briefings and create problem tickets",
5
+ "bin": {
6
+ "windyroad-retrospective": "./bin/install.mjs"
7
+ },
8
+ "type": "module",
9
+ "license": "MIT",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "https://github.com/windyroad/agent-plugins.git",
13
+ "directory": "packages/retrospective"
14
+ },
15
+ "keywords": [
16
+ "claude-code",
17
+ "claude-code-plugin",
18
+ "ai-agent",
19
+ "ai-coding"
20
+ ],
21
+ "files": [
22
+ "bin/",
23
+ "agents/",
24
+ "hooks/",
25
+ "skills/",
26
+ ".claude-plugin/"
27
+ ]
28
+ }
@@ -0,0 +1,72 @@
1
+ ---
2
+ name: wr:retrospective
3
+ description: Run a session retrospective. Updates docs/BRIEFING.md with learnings and creates problem tickets for failures and friction.
4
+ allowed-tools: Read, Write, Edit, Bash, Glob, Grep, AskUserQuestion, Skill
5
+ ---
6
+
7
+ # Session Retrospective
8
+
9
+ Reflect on the current session, update the project briefing, and create problem tickets for failures and friction.
10
+
11
+ ## Steps
12
+
13
+ ### 1. Read the current briefing
14
+
15
+ Read `docs/BRIEFING.md` to understand what previous sessions already captured.
16
+
17
+ ### 2. Reflect on this session
18
+
19
+ Consider the work done in this session and identify:
20
+
21
+ **What you wish you'd been told up front** — things that were non-obvious and caused wasted effort or wrong assumptions. These should be added to BRIEFING.md "What You Need to Know" if they aren't already there.
22
+
23
+ **What surprised you** — things that contradicted reasonable expectations. These should be added to BRIEFING.md "What Will Surprise You" if they aren't already there.
24
+
25
+ **What was harder than it should have been** — friction points, tool limitations, process overhead, confusing code. These should become problem tickets via the `/problem` skill.
26
+
27
+ **What failed** — things that broke, bugs encountered, hooks that errored, tests that failed unexpectedly. These should become problem tickets via the `/problem` skill.
28
+
29
+ **What should we make easier or automate** — repetitive manual steps, missing tooling, things that could be scripted. These should become problem tickets via the `/problem` skill.
30
+
31
+ ### 3. Update BRIEFING.md
32
+
33
+ Edit `docs/BRIEFING.md`:
34
+
35
+ - **Add** new learnings to the appropriate section ("What You Need to Know" or "What Will Surprise You")
36
+ - **Remove** stale items that are no longer true. A learning is stale when:
37
+ - The issue has been fixed (e.g., "CI doesn't test v2" after v2 tests are added)
38
+ - It's now documented elsewhere (e.g., in an ADR, CLAUDE.md, or README)
39
+ - The codebase has changed enough that it's no longer relevant
40
+ - **Update** items where the details have changed
41
+ - Keep the file concise — under 2000 tokens. Each item should be 1-2 lines.
42
+
43
+ Use the AskUserQuestion tool to confirm any removals: "I'd like to remove [item] from BRIEFING.md because [reason]. Is this correct?"
44
+
45
+ ### 4. Create or update problem tickets
46
+
47
+ For each item identified in "What was harder than it should have been", "What failed", and "What should we make easier or automate", use the `/problem` skill to:
48
+
49
+ - Check if a problem ticket already exists in `docs/problems/`
50
+ - If yes: update it with new evidence from this session
51
+ - If no: create a new problem ticket
52
+
53
+ ### 5. Summary
54
+
55
+ Present a summary to the user:
56
+
57
+ ```
58
+ ## Session Retrospective
59
+
60
+ ### BRIEFING.md Changes
61
+ - Added: [items added]
62
+ - Removed: [items removed with reasons]
63
+ - Updated: [items modified]
64
+
65
+ ### Problems Created/Updated
66
+ - [problem ticket]: [summary]
67
+
68
+ ### No Action Needed
69
+ - [learnings that were already captured]
70
+ ```
71
+
72
+ $ARGUMENTS