-
Notifications
You must be signed in to change notification settings - Fork 0
chore: sync workflow templates #762
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,116 @@ | ||
| #!/usr/bin/env python3 | ||
| """Extract an unspoofable verifier verdict from structured agent output.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import argparse | ||
| import json | ||
| import re | ||
| from pathlib import Path | ||
| from typing import Any | ||
|
|
||
| VERDICT_RE = re.compile( | ||
| r"\b[\"']?verdict[\"']?\s*:\s*[\"']?(pass|fail)[\"']?\b", | ||
| re.IGNORECASE, | ||
| ) | ||
| FENCED_BLOCK_RE = re.compile( | ||
| r"^[ \t]*```(?P<lang>[^\n`]*)\n(?P<body>.*?)^[ \t]*```[ \t]*$", | ||
| re.MULTILINE | re.DOTALL, | ||
| ) | ||
|
|
||
| VALID_VERDICTS = {"pass", "concerns", "fail", "error"} | ||
|
|
||
|
|
||
| def _normalize_verdict(value: object) -> str: | ||
| verdict = str(value or "").strip().lower().replace("_", "-") | ||
| if verdict in {"passed", "success"}: | ||
| return "pass" | ||
| if verdict in {"needs-review", "needs review", "review", "concern", "concerns"}: | ||
| return "concerns" | ||
| if verdict in {"failed", "failure"}: | ||
| return "fail" | ||
| return verdict if verdict in VALID_VERDICTS else "" | ||
|
|
||
|
|
||
| def _diff_regions(markdown: str) -> list[str]: | ||
| regions: list[str] = [] | ||
| for match in FENCED_BLOCK_RE.finditer(markdown): | ||
| lang = match.group("lang").strip().lower() | ||
| if lang in {"diff", "patch"}: | ||
| regions.append(match.group("body")) | ||
| return regions | ||
|
|
||
|
|
||
| def _without_diff_regions(markdown: str) -> str: | ||
| def replace(match: re.Match[str]) -> str: | ||
| lang = match.group("lang").strip().lower() | ||
| return "\n" if lang in {"diff", "patch"} else match.group(0) | ||
|
|
||
| return FENCED_BLOCK_RE.sub(replace, markdown) | ||
|
|
||
|
|
||
| def _json_candidates(markdown: str) -> list[dict[str, Any]]: | ||
| candidates: list[dict[str, Any]] = [] | ||
| for match in FENCED_BLOCK_RE.finditer(markdown): | ||
| if match.group("lang").strip().lower() != "json": | ||
| continue | ||
| try: | ||
| parsed = json.loads(match.group("body")) | ||
| except json.JSONDecodeError: | ||
| continue | ||
| if isinstance(parsed, dict): | ||
| candidates.append(parsed) | ||
|
|
||
| return candidates | ||
|
|
||
|
|
||
| def build_verdict(output: str) -> dict[str, Any]: | ||
| for region in _diff_regions(output): | ||
| if VERDICT_RE.search(region): | ||
| return { | ||
| "verdict": "fail", | ||
| "source": "diff-tamper", | ||
| "needs_attention": True, | ||
| "reason": "verdict marker appeared inside a diff/patch region", | ||
| } | ||
|
|
||
| output_without_diff = _without_diff_regions(output) | ||
| for candidate in _json_candidates(output_without_diff): | ||
| verdict = _normalize_verdict(candidate.get("verdict")) | ||
| if not verdict: | ||
| continue | ||
| return { | ||
| **candidate, | ||
| "verdict": verdict, | ||
| "source": "structured-json", | ||
| "needs_attention": bool(candidate.get("needs_attention", verdict != "pass")), | ||
| } | ||
|
|
||
| return { | ||
| "verdict": "error", | ||
| "source": "missing-structured-json", | ||
| "needs_attention": True, | ||
| "reason": "no structured verifier JSON verdict found outside diff regions", | ||
| } | ||
|
|
||
|
|
||
| def main() -> int: | ||
| parser = argparse.ArgumentParser() | ||
| parser.add_argument("--output", required=True, help="Verifier agent output markdown") | ||
| parser.add_argument("--json", required=True, help="Destination verdict JSON path") | ||
| args = parser.parse_args() | ||
|
|
||
| output = Path(args.output).read_text(encoding="utf-8") if Path(args.output).is_file() else "" | ||
| verdict = build_verdict(output) | ||
| destination = Path(args.json) | ||
| destination.parent.mkdir(parents=True, exist_ok=True) | ||
| destination.write_text(json.dumps(verdict, sort_keys=True) + "\n", encoding="utf-8") | ||
| print(f"verdict={verdict['verdict']}") | ||
| print(f"source={verdict['source']}") | ||
| if verdict.get("reason"): | ||
| print(f"reason={verdict['reason']}") | ||
| return 0 | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| raise SystemExit(main()) | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -37,16 +37,24 @@ on: | |||||||||||||||||
| required: false | ||||||||||||||||||
| default: false | ||||||||||||||||||
| type: boolean | ||||||||||||||||||
| max_parallel: | ||||||||||||||||||
| description: 'Maximum concurrent worker runs permitted' | ||||||||||||||||||
| required: false | ||||||||||||||||||
| default: '1' | ||||||||||||||||||
| type: string | ||||||||||||||||||
| keepalive: | ||||||||||||||||||
| description: 'True when invocation originates from a keepalive sweep' | ||||||||||||||||||
| required: false | ||||||||||||||||||
| default: false | ||||||||||||||||||
| type: boolean | ||||||||||||||||||
| orchestrator_skill_pack: | ||||||||||||||||||
| description: >- | ||||||||||||||||||
| Optional reference-pack name override for exported Orchestrator skill context on | ||||||||||||||||||
| downstream Codex opener/closer runs (repo config remains primary). | ||||||||||||||||||
| required: false | ||||||||||||||||||
| default: '' | ||||||||||||||||||
| type: string | ||||||||||||||||||
| orchestrator_skill_enabled: | ||||||||||||||||||
| description: >- | ||||||||||||||||||
| Optional enabled override for exported Orchestrator skill context (true/false). | ||||||||||||||||||
| required: false | ||||||||||||||||||
| default: '' | ||||||||||||||||||
| type: string | ||||||||||||||||||
|
|
||||||||||||||||||
| permissions: | ||||||||||||||||||
| contents: write | ||||||||||||||||||
|
|
@@ -66,8 +74,10 @@ jobs: | |||||||||||||||||
| source: ${{ inputs.source }} | ||||||||||||||||||
| dry_run: ${{ inputs.dry_run }} | ||||||||||||||||||
| use_step_branch: ${{ inputs.use_step_branch }} | ||||||||||||||||||
| max_parallel: ${{ fromJSON(inputs.max_parallel) }} | ||||||||||||||||||
| max_parallel: 1 | ||||||||||||||||||
| keepalive: ${{ inputs.keepalive }} | ||||||||||||||||||
| orchestrator_skill_pack: ${{ inputs.orchestrator_skill_pack }} | ||||||||||||||||||
| orchestrator_skill_enabled: ${{ inputs.orchestrator_skill_enabled }} | ||||||||||||||||||
|
Comment on lines
+77
to
+80
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Line 77 hard-codes Suggested fix (template-side)- max_parallel: 1
+ max_parallel: ${{ fromJSON(inputs.max_parallel) }}As per coding guidelines, " 📝 Committable suggestion
Suggested change
🤖 Prompt for AI AgentsSources: Coding guidelines, Linked repositories |
||||||||||||||||||
| secrets: | ||||||||||||||||||
| WORKFLOWS_APP_ID: ${{ secrets.WORKFLOWS_APP_ID }} | ||||||||||||||||||
| WORKFLOWS_APP_PRIVATE_KEY: ${{ secrets.WORKFLOWS_APP_PRIVATE_KEY }} | ||||||||||||||||||
|
|
||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial | 💤 Low value
Consider explicit error handling for path edge cases.
If
args.outputpoints to a directory (exists but not a file), this silently treats it as empty output, which could mask configuration errors in CI pipelines. An explicit error might be more helpful for debugging.That said, if the current behavior of returning an
errorverdict withmissing-structured-jsonsource is intentional for graceful degradation in cases where verifier output doesn't exist, feel free to dismiss this.📝 Committable suggestion
🤖 Prompt for AI Agents