@apdesign/cursor-roi-tracker 0.5.3 → 0.5.4

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 CHANGED
@@ -89,6 +89,8 @@ npx cursor-roi-install-hooks
89
89
  ```json
90
90
  {
91
91
  "highThreshold": 0.85,
92
+ "bulkInsertThreshold": 8,
93
+ "pureDeleteThreshold": 8,
92
94
  "sourceExtensions": [".ts", ".tsx", ".js", ".jsx", ".vue"],
93
95
  "excludeRegexes": ["(^|/)dist/", "(^|/)build/", "\\.min\\."],
94
96
  "eventsDirectory": [".cursor/animus-ai-events.jsonl", ".cursor/local-ai-events"],
@@ -103,6 +105,8 @@ npx cursor-roi-install-hooks
103
105
  ### 关键字段说明
104
106
 
105
107
  - `highThreshold`:判定 AI 归因命中阈值
108
+ - `bulkInsertThreshold`:扩展记录 `agent_bulk_insert` 的最小插入行数(默认 8)
109
+ - `pureDeleteThreshold`:扩展记录 `agent_pure_delete` 的最小删除行数(默认 8)
106
110
  - `sourceExtensions`:参与统计的代码文件后缀
107
111
  - `excludeRegexes`:排除路径/文件规则(正则字符串)
108
112
  - `eventsDirectory`:AI 事件文件路径(支持多个)
@@ -2,11 +2,12 @@ const fs = require("fs");
2
2
  const path = require("path");
3
3
  const vscode = require("vscode");
4
4
  const crypto = require("crypto");
5
+ const { loadConfig } = require("../src/config");
5
6
  const { acquireMutexLock, releaseMutexLock, resolveEventLockPath } = require("../src/mutex-lock");
6
7
 
7
8
  const EVENT_FILE_RELATIVE_PATH = path.join(".cursor", "animus-ai-events.jsonl");
8
- const BULK_INSERT_THRESHOLD = 15;
9
- const PURE_DELETE_THRESHOLD = 15;
9
+ const DEFAULT_BULK_INSERT_THRESHOLD = 8;
10
+ const DEFAULT_PURE_DELETE_THRESHOLD = 8;
10
11
  const DEBOUNCE_WINDOW_MS = 200;
11
12
  const NEARBY_LINE_DISTANCE = 8;
12
13
  const writeChainsByFile = new Map();
@@ -68,11 +69,13 @@ function handleDocumentChanged(event) {
68
69
  }
69
70
 
70
71
  const eventFilePath = path.join(workspaceFolder.uri.fsPath, EVENT_FILE_RELATIVE_PATH);
72
+ const thresholds = resolveThresholds(workspaceFolder.uri.fsPath);
71
73
  const payloads = buildEventPayloads({
72
74
  change,
73
75
  relativePath,
74
76
  insertLines,
75
77
  deletedLines,
78
+ thresholds,
76
79
  });
77
80
  for (const payload of payloads) {
78
81
  enqueueDebouncedEvent(eventFilePath, payload);
@@ -127,7 +130,7 @@ function enqueueJsonlAppend(filePath, payload) {
127
130
  writeChainsByFile.set(filePath, next);
128
131
  }
129
132
 
130
- function buildEventPayloads({ change, relativePath, insertLines, deletedLines }) {
133
+ function buildEventPayloads({ change, relativePath, insertLines, deletedLines, thresholds }) {
131
134
  const payloads = [];
132
135
  const now = Date.now();
133
136
  const insertStartLine = change.range.start.line + 1;
@@ -135,7 +138,16 @@ function buildEventPayloads({ change, relativePath, insertLines, deletedLines })
135
138
  const deleteStartLine = change.range.start.line + 1;
136
139
  const deleteEndLine = deletedLines > 0 ? deleteStartLine + deletedLines - 1 : null;
137
140
 
138
- if (insertLines > BULK_INSERT_THRESHOLD) {
141
+ const bulkInsertThreshold = positiveIntegerOrFallback(
142
+ thresholds?.bulkInsertThreshold,
143
+ DEFAULT_BULK_INSERT_THRESHOLD
144
+ );
145
+ const pureDeleteThreshold = positiveIntegerOrFallback(
146
+ thresholds?.pureDeleteThreshold,
147
+ DEFAULT_PURE_DELETE_THRESHOLD
148
+ );
149
+
150
+ if (insertLines >= bulkInsertThreshold) {
139
151
  payloads.push({
140
152
  eventId: crypto.randomUUID(),
141
153
  type: "agent_bulk_insert",
@@ -150,7 +162,7 @@ function buildEventPayloads({ change, relativePath, insertLines, deletedLines })
150
162
  });
151
163
  }
152
164
 
153
- if (insertLines === 0 && deletedLines > PURE_DELETE_THRESHOLD) {
165
+ if (insertLines === 0 && deletedLines >= pureDeleteThreshold) {
154
166
  payloads.push({
155
167
  eventId: crypto.randomUUID(),
156
168
  type: "agent_pure_delete",
@@ -166,6 +178,29 @@ function buildEventPayloads({ change, relativePath, insertLines, deletedLines })
166
178
  return payloads;
167
179
  }
168
180
 
181
+ function resolveThresholds(repoRoot) {
182
+ try {
183
+ const config = loadConfig(repoRoot);
184
+ return {
185
+ bulkInsertThreshold: config.bulkInsertThreshold,
186
+ pureDeleteThreshold: config.pureDeleteThreshold,
187
+ };
188
+ } catch (_error) {
189
+ return {
190
+ bulkInsertThreshold: DEFAULT_BULK_INSERT_THRESHOLD,
191
+ pureDeleteThreshold: DEFAULT_PURE_DELETE_THRESHOLD,
192
+ };
193
+ }
194
+ }
195
+
196
+ function positiveIntegerOrFallback(value, fallback) {
197
+ const num = Number(value);
198
+ if (Number.isFinite(num) && num > 0) {
199
+ return Math.floor(num);
200
+ }
201
+ return fallback;
202
+ }
203
+
169
204
  function resolveRepoRootFromEventFile(filePath) {
170
205
  return path.dirname(path.dirname(filePath));
171
206
  }
@@ -2,7 +2,7 @@
2
2
  "name": "animus-cursor-roi-tracker",
3
3
  "displayName": "Animus Cursor ROI Tracker",
4
4
  "description": "Silently captures bulk agent insert events for ROI attribution.",
5
- "version": "0.1.0",
5
+ "version": "0.1.2",
6
6
  "publisher": "animus",
7
7
  "engines": {
8
8
  "vscode": "^1.74.0"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@apdesign/cursor-roi-tracker",
3
- "version": "0.5.3",
3
+ "version": "0.5.4",
4
4
  "description": "Collect commit-level AI coding metrics from staged diff and local AI events",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",
@@ -161,6 +161,8 @@ function ensureDefaultConfig(repoRoot) {
161
161
  }
162
162
  const payload = {
163
163
  highThreshold: 0.85,
164
+ bulkInsertThreshold: 8,
165
+ pureDeleteThreshold: 8,
164
166
  sourceExtensions: [".ts", ".tsx", ".js", ".jsx", ".vue", ".css", ".scss", ".html"],
165
167
  excludeRegexes: [
166
168
  "(^|/)dist/",
package/src/config.js CHANGED
@@ -9,6 +9,8 @@ const {
9
9
  function loadConfig(repoRoot) {
10
10
  const defaults = {
11
11
  highThreshold: 0.85,
12
+ bulkInsertThreshold: 8,
13
+ pureDeleteThreshold: 8,
12
14
  sourceExtensions: DEFAULT_SOURCE_EXTENSIONS,
13
15
  excludeRegexes: DEFAULT_EXCLUDE_PATTERNS.map((item) => item.source),
14
16
  eventsDirectory: [".cursor/animus-ai-events.jsonl", ".cursor/local-ai-events"],
@@ -31,6 +33,14 @@ function loadConfig(repoRoot) {
31
33
  typeof parsed.highThreshold === "number"
32
34
  ? parsed.highThreshold
33
35
  : defaults.highThreshold,
36
+ bulkInsertThreshold:
37
+ Number.isFinite(Number(parsed.bulkInsertThreshold)) && Number(parsed.bulkInsertThreshold) > 0
38
+ ? Number(parsed.bulkInsertThreshold)
39
+ : defaults.bulkInsertThreshold,
40
+ pureDeleteThreshold:
41
+ Number.isFinite(Number(parsed.pureDeleteThreshold)) && Number(parsed.pureDeleteThreshold) > 0
42
+ ? Number(parsed.pureDeleteThreshold)
43
+ : defaults.pureDeleteThreshold,
34
44
  sourceExtensions: Array.isArray(parsed.sourceExtensions)
35
45
  ? parsed.sourceExtensions
36
46
  : defaults.sourceExtensions,
package/src/mutex-lock.js CHANGED
@@ -21,6 +21,7 @@ async function acquireMutexLock(lockDirPath, options = {}) {
21
21
  startMs: Date.now(),
22
22
  };
23
23
  const ownerPath = path.join(lockDirPath, LOCK_OWNER_FILE);
24
+ await fs.promises.mkdir(path.dirname(lockDirPath), { recursive: true });
24
25
  const startedAt = Date.now();
25
26
  let attempt = 0;
26
27