hacklab 0.21.2 → 0.22.1

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 (57) hide show
  1. package/README.md +101 -10
  2. package/dist/commands/config.d.ts.map +1 -1
  3. package/dist/commands/config.js +3 -3
  4. package/dist/commands/config.js.map +1 -1
  5. package/dist/commands/daemon.d.ts.map +1 -1
  6. package/dist/commands/daemon.js +4 -2
  7. package/dist/commands/daemon.js.map +1 -1
  8. package/dist/commands/scan.d.ts.map +1 -1
  9. package/dist/commands/scan.js +6 -2
  10. package/dist/commands/scan.js.map +1 -1
  11. package/dist/commands/setup.d.ts.map +1 -1
  12. package/dist/commands/setup.js +5 -3
  13. package/dist/commands/setup.js.map +1 -1
  14. package/dist/commands/sync.js +11 -4
  15. package/dist/commands/sync.js.map +1 -1
  16. package/dist/config.d.ts +4 -3
  17. package/dist/config.d.ts.map +1 -1
  18. package/dist/config.js.map +1 -1
  19. package/dist/daily-sync.d.ts +14 -2
  20. package/dist/daily-sync.d.ts.map +1 -1
  21. package/dist/daily-sync.js +20 -9
  22. package/dist/daily-sync.js.map +1 -1
  23. package/dist/prompt-consent.d.ts +4 -6
  24. package/dist/prompt-consent.d.ts.map +1 -1
  25. package/dist/prompt-consent.js +16 -9
  26. package/dist/prompt-consent.js.map +1 -1
  27. package/dist/prompt-stats.d.ts +14 -2
  28. package/dist/prompt-stats.d.ts.map +1 -1
  29. package/dist/prompt-stats.js +83 -26
  30. package/dist/prompt-stats.js.map +1 -1
  31. package/dist/scanners/antigravity.d.ts +41 -0
  32. package/dist/scanners/antigravity.d.ts.map +1 -0
  33. package/dist/scanners/antigravity.js +210 -0
  34. package/dist/scanners/antigravity.js.map +1 -0
  35. package/dist/scanners/github-copilot-telemetry.d.ts +23 -0
  36. package/dist/scanners/github-copilot-telemetry.d.ts.map +1 -0
  37. package/dist/scanners/github-copilot-telemetry.js +220 -0
  38. package/dist/scanners/github-copilot-telemetry.js.map +1 -0
  39. package/dist/scanners/github-copilot.d.ts +68 -0
  40. package/dist/scanners/github-copilot.d.ts.map +1 -0
  41. package/dist/scanners/github-copilot.js +262 -0
  42. package/dist/scanners/github-copilot.js.map +1 -0
  43. package/dist/scanners/incremental.d.ts +19 -4
  44. package/dist/scanners/incremental.d.ts.map +1 -1
  45. package/dist/scanners/incremental.js +104 -23
  46. package/dist/scanners/incremental.js.map +1 -1
  47. package/dist/scanners/index.d.ts.map +1 -1
  48. package/dist/scanners/index.js +16 -2
  49. package/dist/scanners/index.js.map +1 -1
  50. package/dist/scanners/util.d.ts +1 -1
  51. package/dist/scanners/util.d.ts.map +1 -1
  52. package/dist/scanners/util.js.map +1 -1
  53. package/dist/sync.d.ts +2 -0
  54. package/dist/sync.d.ts.map +1 -1
  55. package/dist/sync.js +4 -0
  56. package/dist/sync.js.map +1 -1
  57. package/package.json +3 -1
