From 496c6d7fe73cabb591e5c55149a1c48275a82aac Mon Sep 17 00:00:00 2001 From: shaun0927 <70629228+shaun0927@users.noreply.github.com> Date: Mon, 25 May 2026 18:34:17 +0900 Subject: [PATCH 1/3] fix(auto): honor prompt-declared non_goals in unsafe-context matcher MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `_unsafe_context_reason` already excludes ledger NON_GOAL entries because confirmed non-goals are explicit exclusions, but that exclusion only fires after the interview has structured them into the ledger. Callers that pre-declare their non-goals in the free-form goal string — handoff prompts and scripted `ooo auto` invocations that bundle the seven canonical interview slots in the request body — leak those exclusion words into the matcher before the interview can register them, flipping the gate into "ambiguous external side effect" on the user's own exclusion text. Add a line-anchored `_strip_prompt_non_goal_sections` pre-pass that drops any `non_goals:` / `non-goals:` / `non goals:` / `excludes:` / `out-of-scope:` section (including bullet-list bodies) from the goal string before unsafe-context matching. Inline prose mentions of "non-goals" without a trailing colon are intentionally untouched. Repro from `ouroboros-plugins` Issue #28 (Superpowers AgentOS L3a): ouroboros auto --skip-run --max-interview-rounds 8 \ "Add bounded retry to a network client. non_goals: implementing a production deploy, ... constraints: filesystem:read and filesystem:write only; ..." Before this fix the matcher fires on "deploy" inside `non_goals:` at round 8, marks every gap as unsafe, and blocks with `auto.interview.safe_default.unsafe_context_match pattern_name='ambiguous external side effect'`. After this fix the same prompt closes through the normal safe-default path because the non-goals body is excluded from matcher input — matching what the NON_GOAL ledger exclusion already promises. Tests: 17 new cases in `tests/unit/auto/test_safe_defaults_prompt_non_goals.py` covering header variants, bullet-list bodies, blank-line termination, inline-prose preservation, idempotency, and four `_unsafe_context_reason` integration cases. Existing 153 safe-defaults / interview tests still pass. --- src/ouroboros/auto/safe_defaults.py | 93 +++++++++- .../test_safe_defaults_prompt_non_goals.py | 167 ++++++++++++++++++ 2 files changed, 256 insertions(+), 4 deletions(-) create mode 100644 tests/unit/auto/test_safe_defaults_prompt_non_goals.py diff --git a/src/ouroboros/auto/safe_defaults.py b/src/ouroboros/auto/safe_defaults.py index c59d1a539..1349a4497 100644 --- a/src/ouroboros/auto/safe_defaults.py +++ b/src/ouroboros/auto/safe_defaults.py @@ -104,6 +104,27 @@ def _is_valid_default_spec(spec: _DefaultSpec) -> bool: ), } + +# Line-anchored marker for a user-declared non-goal / exclusion section in +# a free-form goal string. Examples that match: +# ``non_goals: …`` ``non-goals: …`` ``Non Goals: …`` +# ``excludes: …`` ``Out-of-scope: …`` ``- non_goals:`` +# The trailing colon is required so that prose that merely mentions +# ``non-goals`` in a sentence is not mistaken for a section header. +_PROMPT_NON_GOAL_HEADER = re.compile( + r"^\s*(?:[-*•]\s+)?(?:non[ _-]?goals?|excludes?|out[ _-]?of[ _-]?scope)\s*:", + re.IGNORECASE, +) + +# Any other line-anchored ``