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.
This commit is contained in:
@@ -22,42 +22,80 @@ The user invoked `/atocore-context` with:
|
||||
$ARGUMENTS
|
||||
```
|
||||
|
||||
Treat the **entire argument string** as the prompt by default. If the
|
||||
last whitespace-separated token looks like a registered project id or
|
||||
alias (`atocore`, `p04`, `p04-gigabit`, `gigabit`, `p05`,
|
||||
`p05-interferometer`, `interferometer`, `p06`, `p06-polisher`,
|
||||
`polisher`, or any case-insensitive variant), pull it off and treat
|
||||
it as an explicit project hint. The remaining tokens become the
|
||||
prompt. Otherwise leave the project hint empty and the client will
|
||||
try to auto-detect one from the prompt itself.
|
||||
You need to figure out two things:
|
||||
|
||||
## Step 2 — call the shared client
|
||||
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
|
||||
|
||||
Use the Bash tool. The client respects `ATOCORE_BASE_URL` (default
|
||||
`http://dalidou:8100`) and is fail-open by default — if AtoCore is
|
||||
unreachable it returns a `{"status": "unavailable"}` payload and
|
||||
exits 0, which is what the daily-use loop wants.
|
||||
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:
|
||||
|
||||
**If the user passed an explicit project hint**, call `context-build`
|
||||
directly so AtoCore uses exactly that project:
|
||||
- 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 \
|
||||
"<the prompt text>" \
|
||||
"<the project hint>"
|
||||
"$PROMPT_TEXT" \
|
||||
"$PROJECT_HINT"
|
||||
```
|
||||
|
||||
**If no explicit project hint**, call `auto-context` which will run
|
||||
the client's `detect-project` routing first and only call
|
||||
`context-build` once it has a match:
|
||||
**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
|
||||
python scripts/atocore_client.py auto-context "<the prompt text>"
|
||||
# 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"
|
||||
```
|
||||
|
||||
In both cases the response is the JSON payload from `/context/build`
|
||||
(or, for the `auto-context` no-match case, a small
|
||||
`{"status": "no_project_match"}` envelope).
|
||||
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
|
||||
|
||||
@@ -78,21 +116,15 @@ Render in this order:
|
||||
3. The `chunks` array as a small bullet list with `source_file`,
|
||||
`heading_path`, and `score` per chunk
|
||||
|
||||
Three special cases:
|
||||
Two special cases:
|
||||
|
||||
- **`{"status": "no_project_match"}`** (from `auto-context`)
|
||||
→ Tell the user: "AtoCore could not auto-detect a project from the
|
||||
prompt. Re-run with an explicit project id:
|
||||
`/atocore-context <prompt> <project-id>` (or call without a hint
|
||||
to use the corpus-wide context build)."
|
||||
- **`{"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 for the
|
||||
detected project or the project hint is wrong. Try a different
|
||||
hint or a longer 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
|
||||
|
||||
@@ -115,8 +147,11 @@ queued.
|
||||
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 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 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;
|
||||
|
||||
Reference in New Issue
Block a user