claude-dev-env 2.8.0 → 2.10.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 (323) hide show
  1. package/CLAUDE.md +8 -2
  2. package/_shared/pr-loop/CLAUDE.md +1 -1
  3. package/_shared/pr-loop/audit-contract.md +17 -6
  4. package/_shared/pr-loop/audit-reply-template.md +4 -4
  5. package/_shared/pr-loop/code-rules-gate.md +3 -5
  6. package/_shared/pr-loop/fix-protocol.md +2 -3
  7. package/_shared/pr-loop/gh-payloads.md +1 -1
  8. package/_shared/pr-loop/scripts/CLAUDE.md +1 -1
  9. package/_shared/pr-loop/scripts/README.md +1 -1
  10. package/_shared/pr-loop/scripts/code_rules_gate.py +2 -0
  11. package/_shared/pr-loop/scripts/code_rules_gate_parts/gate_running.py +16 -1
  12. package/_shared/pr-loop/scripts/code_rules_gate_parts/git_blob_readers.py +11 -5
  13. package/_shared/pr-loop/scripts/preflight.py +9 -4
  14. package/_shared/pr-loop/scripts/reviews_disabled.py +50 -22
  15. package/_shared/pr-loop/scripts/tests/conftest.py +20 -0
  16. package/_shared/pr-loop/scripts/tests/test_claude_permissions_common.py +6 -6
  17. package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +50 -6
  18. package/_shared/pr-loop/scripts/tests/test_revoke_project_claude_permissions.py +1 -1
  19. package/_shared/pr-loop/state-schema.md +5 -14
  20. package/agents/CLAUDE.md +2 -2
  21. package/agents/clean-coder.md +62 -562
  22. package/agents/code-quality-agent.md +10 -2
  23. package/agents/code-verifier.md +1 -1
  24. package/agents/test_agent_frontmatter.py +58 -40
  25. package/audit-rubrics/CLAUDE.md +2 -1
  26. package/audit-rubrics/audit-categories.json +704 -0
  27. package/audit-rubrics/prompts/category-i-concurrency.md +1 -1
  28. package/bin/CLAUDE.md +16 -5
  29. package/bin/ever-shipped-skills.mjs +2 -0
  30. package/bin/install-plan.mjs +402 -0
  31. package/bin/install-transaction.mjs +455 -0
  32. package/bin/install.mjs +593 -147
  33. package/bin/install.plan.test.mjs +194 -0
  34. package/bin/install.profile-root.test.mjs +154 -0
  35. package/bin/install.profiles.test.mjs +253 -0
  36. package/bin/install.settings-defaults.test.mjs +200 -0
  37. package/bin/install.transaction.test.mjs +400 -0
  38. package/bin/install.uninstall-transaction.test.mjs +418 -0
  39. package/bin/merge_managed_permissions.mjs +130 -0
  40. package/bin/resolve-install-root.mjs +181 -0
  41. package/bin/select-install-targets.mjs +401 -0
  42. package/commands/CLAUDE.md +0 -2
  43. package/docs/CODE_RULES.md +4 -2
  44. package/docs/references/CLAUDE.md +3 -2
  45. package/docs/references/advisor-tool.md +44 -6
  46. package/docs/references/prose-style-enforcement.md +25 -0
  47. package/docs/references/team-advisor-skill.md +14 -8
  48. package/hooks/blocking/CLAUDE.md +6 -6
  49. package/hooks/blocking/_path_setup.py +9 -5
  50. package/hooks/blocking/code_rules_docstrings.py +124 -30
  51. package/hooks/blocking/code_rules_enforcer.py +161 -16
  52. package/hooks/blocking/code_rules_shared.py +40 -23
  53. package/hooks/blocking/config/CLAUDE.md +3 -5
  54. package/hooks/blocking/config/prose_style_enforcement_constants.py +38 -0
  55. package/hooks/blocking/config/test_prose_style_enforcement_constants.py +45 -0
  56. package/hooks/blocking/eli11_reply_enforcer.py +70 -113
  57. package/hooks/blocking/hedging_language_blocker.py +103 -20
  58. package/hooks/blocking/hook_prose_detector_consistency.py +6 -0
  59. package/hooks/blocking/intent_only_ending_blocker.py +6 -0
  60. package/hooks/blocking/plain_language_blocker.py +139 -20
  61. package/hooks/blocking/pre_tool_use_dispatcher.py +102 -20
  62. package/hooks/blocking/state_description_blocker.py +7 -1
  63. package/hooks/blocking/tdd_enforcer.py +8 -0
  64. package/hooks/blocking/test__path_setup.py +28 -0
  65. package/hooks/blocking/test_code_rules_enforcer_agent_home_tooling.py +99 -0
  66. package/hooks/blocking/test_code_rules_enforcer_docstring_args_span_scope.py +232 -10
  67. package/hooks/blocking/test_code_rules_enforcer_ephemeral.py +1 -1
  68. package/hooks/blocking/test_code_rules_enforcer_join_separator_magic.py +41 -0
  69. package/hooks/blocking/test_code_rules_enforcer_string_magic.py +98 -0
  70. package/hooks/blocking/test_eli11_reply_enforcer.py +98 -165
  71. package/hooks/blocking/test_hedging_language_blocker.py +120 -1
  72. package/hooks/blocking/test_hook_prose_detector_consistency.py +28 -8
  73. package/hooks/blocking/test_intent_only_ending_blocker.py +27 -2
  74. package/hooks/blocking/test_package_inventory_stale_blocker.py +11 -4
  75. package/hooks/blocking/test_plain_language_blocker.py +129 -19
  76. package/hooks/blocking/test_plain_language_blocker_allowlist.py +70 -26
  77. package/hooks/blocking/test_pre_tool_use_dispatcher.py +99 -26
  78. package/hooks/blocking/test_pre_tool_use_dispatcher_native.py +87 -50
  79. package/hooks/blocking/test_state_description_blocker.py +45 -2
  80. package/hooks/blocking/test_stop_dispatcher.py +11 -7
  81. package/hooks/blocking/test_volatile_path_in_post_blocker.py +12 -12
  82. package/hooks/blocking/volatile_path_in_post_blocker.py +2 -2
  83. package/hooks/hooks.json +15 -0
  84. package/hooks/hooks_constants/CLAUDE.md +14 -3
  85. package/hooks/hooks_constants/ask_user_question_shape.py +281 -0
  86. package/hooks/hooks_constants/code_rules_enforcer_constants.py +2 -1
  87. package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -0
  88. package/hooks/hooks_constants/eli11_reply_enforcer_constants.py +5 -12
  89. package/hooks/hooks_constants/hedging_uncertainty_constants.py +42 -0
  90. package/hooks/hooks_constants/issue_tracker_session_starter_constants.py +23 -0
  91. package/hooks/hooks_constants/orchestrator_auto_starter_constants.py +23 -0
  92. package/hooks/hooks_constants/piped_pytest_blocker_constants.py +4 -1
  93. package/hooks/hooks_constants/plain_language_blocker_constants.py +4 -1
  94. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +6 -0
  95. package/hooks/hooks_constants/project_paths_reader.py +31 -4
  96. package/hooks/hooks_constants/prose_matcher_precision_constants.py +40 -0
  97. package/hooks/hooks_constants/pytest_invocation.py +354 -0
  98. package/hooks/hooks_constants/session_start_injector.py +163 -0
  99. package/hooks/hooks_constants/session_start_injector_constants.py +46 -0
  100. package/hooks/hooks_constants/shell_command_pipeline.py +397 -0
  101. package/hooks/hooks_constants/shell_command_segments.py +5 -0
  102. package/hooks/hooks_constants/test_ask_user_question_shape.py +167 -0
  103. package/hooks/hooks_constants/test_project_paths_reader.py +29 -0
  104. package/hooks/hooks_constants/test_prose_metrics_parity.py +8 -0
  105. package/hooks/hooks_constants/test_pytest_invocation.py +130 -0
  106. package/hooks/hooks_constants/test_session_start_injector.py +168 -0
  107. package/hooks/hooks_constants/test_shell_command_pipeline.py +135 -0
  108. package/hooks/hooks_constants/volatile_path_in_post_blocker_constants.py +1 -1
  109. package/hooks/hooks_constants/working_style_prompt_constants.py +30 -0
  110. package/hooks/observability/CLAUDE.md +2 -0
  111. package/hooks/observability/prose_matcher_advisory.py +237 -0
  112. package/hooks/observability/test_prose_matcher_advisory.py +143 -0
  113. package/hooks/session/CLAUDE.md +9 -1
  114. package/hooks/session/_path_setup.py +13 -0
  115. package/hooks/session/issue_tracker_session_starter.py +135 -0
  116. package/hooks/session/orchestrator_auto_starter.py +100 -0
  117. package/hooks/session/test__path_setup.py +28 -0
  118. package/hooks/session/test_issue_tracker_session_starter.py +104 -0
  119. package/hooks/session/test_orchestrator_auto_starter.py +99 -0
  120. package/hooks/session/test_working_style_prompt.py +47 -0
  121. package/hooks/session/untracked_repo_detector.py +1 -24
  122. package/hooks/session/working_style_prompt.py +36 -0
  123. package/hooks/validators/_path_setup.py +19 -0
  124. package/hooks/validators/run_all_validators.py +8 -13
  125. package/installable-surfaces.manifest.json +21 -0
  126. package/output-styles/CLAUDE.md +15 -0
  127. package/package.json +5 -2
  128. package/rules/CLAUDE.md +1 -0
  129. package/rules/code-standards.md +33 -7
  130. package/rules/durable-post-artifacts.md +2 -2
  131. package/rules/eli11-replies.md +7 -2
  132. package/rules/hedging-claims.md +4 -2
  133. package/rules/long-horizon-autonomy.md +3 -1
  134. package/rules/opus5-communication-contract.md +45 -0
  135. package/rules/plain-language.md +2 -2
  136. package/rules/research-mode.md +1 -1
  137. package/scripts/CLAUDE.md +13 -2
  138. package/scripts/Sync-RepoMain.ps1 +215 -0
  139. package/scripts/active_capability_references.py +218 -0
  140. package/scripts/ci/windows-installer-lifecycle.ps1 +78 -0
  141. package/scripts/claude_chain_runner.py +394 -6
  142. package/scripts/claude_chain_usage.py +1 -1
  143. package/scripts/codex_compat_materializer.py +105 -85
  144. package/scripts/dev_env_scripts_constants/CLAUDE.md +3 -1
  145. package/scripts/dev_env_scripts_constants/active_capability_constants.py +46 -0
  146. package/scripts/dev_env_scripts_constants/claude_chain_constants.py +74 -0
  147. package/scripts/dev_env_scripts_constants/grok_run_ledger_constants.py +50 -0
  148. package/scripts/dev_env_scripts_constants/grok_worker_constants.py +104 -0
  149. package/scripts/dev_env_scripts_constants/verify_installable_package_constants.py +116 -0
  150. package/scripts/grok_patch_artifacts.py +123 -0
  151. package/scripts/grok_run_ledger.py +318 -0
  152. package/scripts/profile-isolation-launchers/config/mcp-bundles.json +25 -0
  153. package/scripts/profile-isolation-launchers/config/profile-isolation-constants.mjs +60 -0
  154. package/scripts/profile-isolation-launchers/config/profiles.manifest.json +54 -0
  155. package/scripts/profile-isolation-launchers/config/shared-allowlist.json +64 -0
  156. package/scripts/profile-isolation-launchers/launcher-runtime.mjs +180 -0
  157. package/scripts/profile-isolation-launchers/lib/profile-manifest.mjs +288 -0
  158. package/scripts/profile-isolation-launchers/mcp-bundles.mjs +275 -0
  159. package/scripts/profile-isolation-launchers/profile-isolation-contract.test.mjs +221 -0
  160. package/scripts/profile-isolation-launchers/tests/launcher-runtime.test.mjs +108 -0
  161. package/scripts/profile-isolation-launchers/tests/mcp-bundles.test.mjs +147 -0
  162. package/scripts/profile-isolation-launchers/tests/shortcut-contract.test.ps1 +102 -0
  163. package/scripts/profile-isolation-launchers/tests/version-compatibility.test.mjs +210 -0
  164. package/scripts/profile-isolation-launchers/version-compatibility.mjs +299 -0
  165. package/scripts/profile-isolation-launchers/windows/shortcut-inventory.ps1 +127 -0
  166. package/scripts/profile-isolation-launchers/windows/shortcut-manifest.json +51 -0
  167. package/scripts/profile-isolation-launchers/windows/shortcut-reconcile.ps1 +77 -0
  168. package/scripts/spawn_grok_batch.py +556 -9
  169. package/scripts/test_active_capability_references.py +108 -0
  170. package/scripts/test_claude_chain_runner.py +414 -82
  171. package/scripts/test_claude_chain_usage.py +12 -12
  172. package/scripts/test_grok_patch_artifacts.py +82 -0
  173. package/scripts/test_grok_run_ledger.py +116 -0
  174. package/scripts/test_resolve_worker_spawn.py +2 -2
  175. package/scripts/test_spawn_grok_batch.py +295 -0
  176. package/scripts/test_verify_installable_package.py +208 -0
  177. package/scripts/tests/test_codex_compat_materializer.py +33 -0
  178. package/scripts/verify_installable_package.py +612 -0
  179. package/settings.json +10 -0
  180. package/skills/CLAUDE.md +6 -2
  181. package/skills/_shared/CLAUDE.md +37 -4
  182. package/skills/_shared/advisor/CLAUDE.md +9 -0
  183. package/skills/_shared/advisor/advisor-protocol.md +5 -0
  184. package/skills/_shared/advisor/scripts/README.md +9 -0
  185. package/skills/_shared/end-of-run-gotcha-recommendations.md +156 -0
  186. package/skills/_shared/pr-loop/CLAUDE.md +18 -1
  187. package/skills/_shared/pr-loop/audit-contract.md +5 -0
  188. package/skills/_shared/pr-loop/audit-reply-template.md +5 -0
  189. package/skills/_shared/pr-loop/code-rules-gate.md +5 -0
  190. package/skills/_shared/pr-loop/fix-protocol.md +5 -0
  191. package/skills/_shared/pr-loop/gh-payloads.md +5 -0
  192. package/skills/_shared/pr-loop/post-audit-thread-contract.md +5 -0
  193. package/skills/_shared/pr-loop/precatch-rubric.md +5 -0
  194. package/skills/_shared/pr-loop/scripts/CLAUDE.md +9 -1
  195. package/skills/_shared/pr-loop/scripts/RUNTIME_SCRIPTS.md +29 -0
  196. package/skills/_shared/pr-loop/scripts/audit_category_schema.py +355 -0
  197. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/CLAUDE.md +1 -0
  198. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/audit_category_schema_constants.py +32 -0
  199. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py +7 -19
  200. package/skills/_shared/pr-loop/scripts/test_audit_category_schema.py +94 -0
  201. package/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +21 -0
  202. package/skills/_shared/pr-loop/state-schema.md +5 -0
  203. package/skills/_shared/pr-loop/worker-spawn.md +5 -0
  204. package/skills/autoconverge/reference/convergence.md +2 -1
  205. package/skills/autoconverge/reference/stop-conditions.md +5 -3
  206. package/skills/beat-sheet/SKILL.md +54 -0
  207. package/skills/beat-sheet/reference/visual-beats.md +29 -0
  208. package/skills/bugteam/CONSTRAINTS.md +4 -4
  209. package/skills/bugteam/EXAMPLES.md +1 -1
  210. package/skills/bugteam/reference/README.md +1 -1
  211. package/skills/e-code-review/SKILL.md +29 -3
  212. package/skills/e-code-review/reference/effort-evaluation.md +35 -0
  213. package/skills/e-code-review/reference/medium.md +15 -4
  214. package/skills/e-code-review/reference/runner-selection.md +40 -0
  215. package/skills/e-code-review/scripts/config/e_code_review_effort_constants/__init__.py +41 -0
  216. package/skills/e-code-review/scripts/config/e_code_review_effort_constants/effort_constants.py +40 -0
  217. package/skills/e-code-review/scripts/e_code_review_scripts_constants/__init__.py +1 -0
  218. package/skills/e-code-review/scripts/e_code_review_scripts_constants/finding_pipeline_constants.py +49 -0
  219. package/skills/e-code-review/scripts/e_code_review_scripts_constants/grok_code_review_constants.py +55 -0
  220. package/skills/e-code-review/scripts/effort_defaults_evidence.json +186 -0
  221. package/skills/e-code-review/scripts/effort_evaluation.py +362 -0
  222. package/skills/e-code-review/scripts/finding_pipeline.py +140 -0
  223. package/skills/e-code-review/scripts/fixtures/demanding.json +26 -0
  224. package/skills/e-code-review/scripts/fixtures/easy.json +14 -0
  225. package/skills/e-code-review/scripts/fixtures/medium.json +20 -0
  226. package/skills/e-code-review/scripts/grok_code_review.py +230 -0
  227. package/skills/e-code-review/scripts/test_effort_evaluation.py +180 -0
  228. package/skills/e-code-review/scripts/test_finding_pipeline.py +197 -0
  229. package/skills/e-code-review/scripts/test_grok_code_review.py +289 -0
  230. package/skills/grok-spawn/SKILL.md +5 -0
  231. package/skills/orchestrator/SKILL.md +5 -0
  232. package/skills/plan-to-pr/scripts/create_packet.py +4 -4
  233. package/skills/plan-to-pr/scripts/load_skill_constants.py +41 -0
  234. package/skills/plan-to-pr/scripts/validate_packet.py +4 -4
  235. package/skills/plan-to-pr/scripts/validate_protocol.py +4 -1
  236. package/skills/plan-to-pr/scripts/validate_run.py +4 -1
  237. package/skills/pr-converge/scripts/check_convergence.py +21 -19
  238. package/skills/pr-converge/scripts/check_convergence_availability.py +50 -7
  239. package/skills/pr-converge/scripts/conftest.py +35 -0
  240. package/skills/pr-converge/scripts/test_check_convergence_availability.py +65 -0
  241. package/skills/pr-converge/scripts/test_check_convergence_codex.py +11 -1
  242. package/skills/pr-converge/scripts/test_check_convergence_contract.py +9 -2
  243. package/skills/pr-loop-cloud-transport/SKILL.md +1 -1
  244. package/skills/rebase/SKILL.md +15 -3
  245. package/skills/reviewer-gates/SKILL.md +2 -2
  246. package/skills/show/SKILL.md +51 -0
  247. package/skills/show/references/accessibility.md +7 -0
  248. package/skills/show/references/art.md +3 -0
  249. package/skills/show/references/charts.md +3 -0
  250. package/skills/show/references/core-design.md +14 -0
  251. package/skills/show/references/erds.md +3 -0
  252. package/skills/show/references/flowcharts.md +3 -0
  253. package/skills/show/references/host-and-html.md +3 -0
  254. package/skills/show/references/illustrative-diagrams.md +10 -0
  255. package/skills/show/references/interaction.md +3 -0
  256. package/skills/show/references/mockups.md +3 -0
  257. package/skills/show/references/quality-gates.md +7 -0
  258. package/skills/show/references/structural-diagrams.md +3 -0
  259. package/skills/show/references/subject-inventory.md +21 -0
  260. package/skills/show/references/svg-contract.md +22 -0
  261. package/skills/show/routing.yaml +30 -0
  262. package/skills/show/samples/pr1262-v2.svg +222 -0
  263. package/skills/show/scripts/README.md +6 -0
  264. package/skills/show/scripts/validate-artifact.py +91 -0
  265. package/skills/show/scripts/validate-package.py +18 -0
  266. package/skills/show/templates/html-widget.html +4 -0
  267. package/skills/show/templates/svg-base.svg +19 -0
  268. package/skills/show/tests/fixtures/css-var.svg +6 -0
  269. package/skills/show/tests/fixtures/dead-ref.svg +7 -0
  270. package/skills/show/tests/fixtures/filled-glyph.svg +8 -0
  271. package/skills/show/tests/fixtures/inherited-fill.svg +18 -0
  272. package/skills/show/tests/fixtures/invalid.svg +1 -0
  273. package/skills/show/tests/fixtures/large-canvas.svg +21 -0
  274. package/skills/show/tests/fixtures/unfilled-connector.svg +15 -0
  275. package/skills/show/tests/fixtures/valid.html +1 -0
  276. package/skills/show/tests/test_validate-artifact.py +74 -0
  277. package/skills/show/tests/test_validators.py +59 -0
  278. package/skills/show/workflows/create-visual.md +13 -0
  279. package/skills/show/workflows/review-visual.md +20 -0
  280. package/skills/split-pr/SKILL.md +85 -0
  281. package/skills/split-pr/reference/path-layers.md +16 -0
  282. package/skills/split-pr/reference/proposal-format.md +15 -0
  283. package/skills/split-pr/reference/split-further-loop.md +10 -0
  284. package/skills/split-pr/reference/splitting-principles.md +26 -0
  285. package/skills/split-pr/scripts/analyze_pr.py +279 -0
  286. package/skills/split-pr/scripts/categorize_files.py +106 -0
  287. package/skills/split-pr/scripts/config/__init__.py +1 -0
  288. package/skills/split-pr/scripts/config/dependency_constants.py +14 -0
  289. package/skills/split-pr/scripts/config/git_operations_constants.py +36 -0
  290. package/skills/split-pr/scripts/config/packing_constants.py +61 -0
  291. package/skills/split-pr/scripts/config/plan_constants.py +49 -0
  292. package/skills/split-pr/scripts/config/split_pr_constants.py +110 -0
  293. package/skills/split-pr/scripts/execute_split_slices.py +82 -0
  294. package/skills/split-pr/scripts/pack_files_into_slices.py +212 -0
  295. package/skills/split-pr/scripts/split_pr_dependency_graph.py +70 -0
  296. package/skills/split-pr/scripts/split_pr_git_operations.py +184 -0
  297. package/skills/split-pr/scripts/split_pr_layer_order.py +58 -0
  298. package/skills/split-pr/scripts/split_pr_paginate.py +119 -0
  299. package/skills/split-pr/scripts/split_pr_process_runner.py +52 -0
  300. package/skills/split-pr/scripts/split_pr_script_types.py +126 -0
  301. package/skills/split-pr/scripts/split_pr_title.py +41 -0
  302. package/skills/split-pr/scripts/test_analyze_pr.py +228 -0
  303. package/skills/split-pr/scripts/test_categorize_files.py +55 -0
  304. package/skills/split-pr/scripts/test_categorize_files_packing.py +59 -0
  305. package/skills/split-pr/scripts/test_execute_split_slices.py +99 -0
  306. package/skills/split-pr/scripts/test_split_pr_dependency_graph.py +47 -0
  307. package/skills/split-pr/scripts/test_split_pr_git_operations.py +125 -0
  308. package/skills/split-pr/scripts/test_split_pr_layer_order.py +36 -0
  309. package/skills/split-pr/scripts/test_split_pr_paginate.py +65 -0
  310. package/skills/split-pr/scripts/test_split_pr_script_types.py +73 -0
  311. package/skills/split-pr/scripts/test_split_pr_title.py +28 -0
  312. package/skills/split-pr/scripts/test_verify_dependency_graph.py +46 -0
  313. package/skills/split-pr/scripts/test_verify_plan.py +56 -0
  314. package/skills/split-pr/scripts/test_verify_plan_contract.py +50 -0
  315. package/skills/split-pr/scripts/test_verify_plan_path_normalization.py +45 -0
  316. package/skills/split-pr/scripts/verify_dependency_graph.py +111 -0
  317. package/skills/split-pr/scripts/verify_plan.py +139 -0
  318. package/skills/task-build/reference/tool-routing.md +3 -0
  319. package/skills/team-advisor/SKILL.md +23 -44
  320. package/system-prompts/software-engineer.xml +17 -5
  321. package/commands/initialize.md +0 -90
  322. package/commands/stubcheck.md +0 -88
  323. package/skills/test_markdown_link_integrity.py +0 -107
