commitguardian 0.1.0__tar.gz → 0.1.2__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.
- {commitguardian-0.1.0 → commitguardian-0.1.2}/CHANGELOG.md +40 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/CITATION.cff +2 -2
- {commitguardian-0.1.0 → commitguardian-0.1.2}/PKG-INFO +7 -3
- {commitguardian-0.1.0 → commitguardian-0.1.2}/README.md +6 -2
- {commitguardian-0.1.0 → commitguardian-0.1.2}/config/default.yaml +6 -2
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/api/README.md +1 -1
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/cli/doctor.md +1 -1
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/configuration.md +62 -10
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/deployment/README.md +1 -1
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/deployment/local.md +1 -1
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/deployment/production.md +1 -1
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/deployment/troubleshooting.md +1 -1
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/git-hooks.md +10 -3
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/maintainers/release-process.md +35 -1
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/security/threat-model.md +9 -3
- {commitguardian-0.1.0 → commitguardian-0.1.2}/pyproject.toml +1 -1
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/__init__.py +1 -1
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/cli/commands/doctor.py +13 -1
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/cli/commands/hook.py +34 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/cli/render.py +4 -2
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/config/defaults.py +6 -2
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/config/enforcement.py +1 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/config/schema.py +9 -1
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/detectors/identity.py +1 -1
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/git/repository.py +79 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/services/hooks.py +49 -11
- commitguardian-0.1.2/src/commitguard/services/push_fix.py +211 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/integration/hooks/test_pre_push.py +274 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/unit/config/test_config_validation.py +29 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/unit/policies/test_governance_resolution.py +9 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/.gitignore +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/CODE_OF_CONDUCT.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/CONTRIBUTING.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/LICENSE +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/SECURITY.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/action.yml +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/config/examples/allow-ai.yaml +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/config/examples/block-ai-coauthors.yaml +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/config/examples/enterprise.yaml +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/config/strict.yaml +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/README.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/adr/001-detection-separate-from-policy.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/adr/002-rules-as-data.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/adr/003-fail-closed.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/adr/004-trusted-policy-source.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/adr/005-no-regex-trailer-parser.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/adr/006-mandatory-policy-only-strengthens.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/adr/007-immutable-evidence.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/adr/008-not-published-to-pypi.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/adr/009-published-to-pypi-as-commitguardian.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/adr/README.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/api/audit.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/api/authentication.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/api/authorization.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/api/bulk-operations.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/api/compliance-exports.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/api/errors.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/api/exceptions.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/api/github-installations.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/api/notifications.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/api/organizations.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/api/pagination.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/api/policies.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/api/policy-drafts.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/api/rate-limits.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/api/repositories.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/api/repository-groups.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/api/rollouts.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/api/rules.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/api/scan-schedules.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/api/scans.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/api/security-posture.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/api/simulations.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/api/violations.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/api/webhooks.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/architecture.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/cli/README.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/cli/benchmark.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/cli/github.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/cli/init.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/cli/install.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/cli/policy.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/cli/reproduce.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/cli/scan.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/community/README.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/community/case-study-template.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/community/data-policy.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/community/external-evaluation.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/community/feedback-triage.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/community/good-first-issues.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/community/pilot-program.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/compliance-reporting.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/dashboard.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/deployment/github-actions.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/deployment/github-app.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/deployment/organization.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/deployment/self-hosted.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/deployment.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/detection-engine.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/evidence/cli.json +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/evidence/phase-10-report.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/evidence/phase-10-verification-report.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/evidence/tests.json +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/evidence/timeline.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/evidence/validation-matrix.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/getting-started/quickstart.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/github-app.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/github-enforcement.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/maintainers/README.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/maintainers/architecture.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/maintainers/benchmarking.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/maintainers/development.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/maintainers/incident-response.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/maintainers/repository-management.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/maintainers/security.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/merge-queue.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/notifications.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/operations/runbook.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/organization-governance.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/policy-engine.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/policy-exceptions.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/policy-inheritance.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/policy-management.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/policy-rollouts.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/policy-simulation.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/presentations/commitguard-technical-overview.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/presentations/technical-article.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/recovery.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/repository-management.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/research/README.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/research/bypass-resistance.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/research/commitguard-architecture.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/research/cross-platform-validation.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/research/detection-evaluation.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/research/fuzzing.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/research/github-enforcement-validation.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/research/impact.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/research/innovation.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/research/limitations.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/research/merge-queue-security.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/research/methodology.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/research/performance-evaluation.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/research/policy-tampering.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/research/problem-definition.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/research/reliability-evaluation.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/research/reproducibility.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/research/roadmap.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/security/README.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/security/authorization-matrix.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/security/ci-pipeline-security.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/security/incident-response.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/security/review-guide.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/security/security-boundaries.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/security/supply-chain.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/security/vulnerability-response.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/security-posture.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/support/compatibility-policy.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/support/compatibility.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/support/maintenance-policy.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/docs/threat-model.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/examples/README.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/examples/basic/.commitguard.yaml +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/examples/basic/README.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/examples/basic/commits/ai-coauthor.txt +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/examples/basic/commits/ai-tool-footer.txt +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/examples/basic/commits/clean.txt +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/examples/basic/commits/disguised-coauthor.txt +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/examples/basic/commits/malformed-trailer.txt +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/examples/basic/expected.json +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/examples/exceptions/README.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/examples/github-actions/README.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/examples/github-actions/commitguard.yml +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/examples/github-app/README.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/examples/organization-policy/README.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/examples/organization-policy/mandatory-policy.yaml +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/examples/organization-policy/repository-attempt.yaml +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/examples/policy-rollout/README.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/hooks/README.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/hooks/commit-msg +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/hooks/pre-commit +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/hooks/pre-push +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/requirements/ci.in +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/requirements/ci.txt +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/rules/ai-domains.yaml +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/rules/ai-identities.yaml +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/rules/bot-identities.yaml +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/rules/patterns.yaml +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/__main__.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/api/__init__.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/api/app.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/api/governance.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/api/hosting.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/api/http.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/api/settings.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/audit/__init__.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/audit/logger.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/audit/models.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/audit/storage.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/ci/__init__.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/ci/context.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/cli/__init__.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/cli/app.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/cli/commands/__init__.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/cli/commands/benchmark.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/cli/commands/check.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/cli/commands/ci.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/cli/commands/dashboard.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/cli/commands/github.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/cli/commands/init.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/cli/commands/install.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/cli/commands/policy.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/cli/commands/report.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/cli/commands/reproduce.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/cli/commands/scan.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/cli/common.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/cli/output.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/config/__init__.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/config/loader.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/config/sources.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/controlplane/__init__.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/controlplane/access.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/controlplane/commands.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/controlplane/errors.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/controlplane/identity.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/controlplane/members.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/controlplane/notifications.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/controlplane/pagination.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/controlplane/policies.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/controlplane/queries.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/controlplane/results.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/controlplane/rules.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/controlplane/views.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/core/__init__.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/core/context.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/core/decision.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/core/engine.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/core/result.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/detectors/__init__.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/detectors/base.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/detectors/bot.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/detectors/coauthor.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/detectors/registry.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/detectors/trailer.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/exceptions/__init__.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/exceptions/base.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/exceptions/configuration.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/exceptions/detection.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/exceptions/git.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/exceptions/service.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/git/__init__.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/git/commands.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/git/commit.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/git/diff.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/git/hooks.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/git/push.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/git/ranges.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/github/__init__.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/github/actions.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/github/app.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/github/auth.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/github/check_runs.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/github/checks.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/github/client.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/github/enforcement_status.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/github/errors.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/github/events.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/github/identifiers.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/github/installations.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/github/markdown.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/github/permissions.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/github/pull_requests.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/github/queue.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/github/recovery.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/github/repositories.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/github/server.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/github/settings.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/github/storage.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/github/webhooks.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/github/worker.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/github/workflow.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/governance/__init__.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/governance/bulk.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/governance/cache.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/governance/common.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/governance/exceptions.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/governance/groups.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/governance/inventory.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/governance/posture.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/governance/resolver.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/governance/rollouts.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/governance/rules.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/governance/schedules.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/governance/service.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/governance/settings.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/governance/simulation.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/governance/workflow.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/notifications/__init__.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/notifications/channels/__init__.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/notifications/channels/base.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/notifications/channels/email.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/notifications/channels/in_app.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/notifications/channels/sink.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/notifications/channels/webhook.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/notifications/deduplication.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/notifications/dispatcher.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/notifications/models.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/notifications/outbox.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/notifications/preferences.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/notifications/retry.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/notifications/service.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/notifications/settings.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/notifications/templates.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/observability/__init__.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/observability/logging.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/observability/metrics.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/policies/__init__.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/policies/defaults.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/policies/evaluator.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/policies/governance.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/policies/loader.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/policies/mandatory.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/policies/model.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/provenance/__init__.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/provenance/author.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/provenance/committer.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/provenance/normalization.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/provenance/signatures.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/provenance/trailers.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/research/__init__.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/research/compare.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/research/datasets.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/research/detection.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/research/environment.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/research/gitenv.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/research/hooks.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/research/metrics.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/research/performance.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/research/platform.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/research/report.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/research/repository.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/research/reproduction.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/research/results.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/rules/__init__.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/rules/loader.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/rules/matcher.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/rules/models.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/security/__init__.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/security/hashing.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/security/rate_limit.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/security/safe_yaml.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/security/sanitization.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/security/secrets.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/security/validation.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/services/__init__.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/services/analysis.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/services/audit.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/services/ci.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/services/enforcement.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/services/remediation.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/services/reports.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/services/scan.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/utils/__init__.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/utils/filesystem.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/utils/platform.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/src/commitguard/utils/subprocess.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/conftest.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/e2e/dashboard_harness.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/fixtures/commits/ai_agent_author.yaml +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/fixtures/commits/ai_wording_only.yaml +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/fixtures/commits/bot_author.yaml +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/fixtures/commits/chatgpt_coauthor.yaml +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/fixtures/commits/claude_coauthor.yaml +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/fixtures/commits/copilot_coauthor.yaml +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/fixtures/commits/generated_with_trailer.yaml +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/fixtures/commits/human_coauthor.yaml +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/fixtures/commits/malformed_trailer.yaml +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/fixtures/commits/malicious_message.yaml +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/fixtures/commits/multiple_coauthors.yaml +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/fixtures/commits/normal_human.yaml +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/fixtures/commits/unknown_ai_agent.yaml +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/fixtures/policies/alias.yaml +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/fixtures/policies/duplicate-keys.yaml +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/fixtures/policies/null-action.yaml +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/fixtures/policies/python-tag.yaml +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/fixtures/policies/string-boolean.yaml +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/fixtures/policies/unknown-key.yaml +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/fixtures/policies/unknown-policy.yaml +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/fixtures/policies/valid-minimal.yaml +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/fixtures/repositories/README.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/fixtures/webhooks/README.md +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/fixtures/webhooks/installation.created.json +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/fixtures/webhooks/installation.deleted.json +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/fixtures/webhooks/installation_repositories.added.json +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/fixtures/webhooks/installation_repositories.removed.json +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/fixtures/webhooks/pull_request.closed.json +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/fixtures/webhooks/pull_request.opened.json +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/fixtures/webhooks/pull_request.reopened.json +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/fixtures/webhooks/pull_request.synchronize.json +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/fixtures/webhooks/push.json +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/integration/git/test_cli_commands.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/integration/git/test_repository.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/integration/github/app/conftest.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/integration/github/app/dashboard/conftest.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/integration/github/app/dashboard/test_dashboard_api.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/integration/github/app/dashboard/test_dashboard_authorization.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/integration/github/app/dashboard/test_dashboard_lifecycle.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/integration/github/app/dashboard/test_dashboard_notifications.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/integration/github/app/dashboard/test_dashboard_performance.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/integration/github/app/dashboard/test_dashboard_policies.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/integration/github/app/dashboard/test_dashboard_recovery.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/integration/github/app/dashboard/test_dashboard_security.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/integration/github/app/dashboard/test_governance_end_to_end.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/integration/github/app/dashboard/test_governance_isolation.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/integration/github/app/dashboard/test_governance_operations.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/integration/github/app/dashboard/test_governance_performance.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/integration/github/app/dashboard/test_governance_policies.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/integration/github/app/dashboard/test_governance_rollouts.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/integration/github/app/dashboard/test_governance_workflow.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/integration/github/app/security/test_server_enforcement_experiments.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/integration/github/app/test_app_action_consistency.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/integration/github/app/test_app_cli.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/integration/github/app/test_app_concurrency.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/integration/github/app/test_app_end_to_end.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/integration/github/app/test_app_http.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/integration/github/app/test_app_merge_queue_reruns.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/integration/github/app/test_app_security.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/integration/github/conftest.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/integration/github/security/test_bypass_resistance.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/integration/github/test_action_scripts.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/integration/github/test_ci_github.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/integration/github/test_ci_security.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/integration/github/test_defense_in_depth.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/integration/github/test_github_cli.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/integration/github/test_github_placeholder.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/integration/hooks/conftest.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/integration/hooks/test_commit_hooks.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/integration/hooks/test_end_to_end.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/integration/hooks/test_hook_templates.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/integration/hooks/test_install.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/integration/test_examples.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/security/conftest.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/security/test_fuzz_parsers.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/security/test_properties.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/security/test_redos.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/unit/controlplane/test_controlplane.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/unit/controlplane/test_governance_units.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/unit/core/test_decision.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/unit/core/test_engine.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/unit/core/test_result.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/unit/detectors/test_bot.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/unit/detectors/test_coauthor.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/unit/detectors/test_identity.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/unit/detectors/test_registry.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/unit/detectors/test_trailer.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/unit/git/test_hook_rendering.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/unit/git/test_push_parsing.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/unit/github/app/conftest.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/unit/github/app/test_auth.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/unit/github/app/test_client.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/unit/github/app/test_phase7_events_and_storage.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/unit/github/app/test_policy_and_checks.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/unit/github/app/test_secrets_and_logging.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/unit/github/app/test_service_entry_points.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/unit/github/app/test_storage.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/unit/github/app/test_webhook_events.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/unit/github/app/test_webhooks.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/unit/github/test_events_and_output.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/unit/github/test_workflows_static.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/unit/notifications/test_notification_units.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/unit/policies/test_evaluator.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/unit/policies/test_policy_loader.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/unit/provenance/test_identity_parsing.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/unit/provenance/test_models.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/unit/provenance/test_trailers.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/unit/research/test_compare.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/unit/rules/test_matcher.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/unit/rules/test_rules_loader.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/unit/security/test_safe_subprocess.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/unit/security/test_sanitization.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/unit/security/test_validation.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/unit/services/test_analyzer.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/unit/services/test_remediation.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/unit/test_architecture.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/unit/test_documented_commands.py +0 -0
- {commitguardian-0.1.0 → commitguardian-0.1.2}/tests/unit/test_env_example.py +0 -0
|
@@ -4,6 +4,46 @@ All notable changes to this project are documented here. The format follows
|
|
|
4
4
|
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project uses
|
|
5
5
|
[Semantic Versioning](https://semver.org/).
|
|
6
6
|
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
## [0.1.2] - 2026-09-19
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- `remediation.fix_on_push` did nothing for `git push origin HEAD` or
|
|
14
|
+
`git push origin HEAD:main`. For those forms Git reports the local ref as plain
|
|
15
|
+
`HEAD`, which the "only move local branches" guard did not recognise, so the
|
|
16
|
+
push was blocked instead of cleaned (safely - nothing was rewritten or sent).
|
|
17
|
+
`HEAD` is now resolved to the checked-out branch when that branch's tip is the
|
|
18
|
+
commit being pushed; a detached `HEAD` is still refused. Found by testing the
|
|
19
|
+
released 0.1.1 package on a real machine.
|
|
20
|
+
|
|
21
|
+
## [0.1.1] - 2026-09-19
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- **`remediation.fix_on_push`** (opt-in, off by default): the `pre-push` hook
|
|
26
|
+
removes AI, bot and agent attribution from *unpushed* commits that never
|
|
27
|
+
passed through `commit-msg` - made with `git commit --no-verify`,
|
|
28
|
+
`git cherry-pick`, `git rebase`, `git am`, or tools that skip hooks - then
|
|
29
|
+
stops that push so the next `git push` sends the cleaned commits (a hook
|
|
30
|
+
cannot change what a push in progress sends). Only the message changes: the
|
|
31
|
+
tree, author, committer and timestamps are copied byte for byte, and the
|
|
32
|
+
originals stay in the reflog. It refuses, and blocks as before, for any
|
|
33
|
+
commit a remote-tracking branch contains, tags, signed commits, identity
|
|
34
|
+
findings, a message that would be left empty, or an unfinished rebase, merge,
|
|
35
|
+
cherry-pick, revert or bisect; if any commit cannot be fixed, none are
|
|
36
|
+
rewritten; and nothing moves unless every rewritten commit re-analyses clean.
|
|
37
|
+
Branches move atomically with a compare-and-swap. `commitguard doctor` warns
|
|
38
|
+
while it is on, and a mandatory policy cannot enable it. Each of these guards
|
|
39
|
+
is covered by a test that fails when the guard is removed.
|
|
40
|
+
|
|
41
|
+
### Changed
|
|
42
|
+
|
|
43
|
+
- The blocked-push message no longer says "CommitGuard never modifies
|
|
44
|
+
commits", which is not true when `fix_on_push` is enabled. It now says the
|
|
45
|
+
commits were not changed and points to the setting.
|
|
46
|
+
|
|
7
47
|
## [0.1.0] - 2026-09-18
|
|
8
48
|
|
|
9
49
|
### Packaging
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: commitguardian
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: Block AI, bot and agent attribution in Git commits, locally and on GitHub.
|
|
5
5
|
Project-URL: Homepage, https://github.com/oyinlola-tech/commitguard
|
|
6
6
|
Project-URL: Repository, https://github.com/oyinlola-tech/commitguard
|
|
@@ -325,8 +325,12 @@ result=BLOCK commits=1 block=1 warn=0 allow=0
|
|
|
325
325
|
| `1` | blocked by policy (a finding or detector failure evaluated to `block`) |
|
|
326
326
|
| `2` | error: invalid configuration/rules, Git error, bad arguments, unexpected failure (hooks block on errors) |
|
|
327
327
|
|
|
328
|
-
CommitGuard never modifies commits or rewrites history; remediation
|
|
329
|
-
left to the developer.
|
|
328
|
+
By default CommitGuard never modifies commits or rewrites history; remediation
|
|
329
|
+
is left to the developer. Two opt-in settings change that, locally only:
|
|
330
|
+
`remediation.auto_remove` deletes AI attribution from a message before the
|
|
331
|
+
commit is created, and `remediation.fix_on_push` rewrites *unpushed* commits
|
|
332
|
+
that slipped past it (see [configuration](docs/configuration.md#remediation)).
|
|
333
|
+
The GitHub Action and App never rewrite anything.
|
|
330
334
|
|
|
331
335
|
## Configuration
|
|
332
336
|
|
|
@@ -285,8 +285,12 @@ result=BLOCK commits=1 block=1 warn=0 allow=0
|
|
|
285
285
|
| `1` | blocked by policy (a finding or detector failure evaluated to `block`) |
|
|
286
286
|
| `2` | error: invalid configuration/rules, Git error, bad arguments, unexpected failure (hooks block on errors) |
|
|
287
287
|
|
|
288
|
-
CommitGuard never modifies commits or rewrites history; remediation
|
|
289
|
-
left to the developer.
|
|
288
|
+
By default CommitGuard never modifies commits or rewrites history; remediation
|
|
289
|
+
is left to the developer. Two opt-in settings change that, locally only:
|
|
290
|
+
`remediation.auto_remove` deletes AI attribution from a message before the
|
|
291
|
+
commit is created, and `remediation.fix_on_push` rewrites *unpushed* commits
|
|
292
|
+
that slipped past it (see [configuration](docs/configuration.md#remediation)).
|
|
293
|
+
The GitHub Action and App never rewrite anything.
|
|
290
294
|
|
|
291
295
|
## Configuration
|
|
292
296
|
|
|
@@ -33,7 +33,11 @@ enforcement:
|
|
|
33
33
|
# What to do about a violation. By default CommitGuard blocks and leaves the
|
|
34
34
|
# message to you. Set auto_remove: true and the commit-msg hook deletes the
|
|
35
35
|
# offending lines instead, reports what it removed, and lets the commit through.
|
|
36
|
-
#
|
|
37
|
-
#
|
|
36
|
+
# Set fix_on_push: true and pre-push also rewrites unpushed commits that skipped
|
|
37
|
+
# commit-msg (--no-verify, cherry-pick, rebase), then asks you to push again; it
|
|
38
|
+
# never touches a commit any remote already has. Attribution in the author or
|
|
39
|
+
# committer identity always blocks: no edit to the message can fix that.
|
|
40
|
+
# `commitguard doctor` shows when either is on.
|
|
38
41
|
# remediation:
|
|
39
42
|
# auto_remove: true
|
|
43
|
+
# fix_on_push: true
|
|
@@ -5,7 +5,7 @@ dashboard is built on it. It exposes repositories, scans, violations, policies
|
|
|
5
5
|
and their governance, exceptions, audit events, notifications and GitHub
|
|
6
6
|
installations. The same process also receives GitHub webhooks.
|
|
7
7
|
|
|
8
|
-
> **Stability.** CommitGuard is pre-alpha (`0.1.
|
|
8
|
+
> **Stability.** CommitGuard is pre-alpha (`0.1.2`, "Development Status :: 2
|
|
9
9
|
> - Pre-Alpha"). The API is versioned `v1` in its path, but no compatibility
|
|
10
10
|
> promise exists yet. Endpoints, fields and error codes can change between
|
|
11
11
|
> commits. There is no OpenAPI document; these pages, the view models in
|
|
@@ -60,7 +60,7 @@ commitguard doctor --json
|
|
|
60
60
|
|
|
61
61
|
```json
|
|
62
62
|
{
|
|
63
|
-
"commitguard_version": "0.1.
|
|
63
|
+
"commitguard_version": "0.1.2",
|
|
64
64
|
"status": "HEALTHY",
|
|
65
65
|
"enforcement": "LOCAL ENFORCEMENT ONLY",
|
|
66
66
|
"counts": {"PASS": 14, "INFO": 1, "NOT CONFIGURED": 2, "WARNING": 0, "FAIL": 0},
|
|
@@ -73,7 +73,8 @@ never treated as a fully protected state.
|
|
|
73
73
|
|
|
74
74
|
```yaml
|
|
75
75
|
remediation:
|
|
76
|
-
auto_remove: false # default
|
|
76
|
+
auto_remove: false # default: remove attribution before a commit is created
|
|
77
|
+
fix_on_push: false # default: rewrite unpushed commits that slipped past it
|
|
77
78
|
```
|
|
78
79
|
|
|
79
80
|
By default CommitGuard blocks a violating commit and leaves the message to you.
|
|
@@ -98,17 +99,68 @@ The stripped message is re-analysed before the commit is allowed. If it is still
|
|
|
98
99
|
blocked, the original block stands: nothing is ever allowed on the assumption
|
|
99
100
|
that the removal worked.
|
|
100
101
|
|
|
101
|
-
|
|
102
|
+
A mandatory policy cannot set either option. A floor may only make enforcement
|
|
103
|
+
stricter, and both turn a block into something that proceeds, so `remediation:`
|
|
104
|
+
in a mandatory policy is a configuration error.
|
|
102
105
|
|
|
103
|
-
|
|
104
|
-
means rewriting history. CommitGuard never does that on its own; it blocks and
|
|
105
|
-
tells you which commits to amend or reword.
|
|
106
|
-
* **A mandatory policy cannot set it.** A floor may only make enforcement
|
|
107
|
-
stricter, and `auto_remove` turns a block into a commit, so
|
|
108
|
-
`remediation:` in a mandatory policy is a configuration error.
|
|
106
|
+
### `fix_on_push`: commits the commit-msg hook never saw
|
|
109
107
|
|
|
110
|
-
`
|
|
111
|
-
|
|
108
|
+
`auto_remove` only sees commits made with `git commit`. These create commits
|
|
109
|
+
without running `commit-msg`, so their attribution reaches `pre-push` intact:
|
|
110
|
+
|
|
111
|
+
| How the commit was made | Cleaned by `auto_remove`? |
|
|
112
|
+
|---|---|
|
|
113
|
+
| `git commit`, `git commit --amend`, `git merge` | yes |
|
|
114
|
+
| `git commit --no-verify` | no |
|
|
115
|
+
| `git cherry-pick`, `git rebase`, `git am` | no |
|
|
116
|
+
| tools that create commits without running hooks | no |
|
|
117
|
+
|
|
118
|
+
With `fix_on_push: true`, the `pre-push` hook rewrites those commits' messages
|
|
119
|
+
to remove the attribution. **It then stops that push.** Git decides which
|
|
120
|
+
objects a push sends before the hook runs, so the push in progress would still
|
|
121
|
+
carry the old commits; the next `git push` sends the cleaned ones:
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
$ git push
|
|
125
|
+
CommitGuard
|
|
126
|
+
! CLEANED 2 unpushed commits (AI attribution removed; history rewritten)
|
|
127
|
+
|
|
128
|
+
e70fd0f -> 23acf33 feat: two
|
|
129
|
+
removed: Co-Authored-By: Claude <noreply@anthropic.com> [ai_coauthor]
|
|
130
|
+
d91e62b -> 06d7cc6 feat: three
|
|
131
|
+
|
|
132
|
+
Only commits that no remote has were changed; the old ones are in
|
|
133
|
+
`git reflog`. Branch updated: main.
|
|
134
|
+
|
|
135
|
+
This push was stopped (Git had already chosen what to send).
|
|
136
|
+
Run `git push` again to send the cleaned commits.
|
|
137
|
+
|
|
138
|
+
$ git push
|
|
139
|
+
CommitGuard: ✓ 3 outgoing commits checked, no policy violations
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
This rewrites history, so it is deliberately narrow. It does nothing - and the
|
|
143
|
+
push is blocked as usual - unless every one of these holds:
|
|
144
|
+
|
|
145
|
+
* **No remote has the commits.** A commit contained in *any* remote-tracking
|
|
146
|
+
branch, not only the one you are pushing to, has been shared and is never
|
|
147
|
+
rewritten.
|
|
148
|
+
* **Only a local branch is being pushed.** Tags are never touched.
|
|
149
|
+
* **Every blocking finding is a message line.** An AI author or committer still
|
|
150
|
+
blocks. If any commit cannot be fixed, *none* are rewritten.
|
|
151
|
+
* **No commit is signed** (`gpgsig`, `mergetag`), since rewriting would silently
|
|
152
|
+
drop the signature, and none carries a header CommitGuard does not recognise.
|
|
153
|
+
* **No rebase, merge, cherry-pick, revert or bisect is in progress.**
|
|
154
|
+
* **Every rewritten commit re-analyses clean.** Nothing moves on assumption.
|
|
155
|
+
|
|
156
|
+
Only the message changes. The tree, author, committer and both timestamps are
|
|
157
|
+
copied byte for byte, so your working tree and index are untouched. Commits
|
|
158
|
+
after a cleaned one get new IDs because their parent changed; commits before it
|
|
159
|
+
keep theirs. Branches move atomically, and only if they still point where they
|
|
160
|
+
did when the push began. The originals stay in `git reflog`.
|
|
161
|
+
|
|
162
|
+
`commitguard doctor` reports a warning whenever either option is on, because
|
|
163
|
+
each changes a block into something that proceeds.
|
|
112
164
|
|
|
113
165
|
## Validation (security-relevant)
|
|
114
166
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Deploying CommitGuard
|
|
2
2
|
|
|
3
|
-
CommitGuard 0.1.
|
|
3
|
+
CommitGuard 0.1.2 is pre-alpha software. This section describes the ways it
|
|
4
4
|
can be deployed today, what each way protects against, and what it does not.
|
|
5
5
|
Every mode runs the same detection engine and policy evaluator; they differ in
|
|
6
6
|
where the check runs, who controls it, and whether it can prevent a merge.
|
|
@@ -25,7 +25,7 @@ and `commitguard-cli` on PyPI are unrelated projects.)
|
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
27
|
pipx install commitguardian
|
|
28
|
-
commitguard --version # commitguard 0.1.
|
|
28
|
+
commitguard --version # commitguard 0.1.2
|
|
29
29
|
|
|
30
30
|
# or pinned to an exact commit:
|
|
31
31
|
pipx install "git+https://github.com/oyinlola-tech/commitguard@<commit-sha>"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Running the App service in production
|
|
2
2
|
|
|
3
|
-
> Status: **Experimental.** CommitGuard is pre-alpha (0.1.
|
|
3
|
+
> Status: **Experimental.** CommitGuard is pre-alpha (0.1.2).
|
|
4
4
|
> No external production deployments have been recorded yet.
|
|
5
5
|
> The App service and organization governance have been validated only by this
|
|
6
6
|
> repository's test suites, against an offline model of GitHub. There are no
|
|
@@ -30,7 +30,7 @@ repository. Uninstall it and install from source, pinned to a commit:
|
|
|
30
30
|
```bash
|
|
31
31
|
python -m pip uninstall commitguard
|
|
32
32
|
pipx install "git+https://github.com/oyinlola-tech/commitguard@<commit-sha>"
|
|
33
|
-
commitguard --version # commitguard 0.1.
|
|
33
|
+
commitguard --version # commitguard 0.1.2
|
|
34
34
|
```
|
|
35
35
|
|
|
36
36
|
**`the GitHub App needs the optional dependencies: pip install 'commitguard[app]'`**
|
|
@@ -111,7 +111,7 @@ Allowed: 1
|
|
|
111
111
|
Evidence: Claude <noreply@anthropic.com> (Co-authored-by trailer, line 3)
|
|
112
112
|
...
|
|
113
113
|
How to fix:
|
|
114
|
-
CommitGuard
|
|
114
|
+
CommitGuard did not change these commits. ...
|
|
115
115
|
No changes were pushed to the remote repository.
|
|
116
116
|
```
|
|
117
117
|
|
|
@@ -266,8 +266,15 @@ CommitGuard
|
|
|
266
266
|
```
|
|
267
267
|
|
|
268
268
|
It is off by default and never applies to identity-based findings, which cannot
|
|
269
|
-
be fixed by editing text.
|
|
270
|
-
|
|
269
|
+
be fixed by editing text. See [configuration.md](configuration.md#remediation)
|
|
270
|
+
for the full rules.
|
|
271
|
+
|
|
272
|
+
Commits made with `git commit --no-verify`, `git cherry-pick`, `git rebase` or
|
|
273
|
+
`git am` skip `commit-msg`. `remediation.fix_on_push: true` makes `pre-push`
|
|
274
|
+
rewrite those *unpushed* commits' messages and stop the push, so the next
|
|
275
|
+
`git push` sends the cleaned commits. It never touches a commit any remote
|
|
276
|
+
already has, tags, signed commits or identity findings; see
|
|
277
|
+
[configuration.md](configuration.md#fix_on_push-commits-the-commit-msg-hook-never-saw).
|
|
271
278
|
|
|
272
279
|
## Doctor
|
|
273
280
|
|
|
@@ -29,7 +29,7 @@ Artifacts are not signed, and no build provenance attestation is generated.
|
|
|
29
29
|
| Runtime version (`commitguard --version`) | `src/commitguard/__init__.py`, `__version__` |
|
|
30
30
|
| Dashboard package version | `web/package.json` (`"private": true`, not released separately; currently `0.1.0`, independent of the Python version) |
|
|
31
31
|
|
|
32
|
-
Both Python locations must be changed together; today they both say `0.1.
|
|
32
|
+
Both Python locations must be changed together; today they both say `0.1.2`.
|
|
33
33
|
`tests/integration/git/test_cli_commands.py` checks that `commitguard --version`
|
|
34
34
|
prints `__version__`; no test compares it with `pyproject.toml`, so check both by
|
|
35
35
|
hand. The name in `pyproject.toml` is pinned by
|
|
@@ -234,6 +234,40 @@ Marketplace shows the tag, so a release candidate must not be listed - which is
|
|
|
234
234
|
why the workflow leaves an `rc` tag as a **draft prerelease** and only publishes
|
|
235
235
|
a final tag.
|
|
236
236
|
|
|
237
|
+
## When a release only half-finishes
|
|
238
|
+
|
|
239
|
+
The first real tag, `v0.1.0`, exposed two faults at once. Both are fixed; this
|
|
240
|
+
is what to do if a release stops partway.
|
|
241
|
+
|
|
242
|
+
**Symptom:** the tag exists, the GitHub release exists, but PyPI has nothing.
|
|
243
|
+
|
|
244
|
+
**Cause 1 - the build job imported the package it had just built.** It ran
|
|
245
|
+
`python -c "import commitguard"` from inside `dist/`, where the package is
|
|
246
|
+
neither installed nor on the path. The gate installs the package, so the gate
|
|
247
|
+
passed and the build failed. The version now comes from the wheel filename.
|
|
248
|
+
|
|
249
|
+
**Cause 2 - the release job assumed it was the one creating the release.**
|
|
250
|
+
Listing an Action on the Marketplace *requires* a published release, and the
|
|
251
|
+
web form creates the tag and the release together - which triggers this
|
|
252
|
+
workflow, which then found the release already there. `gh release create`
|
|
253
|
+
failed, so `publish` never ran. The job now creates **or updates**: it uploads
|
|
254
|
+
the artifacts to the existing release with `--clobber`, and leaves hand-written
|
|
255
|
+
notes alone.
|
|
256
|
+
|
|
257
|
+
**To finish a stalled release, do not move or reuse the tag.** Run the workflow
|
|
258
|
+
manually against it:
|
|
259
|
+
|
|
260
|
+
> Actions -> Release -> Run workflow -> *Use workflow from:* `main`
|
|
261
|
+
> -> *ref:* `v0.1.0`
|
|
262
|
+
|
|
263
|
+
The workflow definition comes from `main` (so you get the fixes) while the code
|
|
264
|
+
built comes from the tag (so you release what you tagged). The gate still
|
|
265
|
+
checks that the tag matches `__version__`, the release job attaches the missing
|
|
266
|
+
artifacts, and `publish` uploads to PyPI.
|
|
267
|
+
|
|
268
|
+
Passing a commit SHA instead of a tag stays a dry run: it builds and tests, and
|
|
269
|
+
skips both the release and the publish.
|
|
270
|
+
|
|
237
271
|
## Manual release checklist
|
|
238
272
|
|
|
239
273
|
- [ ] CI, Security and CommitGuard workflows green on the tagged commit
|
|
@@ -706,9 +706,15 @@ not file contents) of repositories while they are installed. See
|
|
|
706
706
|
- Detectors receive data, not a repository handle; architecture tests forbid
|
|
707
707
|
I/O imports in detection layers **[done]**.
|
|
708
708
|
- Git reads use `GIT_OPTIONAL_LOCKS=0` **[done]**.
|
|
709
|
-
- CommitGuard never
|
|
710
|
-
`
|
|
711
|
-
|
|
709
|
+
- CommitGuard never runs `git reset`, `rebase`, `commit --amend`,
|
|
710
|
+
`filter-branch` or `filter-repo`, and by default never rewrites commits or
|
|
711
|
+
history. The one exception is opt-in: with `remediation.fix_on_push`, the
|
|
712
|
+
`pre-push` hook rewrites the *message* of unpushed commits. It refuses any
|
|
713
|
+
commit a remote-tracking branch contains, tags, signed commits, identity
|
|
714
|
+
findings and in-progress rebases or merges; moves branches atomically with a
|
|
715
|
+
compare-and-swap; re-analyses every rewritten commit before moving anything;
|
|
716
|
+
and leaves the originals in the reflog. The server side (Action, App) never
|
|
717
|
+
rewrites anything **[by design; `tests/integration/hooks/test_pre_push.py`]**.
|
|
712
718
|
- `init` never overwrites an existing file **[done]**.
|
|
713
719
|
|
|
714
720
|
## Maintaining this document
|
|
@@ -7,7 +7,7 @@ build-backend = "hatchling.build"
|
|
|
7
7
|
# "commitguard-cli", belong to two unrelated projects (see docs/adr/009). The
|
|
8
8
|
# import package and the console script stay `commitguard`.
|
|
9
9
|
name = "commitguardian"
|
|
10
|
-
version = "0.1.
|
|
10
|
+
version = "0.1.2"
|
|
11
11
|
description = "Block AI, bot and agent attribution in Git commits, locally and on GitHub."
|
|
12
12
|
readme = "README.md"
|
|
13
13
|
license = "MIT"
|
|
@@ -182,7 +182,8 @@ def _hook_checks(repository: Repository, loaded: LoadedConfig | None) -> list[Ch
|
|
|
182
182
|
"set enforcement." + name.replace("-", "_") + ": true",
|
|
183
183
|
)
|
|
184
184
|
)
|
|
185
|
-
if loaded is not None
|
|
185
|
+
remediation = build_remediation(*loaded.configs) if loaded is not None else None
|
|
186
|
+
if remediation is not None and remediation.auto_remove:
|
|
186
187
|
# This turns a blocked commit into a commit. It must never be invisible.
|
|
187
188
|
checks.append(
|
|
188
189
|
Check(
|
|
@@ -193,6 +194,17 @@ def _hook_checks(repository: Repository, loaded: LoadedConfig | None) -> list[Ch
|
|
|
193
194
|
"set remediation.auto_remove: false to block instead",
|
|
194
195
|
)
|
|
195
196
|
)
|
|
197
|
+
if remediation is not None and remediation.fix_on_push:
|
|
198
|
+
checks.append(
|
|
199
|
+
Check(
|
|
200
|
+
"Enforcement",
|
|
201
|
+
Status.WARN,
|
|
202
|
+
"remediation.fix_on_push is on: pre-push rewrites unpushed commits to remove "
|
|
203
|
+
"prohibited attribution, then asks you to push again (never commits a remote "
|
|
204
|
+
"already has; identity findings still block)",
|
|
205
|
+
"set remediation.fix_on_push: false to block instead",
|
|
206
|
+
)
|
|
207
|
+
)
|
|
196
208
|
return checks
|
|
197
209
|
|
|
198
210
|
|
|
@@ -28,6 +28,7 @@ from commitguard.services.hooks import (
|
|
|
28
28
|
run_pre_commit,
|
|
29
29
|
run_pre_push,
|
|
30
30
|
)
|
|
31
|
+
from commitguard.services.push_fix import PushFix
|
|
31
32
|
|
|
32
33
|
MAX_PRE_PUSH_STDIN_BYTES = 8 * 1024 * 1024
|
|
33
34
|
|
|
@@ -86,6 +87,35 @@ def _removal_notice(run: HookRun) -> str:
|
|
|
86
87
|
return "\n".join(lines)
|
|
87
88
|
|
|
88
89
|
|
|
90
|
+
def _push_fix_notice(fixed: PushFix) -> str:
|
|
91
|
+
"""What ``remediation.fix_on_push`` rewrote. Always shown in full: it
|
|
92
|
+
changed commits the developer made."""
|
|
93
|
+
count = len(fixed.commits)
|
|
94
|
+
lines = [
|
|
95
|
+
"CommitGuard",
|
|
96
|
+
f"! CLEANED {count} unpushed commit{'s' if count != 1 else ''} "
|
|
97
|
+
"(AI attribution removed; history rewritten)",
|
|
98
|
+
"",
|
|
99
|
+
]
|
|
100
|
+
for commit in fixed.commits:
|
|
101
|
+
subject = sanitize_block(commit.subject, max_length=72)
|
|
102
|
+
lines.append(f" {commit.old_sha[:7]} -> {commit.new_sha[:7]} {subject}")
|
|
103
|
+
lines += [
|
|
104
|
+
f" removed: {sanitize_block(r.text, max_length=200)} [{r.rule_id}]"
|
|
105
|
+
for r in commit.removed
|
|
106
|
+
]
|
|
107
|
+
branches = ", ".join(ref.removeprefix("refs/heads/") for ref in fixed.branches)
|
|
108
|
+
lines += [
|
|
109
|
+
"",
|
|
110
|
+
" Only commits that no remote has were changed; the old ones are in",
|
|
111
|
+
f" `git reflog`. Branch updated: {branches}.",
|
|
112
|
+
"",
|
|
113
|
+
" This push was stopped (Git had already chosen what to send).",
|
|
114
|
+
" Run `git push` again to send the cleaned commits.",
|
|
115
|
+
]
|
|
116
|
+
return "\n".join(lines)
|
|
117
|
+
|
|
118
|
+
|
|
89
119
|
def _finish(run: HookRun, text: str) -> None:
|
|
90
120
|
if not run.enabled:
|
|
91
121
|
_err(
|
|
@@ -95,6 +125,10 @@ def _finish(run: HookRun, text: str) -> None:
|
|
|
95
125
|
return
|
|
96
126
|
if run.removed:
|
|
97
127
|
_err(_removal_notice(run))
|
|
128
|
+
if run.fixed is not None:
|
|
129
|
+
# The commits being pushed are the old ones: this push must not proceed.
|
|
130
|
+
_err(_push_fix_notice(run.fixed))
|
|
131
|
+
raise typer.Exit(code=int(ExitCode.BLOCKED))
|
|
98
132
|
_err(text)
|
|
99
133
|
if run.report is not None and run.report.action is Action.BLOCK:
|
|
100
134
|
raise typer.Exit(code=int(ExitCode.BLOCKED))
|
|
@@ -153,13 +153,15 @@ def render_check_text(report: ScanReport) -> str:
|
|
|
153
153
|
# --------------------------------------------------------------------------- #
|
|
154
154
|
_PUSH_REMEDIATION = [
|
|
155
155
|
"How to fix:",
|
|
156
|
-
" CommitGuard
|
|
157
|
-
" longer carry the attribution, then push again:",
|
|
156
|
+
" CommitGuard did not change these commits. Rewrite the blocked commits so",
|
|
157
|
+
" they no longer carry the attribution, then push again:",
|
|
158
158
|
" - latest commit only: git commit --amend",
|
|
159
159
|
" (replaces that one local commit with an edited copy)",
|
|
160
160
|
" - older commits: git rebase -i <commit>^ and mark them 'reword'",
|
|
161
161
|
" (recreates the selected commits and every commit after them)",
|
|
162
162
|
" Only rewrite commits that have not already been shared with others.",
|
|
163
|
+
" To have CommitGuard do this for unpushed commits, set",
|
|
164
|
+
" remediation.fix_on_push: true (see docs/configuration.md).",
|
|
163
165
|
]
|
|
164
166
|
|
|
165
167
|
|
|
@@ -46,8 +46,12 @@ enforcement:
|
|
|
46
46
|
# What to do about a violation. By default CommitGuard blocks and leaves the
|
|
47
47
|
# message to you. Set auto_remove: true and the commit-msg hook deletes the
|
|
48
48
|
# offending lines instead, reports what it removed, and lets the commit through.
|
|
49
|
-
#
|
|
50
|
-
#
|
|
49
|
+
# Set fix_on_push: true and pre-push also rewrites unpushed commits that skipped
|
|
50
|
+
# commit-msg (--no-verify, cherry-pick, rebase), then asks you to push again; it
|
|
51
|
+
# never touches a commit any remote already has. Attribution in the author or
|
|
52
|
+
# committer identity always blocks: no edit to the message can fix that.
|
|
53
|
+
# `commitguard doctor` shows when either is on.
|
|
51
54
|
# remediation:
|
|
52
55
|
# auto_remove: true
|
|
56
|
+
# fix_on_push: true
|
|
53
57
|
"""
|
|
@@ -64,13 +64,21 @@ class RemediationOverride(BaseModel):
|
|
|
64
64
|
it is then clean.
|
|
65
65
|
|
|
66
66
|
It is off by default, because it edits what the developer wrote.
|
|
67
|
+
|
|
68
|
+
``fix_on_push`` covers the commits ``commit-msg`` never saw (``--no-verify``,
|
|
69
|
+
cherry-pick, rebase, ``git am``). The ``pre-push`` hook rewrites the
|
|
70
|
+
*unpushed* commits on the branch to remove the same lines, then stops that
|
|
71
|
+
push: Git has already chosen which objects to send, so the corrected
|
|
72
|
+
commits go out on the next ``git push``. This rewrites history, so it is a
|
|
73
|
+
separate switch and also off by default.
|
|
67
74
|
"""
|
|
68
75
|
|
|
69
76
|
model_config = ConfigDict(frozen=True, extra="forbid")
|
|
70
77
|
|
|
71
78
|
auto_remove: StrictBool | None = None
|
|
79
|
+
fix_on_push: StrictBool | None = None
|
|
72
80
|
|
|
73
|
-
@field_validator("auto_remove", mode="before")
|
|
81
|
+
@field_validator("auto_remove", "fix_on_push", mode="before")
|
|
74
82
|
@classmethod
|
|
75
83
|
def _reject_explicit_null(cls, value: object) -> object:
|
|
76
84
|
if value is None:
|
|
@@ -69,7 +69,7 @@ class IdentityDetector(Detector):
|
|
|
69
69
|
commit_sha=commit.sha,
|
|
70
70
|
remediation=(
|
|
71
71
|
"Recreate the commit under the responsible human contributor's identity "
|
|
72
|
-
"before pushing. CommitGuard never
|
|
72
|
+
"before pushing. CommitGuard never changes who authored a commit."
|
|
73
73
|
),
|
|
74
74
|
)
|
|
75
75
|
)
|
|
@@ -386,6 +386,85 @@ class Repository:
|
|
|
386
386
|
raise UnsafeInputError(f"{path} at {commit[:12]} is larger than {max_bytes} bytes")
|
|
387
387
|
return self._git(["cat-file", "blob", oid]).stdout
|
|
388
388
|
|
|
389
|
+
# ------------------------------------------------------------------ #
|
|
390
|
+
# Rewriting unpushed commits (remediation.fix_on_push)
|
|
391
|
+
# ------------------------------------------------------------------ #
|
|
392
|
+
#: State files that mean a rebase, merge, cherry-pick, revert or bisect is
|
|
393
|
+
#: in progress. Moving a branch underneath one of those corrupts it.
|
|
394
|
+
_IN_PROGRESS = (
|
|
395
|
+
"rebase-merge",
|
|
396
|
+
"rebase-apply",
|
|
397
|
+
"MERGE_HEAD",
|
|
398
|
+
"CHERRY_PICK_HEAD",
|
|
399
|
+
"REVERT_HEAD",
|
|
400
|
+
"BISECT_LOG",
|
|
401
|
+
)
|
|
402
|
+
|
|
403
|
+
def operation_in_progress(self) -> str | None:
|
|
404
|
+
"""Name of an unfinished rebase/merge/cherry-pick/revert/bisect, if any."""
|
|
405
|
+
for name in self._IN_PROGRESS:
|
|
406
|
+
result = self._git(["rev-parse", "--git-path", name])
|
|
407
|
+
path = Path(result.stdout.decode("utf-8", errors="replace").strip())
|
|
408
|
+
if not path.is_absolute():
|
|
409
|
+
path = self.root / path
|
|
410
|
+
if path.exists():
|
|
411
|
+
return name
|
|
412
|
+
return None
|
|
413
|
+
|
|
414
|
+
def read_raw_commit(self, sha: str) -> bytes:
|
|
415
|
+
"""The commit object exactly as stored (headers, blank line, message)."""
|
|
416
|
+
if not is_git_sha(sha):
|
|
417
|
+
raise UnsafeInputError("read_raw_commit requires a full commit id")
|
|
418
|
+
return self._git(["cat-file", "commit", sha]).stdout
|
|
419
|
+
|
|
420
|
+
def write_commit_object(self, data: bytes) -> str:
|
|
421
|
+
"""Store a commit object and return its id. Nothing references it yet."""
|
|
422
|
+
result = self._git(["hash-object", "-t", "commit", "-w", "--stdin"], input_bytes=data)
|
|
423
|
+
sha = result.stdout.decode("ascii", errors="replace").strip()
|
|
424
|
+
if not is_git_sha(sha):
|
|
425
|
+
raise MalformedGitOutputError("git hash-object returned no object id")
|
|
426
|
+
return sha
|
|
427
|
+
|
|
428
|
+
def current_branch(self) -> str | None:
|
|
429
|
+
"""``refs/heads/<name>`` that ``HEAD`` points at, or None when detached."""
|
|
430
|
+
result = self._git(["symbolic-ref", "--quiet", "HEAD"], check=False)
|
|
431
|
+
ref = result.stdout.decode("utf-8", errors="replace").strip()
|
|
432
|
+
return ref if result.ok and ref.startswith("refs/heads/") else None
|
|
433
|
+
|
|
434
|
+
def not_on_any_remote(self, shas: Sequence[str]) -> set[str]:
|
|
435
|
+
"""The subset of ``shas`` not reachable from any remote-tracking branch.
|
|
436
|
+
|
|
437
|
+
A commit that some remote already has - even a different remote than the
|
|
438
|
+
one being pushed to - has been shared, and must never be rewritten.
|
|
439
|
+
"""
|
|
440
|
+
for sha in shas:
|
|
441
|
+
if not is_git_sha(sha):
|
|
442
|
+
raise UnsafeInputError("not_on_any_remote requires full commit ids")
|
|
443
|
+
if not shas:
|
|
444
|
+
return set()
|
|
445
|
+
result = self._git(
|
|
446
|
+
["rev-list", "--not", "--remotes", "--not", "--end-of-options", *shas, "--"]
|
|
447
|
+
)
|
|
448
|
+
reachable = set(result.stdout.decode("ascii", errors="replace").split())
|
|
449
|
+
return {sha for sha in shas if sha in reachable}
|
|
450
|
+
|
|
451
|
+
def move_branches(self, moves: Sequence[tuple[str, str, str]], reason: str) -> None:
|
|
452
|
+
"""Atomically point each ``refs/heads/...`` at a new commit.
|
|
453
|
+
|
|
454
|
+
Each move is ``(ref, new, old)`` and applies only if the ref still points
|
|
455
|
+
at ``old``; either every ref moves or none does. Old commits stay in the
|
|
456
|
+
reflog.
|
|
457
|
+
"""
|
|
458
|
+
lines = []
|
|
459
|
+
for ref, new, old in moves:
|
|
460
|
+
if not ref.startswith("refs/heads/") or any(ord(c) <= 0x20 for c in ref):
|
|
461
|
+
raise UnsafeInputError("move_branches only updates local branches")
|
|
462
|
+
if not (is_git_sha(new) and is_git_sha(old)):
|
|
463
|
+
raise UnsafeInputError("move_branches requires full commit ids")
|
|
464
|
+
lines.append(f"update {ref} {new} {old}\n")
|
|
465
|
+
payload = "start\n" + "".join(lines) + "prepare\ncommit\n"
|
|
466
|
+
self._git(["update-ref", "-m", reason, "--stdin"], input_bytes=payload.encode("ascii"))
|
|
467
|
+
|
|
389
468
|
def config_get(self, key: str) -> str | None:
|
|
390
469
|
"""Return a Git configuration value, or None if it is unset."""
|
|
391
470
|
validate_git_config_key(key)
|