qodewk 0.2.3 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -9
- package/dist/index.cjs +385 -96
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -78,15 +78,13 @@ npx qodewk audit --base origin/main --head HEAD --format markdown --out receipt.
|
|
|
78
78
|
|
|
79
79
|
Unlike single-vendor utilities (like Chit, which only support Claude Code CLI), **Qodewk operates globally across all major AI coding platforms**. It reads your local agent transcripts, matches them with your repository's Git revision graph, and proves what was actually shipped.
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
|
84
|
-
|
|
85
|
-
| **
|
|
86
|
-
| **
|
|
87
|
-
| **
|
|
88
|
-
| **Aider** | `.aider.chat.history.md` (Repository root) | Session prompts, model IDs, and code file diffs |
|
|
89
|
-
| **GitHub Copilot** | Git commit revision graph trailers (`Co-authored-by: Copilot`) | Commit-level author attribution |
|
|
81
|
+
| Platform | Local Data Source / Footprint | Captured Telemetry | Status |
|
|
82
|
+
|---|---|---|---|
|
|
83
|
+
| **Google Antigravity** | `~/.gemini/antigravity/conversation_summaries.db` & `brain/<id>/.../transcript.jsonl` | Shipped task titles, exact models (`Claude Opus 4.6 Thinking`, `Gemini 3.8 Flash`, etc.), tool calls (`replace_file_content`, `write_to_file`), and multi-turn tokens | **Tier A (Verified)** |
|
|
84
|
+
| **Claude Code CLI** | `~/.claude/projects/<slug>/sessions/*.jsonl` | User prompts, exact input/output/cached token usage, and `Edit`/`Write` file mutations | **Tier A (Verified)** |
|
|
85
|
+
| **Cursor IDE** | `~/.cursor/projects/<slug>/agent-transcripts/*.jsonl` & `workspaceStorage/*/state.vscdb` | Commit hash binding (`aiCodeTracking.recentCommit`), agent transcripts, tool edits, and token expenditures | **Tier A (Verified / Imported)** |
|
|
86
|
+
| **GitHub Copilot** | Git commit revision trailers (`Co-authored-by: Copilot`) | Commit-level author attribution | **Heuristic (Observed)** |
|
|
87
|
+
| **Aider** | `.aider.chat.history.md` (Repository root) | Session prompts, model IDs, and code file diffs | *Tier B (Experimental Roadmap)* |
|
|
90
88
|
|
|
91
89
|
### Key Capabilities
|
|
92
90
|
|
package/dist/index.cjs
CHANGED
|
@@ -13506,6 +13506,7 @@ async function extractGitMetrics(optionsOrPath = process.cwd()) {
|
|
|
13506
13506
|
let headSha = options.headSha || "0000000000000000000000000000000000000000";
|
|
13507
13507
|
let baseSha = options.baseSha;
|
|
13508
13508
|
let commitMessage = void 0;
|
|
13509
|
+
let commitDate = void 0;
|
|
13509
13510
|
let commitsCount = 1;
|
|
13510
13511
|
const sinceDate = parseSinceOption(options.since);
|
|
13511
13512
|
if (sinceDate && !options.baseSha) {
|
|
@@ -13537,6 +13538,7 @@ async function extractGitMetrics(optionsOrPath = process.cwd()) {
|
|
|
13537
13538
|
headSha = log.latest.hash;
|
|
13538
13539
|
}
|
|
13539
13540
|
commitMessage = log.latest.message;
|
|
13541
|
+
commitDate = log.latest.date;
|
|
13540
13542
|
if (!options.baseSha && log.all.length > 1 && log.all[1]) {
|
|
13541
13543
|
baseSha = log.all[1].hash;
|
|
13542
13544
|
}
|
|
@@ -13556,22 +13558,34 @@ async function extractGitMetrics(optionsOrPath = process.cwd()) {
|
|
|
13556
13558
|
const projectAlias = path.basename(path.resolve(repoPath)).toLowerCase().replace(/[^a-z0-9_-]/g, "-");
|
|
13557
13559
|
const repoHash = computeSaltedHash(repoIdentifier);
|
|
13558
13560
|
let diffSummary;
|
|
13559
|
-
|
|
13560
|
-
|
|
13561
|
-
|
|
13562
|
-
|
|
13561
|
+
try {
|
|
13562
|
+
if (baseSha && headSha && baseSha !== headSha) {
|
|
13563
|
+
const status = await git.status();
|
|
13564
|
+
if (status.files.length > 0) {
|
|
13565
|
+
diffSummary = await git.diffSummary([baseSha]);
|
|
13566
|
+
} else {
|
|
13567
|
+
diffSummary = await git.diffSummary([`${baseSha}...${headSha}`]).catch(async () => {
|
|
13568
|
+
return await git.diffSummary([`${baseSha}..${headSha}`]);
|
|
13569
|
+
});
|
|
13570
|
+
}
|
|
13563
13571
|
} else {
|
|
13564
|
-
|
|
13572
|
+
const status = await git.status();
|
|
13573
|
+
if (status.files.length > 0) {
|
|
13574
|
+
diffSummary = await git.diffSummary(["HEAD"]);
|
|
13575
|
+
} else if (baseSha) {
|
|
13576
|
+
diffSummary = await git.diffSummary([`${baseSha}..${headSha}`]);
|
|
13577
|
+
} else if (headSha !== "0000000000000000000000000000000000000000") {
|
|
13578
|
+
diffSummary = await git.diffSummary([`${headSha}~1..${headSha}`]).catch(async () => {
|
|
13579
|
+
return await git.diffSummary([headSha]);
|
|
13580
|
+
});
|
|
13581
|
+
} else {
|
|
13582
|
+
diffSummary = { changed: 0, insertions: 0, deletions: 0, files: [] };
|
|
13583
|
+
}
|
|
13565
13584
|
}
|
|
13566
|
-
}
|
|
13567
|
-
|
|
13568
|
-
if (status.files.length > 0) {
|
|
13585
|
+
} catch {
|
|
13586
|
+
try {
|
|
13569
13587
|
diffSummary = await git.diffSummary(["HEAD"]);
|
|
13570
|
-
}
|
|
13571
|
-
diffSummary = await git.diffSummary([`${baseSha}..${headSha}`]);
|
|
13572
|
-
} else if (headSha !== "0000000000000000000000000000000000000000") {
|
|
13573
|
-
diffSummary = await git.diffSummary([headSha]);
|
|
13574
|
-
} else {
|
|
13588
|
+
} catch {
|
|
13575
13589
|
diffSummary = { changed: 0, insertions: 0, deletions: 0, files: [] };
|
|
13576
13590
|
}
|
|
13577
13591
|
}
|
|
@@ -13621,6 +13635,7 @@ async function extractGitMetrics(optionsOrPath = process.cwd()) {
|
|
|
13621
13635
|
repoHash,
|
|
13622
13636
|
projectAlias,
|
|
13623
13637
|
commitMessage,
|
|
13638
|
+
commitDate,
|
|
13624
13639
|
commitsCount
|
|
13625
13640
|
};
|
|
13626
13641
|
}
|
|
@@ -13916,6 +13931,120 @@ function computeCost(inputTokens, outputTokens, cachedTokens, card) {
|
|
|
13916
13931
|
return Number((freshCost + cacheCost + outCost).toFixed(4));
|
|
13917
13932
|
}
|
|
13918
13933
|
|
|
13934
|
+
// ../core/dist/harvester/scoring.js
|
|
13935
|
+
function normalizeFilePath(filePath, repoPath) {
|
|
13936
|
+
let p2 = filePath.replace(/\\/g, "/").trim();
|
|
13937
|
+
p2 = p2.replace(/^file:\/\/\/?/, "");
|
|
13938
|
+
if (repoPath) {
|
|
13939
|
+
const normRepo = repoPath.replace(/\\/g, "/").replace(/\/$/, "");
|
|
13940
|
+
if (p2.toLowerCase().startsWith(normRepo.toLowerCase())) {
|
|
13941
|
+
p2 = p2.slice(normRepo.length);
|
|
13942
|
+
}
|
|
13943
|
+
}
|
|
13944
|
+
p2 = p2.replace(/^\.?\//, "");
|
|
13945
|
+
return p2.toLowerCase();
|
|
13946
|
+
}
|
|
13947
|
+
function scoreFootprint(fp, gitCtx) {
|
|
13948
|
+
if (!gitCtx) {
|
|
13949
|
+
return 0.5;
|
|
13950
|
+
}
|
|
13951
|
+
let score = 0.1;
|
|
13952
|
+
if (fp.boundCommitSha && gitCtx.headSha) {
|
|
13953
|
+
const bound = fp.boundCommitSha.toLowerCase().trim();
|
|
13954
|
+
const head = gitCtx.headSha.toLowerCase().trim();
|
|
13955
|
+
if (bound === head || bound.startsWith(head.slice(0, 7)) || head.startsWith(bound.slice(0, 7))) {
|
|
13956
|
+
score += 1;
|
|
13957
|
+
} else {
|
|
13958
|
+
score -= 0.5;
|
|
13959
|
+
}
|
|
13960
|
+
}
|
|
13961
|
+
if (gitCtx.changedFiles && gitCtx.changedFiles.length > 0) {
|
|
13962
|
+
const normChanged = gitCtx.changedFiles.map((f) => normalizeFilePath(f, fp.repoPath));
|
|
13963
|
+
const normEdited = (fp.filesEdited || []).map((f) => normalizeFilePath(f, fp.repoPath));
|
|
13964
|
+
if (normEdited.length > 0) {
|
|
13965
|
+
let matches = 0;
|
|
13966
|
+
for (const cf of normChanged) {
|
|
13967
|
+
const found = normEdited.some((ef) => ef === cf || cf.endsWith("/" + ef) || ef.endsWith("/" + cf) || cf.endsWith(ef) || ef.endsWith(cf));
|
|
13968
|
+
if (found)
|
|
13969
|
+
matches++;
|
|
13970
|
+
}
|
|
13971
|
+
const ratio = matches / normChanged.length;
|
|
13972
|
+
if (matches > 0) {
|
|
13973
|
+
score += ratio * 0.4;
|
|
13974
|
+
} else {
|
|
13975
|
+
score -= 0.15;
|
|
13976
|
+
}
|
|
13977
|
+
}
|
|
13978
|
+
}
|
|
13979
|
+
if (gitCtx.commitDate && fp.timestamp) {
|
|
13980
|
+
try {
|
|
13981
|
+
const commitTime = new Date(gitCtx.commitDate).getTime();
|
|
13982
|
+
const fpTime = new Date(fp.timestamp).getTime();
|
|
13983
|
+
if (!isNaN(commitTime) && !isNaN(fpTime)) {
|
|
13984
|
+
const diffMinutes = Math.abs(commitTime - fpTime) / (60 * 1e3);
|
|
13985
|
+
if (diffMinutes <= 15) {
|
|
13986
|
+
score += 0.25;
|
|
13987
|
+
} else if (diffMinutes <= 60) {
|
|
13988
|
+
score += 0.2;
|
|
13989
|
+
} else if (diffMinutes <= 180) {
|
|
13990
|
+
score += 0.15;
|
|
13991
|
+
} else if (diffMinutes <= 720) {
|
|
13992
|
+
score += 0.08;
|
|
13993
|
+
} else if (diffMinutes <= 1440) {
|
|
13994
|
+
score += 0.03;
|
|
13995
|
+
} else {
|
|
13996
|
+
score -= 0.1;
|
|
13997
|
+
}
|
|
13998
|
+
}
|
|
13999
|
+
} catch {
|
|
14000
|
+
}
|
|
14001
|
+
}
|
|
14002
|
+
switch (fp.mode) {
|
|
14003
|
+
case "verified":
|
|
14004
|
+
score += 0.1;
|
|
14005
|
+
break;
|
|
14006
|
+
case "observed":
|
|
14007
|
+
score += 0.08;
|
|
14008
|
+
break;
|
|
14009
|
+
case "imported":
|
|
14010
|
+
score += 0.05;
|
|
14011
|
+
break;
|
|
14012
|
+
case "estimated":
|
|
14013
|
+
score += 0.02;
|
|
14014
|
+
break;
|
|
14015
|
+
default:
|
|
14016
|
+
break;
|
|
14017
|
+
}
|
|
14018
|
+
return Math.max(0, Math.round(score * 100) / 100);
|
|
14019
|
+
}
|
|
14020
|
+
function selectPrimaryFootprint(footprints, gitCtx) {
|
|
14021
|
+
if (footprints.length === 0) {
|
|
14022
|
+
return { primary: void 0, rankedFootprints: [] };
|
|
14023
|
+
}
|
|
14024
|
+
if (!gitCtx || !gitCtx.headSha && !gitCtx.commitDate && (!gitCtx.changedFiles || gitCtx.changedFiles.length === 0)) {
|
|
14025
|
+
const sorted2 = [...footprints].sort((a, b2) => new Date(b2.timestamp).getTime() - new Date(a.timestamp).getTime());
|
|
14026
|
+
return { primary: sorted2[0], rankedFootprints: sorted2 };
|
|
14027
|
+
}
|
|
14028
|
+
const scored = footprints.map((fp) => {
|
|
14029
|
+
const score = scoreFootprint(fp, gitCtx);
|
|
14030
|
+
return {
|
|
14031
|
+
...fp,
|
|
14032
|
+
attributionScore: score
|
|
14033
|
+
};
|
|
14034
|
+
});
|
|
14035
|
+
scored.sort((a, b2) => {
|
|
14036
|
+
const scoreDiff = (b2.attributionScore || 0) - (a.attributionScore || 0);
|
|
14037
|
+
if (Math.abs(scoreDiff) > 1e-3) {
|
|
14038
|
+
return scoreDiff;
|
|
14039
|
+
}
|
|
14040
|
+
return new Date(b2.timestamp).getTime() - new Date(a.timestamp).getTime();
|
|
14041
|
+
});
|
|
14042
|
+
return {
|
|
14043
|
+
primary: scored[0],
|
|
14044
|
+
rankedFootprints: scored
|
|
14045
|
+
};
|
|
14046
|
+
}
|
|
14047
|
+
|
|
13919
14048
|
// ../core/dist/harvester/antigravity.js
|
|
13920
14049
|
var import_meta = {};
|
|
13921
14050
|
function getSqliteDatabase() {
|
|
@@ -14002,7 +14131,7 @@ function harvestAntigravityFootprints(repoPath, sinceDate) {
|
|
|
14002
14131
|
if ((tc.name === "replace_file_content" || tc.name === "write_to_file") && tc.args) {
|
|
14003
14132
|
const file = tc.args.TargetFile || tc.args.targetFile;
|
|
14004
14133
|
if (file) {
|
|
14005
|
-
filesEdited.add(
|
|
14134
|
+
filesEdited.add(normalizeFilePath(String(file), repoPath));
|
|
14006
14135
|
}
|
|
14007
14136
|
}
|
|
14008
14137
|
}
|
|
@@ -14094,7 +14223,7 @@ function harvestClaudeFootprints(repoPath, projectAlias, sinceDate) {
|
|
|
14094
14223
|
}
|
|
14095
14224
|
if (data.tool_name === "Edit" || data.tool_name === "Write") {
|
|
14096
14225
|
if (data.tool_input?.file_path) {
|
|
14097
|
-
filesEdited.add(
|
|
14226
|
+
filesEdited.add(normalizeFilePath(String(data.tool_input.file_path), repoPath));
|
|
14098
14227
|
}
|
|
14099
14228
|
}
|
|
14100
14229
|
} catch {
|
|
@@ -14154,88 +14283,236 @@ function getSqliteDatabase2() {
|
|
|
14154
14283
|
}
|
|
14155
14284
|
function harvestCursorFootprints(repoPath, sinceDate) {
|
|
14156
14285
|
const footprints = [];
|
|
14157
|
-
const
|
|
14158
|
-
|
|
14159
|
-
return footprints;
|
|
14160
|
-
let storageDir = "";
|
|
14161
|
-
if (process.platform === "win32") {
|
|
14162
|
-
storageDir = path5.join(process.env.APPDATA || path5.join(os4.homedir(), "AppData", "Roaming"), "Cursor", "User", "workspaceStorage");
|
|
14163
|
-
} else if (process.platform === "darwin") {
|
|
14164
|
-
storageDir = path5.join(os4.homedir(), "Library", "Application Support", "Cursor", "User", "workspaceStorage");
|
|
14165
|
-
} else {
|
|
14166
|
-
storageDir = path5.join(os4.homedir(), ".config", "Cursor", "User", "workspaceStorage");
|
|
14167
|
-
}
|
|
14168
|
-
if (!fs4.existsSync(storageDir))
|
|
14169
|
-
return footprints;
|
|
14286
|
+
const normTarget = repoPath.toLowerCase().replace(/\\/g, "/").replace(/\/$/, "");
|
|
14287
|
+
const targetBasename = path5.basename(repoPath).toLowerCase();
|
|
14170
14288
|
try {
|
|
14171
|
-
const
|
|
14172
|
-
|
|
14173
|
-
|
|
14174
|
-
const
|
|
14175
|
-
|
|
14176
|
-
|
|
14177
|
-
|
|
14178
|
-
|
|
14179
|
-
|
|
14180
|
-
|
|
14181
|
-
|
|
14182
|
-
|
|
14183
|
-
|
|
14184
|
-
|
|
14185
|
-
|
|
14186
|
-
|
|
14187
|
-
|
|
14188
|
-
|
|
14189
|
-
|
|
14190
|
-
|
|
14191
|
-
|
|
14192
|
-
|
|
14193
|
-
|
|
14194
|
-
|
|
14195
|
-
|
|
14196
|
-
|
|
14197
|
-
|
|
14198
|
-
|
|
14199
|
-
|
|
14200
|
-
|
|
14201
|
-
|
|
14202
|
-
|
|
14203
|
-
|
|
14204
|
-
|
|
14289
|
+
const cursorProjectsDir = path5.join(os4.homedir(), ".cursor", "projects");
|
|
14290
|
+
if (fs4.existsSync(cursorProjectsDir)) {
|
|
14291
|
+
const projectDirs = fs4.readdirSync(cursorProjectsDir);
|
|
14292
|
+
for (const pDir of projectDirs) {
|
|
14293
|
+
const cleanPDir = pDir.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
14294
|
+
const cleanTarget = normTarget.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
14295
|
+
const cleanBase = targetBasename.replace(/[^a-z0-9]/g, "");
|
|
14296
|
+
if (cleanPDir.includes(cleanTarget) || cleanTarget.includes(cleanPDir) || cleanPDir.endsWith(cleanBase)) {
|
|
14297
|
+
const transcriptsDir = path5.join(cursorProjectsDir, pDir, "agent-transcripts");
|
|
14298
|
+
if (!fs4.existsSync(transcriptsDir))
|
|
14299
|
+
continue;
|
|
14300
|
+
const sessionDirs = fs4.readdirSync(transcriptsDir);
|
|
14301
|
+
for (const sDir of sessionDirs) {
|
|
14302
|
+
const sPath = path5.join(transcriptsDir, sDir);
|
|
14303
|
+
if (!fs4.statSync(sPath).isDirectory())
|
|
14304
|
+
continue;
|
|
14305
|
+
const jsonlFile = path5.join(sPath, `${sDir}.jsonl`);
|
|
14306
|
+
if (!fs4.existsSync(jsonlFile))
|
|
14307
|
+
continue;
|
|
14308
|
+
const stat = fs4.statSync(jsonlFile);
|
|
14309
|
+
if (sinceDate && stat.mtime < sinceDate)
|
|
14310
|
+
continue;
|
|
14311
|
+
try {
|
|
14312
|
+
const content = fs4.readFileSync(jsonlFile, "utf-8");
|
|
14313
|
+
const lines = content.split("\n");
|
|
14314
|
+
let stepCount = 0;
|
|
14315
|
+
let taskTitle = "";
|
|
14316
|
+
let model = "claude-3-5-sonnet";
|
|
14317
|
+
const filesEditedSet = /* @__PURE__ */ new Set();
|
|
14318
|
+
for (const line of lines) {
|
|
14319
|
+
if (!line.trim())
|
|
14320
|
+
continue;
|
|
14321
|
+
try {
|
|
14322
|
+
const entry = JSON.parse(line);
|
|
14323
|
+
if (!taskTitle && entry.role === "user" && entry.message?.content) {
|
|
14324
|
+
for (const c3 of entry.message.content) {
|
|
14325
|
+
if (c3.type === "text" && c3.text) {
|
|
14326
|
+
const m = c3.text.match(/<user_query>\s*([\s\S]*?)\s*<\/user_query>/);
|
|
14327
|
+
if (m && m[1]) {
|
|
14328
|
+
taskTitle = m[1].replace(/\n/g, " ").trim().slice(0, 60);
|
|
14329
|
+
} else if (!c3.text.startsWith("<timestamp>")) {
|
|
14330
|
+
taskTitle = c3.text.replace(/\n/g, " ").trim().slice(0, 60);
|
|
14331
|
+
}
|
|
14332
|
+
}
|
|
14333
|
+
}
|
|
14334
|
+
}
|
|
14335
|
+
if (entry.role === "assistant" && entry.message?.content) {
|
|
14336
|
+
for (const c3 of entry.message.content) {
|
|
14337
|
+
if (c3.type === "tool_use") {
|
|
14338
|
+
stepCount++;
|
|
14339
|
+
const name = (c3.name || "").toLowerCase();
|
|
14340
|
+
const inp = c3.input || {};
|
|
14341
|
+
if (name.includes("replace") || name.includes("edit") || name.includes("write") || name.includes("diff") || name.includes("apply")) {
|
|
14342
|
+
const target = inp.path || inp.file_path || inp.target_file || inp.file;
|
|
14343
|
+
if (target && typeof target === "string") {
|
|
14344
|
+
filesEditedSet.add(normalizeFilePath(target, repoPath));
|
|
14345
|
+
}
|
|
14346
|
+
}
|
|
14347
|
+
}
|
|
14348
|
+
}
|
|
14349
|
+
}
|
|
14350
|
+
} catch {
|
|
14351
|
+
}
|
|
14352
|
+
}
|
|
14353
|
+
if (stepCount > 0) {
|
|
14354
|
+
const rateCard = getRateCard(model);
|
|
14355
|
+
const estInput = stepCount * 3e3;
|
|
14356
|
+
const estCached = Math.round(estInput * 0.6);
|
|
14357
|
+
const estOutput = stepCount * 450;
|
|
14358
|
+
const cost = computeCost(estInput, estOutput, estCached, rateCard);
|
|
14359
|
+
footprints.push({
|
|
14360
|
+
id: `cursor_transcript_${sDir}`,
|
|
14361
|
+
platform: "cursor",
|
|
14362
|
+
sessionId: sDir,
|
|
14363
|
+
repoPath,
|
|
14364
|
+
taskTitle: taskTitle || "Cursor Agent Session",
|
|
14365
|
+
model,
|
|
14366
|
+
stepsCount: stepCount,
|
|
14367
|
+
filesEdited: Array.from(filesEditedSet),
|
|
14368
|
+
timestamp: stat.mtime.toISOString(),
|
|
14369
|
+
tokens: {
|
|
14370
|
+
input: estInput,
|
|
14371
|
+
output: estOutput,
|
|
14372
|
+
cached: estCached
|
|
14373
|
+
},
|
|
14374
|
+
cost,
|
|
14375
|
+
mode: "imported",
|
|
14376
|
+
confidence: 0.85,
|
|
14377
|
+
rawTranscriptPath: jsonlFile
|
|
14378
|
+
});
|
|
14379
|
+
}
|
|
14380
|
+
} catch {
|
|
14205
14381
|
}
|
|
14206
14382
|
}
|
|
14207
|
-
|
|
14208
|
-
|
|
14209
|
-
|
|
14210
|
-
|
|
14211
|
-
|
|
14212
|
-
|
|
14213
|
-
|
|
14214
|
-
|
|
14215
|
-
|
|
14216
|
-
|
|
14217
|
-
|
|
14218
|
-
|
|
14219
|
-
|
|
14220
|
-
|
|
14221
|
-
|
|
14222
|
-
|
|
14223
|
-
|
|
14224
|
-
|
|
14225
|
-
|
|
14226
|
-
|
|
14227
|
-
|
|
14228
|
-
|
|
14229
|
-
|
|
14230
|
-
|
|
14231
|
-
|
|
14232
|
-
|
|
14383
|
+
}
|
|
14384
|
+
}
|
|
14385
|
+
}
|
|
14386
|
+
} catch {
|
|
14387
|
+
}
|
|
14388
|
+
const DatabaseSync2 = getSqliteDatabase2();
|
|
14389
|
+
if (DatabaseSync2) {
|
|
14390
|
+
let storageDir = "";
|
|
14391
|
+
if (process.platform === "win32") {
|
|
14392
|
+
storageDir = path5.join(process.env.APPDATA || path5.join(os4.homedir(), "AppData", "Roaming"), "Cursor", "User", "workspaceStorage");
|
|
14393
|
+
} else if (process.platform === "darwin") {
|
|
14394
|
+
storageDir = path5.join(os4.homedir(), "Library", "Application Support", "Cursor", "User", "workspaceStorage");
|
|
14395
|
+
} else {
|
|
14396
|
+
storageDir = path5.join(os4.homedir(), ".config", "Cursor", "User", "workspaceStorage");
|
|
14397
|
+
}
|
|
14398
|
+
if (fs4.existsSync(storageDir)) {
|
|
14399
|
+
try {
|
|
14400
|
+
const wsDirs = fs4.readdirSync(storageDir);
|
|
14401
|
+
for (const ws of wsDirs) {
|
|
14402
|
+
const wsPath = path5.join(storageDir, ws);
|
|
14403
|
+
const wsJsonPath = path5.join(wsPath, "workspace.json");
|
|
14404
|
+
if (!fs4.existsSync(wsJsonPath))
|
|
14405
|
+
continue;
|
|
14406
|
+
try {
|
|
14407
|
+
const wsJson = JSON.parse(fs4.readFileSync(wsJsonPath, "utf-8"));
|
|
14408
|
+
let folderUri = wsJson.folder || wsJson.workspace || "";
|
|
14409
|
+
try {
|
|
14410
|
+
folderUri = decodeURIComponent(folderUri.replace(/^file:\/\/\/?/, ""));
|
|
14411
|
+
} catch {
|
|
14412
|
+
}
|
|
14413
|
+
const folder = folderUri.toLowerCase().replace(/\\/g, "/");
|
|
14414
|
+
if (!folder.includes(normTarget) && !normTarget.includes(folder) && !folder.endsWith(targetBasename)) {
|
|
14415
|
+
continue;
|
|
14416
|
+
}
|
|
14417
|
+
const dbFile = path5.join(wsPath, "state.vscdb");
|
|
14418
|
+
if (!fs4.existsSync(dbFile))
|
|
14419
|
+
continue;
|
|
14420
|
+
const dbStat = fs4.statSync(dbFile);
|
|
14421
|
+
if (sinceDate && dbStat.mtime < sinceDate)
|
|
14422
|
+
continue;
|
|
14423
|
+
const db = new DatabaseSync2(dbFile, { readOnly: true });
|
|
14424
|
+
try {
|
|
14425
|
+
const rowRecent = db.prepare("SELECT value FROM ItemTable WHERE key = 'aiCodeTracking.recentCommit' LIMIT 1").get();
|
|
14426
|
+
if (rowRecent && rowRecent.value) {
|
|
14427
|
+
const recent = JSON.parse(rowRecent.value);
|
|
14428
|
+
if (recent && recent.commitHash) {
|
|
14429
|
+
const added = Number(recent.linesAdded || recent.composerLinesAdded || 0);
|
|
14430
|
+
const deleted = Number(recent.linesDeleted || recent.composerLinesDeleted || 0);
|
|
14431
|
+
const estOut = Math.max(Math.round((added + deleted) * 35 / 4), 100);
|
|
14432
|
+
const estIn = estOut * 20;
|
|
14433
|
+
const estCached = Math.round(estIn * 0.6);
|
|
14434
|
+
const rateCard = getRateCard("claude-3-7-sonnet");
|
|
14435
|
+
const cost = computeCost(estIn, estOut, estCached, rateCard);
|
|
14436
|
+
const commitDate = recent.commitDate ? new Date(recent.commitDate).toISOString() : recent.timestamp ? new Date(recent.timestamp).toISOString() : dbStat.mtime.toISOString();
|
|
14437
|
+
footprints.push({
|
|
14438
|
+
id: `cursor_commit_${recent.commitHash.slice(0, 12)}`,
|
|
14439
|
+
platform: "cursor",
|
|
14440
|
+
sessionId: ws,
|
|
14441
|
+
repoPath,
|
|
14442
|
+
taskTitle: recent.commitMessage || "Cursor AI Commit",
|
|
14443
|
+
model: "claude-3-7-sonnet",
|
|
14444
|
+
stepsCount: Math.max(Math.round((added + deleted) / 10), 1),
|
|
14445
|
+
filesEdited: [],
|
|
14446
|
+
timestamp: commitDate,
|
|
14447
|
+
boundCommitSha: recent.commitHash,
|
|
14448
|
+
tokens: {
|
|
14449
|
+
input: estIn,
|
|
14450
|
+
output: estOut,
|
|
14451
|
+
cached: estCached
|
|
14452
|
+
},
|
|
14453
|
+
cost,
|
|
14454
|
+
mode: "verified",
|
|
14455
|
+
confidence: 0.95,
|
|
14456
|
+
rawTranscriptPath: dbFile
|
|
14457
|
+
});
|
|
14458
|
+
}
|
|
14459
|
+
}
|
|
14460
|
+
} catch {
|
|
14461
|
+
}
|
|
14462
|
+
const rowLegacy = db.prepare("SELECT value FROM ItemTable WHERE key = 'workbench.panel.aichat.chatdata' OR key = 'aiService.generations' LIMIT 1").get();
|
|
14463
|
+
if (rowLegacy && rowLegacy.value) {
|
|
14464
|
+
try {
|
|
14465
|
+
const parsed = JSON.parse(rowLegacy.value);
|
|
14466
|
+
const tabs = parsed.tabs || (Array.isArray(parsed) ? parsed : []);
|
|
14467
|
+
let stepCount = 0;
|
|
14468
|
+
let taskTitle = "";
|
|
14469
|
+
let model = "claude-3-5-sonnet";
|
|
14470
|
+
for (const tab of tabs) {
|
|
14471
|
+
const bubbles = tab.bubbles || [];
|
|
14472
|
+
stepCount += bubbles.length;
|
|
14473
|
+
if (!taskTitle && tab.chatTitle)
|
|
14474
|
+
taskTitle = tab.chatTitle;
|
|
14475
|
+
for (const b2 of bubbles) {
|
|
14476
|
+
if (b2.modelType)
|
|
14477
|
+
model = b2.modelType;
|
|
14478
|
+
}
|
|
14479
|
+
}
|
|
14480
|
+
if (stepCount > 0) {
|
|
14481
|
+
const rateCard = getRateCard(model);
|
|
14482
|
+
const estInput = stepCount * 2500;
|
|
14483
|
+
const estCached = Math.round(estInput * 0.6);
|
|
14484
|
+
const estOutput = stepCount * 250;
|
|
14485
|
+
const cost = computeCost(estInput, estOutput, estCached, rateCard);
|
|
14486
|
+
footprints.push({
|
|
14487
|
+
id: `cursor_chat_${ws}`,
|
|
14488
|
+
platform: "cursor",
|
|
14489
|
+
sessionId: ws,
|
|
14490
|
+
repoPath,
|
|
14491
|
+
taskTitle: taskTitle || "Cursor Chat Session",
|
|
14492
|
+
model,
|
|
14493
|
+
stepsCount: stepCount,
|
|
14494
|
+
filesEdited: [],
|
|
14495
|
+
timestamp: dbStat.mtime.toISOString(),
|
|
14496
|
+
tokens: {
|
|
14497
|
+
input: estInput,
|
|
14498
|
+
output: estOutput,
|
|
14499
|
+
cached: estCached
|
|
14500
|
+
},
|
|
14501
|
+
cost,
|
|
14502
|
+
mode: "imported",
|
|
14503
|
+
confidence: 0.8,
|
|
14504
|
+
rawTranscriptPath: dbFile
|
|
14505
|
+
});
|
|
14506
|
+
}
|
|
14507
|
+
} catch {
|
|
14508
|
+
}
|
|
14509
|
+
}
|
|
14510
|
+
} catch {
|
|
14233
14511
|
}
|
|
14234
14512
|
}
|
|
14235
14513
|
} catch {
|
|
14236
14514
|
}
|
|
14237
14515
|
}
|
|
14238
|
-
} catch {
|
|
14239
14516
|
}
|
|
14240
14517
|
return footprints;
|
|
14241
14518
|
}
|
|
@@ -14440,7 +14717,8 @@ async function harvestUniversalFootprints(options) {
|
|
|
14440
14717
|
const cursorFootprints = harvestCursorFootprints(options.repoPath, sinceDate);
|
|
14441
14718
|
allFootprints.push(...cursorFootprints);
|
|
14442
14719
|
}
|
|
14443
|
-
|
|
14720
|
+
const { primary, rankedFootprints } = selectPrimaryFootprint(allFootprints, options.gitContext);
|
|
14721
|
+
allFootprints = rankedFootprints;
|
|
14444
14722
|
try {
|
|
14445
14723
|
const db = new LocalStateDB();
|
|
14446
14724
|
for (const fp of allFootprints) {
|
|
@@ -14467,7 +14745,6 @@ async function harvestUniversalFootprints(options) {
|
|
|
14467
14745
|
totalCached += fp.tokens.cached;
|
|
14468
14746
|
totalCost += fp.cost;
|
|
14469
14747
|
}
|
|
14470
|
-
const primary = allFootprints[0];
|
|
14471
14748
|
return {
|
|
14472
14749
|
footprints: allFootprints,
|
|
14473
14750
|
primaryFootprint: primary,
|
|
@@ -14501,16 +14778,16 @@ function estimateCost(options) {
|
|
|
14501
14778
|
totalCost += s.cost;
|
|
14502
14779
|
}
|
|
14503
14780
|
return {
|
|
14504
|
-
provider: primary.provider,
|
|
14505
|
-
model: primary.model || "claude-3-7-sonnet",
|
|
14781
|
+
provider: options.provider || primary.provider,
|
|
14782
|
+
model: options.model || primary.model || "claude-3-7-sonnet",
|
|
14506
14783
|
tokens: {
|
|
14507
14784
|
input: totalInput,
|
|
14508
14785
|
output: totalOutput,
|
|
14509
14786
|
cached: totalCached
|
|
14510
14787
|
},
|
|
14511
14788
|
cost: Number(totalCost.toFixed(4)),
|
|
14512
|
-
mode: primary.mode,
|
|
14513
|
-
confidence: primary.confidence,
|
|
14789
|
+
mode: options.mode || primary.mode,
|
|
14790
|
+
confidence: options.confidence !== void 0 ? options.confidence : primary.confidence,
|
|
14514
14791
|
sessions: options.sessions
|
|
14515
14792
|
};
|
|
14516
14793
|
}
|
|
@@ -14630,7 +14907,13 @@ async function generateReceipt(options = {}) {
|
|
|
14630
14907
|
repoPath,
|
|
14631
14908
|
projectAlias: metrics.projectAlias,
|
|
14632
14909
|
since: options.since,
|
|
14633
|
-
platform: options.platform
|
|
14910
|
+
platform: options.platform,
|
|
14911
|
+
gitContext: {
|
|
14912
|
+
headSha: metrics.headSha,
|
|
14913
|
+
commitDate: metrics.commitDate,
|
|
14914
|
+
changedFiles: metrics.changedFiles,
|
|
14915
|
+
branch: metrics.branch
|
|
14916
|
+
}
|
|
14634
14917
|
});
|
|
14635
14918
|
const primaryFp = harvestResult.primaryFootprint;
|
|
14636
14919
|
const detected = detectProviderFromCommit(metrics.commitMessage);
|
|
@@ -15073,6 +15356,12 @@ function buildTerminalReceipt(receipt, publicUrl) {
|
|
|
15073
15356
|
const provString = receipt.ai.provider + " \xB7 " + (receipt.ai.model || "Unknown");
|
|
15074
15357
|
const cleanProv = provString.length > 40 ? provString.slice(0, 39) + "\u2026" : provString;
|
|
15075
15358
|
printRow(`Provider: ${cleanProv}`);
|
|
15359
|
+
if (receipt.ai.sessions && receipt.ai.sessions.length > 1) {
|
|
15360
|
+
const others = receipt.ai.sessions.slice(1).map((s) => s.provider).filter((p2, i2, arr) => arr.indexOf(p2) === i2 && p2 !== receipt.ai.provider);
|
|
15361
|
+
if (others.length > 0) {
|
|
15362
|
+
printRow(import_picocolors.default.dim(`Also seen: ${others.join(", ")}`));
|
|
15363
|
+
}
|
|
15364
|
+
}
|
|
15076
15365
|
if (receipt.ai.aiWrittenRatio !== void 0) {
|
|
15077
15366
|
const aiPct = Math.round(receipt.ai.aiWrittenRatio * 100);
|
|
15078
15367
|
printRowSplit("AI Written Code", `${aiPct}% (Human: ${100 - aiPct}%)`);
|