@@ -0,0 +1,262 @@
1
+ import { readdir, readFile } from 'node:fs/promises';
2
+ import { homedir } from 'node:os';
3
+ import { basename, dirname, join } from 'node:path';
4
+ import { copilotTelemetrySources, readCopilotTelemetry, } from './github-copilot-telemetry.js';
5
+ import { findFiles, TokenCollector } from './util.js';
6
+ /**
7
+ * Source: microsoft/vscode, extensions/copilot/src/extension/chat/vscode-node/
8
+ * sessionTranscriptService.ts (session.start + user.message JSONL).
9
+ * VS Code's built-in GitHub Copilot extension writes these JSONL transcripts
10
+ * under workspace storage. We intentionally name only its extension storage,
11
+ * never generic VS Code chat persistence, so another chat provider cannot be
12
+ * mistaken for Copilot.
13
+ */
14
+ export function githubCopilotVsCodeWorkspaceStorageRoots() {
15
+ const home = homedir();
16
+ switch (process.platform) {
17
+ case 'win32': {
18
+ const appData = process.env.APPDATA || join(home, 'AppData', 'Roaming');
19
+ return [
20
+ join(appData, 'Code', 'User', 'workspaceStorage'),
21
+ join(appData, 'Code - Insiders', 'User', 'workspaceStorage'),
22
+ ];
23
+ }
24
+ case 'darwin':
25
+ return [
26
+ join(home, 'Library', 'Application Support', 'Code', 'User', 'workspaceStorage'),
27
+ join(home, 'Library', 'Application Support', 'Code - Insiders', 'User', 'workspaceStorage'),
28
+ ];
29
+ case 'linux': {
30
+ const configHome = process.env.XDG_CONFIG_HOME || join(home, '.config');
31
+ return [
32
+ join(configHome, 'Code', 'User', 'workspaceStorage'),
33
+ join(configHome, 'Code - Insiders', 'User', 'workspaceStorage'),
34
+ ];
35
+ }
36
+ default:
37
+ return [];
38
+ }
39
+ }
40
+ /** Copilot CLI uses this state root; COPILOT_HOME takes precedence at scan time. */
41
+ export function githubCopilotCliSessionStateDir() {
42
+ return join(process.env.COPILOT_HOME || join(homedir(), '.copilot'), 'session-state');
43
+ }
44
+ /**
45
+ * Local files deliberately written by GitHub Copilot only.
46
+ *
47
+ * VS Code transcript persistence is currently limited to the extension's
48
+ * `github.copilot-chat/transcripts` directory. Copilot CLI persists one
49
+ * `events.jsonl` file per session. Neither generic VS Code workspace state nor
50
+ * exported chats are read, avoiding duplicate/snapshot records and unrelated
51
+ * providers' chats.
52
+ */
53
+ export async function githubCopilotFiles() {
54
+ const vscodeFiles = await Promise.all(githubCopilotVsCodeWorkspaceStorageRoots().map(async (root) => {
55
+ let workspaces;
56
+ try {
57
+ workspaces = await readdir(root);
58
+ }
59
+ catch {
60
+ return [];
61
+ }
62
+ return (await Promise.all(workspaces.map((workspace) => findFiles(join(root, workspace, 'github.copilot-chat', 'transcripts'), '.jsonl')))).flat();
63
+ }));
64
+ return [...vscodeFiles.flat(), ...(await githubCopilotSessionFiles())];
65
+ }
66
+ /** Keep a source prefix because VS Code and the CLI may reuse a UUID. */
67
+ function prefixedSessionId(source, sessionId) {
68
+ return `github_copilot:${source}:${sessionId}`;
69
+ }
70
+ function timestamp(value) {
71
+ if (typeof value !== 'string' && typeof value !== 'number')
72
+ return null;
73
+ const instant = typeof value === 'number' ? value : Date.parse(value);
74
+ const date = new Date(instant);
75
+ return Number.isFinite(date.getTime()) ? date.toISOString() : null;
76
+ }
77
+ /**
78
+ * Parse the documented JSONL event shape written by VS Code's Copilot
79
+ * transcript service and the Copilot CLI SDK. Synthetic SDK user messages
80
+ * (for example tool/skill traffic) are excluded; only absent or `user` sources
81
+ * represent a person-entered prompt.
82
+ */
83
+ export function parseGitHubCopilotJsonl(content, options) {
84
+ let sessionId = options.sessionId?.trim() || '';
85
+ let sawVsCodeTranscript = options.source !== 'vscode';
86
+ const prompts = [];
87
+ for (const line of content.split('\n')) {
88
+ if (!line.trim())
89
+ continue;
90
+ let event;
91
+ try {
92
+ event = JSON.parse(line);
93
+ }
94
+ catch {
95
+ continue;
96
+ }
97
+ if (!event || typeof event !== 'object')
98
+ continue;
99
+ const record = event;
100
+ if (record.type === 'session.start') {
101
+ if (record.data?.producer === 'copilot-agent')
102
+ sawVsCodeTranscript = true;
103
+ if (typeof record.data?.sessionId === 'string' &&
104
+ record.data.sessionId.trim()) {
105
+ sessionId = record.data.sessionId.trim();
106
+ }
107
+ continue;
108
+ }
109
+ if (record.type !== 'user.message' ||
110
+ !sawVsCodeTranscript ||
111
+ !sessionId ||
112
+ prefixedSessionId(options.source, sessionId).length > 128)
113
+ continue;
114
+ if (typeof record.data?.source === 'string' &&
115
+ record.data.source.toLowerCase() !== 'user') {
116
+ continue;
117
+ }
118
+ if (typeof record.data?.content !== 'string' || !record.data.content.trim())
119
+ continue;
120
+ prompts.push({
121
+ sessionId: prefixedSessionId(options.source, sessionId),
122
+ text: record.data.content,
123
+ timestamp: timestamp(record.timestamp),
124
+ });
125
+ }
126
+ return prompts;
127
+ }
128
+ /**
129
+ * Read raw local prompts only for the consented prompt pipeline. Token scanning
130
+ * below uses numeric usage records independently of prompt-sharing consent.
131
+ */
132
+ export async function scanGitHubCopilotPrompts() {
133
+ const files = await githubCopilotFiles();
134
+ const prompts = await Promise.all(files.map(async (path) => {
135
+ let content;
136
+ try {
137
+ content = await readFile(path, 'utf8');
138
+ }
139
+ catch {
140
+ return [];
141
+ }
142
+ const cliMatch = path.match(/[\\/]session-state[\\/]([^\\/]+)[\\/]events\.jsonl$/i);
143
+ return parseGitHubCopilotJsonl(content, {
144
+ source: cliMatch ? 'cli' : 'vscode',
145
+ sessionId: cliMatch?.[1],
146
+ });
147
+ }));
148
+ return prompts.flat();
149
+ }
150
+ async function githubCopilotSessionFiles() {
151
+ return (await findFiles(githubCopilotCliSessionStateDir(), '.jsonl')).filter((path) => /[\\/]session-state[\\/][^\\/]+[\\/]events\.jsonl$/i.test(path));
152
+ }
153
+ export async function githubCopilotTokenFiles() {
154
+ const telemetry = await copilotTelemetrySources(githubCopilotVsCodeWorkspaceStorageRoots().map((root) => dirname(root)));
155
+ return [
156
+ ...(await githubCopilotSessionFiles()),
157
+ ...telemetry.files,
158
+ ...telemetry.databases.flatMap((path) => [path, `${path}-wal`]),
159
+ ];
160
+ }
161
+ function tokenCount(value) {
162
+ return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
163
+ }
164
+ /**
165
+ * SDK session.shutdown is durable, unlike ephemeral assistant.usage events.
166
+ * modelMetrics is cumulative across resumes. Input already includes both cache
167
+ * buckets, output already includes reasoning; agentMetrics is a breakdown of
168
+ * these same modelMetrics, not additional usage.
169
+ * Sources: github/copilot-sdk generated/session-events.ts; ccusage.com/guide/copilot/
170
+ */
171
+ export function parseGitHubCopilotUsage(content, fallbackSessionId) {
172
+ let sessionId = fallbackSessionId;
173
+ const snapshots = [];
174
+ for (const line of content.split('\n')) {
175
+ let event;
176
+ try {
177
+ event = JSON.parse(line);
178
+ }
179
+ catch {
180
+ continue;
181
+ }
182
+ if (!event || typeof event !== 'object')
183
+ continue;
184
+ if (event.type === 'session.start' &&
185
+ typeof event.data?.sessionId === 'string') {
186
+ sessionId = event.data.sessionId.trim() || sessionId;
187
+ }
188
+ if (event.type !== 'session.shutdown' || event.agentId)
189
+ continue;
190
+ const at = timestamp(event.timestamp);
191
+ const metrics = event.data?.modelMetrics;
192
+ if (!at ||
193
+ !sessionId ||
194
+ !metrics ||
195
+ typeof metrics !== 'object' ||
196
+ Array.isArray(metrics))
197
+ continue;
198
+ for (const [model, metric] of Object.entries(metrics)) {
199
+ const input = metric?.usage?.inputTokens;
200
+ const output = metric?.usage?.outputTokens;
201
+ if (!model || !tokenCount(input) || !tokenCount(output))
202
+ continue;
203
+ const tokens = input + output;
204
+ if (!Number.isSafeInteger(tokens))
205
+ continue;
206
+ snapshots.push({
207
+ sessionId,
208
+ model,
209
+ timestamp: at,
210
+ tokens,
211
+ messages: tokenCount(metric?.requests?.count)
212
+ ? metric.requests.count
213
+ : 0,
214
+ });
215
+ }
216
+ }
217
+ return snapshots;
218
+ }
219
+ export async function scanGitHubCopilot() {
220
+ const snapshots = [];
221
+ for (const path of await githubCopilotSessionFiles()) {
222
+ try {
223
+ snapshots.push(...parseGitHubCopilotUsage(await readFile(path, 'utf8'), basename(dirname(path))));
224
+ }
225
+ catch (error) {
226
+ console.warn(`Copilot usage unavailable in ${basename(dirname(path))}: ${error instanceof Error ? error.message : String(error)}`);
227
+ }
228
+ }
229
+ snapshots.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
230
+ const collector = new TokenCollector('github_copilot');
231
+ const previous = new Map();
232
+ const latestShutdown = new Map();
233
+ for (const snapshot of snapshots) {
234
+ const key = JSON.stringify([snapshot.sessionId, snapshot.model]);
235
+ latestShutdown.set(JSON.stringify([
236
+ snapshot.sessionId,
237
+ snapshot.model.replace(/-1m(?:-internal)?$/, ''),
238
+ ]), snapshot.timestamp);
239
+ const before = previous.get(key) ?? { tokens: 0, messages: 0 };
240
+ const tokens = Math.max(before.tokens, snapshot.tokens);
241
+ const messages = Math.max(before.messages, snapshot.messages);
242
+ if (tokens > before.tokens) {
243
+ collector.addDaily(snapshot.timestamp.slice(0, 10), snapshot.model, tokens - before.tokens, messages - before.messages);
244
+ }
245
+ previous.set(key, { tokens, messages });
246
+ }
247
+ const telemetry = await copilotTelemetrySources(githubCopilotVsCodeWorkspaceStorageRoots().map((root) => dirname(root)));
248
+ for (const inference of await readCopilotTelemetry(telemetry)) {
249
+ const key = JSON.stringify([
250
+ inference.sessionId,
251
+ inference.model.replace(/-1m(?:-internal)?$/, ''),
252
+ ]);
253
+ const shutdown = latestShutdown.get(key);
254
+ // A later resumed call is new; earlier calls already belong to the durable
255
+ // cumulative snapshot. Never add two representations of the same usage.
256
+ if (shutdown && inference.timestamp <= shutdown)
257
+ continue;
258
+ collector.addDaily(inference.timestamp.slice(0, 10), inference.model, inference.tokens, 1);
259
+ }
260
+ return collector.result();
261
+ }
262
+ //# sourceMappingURL=github-copilot.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"github-copilot.js","sourceRoot":"","sources":["../../src/scanners/github-copilot.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAA;AACjC,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAEnD,OAAO,EACL,uBAAuB,EACvB,oBAAoB,GACrB,MAAM,+BAA+B,CAAA;AAEtC,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA;AAoBrD;;;;;;;GAOG;AACH,MAAM,UAAU,wCAAwC;IACtD,MAAM,IAAI,GAAG,OAAO,EAAE,CAAA;IACtB,QAAQ,OAAO,CAAC,QAAQ,EAAE,CAAC;QACzB,KAAK,OAAO,CAAC,CAAC,CAAC;YACb,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,OAAO,IAAI,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,SAAS,CAAC,CAAA;YACvE,OAAO;gBACL,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,CAAC;gBACjD,IAAI,CAAC,OAAO,EAAE,iBAAiB,EAAE,MAAM,EAAE,kBAAkB,CAAC;aAC7D,CAAA;QACH,CAAC;QACD,KAAK,QAAQ;YACX,OAAO;gBACL,IAAI,CACF,IAAI,EACJ,SAAS,EACT,qBAAqB,EACrB,MAAM,EACN,MAAM,EACN,kBAAkB,CACnB;gBACD,IAAI,CACF,IAAI,EACJ,SAAS,EACT,qBAAqB,EACrB,iBAAiB,EACjB,MAAM,EACN,kBAAkB,CACnB;aACF,CAAA;QACH,KAAK,OAAO,CAAC,CAAC,CAAC;YACb,MAAM,UAAU,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,IAAI,CAAC,IAAI,EAAE,SAAS,CAAC,CAAA;YACvE,OAAO;gBACL,IAAI,CAAC,UAAU,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,CAAC;gBACpD,IAAI,CAAC,UAAU,EAAE,iBAAiB,EAAE,MAAM,EAAE,kBAAkB,CAAC;aAChE,CAAA;QACH,CAAC;QACD;YACE,OAAO,EAAE,CAAA;IACb,CAAC;AACH,CAAC;AAED,oFAAoF;AACpF,MAAM,UAAU,+BAA+B;IAC7C,OAAO,IAAI,CACT,OAAO,CAAC,GAAG,CAAC,YAAY,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,UAAU,CAAC,EACvD,eAAe,CAChB,CAAA;AACH,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB;IACtC,MAAM,WAAW,GAAG,MAAM,OAAO,CAAC,GAAG,CACnC,wCAAwC,EAAE,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QAC5D,IAAI,UAAoB,CAAA;QACxB,IAAI,CAAC;YACH,UAAU,GAAG,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;QAClC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,CAAA;QACX,CAAC;QACD,OAAO,CACL,MAAM,OAAO,CAAC,GAAG,CACf,UAAU,CAAC,GAAG,CAAC,CAAC,SAAS,EAAE,EAAE,CAC3B,SAAS,CACP,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,qBAAqB,EAAE,aAAa,CAAC,EAC3D,QAAQ,CACT,CACF,CACF,CACF,CAAC,IAAI,EAAE,CAAA;IACV,CAAC,CAAC,CACH,CAAA;IACD,OAAO,CAAC,GAAG,WAAW,CAAC,IAAI,EAAE,EAAE,GAAG,CAAC,MAAM,yBAAyB,EAAE,CAAC,CAAC,CAAA;AACxE,CAAC;AAED,yEAAyE;AACzE,SAAS,iBAAiB,CAAC,MAAqB,EAAE,SAAiB;IACjE,OAAO,kBAAkB,MAAM,IAAI,SAAS,EAAE,CAAA;AAChD,CAAC;AAED,SAAS,SAAS,CAAC,KAAc;IAC/B,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IACvE,MAAM,OAAO,GAAG,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;IACrE,MAAM,IAAI,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,CAAA;IAC9B,OAAO,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,IAAI,CAAA;AACpE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CACrC,OAAe,EACf,OAAqB;IAErB,IAAI,SAAS,GAAG,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,CAAA;IAC/C,IAAI,mBAAmB,GAAG,OAAO,CAAC,MAAM,KAAK,QAAQ,CAAA;IACrD,MAAM,OAAO,GAA0B,EAAE,CAAA;IAEzC,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACvC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;YAAE,SAAQ;QAC1B,IAAI,KAAc,CAAA;QAClB,IAAI,CAAC;YACH,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QAC1B,CAAC;QAAC,MAAM,CAAC;YACP,SAAQ;QACV,CAAC;QACD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,SAAQ;QACjD,MAAM,MAAM,GAAG,KASd,CAAA;QACD,IAAI,MAAM,CAAC,IAAI,KAAK,eAAe,EAAE,CAAC;YACpC,IAAI,MAAM,CAAC,IAAI,EAAE,QAAQ,KAAK,eAAe;gBAAE,mBAAmB,GAAG,IAAI,CAAA;YACzE,IACE,OAAO,MAAM,CAAC,IAAI,EAAE,SAAS,KAAK,QAAQ;gBAC1C,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,EAC5B,CAAC;gBACD,SAAS,GAAG,MAAM,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,CAAA;YAC1C,CAAC;YACD,SAAQ;QACV,CAAC;QACD,IACE,MAAM,CAAC,IAAI,KAAK,cAAc;YAC9B,CAAC,mBAAmB;YACpB,CAAC,SAAS;YACV,iBAAiB,CAAC,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC,MAAM,GAAG,GAAG;YAEzD,SAAQ;QACV,IACE,OAAO,MAAM,CAAC,IAAI,EAAE,MAAM,KAAK,QAAQ;YACvC,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,KAAK,MAAM,EAC3C,CAAC;YACD,SAAQ;QACV,CAAC;QACD,IAAI,OAAO,MAAM,CAAC,IAAI,EAAE,OAAO,KAAK,QAAQ,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE;YACzE,SAAQ;QACV,OAAO,CAAC,IAAI,CAAC;YACX,SAAS,EAAE,iBAAiB,CAAC,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;YACvD,IAAI,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO;YACzB,SAAS,EAAE,SAAS,CAAC,MAAM,CAAC,SAAS,CAAC;SACvC,CAAC,CAAA;IACJ,CAAC;IACD,OAAO,OAAO,CAAA;AAChB,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,wBAAwB;IAG5C,MAAM,KAAK,GAAG,MAAM,kBAAkB,EAAE,CAAA;IACxC,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,GAAG,CAC/B,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,EAAE,EAAE;QACvB,IAAI,OAAe,CAAA;QACnB,IAAI,CAAC;YACH,OAAO,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;QACxC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,EAAE,CAAA;QACX,CAAC;QACD,MAAM,QAAQ,GAAG,IAAI,CAAC,KAAK,CACzB,sDAAsD,CACvD,CAAA;QACD,OAAO,uBAAuB,CAAC,OAAO,EAAE;YACtC,MAAM,EAAE,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,QAAQ;YACnC,SAAS,EAAE,QAAQ,EAAE,CAAC,CAAC,CAAC;SACzB,CAAC,CAAA;IACJ,CAAC,CAAC,CACH,CAAA;IACD,OAAO,OAAO,CAAC,IAAI,EAAE,CAAA;AACvB,CAAC;AAED,KAAK,UAAU,yBAAyB;IACtC,OAAO,CAAC,MAAM,SAAS,CAAC,+BAA+B,EAAE,EAAE,QAAQ,CAAC,CAAC,CAAC,MAAM,CAC1E,CAAC,IAAI,EAAE,EAAE,CAAC,oDAAoD,CAAC,IAAI,CAAC,IAAI,CAAC,CAC1E,CAAA;AACH,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,uBAAuB;IAC3C,MAAM,SAAS,GAAG,MAAM,uBAAuB,CAC7C,wCAAwC,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CACxE,CAAA;IACD,OAAO;QACL,GAAG,CAAC,MAAM,yBAAyB,EAAE,CAAC;QACtC,GAAG,SAAS,CAAC,KAAK;QAClB,GAAG,SAAS,CAAC,SAAS,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,GAAG,IAAI,MAAM,CAAC,CAAC;KAChE,CAAA;AACH,CAAC;AAUD,SAAS,UAAU,CAAC,KAAc;IAChC,OAAO,OAAO,KAAK,KAAK,QAAQ,IAAI,MAAM,CAAC,aAAa,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,CAAA;AAC/E,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,uBAAuB,CACrC,OAAe,EACf,iBAAyB;IAEzB,IAAI,SAAS,GAAG,iBAAiB,CAAA;IACjC,MAAM,SAAS,GAA2B,EAAE,CAAA;IAC5C,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACvC,IAAI,KAcH,CAAA;QACD,IAAI,CAAC;YACH,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QAC1B,CAAC;QAAC,MAAM,CAAC;YACP,SAAQ;QACV,CAAC;QACD,IAAI,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,SAAQ;QACjD,IACE,KAAK,CAAC,IAAI,KAAK,eAAe;YAC9B,OAAO,KAAK,CAAC,IAAI,EAAE,SAAS,KAAK,QAAQ,EACzC,CAAC;YACD,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,SAAS,CAAA;QACtD,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,KAAK,kBAAkB,IAAI,KAAK,CAAC,OAAO;YAAE,SAAQ;QAChE,MAAM,EAAE,GAAG,SAAS,CAAC,KAAK,CAAC,SAAS,CAAC,CAAA;QACrC,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,YAAY,CAAA;QACxC,IACE,CAAC,EAAE;YACH,CAAC,SAAS;YACV,CAAC,OAAO;YACR,OAAO,OAAO,KAAK,QAAQ;YAC3B,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC;YAEtB,SAAQ;QACV,KAAK,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;YACtD,MAAM,KAAK,GAAG,MAAM,EAAE,KAAK,EAAE,WAAW,CAAA;YACxC,MAAM,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,YAAY,CAAA;YAC1C,IAAI,CAAC,KAAK,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC;gBAAE,SAAQ;YACjE,MAAM,MAAM,GAAG,KAAK,GAAG,MAAM,CAAA;YAC7B,IAAI,CAAC,MAAM,CAAC,aAAa,CAAC,MAAM,CAAC;gBAAE,SAAQ;YAC3C,SAAS,CAAC,IAAI,CAAC;gBACb,SAAS;gBACT,KAAK;gBACL,SAAS,EAAE,EAAE;gBACb,MAAM;gBACN,QAAQ,EAAE,UAAU,CAAC,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC;oBAC3C,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK;oBACvB,CAAC,CAAC,CAAC;aACN,CAAC,CAAA;QACJ,CAAC;IACH,CAAC;IACD,OAAO,SAAS,CAAA;AAClB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB;IACrC,MAAM,SAAS,GAA2B,EAAE,CAAA;IAC5C,KAAK,MAAM,IAAI,IAAI,MAAM,yBAAyB,EAAE,EAAE,CAAC;QACrD,IAAI,CAAC;YACH,SAAS,CAAC,IAAI,CACZ,GAAG,uBAAuB,CACxB,MAAM,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC,EAC5B,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CACxB,CACF,CAAA;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,OAAO,CAAC,IAAI,CACV,gCAAgC,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,CACrH,CAAA;QACH,CAAC;IACH,CAAC;IACD,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAA;IAChE,MAAM,SAAS,GAAG,IAAI,cAAc,CAAC,gBAAgB,CAAC,CAAA;IACtD,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAgD,CAAA;IACxE,MAAM,cAAc,GAAG,IAAI,GAAG,EAAkB,CAAA;IAChD,KAAK,MAAM,QAAQ,IAAI,SAAS,EAAE,CAAC;QACjC,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,QAAQ,CAAC,SAAS,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAA;QAChE,cAAc,CAAC,GAAG,CAChB,IAAI,CAAC,SAAS,CAAC;YACb,QAAQ,CAAC,SAAS;YAClB,QAAQ,CAAC,KAAK,CAAC,OAAO,CAAC,oBAAoB,EAAE,EAAE,CAAC;SACjD,CAAC,EACF,QAAQ,CAAC,SAAS,CACnB,CAAA;QACD,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAA;QAC9D,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,CAAA;QACvD,MAAM,QAAQ,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,QAAQ,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAA;QAC7D,IAAI,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,CAAC;YAC3B,SAAS,CAAC,QAAQ,CAChB,QAAQ,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAC/B,QAAQ,CAAC,KAAK,EACd,MAAM,GAAG,MAAM,CAAC,MAAM,EACtB,QAAQ,GAAG,MAAM,CAAC,QAAQ,CAC3B,CAAA;QACH,CAAC;QACD,QAAQ,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC,CAAA;IACzC,CAAC;IACD,MAAM,SAAS,GAAG,MAAM,uBAAuB,CAC7C,wCAAwC,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CACxE,CAAA;IACD,KAAK,MAAM,SAAS,IAAI,MAAM,oBAAoB,CAAC,SAAS,CAAC,EAAE,CAAC;QAC9D,MAAM,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC;YACzB,SAAS,CAAC,SAAS;YACnB,SAAS,CAAC,KAAK,CAAC,OAAO,CAAC,oBAAoB,EAAE,EAAE,CAAC;SAClD,CAAC,CAAA;QACF,MAAM,QAAQ,GAAG,cAAc,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QACxC,2EAA2E;QAC3E,wEAAwE;QACxE,IAAI,QAAQ,IAAI,SAAS,CAAC,SAAS,IAAI,QAAQ;YAAE,SAAQ;QACzD,SAAS,CAAC,QAAQ,CAChB,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAChC,SAAS,CAAC,KAAK,EACf,SAAS,CAAC,MAAM,EAChB,CAAC,CACF,CAAA;IACH,CAAC;IACD,OAAO,SAAS,CAAC,MAAM,EAAE,CAAA;AAC3B,CAAC"}
@@ -1,4 +1,4 @@
1
- import { type PromptActivityAggregate, type PromptLine } from '../prompt-stats.js';
1
+ import { IDE_PROMPT_SOURCES, type PromptActivityAggregate, type PromptLine, type ScannedPromptActivity } from '../prompt-stats.js';
2
2
  import { type AggregateScan, type PromptActivity, type ScanResult, type TokensMessages, type Tool, type UsageLine } from './index.js';
