plugin-scanner 2.0.95__tar.gz → 2.0.96__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (341) hide show
  1. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/PKG-INFO +1 -1
  2. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/pyproject.toml +1 -1
  3. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/pyproject.toml.bak +1 -1
  4. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/runtime/actions.py +215 -14
  5. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/version.py +1 -1
  6. plugin_scanner-2.0.96/tests/test_guard_runtime_action_harnesses.py +316 -0
  7. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/.clusterfuzzlite/Dockerfile +0 -0
  8. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/.clusterfuzzlite/build.sh +0 -0
  9. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/.clusterfuzzlite/project.yaml +0 -0
  10. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/.clusterfuzzlite/requirements-atheris.txt +0 -0
  11. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/.dockerignore +0 -0
  12. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/.github/CODEOWNERS +0 -0
  13. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/.github/ISSUE_TEMPLATE/bug-report.yml +0 -0
  14. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/.github/ISSUE_TEMPLATE/config.yml +0 -0
  15. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/.github/ISSUE_TEMPLATE/feature-request.yml +0 -0
  16. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/.github/dependabot.yml +0 -0
  17. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/.github/workflows/ci.yml +0 -0
  18. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/.github/workflows/codeql.yml +0 -0
  19. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/.github/workflows/dependabot-uv-lock.yml +0 -0
  20. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/.github/workflows/fuzz.yml +0 -0
  21. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/.github/workflows/harness-smoke.yml +0 -0
  22. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/.github/workflows/publish.yml +0 -0
  23. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/.github/workflows/scorecard.yml +0 -0
  24. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/.gitignore +0 -0
  25. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/.pre-commit-hooks.yaml +0 -0
  26. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/CONTRIBUTING.md +0 -0
  27. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/Dockerfile +0 -0
  28. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/LICENSE +0 -0
  29. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/README.md +0 -0
  30. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/SECURITY.md +0 -0
  31. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/dashboard/index.html +0 -0
  32. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/dashboard/package.json +0 -0
  33. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/dashboard/pnpm-lock.yaml +0 -0
  34. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/dashboard/public/apple-touch-icon.png +0 -0
  35. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/dashboard/public/brand/Logo_Icon_Dark.png +0 -0
  36. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/dashboard/public/brand/Logo_Whole.png +0 -0
  37. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/dashboard/public/favicon-16x16.png +0 -0
  38. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/dashboard/public/favicon-32x32.png +0 -0
  39. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/dashboard/public/favicon.ico +0 -0
  40. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/dashboard/src/app.tsx +0 -0
  41. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/dashboard/src/approval-center-layout.tsx +0 -0
  42. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/dashboard/src/approval-center-primitives.tsx +0 -0
  43. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/dashboard/src/approval-center-utils.ts +0 -0
  44. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/dashboard/src/fleet-workspace.tsx +0 -0
  45. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/dashboard/src/guard-api.test.ts +0 -0
  46. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/dashboard/src/guard-api.ts +0 -0
  47. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/dashboard/src/guard-demo.ts +0 -0
  48. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/dashboard/src/guard-types.ts +0 -0
  49. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/dashboard/src/main.tsx +0 -0
  50. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/dashboard/src/receipts-workspace.tsx +0 -0
  51. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/dashboard/src/runtime-overview.tsx +0 -0
  52. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/dashboard/src/settings-workspace.tsx +0 -0
  53. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/dashboard/src/styles.css +0 -0
  54. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/dashboard/src/vite-env.d.ts +0 -0
  55. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/dashboard/tsconfig.json +0 -0
  56. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/dashboard/vite.config.ts +0 -0
  57. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/docker-requirements.txt +0 -0
  58. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/docs/guard/approval-audit.md +0 -0
  59. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/docs/guard/architecture.md +0 -0
  60. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/docs/guard/get-started.md +0 -0
  61. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/docs/guard/harness-support.md +0 -0
  62. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/docs/guard/local-vs-cloud.md +0 -0
  63. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/docs/guard/testing-matrix.md +0 -0
  64. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/docs/trust/mcp-trust-draft.md +0 -0
  65. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/docs/trust/plugin-trust-draft.md +0 -0
  66. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/docs/trust/skill-trust-local.md +0 -0
  67. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/fuzzers/manifest_fuzzer.py +0 -0
  68. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/requirements.txt +0 -0
  69. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/schemas/plugin-quality.v1.json +0 -0
  70. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/schemas/scan-result.v1.json +0 -0
  71. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/schemas/verify-result.v1.json +0 -0
  72. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/__init__.py +0 -0
  73. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/action_runner.py +0 -0
  74. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/argparse_utils.py +0 -0
  75. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/checks/__init__.py +0 -0
  76. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/checks/best_practices.py +0 -0
  77. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/checks/claude.py +0 -0
  78. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/checks/code_quality.py +0 -0
  79. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/checks/ecosystem_common.py +0 -0
  80. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/checks/gemini.py +0 -0
  81. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/checks/manifest.py +0 -0
  82. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/checks/manifest_support.py +0 -0
  83. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/checks/marketplace.py +0 -0
  84. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/checks/mcp_security.py +0 -0
  85. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/checks/opencode.py +0 -0
  86. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/checks/operational_security.py +0 -0
  87. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/checks/security.py +0 -0
  88. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/checks/skill_security.py +0 -0
  89. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/cli.py +0 -0
  90. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/cli_ui.py +0 -0
  91. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/config.py +0 -0
  92. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/ecosystems/__init__.py +0 -0
  93. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/ecosystems/base.py +0 -0
  94. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/ecosystems/claude.py +0 -0
  95. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/ecosystems/codex.py +0 -0
  96. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/ecosystems/detect.py +0 -0
  97. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/ecosystems/gemini.py +0 -0
  98. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/ecosystems/opencode.py +0 -0
  99. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/ecosystems/registry.py +0 -0
  100. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/ecosystems/types.py +0 -0
  101. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/github_reporting.py +0 -0
  102. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/__init__.py +0 -0
  103. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/adapters/__init__.py +0 -0
  104. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/adapters/antigravity.py +0 -0
  105. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/adapters/base.py +0 -0
  106. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/adapters/claude_code.py +0 -0
  107. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/adapters/codex.py +0 -0
  108. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/adapters/copilot.py +0 -0
  109. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/adapters/cursor.py +0 -0
  110. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/adapters/gemini.py +0 -0
  111. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/adapters/hermes.py +0 -0
  112. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/adapters/mcp_servers.py +0 -0
  113. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/adapters/openclaw.py +0 -0
  114. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/adapters/openclaw_config.py +0 -0
  115. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/adapters/openclaw_support.py +0 -0
  116. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/adapters/opencode.py +0 -0
  117. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/adapters/opencode_artifacts.py +0 -0
  118. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/advisory_model.py +0 -0
  119. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/approvals.py +0 -0
  120. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/bridge/__init__.py +0 -0
  121. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/capabilities.py +0 -0
  122. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/cli/__init__.py +0 -0
  123. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/cli/approval_commands.py +0 -0
  124. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/cli/bootstrap.py +0 -0
  125. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/cli/commands.py +0 -0
  126. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/cli/connect_flow.py +0 -0
  127. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/cli/install_commands.py +0 -0
  128. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/cli/product.py +0 -0
  129. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/cli/prompt.py +0 -0
  130. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/cli/render.py +0 -0
  131. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/cli/update_commands.py +0 -0
  132. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/codex_config.py +0 -0
  133. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/config.py +0 -0
  134. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/consumer/__init__.py +0 -0
  135. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/consumer/service.py +0 -0
  136. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/daemon/__init__.py +0 -0
  137. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/daemon/client.py +0 -0
  138. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/daemon/manager.py +0 -0
  139. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/daemon/server.py +0 -0
  140. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/daemon/static/apple-touch-icon.png +0 -0
  141. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/daemon/static/assets/guard-dashboard.js +0 -0
  142. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/daemon/static/assets/index.css +0 -0
  143. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/daemon/static/brand/Logo_Icon_Dark.png +0 -0
  144. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/daemon/static/brand/Logo_Whole.png +0 -0
  145. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/daemon/static/favicon-16x16.png +0 -0
  146. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/daemon/static/favicon-32x32.png +0 -0
  147. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/daemon/static/favicon.ico +0 -0
  148. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/daemon/static/index.html +0 -0
  149. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/edge_events.py +0 -0
  150. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/incident.py +0 -0
  151. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/launcher.py +0 -0
  152. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/mcp_tool_calls.py +0 -0
  153. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/models.py +0 -0
  154. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/policy/__init__.py +0 -0
  155. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/policy/engine.py +0 -0
  156. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/protect.py +0 -0
  157. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/proxy/__init__.py +0 -0
  158. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/proxy/remote.py +0 -0
  159. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/proxy/runtime_mcp.py +0 -0
  160. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/proxy/stdio.py +0 -0
  161. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/receipts/__init__.py +0 -0
  162. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/receipts/manager.py +0 -0
  163. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/redaction.py +0 -0
  164. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/risk.py +0 -0
  165. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/runtime/__init__.py +0 -0
  166. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/runtime/runner.py +0 -0
  167. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/runtime/secret_file_requests.py +0 -0
  168. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/runtime/surface_server.py +0 -0
  169. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/schemas/__init__.py +0 -0
  170. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/schemas/consumer_mode.py +0 -0
  171. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/schemas/guard_event_v1.py +0 -0
  172. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/schemas/surface_server.py +0 -0
  173. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/shims.py +0 -0
  174. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/store.py +0 -0
  175. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/store_approvals.py +0 -0
  176. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/store_connect.py +0 -0
  177. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/guard/types.py +0 -0
  178. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/integrations/__init__.py +0 -0
  179. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/integrations/cisco_mcp_scanner.py +0 -0
  180. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/integrations/cisco_skill_scanner.py +0 -0
  181. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/lint_fixes.py +0 -0
  182. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/marketplace_support.py +0 -0
  183. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/models.py +0 -0
  184. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/path_support.py +0 -0
  185. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/policy.py +0 -0
  186. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/quality_artifact.py +0 -0
  187. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/repo_detect.py +0 -0
  188. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/reporting.py +0 -0
  189. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/rules/__init__.py +0 -0
  190. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/rules/registry.py +0 -0
  191. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/rules/specs.py +0 -0
  192. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/scanner.py +0 -0
  193. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/submission.py +0 -0
  194. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/suppressions.py +0 -0
  195. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/trust_domain_scoring.py +0 -0
  196. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/trust_helpers.py +0 -0
  197. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/trust_mcp_scoring.py +0 -0
  198. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/trust_models.py +0 -0
  199. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/trust_plugin_scoring.py +0 -0
  200. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/trust_scoring.py +0 -0
  201. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/trust_skill_scoring.py +0 -0
  202. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/trust_specs.py +0 -0
  203. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/src/codex_plugin_scanner/verification.py +0 -0
  204. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/__init__.py +0 -0
  205. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/conftest.py +0 -0
  206. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/__init__.py +0 -0
  207. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/bad-plugin/.codex-plugin/plugin.json +0 -0
  208. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/bad-plugin/.mcp.json +0 -0
  209. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/bad-plugin/secrets.js +0 -0
  210. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/claude-plugin-good/.claude-plugin/plugin.json +0 -0
  211. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/claude-plugin-good/LICENSE +0 -0
  212. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/claude-plugin-good/README.md +0 -0
  213. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/claude-plugin-good/SECURITY.md +0 -0
  214. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/claude-plugin-good/hooks/hooks.json +0 -0
  215. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/claude-plugin-good/skills/example/SKILL.md +0 -0
  216. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/code-quality-bad/evil.js +0 -0
  217. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/code-quality-bad/inject.js +0 -0
  218. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/gemini-extension-good/GEMINI.md +0 -0
  219. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/gemini-extension-good/LICENSE +0 -0
  220. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/gemini-extension-good/README.md +0 -0
  221. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/gemini-extension-good/SECURITY.md +0 -0
  222. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/gemini-extension-good/commands/hello.toml +0 -0
  223. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/gemini-extension-good/gemini-extension.json +0 -0
  224. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/good-plugin/.codex-plugin/plugin.json +0 -0
  225. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/good-plugin/.codexignore +0 -0
  226. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/good-plugin/LICENSE +0 -0
  227. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/good-plugin/README.md +0 -0
  228. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/good-plugin/SECURITY.md +0 -0
  229. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/good-plugin/assets/icon.svg +0 -0
  230. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/good-plugin/assets/logo.svg +0 -0
  231. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/good-plugin/assets/screenshot.svg +0 -0
  232. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/good-plugin/skills/example/SKILL.md +0 -0
  233. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/guard-codex-malicious-mcp/.codex/config.toml +0 -0
  234. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/hermes-plugin-evil/config.yaml +0 -0
  235. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/hermes-plugin-evil/mcp_servers.json +0 -0
  236. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/hermes-plugin-evil/skills/security/malicious/SKILL.md +0 -0
  237. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/SKILL.md +0 -0
  238. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/references/api-setup.md +0 -0
  239. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/hermes-plugin-evil/skills/stealth/sneaky/scripts/deploy.sh +0 -0
  240. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/hermes-plugin-evil/skills/utils/benign/SKILL.md +0 -0
  241. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/malformed-json/.codex-plugin/plugin.json +0 -0
  242. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/malicious-skill-plugin/.codex-plugin/plugin.json +0 -0
  243. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/malicious-skill-plugin/.codexignore +0 -0
  244. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/malicious-skill-plugin/LICENSE +0 -0
  245. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/malicious-skill-plugin/README.md +0 -0
  246. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/malicious-skill-plugin/SECURITY.md +0 -0
  247. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/malicious-skill-plugin/skills/leaky-skill/SKILL.md +0 -0
  248. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/mcp-canary-server.py +0 -0
  249. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/minimal-plugin/.codex-plugin/plugin.json +0 -0
  250. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/missing-fields/.codex-plugin/plugin.json +0 -0
  251. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/mit-license/LICENSE +0 -0
  252. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/multi-ecosystem-repo/codex-plugin/.codex-plugin/plugin.json +0 -0
  253. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/multi-ecosystem-repo/codex-plugin/LICENSE +0 -0
  254. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/multi-ecosystem-repo/codex-plugin/README.md +0 -0
  255. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/multi-ecosystem-repo/codex-plugin/SECURITY.md +0 -0
  256. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/multi-ecosystem-repo/gemini-ext/README.md +0 -0
  257. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/multi-ecosystem-repo/gemini-ext/gemini-extension.json +0 -0
  258. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/multi-plugin-repo/.agents/plugins/marketplace.json +0 -0
  259. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/.codex-plugin/plugin.json +0 -0
  260. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/.codexignore +0 -0
  261. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/LICENSE +0 -0
  262. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/README.md +0 -0
  263. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/SECURITY.md +0 -0
  264. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/multi-plugin-repo/plugins/alpha-plugin/skills/example/SKILL.md +0 -0
  265. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/multi-plugin-repo/plugins/beta-plugin/.codex-plugin/plugin.json +0 -0
  266. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/multi-plugin-repo/plugins/beta-plugin/skills/example/SKILL.md +0 -0
  267. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/no-version/.codex-plugin/plugin.json +0 -0
  268. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/opencode-good/.opencode/commands/hello.md +0 -0
  269. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/opencode-good/.opencode/plugins/example.ts +0 -0
  270. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/opencode-good/LICENSE +0 -0
  271. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/opencode-good/README.md +0 -0
  272. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/opencode-good/SECURITY.md +0 -0
  273. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/opencode-good/opencode.jsonc +0 -0
  274. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/skills-missing-dir/.codex-plugin/plugin.json +0 -0
  275. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/skills-no-frontmatter/.codex-plugin/plugin.json +0 -0
  276. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/skills-no-frontmatter/skills/bad-skill/SKILL.md +0 -0
  277. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/with-marketplace/.codex-plugin/plugin.json +0 -0
  278. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/with-marketplace/marketplace-broken.json +0 -0
  279. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/fixtures/with-marketplace/marketplace.json +0 -0
  280. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test-trust-scoring.py +0 -0
  281. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test-trust-specs.py +0 -0
  282. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_action_runner.py +0 -0
  283. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_best_practices.py +0 -0
  284. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_cisco_install_surfaces.py +0 -0
  285. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_cli.py +0 -0
  286. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_code_quality.py +0 -0
  287. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_config.py +0 -0
  288. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_coverage_remaining.py +0 -0
  289. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_ecosystems.py +0 -0
  290. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_edge_cases.py +0 -0
  291. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_final_coverage.py +0 -0
  292. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_guard_approvals.py +0 -0
  293. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_guard_bootstrap.py +0 -0
  294. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_guard_capabilities.py +0 -0
  295. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_guard_claude_adapter.py +0 -0
  296. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_guard_cli.py +0 -0
  297. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_guard_codex_e2e.py +0 -0
  298. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_guard_codex_install.py +0 -0
  299. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_guard_codex_proxy.py +0 -0
  300. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_guard_config_paths.py +0 -0
  301. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_guard_connect_flow.py +0 -0
  302. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_guard_consumer_mode.py +0 -0
  303. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_guard_copilot_adapter.py +0 -0
  304. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_guard_copilot_proxy.py +0 -0
  305. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_guard_daemon_manager.py +0 -0
  306. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_guard_event_schema_v1.py +0 -0
  307. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_guard_events.py +0 -0
  308. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_guard_launch_env.py +0 -0
  309. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_guard_opencode_proxy.py +0 -0
  310. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_guard_product_flow.py +0 -0
  311. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_guard_protect.py +0 -0
  312. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_guard_render.py +0 -0
  313. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_guard_risk.py +0 -0
  314. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_guard_runtime.py +0 -0
  315. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_guard_runtime_actions.py +0 -0
  316. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_guard_store_migrations.py +0 -0
  317. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_guard_surface_server.py +0 -0
  318. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_guard_verdicts.py +0 -0
  319. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_hermes_adapter.py +0 -0
  320. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_integration.py +0 -0
  321. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_lint_fixes.py +0 -0
  322. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_live_cisco_smoke.py +0 -0
  323. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_manifest.py +0 -0
  324. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_marketplace.py +0 -0
  325. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_mcp_security.py +0 -0
  326. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_openclaw_adapter.py +0 -0
  327. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_operational_security.py +0 -0
  328. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_policy.py +0 -0
  329. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_quality_artifact.py +0 -0
  330. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_rule_registry.py +0 -0
  331. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_scanner.py +0 -0
  332. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_schema_contracts.py +0 -0
  333. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_security.py +0 -0
  334. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_security_ops.py +0 -0
  335. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_skill_security.py +0 -0
  336. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_submission.py +0 -0
  337. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_trust_scoring.py +0 -0
  338. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_trust_specs.py +0 -0
  339. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_verification.py +0 -0
  340. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/tests/test_versioning.py +0 -0
  341. {plugin_scanner-2.0.95 → plugin_scanner-2.0.96}/uv.lock +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plugin-scanner
