-
Notifications
You must be signed in to change notification settings - Fork 0
chore: sync workflow templates #620
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,161 @@ | ||
| #!/usr/bin/env python3 | ||
| """Warn when the managed Orchestrator AGENTS.md section cites stale repo facts.""" | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| import argparse | ||
| import json | ||
| import re | ||
| import shutil | ||
| import sys | ||
| from dataclasses import dataclass | ||
| from pathlib import Path | ||
|
|
||
| MANAGED_START = "<!-- BEGIN orch-playbook -->" | ||
| MANAGED_END = "<!-- END orch-playbook -->" | ||
| PATH_SUFFIXES = { | ||
| ".cfg", | ||
| ".ini", | ||
| ".js", | ||
| ".json", | ||
| ".md", | ||
| ".py", | ||
| ".sh", | ||
| ".toml", | ||
| ".txt", | ||
| ".yaml", | ||
| ".yml", | ||
| } | ||
|
|
||
|
|
||
| @dataclass(frozen=True) | ||
| class Finding: | ||
| kind: str | ||
| value: str | ||
| message: str | ||
|
|
||
| def as_dict(self) -> dict[str, str]: | ||
| return {"kind": self.kind, "value": self.value, "message": self.message} | ||
|
|
||
|
|
||
| def managed_section(text: str) -> str | None: | ||
| start = text.find(MANAGED_START) | ||
| end = text.find(MANAGED_END, start + len(MANAGED_START)) if start >= 0 else -1 | ||
| if start < 0 or end < 0 or end < start: | ||
| return None | ||
| return text[start : end + len(MANAGED_END)] | ||
|
|
||
|
|
||
| def _clean_ref(value: str) -> str: | ||
| value = value.strip().strip("\"'") | ||
| value = re.sub(r"[:#]L?\d+(?:-L?\d+)?$", "", value) | ||
| return value | ||
|
|
||
|
|
||
| def _looks_like_path(value: str) -> bool: | ||
| if value.startswith(("./", "../", ".github/", "docs/", "scripts/", "templates/", "tools/")): | ||
| return True | ||
| path = Path(value) | ||
| return "/" in value or path.suffix.lower() in PATH_SUFFIXES | ||
|
|
||
|
|
||
| def _path_exists(repo_root: Path, ref: str) -> bool: | ||
| return (repo_root / ref).exists() | ||
|
|
||
|
|
||
| def _command_exists(repo_root: Path, ref: str) -> bool: | ||
| parts = ref.split() | ||
| if not parts: | ||
|
Comment on lines
+67
to
+68
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. Use shell-aware tokenization for command refs.
Suggested fix import argparse
import json
import re
+import shlex
import shutil
import sys
@@
def _command_exists(repo_root: Path, ref: str) -> bool:
- parts = ref.split()
+ try:
+ parts = shlex.split(ref)
+ except ValueError:
+ parts = ref.split()
@@
def _check_command_ref(repo_root: Path, ref: str) -> list[Finding]:
findings: list[Finding] = []
@@
- for arg in ref.split()[1:]:
+ try:
+ args = shlex.split(ref)[1:]
+ except ValueError:
+ args = ref.split()[1:]
+ for arg in args:Also applies to: 80-80 🤖 Prompt for AI Agents |
||
| return True | ||
| command = parts[0] | ||
| if command.startswith(("./", "../")) or "/" in command: | ||
| return (repo_root / command).exists() | ||
| return shutil.which(command) is not None | ||
|
|
||
|
|
||
| def _check_command_ref(repo_root: Path, ref: str) -> list[Finding]: | ||
| findings: list[Finding] = [] | ||
| if not _command_exists(repo_root, ref): | ||
| findings.append(Finding("command", ref, f"referenced command not found: {ref}")) | ||
| for arg in ref.split()[1:]: | ||
| arg = _clean_ref(arg) | ||
| if "=" in arg: | ||
| _, arg = arg.split("=", 1) | ||
| arg = _clean_ref(arg) | ||
| if _looks_like_path(arg) and not _path_exists(repo_root, arg): | ||
| findings.append(Finding("path", arg, f"referenced path not found: {arg}")) | ||
| return findings | ||
|
|
||
|
|
||
| def cited_refs(section: str) -> list[str]: | ||
| refs: list[str] = [] | ||
| for raw in re.findall(r"`([^`]+)`", section): | ||
| value = _clean_ref(raw) | ||
| if not value or value.startswith(("http://", "https://")): | ||
| continue | ||
| refs.append(value) | ||
| return refs | ||
|
|
||
|
|
||
| def check_agents_md(repo_root: Path, agents_md: Path | None = None) -> list[Finding]: | ||
| agents_path = agents_md or repo_root / "AGENTS.md" | ||
| if not agents_path.exists(): | ||
| return [] | ||
| section = managed_section(agents_path.read_text(encoding="utf-8")) | ||
| if section is None: | ||
| return [] | ||
|
|
||
| findings: list[Finding] = [] | ||
| seen: set[tuple[str, str]] = set() | ||
| for ref in cited_refs(section): | ||
| if " " in ref: | ||
| for finding in _check_command_ref(repo_root, ref): | ||
| key = (finding.kind, finding.value) | ||
| if key not in seen: | ||
| findings.append(finding) | ||
| seen.add(key) | ||
| elif _looks_like_path(ref): | ||
| key = ("path", ref) | ||
| if key not in seen and not _path_exists(repo_root, ref): | ||
| findings.append(Finding("path", ref, f"referenced path not found: {ref}")) | ||
| seen.add(key) | ||
| return findings | ||
|
|
||
|
|
||
| def _emit_github_warnings(findings: list[Finding]) -> None: | ||
| for finding in findings: | ||
| message = finding.message.replace("%", "%25").replace("\n", "%0A").replace("\r", "%0D") | ||
| print(f"::warning title=AGENTS.md freshness::{message}") | ||
|
|
||
|
|
||
| def main(argv: list[str] | None = None) -> int: | ||
| parser = argparse.ArgumentParser(description=__doc__) | ||
| parser.add_argument("--repo-root", type=Path, default=Path.cwd()) | ||
| parser.add_argument("--agents-md", type=Path) | ||
| parser.add_argument("--github-annotations", action="store_true") | ||
| parser.add_argument("--json", action="store_true", dest="as_json") | ||
| parser.add_argument( | ||
| "--strict", action="store_true", help="Exit non-zero when findings are present." | ||
| ) | ||
| args = parser.parse_args(argv) | ||
|
|
||
| repo_root = args.repo_root.resolve() | ||
| agents_md = args.agents_md.resolve() if args.agents_md else None | ||
| findings = check_agents_md(repo_root, agents_md) | ||
|
Comment on lines
+142
to
+144
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. Resolve
Suggested fix- repo_root = args.repo_root.resolve()
- agents_md = args.agents_md.resolve() if args.agents_md else None
+ repo_root = args.repo_root.resolve()
+ if args.agents_md:
+ agents_md = (
+ args.agents_md
+ if args.agents_md.is_absolute()
+ else (repo_root / args.agents_md)
+ ).resolve()
+ else:
+ agents_md = None🤖 Prompt for AI Agents |
||
|
|
||
| if args.as_json: | ||
| print(json.dumps({"findings": [finding.as_dict() for finding in findings]}, indent=2)) | ||
| elif findings: | ||
| for finding in findings: | ||
| print(finding.message) | ||
| else: | ||
| print("AGENTS.md managed section freshness check passed.") | ||
|
|
||
| if args.github_annotations and findings: | ||
| _emit_github_warnings(findings) | ||
|
|
||
| return 1 if args.strict and findings else 0 | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| sys.exit(main()) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -29,13 +29,6 @@ | |
| from abc import ABC, abstractmethod | ||
| from dataclasses import dataclass | ||
|
|
||
| from tools.llm_registry import ( | ||
| PROVIDER_ANTHROPIC, | ||
| PROVIDER_GITHUB, | ||
| PROVIDER_OPENAI, | ||
| configured_model_for_provider, | ||
| ) | ||
|
|
||
| logger = logging.getLogger(__name__) | ||
|
|
||
| # GitHub Models API endpoint (OpenAI-compatible) | ||
|
|
@@ -344,11 +337,8 @@ def _get_client(self): | |
| logger.warning("langchain_openai not installed") | ||
| return None | ||
|
|
||
| model = configured_model_for_provider(PROVIDER_GITHUB, fallback="gpt-4.1") | ||
| if not model: | ||
| return None | ||
| return ChatOpenAI( | ||
| model=model, | ||
| model="gpt-4.1", # Battle-tested, reliable, available on GitHub Models | ||
| base_url=GITHUB_MODELS_BASE_URL, | ||
| api_key=os.environ.get("GITHUB_TOKEN"), | ||
| temperature=0.1, # Low temperature for consistent analysis | ||
|
Comment on lines
+341
to
344
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. Restore config-driven model resolution instead of hardcoded provider models. At Line 341, Line 594, and Line 660 (and corresponding Suggested fix- return ChatOpenAI(
- model="gpt-4.1", # Battle-tested, reliable, available on GitHub Models
+ selected_model = configured_model_for_provider(
+ self.name,
+ fallback="gpt-4.1",
+ )
+ return ChatOpenAI(
+ model=selected_model,
base_url=GITHUB_MODELS_BASE_URL,
api_key=os.environ.get("GITHUB_TOKEN"),
temperature=0.1, # Low temperature for consistent analysis
)
...
- model_name="gpt-4.1", # Actual model used by GitHubModelsProvider
+ model_name=selected_model,
...
- model_name="gpt-4.1", # Actual model used by GitHubModelsProvider
+ model_name=selected_model or "",
...
- return ChatOpenAI(
- model="gpt-5.1-codex", # Purpose-built for analyzing Codex coding sessions
+ selected_model = configured_model_for_provider(
+ self.name,
+ fallback="gpt-5.1-codex",
+ )
+ return ChatOpenAI(
+ model=selected_model,
api_key=os.environ.get("OPENAI_API_KEY"),
temperature=0.1,
)
...
- model_name="gpt-5.1-codex", # Actual model used by OpenAIProvider
+ model_name=selected_model,
...
- return ChatAnthropic(
- model="claude-sonnet-4-5-20250929",
+ selected_model = configured_model_for_provider(
+ self.name,
+ fallback="claude-sonnet-4-5-20250929",
+ )
+ return ChatAnthropic(
+ model=selected_model,
anthropic_api_key=os.environ.get(ANTHROPIC_API_KEY_ENV),
temperature=0.1,
)
...
- model_name="claude-sonnet-4-5-20250929",
+ model_name=selected_model,Also applies to: 553-553, 568-568, 594-597, 629-629, 660-663, 705-705 🤖 Prompt for AI Agents |
||
|
|
@@ -560,7 +550,7 @@ def _parse_response( | |
| confidence=adjusted_confidence, | ||
| reasoning=reasoning, | ||
| provider_used=self.name, | ||
| model_name=configured_model_for_provider(PROVIDER_GITHUB, fallback="gpt-4.1"), | ||
| model_name="gpt-4.1", # Actual model used by GitHubModelsProvider | ||
| raw_confidence=raw_confidence if adjusted_confidence != raw_confidence else None, | ||
| confidence_adjusted=adjusted_confidence != raw_confidence, | ||
| quality_warnings=warnings if warnings else None, | ||
|
|
@@ -575,7 +565,7 @@ def _parse_response( | |
| confidence=0.0, | ||
| reasoning=f"Failed to parse response: {e}", | ||
| provider_used=self.name, | ||
| model_name=configured_model_for_provider(PROVIDER_GITHUB, fallback="gpt-4.1"), | ||
| model_name="gpt-4.1", # Actual model used by GitHubModelsProvider | ||
| ) | ||
|
|
||
|
|
||
|
|
@@ -600,11 +590,8 @@ def _get_client(self): | |
| logger.warning("langchain_openai not installed") | ||
| return None | ||
|
|
||
| model = configured_model_for_provider(PROVIDER_OPENAI, fallback="gpt-5.1-codex") | ||
| if not model: | ||
| return None | ||
| return ChatOpenAI( | ||
| model=model, | ||
| model="gpt-5.1-codex", # Purpose-built for analyzing Codex coding sessions | ||
| api_key=os.environ.get("OPENAI_API_KEY"), | ||
| temperature=0.1, | ||
| ) | ||
|
|
@@ -639,7 +626,7 @@ def analyze_completion( | |
| confidence=result.confidence, | ||
| reasoning=result.reasoning, | ||
| provider_used=self.name, | ||
| model_name=configured_model_for_provider(PROVIDER_OPENAI, fallback="gpt-5.1-codex"), | ||
| model_name="gpt-5.1-codex", # Actual model used by OpenAIProvider | ||
| raw_confidence=result.raw_confidence, | ||
| confidence_adjusted=result.confidence_adjusted, | ||
| quality_warnings=result.quality_warnings, | ||
|
|
@@ -669,14 +656,8 @@ def _get_client(self): | |
| logger.warning("langchain_anthropic not installed") | ||
| return None | ||
|
|
||
| model = configured_model_for_provider( | ||
| PROVIDER_ANTHROPIC, | ||
| fallback="claude-sonnet-4-5-20250929", | ||
| ) | ||
| if not model: | ||
| return None | ||
| return ChatAnthropic( | ||
| model=model, | ||
| model="claude-sonnet-4-5-20250929", | ||
| anthropic_api_key=os.environ.get(ANTHROPIC_API_KEY_ENV), | ||
| temperature=0.1, | ||
| ) | ||
|
|
@@ -721,10 +702,7 @@ def analyze_completion( | |
| confidence=result.confidence, | ||
| reasoning=result.reasoning, | ||
| provider_used=self.name, | ||
| model_name=configured_model_for_provider( | ||
| PROVIDER_ANTHROPIC, | ||
| fallback="claude-sonnet-4-5-20250929", | ||
| ), | ||
| model_name="claude-sonnet-4-5-20250929", | ||
| raw_confidence=result.raw_confidence, | ||
| confidence_adjusted=result.confidence_adjusted, | ||
| quality_warnings=result.quality_warnings, | ||
|
|
||
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.
Constrain path checks to stay inside
repo_root.Absolute refs (
/tmp/x) and traversal refs (../x) currently pass if they exist on disk, which weakens “repo freshness” guarantees and can hide stale repo references.Suggested fix
Also applies to: 71-73
🤖 Prompt for AI Agents