ai-lens 0.8.91 → 0.8.92

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
- 8894c21
1
+ 2196993
package/CHANGELOG.md CHANGED
@@ -2,6 +2,10 @@
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.92 — 2026-06-11
6
+ - fix: when global `~/.claude` hooks are active, `ai-lens init` no longer also installs the `.claude/settings.local.json` platform overlay (and removes a previously-written one) — global hooks already capture every project, and registering both made every hook event fire capture twice
7
+ - improve: `ai-lens status` shows committed other-OS project hooks as healthy ("capture covered by global hooks") when global hooks are doing the work, instead of a false warning
8
+
5
9
  ## 0.8.91 — 2026-06-11
6
10
  - fix: Claude Code hooks committed to a repo now work on both Windows and macOS/Linux. A committed `.claude/settings.json` can only carry one OS's `CLAUDE_PROJECT_DIR` syntax, so on the other OS hooks silently captured nothing; `ai-lens init` now writes the running OS's form into `.claude/settings.local.json` (per-machine, auto-gitignored) without touching the shared file
7
11
  - improve: `ai-lens status` correctly diagnoses committed project hooks — green when the platform overlay is in place, a clear fix hint when it's missing, and no more false "command path missing" on healthy setups
package/cli/hooks.js CHANGED
@@ -1080,6 +1080,43 @@ export function writeClaudeLocalOverlay(tool, opts = {}) {
1080
1080
  return { written: true, localPath: a.localPath };
1081
1081
  }
1082
1082
 
