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.
160 lines
6.4 KiB
Markdown
160 lines
6.4 KiB
Markdown
---
|
|
description: Pull a context pack from the live AtoCore service for the current prompt
|
|
argument-hint: <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:
|
|
|
|
```bash
|
|
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:
|
|
|
|
```bash
|
|
# 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.
|