Compare commits

...
4 Commits
Author SHA1 Message Date
OthmanAdiandClaude Sonnet 4.5 8ee4328ef8 Hotfix: Restore skill activation language lost in v2.2.1 merge
- Restore full description in SKILL.md files
- Description now includes activation context
- Fixes issue where skill purpose was unclear

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-17 22:44:18 +01:00
OthmanAdi ce59730881 Release v2.2.1: Session recovery after /clear
Merging feature/session-catchup-recovery from @lasmarois (PR #33)

This fast-follow release adds automatic session recovery for seamless
work continuation after running /clear when context fills up.

New Features:
- Session recovery via session-catchup.py script
- Analyzes previous session JSONL files in ~/.claude/projects/
- Finds last planning file update
- Extracts unsynced conversation after that point
- Automatic recovery when invoking /planning-with-files

Enhanced Hooks:
- PreToolUse now includes Read/Glob/Grep operations
- Keeps task_plan.md in attention during research phases
- Better context management throughout workflow

Files Added:
- scripts/session-catchup.py (all 3 locations)
- Session recovery instructions in SKILL.md
- README session recovery documentation

Version Updates:
- Version bumped to 2.2.1 in all manifests
- Updated CHANGELOG.md with v2.2.1 release notes
- Added @lasmarois to CONTRIBUTORS.md

Conflicts resolved:
- CHANGELOG: Combined v2.2.1 and v2.2.0 entries
- README: Added session recovery to v2.2.1, kept v2.2.0 features
- SKILL.md: Merged session recovery with existing hooks

Contributors:
- @lasmarois (session recovery implementation)
- Ahmad Othman Ammar Adi (conflict resolution, version management)
2026-01-17 21:49:15 +01:00
OthmanAdi 74d37d96ff Add devis to Community Forks section
Recognition of @st01cs's devis project which adds:
- Interview-first workflow before implementation
- /devis:intv and /devis:impl commands for guaranteed activation
- Extends planning-with-files with structured requirement gathering

This addresses the community contribution mentioned in issue #28.
2026-01-17 21:44:20 +01:00
nicolasandClaude Opus 4.5 69ded714b7 feat: Add session recovery after /clear (v2.2.0)
Automatically detect and recover unsynced work from previous sessions
when context fills up and user runs /clear.

New features:
- session-catchup.py: Analyzes previous session JSONL to find unsynced context
- Recovery triggered automatically when invoking /planning-with-files
- PreToolUse hook now includes Read/Glob/Grep for better attention management

Optimal workflow:
1. Disable auto-compact in Claude Code settings
2. Start fresh session
3. Run /planning-with-files when ready for complex task
4. Work until context fills up
5. Run /clear
6. Run /planning-with-files again — automatically recovers where you left off

How recovery works:
- SKILL.md instructs Claude to first run session-catchup.py
- Script checks ~/.claude/projects/ for previous session JSONL
- Finds last planning file update, extracts conversation after that point
- Claude updates planning files based on catchup + git diff

Changes:
- SKILL.md: Added session recovery section, preserved all original content
- README: Added optimal workflow and setup instructions
- Removed SessionStart hook (doesn't work per Issue #24)
- Version bumped to 2.2.0

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-17 15:28:08 -05:00
9 changed files with 781 additions and 44 deletions
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "planning-with-files",
"version": "2.2.0",
"version": "2.2.2",
"description": "Manus-style persistent markdown files for planning, progress tracking, and knowledge storage. Includes hooks for automatic plan re-reading and completion verification. Use when starting complex tasks, multi-step projects, research tasks, or when you want structured output.",
"author": {
"name": "OthmanAdi",
+56 -5
View File
@@ -2,6 +2,62 @@
All notable changes to this project will be documented in this file.
## [2.2.2] - 2026-01-17
### Fixed
- **Restored Skill Activation Language** (PR #34)
- Restored the activation trigger in SKILL.md description
- Description now includes: "Use when starting complex multi-step tasks, research projects, or any task requiring >5 tool calls"
- This language was accidentally removed during the v2.2.1 merge
- Helps Claude auto-activate the skill when detecting appropriate tasks
### Changed
- Updated version to 2.2.2 in all SKILL.md files and plugin.json
### Thanks
- Community members for catching this issue
---
## [2.2.1] - 2026-01-17
### Added
- **Session Recovery Feature** (PR #33 by @lasmarois)
- Automatically detect and recover unsynced work from previous sessions after `/clear`
- New `scripts/session-catchup.py` analyzes previous session JSONL files
- Finds last planning file update and extracts conversation that happened after
- Recovery triggered automatically when invoking `/planning-with-files`
- Pure Python stdlib implementation, no external dependencies
- **PreToolUse Hook Enhancement**
- Now triggers on Read/Glob/Grep in addition to Write/Edit/Bash
- Keeps task_plan.md in attention during research/exploration phases
- Better context management throughout workflow
### Changed
- SKILL.md restructured with session recovery as first instruction
- Description updated to mention session recovery feature
- README updated with session recovery workflow and instructions
### Documentation
- Added "Session Recovery" section to README
- Documented optimal workflow for context window management
- Instructions for disabling auto-compact in Claude Code settings
### Thanks
Special thanks to:
- @lasmarois for session recovery implementation (PR #33)
- Community members for testing and feedback
---
## [2.2.0] - 2026-01-17
### Added
@@ -76,11 +132,6 @@ Special thanks to:
- @mtuwei for reporting Windows compatibility issues (#32)
- All community members who tested and provided feedback
## [2.1.2] - 2026-01-11
### Fixed
- **Template Files Not Found in Cache** (Fixes #18)
- Root cause: `${CLAUDE_PLUGIN_ROOT}` resolves to repo root, but templates were only in subfolders
- Added `templates/` and `scripts/` directories at repo root level
- Now templates are accessible regardless of how `CLAUDE_PLUGIN_ROOT` resolves
+7
View File
@@ -25,6 +25,13 @@ These amazing people have contributed code, documentation, or significant improv
- Created workflow diagram showing task lifecycle
- **Impact:** Dramatically improved beginner onboarding
- **[@lasmarois](https://github.com/lasmarois)** - [PR #33](https://github.com/OthmanAdi/planning-with-files/pull/33)
- Created session recovery feature for context preservation after `/clear`
- Built `session-catchup.py` script to analyze previous session JSONL files
- Enhanced PreToolUse hook to include Read/Glob/Grep operations
- Restructured SKILL.md for better session recovery workflow
- **Impact:** Solves context loss problem, enables seamless work resumption
- **[@aimasteracc](https://github.com/aimasteracc)** - [PR #30](https://github.com/OthmanAdi/planning-with-files/pull/30)
- Added Kilocode IDE support and documentation
- Created PowerShell scripts for Windows compatibility
+45 -4
View File
@@ -16,7 +16,7 @@ A Claude Code plugin that transforms your workflow to use persistent markdown fi
[![Claude Code Plugin](https://img.shields.io/badge/Claude%20Code-Plugin-blue)](https://code.claude.com/docs/en/plugins)
[![Claude Code Skill](https://img.shields.io/badge/Claude%20Code-Skill-green)](https://code.claude.com/docs/en/skills)
[![Cursor Rules](https://img.shields.io/badge/Cursor-Rules-purple)](https://docs.cursor.com/context/rules-for-ai)
[![Version](https://img.shields.io/badge/version-2.2.0-brightgreen)](https://github.com/OthmanAdi/planning-with-files/releases)
[![Version](https://img.shields.io/badge/version-2.2.2-brightgreen)](https://github.com/OthmanAdi/planning-with-files/releases)
## Quick Install
@@ -43,10 +43,11 @@ See [docs/installation.md](docs/installation.md) for all installation methods.
| Version | Features | Install |
|---------|----------|---------|
| **v2.2.0** (current) | Kilo Code IDE support, Windows PowerShell support, OS-aware hooks | `/plugin install planning-with-files@planning-with-files` |
| **v2.2.2** (current) | Restored skill activation language | `/plugin install planning-with-files@planning-with-files` |
| **v2.2.1** | Session recovery after /clear, enhanced PreToolUse hook | See [releases](https://github.com/OthmanAdi/planning-with-files/releases) |
| **v2.2.0** | Kilo Code IDE support, Windows PowerShell support, OS-aware hooks | See [releases](https://github.com/OthmanAdi/planning-with-files/releases) |
| **v2.1.2** | Fix template cache issue (Issue #18) | See [releases](https://github.com/OthmanAdi/planning-with-files/releases) |
| **v2.1.1** | Fix plugin template paths | See [releases](https://github.com/OthmanAdi/planning-with-files/releases) |
| **v2.1.0** | Claude Code v2.1 compatible, SessionStart hook, PostToolUse hook, user-invocable | See [releases](https://github.com/OthmanAdi/planning-with-files/releases) |
| **v2.1.0** | Claude Code v2.1 compatible, PostToolUse hook, user-invocable | See [releases](https://github.com/OthmanAdi/planning-with-files/releases) |
| **v2.0.x** | Hooks, templates, scripts | See [releases](https://github.com/OthmanAdi/planning-with-files/releases) |
| **v1.0.0** (legacy) | Core 3-file pattern | `git clone -b legacy` |
@@ -102,6 +103,45 @@ Or invoke manually with `/planning-with-files`.
See [docs/quickstart.md](docs/quickstart.md) for the full 5-step guide.
## Session Recovery (NEW in v2.2.0)
When your context window fills up and you run `/clear`, this skill automatically recovers unsynced work from your previous session.
### Optimal Workflow
For the best experience, we recommend:
1. **Disable auto-compact** in Claude Code settings (use full context window)
2. **Start a fresh session** in your project
3. **Run `/planning-with-files`** when ready to work on a complex task
4. **Work until context fills up** (Claude will warn you)
5. **Run `/clear`** to start fresh
6. **Run `/planning-with-files`** again — it will automatically recover where you left off
### How Recovery Works
When you invoke `/planning-with-files`, the skill:
1. Checks for previous session data (stored in `~/.claude/projects/`)
2. Finds the last time planning files were updated
3. Extracts conversation that happened after (potentially lost context)
4. Shows a catchup report so you can sync planning files
This means even if context filled up before you could update your planning files, the skill will recover that context in your next session.
### Disabling Auto-Compact
To use the full context window without automatic compaction:
```bash
# In your Claude Code settings or .claude/settings.json
{
"autoCompact": false
}
```
This lets you maximize context usage before manually clearing with `/clear`.
## Key Rules
1. **Create Plan First** — Never start without `task_plan.md`
@@ -185,6 +225,7 @@ See [docs/kilocode.md](docs/kilocode.md) for detailed Kilo Code integration guid
| Fork | Author | Features |
|------|--------|----------|
| [devis](https://github.com/st01cs/devis) | [@st01cs](https://github.com/st01cs) | Interview-first workflow, `/devis:intv` and `/devis:impl` commands, guaranteed activation |
| [multi-manus-planning](https://github.com/kmichels/multi-manus-planning) | [@kmichels](https://github.com/kmichels) | Multi-project support, SessionStart git sync |
*Built something? Open an issue to get listed!*
+24 -17
View File
@@ -1,7 +1,7 @@
---
name: planning-with-files
version: "2.2.0"
description: Implements Manus-style file-based planning for complex tasks. Creates task_plan.md, findings.md, and progress.md. Use when starting complex multi-step tasks, research projects, or any task requiring >5 tool calls.
version: "2.2.2"
description: Implements Manus-style file-based planning for complex tasks. Creates task_plan.md, findings.md, and progress.md. Use when starting complex multi-step tasks, research projects, or any task requiring >5 tool calls. Now with automatic session recovery after /clear.
user-invocable: true
allowed-tools:
- Read
@@ -13,12 +13,8 @@ allowed-tools:
- WebFetch
- WebSearch
hooks:
SessionStart:
- hooks:
- type: command
command: "echo '[planning-with-files] Ready. Auto-activates for complex tasks, or invoke manually with /planning-with-files'"
PreToolUse:
- matcher: "Write|Edit|Bash"
- matcher: "Write|Edit|Bash|Read|Glob|Grep"
hooks:
- type: command
command: "cat task_plan.md 2>/dev/null | head -30 || true"
@@ -42,31 +38,41 @@ hooks:
Work like Manus: Use persistent markdown files as your "working memory on disk."
## FIRST: Check for Previous Session (v2.2.0)
**Before starting work**, check for unsynced context from a previous session:
```bash
python3 ${CLAUDE_PLUGIN_ROOT}/scripts/session-catchup.py "$(pwd)"
```
If catchup report shows unsynced context:
1. Run `git diff --stat` to see actual code changes
2. Read current planning files
3. Update planning files based on catchup + git diff
4. Then proceed with task
## Important: Where Files Go
When using this skill:
- **Templates** are stored in the skill directory at `${CLAUDE_PLUGIN_ROOT}/templates/`
- **Your planning files** (`task_plan.md`, `findings.md`, `progress.md`) should be created in **your project directory** — the folder where you're working
- **Templates** are in `${CLAUDE_PLUGIN_ROOT}/templates/`
- **Your planning files** go in **your project directory**
| Location | What Goes There |
|----------|-----------------|
| Skill directory (`${CLAUDE_PLUGIN_ROOT}/`) | Templates, scripts, reference docs |
| Your project directory | `task_plan.md`, `findings.md`, `progress.md` |
This ensures your planning files live alongside your code, not buried in the skill installation folder.
## Quick Start
Before ANY complex task:
1. **Create `task_plan.md`** in your project — Use [templates/task_plan.md](templates/task_plan.md) as reference
2. **Create `findings.md`** in your project — Use [templates/findings.md](templates/findings.md) as reference
3. **Create `progress.md`** in your project — Use [templates/progress.md](templates/progress.md) as reference
1. **Create `task_plan.md`** — Use [templates/task_plan.md](templates/task_plan.md) as reference
2. **Create `findings.md`** — Use [templates/findings.md](templates/findings.md) as reference
3. **Create `progress.md`** — Use [templates/progress.md](templates/progress.md) as reference
4. **Re-read plan before decisions** — Refreshes goals in attention window
5. **Update after each phase** — Mark complete, log errors
> **Note:** All three planning files should be created in your current working directory (your project root), not in the skill's installation folder.
> **Note:** Planning files go in your project root, not the skill installation folder.
## The Core Pattern
@@ -197,6 +203,7 @@ Helper scripts for automation:
- `scripts/init-session.sh` — Initialize all planning files
- `scripts/check-complete.sh` — Verify all phases complete
- `scripts/session-catchup.py` — Recover context from previous session (v2.2.0)
## Advanced Topics
+208
View File
@@ -0,0 +1,208 @@
#!/usr/bin/env python3
"""
Session Catchup Script for planning-with-files
Analyzes the previous session to find unsynced context after the last
planning file update. Designed to run on SessionStart.
Usage: python3 session-catchup.py [project-path]
"""
import json
import sys
import os
from pathlib import Path
from typing import List, Dict, Optional, Tuple
from datetime import datetime
PLANNING_FILES = ['task_plan.md', 'progress.md', 'findings.md']
def get_project_dir(project_path: str) -> Path:
"""Convert project path to Claude's storage path format."""
sanitized = project_path.replace('/', '-')
if not sanitized.startswith('-'):
sanitized = '-' + sanitized
sanitized = sanitized.replace('_', '-')
return Path.home() / '.claude' / 'projects' / sanitized
def get_sessions_sorted(project_dir: Path) -> List[Path]:
"""Get all session files sorted by modification time (newest first)."""
sessions = list(project_dir.glob('*.jsonl'))
main_sessions = [s for s in sessions if not s.name.startswith('agent-')]
return sorted(main_sessions, key=lambda p: p.stat().st_mtime, reverse=True)
def parse_session_messages(session_file: Path) -> List[Dict]:
"""Parse all messages from a session file, preserving order."""
messages = []
with open(session_file, 'r') as f:
for line_num, line in enumerate(f):
try:
data = json.loads(line)
data['_line_num'] = line_num
messages.append(data)
except json.JSONDecodeError:
pass
return messages
def find_last_planning_update(messages: List[Dict]) -> Tuple[int, Optional[str]]:
"""
Find the last time a planning file was written/edited.
Returns (line_number, filename) or (-1, None) if not found.
"""
last_update_line = -1
last_update_file = None
for msg in messages:
msg_type = msg.get('type')
if msg_type == 'assistant':
content = msg.get('message', {}).get('content', [])
if isinstance(content, list):
for item in content:
if item.get('type') == 'tool_use':
tool_name = item.get('name', '')
tool_input = item.get('input', {})
if tool_name in ('Write', 'Edit'):
file_path = tool_input.get('file_path', '')
for pf in PLANNING_FILES:
if file_path.endswith(pf):
last_update_line = msg['_line_num']
last_update_file = pf
return last_update_line, last_update_file
def extract_messages_after(messages: List[Dict], after_line: int) -> List[Dict]:
"""Extract conversation messages after a certain line number."""
result = []
for msg in messages:
if msg['_line_num'] <= after_line:
continue
msg_type = msg.get('type')
is_meta = msg.get('isMeta', False)
if msg_type == 'user' and not is_meta:
content = msg.get('message', {}).get('content', '')
if isinstance(content, list):
for item in content:
if isinstance(item, dict) and item.get('type') == 'text':
content = item.get('text', '')
break
else:
content = ''
if content and isinstance(content, str):
if content.startswith(('<local-command', '<command-', '<task-notification')):
continue
if len(content) > 20:
result.append({'role': 'user', 'content': content, 'line': msg['_line_num']})
elif msg_type == 'assistant':
msg_content = msg.get('message', {}).get('content', '')
text_content = ''
tool_uses = []
if isinstance(msg_content, str):
text_content = msg_content
elif isinstance(msg_content, list):
for item in msg_content:
if item.get('type') == 'text':
text_content = item.get('text', '')
elif item.get('type') == 'tool_use':
tool_name = item.get('name', '')
tool_input = item.get('input', {})
if tool_name == 'Edit':
tool_uses.append(f"Edit: {tool_input.get('file_path', 'unknown')}")
elif tool_name == 'Write':
tool_uses.append(f"Write: {tool_input.get('file_path', 'unknown')}")
elif tool_name == 'Bash':
cmd = tool_input.get('command', '')[:80]
tool_uses.append(f"Bash: {cmd}")
else:
tool_uses.append(f"{tool_name}")
if text_content or tool_uses:
result.append({
'role': 'assistant',
'content': text_content[:600] if text_content else '',
'tools': tool_uses,
'line': msg['_line_num']
})
return result
def main():
project_path = sys.argv[1] if len(sys.argv) > 1 else os.getcwd()
project_dir = get_project_dir(project_path)
# Check if planning files exist (indicates active task)
has_planning_files = any(
Path(project_path, f).exists() for f in PLANNING_FILES
)
if not project_dir.exists():
# No previous sessions, nothing to catch up on
return
sessions = get_sessions_sorted(project_dir)
if len(sessions) < 1:
return
# Find a substantial previous session
target_session = None
for session in sessions:
if session.stat().st_size > 5000:
target_session = session
break
if not target_session:
return
messages = parse_session_messages(target_session)
last_update_line, last_update_file = find_last_planning_update(messages)
# Only output if there's unsynced content
if last_update_line < 0:
messages_after = extract_messages_after(messages, len(messages) - 30)
else:
messages_after = extract_messages_after(messages, last_update_line)
if not messages_after:
return
# Output catchup report
print("\n[planning-with-files] SESSION CATCHUP DETECTED")
print(f"Previous session: {target_session.stem}")
if last_update_line >= 0:
print(f"Last planning update: {last_update_file} at message #{last_update_line}")
print(f"Unsynced messages: {len(messages_after)}")
else:
print("No planning file updates found in previous session")
print("\n--- UNSYNCED CONTEXT ---")
for msg in messages_after[-15:]: # Last 15 messages
if msg['role'] == 'user':
print(f"USER: {msg['content'][:300]}")
else:
if msg.get('content'):
print(f"CLAUDE: {msg['content'][:300]}")
if msg.get('tools'):
print(f" Tools: {', '.join(msg['tools'][:4])}")
print("\n--- RECOMMENDED ---")
print("1. Run: git diff --stat")
print("2. Read: task_plan.md, progress.md, findings.md")
print("3. Update planning files based on above context")
print("4. Continue with task")
if __name__ == '__main__':
main()
+208
View File
@@ -0,0 +1,208 @@
#!/usr/bin/env python3
"""
Session Catchup Script for planning-with-files
Analyzes the previous session to find unsynced context after the last
planning file update. Designed to run on SessionStart.
Usage: python3 session-catchup.py [project-path]
"""
import json
import sys
import os
from pathlib import Path
from typing import List, Dict, Optional, Tuple
from datetime import datetime
PLANNING_FILES = ['task_plan.md', 'progress.md', 'findings.md']
def get_project_dir(project_path: str) -> Path:
"""Convert project path to Claude's storage path format."""
sanitized = project_path.replace('/', '-')
if not sanitized.startswith('-'):
sanitized = '-' + sanitized
sanitized = sanitized.replace('_', '-')
return Path.home() / '.claude' / 'projects' / sanitized
def get_sessions_sorted(project_dir: Path) -> List[Path]:
"""Get all session files sorted by modification time (newest first)."""
sessions = list(project_dir.glob('*.jsonl'))
main_sessions = [s for s in sessions if not s.name.startswith('agent-')]
return sorted(main_sessions, key=lambda p: p.stat().st_mtime, reverse=True)
def parse_session_messages(session_file: Path) -> List[Dict]:
"""Parse all messages from a session file, preserving order."""
messages = []
with open(session_file, 'r') as f:
for line_num, line in enumerate(f):
try:
data = json.loads(line)
data['_line_num'] = line_num
messages.append(data)
except json.JSONDecodeError:
pass
return messages
def find_last_planning_update(messages: List[Dict]) -> Tuple[int, Optional[str]]:
"""
Find the last time a planning file was written/edited.
Returns (line_number, filename) or (-1, None) if not found.
"""
last_update_line = -1
last_update_file = None
for msg in messages:
msg_type = msg.get('type')
if msg_type == 'assistant':
content = msg.get('message', {}).get('content', [])
if isinstance(content, list):
for item in content:
if item.get('type') == 'tool_use':
tool_name = item.get('name', '')
tool_input = item.get('input', {})
if tool_name in ('Write', 'Edit'):
file_path = tool_input.get('file_path', '')
for pf in PLANNING_FILES:
if file_path.endswith(pf):
last_update_line = msg['_line_num']
last_update_file = pf
return last_update_line, last_update_file
def extract_messages_after(messages: List[Dict], after_line: int) -> List[Dict]:
"""Extract conversation messages after a certain line number."""
result = []
for msg in messages:
if msg['_line_num'] <= after_line:
continue
msg_type = msg.get('type')
is_meta = msg.get('isMeta', False)
if msg_type == 'user' and not is_meta:
content = msg.get('message', {}).get('content', '')
if isinstance(content, list):
for item in content:
if isinstance(item, dict) and item.get('type') == 'text':
content = item.get('text', '')
break
else:
content = ''
if content and isinstance(content, str):
if content.startswith(('<local-command', '<command-', '<task-notification')):
continue
if len(content) > 20:
result.append({'role': 'user', 'content': content, 'line': msg['_line_num']})
elif msg_type == 'assistant':
msg_content = msg.get('message', {}).get('content', '')
text_content = ''
tool_uses = []
if isinstance(msg_content, str):
text_content = msg_content
elif isinstance(msg_content, list):
for item in msg_content:
if item.get('type') == 'text':
text_content = item.get('text', '')
elif item.get('type') == 'tool_use':
tool_name = item.get('name', '')
tool_input = item.get('input', {})
if tool_name == 'Edit':
tool_uses.append(f"Edit: {tool_input.get('file_path', 'unknown')}")
elif tool_name == 'Write':
tool_uses.append(f"Write: {tool_input.get('file_path', 'unknown')}")
elif tool_name == 'Bash':
cmd = tool_input.get('command', '')[:80]
tool_uses.append(f"Bash: {cmd}")
else:
tool_uses.append(f"{tool_name}")
if text_content or tool_uses:
result.append({
'role': 'assistant',
'content': text_content[:600] if text_content else '',
'tools': tool_uses,
'line': msg['_line_num']
})
return result
def main():
project_path = sys.argv[1] if len(sys.argv) > 1 else os.getcwd()
project_dir = get_project_dir(project_path)
# Check if planning files exist (indicates active task)
has_planning_files = any(
Path(project_path, f).exists() for f in PLANNING_FILES
)
if not project_dir.exists():
# No previous sessions, nothing to catch up on
return
sessions = get_sessions_sorted(project_dir)
if len(sessions) < 1:
return
# Find a substantial previous session
target_session = None
for session in sessions:
if session.stat().st_size > 5000:
target_session = session
break
if not target_session:
return
messages = parse_session_messages(target_session)
last_update_line, last_update_file = find_last_planning_update(messages)
# Only output if there's unsynced content
if last_update_line < 0:
messages_after = extract_messages_after(messages, len(messages) - 30)
else:
messages_after = extract_messages_after(messages, last_update_line)
if not messages_after:
return
# Output catchup report
print("\n[planning-with-files] SESSION CATCHUP DETECTED")
print(f"Previous session: {target_session.stem}")
if last_update_line >= 0:
print(f"Last planning update: {last_update_file} at message #{last_update_line}")
print(f"Unsynced messages: {len(messages_after)}")
else:
print("No planning file updates found in previous session")
print("\n--- UNSYNCED CONTEXT ---")
for msg in messages_after[-15:]: # Last 15 messages
if msg['role'] == 'user':
print(f"USER: {msg['content'][:300]}")
else:
if msg.get('content'):
print(f"CLAUDE: {msg['content'][:300]}")
if msg.get('tools'):
print(f" Tools: {', '.join(msg['tools'][:4])}")
print("\n--- RECOMMENDED ---")
print("1. Run: git diff --stat")
print("2. Read: task_plan.md, progress.md, findings.md")
print("3. Update planning files based on above context")
print("4. Continue with task")
if __name__ == '__main__':
main()
+24 -17
View File
@@ -1,7 +1,7 @@
---
name: planning-with-files
version: "2.2.0"
description: Implements Manus-style file-based planning for complex tasks. Creates task_plan.md, findings.md, and progress.md. Use when starting complex multi-step tasks, research projects, or any task requiring >5 tool calls.
version: "2.2.2"
description: Implements Manus-style file-based planning for complex tasks. Creates task_plan.md, findings.md, and progress.md. Use when starting complex multi-step tasks, research projects, or any task requiring >5 tool calls. Now with automatic session recovery after /clear.
user-invocable: true
allowed-tools:
- Read
@@ -13,12 +13,8 @@ allowed-tools:
- WebFetch
- WebSearch
hooks:
SessionStart:
- hooks:
- type: command
command: "echo '[planning-with-files] Ready. Auto-activates for complex tasks, or invoke manually with /planning-with-files'"
PreToolUse:
- matcher: "Write|Edit|Bash"
- matcher: "Write|Edit|Bash|Read|Glob|Grep"
hooks:
- type: command
command: "cat task_plan.md 2>/dev/null | head -30 || true"
@@ -42,31 +38,41 @@ hooks:
Work like Manus: Use persistent markdown files as your "working memory on disk."
## FIRST: Check for Previous Session (v2.2.0)
**Before starting work**, check for unsynced context from a previous session:
```bash
python3 ${CLAUDE_PLUGIN_ROOT}/scripts/session-catchup.py "$(pwd)"
```
If catchup report shows unsynced context:
1. Run `git diff --stat` to see actual code changes
2. Read current planning files
3. Update planning files based on catchup + git diff
4. Then proceed with task
## Important: Where Files Go
When using this skill:
- **Templates** are stored in the skill directory at `${CLAUDE_PLUGIN_ROOT}/templates/`
- **Your planning files** (`task_plan.md`, `findings.md`, `progress.md`) should be created in **your project directory** — the folder where you're working
- **Templates** are in `${CLAUDE_PLUGIN_ROOT}/templates/`
- **Your planning files** go in **your project directory**
| Location | What Goes There |
|----------|-----------------|
| Skill directory (`${CLAUDE_PLUGIN_ROOT}/`) | Templates, scripts, reference docs |
| Your project directory | `task_plan.md`, `findings.md`, `progress.md` |
This ensures your planning files live alongside your code, not buried in the skill installation folder.
## Quick Start
Before ANY complex task:
1. **Create `task_plan.md`** in your project — Use [templates/task_plan.md](templates/task_plan.md) as reference
2. **Create `findings.md`** in your project — Use [templates/findings.md](templates/findings.md) as reference
3. **Create `progress.md`** in your project — Use [templates/progress.md](templates/progress.md) as reference
1. **Create `task_plan.md`** — Use [templates/task_plan.md](templates/task_plan.md) as reference
2. **Create `findings.md`** — Use [templates/findings.md](templates/findings.md) as reference
3. **Create `progress.md`** — Use [templates/progress.md](templates/progress.md) as reference
4. **Re-read plan before decisions** — Refreshes goals in attention window
5. **Update after each phase** — Mark complete, log errors
> **Note:** All three planning files should be created in your current working directory (your project root), not in the skill's installation folder.
> **Note:** Planning files go in your project root, not the skill installation folder.
## The Core Pattern
@@ -197,6 +203,7 @@ Helper scripts for automation:
- `scripts/init-session.sh` — Initialize all planning files
- `scripts/check-complete.sh` — Verify all phases complete
- `scripts/session-catchup.py` — Recover context from previous session (v2.2.0)
## Advanced Topics
+208
View File
@@ -0,0 +1,208 @@
#!/usr/bin/env python3
"""
Session Catchup Script for planning-with-files
Analyzes the previous session to find unsynced context after the last
planning file update. Designed to run on SessionStart.
Usage: python3 session-catchup.py [project-path]
"""
import json
import sys
import os
from pathlib import Path
from typing import List, Dict, Optional, Tuple
from datetime import datetime
PLANNING_FILES = ['task_plan.md', 'progress.md', 'findings.md']
def get_project_dir(project_path: str) -> Path:
"""Convert project path to Claude's storage path format."""
sanitized = project_path.replace('/', '-')
if not sanitized.startswith('-'):
sanitized = '-' + sanitized
sanitized = sanitized.replace('_', '-')
return Path.home() / '.claude' / 'projects' / sanitized
def get_sessions_sorted(project_dir: Path) -> List[Path]:
"""Get all session files sorted by modification time (newest first)."""
sessions = list(project_dir.glob('*.jsonl'))
main_sessions = [s for s in sessions if not s.name.startswith('agent-')]
return sorted(main_sessions, key=lambda p: p.stat().st_mtime, reverse=True)
def parse_session_messages(session_file: Path) -> List[Dict]:
"""Parse all messages from a session file, preserving order."""
messages = []
with open(session_file, 'r') as f:
for line_num, line in enumerate(f):
try:
data = json.loads(line)
data['_line_num'] = line_num
messages.append(data)
except json.JSONDecodeError:
pass
return messages
def find_last_planning_update(messages: List[Dict]) -> Tuple[int, Optional[str]]:
"""
Find the last time a planning file was written/edited.
Returns (line_number, filename) or (-1, None) if not found.
"""
last_update_line = -1
last_update_file = None
for msg in messages:
msg_type = msg.get('type')
if msg_type == 'assistant':
content = msg.get('message', {}).get('content', [])
if isinstance(content, list):
for item in content:
if item.get('type') == 'tool_use':
tool_name = item.get('name', '')
tool_input = item.get('input', {})
if tool_name in ('Write', 'Edit'):
file_path = tool_input.get('file_path', '')
for pf in PLANNING_FILES:
if file_path.endswith(pf):
last_update_line = msg['_line_num']
last_update_file = pf
return last_update_line, last_update_file
def extract_messages_after(messages: List[Dict], after_line: int) -> List[Dict]:
"""Extract conversation messages after a certain line number."""
result = []
for msg in messages:
if msg['_line_num'] <= after_line:
continue
msg_type = msg.get('type')
is_meta = msg.get('isMeta', False)
if msg_type == 'user' and not is_meta:
content = msg.get('message', {}).get('content', '')
if isinstance(content, list):
for item in content:
if isinstance(item, dict) and item.get('type') == 'text':
content = item.get('text', '')
break
else:
content = ''
if content and isinstance(content, str):
if content.startswith(('<local-command', '<command-', '<task-notification')):
continue
if len(content) > 20:
result.append({'role': 'user', 'content': content, 'line': msg['_line_num']})
elif msg_type == 'assistant':
msg_content = msg.get('message', {}).get('content', '')
text_content = ''
tool_uses = []
if isinstance(msg_content, str):
text_content = msg_content
elif isinstance(msg_content, list):
for item in msg_content:
if item.get('type') == 'text':
text_content = item.get('text', '')
elif item.get('type') == 'tool_use':
tool_name = item.get('name', '')
tool_input = item.get('input', {})
if tool_name == 'Edit':
tool_uses.append(f"Edit: {tool_input.get('file_path', 'unknown')}")
elif tool_name == 'Write':
tool_uses.append(f"Write: {tool_input.get('file_path', 'unknown')}")
elif tool_name == 'Bash':
cmd = tool_input.get('command', '')[:80]
tool_uses.append(f"Bash: {cmd}")
else:
tool_uses.append(f"{tool_name}")
if text_content or tool_uses:
result.append({
'role': 'assistant',
'content': text_content[:600] if text_content else '',
'tools': tool_uses,
'line': msg['_line_num']
})
return result
def main():
project_path = sys.argv[1] if len(sys.argv) > 1 else os.getcwd()
project_dir = get_project_dir(project_path)
# Check if planning files exist (indicates active task)
has_planning_files = any(
Path(project_path, f).exists() for f in PLANNING_FILES
)
if not project_dir.exists():
# No previous sessions, nothing to catch up on
return
sessions = get_sessions_sorted(project_dir)
if len(sessions) < 1:
return
# Find a substantial previous session
target_session = None
for session in sessions:
if session.stat().st_size > 5000:
target_session = session
break
if not target_session:
return
messages = parse_session_messages(target_session)
last_update_line, last_update_file = find_last_planning_update(messages)
# Only output if there's unsynced content
if last_update_line < 0:
messages_after = extract_messages_after(messages, len(messages) - 30)
else:
messages_after = extract_messages_after(messages, last_update_line)
if not messages_after:
return
# Output catchup report
print("\n[planning-with-files] SESSION CATCHUP DETECTED")
print(f"Previous session: {target_session.stem}")
if last_update_line >= 0:
print(f"Last planning update: {last_update_file} at message #{last_update_line}")
print(f"Unsynced messages: {len(messages_after)}")
else:
print("No planning file updates found in previous session")
print("\n--- UNSYNCED CONTEXT ---")
for msg in messages_after[-15:]: # Last 15 messages
if msg['role'] == 'user':
print(f"USER: {msg['content'][:300]}")
else:
if msg.get('content'):
print(f"CLAUDE: {msg['content'][:300]}")
if msg.get('tools'):
print(f" Tools: {', '.join(msg['tools'][:4])}")
print("\n--- RECOMMENDED ---")
print("1. Run: git diff --stat")
print("2. Read: task_plan.md, progress.md, findings.md")
print("3. Update planning files based on above context")
print("4. Continue with task")
if __name__ == '__main__':
main()