3
- Version: 2.0.95
3
+ Version: 2.0.96
4
4
  Summary: Lint, verify, and gate plugin ecosystems for maintainers, CI, and publish workflows.
5
5
  Project-URL: Homepage, https://github.com/hashgraph-online/ai-plugin-scanner
6
6
  Project-URL: Repository, https://github.com/hashgraph-online/ai-plugin-scanner
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "plugin-scanner"
7
- version = "2.0.95"
7
+ version = "2.0.96"
8
8
  description = "Lint, verify, and gate plugin ecosystems for maintainers, CI, and publish workflows."
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "hol-guard"
7
- version = "2.0.95"
7
+ version = "2.0.96"
8
8
  description = "Protect local AI harnesses with HOL Guard and run scanner checks for Codex, Claude, Cursor, Gemini, and OpenCode."
9
9
  readme = "README.md"
10
10
  license = "Apache-2.0"
@@ -73,9 +73,12 @@ _SENSITIVE_RAW_KEYS = frozenset(
73
73
  )
74
74
  _SENSITIVE_RAW_KEY_ALIASES = frozenset(key.replace("_", "") for key in _SENSITIVE_RAW_KEYS)
75
75
  _HOOK_EVENT_NAME_MAP = {
76
+ "prompt": "UserPromptSubmit",
76
77
  "userpromptsubmit": "UserPromptSubmit",
77
78
  "userpromptsubmitted": "UserPromptSubmit",
79
+ "pretool": "PreToolUse",
78
80
  "pretooluse": "PreToolUse",
81
+ "posttool": "PostToolUse",
79
82
  "posttooluse": "PostToolUse",
80
83
  "permissionrequest": "PermissionRequest",
81
84
  }