@@ -0,0 +1,130 @@
1
+ """Recognize and allow fixtures for pytest_invocation segment classification."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import pytest
6
+
7
+ from hooks_constants.pytest_invocation import (
8
+ segment_reports_a_pytest_exit_code,
9
+ segment_runs_pytest,
10
+ )
11
+ from hooks_constants.shell_command_pipeline import pipeline_segments_for_command
12
+
13
+
14
+ def _first_segment_tokens(command: str) -> list[str]:
15
+ all_pairs = pipeline_segments_for_command(command)
16
+ assert all_pairs, f"expected at least one segment for {command!r}"
17
+ return all_pairs[0][0]
18
+
19
+
20
+ @pytest.mark.parametrize(
21
+ "all_segment_tokens",
22
+ [
23
+ ["pytest", "tests"],
24
+ ["pytest.exe", "tests"],
25
+ ["pytest.bat", "tests"],
26
+ ["py.test", "tests"],
27
+ ["python", "-m", "pytest"],
28
+ ["python", "-mpytest"],
29
+ ["python3", "-m", "pytest", "tests"],
30
+ ["pythonw", "-m", "pytest"],
31
+ ["pypy3", "-m", "pytest", "tests"],
32
+ ["pypy3", "--jit", "off", "-m", "pytest"],
33
+ ["py", "-3", "-m", "pytest"],
34
+ [r"C:\Python313\python.exe", "-m", "pytest"],
35
+ ["time", "pytest", "tests"],
36
+ ["sudo", "pytest", "tests"],
37
+ ["sudo", "-u", "ci", "pytest", "tests"],
38
+ ["sudo", "-nu", "ci", "pytest", "tests"],
39
+ ["uv", "run", "pytest", "tests"],
40
+ ["uv", "run", "--frozen", "pytest", "tests"],
41
+ ["uv", "tool", "run", "pytest", "tests"],
42
+ ["uvx", "pytest", "tests"],
43
+ ["poetry", "run", "pytest", "tests"],
44
+ ["pipenv", "run", "pytest", "tests"],
45
+ ["pdm", "run", "pytest", "tests"],
46
+ ["hatch", "run", "pytest", "tests"],
47
+ ["rye", "run", "pytest", "tests"],
48
+ ["coverage", "run", "-m", "pytest", "tests"],
49
+ ["python", "-m", "coverage", "run", "-m", "pytest"],
50
+ ["sudo", "uv", "run", "pytest", "tests"],
51
+ ],
52
+ )
53
+ def test_should_recognize_pytest_invocation_forms(
54
+ all_segment_tokens: list[str],
55
+ ) -> None:
56
+ assert segment_runs_pytest(all_segment_tokens) is True
57
+
58
+
59
+ @pytest.mark.parametrize(
60
+ "all_segment_tokens",
61
+ [
62
+ ["git", "status"],
63
+ ["python", "-m", "mypy", "."],
64
+ ["python", "myscript.py", "-m", "pytest"],
65
+ ["uv", "run", "--with", "pytest", "mypy", "."],
66
+ ["uv", "sync"],
67
+ ["uv", "tool", "install", "pytest"],
68
+ ["coverage", "run", "-m", "mypy", "."],
69
+ ["coverage", "report"],
70
+ ["sudo", "apt", "update"],
71
+ ["pyright", "-m", "pytest", "."],
72
+ ["echo", "pytest"],
73
+ ["ls", "pytest"],
74
+ [],
75
+ ],
76
+ )
77
+ def test_should_allow_non_pytest_segment_forms(
78
+ all_segment_tokens: list[str],
79
+ ) -> None:
80
+ assert segment_runs_pytest(all_segment_tokens) is False
81
+
82
+
83
+ @pytest.mark.parametrize(
84
+ "all_segment_tokens",
85
+ [
86
+ ["bash", "-c", "pytest tests"],
87
+ ["bash", "-euc", "pytest tests"],
88
+ ["bash", "-euo", "pipefail", "-c", "python -m pytest"],
89
+ ["sh", "-c", "pytest tests"],
90
+ ["pwsh", "-Command", "pytest tests"],
91
+ ["cmd", "/c", "python", "-m", "pytest", "tests"],
92
+ ],
93
+ )
94
+ def test_should_recognize_shell_string_exec_pytest(
95
+ all_segment_tokens: list[str],
96
+ ) -> None:
97
+ assert segment_reports_a_pytest_exit_code(all_segment_tokens) is True
98
+
99
+
100
+ @pytest.mark.parametrize(
101
+ "all_segment_tokens",
102
+ [
103
+ ["bash", "scripts/ci.sh", "-c", "pytest tests"],
104
+ ["bash", "--", "-c", "pytest tests"],
105
+ ["bash", "-Cu", "script.sh"],
106
+ ["pwsh", "-NonInteractive", "-File", "a.ps1"],
107
+ ["bash", "script.sh"],
108
+ ],
109
+ )
110
+ def test_should_allow_shell_forms_that_do_not_run_pytest(
111
+ all_segment_tokens: list[str],
112
+ ) -> None:
113
+ assert segment_reports_a_pytest_exit_code(all_segment_tokens) is False
114
+
115
+
116
+ def test_should_classify_segments_from_pipeline_parser() -> None:
117
+ all_pairs = pipeline_segments_for_command("uv run pytest tests | tee out.log")
118
+ assert len(all_pairs) >= 2
119
+ assert segment_runs_pytest(all_pairs[0][0]) is True
120
+ assert segment_runs_pytest(all_pairs[1][0]) is False
121
+
122
+
123
+ def test_should_not_false_classify_option_value_named_pytest() -> None:
124
+ all_tokens = _first_segment_tokens("uv run --with pytest mypy .")
125
+ assert segment_runs_pytest(all_tokens) is False
126
+
127
+
128
+ def test_should_not_false_classify_script_argument_named_pytest() -> None:
129
+ all_tokens = _first_segment_tokens("python myscript.py -m pytest")
130
+ assert segment_runs_pytest(all_tokens) is False
@@ -0,0 +1,168 @@
1
+ """Tests for the shared SessionStart context injector."""
2
+
3
+ from __future__ import annotations
4
+
5
+
6
+ import pytest
7
+
8
+ from hooks_constants.session_start_injector import (
9
+ InjectionResult,
10
+ InjectorConfiguration,
11
+ build_additional_context_payload,
12
+ default_injector_configuration,
13
+ inject_session_start_context,
14
+ injector_enabled_in_environment,
15
+ normalize_session_start_source,
16
+ )
17
+ from hooks_constants.session_start_injector_constants import (
18
+ INJECTION_STATUS_DISABLED,
19
+ INJECTION_STATUS_OK,
20
+ INJECTION_STATUS_TIMEOUT,
21
+ INJECTION_STATUS_UNKNOWN_SOURCE,
22
+ SESSION_START_INJECTOR_ENABLED_ENV_VAR,
23
+ SESSION_START_SOURCE_CLEAR,
24
+ SESSION_START_SOURCE_COMPACT,
25
+ SESSION_START_SOURCE_RESUME,
26
+ SESSION_START_SOURCE_STARTUP,
27
+ SESSION_START_SOURCE_UNKNOWN,
28
+ )
29
+
30
+
31
+ def _configuration(
32
+ *,
33
+ is_enabled: bool = True,
34
+ timeout_milliseconds: int = 50,
35
+ context_by_source: dict[str, str] | None = None,
36
+ default_context_for_unknown: str = "",
37
+ ) -> InjectorConfiguration:
38
+ return InjectorConfiguration(
39
+ is_enabled=is_enabled,
40
+ timeout_milliseconds=timeout_milliseconds,
41
+ context_by_source=context_by_source
42
+ or {
43
+ SESSION_START_SOURCE_STARTUP: "startup-context",
44
+ SESSION_START_SOURCE_RESUME: "resume-context",
45
+ SESSION_START_SOURCE_CLEAR: "clear-context",
46
+ SESSION_START_SOURCE_COMPACT: "compact-context",
47
+ },
48
+ default_context_for_unknown=default_context_for_unknown,
49
+ )
50
+
51
+
52
+ def _fixed_clock(all_readings: list[float]) -> object:
53
+ index = {"value": 0}
54
+
55
+ def _read() -> float:
56
+ reading = all_readings[min(index["value"], len(all_readings) - 1)]
57
+ index["value"] += 1
58
+ return reading
59
+
60
+ return _read
61
+
62
+
63
+ @pytest.mark.parametrize(
64
+ ("source", "expected_context"),
65
+ [
66
+ (SESSION_START_SOURCE_STARTUP, "startup-context"),
67
+ (SESSION_START_SOURCE_RESUME, "resume-context"),
68
+ (SESSION_START_SOURCE_CLEAR, "clear-context"),
69
+ (SESSION_START_SOURCE_COMPACT, "compact-context"),
70
+ ],
71
+ )
72
+ def test_inject_known_source_returns_ok_with_context(
73
+ source: str, expected_context: str
74
+ ) -> None:
75
+ result = inject_session_start_context(
76
+ {"source": source},
77
+ _configuration(),
78
+ clock=_fixed_clock([1.0, 1.002]),
79
+ )
80
+ assert result.status == INJECTION_STATUS_OK
81
+ assert result.source == source
82
+ assert result.additional_context == expected_context
83
+ assert result.is_context_injected is True
84
+ assert result.latency_milliseconds == pytest.approx(2.0)
85
+
86
+
87
+ def test_inject_disabled_returns_quickly_without_context() -> None:
88
+ result = inject_session_start_context(
89
+ {"source": SESSION_START_SOURCE_STARTUP},
90
+ _configuration(is_enabled=False),
91
+ clock=_fixed_clock([0.0, 0.0001]),
92
+ )
93
+ assert result.status == INJECTION_STATUS_DISABLED
94
+ assert result.additional_context == ""
95
+ assert result.is_context_injected is False
96
+ assert result.latency_milliseconds < 1.0
97
+
98
+
99
+ def test_inject_timeout_budget_zero_returns_timeout() -> None:
100
+ result = inject_session_start_context(
101
+ {"source": SESSION_START_SOURCE_RESUME},
102
+ _configuration(timeout_milliseconds=0),
103
+ clock=_fixed_clock([0.0, 0.0]),
104
+ )
105
+ assert result.status == INJECTION_STATUS_TIMEOUT
106
+ assert result.is_context_injected is False
107
+
108
+
109
+ def test_inject_unknown_source_is_explicit() -> None:
110
+ result = inject_session_start_context(
111
+ {"source": "not-a-real-source"},
112
+ _configuration(default_context_for_unknown="fallback"),
113
+ clock=_fixed_clock([0.0, 0.001]),
114
+ )
115
+ assert result.status == INJECTION_STATUS_UNKNOWN_SOURCE
116
+ assert result.source == SESSION_START_SOURCE_UNKNOWN
117
+ assert result.additional_context == "fallback"
118
+ assert result.is_context_injected is True
119
+
120
+
121
+ def test_normalize_session_start_source_handles_missing_and_case() -> None:
122
+ assert normalize_session_start_source({}) == SESSION_START_SOURCE_UNKNOWN
123
+ assert (
124
+ normalize_session_start_source({"source": " STARTUP "})
125
+ == SESSION_START_SOURCE_STARTUP
126
+ )
127
+ assert normalize_session_start_source({"source": 12}) == SESSION_START_SOURCE_UNKNOWN
128
+
129
+
130
+ def test_build_additional_context_payload_only_when_injected() -> None:
131
+ injected = InjectionResult(
132
+ source=SESSION_START_SOURCE_STARTUP,
133
+ status=INJECTION_STATUS_OK,
134
+ additional_context="hello",
135
+ latency_milliseconds=1.0,
136
+ is_context_injected=True,
137
+ )
138
+ empty = InjectionResult(
139
+ source=SESSION_START_SOURCE_STARTUP,
140
+ status=INJECTION_STATUS_DISABLED,
141
+ additional_context="",
142
+ latency_milliseconds=0.1,
143
+ is_context_injected=False,
144
+ )
145
+ assert build_additional_context_payload(injected) == {
146
+ "additionalContext": "hello"
147
+ }
148
+ assert build_additional_context_payload(empty) == {}
149
+
150
+
151
+ def test_injector_enabled_in_environment_default_on(
152
+ monkeypatch: pytest.MonkeyPatch,
153
+ ) -> None:
154
+ monkeypatch.delenv(SESSION_START_INJECTOR_ENABLED_ENV_VAR, raising=False)
155
+ assert injector_enabled_in_environment() is True
156
+ monkeypatch.setenv(SESSION_START_INJECTOR_ENABLED_ENV_VAR, "0")
157
+ assert injector_enabled_in_environment() is False
158
+ monkeypatch.setenv(SESSION_START_INJECTOR_ENABLED_ENV_VAR, "true")
159
+ assert injector_enabled_in_environment() is True
160
+
161
+
162
+ def test_default_injector_configuration_reads_env(
163
+ monkeypatch: pytest.MonkeyPatch,
164
+ ) -> None:
165
+ monkeypatch.setenv(SESSION_START_INJECTOR_ENABLED_ENV_VAR, "off")
166
+ configuration = default_injector_configuration()
167
+ assert configuration.is_enabled is False
168
+
@@ -0,0 +1,135 @@
1
+ """Real command fixtures for the quote-aware shell pipeline parser.
2
+
3
+ Covers quotes, operators, heredocs, continuations, groups, wrappers, and
4
+ compound shells. No pytest-specific classification runs here.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import pathlib
10
+ import sys
11
+
12
+ _HOOKS_ROOT = pathlib.Path(__file__).resolve().parent.parent
13
+ if str(_HOOKS_ROOT) not in sys.path:
14
+ sys.path.insert(0, str(_HOOKS_ROOT))
15
+
16
+ from hooks_constants.shell_command_pipeline import ( # noqa: E402
17
+ all_operator_aware_tokenizations,
18
+ join_line_continuations,
19
+ live_command_lines,
20
+ pipeline_segments_for_command,
21
+ scannable_command_lines,
22
+ segments_with_following_operator,
23
+ )
24
+ from hooks_constants.shell_command_segments import ( # noqa: E402
25
+ split_into_segments,
26
+ )
27
+
28
+
29
+ def test_pipe_segments_pair_with_following_operator() -> None:
30
+ all_tokens = ["pytest", "tests", "|", "tee", "out.log"]
31
+ all_pairs = segments_with_following_operator(all_tokens)
32
+ assert all_pairs[0] == (["pytest", "tests"], "|")
33
+ assert all_pairs[1] == (["tee", "out.log"], "")
34
+
35
+
36
+ def test_quoted_pipe_inside_k_expression_stays_in_one_segment() -> None:
37
+ """Quote-aware tokenization keeps pytest -k \"a|b\" as one segment."""
38
+ command = 'pytest -k "a|b" tests'
39
+ all_tokenizations = all_operator_aware_tokenizations(command)
40
+ assert all_tokenizations
41
+ all_pairs = segments_with_following_operator(all_tokenizations[0])
42
+ assert len(all_pairs) == 1
43
+ segment_tokens, following_operator = all_pairs[0]
44
+ assert following_operator == ""
45
+ assert any("a|b" in each_token or each_token == "a|b" for each_token in segment_tokens)
46
+
47
+
48
+ def test_quote_unaware_segments_split_on_pipe_inside_quotes() -> None:
49
+ """Divergence: shell_command_segments peels | inside a|b; quote-aware path does not."""
50
+ all_segments = split_into_segments(["pytest", "-k", "a|b", "tests"])
51
+ assert len(all_segments) > 1
52
+ quote_aware_pairs = pipeline_segments_for_command('pytest -k "a|b" tests')
53
+ assert len(quote_aware_pairs) >= 1
54
+ assert all(each_operator != "|" for _, each_operator in quote_aware_pairs)
55
+
56
+
57
+ def test_heredoc_body_is_dropped_from_live_lines() -> None:
58
+ all_lines = [
59
+ "cat > run.sh <<'EOF'",
60
+ "pytest tests | tee out.log",
61
+ "EOF",
62
+ "echo done",
63
+ ]
64
+ assert live_command_lines(all_lines) == ["cat > run.sh <<'EOF'", "echo done"]
65
+
66
+
67
+ def test_scannable_lines_drop_heredoc_body_and_keep_later_command() -> None:
68
+ command = "cat > run.sh <<'EOF'\npytest tests | tee out.log\nEOF\necho done"
69
+ all_lines = scannable_command_lines(join_line_continuations(command))
70
+ assert "pytest tests | tee out.log" not in all_lines
71
+ assert any("echo done" in each_line for each_line in all_lines)
72
+
73
+
74
+ def test_line_continuation_joins_pipe_onto_one_line() -> None:
75
+ command = "echo hi \\\n| cat"
76
+ joined = join_line_continuations(command)
77
+ assert "\n" not in joined
78
+ assert "| cat" in joined
79
+
80
+
81
+ def test_comment_does_not_hide_following_line() -> None:
82
+ command = "echo hi # note\necho bye"
83
+ all_lines = scannable_command_lines(command)
84
+ assert any("echo bye" in each_line for each_line in all_lines)
85
+ assert all("# note" not in each_line for each_line in all_lines)
86
+
87
+
88
+ def test_paren_group_joins_multiline_subshell_before_pipe() -> None:
89
+ command = "(\necho hi\n) | cat"
90
+ all_lines = scannable_command_lines(command)
91
+ assert len(all_lines) == 1
92
+ assert "|" in all_lines[0]
93
+ assert "echo hi" in all_lines[0]
94
+
95
+
96
+ def test_glued_close_paren_and_pipe_split_into_operator_token() -> None:
97
+ all_pairs = segments_with_following_operator(["(", "echo", "hi", ")|", "cat"])
98
+ assert any(each_operator == "|" for _, each_operator in all_pairs)
99
+
100
+
101
+ def test_and_operator_resets_segment() -> None:
102
+ all_pairs = segments_with_following_operator(
103
+ ["echo", "a", "&&", "echo", "b"]
104
+ )
105
+ assert all_pairs[0] == (["echo", "a"], "&&")
106
+ assert all_pairs[1][0] == ["echo", "b"]
107
+
108
+
109
+ def test_pipeline_segments_for_command_on_simple_pipe() -> None:
110
+ all_pairs = pipeline_segments_for_command("echo hi | cat")
111
+ leading_programs = [
112
+ each_segment[0] for each_segment, _ in all_pairs if each_segment
113
+ ]
114
+ assert "echo" in leading_programs
115
+ assert "cat" in leading_programs
116
+ assert any(each_operator == "|" for _, each_operator in all_pairs)
117
+
118
+
119
+ def test_wrapper_tokens_remain_visible_in_segment() -> None:
120
+ """Wrappers stay in the segment; classification is a later slice."""
121
+ all_pairs = pipeline_segments_for_command("sudo -n echo hi | cat")
122
+ first_pipe_segment = next(
123
+ each_segment for each_segment, each_operator in all_pairs if each_operator == "|"
124
+ )
125
+ assert first_pipe_segment[0] in {"sudo", "sudo.exe"} or "sudo" in first_pipe_segment[0]
126
+
127
+
128
+ def test_compound_shell_if_fi_tokens_survive_tokenization() -> None:
129
+ command = "if true; then echo hi; fi"
130
+ all_tokenizations = all_operator_aware_tokenizations(command)
131
+ assert all_tokenizations
132
+ flat_tokens = all_tokenizations[0]
133
+ assert "if" in flat_tokens
134
+ assert "then" in flat_tokens
135
+ assert "fi" in flat_tokens
@@ -24,7 +24,7 @@ PATH_ANCHOR_CHARACTER: str = "/"
24
24
  PATH_SEGMENT_START_CHARACTERS: str = "_-"
