Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 12 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
# - git (for the release flow)

.PHONY: help bump release publish publish-npm publish-pypi publish-dry \
check-version check-clean ensure-remote-reflexio unskip-worktree
check-version check-clean check-npm-auth ensure-remote-reflexio unskip-worktree

VERSION_FILES := package.json plugin/pyproject.toml \
plugin/.claude-plugin/plugin.json .claude-plugin/marketplace.json \
Expand All @@ -36,6 +36,16 @@ check-clean:
@git diff --quiet && git diff --cached --quiet \
|| { echo "error: working tree is dirty — commit or stash first" >&2; exit 1; }

check-npm-auth: ## Verify npm auth via NPM_TOKEN or `npm whoami`; fail if neither is available
@if [ -n "$$NPM_TOKEN" ]; then \
echo "→ npm: NPM_TOKEN is set"; \
elif npm whoami >/dev/null 2>&1; then \
echo "→ npm: logged in as $$(npm whoami)"; \
else \
echo "error: not authenticated via npm whoami and NPM_TOKEN is not set; set NPM_TOKEN for CI or run npm login locally" >&2; \
exit 1; \
fi

unskip-worktree: ## Clear skip-worktree on plugin/pyproject.toml and plugin/uv.lock so release edits land in git
@echo "→ clearing skip-worktree on $(PYPROJECT) $(LOCK_FILES)"
@git update-index --no-skip-worktree $(PYPROJECT) $(LOCK_FILES) 2>/dev/null || true
Expand Down Expand Up @@ -87,7 +97,7 @@ publish-dry: unskip-worktree ensure-remote-reflexio ## Show what would be publis

publish: publish-npm publish-pypi ## Publish to both npm and PyPI

release: check-version check-clean bump ## Bump + commit + tag + publish + push
release: check-version check-clean check-npm-auth bump ## Bump + commit + tag + publish + push
@echo "→ committing release v$(VERSION)"
git add $(VERSION_FILES) $(LOCK_FILES)
git commit -m "Release v$(VERSION)"
Expand Down
4 changes: 2 additions & 2 deletions plugin/commands/clear-all.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
---
description: Delete ALL reflexio interactions, profiles, and user playbooks (destructive)
allowed-tools: Bash(uv run:*)
allowed-tools: Bash(bash:*)
---

Run this bash command and show its output verbatim:

!`uv run --project "$HOME/.reflexio/plugin-root" --quiet python -m claude_smart.cli clear-all --yes`
!`bash "$HOME/.reflexio/plugin-root/scripts/cli.sh" clear-all --yes`
4 changes: 2 additions & 2 deletions plugin/commands/learn.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
---
description: Flag the last turn as a correction so reflexio learns from it
allowed-tools: Bash(uv run:*)
allowed-tools: Bash(bash:*)
argument-hint: [note]
---

Flag the user's previous turn as a correction and force reflexio to run extraction on this session's interactions now.
Run the bash command below and show its output verbatim.

!`uv run --project "$HOME/.reflexio/plugin-root" --quiet python -m claude_smart.cli learn "$ARGUMENTS"`
!`bash "$HOME/.reflexio/plugin-root/scripts/cli.sh" learn "$ARGUMENTS"`
4 changes: 2 additions & 2 deletions plugin/commands/restart.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
---
description: Restart the reflexio backend and dashboard to pick up new changes
allowed-tools: Bash(uv run:*)
allowed-tools: Bash(bash:*)
---

Run this bash command and show its output verbatim:

!`uv run --project "$HOME/.reflexio/plugin-root" --quiet python -m claude_smart.cli restart`
!`bash "$HOME/.reflexio/plugin-root/scripts/cli.sh" restart`
4 changes: 2 additions & 2 deletions plugin/commands/show.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
---
description: Show the current project playbook and session user profiles
allowed-tools: Bash(uv run:*)
allowed-tools: Bash(bash:*)
---

Run this bash command and show its output verbatim:

!`uv run --project "$HOME/.reflexio/plugin-root" --quiet python -m claude_smart.cli show`
!`bash "$HOME/.reflexio/plugin-root/scripts/cli.sh" show`
39 changes: 39 additions & 0 deletions plugin/scripts/cli.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
#!/usr/bin/env bash
# Wrapper for slash commands that invoke the claude_smart CLI via uv.
# Claude Code runs `!` bash directives in slash command .md files in a
# non-interactive, non-login shell that does NOT source ~/.zshrc or
# ~/.bash_profile. As a result, binaries installed by smart-install.sh
# at ~/.local/bin (e.g. uv from the astral.sh installer) are invisible
# to those directives until the user manually re-sources their shell rc.
# This wrapper bootstraps PATH the same way hook_entry.sh does so the
# slash commands work on a fresh install.
set -eu

