testguard-cli 0.18.3__tar.gz → 0.19.0__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 (546) hide show
  1. testguard_cli-0.19.0/.github/ISSUE_TEMPLATE/config.yml +11 -0
  2. testguard_cli-0.19.0/.github/ISSUE_TEMPLATE/documentation.md +17 -0
  3. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/.github/scripts/install-smoke.mjs +1 -1
  4. testguard_cli-0.19.0/.github/scripts/jules-policy.mjs +269 -0
  5. testguard_cli-0.19.0/.github/scripts/jules-triage.mjs +68 -0
  6. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/.github/scripts/sync-release-version.mjs +34 -0
  7. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/.github/workflows/auto-merge.yml +42 -8
  8. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/.github/workflows/ci.yml +44 -0
  9. testguard_cli-0.19.0/.github/workflows/jules-triage.yml +41 -0
  10. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/.gitignore +5 -0
  11. testguard_cli-0.19.0/.jules/README.md +94 -0
  12. testguard_cli-0.19.0/.jules/RULES.md +100 -0
  13. testguard_cli-0.19.0/.jules/preflight.mjs +75 -0
  14. testguard_cli-0.19.0/.jules/routines/docs-drift.md +37 -0
  15. testguard_cli-0.19.0/.jules/routines/runner-scout.md +55 -0
  16. testguard_cli-0.19.0/.jules/routines/scaffold-hunt.md +60 -0
  17. testguard_cli-0.19.0/.jules/setup.sh +33 -0
  18. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/.pre-commit-hooks.yaml +1 -1
  19. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/AGENTS.md +25 -0
  20. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/CHANGELOG.md +89 -0
  21. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/CONTRIBUTING.md +36 -1
  22. testguard_cli-0.19.0/PKG-INFO +291 -0
  23. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/PRIVACY.md +7 -1
  24. testguard_cli-0.19.0/README.md +267 -0
  25. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/SUPPORT.md +9 -2
  26. testguard_cli-0.19.0/action.yml +137 -0
  27. testguard_cli-0.19.0/docs/README.md +74 -0
  28. testguard_cli-0.19.0/docs/concepts/how-it-works.md +372 -0
  29. testguard_cli-0.19.0/docs/faq.md +124 -0
  30. testguard_cli-0.19.0/docs/glossary.md +199 -0
  31. testguard_cli-0.19.0/docs/guides/ai-agents.md +271 -0
  32. testguard_cli-0.19.0/docs/guides/ci/github-actions.md +312 -0
  33. testguard_cli-0.19.0/docs/guides/ci/gitlab.md +191 -0
  34. testguard_cli-0.19.0/docs/guides/ci/pre-commit.md +119 -0
  35. testguard_cli-0.19.0/docs/guides/existing-projects.md +291 -0
  36. testguard_cli-0.19.0/docs/guides/monorepo.md +239 -0
  37. testguard_cli-0.19.0/docs/guides/new-projects.md +174 -0
  38. testguard_cli-0.19.0/docs/guides/performance.md +266 -0
  39. testguard_cli-0.19.0/docs/guides/python.md +283 -0
  40. testguard_cli-0.19.0/docs/guides/replay.md +209 -0
  41. testguard_cli-0.19.0/docs/guides/writing-claims.md +713 -0
  42. testguard_cli-0.19.0/docs/i18n/es/README.md +297 -0
  43. testguard_cli-0.19.0/docs/i18n/es/quickstart.md +393 -0
  44. testguard_cli-0.19.0/docs/i18n/pt-BR/README.md +290 -0
  45. testguard_cli-0.19.0/docs/i18n/pt-BR/quickstart.md +391 -0
  46. testguard_cli-0.19.0/docs/i18n/zh-CN/README.md +190 -0
  47. testguard_cli-0.19.0/docs/i18n/zh-CN/quickstart.md +333 -0
  48. testguard_cli-0.19.0/docs/installation.md +254 -0
  49. testguard_cli-0.19.0/docs/quickstart.md +381 -0
  50. testguard_cli-0.19.0/docs/reference/artifacts.md +225 -0
  51. testguard_cli-0.19.0/docs/reference/cli.md +560 -0
  52. testguard_cli-0.19.0/docs/reference/configuration.md +324 -0
  53. testguard_cli-0.19.0/docs/reference/languages-and-runners.md +348 -0
  54. testguard_cli-0.19.0/docs/reference/mcp.md +186 -0
  55. testguard_cli-0.19.0/docs/reference/verdicts.md +184 -0
  56. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/docs/testguard-explained.html +1 -1
  57. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/docs/testguard-explained.pdf +0 -0
  58. testguard_cli-0.19.0/docs/troubleshooting.md +226 -0
  59. testguard_cli-0.19.0/docs/upgrade.md +197 -0
  60. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-playwright/package-lock.json +3 -3
  61. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-python/README.md +10 -4
  62. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/package-lock.json +5 -5
  63. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/package.json +1 -1
  64. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/packaging/gitlab/testguard.gitlab-ci.yml +43 -17
  65. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/packaging/homebrew/testguard.rb +2 -2
  66. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/pyproject.toml +2 -2
  67. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/GATE-SEMANTICS.md +72 -24
  68. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/README.md +4 -2
  69. testguard_cli-0.19.0/spec/conformance/examples/status-baseline.json +88 -0
  70. testguard_cli-0.19.0/spec/conformance/invalid/ignore.claim-pattern-not-an-identifier.json +10 -0
  71. testguard_cli-0.19.0/spec/conformance/invalid/ignore.fault-pattern-without-fault-id.json +10 -0
  72. testguard_cli-0.19.0/spec/conformance/invalid/ignore.fingerprint-kind.json +10 -0
  73. testguard_cli-0.19.0/spec/conformance/invalid/status.baseline-and-run.json +108 -0
  74. testguard_cli-0.19.0/spec/conformance/invalid/status.baseline-frozen-and-restamped.json +89 -0
  75. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/schemas.test.mjs +14 -0
  76. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/lib/validate.mjs +22 -1
  77. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/schemas/evidence.schema.json +2 -2
  78. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/schemas/ignore.schema.json +2 -2
  79. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/schemas/status.schema.json +23 -0
  80. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/brief/brief.mjs +18 -0
  81. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/cli.mjs +34 -10
  82. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/commands/admit.mjs +8 -1
  83. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/commands/baseline.mjs +16 -2
  84. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/commands/brief.mjs +2 -2
  85. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/commands/claims.mjs +1 -1
  86. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/commands/concerns.mjs +2 -10
  87. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/commands/gate.mjs +2 -4
  88. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/commands/init.mjs +12 -2
  89. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/commands/probe.mjs +13 -5
  90. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/commands/replay.mjs +9 -2
  91. testguard_cli-0.19.0/src/commands/runner-options.mjs +28 -0
  92. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/commands/sweep.mjs +8 -1
  93. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/gate/changed.mjs +11 -0
  94. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/git.mjs +12 -2
  95. testguard_cli-0.19.0/src/init/init.mjs +349 -0
  96. testguard_cli-0.19.0/src/init/templates/SKILL.md +163 -0
  97. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/probe/attribution.mjs +5 -0
  98. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/probe/contention.mjs +14 -3
  99. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/probe/cost.mjs +7 -1
  100. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/probe/policy-freshness.mjs +5 -1
  101. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/probe/probe.mjs +31 -5
  102. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/probe/runners/python.mjs +19 -5
  103. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/probe/universe.mjs +14 -2
  104. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/replay/replay.mjs +9 -8
  105. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/supply/concerns.mjs +14 -8
  106. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/sweep/sweep.mjs +2 -0
  107. testguard_cli-0.19.0/test/action.test.mjs +129 -0
  108. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/admit.test.mjs +3 -2
  109. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/attribution.test.mjs +9 -0
  110. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/brief.test.mjs +26 -1
  111. testguard_cli-0.19.0/test/ci-evidence-helper.github.test.mjs +64 -0
  112. testguard_cli-0.19.0/test/ci-evidence-helper.gitlab.test.mjs +91 -0
  113. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/claim-selection.test.mjs +2 -1
  114. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/claims-since.test.mjs +2 -1
  115. testguard_cli-0.19.0/test/cli-surface.test.mjs +185 -0
  116. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/contention.test.mjs +20 -0
  117. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/cost.test.mjs +19 -0
  118. testguard_cli-0.19.0/test/docs.test.mjs +191 -0
  119. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/fault-defender-orchestration.test.mjs +2 -1
  120. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/fault-defenders.test.mjs +2 -1
  121. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/fix-input.test.mjs +4 -3
  122. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/fix-inventory.test.mjs +2 -1
  123. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/fix-nested.test.mjs +2 -1
  124. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/fix-paths.test.mjs +2 -1
  125. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/fix-projects.test.mjs +2 -1
  126. testguard_cli-0.19.0/test/fixture-git.test.mjs +48 -0
  127. testguard_cli-0.19.0/test/gitlab-template.test.mjs +187 -0
  128. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/helpers/gate-project.mjs +2 -1
  129. testguard_cli-0.19.0/test/helpers/git.mjs +19 -0
  130. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/helpers/probe-assertions.mjs +2 -1
  131. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/helpers/release-project.mjs +1 -1
  132. testguard_cli-0.19.0/test/helpers/yaml.mjs +13 -0
  133. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/independence.test.mjs +2 -1
  134. testguard_cli-0.19.0/test/init-hook-replace.test.mjs +89 -0
  135. testguard_cli-0.19.0/test/init-ignores.test.mjs +118 -0
  136. testguard_cli-0.19.0/test/init-preflight.test.mjs +72 -0
  137. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/init.test.mjs +26 -5
  138. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/intent-input-cli.test.mjs +2 -1
  139. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/intent-input-plan.test.mjs +2 -1
  140. testguard_cli-0.19.0/test/jules-policy.test.mjs +256 -0
  141. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/mcp-next.test.mjs +2 -1
  142. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/mcp-protocol.test.mjs +2 -1
  143. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/mcp.test.mjs +2 -1
  144. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/native-universe-hash.test.mjs +13 -0
  145. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/negative-control.test.mjs +5 -4
  146. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/origin-policy-cli.test.mjs +2 -1
  147. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/origin-reporting.test.mjs +2 -1
  148. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/policy-freshness.test.mjs +3 -1
  149. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/probe-dirty-admission.test.mjs +2 -1
  150. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/probe-dirty-paths.test.mjs +4 -3
  151. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/probe-error-boundary.test.mjs +2 -1
  152. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/probe-error.test.mjs +2 -1
  153. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/probe-native-manifest.test.mjs +2 -1
  154. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/probe-persistence-signal.test.mjs +2 -1
  155. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/probe-preconditions.test.mjs +2 -1
  156. testguard_cli-0.19.0/test/probe-runner-identity.test.mjs +96 -0
  157. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/probe.fixture.test.mjs +5 -4
  158. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/probe.jest.test.mjs +2 -1
  159. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/probe.node.test.mjs +5 -4
  160. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/probe.playwright.test.mjs +2 -1
  161. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/probe.python.test.mjs +2 -1
  162. testguard_cli-0.19.0/test/python-interpreter.test.mjs +43 -0
  163. testguard_cli-0.19.0/test/python-option-commands.test.mjs +55 -0
  164. testguard_cli-0.19.0/test/python-wrapper.test.mjs +81 -0
  165. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/release-tap.test.mjs +19 -13
  166. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/release-version.test.mjs +14 -0
  167. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/replay-mixed-runners.test.mjs +2 -1
  168. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/replay-no-test-file-left.test.mjs +2 -1
  169. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/replay-python-defenders.test.mjs +2 -1
  170. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/replay-selection.test.mjs +4 -3
  171. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/replay.test.mjs +23 -7
  172. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/resource-timing.test.mjs +5 -2
  173. testguard_cli-0.19.0/test/runner-options.test.mjs +57 -0
  174. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/runner-playwright.test.mjs +2 -1
  175. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/snapshot.test.mjs +2 -1
  176. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/status.test.mjs +3 -2
  177. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/surface.test.mjs +4 -3
  178. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/sweep.fixture.test.mjs +2 -1
  179. testguard_cli-0.19.0/test/worker-policy.test.mjs +38 -0
  180. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/worktree.test.mjs +21 -5
  181. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/testguard.claims.json +1048 -5
  182. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/testguard.cost-budget.json +15 -10
  183. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/testguard.ignore.json +87 -0
  184. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/testguard_cli/wrapper.py +30 -2
  185. testguard_cli-0.18.3/.agents/skills/testguard/SKILL.md +0 -66
  186. testguard_cli-0.18.3/PKG-INFO +0 -1304
  187. testguard_cli-0.18.3/README.md +0 -1280
  188. testguard_cli-0.18.3/action.yml +0 -104
  189. testguard_cli-0.18.3/src/init/init.mjs +0 -254
  190. testguard_cli-0.18.3/test/gitlab-template.test.mjs +0 -47
  191. testguard_cli-0.18.3/test/worker-policy.test.mjs +0 -14
  192. {testguard_cli-0.18.3/src/init/templates → testguard_cli-0.19.0/.agents/skills/testguard}/SKILL.md +0 -0
  193. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/.docguard.json +0 -0
  194. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/.docguardignore +0 -0
  195. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/.gitattributes +0 -0
  196. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  197. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  198. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  199. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/.github/dependabot.yml +0 -0
  200. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/.github/scripts/approve-held-runs.sh +0 -0
  201. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/.github/scripts/benchmark-node-direct.mjs +0 -0
  202. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/.github/scripts/benchmark-node-runner.mjs +0 -0
  203. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/.github/scripts/build-one-pager.mjs +0 -0
  204. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/.github/scripts/check-cost-budget.mjs +0 -0
  205. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/.github/scripts/check-node-fixture.mjs +0 -0
  206. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/.github/scripts/check-python-fixture.mjs +0 -0
  207. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/.github/scripts/publish-homebrew-tap.sh +0 -0
  208. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/.github/workflows/release.yml +0 -0
  209. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/.github/workflows/scheduled-release.yml +0 -0
  210. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/.github/workflows/supply-chain.yml +0 -0
  211. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/.npmignore +0 -0
  212. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/.npmrc +0 -0
  213. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/.pre-commit-config.yaml +0 -0
  214. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/CLAUDE.md +0 -0
  215. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/CODE_OF_CONDUCT.md +0 -0
  216. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/DRIFT-LOG.md +0 -0
  217. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/LICENSE +0 -0
  218. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/SECURITY.md +0 -0
  219. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/bench/README.md +0 -0
  220. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/cli/testguard.mjs +0 -0
  221. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/docs-canonical/ARCHITECTURE.md +0 -0
  222. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/docs-canonical/DATA-MODEL.md +0 -0
  223. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/docs-canonical/ENVIRONMENT.md +0 -0
  224. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/docs-canonical/PRIOR-ART.md +0 -0
  225. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/docs-canonical/REQUIREMENTS.md +0 -0
  226. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/docs-canonical/SECURITY.md +0 -0
  227. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/docs-canonical/TEST-SPEC.md +0 -0
  228. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer/.gitignore +0 -0
  229. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer/README.md +0 -0
  230. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer/expected.json +0 -0
  231. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer/package.json +0 -0
  232. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer/src/export.mjs +0 -0
  233. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer/src/redact.mjs +0 -0
  234. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer/test/export-mocked.test.mjs +0 -0
  235. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer/test/flaky.test.mjs +0 -0
  236. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer/test/redact.test.mjs +0 -0
  237. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer/testguard.claims.json +0 -0
  238. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer/vitest.config.mjs +0 -0
  239. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-jest/.gitignore +0 -0
  240. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-jest/README.md +0 -0
  241. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-jest/expected.json +0 -0
  242. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-jest/jest.config.js +0 -0
  243. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-jest/package.json +0 -0
  244. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-jest/src/export.js +0 -0
  245. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-jest/src/redact.js +0 -0
  246. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-jest/test/export-mocked.test.js +0 -0
  247. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-jest/test/flaky.test.js +0 -0
  248. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-jest/test/redact.test.js +0 -0
  249. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-jest/testguard.claims.json +0 -0
  250. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-node/.gitignore +0 -0
  251. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-node/README.md +0 -0
  252. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-node/expected.json +0 -0
  253. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-node/package.json +0 -0
  254. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-node/src/policy.mjs +0 -0
  255. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-node/src/unused.mjs +0 -0
  256. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-node/test/flaky.test.mjs +0 -0
  257. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-node/test/mixed.test.mjs +0 -0
  258. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-node/test/policy.test.mjs +0 -0
  259. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-node/testguard.claims.json +0 -0
  260. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-playwright/.gitignore +0 -0
  261. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-playwright/README.md +0 -0
  262. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-playwright/e2e/flaky.spec.mjs +0 -0
  263. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-playwright/e2e/toggle.spec.mjs +0 -0
  264. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-playwright/expected.json +0 -0
  265. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-playwright/package.json +0 -0
  266. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-playwright/playwright.config.mjs +0 -0
  267. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-playwright/src/toggle.mjs +0 -0
  268. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-playwright/test/toggle.test.mjs +0 -0
  269. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-playwright/testguard.claims.json +0 -0
  270. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-playwright/vitest.config.mjs +0 -0
  271. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-python/.gitignore +0 -0
  272. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-python/demo/__init__.py +0 -0
  273. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-python/demo/export.py +0 -0
  274. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-python/demo/orphan.py +0 -0
  275. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-python/demo/pipeline.py +0 -0
  276. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-python/demo/redact.py +0 -0
  277. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-python/expected.json +0 -0
  278. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-python/testguard.claims.json +0 -0
  279. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-python/tests/test_export_mocked.py +0 -0
  280. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-python/tests/test_flaky.py +0 -0
  281. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-python/tests/test_patched.py +0 -0
  282. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/fixtures/known-answer-python/tests/test_redact.py +0 -0
  283. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/annotations.test.mjs +0 -0
  284. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/authoring-input.test.mjs +0 -0
  285. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/draft-append.test.mjs +0 -0
  286. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/examples/annotations.json +0 -0
  287. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/examples/authoring-input.json +0 -0
  288. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/examples/baseline.json +0 -0
  289. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/examples/brief.json +0 -0
  290. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/examples/calibration-websec.json +0 -0
  291. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/examples/calibration.json +0 -0
  292. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/examples/claims-methods.json +0 -0
  293. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/examples/claims.json +0 -0
  294. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/examples/evidence-disturbed-run.json +0 -0
  295. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/examples/evidence-negative-control.json +0 -0
  296. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/examples/evidence-persistence-signal.json +0 -0
  297. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/examples/evidence-provisional.json +0 -0
  298. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/examples/evidence-python.json +0 -0
  299. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/examples/evidence-resources.json +0 -0
  300. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/examples/evidence-scan-method.json +0 -0
  301. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/examples/evidence.json +0 -0
  302. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/examples/gate.json +0 -0
  303. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/examples/ignore.json +0 -0
  304. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/examples/replay.json +0 -0
  305. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/examples/status-resource-cost.json +0 -0
  306. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/examples/status.json +0 -0
  307. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/baseline.bad-fingerprint-key.json +0 -0
  308. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/baseline.zero-count.json +0 -0
  309. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/brief.text-without-heading.json +0 -0
  310. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/brief.unknown-verdict-key.json +0 -0
  311. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/calibration.backoff-cell-not-wilson.json +0 -0
  312. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/calibration.breakdown-does-not-sum-to-n.json +0 -0
  313. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/calibration.ci-not-wilson.json +0 -0
  314. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/calibration.compound-key-arity-mismatch.json +0 -0
  315. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/calibration.fallback-without-basis.json +0 -0
  316. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/calibration.p-not-positives-over-n.json +0 -0
  317. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/calibration.p-outside-ci.json +0 -0
  318. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/calibration.positives-exceed-n.json +0 -0
  319. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/claims.absolute-path.json +0 -0
  320. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/claims.duplicate-fault-id.json +0 -0
  321. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/claims.fault-injection-without-a-substitution.json +0 -0
  322. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/claims.method-detail-on-a-specified-method.json +0 -0
  323. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/claims.missing-provenance.json +0 -0
  324. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/claims.noop-fault.json +0 -0
  325. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/claims.occurrence-exceeds-hits.json +0 -0
  326. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/claims.parent-traversal.json +0 -0
  327. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/evidence.annotated-signal-without-reason.json +0 -0
  328. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/evidence.attribute-signal-not-a-defender.json +0 -0
  329. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/evidence.attribute-signal-without-attributes.json +0 -0
  330. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/evidence.byrunner-file-not-resolved.json +0 -0
  331. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/evidence.contention-without-runners.json +0 -0
  332. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/evidence.discovered-defender-mocks-subject.json +0 -0
  333. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/evidence.fault-injection-without-its-evidence.json +0 -0
  334. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/evidence.flaky-defender-without-flakiness.json +0 -0
  335. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/evidence.ignored-dirty-on-snapshot.json +0 -0
  336. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/evidence.killed-by-non-assertion.json +0 -0
  337. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/evidence.killed-on-red-baseline.json +0 -0
  338. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/evidence.killed-without-n-runs.json +0 -0
  339. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/evidence.method-detail-on-an-injecting-run.json +0 -0
  340. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/evidence.negative-control-not-reached-but-survived.json +0 -0
  341. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/evidence.negative-control-reached-on-a-kill.json +0 -0
  342. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/evidence.persistence-signal-not-a-defender.json +0 -0
  343. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/evidence.persistence-signal-on-a-non-survivor.json +0 -0
  344. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/evidence.persistence-signal-without-reason.json +0 -0
  345. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/evidence.probe-error-without-message.json +0 -0
  346. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/evidence.provisional-flag-on-confirmed-run.json +0 -0
  347. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/evidence.rank-extra-property.json +0 -0
  348. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/evidence.resource-accounting.json +0 -0
  349. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/evidence.restore-skipped-in-place.json +0 -0
  350. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/evidence.serial-worker-conflict.json +0 -0
  351. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/evidence.single-run-without-provisional.json +0 -0
  352. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/evidence.target-not-imported-but-killed.json +0 -0
  353. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/evidence.undeclared-killers-without-reason.json +0 -0
  354. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/evidence.unknown-verdict.json +0 -0
  355. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/evidence.unverifiable-without-reason.json +0 -0
  356. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/evidence.wrong-fingerprint.json +0 -0
  357. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/gate.counts-disagree.json +0 -0
  358. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/gate.exit-zero-with-uncovered.json +0 -0
  359. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/gate.ignore-cover-without-pattern.json +0 -0
  360. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/ignore.missing-reason.json +0 -0
  361. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/ignore.short-reason.json +0 -0
  362. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/replay.blind-with-a-failing-run.json +0 -0
  363. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/replay.duplicate-patch-id.json +0 -0
  364. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/replay.nocover-when-no-test-file-remained.json +0 -0
  365. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/status.claim-without-file.json +0 -0
  366. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/status.clean-with-new-findings.json +0 -0
  367. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/status.invalid-anchor-hidden.json +0 -0
  368. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/status.partial-scope-counts-disagree.json +0 -0
  369. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/status.resource-accounting.json +0 -0
  370. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/status.surface-counts-disagree.json +0 -0
  371. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/status.unclaimed-hidden-behind-unproven.json +0 -0
  372. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/invalid/status.write-test-without-target.json +0 -0
  373. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/origin-policy.test.mjs +0 -0
  374. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/origin-summaries.test.mjs +0 -0
  375. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/conformance/origins.test.mjs +0 -0
  376. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/lib/authoring-input.mjs +0 -0
  377. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/lib/fingerprint.mjs +0 -0
  378. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/lib/origins.mjs +0 -0
  379. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/lib/wilson.mjs +0 -0
  380. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/schemas/annotations.schema.json +0 -0
  381. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/schemas/authoring-input.schema.json +0 -0
  382. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/schemas/baseline.schema.json +0 -0
  383. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/schemas/brief.schema.json +0 -0
  384. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/schemas/calibration.schema.json +0 -0
  385. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/schemas/claims.schema.json +0 -0
  386. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/schemas/common.schema.json +0 -0
  387. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/schemas/concerns.schema.json +0 -0
  388. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/schemas/gate.schema.json +0 -0
  389. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/schemas/replay.schema.json +0 -0
  390. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/spec/schemas/sweep.schema.json +0 -0
  391. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/admit/admit.mjs +0 -0
  392. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/baseline/baseline.mjs +0 -0
  393. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/claims/anchors.mjs +0 -0
  394. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/claims/annotation-placement.mjs +0 -0
  395. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/claims/annotations.mjs +0 -0
  396. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/claims/load.mjs +0 -0
  397. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/claims/removed.mjs +0 -0
  398. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/command-budget.mjs +0 -0
  399. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/commands/annotations.mjs +0 -0
  400. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/commands/mcp.mjs +0 -0
  401. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/commands/scaffold.mjs +0 -0
  402. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/commands/status.mjs +0 -0
  403. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/evidence/writer.mjs +0 -0
  404. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/mcp/server.mjs +0 -0
  405. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/mcp/tools.mjs +0 -0
  406. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/probe/classify.mjs +0 -0
  407. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/probe/discover.mjs +0 -0
  408. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/probe/imports.mjs +0 -0
  409. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/probe/independence.mjs +0 -0
  410. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/probe/inject.mjs +0 -0
  411. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/probe/mocks.mjs +0 -0
  412. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/probe/origin-policy.mjs +0 -0
  413. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/probe/progress.mjs +0 -0
  414. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/probe/pyimports.mjs +0 -0
  415. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/probe/rank.mjs +0 -0
  416. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/probe/runners/discovery.mjs +0 -0
  417. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/probe/runners/index.mjs +0 -0
  418. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/probe/runners/jest.mjs +0 -0
  419. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/probe/runners/lifecycle.mjs +0 -0
  420. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/probe/runners/node/direct.mjs +0 -0
  421. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/probe/runners/node/entry.mjs +0 -0
  422. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/probe/runners/node/report.mjs +0 -0
  423. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/probe/runners/node/runner.mjs +0 -0
  424. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/probe/runners/node-test.mjs +0 -0
  425. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/probe/runners/playwright.mjs +0 -0
  426. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/probe/runners/python/_testguard_pytest_plugin.py +0 -0
  427. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/probe/runners/python/_testguard_report.py +0 -0
  428. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/probe/runners/python/_testguard_unittest_main.py +0 -0
  429. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/probe/runners/shared.mjs +0 -0
  430. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/probe/runners/vitest.mjs +0 -0
  431. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/probe/worktree.mjs +0 -0
  432. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/render.mjs +0 -0
  433. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/replay/label.mjs +0 -0
  434. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/scaffold/admission.mjs +0 -0
  435. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/scaffold/append.mjs +0 -0
  436. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/scaffold/apply-append.mjs +0 -0
  437. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/scaffold/fix-input.mjs +0 -0
  438. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/scaffold/fix-paths.mjs +0 -0
  439. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/scaffold/intent.mjs +0 -0
  440. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/scaffold/open-append.mjs +0 -0
  441. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/scaffold/plan-append.mjs +0 -0
  442. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/scaffold/plan-intent-input.mjs +0 -0
  443. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/scaffold/prepare-append.mjs +0 -0
  444. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/scaffold/producers.mjs +0 -0
  445. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/scaffold/producers.python.mjs +0 -0
  446. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/scaffold/request.mjs +0 -0
  447. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/scaffold/scaffold.mjs +0 -0
  448. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/status/status.mjs +0 -0
  449. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/status/surface.mjs +0 -0
  450. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/supply/feedback.mjs +0 -0
  451. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/supply/persistence.mjs +0 -0
  452. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/supply/savepath.mjs +0 -0
  453. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/supply/select.mjs +0 -0
  454. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/sweep/pool.mjs +0 -0
  455. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/util/glob.mjs +0 -0
  456. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/src/util/hash.mjs +0 -0
  457. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/admit-decision.test.mjs +0 -0
  458. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/anchor-admission.test.mjs +0 -0
  459. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/annotation-advisory-limits.test.mjs +0 -0
  460. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/annotation-advisory.test.mjs +0 -0
  461. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/annotation-apply.test.mjs +0 -0
  462. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/annotation-cli.test.mjs +0 -0
  463. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/annotation-id-bounds.test.mjs +0 -0
  464. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/annotation-placement.test.mjs +0 -0
  465. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/annotations.test.mjs +0 -0
  466. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/baseline.test.mjs +0 -0
  467. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/calibration.test.mjs +0 -0
  468. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/claim-options.test.mjs +0 -0
  469. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/claims-anchors.test.mjs +0 -0
  470. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/classify.test.mjs +0 -0
  471. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/cli-unexpected-error.test.mjs +0 -0
  472. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/command-budget-write-boundaries.test.mjs +0 -0
  473. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/command-budget.test.mjs +0 -0
  474. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/discover.test.mjs +0 -0
  475. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/discovery-config.test.mjs +0 -0
  476. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/draft-admission.test.mjs +0 -0
  477. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/draft-append-apply.test.mjs +0 -0
  478. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/draft-append-cli.test.mjs +0 -0
  479. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/draft-append-open.test.mjs +0 -0
  480. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/draft-append-plan.test.mjs +0 -0
  481. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/draft-append-prepare.test.mjs +0 -0
  482. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/draft-append.test.mjs +0 -0
  483. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/evidence-persistence-signal.test.mjs +0 -0
  484. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/gate-coverage.test.mjs +0 -0
  485. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/gate-ref.test.mjs +0 -0
  486. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/gate.test.mjs +0 -0
  487. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/glob.test.mjs +0 -0
  488. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/imports.test.mjs +0 -0
  489. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/inject.test.mjs +0 -0
  490. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/intent-authoring.test.mjs +0 -0
  491. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/intent-document.test.mjs +0 -0
  492. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/intent-input-request.test.mjs +0 -0
  493. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/intent-project.test.mjs +0 -0
  494. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/label.test.mjs +0 -0
  495. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/mocks.test.mjs +0 -0
  496. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/native-universe.test.mjs +0 -0
  497. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/negative-control-capabilities.test.mjs +0 -0
  498. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/one-pager.test.mjs +0 -0
  499. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/origin-policy-admission.test.mjs +0 -0
  500. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/origin-policy-projections.test.mjs +0 -0
  501. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/origin-policy-request.test.mjs +0 -0
  502. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/origin-policy.test.mjs +0 -0
  503. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/origin-summary.test.mjs +0 -0
  504. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/probe-control.test.mjs +0 -0
  505. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/probe-isolation.test.mjs +0 -0
  506. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/probe-records.test.mjs +0 -0
  507. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/progress.test.mjs +0 -0
  508. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/pyimports.test.mjs +0 -0
  509. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/rank-aliases.test.mjs +0 -0
  510. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/release-approval.test.mjs +0 -0
  511. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/release-sync.test.mjs +0 -0
  512. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/replay-verdict.test.mjs +0 -0
  513. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/resource-safety.test.mjs +0 -0
  514. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/runner-argv.test.mjs +0 -0
  515. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/runner-command.test.mjs +0 -0
  516. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/runner-discovery.test.mjs +0 -0
  517. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/runner-node-admission.test.mjs +0 -0
  518. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/runner-node-controller.test.mjs +0 -0
  519. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/runner-node-direct.test.mjs +0 -0
  520. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/runner-node-report.test.mjs +0 -0
  521. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/runner-output-bounds.test.mjs +0 -0
  522. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/runner-project-resolution.test.mjs +0 -0
  523. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/runner-python.test.mjs +0 -0
  524. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/runner-report-allocation.test.mjs +0 -0
  525. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/runner-report.test.mjs +0 -0
  526. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/runner-selection-blind.test.mjs +0 -0
  527. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/runner-vitest.test.mjs +0 -0
  528. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/runner-worker-argv.test.mjs +0 -0
  529. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/samples/playwright-report.json +0 -0
  530. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/samples/python-report-collect-error.json +0 -0
  531. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/samples/python-report.json +0 -0
  532. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/scaffold-admitted-source.test.mjs +0 -0
  533. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/scaffold-placeholder.test.mjs +0 -0
  534. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/scaffold.python.test.mjs +0 -0
  535. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/scaffold.test.mjs +0 -0
  536. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/supply-concerns.test.mjs +0 -0
  537. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/supply-feedback.test.mjs +0 -0
  538. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/supply-persistence.test.mjs +0 -0
  539. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/supply-savepath.test.mjs +0 -0
  540. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/supply-select.test.mjs +0 -0
  541. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/sweep-pool.test.mjs +0 -0
  542. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/sweep.test.mjs +0 -0
  543. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/vitest-config.test.mjs +0 -0
  544. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/test/writer.test.mjs +0 -0
  545. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/testguard_cli/__init__.py +0 -0
  546. {testguard_cli-0.18.3 → testguard_cli-0.19.0}/vitest.config.mjs +0 -0
