claude-jacked 0.2.3__py3-none-any.whl → 0.2.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- claude_jacked-0.2.9.dist-info/METADATA +523 -0
- claude_jacked-0.2.9.dist-info/RECORD +33 -0
- jacked/cli.py +752 -47
- jacked/client.py +196 -29
- jacked/data/agents/code-simplicity-reviewer.md +87 -0
- jacked/data/agents/defensive-error-handler.md +93 -0
- jacked/data/agents/double-check-reviewer.md +214 -0
- jacked/data/agents/git-pr-workflow-manager.md +149 -0
- jacked/data/agents/issue-pr-coordinator.md +131 -0
- jacked/data/agents/pr-workflow-checker.md +199 -0
- jacked/data/agents/readme-maintainer.md +123 -0
- jacked/data/agents/test-coverage-engineer.md +155 -0
- jacked/data/agents/test-coverage-improver.md +139 -0
- jacked/data/agents/wiki-documentation-architect.md +580 -0
- jacked/data/commands/audit-rules.md +103 -0
- jacked/data/commands/dc.md +155 -0
- jacked/data/commands/learn.md +89 -0
- jacked/data/commands/pr.md +4 -0
- jacked/data/commands/redo.md +85 -0
- jacked/data/commands/techdebt.md +115 -0
- jacked/data/prompts/security_gatekeeper.txt +58 -0
- jacked/data/rules/jacked_behaviors.md +11 -0
- jacked/data/skills/jacked/SKILL.md +162 -0
- jacked/index_write_tracker.py +227 -0
- jacked/indexer.py +255 -129
- jacked/retriever.py +389 -137
- jacked/searcher.py +65 -13
- jacked/transcript.py +339 -0
- claude_jacked-0.2.3.dist-info/METADATA +0 -483
- claude_jacked-0.2.3.dist-info/RECORD +0 -13
- {claude_jacked-0.2.3.dist-info → claude_jacked-0.2.9.dist-info}/WHEEL +0 -0
- {claude_jacked-0.2.3.dist-info → claude_jacked-0.2.9.dist-info}/entry_points.txt +0 -0
- {claude_jacked-0.2.3.dist-info → claude_jacked-0.2.9.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: jacked
|
|
3
|
+
description: Search and load context from past Claude Code sessions. Use when: user mentions a past project like "configurator" or other previous work, asks to continue/resume previous work, says "how did I do X before", references past sessions, or starts work on a feature that may have been done before.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Jacked
|
|
7
|
+
|
|
8
|
+
Search and load context from past Claude Code sessions using semantic search.
|
|
9
|
+
|
|
10
|
+
## Usage
|
|
11
|
+
|
|
12
|
+
```
|
|
13
|
+
/jacked <description of what you want to work on>
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Example:
|
|
17
|
+
```
|
|
18
|
+
/jacked implement overnight OB time handling
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## How It Works
|
|
22
|
+
|
|
23
|
+
1. Takes your description and searches for similar past sessions
|
|
24
|
+
2. Shows matching sessions with relevance scores and content indicators
|
|
25
|
+
3. You pick which session(s) to load context from
|
|
26
|
+
4. Uses **smart mode** by default - loads plan files, agent summaries, and key user messages (NOT the full transcript which would blow up context)
|
|
27
|
+
5. If the session exists locally, suggests native Claude resume as an option
|
|
28
|
+
|
|
29
|
+
## Instructions for Claude
|
|
30
|
+
|
|
31
|
+
When the user runs `/jacked <description>`, follow these steps:
|
|
32
|
+
|
|
33
|
+
### Step 1: Search for Similar Sessions
|
|
34
|
+
|
|
35
|
+
Run the search command:
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
jacked search "<user's description>" --limit 5
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
The output includes:
|
|
42
|
+
- Relevance score (percentage)
|
|
43
|
+
- User (YOU or @username for teammates)
|
|
44
|
+
- Age (relative time like "24 days ago")
|
|
45
|
+
- Repository name
|
|
46
|
+
- Content indicators: 📋 = has plan file, 🤖 = has agent summaries
|
|
47
|
+
- Preview of matched content
|
|
48
|
+
|
|
49
|
+
### Step 2: Present Results Using AskUserQuestion
|
|
50
|
+
|
|
51
|
+
Use the AskUserQuestion tool with multiSelect=true to let the user pick which sessions to load:
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"questions": [{
|
|
56
|
+
"question": "Which sessions would you like to load context from?",
|
|
57
|
+
"header": "Sessions",
|
|
58
|
+
"multiSelect": true,
|
|
59
|
+
"options": [
|
|
60
|
+
{
|
|
61
|
+
"label": "1. YOU - 24d ago 📋🤖",
|
|
62
|
+
"description": "hank-coder: Implementing overnight time calculation..."
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
"label": "2. @bob - 3mo ago 🤖",
|
|
66
|
+
"description": "hank-coder: Time handling refactor for multiple..."
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
"label": "3. YOU - 2d ago",
|
|
70
|
+
"description": "krac-llm: Staff time merging edge cases..."
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
"label": "None - skip",
|
|
74
|
+
"description": "Don't load any previous context"
|
|
75
|
+
}
|
|
76
|
+
]
|
|
77
|
+
}]
|
|
78
|
+
}
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Note: AskUserQuestion supports max 4 options, so if there are 5+ results, show top 3 + "None" option.
|
|
82
|
+
|
|
83
|
+
### Step 3: Retrieve Selected Sessions
|
|
84
|
+
|
|
85
|
+
When the user selects one or more sessions:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
# Default: smart mode (recommended) - plan + summaries + labels + user msgs
|
|
89
|
+
jacked retrieve <session_id> --mode smart
|
|
90
|
+
|
|
91
|
+
# For full transcript (use sparingly - can be 50K+ chars)
|
|
92
|
+
jacked retrieve <session_id> --mode full
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Smart mode retrieval output includes:
|
|
96
|
+
- Session metadata with relative age
|
|
97
|
+
- Token budget accounting
|
|
98
|
+
- Plan file content (if exists)
|
|
99
|
+
- Subagent summaries (exploration/planning results)
|
|
100
|
+
- Summary labels (chapter titles)
|
|
101
|
+
- First few user messages
|
|
102
|
+
|
|
103
|
+
### Step 4: Handle Based on Session Location
|
|
104
|
+
|
|
105
|
+
**If session is local:**
|
|
106
|
+
Tell the user:
|
|
107
|
+
```
|
|
108
|
+
This session exists locally on your machine!
|
|
109
|
+
To resume it natively (with full Claude memory), run in a new terminal:
|
|
110
|
+
|
|
111
|
+
claude --resume <session_id>
|
|
112
|
+
|
|
113
|
+
Or I can inject the smart context into our current conversation.
|
|
114
|
+
Would you like me to inject it here? (yes/no)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
**If session is remote only:**
|
|
118
|
+
Tell the user:
|
|
119
|
+
```
|
|
120
|
+
This session is from another machine (<machine_name>).
|
|
121
|
+
I'll inject the context into our conversation.
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Step 5: Context Injection with Staleness Warning
|
|
125
|
+
|
|
126
|
+
The retrieve output already includes proper formatting with staleness warnings.
|
|
127
|
+
|
|
128
|
+
**For context older than 7 days, include the staleness warning** that appears in the output:
|
|
129
|
+
- 7-30 days: Mild warning - "Code may have changed"
|
|
130
|
+
- 30-90 days: Medium warning - "Use as starting point for WHERE to look"
|
|
131
|
+
- 90+ days: Strong warning - "Treat as historical reference only"
|
|
132
|
+
|
|
133
|
+
After injection, summarize:
|
|
134
|
+
1. What the previous session covered
|
|
135
|
+
2. Key decisions or implementations found
|
|
136
|
+
3. Ask what the user wants to work on now
|
|
137
|
+
|
|
138
|
+
## Retrieval Modes
|
|
139
|
+
|
|
140
|
+
| Mode | What's Included | When to Use |
|
|
141
|
+
|------|-----------------|-------------|
|
|
142
|
+
| smart | Plan + agent summaries + labels + user msgs | Default - best balance |
|
|
143
|
+
| plan | Just the plan file | Quick strategic overview |
|
|
144
|
+
| labels | Just summary labels (tiny) | Quick topic check |
|
|
145
|
+
| agents | All subagent summaries | Deep dive into exploration results |
|
|
146
|
+
| full | Everything including transcript | Need full details (use sparingly) |
|
|
147
|
+
|
|
148
|
+
## Error Handling
|
|
149
|
+
|
|
150
|
+
- If search returns no results: "No matching sessions found. Try a different description or run `jacked backfill` to index your sessions."
|
|
151
|
+
- If retrieve fails: "Session not found in index. It may have been deleted or the session ID is invalid."
|
|
152
|
+
- If jacked command not found: "jacked not installed or not on PATH. Run `pipx install claude-jacked` to install."
|
|
153
|
+
|
|
154
|
+
## Notes
|
|
155
|
+
|
|
156
|
+
- Sessions are indexed automatically via a Stop hook (after each Claude response)
|
|
157
|
+
- **New in v0.2.6**: Indexes plan files, subagent summaries, and summary labels for smarter retrieval
|
|
158
|
+
- Smart mode prevents context explosion by returning ~5-10K tokens instead of 50-200K
|
|
159
|
+
- The index is stored in Qdrant Cloud, accessible from any machine
|
|
160
|
+
- Local sessions can be resumed natively with `claude --resume` for the best experience
|
|
161
|
+
- Remote sessions are retrieved and injected as context (works but Claude won't have internal memory state)
|
|
162
|
+
- Use `jacked cleardb` to wipe your data before re-indexing with a new schema
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
"""
|
|
2
|
+
SQLite-based tracker for what has been PUSHED to Qdrant.
|
|
3
|
+
|
|
4
|
+
WARNING: This is WRITE-SIDE ONLY. Used to track what we've already indexed
|
|
5
|
+
so we don't re-push unchanged content. This is NOT a read cache and MUST NOT
|
|
6
|
+
be used for search or retrieval - always query Qdrant directly for that.
|
|
7
|
+
"""
|
|
8
|
+
import sqlite3
|
|
9
|
+
import logging
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
from typing import Optional
|
|
12
|
+
from contextlib import contextmanager
|
|
13
|
+
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
DB_PATH = Path.home() / ".claude" / "jacked_index_write_tracker.db"
|
|
17
|
+
MAX_SEED_POINTS = 5000 # Sanity limit to prevent OOM on pathological sessions
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class IndexWriteTracker:
|
|
21
|
+
"""
|
|
22
|
+
Tracks what content has been pushed to Qdrant to enable incremental indexing.
|
|
23
|
+
|
|
24
|
+
WARNING: This is WRITE-SIDE ONLY tracking. NOT for retrieval/search.
|
|
25
|
+
Always query Qdrant directly for search operations.
|
|
26
|
+
|
|
27
|
+
The tracker uses SQLite for:
|
|
28
|
+
- Indexed lookups (no loading entire file into memory)
|
|
29
|
+
- Built-in locking for concurrent access (WAL mode)
|
|
30
|
+
- ACID transactions for crash safety
|
|
31
|
+
|
|
32
|
+
On cache miss or --force, seeds from Qdrant (source of truth).
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
def __init__(self, config_hash: str):
|
|
36
|
+
"""
|
|
37
|
+
Initialize the write tracker.
|
|
38
|
+
|
|
39
|
+
Args:
|
|
40
|
+
config_hash: Hash of chunk_size:chunk_overlap to detect config changes
|
|
41
|
+
"""
|
|
42
|
+
self.config_hash = config_hash
|
|
43
|
+
self._init_db()
|
|
44
|
+
|
|
45
|
+
def _init_db(self):
|
|
46
|
+
"""Initialize SQLite database with schema."""
|
|
47
|
+
DB_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
48
|
+
with self._connect() as conn:
|
|
49
|
+
conn.executescript("""
|
|
50
|
+
CREATE TABLE IF NOT EXISTS indexed_points (
|
|
51
|
+
session_id TEXT NOT NULL,
|
|
52
|
+
content_type TEXT NOT NULL,
|
|
53
|
+
content_index INT NOT NULL,
|
|
54
|
+
content_hash TEXT NOT NULL,
|
|
55
|
+
qdrant_point_id TEXT NOT NULL,
|
|
56
|
+
indexed_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
|
57
|
+
PRIMARY KEY (session_id, content_type, content_index)
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
CREATE TABLE IF NOT EXISTS session_meta (
|
|
61
|
+
session_id TEXT PRIMARY KEY,
|
|
62
|
+
config_hash TEXT,
|
|
63
|
+
status TEXT DEFAULT 'complete',
|
|
64
|
+
last_indexed TIMESTAMP
|
|
65
|
+
);
|
|
66
|
+
|
|
67
|
+
CREATE INDEX IF NOT EXISTS idx_session ON indexed_points(session_id);
|
|
68
|
+
""")
|
|
69
|
+
|
|
70
|
+
@contextmanager
|
|
71
|
+
def _connect(self):
|
|
72
|
+
"""Context manager for DB connection with WAL mode for concurrency."""
|
|
73
|
+
conn = sqlite3.connect(DB_PATH, timeout=30)
|
|
74
|
+
conn.execute("PRAGMA journal_mode=WAL") # Better concurrent access
|
|
75
|
+
conn.execute("PRAGMA busy_timeout=30000") # 30s retry on lock
|
|
76
|
+
try:
|
|
77
|
+
yield conn
|
|
78
|
+
conn.commit()
|
|
79
|
+
finally:
|
|
80
|
+
conn.close()
|
|
81
|
+
|
|
82
|
+
def is_indexed(self, session_id: str, content_type: str, index: int, content_hash: str) -> bool:
|
|
83
|
+
"""
|
|
84
|
+
Check if specific content is already indexed with same hash.
|
|
85
|
+
|
|
86
|
+
Args:
|
|
87
|
+
session_id: Session UUID
|
|
88
|
+
content_type: One of 'plan', 'chunk', 'user_message', 'agent_summary', 'summary_label'
|
|
89
|
+
index: Index within content type (e.g., chunk 0, 1, 2...)
|
|
90
|
+
content_hash: Hash of the content
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
True if this exact content is already indexed
|
|
94
|
+
"""
|
|
95
|
+
with self._connect() as conn:
|
|
96
|
+
row = conn.execute("""
|
|
97
|
+
SELECT 1 FROM indexed_points
|
|
98
|
+
WHERE session_id = ? AND content_type = ? AND content_index = ? AND content_hash = ?
|
|
99
|
+
""", (session_id, content_type, index, content_hash)).fetchone()
|
|
100
|
+
return row is not None
|
|
101
|
+
|
|
102
|
+
def get_session_state(self, session_id: str) -> dict:
|
|
103
|
+
"""
|
|
104
|
+
Get all indexed content hashes for a session.
|
|
105
|
+
|
|
106
|
+
Args:
|
|
107
|
+
session_id: Session UUID
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
Dict mapping (content_type, index) -> content_hash
|
|
111
|
+
"""
|
|
112
|
+
with self._connect() as conn:
|
|
113
|
+
rows = conn.execute("""
|
|
114
|
+
SELECT content_type, content_index, content_hash
|
|
115
|
+
FROM indexed_points WHERE session_id = ?
|
|
116
|
+
""", (session_id,)).fetchall()
|
|
117
|
+
return {(r[0], r[1]): r[2] for r in rows}
|
|
118
|
+
|
|
119
|
+
def get_session_meta(self, session_id: str) -> Optional[dict]:
|
|
120
|
+
"""
|
|
121
|
+
Get session metadata.
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
session_id: Session UUID
|
|
125
|
+
|
|
126
|
+
Returns:
|
|
127
|
+
Dict with config_hash and status, or None if not found
|
|
128
|
+
"""
|
|
129
|
+
with self._connect() as conn:
|
|
130
|
+
row = conn.execute("""
|
|
131
|
+
SELECT config_hash, status FROM session_meta WHERE session_id = ?
|
|
132
|
+
""", (session_id,)).fetchone()
|
|
133
|
+
return {"config_hash": row[0], "status": row[1]} if row else None
|
|
134
|
+
|
|
135
|
+
def mark_indexing(self, session_id: str):
|
|
136
|
+
"""
|
|
137
|
+
Mark session as indexing-in-progress (crash safety).
|
|
138
|
+
|
|
139
|
+
If process crashes mid-index, next run will see 'indexing' status
|
|
140
|
+
and force a re-seed from Qdrant.
|
|
141
|
+
"""
|
|
142
|
+
with self._connect() as conn:
|
|
143
|
+
conn.execute("""
|
|
144
|
+
INSERT OR REPLACE INTO session_meta (session_id, config_hash, status, last_indexed)
|
|
145
|
+
VALUES (?, ?, 'indexing', CURRENT_TIMESTAMP)
|
|
146
|
+
""", (session_id, self.config_hash))
|
|
147
|
+
|
|
148
|
+
def record_indexed(self, session_id: str, content_type: str, index: int,
|
|
149
|
+
content_hash: str, point_id: str):
|
|
150
|
+
"""
|
|
151
|
+
Record that a point was successfully indexed to Qdrant.
|
|
152
|
+
|
|
153
|
+
Args:
|
|
154
|
+
session_id: Session UUID
|
|
155
|
+
content_type: Type of content indexed
|
|
156
|
+
index: Index within content type
|
|
157
|
+
content_hash: Hash of content
|
|
158
|
+
point_id: Qdrant point ID
|
|
159
|
+
"""
|
|
160
|
+
with self._connect() as conn:
|
|
161
|
+
conn.execute("""
|
|
162
|
+
INSERT OR REPLACE INTO indexed_points
|
|
163
|
+
(session_id, content_type, content_index, content_hash, qdrant_point_id)
|
|
164
|
+
VALUES (?, ?, ?, ?, ?)
|
|
165
|
+
""", (session_id, content_type, index, content_hash, point_id))
|
|
166
|
+
|
|
167
|
+
def mark_complete(self, session_id: str):
|
|
168
|
+
"""Mark session indexing as complete."""
|
|
169
|
+
with self._connect() as conn:
|
|
170
|
+
conn.execute("""
|
|
171
|
+
UPDATE session_meta SET status = 'complete', last_indexed = CURRENT_TIMESTAMP
|
|
172
|
+
WHERE session_id = ?
|
|
173
|
+
""", (session_id,))
|
|
174
|
+
|
|
175
|
+
def clear_session(self, session_id: str):
|
|
176
|
+
"""
|
|
177
|
+
Clear all tracked data for a session.
|
|
178
|
+
|
|
179
|
+
Used before re-seeding from Qdrant on --force or config change.
|
|
180
|
+
"""
|
|
181
|
+
with self._connect() as conn:
|
|
182
|
+
conn.execute("DELETE FROM indexed_points WHERE session_id = ?", (session_id,))
|
|
183
|
+
conn.execute("DELETE FROM session_meta WHERE session_id = ?", (session_id,))
|
|
184
|
+
|
|
185
|
+
def seed_from_qdrant(self, session_id: str, qdrant_client, user_name: str):
|
|
186
|
+
"""
|
|
187
|
+
Seed tracker from what's actually in Qdrant FOR THIS USER ONLY.
|
|
188
|
+
|
|
189
|
+
This is write-side only - we only care about what WE have indexed,
|
|
190
|
+
not what other users have indexed. NOT for retrieval.
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
session_id: Session UUID
|
|
194
|
+
qdrant_client: QdrantSessionClient instance
|
|
195
|
+
user_name: Current user's name (for filtering)
|
|
196
|
+
|
|
197
|
+
Raises:
|
|
198
|
+
ValueError: If session has more than MAX_SEED_POINTS (pathological case)
|
|
199
|
+
"""
|
|
200
|
+
points = qdrant_client.get_session_points(session_id, user_name)
|
|
201
|
+
|
|
202
|
+
# Sanity limit to prevent OOM on pathological sessions
|
|
203
|
+
if len(points) > MAX_SEED_POINTS:
|
|
204
|
+
raise ValueError(
|
|
205
|
+
f"Session {session_id} has {len(points)} points, exceeds limit {MAX_SEED_POINTS}"
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
logger.debug(f"Seeding tracker from Qdrant: {len(points)} points for session {session_id}")
|
|
209
|
+
|
|
210
|
+
with self._connect() as conn:
|
|
211
|
+
for point in points:
|
|
212
|
+
payload = point.payload or {}
|
|
213
|
+
conn.execute("""
|
|
214
|
+
INSERT OR REPLACE INTO indexed_points
|
|
215
|
+
(session_id, content_type, content_index, content_hash, qdrant_point_id)
|
|
216
|
+
VALUES (?, ?, ?, ?, ?)
|
|
217
|
+
""", (
|
|
218
|
+
session_id,
|
|
219
|
+
payload.get("content_type") or payload.get("type"),
|
|
220
|
+
payload.get("chunk_index", 0),
|
|
221
|
+
payload.get("content_hash"),
|
|
222
|
+
str(point.id)
|
|
223
|
+
))
|
|
224
|
+
conn.execute("""
|
|
225
|
+
INSERT OR REPLACE INTO session_meta (session_id, config_hash, status)
|
|
226
|
+
VALUES (?, ?, 'complete')
|
|
227
|
+
""", (session_id, self.config_hash))
|