@exero1/claudecontext 0.1.0

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 (78) hide show
  1. package/README.md +286 -0
  2. package/dist/installer/install.d.ts +12 -0
  3. package/dist/installer/install.d.ts.map +1 -0
  4. package/dist/installer/install.js +714 -0
  5. package/dist/installer/install.js.map +1 -0
  6. package/dist/src/cache/budget.d.ts +48 -0
  7. package/dist/src/cache/budget.d.ts.map +1 -0
  8. package/dist/src/cache/budget.js +55 -0
  9. package/dist/src/cache/budget.js.map +1 -0
  10. package/dist/src/cache/compressor.d.ts +21 -0
  11. package/dist/src/cache/compressor.d.ts.map +1 -0
  12. package/dist/src/cache/compressor.js +89 -0
  13. package/dist/src/cache/compressor.js.map +1 -0
  14. package/dist/src/cache/levels.d.ts +16 -0
  15. package/dist/src/cache/levels.d.ts.map +1 -0
  16. package/dist/src/cache/levels.js +41 -0
  17. package/dist/src/cache/levels.js.map +1 -0
  18. package/dist/src/cache/manager.d.ts +38 -0
  19. package/dist/src/cache/manager.d.ts.map +1 -0
  20. package/dist/src/cache/manager.js +196 -0
  21. package/dist/src/cache/manager.js.map +1 -0
  22. package/dist/src/cli.d.ts +3 -0
  23. package/dist/src/cli.d.ts.map +1 -0
  24. package/dist/src/cli.js +279 -0
  25. package/dist/src/cli.js.map +1 -0
  26. package/dist/src/detection/areas.d.ts +13 -0
  27. package/dist/src/detection/areas.d.ts.map +1 -0
  28. package/dist/src/detection/areas.js +96 -0
  29. package/dist/src/detection/areas.js.map +1 -0
  30. package/dist/src/detection/task.d.ts +28 -0
  31. package/dist/src/detection/task.d.ts.map +1 -0
  32. package/dist/src/detection/task.js +77 -0
  33. package/dist/src/detection/task.js.map +1 -0
  34. package/dist/src/gating/gate.d.ts +38 -0
  35. package/dist/src/gating/gate.d.ts.map +1 -0
  36. package/dist/src/gating/gate.js +74 -0
  37. package/dist/src/gating/gate.js.map +1 -0
  38. package/dist/src/graph/edges.d.ts +41 -0
  39. package/dist/src/graph/edges.d.ts.map +1 -0
  40. package/dist/src/graph/edges.js +115 -0
  41. package/dist/src/graph/edges.js.map +1 -0
  42. package/dist/src/graph/indexer.d.ts +38 -0
  43. package/dist/src/graph/indexer.d.ts.map +1 -0
  44. package/dist/src/graph/indexer.js +228 -0
  45. package/dist/src/graph/indexer.js.map +1 -0
  46. package/dist/src/graph/traversal.d.ts +25 -0
  47. package/dist/src/graph/traversal.d.ts.map +1 -0
  48. package/dist/src/graph/traversal.js +173 -0
  49. package/dist/src/graph/traversal.js.map +1 -0
  50. package/dist/src/index.d.ts +3 -0
  51. package/dist/src/index.d.ts.map +1 -0
  52. package/dist/src/index.js +82 -0
  53. package/dist/src/index.js.map +1 -0
  54. package/dist/src/indexing/codebase.d.ts +30 -0
  55. package/dist/src/indexing/codebase.d.ts.map +1 -0
  56. package/dist/src/indexing/codebase.js +127 -0
  57. package/dist/src/indexing/codebase.js.map +1 -0
  58. package/dist/src/markdown/writer.d.ts +34 -0
  59. package/dist/src/markdown/writer.d.ts.map +1 -0
  60. package/dist/src/markdown/writer.js +96 -0
  61. package/dist/src/markdown/writer.js.map +1 -0
  62. package/dist/src/server.d.ts +15 -0
  63. package/dist/src/server.d.ts.map +1 -0
  64. package/dist/src/server.js +520 -0
  65. package/dist/src/server.js.map +1 -0
  66. package/dist/src/storage/db.d.ts +123 -0
  67. package/dist/src/storage/db.d.ts.map +1 -0
  68. package/dist/src/storage/db.js +318 -0
  69. package/dist/src/storage/db.js.map +1 -0
  70. package/dist/src/utils/glob.d.ts +11 -0
  71. package/dist/src/utils/glob.d.ts.map +1 -0
  72. package/dist/src/utils/glob.js +20 -0
  73. package/dist/src/utils/glob.js.map +1 -0
  74. package/hooks/post-write.mjs +57 -0
  75. package/hooks/pre-compact.mjs +44 -0
  76. package/hooks/pre-tool-use.mjs +87 -0
  77. package/hooks/session-start.mjs +54 -0
  78. package/package.json +51 -0
