From ac84044ee64017034bf641934ad8593dcd55d3f1 Mon Sep 17 00:00:00 2001 From: samson910022 Date: Thu, 10 Sep 2026 03:43:45 +0800 Subject: [PATCH 1/3] fix(ci): secrets context not allowed in step if, use outputs --- .github/workflows/ai-fix.yml | 18 ++++++++++++++++-- .github/workflows/ai-review.yml | 19 +++++++++++++++++-- .github/workflows/ai-scan.yml | 18 ++++++++++++++++-- 3 files changed, 49 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ai-fix.yml b/.github/workflows/ai-fix.yml index 87a54c9..8829306 100644 --- a/.github/workflows/ai-fix.yml +++ b/.github/workflows/ai-fix.yml @@ -68,15 +68,29 @@ jobs: echo "::warning::No CPA credentials; CPA fallback channel disabled (gateway only)." fi + - name: Detect gateway source + id: gw + shell: bash + env: + GATEWAY_BASE_URL: ${{ secrets.GATEWAY_BASE_URL }} + run: | + set -euo pipefail + # secrets context is not allowed in step `if:`; expose via outputs. + if [[ -n "${GATEWAY_BASE_URL}" ]]; then + echo "external=true" >> "$GITHUB_OUTPUT" + else + echo "external=false" >> "$GITHUB_OUTPUT" + fi + - name: Set up Node - if: secrets.GATEWAY_BASE_URL == '' + if: steps.gw.outputs.external != 'true' uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: 24 cache: npm - name: Start in-job gateway (free models, no login needed) - if: secrets.GATEWAY_BASE_URL == '' + if: steps.gw.outputs.external != 'true' shell: bash run: | set -euo pipefail diff --git a/.github/workflows/ai-review.yml b/.github/workflows/ai-review.yml index 453f872..f177a30 100644 --- a/.github/workflows/ai-review.yml +++ b/.github/workflows/ai-review.yml @@ -102,15 +102,30 @@ jobs: echo "head_sha=${head_sha}" } >> "$GITHUB_OUTPUT" + - name: Detect gateway source + id: gw + if: steps.gate.outputs.skip != 'true' + shell: bash + env: + GATEWAY_BASE_URL: ${{ secrets.GATEWAY_BASE_URL }} + run: | + set -euo pipefail + # secrets context is not allowed in step `if:`; expose via outputs. + if [[ -n "${GATEWAY_BASE_URL}" ]]; then + echo "external=true" >> "$GITHUB_OUTPUT" + else + echo "external=false" >> "$GITHUB_OUTPUT" + fi + - name: Set up Node - if: steps.gate.outputs.skip != 'true' && secrets.GATEWAY_BASE_URL == '' + if: steps.gate.outputs.skip != 'true' && steps.gw.outputs.external != 'true' uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: 24 cache: npm - name: Start in-job gateway (free models, no login needed) - if: steps.gate.outputs.skip != 'true' && secrets.GATEWAY_BASE_URL == '' + if: steps.gate.outputs.skip != 'true' && steps.gw.outputs.external != 'true' shell: bash run: | set -euo pipefail diff --git a/.github/workflows/ai-scan.yml b/.github/workflows/ai-scan.yml index bbba7c7..c88ab15 100644 --- a/.github/workflows/ai-scan.yml +++ b/.github/workflows/ai-scan.yml @@ -59,15 +59,29 @@ jobs: echo "::warning::No CPA credentials; CPA fallback channel disabled (gateway only)." fi + - name: Detect gateway source + id: gw + shell: bash + env: + GATEWAY_BASE_URL: ${{ secrets.GATEWAY_BASE_URL }} + run: | + set -euo pipefail + # secrets context is not allowed in step `if:`; expose via outputs. + if [[ -n "${GATEWAY_BASE_URL}" ]]; then + echo "external=true" >> "$GITHUB_OUTPUT" + else + echo "external=false" >> "$GITHUB_OUTPUT" + fi + - name: Set up Node - if: secrets.GATEWAY_BASE_URL == '' + if: steps.gw.outputs.external != 'true' uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: 24 cache: npm - name: Start in-job gateway (free models, no login needed) - if: secrets.GATEWAY_BASE_URL == '' + if: steps.gw.outputs.external != 'true' shell: bash run: | set -euo pipefail From f95c4a3a48e752d528019f4b23700cdb514db780 Mon Sep 17 00:00:00 2001 From: samson910022 Date: Thu, 10 Sep 2026 03:53:52 +0800 Subject: [PATCH 2/3] fix(bot): tolerate bold VERDICT lines; always run gateway detect step --- .github/workflows/ai-review.yml | 2 +- github_bot/src/agent_orchestrator.py | 8 +++++--- tests/test_ai_bot.py | 11 +++++++++++ 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ai-review.yml b/.github/workflows/ai-review.yml index f177a30..abeed25 100644 --- a/.github/workflows/ai-review.yml +++ b/.github/workflows/ai-review.yml @@ -104,13 +104,13 @@ jobs: - name: Detect gateway source id: gw - if: steps.gate.outputs.skip != 'true' shell: bash env: GATEWAY_BASE_URL: ${{ secrets.GATEWAY_BASE_URL }} run: | set -euo pipefail # secrets context is not allowed in step `if:`; expose via outputs. + # Always runs (trivial check) so downstream never reads an absent output. if [[ -n "${GATEWAY_BASE_URL}" ]]; then echo "external=true" >> "$GITHUB_OUTPUT" else diff --git a/github_bot/src/agent_orchestrator.py b/github_bot/src/agent_orchestrator.py index 1f0326a..ad1138b 100644 --- a/github_bot/src/agent_orchestrator.py +++ b/github_bot/src/agent_orchestrator.py @@ -414,11 +414,13 @@ def run_multi_agent_review(self, ctx: ReviewContext) -> str: "response_ids": [metas[r].get("response_id", "n/a") for r in pipeline if r in metas], "roles": {r: metas.get(r, {}) for r in pipeline}, } - # Only the VERDICT: line counts. Prose mentions of NEEDS_CHANGES must - # not flip the outcome; unparseable outputs degrade to COMMENT. + # Only the VERDICT: line counts (tolerating markdown bold like + # **VERDICT: APPROVE**). Prose mentions of NEEDS_CHANGES must not flip + # the outcome; unparseable outputs degrade to COMMENT. verdict = "APPROVE" for text in results.values(): - m = re.search(r"(?m)^\s*VERDICT\s*:\s*(APPROVE|NEEDS_CHANGES|COMMENT)", text.upper()) + m = re.search(r"(?m)^\s*\*{0,2}\s*VERDICT\s*:\s*(APPROVE|NEEDS_CHANGES|COMMENT)\b", + text.upper()) parsed = m.group(1) if m else "COMMENT" if parsed == "NEEDS_CHANGES": verdict = "NEEDS_CHANGES" diff --git a/tests/test_ai_bot.py b/tests/test_ai_bot.py index 54f2110..b2b7fef 100644 --- a/tests/test_ai_bot.py +++ b/tests/test_ai_bot.py @@ -306,6 +306,17 @@ def mixed(role, prompt, **kw): report3 = AgentOrchestrator.run_multi_agent_review(orch, review_ctx) self.assertIn("FINAL_VERDICT: NEEDS_CHANGES", report3) + def test_verdict_aggregation_bold_markdown(self): + # Live models wrap the verdict in bold: **VERDICT: APPROVE**. + orch = AgentOrchestrator.__new__(AgentOrchestrator) + orch.config = load_json(BOT_DIR / "config" / "bot_config.json") + import agent_orchestrator as ao + review_ctx = ao.ReviewContext(title="t", body="b") + orch._run_role = lambda role, prompt, **kw: ( + "**VERDICT: APPROVE**\n\nAll good.", {"response_id": "r1"}) + report = AgentOrchestrator.run_multi_agent_review(orch, review_ctx) + self.assertIn("FINAL_VERDICT: APPROVE", report) + class TestScanAndRunner(unittest.TestCase): def test_fingerprint_stable(self): From e03b48ed51055b4b938b4c6643c17563c47abfc2 Mon Sep 17 00:00:00 2001 From: samson910022 Date: Thu, 10 Sep 2026 03:57:52 +0800 Subject: [PATCH 3/3] fix(bot): verdict regex tolerates bold-italic; top-level ReviewContext import --- github_bot/src/agent_orchestrator.py | 2 +- tests/test_ai_bot.py | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/github_bot/src/agent_orchestrator.py b/github_bot/src/agent_orchestrator.py index ad1138b..49ed49a 100644 --- a/github_bot/src/agent_orchestrator.py +++ b/github_bot/src/agent_orchestrator.py @@ -419,7 +419,7 @@ def run_multi_agent_review(self, ctx: ReviewContext) -> str: # the outcome; unparseable outputs degrade to COMMENT. verdict = "APPROVE" for text in results.values(): - m = re.search(r"(?m)^\s*\*{0,2}\s*VERDICT\s*:\s*(APPROVE|NEEDS_CHANGES|COMMENT)\b", + m = re.search(r"(?m)^\s*\*{0,3}\s*VERDICT\s*:\s*(APPROVE|NEEDS_CHANGES|COMMENT)\b", text.upper()) parsed = m.group(1) if m else "COMMENT" if parsed == "NEEDS_CHANGES": diff --git a/tests/test_ai_bot.py b/tests/test_ai_bot.py index b2b7fef..6c3a8bf 100644 --- a/tests/test_ai_bot.py +++ b/tests/test_ai_bot.py @@ -14,6 +14,7 @@ from agent_orchestrator import ( # noqa: E402 AgentOrchestrator, + ReviewContext, is_triage_report_publishable, scrub_internal_names, validate_triage_report, @@ -284,8 +285,7 @@ def test_verdict_aggregation_verdict_line_only(self): orch = AgentOrchestrator.__new__(AgentOrchestrator) orch.config = load_json(BOT_DIR / "config" / "bot_config.json") ctx = AgentOrchestrator.__new__(AgentOrchestrator) - import agent_orchestrator as ao - review_ctx = ao.ReviewContext(title="t", body="b") + review_ctx = ReviewContext(title="t", body="b") # Prose mention of NEEDS_CHANGES must not flip an APPROVE verdict. orch._run_role = lambda role, prompt, **kw: ( "VERDICT: APPROVE\nAll good, no NEEDS_CHANGES here.", {"response_id": "r1"}) @@ -308,14 +308,18 @@ def mixed(role, prompt, **kw): def test_verdict_aggregation_bold_markdown(self): # Live models wrap the verdict in bold: **VERDICT: APPROVE**. + # Bold-italic (***) is tolerated too. orch = AgentOrchestrator.__new__(AgentOrchestrator) orch.config = load_json(BOT_DIR / "config" / "bot_config.json") - import agent_orchestrator as ao - review_ctx = ao.ReviewContext(title="t", body="b") + review_ctx = ReviewContext(title="t", body="b") orch._run_role = lambda role, prompt, **kw: ( "**VERDICT: APPROVE**\n\nAll good.", {"response_id": "r1"}) report = AgentOrchestrator.run_multi_agent_review(orch, review_ctx) self.assertIn("FINAL_VERDICT: APPROVE", report) + orch._run_role = lambda role, prompt, **kw: ( + "***VERDICT: APPROVE***\n\nAll good.", {"response_id": "r2"}) + report2 = AgentOrchestrator.run_multi_agent_review(orch, review_ctx) + self.assertIn("FINAL_VERDICT: APPROVE", report2) class TestScanAndRunner(unittest.TestCase):