mirror of
https://github.com/luongnv89/claude-howto.git
synced 2026-06-05 22:36:34 +02:00
47b97d1037
* feat(hooks): add SessionEnd progress logger and local progress tracker Adds a SessionEnd hook that prompts for modules studied at session end and appends a record to ~/.claude-howto-progress.json — outside the repo so progress survives git pull without being overwritten. Also adds local-progress/index.html: a self-contained visual tracker with checkboxes for all 10 modules, per-module notes, an overall progress bar, and Export/Import to sync with a local JSON backup file. Key patterns demonstrated: - SessionEnd vs Stop (fires once on exit, not after every response) - /dev/tty for interactive input in hooks (stdin carries the JSON payload) - $CLAUDE_PROJECT_DIR for portable paths (never hardcode /Users/...) - Guard clause to prevent global hook running in unrelated projects Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> * fix(hooks): address PR review — bash 3.2 compat, JSON escaping, textarea XSS - Replace pipeline+while with IFS for-loop (bash 3.2 compatible) - Pass NOTES as Python arg to avoid broken JSON on quotes/backslashes - Set textarea.value instead of innerHTML to prevent XSS from imported JSON Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
95 lines
2.9 KiB
Bash
Executable File
95 lines
2.9 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# SessionEnd hook: prompts for modules worked on, then appends a session record
|
|
# to ~/.claude-howto-progress.json for persistent learning progress tracking.
|
|
#
|
|
# Fires once when the Claude Code session terminates — not after every response.
|
|
# Uses /dev/tty for interactive input since stdin carries the hook's JSON payload.
|
|
#
|
|
# Install: add to .claude/settings.json under the "SessionEnd" event (see below).
|
|
|
|
PROGRESS_FILE="$HOME/.claude-howto-progress.json"
|
|
|
|
# Guard: only run inside this repo
|
|
if [[ "$CLAUDE_PROJECT_DIR" != *"claude-howto"* ]] && [[ "$PWD" != *"claude-howto"* ]]; then
|
|
exit 0
|
|
fi
|
|
|
|
# Create progress file if it doesn't exist
|
|
if [ ! -f "$PROGRESS_FILE" ]; then
|
|
echo '{"sessions":[]}' > "$PROGRESS_FILE"
|
|
fi
|
|
|
|
DATE=$(date +"%Y-%m-%d")
|
|
TIME=$(date +"%H:%M")
|
|
|
|
echo ""
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo " Claude Code — Learning Session End"
|
|
echo " $DATE $TIME"
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo ""
|
|
echo " Which modules did you work on? (e.g. 06,07 or press Enter to skip)"
|
|
echo " 01=Slash 02=Memory 03=Skills 04=Subagents 05=MCP"
|
|
echo " 06=Hooks 07=Plugins 08=Checkpoints 09=Advanced 10=CLI"
|
|
echo ""
|
|
printf " > "
|
|
read -r INPUT </dev/tty
|
|
|
|
if [ -z "$INPUT" ] || [ "$INPUT" = "skip" ]; then
|
|
echo " Skipped — no session logged."
|
|
echo ""
|
|
exit 0
|
|
fi
|
|
|
|
# Map short numbers to module names (for loop avoids pipeline+while, which bash 3.2 can't parse)
|
|
IFS=',' read -ra PARTS <<< "$INPUT"
|
|
MODULES_JSON=""
|
|
for m in "${PARTS[@]}"; do
|
|
m="${m// /}" # strip spaces
|
|
case "$m" in
|
|
01) label='"01-slash-commands"' ;;
|
|
02) label='"02-memory"' ;;
|
|
03) label='"03-skills"' ;;
|
|
04) label='"04-subagents"' ;;
|
|
05) label='"05-mcp"' ;;
|
|
06) label='"06-hooks"' ;;
|
|
07) label='"07-plugins"' ;;
|
|
08) label='"08-checkpoints"' ;;
|
|
09) label='"09-advanced-features"' ;;
|
|
10) label='"10-cli"' ;;
|
|
*) label="\"$m\"" ;;
|
|
esac
|
|
MODULES_JSON="${MODULES_JSON:+$MODULES_JSON,}$label"
|
|
done
|
|
|
|
printf " Notes? (optional, press Enter to skip): "
|
|
read -r NOTES </dev/tty
|
|
|
|
# Pass NOTES as a separate argument so Python handles JSON escaping —
|
|
# avoids broken JSON when notes contain quotes or backslashes.
|
|
python3 - "$PROGRESS_FILE" "$DATE" "$TIME" "$MODULES_JSON" "$NOTES" <<'PYEOF'
|
|
import sys, json
|
|
|
|
path, date, time_str, modules_raw, notes = sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4], sys.argv[5]
|
|
|
|
new_session = {
|
|
"date": date,
|
|
"time": time_str,
|
|
"modules": json.loads(f"[{modules_raw}]") if modules_raw else [],
|
|
"notes": notes,
|
|
}
|
|
|
|
with open(path, 'r') as f:
|
|
data = json.load(f)
|
|
|
|
data.setdefault('sessions', []).append(new_session)
|
|
|
|
with open(path, 'w') as f:
|
|
json.dump(data, f, indent=2)
|
|
PYEOF
|
|
|
|
echo ""
|
|
echo " Saved to $PROGRESS_FILE"
|
|
[ -n "$NOTES" ] && echo " Notes: $NOTES"
|
|
echo ""
|