mirror of
https://github.com/SeriousBug/dotfiles
synced 2026-07-27 12:35:37 -05:00
cc-compact: auto-select latest session when no selector given
Default to the most recently active session in the current project, excluding the caller's own session (found via CLAUDE_CODE_SESSION_ID). Lets you run /clear then cc-compact to reload the just-cleared session, emulating Claude Code's built-in /compact.
This commit is contained in:
parent
705e914ea0
commit
876ac92432
|
|
@ -16,8 +16,13 @@ The helper script lives at `~/.claude/skills/cc-compact/compact_session.py`.
|
||||||
|
|
||||||
## Step 1: Resolve the session
|
## Step 1: Resolve the session
|
||||||
|
|
||||||
The argument is one of two forms:
|
The argument is one of three forms:
|
||||||
|
|
||||||
|
- **Nothing** — no id or title given. This is the common case: the user ran
|
||||||
|
`/clear` and then `cc-compact` to reload the session they just cleared. Run
|
||||||
|
the script with no selector; it auto-picks the most recently active session
|
||||||
|
in the current project, **excluding this session** (the fresh one `/clear`
|
||||||
|
created). This emulates Claude Code's built-in `/compact`.
|
||||||
- **A session id** — when invoked like `/resume claude --resume <id>` or
|
- **A session id** — when invoked like `/resume claude --resume <id>` or
|
||||||
`cc-compact <id>`, the user already gave you the UUID. Pass it as `--id`.
|
`cc-compact <id>`, the user already gave you the UUID. Pass it as `--id`.
|
||||||
- **A session name / title** — free text. Pass it as `--title`; the script
|
- **A session name / title** — free text. Pass it as `--title`; the script
|
||||||
|
|
@ -27,6 +32,9 @@ The argument is one of two forms:
|
||||||
Run the script exactly once with the matching form:
|
Run the script exactly once with the matching form:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
|
# Latest session before the /clear (default, no selector):
|
||||||
|
python3 ~/.claude/skills/cc-compact/compact_session.py
|
||||||
|
|
||||||
# By id:
|
# By id:
|
||||||
python3 ~/.claude/skills/cc-compact/compact_session.py --id <session-uuid>
|
python3 ~/.claude/skills/cc-compact/compact_session.py --id <session-uuid>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,10 @@ Session resolution (pick one):
|
||||||
--id UUID find <UUID>.jsonl under the projects dir
|
--id UUID find <UUID>.jsonl under the projects dir
|
||||||
--title TEXT find the session whose ai-title contains TEXT
|
--title TEXT find the session whose ai-title contains TEXT
|
||||||
(case-insensitive substring; newest match wins)
|
(case-insensitive substring; newest match wins)
|
||||||
|
--latest the most recently active session in the current project,
|
||||||
|
excluding the caller's own session — this is the default
|
||||||
|
when no selector is given, so `cc-compact` right after
|
||||||
|
`/clear` picks up the session you just cleared
|
||||||
|
|
||||||
Sessions live at: ~/.claude/projects/<encoded-cwd>/<session-id>.jsonl
|
Sessions live at: ~/.claude/projects/<encoded-cwd>/<session-id>.jsonl
|
||||||
"""
|
"""
|
||||||
|
|
@ -27,6 +31,7 @@ import glob
|
||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
|
import re
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
PROJECTS_DIR = os.path.expanduser("~/.claude/projects")
|
PROJECTS_DIR = os.path.expanduser("~/.claude/projects")
|
||||||
|
|
@ -84,7 +89,45 @@ def is_genuine_prompt(rec):
|
||||||
return not s.startswith(skip_prefixes)
|
return not s.startswith(skip_prefixes)
|
||||||
|
|
||||||
|
|
||||||
|
def current_session():
|
||||||
|
"""(project_dir, session_id) for the session invoking this script.
|
||||||
|
|
||||||
|
Claude Code exports CLAUDE_CODE_SESSION_ID; its log lives at
|
||||||
|
<project_dir>/<id>.jsonl. Fall back to the encoded cwd when the env var is
|
||||||
|
missing (script run by hand outside a session). Either value may be None."""
|
||||||
|
sid = os.environ.get("CLAUDE_CODE_SESSION_ID")
|
||||||
|
if sid:
|
||||||
|
matches = glob.glob(os.path.join(PROJECTS_DIR, "**", f"{sid}.jsonl"), recursive=True)
|
||||||
|
if matches:
|
||||||
|
return os.path.dirname(matches[0]), sid
|
||||||
|
# Claude Code encodes the cwd into the project dir name by replacing every
|
||||||
|
# non-alphanumeric char with a dash (/Users/kaan/Code/Veery -> -Users-kaan-Code-Veery).
|
||||||
|
encoded = re.sub(r"[^A-Za-z0-9]", "-", os.getcwd())
|
||||||
|
cand = os.path.join(PROJECTS_DIR, encoded)
|
||||||
|
return (cand if os.path.isdir(cand) else None), sid
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_latest():
|
||||||
|
"""Newest session log in the current project, excluding the caller's own
|
||||||
|
session, so compacting right after `/clear` lands on the just-cleared one."""
|
||||||
|
project_dir, sid = current_session()
|
||||||
|
if project_dir:
|
||||||
|
pool = glob.glob(os.path.join(project_dir, "*.jsonl"))
|
||||||
|
else:
|
||||||
|
pool = glob.glob(os.path.join(PROJECTS_DIR, "**", "*.jsonl"), recursive=True)
|
||||||
|
if sid:
|
||||||
|
pool = [p for p in pool if os.path.basename(p) != f"{sid}.jsonl"]
|
||||||
|
if not pool:
|
||||||
|
sys.exit("No previous session found to compact in this project")
|
||||||
|
latest = max(pool, key=os.path.getmtime)
|
||||||
|
sys.stderr.write(f"Auto-selected latest session: {latest}\n")
|
||||||
|
return latest
|
||||||
|
|
||||||
|
|
||||||
def resolve_file(args):
|
def resolve_file(args):
|
||||||
|
if args.latest:
|
||||||
|
return resolve_latest()
|
||||||
|
|
||||||
if args.file:
|
if args.file:
|
||||||
return os.path.expanduser(args.file)
|
return os.path.expanduser(args.file)
|
||||||
|
|
||||||
|
|
@ -131,10 +174,13 @@ def resolve_file(args):
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
||||||
g = ap.add_mutually_exclusive_group(required=True)
|
g = ap.add_mutually_exclusive_group()
|
||||||
g.add_argument("--file", help="path to a session .jsonl")
|
g.add_argument("--file", help="path to a session .jsonl")
|
||||||
g.add_argument("--id", help="session UUID")
|
g.add_argument("--id", help="session UUID")
|
||||||
g.add_argument("--title", help="substring of the session's ai-title")
|
g.add_argument("--title", help="substring of the session's ai-title")
|
||||||
|
g.add_argument("--latest", action="store_true",
|
||||||
|
help="newest session in the current project, excluding the caller's own "
|
||||||
|
"(the default when no selector is given)")
|
||||||
ap.add_argument("--first", type=int, default=5, help="how many opening exchanges")
|
ap.add_argument("--first", type=int, default=5, help="how many opening exchanges")
|
||||||
ap.add_argument("--last", type=int, default=10, help="how many closing exchanges")
|
ap.add_argument("--last", type=int, default=10, help="how many closing exchanges")
|
||||||
ap.add_argument("--middle", type=int, default=5, help="how many exchanges randomly sampled from the middle")
|
ap.add_argument("--middle", type=int, default=5, help="how many exchanges randomly sampled from the middle")
|
||||||
|
|
@ -142,6 +188,8 @@ def main():
|
||||||
ap.add_argument("--maxlen", type=int, default=800, help="max chars per quoted message")
|
ap.add_argument("--maxlen", type=int, default=800, help="max chars per quoted message")
|
||||||
ap.add_argument("--seed", type=int, default=1, help="seed for the middle random sample")
|
ap.add_argument("--seed", type=int, default=1, help="seed for the middle random sample")
|
||||||
args = ap.parse_args()
|
args = ap.parse_args()
|
||||||
|
if not (args.file or args.id or args.title):
|
||||||
|
args.latest = True
|
||||||
|
|
||||||
path = resolve_file(args)
|
path = resolve_file(args)
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue