ai-lens 0.8.72 → 0.8.73

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/.commithash CHANGED
@@ -1 +1 @@
1
- 78b5c4c
1
+ 45d094e
package/CHANGELOG.md CHANGED
@@ -2,6 +2,9 @@
2
2
 
3
3
  History of changes to the `ai-lens` CLI package on npm. New entries go on top. Format: `## X.Y.Z — YYYY-MM-DD`, followed by user-facing bullets.
4
4
 
5
+ ## 0.8.73 — 2026-05-29
6
+ - fix: spooling an event now retries the atomic `rename` in `~/.ai-lens/pending/` a few times with a short backoff before giving up. On Windows, antivirus/Defender or the search indexer transiently locks the temp file and the rename failed with EPERM, silently dropping that event (observed in prod via the 0.8.70 error-code instrumentation). POSIX is unaffected (its rename is atomic and never hits this).
7
+
5
8
  ## 0.8.72 — 2026-05-29
6
9
  - fix: `status` no longer reports hooks as "outdated" when they already capture reliably. A hook is now considered current if it's GUI-safe — either the per-machine launcher (`run.sh`/`run.cmd`, including the transitional `sh -c` wrapper) OR a `capture.js` command with an absolute node path baked in (e.g. `/opt/homebrew/bin/node`). Only PATH-dependent forms (bare `node`, `/usr/bin/env node`) — which break for GUI-launched Cursor/Claude on macOS — stay flagged outdated so `init` rewrites them.
7
10
  - fix: `ai-lens status --report` now prints the full status to the screen just like plain `ai-lens status`, in addition to sending the report to the server. Previously it ran silently. The only difference from plain status is that it POSTs the report instead of writing the local `~/ai-lens-status.txt` file.
package/client/capture.js CHANGED
@@ -1182,6 +1182,32 @@ export function normalizeEvent(event) {
1182
1182
  // Queue + Sender Spawn
1183
1183
  // =============================================================================
1184
1184
 
1185
+ // Windows (Defender / other AV, search indexer, file-locks) transiently fails an
1186
+ // atomic rename with EPERM/EACCES/EBUSY while another process briefly holds the
1187
+ // .tmp or destination handle. The rename is our spool-write step, so a transient
1188
+ // failure would drop the event (observed in prod: queue-write-failed EPERM on
1189
+ // rename in ~/.ai-lens/pending). Retry a few times with a short synchronous
1190
+ // backoff before giving up. POSIX rename is atomic and never hits these, so this
1191
+ // is a no-op there. Non-transient errors (e.g. ENOENT) are re-thrown immediately.
1192
+ const RENAME_RETRY_CODES = new Set(['EPERM', 'EACCES', 'EBUSY']);
1193
+ export function renameSyncWithRetry(from, to, { attempts = 5, baseDelayMs = 20, renameFn = renameSync } = {}) {
1194
+ for (let i = 1; ; i++) {
1195
+ try {
1196
+ renameFn(from, to);
1197
+ return;
1198
+ } catch (err) {
1199
+ if (i >= attempts || !RENAME_RETRY_CODES.has(err?.code)) throw err;
1200
+ // Synchronous backoff — capture.js runs in a one-shot hook context, no event loop to yield to.
1201
+ try {
1202
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, baseDelayMs * i);
1203
+ } catch {
1204
+ const end = Date.now() + baseDelayMs * i;
1205
+ while (Date.now() < end) { /* spin fallback if Atomics unavailable */ }
1206
+ }
1207
+ }
1208
+ }
1209
+ }
1210
+
1185
1211
  export function writeToSpool(unified) {
1186
1212
  // Shallow copy before redaction — do not mutate the caller's object
1187
1213
  const toWrite = { ...unified };
@@ -1198,7 +1224,7 @@ export function writeToSpool(unified) {
1198
1224
  const tmpPath = dstPath + '.tmp.' + process.pid;
1199
1225
  writeFileSync(tmpPath, JSON.stringify(toWrite));
1200
1226
  try {
1201
- renameSync(tmpPath, dstPath);
1227
+ renameSyncWithRetry(tmpPath, dstPath);
1202
1228
  } catch (err) {
1203
1229
  // Clean up orphaned tmp file (e.g. antivirus/file-lock on Windows)
1204
1230
  try { unlinkSync(tmpPath); } catch {}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-lens",
3
- "version": "0.8.72",
3
+ "version": "0.8.73",
4
4
  "type": "module",
5
5
  "description": "Centralized session analytics for AI coding tools",
6
6
  "bin": {