claude-smart 0.2.29 → 0.2.30

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
@@ -6,14 +6,14 @@
6
6
  claude-smart
7
7
  </h1>
8
8
 
9
- <h4 align="center">A local learning plugin for <a href="https://claude.com/claude-code" target="_blank">Claude Code</a> and Codex that turns corrections into durable rules your coding assistant follows in future sessions.</h4>
9
+ <h4 align="center">A self-improvement plugin for <a href="https://claude.com/claude-code" target="_blank">Claude Code</a> and Codex that turns interactions into durable skills they follow in future sessions.</h4>
10
10
 
11
11
  <p align="center">
12
12
  <a href="LICENSE">
13
13
  <img src="https://img.shields.io/badge/License-Apache%202.0-blue.svg" alt="License">
14
14
  </a>
15
15
  <a href="plugin/pyproject.toml">
16
- <img src="https://img.shields.io/badge/version-0.2.29-green.svg" alt="Version">
16
+ <img src="https://img.shields.io/badge/version-0.2.30-green.svg" alt="Version">
17
17
  </a>
18
18
  <a href="plugin/pyproject.toml">
19
19
  <img src="https://img.shields.io/badge/python-%3E%3D3.12-brightgreen.svg" alt="Python">
@@ -24,9 +24,6 @@
24
24
  <a href="#quick-start">
25
25
  <img src="https://img.shields.io/badge/hosts-Claude%20Code%20%2B%20Codex-purple.svg" alt="Hosts">
26
26
  </a>
27
- <a href="https://discord.gg/Jbft3jPn">
28
- <img src="https://img.shields.io/badge/Discord-Join%20Chat-5865F2?logo=discord&logoColor=white" alt="Discord">
29
- </a>
30
27
  </p>
31
28
 
32
29
  <p align="center">
@@ -40,22 +37,22 @@
40
37
  </p>
41
38
 
42
39
  <p align="center">
43
- It learns both corrections and successful execution patterns—so your coding assistant avoids repeating mistakes and reuses what works. Project-specific skills capture repo-local rules, and shared skills roll up durable patterns for reuse across projects.
40
+ claude-smart learns from your corrections <i>and</i> from what's already working so the assistant stops repeating mistakes and reuses proven paths. Project-specific skills capture repo-local rules, and shared skills roll up durable patterns for reuse across projects.
44
41
  </p>
45
42
 
46
43
  <p align="center">
47
- <b>vs <code>claude-mem</code>:</b> ~3× better at turning your corrections into rules Claude follows, ~50% more of what you tell Claude sticks. <a href="EXPERIMENT.md"><b>Read the benchmark →</b></a>
44
+ <b>vs <code>claude-mem</code>:</b> ~3× better at turning your corrections into rules Claude follows, ~50% more of your guidance is retained. <a href="EXPERIMENT.md"><b>Read the benchmark →</b></a>
48
45
  </p>
49
46
 
50
47
  ---
51
48
 
52
49
  ## Why Learning, Not Memory
53
50
 
54
- Most memory solutions are still mostly informative—Claude remembers what happened, without necessarily changing what it does next.
51
+ Most memory tools just record. Claude remembers what happened, but doesn't change what it does next.
55
52
 
56
53
  `claude-smart` focuses on learning instead.
57
54
 
58
- Four ways this changes what your coding assistant can do for you:
55
+ Four things this changes:
59
56
 
60
57
  - 💡 **Stop repeating the same mistakes:** Produces actionable skills Claude can follow next time; memory only records what happened.
61
58
 
@@ -63,12 +60,12 @@ Four ways this changes what your coding assistant can do for you:
63
60
  > **Memory:** “deploy broke after prisma bump; user rolled back”<br>
64
61
  > **Learning:** “treat major-version bumps of ORMs/DB drivers as breaking — verify with integration tests, not just unit tests”
65
62
 
