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.
Files changed (33) hide show
  1. claude_jacked-0.2.9.dist-info/METADATA +523 -0
  2. claude_jacked-0.2.9.dist-info/RECORD +33 -0
  3. jacked/cli.py +752 -47
  4. jacked/client.py +196 -29
  5. jacked/data/agents/code-simplicity-reviewer.md +87 -0
  6. jacked/data/agents/defensive-error-handler.md +93 -0
  7. jacked/data/agents/double-check-reviewer.md +214 -0
  8. jacked/data/agents/git-pr-workflow-manager.md +149 -0
  9. jacked/data/agents/issue-pr-coordinator.md +131 -0
  10. jacked/data/agents/pr-workflow-checker.md +199 -0
  11. jacked/data/agents/readme-maintainer.md +123 -0
  12. jacked/data/agents/test-coverage-engineer.md +155 -0
  13. jacked/data/agents/test-coverage-improver.md +139 -0
  14. jacked/data/agents/wiki-documentation-architect.md +580 -0
  15. jacked/data/commands/audit-rules.md +103 -0
  16. jacked/data/commands/dc.md +155 -0
  17. jacked/data/commands/learn.md +89 -0
  18. jacked/data/commands/pr.md +4 -0
  19. jacked/data/commands/redo.md +85 -0
  20. jacked/data/commands/techdebt.md +115 -0
  21. jacked/data/prompts/security_gatekeeper.txt +58 -0
  22. jacked/data/rules/jacked_behaviors.md +11 -0
  23. jacked/data/skills/jacked/SKILL.md +162 -0
  24. jacked/index_write_tracker.py +227 -0
  25. jacked/indexer.py +255 -129
  26. jacked/retriever.py +389 -137
  27. jacked/searcher.py +65 -13
  28. jacked/transcript.py +339 -0
  29. claude_jacked-0.2.3.dist-info/METADATA +0 -483
  30. claude_jacked-0.2.3.dist-info/RECORD +0 -13
  31. {claude_jacked-0.2.3.dist-info → claude_jacked-0.2.9.dist-info}/WHEEL +0 -0
  32. {claude_jacked-0.2.3.dist-info → claude_jacked-0.2.9.dist-info}/entry_points.txt +0 -0
  33. {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))