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
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:
@@ -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."
|
||||
)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user