@@ -0,0 +1,11 @@
1
+ blank_issues_enabled: false
2
+ contact_links:
3
+ - name: 📖 Documentation
4
+ url: https://github.com/raccioly/testguard/blob/main/docs/README.md
5
+ about: Quickstart, adoption guides, the CLI reference and troubleshooting.
6
+ - name: 💬 Questions and adoption help
7
+ url: https://github.com/raccioly/testguard/discussions
8
+ about: How to state a claim, read a verdict or fit TestGuard to your stack.
9
+ - name: 🔒 Security vulnerability
10
+ url: https://github.com/raccioly/testguard/security/advisories/new
11
+ about: Report privately. Do not open a public issue; see SECURITY.md.
@@ -0,0 +1,17 @@
1
+ ---
2
+ name: 📖 Documentation
3
+ about: A page that is wrong, missing, unclear or out of date
4
+ title: '[DOCS] '
5
+ labels: documentation
6
+ assignees: ''
7
+ ---
8
+
9
+ ## Which page
10
+
11
+ <!-- A link to the page and heading, e.g. docs/guides/existing-projects.md#4-gate-new-changes-from-today -->
12
+
13
+ ## What is wrong or missing
14
+
15
+ <!-- If the page contradicts what the CLI does, paste the command and its output. -->
16
+
17
+ ## What you were trying to do
@@ -49,7 +49,7 @@ try {
49
49
  const emptyStatus = JSON.parse(run(bin, ['probe', emptyProject, '--allow-empty', '--json'], consumer));
50
50
  if (emptyStatus.state !== 'no-claims' || emptyStatus.run || existsSync(join(emptyProject, '.testguard', 'evidence.json'))) throw new Error('installed empty-adoption probe invented verification evidence');
51
51
  const replayHelp = run(bin, ['replay', '--help'], consumer);
52
- if (!replayHelp.includes('--since') || replayHelp.includes('--node-modules')) throw new Error('installed command help is not command-specific');
52
+ if (!replayHelp.includes('--since') || replayHelp.includes('--allow-empty')) throw new Error('installed command help is not command-specific');
53
53
  writeFileSync(join(emptyProject, 'vite.config.mjs'), "export default { base: '/', plugins: [{ configureServer(s) { s.middlewares.use('/__rooms', () => {}); const parts = '/__room'.split('/'); } }] };\n");
54
54
  const installedDiscovery = join(consumer, 'node_modules', 'testguard-cli', 'src', 'probe', 'runners', 'discovery.mjs');
55
55
  run(process.execPath, ['--input-type=module', '-e',
@@ -0,0 +1,269 @@
1
+ /**
2
+ * Policy for Google Jules pull requests: which routines exist and what each
3
+ * may work on, what is noise, what is a duplicate, when the queue is full,
4
+ * and what may merge without a human.
5
+ *
6
+ * Pure: no network, no filesystem, no clock. Three consumers pass it data:
7
+ * .jules/preflight.mjs — before Jules works (cooperative)
8
+ * .github/workflows/jules-triage.yml — when a Jules PR opens (authoritative)
9
+ * .github/workflows/auto-merge.yml — after CI is green on a Jules PR
10
+ *
11
+ * The default is HOLD. Every merge rule is an allowlist; anything it does not
12
+ * recognise goes to a human. A wrongly held PR costs a click; a wrongly merged
13
+ * one ships through the weekly release to npm and PyPI.
14
+ */
15
+
16
+ export const JULES_LOGIN = 'google-labs-jules[bot]';
17
+
18
+ /** Open Jules PRs that may wait for review before new ones are closed unread. */
19
+ export const QUEUE_CAP = 5;
20
+
21
+ /** A Jules PR closed unmerged this recently blocks the same work being re-proposed. */
22
+ export const DECLINE_WINDOW_DAYS = 60;
23
+
24
+ export const LABELS = Object.freeze({
25
+ noise: 'jules-noise',
26
+ duplicate: 'jules-duplicate',
27
+ declined: 'jules-previously-declined',
28
+ queueFull: 'jules-queue-full',
29
+ review: 'jules-review',
30
+ eligible: 'jules-automerge-eligible',
31
+ });
32
+
33
+ /**
34
+ * Runner and project shapes a field user is likely to have. `runner-scout`
35
+ * builds one per run, outside the repository, and probes it.
36
+ */
37
+ export const RUNNER_SCENARIOS = Object.freeze([
38
+ ['vitest-workspace-projects', 'vitest with a workspace of two projects, defenders in only one of them'],
39
+ ['vitest-ts-path-aliases', 'vitest + TypeScript, imports through tsconfig "paths" aliases that extend a base config'],
40
+ ['vitest-setup-files', 'vitest with setupFiles that mock a module the claim targets'],
41
+ ['jest-projects', 'jest with a "projects" array, defenders split across both projects'],
42
+ ['jest-esm', 'jest running native ESM via --experimental-vm-modules'],
43
+ ['jest-ts-jest', 'jest + ts-jest transforming TypeScript sources'],
44
+ ['playwright-custom-testdir', 'Playwright with a non-default testDir beside vitest unit tests'],
45
+ ['pytest-conftest-fixtures', 'pytest with conftest.py fixtures that import the target module'],
46
+ ['pytest-src-layout', 'pytest on a src/ layout package installed in editable mode'],
47
+ ['unittest-nested-discovery', 'stdlib unittest discovery across nested packages'],
48
+ ['node-test-native', 'node:test with no test dependency installed'],
49
+ ['npm-workspaces-monorepo', 'npm workspaces monorepo, probing one package from the repository root'],
50
+ ]);
51
+ export const SCENARIO_IDS = Object.freeze(RUNNER_SCENARIOS.map(([id]) => id));
52
+
53
+ export const ROUTINES = Object.freeze(['scaffold-hunt', 'runner-scout', 'docs-drift']);
54
+
55
+ export const isJules = (login) => String(login ?? '').toLowerCase().replace(/^app\//, '') === JULES_LOGIN
56
+ || String(login ?? '').toLowerCase() === 'google-labs-jules';
57
+
58
+ /** `[jules:scaffold-hunt] src/git.mjs — two survivors defended` → { routine, target } */
59
+ export function parseTag(title) {
60
+ const m = /^\[jules:([a-z0-9-]+)\]\s+(\S+)/.exec(String(title ?? '').trim());
61
+ return m && ROUTINES.includes(m[1]) ? { routine: m[1], target: m[2] } : null;
62
+ }
63
+
64
+ export const tagTitle = (routine, target) => `[jules:${routine}] ${target}`;
65
+
66
+ const names = (files) => files.map((f) => f.filename ?? f);
67
+
68
+ /** Bookkeeping files that do not make two changes different. */
69
+ const BOOKKEEPING = new Set(['CHANGELOG.md']);
70
+
71
+ /** The file set that identifies a change. Bots word one change many ways; the files do not move. */
72
+ export const dupKey = (filenames) => [...new Set(filenames)].filter((f) => !BOOKKEEPING.has(f)).sort().join('\n');
73
+
74
+ const PERSONA = /(?:^|\s|\])(?:🛡️|⚡|🎨)|\b(?:sentinel|bolt|palette)\s*:/i;
75
+ const NO_OP = /\b(?:no (?:changes?|action|issues?|updates?) (?:needed|required|found)|(?:already|is|are) up[- ]to[- ]date|nothing to (?:do|change|fix|update)|verif(?:y|ied|ying) (?:test )?stability)\b/i;
76
+ const COUNT_SYNC = /\b(?:sync|update|bump|refresh|fix)\b.{0,40}\b(?:test )?(?:counts?|metrics?|numbers?|totals?)\b/i;
77
+
78
+ /**
79
+ * Classes that are reliably regenerated noise. Returns a reason, or null.
80
+ * Each one cost a sibling project dozens of PRs before it had a name.
81
+ */
82
+ export function noiseReason({ title, files }) {
83
+ const changed = names(files);
84
+ if (changed.length === 0) return 'it changes no files: a status report is a task summary, not a pull request';
85
+ if (PERSONA.test(title ?? '')) return 'it comes from a built-in Jules persona (Sentinel, Bolt, Palette), which is not enabled for this repository';
86
+ if (NO_OP.test(title ?? '')) return 'its title reports that nothing needed changing';
87
+ if (changed.every((n) => n.startsWith('.jules/'))) return 'it changes only `.jules/`, the routines that steer Jules; those change through a maintainer';
88
+ if (COUNT_SYNC.test(title ?? '') && changed.every(isDocPath)) return 'it syncs a count or metric in prose; the docs checks in CI own that, and a count changes with every merge';
89
+ return null;
90
+ }
91
+
92
+ const daysBetween = (a, b) => Math.abs(Date.parse(a) - Date.parse(b)) / 86_400_000;
93
+
94
+ /** The same work: the same routine target, or the same changed-file set. */
95
+ function sameWork(a, b) {
96
+ const ta = parseTag(a.title), tb = parseTag(b.title);
97
+ if (ta && tb && ta.routine === tb.routine && ta.target === tb.target) return true;
98
+ const ka = dupKey(names(a.files)), kb = dupKey(names(b.files));
99
+ return ka !== '' && ka === kb;
100
+ }
101
+
102
+ const labelled = (pr, name) => (pr.labels ?? []).some((l) => (l.name ?? l) === name);
103
+
104
+ /**
105
+ * Decide what happens to a Jules PR the moment it opens.
106
+ *
107
+ * `pr` { number, title, files: [{ filename, status }] }
108
+ * `open` other OPEN Jules PRs, each { number, title, files: [names], labels }
109
+ * `closed` recently closed Jules PRs, each { number, title, files, labels, mergedAt, closedAt }
110
+ * `now` ISO timestamp (passed in: the policy reads no clock)
111
+ *
112
+ * → { action: 'close' | 'keep', label, reason, ref? }
113
+ */
114
+ export function triage({ pr, open, closed, now }) {
115
+ const noise = noiseReason({ title: pr.title, files: pr.files });
116
+ if (noise) return { action: 'close', label: LABELS.noise, reason: noise };
117
+
118
+ const others = open.filter((o) => o.number !== pr.number);
119
+ const older = others.filter((o) => o.number < pr.number && sameWork(o, pr)).sort((a, b) => a.number - b.number)[0];
120
+ if (older) return { action: 'close', label: LABELS.duplicate, ref: older.number, reason: `it is the same work as #${older.number}, which is still open` };
121
+
122
+ const declined = closed
123
+ .filter((c) => !c.mergedAt && c.closedAt && daysBetween(c.closedAt, now) <= DECLINE_WINDOW_DAYS)
124
+ .filter((c) => !labelled(c, LABELS.queueFull))
125
+ .filter((c) => sameWork(c, pr))
126
+ .sort((a, b) => b.number - a.number)[0];
127
+ if (declined) return { action: 'close', label: LABELS.declined, ref: declined.number, reason: `the same work was closed without merging in #${declined.number} within the last ${DECLINE_WINDOW_DAYS} days` };
128
+
129
+ const waiting = others.filter((o) => !labelled(o, LABELS.queueFull)).length;
130
+ if (waiting >= QUEUE_CAP) return { action: 'close', label: LABELS.queueFull, reason: `${waiting} Jules PRs are already waiting for review (cap ${QUEUE_CAP}); the work is not lost, the routine proposes it again once the queue drains` };
131
+
132
+ // A preview: the claims contents are compared only once CI is green, by auto-merge.
133
+ const { verdict, reason } = mergeVerdict({ files: pr.files, claims: null, preview: true });
134
+ return verdict === 'merge'
135
+ ? { action: 'keep', label: LABELS.eligible, reason }
136
+ : { action: 'keep', label: LABELS.review, reason };
137
+ }
138
+
139
+ /** Prose a bot may change without a human: the user docs, not the rules. */
140
+ export function isDocPath(f) {
141
+ return f === 'README.md' || (f.startsWith('docs/') && f.endsWith('.md'));
142
+ }
143
+
144
+ /** A new test module, outside the shared helpers every other test imports. */
145
+ const isNewTestPath = (f) => /^test\/(?!helpers\/)[^]*\.test\.mjs$/.test(f);
146
+
147
+ const MAX_FILES = 25;
148
+
149
+ const canonical = (v) => Array.isArray(v) ? v.map(canonical)
150
+ : v && typeof v === 'object' ? Object.fromEntries(Object.keys(v).sort().map((k) => [k, canonical(v[k])]))
151
+ : v;
152
+ const same = (a, b) => JSON.stringify(canonical(a)) === JSON.stringify(canonical(b));
153
+ const without = (o, key) => { const { [key]: _drop, ...rest } = o; return rest; };
154
+
155
+ /**
156
+ * True when `head` differs from `base` only by defenders appended to
157
+ * `defendedBy` lists (claim- or fault-level), each one a test this PR adds.
158
+ * Anything else in the claims file is the contract and needs a human.
159
+ */
160
+ export function claimsOnlyGainDefenders(base, head, addedTests) {
161
+ if (!base || !head || !same(without(base, 'claims'), without(head, 'claims'))) return false;
162
+ if (base.claims.length !== head.claims.length) return false;
163
+ const added = new Set(addedTests);
164
+ const grew = (b = [], h = []) => b.every((x, i) => h[i] === x) && h.slice(b.length).every((x) => added.has(x));
165
+ return base.claims.every((bc, i) => {
166
+ const hc = head.claims[i];
167
+ if (bc.id !== hc.id || bc.faults.length !== hc.faults.length) return false;
168
+ if (!same(without(without(bc, 'defendedBy'), 'faults'), without(without(hc, 'defendedBy'), 'faults'))) return false;
169
+ if (!grew(bc.defendedBy, hc.defendedBy)) return false;
170
+ return bc.faults.every((bf, j) => {
171
+ const hf = hc.faults[j];
172
+ return same(without(bf, 'defendedBy'), without(hf, 'defendedBy')) && grew(bf.defendedBy, hf.defendedBy);
173
+ });
174
+ });
175
+ }
176
+
177
+ /**
178
+ * May a green Jules PR merge without a human?
179
+ *
180
+ * `files` the immutable base...head comparison: { filename, status, previous_filename? }
181
+ * `claims` { base, head } parsed testguard.claims.json when the PR changes it, else null
182
+ *
183
+ * Merges: user docs; new test modules; and defenders for those new tests
184
+ * appended to existing claims (a new test file must defend a claim or the
185
+ * gate fails it). Never: src/, spec/, fixtures/, .github/, .jules/, packaging,
186
+ * instruction files, a removed or renamed file, an edited test, a claim or
187
+ * fault that changed.
188
+ */
189
+ export function mergeVerdict({ files, claims, preview = false }) {
190
+ if (!files.length) return { verdict: 'hold', reason: 'no files changed' };
191
+ if (files.length > MAX_FILES) return { verdict: 'hold', reason: `${files.length} files changed (limit ${MAX_FILES}): a refactor in disguise` };
192
+
193
+ const addedTests = [];
194
+ let claimsFile = false;
195
+ let substance = 0;
196
+ for (const f of files) {
197
+ const name = f.filename;
198
+ if (f.previous_filename || !['added', 'modified'].includes(f.status)) return { verdict: 'hold', reason: `${name} was ${f.status}: removing or renaming a file needs a human` };
199
+ if (name === 'testguard.claims.json') { claimsFile = true; continue; }
200
+ if (BOOKKEEPING.has(name)) continue;
201
+ if (isDocPath(name)) { substance++; continue; }
202
+ if (isNewTestPath(name) && f.status === 'added') { addedTests.push(name); substance++; continue; }
203
+ if (isNewTestPath(name)) return { verdict: 'hold', reason: `${name} edits an existing test: an assertion that changed is a change to what the suite defends` };
204
+ return { verdict: 'hold', reason: `${name} is outside what a bot may merge alone (user docs, new tests, defenders for them)` };
205
+ }
206
+ if (!substance) return { verdict: 'hold', reason: 'only bookkeeping changed' };
207
+ if (claimsFile && !preview) {
208
+ if (!claims) return { verdict: 'hold', reason: 'testguard.claims.json changed and its contents were not compared' };
209
+ if (!claimsOnlyGainDefenders(claims.base, claims.head, addedTests)) {
210
+ return { verdict: 'hold', reason: 'testguard.claims.json changes more than defenders for the tests this PR adds: a claim or fault changed, which is the contract' };
211
+ }
212
+ }
213
+ const parts = [addedTests.length && `${addedTests.length} new test file(s)`, claimsFile && 'their defenders', files.some((f) => isDocPath(f.filename)) && 'user docs'].filter(Boolean);
214
+ return { verdict: 'merge', reason: parts.join(', ') };
215
+ }
216
+
217
+ /** ISO-8601 week number, so a weekly routine walks its targets in a fixed order. */
218
+ export function isoWeek(iso) {
219
+ const d = new Date(Date.parse(iso));
220
+ const t = Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate());
221
+ const day = (new Date(t).getUTCDay() + 6) % 7;
222
+ const thursday = t - day * 86_400_000 + 3 * 86_400_000;
223
+ const yearStart = Date.UTC(new Date(thursday).getUTCFullYear(), 0, 1);
224
+ return 1 + Math.floor((thursday - yearStart) / (7 * 86_400_000));
225
+ }
226
+
227
+ /**
228
+ * The work a routine has been given, or why it has none. Deterministic for a
229
+ * week: the rotation starts at the week number and skips every target that
230
+ * is open, recently declined, or (for the queue) over the cap.
231
+ *
232
+ * → { go: true, target } | { go: false, reason }
233
+ */
234
+ export function pickTarget({ routine, candidates, open, closed, now }) {
235
+ if (!ROUTINES.includes(routine)) return { go: false, reason: `unknown routine "${routine}"; known: ${ROUTINES.join(', ')}` };
236
+ const waiting = open.filter((o) => !labelled(o, LABELS.queueFull)).length;
237
+ if (waiting >= QUEUE_CAP) return { go: false, reason: `the review queue is full: ${waiting} Jules PRs are waiting (cap ${QUEUE_CAP})` };
238
+ if (!candidates.length) return { go: false, reason: 'the routine has no candidate targets' };
239
+ const taken = new Set([
240
+ ...open,
241
+ ...closed.filter((c) => !c.mergedAt && c.closedAt && daysBetween(c.closedAt, now) <= DECLINE_WINDOW_DAYS && !labelled(c, LABELS.queueFull)),
242
+ ].map((p) => parseTag(p.title)).filter((t) => t?.routine === routine).map((t) => t.target));
243
+ const start = isoWeek(now) % candidates.length;
244
+ for (let i = 0; i < candidates.length; i++) {
245
+ const target = candidates[(start + i) % candidates.length];
246
+ if (!taken.has(target)) return { go: true, target };
247
+ }
248
+ return { go: false, reason: `every ${routine} target is already open or was declined in the last ${DECLINE_WINDOW_DAYS} days` };
249
+ }
250
+
251
+ /**
252
+ * scaffold-hunt targets: claimed source modules, thinnest first (fewest
253
+ * faults), so the files the claims say least about are hunted first.
254
+ */
255
+ export function scaffoldTargets(claimsDoc) {
256
+ const faults = new Map();
257
+ for (const claim of claimsDoc.claims) {
258
+ for (const fault of claim.faults) {
259
+ if (!/^src\/.*\.mjs$/.test(fault.file)) continue;
260
+ faults.set(fault.file, (faults.get(fault.file) ?? 0) + 1);
261
+ }
262
+ }
263
+ return [...faults].sort((a, b) => a[1] - b[1] || a[0].localeCompare(b[0])).map(([file]) => file);
264
+ }
265
+
266
+ /** docs-drift targets: the subcommands `testguard --help` lists, in order. */
267
+ export function helpSubcommands(helpText) {
268
+ return [...new Set([...String(helpText).matchAll(/^\s+testguard\s+([a-z][a-z-]*)\b/gm)].map((m) => m[1]))];
269
+ }
@@ -0,0 +1,68 @@
1
+ /**
2
+ * API glue for .github/workflows/jules-triage.yml. Every decision is made by
3
+ * jules-policy.mjs; this only gathers its inputs and carries out its answer.
4
+ *
5
+ * Runs under pull_request_target (write scope) from the default branch. It
6
+ * reads PR metadata through the API and never checks out or executes PR code.
7
+ *
8
+ * Comments never mention the agent by handle: with Jules' Reactive Mode on,
9
+ * a mention is an instruction, and a closed PR is not one to act on.
10
+ */
11
+ import { DECLINE_WINDOW_DAYS, LABELS, isJules, triage } from './jules-policy.mjs';
12
+
13
+ const MARKER = '<!-- testguard-jules:triage -->';
14
+ const COLORS = {
15
+ [LABELS.noise]: 'CCCCCC', [LABELS.duplicate]: 'CCCCCC', [LABELS.declined]: 'CCCCCC',
16
+ [LABELS.queueFull]: 'FBCA04', [LABELS.review]: 'B60205', [LABELS.eligible]: '0E8A16',
17
+ };
18
+
19
+ export async function run({ github, context, core }) {
20
+ const { owner, repo } = context.repo;
21
+ const number = context.payload.pull_request.number;
22
+ const { data: pr } = await github.rest.pulls.get({ owner, repo, pull_number: number });
23
+ if (!isJules(pr.user.login) || pr.state !== 'open') { core.info(`#${number}: not an open Jules PR`); return; }
24
+
25
+ const filesOf = async (n) => (await github.paginate(github.rest.pulls.listFiles, { owner, repo, pull_number: n, per_page: 100 }))
26
+ .map((f) => ({ filename: f.filename, status: f.status }));
27
+ const shape = async (p) => ({
28
+ number: p.number, title: p.title, labels: p.labels, mergedAt: p.merged_at, closedAt: p.closed_at,
29
+ files: (await filesOf(p.number)).map((f) => f.filename),
30
+ });
31
+
32
+ const now = new Date().toISOString();
33
+ const openJules = (await github.paginate(github.rest.pulls.list, { owner, repo, state: 'open', per_page: 100 }))
34
+ .filter((p) => isJules(p.user.login) && p.number !== number);
35
+ const cutoff = Date.now() - DECLINE_WINDOW_DAYS * 86_400_000;
36
+ const { data: recent } = await github.rest.pulls.list({ owner, repo, state: 'closed', sort: 'updated', direction: 'desc', per_page: 100 });
37
+ const closedJules = recent.filter((p) => isJules(p.user.login) && p.closed_at && Date.parse(p.closed_at) >= cutoff);
38
+
39
+ const decision = triage({
40
+ pr: { number, title: pr.title, files: await filesOf(number) },
41
+ open: await Promise.all(openJules.map(shape)),
42
+ closed: await Promise.all(closedJules.map(shape)),
43
+ now,
44
+ });
45
+ core.info(`#${number}: ${decision.action} (${decision.label}) — ${decision.reason}`);
46
+
47
+ await github.rest.issues.createLabel({ owner, repo, name: decision.label, color: COLORS[decision.label] ?? 'CCCCCC' }).catch(() => {});
48
+ await github.rest.issues.addLabels({ owner, repo, issue_number: number, labels: [decision.label] });
49
+
50
+ if (decision.action === 'close') {
51
+ await github.rest.issues.createComment({
52
+ owner, repo, issue_number: number,
53
+ body: `${MARKER}\n**Closed automatically:** ${decision.reason}.\n\n`
54
+ + 'Jules PRs on this repository follow the policy in `.github/scripts/jules-policy.mjs` and the '
55
+ + '**Automated agents** section of `AGENTS.md`: one PR per routine target, a target is not re-proposed '
56
+ + 'while it is open or for 60 days after it was declined, and at most five wait for review at once. '
57
+ + 'A maintainer can reopen this if the policy got it wrong.',
58
+ });
59
+ await github.rest.pulls.update({ owner, repo, pull_number: number, state: 'closed' });
60
+ return;
61
+ }
62
+ await github.rest.issues.createComment({
63
+ owner, repo, issue_number: number,
64
+ body: decision.label === LABELS.eligible
65
+ ? `${MARKER}\n**Eligible to merge on green CI** — ${decision.reason}. The auto-merge workflow re-checks the exact head and the claims file before it merges.`
66
+ : `${MARKER}\n**Needs a maintainer** — ${decision.reason}. CI still runs; nothing merges without a human.`,
67
+ });
68
+ }
@@ -8,6 +8,8 @@
8
8
  * from the PUBLISHED tarball (release.yml, after npm)
9
9
  * - packaging/gitlab/*.yml the include URL tag and the `version` input default
10
10
  * - README.md the GitHub Action reference AND the GitLab include URL
11
+ * - .pre-commit-hooks.yaml the `rev:` in the consumer example
12
+ * - docs/** (DOC_SURFACES) every docs page that shows a pinned install
11
13
  *
12
14
  * A file may carry more than one form, and each needs its own entry: README
13
15
  * holds both the `raccioly/testguard@vX.Y.Z` action reference and the
@@ -102,8 +104,40 @@ const surfaces = [
102
104
  // Sync the two separately and they disagree — the failure that is worse than
103
105
  // being stale, because the document then contradicts itself.
104
106
  ['docs/testguard-explained.html', /(Technical Brief · v)\d+\.\d+\.\d+( · MIT)/, `$1${version}$2`],
107
+ // The consumer-facing example at the top of the hooks manifest. It read
108
+ // v0.1.0 for eighteen releases because nothing looked at it.
109
+ ['.pre-commit-hooks.yaml', /(raccioly\/testguard\n#\s+rev: )v\d+\.\d+\.\d+/, `$1v${version}`],
105
110
  ];
106
111
 
112
+ /**
113
+ * The docs pages that show a pinned install. Each may carry any of the pin
114
+ * forms below but must carry at least one: a page registered here that pins
115
+ * nothing is a page someone moved the pin out of, and the list is stale.
116
+ * A pin in a page NOT listed here fails test/release-version.test.mjs, which
117
+ * finds pins by content — the README lesson above, applied to docs/.
118
+ */
119
+ const DOC_SURFACES = [
120
+ 'docs/installation.md',
121
+ 'docs/guides/ci/github-actions.md',
122
+ 'docs/guides/ci/gitlab.md',
123
+ 'docs/guides/ci/pre-commit.md',
124
+ 'docs/guides/monorepo.md',
125
+ // Translations of the README carry its install table, pins included.
126
+ 'docs/i18n/pt-BR/README.md',
127
+ 'docs/i18n/es/README.md',
128
+ 'docs/i18n/zh-CN/README.md',
129
+ ];
130
+ const DOC_PIN_FORMS = [
131
+ [/raccioly\/testguard@v\d+\.\d+\.\d+/g, `raccioly/testguard@v${version}`],
132
+ [/testguard\/v\d+\.\d+\.\d+\/packaging/g, `testguard/v${version}/packaging`],
133
+ [/(raccioly\/testguard\n\s*rev: )v\d+\.\d+\.\d+/g, `$1v${version}`],
134
+ ];
135
+ for (const rel of DOC_SURFACES) {
136
+ const forms = DOC_PIN_FORMS.filter(([pattern]) => new RegExp(pattern.source).test(readFileSync(resolve(root, rel), 'utf8')));
137
+ if (!forms.length) throw new Error(`${rel}: registered as a docs version surface but pins no version`);
138
+ for (const [pattern, replacement] of forms) surfaces.push([rel, pattern, replacement]);
139
+ }
140
+
107
141
  if (listSurfaces) {
108
142
  // Derived from the same table the writes use, so the two cannot disagree.
109
143
  for (const rel of [...new Set(surfaces.map(([rel]) => rel))]) console.log(rel);
@@ -1,9 +1,15 @@
1
1
  name: Auto-merge (green bot PRs)
2
2
 
3
3
  # After a successful CI run on a PR, merge it if — and only if — it is a
4
- # non-major dependabot bump or an automated release PR touching release
5
- # surfaces only. Everything else is left for a human. The CI run that
6
- # triggered this is re-verified job by job; a green badge is not trusted.
4
+ # non-major dependabot bump, an automated release PR touching release
5
+ # surfaces only, or a Jules PR that .github/scripts/jules-policy.mjs allows
6
+ # (user docs, new tests, defenders for them). Everything else is left for a
7
+ # human. The CI run that triggered this is re-verified job by job; a green
8
+ # badge is not trusted.
9
+ #
10
+ # SECURITY: workflow_run has write scope. The only checkout is the default
11
+ # branch (for the Jules policy), without persisted credentials; PR code is
12
+ # never checked out or run, and PR contents are read through the API.
7
13
 
8
14
  on:
9
15
  workflow_run:
@@ -20,6 +26,12 @@ jobs:
20
26
  if: github.event.workflow_run.conclusion == 'success' && github.event.workflow_run.event == 'pull_request'
21
27
  runs-on: ubuntu-latest
22
28
  steps:
29
+ - name: Check out the merge policy from the default branch (never the PR head)
30
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
31
+ with:
32
+ ref: ${{ github.event.repository.default_branch }}
33
+ persist-credentials: false
34
+
23
35
  - name: Evaluate and merge if safe
24
36
  uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
25
37
  with:
@@ -30,9 +42,12 @@ jobs:
30
42
  const pr = prs.find((p) => p.state === 'open');
31
43
  if (!pr) { core.info('No open PR for this SHA.'); return; }
32
44
  const num = pr.number, author = pr.user.login;
33
- const hold = async (why) => {
34
- try { await github.rest.issues.addLabels({ owner, repo, issue_number: num, labels: ['needs-review'] }); } catch {}
35
- await github.rest.issues.createComment({ owner, repo, issue_number: num, body: `⏸️ **Held for human review** — ${why}\n\nCI is green, but this falls outside the policy in \`.github/workflows/auto-merge.yml\`.` });
45
+ const hold = async (why, label = 'needs-review') => {
46
+ try { await github.rest.issues.addLabels({ owner, repo, issue_number: num, labels: [label] }); } catch {}
47
+ // One comment per distinct reason: a bot that pushes again must not stack identical holds.
48
+ const body = `<!-- testguard-automerge:hold -->\n⏸️ **Held for human review** — ${why}\n\nCI is green, but this falls outside the policy in \`.github/workflows/auto-merge.yml\`.`;
49
+ const seen = await github.paginate(github.rest.issues.listComments, { owner, repo, issue_number: num, per_page: 100 });
50
+ if (!seen.some((c) => c.body === body)) await github.rest.issues.createComment({ owner, repo, issue_number: num, body });
36
51
  core.notice(`#${num} held: ${why}`);
37
52
  };
38
53
  if (run.head_sha !== pr.head.sha || run.head_branch !== pr.head.ref) { core.info('Run does not match current PR head.'); return; }
@@ -53,7 +68,7 @@ jobs:
53
68
  // three files the bump writes — test/release-sync.test.mjs fails if
54
69
  // a surface is missing, which is how the GitLab template's absence
55
70
  // (it would have held every release PR for review) was found.
56
- const RELEASE_SURFACES = new Set(['package.json', 'package-lock.json', 'pyproject.toml', 'CHANGELOG.md', 'action.yml', 'packaging/homebrew/testguard.rb', 'README.md', 'packaging/gitlab/testguard.gitlab-ci.yml', 'docs/testguard-explained.html', 'docs/testguard-explained.pdf']);
71
+ const RELEASE_SURFACES = new Set(['package.json', 'package-lock.json', 'pyproject.toml', 'CHANGELOG.md', 'action.yml', 'packaging/homebrew/testguard.rb', 'README.md', 'packaging/gitlab/testguard.gitlab-ci.yml', 'docs/testguard-explained.html', 'docs/testguard-explained.pdf', '.pre-commit-hooks.yaml', 'docs/installation.md', 'docs/guides/ci/github-actions.md', 'docs/guides/ci/gitlab.md', 'docs/guides/ci/pre-commit.md', 'docs/guides/monorepo.md', 'docs/i18n/pt-BR/README.md', 'docs/i18n/es/README.md', 'docs/i18n/zh-CN/README.md']);
57
72
 
58
73
  if (author === 'dependabot[bot]') {
59
74
  const m = /from (\d+)\.(\d+)\.\S+ to (\d+)\.(\d+)\./.exec(pr.title || '');
@@ -62,12 +77,31 @@ jobs:
62
77
  } else if (author === 'github-actions[bot]' && /^release: v\d+\.\d+\.\d+/.test(pr.title || '')) {
63
78
  const stray = paths.filter((p) => !RELEASE_SURFACES.has(p));
64
79
  if (stray.length) { await hold(`release PR touches non-release files:\n${stray.map((p) => `- \`${p}\``).join('\n')}`); return; }
80
+ } else if (author === 'google-labs-jules[bot]') {
81
+ const policy = await import(`${process.env.GITHUB_WORKSPACE}/.github/scripts/jules-policy.mjs`);
82
+ if (pr.labels.some((l) => l.name === policy.LABELS.queueFull)) { core.info(`#${num} was closed by triage as queue-full.`); return; }
83
+ // listFiles follows a branch that can move while it is read; the
84
+ // comparison of this exact head against its base cannot.
85
+ const { data: cmp } = await github.rest.repos.compareCommitsWithBasehead({ owner, repo, basehead: `${pr.base.sha}...${run.head_sha}` });
86
+ let claims = null;
87
+ if (cmp.files.some((f) => f.filename === 'testguard.claims.json')) {
88
+ // Raw, not base64: the inline content field stops at 1 MB and this file only grows.
89
+ const read = async (ref) => {
90
+ const { data } = await github.rest.repos.getContent({ owner, repo, path: 'testguard.claims.json', ref, mediaType: { format: 'raw' } });
91
+ return JSON.parse(typeof data === 'string' ? data : Buffer.from(data).toString('utf8'));
92
+ };
93
+ claims = { base: await read(cmp.merge_base_commit.sha), head: await read(run.head_sha) };
94
+ }
95
+ const v = policy.mergeVerdict({ files: cmp.files, claims });
96
+ if (v.verdict !== 'merge') { await hold(v.reason, policy.LABELS.review); return; }
97
+ core.info(`#${num} Jules: ${v.reason} — eligible.`);
65
98
  } else {
66
99
  core.info(`#${num} by ${author} — not a handled bot; leaving alone.`); return;
67
100
  }
68
101
 
69
102
  try {
70
- const { data: merge } = await github.rest.pulls.merge({ owner, repo, pull_number: num, merge_method: 'squash' });
103
+ // Pinned to the verified head: a push after CI ran makes GitHub refuse the merge.
104
+ const { data: merge } = await github.rest.pulls.merge({ owner, repo, pull_number: num, merge_method: 'squash', sha: run.head_sha });
71
105
  if (!merge.merged) throw new Error(merge.message || 'GitHub declined the merge');
72
106
  await github.rest.issues.createComment({ owner, repo, issue_number: num, body: `🤖 **Auto-merged** — the exact CI run passed all three Node legs (20/22/24) within the runtime budget and satisfied \`.github/workflows/auto-merge.yml\`.` });
73
107
  } catch (e) {
@@ -405,6 +405,50 @@ jobs:
405
405
  console.log('playwright fixture verdicts match expected.json:', ev.records.length);
406
406
  "
407
407
 
408
+ # ── Hermeticity: the suite must not depend on the machine's git config.
409
+ # Runners carry none, so a fixture that commits through a global
410
+ # core.hooksPath or commit.gpgsign is green here and red on every
411
+ # machine that has one — 142 tests were. On a sibling project that gap
412
+ # made a coding agent re-propose the same "fix" 27 times, because CI
413
+ # could never reproduce what its sandbox saw. Fixtures pin their own
414
+ # git options (test/helpers/git.mjs); the product code under test
415
+ # still meets this config, which is the point of running it. ──
416
+ hermeticity:
417
+ runs-on: ubuntu-latest
418
+ steps:
419
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
420
+ with:
421
+ fetch-depth: 0
422
+ - uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
423
+ with:
424
+ node-version: 22
425
+ - name: Install
426
+ run: npm ci
427
+ - name: Manufacture a hostile global git config
428
+ shell: bash
429
+ run: |
430
+ H="$RUNNER_TEMP/hostile"
431
+ mkdir -p "$H/hooks"
432
+ for hook in pre-commit prepare-commit-msg commit-msg post-commit pre-push post-checkout; do
433
+ printf '#!/bin/sh\necho "hostile %s hook fired" >&2\nexit 1\n' "$hook" > "$H/hooks/$hook"
434
+ chmod +x "$H/hooks/$hook"
435
+ done
436
+ printf '[core]\n\thooksPath = %s\n[commit]\n\tgpgsign = true\n[tag]\n\tgpgsign = true\n[gpg]\n\tprogram = /usr/bin/false\n[init]\n\tdefaultBranch = trunk\n' "$H/hooks" > "$H/gitconfig"
437
+ echo "GIT_CONFIG_GLOBAL=$H/gitconfig" >> "$GITHUB_ENV"
438
+ # A hostile config that git ignores proves nothing; fail before the suite
439
+ # if an unpinned commit would still succeed under it.
440
+ - name: Prove the config is hostile
441
+ shell: bash
442
+ run: |
443
+ R="$(mktemp -d)"
444
+ git -C "$R" init -q
445
+ if git -C "$R" -c user.name=probe -c user.email=probe@example.invalid commit -q --allow-empty -m probe 2>/dev/null; then
446
+ echo "::error::an unpinned commit succeeded; the hostile config is not in effect"
447
+ exit 1
448
+ fi
449
+ - name: Run tests under the hostile config
450
+ run: npm test
451
+
408
452
  # ── npm publish dry-run on tags ──
409
453
  publish-check:
410
454
  runs-on: ubuntu-latest
@@ -0,0 +1,41 @@
1
+ name: Jules PR triage
2
+
3
+ # Closes Google Jules PRs that are noise, duplicates, previously declined
4
+ # work, or over the review queue cap; labels the rest. Policy:
5
+ # .github/scripts/jules-policy.mjs (pure, unit-tested in
6
+ # test/jules-policy.test.mjs, claimed in testguard.claims.json).
7
+ #
8
+ # SECURITY: pull_request_target carries write scope, so nothing here checks
9
+ # out or runs PR code. The checkout is the default branch, credentials are not
10
+ # persisted, and the PR is read through the API only.
11
+
12
+ on:
13
+ pull_request_target:
14
+ types: [opened, reopened]
15
+
16
+ permissions:
17
+ contents: read
18
+ pull-requests: write
19
+ issues: write
20
+
21
+ concurrency:
22
+ group: jules-triage
23
+ cancel-in-progress: false
24
+
25
+ jobs:
26
+ triage:
27
+ if: github.event.pull_request.user.login == 'google-labs-jules[bot]'
28
+ runs-on: ubuntu-latest
29
+ steps:
30
+ - name: Check out the policy from the default branch (never the PR head)
31
+ uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
32
+ with:
33
+ ref: ${{ github.event.repository.default_branch }}
34
+ persist-credentials: false
35
+
36
+ - name: Triage
37
+ uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
38
+ with:
39
+ script: |
40
+ const { run } = await import(`${process.env.GITHUB_WORKSPACE}/.github/scripts/jules-triage.mjs`);
41
+ await run({ github, context, core });
@@ -11,6 +11,11 @@ coverage/
11
11
  .testguard/gate.json
12
12
  .testguard/ci-self-evidence.json
13
13
  .testguard/scaffold-*.json
14
+ .testguard/replay.json
15
+ .testguard/calibration.json
16
+ .testguard/ci/
17
+ .testguard/sweep.json
18
+ .testguard/sweep-evidence.json
14
19
  # Local agent tooling — not part of the public project
15
20
  .wolf/
16
21
  .claude/