session-recap
Convert a Claude Code session's transcript JSONL into two markdown artifacts plus a
machine-readable meta.json:
summary.md — scannable cheat sheet: TL;DR, accomplishments, key terms (with
cross-file anchor links to detailed definitions), files, decisions, next steps.
note.md — companion deep-dive: full glossary with named anchors, chronological
narrative, command-by-command annotations, pitfalls.
meta.json — structured metadata for downstream tools (recap-to-notion, etc.).
Both markdown files are written so the user can re-skim later and recover everything
technical that was encountered. Assume the user will forget — be explicit.
Workflow
Phase 0 — Cleanup (runs before generation, including in cleanup-only mode)
Always run this at the start of every invocation. The skill owns the recap manifest and
retention policy. Each recap-to-X uploader writes its own entry to the shared
manifest after a successful upload; this skill reads the manifest to decide what is safe
to delete.
- Read
~/.claude/session-recaps/.recap-config.json (defaults below if the file
is missing, or if a given key is absent from it):{
"version": 1,
"chain": [
{"skill": "recap-to-notion:recap-to-notion", "enabled": true, "notes": ""}
],
"retention_days": 20,
"retention_minutes_override": null
}
chain is a list of {skill, enabled, notes} objects, not a flat list of
names — enabled: false lets the user pause a downstream skill without
removing its config entry. retention_days / retention_minutes_override
are read the same way (missing key → default applies); as of this writing
the live config in this repo's development environment only sets version
and chain — retention runs on its default until a config explicitly sets it.
- Read
~/.claude/session-recaps/.manifest.jsonl (skip if missing).
- Walk
~/.claude/session-recaps/<YYYY-MM-DD>_* directories.
- For each dir:
- Has at least one manifest entry whose
local_dir matches this dir? (target type
does not matter — any successful upload counts as evidence of preservation).
- Is the dir's
mtime older than the threshold? Threshold =
retention_minutes_override (if non-null) else retention_days * 1440 (minutes).
- Both yes →
rm -rf the dir. Otherwise → keep.
- Report deletion count in one line in the UI language.
- Never touch dotfiles (
.recap-config.json, .notion-config.json,
.<other>-config.json, .manifest.jsonl) or non-recap directories.
Failure handling:
- A single
rm -rf failure → log it, skip that dir, continue.
.manifest.jsonl parse error → skip cleanup entirely, notify the user, continue with
generation and chain dispatch as normal.
Cleanup-only mode: if the user invoked the skill with phrasing like recap掃除,
recap cleanup, 古いrecap削除, or with an explicit --cleanup-only flag, run Phase 0
and return immediately. Do not generate, do not chain.
Feedback Check
If feedback/log.md exists alongside this SKILL.md and has 5 or more entries, read the
last 10. If a pattern is apparent (the same issue appears in 3+ entries, or average
rating is below 3):
- Tell the user (in the UI language): 「過去のフィードバックで類似パターンを検出: [簡潔に]。
/my-skill-factory improve session-recap で改善できます。」 / English equivalent.
- Continue with normal execution.
Step 1: Resolve target session
Look at the user's invocation message for any of:
- A bare UUID (8-4-4-4-12 hex) — explicit session ID.
- A path or
--cwd mention — alternate working directory to scan instead of $PWD.
- An
--out <dir> style argument — output directory override (default
~/.claude/session-recaps/).
--language ja / --language en — force the output language.
--no-chain — skip the chain dispatch in Step 6.
- Otherwise → scan the current working directory's project transcript folder.
If a session ID was given
find ~/.claude/projects -maxdepth 2 -name "<session-id>.jsonl" 2>/dev/null
If found → use it. If not → tell the user the ID was not seen anywhere under
~/.claude/projects/ and stop.
If no session ID was given (default)
Encode the target cwd: replace every /, ., and _ with -. Example:
/Users/alice/code/foo → -Users-alice-code-foo; a worktree path like
/Users/alice/repo/.claude/worktrees/my_branch →
-Users-alice-repo--claude-worktrees-my-branch (note the doubled - where
/ is immediately followed by .). A single /-only substitution misses
the .claude/worktrees/... segment every worktree session lives under —
this repo's own convention — and any branch name with an underscore.
The transcript directory is ~/.claude/projects/<encoded>/.
List the 10 most recent sessions with their slug and last-event timestamp:
ENC=$(echo "$PWD" | sed 's|[/._]|-|g')
DIR=~/.claude/projects/$ENC
ls -t "$DIR"/*.jsonl 2>/dev/null | head -10 | while read f; do
id=$(basename "$f" .jsonl)
last=$(tail -1 "$f" | jq -r '.timestamp // "?"')
slug=$(grep -m1 -o '"slug":"[^"]*"' "$f" | head -1 | sed 's/"slug":"//;s/"$//')
msgs=$(wc -l < "$f")
echo "$id | $last | $slug | $msgs lines"
done
Pick:
- 0 candidates → tell the user no session was found in this directory; suggest
passing a session ID or a different cwd.
- 1 candidate → use it; show the session ID + slug for confirmation.
- 2+ candidates → invoke
AskUserQuestion with the list (session ID, last
activity, slug, line count). Let the user pick.
When invoked from session-watch
session-watch passes the session ID directly via the Skill tool's args. Use it as
the explicit-ID path above and skip the picker.
Step 2: Extract structured events
Run jq once to project the transcript into a compact event stream. Save to a temp file
so subsequent passes are cheap.
TRANSCRIPT=<resolved-jsonl-path>
SHORT=$(basename "$TRANSCRIPT" .jsonl | cut -c1-8)
TMP=/tmp/session-recap-$SHORT.events.jsonl
jq -c '
. as $r |
if $r.type == "user" then
{kind: "user", ts: $r.timestamp, sc: $r.isSidechain,
content: ($r.message.content // "")}
elif $r.type == "assistant" then
($r.message.content // [])[]? |
if .type == "text" then
{kind: "text", ts: $r.timestamp, sc: $r.isSidechain, text: .text}
elif .type == "tool_use" then
{kind: "tool", ts: $r.timestamp, sc: $r.isSidechain,
name: .name, input: .input}
else empty end
else empty end
' "$TRANSCRIPT" > "$TMP"
wc -l "$TMP"
Also pull session metadata:
head -1 "$TRANSCRIPT" | jq '{started_at: .timestamp, cwd, version, slug, gitBranch, sessionId}'
tail -1 "$TRANSCRIPT" | jq '{ended_at: .timestamp}'
grep -c '"type":"assistant"' "$TRANSCRIPT"
grep -c '"type":"user"' "$TRANSCRIPT"
Step 3: Detect output language
Unless --language was passed (use as-is), determine the output language from the
transcript:
- Concatenate all assistant text content (the
kind: "text" events from $TMP).
- Count Hiragana (U+3040-U+309F), Katakana (U+30A0-U+30FF), and CJK Unified Ideograph
(U+4E00-U+9FFF) characters as "Japanese".
- Count ASCII letters (a-z, A-Z) as "English".
- If
Japanese / (Japanese + English) >= 0.5 → output language = ja.
Otherwise → en.
- If both counts are zero (or transcript is empty) → fall back to
en.
The detected language is stored in meta.json and used to drive Step 4 templates.
Step 4: Long-transcript handling
If $TMP has more than 2000 lines, do not load it all into context at once:
- Read in chunks (e.g., 500 events at a time, with
sed -n 'A,Bp' or head/tail).
- For each chunk extract: tool calls, decisions ("let's do X / instead of Y"), errors,
file edits, and a 1-2 sentence narrative summary.
- Combine chunk summaries into the final synthesis in Step 5.
For shorter transcripts, read the whole $TMP and proceed directly.
Step 5: Synthesize the artifacts
Produce three files. The two markdown files are in the language detected in Step 3.
Output language note
- Quoted commands, file paths, code blocks, and proper nouns stay verbatim regardless of
language.
- Section headings, prose, term definitions, decisions, narratives are in the chosen
language.
- The Japanese template below is shown as the primary example; for English output, use
obvious analogues (e.g. "TL;DR" stays, "達成したこと" → "What got done", "用語集"
→ "Glossary", "コマンド逐次解説" → "Commands annotated", "つまずきと解決" →
"Pitfalls", etc.).
Cross-file anchor link convention
The two files must remain navigable in any standard markdown viewer (GitHub, VS Code,
mdcat, etc.) even if Notion sync is unavailable.
- In
summary.md, every key term and key command links to its full definition in
note.md using a cross-file anchor: [label](./note.md#anchor-id).
- Anchor IDs use kebab-case lowercase. Conventions:
- Term:
#<term-slug> (e.g. #derivation, #flake)
- Command:
#cmd-<short-label> (e.g. #cmd-nix-build, #cmd-git-add-a)
- Pitfall:
#pitfall-<short-label>
- Phase:
#phase-<n>-<short-label>
- In
note.md, a heading must always carry an explicit {#kebab-id} whenever its
rendered text contains anything other than [a-z0-9-] — that is, dots,
underscores, slashes, spaces with mixed case, backticks, parentheses, dollar signs,
uppercase letters, etc. Standard markdown sluggers (GitHub, VS Code, mdBook, Pandoc)
disagree on how to slugify these, so without an explicit ID the cross-file links in
summary.md will silently break. Examples:
### derivation — plain alphanumeric, auto-slug derivation is reliable, no
{#…} needed.
### home.sessionPath {#home-sessionpath} — has a dot AND mixed case, must
declare ID explicitly.
### system.defaults.finder {#system-defaults-finder} — has dots, must declare.
### \hm-session-vars.sh` {#hm-session-vars-sh}` — has backticks and a dot.
### \nix build #` {#cmd-nix-build}— has angle brackets and#. When in doubt, add the {#…}` — it never hurts and always makes the link resolve in
every renderer.
When recap-to-notion later merges these into a single Notion page, it rewrites the
cross-file (./note.md#x) form into same-page (#x) form. The local files keep
working independently.
summary.md template (Japanese variant — adapt to English when language=en)
# Session Recap: <slug or short-id> — <YYYY-MM-DD>
- **Session ID**: `<full-id>`
- **期間**: `<started_at>` → `<ended_at>` (`<elapsed>`)
- **cwd**: `<cwd>`
- **イベント総数**: <N> (assistant text: <a>, user: <u>, tool calls: <t>, sidechain: <s>)
## TL;DR
<1-3行で全体を要約>
## 達成したこと
1. <フェーズまたは主要タスク 1>
2. <フェーズまたは主要タスク 2>
## 主な用語 (チートシート)
| 用語 | 一行定義 |
|------|----------|
| [`derivation`](./note.md#derivation) | <gloss> |
| [`flake`](./note.md#flake) | <gloss> |
## 触ったファイル
| パス | 操作 | 一言メモ |
|------|------|----------|
| `<path>` | created / edited / deleted | <note> |
## 注目コマンド
| コマンド | 目的 | 結果 |
|----------|------|------|
| [`nix build ...`](./note.md#cmd-nix-build) | <purpose> | <outcome> |
## 決定事項
- **<decision>**: <reasoning>
## 未解決 / 次にやること
- [ ] <item>
note.md template
# 詳細ノート: <slug> — <YYYY-MM-DD>
`summary.md` の補完。
## コンテキスト
<2-3段落>
## 用語集
### derivation
<2-4行の定義>
**このsessionでの登場文脈**: <文脈>
**関連**: <他の用語>
---
### flake
...
## 時系列の流れ
### Phase 1: <名前> {#phase-1-<short-label>}
<narrative>
## コマンド逐次解説
### `nix build <flake>#<output>` {#cmd-nix-build}
\`\`\`bash
<full command>
\`\`\`
- <flag/構文 1>: <意味>
- <flag/構文 2>: <意味>
**やったこと**: <出力 / 副作用>
**学び**: <一般化>
---
## つまずきと解決
### <pitfall> {#pitfall-<short-label>}
- **症状**: <観測>
- **原因**: <ルートコーズ>
- **対処**: <修正>
- **教訓**: <次回に持っていくもの>
## 参照
- <URL 1>
meta.json schema (machine-readable)
Write this alongside the two markdown files. Downstream skills (recap-to-notion etc.)
read it instead of re-parsing markdown.
{
"session_id": "<uuid>",
"short_id": "<8 chars>",
"slug": "<slug or empty>",
"cwd": "<absolute path>",
"language": "ja",
"started_at": "<ISO-8601 with time and timezone>",
"ended_at": "<ISO-8601 with time and timezone>",
"duration_seconds": 1234,
"total_events": 290,
"events": {"user": 12, "assistant_text": 45, "tool": 200, "sidechain": 33},
"generator_version": "1.2.0"
}
Synthesis rules
- Output language: matches the result of Step 3 (or
--language override).
- Term cross-references: every term that appears in
summary.md's 用語表 must have
a corresponding ### <term> heading in note.md and link to it via
[term](./note.md#term-slug). Same for commands.
- Command breakdown: in
note.md, decompose flags, redirections, heredocs, pipes
one by one. In summary.md keep entries one-line.
- Side effects / destructive ops: push, kill, rm, sudo, network calls, deploys are
surfaced explicitly (e.g. "副作用: GitHubへのpush" / "side effect: git push to GitHub").
- Truncation: when transcript content is truncated, note "省略あり" / "truncated"
and avoid claiming certainty about content past the cut.
- Sidechain: sub-agent activity goes into note.md timeline; surface results, not
inner monologue.
- Failure → fix patterns: pitfalls are the highest-value content. Always record
symptom → root cause → fix → lesson.
- Never invent: only describe tool calls / decisions actually present in the
transcript.
Step 5b: Write files and report
OUT_BASE=${OUT_DIR:-~/.claude/session-recaps}
DATE=$(date -u +%Y-%m-%d)
SLUG=<extracted slug, or "session" if missing>
DIR="$OUT_BASE/${DATE}_${SHORT}_${SLUG}"
mkdir -p "$DIR"
# Use the Write tool to create:
# $DIR/summary.md
# $DIR/note.md
# $DIR/meta.json
Then tell the user (in UI language):
- Output directory path
- File names + approximate sizes
- A 2-3 line preview of the TL;DR
- One-line invitation to ask follow-ups about specific terms or phases
Step 6 — Chain dispatch
After local files are written, dispatch any user-configured follow-up skills.
- Read
~/.claude/session-recaps/.recap-config.json (default chain — a single
{"skill": "recap-to-notion:recap-to-notion", "enabled": true} entry — if the
file or the chain key is missing).
- Determine if the chain should be skipped entirely:
--no-chain argument was passed → skip.
- The user's recent invocation contains any of these opt-out phrases → skip:
アップロードしないで, Notionいらない, ローカルだけ,
no upload, skip notion, local only, no chain.
chain is an empty list [] → skip (no-op).
- Otherwise, for each entry in
chain, in order:
enabled: false → skip this entry silently, continue to the next.
- Verify the entry's
skill is installed (skill list lookup). If not, emit a
one-line skip note in UI language and continue with the next.
- Invoke
Skill(skill: "<entry.skill>", args: "<absolute path to recap dir>").
- Surface the returned one-line status in UI language.
- The chain runs sequentially. A failure in one chain entry does not stop the
others (they are independent).
This skill never references recap-to-notion (or any other uploader) by name in the
code path — only the entries listed in the user's .recap-config.json. Adding a new
uploader is a config change, not a source change.
Retrospective
After Step 6 returns:
Consider: were there mid-session corrections (rewrote sections, expanded glossary,
user pointed out missed terms)? Did chain dispatch fail anywhere? Did jq fail on
any line?
Ask the user (in UI language): 「今回のrecapのフィードバック (1-5の評価、抜け落ちた点、または何もなければEnter)」 / English equivalent.
If feedback OR corrections occurred:
a. Create feedback/ next to this SKILL.md if missing.
b. Read or create feedback/log.md with the standard header.
c. Prepend a new entry:
## <ISO-8601 timestamp>
- **Skill Version**: <version from this file's frontmatter>
- **Task**: <1-line description>
- **Outcome**: success | partial-success | failure | error
- **Rating**: <N>/5 (or "—")
- **Corrections**: <session corrections, or "none">
- **Issues**: <issues, or "none">
- **User Note**: <verbatim, or "—">
---
Skip recording if the user passes AND no corrections/issues occurred.
Behavior Scenarios
Scenario: Standalone — single recent session in cwd
Given the user invokes session-recap with no arguments
And exactly one session in cwd has prior activity
When Step 1 runs
Then the skill picks that session, generates summary.md / note.md / meta.json
under ~/.claude/session-recaps/<date>_<short>_<slug>/, and dispatches the
configured chain.
Scenario: Standalone — explicit session ID
Given the user passes a UUID in the message
When Step 1 runs
Then the skill locates the JSONL anywhere under ~/.claude/projects/ regardless
of cwd, and produces the recap from it.
Scenario: Standalone — multiple recent sessions in cwd
Given two or more sessions exist in the cwd's project transcript dir
When Step 1 runs
Then the skill presents the top 10 by recency via AskUserQuestion and recaps
the chosen one.
Scenario: Auto-invoked from session-watch
Given session-watch has just stopped after watching session <X>
When session-watch invokes session-recap with <X> as the session ID
Then this skill generates the recap and dispatches the configured chain.
Scenario: Output language matches session — Japanese >= 50%
Given the watched session's assistant text is dominantly Japanese
When Step 3 runs
Then language is set to "ja" and the artifacts are produced in Japanese.
Scenario: Output language defaults to English when Japanese < 50%
Given the watched session is mostly English
When Step 3 runs
Then language is set to "en" and the artifacts are produced in English.
Scenario: --language argument overrides automatic detection
Given the user passes --language ja against an English session
Then artifacts are produced in Japanese regardless of the detected ratio.
Scenario: Local cross-file anchors work in standard markdown viewers
Given a recap was generated and Notion sync was skipped or failed
When the user opens summary.md in VS Code or GitHub
Then clicking a term link navigates to the corresponding heading in note.md.
Scenario: Chain dispatch — default chain runs recap-to-notion
Given .recap-config.json is missing or chain is the default
When Step 6 runs
Then session-recap invokes recap-to-notion with the recap dir absolute path
and surfaces its one-line status.
Scenario: Chain dispatch — skipped when user says "no upload" or similar
Given the user's invocation contains an opt-out phrase
When Step 6 evaluates skip conditions
Then the chain is fully skipped.
Scenario: Chain dispatch — skipped via --no-chain argument
Given --no-chain was passed
When Step 6 runs
Then the chain is fully skipped.
Scenario: Chain dispatch — empty chain in .recap-config.json runs no follow-up
Given chain is []
When Step 6 runs
Then no chain skills are invoked.
Scenario: Chain dispatch — a disabled entry is skipped without stopping the chain
Given chain has an entry with enabled: false followed by an enabled: true entry
When Step 6 runs
Then the disabled entry is skipped silently and the next entry still runs.
Scenario: Chain dispatch — chained skill not installed → log and continue
Given the chain references a skill that is not installed
When Step 6 attempts to invoke it
Then the skill emits a one-line skip note and proceeds to the next chain entry.
Scenario: Cleanup — confirmed in manifest + past retention → deleted
Given a recap dir has at least one manifest entry
And its mtime is older than the retention threshold
When Phase 0 runs
Then the dir is deleted and the count is reported.
Scenario: Cleanup — confirmed in manifest + within retention → kept
Given a recap dir has at least one manifest entry
And its mtime is within the retention threshold
When Phase 0 runs
Then the dir is kept.
Scenario: Cleanup — no manifest entry + past retention → kept
Given a recap dir has no manifest entry of any target
When Phase 0 runs
Then the dir is kept regardless of age.
Scenario: Cleanup-only mode — invoked via "recap cleanup" runs Phase 0 only
Given the user invokes the skill with "recap cleanup" or similar
When the skill starts
Then it runs Phase 0 and exits without generating or chaining.
Scenario: Cleanup never touches dotfiles or non-recap directories
Given .recap-config.json, .notion-config.json, .manifest.jsonl exist alongside dirs
When Phase 0 runs
Then only directories matching <YYYY-MM-DD>_* are considered for deletion.
Scenario: Long transcript
Given the resolved transcript has more than 2000 events
When Step 4 runs
Then chunked synthesis is used.
Scenario: No usable transcript
Given the resolved JSONL has fewer than 5 events
When Step 5 runs
Then a minimal summary.md is produced and note.md is skipped.
Scenario: Output directory override
Given the user passes --out ~/notes/sessions
When Step 5b runs
Then artifacts are written under ~/notes/sessions/<date>_<short>_<slug>/.
Scenario: Retrospective on a clean run
Given no corrections, no issues, and no user feedback
When the Retrospective runs
Then nothing is written to feedback/log.md.
Scenario: Feedback Check surfaces a pattern
Given feedback/log.md has 5+ entries with a recurring issue keyword
When the skill is invoked
Then it surfaces the pattern in UI language and suggests
/my-skill-factory improve session-recap.
Notes and constraints
- Read-only on the watched session. Do not write back into the transcript or
message the watched session.
- Default output dir:
~/.claude/session-recaps/.
- Naming:
<YYYY-MM-DD>_<short-id>_<slug>/ (slug omitted if not present).
<short-id> is the first 8 chars of the session UUID.
- No invention: never describe a tool call or decision that is not present in the
transcript. When in doubt, say so explicitly.
- Truncated inputs: transcript fields can be truncated; mark them.
- Sidechain depth: sub-agent activity is captured but not exhaustively expanded.
- Idempotent rewrites: invoking again on the same session ID overwrites the
previous artifacts in the same directory (no duplicates).
- Manifest is shared: each
recap-to-X uploader appends an entry on success. This
skill defines the schema and reads the file for cleanup; uploaders only append.
- Manifest schema (
~/.claude/session-recaps/.manifest.jsonl, append-only):{"target": "<name>", "session_id": "<uuid>", "local_dir": "<absolute path>", "target_id": "<id>", "target_url": "<url>", "uploaded_at": "<ISO-8601 with time>", "status": "created"|"updated"}
- Cleanup policy: a recap dir is deletable if it has at least one manifest entry
(regardless of target) AND mtime exceeds the retention threshold. This is intentional
— we trust that any successful upload preserves the data.
- OCP: adding a new uploader (e.g.
recap-to-confluence) requires no edits to this
skill's source. The user adds a new {skill, enabled} entry to chain in their
.recap-config.json.
1---2name: session-recap3description: Read a Claude Code session's transcript and produce two markdown artifacts plus a meta.json: a scannable Summary (TL;DR, key terms, files, decisions, open threads) and a complementary Detailed Note (full glossary, chronological flow, command-by-command annotations, pitfalls). The output language follows the watched session's dominant language (>= 50% Japanese characters → Japanese, otherwise English; overridable via --language). Standalone — pass a session ID, or omit to pick from recent sessions in the current directory. After generation, dispatches a configurable chain of follow-up skills (default ["recap-to-notion"]) read from ~/.claude/session-recaps/.recap-config.json. Owns the manifest schema and runs cleanup of confirmed-uploaded local recap dirs past retention. Read-only on the transcript itself. Use when the user says "session recap" / "セッションのまとめ" / "セッションサマリ" / "学びをまとめて" / "用語集を作って" / "summarize the session" / "recap session" / "session note" / "セッションのノート" / "/session-recap" / "セッション解説まとめ" / "recap掃4---56# session-recap78Convert a Claude Code session's transcript JSONL into two markdown artifacts plus a9machine-readable `meta.json`:1011- **`summary.md`** — scannable cheat sheet: TL;DR, accomplishments, key terms (with12 cross-file anchor links to detailed definitions), files, decisions, next steps.13- **`note.md`** — companion deep-dive: full glossary with named anchors, chronological14 narrative, command-by-command annotations, pitfalls.15- **`meta.json`** — structured metadata for downstream tools (recap-to-notion, etc.).1617Both markdown files are written so the user can re-skim later and recover everything18technical that was encountered. Assume the user will forget — be explicit.1920## Workflow2122### Phase 0 — Cleanup (runs before generation, including in cleanup-only mode)2324Always run this at the start of every invocation. The skill owns the recap manifest and25retention policy. Each `recap-to-X` uploader writes its own entry to the shared26manifest after a successful upload; this skill reads the manifest to decide what is safe27to delete.28291. Read `~/.claude/session-recaps/.recap-config.json` (defaults below if the file30 is missing, or if a given key is absent from it):31 ```json32 {33 "version": 1,34 "chain": [35 {"skill": "recap-to-notion:recap-to-notion", "enabled": true, "notes": ""}36 ],37 "retention_days": 20,38 "retention_minutes_override": null39 }40 ```41 `chain` is a list of `{skill, enabled, notes}` objects, not a flat list of42 names — `enabled: false` lets the user pause a downstream skill without43 removing its config entry. `retention_days` / `retention_minutes_override`44 are read the same way (missing key → default applies); as of this writing45 the live config in this repo's development environment only sets `version`46 and `chain` — retention runs on its default until a config explicitly sets it.472. Read `~/.claude/session-recaps/.manifest.jsonl` (skip if missing).483. Walk `~/.claude/session-recaps/<YYYY-MM-DD>_*` directories.494. For each dir:50 - Has at least one manifest entry whose `local_dir` matches this dir? (target type51 does not matter — any successful upload counts as evidence of preservation).52 - Is the dir's `mtime` older than the threshold? Threshold =53 `retention_minutes_override` (if non-null) else `retention_days * 1440` (minutes).54 - Both yes → `rm -rf` the dir. Otherwise → keep.555. Report deletion count in one line in the UI language.566. Never touch dotfiles (`.recap-config.json`, `.notion-config.json`,57 `.<other>-config.json`, `.manifest.jsonl`) or non-recap directories.5859**Failure handling**:60- A single `rm -rf` failure → log it, skip that dir, continue.61- `.manifest.jsonl` parse error → skip cleanup entirely, notify the user, continue with62 generation and chain dispatch as normal.6364**Cleanup-only mode**: if the user invoked the skill with phrasing like `recap掃除`,65`recap cleanup`, `古いrecap削除`, or with an explicit `--cleanup-only` flag, run Phase 066and return immediately. Do not generate, do not chain.6768### Feedback Check6970If `feedback/log.md` exists alongside this SKILL.md and has 5 or more entries, read the71last 10. If a pattern is apparent (the same issue appears in 3+ entries, or average72rating is below 3):7374- Tell the user (in the UI language): 「過去のフィードバックで類似パターンを検出: [簡潔に]。`/my-skill-factory improve session-recap` で改善できます。」 / English equivalent.75- Continue with normal execution.7677### Step 1: Resolve target session7879Look at the user's invocation message for any of:8081- A bare UUID (8-4-4-4-12 hex) — explicit session ID.82- A path or `--cwd` mention — alternate working directory to scan instead of `$PWD`.83- An `--out <dir>` style argument — output directory override (default84 `~/.claude/session-recaps/`).85- `--language ja` / `--language en` — force the output language.86- `--no-chain` — skip the chain dispatch in Step 6.87- Otherwise → scan the current working directory's project transcript folder.8889#### If a session ID was given9091```bash92find ~/.claude/projects -maxdepth 2 -name "<session-id>.jsonl" 2>/dev/null93```9495If found → use it. If not → tell the user the ID was not seen anywhere under96`~/.claude/projects/` and stop.9798#### If no session ID was given (default)991001. Encode the target cwd: replace every `/`, `.`, and `_` with `-`. Example:101 `/Users/alice/code/foo` → `-Users-alice-code-foo`; a worktree path like102 `/Users/alice/repo/.claude/worktrees/my_branch` →103 `-Users-alice-repo--claude-worktrees-my-branch` (note the doubled `-` where104 `/` is immediately followed by `.`). A single `/`-only substitution misses105 the `.claude/worktrees/...` segment every worktree session lives under —106 this repo's own convention — and any branch name with an underscore.1072. The transcript directory is `~/.claude/projects/<encoded>/`.1083. List the 10 most recent sessions with their slug and last-event timestamp:109110 ```bash111 ENC=$(echo "$PWD" | sed 's|[/._]|-|g')112 DIR=~/.claude/projects/$ENC113 ls -t "$DIR"/*.jsonl 2>/dev/null | head -10 | while read f; do114 id=$(basename "$f" .jsonl)115 last=$(tail -1 "$f" | jq -r '.timestamp // "?"')116 slug=$(grep -m1 -o '"slug":"[^"]*"' "$f" | head -1 | sed 's/"slug":"//;s/"$//')117 msgs=$(wc -l < "$f")118 echo "$id | $last | $slug | $msgs lines"119 done120 ```1211224. Pick:123 - **0 candidates** → tell the user no session was found in this directory; suggest124 passing a session ID or a different cwd.125 - **1 candidate** → use it; show the session ID + slug for confirmation.126 - **2+ candidates** → invoke `AskUserQuestion` with the list (session ID, last127 activity, slug, line count). Let the user pick.128129#### When invoked from session-watch130131`session-watch` passes the session ID directly via the Skill tool's `args`. Use it as132the explicit-ID path above and skip the picker.133134### Step 2: Extract structured events135136Run jq once to project the transcript into a compact event stream. Save to a temp file137so subsequent passes are cheap.138139```bash140TRANSCRIPT=<resolved-jsonl-path>141SHORT=$(basename "$TRANSCRIPT" .jsonl | cut -c1-8)142TMP=/tmp/session-recap-$SHORT.events.jsonl143144jq -c '145 . as $r |146 if $r.type == "user" then147 {kind: "user", ts: $r.timestamp, sc: $r.isSidechain,148 content: ($r.message.content // "")}149 elif $r.type == "assistant" then150 ($r.message.content // [])[]? |151 if .type == "text" then152 {kind: "text", ts: $r.timestamp, sc: $r.isSidechain, text: .text}153 elif .type == "tool_use" then154 {kind: "tool", ts: $r.timestamp, sc: $r.isSidechain,155 name: .name, input: .input}156 else empty end157 else empty end158' "$TRANSCRIPT" > "$TMP"159160wc -l "$TMP"161```162163Also pull session metadata:164165```bash166head -1 "$TRANSCRIPT" | jq '{started_at: .timestamp, cwd, version, slug, gitBranch, sessionId}'167tail -1 "$TRANSCRIPT" | jq '{ended_at: .timestamp}'168grep -c '"type":"assistant"' "$TRANSCRIPT"169grep -c '"type":"user"' "$TRANSCRIPT"170```171172### Step 3: Detect output language173174Unless `--language` was passed (use as-is), determine the output language from the175transcript:1761771. Concatenate all assistant text content (the `kind: "text"` events from `$TMP`).1782. Count Hiragana (U+3040-U+309F), Katakana (U+30A0-U+30FF), and CJK Unified Ideograph179 (U+4E00-U+9FFF) characters as "Japanese".1803. Count ASCII letters (a-z, A-Z) as "English".1814. If `Japanese / (Japanese + English) >= 0.5` → output language = `ja`.182 Otherwise → `en`.1835. If both counts are zero (or transcript is empty) → fall back to `en`.184185The detected language is stored in `meta.json` and used to drive Step 4 templates.186187### Step 4: Long-transcript handling188189If `$TMP` has more than 2000 lines, do not load it all into context at once:1901911. Read in chunks (e.g., 500 events at a time, with `sed -n 'A,Bp'` or `head/tail`).1922. For each chunk extract: tool calls, decisions ("let's do X / instead of Y"), errors,193 file edits, and a 1-2 sentence narrative summary.1943. Combine chunk summaries into the final synthesis in Step 5.195196For shorter transcripts, read the whole `$TMP` and proceed directly.197198### Step 5: Synthesize the artifacts199200Produce three files. The two markdown files are in the language detected in Step 3.201202#### Output language note203204- Quoted commands, file paths, code blocks, and proper nouns stay verbatim regardless of205 language.206- Section headings, prose, term definitions, decisions, narratives are in the chosen207 language.208- The Japanese template below is shown as the primary example; for English output, use209 obvious analogues (e.g. "TL;DR" stays, "達成したこと" → "What got done", "用語集"210 → "Glossary", "コマンド逐次解説" → "Commands annotated", "つまずきと解決" →211 "Pitfalls", etc.).212213#### Cross-file anchor link convention214215The two files must remain navigable in any standard markdown viewer (GitHub, VS Code,216mdcat, etc.) even if Notion sync is unavailable.217218- In `summary.md`, every key term and key command links to its full definition in219 `note.md` using a **cross-file anchor**: `[label](./note.md#anchor-id)`.220- Anchor IDs use kebab-case lowercase. Conventions:221 - Term: `#<term-slug>` (e.g. `#derivation`, `#flake`)222 - Command: `#cmd-<short-label>` (e.g. `#cmd-nix-build`, `#cmd-git-add-a`)223 - Pitfall: `#pitfall-<short-label>`224 - Phase: `#phase-<n>-<short-label>`225- In `note.md`, **a heading must always carry an explicit `{#kebab-id}` whenever its226 rendered text contains anything other than `[a-z0-9-]`** — that is, dots,227 underscores, slashes, spaces with mixed case, backticks, parentheses, dollar signs,228 uppercase letters, etc. Standard markdown sluggers (GitHub, VS Code, mdBook, Pandoc)229 disagree on how to slugify these, so without an explicit ID the cross-file links in230 `summary.md` will silently break. Examples:231 - `### derivation` — plain alphanumeric, auto-slug `derivation` is reliable, no232 `{#…}` needed.233 - `### home.sessionPath {#home-sessionpath}` — has a dot AND mixed case, must234 declare ID explicitly.235 - `### system.defaults.finder {#system-defaults-finder}` — has dots, must declare.236 - `### \`hm-session-vars.sh\` {#hm-session-vars-sh}` — has backticks and a dot.237 - `### \`nix build <flake>#<output>\` {#cmd-nix-build}` — has angle brackets and `#`.238 When in doubt, add the `{#…}` — it never hurts and always makes the link resolve in239 every renderer.240241When recap-to-notion later merges these into a single Notion page, it rewrites the242cross-file `(./note.md#x)` form into same-page `(#x)` form. The local files keep243working independently.244245#### `summary.md` template (Japanese variant — adapt to English when language=en)246247```markdown248# Session Recap: <slug or short-id> — <YYYY-MM-DD>249250- **Session ID**: `<full-id>`251- **期間**: `<started_at>` → `<ended_at>` (`<elapsed>`)252- **cwd**: `<cwd>`253- **イベント総数**: <N> (assistant text: <a>, user: <u>, tool calls: <t>, sidechain: <s>)254255## TL;DR256<1-3行で全体を要約>257258## 達成したこと2591. <フェーズまたは主要タスク 1>2602. <フェーズまたは主要タスク 2>261262## 主な用語 (チートシート)263| 用語 | 一行定義 |264|------|----------|265| [`derivation`](./note.md#derivation) | <gloss> |266| [`flake`](./note.md#flake) | <gloss> |267268## 触ったファイル269| パス | 操作 | 一言メモ |270|------|------|----------|271| `<path>` | created / edited / deleted | <note> |272273## 注目コマンド274| コマンド | 目的 | 結果 |275|----------|------|------|276| [`nix build ...`](./note.md#cmd-nix-build) | <purpose> | <outcome> |277278## 決定事項279- **<decision>**: <reasoning>280281## 未解決 / 次にやること282- [ ] <item>283```284285#### `note.md` template286287```markdown288# 詳細ノート: <slug> — <YYYY-MM-DD>289290`summary.md` の補完。291292## コンテキスト293<2-3段落>294295## 用語集296297### derivation298<2-4行の定義>299300**このsessionでの登場文脈**: <文脈>301302**関連**: <他の用語>303304---305306### flake307...308309## 時系列の流れ310311### Phase 1: <名前> {#phase-1-<short-label>}312<narrative>313314## コマンド逐次解説315316### `nix build <flake>#<output>` {#cmd-nix-build}317\`\`\`bash318<full command>319\`\`\`320- <flag/構文 1>: <意味>321- <flag/構文 2>: <意味>322323**やったこと**: <出力 / 副作用>324**学び**: <一般化>325326---327328## つまずきと解決329330### <pitfall> {#pitfall-<short-label>}331- **症状**: <観測>332- **原因**: <ルートコーズ>333- **対処**: <修正>334- **教訓**: <次回に持っていくもの>335336## 参照337- <URL 1>338```339340#### `meta.json` schema (machine-readable)341342Write this alongside the two markdown files. Downstream skills (recap-to-notion etc.)343read it instead of re-parsing markdown.344345```json346{347 "session_id": "<uuid>",348 "short_id": "<8 chars>",349 "slug": "<slug or empty>",350 "cwd": "<absolute path>",351 "language": "ja",352 "started_at": "<ISO-8601 with time and timezone>",353 "ended_at": "<ISO-8601 with time and timezone>",354 "duration_seconds": 1234,355 "total_events": 290,356 "events": {"user": 12, "assistant_text": 45, "tool": 200, "sidechain": 33},357 "generator_version": "1.2.0"358}359```360361#### Synthesis rules362363- **Output language**: matches the result of Step 3 (or `--language` override).364- **Term cross-references**: every term that appears in `summary.md`'s 用語表 must have365 a corresponding `### <term>` heading in `note.md` and link to it via366 `[term](./note.md#term-slug)`. Same for commands.367- **Command breakdown**: in `note.md`, decompose flags, redirections, heredocs, pipes368 one by one. In `summary.md` keep entries one-line.369- **Side effects / destructive ops**: push, kill, rm, sudo, network calls, deploys are370 surfaced explicitly (e.g. "副作用: GitHubへのpush" / "side effect: git push to GitHub").371- **Truncation**: when transcript content is truncated, note "省略あり" / "truncated"372 and avoid claiming certainty about content past the cut.373- **Sidechain**: sub-agent activity goes into note.md timeline; surface results, not374 inner monologue.375- **Failure → fix patterns**: pitfalls are the highest-value content. Always record376 symptom → root cause → fix → lesson.377- **Never invent**: only describe tool calls / decisions actually present in the378 transcript.379380### Step 5b: Write files and report381382```bash383OUT_BASE=${OUT_DIR:-~/.claude/session-recaps}384DATE=$(date -u +%Y-%m-%d)385SLUG=<extracted slug, or "session" if missing>386DIR="$OUT_BASE/${DATE}_${SHORT}_${SLUG}"387mkdir -p "$DIR"388# Use the Write tool to create:389# $DIR/summary.md390# $DIR/note.md391# $DIR/meta.json392```393394Then tell the user (in UI language):395- Output directory path396- File names + approximate sizes397- A 2-3 line preview of the TL;DR398- One-line invitation to ask follow-ups about specific terms or phases399400### Step 6 — Chain dispatch401402After local files are written, dispatch any user-configured follow-up skills.4034041. Read `~/.claude/session-recaps/.recap-config.json` (default chain — a single405 `{"skill": "recap-to-notion:recap-to-notion", "enabled": true}` entry — if the406 file or the `chain` key is missing).4072. Determine if the chain should be skipped entirely:408 - `--no-chain` argument was passed → skip.409 - The user's recent invocation contains any of these opt-out phrases → skip:410 `アップロードしないで`, `Notionいらない`, `ローカルだけ`,411 `no upload`, `skip notion`, `local only`, `no chain`.412 - `chain` is an empty list `[]` → skip (no-op).4133. Otherwise, for each entry in `chain`, in order:414 - `enabled: false` → skip this entry silently, continue to the next.415 - Verify the entry's `skill` is installed (skill list lookup). If not, emit a416 one-line skip note in UI language and continue with the next.417 - Invoke `Skill(skill: "<entry.skill>", args: "<absolute path to recap dir>")`.418 - Surface the returned one-line status in UI language.4194. The chain runs sequentially. A failure in one chain entry does not stop the420 others (they are independent).421422This skill never references `recap-to-notion` (or any other uploader) by name in the423code path — only the entries listed in the user's `.recap-config.json`. Adding a new424uploader is a config change, not a source change.425426### Retrospective427428After Step 6 returns:4294301. Consider: were there mid-session corrections (rewrote sections, expanded glossary,431 user pointed out missed terms)? Did chain dispatch fail anywhere? Did jq fail on432 any line?4332. Ask the user (in UI language): 「今回のrecapのフィードバック (1-5の評価、抜け落ちた点、または何もなければEnter)」 / English equivalent.4343. If feedback OR corrections occurred:435 a. Create `feedback/` next to this SKILL.md if missing.436 b. Read or create `feedback/log.md` with the standard header.437 c. Prepend a new entry:438439 ```markdown440 ## <ISO-8601 timestamp>441 - **Skill Version**: <version from this file's frontmatter>442 - **Task**: <1-line description>443 - **Outcome**: success | partial-success | failure | error444 - **Rating**: <N>/5 (or "—")445 - **Corrections**: <session corrections, or "none">446 - **Issues**: <issues, or "none">447 - **User Note**: <verbatim, or "—">448 ---449 ```4504514. Skip recording if the user passes AND no corrections/issues occurred.452453## Behavior Scenarios454455```gherkin456Scenario: Standalone — single recent session in cwd457 Given the user invokes session-recap with no arguments458 And exactly one session in cwd has prior activity459 When Step 1 runs460 Then the skill picks that session, generates summary.md / note.md / meta.json461 under ~/.claude/session-recaps/<date>_<short>_<slug>/, and dispatches the462 configured chain.463464Scenario: Standalone — explicit session ID465 Given the user passes a UUID in the message466 When Step 1 runs467 Then the skill locates the JSONL anywhere under ~/.claude/projects/ regardless468 of cwd, and produces the recap from it.469470Scenario: Standalone — multiple recent sessions in cwd471 Given two or more sessions exist in the cwd's project transcript dir472 When Step 1 runs473 Then the skill presents the top 10 by recency via AskUserQuestion and recaps474 the chosen one.475476Scenario: Auto-invoked from session-watch477 Given session-watch has just stopped after watching session <X>478 When session-watch invokes session-recap with <X> as the session ID479 Then this skill generates the recap and dispatches the configured chain.480481Scenario: Output language matches session — Japanese >= 50%482 Given the watched session's assistant text is dominantly Japanese483 When Step 3 runs484 Then language is set to "ja" and the artifacts are produced in Japanese.485486Scenario: Output language defaults to English when Japanese < 50%487 Given the watched session is mostly English488 When Step 3 runs489 Then language is set to "en" and the artifacts are produced in English.490491Scenario: --language argument overrides automatic detection492 Given the user passes --language ja against an English session493 Then artifacts are produced in Japanese regardless of the detected ratio.494495Scenario: Local cross-file anchors work in standard markdown viewers496 Given a recap was generated and Notion sync was skipped or failed497 When the user opens summary.md in VS Code or GitHub498 Then clicking a term link navigates to the corresponding heading in note.md.499500Scenario: Chain dispatch — default chain runs recap-to-notion501 Given .recap-config.json is missing or chain is the default502 When Step 6 runs503 Then session-recap invokes recap-to-notion with the recap dir absolute path504 and surfaces its one-line status.505506Scenario: Chain dispatch — skipped when user says "no upload" or similar507 Given the user's invocation contains an opt-out phrase508 When Step 6 evaluates skip conditions509 Then the chain is fully skipped.510511Scenario: Chain dispatch — skipped via --no-chain argument512 Given --no-chain was passed513 When Step 6 runs514 Then the chain is fully skipped.515516Scenario: Chain dispatch — empty chain in .recap-config.json runs no follow-up517 Given chain is []518 When Step 6 runs519 Then no chain skills are invoked.520521Scenario: Chain dispatch — a disabled entry is skipped without stopping the chain522 Given chain has an entry with enabled: false followed by an enabled: true entry523 When Step 6 runs524 Then the disabled entry is skipped silently and the next entry still runs.525526Scenario: Chain dispatch — chained skill not installed → log and continue527 Given the chain references a skill that is not installed528 When Step 6 attempts to invoke it529 Then the skill emits a one-line skip note and proceeds to the next chain entry.530531Scenario: Cleanup — confirmed in manifest + past retention → deleted532 Given a recap dir has at least one manifest entry533 And its mtime is older than the retention threshold534 When Phase 0 runs535 Then the dir is deleted and the count is reported.536537Scenario: Cleanup — confirmed in manifest + within retention → kept538 Given a recap dir has at least one manifest entry539 And its mtime is within the retention threshold540 When Phase 0 runs541 Then the dir is kept.542543Scenario: Cleanup — no manifest entry + past retention → kept544 Given a recap dir has no manifest entry of any target545 When Phase 0 runs546 Then the dir is kept regardless of age.547548Scenario: Cleanup-only mode — invoked via "recap cleanup" runs Phase 0 only549 Given the user invokes the skill with "recap cleanup" or similar550 When the skill starts551 Then it runs Phase 0 and exits without generating or chaining.552553Scenario: Cleanup never touches dotfiles or non-recap directories554 Given .recap-config.json, .notion-config.json, .manifest.jsonl exist alongside dirs555 When Phase 0 runs556 Then only directories matching <YYYY-MM-DD>_* are considered for deletion.557558Scenario: Long transcript559 Given the resolved transcript has more than 2000 events560 When Step 4 runs561 Then chunked synthesis is used.562563Scenario: No usable transcript564 Given the resolved JSONL has fewer than 5 events565 When Step 5 runs566 Then a minimal summary.md is produced and note.md is skipped.567568Scenario: Output directory override569 Given the user passes --out ~/notes/sessions570 When Step 5b runs571 Then artifacts are written under ~/notes/sessions/<date>_<short>_<slug>/.572573Scenario: Retrospective on a clean run574 Given no corrections, no issues, and no user feedback575 When the Retrospective runs576 Then nothing is written to feedback/log.md.577578Scenario: Feedback Check surfaces a pattern579 Given feedback/log.md has 5+ entries with a recurring issue keyword580 When the skill is invoked581 Then it surfaces the pattern in UI language and suggests582 /my-skill-factory improve session-recap.583```584585## Notes and constraints586587- **Read-only on the watched session.** Do not write back into the transcript or588 message the watched session.589- **Default output dir**: `~/.claude/session-recaps/`.590- **Naming**: `<YYYY-MM-DD>_<short-id>_<slug>/` (slug omitted if not present).591 `<short-id>` is the first 8 chars of the session UUID.592- **No invention**: never describe a tool call or decision that is not present in the593 transcript. When in doubt, say so explicitly.594- **Truncated inputs**: transcript fields can be truncated; mark them.595- **Sidechain depth**: sub-agent activity is captured but not exhaustively expanded.596- **Idempotent rewrites**: invoking again on the same session ID overwrites the597 previous artifacts in the same directory (no duplicates).598- **Manifest is shared**: each `recap-to-X` uploader appends an entry on success. This599 skill defines the schema and reads the file for cleanup; uploaders only append.600- **Manifest schema** (`~/.claude/session-recaps/.manifest.jsonl`, append-only):601 ```jsonl602 {"target": "<name>", "session_id": "<uuid>", "local_dir": "<absolute path>", "target_id": "<id>", "target_url": "<url>", "uploaded_at": "<ISO-8601 with time>", "status": "created"|"updated"}603 ```604- **Cleanup policy**: a recap dir is deletable if it has at least one manifest entry605 (regardless of target) AND mtime exceeds the retention threshold. This is intentional606 — we trust that any successful upload preserves the data.607- **OCP**: adding a new uploader (e.g. `recap-to-confluence`) requires no edits to this608 skill's source. The user adds a new `{skill, enabled}` entry to `chain` in their609 `.recap-config.json`.