feat(shapes): the agent judges what it wrote, at the end of the turn (milestone 439 steps 1-3)
CI & Build / Python lint (push) Successful in 5s
CI & Build / Plugin hooks (push) Successful in 18s
CI & Build / TypeScript typecheck (push) Successful in 54s
CI & Build / integration (push) Successful in 52s
CI & Build / Python tests (push) Successful in 1m38s
CI & Build / Build & push image (push) Successful in 33s
CI & Build / Python lint (push) Successful in 5s
CI & Build / Plugin hooks (push) Successful in 18s
CI & Build / TypeScript typecheck (push) Successful in 54s
CI & Build / integration (push) Successful in 52s
CI & Build / Python tests (push) Successful in 1m38s
CI & Build / Build & push image (push) Successful in 33s
Recording used to be decided by machinery — the only "record it" prompt fired when a same-named copy already existed (#2664), so a first instance of a reusable piece was never asked about, and judgment arrived only through audits. Now the question is asked where the knowledge is: the end of the turn that wrote the code, of the agent that wrote it. - Write hooks keep `<sid>.written.ids` (path, kind, name) for every definition a write names; a new file adds a `file` line for its stem — a candidate in any language without a framework rule (scribe_written_append). - Stop hook scribe_shape_check.sh sends the ledger to GET /api/plugin/shape-check and blocks once, in the server's words, when anything is unjudged. Same discipline as the report check: never twice, never without a recorded check, another hook's loop left alone; the ledger is kept when the instance cannot be reached. - shape_ledger.unjudged_shapes: no row, unclassified, scoped and hook stamps are unjudged; an agent/audit/import verdict is not. A snippet recorded at the shape answers for it until the refresh stamps it canonical. - services/shape_check owns the reason text and records every outcome in app_logs (passed / blocked / judged_after_block / left_after_block). - classify_shapes(repo=…) judges a shape the ledger has not synced yet via a provisional row under a bound repo; the sync confirms it, or vanishes and revives it with the verdict intact. An unbound repo is refused. Plugin version minted. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -122,6 +122,24 @@ async def bindings_for_project(user_id: int, project_id: int) -> list[RepoBindin
|
||||
return list(rows.scalars().all())
|
||||
|
||||
|
||||
async def is_bound(project_id: int, raw_repo: str) -> str:
|
||||
"""The normalized key when ``raw_repo`` is bound to ``project_id`` by
|
||||
anyone, else "". By anyone, not by the caller: a collaborator judging a
|
||||
shared project's shapes holds no binding of their own, and the question
|
||||
is whether the repo belongs to the project, not who bound it."""
|
||||
key = normalize_repo_key(raw_repo or "")
|
||||
if not key or not project_id:
|
||||
return ""
|
||||
async with async_session() as session:
|
||||
found = await session.execute(
|
||||
select(RepoBinding.id).where(
|
||||
RepoBinding.project_id == project_id,
|
||||
RepoBinding.repo_key == key,
|
||||
).limit(1)
|
||||
)
|
||||
return key if found.first() is not None else ""
|
||||
|
||||
|
||||
async def keys_for_project(user_id: int, project_id: int) -> list[str]:
|
||||
"""Every repo key bound to a project — the snippet→forge join (#2691).
|
||||
|
||||
|
||||
Reference in New Issue
Block a user