Files
ATOCore/.claude/commands/atocore-context.md
Anto01 f2372eff9e fix(P1+P2): alias-aware project state lookup + slash command corpus fallback
Two regression fixes from codex's review of the slash command
refactor commit (78d4e97). Both findings are real and now have
covered tests.

P1 — server-side alias resolution for project_state lookup
----------------------------------------------------------
The bug:
- /context/build forwarded the caller's project hint verbatim to
  get_state(project_hint), which does an exact-name lookup against
  the projects table (case-insensitive but no alias resolution)
- the project registry's alias matching was only used by the
  client's auto-context path and the retriever's project-match
  boost, never by the server's project_state lookup
- consequence: /atocore-context "... p05" would silently miss
  trusted project state stored under the canonical id
  "p05-interferometer", weakening project-hinted retrieval to
  the point that an explicit alias hint was *worse* than no hint

The fix in src/atocore/context/builder.py:
- import get_registered_project from the projects registry
- before calling get_state(project_hint), resolve the hint
  through get_registered_project; if a registry record exists,
  use the canonical project_id for the state lookup
- if no registry record exists, fall back to the raw hint so a
  hand-curated project_state entry that predates the registry
  still works (backwards compat with pre-registry deployments)

The retriever already does its own alias expansion via
get_registered_project for the project-match boost, so the
retriever side was never broken — only the project_state lookup
in the builder. The fix is scoped to that one call site.

Tests added in tests/test_context_builder.py:
- test_alias_hint_resolves_through_registry: stands up a fresh
  registry, sets state under "p05-interferometer", then verifies
  build_context with project_hint="p05" finds the state, AND
  with project_hint="interferometer" (the second alias) finds it
  too, AND with the canonical id finds it. Covers all three
  resolution paths.
- test_unknown_hint_falls_back_to_raw_lookup: empty registry,
  set state under an unregistered project name, verify the
  build_context call with that name as the hint still finds the
  state. Locks in the backwards-compat behavior.

P2 — slash command no-hint fallback to corpus-wide context build
----------------------------------------------------------------
The bug:
- the slash command's no-hint path called auto-context, which
  returns {"status": "no_project_match"} when project detection
  fails and does NOT fall back to a plain context-build
- the slash command's own help text told the user "call without
  a hint to use the corpus-wide context build" — which was a lie
  because the wrapper no longer did that
- consequence: generic prompts like "what changed in AtoCore
  backup policy?" or any cross-project question got a useless
  no_project_match envelope instead of a context pack

The fix in .claude/commands/atocore-context.md:
- the no-hint path now does the 2-step fallback dance:
    1. try `auto-context "<prompt>"` for project detection
    2. if the response contains "no_project_match", fall back to
       `context-build "<prompt>"` (no project arg)
- both branches return a real context pack, fail-open envelope
  is preserved for genuine network errors
- the underlying client surface is unchanged (no new flags, no
  new subcommands) — the fallback is per-frontend logic in the
  slash command, leaving auto-context's existing semantics
  intact for OpenClaw and any other caller that depends on the
  no_project_match envelope as a "do nothing" signal

While I was here, also tightened the slash command's argument
parsing to delegate alias-knowledge to the registry instead of
embedding a hardcoded list:
- old version had a literal list of "atocore", "p04", "p05",
  "p06" and their aliases that needed manual maintenance every
  time a project was added
- new version takes the last token of $ARGUMENTS and asks the
  client's `detect-project` subcommand whether it's a known
  alias; if matched, it's the explicit hint, if not it's part
  of the prompt
- this delegates registry knowledge to the registry, where it
  belongs

Unrelated improvement noted but NOT fixed in this commit:
- _rank_chunks in builder.py also has a naive substring boost
  that uses the original hint without alias expansion. The
  retriever already does the right thing, so this secondary
  boost is redundant. Tracked as a future cleanup but not in
  scope for the P1/P2 fix; codex's findings are about
  project_state lookup, not about the secondary chunk boost.

Full suite: 162 passing (was 160), 1 warning. The +2 is the two
new P1 regression tests.
2026-04-07 07:47:03 -04:00

6.4 KiB

description, argument-hint
description argument-hint
Pull a context pack from the live AtoCore service for the current prompt <prompt text> [project-id]

You are about to enrich a user prompt with context from the live AtoCore service. This is the daily-use entry point for AtoCore from inside Claude Code.

The work happens via the shared AtoCore operator client at scripts/atocore_client.py. That client is the canonical Python backbone for stable AtoCore operations and is meant to be reused by every LLM client (OpenClaw helper, future Codex skill, etc.) — see docs/architecture/llm-client-integration.md for the layering. This slash command is a thin Claude Code-specific frontend on top of it.

Step 1 — parse the arguments

The user invoked /atocore-context with:

$ARGUMENTS

