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.
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:
- The prompt text — what AtoCore will retrieve context for
- 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 itMAYBE_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/projectsand 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_TEXTis everything except the last token;PROJECT_HINTis the matched canonical project id. - Otherwise the last token is just part of the prompt.
PROMPT_TEXTis the full$ARGUMENTS;PROJECT_HINTis 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 LLMchunks_used,total_chars,budget,budget_remaining,duration_mschunks— array of source documents that contributed, each withsource_file,heading_path,score
Render in this order:
- A one-line stats banner:
chunks=N, chars=X/budget, duration=Yms - The
formatted_contextblock verbatim inside a fenced text code block so the user can read what AtoCore would feed an LLM - The
chunksarray as a small bullet list withsource_file,heading_path, andscoreper chunk
Two special cases:
{"status": "unavailable"}(fail-open from the client) → Tell the user: "AtoCore is unreachable at$ATOCORE_BASE_URL. Checkpython scripts/atocore_client.py healthfor diagnostics."- Empty
chunks_used: 0with 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-projectto 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.