claude-jacked 0.2.7__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.7.dist-info → claude_jacked-0.2.9.dist-info}/RECORD +18 -11
- jacked/cli.py +391 -22
- jacked/client.py +78 -28
- jacked/data/agents/double-check-reviewer.md +42 -0
- jacked/data/commands/audit-rules.md +103 -0
- jacked/data/commands/dc.md +36 -3
- jacked/data/commands/learn.md +89 -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/index_write_tracker.py +227 -0
- jacked/indexer.py +189 -163
- jacked/searcher.py +4 -0
- claude_jacked-0.2.7.dist-info/METADATA +0 -580
- {claude_jacked-0.2.7.dist-info → claude_jacked-0.2.9.dist-info}/WHEEL +0 -0
- {claude_jacked-0.2.7.dist-info → claude_jacked-0.2.9.dist-info}/entry_points.txt +0 -0
- {claude_jacked-0.2.7.dist-info → claude_jacked-0.2.9.dist-info}/licenses/LICENSE +0 -0
|
@@ -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))
|