@@ -229,11 +232,122 @@ def normalize_codex_hook_payload(
229
232
  ) -> GuardActionEnvelope:
230
233
  """Normalize a Codex hook payload into a typed action envelope."""
231
234
 
235
+ return _normalize_action_payload(
236
+ payload,
237
+ harness="codex",
238
+ default_event_name=None,
239
+ workspace=workspace,
240
+ home_dir=home_dir,
241
+ )
242
+
243
+
244
+ def normalize_claude_hook_payload(
245
+ payload: Mapping[str, object],
246
+ *,
247
+ workspace: Path | str | None = None,
248
+ home_dir: Path | str | None = None,
249
+ ) -> GuardActionEnvelope:
250
+ """Normalize a Claude Code hook payload into a typed action envelope."""
251
+
252
+ return _normalize_action_payload(
253
+ payload,
254
+ harness="claude-code",
255
+ default_event_name=None,
256
+ workspace=workspace,
257
+ home_dir=home_dir,
258
+ )
259
+
260
+
261
+ def normalize_opencode_payload(
262
+ payload: Mapping[str, object],
263
+ *,
264
+ workspace: Path | str | None = None,
265
+ home_dir: Path | str | None = None,
266
+ ) -> GuardActionEnvelope:
267
+ """Normalize an OpenCode runtime payload into a typed action envelope."""
268
+
269
+ return _normalize_action_payload(
270
+ payload,
271
+ harness="opencode",
272
+ default_event_name=None,
273
+ workspace=workspace,
274
+ home_dir=home_dir,
275
+ )
276
+
277
+
278
+ def normalize_copilot_payload(
279
+ payload: Mapping[str, object],
280
+ *,
281
+ workspace: Path | str | None = None,
282
+ home_dir: Path | str | None = None,
283
+ ) -> GuardActionEnvelope:
284
+ """Normalize a Copilot runtime payload into a typed action envelope."""
285
+
286
+ return _normalize_action_payload(
287
+ payload,
288
+ harness="copilot",
289
+ default_event_name=None,
290
+ workspace=workspace,
291
+ home_dir=home_dir,
292
+ )
293
+
294
+
295
+ def normalize_gemini_payload(
296
+ payload: Mapping[str, object],
297
+ *,
298
+ workspace: Path | str | None = None,
299
+ home_dir: Path | str | None = None,
300
+ ) -> GuardActionEnvelope:
301
+ """Normalize a Gemini runtime payload into a typed action envelope."""
302
+
303
+ return _normalize_action_payload(
304
+ payload,
305
+ harness="gemini",
306
+ default_event_name=None,
307
+ workspace=workspace,
308
+ home_dir=home_dir,
309
+ )
310
+
311
+
312
+ def normalize_harness_payload(
313
+ harness: str,
314
+ event_name: str,
315
+ payload: Mapping[str, object],
316
+ *,
317
+ workspace: Path | str | None = None,
318
+ home_dir: Path | str | None = None,
319
+ ) -> GuardActionEnvelope:
320
+ """Normalize any supported Guard harness payload into a typed action envelope."""
321
+
322
+ normalized_harness = harness.strip().lower()
323
+ normalizers = {
324
+ "codex": normalize_codex_hook_payload,
325
+ "claude": normalize_claude_hook_payload,
326
+ "claude-code": normalize_claude_hook_payload,
327
+ "opencode": normalize_opencode_payload,
328
+ "copilot": normalize_copilot_payload,
329
+ "gemini": normalize_gemini_payload,
330
+ }
331
+ normalizer = normalizers.get(normalized_harness)
332
+ if normalizer is None:
333
+ raise ValueError(f"Unsupported Guard harness for action normalization: {harness}")
334
+ normalized_payload = _payload_with_default_event(payload, event_name)
335
+ return normalizer(normalized_payload, workspace=workspace, home_dir=home_dir)
336
+
337
+
338
+ def _normalize_action_payload(
339
+ payload: Mapping[str, object],
340
+ *,
341
+ harness: str,
342
+ default_event_name: str | None,
343
+ workspace: Path | str | None,
344
+ home_dir: Path | str | None,
345
+ ) -> GuardActionEnvelope:
232
346
  normalized_payload = dict(payload)
