Internal notes for maintainers of claude-smart. End-user install instructions live in README.md; this file covers the release loop.
| Path | Purpose |
|---|---|
plugin/ |
Claude Code plugin — hooks, slash commands, install script, and the Python package |
plugin/src/claude_smart/ |
Python package — hook handler, CLI, reflexio adapter |
plugin/pyproject.toml |
Python manifest — shipped to PyPI via uv build --project plugin |
tests/ |
Pytest suite for the Python package (run via uv run --project plugin pytest tests/ -q from repo root) |
bin/claude-smart.js |
Node wrapper so npx claude-smart install works |
package.json |
npm manifest — ships bin/, marketplace metadata, plugin/, README.md, and LICENSE |
plugin/.claude-plugin/plugin.json |
Plugin metadata read by Claude Code |
.claude-plugin/marketplace.json |
Marketplace entry — claude plugin marketplace add reads this |
reflexio.lock.json |
Reflexio provenance — records the PyPI baseline or generated vendor bundle commit |
plugin/vendor/reflexio/ |
Generated release-only Reflexio bundle for npm artifacts; gitignored, not committed |
plugin/dashboard/ |
Next.js management UI for interactions, preferences, skills, configuration |
Makefile |
Release automation |
Tunables read by the plugin at runtime. Most users don't need to touch these — the installer writes the local-provider flags to ~/.reflexio/.env and sensible defaults cover the rest. Hook-side variables must be set in the Claude Code process environment, such as a project .claude/settings.local.json env block or user-scoped ~/.claude/settings.json, because hooks do not read Reflexio's backend .env file.
| Variable | Default | Purpose |
|---|---|---|
CLAUDE_SMART_USE_LOCAL_CLI |
0 (installer sets 1) |
Route generation through the active host CLI (claude for Claude Code, codex for Codex). Written to ~/.reflexio/.env by claude-smart install. |
CLAUDE_SMART_USE_LOCAL_EMBEDDING |
0 (installer sets 1) |
Route embeddings to the shared local embedding daemon on localhost:8072. Written to ~/.reflexio/.env by claude-smart install. |
REFLEXIO_EMBEDDING_PROVIDER |
local_service when local embedding is enabled |
Embedding provider mode: cloud, local_service, internal_service, inprocess, or off. |
REFLEXIO_EMBEDDING_SERVICE_URL |
http://127.0.0.1:$EMBEDDING_PORT |
OpenAI-compatible embedding endpoint used by local_service and internal_service. |
EMBEDDING_PORT |
8072 |
Local embedding daemon port. Shared by Claude Code and Codex on the same machine. |
REFLEXIO_RERANK_DEVICE |
cpu |
Torch device for the in-process cross-encoder reranker used by search. Defaults to CPU: auto-selected MPS on Apple Silicon accumulates gigabytes of GPU memory in the backend process for no latency win at this model size. Set to cuda only for GPU deployments. |
CLAUDE_SMART_ENABLE_OPTIMIZER |
auto: local Reflexio only | Hook-side env var that controls shared skill optimization and rollups during SessionStart. Set to 1 to force optimizer defaults for hosted Reflexio, or 0 to disable everywhere. Set it in Claude Code settings, not ~/.reflexio/.env. |
CLAUDE_SMART_CITATIONS |
on |
Controls the final ✨ claude-smart rule applied marker instruction. on (default) injects a compact instruction asking the assistant to cite only memories that materially changed the answer. off skips injection of the citation instruction entirely and strips any stray marker line from the assistant text before publishing. Legacy values auto and marker-only are accepted as enabled aliases. Set in Claude Code settings (hook env), not ~/.reflexio/.env. |
CLAUDE_SMART_CITATION_LINK_STYLE |
markdown |
Controls the visible links in the final ✨ claude-smart rule applied marker. markdown asks the model to emit [human title](dashboard URL) links and is the Codex default because Codex renders markdown links in assistant messages. osc8 asks it to emit terminal-native OSC 8 hyperlinks so the human title can be clickable without showing the URL; unsupported terminals should fall back to markdown links. |
CLAUDE_SMART_DASHBOARD_URL |
http://localhost:3001 |
Base URL used in injected citation targets and the final ✨ claude-smart rule applied marker links. Override only if the local dashboard is exposed on another host or port. Model-facing citation links use short /rules/{citationId} resolver URLs; canonical direct links remain /skills/project/{id}, /skills/shared/{id}, and /preferences/project/{id}. /preferences/{id} remains a compatibility alias. |
CLAUDE_SMART_CLI_PATH |
claude on Claude Code; Codex compatibility wrapper or codex on Codex |
Override the executable Reflexio calls for local generation. |
CLAUDE_SMART_STATE_DIR |
~/.claude-smart/sessions/ |
Where the per-session JSONL buffer lives. |
CLAUDE_SMART_NODE_LTS_MAJOR |
22 |
Major version used when the installer downloads private Node.js/npm into ~/.claude-smart/node/current. |
CLAUDE_SMART_NODE_BASE_URL |
https://nodejs.org/dist/latest-v${CLAUDE_SMART_NODE_LTS_MAJOR}.x |
Override the Node.js download base URL for tests, mirrors, or recovery. |
CLAUDE_SMART_BACKEND_AUTOSTART |
1 |
Set to 0 to stop the SessionStart hook from spawning the reflexio backend on localhost:8071. |
CLAUDE_SMART_DASHBOARD_AUTOSTART |
1 |
Set to 0 to stop the SessionStart hook from spawning the Next.js dashboard on localhost:3001. |
CLAUDE_SMART_BACKEND_STOP_ON_END |
0 |
Set to 1 to tear down the backend at SessionEnd instead of leaving it long-lived. |
REFLEXIO_URL |
http://localhost:8071/ |
Point the plugin at a non-local reflexio backend. |
claude-smart uses one shared local embedding daemon by default. The backend starts reflexio embeddings serve on 127.0.0.1:8072 when CLAUDE_SMART_USE_LOCAL_EMBEDDING=1, then every backend worker and host process calls its OpenAI-compatible /v1/embeddings endpoint instead of loading a model in each process.
Supported local models are local/nomic-embed-v1.5, local/nomic-embed-text-v1.5, and local/minilm-l6-v2. The daemon owns exactly one model for its lifetime; start a second daemon on another port if you need to serve a different model concurrently.
If you still want to use a cloud embedding provider (OpenAI, Gemini, etc.), omit CLAUDE_SMART_USE_LOCAL_EMBEDDING and set the corresponding API key in ~/.reflexio/.env — reflexio will fall back to its standard provider-priority chain. For hosted Reflexio, prefer a managed embedding provider or a scalable REFLEXIO_EMBEDDING_PROVIDER=internal_service endpoint instead of a single-machine daemon.
Vanilla install support means the user has installed the host (claude or
codex) and the chosen installer runner (npx needs Node, uvx needs uv), but
does not need global Python, uv, Node/npm for plugin runtime, npm packages, or
embedding model files. The plugin bootstraps runtime dependencies into user
state:
| Dependency | Installed/managed by | Location |
|---|---|---|
| Python 3.12 env and Python packages | uv sync --locked --python 3.12 |
plugin .venv |
| Runtime uv | installer if missing | ~/.local/bin or ~/.cargo/bin |
| Runtime Node.js/npm | installer if missing | ~/.claude-smart/node/current |
| Dashboard packages/build | installer or first dashboard start | plugin/dashboard/node_modules, plugin/dashboard/.next |
| Local embedding model | Chroma/ONNX on first use | ~/.cache/chroma/onnx_models/all-MiniLM-L6-v2/ |
Supported native vanilla targets are Apple Silicon macOS 14+ and Windows x64; Linux remains supported when host hooks run locally and wheels are available. Intel Mac, macOS 13 or older, and Windows ARM should fail before dependency sync with a visible unsupported-platform message because the current ML dependency stack does not publish a complete native wheel set for those targets.
On native Windows, Claude Code hooks still need a Git Bash-compatible bash
until Claude Code exposes a single cross-platform hook command shape.
When changing dependencies, verify:
uv sync --project plugin --locked --dry-run --python 3.12 --python-platform x86_64-pc-windows-msvc
uv sync --project plugin --locked --dry-run --python 3.12
cd plugin/dashboard && npm ci && npm run buildAlso check the published package surface:
npm pack --dry-run --jsonUser-facing surfaces should use these names:
- Preference — project-scoped facts or preferences ("prefers anyio over asyncio"). Free-form bullets.
- Project-specific skill — project-scoped rules with trigger and rationale ("when writing a script, use pathlib because os.path is error-prone").
- Shared skill — cross-project rollups of project-specific skills. Shared skills can be auto generated, persisted, or rejected.
Reflexio still exposes legacy storage/API names, and claude-smart keeps those identifiers in code because they are wire-format names:
| Public term | Reflexio storage/API term |
|---|---|
| Preference | user_profiles, profile_id, UserProfile |
| Project-specific skill | user_playbooks, user_playbook_id, UserPlaybook |
| Shared skill | agent_playbooks, agent_playbook_id, AgentPlaybook |
Do not use the storage/API terms in README copy, dashboard labels, slash-command descriptions, or injected context unless documenting raw Reflexio fields for maintainers.
A standalone Next.js app at plugin/dashboard/ that gives a visual view of what
claude-smart has learned:
- Interactions — session list + transcript reader backed by
~/.claude-smart/sessions/*.jsonl(read server-side inplugin/dashboard/app/api/sessions/route.ts). - Preferences / Skills — reflexio data fetched via a proxy route
(
plugin/dashboard/app/api/reflexio/[...path]/route.ts) that forwards to the URL configured in the top bar; defaults tohttp://localhost:8071. - Configure — reads and writes backend/provider settings in
~/.reflexio/.envand hook-side Claude Code settings in.claude/settings.local.json. Unknown.envkeys (API secrets, user additions) are preserved on write and never returned to the browser.
Stack mirrors reflexio/docs/ exactly (Next.js 16, Tailwind v4, shadcn
base-nova, Base UI primitives, Lucide icons, next-themes). Runs on port
3001 so it can coexist with reflexio's docs site on 3000.
cd plugin/dashboard
npm install
npm run dev # http://localhost:3001
npm run build
npm run lintNext.js 16 caveat — the same one reflexio/docs flags: APIs and
conventions differ from anything pre-16. Before touching route handlers or
dynamic-param types, consult plugin/dashboard/node_modules/next/dist/docs/.
claude-smart uses semver. Four files carry the version and must stay in lockstep — if they drift, the npm wrapper, PyPI wheel, plugin metadata, and marketplace entry can claim different versions, and users see confusing mismatches in claude plugin list vs. npm view claude-smart version.
| File | Field |
|---|---|
package.json |
.version |
plugin/pyproject.toml |
project.version |
plugin/.claude-plugin/plugin.json |
.version |
.claude-plugin/marketplace.json |
.plugins[0].version |
Don't edit these by hand — use make bump.
- npm:
npm login(writes a token to~/.npmrc). - PyPI: create a project-scoped token at https://pypi.org/manage/account/token/ and export it:
Persist it in your shell rc if you release frequently.
export UV_PUBLISH_TOKEN=pypi-AgEN... - git: push access to
originfor the tag push at the end ofmake release.
# 1. Make sure you're on a clean main (no staged/unstaged edits).
git checkout main
git pull --ff-only
# 2. Dry-run to confirm what will ship (optional but cheap).
make publish-dry
# → npm: tarball includes bin/, .agents/plugins/marketplace.json, plugin/, LICENSE, README.md, package.json
# → PyPI: dist/ contains a wheel + sdist
# 3. One-shot release. Bumps, commits, tags v$VERSION, publishes to both
# registries, pushes the commit and tag.
make release VERSION=0.1.1make release runs these steps in order and aborts on the first failure:
check-version— regex-validatesVERSIONas semver.check-clean— refuses to run with a dirty working tree.bump— rewrites the four version strings.git add+git commit -m "Release v$VERSION".git tag -a v$VERSION.publish-npm→npm publish --access public.publish-pypi→rm -rf dist/ && uv build && uv publish.git push --follow-tags.
For a vendor-mode Reflexio release, run make release-npm VERSION=... after
bash scripts/release-with-reflexio.sh. The generated vendor bundle is only in
the npm tarball; make release intentionally refuses to publish PyPI while
reflexio.lock.json says source=vendor.
Because publish happens before the push, a registry failure (e.g. auth expired) leaves the commit and tag local — you can fix the cause and re-run make publish && git push --follow-tags without re-bumping.
# Just bump the version. Useful when you want to edit CHANGELOG, amend the
# commit message, or split the bump across branches.
make bump VERSION=0.1.1
git diff # inspect
git commit -am "Release v0.1.1"
git tag -a v0.1.1 -m "Release v0.1.1"
make publish # both registries
git push --follow-tags
# Only one registry (e.g. if the other published successfully and you're retrying).
make publish-npm
make publish-pypi
# Vendor-mode Reflexio release (npm only).
make release-npm VERSION=0.1.1
# Preview without uploading anything.
make publish-drymake help prints the full target list.
Once published, end users update to the latest version with either:
npx claude-smart update # or: uvx claude-smart updateBoth wrap claude plugin update claude-smart@reflexioai. Users restart Claude Code to apply. Codex users rerun npx claude-smart install --host codex, then restart Codex after /plugins has upgraded the installed plugin.
Before running make release:
-
README.mdreflects any user-visible changes. - Hook behavior, CLI flags, env vars are unchanged — or a migration note is in the README.
-
plugin/hooks/hooks.jsonandplugin/commands/*.mdrender correctly in a local Claude Code session (smoke test: install from a localdirectorymarketplace, start a session, run/show). - If claude-smart needs new Reflexio behavior, choose a Reflexio release mode:
- PyPI mode:
reflexio-aiis published, thenREFLEXIO_RELEASE_SOURCE=pypi bash scripts/release-with-reflexio.shupdatesplugin/pyproject.toml,plugin/uv.lock, andreflexio.lock.json. - Vendor mode:
bash scripts/release-with-reflexio.shgeneratesplugin/vendor/reflexioand updatesreflexio.lock.json; keep the generated vendor directory in place until npm publish completes.
- PyPI mode:
-
python scripts/check-reflexio-lock.pypasses. -
npm pack --dry-run --jsonincludes the root wrapper, marketplace metadata, plugin payload, dashboard sources, README, and LICENSE, and excludes.venv,node_modules,.next/cache, and Python caches. - If you touched
pyproject.tomldependencies,uv buildsucceeds locally and the wheel'sMETADATAdoes not carry any local path dependency.
npm publish fails with 403 Forbidden.
Usually means the version already exists. Check npm view claude-smart versions. npm does not allow re-uploading the same version — bump and re-release.
uv publish fails with 400 File already exists.
Same as above for PyPI. Bump and re-release.
uv build produces a wheel that fails to install with No matching distribution found for reflexio-ai.
The committed plugin/pyproject.toml must resolve reflexio-ai from PyPI. If this release only needs Reflexio through the npm plugin artifact, use vendor mode instead of raising the PyPI dependency to an unpublished Reflexio version:
bash scripts/release-with-reflexio.shIf this release needs the PyPI package dependency itself to require a newer published Reflexio version, publish that Reflexio version first, then run:
REFLEXIO_RELEASE_SOURCE=pypi bash scripts/release-with-reflexio.shFor non-standard worktree layouts, point the helper at the intended Reflexio checkout:
REFLEXIO_PATH=/path/to/reflexio-main bash scripts/release-with-reflexio.shVendor mode verifies that the Reflexio checkout is clean, exports the committed HEAD into plugin/vendor/reflexio, and records the commit in reflexio.lock.json. PyPI mode additionally verifies that the Reflexio checkout matches origin/main, is tagged v<version>, and that reflexio-ai==version exists on PyPI.
Manual equivalent:
python scripts/sync-reflexio-dep.py --write --check-pypi --release-checks
uv lock --project plugin --upgrade-package reflexio-ai
python scripts/check-reflexio-lock.pymake release committed and tagged but npm publish failed after.
publish runs before git push --follow-tags, so nothing was pushed. Fix the cause (auth, network, registry), then:
make publish # retry both, or publish-npm / publish-pypi individually
git push --follow-tagsIf you need to unwind the local commit and tag:
git tag -d v$VERSION
git reset --hard HEAD~1make bump left .bak files behind.
A sed failure mid-run can leave *.bak siblings. Safe to delete:
rm -f package.json.bak pyproject.toml.bak .claude-plugin/*.bakThere is one local test path: build the npm tarball from this checkout and install it. The tarball bundles the plugin (and, in vendor mode, a Reflexio source snapshot), so installing it exercises your local plugin code end-to-end on both hosts.
make package
# → prints the absolute path of the resulting claude-smart-<version>.tgzInstall the tarball globally and run the host installer:
npm install -g /abs/path/claude-smart-<version>.tgz # path or ./prefix, not a bare name
claude-smart install # Claude Code
claude-smart install --host codex # Codexnpm decides the source from the shape of the argument: a filesystem path (absolute, or relative with a ./ prefix) is installed as a local tarball, while a bare claude-smart-<version>.tgz is treated as a registry package name and fails. Always pass a path.
Restart the host. claude-smart install (Claude Code) registers the bundled package root as a local marketplace and runs claude plugin install claude-smart@reflexioai; the Codex variant copies the bundled plugin into Codex's marketplace cache. Either way, your local plugin changes are what gets loaded.
To iterate: edit plugin code, rerun make package, reinstall the tarball, rerun claude-smart install, restart the host.
During active development you'll often rebuild the tarball without bumping the version. A plain npm install -g can then be a no-op, because npm sees the version already satisfied. Force the overwrite, or uninstall first:
npm install -g --force /abs/path/claude-smart-<version>.tgz # force overwrite
# or, most predictable:
npm uninstall -g claude-smart && npm install -g /abs/path/claude-smart-<version>.tgzReinstalling the npm package only refreshes the claude-smart CLI wrapper — the plugin payload the host loads is whatever claude-smart install last copied in, so always re-run claude-smart install and restart the host afterward. If a same-version change still doesn't take effect (the host may dedupe the plugin by version), force a clean re-ingest:
claude plugin uninstall claude-smart@reflexioai # Claude Code
claude-smart installDo not use claude-smart update for this loop — it wraps claude plugin update claude-smart@reflexioai, the end-user path for pulling a newer published release from the marketplace. It is version-driven (a same-version rebuild has nothing to update to) and updates from the registered source rather than re-ingesting a tarball you just built. Bumping the patch version with make bump sidesteps all version-dedup ambiguity if you'd rather not use --force.
For Reflexio backend changes, edit open_source/reflexio/ and either:
- Publish
reflexio-aito PyPI and bumpplugin/pyproject.toml, or - Use vendor mode (see Release flow with Reflexio below) —
make packagethen bundles the local Reflexio source intoplugin/vendor/reflexioso the tarball install picks it up.
What's picked up after each reinstall + host restart:
plugin/src/claude_smart/,plugin/commands/*.md,plugin/hooks/,plugin/dashboard/— all from the freshly installed tarball. There is no editable code path; the running plugin is always whatever the latest installed tarball contains.
If claude-smart does not need new Reflexio behavior, release claude-smart only; do not change the reflexio-ai dependency.
If claude-smart needs unpublished Reflexio behavior, use vendor mode. The committed package metadata still points at the PyPI baseline, but the npm artifact carries the exact Reflexio source snapshot:
bash scripts/release-with-reflexio.shFor non-standard worktree layouts:
REFLEXIO_PATH=/path/to/reflexio-main bash scripts/release-with-reflexio.shCommit:
reflexio.lock.json
Keep generated plugin/vendor/reflexio in place until npm publish completes; it is gitignored but included in the npm tarball. Then release claude-smart to npm:
make release-npm VERSION=<new-claude-smart-version>If claude-smart needs a newer published Reflexio package dependency, merge Reflexio, tag the release commit as v<version>, publish reflexio-ai, then run:
REFLEXIO_RELEASE_SOURCE=pypi bash scripts/release-with-reflexio.shCommit:
plugin/pyproject.toml
plugin/uv.lock
reflexio.lock.json
Then release claude-smart.
Inside Claude Code:
/show
On a fresh project: _No skills or preferences yet for project <name>._. Have a conversation, correct Claude on something (e.g. "no, don't use X — use Y"), then run /learn — this flags the previous turn as a correction and forces extraction immediately. After ~20–30 seconds, /show surfaces the new skill or preference.
To check which Reflexio source is loaded (PyPI vs vendored bundle):
uv run --project plugin --no-sync python -c "import reflexio, os; print(reflexio.__version__); print(os.path.dirname(reflexio.__file__))"uv run --project plugin pytest --rootdir . -o addopts= tests/ -q # claude-smart package tests (from repo root)
cd ../reflexio && uv run pytest tests/server/llm/ -q -o 'addopts=' # reflexio patch testsRun both locally before make release. There's no CI gate today — the release flow trusts the maintainer.
Useful for debugging without a live Claude Code session:
echo '{"session_id":"dev-1","source":"startup","cwd":"'"$PWD"'"}' \
| uv run --project plugin --no-sync python -m claude_smart.hook session-startThese exercise the credit-stall detection path end-to-end against real reflexio state.
sqlite3 ~/.reflexio/data/reflexio.db <<'SQL'
UPDATE stall_state SET stalled=1, reason='billing_error',
stalled_at=datetime('now'), notified_in_cc=0,
error_message='manual smoke test';
SQLOpen http://localhost:3001 — the banner should appear. Start a new Claude Code session — the SessionStart banner should appear once. Start a second session immediately — no banner (notified_in_cc was flipped).
Clear:
sqlite3 ~/.reflexio/data/reflexio.db "UPDATE stall_state SET stalled=0, reason=NULL, notified_in_cc=0 WHERE id=1;"claude /logout, then trigger any reflexio extraction (e.g. /learn). Expect reason='auth_error', then a /login banner on the next SessionStart.
Reproducible only against an exhausted Agent SDK credit pool. Capture the stream-json output as a fixture under reflexio/tests/server/llm/fixtures/ so the integration test stays current as Anthropic's error format evolves.