openaca 0.1.0b1__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openaca-0.1.0b1/.claude/settings.json +35 -0
- openaca-0.1.0b1/.claude/settings.local.json +10 -0
- openaca-0.1.0b1/.github/ISSUE_TEMPLATE/beta-feedback.md +68 -0
- openaca-0.1.0b1/.github/ISSUE_TEMPLATE/config.yml +8 -0
- openaca-0.1.0b1/.github/workflows/autofix.yml +309 -0
- openaca-0.1.0b1/.github/workflows/ci.yml +70 -0
- openaca-0.1.0b1/.github/workflows/claude.yml +142 -0
- openaca-0.1.0b1/.github/workflows/publish.yml +87 -0
- openaca-0.1.0b1/.github/workflows/self-scan.yml +58 -0
- openaca-0.1.0b1/.gitignore +237 -0
- openaca-0.1.0b1/.openaca-seed-state-npm.json +6 -0
- openaca-0.1.0b1/.openaca-seed-state-pypi.json +6 -0
- openaca-0.1.0b1/.python-version +1 -0
- openaca-0.1.0b1/AGENTS.md +1 -0
- openaca-0.1.0b1/CLAUDE.md +173 -0
- openaca-0.1.0b1/CONTRIBUTING.md +262 -0
- openaca-0.1.0b1/LICENSE +201 -0
- openaca-0.1.0b1/PKG-INFO +464 -0
- openaca-0.1.0b1/README.md +445 -0
- openaca-0.1.0b1/SECURITY.md +32 -0
- openaca-0.1.0b1/action.yml +75 -0
- openaca-0.1.0b1/docs/adrs/0001-licenses.md +49 -0
- openaca-0.1.0b1/docs/adrs/0002-schema-extension-key.md +47 -0
- openaca-0.1.0b1/docs/adrs/0003-single-namespace-architecture.md +56 -0
- openaca-0.1.0b1/docs/adrs/0004-advisory-id-year.md +66 -0
- openaca-0.1.0b1/docs/adrs/0005-manifest-parsers-posix-only.md +123 -0
- openaca-0.1.0b1/docs/adrs/0006-openaca-scan-subcommands-and-attribution.md +122 -0
- openaca-0.1.0b1/docs/adrs/0007-component-inventory-and-host-adapters.md +183 -0
- openaca-0.1.0b1/docs/adrs/0008-lockfile-dispatch-and-osv-federation.md +125 -0
- openaca-0.1.0b1/docs/adrs/0009-overlay-only-v0.md +64 -0
- openaca-0.1.0b1/docs/adrs/0010-overlay-taxonomies-and-seeding.md +86 -0
- openaca-0.1.0b1/docs/adrs/0011-llm-assisted-seed-annotation.md +78 -0
- openaca-0.1.0b1/docs/adrs/0012-minimal-overlay-schema.md +116 -0
- openaca-0.1.0b1/docs/adrs/0013-non-package-component-identities.md +82 -0
- openaca-0.1.0b1/docs/adrs/0014-rename-extension-key-to-openaca.md +64 -0
- openaca-0.1.0b1/docs/adrs/0015-overlay-data-cc-by-inlined-in-readme.md +69 -0
- openaca-0.1.0b1/docs/adrs/0016-agent-component-identity-and-scan-output.md +126 -0
- openaca-0.1.0b1/docs/adrs/HOOK-PROMPT.md +1 -0
- openaca-0.1.0b1/docs/adrs/INDEX.md +42 -0
- openaca-0.1.0b1/docs/adrs/TEMPLATE.md +42 -0
- openaca-0.1.0b1/docs/deploy.md +84 -0
- openaca-0.1.0b1/docs/frameworks/README.md +24 -0
- openaca-0.1.0b1/docs/frameworks/mitre-atlas.md +212 -0
- openaca-0.1.0b1/docs/frameworks/owasp-agentic-ai-top-10-2026.md +363 -0
- openaca-0.1.0b1/docs/frameworks/owasp-agentic-skills-top-10-2026.md +337 -0
- openaca-0.1.0b1/docs/frameworks/owasp-llm-top-10-2025.md +350 -0
- openaca-0.1.0b1/docs/frameworks/owasp-mcp-top-10-2025.md +358 -0
- openaca-0.1.0b1/docs/plans/001-schema-and-tooling.md +1321 -0
- openaca-0.1.0b1/docs/plans/002-first-advisories.md +745 -0
- openaca-0.1.0b1/docs/plans/003-manifest-parsers.md +830 -0
- openaca-0.1.0b1/docs/plans/004-static-export.md +646 -0
- openaca-0.1.0b1/docs/plans/005-reference-action.md +668 -0
- openaca-0.1.0b1/docs/plans/006-disclosure-policy.md +413 -0
- openaca-0.1.0b1/docs/plans/007-fs-mode-cli-and-attribution.md +1153 -0
- openaca-0.1.0b1/docs/plans/008-component-inventory.md +416 -0
- openaca-0.1.0b1/docs/plans/009-plugin-internal-deps.md +2804 -0
- openaca-0.1.0b1/docs/plans/010-seed-overlay-pipeline.md +135 -0
- openaca-0.1.0b1/docs/plans/011-minimal-overlay-schema.md +114 -0
- openaca-0.1.0b1/docs/plans/012-candidate-annotation-surface-lock.md +905 -0
- openaca-0.1.0b1/docs/plans/013-rename-asve-to-openaca.md +73 -0
- openaca-0.1.0b1/docs/plans/014-posture-findings.md +1007 -0
- openaca-0.1.0b1/docs/plans/015-agent-component-identity-and-scan-output.md +561 -0
- openaca-0.1.0b1/docs/plans/016-claude-code-parser-coverage.md +92 -0
- openaca-0.1.0b1/docs/plans/README.md +51 -0
- openaca-0.1.0b1/docs/posture/README.md +50 -0
- openaca-0.1.0b1/docs/posture/openaca-posture-insecure-transport.md +55 -0
- openaca-0.1.0b1/docs/posture/openaca-posture-mutable-install-reference.md +75 -0
- openaca-0.1.0b1/docs/sarif-conventions.md +53 -0
- openaca-0.1.0b1/docs/seed-review-rules.md +121 -0
- openaca-0.1.0b1/docs/specs/openaca-thesis.md +251 -0
- openaca-0.1.0b1/examples/skills/claude/openaca-candidate-review/SKILL.md +155 -0
- openaca-0.1.0b1/findings.sarif +549 -0
- openaca-0.1.0b1/overlays/CVE-2026-20205.yaml +9 -0
- openaca-0.1.0b1/overlays/GHSA-3ch2-jxxc-v4xf.yaml +12 -0
- openaca-0.1.0b1/overlays/GHSA-3q26-f695-pp76.yaml +12 -0
- openaca-0.1.0b1/overlays/GHSA-6xpm-ggf7-wc3p.yaml +12 -0
- openaca-0.1.0b1/overlays/GHSA-m4qw-j7mx-qv6h.yaml +12 -0
- openaca-0.1.0b1/overlays/GHSA-rwc2-f344-q6w6.yaml +12 -0
- openaca-0.1.0b1/overlays/MAL-2024-8094.yaml +16 -0
- openaca-0.1.0b1/overlays/MAL-2024-9212.yaml +14 -0
- openaca-0.1.0b1/overlays/MAL-2024-9263.yaml +14 -0
- openaca-0.1.0b1/overlays/MAL-2025-15093.yaml +17 -0
- openaca-0.1.0b1/overlays/MAL-2025-190848.yaml +19 -0
- openaca-0.1.0b1/overlays/MAL-2025-190867.yaml +16 -0
- openaca-0.1.0b1/overlays/MAL-2025-190868.yaml +16 -0
- openaca-0.1.0b1/overlays/MAL-2025-190869.yaml +16 -0
- openaca-0.1.0b1/overlays/MAL-2025-190902.yaml +16 -0
- openaca-0.1.0b1/overlays/MAL-2025-190908.yaml +16 -0
- openaca-0.1.0b1/overlays/MAL-2025-190909.yaml +17 -0
- openaca-0.1.0b1/overlays/MAL-2025-190918.yaml +17 -0
- openaca-0.1.0b1/overlays/MAL-2025-190922.yaml +16 -0
- openaca-0.1.0b1/overlays/MAL-2025-190923.yaml +16 -0
- openaca-0.1.0b1/overlays/MAL-2025-190943.yaml +19 -0
- openaca-0.1.0b1/overlays/MAL-2025-191051.yaml +19 -0
- openaca-0.1.0b1/overlays/MAL-2025-191052.yaml +19 -0
- openaca-0.1.0b1/overlays/MAL-2025-191053.yaml +19 -0
- openaca-0.1.0b1/overlays/MAL-2025-191107.yaml +19 -0
- openaca-0.1.0b1/overlays/MAL-2025-191195.yaml +18 -0
- openaca-0.1.0b1/overlays/MAL-2025-191196.yaml +17 -0
- openaca-0.1.0b1/overlays/MAL-2025-191332.yaml +14 -0
- openaca-0.1.0b1/overlays/MAL-2025-191465.yaml +19 -0
- openaca-0.1.0b1/overlays/MAL-2025-191527.yaml +16 -0
- openaca-0.1.0b1/overlays/MAL-2025-191647.yaml +17 -0
- openaca-0.1.0b1/overlays/MAL-2025-191648.yaml +17 -0
- openaca-0.1.0b1/overlays/MAL-2025-191788.yaml +17 -0
- openaca-0.1.0b1/overlays/MAL-2025-191789.yaml +19 -0
- openaca-0.1.0b1/overlays/MAL-2025-191924.yaml +19 -0
- openaca-0.1.0b1/overlays/MAL-2025-191925.yaml +19 -0
- openaca-0.1.0b1/overlays/MAL-2025-191926.yaml +19 -0
- openaca-0.1.0b1/overlays/MAL-2025-191927.yaml +19 -0
- openaca-0.1.0b1/overlays/MAL-2025-191929.yaml +19 -0
- openaca-0.1.0b1/overlays/MAL-2025-191930.yaml +19 -0
- openaca-0.1.0b1/overlays/MAL-2025-191931.yaml +19 -0
- openaca-0.1.0b1/overlays/MAL-2025-192601.yaml +19 -0
- openaca-0.1.0b1/overlays/MAL-2025-192746.yaml +18 -0
- openaca-0.1.0b1/overlays/MAL-2025-192747.yaml +18 -0
- openaca-0.1.0b1/overlays/MAL-2025-2298.yaml +16 -0
- openaca-0.1.0b1/overlays/MAL-2025-26053.yaml +16 -0
- openaca-0.1.0b1/overlays/MAL-2025-26054.yaml +17 -0
- openaca-0.1.0b1/overlays/MAL-2025-3471.yaml +16 -0
- openaca-0.1.0b1/overlays/MAL-2025-4045.yaml +14 -0
- openaca-0.1.0b1/overlays/MAL-2025-41387.yaml +14 -0
- openaca-0.1.0b1/overlays/MAL-2025-41934.yaml +14 -0
- openaca-0.1.0b1/overlays/MAL-2025-41939.yaml +17 -0
- openaca-0.1.0b1/overlays/MAL-2025-41940.yaml +17 -0
- openaca-0.1.0b1/overlays/MAL-2025-42116.yaml +17 -0
- openaca-0.1.0b1/overlays/MAL-2025-42192.yaml +15 -0
- openaca-0.1.0b1/overlays/MAL-2025-4223.yaml +16 -0
- openaca-0.1.0b1/overlays/MAL-2025-42690.yaml +15 -0
- openaca-0.1.0b1/overlays/MAL-2025-4635.yaml +18 -0
- openaca-0.1.0b1/overlays/MAL-2025-46986.yaml +19 -0
- openaca-0.1.0b1/overlays/MAL-2025-47076.yaml +18 -0
- openaca-0.1.0b1/overlays/MAL-2025-47098.yaml +18 -0
- openaca-0.1.0b1/overlays/MAL-2025-47326.yaml +19 -0
- openaca-0.1.0b1/overlays/MAL-2025-47327.yaml +19 -0
- openaca-0.1.0b1/overlays/MAL-2025-47604.yaml +20 -0
- openaca-0.1.0b1/overlays/MAL-2025-47838.yaml +19 -0
- openaca-0.1.0b1/overlays/MAL-2025-47907.yaml +14 -0
- openaca-0.1.0b1/overlays/MAL-2025-47929.yaml +20 -0
- openaca-0.1.0b1/overlays/MAL-2025-48030.yaml +16 -0
- openaca-0.1.0b1/overlays/MAL-2025-48558.yaml +17 -0
- openaca-0.1.0b1/overlays/MAL-2025-48761.yaml +19 -0
- openaca-0.1.0b1/overlays/MAL-2025-48786.yaml +17 -0
- openaca-0.1.0b1/overlays/MAL-2025-49378.yaml +16 -0
- openaca-0.1.0b1/overlays/MAL-2025-49379.yaml +18 -0
- openaca-0.1.0b1/overlays/MAL-2025-49383.yaml +20 -0
- openaca-0.1.0b1/overlays/MAL-2025-5286.yaml +18 -0
- openaca-0.1.0b1/overlays/MAL-2025-5813.yaml +19 -0
- openaca-0.1.0b1/overlays/MAL-2025-6007.yaml +16 -0
- openaca-0.1.0b1/overlays/MAL-2025-6115.yaml +16 -0
- openaca-0.1.0b1/overlays/MAL-2025-6139.yaml +19 -0
- openaca-0.1.0b1/overlays/MAL-2025-635.yaml +17 -0
- openaca-0.1.0b1/overlays/MAL-2026-1151.yaml +16 -0
- openaca-0.1.0b1/overlays/MAL-2026-1321.yaml +16 -0
- openaca-0.1.0b1/overlays/MAL-2026-1380.yaml +16 -0
- openaca-0.1.0b1/overlays/MAL-2026-1642.yaml +17 -0
- openaca-0.1.0b1/overlays/MAL-2026-1930.yaml +19 -0
- openaca-0.1.0b1/overlays/MAL-2026-1990.yaml +16 -0
- openaca-0.1.0b1/overlays/MAL-2026-2004.yaml +16 -0
- openaca-0.1.0b1/overlays/MAL-2026-2005.yaml +16 -0
- openaca-0.1.0b1/overlays/MAL-2026-2006.yaml +16 -0
- openaca-0.1.0b1/overlays/MAL-2026-2144.yaml +18 -0
- openaca-0.1.0b1/overlays/MAL-2026-227.yaml +16 -0
- openaca-0.1.0b1/overlays/MAL-2026-229.yaml +16 -0
- openaca-0.1.0b1/overlays/MAL-2026-2315.yaml +20 -0
- openaca-0.1.0b1/overlays/MAL-2026-2328.yaml +19 -0
- openaca-0.1.0b1/overlays/MAL-2026-248.yaml +14 -0
- openaca-0.1.0b1/overlays/MAL-2026-2669.yaml +16 -0
- openaca-0.1.0b1/overlays/MAL-2026-2974.yaml +19 -0
- openaca-0.1.0b1/overlays/MAL-2026-3280.yaml +19 -0
- openaca-0.1.0b1/overlays/MAL-2026-3448.yaml +19 -0
- openaca-0.1.0b1/overlays/MAL-2026-3588.yaml +19 -0
- openaca-0.1.0b1/overlays/MAL-2026-3589.yaml +19 -0
- openaca-0.1.0b1/overlays/MAL-2026-3811.yaml +14 -0
- openaca-0.1.0b1/overlays/MAL-2026-405.yaml +16 -0
- openaca-0.1.0b1/overlays/MAL-2026-603.yaml +16 -0
- openaca-0.1.0b1/overlays/MAL-2026-844.yaml +15 -0
- openaca-0.1.0b1/overlays/MAL-2026-862.yaml +16 -0
- openaca-0.1.0b1/overlays/MAL-2026-904.yaml +14 -0
- openaca-0.1.0b1/pyproject.toml +54 -0
- openaca-0.1.0b1/schema/openaca.schema.json +154 -0
- openaca-0.1.0b1/scripts/git-hooks/pre-push +102 -0
- openaca-0.1.0b1/scripts/install-hooks.sh +36 -0
- openaca-0.1.0b1/scripts/seed-osv-overlays.sh +45 -0
- openaca-0.1.0b1/tests/__init__.py +0 -0
- openaca-0.1.0b1/tests/conftest.py +72 -0
- openaca-0.1.0b1/tests/fixtures/candidates/flowise-corrected-good.yaml +26 -0
- openaca-0.1.0b1/tests/fixtures/candidates/flowise-nano-bad.yaml +30 -0
- openaca-0.1.0b1/tests/fixtures/installs/minimal/plugins/cache/test-marketplace/sample-plugin/1.2.0/.claude-plugin/plugin.json +5 -0
- openaca-0.1.0b1/tests/fixtures/installs/minimal/plugins/installed_plugins.json +15 -0
- openaca-0.1.0b1/tests/fixtures/installs/minimal/settings.json +5 -0
- openaca-0.1.0b1/tests/fixtures/invalid/bad-cvss.yaml +24 -0
- openaca-0.1.0b1/tests/fixtures/invalid/bad-datetime.yaml +24 -0
- openaca-0.1.0b1/tests/fixtures/invalid/config-not-allowed.yaml +10 -0
- openaca-0.1.0b1/tests/fixtures/invalid/exposure-not-allowed.yaml +10 -0
- openaca-0.1.0b1/tests/fixtures/osv/ghsa-3ch2-jxxc-v4xf.json +19 -0
- openaca-0.1.0b1/tests/fixtures/osv/ghsa-3q26-f695-pp76.json +30 -0
- openaca-0.1.0b1/tests/fixtures/osv/ghsa-6xpm-ggf7-wc3p.json +18 -0
- openaca-0.1.0b1/tests/fixtures/osv/ghsa-m4qw-j7mx-qv6h.json +18 -0
- openaca-0.1.0b1/tests/fixtures/osv/ghsa-rwc2-f344-q6w6.json +19 -0
- openaca-0.1.0b1/tests/fixtures/repos/declared-components/.claude/agents/reviewer.md +4 -0
- openaca-0.1.0b1/tests/fixtures/repos/declared-components/.claude/commands/deploy.md +1 -0
- openaca-0.1.0b1/tests/fixtures/repos/declared-components/.claude/skills/bootstrap/SKILL.md +7 -0
- openaca-0.1.0b1/tests/fixtures/repos/exposed-mcp/.claude/settings.json +5 -0
- openaca-0.1.0b1/tests/fixtures/repos/exposed-mcp/.claude-plugin/plugin.json +5 -0
- openaca-0.1.0b1/tests/fixtures/repos/exposed-mcp/package.json +7 -0
- openaca-0.1.0b1/tests/fixtures/repos/sample-claude-desktop/claude_desktop_config.json +12 -0
- openaca-0.1.0b1/tests/fixtures/repos/sample-lockfile-npm/package-lock.json +9 -0
- openaca-0.1.0b1/tests/fixtures/repos/sample-lockfile-uv/uv.lock +5 -0
- openaca-0.1.0b1/tests/fixtures/repos/sample-mcp/mcp.json +20 -0
- openaca-0.1.0b1/tests/fixtures/repos/sample-npm/package.json +11 -0
- openaca-0.1.0b1/tests/fixtures/repos/sample-plugin/.claude-plugin/plugin.json +19 -0
- openaca-0.1.0b1/tests/fixtures/repos/sample-plugin-string-mcp/.claude-plugin/plugin.json +6 -0
- openaca-0.1.0b1/tests/fixtures/repos/sample-plugin-string-mcp/.mcp.json +8 -0
- openaca-0.1.0b1/tests/fixtures/repos/sample-pyproject/pyproject.toml +22 -0
- openaca-0.1.0b1/tests/fixtures/repos/sample-settings/.claude/settings.json +7 -0
- openaca-0.1.0b1/tests/fixtures/valid/cve-2026-0001.yaml +35 -0
- openaca-0.1.0b1/tests/test_component_ref.py +76 -0
- openaca-0.1.0b1/tests/test_cvss.py +161 -0
- openaca-0.1.0b1/tests/test_e2e.py +679 -0
- openaca-0.1.0b1/tests/test_export.py +188 -0
- openaca-0.1.0b1/tests/test_finding_output.py +44 -0
- openaca-0.1.0b1/tests/test_lint.py +318 -0
- openaca-0.1.0b1/tests/test_matcher.py +452 -0
- openaca-0.1.0b1/tests/test_osv_federation.py +226 -0
- openaca-0.1.0b1/tests/test_overlays.py +47 -0
- openaca-0.1.0b1/tests/test_parsers/__init__.py +0 -0
- openaca-0.1.0b1/tests/test_parsers/test_claude_command_agent.py +268 -0
- openaca-0.1.0b1/tests/test_parsers/test_claude_desktop_config.py +25 -0
- openaca-0.1.0b1/tests/test_parsers/test_claude_install.py +1178 -0
- openaca-0.1.0b1/tests/test_parsers/test_claude_plugin.py +278 -0
- openaca-0.1.0b1/tests/test_parsers/test_claude_settings.py +100 -0
- openaca-0.1.0b1/tests/test_parsers/test_claude_skill.py +148 -0
- openaca-0.1.0b1/tests/test_parsers/test_gitignore.py +197 -0
- openaca-0.1.0b1/tests/test_parsers/test_hooks_json.py +255 -0
- openaca-0.1.0b1/tests/test_parsers/test_mcp_json.py +439 -0
- openaca-0.1.0b1/tests/test_parsers/test_package_json.py +34 -0
- openaca-0.1.0b1/tests/test_parsers/test_package_lock_json.py +135 -0
- openaca-0.1.0b1/tests/test_parsers/test_pyproject_toml.py +140 -0
- openaca-0.1.0b1/tests/test_parsers/test_registry.py +62 -0
- openaca-0.1.0b1/tests/test_parsers/test_repo_mode_components.py +101 -0
- openaca-0.1.0b1/tests/test_parsers/test_repo_mode_lockfiles.py +28 -0
- openaca-0.1.0b1/tests/test_parsers/test_settings_layers.py +217 -0
- openaca-0.1.0b1/tests/test_parsers/test_uv_lock.py +90 -0
- openaca-0.1.0b1/tests/test_posture_finding.py +37 -0
- openaca-0.1.0b1/tests/test_posture_immutability.py +86 -0
- openaca-0.1.0b1/tests/test_posture_insecure_transport.py +105 -0
- openaca-0.1.0b1/tests/test_posture_integration.py +142 -0
- openaca-0.1.0b1/tests/test_posture_mutable_install.py +152 -0
- openaca-0.1.0b1/tests/test_promote.py +166 -0
- openaca-0.1.0b1/tests/test_render.py +1092 -0
- openaca-0.1.0b1/tests/test_sarif.py +303 -0
- openaca-0.1.0b1/tests/test_scan.py +1569 -0
- openaca-0.1.0b1/tests/test_schema.py +137 -0
- openaca-0.1.0b1/tests/test_seed_cli.py +1185 -0
- openaca-0.1.0b1/tests/test_seed_llm.py +316 -0
- openaca-0.1.0b1/tests/test_seed_validator.py +174 -0
- openaca-0.1.0b1/tests/test_seed_workflow_script.py +181 -0
- openaca-0.1.0b1/tests/test_severity.py +145 -0
- openaca-0.1.0b1/tools/__init__.py +1 -0
- openaca-0.1.0b1/tools/cli.py +34 -0
- openaca-0.1.0b1/tools/component_ref.py +63 -0
- openaca-0.1.0b1/tools/cvss.py +154 -0
- openaca-0.1.0b1/tools/export.py +161 -0
- openaca-0.1.0b1/tools/finding_output.py +144 -0
- openaca-0.1.0b1/tools/lint.py +215 -0
- openaca-0.1.0b1/tools/matcher.py +225 -0
- openaca-0.1.0b1/tools/osv_federation.py +144 -0
- openaca-0.1.0b1/tools/overlays.py +83 -0
- openaca-0.1.0b1/tools/parsers/__init__.py +264 -0
- openaca-0.1.0b1/tools/parsers/claude_command_agent.py +168 -0
- openaca-0.1.0b1/tools/parsers/claude_install.py +741 -0
- openaca-0.1.0b1/tools/parsers/claude_plugin.py +201 -0
- openaca-0.1.0b1/tools/parsers/claude_settings.py +62 -0
- openaca-0.1.0b1/tools/parsers/claude_skill.py +91 -0
- openaca-0.1.0b1/tools/parsers/gitignore.py +102 -0
- openaca-0.1.0b1/tools/parsers/hooks_json.py +145 -0
- openaca-0.1.0b1/tools/parsers/mcp_json.py +393 -0
- openaca-0.1.0b1/tools/parsers/package_json.py +32 -0
- openaca-0.1.0b1/tools/parsers/package_lock_json.py +66 -0
- openaca-0.1.0b1/tools/parsers/pyproject_toml.py +99 -0
- openaca-0.1.0b1/tools/parsers/settings_layers.py +155 -0
- openaca-0.1.0b1/tools/parsers/uv_lock.py +51 -0
- openaca-0.1.0b1/tools/posture/__init__.py +145 -0
- openaca-0.1.0b1/tools/posture/finding.py +65 -0
- openaca-0.1.0b1/tools/posture/immutability.py +122 -0
- openaca-0.1.0b1/tools/posture/rules/__init__.py +1 -0
- openaca-0.1.0b1/tools/posture/rules/insecure_transport.py +84 -0
- openaca-0.1.0b1/tools/posture/rules/mutable_install.py +161 -0
- openaca-0.1.0b1/tools/promote.py +108 -0
- openaca-0.1.0b1/tools/render.py +980 -0
- openaca-0.1.0b1/tools/sarif.py +207 -0
- openaca-0.1.0b1/tools/scan.py +663 -0
- openaca-0.1.0b1/tools/seed/__init__.py +1 -0
- openaca-0.1.0b1/tools/seed/__main__.py +683 -0
- openaca-0.1.0b1/tools/seed/llm.py +422 -0
- openaca-0.1.0b1/tools/seed/validator.py +79 -0
- openaca-0.1.0b1/tools/severity.py +109 -0
- openaca-0.1.0b1/tools/templates/advisory.html.j2 +117 -0
- openaca-0.1.0b1/tools/templates/index.html.j2 +68 -0
- openaca-0.1.0b1/tools/templates/style.css +118 -0
- openaca-0.1.0b1/uv.lock +802 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/claude-code-settings.json",
|
|
3
|
+
"hooks": {
|
|
4
|
+
"SessionStart": [
|
|
5
|
+
{
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "if [ -f docs/adrs/INDEX.md ]; then printf '=== ARCHITECTURE DECISIONS (docs/adrs/) ===\\n\\nRead a full ADR before changing logic in the area it covers.\\n\\n'; cat docs/adrs/INDEX.md; fi"
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"PreCompact": [
|
|
15
|
+
{
|
|
16
|
+
"hooks": [
|
|
17
|
+
{
|
|
18
|
+
"type": "command",
|
|
19
|
+
"command": "if [ -f docs/adrs/HOOK-PROMPT.md ]; then printf '=== ADR CHECK BEFORE COMPACTION ===\\n\\n'; cat docs/adrs/HOOK-PROMPT.md; fi"
|
|
20
|
+
}
|
|
21
|
+
]
|
|
22
|
+
}
|
|
23
|
+
],
|
|
24
|
+
"SessionEnd": [
|
|
25
|
+
{
|
|
26
|
+
"hooks": [
|
|
27
|
+
{
|
|
28
|
+
"type": "command",
|
|
29
|
+
"command": "if [ -f docs/adrs/HOOK-PROMPT.md ]; then printf '=== ADR CHECK BEFORE SESSION END ===\\n\\n'; cat docs/adrs/HOOK-PROMPT.md; fi"
|
|
30
|
+
}
|
|
31
|
+
]
|
|
32
|
+
}
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Beta feedback
|
|
3
|
+
about: Report scanner ergonomics, coverage gaps, or workflow fit during the 0.1.0bN beta.
|
|
4
|
+
title: '[beta] '
|
|
5
|
+
labels: ['beta']
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
<!-- Thanks for testing OpenACA. Fields marked (required) are the ones I look at first. -->
|
|
9
|
+
|
|
10
|
+
### Feedback type (required)
|
|
11
|
+
|
|
12
|
+
<!-- Pick the closest fit — helps me triage. -->
|
|
13
|
+
|
|
14
|
+
- [ ] Scanner ergonomics — install / first scan / output legibility / CLI friction
|
|
15
|
+
- [ ] Coverage gap — something the scanner inventoried or missed that surprised me
|
|
16
|
+
- [ ] Workflow fit — where OpenACA does or doesn't fit in my security tooling
|
|
17
|
+
|
|
18
|
+
### Command run (required)
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
# Paste the full command you ran, e.g.:
|
|
22
|
+
# openaca scan repo --target ./my-mcp-server --include-posture
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### OpenACA version (required)
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
# Output of `openaca --version`
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Expected vs actual (required)
|
|
32
|
+
|
|
33
|
+
**Expected:**
|
|
34
|
+
|
|
35
|
+
**Actual:**
|
|
36
|
+
|
|
37
|
+
### Output (redacted as needed)
|
|
38
|
+
|
|
39
|
+
<!--
|
|
40
|
+
Paste scanner output. If it contains internal names, paths, or component IDs
|
|
41
|
+
you don't want public, redact freely — replace with <redacted> or generic
|
|
42
|
+
placeholders. The shape of the output is more useful than the literal
|
|
43
|
+
contents. SARIF is sometimes easier to redact than text.
|
|
44
|
+
-->
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Missing or incorrect inventory
|
|
50
|
+
|
|
51
|
+
<!--
|
|
52
|
+
If the scanner missed something it should have inventoried, or inventoried
|
|
53
|
+
something incorrectly, describe what + where. Format hint:
|
|
54
|
+
- Manifest: path/to/file
|
|
55
|
+
- Should have shown: <name@version>
|
|
56
|
+
- Actually showed: <empty | wrong name | wrong version>
|
|
57
|
+
Leave blank if this isn't an inventory issue.
|
|
58
|
+
-->
|
|
59
|
+
|
|
60
|
+
### Environment
|
|
61
|
+
|
|
62
|
+
- OS:
|
|
63
|
+
- Python version (`python --version`):
|
|
64
|
+
- Install path (`pip install ...` / `uv sync` / other):
|
|
65
|
+
|
|
66
|
+
### Anything else?
|
|
67
|
+
|
|
68
|
+
<!-- Workflow context, what you wish it did, why you tried it, etc. -->
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
blank_issues_enabled: false
|
|
2
|
+
contact_links:
|
|
3
|
+
- name: Security disclosure
|
|
4
|
+
url: https://github.com/open-agent-security/openaca/security/advisories/new
|
|
5
|
+
about: Private security advisories for OpenACA-the-scanner bugs. See SECURITY.md.
|
|
6
|
+
- name: Beta tester guide
|
|
7
|
+
url: https://github.com/open-agent-security/openaca-demo/blob/main/BETA-TESTER-GUIDE.md
|
|
8
|
+
about: Beta-tester guide (hosted in the public openaca-demo repo since openaca is private during the closed beta).
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
name: Autofix
|
|
2
|
+
|
|
3
|
+
# Two-direction comment-bounce that closes the Codex/Claude review loop
|
|
4
|
+
# without human intervention:
|
|
5
|
+
#
|
|
6
|
+
# 1. Codex bot reviews a PR -> this workflow posts `@claude please
|
|
7
|
+
# address...` -> claude.yml fires, claude reads the review, fixes
|
|
8
|
+
# it, pushes commits, posts a "Claude finished" summary comment.
|
|
9
|
+
#
|
|
10
|
+
# 2. Claude bot's "finished" summary is itself the trigger for the second
|
|
11
|
+
# bounce: this workflow posts `@codex review` -> Codex re-reviews
|
|
12
|
+
# HEAD on the same PR. (Codex auto-reviews on PR open, draft->ready,
|
|
13
|
+
# and explicit @codex review comments — but NOT on subsequent pushes.
|
|
14
|
+
# Without this bounce, claude's fixes land unreviewed.)
|
|
15
|
+
#
|
|
16
|
+
# The two bounces together produce: codex-flag -> claude-fix ->
|
|
17
|
+
# codex-rereview -> ... bounded by the claude-side iteration cap below.
|
|
18
|
+
# Codex is silent when clean (thumbs reaction), so an unnecessary @codex
|
|
19
|
+
# review on a no-op SHA costs one cheap call with no PR noise.
|
|
20
|
+
#
|
|
21
|
+
# Authentication: comments are posted (and PR branches are rebased)
|
|
22
|
+
# using AUTOFIX_TRIGGER_PAT, NOT secrets.GITHUB_TOKEN. GitHub explicitly
|
|
23
|
+
# suppresses workflow runs triggered by events created with
|
|
24
|
+
# GITHUB_TOKEN (recursion guard) — so a comment posted under
|
|
25
|
+
# github-actions[bot] would be silently dropped before claude.yml ever
|
|
26
|
+
# fired, and a rebase pushed under GITHUB_TOKEN would not re-trigger
|
|
27
|
+
# CI / Codex on the rebased branch. The PAT is fine-grained, owned by
|
|
28
|
+
# a repo OWNER/MEMBER, with BOTH:
|
|
29
|
+
# - `Pull requests: Read and write` — for `gh pr comment` (bounces 1
|
|
30
|
+
# and 2) and `gh pr list` (bounce 0).
|
|
31
|
+
# - `Contents: Read and write` — for `actions/checkout` to fetch and
|
|
32
|
+
# `git push` from the rebase-stale-prs job. WITHOUT this, the job
|
|
33
|
+
# fails at the `Checkout` step with `403: Write access to
|
|
34
|
+
# repository not granted` — actions/checkout asks GitHub to verify
|
|
35
|
+
# write permission upfront because the same token will push later.
|
|
36
|
+
# The resulting comments/pushes are authored by that user, which DOES
|
|
37
|
+
# trigger downstream workflows AND naturally passes claude.yml's
|
|
38
|
+
# OWNER/MEMBER/COLLABORATOR trust gate. The same PAT serves all three
|
|
39
|
+
# bounces — Codex's webhook listener watches @-mentions in PR comments
|
|
40
|
+
# regardless of author.
|
|
41
|
+
#
|
|
42
|
+
# If AUTOFIX_TRIGGER_PAT is unset, both `gh pr comment` paths fail
|
|
43
|
+
# with an auth error and the chain breaks; rebase-stale-prs detects
|
|
44
|
+
# the missing secret and skips with a workflow warning. Manual
|
|
45
|
+
# @claude triggers are unaffected.
|
|
46
|
+
#
|
|
47
|
+
# Workflow-injection safety: every user/bot-controlled input (PR number,
|
|
48
|
+
# comment body, review id) is read via env: and referenced as a shell
|
|
49
|
+
# variable. Comment bodies posted to the PR are hardcoded literals — no
|
|
50
|
+
# interpolation of untrusted input into shell.
|
|
51
|
+
|
|
52
|
+
on:
|
|
53
|
+
pull_request_review:
|
|
54
|
+
types: [submitted]
|
|
55
|
+
issue_comment:
|
|
56
|
+
# claude-code-action posts ONE progress comment when it starts and
|
|
57
|
+
# updates the SAME comment in place when it finishes — that fires
|
|
58
|
+
# `issue_comment.edited`, not `created`. Listen to both so the
|
|
59
|
+
# "Claude finished" marker is visible to bounce 2 regardless of
|
|
60
|
+
# whether the action posts fresh or updates in place. The body
|
|
61
|
+
# marker check in the `if:` gate keeps non-claude edits out.
|
|
62
|
+
types: [created, edited]
|
|
63
|
+
push:
|
|
64
|
+
# Bounce 0: main moved -> rebase open PRs that are now stale. Catches
|
|
65
|
+
# both PR merges AND direct pushes to main (manual hotfixes, manual
|
|
66
|
+
# main pushes). pull_request:closed+merged would miss the latter.
|
|
67
|
+
branches: [main]
|
|
68
|
+
|
|
69
|
+
jobs:
|
|
70
|
+
trigger-claude-via-comment:
|
|
71
|
+
# Bounce 1: Codex review submitted -> @claude addresses it.
|
|
72
|
+
if: github.event_name == 'pull_request_review' && github.event.review.user.login == 'chatgpt-codex-connector[bot]' && github.event.review.state == 'commented'
|
|
73
|
+
runs-on: ubuntu-latest
|
|
74
|
+
permissions:
|
|
75
|
+
pull-requests: write
|
|
76
|
+
env:
|
|
77
|
+
GH_TOKEN: ${{ secrets.AUTOFIX_TRIGGER_PAT }}
|
|
78
|
+
REPO: ${{ github.repository }}
|
|
79
|
+
PR_NUM: ${{ github.event.pull_request.number }}
|
|
80
|
+
REVIEW_ID: ${{ github.event.review.id }}
|
|
81
|
+
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
82
|
+
steps:
|
|
83
|
+
# Cap the auto-address loop at 7 claude runs per PR. If claude has
|
|
84
|
+
# already run 7+ times and Codex is still finding issues, the loop
|
|
85
|
+
# isn't converging — force human attention rather than burn cycles.
|
|
86
|
+
#
|
|
87
|
+
# Cap was 3 originally; bumped to 7 because legitimate Codex
|
|
88
|
+
# reviews on substantive PRs routinely surface 5-6 rounds of real
|
|
89
|
+
# findings (defensive type guards across sibling parsers, CLI-flag
|
|
90
|
+
# table extensions one per round, OSV event-window edge cases).
|
|
91
|
+
# 3 was capping real review work, not just runaway loops; 7 keeps
|
|
92
|
+
# the safety net while accommodating multi-round legitimate review.
|
|
93
|
+
#
|
|
94
|
+
# Count claude-code-action runs by counting top-level PR comments
|
|
95
|
+
# authored by the bot (the action posts ONE in-progress comment
|
|
96
|
+
# per run and edits it in place to the "Claude finished..." summary
|
|
97
|
+
# — 1 comment == 1 run). More accurate than counting bot commits:
|
|
98
|
+
# a run that lands 0 commits (no-op fix or pre-push failure) and a
|
|
99
|
+
# run that lands N commits both equal one iteration of the loop.
|
|
100
|
+
#
|
|
101
|
+
# Login string is "claude" — NOT "claude[bot]" — because
|
|
102
|
+
# `gh pr view --json comments` uses GraphQL, and GraphQL's
|
|
103
|
+
# `author.login` returns the bare bot name without the `[bot]`
|
|
104
|
+
# suffix. The `[bot]` suffix only appears in REST surfaces (e.g.
|
|
105
|
+
# `github.event.comment.user.login` at bounce-2 below). Same bot,
|
|
106
|
+
# two string representations depending on which API you read.
|
|
107
|
+
- name: Cap auto-address iterations (max 7)
|
|
108
|
+
run: |
|
|
109
|
+
MAX=7
|
|
110
|
+
CLAUDE_RUNS=$(gh pr view "$PR_NUM" -R "$REPO" --json comments \
|
|
111
|
+
--jq '[.comments[] | select(.author.login == "claude")] | length')
|
|
112
|
+
if [ "$CLAUDE_RUNS" -ge "$MAX" ]; then
|
|
113
|
+
gh pr comment "$PR_NUM" -R "$REPO" --body "🛑 Auto-address loop limit hit — claude has already run $CLAUDE_RUNS times on this PR (max $MAX). Codex review id $REVIEW_ID was NOT auto-addressed. Manual review needed; re-tag claude explicitly after addressing the underlying issue if you want to continue. ([workflow run]($RUN_URL))"
|
|
114
|
+
echo "::error::Auto-address loop limit hit ($CLAUDE_RUNS/$MAX) — see PR comment."
|
|
115
|
+
exit 1
|
|
116
|
+
fi
|
|
117
|
+
echo "Auto-address iteration $((CLAUDE_RUNS + 1)) of $MAX"
|
|
118
|
+
|
|
119
|
+
# Post the @claude trigger. The body intentionally references "the
|
|
120
|
+
# Codex review above" — claude-code-action's default prompt
|
|
121
|
+
# construction will include the full review thread so the bot has
|
|
122
|
+
# the actual findings + line context to act on.
|
|
123
|
+
- name: Post @claude trigger comment
|
|
124
|
+
run: |
|
|
125
|
+
gh pr comment "$PR_NUM" -R "$REPO" --body "@claude please address the Codex review feedback above. CRITICAL: before applying any fix that depends on infrastructure or invariants you have not verified (DLQ, schema, contracts, ADRs in docs/adrs/), grep the repo for the prerequisite. If it is not present, push back via a PR comment explaining the missing context rather than implementing on a false premise."
|
|
126
|
+
|
|
127
|
+
trigger-codex-rereview-via-comment:
|
|
128
|
+
# Bounce 2: claude finished addressing -> @codex re-reviews HEAD.
|
|
129
|
+
# The "Claude finished" marker is the standard heading
|
|
130
|
+
# claude-code-action posts when it completes a task. Filtering on
|
|
131
|
+
# both bot login AND body marker prevents accidental firing from any
|
|
132
|
+
# other claude[bot] activity (e.g., scheduled tasks). The
|
|
133
|
+
# pull_request guard is required because issue_comment also fires
|
|
134
|
+
# on real Issues, where there is no PR head to review.
|
|
135
|
+
if: |
|
|
136
|
+
github.event_name == 'issue_comment'
|
|
137
|
+
&& github.event.issue.pull_request != null
|
|
138
|
+
&& github.event.comment.user.login == 'claude[bot]'
|
|
139
|
+
&& contains(github.event.comment.body, 'Claude finished')
|
|
140
|
+
runs-on: ubuntu-latest
|
|
141
|
+
permissions:
|
|
142
|
+
pull-requests: write
|
|
143
|
+
env:
|
|
144
|
+
GH_TOKEN: ${{ secrets.AUTOFIX_TRIGGER_PAT }}
|
|
145
|
+
REPO: ${{ github.repository }}
|
|
146
|
+
PR_NUM: ${{ github.event.issue.number }}
|
|
147
|
+
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
148
|
+
steps:
|
|
149
|
+
# Skip if HEAD hasn't changed since our last @codex review request.
|
|
150
|
+
# Claude can post "Claude finished" without pushing any commits
|
|
151
|
+
# (e.g., it pushed back via comment instead of implementing).
|
|
152
|
+
# Without this check, a no-op finished-comment would trigger
|
|
153
|
+
# another @codex review on the same SHA, codex would re-flag the
|
|
154
|
+
# same issue, autofix would post @claude again, and the cycle
|
|
155
|
+
# could repeat indefinitely — the claude-side commit cap doesn't
|
|
156
|
+
# advance because claude isn't committing.
|
|
157
|
+
#
|
|
158
|
+
# Strategy: embed the HEAD SHA in our trigger-comment body via an
|
|
159
|
+
# HTML comment marker, then check on each run whether we've
|
|
160
|
+
# already requested review for the current SHA.
|
|
161
|
+
- name: Post @codex re-review trigger (idempotent per SHA)
|
|
162
|
+
run: |
|
|
163
|
+
HEAD_SHA=$(gh pr view "$PR_NUM" -R "$REPO" --json headRefOid --jq '.headRefOid')
|
|
164
|
+
MARKER="<!-- autofix-rereview-sha:$HEAD_SHA -->"
|
|
165
|
+
ALREADY=$(gh pr view "$PR_NUM" -R "$REPO" --json comments \
|
|
166
|
+
--jq "[.comments[] | select(.body | contains(\"$MARKER\"))] | length")
|
|
167
|
+
if [ "$ALREADY" -gt 0 ]; then
|
|
168
|
+
echo "Already requested @codex review for $HEAD_SHA — skipping (claude finished without pushing new commits)."
|
|
169
|
+
exit 0
|
|
170
|
+
fi
|
|
171
|
+
gh pr comment "$PR_NUM" -R "$REPO" --body "$MARKER
|
|
172
|
+
@codex review the latest commits — claude bot just pushed fixes that haven't been reviewed yet."
|
|
173
|
+
|
|
174
|
+
rebase-stale-prs:
|
|
175
|
+
# Bounce 0: main moved -> for each open PR (same-repo only, fork PRs
|
|
176
|
+
# excluded), check mergeStateStatus and either rebase cleanly or ask
|
|
177
|
+
# @claude to resolve conflicts. Catches both PR-merge pushes and
|
|
178
|
+
# direct pushes to main.
|
|
179
|
+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
|
|
180
|
+
runs-on: ubuntu-latest
|
|
181
|
+
permissions:
|
|
182
|
+
contents: write
|
|
183
|
+
pull-requests: write
|
|
184
|
+
concurrency:
|
|
185
|
+
# Coalesce: if main moves twice while we're still working on the
|
|
186
|
+
# first push, cancel and re-run with the latest SHA. Avoids racing
|
|
187
|
+
# two rebase runs against the same PR.
|
|
188
|
+
group: rebase-stale-prs
|
|
189
|
+
cancel-in-progress: true
|
|
190
|
+
env:
|
|
191
|
+
# Use the PAT for both gh API calls AND the git push. Pushing to a
|
|
192
|
+
# PR branch under GITHUB_TOKEN works but invalidates downstream
|
|
193
|
+
# workflow triggers (recursion guard) — the PAT keeps the @claude
|
|
194
|
+
# comment path working if this rebase later races with a Codex
|
|
195
|
+
# review on the same PR.
|
|
196
|
+
GH_TOKEN: ${{ secrets.AUTOFIX_TRIGGER_PAT }}
|
|
197
|
+
REPO: ${{ github.repository }}
|
|
198
|
+
MAIN_SHA: ${{ github.sha }}
|
|
199
|
+
steps:
|
|
200
|
+
# Graceful skip when the PAT secret isn't configured yet (initial
|
|
201
|
+
# repo setup). Without this guard, actions/checkout fails with
|
|
202
|
+
# "Input required and not supplied: token" on every push to main,
|
|
203
|
+
# turning every merge into a red workflow run.
|
|
204
|
+
- id: have_pat
|
|
205
|
+
name: Check AUTOFIX_TRIGGER_PAT presence
|
|
206
|
+
env:
|
|
207
|
+
PAT: ${{ secrets.AUTOFIX_TRIGGER_PAT }}
|
|
208
|
+
run: |
|
|
209
|
+
if [ -z "$PAT" ]; then
|
|
210
|
+
echo "::warning::AUTOFIX_TRIGGER_PAT not set; skipping rebase. Add the secret per setup-autofix instructions to enable."
|
|
211
|
+
echo "skip=true" >> "$GITHUB_OUTPUT"
|
|
212
|
+
else
|
|
213
|
+
echo "skip=false" >> "$GITHUB_OUTPUT"
|
|
214
|
+
fi
|
|
215
|
+
|
|
216
|
+
- name: Checkout (full history for rebase)
|
|
217
|
+
if: steps.have_pat.outputs.skip != 'true'
|
|
218
|
+
uses: actions/checkout@v4
|
|
219
|
+
with:
|
|
220
|
+
fetch-depth: 0
|
|
221
|
+
token: ${{ secrets.AUTOFIX_TRIGGER_PAT }}
|
|
222
|
+
|
|
223
|
+
- name: Configure git author
|
|
224
|
+
if: steps.have_pat.outputs.skip != 'true'
|
|
225
|
+
run: |
|
|
226
|
+
git config user.email "actions@users.noreply.github.com"
|
|
227
|
+
git config user.name "github-actions[bot]"
|
|
228
|
+
|
|
229
|
+
- name: Rebase BEHIND PRs / flag DIRTY ones
|
|
230
|
+
if: steps.have_pat.outputs.skip != 'true'
|
|
231
|
+
run: |
|
|
232
|
+
set -euo pipefail
|
|
233
|
+
|
|
234
|
+
# mergeStateStatus values that matter:
|
|
235
|
+
# CLEAN - up to date with main (or ahead). skip.
|
|
236
|
+
# BEHIND - mergeable, base moved. clean rebase needed.
|
|
237
|
+
# DIRTY - conflicts. ask @claude.
|
|
238
|
+
# BLOCKED - branch protection or required checks failing. skip.
|
|
239
|
+
# UNSTABLE - mergeable but failing checks. skip — pre-existing.
|
|
240
|
+
# UNKNOWN - GitHub still computing. skip; next push retries.
|
|
241
|
+
#
|
|
242
|
+
# We exclude fork PRs: pushing to a fork branch from this workflow
|
|
243
|
+
# 403s, and posting @claude on a fork is blocked downstream by
|
|
244
|
+
# claude.yml's fork-refusal step anyway.
|
|
245
|
+
PRS_JSON=$(gh pr list --state open -R "$REPO" \
|
|
246
|
+
--json number,headRefName,mergeStateStatus,baseRefName,headRepository \
|
|
247
|
+
--jq "[.[] | select(.baseRefName == \"main\") | select(.headRepository.nameWithOwner == \"$REPO\")]")
|
|
248
|
+
|
|
249
|
+
echo "Open same-repo PRs targeting main:"
|
|
250
|
+
echo "$PRS_JSON" | jq -r '.[] | " #\(.number) [\(.mergeStateStatus)] \(.headRefName)"'
|
|
251
|
+
|
|
252
|
+
# Process each PR. Loop over JSON array via index so we can read
|
|
253
|
+
# only env-style fields and never interpolate untrusted data.
|
|
254
|
+
COUNT=$(echo "$PRS_JSON" | jq 'length')
|
|
255
|
+
for i in $(seq 0 $((COUNT - 1))); do
|
|
256
|
+
PR_NUM=$(echo "$PRS_JSON" | jq -r ".[$i].number")
|
|
257
|
+
BRANCH=$(echo "$PRS_JSON" | jq -r ".[$i].headRefName")
|
|
258
|
+
STATUS=$(echo "$PRS_JSON" | jq -r ".[$i].mergeStateStatus")
|
|
259
|
+
|
|
260
|
+
# Defensive: branch names are user-controlled. Refuse anything
|
|
261
|
+
# that isn't a sane ref. (`refs/heads/<name>` syntax: letters,
|
|
262
|
+
# digits, slashes, dashes, underscores, dots.)
|
|
263
|
+
if ! printf '%s' "$BRANCH" | grep -qE '^[A-Za-z0-9._/-]+$'; then
|
|
264
|
+
echo "::warning::PR #$PR_NUM has unsafe branch name; skipping"
|
|
265
|
+
continue
|
|
266
|
+
fi
|
|
267
|
+
|
|
268
|
+
case "$STATUS" in
|
|
269
|
+
BEHIND)
|
|
270
|
+
echo "PR #$PR_NUM ($BRANCH): clean rebase needed"
|
|
271
|
+
git fetch origin "$BRANCH":"refs/remotes/origin/$BRANCH" --force
|
|
272
|
+
git checkout -B "$BRANCH" "origin/$BRANCH"
|
|
273
|
+
if git rebase origin/main; then
|
|
274
|
+
if git push --force-with-lease origin "$BRANCH"; then
|
|
275
|
+
gh pr comment "$PR_NUM" -R "$REPO" \
|
|
276
|
+
--body "🔁 Auto-rebased on \`main\` (no conflicts) after $MAIN_SHA."
|
|
277
|
+
else
|
|
278
|
+
echo "::warning::Force-push of #$PR_NUM failed (lease lost?). Will retry on next push."
|
|
279
|
+
fi
|
|
280
|
+
else
|
|
281
|
+
# Race: GitHub said BEHIND but the rebase produced
|
|
282
|
+
# conflicts. The next push will see DIRTY and route to
|
|
283
|
+
# the @claude path.
|
|
284
|
+
git rebase --abort
|
|
285
|
+
echo "::warning::Rebase of #$PR_NUM failed despite BEHIND status (likely race with another push)"
|
|
286
|
+
fi
|
|
287
|
+
git checkout main
|
|
288
|
+
;;
|
|
289
|
+
DIRTY)
|
|
290
|
+
echo "PR #$PR_NUM ($BRANCH): conflicts — asking @claude to rebase"
|
|
291
|
+
# Idempotency: don't re-ask claude for the same main SHA.
|
|
292
|
+
MARKER="<!-- autofix-rebase-claude:$MAIN_SHA -->"
|
|
293
|
+
ALREADY=$(gh pr view "$PR_NUM" -R "$REPO" --json comments \
|
|
294
|
+
--jq "[.comments[] | select(.body | contains(\"$MARKER\"))] | length")
|
|
295
|
+
if [ "$ALREADY" -gt 0 ]; then
|
|
296
|
+
echo " already asked @claude for $MAIN_SHA — skipping"
|
|
297
|
+
continue
|
|
298
|
+
fi
|
|
299
|
+
gh pr comment "$PR_NUM" -R "$REPO" --body "$MARKER
|
|
300
|
+
@claude main has moved and this PR has merge conflicts. Please rebase \`$BRANCH\` on \`origin/main\` and resolve any conflicts. Run the local pre-push gate before force-pushing. If the conflicts indicate the underlying approach is invalidated by the new main, push back via a comment instead of force-fitting a resolution."
|
|
301
|
+
;;
|
|
302
|
+
CLEAN)
|
|
303
|
+
echo "PR #$PR_NUM ($BRANCH): already current — skipping"
|
|
304
|
+
;;
|
|
305
|
+
*)
|
|
306
|
+
echo "PR #$PR_NUM ($BRANCH): status $STATUS — leaving alone"
|
|
307
|
+
;;
|
|
308
|
+
esac
|
|
309
|
+
done
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
name: ci
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
lint-and-test:
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
steps:
|
|
12
|
+
- uses: actions/checkout@v4
|
|
13
|
+
|
|
14
|
+
# Skip the gates on chore/setup PRs that don't have Python sources yet.
|
|
15
|
+
# Without this, `uv sync --frozen` fails on a clean repo because there's
|
|
16
|
+
# no pyproject.toml/uv.lock to install from. Once the first pyproject.toml
|
|
17
|
+
# lands, the rest of the workflow runs.
|
|
18
|
+
- id: project_check
|
|
19
|
+
name: Detect Python project files
|
|
20
|
+
run: |
|
|
21
|
+
if [ -f pyproject.toml ] && [ -f uv.lock ]; then
|
|
22
|
+
echo "have_project=true" >> "$GITHUB_OUTPUT"
|
|
23
|
+
else
|
|
24
|
+
echo "have_project=false" >> "$GITHUB_OUTPUT"
|
|
25
|
+
echo "::notice::No pyproject.toml + uv.lock yet — skipping lint/test gates."
|
|
26
|
+
fi
|
|
27
|
+
|
|
28
|
+
- name: Install uv
|
|
29
|
+
if: steps.project_check.outputs.have_project == 'true'
|
|
30
|
+
uses: astral-sh/setup-uv@v6
|
|
31
|
+
with:
|
|
32
|
+
enable-cache: true
|
|
33
|
+
# Match pyproject.toml `requires-python`. Pinning here in CI catches
|
|
34
|
+
# any newer-syntax regressions that would break the lower bound.
|
|
35
|
+
# `python-version` here both installs that interpreter AND sets
|
|
36
|
+
# UV_PYTHON so every subsequent `uv sync`/`uv run` uses it.
|
|
37
|
+
python-version: "3.11"
|
|
38
|
+
|
|
39
|
+
- name: Sync deps
|
|
40
|
+
if: steps.project_check.outputs.have_project == 'true'
|
|
41
|
+
# `--frozen` forbids re-resolution: CI installs exactly what's in the
|
|
42
|
+
# committed uv.lock instead of silently picking up newer transitive
|
|
43
|
+
# versions. If uv.lock is missing or stale vs. pyproject.toml, this
|
|
44
|
+
# fails fast — exactly what we want in CI.
|
|
45
|
+
run: uv sync --frozen
|
|
46
|
+
|
|
47
|
+
- name: Ruff check
|
|
48
|
+
if: steps.project_check.outputs.have_project == 'true'
|
|
49
|
+
run: uv run ruff check .
|
|
50
|
+
|
|
51
|
+
- name: Ruff format check
|
|
52
|
+
if: steps.project_check.outputs.have_project == 'true'
|
|
53
|
+
run: uv run ruff format --check .
|
|
54
|
+
|
|
55
|
+
- name: Pyright
|
|
56
|
+
if: steps.project_check.outputs.have_project == 'true'
|
|
57
|
+
run: uv run pyright
|
|
58
|
+
|
|
59
|
+
- name: Pytest
|
|
60
|
+
if: steps.project_check.outputs.have_project == 'true'
|
|
61
|
+
run: uv run pytest --cov=tools --cov-report=term-missing
|
|
62
|
+
|
|
63
|
+
- name: Lint overlay corpus
|
|
64
|
+
if: steps.project_check.outputs.have_project == 'true'
|
|
65
|
+
run: |
|
|
66
|
+
if [ -d overlays ] && [ "$(find overlays -name '*.yaml' -print -quit)" ]; then
|
|
67
|
+
uv run openaca lint overlays/
|
|
68
|
+
else
|
|
69
|
+
echo "::notice::no overlays present yet; skipping openaca lint"
|
|
70
|
+
fi
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
name: Claude Code
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
issue_comment:
|
|
5
|
+
types: [created]
|
|
6
|
+
pull_request_review_comment:
|
|
7
|
+
types: [created]
|
|
8
|
+
# 'assigned' is intentionally NOT included: the assigned event fires under
|
|
9
|
+
# the assigner's identity but `github.event.issue.author_association` stays
|
|
10
|
+
# the OPENER, so we can't reliably gate it. The practical 'invoke @claude
|
|
11
|
+
# on an existing issue' path is to comment '@claude ...' on the issue —
|
|
12
|
+
# that goes through issue_comment, where comment.author_association is the
|
|
13
|
+
# commenter and the gate works correctly.
|
|
14
|
+
issues:
|
|
15
|
+
types: [opened]
|
|
16
|
+
pull_request_review:
|
|
17
|
+
types: [submitted]
|
|
18
|
+
|
|
19
|
+
jobs:
|
|
20
|
+
claude:
|
|
21
|
+
# Only TRUSTED actors (repo OWNER/MEMBER/COLLABORATOR) can invoke @claude.
|
|
22
|
+
# The job has write perms on contents/pull-requests/issues, so any
|
|
23
|
+
# non-collaborator who could comment '@claude do X' on an issue/PR would
|
|
24
|
+
# otherwise be able to trigger arbitrary commits/comments under our token.
|
|
25
|
+
if: |
|
|
26
|
+
(github.event_name == 'issue_comment' && contains(github.event.comment.body, '@claude') && (github.event.comment.author_association == 'OWNER' || github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'COLLABORATOR')) ||
|
|
27
|
+
(github.event_name == 'pull_request_review_comment' && contains(github.event.comment.body, '@claude') && (github.event.comment.author_association == 'OWNER' || github.event.comment.author_association == 'MEMBER' || github.event.comment.author_association == 'COLLABORATOR')) ||
|
|
28
|
+
(github.event_name == 'pull_request_review' && contains(github.event.review.body, '@claude') && (github.event.review.author_association == 'OWNER' || github.event.review.author_association == 'MEMBER' || github.event.review.author_association == 'COLLABORATOR')) ||
|
|
29
|
+
(github.event_name == 'issues' && (contains(github.event.issue.body, '@claude') || contains(github.event.issue.title, '@claude')) && (github.event.issue.author_association == 'OWNER' || github.event.issue.author_association == 'MEMBER' || github.event.issue.author_association == 'COLLABORATOR'))
|
|
30
|
+
runs-on: ubuntu-latest
|
|
31
|
+
permissions:
|
|
32
|
+
# Write across contents/pull-requests/issues so @claude can do the work
|
|
33
|
+
# people @-mention it for: post comments, create branches, push commits.
|
|
34
|
+
# The default read-only token would silently 403 on any of those API
|
|
35
|
+
# calls, breaking the entire mention-driven flow.
|
|
36
|
+
contents: write
|
|
37
|
+
pull-requests: write
|
|
38
|
+
issues: write
|
|
39
|
+
id-token: write
|
|
40
|
+
actions: read # Required for Claude to read CI results on PRs
|
|
41
|
+
steps:
|
|
42
|
+
# Refuse to operate on fork PRs. The if: gate above only checks the
|
|
43
|
+
# COMMENTER's association — but the next step checks out the PR head
|
|
44
|
+
# and the action then runs with write tokens. Fork PR + write secrets
|
|
45
|
+
# against untrusted code is a write-token-exposure risk. Native
|
|
46
|
+
# filtering in `if:` only works for pull_request_review* events (the
|
|
47
|
+
# payload has head.repo); for issue_comment events the PR head repo
|
|
48
|
+
# isn't in the payload, so we resolve it at runtime via the API.
|
|
49
|
+
- name: Refuse fork PRs (write-token exposure)
|
|
50
|
+
if: github.event.issue.pull_request != null || github.event.pull_request != null
|
|
51
|
+
env:
|
|
52
|
+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
53
|
+
REPO: ${{ github.repository }}
|
|
54
|
+
PR_NUM: ${{ github.event.issue.number || github.event.pull_request.number }}
|
|
55
|
+
run: |
|
|
56
|
+
HEAD_REPO=$(gh pr view "$PR_NUM" -R "$REPO" --json headRepository --jq '.headRepository.nameWithOwner')
|
|
57
|
+
if [ "$HEAD_REPO" != "$REPO" ]; then
|
|
58
|
+
echo "::error::Refusing to run @claude on PR from fork ($HEAD_REPO) — workflow grants write tokens and would expose secrets to untrusted code."
|
|
59
|
+
exit 1
|
|
60
|
+
fi
|
|
61
|
+
|
|
62
|
+
- name: Checkout repository
|
|
63
|
+
uses: actions/checkout@v4
|
|
64
|
+
with:
|
|
65
|
+
# Full history. `fetch-depth: 1` (default) breaks rebase tasks:
|
|
66
|
+
# `git merge-base origin/main HEAD` returns empty on a shallow
|
|
67
|
+
# clone whose ancestor commit isn't in the local object store,
|
|
68
|
+
# and the action then mis-reports the branch as having "no
|
|
69
|
+
# common ancestor" / "unrelated histories" — see PR #38 where
|
|
70
|
+
# this caused a clean rebase to be replaced with a merge commit
|
|
71
|
+
# on a falsely-claimed unrelated-histories premise. Cost is
|
|
72
|
+
# one slower checkout; correctness is not optional.
|
|
73
|
+
fetch-depth: 0
|
|
74
|
+
# For `issue_comment` on a PR, GitHub sets `github.sha` to the
|
|
75
|
+
# default branch's HEAD — NOT the PR head. Checking out that SHA
|
|
76
|
+
# means PR-only files don't exist on disk and the action crashes
|
|
77
|
+
# on `git hash-object`. Resolve to the PR head explicitly:
|
|
78
|
+
# - pull_request_review / _review_comment expose pull_request.head.sha
|
|
79
|
+
# - issue_comment on a PR has issue.pull_request set; build the
|
|
80
|
+
# head ref from the PR number
|
|
81
|
+
# - issues:opened (not on a PR) -> fall through to github.ref
|
|
82
|
+
# The fork-refusal step above guarantees same-repo head when we reach this.
|
|
83
|
+
ref: ${{ github.event.pull_request.head.sha || (github.event.issue.pull_request && format('refs/pull/{0}/head', github.event.issue.number)) || github.ref }}
|
|
84
|
+
|
|
85
|
+
# Install uv + sync deps so @claude can run the same lint/test gates
|
|
86
|
+
# the pre-push hook + ci.yml run, before pushing commits to a PR.
|
|
87
|
+
# Without this, @claude's pushes skip the gate entirely and PR-breaking
|
|
88
|
+
# lint/format/type/test errors land on the PR before anyone notices.
|
|
89
|
+
- name: Install uv
|
|
90
|
+
uses: astral-sh/setup-uv@v6
|
|
91
|
+
with:
|
|
92
|
+
# Match pyproject.toml `requires-python` and ci.yml. `python-version`
|
|
93
|
+
# here installs that interpreter AND sets UV_PYTHON, so every
|
|
94
|
+
# `uv sync`/`uv run` in this job uses it.
|
|
95
|
+
python-version: "3.11"
|
|
96
|
+
|
|
97
|
+
- name: Sync deps
|
|
98
|
+
# `--frozen` forbids re-resolution: install exactly what's in the
|
|
99
|
+
# committed uv.lock. Same behavior as ci.yml so claude bot's pushes
|
|
100
|
+
# hit the same dep set the rest of CI uses.
|
|
101
|
+
run: uv sync --frozen
|
|
102
|
+
|
|
103
|
+
- name: Install pre-push hook
|
|
104
|
+
run: bash scripts/install-hooks.sh
|
|
105
|
+
|
|
106
|
+
- name: Run Claude Code
|
|
107
|
+
id: claude
|
|
108
|
+
uses: anthropics/claude-code-action@v1
|
|
109
|
+
with:
|
|
110
|
+
claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }}
|
|
111
|
+
|
|
112
|
+
# Lets Claude read CI results on PRs.
|
|
113
|
+
additional_permissions: |
|
|
114
|
+
actions: read
|
|
115
|
+
|
|
116
|
+
# Extend the action's default allowed tools with the dev-loop
|
|
117
|
+
# commands the pre-push hook gates on. Without these, Claude can
|
|
118
|
+
# commit format-broken code but the wrapped `git push` script's
|
|
119
|
+
# hook fails on `ruff format --check` and the bot has no way to
|
|
120
|
+
# run the formatter to recover. PR #8 hit this exact loop — bot
|
|
121
|
+
# wedged trying to hand-reformat to match ruff's output.
|
|
122
|
+
#
|
|
123
|
+
# Each `Bash(<cmd>:*)` entry is a glob over arg lists for that
|
|
124
|
+
# exact prefix. Keep prefixes specific so we don't open a wide
|
|
125
|
+
# `Bash(*)` hole on a write-token job.
|
|
126
|
+
claude_args: |
|
|
127
|
+
--allowed-tools "Bash(uv run ruff format:*),Bash(uv run ruff check:*),Bash(uv run pyright:*),Bash(uv run pytest:*)"
|
|
128
|
+
|
|
129
|
+
# Surface Claude's full SDK conversation in the workflow log.
|
|
130
|
+
# Default is `false` (per claude-code-action upstream) which hides
|
|
131
|
+
# everything as "Running Claude Code via SDK (full output hidden
|
|
132
|
+
# for security)..." and makes silent stalls impossible to diagnose
|
|
133
|
+
# — runs reporting "success" while making zero commits, with no
|
|
134
|
+
# way to tell if Claude hit a token cap, got stuck on a tool call,
|
|
135
|
+
# or decided to bail mid-task.
|
|
136
|
+
#
|
|
137
|
+
# Privacy / security tradeoff: workflow logs are visible to anyone
|
|
138
|
+
# with read access to Actions on this repo. The action's default
|
|
139
|
+
# prompt construction includes PR title, body, comments, and
|
|
140
|
+
# review threads — all of which are already public on the PR. We
|
|
141
|
+
# don't pass secrets through the prompt. Acceptable.
|
|
142
|
+
show_full_output: true
|