@tznthou/ccrecall 0.1.2
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.
- package/LICENSE +202 -0
- package/README.md +290 -0
- package/README_ZH.md +288 -0
- package/dist/api/routes.d.ts +13 -0
- package/dist/api/routes.d.ts.map +1 -0
- package/dist/api/routes.js +428 -0
- package/dist/api/routes.js.map +1 -0
- package/dist/api/server.d.ts +7 -0
- package/dist/api/server.d.ts.map +1 -0
- package/dist/api/server.js +44 -0
- package/dist/api/server.js.map +1 -0
- package/dist/cli/daemon.d.ts +35 -0
- package/dist/cli/daemon.d.ts.map +1 -0
- package/dist/cli/daemon.js +163 -0
- package/dist/cli/daemon.js.map +1 -0
- package/dist/core/compression.d.ts +37 -0
- package/dist/core/compression.d.ts.map +1 -0
- package/dist/core/compression.js +129 -0
- package/dist/core/compression.js.map +1 -0
- package/dist/core/database.d.ts +219 -0
- package/dist/core/database.d.ts.map +1 -0
- package/dist/core/database.js +1513 -0
- package/dist/core/database.js.map +1 -0
- package/dist/core/index.d.ts +9 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +8 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/indexer.d.ts +15 -0
- package/dist/core/indexer.d.ts.map +1 -0
- package/dist/core/indexer.js +224 -0
- package/dist/core/indexer.js.map +1 -0
- package/dist/core/lint.d.ts +17 -0
- package/dist/core/lint.d.ts.map +1 -0
- package/dist/core/lint.js +51 -0
- package/dist/core/lint.js.map +1 -0
- package/dist/core/log-safe.d.ts +6 -0
- package/dist/core/log-safe.d.ts.map +1 -0
- package/dist/core/log-safe.js +11 -0
- package/dist/core/log-safe.js.map +1 -0
- package/dist/core/maintenance-coordinator.d.ts +26 -0
- package/dist/core/maintenance-coordinator.d.ts.map +1 -0
- package/dist/core/maintenance-coordinator.js +67 -0
- package/dist/core/maintenance-coordinator.js.map +1 -0
- package/dist/core/memory-service.d.ts +31 -0
- package/dist/core/memory-service.d.ts.map +1 -0
- package/dist/core/memory-service.js +47 -0
- package/dist/core/memory-service.js.map +1 -0
- package/dist/core/parser.d.ts +17 -0
- package/dist/core/parser.d.ts.map +1 -0
- package/dist/core/parser.js +205 -0
- package/dist/core/parser.js.map +1 -0
- package/dist/core/scanner.d.ts +8 -0
- package/dist/core/scanner.d.ts.map +1 -0
- package/dist/core/scanner.js +123 -0
- package/dist/core/scanner.js.map +1 -0
- package/dist/core/summarizer.d.ts +12 -0
- package/dist/core/summarizer.d.ts.map +1 -0
- package/dist/core/summarizer.js +430 -0
- package/dist/core/summarizer.js.map +1 -0
- package/dist/core/topic-extractor.d.ts +11 -0
- package/dist/core/topic-extractor.d.ts.map +1 -0
- package/dist/core/topic-extractor.js +51 -0
- package/dist/core/topic-extractor.js.map +1 -0
- package/dist/core/types.d.ts +329 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +12 -0
- package/dist/core/types.js.map +1 -0
- package/dist/core/watcher.d.ts +30 -0
- package/dist/core/watcher.d.ts.map +1 -0
- package/dist/core/watcher.js +146 -0
- package/dist/core/watcher.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +116 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/server.d.ts +3 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +32 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.d.ts +39 -0
- package/dist/mcp/tools.d.ts.map +1 -0
- package/dist/mcp/tools.js +215 -0
- package/dist/mcp/tools.js.map +1 -0
- package/hooks/README.md +78 -0
- package/hooks/session-end.mjs +92 -0
- package/hooks/session-start.mjs +112 -0
- package/package.json +54 -0
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/** 單行 JSONL 解析結果 */
|
|
2
|
+
export interface ParsedLine {
|
|
3
|
+
type: string;
|
|
4
|
+
uuid: string | null;
|
|
5
|
+
parentUuid: string | null;
|
|
6
|
+
sessionId: string | null;
|
|
7
|
+
timestamp: string | null;
|
|
8
|
+
role: 'user' | 'assistant' | null;
|
|
9
|
+
contentText: string | null;
|
|
10
|
+
contentJson: string | null;
|
|
11
|
+
hasToolUse: boolean;
|
|
12
|
+
hasToolResult: boolean;
|
|
13
|
+
toolNames: string[];
|
|
14
|
+
rawJson: string;
|
|
15
|
+
inputTokens: number | null;
|
|
16
|
+
outputTokens: number | null;
|
|
17
|
+
cacheReadTokens: number | null;
|
|
18
|
+
cacheCreationTokens: number | null;
|
|
19
|
+
model: string | null;
|
|
20
|
+
requestId: string | null;
|
|
21
|
+
}
|
|
22
|
+
/** 整個 session 解析結果 */
|
|
23
|
+
export interface ParsedSession {
|
|
24
|
+
sessionId: string;
|
|
25
|
+
title: string | null;
|
|
26
|
+
messages: ParsedLine[];
|
|
27
|
+
startedAt: string | null;
|
|
28
|
+
endedAt: string | null;
|
|
29
|
+
skippedLines: number;
|
|
30
|
+
totalLines: number;
|
|
31
|
+
}
|
|
32
|
+
/** 掃描到的 session 檔案資訊 */
|
|
33
|
+
export interface ScannedSession {
|
|
34
|
+
filePath: string;
|
|
35
|
+
fileSize: number;
|
|
36
|
+
fileMtime: string;
|
|
37
|
+
sessionId: string;
|
|
38
|
+
}
|
|
39
|
+
/** 掃描到的 subagent 檔案資訊 */
|
|
40
|
+
export interface ScannedSubagent {
|
|
41
|
+
filePath: string;
|
|
42
|
+
fileSize: number;
|
|
43
|
+
fileMtime: string;
|
|
44
|
+
subagentId: string;
|
|
45
|
+
parentSessionId: string;
|
|
46
|
+
agentType: string | null;
|
|
47
|
+
}
|
|
48
|
+
/** 掃描到的專案資訊 */
|
|
49
|
+
export interface ScannedProject {
|
|
50
|
+
projectId: string;
|
|
51
|
+
displayName: string;
|
|
52
|
+
sessions: ScannedSession[];
|
|
53
|
+
}
|
|
54
|
+
/** 專案 */
|
|
55
|
+
export interface Project {
|
|
56
|
+
id: string;
|
|
57
|
+
displayName: string;
|
|
58
|
+
sessionCount: number;
|
|
59
|
+
lastActivityAt: string | null;
|
|
60
|
+
}
|
|
61
|
+
/** session_files 操作類型 */
|
|
62
|
+
export type FileOperation = 'read' | 'edit' | 'write' | 'discovery';
|
|
63
|
+
/** session_files 表記錄 */
|
|
64
|
+
export interface SessionFile {
|
|
65
|
+
sessionId: string;
|
|
66
|
+
filePath: string;
|
|
67
|
+
operation: FileOperation;
|
|
68
|
+
count: number;
|
|
69
|
+
firstSeenSeq: number;
|
|
70
|
+
lastSeenSeq: number;
|
|
71
|
+
}
|
|
72
|
+
/** session_files 寫入用型別(解決 summarizer ↔ database 循環依賴) */
|
|
73
|
+
export interface SessionFileInput {
|
|
74
|
+
filePath: string;
|
|
75
|
+
operation: FileOperation;
|
|
76
|
+
count: number;
|
|
77
|
+
firstSeenSeq: number;
|
|
78
|
+
lastSeenSeq: number;
|
|
79
|
+
}
|
|
80
|
+
/** Session 摘要 */
|
|
81
|
+
export interface SessionMeta {
|
|
82
|
+
id: string;
|
|
83
|
+
projectId: string;
|
|
84
|
+
title: string | null;
|
|
85
|
+
messageCount: number;
|
|
86
|
+
startedAt: string | null;
|
|
87
|
+
endedAt: string | null;
|
|
88
|
+
archived: boolean;
|
|
89
|
+
summaryText: string | null;
|
|
90
|
+
intentText: string | null;
|
|
91
|
+
outcomeStatus: OutcomeStatus;
|
|
92
|
+
durationSeconds: number | null;
|
|
93
|
+
activeDurationSeconds: number | null;
|
|
94
|
+
summaryVersion: number | null;
|
|
95
|
+
tags: string | null;
|
|
96
|
+
filesTouched: string | null;
|
|
97
|
+
toolsUsed: string | null;
|
|
98
|
+
totalInputTokens: number | null;
|
|
99
|
+
totalOutputTokens: number | null;
|
|
100
|
+
}
|
|
101
|
+
/** 訊息 */
|
|
102
|
+
export interface Message {
|
|
103
|
+
id: number;
|
|
104
|
+
sessionId: string;
|
|
105
|
+
type: 'user' | 'assistant' | 'queue-operation' | 'last-prompt';
|
|
106
|
+
role: 'user' | 'assistant' | null;
|
|
107
|
+
contentText: string | null;
|
|
108
|
+
contentJson: string | null;
|
|
109
|
+
hasToolUse: boolean;
|
|
110
|
+
hasToolResult: boolean;
|
|
111
|
+
toolNames: string[] | null;
|
|
112
|
+
timestamp: string | null;
|
|
113
|
+
sequence: number;
|
|
114
|
+
inputTokens: number | null;
|
|
115
|
+
outputTokens: number | null;
|
|
116
|
+
cacheReadTokens: number | null;
|
|
117
|
+
cacheCreationTokens: number | null;
|
|
118
|
+
model: string | null;
|
|
119
|
+
}
|
|
120
|
+
/** Session Token 統計 */
|
|
121
|
+
export interface SessionTokenStats {
|
|
122
|
+
totalInputTokens: number;
|
|
123
|
+
totalOutputTokens: number;
|
|
124
|
+
totalCacheReadTokens: number;
|
|
125
|
+
totalCacheCreationTokens: number;
|
|
126
|
+
cacheHitRate: number;
|
|
127
|
+
models: string[];
|
|
128
|
+
primaryModel: string | null;
|
|
129
|
+
turns: Array<{
|
|
130
|
+
sequence: number;
|
|
131
|
+
timestamp: string | null;
|
|
132
|
+
inputTokens: number;
|
|
133
|
+
outputTokens: number;
|
|
134
|
+
cacheReadTokens: number;
|
|
135
|
+
cacheCreationTokens: number;
|
|
136
|
+
contextTotal: number;
|
|
137
|
+
hasToolUse: boolean;
|
|
138
|
+
toolNames: string[];
|
|
139
|
+
model: string | null;
|
|
140
|
+
}>;
|
|
141
|
+
}
|
|
142
|
+
/** 訊息上下文(搜尋結果預覽用) */
|
|
143
|
+
export interface MessageContext {
|
|
144
|
+
target: Message | null;
|
|
145
|
+
before: Message[];
|
|
146
|
+
after: Message[];
|
|
147
|
+
}
|
|
148
|
+
/** 搜尋範圍 */
|
|
149
|
+
export type SearchScope = 'messages' | 'sessions';
|
|
150
|
+
export type SearchSortBy = 'rank' | 'date';
|
|
151
|
+
/** 搜尋選項 */
|
|
152
|
+
export interface SearchOptions {
|
|
153
|
+
dateFrom?: string;
|
|
154
|
+
dateTo?: string;
|
|
155
|
+
sortBy?: SearchSortBy;
|
|
156
|
+
}
|
|
157
|
+
/** 搜尋結果 */
|
|
158
|
+
export interface SearchResult {
|
|
159
|
+
sessionId: string;
|
|
160
|
+
sessionTitle: string | null;
|
|
161
|
+
projectId: string;
|
|
162
|
+
projectName: string;
|
|
163
|
+
messageId: number;
|
|
164
|
+
snippet: string;
|
|
165
|
+
timestamp: string | null;
|
|
166
|
+
sessionStartedAt: string | null;
|
|
167
|
+
}
|
|
168
|
+
/** 搜尋分頁回應 */
|
|
169
|
+
export interface SearchPage {
|
|
170
|
+
results: SearchResult[];
|
|
171
|
+
offset: number;
|
|
172
|
+
hasMore: boolean;
|
|
173
|
+
}
|
|
174
|
+
/** Session 層級搜尋結果 */
|
|
175
|
+
export interface SessionSearchResult {
|
|
176
|
+
sessionId: string;
|
|
177
|
+
sessionTitle: string | null;
|
|
178
|
+
projectId: string;
|
|
179
|
+
projectName: string;
|
|
180
|
+
tags: string | null;
|
|
181
|
+
filesTouched: string | null;
|
|
182
|
+
snippet: string;
|
|
183
|
+
startedAt: string | null;
|
|
184
|
+
outcomeStatus: OutcomeStatus;
|
|
185
|
+
}
|
|
186
|
+
/** Session 搜尋分頁回應 */
|
|
187
|
+
export interface SessionSearchPage {
|
|
188
|
+
results: SessionSearchResult[];
|
|
189
|
+
offset: number;
|
|
190
|
+
hasMore: boolean;
|
|
191
|
+
}
|
|
192
|
+
/** 檔案歷史條目 */
|
|
193
|
+
export interface FileHistoryEntry {
|
|
194
|
+
sessionId: string;
|
|
195
|
+
sessionTitle: string | null;
|
|
196
|
+
projectId: string;
|
|
197
|
+
projectName: string;
|
|
198
|
+
operation: FileOperation;
|
|
199
|
+
count: number;
|
|
200
|
+
startedAt: string | null;
|
|
201
|
+
}
|
|
202
|
+
/** Subagent session */
|
|
203
|
+
export interface SubagentSession {
|
|
204
|
+
id: string;
|
|
205
|
+
parentSessionId: string;
|
|
206
|
+
agentType: string | null;
|
|
207
|
+
filePath: string;
|
|
208
|
+
fileSize: number | null;
|
|
209
|
+
fileMtime: string | null;
|
|
210
|
+
messageCount: number;
|
|
211
|
+
startedAt: string | null;
|
|
212
|
+
endedAt: string | null;
|
|
213
|
+
createdAt: string;
|
|
214
|
+
}
|
|
215
|
+
/** 索引進度 */
|
|
216
|
+
export interface IndexerStatus {
|
|
217
|
+
phase: 'scanning' | 'parsing' | 'indexing' | 'done';
|
|
218
|
+
progress: number;
|
|
219
|
+
total: number;
|
|
220
|
+
current: number;
|
|
221
|
+
}
|
|
222
|
+
export type OutcomeStatus = 'committed' | 'tested' | 'in-progress' | 'quick-qa' | null;
|
|
223
|
+
export interface OutcomeSignals {
|
|
224
|
+
gitCommitInvoked: boolean;
|
|
225
|
+
testCommandRan: boolean;
|
|
226
|
+
endedWithEdits: boolean;
|
|
227
|
+
isQuickQA: boolean;
|
|
228
|
+
}
|
|
229
|
+
export interface SessionSummary {
|
|
230
|
+
intentText: string;
|
|
231
|
+
activityText: string;
|
|
232
|
+
outcomeStatus: OutcomeStatus;
|
|
233
|
+
outcomeSignals: OutcomeSignals;
|
|
234
|
+
summaryText: string;
|
|
235
|
+
tags: string;
|
|
236
|
+
filesTouched: string;
|
|
237
|
+
toolsUsed: string;
|
|
238
|
+
summaryVersion: number;
|
|
239
|
+
durationSeconds: number | null;
|
|
240
|
+
activeDurationSeconds: number | null;
|
|
241
|
+
}
|
|
242
|
+
/** 記憶類型 */
|
|
243
|
+
export type MemoryType = 'decision' | 'discovery' | 'preference' | 'pattern' | 'feedback';
|
|
244
|
+
/** 記憶條目 */
|
|
245
|
+
export interface Memory {
|
|
246
|
+
id: number;
|
|
247
|
+
sessionId: string | null;
|
|
248
|
+
messageId: string | null;
|
|
249
|
+
content: string;
|
|
250
|
+
type: MemoryType;
|
|
251
|
+
confidence: number;
|
|
252
|
+
createdAt: string;
|
|
253
|
+
}
|
|
254
|
+
/** 知識深度(由 mention_count 衍生:>=5 deep, >=2 medium, >=1 shallow, else none) */
|
|
255
|
+
export type KnowledgeDepth = 'deep' | 'medium' | 'shallow' | 'none';
|
|
256
|
+
export declare function deriveDepth(mentionCount: number): KnowledgeDepth;
|
|
257
|
+
/** knowledge_map 條目(DB row) */
|
|
258
|
+
export interface Topic {
|
|
259
|
+
topicKey: string;
|
|
260
|
+
projectId: string;
|
|
261
|
+
mentionCount: number;
|
|
262
|
+
lastTouched: string;
|
|
263
|
+
}
|
|
264
|
+
/** knowledge_map 查詢用(含衍生 depth) */
|
|
265
|
+
export interface TopicWithDepth extends Topic {
|
|
266
|
+
depth: KnowledgeDepth;
|
|
267
|
+
}
|
|
268
|
+
/** session 中途快照 */
|
|
269
|
+
export interface SessionCheckpoint {
|
|
270
|
+
id: number;
|
|
271
|
+
sessionId: string;
|
|
272
|
+
projectId: string;
|
|
273
|
+
snapshotText: string;
|
|
274
|
+
createdAt: string;
|
|
275
|
+
}
|
|
276
|
+
/** /memory/query 回應 */
|
|
277
|
+
export interface MemoryQueryResult {
|
|
278
|
+
memories: Array<{
|
|
279
|
+
content: string;
|
|
280
|
+
source: string;
|
|
281
|
+
confidence: number;
|
|
282
|
+
depth: KnowledgeDepth | null;
|
|
283
|
+
}>;
|
|
284
|
+
totalTokenEstimate: number;
|
|
285
|
+
}
|
|
286
|
+
/** /memory/context 回應 */
|
|
287
|
+
export interface SessionContextResult {
|
|
288
|
+
summary: string | null;
|
|
289
|
+
decisions: string[];
|
|
290
|
+
filesTouched: string[];
|
|
291
|
+
}
|
|
292
|
+
/** /metacognition/check summary 模式回應 */
|
|
293
|
+
export interface MetacognitionSummary {
|
|
294
|
+
projectId: string;
|
|
295
|
+
topTopics: TopicWithDepth[];
|
|
296
|
+
recentTopics: TopicWithDepth[];
|
|
297
|
+
staleTopics: TopicWithDepth[];
|
|
298
|
+
counts: {
|
|
299
|
+
totalTopics: number;
|
|
300
|
+
totalMemories: number;
|
|
301
|
+
totalSessions: number;
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
/** /metacognition/check detail 模式回應 */
|
|
305
|
+
export interface TopicDetail {
|
|
306
|
+
topicKey: string;
|
|
307
|
+
projectId: string;
|
|
308
|
+
mentionCount: number;
|
|
309
|
+
lastTouched: string;
|
|
310
|
+
depth: KnowledgeDepth;
|
|
311
|
+
memories: Memory[];
|
|
312
|
+
relatedTopics: string[];
|
|
313
|
+
}
|
|
314
|
+
/** /session/checkpoint 回應 */
|
|
315
|
+
export interface CheckpointResult {
|
|
316
|
+
ok: boolean;
|
|
317
|
+
checkpointId: number;
|
|
318
|
+
}
|
|
319
|
+
/** /health 回應 */
|
|
320
|
+
export interface HealthResult {
|
|
321
|
+
status: 'ok' | 'error';
|
|
322
|
+
version: string;
|
|
323
|
+
dbPath: string;
|
|
324
|
+
sessionCount: number;
|
|
325
|
+
memoryCount: number;
|
|
326
|
+
topicCount: number;
|
|
327
|
+
uptime: number;
|
|
328
|
+
}
|
|
329
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAGA,oBAAoB;AACpB,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAAA;IACjC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,UAAU,EAAE,OAAO,CAAA;IACnB,aAAa,EAAE,OAAO,CAAA;IACtB,SAAS,EAAE,MAAM,EAAE,CAAA;IACnB,OAAO,EAAE,MAAM,CAAA;IACf,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAA;IAClC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAED,sBAAsB;AACtB,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,QAAQ,EAAE,UAAU,EAAE,CAAA;IACtB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,YAAY,EAAE,MAAM,CAAA;IACpB,UAAU,EAAE,MAAM,CAAA;CACnB;AAID,wBAAwB;AACxB,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,yBAAyB;AACzB,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,UAAU,EAAE,MAAM,CAAA;IAClB,eAAe,EAAE,MAAM,CAAA;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAED,eAAe;AACf,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,QAAQ,EAAE,cAAc,EAAE,CAAA;CAC3B;AAID,SAAS;AACT,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAA;IACV,WAAW,EAAE,MAAM,CAAA;IACnB,YAAY,EAAE,MAAM,CAAA;IACpB,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;CAC9B;AAED,yBAAyB;AACzB,MAAM,MAAM,aAAa,GAAG,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,WAAW,CAAA;AAEnE,wBAAwB;AACxB,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAA;IACjB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,aAAa,CAAA;IACxB,KAAK,EAAE,MAAM,CAAA;IACb,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,yDAAyD;AACzD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,aAAa,CAAA;IACxB,KAAK,EAAE,MAAM,CAAA;IACb,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,iBAAiB;AACjB,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,EAAE,MAAM,CAAA;IACjB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,QAAQ,EAAE,OAAO,CAAA;IACjB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,UAAU,EAAE,MAAM,GAAG,IAAI,CAAA;IACzB,aAAa,EAAE,aAAa,CAAA;IAC5B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAA;IACpC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAA;IAC7B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;IAC/B,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAA;CACjC;AAED,SAAS;AACT,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,EAAE,MAAM,CAAA;IACjB,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,iBAAiB,GAAG,aAAa,CAAA;IAC9D,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,IAAI,CAAA;IACjC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,UAAU,EAAE,OAAO,CAAA;IACnB,aAAa,EAAE,OAAO,CAAA;IACtB,SAAS,EAAE,MAAM,EAAE,GAAG,IAAI,CAAA;IAC1B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,QAAQ,EAAE,MAAM,CAAA;IAChB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,mBAAmB,EAAE,MAAM,GAAG,IAAI,CAAA;IAClC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;CACrB;AAED,uBAAuB;AACvB,MAAM,WAAW,iBAAiB;IAChC,gBAAgB,EAAE,MAAM,CAAA;IACxB,iBAAiB,EAAE,MAAM,CAAA;IACzB,oBAAoB,EAAE,MAAM,CAAA;IAC5B,wBAAwB,EAAE,MAAM,CAAA;IAChC,YAAY,EAAE,MAAM,CAAA;IACpB,MAAM,EAAE,MAAM,EAAE,CAAA;IAChB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,KAAK,EAAE,KAAK,CAAC;QACX,QAAQ,EAAE,MAAM,CAAA;QAChB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;QACxB,WAAW,EAAE,MAAM,CAAA;QACnB,YAAY,EAAE,MAAM,CAAA;QACpB,eAAe,EAAE,MAAM,CAAA;QACvB,mBAAmB,EAAE,MAAM,CAAA;QAC3B,YAAY,EAAE,MAAM,CAAA;QACpB,UAAU,EAAE,OAAO,CAAA;QACnB,SAAS,EAAE,MAAM,EAAE,CAAA;QACnB,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;KACrB,CAAC,CAAA;CACH;AAED,qBAAqB;AACrB,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,OAAO,GAAG,IAAI,CAAA;IACtB,MAAM,EAAE,OAAO,EAAE,CAAA;IACjB,KAAK,EAAE,OAAO,EAAE,CAAA;CACjB;AAED,WAAW;AACX,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,UAAU,CAAA;AACjD,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,CAAA;AAE1C,WAAW;AACX,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,YAAY,CAAA;CACtB;AAED,WAAW;AACX,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAA;CAChC;AAED,aAAa;AACb,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,YAAY,EAAE,CAAA;IACvB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,OAAO,CAAA;CACjB;AAED,qBAAqB;AACrB,MAAM,WAAW,mBAAmB;IAClC,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IACnB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,aAAa,EAAE,aAAa,CAAA;CAC7B;AAED,qBAAqB;AACrB,MAAM,WAAW,iBAAiB;IAChC,OAAO,EAAE,mBAAmB,EAAE,CAAA;IAC9B,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,EAAE,OAAO,CAAA;CACjB;AAED,aAAa;AACb,MAAM,WAAW,gBAAgB;IAC/B,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAA;IAC3B,SAAS,EAAE,MAAM,CAAA;IACjB,WAAW,EAAE,MAAM,CAAA;IACnB,SAAS,EAAE,aAAa,CAAA;IACxB,KAAK,EAAE,MAAM,CAAA;IACb,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;CACzB;AAED,uBAAuB;AACvB,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAA;IACV,eAAe,EAAE,MAAM,CAAA;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;IACvB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED,WAAW;AACX,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,UAAU,GAAG,SAAS,GAAG,UAAU,GAAG,MAAM,CAAA;IACnD,QAAQ,EAAE,MAAM,CAAA;IAChB,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;CAChB;AAID,MAAM,MAAM,aAAa,GAAG,WAAW,GAAG,QAAQ,GAAG,aAAa,GAAG,UAAU,GAAG,IAAI,CAAA;AAEtF,MAAM,WAAW,cAAc;IAC7B,gBAAgB,EAAE,OAAO,CAAA;IACzB,cAAc,EAAE,OAAO,CAAA;IACvB,cAAc,EAAE,OAAO,CAAA;IACvB,SAAS,EAAE,OAAO,CAAA;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,UAAU,EAAE,MAAM,CAAA;IAClB,YAAY,EAAE,MAAM,CAAA;IACpB,aAAa,EAAE,aAAa,CAAA;IAC5B,cAAc,EAAE,cAAc,CAAA;IAC9B,WAAW,EAAE,MAAM,CAAA;IACnB,IAAI,EAAE,MAAM,CAAA;IACZ,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;IACjB,cAAc,EAAE,MAAM,CAAA;IACtB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,qBAAqB,EAAE,MAAM,GAAG,IAAI,CAAA;CACrC;AAID,WAAW;AACX,MAAM,MAAM,UAAU,GAAG,UAAU,GAAG,WAAW,GAAG,YAAY,GAAG,SAAS,GAAG,UAAU,CAAA;AAEzF,WAAW;AACX,MAAM,WAAW,MAAM;IACrB,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAA;IACxB,OAAO,EAAE,MAAM,CAAA;IACf,IAAI,EAAE,UAAU,CAAA;IAChB,UAAU,EAAE,MAAM,CAAA;IAClB,SAAS,EAAE,MAAM,CAAA;CAClB;AAID,4EAA4E;AAC5E,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,QAAQ,GAAG,SAAS,GAAG,MAAM,CAAA;AAEnE,wBAAgB,WAAW,CAAC,YAAY,EAAE,MAAM,GAAG,cAAc,CAKhE;AAED,+BAA+B;AAC/B,MAAM,WAAW,KAAK;IACpB,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;CACpB;AAED,mCAAmC;AACnC,MAAM,WAAW,cAAe,SAAQ,KAAK;IAC3C,KAAK,EAAE,cAAc,CAAA;CACtB;AAED,mBAAmB;AACnB,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAA;IACV,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,EAAE,MAAM,CAAA;IACpB,SAAS,EAAE,MAAM,CAAA;CAClB;AAID,uBAAuB;AACvB,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,KAAK,CAAC;QACd,OAAO,EAAE,MAAM,CAAA;QACf,MAAM,EAAE,MAAM,CAAA;QACd,UAAU,EAAE,MAAM,CAAA;QAClB,KAAK,EAAE,cAAc,GAAG,IAAI,CAAA;KAC7B,CAAC,CAAA;IACF,kBAAkB,EAAE,MAAM,CAAA;CAC3B;AAED,yBAAyB;AACzB,MAAM,WAAW,oBAAoB;IACnC,OAAO,EAAE,MAAM,GAAG,IAAI,CAAA;IACtB,SAAS,EAAE,MAAM,EAAE,CAAA;IACnB,YAAY,EAAE,MAAM,EAAE,CAAA;CACvB;AAED,wCAAwC;AACxC,MAAM,WAAW,oBAAoB;IACnC,SAAS,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,cAAc,EAAE,CAAA;IAC3B,YAAY,EAAE,cAAc,EAAE,CAAA;IAC9B,WAAW,EAAE,cAAc,EAAE,CAAA;IAC7B,MAAM,EAAE;QACN,WAAW,EAAE,MAAM,CAAA;QACnB,aAAa,EAAE,MAAM,CAAA;QACrB,aAAa,EAAE,MAAM,CAAA;KACtB,CAAA;CACF;AAED,uCAAuC;AACvC,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAA;IACjB,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;IACnB,KAAK,EAAE,cAAc,CAAA;IACrB,QAAQ,EAAE,MAAM,EAAE,CAAA;IAClB,aAAa,EAAE,MAAM,EAAE,CAAA;CACxB;AAED,6BAA6B;AAC7B,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,OAAO,CAAA;IACX,YAAY,EAAE,MAAM,CAAA;CACrB;AAED,iBAAiB;AACjB,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,IAAI,GAAG,OAAO,CAAA;IACtB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,YAAY,EAAE,MAAM,CAAA;IACpB,WAAW,EAAE,MAAM,CAAA;IACnB,UAAU,EAAE,MAAM,CAAA;IAClB,MAAM,EAAE,MAAM,CAAA;CACf"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
// ── 從 ccRewind 抽取:JSONL Parser 型別 ──
|
|
3
|
+
export function deriveDepth(mentionCount) {
|
|
4
|
+
if (mentionCount >= 5)
|
|
5
|
+
return 'deep';
|
|
6
|
+
if (mentionCount >= 2)
|
|
7
|
+
return 'medium';
|
|
8
|
+
if (mentionCount >= 1)
|
|
9
|
+
return 'shallow';
|
|
10
|
+
return 'none';
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/core/types.ts"],"names":[],"mappings":"AAAA,sCAAsC;AACtC,sCAAsC;AAuStC,MAAM,UAAU,WAAW,CAAC,YAAoB;IAC9C,IAAI,YAAY,IAAI,CAAC;QAAE,OAAO,MAAM,CAAA;IACpC,IAAI,YAAY,IAAI,CAAC;QAAE,OAAO,QAAQ,CAAA;IACtC,IAAI,YAAY,IAAI,CAAC;QAAE,OAAO,SAAS,CAAA;IACvC,OAAO,MAAM,CAAA;AACf,CAAC"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Database } from './database.js';
|
|
2
|
+
export type RunIndexerFn = (db: Database) => Promise<void>;
|
|
3
|
+
export interface JsonlWatcherOptions {
|
|
4
|
+
baseDir?: string;
|
|
5
|
+
debounceMs?: number;
|
|
6
|
+
fullResyncMs?: number;
|
|
7
|
+
runIndexer?: RunIndexerFn;
|
|
8
|
+
}
|
|
9
|
+
export declare class JsonlWatcher {
|
|
10
|
+
private readonly db;
|
|
11
|
+
private readonly baseDir;
|
|
12
|
+
private readonly debounceMs;
|
|
13
|
+
private readonly fullResyncMs;
|
|
14
|
+
private readonly runIndexerFn;
|
|
15
|
+
private watcher;
|
|
16
|
+
private debounceTimer;
|
|
17
|
+
private fullResyncTimer;
|
|
18
|
+
private inflight;
|
|
19
|
+
private dirty;
|
|
20
|
+
constructor(db: Database, opts?: JsonlWatcherOptions);
|
|
21
|
+
isRunning(): boolean;
|
|
22
|
+
start(): Promise<void>;
|
|
23
|
+
stop(): Promise<void>;
|
|
24
|
+
private scheduleIncrementalScan;
|
|
25
|
+
private runScan;
|
|
26
|
+
/** Trigger a scan immediately, bypassing debounce. Intended for /session/end
|
|
27
|
+
* rescue and integration tests. */
|
|
28
|
+
runNow(): Promise<void>;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=watcher.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watcher.d.ts","sourceRoot":"","sources":["../../src/core/watcher.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAoB7C,MAAM,MAAM,YAAY,GAAG,CAAC,EAAE,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;AAE1D,MAAM,WAAW,mBAAmB;IAClC,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,UAAU,CAAC,EAAE,MAAM,CAAA;IACnB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,UAAU,CAAC,EAAE,YAAY,CAAA;CAC1B;AAYD,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAU;IAC7B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAQ;IAChC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAQ;IACnC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAQ;IACrC,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAc;IAE3C,OAAO,CAAC,OAAO,CAAyB;IACxC,OAAO,CAAC,aAAa,CAA8B;IACnD,OAAO,CAAC,eAAe,CAA8B;IACrD,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,KAAK,CAAQ;gBAET,EAAE,EAAE,QAAQ,EAAE,IAAI,GAAE,mBAAwB;IAQxD,SAAS,IAAI,OAAO;IAId,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IA0CtB,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAgB3B,OAAO,CAAC,uBAAuB;YASjB,OAAO;IAqBrB;wCACoC;IAC9B,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;CAO9B"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import os from 'node:os';
|
|
4
|
+
import chokidar from 'chokidar';
|
|
5
|
+
import { runIndexer } from './indexer.js';
|
|
6
|
+
import { scrubErrorMessage } from './log-safe.js';
|
|
7
|
+
/**
|
|
8
|
+
* Phase 4e: JSONL watch mode.
|
|
9
|
+
*
|
|
10
|
+
* Watches ~/.claude/projects for JSONL changes so fresh-session harvests don't
|
|
11
|
+
* race the SessionEnd hook (the JSONL may not exist when hook fires; chokidar
|
|
12
|
+
* add event triggers an incremental reindex so /session/end finds the row on
|
|
13
|
+
* retry). Single-flight — at most one runIndexer inflight; events arriving
|
|
14
|
+
* during a scan mark dirty, triggering one follow-up scan after completion.
|
|
15
|
+
* Periodic full-resync backstop covers events chokidar might miss on some
|
|
16
|
+
* platforms (APFS rename edge cases, NFS, etc).
|
|
17
|
+
*/
|
|
18
|
+
const DEFAULT_BASE_DIR = path.join(os.homedir(), '.claude', 'projects');
|
|
19
|
+
const DEFAULT_DEBOUNCE_MS = 2000;
|
|
20
|
+
const DEFAULT_FULL_RESYNC_MS = 10 * 60 * 1000;
|
|
21
|
+
function isJsonlFileOrDir(p, stats) {
|
|
22
|
+
// chokidar 5 ignored() returns true to skip. Keep dirs (need recursion) and
|
|
23
|
+
// .jsonl files; drop everything else. stats is undefined during directory
|
|
24
|
+
// enumeration of an entry we haven't stat'd yet — treat as "keep" and let
|
|
25
|
+
// the recursive walk re-check with stats.
|
|
26
|
+
if (!stats)
|
|
27
|
+
return false;
|
|
28
|
+
if (stats.isDirectory())
|
|
29
|
+
return false;
|
|
30
|
+
return !p.endsWith('.jsonl');
|
|
31
|
+
}
|
|
32
|
+
export class JsonlWatcher {
|
|
33
|
+
db;
|
|
34
|
+
baseDir;
|
|
35
|
+
debounceMs;
|
|
36
|
+
fullResyncMs;
|
|
37
|
+
runIndexerFn;
|
|
38
|
+
watcher = null;
|
|
39
|
+
debounceTimer = null;
|
|
40
|
+
fullResyncTimer = null;
|
|
41
|
+
inflight = false;
|
|
42
|
+
dirty = false;
|
|
43
|
+
constructor(db, opts = {}) {
|
|
44
|
+
this.db = db;
|
|
45
|
+
this.baseDir = opts.baseDir ?? DEFAULT_BASE_DIR;
|
|
46
|
+
this.debounceMs = opts.debounceMs ?? DEFAULT_DEBOUNCE_MS;
|
|
47
|
+
this.fullResyncMs = opts.fullResyncMs ?? DEFAULT_FULL_RESYNC_MS;
|
|
48
|
+
this.runIndexerFn = opts.runIndexer ?? runIndexer;
|
|
49
|
+
}
|
|
50
|
+
isRunning() {
|
|
51
|
+
return this.watcher !== null;
|
|
52
|
+
}
|
|
53
|
+
async start() {
|
|
54
|
+
if (this.watcher)
|
|
55
|
+
return;
|
|
56
|
+
const watcher = chokidar.watch(this.baseDir, {
|
|
57
|
+
persistent: true,
|
|
58
|
+
// runIndexer ran before watcher.start() — skip the add spam from the
|
|
59
|
+
// initial tree walk.
|
|
60
|
+
ignoreInitial: true,
|
|
61
|
+
ignored: isJsonlFileOrDir,
|
|
62
|
+
awaitWriteFinish: {
|
|
63
|
+
stabilityThreshold: 500,
|
|
64
|
+
pollInterval: 100,
|
|
65
|
+
},
|
|
66
|
+
});
|
|
67
|
+
const onEvent = () => { this.scheduleIncrementalScan(); };
|
|
68
|
+
watcher.on('add', onEvent);
|
|
69
|
+
watcher.on('change', onEvent);
|
|
70
|
+
watcher.on('unlink', onEvent);
|
|
71
|
+
watcher.on('error', (err) => {
|
|
72
|
+
console.warn('[watcher] chokidar error:', scrubErrorMessage(err));
|
|
73
|
+
});
|
|
74
|
+
// Await ready so callers (daemon bootstrap) can be sure ignoreInitial has
|
|
75
|
+
// settled before advertising the service. Without this, a JSONL written
|
|
76
|
+
// between scanProjects() and chokidar's ready event would be missed by
|
|
77
|
+
// both paths until the backstop or a rescue fires.
|
|
78
|
+
await new Promise((resolve) => {
|
|
79
|
+
watcher.once('ready', () => resolve());
|
|
80
|
+
});
|
|
81
|
+
this.watcher = watcher;
|
|
82
|
+
this.fullResyncTimer = setInterval(() => {
|
|
83
|
+
// Bypass scheduleIncrementalScan so steady JSONL churn cannot keep
|
|
84
|
+
// pushing the debounce out and starve the backstop. runScan's single-
|
|
85
|
+
// flight guard still prevents overlapping work.
|
|
86
|
+
void this.runScan();
|
|
87
|
+
}, this.fullResyncMs);
|
|
88
|
+
this.fullResyncTimer.unref?.();
|
|
89
|
+
}
|
|
90
|
+
async stop() {
|
|
91
|
+
if (this.debounceTimer) {
|
|
92
|
+
clearTimeout(this.debounceTimer);
|
|
93
|
+
this.debounceTimer = null;
|
|
94
|
+
}
|
|
95
|
+
if (this.fullResyncTimer) {
|
|
96
|
+
clearInterval(this.fullResyncTimer);
|
|
97
|
+
this.fullResyncTimer = null;
|
|
98
|
+
}
|
|
99
|
+
if (this.watcher) {
|
|
100
|
+
const w = this.watcher;
|
|
101
|
+
this.watcher = null;
|
|
102
|
+
await w.close();
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
scheduleIncrementalScan() {
|
|
106
|
+
if (this.debounceTimer)
|
|
107
|
+
clearTimeout(this.debounceTimer);
|
|
108
|
+
this.debounceTimer = setTimeout(() => {
|
|
109
|
+
this.debounceTimer = null;
|
|
110
|
+
void this.runScan();
|
|
111
|
+
}, this.debounceMs);
|
|
112
|
+
this.debounceTimer.unref?.();
|
|
113
|
+
}
|
|
114
|
+
async runScan() {
|
|
115
|
+
if (this.inflight) {
|
|
116
|
+
this.dirty = true;
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
this.inflight = true;
|
|
120
|
+
this.dirty = false;
|
|
121
|
+
try {
|
|
122
|
+
await this.runIndexerFn(this.db);
|
|
123
|
+
}
|
|
124
|
+
catch (err) {
|
|
125
|
+
console.warn('[watcher] incremental scan error:', scrubErrorMessage(err));
|
|
126
|
+
}
|
|
127
|
+
finally {
|
|
128
|
+
this.inflight = false;
|
|
129
|
+
if (this.dirty) {
|
|
130
|
+
// Events that arrived during the inflight scan bumped dirty; kick the
|
|
131
|
+
// debounce again so the follow-up scan sees them.
|
|
132
|
+
this.scheduleIncrementalScan();
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/** Trigger a scan immediately, bypassing debounce. Intended for /session/end
|
|
137
|
+
* rescue and integration tests. */
|
|
138
|
+
async runNow() {
|
|
139
|
+
if (this.debounceTimer) {
|
|
140
|
+
clearTimeout(this.debounceTimer);
|
|
141
|
+
this.debounceTimer = null;
|
|
142
|
+
}
|
|
143
|
+
await this.runScan();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=watcher.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watcher.js","sourceRoot":"","sources":["../../src/core/watcher.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import os from 'node:os';
|
|
5
|
+
import { createServer } from './api/server.js';
|
|
6
|
+
import { Database } from './core/database.js';
|
|
7
|
+
import { runIndexer } from './core/indexer.js';
|
|
8
|
+
import { MemoryService } from './core/memory-service.js';
|
|
9
|
+
import { MaintenanceCoordinator } from './core/maintenance-coordinator.js';
|
|
10
|
+
import { JsonlWatcher } from './core/watcher.js';
|
|
11
|
+
import { installDaemon, uninstallDaemon } from './cli/daemon.js';
|
|
12
|
+
const subcommand = process.argv[2];
|
|
13
|
+
if (subcommand === 'install-daemon' || subcommand === 'uninstall-daemon') {
|
|
14
|
+
const dryRun = process.argv.includes('--dry-run');
|
|
15
|
+
// Propagate the current daemon's runtime config into the plist so the
|
|
16
|
+
// auto-started instance uses the same port/db as the user's interactive
|
|
17
|
+
// session. Without this the LaunchAgent silently resets to defaults after
|
|
18
|
+
// the next login, which looks like memory loss or a port mismatch.
|
|
19
|
+
const envPort = process.env.CCRECALL_PORT;
|
|
20
|
+
const parsedPort = envPort ? parseInt(envPort, 10) : NaN;
|
|
21
|
+
// Cap at 65535 so a typo like CCRECALL_PORT=70000 doesn't get baked into
|
|
22
|
+
// the plist and crash-loop launchd (ERR_SOCKET_BAD_PORT on every restart).
|
|
23
|
+
const port = Number.isFinite(parsedPort) && parsedPort > 0 && parsedPort <= 65535
|
|
24
|
+
? parsedPort
|
|
25
|
+
: undefined;
|
|
26
|
+
const dbPath = process.env.CCRECALL_DB_PATH;
|
|
27
|
+
const action = subcommand === 'install-daemon'
|
|
28
|
+
? () => installDaemon({ dryRun, port, dbPath })
|
|
29
|
+
: () => uninstallDaemon();
|
|
30
|
+
action().then(() => process.exit(0)).catch((err) => {
|
|
31
|
+
console.error(`[ccmem ${subcommand}] ${err.message}`);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
else if (subcommand === '--help' || subcommand === '-h' || subcommand === 'help') {
|
|
36
|
+
printHelp();
|
|
37
|
+
process.exit(0);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
startDaemon().catch((err) => {
|
|
41
|
+
console.error(`[ccmem] fatal: ${err.message}`);
|
|
42
|
+
process.exit(1);
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
function printHelp() {
|
|
46
|
+
const port = process.env.CCRECALL_PORT ?? '7749';
|
|
47
|
+
console.log(`ccRecall — AI memory service for Claude Code
|
|
48
|
+
|
|
49
|
+
Usage:
|
|
50
|
+
ccmem Run the daemon (HTTP API on :${port})
|
|
51
|
+
ccmem install-daemon Install macOS LaunchAgent for auto-start
|
|
52
|
+
ccmem install-daemon --dry-run Print plist without installing
|
|
53
|
+
ccmem uninstall-daemon Remove LaunchAgent and stop auto-start
|
|
54
|
+
|
|
55
|
+
Environment:
|
|
56
|
+
CCRECALL_PORT HTTP port (default: 7749)
|
|
57
|
+
CCRECALL_DB_PATH SQLite path (default: ~/.ccrecall/ccrecall.db)
|
|
58
|
+
|
|
59
|
+
See docs/launchd.md for manual install and troubleshooting.`);
|
|
60
|
+
}
|
|
61
|
+
async function startDaemon() {
|
|
62
|
+
const rawPort = parseInt(process.env.CCRECALL_PORT ?? '7749', 10);
|
|
63
|
+
const PORT = Number.isFinite(rawPort) && rawPort > 0 && rawPort <= 65535 ? rawPort : 7749;
|
|
64
|
+
const DB_PATH = process.env.CCRECALL_DB_PATH ?? path.join(os.homedir(), '.ccrecall', 'ccrecall.db');
|
|
65
|
+
const db = new Database(DB_PATH);
|
|
66
|
+
console.log(`Database initialized at ${DB_PATH}`);
|
|
67
|
+
// Await the initial index so the watcher (which uses ignoreInitial=true)
|
|
68
|
+
// doesn't start before scanProjects has observed the tree. A JSONL written
|
|
69
|
+
// between these two phases would otherwise be invisible until the 10-minute
|
|
70
|
+
// backstop or a /session/end rescue.
|
|
71
|
+
console.log('Running indexer...');
|
|
72
|
+
try {
|
|
73
|
+
await runIndexer(db);
|
|
74
|
+
console.log('Indexer complete.');
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
console.error('Indexer error:', err);
|
|
78
|
+
}
|
|
79
|
+
// Phase 4d: start background compression scheduler. unref'd so it never blocks
|
|
80
|
+
// process exit — HTTP server keep-alive is authoritative.
|
|
81
|
+
const memoryService = new MemoryService(db);
|
|
82
|
+
const coordinator = new MaintenanceCoordinator(db, memoryService);
|
|
83
|
+
coordinator.start();
|
|
84
|
+
console.log('Maintenance coordinator started.');
|
|
85
|
+
// Phase 4e: JSONL watch mode — incremental reindex when Claude Code writes new
|
|
86
|
+
// session files. Complements the hook path; covers resumed sessions too.
|
|
87
|
+
// start() resolves only after chokidar's `ready` event, so ignoreInitial
|
|
88
|
+
// has settled before we advertise the service.
|
|
89
|
+
const watcher = new JsonlWatcher(db);
|
|
90
|
+
try {
|
|
91
|
+
await watcher.start();
|
|
92
|
+
console.log('JSONL watcher started.');
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
console.error('Watcher start error:', err);
|
|
96
|
+
}
|
|
97
|
+
// Rescue reindex uses runIndexer directly (not watcher.runNow) so /session/end
|
|
98
|
+
// gets deterministic execution instead of being dropped by watcher's single-
|
|
99
|
+
// flight guard when a scheduled scan is already inflight.
|
|
100
|
+
const server = createServer(db, {
|
|
101
|
+
rescueReindex: () => runIndexer(db),
|
|
102
|
+
});
|
|
103
|
+
server.listen(PORT, '127.0.0.1', () => {
|
|
104
|
+
console.log(`ccRecall listening on http://127.0.0.1:${PORT}`);
|
|
105
|
+
});
|
|
106
|
+
for (const signal of ['SIGINT', 'SIGTERM']) {
|
|
107
|
+
process.on(signal, () => {
|
|
108
|
+
console.log(`\nReceived ${signal}, shutting down...`);
|
|
109
|
+
coordinator.stop();
|
|
110
|
+
watcher.stop().catch(() => { });
|
|
111
|
+
db.close();
|
|
112
|
+
server.close(() => process.exit(0));
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
//# sourceMappingURL=index.js.map
|