66
- - 🚀 **Start from the optimized path:** Preserves and optimizes execution paths so Claude can reuse what already works.
63
+ - 🚀 **Start from the optimized path:** Records and optimizes the steps that worked so Claude can reuse them, instead of re-exploring each session.
67
64
  > *Example:* Claude spends several iterations trying to start the local dev environment before discovering that this repo requires `pnpm dev:all` instead of the usual `npm run dev`.<br>
68
65
  > **Memory:** “user mentioned that `npm run dev` did not work”<br>
69
66
  > **Learning:** “for this repo, always use `pnpm dev:all` to start the full local stack — `npm run dev` only starts the frontend and causes missing service errors”
70
67
 
71
- Instead of re-exploring, Claude starts from the proven path—reducing planning steps, latency, and token usage.
68
+ Instead of re-exploring, Claude Code starts from the proven path—reducing planning steps, latency, and token usage.
72
69
 
73
70
  - 🌐 **Project-specific and shared skills:** Session memory disappears with the conversation. Project-specific skills preserve repo-local rules, while shared skills roll up patterns that should transfer across projects. Preferences stay scoped to the current project so per-repo preferences don't leak across projects.
74
71
 
@@ -95,12 +92,6 @@ claude plugin marketplace add ReflexioAI/claude-smart
95
92
  claude plugin install claude-smart@reflexioai
96
93
  ```
97
94
 
98
- The plugin Setup hook installs its own `uv`, Python 3.12 environment, and
99
- private Node.js/npm runtime under `~/.claude-smart/` when they are missing, so
100
- you do not need to install Python or Node globally for the plugin/dashboard.
101
- On native Windows, Claude Code hooks still need a Git Bash-compatible `bash`
102
- until Claude Code exposes a single cross-platform hook command shape.
103
-
104
95
  To uninstall:
105
96
 
106
97
  ```bash
@@ -123,11 +114,6 @@ Then fully quit and reopen Codex so hooks reload.
123
114
 
124
115
  Requires the `codex` CLI on `PATH` and Node.js (for `npx`).
125
116
 
126
- The `npx` command needs Node.js only to launch the installer. The installed
127
- plugin uses a private Node.js/npm runtime and a `uv`-managed Python 3.12
128
- environment under `~/.claude-smart/`, so hooks and the dashboard do not depend
129
- on global Python, uv, Node, or npm after install.
130
-
131
117
  To uninstall:
132
118
 