1083
+ /**
1084
+ * Whether GLOBAL Claude Code hooks (~/.claude/settings.json) are active on this
1085
+ * machine — current AI Lens hooks and not disabled. When they are, the
1086
+ * settings.local.json platform overlay is redundant: global hooks fire for every
1087
+ * project, and registering both would double-capture each event (collapsed by the
1088
+ * server's content-hash dedup, but pointless client work).
1089
+ * @param {object} [globalTool] — override for tests (defaults to the real ~/.claude tool)
1090
+ */
1091
+ export function globalClaudeHooksActive(globalTool = TOOL_CONFIGS.find(t => t.name === 'Claude Code')) {
1092
+ if (!globalTool || !existsSync(globalTool.configPath)) return false;
1093
+ if (analyzeToolHooks(globalTool).status !== 'current') return false;
1094
+ return checkHooksDisabled(globalTool).length === 0;
1095
+ }
1096
+
1097
+ /**
1098
+ * Strip AI Lens hooks from the settings.local.json overlay (e.g. after global
1099
+ * Claude Code hooks were installed and the overlay became a double-capture source).
1100
+ * Preserves everything else in the file; refuses a malformed file.
1101
+ */
1102
+ export function removeClaudeLocalOverlay(tool) {
1103
+ const localPath = claudeLocalSettingsPath(tool);
1104
+ if (!existsSync(localPath)) return { removed: false, reason: 'no settings.local.json' };
1105
+ let config;
1106
+ try {
1107
+ config = JSON.parse(readFileSync(localPath, 'utf-8'));
1108
+ } catch {
1109
+ return { removed: false, reason: `${localPath} is malformed`, localPath };
1110
+ }
1111
+ const hasAiLens = config.hooks && typeof config.hooks === 'object'
1112
+ && Object.values(config.hooks).some(en => Array.isArray(en) && en.some(e => isAiLensHook(e)));
1113
+ if (!hasAiLens) return { removed: false, reason: 'no AI Lens hooks in overlay', localPath };
1114
+ const probe = { ...tool, configPath: localPath, sharedConfig: true };
1115
+ const stripped = buildStrippedConfig(probe, config);
1116
+ writeHooksConfig(probe, stripped ?? {});
1117
+ return { removed: true, localPath };
1118
+ }
1119
+
1083
1120
  function isCurrentAiLensHook(entry, expected, opts = {}) {
1084
1121
  // "Current" = a GUI-safe install (launcher OR absolute-node capture.js) OR a
1085
1122
  // committed Claude Code $CLAUDE_PROJECT_DIR project hook. We do NOT require an exact
package/cli/init.js CHANGED
@@ -15,7 +15,7 @@ import { migrateIfNeeded } from '../client/sender.js';
15
15
  import {
16
16
  CAPTURE_PATH, REPO_CAPTURE_PATH, detectInstalledTools, getCursorToolConfig, getClaudeCodeToolConfig, getCodexToolConfig,
17
17
  analyzeToolHooks, buildMergedConfig, writeHooksConfig, describePlan, enableCodexHookTrust,
18
- analyzeClaudeLocalOverlay, writeClaudeLocalOverlay,
18
+ analyzeClaudeLocalOverlay, writeClaudeLocalOverlay, removeClaudeLocalOverlay, globalClaudeHooksActive,
19
19
  installClientFiles, readLensConfig, saveLensConfig, getVersionInfo,
20
20
  getClaudeCodeHookDefsWithPath, getClaudeCodeHookDefsWithProjectDir, getCursorHookDefsWithPath, getCodexHookDefsWithPath,
21
21
  cleanupLegacyHooks, cleanupOppositeScope, cleanupEmptyMcpJson, addCursorMcp, removeCursorMcp,
@@ -1002,7 +1002,16 @@ export default async function init() {
1002
1002
  const overlayClaudeTool = getClaudeCodeToolConfig(overlayProjectRoot, 'Claude Code (project)', ctx);
1003
1003
  if (overlayClaudeTool && existsSync(overlayClaudeTool.configPath)) {
1004
1004
  const overlay = analyzeClaudeLocalOverlay(overlayClaudeTool);
1005
- if (overlay.applicable && !overlay.overlayCurrent) {
1005
+ if (overlay.applicable && globalClaudeHooksActive()) {
1006
+ // Global ~/.claude hooks fire for every project — an overlay would only
1007
+ // double-register capture. Make sure it's absent rather than present.
1008
+ const rm = removeClaudeLocalOverlay(overlayClaudeTool);
1009
+ if (rm.removed) {
1010
+ success(` Claude Code: global hooks cover this project — removed the redundant overlay from ${rm.localPath}`);
1011
+ } else {
1012
+ info(' Claude Code: global hooks cover this project — settings.local.json overlay not needed.');
1013
+ }
1014
+ } else if (overlay.applicable && !overlay.overlayCurrent) {
1006
1015
  const r = writeClaudeLocalOverlay(overlayClaudeTool, { analysis: overlay });
1007
1016
  if (r.written) {
1008
1017
  success(` Claude Code: committed project hooks use the other OS's CLAUDE_PROJECT_DIR syntax — wrote this OS's form to ${r.localPath} (per-machine, not committed)`);
package/cli/status.js CHANGED
@@ -7,7 +7,7 @@ import tls from 'node:tls';
7
7
 
8
8
  import { TLS_TRUST_CODES, tlsCodeOf, tlsVerdictSummary, issuerName } from '../client/tls-trust.js';
9
9
 
10
- import { getVersionInfo, readLensConfig, detectInstalledTools, getCursorToolConfig, getClaudeCodeToolConfig, getCodexToolConfig, analyzeToolHooks, checkHooksDisabled, verifyCodexHookTrust, CAPTURE_PATH, TOOL_CONFIGS, isClaudeProjectDirCommand, analyzeClaudeLocalOverlay, extractProjectDirRelPath } from './hooks.js';
10
+ import { getVersionInfo, readLensConfig, detectInstalledTools, getCursorToolConfig, getClaudeCodeToolConfig, getCodexToolConfig, analyzeToolHooks, checkHooksDisabled, verifyCodexHookTrust, CAPTURE_PATH, TOOL_CONFIGS, isClaudeProjectDirCommand, analyzeClaudeLocalOverlay, extractProjectDirRelPath, globalClaudeHooksActive } from './hooks.js';
11
11
  import { DATA_DIR, PENDING_DIR, SENDING_DIR, SESSION_PATHS_DIR, LOG_PATH, CAPTURE_LOG_PATH, LAST_STATUS_REPORT_PATH, getGitIdentity, getMonitoredProjects } from '../client/config.js';
12
12
  import { isLockStale } from '../client/sender.js';
13
13
  import { initLogger, info, success, warn, error, heading, blank } from './logger.js';
@@ -555,6 +555,13 @@ function checkHooks(tool) {
555
555
  if (cmd && isClaudeProjectDirCommand(cmd)) {
556
556
  const overlay = analyzeClaudeLocalOverlay(tool);
557
557
  if (overlay.applicable) {
558
+ if (globalClaudeHooksActive()) {
559
+ return {
560
+ ok: true,
561
+ summary: 'committed hooks are for the other OS \u2014 capture covered by global hooks',
562
+ detail: `${detail}\n\nCommitted settings.json uses the other OS's CLAUDE_PROJECT_DIR syntax (never fires here), but global ~/.claude hooks are active and capture every project \u2014 no overlay needed.`,
563
+ };
564
+ }
558
565
  if (overlay.overlayCurrent) {
559
566
  return {
560
567
  ok: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-lens",
3
- "version": "0.8.91",
3
+ "version": "0.8.92",
4
4
  "type": "module",
5
5
  "description": "Centralized session analytics for AI coding tools",
6
6
  "bin": {