From 453c847359535242111ab68e3bbe30712b45178e Mon Sep 17 00:00:00 2001 From: AIOSAI Date: Tue, 12 May 2026 22:41:03 -0700 Subject: [PATCH 1/2] fix(ci): auto-format on commit, decouple lint from tests, add codecov threshold --- .github/workflows/ci.yml | 4 +++- .github/workflows/security.yml | 3 +++ codecov.yml | 14 ++++++++++++++ pyproject.toml | 2 +- src/aipass/aipass/apps/modules/doctor.py | 10 ++++++---- src/aipass/aipass/apps/modules/doctor_wire.py | 5 +---- src/aipass/aipass/apps/modules/init_flow.py | 4 +++- .../drone/apps/handlers/git/commit_handler.py | 14 ++++++++++++++ .../drone/apps/handlers/git/dev_pr_handler.py | 4 +++- src/aipass/drone/apps/modules/git_module.py | 3 +-- .../builder/.spawn/.template_registry.json | 6 +++--- 11 files changed, 52 insertions(+), 17 deletions(-) create mode 100644 codecov.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4e8fc602..4eac18f0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,6 +6,9 @@ on: pull_request: branches: [main, dev] +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" + jobs: lint: runs-on: ubuntu-latest @@ -19,7 +22,6 @@ jobs: - run: ruff format --check src/ tests/ test: - needs: lint strategy: fail-fast: false matrix: diff --git a/.github/workflows/security.yml b/.github/workflows/security.yml index 771fd1e8..d788a6f5 100644 --- a/.github/workflows/security.yml +++ b/.github/workflows/security.yml @@ -8,6 +8,9 @@ on: schedule: - cron: "0 6 * * 1" +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" + jobs: dependency-scan: runs-on: ubuntu-latest diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 00000000..0fe567d0 --- /dev/null +++ b/codecov.yml @@ -0,0 +1,14 @@ +coverage: + status: + project: + default: + target: 75% + threshold: 2% + patch: + default: + target: 70% + +comment: + layout: "reach,diff,flags,files" + behavior: default + require_changes: false diff --git a/pyproject.toml b/pyproject.toml index 404ada93..f8cacf6e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -55,7 +55,7 @@ dev = [ "pytest>=9.0.3", "pytest-cov", "pytest-timeout", - "ruff", + "ruff>=0.11", "coverage", "pyright", "Pygments>=2.20.0", diff --git a/src/aipass/aipass/apps/modules/doctor.py b/src/aipass/aipass/apps/modules/doctor.py index 6d1f5234..e1884fe7 100644 --- a/src/aipass/aipass/apps/modules/doctor.py +++ b/src/aipass/aipass/apps/modules/doctor.py @@ -64,6 +64,7 @@ from aipass.aipass.apps.handlers.ui.progress import ( _BRANCH_ROOT = Path(__file__).resolve().parents[2] + class CheckResult(NamedTuple): """Single doctor check result.""" @@ -426,8 +427,10 @@ def _print_manual_wire_warning( console.print(f" [dim]•[/dim] {var} — {desc}") console.print() if missing_deny or missing_ask: - console.print(f"{len(missing_deny)} deny rules + {len(missing_ask)} ask rules" - " (protect ~/.secrets/, block destructive git)") + console.print( + f"{len(missing_deny)} deny rules + {len(missing_ask)} ask rules" + " (protect ~/.secrets/, block destructive git)" + ) console.print() console.print("[dim]Wire manually when ready — see .claude/hooks/README.md[/dim]") @@ -555,8 +558,7 @@ def run_doctor(verbose: bool = False, interactive: bool = False, fix: bool = Fal manifest_results = _check_provider_manifest(interactive=interactive, fix=fix) if manifest_results: groups["Services"] = [ - r for r in groups.get("Services", []) - if r.label not in ("hooks", "env vars", "permissions") + r for r in groups.get("Services", []) if r.label not in ("hooks", "env vars", "permissions") ] + manifest_results pass_count = 0 diff --git a/src/aipass/aipass/apps/modules/doctor_wire.py b/src/aipass/aipass/apps/modules/doctor_wire.py index fd91c0ec..367cbab8 100644 --- a/src/aipass/aipass/apps/modules/doctor_wire.py +++ b/src/aipass/aipass/apps/modules/doctor_wire.py @@ -157,10 +157,7 @@ def _auto_wire_provider(manifest_path: Path, interactive: bool = True) -> List[s event_hooks = [event_hooks] settings["hooks"][event] = event_hooks - already_wired = any( - isinstance(h, dict) and script in json.dumps(h) - for h in event_hooks - ) + already_wired = any(isinstance(h, dict) and script in json.dumps(h) for h in event_hooks) if not already_wired: if source_type == "user": hook_path = f"~/.claude/hooks/{script}" diff --git a/src/aipass/aipass/apps/modules/init_flow.py b/src/aipass/aipass/apps/modules/init_flow.py index 6f542b06..46bfb93e 100644 --- a/src/aipass/aipass/apps/modules/init_flow.py +++ b/src/aipass/aipass/apps/modules/init_flow.py @@ -615,7 +615,9 @@ def _write_init_report(agent_path: str, accumulated: Dict[str, Any], dry_run: bo provider_gaps = accumulated.get("provider_gaps", {}) if provider_gaps: report["provider_gaps"] = provider_gaps - report["provider_action"] = "Provider settings need configuring. Tell the user what is missing and point them to provider_manifest.json for details." + report["provider_action"] = ( + "Provider settings need configuring. Tell the user what is missing and point them to provider_manifest.json for details." + ) report_path = dropbox / "init_report.json" report_path.write_text(json.dumps(report, indent=2) + "\n", encoding="utf-8") logger.info("[init_flow] init report written to %s", report_path) diff --git a/src/aipass/drone/apps/handlers/git/commit_handler.py b/src/aipass/drone/apps/handlers/git/commit_handler.py index 2e6dec6b..6ff07144 100644 --- a/src/aipass/drone/apps/handlers/git/commit_handler.py +++ b/src/aipass/drone/apps/handlers/git/commit_handler.py @@ -67,6 +67,20 @@ def commit_changes( repo_root = find_repo_root() if all_files: + import shutil + + ruff_bin = shutil.which("ruff") + if not ruff_bin: + venv_ruff = repo_root / ".venv" / "bin" / "ruff" + if venv_ruff.exists(): + ruff_bin = str(venv_ruff) + if ruff_bin: + subprocess.run( + [ruff_bin, "format", "src/", "tests/"], + capture_output=True, + text=True, + cwd=str(repo_root), + ) add_result = subprocess.run( ["git", "add", "-A"], capture_output=True, diff --git a/src/aipass/drone/apps/handlers/git/dev_pr_handler.py b/src/aipass/drone/apps/handlers/git/dev_pr_handler.py index 3b5b6023..ad7369aa 100644 --- a/src/aipass/drone/apps/handlers/git/dev_pr_handler.py +++ b/src/aipass/drone/apps/handlers/git/dev_pr_handler.py @@ -82,7 +82,9 @@ def create_dev_pr(description: str) -> dict: if "github.com" in line: existing_url = line.strip() break - msg = f"Pushed to dev. PR already open: {existing_url}" if existing_url else "Pushed to dev. PR already open." + msg = ( + f"Pushed to dev. PR already open: {existing_url}" if existing_url else "Pushed to dev. PR already open." + ) json_handler.log_operation("dev_pr_push_existing", {"pr_url": existing_url, "description": description}) return {"success": True, "message": msg, "pr_url": existing_url} return {"success": False, "message": f"PR creation failed: {stderr}", "pr_url": ""} diff --git a/src/aipass/drone/apps/modules/git_module.py b/src/aipass/drone/apps/modules/git_module.py index 492e3f4d..fb94515e 100644 --- a/src/aipass/drone/apps/modules/git_module.py +++ b/src/aipass/drone/apps/modules/git_module.py @@ -570,8 +570,7 @@ def get_help(command: str | None = None) -> str: ) if command == "delete-branch": return ( - "git delete-branch — Delete a remote branch [owner]\n" - " Protected: main and dev cannot be deleted.\n" + "git delete-branch — Delete a remote branch [owner]\n Protected: main and dev cannot be deleted.\n" ) if command == "commit": return ( diff --git a/src/aipass/spawn/templates/builder/.spawn/.template_registry.json b/src/aipass/spawn/templates/builder/.spawn/.template_registry.json index e5e17e8e..79d7b496 100644 --- a/src/aipass/spawn/templates/builder/.spawn/.template_registry.json +++ b/src/aipass/spawn/templates/builder/.spawn/.template_registry.json @@ -1,7 +1,7 @@ { "metadata": { "version": "1.0.0", - "last_updated": "2026-05-10", + "last_updated": "2026-05-12", "description": "Template file tracking registry for ID-based updates" }, "files": { @@ -155,7 +155,7 @@ "content_hash": "a4cf0a8e3b4f", "has_branch_placeholder": false }, - "f026": { + "f015": { "path": "apps/modules/__init__.py", "name": "__init__.py", "content_hash": "e3b0c44298fc", @@ -263,7 +263,7 @@ "content_hash": "28e9ae373563", "has_branch_placeholder": false }, - "f015": { + "f026": { "path": "apps/plugins/__init__.py", "name": "__init__.py", "content_hash": "e3b0c44298fc", From 87920fff09c857cc7ca4edb5b734f93510a81695 Mon Sep 17 00:00:00 2001 From: AIOSAI Date: Tue, 12 May 2026 22:47:29 -0700 Subject: [PATCH 2/2] fix(tests): update drone tests for S144 handler changes (sync branch-aware, checkout auto-create, help text) --- src/aipass/drone/tests/test_git_access.py | 21 +++++++++++++-------- src/aipass/drone/tests/test_git_module.py | 12 ++++++------ 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/src/aipass/drone/tests/test_git_access.py b/src/aipass/drone/tests/test_git_access.py index 957e5082..d634538e 100644 --- a/src/aipass/drone/tests/test_git_access.py +++ b/src/aipass/drone/tests/test_git_access.py @@ -343,15 +343,19 @@ class TestCommitChanges: assert "nothing to commit" in result["stderr"].lower() def test_commit_all_stages_first(self, repo_dir: Path) -> None: + mock_ruff = MagicMock(returncode=0, stdout="", stderr="") mock_add = MagicMock(returncode=0, stderr="") mock_diff = MagicMock(returncode=1, stdout="", stderr="") mock_commit = MagicMock(returncode=0, stdout="[main def456] all commit", stderr="") branch_dir = repo_dir / "src" / "aipass" / "api" - with patch( - "aipass.drone.apps.handlers.git.commit_handler.subprocess.run", - side_effect=[mock_add, mock_diff, mock_commit], + with ( + patch("shutil.which", return_value="/usr/bin/ruff"), + patch( + "aipass.drone.apps.handlers.git.commit_handler.subprocess.run", + side_effect=[mock_ruff, mock_add, mock_diff, mock_commit], + ), ): result = commit_changes("all commit", branch_dir=branch_dir, all_files=True) @@ -427,15 +431,16 @@ class TestCheckoutHandler: def test_checkout_git_failure(self, repo_dir: Path) -> None: mock_status = MagicMock(returncode=0, stdout="", stderr="") mock_checkout = MagicMock(returncode=1, stdout="", stderr="error: pathspec 'main' did not match") + mock_create = MagicMock(returncode=0, stdout="Switched to a new branch 'main'", stderr="") with patch( "aipass.drone.apps.handlers.git.checkout_handler.subprocess.run", - side_effect=[mock_status, mock_checkout], + side_effect=[mock_status, mock_checkout, mock_create], ): result = checkout_branch("main") - assert result["exit_code"] == 1 - assert result["current_branch"] == "" + assert result["exit_code"] == 0 + assert result["current_branch"] == "main" # =========================================================================== @@ -554,11 +559,11 @@ class TestUpdatedHelp: assert "global" in text.lower() assert "owner" in text.lower() - def test_help_marks_pr_deprecated(self) -> None: + def test_help_marks_pr_legacy(self) -> None: from aipass.drone.apps.modules.git_module import get_help text = get_help() - assert "deprecated" in text.lower() + assert "legacy" in text.lower() def test_introspection_includes_new_handlers(self) -> None: from aipass.drone.apps.modules.git_module import get_introspective diff --git a/src/aipass/drone/tests/test_git_module.py b/src/aipass/drone/tests/test_git_module.py index 7f243e1d..30a6cc48 100644 --- a/src/aipass/drone/tests/test_git_module.py +++ b/src/aipass/drone/tests/test_git_module.py @@ -295,14 +295,14 @@ class TestSyncHandler: registry.write_text("{}", encoding="utf-8") monkeypatch.chdir(tmp_path) - mock_checkout = MagicMock(returncode=0, stdout="Switched to branch 'main'", stderr="") + mock_head = MagicMock(returncode=0, stdout="main", stderr="") mock_fetch = MagicMock(returncode=0, stdout="", stderr="") mock_rev_list = MagicMock(returncode=0, stdout="0\t0\n", stderr="") mock_pull = MagicMock(returncode=0, stdout="Already up to date.", stderr="") with patch( "aipass.drone.apps.handlers.git.sync_handler.subprocess.run", - side_effect=[mock_checkout, mock_fetch, mock_rev_list, mock_pull], + side_effect=[mock_head, mock_fetch, mock_rev_list, mock_pull], ): result = sync_main() @@ -335,14 +335,14 @@ class TestSyncHandler: registry.write_text("{}", encoding="utf-8") monkeypatch.chdir(tmp_path) - mock_checkout = MagicMock(returncode=0, stdout="", stderr="") + mock_head = MagicMock(returncode=0, stdout="main", stderr="") mock_fetch = MagicMock(returncode=0, stdout="", stderr="") mock_rev_list = MagicMock(returncode=0, stdout="0\t1\n", stderr="") mock_pull = MagicMock(returncode=1, stdout="", stderr="fatal: unable to access remote") with patch( "aipass.drone.apps.handlers.git.sync_handler.subprocess.run", - side_effect=[mock_checkout, mock_fetch, mock_rev_list, mock_pull], + side_effect=[mock_head, mock_fetch, mock_rev_list, mock_pull], ): result = sync_main() @@ -658,14 +658,14 @@ class TestGitModuleRouting: registry.write_text("{}", encoding="utf-8") monkeypatch.chdir(tmp_path) - mock_checkout = MagicMock(returncode=0, stdout="", stderr="") + mock_head = MagicMock(returncode=0, stdout="main", stderr="") mock_fetch = MagicMock(returncode=0, stdout="", stderr="") mock_rev_list = MagicMock(returncode=0, stdout="0\t0\n", stderr="") mock_pull = MagicMock(returncode=0, stdout="Already up to date.", stderr="") with patch( "aipass.drone.apps.handlers.git.sync_handler.subprocess.run", - side_effect=[mock_checkout, mock_fetch, mock_rev_list, mock_pull], + side_effect=[mock_head, mock_fetch, mock_rev_list, mock_pull], ): result = handle_command("sync")