claude-smart 0.2.49 → 0.2.50

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 (177) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/README.md +10 -43
  3. package/bin/claude-smart.js +105 -0
  4. package/package.json +3 -2
  5. package/plugin/.claude-plugin/plugin.json +1 -1
  6. package/plugin/.codex-plugin/plugin.json +1 -1
  7. package/plugin/.coverage +0 -0
  8. package/plugin/README.md +4 -3
  9. package/plugin/dashboard/app/api/reflexio/[...path]/route.ts +4 -2
  10. package/plugin/dashboard/app/dashboard/page.tsx +6 -1
  11. package/plugin/dashboard/app/preferences/[id]/page.tsx +18 -6
  12. package/plugin/dashboard/app/preferences/page.tsx +32 -35
  13. package/plugin/dashboard/app/sessions/[sessionId]/page.tsx +16 -1
  14. package/plugin/dashboard/app/sessions/page.tsx +2 -0
  15. package/plugin/dashboard/app/skills/page.tsx +65 -50
  16. package/plugin/dashboard/app/skills/project/[id]/page.tsx +17 -8
  17. package/plugin/dashboard/app/skills/shared/[id]/page.tsx +1 -6
  18. package/plugin/dashboard/components/common/host-badge.tsx +118 -0
  19. package/plugin/dashboard/components/common/learning-application-badge.tsx +34 -0
  20. package/plugin/dashboard/components/common/learnings-badge.tsx +1 -1
  21. package/plugin/dashboard/components/common/page-header.tsx +3 -3
  22. package/plugin/dashboard/lib/config-file.ts +5 -1
  23. package/plugin/dashboard/lib/host-attribution.ts +62 -0
  24. package/plugin/dashboard/lib/session-reader.ts +40 -2
  25. package/plugin/dashboard/lib/types.ts +7 -1
  26. package/plugin/pyproject.toml +1 -1
  27. package/plugin/scripts/_lib.sh +197 -1
  28. package/plugin/scripts/backend-python-runner.py +46 -0
  29. package/plugin/scripts/backend-service.sh +757 -119
  30. package/plugin/scripts/codex-hook.js +63 -225
  31. package/plugin/scripts/dashboard-open.sh +6 -4
  32. package/plugin/scripts/dashboard-service.sh +117 -136
  33. package/plugin/scripts/hook_entry.sh +3 -0
  34. package/plugin/scripts/smart-install.sh +15 -1
  35. package/plugin/src/claude_smart/cli.py +14 -0
  36. package/plugin/src/claude_smart/context_inject.py +3 -0
  37. package/plugin/src/claude_smart/env_config.py +4 -1
  38. package/plugin/src/claude_smart/events/post_tool.py +2 -1
  39. package/plugin/src/claude_smart/events/session_end.py +2 -1
  40. package/plugin/src/claude_smart/events/stop.py +3 -0
  41. package/plugin/src/claude_smart/events/user_prompt.py +2 -1
  42. package/plugin/src/claude_smart/internal_call.py +5 -2
  43. package/plugin/src/claude_smart/optimizer_assistant.py +59 -13
  44. package/plugin/src/claude_smart/publish.py +59 -7
  45. package/plugin/src/claude_smart/reflexio_adapter.py +137 -14
  46. package/plugin/src/claude_smart/runtime.py +15 -6
  47. package/plugin/src/claude_smart/state.py +211 -52
  48. package/plugin/uv.lock +1 -1
  49. package/plugin/vendor/reflexio/.env.example +13 -0
  50. package/plugin/vendor/reflexio/reflexio/README.md +7 -3
  51. package/plugin/vendor/reflexio/reflexio/__init__.py +12 -0
  52. package/plugin/vendor/reflexio/reflexio/client/client.py +126 -3
  53. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/src/openclaw_smart/state.py +10 -3
  54. package/plugin/vendor/reflexio/reflexio/integrations/openclaw/plugin/tests/test_state.py +28 -0
  55. package/plugin/vendor/reflexio/reflexio/lib/_search.py +41 -0
  56. package/plugin/vendor/reflexio/reflexio/models/api_schema/domain/entities.py +177 -25
  57. package/plugin/vendor/reflexio/reflexio/models/api_schema/eval_overview_schema.py +7 -1
  58. package/plugin/vendor/reflexio/reflexio/models/api_schema/internal_schema.py +2 -1
  59. package/plugin/vendor/reflexio/reflexio/models/api_schema/retriever_schema.py +63 -4
  60. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/converters.py +1 -0
  61. package/plugin/vendor/reflexio/reflexio/models/api_schema/ui/entities.py +8 -1
  62. package/plugin/vendor/reflexio/reflexio/models/config_schema.py +1 -1
  63. package/plugin/vendor/reflexio/reflexio/server/README.md +22 -4
  64. package/plugin/vendor/reflexio/reflexio/server/__init__.py +18 -8
  65. package/plugin/vendor/reflexio/reflexio/server/__main__.py +6 -0
  66. package/plugin/vendor/reflexio/reflexio/server/api.py +66 -3
  67. package/plugin/vendor/reflexio/reflexio/server/billing_meter.py +263 -3
  68. package/plugin/vendor/reflexio/reflexio/server/callback_executor.py +164 -0
  69. package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_embedding.py +81 -81
  70. package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_subprocess.py +28 -0
  71. package/plugin/vendor/reflexio/reflexio/server/llm/_litellm_text_generation.py +62 -5
  72. package/plugin/vendor/reflexio/reflexio/server/llm/embedding_service.py +19 -52
  73. package/plugin/vendor/reflexio/reflexio/server/llm/model_defaults.py +28 -0
  74. package/plugin/vendor/reflexio/reflexio/server/llm/providers/claude_code_provider.py +9 -1
  75. package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedder_warmup.py +329 -0
  76. package/plugin/vendor/reflexio/reflexio/server/llm/providers/embedding_service_provider.py +85 -10
  77. package/plugin/vendor/reflexio/reflexio/server/llm/providers/local_embedding_provider.py +20 -5
  78. package/plugin/vendor/reflexio/reflexio/server/llm/providers/nomic_embedding_provider.py +77 -9
  79. package/plugin/vendor/reflexio/reflexio/server/org_fanout.py +184 -0
  80. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/document_expansion/v1.0.0.prompt.md +1 -1
  81. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/document_expansion/v1.1.0.prompt.md +32 -0
  82. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.3.3.prompt.md +1 -1
  83. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/playbook_consolidation/v2.4.0.prompt.md +63 -0
  84. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/query_reformulation/v1.0.0.prompt.md +1 -1
  85. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/query_reformulation/v2.0.0.prompt.md +30 -0
  86. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/retrieved_learning_impact/v1.0.0.prompt.md +51 -0
  87. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/retrieved_learning_relevance/v1.0.0.prompt.md +39 -0
  88. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/shadow_comparison/v1.0.0.prompt.md +1 -1
  89. package/plugin/vendor/reflexio/reflexio/server/prompt/prompt_bank/shadow_comparison/v1.1.0.prompt.md +43 -0
  90. package/plugin/vendor/reflexio/reflexio/server/routes/config.py +3 -3
  91. package/plugin/vendor/reflexio/reflexio/server/routes/evaluation.py +122 -28
  92. package/plugin/vendor/reflexio/reflexio/server/routes/system.py +22 -3
  93. package/plugin/vendor/reflexio/reflexio/server/scheduling.py +64 -3
  94. package/plugin/vendor/reflexio/reflexio/server/services/README.md +6 -4
  95. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/README.md +3 -2
  96. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/_eval_health.py +41 -0
  97. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/components/retrieved_learning_evaluator.py +554 -0
  98. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/regen_jobs.py +35 -1
  99. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/runner.py +253 -101
  100. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/scheduler.py +5 -7
  101. package/plugin/vendor/reflexio/reflexio/server/services/agent_success_evaluation/service.py +27 -0
  102. package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_extraction_lifecycle.py +11 -5
  103. package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_should_run.py +4 -4
  104. package/plugin/vendor/reflexio/reflexio/server/services/base_generation/_usage_billing.py +6 -2
  105. package/plugin/vendor/reflexio/reflexio/server/services/base_generation_service.py +255 -74
  106. package/plugin/vendor/reflexio/reflexio/server/services/deduplication_utils.py +72 -0
  107. package/plugin/vendor/reflexio/reflexio/server/services/deferred_learning_plan.py +270 -0
  108. package/plugin/vendor/reflexio/reflexio/server/services/durable_learning/worker.py +85 -16
  109. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/components/hero_state.py +2 -11
  110. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/components/rule_attribution.py +6 -2
  111. package/plugin/vendor/reflexio/reflexio/server/services/evaluation_overview/service.py +17 -13
  112. package/plugin/vendor/reflexio/reflexio/server/services/extraction/agent_run_records.py +18 -3
  113. package/plugin/vendor/reflexio/reflexio/server/services/extraction/outcome.py +12 -1
  114. package/plugin/vendor/reflexio/reflexio/server/services/extraction/prior_answer_search.py +1 -1
  115. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resumable_agent.py +2 -1
  116. package/plugin/vendor/reflexio/reflexio/server/services/extraction/resume_worker.py +66 -0
  117. package/plugin/vendor/reflexio/reflexio/server/services/generation_service.py +510 -63
  118. package/plugin/vendor/reflexio/reflexio/server/services/governance/service.py +2 -0
  119. package/plugin/vendor/reflexio/reflexio/server/services/lineage/gc_scheduler.py +179 -99
  120. package/plugin/vendor/reflexio/reflexio/server/services/lineage/vector_backfill_sweep.py +139 -0
  121. package/plugin/vendor/reflexio/reflexio/server/services/operation_state_utils.py +66 -27
  122. package/plugin/vendor/reflexio/reflexio/server/services/playbook/aggregation_trigger.py +177 -0
  123. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/aggregator.py +68 -2
  124. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/consolidator.py +360 -49
  125. package/plugin/vendor/reflexio/reflexio/server/services/playbook/components/extractor.py +27 -30
  126. package/plugin/vendor/reflexio/reflexio/server/services/playbook/playbook_edit_apply.py +8 -1
  127. package/plugin/vendor/reflexio/reflexio/server/services/playbook/service.py +137 -69
  128. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/optimizer.py +20 -1
  129. package/plugin/vendor/reflexio/reflexio/server/services/playbook_optimizer/scheduler.py +13 -6
  130. package/plugin/vendor/reflexio/reflexio/server/services/pre_retrieval/_query_reformulator.py +40 -25
  131. package/plugin/vendor/reflexio/reflexio/server/services/profile/components/consolidator.py +12 -39
  132. package/plugin/vendor/reflexio/reflexio/server/services/profile/components/extractor.py +30 -35
  133. package/plugin/vendor/reflexio/reflexio/server/services/profile/service.py +122 -44
  134. package/plugin/vendor/reflexio/reflexio/server/services/reflection/service.py +457 -107
  135. package/plugin/vendor/reflexio/reflexio/server/services/retrieval/session_dedup.py +127 -0
  136. package/plugin/vendor/reflexio/reflexio/server/services/retrieval/temporal.py +104 -0
  137. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/dispatcher.py +139 -0
  138. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/judge.py +16 -6
  139. package/plugin/vendor/reflexio/reflexio/server/services/shadow_comparison/worker.py +137 -0
  140. package/plugin/vendor/reflexio/reflexio/server/services/storage/governance_validation.py +12 -0
  141. package/plugin/vendor/reflexio/reflexio/server/services/storage/lifecycle_filters.py +54 -0
  142. package/plugin/vendor/reflexio/reflexio/server/services/storage/retention.py +41 -3
  143. package/plugin/vendor/reflexio/reflexio/server/services/storage/retention_mixin.py +40 -2
  144. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/__init__.py +1 -1
  145. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_base.py +50 -0
  146. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_extras.py +11 -4
  147. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_governance.py +28 -1
  148. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_learning_jobs.py +36 -1
  149. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/_shadow_verdicts.py +4 -0
  150. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/base/_deletion.py +16 -5
  151. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_erase_execution.py +104 -42
  152. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/governance/_subject_barrier.py +7 -1
  153. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_agent.py +43 -0
  154. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_eval_results.py +430 -5
  155. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/playbook/_user.py +45 -7
  156. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_interaction_store.py +146 -6
  157. package/plugin/vendor/reflexio/reflexio/server/services/storage/sqlite_storage/profiles/_profile_store.py +42 -7
  158. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/__init__.py +1 -1
  159. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_extras.py +9 -2
  160. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_learning_jobs.py +50 -0
  161. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_operations.py +7 -0
  162. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_retrieval_log.py +3 -1
  163. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/_shadow_verdicts.py +4 -0
  164. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/agent_run/_models.py +12 -1
  165. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/evaluation_state_keys.py +78 -0
  166. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_agent.py +38 -0
  167. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_eval_results.py +171 -0
  168. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/playbook/_user.py +52 -1
  169. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_interaction_store.py +59 -0
  170. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/profiles/_profile_store.py +52 -2
  171. package/plugin/vendor/reflexio/reflexio/server/services/storage/storage_base/retrieved_learning_state.py +226 -0
  172. package/plugin/vendor/reflexio/reflexio/server/services/tagging/tagging_scheduler.py +5 -6
  173. package/plugin/vendor/reflexio/reflexio/server/services/unified_search_service.py +209 -29
  174. package/plugin/vendor/reflexio/reflexio/server/usage_metrics.py +3 -0
  175. package/plugin/vendor/reflexio/reflexio/test_support/llm_mock.py +61 -0
  176. package/plugin/vendor/reflexio/reflexio/test_support/llm_model_registry.py +33 -0
  177. package/plugin/vendor/reflexio/reflexio/server/services/search/__init__.py +0 -0
