From 188e131cb6204c64b5317cc490fd11578617db78 Mon Sep 17 00:00:00 2001 From: goonobu-dot Date: Mon, 15 Jun 2026 19:16:59 +0900 Subject: [PATCH] feat: add OSS maintenance audit --- CHANGELOG.md | 1 + README.md | 24 ++- .../plans/2026-06-15-audit-command.md | 52 +++++ examples/OSS_MAINTENANCE_AUDIT.generated.md | 33 +++ src/codex_maintainer_kit/audit.py | 196 ++++++++++++++++++ src/codex_maintainer_kit/cli.py | 20 ++ tests/test_audit.py | 88 ++++++++ tests/test_cli.py | 15 ++ 8 files changed, 422 insertions(+), 7 deletions(-) create mode 100644 docs/superpowers/plans/2026-06-15-audit-command.md create mode 100644 examples/OSS_MAINTENANCE_AUDIT.generated.md create mode 100644 src/codex_maintainer_kit/audit.py create mode 100644 tests/test_audit.py diff --git a/CHANGELOG.md b/CHANGELOG.md index 00b5376..f04c20f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +- Add `codex-maintainer-kit audit` for OSS maintenance health scoring and prioritized next actions. - Improve generated issue drafts with suggested labels, verification commands, and maintainer checklists. - Add a human-reviewed release workflow for Codex-assisted maintenance releases. - Add Java, .NET, and Swift project hint detection. diff --git a/README.md b/README.md index 5843c22..e1d45a6 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ Codex can help with that work, but it needs clear repository context and human r - Detects common maintainer files such as `README.md`, `LICENSE`, `CONTRIBUTING.md`, `SECURITY.md`, `AGENTS.md`, issue templates, CI workflows, and tests. - Detects simple project hints such as Python, JavaScript, Go, Rust, Ruby, PHP, Java, .NET, and Swift markers. - Summarizes current Git working-tree changes. +- Generates an OSS maintenance audit with a health score, maintainer essentials checklist, and prioritized next actions. - Generates a Markdown maintainer brief with: - repository readiness checklist - Codex task queue @@ -55,6 +56,12 @@ The action runs this CLI and uploads `MAINTAINER_BRIEF.md`, `CODEX_TASKS.md`, an ## Usage +Generate an OSS maintenance audit: + +```bash +codex-maintainer-kit audit /path/to/repo --output OSS_MAINTENANCE_AUDIT.md +``` + Generate a maintainer brief: ```bash @@ -117,6 +124,7 @@ Existing files are skipped unless `--force` is passed. See: +- [examples/OSS_MAINTENANCE_AUDIT.generated.md](examples/OSS_MAINTENANCE_AUDIT.generated.md), generated from this repository - [examples/MAINTAINER_BRIEF.example.md](examples/MAINTAINER_BRIEF.example.md) - [examples/MAINTAINER_BRIEF.generated.md](examples/MAINTAINER_BRIEF.generated.md), generated from this repository - [examples/CODEX_TASKS.example.md](examples/CODEX_TASKS.example.md) @@ -145,13 +153,14 @@ Codex Maintainer Kit focuses on a narrower workflow: creating a practical mainte ## Maintainer Workflow -1. Run `codex-maintainer-kit brief`, or run `codex-maintainer-action` in GitHub Actions. -2. Review the generated checklist. -3. Run `codex-maintainer-kit tasks`. -4. Convert the generated task file or issue drafts into scoped maintenance work. -5. Ask Codex to make the smallest useful change. -6. Run tests and inspect the diff. -7. Merge only after human review. +1. Run `codex-maintainer-kit audit` to understand the repository's maintenance health. +2. Run `codex-maintainer-kit brief`, or run `codex-maintainer-action` in GitHub Actions. +3. Review the generated checklist. +4. Run `codex-maintainer-kit tasks`. +5. Convert the generated task file or issue drafts into scoped maintenance work. +6. Ask Codex to make the smallest useful change. +7. Run tests and inspect the diff. +8. Merge only after human review. ## Development @@ -165,6 +174,7 @@ Run the CLI without installing: ```bash PYTHONPATH=src python3 -m codex_maintainer_kit.cli brief . --output /tmp/maintainer-brief.md +PYTHONPATH=src python3 -m codex_maintainer_kit.cli audit . --output /tmp/oss-maintenance-audit.md PYTHONPATH=src python3 -m codex_maintainer_kit.cli tasks . --output /tmp/codex-tasks.md ``` diff --git a/docs/superpowers/plans/2026-06-15-audit-command.md b/docs/superpowers/plans/2026-06-15-audit-command.md new file mode 100644 index 0000000..698814a --- /dev/null +++ b/docs/superpowers/plans/2026-06-15-audit-command.md @@ -0,0 +1,52 @@ +# Audit Command Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Add an `audit` command that generates a practical OSS maintenance health report. + +**Architecture:** Keep scanning in `scanner.py`, add audit scoring and report rendering in a new `audit.py`, and wire the CLI in `cli.py`. The audit report reuses existing repository facts and gives maintainers a prioritized next-step summary. + +**Tech Stack:** Python 3.9+, argparse, pytest. + +--- + +### Task 1: Audit Report Contract + +**Files:** +- Create: `tests/test_audit.py` +- Modify: `tests/test_cli.py` + +- [ ] Write failing tests for audit item classification, summary score, Markdown rendering, and CLI file output. +- [ ] Run targeted tests and confirm they fail because `codex_maintainer_kit.audit` and the CLI command do not exist. + +### Task 2: Audit Implementation + +**Files:** +- Create: `src/codex_maintainer_kit/audit.py` +- Modify: `src/codex_maintainer_kit/cli.py` + +- [ ] Add `AuditItem` and `AuditReport` dataclasses. +- [ ] Add `build_audit_report(scan)` with weighted checks for maintainer essentials. +- [ ] Add `render_audit_markdown(report)` with score, status summary, prioritized next actions, and Codex prompt. +- [ ] Add `codex-maintainer-kit audit` with optional `--output`. +- [ ] Run targeted tests until they pass. + +### Task 3: Public Documentation + +**Files:** +- Modify: `README.md` +- Modify: `CHANGELOG.md` +- Create: `examples/OSS_MAINTENANCE_AUDIT.generated.md` + +- [ ] Document the `audit` command and how it fits before `brief` and `tasks`. +- [ ] Generate an example audit report from this repository. +- [ ] Update changelog with the unreleased audit feature. + +### Task 4: Verification + +**Files:** +- All changed files. + +- [ ] Run `python3 -m pytest -p no:cacheprovider tests -q`. +- [ ] Run the new CLI command against this repository. +- [ ] Review `git diff --check` and `git status --short`. diff --git a/examples/OSS_MAINTENANCE_AUDIT.generated.md b/examples/OSS_MAINTENANCE_AUDIT.generated.md new file mode 100644 index 0000000..4c490d2 --- /dev/null +++ b/examples/OSS_MAINTENANCE_AUDIT.generated.md @@ -0,0 +1,33 @@ +# OSS Maintenance Audit + +Repository: `/Users/admin/Documents/skill/codex-maintainer-kit` +Project hints: python +Git state: `clean` +Health score: **100/100** (`ready`) + +## Maintainer Essentials + +| Check | Status | Priority | Why it matters | +| --- | --- | --- | --- | +| LICENSE | ready | high | Users need clear reuse rights before they can adopt or contribute to the project. | +| AGENTS.md | ready | high | Codex and other agents need repository rules, commands, and human review boundaries. | +| Test suite | ready | high | Automated tests let maintainers verify AI-assisted changes before merge. | +| README | ready | medium | A useful README explains purpose, setup, usage, and the maintainer workflow. | +| CI workflow | ready | medium | CI catches regressions and gives contributors fast feedback. | +| CONTRIBUTING | ready | medium | Contribution docs reduce maintainer back-and-forth and clarify expectations. | +| SECURITY | ready | medium | Security reporting instructions keep sensitive reports out of public issues. | +| Issue templates | ready | low | Templates turn vague reports into actionable maintenance work. | +| CHANGELOG | ready | low | Release notes help users understand what changed and whether to upgrade. | +| CODE_OF_CONDUCT | ready | low | Community expectations help public projects handle collaboration consistently. | + +## Prioritized Next Actions + +- [ ] Run a focused Codex maintenance review for stale docs, missing edge-case tests, and small safe improvements. + +## Suggested Codex Prompt + +Review this OSS maintenance audit. Pick the highest-priority missing item, make the smallest useful change, run the documented verification command, and leave a concise maintainer note explaining the tradeoff and risk. + +## Human Review Rule + +Do not auto-merge AI-generated maintenance changes. A human maintainer must review the diff, confirm the project policy choices, and verify the result before merge. diff --git a/src/codex_maintainer_kit/audit.py b/src/codex_maintainer_kit/audit.py new file mode 100644 index 0000000..61c8eb2 --- /dev/null +++ b/src/codex_maintainer_kit/audit.py @@ -0,0 +1,196 @@ +from __future__ import annotations + +from dataclasses import dataclass + +from codex_maintainer_kit.scanner import RepositoryScan + + +@dataclass(frozen=True) +class AuditCheck: + key: str + label: str + weight: int + priority: str + why_it_matters: str + next_action: str + + +@dataclass(frozen=True) +class AuditItem: + label: str + status: str + priority: str + why_it_matters: str + next_action: str + + +@dataclass(frozen=True) +class AuditReport: + repository: str + project_hints: list[str] + git_state: str + score: int + status: str + items: list[AuditItem] + next_actions: list[str] + + +AUDIT_CHECKS = [ + AuditCheck( + key="license", + label="LICENSE", + weight=14, + priority="high", + why_it_matters="Users need clear reuse rights before they can adopt or contribute to the project.", + next_action="Add LICENSE: choose the intended license and update README references if needed.", + ), + AuditCheck( + key="agents", + label="AGENTS.md", + weight=12, + priority="high", + why_it_matters="Codex and other agents need repository rules, commands, and human review boundaries.", + next_action="Add AGENTS.md: document setup, test commands, coding rules, and the human review rule.", + ), + AuditCheck( + key="tests", + label="Test suite", + weight=12, + priority="high", + why_it_matters="Automated tests let maintainers verify AI-assisted changes before merge.", + next_action="Add Test Suite: cover the smallest public behavior users rely on.", + ), + AuditCheck( + key="readme", + label="README", + weight=11, + priority="medium", + why_it_matters="A useful README explains purpose, setup, usage, and the maintainer workflow.", + next_action="Update README: add purpose, install steps, usage examples, and verification commands.", + ), + AuditCheck( + key="ci", + label="CI workflow", + weight=11, + priority="medium", + why_it_matters="CI catches regressions and gives contributors fast feedback.", + next_action="Add CI Workflow: run the documented test command on pull requests.", + ), + AuditCheck( + key="contributing", + label="CONTRIBUTING", + weight=10, + priority="medium", + why_it_matters="Contribution docs reduce maintainer back-and-forth and clarify expectations.", + next_action="Add CONTRIBUTING: document setup, tests, PR expectations, and AI-assisted contribution rules.", + ), + AuditCheck( + key="security", + label="SECURITY", + weight=10, + priority="medium", + why_it_matters="Security reporting instructions keep sensitive reports out of public issues.", + next_action="Add SECURITY: explain how to report vulnerabilities and what is in scope.", + ), + AuditCheck( + key="issue_templates", + label="Issue templates", + weight=8, + priority="low", + why_it_matters="Templates turn vague reports into actionable maintenance work.", + next_action="Add Issue Templates: include expected behavior, context, and verification fields.", + ), + AuditCheck( + key="changelog", + label="CHANGELOG", + weight=7, + priority="low", + why_it_matters="Release notes help users understand what changed and whether to upgrade.", + next_action="Add CHANGELOG: record the current release state without claiming future work.", + ), + AuditCheck( + key="code_of_conduct", + label="CODE_OF_CONDUCT", + weight=5, + priority="low", + why_it_matters="Community expectations help public projects handle collaboration consistently.", + next_action="Add CODE_OF_CONDUCT: set basic contribution behavior and maintainer response expectations.", + ), +] + + +def build_audit_report(scan: RepositoryScan) -> AuditReport: + total_weight = sum(check.weight for check in AUDIT_CHECKS) + earned_weight = sum(check.weight for check in AUDIT_CHECKS if scan.files.get(check.key, False)) + score = round((earned_weight / total_weight) * 100) + items = [_build_item(scan, check) for check in AUDIT_CHECKS] + next_actions = [item.next_action for item in items if item.status == "missing"][:5] + if not next_actions: + next_actions = ["Run a focused Codex maintenance review for stale docs, missing edge-case tests, and small safe improvements."] + + return AuditReport( + repository=str(scan.root), + project_hints=scan.project_hints, + git_state=scan.git_state.status, + score=score, + status=_status_for_score(score), + items=items, + next_actions=next_actions, + ) + + +def render_audit_markdown(report: AuditReport) -> str: + lines = [ + "# OSS Maintenance Audit", + "", + f"Repository: `{report.repository}`", + f"Project hints: {', '.join(report.project_hints)}", + f"Git state: `{report.git_state}`", + f"Health score: **{report.score}/100** (`{report.status}`)", + "", + "## Maintainer Essentials", + "", + "| Check | Status | Priority | Why it matters |", + "| --- | --- | --- | --- |", + ] + + for item in report.items: + lines.append(f"| {item.label} | {item.status} | {item.priority} | {item.why_it_matters} |") + + lines.extend(["", "## Prioritized Next Actions", ""]) + for action in report.next_actions: + lines.append(f"- [ ] {action}") + + lines.extend( + [ + "", + "## Suggested Codex Prompt", + "", + "Review this OSS maintenance audit. Pick the highest-priority missing item, make the smallest useful change, run the documented verification command, and leave a concise maintainer note explaining the tradeoff and risk.", + "", + "## Human Review Rule", + "", + "Do not auto-merge AI-generated maintenance changes. A human maintainer must review the diff, confirm the project policy choices, and verify the result before merge.", + "", + ] + ) + return "\n".join(lines) + + +def _build_item(scan: RepositoryScan, check: AuditCheck) -> AuditItem: + present = scan.files.get(check.key, False) + return AuditItem( + label=check.label, + status="ready" if present else "missing", + priority=check.priority, + why_it_matters=check.why_it_matters, + next_action=check.next_action, + ) + + +def _status_for_score(score: int) -> str: + if score >= 90: + return "ready" + if score >= 70: + return "mostly-ready" + return "needs-work" diff --git a/src/codex_maintainer_kit/cli.py b/src/codex_maintainer_kit/cli.py index 96239b6..ec4b49c 100644 --- a/src/codex_maintainer_kit/cli.py +++ b/src/codex_maintainer_kit/cli.py @@ -4,6 +4,7 @@ from pathlib import Path import sys +from codex_maintainer_kit.audit import build_audit_report, render_audit_markdown from codex_maintainer_kit.config import load_config from codex_maintainer_kit.renderer import render_maintenance_brief from codex_maintainer_kit.scanner import scan_repository @@ -56,6 +57,8 @@ def main(argv: list[str] | None = None) -> int: parser = _build_parser() args = parser.parse_args(argv) + if args.command == "audit": + return _audit(args) if args.command == "brief": return _brief(args) if args.command == "init": @@ -74,6 +77,10 @@ def _build_parser() -> argparse.ArgumentParser: ) subparsers = parser.add_subparsers(dest="command") + audit = subparsers.add_parser("audit", help="Generate an OSS maintenance health audit.") + audit.add_argument("repo", nargs="?", default=".", help="Repository path to inspect.") + audit.add_argument("--output", "-o", help="Write Markdown to this file instead of stdout.") + brief = subparsers.add_parser("brief", help="Generate a Codex-ready maintainer brief.") brief.add_argument("repo", nargs="?", default=".", help="Repository path to inspect.") brief.add_argument("--output", "-o", help="Write Markdown to this file instead of stdout.") @@ -91,6 +98,19 @@ def _build_parser() -> argparse.ArgumentParser: return parser +def _audit(args: argparse.Namespace) -> int: + scan = scan_repository(args.repo) + report = build_audit_report(scan) + markdown = render_audit_markdown(report) + if args.output: + output = Path(args.output) + output.parent.mkdir(parents=True, exist_ok=True) + output.write_text(markdown, encoding="utf-8") + else: + print(markdown) + return 0 + + def _brief(args: argparse.Namespace) -> int: scan = scan_repository(args.repo) markdown = render_maintenance_brief(scan) diff --git a/tests/test_audit.py b/tests/test_audit.py new file mode 100644 index 0000000..015872d --- /dev/null +++ b/tests/test_audit.py @@ -0,0 +1,88 @@ +from pathlib import Path + +from codex_maintainer_kit.audit import build_audit_report, render_audit_markdown +from codex_maintainer_kit.scanner import GitState, RepositoryScan + + +def _scan(files: dict[str, bool]) -> RepositoryScan: + return RepositoryScan( + root=Path("/repo/demo"), + files=files, + project_hints=["python"], + git_state=GitState(status="clean", changed_files=[]), + ) + + +def test_build_audit_report_scores_ready_repository() -> None: + report = build_audit_report( + _scan( + { + "readme": True, + "license": True, + "contributing": True, + "code_of_conduct": True, + "security": True, + "changelog": True, + "agents": True, + "issue_templates": True, + "ci": True, + "tests": True, + } + ) + ) + + assert report.score == 100 + assert report.status == "ready" + assert all(item.status == "ready" for item in report.items) + assert report.next_actions == ["Run a focused Codex maintenance review for stale docs, missing edge-case tests, and small safe improvements."] + + +def test_build_audit_report_prioritizes_missing_maintainer_essentials() -> None: + report = build_audit_report( + _scan( + { + "readme": True, + "license": False, + "contributing": False, + "code_of_conduct": True, + "security": False, + "changelog": True, + "agents": False, + "issue_templates": False, + "ci": True, + "tests": False, + } + ) + ) + + assert report.score < 70 + assert report.status == "needs-work" + assert [action.split(":", 1)[0] for action in report.next_actions[:3]] == ["Add LICENSE", "Add AGENTS.md", "Add Test Suite"] + + +def test_render_audit_markdown_is_maintainer_friendly() -> None: + report = build_audit_report( + _scan( + { + "readme": True, + "license": False, + "contributing": True, + "code_of_conduct": True, + "security": True, + "changelog": True, + "agents": False, + "issue_templates": True, + "ci": True, + "tests": True, + } + ) + ) + + markdown = render_audit_markdown(report) + + assert markdown.startswith("# OSS Maintenance Audit") + assert "Health score:" in markdown + assert "## Maintainer Essentials" in markdown + assert "## Prioritized Next Actions" in markdown + assert "## Suggested Codex Prompt" in markdown + assert "| LICENSE | missing | high |" in markdown diff --git a/tests/test_cli.py b/tests/test_cli.py index fedc11d..55378a3 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -41,6 +41,21 @@ def test_tasks_command_writes_markdown_file(tmp_path: Path) -> None: assert "## Task 1: Add LICENSE" in text +def test_audit_command_writes_markdown_file(tmp_path: Path) -> None: + repo = tmp_path / "repo" + repo.mkdir() + (repo / "README.md").write_text("# Demo\n", encoding="utf-8") + output = tmp_path / "OSS_MAINTENANCE_AUDIT.md" + + exit_code = main(["audit", str(repo), "--output", str(output)]) + + assert exit_code == 0 + text = output.read_text(encoding="utf-8") + assert text.startswith("# OSS Maintenance Audit") + assert "Health score:" in text + assert "## Prioritized Next Actions" in text + + def test_tasks_command_writes_json_file(tmp_path: Path) -> None: repo = tmp_path / "repo" repo.mkdir()