diff --git a/Makefile b/Makefile index d9e7f1e..0100f9d 100644 --- a/Makefile +++ b/Makefile @@ -1,15 +1,18 @@ VENV := .venv/bin +# ruff: prefer the project venv, fall back to `uvx ruff` when no venv is present +RUFF := $(shell [ -x .venv/bin/ruff ] && echo .venv/bin/ruff || echo "uvx ruff") + .PHONY: test lint fmt typecheck check hooks test: $(VENV)/pytest tests/ -v lint: - $(VENV)/ruff check src/ tests/ + $(RUFF) check src/ tests/ fmt: - $(VENV)/ruff format src/ tests/ + $(RUFF) format src/ tests/ typecheck: $(VENV)/pyright src/ diff --git a/src/codeatrium/cli/prime_cmd.py b/src/codeatrium/cli/prime_cmd.py index 52837c6..139d5b3 100644 --- a/src/codeatrium/cli/prime_cmd.py +++ b/src/codeatrium/cli/prime_cmd.py @@ -41,6 +41,37 @@ # Retrieve past conversations from work on a specific branch loci context --branch "feature/foo" --json +``` + +### IDE selection as a deictic anchor + +When the IDE injects an active editor selection (shown as `⧉ Selected N lines from `), treat that selection as the referent of "this / これ / この〜" in the user's prompt. The selection resolves *which* symbol the memory lookup is about — it is NOT by itself a request to recall. + +Recall from a selection ONLY when BOTH hold: + +1. a selection is active, AND +2. either (a) the user asks about the past — recall / why / history / decisions ("この実装の時の会話を思い出して", "これ前にどう決めた?"), or (b) your own next action on the selected code (edit / refactor / debug) needs prior decisions or constraints. + +Do NOT recall when the selection is present only because the user is about to edit it and asks nothing past-oriented. + +Map the selection to a query: + +- Selection IS a named function/class → look it up directly: + +```bash +loci context --symbol "" --json +``` + +- Selection is a fragment INSIDE one function/class — the `def`/`class` line is usually not in the selection, so resolve the enclosing symbol first (LSP `workspaceSymbol`/`hover`, or read `` around the selection), then: + +```bash +loci context --symbol "" --json +``` + +- Selection spans multiple symbols or belongs to none (module-level code, config, comments), OR the lookups above return no results → fall back to semantic search over your question: + +```bash +loci search "" --json ```\ """ diff --git a/tests/test_prime_cmd.py b/tests/test_prime_cmd.py index 3722bb9..b8456f2 100644 --- a/tests/test_prime_cmd.py +++ b/tests/test_prime_cmd.py @@ -44,6 +44,40 @@ def test_prime_text_context_section_explains_bidirectional_recall(): ) +# ---- IDE selection trigger contract ---- + + +def test_prime_text_has_ide_selection_section(): + """PRIME_TEXT must explain IDE selection as a deictic anchor for context recall""" + assert "IDE selection as a deictic anchor" in PRIME_TEXT + + +def test_prime_text_ide_selection_requires_conjunction(): + """A selection alone must NOT trigger recall — requires selection AND a recall need""" + text_lower = PRIME_TEXT.lower() + assert "only when both" in text_lower + # both branches of the recall need must be present + assert "asks about the past" in text_lower + assert "your own next action" in text_lower + + +def test_prime_text_ide_selection_guards_against_over_fire(): + """PRIME_TEXT must tell the agent NOT to recall on edit-only selections""" + assert "Do NOT recall" in PRIME_TEXT + + +def test_prime_text_ide_selection_resolves_enclosing_symbol_for_fragments(): + """A fragment selection must resolve its enclosing symbol before context lookup""" + assert "enclosing symbol" in PRIME_TEXT + + +def test_prime_text_ide_selection_has_search_fallback(): + """When no single symbol applies or context is empty, fall back to loci search""" + text_lower = PRIME_TEXT.lower() + assert "spans multiple symbols" in text_lower + assert "return no results" in text_lower + + # ---- inject_claude_md idempotency ----