@ascenda-one/history-import 0.1.12
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/README.md +127 -0
- package/dist/cli.js +3889 -0
- package/dist/cli.js.map +1 -0
- package/dist/daySlice.js +98 -0
- package/dist/daySlice.js.map +1 -0
- package/dist/extractors/claudeCode.js +779 -0
- package/dist/extractors/claudeCode.js.map +1 -0
- package/dist/extractors/cursor.js +601 -0
- package/dist/extractors/cursor.js.map +1 -0
- package/dist/extractors/git.js +9 -0
- package/dist/extractors/git.js.map +1 -0
- package/dist/extractors/vscode.js +615 -0
- package/dist/extractors/vscode.js.map +1 -0
- package/dist/localHandoff.js +238 -0
- package/dist/localHandoff.js.map +1 -0
- package/dist/retention.js +68 -0
- package/dist/retention.js.map +1 -0
- package/dist/scan.js +220 -0
- package/dist/scan.js.map +1 -0
- package/dist/ship.js +208 -0
- package/dist/ship.js.map +1 -0
- package/dist/staging.js +121 -0
- package/dist/staging.js.map +1 -0
- package/dist/stores.js +25 -0
- package/dist/stores.js.map +1 -0
- package/dist/types.js +74 -0
- package/dist/types.js.map +1 -0
- package/package.json +32 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,3889 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire as __cr } from 'node:module'; const require = __cr(import.meta.url);
|
|
3
|
+
var __create = Object.create;
|
|
4
|
+
var __defProp = Object.defineProperty;
|
|
5
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
8
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
9
|
+
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
10
|
+
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
11
|
+
}) : x)(function(x) {
|
|
12
|
+
if (typeof require !== "undefined") return require.apply(this, arguments);
|
|
13
|
+
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
14
|
+
});
|
|
15
|
+
var __commonJS = (cb, mod) => function __require2() {
|
|
16
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
17
|
+
};
|
|
18
|
+
var __copyProps = (to, from, except, desc) => {
|
|
19
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
20
|
+
for (let key of __getOwnPropNames(from))
|
|
21
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
22
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
23
|
+
}
|
|
24
|
+
return to;
|
|
25
|
+
};
|
|
26
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
27
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
28
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
29
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
30
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
31
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
32
|
+
mod
|
|
33
|
+
));
|
|
34
|
+
|
|
35
|
+
// ../packages/tool-kit/out/commandClassifier.js
|
|
36
|
+
var require_commandClassifier = __commonJS({
|
|
37
|
+
"../packages/tool-kit/out/commandClassifier.js"(exports) {
|
|
38
|
+
"use strict";
|
|
39
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
+
exports.classifyCommand = classifyCommand;
|
|
41
|
+
exports.isVerificationCommand = isVerificationCommand;
|
|
42
|
+
function classifyCommand(command) {
|
|
43
|
+
if (!command)
|
|
44
|
+
return "unknown";
|
|
45
|
+
const value = command.toLowerCase().trim();
|
|
46
|
+
if (/\b(test|jest|vitest|mocha|pytest|rspec|go test|cargo test|dotnet test|xcodebuild test)\b/.test(value) || /\bnpm\s+(run\s+)?test\b/.test(value) || /\byarn\s+test\b/.test(value) || /\bpnpm\s+(run\s+)?test\b/.test(value))
|
|
47
|
+
return "test";
|
|
48
|
+
if (/\b(lint|eslint|ruff|flake8|pylint|rubocop)\b/.test(value) || /\bnpm\s+(run\s+)?lint\b/.test(value))
|
|
49
|
+
return "lint";
|
|
50
|
+
if (/\b(tsc|typecheck|mypy|pyright|sorbet|flow)\b/.test(value) || /\bnpm\s+(run\s+)?typecheck\b/.test(value))
|
|
51
|
+
return "typecheck";
|
|
52
|
+
if (/\b(build|webpack|vite build|next build|turbo build|cargo build|go build|dotnet build|xcodebuild)\b/.test(value) || /\bnpm\s+(run\s+)?build\b/.test(value))
|
|
53
|
+
return "build";
|
|
54
|
+
if (/\b(git)\b/.test(value))
|
|
55
|
+
return "git";
|
|
56
|
+
if (/\b(npm install|yarn install|pnpm install|bun install|pip install|poetry install|bundle install)\b/.test(value))
|
|
57
|
+
return "install";
|
|
58
|
+
if (/\b(npm start|npm run dev|yarn dev|pnpm dev|next dev|vite|node|python|tsx|ts-node)\b/.test(value))
|
|
59
|
+
return "run";
|
|
60
|
+
return "unknown";
|
|
61
|
+
}
|
|
62
|
+
function isVerificationCommand(commandClass) {
|
|
63
|
+
return commandClass === "test" || commandClass === "lint" || commandClass === "typecheck" || commandClass === "build";
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// ../packages/tool-kit/out/commandHeads.js
|
|
69
|
+
var require_commandHeads = __commonJS({
|
|
70
|
+
"../packages/tool-kit/out/commandHeads.js"(exports) {
|
|
71
|
+
"use strict";
|
|
72
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
73
|
+
exports.commandHeads = commandHeads;
|
|
74
|
+
function commandHeads(value) {
|
|
75
|
+
const stripped = stripQuoted(stripHeredocBodies(value));
|
|
76
|
+
return stripped.split(/[;&|()\n]+/).map((segment) => {
|
|
77
|
+
let head = segment.trim();
|
|
78
|
+
for (; ; ) {
|
|
79
|
+
const env = /^[a-z_][a-z0-9_]*=\S*\s+/.exec(head);
|
|
80
|
+
if (!env)
|
|
81
|
+
break;
|
|
82
|
+
head = head.slice(env[0].length);
|
|
83
|
+
}
|
|
84
|
+
return head;
|
|
85
|
+
}).filter((head) => head.length > 0);
|
|
86
|
+
}
|
|
87
|
+
function stripHeredocBodies(value) {
|
|
88
|
+
const marker = /<<-?\s*(["']?)(\w+)\1/;
|
|
89
|
+
let kept = "";
|
|
90
|
+
let rest = value;
|
|
91
|
+
for (; ; ) {
|
|
92
|
+
const opened = marker.exec(rest);
|
|
93
|
+
if (!opened)
|
|
94
|
+
return kept + rest;
|
|
95
|
+
const introLineEnd = rest.indexOf("\n", opened.index + opened[0].length);
|
|
96
|
+
if (introLineEnd === -1)
|
|
97
|
+
return kept + rest;
|
|
98
|
+
kept += rest.slice(0, introLineEnd + 1);
|
|
99
|
+
const body = rest.slice(introLineEnd + 1);
|
|
100
|
+
const terminator = new RegExp(`^\\s*${opened[2]}\\s*$`, "m").exec(body);
|
|
101
|
+
if (!terminator)
|
|
102
|
+
return kept;
|
|
103
|
+
rest = body.slice(terminator.index + terminator[0].length);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
function stripQuoted(value) {
|
|
107
|
+
let out = "";
|
|
108
|
+
let i = 0;
|
|
109
|
+
while (i < value.length) {
|
|
110
|
+
const ch = value[i];
|
|
111
|
+
if (ch === "'") {
|
|
112
|
+
const close = value.indexOf("'", i + 1);
|
|
113
|
+
if (close === -1)
|
|
114
|
+
return out;
|
|
115
|
+
i = close + 1;
|
|
116
|
+
} else if (ch === '"') {
|
|
117
|
+
let j = i + 1;
|
|
118
|
+
while (j < value.length && value[j] !== '"') {
|
|
119
|
+
j += value[j] === "\\" ? 2 : 1;
|
|
120
|
+
}
|
|
121
|
+
if (j >= value.length)
|
|
122
|
+
return out;
|
|
123
|
+
i = j + 1;
|
|
124
|
+
} else if (ch === "\\") {
|
|
125
|
+
out += value.slice(i, i + 2);
|
|
126
|
+
i += 2;
|
|
127
|
+
} else {
|
|
128
|
+
out += ch;
|
|
129
|
+
i += 1;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return out;
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
// ../packages/tool-kit/out/gitActionClassifier.js
|
|
138
|
+
var require_gitActionClassifier = __commonJS({
|
|
139
|
+
"../packages/tool-kit/out/gitActionClassifier.js"(exports) {
|
|
140
|
+
"use strict";
|
|
141
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
142
|
+
exports.classifyGitAction = classifyGitAction;
|
|
143
|
+
exports.isReworkGitAction = isReworkGitAction;
|
|
144
|
+
var commandHeads_1 = require_commandHeads();
|
|
145
|
+
function classifyGitAction(command) {
|
|
146
|
+
if (!command)
|
|
147
|
+
return void 0;
|
|
148
|
+
const value = command.toLowerCase().trim();
|
|
149
|
+
if (!/\bgit\b/.test(value))
|
|
150
|
+
return void 0;
|
|
151
|
+
const heads = (0, commandHeads_1.commandHeads)(value);
|
|
152
|
+
if (heads.some((h) => /^git\s+commit\b/.test(h) && /\s--amend\b/.test(h)))
|
|
153
|
+
return "amend";
|
|
154
|
+
if (heads.some((h) => /^git\s+revert\b/.test(h)))
|
|
155
|
+
return "revert";
|
|
156
|
+
if (heads.some((h) => /^git\s+reset\b/.test(h) && /\s--hard\b/.test(h)))
|
|
157
|
+
return "reset_hard";
|
|
158
|
+
if (heads.some((h) => /^git\s+restore\b/.test(h) && !/\s--staged\b/.test(h)))
|
|
159
|
+
return "restore";
|
|
160
|
+
if (heads.some((h) => /^git\s+checkout\b.*\s--\s/.test(h)))
|
|
161
|
+
return "restore";
|
|
162
|
+
if (heads.some((h) => /^git\s+push\b/.test(h)))
|
|
163
|
+
return "push";
|
|
164
|
+
if (heads.some((h) => /^git\s+commit\b/.test(h)))
|
|
165
|
+
return "commit";
|
|
166
|
+
return void 0;
|
|
167
|
+
}
|
|
168
|
+
function isReworkGitAction(action) {
|
|
169
|
+
return action === "revert" || action === "reset_hard" || action === "restore";
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
// ../packages/tool-kit/out/workMilestoneClassifier.js
|
|
175
|
+
var require_workMilestoneClassifier = __commonJS({
|
|
176
|
+
"../packages/tool-kit/out/workMilestoneClassifier.js"(exports) {
|
|
177
|
+
"use strict";
|
|
178
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
179
|
+
exports.classifyWorkMilestone = classifyWorkMilestone;
|
|
180
|
+
exports.invitesDebrief = invitesDebrief;
|
|
181
|
+
var commandHeads_1 = require_commandHeads();
|
|
182
|
+
function classifyWorkMilestone(command) {
|
|
183
|
+
if (!command)
|
|
184
|
+
return void 0;
|
|
185
|
+
const value = command.toLowerCase().trim();
|
|
186
|
+
if (!/\bgh\b/.test(value))
|
|
187
|
+
return void 0;
|
|
188
|
+
const heads = (0, commandHeads_1.commandHeads)(value);
|
|
189
|
+
if (heads.some((head) => /^gh\s+pr\s+merge\b/.test(head)))
|
|
190
|
+
return "pr_merged";
|
|
191
|
+
if (heads.some((head) => /^gh\s+issue\s+close\b/.test(head)))
|
|
192
|
+
return "issue_closed";
|
|
193
|
+
if (heads.some((head) => /^gh\s+pr\s+create\b/.test(head)))
|
|
194
|
+
return "pr_opened";
|
|
195
|
+
return void 0;
|
|
196
|
+
}
|
|
197
|
+
function invitesDebrief(kind) {
|
|
198
|
+
return kind === "pr_merged" || kind === "issue_closed";
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
// ../packages/tool-kit/out/buckets.js
|
|
204
|
+
var require_buckets = __commonJS({
|
|
205
|
+
"../packages/tool-kit/out/buckets.js"(exports) {
|
|
206
|
+
"use strict";
|
|
207
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
208
|
+
exports.bucketLinesChanged = bucketLinesChanged2;
|
|
209
|
+
exports.bucketDurationMs = bucketDurationMs4;
|
|
210
|
+
function bucketLinesChanged2(count) {
|
|
211
|
+
if (count <= 0)
|
|
212
|
+
return "0";
|
|
213
|
+
if (count <= 10)
|
|
214
|
+
return "1-10";
|
|
215
|
+
if (count <= 50)
|
|
216
|
+
return "10-50";
|
|
217
|
+
if (count <= 200)
|
|
218
|
+
return "50-200";
|
|
219
|
+
return "200+";
|
|
220
|
+
}
|
|
221
|
+
function bucketDurationMs4(durationMs) {
|
|
222
|
+
if (typeof durationMs !== "number" || !Number.isFinite(durationMs) || durationMs < 0)
|
|
223
|
+
return void 0;
|
|
224
|
+
const minutes = durationMs / 6e4;
|
|
225
|
+
if (minutes <= 1)
|
|
226
|
+
return "0-1m";
|
|
227
|
+
if (minutes <= 5)
|
|
228
|
+
return "1-5m";
|
|
229
|
+
if (minutes <= 10)
|
|
230
|
+
return "5-10m";
|
|
231
|
+
if (minutes <= 30)
|
|
232
|
+
return "10-30m";
|
|
233
|
+
if (minutes <= 60)
|
|
234
|
+
return "30-60m";
|
|
235
|
+
return "60m+";
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
// ../packages/tool-kit/out/afterHours.js
|
|
241
|
+
var require_afterHours = __commonJS({
|
|
242
|
+
"../packages/tool-kit/out/afterHours.js"(exports) {
|
|
243
|
+
"use strict";
|
|
244
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
245
|
+
exports.isAfterHours = isAfterHours4;
|
|
246
|
+
exports.utcOffsetMinutesAt = utcOffsetMinutesAt2;
|
|
247
|
+
exports.localHourAt = localHourAt;
|
|
248
|
+
function isAfterHours4(now = /* @__PURE__ */ new Date(), start = "19:00", end = "07:00") {
|
|
249
|
+
const currentMinutes = now.getHours() * 60 + now.getMinutes();
|
|
250
|
+
const startMinutes = parseTimeToMinutes(start, 19 * 60);
|
|
251
|
+
const endMinutes = parseTimeToMinutes(end, 7 * 60);
|
|
252
|
+
if (startMinutes === endMinutes)
|
|
253
|
+
return false;
|
|
254
|
+
if (startMinutes < endMinutes)
|
|
255
|
+
return currentMinutes >= startMinutes && currentMinutes < endMinutes;
|
|
256
|
+
return currentMinutes >= startMinutes || currentMinutes < endMinutes;
|
|
257
|
+
}
|
|
258
|
+
function parseTimeToMinutes(value, fallback) {
|
|
259
|
+
const match = /^(\d{1,2}):(\d{2})$/.exec(value.trim());
|
|
260
|
+
if (!match)
|
|
261
|
+
return fallback;
|
|
262
|
+
const hours = Number(match[1]);
|
|
263
|
+
const minutes = Number(match[2]);
|
|
264
|
+
if (Number.isNaN(hours) || Number.isNaN(minutes))
|
|
265
|
+
return fallback;
|
|
266
|
+
return Math.max(0, Math.min(23, hours)) * 60 + Math.max(0, Math.min(59, minutes));
|
|
267
|
+
}
|
|
268
|
+
function utcOffsetMinutesAt2(at) {
|
|
269
|
+
return -at.getTimezoneOffset();
|
|
270
|
+
}
|
|
271
|
+
function localHourAt(at, utcOffsetMinutes) {
|
|
272
|
+
const shifted = new Date(at.getTime() + utcOffsetMinutes * 6e4);
|
|
273
|
+
return shifted.getUTCHours();
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
// ../packages/tool-kit/out/payload.js
|
|
279
|
+
var require_payload = __commonJS({
|
|
280
|
+
"../packages/tool-kit/out/payload.js"(exports) {
|
|
281
|
+
"use strict";
|
|
282
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
283
|
+
exports.getString = getString;
|
|
284
|
+
exports.getNumber = getNumber;
|
|
285
|
+
exports.getNested = getNested;
|
|
286
|
+
exports.getNestedString = getNestedString;
|
|
287
|
+
exports.getNestedNumber = getNestedNumber;
|
|
288
|
+
exports.inferOutcome = inferOutcome;
|
|
289
|
+
exports.outcomeForHook = outcomeForHook;
|
|
290
|
+
exports.looksLikeCorrection = looksLikeCorrection;
|
|
291
|
+
function getString(input, keys) {
|
|
292
|
+
for (const key of keys) {
|
|
293
|
+
const value = input[key];
|
|
294
|
+
if (typeof value === "string" && value.trim())
|
|
295
|
+
return value;
|
|
296
|
+
}
|
|
297
|
+
return void 0;
|
|
298
|
+
}
|
|
299
|
+
function getNumber(input, keys) {
|
|
300
|
+
for (const key of keys) {
|
|
301
|
+
const value = input[key];
|
|
302
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
303
|
+
return value;
|
|
304
|
+
}
|
|
305
|
+
return void 0;
|
|
306
|
+
}
|
|
307
|
+
function getNested(input, path9) {
|
|
308
|
+
let current = input;
|
|
309
|
+
for (const segment of path9) {
|
|
310
|
+
if (!current || typeof current !== "object")
|
|
311
|
+
return void 0;
|
|
312
|
+
current = current[segment];
|
|
313
|
+
}
|
|
314
|
+
return current;
|
|
315
|
+
}
|
|
316
|
+
function getNestedString(input, paths) {
|
|
317
|
+
for (const path9 of paths) {
|
|
318
|
+
const value = getNested(input, path9);
|
|
319
|
+
if (typeof value === "string" && value.trim())
|
|
320
|
+
return value;
|
|
321
|
+
}
|
|
322
|
+
return void 0;
|
|
323
|
+
}
|
|
324
|
+
function getNestedNumber(input, paths) {
|
|
325
|
+
for (const path9 of paths) {
|
|
326
|
+
const value = getNested(input, path9);
|
|
327
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
328
|
+
return value;
|
|
329
|
+
}
|
|
330
|
+
return void 0;
|
|
331
|
+
}
|
|
332
|
+
function inferOutcome(input) {
|
|
333
|
+
const exitCode = getNumber(input, ["exitCode", "exit_code", "status"]) ?? getNestedNumber(input, [["tool_response", "exitCode"], ["tool_response", "exit_code"], ["result", "exitCode"], ["result", "exit_code"]]);
|
|
334
|
+
if (typeof exitCode === "number")
|
|
335
|
+
return exitCode === 0 ? "success" : "failure";
|
|
336
|
+
const error = getString(input, ["error", "errorMessage"]) ?? getNestedString(input, [["tool_response", "error"], ["result", "error"]]);
|
|
337
|
+
if (error)
|
|
338
|
+
return "failure";
|
|
339
|
+
return "unknown";
|
|
340
|
+
}
|
|
341
|
+
function outcomeForHook(hookName, input) {
|
|
342
|
+
if (hookName === "PostToolUseFailure") {
|
|
343
|
+
const interrupted = input["is_interrupt"] === true || getNested(input, ["tool_response", "interrupted"]) === true;
|
|
344
|
+
return interrupted ? "cancelled" : "failure";
|
|
345
|
+
}
|
|
346
|
+
if (hookName === "PostToolUse") {
|
|
347
|
+
if (getNested(input, ["tool_response", "interrupted"]) === true)
|
|
348
|
+
return "cancelled";
|
|
349
|
+
return "success";
|
|
350
|
+
}
|
|
351
|
+
return "unknown";
|
|
352
|
+
}
|
|
353
|
+
function looksLikeCorrection(text) {
|
|
354
|
+
if (!text)
|
|
355
|
+
return false;
|
|
356
|
+
return /\b(wrong|incorrect|try again|fix|not what i asked|that's not|that is not|redo|regenerate|you missed|doesn't work|does not work)\b/i.test(text);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
// ../packages/tool-contract/out/index.js
|
|
362
|
+
var require_out = __commonJS({
|
|
363
|
+
"../packages/tool-contract/out/index.js"(exports) {
|
|
364
|
+
"use strict";
|
|
365
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
366
|
+
exports.ASCENDA_SEMANTIC_PROVENANCE = exports.ASCENDA_HISTORICAL_CONSENT_SCOPE = exports.ASCENDA_COLLABORATION_CONSENT_SCOPE = exports.ASCENDA_SEMANTIC_CONSENT_SCOPE = exports.ASCENDA_PROVENANCE = exports.ASCENDA_CONSENT_SCOPE = exports.EVENT_WORKLOAD_CATEGORY = exports.COLLABORATION_EVENT_TYPES = exports.SEMANTIC_WORK_SIGNAL_EVENT_TYPES = void 0;
|
|
367
|
+
exports.SEMANTIC_WORK_SIGNAL_EVENT_TYPES = [
|
|
368
|
+
"approach_churn_detected",
|
|
369
|
+
"goal_drift_detected",
|
|
370
|
+
"progress_stalled",
|
|
371
|
+
"progress_recovered",
|
|
372
|
+
"session_intention_declared",
|
|
373
|
+
"scope_change_declared"
|
|
374
|
+
];
|
|
375
|
+
exports.COLLABORATION_EVENT_TYPES = [
|
|
376
|
+
"review_requested_of_me",
|
|
377
|
+
"review_given",
|
|
378
|
+
"pull_request_opened"
|
|
379
|
+
];
|
|
380
|
+
exports.EVENT_WORKLOAD_CATEGORY = {
|
|
381
|
+
create_focus_session: "creation",
|
|
382
|
+
ai_prompt_submitted: "creation",
|
|
383
|
+
ai_generation_completed: "creation",
|
|
384
|
+
ai_file_write: "creation",
|
|
385
|
+
ai_file_edit: "creation",
|
|
386
|
+
editor_verification_activity: "verification",
|
|
387
|
+
compile_diagnostic: "verification",
|
|
388
|
+
editor_correction_activity: "supervision",
|
|
389
|
+
ai_correction_prompt: "supervision",
|
|
390
|
+
supervis_meeting_load: "supervision",
|
|
391
|
+
ai_tool_call_started: "supervision",
|
|
392
|
+
ai_tool_call_completed: "supervision",
|
|
393
|
+
ai_tool_call_failed: "supervision",
|
|
394
|
+
// Collaboration (the report's §4.2 collaboration family). Both review
|
|
395
|
+
// events are supervision: being asked to check work, and checking it, are
|
|
396
|
+
// the load the report's "verification overload" concern is about — the one
|
|
397
|
+
// that concentrates on senior engineers as a team adopts AI. Opening a pull
|
|
398
|
+
// request is creation: it is the point your own work leaves your hands.
|
|
399
|
+
review_requested_of_me: "supervision",
|
|
400
|
+
review_given: "supervision",
|
|
401
|
+
pull_request_opened: "creation",
|
|
402
|
+
context_pressure_high: "risk",
|
|
403
|
+
agent_loop_long: "risk",
|
|
404
|
+
after_hours_ai_session: "risk",
|
|
405
|
+
compile_error: "risk",
|
|
406
|
+
tool_failure: "risk",
|
|
407
|
+
recovery_offline_period: "neutral",
|
|
408
|
+
context_compression_manual: "neutral",
|
|
409
|
+
context_compression_auto: "neutral",
|
|
410
|
+
editor_activity: "neutral",
|
|
411
|
+
// Semantic (agent-observed) — see SEMANTIC_WORK_SIGNAL_EVENT_TYPES.
|
|
412
|
+
approach_churn_detected: "risk",
|
|
413
|
+
goal_drift_detected: "risk",
|
|
414
|
+
progress_stalled: "risk",
|
|
415
|
+
progress_recovered: "neutral",
|
|
416
|
+
session_intention_declared: "neutral",
|
|
417
|
+
scope_change_declared: "neutral"
|
|
418
|
+
};
|
|
419
|
+
exports.ASCENDA_CONSENT_SCOPE = "ide_telemetry";
|
|
420
|
+
exports.ASCENDA_PROVENANCE = "ai_work_telemetry";
|
|
421
|
+
exports.ASCENDA_SEMANTIC_CONSENT_SCOPE = "semantic_work_signals";
|
|
422
|
+
exports.ASCENDA_COLLABORATION_CONSENT_SCOPE = "workflow_telemetry";
|
|
423
|
+
exports.ASCENDA_HISTORICAL_CONSENT_SCOPE = "historical_import";
|
|
424
|
+
exports.ASCENDA_SEMANTIC_PROVENANCE = "semantic_work_signals";
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
// ../packages/tool-kit/out/eventLog.js
|
|
429
|
+
var require_eventLog = __commonJS({
|
|
430
|
+
"../packages/tool-kit/out/eventLog.js"(exports) {
|
|
431
|
+
"use strict";
|
|
432
|
+
var __createBinding = exports && exports.__createBinding || (Object.create ? function(o, m, k, k2) {
|
|
433
|
+
if (k2 === void 0) k2 = k;
|
|
434
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
435
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
436
|
+
desc = { enumerable: true, get: function() {
|
|
437
|
+
return m[k];
|
|
438
|
+
} };
|
|
439
|
+
}
|
|
440
|
+
Object.defineProperty(o, k2, desc);
|
|
441
|
+
} : function(o, m, k, k2) {
|
|
442
|
+
if (k2 === void 0) k2 = k;
|
|
443
|
+
o[k2] = m[k];
|
|
444
|
+
});
|
|
445
|
+
var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? function(o, v) {
|
|
446
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
447
|
+
} : function(o, v) {
|
|
448
|
+
o["default"] = v;
|
|
449
|
+
});
|
|
450
|
+
var __importStar = exports && exports.__importStar || /* @__PURE__ */ function() {
|
|
451
|
+
var ownKeys = function(o) {
|
|
452
|
+
ownKeys = Object.getOwnPropertyNames || function(o2) {
|
|
453
|
+
var ar = [];
|
|
454
|
+
for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
|
|
455
|
+
return ar;
|
|
456
|
+
};
|
|
457
|
+
return ownKeys(o);
|
|
458
|
+
};
|
|
459
|
+
return function(mod) {
|
|
460
|
+
if (mod && mod.__esModule) return mod;
|
|
461
|
+
var result = {};
|
|
462
|
+
if (mod != null) {
|
|
463
|
+
for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
464
|
+
}
|
|
465
|
+
__setModuleDefault(result, mod);
|
|
466
|
+
return result;
|
|
467
|
+
};
|
|
468
|
+
}();
|
|
469
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
470
|
+
exports.EVENT_LOG_ENV_VAR = void 0;
|
|
471
|
+
exports.expandUserPath = expandUserPath;
|
|
472
|
+
exports.resolveEventLogPath = resolveEventLogPath;
|
|
473
|
+
exports.appendEventLog = appendEventLog;
|
|
474
|
+
var fs9 = __importStar(__require("fs"));
|
|
475
|
+
var os3 = __importStar(__require("os"));
|
|
476
|
+
var path9 = __importStar(__require("path"));
|
|
477
|
+
exports.EVENT_LOG_ENV_VAR = "ASCENDA_EVENT_LOG_FILE";
|
|
478
|
+
var MAX_BYTES = 5 * 1024 * 1024;
|
|
479
|
+
function expandUserPath(configured) {
|
|
480
|
+
const value = configured?.trim();
|
|
481
|
+
if (!value)
|
|
482
|
+
return void 0;
|
|
483
|
+
if (value === "~")
|
|
484
|
+
return os3.homedir();
|
|
485
|
+
if (value.startsWith("~/"))
|
|
486
|
+
return path9.join(os3.homedir(), value.slice(2));
|
|
487
|
+
return path9.resolve(value);
|
|
488
|
+
}
|
|
489
|
+
function resolveEventLogPath() {
|
|
490
|
+
return expandUserPath(process.env[exports.EVENT_LOG_ENV_VAR]);
|
|
491
|
+
}
|
|
492
|
+
function appendEventLog(logFilePath, entry) {
|
|
493
|
+
try {
|
|
494
|
+
rotateIfLarge(logFilePath);
|
|
495
|
+
const dir = path9.dirname(logFilePath);
|
|
496
|
+
fs9.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
497
|
+
fs9.appendFileSync(logFilePath, `${JSON.stringify(entry)}
|
|
498
|
+
`, { encoding: "utf8", mode: 384 });
|
|
499
|
+
if (process.platform !== "win32")
|
|
500
|
+
fs9.chmodSync(logFilePath, 384);
|
|
501
|
+
} catch {
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
function rotateIfLarge(logFilePath) {
|
|
505
|
+
try {
|
|
506
|
+
if (fs9.statSync(logFilePath).size < MAX_BYTES)
|
|
507
|
+
return;
|
|
508
|
+
fs9.renameSync(logFilePath, `${logFilePath}.1`);
|
|
509
|
+
} catch {
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
});
|
|
514
|
+
|
|
515
|
+
// ../packages/tool-kit/out/http.js
|
|
516
|
+
var require_http = __commonJS({
|
|
517
|
+
"../packages/tool-kit/out/http.js"(exports) {
|
|
518
|
+
"use strict";
|
|
519
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
520
|
+
exports.AscendaApiError = void 0;
|
|
521
|
+
exports.createPairingSession = createPairingSession;
|
|
522
|
+
exports.getPairingStatus = getPairingStatus;
|
|
523
|
+
exports.renewToolToken = renewToolToken;
|
|
524
|
+
exports.isRetryableStatus = isRetryableStatus;
|
|
525
|
+
exports.postToolEvent = postToolEvent;
|
|
526
|
+
exports.postToolEventsBatch = postToolEventsBatch;
|
|
527
|
+
exports.parseIngestResponse = parseIngestResponse;
|
|
528
|
+
var AscendaApiError = class extends Error {
|
|
529
|
+
status;
|
|
530
|
+
errorCode;
|
|
531
|
+
constructor(status, errorCode, body) {
|
|
532
|
+
super(body ?? `Ascenda API error ${status}`);
|
|
533
|
+
this.status = status;
|
|
534
|
+
this.errorCode = errorCode;
|
|
535
|
+
this.name = "AscendaApiError";
|
|
536
|
+
}
|
|
537
|
+
};
|
|
538
|
+
exports.AscendaApiError = AscendaApiError;
|
|
539
|
+
async function createPairingSession(apiBaseUrl, toolInstallationId, toolType, displayName) {
|
|
540
|
+
const response = await fetch(`${apiBaseUrl}/v1/tool-pairing-sessions`, {
|
|
541
|
+
method: "POST",
|
|
542
|
+
headers: { "Content-Type": "application/json" },
|
|
543
|
+
body: JSON.stringify({ toolInstallationId, toolType, displayName })
|
|
544
|
+
});
|
|
545
|
+
if (!response.ok)
|
|
546
|
+
throw new AscendaApiError(response.status, void 0, await response.text());
|
|
547
|
+
return await response.json();
|
|
548
|
+
}
|
|
549
|
+
async function getPairingStatus(apiBaseUrl, pairingSessionId) {
|
|
550
|
+
const response = await fetch(`${apiBaseUrl}/v1/tool-pairing-sessions/${encodeURIComponent(pairingSessionId)}/status`, {
|
|
551
|
+
method: "GET",
|
|
552
|
+
headers: { Accept: "application/json" }
|
|
553
|
+
});
|
|
554
|
+
if (!response.ok)
|
|
555
|
+
throw new AscendaApiError(response.status, void 0, await response.text());
|
|
556
|
+
return await response.json();
|
|
557
|
+
}
|
|
558
|
+
async function renewToolToken(apiBaseUrl, eventWriteToken, signal) {
|
|
559
|
+
const response = await fetch(`${apiBaseUrl}/v1/tool-events/renew-token`, {
|
|
560
|
+
method: "POST",
|
|
561
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${eventWriteToken}` },
|
|
562
|
+
signal
|
|
563
|
+
});
|
|
564
|
+
if (response.status === 401)
|
|
565
|
+
return null;
|
|
566
|
+
if (!response.ok)
|
|
567
|
+
throw new AscendaApiError(response.status, void 0, await response.text());
|
|
568
|
+
return await response.json();
|
|
569
|
+
}
|
|
570
|
+
function isRetryableStatus(status) {
|
|
571
|
+
return status === 408 || status === 429 || status !== void 0 && status >= 500 && status <= 599;
|
|
572
|
+
}
|
|
573
|
+
async function postToolEvent(apiBaseUrl, eventWriteToken, payload, signal) {
|
|
574
|
+
return sendIngest(`${apiBaseUrl}/v1/tool-events`, eventWriteToken, JSON.stringify(payload), signal);
|
|
575
|
+
}
|
|
576
|
+
async function postToolEventsBatch(apiBaseUrl, eventWriteToken, payloads, signal) {
|
|
577
|
+
return sendIngest(`${apiBaseUrl}/v1/tool-events/batch`, eventWriteToken, JSON.stringify({ events: payloads }), signal);
|
|
578
|
+
}
|
|
579
|
+
async function sendIngest(url, eventWriteToken, body, signal) {
|
|
580
|
+
try {
|
|
581
|
+
const response = await fetch(url, {
|
|
582
|
+
method: "POST",
|
|
583
|
+
headers: { "Content-Type": "application/json", Authorization: `Bearer ${eventWriteToken}` },
|
|
584
|
+
body,
|
|
585
|
+
signal
|
|
586
|
+
});
|
|
587
|
+
return parseIngestResponse(response);
|
|
588
|
+
} catch (error) {
|
|
589
|
+
return {
|
|
590
|
+
result: "transport_error",
|
|
591
|
+
detail: error instanceof Error ? `${error.name}: ${error.message}` : String(error)
|
|
592
|
+
};
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
async function parseIngestResponse(response) {
|
|
596
|
+
if (response.ok)
|
|
597
|
+
return { result: "accepted", httpStatus: response.status };
|
|
598
|
+
const body = await response.text();
|
|
599
|
+
let errorCode;
|
|
600
|
+
try {
|
|
601
|
+
errorCode = JSON.parse(body).error;
|
|
602
|
+
} catch {
|
|
603
|
+
errorCode = void 0;
|
|
604
|
+
}
|
|
605
|
+
const base = { httpStatus: response.status, errorCode, detail: body || void 0 };
|
|
606
|
+
if (response.status === 401)
|
|
607
|
+
return { ...base, result: "auth_failed" };
|
|
608
|
+
if (response.status === 403 && errorCode === "consent_missing_or_expired")
|
|
609
|
+
return { ...base, result: "consent_missing" };
|
|
610
|
+
if (response.status === 400 || response.status === 422)
|
|
611
|
+
return { ...base, result: "validation_failed" };
|
|
612
|
+
return { ...base, result: "transport_error" };
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
// ../packages/tool-kit/out/tokenStore.js
|
|
618
|
+
var require_tokenStore = __commonJS({
|
|
619
|
+
"../packages/tool-kit/out/tokenStore.js"(exports) {
|
|
620
|
+
"use strict";
|
|
621
|
+
var __createBinding = exports && exports.__createBinding || (Object.create ? function(o, m, k, k2) {
|
|
622
|
+
if (k2 === void 0) k2 = k;
|
|
623
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
624
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
625
|
+
desc = { enumerable: true, get: function() {
|
|
626
|
+
return m[k];
|
|
627
|
+
} };
|
|
628
|
+
}
|
|
629
|
+
Object.defineProperty(o, k2, desc);
|
|
630
|
+
} : function(o, m, k, k2) {
|
|
631
|
+
if (k2 === void 0) k2 = k;
|
|
632
|
+
o[k2] = m[k];
|
|
633
|
+
});
|
|
634
|
+
var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? function(o, v) {
|
|
635
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
636
|
+
} : function(o, v) {
|
|
637
|
+
o["default"] = v;
|
|
638
|
+
});
|
|
639
|
+
var __importStar = exports && exports.__importStar || /* @__PURE__ */ function() {
|
|
640
|
+
var ownKeys = function(o) {
|
|
641
|
+
ownKeys = Object.getOwnPropertyNames || function(o2) {
|
|
642
|
+
var ar = [];
|
|
643
|
+
for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
|
|
644
|
+
return ar;
|
|
645
|
+
};
|
|
646
|
+
return ownKeys(o);
|
|
647
|
+
};
|
|
648
|
+
return function(mod) {
|
|
649
|
+
if (mod && mod.__esModule) return mod;
|
|
650
|
+
var result = {};
|
|
651
|
+
if (mod != null) {
|
|
652
|
+
for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
653
|
+
}
|
|
654
|
+
__setModuleDefault(result, mod);
|
|
655
|
+
return result;
|
|
656
|
+
};
|
|
657
|
+
}();
|
|
658
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
659
|
+
exports.defaultTokenFilePath = defaultTokenFilePath2;
|
|
660
|
+
exports.persistEventWriteToken = persistEventWriteToken;
|
|
661
|
+
exports.readTokenFile = readTokenFile2;
|
|
662
|
+
exports.sanitizeFilePart = sanitizeFilePart;
|
|
663
|
+
var fs9 = __importStar(__require("fs"));
|
|
664
|
+
var os3 = __importStar(__require("os"));
|
|
665
|
+
var path9 = __importStar(__require("path"));
|
|
666
|
+
function defaultTokenFilePath2(toolInstallationId) {
|
|
667
|
+
return path9.join(os3.homedir(), ".ascenda", "tokens", sanitizeFilePart(toolInstallationId));
|
|
668
|
+
}
|
|
669
|
+
function persistEventWriteToken(tokenFilePath, token) {
|
|
670
|
+
const dir = path9.dirname(tokenFilePath);
|
|
671
|
+
fs9.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
672
|
+
fs9.writeFileSync(tokenFilePath, token, { encoding: "utf8", mode: 384 });
|
|
673
|
+
if (process.platform !== "win32") {
|
|
674
|
+
fs9.chmodSync(dir, 448);
|
|
675
|
+
fs9.chmodSync(tokenFilePath, 384);
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
function readTokenFile2(tokenFilePath) {
|
|
679
|
+
try {
|
|
680
|
+
if (!fs9.existsSync(tokenFilePath))
|
|
681
|
+
return void 0;
|
|
682
|
+
const value = fs9.readFileSync(tokenFilePath, "utf8").trim();
|
|
683
|
+
return value || void 0;
|
|
684
|
+
} catch {
|
|
685
|
+
return void 0;
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
function sanitizeFilePart(value) {
|
|
689
|
+
return value.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
});
|
|
693
|
+
|
|
694
|
+
// ../packages/tool-kit/out/stateStore.js
|
|
695
|
+
var require_stateStore = __commonJS({
|
|
696
|
+
"../packages/tool-kit/out/stateStore.js"(exports) {
|
|
697
|
+
"use strict";
|
|
698
|
+
var __createBinding = exports && exports.__createBinding || (Object.create ? function(o, m, k, k2) {
|
|
699
|
+
if (k2 === void 0) k2 = k;
|
|
700
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
701
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
702
|
+
desc = { enumerable: true, get: function() {
|
|
703
|
+
return m[k];
|
|
704
|
+
} };
|
|
705
|
+
}
|
|
706
|
+
Object.defineProperty(o, k2, desc);
|
|
707
|
+
} : function(o, m, k, k2) {
|
|
708
|
+
if (k2 === void 0) k2 = k;
|
|
709
|
+
o[k2] = m[k];
|
|
710
|
+
});
|
|
711
|
+
var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? function(o, v) {
|
|
712
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
713
|
+
} : function(o, v) {
|
|
714
|
+
o["default"] = v;
|
|
715
|
+
});
|
|
716
|
+
var __importStar = exports && exports.__importStar || /* @__PURE__ */ function() {
|
|
717
|
+
var ownKeys = function(o) {
|
|
718
|
+
ownKeys = Object.getOwnPropertyNames || function(o2) {
|
|
719
|
+
var ar = [];
|
|
720
|
+
for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
|
|
721
|
+
return ar;
|
|
722
|
+
};
|
|
723
|
+
return ownKeys(o);
|
|
724
|
+
};
|
|
725
|
+
return function(mod) {
|
|
726
|
+
if (mod && mod.__esModule) return mod;
|
|
727
|
+
var result = {};
|
|
728
|
+
if (mod != null) {
|
|
729
|
+
for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
730
|
+
}
|
|
731
|
+
__setModuleDefault(result, mod);
|
|
732
|
+
return result;
|
|
733
|
+
};
|
|
734
|
+
}();
|
|
735
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
736
|
+
exports.defaultStateFilePath = defaultStateFilePath;
|
|
737
|
+
exports.readCollectorState = readCollectorState;
|
|
738
|
+
exports.recordSendOutcome = recordSendOutcome;
|
|
739
|
+
exports.shouldAnnounceFailure = shouldAnnounceFailure;
|
|
740
|
+
exports.markFailureNotified = markFailureNotified;
|
|
741
|
+
var fs9 = __importStar(__require("fs"));
|
|
742
|
+
var os3 = __importStar(__require("os"));
|
|
743
|
+
var path9 = __importStar(__require("path"));
|
|
744
|
+
var tokenStore_1 = require_tokenStore();
|
|
745
|
+
function defaultStateFilePath(toolInstallationId) {
|
|
746
|
+
const dir = process.env.ASCENDA_STATE_DIR?.trim();
|
|
747
|
+
const base = dir ? dir : path9.join(os3.homedir(), ".ascenda", "state");
|
|
748
|
+
return path9.join(base, `${(0, tokenStore_1.sanitizeFilePart)(toolInstallationId)}.json`);
|
|
749
|
+
}
|
|
750
|
+
function readCollectorState(stateFilePath) {
|
|
751
|
+
try {
|
|
752
|
+
if (!fs9.existsSync(stateFilePath))
|
|
753
|
+
return void 0;
|
|
754
|
+
const raw = fs9.readFileSync(stateFilePath, "utf8").trim();
|
|
755
|
+
if (!raw)
|
|
756
|
+
return void 0;
|
|
757
|
+
const parsed = JSON.parse(raw);
|
|
758
|
+
if (!parsed || typeof parsed !== "object")
|
|
759
|
+
return void 0;
|
|
760
|
+
return parsed;
|
|
761
|
+
} catch {
|
|
762
|
+
return void 0;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
function recordSendOutcome(stateFilePath, toolInstallationId, outcome, detail = {}) {
|
|
766
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
767
|
+
const previous = readCollectorState(stateFilePath);
|
|
768
|
+
const accepted = outcome === "accepted";
|
|
769
|
+
const next = {
|
|
770
|
+
toolInstallationId,
|
|
771
|
+
lastAttemptAt: now,
|
|
772
|
+
lastSuccessAt: accepted ? now : previous?.lastSuccessAt,
|
|
773
|
+
lastOutcome: outcome,
|
|
774
|
+
consecutiveFailures: accepted ? 0 : (previous?.consecutiveFailures ?? 0) + 1,
|
|
775
|
+
...detail.httpStatus !== void 0 ? { httpStatus: detail.httpStatus } : {},
|
|
776
|
+
...detail.errorCode !== void 0 ? { errorCode: detail.errorCode } : {},
|
|
777
|
+
...detail.detail !== void 0 ? { detail: truncate(detail.detail) } : {},
|
|
778
|
+
// A success closes the episode; a failure either opens one or continues
|
|
779
|
+
// the one already open. Carrying `notifiedFailingSince` across a
|
|
780
|
+
// continuing episode is what keeps the notice to once per outage.
|
|
781
|
+
...accepted ? {} : { failingSince: previous?.failingSince ?? now },
|
|
782
|
+
...accepted ? {} : previous?.notifiedFailingSince !== void 0 ? { notifiedFailingSince: previous.notifiedFailingSince } : {}
|
|
783
|
+
};
|
|
784
|
+
writeStateFile(stateFilePath, next);
|
|
785
|
+
return next;
|
|
786
|
+
}
|
|
787
|
+
function shouldAnnounceFailure(state) {
|
|
788
|
+
if (!state || state.consecutiveFailures === 0 || !state.failingSince)
|
|
789
|
+
return false;
|
|
790
|
+
return state.notifiedFailingSince !== state.failingSince;
|
|
791
|
+
}
|
|
792
|
+
function markFailureNotified(stateFilePath, state) {
|
|
793
|
+
if (!state.failingSince)
|
|
794
|
+
return;
|
|
795
|
+
writeStateFile(stateFilePath, { ...state, notifiedFailingSince: state.failingSince });
|
|
796
|
+
}
|
|
797
|
+
function writeStateFile(stateFilePath, state) {
|
|
798
|
+
const temporaryPath = `${stateFilePath}.${process.pid}.tmp`;
|
|
799
|
+
try {
|
|
800
|
+
const dir = path9.dirname(stateFilePath);
|
|
801
|
+
fs9.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
802
|
+
fs9.writeFileSync(temporaryPath, `${JSON.stringify(state, null, 2)}
|
|
803
|
+
`, { encoding: "utf8", mode: 384 });
|
|
804
|
+
fs9.renameSync(temporaryPath, stateFilePath);
|
|
805
|
+
if (process.platform !== "win32")
|
|
806
|
+
fs9.chmodSync(stateFilePath, 384);
|
|
807
|
+
} catch {
|
|
808
|
+
try {
|
|
809
|
+
if (fs9.existsSync(temporaryPath))
|
|
810
|
+
fs9.unlinkSync(temporaryPath);
|
|
811
|
+
} catch {
|
|
812
|
+
}
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
function truncate(value) {
|
|
816
|
+
const collapsed = value.replace(/\s+/g, " ").trim();
|
|
817
|
+
return collapsed.length > 200 ? `${collapsed.slice(0, 197)}...` : collapsed;
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
});
|
|
821
|
+
|
|
822
|
+
// ../packages/tool-kit/out/eventSender.js
|
|
823
|
+
var require_eventSender = __commonJS({
|
|
824
|
+
"../packages/tool-kit/out/eventSender.js"(exports) {
|
|
825
|
+
"use strict";
|
|
826
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
827
|
+
exports.AscendaEventSender = exports.AscendaSemanticEventError = void 0;
|
|
828
|
+
exports.buildEventPayload = buildEventPayload;
|
|
829
|
+
var afterHours_1 = require_afterHours();
|
|
830
|
+
var tool_contract_1 = require_out();
|
|
831
|
+
var eventLog_1 = require_eventLog();
|
|
832
|
+
var http_1 = require_http();
|
|
833
|
+
var tokenStore_1 = require_tokenStore();
|
|
834
|
+
var stateStore_1 = require_stateStore();
|
|
835
|
+
var AscendaSemanticEventError = class extends Error {
|
|
836
|
+
constructor(message) {
|
|
837
|
+
super(message);
|
|
838
|
+
this.name = "AscendaSemanticEventError";
|
|
839
|
+
}
|
|
840
|
+
};
|
|
841
|
+
exports.AscendaSemanticEventError = AscendaSemanticEventError;
|
|
842
|
+
function buildEventPayload(identity, mapped) {
|
|
843
|
+
return {
|
|
844
|
+
toolInstallationId: identity.toolInstallationId,
|
|
845
|
+
source: identity.source,
|
|
846
|
+
occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
847
|
+
utcOffsetMinutes: (0, afterHours_1.utcOffsetMinutesAt)(/* @__PURE__ */ new Date()),
|
|
848
|
+
sessionId: identity.sessionId ?? void 0,
|
|
849
|
+
workspaceHash: identity.workspaceHash ?? void 0,
|
|
850
|
+
consentScope: tool_contract_1.ASCENDA_CONSENT_SCOPE,
|
|
851
|
+
provenance: tool_contract_1.ASCENDA_PROVENANCE,
|
|
852
|
+
privacyMode: "metadata_only",
|
|
853
|
+
...mapped,
|
|
854
|
+
metadata: mapped.metadata ?? {}
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
var DEFAULT_TIMEOUT_MS = 5e3;
|
|
858
|
+
var RETRY_DELAY_MS = 250;
|
|
859
|
+
var AscendaEventSender = class {
|
|
860
|
+
config;
|
|
861
|
+
eventWriteToken;
|
|
862
|
+
lastState;
|
|
863
|
+
constructor(config) {
|
|
864
|
+
this.config = config;
|
|
865
|
+
this.eventWriteToken = config.eventWriteToken;
|
|
866
|
+
}
|
|
867
|
+
async send(mapped) {
|
|
868
|
+
return this.post(buildEventPayload(this.config, mapped));
|
|
869
|
+
}
|
|
870
|
+
/**
|
|
871
|
+
* Sends one of the six agent-observed types (dark-flow-gap-analysis §2.1).
|
|
872
|
+
* Distinct from {@link send} rather than an option on it, because the
|
|
873
|
+
* differences are non-negotiable, not caller preference:
|
|
874
|
+
*
|
|
875
|
+
* - `consentScope`/`provenance` are always the semantic pair — a lease on
|
|
876
|
+
* `ide_telemetry` alone does not cover these.
|
|
877
|
+
* - `severity` is always `"low"`. The emitter has no baseline to judge
|
|
878
|
+
* against; an elevated reading can only come from the backend's own
|
|
879
|
+
* z-scored evaluation, never from this payload.
|
|
880
|
+
* - `metadata.skillVersion` is required by the type, not merely
|
|
881
|
+
* documented, and checked again here in case a caller building the
|
|
882
|
+
* object dynamically bypasses the type system.
|
|
883
|
+
*
|
|
884
|
+
* Rejects locally (never reaches the network) for an eventType outside
|
|
885
|
+
* {@link SEMANTIC_WORK_SIGNAL_EVENT_TYPES} or a missing/blank
|
|
886
|
+
* `skillVersion` — a malformed semantic event is a bug in the caller, not
|
|
887
|
+
* something the backend should have to catch.
|
|
888
|
+
*/
|
|
889
|
+
async sendSemanticSignal(mapped) {
|
|
890
|
+
if (!tool_contract_1.SEMANTIC_WORK_SIGNAL_EVENT_TYPES.includes(mapped.eventType)) {
|
|
891
|
+
throw new AscendaSemanticEventError(`"${mapped.eventType}" is not a semantic work-signal type. Use send() for a deterministic host event.`);
|
|
892
|
+
}
|
|
893
|
+
if (!mapped.metadata.skillVersion || !mapped.metadata.skillVersion.trim()) {
|
|
894
|
+
throw new AscendaSemanticEventError(`metadata.skillVersion is required for semantic event "${mapped.eventType}".`);
|
|
895
|
+
}
|
|
896
|
+
const payload = {
|
|
897
|
+
toolInstallationId: this.config.toolInstallationId,
|
|
898
|
+
source: this.config.source,
|
|
899
|
+
eventType: mapped.eventType,
|
|
900
|
+
occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
901
|
+
utcOffsetMinutes: (0, afterHours_1.utcOffsetMinutesAt)(/* @__PURE__ */ new Date()),
|
|
902
|
+
severity: "low",
|
|
903
|
+
sessionId: this.config.sessionId ?? void 0,
|
|
904
|
+
workspaceHash: this.config.workspaceHash ?? void 0,
|
|
905
|
+
consentScope: tool_contract_1.ASCENDA_SEMANTIC_CONSENT_SCOPE,
|
|
906
|
+
provenance: tool_contract_1.ASCENDA_SEMANTIC_PROVENANCE,
|
|
907
|
+
privacyMode: "metadata_only",
|
|
908
|
+
metadata: mapped.metadata
|
|
909
|
+
};
|
|
910
|
+
return this.post(payload);
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* Sends a collaboration event under `workflow_telemetry`.
|
|
914
|
+
*
|
|
915
|
+
* A separate method rather than an option on {@link send}, for the same
|
|
916
|
+
* reason {@link sendSemanticSignal} is: the consent scope is a property of
|
|
917
|
+
* what the event *is*, and an options bag would let the wrong one be passed
|
|
918
|
+
* by accident. Rejects locally for anything outside
|
|
919
|
+
* {@link COLLABORATION_EVENT_TYPES}.
|
|
920
|
+
*/
|
|
921
|
+
async sendCollaborationSignal(mapped) {
|
|
922
|
+
if (!tool_contract_1.COLLABORATION_EVENT_TYPES.includes(mapped.eventType)) {
|
|
923
|
+
throw new AscendaSemanticEventError(`"${mapped.eventType}" is not a collaboration event type. Use send() for a deterministic host event.`);
|
|
924
|
+
}
|
|
925
|
+
const payload = {
|
|
926
|
+
toolInstallationId: this.config.toolInstallationId,
|
|
927
|
+
source: this.config.source,
|
|
928
|
+
eventType: mapped.eventType,
|
|
929
|
+
occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
930
|
+
utcOffsetMinutes: (0, afterHours_1.utcOffsetMinutesAt)(/* @__PURE__ */ new Date()),
|
|
931
|
+
severity: "low",
|
|
932
|
+
sessionId: this.config.sessionId ?? void 0,
|
|
933
|
+
workspaceHash: this.config.workspaceHash ?? void 0,
|
|
934
|
+
consentScope: tool_contract_1.ASCENDA_COLLABORATION_CONSENT_SCOPE,
|
|
935
|
+
provenance: tool_contract_1.ASCENDA_PROVENANCE,
|
|
936
|
+
privacyMode: "metadata_only",
|
|
937
|
+
metadata: mapped.metadata ?? {}
|
|
938
|
+
};
|
|
939
|
+
return this.post(payload);
|
|
940
|
+
}
|
|
941
|
+
/**
|
|
942
|
+
* The single choke point every event passes through, and therefore the only
|
|
943
|
+
* honest place to journal one. Recording here rather than in each adapter is
|
|
944
|
+
* deliberate: Claude Code, Codex, the GitHub collector and the MCP server all
|
|
945
|
+
* send through this method, and the defect being fixed showed up in three
|
|
946
|
+
* separate components because each was left to notice its own failures.
|
|
947
|
+
*/
|
|
948
|
+
async post(payload) {
|
|
949
|
+
const outcome = await this.attempt(payload);
|
|
950
|
+
this.lastState = (0, stateStore_1.recordSendOutcome)(this.stateFilePath(), this.config.toolInstallationId, outcome.result, {
|
|
951
|
+
httpStatus: outcome.httpStatus,
|
|
952
|
+
errorCode: outcome.errorCode,
|
|
953
|
+
detail: outcome.detail
|
|
954
|
+
});
|
|
955
|
+
this.log(payload, outcome.result);
|
|
956
|
+
return outcome.result;
|
|
957
|
+
}
|
|
958
|
+
/**
|
|
959
|
+
* Two recoveries, each tried once. A rejected token gets a renewal and one
|
|
960
|
+
* replay — rotation is expected and should heal unattended. A transport
|
|
961
|
+
* error gets one retry, because the common cases (a restarting instance, a
|
|
962
|
+
* proxy blip, a 429) clear in well under a second and the alternative is
|
|
963
|
+
* losing the event outright.
|
|
964
|
+
*/
|
|
965
|
+
async attempt(payload) {
|
|
966
|
+
const outcome = await (0, http_1.postToolEvent)(this.config.apiBaseUrl, this.eventWriteToken, payload, this.signal());
|
|
967
|
+
if (outcome.result === "auth_failed") {
|
|
968
|
+
if (!await this.renewEventToken())
|
|
969
|
+
return outcome;
|
|
970
|
+
return (0, http_1.postToolEvent)(this.config.apiBaseUrl, this.eventWriteToken, payload, this.signal());
|
|
971
|
+
}
|
|
972
|
+
if (outcome.result === "transport_error" && (outcome.httpStatus === void 0 || (0, http_1.isRetryableStatus)(outcome.httpStatus))) {
|
|
973
|
+
await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS));
|
|
974
|
+
return (0, http_1.postToolEvent)(this.config.apiBaseUrl, this.eventWriteToken, payload, this.signal());
|
|
975
|
+
}
|
|
976
|
+
return outcome;
|
|
977
|
+
}
|
|
978
|
+
/**
|
|
979
|
+
* The state written by the most recent send, so a caller can decide whether
|
|
980
|
+
* to surface a one-time notice without re-reading the journal it just wrote.
|
|
981
|
+
*/
|
|
982
|
+
get state() {
|
|
983
|
+
return this.lastState;
|
|
984
|
+
}
|
|
985
|
+
stateFilePath() {
|
|
986
|
+
return this.config.stateFilePath ?? (0, stateStore_1.defaultStateFilePath)(this.config.toolInstallationId);
|
|
987
|
+
}
|
|
988
|
+
/** Never throws: a renewal that errors is a failed renewal, not a failed turn. */
|
|
989
|
+
/**
|
|
990
|
+
* Every send path funnels through {@link post}, so semantic and
|
|
991
|
+
* collaboration signals are logged on the same terms as host events — the
|
|
992
|
+
* log would be misleading as an audit of what left the machine otherwise.
|
|
993
|
+
*
|
|
994
|
+
* An unreachable backend used to be logged as `other` from a catch block.
|
|
995
|
+
* It is now `transport_error` through the ordinary path, because the
|
|
996
|
+
* transport returns that outcome instead of throwing.
|
|
997
|
+
*/
|
|
998
|
+
log(payload, delivery) {
|
|
999
|
+
const logFile = this.config.eventLogFile === void 0 ? (0, eventLog_1.resolveEventLogPath)() : this.config.eventLogFile;
|
|
1000
|
+
if (!logFile)
|
|
1001
|
+
return;
|
|
1002
|
+
(0, eventLog_1.appendEventLog)(logFile, { loggedAt: (/* @__PURE__ */ new Date()).toISOString(), delivery, payload });
|
|
1003
|
+
}
|
|
1004
|
+
async renewEventToken() {
|
|
1005
|
+
try {
|
|
1006
|
+
const renewed = await (0, http_1.renewToolToken)(this.config.apiBaseUrl, this.eventWriteToken, this.signal());
|
|
1007
|
+
if (!renewed)
|
|
1008
|
+
return false;
|
|
1009
|
+
this.eventWriteToken = renewed.eventWriteToken;
|
|
1010
|
+
(0, tokenStore_1.persistEventWriteToken)(this.config.tokenFilePath, renewed.eventWriteToken);
|
|
1011
|
+
return true;
|
|
1012
|
+
} catch {
|
|
1013
|
+
return false;
|
|
1014
|
+
}
|
|
1015
|
+
}
|
|
1016
|
+
signal() {
|
|
1017
|
+
return AbortSignal.timeout(this.config.timeoutMs ?? DEFAULT_TIMEOUT_MS);
|
|
1018
|
+
}
|
|
1019
|
+
};
|
|
1020
|
+
exports.AscendaEventSender = AscendaEventSender;
|
|
1021
|
+
}
|
|
1022
|
+
});
|
|
1023
|
+
|
|
1024
|
+
// ../packages/tool-kit/out/hookAdapter.js
|
|
1025
|
+
var require_hookAdapter = __commonJS({
|
|
1026
|
+
"../packages/tool-kit/out/hookAdapter.js"(exports) {
|
|
1027
|
+
"use strict";
|
|
1028
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1029
|
+
exports.DEFAULT_API_BASE_URL = void 0;
|
|
1030
|
+
exports.loadCliAgentConfig = loadCliAgentConfig;
|
|
1031
|
+
exports.deliverHookEvents = deliverHookEvents;
|
|
1032
|
+
var eventLog_1 = require_eventLog();
|
|
1033
|
+
var eventSender_1 = require_eventSender();
|
|
1034
|
+
var tokenStore_1 = require_tokenStore();
|
|
1035
|
+
exports.DEFAULT_API_BASE_URL = "https://api.ascenda.one";
|
|
1036
|
+
function loadCliAgentConfig(toolType, sessionIdFromHook) {
|
|
1037
|
+
const apiBaseUrl = (process.env.ASCENDA_API_BASE_URL ?? exports.DEFAULT_API_BASE_URL).replace(/\/$/, "");
|
|
1038
|
+
const toolInstallationIdRaw = process.env.ASCENDA_TOOL_INSTALLATION_ID;
|
|
1039
|
+
if (!toolInstallationIdRaw)
|
|
1040
|
+
throw new Error("Missing ASCENDA_TOOL_INSTALLATION_ID");
|
|
1041
|
+
const toolInstallationId = toolInstallationIdRaw.trim().includes(":") ? toolInstallationIdRaw.trim() : `${toolType}:${toolInstallationIdRaw.trim()}`;
|
|
1042
|
+
const tokenFilePath = process.env.ASCENDA_EVENT_WRITE_TOKEN_FILE ?? (0, tokenStore_1.defaultTokenFilePath)(toolInstallationId);
|
|
1043
|
+
const fileToken = (0, tokenStore_1.readTokenFile)(tokenFilePath);
|
|
1044
|
+
const eventWriteToken = fileToken ?? process.env.ASCENDA_EVENT_WRITE_TOKEN;
|
|
1045
|
+
if (!eventWriteToken)
|
|
1046
|
+
throw new Error("Missing ASCENDA_EVENT_WRITE_TOKEN (or token file)");
|
|
1047
|
+
if (!fileToken)
|
|
1048
|
+
(0, tokenStore_1.persistEventWriteToken)(tokenFilePath, eventWriteToken);
|
|
1049
|
+
return {
|
|
1050
|
+
apiBaseUrl,
|
|
1051
|
+
toolInstallationId,
|
|
1052
|
+
eventWriteToken,
|
|
1053
|
+
tokenFilePath,
|
|
1054
|
+
sessionId: process.env.ASCENDA_SESSION_ID ?? sessionIdFromHook ?? null,
|
|
1055
|
+
workspaceHash: process.env.ASCENDA_WORKSPACE_HASH ?? null,
|
|
1056
|
+
// Agents await command hooks; fail fast rather than stall the user's turn.
|
|
1057
|
+
timeoutMs: parsePositiveInt(process.env.ASCENDA_HTTP_TIMEOUT_MS) ?? 3e3
|
|
1058
|
+
};
|
|
1059
|
+
}
|
|
1060
|
+
async function deliverHookEvents(events, options) {
|
|
1061
|
+
if (events.length === 0)
|
|
1062
|
+
return;
|
|
1063
|
+
const notice = options.onNotice ?? ((message) => console.error(message));
|
|
1064
|
+
let config;
|
|
1065
|
+
try {
|
|
1066
|
+
config = loadCliAgentConfig(options.toolType, options.sessionId);
|
|
1067
|
+
} catch (error) {
|
|
1068
|
+
const logFile = (0, eventLog_1.resolveEventLogPath)();
|
|
1069
|
+
if (!logFile)
|
|
1070
|
+
throw error;
|
|
1071
|
+
for (const event of events) {
|
|
1072
|
+
(0, eventLog_1.appendEventLog)(logFile, {
|
|
1073
|
+
loggedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1074
|
+
delivery: "not_sent",
|
|
1075
|
+
payload: (0, eventSender_1.buildEventPayload)({
|
|
1076
|
+
toolInstallationId: `${options.toolType}:unpaired`,
|
|
1077
|
+
source: options.source,
|
|
1078
|
+
sessionId: options.sessionId ?? null,
|
|
1079
|
+
workspaceHash: process.env.ASCENDA_WORKSPACE_HASH ?? null
|
|
1080
|
+
}, event)
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
1083
|
+
return;
|
|
1084
|
+
}
|
|
1085
|
+
const sender = new eventSender_1.AscendaEventSender({
|
|
1086
|
+
apiBaseUrl: config.apiBaseUrl,
|
|
1087
|
+
toolInstallationId: config.toolInstallationId,
|
|
1088
|
+
source: options.source,
|
|
1089
|
+
eventWriteToken: config.eventWriteToken,
|
|
1090
|
+
tokenFilePath: config.tokenFilePath,
|
|
1091
|
+
sessionId: config.sessionId,
|
|
1092
|
+
workspaceHash: config.workspaceHash,
|
|
1093
|
+
timeoutMs: config.timeoutMs
|
|
1094
|
+
});
|
|
1095
|
+
for (const event of events) {
|
|
1096
|
+
const result = await sender.send(event);
|
|
1097
|
+
if (result === "accepted")
|
|
1098
|
+
continue;
|
|
1099
|
+
if (result === "consent_missing") {
|
|
1100
|
+
notice("Ascenda telemetry paused: renew IDE telemetry consent in the Ascenda app.");
|
|
1101
|
+
} else if (result === "auth_failed") {
|
|
1102
|
+
notice("Ascenda telemetry paused: connection revoked or expired. Re-pair via an Ascenda IDE extension or pairing-sim.");
|
|
1103
|
+
} else if (result === "transport_error") {
|
|
1104
|
+
notice("Ascenda telemetry paused: the ingest endpoint could not be reached. Your work is unaffected.");
|
|
1105
|
+
} else {
|
|
1106
|
+
notice(`Ascenda telemetry rejected: ${result}`);
|
|
1107
|
+
}
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
function parsePositiveInt(value) {
|
|
1112
|
+
const n = Number(value);
|
|
1113
|
+
return Number.isInteger(n) && n > 0 ? n : void 0;
|
|
1114
|
+
}
|
|
1115
|
+
}
|
|
1116
|
+
});
|
|
1117
|
+
|
|
1118
|
+
// ../packages/tool-kit/out/turnState.js
|
|
1119
|
+
var require_turnState = __commonJS({
|
|
1120
|
+
"../packages/tool-kit/out/turnState.js"(exports) {
|
|
1121
|
+
"use strict";
|
|
1122
|
+
var __createBinding = exports && exports.__createBinding || (Object.create ? function(o, m, k, k2) {
|
|
1123
|
+
if (k2 === void 0) k2 = k;
|
|
1124
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
1125
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
1126
|
+
desc = { enumerable: true, get: function() {
|
|
1127
|
+
return m[k];
|
|
1128
|
+
} };
|
|
1129
|
+
}
|
|
1130
|
+
Object.defineProperty(o, k2, desc);
|
|
1131
|
+
} : function(o, m, k, k2) {
|
|
1132
|
+
if (k2 === void 0) k2 = k;
|
|
1133
|
+
o[k2] = m[k];
|
|
1134
|
+
});
|
|
1135
|
+
var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? function(o, v) {
|
|
1136
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
1137
|
+
} : function(o, v) {
|
|
1138
|
+
o["default"] = v;
|
|
1139
|
+
});
|
|
1140
|
+
var __importStar = exports && exports.__importStar || /* @__PURE__ */ function() {
|
|
1141
|
+
var ownKeys = function(o) {
|
|
1142
|
+
ownKeys = Object.getOwnPropertyNames || function(o2) {
|
|
1143
|
+
var ar = [];
|
|
1144
|
+
for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
|
|
1145
|
+
return ar;
|
|
1146
|
+
};
|
|
1147
|
+
return ownKeys(o);
|
|
1148
|
+
};
|
|
1149
|
+
return function(mod) {
|
|
1150
|
+
if (mod && mod.__esModule) return mod;
|
|
1151
|
+
var result = {};
|
|
1152
|
+
if (mod != null) {
|
|
1153
|
+
for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
1154
|
+
}
|
|
1155
|
+
__setModuleDefault(result, mod);
|
|
1156
|
+
return result;
|
|
1157
|
+
};
|
|
1158
|
+
}();
|
|
1159
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1160
|
+
exports.recordTurnStart = recordTurnStart;
|
|
1161
|
+
exports.consumeTurnDurationMs = consumeTurnDurationMs;
|
|
1162
|
+
var fs9 = __importStar(__require("fs"));
|
|
1163
|
+
var os3 = __importStar(__require("os"));
|
|
1164
|
+
var path9 = __importStar(__require("path"));
|
|
1165
|
+
var stateDir = () => process.env.ASCENDA_STATE_DIR ?? path9.join(os3.homedir(), ".ascenda", "state");
|
|
1166
|
+
function turnFile(agent, sessionId) {
|
|
1167
|
+
return path9.join(stateDir(), `${sanitize(agent)}-turn-${sanitize(sessionId)}`);
|
|
1168
|
+
}
|
|
1169
|
+
function sanitize(value) {
|
|
1170
|
+
return value.replace(/[^a-zA-Z0-9._-]/g, "_");
|
|
1171
|
+
}
|
|
1172
|
+
function recordTurnStart(agent, sessionId, now = Date.now()) {
|
|
1173
|
+
if (!sessionId)
|
|
1174
|
+
return;
|
|
1175
|
+
try {
|
|
1176
|
+
fs9.mkdirSync(stateDir(), { recursive: true });
|
|
1177
|
+
fs9.writeFileSync(turnFile(agent, sessionId), String(now), { encoding: "utf8", mode: 384 });
|
|
1178
|
+
} catch {
|
|
1179
|
+
}
|
|
1180
|
+
}
|
|
1181
|
+
function consumeTurnDurationMs(agent, sessionId, now = Date.now()) {
|
|
1182
|
+
if (!sessionId)
|
|
1183
|
+
return void 0;
|
|
1184
|
+
const file = turnFile(agent, sessionId);
|
|
1185
|
+
try {
|
|
1186
|
+
const started = Number(fs9.readFileSync(file, "utf8").trim());
|
|
1187
|
+
fs9.rmSync(file, { force: true });
|
|
1188
|
+
if (!Number.isFinite(started) || started <= 0 || started > now)
|
|
1189
|
+
return void 0;
|
|
1190
|
+
return now - started;
|
|
1191
|
+
} catch {
|
|
1192
|
+
return void 0;
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
}
|
|
1196
|
+
});
|
|
1197
|
+
|
|
1198
|
+
// ../packages/tool-kit/out/salt.js
|
|
1199
|
+
var require_salt = __commonJS({
|
|
1200
|
+
"../packages/tool-kit/out/salt.js"(exports) {
|
|
1201
|
+
"use strict";
|
|
1202
|
+
var __createBinding = exports && exports.__createBinding || (Object.create ? function(o, m, k, k2) {
|
|
1203
|
+
if (k2 === void 0) k2 = k;
|
|
1204
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
1205
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
1206
|
+
desc = { enumerable: true, get: function() {
|
|
1207
|
+
return m[k];
|
|
1208
|
+
} };
|
|
1209
|
+
}
|
|
1210
|
+
Object.defineProperty(o, k2, desc);
|
|
1211
|
+
} : function(o, m, k, k2) {
|
|
1212
|
+
if (k2 === void 0) k2 = k;
|
|
1213
|
+
o[k2] = m[k];
|
|
1214
|
+
});
|
|
1215
|
+
var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? function(o, v) {
|
|
1216
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
1217
|
+
} : function(o, v) {
|
|
1218
|
+
o["default"] = v;
|
|
1219
|
+
});
|
|
1220
|
+
var __importStar = exports && exports.__importStar || /* @__PURE__ */ function() {
|
|
1221
|
+
var ownKeys = function(o) {
|
|
1222
|
+
ownKeys = Object.getOwnPropertyNames || function(o2) {
|
|
1223
|
+
var ar = [];
|
|
1224
|
+
for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
|
|
1225
|
+
return ar;
|
|
1226
|
+
};
|
|
1227
|
+
return ownKeys(o);
|
|
1228
|
+
};
|
|
1229
|
+
return function(mod) {
|
|
1230
|
+
if (mod && mod.__esModule) return mod;
|
|
1231
|
+
var result = {};
|
|
1232
|
+
if (mod != null) {
|
|
1233
|
+
for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
1234
|
+
}
|
|
1235
|
+
__setModuleDefault(result, mod);
|
|
1236
|
+
return result;
|
|
1237
|
+
};
|
|
1238
|
+
}();
|
|
1239
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1240
|
+
exports.machineSaltFilePath = machineSaltFilePath;
|
|
1241
|
+
exports.readOrCreateMachineSalt = readOrCreateMachineSalt;
|
|
1242
|
+
exports.hashWithMachineSalt = hashWithMachineSalt2;
|
|
1243
|
+
var crypto = __importStar(__require("crypto"));
|
|
1244
|
+
var fs9 = __importStar(__require("fs"));
|
|
1245
|
+
var os3 = __importStar(__require("os"));
|
|
1246
|
+
var path9 = __importStar(__require("path"));
|
|
1247
|
+
function machineSaltFilePath() {
|
|
1248
|
+
return path9.join(os3.homedir(), ".ascenda", "salt");
|
|
1249
|
+
}
|
|
1250
|
+
var cache = /* @__PURE__ */ new Map();
|
|
1251
|
+
function readOrCreateMachineSalt(saltFilePath = machineSaltFilePath()) {
|
|
1252
|
+
const hit = cache.get(saltFilePath);
|
|
1253
|
+
if (hit)
|
|
1254
|
+
return hit;
|
|
1255
|
+
const dir = path9.dirname(saltFilePath);
|
|
1256
|
+
fs9.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
1257
|
+
let salt;
|
|
1258
|
+
try {
|
|
1259
|
+
salt = crypto.randomBytes(32).toString("hex");
|
|
1260
|
+
fs9.writeFileSync(saltFilePath, salt, { encoding: "utf8", mode: 384, flag: "wx" });
|
|
1261
|
+
} catch (error) {
|
|
1262
|
+
if (error.code !== "EEXIST")
|
|
1263
|
+
throw error;
|
|
1264
|
+
salt = fs9.readFileSync(saltFilePath, "utf8").trim();
|
|
1265
|
+
}
|
|
1266
|
+
if (process.platform !== "win32") {
|
|
1267
|
+
fs9.chmodSync(dir, 448);
|
|
1268
|
+
fs9.chmodSync(saltFilePath, 384);
|
|
1269
|
+
}
|
|
1270
|
+
cache.set(saltFilePath, salt);
|
|
1271
|
+
return salt;
|
|
1272
|
+
}
|
|
1273
|
+
function hashWithMachineSalt2(value, saltFilePath) {
|
|
1274
|
+
if (!value)
|
|
1275
|
+
return null;
|
|
1276
|
+
return crypto.createHash("sha256").update(readOrCreateMachineSalt(saltFilePath)).update("\0").update(value).digest("hex").slice(0, 16);
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
});
|
|
1280
|
+
|
|
1281
|
+
// ../packages/tool-kit/out/liveBus.js
|
|
1282
|
+
var require_liveBus = __commonJS({
|
|
1283
|
+
"../packages/tool-kit/out/liveBus.js"(exports) {
|
|
1284
|
+
"use strict";
|
|
1285
|
+
var __createBinding = exports && exports.__createBinding || (Object.create ? function(o, m, k, k2) {
|
|
1286
|
+
if (k2 === void 0) k2 = k;
|
|
1287
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
1288
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
1289
|
+
desc = { enumerable: true, get: function() {
|
|
1290
|
+
return m[k];
|
|
1291
|
+
} };
|
|
1292
|
+
}
|
|
1293
|
+
Object.defineProperty(o, k2, desc);
|
|
1294
|
+
} : function(o, m, k, k2) {
|
|
1295
|
+
if (k2 === void 0) k2 = k;
|
|
1296
|
+
o[k2] = m[k];
|
|
1297
|
+
});
|
|
1298
|
+
var __setModuleDefault = exports && exports.__setModuleDefault || (Object.create ? function(o, v) {
|
|
1299
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
1300
|
+
} : function(o, v) {
|
|
1301
|
+
o["default"] = v;
|
|
1302
|
+
});
|
|
1303
|
+
var __importStar = exports && exports.__importStar || /* @__PURE__ */ function() {
|
|
1304
|
+
var ownKeys = function(o) {
|
|
1305
|
+
ownKeys = Object.getOwnPropertyNames || function(o2) {
|
|
1306
|
+
var ar = [];
|
|
1307
|
+
for (var k in o2) if (Object.prototype.hasOwnProperty.call(o2, k)) ar[ar.length] = k;
|
|
1308
|
+
return ar;
|
|
1309
|
+
};
|
|
1310
|
+
return ownKeys(o);
|
|
1311
|
+
};
|
|
1312
|
+
return function(mod) {
|
|
1313
|
+
if (mod && mod.__esModule) return mod;
|
|
1314
|
+
var result = {};
|
|
1315
|
+
if (mod != null) {
|
|
1316
|
+
for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
1317
|
+
}
|
|
1318
|
+
__setModuleDefault(result, mod);
|
|
1319
|
+
return result;
|
|
1320
|
+
};
|
|
1321
|
+
}();
|
|
1322
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1323
|
+
exports.liveBusSocketCandidates = liveBusSocketCandidates;
|
|
1324
|
+
exports.liveBusSocketPath = liveBusSocketPath;
|
|
1325
|
+
exports.bucketPromptSize = bucketPromptSize;
|
|
1326
|
+
exports.emitLiveSignal = emitLiveSignal;
|
|
1327
|
+
var fs9 = __importStar(__require("fs"));
|
|
1328
|
+
var net = __importStar(__require("net"));
|
|
1329
|
+
var os3 = __importStar(__require("os"));
|
|
1330
|
+
var path9 = __importStar(__require("path"));
|
|
1331
|
+
var WRITE_TIMEOUT_MS = 50;
|
|
1332
|
+
var APP_BUNDLE_ID2 = "one.ascenda.ascendaMissionControl";
|
|
1333
|
+
function liveBusSocketCandidates() {
|
|
1334
|
+
const override = process.env.ASCENDA_LIVE_BUS_SOCKET;
|
|
1335
|
+
if (override)
|
|
1336
|
+
return [override];
|
|
1337
|
+
const home = os3.homedir();
|
|
1338
|
+
return [
|
|
1339
|
+
path9.join(home, ".ascenda", "live.sock"),
|
|
1340
|
+
path9.join(home, "Library", "Containers", APP_BUNDLE_ID2, "Data", ".ascenda", "live.sock")
|
|
1341
|
+
];
|
|
1342
|
+
}
|
|
1343
|
+
function liveBusSocketPath() {
|
|
1344
|
+
const candidates = liveBusSocketCandidates();
|
|
1345
|
+
for (const candidate of candidates) {
|
|
1346
|
+
try {
|
|
1347
|
+
if (fs9.statSync(candidate).isSocket())
|
|
1348
|
+
return candidate;
|
|
1349
|
+
} catch {
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
return candidates[0];
|
|
1353
|
+
}
|
|
1354
|
+
function bucketPromptSize(text) {
|
|
1355
|
+
const length = typeof text === "string" ? text.length : 0;
|
|
1356
|
+
if (length <= 280)
|
|
1357
|
+
return "s";
|
|
1358
|
+
if (length <= 2e3)
|
|
1359
|
+
return "m";
|
|
1360
|
+
if (length <= 8e3)
|
|
1361
|
+
return "l";
|
|
1362
|
+
return "xl";
|
|
1363
|
+
}
|
|
1364
|
+
function emitLiveSignal(signal) {
|
|
1365
|
+
return new Promise((resolve) => {
|
|
1366
|
+
let settled = false;
|
|
1367
|
+
const done = () => {
|
|
1368
|
+
if (settled)
|
|
1369
|
+
return;
|
|
1370
|
+
settled = true;
|
|
1371
|
+
try {
|
|
1372
|
+
socket.destroy();
|
|
1373
|
+
} catch {
|
|
1374
|
+
}
|
|
1375
|
+
resolve();
|
|
1376
|
+
};
|
|
1377
|
+
let socket;
|
|
1378
|
+
try {
|
|
1379
|
+
socket = net.createConnection(liveBusSocketPath());
|
|
1380
|
+
} catch {
|
|
1381
|
+
resolve();
|
|
1382
|
+
return;
|
|
1383
|
+
}
|
|
1384
|
+
const timer = setTimeout(done, WRITE_TIMEOUT_MS);
|
|
1385
|
+
if (typeof timer.unref === "function")
|
|
1386
|
+
timer.unref();
|
|
1387
|
+
if (typeof socket.unref === "function")
|
|
1388
|
+
socket.unref();
|
|
1389
|
+
socket.on("error", done);
|
|
1390
|
+
socket.on("connect", () => {
|
|
1391
|
+
try {
|
|
1392
|
+
socket.write(`${JSON.stringify(signal)}
|
|
1393
|
+
`, () => {
|
|
1394
|
+
clearTimeout(timer);
|
|
1395
|
+
done();
|
|
1396
|
+
});
|
|
1397
|
+
} catch {
|
|
1398
|
+
clearTimeout(timer);
|
|
1399
|
+
done();
|
|
1400
|
+
}
|
|
1401
|
+
});
|
|
1402
|
+
});
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
});
|
|
1406
|
+
|
|
1407
|
+
// ../packages/tool-kit/out/index.js
|
|
1408
|
+
var require_out2 = __commonJS({
|
|
1409
|
+
"../packages/tool-kit/out/index.js"(exports) {
|
|
1410
|
+
"use strict";
|
|
1411
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
1412
|
+
exports.renewToolToken = exports.getPairingStatus = exports.createPairingSession = exports.AscendaApiError = exports.liveBusSocketCandidates = exports.liveBusSocketPath = exports.bucketPromptSize = exports.emitLiveSignal = exports.hashWithMachineSalt = exports.readOrCreateMachineSalt = exports.machineSaltFilePath = exports.markFailureNotified = exports.shouldAnnounceFailure = exports.recordSendOutcome = exports.readCollectorState = exports.defaultStateFilePath = exports.readTokenFile = exports.persistEventWriteToken = exports.defaultTokenFilePath = exports.recordTurnStart = exports.consumeTurnDurationMs = exports.loadCliAgentConfig = exports.deliverHookEvents = exports.DEFAULT_API_BASE_URL = exports.resolveEventLogPath = exports.expandUserPath = exports.appendEventLog = exports.EVENT_LOG_ENV_VAR = exports.buildEventPayload = exports.AscendaSemanticEventError = exports.AscendaEventSender = exports.looksLikeCorrection = exports.outcomeForHook = exports.inferOutcome = exports.getNestedNumber = exports.getNestedString = exports.getNested = exports.getNumber = exports.getString = exports.localHourAt = exports.utcOffsetMinutesAt = exports.isAfterHours = exports.bucketDurationMs = exports.bucketLinesChanged = exports.invitesDebrief = exports.classifyWorkMilestone = exports.isReworkGitAction = exports.classifyGitAction = exports.isVerificationCommand = exports.classifyCommand = void 0;
|
|
1413
|
+
exports.isRetryableStatus = exports.parseIngestResponse = exports.postToolEventsBatch = exports.postToolEvent = void 0;
|
|
1414
|
+
var commandClassifier_1 = require_commandClassifier();
|
|
1415
|
+
Object.defineProperty(exports, "classifyCommand", { enumerable: true, get: function() {
|
|
1416
|
+
return commandClassifier_1.classifyCommand;
|
|
1417
|
+
} });
|
|
1418
|
+
Object.defineProperty(exports, "isVerificationCommand", { enumerable: true, get: function() {
|
|
1419
|
+
return commandClassifier_1.isVerificationCommand;
|
|
1420
|
+
} });
|
|
1421
|
+
var gitActionClassifier_1 = require_gitActionClassifier();
|
|
1422
|
+
Object.defineProperty(exports, "classifyGitAction", { enumerable: true, get: function() {
|
|
1423
|
+
return gitActionClassifier_1.classifyGitAction;
|
|
1424
|
+
} });
|
|
1425
|
+
Object.defineProperty(exports, "isReworkGitAction", { enumerable: true, get: function() {
|
|
1426
|
+
return gitActionClassifier_1.isReworkGitAction;
|
|
1427
|
+
} });
|
|
1428
|
+
var workMilestoneClassifier_1 = require_workMilestoneClassifier();
|
|
1429
|
+
Object.defineProperty(exports, "classifyWorkMilestone", { enumerable: true, get: function() {
|
|
1430
|
+
return workMilestoneClassifier_1.classifyWorkMilestone;
|
|
1431
|
+
} });
|
|
1432
|
+
Object.defineProperty(exports, "invitesDebrief", { enumerable: true, get: function() {
|
|
1433
|
+
return workMilestoneClassifier_1.invitesDebrief;
|
|
1434
|
+
} });
|
|
1435
|
+
var buckets_1 = require_buckets();
|
|
1436
|
+
Object.defineProperty(exports, "bucketLinesChanged", { enumerable: true, get: function() {
|
|
1437
|
+
return buckets_1.bucketLinesChanged;
|
|
1438
|
+
} });
|
|
1439
|
+
Object.defineProperty(exports, "bucketDurationMs", { enumerable: true, get: function() {
|
|
1440
|
+
return buckets_1.bucketDurationMs;
|
|
1441
|
+
} });
|
|
1442
|
+
var afterHours_1 = require_afterHours();
|
|
1443
|
+
Object.defineProperty(exports, "isAfterHours", { enumerable: true, get: function() {
|
|
1444
|
+
return afterHours_1.isAfterHours;
|
|
1445
|
+
} });
|
|
1446
|
+
Object.defineProperty(exports, "utcOffsetMinutesAt", { enumerable: true, get: function() {
|
|
1447
|
+
return afterHours_1.utcOffsetMinutesAt;
|
|
1448
|
+
} });
|
|
1449
|
+
Object.defineProperty(exports, "localHourAt", { enumerable: true, get: function() {
|
|
1450
|
+
return afterHours_1.localHourAt;
|
|
1451
|
+
} });
|
|
1452
|
+
var payload_1 = require_payload();
|
|
1453
|
+
Object.defineProperty(exports, "getString", { enumerable: true, get: function() {
|
|
1454
|
+
return payload_1.getString;
|
|
1455
|
+
} });
|
|
1456
|
+
Object.defineProperty(exports, "getNumber", { enumerable: true, get: function() {
|
|
1457
|
+
return payload_1.getNumber;
|
|
1458
|
+
} });
|
|
1459
|
+
Object.defineProperty(exports, "getNested", { enumerable: true, get: function() {
|
|
1460
|
+
return payload_1.getNested;
|
|
1461
|
+
} });
|
|
1462
|
+
Object.defineProperty(exports, "getNestedString", { enumerable: true, get: function() {
|
|
1463
|
+
return payload_1.getNestedString;
|
|
1464
|
+
} });
|
|
1465
|
+
Object.defineProperty(exports, "getNestedNumber", { enumerable: true, get: function() {
|
|
1466
|
+
return payload_1.getNestedNumber;
|
|
1467
|
+
} });
|
|
1468
|
+
Object.defineProperty(exports, "inferOutcome", { enumerable: true, get: function() {
|
|
1469
|
+
return payload_1.inferOutcome;
|
|
1470
|
+
} });
|
|
1471
|
+
Object.defineProperty(exports, "outcomeForHook", { enumerable: true, get: function() {
|
|
1472
|
+
return payload_1.outcomeForHook;
|
|
1473
|
+
} });
|
|
1474
|
+
Object.defineProperty(exports, "looksLikeCorrection", { enumerable: true, get: function() {
|
|
1475
|
+
return payload_1.looksLikeCorrection;
|
|
1476
|
+
} });
|
|
1477
|
+
var eventSender_1 = require_eventSender();
|
|
1478
|
+
Object.defineProperty(exports, "AscendaEventSender", { enumerable: true, get: function() {
|
|
1479
|
+
return eventSender_1.AscendaEventSender;
|
|
1480
|
+
} });
|
|
1481
|
+
Object.defineProperty(exports, "AscendaSemanticEventError", { enumerable: true, get: function() {
|
|
1482
|
+
return eventSender_1.AscendaSemanticEventError;
|
|
1483
|
+
} });
|
|
1484
|
+
Object.defineProperty(exports, "buildEventPayload", { enumerable: true, get: function() {
|
|
1485
|
+
return eventSender_1.buildEventPayload;
|
|
1486
|
+
} });
|
|
1487
|
+
var eventLog_1 = require_eventLog();
|
|
1488
|
+
Object.defineProperty(exports, "EVENT_LOG_ENV_VAR", { enumerable: true, get: function() {
|
|
1489
|
+
return eventLog_1.EVENT_LOG_ENV_VAR;
|
|
1490
|
+
} });
|
|
1491
|
+
Object.defineProperty(exports, "appendEventLog", { enumerable: true, get: function() {
|
|
1492
|
+
return eventLog_1.appendEventLog;
|
|
1493
|
+
} });
|
|
1494
|
+
Object.defineProperty(exports, "expandUserPath", { enumerable: true, get: function() {
|
|
1495
|
+
return eventLog_1.expandUserPath;
|
|
1496
|
+
} });
|
|
1497
|
+
Object.defineProperty(exports, "resolveEventLogPath", { enumerable: true, get: function() {
|
|
1498
|
+
return eventLog_1.resolveEventLogPath;
|
|
1499
|
+
} });
|
|
1500
|
+
var hookAdapter_1 = require_hookAdapter();
|
|
1501
|
+
Object.defineProperty(exports, "DEFAULT_API_BASE_URL", { enumerable: true, get: function() {
|
|
1502
|
+
return hookAdapter_1.DEFAULT_API_BASE_URL;
|
|
1503
|
+
} });
|
|
1504
|
+
Object.defineProperty(exports, "deliverHookEvents", { enumerable: true, get: function() {
|
|
1505
|
+
return hookAdapter_1.deliverHookEvents;
|
|
1506
|
+
} });
|
|
1507
|
+
Object.defineProperty(exports, "loadCliAgentConfig", { enumerable: true, get: function() {
|
|
1508
|
+
return hookAdapter_1.loadCliAgentConfig;
|
|
1509
|
+
} });
|
|
1510
|
+
var turnState_1 = require_turnState();
|
|
1511
|
+
Object.defineProperty(exports, "consumeTurnDurationMs", { enumerable: true, get: function() {
|
|
1512
|
+
return turnState_1.consumeTurnDurationMs;
|
|
1513
|
+
} });
|
|
1514
|
+
Object.defineProperty(exports, "recordTurnStart", { enumerable: true, get: function() {
|
|
1515
|
+
return turnState_1.recordTurnStart;
|
|
1516
|
+
} });
|
|
1517
|
+
var tokenStore_1 = require_tokenStore();
|
|
1518
|
+
Object.defineProperty(exports, "defaultTokenFilePath", { enumerable: true, get: function() {
|
|
1519
|
+
return tokenStore_1.defaultTokenFilePath;
|
|
1520
|
+
} });
|
|
1521
|
+
Object.defineProperty(exports, "persistEventWriteToken", { enumerable: true, get: function() {
|
|
1522
|
+
return tokenStore_1.persistEventWriteToken;
|
|
1523
|
+
} });
|
|
1524
|
+
Object.defineProperty(exports, "readTokenFile", { enumerable: true, get: function() {
|
|
1525
|
+
return tokenStore_1.readTokenFile;
|
|
1526
|
+
} });
|
|
1527
|
+
var stateStore_1 = require_stateStore();
|
|
1528
|
+
Object.defineProperty(exports, "defaultStateFilePath", { enumerable: true, get: function() {
|
|
1529
|
+
return stateStore_1.defaultStateFilePath;
|
|
1530
|
+
} });
|
|
1531
|
+
Object.defineProperty(exports, "readCollectorState", { enumerable: true, get: function() {
|
|
1532
|
+
return stateStore_1.readCollectorState;
|
|
1533
|
+
} });
|
|
1534
|
+
Object.defineProperty(exports, "recordSendOutcome", { enumerable: true, get: function() {
|
|
1535
|
+
return stateStore_1.recordSendOutcome;
|
|
1536
|
+
} });
|
|
1537
|
+
Object.defineProperty(exports, "shouldAnnounceFailure", { enumerable: true, get: function() {
|
|
1538
|
+
return stateStore_1.shouldAnnounceFailure;
|
|
1539
|
+
} });
|
|
1540
|
+
Object.defineProperty(exports, "markFailureNotified", { enumerable: true, get: function() {
|
|
1541
|
+
return stateStore_1.markFailureNotified;
|
|
1542
|
+
} });
|
|
1543
|
+
var salt_1 = require_salt();
|
|
1544
|
+
Object.defineProperty(exports, "machineSaltFilePath", { enumerable: true, get: function() {
|
|
1545
|
+
return salt_1.machineSaltFilePath;
|
|
1546
|
+
} });
|
|
1547
|
+
Object.defineProperty(exports, "readOrCreateMachineSalt", { enumerable: true, get: function() {
|
|
1548
|
+
return salt_1.readOrCreateMachineSalt;
|
|
1549
|
+
} });
|
|
1550
|
+
Object.defineProperty(exports, "hashWithMachineSalt", { enumerable: true, get: function() {
|
|
1551
|
+
return salt_1.hashWithMachineSalt;
|
|
1552
|
+
} });
|
|
1553
|
+
var liveBus_1 = require_liveBus();
|
|
1554
|
+
Object.defineProperty(exports, "emitLiveSignal", { enumerable: true, get: function() {
|
|
1555
|
+
return liveBus_1.emitLiveSignal;
|
|
1556
|
+
} });
|
|
1557
|
+
Object.defineProperty(exports, "bucketPromptSize", { enumerable: true, get: function() {
|
|
1558
|
+
return liveBus_1.bucketPromptSize;
|
|
1559
|
+
} });
|
|
1560
|
+
Object.defineProperty(exports, "liveBusSocketPath", { enumerable: true, get: function() {
|
|
1561
|
+
return liveBus_1.liveBusSocketPath;
|
|
1562
|
+
} });
|
|
1563
|
+
Object.defineProperty(exports, "liveBusSocketCandidates", { enumerable: true, get: function() {
|
|
1564
|
+
return liveBus_1.liveBusSocketCandidates;
|
|
1565
|
+
} });
|
|
1566
|
+
var http_1 = require_http();
|
|
1567
|
+
Object.defineProperty(exports, "AscendaApiError", { enumerable: true, get: function() {
|
|
1568
|
+
return http_1.AscendaApiError;
|
|
1569
|
+
} });
|
|
1570
|
+
Object.defineProperty(exports, "createPairingSession", { enumerable: true, get: function() {
|
|
1571
|
+
return http_1.createPairingSession;
|
|
1572
|
+
} });
|
|
1573
|
+
Object.defineProperty(exports, "getPairingStatus", { enumerable: true, get: function() {
|
|
1574
|
+
return http_1.getPairingStatus;
|
|
1575
|
+
} });
|
|
1576
|
+
Object.defineProperty(exports, "renewToolToken", { enumerable: true, get: function() {
|
|
1577
|
+
return http_1.renewToolToken;
|
|
1578
|
+
} });
|
|
1579
|
+
Object.defineProperty(exports, "postToolEvent", { enumerable: true, get: function() {
|
|
1580
|
+
return http_1.postToolEvent;
|
|
1581
|
+
} });
|
|
1582
|
+
Object.defineProperty(exports, "postToolEventsBatch", { enumerable: true, get: function() {
|
|
1583
|
+
return http_1.postToolEventsBatch;
|
|
1584
|
+
} });
|
|
1585
|
+
Object.defineProperty(exports, "parseIngestResponse", { enumerable: true, get: function() {
|
|
1586
|
+
return http_1.parseIngestResponse;
|
|
1587
|
+
} });
|
|
1588
|
+
Object.defineProperty(exports, "isRetryableStatus", { enumerable: true, get: function() {
|
|
1589
|
+
return http_1.isRetryableStatus;
|
|
1590
|
+
} });
|
|
1591
|
+
}
|
|
1592
|
+
});
|
|
1593
|
+
|
|
1594
|
+
// src/cli.ts
|
|
1595
|
+
import * as fs8 from "node:fs/promises";
|
|
1596
|
+
import * as path8 from "node:path";
|
|
1597
|
+
|
|
1598
|
+
// src/stores.ts
|
|
1599
|
+
import * as os from "node:os";
|
|
1600
|
+
import * as path from "node:path";
|
|
1601
|
+
function resolveStorePaths(home = os.homedir()) {
|
|
1602
|
+
const appSupport = path.join(home, "Library", "Application Support");
|
|
1603
|
+
return {
|
|
1604
|
+
home,
|
|
1605
|
+
claudeRoot: path.join(home, ".claude"),
|
|
1606
|
+
claudeProjects: path.join(home, ".claude", "projects"),
|
|
1607
|
+
claudeSettings: path.join(home, ".claude", "settings.json"),
|
|
1608
|
+
cursorStateDb: path.join(appSupport, "Cursor", "User", "globalStorage", "state.vscdb"),
|
|
1609
|
+
cursorSearchDb: path.join(
|
|
1610
|
+
appSupport,
|
|
1611
|
+
"Cursor",
|
|
1612
|
+
"User",
|
|
1613
|
+
"globalStorage",
|
|
1614
|
+
"conversation-search.db"
|
|
1615
|
+
),
|
|
1616
|
+
vscodeHistory: path.join(appSupport, "Code", "User", "History"),
|
|
1617
|
+
vscodeWorkspaceStorage: path.join(appSupport, "Code", "User", "workspaceStorage"),
|
|
1618
|
+
devRoot: path.join(home, "Dev"),
|
|
1619
|
+
zshHistory: path.join(home, ".zsh_history")
|
|
1620
|
+
};
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
// src/scan.ts
|
|
1624
|
+
import * as fs from "node:fs/promises";
|
|
1625
|
+
import * as path2 from "node:path";
|
|
1626
|
+
import { execFile } from "node:child_process";
|
|
1627
|
+
import { promisify } from "node:util";
|
|
1628
|
+
var execFileAsync = promisify(execFile);
|
|
1629
|
+
async function statOrNull(p) {
|
|
1630
|
+
try {
|
|
1631
|
+
return await fs.stat(p);
|
|
1632
|
+
} catch {
|
|
1633
|
+
return null;
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
var Window = class {
|
|
1637
|
+
oldest = null;
|
|
1638
|
+
newest = null;
|
|
1639
|
+
see(d) {
|
|
1640
|
+
if (!this.oldest || d < this.oldest) this.oldest = d;
|
|
1641
|
+
if (!this.newest || d > this.newest) this.newest = d;
|
|
1642
|
+
}
|
|
1643
|
+
};
|
|
1644
|
+
async function scanClaudeCode(paths) {
|
|
1645
|
+
const inv = {
|
|
1646
|
+
store: "claude_code",
|
|
1647
|
+
rootPath: paths.claudeProjects,
|
|
1648
|
+
present: false,
|
|
1649
|
+
counts: {},
|
|
1650
|
+
notes: []
|
|
1651
|
+
};
|
|
1652
|
+
const root = await statOrNull(paths.claudeProjects);
|
|
1653
|
+
if (!root) return inv;
|
|
1654
|
+
inv.present = true;
|
|
1655
|
+
const window = new Window();
|
|
1656
|
+
let transcripts = 0;
|
|
1657
|
+
let bytes = 0;
|
|
1658
|
+
const projects = await fs.readdir(paths.claudeProjects, { withFileTypes: true });
|
|
1659
|
+
for (const project of projects) {
|
|
1660
|
+
if (!project.isDirectory()) continue;
|
|
1661
|
+
const dir = path2.join(paths.claudeProjects, project.name);
|
|
1662
|
+
for (const entry of await fs.readdir(dir)) {
|
|
1663
|
+
if (!entry.endsWith(".jsonl")) continue;
|
|
1664
|
+
const st = await statOrNull(path2.join(dir, entry));
|
|
1665
|
+
if (!st) continue;
|
|
1666
|
+
transcripts += 1;
|
|
1667
|
+
bytes += st.size;
|
|
1668
|
+
window.see(st.mtime);
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
inv.counts = { projects: projects.length, transcripts, bytes };
|
|
1672
|
+
inv.oldest = window.oldest?.toISOString();
|
|
1673
|
+
inv.newest = window.newest?.toISOString();
|
|
1674
|
+
let cleanupPeriodDays = null;
|
|
1675
|
+
try {
|
|
1676
|
+
const settings = JSON.parse(await fs.readFile(paths.claudeSettings, "utf8"));
|
|
1677
|
+
if (typeof settings.cleanupPeriodDays === "number") {
|
|
1678
|
+
cleanupPeriodDays = settings.cleanupPeriodDays;
|
|
1679
|
+
}
|
|
1680
|
+
} catch {
|
|
1681
|
+
}
|
|
1682
|
+
if (cleanupPeriodDays === null || cleanupPeriodDays <= 30) {
|
|
1683
|
+
inv.retentionRisk = `rolling purge active (cleanupPeriodDays: ${cleanupPeriodDays ?? "30 by default"}) \u2014 the oldest transcript is deleted daily`;
|
|
1684
|
+
}
|
|
1685
|
+
return inv;
|
|
1686
|
+
}
|
|
1687
|
+
async function scanCursor(paths) {
|
|
1688
|
+
const inv = {
|
|
1689
|
+
store: "cursor",
|
|
1690
|
+
rootPath: paths.cursorStateDb,
|
|
1691
|
+
present: false,
|
|
1692
|
+
counts: {},
|
|
1693
|
+
notes: []
|
|
1694
|
+
};
|
|
1695
|
+
const st = await statOrNull(paths.cursorStateDb);
|
|
1696
|
+
if (!st) return inv;
|
|
1697
|
+
inv.present = true;
|
|
1698
|
+
inv.counts = { bytes: st.size };
|
|
1699
|
+
inv.newest = st.mtime.toISOString();
|
|
1700
|
+
try {
|
|
1701
|
+
const uri = `file:${paths.cursorStateDb}?mode=ro&immutable=1`;
|
|
1702
|
+
const { stdout } = await execFileAsync("sqlite3", [
|
|
1703
|
+
uri,
|
|
1704
|
+
"SELECT (SELECT count(*) FROM cursorDiskKV WHERE key LIKE 'composerData:%'), (SELECT count(*) FROM cursorDiskKV WHERE key LIKE 'bubbleId:%');"
|
|
1705
|
+
]);
|
|
1706
|
+
const [conversations, bubbles] = stdout.trim().split("|").map(Number);
|
|
1707
|
+
if (Number.isFinite(conversations)) inv.counts.conversations = conversations;
|
|
1708
|
+
if (Number.isFinite(bubbles)) inv.counts.bubbles = bubbles;
|
|
1709
|
+
} catch (error) {
|
|
1710
|
+
inv.notes.push(
|
|
1711
|
+
`row counts unavailable (${error instanceof Error ? error.message.split("\n")[0] : "sqlite3 failed"}); file-level facts only`
|
|
1712
|
+
);
|
|
1713
|
+
}
|
|
1714
|
+
return inv;
|
|
1715
|
+
}
|
|
1716
|
+
async function scanVsCode(paths) {
|
|
1717
|
+
const inv = {
|
|
1718
|
+
store: "vscode",
|
|
1719
|
+
rootPath: paths.vscodeHistory,
|
|
1720
|
+
present: false,
|
|
1721
|
+
counts: {},
|
|
1722
|
+
notes: []
|
|
1723
|
+
};
|
|
1724
|
+
const root = await statOrNull(paths.vscodeHistory);
|
|
1725
|
+
if (root) {
|
|
1726
|
+
inv.present = true;
|
|
1727
|
+
const window = new Window();
|
|
1728
|
+
let fileHistories = 0;
|
|
1729
|
+
for (const entry of await fs.readdir(paths.vscodeHistory, { withFileTypes: true })) {
|
|
1730
|
+
if (!entry.isDirectory()) continue;
|
|
1731
|
+
fileHistories += 1;
|
|
1732
|
+
const st = await statOrNull(path2.join(paths.vscodeHistory, entry.name));
|
|
1733
|
+
if (st) window.see(st.mtime);
|
|
1734
|
+
}
|
|
1735
|
+
inv.counts.fileHistories = fileHistories;
|
|
1736
|
+
inv.oldest = window.oldest?.toISOString();
|
|
1737
|
+
inv.newest = window.newest?.toISOString();
|
|
1738
|
+
}
|
|
1739
|
+
let chatSessions = 0;
|
|
1740
|
+
const wsRoot = await statOrNull(paths.vscodeWorkspaceStorage);
|
|
1741
|
+
if (wsRoot) {
|
|
1742
|
+
inv.present = true;
|
|
1743
|
+
for (const ws of await fs.readdir(paths.vscodeWorkspaceStorage, { withFileTypes: true })) {
|
|
1744
|
+
if (!ws.isDirectory()) continue;
|
|
1745
|
+
const sessions = path2.join(paths.vscodeWorkspaceStorage, ws.name, "chatSessions");
|
|
1746
|
+
try {
|
|
1747
|
+
chatSessions += (await fs.readdir(sessions)).filter((f) => f.endsWith(".json")).length;
|
|
1748
|
+
} catch {
|
|
1749
|
+
}
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
inv.counts.chatSessions = chatSessions;
|
|
1753
|
+
return inv;
|
|
1754
|
+
}
|
|
1755
|
+
async function scanGit(paths) {
|
|
1756
|
+
const inv = {
|
|
1757
|
+
store: "git",
|
|
1758
|
+
rootPath: paths.devRoot,
|
|
1759
|
+
present: false,
|
|
1760
|
+
counts: {},
|
|
1761
|
+
notes: ["the only fully durable, fully supported source \u2014 the spine other stores hang off"]
|
|
1762
|
+
};
|
|
1763
|
+
const root = await statOrNull(paths.devRoot);
|
|
1764
|
+
if (!root) return inv;
|
|
1765
|
+
inv.present = true;
|
|
1766
|
+
let repos = 0;
|
|
1767
|
+
const level1 = await fs.readdir(paths.devRoot, { withFileTypes: true });
|
|
1768
|
+
for (const entry of level1) {
|
|
1769
|
+
if (!entry.isDirectory()) continue;
|
|
1770
|
+
const dir = path2.join(paths.devRoot, entry.name);
|
|
1771
|
+
if (await statOrNull(path2.join(dir, ".git"))) {
|
|
1772
|
+
repos += 1;
|
|
1773
|
+
continue;
|
|
1774
|
+
}
|
|
1775
|
+
let nested = [];
|
|
1776
|
+
try {
|
|
1777
|
+
nested = await fs.readdir(dir, { withFileTypes: true });
|
|
1778
|
+
} catch {
|
|
1779
|
+
continue;
|
|
1780
|
+
}
|
|
1781
|
+
for (const sub of nested) {
|
|
1782
|
+
if (!sub.isDirectory()) continue;
|
|
1783
|
+
if (await statOrNull(path2.join(dir, sub.name, ".git"))) repos += 1;
|
|
1784
|
+
}
|
|
1785
|
+
}
|
|
1786
|
+
inv.counts.repos = repos;
|
|
1787
|
+
return inv;
|
|
1788
|
+
}
|
|
1789
|
+
async function scanAll(paths) {
|
|
1790
|
+
return [
|
|
1791
|
+
await scanClaudeCode(paths),
|
|
1792
|
+
await scanCursor(paths),
|
|
1793
|
+
await scanVsCode(paths),
|
|
1794
|
+
await scanGit(paths)
|
|
1795
|
+
];
|
|
1796
|
+
}
|
|
1797
|
+
|
|
1798
|
+
// src/retention.ts
|
|
1799
|
+
import * as fs2 from "node:fs/promises";
|
|
1800
|
+
var PROPOSED_CLEANUP_PERIOD_DAYS = 3650;
|
|
1801
|
+
async function planClaudeRetentionFix(paths) {
|
|
1802
|
+
let current = null;
|
|
1803
|
+
try {
|
|
1804
|
+
const settings = JSON.parse(await fs2.readFile(paths.claudeSettings, "utf8"));
|
|
1805
|
+
if (typeof settings.cleanupPeriodDays === "number") current = settings.cleanupPeriodDays;
|
|
1806
|
+
} catch {
|
|
1807
|
+
}
|
|
1808
|
+
return {
|
|
1809
|
+
settingsPath: paths.claudeSettings,
|
|
1810
|
+
currentCleanupPeriodDays: current,
|
|
1811
|
+
proposedCleanupPeriodDays: PROPOSED_CLEANUP_PERIOD_DAYS,
|
|
1812
|
+
changeNeeded: current === null || current < PROPOSED_CLEANUP_PERIOD_DAYS
|
|
1813
|
+
};
|
|
1814
|
+
}
|
|
1815
|
+
async function applyClaudeRetentionFix(plan) {
|
|
1816
|
+
if (!plan.changeNeeded) return;
|
|
1817
|
+
let settings = {};
|
|
1818
|
+
let raw = null;
|
|
1819
|
+
try {
|
|
1820
|
+
raw = await fs2.readFile(plan.settingsPath, "utf8");
|
|
1821
|
+
} catch {
|
|
1822
|
+
}
|
|
1823
|
+
if (raw !== null) {
|
|
1824
|
+
try {
|
|
1825
|
+
settings = JSON.parse(raw);
|
|
1826
|
+
} catch {
|
|
1827
|
+
throw new Error(
|
|
1828
|
+
`${plan.settingsPath} exists but is not valid JSON \u2014 refusing to overwrite it`
|
|
1829
|
+
);
|
|
1830
|
+
}
|
|
1831
|
+
}
|
|
1832
|
+
settings.cleanupPeriodDays = plan.proposedCleanupPeriodDays;
|
|
1833
|
+
await fs2.writeFile(plan.settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
1834
|
+
}
|
|
1835
|
+
|
|
1836
|
+
// src/staging.ts
|
|
1837
|
+
import * as fs3 from "node:fs/promises";
|
|
1838
|
+
import { constants } from "node:fs";
|
|
1839
|
+
import * as path3 from "node:path";
|
|
1840
|
+
import { randomUUID } from "node:crypto";
|
|
1841
|
+
var CLONE = constants.COPYFILE_FICLONE;
|
|
1842
|
+
function defaultStagingRoot(home) {
|
|
1843
|
+
return path3.join(home, ".ascenda", "history-import", "staging");
|
|
1844
|
+
}
|
|
1845
|
+
async function createStagingArea(stagingRoot) {
|
|
1846
|
+
const extractionId = `${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}-${randomUUID().slice(0, 8)}`;
|
|
1847
|
+
const root = path3.join(stagingRoot, extractionId);
|
|
1848
|
+
await fs3.mkdir(root, { recursive: true });
|
|
1849
|
+
return { extractionId, root };
|
|
1850
|
+
}
|
|
1851
|
+
async function snapshotPath(area, sourcePath, label) {
|
|
1852
|
+
const dest = path3.join(area.root, label);
|
|
1853
|
+
await fs3.mkdir(path3.dirname(dest), { recursive: true });
|
|
1854
|
+
const st = await fs3.stat(sourcePath);
|
|
1855
|
+
if (st.isDirectory()) {
|
|
1856
|
+
await fs3.cp(sourcePath, dest, {
|
|
1857
|
+
recursive: true,
|
|
1858
|
+
errorOnExist: false,
|
|
1859
|
+
force: true,
|
|
1860
|
+
mode: CLONE
|
|
1861
|
+
});
|
|
1862
|
+
} else {
|
|
1863
|
+
await fs3.copyFile(sourcePath, dest, CLONE);
|
|
1864
|
+
for (const suffix of ["-wal", "-shm"]) {
|
|
1865
|
+
try {
|
|
1866
|
+
await fs3.copyFile(sourcePath + suffix, dest + suffix, CLONE);
|
|
1867
|
+
} catch {
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
return dest;
|
|
1872
|
+
}
|
|
1873
|
+
async function snapshotVsCodeWorkspaceStorage(area, sourceRoot, label) {
|
|
1874
|
+
const dest = path3.join(area.root, label);
|
|
1875
|
+
await fs3.mkdir(dest, { recursive: true });
|
|
1876
|
+
let hashDirs = [];
|
|
1877
|
+
try {
|
|
1878
|
+
hashDirs = (await fs3.readdir(sourceRoot, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
1879
|
+
} catch {
|
|
1880
|
+
return dest;
|
|
1881
|
+
}
|
|
1882
|
+
for (const hash of hashDirs) {
|
|
1883
|
+
const destHashDir = path3.join(dest, hash);
|
|
1884
|
+
const workspaceJson = path3.join(sourceRoot, hash, "workspace.json");
|
|
1885
|
+
try {
|
|
1886
|
+
await fs3.mkdir(destHashDir, { recursive: true });
|
|
1887
|
+
await fs3.copyFile(workspaceJson, path3.join(destHashDir, "workspace.json"), CLONE);
|
|
1888
|
+
} catch {
|
|
1889
|
+
}
|
|
1890
|
+
const chatSessionsDir = path3.join(sourceRoot, hash, "chatSessions");
|
|
1891
|
+
let sessionFiles = [];
|
|
1892
|
+
try {
|
|
1893
|
+
sessionFiles = (await fs3.readdir(chatSessionsDir)).filter(
|
|
1894
|
+
(f) => f.endsWith(".json") || f.endsWith(".jsonl")
|
|
1895
|
+
);
|
|
1896
|
+
} catch {
|
|
1897
|
+
continue;
|
|
1898
|
+
}
|
|
1899
|
+
if (sessionFiles.length === 0) continue;
|
|
1900
|
+
const destSessionsDir = path3.join(destHashDir, "chatSessions");
|
|
1901
|
+
await fs3.mkdir(destSessionsDir, { recursive: true });
|
|
1902
|
+
for (const file of sessionFiles) {
|
|
1903
|
+
await fs3.copyFile(path3.join(chatSessionsDir, file), path3.join(destSessionsDir, file), CLONE);
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
return dest;
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
// src/extractors/claudeCode.ts
|
|
1910
|
+
var import_tool_kit = __toESM(require_out2(), 1);
|
|
1911
|
+
import * as fs4 from "node:fs/promises";
|
|
1912
|
+
import * as path4 from "node:path";
|
|
1913
|
+
|
|
1914
|
+
// src/types.ts
|
|
1915
|
+
var import_tool_contract = __toESM(require_out(), 1);
|
|
1916
|
+
var STORE_SOURCE = {
|
|
1917
|
+
claude_code: "claude_code",
|
|
1918
|
+
cursor: "cursor_mcp",
|
|
1919
|
+
vscode: "vscode_extension",
|
|
1920
|
+
git: "code_forge"
|
|
1921
|
+
};
|
|
1922
|
+
var HISTORICAL_PROVENANCE = {
|
|
1923
|
+
direct: "historical_direct",
|
|
1924
|
+
derived: "historical_derived",
|
|
1925
|
+
unparsed: "historical_unparsed"
|
|
1926
|
+
};
|
|
1927
|
+
var HISTORICAL_CONSENT_SCOPE = import_tool_contract.ASCENDA_HISTORICAL_CONSENT_SCOPE;
|
|
1928
|
+
var EXTRACTION_EPOCH_KIND = "extraction_epoch";
|
|
1929
|
+
|
|
1930
|
+
// src/daySlice.ts
|
|
1931
|
+
var LOCAL_TIMEZONE = Intl.DateTimeFormat().resolvedOptions().timeZone ?? null;
|
|
1932
|
+
function localDayKey(t) {
|
|
1933
|
+
const y = t.getFullYear();
|
|
1934
|
+
const m = String(t.getMonth() + 1).padStart(2, "0");
|
|
1935
|
+
const d = String(t.getDate()).padStart(2, "0");
|
|
1936
|
+
return `${y}-${m}-${d}`;
|
|
1937
|
+
}
|
|
1938
|
+
function nextLocalMidnight(t) {
|
|
1939
|
+
return new Date(t.getFullYear(), t.getMonth(), t.getDate() + 1, 0, 0, 0, 0);
|
|
1940
|
+
}
|
|
1941
|
+
function addSpan(into, from, to) {
|
|
1942
|
+
let cursor = from;
|
|
1943
|
+
while (cursor < to) {
|
|
1944
|
+
const boundary = nextLocalMidnight(cursor);
|
|
1945
|
+
const segmentEnd = boundary < to ? boundary : to;
|
|
1946
|
+
const key = localDayKey(cursor);
|
|
1947
|
+
into.set(key, (into.get(key) ?? 0) + (segmentEnd.getTime() - cursor.getTime()));
|
|
1948
|
+
cursor = segmentEnd;
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
function sliceSessionByLocalDay(timestamps, options = {}) {
|
|
1952
|
+
const instants = timestamps.filter((t) => typeof t === "string" && t.length > 0).map((t) => new Date(t)).filter((d) => Number.isFinite(d.getTime())).sort((a, b) => a.getTime() - b.getTime());
|
|
1953
|
+
if (instants.length === 0) return [];
|
|
1954
|
+
const prompts = /* @__PURE__ */ new Map();
|
|
1955
|
+
for (const t of instants) {
|
|
1956
|
+
const key = localDayKey(t);
|
|
1957
|
+
prompts.set(key, (prompts.get(key) ?? 0) + 1);
|
|
1958
|
+
}
|
|
1959
|
+
const activeMs = /* @__PURE__ */ new Map();
|
|
1960
|
+
const gapMs = options.activeGapMs;
|
|
1961
|
+
if (gapMs !== void 0) {
|
|
1962
|
+
for (let i = 1; i < instants.length; i += 1) {
|
|
1963
|
+
const gap = instants[i].getTime() - instants[i - 1].getTime();
|
|
1964
|
+
if (gap > 0 && gap <= gapMs) addSpan(activeMs, instants[i - 1], instants[i]);
|
|
1965
|
+
}
|
|
1966
|
+
}
|
|
1967
|
+
const days = [.../* @__PURE__ */ new Set([...prompts.keys(), ...activeMs.keys()])].sort();
|
|
1968
|
+
return days.map((day) => {
|
|
1969
|
+
const slice = { day, prompts: prompts.get(day) ?? 0 };
|
|
1970
|
+
if (gapMs !== void 0) {
|
|
1971
|
+
slice.activeMinutes = Math.round((activeMs.get(day) ?? 0) / 6e4);
|
|
1972
|
+
}
|
|
1973
|
+
return slice;
|
|
1974
|
+
});
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
// src/extractors/claudeCode.ts
|
|
1978
|
+
var KNOWN_CLAUDE_LINE_TYPES = [
|
|
1979
|
+
"user",
|
|
1980
|
+
"assistant",
|
|
1981
|
+
"attachment",
|
|
1982
|
+
"system",
|
|
1983
|
+
"queue-operation",
|
|
1984
|
+
"last-prompt",
|
|
1985
|
+
"custom-title"
|
|
1986
|
+
];
|
|
1987
|
+
function sniffClaudeLine(line) {
|
|
1988
|
+
const trimmed = line.trim();
|
|
1989
|
+
if (trimmed === "") return { kind: "unparsed", raw: line };
|
|
1990
|
+
let parsed;
|
|
1991
|
+
try {
|
|
1992
|
+
parsed = JSON.parse(trimmed);
|
|
1993
|
+
} catch {
|
|
1994
|
+
return { kind: "unparsed", raw: line };
|
|
1995
|
+
}
|
|
1996
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
1997
|
+
return { kind: "unparsed", raw: line };
|
|
1998
|
+
}
|
|
1999
|
+
const record = parsed;
|
|
2000
|
+
const type = record.type;
|
|
2001
|
+
if (typeof type !== "string") return { kind: "unparsed", raw: line };
|
|
2002
|
+
if (!KNOWN_CLAUDE_LINE_TYPES.includes(type)) {
|
|
2003
|
+
return { kind: "unknown", type };
|
|
2004
|
+
}
|
|
2005
|
+
const message = record.message;
|
|
2006
|
+
return {
|
|
2007
|
+
kind: type,
|
|
2008
|
+
sourceVersion: typeof record.version === "string" ? record.version : null,
|
|
2009
|
+
occurredAt: typeof record.timestamp === "string" ? record.timestamp : null,
|
|
2010
|
+
sessionId: typeof record.sessionId === "string" ? record.sessionId : null,
|
|
2011
|
+
model: message && typeof message.model === "string" ? message.model : null
|
|
2012
|
+
};
|
|
2013
|
+
}
|
|
2014
|
+
function isToolResultUserLine(record) {
|
|
2015
|
+
if ("toolUseResult" in record) return true;
|
|
2016
|
+
const message = record.message;
|
|
2017
|
+
const content = message?.content;
|
|
2018
|
+
if (Array.isArray(content)) {
|
|
2019
|
+
return content.some(
|
|
2020
|
+
(item) => typeof item === "object" && item !== null && item.type === "tool_result"
|
|
2021
|
+
);
|
|
2022
|
+
}
|
|
2023
|
+
return false;
|
|
2024
|
+
}
|
|
2025
|
+
function isToolFailureLine(record) {
|
|
2026
|
+
const message = record.message;
|
|
2027
|
+
const content = message?.content;
|
|
2028
|
+
if (!Array.isArray(content)) return false;
|
|
2029
|
+
return content.some(
|
|
2030
|
+
(item) => typeof item === "object" && item !== null && item.type === "tool_result" && item.is_error === true
|
|
2031
|
+
);
|
|
2032
|
+
}
|
|
2033
|
+
function countChangedLines(structuredPatch) {
|
|
2034
|
+
let count = 0;
|
|
2035
|
+
for (const hunk of structuredPatch) {
|
|
2036
|
+
if (typeof hunk !== "object" || hunk === null) continue;
|
|
2037
|
+
const lines = hunk.lines;
|
|
2038
|
+
if (!Array.isArray(lines)) continue;
|
|
2039
|
+
for (const l of lines) {
|
|
2040
|
+
if (typeof l === "string" && (l.startsWith("+") || l.startsWith("-"))) count += 1;
|
|
2041
|
+
}
|
|
2042
|
+
}
|
|
2043
|
+
return count;
|
|
2044
|
+
}
|
|
2045
|
+
function isAbandonedPromptRemoval(content) {
|
|
2046
|
+
return typeof content === "string" && content.length > 0 && !content.startsWith("<task-notification>");
|
|
2047
|
+
}
|
|
2048
|
+
function newFold(sessionId, projectSlug) {
|
|
2049
|
+
return {
|
|
2050
|
+
sessionId,
|
|
2051
|
+
projectSlug,
|
|
2052
|
+
cwd: null,
|
|
2053
|
+
gitBranch: null,
|
|
2054
|
+
sourceVersion: null,
|
|
2055
|
+
firstTs: null,
|
|
2056
|
+
lastTs: null,
|
|
2057
|
+
humanPrompts: 0,
|
|
2058
|
+
afterHoursPrompts: 0,
|
|
2059
|
+
assistantTurns: 0,
|
|
2060
|
+
toolResults: 0,
|
|
2061
|
+
queuedPrompts: 0,
|
|
2062
|
+
unknownLines: 0,
|
|
2063
|
+
unparsedLines: 0,
|
|
2064
|
+
inputTokens: 0,
|
|
2065
|
+
outputTokens: 0,
|
|
2066
|
+
cacheReadTokens: 0,
|
|
2067
|
+
models: /* @__PURE__ */ new Map(),
|
|
2068
|
+
lastMainModel: null,
|
|
2069
|
+
modelSwitchCount: 0,
|
|
2070
|
+
compactionCount: 0,
|
|
2071
|
+
compactionManualCount: 0,
|
|
2072
|
+
compactionAutoCount: 0,
|
|
2073
|
+
toolResultErrorCount: 0,
|
|
2074
|
+
apiErrorCount: 0,
|
|
2075
|
+
contextWindowPeakTokens: 0,
|
|
2076
|
+
userModifiedEditCount: 0,
|
|
2077
|
+
linesChangedTotal: 0,
|
|
2078
|
+
abandonedPromptCount: 0,
|
|
2079
|
+
subagentTranscripts: 0,
|
|
2080
|
+
subagentPrompts: 0,
|
|
2081
|
+
subagentAssistantTurns: 0,
|
|
2082
|
+
subagentTokensTotal: 0,
|
|
2083
|
+
timelinePoints: [],
|
|
2084
|
+
humanPromptTimestamps: []
|
|
2085
|
+
};
|
|
2086
|
+
}
|
|
2087
|
+
function asNumber(value) {
|
|
2088
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
2089
|
+
}
|
|
2090
|
+
function sessionDurationMs(fold) {
|
|
2091
|
+
if (!fold.firstTs || !fold.lastTs) return null;
|
|
2092
|
+
const ms = Date.parse(fold.lastTs) - Date.parse(fold.firstTs);
|
|
2093
|
+
return Number.isFinite(ms) && ms >= 0 ? ms : null;
|
|
2094
|
+
}
|
|
2095
|
+
function durationBucketOf(fold) {
|
|
2096
|
+
const ms = sessionDurationMs(fold);
|
|
2097
|
+
if (ms === null) return "unknown";
|
|
2098
|
+
return (0, import_tool_kit.bucketDurationMs)(ms) ?? "unknown";
|
|
2099
|
+
}
|
|
2100
|
+
var ACTIVE_GAP_MS = 5 * 6e4;
|
|
2101
|
+
function activeMinutesOf(fold) {
|
|
2102
|
+
if (fold.timelinePoints.length < 2) return 0;
|
|
2103
|
+
const sorted = [...fold.timelinePoints].sort((a, b) => a - b);
|
|
2104
|
+
let activeMs = 0;
|
|
2105
|
+
for (let i = 1; i < sorted.length; i++) {
|
|
2106
|
+
const gap = sorted[i] - sorted[i - 1];
|
|
2107
|
+
if (gap > 0 && gap <= ACTIVE_GAP_MS) activeMs += gap;
|
|
2108
|
+
}
|
|
2109
|
+
return Math.round(activeMs / 6e4);
|
|
2110
|
+
}
|
|
2111
|
+
var RAPID_REPROMPT_MS = 2 * 6e4;
|
|
2112
|
+
function rapidRepromptCountOf(fold) {
|
|
2113
|
+
const timestamps = fold.humanPromptTimestamps.filter((t) => t !== null);
|
|
2114
|
+
let count = 0;
|
|
2115
|
+
for (let i = 1; i < timestamps.length; i++) {
|
|
2116
|
+
const gap = Date.parse(timestamps[i]) - Date.parse(timestamps[i - 1]);
|
|
2117
|
+
if (Number.isFinite(gap) && gap >= 0 && gap < RAPID_REPROMPT_MS) count += 1;
|
|
2118
|
+
}
|
|
2119
|
+
return count;
|
|
2120
|
+
}
|
|
2121
|
+
var ASSUMED_CONTEXT_WINDOW_TOKENS = 2e5;
|
|
2122
|
+
function contextWindowPeakPctOf(fold) {
|
|
2123
|
+
if (fold.contextWindowPeakTokens <= 0) return 0;
|
|
2124
|
+
const pct = fold.contextWindowPeakTokens / ASSUMED_CONTEXT_WINDOW_TOKENS;
|
|
2125
|
+
return Math.round(Math.min(1, pct) * 1e3) / 1e3;
|
|
2126
|
+
}
|
|
2127
|
+
async function foldLinesInto(fold, filePath, opts) {
|
|
2128
|
+
const handle = await fs4.open(filePath);
|
|
2129
|
+
try {
|
|
2130
|
+
for await (const line of handle.readLines({ encoding: "utf8" })) {
|
|
2131
|
+
const sniffed = sniffClaudeLine(line);
|
|
2132
|
+
if (sniffed.kind === "unparsed") {
|
|
2133
|
+
if (line.trim() !== "") fold.unparsedLines += 1;
|
|
2134
|
+
continue;
|
|
2135
|
+
}
|
|
2136
|
+
if (sniffed.kind === "unknown") {
|
|
2137
|
+
fold.unknownLines += 1;
|
|
2138
|
+
continue;
|
|
2139
|
+
}
|
|
2140
|
+
if (!opts.isSidechain && sniffed.sessionId && fold.sessionId !== sniffed.sessionId) {
|
|
2141
|
+
fold.sessionId = sniffed.sessionId;
|
|
2142
|
+
}
|
|
2143
|
+
if (sniffed.sourceVersion) fold.sourceVersion = sniffed.sourceVersion;
|
|
2144
|
+
if (sniffed.occurredAt) {
|
|
2145
|
+
if (!fold.firstTs || sniffed.occurredAt < fold.firstTs) fold.firstTs = sniffed.occurredAt;
|
|
2146
|
+
if (!fold.lastTs || sniffed.occurredAt > fold.lastTs) fold.lastTs = sniffed.occurredAt;
|
|
2147
|
+
const ms = Date.parse(sniffed.occurredAt);
|
|
2148
|
+
if (Number.isFinite(ms)) fold.timelinePoints.push(ms);
|
|
2149
|
+
}
|
|
2150
|
+
switch (sniffed.kind) {
|
|
2151
|
+
case "user": {
|
|
2152
|
+
const record = JSON.parse(line);
|
|
2153
|
+
if (typeof record.cwd === "string" && !fold.cwd) fold.cwd = record.cwd;
|
|
2154
|
+
if (typeof record.gitBranch === "string" && !fold.gitBranch) {
|
|
2155
|
+
fold.gitBranch = record.gitBranch;
|
|
2156
|
+
}
|
|
2157
|
+
if (isToolFailureLine(record)) fold.toolResultErrorCount += 1;
|
|
2158
|
+
if (isToolResultUserLine(record)) {
|
|
2159
|
+
fold.toolResults += 1;
|
|
2160
|
+
const tur = record.toolUseResult;
|
|
2161
|
+
if (tur && typeof tur === "object" && !Array.isArray(tur)) {
|
|
2162
|
+
const turRecord = tur;
|
|
2163
|
+
if (turRecord.userModified === true) fold.userModifiedEditCount += 1;
|
|
2164
|
+
if (Array.isArray(turRecord.structuredPatch)) {
|
|
2165
|
+
fold.linesChangedTotal += countChangedLines(turRecord.structuredPatch);
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
} else if (opts.isSidechain) {
|
|
2169
|
+
fold.subagentPrompts += 1;
|
|
2170
|
+
} else {
|
|
2171
|
+
fold.humanPrompts += 1;
|
|
2172
|
+
if (sniffed.occurredAt && (0, import_tool_kit.isAfterHours)(new Date(sniffed.occurredAt))) {
|
|
2173
|
+
fold.afterHoursPrompts += 1;
|
|
2174
|
+
}
|
|
2175
|
+
fold.humanPromptTimestamps.push(sniffed.occurredAt);
|
|
2176
|
+
}
|
|
2177
|
+
break;
|
|
2178
|
+
}
|
|
2179
|
+
case "assistant": {
|
|
2180
|
+
const record = JSON.parse(line);
|
|
2181
|
+
const usage = record.message?.usage;
|
|
2182
|
+
if (opts.isSidechain) {
|
|
2183
|
+
fold.subagentAssistantTurns += 1;
|
|
2184
|
+
if (usage) {
|
|
2185
|
+
fold.subagentTokensTotal += asNumber(usage.input_tokens) + asNumber(usage.output_tokens) + asNumber(usage.cache_read_input_tokens);
|
|
2186
|
+
}
|
|
2187
|
+
break;
|
|
2188
|
+
}
|
|
2189
|
+
fold.assistantTurns += 1;
|
|
2190
|
+
if (sniffed.model) {
|
|
2191
|
+
fold.models.set(sniffed.model, (fold.models.get(sniffed.model) ?? 0) + 1);
|
|
2192
|
+
if (fold.lastMainModel && fold.lastMainModel !== sniffed.model) {
|
|
2193
|
+
fold.modelSwitchCount += 1;
|
|
2194
|
+
}
|
|
2195
|
+
fold.lastMainModel = sniffed.model;
|
|
2196
|
+
}
|
|
2197
|
+
if (usage) {
|
|
2198
|
+
fold.inputTokens += asNumber(usage.input_tokens);
|
|
2199
|
+
fold.outputTokens += asNumber(usage.output_tokens);
|
|
2200
|
+
fold.cacheReadTokens += asNumber(usage.cache_read_input_tokens);
|
|
2201
|
+
const contextTokens = asNumber(usage.input_tokens) + asNumber(usage.cache_read_input_tokens) + asNumber(usage.cache_creation_input_tokens);
|
|
2202
|
+
if (contextTokens > fold.contextWindowPeakTokens) fold.contextWindowPeakTokens = contextTokens;
|
|
2203
|
+
}
|
|
2204
|
+
break;
|
|
2205
|
+
}
|
|
2206
|
+
case "system": {
|
|
2207
|
+
const record = JSON.parse(line);
|
|
2208
|
+
if (record.subtype === "compact_boundary") {
|
|
2209
|
+
fold.compactionCount += 1;
|
|
2210
|
+
const trigger = record.compactMetadata?.trigger;
|
|
2211
|
+
if (trigger === "manual") fold.compactionManualCount += 1;
|
|
2212
|
+
else if (trigger === "auto") fold.compactionAutoCount += 1;
|
|
2213
|
+
} else if (record.subtype === "api_error") {
|
|
2214
|
+
fold.apiErrorCount += 1;
|
|
2215
|
+
}
|
|
2216
|
+
break;
|
|
2217
|
+
}
|
|
2218
|
+
case "queue-operation": {
|
|
2219
|
+
const record = JSON.parse(line);
|
|
2220
|
+
if (record.operation === "enqueue") {
|
|
2221
|
+
fold.queuedPrompts += 1;
|
|
2222
|
+
} else if (record.operation === "remove" && isAbandonedPromptRemoval(record.content)) {
|
|
2223
|
+
fold.abandonedPromptCount += 1;
|
|
2224
|
+
}
|
|
2225
|
+
break;
|
|
2226
|
+
}
|
|
2227
|
+
default:
|
|
2228
|
+
break;
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
} finally {
|
|
2232
|
+
await handle.close();
|
|
2233
|
+
}
|
|
2234
|
+
}
|
|
2235
|
+
async function foldTranscript(filePath, projectSlug) {
|
|
2236
|
+
const fold = newFold(path4.basename(filePath, ".jsonl"), projectSlug);
|
|
2237
|
+
await foldLinesInto(fold, filePath, { isSidechain: false });
|
|
2238
|
+
return fold;
|
|
2239
|
+
}
|
|
2240
|
+
async function walkJsonlFiles(root) {
|
|
2241
|
+
const out = [];
|
|
2242
|
+
let otherFiles = 0;
|
|
2243
|
+
async function walk(dir) {
|
|
2244
|
+
let entries;
|
|
2245
|
+
try {
|
|
2246
|
+
entries = await fs4.readdir(dir, { withFileTypes: true });
|
|
2247
|
+
} catch {
|
|
2248
|
+
return;
|
|
2249
|
+
}
|
|
2250
|
+
for (const entry of [...entries].sort((a, b) => a.name.localeCompare(b.name))) {
|
|
2251
|
+
const full = path4.join(dir, entry.name);
|
|
2252
|
+
if (entry.isDirectory()) {
|
|
2253
|
+
await walk(full);
|
|
2254
|
+
} else if (entry.isFile() && entry.name.endsWith(".jsonl")) {
|
|
2255
|
+
out.push(full);
|
|
2256
|
+
} else if (entry.isFile()) {
|
|
2257
|
+
otherFiles += 1;
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
}
|
|
2261
|
+
await walk(root);
|
|
2262
|
+
return { files: out, otherFiles };
|
|
2263
|
+
}
|
|
2264
|
+
function topModel(models) {
|
|
2265
|
+
let best = null;
|
|
2266
|
+
let bestCount = 0;
|
|
2267
|
+
for (const [model, count] of models) {
|
|
2268
|
+
if (count > bestCount) {
|
|
2269
|
+
best = model;
|
|
2270
|
+
bestCount = count;
|
|
2271
|
+
}
|
|
2272
|
+
}
|
|
2273
|
+
return best;
|
|
2274
|
+
}
|
|
2275
|
+
async function* extractClaudeCode(snapshotDir, extractionId) {
|
|
2276
|
+
const projectsDir = path4.join(snapshotDir, "projects");
|
|
2277
|
+
let projectSlugs = [];
|
|
2278
|
+
try {
|
|
2279
|
+
projectSlugs = (await fs4.readdir(projectsDir, { withFileTypes: true })).filter((entry) => entry.isDirectory()).map((entry) => entry.name);
|
|
2280
|
+
} catch {
|
|
2281
|
+
return;
|
|
2282
|
+
}
|
|
2283
|
+
let windowOldest = null;
|
|
2284
|
+
let windowNewest = null;
|
|
2285
|
+
let sessionCount = 0;
|
|
2286
|
+
let projectsWithNoReadableTranscript = 0;
|
|
2287
|
+
for (const slug of projectSlugs.sort()) {
|
|
2288
|
+
const dir = path4.join(projectsDir, slug);
|
|
2289
|
+
let entries;
|
|
2290
|
+
try {
|
|
2291
|
+
entries = await fs4.readdir(dir, { withFileTypes: true });
|
|
2292
|
+
} catch {
|
|
2293
|
+
continue;
|
|
2294
|
+
}
|
|
2295
|
+
const topLevelFiles = entries.filter((e) => e.isFile() && e.name.endsWith(".jsonl")).map((e) => e.name).sort();
|
|
2296
|
+
let slugJsonlFiles = topLevelFiles.length;
|
|
2297
|
+
let slugOtherFiles = entries.filter((e) => e.isFile() && !e.name.endsWith(".jsonl")).length;
|
|
2298
|
+
const sessionDirNames = entries.filter((e) => e.isDirectory()).map((e) => e.name).sort();
|
|
2299
|
+
const foldsByKey = /* @__PURE__ */ new Map();
|
|
2300
|
+
for (const name of topLevelFiles) {
|
|
2301
|
+
const fold = await foldTranscript(path4.join(dir, name), slug);
|
|
2302
|
+
foldsByKey.set(path4.basename(name, ".jsonl"), fold);
|
|
2303
|
+
}
|
|
2304
|
+
for (const sessionDirName of sessionDirNames) {
|
|
2305
|
+
const nested = await walkJsonlFiles(path4.join(dir, sessionDirName));
|
|
2306
|
+
const nestedFiles = nested.files;
|
|
2307
|
+
slugJsonlFiles += nestedFiles.length;
|
|
2308
|
+
slugOtherFiles += nested.otherFiles;
|
|
2309
|
+
if (nestedFiles.length === 0) continue;
|
|
2310
|
+
let fold = foldsByKey.get(sessionDirName);
|
|
2311
|
+
if (!fold) {
|
|
2312
|
+
fold = newFold(sessionDirName, slug);
|
|
2313
|
+
foldsByKey.set(sessionDirName, fold);
|
|
2314
|
+
}
|
|
2315
|
+
for (const nestedPath of nestedFiles) {
|
|
2316
|
+
fold.subagentTranscripts += 1;
|
|
2317
|
+
await foldLinesInto(fold, nestedPath, { isSidechain: true });
|
|
2318
|
+
}
|
|
2319
|
+
}
|
|
2320
|
+
if (slugJsonlFiles === 0 && slugOtherFiles > 0) projectsWithNoReadableTranscript += 1;
|
|
2321
|
+
for (const fold of foldsByKey.values()) {
|
|
2322
|
+
if (!fold.firstTs || !fold.lastTs) continue;
|
|
2323
|
+
sessionCount += 1;
|
|
2324
|
+
if (!windowOldest || fold.firstTs < windowOldest) windowOldest = fold.firstTs;
|
|
2325
|
+
if (!windowNewest || fold.lastTs > windowNewest) windowNewest = fold.lastTs;
|
|
2326
|
+
for (const ts of fold.humanPromptTimestamps) {
|
|
2327
|
+
if (!ts) continue;
|
|
2328
|
+
yield {
|
|
2329
|
+
occurredAt: ts,
|
|
2330
|
+
store: "claude_code",
|
|
2331
|
+
sourceVersion: fold.sourceVersion,
|
|
2332
|
+
sessionRef: fold.sessionId,
|
|
2333
|
+
repoRef: fold.cwd ?? fold.projectSlug,
|
|
2334
|
+
eventKind: "ai_prompt_submitted",
|
|
2335
|
+
metrics: {},
|
|
2336
|
+
provenance: HISTORICAL_PROVENANCE.direct,
|
|
2337
|
+
extractionId
|
|
2338
|
+
};
|
|
2339
|
+
}
|
|
2340
|
+
const durationMs = sessionDurationMs(fold);
|
|
2341
|
+
const toolFailureCount = fold.toolResultErrorCount + fold.apiErrorCount;
|
|
2342
|
+
const sessionMetrics = {
|
|
2343
|
+
promptCount: fold.humanPrompts,
|
|
2344
|
+
assistantTurns: fold.assistantTurns,
|
|
2345
|
+
toolResultCount: fold.toolResults,
|
|
2346
|
+
queuedPrompts: fold.queuedPrompts,
|
|
2347
|
+
inputTokens: fold.inputTokens,
|
|
2348
|
+
outputTokens: fold.outputTokens,
|
|
2349
|
+
cacheReadTokens: fold.cacheReadTokens,
|
|
2350
|
+
durationBucket: durationBucketOf(fold),
|
|
2351
|
+
afterHoursPrompts: fold.afterHoursPrompts,
|
|
2352
|
+
unknownLines: fold.unknownLines,
|
|
2353
|
+
unparsedLines: fold.unparsedLines,
|
|
2354
|
+
modelCount: fold.models.size,
|
|
2355
|
+
modelSwitchCount: fold.modelSwitchCount,
|
|
2356
|
+
compactionCount: fold.compactionCount,
|
|
2357
|
+
compactionManualCount: fold.compactionManualCount,
|
|
2358
|
+
compactionAutoCount: fold.compactionAutoCount,
|
|
2359
|
+
toolResultErrorCount: fold.toolResultErrorCount,
|
|
2360
|
+
apiErrorCount: fold.apiErrorCount,
|
|
2361
|
+
toolFailureCount,
|
|
2362
|
+
contextWindowPeakTokens: fold.contextWindowPeakTokens,
|
|
2363
|
+
contextWindowPeakPct: contextWindowPeakPctOf(fold),
|
|
2364
|
+
userModifiedEditCount: fold.userModifiedEditCount,
|
|
2365
|
+
linesChanged: fold.linesChangedTotal,
|
|
2366
|
+
linesChangedBucket: (0, import_tool_kit.bucketLinesChanged)(fold.linesChangedTotal),
|
|
2367
|
+
rapidRepromptCount: rapidRepromptCountOf(fold),
|
|
2368
|
+
abandonedPromptCount: fold.abandonedPromptCount,
|
|
2369
|
+
activeMinutes: activeMinutesOf(fold),
|
|
2370
|
+
subagentTranscripts: fold.subagentTranscripts,
|
|
2371
|
+
subagentPrompts: fold.subagentPrompts,
|
|
2372
|
+
subagentAssistantTurns: fold.subagentAssistantTurns,
|
|
2373
|
+
subagentTokensTotal: fold.subagentTokensTotal
|
|
2374
|
+
};
|
|
2375
|
+
if (durationMs !== null) sessionMetrics.sessionMinutes = Math.round(durationMs / 6e4);
|
|
2376
|
+
sessionMetrics.sessionStartedAt = fold.firstTs;
|
|
2377
|
+
const primaryModel = topModel(fold.models);
|
|
2378
|
+
if (primaryModel) sessionMetrics.primaryModel = primaryModel;
|
|
2379
|
+
if (fold.gitBranch) sessionMetrics.gitBranch = fold.gitBranch;
|
|
2380
|
+
yield {
|
|
2381
|
+
occurredAt: fold.lastTs,
|
|
2382
|
+
store: "claude_code",
|
|
2383
|
+
sourceVersion: fold.sourceVersion,
|
|
2384
|
+
sessionRef: fold.sessionId,
|
|
2385
|
+
repoRef: fold.cwd ?? fold.projectSlug,
|
|
2386
|
+
eventKind: "create_focus_session",
|
|
2387
|
+
metrics: sessionMetrics,
|
|
2388
|
+
// Same gap threshold the session-level activeMinutes uses, passed in
|
|
2389
|
+
// rather than redeclared: two definitions of "active" would drift.
|
|
2390
|
+
dayBreakdown: sliceSessionByLocalDay(fold.humanPromptTimestamps, {
|
|
2391
|
+
activeGapMs: ACTIVE_GAP_MS
|
|
2392
|
+
}),
|
|
2393
|
+
provenance: HISTORICAL_PROVENANCE.derived,
|
|
2394
|
+
extractionId
|
|
2395
|
+
};
|
|
2396
|
+
if (fold.afterHoursPrompts > 0) {
|
|
2397
|
+
yield {
|
|
2398
|
+
occurredAt: fold.lastTs,
|
|
2399
|
+
store: "claude_code",
|
|
2400
|
+
sourceVersion: fold.sourceVersion,
|
|
2401
|
+
sessionRef: fold.sessionId,
|
|
2402
|
+
repoRef: fold.cwd ?? fold.projectSlug,
|
|
2403
|
+
eventKind: "after_hours_ai_session",
|
|
2404
|
+
metrics: { afterHoursPrompts: fold.afterHoursPrompts },
|
|
2405
|
+
provenance: HISTORICAL_PROVENANCE.derived,
|
|
2406
|
+
extractionId
|
|
2407
|
+
};
|
|
2408
|
+
}
|
|
2409
|
+
if (fold.compactionManualCount > 0) {
|
|
2410
|
+
yield {
|
|
2411
|
+
occurredAt: fold.lastTs,
|
|
2412
|
+
store: "claude_code",
|
|
2413
|
+
sourceVersion: fold.sourceVersion,
|
|
2414
|
+
sessionRef: fold.sessionId,
|
|
2415
|
+
repoRef: fold.cwd ?? fold.projectSlug,
|
|
2416
|
+
eventKind: "context_compression_manual",
|
|
2417
|
+
metrics: { compactionCount: fold.compactionManualCount },
|
|
2418
|
+
provenance: HISTORICAL_PROVENANCE.derived,
|
|
2419
|
+
extractionId
|
|
2420
|
+
};
|
|
2421
|
+
}
|
|
2422
|
+
if (fold.compactionAutoCount > 0) {
|
|
2423
|
+
yield {
|
|
2424
|
+
occurredAt: fold.lastTs,
|
|
2425
|
+
store: "claude_code",
|
|
2426
|
+
sourceVersion: fold.sourceVersion,
|
|
2427
|
+
sessionRef: fold.sessionId,
|
|
2428
|
+
repoRef: fold.cwd ?? fold.projectSlug,
|
|
2429
|
+
eventKind: "context_compression_auto",
|
|
2430
|
+
metrics: { compactionCount: fold.compactionAutoCount },
|
|
2431
|
+
provenance: HISTORICAL_PROVENANCE.derived,
|
|
2432
|
+
extractionId
|
|
2433
|
+
};
|
|
2434
|
+
}
|
|
2435
|
+
if (toolFailureCount > 0) {
|
|
2436
|
+
yield {
|
|
2437
|
+
occurredAt: fold.lastTs,
|
|
2438
|
+
store: "claude_code",
|
|
2439
|
+
sourceVersion: fold.sourceVersion,
|
|
2440
|
+
sessionRef: fold.sessionId,
|
|
2441
|
+
repoRef: fold.cwd ?? fold.projectSlug,
|
|
2442
|
+
eventKind: "tool_failure",
|
|
2443
|
+
metrics: {
|
|
2444
|
+
toolFailureCount,
|
|
2445
|
+
toolResultErrorCount: fold.toolResultErrorCount,
|
|
2446
|
+
apiErrorCount: fold.apiErrorCount
|
|
2447
|
+
},
|
|
2448
|
+
provenance: HISTORICAL_PROVENANCE.derived,
|
|
2449
|
+
extractionId
|
|
2450
|
+
};
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
}
|
|
2454
|
+
if (windowOldest && windowNewest || projectsWithNoReadableTranscript > 0) {
|
|
2455
|
+
const window = windowOldest && windowNewest ? { windowOldest, windowNewest } : {};
|
|
2456
|
+
yield {
|
|
2457
|
+
// No window means nothing datable was read; the only honest timestamp
|
|
2458
|
+
// left is when the read happened.
|
|
2459
|
+
occurredAt: windowNewest ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
2460
|
+
store: "claude_code",
|
|
2461
|
+
sourceVersion: null,
|
|
2462
|
+
sessionRef: null,
|
|
2463
|
+
repoRef: null,
|
|
2464
|
+
eventKind: "extraction_epoch",
|
|
2465
|
+
metrics: {
|
|
2466
|
+
...window,
|
|
2467
|
+
sessionCount,
|
|
2468
|
+
projectsWithNoReadableTranscript
|
|
2469
|
+
},
|
|
2470
|
+
provenance: HISTORICAL_PROVENANCE.derived,
|
|
2471
|
+
extractionId
|
|
2472
|
+
};
|
|
2473
|
+
}
|
|
2474
|
+
}
|
|
2475
|
+
|
|
2476
|
+
// src/extractors/cursor.ts
|
|
2477
|
+
var import_tool_kit2 = __toESM(require_out2(), 1);
|
|
2478
|
+
import * as fs5 from "node:fs/promises";
|
|
2479
|
+
import * as path5 from "node:path";
|
|
2480
|
+
import { execFile as execFile2 } from "node:child_process";
|
|
2481
|
+
import { promisify as promisify2 } from "node:util";
|
|
2482
|
+
var execFileAsync2 = promisify2(execFile2);
|
|
2483
|
+
var KNOWN_CURSOR_BUBBLE_VERSIONS = /* @__PURE__ */ new Set([3]);
|
|
2484
|
+
var KNOWN_CURSOR_HEADER_TYPES = /* @__PURE__ */ new Set(["head"]);
|
|
2485
|
+
var BUBBLE_TYPE_USER = 1;
|
|
2486
|
+
var BUBBLE_TYPE_ASSISTANT = 2;
|
|
2487
|
+
function asNumber2(value) {
|
|
2488
|
+
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
2489
|
+
}
|
|
2490
|
+
function pickTop(counts) {
|
|
2491
|
+
let best = null;
|
|
2492
|
+
let bestCount = 0;
|
|
2493
|
+
for (const [key, count] of counts) {
|
|
2494
|
+
if (count > bestCount) {
|
|
2495
|
+
best = key;
|
|
2496
|
+
bestCount = count;
|
|
2497
|
+
}
|
|
2498
|
+
}
|
|
2499
|
+
return best;
|
|
2500
|
+
}
|
|
2501
|
+
var MAX_QUERY_BUFFER = 256 * 1024 * 1024;
|
|
2502
|
+
async function queryJson(dbPath, sql) {
|
|
2503
|
+
const { stdout } = await execFileAsync2("sqlite3", ["-json", dbPath, sql], {
|
|
2504
|
+
maxBuffer: MAX_QUERY_BUFFER
|
|
2505
|
+
});
|
|
2506
|
+
const trimmed = stdout.trim();
|
|
2507
|
+
if (trimmed === "") return [];
|
|
2508
|
+
return JSON.parse(trimmed);
|
|
2509
|
+
}
|
|
2510
|
+
var HEADER_QUERY = `
|
|
2511
|
+
SELECT
|
|
2512
|
+
composerId,
|
|
2513
|
+
createdAt,
|
|
2514
|
+
lastUpdatedAt,
|
|
2515
|
+
recency,
|
|
2516
|
+
checkpointAt,
|
|
2517
|
+
isSubagent,
|
|
2518
|
+
json_extract(value,'$.type') AS recordType,
|
|
2519
|
+
json_extract(value,'$.totalLinesAdded') AS linesAdded,
|
|
2520
|
+
json_extract(value,'$.totalLinesRemoved') AS linesRemoved,
|
|
2521
|
+
json_extract(value,'$.filesChangedCount') AS filesChangedCount,
|
|
2522
|
+
json_extract(value,'$.contextUsagePercent') AS contextUsagePercent,
|
|
2523
|
+
json_extract(value,'$.unifiedMode') AS mode,
|
|
2524
|
+
json_extract(value,'$.trackedGitRepos') AS trackedGitReposJson,
|
|
2525
|
+
json_extract(value,'$.workspaceIdentifier.uri.fsPath') AS workspaceFsPath,
|
|
2526
|
+
json_extract(value,'$.subagentInfo.parentComposerId') AS parentComposerId
|
|
2527
|
+
FROM composerHeaders;
|
|
2528
|
+
`;
|
|
2529
|
+
var BUBBLE_QUERY = `
|
|
2530
|
+
SELECT
|
|
2531
|
+
key,
|
|
2532
|
+
json_extract(value,'$._v') AS v,
|
|
2533
|
+
json_extract(value,'$.type') AS btype,
|
|
2534
|
+
json_extract(value,'$.createdAt') AS createdAt,
|
|
2535
|
+
json_extract(value,'$.tokenCount.inputTokens') AS inputTokens,
|
|
2536
|
+
json_extract(value,'$.tokenCount.outputTokens') AS outputTokens,
|
|
2537
|
+
coalesce(json_array_length(value,'$.humanChanges'),0) AS humanChangesCount,
|
|
2538
|
+
coalesce(json_array_length(value,'$.approximateLintErrors'),0) AS lintErrorsCount,
|
|
2539
|
+
json_extract(value,'$.modelInfo.modelName') AS modelName
|
|
2540
|
+
FROM cursorDiskKV WHERE key LIKE 'bubbleId:%';
|
|
2541
|
+
`;
|
|
2542
|
+
function repoInfoOf(row) {
|
|
2543
|
+
let repoRef = null;
|
|
2544
|
+
let gitBranch = null;
|
|
2545
|
+
if (row.trackedGitReposJson) {
|
|
2546
|
+
try {
|
|
2547
|
+
const repos = JSON.parse(row.trackedGitReposJson);
|
|
2548
|
+
if (Array.isArray(repos) && repos.length > 0) {
|
|
2549
|
+
const first = repos[0];
|
|
2550
|
+
if (typeof first.repoPath === "string") repoRef = first.repoPath;
|
|
2551
|
+
const branches = first.branches;
|
|
2552
|
+
if (Array.isArray(branches) && branches.length > 0) {
|
|
2553
|
+
const branch = branches[0];
|
|
2554
|
+
if (typeof branch.branchName === "string") gitBranch = branch.branchName;
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
} catch {
|
|
2558
|
+
}
|
|
2559
|
+
}
|
|
2560
|
+
if (!repoRef && row.workspaceFsPath) repoRef = row.workspaceFsPath;
|
|
2561
|
+
return { repoRef, gitBranch };
|
|
2562
|
+
}
|
|
2563
|
+
function sniffCursorHeaderRow(row) {
|
|
2564
|
+
if (row.recordType === null) return { kind: "unparsed" };
|
|
2565
|
+
if (!KNOWN_CURSOR_HEADER_TYPES.has(row.recordType)) {
|
|
2566
|
+
return { kind: "unknown", type: row.recordType };
|
|
2567
|
+
}
|
|
2568
|
+
const { repoRef, gitBranch } = repoInfoOf(row);
|
|
2569
|
+
return {
|
|
2570
|
+
kind: "head",
|
|
2571
|
+
linesAdded: asNumber2(row.linesAdded),
|
|
2572
|
+
linesRemoved: asNumber2(row.linesRemoved),
|
|
2573
|
+
filesChangedCount: asNumber2(row.filesChangedCount),
|
|
2574
|
+
contextUsagePercent: typeof row.contextUsagePercent === "number" ? row.contextUsagePercent : null,
|
|
2575
|
+
mode: row.mode,
|
|
2576
|
+
repoRef,
|
|
2577
|
+
gitBranch,
|
|
2578
|
+
isSubagent: row.isSubagent === 1,
|
|
2579
|
+
parentComposerId: row.parentComposerId
|
|
2580
|
+
};
|
|
2581
|
+
}
|
|
2582
|
+
function sniffCursorBubbleRow(row) {
|
|
2583
|
+
const parts = row.key.split(":");
|
|
2584
|
+
const composerId = parts.length >= 3 && parts[0] === "bubbleId" ? parts[1] : null;
|
|
2585
|
+
if (row.v === null || !KNOWN_CURSOR_BUBBLE_VERSIONS.has(row.v) || composerId === null) {
|
|
2586
|
+
return { kind: "unparsed", composerId };
|
|
2587
|
+
}
|
|
2588
|
+
if (row.btype !== BUBBLE_TYPE_USER && row.btype !== BUBBLE_TYPE_ASSISTANT) {
|
|
2589
|
+
return { kind: "unknown", composerId };
|
|
2590
|
+
}
|
|
2591
|
+
return {
|
|
2592
|
+
kind: row.btype === BUBBLE_TYPE_USER ? "user" : "assistant",
|
|
2593
|
+
composerId,
|
|
2594
|
+
createdAt: row.createdAt,
|
|
2595
|
+
inputTokens: asNumber2(row.inputTokens),
|
|
2596
|
+
outputTokens: asNumber2(row.outputTokens),
|
|
2597
|
+
humanChangesCount: asNumber2(row.humanChangesCount),
|
|
2598
|
+
lintErrorsCount: asNumber2(row.lintErrorsCount),
|
|
2599
|
+
modelName: row.modelName,
|
|
2600
|
+
sourceVersion: String(row.v)
|
|
2601
|
+
};
|
|
2602
|
+
}
|
|
2603
|
+
function newFold2(row, sniffed) {
|
|
2604
|
+
return {
|
|
2605
|
+
composerId: row.composerId,
|
|
2606
|
+
createdAtMs: Number.isFinite(row.createdAt) ? row.createdAt : null,
|
|
2607
|
+
lastUpdatedAtMs: Number.isFinite(row.lastUpdatedAt) ? row.lastUpdatedAt : null,
|
|
2608
|
+
recencyMs: Number.isFinite(row.recency) ? row.recency : null,
|
|
2609
|
+
checkpointAtMs: Number.isFinite(row.checkpointAt) ? row.checkpointAt : null,
|
|
2610
|
+
lastBubbleMs: null,
|
|
2611
|
+
mode: sniffed.mode,
|
|
2612
|
+
linesAdded: sniffed.linesAdded,
|
|
2613
|
+
linesRemoved: sniffed.linesRemoved,
|
|
2614
|
+
filesChangedCount: sniffed.filesChangedCount,
|
|
2615
|
+
contextUsagePercent: sniffed.contextUsagePercent,
|
|
2616
|
+
repoRef: sniffed.repoRef,
|
|
2617
|
+
gitBranch: sniffed.gitBranch,
|
|
2618
|
+
humanPrompts: 0,
|
|
2619
|
+
afterHoursPrompts: 0,
|
|
2620
|
+
assistantTurns: 0,
|
|
2621
|
+
inputTokens: 0,
|
|
2622
|
+
outputTokens: 0,
|
|
2623
|
+
humanChangesCount: 0,
|
|
2624
|
+
lintErrorsCount: 0,
|
|
2625
|
+
unknownBubbles: 0,
|
|
2626
|
+
unparsedBubbles: 0,
|
|
2627
|
+
ownBubbles: 0,
|
|
2628
|
+
models: /* @__PURE__ */ new Map(),
|
|
2629
|
+
bubbleVersions: /* @__PURE__ */ new Map(),
|
|
2630
|
+
humanPromptTimestamps: [],
|
|
2631
|
+
subagentComposerIds: /* @__PURE__ */ new Set(),
|
|
2632
|
+
subagentPromptCount: 0,
|
|
2633
|
+
subagentAssistantTurns: 0,
|
|
2634
|
+
subagentTokensTotal: 0
|
|
2635
|
+
};
|
|
2636
|
+
}
|
|
2637
|
+
function resolveTimeline(fold) {
|
|
2638
|
+
if (fold.createdAtMs === null) return null;
|
|
2639
|
+
if (fold.lastUpdatedAtMs !== null) return { endedAtMs: fold.lastUpdatedAtMs, source: "header" };
|
|
2640
|
+
const candidates = [
|
|
2641
|
+
[fold.lastBubbleMs, "bubbles"],
|
|
2642
|
+
[fold.recencyMs, "recency"],
|
|
2643
|
+
[fold.checkpointAtMs, "checkpoint"]
|
|
2644
|
+
];
|
|
2645
|
+
for (const [ms, source] of candidates) {
|
|
2646
|
+
if (ms !== null && Number.isFinite(ms) && ms >= fold.createdAtMs) {
|
|
2647
|
+
return { endedAtMs: ms, source };
|
|
2648
|
+
}
|
|
2649
|
+
}
|
|
2650
|
+
return null;
|
|
2651
|
+
}
|
|
2652
|
+
function sessionDurationMs2(fold) {
|
|
2653
|
+
const timeline = resolveTimeline(fold);
|
|
2654
|
+
if (timeline === null) return null;
|
|
2655
|
+
const ms = timeline.endedAtMs - fold.createdAtMs;
|
|
2656
|
+
return Number.isFinite(ms) && ms >= 0 ? ms : null;
|
|
2657
|
+
}
|
|
2658
|
+
function durationBucketOf2(fold) {
|
|
2659
|
+
const ms = sessionDurationMs2(fold);
|
|
2660
|
+
if (ms === null) return "unknown";
|
|
2661
|
+
return (0, import_tool_kit2.bucketDurationMs)(ms) ?? "unknown";
|
|
2662
|
+
}
|
|
2663
|
+
async function* extractCursor(snapshotDir, extractionId) {
|
|
2664
|
+
const dbPath = path5.join(snapshotDir, "state.vscdb");
|
|
2665
|
+
try {
|
|
2666
|
+
await fs5.stat(dbPath);
|
|
2667
|
+
} catch {
|
|
2668
|
+
return;
|
|
2669
|
+
}
|
|
2670
|
+
let headerRows;
|
|
2671
|
+
try {
|
|
2672
|
+
headerRows = await queryJson(dbPath, HEADER_QUERY);
|
|
2673
|
+
} catch {
|
|
2674
|
+
yield {
|
|
2675
|
+
occurredAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2676
|
+
store: "cursor",
|
|
2677
|
+
sourceVersion: null,
|
|
2678
|
+
sessionRef: null,
|
|
2679
|
+
repoRef: null,
|
|
2680
|
+
eventKind: "extraction_epoch",
|
|
2681
|
+
metrics: {
|
|
2682
|
+
sessionCount: 0,
|
|
2683
|
+
schemaUnreadable: 1
|
|
2684
|
+
},
|
|
2685
|
+
provenance: HISTORICAL_PROVENANCE.derived,
|
|
2686
|
+
extractionId
|
|
2687
|
+
};
|
|
2688
|
+
return;
|
|
2689
|
+
}
|
|
2690
|
+
const folds = /* @__PURE__ */ new Map();
|
|
2691
|
+
const subagentParentOf = /* @__PURE__ */ new Map();
|
|
2692
|
+
let unparsedComposerHeaders = 0;
|
|
2693
|
+
let unknownComposerHeaderTypes = 0;
|
|
2694
|
+
for (const row of headerRows) {
|
|
2695
|
+
const sniffed = sniffCursorHeaderRow(row);
|
|
2696
|
+
if (sniffed.kind === "unparsed") {
|
|
2697
|
+
unparsedComposerHeaders += 1;
|
|
2698
|
+
continue;
|
|
2699
|
+
}
|
|
2700
|
+
if (sniffed.kind === "unknown") {
|
|
2701
|
+
unknownComposerHeaderTypes += 1;
|
|
2702
|
+
continue;
|
|
2703
|
+
}
|
|
2704
|
+
if (sniffed.isSubagent) {
|
|
2705
|
+
if (sniffed.parentComposerId) subagentParentOf.set(row.composerId, sniffed.parentComposerId);
|
|
2706
|
+
continue;
|
|
2707
|
+
}
|
|
2708
|
+
folds.set(row.composerId, newFold2(row, sniffed));
|
|
2709
|
+
}
|
|
2710
|
+
let bubbleRows = [];
|
|
2711
|
+
try {
|
|
2712
|
+
bubbleRows = await queryJson(dbPath, BUBBLE_QUERY);
|
|
2713
|
+
} catch {
|
|
2714
|
+
}
|
|
2715
|
+
let orphanedSubagentBubbles = 0;
|
|
2716
|
+
let orphanedBubbles = 0;
|
|
2717
|
+
for (const row of bubbleRows) {
|
|
2718
|
+
const sniffed = sniffCursorBubbleRow(row);
|
|
2719
|
+
if (sniffed.kind === "unparsed") {
|
|
2720
|
+
const fold = sniffed.composerId ? folds.get(sniffed.composerId) : void 0;
|
|
2721
|
+
if (fold) {
|
|
2722
|
+
fold.unparsedBubbles += 1;
|
|
2723
|
+
fold.ownBubbles += 1;
|
|
2724
|
+
} else if (sniffed.composerId && subagentParentOf.has(sniffed.composerId)) {
|
|
2725
|
+
const parent = folds.get(subagentParentOf.get(sniffed.composerId));
|
|
2726
|
+
if (parent) parent.unparsedBubbles += 1;
|
|
2727
|
+
} else orphanedBubbles += 1;
|
|
2728
|
+
continue;
|
|
2729
|
+
}
|
|
2730
|
+
const ownFold = folds.get(sniffed.composerId);
|
|
2731
|
+
if (ownFold) {
|
|
2732
|
+
ownFold.ownBubbles += 1;
|
|
2733
|
+
if (sniffed.kind === "unknown") {
|
|
2734
|
+
ownFold.unknownBubbles += 1;
|
|
2735
|
+
continue;
|
|
2736
|
+
}
|
|
2737
|
+
ownFold.bubbleVersions.set(
|
|
2738
|
+
sniffed.sourceVersion,
|
|
2739
|
+
(ownFold.bubbleVersions.get(sniffed.sourceVersion) ?? 0) + 1
|
|
2740
|
+
);
|
|
2741
|
+
ownFold.inputTokens += sniffed.inputTokens;
|
|
2742
|
+
ownFold.outputTokens += sniffed.outputTokens;
|
|
2743
|
+
ownFold.humanChangesCount += sniffed.humanChangesCount;
|
|
2744
|
+
ownFold.lintErrorsCount += sniffed.lintErrorsCount;
|
|
2745
|
+
if (sniffed.modelName) {
|
|
2746
|
+
ownFold.models.set(sniffed.modelName, (ownFold.models.get(sniffed.modelName) ?? 0) + 1);
|
|
2747
|
+
}
|
|
2748
|
+
if (sniffed.createdAt) {
|
|
2749
|
+
const ms = Date.parse(sniffed.createdAt);
|
|
2750
|
+
if (Number.isFinite(ms) && (ownFold.lastBubbleMs === null || ms > ownFold.lastBubbleMs)) {
|
|
2751
|
+
ownFold.lastBubbleMs = ms;
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
if (sniffed.kind === "user") {
|
|
2755
|
+
ownFold.humanPrompts += 1;
|
|
2756
|
+
if (sniffed.createdAt && (0, import_tool_kit2.isAfterHours)(new Date(sniffed.createdAt))) {
|
|
2757
|
+
ownFold.afterHoursPrompts += 1;
|
|
2758
|
+
}
|
|
2759
|
+
ownFold.humanPromptTimestamps.push(sniffed.createdAt);
|
|
2760
|
+
} else {
|
|
2761
|
+
ownFold.assistantTurns += 1;
|
|
2762
|
+
}
|
|
2763
|
+
continue;
|
|
2764
|
+
}
|
|
2765
|
+
const parentId = subagentParentOf.get(sniffed.composerId);
|
|
2766
|
+
const parentFold = parentId ? folds.get(parentId) : void 0;
|
|
2767
|
+
if (!parentFold) {
|
|
2768
|
+
orphanedSubagentBubbles += parentId ? 1 : 0;
|
|
2769
|
+
orphanedBubbles += parentId ? 0 : 1;
|
|
2770
|
+
continue;
|
|
2771
|
+
}
|
|
2772
|
+
if (sniffed.kind === "unknown") {
|
|
2773
|
+
parentFold.unknownBubbles += 1;
|
|
2774
|
+
continue;
|
|
2775
|
+
}
|
|
2776
|
+
parentFold.subagentComposerIds.add(sniffed.composerId);
|
|
2777
|
+
parentFold.subagentTokensTotal += sniffed.inputTokens + sniffed.outputTokens;
|
|
2778
|
+
if (sniffed.kind === "user") {
|
|
2779
|
+
parentFold.subagentPromptCount += 1;
|
|
2780
|
+
} else {
|
|
2781
|
+
parentFold.subagentAssistantTurns += 1;
|
|
2782
|
+
}
|
|
2783
|
+
}
|
|
2784
|
+
let windowOldest = null;
|
|
2785
|
+
let windowNewest = null;
|
|
2786
|
+
let sessionCount = 0;
|
|
2787
|
+
const derivedTimelines = /* @__PURE__ */ new Map();
|
|
2788
|
+
let sessionsWithoutTimeline = 0;
|
|
2789
|
+
let emptyComposers = 0;
|
|
2790
|
+
for (const composerId of [...folds.keys()].sort()) {
|
|
2791
|
+
const fold = folds.get(composerId);
|
|
2792
|
+
if (fold.ownBubbles === 0 && fold.subagentComposerIds.size === 0) {
|
|
2793
|
+
emptyComposers += 1;
|
|
2794
|
+
continue;
|
|
2795
|
+
}
|
|
2796
|
+
const timeline = resolveTimeline(fold);
|
|
2797
|
+
if (timeline === null) {
|
|
2798
|
+
sessionsWithoutTimeline += 1;
|
|
2799
|
+
continue;
|
|
2800
|
+
}
|
|
2801
|
+
if (timeline.source !== "header") {
|
|
2802
|
+
derivedTimelines.set(timeline.source, (derivedTimelines.get(timeline.source) ?? 0) + 1);
|
|
2803
|
+
}
|
|
2804
|
+
const startedAt = new Date(fold.createdAtMs).toISOString();
|
|
2805
|
+
const endedAt = new Date(timeline.endedAtMs).toISOString();
|
|
2806
|
+
sessionCount += 1;
|
|
2807
|
+
if (!windowOldest || startedAt < windowOldest) windowOldest = startedAt;
|
|
2808
|
+
if (!windowNewest || endedAt > windowNewest) windowNewest = endedAt;
|
|
2809
|
+
const sourceVersion = pickTop(fold.bubbleVersions);
|
|
2810
|
+
for (const ts of fold.humanPromptTimestamps) {
|
|
2811
|
+
if (!ts) continue;
|
|
2812
|
+
yield {
|
|
2813
|
+
occurredAt: ts,
|
|
2814
|
+
store: "cursor",
|
|
2815
|
+
sourceVersion,
|
|
2816
|
+
sessionRef: fold.composerId,
|
|
2817
|
+
repoRef: fold.repoRef,
|
|
2818
|
+
eventKind: "ai_prompt_submitted",
|
|
2819
|
+
metrics: {},
|
|
2820
|
+
provenance: HISTORICAL_PROVENANCE.direct,
|
|
2821
|
+
extractionId
|
|
2822
|
+
};
|
|
2823
|
+
}
|
|
2824
|
+
const durationMs = sessionDurationMs2(fold);
|
|
2825
|
+
const sessionMetrics = {
|
|
2826
|
+
promptCount: fold.humanPrompts,
|
|
2827
|
+
assistantTurns: fold.assistantTurns,
|
|
2828
|
+
inputTokens: fold.inputTokens,
|
|
2829
|
+
outputTokens: fold.outputTokens,
|
|
2830
|
+
linesAdded: fold.linesAdded,
|
|
2831
|
+
linesRemoved: fold.linesRemoved,
|
|
2832
|
+
filesChangedCount: fold.filesChangedCount,
|
|
2833
|
+
humanChangesCount: fold.humanChangesCount,
|
|
2834
|
+
approximateLintErrorsCount: fold.lintErrorsCount,
|
|
2835
|
+
durationBucket: durationBucketOf2(fold),
|
|
2836
|
+
afterHoursPrompts: fold.afterHoursPrompts,
|
|
2837
|
+
unknownBubbles: fold.unknownBubbles,
|
|
2838
|
+
unparsedBubbles: fold.unparsedBubbles,
|
|
2839
|
+
modelCount: fold.models.size,
|
|
2840
|
+
subagentComposers: fold.subagentComposerIds.size,
|
|
2841
|
+
subagentPrompts: fold.subagentPromptCount,
|
|
2842
|
+
subagentAssistantTurns: fold.subagentAssistantTurns,
|
|
2843
|
+
subagentTokensTotal: fold.subagentTokensTotal,
|
|
2844
|
+
sessionStartedAt: startedAt
|
|
2845
|
+
};
|
|
2846
|
+
if (durationMs !== null) sessionMetrics.sessionMinutes = Math.round(durationMs / 6e4);
|
|
2847
|
+
if (fold.mode) sessionMetrics.mode = fold.mode;
|
|
2848
|
+
if (fold.contextUsagePercent !== null) {
|
|
2849
|
+
sessionMetrics.contextUsagePercent = Math.round(fold.contextUsagePercent * 100) / 100;
|
|
2850
|
+
}
|
|
2851
|
+
const primaryModel = pickTop(fold.models);
|
|
2852
|
+
if (primaryModel) sessionMetrics.primaryModel = primaryModel;
|
|
2853
|
+
if (fold.gitBranch) sessionMetrics.gitBranch = fold.gitBranch;
|
|
2854
|
+
yield {
|
|
2855
|
+
occurredAt: endedAt,
|
|
2856
|
+
store: "cursor",
|
|
2857
|
+
sourceVersion,
|
|
2858
|
+
sessionRef: fold.composerId,
|
|
2859
|
+
repoRef: fold.repoRef,
|
|
2860
|
+
eventKind: "create_focus_session",
|
|
2861
|
+
metrics: sessionMetrics,
|
|
2862
|
+
dayBreakdown: sliceSessionByLocalDay(fold.humanPromptTimestamps),
|
|
2863
|
+
provenance: HISTORICAL_PROVENANCE.derived,
|
|
2864
|
+
extractionId
|
|
2865
|
+
};
|
|
2866
|
+
if (fold.afterHoursPrompts > 0) {
|
|
2867
|
+
yield {
|
|
2868
|
+
occurredAt: endedAt,
|
|
2869
|
+
store: "cursor",
|
|
2870
|
+
sourceVersion,
|
|
2871
|
+
sessionRef: fold.composerId,
|
|
2872
|
+
repoRef: fold.repoRef,
|
|
2873
|
+
eventKind: "after_hours_ai_session",
|
|
2874
|
+
metrics: { afterHoursPrompts: fold.afterHoursPrompts },
|
|
2875
|
+
provenance: HISTORICAL_PROVENANCE.derived,
|
|
2876
|
+
extractionId
|
|
2877
|
+
};
|
|
2878
|
+
}
|
|
2879
|
+
}
|
|
2880
|
+
const readFailures = unparsedComposerHeaders + unknownComposerHeaderTypes + orphanedSubagentBubbles + orphanedBubbles + sessionsWithoutTimeline;
|
|
2881
|
+
if (windowOldest && windowNewest || readFailures > 0) {
|
|
2882
|
+
const window = windowOldest && windowNewest ? { windowOldest, windowNewest } : {};
|
|
2883
|
+
yield {
|
|
2884
|
+
occurredAt: windowNewest ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
2885
|
+
store: "cursor",
|
|
2886
|
+
sourceVersion: null,
|
|
2887
|
+
sessionRef: null,
|
|
2888
|
+
repoRef: null,
|
|
2889
|
+
eventKind: "extraction_epoch",
|
|
2890
|
+
metrics: {
|
|
2891
|
+
...window,
|
|
2892
|
+
sessionCount,
|
|
2893
|
+
schemaUnreadable: 0,
|
|
2894
|
+
unparsedComposerHeaders,
|
|
2895
|
+
unknownComposerHeaderTypes,
|
|
2896
|
+
orphanedSubagentBubbles,
|
|
2897
|
+
orphanedBubbles,
|
|
2898
|
+
sessionsWithoutTimeline,
|
|
2899
|
+
emptyComposers,
|
|
2900
|
+
sessionsFromBubbleTimeline: derivedTimelines.get("bubbles") ?? 0,
|
|
2901
|
+
sessionsFromRecencyTimeline: derivedTimelines.get("recency") ?? 0,
|
|
2902
|
+
sessionsFromCheckpointTimeline: derivedTimelines.get("checkpoint") ?? 0
|
|
2903
|
+
},
|
|
2904
|
+
provenance: HISTORICAL_PROVENANCE.derived,
|
|
2905
|
+
extractionId
|
|
2906
|
+
};
|
|
2907
|
+
}
|
|
2908
|
+
}
|
|
2909
|
+
|
|
2910
|
+
// src/extractors/vscode.ts
|
|
2911
|
+
var import_tool_kit3 = __toESM(require_out2(), 1);
|
|
2912
|
+
import * as fs6 from "node:fs/promises";
|
|
2913
|
+
import * as path6 from "node:path";
|
|
2914
|
+
import { fileURLToPath } from "node:url";
|
|
2915
|
+
var KNOWN_HISTORY_VERSION = 1;
|
|
2916
|
+
var KNOWN_CHAT_SESSION_VERSION = 3;
|
|
2917
|
+
var CHAT_EDIT_PREFIX = "Chat Edit:";
|
|
2918
|
+
function uriToPath(uri) {
|
|
2919
|
+
if (typeof uri !== "string" || !uri.startsWith("file://")) return null;
|
|
2920
|
+
try {
|
|
2921
|
+
return fileURLToPath(uri);
|
|
2922
|
+
} catch {
|
|
2923
|
+
return null;
|
|
2924
|
+
}
|
|
2925
|
+
}
|
|
2926
|
+
async function buildWorkspaceFolderIndex(workspaceStorageDir) {
|
|
2927
|
+
let hashDirs = [];
|
|
2928
|
+
try {
|
|
2929
|
+
hashDirs = (await fs6.readdir(workspaceStorageDir, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
2930
|
+
} catch {
|
|
2931
|
+
return [];
|
|
2932
|
+
}
|
|
2933
|
+
const folders = [];
|
|
2934
|
+
for (const hash of hashDirs) {
|
|
2935
|
+
let raw;
|
|
2936
|
+
try {
|
|
2937
|
+
raw = await fs6.readFile(path6.join(workspaceStorageDir, hash, "workspace.json"), "utf8");
|
|
2938
|
+
} catch {
|
|
2939
|
+
continue;
|
|
2940
|
+
}
|
|
2941
|
+
let parsed;
|
|
2942
|
+
try {
|
|
2943
|
+
parsed = JSON.parse(raw);
|
|
2944
|
+
} catch {
|
|
2945
|
+
continue;
|
|
2946
|
+
}
|
|
2947
|
+
if (typeof parsed !== "object" || parsed === null) continue;
|
|
2948
|
+
const folderPath = uriToPath(parsed.folder);
|
|
2949
|
+
if (folderPath) folders.push({ folderPath });
|
|
2950
|
+
}
|
|
2951
|
+
return folders.sort((a, b) => b.folderPath.length - a.folderPath.length);
|
|
2952
|
+
}
|
|
2953
|
+
function workspaceRootOf(filePath, index) {
|
|
2954
|
+
for (const folder of index) {
|
|
2955
|
+
const withSlash = folder.folderPath.endsWith("/") ? folder.folderPath : `${folder.folderPath}/`;
|
|
2956
|
+
if (filePath === folder.folderPath || filePath.startsWith(withSlash)) {
|
|
2957
|
+
return folder.folderPath;
|
|
2958
|
+
}
|
|
2959
|
+
}
|
|
2960
|
+
return path6.dirname(filePath);
|
|
2961
|
+
}
|
|
2962
|
+
function sniffHistoryFile(raw) {
|
|
2963
|
+
let parsed;
|
|
2964
|
+
try {
|
|
2965
|
+
parsed = JSON.parse(raw);
|
|
2966
|
+
} catch {
|
|
2967
|
+
return null;
|
|
2968
|
+
}
|
|
2969
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
2970
|
+
const record = parsed;
|
|
2971
|
+
if (record.version !== KNOWN_HISTORY_VERSION) return null;
|
|
2972
|
+
if (typeof record.resource !== "string" || !Array.isArray(record.entries)) return null;
|
|
2973
|
+
return { resource: record.resource, entries: record.entries };
|
|
2974
|
+
}
|
|
2975
|
+
async function foldEditDays(historyDir, index) {
|
|
2976
|
+
const result = {
|
|
2977
|
+
days: /* @__PURE__ */ new Map(),
|
|
2978
|
+
unparsedFiles: 0,
|
|
2979
|
+
malformedEntries: 0,
|
|
2980
|
+
oldest: null,
|
|
2981
|
+
newest: null
|
|
2982
|
+
};
|
|
2983
|
+
let hashDirs = [];
|
|
2984
|
+
try {
|
|
2985
|
+
hashDirs = (await fs6.readdir(historyDir, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
2986
|
+
} catch {
|
|
2987
|
+
return result;
|
|
2988
|
+
}
|
|
2989
|
+
for (const hash of hashDirs.sort()) {
|
|
2990
|
+
let raw;
|
|
2991
|
+
try {
|
|
2992
|
+
raw = await fs6.readFile(path6.join(historyDir, hash, "entries.json"), "utf8");
|
|
2993
|
+
} catch {
|
|
2994
|
+
continue;
|
|
2995
|
+
}
|
|
2996
|
+
const sniffed = sniffHistoryFile(raw);
|
|
2997
|
+
if (!sniffed) {
|
|
2998
|
+
result.unparsedFiles += 1;
|
|
2999
|
+
continue;
|
|
3000
|
+
}
|
|
3001
|
+
const resourcePath = uriToPath(sniffed.resource);
|
|
3002
|
+
if (!resourcePath) {
|
|
3003
|
+
result.unparsedFiles += 1;
|
|
3004
|
+
continue;
|
|
3005
|
+
}
|
|
3006
|
+
const workspaceRoot = workspaceRootOf(resourcePath, index);
|
|
3007
|
+
for (const entry of sniffed.entries) {
|
|
3008
|
+
const ts = typeof entry.timestamp === "number" && Number.isFinite(entry.timestamp) ? entry.timestamp : null;
|
|
3009
|
+
if (ts === null) {
|
|
3010
|
+
result.malformedEntries += 1;
|
|
3011
|
+
continue;
|
|
3012
|
+
}
|
|
3013
|
+
const iso = new Date(ts).toISOString();
|
|
3014
|
+
const date = iso.slice(0, 10);
|
|
3015
|
+
if (!result.oldest || iso < result.oldest) result.oldest = iso;
|
|
3016
|
+
if (!result.newest || iso > result.newest) result.newest = iso;
|
|
3017
|
+
const key = `${date}\0${workspaceRoot}`;
|
|
3018
|
+
let fold = result.days.get(key);
|
|
3019
|
+
if (!fold) {
|
|
3020
|
+
fold = { date, workspaceRoot, chatEditCount: 0, totalEntryCount: 0, lastTs: iso };
|
|
3021
|
+
result.days.set(key, fold);
|
|
3022
|
+
}
|
|
3023
|
+
fold.totalEntryCount += 1;
|
|
3024
|
+
if (iso > fold.lastTs) fold.lastTs = iso;
|
|
3025
|
+
if (typeof entry.source === "string" && entry.source.startsWith(CHAT_EDIT_PREFIX)) {
|
|
3026
|
+
fold.chatEditCount += 1;
|
|
3027
|
+
}
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
return result;
|
|
3031
|
+
}
|
|
3032
|
+
var JSONL_KIND_HEADER = 0;
|
|
3033
|
+
var JSONL_KIND_SET = 1;
|
|
3034
|
+
var JSONL_KIND_SPLICE = 2;
|
|
3035
|
+
function resolvePath(root, path9) {
|
|
3036
|
+
let cur = root;
|
|
3037
|
+
for (const seg of path9) {
|
|
3038
|
+
if (typeof seg === "number") {
|
|
3039
|
+
if (!Array.isArray(cur) || seg >= cur.length) return null;
|
|
3040
|
+
cur = cur[seg];
|
|
3041
|
+
} else {
|
|
3042
|
+
if (typeof cur !== "object" || cur === null || Array.isArray(cur)) return null;
|
|
3043
|
+
cur = cur[seg];
|
|
3044
|
+
}
|
|
3045
|
+
}
|
|
3046
|
+
return cur ?? null;
|
|
3047
|
+
}
|
|
3048
|
+
function applySet(root, path9, value) {
|
|
3049
|
+
if (path9.length === 0) return;
|
|
3050
|
+
const parent = resolvePath(root, path9.slice(0, -1));
|
|
3051
|
+
const last = path9[path9.length - 1];
|
|
3052
|
+
if (typeof last === "number") {
|
|
3053
|
+
if (!Array.isArray(parent)) return;
|
|
3054
|
+
while (parent.length <= last) parent.push(null);
|
|
3055
|
+
parent[last] = value;
|
|
3056
|
+
} else {
|
|
3057
|
+
if (typeof parent !== "object" || parent === null || Array.isArray(parent)) return;
|
|
3058
|
+
parent[last] = value;
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
3061
|
+
function applySplice(root, path9, values, index) {
|
|
3062
|
+
const target = resolvePath(root, path9);
|
|
3063
|
+
if (!Array.isArray(target)) return;
|
|
3064
|
+
if (index !== null && index >= 0 && index <= target.length) target.splice(index, 0, ...values);
|
|
3065
|
+
else target.push(...values);
|
|
3066
|
+
}
|
|
3067
|
+
function parseJsonlChatSession(raw) {
|
|
3068
|
+
let malformedLines = 0;
|
|
3069
|
+
let record = null;
|
|
3070
|
+
for (const line of raw.split("\n")) {
|
|
3071
|
+
const trimmed = line.trim();
|
|
3072
|
+
if (trimmed === "") continue;
|
|
3073
|
+
let parsed;
|
|
3074
|
+
try {
|
|
3075
|
+
parsed = JSON.parse(trimmed);
|
|
3076
|
+
} catch {
|
|
3077
|
+
malformedLines += 1;
|
|
3078
|
+
continue;
|
|
3079
|
+
}
|
|
3080
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
3081
|
+
malformedLines += 1;
|
|
3082
|
+
continue;
|
|
3083
|
+
}
|
|
3084
|
+
const delta = parsed;
|
|
3085
|
+
if (delta.kind === JSONL_KIND_HEADER) {
|
|
3086
|
+
if (record === null && typeof delta.v === "object" && delta.v !== null) {
|
|
3087
|
+
record = delta.v;
|
|
3088
|
+
}
|
|
3089
|
+
continue;
|
|
3090
|
+
}
|
|
3091
|
+
if (record === null || !Array.isArray(delta.k)) continue;
|
|
3092
|
+
const keyPath = delta.k.filter(
|
|
3093
|
+
(seg) => typeof seg === "string" || typeof seg === "number"
|
|
3094
|
+
);
|
|
3095
|
+
if (keyPath.length !== delta.k.length) continue;
|
|
3096
|
+
if (delta.kind === JSONL_KIND_SET) {
|
|
3097
|
+
applySet(record, keyPath, delta.v);
|
|
3098
|
+
} else if (delta.kind === JSONL_KIND_SPLICE && Array.isArray(delta.v)) {
|
|
3099
|
+
applySplice(record, keyPath, delta.v, typeof delta.i === "number" ? delta.i : null);
|
|
3100
|
+
}
|
|
3101
|
+
}
|
|
3102
|
+
return { record, malformedLines };
|
|
3103
|
+
}
|
|
3104
|
+
function topModel2(models) {
|
|
3105
|
+
let best = null;
|
|
3106
|
+
let bestCount = 0;
|
|
3107
|
+
for (const [model, count] of models) {
|
|
3108
|
+
if (count > bestCount) {
|
|
3109
|
+
best = model;
|
|
3110
|
+
bestCount = count;
|
|
3111
|
+
}
|
|
3112
|
+
}
|
|
3113
|
+
return best;
|
|
3114
|
+
}
|
|
3115
|
+
async function foldChatSessions(workspaceStorageDir) {
|
|
3116
|
+
const result = {
|
|
3117
|
+
sessions: [],
|
|
3118
|
+
unparsedFiles: 0,
|
|
3119
|
+
unrecognisedFiles: 0,
|
|
3120
|
+
emptyDrafts: 0,
|
|
3121
|
+
malformedLines: 0
|
|
3122
|
+
};
|
|
3123
|
+
let wsHashes = [];
|
|
3124
|
+
try {
|
|
3125
|
+
wsHashes = (await fs6.readdir(workspaceStorageDir, { withFileTypes: true })).filter((e) => e.isDirectory()).map((e) => e.name);
|
|
3126
|
+
} catch {
|
|
3127
|
+
return result;
|
|
3128
|
+
}
|
|
3129
|
+
for (const hash of wsHashes.sort()) {
|
|
3130
|
+
const wsDir = path6.join(workspaceStorageDir, hash);
|
|
3131
|
+
let workspaceRoot = null;
|
|
3132
|
+
try {
|
|
3133
|
+
const raw = await fs6.readFile(path6.join(wsDir, "workspace.json"), "utf8");
|
|
3134
|
+
const parsed = JSON.parse(raw);
|
|
3135
|
+
workspaceRoot = uriToPath(parsed.folder);
|
|
3136
|
+
} catch {
|
|
3137
|
+
}
|
|
3138
|
+
let files = [];
|
|
3139
|
+
try {
|
|
3140
|
+
const entries = await fs6.readdir(path6.join(wsDir, "chatSessions"));
|
|
3141
|
+
files = entries.filter((f) => f.endsWith(".json") || f.endsWith(".jsonl"));
|
|
3142
|
+
result.unrecognisedFiles += entries.length - files.length;
|
|
3143
|
+
} catch {
|
|
3144
|
+
continue;
|
|
3145
|
+
}
|
|
3146
|
+
for (const file of files.sort()) {
|
|
3147
|
+
let raw;
|
|
3148
|
+
try {
|
|
3149
|
+
raw = await fs6.readFile(path6.join(wsDir, "chatSessions", file), "utf8");
|
|
3150
|
+
} catch {
|
|
3151
|
+
continue;
|
|
3152
|
+
}
|
|
3153
|
+
let record = null;
|
|
3154
|
+
if (file.endsWith(".jsonl")) {
|
|
3155
|
+
const folded = parseJsonlChatSession(raw);
|
|
3156
|
+
result.malformedLines += folded.malformedLines;
|
|
3157
|
+
record = folded.record;
|
|
3158
|
+
} else {
|
|
3159
|
+
try {
|
|
3160
|
+
const parsed = JSON.parse(raw);
|
|
3161
|
+
if (typeof parsed === "object" && parsed !== null) {
|
|
3162
|
+
record = parsed;
|
|
3163
|
+
}
|
|
3164
|
+
} catch {
|
|
3165
|
+
record = null;
|
|
3166
|
+
}
|
|
3167
|
+
}
|
|
3168
|
+
if (record === null) {
|
|
3169
|
+
result.unparsedFiles += 1;
|
|
3170
|
+
continue;
|
|
3171
|
+
}
|
|
3172
|
+
if (record.version !== KNOWN_CHAT_SESSION_VERSION) {
|
|
3173
|
+
result.unparsedFiles += 1;
|
|
3174
|
+
continue;
|
|
3175
|
+
}
|
|
3176
|
+
const requests = Array.isArray(record.requests) ? record.requests : [];
|
|
3177
|
+
if (requests.length === 0) {
|
|
3178
|
+
result.emptyDrafts += 1;
|
|
3179
|
+
continue;
|
|
3180
|
+
}
|
|
3181
|
+
const fold = {
|
|
3182
|
+
sessionId: typeof record.sessionId === "string" ? record.sessionId : path6.basename(file, path6.extname(file)),
|
|
3183
|
+
workspaceRoot,
|
|
3184
|
+
firstTs: null,
|
|
3185
|
+
lastTs: null,
|
|
3186
|
+
requestCount: 0,
|
|
3187
|
+
afterHoursRequests: 0,
|
|
3188
|
+
canceledCount: 0,
|
|
3189
|
+
errorCount: 0,
|
|
3190
|
+
models: /* @__PURE__ */ new Map(),
|
|
3191
|
+
requestTimestamps: []
|
|
3192
|
+
};
|
|
3193
|
+
for (const r of requests) {
|
|
3194
|
+
if (typeof r !== "object" || r === null) continue;
|
|
3195
|
+
const req = r;
|
|
3196
|
+
const ts = typeof req.timestamp === "number" && Number.isFinite(req.timestamp) ? req.timestamp : null;
|
|
3197
|
+
if (ts === null) continue;
|
|
3198
|
+
const iso = new Date(ts).toISOString();
|
|
3199
|
+
fold.requestCount += 1;
|
|
3200
|
+
fold.requestTimestamps.push(iso);
|
|
3201
|
+
if (!fold.firstTs || iso < fold.firstTs) fold.firstTs = iso;
|
|
3202
|
+
if (!fold.lastTs || iso > fold.lastTs) fold.lastTs = iso;
|
|
3203
|
+
if ((0, import_tool_kit3.isAfterHours)(new Date(iso))) fold.afterHoursRequests += 1;
|
|
3204
|
+
if (req.isCanceled === true) fold.canceledCount += 1;
|
|
3205
|
+
const requestResult = req.result;
|
|
3206
|
+
if (requestResult && requestResult.errorDetails) fold.errorCount += 1;
|
|
3207
|
+
if (typeof req.modelId === "string") {
|
|
3208
|
+
fold.models.set(req.modelId, (fold.models.get(req.modelId) ?? 0) + 1);
|
|
3209
|
+
}
|
|
3210
|
+
}
|
|
3211
|
+
if (fold.requestCount > 0) result.sessions.push(fold);
|
|
3212
|
+
}
|
|
3213
|
+
}
|
|
3214
|
+
return result;
|
|
3215
|
+
}
|
|
3216
|
+
async function* extractVsCode(snapshotDir, extractionId) {
|
|
3217
|
+
const historyDir = path6.join(snapshotDir, "history");
|
|
3218
|
+
const workspaceStorageDir = path6.join(snapshotDir, "workspaceStorage");
|
|
3219
|
+
const index = await buildWorkspaceFolderIndex(workspaceStorageDir);
|
|
3220
|
+
const editDays = await foldEditDays(historyDir, index);
|
|
3221
|
+
const chatSessions = await foldChatSessions(workspaceStorageDir);
|
|
3222
|
+
let windowOldest = editDays.oldest;
|
|
3223
|
+
let windowNewest = editDays.newest;
|
|
3224
|
+
const sortedDays = [...editDays.days.values()].sort(
|
|
3225
|
+
(a, b) => `${a.date}\0${a.workspaceRoot}`.localeCompare(`${b.date}\0${b.workspaceRoot}`)
|
|
3226
|
+
);
|
|
3227
|
+
for (const fold of sortedDays) {
|
|
3228
|
+
yield {
|
|
3229
|
+
occurredAt: fold.lastTs,
|
|
3230
|
+
store: "vscode",
|
|
3231
|
+
sourceVersion: String(KNOWN_HISTORY_VERSION),
|
|
3232
|
+
sessionRef: null,
|
|
3233
|
+
repoRef: fold.workspaceRoot,
|
|
3234
|
+
eventKind: "editor_activity",
|
|
3235
|
+
metrics: {
|
|
3236
|
+
date: fold.date,
|
|
3237
|
+
chatEditCount: fold.chatEditCount,
|
|
3238
|
+
totalEntryCount: fold.totalEntryCount
|
|
3239
|
+
},
|
|
3240
|
+
provenance: HISTORICAL_PROVENANCE.derived,
|
|
3241
|
+
extractionId
|
|
3242
|
+
};
|
|
3243
|
+
}
|
|
3244
|
+
let sessionCount = 0;
|
|
3245
|
+
for (const fold of chatSessions.sessions) {
|
|
3246
|
+
if (!fold.firstTs || !fold.lastTs) continue;
|
|
3247
|
+
sessionCount += 1;
|
|
3248
|
+
if (!windowOldest || fold.firstTs < windowOldest) windowOldest = fold.firstTs;
|
|
3249
|
+
if (!windowNewest || fold.lastTs > windowNewest) windowNewest = fold.lastTs;
|
|
3250
|
+
for (const ts of fold.requestTimestamps) {
|
|
3251
|
+
yield {
|
|
3252
|
+
occurredAt: ts,
|
|
3253
|
+
store: "vscode",
|
|
3254
|
+
sourceVersion: String(KNOWN_CHAT_SESSION_VERSION),
|
|
3255
|
+
sessionRef: fold.sessionId,
|
|
3256
|
+
repoRef: fold.workspaceRoot,
|
|
3257
|
+
eventKind: "ai_prompt_submitted",
|
|
3258
|
+
metrics: {},
|
|
3259
|
+
provenance: HISTORICAL_PROVENANCE.direct,
|
|
3260
|
+
extractionId
|
|
3261
|
+
};
|
|
3262
|
+
}
|
|
3263
|
+
const durationMs = Date.parse(fold.lastTs) - Date.parse(fold.firstTs);
|
|
3264
|
+
const sessionMetrics = {
|
|
3265
|
+
requestCount: fold.requestCount,
|
|
3266
|
+
afterHoursRequests: fold.afterHoursRequests,
|
|
3267
|
+
canceledCount: fold.canceledCount,
|
|
3268
|
+
errorCount: fold.errorCount,
|
|
3269
|
+
modelCount: fold.models.size,
|
|
3270
|
+
sessionStartedAt: fold.firstTs
|
|
3271
|
+
};
|
|
3272
|
+
const primaryModel = topModel2(fold.models);
|
|
3273
|
+
if (primaryModel) sessionMetrics.primaryModel = primaryModel;
|
|
3274
|
+
if (Number.isFinite(durationMs) && durationMs >= 0) {
|
|
3275
|
+
sessionMetrics.sessionMinutes = Math.round(durationMs / 6e4);
|
|
3276
|
+
const bucket = (0, import_tool_kit3.bucketDurationMs)(durationMs);
|
|
3277
|
+
if (bucket) sessionMetrics.durationBucket = bucket;
|
|
3278
|
+
}
|
|
3279
|
+
yield {
|
|
3280
|
+
occurredAt: fold.lastTs,
|
|
3281
|
+
store: "vscode",
|
|
3282
|
+
sourceVersion: String(KNOWN_CHAT_SESSION_VERSION),
|
|
3283
|
+
sessionRef: fold.sessionId,
|
|
3284
|
+
repoRef: fold.workspaceRoot,
|
|
3285
|
+
eventKind: "create_focus_session",
|
|
3286
|
+
metrics: sessionMetrics,
|
|
3287
|
+
dayBreakdown: sliceSessionByLocalDay(fold.requestTimestamps),
|
|
3288
|
+
provenance: HISTORICAL_PROVENANCE.derived,
|
|
3289
|
+
extractionId
|
|
3290
|
+
};
|
|
3291
|
+
if (fold.afterHoursRequests > 0) {
|
|
3292
|
+
yield {
|
|
3293
|
+
occurredAt: fold.lastTs,
|
|
3294
|
+
store: "vscode",
|
|
3295
|
+
sourceVersion: String(KNOWN_CHAT_SESSION_VERSION),
|
|
3296
|
+
sessionRef: fold.sessionId,
|
|
3297
|
+
repoRef: fold.workspaceRoot,
|
|
3298
|
+
eventKind: "after_hours_ai_session",
|
|
3299
|
+
metrics: { afterHoursPrompts: fold.afterHoursRequests },
|
|
3300
|
+
provenance: HISTORICAL_PROVENANCE.derived,
|
|
3301
|
+
extractionId
|
|
3302
|
+
};
|
|
3303
|
+
}
|
|
3304
|
+
if (fold.errorCount > 0) {
|
|
3305
|
+
yield {
|
|
3306
|
+
occurredAt: fold.lastTs,
|
|
3307
|
+
store: "vscode",
|
|
3308
|
+
sourceVersion: String(KNOWN_CHAT_SESSION_VERSION),
|
|
3309
|
+
sessionRef: fold.sessionId,
|
|
3310
|
+
repoRef: fold.workspaceRoot,
|
|
3311
|
+
eventKind: "tool_failure",
|
|
3312
|
+
metrics: { toolFailureCount: fold.errorCount },
|
|
3313
|
+
provenance: HISTORICAL_PROVENANCE.derived,
|
|
3314
|
+
extractionId
|
|
3315
|
+
};
|
|
3316
|
+
}
|
|
3317
|
+
}
|
|
3318
|
+
const readFailures = editDays.unparsedFiles + editDays.malformedEntries + chatSessions.unparsedFiles + chatSessions.unrecognisedFiles + chatSessions.malformedLines;
|
|
3319
|
+
if (windowOldest && windowNewest || readFailures > 0) {
|
|
3320
|
+
const window = windowOldest && windowNewest ? { windowOldest, windowNewest } : {};
|
|
3321
|
+
yield {
|
|
3322
|
+
// No window means nothing datable was read; the only honest timestamp
|
|
3323
|
+
// left is when the read happened.
|
|
3324
|
+
occurredAt: windowNewest ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
3325
|
+
store: "vscode",
|
|
3326
|
+
sourceVersion: null,
|
|
3327
|
+
sessionRef: null,
|
|
3328
|
+
repoRef: null,
|
|
3329
|
+
eventKind: "extraction_epoch",
|
|
3330
|
+
metrics: {
|
|
3331
|
+
...window,
|
|
3332
|
+
editDayCount: editDays.days.size,
|
|
3333
|
+
sessionCount,
|
|
3334
|
+
unparsedHistoryFiles: editDays.unparsedFiles,
|
|
3335
|
+
malformedHistoryEntries: editDays.malformedEntries,
|
|
3336
|
+
unparsedChatSessionFiles: chatSessions.unparsedFiles,
|
|
3337
|
+
unrecognisedChatSessionFiles: chatSessions.unrecognisedFiles,
|
|
3338
|
+
emptyChatSessions: chatSessions.emptyDrafts,
|
|
3339
|
+
malformedChatSessionLines: chatSessions.malformedLines
|
|
3340
|
+
},
|
|
3341
|
+
provenance: HISTORICAL_PROVENANCE.derived,
|
|
3342
|
+
extractionId
|
|
3343
|
+
};
|
|
3344
|
+
}
|
|
3345
|
+
}
|
|
3346
|
+
|
|
3347
|
+
// src/ship.ts
|
|
3348
|
+
var import_tool_kit4 = __toESM(require_out2(), 1);
|
|
3349
|
+
import { createHash } from "node:crypto";
|
|
3350
|
+
var DEFAULT_API_BASE_URL = "https://api.ascenda.one";
|
|
3351
|
+
function loadShipConfig() {
|
|
3352
|
+
const apiBaseUrl = (process.env.ASCENDA_API_BASE_URL ?? DEFAULT_API_BASE_URL).replace(/\/$/, "");
|
|
3353
|
+
const idRaw = process.env.ASCENDA_TOOL_INSTALLATION_ID;
|
|
3354
|
+
if (!idRaw) {
|
|
3355
|
+
throw new Error(
|
|
3356
|
+
"Missing ASCENDA_TOOL_INSTALLATION_ID \u2014 the importer ships as the machine's paired tool; pair it first"
|
|
3357
|
+
);
|
|
3358
|
+
}
|
|
3359
|
+
const toolInstallationId = idRaw.trim().includes(":") ? idRaw.trim() : `claude_code:${idRaw.trim()}`;
|
|
3360
|
+
const tokenFilePath = process.env.ASCENDA_EVENT_WRITE_TOKEN_FILE ?? (0, import_tool_kit4.defaultTokenFilePath)(toolInstallationId);
|
|
3361
|
+
const eventWriteToken = (0, import_tool_kit4.readTokenFile)(tokenFilePath) ?? process.env.ASCENDA_EVENT_WRITE_TOKEN;
|
|
3362
|
+
if (!eventWriteToken) {
|
|
3363
|
+
throw new Error(`No event write token at ${tokenFilePath} (or ASCENDA_EVENT_WRITE_TOKEN)`);
|
|
3364
|
+
}
|
|
3365
|
+
return { apiBaseUrl, toolInstallationId, eventWriteToken };
|
|
3366
|
+
}
|
|
3367
|
+
function importOrdinals(events) {
|
|
3368
|
+
const seen = /* @__PURE__ */ new Map();
|
|
3369
|
+
return events.map((event) => {
|
|
3370
|
+
const identity = [event.store, event.sessionRef ?? "", event.eventKind, event.occurredAt].join(
|
|
3371
|
+
"|"
|
|
3372
|
+
);
|
|
3373
|
+
const ordinal = seen.get(identity) ?? 0;
|
|
3374
|
+
seen.set(identity, ordinal + 1);
|
|
3375
|
+
return ordinal;
|
|
3376
|
+
});
|
|
3377
|
+
}
|
|
3378
|
+
function importKeyOf(event, ordinal) {
|
|
3379
|
+
return createHash("sha256").update(
|
|
3380
|
+
[event.store, event.sessionRef ?? "", event.eventKind, event.occurredAt, String(ordinal)].join(
|
|
3381
|
+
"|"
|
|
3382
|
+
)
|
|
3383
|
+
).digest("hex").slice(0, 16);
|
|
3384
|
+
}
|
|
3385
|
+
function toWirePayload(event, ordinal, toolInstallationId) {
|
|
3386
|
+
const metadata = {
|
|
3387
|
+
importKey: importKeyOf(event, ordinal),
|
|
3388
|
+
extractionId: event.extractionId,
|
|
3389
|
+
importSchema: 1
|
|
3390
|
+
};
|
|
3391
|
+
if (event.sourceVersion) metadata.sourceVersion = event.sourceVersion;
|
|
3392
|
+
for (const [key, value] of Object.entries(event.metrics)) {
|
|
3393
|
+
metadata[key] = value;
|
|
3394
|
+
}
|
|
3395
|
+
if (typeof metadata.gitBranch === "string") {
|
|
3396
|
+
metadata.gitBranchHash = (0, import_tool_kit4.hashWithMachineSalt)(metadata.gitBranch) ?? "";
|
|
3397
|
+
delete metadata.gitBranch;
|
|
3398
|
+
}
|
|
3399
|
+
const workspaceHash = event.repoRef ? (0, import_tool_kit4.hashWithMachineSalt)(event.repoRef) : null;
|
|
3400
|
+
return {
|
|
3401
|
+
toolInstallationId,
|
|
3402
|
+
source: STORE_SOURCE[event.store],
|
|
3403
|
+
// Always a canonical catalog type. `eventKind` is typed against the
|
|
3404
|
+
// contract union and the epoch marker is filtered out upstream in
|
|
3405
|
+
// `shippableEvents`, so no cast is needed here — and an off-catalog name
|
|
3406
|
+
// cannot reach the wire to be silently bucketed as `unclassified`.
|
|
3407
|
+
eventType: event.eventKind,
|
|
3408
|
+
occurredAt: event.occurredAt,
|
|
3409
|
+
// The offset in force WHEN THE EVENT HAPPENED, not when the import ran.
|
|
3410
|
+
// A nine-month backfill crosses DST boundaries; stamping today's
|
|
3411
|
+
// offset on all of it would shift a whole season by an hour.
|
|
3412
|
+
utcOffsetMinutes: (0, import_tool_kit4.utcOffsetMinutesAt)(new Date(event.occurredAt)),
|
|
3413
|
+
severity: "low",
|
|
3414
|
+
sessionId: event.sessionRef,
|
|
3415
|
+
workspaceHash,
|
|
3416
|
+
projectHash: workspaceHash,
|
|
3417
|
+
// A real contract ToolConsentScope now, and a real gate: the backend
|
|
3418
|
+
// requires an active historical-import consent lease for anything carrying
|
|
3419
|
+
// one of the provenance classes below, and decides that on the provenance
|
|
3420
|
+
// rather than on this string — sending `ide_telemetry` here would not buy
|
|
3421
|
+
// the event a way in.
|
|
3422
|
+
consentScope: HISTORICAL_CONSENT_SCOPE,
|
|
3423
|
+
provenance: event.provenance,
|
|
3424
|
+
privacyMode: "metadata_only",
|
|
3425
|
+
metadata
|
|
3426
|
+
};
|
|
3427
|
+
}
|
|
3428
|
+
var BATCH_SIZE = 200;
|
|
3429
|
+
function shippableEvents(events) {
|
|
3430
|
+
return events.filter(
|
|
3431
|
+
(e) => e.eventKind !== EXTRACTION_EPOCH_KIND
|
|
3432
|
+
);
|
|
3433
|
+
}
|
|
3434
|
+
async function shipEvents(events, config, onProgress) {
|
|
3435
|
+
const result = {
|
|
3436
|
+
sent: 0,
|
|
3437
|
+
accepted: 0,
|
|
3438
|
+
duplicate: 0,
|
|
3439
|
+
rejected: 0,
|
|
3440
|
+
rejectionReasons: {},
|
|
3441
|
+
httpFailures: 0
|
|
3442
|
+
};
|
|
3443
|
+
const wireEvents = shippableEvents(events);
|
|
3444
|
+
const ordinals = importOrdinals(wireEvents);
|
|
3445
|
+
for (let offset = 0; offset < wireEvents.length; offset += BATCH_SIZE) {
|
|
3446
|
+
const chunk = wireEvents.slice(offset, offset + BATCH_SIZE);
|
|
3447
|
+
const payloads = chunk.map(
|
|
3448
|
+
(event, i) => toWirePayload(event, ordinals[offset + i], config.toolInstallationId)
|
|
3449
|
+
);
|
|
3450
|
+
let response;
|
|
3451
|
+
try {
|
|
3452
|
+
response = await fetch(`${config.apiBaseUrl}/v1/tool-events/batch`, {
|
|
3453
|
+
method: "POST",
|
|
3454
|
+
headers: {
|
|
3455
|
+
"content-type": "application/json",
|
|
3456
|
+
authorization: `Bearer ${config.eventWriteToken}`
|
|
3457
|
+
},
|
|
3458
|
+
body: JSON.stringify({ events: payloads })
|
|
3459
|
+
});
|
|
3460
|
+
} catch {
|
|
3461
|
+
result.httpFailures += 1;
|
|
3462
|
+
continue;
|
|
3463
|
+
}
|
|
3464
|
+
result.sent += chunk.length;
|
|
3465
|
+
if (!response.ok) {
|
|
3466
|
+
result.httpFailures += 1;
|
|
3467
|
+
if (response.status === 401 || response.status === 403) {
|
|
3468
|
+
throw new Error(
|
|
3469
|
+
`Ingest refused (${response.status}) \u2014 token invalid/revoked or consent missing; aborting rather than burning ${wireEvents.length - offset} more events`
|
|
3470
|
+
);
|
|
3471
|
+
}
|
|
3472
|
+
continue;
|
|
3473
|
+
}
|
|
3474
|
+
try {
|
|
3475
|
+
const body = await response.json();
|
|
3476
|
+
result.accepted += body.accepted ?? 0;
|
|
3477
|
+
result.duplicate += body.duplicate ?? 0;
|
|
3478
|
+
result.rejected += body.rejected ?? 0;
|
|
3479
|
+
for (const item of body.results ?? []) {
|
|
3480
|
+
if (item.status !== "accepted" && item.status !== "duplicate") {
|
|
3481
|
+
const reason = item.reason ?? "unknown";
|
|
3482
|
+
result.rejectionReasons[reason] = (result.rejectionReasons[reason] ?? 0) + 1;
|
|
3483
|
+
}
|
|
3484
|
+
}
|
|
3485
|
+
} catch {
|
|
3486
|
+
result.httpFailures += 1;
|
|
3487
|
+
}
|
|
3488
|
+
onProgress?.(Math.min(offset + BATCH_SIZE, wireEvents.length), wireEvents.length);
|
|
3489
|
+
}
|
|
3490
|
+
return result;
|
|
3491
|
+
}
|
|
3492
|
+
|
|
3493
|
+
// src/localHandoff.ts
|
|
3494
|
+
import * as fs7 from "node:fs/promises";
|
|
3495
|
+
import * as os2 from "node:os";
|
|
3496
|
+
import * as path7 from "node:path";
|
|
3497
|
+
var APP_BUNDLE_ID = "one.ascenda.ascendaMissionControl";
|
|
3498
|
+
var HANDOFF_SCHEMA = 2;
|
|
3499
|
+
function handoffDir(home = os2.homedir()) {
|
|
3500
|
+
return path7.join(home, "Library", "Containers", APP_BUNDLE_ID, "Data", ".ascenda", "history-import");
|
|
3501
|
+
}
|
|
3502
|
+
function handoffFilePath(home = os2.homedir(), store = "claude_code") {
|
|
3503
|
+
return path7.join(handoffDir(home), `${store}.json`);
|
|
3504
|
+
}
|
|
3505
|
+
function projectLabelOf(repoRef) {
|
|
3506
|
+
if (!repoRef) return null;
|
|
3507
|
+
const trimmed = repoRef.replace(/\/+$/, "");
|
|
3508
|
+
const segment = trimmed.split("/").filter(Boolean).pop() ?? null;
|
|
3509
|
+
return segment && segment.length > 0 ? segment : null;
|
|
3510
|
+
}
|
|
3511
|
+
function buildHandoff(events, extractionId, generatedAt) {
|
|
3512
|
+
const sessions = [];
|
|
3513
|
+
let windowOldest = null;
|
|
3514
|
+
let windowNewest = null;
|
|
3515
|
+
for (const event of events) {
|
|
3516
|
+
if (event.eventKind === "extraction_epoch") {
|
|
3517
|
+
const oldest = event.metrics.windowOldest;
|
|
3518
|
+
const newest = event.metrics.windowNewest;
|
|
3519
|
+
if (typeof oldest === "string") windowOldest = oldest;
|
|
3520
|
+
if (typeof newest === "string") windowNewest = newest;
|
|
3521
|
+
continue;
|
|
3522
|
+
}
|
|
3523
|
+
if (event.eventKind !== "create_focus_session") continue;
|
|
3524
|
+
sessions.push({
|
|
3525
|
+
at: event.occurredAt,
|
|
3526
|
+
startedAt: typeof event.metrics.sessionStartedAt === "string" ? event.metrics.sessionStartedAt : null,
|
|
3527
|
+
sessionRef: event.sessionRef,
|
|
3528
|
+
projectLabel: projectLabelOf(event.repoRef),
|
|
3529
|
+
promptCount: Number(event.metrics.promptCount ?? 0),
|
|
3530
|
+
durationBucket: String(event.metrics.durationBucket ?? "unknown"),
|
|
3531
|
+
afterHoursPrompts: Number(event.metrics.afterHoursPrompts ?? 0),
|
|
3532
|
+
primaryModel: typeof event.metrics.primaryModel === "string" ? event.metrics.primaryModel : null,
|
|
3533
|
+
activeMinutes: Number(event.metrics.activeMinutes ?? 0),
|
|
3534
|
+
compactionCount: Number(event.metrics.compactionCount ?? 0),
|
|
3535
|
+
toolFailureCount: Number(event.metrics.toolFailureCount ?? 0),
|
|
3536
|
+
contextWindowPeakPct: Number(event.metrics.contextWindowPeakPct ?? 0),
|
|
3537
|
+
userModifiedEditCount: Number(event.metrics.userModifiedEditCount ?? 0),
|
|
3538
|
+
subagentTranscripts: Number(event.metrics.subagentTranscripts ?? 0),
|
|
3539
|
+
days: event.dayBreakdown ?? [],
|
|
3540
|
+
provenance: event.provenance
|
|
3541
|
+
});
|
|
3542
|
+
}
|
|
3543
|
+
sessions.sort((a, b) => a.at.localeCompare(b.at));
|
|
3544
|
+
return {
|
|
3545
|
+
schema: HANDOFF_SCHEMA,
|
|
3546
|
+
extractionId,
|
|
3547
|
+
generatedAt,
|
|
3548
|
+
timezone: LOCAL_TIMEZONE,
|
|
3549
|
+
store: "claude_code",
|
|
3550
|
+
windowOldest,
|
|
3551
|
+
windowNewest,
|
|
3552
|
+
sessions
|
|
3553
|
+
};
|
|
3554
|
+
}
|
|
3555
|
+
function buildCursorHandoff(events, extractionId, generatedAt) {
|
|
3556
|
+
const sessions = [];
|
|
3557
|
+
let windowOldest = null;
|
|
3558
|
+
let windowNewest = null;
|
|
3559
|
+
for (const event of events) {
|
|
3560
|
+
if (event.eventKind === "extraction_epoch") {
|
|
3561
|
+
const oldest = event.metrics.windowOldest;
|
|
3562
|
+
const newest = event.metrics.windowNewest;
|
|
3563
|
+
if (typeof oldest === "string") windowOldest = oldest;
|
|
3564
|
+
if (typeof newest === "string") windowNewest = newest;
|
|
3565
|
+
continue;
|
|
3566
|
+
}
|
|
3567
|
+
if (event.eventKind !== "create_focus_session") continue;
|
|
3568
|
+
sessions.push({
|
|
3569
|
+
at: event.occurredAt,
|
|
3570
|
+
startedAt: typeof event.metrics.sessionStartedAt === "string" ? event.metrics.sessionStartedAt : null,
|
|
3571
|
+
sessionRef: event.sessionRef,
|
|
3572
|
+
projectLabel: projectLabelOf(event.repoRef),
|
|
3573
|
+
promptCount: Number(event.metrics.promptCount ?? 0),
|
|
3574
|
+
durationBucket: String(event.metrics.durationBucket ?? "unknown"),
|
|
3575
|
+
afterHoursPrompts: Number(event.metrics.afterHoursPrompts ?? 0),
|
|
3576
|
+
primaryModel: typeof event.metrics.primaryModel === "string" ? event.metrics.primaryModel : null,
|
|
3577
|
+
linesAdded: Number(event.metrics.linesAdded ?? 0),
|
|
3578
|
+
linesRemoved: Number(event.metrics.linesRemoved ?? 0),
|
|
3579
|
+
filesChangedCount: Number(event.metrics.filesChangedCount ?? 0),
|
|
3580
|
+
contextUsagePercent: typeof event.metrics.contextUsagePercent === "number" ? event.metrics.contextUsagePercent : null,
|
|
3581
|
+
humanChangesCount: Number(event.metrics.humanChangesCount ?? 0),
|
|
3582
|
+
approximateLintErrorsCount: Number(event.metrics.approximateLintErrorsCount ?? 0),
|
|
3583
|
+
subagentComposers: Number(event.metrics.subagentComposers ?? 0),
|
|
3584
|
+
days: event.dayBreakdown ?? [],
|
|
3585
|
+
provenance: event.provenance
|
|
3586
|
+
});
|
|
3587
|
+
}
|
|
3588
|
+
sessions.sort((a, b) => a.at.localeCompare(b.at));
|
|
3589
|
+
return {
|
|
3590
|
+
schema: HANDOFF_SCHEMA,
|
|
3591
|
+
extractionId,
|
|
3592
|
+
generatedAt,
|
|
3593
|
+
timezone: LOCAL_TIMEZONE,
|
|
3594
|
+
store: "cursor",
|
|
3595
|
+
windowOldest,
|
|
3596
|
+
windowNewest,
|
|
3597
|
+
sessions
|
|
3598
|
+
};
|
|
3599
|
+
}
|
|
3600
|
+
function buildVsCodeHandoff(events, extractionId, generatedAt) {
|
|
3601
|
+
const editDays = [];
|
|
3602
|
+
const chatSessions = [];
|
|
3603
|
+
let windowOldest = null;
|
|
3604
|
+
let windowNewest = null;
|
|
3605
|
+
for (const event of events) {
|
|
3606
|
+
if (event.eventKind === "extraction_epoch") {
|
|
3607
|
+
const oldest = event.metrics.windowOldest;
|
|
3608
|
+
const newest = event.metrics.windowNewest;
|
|
3609
|
+
if (typeof oldest === "string") windowOldest = oldest;
|
|
3610
|
+
if (typeof newest === "string") windowNewest = newest;
|
|
3611
|
+
continue;
|
|
3612
|
+
}
|
|
3613
|
+
if (event.eventKind === "editor_activity") {
|
|
3614
|
+
editDays.push({
|
|
3615
|
+
date: typeof event.metrics.date === "string" ? event.metrics.date : event.occurredAt.slice(0, 10),
|
|
3616
|
+
projectLabel: projectLabelOf(event.repoRef),
|
|
3617
|
+
chatEditCount: Number(event.metrics.chatEditCount ?? 0),
|
|
3618
|
+
totalEntryCount: Number(event.metrics.totalEntryCount ?? 0),
|
|
3619
|
+
provenance: event.provenance
|
|
3620
|
+
});
|
|
3621
|
+
continue;
|
|
3622
|
+
}
|
|
3623
|
+
if (event.eventKind !== "create_focus_session") continue;
|
|
3624
|
+
chatSessions.push({
|
|
3625
|
+
at: event.occurredAt,
|
|
3626
|
+
startedAt: typeof event.metrics.sessionStartedAt === "string" ? event.metrics.sessionStartedAt : null,
|
|
3627
|
+
sessionRef: event.sessionRef,
|
|
3628
|
+
projectLabel: projectLabelOf(event.repoRef),
|
|
3629
|
+
requestCount: Number(event.metrics.requestCount ?? 0),
|
|
3630
|
+
durationBucket: String(event.metrics.durationBucket ?? "unknown"),
|
|
3631
|
+
afterHoursRequests: Number(event.metrics.afterHoursRequests ?? 0),
|
|
3632
|
+
primaryModel: typeof event.metrics.primaryModel === "string" ? event.metrics.primaryModel : null,
|
|
3633
|
+
canceledCount: Number(event.metrics.canceledCount ?? 0),
|
|
3634
|
+
errorCount: Number(event.metrics.errorCount ?? 0),
|
|
3635
|
+
days: event.dayBreakdown ?? [],
|
|
3636
|
+
provenance: event.provenance
|
|
3637
|
+
});
|
|
3638
|
+
}
|
|
3639
|
+
editDays.sort((a, b) => a.date.localeCompare(b.date) || (a.projectLabel ?? "").localeCompare(b.projectLabel ?? ""));
|
|
3640
|
+
chatSessions.sort((a, b) => a.at.localeCompare(b.at));
|
|
3641
|
+
return {
|
|
3642
|
+
schema: HANDOFF_SCHEMA,
|
|
3643
|
+
extractionId,
|
|
3644
|
+
generatedAt,
|
|
3645
|
+
timezone: LOCAL_TIMEZONE,
|
|
3646
|
+
store: "vscode",
|
|
3647
|
+
windowOldest,
|
|
3648
|
+
windowNewest,
|
|
3649
|
+
editDays,
|
|
3650
|
+
chatSessions
|
|
3651
|
+
};
|
|
3652
|
+
}
|
|
3653
|
+
async function writeHandoff(handoff, home = os2.homedir()) {
|
|
3654
|
+
const containerRoot = path7.join(home, "Library", "Containers", APP_BUNDLE_ID, "Data");
|
|
3655
|
+
try {
|
|
3656
|
+
await fs7.stat(containerRoot);
|
|
3657
|
+
} catch {
|
|
3658
|
+
return null;
|
|
3659
|
+
}
|
|
3660
|
+
const dir = handoffDir(home);
|
|
3661
|
+
await fs7.mkdir(dir, { recursive: true });
|
|
3662
|
+
const target = handoffFilePath(home, handoff.store);
|
|
3663
|
+
const temp = `${target}.tmp`;
|
|
3664
|
+
await fs7.writeFile(temp, JSON.stringify(handoff, null, 2) + "\n", "utf8");
|
|
3665
|
+
await fs7.rename(temp, target);
|
|
3666
|
+
return target;
|
|
3667
|
+
}
|
|
3668
|
+
|
|
3669
|
+
// src/cli.ts
|
|
3670
|
+
function formatInventory(inv) {
|
|
3671
|
+
const lines = [];
|
|
3672
|
+
const status = inv.present ? "found" : "not found";
|
|
3673
|
+
lines.push(`${inv.store} (${status}) ${inv.rootPath}`);
|
|
3674
|
+
if (inv.present) {
|
|
3675
|
+
if (inv.oldest || inv.newest) {
|
|
3676
|
+
lines.push(` window: ${inv.oldest ?? "?"} \u2192 ${inv.newest ?? "?"}`);
|
|
3677
|
+
}
|
|
3678
|
+
const counts = Object.entries(inv.counts).map(([k, v]) => `${k}=${v.toLocaleString("en-US")}`).join(" ");
|
|
3679
|
+
if (counts) lines.push(` ${counts}`);
|
|
3680
|
+
if (inv.retentionRisk) lines.push(` \u26A0 ${inv.retentionRisk}`);
|
|
3681
|
+
for (const note of inv.notes) lines.push(` note: ${note}`);
|
|
3682
|
+
}
|
|
3683
|
+
return lines.join("\n");
|
|
3684
|
+
}
|
|
3685
|
+
async function main() {
|
|
3686
|
+
const [command, ...rest] = process.argv.slice(2);
|
|
3687
|
+
const paths = resolveStorePaths();
|
|
3688
|
+
switch (command) {
|
|
3689
|
+
case "scan": {
|
|
3690
|
+
const inventories = await scanAll(paths);
|
|
3691
|
+
if (rest.includes("--json")) {
|
|
3692
|
+
process.stdout.write(JSON.stringify(inventories, null, 2) + "\n");
|
|
3693
|
+
} else {
|
|
3694
|
+
process.stdout.write(inventories.map(formatInventory).join("\n\n") + "\n");
|
|
3695
|
+
}
|
|
3696
|
+
return 0;
|
|
3697
|
+
}
|
|
3698
|
+
case "import": {
|
|
3699
|
+
const ship = rest.includes("--ship");
|
|
3700
|
+
const claudeInventory = await scanClaudeCode(paths);
|
|
3701
|
+
const cursorInventory = await scanCursor(paths);
|
|
3702
|
+
const vsCodeInventory = await scanVsCode(paths);
|
|
3703
|
+
if (!claudeInventory.present && !cursorInventory.present && !vsCodeInventory.present) {
|
|
3704
|
+
process.stderr.write(
|
|
3705
|
+
`no Claude Code store at ${claudeInventory.rootPath}, no Cursor store at ${cursorInventory.rootPath}, and no VS Code store at ${vsCodeInventory.rootPath} \u2014 nothing to import
|
|
3706
|
+
`
|
|
3707
|
+
);
|
|
3708
|
+
return 2;
|
|
3709
|
+
}
|
|
3710
|
+
const area = await createStagingArea(defaultStagingRoot(paths.home));
|
|
3711
|
+
process.stdout.write(`staging: ${area.root}
|
|
3712
|
+
|
|
3713
|
+
`);
|
|
3714
|
+
const allEvents = [];
|
|
3715
|
+
if (claudeInventory.present) {
|
|
3716
|
+
process.stdout.write(formatInventory(claudeInventory) + "\n\n");
|
|
3717
|
+
const snapshotRoot = path8.join(area.root, "claude_code");
|
|
3718
|
+
await snapshotPath(area, paths.claudeProjects, path8.join("claude_code", "projects"));
|
|
3719
|
+
const claudeEvents = [];
|
|
3720
|
+
const kindCounts = {};
|
|
3721
|
+
for await (const event of extractClaudeCode(snapshotRoot, area.extractionId)) {
|
|
3722
|
+
claudeEvents.push(event);
|
|
3723
|
+
kindCounts[event.eventKind] = (kindCounts[event.eventKind] ?? 0) + 1;
|
|
3724
|
+
}
|
|
3725
|
+
allEvents.push(...claudeEvents);
|
|
3726
|
+
const epoch = claudeEvents.find((e) => e.eventKind === "extraction_epoch");
|
|
3727
|
+
process.stdout.write(`claude_code extracted: ${claudeEvents.length.toLocaleString("en-US")} events
|
|
3728
|
+
`);
|
|
3729
|
+
for (const [kind, n] of Object.entries(kindCounts)) {
|
|
3730
|
+
process.stdout.write(` ${kind}: ${n.toLocaleString("en-US")}
|
|
3731
|
+
`);
|
|
3732
|
+
}
|
|
3733
|
+
if (epoch) {
|
|
3734
|
+
process.stdout.write(` window: ${epoch.metrics.windowOldest} \u2192 ${epoch.metrics.windowNewest}
|
|
3735
|
+
`);
|
|
3736
|
+
}
|
|
3737
|
+
const handoff = buildHandoff(claudeEvents, area.extractionId, (/* @__PURE__ */ new Date()).toISOString());
|
|
3738
|
+
const handoffPath = await writeHandoff(handoff);
|
|
3739
|
+
if (handoffPath) {
|
|
3740
|
+
process.stdout.write(` handoff: ${handoff.sessions.length} sessions \u2192 ${handoffPath}
|
|
3741
|
+
|
|
3742
|
+
`);
|
|
3743
|
+
} else {
|
|
3744
|
+
process.stdout.write(" handoff: desktop app container not found \u2014 skipped\n\n");
|
|
3745
|
+
}
|
|
3746
|
+
} else {
|
|
3747
|
+
process.stdout.write(formatInventory(claudeInventory) + "\n\n");
|
|
3748
|
+
}
|
|
3749
|
+
if (cursorInventory.present) {
|
|
3750
|
+
process.stdout.write(formatInventory(cursorInventory) + "\n\n");
|
|
3751
|
+
const cursorSnapshotRoot = path8.join(area.root, "cursor");
|
|
3752
|
+
await snapshotPath(area, paths.cursorStateDb, path8.join("cursor", "state.vscdb"));
|
|
3753
|
+
const cursorEvents = [];
|
|
3754
|
+
const kindCounts = {};
|
|
3755
|
+
for await (const event of extractCursor(cursorSnapshotRoot, area.extractionId)) {
|
|
3756
|
+
cursorEvents.push(event);
|
|
3757
|
+
kindCounts[event.eventKind] = (kindCounts[event.eventKind] ?? 0) + 1;
|
|
3758
|
+
}
|
|
3759
|
+
allEvents.push(...cursorEvents);
|
|
3760
|
+
const epoch = cursorEvents.find((e) => e.eventKind === "extraction_epoch");
|
|
3761
|
+
process.stdout.write(`cursor extracted: ${cursorEvents.length.toLocaleString("en-US")} events
|
|
3762
|
+
`);
|
|
3763
|
+
for (const [kind, n] of Object.entries(kindCounts)) {
|
|
3764
|
+
process.stdout.write(` ${kind}: ${n.toLocaleString("en-US")}
|
|
3765
|
+
`);
|
|
3766
|
+
}
|
|
3767
|
+
if (epoch) {
|
|
3768
|
+
process.stdout.write(` window: ${epoch.metrics.windowOldest} \u2192 ${epoch.metrics.windowNewest}
|
|
3769
|
+
`);
|
|
3770
|
+
}
|
|
3771
|
+
const handoff = buildCursorHandoff(cursorEvents, area.extractionId, (/* @__PURE__ */ new Date()).toISOString());
|
|
3772
|
+
const handoffPath = await writeHandoff(handoff);
|
|
3773
|
+
if (handoffPath) {
|
|
3774
|
+
process.stdout.write(` handoff: ${handoff.sessions.length} sessions \u2192 ${handoffPath}
|
|
3775
|
+
|
|
3776
|
+
`);
|
|
3777
|
+
} else {
|
|
3778
|
+
process.stdout.write(" handoff: desktop app container not found \u2014 skipped\n\n");
|
|
3779
|
+
}
|
|
3780
|
+
} else {
|
|
3781
|
+
process.stdout.write(formatInventory(cursorInventory) + "\n\n");
|
|
3782
|
+
}
|
|
3783
|
+
if (vsCodeInventory.present) {
|
|
3784
|
+
process.stdout.write(formatInventory(vsCodeInventory) + "\n\n");
|
|
3785
|
+
const vsCodeSnapshotRoot = path8.join(area.root, "vscode");
|
|
3786
|
+
await snapshotPath(area, paths.vscodeHistory, path8.join("vscode", "history"));
|
|
3787
|
+
await snapshotVsCodeWorkspaceStorage(area, paths.vscodeWorkspaceStorage, path8.join("vscode", "workspaceStorage"));
|
|
3788
|
+
const vsCodeEvents = [];
|
|
3789
|
+
const kindCounts = {};
|
|
3790
|
+
for await (const event of extractVsCode(vsCodeSnapshotRoot, area.extractionId)) {
|
|
3791
|
+
vsCodeEvents.push(event);
|
|
3792
|
+
kindCounts[event.eventKind] = (kindCounts[event.eventKind] ?? 0) + 1;
|
|
3793
|
+
}
|
|
3794
|
+
allEvents.push(...vsCodeEvents);
|
|
3795
|
+
const epoch = vsCodeEvents.find((e) => e.eventKind === "extraction_epoch");
|
|
3796
|
+
process.stdout.write(`vscode extracted: ${vsCodeEvents.length.toLocaleString("en-US")} events
|
|
3797
|
+
`);
|
|
3798
|
+
for (const [kind, n] of Object.entries(kindCounts)) {
|
|
3799
|
+
process.stdout.write(` ${kind}: ${n.toLocaleString("en-US")}
|
|
3800
|
+
`);
|
|
3801
|
+
}
|
|
3802
|
+
if (epoch) {
|
|
3803
|
+
process.stdout.write(` window: ${epoch.metrics.windowOldest} \u2192 ${epoch.metrics.windowNewest}
|
|
3804
|
+
`);
|
|
3805
|
+
}
|
|
3806
|
+
const handoff = buildVsCodeHandoff(vsCodeEvents, area.extractionId, (/* @__PURE__ */ new Date()).toISOString());
|
|
3807
|
+
const handoffPath = await writeHandoff(handoff);
|
|
3808
|
+
if (handoffPath) {
|
|
3809
|
+
process.stdout.write(
|
|
3810
|
+
` handoff: ${handoff.editDays.length} edit-days, ${handoff.chatSessions.length} chat sessions \u2192 ${handoffPath}
|
|
3811
|
+
|
|
3812
|
+
`
|
|
3813
|
+
);
|
|
3814
|
+
} else {
|
|
3815
|
+
process.stdout.write(" handoff: desktop app container not found \u2014 skipped\n\n");
|
|
3816
|
+
}
|
|
3817
|
+
} else {
|
|
3818
|
+
process.stdout.write(formatInventory(vsCodeInventory) + "\n\n");
|
|
3819
|
+
}
|
|
3820
|
+
const eventsFile = path8.join(area.root, "events.jsonl");
|
|
3821
|
+
await fs8.writeFile(eventsFile, allEvents.map((e) => JSON.stringify(e)).join("\n") + "\n", "utf8");
|
|
3822
|
+
process.stdout.write(`total extracted: ${allEvents.length.toLocaleString("en-US")} events
|
|
3823
|
+
`);
|
|
3824
|
+
process.stdout.write(`events file: ${eventsFile}
|
|
3825
|
+
`);
|
|
3826
|
+
if (!ship) {
|
|
3827
|
+
process.stdout.write("dry run \u2014 pass --ship to send these over the batch wire.\n");
|
|
3828
|
+
return 0;
|
|
3829
|
+
}
|
|
3830
|
+
const config = loadShipConfig();
|
|
3831
|
+
process.stdout.write(`shipping to ${config.apiBaseUrl} as ${config.toolInstallationId}\u2026
|
|
3832
|
+
`);
|
|
3833
|
+
const result = await shipEvents(allEvents, config, (done, total) => {
|
|
3834
|
+
process.stdout.write(` ${done.toLocaleString("en-US")}/${total.toLocaleString("en-US")}\r`);
|
|
3835
|
+
});
|
|
3836
|
+
process.stdout.write("\n");
|
|
3837
|
+
process.stdout.write(
|
|
3838
|
+
`shipped: sent=${result.sent} accepted=${result.accepted} duplicate=${result.duplicate} rejected=${result.rejected} httpFailures=${result.httpFailures}
|
|
3839
|
+
`
|
|
3840
|
+
);
|
|
3841
|
+
if (result.duplicate > 0) {
|
|
3842
|
+
process.stdout.write(
|
|
3843
|
+
` ${result.duplicate.toLocaleString("en-US")} already imported \u2014 the backend kept its existing copy and stored nothing new.
|
|
3844
|
+
`
|
|
3845
|
+
);
|
|
3846
|
+
}
|
|
3847
|
+
for (const [reason, n] of Object.entries(result.rejectionReasons)) {
|
|
3848
|
+
process.stdout.write(` rejected ${reason}: ${n}
|
|
3849
|
+
`);
|
|
3850
|
+
}
|
|
3851
|
+
return result.accepted + result.duplicate > 0 && result.rejected === 0 && result.httpFailures === 0 ? 0 : 1;
|
|
3852
|
+
}
|
|
3853
|
+
case "fix-retention": {
|
|
3854
|
+
const plan = await planClaudeRetentionFix(paths);
|
|
3855
|
+
if (!plan.changeNeeded) {
|
|
3856
|
+
process.stdout.write(
|
|
3857
|
+
`cleanupPeriodDays is already ${plan.currentCleanupPeriodDays} \u2014 nothing to do.
|
|
3858
|
+
`
|
|
3859
|
+
);
|
|
3860
|
+
return 0;
|
|
3861
|
+
}
|
|
3862
|
+
process.stdout.write(
|
|
3863
|
+
`${plan.settingsPath}: cleanupPeriodDays ${plan.currentCleanupPeriodDays ?? "(unset, 30 by default)"} \u2192 ${plan.proposedCleanupPeriodDays}
|
|
3864
|
+
`
|
|
3865
|
+
);
|
|
3866
|
+
if (rest.includes("--apply")) {
|
|
3867
|
+
await applyClaudeRetentionFix(plan);
|
|
3868
|
+
process.stdout.write("applied.\n");
|
|
3869
|
+
} else {
|
|
3870
|
+
process.stdout.write("dry run \u2014 pass --apply to write it.\n");
|
|
3871
|
+
}
|
|
3872
|
+
return 0;
|
|
3873
|
+
}
|
|
3874
|
+
default: {
|
|
3875
|
+
process.stderr.write(
|
|
3876
|
+
"usage: ascenda-history-import <scan [--json] | import [--ship] | fix-retention [--apply]>\n"
|
|
3877
|
+
);
|
|
3878
|
+
return command === void 0 || command === "--help" ? 0 : 2;
|
|
3879
|
+
}
|
|
3880
|
+
}
|
|
3881
|
+
}
|
|
3882
|
+
main().then(
|
|
3883
|
+
(code) => process.exit(code),
|
|
3884
|
+
(error) => {
|
|
3885
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
3886
|
+
`);
|
|
3887
|
+
process.exit(1);
|
|
3888
|
+
}
|
|
3889
|
+
);
|