claude-dev-env 4.3.0 → 7.1.0

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 (480) hide show
  1. package/.agents/agents/pr-description-writer.md +2 -2
  2. package/.agents/agents/test_agent_frontmatter.py +2 -2
  3. package/.agents/agents/test_pr_description_writer_style.py +1 -3
  4. package/.agents/skills/e-code-review/SKILL.md +12 -12
  5. package/.agents/skills/e-code-review/preflight-proposal.contract.test.mjs +20 -0
  6. package/.agents/skills/e-code-review/reference/fix.md +15 -17
  7. package/.agents/skills/e-code-review/reference/loop.md +35 -39
  8. package/.agents/skills/e-code-review/reference/mode-contract.test.mjs +2 -1
  9. package/.agents/skills/e-code-review/reference/preflight-proposal.md +44 -9
  10. package/.agents/skills/e-code-review/reference/runner-selection.md +13 -14
  11. package/.agents/skills/issue-tracker/SKILL.md +5 -4
  12. package/.agents/skills/pull-request/SKILL.md +11 -13
  13. package/.agents/skills/pull-request/reference/publication-tasks.md +3 -3
  14. package/.agents/skills/pull-request/scripts/github_pr_command_constants/config/constants.py +9 -1
  15. package/.agents/skills/pull-request/scripts/pull_request.py +35 -44
  16. package/.agents/skills/pull-request/scripts/test_github_pr_command_constants.py +9 -0
  17. package/.agents/skills/pull-request/scripts/test_pull_request.py +35 -3
  18. package/.agents/skills/session-continuity/README.md +189 -0
  19. package/.agents/skills/session-continuity/SKILL.md +114 -0
  20. package/.agents/skills/session-continuity/continuity.mjs +375 -0
  21. package/.agents/skills/session-continuity/continuity.test.mjs +303 -0
  22. package/.agents/skills/skill-builder/SKILL.md +3 -196
  23. package/.agents/skills/syncing-submodules/scripts/test_sync_parent_pointer.py +0 -7
  24. package/.agents/skills/windows-scheduled-task/SKILL.md +153 -0
  25. package/_shared/pr-loop/scripts/pr_loop_shared_constants/terminology_sweep_constants.py +6 -0
  26. package/_shared/pr-loop/scripts/terminology_sweep.py +64 -19
  27. package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +59 -0
  28. package/bin/cde.mjs +36 -8
  29. package/bin/cde.test.mjs +48 -0
  30. package/bin/ever-shipped-skills.mjs +2 -20
  31. package/bin/ever-shipped-skills.test.mjs +8 -0
  32. package/bin/expand_home_directory_tokens.mjs +4 -5
  33. package/bin/expand_home_directory_tokens.test.mjs +128 -0
  34. package/bin/git_hooks_installer.mjs +22 -3
  35. package/bin/git_hooks_installer.test.mjs +159 -5
  36. package/bin/install-session-continuity.mjs +78 -0
  37. package/bin/install-session-continuity.test.mjs +42 -0
  38. package/bin/install.agents-home.test.mjs +15 -13
  39. package/bin/install.codex-rules.test.mjs +5 -2
  40. package/bin/install.cursor-rules.test.mjs +5 -2
  41. package/bin/install.mjs +506 -63
  42. package/bin/install.policy-lint.test.mjs +130 -0
  43. package/bin/install.profiles.test.mjs +10 -0
  44. package/bin/install.prune.test.mjs +286 -37
  45. package/bin/install.pstack-plugin.test.mjs +114 -0
  46. package/bin/install.test.mjs +537 -120
  47. package/bin/install.transaction.test.mjs +3 -3
  48. package/bin/refresh-pstack-plugin-skills.test.mjs +211 -0
  49. package/docs/CODE_RULES.md +1 -1
  50. package/hooks/advisory/pr_done_reminder.py +342 -0
  51. package/hooks/advisory/test_pr_done_reminder.py +249 -0
  52. package/hooks/blocking/bash_post_call_dispatcher.py +53 -6
  53. package/hooks/blocking/code_rules_banned_identifiers.py +3 -0
  54. package/hooks/blocking/code_rules_paired_test.py +22 -15
  55. package/hooks/blocking/precommit_code_rules_gate.py +5 -8
  56. package/hooks/blocking/test_bash_dispatcher_interpreter_starts.py +7 -52
  57. package/hooks/blocking/test_bash_post_call_dispatcher.py +65 -4
  58. package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +0 -69
  59. package/hooks/blocking/test_code_rules_banned_identifiers.py +64 -0
  60. package/hooks/blocking/test_code_rules_enforcer_paired_test.py +36 -29
  61. package/hooks/blocking/test_code_rules_js_resume_task_enumeration.py +4 -4
  62. package/hooks/blocking/test_code_rules_js_returns_object_schemaless.py +5 -5
  63. package/hooks/blocking/test_pre_tool_use_dispatcher.py +43 -342
  64. package/hooks/blocking/test_pre_tool_use_dispatcher_native.py +20 -51
  65. package/hooks/blocking/test_shared_stdin_adoption.py +1 -47
  66. package/hooks/git-hooks/git_hooks_constants/staged_policy_lint.py +12 -0
  67. package/hooks/git-hooks/git_hooks_constants/verification_notice_constants.py +116 -0
  68. package/hooks/git-hooks/post_commit.py +33 -0
  69. package/hooks/git-hooks/pre_commit.py +18 -49
  70. package/hooks/git-hooks/pre_push.py +45 -541
  71. package/hooks/git-hooks/test_installed_policy_lint.py +208 -0
  72. package/hooks/git-hooks/test_native_hook_support.py +129 -0
  73. package/hooks/git-hooks/test_native_policy_commit.py +120 -0
  74. package/hooks/git-hooks/test_post_commit.py +43 -0
  75. package/hooks/git-hooks/test_pre_commit.py +38 -148
  76. package/hooks/git-hooks/test_pre_push.py +48 -1631
  77. package/hooks/git-hooks/test_verification_notice.py +60 -0
  78. package/hooks/git-hooks/test_verification_start.py +173 -0
  79. package/hooks/git-hooks/verification_notice.py +192 -0
  80. package/hooks/git-hooks/verification_notice_context.py +253 -0
  81. package/hooks/git-hooks/verification_notice_state.py +237 -0
  82. package/hooks/git-hooks/verification_start.py +100 -0
  83. package/hooks/hooks.json +1 -120
  84. package/hooks/hooks_constants/banned_identifiers_constants.py +4 -0
  85. package/hooks/hooks_constants/bash_post_call_dispatcher_constants.py +14 -1
  86. package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +1 -15
  87. package/hooks/hooks_constants/post_tool_use_dispatcher_constants.py +0 -4
  88. package/hooks/hooks_constants/pr_done_reminder_constants.py +98 -0
  89. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +10 -93
  90. package/hooks/hooks_constants/stop_dispatcher_constants.py +1 -4
  91. package/hooks/hooks_constants/test_bash_post_call_dispatcher_constants.py +19 -2
  92. package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +3 -66
  93. package/hooks/hooks_constants/test_pr_done_reminder_constants.py +40 -0
  94. package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +46 -201
  95. package/hooks/hooks_constants/test_stop_dispatcher_constants.py +3 -25
  96. package/hooks/validation/test_post_tool_use_dispatcher.py +35 -298
  97. package/hooks/validators/run_all_validators.py +5 -1
  98. package/hooks/validators/security_checks.py +57 -2
  99. package/hooks/validators/test_hook_timing_harness.py +2 -5
  100. package/hooks/validators/test_run_all_validators_mutation_tool_parity.py +3 -11
  101. package/hooks/validators/test_run_all_validators_policy_lint.py +36 -0
  102. package/hooks/validators/test_security_checks.py +57 -0
  103. package/package.json +2 -2
  104. package/rules/confirm-implementation-forks.md +2 -0
  105. package/rules/durable-post-artifacts.md +2 -1
  106. package/rules/git-workflow.md +18 -6
  107. package/rules/pstack-host-mapping.md +48 -0
  108. package/rules/vault-context.md +3 -5
  109. package/rules/verify-runtime-state.md +7 -0
  110. package/scripts/audit-hooks-cli.mjs +5 -0
  111. package/scripts/audit-hooks.mjs +72 -16
  112. package/scripts/automatic_advisory/__init__.py +1 -0
  113. package/scripts/automatic_advisory/checkout.py +51 -0
  114. package/scripts/automatic_advisory/cli.py +334 -0
  115. package/scripts/automatic_advisory/config/__init__.py +1 -0
  116. package/scripts/automatic_advisory/config/constants.py +93 -0
  117. package/scripts/automatic_advisory/config/timing.py +4 -0
  118. package/scripts/automatic_advisory/configuration.py +197 -0
  119. package/scripts/automatic_advisory/execution.py +350 -0
  120. package/scripts/automatic_advisory/git.py +69 -0
  121. package/scripts/automatic_advisory/model.py +107 -0
  122. package/scripts/automatic_advisory/process_host.py +32 -0
  123. package/scripts/automatic_advisory/publisher.py +84 -0
  124. package/scripts/automatic_advisory/runner.py +399 -0
  125. package/scripts/automatic_advisory/state.py +116 -0
  126. package/scripts/automatic_advisory/window_flags.py +41 -0
  127. package/scripts/automatic_advisory/windows_job.py +122 -0
  128. package/scripts/check.ps1 +0 -1
  129. package/{.agents/skills/run-claude-dev-env/driver.mjs → scripts/ci/installer-lifecycle.mjs} +3 -28
  130. package/scripts/ci/installer-lifecycle.test.mjs +26 -0
  131. package/scripts/ci/windows-installer-lifecycle.ps1 +1 -11
  132. package/scripts/codex_compat_materializer.py +99 -13
  133. package/scripts/dev_env_scripts_constants/shared_tree_constants.py +4 -5
  134. package/{.agents/skills/_shared/pr-loop/scripts → scripts}/durable_post_lint.py +35 -43
  135. package/{.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/durable_post_lint_constants.py → scripts/durable_post_lint_config/config/constants.py} +10 -10
  136. package/{.agents/skills/_shared/pr-loop/scripts/test_durable_post_lint_constants.py → scripts/durable_post_lint_config/config/test_constants.py} +14 -1
  137. package/scripts/local_report_cli.py +184 -0
  138. package/scripts/local_report_core.py +247 -0
  139. package/scripts/local_report_publisher.py +12 -0
  140. package/scripts/local_report_validation.py +357 -0
  141. package/scripts/local_verification/__init__.py +23 -0
  142. package/scripts/local_verification/check_runner.py +364 -0
  143. package/scripts/local_verification/check_support.py +99 -0
  144. package/scripts/local_verification/cli.py +158 -0
  145. package/scripts/local_verification/command_runner.py +84 -0
  146. package/scripts/local_verification/config/__init__.py +1 -0
  147. package/scripts/local_verification/config/constants.py +89 -0
  148. package/scripts/local_verification/config/timing.py +1 -0
  149. package/scripts/local_verification/git_state.py +146 -0
  150. package/scripts/local_verification/manifest.py +224 -0
  151. package/scripts/local_verification/model.py +174 -0
  152. package/scripts/local_verification/runner.py +234 -0
  153. package/scripts/policy_lint/adapter_detectors.py +13 -6
  154. package/scripts/policy_lint/adapter_pairing.py +58 -3
  155. package/scripts/policy_lint/adapters.py +7 -4
  156. package/scripts/policy_lint/config/approved_test_pairs.py +323 -0
  157. package/scripts/policy_lint/config/constants.py +5 -0
  158. package/scripts/policy_lint/engine.py +48 -4
  159. package/scripts/policy_lint/model.py +1 -0
  160. package/scripts/policy_lint/registry.py +1 -1
  161. package/scripts/policy_lint/selection.py +2 -1
  162. package/scripts/policy_lint/selection_git.py +2 -0
  163. package/scripts/pr_verification/__init__.py +1 -0
  164. package/scripts/pr_verification/branch_refs.py +114 -0
  165. package/scripts/pr_verification/config/__init__.py +1 -0
  166. package/scripts/pr_verification/config/constants.py +84 -0
  167. package/scripts/pr_verification/config/timing.py +5 -0
  168. package/scripts/pr_verification/github.py +137 -0
  169. package/scripts/pr_verification/github_api.py +376 -0
  170. package/scripts/pr_verification/github_labels.py +88 -0
  171. package/scripts/pr_verification/github_parsing.py +182 -0
  172. package/scripts/pr_verification/github_transport.py +91 -0
  173. package/scripts/pr_verification/lock.py +122 -0
  174. package/scripts/pr_verification/model.py +43 -0
  175. package/scripts/pr_verification/requirements.txt +1 -0
  176. package/scripts/refresh_pstack_plugin_skills.mjs +200 -0
  177. package/scripts/shared_tree_paths.py +32 -3
  178. package/{.agents/skills/_shared/pr-loop/scripts → scripts}/test_durable_post_lint.py +97 -0
  179. package/scripts/test_shared_tree_paths.py +33 -0
  180. package/scripts/tests/test-audit-hooks-cli.mjs +150 -0
  181. package/scripts/tests/test-audit-hooks.mjs +94 -2
  182. package/scripts/tests/test_automatic_advisory.py +376 -0
  183. package/scripts/tests/test_automatic_advisory_job_cleanup.py +71 -0
  184. package/scripts/tests/test_automatic_advisory_polling.py +216 -0
  185. package/scripts/tests/test_automatic_advisory_recovery.py +320 -0
  186. package/scripts/tests/test_automatic_advisory_repairs.py +400 -0
  187. package/scripts/tests/test_automatic_advisory_settings.py +48 -0
  188. package/scripts/tests/test_automatic_advisory_window_flags.py +163 -0
  189. package/scripts/tests/test_closed_pr_label.py +157 -0
  190. package/scripts/tests/test_codex_compat_materializer.py +189 -6
  191. package/scripts/tests/test_github_labels.py +101 -0
  192. package/scripts/tests/test_github_transport.py +125 -0
  193. package/scripts/tests/test_local_report_publisher.py +396 -0
  194. package/scripts/tests/test_local_report_publisher_cli.py +85 -0
  195. package/scripts/tests/test_local_verification.py +348 -0
  196. package/scripts/tests/test_local_verification_cli.py +98 -0
  197. package/scripts/tests/test_policy_lint_rules_archive.py +120 -0
  198. package/scripts/tests/test_policy_lint_rules_engine.py +25 -4
  199. package/scripts/tests/test_policy_lint_rules_installed_loader.py +29 -0
  200. package/scripts/tests/test_policy_lint_rules_pairing.py +163 -0
  201. package/scripts/tests/test_policy_lint_rules_registry.py +227 -13
  202. package/scripts/tests/test_policy_lint_selection.py +25 -2
  203. package/scripts/tests/test_policy_lint_selection_native_index.py +30 -0
  204. package/scripts/tests/test_pr_verification_github.py +392 -0
  205. package/scripts/tests/test_pr_verification_lock.py +168 -0
  206. package/scripts/tests/test_rules.py +3 -7
  207. package/scripts/tests/test_verification_notice.py +347 -0
  208. package/.agents/skills/_shared/pr-loop/.claude/CLAUDE.md +0 -1
  209. package/.agents/skills/_shared/pr-loop/audit-contract.md +0 -5
  210. package/.agents/skills/_shared/pr-loop/audit-reply-template.md +0 -5
  211. package/.agents/skills/_shared/pr-loop/code-rules-gate.md +0 -5
  212. package/.agents/skills/_shared/pr-loop/fix-protocol.md +0 -5
  213. package/.agents/skills/_shared/pr-loop/gh-payloads.md +0 -5
  214. package/.agents/skills/_shared/pr-loop/portable-driver.md +0 -149
  215. package/.agents/skills/_shared/pr-loop/post-audit-thread-contract.md +0 -5
  216. package/.agents/skills/_shared/pr-loop/precatch-rubric.md +0 -5
  217. package/.agents/skills/_shared/pr-loop/preflight-proposal.contract.test.mjs +0 -119
  218. package/.agents/skills/_shared/pr-loop/preflight-proposal.md +0 -5
  219. package/.agents/skills/_shared/pr-loop/prompts/.claude/CLAUDE.md +0 -1
  220. package/.agents/skills/_shared/pr-loop/prompts/AGENTS.md +0 -1
  221. package/.agents/skills/_shared/pr-loop/prompts/pr-consistency-audit.xml +0 -324
  222. package/.agents/skills/_shared/pr-loop/scripts/.claude/CLAUDE.md +0 -1
  223. package/.agents/skills/_shared/pr-loop/scripts/AGENTS.md +0 -1
  224. package/.agents/skills/_shared/pr-loop/scripts/RUNTIME_SCRIPTS.md +0 -29
  225. package/.agents/skills/_shared/pr-loop/scripts/_cli_utils.py +0 -22
  226. package/.agents/skills/_shared/pr-loop/scripts/_path_resolver.py +0 -186
  227. package/.agents/skills/_shared/pr-loop/scripts/_xml_utils.py +0 -20
  228. package/.agents/skills/_shared/pr-loop/scripts/audit_category_schema.py +0 -355
  229. package/.agents/skills/_shared/pr-loop/scripts/build_audit_prompt.py +0 -289
  230. package/.agents/skills/_shared/pr-loop/scripts/build_converge_task_list.py +0 -310
  231. package/.agents/skills/_shared/pr-loop/scripts/build_fix_prompt.py +0 -292
  232. package/.agents/skills/_shared/pr-loop/scripts/init_loop_state.py +0 -134
  233. package/.agents/skills/_shared/pr-loop/scripts/portable_converge_driver.py +0 -1646
  234. package/.agents/skills/_shared/pr-loop/scripts/preflight_worktree.py +0 -392
  235. package/.agents/skills/_shared/pr-loop/scripts/select_converge_pacer.py +0 -215
  236. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/.claude/CLAUDE.md +0 -1
  237. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/AGENTS.md +0 -1
  238. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/__init__.py +0 -0
  239. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/audit_category_schema_constants.py +0 -37
  240. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/converge_task_list_constants.py +0 -56
  241. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/handoff_constants.py +0 -45
  242. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/pacer_constants.py +0 -47
  243. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py +0 -157
  244. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/portable_driver_constants.py +0 -177
  245. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/preflight_constants.py +0 -70
  246. package/.agents/skills/_shared/pr-loop/scripts/teardown_worktrees.py +0 -172
  247. package/.agents/skills/_shared/pr-loop/scripts/test__path_resolver.py +0 -106
  248. package/.agents/skills/_shared/pr-loop/scripts/test_audit_category_schema.py +0 -94
  249. package/.agents/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +0 -499
  250. package/.agents/skills/_shared/pr-loop/scripts/test_build_converge_task_list.py +0 -140
  251. package/.agents/skills/_shared/pr-loop/scripts/test_build_fix_prompt.py +0 -262
  252. package/.agents/skills/_shared/pr-loop/scripts/test_init_loop_state.py +0 -71
  253. package/.agents/skills/_shared/pr-loop/scripts/test_portable_converge_driver.py +0 -1119
  254. package/.agents/skills/_shared/pr-loop/scripts/test_preflight_worktree.py +0 -287
  255. package/.agents/skills/_shared/pr-loop/scripts/test_select_converge_pacer.py +0 -207
  256. package/.agents/skills/_shared/pr-loop/scripts/test_teardown_worktrees.py +0 -59
  257. package/.agents/skills/_shared/pr-loop/scripts/test_write_handoff.py +0 -201
  258. package/.agents/skills/_shared/pr-loop/scripts/write_audit_outcomes.py +0 -182
  259. package/.agents/skills/_shared/pr-loop/scripts/write_fix_outcomes.py +0 -206
  260. package/.agents/skills/_shared/pr-loop/scripts/write_handoff.py +0 -309
  261. package/.agents/skills/_shared/pr-loop/state-schema.md +0 -5
  262. package/.agents/skills/_shared/pr-loop/worker-spawn.md +0 -5
  263. package/.agents/skills/autoconverge/.claude/CLAUDE.md +0 -1
  264. package/.agents/skills/autoconverge/AGENTS.md +0 -1
  265. package/.agents/skills/autoconverge/SKILL.md +0 -431
  266. package/.agents/skills/autoconverge/reference/.claude/CLAUDE.md +0 -1
  267. package/.agents/skills/autoconverge/reference/AGENTS.md +0 -1
  268. package/.agents/skills/autoconverge/reference/closing-report.md +0 -93
  269. package/.agents/skills/autoconverge/reference/convergence-gates.md +0 -31
  270. package/.agents/skills/autoconverge/reference/convergence.md +0 -219
  271. package/.agents/skills/autoconverge/reference/copilot-findings.md +0 -51
  272. package/.agents/skills/autoconverge/reference/gotchas.md +0 -66
  273. package/.agents/skills/autoconverge/reference/headless-safety.md +0 -44
  274. package/.agents/skills/autoconverge/reference/multi-pr.md +0 -128
  275. package/.agents/skills/autoconverge/reference/self-closing-loop.md +0 -84
  276. package/.agents/skills/autoconverge/reference/stop-conditions.md +0 -127
  277. package/.agents/skills/autoconverge/test_portable_pacer_gate.py +0 -54
  278. package/.agents/skills/autoconverge/workflow/.claude/CLAUDE.md +0 -1
  279. package/.agents/skills/autoconverge/workflow/AGENTS.md +0 -1
  280. package/.agents/skills/autoconverge/workflow/aggregate_runs.py +0 -371
  281. package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/.claude/CLAUDE.md +0 -1
  282. package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/AGENTS.md +0 -1
  283. package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/__init__.py +0 -0
  284. package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/render_report_constants.py +0 -250
  285. package/.agents/skills/autoconverge/workflow/converge.clean-audit.test.mjs +0 -665
  286. package/.agents/skills/autoconverge/workflow/converge.codex-gate.test.mjs +0 -472
  287. package/.agents/skills/autoconverge/workflow/converge.contract.test.mjs +0 -64
  288. package/.agents/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +0 -1009
  289. package/.agents/skills/autoconverge/workflow/converge.fix-progress.test.mjs +0 -107
  290. package/.agents/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +0 -33
  291. package/.agents/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +0 -15
  292. package/.agents/skills/autoconverge/workflow/converge.mjs +0 -3132
  293. package/.agents/skills/autoconverge/workflow/converge.p2-advance.test.mjs +0 -202
  294. package/.agents/skills/autoconverge/workflow/converge.path-aware.test.mjs +0 -47
  295. package/.agents/skills/autoconverge/workflow/converge.precatch.test.mjs +0 -152
  296. package/.agents/skills/autoconverge/workflow/converge.run-input.test.mjs +0 -116
  297. package/.agents/skills/autoconverge/workflow/converge_multi.mjs +0 -169
  298. package/.agents/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +0 -173
  299. package/.agents/skills/autoconverge/workflow/convergence_summary.py +0 -110
  300. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a11d903476b803493.jsonl +0 -2
  301. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a26213978adeef6fb.jsonl +0 -2
  302. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a3def0d15ed9d9110.jsonl +0 -2
  303. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a41f41b1b708ee3b7.jsonl +0 -2
  304. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a758b880abecc3ff7.jsonl +0 -2
  305. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a8897b89656b1bd16.jsonl +0 -2
  306. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ab1c2d3e4f5a6b7c8.jsonl +0 -2
  307. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-abd463d744a1437bc.jsonl +0 -2
  308. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ad19d027ae8ee1816.jsonl +0 -2
  309. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/workflows/wf_881252e6-700.json +0 -266
  310. package/.agents/skills/autoconverge/workflow/render_report.py +0 -1024
  311. package/.agents/skills/autoconverge/workflow/test_aggregate_runs.py +0 -134
  312. package/.agents/skills/autoconverge/workflow/test_convergence_summary.py +0 -149
  313. package/.agents/skills/autoconverge/workflow/test_render_report.py +0 -812
  314. package/.agents/skills/hitl/SKILL.md +0 -45
  315. package/.agents/skills/pr-cleanup/SKILL.md +0 -140
  316. package/.agents/skills/pr-cleanup/reference/process-inventory.md +0 -17
  317. package/.agents/skills/pr-cleanup/reference/task-seeds.md +0 -22
  318. package/.agents/skills/pr-name-by-capability/SKILL.md +0 -73
  319. package/.agents/skills/pr-name-by-capability/reference/fetch-commands.md +0 -29
  320. package/.agents/skills/pr-name-by-capability/reference/offense-examples.md +0 -32
  321. package/.agents/skills/pr-name-by-capability/reference/report-template.md +0 -29
  322. package/.agents/skills/pr-name-by-capability/reference/rule-checklist.md +0 -38
  323. package/.agents/skills/pr-name-by-capability/reference/task-seeds.md +0 -10
  324. package/.agents/skills/pr-plain-language-cleanup/SKILL.md +0 -93
  325. package/.agents/skills/pr-refinement/SKILL.md +0 -31
  326. package/.agents/skills/pr-shared-extraction/SKILL.md +0 -177
  327. package/.agents/skills/pr-shared-extraction/reference/examples.md +0 -46
  328. package/.agents/skills/pr-shared-extraction/reference/offense-taxonomy.md +0 -100
  329. package/.agents/skills/pr-small-cl/SKILL.md +0 -52
  330. package/.agents/skills/pr-title-description/SKILL.md +0 -96
  331. package/.agents/skills/prototype/SKILL.md +0 -85
  332. package/.agents/skills/prototype/reference/honest-limitations.md +0 -23
  333. package/.agents/skills/prototype/reference/promotion-tasks.md +0 -23
  334. package/.agents/skills/prototype/scripts/build_sandbox_settings.py +0 -249
  335. package/.agents/skills/prototype/scripts/conftest.py +0 -15
  336. package/.agents/skills/prototype/scripts/launch_sandbox.py +0 -205
  337. package/.agents/skills/prototype/scripts/probe_sandbox_safety.py +0 -311
  338. package/.agents/skills/prototype/scripts/prototype_scripts_constants/__init__.py +0 -1
  339. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/__init__.py +0 -0
  340. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/build_sandbox_settings_constants.py +0 -41
  341. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/launch_sandbox_constants.py +0 -23
  342. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/probe_sandbox_safety_constants.py +0 -45
  343. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/prototype_common_constants.py +0 -10
  344. package/.agents/skills/prototype/scripts/test_build_sandbox_settings.py +0 -275
  345. package/.agents/skills/prototype/scripts/test_launch_sandbox.py +0 -303
  346. package/.agents/skills/prototype/scripts/test_probe_sandbox_safety.py +0 -312
  347. package/.agents/skills/prototype/workflows/promotion.md +0 -25
  348. package/.agents/skills/prototype/workflows/sandbox.md +0 -35
  349. package/.agents/skills/rebase/.claude/CLAUDE.md +0 -1
  350. package/.agents/skills/rebase/AGENTS.md +0 -1
  351. package/.agents/skills/rebase/SKILL.md +0 -178
  352. package/.agents/skills/review-router/SKILL.md +0 -51
  353. package/.agents/skills/review-router/reference/route-policy.json +0 -7
  354. package/.agents/skills/review-router/reference/task-seeds.md +0 -1
  355. package/.agents/skills/review-router/scripts/conftest.py +0 -4
  356. package/.agents/skills/review-router/scripts/review_router.py +0 -50
  357. package/.agents/skills/review-router/scripts/review_router_cli.py +0 -211
  358. package/.agents/skills/review-router/scripts/review_router_constants/__init__.py +0 -1
  359. package/.agents/skills/review-router/scripts/review_router_constants/config/__init__.py +0 -0
  360. package/.agents/skills/review-router/scripts/review_router_constants/config/constants.py +0 -13
  361. package/.agents/skills/review-router/scripts/test_review_router.py +0 -23
  362. package/.agents/skills/review-router/scripts/test_review_router_cli.py +0 -47
  363. package/.agents/skills/review-tier/SKILL.md +0 -56
  364. package/.agents/skills/review-tier/reference/task-seeds.md +0 -1
  365. package/.agents/skills/review-tier/reference/tier-policy.json +0 -9
  366. package/.agents/skills/review-tier/scripts/conftest.py +0 -4
  367. package/.agents/skills/review-tier/scripts/review_tier.py +0 -359
  368. package/.agents/skills/review-tier/scripts/review_tier_constants/__init__.py +0 -1
  369. package/.agents/skills/review-tier/scripts/review_tier_constants/config/__init__.py +0 -0
  370. package/.agents/skills/review-tier/scripts/review_tier_constants/config/constants.py +0 -29
  371. package/.agents/skills/review-tier/scripts/test_generation.py +0 -159
  372. package/.agents/skills/review-tier/scripts/test_review_tier.py +0 -33
  373. package/.agents/skills/run-claude-dev-env/SKILL.md +0 -84
  374. package/.agents/skills/session-log/.claude/CLAUDE.md +0 -1
  375. package/.agents/skills/session-log/AGENTS.md +0 -1
  376. package/.agents/skills/session-log/SKILL.md +0 -170
  377. package/.agents/skills/session-log/templates/frontmatter.md +0 -40
  378. package/.agents/skills/session-tidy/.claude/CLAUDE.md +0 -1
  379. package/.agents/skills/session-tidy/AGENTS.md +0 -1
  380. package/.agents/skills/session-tidy/SKILL.md +0 -191
  381. package/.agents/skills/skill-builder/references/.claude/CLAUDE.md +0 -1
  382. package/.agents/skills/skill-builder/references/AGENTS.md +0 -1
  383. package/.agents/skills/skill-builder/references/delegation-map.md +0 -130
  384. package/.agents/skills/skill-builder/references/description-field.md +0 -111
  385. package/.agents/skills/skill-builder/references/deterministic-elements.md +0 -218
  386. package/.agents/skills/skill-builder/references/progressive-disclosure.md +0 -122
  387. package/.agents/skills/skill-builder/references/self-audit-checklist.md +0 -126
  388. package/.agents/skills/skill-builder/references/skill-modularity.md +0 -111
  389. package/.agents/skills/skill-builder/references/skill-types.md +0 -232
  390. package/.agents/skills/skill-builder/references/thariq-x-post-skills.json +0 -33
  391. package/.agents/skills/skill-builder/templates/.claude/CLAUDE.md +0 -1
  392. package/.agents/skills/skill-builder/templates/AGENTS.md +0 -1
  393. package/.agents/skills/skill-builder/templates/gap-analysis.md +0 -87
  394. package/.agents/skills/skill-builder/workflows/.claude/CLAUDE.md +0 -1
  395. package/.agents/skills/skill-builder/workflows/AGENTS.md +0 -1
  396. package/.agents/skills/skill-builder/workflows/improve-skill.md +0 -136
  397. package/.agents/skills/skill-builder/workflows/new-skill.md +0 -179
  398. package/.agents/skills/skill-builder/workflows/polish-skill.md +0 -139
  399. package/.agents/skills/source-command-sr-loop/SKILL.md +0 -82
  400. package/.agents/skills/update/.claude/CLAUDE.md +0 -1
  401. package/.agents/skills/update/AGENTS.md +0 -1
  402. package/.agents/skills/update/SKILL.md +0 -190
  403. package/_shared/pr-loop/AGENTS.md +0 -1
  404. package/_shared/pr-loop/audit-contract.md +0 -183
  405. package/_shared/pr-loop/audit-reply-template.md +0 -151
  406. package/_shared/pr-loop/code-rules-gate.md +0 -65
  407. package/_shared/pr-loop/fix-protocol.md +0 -60
  408. package/_shared/pr-loop/gh-payloads.md +0 -75
  409. package/_shared/pr-loop/post-audit-thread-contract.md +0 -51
  410. package/_shared/pr-loop/precatch-rubric.md +0 -66
  411. package/_shared/pr-loop/preflight-proposal.md +0 -56
  412. package/_shared/pr-loop/scripts/post_audit_thread.py +0 -1242
  413. package/_shared/pr-loop/scripts/pr_loop_shared_constants/post_audit_thread_constants.py +0 -199
  414. package/_shared/pr-loop/scripts/tests/test_post_audit_thread.py +0 -1115
  415. package/_shared/pr-loop/scripts/tests/test_post_audit_thread_constants.py +0 -129
  416. package/_shared/pr-loop/state-schema.md +0 -45
  417. package/_shared/pr-loop/worker-spawn.md +0 -189
  418. package/hooks/blocking/ask_user_question_shape_blocker.py +0 -83
  419. package/hooks/blocking/bot_mention_comment_blocker.py +0 -76
  420. package/hooks/blocking/claude_md_orphan_file_blocker.py +0 -129
  421. package/hooks/blocking/cursor_cli_python_misfire_blocker.py +0 -108
  422. package/hooks/blocking/destructive_command_blocker.py +0 -2073
  423. package/hooks/blocking/fable_spawn_gate.py +0 -187
  424. package/hooks/blocking/luna_fast_mode_gate.py +0 -160
  425. package/hooks/blocking/nas_ssh_binary_enforcer.py +0 -163
  426. package/hooks/blocking/orchestrator_refresh_reschedule_gate.py +0 -260
  427. package/hooks/blocking/package_inventory_stale_blocker.py +0 -107
  428. package/hooks/blocking/pii_prevention_blocker.py +0 -365
  429. package/hooks/blocking/piped_pytest_blocker.py +0 -1223
  430. package/hooks/blocking/question_to_user_enforcer.py +0 -130
  431. package/hooks/blocking/send_user_file_open_locally_blocker.py +0 -71
  432. package/hooks/blocking/sensitive_file_protector.py +0 -167
  433. package/hooks/blocking/session_edit_stage_gate.py +0 -684
  434. package/hooks/blocking/session_handoff_blocker.py +0 -182
  435. package/hooks/blocking/shell_substitution_blocker.py +0 -350
  436. package/hooks/blocking/test_ask_user_question_shape.py +0 -69
  437. package/hooks/blocking/test_bot_mention_comment_blocker.py +0 -131
  438. package/hooks/blocking/test_claude_md_orphan_file_blocker.py +0 -1277
  439. package/hooks/blocking/test_cursor_cli_python_misfire_blocker.py +0 -208
  440. package/hooks/blocking/test_destructive_command_blocker.py +0 -2024
  441. package/hooks/blocking/test_destructive_command_blocker_deny_mode.py +0 -105
  442. package/hooks/blocking/test_destructive_command_blocker_no_verify.py +0 -122
  443. package/hooks/blocking/test_destructive_command_blocker_patterns.py +0 -133
  444. package/hooks/blocking/test_fable_spawn_gate.py +0 -381
  445. package/hooks/blocking/test_inventory_deadlock_resolution.py +0 -154
  446. package/hooks/blocking/test_luna_fast_mode_gate.py +0 -211
  447. package/hooks/blocking/test_nas_ssh_binary_enforcer.py +0 -225
  448. package/hooks/blocking/test_orchestrator_refresh_reschedule_gate.py +0 -231
  449. package/hooks/blocking/test_package_inventory_stale_blocker.py +0 -382
  450. package/hooks/blocking/test_pii_prevention_blocker.py +0 -1222
  451. package/hooks/blocking/test_pii_prevention_windows_git_path.py +0 -41
  452. package/hooks/blocking/test_pii_write_surface_ephemeral.py +0 -221
  453. package/hooks/blocking/test_piped_pytest_blocker.py +0 -587
  454. package/hooks/blocking/test_precommit_code_rules_gate_native_owner.py +0 -233
  455. package/hooks/blocking/test_question_to_user_enforcer.py +0 -169
  456. package/hooks/blocking/test_send_user_file_open_locally_blocker.py +0 -118
  457. package/hooks/blocking/test_sensitive_file_protector.py +0 -365
  458. package/hooks/blocking/test_session_edit_stage_gate.py +0 -537
  459. package/hooks/blocking/test_session_edit_stage_gate_staging.py +0 -64
  460. package/hooks/blocking/test_session_edit_stage_gate_support.py +0 -204
  461. package/hooks/blocking/test_session_edit_stage_gate_tracker.py +0 -116
  462. package/hooks/blocking/test_session_handoff_blocker.py +0 -318
  463. package/hooks/blocking/test_shell_substitution_blocker.py +0 -298
  464. package/hooks/blocking/test_unscoped_search_blocker.py +0 -102
  465. package/hooks/blocking/test_write_existing_file_blocker.py +0 -122
  466. package/hooks/blocking/tests/test_pii_prevention_blocker.py +0 -259
  467. package/hooks/blocking/unscoped_search_blocker.py +0 -391
  468. package/hooks/blocking/write_existing_file_blocker.py +0 -124
  469. package/hooks/hooks_constants/bot_mention_comment_blocker_constants.py +0 -19
  470. package/hooks/hooks_constants/cursor_cli_python_misfire_blocker_constants.py +0 -79
  471. package/hooks/hooks_constants/fable_spawn_gate_constants.py +0 -61
  472. package/hooks/hooks_constants/luna_fast_mode_gate_constants.py +0 -45
  473. package/hooks/hooks_constants/nas_ssh_binary_enforcer_constants.py +0 -20
  474. package/hooks/hooks_constants/orchestrator_refresh_reschedule_gate_constants.py +0 -57
  475. package/hooks/hooks_constants/send_user_file_open_locally_blocker_constants.py +0 -17
  476. package/hooks/hooks_constants/sensitive_file_protector_constants.py +0 -40
  477. package/hooks/hooks_constants/session_handoff_blocker_constants.py +0 -10
  478. package/hooks/hooks_constants/shell_substitution_blocker_constants.py +0 -80
  479. /package/.agents/skills/{_shared/pr-loop → session-continuity}/AGENTS.md +0 -0
  480. /package/{_shared/pr-loop → .agents/skills/session-continuity}/CLAUDE.md +0 -0
