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",