347
+ if default_event_name is not None:
348
+ normalized_payload = _payload_with_default_event(normalized_payload, default_event_name)
233
349
  event_name = _hook_event_name(normalized_payload)
234
- explicit_tool_name = _string_value(normalized_payload.get("tool_name")) or _string_value(
235
- normalized_payload.get("toolName")
236
- )
350
+ explicit_tool_name = _tool_name_from_payload(normalized_payload)
237
351
  tool_call_name, tool_call_input = _tool_call_from_payload(
238
352
  normalized_payload.get("toolCalls"),
239
353
  expected_tool_name=explicit_tool_name,
@@ -244,10 +358,10 @@ def normalize_codex_hook_payload(
244
358
  tool_input = tool_call_input
245
359
  raw_command = _command_from_payload(tool_input)
246
360
  command = _command_detail(raw_command, home_dir=home_dir)
247
- prompt_text = _prompt_text(normalized_payload.get("prompt"))
361
+ prompt_text = _prompt_text(_prompt_value(normalized_payload))
248
362
  prompt_excerpt = _prompt_excerpt(prompt_text)
249
- mcp_server, mcp_tool = _mcp_parts(tool_name)
250
- action_type = _codex_action_type(
363
+ mcp_server, mcp_tool = _mcp_details(normalized_payload, tool_name)
364
+ action_type = _action_type(
251
365
  event_name=event_name,
252
366
  tool_name=tool_name,
253
367
  command=raw_command,
@@ -266,7 +380,7 @@ def normalize_codex_hook_payload(
266
380
  return GuardActionEnvelope(
267
381
  schema_version=_SCHEMA_VERSION,
268
382
  action_id="",
269
- harness="codex",
383
+ harness=harness,
270
384
  event_name=event_name,
271
385
  action_type=action_type,
272
386
  workspace=workspace_label,
@@ -329,6 +443,26 @@ def _mapping_value(value: object) -> Mapping[str, object]:
329
443
  return {}
330
444
 
331
445
 
446
+ def _payload_with_default_event(payload: Mapping[str, object], event_name: str) -> dict[str, object]:
447
+ normalized_payload = dict(payload)
448
+ if not event_name.strip():
449
+ return normalized_payload
450
+ for key in ("event", "eventName", "hook_event_name", "hookEventName", "hook_name", "hookName"):
451
+ value = normalized_payload.get(key)
452
+ if isinstance(value, str) and value.strip():
453
+ return normalized_payload
454
+ normalized_payload["hook_event_name"] = event_name
455
+ return normalized_payload
456
+
457
+
458
+ def _tool_name_from_payload(payload: Mapping[str, object]) -> str | None:
459
+ for key in ("tool_name", "toolName", "name", "tool"):
460
+ value = _string_value(payload.get(key))
461
+ if value is not None:
462
+ return value
463
+ return None
464
+
465
+
332
466
  def _tool_input_from_payload(payload: Mapping[str, object]) -> Mapping[str, object]:
333
467
  for key in ("tool_input", "toolInput", "toolArgs", "arguments"):
334
468
  parsed = _mapping_from_value(payload.get(key))
@@ -375,7 +509,7 @@ def _mapping_from_value(value: object) -> Mapping[str, object] | None:
375
509
 
376
510
 
377
511
  def _hook_event_name(payload: Mapping[str, object]) -> str:
378
- for key in ("event", "hook_event_name", "hookEventName", "hook_name"):
512
+ for key in ("event", "eventName", "hook_event_name", "hookEventName", "hook_name", "hookName"):
379
513
  value = payload.get(key)
380
514
  if isinstance(value, str) and value.strip():
381
515
  stripped = value.strip()
@@ -383,6 +517,14 @@ def _hook_event_name(payload: Mapping[str, object]) -> str:
383
517
  return "PreToolUse"
384
518
 
385
519
 
520
+ def _prompt_value(payload: Mapping[str, object]) -> object:
521
+ for key in ("prompt", "userPrompt", "user_prompt", "message", "text", "input"):
522
+ value = payload.get(key)
523
+ if isinstance(value, str) and value.strip():
524
+ return value
525
+ return None
526
+
527
+
386
528
  def _command_from_payload(tool_input: Mapping[str, object]) -> str | None:
387
529
  for key in _COMMAND_KEYS:
388
530
  value = tool_input.get(key)
@@ -413,16 +555,70 @@ def _prompt_excerpt(prompt_text: str | None) -> str | None:
413
555
  return prompt_text[:_PROMPT_EXCERPT_LIMIT]
414
556
 
415
557
 
416
- def _mcp_parts(tool_name: str | None) -> tuple[str | None, str | None]:
417
- if tool_name is None or not tool_name.startswith("mcp__"):
558
+ def _mcp_details(payload: Mapping[str, object], tool_name: str | None) -> tuple[str | None, str | None]:
559
+ explicit_server = _string_from_keys(payload, ("mcp_server", "mcpServer", "server", "serverName"))
560
+ explicit_tool = _string_from_keys(payload, ("mcp_tool", "mcpTool"))
561
+ tool_name_value = _string_from_keys(payload, ("tool_name", "toolName"))
562
+ parts_server, parts_tool = _mcp_parts(tool_name, known_servers=_known_mcp_servers(payload))
563
+ server = explicit_server or parts_server
564
+ tool = explicit_tool or parts_tool
565
+ if tool is None and server is not None and tool_name_value is not None:
566
+ tool = tool_name_value
567
+ return server, tool
568
+
569
+
570
+ def _string_from_keys(payload: Mapping[str, object], keys: tuple[str, ...]) -> str | None:
571
+ for key in keys:
572
+ value = _string_value(payload.get(key))
573
+ if value is not None:
574
+ return value
575
+ return None
576
+
577
+
578
+ def _known_mcp_servers(payload: Mapping[str, object]) -> tuple[str, ...]:
579
+ servers: set[str] = set()
580
+ for key in ("mcp_servers", "mcpServers", "servers"):
581
+ value = payload.get(key)
582
+ if isinstance(value, Mapping):
583
+ servers.update(str(server_name).strip() for server_name in value if isinstance(server_name, str))
584
+ elif isinstance(value, list):
585
+ servers.update(item.strip() for item in value if isinstance(item, str) and item.strip())
586
+ return tuple(
587
+ sorted(
588
+ (server for server in servers if server), key=lambda server: len(_mcp_server_token(server)), reverse=True
589
+ )
590
+ )
591
+
592
+
593
+ def _mcp_parts(tool_name: str | None, *, known_servers: tuple[str, ...] = ()) -> tuple[str | None, str | None]:
594
+ if tool_name is None:
595
+ return None, None
596
+ if "/" in tool_name:
597
+ server, tool = tool_name.split("/", 1)
598
+ return (server, tool) if server and tool else (None, None)
599
+ if tool_name.startswith("mcp__"):
600
+ parts = tool_name.split("__", 2)
601
+ if len(parts) == 3 and parts[1] and parts[2]:
602
+ return parts[1], parts[2]
418
603
  return None, None
419
- parts = tool_name.split("__", 2)
420
- if len(parts) != 3 or not parts[1] or not parts[2]:
604
+ if tool_name.startswith("mcp_"):
605
+ suffix = tool_name[len("mcp_") :]
606
+ for server in known_servers:
607
+ server_token = _mcp_server_token(server)
608
+ prefix = f"{server_token}_"
609
+ if suffix.startswith(prefix):
610
+ tool = suffix[len(prefix) :]
611
+ return (server, tool) if tool else (None, None)
421
612
  return None, None
422
- return parts[1], parts[2]
613
+ return None, None
614
+
615
+
616
+ def _mcp_server_token(value: str) -> str:
617
+ token = re.sub(r"[^a-z0-9]+", "_", value.strip().lower())
618
+ return token.strip("_")
423
619
 
424
620
 
425
- def _codex_action_type(
621
+ def _action_type(
426
622
  *,
427
623
  event_name: str,
428
624
  tool_name: str | None,
@@ -573,7 +769,12 @@ def _normalized_command(command: str | None) -> str | None:
573
769
  __all__ = [
574
770
  "GuardActionEnvelope",
575
771
  "GuardActionType",
772
+ "normalize_claude_hook_payload",
576
773
  "normalize_codex_hook_payload",
774
+ "normalize_copilot_payload",
775
+ "normalize_gemini_payload",
776
+ "normalize_harness_payload",
777
+ "normalize_opencode_payload",
577
778
  "redacted_workspace_label",
578
779
  "stable_action_hash",
579
780
  ]
@@ -1,3 +1,3 @@
1
1
  """Single source of truth for tool version."""
2
2
 
3
- __version__ = "2.0.95"
3
+ __version__ = "2.0.96"
@@ -0,0 +1,316 @@
1
+ """Runtime action envelope harness normalizer tests."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ import pytest
8
+
9
+ from codex_plugin_scanner.guard.runtime.actions import (
10
+ normalize_claude_hook_payload,
11
+ normalize_copilot_payload,
12
+ normalize_gemini_payload,
13
+ normalize_harness_payload,
14
+ normalize_opencode_payload,
15
+ )
16
+
17
+
18
+ def test_normalize_claude_pre_tool_read_payload(tmp_path: Path) -> None:
19
+ payload = {
20
+ "hook_event_name": "PreToolUse",
21
+ "tool_name": "Read",
22
+ "tool_input": {"file_path": "~/.npmrc"},
23
+ }
24
+
25
+ envelope = normalize_claude_hook_payload(payload, workspace=tmp_path / "workspace", home_dir=tmp_path)
26
+
27
+ assert envelope.harness == "claude-code"
28
+ assert envelope.event_name == "PreToolUse"
29
+ assert envelope.action_type == "file_read"
30
+ assert envelope.tool_name == "Read"
31
+ assert envelope.target_paths == ("~/.npmrc",)
32
+
33
+
34
+ def test_normalize_claude_user_prompt_submit_payload(tmp_path: Path) -> None:
35
+ payload = {
36
+ "hook_event_name": "UserPromptSubmit",
37
+ "prompt": "Please print ~/.env to debug local setup.",
38
+ }
39
+
40
+ envelope = normalize_claude_hook_payload(payload, workspace=tmp_path / "workspace", home_dir=tmp_path)
41
+
42
+ assert envelope.harness == "claude-code"
43
+ assert envelope.action_type == "prompt"
44
+ assert envelope.prompt_excerpt == "Please print ~/.env to debug local setup."
45
+ assert envelope.target_paths == ("~/.env",)
46
+
47
+
48
+ def test_normalize_claude_pre_tool_bash_payload(tmp_path: Path) -> None:
49
+ payload = {
50
+ "hook_event_name": "PreToolUse",
51
+ "tool_name": "Bash",
52
+ "tool_input": {"command": "cat ~/.npmrc"},
53
+ }
54
+
55
+ envelope = normalize_claude_hook_payload(payload, workspace=tmp_path / "workspace", home_dir=tmp_path)
56
+
57
+ assert envelope.harness == "claude-code"
58
+ assert envelope.action_type == "shell_command"
59
+ assert envelope.command == "cat ~/.npmrc"
60
+ assert envelope.target_paths == ("~/.npmrc",)
61
+
62
+
63
+ def test_normalize_opencode_mcp_payload(tmp_path: Path) -> None:
64
+ payload = {
65
+ "event": "permissionRequest",
66
+ "tool_name": "mcp__guard_lab__inspect",
67
+ "tool_input": {"target": "workspace"},
68
+ }
69
+
70
+ envelope = normalize_opencode_payload(payload, workspace=tmp_path / "workspace", home_dir=tmp_path)
71
+
72
+ assert envelope.harness == "opencode"
73
+ assert envelope.event_name == "PermissionRequest"
74
+ assert envelope.action_type == "mcp_tool"
75
+ assert envelope.mcp_server == "guard_lab"
76
+ assert envelope.mcp_tool == "inspect"
77
+
78
+
79
+ def test_normalize_copilot_autopilot_shell_payload(tmp_path: Path) -> None:
80
+ payload = {
81
+ "eventName": "preToolUse",
82
+ "mode": "Autopilot",
83
+ "toolName": "run_terminal_command",
84
+ "toolInput": {"command": "cat ~/.npmrc"},
85
+ }
86
+
87
+ envelope = normalize_copilot_payload(payload, workspace=tmp_path / "workspace", home_dir=tmp_path)
88
+
89
+ assert envelope.harness == "copilot"
90
+ assert envelope.event_name == "PreToolUse"
91
+ assert envelope.action_type == "shell_command"
92
+ assert envelope.command == "cat ~/.npmrc"
93
+ assert envelope.target_paths == ("~/.npmrc",)
94
+
95
+
96
+ def test_normalize_copilot_hook_name_payload(tmp_path: Path) -> None:
97
+ payload = {
98
+ "hookName": "permissionRequest",
99
+ "toolName": "run_terminal_command",
100
+ "toolInput": {"command": "cat ~/.npmrc"},
101
+ }
102
+
103
+ envelope = normalize_copilot_payload(payload, workspace=tmp_path / "workspace", home_dir=tmp_path)
104
+
105
+ assert envelope.event_name == "PermissionRequest"
106
+ assert envelope.action_type == "shell_command"
107
+
108
+
109
+ def test_normalize_copilot_slash_mcp_payload(tmp_path: Path) -> None:
110
+ payload = {
111
+ "hookName": "preToolUse",
112
+ "toolName": "danger_lab/safe_echo",
113
+ "toolInput": {"message": "hello"},
114
+ }
115
+
116
+ envelope = normalize_copilot_payload(payload, workspace=tmp_path / "workspace", home_dir=tmp_path)
117
+
118
+ assert envelope.action_type == "mcp_tool"
119
+ assert envelope.mcp_server == "danger_lab"
120
+ assert envelope.mcp_tool == "safe_echo"
121
+
122
+
123
+ def test_normalize_copilot_prefixed_mcp_payload(tmp_path: Path) -> None:
124
+ payload = {
125
+ "hookName": "preToolUse",
126
+ "toolName": "mcp_danger_lab_safe_echo",
127
+ "toolInput": {"message": "hello"},
128
+ "mcpServers": {"danger_lab": {}},
129
+ }
130
+
131
+ envelope = normalize_copilot_payload(payload, workspace=tmp_path / "workspace", home_dir=tmp_path)
132
+
133
+ assert envelope.action_type == "mcp_tool"
134
+ assert envelope.mcp_server == "danger_lab"
135
+ assert envelope.mcp_tool == "safe_echo"
136
+
137
+
138
+ def test_normalize_copilot_three_part_prefixed_mcp_payload(tmp_path: Path) -> None:
139
+ payload = {
140
+ "hookName": "preToolUse",
141
+ "toolName": "mcp_guard_lab_inspect",
142
+ "toolInput": {"target": "workspace"},
143
+ "mcpServers": {"guard_lab": {}},
144
+ }
145
+
146
+ envelope = normalize_copilot_payload(payload, workspace=tmp_path / "workspace", home_dir=tmp_path)
147
+
148
+ assert envelope.action_type == "mcp_tool"
149
+ assert envelope.mcp_server == "guard_lab"
150
+ assert envelope.mcp_tool == "inspect"
151
+
152
+
153
+ def test_normalize_copilot_long_server_prefixed_mcp_payload(tmp_path: Path) -> None:
154
+ payload = {
155
+ "hookName": "preToolUse",
156
+ "toolName": "mcp_my_server_name_my_tool",
157
+ "toolInput": {"target": "workspace"},
158
+ "mcpServers": {"my_server_name": {}},
159
+ }
160
+
161
+ envelope = normalize_copilot_payload(payload, workspace=tmp_path / "workspace", home_dir=tmp_path)
162
+
163
+ assert envelope.action_type == "mcp_tool"
164
+ assert envelope.mcp_server == "my_server_name"
165
+ assert envelope.mcp_tool == "my_tool"
166
+
167
+
168
+ def test_normalize_copilot_single_token_tool_prefixed_mcp_payload(tmp_path: Path) -> None:
169
+ payload = {
170
+ "hookName": "preToolUse",
171
+ "toolName": "mcp_guard_team_lab_ping",
172
+ "toolInput": {"target": "workspace"},
173
+ "mcpServers": {"guard_team_lab": {}},
174
+ }
175
+
176
+ envelope = normalize_copilot_payload(payload, workspace=tmp_path / "workspace", home_dir=tmp_path)
177
+
178
+ assert envelope.action_type == "mcp_tool"
179
+ assert envelope.mcp_server == "guard_team_lab"
180
+ assert envelope.mcp_tool == "ping"
181
+
182
+
183
+ def test_normalize_copilot_unknown_prefixed_mcp_payload_stays_untyped(tmp_path: Path) -> None:
184
+ payload = {
185
+ "hookName": "preToolUse",
186
+ "toolName": "mcp_guard_team_lab_ping",
187
+ "toolInput": {"target": "workspace"},
188
+ }
189
+
190
+ envelope = normalize_copilot_payload(payload, workspace=tmp_path / "workspace", home_dir=tmp_path)
191
+
192
+ assert envelope.action_type == "config_change"
193
+ assert envelope.mcp_server is None
194
+ assert envelope.mcp_tool is None
195
+
196
+
197
+ def test_normalize_copilot_tokenized_server_prefixed_mcp_payload(tmp_path: Path) -> None:
198
+ payload = {
199
+ "hookName": "preToolUse",
200
+ "toolName": "mcp_shared_tools_ping",
201
+ "toolInput": {"target": "workspace"},
202
+ "mcpServers": {"shared-tools": {}},
203
+ }
204
+
205
+ envelope = normalize_copilot_payload(payload, workspace=tmp_path / "workspace", home_dir=tmp_path)
206
+
207
+ assert envelope.action_type == "mcp_tool"
208
+ assert envelope.mcp_server == "shared-tools"
209
+ assert envelope.mcp_tool == "ping"
210
+
211
+
212
+ def test_normalize_generic_tool_alias_does_not_set_mcp_tool(tmp_path: Path) -> None:
213
+ payload = {
214
+ "hook_event_name": "PreToolUse",
215
+ "tool": "Read",
216
+ "tool_input": {"path": "~/.npmrc"},
217
+ }
218
+
219
+ envelope = normalize_claude_hook_payload(payload, workspace=tmp_path / "workspace", home_dir=tmp_path)
220
+
221
+ assert envelope.action_type == "file_read"
222
+ assert envelope.tool_name == "Read"
223
+ assert envelope.mcp_server is None
224
+ assert envelope.mcp_tool is None
225
+
226
+
227
+ def test_normalize_harness_payload_uses_default_for_empty_event(tmp_path: Path) -> None:
228
+ payload = {
229
+ "event": "",
230
+ "tool_name": "Bash",
231
+ "tool_input": {"command": "cat ~/.npmrc"},
232
+ }
233
+
234
+ envelope = normalize_harness_payload(
235
+ "claude-code",
236
+ "PermissionRequest",
237
+ payload,
238
+ workspace=tmp_path / "workspace",
239
+ home_dir=tmp_path,
240
+ )
241
+
242
+ assert envelope.event_name == "PermissionRequest"
243
+ assert envelope.action_type == "shell_command"
244
+
245
+
246
+ def test_normalize_opencode_merges_partial_mcp_details(tmp_path: Path) -> None:
247
+ payload = {
248
+ "event": "permissionRequest",
249
+ "server": "guard_lab",
250
+ "toolName": "inspect",
251
+ "toolInput": {"target": "workspace"},
252
+ }
253
+
254
+ envelope = normalize_opencode_payload(payload, workspace=tmp_path / "workspace", home_dir=tmp_path)
255
+
256
+ assert envelope.action_type == "mcp_tool"
257
+ assert envelope.mcp_server == "guard_lab"
258
+ assert envelope.mcp_tool == "inspect"
259
+
260
+
261
+ def test_normalize_opencode_merges_snake_case_partial_mcp_details(tmp_path: Path) -> None:
262
+ payload = {
263
+ "event": "permissionRequest",
264
+ "server": "guard_lab",
265
+ "tool_name": "inspect",
266
+ "tool_input": {"target": "workspace"},
267
+ }
268
+
269
+ envelope = normalize_opencode_payload(payload, workspace=tmp_path / "workspace", home_dir=tmp_path)
270
+
271
+ assert envelope.action_type == "mcp_tool"
272
+ assert envelope.mcp_server == "guard_lab"
273
+ assert envelope.mcp_tool == "inspect"
274
+
275
+
276
+ def test_normalize_gemini_prompt_payload(tmp_path: Path) -> None:
277
+ payload = {
278
+ "event": "prompt",
279
+ "prompt": "Inspect ~/.npmrc, then explain risk.",
280
+ }
281
+
282
+ envelope = normalize_gemini_payload(payload, workspace=tmp_path / "workspace", home_dir=tmp_path)
283
+
284
+ assert envelope.harness == "gemini"
285
+ assert envelope.event_name == "UserPromptSubmit"
286
+ assert envelope.action_type == "prompt"
287
+ assert envelope.prompt_excerpt == "Inspect ~/.npmrc, then explain risk."
288
+ assert envelope.target_paths == ("~/.npmrc",)
289
+
290
+
291
+ def test_normalize_harness_payload_dispatches_supported_harnesses(tmp_path: Path) -> None:
292
+ payload = {"tool_name": "Bash", "tool_input": {"command": "cat ~/.npmrc"}}
293
+
294
+ envelope = normalize_harness_payload(
295
+ "claude-code",
296
+ "PreToolUse",
297
+ payload,
298
+ workspace=tmp_path / "workspace",
299
+ home_dir=tmp_path,
300
+ )
301
+
302
+ assert envelope.harness == "claude-code"
303
+ assert envelope.event_name == "PreToolUse"
304
+ assert envelope.action_type == "shell_command"
305
+ assert envelope.target_paths == ("~/.npmrc",)
306
+
307
+
308
+ def test_normalize_harness_payload_rejects_unknown_harness(tmp_path: Path) -> None:
309
+ with pytest.raises(ValueError, match="Unsupported Guard harness"):
310
+ normalize_harness_payload(
311
+ "unknown-harness",
312
+ "PreToolUse",
313
+ {},
314
+ workspace=tmp_path / "workspace",
315
+ home_dir=tmp_path,
316
+ )