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,259 +0,0 @@
1
- """Repository-resolution behavior of the PII commit gate (issue #65).
2
-
3
- The gate scans the repository the command names, not the session working
4
- directory, so::
5
-
6
- git -C <healthy repo> commit -> scans <healthy repo> from any cwd
7
- cd <repo> && git commit -> scans <repo>
8
- git -C <repo> commit (cwd gone) -> still scans <repo>, never refuses for cwd
9
- git -C <not-a-repo> commit -> refusal names <not-a-repo>
10
- """
11
-
12
- from __future__ import annotations
13
-
14
- import json
15
- import subprocess
16
- import sys
17
- from pathlib import Path
18
-
19
- import pytest
20
- from pii_prevention_blocker import evaluate, evaluate_bash_command
21
-
22
- _HOOK_PATH = Path(__file__).resolve().parents[1] / "pii_prevention_blocker.py"
23
- _ALLOW_SLUG = "AllowOwner/allow-repo"
24
- _OTHER_SLUG = "OtherOwner/other-repo"
25
-
26
-
27
- def _run_hook(payload: dict[str, object]) -> tuple[int, str]:
28
- completed_process = subprocess.run(
29
- [sys.executable, str(_HOOK_PATH)],
30
- input=json.dumps(payload),
31
- capture_output=True,
32
- text=True,
33
- timeout=30,
34
- check=False,
35
- )
36
- return completed_process.returncode, completed_process.stdout
37
-
38
-
39
- def _assembled_fixture_email() -> str:
40
- return "owner.fixture" + "@" + "acme-corp" + ".example" + ".io"
41
-
42
-
43
- def _assembled_unlisted_email() -> str:
44
- return "other.person" + "@" + "different-co" + ".example" + ".io"
45
-
46
-
47
- def _write_allowlist_identity(identity_path: Path, slug: str, value: str) -> None:
48
- identity_path.write_text(
49
- json.dumps({"pii_allowlisted_values": {slug: [value]}}),
50
- encoding="utf-8",
51
- )
52
-
53
-
54
- def _init_repo_with_origin_and_staged_value(
55
- repository_root: Path, origin_slug: str, staged_value: str
56
- ) -> None:
57
- repository_root.mkdir(parents=True, exist_ok=True)
58
- subprocess.run(["git", "init", "-q"], cwd=repository_root, check=True)
59
- subprocess.run(
60
- ["git", "config", "user.email", "dev@example.com"],
61
- cwd=repository_root,
62
- check=True,
63
- )
64
- subprocess.run(
65
- ["git", "config", "user.name", "Fixture Dev"], cwd=repository_root, check=True
66
- )
67
- subprocess.run(
68
- ["git", "remote", "add", "origin", "https://github.com/" + origin_slug + ".git"],
69
- cwd=repository_root,
70
- check=True,
71
- )
72
- (repository_root / "notes.md").write_text(
73
- "owner email " + staged_value + "\n", encoding="utf-8"
74
- )
75
- subprocess.run(["git", "add", "notes.md"], cwd=repository_root, check=True)
76
-
77
-
78
- def _commit_payload(repository_root: Path) -> dict[str, object]:
79
- return {
80
- "tool_name": "Bash",
81
- "tool_input": {"command": f"git -C {repository_root} commit -m x"},
82
- }
83
-
84
-
85
- def test_commit_subprocess_allows_allowlisted_value_in_the_allowlisted_repo(
86
- monkeypatch: pytest.MonkeyPatch, tmp_path: Path
87
- ) -> None:
88
- allowed_value = _assembled_fixture_email()
89
- identity_path = tmp_path / "local-identity.json"
90
- _write_allowlist_identity(identity_path, _ALLOW_SLUG, allowed_value)
91
- monkeypatch.setenv("CLAUDE_LOCAL_IDENTITY_PATH", str(identity_path))
92
- repository_root = tmp_path / "repo"
93
- _init_repo_with_origin_and_staged_value(repository_root, _ALLOW_SLUG, allowed_value)
94
- return_code, hook_stdout = _run_hook(_commit_payload(repository_root))
95
- assert return_code == 0
96
- assert hook_stdout.strip() == ""
97
-
98
-
99
- def test_commit_subprocess_blocks_allowlisted_value_in_a_different_repo(
100
- monkeypatch: pytest.MonkeyPatch, tmp_path: Path
101
- ) -> None:
102
- allowed_value = _assembled_fixture_email()
103
- identity_path = tmp_path / "local-identity.json"
104
- _write_allowlist_identity(identity_path, _ALLOW_SLUG, allowed_value)
105
- monkeypatch.setenv("CLAUDE_LOCAL_IDENTITY_PATH", str(identity_path))
106
- repository_root = tmp_path / "repo"
107
- _init_repo_with_origin_and_staged_value(repository_root, _OTHER_SLUG, allowed_value)
108
- _return_code, hook_stdout = _run_hook(_commit_payload(repository_root))
109
- assert "email" in hook_stdout
110
-
111
-
112
- def test_commit_blocks_an_unlisted_value_in_the_allowlisted_repo(
113
- monkeypatch: pytest.MonkeyPatch, tmp_path: Path
114
- ) -> None:
115
- identity_path = tmp_path / "local-identity.json"
116
- _write_allowlist_identity(identity_path, _ALLOW_SLUG, _assembled_fixture_email())
117
- monkeypatch.setenv("CLAUDE_LOCAL_IDENTITY_PATH", str(identity_path))
118
- repository_root = tmp_path / "repo"
119
- _init_repo_with_origin_and_staged_value(
120
- repository_root, _ALLOW_SLUG, _assembled_unlisted_email()
121
- )
122
- deny_reason = evaluate_bash_command(
123
- f"git -C {repository_root} commit -m x",
124
- working_directory=str(repository_root),
125
- )
126
- assert deny_reason is not None
127
- assert "email" in deny_reason
128
-
129
-
130
- def test_commit_with_missing_local_identity_still_blocks_the_value(
131
- monkeypatch: pytest.MonkeyPatch, tmp_path: Path
132
- ) -> None:
133
- monkeypatch.setenv("CLAUDE_LOCAL_IDENTITY_PATH", str(tmp_path / "absent.json"))
134
- repository_root = tmp_path / "repo"
135
- _init_repo_with_origin_and_staged_value(
136
- repository_root, _ALLOW_SLUG, _assembled_fixture_email()
137
- )
138
- deny_reason = evaluate_bash_command(
139
- f"git -C {repository_root} commit -m x",
140
- working_directory=str(repository_root),
141
- )
142
- assert deny_reason is not None
143
- assert "email" in deny_reason
144
-
145
- def _init_repo_with_staged_pii(repository_root: Path) -> None:
146
- repository_root.mkdir(parents=True, exist_ok=True)
147
- subprocess.run(["git", "init", "-q"], cwd=repository_root, check=True)
148
- subprocess.run(
149
- ["git", "config", "user.email", "dev@example.com"],
150
- cwd=repository_root,
151
- check=True,
152
- )
153
- subprocess.run(
154
- ["git", "config", "user.name", "Fixture Dev"],
155
- cwd=repository_root,
156
- check=True,
157
- )
158
- (repository_root / "notes.md").write_text(
159
- "owner email " + _assembled_fixture_email() + "\n",
160
- encoding="utf-8",
161
- )
162
- subprocess.run(["git", "add", "notes.md"], cwd=repository_root, check=True)
163
-
164
-
165
- def test_dash_c_scans_named_repo_from_unrelated_cwd(tmp_path: Path) -> None:
166
- repository_root = tmp_path / "healthy_repo"
167
- _init_repo_with_staged_pii(repository_root)
168
- deny_reason = evaluate_bash_command(
169
- f"git -C {repository_root} commit -m x",
170
- working_directory="/tmp",
171
- )
172
- assert deny_reason is not None
173
- assert "email" in deny_reason
174
-
175
-
176
- def test_cd_prefix_scans_named_repo_from_unrelated_cwd(tmp_path: Path) -> None:
177
- repository_root = tmp_path / "healthy_repo"
178
- _init_repo_with_staged_pii(repository_root)
179
- deny_reason = evaluate_bash_command(
180
- f"cd {repository_root} && git commit -m x",
181
- working_directory="/tmp",
182
- )
183
- assert deny_reason is not None
184
- assert "email" in deny_reason
185
-
186
-
187
- def test_cd_prefix_with_git_exe_scans_named_repo_from_unrelated_cwd(
188
- tmp_path: Path,
189
- ) -> None:
190
- repository_root = tmp_path / "healthy_repo"
191
- _init_repo_with_staged_pii(repository_root)
192
- deny_reason = evaluate_bash_command(
193
- f"cd {repository_root} && git.exe commit -m x",
194
- working_directory="/tmp",
195
- )
196
- assert deny_reason is not None
197
- assert "email" in deny_reason
198
-
199
-
200
- def test_removed_working_directory_does_not_block_named_repo(tmp_path: Path) -> None:
201
- repository_root = tmp_path / "healthy_repo"
202
- _init_repo_with_staged_pii(repository_root)
203
- removed_directory = tmp_path / "gone"
204
- removed_directory.mkdir()
205
- removed_directory.rmdir()
206
- deny_reason = evaluate_bash_command(
207
- f"git -C {repository_root} commit -m x",
208
- working_directory=str(removed_directory),
209
- )
210
- assert deny_reason is not None
211
- assert "email" in deny_reason
212
-
213
-
214
- def test_multiple_dash_c_compose_to_the_named_repo(tmp_path: Path) -> None:
215
- repository_root = tmp_path / "parent" / "healthy_repo"
216
- _init_repo_with_staged_pii(repository_root)
217
- deny_reason = evaluate_bash_command(
218
- f"git -C {tmp_path / 'parent'} -C healthy_repo commit -m x",
219
- working_directory="/tmp",
220
- )
221
- assert deny_reason is not None
222
- assert "email" in deny_reason
223
-
224
-
225
- def test_full_dispatch_blocks_pii_commit_from_unrelated_cwd(tmp_path: Path) -> None:
226
- repository_root = tmp_path / "healthy_repo"
227
- _init_repo_with_staged_pii(repository_root)
228
- deny_reason = evaluate(
229
- {
230
- "tool_name": "Bash",
231
- "tool_input": {"command": f"git -C {repository_root} commit -m x"},
232
- "cwd": "/tmp",
233
- }
234
- )
235
- assert deny_reason is not None
236
- assert "email" in deny_reason
237
-
238
-
239
- def test_unresolvable_named_repo_refusal_names_the_path() -> None:
240
- deny_reason = evaluate_bash_command(
241
- "git -C /nonexistent/repo/xyz commit -m x",
242
- working_directory="/tmp",
243
- )
244
- assert deny_reason is not None
245
- assert "repository root" in deny_reason
246
- assert "/nonexistent/repo/xyz" in deny_reason
247
-
248
-
249
- def test_clean_named_repo_passes(tmp_path: Path) -> None:
250
- repository_root = tmp_path / "clean_repo"
251
- repository_root.mkdir()
252
- subprocess.run(["git", "init", "-q"], cwd=repository_root, check=True)
253
- (repository_root / "readme.md").write_text("all clear\n", encoding="utf-8")
254
- subprocess.run(["git", "add", "readme.md"], cwd=repository_root, check=True)
255
- deny_reason = evaluate_bash_command(
256
- f"git -C {repository_root} commit -m x",
257
- working_directory="/tmp",
258
- )
259
- assert deny_reason is None
@@ -1,391 +0,0 @@
1
- #!/usr/bin/env python3
2
- """PreToolUse hook: deny drive-root, bare-home, and share-root filesystem searches.
3
-
4
- Blocks Bash/PowerShell commands that walk from an unscoped root — ``find /``,
5
- ``find C:\\``, ``find ~``, ``find //server/share``, recursive ``Get-ChildItem``
6
- on a drive root — so a session cannot thrash the host with millions of handles.
7
- Scoped walks under a project path stay allowed. Parallel shell storms are out of
8
- scope for this detector; the deny message still steers agents to batch shell work.
9
-
10
- ::
11
-
12
- find / -iname code_rules_gate.py flag: unscoped root
13
- find . -iname code_rules_gate.py ok: cwd scope
14
- find //server/share -name x flag: network share root
15
- find //server/share/project -name x ok: scoped under the share
16
- Get-ChildItem -Path C:\\ -Recurse flag: recursive drive root
17
- Get-ChildItem -Path .\\src -Recurse ok: project scope
18
- ls -r / ok: reverse sort, not recurse
19
- ls -R / flag: recursive root listing
20
- ls -laR / flag: clustered short options
21
- bash -c 'find / -name x' flag: string-exec unwrap
22
- """
23
-
24
- from __future__ import annotations
25
-
26
- import json
27
- import shlex
28
- import sys
29
- from pathlib import Path
30
-
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 hooks_constants.hook_block_logger import log_hook_block # noqa: E402
36
- from hooks_constants.shell_command_segments import ( # noqa: E402
37
- effective_leading_program,
38
- split_into_segments,
39
- token_basename,
40
- )
41
- from hooks_constants.unscoped_search_blocker_constants import ( # noqa: E402
42
- ALL_FIND_EXPRESSION_INTRODUCER_TOKENS,
43
- ALL_FIND_GLOBAL_OPTION_FLAGS_TAKING_A_VALUE,
44
- ALL_FIND_GLOBAL_OPTION_FLAGS_WITHOUT_VALUE,
45
- ALL_FIND_PROGRAM_BASENAMES,
46
- ALL_LISTING_PROGRAM_BASENAMES,
47
- ALL_POWERSHELL_PATH_FLAG_PREFIXES,
48
- ALL_POWERSHELL_PATH_FLAGS,
49
- ALL_POWERSHELL_RECURSE_FLAG_PREFIXES,
50
- ALL_POWERSHELL_RECURSE_FLAGS,
51
- ALL_STRING_EXEC_COMMAND_FLAGS,
52
- ALL_STRING_EXECUTING_SHELL_BASENAMES,
53
- ALL_SUPPORTED_TOOL_NAMES,
54
- ALL_TRUTHY_RECURSE_COLON_VALUES,
55
- ALL_UNIX_LS_PROGRAM_BASENAMES,
56
- ALL_UNIX_LS_RECURSE_FLAGS,
57
- ALL_UNSCOPED_HOME_LITERALS_CASEFOLD,
58
- CALLING_HOOK_NAME,
59
- CLUSTERED_SHORT_OPTION_PATTERN,
60
- CORRECTIVE_MESSAGE,
61
- DENY_DECISION,
62
- FIND_OPTIMIZATION_LEVEL_OPTION_PREFIX,
63
- FLAG_AND_VALUE_TOKEN_STRIDE,
64
- GIT_BASH_DRIVE_ROOT_PATTERN,
65
- HOOK_EVENT_NAME,
66
- POSIX_ROOT_ALIAS_PATTERN,
67
- UNC_SHARE_ROOT_PATTERN,
68
- UNIX_LS_RECURSE_SHORT_OPTION_LETTER,
69
- WINDOWS_DRIVE_ROOT_PATTERN,
70
- )
71
-
72
-
73
- def is_unscoped_search_root(path_token: str) -> bool:
74
- """Return True when a path token is a drive, home, or network-share root.
75
-
76
- ::
77
-
78
- / flag
79
- /. flag (posix root alias)
80
- /c/ flag (Git Bash drive root)
81
- C:\\ flag
82
- ~ flag
83
- //server/share flag (UNC share root)
84
- //server/share/src ok (scoped under the share)
85
- ./src ok
86
- /c/Users/x ok
87
-
88
- Args:
89
- path_token: One shell token treated as a search start path.
90
-
91
- Returns:
92
- True when the token names an unscoped filesystem root.
93
- """
94
- stripped_path = path_token.strip().strip("\"'")
95
- if not stripped_path:
96
- return False
97
- if stripped_path.casefold() in ALL_UNSCOPED_HOME_LITERALS_CASEFOLD:
98
- return True
99
- forward_slash_path = stripped_path.replace("\\", "/")
100
- if POSIX_ROOT_ALIAS_PATTERN.fullmatch(forward_slash_path):
101
- return True
102
- if GIT_BASH_DRIVE_ROOT_PATTERN.fullmatch(forward_slash_path):
103
- return True
104
- if UNC_SHARE_ROOT_PATTERN.fullmatch(forward_slash_path):
105
- return True
106
- if WINDOWS_DRIVE_ROOT_PATTERN.fullmatch(stripped_path):
107
- return True
108
- windows_drive_alias = stripped_path.rstrip(".\\/")
109
- if WINDOWS_DRIVE_ROOT_PATTERN.fullmatch(windows_drive_alias + "\\"):
110
- return True
111
- if WINDOWS_DRIVE_ROOT_PATTERN.fullmatch(windows_drive_alias):
112
- return True
113
- return False
114
-
115
-
116
- def _tokens_after_leading_program(
117
- all_segment_tokens: list[str], leading_program: str | None
118
- ) -> list[str]:
119
- if leading_program is None:
120
- return []
121
- leading_index = all_segment_tokens.index(leading_program)
122
- return all_segment_tokens[leading_index + 1 :]
123
-
124
-
125
- def _is_find_expression_token(token: str) -> bool:
126
- if token in ALL_FIND_EXPRESSION_INTRODUCER_TOKENS:
127
- return True
128
- return token.startswith("-")
129
-
130
-
131
- def _collect_find_starting_points(all_argument_tokens: list[str]) -> list[str]:
132
- all_starting_points: list[str] = []
133
- token_index = 0
134
- while token_index < len(all_argument_tokens):
135
- each_token = all_argument_tokens[token_index]
136
- if each_token in ALL_FIND_GLOBAL_OPTION_FLAGS_WITHOUT_VALUE:
137
- token_index += 1
138
- continue
139
- if each_token in ALL_FIND_GLOBAL_OPTION_FLAGS_TAKING_A_VALUE:
140
- if token_index + 1 >= len(all_argument_tokens):
141
- token_index += 1
142
- continue
143
- next_token = all_argument_tokens[token_index + 1]
144
- if is_unscoped_search_root(next_token) or _is_find_expression_token(
145
- next_token
146
- ):
147
- token_index += 1
148
- continue
149
- token_index += FLAG_AND_VALUE_TOKEN_STRIDE
150
- continue
151
- if each_token.startswith(FIND_OPTIMIZATION_LEVEL_OPTION_PREFIX):
152
- token_index += 1
153
- continue
154
- if _is_find_expression_token(each_token):
155
- break
156
- all_starting_points.append(each_token)
157
- token_index += 1
158
- return all_starting_points
159
-
160
-
161
- def _segment_has_unscoped_find(all_segment_tokens: list[str]) -> bool:
162
- leading_program = effective_leading_program(all_segment_tokens)
163
- if leading_program is None:
164
- return False
165
- if token_basename(leading_program) not in ALL_FIND_PROGRAM_BASENAMES:
166
- return False
167
- all_starting_points = _collect_find_starting_points(
168
- _tokens_after_leading_program(all_segment_tokens, leading_program)
169
- )
170
- return any(
171
- is_unscoped_search_root(each_start) for each_start in all_starting_points
172
- )
173
-
174
-
175
- def _token_is_unix_ls_recurse_flag(token: str) -> bool:
176
- """Return True for an ls token that turns the listing recursive.
177
-
178
- ::
179
-
180
- -R flag
181
- -laR flag (clustered short options)
182
- -Rl flag
183
- --recursive flag
184
- -r ok (reverse sort, not recurse)
185
- -la ok
186
-
187
- Args:
188
- token: One argument token from an ``ls`` segment.
189
-
190
- Returns:
191
- True when the token carries the recursive option.
192
- """
193
- if token in ALL_UNIX_LS_RECURSE_FLAGS:
194
- return True
195
- if not CLUSTERED_SHORT_OPTION_PATTERN.fullmatch(token):
196
- return False
197
- return UNIX_LS_RECURSE_SHORT_OPTION_LETTER in token
198
-
199
-
200
- def _token_is_recurse_flag(token: str, program_basename: str) -> bool:
201
- if program_basename in ALL_UNIX_LS_PROGRAM_BASENAMES:
202
- return _token_is_unix_ls_recurse_flag(token)
203
- lowered_token = token.lower()
204
- if lowered_token in ALL_POWERSHELL_RECURSE_FLAGS:
205
- return True
206
- for each_prefix in ALL_POWERSHELL_RECURSE_FLAG_PREFIXES:
207
- if lowered_token.startswith(each_prefix):
208
- colon_value = lowered_token[len(each_prefix) :]
209
- return colon_value in ALL_TRUTHY_RECURSE_COLON_VALUES
210
- return False
211
-
212
-
213
- def _path_value_from_path_flag_token(token: str) -> str | None:
214
- lowered_token = token.lower()
215
- for each_prefix in ALL_POWERSHELL_PATH_FLAG_PREFIXES:
216
- if lowered_token.startswith(each_prefix):
217
- return token[len(each_prefix) :]
218
- return None
219
-
220
-
221
- def _collect_get_child_item_path_tokens(all_argument_tokens: list[str]) -> list[str]:
222
- all_path_tokens: list[str] = []
223
- token_index = 0
224
- while token_index < len(all_argument_tokens):
225
- each_token = all_argument_tokens[token_index]
226
- lowered_token = each_token.lower()
227
- path_from_glued_flag = _path_value_from_path_flag_token(each_token)
228
- if path_from_glued_flag is not None:
229
- if path_from_glued_flag:
230
- all_path_tokens.append(path_from_glued_flag)
231
- token_index += 1
232
- continue
233
- if lowered_token in ALL_POWERSHELL_PATH_FLAGS:
234
- if token_index + 1 < len(all_argument_tokens):
235
- all_path_tokens.append(all_argument_tokens[token_index + 1])
236
- token_index += FLAG_AND_VALUE_TOKEN_STRIDE
237
- continue
238
- if lowered_token == "/s" or each_token.startswith("-"):
239
- token_index += 1
240
- continue
241
- all_path_tokens.append(each_token)
242
- token_index += 1
243
- return all_path_tokens
244
-
245
-
246
- def _segment_has_unscoped_recursive_listing(all_segment_tokens: list[str]) -> bool:
247
- leading_program = effective_leading_program(all_segment_tokens)
248
- if leading_program is None:
249
- return False
250
- program_basename = token_basename(leading_program)
251
- if program_basename not in ALL_LISTING_PROGRAM_BASENAMES:
252
- return False
253
- all_argument_tokens = _tokens_after_leading_program(
254
- all_segment_tokens, leading_program
255
- )
256
- has_recurse_flag = any(
257
- _token_is_recurse_flag(each_token, program_basename)
258
- for each_token in all_argument_tokens
259
- )
260
- if not has_recurse_flag:
261
- return False
262
- all_path_tokens = _collect_get_child_item_path_tokens(all_argument_tokens)
263
- if not all_path_tokens:
264
- return False
265
- return any(
266
- is_unscoped_search_root(each_path) for each_path in all_path_tokens
267
- )
268
-
269
-
270
- def _string_exec_inner_command(all_segment_tokens: list[str]) -> str | None:
271
- leading_program = effective_leading_program(all_segment_tokens)
272
- if leading_program is None:
273
- return None
274
- if token_basename(leading_program) not in ALL_STRING_EXECUTING_SHELL_BASENAMES:
275
- return None
276
- all_argument_tokens = _tokens_after_leading_program(
277
- all_segment_tokens, leading_program
278
- )
279
- token_index = 0
280
- while token_index < len(all_argument_tokens):
281
- each_token = all_argument_tokens[token_index]
282
- if each_token.lower() in ALL_STRING_EXEC_COMMAND_FLAGS:
283
- if token_index + 1 < len(all_argument_tokens):
284
- return all_argument_tokens[token_index + 1]
285
- return None
286
- token_index += 1
287
- return None
288
-
289
-
290
- def _all_command_tokenizations(command: str) -> list[list[str]]:
291
- """Return POSIX and non-POSIX tokenizations so Windows paths stay intact.
292
-
293
- Git Bash commands need POSIX splitting (quote rules). PowerShell paths like
294
- ``C:\\`` need non-POSIX splitting so the backslash is not an escape. Both
295
- are tried; an empty or failed split is dropped.
296
- """
297
- all_tokenizations: list[list[str]] = []
298
- for each_posix_mode in (True, False):
299
- try:
300
- all_tokens = shlex.split(command, posix=each_posix_mode)
301
- except ValueError:
302
- continue
303
- if all_tokens:
304
- all_tokenizations.append(all_tokens)
305
- return all_tokenizations
306
-
307
-
308
- def find_unscoped_search_violation(command: str) -> str | None:
309
- """Return the deny message for an unscoped tree walk, or None to allow.
310
-
311
- ::
312
-
313
- find / -iname x.py flag
314
- find packages -iname x.py ok
315
- Get-ChildItem -Path C:\\ -Recurse flag
316
- Get-ChildItem -Path .\\src -Recurse ok
317
- find //server/share -name x flag
318
- find //server/share/project -name x ok
319
- bash -c 'find / -name x' flag
320
- ls -r / ok
321
- ls -R / flag
322
- ls -laR / flag
323
-
324
- Tokenizes the command under POSIX and non-POSIX shlex modes, splits on shell
325
- control operators, and evaluates each simple-command segment. A ``find``
326
- segment with an unscoped starting point denies. A recursive listing segment
327
- (PowerShell ``Get-ChildItem``/``gci``/``dir`` or Unix ``ls -R``, including a
328
- clustered short-option token such as ``-laR``) with an unscoped path denies.
329
- A ``bash -c`` / ``pwsh -Command`` wrapper re-runs the check on its string
330
- argument. Returns None when neither tokenization yields tokens.
331
-
332
- Args:
333
- command: The raw Bash or PowerShell command string from the tool input.
334
-
335
- Returns:
336
- The corrective deny message when a segment walks from an unscoped root,
337
- else None.
338
- """
339
- for each_tokenization in _all_command_tokenizations(command):
340
- for each_segment in split_into_segments(each_tokenization):
341
- if _segment_has_unscoped_find(each_segment):
342
- return CORRECTIVE_MESSAGE
343
- if _segment_has_unscoped_recursive_listing(each_segment):
344
- return CORRECTIVE_MESSAGE
345
- inner_command = _string_exec_inner_command(each_segment)
346
- if inner_command is not None:
347
- nested_denial = find_unscoped_search_violation(inner_command)
348
- if nested_denial is not None:
349
- return nested_denial
350
- return None
351
-
352
-
353
- def main() -> None:
354
- try:
355
- hook_input = json.load(sys.stdin)
356
- except json.JSONDecodeError:
357
- sys.exit(0)
358
-
359
- tool_name = hook_input.get("tool_name", "")
360
- if tool_name not in ALL_SUPPORTED_TOOL_NAMES:
361
- sys.exit(0)
362
-
363
- command = hook_input.get("tool_input", {}).get("command", "")
364
- if not command:
365
- sys.exit(0)
366
-
367
- deny_reason = find_unscoped_search_violation(command)
368
- if deny_reason is None:
369
- sys.exit(0)
370
-
371
- deny_payload = {
372
- "hookSpecificOutput": {
373
- "hookEventName": HOOK_EVENT_NAME,
374
- "permissionDecision": DENY_DECISION,
375
- "permissionDecisionReason": deny_reason,
376
- }
377
- }
378
- log_hook_block(
379
- calling_hook_name=CALLING_HOOK_NAME,
380
- hook_event=HOOK_EVENT_NAME,
381
- block_reason=deny_reason,
382
- tool_name=tool_name,
383
- offending_input_preview=command,
384
- )
385
- print(json.dumps(deny_payload))
386
- sys.stdout.flush()
387
- sys.exit(0)
388
-
389
-
390
- if __name__ == "__main__":
391
- main()