25
25
 
26
26
  ALL_PATH_ANCHORED_VOLATILE_PATH_MARKERS: tuple[str, ...] = (
27
- ".claude-editor/jobs/",
27
+ ".claude-profile-a/jobs/",
28
28
  ".claude/worktrees/",
29
29
  )
30
30
 
@@ -0,0 +1,30 @@
1
+ """Working-style prompt text for the SessionStart working_style_prompt hook."""
2
+
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "WORKING_STYLE_PROMPT",
7
+ ]
8
+
9
+ WORKING_STYLE_PROMPT = (
10
+ "document everything in a location you can find later, rather than relying on "
11
+ "memory. scratch txt file you'll keep running as you go; ledger, if you will."
12
+ "Always, without exception, write in plain english. Keep responses focused, "
13
+ "brief, and concise. Keep disclaimers and caveats short, and spend most of the "
14
+ "response on the main answer. When asked to explain something, give a high-level "
15
+ "summary unless an in-depth explanation is specifically requested."
16
+ "Before your first tool call, say in one sentence what you're about to do. While "
17
+ "working, give a brief update only when you find something important or change "
18
+ "direction. When you finish, lead with the outcome: your first sentence should "
19
+ "answer \"what happened\" or \"what did you find,\" with supporting detail after "
20
+ "it for readers who want it."
21
+ "Match the length of written documents to what the task needs: cover the "
22
+ "substance, but do not pad with filler sections, redundant summaries, or "
23
+ "boilerplate."
24
+ "Deliver what was asked, at the scope intended. Make routine judgment calls "
25
+ "yourself, and check in only when different readings of the request would lead "
26
+ "to materially different work. If the request seems mistaken or a better "
27
+ "approach exists, say so in a sentence and continue with the task as asked "
28
+ "rather than quietly narrowing, widening, or transforming it. Finish the whole "
29
+ "task, and stop short of actions that are clearly beyond what was asked."
30
+ )
@@ -8,8 +8,10 @@ PostToolUse hooks that record agent behavior for later review. These hooks do no
8
8
  |---|---|---|
9
9
  | `instructions_loaded_logger.py` | PostToolUse (file load events) | Appends a JSONL record to `~/.claude/logs/instructions_loaded.jsonl` each time Claude Code loads a context file (CLAUDE.md, rules, skills), capturing the file path, load reason, memory type, and session ID |
10
10
  | `session_file_edit_tracker.py` | PostToolUse (Write/Edit/MultiEdit) | Records the resolved absolute path of each file edited this session into a per-session JSON file under the system temp directory, for the session edit stage gate to read at commit time |
11
+ | `prose_matcher_advisory.py` | Advisory emit from prose matchers | Privacy-safe JSONL candidates (`matcher_id`, surface, context fingerprint, label) plus keep/narrow/drop/advisory classification against the sample floor |
11
12
  | `test_instructions_loaded_logger.py` | — | Tests for `instructions_loaded_logger.py` |
12
13
  | `test_session_file_edit_tracker.py` | — | Tests for `session_file_edit_tracker.py` |
14
+ | `test_prose_matcher_advisory.py` | — | Tests for advisory emit and precision classification |
13
15
 
14
16
  ## Conventions
15
17