@@ -0,0 +1,123 @@
1
+ export interface Task {
2
+ id: string;
3
+ title: string;
4
+ goal: string;
5
+ status: 'active' | 'completed' | 'archived';
6
+ files_touched: string;
7
+ open_questions: string;
8
+ decisions: string;
9
+ created_at: number;
10
+ updated_at: number;
11
+ }
12
+ export interface FileIndex {
13
+ path: string;
14
+ last_touched: number;
15
+ touch_count: number;
16
+ relevance_score: number;
17
+ summary: string;
18
+ }
19
+ export interface Session {
20
+ session_id: string;
21
+ task_id: string;
22
+ started_at: number;
23
+ last_active: number;
24
+ }
25
+ export interface Archive {
26
+ id: string;
27
+ task_id: string;
28
+ summary: string;
29
+ created_at: number;
30
+ source_level: string;
31
+ }
32
+ export interface TestResult {
33
+ id: string;
34
+ task_id: string;
35
+ passed: number;
36
+ failed: number;
37
+ skipped: number;
38
+ summary: string;
39
+ failing_tests: string;
40
+ recorded_at: number;
41
+ }
42
+ export type EdgeType = 'imports' | 'subsystem_of' | 'co_modified' | 'always_with' | 'informed_by' | 'caused_by' | 'breaks_with';
43
+ export type NodeType = 'file' | 'task' | 'archive' | 'area';
44
+ export interface Edge {
45
+ id: string;
46
+ from_node: string;
47
+ from_type: NodeType;
48
+ to_node: string;
49
+ to_type: NodeType;
50
+ edge_type: EdgeType;
51
+ weight: number;
52
+ created_at: number;
53
+ last_reinforced: number;
54
+ session_count: number;
55
+ co_occurrence_count: number;
56
+ confidence: number;
57
+ }
58
+ export declare class Db {
59
+ private db;
60
+ private lockPath;
61
+ constructor(dbPath: string);
62
+ private writeLock;
63
+ static isLocked(stateDir: string): boolean;
64
+ getTask(id: string): Task | undefined;
65
+ getActiveTasks(): Task[];
66
+ ensureTask(id: string, title: string): void;
67
+ upsertTask(task: Omit<Task, 'created_at' | 'updated_at'> & {
68
+ created_at?: number;
69
+ updated_at?: number;
70
+ }): void;
71
+ getFile(path: string): FileIndex | undefined;
72
+ touchFile(path: string): void;
73
+ getTopFiles(limit?: number): FileIndex[];
74
+ /**
75
+ * Return paths of files touched within the last `windowMs` milliseconds.
76
+ * Used by ingest-diff to find co-session files for co_modified edge creation.
77
+ */
78
+ getRecentlyTouchedFiles(windowMs?: number, excludePath?: string): string[];
79
+ upsertSession(session: Session): void;
80
+ getSession(sessionId: string): Session | undefined;
81
+ insertArchive(entry: Archive): void;
82
+ getArchiveForTask(taskId: string): Archive[];
83
+ getRecentArchive(limit?: number): Archive[];
84
+ insertTestResult(result: TestResult): void;
85
+ getLatestTestResult(taskId: string): TestResult | undefined;
86
+ updateRelevanceScore(path: string, score: number): void;
87
+ getEdge(id: string): Edge | undefined;
88
+ getEdgesFrom(fromNode: string, limit?: number): Edge[];
89
+ getEdgesTo(toNode: string, limit?: number): Edge[];
90
+ getEdgesByType(edgeType: EdgeType, limit?: number): Edge[];
91
+ upsertEdge(edge: Omit<Edge, 'created_at' | 'last_reinforced' | 'session_count' | 'co_occurrence_count' | 'confidence'> & Partial<Pick<Edge, 'created_at' | 'last_reinforced' | 'session_count' | 'co_occurrence_count' | 'confidence'>>): void;
92
+ deleteEdge(id: string): void;
93
+ countEdgesByType(): Record<EdgeType, number>;
94
+ getAllEdgesForDecay(): Edge[];
95
+ pruneWeakEdges(minWeight?: number, maxAgeDays?: number): number;
96
+ bulkUpdateEdgeWeights(updates: Array<{
97
+ id: string;
98
+ weight: number;
99
+ last_reinforced: number;
100
+ }>): void;
101
+ /**
102
+ * Returns age of the oldest edge in the database, in days.
103
+ * Used for context_status "Graph age" display.
104
+ */
105
+ getOldestEdgeAgeDays(): number;
106
+ /**
107
+ * Wrap multiple writes in a single SQLite transaction.
108
+ * Provides atomicity: all operations succeed or all are rolled back.
109
+ */
110
+ runInTransaction(fn: () => void): void;
111
+ /**
112
+ * Batch-touch multiple files in a single transaction.
113
+ */
114
+ touchFilesInBatch(paths: string[]): void;
115
+ close(): void;
116
+ getStats(): {
117
+ taskCount: number;
118
+ fileCount: number;
119
+ edgeCount: number;
120
+ archiveCount: number;
121
+ };
122
+ }
123
+ //# sourceMappingURL=db.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.d.ts","sourceRoot":"","sources":["../../../src/storage/db.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,318 @@
1
+ import { DatabaseSync } from 'node:sqlite';
2
+ import { existsSync, mkdirSync, writeFileSync, readFileSync, statSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ const SCHEMA = `
5
+ PRAGMA journal_mode=WAL;
6
+ PRAGMA synchronous=NORMAL;
7
+ PRAGMA cache_size=-64000;
8
+ PRAGMA foreign_keys=ON;
9
+ PRAGMA busy_timeout=5000;
10
+
11
+ CREATE TABLE IF NOT EXISTS tasks (
12
+ id TEXT PRIMARY KEY,
13
+ title TEXT NOT NULL DEFAULT '',
14
+ goal TEXT DEFAULT '',
15
+ status TEXT DEFAULT 'active',
16
+ files_touched TEXT DEFAULT '[]',
17
+ open_questions TEXT DEFAULT '[]',
18
+ decisions TEXT DEFAULT '[]',
19
+ created_at INTEGER NOT NULL,
20
+ updated_at INTEGER NOT NULL
21
+ );
22
+
23
+ CREATE TABLE IF NOT EXISTS file_index (
24
+ path TEXT PRIMARY KEY,
25
+ last_touched INTEGER DEFAULT 0,
26
+ touch_count INTEGER DEFAULT 0,
27
+ relevance_score REAL DEFAULT 0.0,
28
+ summary TEXT DEFAULT ''
29
+ );
30
+
31
+ CREATE TABLE IF NOT EXISTS sessions (
32
+ session_id TEXT PRIMARY KEY,
33
+ task_id TEXT NOT NULL DEFAULT '',
34
+ started_at INTEGER NOT NULL,
35
+ last_active INTEGER NOT NULL
36
+ );
37
+
38
+ CREATE TABLE IF NOT EXISTS archive (
39
+ id TEXT PRIMARY KEY,
40
+ task_id TEXT NOT NULL DEFAULT '',
41
+ summary TEXT NOT NULL DEFAULT '',
42
+ created_at INTEGER NOT NULL,
43
+ source_level TEXT NOT NULL DEFAULT 'L4'
44
+ );
45
+
46
+ CREATE TABLE IF NOT EXISTS edges (
47
+ id TEXT PRIMARY KEY,
48
+ from_node TEXT NOT NULL,
49
+ from_type TEXT NOT NULL,
50
+ to_node TEXT NOT NULL,
51
+ to_type TEXT NOT NULL,
52
+ edge_type TEXT NOT NULL,
53
+ weight REAL DEFAULT 1.0,
54
+ created_at INTEGER NOT NULL,
55
+ last_reinforced INTEGER NOT NULL,
56
+ session_count INTEGER DEFAULT 1,
57
+ co_occurrence_count INTEGER DEFAULT 1,
58
+ confidence REAL DEFAULT 1.0
59
+ );
60
+
61
+ CREATE INDEX IF NOT EXISTS idx_edges_from ON edges(from_node, weight DESC);
62
+ CREATE INDEX IF NOT EXISTS idx_edges_to ON edges(to_node, weight DESC);
63
+ CREATE INDEX IF NOT EXISTS idx_edges_type ON edges(edge_type);
64
+ CREATE INDEX IF NOT EXISTS idx_file_score ON file_index(relevance_score DESC, last_touched DESC);
65
+ CREATE INDEX IF NOT EXISTS idx_tasks_status ON tasks(status);
66
+
67
+ CREATE TABLE IF NOT EXISTS test_results (
68
+ id TEXT PRIMARY KEY,
69
+ task_id TEXT NOT NULL DEFAULT '',
70
+ passed INTEGER DEFAULT 0,
71
+ failed INTEGER DEFAULT 0,
72
+ skipped INTEGER DEFAULT 0,
73
+ summary TEXT DEFAULT '',
74
+ failing_tests TEXT DEFAULT '[]',
75
+ recorded_at INTEGER NOT NULL
76
+ );
77
+ `;
78
+ export class Db {
79
+ db;
80
+ lockPath;
81
+ constructor(dbPath) {
82
+ const dir = join(dbPath, '..');
83
+ if (!existsSync(dir)) {
84
+ mkdirSync(dir, { recursive: true });
85
+ }
86
+ this.lockPath = join(dir, '.session.lock');
87
+ this.db = new DatabaseSync(dbPath);
88
+ this.db.exec(SCHEMA);
89
+ this.writeLock();
90
+ }
91
+ writeLock() {
92
+ writeFileSync(this.lockPath, String(process.pid), 'utf8');
93
+ }
94
+ static isLocked(stateDir) {
95
+ const lockPath = join(stateDir, '.session.lock');
96
+ if (!existsSync(lockPath))
97
+ return false;
98
+ try {
99
+ const { mtimeMs } = statSync(lockPath);
100
+ // Locks older than 2 hours are considered stale (handles unclean shutdown)
101
+ const MAX_LOCK_AGE_MS = 2 * 60 * 60 * 1000;
102
+ if (Date.now() - mtimeMs > MAX_LOCK_AGE_MS)
103
+ return false;
104
+ const pid = parseInt(readFileSync(lockPath, 'utf8').trim(), 10);
105
+ if (isNaN(pid))
106
+ return false;
107
+ process.kill(pid, 0); // throws ESRCH if process doesn't exist
108
+ return pid !== process.pid;
109
+ }
110
+ catch {
111
+ return false;
112
+ }
113
+ }
114
+ // ── Tasks ──────────────────────────────────────────────────────────────────
115
+ getTask(id) {
116
+ const stmt = this.db.prepare('SELECT * FROM tasks WHERE id = ?');
117
+ return stmt.get(id);
118
+ }
119
+ getActiveTasks() {
120
+ const stmt = this.db.prepare("SELECT * FROM tasks WHERE status = 'active' ORDER BY updated_at DESC");
121
+ return stmt.all();
122
+ }
123
+ ensureTask(id, title) {
124
+ const now = Date.now();
125
+ this.db.prepare(`
126
+ INSERT OR IGNORE INTO tasks (id, title, goal, status, files_touched, open_questions, decisions, created_at, updated_at)
127
+ VALUES (?, ?, '', 'active', '[]', '', '', ?, ?)
128
+ `).run(id, title, now, now);
129
+ }
130
+ upsertTask(task) {
131
+ const now = Date.now();
132
+ this.db.prepare(`
133
+ INSERT INTO tasks (id, title, goal, status, files_touched, open_questions, decisions, created_at, updated_at)
134
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
135
+ ON CONFLICT(id) DO UPDATE SET
136
+ title = excluded.title,
137
+ goal = excluded.goal,
138
+ status = excluded.status,
139
+ files_touched = excluded.files_touched,
140
+ open_questions = excluded.open_questions,
141
+ decisions = excluded.decisions,
142
+ updated_at = excluded.updated_at
143
+ `).run(task.id, task.title, task.goal, task.status, task.files_touched, task.open_questions, task.decisions, task.created_at ?? now, task.updated_at ?? now);
144
+ }
145
+ // ── File Index ─────────────────────────────────────────────────────────────
146
+ getFile(path) {
147
+ return this.db.prepare('SELECT * FROM file_index WHERE path = ?').get(path);
148
+ }
149
+ touchFile(path) {
150
+ const now = Date.now();
151
+ this.db.prepare(`
152
+ INSERT INTO file_index (path, last_touched, touch_count, relevance_score, summary)
153
+ VALUES (?, ?, 1, 0.0, '')
154
+ ON CONFLICT(path) DO UPDATE SET
155
+ last_touched = ?,
156
+ touch_count = touch_count + 1
157
+ `).run(path, now, now);
158
+ }
159
+ getTopFiles(limit = 20) {
160
+ return this.db.prepare('SELECT * FROM file_index ORDER BY relevance_score DESC, last_touched DESC, touch_count DESC LIMIT ?').all(limit);
161
+ }
162
+ /**
163
+ * Return paths of files touched within the last `windowMs` milliseconds.
164
+ * Used by ingest-diff to find co-session files for co_modified edge creation.
165
+ */
166
+ getRecentlyTouchedFiles(windowMs = 30 * 60 * 1000, excludePath) {
167
+ const cutoff = Date.now() - windowMs;
168
+ const rows = this.db.prepare('SELECT path FROM file_index WHERE last_touched > ? ORDER BY last_touched DESC LIMIT 50').all(cutoff);
169
+ return rows.map(r => r.path).filter(p => p !== excludePath);
170
+ }
171
+ // ── Sessions ───────────────────────────────────────────────────────────────
172
+ upsertSession(session) {
173
+ this.db.prepare(`
174
+ INSERT INTO sessions (session_id, task_id, started_at, last_active)
175
+ VALUES (?, ?, ?, ?)
176
+ ON CONFLICT(session_id) DO UPDATE SET
177
+ task_id = excluded.task_id,
178
+ last_active = excluded.last_active
179
+ `).run(session.session_id, session.task_id, session.started_at, session.last_active);
180
+ }
181
+ getSession(sessionId) {
182
+ return this.db.prepare('SELECT * FROM sessions WHERE session_id = ?').get(sessionId);
183
+ }
184
+ // ── Archive ────────────────────────────────────────────────────────────────
185
+ insertArchive(entry) {
186
+ this.db.prepare(`
187
+ INSERT OR REPLACE INTO archive (id, task_id, summary, created_at, source_level)
188
+ VALUES (?, ?, ?, ?, ?)
189
+ `).run(entry.id, entry.task_id, entry.summary, entry.created_at, entry.source_level);
190
+ }
191
+ getArchiveForTask(taskId) {
192
+ return this.db.prepare('SELECT * FROM archive WHERE task_id = ? ORDER BY created_at DESC').all(taskId);
193
+ }
194
+ getRecentArchive(limit = 5) {
195
+ return this.db.prepare('SELECT * FROM archive ORDER BY created_at DESC LIMIT ?').all(limit);
196
+ }
197
+ // ── Test Results ───────────────────────────────────────────────────────────
198
+ insertTestResult(result) {
199
+ this.db.prepare('INSERT OR REPLACE INTO test_results (id, task_id, passed, failed, skipped, summary, failing_tests, recorded_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)').run(result.id, result.task_id, result.passed, result.failed, result.skipped, result.summary, result.failing_tests, result.recorded_at);
200
+ }
201
+ getLatestTestResult(taskId) {
202
+ return this.db.prepare('SELECT * FROM test_results WHERE task_id = ? ORDER BY recorded_at DESC LIMIT 1').get(taskId);
203
+ }
204
+ // ── File Index (score update) ───────────────────────────────────────────────
205
+ updateRelevanceScore(path, score) {
206
+ this.db.prepare('UPDATE file_index SET relevance_score = ? WHERE path = ?').run(score, path);
207
+ }
208
+ // ── Edges ──────────────────────────────────────────────────────────────────
209
+ getEdge(id) {
210
+ return this.db.prepare('SELECT * FROM edges WHERE id = ?').get(id);
211
+ }
212
+ getEdgesFrom(fromNode, limit = 50) {
213
+ return this.db.prepare('SELECT * FROM edges WHERE from_node = ? ORDER BY weight DESC LIMIT ?').all(fromNode, limit);
214
+ }
215
+ getEdgesTo(toNode, limit = 50) {
216
+ return this.db.prepare('SELECT * FROM edges WHERE to_node = ? ORDER BY weight DESC LIMIT ?').all(toNode, limit);
217
+ }
218
+ getEdgesByType(edgeType, limit = 100) {
219
+ return this.db.prepare('SELECT * FROM edges WHERE edge_type = ? ORDER BY weight DESC LIMIT ?').all(edgeType, limit);
220
+ }
221
+ upsertEdge(edge) {
222
+ const now = Date.now();
223
+ this.db.prepare(`
224
+ INSERT INTO edges (id, from_node, from_type, to_node, to_type, edge_type, weight, created_at, last_reinforced, session_count, co_occurrence_count, confidence)
225
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
226
+ ON CONFLICT(id) DO UPDATE SET
227
+ weight = excluded.weight,
228
+ last_reinforced = excluded.last_reinforced,
229
+ session_count = excluded.session_count,
230
+ co_occurrence_count = excluded.co_occurrence_count,
231
+ confidence = excluded.confidence
232
+ `).run(edge.id, edge.from_node, edge.from_type, edge.to_node, edge.to_type, edge.edge_type, edge.weight ?? 1.0, edge.created_at ?? now, edge.last_reinforced ?? now, edge.session_count ?? 1, edge.co_occurrence_count ?? 1, edge.confidence ?? 1.0);
233
+ }
234
+ deleteEdge(id) {
235
+ this.db.prepare('DELETE FROM edges WHERE id = ?').run(id);
236
+ }
237
+ countEdgesByType() {
238
+ const rows = this.db.prepare('SELECT edge_type, COUNT(*) as count FROM edges GROUP BY edge_type').all();
239
+ const result = {};
240
+ for (const row of rows) {
241
+ result[row.edge_type] = row.count;
242
+ }
243
+ return result;
244
+ }
245
+ getAllEdgesForDecay() {
246
+ return this.db.prepare('SELECT * FROM edges').all();
247
+ }
248
+ pruneWeakEdges(minWeight = 0.1, maxAgeDays = 90) {
249
+ const cutoff = Date.now() - maxAgeDays * 24 * 60 * 60 * 1000;
250
+ const result = this.db.prepare('DELETE FROM edges WHERE weight < ? AND last_reinforced < ?').run(minWeight, cutoff);
251
+ return Number(result.changes);
252
+ }
253
+ bulkUpdateEdgeWeights(updates) {
254
+ const stmt = this.db.prepare('UPDATE edges SET weight = ?, last_reinforced = ? WHERE id = ?');
255
+ for (const u of updates) {
256
+ stmt.run(u.weight, u.last_reinforced, u.id);
257
+ }
258
+ }
259
+ /**
260
+ * Returns age of the oldest edge in the database, in days.
261
+ * Used for context_status "Graph age" display.
262
+ */
263
+ getOldestEdgeAgeDays() {
264
+ const row = this.db.prepare('SELECT MIN(created_at) as oldest FROM edges').get();
265
+ if (!row || row.oldest === null || row.oldest === undefined)
266
+ return 0;
267
+ const oldestMs = Number(row.oldest);
268
+ const now = Date.now();
269
+ // Sanity check: reject sentinel 0 or any future timestamp
270
+ if (oldestMs === 0 || oldestMs > now)
271
+ return 0;
272
+ const days = Math.floor((now - oldestMs) / (24 * 60 * 60 * 1000));
273
+ // Cap at 10 years to avoid absurd display from corrupt data
274
+ return Math.min(days, 3650);
275
+ }
276
+ /**
277
+ * Wrap multiple writes in a single SQLite transaction.
278
+ * Provides atomicity: all operations succeed or all are rolled back.
279
+ */
280
+ runInTransaction(fn) {
281
+ this.db.prepare('BEGIN').run();
282
+ try {
283
+ fn();
284
+ this.db.prepare('COMMIT').run();
285
+ }
286
+ catch (err) {
287
+ this.db.prepare('ROLLBACK').run();
288
+ throw err;
289
+ }
290
+ }
291
+ /**
292
+ * Batch-touch multiple files in a single transaction.
293
+ */
294
+ touchFilesInBatch(paths) {
295
+ this.runInTransaction(() => {
296
+ for (const path of paths) {
297
+ this.touchFile(path);
298
+ }
299
+ });
300
+ }
301
+ // ── Utilities ──────────────────────────────────────────────────────────────
302
+ close() {
303
+ try {
304
+ this.db.close();
305
+ }
306
+ catch {
307
+ // ignore
308
+ }
309
+ }
310
+ getStats() {
311
+ const taskCount = Number(this.db.prepare('SELECT COUNT(*) as n FROM tasks').get().n);
312
+ const fileCount = Number(this.db.prepare('SELECT COUNT(*) as n FROM file_index').get().n);
313
+ const edgeCount = Number(this.db.prepare('SELECT COUNT(*) as n FROM edges').get().n);
314
+ const archiveCount = Number(this.db.prepare('SELECT COUNT(*) as n FROM archive').get().n);
315
+ return { taskCount, fileCount, edgeCount, archiveCount };
316
+ }
317
+ }
318
+ //# sourceMappingURL=db.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"db.js","sourceRoot":"","sources":["../../../src/storage/db.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,aAAa,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AACvF,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AA0EjC,MAAM,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAyEd,CAAC;AAEF,MAAM,OAAO,EAAE;IACL,EAAE,CAAe;IACjB,QAAQ,CAAS;IAEzB,YAAY,MAAc;QACxB,MAAM,GAAG,GAAG,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,CAAC;QAC/B,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YACrB,SAAS,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACtC,CAAC;QACD,IAAI,CAAC,QAAQ,GAAG,IAAI,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC;QAC3C,IAAI,CAAC,EAAE,GAAG,IAAI,YAAY,CAAC,MAAM,CAAC,CAAC;QACnC,IAAI,CAAC,EAAE,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QACrB,IAAI,CAAC,SAAS,EAAE,CAAC;IACnB,CAAC;IAEO,SAAS;QACf,aAAa,CAAC,IAAI,CAAC,QAAQ,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC;IAC5D,CAAC;IAED,MAAM,CAAC,QAAQ,CAAC,QAAgB;QAC9B,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE,eAAe,CAAC,CAAC;QACjD,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;YAAE,OAAO,KAAK,CAAC;QACxC,IAAI,CAAC;YACH,MAAM,EAAE,OAAO,EAAE,GAAG,QAAQ,CAAC,QAAQ,CAAC,CAAC;YACvC,2EAA2E;YAC3E,MAAM,eAAe,GAAG,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;YAC3C,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,OAAO,GAAG,eAAe;gBAAE,OAAO,KAAK,CAAC;YAEzD,MAAM,GAAG,GAAG,QAAQ,CAAC,YAAY,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC,CAAC;YAChE,IAAI,KAAK,CAAC,GAAG,CAAC;gBAAE,OAAO,KAAK,CAAC;YAC7B,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,wCAAwC;YAC9D,OAAO,GAAG,KAAK,OAAO,CAAC,GAAG,CAAC;QAC7B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,KAAK,CAAC;QACf,CAAC;IACH,CAAC;IAED,8EAA8E;IAE9E,OAAO,CAAC,EAAU;QAChB,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,kCAAkC,CAAC,CAAC;QACjE,OAAO,IAAI,CAAC,GAAG,CAAC,EAAE,CAAqB,CAAC;IAC1C,CAAC;IAED,cAAc;QACZ,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,sEAAsE,CAAC,CAAC;QACrG,OAAO,IAAI,CAAC,GAAG,EAAuB,CAAC;IACzC,CAAC;IAED,UAAU,CAAC,EAAU,EAAE,KAAa;QAClC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;;KAGf,CAAC,CAAC,GAAG,CAAC,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;IAC9B,CAAC;IAED,UAAU,CAAC,IAA4F;QACrG,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;;;;;;;;;;KAWf,CAAC,CAAC,GAAG,CACJ,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,MAAM,EAC3C,IAAI,CAAC,aAAa,EAAE,IAAI,CAAC,cAAc,EAAE,IAAI,CAAC,SAAS,EACvD,IAAI,CAAC,UAAU,IAAI,GAAG,EAAE,IAAI,CAAC,UAAU,IAAI,GAAG,CAC/C,CAAC;IACJ,CAAC;IAED,8EAA8E;IAE9E,OAAO,CAAC,IAAY;QAClB,OAAO,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,yCAAyC,CAAC,CAAC,GAAG,CAAC,IAAI,CAA0B,CAAC;IACvG,CAAC;IAED,SAAS,CAAC,IAAY;QACpB,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;;;;;KAMf,CAAC,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;IACzB,CAAC;IAED,WAAW,CAAC,KAAK,GAAG,EAAE;QACpB,OAAO,IAAI,CAAC,EAAE,CAAC,OAAO,CACpB,qGAAqG,CACtG,CAAC,GAAG,CAAC,KAAK,CAA2B,CAAC;IACzC,CAAC;IAED;;;OAGG;IACH,uBAAuB,CAAC,QAAQ,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,WAAoB;QACrE,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,QAAQ,CAAC;QACrC,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAC1B,wFAAwF,CACzF,CAAC,GAAG,CAAC,MAAM,CAA4B,CAAC;QACzC,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,WAAW,CAAC,CAAC;IAC9D,CAAC;IAED,8EAA8E;IAE9E,aAAa,CAAC,OAAgB;QAC5B,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;;;;;KAMf,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,UAAU,EAAE,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,UAAU,EAAE,OAAO,CAAC,WAAW,CAAC,CAAC;IACvF,CAAC;IAED,UAAU,CAAC,SAAiB;QAC1B,OAAO,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,6CAA6C,CAAC,CAAC,GAAG,CAAC,SAAS,CAAwB,CAAC;IAC9G,CAAC;IAED,8EAA8E;IAE9E,aAAa,CAAC,KAAc;QAC1B,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;;KAGf,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,KAAK,CAAC,UAAU,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC;IACvF,CAAC;IAED,iBAAiB,CAAC,MAAc;QAC9B,OAAO,IAAI,CAAC,EAAE,CAAC,OAAO,CACpB,kEAAkE,CACnE,CAAC,GAAG,CAAC,MAAM,CAAyB,CAAC;IACxC,CAAC;IAED,gBAAgB,CAAC,KAAK,GAAG,CAAC;QACxB,OAAO,IAAI,CAAC,EAAE,CAAC,OAAO,CACpB,wDAAwD,CACzD,CAAC,GAAG,CAAC,KAAK,CAAyB,CAAC;IACvC,CAAC;IAED,8EAA8E;IAE9E,gBAAgB,CAAC,MAAkB;QACjC,IAAI,CAAC,EAAE,CAAC,OAAO,CACb,iJAAiJ,CAClJ,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,aAAa,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC;IAC3I,CAAC;IAED,mBAAmB,CAAC,MAAc;QAChC,OAAO,IAAI,CAAC,EAAE,CAAC,OAAO,CACpB,gFAAgF,CACjF,CAAC,GAAG,CAAC,MAAM,CAA2B,CAAC;IAC1C,CAAC;IAED,+EAA+E;IAE/E,oBAAoB,CAAC,IAAY,EAAE,KAAa;QAC9C,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,0DAA0D,CAAC,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;IAC/F,CAAC;IAED,8EAA8E;IAE9E,OAAO,CAAC,EAAU;QAChB,OAAO,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,kCAAkC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAqB,CAAC;IACzF,CAAC;IAED,YAAY,CAAC,QAAgB,EAAE,KAAK,GAAG,EAAE;QACvC,OAAO,IAAI,CAAC,EAAE,CAAC,OAAO,CACpB,sEAAsE,CACvE,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAsB,CAAC;IAC9C,CAAC;IAED,UAAU,CAAC,MAAc,EAAE,KAAK,GAAG,EAAE;QACnC,OAAO,IAAI,CAAC,EAAE,CAAC,OAAO,CACpB,oEAAoE,CACrE,CAAC,GAAG,CAAC,MAAM,EAAE,KAAK,CAAsB,CAAC;IAC5C,CAAC;IAED,cAAc,CAAC,QAAkB,EAAE,KAAK,GAAG,GAAG;QAC5C,OAAO,IAAI,CAAC,EAAE,CAAC,OAAO,CACpB,sEAAsE,CACvE,CAAC,GAAG,CAAC,QAAQ,EAAE,KAAK,CAAsB,CAAC;IAC9C,CAAC;IAED,UAAU,CAAC,IAA4N;QACrO,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC;;;;;;;;;KASf,CAAC,CAAC,GAAG,CACJ,IAAI,CAAC,EAAE,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,SAAS,EACnF,IAAI,CAAC,MAAM,IAAI,GAAG,EAClB,IAAI,CAAC,UAAU,IAAI,GAAG,EACtB,IAAI,CAAC,eAAe,IAAI,GAAG,EAC3B,IAAI,CAAC,aAAa,IAAI,CAAC,EACvB,IAAI,CAAC,mBAAmB,IAAI,CAAC,EAC7B,IAAI,CAAC,UAAU,IAAI,GAAG,CACvB,CAAC;IACJ,CAAC;IAED,UAAU,CAAC,EAAU;QACnB,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,gCAAgC,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC5D,CAAC;IAED,gBAAgB;QACd,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAC1B,mEAAmE,CACpE,CAAC,GAAG,EAA4D,CAAC;QAClE,MAAM,MAAM,GAA2B,EAAE,CAAC;QAC1C,KAAK,MAAM,GAAG,IAAI,IAAI,EAAE,CAAC;YACvB,MAAM,CAAC,GAAG,CAAC,SAAS,CAAC,GAAG,GAAG,CAAC,KAAK,CAAC;QACpC,CAAC;QACD,OAAO,MAAkC,CAAC;IAC5C,CAAC;IAED,mBAAmB;QACjB,OAAO,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,qBAAqB,CAAC,CAAC,GAAG,EAAuB,CAAC;IAC3E,CAAC;IAED,cAAc,CAAC,SAAS,GAAG,GAAG,EAAE,UAAU,GAAG,EAAE;QAC7C,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,UAAU,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;QAC7D,MAAM,MAAM,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAC5B,4DAA4D,CAC7D,CAAC,GAAG,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QACzB,OAAO,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;IAChC,CAAC;IAED,qBAAqB,CAAC,OAAuE;QAC3F,MAAM,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,+DAA+D,CAAC,CAAC;QAC9F,KAAK,MAAM,CAAC,IAAI,OAAO,EAAE,CAAC;YACxB,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,eAAe,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC;QAC9C,CAAC;IACH,CAAC;IAED;;;OAGG;IACH,oBAAoB;QAClB,MAAM,GAAG,GAAG,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,6CAA6C,CAAC,CAAC,GAAG,EAEjE,CAAC;QACd,IAAI,CAAC,GAAG,IAAI,GAAG,CAAC,MAAM,KAAK,IAAI,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS;YAAE,OAAO,CAAC,CAAC;QACtE,MAAM,QAAQ,GAAG,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACpC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,0DAA0D;QAC1D,IAAI,QAAQ,KAAK,CAAC,IAAI,QAAQ,GAAG,GAAG;YAAE,OAAO,CAAC,CAAC;QAC/C,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,GAAG,GAAG,QAAQ,CAAC,GAAG,CAAC,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC,CAAC,CAAC;QAClE,4DAA4D;QAC5D,OAAO,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC9B,CAAC;IAED;;;OAGG;IACH,gBAAgB,CAAC,EAAc;QAC7B,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,GAAG,EAAE,CAAC;QAC/B,IAAI,CAAC;YACH,EAAE,EAAE,CAAC;YACL,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC,GAAG,EAAE,CAAC;QAClC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,GAAG,EAAE,CAAC;YAClC,MAAM,GAAG,CAAC;QACZ,CAAC;IACH,CAAC;IAED;;OAEG;IACH,iBAAiB,CAAC,KAAe;QAC/B,IAAI,CAAC,gBAAgB,CAAC,GAAG,EAAE;YACzB,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;gBACzB,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;YACvB,CAAC;QACH,CAAC,CAAC,CAAC;IACL,CAAC;IAED,8EAA8E;IAE9E,KAAK;QACH,IAAI,CAAC;YACH,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;QAClB,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;IACH,CAAC;IAED,QAAQ;QACN,MAAM,SAAS,GAAG,MAAM,CAAE,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,iCAAiC,CAAC,CAAC,GAAG,EAA6B,CAAC,CAAC,CAAC,CAAC;QACjH,MAAM,SAAS,GAAG,MAAM,CAAE,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,sCAAsC,CAAC,CAAC,GAAG,EAA6B,CAAC,CAAC,CAAC,CAAC;QACtH,MAAM,SAAS,GAAG,MAAM,CAAE,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,iCAAiC,CAAC,CAAC,GAAG,EAA6B,CAAC,CAAC,CAAC,CAAC;QACjH,MAAM,YAAY,GAAG,MAAM,CAAE,IAAI,CAAC,EAAE,CAAC,OAAO,CAAC,mCAAmC,CAAC,CAAC,GAAG,EAA6B,CAAC,CAAC,CAAC,CAAC;QACtH,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,CAAC;IAC3D,CAAC;CACF"}
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Minimal glob matching utility.
3
+ * Supports * (any chars except /) and ** (any chars including /).
4
+ * Used for area definition pattern matching.
5
+ */
6
+ export declare function matchGlob(filePath: string, glob: string): boolean;
7
+ /**
8
+ * Returns true if filePath matches any of the given glob patterns.
9
+ */
10
+ export declare function matchesAnyGlob(filePath: string, globs: string[]): boolean;
11
+ //# sourceMappingURL=glob.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glob.d.ts","sourceRoot":"","sources":["../../../src/utils/glob.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAOjE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,OAAO,CAEzE"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Minimal glob matching utility.
3
+ * Supports * (any chars except /) and ** (any chars including /).
4
+ * Used for area definition pattern matching.
5
+ */
6
+ export function matchGlob(filePath, glob) {
7
+ const escaped = glob
8
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&') // escape regex special chars (not * or ?)
9
+ .replace(/\*\*/g, '__GLOBSTAR__') // protect ** before replacing *
10
+ .replace(/\*/g, '[^/]*') // * matches anything except /
11
+ .replace(/__GLOBSTAR__/g, '.*'); // ** matches anything including /
12
+ return new RegExp(`^${escaped}$`).test(filePath);
13
+ }
14
+ /**
15
+ * Returns true if filePath matches any of the given glob patterns.
16
+ */
17
+ export function matchesAnyGlob(filePath, globs) {
18
+ return globs.some(g => matchGlob(filePath, g));
19
+ }
20
+ //# sourceMappingURL=glob.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"glob.js","sourceRoot":"","sources":["../../../src/utils/glob.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,UAAU,SAAS,CAAC,QAAgB,EAAE,IAAY;IACtD,MAAM,OAAO,GAAG,IAAI;SACjB,OAAO,CAAC,mBAAmB,EAAE,MAAM,CAAC,CAAC,0CAA0C;SAC/E,OAAO,CAAC,OAAO,EAAE,cAAc,CAAC,CAAQ,gCAAgC;SACxE,OAAO,CAAC,KAAK,EAAE,OAAO,CAAC,CAAe,8BAA8B;SACpE,OAAO,CAAC,eAAe,EAAE,IAAI,CAAC,CAAC,CAAQ,kCAAkC;IAC5E,OAAO,IAAI,MAAM,CAAC,IAAI,OAAO,GAAG,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;AACnD,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,QAAgB,EAAE,KAAe;IAC9D,OAAO,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,CAAC;AACjD,CAAC"}
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ClaudeContext PostToolUse hook (Write | Edit | MultiEdit).
4
+ * Ingests diff + creates/reinforces co_modified edges.
5
+ * Runs async — does not block tool completion.
6
+ * Timeout: 20s.
7
+ */
8
+
9
+ import { execFile } from 'node:child_process';
10
+ import { existsSync } from 'node:fs';
11
+ import { join, resolve } from 'node:path';
12
+
13
+ const projectRoot = resolve(process.env.CLAUDECONTEXT_PROJECT_ROOT ?? process.cwd());
14
+
15
+ function findCli() {
16
+ const local = join(projectRoot, 'node_modules', '.bin', 'claudecontext-cli');
17
+ if (existsSync(local)) return local;
18
+ return null;
19
+ }
20
+
21
+ // Read hook input from stdin
22
+ let input = '';
23
+ process.stdin.setEncoding('utf8');
24
+ process.stdin.on('data', chunk => { input += chunk; });
25
+ process.stdin.on('end', () => {
26
+ // Exit immediately — async work continues
27
+ // Claude Code doesn't wait for PostToolUse to complete
28
+ process.stdout.write('{}'); // minimal valid JSON response
29
+
30
+ try {
31
+ const hookData = JSON.parse(input);
32
+ const toolInput = hookData.tool_input ?? {};
33
+ const filePath = toolInput.file_path ?? toolInput.path ?? '';
34
+
35
+ if (!filePath) return;
36
+
37
+ const cli = findCli();
38
+ const args = cli
39
+ ? [cli, 'ingest-diff', filePath]
40
+ : ['npx', 'claudecontext-cli', 'ingest-diff', filePath];
41
+
42
+ const cmd = args[0];
43
+ const cmdArgs = args.slice(1);
44
+
45
+ execFile(cmd, cmdArgs, {
46
+ cwd: projectRoot,
47
+ env: { ...process.env, NODE_NO_WARNINGS: '1', CLAUDE_SESSION_ID: hookData.session_id ?? '' },
48
+ timeout: 19000,
49
+ }, (err) => {
50
+ if (err) {
51
+ process.stderr.write(`[claudecontext/post-write] ${err.message}\n`);
52
+ }
53
+ });
54
+ } catch (err) {
55
+ process.stderr.write(`[claudecontext/post-write] Parse error: ${err}\n`);
56
+ }
57
+ });
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ClaudeContext PreCompact hook.
4
+ * Deterministic snapshot: write session summary to L4, apply weight decay.
5
+ * NO Claude API calls — rule-based only.
6
+ * Timeout: 25s.
7
+ */
8
+
9
+ import { execFileSync } from 'node:child_process';
10
+ import { existsSync } from 'node:fs';
11
+ import { join, resolve } from 'node:path';
12
+
13
+ const projectRoot = resolve(process.env.CLAUDECONTEXT_PROJECT_ROOT ?? process.cwd());
14
+
15
+ function findCli() {
16
+ const local = join(projectRoot, 'node_modules', '.bin', 'claudecontext-cli');
17
+ if (existsSync(local)) return local;
18
+ return null;
19
+ }
20
+
21
+ try {
22
+ const cli = findCli();
23
+
24
+ if (cli) {
25
+ execFileSync(cli, ['summarize-session'], {
26
+ cwd: projectRoot,
27
+ env: { ...process.env, NODE_NO_WARNINGS: '1' },
28
+ timeout: 24000,
29
+ stdio: 'inherit',
30
+ });
31
+ } else {
32
+ execFileSync('npx', ['claudecontext-cli', 'summarize-session'], {
33
+ cwd: projectRoot,
34
+ env: { ...process.env, NODE_NO_WARNINGS: '1' },
35
+ timeout: 24000,
36
+ stdio: 'inherit',
37
+ });
38
+ }
39
+ } catch (err) {
40
+ // Fail silently — don't block compaction
41
+ process.stderr.write(`[claudecontext/pre-compact] Error: ${err?.message ?? err}\n`);
42
+ }
43
+
44
+ process.exit(0);