From a3c2f6411ce99c08a8c7bb5c3615a504d5e1cef1 Mon Sep 17 00:00:00 2001 From: Jarvis Date: Thu, 11 Jun 2026 09:40:38 -0700 Subject: [PATCH 1/3] =?UTF-8?q?feat(kanban):=20v6.8=20Part=202=20=E2=80=94?= =?UTF-8?q?=20honest-reject=20floor=20bypass=20+=20bullet=20not=5Fapplicab?= =?UTF-8?q?le=20evidence?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Bundles two precision fixes for the v6.7 completion gates surfaced by the 2026-06-10 validation chain on hermes-dashboard. Both touch ``kanban_completion_gates.py``. Closes hermes-jarvis#74 (honest-reject floor bypass) Closes hermes-jarvis#75 (bullet not_applicable for evidence) ## #74 — honest-reject floor bypass The 90s reviewer runtime floor was designed against rubber-stamp APPROVES. The validation chain showed it also blocked accurate rejects: Tony correctly identified 3 real bugs in 20-38 seconds, verdict: reject with substantive evidence, but the gate rejected the completion 6+ times. Penalizing accurate fast rejects creates pressure to either fake-pad time or rubber-stamp-approve. Neither is good. Fix: ``verify_runtime_floor`` gains an ``is_honest_reject`` kwarg. When True AND the worker is in REVIEW_ROLES (tony/tchalla/vision/ reviewer), the floor is bypassed. Build/orchestration roles are unaffected — a friday 60-second "implementation" that ends in reject still warrants the floor (could be bailing on work, not a real fast reject). The verdict is parsed once at the top of ``_v6_7_run_completion_gates`` via the existing ``_v6_7_parse_verdict`` and passed through. Also hoisted ``REVIEW_ROLES`` / ``ORCHESTRATION_ROLES`` constants above their first use (they were used at line 92 but defined at line 144 — Python tolerates the forward ref but it's fragile). ## #75 — bullet `not_applicable: ` for evidence The 2026-06-10 Tony case: he wrote ``evidence:\n - not_applicable: no tests directory or *.test.* files reference swarm status`` — honestly declaring there are no tests. The previous regex only accepted inline ``evidence: none`` (or ``[]`` / ``n/a``), not the bullet form. So Tony's verbose-but- correct verdict was rejected for missing test_quality.evidence. Fix: ``_field_present`` for test_quality.evidence now accepts a ``- not_applicable: `` bullet item with reason ≥8 chars, mirroring the existing imports_match_deliverable_entrypoints rule. The same ``code_change_context`` rule applies — code-touching reviews must still produce real citations, not honest-empty escapes. ## Tests 13 new tests covering both fixes: **TestRuntimeFloor (6 new):** - honest reject bypasses floor for tony / tchalla / vision - approve under floor still rejects (rubber-stamp protection) - honest reject does NOT bypass for build roles (friday still 5min) - honest reject + unknown role is a pass (existing behavior) **TestBulletNotApplicableEvidence (7):** - bullet not_applicable accepted on docs review - bullet not_applicable blocked on code review (code_change_context) - bullet not_applicable with <8-char reason rejected - bullet not_applicable with empty reason rejected - real citations still pass - inline ``evidence: none`` still works (backwards-compat) - mixing real bullets with not_applicable doesn't regress **TestHonestRejectIntegration (2):** - tony fast reject in 20s passes complete_task end-to-end - tony fast approve in 20s still blocked (regression guard) 105/105 in test_kanban_completion_gates.py pass. 191/191 across full v6.7/v6.8 + adjacent regression set — zero failures. Co-Authored-By: Claude Opus 4.7 (1M context) --- hermes_cli/kanban_completion_gates.py | 37 ++- hermes_cli/kanban_db.py | 6 + tests/cli/test_kanban_completion_gates.py | 271 ++++++++++++++++++++++ 3 files changed, 310 insertions(+), 4 deletions(-) diff --git a/hermes_cli/kanban_completion_gates.py b/hermes_cli/kanban_completion_gates.py index 7a9e7c7d2d38..fe6f787b7df3 100644 --- a/hermes_cli/kanban_completion_gates.py +++ b/hermes_cli/kanban_completion_gates.py @@ -66,6 +66,13 @@ "banner": 0, } +# Role sets — referenced by the floor's honest-reject bypass (#74) and +# the workspace-diff and reviewer-fields gates below. Hoisted here so +# verify_runtime_floor can reference REVIEW_ROLES at call time without +# forward-ref gymnastics. +REVIEW_ROLES = {"tony", "tchalla", "vision", "reviewer"} +ORCHESTRATION_ROLES = {"jarvis", "pepper", "banner"} + @dataclass(frozen=True) class RuntimeFloorViolation: @@ -92,6 +99,7 @@ def verify_runtime_floor( completed_at: int, *, allow_below_floor: bool = False, + is_honest_reject: bool = False, ) -> Optional[RuntimeFloorViolation]: """Return a violation if the worker's runtime is below its role floor. @@ -101,12 +109,23 @@ def verify_runtime_floor( A floor of 0 (or an unknown assignee, or a missing ``started_at``) is a pass — we never invent floors for roles we don't know. + + ``is_honest_reject=True`` is a bypass for review-role tasks that + are completing with ``verdict: reject``. Closes hermes-jarvis#74: + the floor was designed against rubber-stamp APPROVES; penalizing + accurate fast rejects creates pressure to either fake-pad time or + rubber-stamp-approve, neither of which is good. Build/orchestration + roles get no bypass — a 60-second "implementation" that ends in + reject still warrants the floor. """ if allow_below_floor: return None if not assignee or started_at is None: return None - floor = ROLE_RUNTIME_FLOORS_SECONDS.get(assignee.lower()) + role = assignee.lower() + if is_honest_reject and role in REVIEW_ROLES: + return None + floor = ROLE_RUNTIME_FLOORS_SECONDS.get(role) if not floor: return None actual = max(0, completed_at - int(started_at)) @@ -122,9 +141,6 @@ def verify_runtime_floor( # Workspace-diff gate (#62) # ===================================================================== -REVIEW_ROLES = {"tony", "tchalla", "vision", "reviewer"} -ORCHESTRATION_ROLES = {"jarvis", "pepper", "banner"} - @dataclass(frozen=True) class WorkspaceDiffViolation: @@ -589,6 +605,13 @@ def _has_adversarial_structure(text: str) -> bool: # Empty-marker tokens accepted as honest declarations. _HONEST_EMPTY_MARKERS = {"none", "[]", "{}", "n/a"} +# hermes-jarvis#75: bullet form of honest-empty for test_quality.evidence. +# The reason must be ≥8 chars (non-whitespace), mirroring the +# ``not_applicable`` shape already accepted for imports_match. +_BULLET_NOT_APPLICABLE_RE = re.compile( + r"(?mi)^\s*-\s*not_applicable\s*:\s*(\S.{6,})\s*$", +) + def _adversarial_value_substantive(captured: str) -> bool: """True if an adversarial_pass.* value's content shows the reviewer @@ -657,6 +680,12 @@ def _field_present(field_key: str, text: str, *, code_change_context: bool = Fal head = body.strip().split("\n", 1)[0].strip().lower() if head in _HONEST_EMPTY_MARKERS: return not code_change_context + # hermes-jarvis#75: accept ``- not_applicable: <≥8 char reason>`` + # as a bullet equivalent of the inline honest-empty markers. + # Same code_change_context rule applies — code-touching reviews + # must produce real citations, not honest-empty escapes. + if _BULLET_NOT_APPLICABLE_RE.search(body): + return not code_change_context return bool(_EVIDENCE_CITATION_RE.search(body)) # Other multi-key fields (currently none) fall through. diff --git a/hermes_cli/kanban_db.py b/hermes_cli/kanban_db.py index 2478f76ed3ff..d6422eb65896 100644 --- a/hermes_cli/kanban_db.py +++ b/hermes_cli/kanban_db.py @@ -3678,12 +3678,18 @@ def _v6_7_run_completion_gates( no_reviewer_fields = no_rf_reason is not None phantom_ok = phantom_ok_reason is not None doc_drift_ok = doc_drift_reason is not None + # hermes-jarvis#74: honest-reject completion bypasses the floor for + # reviewers. Parsing the verdict here once so both the floor gate + # and any future gate can use it. + parsed_verdict = _v6_7_parse_verdict(result) + is_honest_reject = parsed_verdict == "reject" violations: list = [] floor = verify_runtime_floor( assignee=row["assignee"], started_at=row["started_at"], completed_at=now, allow_below_floor=fast_ok, + is_honest_reject=is_honest_reject, ) if floor is not None: violations.append(floor) diff --git a/tests/cli/test_kanban_completion_gates.py b/tests/cli/test_kanban_completion_gates.py index 8449c149b437..e5cdf76a4165 100644 --- a/tests/cli/test_kanban_completion_gates.py +++ b/tests/cli/test_kanban_completion_gates.py @@ -90,6 +90,64 @@ def test_case_insensitive_role_match(self) -> None: v = verify_runtime_floor("Tony", 1000, 1020) assert v is not None + # === hermes-jarvis#74 — honest-reject bypass === + + def test_honest_reject_bypasses_floor_for_tony(self) -> None: + """The exact 2026-06-10 case: Tony correctly identified bugs in + 20s, verdict was reject, but the 90s floor blocked the + completion 6+ times. Now honest reject bypasses the floor.""" + v = verify_runtime_floor( + "tony", 1000, 1020, # 20s, way under 90s floor + is_honest_reject=True, + ) + assert v is None + + def test_honest_reject_bypasses_floor_for_tchalla(self) -> None: + v = verify_runtime_floor( + "tchalla", 1000, 1015, + is_honest_reject=True, + ) + assert v is None + + def test_honest_reject_bypasses_floor_for_vision(self) -> None: + v = verify_runtime_floor( + "vision", 1000, 1015, + is_honest_reject=True, + ) + assert v is None + + def test_approve_under_floor_still_rejects(self) -> None: + """The floor is designed against rubber-stamp APPROVES. + is_honest_reject=False (verdict is approve / no verdict / etc.) + must NOT bypass.""" + v = verify_runtime_floor( + "tony", 1000, 1020, + is_honest_reject=False, + ) + assert v is not None + assert v.floor_seconds == 90 + + def test_honest_reject_does_NOT_bypass_floor_for_build_role(self) -> None: + """The bypass is reviewer-scoped. A friday/shuri 60-second + 'implementation' that ends in reject still warrants the + 5-minute floor — a fast reject from a build role isn't a sign + of work done, just of work bailed on.""" + v = verify_runtime_floor( + "friday", 1000, 1060, # 60s, way under 5min floor + is_honest_reject=True, + ) + assert v is not None + assert v.floor_seconds == 300 + + def test_honest_reject_with_unknown_role_still_passes(self) -> None: + """Unknown roles already have no floor; the bypass changes + nothing.""" + v = verify_runtime_floor( + "rando-profile", 1000, 1010, + is_honest_reject=True, + ) + assert v is None + # ===================================================================== # verify_workspace_diff — #62 @@ -1454,3 +1512,216 @@ def test_markdown_link_url_extracted(self) -> None: "See [PR #42](https://github.com/o/r/pull/42) for details" ) assert urls == ["https://github.com/o/r/pull/42"] + + +# ===================================================================== +# hermes-jarvis#75 — bullet `not_applicable: ` for evidence +# ===================================================================== + + +class TestBulletNotApplicableEvidence: + """The exact 2026-06-10 validation case: Tony wrote + `evidence:\\n - not_applicable: no tests directory...` declaring + honestly that no tests existed. The regex only accepted inline + `evidence: none` so verbose-but-correct form was rejected. Closes + hermes-jarvis#75.""" + + def test_bullet_not_applicable_accepted_on_docs_review(self) -> None: + """The Tony-2026-06-10 case: bullet `not_applicable: ` + passes when body doesn't trigger code_change_context.""" + verdict = """\ +verdict: reject + +test_quality: + imports_match_deliverable_entrypoints: false + evidence: + - not_applicable: no tests directory or *.test.* files reference swarm status +""" + v = verify_reviewer_fields( + assignee="tony", + body="Review the Friday widget deliverable", + result=verdict, + ) + assert v is None + + def test_bullet_not_applicable_blocked_on_code_review(self) -> None: + """For code-touching reviews (body triggers adversarial), + evidence honest-empty escapes are NOT accepted — same rule + already applied to inline `evidence: none`.""" + verdict = """\ +verdict: approve + +test_quality: + imports_match_deliverable_entrypoints: true + evidence: + - not_applicable: no tests for this route handler + +adversarial_pass: + env_vars: [] + request_inputs: [] + file_paths: [] + external_io: [] +""" + v = verify_reviewer_fields( + assignee="tony", + body="Review changes to app/api/metrics/route.ts", + result=verdict, + ) + assert v is not None + assert "test_quality.evidence" in v.missing_fields + + def test_bullet_not_applicable_with_short_reason_rejected(self) -> None: + """Reason must be ≥8 chars (matching the existing imports_match + not_applicable rule). Short reasons should be rejected so + workers don't bypass with `not_applicable: ok`.""" + verdict = """\ +verdict: reject + +test_quality: + imports_match_deliverable_entrypoints: false + evidence: + - not_applicable: ok +""" + v = verify_reviewer_fields( + assignee="tony", + body="Review the deliverable", + result=verdict, + ) + assert v is not None + assert "test_quality.evidence" in v.missing_fields + + def test_bullet_not_applicable_with_empty_reason_rejected(self) -> None: + verdict = """\ +verdict: reject + +test_quality: + imports_match_deliverable_entrypoints: false + evidence: + - not_applicable: +""" + v = verify_reviewer_fields( + assignee="tony", + body="Review the deliverable", + result=verdict, + ) + assert v is not None + assert "test_quality.evidence" in v.missing_fields + + def test_real_citations_still_pass(self) -> None: + """Backwards-compat: a real bullet-list of citations still + passes (the canonical happy path).""" + verdict = """\ +verdict: approve + +test_quality: + imports_match_deliverable_entrypoints: true + evidence: + - tests/integration.test.ts:42 calls app/api/metrics/route.ts:GET +""" + v = verify_reviewer_fields( + assignee="tony", + body="Review changes to lib/foo.ts", + result=verdict, + ) + assert v is None + + def test_inline_evidence_none_still_works(self) -> None: + """Backwards-compat: inline `evidence: none` (the original + honest-empty form) continues to work on non-code reviews.""" + verdict = """\ +verdict: reject + +test_quality: + imports_match_deliverable_entrypoints: not_applicable: pure docs reshuffle + evidence: none +""" + v = verify_reviewer_fields( + assignee="tony", + body="Review the README rewrite", + result=verdict, + ) + assert v is None + + def test_bullet_with_other_bullets_still_accepted(self) -> None: + """A reviewer who lists 2 real citations AND one + not_applicable bullet should still pass — the not_applicable + is the escape hatch, not a requirement.""" + verdict = """\ +verdict: approve + +test_quality: + imports_match_deliverable_entrypoints: true + evidence: + - tests/integration.test.ts:42 calls lib/foo.ts:bar + - tests/unit.test.ts:88 covers lib/foo.ts:baz +""" + v = verify_reviewer_fields( + assignee="tony", + body="Review the deliverable", + result=verdict, + ) + assert v is None + + +# ===================================================================== +# hermes-jarvis#74 — honest-reject floor bypass (integration) +# ===================================================================== + + +class TestHonestRejectIntegration: + """End-to-end through complete_task: a reviewer who completes fast + with `verdict: reject` should not trip the runtime-floor gate.""" + + @pytest.fixture + def tony_running_task(self, tmp_path, monkeypatch): + monkeypatch.setenv("HERMES_HOME", str(tmp_path)) + monkeypatch.setenv("HERMES_KANBAN_DB", str(tmp_path / "k.db")) + conn = kb.connect(board="default") + import time + now = int(time.time()) + # started_at = now-20s, way under the 90s reviewer floor + conn.execute( + "INSERT INTO tasks (id, title, body, status, assignee, started_at, " + " created_at, workspace_kind, workspace_path) " + "VALUES ('t_rev', 'Tony fast review', 'Review the docs reshuffle', " + " 'running', 'tony', ?, ?, 'scratch', NULL)", + (now - 20, now), + ) + conn.commit() + yield conn + conn.close() + + def test_tony_fast_reject_passes_floor(self, tony_running_task) -> None: + """The 2026-06-10 case end-to-end: Tony submits a structured + reject in 20s. Old behavior: rejected by floor. New: accepted.""" + verdict = """\ +verdict: reject + +test_quality: + imports_match_deliverable_entrypoints: false + evidence: + - not_applicable: docs-only review, no tests cover the deliverable +""" + ok = kb.complete_task( + tony_running_task, "t_rev", + summary="reject — docs review", result=verdict, + ) + assert ok + + def test_tony_fast_approve_still_blocked_by_floor( + self, tony_running_task, + ) -> None: + """The other half of the bypass: approves still need the floor + (rubber-stamp protection unchanged).""" + good_verdict = """\ +verdict: approve + +test_quality: + imports_match_deliverable_entrypoints: not_applicable: pure docs + evidence: none +""" + with pytest.raises(kb.CompletionGateError): + kb.complete_task( + tony_running_task, "t_rev", + summary="approve docs", result=good_verdict, + ) From a3cb9657697ab4da250741bfcb32fc6160892762 Mon Sep 17 00:00:00 2001 From: Jarvis Date: Thu, 11 Jun 2026 09:50:41 -0700 Subject: [PATCH 2/3] =?UTF-8?q?fix(kanban):=20Part=202=20self-review=20?= =?UTF-8?q?=E2=80=94=20tighten=20regex=20to=20=E2=89=A58,=20add=20gap=20te?= =?UTF-8?q?sts,=20document=20edit-result=20caveat?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Independent review of v6.8 Part 2 flagged 3 polish items. All addressed. ## Regex tightening (#75 follow-up) The bullet-not-applicable regex `\S.{6,}` was accepting reasons of 7 chars (1 + 6) but the docstring and tests asserted ≥8 chars to match the `imports_match_deliverable_entrypoints` invariant. Tightened to `\S.{7,}` so 1 + 7 = 8 chars minimum. Same as the imports_match length check, no off-by-one. ## Integration test gaps (#74 follow-up) Reviewer flagged two missing integration cases: - Bare `verdict: reject` with no test_quality fields: floor bypasses (because verdict IS reject) but reviewer-fields gate catches the missing discipline. New test asserts the gate that catches it is MissingReviewerFieldViolation, NOT RuntimeFloorViolation. This proves the bypass doesn't accidentally green-light no-evidence rejects. - No verdict at all: parsed_verdict is None, is_honest_reject is False, floor fires normally. New test asserts a result like "Just my thoughts, no verdict line." doesn't silently bypass the floor. ## edit_completed_task_result documentation The reviewer noted this function exists at kanban_db.py:4180 to back- fill results on done tasks (via `hermes kanban edit`). It bypasses EVERY v6.7/v6.8 gate. If a future maintainer wires it into a worker tool surface, every gate becomes bypassable: complete with low- discipline, edit to high-discipline, or flip approve↔reject after the gate already ran. Added a multi-line .warning:: docstring listing every gate it bypasses and the specific attacks a worker-accessible wiring would enable. No code change — this is a comment-level guard for human ops. 107/107 in test_kanban_completion_gates.py pass (105 + 2 new). Co-Authored-By: Claude Opus 4.7 (1M context) --- hermes_cli/kanban_completion_gates.py | 7 +++-- hermes_cli/kanban_db.py | 25 +++++++++++++++- tests/cli/test_kanban_completion_gates.py | 36 +++++++++++++++++++++++ tools/kanban_tools.py | 4 +++ 4 files changed, 68 insertions(+), 4 deletions(-) diff --git a/hermes_cli/kanban_completion_gates.py b/hermes_cli/kanban_completion_gates.py index fe6f787b7df3..f7635bb4b8ca 100644 --- a/hermes_cli/kanban_completion_gates.py +++ b/hermes_cli/kanban_completion_gates.py @@ -606,10 +606,11 @@ def _has_adversarial_structure(text: str) -> bool: _HONEST_EMPTY_MARKERS = {"none", "[]", "{}", "n/a"} # hermes-jarvis#75: bullet form of honest-empty for test_quality.evidence. -# The reason must be ≥8 chars (non-whitespace), mirroring the -# ``not_applicable`` shape already accepted for imports_match. +# The reason must be ≥8 chars (1 non-whitespace + at least 7 more +# characters), mirroring the ``not_applicable`` shape already accepted +# for imports_match. _BULLET_NOT_APPLICABLE_RE = re.compile( - r"(?mi)^\s*-\s*not_applicable\s*:\s*(\S.{6,})\s*$", + r"(?mi)^\s*-\s*not_applicable\s*:\s*(\S.{7,})\s*$", ) diff --git a/hermes_cli/kanban_db.py b/hermes_cli/kanban_db.py index d6422eb65896..8f537570fe1e 100644 --- a/hermes_cli/kanban_db.py +++ b/hermes_cli/kanban_db.py @@ -4185,7 +4185,30 @@ def edit_completed_task_result( summary: Optional[str] = None, metadata: Optional[dict] = None, ) -> bool: - """Backfill the user-visible result for an already completed task.""" + """Backfill the user-visible result for an already completed task. + + .. warning:: + + This bypasses every v6.7/v6.8 verification gate (runtime + floor, workspace-diff, repo hygiene, reviewer fields, + adversarial pass, PR existence, doc drift, honest-reject + verdict parser, integrative-review-spawn). It exists for + human/CLI ops on a finished task (``hermes kanban edit``); + do NOT wire it into a worker tool surface or any agent + accessible path. Doing so would let a worker: + + - Complete a task with a low-discipline result, then + re-write to a high-discipline one (or vice-versa) to + satisfy whatever gate ran first. + - Flip ``verdict: approve`` → ``verdict: reject`` after the + gate path already accepted the result. + - Insert phantom PR URLs that the gate already verified + against at completion time. + + If you need worker access to amend a completed result, run + the full gate pipeline (``_v6_7_run_completion_gates``) + against the new content before the UPDATE. + """ handoff_summary = summary if summary is not None else result with write_txn(conn): row = conn.execute( diff --git a/tests/cli/test_kanban_completion_gates.py b/tests/cli/test_kanban_completion_gates.py index e5cdf76a4165..d4a8783d15de 100644 --- a/tests/cli/test_kanban_completion_gates.py +++ b/tests/cli/test_kanban_completion_gates.py @@ -1725,3 +1725,39 @@ def test_tony_fast_approve_still_blocked_by_floor( tony_running_task, "t_rev", summary="approve docs", result=good_verdict, ) + + def test_tony_fast_bare_reject_still_blocks_on_reviewer_fields( + self, tony_running_task, + ) -> None: + """The floor bypass for honest-reject doesn't accidentally + green-light no-evidence rejects. A reviewer who submits + `verdict: reject` in 20s with NO structured test_quality + fields should still trip the reviewer-fields gate. The floor + gate passes (because verdict is reject) but reviewer-fields + catches the missing discipline.""" + with pytest.raises(kb.CompletionGateError) as excinfo: + kb.complete_task( + tony_running_task, "t_rev", + summary="bare reject", result="verdict: reject\n", + ) + # Verify it was the REVIEWER-FIELDS gate that caught it, not + # the runtime-floor gate. + violations = excinfo.value.violations + kinds = {type(v).__name__ for v in violations} + assert "MissingReviewerFieldViolation" in kinds + assert "RuntimeFloorViolation" not in kinds + + def test_tony_no_verdict_still_subject_to_floor( + self, tony_running_task, + ) -> None: + """A null verdict (no canonical `verdict: ...` line) must NOT + be treated as honest reject — that would let any reviewer + bypass the floor by simply omitting a verdict. parsed_verdict + is None, is_honest_reject is False, floor fires normally.""" + with pytest.raises(kb.CompletionGateError) as excinfo: + kb.complete_task( + tony_running_task, "t_rev", + summary="no verdict", result="Just my thoughts, no verdict line.", + ) + kinds = {type(v).__name__ for v in excinfo.value.violations} + assert "RuntimeFloorViolation" in kinds diff --git a/tools/kanban_tools.py b/tools/kanban_tools.py index ea40cfdb43e9..9d8377b7c88e 100644 --- a/tools/kanban_tools.py +++ b/tools/kanban_tools.py @@ -548,6 +548,10 @@ def _handle_complete(args: dict, **kw) -> str: return tool_error( f"metadata must be an object/dict, got {type(metadata).__name__}" ) + # Wave A #28 hygiene gate (dispatcher enforcement) + hygiene_err = _enforce_kanban_complete_hygiene(tid, summary, metadata, artifacts) + if hygiene_err: + return hygiene_err metadata = _stamp_worker_session_metadata(tid, metadata) board = args.get("board") try: From 3ae8c7a1a91f7847877d0363ebbfc178ac78b1a2 Mon Sep 17 00:00:00 2001 From: Jarvis Date: Thu, 11 Jun 2026 10:10:08 -0700 Subject: [PATCH 3/3] =?UTF-8?q?fix(kanban):=20pre-merge=20sanity=20?= =?UTF-8?q?=E2=80=94=20remove=20stale=20dangling-reference=20in=20kanban?= =?UTF-8?q?=5Ftools?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous self-review commit accidentally included 4 lines from an uncommitted local edit (likely from an unrelated v6.7 swarm session — comment said "Wave A #28 hygiene gate (dispatcher enforcement)"). Those lines called ``_enforce_kanban_complete_hygiene``, a function that doesn't exist anywhere in the codebase — it would NameError on every kanban_complete tool call. Caught by diff review before squash-merge. Removed. 137/137 tests still pass after removal. tools.kanban_tools imports cleanly. Co-Authored-By: Claude Opus 4.7 (1M context) --- tools/kanban_tools.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tools/kanban_tools.py b/tools/kanban_tools.py index 9d8377b7c88e..ea40cfdb43e9 100644 --- a/tools/kanban_tools.py +++ b/tools/kanban_tools.py @@ -548,10 +548,6 @@ def _handle_complete(args: dict, **kw) -> str: return tool_error( f"metadata must be an object/dict, got {type(metadata).__name__}" ) - # Wave A #28 hygiene gate (dispatcher enforcement) - hygiene_err = _enforce_kanban_complete_hygiene(tid, summary, metadata, artifacts) - if hygiene_err: - return hygiene_err metadata = _stamp_worker_session_metadata(tid, metadata) board = args.get("board") try: