feat(shapes): the write-path hook suggests and no longer stamps (milestone 439 step 4)
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / integration (push) Successful in 54s
CI & Build / Python tests (push) Successful in 1m49s
CI & Build / Build & push image (push) Successful in 27s

The hook used to land its evidence as `instance` rows, classified_by=hook —
a permanent verdict nobody read, and the source of the weak stamps that
poisoned two ledgers (#4608). Now the agent that wrote the code judges it at
the end of the turn, and the hook's evidence is what it is shown.

- shape_ledger.suggest_write_path_instances (was stamp_write_path_instances):
  same evidence and form gate, but it writes a PROPOSAL — proposed_snippet_id,
  basis "reference" (named) or "semantic" (resembles), score — and never a
  status. A judged row is left alone; a new shape gets an unclassified
  provisional row so the suggestion reaches the end-of-turn question as
  "looks like #N". By-name uses edges stay: a fact, not a verdict.
- The prior-art line says "looks like #N … when you judge this turn's
  shapes, say whether it is", and the result key is `suggested`.
- write_time_divergence takes `suggested`; _RESEMBLE_MIN re-documented as the
  suggestion bar and the weak-stamp line.
- reusing-code skill no longer says the pull stamps an instance.
- Tests moved to the new contract, plus a guard that nothing the hook does
  writes classified_by="hook".

Plugin version minted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-01 08:42:10 -04:00
co-authored by Claude Opus 5.5
parent f1fbdf746a
commit 4cf1c6f042
7 changed files with 150 additions and 110 deletions
+29 -26
View File
@@ -2083,16 +2083,18 @@ async def build_write_path_hint(
``stamp_shapes`` turns the same request into the ledger's write-path feed
(#2791): the (kind, name) definitions the hook saw in — or enclosing —
the payload. When the session has PULLED a snippet recently and this
payload references or resembles it, those shapes land as `instance` rows
(classified_by=hook, see shape_ledger.stamp_write_path_instances) and
the result's ``stamped`` lists them. The route passes it only for a
caller allowed to write — a read-scoped key gets the hint, never the
stamp. ``repo_key`` (the hook's remote, normalised) homes a provisional
row for a shape the ledger has not synced yet.
payload references or resembles it, those shapes carry it as a PROPOSAL
(see shape_ledger.suggest_write_path_instances) and the result's
``suggested`` lists them. Never a verdict since milestone 439: the agent
that wrote the code judges it at the end of the turn, shown this as
evidence. The route passes it only for a caller allowed to write — a
read-scoped key gets the hint, never a ledger write. ``repo_key`` (the
hook's remote, normalised) homes a provisional row for a shape the
ledger has not synced yet.
"""
cfg = await get_writepath_config(user_id)
empty = {"context": "", "note_ids": [], "sync_note_ids": [], "config": cfg,
"stamped": [], "divergence": [], "derive": [], "derive_keys": [],
"suggested": [], "divergence": [], "derive": [], "derive_keys": [],
"rule_ids": []}
path = (path or "").strip()
if not cfg["enabled"] or not path:
@@ -2356,18 +2358,18 @@ async def build_write_path_hint(
menu = (placed + scored)[:max(0, top_k - len(synced))]
# The stamp runs whether or not anything is rendered — after dedup, the
# common case is a silent hint and a pulled canon being instantiated.
stamped: list[dict] = []
# The suggestion runs whether or not anything is rendered — after dedup,
# the common case is a silent hint and a pulled canon being instantiated.
suggested: list[dict] = []
if stamp_shapes and pulled:
try:
stamped = await shape_ledger_svc.stamp_write_path_instances(
suggested = await shape_ledger_svc.suggest_write_path_instances(
user_id, project_id, path=path, shapes=stamp_shapes,
code=code or "", pulled=pulled, resembles=resembles,
repo_key=repo_key,
)
except Exception:
logger.warning("Write-path ledger stamping failed", exc_info=True)
logger.warning("Write-path ledger suggestion failed", exc_info=True)
# The in-band button-B check (#2793): the hook named the shapes being
# written; if this directory+kind is canon-dense and a named shape isn't
# (about to be) an instance of that canon, say so NOW — at the write,
@@ -2376,7 +2378,7 @@ async def build_write_path_hint(
if stamp_shapes and project_id:
try:
divergence = await shape_ledger_svc.write_time_divergence(
project_id, path, stamp_shapes, stamped, code or "",
project_id, path, stamp_shapes, suggested, code or "",
)
except Exception:
logger.warning("write-time divergence check failed", exc_info=True)
@@ -2430,7 +2432,7 @@ async def build_write_path_hint(
design_text, design_dedup = await _design_arm(
user_id, project_id, path, set(exclude_derive or []),
)
if not staleness and not synced and not menu and not stamped and not divergence and not derive:
if not staleness and not synced and not menu and not suggested and not divergence and not derive:
if design_text:
return {**empty, "context": design_text, "derive_keys": [design_dedup]}
return empty
@@ -2531,8 +2533,8 @@ async def build_write_path_hint(
if passage:
lines.append(f"> ↳ {passage}")
if stamped:
lines.append(_stamp_line(path, stamped))
if suggested:
lines.append(_suggest_line(path, suggested))
if divergence:
lines.append(_divergence_line(path, divergence))
if derive:
@@ -2694,7 +2696,7 @@ async def build_write_path_hint(
"note_ids": note_ids,
"sync_note_ids": sync_note_ids,
"config": cfg,
"stamped": stamped,
"suggested": suggested,
"divergence": divergence,
"derive": derive,
"derive_keys": [d["key"] for d in derive] + (
@@ -3017,22 +3019,23 @@ def _divergence_line(path: str, divergence: list[dict]) -> str:
)
def _stamp_line(path: str, stamped: list[dict]) -> str:
"""One line saying what the ledger just recorded, so the session can
correct a wrong stamp in the moment rather than an audit finding it."""
def _suggest_line(path: str, suggested: list[dict]) -> str:
"""One line naming the recorded shape this code looks like, as evidence
for the judgment the agent gives at the end of the turn (milestone 439)
— not a record of one already made."""
by_snippet: dict[int, list[str]] = {}
for row in stamped:
for row in suggested:
label = f".{row['symbol']}" if row["kind"] == "css" else row["symbol"]
by_snippet.setdefault(int(row["snippet_id"]), []).append(f"`{label}`")
parts = [
f"{', '.join(names)} → instance of #{sid}"
f"{', '.join(names)} → looks like #{sid}"
for sid, names in by_snippet.items()
]
return (
f"> Shape accounting: recorded at `{path}` — {'; '.join(parts)} "
"(classified_by=hook: you pulled that snippet this session and this "
"code references/resembles it). Not an instance? `classify_shapes` "
"overrides a hook stamp."
f"> Shape accounting: at `{path}` — {'; '.join(parts)} "
"(you pulled that snippet this session and this code names or resembles "
"it). When you judge this turn's shapes, say whether it is: `instance` "
"of it, `variant` with the why, or something else."
)