claude-smart 0.2.43 → 0.2.45

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.
Files changed (68) hide show
  1. package/.claude-plugin/marketplace.json +3 -3
  2. package/README.md +1 -1
  3. package/bin/claude-smart.js +2 -2
  4. package/package.json +2 -2
  5. package/plugin/.claude-plugin/plugin.json +2 -2
  6. package/plugin/.codex-plugin/plugin.json +1 -1
  7. package/plugin/README.md +21 -1
  8. package/plugin/pyproject.toml +3 -3
  9. package/plugin/scripts/_lib.sh +72 -28
  10. package/plugin/scripts/backend-service.sh +55 -7
  11. package/plugin/scripts/cli.sh +2 -1
  12. package/plugin/scripts/dashboard-service.sh +7 -5
  13. package/plugin/scripts/ensure-plugin-root.sh +1 -0
  14. package/plugin/scripts/hook_entry.sh +7 -5
  15. package/plugin/scripts/smart-install.sh +2 -2
  16. package/plugin/src/README.md +57 -0
  17. package/plugin/uv.lock +126 -5
  18. package/plugin/vendor/reflexio/.env.example +9 -0
  19. package/plugin/vendor/reflexio/pyproject.toml +5 -2
  20. package/plugin/vendor/reflexio/reflexio/cli/bootstrap_config.py +0 -1
  21. package/plugin/vendor/reflexio/reflexio/cli/commands/setup_cmd.py +7 -4
  22. package/plugin/vendor/reflexio/reflexio/cli/env_loader.py +4 -4
  23. package/plugin/vendor/reflexio/reflexio/lib/_storage_labels.py +2 -2
  24. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/entities.py +13 -4
  25. package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +3 -1
  26. package/plugin/vendor/reflexio/reflexio/models/api_schema/validators.py +59 -6
  27. package/plugin/vendor/reflexio/reflexio/models/config_schema.py +1 -1
  28. package/plugin/vendor/reflexio/reflexio/server/README.md +7 -1
  29. package/plugin/vendor/reflexio/reflexio/server/api.py +188 -34
  30. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/README.md +34 -0
  31. package/plugin/vendor/reflexio/reflexio/server/api_endpoints/publisher_api.py +33 -11
  32. package/plugin/vendor/reflexio/reflexio/server/llm/embedding_service.py +278 -29
  33. package/plugin/vendor/reflexio/reflexio/server/llm/litellm_client.py +457 -181
  34. package/plugin/vendor/reflexio/reflexio/server/llm/llm_utils.py +28 -0
  35. package/plugin/vendor/reflexio/reflexio/server/llm/model_defaults.py +22 -12
  36. package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedding_service_provider.py +137 -9
  37. package/plugin/vendor/reflexio/reflexio/server/llm/providers/local_embedding_provider.py +1 -1
  38. package/plugin/vendor/reflexio/reflexio/server/llm/providers/nomic_embedding_provider.py +36 -3
  39. package/plugin/vendor/reflexio/reflexio/server/llm/rerank/cross_encoder_reranker.py +13 -3
  40. package/plugin/vendor/reflexio/reflexio/server/llm/tools.py +17 -0
  41. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.0.prompt.md +1 -1
  42. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.1.prompt.md +69 -0
  43. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.2.prompt.md +71 -0
  44. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.2.0.prompt.md +27 -23
  45. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.2.2.prompt.md +234 -0
  46. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context/v4.2.3.prompt.md +244 -0
  47. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_extraction_context_expert/v3.3.0.prompt.md +19 -9
  48. package/plugin/vendor/reflexio/reflexio/server/services/README.md +58 -0
  49. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/group_evaluation_runner.py +1 -5
  50. package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +44 -2
  51. package/plugin/vendor/reflexio/reflexio/server/services/embedding_text.py +62 -0
  52. package/plugin/vendor/reflexio/reflexio/server/services/extraction/pending_tool_call_dispatch.py +8 -1
  53. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resumable_agent.py +91 -24
  54. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +3 -1
  55. package/plugin/vendor/reflexio/reflexio/server/services/extractor_config_utils.py +6 -3
  56. package/plugin/vendor/reflexio/reflexio/server/services/extractor_interaction_utils.py +1 -1
  57. package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +18 -5
  58. package/plugin/vendor/reflexio/reflexio/server/services/operation_state_utils.py +2 -2
  59. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_consolidator.py +88 -3
  60. package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_deduplicator.py +36 -5
  61. package/plugin/vendor/reflexio/reflexio/server/services/profile/profile_generation_service.py +6 -3
  62. package/plugin/vendor/reflexio/reflexio/server/services/reflection/reflection_service.py +6 -3
  63. package/plugin/vendor/reflexio/reflexio/server/services/retrieval/relevance_floor.py +32 -22
  64. package/plugin/vendor/reflexio/reflexio/server/services/service_utils.py +89 -4
  65. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_agent_run.py +46 -1
  66. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_agent_run.py +12 -0
  67. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_operations.py +1 -1
  68. package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +8 -4