133
119
  ```bash
@@ -170,7 +156,7 @@ the first local embedding model download. The ONNX model cache lives at
170
156
 
171
157
  ## Dashboard
172
158
 
173
- A web UI for browsing session histories, inspecting preferences, and editing project-specific and shared skills. The dashboard auto-starts alongside the backend, so you can open **http://localhost:3001** directly. Or run `/claude-smart:dashboard` in Claude Code to launch dashboard in browser. In Codex, run `bash ~/.reflexio/plugin-root/scripts/dashboard-open.sh`.
159
+ A web UI for browsing session histories, inspecting preferences, and editing project-specific and shared skills. The dashboard auto-starts alongside the backend, so you can open **http://localhost:3001** directly. Or run `/claude-smart:dashboard` in Claude Code to open it in your browser. In Codex, run `bash ~/.reflexio/plugin-root/scripts/dashboard-open.sh`.
174
160
 
175
161
  <p align="center">
176
162
  <img src="assets/preferences_dashboard.png" alt="Preferences dashboard" width="49%">
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-smart",
3
- "version": "0.2.29",
3
+ "version": "0.2.30",
4
4
  "description": "Self-improving Claude Code plugin — learns from corrections via reflexio",
5
5
  "keywords": [
6
6
  "claude",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-smart",
3
- "version": "0.2.29",
3
+ "version": "0.2.30",
4
4
  "description": "Self-improving Claude Code plugin — learns from corrections across sessions via reflexio",
5
5
  "author": {
6
6
  "name": "Yi Lu"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-smart",
3
- "version": "0.2.29",
3
+ "version": "0.2.30",
4
4
  "description": "Self-improving coding assistant plugin — learns from corrections across sessions via reflexio",
5
5
  "author": {
6
6
  "name": "Yi Lu"
@@ -18,17 +18,17 @@
18
18
  {
19
19
  "type": "command",
20
20
  "command": "_R=\"${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-}}\"; [ -z \"$_R\" ] && _R=$(ls -dt \"$HOME/.codex/plugins/cache/reflexioai/claude-smart\"/*/ 2>/dev/null | head -n 1); [ -n \"$_R\" ] && . \"${_R%/}/scripts/_codex_env.sh\" && bash \"$_R/scripts/backend-service.sh\" start",
21
- "timeout": 30
21
+ "timeout": 300
22
22
  },
23
23
  {
24
24
  "type": "command",
25
25
  "command": "_R=\"${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-}}\"; [ -z \"$_R\" ] && _R=$(ls -dt \"$HOME/.codex/plugins/cache/reflexioai/claude-smart\"/*/ 2>/dev/null | head -n 1); [ -n \"$_R\" ] && . \"${_R%/}/scripts/_codex_env.sh\" && bash \"$_R/scripts/dashboard-service.sh\" start",
26
- "timeout": 10
26
+ "timeout": 300
27
27
  },
28
28
  {
29
29
  "type": "command",
30
30
  "command": "_R=\"${CLAUDE_PLUGIN_ROOT:-${PLUGIN_ROOT:-}}\"; [ -z \"$_R\" ] && _R=$(ls -dt \"$HOME/.codex/plugins/cache/reflexioai/claude-smart\"/*/ 2>/dev/null | head -n 1); [ -n \"$_R\" ] && . \"${_R%/}/scripts/_codex_env.sh\" && bash \"$_R/scripts/hook_entry.sh\" codex session-start",
31
- "timeout": 30,
31
+ "timeout": 300,
32
32
  "statusMessage": "Loading claude-smart context"
33
33
  }
34
34
  ]
@@ -35,17 +35,17 @@
35
35
  {
36
36
  "type": "command",
37
37
  "command": "_R=\"${CLAUDE_PLUGIN_ROOT}\"; [ -z \"$_R\" ] && _R=\"$HOME/.claude/plugins/marketplaces/reflexioai/plugin\"; bash \"$_R/scripts/hook_entry.sh\" claude-code session-start",
38
- "timeout": 30
38
+ "timeout": 300
39
39
  },
40
40
  {
41
41
  "type": "command",
42
42
  "command": "_R=\"${CLAUDE_PLUGIN_ROOT}\"; [ -z \"$_R\" ] && _R=\"$HOME/.claude/plugins/marketplaces/reflexioai/plugin\"; bash \"$_R/scripts/backend-service.sh\" start",
43
- "timeout": 30
43
+ "timeout": 300
44
44
  },
45
45
  {
46
46
  "type": "command",
47
47
  "command": "_R=\"${CLAUDE_PLUGIN_ROOT}\"; [ -z \"$_R\" ] && _R=\"$HOME/.claude/plugins/marketplaces/reflexioai/plugin\"; bash \"$_R/scripts/dashboard-service.sh\" start",
48
- "timeout": 10
48
+ "timeout": 300
49
49
  }
50
50
  ]
51
51
  }
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "claude-smart"
3
- version = "0.2.29"
3
+ version = "0.2.30"
4
4
  description = "Self-improving Claude Code plugin — learns from corrections via reflexio"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
@@ -186,8 +186,16 @@ case "$CMD" in
186
186
  emit_ok; exit 0
187
187
  fi
188
188
  if ! command -v uv >/dev/null 2>&1; then
189
- claude_smart_append_capped_log "$LOG_FILE" "$LOG_MAX_BYTES" "[claude-smart] backend: uv not on PATH; skipping"
190
- emit_ok; exit 0
189
+ if [ "${CLAUDE_SMART_BOOTSTRAPPING:-}" != "1" ] && [ -x "$PLUGIN_ROOT/scripts/smart-install.sh" ]; then
190
+ claude_smart_append_capped_log "$LOG_FILE" "$LOG_MAX_BYTES" "[claude-smart] backend: uv not on PATH; running installer"
191
+ CLAUDE_SMART_BOOTSTRAPPING=1 bash "$PLUGIN_ROOT/scripts/smart-install.sh" >>"$STATE_DIR/install.log" 2>&1 || true
192
+ claude_smart_source_login_path
193
+ claude_smart_prepend_astral_bins
194
+ fi
195
+ if ! command -v uv >/dev/null 2>&1; then
196
+ claude_smart_append_capped_log "$LOG_FILE" "$LOG_MAX_BYTES" "[claude-smart] backend: uv not on PATH after installer; skipping"
197
+ emit_ok; exit 0
198
+ fi
191
199
  fi
192
200
  cd "$PLUGIN_ROOT"
193
201
 
@@ -104,6 +104,10 @@ function npmPath() {
104
104
  return commandPath(process.platform === "win32" ? ["npm.cmd", "npm.exe", "npm"] : ["npm"]);
105
105
  }
106
106
 
107
+ function bashPath() {
108
+ return commandPath(process.platform === "win32" ? ["bash.exe", "bash"] : ["bash"]);
109
+ }
110
+
107
111
  function stateFile(name) {
108
112
  return path.join(STATE_DIR, name);
109
113
  }
@@ -202,6 +206,49 @@ function detached(command, args, options = {}) {
202
206
  return child.pid;
203
207
  }
204
208
 
209
+ function runInstaller(root, reason) {
210
+ if (process.env.CLAUDE_SMART_BOOTSTRAPPING === "1") return false;
211
+ const script = path.join(root, "scripts", "smart-install.sh");
212
+ if (!fs.existsSync(script)) return false;
213
+ const bash = bashPath();
214
+ if (!bash) return false;
215
+ appendLog("backend.log", `[claude-smart] ${reason}; running installer`);
216
+ const result = spawnSync(bash, [script], {
217
+ cwd: root,
218
+ env: {
219
+ ...process.env,
220
+ CLAUDE_SMART_BOOTSTRAPPING: "1",
221
+ },
222
+ encoding: "utf8",
223
+ maxBuffer: 20 * 1024 * 1024,
224
+ windowsHide: true,
225
+ });
226
+ const output = `${result.stdout || ""}${result.stderr || ""}`.trim();
227
+ if (output) {
228
+ ensureDir(STATE_DIR);
229
+ fs.appendFileSync(path.join(STATE_DIR, "install.log"), `${output}\n`);
230
+ trimLog(path.join(STATE_DIR, "install.log"));
231
+ }
232
+ prependRuntimePath();
233
+ return result.status === 0;
234
+ }
235
+
236
+ function startInstallerDetached(root, reason) {
237
+ if (process.env.CLAUDE_SMART_BOOTSTRAPPING === "1") return false;
238
+ const script = path.join(root, "scripts", "smart-install.sh");
239
+ const bash = bashPath();
240
+ if (!bash || !fs.existsSync(script)) return false;
241
+ appendLog("backend.log", `[claude-smart] ${reason}; starting installer in background`);
242
+ detached(bash, [script], {
243
+ cwd: root,
244
+ env: {
245
+ ...process.env,
246
+ CLAUDE_SMART_BOOTSTRAPPING: "1",
247
+ },
248
+ });
249
+ return true;
250
+ }
251
+
205
252
  function readPid(file) {
206
253
  try {
207
254
  const value = fs.readFileSync(file, "utf8").trim();
@@ -262,7 +309,11 @@ async function startBackend(root) {
262
309
  }
263
310
  const uv = uvPath();
264
311
  if (!uv) {
265
- appendLog("backend.log", "[claude-smart] backend: uv not on PATH; skipping");
312
+ runInstaller(root, "backend: uv not on PATH");
313
+ }
314
+ const readyUv = uvPath();
315
+ if (!readyUv) {
316
+ appendLog("backend.log", "[claude-smart] backend: uv not on PATH after installer; skipping");
266
317
  emitOk();
267
318
  return;
268
319
  }
@@ -284,7 +335,7 @@ async function startBackend(root) {
284
335
  INTERACTION_CLEANUP_DELETE_COUNT: process.env.INTERACTION_CLEANUP_DELETE_COUNT || "200",
285
336
  };
286
337
  const pid = detached(
287
- uv,
338
+ readyUv,
288
339
  [
289
340
  "run",
290
341
  "--project",
@@ -326,14 +377,18 @@ async function startDashboard(root) {
326
377
  }
327
378
  const npm = npmPath();
328
379
  if (!npm) {
329
- appendLog("dashboard.log", "[claude-smart] dashboard: npm not on PATH; skipping");
380
+ runInstaller(root, "dashboard: npm not on PATH");
381
+ }
382
+ const readyNpm = npmPath();
383
+ if (!readyNpm) {
384
+ appendLog("dashboard.log", "[claude-smart] dashboard: npm not on PATH after installer; skipping");
330
385
  emitOk();
331
386
  return;
332
387
  }
333
388
  if (!fs.existsSync(path.join(dashboard, ".next"))) {
334
389
  const buildPidFile = path.join(STATE_DIR, "dashboard-build.pid");
335
390
  if (!pidAlive(readPid(buildPidFile))) {
336
- const pid = detached(npm, ["run", "build"], { cwd: dashboard });
391
+ const pid = detached(readyNpm, ["run", "build"], { cwd: dashboard });
337
392
  writePid(buildPidFile, pid);
338
393
  appendLog("dashboard.log", "[claude-smart] dashboard: .next missing; started background build");
339
394
  }
@@ -346,7 +401,7 @@ async function startDashboard(root) {
346
401
  REFLEXIO_URL: readBackendUrl(),
347
402
  CLAUDE_SMART_DASHBOARD_WORKSPACE: process.cwd(),
348
403
  };
349
- const pid = detached(npm, ["run", "start"], { cwd: dashboard, env });
404
+ const pid = detached(readyNpm, ["run", "start"], { cwd: dashboard, env });
350
405
  writePid(pidFile, pid);
351
406
  await waitForHealth(DASHBOARD_PORT, "/api/health", "x-claude-smart-dashboard", 5);
352
407
  emitOk();
@@ -354,11 +409,19 @@ async function startDashboard(root) {
354
409
 
355
410
  function runHook(root, event) {
356
411
  trimLog(path.join(STATE_DIR, "backend.log"));
357
- const uv = uvPath();
412
+ let uv = uvPath();
358
413
  if (!uv) {
359
- appendLog("backend.log", "[claude-smart] hook: uv not on PATH; skipping");
360
- emitOk();
361
- return 0;
414
+ if (event === "session-start") {
415
+ runInstaller(root, "hook: uv not on PATH");
416
+ uv = uvPath();
417
+ } else {
418
+ startInstallerDetached(root, "hook: uv not on PATH");
419
+ }
420
+ if (!uv) {
421
+ appendLog("backend.log", "[claude-smart] hook: uv not on PATH after installer; skipping");
422
+ emitOk();
423
+ return 0;
424
+ }
362
425
  }
363
426
  const input = fs.readFileSync(0);
364
427
  const result = spawnSync(
@@ -100,10 +100,19 @@ case "$CMD" in
100
100
  fi
101
101
  NPM_BIN=$(claude_smart_resolve_npm || true)
102
102
  if [ -z "$NPM_BIN" ] || ! "$NPM_BIN" --version >/dev/null 2>&1; then
103
- reason="npm is not on PATH; dashboard cannot start"
104
- echo "[claude-smart] dashboard: $reason; skipping" >>"$LOG_FILE"
105
- claude_smart_write_dashboard_unavailable "$reason"
106
- emit_ok; exit 0
103
+ if [ "${CLAUDE_SMART_BOOTSTRAPPING:-}" != "1" ] && [ -x "$PLUGIN_ROOT/scripts/smart-install.sh" ]; then
104
+ echo "[claude-smart] dashboard: npm is not on PATH; running installer" >>"$LOG_FILE"
105
+ CLAUDE_SMART_BOOTSTRAPPING=1 bash "$PLUGIN_ROOT/scripts/smart-install.sh" >>"$STATE_DIR/install.log" 2>&1 || true
106
+ claude_smart_source_login_path
107
+ claude_smart_prepend_node_bins
108
+ NPM_BIN=$(claude_smart_resolve_npm || true)
109
+ fi
110
+ if [ -z "$NPM_BIN" ] || ! "$NPM_BIN" --version >/dev/null 2>&1; then
111
+ reason="npm is not on PATH after installer; dashboard cannot start"
112
+ echo "[claude-smart] dashboard: $reason; skipping" >>"$LOG_FILE"
113
+ claude_smart_write_dashboard_unavailable "$reason"
114
+ emit_ok; exit 0
115
+ fi
107
116
  fi
108
117
 
109
118
  # `npm run start` requires a prior `next build`. Do NOT build in the
@@ -37,6 +37,7 @@ claude_smart_prepend_astral_bins
37
37
  PLUGIN_ROOT="$(cd "$HERE/.." && pwd)"
38
38
 
39
39
  FAILURE_MARKER="$HOME/.claude-smart/install-failed"
40
+ STATE_DIR="$HOME/.claude-smart"
40
41
  if [ -f "$FAILURE_MARKER" ]; then
41
42
  if [ "$EVENT" = "session-start" ] && command -v python3 >/dev/null 2>&1; then
42
43
  python3 - "$FAILURE_MARKER" <<'PY'
@@ -61,9 +62,34 @@ PY
61
62
  fi
62
63
 
63
64
  if ! command -v uv >/dev/null 2>&1; then
64
- # uv missing post-install don't crash the session, just no-op.
65
- echo '{"continue":true,"suppressOutput":true}'
66
- exit 0
65
+ # Self-heal from skipped Setup/SessionStart bootstrap. SessionStart can
66
+ # afford to wait because it has the install budget; prompt/tool hooks start
67
+ # the same installer detached so normal work is not blocked by first-run
68
+ # dependency setup.
69
+ if [ "${CLAUDE_SMART_BOOTSTRAPPING:-}" = "1" ]; then
70
+ echo '{"continue":true,"suppressOutput":true}'
71
+ exit 0
72
+ fi
73
+ if [ -x "$PLUGIN_ROOT/scripts/smart-install.sh" ]; then
74
+ mkdir -p "$STATE_DIR"
75
+ if [ "$EVENT" = "session-start" ]; then
76
+ CLAUDE_SMART_BOOTSTRAPPING=1 bash "$PLUGIN_ROOT/scripts/smart-install.sh" >&2
77
+ claude_smart_prepend_astral_bins
78
+ claude_smart_prepend_node_bins
79
+ if command -v uv >/dev/null 2>&1; then
80
+ bash "$HERE/backend-service.sh" start >/dev/null 2>&1 || true
81
+ bash "$HERE/dashboard-service.sh" start >/dev/null 2>&1 || true
82
+ fi
83
+ else
84
+ claude_smart_spawn_detached env CLAUDE_SMART_BOOTSTRAPPING=1 \
85
+ bash "$PLUGIN_ROOT/scripts/smart-install.sh" \
86
+ >>"$STATE_DIR/install.log" 2>&1 || true
87
+ fi
88
+ fi
89
+ if ! command -v uv >/dev/null 2>&1; then
90
+ echo '{"continue":true,"suppressOutput":true}'
91
+ exit 0
92
+ fi
67
93
  fi
68
94
 
69
95
  # Stdin is the hook payload JSON — stream it through to the Python CLI.
@@ -22,20 +22,60 @@ MARKER_DIR="$HOME/.claude-smart"
22
22
  FAILURE_MARKER="$MARKER_DIR/install-failed"
23
23
  SUCCESS_MARKER="$MARKER_DIR/install-complete"
24
24
  INSTALL_LOCK="$MARKER_DIR/install.lock"
25
+ INSTALL_REAP_LOCK="$MARKER_DIR/install.lock.reap"
25
26
  mkdir -p "$MARKER_DIR"
26
27
 
28
+ remove_stale_install_lock() {
29
+ local expected current
30
+
31
+ expected="$1"
32
+ if ! mkdir "$INSTALL_REAP_LOCK" 2>/dev/null; then
33
+ sleep 1
34
+ return 0
35
+ fi
36
+ current="$(cat "$INSTALL_LOCK" 2>/dev/null || true)"
37
+ if [ "$current" = "$expected" ]; then
38
+ rm -f "$INSTALL_LOCK"
39
+ fi
40
+ rmdir "$INSTALL_REAP_LOCK" 2>/dev/null || true
41
+ }
42
+
43
+ acquire_install_lock() {
44
+ local lock_pid
45
+
46
+ if command -v flock >/dev/null 2>&1; then
47
+ exec 9>"$INSTALL_LOCK"
48
+ if ! flock 9; then
49
+ echo "[claude-smart] install lock failed; continuing without serialization" >&2
50
+ echo '{"continue":true,"suppressOutput":true}'
51
+ exit 0
52
+ fi
53
+ return 0
54
+ fi
55
+
56
+ while ! ( set -C; printf '%s\n' "$$" > "$INSTALL_LOCK" ) 2>/dev/null; do
57
+ lock_pid="$(cat "$INSTALL_LOCK" 2>/dev/null || true)"
58
+ case "$lock_pid" in
59
+ ''|*[!0-9]*)
60
+ remove_stale_install_lock "$lock_pid"
61
+ ;;
62
+ *)
63
+ if kill -0 "$lock_pid" 2>/dev/null; then
64
+ sleep 1
65
+ else
66
+ remove_stale_install_lock "$lock_pid"
67
+ fi
68
+ ;;
69
+ esac
70
+ done
71
+ trap '[ "$(cat "$INSTALL_LOCK" 2>/dev/null || true)" = "$$" ] && rm -f "$INSTALL_LOCK" || true' EXIT
72
+ }
73
+
27
74
  # Serialize concurrent installer runs (SessionStart hook + slash-command
28
75
  # self-heal can both invoke this script). Wait for the active installer
29
76
  # rather than returning early, otherwise callers can re-check uv before
30
77
  # the first install has finished and report a false missing-dependency error.
31
- if command -v flock >/dev/null 2>&1; then
32
- exec 9>"$INSTALL_LOCK"
33
- if ! flock 9; then
34
- echo "[claude-smart] install lock failed; continuing without serialization" >&2
35
- echo '{"continue":true,"suppressOutput":true}'
36
- exit 0
37
- fi
38
- fi
78
+ acquire_install_lock
39
79
 
40
80
  rm -f "$FAILURE_MARKER"
41
81
 
package/plugin/uv.lock CHANGED
@@ -419,7 +419,7 @@ wheels = [
419
419
 
420
420
  [[package]]
421
421
  name = "claude-smart"
422
- version = "0.2.29"
422
+ version = "0.2.30"
423
423
  source = { editable = "." }
424
424
  dependencies = [
425
425
  { name = "chromadb" },