diff --git a/WORKFLOW_USER_GUIDE.md b/WORKFLOW_USER_GUIDE.md index 51854138..a934f186 100644 --- a/WORKFLOW_USER_GUIDE.md +++ b/WORKFLOW_USER_GUIDE.md @@ -146,7 +146,6 @@ Issue: "Add user authentication" | `agents:optimize` | Issue | Analyze and suggest improvements | | `agents:apply-suggestions` | Issue | Apply optimization suggestions | | `agents:auto-pilot` | Issue | Full end-to-end automation | -| `agents:allow-change` | PR | Permission signal for `agents-guard`; automatically applied to dependency-bot PRs by `maint-auto-label-dep-prs.yml` | | `agents:capability-check` | Issue | Check if agent can complete | | `agents:decompose` | Issue | Break into smaller issues | | `agents:dedup` | Issue | Check for duplicates | @@ -166,6 +165,7 @@ Issue: "Add user authentication" | `agent:needs-attention` | Human intervention required | | `agents:auto-pilot-pause` | Auto-pilot paused | | `agents:auto-pilot-failed` | Auto-pilot stopped due to errors | +| `agents:allow-change` | Permission signal for `agents-guard`; bypasses CODEOWNER approval only for automated dependency PRs from Dependabot/Renovate. Auto-applied by `maint-auto-label-dep-prs.yml`; manual application does not bypass guard enforcement. | | `needs-human` | Escalated to human | | `follow-up` | Created as follow-up to another issue/PR | | `duplicate` | Potential duplicate detected | diff --git a/scripts/aggregate_agent_metrics.py b/scripts/aggregate_agent_metrics.py index 011dbe1e..58accc5e 100755 --- a/scripts/aggregate_agent_metrics.py +++ b/scripts/aggregate_agent_metrics.py @@ -14,8 +14,6 @@ from pathlib import Path from typing import Any -from src.ndjson_parser import read_ndjson_file - _DEFAULT_METRICS_DIR = "agent-metrics" _DEFAULT_OUTPUT = "agent-metrics-summary.md" _DEFAULT_JSON_OUTPUT = "agent-metrics-summary.json" @@ -305,6 +303,66 @@ def _canonical_parse_error_detail(path: Path, error: str) -> ParseErrorDetail: return _parse_error_detail(path, line, reason) +def read_ndjson_file(path: Path) -> tuple[list[dict[str, Any]], list[str]]: + """Read an NDJSON file without depending on Workflows-only modules.""" + try: + handle = path.open("r", encoding="utf-8") + except OSError as exc: + return [], [f"{path}: {exc}"] + + entries: list[dict[str, Any]] = [] + errors: list[str] = [] + raw_lines_for_fallback: list[str] = [] + raw_fallback_bytes = 0 + raw_fallback_truncated = False + with handle: + for line_number, line in enumerate(handle, start=1): + raw = line.strip() + if not raw: + continue + if not entries and not raw_fallback_truncated: + raw_bytes = len(raw.encode("utf-8")) + 1 + fallback_within_limit = ( + len(raw_lines_for_fallback) < _MAX_LEGACY_JSON_FALLBACK_LINES + and raw_fallback_bytes + raw_bytes <= _MAX_LEGACY_JSON_FALLBACK_BYTES + ) + if fallback_within_limit: + raw_fallback_bytes += raw_bytes + raw_lines_for_fallback.append(raw) + else: + raw_fallback_truncated = True + raw_lines_for_fallback = [] + try: + parsed = json.loads(raw) + except json.JSONDecodeError as exc: + errors.append(f"{path}:{line_number}: invalid JSON ({exc.msg})") + continue + if isinstance(parsed, dict): + entries.append(parsed) + raw_lines_for_fallback = [] + else: + errors.append(f"{path}:{line_number}: expected object, got {type(parsed).__name__}") + + if entries or not errors: + return entries, errors + + raw_text = "\n".join(raw_lines_for_fallback) + if raw_fallback_truncated: + errors.append(f"{path}: legacy-json-fallback-buffer-limit") + return entries, errors + + try: + parsed_file = json.loads(raw_text) + except json.JSONDecodeError: + return entries, errors + + if isinstance(parsed_file, dict): + return [parsed_file], [] + if isinstance(parsed_file, list) and all(isinstance(item, dict) for item in parsed_file): + return list(parsed_file), [] + return entries, errors + + def read_metric_ndjson_files( files: Iterable[Path], ) -> tuple[list[dict[str, Any]], list[ParseErrorDetail]]: