Registro público
Informe de salud del softwareesquema 0.27.0 · métricas 1.13.0 · 2026-07-27 18:15 UTC

philpaz / recusal

Deterministic governance for Claude and MCP tool calls. Pin approved capabilities, detect drift, and refuse unsafe or unapproved actions before execution. No model in the decision path.

PythonApache-2.0★ 3 estrellas⑂ 0 forksdesde jul 2026Ver en GitHub ↗

philpaz/recusal tiene un índice de salud de 65 sobre 100, lo que lo sitúa en la banda Moderado. Su puntuación más alta es Engineering Quality (96/100) y la más baja, Community & Adoption (49/100). Se actualizó por última vez hoy. Una sola persona concentra la mayor parte del trabajo reciente.

65
global / 100
Moderado

Índice de salud del software

Las métricas se agrupan en categorías ponderadas sobre una escala de 1 a 100. El resultado global parte de su media; cuando la evidencia pública activa la Política de Jurisdicciones de Alto Riesgo, la calificación se ajusta y recibe el límite 49 (En riesgo). Preparación para IA queda fuera.

65
Excelente85-100Ejemplar; cumple prácticamente todos los criterios evaluados
Bueno70-84Saludable; carencias menores
Moderado50-69Aceptable con carencias notables; se recomienda revisión
En riesgo30-49Debilidades significativas; su adopción exige cautela
Crítico1-29Problemas graves (proyecto abandonado, un solo mantenedor, sin higiene)
VitalidadComunidad yAdopciónSostenibilidady GobernanzaCalidad deIngenieríaSeguridadPreparaciónpara IA

Perfil de puntuación

Cada eje es una categoría. La forma importa más que la media: un proyecto sano llena toda la figura, mientras que un perfil de picos y cráteres indica que la fortaleza en una dimensión enmascara el riesgo en otra.

Titularidad

Philip PazCuenta personal
0 seguidores8 repositorios públicosdesde ago 2012

Este repositorio pertenece a una cuenta personal. Un proyecto con un único propietario conlleva más riesgo de continuidad que uno respaldado por una organización.

Ecosistemas de paquetes

Métricas por categoría

Vitalidad

¿Está vivo el proyecto: se escribe código y se publican versiones?

73Bueno · 22% del índice global
Cómo se puntúa
36/36Recencia de push — último push hace 0 días
2.8/36Cadencia de commits — 4/52 semanas con commits
18/18Volumen de commits — 105 commits en el último año
0/10OpenSSF Scorecard: Maintained — project was created within the last 90 days. Please review its contents carefully
Datos de entrada utilizados
commits_last_year105
human_commit_share1
days_since_last_push0
active_weeks_last_year4
Cómo se puntúa
27/27Publica versiones — 23 versiones publicadas
36/36Recencia de las versiones — última versión hace 0 días
27/27Cadencia de publicación — una versión cada ~1,7 días
8/10OpenSSF Scorecard: Signed-Releases — 2 out of the last 2 releases have a total of 2 signed artifacts.
Datos de entrada utilizados
releases_count23
latest_release_tagv0.7.1
releases_from_tagsno
days_since_latest_release0
mean_days_between_releases1,7

Comunidad y Adopción

¿Tiene el proyecto usuarios, descargas, atención y unas condiciones acogedoras para quienes contribuyen?

49En riesgo · 18% del índice global
Cómo se puntúa
4.9/60Estrellas — 3 estrellas
0/25Forks — 0 forks
0/15Observadores — 0 observadores
Datos de entrada utilizados
forks0
stars3
watchers0
growth_stateunverified
growth_factor_pct100
growth_unverified_reasonno_history
Cómo se puntúa
22.5/22.5README
22.5/22.5Licencia — licencia reconocida (Apache-2.0)
18/18Guía CONTRIBUTING
13.5/13.5Código de conducta
0/7.2Plantilla de issues
6.3/6.3Plantilla de PR
Datos de entrada utilizados
has_readme
has_license
has_contributing
has_issue_templateno
has_code_of_conduct
has_pull_request_template
Cómo se puntúa
46.1/80Descargas mensuales — 2854 descargas/mes en pypi
0/20Dependientes en el registro — no lo informa este ecosistema
Datos de entrada utilizados
packagesrecusal
dependents
ecosystemspypi
total_downloads
monthly_downloads2854
Excluidos de la puntuación (sin datos o no aplicable): Dependientes en el registro. Los pesos restantes se han renormalizado.

Sostenibilidad y Gobernanza

¿Sobrevivirá el proyecto a sus personas: factor bus, capacidad de respuesta, quién lo respalda y mantenimiento del paquete?

50Moderado · 24% del índice global
Cómo se puntúa
9/54Factor bus — la mitad de los commits recae en 1 contribuyente(s)
0/22.5Distribución de commits — el principal contribuyente firma el 100% de los commits
1.4/13.5Amplitud de contribuyentes — 1 contribuyentes
0/10OpenSSF Scorecard: Contributors — project has 0 contributing companies or organizations -- score normalized to 0
Datos de entrada utilizados
bus_factor1
contributors_sampled1
top_contributor_share1
Cómo se puntúa
0/46.8Resolución de issues — sin issues o sin datos
38.2/38.3Aceptación de PR — 3/3 PR decididos fusionados
0/15OpenSSF Scorecard: Code-Review — Found 0/29 approved changesets -- score normalized to 0
Datos de entrada utilizados
merged_prs3
open_issues0
closed_issues0
issue_closed_ratio
closed_unmerged_prs0
Excluidos de la puntuación (sin datos o no aplicable): Resolución de issues. Los pesos restantes se han renormalizado.
Cómo se puntúa
10/30Respaldo de la propiedad — cuenta personal (usuario)
0/20Dominio verificado — no aplicable a cuentas de usuario
0/25Alcance del propietario — 0 seguidores de philpaz
18.9/25Trayectoria — 8 repos públicos, cuenta de ~13 años
Datos de entrada utilizados
followers0
owner_typeUser
is_verified
owner_loginphilpaz
public_repos8
account_age_days5095
Excluidos de la puntuación (sin datos o no aplicable): Dominio verificado. Los pesos restantes se han renormalizado.
Cómo se puntúa
25/25Publicado y resoluble — 1 paquete(s) en pypi
35/35Recencia de publicación — última publicación hace 0 días
20/20Historial de versiones — 23 versiones en el registro
20/20No obsoleto — activo, ni obsoleto ni retirado
Datos de entrada utilizados
packagesrecusal
ecosystemspypi
any_deprecatedno
min_days_since_publish0

Calidad de Ingeniería

¿Existen unas prácticas mínimas de ingeniería y documentación?

96Excelente · 20% del índice global
Cómo se puntúa
24/24Flujos de trabajo de CI — 4 flujo(s) de trabajo
24/24Pruebas presentes
16/16Configuración de linter
9.6/9.6Hooks de pre-commit
0/6.4.editorconfig
20/20OpenSSF Scorecard: CI-Tests — 1 out of 1 merged PRs checked by a CI test -- score normalized to 10
Datos de entrada utilizados
has_ci
has_tests
has_editorconfigno
has_linter_config
has_precommit_config

Documentación

100Excelente
Cómo se puntúa
30/30README
25/25Directorio de documentación
15/15Sitio de documentación / página del proyecto — https://pypi.org/project/recusal/
10/10Descripción del repositorio
10/10Topics — 17 topics
10/10Wiki
Datos de entrada utilizados
topicsagent-governance, ai-agents, ai-safety, claude, guardrails, llm, agent-security, agentic-ai, ai-governance, claude-code, hooks, llm-security, python, audit-log, ci, devsecops, github-actions
has_wiki
homepagehttps://pypi.org/project/recusal/
has_readme
has_docs_dir
has_description

Seguridad

¿Son sólidas las prácticas visibles de seguridad y de cadena de suministro, sin exposición jurisdiccional de alto riesgo sin resolver?

54Moderado · 16% del índice global
Cómo se puntúa
7.5/7.5Binary-Artifacts — no binaries found in the repo
0/7.5Branch-Protection — branch protection not enabled on development/release branches
2.5/2.5CI-Tests — 1 out of 1 merged PRs checked by a CI test -- score normalized to 10
0/2.5CII-Best-Practices — no effort to earn an OpenSSF best practices badge detected
0/7.5Code-Review — Found 0/29 approved changesets -- score normalized to 0
0/2.5Contributors — project has 0 contributing companies or organizations -- score normalized to 0
10/10Dangerous-Workflow — no dangerous workflow patterns detected
0/7.5Dependency-Update-Tool — no update tool detected
0/5Fuzzing — project is not fuzzed
2.5/2.5Licencia — license file detected
0/7.5Maintained — project was created within the last 90 days. Please review its contents carefully
5/5Packaging — packaging workflow detected
3/5Pinned-Dependencies — dependency not pinned by hash detected -- score normalized to 6
0/5SAST — SAST tool is not run on all commits -- score normalized to 0
5/5Security-Policy — security policy file detected
6/7.5Signed-Releases — 2 out of the last 2 releases have a total of 2 signed artifacts.
7.5/7.5Token-Permissions — GitHub workflow tokens follow principle of least privilege
7.5/7.5Vulnerabilities — 0 existing vulnerabilities detected
Datos de entrada utilizados
sourceopenssf_scorecard
checks_evaluated18
scorecard_versionv5.5.0
checks_inconclusive0
scorecard_aggregate5,4

Preparación para IA

¿Hasta qué punto está el repositorio preparado para desarrollarse y mantenerse con agentes de codificación de IA? Es una insignia independiente y experimental — peso 0,0, de modo que se presenta por separado y no afecta a la puntuación de salud global.

50Moderado · 0% del índice global
Cómo se puntúa
0/45Instrucciones para agentes — sin CLAUDE.md / AGENTS.md / reglas de editor
0/15Documentación legible por máquinas (llms.txt)
40/40Historial de commits legible — 95 de 100 commits humanos declaran su intención (asunto estructurado o cuerpo explicativo)
Datos de entrada utilizados
has_llms_txtno
legible_history_share0,95
agent_instruction_files
agent_instruction_max_bytes
Cómo se puntúa
0/18Arranque con un solo comando
22/22Pruebas automatizadas
11/11Configuración de lint / formato
11/11Verificación estática de tipos — claude-plugin/vendor/recusal/py.typed, recusal/py.typed
0/10Entorno reproducible
0/10Práctica demostrada con agentes — ningún commit con autoría de agente entre los últimos 100
0/8Mantenimiento automatizado — no se observan actualizaciones automáticas de dependencias
6/10OpenSSF Scorecard: Pinned-Dependencies — dependency not pinned by hash detected -- score normalized to 6
Datos de entrada utilizados
has_nixno
has_tests
lockfiles
has_dockerfileno
typed_languageno
bootstrap_files
has_devcontainerno
has_linter_config
typecheck_configsclaude-plugin/vendor/recusal/py.typed, recusal/py.typed
agent_commit_share0
toolchain_manifests
dependency_bot_commit_share0
Cómo se puntúa
27/45Código verificable por tipos — Python con configuración de verificación de tipos (claude-plugin/vendor/recusal/py.typed, recusal/py.typed)
53.8/55Tamaños de archivo manejables — 2/89 archivos fuente de más de 60 KB
Datos de entrada utilizados
primary_languagePython
largest_source_bytes94.203
source_files_sampled89
oversized_source_files2
Cómo se puntúa
0/40Esquema de API (OpenAPI/GraphQL/proto)
0/20Servidor MCP
40/40Ejemplos ejecutables — examples
Datos de entrada utilizados
example_dirsexamples
has_mcp_signalno
api_schema_files

Datos clave

3estrellas de GitHub
1contribuidores
105commits en los últimos 12 meses
0días desde el último push
23versiones publicadas
1factor bus
0issues abiertas
PyPIecosistemas de paquetes

Advertencias de recopilación de datos

  • Star history unavailable: GitHub GraphQL error: Resource not accessible by personal access token
  • GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository
  • deps.dev does not index pypi:recusal@0.7.1; advisories assessed against the repository dependency graph instead

Más detalle

OpenSSF Scorecard 5.4 / 10
5.4agregado

Evaluación de seguridad independiente y agnóstica en cuanto a herramientas, procedente del proyecto de código abierto OpenSSF Scorecard. Cada comprobación premia una práctica de seguridad, no la herramienta de un proveedor concreto. Las comprobaciones que Scorecard no pudo determinar se marcan como n/d y se excluyen de la puntuación de seguridad (nunca se cuentan como cero).Scorecard v5.5.0 · 2026-07-27 18:15 UTC

10Binary-Artifactsno binaries found in the repo
0Branch-Protectionbranch protection not enabled on development/release branches
10CI-Tests1 out of 1 merged PRs checked by a CI test -- score normalized to 10
0CII-Best-Practicesno effort to earn an OpenSSF best practices badge detected
0Code-ReviewFound 0/29 approved changesets -- score normalized to 0
0Contributorsproject has 0 contributing companies or organizations -- score normalized to 0
10Dangerous-Workflowno dangerous workflow patterns detected
0Dependency-Update-Toolno update tool detected
0Fuzzingproject is not fuzzed
10Licenselicense file detected
0Maintainedproject was created within the last 90 days. Please review its contents carefully
10Packagingpackaging workflow detected
6Pinned-Dependenciesdependency not pinned by hash detected -- score normalized to 6
0SASTSAST tool is not run on all commits -- score normalized to 0
10Security-Policysecurity policy file detected
8Signed-Releases2 out of the last 2 releases have a total of 2 signed artifacts.
10Token-PermissionsGitHub workflow tokens follow principle of least privilege
10Vulnerabilities0 existing vulnerabilities detected
Todas las dependencias no recopilado

No fue posible recopilar el conjunto de dependencias resuelto para este informe: GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository

Informe JSON sin procesar legible por máquina
{
  "data": {
    "repo": {
      "topics": [
        "agent-governance",
        "ai-agents",
        "ai-safety",
        "claude",
        "guardrails",
        "llm",
        "agent-security",
        "agentic-ai",
        "ai-governance",
        "claude-code",
        "hooks",
        "llm-security",
        "python",
        "audit-log",
        "ci",
        "devsecops",
        "github-actions"
      ],
      "is_fork": false,
      "size_kb": 3620,
      "has_wiki": true,
      "homepage": "https://pypi.org/project/recusal/",
      "languages": {
        "Python": 784221
      },
      "pushed_at": "2026-07-27T18:11:53Z",
      "created_at": "2026-07-06T00:45:29Z",
      "owner_type": "User",
      "updated_at": "2026-07-27T18:11:44Z",
      "description": "Deterministic governance for Claude and MCP tool calls. Pin approved capabilities, detect drift, and refuse unsafe or unapproved actions before execution. No model in the decision path.",
      "is_archived": false,
      "is_disabled": false,
      "license_spdx": "Apache-2.0",
      "default_branch": "main",
      "license_spdx_raw": "Apache-2.0",
      "primary_language": "Python",
      "significant_languages": [
        "Python"
      ]
    },
    "owner": {
      "blog": null,
      "name": "Philip Paz",
      "type": "User",
      "login": "philpaz",
      "company": null,
      "location": null,
      "followers": 0,
      "avatar_url": "https://avatars.githubusercontent.com/u/2147449?v=4",
      "created_at": "2012-08-13T23:26:59Z",
      "is_verified": null,
      "public_repos": 8,
      "account_age_days": 5095
    },
    "license": {
      "state": "standard",
      "spdx_id": "Apache-2.0",
      "raw_spdx": "Apache-2.0",
      "file_present": true,
      "scorecard_found": true,
      "profile_has_license": true
    },
    "activity": {
      "releases": [
        {
          "tag": "v0.7.1",
          "kind": "patch",
          "published_at": "2026-07-27T18:12:37Z"
        },
        {
          "tag": "v0.7.0",
          "kind": "minor",
          "published_at": "2026-07-23T23:16:07Z"
        },
        {
          "tag": "v0.6.0",
          "kind": "minor",
          "published_at": "2026-07-23T18:47:24Z"
        },
        {
          "tag": "v0.5.12",
          "kind": "patch",
          "published_at": "2026-07-13T17:07:50Z"
        },
        {
          "tag": "v0.5.11",
          "kind": "patch",
          "published_at": "2026-07-13T04:57:02Z"
        },
        {
          "tag": "v0.5.10",
          "kind": "patch",
          "published_at": "2026-07-13T00:34:40Z"
        },
        {
          "tag": "v0.5.9",
          "kind": "patch",
          "published_at": "2026-07-12T23:45:15Z"
        },
        {
          "tag": "v0.5.8",
          "kind": "patch",
          "published_at": "2026-07-12T22:35:10Z"
        },
        {
          "tag": "v0.5.7",
          "kind": "patch",
          "published_at": "2026-07-12T22:05:13Z"
        },
        {
          "tag": "v0.5.6",
          "kind": "patch",
          "published_at": "2026-07-12T21:37:28Z"
        },
        {
          "tag": "v0.5.5",
          "kind": "patch",
          "published_at": "2026-07-12T21:00:40Z"
        },
        {
          "tag": "v0.5.4",
          "kind": "patch",
          "published_at": "2026-07-12T20:13:39Z"
        },
        {
          "tag": "v0.5.3",
          "kind": "patch",
          "published_at": "2026-07-12T19:24:15Z"
        },
        {
          "tag": "v0.5.2",
          "kind": "patch",
          "published_at": "2026-07-12T18:29:02Z"
        },
        {
          "tag": "v0.5.1",
          "kind": "patch",
          "published_at": "2026-07-12T17:39:14Z"
        },
        {
          "tag": "v0.5.0",
          "kind": "minor",
          "published_at": "2026-07-12T16:47:42Z"
        },
        {
          "tag": "v0.4.0",
          "kind": "minor",
          "published_at": "2026-07-10T19:57:28Z"
        },
        {
          "tag": "v0.3.0",
          "kind": "minor",
          "published_at": "2026-07-09T04:05:11Z"
        },
        {
          "tag": "v0.2.0",
          "kind": "minor",
          "published_at": "2026-07-09T03:47:42Z"
        },
        {
          "tag": "v0.1.3",
          "kind": "patch",
          "published_at": "2026-07-06T04:47:57Z"
        },
        {
          "tag": "v0.1.2",
          "kind": "patch",
          "published_at": "2026-07-06T04:22:41Z"
        },
        {
          "tag": "v0.1.1",
          "kind": "patch",
          "published_at": "2026-07-06T01:06:36Z"
        },
        {
          "tag": "v0.1.0",
          "kind": "minor",
          "published_at": "2026-07-06T00:47:38Z"
        }
      ],
      "recent_commits": [
        {
          "oid": "e7e30284fea836f9444bb3e2f82ee10aea2d638a",
          "body": "Prepare v0.7.1 release integrity patch",
          "is_bot": false,
          "headline": "Merge pull request #4 from philpaz/agent/prepare-v0.7.1",
          "author_name": "Philip Paz",
          "author_login": "philpaz",
          "committed_at": "2026-07-27T18:10:19Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "d0715a0ee7568130b35cb63784a7262fb34b3f1a",
          "body": null,
          "is_bot": false,
          "headline": "Prepare v0.7.1 release integrity patch",
          "author_name": "Philip Paz",
          "author_login": "philpaz",
          "committed_at": "2026-07-27T18:07:26Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "ecd3e7cc1cde426191fcaaa3ceadd7dafce6ed7e",
          "body": null,
          "is_bot": false,
          "headline": "0.7.0: observation scope, audited pipeline, reusable signed build",
          "author_name": "Philip Paz",
          "author_login": "philpaz",
          "committed_at": "2026-07-23T23:14:44Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "7eb2c57f7c3a86c153fc0a4eaa7bace0e27aa4b2",
          "body": null,
          "is_bot": false,
          "headline": "chore: pin ruff exactly; format the documentation code fences with it",
          "author_name": "Philip Paz",
          "author_login": "philpaz",
          "committed_at": "2026-07-23T23:05:15Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "245b9431be358b4f49713bfca200bad53b5891e1",
          "body": null,
          "is_bot": false,
          "headline": "ci: Sigstore-sign the distributions; publish fails closed behind signing",
          "author_name": "Philip Paz",
          "author_login": "philpaz",
          "committed_at": "2026-07-23T23:03:22Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "17930ecfe1c6bedbba92dc5eff006695eae61420",
          "body": null,
          "is_bot": false,
          "headline": "ci: release build in a reusable workflow, the documented SLSA L3 pattern",
          "author_name": "Philip Paz",
          "author_login": "philpaz",
          "committed_at": "2026-07-23T23:01:45Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "3b5e522801e14076de19fdf22c8f1f85f951944c",
          "body": null,
          "is_bot": false,
          "headline": "ci: continuous OpenSSF Scorecard, badge reads the public API",
          "author_name": "Philip Paz",
          "author_login": "philpaz",
          "committed_at": "2026-07-23T22:59:05Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c4188c55b4844530d6363c3f2e0227e5f1f818a9",
          "body": null,
          "is_bot": false,
          "headline": "ci: zizmor workflow audit as a blocking check; no credential persistence",
          "author_name": "Philip Paz",
          "author_login": "philpaz",
          "committed_at": "2026-07-23T22:57:39Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "61d4b6daf721ac0b37bcf1d5cdda9b2aeedd7a1d",
          "body": null,
          "is_bot": false,
          "headline": "feat: observation_scope operator metadata, manifest v8",
          "author_name": "Philip Paz",
          "author_login": "philpaz",
          "committed_at": "2026-07-23T22:54:53Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "a592caa44304875fd573f304ae196bac9ab997c2",
          "body": null,
          "is_bot": false,
          "headline": "ci: keep the SBOM as a workflow artifact, not a release asset",
          "author_name": "Philip Paz",
          "author_login": "philpaz",
          "committed_at": "2026-07-23T18:45:25Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "ecadd96ba8659527d48f9fc1d82ecc64f7a502c3",
          "body": "…runtime",
          "is_bot": false,
          "headline": "0.6.0: audit anchoring, resolved-executable pinning, vendored plugin …",
          "author_name": "Philip Paz",
          "author_login": "philpaz",
          "committed_at": "2026-07-23T18:17:04Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "7994083fe3242099521fadcb32a7045b98a4707e",
          "body": "The recusal-gate plugin previously required a separately pip-installed recusal\nand bound to it by declared version only, so the implementation that actually\nadjudicated was whatever package the interpreter found. R5 named the stronger\nfix and it ships here: the plugin vendors the exact recusal runti\n[…]\nion drops the pip pairing; the onboarding-pin lock is updated\nto match (an exact install pin is no longer required, but any that appears and\nevery Action example must still match the package version).",
          "is_bot": false,
          "headline": "feat: vendor the plugin runtime, plugin = implementation",
          "author_name": "Philip Paz",
          "author_login": "philpaz",
          "committed_at": "2026-07-23T04:43:29Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "f90bf221d64bae3ccca0dfec06a7223eeca7ad13",
          "body": "The launch-identity residual named since 0.5.0: the pin stored the unexpanded\ncommand template, so swapping the FILE that template resolves to - a replaced\nbinary on PATH, edited wrapper-script bytes - passed verify with the template\nbyte-identical. Strict mode closes the first-process-image half of\n[…]\nocess image only (interpreter script arguments\nand launcher-fetched packages stay behind the template, stated in MCP.md and\nSECURITY.md).\n\n29 new tests; golden manifest bytes deliberately moved to v7.",
          "is_bot": false,
          "headline": "feat: resolved-executable strict mode, manifest v7",
          "author_name": "Philip Paz",
          "author_login": "philpaz",
          "committed_at": "2026-07-23T04:35:56Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "654216f30bd7e2fdb754a60716cefdf5cd6d95a0",
          "body": "The chain is tamper-evident, not tamper-proof: a write-access attacker can\nrewrite or truncate the tail. The countermeasure is now first-class instead of\na docstring instruction. AuditLog(sinks=[...]) mirrors every committed entry,\nin chain order and inside the append lock, to any object with a writ\n[…]\n 19 new tests including the hook fail-closed\ncomposition, the HeadSink anchor recipe end to end, tampered-log refusal at\nopen, and a derandomized Hypothesis property (mirror equals log, anchor holds).",
          "is_bot": false,
          "headline": "feat: AuditSink protocol + verify-on-open anchoring for the audit log",
          "author_name": "Philip Paz",
          "author_login": "philpaz",
          "committed_at": "2026-07-23T04:15:53Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "b9b11fae5cb3830b10198d6bd604c6972d799f56",
          "body": null,
          "is_bot": false,
          "headline": "feat: add executable MCP launch-drift proof",
          "author_name": "Philip Paz",
          "author_login": "philpaz",
          "committed_at": "2026-07-21T18:55:51Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "623ee1584a5ea0f25767be9cd3c5ea84f25b4896",
          "body": "…idence\n\ncompute_verdict([]) is PASS, one behavior serving two intents. The kernel now\nnames them: evaluate_policy reads findings as objections (empty = no objection,\nPASS; identical to the raw fold) and certify_evidence reads findings as proof\n(empty = refuse through a synthesized failed-CRITICAL n\n[…]\nr safety, the\ncomplete empty-refusal verdict shape, gates regression). docs/EVIDENCE.md and\nthe changelog document the contract.\n\nResolves the 0.6.0 evidence-semantics decision (R3 P1-2, R4 P2-5, R5).",
          "is_bot": false,
          "headline": "feat: name the empty-evidence semantics, evaluate_policy / certify_ev…",
          "author_name": "Philip Paz",
          "author_login": "philpaz",
          "committed_at": "2026-07-21T03:24:02Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "f9cec4836fa62adeef4a98b793a10d8214240384",
          "body": "Seven derandomized generative tests over the evidence kernel and fingerprint\ncanon: adding a finding never weakens the decision, verdicts are independent of\nfinding order, the fold matches its documented decision rule for all inputs,\nstring passed/status values never fail open (the affirmative-token\n[…]\n outcome-less\nevidence, and tool fingerprints are stable across calls and key insertion\norder. hypothesis lands in the dev extra only; runtime dependencies stay zero\nand the release lock is untouched.",
          "is_bot": false,
          "headline": "tests: property-lock the frozen kernel invariants with Hypothesis",
          "author_name": "Philip Paz",
          "author_login": "philpaz",
          "committed_at": "2026-07-21T03:15:31Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "69aebd48f906d7d7beb99d055c42ecfb842e0cdd",
          "body": "The build job now catalogs the built distributions as an SPDX SBOM\n(anchore/sbom-action, uploaded with the workflow artifacts) and writes GitHub\nbuild-provenance attestations for dist/* (actions/attest-build-provenance),\nwith job-scoped id-token/attestations permissions. Stated narrowly: neither\nmakes the build byte-reproducible. Drift locks pin both steps, the required\npermission, and full-SHA pinning of every action in the workflow.",
          "is_bot": false,
          "headline": "ci: SBOM + build-provenance attestations for the release artifacts",
          "author_name": "Philip Paz",
          "author_login": "philpaz",
          "committed_at": "2026-07-21T02:55:36Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "4377b616462867e414189369d51b9130e4df0dcd",
          "body": "…h step\n\nRemove skip-existing from the PyPI publish step: a release re-run whose version\nis already on PyPI now fails loudly instead of silently no-opping, so a\ndivergent rebuild can never hide behind an idempotent-looking rerun (review 9\npublish hardening). Two drift-lock tests pin the publish action identity and\nthe absence of any effective skip-existing key.",
          "is_bot": false,
          "headline": "ci: hard-fail republishing an existing version; drift-lock the publis…",
          "author_name": "Philip Paz",
          "author_login": "philpaz",
          "committed_at": "2026-07-21T02:52:52Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "0882d40f119265c8526383bb3505ffe9c0e6792e",
          "body": "…idence-kernel import lock\n\n- _mcp_screening._declared_text regains the docstring that explains why the\n  walk is iterative and depth-capped (a hostile server must not crash the\n  screen out of a verdict; a crash is not a refusal); the rationale was\n  dropped in the extraction commit.\n- The evidence\n[…]\nom forms), not only relative ones, so it enforces the\n  boundary ARCHITECTURE.md promises.\n- recusal/mcp.py drops a docstring cross-reference to _declared_text that\n  dangled after the function moved.",
          "is_bot": false,
          "headline": "docs+tests: restore the screening-walk threat rationale; widen the ev…",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-21T02:18:28Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "c1bd0f48b454f164e21a923d799fd6ba98a9b1ce",
          "body": null,
          "is_bot": false,
          "headline": "refactor: extract MCP screening behind compatibility facade",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-15T15:21:42Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "028afbf047d8a703bb93bd0853624904ccf2c56f",
          "body": "The README install line and the Action usage examples stayed pinned to\n0.5.11 through the 0.5.12 release. The plugin fails CLOSED on a package-\nversion mismatch by design, so a user following the README exactly would\ninstall the 0.5.12 plugin against the 0.5.11 package and have every tool\ncall refus\n[…]\nd, and a new\ntest locks the exact pin spellings in README.md and action.yml to\nrecusal.__version__ so a release can never ship a stale onboarding pin\nagain (historical narrative mentions stay exempt).",
          "is_bot": false,
          "headline": "docs: sync version-bound onboarding pins to 0.5.12 and drift-lock them",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-13T17:34:52Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "e5097310ab71eab2d60c4419a29d0bb34e210c88",
          "body": null,
          "is_bot": false,
          "headline": "docs: publish the v0.5.12 release proofs",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-13T17:11:28Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "f02d37da3d8e1991ddcccc69d3c23683007a347a",
          "body": "Hardening pass on the 0.5.11 package_self_protection check, per the 0.5.11\nreview (P1 + three P2s), no API change:\n\n- A bounded token walk runs in union with the regex and covers the valid\n  static forms the flat regex missed: global options with separate values\n  (pip --python .venv uninstall, uv -\n[…]\n system of records, not \"the artifact your auditor reads\".\n- docs/PROVEN.md: v0.5.11 release proofs published (CI run 29225000959,\n  release run 29225128002, refusals proven from the published wheel).",
          "is_bot": false,
          "headline": "0.5.12: widen the package-manager matcher; canonical protected names",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-13T16:47:31Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "5d1acc1e8e5e7c7a00a9c69cb5f4c7bd844299a2",
          "body": "… README\n\nSecurity (deny-list): `pip uninstall recusal` deferred through the gate; the\nself-protect verb pattern matched \\binstall\\b (no word boundary inside\n\"uninstall\") and a bare package name carries no recusal/ path segment. New\npackage_self_protection check refuses uninstall, reinstall/downgrad\n[…]\ne thesis as the why); the full MCP governance\nstatement moves verbatim to docs/MCP.md with a summary and the three-boundary\ntable kept in the README; docs index updated; version refs bumped to 0.5.11.",
          "is_bot": false,
          "headline": "0.5.11: refuse package-manager mutation of the gate; capability-first…",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-13T04:53:35Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "3ae604c8963bd4848407f31bb1d2ffd1f4aeb212",
          "body": "PROVEN.md carries the v0.5.10 evidence: the public CI and release run IDs (both\nverified green before citing) and the hand-edited collision refusal proven from the\npublished wheel, with the three deterministic tests that pin the same property.",
          "is_bot": false,
          "headline": "docs: publish the v0.5.10 release proofs",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-13T00:37:19Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "65b1b27bcfaad15052064fd314c1c8f4c8460f68",
          "body": "Aligns the published package with current main's stronger invariant (review 13, P1,\nalready on main at a266429): the normal `recusal mcp pin` path in 0.5.9 already\nrejected callable collisions, so this closes the remaining gap for hand-edited and\nexternally generated manifests - load_manifest refuse\n[…]\no other behavior changes; version surfaces bumped.\n\n947 tests green with every gate exit code checked; wheel smoke from a neutral dir\nproves the hand-edited-collision refusal from the installed wheel.",
          "is_bot": false,
          "headline": "0.5.10: publish the loader-wide collision invariant",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-13T00:31:25Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "a266429d9fea97cf4c15dc3af61899d57aff1a2a",
          "body": "The loader-invariant test bound the helper's return value it never used; ruff F841\nfailed the Lint step on every matrix job. Gate discipline note recorded: the local\ngate had shown this as a \"hidden fix\" line and it was misread as informational.",
          "is_bot": false,
          "headline": "tests: remove unused variable that failed lint",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-13T00:05:55Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "b082e8b6d6b9fc0ebb776c8382d6e4b28fad8016",
          "body": "The builder refused two raw plugin tools normalizing to one callable, but\n_validate_manifest checked each pin independently - so an externally generated or\nhand-edited manifest whose pins were EACH individually canonical (correctly derived\ncallable_name, valid digest shapes) could carry a collision,\n[…]\nrding in verifier\ndocstrings and the CLI comment replaced with schema-stable phrasing, and the README\ntest references include tests/test_mcp_runtime_identity.py.\n\n947 tests green, ruff and mypy clean.",
          "is_bot": false,
          "headline": "mcp: callable-collision refusal is a loader invariant (review 13, P1)",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-13T00:02:59Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "7a618a62f3f09f8652d434f9040ace0d8b2c90b3",
          "body": "PROVEN.md carries the v0.5.9 evidence: the public CI and release run IDs (both\nverified green before citing) and the verbatim manifest-v6 proofs - both identities\npinned, callable-identity authorization with the raw spelling refused, collision\nrefusal with no manifest written, and the v5 migration refusal - with the 25\ndeterministic tests that pin the same properties.",
          "is_bot": false,
          "headline": "docs: publish the v0.5.9 release proofs",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-12T23:48:16Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "bc9a7220f2298517a60c00b90c71e5b5d46db24b",
          "body": "…ntity, modeled separately\n\nThe single carry-forward item from the review sequence, pulled forward from 0.6.0 on\nPhilip's direction and closed, tightly scoped: the 0.5.8 boundaries are otherwise\nunchanged.\n\nManifest v6 (MANIFEST_VERSION = 6): every server entry carries a canonical\nruntime: {mode: st\n[…]\nshable at call time until the next verify, which refuses on raw\nidentity - exactly why both identities are pinned.\n\n944 tests green, ruff and mypy clean, wheel smoke verified 0.5.9 from a neutral dir.",
          "is_bot": false,
          "headline": "0.5.9: manifest v6 - raw declaration identity and Claude callable ide…",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-12T23:43:22Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "91aa6086f1eb3da191913c68499dca174b836daa",
          "body": "PROVEN.md carries the v0.5.8 evidence: the public CI and release run IDs (both\nverified green before citing), the in-place claim-correction record, and the\nalias-residual demonstration test reference.",
          "is_bot": false,
          "headline": "docs: publish the v0.5.8 release proofs",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-12T22:37:42Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "cb61e9ee565dfa70811903072ed1a0ad7251169e",
          "body": "A claim-correction patch from an eleventh external review, whose central finding was\na failure of OUR verification, not of the reviewed code: 0.5.7 published that Claude\ndoes not document plugin callable-name normalization, when the rule IS explicitly in\nthe Claude Code MCP reference (any character \n[…]\ned as a demonstration test (call time allows the approved\nalias, the next verify refuses the swapped declaration).\n\n919 tests green, ruff and mypy clean, wheel smoke verified 0.5.8 from a neutral dir.",
          "is_bot": false,
          "headline": "0.5.8: plugin callable-name claims corrected to the documented rule",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-12T22:33:38Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "43ebf4b6aaf91bba159c59effc5b5b4461ec9609",
          "body": "PROVEN.md carries the v0.5.7 evidence: the public CI and release run IDs (both\nverified green before citing), the verbatim full-decommission refusal, and the\nverbatim ValueError for an unhashable sequence member, with the deterministic tests\nthat pin the same properties.",
          "is_bot": false,
          "headline": "docs: publish the v0.5.7 release proofs (review 10, evidence pattern)",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-12T22:07:44Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "a7fd4310398fad009d3398af1bd4abb49631b4c4",
          "body": "…gin name boundary\n\nA small correctness patch from a tenth external review (no P0 found; the review\nrecommended folding these into 0.6.0 - shipped now as 0.5.7 per Philip).\n\nP1, full decommission refuses precisely: acknowledging removal of EVERY pinned server\ntripped the generic empty-observation re\n[…]\nent includes removal acknowledgements and\nwhole-server inventory; verify docstring OAuth wording is per-transport.\n\n918 tests green, ruff and mypy clean, wheel smoke verified 0.5.7 from a neutral dir.",
          "is_bot": false,
          "headline": "0.5.7: deliberate decommission semantics, ValueError consistency, plu…",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-12T22:03:01Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "474d5ee6d143cb7ec34c299010fa20b315faec90",
          "body": "…esiduals\n\nPROVEN.md carries the v0.5.6 evidence: the public CI and release run IDs (both\nverified green before citing), the verbatim whole-server-omission refusal and its\n--removed acknowledgement, and the verbatim reserved-name refusal with the\nnever-executed marker command.\n\nThe 100%-compliance s\n[…]\ned (the rug-pull example banner, the CLI section\ncomment, and the mcp subcommand help still said \"tool-catalog governance\" / \"pin the\ncatalog\"); all three now name instructions alongside declarations.",
          "is_bot": false,
          "headline": "docs: publish the v0.5.6 release proofs; close the last terminology r…",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-12T21:40:14Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "c5dd3d5ba8c09aa4abab3700810d4e36a0889b60",
          "body": "…licy order, reserved names\n\nA narrowly scoped correctness patch mandated by a ninth external review.\n\nP0-1, a wholly omitted pinned server refuses: diff_observation inherited the\nabsent-server WARNING, so a partial multi-server observation verified clean while the\nmanifest kept authorizing the omit\n[…]\nrding, transport-specific OAuth\ndocstring); whole-server inventory rule stated in README and the verify docstring.\n\n901 tests green, ruff and mypy clean, wheel smoke verified 0.5.6 from a neutral dir.",
          "is_bot": false,
          "headline": "0.5.6: whole-server inventory, strict containers, membership-first po…",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-12T21:35:47Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "cd7048ce4ea496ce008244b0ff894929a43ee100",
          "body": "PROVEN.md carries the v0.5.5 evidence: the public CI and release run IDs (both\nverified green before citing), the verbatim source-omission refusal, the verbatim\nmalformed-event provenance record from a reused process through the real hook, and\nthe verbatim WebSocket-OAuth CLI refusal, with the deterministic tests that pin the\nsame properties.",
          "is_bot": false,
          "headline": "docs: publish the v0.5.5 release proofs (review 8, item 25)",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-12T21:03:12Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "b1bcddfbcb99279864bc135d85631b3429765071",
          "body": "…t, canonical pins, ws header-only\n\nA narrowly scoped correctness patch mandated by an eighth external review.\n\nP0-1, McpObservation is a strict complete-observation contract: 0.5.4's\ndiff_observation compared only the components supplied, so omitting sources bypassed\nlaunch/remote identity entirely\n[…]\nt\nunqualified determinism shorthand qualified; pin CLI help names declarations,\ninstructions, and source warnings.\n\n869 tests green, ruff and mypy clean, wheel smoke verified 0.5.5 from a neutral dir.",
          "is_bot": false,
          "headline": "0.5.5: complete-observation contract, malformed-event provenance rese…",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-12T20:58:41Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "40dd451fcc464ba8643ae764189836f192513e24",
          "body": "…tem validation\n\nA 100%-compliance validation of 0.5.4 against review 7's 29-item required scope found\nfour residual gaps, all documentation:\n\n- Item 13 (publish evidence): PROVEN.md now carries the v0.5.4 release proofs - the\n  public CI and release run IDs (verified to exist and be green before ci\n[…]\nsame\n  normalized-inputs-same-version qualifier as everywhere else.\n\nAll other 26 items were verified compliant with evidence; the full matrix is in the\nsession record. 833 tests, ruff and mypy clean.",
          "is_bot": false,
          "headline": "docs: close the last four review-7 compliance gaps found by item-by-i…",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-12T20:25:43Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "7c456a3af109486ec7ca5a62c6d025e369f9e4c1",
          "body": "…e, hash-locked release toolchain\n\nThe final 0.5.x correctness patch, mandated by a seventh external review (the first\nwhose Claude-behavior claims all verified against the official docs without\ncorrection), before 0.6.0 begins.\n\nP0-1, one omission-resistant manifest-v5 verify: diff_manifest is cata\n[…]\nION, FAQ, WHY, and the package docstring; LANDSCAPE scoped as a dated\ndocumentation review with no absence claims.\n\n833 tests green, ruff and mypy clean, wheel smoke verified 0.5.4 from a neutral dir.",
          "is_bot": false,
          "headline": "0.5.4: omission-resistant v5 verify, concurrency-safe audit provenanc…",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-12T20:11:54Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "0499397ec00d5ac57db6a1b3b18e2b704edb9d99",
          "body": "… read the dynamic version source\n\nhatchling 1.31.0 requires Python >= 3.10, but the build-system requires clause is\nevaluated on every supported interpreter (pip install -e on 3.9 builds with it too),\nso the pin broke the 3.9 lane. Pin 1.27.0, the newest hatchling that still supports\n3.9, with the \n[…]\nectory:\nfrom the repo root, `import recusal` finds the checkout's source tree and the\ncomparison proved nothing about what the action installed. An empty parse also\nfails rather than comparing blanks.",
          "is_bot": false,
          "headline": "ci: hatchling pin must track the 3.9 package floor; provenance checks…",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-12T19:22:26Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "970c97d4968555b5fd550d534f5f6211f9a9b6fe",
          "body": "…g, claim-boundary corrections\n\nCorrectness and claim-boundary release mandated by a sixth external review, before\nany 0.6.0 work.\n\nP0-1, audit control identity is authoritative: caller-supplied recusal_version and\nmanifest_sha256 are stripped, never merged; one _control_identity helper serves every\n[…]\norked example; stale v3 header-name\ndescriptions corrected; categorical peer claims and percentage estimates removed.\n\n805 tests green, ruff and mypy clean, wheel smoke from clean venv verified 0.5.3.",
          "is_bot": false,
          "headline": "0.5.3: authoritative audit provenance, manifest v5 instruction pinnin…",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-12T19:18:14Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "25a87ad35dc2072898a701cc23db1232a1ccb19e",
          "body": "…16, repair-launcher in HOWTO\n\nRecipe 14 now shows the config-plus-dump pin for remote servers with header templates, oauth scopes, and the headersHelper guidance (pin the reference, never the value; recusal never executes the helper); new recipe 16 wires audit= with control identity end to end (tail resume, policy_id/policy_version, automatic recusal_version and manifest digest, anchored verify); HOWTO documents the --repair-launcher migration for pre-0.4.2 installs.",
          "is_bot": false,
          "headline": "Cookbook: v4 remote identity worked example, control-identity recipe …",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-12T18:32:35Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "c0873e9ed5d8fc85b31137d455ab5cae0eacf330",
          "body": "…d completely\n\nManifest v4 completes remote source identity: header value TEMPLATES\n(a same-name Authorization swap between read-only and admin credential\nreferences is drift; v3 pinned names only and passed it), the\nheadersHelper command template (Claude executes it at connect time; it\nwas previous\n[…]\n-sources-readable, same evidence\nsame policy same version same verdict); guardrail and agent-framework\ncomparisons corrected to the defensible form; the 0.5.1 changelog\noverstatement amended in place.",
          "is_bot": false,
          "headline": "0.5.2: architecture closure - the boundary stated exactly, implemente…",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-12T18:27:08Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "7a5812cf717b1a45df24938a02001b508da26cd9",
          "body": "Every configured server is verified, remote transports included:\n.mcp.json is parsed by transport type with Claude Code's rules, remote\nentries pin {transport, url_template, header_keys}, and an added\nunpinned remote server, a transport swap, a URL without a type, or a\nremote entry carrying stdio la\n[…]\nurces, and\nfalsey malformed config values.\n\nBoth P0 attacks live-proven refused through the real CLI: the added\nremote server and the env value swap each exit 2 with the drift named,\nnothing executed.",
          "is_bot": false,
          "headline": "0.5.1: the source artifact covers every configured server, completely",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-12T17:37:18Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "e16f8869340de7ffdd289bd9fb58d611fd6d26f2",
          "body": "The 0.5.0 feature is launch-template integrity, not execution identity: the CHANGELOG 0.5.0 section, README, and the published release notes now say so, with the residuals (env values, PATH/registry resolution, executable bytes, remote transport identity) named. Plugin metadata scopes its fail-close\n[…]\nstatement for the lock-serialized reality; the tail-resume docstring describes the end-seek implementation; minimal-env is stated as not-a-sandbox; --from is stated as not attesting endpoint identity.",
          "is_bot": false,
          "headline": "Docs: correct every claim that outran the implementation",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-12T17:21:19Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "70778b2e613047e5e3c7e97c2885e2cd9c71afb3",
          "body": "Reordered args, an injected env key (LD_PRELOAD), and a renamed server are each refused before any launch; the documented template-level residual (a changed env VALUE under an unchanged template verifies) is pinned as a test so it cannot silently become a claim; and a server pinned via --stdio verifies via --claude-config, proving templates compare structurally, not by which flag supplied them.",
          "is_bot": false,
          "headline": "Extend launch-identity adversarial coverage",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-12T16:42:56Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "6506f926e12ca02cee60e483d228dac1db6bef8c",
          "body": "…the catalog\n\nManifest v2 pins each server's launch specification: the UNEXPANDED\ncommand template, args, cwd, and environment variable names (never\nvalues, and never hashes of values), plus a source_fingerprint over the\ncanonical identity. verify compares every configured launch spec\nagainst the pi\n[…]\nmplate-level (env values are not pinned, PATH and registries resolve\nwhat they are asked for - pin package versions in args), and external\ntransports are outside launch-identity scope by construction.",
          "is_bot": false,
          "headline": "0.5.0: MCP execution identity - the pin covers the process, not just …",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-12T16:37:29Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "50be54bea60bebd1dbcf20a20a8a4a8f19edeffe",
          "body": "…erant\n\nPython 3.12+ on Linux/macOS parses far deeper JSON before the C-stack guard trips, so at 5000 levels the deep payload parsed cleanly there and was refused by the non-object-tool check instead of the depth wrap - a different named refusal, same property. The fake server now nests 200000 levels and the test accepts whichever named McpFetchError fires: the property is refusal-not-crash, not which guard fired first.",
          "is_bot": false,
          "headline": "Pin the hostile-depth wire test to the refusal property, platform-tol…",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-12T16:23:11Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "6087e0b9efc2ba4e57357004045258de579370a3",
          "body": "…amed\n\nThe launcher: Claude Code runs shell-form hooks under Git Bash on\nWindows and falls back to PowerShell when it is absent, where the POSIX\nlauncher is a parse error with exit 1, a non-blocking code - the gate\nsilently disabled (live-verified on Windows). init is now\nplatform-aware: a PowerShel\n[…]\n policy, same version, same verdict\"; the Windows scope\nis stated wherever the launcher appears; @file references are named as\noutside PreToolUse; audit messages are named as plaintext record\ncontent.",
          "is_bot": false,
          "headline": "0.4.2: close the runtime trust boundaries the third external review n…",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-12T16:20:35Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "72338df10aa99ed50e404a4978fe21f2f4b6bc07",
          "body": "run_pretooluse_hook(audit=, actor=): every adjudication - defer, allow,\ndeny, malformed-event and policy-error denials included - appends one\nhash-chained entry naming the tool, the decision, and the reasons, with\nthe proposed tool_input bound by SHA-256 fingerprint and never embedded.\nAn unwritable\n[…]\n after; the\ndefault resume=full is unchanged. Reading stays tolerant; verifying\nstays strict (verify_file). Pinned across both resume modes, including\nthe chain growing across separate hook processes.",
          "is_bot": false,
          "headline": "Audit wiring for the hook and memory-bounded tail resume",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-12T15:24:14Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "3e77c8d26d2dbe6839bdef615db288c5ef60e0bb",
          "body": "Fixed: verify_file is a strict verifier (malformed nonblank line or a\nmissing file is a verification failure; the CLI already refused these,\nthe library helper now matches); the GitHub Action installs the recusal\nbundled with its selected ref instead of latest-from-PyPI; the stdio\nfetcher treats ini\n[…]\n executable\ncode; launch-spec pinning is a named roadmap item); claims tightened to\nwhat the implementation proves; read-only stated as nonmutating, not\nconfidentiality-safe; independent defined once.",
          "is_bot": false,
          "headline": "0.4.1: hardening and documentation, driven by two external reviews",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-12T15:23:14Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "16062bbcb7d3166b57e8963d43a4032a2e4f702d",
          "body": "…ison section\n\nThe wedge is now two sentences plus a pointer: the external claims (the\nAnthropic reward-hacking study, the Berkeley benchmark-gaming result, the\nauto-mode false-negative figure, the MCPTox attack-success figure) live in\ndocs/WHY.md prose and docs/REFERENCES.md, not on the front page \n[…]\nsummary) and the product-comparison section are\nremoved; docs/LANDSCAPE.md keeps the comparison one click away. Prose\npass throughout: comma splices replaced with colons/semicolons, em\ndashes removed.",
          "is_bot": false,
          "headline": "README: shrink the wedge, move evidence to WHY/REFERENCES, cut compar…",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-12T14:35:52Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "f401da84ee90aad3d646709779ce191a7bebb61b",
          "body": "…ture\n\nA string `passed` was read against a false-token blocklist, so an\nunrecognized token (passed: maybe) coerced to PASS while\nstatus: maybe failed closed. Both fields now share the allowlist\nposture: a string counts as a pass only when it is an affirmative token;\nanything unrecognized reads as a\n[…]\n\nonly widens which paths an allowlisted read-only binary reads, and can\nnever select the binary itself. The comment now says exactly that, and\nboth directions are pinned in test_claude_code_allowlist.",
          "is_bot": false,
          "headline": "Fail closed on unrecognized string `passed` tokens; document glob pos…",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-12T14:24:54Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "6e749da23974a149b8f45f55e57ea3f58868950f",
          "body": "…e stack\n\nTwo new cookbook recipes close the \"how do I actually use the 0.4.0 MCP feature\" gap:\n\n- Recipe 13 \"Pin your MCP servers and enforce the pin\": the flagship local-server\n  workflow — pin a .mcp.json / stdio catalog, wire manifest_policy into a PreToolUse\n  hook (\"no pin, no MCP\"), and verif\n[…]\nests/test_mcp_cookbook.py\nexercise the exact recipe code (importing the real example module, not a paraphrase), so\na recipe that drifts from its promise fails CI. Docs-only + tests; no product change.",
          "is_bot": false,
          "headline": "docs: cookbook recipes for MCP pin-and-enforce and the full governanc…",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-10T22:27:55Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "b1e6613c0ccbcc082cbca799d723da28770d7a70",
          "body": "…ning (#3)\n\nGitHub is force-migrating Node 20 actions onto the Node 24 runtime; pinning to the\nfirst Node-24 major of each silences the warning (verified via each action's\nruns.using): checkout v4->v5, setup-python v5->v6, upload-artifact v4->v6,\ndownload-artifact v4->v7. Minimal bumps, not latest, to keep the diff small. CI\nconfig only; no product change.\n\nCo-authored-by: Philip Paz <philip.paz@gmail.com>",
          "is_bot": false,
          "headline": "ci: bump actions to their Node 24 majors to clear the deprecation war…",
          "author_name": "Philip Paz",
          "author_login": "philpaz",
          "committed_at": "2026-07-10T21:18:57Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "6dbff5ecb0efa6029b8ccace208d018d7c709620",
          "body": "The test job now runs the full Python 3.9–3.13 sweep on Linux plus a cross-platform\nsmoke (Python 3.12) on macOS and Windows. The interpreter-probe launcher and the stdio\nfetcher have OS-specific paths (POSIX vs. Windows argv/quoting), and the suite already\npasses locally on Windows; this makes that coverage continuous instead of manual. The\ndogfood action-gate job stays Linux-only (it uses a POSIX heredoc and /tmp paths).\n\nCo-authored-by: Philip Paz <philip.paz@gmail.com>",
          "is_bot": false,
          "headline": "ci: run tests on macOS and Windows too, not only Linux (#2)",
          "author_name": "Philip Paz",
          "author_login": "philpaz",
          "committed_at": "2026-07-10T20:01:42Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "56ce1978630bb1edb55f38cf6ba20726033e98dd",
          "body": "…g pull\n\nAdd recusal.mcp (pure kernel) + recusal.mcp_fetch (stdio collection), the\ndiscovery-boundary counterpart to the call-time gate. The model chooses tools by\nreading their declared descriptions, so a poisoned or post-approval-changed\ndeclaration steers the agent before any call exists to gate.\n[…]\nainst two real MCP\nservers (a FastMCP gateway and the production Salesforce Hosted MCP), captured as\nfixtures in tests/test_mcp_live.py. Zero runtime dependencies. ruff + mypy clean;\nfull suite green.",
          "is_bot": false,
          "headline": "0.4.0: MCP discovery governance — pin the tool catalog, refuse the ru…",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-10T19:49:49Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "34466608684d345567c48b40b3fd97d9e38c9401",
          "body": "…ded GitHub Action\n\nThe kernel, one seam further out: CI is by construction not the session that did\nthe work, so a recusal verdict there blocks a merge the way the hook blocks a\ntool call.\n\n- recusal verdict findings.json: PASS/RETRY/FAIL -> exit 0/1/2; strict by default\n  (a finding with no status\n[…]\n(a\n  tampered audit log must make the gate refuse); drift-locked by tests/test_cli.py.\n- recusal --version; plugin version surfaces bumped in lockstep.\n\n535 tests green; ruff, ruff format, mypy clean.",
          "is_bot": false,
          "headline": "0.3.0: CI adjudication CLI (verdict / audit verify / doctor) + dogfoo…",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-09T03:46:23Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "88e5d7b9e7843f9b5f859f8400c5c792fdf597b8",
          "body": null,
          "is_bot": false,
          "headline": "Add author contact (LinkedIn) to README and PyPI project urls",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-07T21:35:58Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "02ef7b35d19e01bf61e9d0f489a25fb07eca1145",
          "body": null,
          "is_bot": false,
          "headline": "style: ruff-format the 0.2.0 scaffolder and plugin test files",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-07T19:33:33Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "47ffebefe8472329660c2d57f645d99ea5fd1886",
          "body": "… GIF\n\n- python -m recusal init (and the recusal console script): scaffolds the gate\n  and registers the fail-closed launcher; never overwrites a gate, merges\n  (never clobbers) settings.json, refuses unparseable settings untouched,\n  idempotent; launcher drift-locked to .claude/settings.json.exampl\n[…]\nmo GIF above the fold, rendered from two verbatim transcripts\n  (live bypass-mode refusal + offline claude_refusal.py run).\n- 28 new tests (scaffolder 20, plugin 8); suite 496 passed, ruff/mypy clean.",
          "is_bot": false,
          "headline": "0.2.0: one-command init, Claude Code plugin distribution, README demo…",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-07T19:21:17Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "b5b891707bed67c740612a9601e1ad06a59e8999",
          "body": "…list)\n\nThe battle-tested command/path guard that governs this repo used to live only in\nthe copy-paste hook script .claude/hooks/recusal_gate.py, so it could not be\nimported, was tested only end-to-end, and shipped no fix to adopters. Move it into\nthe installable package.\n\n- recusal/deny_list.py: t\n[…]\n\nBehavior is unchanged: the existing red-team and subversion suites pass against the\nextracted engine, and the shimmed hook denies the same payloads. 471 tests pass; ruff,\nruff format, and mypy clean.",
          "is_bot": false,
          "headline": "0.1.3: extract the deny-list into an importable module (recusal.deny_…",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-06T04:47:19Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "9055dcf7ddfd0caee831a725be0fa546c5c8b61e",
          "body": "… in the hook\n\nSecurity review (three adversarial passes) found and closed two HIGH issues plus\nsupporting gaps, all pinned by regression tests.\n\n- Kernel (shipped): Finding.coerce read the `status` field against a hardcoded\n  {\"fail\",\"error\",\"warn\"} blocklist while `passed` used the full false-toke\n[…]\nor) across README, SECURITY, HOWTO, WHY, CHANGELOG,\n  and the module docstrings, pinned by a new test. Purged em/en-dashes from all\n  shipped files.\n\n460 tests pass; ruff, ruff format, and mypy clean.",
          "is_bot": false,
          "headline": "0.1.2: fix status silent-pass in the kernel and a self-protect bypass…",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-06T04:13:35Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "a850304602a195b57a56b33e5f888da3df487be1",
          "body": "Post-launch validation red-team (adopter + fresh-adversarial + live-install)\nsurfaced issues that reach USERS, now fixed and shipped as 0.1.1:\n\nAllowlist (the strong posture) — HIGH:\n- Removed pytest/mypy/rg/ruff from DEFAULT_SAFE_BINARIES. Each runs arbitrary\n  code through an argument (pytest impo\n[…]\nas contextual (no ranking); SECURITY.md documents the\nsafe-direction over-blocks and the out-of-repo environment boundary.\n\nAll pinned by tests. Suite 421 passed / 3 skipped; ruff, format, mypy clean.",
          "is_bot": false,
          "headline": "0.1.1: harden allowlist default + close self-protection enumeration gaps",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-06T01:05:51Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "accd3ef65f1ad62152706511cd9b5cdfaf2ba1a3",
          "body": "…rsion is a no-op",
          "is_bot": false,
          "headline": "release: skip-existing on PyPI publish so re-releasing a published ve…",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-06T00:41:21Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "7d20e64d0db0e2150be118a699fc2003870bafac",
          "body": "…test\n\nThe trailing-dot/space stripping added to _norm_path used a lookahead\n(`[ .]+(?=/)`) that backtracks quadratically on a long run of dots or\nspaces -- a ReDoS foot-gun (0.26s on 10k chars, and it grows with the\nsquare). Replaced it with an rstrip per '/'-delimited component, which is\nlinear: 2\n[…]\n5s. 2s still cleanly separates linear from the old quadratic (minutes)\nwhile tolerating runner variance. Added a linearity test for _norm_path.\n\nSuite 418 passed / 3 skipped; ruff, format, mypy clean.",
          "is_bot": false,
          "headline": "Fix ReDoS in path normalization; stabilize the anti-quadratic timing …",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-06T00:18:31Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "fc9df69afe1ff8ad8bec996967ff23259f0b0293",
          "body": "… the two postures as contextual\n\nThird red-team round (evade-new-protections, import/env-shadow, allowlist).\n\nDogfood hook:\n- Protect the recusal package DIRECTORY itself: mv/rename/rm/mklink of\n  recusal (bare token) now refused, mirroring the .claude/.git guard.\n- Refuse git restore (the modern g\n[…]\nt-of-repo environment\nboundary (site-packages/.pth/sitecustomize) and the safe_binaries arg-safe\nrule in SECURITY.md.\n\nAll gaps pinned by tests. Suite 417 passed / 3 skipped; ruff, format, mypy clean.",
          "is_bot": false,
          "headline": "Close remaining self-protection gaps; harden default allowlist; frame…",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-06T00:09:17Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "9a4c1df1ab5a265b21a959717c09f8f64406f00f",
          "body": "Continued red-team of the dogfood hook found control-path writes that the\nself-protect verb list missed, so they deferred (fail open):\n\n- New-Item -Force and Clear-Content (overwrite / truncate a protected file)\n- [IO.File]::WriteAllText / [System.IO.File]::WriteAllText (.NET write)\n- icacls / attri\n[…]\npinned by a no-over-block\ntest). Path-form evasions of the recusal/ guard (./, //, backslash, case,\n__pycache__/*.pyc) were all already denied.\n\nSuite 414 passed / 2 skipped; ruff, format, mypy clean.",
          "is_bot": false,
          "headline": "Extend kill-switch guard to PowerShell/.NET write verbs",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-05T23:53:53Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "854ffd8ab3ce5760438225be3e52fef6683eaa24",
          "body": "…t at front\n\nSecond red-team pass found a fresh fail-open seam in the dogfood hook. It\nimported the in-repo recusal package via sys.path.insert(0, _REPO), putting\nthe repo root ahead of the standard library. An agent could then write a\ntop-level hashlib.py / json.py / shlex.py / re.py -- a path with\n[…]\ncan shadow nothing.\nThe script dir Python places at sys.path[0] is .claude/hooks, itself a\nprotected control path. Pinned by a regression test.\n\nSuite 412 passed / 2 skipped; ruff, format, mypy clean.",
          "is_bot": false,
          "headline": "Close stdlib-shadow gate bypass: append repo to sys.path, never inser…",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-05T23:48:22Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "d400e81e8914aa9fb128aaf92c585686af089e60",
          "body": "Red-team of the public repo surfaced two real fail-open seams in the\ndogfood posture, both now closed and pinned by tests:\n\n- The hook delegates every decision to the in-repo `recusal/` package it\n  imports, but only the hook shim and settings were protected. An ordinary\n  allowed Edit to `recusal/*\n[…]\nh, two unsourced WHY.md claims, the unscoped 17% figure, the\n\"no I/O\" / stdlib-only / line-count nits, and a dangling landscape note.\n\nFull suite 411 passed / 2 skipped; ruff, ruff format, mypy clean.",
          "is_bot": false,
          "headline": "Harden dogfood self-protection and fail-closed launcher; fix doc claims",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-05T23:41:06Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "4af31a229b0691506a64691267107fefa3f484b9",
          "body": "… hole\n\nClaude Code treats a hook command that fails to launch (exit 127) as a\nnon-blocking error: the tool call proceeds. The documented `python3 ...`\ncommand therefore silently disabled the gate on stock Windows, where only\n`python`/`py` exist and the WindowsApps store stubs shadow both names.\n\nTh\n[…]\ndenied (backslash paths normalized), benign\ncommands defer and run.\n\n.gitignore now excludes .claude/settings.json so activating the gate on a\nclone stays a deliberate step; the example is what ships.",
          "is_bot": false,
          "headline": "Fail-closed cross-platform hook launcher; close the Windows fail-open…",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-05T21:55:56Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "b1e564a335a9ca6e7bf75cffabb0d89cf113e5c5",
          "body": null,
          "is_bot": false,
          "headline": "Add launch banner and brand assets",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-04T22:36:40Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "ca4a33be450ea194aff4b94fbcc193bbeb5170c2",
          "body": "Promote the default-deny posture from a cookbook recipe / example into\nshipped API: recusal.claude_code.allowlist_policy. Nothing runs unless\naffirmatively named — vetted first binaries only (exact argv token), no\nshell metacharacters, bare interpreters refused (python script.py is a\nprogram the gat\n[…]\nlimit note.\nexamples/allowlist_gate.py now wires the library policy instead of a\nprivate copy, so the demo and the API cannot drift.\n\nGates: 406 passed / 2 skipped, ruff lint+format clean, mypy clean.",
          "is_bot": false,
          "headline": "Allowlist mode as library API + two-tier subvertability claims",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-04T02:16:52Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "93f47a3763fe041b1a953b9ea849311def9a02ae",
          "body": "Both tools self-ignore via an internal .gitignore, but listing them keeps\ncontributor setups clean regardless of tool version or configuration.",
          "is_bot": false,
          "headline": "Ignore mypy and ruff caches explicitly",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-03T00:28:32Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "1b05792daec41f158893db950bda1b7abd8c9611",
          "body": "Rename the [Unreleased] heading to [0.1.0] - 2026-07-02 so the GitHub release\nnotes (generated from CHANGELOG.md) and the tag-vs-version release guard line up.",
          "is_bot": false,
          "headline": "Cut the 0.1.0 changelog section for release",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-03T00:19:56Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "51555725a7b6a97253f61db38860dde1e2e1e256",
          "body": "Fact-check external citations and reconcile claims: add docs/REFERENCES.md,\ncorrect LANDSCAPE (synthesis not a fabricated quote; add LlamaFirewall,\nAgentRunner), refresh WHY, and regenerate the PROVEN verbatim blocks so they\nreproduce the hook output exactly. CHANGELOG + CITATION housekeeping.",
          "is_bot": false,
          "headline": "Audit docs against sources; add REFERENCES, sync PROVEN verbatim blocks",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-03T00:17:50Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "cd13655d7b8a00380523d42a8f9e5c071a00ccc7",
          "body": "… quarantine recipes\n\nClose 11 red-team bypasses in the self-governance hook, fix a ReDoS in its\npattern matching, and document the deny-list's limits in SECURITY.md. Add\ndefault-deny allowlist and injection-quarantine example policies with smoke\ncoverage.",
          "is_bot": false,
          "headline": "Harden the dogfood hook (ReDoS + red-team bypasses); ship allowlist +…",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-03T00:17:43Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "6f1aa1e6b5a5993a4b833758b3290da79cfcad0f",
          "body": "…rsion test library\n\nA stringified failing outcome (`\"passed\": \"false\"`) is truthy in Python, so\n`bool(\"false\")` is True and a failure could slip through Finding.coerce as a\nsilent PASS - the exact failure mode this library exists to prevent. Read a\nloose `passed` value for intent via `_as_bool`/`_FALSE_LIKE` instead of raw\ntruthiness.\n\nAdd an adversarial \"subversion\" test suite (kernel, classify, audit, hook,\nadapters) that tries to make each surface certify work it should refuse.",
          "is_bot": false,
          "headline": "Harden the evidence kernel against loose-truthiness bypass; add subve…",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-07-03T00:17:37Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "aab21ce4c7318e2c31503ca23d0010e63665f869",
          "body": "…) recipe\n\n(a) ReDoS: a timing sweep of every hook regex found _REDIRECT_TO_SECRET was O(n^2)\non a long run of '>' (`>>?...\\S*` matching at each position) -> a 200k-char command\nhung the hook for minutes (a real DoS). Fixed: bounded the unbounded `\\S*` to\n`\\S{0,256}` (linear), and added a 4096-char \n[…]\nface table updated: obfuscation row points to the allowlist recipe; new\ndenial-of-service row covers the length cap + bounded matchers.\n\nAll gates green: ruff + format + mypy + 178 pytest. Not pushed.",
          "is_bot": false,
          "headline": "Fix a real ReDoS in the dogfood hook; ship an allowlist (default-deny…",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-06-30T23:49:45Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "f48242c75bb02c2a39a4a31f39019ea3dbc707c0",
          "body": "…limit; SECURITY threat model\n\nRan a targeted red-team sweep (44 obfuscated Bash payloads: spacing, quoting, escapes,\nsubstitution, Windows forms) through the actual hook. The existing de-obfuscation pass\nalready caught all quoting/escape/flag variants; found and closed 11 real bypasses:\n\n- POSIX de\n[…]\nrunaway, audit\ntampering, supply chain), each with the architectural response and the honest limit,\nframed as a reference architecture.\n\nAll gates green: ruff + format + mypy + 175 pytest. Not pushed.",
          "is_bot": false,
          "headline": "Red-team the dogfood hook: close 11 bypasses; document the deny-list …",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-06-30T23:33:40Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "df83e44ce62f8532695c2dc450bb1e2c0bbe3ffc",
          "body": "…orms\n\nBroaden the dogfood deny-list beyond shell redirects: a write-like Bash command\n(tee, cp/mv, sed -i, python/perl/ruby/node -e/-c, install, rsync, PowerShell\nSet-Content/Out-File, copy/xcopy/robocopy, or any > / >>) targeting a secret file\n(.env*, *.pem/.key/.p12, id_rsa/id_ed25519) or the gat\n[…]\n deny-list, not a guarantee (documented), but it closes the obvious\nredirect/copy/inline-script bypasses. +14 dogfood regression tests.\n\nAll gates green: ruff + format + mypy + 159 pytest. Not pushed.",
          "is_bot": false,
          "headline": "Extend dogfood hook: catch secret/self-protect writes via more Bash f…",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-06-30T23:22:48Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "f35ca8f7e48298ea36ab851667559263946ec96f",
          "body": "…choice\n\n- HOWTO §2 and examples/scenarios.py (the gallery + test scenario) used the\n  bypassable startswith() path confinement; switched both to os.path.commonpath,\n  consistent with COOKBOOK/EXAMPLE. \"/workspace/tmp_evil\" no longer slips past. Tests\n  assert decisions (not message text) and stay g\n[…]\nn ci.yml and\n  release.yml documenting that and pointing to SHA-pinning as the production step, so\n  it's a stated decision, not drift.\n\nAll gates green: ruff + format + mypy + 152 pytest. Not pushed.",
          "is_bot": false,
          "headline": "Fix last prefix-path drift (HOWTO + scenarios); document tag-pinning …",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-06-30T23:14:39Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "aed8e31b6eaa4f8adcaa0ceb3575f5210db902b4",
          "body": "…audit-doc fixes\n\nSecond hardening round. Reference-architecture framing: NOT chasing auditor-grade\nsupply-chain posture (skipped SHA-pinning the Actions); fixing correctness + honesty.\n\n- (high) The Claude enforcement adapters now adjudicate with strict=True, so a\n  policy-bug dict like {\"severity\"\n[…]\nead audit paths (truncation/full-rewrite detection) already have\ndirect coverage in tests/test_audit_hardening.py from the prior round.\n\nAll gates green: ruff + format + mypy + 152 pytest. Not pushed.",
          "is_bot": false,
          "headline": "Enforcement boundaries fail closed on ambiguous evidence; envelope + …",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-06-30T23:09:49Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "8f9271fbe2853ab029cb22b795415c52acd4c539",
          "body": "…ng notes\n\nDoc/claim corrections so the wording matches what the hardened code guarantees.\n\n- audit overclaim fixed (README, WHY, audit.py docstring): \"any later edit detectable\"\n  -> detects in-place edits and reordering only; truncation / full re-hash by a\n  write-access attacker need verify(..., \n[…]\nn audit.py and dropped \"production\" from the two\n  working docs (linkedin/RUNBOOK).\n\nAll gates green: ruff + format + mypy + 148 pytest. 0 em-dashes, 0 \"production\" claims,\n0 broken links. Not pushed.",
          "is_bot": false,
          "headline": "Correct audit/hook/classify claims; reframe PROVEN; CHANGELOG hardeni…",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-06-30T23:05:00Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "119366f5827bbe259122af8008efa36f15de827e",
          "body": "dogfood hook (.claude/hooks/recusal_gate.py): now protects its own kill-switch, an\nagent under the gate could previously Edit .claude/settings.json (disableAllHooks)\nor the hook script and disable enforcement mid-session (Claude Code hot-reloads\nsettings). Refuses writes to .claude/settings.json, se\n[…]\n basename .env match (was \"/.env\",\nmissed a relative .env), added MultiEdit, noted Bash-redirect is out of scope for a\nfile_path guard.\n\nAll gates green: ruff + format + mypy + 148 pytest. Not pushed.",
          "is_bot": false,
          "headline": "Harden dogfood hook + example/cookbook policies",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-06-30T23:00:36Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "92185d670bf2717ec8c64ed782d94a55eb9fde0d",
          "body": "…, strict coerce\n\nFrom three adversarial module reviews + an external hardening pass. Code + tests\n(141 pass, +17). Doc/claim corrections and the dogfood/example policy fixes follow\nin the next commit.\n\nclaude_code: a malformed or non-object PreToolUse event now FAILS CLOSED (deny) by\ndefault instea\n[…]\n., strict=True) / Finding.coerce(\n..., strict=True) reject a loose dict with no explicit status/passed instead of\nsilently treating it as a pass; also accept a \"passed\" bool key. Lenient default kept.",
          "is_bot": false,
          "headline": "Harden core modules: hook fail-closed, classify markers, audit limits…",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-06-30T22:57:08Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "eda641aba5d220d6d9299e711bb0f7e3a4e83ddb",
          "body": "An external review flagged three real issues; all confirmed against the code and fixed.\n\n1. (medium) Release could be \"ready\" with missing/empty gate evidence. all([]) is\n   True, so release(\"m\", []) and adjudicate_all() over a partial evidence map both\n   reported release_ready=True, undermining \"e\n[…]\nlt\n(require_all=True); checks intentionally require materialized sequences (docs tightened,\nsignature already Sequence), not iterators.\n\nAll gates green: ruff + format + mypy + 124 pytest. Not pushed.",
          "is_bot": false,
          "headline": "Fix 3 review findings: release vacuous-pass, checks docs, HOWTO assert",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-06-30T22:41:59Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "4f1816f4b78d608c3864450c42853ca54a5597a5",
          "body": "- README: link the three load-bearing claims to primary sources (Anthropic\n  reward-hacking research, UC Berkeley RDI benchmarks, Anthropic Claude Code\n  auto-mode post), so the wedge section is sourced, not asserted.\n- HOWTO §3 + README: mark the Managed Agents SDK surfaces (permission_policy,\n  us\n[…]\nllustrative and to be verified\n  against the reader's Agent SDK version; tool_confirmation only builds the dict\n  and has no SDK dependency.\n\nGates green: ruff format --check + 119 pytest. Not pushed.",
          "is_bot": false,
          "headline": "Cite README stats; label Managed-Agents adapter as illustrative",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-06-30T22:31:34Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "cd327ab6817f82a81476e488e41a00bad80969bf",
          "body": "…ction\"\n\nThree subagents re-verified every external claim across all markdown against the\nweb and flagged tone. Findings applied:\n\nACCURACY (one real fabrication, one real doc/code bug, both fixed):\n- WHY.md cited a human-oversight \"single-digit to low-double-digit %\" approver\n  catch rate to \"studi\n[…]\ned (PR template); examples/README no longer\n  references screenshots that don't exist.\n\nAll gates green: ruff + ruff format --check + mypy + 119 pytest. 0 em-dashes, 0\n\"production\" claims. Not pushed.",
          "is_bot": false,
          "headline": "Deep-research audit pass: cut overclaim, reframe LANDSCAPE, no \"produ…",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-06-30T22:28:56Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "450a0fd2b95b0308e95b2ea23f8fe3737c98a370",
          "body": "Three things, all toward a credible public release: facts only, no em-dashes,\nclear \"so what\" plus a concrete use-case example.\n\nACCURACY (every claim web-audited; two were false):\n- \"Nature / peer-reviewed study\" was wrong: the sys.exit(0) reward-hacking finding\n  is Anthropic's \"Natural Emergent M\n[…]\nuration\nfor a real use case (a database-admin agent left in auto mode), wired into README,\ndocs index, and examples index.\n\nAll gates green: ruff + ruff format --check + mypy + 119 pytest. Not pushed.",
          "is_bot": false,
          "headline": "Accuracy audit + remove all em-dashes + add worked-example doc",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-06-30T21:51:50Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "55ce85cb22933807cddefc3b109a2385225d5731",
          "body": "One-line-per-example index grouped by intent (see-the-refusal / plug-into-an-agent\n/ other surfaces), with the offline no-key demos flagged and links to the cookbook.\nThe one non-bloat content add; makes the examples dir self-explanatory for a\nfirst-time visitor. Not pushed.",
          "is_bot": false,
          "headline": "Add examples/README.md — a navigable index of the demos",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-06-30T19:03:00Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "ad9ee9c093046adc46639215cdadf3abd43930e6",
          "body": "… CITATION\n\nGitHub/PyPI first-impression pass:\n- README: add a \"See it refuse (20s, no API key)\" section right after Install — a\n  clone-and-run path that pays off immediately (claude_refusal + gallery).\n- pyproject: broaden classifiers (AI, Security, OS Independent, Py3-only,\n  Information-Technolo\n[…]\nff: lets people cite the work and renders GitHub's \"Cite this\n  repository\" button — on-brand for a thesis-driven project.\n\nAll gates green: ruff + ruff format --check + mypy + 119 pytest. Not pushed.",
          "is_bot": false,
          "headline": "Release polish: 20-second \"see it refuse\" path, PyPI discoverability,…",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-06-30T18:55:14Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "d868955b864611330d2b91b141e2285305e8ab8c",
          "body": "The biggest stars->installs adoption lever: a curated, copy-paste recipe set so\nusers don't start from an empty policy. 10 recipes for the actions people actually\ngate — destructive shell, unscoped SQL, secret-file/workspace confinement,\nwrong-subject writes, egress allowlist, prompt-injection quara\n[…]\n(honest: \"not turnkey security\"), wired for\nboth the Claude Code hook and any agent loop. Linked from README + docs index;\nCI-tested versions of the core recipes already live in examples/scenarios.py.",
          "is_bot": false,
          "headline": "Add docs/COOKBOOK.md — copy-paste policies to kill blank-policy friction",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-06-30T18:53:41Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "5c6c5bbed20bdc74bfd0da2517a337fe654dc604",
          "body": "Positioning decision: Claude-native hero + framework-neutral core, with the\nfibo/banking origin vocabulary removed from all code, examples, and tests. No\nbehavior change — naming only — all 119 tests still green.\n\n- data-quality examples (checks docstring, README, HOWTO, EVIDENCE, quickstart,\n  test\n[…]\n dishonest, and that doc's value is\nthat it's reproducible. CODE_OF_CONDUCT \"members\" is Contributor-Covenant boilerplate.\n\nAll gates green: ruff + ruff format --check + mypy + 119 pytest. Not pushed.",
          "is_bot": false,
          "headline": "Scrub origin-flavor vocabulary: Claude-native hero, vendor-neutral core",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-06-30T18:45:59Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "775a11b418f7772c87e38d93b5f2d868841d2067",
          "body": "…lity, close doc gaps\n\nResolve the open review critiques with real changes, not just notes:\n\n- gates: rebuild GateAdjudicator on the shared compute_verdict kernel — typed\n  GateResult/ReleaseEvidence, no parallel dict world. Strip all Salesforce/\n  migration vocabulary (sf_count/target_org/event_bus\n[…]\noor row); make quickstart runnable from a bare clone; bump the mypy\n  target to 3.10 to clear the config warning.\n\nAll gates green: ruff + ruff format --check + mypy clean, 119 tests pass. Not pushed.",
          "is_bot": false,
          "headline": "Launch-prep: unify gates on the verdict spine, prove framework-neutra…",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-06-30T18:26:56Z",
          "body_truncated": true,
          "is_coding_agent": false
        },
        {
          "oid": "7ea37544c60a73afa4065063a22a87ffd7a1475f",
          "body": "Add a full-system Architecture section to the README and update the EVIDENCE spine\ndiagram so the Verdict shows all consumers (claude/claude_code, audit, classify, gates).\nCorrect PROVEN (audit log is shipped, not roadmap) and add routing/audit to WHY's\n\"what it gives you\". All modules now documented; internal links verified.",
          "is_bot": false,
          "headline": "Docs accuracy pass: full architecture + latest modules everywhere",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-06-30T04:19:10Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "369345029aad422b4e035bb51cadb6bff4a75558",
          "body": "classify_failure / classify_verdict route a failure to a class + remediation channel\n(transient->retry, policy_violation->refuse, prompt_injection->quarantine, code_bug->\nfix-code, data_shape->fix-data, data_missing->fetch-data, spec_ambiguity->ask-human) by\nexplicit markers — first match wins, unmatched falls back to ask-human, never guesses.\nExtensible/replaceable taxonomy. Demo + 12 tests; documented in README/HOWTO/EXTENDING.",
          "is_bot": false,
          "headline": "Add deterministic failure classifier/router (recusal.classify)",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-06-30T04:05:42Z",
          "body_truncated": false,
          "is_coding_agent": false
        },
        {
          "oid": "24ac729e9481f7b3f5f62b9882a69ac6b98a41f6",
          "body": "Second proof — wired into a production FastAPI agent's confirm-gated CRM-write path\nto close the wrong-subject (C2) audit finding. The subject-match guard adjudicates with\ncompute_verdict; mismatch -> 409, write never runs; fail-open when no active member bound.\nVerified by live-endpoint tests with zero regression on the existing confirm/CRM suite.",
          "is_bot": false,
          "headline": "PROVEN: Recusal catches a real wrong-subject write in a production agent",
          "author_name": "Philip Paz",
          "author_login": null,
          "committed_at": "2026-06-30T03:52:27Z",
          "body_truncated": false,
          "is_coding_agent": false
        }
      ],
      "releases_count": 23,
      "commits_last_year": 105,
      "latest_release_at": "2026-07-27T18:12:37Z",
      "latest_release_tag": "v0.7.1",
      "releases_from_tags": false,
      "days_since_last_push": 0,
      "active_weeks_last_year": 4,
      "days_since_latest_release": 0,
      "mean_days_between_releases": 1.7
    },
    "community": {
      "has_readme": true,
      "has_license": true,
      "has_description": true,
      "has_contributing": true,
      "health_percentage": 100,
      "has_issue_template": false,
      "has_code_of_conduct": true,
      "has_pull_request_template": true
    },
    "ecosystem": {
      "packages": [
        {
          "name": "recusal",
          "exists": true,
          "license": null,
          "keywords": [
            "agent-governance",
            "agent-reliability",
            "agent-safety",
            "ai-agents",
            "ai-safety",
            "claude",
            "claude-code",
            "determinism",
            "guardrails",
            "llm",
            "runtime-governance",
            "separation-of-powers",
            "tool-use",
            "verifier",
            "Development Status :: 3 - Alpha",
            "Intended Audience :: Developers",
            "Intended Audience :: Information Technology",
            "License :: OSI Approved :: Apache Software License",
            "Operating System :: OS Independent",
            "Programming Language :: Python :: 3",
            "Programming Language :: Python :: 3 :: Only",
            "Topic :: Scientific/Engineering :: Artificial Intelligence",
            "Topic :: Security",
            "Topic :: Software Development :: Quality Assurance",
            "Typing :: Typed"
          ],
          "ecosystem": "pypi",
          "matches_repo": true,
          "registry_url": "https://pypi.org/project/recusal/",
          "is_deprecated": false,
          "latest_version": "0.7.1",
          "repository_url": "https://github.com/philpaz/recusal",
          "versions_count": 23,
          "total_downloads": null,
          "dependents_count": null,
          "deprecation_note": null,
          "maintainers_count": null,
          "monthly_downloads": 2854,
          "first_published_at": "2026-07-05T22:21:39.118778Z",
          "latest_published_at": "2026-07-27T18:14:38.625209Z",
          "latest_version_yanked": null,
          "days_since_latest_publish": 0
        }
      ]
    },
    "popularity": {
      "forks": 0,
      "stars": 3,
      "watchers": 0,
      "fork_history": {
        "days": [],
        "complete": true,
        "collected": 0,
        "total_forks": 0
      },
      "star_history": null,
      "open_issues_and_prs": 0
    },
    "ai_readiness": {
      "has_nix": false,
      "example_dirs": [
        "examples"
      ],
      "has_llms_txt": false,
      "has_dockerfile": false,
      "has_mcp_signal": false,
      "bootstrap_files": [],
      "api_schema_files": [],
      "has_devcontainer": false,
      "typecheck_configs": [
        "claude-plugin/vendor/recusal/py.typed",
        "recusal/py.typed"
      ],
      "toolchain_manifests": [],
      "largest_source_bytes": 94203,
      "source_files_sampled": 89,
      "oversized_source_files": 2,
      "agent_instruction_files": [],
      "agent_instruction_max_bytes": null
    },
    "dependencies": {
      "manifests": [
        "pyproject.toml"
      ],
      "advisories": {
        "error": null,
        "scope": null,
        "source": null,
        "findings": [],
        "collected": false,
        "malicious": [],
        "truncated": false,
        "by_severity": {},
        "advisory_count": 0,
        "affected_count": 0,
        "assessed_count": 0,
        "malicious_count": 0,
        "assessed_package": null,
        "unassessed_count": 0,
        "direct_affected_count": 0
      },
      "ecosystems": [
        "pypi"
      ],
      "dependencies": [],
      "all_dependencies": {
        "error": "GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository",
        "source": null,
        "packages": [],
        "collected": false,
        "truncated": false,
        "total_count": null,
        "direct_count": null,
        "indirect_count": null
      }
    },
    "maintainership": {
      "issues": {
        "open_prs": 0,
        "merged_prs": 3,
        "open_issues": 0,
        "closed_ratio": null,
        "closed_issues": 0,
        "closed_unmerged_prs": 0
      },
      "bus_factor": 1,
      "bot_contributors": 0,
      "top_contributors": [
        {
          "type": "User",
          "login": "philpaz",
          "commits": 21,
          "avatar_url": "https://avatars.githubusercontent.com/u/2147449?v=4"
        }
      ],
      "contributors_sampled": 1,
      "top_contributor_share": 1
    },
    "quality_signals": {
      "has_ci": true,
      "has_tests": true,
      "ci_workflows": [
        "build-dist.yml",
        "ci.yml",
        "release.yml",
        "scorecard.yml"
      ],
      "has_docs_dir": true,
      "linter_configs": [],
      "has_editorconfig": false,
      "has_linter_config": true,
      "has_precommit_config": true
    },
    "security_signals": {
      "lockfiles": [],
      "scorecard": {
        "checks": [
          {
            "name": "Binary-Artifacts",
            "score": 10,
            "reason": "no binaries found in the repo",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#binary-artifacts"
          },
          {
            "name": "Branch-Protection",
            "score": 0,
            "reason": "branch protection not enabled on development/release branches",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#branch-protection"
          },
          {
            "name": "CI-Tests",
            "score": 10,
            "reason": "1 out of 1 merged PRs checked by a CI test -- score normalized to 10",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#ci-tests"
          },
          {
            "name": "CII-Best-Practices",
            "score": 0,
            "reason": "no effort to earn an OpenSSF best practices badge detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#cii-best-practices"
          },
          {
            "name": "Code-Review",
            "score": 0,
            "reason": "Found 0/29 approved changesets -- score normalized to 0",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#code-review"
          },
          {
            "name": "Contributors",
            "score": 0,
            "reason": "project has 0 contributing companies or organizations -- score normalized to 0",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#contributors"
          },
          {
            "name": "Dangerous-Workflow",
            "score": 10,
            "reason": "no dangerous workflow patterns detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dangerous-workflow"
          },
          {
            "name": "Dependency-Update-Tool",
            "score": 0,
            "reason": "no update tool detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dependency-update-tool"
          },
          {
            "name": "Fuzzing",
            "score": 0,
            "reason": "project is not fuzzed",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#fuzzing"
          },
          {
            "name": "License",
            "score": 10,
            "reason": "license file detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#license"
          },
          {
            "name": "Maintained",
            "score": 0,
            "reason": "project was created within the last 90 days. Please review its contents carefully",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#maintained"
          },
          {
            "name": "Packaging",
            "score": 10,
            "reason": "packaging workflow detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#packaging"
          },
          {
            "name": "Pinned-Dependencies",
            "score": 6,
            "reason": "dependency not pinned by hash detected -- score normalized to 6",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#pinned-dependencies"
          },
          {
            "name": "SAST",
            "score": 0,
            "reason": "SAST tool is not run on all commits -- score normalized to 0",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#sast"
          },
          {
            "name": "Security-Policy",
            "score": 10,
            "reason": "security policy file detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#security-policy"
          },
          {
            "name": "Signed-Releases",
            "score": 8,
            "reason": "2 out of the last 2 releases have a total of 2 signed artifacts.",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#signed-releases"
          },
          {
            "name": "Token-Permissions",
            "score": 10,
            "reason": "GitHub workflow tokens follow principle of least privilege",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#token-permissions"
          },
          {
            "name": "Vulnerabilities",
            "score": 10,
            "reason": "0 existing vulnerabilities detected",
            "documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#vulnerabilities"
          }
        ],
        "commit": "e7e30284fea836f9444bb3e2f82ee10aea2d638a",
        "ran_at": "2026-07-27T18:15:39Z",
        "aggregate_score": 5.4,
        "scorecard_version": "v5.5.0"
      },
      "has_codeql_workflow": false,
      "has_security_policy": true,
      "has_dependabot_config": false
    },
    "contribution_flow": {
      "collected": true,
      "ci_last_run_at": "2026-07-27T18:14:43Z",
      "oldest_open_prs": [],
      "last_merged_pr_at": "2026-07-27T18:10:20Z",
      "ci_last_conclusion": "SUCCESS",
      "oldest_open_issues": []
    }
  },
  "config": {
    "disabled_metrics": [],
    "disabled_categories": [],
    "disabled_components": {}
  },
  "source": {
    "url": "https://github.com/philpaz/recusal",
    "host": "github.com",
    "name": "recusal",
    "owner": "philpaz"
  },
  "metrics": {
    "overall": {
      "key": "overall",
      "band": "moderate",
      "name": "Overall health",
      "note": null,
      "notes": [],
      "value": 65,
      "inputs": {
        "security": 54,
        "vitality": 73,
        "community": 49,
        "governance": 50,
        "engineering": 96
      },
      "components": []
    },
    "categories": [
      {
        "key": "vitality",
        "band": "good",
        "name": "Vitality",
        "value": 73,
        "weight": 0.22,
        "metrics": [
          {
            "key": "development_activity",
            "band": "moderate",
            "name": "Development activity",
            "note": null,
            "notes": [],
            "value": 57,
            "inputs": {
              "commits_last_year": 105,
              "human_commit_share": 1,
              "days_since_last_push": 0,
              "active_weeks_last_year": 4
            },
            "components": [
              {
                "key": "push_recency",
                "name": "Push recency",
                "detail": "last push 0 days ago",
                "points": 36,
                "status": "met",
                "details": [
                  {
                    "code": "push_recency",
                    "params": {
                      "days": 0
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "commit_cadence",
                "name": "Commit cadence",
                "detail": "4/52 weeks with commits",
                "points": 2.8,
                "status": "partial",
                "details": [
                  {
                    "code": "commit_cadence_weeks",
                    "params": {
                      "weeks": 4
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "commit_volume",
                "name": "Commit volume",
                "detail": "105 commits in the last year",
                "points": 18,
                "status": "met",
                "details": [
                  {
                    "code": "commits_last_year",
                    "params": {
                      "count": 105
                    }
                  }
                ],
                "max_points": 18
              },
              {
                "key": "openssf_scorecard_maintained",
                "name": "OpenSSF Scorecard: Maintained",
                "detail": "project was created within the last 90 days. Please review its contents carefully",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "release_discipline",
            "band": "excellent",
            "name": "Release discipline",
            "note": null,
            "notes": [],
            "value": 98,
            "inputs": {
              "releases_count": 23,
              "latest_release_tag": "v0.7.1",
              "releases_from_tags": false,
              "days_since_latest_release": 0,
              "mean_days_between_releases": 1.7
            },
            "components": [
              {
                "key": "ships_releases",
                "name": "Ships releases",
                "detail": "23 releases published",
                "points": 27,
                "status": "met",
                "details": [
                  {
                    "code": "releases_published",
                    "params": {
                      "count": 23
                    }
                  }
                ],
                "max_points": 27
              },
              {
                "key": "release_recency",
                "name": "Release recency",
                "detail": "latest release 0 days ago",
                "points": 36,
                "status": "met",
                "details": [
                  {
                    "code": "release_recency",
                    "params": {
                      "days": 0
                    }
                  }
                ],
                "max_points": 36
              },
              {
                "key": "release_cadence",
                "name": "Release cadence",
                "detail": "a release every ~1.7 days",
                "points": 27,
                "status": "met",
                "details": [
                  {
                    "code": "release_cadence",
                    "params": {
                      "gap": 1.7
                    }
                  }
                ],
                "max_points": 27
              },
              {
                "key": "openssf_scorecard_signed_releases",
                "name": "OpenSSF Scorecard: Signed-Releases",
                "detail": "2 out of the last 2 releases have a total of 2 signed artifacts.",
                "points": 8,
                "status": "partial",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "abandonment",
            "band": "excellent",
            "name": "Abandonment",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "cap": null,
              "state": "unverified",
              "guards": [],
              "signals": [],
              "red_flag": false,
              "multiplier_pct": 100,
              "declared_reason": null,
              "unverified_reason": "repository_too_young",
              "unanswered_open_prs": null,
              "unanswered_open_issues": null,
              "days_since_last_merged_pr": null,
              "days_since_last_human_commit": null,
              "days_since_last_human_commit_is_floor": false
            },
            "components": [
              {
                "key": "project_is_still_maintained",
                "name": "Project is still maintained",
                "detail": "maintenance record not established from the collected data",
                "points": 100,
                "status": "met",
                "details": [
                  {
                    "code": "abandonment_unverified",
                    "params": {}
                  }
                ],
                "max_points": 100
              }
            ]
          }
        ],
        "description": "Is the project alive — is code being written and are releases shipping?"
      },
      {
        "key": "community",
        "band": "at_risk",
        "name": "Community & Adoption",
        "value": 49,
        "weight": 0.18,
        "metrics": [
          {
            "key": "popularity",
            "band": "critical",
            "name": "Popularity & adoption",
            "note": null,
            "notes": [],
            "value": 5,
            "inputs": {
              "forks": 0,
              "stars": 3,
              "watchers": 0,
              "growth_state": "unverified",
              "growth_factor_pct": 100,
              "growth_unverified_reason": "no_history"
            },
            "components": [
              {
                "key": "stars",
                "name": "Stars",
                "detail": "3 stars",
                "points": 4.9,
                "status": "partial",
                "details": [
                  {
                    "code": "stars",
                    "params": {
                      "count": 3
                    }
                  }
                ],
                "max_points": 60
              },
              {
                "key": "forks",
                "name": "Forks",
                "detail": "0 forks",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "forks",
                    "params": {
                      "count": 0
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "watchers",
                "name": "Watchers",
                "detail": "0 watchers",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "watchers",
                    "params": {
                      "count": 0
                    }
                  }
                ],
                "max_points": 15
              }
            ]
          },
          {
            "key": "community_health",
            "band": "excellent",
            "name": "Community health",
            "note": null,
            "notes": [],
            "value": 92,
            "inputs": {
              "has_readme": true,
              "has_license": true,
              "has_contributing": true,
              "has_issue_template": false,
              "has_code_of_conduct": true,
              "has_pull_request_template": true
            },
            "components": [
              {
                "key": "readme",
                "name": "README",
                "detail": null,
                "points": 22.5,
                "status": "met",
                "details": [],
                "max_points": 22.5
              },
              {
                "key": "license",
                "name": "License",
                "detail": "recognized license (Apache-2.0)",
                "points": 22.5,
                "status": "met",
                "details": [
                  {
                    "code": "license_standard",
                    "params": {}
                  },
                  {
                    "code": "license_spdx",
                    "params": {
                      "spdx": "Apache-2.0"
                    }
                  }
                ],
                "max_points": 22.5
              },
              {
                "key": "contributing_guide",
                "name": "CONTRIBUTING guide",
                "detail": null,
                "points": 18,
                "status": "met",
                "details": [],
                "max_points": 18
              },
              {
                "key": "code_of_conduct",
                "name": "Code of conduct",
                "detail": null,
                "points": 13.5,
                "status": "met",
                "details": [],
                "max_points": 13.5
              },
              {
                "key": "issue_template",
                "name": "Issue template",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.2
              },
              {
                "key": "pr_template",
                "name": "PR template",
                "detail": null,
                "points": 6.3,
                "status": "met",
                "details": [],
                "max_points": 6.3
              }
            ]
          },
          {
            "key": "ecosystem_adoption",
            "band": "moderate",
            "name": "Ecosystem adoption (downloads)",
            "note": "Excluded from scoring (no data or not applicable): Registry dependents. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "registry_dependents"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 58,
            "inputs": {
              "packages": [
                "recusal"
              ],
              "dependents": null,
              "ecosystems": "pypi",
              "total_downloads": null,
              "monthly_downloads": 2854
            },
            "components": [
              {
                "key": "monthly_downloads",
                "name": "Monthly downloads",
                "detail": "2,854 downloads/month across pypi",
                "points": 46.1,
                "status": "partial",
                "details": [
                  {
                    "code": "downloads_monthly",
                    "params": {
                      "count": 2854,
                      "ecosystems": "pypi"
                    }
                  }
                ],
                "max_points": 80
              },
              {
                "key": "registry_dependents",
                "name": "Registry dependents",
                "detail": "not reported by this ecosystem",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "not_reported_by_this_ecosystem",
                    "params": {}
                  }
                ],
                "max_points": 20
              }
            ]
          }
        ],
        "description": "Does the project have users, downloads, attention, and a welcoming setup for contributors?"
      },
      {
        "key": "governance",
        "band": "moderate",
        "name": "Sustainability & Governance",
        "value": 50,
        "weight": 0.24,
        "metrics": [
          {
            "key": "maintainer_resilience",
            "band": "critical",
            "name": "Maintainer resilience (bus factor)",
            "note": null,
            "notes": [],
            "value": 10,
            "inputs": {
              "bus_factor": 1,
              "contributors_sampled": 1,
              "top_contributor_share": 1
            },
            "components": [
              {
                "key": "bus_factor",
                "name": "Bus factor",
                "detail": "1 contributor(s) cover half of all commits",
                "points": 9,
                "status": "partial",
                "details": [
                  {
                    "code": "bus_factor",
                    "params": {
                      "count": 1
                    }
                  }
                ],
                "max_points": 54
              },
              {
                "key": "commit_distribution",
                "name": "Commit distribution",
                "detail": "top contributor authored 100% of commits",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "top_contributor_share",
                    "params": {
                      "share": 100
                    }
                  }
                ],
                "max_points": 22.5
              },
              {
                "key": "contributor_breadth",
                "name": "Contributor breadth",
                "detail": "1 contributors",
                "points": 1.4,
                "status": "partial",
                "details": [
                  {
                    "code": "contributors_sampled",
                    "params": {
                      "count": 1
                    }
                  }
                ],
                "max_points": 13.5
              },
              {
                "key": "openssf_scorecard_contributors",
                "name": "OpenSSF Scorecard: Contributors",
                "detail": "project has 0 contributing companies or organizations -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "responsiveness",
            "band": "good",
            "name": "Issue & PR responsiveness",
            "note": "Excluded from scoring (no data or not applicable): Issue resolution. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "issue_resolution"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 72,
            "inputs": {
              "merged_prs": 3,
              "open_issues": 0,
              "closed_issues": 0,
              "issue_closed_ratio": null,
              "closed_unmerged_prs": 0
            },
            "components": [
              {
                "key": "issue_resolution",
                "name": "Issue resolution",
                "detail": "no issues or no data",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "no_issues_or_data",
                    "params": {}
                  }
                ],
                "max_points": 46.75
              },
              {
                "key": "pr_acceptance",
                "name": "PR acceptance",
                "detail": "3/3 decided PRs merged",
                "points": 38.2,
                "status": "met",
                "details": [
                  {
                    "code": "decided_prs_merged",
                    "params": {
                      "merged": 3,
                      "decided": 3
                    }
                  }
                ],
                "max_points": 38.25
              },
              {
                "key": "openssf_scorecard_code_review",
                "name": "OpenSSF Scorecard: Code-Review",
                "detail": "Found 0/29 approved changesets -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 15
              }
            ]
          },
          {
            "key": "stewardship",
            "band": "at_risk",
            "name": "Ownership & stewardship",
            "note": "Excluded from scoring (no data or not applicable): Verified domain. Remaining weights renormalized.",
            "notes": [
              {
                "code": "excluded_no_data",
                "params": {
                  "components": [
                    "verified_domain"
                  ]
                }
              },
              {
                "code": "weights_renormalized",
                "params": {}
              }
            ],
            "value": 36,
            "inputs": {
              "followers": 0,
              "owner_type": "User",
              "is_verified": null,
              "owner_login": "philpaz",
              "public_repos": 8,
              "account_age_days": 5095
            },
            "components": [
              {
                "key": "ownership_backing",
                "name": "Ownership backing",
                "detail": "personal (user) account",
                "points": 10,
                "status": "partial",
                "details": [
                  {
                    "code": "owner_personal",
                    "params": {}
                  }
                ],
                "max_points": 30
              },
              {
                "key": "verified_domain",
                "name": "Verified domain",
                "detail": "not applicable to user accounts",
                "points": 0,
                "status": "excluded",
                "details": [
                  {
                    "code": "not_applicable_to_user_accounts",
                    "params": {}
                  }
                ],
                "max_points": 20
              },
              {
                "key": "owner_reach",
                "name": "Owner reach",
                "detail": "0 followers of philpaz",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "owner_followers",
                    "params": {
                      "count": 0,
                      "login": "philpaz"
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "track_record",
                "name": "Track record",
                "detail": "8 public repos, account ~13 yr old",
                "points": 18.9,
                "status": "partial",
                "details": [
                  {
                    "code": "public_repos",
                    "params": {
                      "count": 8
                    }
                  },
                  {
                    "code": "account_age_years",
                    "params": {
                      "years": 13
                    }
                  }
                ],
                "max_points": 25
              }
            ]
          },
          {
            "key": "package_maintenance",
            "band": "excellent",
            "name": "Package maintenance",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "packages": [
                "recusal"
              ],
              "ecosystems": "pypi",
              "any_deprecated": false,
              "min_days_since_publish": 0
            },
            "components": [
              {
                "key": "published_resolvable",
                "name": "Published & resolvable",
                "detail": "1 package(s) on pypi",
                "points": 25,
                "status": "met",
                "details": [
                  {
                    "code": "packages_published",
                    "params": {
                      "count": 1,
                      "ecosystems": "pypi"
                    }
                  }
                ],
                "max_points": 25
              },
              {
                "key": "publish_recency",
                "name": "Publish recency",
                "detail": "latest publish 0 days ago",
                "points": 35,
                "status": "met",
                "details": [
                  {
                    "code": "publish_recency",
                    "params": {
                      "days": 0
                    }
                  }
                ],
                "max_points": 35
              },
              {
                "key": "version_history",
                "name": "Version history",
                "detail": "23 published versions",
                "points": 20,
                "status": "met",
                "details": [
                  {
                    "code": "published_versions",
                    "params": {
                      "count": 23
                    }
                  }
                ],
                "max_points": 20
              },
              {
                "key": "not_deprecated",
                "name": "Not deprecated",
                "detail": "active, not deprecated or yanked",
                "points": 20,
                "status": "met",
                "details": [
                  {
                    "code": "package_not_deprecated",
                    "params": {}
                  }
                ],
                "max_points": 20
              }
            ]
          }
        ],
        "description": "Will the project survive its people — bus factor, responsiveness, who backs it, and package upkeep?"
      },
      {
        "key": "engineering",
        "band": "excellent",
        "name": "Engineering Quality",
        "value": 96,
        "weight": 0.2,
        "metrics": [
          {
            "key": "engineering_practices",
            "band": "excellent",
            "name": "Engineering practices",
            "note": null,
            "notes": [],
            "value": 94,
            "inputs": {
              "has_ci": true,
              "has_tests": true,
              "has_editorconfig": false,
              "has_linter_config": true,
              "has_precommit_config": true
            },
            "components": [
              {
                "key": "ci_workflows",
                "name": "CI workflows",
                "detail": "4 workflow(s)",
                "points": 24,
                "status": "met",
                "details": [
                  {
                    "code": "ci_workflows",
                    "params": {
                      "count": 4
                    }
                  }
                ],
                "max_points": 24
              },
              {
                "key": "tests_present",
                "name": "Tests present",
                "detail": null,
                "points": 24,
                "status": "met",
                "details": [],
                "max_points": 24
              },
              {
                "key": "linter_config",
                "name": "Linter config",
                "detail": null,
                "points": 16,
                "status": "met",
                "details": [],
                "max_points": 16
              },
              {
                "key": "pre_commit_hooks",
                "name": "Pre-commit hooks",
                "detail": null,
                "points": 9.6,
                "status": "met",
                "details": [],
                "max_points": 9.6
              },
              {
                "key": "editorconfig",
                "name": ".editorconfig",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 6.4
              },
              {
                "key": "openssf_scorecard_ci_tests",
                "name": "OpenSSF Scorecard: CI-Tests",
                "detail": "1 out of 1 merged PRs checked by a CI test -- score normalized to 10",
                "points": 20,
                "status": "met",
                "details": [],
                "max_points": 20
              }
            ]
          },
          {
            "key": "documentation",
            "band": "excellent",
            "name": "Documentation",
            "note": null,
            "notes": [],
            "value": 100,
            "inputs": {
              "topics": [
                "agent-governance",
                "ai-agents",
                "ai-safety",
                "claude",
                "guardrails",
                "llm",
                "agent-security",
                "agentic-ai",
                "ai-governance",
                "claude-code",
                "hooks",
                "llm-security",
                "python",
                "audit-log",
                "ci",
                "devsecops",
                "github-actions"
              ],
              "has_wiki": true,
              "homepage": "https://pypi.org/project/recusal/",
              "has_readme": true,
              "has_docs_dir": true,
              "has_description": true
            },
            "components": [
              {
                "key": "readme",
                "name": "README",
                "detail": null,
                "points": 30,
                "status": "met",
                "details": [],
                "max_points": 30
              },
              {
                "key": "documentation_directory",
                "name": "Documentation directory",
                "detail": null,
                "points": 25,
                "status": "met",
                "details": [],
                "max_points": 25
              },
              {
                "key": "documentation_homepage_site",
                "name": "Documentation / homepage site",
                "detail": "https://pypi.org/project/recusal/",
                "points": 15,
                "status": "met",
                "details": [],
                "max_points": 15
              },
              {
                "key": "repository_description",
                "name": "Repository description",
                "detail": null,
                "points": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              },
              {
                "key": "topics",
                "name": "Topics",
                "detail": "17 topics",
                "points": 10,
                "status": "met",
                "details": [
                  {
                    "code": "topics_count",
                    "params": {
                      "count": 17
                    }
                  }
                ],
                "max_points": 10
              },
              {
                "key": "wiki",
                "name": "Wiki",
                "detail": null,
                "points": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              }
            ]
          }
        ],
        "description": "Are baseline engineering and documentation practices in place?"
      },
      {
        "key": "security",
        "band": "moderate",
        "name": "Security",
        "value": 54,
        "weight": 0.16,
        "metrics": [
          {
            "key": "security_posture",
            "band": "moderate",
            "name": "Security posture",
            "note": null,
            "notes": [],
            "value": 54,
            "inputs": {
              "source": "openssf_scorecard",
              "checks_evaluated": 18,
              "scorecard_version": "v5.5.0",
              "checks_inconclusive": 0,
              "scorecard_aggregate": 5.4
            },
            "components": [
              {
                "key": "binary_artifacts",
                "name": "Binary-Artifacts",
                "detail": "no binaries found in the repo",
                "points": 7.5,
                "status": "met",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "branch_protection",
                "name": "Branch-Protection",
                "detail": "branch protection not enabled on development/release branches",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "ci_tests",
                "name": "CI-Tests",
                "detail": "1 out of 1 merged PRs checked by a CI test -- score normalized to 10",
                "points": 2.5,
                "status": "met",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "cii_best_practices",
                "name": "CII-Best-Practices",
                "detail": "no effort to earn an OpenSSF best practices badge detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "code_review",
                "name": "Code-Review",
                "detail": "Found 0/29 approved changesets -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "contributors",
                "name": "Contributors",
                "detail": "project has 0 contributing companies or organizations -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "dangerous_workflow",
                "name": "Dangerous-Workflow",
                "detail": "no dangerous workflow patterns detected",
                "points": 10,
                "status": "met",
                "details": [],
                "max_points": 10
              },
              {
                "key": "dependency_update_tool",
                "name": "Dependency-Update-Tool",
                "detail": "no update tool detected",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "fuzzing",
                "name": "Fuzzing",
                "detail": "project is not fuzzed",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 5
              },
              {
                "key": "license",
                "name": "License",
                "detail": "license file detected",
                "points": 2.5,
                "status": "met",
                "details": [],
                "max_points": 2.5
              },
              {
                "key": "maintained",
                "name": "Maintained",
                "detail": "project was created within the last 90 days. Please review its contents carefully",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "packaging",
                "name": "Packaging",
                "detail": "packaging workflow detected",
                "points": 5,
                "status": "met",
                "details": [],
                "max_points": 5
              },
              {
                "key": "pinned_dependencies",
                "name": "Pinned-Dependencies",
                "detail": "dependency not pinned by hash detected -- score normalized to 6",
                "points": 3,
                "status": "partial",
                "details": [],
                "max_points": 5
              },
              {
                "key": "sast",
                "name": "SAST",
                "detail": "SAST tool is not run on all commits -- score normalized to 0",
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 5
              },
              {
                "key": "security_policy",
                "name": "Security-Policy",
                "detail": "security policy file detected",
                "points": 5,
                "status": "met",
                "details": [],
                "max_points": 5
              },
              {
                "key": "signed_releases",
                "name": "Signed-Releases",
                "detail": "2 out of the last 2 releases have a total of 2 signed artifacts.",
                "points": 6,
                "status": "partial",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "token_permissions",
                "name": "Token-Permissions",
                "detail": "GitHub workflow tokens follow principle of least privilege",
                "points": 7.5,
                "status": "met",
                "details": [],
                "max_points": 7.5
              },
              {
                "key": "vulnerabilities",
                "name": "Vulnerabilities",
                "detail": "0 existing vulnerabilities detected",
                "points": 7.5,
                "status": "met",
                "details": [],
                "max_points": 7.5
              }
            ]
          }
        ],
        "description": "Are visible security and supply-chain practices strong, with no malicious dependency and no unresolved high-risk jurisdiction exposure?"
      },
      {
        "key": "ai_readiness",
        "band": "moderate",
        "name": "AI Readiness",
        "value": 50,
        "weight": 0,
        "metrics": [
          {
            "key": "ai_agent_context",
            "band": "at_risk",
            "name": "Agent context & guidance",
            "note": null,
            "notes": [],
            "value": 40,
            "inputs": {
              "has_llms_txt": false,
              "legible_history_share": 0.95,
              "agent_instruction_files": [],
              "agent_instruction_max_bytes": null
            },
            "components": [
              {
                "key": "agent_instructions",
                "name": "Agent instructions",
                "detail": "no CLAUDE.md / AGENTS.md / editor rules",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "no_agent_instructions",
                    "params": {}
                  }
                ],
                "max_points": 45
              },
              {
                "key": "machine_readable_docs_llms_txt",
                "name": "Machine-readable docs (llms.txt)",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 15
              },
              {
                "key": "legible_commit_history",
                "name": "Legible commit history",
                "detail": "95 of 100 human commits state their intent (structured subject or explanatory body)",
                "points": 40,
                "status": "met",
                "details": [
                  {
                    "code": "legible_history",
                    "params": {
                      "legible": 95,
                      "sampled": 100
                    }
                  }
                ],
                "max_points": 40
              }
            ]
          },
          {
            "key": "ai_verify_loop",
            "band": "moderate",
            "name": "Verify loop (build / test / typecheck)",
            "note": null,
            "notes": [],
            "value": 50,
            "inputs": {
              "has_nix": false,
              "has_tests": true,
              "lockfiles": [],
              "has_dockerfile": false,
              "typed_language": false,
              "bootstrap_files": [],
              "has_devcontainer": false,
              "has_linter_config": true,
              "typecheck_configs": [
                "claude-plugin/vendor/recusal/py.typed",
                "recusal/py.typed"
              ],
              "agent_commit_share": 0,
              "toolchain_manifests": [],
              "dependency_bot_commit_share": 0
            },
            "components": [
              {
                "key": "one_command_bootstrap",
                "name": "One-command bootstrap",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 18
              },
              {
                "key": "automated_tests",
                "name": "Automated tests",
                "detail": null,
                "points": 22,
                "status": "met",
                "details": [],
                "max_points": 22
              },
              {
                "key": "lint_format_config",
                "name": "Lint / format config",
                "detail": null,
                "points": 11,
                "status": "met",
                "details": [],
                "max_points": 11
              },
              {
                "key": "static_type_checking",
                "name": "Static type checking",
                "detail": "claude-plugin/vendor/recusal/py.typed, recusal/py.typed",
                "points": 11,
                "status": "met",
                "details": [
                  {
                    "code": "file_list",
                    "params": {
                      "files": "claude-plugin/vendor/recusal/py.typed, recusal/py.typed"
                    }
                  }
                ],
                "max_points": 11
              },
              {
                "key": "reproducible_environment",
                "name": "Reproducible environment",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 10
              },
              {
                "key": "demonstrated_agent_practice",
                "name": "Demonstrated agent practice",
                "detail": "no agent-authored commits among the last 100",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "no_agent_authored_commits",
                    "params": {
                      "sampled": 100
                    }
                  }
                ],
                "max_points": 10
              },
              {
                "key": "automated_maintenance",
                "name": "Automated maintenance",
                "detail": "no automated dependency updates observed",
                "points": 0,
                "status": "missed",
                "details": [
                  {
                    "code": "no_dependency_automation",
                    "params": {}
                  }
                ],
                "max_points": 8
              },
              {
                "key": "openssf_scorecard_pinned_dependencies",
                "name": "OpenSSF Scorecard: Pinned-Dependencies",
                "detail": "dependency not pinned by hash detected -- score normalized to 6",
                "points": 6,
                "status": "partial",
                "details": [],
                "max_points": 10
              }
            ]
          },
          {
            "key": "ai_code_legibility",
            "band": "good",
            "name": "Code legibility for models",
            "note": null,
            "notes": [],
            "value": 81,
            "inputs": {
              "primary_language": "Python",
              "largest_source_bytes": 94203,
              "source_files_sampled": 89,
              "oversized_source_files": 2
            },
            "components": [
              {
                "key": "type_checkable_code",
                "name": "Type-checkable code",
                "detail": "Python with type-check config (claude-plugin/vendor/recusal/py.typed, recusal/py.typed)",
                "points": 27,
                "status": "partial",
                "details": [
                  {
                    "code": "typecheck_config_language",
                    "params": {
                      "files": "claude-plugin/vendor/recusal/py.typed, recusal/py.typed",
                      "language": "Python"
                    }
                  }
                ],
                "max_points": 45
              },
              {
                "key": "manageable_file_sizes",
                "name": "Manageable file sizes",
                "detail": "2/89 source files over 60KB",
                "points": 53.8,
                "status": "partial",
                "details": [
                  {
                    "code": "oversized_source_files",
                    "params": {
                      "kb": 60,
                      "sampled": 89,
                      "oversized": 2
                    }
                  }
                ],
                "max_points": 55
              }
            ]
          },
          {
            "key": "ai_interfaces",
            "band": "at_risk",
            "name": "Machine-readable interfaces",
            "note": null,
            "notes": [],
            "value": 40,
            "inputs": {
              "example_dirs": [
                "examples"
              ],
              "has_mcp_signal": false,
              "api_schema_files": []
            },
            "components": [
              {
                "key": "api_schema_openapi_graphql_proto",
                "name": "API schema (OpenAPI/GraphQL/proto)",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 40
              },
              {
                "key": "mcp_server",
                "name": "MCP server",
                "detail": null,
                "points": 0,
                "status": "missed",
                "details": [],
                "max_points": 20
              },
              {
                "key": "runnable_examples",
                "name": "Runnable examples",
                "detail": "examples",
                "points": 40,
                "status": "met",
                "details": [
                  {
                    "code": "file_list",
                    "params": {
                      "files": "examples"
                    }
                  }
                ],
                "max_points": 40
              }
            ]
          }
        ],
        "description": "How well is the repo equipped to be developed and maintained with AI coding agents? An independent, experimental badge — weight 0.0, so it is surfaced on its own and does not affect the overall health score."
      }
    ],
    "metrics_version": "1.13.0"
  },
  "warnings": [
    "Star history unavailable: GitHub GraphQL error: Resource not accessible by personal access token",
    "GitHub dependency-graph SBOM unavailable (404); the dependency graph may be disabled for this repository",
    "deps.dev does not index pypi:recusal@0.7.1; advisories assessed against the repository dependency graph instead"
  ],
  "report_type": "repository",
  "generated_at": "2026-07-27T18:15:47.986459Z",
  "schema_version": "0.27.0",
  "badge_url": "https://raw.githubusercontent.com/inspect-software/badges/main/v1/p/philpaz/recusal.svg",
  "full_name": "philpaz/recusal",
  "license_state": "standard",
  "license_spdx": "Apache-2.0"
}

Las puntuaciones son señales, no garantías. Reflejan prácticas públicamente visibles en GitHub; no son una auditoría de código ni una garantía de seguridad.

Los datos ausentes se excluyen y los pesos se renormalizan; nunca se puntúan como cero. La metodología es versionada y abierta: métricas v1.13.0, esquema v0.27.0 — metodología completa · wiki de métricas.

Cómo se sitúa un resultado dentro del registro general: estadísticas agregadasPyPI.