@@ -9,9 +9,9 @@
9
9
  "plugins": [
10
10
  {
11
11
  "name": "claude-smart",
12
- "version": "0.2.49",
13
12
  "source": "./plugin",
14
- "description": "Turns corrections into Preferences, Project-specific skills, and Shared skills — uses Claude Code, Codex, or OpenCode as the LLM backend (no external API key required)"
13
+ "description": "Turns corrections into Preferences, Project-specific skills, and Shared skills — uses Claude Code, Codex, or OpenCode as the LLM backend (no external API key required)",
14
+ "version": "0.2.50"
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.49-green.svg" alt="Version">
16
+ <img src="https://img.shields.io/badge/version-0.2.50-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">
@@ -58,6 +58,8 @@
58
58
 
59
59
  ## Quick Start
60
60
 
61
+ Requires Node.js 20+ (for `npx`) and the host CLI you're installing into (`claude`, `codex`, or `opencode`) on `PATH`.
62
+
61
63
  ### Claude Code
62
64
 
63
65
  ```bash
@@ -66,27 +68,6 @@ npx claude-smart install
66
68
 
67
69
  Then restart Claude Code.
68
70
 
69
- Requires Node.js (for `npx`) to already exist.
70
-
71
- Alternatively, install via Claude Code's plugin marketplace:
72
-
73
- ```bash
74
- claude plugin marketplace add ReflexioAI/claude-smart
75
- claude plugin install claude-smart@reflexioai
76
- ```
77
-
78
- To uninstall:
79
-
80
- ```bash
81
- npx claude-smart uninstall
82
- ```
83
-
84
- Or, if installed via the plugin marketplace:
85
-
86
- ```bash
87
- claude plugin uninstall claude-smart@reflexioai
88
- ```
89
-
90
71
  ### Codex
91
72
 
92
73
  ```bash
@@ -95,39 +76,25 @@ npx claude-smart install --host codex
95
76
 
96
77
  Then fully quit and reopen Codex so hooks reload.
97
78
 
98
- Requires the `codex` CLI on `PATH` and Node.js (for `npx`).
99
-
100
- To uninstall:
101
-
102
- ```bash
103
- npx claude-smart uninstall --host codex
104
- ```
105
-
106
- Restart Codex after uninstalling. The uninstaller stops local claude-smart services and removes plugin/cache/config state; learned data under `~/.reflexio/` and `~/.claude-smart/` is preserved and shared with Claude Code, so you can switch between hosts without losing skills or preferences.
107
-
108
79
  ### OpenCode
109
80
 
110
81
  ```bash
111
82
  npx claude-smart install --host opencode
112
83
  ```
113
84
 
114
- Then restart OpenCode in your project so it loads the plugin from `opencode.json`, the documented project config file. If that project does not already have a root config but does have `.opencode/opencode.json` or `.opencode/opencode.jsonc`, the installer updates that existing file instead of creating a second config. If both locations exist, the root config wins. Use `--global` to install into `~/.config/opencode/opencode.json` for all OpenCode projects on this machine. The installer copies the active package to `~/.claude-smart/opencode/claude-smart`, prepares that runtime immediately, and writes a `file://` plugin entry so OpenCode reloads the same prepared package after restart.
115
-
116
- The installer prepares claude-smart's runtime dependencies, but expects the OpenCode CLI to already be installed. It resolves and records the OpenCode executable in `~/.claude-smart/.env` for later backend restarts; set `CLAUDE_SMART_OPENCODE_PATH` before install if `opencode` is not on `PATH`.
117
-
118
- OpenCode support is new and uses OpenCode's plugin loader to inject relevant learned context before each model request. Learning extraction runs `opencode run --pure` from an isolated temp project, so it uses OpenCode's default model unless you set `CLAUDE_SMART_OPENCODE_MODEL=provider/model`. Set that env var if your normal project config pins a different provider or model.
119
-
120
- On native Windows, install Git for Windows and make `bash.exe` available on `PATH` before starting OpenCode, or run OpenCode from WSL. The installer also checks the Windows local embedding runtime and writes actionable setup failures to `~/.claude-smart/install-failed`.
85
+ Then restart OpenCode in your project so it loads the plugin from `opencode.json`. Add `--global` to install for all OpenCode projects on this machine instead.
121
86
 
122
- To uninstall:
87
+ ### Uninstall
123
88
 
124
89
  ```bash
125
- npx claude-smart uninstall --host opencode
90
+ npx claude-smart uninstall # Claude Code
91
+ npx claude-smart uninstall --host codex # Codex
92
+ npx claude-smart uninstall --host opencode # OpenCode
126
93
  ```
127
94
 
128
- Restart OpenCode after uninstalling. The uninstaller removes the `claude-smart` entry from OpenCode's plugin list and deletes the copied OpenCode package; learned data under `~/.reflexio/` and `~/.claude-smart/` is preserved and shared across hosts.
95
+ Restart the host afterward. Learned data under `~/.reflexio/` and `~/.claude-smart/` is preserved and shared across hosts, so you can uninstall or switch hosts without losing skills or preferences.
129
96
 
130
- Developing the plugin itself? See [DEVELOPER.md](./DEVELOPER.md#developing-locally) for what the installer does, manual toggles via `/plugins`, and clone-based development.
97
+ For per-host details — what the installers and uninstallers touch, OpenCode config resolution and model/env overrides, and Windows notes — see [Host install notes](./DEVELOPER.md#host-install-notes) in DEVELOPER.md. Developing the plugin itself? See [Developing locally](./DEVELOPER.md#developing-locally).
131
98
 
132
99
  > **Not supported:** Claude Code Cowork, claude.ai/code web, or remote Codex environments without local plugin hooks — they run outside your local machine, so the local backend/dashboard and `~/.reflexio/` aren't reachable.
133
100
 
@@ -816,6 +816,66 @@ function fileSha256(path) {
816
816
  return crypto.createHash("sha256").update(readFileSync(path)).digest("hex");
817
817
  }
818
818
 
819
+ function claudeCodeCacheRoot() {
820
+ return join(homedir(), ".claude", "plugins", "cache", CODEX_MARKETPLACE_NAME, "claude-smart");
821
+ }
822
+
823
+ // Version of the plugin this npm package ships. The Claude Code cache is keyed
824
+ // by this version, and each version vendors a different Reflexio — so the
825
+ // vendor payload may only ever be compared against the cache dir for *this*
826
+ // version. Comparing against some other version's cache reports a spurious
827
+ // mismatch (its vendor legitimately differs) and would force a pointless
828
+ // reinstall.
829
+ function claudeCodePackageVersion(sourceRoot = join(PACKAGE_ROOT, "plugin")) {
830
+ try {
831
+ const manifest = JSON.parse(
832
+ readFileSync(join(sourceRoot, ".claude-plugin", "plugin.json"), "utf8"),
833
+ );
834
+ if (typeof manifest.version === "string" && manifest.version) return manifest.version;
835
+ } catch {
836
+ // Fall through to pyproject.toml.
837
+ }
838
+ try {
839
+ const match = readFileSync(join(sourceRoot, "pyproject.toml"), "utf8").match(
840
+ /^version\s*=\s*"([^"]+)"/m,
841
+ );
842
+ if (match) return match[1];
843
+ } catch {
844
+ // No readable version.
845
+ }
846
+ return null;
847
+ }
848
+
849
+ function claudeCodeVendorPayloadMismatch(
850
+ pluginRoot,
851
+ sourceRoot = join(PACKAGE_ROOT, "plugin"),
852
+ ) {
853
+ // A sentinel pair, not a full tree hash: it catches the failure this exists
854
+ // for (a cache with no vendor bundle at all, or one from a different build),
855
+ // not a vendor tree that is individually corrupt past these two files.
856
+ const vendorFiles = [
857
+ "vendor/reflexio/pyproject.toml",
858
+ "vendor/reflexio/reflexio/__init__.py",
859
+ ];
860
+ // A git checkout gitignores the generated vendor bundle, so a source tree
861
+ // without one is a dev install, not a broken cache. Nothing to compare.
862
+ const sourceHasVendor = vendorFiles.some((rel) =>
863
+ existsSync(join(sourceRoot, rel)),
864
+ );
865
+ if (!sourceHasVendor) return null;
866
+
867
+ for (const rel of vendorFiles) {
868
+ const source = join(sourceRoot, rel);
869
+ const cached = join(pluginRoot, rel);
870
+ if (!existsSync(source)) {
871
+ throw new Error(`installed claude-smart package is missing ${rel}`);
872
+ }
873
+ if (!existsSync(cached)) return rel;
874
+ if (fileSha256(source) !== fileSha256(cached)) return rel;
875
+ }
876
+ return null;
877
+ }
878
+
819
879
  function verifyOpenCodePluginPackage(packageRoot) {
820
880
  const sourceScript = join(PACKAGE_ROOT, "plugin", "scripts", "smart-install.sh");
821
881
  const copiedScript = join(packageRoot, "plugin", "scripts", "smart-install.sh");
@@ -951,6 +1011,48 @@ async function bootstrapClaudeCodeInstall() {
951
1011
  return pluginRoot;
952
1012
  }
953
1013
 
1014
+ async function refreshClaudeCodeCacheIfVendorMissing() {
1015
+ const version = claudeCodePackageVersion();
1016
+ if (!version) return null;
1017
+ // Only ever inspect and repair the cache dir for the version we are
1018
+ // installing. Other version dirs coexist in the cache and are not ours to
1019
+ // judge — `findClaudeCodePluginRoot()` returns the highest one, which is a
1020
+ // different plugin build whenever this package is a downgrade.
1021
+ const pluginRoot = join(claudeCodeCacheRoot(), version);
1022
+ if (!existsSync(pluginRoot)) return null;
1023
+
1024
+ const mismatch = claudeCodeVendorPayloadMismatch(pluginRoot);
1025
+ if (!mismatch) return pluginRoot;
1026
+
1027
+ process.stderr.write(
1028
+ `warning: cached claude-smart plugin is missing or has stale ${mismatch}; reinstalling from the bundled npm package.\n`,
1029
+ );
1030
+ let code = await runClaude(["plugin", "uninstall", PLUGIN_SPEC], {
1031
+ spinnerLabel: "Removing stale claude-smart cache…",
1032
+ });
1033
+ if (code !== 0) {
1034
+ throw new Error(`could not remove stale ${PLUGIN_SPEC} cache (exit ${code})`);
1035
+ }
1036
+ code = await runClaude(["plugin", "install", PLUGIN_SPEC], {
1037
+ spinnerLabel: "Refreshing claude-smart cache…",
1038
+ });
1039
+ if (code !== 0) {
1040
+ throw new Error(`could not reinstall ${PLUGIN_SPEC} from the bundled package (exit ${code})`);
1041
+ }
1042
+
1043
+ if (!existsSync(pluginRoot)) {
1044
+ throw new Error(`Claude Code did not recreate the claude-smart plugin cache at ${pluginRoot}`);
1045
+ }
1046
+ const remainingMismatch = claudeCodeVendorPayloadMismatch(pluginRoot);
1047
+ if (remainingMismatch) {
1048
+ throw new Error(
1049
+ `refreshed claude-smart cache still does not match the bundled npm package: ${remainingMismatch}`,
1050
+ );
1051
+ }
1052
+ process.stdout.write("Refreshed stale claude-smart plugin cache from the bundled npm package.\n");
1053
+ return pluginRoot;
1054
+ }
1055
+
954
1056
  function isWindows() {
955
1057
  return currentPlatform() === "win32";
956
1058
  }
@@ -2181,6 +2283,7 @@ async function runInstall(args, options = {}) {
2181
2283
  }
2182
2284
 
2183
2285
  try {
2286
+ await refreshClaudeCodeCacheIfVendorMissing();
2184
2287
  const pluginRoot = await bootstrapClaudeCodeInstall();
2185
2288
  restorePublishHooksFromSource(pluginRoot);
2186
2289
  if (readOnly) {
@@ -2492,6 +2595,8 @@ if (require.main === module) {
2492
2595
  module.exports = {
2493
2596
  assertSupportedRuntimePlatform,
2494
2597
  bootstrapPluginRuntime,
2598
+ claudeCodePackageVersion,
2599
+ claudeCodeVendorPayloadMismatch,
2495
2600
  codexMarketplacePluginRoot,
2496
2601
  copyCodexMarketplace,
2497
2602
  ensurePrivateNode,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-smart",
3
- "version": "0.2.49",
3
+ "version": "0.2.50",
4
4
  "description": "Self-improving Claude Code, Codex, and OpenCode plugin that turns corrections into Preferences, Project-specific skills, and Shared skills",
5
5
  "keywords": [
6
6
  "claude",
@@ -40,7 +40,8 @@
40
40
  },
41
41
  "scripts": {
42
42
  "build:opencode": "tsc -p plugin/opencode/tsconfig.json",
43
- "prepack": "npm run build:opencode"
43
+ "prepack": "npm run gen:marketplace && npm run build:opencode",
44
+ "gen:marketplace": "node scripts/generate-marketplace.js"
44
45
  },
45
46
  "bin": {
46
47
  "claude-smart": "bin/claude-smart.js"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-smart",
3
- "version": "0.2.49",
3
+ "version": "0.2.50",
4
4
  "description": "Self-improving Claude Code, Codex, and OpenCode plugin that turns corrections into Preferences, Project-specific skills, and Shared skills",
5
5
  "author": {
6
6
  "name": "Yi Lu"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-smart",
3
- "version": "0.2.49",
3
+ "version": "0.2.50",
4
4
  "description": "Self-improving coding assistant plugin — learns from corrections across sessions via reflexio",
5
5
  "author": {
6
6
  "name": "Yi Lu"
Binary file
package/plugin/README.md CHANGED
@@ -9,13 +9,14 @@ This directory is the Claude Code/Codex/OpenCode plugin payload shipped through
9
9
  ### Claude Code
10
10
 
11
11
  ```bash
12
- claude plugin marketplace add ReflexioAI/claude-smart
13
- claude plugin install claude-smart@reflexioai
12
+ npx claude-smart install
14
13
  ```
15
14
 
16
15
  The Setup hook bootstraps `uv`, Python 3.12, and a private Node.js/npm runtime
17
16
  under `~/.claude-smart/` when they are missing. If Node.js is already installed,
18
- `npx claude-smart install` is equivalent.
17
+ the npm installer also supplies the generated Reflexio runtime bundle that is
18
+ not committed to the GitHub repository. Installing this repository directly as
19
+ a Claude Code marketplace is therefore unsupported.
19
20
 
20
21
  Add `--read-only` to either install command to skip hooks that publish
21
22
  interactions for learning.
@@ -4,7 +4,9 @@ import { readConfig } from "@/lib/config-file";
4
4
 
5
5
  export const dynamic = "force-dynamic";
6
6
 
7
- const DEFAULT_URL = "http://localhost:8071";
7
+ function defaultUrl(): string {
8
+ return `http://localhost:${process.env.BACKEND_PORT || "8071"}`;
9
+ }
8
10
 
9
11
  async function reflexioConfig(): Promise<{ base: string; apiKey: string }> {
10
12
  const config = await readConfig();
@@ -13,7 +15,7 @@ async function reflexioConfig(): Promise<{ base: string; apiKey: string }> {
13
15
  const fromConfig = originOnly(config.REFLEXIO_URL ?? "");
14
16
  const configuredBase = fromEnv ?? fromConfig;
15
17
  return {
16
- base: configuredBase ?? DEFAULT_URL,
18
+ base: configuredBase ?? defaultUrl(),
17
19
  apiKey: configuredBase ? apiKey : "",
18
20
  };
19
21
  }
@@ -11,6 +11,7 @@ import {
11
11
  Users,
12
12
  } from "lucide-react";
13
13
  import { LearningsBadge } from "@/components/common/learnings-badge";
14
+ import { HostBadge } from "@/components/common/host-badge";
14
15
  import { PageHeader } from "@/components/common/page-header";
15
16
  import { StatCard } from "@/components/common/stat-card";
16
17
  import { EmptyState } from "@/components/common/empty-state";
@@ -217,7 +218,11 @@ export default function DashboardPage() {
217
218
  <code className="font-mono text-xs truncate">
218
219
  {truncateId(s.session_id, 10, 6)}
219
220
  </code>
220
- <LearningsBadge count={s.learning_interaction_count} />
221
+ <LearningsBadge
222
+ count={s.learning_interaction_count}
223
+ size="sm"
224
+ />
225
+ <HostBadge host={s.host} size="sm" />
221
226
  </div>
222
227
  <div className="flex w-full items-center justify-end gap-4 text-xs text-muted-foreground sm:w-auto sm:shrink-0">
223
228
  <span>{s.turn_count} turns</span>
@@ -21,6 +21,7 @@ import {
21
21
  FolderGit2,
22
22
  } from "lucide-react";
23
23
  import { PageHeader } from "@/components/common/page-header";
24
+ import { LearningHostProvenance } from "@/components/common/host-badge";
24
25
  import { EmptyState } from "@/components/common/empty-state";
25
26
  import { DeleteLearningDangerZone } from "@/components/common/delete-learning-danger-zone";
26
27
  import { Button } from "@/components/ui/button";
@@ -29,6 +30,7 @@ import { Badge } from "@/components/ui/badge";
29
30
  import { Separator } from "@/components/ui/separator";
30
31
  import { reflexio } from "@/lib/reflexio-client";
31
32
  import { formatTimestamp, truncateId } from "@/lib/format";
33
+ import { useRequestHostAttribution } from "@/lib/host-attribution";
32
34
  import { cn } from "@/lib/utils";
33
35
  import { statusLabel as status } from "@/lib/status";
34
36
  import type { UserProfile } from "@/lib/types";
@@ -49,6 +51,7 @@ export default function PreferenceDetailPage({
49
51
  const [deleting, setDeleting] = useState(false);
50
52
  const [editing, setEditing] = useState(false);
51
53
  const [content, setContent] = useState("");
54
+ const attribution = useRequestHostAttribution();
52
55
 
53
56
  useEffect(() => {
54
57
  let cancelled = false;
@@ -205,11 +208,6 @@ export default function PreferenceDetailPage({
205
208
  <FolderGit2 className="h-3 w-3" />
206
209
  {truncateId(profile.user_id, 32, 8)}
207
210
  </Badge>
208
- {profile.source && (
209
- <Badge variant="secondary" className="font-mono text-[10px]">
210
- {profile.source}
211
- </Badge>
212
- )}
213
211
  {dirty && (
214
212
  <Badge variant="destructive" className="gap-1.5">
215
213
  unsaved changes
@@ -343,6 +341,20 @@ export default function PreferenceDetailPage({
343
341
  display={truncateId(profile.generated_from_request_id, 8, 4)}
344
342
  />
345
343
  )}
344
+ {attribution && profile.generated_from_request_id && (
345
+ <Meta
346
+ label="Origin"
347
+ value={
348
+ <LearningHostProvenance
349
+ attribution={attribution}
350
+ requestId={profile.generated_from_request_id}
351
+ />
352
+ }
353
+ />
354
+ )}
355
+ {profile.source && (
356
+ <Meta label="Integration" value={profile.source} mono />
357
+ )}
346
358
  </dl>
347
359
  </div>
348
360
  </aside>
@@ -428,7 +440,7 @@ function Meta({
428
440
  }: {
429
441
  icon?: React.ComponentType<{ className?: string }>;
430
442
  label: string;
431
- value: string;
443
+ value: React.ReactNode;
432
444
  mono?: boolean;
433
445
  }) {
434
446
  return (
@@ -2,8 +2,10 @@
2
2
 
3
3
  import { useEffect, useMemo, useState } from "react";
4
4
  import Link from "next/link";
5
- import { Users, ChevronRight } from "lucide-react";
5
+ import { AlertTriangle, Users, ChevronRight } from "lucide-react";
6
6
  import { PageHeader } from "@/components/common/page-header";
7
+ import { HostBadge } from "@/components/common/host-badge";
8
+ import { LearningApplicationBadge } from "@/components/common/learning-application-badge";
7
9
  import { EmptyState } from "@/components/common/empty-state";
8
10
  import { DeleteAllButton } from "@/components/common/delete-all-button";
9
11
  import { Badge } from "@/components/ui/badge";
@@ -17,6 +19,7 @@ import {
17
19
  } from "@/components/ui/select";
18
20
  import { reflexio } from "@/lib/reflexio-client";
19
21
  import { formatRelative, truncateId } from "@/lib/format";
22
+ import { useRequestHostAttribution } from "@/lib/host-attribution";
20
23
  import type { PlaybookApplicationStat, UserProfile } from "@/lib/types";
21
24
 
22
25
  type PreferenceSort = "newest" | "applied";
@@ -33,6 +36,7 @@ export default function PreferencesPage() {
33
36
  const [error, setError] = useState<string | null>(null);
34
37
  const [sortBy, setSortBy] = useState<PreferenceSort>("newest");
35
38
  const [filter, setFilter] = useState("");
39
+ const attribution = useRequestHostAttribution();
36
40
 
37
41
  useEffect(() => {
38
42
  let cancelled = false;
@@ -141,6 +145,13 @@ export default function PreferencesPage() {
141
145
  {error}. Is reflexio running on the configured backend URL?
142
146
  </div>
143
147
  )}
148
+ {attribution?.unavailable && !error && (
149
+ <div className="mb-4 flex items-center gap-2 rounded-md border border-amber-500/30 bg-amber-500/10 px-3 py-2 text-sm text-amber-800 dark:text-amber-200">
150
+ <AlertTriangle className="h-4 w-4 shrink-0" />
151
+ Preference source details are temporarily unavailable. Preferences
152
+ remain available.
153
+ </div>
154
+ )}
144
155
 
145
156
  {profiles === null && !error ? (
146
157
  <div className="text-sm text-muted-foreground">Loading…</div>
@@ -162,6 +173,7 @@ export default function PreferencesPage() {
162
173
  >
163
174
  <header className="mb-2 flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
164
175
  <div className="flex min-w-0 flex-wrap items-center gap-2">
176
+ <LearningApplicationBadge stat={stat} />
165
177
  <Badge variant="outline" className="h-5 font-mono text-[10px]">
166
178
  {truncateId(p.user_id, 32, 8)}
167
179
  </Badge>
@@ -170,7 +182,6 @@ export default function PreferencesPage() {
170
182
  {p.status}
171
183
  </Badge>
172
184
  )}
173
- <PreferenceApplicationStatBadge stat={stat} />
174
185
  </div>
175
186
  <div className="flex items-center gap-1.5 shrink-0">
176
187
  <span className="text-[11px] text-muted-foreground">
@@ -182,11 +193,25 @@ export default function PreferencesPage() {
182
193
  <p className="max-w-5xl text-sm leading-relaxed line-clamp-3">
183
194
  {p.content}
184
195
  </p>
185
- {p.source && (
186
- <p className="text-[11px] text-muted-foreground mt-2 font-mono">
187
- source: {p.source}
188
- </p>
189
- )}
196
+ <div className="mt-2 flex flex-wrap items-center gap-3">
197
+ {attribution &&
198
+ !attribution.unavailable &&
199
+ p.generated_from_request_id && (
200
+ <HostBadge
201
+ host={
202
+ attribution.hosts.get(
203
+ p.generated_from_request_id,
204
+ ) ?? null
205
+ }
206
+ display="provenance"
207
+ />
208
+ )}
209
+ {p.source && (
210
+ <span className="text-[11px] text-muted-foreground font-mono">
211
+ Integration: {p.source}
212
+ </span>
213
+ )}
214
+ </div>
190
215
  </Link>
191
216
  );
192
217
  })}
@@ -196,31 +221,3 @@ export default function PreferencesPage() {
196
221
  </div>
197
222
  );
198
223
  }
199
-
200
- function PreferenceApplicationStatBadge({
201
- stat,
202
- }: {
203
- stat: PlaybookApplicationStat | undefined;
204
- }) {
205
- if (!stat || stat.applied_count === 0) {
206
- return (
207
- <Badge
208
- variant="outline"
209
- className="h-5 text-[10px] text-muted-foreground"
210
- title="No citations recorded yet for this preference. It will count once an assistant reply cites it."
211
- >
212
- Never applied
213
- </Badge>
214
- );
215
- }
216
- const last = formatRelative(stat.last_applied_at);
217
- return (
218
- <Badge
219
- variant="secondary"
220
- className="h-5 text-[10px]"
221
- title={`Last applied ${last}`}
222
- >
223
- Applied {stat.applied_count}×{stat.last_applied_at ? ` · ${last}` : ""}
224
- </Badge>
225
- );
226
- }
@@ -16,6 +16,8 @@ import {
16
16
  Sparkles,
17
17
  } from "lucide-react";
18
18
  import { PageHeader } from "@/components/common/page-header";
19
+ import { HostBadge } from "@/components/common/host-badge";
20
+ import { LearningsBadge } from "@/components/common/learnings-badge";
19
21
  import { EmptyState } from "@/components/common/empty-state";
20
22
  import { Badge } from "@/components/ui/badge";
21
23
  import { Button } from "@/components/ui/button";
@@ -75,7 +77,20 @@ export default function InteractionDetailPage({
75
77
  <div className="flex-1 overflow-auto">
76
78
  <PageHeader
77
79
  title="Session transcript"
78
- description={sessionId}
80
+ description={
81
+ <span className="flex flex-wrap items-center gap-2">
82
+ <span>{sessionId}</span>
83
+ {detail && (
84
+ <>
85
+ <LearningsBadge
86
+ count={detail.learning_interaction_count}
87
+ size="sm"
88
+ />
89
+ <HostBadge host={detail.host} size="sm" />
90
+ </>
91
+ )}
92
+ </span>
93
+ }
79
94
  actions={
80
95
  <div className="flex items-center gap-2">
81
96
  <Link href="/sessions">
@@ -7,6 +7,7 @@ import { PageHeader } from "@/components/common/page-header";
7
7
  import { EmptyState } from "@/components/common/empty-state";
8
8
  import { DeleteAllButton } from "@/components/common/delete-all-button";
9
9
  import { LearningsBadge } from "@/components/common/learnings-badge";
10
+ import { HostBadge } from "@/components/common/host-badge";
10
11
  import { Input } from "@/components/ui/input";
11
12
  import { dayBucket, formatRelative, truncateId } from "@/lib/format";
12
13
  import type { SessionSummary } from "@/lib/types";
@@ -153,6 +154,7 @@ export default function SessionsPage() {
153
154
  count={s.learning_interaction_count}
154
155
  size="sm"
155
156
  />
157
+ <HostBadge host={s.host} size="sm" />
156
158
  </div>
157
159
  <div className="flex items-center gap-3 text-[11px] text-muted-foreground mt-0.5">
158
160
  <code className="font-mono">