You need to figure out two things:

  1. The prompt text — what AtoCore will retrieve context for
  2. An optional project hint — used to scope retrieval to a specific project's trusted state and corpus

The user may have passed a project id or alias as the last whitespace-separated token. Don't maintain a hardcoded list of known aliases — let the shared client decide. Use this rule:

  • Take the last token of $ARGUMENTS. Call it MAYBE_HINT.
  • Run python scripts/atocore_client.py detect-project "$MAYBE_HINT" to ask the registry whether it's a known project id or alias. This call is cheap (it just hits /projects and does a regex match) and inherits the client's fail-open behavior.
  • If the response has a non-null matched_project, the last token was an explicit project hint. PROMPT_TEXT is everything except the last token; PROJECT_HINT is the matched canonical project id.
  • Otherwise the last token is just part of the prompt. PROMPT_TEXT is the full $ARGUMENTS; PROJECT_HINT is empty.

This delegates the alias-knowledge to the registry instead of embedding a stale list in this markdown file. When you add a new project to the registry, the slash command picks it up automatically with no edits here.

Step 2 — call the shared client for the context pack

The server resolves project hints through the registry before looking up trusted state, so you can pass either the canonical id or any alias to context-build and the trusted state lookup will work either way. (Regression test: tests/test_context_builder.py::test_alias_hint_resolves_through_registry.)

If PROJECT_HINT is non-empty, call context-build directly with that hint:

python scripts/atocore_client.py context-build \
  "$PROMPT_TEXT" \
  "$PROJECT_HINT"

If PROJECT_HINT is empty, do the 2-step fallback dance so the user always gets a context pack regardless of whether the prompt implies a project:

# Try project auto-detection first.
RESULT=$(python scripts/atocore_client.py auto-context "$PROMPT_TEXT")

# If auto-context could not detect a project it returns a small
# {"status": "no_project_match", ...} envelope. In that case fall
# back to a corpus-wide context build with no project hint, which
# is the right behaviour for cross-project or generic prompts like
# "what changed in AtoCore backup policy this week?"
if echo "$RESULT" | grep -q '"no_project_match"'; then
  RESULT=$(python scripts/atocore_client.py context-build "$PROMPT_TEXT")
fi

echo "$RESULT"

This is the fix for the P2 finding from codex's review: previously the slash command sent every no-hint prompt through auto-context and returned no_project_match to the user with no context, even though the underlying client's context-build subcommand has always supported corpus-wide context builds.

In both branches the response is the JSON payload from /context/build (or, in the rare case where even the corpus-wide build fails, a {"status": "unavailable"} envelope from the client's fail-open layer).

Step 3 — present the context pack to the user

The successful response contains at least:

  • formatted_context — the assembled context block AtoCore would feed an LLM
  • chunks_used, total_chars, budget, budget_remaining, duration_ms
  • chunks — array of source documents that contributed, each with source_file, heading_path, score

Render in this order:

  1. A one-line stats banner: chunks=N, chars=X/budget, duration=Yms
  2. The formatted_context block verbatim inside a fenced text code block so the user can read what AtoCore would feed an LLM
  3. The chunks array as a small bullet list with source_file, heading_path, and score per chunk

Two special cases:

  • {"status": "unavailable"} (fail-open from the client) → Tell the user: "AtoCore is unreachable at $ATOCORE_BASE_URL. Check python scripts/atocore_client.py health for diagnostics."
  • Empty chunks_used: 0 with no project state and no memories → Tell the user: "AtoCore returned no context for this prompt — either the corpus does not have relevant information or the project hint is wrong. Try a different hint or a longer prompt."

Step 4 — what about capturing the interaction

Capture (Phase 9 Commit A) and the rest of the reflection loop (reinforcement, extraction, review queue) are intentionally NOT exposed by the shared client yet. The contracts are stable but the workflow ergonomics are not, so the daily-use slash command stays focused on context retrieval until those review flows have been exercised in real use. See docs/architecture/llm-client-integration.md for the deferral rationale.

When capture is added to the shared client, this slash command will gain a follow-up /atocore-record-response companion command that posts the LLM's response back to the same interaction. That work is queued.

Notes for the assistant

  • DO NOT bypass the shared client by calling curl yourself. The client is the contract between AtoCore and every LLM frontend; if you find a missing capability, the right fix is to extend the client, not to work around it.
  • DO NOT maintain a hardcoded list of project aliases in this file. Use detect-project to ask the registry — that's the whole point of having a registry.
  • DO NOT silently change ATOCORE_BASE_URL. If the env var points at the wrong instance, surface the error so the user can fix it.
  • DO NOT hide the formatted context pack from the user. Showing what AtoCore would feed an LLM is the whole point.
  • The output goes into the user's working context as background; they may follow up with their actual question, and the AtoCore context pack acts as informal injected knowledge.