feat(plugin): a directory says which project it belongs to, git repo or not (#4085)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 10s
CI & Build / integration (push) Successful in 47s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / Python tests (push) Failing after 1m1s
CI & Build / Build & push image (push) Skipped
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 10s
CI & Build / integration (push) Successful in 47s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / Python tests (push) Failing after 1m1s
CI & Build / Build & push image (push) Skipped
All six hooks scoped their requests one way: `git remote get-url origin`,
resolved server-side through the repo bindings. That key does not exist
outside a git repo, so a session in a plain directory was unscoped in every
hook at once — no project context, no prior-art scoping, no project rules —
and silently, because a missing remote is indistinguishable from a remote
nobody bound.
A `.scribe` file is the second key, read by the shared scribe_scope_query so a
directory scopes the same way everywhere:
{"instance": "https://scribe.example.com", "project_id": 2, "project": "…"}
`instance` is why the file is not just a number: an id is a different project
on every Scribe, so a marker that travels — a copied directory, a shared
machine, a repo someone else clones — would otherwise scope the session to the
wrong project without a word. Compared host-only, and a mismatch drops the id:
no project beats the wrong project. A bare integer is accepted too, since it
is what a person writes by hand. The marker beats a git remote — someone put
the file there on purpose — which is also how a directory overrides its
binding.
Two things it found on the way:
* An explicit project_id that did not resolve rendered NO message at all —
the branch hung off `if project_id` as an `elif`, so a caller holding a
pointer it believed in got a context that silently omitted the project it
had asked for. Now reported.
* The refusal reason was a global set inside a function every caller reads
through `$( )`. The assignment died with the subshell, leaving the caller
to read an unset variable under `set -u` — which aborts the hook and costs
the whole session's SessionStart context, to fetch a warning about a file.
It comes back through stdout with the id instead, and a test pins it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01821k5B3Ysecp9fNYs92Kuy
This commit is contained in:
@@ -215,6 +215,31 @@ async def test_build_session_context_unbound_repo_emits_bind_hint():
|
||||
assert "## Active project" not in ctx
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_a_project_id_that_does_not_resolve_is_reported_not_dropped():
|
||||
"""A `.scribe` marker names a project directly (#4085), so for the first
|
||||
time a caller arrives holding a pointer it BELIEVES in. If the id is for
|
||||
another instance, or names a deleted project, the session has to be told —
|
||||
it cannot infer it from an absence.
|
||||
|
||||
This used to render nothing whatsoever: the branch hung off `if project_id`
|
||||
as an `elif`, so an id that was sent and failed took the outer arm, found
|
||||
no project, and fell out of the block having said nothing at all.
|
||||
"""
|
||||
from scribe.services.plugin_context import build_session_context
|
||||
with patch("scribe.services.plugin_context.projects_svc.get_project",
|
||||
AsyncMock(return_value=None)):
|
||||
out = await build_session_context(user_id=7, project_id=41)
|
||||
|
||||
ctx = out["context"]
|
||||
assert out["project"] is None
|
||||
assert "## Project 41 could not be loaded" in ctx
|
||||
assert "list_projects" in ctx
|
||||
# Not mistaken for the repo case, which has a different remedy.
|
||||
assert "## Repository not yet bound" not in ctx
|
||||
assert "No Scribe project is bound" not in ctx
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_build_process_manifest_renders_stub_specs():
|
||||
items = [
|
||||
|
||||
Reference in New Issue
Block a user