@@ -4,14 +4,14 @@
4
4
  "name": "Yi Lu"
5
5
  },
6
6
  "metadata": {
7
- "description": "claude-smart — self-improving Claude Code and Codex plugin via reflexio"
7
+ "description": "claude-smart — self-improving Claude Code and Codex plugin powered by Reflexio"
8
8
  },
9
9
  "plugins": [
10
10
  {
11
11
  "name": "claude-smart",
12
- "version": "0.2.43",
12
+ "version": "0.2.45",
13
13
  "source": "./plugin",
14
- "description": "Turns user corrections into project-specific and shared skills via reflexio — uses Claude Code or Codex as the LLM backend (no external API key required)"
14
+ "description": "Turns corrections into Preferences, Project-specific skills, and Shared skills — uses Claude Code or Codex as the LLM backend (no external API key required)"
15
15
  }
16
16
  ]
17
17
  }
package/README.md CHANGED
@@ -13,7 +13,7 @@
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.43-green.svg" alt="Version">
16
+ <img src="https://img.shields.io/badge/version-0.2.45-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">
@@ -905,7 +905,7 @@ async function installVendoredReflexio(pluginRoot, uv, env) {
905
905
  process.stdout.write(`Installing bundled Reflexio source from ${vendorRoot}...\n`);
906
906
  let code = await runChecked(
907
907
  uv,
908
- ["pip", "install", "--project", pluginRoot, "--python", pythonPath, "--quiet", "-e", vendorRoot],
908
+ ["pip", "install", "--project", pluginRoot, "--python", pythonPath, "--quiet", "--reinstall", "--no-deps", vendorRoot],
909
909
  { cwd: pluginRoot, env },
910
910
  );
911
911
  if (code !== 0) {
@@ -914,7 +914,7 @@ async function installVendoredReflexio(pluginRoot, uv, env) {
914
914
  );
915
915
  code = await runChecked(
916
916
  uv,
917
- ["pip", "install", "--project", pluginRoot, "--python", pythonPath, "-e", vendorRoot],
917
+ ["pip", "install", "--project", pluginRoot, "--python", pythonPath, "--reinstall", "--no-deps", vendorRoot],
918
918
  { cwd: pluginRoot, env },
919
919
  );
920
920
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "claude-smart",
3
- "version": "0.2.43",
4
- "description": "Self-improving Claude Code and Codex plugin turns corrections into durable skills via reflexio",
3
+ "version": "0.2.45",
4
+ "description": "Self-improving Claude Code and Codex plugin that turns corrections into Preferences, Project-specific skills, and Shared skills",
5
5
  "keywords": [
6
6
  "claude",
7
7
  "claude-code",
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "claude-smart",
3
- "version": "0.2.43",
4
- "description": "Self-improving Claude Code and Codex plugin turns corrections into durable skills via reflexio",
3
+ "version": "0.2.45",
4
+ "description": "Self-improving Claude Code and Codex plugin that turns corrections into Preferences, Project-specific skills, and Shared skills",
5
5
  "author": {
6
6
  "name": "Yi Lu"
7
7
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-smart",
3
- "version": "0.2.43",
3
+ "version": "0.2.45",
4
4
  "description": "Self-improving coding assistant plugin — learns from corrections across sessions via reflexio",
5
5
  "author": {
6
6
  "name": "Yi Lu"
package/plugin/README.md CHANGED
@@ -6,6 +6,8 @@ This directory is the published Python package (`claude-smart` on PyPI) and the
6
6
 
7
7
  ## Install
8
8
 
9
+ ### Claude Code
10
+
9
11
  ```bash
10
12
  claude plugin marketplace add ReflexioAI/claude-smart
11
13
  claude plugin install claude-smart@reflexioai
@@ -25,8 +27,19 @@ embedding/ML dependency stack does not provide a complete native wheel set.
25
27
 
26
28
  Then restart Claude Code.
27
29
 
30
+ ### Codex
31
+
32
+ ```bash
33
+ npx claude-smart install --host codex
34
+ ```
35
+
36
+ Then fully quit and reopen Codex so hooks reload. Codex installs reuse the same
37
+ local Preferences, Project-specific skills, and Shared skills as Claude Code.
38
+
28
39
  ## Uninstall
29
40
 
41
+ ### Claude Code
42
+
30
43
  ```bash
31
44
  claude plugin uninstall claude-smart@reflexioai
32
45
  ```
@@ -37,7 +50,14 @@ Or, if Node.js or uv is already installed:
37
50
  npx claude-smart uninstall # or: uvx claude-smart uninstall
38
51
  ```
39
52
 
40
- Local data under `~/.reflexio/` and `~/.claude-smart/` is left in place — remove manually if desired.
53
+ ### Codex
54
+
55
+ ```bash
56
+ npx claude-smart uninstall --host codex
57
+ ```
58
+
59
+ Restart Codex after uninstalling. Local data under `~/.reflexio/` and
60
+ `~/.claude-smart/` is left in place for both hosts — remove manually if desired.
41
61
 
42
62
  ## License
43
63
 
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "claude-smart"
3
- version = "0.2.43"
4
- description = "Self-improving Claude Code and Codex plugin turns corrections into durable skills via reflexio"
3
+ version = "0.2.45"
4
+ description = "Self-improving Claude Code and Codex plugin that turns corrections into Preferences, Project-specific skills, and Shared skills"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12"
7
7
  dependencies = [
@@ -9,7 +9,7 @@ dependencies = [
9
9
  # normal installs; vendor releases may override it after uv sync by
10
10
  # installing plugin/vendor/reflexio. Keep in sync via
11
11
  # scripts/sync-reflexio-dep.py.
12
- "reflexio-ai>=0.2.23",
12
+ "reflexio-ai>=0.2.25",
13
13
  # Needed when Reflexio runs the local embedding provider (ONNXMiniLM_L6_V2).
14
14
  # Pulls in onnxruntime + tokenizers; the ~80 MB ONNX model itself is
15
15
  # downloaded on first use, not at install time.
@@ -3,11 +3,53 @@
3
3
  # Claude Code hooks run with a minimal non-interactive PATH that often omits
4
4
  # nvm/asdf/brew shims where `npm`, `uv`, etc. live. Pull the user's login-shell
5
5
  # PATH the same way claude-mem does so hook-spawned scripts find them without
6
- # the user having to mutate their global PATH. Best-effort failures silent.
6
+ # the user having to mutate their global PATH. Best-effort and bounded: shell
7
+ # profile startup can be slow or stuck on Windows, so this must never outlive a
8
+ # hook's timeout budget.
9
+ claude_smart_login_path_timeout_seconds() {
10
+ local _VALUE
11
+ _VALUE="${CLAUDE_SMART_LOGIN_PATH_TIMEOUT_SECONDS:-2}"
12
+ case "$_VALUE" in
13
+ ''|*[!0-9]*) printf '%s\n' "2" ;;
14
+ *) printf '%s\n' "$_VALUE" ;;
15
+ esac
16
+ }
17
+
18
+ claude_smart_capture_login_path() {
19
+ local _TIMEOUT _TMP _PID _WAITED _LINE
20
+ _TIMEOUT="$(claude_smart_login_path_timeout_seconds)"
21
+ [ "$_TIMEOUT" -gt 0 ] || return 1
22
+
23
+ _TMP="${TMPDIR:-/tmp}/claude-smart-login-path.$$"
24
+ rm -f "$_TMP" 2>/dev/null || true
25
+ "$SHELL" -lc 'printf %s "$PATH"' >"$_TMP" 2>/dev/null &
26
+ _PID=$!
27
+ _WAITED=0
28
+ while kill -0 "$_PID" 2>/dev/null; do
29
+ if [ "$_WAITED" -ge "$_TIMEOUT" ]; then
30
+ kill "$_PID" 2>/dev/null || true
31
+ wait "$_PID" 2>/dev/null || true
32
+ rm -f "$_TMP" 2>/dev/null || true
33
+ return 1
34
+ fi
35
+ sleep 1
36
+ _WAITED=$((_WAITED + 1))
37
+ done
38
+ if wait "$_PID" 2>/dev/null; then
39
+ while IFS= read -r _LINE || [ -n "$_LINE" ]; do
40
+ printf '%s' "$_LINE"
41
+ done <"$_TMP"
42
+ rm -f "$_TMP" 2>/dev/null || true
43
+ return 0
44
+ fi
45
+ rm -f "$_TMP" 2>/dev/null || true
46
+ return 1
47
+ }
48
+
7
49
  claude_smart_source_login_path() {
8
50
  local _SHELL_PATH
9
51
  if [ -n "${SHELL:-}" ] && [ -x "$SHELL" ]; then
10
- if _SHELL_PATH="$("$SHELL" -lc 'printf %s "$PATH"' 2>/dev/null)"; then
52
+ if _SHELL_PATH="$(claude_smart_capture_login_path)"; then
11
53
  [ -n "$_SHELL_PATH" ] && export PATH="$_SHELL_PATH:$PATH"
12
54
  fi
13
55
  fi
@@ -116,30 +158,24 @@ claude_smart_dashboard_unavailable_marker() {
116
158
  }
117
159
 
118
160
  claude_smart_node_recovery_hint() {
119
- cat <<'EOF'
120
- Recovery:
121
- 1. Restart Claude Code to let claude-smart retry its private Node.js install.
122
- 2. If the retry is blocked by your network or OS policy, install Node.js 20.9+ manually:
123
- EOF
161
+ printf '%s\n' \
162
+ "Recovery:" \
163
+ " 1. Restart Claude Code to let claude-smart retry its private Node.js install." \
164
+ " 2. If the retry is blocked by your network or OS policy, install Node.js 20.9+ manually:"
124
165
  if claude_smart_is_windows; then
125
- cat <<'EOF'
126
- - winget install OpenJS.NodeJS.LTS
127
- - or download the LTS installer from https://nodejs.org/
128
- EOF
166
+ printf '%s\n' \
167
+ " - winget install OpenJS.NodeJS.LTS" \
168
+ " - or download the LTS installer from https://nodejs.org/"
129
169
  elif [ "$(uname -s 2>/dev/null)" = "Darwin" ]; then
130
- cat <<'EOF'
131
- - brew install node
132
- - or download the LTS installer from https://nodejs.org/
133
- EOF
170
+ printf '%s\n' \
171
+ " - brew install node" \
172
+ " - or download the LTS installer from https://nodejs.org/"
134
173
  else
135
- cat <<'EOF'
136
- - use your distro package manager for nodejs/npm
137
- - or download the LTS archive from https://nodejs.org/
138
- EOF
174
+ printf '%s\n' \
175
+ " - use your distro package manager for nodejs/npm" \
176
+ " - or download the LTS archive from https://nodejs.org/"
139
177
  fi
140
- cat <<'EOF'
141
- 3. Run /claude-smart:restart, then /claude-smart:dashboard.
142
- EOF
178
+ printf '%s\n' " 3. Run /claude-smart:restart, then /claude-smart:dashboard."
143
179
  }
144
180
 
145
181
  claude_smart_write_dashboard_unavailable() {
@@ -527,14 +563,22 @@ claude_smart_append_capped_log() {
527
563
  # POSIX: setsid → python3 os.setsid → nohup (in that order of strength).
528
564
  # Windows: nohup alone — Git Bash has no setsid, no process groups, and
529
565
  # `os.setsid()` is POSIX-only; nohup ignores SIGHUP which is enough to
530
- # survive the parent console closing. The python3 fallback is gated on a
531
- # real-interpreter probe (-V) so the Windows App Execution Alias stub
532
- # doesn't get invoked. Caller is responsible for redirecting stdout/stderr;
533
- # we do not impose a log destination here. Stdin is closed so the child
534
- # cannot inherit a tty. Use `$!` after this call to capture the pid.
566
+ # survive the parent console closing. On Windows, stdout/stderr are also
567
+ # closed here so hook-runner pipes cannot be kept alive by long-lived
568
+ # background children after the hook script exits unless the caller explicitly
569
+ # opts into preserving stdout/stderr for a file redirection. The python3
570
+ # fallback is gated on a real-interpreter probe (-V) so the Windows App
571
+ # Execution Alias stub doesn't get invoked. POSIX callers are responsible for
572
+ # redirecting stdout/stderr; we do not impose a log destination there. Stdin is
573
+ # closed so the child cannot inherit a tty. Use `$!` after this call to capture
574
+ # the pid.
535
575
  claude_smart_spawn_detached() {
536
576
  if claude_smart_is_windows; then
537
- nohup "$@" < /dev/null &
577
+ if [ "${CLAUDE_SMART_SPAWN_KEEP_OUTPUT:-}" = "1" ]; then
578
+ nohup "$@" < /dev/null &
579
+ else
580
+ nohup "$@" < /dev/null > /dev/null 2>&1 &
581
+ fi
538
582
  return 0
539
583
  fi
540
584
  if command -v setsid >/dev/null 2>&1; then
@@ -5,8 +5,8 @@
5
5
  #
6
6
  # Subcommands:
7
7
  # start probe /health; if nothing we recognize is on the port,
8
- # spawn `uv run reflexio services start --only backend
9
- # --no-reload` detached. Polls /health briefly so first
8
+ # spawn the prepared venv's `reflexio services start --only
9
+ # backend --no-reload` detached. Polls /health briefly so first
10
10
  # use after session start lands on a warm server, then
11
11
  # returns a continue payload regardless.
12
12
  # stop SIGTERM the recorded process group, escalating to
@@ -20,11 +20,13 @@ set -eu
20
20
  HERE="$(cd "$(dirname "$0")" && pwd)"
21
21
  # shellcheck source=_lib.sh
22
22
  . "$HERE/_lib.sh"
23
- claude_smart_source_login_path
23
+ CMD="${1:-start}"
24
+ if [ "$CMD" != "session-end" ]; then
25
+ claude_smart_source_login_path
26
+ fi
24
27
  claude_smart_prepend_astral_bins
25
28
  claude_smart_source_reflexio_env
26
29
 
27
- CMD="${1:-start}"
28
30
  PORT=8071
29
31
  EMBEDDING_PORT="${EMBEDDING_PORT:-8072}"
30
32
  # Pass through to `reflexio services start/stop` so the spawned backend
@@ -183,6 +185,35 @@ port_holder() {
183
185
  | awk 'NR==2 {print $1" (pid "$2")"; exit}'
184
186
  }
185
187
 
188
+ ensure_vendored_reflexio_active() {
189
+ vendor="$PLUGIN_ROOT/vendor/reflexio"
190
+ [ -f "$vendor/pyproject.toml" ] || return 0
191
+ plugin_python="$(claude_smart_plugin_python "$PLUGIN_ROOT")"
192
+ [ -x "$plugin_python" ] || return 0
193
+ if "$plugin_python" - "$vendor/pyproject.toml" <<'PY' >/dev/null 2>&1; then
194
+ import importlib.metadata
195
+ import re
196
+ import sys
197
+ from pathlib import Path
198
+
199
+ text = Path(sys.argv[1]).read_text(encoding="utf-8")
200
+ match = re.search(r'^version\s*=\s*"([^"]+)"', text, re.MULTILINE)
201
+ if not match:
202
+ raise SystemExit(1)
203
+ expected = match.group(1)
204
+ try:
205
+ installed = importlib.metadata.version("reflexio-ai")
206
+ except importlib.metadata.PackageNotFoundError:
207
+ raise SystemExit(1)
208
+ raise SystemExit(0 if installed == expected else 1)
209
+ PY
210
+ return 0
211
+ fi
212
+ claude_smart_append_capped_log "$LOG_FILE" "$LOG_MAX_BYTES" \
213
+ "[claude-smart] backend: repairing vendored Reflexio install before start"
214
+ uv pip install --project "$PLUGIN_ROOT" --python "$plugin_python" --quiet --reinstall --no-deps "$vendor" >&2
215
+ }
216
+
186
217
  # Full shutdown: kill the recorded process group (if any) then sweep
187
218
  # both the backend port and the embedding-service port for surviving
188
219
  # reflexio listeners. Used by both `stop` and the opt-in `session-end`
@@ -233,7 +264,7 @@ case "$CMD" in
233
264
  if ! command -v uv >/dev/null 2>&1; then
234
265
  if [ "${CLAUDE_SMART_BOOTSTRAPPING:-}" != "1" ] && [ -x "$PLUGIN_ROOT/scripts/smart-install.sh" ]; then
235
266
  claude_smart_append_capped_log "$LOG_FILE" "$LOG_MAX_BYTES" "[claude-smart] backend: uv not on PATH; starting installer in background"
236
- claude_smart_spawn_detached env CLAUDE_SMART_BOOTSTRAPPING=1 \
267
+ CLAUDE_SMART_SPAWN_KEEP_OUTPUT=1 claude_smart_spawn_detached env CLAUDE_SMART_BOOTSTRAPPING=1 \
237
268
  bash "$PLUGIN_ROOT/scripts/smart-install.sh" \
238
269
  >>"$STATE_DIR/install.log" 2>&1 || true
239
270
  fi
@@ -243,6 +274,7 @@ case "$CMD" in
243
274
  fi
244
275
  fi
245
276
  cd "$PLUGIN_ROOT"
277
+ ensure_vendored_reflexio_active
246
278
 
247
279
  # Cap local interaction history to keep the SQLite store small for
248
280
  # claude-smart users. Reflexio's library defaults are much higher
@@ -266,6 +298,21 @@ case "$CMD" in
266
298
  # without touching the file on disk.
267
299
  export REFLEXIO_STORAGE="sqlite"
268
300
 
301
+ backend_pythonpath="${PYTHONPATH:-}"
302
+ if [ -d "$PLUGIN_ROOT/vendor/reflexio/reflexio" ]; then
303
+ vendor_pythonpath="$PLUGIN_ROOT/vendor/reflexio"
304
+ pythonpath_sep=":"
305
+ if claude_smart_is_windows; then
306
+ # Native Windows Python expects ;-separated Windows-style paths in
307
+ # PYTHONPATH; MSYS does not auto-convert arbitrary env vars.
308
+ pythonpath_sep=";"
309
+ if command -v cygpath >/dev/null 2>&1; then
310
+ vendor_pythonpath="$(cygpath -w "$vendor_pythonpath")"
311
+ fi
312
+ fi
313
+ backend_pythonpath="$vendor_pythonpath${backend_pythonpath:+$pythonpath_sep$backend_pythonpath}"
314
+ fi
315
+
269
316
  # (nohup; no process groups). backend-log-runner.sh owns stdout/stderr
270
317
  # capture so process output cannot grow backend.log past its cap.
271
318
  #
@@ -277,11 +324,12 @@ case "$CMD" in
277
324
  # CLAUDE_SMART_BACKEND_WORKERS for power users running concurrent
278
325
  # Claude Code sessions or wanting zero-downtime recycling.
279
326
  workers="${CLAUDE_SMART_BACKEND_WORKERS:-1}"
327
+ backend_python="$(claude_smart_plugin_python "$PLUGIN_ROOT")"
280
328
  claude_smart_spawn_detached bash "$HERE/backend-log-runner.sh" \
281
329
  "$LOG_FILE" "$LOG_MAX_BYTES" -- \
282
330
  env PYTHONIOENCODING="${PYTHONIOENCODING:-utf-8}" \
283
- uv run --project "$PLUGIN_ROOT" --no-sync --quiet \
284
- reflexio services start --only backend --no-reload --workers "$workers"
331
+ PYTHONPATH="$backend_pythonpath" \
332
+ "$backend_python" -m reflexio.cli services start --only backend --no-reload --workers "$workers"
285
333
  svc_pid=$!
286
334
  # Record the spawned pid, not a pgid sampled with ps. On POSIX,
287
335
  # setsid/python os.setsid make this pid the new process group leader;
@@ -91,4 +91,5 @@ if ! claude_smart_python_imports "$PLUGIN_ROOT" claude_smart.cli; then
91
91
  fi
92
92
  fi
93
93
 
94
- exec uv run --project "$PLUGIN_ROOT" --no-sync --quiet python -m claude_smart.cli "$@"
94
+ PLUGIN_PYTHON="$(claude_smart_plugin_python "$PLUGIN_ROOT")"
95
+ exec "$PLUGIN_PYTHON" -m claude_smart.cli "$@"
@@ -21,11 +21,13 @@ set -eu
21
21
  HERE="$(cd "$(dirname "$0")" && pwd)"
22
22
  # shellcheck source=_lib.sh
23
23
  . "$HERE/_lib.sh"
24
- claude_smart_source_login_path
24
+ CMD="${1:-start}"
25
+ if [ "$CMD" != "session-end" ]; then
26
+ claude_smart_source_login_path
27
+ fi
25
28
  claude_smart_prepend_node_bins
26
29
  claude_smart_source_reflexio_env
27
30
 
28
- CMD="${1:-start}"
29
31
  PORT=3001
30
32
 
31
33
  PLUGIN_ROOT="$(cd "$HERE/.." && pwd)"
@@ -192,7 +194,7 @@ case "$CMD" in
192
194
  if [ -z "$NPM_BIN" ] || ! "$NPM_BIN" --version >/dev/null 2>&1; then
193
195
  if [ "${CLAUDE_SMART_BOOTSTRAPPING:-}" != "1" ] && [ -x "$PLUGIN_ROOT/scripts/smart-install.sh" ]; then
194
196
  echo "[claude-smart] dashboard: npm is not on PATH; starting installer in background" >>"$LOG_FILE"
195
- claude_smart_spawn_detached env CLAUDE_SMART_BOOTSTRAPPING=1 \
197
+ CLAUDE_SMART_SPAWN_KEEP_OUTPUT=1 claude_smart_spawn_detached env CLAUDE_SMART_BOOTSTRAPPING=1 \
196
198
  bash "$PLUGIN_ROOT/scripts/smart-install.sh" \
197
199
  >>"$STATE_DIR/install.log" 2>&1 || true
198
200
  fi
@@ -214,7 +216,7 @@ case "$CMD" in
214
216
  BUILD_PID_FILE="$STATE_DIR/dashboard-build.pid"
215
217
  if ! claude_smart_pid_alive_file "$BUILD_PID_FILE"; then
216
218
  echo "[claude-smart] dashboard: .next missing — starting background build (~1-2 min)" >>"$LOG_FILE"
217
- claude_smart_spawn_detached bash "$HERE/dashboard-build.sh" >>"$LOG_FILE" 2>&1
219
+ CLAUDE_SMART_SPAWN_KEEP_OUTPUT=1 claude_smart_spawn_detached bash "$HERE/dashboard-build.sh" >>"$LOG_FILE" 2>&1
218
220
  fi
219
221
  emit_ok; exit 0
220
222
  fi
@@ -229,7 +231,7 @@ case "$CMD" in
229
231
  # Caller-side `>>file 2>&1` redirection is honoured before the child
230
232
  # detaches, so per-OS log paths stay identical.
231
233
  export CLAUDE_SMART_DASHBOARD_WORKSPACE="$WORKSPACE_CWD"
232
- claude_smart_spawn_detached "$NPM_BIN" run start >>"$LOG_FILE" 2>&1
234
+ CLAUDE_SMART_SPAWN_KEEP_OUTPUT=1 claude_smart_spawn_detached "$NPM_BIN" run start >>"$LOG_FILE" 2>&1
233
235
  dash_pid=$!
234
236
  # Record the spawned pid, not a pgid sampled with ps. On POSIX,
235
237
  # setsid/python os.setsid make this pid the new process group leader;
@@ -20,6 +20,7 @@ if [ ! -f "$TARGET/pyproject.toml" ]; then
20
20
  echo "[claude-smart] ensure-plugin-root: $TARGET is not a plugin dir (no pyproject.toml)" >&2
21
21
  exit 1
22
22
  fi
23
+ TARGET="$(cd "$TARGET" && pwd -P)"
23
24
 
24
25
  LINK="$HOME/.reflexio/plugin-root"
25
26
  mkdir -p "$(dirname "$LINK")"
@@ -3,8 +3,9 @@
3
3
  # CLAUDE_PLUGIN_ROOT points at the plugin dir (dev: <repo>/plugin;
4
4
  # installed: ~/.claude/plugins/cache/reflexioai/claude-smart/<version>),
5
5
  # which is also the Python project root with pyproject.toml + uv.lock.
6
- # We invoke via `uv run --project --no-sync` so the prepared env is used without
7
- # undoing editable Reflexio installs from local development.
6
+ # We invoke the prepared venv Python directly so hooks use the dependency set
7
+ # produced by smart-install.sh, including the vendored Reflexio snapshot it
8
+ # installs non-editably (--reinstall --no-deps).
8
9
  #
9
10
  # If the Setup hook recorded an install failure at
10
11
  # ~/.claude-smart/install-failed, short-circuit with a user-visible
@@ -95,7 +96,7 @@ if ! command -v uv >/dev/null 2>&1; then
95
96
  fi
96
97
  if [ -x "$PLUGIN_ROOT/scripts/smart-install.sh" ]; then
97
98
  mkdir -p "$STATE_DIR"
98
- claude_smart_spawn_detached env CLAUDE_SMART_BOOTSTRAPPING=1 \
99
+ CLAUDE_SMART_SPAWN_KEEP_OUTPUT=1 claude_smart_spawn_detached env CLAUDE_SMART_BOOTSTRAPPING=1 \
99
100
  bash "$PLUGIN_ROOT/scripts/smart-install.sh" \
100
101
  >>"$STATE_DIR/install.log" 2>&1 || true
101
102
  fi
@@ -122,7 +123,7 @@ ensure_hook_package_importable() {
122
123
  printf '%s\n' "[claude-smart] hook: claude_smart.hook is not importable; retrying install in background"
123
124
  date 2>/dev/null || true
124
125
  } >>"$STATE_DIR/install.log" 2>&1
125
- claude_smart_spawn_detached env CLAUDE_SMART_BOOTSTRAPPING=1 \
126
+ CLAUDE_SMART_SPAWN_KEEP_OUTPUT=1 claude_smart_spawn_detached env CLAUDE_SMART_BOOTSTRAPPING=1 \
126
127
  bash "$PLUGIN_ROOT/scripts/smart-install.sh" \
127
128
  >>"$STATE_DIR/install.log" 2>&1 || true
128
129
 
@@ -135,4 +136,5 @@ if ! ensure_hook_package_importable; then
135
136
  fi
136
137
 
137
138
  # Stdin is the hook payload JSON — stream it through to the Python CLI.
138
- exec uv run --project "$PLUGIN_ROOT" --no-sync --quiet python -m claude_smart.hook "$HOST" "$EVENT"
139
+ PLUGIN_PYTHON="$(claude_smart_plugin_python "$PLUGIN_ROOT")"
140
+ exec "$PLUGIN_PYTHON" -m claude_smart.hook "$HOST" "$EVENT"
@@ -142,12 +142,12 @@ install_vendored_reflexio() {
142
142
  fi
143
143
 
144
144
  echo "[claude-smart] installing bundled Reflexio source from $VENDORED_REFLEXIO" >&2
145
- if uv pip install --project "$PLUGIN_ROOT" --python "$PLUGIN_PYTHON" --quiet -e "$VENDORED_REFLEXIO" >&2; then
145
+ if uv pip install --project "$PLUGIN_ROOT" --python "$PLUGIN_PYTHON" --quiet --reinstall --no-deps "$VENDORED_REFLEXIO" >&2; then
146
146
  return 0
147
147
  fi
148
148
 
149
149
  echo "[claude-smart] warning: quiet vendored Reflexio install failed in $PLUGIN_ROOT; retrying with full output." >&2
150
- if ! uv pip install --project "$PLUGIN_ROOT" --python "$PLUGIN_PYTHON" -e "$VENDORED_REFLEXIO" >&2; then
150
+ if ! uv pip install --project "$PLUGIN_ROOT" --python "$PLUGIN_PYTHON" --reinstall --no-deps "$VENDORED_REFLEXIO" >&2; then
151
151
  write_failure "vendored Reflexio install failed in $PLUGIN_ROOT"
152
152
  fi
153
153
  }
@@ -0,0 +1,57 @@
1
+ # claude_smart (plugin/src)
2
+ Description: Python package powering the claude-smart plugin — hook handlers that buffer Claude Code / Codex session activity, publish it to a local Reflexio backend, and inject the learned skills/preferences back into future sessions.
3
+
4
+ > User-facing install/usage docs live in [`../README.md`](../README.md). This is the code map for the `claude_smart` package.
5
+
6
+ ## Main Entry Points
7
+
8
+ | File | Purpose |
9
+ |------|---------|
10
+ | `hook.py` | Hook dispatcher. Parses the stdin JSON payload and routes each event (Setup, SessionStart, UserPromptSubmit, PreToolUse, PostToolUse, Stop, SessionEnd) to its handler in `events/`. |
11
+ | `cli.py` | `claude-smart` CLI: `install`, `uninstall`, `show`, `learn`, `restart`, `dashboard`, `clear-all`. |
12
+ | `state.py` | Per-session JSONL buffer at `~/.claude-smart/sessions/{session_id}.jsonl`; high-water mark for idempotent publish retries. |
13
+ | `publish.py` | Drains the session buffer, calls the Reflexio adapter, and stamps the watermark. Used by Stop, SessionEnd, and `learn`. |
14
+ | `reflexio_adapter.py` | Thin wrapper over `reflexio.ReflexioClient` — isolates import failures, 5s HTTP timeout, injects/queries learnings, degrades gracefully when the backend is down. |
15
+
16
+ ## Event Handlers (`events/`)
17
+
18
+ | Handler | Hook | Role |
19
+ |---------|------|------|
20
+ | `session_start.py` | SessionStart | Apply extraction defaults, render stall banner, push optimizer context. |
21
+ | `user_prompt.py` | UserPromptSubmit | Inject learned playbooks/preferences into context. |
22
+ | `pre_tool.py` | PreToolUse (Edit/Write/Bash/NotebookEdit) | Inject project-specific context before tool execution. |
23
+ | `post_tool.py` | PostToolUse | Buffer the tool invocation (name, input, response, duration) into session state. |
24
+ | `stop.py` | Stop | Publish unpublished interactions (force extraction if configured). |
25
+ | `session_end.py` | SessionEnd | Final publish + aggregation; optional backend/dashboard shutdown. |
26
+
27
+ ## Supporting Modules
28
+
29
+ | File | Purpose |
30
+ |------|---------|
31
+ | `env_config.py` | Parses `~/.reflexio/.env` (`REFLEXIO_URL`, `REFLEXIO_API_KEY`, `CLAUDE_SMART_READ_ONLY`, local embedding/CLI flags). |
32
+ | `runtime.py` | Host detection (Claude Code vs Codex); shared agent version. |
33
+ | `ids.py` | Session / project ID generation and resolution. |
34
+ | `context_inject.py`, `context_format.py`, `query_compose.py`, `cs_cite.py` | Build search queries, format learned skills as markdown, inject into context, format citations. |
35
+ | `stall_banner.py` | User-facing message when the Reflexio provider hits an auth/billing stall. |
36
+ | `optimizer_assistant.py` | Claude-code CLI agent that extracts Reflexio optimization hints. |
37
+ | `hook_log.py`, `internal_call.py` | Structured JSON logging to `~/.claude-smart/hook.log`; detect internal/test invocations to skip learning. |
38
+
39
+ ## Architecture
40
+
41
+ ```
42
+ SessionStart -> bootstrap Reflexio backend (8071) + Next.js dashboard (3001)
43
+ PostToolUse -> state.py buffers each tool call to ~/.claude-smart/sessions/{id}.jsonl
44
+ Stop/SessionEnd -> publish.py -> reflexio_adapter -> Reflexio extracts playbooks + preferences
45
+ UserPromptSubmit / PreToolUse -> context_inject pulls relevant learnings back into context
46
+ ```
47
+
48
+ - **Dual-host** — the same package runs under Claude Code (native slash commands) and Codex (shell-script fallbacks in `../scripts/`); host shape is normalized in `runtime.py`.
49
+ - **Offline-resilient** — if Reflexio is unreachable, interactions stay buffered and drain on the next successful publish.
50
+ - **Hooks registered** in `../hooks/hooks.json` (Claude Code) and `../hooks/codex-hooks.json` (Codex), dispatched through `../scripts/hook_entry.sh`.
51
+
52
+ ## Requirements / Problems to Avoid
53
+
54
+ - **All learned state is external to the repo** — sessions buffer under `~/.claude-smart/`, Reflexio state under `~/.reflexio/`. Don't write generated runtime artifacts inside the plugin root.
55
+ - **Backend lives at `http://localhost:8071`**, dashboard at `http://localhost:3001`; both are long-lived across sessions (no hard shutdown on SessionEnd by default).
56
+ - **`CLAUDE_SMART_READ_ONLY`** skips publishing entirely — respect it in any new publish path.
57
+ - **Keep handlers fast** — tool-use hooks run on a 10–15s timeout; heavy work belongs in the backend, not the hook.