From a79ed608c524df669cc5ecbf6c904f9e23b4dbda Mon Sep 17 00:00:00 2001 From: stranske Date: Tue, 6 Jan 2026 05:08:06 +0000 Subject: [PATCH 1/2] chore: apply black formatting to all Python files Fixes formatting issues that were blocking CI on Dependabot PRs. 31 files reformatted to comply with black code style. --- .github/scripts/decode_raw_input.py | 16 +++++-- .github/scripts/parse_chatgpt_topics.py | 12 +++-- scripts/build_static_dashboard.py | 48 ++++++++++++++----- scripts/ci_coverage_delta.py | 19 ++++++-- scripts/ci_history.py | 18 +++++-- scripts/ci_metrics.py | 13 +++-- scripts/collect_ecosystem_status.py | 10 ++-- scripts/create_revision_issues.py | 32 +++++++++---- scripts/generate_month_end.py | 3 +- scripts/langchain/issue_formatter.py | 16 +++++-- scripts/sync_dev_dependencies.py | 20 ++++++-- scripts/sync_test_dependencies.py | 12 +++-- scripts/validate_config.py | 32 +++++++++---- scripts/validate_rubrics.py | 4 +- scripts/validate_time_log.py | 4 +- scripts/validate_time_log_template.py | 4 +- scripts/validate_trend_references.py | 47 +++++++++++++----- streamlit_app/app.py | 40 ++++++++++++---- streamlit_app/github_client.py | 12 +++-- streamlit_app/review_console.py | 8 +++- tests/scripts/test_build_static_dashboard.py | 11 +++-- tests/scripts/test_generate_month_end.py | 4 +- tests/scripts/test_validate_config.py | 8 +++- tests/scripts/test_validate_expense_log.py | 4 +- tests/scripts/test_validate_friction_log.py | 4 +- tests/scripts/test_validate_rubrics.py | 4 +- tests/scripts/test_validate_time_log.py | 8 +++- .../scripts/test_validate_trend_references.py | 8 +++- tools/coverage_trend.py | 32 +++++++++---- tools/llm_provider.py | 18 +++++-- tools/pep517_backend.py | 16 +++++-- 31 files changed, 366 insertions(+), 121 deletions(-) diff --git a/.github/scripts/decode_raw_input.py b/.github/scripts/decode_raw_input.py index 7e168058..a789282d 100755 --- a/.github/scripts/decode_raw_input.py +++ b/.github/scripts/decode_raw_input.py @@ -18,7 +18,9 @@ def _parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description="Decode or normalize topic input") - parser.add_argument("--passthrough", action="store_true", help="Read plain text from --in file") + parser.add_argument( + "--passthrough", action="store_true", help="Read plain text from --in file" + ) parser.add_argument("--in", dest="in_file", help="Input file for passthrough mode") parser.add_argument( "--source", @@ -31,7 +33,9 @@ def _parse_args() -> argparse.Namespace: def main() -> None: args = _parse_args() text: str = "" - source_used = args.source_tag or ("passthrough" if args.passthrough else "raw_input") + source_used = args.source_tag or ( + "passthrough" if args.passthrough else "raw_input" + ) if args.passthrough: if not args.in_file: return @@ -114,7 +118,9 @@ def apply_section_headers(s: str) -> str: rebuilt = s for m in markers: # replace occurrences not already preceded by newline - rebuilt = re.sub(rf"(? tuple[list[str], list[str]]: if text.strip(): OUT_FILE.write_text(text + "\n", encoding="utf-8") # Always write diagnostics when debug artifact collection might happen - Path("decode_debug.json").write_text(json.dumps(diagnostics, indent=2), encoding="utf-8") + Path("decode_debug.json").write_text( + json.dumps(diagnostics, indent=2), encoding="utf-8" + ) if __name__ == "__main__": diff --git a/.github/scripts/parse_chatgpt_topics.py b/.github/scripts/parse_chatgpt_topics.py index 4c7eb1c6..3e641165 100755 --- a/.github/scripts/parse_chatgpt_topics.py +++ b/.github/scripts/parse_chatgpt_topics.py @@ -25,7 +25,9 @@ def _load_text() -> str: try: text = INPUT_PATH.read_text(encoding="utf-8").strip() - except FileNotFoundError as exc: # pragma: no cover - guardrail for workflow execution + except ( + FileNotFoundError + ) as exc: # pragma: no cover - guardrail for workflow execution raise SystemExit("No input.txt found to parse.") from exc if not text: raise SystemExit("No topic content provided.") @@ -44,7 +46,9 @@ def _split_numbered_items(text: str) -> list[dict[str, str | list[str] | bool]]: Each returned item dict includes: title, lines, enumerator, continuity_break (bool) """ - pattern = re.compile(r"^\s*(?P(?:\d+|[A-Za-z]\d+|[A-Za-z]))[\).:\-]\s+(?P.+)$") + pattern = re.compile( + r"^\s*(?P<enum>(?:\d+|[A-Za-z]\d+|[A-Za-z]))[\).:\-]\s+(?P<title>.+)$" + ) items: list[dict[str, str | list[str] | bool]] = [] current: dict[str, str | list[str] | bool] | None = None style: str | None = None # 'numeric' | 'alpha' | 'alphanum' @@ -170,7 +174,9 @@ def _join_section(lines: list[str]) -> str: return "\n".join(lines).strip() -def parse_text(text: str, *, allow_single_fallback: bool = False) -> list[dict[str, object]]: +def parse_text( + text: str, *, allow_single_fallback: bool = False +) -> list[dict[str, object]]: """Parse raw *text* into topic dictionaries. Parameters diff --git a/scripts/build_static_dashboard.py b/scripts/build_static_dashboard.py index e37d73d5..4c4ec8e8 100644 --- a/scripts/build_static_dashboard.py +++ b/scripts/build_static_dashboard.py @@ -158,9 +158,13 @@ def _summarize_time_logs(time_log_dir: Path) -> TimeLogSummary: skipped_rows += 1 continue workstream = (row.get("repo") or "Unknown").strip() or "Unknown" - category = (row.get("category") or "Uncategorized").strip() or "Uncategorized" + category = ( + row.get("category") or "Uncategorized" + ).strip() or "Uncategorized" total_hours += hours - workstream_hours[workstream] = workstream_hours.get(workstream, 0.0) + hours + workstream_hours[workstream] = ( + workstream_hours.get(workstream, 0.0) + hours + ) category_hours[category] = category_hours.get(category, 0.0) + hours entries += 1 @@ -199,7 +203,9 @@ def _summarize_reviews(reviews_dir: Path) -> ReviewSummary: numeric_ratings_found=0, ) - review_paths = sorted(list(reviews_dir.rglob("*.yml")) + list(reviews_dir.rglob("*.yaml"))) + review_paths = sorted( + list(reviews_dir.rglob("*.yml")) + list(reviews_dir.rglob("*.yaml")) + ) for path in review_paths: data = _load_yaml(path) @@ -224,7 +230,9 @@ def _summarize_reviews(reviews_dir: Path) -> ReviewSummary: numeric_ratings.append(rating_value) numeric_by_workstream.setdefault(workstream, []).append(rating_value) - numeric_average = sum(numeric_ratings) / len(numeric_ratings) if numeric_ratings else None + numeric_average = ( + sum(numeric_ratings) / len(numeric_ratings) if numeric_ratings else None + ) numeric_average_by_workstream = { workstream: sum(values) / len(values) for workstream, values in numeric_by_workstream.items() @@ -270,7 +278,9 @@ def _coerce_int(value: Any) -> int | None: issues = raw.get("issues", {}) prs = raw.get("pull_requests", {}) issues_open = _coerce_int(issues.get("open")) if isinstance(issues, dict) else None - issues_closed = _coerce_int(issues.get("closed")) if isinstance(issues, dict) else None + issues_closed = ( + _coerce_int(issues.get("closed")) if isinstance(issues, dict) else None + ) prs_open = _coerce_int(prs.get("open")) if isinstance(prs, dict) else None prs_closed = _coerce_int(prs.get("closed")) if isinstance(prs, dict) else None @@ -296,7 +306,9 @@ def _coerce_int(value: Any) -> int | None: updated = entry.get("updated_at") or entry.get("updated") or entry.get("date") number_text = f"#{number}" if number is not None else "" updated_text = f" ({updated})" if updated else "" - recent_activity.append(f"{item_type} {number_text} {title}{updated_text}".strip()) + recent_activity.append( + f"{item_type} {number_text} {title}{updated_text}".strip() + ) return IssuePrSummary( issues_open=issues_open, @@ -390,7 +402,9 @@ def _render_time_section(summary: TimeLogSummary) -> list[str]: return lines -def _render_review_section(summary: ReviewSummary, config: DashboardConfig) -> list[str]: +def _render_review_section( + summary: ReviewSummary, config: DashboardConfig +) -> list[str]: lines = ["## Review Summary"] if summary.total_reviews == 0: lines.append("No reviews found.") @@ -402,10 +416,14 @@ def _render_review_section(summary: ReviewSummary, config: DashboardConfig) -> l if summary.numeric_ratings_found == 0: return lines if config.show_numeric_scoring and summary.numeric_average is not None: - lines.append(f"Average rating (numeric): {_format_float(summary.numeric_average)}") + lines.append( + f"Average rating (numeric): {_format_float(summary.numeric_average)}" + ) if summary.numeric_average_by_workstream: lines.append("Average rating by workstream (numeric):") - for workstream, average in sorted(summary.numeric_average_by_workstream.items()): + for workstream, average in sorted( + summary.numeric_average_by_workstream.items() + ): lines.append(f"- {workstream}: {_format_float(average)}") else: lines.append("Numeric ratings exist but are hidden by dashboard config.") @@ -419,7 +437,9 @@ def _render_issue_pr_section(summary: IssuePrSummary | None) -> list[str]: return lines if summary.issues_open is not None or summary.issues_closed is not None: open_count = summary.issues_open if summary.issues_open is not None else "n/a" - closed_count = summary.issues_closed if summary.issues_closed is not None else "n/a" + closed_count = ( + summary.issues_closed if summary.issues_closed is not None else "n/a" + ) lines.append(f"Issues: open {open_count} | closed {closed_count}") if summary.prs_open is not None or summary.prs_closed is not None: open_count = summary.prs_open if summary.prs_open is not None else "n/a" @@ -503,7 +523,9 @@ def _render_ecosystem_section(summary: EcosystemSummary | None) -> list[str]: if summary is None: lines.append("No ecosystem status data available.") lines.append("") - lines.append("*Run `python scripts/collect_ecosystem_status.py` to collect data.*") + lines.append( + "*Run `python scripts/collect_ecosystem_status.py` to collect data.*" + ) return lines lines.append(f"Source: `{summary.workflows_source}`") @@ -645,7 +667,9 @@ def main(argv: list[str] | None = None) -> int: except ValueError: print(f"Invalid --now value: {args.now}", file=sys.stderr) return 2 - now = now.replace(tzinfo=dt.UTC) if now.tzinfo is None else now.astimezone(dt.UTC) + now = ( + now.replace(tzinfo=dt.UTC) if now.tzinfo is None else now.astimezone(dt.UTC) + ) markdown = build_dashboard( time_log_dir=args.time_log_dir, diff --git a/scripts/ci_coverage_delta.py b/scripts/ci_coverage_delta.py index 64a2ef51..e25c4f04 100755 --- a/scripts/ci_coverage_delta.py +++ b/scripts/ci_coverage_delta.py @@ -54,7 +54,12 @@ def _build_payload( *, fail_on_drop: bool, ) -> tuple[dict[str, Any], bool]: - timestamp = _dt.datetime.now(_dt.UTC).replace(microsecond=0).isoformat().replace("+00:00", "Z") + timestamp = ( + _dt.datetime.now(_dt.UTC) + .replace(microsecond=0) + .isoformat() + .replace("+00:00", "Z") + ) drop = max(0.0, baseline - current) if baseline > 0 else 0.0 delta = current - baseline status: str @@ -89,7 +94,9 @@ def main() -> int: baseline = _parse_float( os.environ.get("BASELINE_COVERAGE"), "BASELINE_COVERAGE", _DEFAULT_BASELINE ) - alert_drop = _parse_float(os.environ.get("ALERT_DROP"), "ALERT_DROP", _DEFAULT_ALERT_DROP) + alert_drop = _parse_float( + os.environ.get("ALERT_DROP"), "ALERT_DROP", _DEFAULT_ALERT_DROP + ) fail_on_drop = _truthy(os.environ.get("FAIL_ON_DROP")) try: @@ -98,9 +105,13 @@ def main() -> int: print(str(exc), file=sys.stderr) return 1 - payload, should_fail = _build_payload(current, baseline, alert_drop, fail_on_drop=fail_on_drop) + payload, should_fail = _build_payload( + current, baseline, alert_drop, fail_on_drop=fail_on_drop + ) - output_path.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8") + output_path.write_text( + json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8" + ) print(f"Coverage delta written to {output_path}") return 1 if should_fail else 0 diff --git a/scripts/ci_history.py b/scripts/ci_history.py index f4c40415..5c2750df 100755 --- a/scripts/ci_history.py +++ b/scripts/ci_history.py @@ -54,7 +54,12 @@ def _build_history_record( metrics_path: Path, metrics_from_file: bool, ) -> dict[str, Any]: - timestamp = _dt.datetime.now(_dt.UTC).replace(microsecond=0).isoformat().replace("+00:00", "Z") + timestamp = ( + _dt.datetime.now(_dt.UTC) + .replace(microsecond=0) + .isoformat() + .replace("+00:00", "Z") + ) summary = metrics.get("summary", {}) failures = metrics.get("failures", []) @@ -80,7 +85,12 @@ def _build_history_record( def _build_classification_payload(metrics: dict[str, Any]) -> dict[str, Any]: - timestamp = _dt.datetime.now(_dt.UTC).replace(microsecond=0).isoformat().replace("+00:00", "Z") + timestamp = ( + _dt.datetime.now(_dt.UTC) + .replace(microsecond=0) + .isoformat() + .replace("+00:00", "Z") + ) failures = metrics.get("failures", []) or [] counts = Counter(entry.get("status", "unknown") for entry in failures) payload: dict[str, Any] = { @@ -109,7 +119,9 @@ def main() -> int: if classification_env is None: classification_env = os.environ.get("ENABLE_CLASSIFICATION_FLAG") classification_flag = _truthy(classification_env) - classification_out = Path(os.environ.get("CLASSIFICATION_OUT", _DEFAULT_CLASSIFICATION)) + classification_out = Path( + os.environ.get("CLASSIFICATION_OUT", _DEFAULT_CLASSIFICATION) + ) if not junit_path.is_file(): print(f"JUnit report not found: {junit_path}", file=sys.stderr) diff --git a/scripts/ci_metrics.py b/scripts/ci_metrics.py index 6a4c4d7d..082f18ee 100755 --- a/scripts/ci_metrics.py +++ b/scripts/ci_metrics.py @@ -205,7 +205,10 @@ def build_metrics( payload: dict[str, Any] = { "generated_at": ( - _dt.datetime.now(_dt.UTC).replace(microsecond=0).isoformat().replace("+00:00", "Z") + _dt.datetime.now(_dt.UTC) + .replace(microsecond=0) + .isoformat() + .replace("+00:00", "Z") ), "junit_path": str(junit_path), "summary": summary, @@ -223,7 +226,9 @@ def main() -> int: junit_path = Path(os.environ.get("JUNIT_PATH", _DEFAULT_JUNIT)) output_path = Path(os.environ.get("OUTPUT_PATH", _DEFAULT_OUTPUT)) top_n = _parse_int(os.environ.get("TOP_N"), "TOP_N", _DEFAULT_TOP_N) - min_seconds = _parse_float(os.environ.get("MIN_SECONDS"), "MIN_SECONDS", _DEFAULT_MIN_SECONDS) + min_seconds = _parse_float( + os.environ.get("MIN_SECONDS"), "MIN_SECONDS", _DEFAULT_MIN_SECONDS + ) try: payload = build_metrics(junit_path, top_n=top_n, min_seconds=min_seconds) @@ -231,7 +236,9 @@ def main() -> int: print(str(exc), file=sys.stderr) return 1 - output_path.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8") + output_path.write_text( + json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8" + ) print(f"Metrics written to {output_path}") return 0 diff --git a/scripts/collect_ecosystem_status.py b/scripts/collect_ecosystem_status.py index 1526ac1a..ef021a41 100644 --- a/scripts/collect_ecosystem_status.py +++ b/scripts/collect_ecosystem_status.py @@ -209,9 +209,9 @@ def collect_ecosystem_status( workflows_using = len({ref.workflow_file for ref in references}) # Check if sync-related workflow exists - sync_workflow_exists = (workflows_dir / "maint-68-sync-consumer-repos.yml").exists() or any( - "sync" in f.name.lower() for f in workflows_dir.glob("*.yml") - ) + sync_workflow_exists = ( + workflows_dir / "maint-68-sync-consumer-repos.yml" + ).exists() or any("sync" in f.name.lower() for f in workflows_dir.glob("*.yml")) return EcosystemStatus( collected_at=datetime.now(UTC).strftime("%Y-%m-%d %H:%M:%S UTC"), @@ -253,7 +253,9 @@ def _dataclass_to_dict(obj: Any) -> Any: def main(argv: list[str] | None = None) -> int: - parser = argparse.ArgumentParser(description="Collect Workflows ecosystem linkage status.") + parser = argparse.ArgumentParser( + description="Collect Workflows ecosystem linkage status." + ) parser.add_argument( "--workflows-dir", type=Path, diff --git a/scripts/create_revision_issues.py b/scripts/create_revision_issues.py index e0ef280b..988c8d92 100644 --- a/scripts/create_revision_issues.py +++ b/scripts/create_revision_issues.py @@ -48,14 +48,20 @@ def _load_yaml(path: Path) -> dict[str, object]: return payload -def _require_field(payload: dict[str, object], name: str, field_type: type, source: Path) -> object: +def _require_field( + payload: dict[str, object], name: str, field_type: type, source: Path +) -> object: value = payload.get(name) if not isinstance(value, field_type): - raise SystemExit(f"Review file missing {name} ({field_type.__name__}) in {source}") + raise SystemExit( + f"Review file missing {name} ({field_type.__name__}) in {source}" + ) return value -def _parse_follow_up_items(raw_items: object, source: Path) -> tuple[FollowUpIssue, ...]: +def _parse_follow_up_items( + raw_items: object, source: Path +) -> tuple[FollowUpIssue, ...]: if raw_items is None: return () if not isinstance(raw_items, list): @@ -72,7 +78,9 @@ def _parse_follow_up_items(raw_items: object, source: Path) -> tuple[FollowUpIss if not isinstance(description, str) or not description.strip(): raise SystemExit(f"follow_up_issues entry missing description in {source}") if not isinstance(required, bool): - raise SystemExit(f"follow_up_issues entry required must be boolean in {source}") + raise SystemExit( + f"follow_up_issues entry required must be boolean in {source}" + ) parsed.append( FollowUpIssue( issue_id=issue_id.strip(), @@ -217,18 +225,26 @@ def process_review( review = load_review(review_path) review_reference = _review_reference(review_path) actions: list[IssueAction] = [] - follow_ups = (issue for issue in review.follow_up_issues if issue.required or include_optional) + follow_ups = ( + issue for issue in review.follow_up_issues if issue.required or include_optional + ) for follow_up in follow_ups: title = build_issue_title(review, follow_up) body = build_issue_body(review, follow_up, review_reference, repo) if dry_run: - actions.append(IssueAction(issue_id=follow_up.issue_id, status="dry-run", url=None)) + actions.append( + IssueAction(issue_id=follow_up.issue_id, status="dry-run", url=None) + ) continue if _issue_exists(follow_up, review_reference, repo, runner=runner): - actions.append(IssueAction(issue_id=follow_up.issue_id, status="skipped", url=None)) + actions.append( + IssueAction(issue_id=follow_up.issue_id, status="skipped", url=None) + ) continue url = _create_issue(title, body, label, repo, runner=runner) - actions.append(IssueAction(issue_id=follow_up.issue_id, status="created", url=url)) + actions.append( + IssueAction(issue_id=follow_up.issue_id, status="created", url=url) + ) return actions diff --git a/scripts/generate_month_end.py b/scripts/generate_month_end.py index 80acc0b5..56dcda01 100644 --- a/scripts/generate_month_end.py +++ b/scripts/generate_month_end.py @@ -165,7 +165,8 @@ def _load_expenses(path: Path) -> list[ExpenseEntry]: date=(row.get("date") or "").strip(), amount=amount, currency=(row.get("currency") or "unknown").strip() or "unknown", - category=(row.get("category") or "unspecified").strip() or "unspecified", + category=(row.get("category") or "unspecified").strip() + or "unspecified", description=(row.get("description") or "").strip(), receipt_link=(row.get("receipt_link") or "").strip(), issue_or_pr=(row.get("issue_or_pr") or "").strip(), diff --git a/scripts/langchain/issue_formatter.py b/scripts/langchain/issue_formatter.py index fe7c0ef0..44106bfa 100755 --- a/scripts/langchain/issue_formatter.py +++ b/scripts/langchain/issue_formatter.py @@ -42,7 +42,9 @@ """.strip() PROMPT_PATH = Path(__file__).resolve().parent / "prompts" / "format_issue.md" -FEEDBACK_PROMPT_PATH = Path(__file__).resolve().parent / "prompts" / "format_issue_feedback.md" +FEEDBACK_PROMPT_PATH = ( + Path(__file__).resolve().parent / "prompts" / "format_issue_feedback.md" +) SECTION_ALIASES = { "why": ["why", "motivation", "summary", "goals"], @@ -336,7 +338,9 @@ def format_issue_body(issue_body: str, *, use_llm: bool = True) -> dict[str, Any # Fall through to fallback if LLM fails (import, auth, API errors) pass - formatted = _append_raw_issue_section(_format_issue_fallback(issue_body), issue_body) + formatted = _append_raw_issue_section( + _format_issue_fallback(issue_body), issue_body + ) return { "formatted_body": formatted, "provider_used": None, @@ -360,11 +364,15 @@ def _load_input(args: argparse.Namespace) -> str: def main() -> None: - parser = argparse.ArgumentParser(description="Format issues into AGENT_ISSUE_TEMPLATE.") + parser = argparse.ArgumentParser( + description="Format issues into AGENT_ISSUE_TEMPLATE." + ) parser.add_argument("--input-file", help="Path to raw issue text.") parser.add_argument("--input-text", help="Raw issue text (inline).") parser.add_argument("--output-file", help="Path to write formatted output.") - parser.add_argument("--json", action="store_true", help="Emit JSON payload to stdout.") + parser.add_argument( + "--json", action="store_true", help="Emit JSON payload to stdout." + ) parser.add_argument("--no-llm", action="store_true", help="Disable LLM usage.") args = parser.parse_args() diff --git a/scripts/sync_dev_dependencies.py b/scripts/sync_dev_dependencies.py index 90a5dd48..74262ba7 100644 --- a/scripts/sync_dev_dependencies.py +++ b/scripts/sync_dev_dependencies.py @@ -131,7 +131,9 @@ def find_project_section_end(content: str) -> int | None: return len(content) -def create_dev_dependencies_section(pins: dict[str, str], use_exact_pins: bool = True) -> str: +def create_dev_dependencies_section( + pins: dict[str, str], use_exact_pins: bool = True +) -> str: """Create a new dev dependencies section with core tools.""" op = "==" if use_exact_pins else ">=" deps = [] @@ -156,7 +158,9 @@ def extract_dependencies(section: str) -> list[tuple[str, str, str]]: deps = [] # Match patterns like "package>=1.0.0" or "package==1.0.0" or just "package" # Be precise: package name followed by optional version specifier - pattern = re.compile(r'"([a-zA-Z0-9_-]+)(?:(>=|==|~=|>|<|<=|!=)([^"\[\]]+))?(?:\[.*?\])?"') + pattern = re.compile( + r'"([a-zA-Z0-9_-]+)(?:(>=|==|~=|>|<|<=|!=)([^"\[\]]+))?(?:\[.*?\])?"' + ) for match in pattern.finditer(section): package = match.group(1) @@ -236,12 +240,20 @@ def sync_pyproject( opt_deps_pos = find_optional_dependencies_section(content) if opt_deps_pos is not None: # Add after [project.optional-dependencies] header - content = content[:opt_deps_pos] + "\n" + new_section + "\n" + content[opt_deps_pos:] + content = ( + content[:opt_deps_pos] + + "\n" + + new_section + + "\n" + + content[opt_deps_pos:] + ) else: # Need to add [project.optional-dependencies] section insert_pos = find_project_section_end(content) if insert_pos is None: - return [], ["Could not find [project] section to add optional-dependencies"] + return [], [ + "Could not find [project] section to add optional-dependencies" + ] section_to_add = "\n[project.optional-dependencies]\n" + new_section + "\n" content = content[:insert_pos] + section_to_add + content[insert_pos:] diff --git a/scripts/sync_test_dependencies.py b/scripts/sync_test_dependencies.py index 2c4beddb..10c747ed 100644 --- a/scripts/sync_test_dependencies.py +++ b/scripts/sync_test_dependencies.py @@ -211,7 +211,9 @@ def _read_local_modules() -> set[str]: def get_project_modules() -> set[str]: """Return the full set of project modules (static + dynamically detected + local).""" - return _BASE_PROJECT_MODULES | _detect_local_project_modules() | _read_local_modules() + return ( + _BASE_PROJECT_MODULES | _detect_local_project_modules() | _read_local_modules() + ) # For backward compatibility - will be populated on first use @@ -364,7 +366,9 @@ def add_dependencies_to_pyproject(missing: set[str], fix: bool = False) -> bool: dev_group.multiline(True) optional[DEV_EXTRA] = dev_group - existing_normalised = {_normalise_package_name(str(item).split("[")[0]) for item in dev_group} + existing_normalised = { + _normalise_package_name(str(item).split("[")[0]) for item in dev_group + } added = False for package in sorted(missing): @@ -383,7 +387,9 @@ def add_dependencies_to_pyproject(missing: set[str], fix: bool = False) -> bool: def main(argv: list[str] | None = None) -> int: """Main entry point.""" - parser = argparse.ArgumentParser(description="Sync test dependencies to pyproject.toml") + parser = argparse.ArgumentParser( + description="Sync test dependencies to pyproject.toml" + ) parser.add_argument( "--fix", action="store_true", diff --git a/scripts/validate_config.py b/scripts/validate_config.py index aebee3f4..48f9a0dd 100644 --- a/scripts/validate_config.py +++ b/scripts/validate_config.py @@ -70,18 +70,24 @@ def _validate_project(path: Path, data: object) -> list[str]: proposal_date = project.get("proposal_version_date") if not isinstance(proposal_date, str) or not proposal_date.strip(): - errors.append(f"{path}: project.proposal_version_date must be a non-empty string.") + errors.append( + f"{path}: project.proposal_version_date must be a non-empty string." + ) else: try: date.fromisoformat(proposal_date) except ValueError: - errors.append(f"{path}: project.proposal_version_date must be YYYY-MM-DD format.") + errors.append( + f"{path}: project.proposal_version_date must be YYYY-MM-DD format." + ) automation = project.get("automation_ecosystem") if not isinstance(automation, dict): errors.append(f"{path}: project.automation_ecosystem must be a mapping.") else: - missing = [field for field in AUTOMATION_REQUIRED_FIELDS if field not in automation] + missing = [ + field for field in AUTOMATION_REQUIRED_FIELDS if field not in automation + ] if missing: errors.append( f"{path}: project.automation_ecosystem missing fields: {', '.join(missing)}." @@ -104,7 +110,9 @@ def _validate_project(path: Path, data: object) -> list[str]: if field not in constraints ] if missing: - errors.append(f"{path}: project.constraints missing fields: {', '.join(missing)}.") + errors.append( + f"{path}: project.constraints missing fields: {', '.join(missing)}." + ) for field in CONSTRAINT_BOOL_FIELDS: if field in constraints and not isinstance(constraints[field], bool): errors.append(f"{path}: project.constraints.{field} must be a boolean.") @@ -124,17 +132,23 @@ def _validate_project(path: Path, data: object) -> list[str]: if not isinstance(stream, dict): errors.append(f"{path}: project.workstreams[{idx}] must be a mapping.") continue - missing = [field for field in WORKSTREAM_REQUIRED_FIELDS if field not in stream] + missing = [ + field for field in WORKSTREAM_REQUIRED_FIELDS if field not in stream + ] if missing: errors.append( f"{path}: project.workstreams[{idx}] missing fields: {', '.join(missing)}." ) continue errors.extend( - _validate_non_empty_str(path, f"project.workstreams[{idx}].id", stream["id"]) + _validate_non_empty_str( + path, f"project.workstreams[{idx}].id", stream["id"] + ) ) errors.extend( - _validate_non_empty_str(path, f"project.workstreams[{idx}].name", stream["name"]) + _validate_non_empty_str( + path, f"project.workstreams[{idx}].name", stream["name"] + ) ) return errors @@ -242,7 +256,9 @@ def main(argv: list[str] | None = None) -> int: errors = validate_configs(Path(args.project_path), Path(args.dashboard_path)) if errors: - message = "Config validation failed:\n" + "\n".join(f"- {error}" for error in errors) + message = "Config validation failed:\n" + "\n".join( + f"- {error}" for error in errors + ) raise SystemExit(message) print("✓ Config validation passed") diff --git a/scripts/validate_rubrics.py b/scripts/validate_rubrics.py index cf71261c..69216f87 100644 --- a/scripts/validate_rubrics.py +++ b/scripts/validate_rubrics.py @@ -79,7 +79,9 @@ def main(argv: list[str] | None = None) -> int: errors = validate_rubrics(Path(args.rubrics_dir), args.check_structure) if errors: - message = "Rubric validation failed:\n" + "\n".join(f"- {error}" for error in errors) + message = "Rubric validation failed:\n" + "\n".join( + f"- {error}" for error in errors + ) raise SystemExit(message) print("OK") diff --git a/scripts/validate_time_log.py b/scripts/validate_time_log.py index cc55913d..7d78244c 100755 --- a/scripts/validate_time_log.py +++ b/scripts/validate_time_log.py @@ -85,7 +85,9 @@ def validate_row( return dt, hours, errors -def validate_time_log(path: str, *, verbose: bool = False, today: date | None = None) -> list[str]: +def validate_time_log( + path: str, *, verbose: bool = False, today: date | None = None +) -> list[str]: today = today or date.today() with open(path, newline="", encoding="utf-8") as f: r = csv.DictReader(f) diff --git a/scripts/validate_time_log_template.py b/scripts/validate_time_log_template.py index d37f7f20..afce41c8 100644 --- a/scripts/validate_time_log_template.py +++ b/scripts/validate_time_log_template.py @@ -50,7 +50,9 @@ def validate_template(template_path: Path) -> tuple[bool, str]: def main() -> int: """Validate the time log template file.""" - parser = argparse.ArgumentParser(description="Validate time log template CSV format.") + parser = argparse.ArgumentParser( + description="Validate time log template CSV format." + ) parser.add_argument( "template", metavar="template.csv", diff --git a/scripts/validate_trend_references.py b/scripts/validate_trend_references.py index 774c2275..155c2e26 100644 --- a/scripts/validate_trend_references.py +++ b/scripts/validate_trend_references.py @@ -63,7 +63,9 @@ class Reference: r"\s*(?:\u2014|-)\s*(?P<desc>.+)" ) -REFERENCE_CORE_RE = re.compile(r"(?P<path>[A-Za-z0-9_./-]+)#L(?P<start>\d+)-L(?P<end>\d+)") +REFERENCE_CORE_RE = re.compile( + r"(?P<path>[A-Za-z0-9_./-]+)#L(?P<start>\d+)-L(?P<end>\d+)" +) HEADING_RE = re.compile(r"^\s*(?P<hashes>#{1,6})\s+(?P<title>.+?)\s*$") @@ -98,7 +100,9 @@ def _extract_category(line: str) -> str | None: return _normalize_category(text) -def _resolve_reference_path(base_dir: Path, source_dir: Path, ref_path: str) -> Path | None: +def _resolve_reference_path( + base_dir: Path, source_dir: Path, ref_path: str +) -> Path | None: candidate = Path(ref_path) if candidate.is_absolute(): return candidate @@ -110,7 +114,9 @@ def _resolve_reference_path(base_dir: Path, source_dir: Path, ref_path: str) -> return None -def _parse_references(markdown: str, source: str) -> tuple[list[Reference], list[str], bool]: +def _parse_references( + markdown: str, source: str +) -> tuple[list[Reference], list[str], bool]: references: list[Reference] = [] errors: list[str] = [] current_category: str | None = None @@ -153,11 +159,17 @@ def _parse_references(markdown: str, source: str) -> tuple[list[Reference], list description = match.group("desc").strip() ref_category = current_category if not ref_category: - errors.append(f"{source}:{line_number}: reference missing category heading.") + errors.append( + f"{source}:{line_number}: reference missing category heading." + ) if end < start: - errors.append(f"{source}:{line_number}: invalid line range L{start}-L{end}.") + errors.append( + f"{source}:{line_number}: invalid line range L{start}-L{end}." + ) if not description: - errors.append(f"{source}:{line_number}: reference missing description.") + errors.append( + f"{source}:{line_number}: reference missing description." + ) references.append( Reference( path=match.group("path"), @@ -192,12 +204,16 @@ def _check_reference_files( for ref in references: resolved = _resolve_reference_path(base_dir, source_dir, ref.path) if resolved is None: - errors.append(f"{ref.source}:{ref.source_line}: file not found for {ref.path}.") + errors.append( + f"{ref.source}:{ref.source_line}: file not found for {ref.path}." + ) continue try: lines = resolved.read_text(encoding="utf-8").splitlines() except OSError as exc: - errors.append(f"{ref.source}:{ref.source_line}: failed to read {resolved}: {exc}.") + errors.append( + f"{ref.source}:{ref.source_line}: failed to read {resolved}: {exc}." + ) continue if ref.start_line < 1 or ref.end_line > len(lines): errors.append( @@ -217,7 +233,8 @@ def _check_category_minimums(references: list[Reference]) -> list[str]: if counts[key] < minimum: label = CATEGORY_LABELS.get(key, key) errors.append( - f"Insufficient {label} references: {counts[key]} found, " f"{minimum}+ required." + f"Insufficient {label} references: {counts[key]} found, " + f"{minimum}+ required." ) return errors @@ -247,7 +264,9 @@ def validate_trend_references(path: Path, check_files: bool = False) -> list[str references, errors, _ = _parse_references(content, str(path)) errors.extend(_check_category_minimums(references)) if check_files: - errors.extend(_check_reference_files(references, Path.cwd(), path.parent.resolve())) + errors.extend( + _check_reference_files(references, Path.cwd(), path.parent.resolve()) + ) return errors @@ -262,7 +281,9 @@ def _collect_markdown_files(paths: list[Path]) -> list[Path]: def main(argv: list[str] | None = None) -> int: - parser = argparse.ArgumentParser(description="Validate Trend memo references in markdown.") + parser = argparse.ArgumentParser( + description="Validate Trend memo references in markdown." + ) parser.add_argument( "paths", nargs="*", @@ -281,7 +302,9 @@ def main(argv: list[str] | None = None) -> int: else: errors = [] for markdown_path in _collect_markdown_files([Path(p) for p in args.paths]): - errors.extend(validate_trend_references(markdown_path, check_files=args.check_files)) + errors.extend( + validate_trend_references(markdown_path, check_files=args.check_files) + ) if errors: message = "Trend reference validation failed:\n" + "\n".join( diff --git a/streamlit_app/app.py b/streamlit_app/app.py index 0e7f714d..a011f4b5 100644 --- a/streamlit_app/app.py +++ b/streamlit_app/app.py @@ -71,7 +71,9 @@ def load_workstreams(path: Path) -> tuple[list[dict[str, str]], str | None]: except Exception as exc: # pragma: no cover - runtime feedback return DEFAULT_WORKSTREAMS, f"Unable to read workstream config: {exc}" - workstreams = data.get("project", {}).get("workstreams") if isinstance(data, dict) else None + workstreams = ( + data.get("project", {}).get("workstreams") if isinstance(data, dict) else None + ) if not isinstance(workstreams, list) or not workstreams: return DEFAULT_WORKSTREAMS, "Workstream config missing workstreams list." @@ -189,13 +191,17 @@ def build_weekly_cap_chart(weekly: pd.DataFrame, cap_hours: float) -> alt.Chart: y=alt.Y("hours:Q", title="Hours"), color=alt.Color( "Status:N", - scale=alt.Scale(domain=["Within cap", "Over cap"], range=["#2a9d8f", "#e76f51"]), + scale=alt.Scale( + domain=["Within cap", "Over cap"], range=["#2a9d8f", "#e76f51"] + ), legend=alt.Legend(title="Cap status"), ), tooltip=["week", "hours", "Status"], ) cap_rule = ( - alt.Chart(pd.DataFrame({"Cap": [cap_hours]})).mark_rule(color="#264653").encode(y="Cap:Q") + alt.Chart(pd.DataFrame({"Cap": [cap_hours]})) + .mark_rule(color="#264653") + .encode(y="Cap:Q") ) return alt.layer(bars, cap_rule) @@ -327,8 +333,12 @@ def load_rubric_definitions( errors.append(f"Rubric {rubric_path} has invalid format.") continue raw_rubric_id = data.get("rubric_id") - rubric_id = raw_rubric_id if isinstance(raw_rubric_id, str) else rubric_path.stem - dims = data.get("dimensions") if isinstance(data.get("dimensions"), list) else [] + rubric_id = ( + raw_rubric_id if isinstance(raw_rubric_id, str) else rubric_path.stem + ) + dims = ( + data.get("dimensions") if isinstance(data.get("dimensions"), list) else [] + ) dim_lookup: dict[str, str] = {} for dim in dims: if not isinstance(dim, dict): @@ -487,7 +497,9 @@ def render_follow_ups(value: object) -> None: if workstream_error: st.info(workstream_error) -workstream_table, deliverable_status = compute_workstream_progress(workstreams, review_records) +workstream_table, deliverable_status = compute_workstream_progress( + workstreams, review_records +) st.dataframe(workstream_table, use_container_width=True) for name, statuses in deliverable_status.items(): @@ -590,7 +602,9 @@ def render_follow_ups(value: object) -> None: "Updated": issue.updated_at.strftime("%Y-%m-%d"), } ) - st.dataframe(pd.DataFrame(issue_rows), use_container_width=True, hide_index=True) + st.dataframe( + pd.DataFrame(issue_rows), use_container_width=True, hide_index=True + ) else: st.info("No issues found or GitHub API unavailable.") @@ -609,7 +623,9 @@ def render_follow_ups(value: object) -> None: "Updated": pr.updated_at.strftime("%Y-%m-%d"), } ) - st.dataframe(pd.DataFrame(pr_rows), use_container_width=True, hide_index=True) + st.dataframe( + pd.DataFrame(pr_rows), use_container_width=True, hide_index=True + ) else: st.info("No pull requests found or GitHub API unavailable.") @@ -633,12 +649,16 @@ def render_follow_ups(value: object) -> None: "Date": run.created_at.strftime("%Y-%m-%d %H:%M"), } ) - st.dataframe(pd.DataFrame(run_rows), use_container_width=True, hide_index=True) + st.dataframe( + pd.DataFrame(run_rows), use_container_width=True, hide_index=True + ) else: st.info("No workflow runs found or GitHub API unavailable.") except ImportError: - st.info("GitHub integration requires 'requests' package. Install with: pip install requests") + st.info( + "GitHub integration requires 'requests' package. Install with: pip install requests" + ) except Exception as e: st.warning(f"GitHub integration error: {e}") diff --git a/streamlit_app/github_client.py b/streamlit_app/github_client.py index baff5158..006b52aa 100644 --- a/streamlit_app/github_client.py +++ b/streamlit_app/github_client.py @@ -60,7 +60,9 @@ def _parse_datetime(dt_str: str) -> datetime: return datetime.fromisoformat(dt_str.replace("Z", "+00:00")) -def _make_request(endpoint: str, token: str | None = None) -> dict[str, Any] | list[Any] | None: +def _make_request( + endpoint: str, token: str | None = None +) -> dict[str, Any] | list[Any] | None: """Make authenticated request to GitHub API.""" headers = {"Accept": "application/vnd.github+json"} if token: @@ -101,7 +103,9 @@ def fetch_issues(repo: str = DEFAULT_REPO, token: str | None = None) -> list[Iss return issues -def fetch_pull_requests(repo: str = DEFAULT_REPO, token: str | None = None) -> list[Issue]: +def fetch_pull_requests( + repo: str = DEFAULT_REPO, token: str | None = None +) -> list[Issue]: """Fetch pull requests from repository.""" data = _make_request(f"/repos/{repo}/pulls?state=all&per_page=30", token) if not data or not isinstance(data, list): @@ -125,7 +129,9 @@ def fetch_pull_requests(repo: str = DEFAULT_REPO, token: str | None = None) -> l return prs -def fetch_workflow_runs(repo: str = DEFAULT_REPO, token: str | None = None) -> list[WorkflowRun]: +def fetch_workflow_runs( + repo: str = DEFAULT_REPO, token: str | None = None +) -> list[WorkflowRun]: """Fetch recent workflow runs from repository.""" data = _make_request(f"/repos/{repo}/actions/runs?per_page=20", token) if not data or not isinstance(data, dict): diff --git a/streamlit_app/review_console.py b/streamlit_app/review_console.py index c2eea34b..5cb45024 100644 --- a/streamlit_app/review_console.py +++ b/streamlit_app/review_console.py @@ -102,7 +102,9 @@ def generate_review_yaml( "follow_up_issues": follow_ups if follow_ups else [], } - return yaml.dump(record, default_flow_style=False, sort_keys=False, allow_unicode=True) + return yaml.dump( + record, default_flow_style=False, sort_keys=False, allow_unicode=True + ) def render_review_console( @@ -198,7 +200,9 @@ def render_review_console( # Follow-up issues st.markdown("### Follow-up Issues") - num_followups = st.number_input("Number of follow-ups", min_value=0, max_value=5, value=0) + num_followups = st.number_input( + "Number of follow-ups", min_value=0, max_value=5, value=0 + ) follow_ups = [] for i in range(int(num_followups)): diff --git a/tests/scripts/test_build_static_dashboard.py b/tests/scripts/test_build_static_dashboard.py index 504c29ea..37ce7aca 100644 --- a/tests/scripts/test_build_static_dashboard.py +++ b/tests/scripts/test_build_static_dashboard.py @@ -13,7 +13,9 @@ def load_module(): - script_path = Path(__file__).resolve().parents[2] / "scripts" / "build_static_dashboard.py" + script_path = ( + Path(__file__).resolve().parents[2] / "scripts" / "build_static_dashboard.py" + ) spec = importlib.util.spec_from_file_location("build_static_dashboard", script_path) module = importlib.util.module_from_spec(spec) sys.modules[spec.name] = module @@ -28,7 +30,9 @@ def load_module(): def write_time_log(path: Path, rows: list[dict[str, str]]) -> None: path.parent.mkdir(parents=True, exist_ok=True) with path.open("w", newline="", encoding="utf-8") as handle: - writer = csv.DictWriter(handle, fieldnames=build_static_dashboard.TIME_LOG_FIELDS) + writer = csv.DictWriter( + handle, fieldnames=build_static_dashboard.TIME_LOG_FIELDS + ) writer.writeheader() writer.writerows(rows) @@ -212,7 +216,8 @@ def test_build_dashboard_with_data(tmp_path: Path) -> None: assert "PR #34 Add feature (2025-01-02)" in dashboard assert "Recent pass rate (last 3 runs): 66.67% (2/3)" in dashboard assert ( - "Latest run: 2025-01-03T00:00:00Z - failed (tests: 10, failures: 1, errors: 0)" in dashboard + "Latest run: 2025-01-03T00:00:00Z - failed (tests: 10, failures: 1, errors: 0)" + in dashboard ) assert "## Workflows Ecosystem Linkage" in dashboard assert "stranske/Workflows" in dashboard diff --git a/tests/scripts/test_generate_month_end.py b/tests/scripts/test_generate_month_end.py index 39c48fab..c04f2520 100644 --- a/tests/scripts/test_generate_month_end.py +++ b/tests/scripts/test_generate_month_end.py @@ -9,7 +9,9 @@ def load_module(): - script_path = Path(__file__).resolve().parents[2] / "scripts" / "generate_month_end.py" + script_path = ( + Path(__file__).resolve().parents[2] / "scripts" / "generate_month_end.py" + ) spec = importlib.util.spec_from_file_location("generate_month_end", script_path) module = importlib.util.module_from_spec(spec) assert spec.loader is not None diff --git a/tests/scripts/test_validate_config.py b/tests/scripts/test_validate_config.py index 9e6a6ec1..2831a767 100644 --- a/tests/scripts/test_validate_config.py +++ b/tests/scripts/test_validate_config.py @@ -7,7 +7,9 @@ from pathlib import Path -def _run_validator(project_path: Path, dashboard_path: Path) -> subprocess.CompletedProcess[str]: +def _run_validator( + project_path: Path, dashboard_path: Path +) -> subprocess.CompletedProcess[str]: repo_root = Path(__file__).resolve().parents[2] script_path = repo_root / "scripts" / "validate_config.py" command = [ @@ -133,7 +135,9 @@ def test_validate_config_rejects_invalid_dashboard_types(tmp_path: Path) -> None result = _run_validator(project_path, dashboard_path) assert result.returncode != 0 - assert "dashboard.show_numeric_scoring must be a boolean" in (result.stderr + result.stdout) + assert "dashboard.show_numeric_scoring must be a boolean" in ( + result.stderr + result.stdout + ) # Tests for single-file validation mode (--type argument) diff --git a/tests/scripts/test_validate_expense_log.py b/tests/scripts/test_validate_expense_log.py index 27b2b560..c5fcef71 100644 --- a/tests/scripts/test_validate_expense_log.py +++ b/tests/scripts/test_validate_expense_log.py @@ -8,7 +8,9 @@ def load_module(): - script_path = Path(__file__).resolve().parents[2] / "scripts" / "validate_expense_log.py" + script_path = ( + Path(__file__).resolve().parents[2] / "scripts" / "validate_expense_log.py" + ) spec = importlib.util.spec_from_file_location("validate_expense_log", script_path) module = importlib.util.module_from_spec(spec) assert spec.loader is not None diff --git a/tests/scripts/test_validate_friction_log.py b/tests/scripts/test_validate_friction_log.py index 72383144..5552c4df 100644 --- a/tests/scripts/test_validate_friction_log.py +++ b/tests/scripts/test_validate_friction_log.py @@ -8,7 +8,9 @@ def load_module(): - script_path = Path(__file__).resolve().parents[2] / "scripts" / "validate_friction_log.py" + script_path = ( + Path(__file__).resolve().parents[2] / "scripts" / "validate_friction_log.py" + ) spec = importlib.util.spec_from_file_location("validate_friction_log", script_path) module = importlib.util.module_from_spec(spec) assert spec.loader is not None diff --git a/tests/scripts/test_validate_rubrics.py b/tests/scripts/test_validate_rubrics.py index e7c72886..1d62abfd 100644 --- a/tests/scripts/test_validate_rubrics.py +++ b/tests/scripts/test_validate_rubrics.py @@ -35,7 +35,9 @@ def test_validate_rubrics_ok(tmp_path: Path) -> None: ), encoding="utf-8", ) - (rubrics_dir / "rubric_index.yml").write_text("rubrics:\n - sample.yml\n", encoding="utf-8") + (rubrics_dir / "rubric_index.yml").write_text( + "rubrics:\n - sample.yml\n", encoding="utf-8" + ) result = _run_validator(rubrics_dir, "--check-structure") diff --git a/tests/scripts/test_validate_time_log.py b/tests/scripts/test_validate_time_log.py index ae00cbfd..365a99de 100644 --- a/tests/scripts/test_validate_time_log.py +++ b/tests/scripts/test_validate_time_log.py @@ -11,7 +11,9 @@ def load_module(): - script_path = Path(__file__).resolve().parents[2] / "scripts" / "validate_time_log.py" + script_path = ( + Path(__file__).resolve().parents[2] / "scripts" / "validate_time_log.py" + ) spec = importlib.util.spec_from_file_location("validate_time_log", script_path) module = importlib.util.module_from_spec(spec) assert spec.loader is not None @@ -99,7 +101,9 @@ def test_date_range_checks(tmp_path: Path) -> None: assert any("too old" in err for err in errors) -def test_verbose_output_in_main(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None: +def test_verbose_output_in_main( + tmp_path: Path, capsys: pytest.CaptureFixture[str] +) -> None: path = tmp_path / "log.csv" write_csv(path, [base_row()]) diff --git a/tests/scripts/test_validate_trend_references.py b/tests/scripts/test_validate_trend_references.py index 9bf5fa6c..bfdb5edc 100644 --- a/tests/scripts/test_validate_trend_references.py +++ b/tests/scripts/test_validate_trend_references.py @@ -8,8 +8,12 @@ def load_module(): - script_path = Path(__file__).resolve().parents[2] / "scripts" / "validate_trend_references.py" - spec = importlib.util.spec_from_file_location("validate_trend_references", script_path) + script_path = ( + Path(__file__).resolve().parents[2] / "scripts" / "validate_trend_references.py" + ) + spec = importlib.util.spec_from_file_location( + "validate_trend_references", script_path + ) module = importlib.util.module_from_spec(spec) sys.modules[spec.name] = module assert spec.loader is not None diff --git a/tools/coverage_trend.py b/tools/coverage_trend.py index 523fa213..c5a2adc3 100755 --- a/tools/coverage_trend.py +++ b/tools/coverage_trend.py @@ -93,15 +93,27 @@ def main(args: list[str] | None = None) -> int: parser.add_argument("--coverage-xml", type=Path, help="Path to coverage.xml") parser.add_argument("--coverage-json", type=Path, help="Path to coverage.json") parser.add_argument("--baseline", type=Path, help="Path to baseline JSON") - parser.add_argument("--summary-path", type=Path, help="Path to output summary markdown") + parser.add_argument( + "--summary-path", type=Path, help="Path to output summary markdown" + ) parser.add_argument("--job-summary", type=Path, help="Path to GITHUB_STEP_SUMMARY") - parser.add_argument("--artifact-path", type=Path, help="Path to output trend artifact") + parser.add_argument( + "--artifact-path", type=Path, help="Path to output trend artifact" + ) parser.add_argument("--github-output", type=Path, help="Path to write env file") - parser.add_argument("--minimum", type=float, default=70.0, help="Minimum coverage threshold") - parser.add_argument("--hotspot-limit", type=int, default=15, help="Max hotspot files to show") - parser.add_argument("--low-threshold", type=float, default=50.0, help="Low coverage threshold") parser.add_argument( - "--soft", action="store_true", help="Soft gate mode - report only, always exit 0" + "--minimum", type=float, default=70.0, help="Minimum coverage threshold" + ) + parser.add_argument( + "--hotspot-limit", type=int, default=15, help="Max hotspot files to show" + ) + parser.add_argument( + "--low-threshold", type=float, default=50.0, help="Low coverage threshold" + ) + parser.add_argument( + "--soft", + action="store_true", + help="Soft gate mode - report only, always exit 0", ) parsed = parser.parse_args(args) @@ -149,7 +161,9 @@ def main(args: list[str] | None = None) -> int: "hotspots": hotspots, "low_coverage_files": low_coverage, } - parsed.artifact_path.write_text(json.dumps(artifact_data, indent=2), encoding="utf-8") + parsed.artifact_path.write_text( + json.dumps(artifact_data, indent=2), encoding="utf-8" + ) status = "✅ Pass" if passes_minimum else "❌ Below minimum" summary = f"""## Coverage Trend @@ -166,7 +180,9 @@ def main(args: list[str] | None = None) -> int: # Add hotspot tables if we have coverage data if hotspots: - summary += _format_hotspot_table(hotspots, "Top Coverage Hotspots (lowest coverage)") + summary += _format_hotspot_table( + hotspots, "Top Coverage Hotspots (lowest coverage)" + ) if low_coverage: summary += _format_hotspot_table( diff --git a/tools/llm_provider.py b/tools/llm_provider.py index ddc08616..e8cb4460 100644 --- a/tools/llm_provider.py +++ b/tools/llm_provider.py @@ -197,7 +197,9 @@ def _validate_confidence( ) # Short text means limited evidence - cap confidence confidence = min(confidence, 0.4) - logger.warning(f"Short analysis text: {quality_context.analysis_text_length} chars") + logger.warning( + f"Short analysis text: {quality_context.analysis_text_length} chars" + ) # BS Detection Rule 3: Zero tasks + high effort score = something's wrong if ( @@ -218,7 +220,9 @@ def _validate_confidence( any(phrase in reasoning_lower for phrase in no_evidence_phrases) and quality_context.has_work_evidence ): - warnings.append("LLM claims 'no evidence' but session has file changes/commands") + warnings.append( + "LLM claims 'no evidence' but session has file changes/commands" + ) confidence = min(confidence, 0.35) # BS Detection Rule 5: Data quality impacts confidence ceiling @@ -318,7 +322,9 @@ def _parse_response( confidence=adjusted_confidence, reasoning=reasoning, provider_used=self.name, - raw_confidence=raw_confidence if adjusted_confidence != raw_confidence else None, + raw_confidence=( + raw_confidence if adjusted_confidence != raw_confidence else None + ), confidence_adjusted=adjusted_confidence != raw_confidence, quality_warnings=warnings if warnings else None, ) @@ -463,7 +469,8 @@ def analyze_completion( is_blocked = any( word in output_lower and any( - p in output_lower for p in ["blocked", "stuck", "failed", "error", "cannot"] + p in output_lower + for p in ["blocked", "stuck", "failed", "error", "cannot"] ) for word in task_words if len(word) > 3 @@ -553,7 +560,8 @@ def get_llm_provider(force_provider: str | None = None) -> LLMProvider: } if force_provider not in provider_map: raise ValueError( - f"Unknown provider: {force_provider}. " f"Options: {list(provider_map.keys())}" + f"Unknown provider: {force_provider}. " + f"Options: {list(provider_map.keys())}" ) provider_class = provider_map[force_provider] provider = provider_class() diff --git a/tools/pep517_backend.py b/tools/pep517_backend.py index 471e4025..bbf4c96e 100644 --- a/tools/pep517_backend.py +++ b/tools/pep517_backend.py @@ -63,7 +63,9 @@ def _write_metadata( metadata_lines.append(f"Provides-Extra: {extra}") for requirement in requirements: metadata_lines.append(f'Requires-Dist: {requirement}; extra == "{extra}"') - (dist_info_dir / "METADATA").write_text("\n".join(metadata_lines) + "\n", encoding="utf-8") + (dist_info_dir / "METADATA").write_text( + "\n".join(metadata_lines) + "\n", encoding="utf-8" + ) (dist_info_dir / "WHEEL").write_text( "\n".join( [ @@ -96,7 +98,9 @@ def _collect_paths(root: Path) -> list[Path]: def _build_wheel(wheel_directory: str, *, editable: bool) -> str: - name, version, description, requires_python, dependencies, optional_deps = _project_metadata() + name, version, description, requires_python, dependencies, optional_deps = ( + _project_metadata() + ) dist = _normalize_dist_name(name) wheel_name = f"{dist}-{version}-py3-none-any.whl" wheel_dir = Path(wheel_directory) @@ -119,7 +123,9 @@ def _build_wheel(wheel_directory: str, *, editable: bool) -> str: if editable: pth_name = f"{dist}.pth" - (tmp_path / pth_name).write_text(str(_project_root() / "src") + "\n", encoding="utf-8") + (tmp_path / pth_name).write_text( + str(_project_root() / "src") + "\n", encoding="utf-8" + ) records.append((pth_name, *_hash_file(tmp_path / pth_name))) else: src_root = _project_root() / "src" @@ -149,7 +155,9 @@ def _build_wheel(wheel_directory: str, *, editable: bool) -> str: def _prepare_metadata(metadata_directory: str) -> str: - name, version, description, requires_python, dependencies, optional_deps = _project_metadata() + name, version, description, requires_python, dependencies, optional_deps = ( + _project_metadata() + ) dist_info_name = _dist_info_name(name, version) dist_info_dir = Path(metadata_directory) / dist_info_name _write_metadata( From fbd03129c0a120402623825dd5fadbc1be06c73b Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <github-actions[bot]@users.noreply.github.com> Date: Tue, 6 Jan 2026 05:10:04 +0000 Subject: [PATCH 2/2] chore(autofix): formatting/lint --- .github/scripts/decode_raw_input.py | 16 ++----- .github/scripts/parse_chatgpt_topics.py | 12 ++--- scripts/build_static_dashboard.py | 48 +++++-------------- scripts/ci_coverage_delta.py | 19 ++------ scripts/ci_history.py | 18 ++----- scripts/ci_metrics.py | 13 ++--- scripts/collect_ecosystem_status.py | 10 ++-- scripts/create_revision_issues.py | 32 ++++--------- scripts/generate_month_end.py | 3 +- scripts/langchain/issue_formatter.py | 16 ++----- scripts/sync_dev_dependencies.py | 20 ++------ scripts/sync_test_dependencies.py | 12 ++--- scripts/validate_config.py | 32 ++++--------- scripts/validate_rubrics.py | 4 +- scripts/validate_time_log.py | 4 +- scripts/validate_time_log_template.py | 4 +- scripts/validate_trend_references.py | 47 +++++------------- streamlit_app/app.py | 40 ++++------------ streamlit_app/github_client.py | 12 ++--- streamlit_app/review_console.py | 8 +--- tests/scripts/test_build_static_dashboard.py | 11 ++--- tests/scripts/test_generate_month_end.py | 4 +- tests/scripts/test_validate_config.py | 8 +--- tests/scripts/test_validate_expense_log.py | 4 +- tests/scripts/test_validate_friction_log.py | 4 +- tests/scripts/test_validate_rubrics.py | 4 +- tests/scripts/test_validate_time_log.py | 8 +--- .../scripts/test_validate_trend_references.py | 8 +--- tools/coverage_trend.py | 28 +++-------- tools/llm_provider.py | 18 ++----- tools/pep517_backend.py | 16 ++----- 31 files changed, 120 insertions(+), 363 deletions(-) diff --git a/.github/scripts/decode_raw_input.py b/.github/scripts/decode_raw_input.py index a789282d..7e168058 100755 --- a/.github/scripts/decode_raw_input.py +++ b/.github/scripts/decode_raw_input.py @@ -18,9 +18,7 @@ def _parse_args() -> argparse.Namespace: parser = argparse.ArgumentParser(description="Decode or normalize topic input") - parser.add_argument( - "--passthrough", action="store_true", help="Read plain text from --in file" - ) + parser.add_argument("--passthrough", action="store_true", help="Read plain text from --in file") parser.add_argument("--in", dest="in_file", help="Input file for passthrough mode") parser.add_argument( "--source", @@ -33,9 +31,7 @@ def _parse_args() -> argparse.Namespace: def main() -> None: args = _parse_args() text: str = "" - source_used = args.source_tag or ( - "passthrough" if args.passthrough else "raw_input" - ) + source_used = args.source_tag or ("passthrough" if args.passthrough else "raw_input") if args.passthrough: if not args.in_file: return @@ -118,9 +114,7 @@ def apply_section_headers(s: str) -> str: rebuilt = s for m in markers: # replace occurrences not already preceded by newline - rebuilt = re.sub( - rf"(?<!\n){re.escape(m)}", "\n" + m.strip() + "\n", rebuilt - ) + rebuilt = re.sub(rf"(?<!\n){re.escape(m)}", "\n" + m.strip() + "\n", rebuilt) if rebuilt != s: applied.append("sections") return rebuilt @@ -170,9 +164,7 @@ def extract_enumerators(s: str) -> tuple[list[str], list[str]]: if text.strip(): OUT_FILE.write_text(text + "\n", encoding="utf-8") # Always write diagnostics when debug artifact collection might happen - Path("decode_debug.json").write_text( - json.dumps(diagnostics, indent=2), encoding="utf-8" - ) + Path("decode_debug.json").write_text(json.dumps(diagnostics, indent=2), encoding="utf-8") if __name__ == "__main__": diff --git a/.github/scripts/parse_chatgpt_topics.py b/.github/scripts/parse_chatgpt_topics.py index 3e641165..4c7eb1c6 100755 --- a/.github/scripts/parse_chatgpt_topics.py +++ b/.github/scripts/parse_chatgpt_topics.py @@ -25,9 +25,7 @@ def _load_text() -> str: try: text = INPUT_PATH.read_text(encoding="utf-8").strip() - except ( - FileNotFoundError - ) as exc: # pragma: no cover - guardrail for workflow execution + except FileNotFoundError as exc: # pragma: no cover - guardrail for workflow execution raise SystemExit("No input.txt found to parse.") from exc if not text: raise SystemExit("No topic content provided.") @@ -46,9 +44,7 @@ def _split_numbered_items(text: str) -> list[dict[str, str | list[str] | bool]]: Each returned item dict includes: title, lines, enumerator, continuity_break (bool) """ - pattern = re.compile( - r"^\s*(?P<enum>(?:\d+|[A-Za-z]\d+|[A-Za-z]))[\).:\-]\s+(?P<title>.+)$" - ) + pattern = re.compile(r"^\s*(?P<enum>(?:\d+|[A-Za-z]\d+|[A-Za-z]))[\).:\-]\s+(?P<title>.+)$") items: list[dict[str, str | list[str] | bool]] = [] current: dict[str, str | list[str] | bool] | None = None style: str | None = None # 'numeric' | 'alpha' | 'alphanum' @@ -174,9 +170,7 @@ def _join_section(lines: list[str]) -> str: return "\n".join(lines).strip() -def parse_text( - text: str, *, allow_single_fallback: bool = False -) -> list[dict[str, object]]: +def parse_text(text: str, *, allow_single_fallback: bool = False) -> list[dict[str, object]]: """Parse raw *text* into topic dictionaries. Parameters diff --git a/scripts/build_static_dashboard.py b/scripts/build_static_dashboard.py index 4c4ec8e8..e37d73d5 100644 --- a/scripts/build_static_dashboard.py +++ b/scripts/build_static_dashboard.py @@ -158,13 +158,9 @@ def _summarize_time_logs(time_log_dir: Path) -> TimeLogSummary: skipped_rows += 1 continue workstream = (row.get("repo") or "Unknown").strip() or "Unknown" - category = ( - row.get("category") or "Uncategorized" - ).strip() or "Uncategorized" + category = (row.get("category") or "Uncategorized").strip() or "Uncategorized" total_hours += hours - workstream_hours[workstream] = ( - workstream_hours.get(workstream, 0.0) + hours - ) + workstream_hours[workstream] = workstream_hours.get(workstream, 0.0) + hours category_hours[category] = category_hours.get(category, 0.0) + hours entries += 1 @@ -203,9 +199,7 @@ def _summarize_reviews(reviews_dir: Path) -> ReviewSummary: numeric_ratings_found=0, ) - review_paths = sorted( - list(reviews_dir.rglob("*.yml")) + list(reviews_dir.rglob("*.yaml")) - ) + review_paths = sorted(list(reviews_dir.rglob("*.yml")) + list(reviews_dir.rglob("*.yaml"))) for path in review_paths: data = _load_yaml(path) @@ -230,9 +224,7 @@ def _summarize_reviews(reviews_dir: Path) -> ReviewSummary: numeric_ratings.append(rating_value) numeric_by_workstream.setdefault(workstream, []).append(rating_value) - numeric_average = ( - sum(numeric_ratings) / len(numeric_ratings) if numeric_ratings else None - ) + numeric_average = sum(numeric_ratings) / len(numeric_ratings) if numeric_ratings else None numeric_average_by_workstream = { workstream: sum(values) / len(values) for workstream, values in numeric_by_workstream.items() @@ -278,9 +270,7 @@ def _coerce_int(value: Any) -> int | None: issues = raw.get("issues", {}) prs = raw.get("pull_requests", {}) issues_open = _coerce_int(issues.get("open")) if isinstance(issues, dict) else None - issues_closed = ( - _coerce_int(issues.get("closed")) if isinstance(issues, dict) else None - ) + issues_closed = _coerce_int(issues.get("closed")) if isinstance(issues, dict) else None prs_open = _coerce_int(prs.get("open")) if isinstance(prs, dict) else None prs_closed = _coerce_int(prs.get("closed")) if isinstance(prs, dict) else None @@ -306,9 +296,7 @@ def _coerce_int(value: Any) -> int | None: updated = entry.get("updated_at") or entry.get("updated") or entry.get("date") number_text = f"#{number}" if number is not None else "" updated_text = f" ({updated})" if updated else "" - recent_activity.append( - f"{item_type} {number_text} {title}{updated_text}".strip() - ) + recent_activity.append(f"{item_type} {number_text} {title}{updated_text}".strip()) return IssuePrSummary( issues_open=issues_open, @@ -402,9 +390,7 @@ def _render_time_section(summary: TimeLogSummary) -> list[str]: return lines -def _render_review_section( - summary: ReviewSummary, config: DashboardConfig -) -> list[str]: +def _render_review_section(summary: ReviewSummary, config: DashboardConfig) -> list[str]: lines = ["## Review Summary"] if summary.total_reviews == 0: lines.append("No reviews found.") @@ -416,14 +402,10 @@ def _render_review_section( if summary.numeric_ratings_found == 0: return lines if config.show_numeric_scoring and summary.numeric_average is not None: - lines.append( - f"Average rating (numeric): {_format_float(summary.numeric_average)}" - ) + lines.append(f"Average rating (numeric): {_format_float(summary.numeric_average)}") if summary.numeric_average_by_workstream: lines.append("Average rating by workstream (numeric):") - for workstream, average in sorted( - summary.numeric_average_by_workstream.items() - ): + for workstream, average in sorted(summary.numeric_average_by_workstream.items()): lines.append(f"- {workstream}: {_format_float(average)}") else: lines.append("Numeric ratings exist but are hidden by dashboard config.") @@ -437,9 +419,7 @@ def _render_issue_pr_section(summary: IssuePrSummary | None) -> list[str]: return lines if summary.issues_open is not None or summary.issues_closed is not None: open_count = summary.issues_open if summary.issues_open is not None else "n/a" - closed_count = ( - summary.issues_closed if summary.issues_closed is not None else "n/a" - ) + closed_count = summary.issues_closed if summary.issues_closed is not None else "n/a" lines.append(f"Issues: open {open_count} | closed {closed_count}") if summary.prs_open is not None or summary.prs_closed is not None: open_count = summary.prs_open if summary.prs_open is not None else "n/a" @@ -523,9 +503,7 @@ def _render_ecosystem_section(summary: EcosystemSummary | None) -> list[str]: if summary is None: lines.append("No ecosystem status data available.") lines.append("") - lines.append( - "*Run `python scripts/collect_ecosystem_status.py` to collect data.*" - ) + lines.append("*Run `python scripts/collect_ecosystem_status.py` to collect data.*") return lines lines.append(f"Source: `{summary.workflows_source}`") @@ -667,9 +645,7 @@ def main(argv: list[str] | None = None) -> int: except ValueError: print(f"Invalid --now value: {args.now}", file=sys.stderr) return 2 - now = ( - now.replace(tzinfo=dt.UTC) if now.tzinfo is None else now.astimezone(dt.UTC) - ) + now = now.replace(tzinfo=dt.UTC) if now.tzinfo is None else now.astimezone(dt.UTC) markdown = build_dashboard( time_log_dir=args.time_log_dir, diff --git a/scripts/ci_coverage_delta.py b/scripts/ci_coverage_delta.py index e25c4f04..64a2ef51 100755 --- a/scripts/ci_coverage_delta.py +++ b/scripts/ci_coverage_delta.py @@ -54,12 +54,7 @@ def _build_payload( *, fail_on_drop: bool, ) -> tuple[dict[str, Any], bool]: - timestamp = ( - _dt.datetime.now(_dt.UTC) - .replace(microsecond=0) - .isoformat() - .replace("+00:00", "Z") - ) + timestamp = _dt.datetime.now(_dt.UTC).replace(microsecond=0).isoformat().replace("+00:00", "Z") drop = max(0.0, baseline - current) if baseline > 0 else 0.0 delta = current - baseline status: str @@ -94,9 +89,7 @@ def main() -> int: baseline = _parse_float( os.environ.get("BASELINE_COVERAGE"), "BASELINE_COVERAGE", _DEFAULT_BASELINE ) - alert_drop = _parse_float( - os.environ.get("ALERT_DROP"), "ALERT_DROP", _DEFAULT_ALERT_DROP - ) + alert_drop = _parse_float(os.environ.get("ALERT_DROP"), "ALERT_DROP", _DEFAULT_ALERT_DROP) fail_on_drop = _truthy(os.environ.get("FAIL_ON_DROP")) try: @@ -105,13 +98,9 @@ def main() -> int: print(str(exc), file=sys.stderr) return 1 - payload, should_fail = _build_payload( - current, baseline, alert_drop, fail_on_drop=fail_on_drop - ) + payload, should_fail = _build_payload(current, baseline, alert_drop, fail_on_drop=fail_on_drop) - output_path.write_text( - json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8" - ) + output_path.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8") print(f"Coverage delta written to {output_path}") return 1 if should_fail else 0 diff --git a/scripts/ci_history.py b/scripts/ci_history.py index 5c2750df..f4c40415 100755 --- a/scripts/ci_history.py +++ b/scripts/ci_history.py @@ -54,12 +54,7 @@ def _build_history_record( metrics_path: Path, metrics_from_file: bool, ) -> dict[str, Any]: - timestamp = ( - _dt.datetime.now(_dt.UTC) - .replace(microsecond=0) - .isoformat() - .replace("+00:00", "Z") - ) + timestamp = _dt.datetime.now(_dt.UTC).replace(microsecond=0).isoformat().replace("+00:00", "Z") summary = metrics.get("summary", {}) failures = metrics.get("failures", []) @@ -85,12 +80,7 @@ def _build_history_record( def _build_classification_payload(metrics: dict[str, Any]) -> dict[str, Any]: - timestamp = ( - _dt.datetime.now(_dt.UTC) - .replace(microsecond=0) - .isoformat() - .replace("+00:00", "Z") - ) + timestamp = _dt.datetime.now(_dt.UTC).replace(microsecond=0).isoformat().replace("+00:00", "Z") failures = metrics.get("failures", []) or [] counts = Counter(entry.get("status", "unknown") for entry in failures) payload: dict[str, Any] = { @@ -119,9 +109,7 @@ def main() -> int: if classification_env is None: classification_env = os.environ.get("ENABLE_CLASSIFICATION_FLAG") classification_flag = _truthy(classification_env) - classification_out = Path( - os.environ.get("CLASSIFICATION_OUT", _DEFAULT_CLASSIFICATION) - ) + classification_out = Path(os.environ.get("CLASSIFICATION_OUT", _DEFAULT_CLASSIFICATION)) if not junit_path.is_file(): print(f"JUnit report not found: {junit_path}", file=sys.stderr) diff --git a/scripts/ci_metrics.py b/scripts/ci_metrics.py index 082f18ee..6a4c4d7d 100755 --- a/scripts/ci_metrics.py +++ b/scripts/ci_metrics.py @@ -205,10 +205,7 @@ def build_metrics( payload: dict[str, Any] = { "generated_at": ( - _dt.datetime.now(_dt.UTC) - .replace(microsecond=0) - .isoformat() - .replace("+00:00", "Z") + _dt.datetime.now(_dt.UTC).replace(microsecond=0).isoformat().replace("+00:00", "Z") ), "junit_path": str(junit_path), "summary": summary, @@ -226,9 +223,7 @@ def main() -> int: junit_path = Path(os.environ.get("JUNIT_PATH", _DEFAULT_JUNIT)) output_path = Path(os.environ.get("OUTPUT_PATH", _DEFAULT_OUTPUT)) top_n = _parse_int(os.environ.get("TOP_N"), "TOP_N", _DEFAULT_TOP_N) - min_seconds = _parse_float( - os.environ.get("MIN_SECONDS"), "MIN_SECONDS", _DEFAULT_MIN_SECONDS - ) + min_seconds = _parse_float(os.environ.get("MIN_SECONDS"), "MIN_SECONDS", _DEFAULT_MIN_SECONDS) try: payload = build_metrics(junit_path, top_n=top_n, min_seconds=min_seconds) @@ -236,9 +231,7 @@ def main() -> int: print(str(exc), file=sys.stderr) return 1 - output_path.write_text( - json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8" - ) + output_path.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n", encoding="utf-8") print(f"Metrics written to {output_path}") return 0 diff --git a/scripts/collect_ecosystem_status.py b/scripts/collect_ecosystem_status.py index ef021a41..1526ac1a 100644 --- a/scripts/collect_ecosystem_status.py +++ b/scripts/collect_ecosystem_status.py @@ -209,9 +209,9 @@ def collect_ecosystem_status( workflows_using = len({ref.workflow_file for ref in references}) # Check if sync-related workflow exists - sync_workflow_exists = ( - workflows_dir / "maint-68-sync-consumer-repos.yml" - ).exists() or any("sync" in f.name.lower() for f in workflows_dir.glob("*.yml")) + sync_workflow_exists = (workflows_dir / "maint-68-sync-consumer-repos.yml").exists() or any( + "sync" in f.name.lower() for f in workflows_dir.glob("*.yml") + ) return EcosystemStatus( collected_at=datetime.now(UTC).strftime("%Y-%m-%d %H:%M:%S UTC"), @@ -253,9 +253,7 @@ def _dataclass_to_dict(obj: Any) -> Any: def main(argv: list[str] | None = None) -> int: - parser = argparse.ArgumentParser( - description="Collect Workflows ecosystem linkage status." - ) + parser = argparse.ArgumentParser(description="Collect Workflows ecosystem linkage status.") parser.add_argument( "--workflows-dir", type=Path, diff --git a/scripts/create_revision_issues.py b/scripts/create_revision_issues.py index 988c8d92..e0ef280b 100644 --- a/scripts/create_revision_issues.py +++ b/scripts/create_revision_issues.py @@ -48,20 +48,14 @@ def _load_yaml(path: Path) -> dict[str, object]: return payload -def _require_field( - payload: dict[str, object], name: str, field_type: type, source: Path -) -> object: +def _require_field(payload: dict[str, object], name: str, field_type: type, source: Path) -> object: value = payload.get(name) if not isinstance(value, field_type): - raise SystemExit( - f"Review file missing {name} ({field_type.__name__}) in {source}" - ) + raise SystemExit(f"Review file missing {name} ({field_type.__name__}) in {source}") return value -def _parse_follow_up_items( - raw_items: object, source: Path -) -> tuple[FollowUpIssue, ...]: +def _parse_follow_up_items(raw_items: object, source: Path) -> tuple[FollowUpIssue, ...]: if raw_items is None: return () if not isinstance(raw_items, list): @@ -78,9 +72,7 @@ def _parse_follow_up_items( if not isinstance(description, str) or not description.strip(): raise SystemExit(f"follow_up_issues entry missing description in {source}") if not isinstance(required, bool): - raise SystemExit( - f"follow_up_issues entry required must be boolean in {source}" - ) + raise SystemExit(f"follow_up_issues entry required must be boolean in {source}") parsed.append( FollowUpIssue( issue_id=issue_id.strip(), @@ -225,26 +217,18 @@ def process_review( review = load_review(review_path) review_reference = _review_reference(review_path) actions: list[IssueAction] = [] - follow_ups = ( - issue for issue in review.follow_up_issues if issue.required or include_optional - ) + follow_ups = (issue for issue in review.follow_up_issues if issue.required or include_optional) for follow_up in follow_ups: title = build_issue_title(review, follow_up) body = build_issue_body(review, follow_up, review_reference, repo) if dry_run: - actions.append( - IssueAction(issue_id=follow_up.issue_id, status="dry-run", url=None) - ) + actions.append(IssueAction(issue_id=follow_up.issue_id, status="dry-run", url=None)) continue if _issue_exists(follow_up, review_reference, repo, runner=runner): - actions.append( - IssueAction(issue_id=follow_up.issue_id, status="skipped", url=None) - ) + actions.append(IssueAction(issue_id=follow_up.issue_id, status="skipped", url=None)) continue url = _create_issue(title, body, label, repo, runner=runner) - actions.append( - IssueAction(issue_id=follow_up.issue_id, status="created", url=url) - ) + actions.append(IssueAction(issue_id=follow_up.issue_id, status="created", url=url)) return actions diff --git a/scripts/generate_month_end.py b/scripts/generate_month_end.py index 56dcda01..80acc0b5 100644 --- a/scripts/generate_month_end.py +++ b/scripts/generate_month_end.py @@ -165,8 +165,7 @@ def _load_expenses(path: Path) -> list[ExpenseEntry]: date=(row.get("date") or "").strip(), amount=amount, currency=(row.get("currency") or "unknown").strip() or "unknown", - category=(row.get("category") or "unspecified").strip() - or "unspecified", + category=(row.get("category") or "unspecified").strip() or "unspecified", description=(row.get("description") or "").strip(), receipt_link=(row.get("receipt_link") or "").strip(), issue_or_pr=(row.get("issue_or_pr") or "").strip(), diff --git a/scripts/langchain/issue_formatter.py b/scripts/langchain/issue_formatter.py index 44106bfa..fe7c0ef0 100755 --- a/scripts/langchain/issue_formatter.py +++ b/scripts/langchain/issue_formatter.py @@ -42,9 +42,7 @@ """.strip() PROMPT_PATH = Path(__file__).resolve().parent / "prompts" / "format_issue.md" -FEEDBACK_PROMPT_PATH = ( - Path(__file__).resolve().parent / "prompts" / "format_issue_feedback.md" -) +FEEDBACK_PROMPT_PATH = Path(__file__).resolve().parent / "prompts" / "format_issue_feedback.md" SECTION_ALIASES = { "why": ["why", "motivation", "summary", "goals"], @@ -338,9 +336,7 @@ def format_issue_body(issue_body: str, *, use_llm: bool = True) -> dict[str, Any # Fall through to fallback if LLM fails (import, auth, API errors) pass - formatted = _append_raw_issue_section( - _format_issue_fallback(issue_body), issue_body - ) + formatted = _append_raw_issue_section(_format_issue_fallback(issue_body), issue_body) return { "formatted_body": formatted, "provider_used": None, @@ -364,15 +360,11 @@ def _load_input(args: argparse.Namespace) -> str: def main() -> None: - parser = argparse.ArgumentParser( - description="Format issues into AGENT_ISSUE_TEMPLATE." - ) + parser = argparse.ArgumentParser(description="Format issues into AGENT_ISSUE_TEMPLATE.") parser.add_argument("--input-file", help="Path to raw issue text.") parser.add_argument("--input-text", help="Raw issue text (inline).") parser.add_argument("--output-file", help="Path to write formatted output.") - parser.add_argument( - "--json", action="store_true", help="Emit JSON payload to stdout." - ) + parser.add_argument("--json", action="store_true", help="Emit JSON payload to stdout.") parser.add_argument("--no-llm", action="store_true", help="Disable LLM usage.") args = parser.parse_args() diff --git a/scripts/sync_dev_dependencies.py b/scripts/sync_dev_dependencies.py index 74262ba7..90a5dd48 100644 --- a/scripts/sync_dev_dependencies.py +++ b/scripts/sync_dev_dependencies.py @@ -131,9 +131,7 @@ def find_project_section_end(content: str) -> int | None: return len(content) -def create_dev_dependencies_section( - pins: dict[str, str], use_exact_pins: bool = True -) -> str: +def create_dev_dependencies_section(pins: dict[str, str], use_exact_pins: bool = True) -> str: """Create a new dev dependencies section with core tools.""" op = "==" if use_exact_pins else ">=" deps = [] @@ -158,9 +156,7 @@ def extract_dependencies(section: str) -> list[tuple[str, str, str]]: deps = [] # Match patterns like "package>=1.0.0" or "package==1.0.0" or just "package" # Be precise: package name followed by optional version specifier - pattern = re.compile( - r'"([a-zA-Z0-9_-]+)(?:(>=|==|~=|>|<|<=|!=)([^"\[\]]+))?(?:\[.*?\])?"' - ) + pattern = re.compile(r'"([a-zA-Z0-9_-]+)(?:(>=|==|~=|>|<|<=|!=)([^"\[\]]+))?(?:\[.*?\])?"') for match in pattern.finditer(section): package = match.group(1) @@ -240,20 +236,12 @@ def sync_pyproject( opt_deps_pos = find_optional_dependencies_section(content) if opt_deps_pos is not None: # Add after [project.optional-dependencies] header - content = ( - content[:opt_deps_pos] - + "\n" - + new_section - + "\n" - + content[opt_deps_pos:] - ) + content = content[:opt_deps_pos] + "\n" + new_section + "\n" + content[opt_deps_pos:] else: # Need to add [project.optional-dependencies] section insert_pos = find_project_section_end(content) if insert_pos is None: - return [], [ - "Could not find [project] section to add optional-dependencies" - ] + return [], ["Could not find [project] section to add optional-dependencies"] section_to_add = "\n[project.optional-dependencies]\n" + new_section + "\n" content = content[:insert_pos] + section_to_add + content[insert_pos:] diff --git a/scripts/sync_test_dependencies.py b/scripts/sync_test_dependencies.py index 10c747ed..2c4beddb 100644 --- a/scripts/sync_test_dependencies.py +++ b/scripts/sync_test_dependencies.py @@ -211,9 +211,7 @@ def _read_local_modules() -> set[str]: def get_project_modules() -> set[str]: """Return the full set of project modules (static + dynamically detected + local).""" - return ( - _BASE_PROJECT_MODULES | _detect_local_project_modules() | _read_local_modules() - ) + return _BASE_PROJECT_MODULES | _detect_local_project_modules() | _read_local_modules() # For backward compatibility - will be populated on first use @@ -366,9 +364,7 @@ def add_dependencies_to_pyproject(missing: set[str], fix: bool = False) -> bool: dev_group.multiline(True) optional[DEV_EXTRA] = dev_group - existing_normalised = { - _normalise_package_name(str(item).split("[")[0]) for item in dev_group - } + existing_normalised = {_normalise_package_name(str(item).split("[")[0]) for item in dev_group} added = False for package in sorted(missing): @@ -387,9 +383,7 @@ def add_dependencies_to_pyproject(missing: set[str], fix: bool = False) -> bool: def main(argv: list[str] | None = None) -> int: """Main entry point.""" - parser = argparse.ArgumentParser( - description="Sync test dependencies to pyproject.toml" - ) + parser = argparse.ArgumentParser(description="Sync test dependencies to pyproject.toml") parser.add_argument( "--fix", action="store_true", diff --git a/scripts/validate_config.py b/scripts/validate_config.py index 48f9a0dd..aebee3f4 100644 --- a/scripts/validate_config.py +++ b/scripts/validate_config.py @@ -70,24 +70,18 @@ def _validate_project(path: Path, data: object) -> list[str]: proposal_date = project.get("proposal_version_date") if not isinstance(proposal_date, str) or not proposal_date.strip(): - errors.append( - f"{path}: project.proposal_version_date must be a non-empty string." - ) + errors.append(f"{path}: project.proposal_version_date must be a non-empty string.") else: try: date.fromisoformat(proposal_date) except ValueError: - errors.append( - f"{path}: project.proposal_version_date must be YYYY-MM-DD format." - ) + errors.append(f"{path}: project.proposal_version_date must be YYYY-MM-DD format.") automation = project.get("automation_ecosystem") if not isinstance(automation, dict): errors.append(f"{path}: project.automation_ecosystem must be a mapping.") else: - missing = [ - field for field in AUTOMATION_REQUIRED_FIELDS if field not in automation - ] + missing = [field for field in AUTOMATION_REQUIRED_FIELDS if field not in automation] if missing: errors.append( f"{path}: project.automation_ecosystem missing fields: {', '.join(missing)}." @@ -110,9 +104,7 @@ def _validate_project(path: Path, data: object) -> list[str]: if field not in constraints ] if missing: - errors.append( - f"{path}: project.constraints missing fields: {', '.join(missing)}." - ) + errors.append(f"{path}: project.constraints missing fields: {', '.join(missing)}.") for field in CONSTRAINT_BOOL_FIELDS: if field in constraints and not isinstance(constraints[field], bool): errors.append(f"{path}: project.constraints.{field} must be a boolean.") @@ -132,23 +124,17 @@ def _validate_project(path: Path, data: object) -> list[str]: if not isinstance(stream, dict): errors.append(f"{path}: project.workstreams[{idx}] must be a mapping.") continue - missing = [ - field for field in WORKSTREAM_REQUIRED_FIELDS if field not in stream - ] + missing = [field for field in WORKSTREAM_REQUIRED_FIELDS if field not in stream] if missing: errors.append( f"{path}: project.workstreams[{idx}] missing fields: {', '.join(missing)}." ) continue errors.extend( - _validate_non_empty_str( - path, f"project.workstreams[{idx}].id", stream["id"] - ) + _validate_non_empty_str(path, f"project.workstreams[{idx}].id", stream["id"]) ) errors.extend( - _validate_non_empty_str( - path, f"project.workstreams[{idx}].name", stream["name"] - ) + _validate_non_empty_str(path, f"project.workstreams[{idx}].name", stream["name"]) ) return errors @@ -256,9 +242,7 @@ def main(argv: list[str] | None = None) -> int: errors = validate_configs(Path(args.project_path), Path(args.dashboard_path)) if errors: - message = "Config validation failed:\n" + "\n".join( - f"- {error}" for error in errors - ) + message = "Config validation failed:\n" + "\n".join(f"- {error}" for error in errors) raise SystemExit(message) print("✓ Config validation passed") diff --git a/scripts/validate_rubrics.py b/scripts/validate_rubrics.py index 69216f87..cf71261c 100644 --- a/scripts/validate_rubrics.py +++ b/scripts/validate_rubrics.py @@ -79,9 +79,7 @@ def main(argv: list[str] | None = None) -> int: errors = validate_rubrics(Path(args.rubrics_dir), args.check_structure) if errors: - message = "Rubric validation failed:\n" + "\n".join( - f"- {error}" for error in errors - ) + message = "Rubric validation failed:\n" + "\n".join(f"- {error}" for error in errors) raise SystemExit(message) print("OK") diff --git a/scripts/validate_time_log.py b/scripts/validate_time_log.py index 7d78244c..cc55913d 100755 --- a/scripts/validate_time_log.py +++ b/scripts/validate_time_log.py @@ -85,9 +85,7 @@ def validate_row( return dt, hours, errors -def validate_time_log( - path: str, *, verbose: bool = False, today: date | None = None -) -> list[str]: +def validate_time_log(path: str, *, verbose: bool = False, today: date | None = None) -> list[str]: today = today or date.today() with open(path, newline="", encoding="utf-8") as f: r = csv.DictReader(f) diff --git a/scripts/validate_time_log_template.py b/scripts/validate_time_log_template.py index afce41c8..d37f7f20 100644 --- a/scripts/validate_time_log_template.py +++ b/scripts/validate_time_log_template.py @@ -50,9 +50,7 @@ def validate_template(template_path: Path) -> tuple[bool, str]: def main() -> int: """Validate the time log template file.""" - parser = argparse.ArgumentParser( - description="Validate time log template CSV format." - ) + parser = argparse.ArgumentParser(description="Validate time log template CSV format.") parser.add_argument( "template", metavar="template.csv", diff --git a/scripts/validate_trend_references.py b/scripts/validate_trend_references.py index 155c2e26..774c2275 100644 --- a/scripts/validate_trend_references.py +++ b/scripts/validate_trend_references.py @@ -63,9 +63,7 @@ class Reference: r"\s*(?:\u2014|-)\s*(?P<desc>.+)" ) -REFERENCE_CORE_RE = re.compile( - r"(?P<path>[A-Za-z0-9_./-]+)#L(?P<start>\d+)-L(?P<end>\d+)" -) +REFERENCE_CORE_RE = re.compile(r"(?P<path>[A-Za-z0-9_./-]+)#L(?P<start>\d+)-L(?P<end>\d+)") HEADING_RE = re.compile(r"^\s*(?P<hashes>#{1,6})\s+(?P<title>.+?)\s*$") @@ -100,9 +98,7 @@ def _extract_category(line: str) -> str | None: return _normalize_category(text) -def _resolve_reference_path( - base_dir: Path, source_dir: Path, ref_path: str -) -> Path | None: +def _resolve_reference_path(base_dir: Path, source_dir: Path, ref_path: str) -> Path | None: candidate = Path(ref_path) if candidate.is_absolute(): return candidate @@ -114,9 +110,7 @@ def _resolve_reference_path( return None -def _parse_references( - markdown: str, source: str -) -> tuple[list[Reference], list[str], bool]: +def _parse_references(markdown: str, source: str) -> tuple[list[Reference], list[str], bool]: references: list[Reference] = [] errors: list[str] = [] current_category: str | None = None @@ -159,17 +153,11 @@ def _parse_references( description = match.group("desc").strip() ref_category = current_category if not ref_category: - errors.append( - f"{source}:{line_number}: reference missing category heading." - ) + errors.append(f"{source}:{line_number}: reference missing category heading.") if end < start: - errors.append( - f"{source}:{line_number}: invalid line range L{start}-L{end}." - ) + errors.append(f"{source}:{line_number}: invalid line range L{start}-L{end}.") if not description: - errors.append( - f"{source}:{line_number}: reference missing description." - ) + errors.append(f"{source}:{line_number}: reference missing description.") references.append( Reference( path=match.group("path"), @@ -204,16 +192,12 @@ def _check_reference_files( for ref in references: resolved = _resolve_reference_path(base_dir, source_dir, ref.path) if resolved is None: - errors.append( - f"{ref.source}:{ref.source_line}: file not found for {ref.path}." - ) + errors.append(f"{ref.source}:{ref.source_line}: file not found for {ref.path}.") continue try: lines = resolved.read_text(encoding="utf-8").splitlines() except OSError as exc: - errors.append( - f"{ref.source}:{ref.source_line}: failed to read {resolved}: {exc}." - ) + errors.append(f"{ref.source}:{ref.source_line}: failed to read {resolved}: {exc}.") continue if ref.start_line < 1 or ref.end_line > len(lines): errors.append( @@ -233,8 +217,7 @@ def _check_category_minimums(references: list[Reference]) -> list[str]: if counts[key] < minimum: label = CATEGORY_LABELS.get(key, key) errors.append( - f"Insufficient {label} references: {counts[key]} found, " - f"{minimum}+ required." + f"Insufficient {label} references: {counts[key]} found, " f"{minimum}+ required." ) return errors @@ -264,9 +247,7 @@ def validate_trend_references(path: Path, check_files: bool = False) -> list[str references, errors, _ = _parse_references(content, str(path)) errors.extend(_check_category_minimums(references)) if check_files: - errors.extend( - _check_reference_files(references, Path.cwd(), path.parent.resolve()) - ) + errors.extend(_check_reference_files(references, Path.cwd(), path.parent.resolve())) return errors @@ -281,9 +262,7 @@ def _collect_markdown_files(paths: list[Path]) -> list[Path]: def main(argv: list[str] | None = None) -> int: - parser = argparse.ArgumentParser( - description="Validate Trend memo references in markdown." - ) + parser = argparse.ArgumentParser(description="Validate Trend memo references in markdown.") parser.add_argument( "paths", nargs="*", @@ -302,9 +281,7 @@ def main(argv: list[str] | None = None) -> int: else: errors = [] for markdown_path in _collect_markdown_files([Path(p) for p in args.paths]): - errors.extend( - validate_trend_references(markdown_path, check_files=args.check_files) - ) + errors.extend(validate_trend_references(markdown_path, check_files=args.check_files)) if errors: message = "Trend reference validation failed:\n" + "\n".join( diff --git a/streamlit_app/app.py b/streamlit_app/app.py index a011f4b5..0e7f714d 100644 --- a/streamlit_app/app.py +++ b/streamlit_app/app.py @@ -71,9 +71,7 @@ def load_workstreams(path: Path) -> tuple[list[dict[str, str]], str | None]: except Exception as exc: # pragma: no cover - runtime feedback return DEFAULT_WORKSTREAMS, f"Unable to read workstream config: {exc}" - workstreams = ( - data.get("project", {}).get("workstreams") if isinstance(data, dict) else None - ) + workstreams = data.get("project", {}).get("workstreams") if isinstance(data, dict) else None if not isinstance(workstreams, list) or not workstreams: return DEFAULT_WORKSTREAMS, "Workstream config missing workstreams list." @@ -191,17 +189,13 @@ def build_weekly_cap_chart(weekly: pd.DataFrame, cap_hours: float) -> alt.Chart: y=alt.Y("hours:Q", title="Hours"), color=alt.Color( "Status:N", - scale=alt.Scale( - domain=["Within cap", "Over cap"], range=["#2a9d8f", "#e76f51"] - ), + scale=alt.Scale(domain=["Within cap", "Over cap"], range=["#2a9d8f", "#e76f51"]), legend=alt.Legend(title="Cap status"), ), tooltip=["week", "hours", "Status"], ) cap_rule = ( - alt.Chart(pd.DataFrame({"Cap": [cap_hours]})) - .mark_rule(color="#264653") - .encode(y="Cap:Q") + alt.Chart(pd.DataFrame({"Cap": [cap_hours]})).mark_rule(color="#264653").encode(y="Cap:Q") ) return alt.layer(bars, cap_rule) @@ -333,12 +327,8 @@ def load_rubric_definitions( errors.append(f"Rubric {rubric_path} has invalid format.") continue raw_rubric_id = data.get("rubric_id") - rubric_id = ( - raw_rubric_id if isinstance(raw_rubric_id, str) else rubric_path.stem - ) - dims = ( - data.get("dimensions") if isinstance(data.get("dimensions"), list) else [] - ) + rubric_id = raw_rubric_id if isinstance(raw_rubric_id, str) else rubric_path.stem + dims = data.get("dimensions") if isinstance(data.get("dimensions"), list) else [] dim_lookup: dict[str, str] = {} for dim in dims: if not isinstance(dim, dict): @@ -497,9 +487,7 @@ def render_follow_ups(value: object) -> None: if workstream_error: st.info(workstream_error) -workstream_table, deliverable_status = compute_workstream_progress( - workstreams, review_records -) +workstream_table, deliverable_status = compute_workstream_progress(workstreams, review_records) st.dataframe(workstream_table, use_container_width=True) for name, statuses in deliverable_status.items(): @@ -602,9 +590,7 @@ def render_follow_ups(value: object) -> None: "Updated": issue.updated_at.strftime("%Y-%m-%d"), } ) - st.dataframe( - pd.DataFrame(issue_rows), use_container_width=True, hide_index=True - ) + st.dataframe(pd.DataFrame(issue_rows), use_container_width=True, hide_index=True) else: st.info("No issues found or GitHub API unavailable.") @@ -623,9 +609,7 @@ def render_follow_ups(value: object) -> None: "Updated": pr.updated_at.strftime("%Y-%m-%d"), } ) - st.dataframe( - pd.DataFrame(pr_rows), use_container_width=True, hide_index=True - ) + st.dataframe(pd.DataFrame(pr_rows), use_container_width=True, hide_index=True) else: st.info("No pull requests found or GitHub API unavailable.") @@ -649,16 +633,12 @@ def render_follow_ups(value: object) -> None: "Date": run.created_at.strftime("%Y-%m-%d %H:%M"), } ) - st.dataframe( - pd.DataFrame(run_rows), use_container_width=True, hide_index=True - ) + st.dataframe(pd.DataFrame(run_rows), use_container_width=True, hide_index=True) else: st.info("No workflow runs found or GitHub API unavailable.") except ImportError: - st.info( - "GitHub integration requires 'requests' package. Install with: pip install requests" - ) + st.info("GitHub integration requires 'requests' package. Install with: pip install requests") except Exception as e: st.warning(f"GitHub integration error: {e}") diff --git a/streamlit_app/github_client.py b/streamlit_app/github_client.py index 006b52aa..baff5158 100644 --- a/streamlit_app/github_client.py +++ b/streamlit_app/github_client.py @@ -60,9 +60,7 @@ def _parse_datetime(dt_str: str) -> datetime: return datetime.fromisoformat(dt_str.replace("Z", "+00:00")) -def _make_request( - endpoint: str, token: str | None = None -) -> dict[str, Any] | list[Any] | None: +def _make_request(endpoint: str, token: str | None = None) -> dict[str, Any] | list[Any] | None: """Make authenticated request to GitHub API.""" headers = {"Accept": "application/vnd.github+json"} if token: @@ -103,9 +101,7 @@ def fetch_issues(repo: str = DEFAULT_REPO, token: str | None = None) -> list[Iss return issues -def fetch_pull_requests( - repo: str = DEFAULT_REPO, token: str | None = None -) -> list[Issue]: +def fetch_pull_requests(repo: str = DEFAULT_REPO, token: str | None = None) -> list[Issue]: """Fetch pull requests from repository.""" data = _make_request(f"/repos/{repo}/pulls?state=all&per_page=30", token) if not data or not isinstance(data, list): @@ -129,9 +125,7 @@ def fetch_pull_requests( return prs -def fetch_workflow_runs( - repo: str = DEFAULT_REPO, token: str | None = None -) -> list[WorkflowRun]: +def fetch_workflow_runs(repo: str = DEFAULT_REPO, token: str | None = None) -> list[WorkflowRun]: """Fetch recent workflow runs from repository.""" data = _make_request(f"/repos/{repo}/actions/runs?per_page=20", token) if not data or not isinstance(data, dict): diff --git a/streamlit_app/review_console.py b/streamlit_app/review_console.py index 5cb45024..c2eea34b 100644 --- a/streamlit_app/review_console.py +++ b/streamlit_app/review_console.py @@ -102,9 +102,7 @@ def generate_review_yaml( "follow_up_issues": follow_ups if follow_ups else [], } - return yaml.dump( - record, default_flow_style=False, sort_keys=False, allow_unicode=True - ) + return yaml.dump(record, default_flow_style=False, sort_keys=False, allow_unicode=True) def render_review_console( @@ -200,9 +198,7 @@ def render_review_console( # Follow-up issues st.markdown("### Follow-up Issues") - num_followups = st.number_input( - "Number of follow-ups", min_value=0, max_value=5, value=0 - ) + num_followups = st.number_input("Number of follow-ups", min_value=0, max_value=5, value=0) follow_ups = [] for i in range(int(num_followups)): diff --git a/tests/scripts/test_build_static_dashboard.py b/tests/scripts/test_build_static_dashboard.py index 37ce7aca..504c29ea 100644 --- a/tests/scripts/test_build_static_dashboard.py +++ b/tests/scripts/test_build_static_dashboard.py @@ -13,9 +13,7 @@ def load_module(): - script_path = ( - Path(__file__).resolve().parents[2] / "scripts" / "build_static_dashboard.py" - ) + script_path = Path(__file__).resolve().parents[2] / "scripts" / "build_static_dashboard.py" spec = importlib.util.spec_from_file_location("build_static_dashboard", script_path) module = importlib.util.module_from_spec(spec) sys.modules[spec.name] = module @@ -30,9 +28,7 @@ def load_module(): def write_time_log(path: Path, rows: list[dict[str, str]]) -> None: path.parent.mkdir(parents=True, exist_ok=True) with path.open("w", newline="", encoding="utf-8") as handle: - writer = csv.DictWriter( - handle, fieldnames=build_static_dashboard.TIME_LOG_FIELDS - ) + writer = csv.DictWriter(handle, fieldnames=build_static_dashboard.TIME_LOG_FIELDS) writer.writeheader() writer.writerows(rows) @@ -216,8 +212,7 @@ def test_build_dashboard_with_data(tmp_path: Path) -> None: assert "PR #34 Add feature (2025-01-02)" in dashboard assert "Recent pass rate (last 3 runs): 66.67% (2/3)" in dashboard assert ( - "Latest run: 2025-01-03T00:00:00Z - failed (tests: 10, failures: 1, errors: 0)" - in dashboard + "Latest run: 2025-01-03T00:00:00Z - failed (tests: 10, failures: 1, errors: 0)" in dashboard ) assert "## Workflows Ecosystem Linkage" in dashboard assert "stranske/Workflows" in dashboard diff --git a/tests/scripts/test_generate_month_end.py b/tests/scripts/test_generate_month_end.py index c04f2520..39c48fab 100644 --- a/tests/scripts/test_generate_month_end.py +++ b/tests/scripts/test_generate_month_end.py @@ -9,9 +9,7 @@ def load_module(): - script_path = ( - Path(__file__).resolve().parents[2] / "scripts" / "generate_month_end.py" - ) + script_path = Path(__file__).resolve().parents[2] / "scripts" / "generate_month_end.py" spec = importlib.util.spec_from_file_location("generate_month_end", script_path) module = importlib.util.module_from_spec(spec) assert spec.loader is not None diff --git a/tests/scripts/test_validate_config.py b/tests/scripts/test_validate_config.py index 2831a767..9e6a6ec1 100644 --- a/tests/scripts/test_validate_config.py +++ b/tests/scripts/test_validate_config.py @@ -7,9 +7,7 @@ from pathlib import Path -def _run_validator( - project_path: Path, dashboard_path: Path -) -> subprocess.CompletedProcess[str]: +def _run_validator(project_path: Path, dashboard_path: Path) -> subprocess.CompletedProcess[str]: repo_root = Path(__file__).resolve().parents[2] script_path = repo_root / "scripts" / "validate_config.py" command = [ @@ -135,9 +133,7 @@ def test_validate_config_rejects_invalid_dashboard_types(tmp_path: Path) -> None result = _run_validator(project_path, dashboard_path) assert result.returncode != 0 - assert "dashboard.show_numeric_scoring must be a boolean" in ( - result.stderr + result.stdout - ) + assert "dashboard.show_numeric_scoring must be a boolean" in (result.stderr + result.stdout) # Tests for single-file validation mode (--type argument) diff --git a/tests/scripts/test_validate_expense_log.py b/tests/scripts/test_validate_expense_log.py index c5fcef71..27b2b560 100644 --- a/tests/scripts/test_validate_expense_log.py +++ b/tests/scripts/test_validate_expense_log.py @@ -8,9 +8,7 @@ def load_module(): - script_path = ( - Path(__file__).resolve().parents[2] / "scripts" / "validate_expense_log.py" - ) + script_path = Path(__file__).resolve().parents[2] / "scripts" / "validate_expense_log.py" spec = importlib.util.spec_from_file_location("validate_expense_log", script_path) module = importlib.util.module_from_spec(spec) assert spec.loader is not None diff --git a/tests/scripts/test_validate_friction_log.py b/tests/scripts/test_validate_friction_log.py index 5552c4df..72383144 100644 --- a/tests/scripts/test_validate_friction_log.py +++ b/tests/scripts/test_validate_friction_log.py @@ -8,9 +8,7 @@ def load_module(): - script_path = ( - Path(__file__).resolve().parents[2] / "scripts" / "validate_friction_log.py" - ) + script_path = Path(__file__).resolve().parents[2] / "scripts" / "validate_friction_log.py" spec = importlib.util.spec_from_file_location("validate_friction_log", script_path) module = importlib.util.module_from_spec(spec) assert spec.loader is not None diff --git a/tests/scripts/test_validate_rubrics.py b/tests/scripts/test_validate_rubrics.py index 1d62abfd..e7c72886 100644 --- a/tests/scripts/test_validate_rubrics.py +++ b/tests/scripts/test_validate_rubrics.py @@ -35,9 +35,7 @@ def test_validate_rubrics_ok(tmp_path: Path) -> None: ), encoding="utf-8", ) - (rubrics_dir / "rubric_index.yml").write_text( - "rubrics:\n - sample.yml\n", encoding="utf-8" - ) + (rubrics_dir / "rubric_index.yml").write_text("rubrics:\n - sample.yml\n", encoding="utf-8") result = _run_validator(rubrics_dir, "--check-structure") diff --git a/tests/scripts/test_validate_time_log.py b/tests/scripts/test_validate_time_log.py index 365a99de..ae00cbfd 100644 --- a/tests/scripts/test_validate_time_log.py +++ b/tests/scripts/test_validate_time_log.py @@ -11,9 +11,7 @@ def load_module(): - script_path = ( - Path(__file__).resolve().parents[2] / "scripts" / "validate_time_log.py" - ) + script_path = Path(__file__).resolve().parents[2] / "scripts" / "validate_time_log.py" spec = importlib.util.spec_from_file_location("validate_time_log", script_path) module = importlib.util.module_from_spec(spec) assert spec.loader is not None @@ -101,9 +99,7 @@ def test_date_range_checks(tmp_path: Path) -> None: assert any("too old" in err for err in errors) -def test_verbose_output_in_main( - tmp_path: Path, capsys: pytest.CaptureFixture[str] -) -> None: +def test_verbose_output_in_main(tmp_path: Path, capsys: pytest.CaptureFixture[str]) -> None: path = tmp_path / "log.csv" write_csv(path, [base_row()]) diff --git a/tests/scripts/test_validate_trend_references.py b/tests/scripts/test_validate_trend_references.py index bfdb5edc..9bf5fa6c 100644 --- a/tests/scripts/test_validate_trend_references.py +++ b/tests/scripts/test_validate_trend_references.py @@ -8,12 +8,8 @@ def load_module(): - script_path = ( - Path(__file__).resolve().parents[2] / "scripts" / "validate_trend_references.py" - ) - spec = importlib.util.spec_from_file_location( - "validate_trend_references", script_path - ) + script_path = Path(__file__).resolve().parents[2] / "scripts" / "validate_trend_references.py" + spec = importlib.util.spec_from_file_location("validate_trend_references", script_path) module = importlib.util.module_from_spec(spec) sys.modules[spec.name] = module assert spec.loader is not None diff --git a/tools/coverage_trend.py b/tools/coverage_trend.py index c5a2adc3..709eb785 100755 --- a/tools/coverage_trend.py +++ b/tools/coverage_trend.py @@ -93,23 +93,13 @@ def main(args: list[str] | None = None) -> int: parser.add_argument("--coverage-xml", type=Path, help="Path to coverage.xml") parser.add_argument("--coverage-json", type=Path, help="Path to coverage.json") parser.add_argument("--baseline", type=Path, help="Path to baseline JSON") - parser.add_argument( - "--summary-path", type=Path, help="Path to output summary markdown" - ) + parser.add_argument("--summary-path", type=Path, help="Path to output summary markdown") parser.add_argument("--job-summary", type=Path, help="Path to GITHUB_STEP_SUMMARY") - parser.add_argument( - "--artifact-path", type=Path, help="Path to output trend artifact" - ) + parser.add_argument("--artifact-path", type=Path, help="Path to output trend artifact") parser.add_argument("--github-output", type=Path, help="Path to write env file") - parser.add_argument( - "--minimum", type=float, default=70.0, help="Minimum coverage threshold" - ) - parser.add_argument( - "--hotspot-limit", type=int, default=15, help="Max hotspot files to show" - ) - parser.add_argument( - "--low-threshold", type=float, default=50.0, help="Low coverage threshold" - ) + parser.add_argument("--minimum", type=float, default=70.0, help="Minimum coverage threshold") + parser.add_argument("--hotspot-limit", type=int, default=15, help="Max hotspot files to show") + parser.add_argument("--low-threshold", type=float, default=50.0, help="Low coverage threshold") parser.add_argument( "--soft", action="store_true", @@ -161,9 +151,7 @@ def main(args: list[str] | None = None) -> int: "hotspots": hotspots, "low_coverage_files": low_coverage, } - parsed.artifact_path.write_text( - json.dumps(artifact_data, indent=2), encoding="utf-8" - ) + parsed.artifact_path.write_text(json.dumps(artifact_data, indent=2), encoding="utf-8") status = "✅ Pass" if passes_minimum else "❌ Below minimum" summary = f"""## Coverage Trend @@ -180,9 +168,7 @@ def main(args: list[str] | None = None) -> int: # Add hotspot tables if we have coverage data if hotspots: - summary += _format_hotspot_table( - hotspots, "Top Coverage Hotspots (lowest coverage)" - ) + summary += _format_hotspot_table(hotspots, "Top Coverage Hotspots (lowest coverage)") if low_coverage: summary += _format_hotspot_table( diff --git a/tools/llm_provider.py b/tools/llm_provider.py index e8cb4460..977c5444 100644 --- a/tools/llm_provider.py +++ b/tools/llm_provider.py @@ -197,9 +197,7 @@ def _validate_confidence( ) # Short text means limited evidence - cap confidence confidence = min(confidence, 0.4) - logger.warning( - f"Short analysis text: {quality_context.analysis_text_length} chars" - ) + logger.warning(f"Short analysis text: {quality_context.analysis_text_length} chars") # BS Detection Rule 3: Zero tasks + high effort score = something's wrong if ( @@ -220,9 +218,7 @@ def _validate_confidence( any(phrase in reasoning_lower for phrase in no_evidence_phrases) and quality_context.has_work_evidence ): - warnings.append( - "LLM claims 'no evidence' but session has file changes/commands" - ) + warnings.append("LLM claims 'no evidence' but session has file changes/commands") confidence = min(confidence, 0.35) # BS Detection Rule 5: Data quality impacts confidence ceiling @@ -322,9 +318,7 @@ def _parse_response( confidence=adjusted_confidence, reasoning=reasoning, provider_used=self.name, - raw_confidence=( - raw_confidence if adjusted_confidence != raw_confidence else None - ), + raw_confidence=(raw_confidence if adjusted_confidence != raw_confidence else None), confidence_adjusted=adjusted_confidence != raw_confidence, quality_warnings=warnings if warnings else None, ) @@ -469,8 +463,7 @@ def analyze_completion( is_blocked = any( word in output_lower and any( - p in output_lower - for p in ["blocked", "stuck", "failed", "error", "cannot"] + p in output_lower for p in ["blocked", "stuck", "failed", "error", "cannot"] ) for word in task_words if len(word) > 3 @@ -560,8 +553,7 @@ def get_llm_provider(force_provider: str | None = None) -> LLMProvider: } if force_provider not in provider_map: raise ValueError( - f"Unknown provider: {force_provider}. " - f"Options: {list(provider_map.keys())}" + f"Unknown provider: {force_provider}. " f"Options: {list(provider_map.keys())}" ) provider_class = provider_map[force_provider] provider = provider_class() diff --git a/tools/pep517_backend.py b/tools/pep517_backend.py index bbf4c96e..471e4025 100644 --- a/tools/pep517_backend.py +++ b/tools/pep517_backend.py @@ -63,9 +63,7 @@ def _write_metadata( metadata_lines.append(f"Provides-Extra: {extra}") for requirement in requirements: metadata_lines.append(f'Requires-Dist: {requirement}; extra == "{extra}"') - (dist_info_dir / "METADATA").write_text( - "\n".join(metadata_lines) + "\n", encoding="utf-8" - ) + (dist_info_dir / "METADATA").write_text("\n".join(metadata_lines) + "\n", encoding="utf-8") (dist_info_dir / "WHEEL").write_text( "\n".join( [ @@ -98,9 +96,7 @@ def _collect_paths(root: Path) -> list[Path]: def _build_wheel(wheel_directory: str, *, editable: bool) -> str: - name, version, description, requires_python, dependencies, optional_deps = ( - _project_metadata() - ) + name, version, description, requires_python, dependencies, optional_deps = _project_metadata() dist = _normalize_dist_name(name) wheel_name = f"{dist}-{version}-py3-none-any.whl" wheel_dir = Path(wheel_directory) @@ -123,9 +119,7 @@ def _build_wheel(wheel_directory: str, *, editable: bool) -> str: if editable: pth_name = f"{dist}.pth" - (tmp_path / pth_name).write_text( - str(_project_root() / "src") + "\n", encoding="utf-8" - ) + (tmp_path / pth_name).write_text(str(_project_root() / "src") + "\n", encoding="utf-8") records.append((pth_name, *_hash_file(tmp_path / pth_name))) else: src_root = _project_root() / "src" @@ -155,9 +149,7 @@ def _build_wheel(wheel_directory: str, *, editable: bool) -> str: def _prepare_metadata(metadata_directory: str) -> str: - name, version, description, requires_python, dependencies, optional_deps = ( - _project_metadata() - ) + name, version, description, requires_python, dependencies, optional_deps = _project_metadata() dist_info_name = _dist_info_name(name, version) dist_info_dir = Path(metadata_directory) / dist_info_name _write_metadata(