@@ -0,0 +1,89 @@
1
+ from __future__ import annotations
2
+
3
+ import re
4
+ from typing import Literal
5
+
6
+ MANIFEST_VERSION = 1
7
+ MANIFEST_VERSION_KEY = "version"
8
+ MANIFEST_CHECKS_KEY = "checks"
9
+ MANIFEST_EXCLUSIONS_KEY = "exclusions"
10
+ CHECK_ID_KEY = "id"
11
+ CHECK_ARGUMENTS_KEY = "argv"
12
+ CHECK_DIRECTORY_KEY = "cwd"
13
+ CHECK_TIMEOUT_KEY = "timeout_seconds"
14
+ CHECK_MINIMUM_TESTS_KEY = "minimum_tests"
15
+ EXCLUSION_SELECTOR_KEY = "selector"
16
+ EXCLUSION_REASON_KEY = "reason"
17
+ MANIFEST_DIGEST_ALGORITHM = "sha256"
18
+ ALL_MANIFEST_DIGEST_SEPARATORS = (",", ":")
19
+ SUCCESS_EXIT_CODE = 0
20
+ CHECK_FAILED_EXIT_CODE = 1
21
+ INVALID_INPUT_EXIT_CODE = 2
22
+ INCOMPLETE_EXIT_CODE = 3
23
+ UTF8_ENCODING = "utf-8"
24
+ JSON_INDENT_SPACES = 2
25
+ PASSED_STATUS = "passed"
26
+ FAILED_STATUS = "failed"
27
+ INCOMPLETE_STATUS = "incomplete"
28
+ MINIMUM_TESTS_ERROR_KIND = "minimum_tests"
29
+ MISSING_TOOL_ERROR_KIND = "missing_tool"
30
+ TIMEOUT_ERROR_KIND = "timeout"
31
+ CRASH_ERROR_KIND = "crash"
32
+ START_ERROR_KIND = "start_error"
33
+ COLLECTION_ERROR_KIND = "collection_error"
34
+ NONZERO_EXIT_ERROR_KIND = "nonzero_exit"
35
+ SKIPPED_TESTS_ERROR_KIND = "skipped_tests"
36
+ PYTEST_COLLECTION_FLAG = "--collect-only"
37
+ PYTEST_QUIET_FLAG = "-q"
38
+ COLLECTED_TESTS_PATTERN = re.compile(r"(?P<count>\d+) tests? collected")
39
+ SKIPPED_TESTS_PATTERN = re.compile(r"(?P<count>\d+) skipped")
40
+ SAFE_LOG_NAME_PATTERN = re.compile(r"[^A-Za-z0-9_.-]+")
41
+ PYTHON_PLACEHOLDER = "{python}"
42
+ REPOSITORY_PLACEHOLDER = "{repo}"
43
+ BASE_PLACEHOLDER = "{base}"
44
+ CDE_LINT_PLACEHOLDER = "{cde_lint}"
45
+ REPOSITORY_POLICY_PLACEHOLDER = "{repository_policy}"
46
+ LOGS_DIRECTORY_SUFFIX = ".logs"
47
+ STDOUT_LOG_SUFFIX = ".stdout.log"
48
+ STDERR_LOG_SUFFIX = ".stderr.log"
49
+ COLLECTION_STDOUT_LOG_SUFFIX = ".collection.stdout.log"
50
+ COLLECTION_STDERR_LOG_SUFFIX = ".collection.stderr.log"
51
+ REPORT_NEWLINE = "\n"
52
+ GIT_EXECUTABLE = "git"
53
+ ALL_GIT_ROOT_ARGUMENTS = ("rev-parse", "--show-toplevel")
54
+ ALL_GIT_HEAD_ARGUMENTS = ("rev-parse", "--verify", "HEAD")
55
+ ALL_GIT_TREE_ARGUMENTS = ("rev-parse", "--verify", "HEAD^{tree}")
56
+ ALL_GIT_BASE_ARGUMENT_PREFIX = ("rev-parse", "--verify")
57
+ ALL_GIT_STATUS_ARGUMENTS = (
58
+ "status",
59
+ "--porcelain=v1",
60
+ "--untracked-files=all",
61
+ )
62
+ ALL_GIT_CHANGED_FILES_ARGUMENTS = ("diff", "--name-only", "-z")
63
+ ALL_GIT_INDEX_CHANGED_FILES_ARGUMENTS = (
64
+ "diff",
65
+ "--cached",
66
+ "--name-only",
67
+ "-z",
68
+ )
69
+ ALL_GIT_UNTRACKED_FILES_ARGUMENTS = (
70
+ "ls-files",
71
+ "--others",
72
+ "--exclude-standard",
73
+ "-z",
74
+ )
75
+ GIT_COMMIT_OBJECT_SUFFIX = "^{commit}"
76
+ GIT_PATH_SEPARATOR = b"\0"
77
+ DIGEST_LENGTH_BYTES = 8
78
+ DIGEST_BYTE_ORDER: Literal["big"] = "big"
79
+ CLI_START_MESSAGE_TEMPLATE = "verification start: {report_path}"
80
+ CLI_CHECK_START_MESSAGE_TEMPLATE = "check start: {check_id}"
81
+ CLI_CHECK_FINISH_MESSAGE_TEMPLATE = "check finish: {check_id} status={status}"
82
+ CLI_AGGREGATE_MESSAGE_TEMPLATE = "aggregate: {status}"
83
+ CLI_REVISION_MESSAGE_TEMPLATE = "revision: head={head} base={base}"
84
+ CLI_ELIGIBILITY_MESSAGE_TEMPLATE = "eligibility: clean={worktree_clean} unchanged={inputs_unchanged} publishable={publishable}"
85
+ CLI_REPORT_MESSAGE_TEMPLATE = "report: {report_path}"
86
+ CLI_FAILURE_MESSAGE_TEMPLATE = "verification {status}: required checks did not pass"
87
+ UNRESOLVED_REVISION = "unresolved"
88
+ RUN_LOG_FILENAME = "run.log"
89
+ RUN_CHECK_LOG_TEMPLATE = "check {check_id} status={status}"
@@ -0,0 +1 @@
1
+ GIT_METADATA_TIMEOUT_SECONDS = 5.0
@@ -0,0 +1,146 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import subprocess
5
+ from dataclasses import dataclass
6
+ from pathlib import Path
7
+
8
+ from .config import (
9
+ ALL_GIT_BASE_ARGUMENT_PREFIX,
10
+ ALL_GIT_CHANGED_FILES_ARGUMENTS,
11
+ ALL_GIT_HEAD_ARGUMENTS,
12
+ ALL_GIT_INDEX_CHANGED_FILES_ARGUMENTS,
13
+ ALL_GIT_ROOT_ARGUMENTS,
14
+ ALL_GIT_STATUS_ARGUMENTS,
15
+ ALL_GIT_TREE_ARGUMENTS,
16
+ ALL_GIT_UNTRACKED_FILES_ARGUMENTS,
17
+ DIGEST_BYTE_ORDER,
18
+ DIGEST_LENGTH_BYTES,
19
+ GIT_COMMIT_OBJECT_SUFFIX,
20
+ GIT_EXECUTABLE,
21
+ GIT_PATH_SEPARATOR,
22
+ UTF8_ENCODING,
23
+ )
24
+ from .config.timing import GIT_METADATA_TIMEOUT_SECONDS
25
+
26
+
27
+ @dataclass(frozen=True)
28
+ class CandidateSnapshot:
29
+ head_revision: str | None
30
+ base_revision: str | None
31
+ is_git_repository: bool
32
+ worktree_clean: bool
33
+ input_digest: str | None
34
+
35
+
36
+ def capture_candidate_snapshot(
37
+ repository_path: Path, base_revision: str
38
+ ) -> CandidateSnapshot:
39
+ """Capture Git revisions and candidate file contents at one point.
40
+
41
+ Args:
42
+ repository_path: Candidate repository root.
43
+ base_revision: Base revision supplied to the verification run.
44
+
45
+ Returns:
46
+ Git revision and candidate content facts for the snapshot.
47
+ """
48
+ git_root = _run_git_text(repository_path, ALL_GIT_ROOT_ARGUMENTS)
49
+ if git_root is None or Path(git_root).resolve() != repository_path.resolve():
50
+ return CandidateSnapshot(None, None, False, False, None)
51
+ head_revision = _run_git_text(repository_path, ALL_GIT_HEAD_ARGUMENTS)
52
+ resolved_base_revision = _run_git_text(
53
+ repository_path,
54
+ (*ALL_GIT_BASE_ARGUMENT_PREFIX, base_revision + GIT_COMMIT_OBJECT_SUFFIX),
55
+ )
56
+ status_bytes = _run_git_bytes(repository_path, ALL_GIT_STATUS_ARGUMENTS)
57
+ is_worktree_clean = status_bytes == b""
58
+ return CandidateSnapshot(
59
+ head_revision,
60
+ resolved_base_revision,
61
+ True,
62
+ is_worktree_clean,
63
+ _compute_input_digest(repository_path, is_worktree_clean),
64
+ )
65
+
66
+
67
+ def _run_git_text(repository_path: Path, all_arguments: tuple[str, ...]) -> str | None:
68
+ command_bytes = _run_git_bytes(repository_path, all_arguments)
69
+ if command_bytes is None:
70
+ return None
71
+ revision_text = command_bytes.decode(UTF8_ENCODING).strip()
72
+ return revision_text or None
73
+
74
+
75
+ def _run_git_bytes(
76
+ repository_path: Path, all_arguments: tuple[str, ...]
77
+ ) -> bytes | None:
78
+ try:
79
+ completed_process = subprocess.run(
80
+ [GIT_EXECUTABLE, *all_arguments],
81
+ cwd=repository_path,
82
+ capture_output=True,
83
+ check=False,
84
+ timeout=GIT_METADATA_TIMEOUT_SECONDS,
85
+ )
86
+ except (OSError, subprocess.SubprocessError):
87
+ return None
88
+ if completed_process.returncode != 0:
89
+ return None
90
+ return completed_process.stdout
91
+
92
+
93
+ def _compute_input_digest(repository_path: Path, is_worktree_clean: bool) -> str | None:
94
+ head_tree = _run_git_text(repository_path, ALL_GIT_TREE_ARGUMENTS)
95
+ if head_tree is None:
96
+ return None
97
+ input_digest = hashlib.sha256()
98
+ _add_digest_record(input_digest, head_tree.encode(UTF8_ENCODING))
99
+ if is_worktree_clean:
100
+ return input_digest.hexdigest()
101
+ all_changed_paths = _changed_input_paths(repository_path)
102
+ if all_changed_paths is None:
103
+ return None
104
+ for each_path_bytes in all_changed_paths:
105
+ input_path = repository_path / Path(each_path_bytes.decode(UTF8_ENCODING))
106
+ input_bytes = _read_input_bytes(input_path)
107
+ if input_bytes is None:
108
+ return None
109
+ _add_digest_record(input_digest, each_path_bytes)
110
+ _add_digest_record(input_digest, input_bytes)
111
+ return input_digest.hexdigest()
112
+
113
+
114
+ def _changed_input_paths(repository_path: Path) -> tuple[bytes, ...] | None:
115
+ all_path_bytes: set[bytes] = set()
116
+ for each_arguments in (
117
+ ALL_GIT_CHANGED_FILES_ARGUMENTS,
118
+ ALL_GIT_INDEX_CHANGED_FILES_ARGUMENTS,
119
+ ALL_GIT_UNTRACKED_FILES_ARGUMENTS,
120
+ ):
121
+ changed_path_bytes = _run_git_bytes(repository_path, each_arguments)
122
+ if changed_path_bytes is None:
123
+ return None
124
+ all_path_bytes.update(
125
+ each_path_bytes
126
+ for each_path_bytes in changed_path_bytes.split(GIT_PATH_SEPARATOR)
127
+ if each_path_bytes
128
+ )
129
+ return tuple(sorted(all_path_bytes))
130
+
131
+
132
+ def _add_digest_record(input_digest: hashlib._Hash, record_bytes: bytes) -> None:
133
+ input_digest.update(
134
+ len(record_bytes).to_bytes(
135
+ DIGEST_LENGTH_BYTES,
136
+ byteorder=DIGEST_BYTE_ORDER,
137
+ )
138
+ )
139
+ input_digest.update(record_bytes)
140
+
141
+
142
+ def _read_input_bytes(input_path: Path) -> bytes | None:
143
+ try:
144
+ return input_path.read_bytes()
145
+ except OSError:
146
+ return None
@@ -0,0 +1,224 @@
1
+ from __future__ import annotations
2
+
3
+ import hashlib
4
+ import json
5
+ import math
6
+ from collections.abc import Mapping
7
+ from pathlib import Path, PurePosixPath, PureWindowsPath
8
+
9
+ from .config import (
10
+ ALL_MANIFEST_DIGEST_SEPARATORS,
11
+ CHECK_ARGUMENTS_KEY,
12
+ CHECK_DIRECTORY_KEY,
13
+ CHECK_ID_KEY,
14
+ CHECK_MINIMUM_TESTS_KEY,
15
+ CHECK_TIMEOUT_KEY,
16
+ EXCLUSION_REASON_KEY,
17
+ EXCLUSION_SELECTOR_KEY,
18
+ MANIFEST_CHECKS_KEY,
19
+ MANIFEST_DIGEST_ALGORITHM,
20
+ MANIFEST_EXCLUSIONS_KEY,
21
+ MANIFEST_VERSION,
22
+ MANIFEST_VERSION_KEY,
23
+ UTF8_ENCODING,
24
+ )
25
+ from .model import CheckSpec, ExclusionSpec, VerificationManifest
26
+
27
+
28
+ class ManifestRunFatal(ValueError):
29
+ """Raised when a verification manifest does not follow version 1."""
30
+
31
+
32
+ def compute_manifest_digest(manifest: VerificationManifest) -> str:
33
+ """Return a stable digest for the validated manifest structure.
34
+
35
+ Args:
36
+ manifest: Validated required-check manifest.
37
+
38
+ Returns:
39
+ A SHA-256 digest that covers every executed manifest field.
40
+ """
41
+ canonical_manifest = json.dumps(
42
+ _manifest_mapping(manifest),
43
+ sort_keys=True,
44
+ separators=ALL_MANIFEST_DIGEST_SEPARATORS,
45
+ )
46
+ return hashlib.new(
47
+ MANIFEST_DIGEST_ALGORITHM, canonical_manifest.encode(UTF8_ENCODING)
48
+ ).hexdigest()
49
+
50
+
51
+ def _manifest_mapping(manifest: VerificationManifest) -> dict[str, object]:
52
+ all_check_mappings = [
53
+ {
54
+ CHECK_ID_KEY: each_check.check_id,
55
+ CHECK_ARGUMENTS_KEY: list(each_check.command_arguments),
56
+ CHECK_DIRECTORY_KEY: each_check.cwd,
57
+ CHECK_TIMEOUT_KEY: each_check.timeout_seconds,
58
+ CHECK_MINIMUM_TESTS_KEY: each_check.minimum_tests,
59
+ }
60
+ for each_check in manifest.checks
61
+ ]
62
+ all_exclusion_mappings = [
63
+ {
64
+ EXCLUSION_SELECTOR_KEY: each_exclusion.selector,
65
+ EXCLUSION_REASON_KEY: each_exclusion.reason,
66
+ }
67
+ for each_exclusion in manifest.exclusions
68
+ ]
69
+ return {
70
+ MANIFEST_VERSION_KEY: manifest.version,
71
+ MANIFEST_CHECKS_KEY: all_check_mappings,
72
+ MANIFEST_EXCLUSIONS_KEY: all_exclusion_mappings,
73
+ }
74
+
75
+
76
+ def load_manifest(manifest_path: Path) -> VerificationManifest:
77
+ """Read and validate a required-check manifest.
78
+
79
+ Args:
80
+ manifest_path: JSON file containing the required checks.
81
+
82
+ Returns:
83
+ The validated manifest.
84
+
85
+ Raises:
86
+ ManifestRunFatal: If the file cannot describe a valid manifest.
87
+ OSError: If the file cannot be read.
88
+ """
89
+ try:
90
+ parsed_manifest: object = json.loads(
91
+ manifest_path.read_text(encoding=UTF8_ENCODING)
92
+ )
93
+ except json.JSONDecodeError as error:
94
+ raise ManifestRunFatal(f"Manifest is not valid JSON: {error}") from error
95
+ manifest_mapping = _require_mapping(parsed_manifest, "manifest")
96
+ manifest_version = _require_integer(manifest_mapping, "version", "manifest")
97
+ if manifest_version != MANIFEST_VERSION:
98
+ raise ManifestRunFatal(f"Manifest version must be {MANIFEST_VERSION}")
99
+ all_checks = _load_checks(manifest_mapping.get("checks"))
100
+ all_exclusions = _load_exclusions(manifest_mapping.get("exclusions", []))
101
+ return VerificationManifest(
102
+ manifest_version, tuple(all_checks), tuple(all_exclusions)
103
+ )
104
+
105
+
106
+ def _load_checks(raw_checks: object) -> list[CheckSpec]:
107
+ if not isinstance(raw_checks, list) or not raw_checks:
108
+ raise ManifestRunFatal("Manifest checks must be a non-empty array")
109
+ all_checks: list[CheckSpec] = []
110
+ all_check_ids: set[str] = set()
111
+ for each_index, each_raw_check in enumerate(raw_checks):
112
+ all_check_fields = _require_mapping(each_raw_check, f"checks[{each_index}]")
113
+ check_spec = _load_check(all_check_fields, each_index)
114
+ if check_spec.check_id in all_check_ids:
115
+ raise ManifestRunFatal("Check ids must be unique")
116
+ all_check_ids.add(check_spec.check_id)
117
+ all_checks.append(check_spec)
118
+ return all_checks
119
+
120
+
121
+ def _load_check(all_check_fields: Mapping[str, object], check_index: int) -> CheckSpec:
122
+ location = f"checks[{check_index}]"
123
+ check_id = _require_text(all_check_fields, "id", location)
124
+ all_command_arguments = _load_command_arguments(
125
+ all_check_fields.get("argv"), location
126
+ )
127
+ cwd = _load_relative_directory(all_check_fields.get("cwd"), location)
128
+ timeout_seconds = _load_timeout(all_check_fields.get("timeout_seconds"), location)
129
+ minimum_tests = _load_minimum_tests(all_check_fields.get("minimum_tests"), location)
130
+ return CheckSpec(
131
+ check_id, all_command_arguments, cwd, timeout_seconds, minimum_tests
132
+ )
133
+
134
+
135
+ def _load_command_arguments(raw_arguments: object, location: str) -> tuple[str, ...]:
136
+ if not isinstance(raw_arguments, list) or not raw_arguments:
137
+ raise ManifestRunFatal(f"{location}.argv must be a non-empty array")
138
+ if any(
139
+ not isinstance(each_argument, str) or not each_argument
140
+ for each_argument in raw_arguments
141
+ ):
142
+ raise ManifestRunFatal(f"{location}.argv must contain non-empty strings")
143
+ return tuple(raw_arguments)
144
+
145
+
146
+ def _load_exclusions(raw_exclusions: object) -> list[ExclusionSpec]:
147
+ if not isinstance(raw_exclusions, list):
148
+ raise ManifestRunFatal("Manifest exclusions must be an array")
149
+ all_exclusions: list[ExclusionSpec] = []
150
+ for each_index, each_raw_exclusion in enumerate(raw_exclusions):
151
+ all_exclusion_fields = _require_mapping(
152
+ each_raw_exclusion, f"exclusions[{each_index}]"
153
+ )
154
+ location = f"exclusions[{each_index}]"
155
+ selector = _require_text(all_exclusion_fields, "selector", location)
156
+ reason = _require_text(all_exclusion_fields, "reason", location)
157
+ all_exclusions.append(ExclusionSpec(selector, reason))
158
+ return all_exclusions
159
+
160
+
161
+ def _load_relative_directory(raw_directory: object, location: str) -> str:
162
+ if not isinstance(raw_directory, str) or not raw_directory:
163
+ raise ManifestRunFatal(f"{location}.cwd must be a repo-relative directory")
164
+ return _validate_relative_directory(raw_directory, location)
165
+
166
+
167
+ def _validate_relative_directory(directory_text: str, location: str) -> str:
168
+ normalized_directory = directory_text.replace("\\", "/")
169
+ directory_path = PurePosixPath(normalized_directory)
170
+ if (
171
+ directory_path.is_absolute()
172
+ or normalized_directory.startswith("/")
173
+ or PureWindowsPath(normalized_directory).drive
174
+ or any(each_part == ".." for each_part in directory_path.parts)
175
+ ):
176
+ raise ManifestRunFatal(f"{location}.cwd must stay inside the repository")
177
+ return directory_path.as_posix()
178
+
179
+
180
+ def _load_timeout(raw_timeout: object, location: str) -> float:
181
+ if isinstance(raw_timeout, bool) or not isinstance(raw_timeout, (int, float)):
182
+ raise ManifestRunFatal(f"{location}.timeout_seconds must be positive")
183
+ timeout_seconds = float(raw_timeout)
184
+ if not math.isfinite(timeout_seconds) or timeout_seconds <= 0:
185
+ raise ManifestRunFatal(f"{location}.timeout_seconds must be positive")
186
+ return timeout_seconds
187
+
188
+
189
+ def _load_minimum_tests(raw_minimum: object, location: str) -> int | None:
190
+ if raw_minimum is None:
191
+ return None
192
+ if (
193
+ isinstance(raw_minimum, bool)
194
+ or not isinstance(raw_minimum, int)
195
+ or raw_minimum < 0
196
+ ):
197
+ raise ManifestRunFatal(
198
+ f"{location}.minimum_tests must be a non-negative integer"
199
+ )
200
+ return raw_minimum
201
+
202
+
203
+ def _require_mapping(candidate: object, location: str) -> Mapping[str, object]:
204
+ if not isinstance(candidate, Mapping):
205
+ raise ManifestRunFatal(f"{location} must be an object")
206
+ return candidate
207
+
208
+
209
+ def _require_text(
210
+ all_fields: Mapping[str, object], field_name: str, location: str
211
+ ) -> str:
212
+ field_text = all_fields.get(field_name)
213
+ if not isinstance(field_text, str) or not field_text.strip():
214
+ raise ManifestRunFatal(f"{location}.{field_name} must be a non-empty string")
215
+ return field_text
216
+
217
+
218
+ def _require_integer(
219
+ all_fields: Mapping[str, object], field_name: str, location: str
220
+ ) -> int:
221
+ field_number = all_fields.get(field_name)
222
+ if isinstance(field_number, bool) or not isinstance(field_number, int):
223
+ raise ManifestRunFatal(f"{location}.{field_name} must be an integer")
224
+ return field_number
@@ -0,0 +1,174 @@
1
+ from __future__ import annotations
2
+
3
+ from dataclasses import dataclass
4
+ from pathlib import Path
5
+ from typing import TypeAlias
6
+
7
+ from .config import (
8
+ CHECK_FAILED_EXIT_CODE,
9
+ FAILED_STATUS,
10
+ INCOMPLETE_EXIT_CODE,
11
+ INCOMPLETE_STATUS,
12
+ PASSED_STATUS,
13
+ SUCCESS_EXIT_CODE,
14
+ )
15
+
16
+ CheckStatus: TypeAlias = str
17
+ AggregateStatus: TypeAlias = str
18
+
19
+
20
+ @dataclass(frozen=True)
21
+ class CheckSpec:
22
+ check_id: str
23
+ command_arguments: tuple[str, ...]
24
+ cwd: str
25
+ timeout_seconds: float
26
+ minimum_tests: int | None = None
27
+
28
+
29
+ @dataclass(frozen=True)
30
+ class ExclusionSpec:
31
+ selector: str
32
+ reason: str
33
+
34
+
35
+ @dataclass(frozen=True)
36
+ class VerificationManifest:
37
+ version: int
38
+ checks: tuple[CheckSpec, ...]
39
+ exclusions: tuple[ExclusionSpec, ...]
40
+
41
+
42
+ @dataclass(frozen=True)
43
+ class CommandCapture:
44
+ exit_code: int | None
45
+ stdout_text: str
46
+ stderr_text: str
47
+ error_kind: str | None
48
+ error_message: str | None
49
+
50
+
51
+ @dataclass(frozen=True)
52
+ class CheckLogPaths:
53
+ stdout: Path
54
+ stderr: Path
55
+ collection_stdout: Path
56
+ collection_stderr: Path
57
+
58
+
59
+ @dataclass(frozen=True)
60
+ class CheckRecord:
61
+ check_id: str
62
+ status: CheckStatus
63
+ command_arguments: tuple[str, ...]
64
+ cwd: str
65
+ exit_code: int | None
66
+ duration_seconds: float
67
+ stdout_log: Path
68
+ stderr_log: Path
69
+ error_kind: str | None = None
70
+ error_message: str | None = None
71
+ minimum_tests: int | None = None
72
+ collected_tests: int | None = None
73
+ collection_exit_code: int | None = None
74
+
75
+ def as_dict(self) -> dict[str, object]:
76
+ """Return the JSON record for this check.
77
+
78
+ Returns:
79
+ A JSON-compatible check record using the manifest argv key.
80
+ """
81
+ return {
82
+ "id": self.check_id,
83
+ "status": self.status,
84
+ "argv": list(self.command_arguments),
85
+ "cwd": self.cwd,
86
+ "exit_code": self.exit_code,
87
+ "duration_seconds": self.duration_seconds,
88
+ "stdout_log": str(self.stdout_log),
89
+ "stderr_log": str(self.stderr_log),
90
+ "error_kind": self.error_kind,
91
+ "error_message": self.error_message,
92
+ "minimum_tests": self.minimum_tests,
93
+ "collected_tests": self.collected_tests,
94
+ "collection_exit_code": self.collection_exit_code,
95
+ }
96
+
97
+
98
+ @dataclass(frozen=True)
99
+ class VerificationReport:
100
+ version: int
101
+ repository: Path
102
+ base_revision: str | None
103
+ checks: tuple[CheckRecord, ...]
104
+ exclusions: tuple[ExclusionSpec, ...]
105
+ head_revision: str | None = None
106
+ manifest_digest: str = ""
107
+ is_worktree_clean: bool = False
108
+ is_inputs_unchanged: bool = False
109
+ is_publishable: bool = False
110
+
111
+ @property
112
+ def worktree_clean(self) -> bool:
113
+ return self.is_worktree_clean
114
+
115
+ @property
116
+ def inputs_unchanged(self) -> bool:
117
+ return self.is_inputs_unchanged
118
+
119
+ @property
120
+ def publishable(self) -> bool:
121
+ return self.is_publishable
122
+
123
+ @property
124
+ def aggregate_status(self) -> AggregateStatus:
125
+ if any(each_record.status == FAILED_STATUS for each_record in self.checks):
126
+ return FAILED_STATUS
127
+ if any(each_record.status == INCOMPLETE_STATUS for each_record in self.checks):
128
+ return INCOMPLETE_STATUS
129
+ return PASSED_STATUS
130
+
131
+ @property
132
+ def exit_code(self) -> int:
133
+ if self.aggregate_status == FAILED_STATUS:
134
+ return CHECK_FAILED_EXIT_CODE
135
+ if self.aggregate_status == INCOMPLETE_STATUS:
136
+ return INCOMPLETE_EXIT_CODE
137
+ return SUCCESS_EXIT_CODE
138
+
139
+ def _aggregate_fields(self, all_statuses: list[str]) -> dict[str, object]:
140
+ return {
141
+ "status": self.aggregate_status,
142
+ "total": len(self.checks),
143
+ "passed": all_statuses.count(PASSED_STATUS),
144
+ "failed": all_statuses.count(FAILED_STATUS),
145
+ "incomplete": all_statuses.count(INCOMPLETE_STATUS),
146
+ "exit_code": self.exit_code,
147
+ }
148
+
149
+ def as_dict(self) -> dict[str, object]:
150
+ """Return the JSON report for this verification run.
151
+
152
+ Returns:
153
+ A JSON-compatible report with aggregate check counts.
154
+ """
155
+ all_statuses = [each_record.status for each_record in self.checks]
156
+ return {
157
+ "version": self.version,
158
+ "schema_version": self.version,
159
+ "repository": str(self.repository),
160
+ "head": self.head_revision,
161
+ "base": self.base_revision,
162
+ "manifest_digest": self.manifest_digest,
163
+ "worktree_clean": self.is_worktree_clean,
164
+ "inputs_unchanged": self.is_inputs_unchanged,
165
+ "publishable": self.is_publishable,
166
+ "checks": [each_record.as_dict() for each_record in self.checks],
167
+ "exclusions": [
168
+ {"selector": each_exclusion.selector, "reason": each_exclusion.reason}
169
+ for each_exclusion in self.exclusions
170
+ ],
171
+ "aggregate": self._aggregate_fields(all_statuses),
172
+ "status": self.aggregate_status,
173
+ "exit_code": self.exit_code,
174
+ }