Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "prep-compact",
"description": "When your Claude Code session's real token count crosses a configurable threshold (default 450K), an informational reminder names the warm handoff file. A Stop hook continuously maintains an on-disk handoff JSON (cumulative file paths, in-progress todos, active subagent launches, recent user-message quotes). The /prep-compact skill reads this warm handoff and adds an analytical layer (decisions, constraints, blockers, verb-anchored next-step) to emit a tailored /compact <instructions> block.",
"version": "3.0.1",
"version": "3.0.2",
"author": {
"name": "Koen van der Heide",
"url": "https://github.com/koenvdheide"
Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ All notable changes to prep-compact will be documented in this file.

The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [3.0.2] - 2026-06-05

### Fixed

- The Stop hook no longer records grep/glob result lines that carry shell code (e.g. a context line `SKILL="$DIR/x.sh"`) as file paths in the handoff. Only clean path tokens are captured.

## [3.0.1] - 2026-06-05

### Fixed
Expand Down
8 changes: 8 additions & 0 deletions hooks/update-handoff.sh
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,12 @@ for entry in reversed(parsed):
# shaped lines. Walk newest-first parity with Tier-A; extract path TOKEN (not
# whole line) so "src/foo.ts:12:match" becomes "src/foo.ts"; handle Windows drive letters.

# A resolved path token never carries quote/$/backtick/redirect/glob punctuation.
# Without this, grep CONTEXT lines like '357-SKILL="$DIR/skills/x.sh"' slip through
# as "paths". '=' and "'" are intentionally NOT rejected -- they occur in real
# filenames (a=b.json, John's-note.md) and the set below still catches the junk.
_PATH_TOKEN_JUNK = set('"' + '`' + '$<>|*?')

def first_path_token(line):
if not line:
return None
Expand All @@ -209,6 +215,8 @@ def first_path_token(line):
last_sep = max(candidate.rfind('/'), candidate.rfind('\\'))
tail = candidate[last_sep+1:]
if '.' in tail and len(tail) > 1:
if any(ch in _PATH_TOKEN_JUNK for ch in candidate):
return None
Comment on lines +218 to +219

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve valid path punctuation in grep results

When a Glob/Grep result contains a real filename with characters such as = or ' (for example fixtures/a=b.json or docs/John's-note.md), this new blanket check drops the candidate even though those are valid path characters on normal filesystems. That regresses Tier-B handoff coverage for repos with generated/config files that commonly include = in names; the shell-junk filter should target assignment/code-shaped lines without rejecting all legitimate paths containing these characters.

Useful? React with 👍 / 👎.

return candidate
return None

Expand Down
26 changes: 24 additions & 2 deletions test/run-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ fi
# After T6: EXPECTED_PASS=87, SKIPPED=39 (T6 tests not Stop-dep)
# After T7: EXPECTED_PASS=88, SKIPPED=39 (T7 test not Stop-dep)
# PR-comment fix: +3 Stop-dep (T-32cap +1 short-still-captured, T-32prior +2)
EXPECTED_PASS=127
EXPECTED_PASS=130
SKIPPED=0

run_hook() {
Expand Down Expand Up @@ -731,8 +731,30 @@ HANDOFF=$(to_native "$CACHE/handoff-s55.json")
assert_eq "T-55: prior real request kept" "yes" "$("$PY" -c "import json;d=json.load(open('$HANDOFF'));print('yes' if any('real prior intent' in q for q in d['recent_user_requests']) else 'no')")"
assert_eq "T-55: prior injected purged" "no" "$("$PY" -c "import json;d=json.load(open('$HANDOFF'));print('yes' if any('task-notification' in q for q in d['recent_user_requests']) else 'no')")"

# T-56: Tier-B grep-result extraction rejects shell/code lines. Regression: a grep
# CONTEXT line "357-SKILL=\"\$DIR/../skills/prep-compact/SKILL.md\"" was captured as a path.
cleanup
JUNK_LINE='357-SKILL="$SCRIPT_DIR/../skills/prep-compact/SKILL.md"' \
REAL_LINE='src/real/keep.ts:42:SKILL' \
LEGIT_LINE='fixtures/a=b.json:1:match' \
FIX_ENV="$FIX" "$PY" -c "
import json, os
content = os.environ['JUNK_LINE'] + '\n' + os.environ['REAL_LINE'] + '\n' + os.environ['LEGIT_LINE'] + '\n'
turns = [
{'message':{'role':'assistant','content':[{'type':'tool_use','id':'gj1','name':'Grep','input':{'pattern':'SKILL','output_mode':'content'}}],'usage':{'input_tokens':100,'cache_creation_input_tokens':1000,'cache_read_input_tokens':0}}},
{'message':{'role':'user','content':[{'type':'tool_result','tool_use_id':'gj1','content':content}]}},
]
with open(os.environ['FIX_ENV']+'/t56.jsonl','w') as f:
for t in turns: f.write(json.dumps(t)+'\n')
"
run_stop_hook '{"session_id":"s56","transcript_path":"'"$FIX/t56.jsonl"'","cwd":"/sample","permission_mode":"default","hook_event_name":"Stop"}' >/dev/null
HANDOFF=$(to_native "$CACHE/handoff-s56.json")
assert_eq "T-56: real grep path kept" "yes" "$("$PY" -c "import json;d=json.load(open('$HANDOFF'));print('yes' if any('src/real/keep.ts' in p for p in d['recent_files']) else 'no')")"
assert_eq "T-56: legit =-char path kept" "yes" "$("$PY" -c "import json;d=json.load(open('$HANDOFF'));print('yes' if any('a=b.json' in p for p in d['recent_files']) else 'no')")"
assert_eq "T-56: shell-junk line rejected" "no" "$("$PY" -c "import json;d=json.load(open('$HANDOFF'));print('yes' if any(('SCRIPT_DIR' in p) or ('SKILL=' in p) or ('prep-compact/SKILL.md' in p) for p in d['recent_files']) else 'no')")"

else
SKIPPED=56
SKIPPED=59
fi # STOP_FIXTURE_OK

# ===================================================================
Expand Down
Loading