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:
@@ -1291,9 +1291,9 @@ async def test_stamping_needs_named_shapes_and_a_recent_pull():
|
||||
patch.object(pc, "semantic_search_notes", AsyncMock(return_value=[])), \
|
||||
patch.object(pc, "record_retrieval", MagicMock()), \
|
||||
patch.object(pc.shape_ledger_svc, "recent_pulls", pulls), \
|
||||
patch.object(pc.shape_ledger_svc, "stamp_write_path_instances", stamp):
|
||||
patch.object(pc.shape_ledger_svc, "suggest_write_path_instances", stamp):
|
||||
out = await pc.build_write_path_hint(1, "src/x.py", code=REAL_CODE, project_id=4)
|
||||
assert out["stamped"] == []
|
||||
assert out["suggested"] == []
|
||||
pulls.assert_not_awaited() # no shapes → no read
|
||||
out = await pc.build_write_path_hint(
|
||||
1, "src/x.py", code=REAL_CODE, project_id=4,
|
||||
@@ -1301,7 +1301,7 @@ async def test_stamping_needs_named_shapes_and_a_recent_pull():
|
||||
)
|
||||
pulls.assert_awaited_once()
|
||||
stamp.assert_not_awaited() # shapes, but no pull
|
||||
assert out["stamped"] == []
|
||||
assert out["suggested"] == []
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -1330,7 +1330,7 @@ async def test_a_pulled_snippet_already_seen_is_evidence_not_menu():
|
||||
patch.object(pc, "record_retrieval", MagicMock()), \
|
||||
patch.object(pc, "owner_names_for", AsyncMock(return_value={})), \
|
||||
patch.object(pc.shape_ledger_svc, "recent_pulls", AsyncMock(return_value={7: _ts()})), \
|
||||
patch.object(pc.shape_ledger_svc, "stamp_write_path_instances", stamp):
|
||||
patch.object(pc.shape_ledger_svc, "suggest_write_path_instances", stamp):
|
||||
out = await pc.build_write_path_hint(
|
||||
1, "src/x.py", code=REAL_CODE, project_id=4, exclude_ids=[7],
|
||||
stamp_shapes=[("sym", "debounce")], repo_key="git.example.com/a/b",
|
||||
@@ -1355,11 +1355,11 @@ async def test_a_pulled_snippet_already_seen_is_evidence_not_menu():
|
||||
assert skw["resembles"] == {7: 0.91}
|
||||
assert skw["shapes"] == [("sym", "debounce")]
|
||||
assert skw["repo_key"] == "git.example.com/a/b"
|
||||
assert out["stamped"][0]["snippet_id"] == 7
|
||||
# And the session is told what landed, with the way to correct it.
|
||||
assert out["suggested"][0]["snippet_id"] == 7
|
||||
# And the session is shown it as evidence for its own end-of-turn judgment.
|
||||
assert "Shape accounting" in out["context"]
|
||||
assert "`debounce` → instance of #7" in out["context"]
|
||||
assert "classify_shapes" in out["context"]
|
||||
assert "`debounce` → looks like #7" in out["context"]
|
||||
assert "When you judge this turn's shapes" in out["context"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -1376,15 +1376,15 @@ async def test_a_stamp_renders_even_when_the_hint_is_otherwise_silent():
|
||||
patch.object(pc, "record_retrieval", MagicMock()), \
|
||||
patch.object(pc, "owner_names_for", AsyncMock(return_value={})), \
|
||||
patch.object(pc.shape_ledger_svc, "recent_pulls", AsyncMock(return_value={5: _ts()})), \
|
||||
patch.object(pc.shape_ledger_svc, "stamp_write_path_instances",
|
||||
patch.object(pc.shape_ledger_svc, "suggest_write_path_instances",
|
||||
AsyncMock(return_value=stamped)):
|
||||
out = await pc.build_write_path_hint(
|
||||
1, "web/b.css", code=".btn-primary { color: red; }" * 4, project_id=4,
|
||||
stamp_shapes=[("css", "btn-primary")],
|
||||
)
|
||||
assert out["note_ids"] == []
|
||||
assert out["stamped"] == stamped
|
||||
assert "`.btn-primary` → instance of #5" in out["context"]
|
||||
assert out["suggested"] == stamped
|
||||
assert "`.btn-primary` → looks like #5" in out["context"]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@@ -1397,13 +1397,13 @@ async def test_a_failing_stamp_does_not_sink_the_hint():
|
||||
patch.object(pc, "record_retrieval", MagicMock()), \
|
||||
patch.object(pc, "owner_names_for", AsyncMock(return_value={})), \
|
||||
patch.object(pc.shape_ledger_svc, "recent_pulls", AsyncMock(return_value={12: _ts()})), \
|
||||
patch.object(pc.shape_ledger_svc, "stamp_write_path_instances",
|
||||
patch.object(pc.shape_ledger_svc, "suggest_write_path_instances",
|
||||
AsyncMock(side_effect=RuntimeError("ledger down"))):
|
||||
out = await pc.build_write_path_hint(
|
||||
1, "src/x.py", code=REAL_CODE, project_id=4, stamp_shapes=[("sym", "f")],
|
||||
)
|
||||
assert out["sync_note_ids"] == [12]
|
||||
assert out["stamped"] == []
|
||||
assert out["suggested"] == []
|
||||
|
||||
|
||||
def test_route_stamps_only_for_a_caller_allowed_to_write():
|
||||
@@ -1760,7 +1760,7 @@ async def test_a_record_this_arm_withheld_itself_is_not_a_near_miss():
|
||||
patch.object(pc, "owner_names_for", AsyncMock(return_value={})), \
|
||||
patch.object(pc.shape_ledger_svc, "recent_pulls",
|
||||
AsyncMock(return_value={7: _ts()})), \
|
||||
patch.object(pc.shape_ledger_svc, "stamp_write_path_instances",
|
||||
patch.object(pc.shape_ledger_svc, "suggest_write_path_instances",
|
||||
AsyncMock(return_value=[])), \
|
||||
patch.object(pc, "semantic_search_notes",
|
||||
_search_reporting(0.9, fake_note(
|
||||
|
||||
Reference in New Issue
Block a user