HERE="$(cd "$(dirname "$0")" && pwd)"
# shellcheck source=_lib.sh
. "$HERE/_lib.sh"
claude_smart_source_login_path
claude_smart_prepend_astral_bins

PLUGIN_ROOT="$(cd "$HERE/.." && pwd)"

# If the Setup hook recorded an install failure, surface that reason
# instead of falling through to a generic "uv not found" — mirrors the
# branch at hook_entry.sh so slash commands and hooks behave consistently
# on a broken install.
FAILURE_MARKER="$HOME/.claude-smart/install-failed"
if [ -f "$FAILURE_MARKER" ]; then
msg="$(cat "$FAILURE_MARKER" 2>/dev/null || echo "")"
[ -n "$msg" ] || msg="unknown error"
echo "claude-smart is not installed correctly: $msg" >&2
echo "Re-run the plugin's Setup (restart Claude Code) or fix the underlying issue and delete $FAILURE_MARKER to retry." >&2
exit 1
fi

if ! command -v uv >/dev/null 2>&1; then
echo "claude-smart: 'uv' not found on PATH." >&2
echo "Install it from https://docs.astral.sh/uv/ or restart Claude Code so the Setup hook can install it." >&2
exit 1
fi

exec uv run --project "$PLUGIN_ROOT" --quiet python -m claude_smart.cli "$@"
8 changes: 4 additions & 4 deletions plugin/src/claude_smart/reflexio_adapter.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,13 +192,13 @@ def fetch_project_profiles(self, project_id: str, top_k: int = 20) -> list[Any]:
if client is None:
return []
try:
response = client.search_profiles(
response = client.search_user_profiles(
user_id=project_id,
query="",
top_k=top_k,
)
except Exception as exc: # noqa: BLE001
_LOGGER.debug("search_profiles failed: %s", exc)
_LOGGER.debug("search_user_profiles failed: %s", exc)
Comment on lines +195 to +201
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify every in-repo client mock/call site has been migrated to search_user_profiles.
rg -nP '\bsearch_profiles\s*\(' tests plugin/src
rg -nP '\bsearch_user_profiles\s*\(' tests plugin/src

Repository: ReflexioAI/claude-smart

Length of output: 939


🏁 Script executed:

sed -n '235,260p' plugin/src/claude_smart/reflexio_adapter.py

Repository: ReflexioAI/claude-smart

Length of output: 957


Update test mocks to provide search_user_profiles() instead of search_profiles().

The adapter's search_profiles() method now calls client.search_user_profiles() internally, but test mocks in tests/test_adapter.py still only define search_profiles(). This causes an AttributeError when tests run, which the silent exception handler at lines 197–201 and 255–259 catches and converts to an empty list. Tests pass, but the integration with the actual client API is never validated. Update the mock definitions at lines 35, 128, 182, and 198 to expose search_user_profiles() instead, or add both methods for compatibility.

Also applies to: 253–259

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@plugin/src/claude_smart/reflexio_adapter.py` around lines 195 - 201, The
adapter's search_profiles method calls client.search_user_profiles but the test
mocks only implement search_profiles, causing AttributeError swallowed by the
exception handler; update the test mocks used by tests that exercise
search_profiles to expose a search_user_profiles method (or add both
search_user_profiles and search_profiles on the mock client) so the adapter's
call to client.search_user_profiles resolves correctly and the integration is
actually tested.

return []
return _extract_items(response, "user_profiles")

Expand Down Expand Up @@ -250,13 +250,13 @@ def search_profiles(
if client is None:
return []
try:
response = client.search_profiles(
response = client.search_user_profiles(
user_id=project_id,
query=query,
top_k=top_k,
)
except Exception as exc: # noqa: BLE001
_LOGGER.debug("search_profiles failed: %s", exc)
_LOGGER.debug("search_user_profiles failed: %s", exc)
return []
return _extract_items(response, "user_profiles")

Expand Down
2 changes: 1 addition & 1 deletion reflexio
Submodule reflexio updated 175 files
Loading