3
3
  /**
4
4
  * Bumping this makes `loadScanState` return null, which re-reads every log
@@ -21,12 +21,15 @@ import { type AggregateScan, type PromptActivity, type ScanResult, type TokensMe
21
21
  * the wipe this time. For anyone still on an older CLI it costs one extra
22
22
  * rescan and nothing else.
23
23
  *
24
+ * 5: prompt activity is partitioned by source, so rebuilding a Claude log or
25
+ * rotating an IDE transcript cannot erase or double-count another harness.
26
+ *
24
27
  * Token totals are unaffected: they travel as cumulative absolutes that the
25
28
  * server diffs against its own per-machine snapshot, and `state.uploaded` is
26
29
  * only the tick's "nothing moved" short-circuit — so a rebuild re-baselines
27
30
  * rather than double-counting.
28
31
  */
29
- export declare const SCAN_STATE_VERSION = 4;
32
+ export declare const SCAN_STATE_VERSION = 5;
30
33
  /** How long a session is kept in the state after its last prompt. */
31
34
  export declare const PROMPT_SESSION_RETENTION_DAYS = 45;
32
35
  /** How long a per-day prompt tally is kept. Matches the wire's date cap. */
@@ -93,6 +96,9 @@ export type ScanState = {
93
96
  dirty: string[];
94
97
  /** Prompt sessions and per-day counts, plus what's outstanding. */
95
98
  prompts: PromptState;
99
+ promptSources: Record<string, PromptActivityAggregate>;
100
+ /** Prompt-only snapshots are re-read only when their file set changes. */
101
+ promptFiles: Record<string, Record<string, FileState>>;
96
102
  /** Cumulative totals as of the last accepted upload. */
97
103
  uploaded: {
98
104
  toolTotals: Record<string, number>;
@@ -153,10 +159,17 @@ export type SqliteSource = {
153
159
  dbPath: () => string;
154
160
  scan: () => Promise<ScanResult>;
155
161
  };
162
+ export type SnapshotSource = {
163
+ tool: Tool;
164
+ files: () => Promise<string[]>;
165
+ scan: () => Promise<ScanResult>;
166
+ };
156
167
  export type TickSources = {
157
168
  jsonl: JsonlSource[];
158
169
  codex: CodexSource;
159
170
  sqlite: SqliteSource[];
171
+ prompts?: typeof IDE_PROMPT_SOURCES;
172
+ snapshots?: SnapshotSource[];
160
173
  };
161
174
  /** The real harnesses. Injectable so the tick can be tested against a tmp dir. */
162
175
  export declare function defaultSources(): TickSources;
@@ -177,7 +190,9 @@ export type TickOutcome = {
177
190
  * cold start (fresh install, or a state file this version can't read) is
178
191
  * exactly the case where we have no evidence any of it ever reached the server.
179
192
  */
180
- export declare function runTick(prev: ScanState | null, sources?: TickSources): Promise<TickOutcome>;
193
+ export declare function runTick(prev: ScanState | null, sources?: TickSources, options?: {
194
+ promptActivity?: boolean;
195
+ }): Promise<TickOutcome>;
181
196
  /** Cumulative per-tool and per-model totals for this machine. */
182
197
  export declare function cumulativeTotals(state: ScanState): {
183
198
  toolTotals: Record<string, number>;
@@ -219,7 +234,7 @@ export declare function markUploaded(state: ScanState): void;
219
234
  export type StagedPrompts =
220
235
  /** A prompt scan ran — re-base the prompt state on its aggregate. */
221
236
  {
222
- scanned: PromptActivityAggregate;
237
+ scanned: ScannedPromptActivity;
223
238
  }
224
239
  /** The user is at the `none` tier — drop the prompt state entirely. */
225
240
  | {
@@ -1 +1 @@
1
- {"version":3,"file":"incremental.d.ts","sourceRoot":"","sources":["../../src/scanners/incremental.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"incremental.d.ts","sourceRoot":"","sources":["../../src/scanners/incremental.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,9 @@
1
1
  import { mkdir, open, readFile, stat, writeFile } from 'node:fs/promises';
2
2
  import { dirname, join } from 'node:path';
3
- import { addPromptToActivity, emptyPromptActivity, parsePromptLine, } from '../prompt-stats.js';
3
+ import { addPromptToActivity, countWords, emptyPromptActivity, IDE_PROMPT_SOURCES, mergePromptActivities, parsePromptLine, } from '../prompt-stats.js';
4
4
  import { getSessionPath } from '../session.js';
5
+ import { antigravityTokenFiles, scanAntigravity } from './antigravity.js';
6
+ import { githubCopilotTokenFiles, scanGitHubCopilot } from './github-copilot.js';
5
7
  import { claudeCodeFiles, codexDateForFile, codexFiles, codexFileTotals, dateDaysAgo, grokLogFiles, hermesDbPath, openclawFiles, opencodeDbPath, PROMPT_ACTIVITY_DATE_CAP, PROMPT_ACTIVITY_SESSION_CAP, parseClaudeCodeLine, parseGrokLine, parseOpenclawLine, scanHermes, scanOpenCode, toDateStr, } from './index.js';
6
8
  // The incremental half of the scanners: what `hacklab sync --tick` runs every
7
9
  // minute instead of the full stateless re-scan the daily job does.
@@ -38,12 +40,15 @@ import { claudeCodeFiles, codexDateForFile, codexFiles, codexFileTotals, dateDay
38
40
  * the wipe this time. For anyone still on an older CLI it costs one extra
39
41
  * rescan and nothing else.
40
42
  *
43
+ * 5: prompt activity is partitioned by source, so rebuilding a Claude log or
44
+ * rotating an IDE transcript cannot erase or double-count another harness.
45
+ *
41
46
  * Token totals are unaffected: they travel as cumulative absolutes that the
42
47
  * server diffs against its own per-machine snapshot, and `state.uploaded` is
43
48
  * only the tick's "nothing moved" short-circuit — so a rebuild re-baselines
44
49
  * rather than double-counting.
45
50
  */
46
- export const SCAN_STATE_VERSION = 4;
51
+ export const SCAN_STATE_VERSION = 5;
47
52
  /** How long a session is kept in the state after its last prompt. */
48
53
  export const PROMPT_SESSION_RETENTION_DAYS = 45;
49
54
  /** How long a per-day prompt tally is kept. Matches the wire's date cap. */
@@ -64,6 +69,8 @@ export function emptyState() {
64
69
  harnesses: {},
65
70
  dirty: [],
66
71
  prompts: emptyPromptState(),
72
+ promptSources: {},
73
+ promptFiles: {},
67
74
  uploaded: { toolTotals: {}, modelTotals: {} },
68
75
  };
69
76
  }
@@ -105,7 +112,7 @@ export async function loadScanState() {
105
112
  * forever on a machine that never consented (the tick still counts prompts at
106
113
  * the `none` tier — it just never sends them).
107
114
  */
108
- function prunePrompts(prompts) {
115
+ function prunePromptAggregate(prompts) {
109
116
  const sessionCutoff = dateDaysAgo(PROMPT_SESSION_RETENTION_DAYS);
110
117
  for (const [id, session] of Object.entries(prompts.sessions)) {
111
118
  if (session.lastActiveAt.slice(0, 10) < sessionCutoff) {
@@ -117,6 +124,9 @@ function prunePrompts(prompts) {
117
124
  if (date < dateCutoff)
118
125
  delete prompts.daily[date];
119
126
  }
127
+ }
128
+ function prunePrompts(prompts) {
129
+ prunePromptAggregate(prompts);
120
130
  prompts.dirtySessions = prompts.dirtySessions.filter((id) => prompts.sessions[id] !== undefined);
121
131
  prompts.dirtyDates = prompts.dirtyDates.filter((date) => prompts.daily[date] !== undefined);
122
132
  }
@@ -124,6 +134,9 @@ function prunePrompts(prompts) {
124
134
  * window (they'd otherwise accumulate forever). Best-effort. */
125
135
  export async function saveScanState(state) {
126
136
  prunePrompts(state.prompts);
137
+ for (const source of Object.values(state.promptSources)) {
138
+ prunePromptAggregate(source);
139
+ }
127
140
  try {
128
141
  await mkdir(dirname(scanStatePath()), { recursive: true });
129
142
  await writeFile(scanStatePath(), `${JSON.stringify(state)}\n`, 'utf8');
@@ -285,6 +298,19 @@ export function defaultSources() {
285
298
  { tool: 'hermes', dbPath: hermesDbPath, scan: scanHermes },
286
299
  { tool: 'opencode', dbPath: opencodeDbPath, scan: scanOpenCode },
287
300
  ],
301
+ prompts: IDE_PROMPT_SOURCES,
302
+ snapshots: [
303
+ {
304
+ tool: 'github_copilot',
305
+ files: githubCopilotTokenFiles,
306
+ scan: scanGitHubCopilot,
307
+ },
308
+ {
309
+ tool: 'antigravity',
310
+ files: antigravityTokenFiles,
311
+ scan: scanAntigravity,
312
+ },
313
+ ],
288
314
  };
289
315
  }
290
316
  async function statFiles(paths) {
@@ -328,14 +354,12 @@ async function tickJsonl(state, src, dirty) {
328
354
  const target = invalidated ? new Set() : dirty;
329
355
  if (invalidated)
330
356
  resetHarness(h);
331
- // Same story for prompts: a re-read has to rebuild the aggregate from
332
- // scratch, or every session in it would be counted twice.
333
- const rebuildPrompts = invalidated && src.parsePrompt !== undefined;
334
- const prompts = rebuildPrompts
335
- ? emptyPromptActivity()
336
- : state.prompts;
337
- const dirtySessions = new Set(state.prompts.dirtySessions);
338
- const dirtyDates = new Set(state.prompts.dirtyDates);
357
+ // Keep each source independent: a rewritten Claude transcript must not
358
+ // discard Copilot/Antigravity activity on the same date.
359
+ if (src.parsePrompt && (invalidated || !state.promptSources[src.tool])) {
360
+ state.promptSources[src.tool] = emptyPromptActivity();
361
+ }
362
+ const prompts = state.promptSources[src.tool];
339
363
  let changed = invalidated;
340
364
  for (const file of files) {
341
365
  const prev = h.files[file.path];
@@ -347,10 +371,8 @@ async function tickJsonl(state, src, dirty) {
347
371
  let fallbackDate = null;
348
372
  for (const line of text.split('\n')) {
349
373
  const prompt = src.parsePrompt?.(line);
350
- if (prompt) {
351
- const touched = addPromptToActivity(prompts, prompt);
352
- dirtySessions.add(touched.sessionId);
353
- dirtyDates.add(touched.date);
374
+ if (prompt && prompts) {
375
+ addPromptToActivity(prompts, prompt);
354
376
  }
355
377
  const usage = src.parse(line);
356
378
  if (!usage)
@@ -367,13 +389,8 @@ async function tickJsonl(state, src, dirty) {
367
389
  }
368
390
  if (before)
369
391
  markMovedDates(before, aggregatesOf(h), dirty);
370
- if (rebuildPrompts) {
371
- // The rebuilt aggregate replaces the old one, dirtying only what moved.
372
- replacePromptActivity(state.prompts, prompts);
373
- }
374
- else if (src.parsePrompt) {
375
- state.prompts.dirtySessions = [...dirtySessions];
376
- state.prompts.dirtyDates = [...dirtyDates].sort();
392
+ if (src.parsePrompt && changed) {
393
+ replacePromptActivity(state.prompts, mergePromptActivities(Object.values(state.promptSources)));
377
394
  }
378
395
  return changed;
379
396
  }
@@ -437,6 +454,50 @@ async function tickSqlite(state, src, dirty) {
437
454
  h.walMtimeMs = walMtimeMs;
438
455
  return true;
439
456
  }
457
+ /** Cumulative session records need replacement, never additive tail parsing. */
458
+ async function tickSnapshot(state, source, dirty) {
459
+ const files = await statFiles(await source.files());
460
+ const h = harness(state, source.tool);
461
+ if (Object.keys(h.files).length === files.length &&
462
+ files.every((file) => h.files[file.path]?.size === file.size &&
463
+ h.files[file.path]?.mtimeMs === file.mtimeMs))
464
+ return false;
465
+ replaceAggregates(h, aggregatesOfResult(await source.scan()), dirty);
466
+ h.files = Object.fromEntries(files.map((file) => [
467
+ file.path,
468
+ { size: file.size, mtimeMs: file.mtimeMs, offset: 0 },
469
+ ]));
470
+ return true;
471
+ }
472
+ async function tickPromptSnapshot(state, source) {
473
+ const files = await statFiles(await source.files());
474
+ const previous = state.promptFiles[source.id];
475
+ if (previous &&
476
+ Object.keys(previous).length === files.length &&
477
+ files.every((file) => previous[file.path]?.size === file.size &&
478
+ previous[file.path]?.mtimeMs === file.mtimeMs))
479
+ return false;
480
+ const activity = emptyPromptActivity();
481
+ for (const prompt of await source.scan()) {
482
+ if (!prompt.timestamp)
483
+ continue;
484
+ const words = countWords(prompt.text);
485
+ if (words > 0) {
486
+ addPromptToActivity(activity, {
487
+ sessionId: prompt.sessionId,
488
+ timestamp: prompt.timestamp,
489
+ words,
490
+ });
491
+ }
492
+ }
493
+ state.promptSources[source.id] = activity;
494
+ state.promptFiles[source.id] = Object.fromEntries(files.map((file) => [
495
+ file.path,
496
+ { size: file.size, mtimeMs: file.mtimeMs, offset: 0 },
497
+ ]));
498
+ replacePromptActivity(state.prompts, mergePromptActivities(Object.values(state.promptSources)));
499
+ return true;
500
+ }
440
501
  /**
441
502
  * One incremental pass over every harness. `prev` is the saved state, or null
442
503
  * for a cold start — in which case this reads everything and marks no *token*
@@ -449,7 +510,7 @@ async function tickSqlite(state, src, dirty) {
449
510
  * cold start (fresh install, or a state file this version can't read) is
450
511
  * exactly the case where we have no evidence any of it ever reached the server.
451
512
  */
452
- export async function runTick(prev, sources = defaultSources()) {
513
+ export async function runTick(prev, sources = defaultSources(), options = {}) {
453
514
  const cold = prev === null;
454
515
  const state = prev ?? emptyState();
455
516
  const dirty = new Set(state.dirty);
@@ -464,6 +525,16 @@ export async function runTick(prev, sources = defaultSources()) {
464
525
  if (await tickSqlite(state, src, dirty))
465
526
  changed = true;
466
527
  }
528
+ for (const source of sources.snapshots ?? []) {
529
+ if (await tickSnapshot(state, source, dirty))
530
+ changed = true;
531
+ }
532
+ if (options.promptActivity !== false) {
533
+ for (const source of sources.prompts ?? []) {
534
+ if (await tickPromptSnapshot(state, source))
535
+ changed = true;
536
+ }
537
+ }
467
538
  if (cold) {
468
539
  state.dirty = [];
469
540
  state.prompts.dirtySessions = Object.keys(state.prompts.sessions);
@@ -641,11 +712,21 @@ export async function stageFullScan(results, prompts = 'untouched', sources = de
641
712
  const previous = await loadScanState();
642
713
  const state = emptyState();
643
714
  state.prompts = previous?.prompts ?? emptyPromptState();
715
+ state.promptSources = previous?.promptSources ?? {};
716
+ state.promptFiles = previous?.promptFiles ?? {};
644
717
  // Nothing is claimed as in-flight any more: whatever the last upload sent
645
718
  // was either acked (and cleared) or lost. A caller with authority re-derives
646
719
  // the claim below; one without leaves the rows dirty for the tick.
647
720
  state.prompts.sent = undefined;
648
721
  if (prompts !== 'untouched') {
722
+ state.promptSources = prompts.scanned
723
+ ? (prompts.scanned.sources ?? {
724
+ claude_code: mergePromptActivities([prompts.scanned]),
725
+ })
726
+ : {};
727
+ // A fresh scan's partitions are authoritative. Recheck IDE snapshots on
728
+ // the next tick rather than advancing a cursor past concurrent writes.
729
+ state.promptFiles = {};
649
730
  replacePromptActivity(state.prompts, prompts.scanned ?? emptyPromptActivity());
650
731
  }
651
732
  for (const result of results) {