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
@@ -1,684 +0,0 @@
1
- #!/usr/bin/env python3
2
- """PreToolUse hook: gate a git commit on staged session-edited files.
3
-
4
- You edit three files, stage two, and run `git commit` — the third was tracked
5
- but never staged, so the commit leaves it behind and you notice only later. This
6
- hook catches that at commit time. It reads the per-session tracker written by
7
- ``session_file_edit_tracker`` and denies the commit when a file this session
8
- edited is tracked yet still unstaged, naming each file and the exact way to
9
- include it.
10
-
11
- Deliberate partial commits still pass: a ``-a``/``--all`` commit, a pathspec
12
- commit, and a ``# partial-commit`` marker each pass through untouched. A
13
- ``--amend`` does not — an amend drops unstaged edits just as a plain commit does.
14
- A missing tracker file or any git failure allows the commit, so the gate never
15
- blocks on infrastructure trouble.
16
- """
17
-
18
- from __future__ import annotations
19
-
20
- import json
21
- import os
22
- import shlex
23
- import subprocess
24
- import sys
25
- import tempfile
26
- from pathlib import Path
27
-
28
- _blocking_dir = str(Path(__file__).resolve().parent)
29
- if _blocking_dir not in sys.path:
30
- sys.path.insert(0, _blocking_dir)
31
- _hooks_dir = str(Path(__file__).resolve().parent.parent)
32
- if _hooks_dir not in sys.path:
33
- sys.path.insert(0, _hooks_dir)
34
-
35
- from block_main_commit import ( # noqa: E402
36
- extract_git_working_directory,
37
- resolve_directory,
38
- )
39
- from precommit_code_rules_gate import ( # noqa: E402
40
- resolve_repository_root,
41
- )
42
-
43
- from hooks_constants.hook_block_logger import log_hook_block # noqa: E402
44
- from hooks_constants.pre_tool_use_stdin import ( # noqa: E402
45
- read_hook_input_dictionary_from_stdin,
46
- )
47
- from hooks_constants.session_edit_stage_gate_constants import ( # noqa: E402
48
- ALL_COMMAND_SEPARATOR_TOKENS,
49
- ALL_COMMIT_ALL_FLAGS,
50
- ALL_COMMIT_VALUE_OPTION_SHORT_LETTERS,
51
- ALL_COMMIT_VALUE_OPTION_TOKENS,
52
- ALL_EDITED_FILE_PATHS_KEY,
53
- ALL_GIT_GLOBAL_VALUE_OPTION_TOKENS,
54
- ALL_STAGE_ALL_ADD_FLAG_TOKENS,
55
- ALL_STAGE_ALL_ADD_PATHSPEC_TOKENS,
56
- ALL_STAGING_SUBCOMMAND_TOKENS,
57
- ALL_TRACKED_UNSTAGED_FILES_COMMAND,
58
- COMMIT_ALL_SHORT_FLAG_LETTER,
59
- COMMIT_SUBCOMMAND_TOKEN,
60
- DENY_FILE_BULLET_LINE_SEPARATOR,
61
- DENY_FILE_BULLET_PREFIX,
62
- DENY_PATHSPEC_SEPARATOR,
63
- ENV_ASSIGNMENT_PREFIX_PATTERN,
64
- GIT_DIFF_OUTPUT_ENCODING,
65
- GIT_DIFF_TIMEOUT_SECONDS,
66
- GIT_EXECUTABLE_TOKEN,
67
- LONG_FLAG_PREFIX,
68
- PARTIAL_COMMIT_BYPASS_MARKER,
69
- SESSION_EDIT_DENY_TEMPLATE,
70
- SESSION_EDIT_FILE_PREFIX,
71
- SESSION_EDIT_FILE_SUFFIX,
72
- SESSION_ID_UNSAFE_CHARACTERS_PATTERN,
73
- SHORT_FLAG_PREFIX,
74
- STATE_FILE_DEFAULT_SESSION_ID,
75
- )
76
-
77
-
78
- def _commit_argument_tokens(bash_command: str) -> list[str]:
79
- """Return the tokens that follow the ``commit`` subcommand of its segment.
80
-
81
- The command is split into shell segments. The segment whose git subcommand
82
- is ``commit`` supplies its post-subcommand tokens, so a bare ``commit``
83
- token in an earlier segment (``echo commit && git commit -a``) never stands
84
- in for the real commit's arguments.
85
-
86
- Args:
87
- bash_command: The Bash tool command string.
88
-
89
- Returns:
90
- The argument tokens of the git commit invocation. Empty when the
91
- command cannot be tokenized or runs no ``git commit`` segment.
92
- """
93
- try:
94
- all_tokens = shlex.split(bash_command, posix=True)
95
- except ValueError:
96
- return []
97
- for each_segment in _split_into_command_segments(all_tokens):
98
- subcommand_index = _git_subcommand_index(each_segment)
99
- if subcommand_index is None:
100
- continue
101
- if each_segment[subcommand_index] != COMMIT_SUBCOMMAND_TOKEN:
102
- continue
103
- return each_segment[subcommand_index + 1 :]
104
- return []
105
-
106
-
107
- def _is_all_flag_token(token: str) -> bool:
108
- """Return whether *token* is a ``-a``/``--all`` flag or a cluster holding ``a``.
109
-
110
- A short-flag cluster is read left to right. The scan stops at the first
111
- value-taking option letter (``m``/``F``/``C``/``c``), whose remainder is
112
- that option's argument rather than more clustered flags — so
113
- ``git commit -m"add feature"`` tokenizes to ``-madd feature`` and does not
114
- count as an ``-a`` cluster even though its message carries an ``a``.
115
-
116
- Args:
117
- token: One argument token of a git commit invocation.
118
-
119
- Returns:
120
- True for ``-a``, ``--all``, or a combined short cluster such as
121
- ``-am`` that carries the ``a`` letter before any value-taking option;
122
- False otherwise.
123
- """
124
- if token in ALL_COMMIT_ALL_FLAGS:
125
- return True
126
- if token.startswith(LONG_FLAG_PREFIX):
127
- return False
128
- if not token.startswith(SHORT_FLAG_PREFIX):
129
- return False
130
- for each_letter in token[1:]:
131
- if each_letter == COMMIT_ALL_SHORT_FLAG_LETTER:
132
- return True
133
- if each_letter in ALL_COMMIT_VALUE_OPTION_SHORT_LETTERS:
134
- return False
135
- return False
136
-
137
-
138
- def _has_trailing_bypass_comment(bash_command: str) -> bool:
139
- """Return whether the command carries the bypass marker as a real comment.
140
-
141
- The ``# partial-commit`` marker opts out only as a trailing shell comment
142
- the author adds on purpose, so its ``#`` and ``partial-commit`` parts are
143
- the final tokens of the command. The same text earlier in the command — a
144
- quoted commit message (``git commit -m "fix # partial-commit"``) or an
145
- earlier ``echo # partial-commit`` segment — does not match, so the gate
146
- still runs.
147
-
148
- Args:
149
- bash_command: The Bash tool command string.
150
-
151
- Returns:
152
- True when the marker is the final tokens of the command; False when it
153
- appears earlier or the command will not tokenize.
154
- """
155
- marker_tokens = PARTIAL_COMMIT_BYPASS_MARKER.split()
156
- try:
157
- all_tokens = shlex.split(bash_command, posix=True)
158
- except ValueError:
159
- return False
160
- marker_length = len(marker_tokens)
161
- if marker_length > len(all_tokens):
162
- return False
163
- return all_tokens[-marker_length:] == marker_tokens
164
-
165
-
166
- def _split_into_command_segments(all_tokens: list[str]) -> list[list[str]]:
167
- """Split a token list into segments at each shell command separator.
168
-
169
- Args:
170
- all_tokens: The tokens of the whole Bash command.
171
-
172
- Returns:
173
- One token list per command segment, in command order. A ``&&``, ``||``,
174
- ``;``, ``|``, or ``&`` separator ends the current segment and starts the
175
- next.
176
- """
177
- all_segments: list[list[str]] = []
178
- current_segment: list[str] = []
179
- for each_token in all_tokens:
180
- if each_token in ALL_COMMAND_SEPARATOR_TOKENS:
181
- all_segments.append(current_segment)
182
- current_segment = []
183
- continue
184
- current_segment.append(each_token)
185
- all_segments.append(current_segment)
186
- return all_segments
187
-
188
-
189
- def _command_token_index(all_segment_tokens: list[str]) -> int | None:
190
- """Return the index of a segment's command word, past any env assignments.
191
-
192
- A segment may open with ``NAME=value`` assignments before the command it
193
- runs, as in ``GIT_DIR=x git commit``. This skips those leading assignments
194
- and returns the first real command token, so ``git`` is read as the command
195
- only when it holds that position — not when it rides as an argument to
196
- another command (``echo git commit``).
197
-
198
- Args:
199
- all_segment_tokens: One command segment's tokens.
200
-
201
- Returns:
202
- The index of the first non-assignment token, or None for an empty
203
- segment or one that is only assignments.
204
- """
205
- for each_offset, each_token in enumerate(all_segment_tokens):
206
- if ENV_ASSIGNMENT_PREFIX_PATTERN.match(each_token):
207
- continue
208
- return each_offset
209
- return None
210
-
211
-
212
- def _git_subcommand_index(all_segment_tokens: list[str]) -> int | None:
213
- """Return the index of a segment's git subcommand, skipping global options.
214
-
215
- The segment runs ``git`` as its command word — past any leading
216
- ``NAME=value`` assignments — and ``git`` may carry global options before its
217
- subcommand: ``-C``/``-c``/``--git-dir`` and their siblings each take a
218
- value, so ``git -C path add file`` resolves the ``add`` token, not ``path``.
219
-
220
- Args:
221
- all_segment_tokens: One command segment's tokens.
222
-
223
- Returns:
224
- The index of the subcommand token following ``git`` and its global
225
- options, or None when the segment does not run ``git`` as its command.
226
- """
227
- command_index = _command_token_index(all_segment_tokens)
228
- if command_index is None or all_segment_tokens[command_index] != GIT_EXECUTABLE_TOKEN:
229
- return None
230
- should_skip_next_value = False
231
- for each_offset in range(command_index + 1, len(all_segment_tokens)):
232
- each_token = all_segment_tokens[each_offset]
233
- if should_skip_next_value:
234
- should_skip_next_value = False
235
- continue
236
- if each_token in ALL_GIT_GLOBAL_VALUE_OPTION_TOKENS:
237
- should_skip_next_value = True
238
- continue
239
- if each_token.startswith(SHORT_FLAG_PREFIX):
240
- continue
241
- return each_offset
242
- return None
243
-
244
-
245
- def _git_subcommand(all_segment_tokens: list[str]) -> str | None:
246
- """Return the git subcommand of a segment, skipping git's global options.
247
-
248
- A leading ``git`` may carry global options before its subcommand, and
249
- ``-C``/``-c``/``--git-dir`` and their siblings each take a value — so
250
- ``git -C path add file`` resolves to the ``add`` subcommand, not ``path``.
251
-
252
- Args:
253
- all_segment_tokens: One command segment's tokens.
254
-
255
- Returns:
256
- The subcommand token following ``git`` and its global options, or None
257
- when the segment invokes no ``git`` subcommand.
258
- """
259
- subcommand_index = _git_subcommand_index(all_segment_tokens)
260
- if subcommand_index is None:
261
- return None
262
- return all_segment_tokens[subcommand_index]
263
-
264
-
265
- def _preceding_staging_segments(bash_command: str) -> list[list[str]]:
266
- """Return the ``git add``/``git stage`` segments that run before the commit.
267
-
268
- The common ``git add widget.py && git commit -m update`` idiom stages a file
269
- in its own segment right before the commit, so the file is staged by the
270
- time the commit runs even though it reads as unstaged when the gate inspects
271
- the index. A staging segment that runs after the commit segment stages
272
- nothing for that commit and is left out.
273
-
274
- Args:
275
- bash_command: The Bash tool command string.
276
-
277
- Returns:
278
- The staging segments (each a token list) that run before the commit
279
- segment, in command order. Empty when the command will not tokenize,
280
- runs no commit, or has no such preceding segment.
281
- """
282
- try:
283
- all_tokens = shlex.split(bash_command, posix=True)
284
- except ValueError:
285
- return []
286
- all_segments = _split_into_command_segments(all_tokens)
287
- commit_segment_index = next(
288
- (
289
- each_index
290
- for each_index, each_segment in enumerate(all_segments)
291
- if _git_subcommand(each_segment) == COMMIT_SUBCOMMAND_TOKEN
292
- ),
293
- None,
294
- )
295
- if commit_segment_index is None:
296
- return []
297
- return [
298
- each_segment
299
- for each_segment in all_segments[:commit_segment_index]
300
- if _git_subcommand(each_segment) in ALL_STAGING_SUBCOMMAND_TOKENS
301
- ]
302
-
303
-
304
- def _staging_segment_covers_all_paths(all_segment_tokens: list[str]) -> bool:
305
- """Return whether one staging segment stages every path (a stage-all form).
306
-
307
- ``git add -A``/``--all``/``-u``/``--update`` and ``git add .``/``git add :/``
308
- each stage the whole working tree, so any file the session edited is staged
309
- by the time the commit runs. A ``--`` ends option parsing: every token after
310
- it is a path, so ``git add -- -A`` stages a file named ``-A`` and is not a
311
- stage-all form.
312
-
313
- Args:
314
- all_segment_tokens: One ``git add``/``git stage`` segment's tokens.
315
-
316
- Returns:
317
- True when the segment carries a stage-all flag before any ``--`` or a
318
- stage-all pathspec; False otherwise.
319
- """
320
- subcommand_index = _git_subcommand_index(all_segment_tokens)
321
- if subcommand_index is None:
322
- return False
323
- has_seen_end_of_options = False
324
- for each_token in all_segment_tokens[subcommand_index + 1 :]:
325
- if not has_seen_end_of_options and each_token == LONG_FLAG_PREFIX:
326
- has_seen_end_of_options = True
327
- continue
328
- if not has_seen_end_of_options and each_token in ALL_STAGE_ALL_ADD_FLAG_TOKENS:
329
- return True
330
- if each_token in ALL_STAGE_ALL_ADD_PATHSPEC_TOKENS:
331
- return True
332
- return False
333
-
334
-
335
- def _staging_covers_all_paths(all_staging_segments: list[list[str]]) -> bool:
336
- """Return whether any preceding staging segment stages every path.
337
-
338
- Args:
339
- all_staging_segments: The staging segments that run before the commit.
340
-
341
- Returns:
342
- True when one of the segments is a stage-all form; False otherwise.
343
- """
344
- return any(
345
- _staging_segment_covers_all_paths(each_segment)
346
- for each_segment in all_staging_segments
347
- )
348
-
349
-
350
- def _staged_positional_path_tokens(all_staging_segments: list[list[str]]) -> list[str]:
351
- """Return the positional path tokens of the specific-path staging segments.
352
-
353
- A specific-path ``git add README.md`` names the paths it stages as
354
- positional tokens after the subcommand, so only those paths are staged by
355
- the time the commit runs. A ``--`` ends option parsing: before it, a
356
- ``-``-prefixed token is a flag; after it, every token is a path, so
357
- ``git add -- -weird.py`` stages the file ``-weird.py``.
358
-
359
- Args:
360
- all_staging_segments: The staging segments that run before the commit.
361
-
362
- Returns:
363
- Every path token following the staging subcommand, across all segments,
364
- in command order.
365
- """
366
- all_path_tokens: list[str] = []
367
- for each_segment in all_staging_segments:
368
- subcommand_index = _git_subcommand_index(each_segment)
369
- if subcommand_index is None:
370
- continue
371
- has_seen_end_of_options = False
372
- for each_token in each_segment[subcommand_index + 1 :]:
373
- if not has_seen_end_of_options and each_token == LONG_FLAG_PREFIX:
374
- has_seen_end_of_options = True
375
- continue
376
- if not has_seen_end_of_options and each_token.startswith(SHORT_FLAG_PREFIX):
377
- continue
378
- all_path_tokens.append(each_token)
379
- return all_path_tokens
380
-
381
-
382
- def _staged_path_keys(
383
- all_staging_segments: list[list[str]],
384
- working_directory: str | None,
385
- repository_root: Path,
386
- ) -> set[str]:
387
- """Return the case-folded resolved keys the specific-path staging segments stage.
388
-
389
- A ``git add`` path token is relative to the command's working directory, so
390
- it resolves against ``working_directory`` when that is known and against the
391
- repository root otherwise.
392
-
393
- Args:
394
- all_staging_segments: The staging segments that run before the commit.
395
- working_directory: The command's resolved git working directory, or None
396
- when the command runs in the hook's own working directory.
397
- repository_root: Repository root used as the base when no working
398
- directory is known.
399
-
400
- Returns:
401
- The case-folded resolved absolute paths of every specific staged path
402
- token, matching the key shape used for session-edited and unstaged paths.
403
- """
404
- base_directory = Path(working_directory) if working_directory is not None else repository_root
405
- staged_keys: set[str] = set()
406
- for each_token in _staged_positional_path_tokens(all_staging_segments):
407
- try:
408
- resolved_key = str((base_directory / each_token).resolve()).casefold()
409
- except OSError:
410
- continue
411
- staged_keys.add(resolved_key)
412
- return staged_keys
413
-
414
-
415
- def _invokes_git_commit(bash_command: str) -> bool:
416
- """Return whether the command runs a real ``git commit`` subcommand.
417
-
418
- A benign command can name the phrase ``git commit`` as plain text, as in
419
- ``grep -rn "git commit" .``. This check tokenizes the command, splits it at
420
- each shell separator, and confirms one segment invokes ``git`` with a
421
- ``commit`` subcommand.
422
-
423
- Args:
424
- bash_command: The Bash tool command string.
425
-
426
- Returns:
427
- True when a command segment runs ``git commit``; False when the command
428
- will not tokenize or names ``commit`` only as text.
429
- """
430
- try:
431
- all_tokens = shlex.split(bash_command, posix=True)
432
- except ValueError:
433
- return False
434
- return any(
435
- _git_subcommand(each_segment) == COMMIT_SUBCOMMAND_TOKEN
436
- for each_segment in _split_into_command_segments(all_tokens)
437
- )
438
-
439
-
440
- def _commit_bypasses_stage_check(bash_command: str) -> bool:
441
- """Return whether the commit intentionally opts out of the stage check.
442
-
443
- A ``# partial-commit`` trailing comment, a ``-a``/``--all`` flag, and a
444
- pathspec argument (a ``--`` separator or a bare positional path) each mark a
445
- deliberate partial commit that the gate leaves alone.
446
-
447
- Args:
448
- bash_command: The Bash tool command string.
449
-
450
- Returns:
451
- True when the commit opts out of the stage check; False otherwise.
452
- """
453
- if _has_trailing_bypass_comment(bash_command):
454
- return True
455
- should_skip_next_value = False
456
- for each_token in _commit_argument_tokens(bash_command):
457
- if should_skip_next_value:
458
- should_skip_next_value = False
459
- continue
460
- if each_token == LONG_FLAG_PREFIX:
461
- return True
462
- if _is_all_flag_token(each_token):
463
- return True
464
- if each_token in ALL_COMMIT_VALUE_OPTION_TOKENS:
465
- should_skip_next_value = True
466
- continue
467
- if each_token.startswith(SHORT_FLAG_PREFIX):
468
- continue
469
- return True
470
- return False
471
-
472
-
473
- def _session_edited_keys(session_id: str) -> set[str]:
474
- """Return the case-folded resolved paths this session recorded as edited.
475
-
476
- Args:
477
- session_id: Raw ``session_id`` from the hook payload.
478
-
479
- Returns:
480
- The case-folded resolved absolute paths from the tracker file, or an
481
- empty set when the file is absent, unreadable, or malformed.
482
- """
483
- sanitized_session_id = SESSION_ID_UNSAFE_CHARACTERS_PATTERN.sub("", session_id)
484
- effective_session_id = sanitized_session_id or STATE_FILE_DEFAULT_SESSION_ID
485
- file_name = f"{SESSION_EDIT_FILE_PREFIX}{effective_session_id}{SESSION_EDIT_FILE_SUFFIX}"
486
- edit_file = Path(tempfile.gettempdir()) / file_name
487
- try:
488
- raw_contents = edit_file.read_text(encoding="utf-8")
489
- except (FileNotFoundError, OSError):
490
- return set()
491
- try:
492
- parsed_payload = json.loads(raw_contents)
493
- except json.JSONDecodeError:
494
- return set()
495
- if not isinstance(parsed_payload, dict):
496
- return set()
497
- recorded_paths = parsed_payload.get(ALL_EDITED_FILE_PATHS_KEY, [])
498
- if not isinstance(recorded_paths, list):
499
- return set()
500
- edited_keys: set[str] = set()
501
- for each_path in recorded_paths:
502
- if not isinstance(each_path, str) or not each_path:
503
- continue
504
- try:
505
- resolved_key = str(Path(each_path).resolve()).casefold()
506
- except OSError:
507
- continue
508
- edited_keys.add(resolved_key)
509
- return edited_keys
510
-
511
-
512
- def _tracked_unstaged_paths(repository_root: Path) -> dict[str, str] | None:
513
- """Return tracked-but-unstaged files keyed by their case-folded resolved path.
514
-
515
- Args:
516
- repository_root: Repository root used as the git working directory.
517
-
518
- Returns:
519
- A mapping of case-folded resolved absolute path to repository-relative
520
- path for each tracked file with unstaged changes. None when the git
521
- command fails, so the caller can allow the commit rather than block on
522
- an infrastructure failure.
523
- """
524
- try:
525
- completed_process = subprocess.run(
526
- list(ALL_TRACKED_UNSTAGED_FILES_COMMAND),
527
- check=False, capture_output=True,
528
- text=True,
529
- encoding=GIT_DIFF_OUTPUT_ENCODING,
530
- timeout=GIT_DIFF_TIMEOUT_SECONDS,
531
- cwd=str(repository_root),
532
- )
533
- except (subprocess.TimeoutExpired, FileNotFoundError, OSError):
534
- return None
535
- if completed_process.returncode != 0:
536
- return None
537
- repository_relative_by_key: dict[str, str] = {}
538
- for each_line in completed_process.stdout.splitlines():
539
- repository_relative_path = each_line.strip()
540
- if not repository_relative_path:
541
- continue
542
- absolute_key = str((repository_root / repository_relative_path).resolve()).casefold()
543
- repository_relative_by_key[absolute_key] = repository_relative_path
544
- return repository_relative_by_key
545
-
546
-
547
- def _offending_repository_relative_paths(
548
- all_session_edited_keys: set[str],
549
- all_unstaged_paths_by_key: dict[str, str],
550
- all_staged_path_keys: set[str],
551
- ) -> list[str]:
552
- """Return the tracked-unstaged files this session edited, repository-relative.
553
-
554
- Args:
555
- all_session_edited_keys: Case-folded resolved paths this session edited.
556
- all_unstaged_paths_by_key: Tracked-unstaged files keyed by their
557
- case-folded resolved path.
558
- all_staged_path_keys: Case-folded resolved paths a preceding specific
559
- ``git add`` already staged, excluded from the result.
560
-
561
- Returns:
562
- The sorted repository-relative paths this session edited that remain
563
- unstaged and were not staged by a preceding specific ``git add``.
564
- """
565
- all_offending_paths = [
566
- repository_relative_path
567
- for absolute_key, repository_relative_path in all_unstaged_paths_by_key.items()
568
- if absolute_key in all_session_edited_keys
569
- and absolute_key not in all_staged_path_keys
570
- ]
571
- return sorted(all_offending_paths)
572
-
573
-
574
- def _build_denial(all_offending_paths: list[str]) -> dict:
575
- """Build the PreToolUse deny payload listing the dropped files.
576
-
577
- Args:
578
- all_offending_paths: The tracked-unstaged files this session edited,
579
- repository-relative.
580
-
581
- Returns:
582
- The hookSpecificOutput deny mapping for the PreToolUse protocol.
583
- """
584
- file_list = DENY_FILE_BULLET_LINE_SEPARATOR.join(
585
- f"{DENY_FILE_BULLET_PREFIX}{each_path}"
586
- for each_path in all_offending_paths
587
- )
588
- space_joined_paths = DENY_PATHSPEC_SEPARATOR.join(
589
- shlex.quote(each_path) for each_path in all_offending_paths
590
- )
591
- denial_reason = SESSION_EDIT_DENY_TEMPLATE.format(
592
- file_list=file_list,
593
- space_joined_paths=space_joined_paths,
594
- bypass_marker=PARTIAL_COMMIT_BYPASS_MARKER,
595
- )
596
- return {
597
- "hookSpecificOutput": {
598
- "hookEventName": "PreToolUse",
599
- "permissionDecision": "deny",
600
- "permissionDecisionReason": denial_reason,
601
- }
602
- }
603
-
604
-
605
- def _resolve_hook_working_directory(
606
- bash_command: str,
607
- all_hook_payload: dict[str, object],
608
- ) -> str | None:
609
- """Resolve the commit directory from the command or its event payload.
610
-
611
- Args:
612
- bash_command: Bash command whose explicit directory takes precedence.
613
- all_hook_payload: PreToolUse payload carrying the event ``cwd``.
614
-
615
- Returns:
616
- The validated commit directory, or None for the hook process directory.
617
- """
618
- command_directory = extract_git_working_directory(bash_command)
619
- event_directory = all_hook_payload.get("cwd")
620
- if not isinstance(event_directory, str) or not event_directory:
621
- return resolve_directory(command_directory)
622
- validated_event_directory = resolve_directory(event_directory)
623
- if command_directory is None:
624
- return validated_event_directory
625
- expanded_command_directory = os.path.expanduser(command_directory)
626
- command_path = Path(expanded_command_directory)
627
- if command_path.is_absolute():
628
- return resolve_directory(str(command_path))
629
- if validated_event_directory is None:
630
- return None
631
- return resolve_directory(str(Path(validated_event_directory) / command_path))
632
-
633
-
634
- def main() -> None:
635
- """Deny a git commit that would drop a tracked file that was edited yet left unstaged."""
636
- hook_payload = read_hook_input_dictionary_from_stdin()
637
- if hook_payload is None:
638
- sys.exit(0)
639
- tool_input = hook_payload.get("tool_input", {})
640
- if not isinstance(tool_input, dict):
641
- sys.exit(0)
642
- bash_command = tool_input.get("command", "")
643
- if not isinstance(bash_command, str) or not bash_command:
644
- sys.exit(0)
645
- if not _invokes_git_commit(bash_command):
646
- sys.exit(0)
647
- if _commit_bypasses_stage_check(bash_command):
648
- sys.exit(0)
649
- session_id = str(hook_payload.get("session_id") or "")
650
- session_edited_keys = _session_edited_keys(session_id)
651
- if not session_edited_keys:
652
- sys.exit(0)
653
- working_directory = _resolve_hook_working_directory(bash_command, hook_payload)
654
- repository_root = resolve_repository_root(working_directory)
655
- if repository_root is None:
656
- sys.exit(0)
657
- preceding_staging_segments = _preceding_staging_segments(bash_command)
658
- if _staging_covers_all_paths(preceding_staging_segments):
659
- sys.exit(0)
660
- repository_relative_by_key = _tracked_unstaged_paths(repository_root)
661
- if repository_relative_by_key is None:
662
- sys.exit(0)
663
- staged_path_keys = _staged_path_keys(
664
- preceding_staging_segments, working_directory, repository_root
665
- )
666
- offending_paths = _offending_repository_relative_paths(
667
- session_edited_keys, repository_relative_by_key, staged_path_keys
668
- )
669
- if not offending_paths:
670
- sys.exit(0)
671
- denial = _build_denial(offending_paths)
672
- log_hook_block(
673
- calling_hook_name="session_edit_stage_gate.py",
674
- hook_event="PreToolUse",
675
- block_reason=denial["hookSpecificOutput"]["permissionDecisionReason"],
676
- tool_name="Bash",
677
- offending_input_preview=bash_command,
678
- )
679
- print(json.dumps(denial))
680
- sys.exit(0)
681
-
682
-
683
- if __name__ == "__main__":
684
- main()