claude-dev-env 2.9.0 → 2.11.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 (318) hide show
  1. package/CLAUDE.md +2 -2
  2. package/_shared/advisor/CLAUDE.md +3 -2
  3. package/_shared/advisor/advisor-protocol.md +74 -108
  4. package/_shared/advisor/reference/advisor-block.md +37 -0
  5. package/_shared/advisor/reference/cli-chain.md +45 -0
  6. package/_shared/advisor/reference/consult-format.md +41 -0
  7. package/_shared/advisor/reference/lifecycle.md +21 -0
  8. package/_shared/advisor/reference/sol-rung.md +31 -0
  9. package/_shared/advisor/reference/spawn-walk-log.md +31 -0
  10. package/_shared/advisor/reference/third-party-bind.md +30 -0
  11. package/_shared/advisor/reference/warm-up.md +33 -0
  12. package/_shared/advisor/scripts/codex_sol_advisor.py +449 -0
  13. package/_shared/advisor/scripts/config/advisor_scripts_constants/advisor_route_constants.py +21 -0
  14. package/_shared/advisor/scripts/config/advisor_scripts_constants/model_tier_run_validator_constants.py +19 -17
  15. package/_shared/advisor/scripts/config/advisor_scripts_constants/sol_advisor_constants.py +28 -0
  16. package/_shared/advisor/scripts/model_tier_run_validator.py +32 -9
  17. package/_shared/advisor/scripts/tests/test_codex_sol_advisor.py +474 -0
  18. package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +79 -0
  19. package/_shared/advisor/scripts/tests/test_tier_model_ids.py +39 -17
  20. package/_shared/advisor/scripts/tier_model_ids.py +24 -0
  21. package/_shared/pr-loop/CLAUDE.md +1 -1
  22. package/_shared/pr-loop/audit-contract.md +17 -6
  23. package/_shared/pr-loop/audit-reply-template.md +4 -4
  24. package/_shared/pr-loop/code-rules-gate.md +3 -5
  25. package/_shared/pr-loop/fix-protocol.md +2 -3
  26. package/_shared/pr-loop/gh-payloads.md +1 -1
  27. package/_shared/pr-loop/scripts/CLAUDE.md +1 -1
  28. package/_shared/pr-loop/scripts/README.md +1 -1
  29. package/_shared/pr-loop/scripts/code_rules_gate.py +2 -0
  30. package/_shared/pr-loop/scripts/code_rules_gate_parts/gate_running.py +16 -1
  31. package/_shared/pr-loop/scripts/code_rules_gate_parts/git_blob_readers.py +11 -5
  32. package/_shared/pr-loop/scripts/preflight.py +9 -4
  33. package/_shared/pr-loop/scripts/reviews_disabled.py +50 -22
  34. package/_shared/pr-loop/scripts/tests/conftest.py +20 -0
  35. package/_shared/pr-loop/scripts/tests/test_claude_permissions_common.py +6 -6
  36. package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +50 -6
  37. package/_shared/pr-loop/scripts/tests/test_revoke_project_claude_permissions.py +1 -1
  38. package/_shared/pr-loop/state-schema.md +5 -14
  39. package/agents/CLAUDE.md +2 -2
  40. package/agents/clean-coder.md +58 -548
  41. package/agents/code-quality-agent.md +10 -2
  42. package/agents/code-verifier.md +1 -1
  43. package/agents/test_agent_frontmatter.py +32 -40
  44. package/audit-rubrics/CLAUDE.md +2 -1
  45. package/audit-rubrics/audit-categories.json +704 -0
  46. package/audit-rubrics/prompts/category-i-concurrency.md +1 -1
  47. package/bin/CLAUDE.md +16 -5
  48. package/bin/ever-shipped-skills.mjs +2 -0
  49. package/bin/install-plan.mjs +402 -0
  50. package/bin/install-transaction.mjs +455 -0
  51. package/bin/install.mjs +593 -147
  52. package/bin/install.plan.test.mjs +194 -0
  53. package/bin/install.profile-root.test.mjs +154 -0
  54. package/bin/install.profiles.test.mjs +253 -0
  55. package/bin/install.settings-defaults.test.mjs +200 -0
  56. package/bin/install.transaction.test.mjs +400 -0
  57. package/bin/install.uninstall-transaction.test.mjs +418 -0
  58. package/bin/merge_managed_permissions.mjs +130 -0
  59. package/bin/resolve-install-root.mjs +181 -0
  60. package/bin/select-install-targets.mjs +401 -0
  61. package/commands/CLAUDE.md +0 -2
  62. package/docs/references/CLAUDE.md +3 -1
  63. package/docs/references/advisor-tool.md +25 -7
  64. package/docs/references/prose-style-enforcement.md +25 -0
  65. package/docs/references/team-advisor-skill.md +3 -3
  66. package/docs/references/weak-executor-advisor.md +91 -0
  67. package/hooks/blocking/CLAUDE.md +6 -6
  68. package/hooks/blocking/_path_setup.py +9 -5
  69. package/hooks/blocking/code_rules_docstrings.py +124 -30
  70. package/hooks/blocking/code_rules_enforcer.py +161 -16
  71. package/hooks/blocking/code_rules_shared.py +40 -23
  72. package/hooks/blocking/config/CLAUDE.md +3 -5
  73. package/hooks/blocking/config/prose_style_enforcement_constants.py +38 -0
  74. package/hooks/blocking/config/test_prose_style_enforcement_constants.py +45 -0
  75. package/hooks/blocking/eli11_reply_enforcer.py +70 -113
  76. package/hooks/blocking/hedging_language_blocker.py +103 -20
  77. package/hooks/blocking/hook_prose_detector_consistency.py +6 -0
  78. package/hooks/blocking/intent_only_ending_blocker.py +6 -0
  79. package/hooks/blocking/plain_language_blocker.py +139 -20
  80. package/hooks/blocking/pre_tool_use_dispatcher.py +102 -20
  81. package/hooks/blocking/state_description_blocker.py +7 -1
  82. package/hooks/blocking/tdd_enforcer.py +8 -0
  83. package/hooks/blocking/test__path_setup.py +28 -0
  84. package/hooks/blocking/test_code_rules_enforcer_agent_home_tooling.py +99 -0
  85. package/hooks/blocking/test_code_rules_enforcer_docstring_args_span_scope.py +232 -10
  86. package/hooks/blocking/test_code_rules_enforcer_ephemeral.py +1 -1
  87. package/hooks/blocking/test_code_rules_enforcer_join_separator_magic.py +41 -0
  88. package/hooks/blocking/test_code_rules_enforcer_string_magic.py +98 -0
  89. package/hooks/blocking/test_eli11_reply_enforcer.py +98 -165
  90. package/hooks/blocking/test_fable_spawn_gate.py +18 -11
  91. package/hooks/blocking/test_hedging_language_blocker.py +120 -1
  92. package/hooks/blocking/test_hook_prose_detector_consistency.py +28 -8
  93. package/hooks/blocking/test_intent_only_ending_blocker.py +27 -2
  94. package/hooks/blocking/test_package_inventory_stale_blocker.py +11 -4
  95. package/hooks/blocking/test_plain_language_blocker.py +129 -19
  96. package/hooks/blocking/test_plain_language_blocker_allowlist.py +70 -26
  97. package/hooks/blocking/test_pre_tool_use_dispatcher.py +99 -26
  98. package/hooks/blocking/test_pre_tool_use_dispatcher_native.py +87 -50
  99. package/hooks/blocking/test_state_description_blocker.py +45 -2
  100. package/hooks/blocking/test_stop_dispatcher.py +11 -7
  101. package/hooks/blocking/test_volatile_path_in_post_blocker.py +12 -12
  102. package/hooks/blocking/volatile_path_in_post_blocker.py +2 -2
  103. package/hooks/hooks.json +15 -0
  104. package/hooks/hooks_constants/CLAUDE.md +14 -3
  105. package/hooks/hooks_constants/ask_user_question_shape.py +281 -0
  106. package/hooks/hooks_constants/code_rules_enforcer_constants.py +2 -1
  107. package/hooks/hooks_constants/eli11_reply_enforcer_constants.py +5 -12
  108. package/hooks/hooks_constants/hedging_uncertainty_constants.py +42 -0
  109. package/hooks/hooks_constants/issue_tracker_session_starter_constants.py +23 -0
  110. package/hooks/hooks_constants/orchestrator_auto_starter_constants.py +23 -0
  111. package/hooks/hooks_constants/piped_pytest_blocker_constants.py +4 -1
  112. package/hooks/hooks_constants/plain_language_blocker_constants.py +4 -1
  113. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +6 -0
  114. package/hooks/hooks_constants/project_paths_reader.py +31 -4
  115. package/hooks/hooks_constants/prose_matcher_precision_constants.py +40 -0
  116. package/hooks/hooks_constants/pytest_invocation.py +354 -0
  117. package/hooks/hooks_constants/session_start_injector.py +163 -0
  118. package/hooks/hooks_constants/session_start_injector_constants.py +46 -0
  119. package/hooks/hooks_constants/shell_command_pipeline.py +397 -0
  120. package/hooks/hooks_constants/shell_command_segments.py +5 -0
  121. package/hooks/hooks_constants/test_ask_user_question_shape.py +167 -0
  122. package/hooks/hooks_constants/test_project_paths_reader.py +29 -0
  123. package/hooks/hooks_constants/test_prose_metrics_parity.py +8 -0
  124. package/hooks/hooks_constants/test_pytest_invocation.py +130 -0
  125. package/hooks/hooks_constants/test_session_start_injector.py +168 -0
  126. package/hooks/hooks_constants/test_shell_command_pipeline.py +135 -0
  127. package/hooks/hooks_constants/volatile_path_in_post_blocker_constants.py +1 -1
  128. package/hooks/hooks_constants/working_style_prompt_constants.py +30 -0
  129. package/hooks/observability/CLAUDE.md +2 -0
  130. package/hooks/observability/prose_matcher_advisory.py +237 -0
  131. package/hooks/observability/test_prose_matcher_advisory.py +143 -0
  132. package/hooks/session/CLAUDE.md +9 -1
  133. package/hooks/session/_path_setup.py +13 -0
  134. package/hooks/session/issue_tracker_session_starter.py +135 -0
  135. package/hooks/session/orchestrator_auto_starter.py +100 -0
  136. package/hooks/session/test__path_setup.py +28 -0
  137. package/hooks/session/test_issue_tracker_session_starter.py +104 -0
  138. package/hooks/session/test_orchestrator_auto_starter.py +99 -0
  139. package/hooks/session/test_working_style_prompt.py +47 -0
  140. package/hooks/session/untracked_repo_detector.py +1 -24
  141. package/hooks/session/working_style_prompt.py +36 -0
  142. package/hooks/validators/_path_setup.py +19 -0
  143. package/hooks/validators/run_all_validators.py +8 -13
  144. package/installable-surfaces.manifest.json +21 -0
  145. package/output-styles/CLAUDE.md +1 -3
  146. package/package.json +4 -2
  147. package/rules/CLAUDE.md +1 -0
  148. package/rules/durable-post-artifacts.md +2 -2
  149. package/rules/eli11-replies.md +6 -1
  150. package/rules/hedging-claims.md +4 -2
  151. package/rules/long-horizon-autonomy.md +3 -1
  152. package/rules/opus5-communication-contract.md +45 -0
  153. package/rules/plain-language.md +2 -2
  154. package/rules/research-mode.md +1 -1
  155. package/scripts/CLAUDE.md +11 -0
  156. package/scripts/Sync-RepoMain.ps1 +215 -0
  157. package/scripts/active_capability_references.py +218 -0
  158. package/scripts/ci/windows-installer-lifecycle.ps1 +78 -0
  159. package/scripts/claude_chain_runner.py +394 -6
  160. package/scripts/claude_chain_usage.py +1 -1
  161. package/scripts/codex_compat_materializer.py +105 -85
  162. package/scripts/dev_env_scripts_constants/CLAUDE.md +2 -0
  163. package/scripts/dev_env_scripts_constants/active_capability_constants.py +46 -0
  164. package/scripts/dev_env_scripts_constants/claude_chain_constants.py +74 -0
  165. package/scripts/dev_env_scripts_constants/verify_installable_package_constants.py +116 -0
  166. package/scripts/profile-isolation-launchers/config/mcp-bundles.json +25 -0
  167. package/scripts/profile-isolation-launchers/config/profile-isolation-constants.mjs +60 -0
  168. package/scripts/profile-isolation-launchers/config/profiles.manifest.json +54 -0
  169. package/scripts/profile-isolation-launchers/config/shared-allowlist.json +64 -0
  170. package/scripts/profile-isolation-launchers/launcher-runtime.mjs +180 -0
  171. package/scripts/profile-isolation-launchers/lib/profile-manifest.mjs +288 -0
  172. package/scripts/profile-isolation-launchers/mcp-bundles.mjs +275 -0
  173. package/scripts/profile-isolation-launchers/profile-isolation-contract.test.mjs +221 -0
  174. package/scripts/profile-isolation-launchers/tests/launcher-runtime.test.mjs +108 -0
  175. package/scripts/profile-isolation-launchers/tests/mcp-bundles.test.mjs +147 -0
  176. package/scripts/profile-isolation-launchers/tests/shortcut-contract.test.ps1 +102 -0
  177. package/scripts/profile-isolation-launchers/tests/version-compatibility.test.mjs +210 -0
  178. package/scripts/profile-isolation-launchers/version-compatibility.mjs +299 -0
  179. package/scripts/profile-isolation-launchers/windows/shortcut-inventory.ps1 +127 -0
  180. package/scripts/profile-isolation-launchers/windows/shortcut-manifest.json +51 -0
  181. package/scripts/profile-isolation-launchers/windows/shortcut-reconcile.ps1 +77 -0
  182. package/scripts/spawn_grok_batch.py +3 -0
  183. package/scripts/test_active_capability_references.py +108 -0
  184. package/scripts/test_claude_chain_runner.py +414 -82
  185. package/scripts/test_claude_chain_usage.py +12 -12
  186. package/scripts/test_resolve_worker_spawn.py +2 -2
  187. package/scripts/test_verify_installable_package.py +208 -0
  188. package/scripts/tests/test_codex_compat_materializer.py +33 -0
  189. package/scripts/verify_installable_package.py +612 -0
  190. package/settings.json +10 -0
  191. package/skills/CLAUDE.md +2 -0
  192. package/skills/_shared/advisor/CLAUDE.md +1 -1
  193. package/skills/_shared/advisor/scripts/README.md +2 -0
  194. package/skills/_shared/pr-loop/scripts/CLAUDE.md +1 -0
  195. package/skills/_shared/pr-loop/scripts/audit_category_schema.py +355 -0
  196. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/CLAUDE.md +1 -0
  197. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/audit_category_schema_constants.py +32 -0
  198. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py +7 -19
  199. package/skills/_shared/pr-loop/scripts/test_audit_category_schema.py +94 -0
  200. package/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +21 -0
  201. package/skills/autoconverge/reference/convergence.md +2 -1
  202. package/skills/autoconverge/reference/stop-conditions.md +5 -3
  203. package/skills/beat-sheet/SKILL.md +54 -0
  204. package/skills/beat-sheet/reference/visual-beats.md +29 -0
  205. package/skills/bugteam/CONSTRAINTS.md +4 -4
  206. package/skills/bugteam/EXAMPLES.md +1 -1
  207. package/skills/bugteam/reference/README.md +1 -1
  208. package/skills/e-code-review/SKILL.md +26 -5
  209. package/skills/e-code-review/reference/effort-evaluation.md +35 -0
  210. package/skills/e-code-review/reference/medium.md +15 -4
  211. package/skills/e-code-review/scripts/config/e_code_review_effort_constants/__init__.py +41 -0
  212. package/skills/e-code-review/scripts/config/e_code_review_effort_constants/effort_constants.py +40 -0
  213. package/skills/e-code-review/scripts/e_code_review_scripts_constants/finding_pipeline_constants.py +49 -0
  214. package/skills/e-code-review/scripts/effort_defaults_evidence.json +186 -0
  215. package/skills/e-code-review/scripts/effort_evaluation.py +362 -0
  216. package/skills/e-code-review/scripts/finding_pipeline.py +140 -0
  217. package/skills/e-code-review/scripts/fixtures/demanding.json +26 -0
  218. package/skills/e-code-review/scripts/fixtures/easy.json +14 -0
  219. package/skills/e-code-review/scripts/fixtures/medium.json +20 -0
  220. package/skills/e-code-review/scripts/grok_code_review.py +16 -7
  221. package/skills/e-code-review/scripts/test_effort_evaluation.py +180 -0
  222. package/skills/e-code-review/scripts/test_finding_pipeline.py +197 -0
  223. package/skills/e-code-review/scripts/test_grok_code_review.py +77 -0
  224. package/skills/grokify/SKILL.md +1 -1
  225. package/skills/grokify/templates/handoff-template.md +2 -2
  226. package/skills/orchestrator/SKILL.md +5 -4
  227. package/skills/plan-to-pr/scripts/create_packet.py +4 -4
  228. package/skills/plan-to-pr/scripts/load_skill_constants.py +41 -0
  229. package/skills/plan-to-pr/scripts/validate_packet.py +4 -4
  230. package/skills/plan-to-pr/scripts/validate_protocol.py +4 -1
  231. package/skills/plan-to-pr/scripts/validate_run.py +4 -1
  232. package/skills/pr-converge/scripts/check_convergence.py +21 -19
  233. package/skills/pr-converge/scripts/check_convergence_availability.py +50 -7
  234. package/skills/pr-converge/scripts/conftest.py +35 -0
  235. package/skills/pr-converge/scripts/test_check_convergence_availability.py +65 -0
  236. package/skills/pr-converge/scripts/test_check_convergence_codex.py +11 -1
  237. package/skills/pr-converge/scripts/test_check_convergence_contract.py +9 -2
  238. package/skills/pr-loop-cloud-transport/SKILL.md +1 -1
  239. package/skills/rebase/SKILL.md +15 -3
  240. package/skills/reviewer-gates/SKILL.md +2 -2
  241. package/skills/show/SKILL.md +51 -0
  242. package/skills/show/references/accessibility.md +7 -0
  243. package/skills/show/references/art.md +3 -0
  244. package/skills/show/references/charts.md +3 -0
  245. package/skills/show/references/core-design.md +14 -0
  246. package/skills/show/references/erds.md +3 -0
  247. package/skills/show/references/flowcharts.md +3 -0
  248. package/skills/show/references/host-and-html.md +3 -0
  249. package/skills/show/references/illustrative-diagrams.md +10 -0
  250. package/skills/show/references/interaction.md +3 -0
  251. package/skills/show/references/mockups.md +3 -0
  252. package/skills/show/references/quality-gates.md +7 -0
  253. package/skills/show/references/structural-diagrams.md +3 -0
  254. package/skills/show/references/subject-inventory.md +21 -0
  255. package/skills/show/references/svg-contract.md +22 -0
  256. package/skills/show/routing.yaml +30 -0
  257. package/skills/show/samples/pr1262-v2.svg +222 -0
  258. package/skills/show/scripts/README.md +6 -0
  259. package/skills/show/scripts/validate-artifact.py +91 -0
  260. package/skills/show/scripts/validate-package.py +18 -0
  261. package/skills/show/templates/html-widget.html +4 -0
  262. package/skills/show/templates/svg-base.svg +19 -0
  263. package/skills/show/tests/fixtures/css-var.svg +6 -0
  264. package/skills/show/tests/fixtures/dead-ref.svg +7 -0
  265. package/skills/show/tests/fixtures/filled-glyph.svg +8 -0
  266. package/skills/show/tests/fixtures/inherited-fill.svg +18 -0
  267. package/skills/show/tests/fixtures/invalid.svg +1 -0
  268. package/skills/show/tests/fixtures/large-canvas.svg +21 -0
  269. package/skills/show/tests/fixtures/unfilled-connector.svg +15 -0
  270. package/skills/show/tests/fixtures/valid.html +1 -0
  271. package/skills/show/tests/test_validate-artifact.py +74 -0
  272. package/skills/show/tests/test_validators.py +59 -0
  273. package/skills/show/workflows/create-visual.md +13 -0
  274. package/skills/show/workflows/review-visual.md +20 -0
  275. package/skills/split-pr/SKILL.md +85 -0
  276. package/skills/split-pr/reference/path-layers.md +16 -0
  277. package/skills/split-pr/reference/proposal-format.md +15 -0
  278. package/skills/split-pr/reference/split-further-loop.md +10 -0
  279. package/skills/split-pr/reference/splitting-principles.md +26 -0
  280. package/skills/split-pr/scripts/analyze_pr.py +279 -0
  281. package/skills/split-pr/scripts/categorize_files.py +106 -0
  282. package/skills/split-pr/scripts/config/__init__.py +1 -0
  283. package/skills/split-pr/scripts/config/dependency_constants.py +14 -0
  284. package/skills/split-pr/scripts/config/git_operations_constants.py +36 -0
  285. package/skills/split-pr/scripts/config/packing_constants.py +61 -0
  286. package/skills/split-pr/scripts/config/plan_constants.py +49 -0
  287. package/skills/split-pr/scripts/config/split_pr_constants.py +110 -0
  288. package/skills/split-pr/scripts/execute_split_slices.py +82 -0
  289. package/skills/split-pr/scripts/pack_files_into_slices.py +212 -0
  290. package/skills/split-pr/scripts/split_pr_dependency_graph.py +70 -0
  291. package/skills/split-pr/scripts/split_pr_git_operations.py +184 -0
  292. package/skills/split-pr/scripts/split_pr_layer_order.py +58 -0
  293. package/skills/split-pr/scripts/split_pr_paginate.py +119 -0
  294. package/skills/split-pr/scripts/split_pr_process_runner.py +52 -0
  295. package/skills/split-pr/scripts/split_pr_script_types.py +126 -0
  296. package/skills/split-pr/scripts/split_pr_title.py +41 -0
  297. package/skills/split-pr/scripts/test_analyze_pr.py +228 -0
  298. package/skills/split-pr/scripts/test_categorize_files.py +55 -0
  299. package/skills/split-pr/scripts/test_categorize_files_packing.py +59 -0
  300. package/skills/split-pr/scripts/test_execute_split_slices.py +99 -0
  301. package/skills/split-pr/scripts/test_split_pr_dependency_graph.py +47 -0
  302. package/skills/split-pr/scripts/test_split_pr_git_operations.py +125 -0
  303. package/skills/split-pr/scripts/test_split_pr_layer_order.py +36 -0
  304. package/skills/split-pr/scripts/test_split_pr_paginate.py +65 -0
  305. package/skills/split-pr/scripts/test_split_pr_script_types.py +73 -0
  306. package/skills/split-pr/scripts/test_split_pr_title.py +28 -0
  307. package/skills/split-pr/scripts/test_verify_dependency_graph.py +46 -0
  308. package/skills/split-pr/scripts/test_verify_plan.py +56 -0
  309. package/skills/split-pr/scripts/test_verify_plan_contract.py +50 -0
  310. package/skills/split-pr/scripts/test_verify_plan_path_normalization.py +45 -0
  311. package/skills/split-pr/scripts/verify_dependency_graph.py +111 -0
  312. package/skills/split-pr/scripts/verify_plan.py +139 -0
  313. package/skills/team-advisor/SKILL.md +7 -4
  314. package/skills/team-advisor/reference/advisor-docs-review.md +207 -0
  315. package/system-prompts/software-engineer.xml +11 -2
  316. package/commands/initialize.md +0 -90
  317. package/commands/stubcheck.md +0 -88
  318. package/output-styles/caveman-agent.md +0 -37
@@ -0,0 +1,25 @@
1
+ # Prose-style enforcement
2
+
3
+ Opinionated prose gates (heavy-word swaps, hedging, historical state phrasing, intent-only endings, hook prose-detector consistency) are **off by default**.
4
+
5
+ ## Opt-in
6
+
7
+ Set `CLAUDE_PROSE_STYLE_ENFORCEMENT` to `1`, `true`, `yes`, or `on` (case and spaces ignored). Any other value, and an unset variable, leave those gates off.
8
+
9
+ The flag is `PROSE_STYLE_ENFORCEMENT_ENABLED` in `hooks/blocking/config/prose_style_enforcement_constants.py`.
10
+
11
+ ## Always on
12
+
13
+ Structural AskUserQuestion lean-block validation in `plain_language_blocker` (chat detail, length caps on question blocks) stays active regardless of the flag. Code-rules and security blockers are out of scope for this switch.
14
+
15
+ ## Advisory precision measurement (OP-07B)
16
+
17
+ When the flag is off, heavy-word and hedging hits still emit privacy-safe advisory candidates to `~/.claude/logs/prose-matcher-advisory.jsonl` (matcher id, surface, hashed context fingerprint, optional label). Classification (`keep` / `narrow` / `drop` / `advisory`) uses a labeled sample floor of 30 and precision floors 0.7 / 0.4. Matchers below the floor stay advisory. No matcher becomes hard-blocking from historical labels alone. See `hooks/observability/prose_matcher_advisory.py`.
18
+
19
+ ## Explicit uncertainty (OP-07C)
20
+
21
+ With enforcement on, a hedge word in a sentence that also labels the claim (`unverified`, `I don't know`, `no source for this claim`, …) passes. A bare hedge in a different sentence still blocks. Detail: `rules/hedging-claims.md`.
22
+
23
+ ## Plain-language advisory (OP-07D)
24
+
25
+ Heavy-word matches never hard-deny a Write/Edit or AskUserQuestion. With enforcement on, the hook allows the call and returns a `systemMessage` that names everyday swaps for both AskUserQuestion and `.md` writes. Lean-block structure denials stay hard. Detail: `rules/plain-language.md`.
@@ -1,6 +1,6 @@
1
1
  # Team-Advisor Skill
2
2
 
3
- `/team-advisor` binds one standing warm advisor for this session at the strongest reachable tier. Use it when `advisor()` is absent, or when you want a standing four-signal reviewer across many decision points.
3
+ `/team-advisor` binds one standing warm advisor for this session at the strongest reachable tier. This skill is the selected custom reproduction path for advisor behavior: it forwards explicit packets to a warm Agent/SendMessage advisor or the read-only `codex_sol_advisor.py` path across many decision points.
4
4
 
5
5
  ## Refs
6
6
 
@@ -15,6 +15,6 @@
15
15
 
16
16
  Follow the call rules in `advisor-tool.md` (orientation first, then consult before substantive work; durable deliverable before the completion consult; stuck or reapproach; long tasks twice).
17
17
 
18
- ## Relation to `advisor()`
18
+ ## Selected path
19
19
 
20
- `/team-advisor` works with no `advisor()` tool. When both exist: `advisor()` for a fast history-forwarded check; `/team-advisor` for a standing named reviewer consulted at the same cadence.
20
+ `/team-advisor` is the repository's advisor implementation. It provides explicit first-consult packets, delta consults, a standing warm reviewer, and a read-only Sol CLI option.
@@ -0,0 +1,91 @@
1
+ # Weak-Executor Advisor Profile
2
+
3
+ Consult profile for an executor spawned below the advisor's own tier — a
4
+ Sonnet or Haiku model carrying an advisor bind. `advisor-tool.md` sets the
5
+ canonical cadence for every consumer; this file adds the deltas a below-tier
6
+ executor needs on top of it.
7
+
8
+ Source: [Anthropic Advisor tool](https://platform.claude.com/docs/en/agents-and-tools/tool-use/advisor-tool)
9
+ (Suggested system prompt for coding tasks). The distilled facts behind each
10
+ section live in
11
+ `skills/team-advisor/reference/advisor-docs-review.md`.
12
+
13
+ ## Scope
14
+
15
+ Applies to any executor running below the advisor's tier. A same-tier or
16
+ stronger executor follows `advisor-tool.md` alone. The paste-ready form of
17
+ this profile is the **Weak-executor add-on** block in
18
+ `~/.claude/_shared/advisor/reference/advisor-block.md`.
19
+
20
+ ## Steering lives in the spawn prompt
21
+
22
+ The advisor rules sit at the top of the spawn prompt, ahead of every other
23
+ sentence that mentions the advisor — the spawn prompt is the one steering
24
+ surface with measured effect on Sonnet.
25
+
26
+ ## Context packaging
27
+
28
+ Everything the advisor learns arrives inside the consult. Each consult
29
+ carries its own packet:
30
+
31
+ - **First consult** — use the complete packet in
32
+ `~/.claude/_shared/advisor/reference/consult-format.md`: assignment,
33
+ desired outcome, constraints and exclusions, actions taken in order, real
34
+ output and current state, decision or blocker, validation evidence,
35
+ unresolved risks, and load-bearing paths or excerpts.
36
+ - **Later consults** — the delta since the last consult.
37
+ - **Ordering** — stable role and charter text first, volatile detail last.
38
+
39
+ ## Two-timing rule
40
+
41
+ Two consult moments carry the measured gain:
42
+
43
+ 1. **Early** — after a few exploratory reads land in the transcript, before
44
+ the first write. This is the hard rule `advisor-tool.md` §Hard rule
45
+ states for every consumer.
46
+ 2. **Final** — after file writes and test output exist to forward. Make the
47
+ deliverable durable first (write the file, save the result, commit the
48
+ change), then consult.
49
+
50
+ ## Planner funnel
51
+
52
+ Consult the advisor before any task-list or planner tool. The advisor's plan
53
+ becomes the task list.
54
+
55
+ ## Consult budget
56
+
57
+ Aim for two consults per task: early orientation and completion review. Reserve a third for advisory recovery or reconciliation guidance, and add a consult when a material fork produces new evidence. This is a planning target that leaves the task free to follow its evidence.
58
+
59
+ ## Advice weight
60
+
61
+ Advice is binding absent empirical contradiction. A conflict between the
62
+ executor's own evidence and the advisor's guidance goes back to the advisor
63
+ as a reconcile consult. See `advisor-tool.md` §How to treat advice for the
64
+ full weighing rule.
65
+
66
+ ## Long-run reminder
67
+
68
+ On a run past roughly 20 advisor-free turns, the executor re-reads its
69
+ advisor rules before the next substantive step — the re-read keeps the
70
+ advisor visible across a long horizon.
71
+
72
+ ## Failure branches
73
+
74
+ - **Transient advisor failure** — retry once, then carry on with the
75
+ evidence in hand and record the gap in the result.
76
+ - **Advisor unreachable** — report upward and hold the decision for the
77
+ owning session; re-binding belongs to that session alone.
78
+
79
+ ## Pairing invariant
80
+
81
+ The advisor binds at or above the strongest consumer's tier. The floor
82
+ holds at that tier whichever executor joins the pairing.
83
+
84
+ ## Related
85
+
86
+ | Doc | Holds |
87
+ |---|---|
88
+ | `advisor-tool.md` | Canonical consult cadence, hard rule, brevity cue |
89
+ | `~/.claude/_shared/advisor/advisor-protocol.md` | Host bind, model floor, and the Advisor-block assembly rule |
90
+ | `~/.claude/_shared/advisor/reference/advisor-block.md` | The paste parts — transport preambles, shared core, weak-executor add-on |
91
+ | `skills/team-advisor/reference/advisor-docs-review.md` | Distilled source facts behind each section above |
@@ -82,16 +82,16 @@ The check modules it calls are the `code_rules_<concern>.py` files below.
82
82
  | `destructive_command_blocker.py` | PreToolUse (Bash/PowerShell) | Shell commands with destructive literals (`rm -rf`, `git reset --hard`, etc.) |
83
83
  | `docstring_rule_gate_count_blocker.py` | PreToolUse (Write/Edit/MultiEdit) | A stale spelled-out gate-validator count in `docstring-prose-matches-implementation.md` — the "N more gate validators" / "M gated slices" count drifting from the `check_docstring_*` validators the prose names |
84
84
  | `duplicate_rmtree_helper_blocker.py` | PreToolUse (Write/Edit) | A local re-definition of the Windows-safe rmtree helper trio (`_strip_read_only_and_retry`, `_force_remove_tree` / `force_rmtree`) in place of importing a shared helper |
85
- | `eli11_reply_enforcer.py` | Stop | Final replies breaking the `eli11-replies` shape — more than 120 reader-visible words, more than 6 bullet lines, more than 2 lines carrying over 20 words each, or instruction lines telling the user to act with no numbered step among the lead lines. Code fences, inline code, blockquotes, table rows, and link targets come off before the reply is judged, replies under 60 words always pass, and a reply opening with `Long form:` opts out entirely |
85
+ | `eli11_reply_enforcer.py` | Stop | Final replies breaking the `eli11-replies` shape — more than 6 bullet lines, more than 2 list lines over 20 words each, or multi-line instructions with no numbered step among the lead lines. Code fences, inline code, blockquotes, table rows, and link targets come off before the reply is judged. |
86
86
  | `env_var_table_code_drift_blocker.py` | PreToolUse (Write/Edit/MultiEdit) | A markdown env-var summary table row attributing an environment variable to a code file whose source never references that variable name |
87
87
  | `es_exe_path_rewriter.py` | PreToolUse | Rewrites paths referencing `.exe` under the Everything search path |
88
88
  | `fable_spawn_gate.py` | PreToolUse (Agent/Task) | An `Agent` or `Task` spawn whose prompt carries no `FABLE-SPAWN-AUTHORIZED` token and whose model field reads `fable` in any letter case — the bare alias, or a delimiter segment of a full model id, so `claude-fable-5` is denied too |
89
89
  | `gh_body_arg_blocker.py` | PreToolUse (Bash) | `gh` commands passing `--body`/`-b` directly (requires `--body-file` instead) |
90
90
  | `gh_pr_author_enforcer.py` | PreToolUse | Enforces PR author identity rules |
91
91
  | `gh_pr_author_restore.py` | PostToolUse | Restores PR author after a tool call |
92
- | `hedging_language_blocker.py` | Stop | Responses with hedging words (`likely`, `probably`, `appears to`) |
93
- | `hook_prose_detector_consistency.py` | PreToolUse (Write/Edit) | Hook docstrings/messages that claim a trigger the detector cannot fire on |
94
- | `intent_only_ending_blocker.py` | Stop | Responses that end on a plan or intent without doing the work |
92
+ | `hedging_language_blocker.py` | Stop | Responses with hedging words (`likely`, `probably`, `appears to`); armed only when `CLAUDE_PROSE_STYLE_ENFORCEMENT` is on (default off) |
93
+ | `hook_prose_detector_consistency.py` | PreToolUse (Write/Edit) | Hook docstrings/messages that claim a trigger the detector cannot fire on; armed only when `CLAUDE_PROSE_STYLE_ENFORCEMENT` is on (default off) |
94
+ | `intent_only_ending_blocker.py` | Stop | Responses that end on a plan or intent without doing the work; armed only when `CLAUDE_PROSE_STYLE_ENFORCEMENT` is on (default off) |
95
95
  | `open_questions_in_plans_blocker.py` | PreToolUse (Write/Edit) | Plan documents with unresolved open questions |
96
96
  | `nas_ssh_binary_enforcer.py` | PreToolUse (Bash) | A bare `ssh`/`scp`/`sftp` command word targeting the NAS (Git Bash's MSYS ssh stalls on an interactive password prompt), or the full `System32/OpenSSH` binary to that host without `-o BatchMode=yes` |
97
97
  | `package_inventory_stale_blocker.py` | PreToolUse (Write) | A new production code file created in a directory whose `README.md`/`CLAUDE.md` inventory (or a parent skill's `SKILL.md` Layout table mapping the `scripts/` subdirectory) names two or more sibling files but no entry for the new file |
@@ -100,7 +100,7 @@ The check modules it calls are the `code_rules_<concern>.py` files below.
100
100
  | `pii_prevention_blocker.py` | PreToolUse (Write/Edit/MultiEdit/Bash/PowerShell/MCP GitHub) | Entry hook — content that carries high-confidence personal data or secrets (real emails, home-dir paths, private IPs, credential material) on write, durable GitHub posts, or staged commit paths; resolves the staged-commit repository from the command it gates (via `pii_prevention_blocker_parts`), not the session working directory |
101
101
  | `pii_scanner.py` | library | Pure text scanners shared by `pii_prevention_blocker.py` |
102
102
  | `piped_pytest_blocker.py` | PreToolUse (Bash) | A pytest run whose output feeds a pipe, where the pipeline reports the exit code of the command on the right |
103
- | `plain_language_blocker.py` | PreToolUse (Write/Edit/AskUserQuestion) | Heavy or jargon words in user-facing prose |
103
+ | `plain_language_blocker.py` | PreToolUse (Write/Edit/AskUserQuestion) | Heavy or jargon words in user-facing prose when `CLAUDE_PROSE_STYLE_ENFORCEMENT` is on (default off); AskUserQuestion lean-block structure stays always on |
104
104
  | `pr_converge_bugteam_enforcer.py` | PreToolUse | Enforces that bugteam runs in parallel with bugbot in pr-converge loops |
105
105
  | `pr_description_enforcer.py` | PreToolUse (Bash) | `gh pr create`/`edit`/`comment` bodies that fail the Anthropic claude-code style audit, proof-shaped `gh pr comment` bodies missing proof-of-work parts, and `gh pr ready` while the PR carries no passing proof comment |
106
106
  | `precommit_code_rules_gate.py` | PreToolUse (Bash) | Staged changes that fail the CODE_RULES gate at commit time |
@@ -113,7 +113,7 @@ The check modules it calls are the `code_rules_<concern>.py` files below.
113
113
  | `session_handoff_blocker.py` | Stop | Responses suggesting a new session mid-task |
114
114
  | `shell_substitution_blocker.py` | PreToolUse (Bash) | A command carrying `$(...)`, a live backtick, or `<(...)`/`>(...)` process substitution, which the allowlist matcher cannot descend into |
115
115
  | `stale_comment_reference_blocker.py` | PreToolUse (Edit) | An Edit that rewrites a Python code line while keeping the standalone comment directly above it, when that comment names an identifier the rewrite removes from the line |
116
- | `state_description_blocker.py` | PreToolUse (Write/Edit) | Historical/comparative language in documentation |
116
+ | `state_description_blocker.py` | PreToolUse (Write/Edit) | Historical/comparative language in documentation; armed only when `CLAUDE_PROSE_STYLE_ENFORCEMENT` is on (default off) |
117
117
  | `subprocess_budget_completeness.py` | PreToolUse | Subprocess calls missing required budget arguments |
118
118
  | `tdd_enforcer.py` | PreToolUse (Write/Edit) | Production code written without a matching failing test |
119
119
  | `unscoped_search_blocker.py` | PreToolUse (Bash/PowerShell) | A `find` or recursive listing that walks from the filesystem root, a drive root, bare home, or a network share root |
@@ -1,13 +1,17 @@
1
- """Add the hooks directory to sys.path for sibling entry-point dispatchers.
1
+ """Add the hooks and blocking directories to sys.path for sibling imports.
2
2
 
3
3
  Importing this module inserts the hooks directory (this file's parent's parent)
4
- at the front of sys.path so a dispatcher that runs as a standalone script from
5
- blocking/ can import hooks_constants with every import kept at module top.
4
+ and the blocking directory (its own parent) at the front of sys.path, so a
5
+ module that runs as a standalone script from blocking/ can import both
6
+ ``hooks_constants`` and its blocking-directory siblings while every import stays
7
+ at module top.
6
8
  """
7
9
 
8
10
  import sys
9
11
  from pathlib import Path
10
12
 
13
+ _blocking_directory = str(Path(__file__).resolve().parent)
11
14
  _hooks_directory = str(Path(__file__).resolve().parent.parent)
12
- if _hooks_directory not in sys.path:
13
- sys.path.insert(0, _hooks_directory)
15
+ for each_directory in (_blocking_directory, _hooks_directory):
16
+ if each_directory not in sys.path:
17
+ sys.path.insert(0, each_directory)
@@ -13,6 +13,7 @@ if _hooks_directory not in sys.path:
13
13
  sys.path.insert(0, _hooks_directory)
14
14
 
15
15
  from code_rules_shared import ( # noqa: E402
16
+ _scope_violations_to_changed_lines,
16
17
  _statement_is_docstring,
17
18
  _walk_skipping_nested_functions,
18
19
  _walk_skipping_type_checking_blocks,
@@ -2664,18 +2665,53 @@ def _runon_sentences(narrative_text: str) -> list[str]:
2664
2665
  return flagged_sentences
2665
2666
 
2666
2667
 
2668
+ def _docstring_owner_span(owner_node: ast.AST, anchor_lineno: int) -> range:
2669
+ """Lines from the owner's anchor through the end of its docstring statement.
2670
+
2671
+ ::
2672
+
2673
+ def clean_helper() -> str: <- anchor (def / class line)
2674
+ '''run-on narrative across lines''' <- docstring end
2675
+ return "ok" <- outside the span
2676
+
2677
+ An edit that only rewrites the docstring body intersects this span and
2678
+ re-grades the finding. An edit to the return line does not.
2679
+ """
2680
+ body = getattr(owner_node, "body", None) or []
2681
+ if not body:
2682
+ return range(anchor_lineno, anchor_lineno + 1)
2683
+ first_statement = body[0]
2684
+ end_lineno = getattr(first_statement, "end_lineno", None) or first_statement.lineno
2685
+ return range(anchor_lineno, end_lineno + 1)
2686
+
2687
+
2667
2688
  def _documentable_docstring_targets(
2668
2689
  parsed_tree: ast.Module,
2669
- ) -> list[tuple[int, str, str]]:
2670
- documentable_targets: list[tuple[int, str, str]] = []
2690
+ ) -> list[tuple[int, str, str, range]]:
2691
+ documentable_targets: list[tuple[int, str, str, range]] = []
2671
2692
  module_docstring = ast.get_docstring(parsed_tree)
2672
2693
  if module_docstring and parsed_tree.body:
2673
- documentable_targets.append((parsed_tree.body[0].lineno, "module", module_docstring))
2694
+ module_anchor = parsed_tree.body[0].lineno
2695
+ documentable_targets.append(
2696
+ (
2697
+ module_anchor,
2698
+ "module",
2699
+ module_docstring,
2700
+ _docstring_owner_span(parsed_tree, module_anchor),
2701
+ )
2702
+ )
2674
2703
  for each_node in _walk_skipping_type_checking_blocks(parsed_tree):
2675
2704
  if isinstance(each_node, ast.ClassDef):
2676
2705
  class_docstring = ast.get_docstring(each_node)
2677
2706
  if class_docstring:
2678
- documentable_targets.append((each_node.lineno, each_node.name, class_docstring))
2707
+ documentable_targets.append(
2708
+ (
2709
+ each_node.lineno,
2710
+ each_node.name,
2711
+ class_docstring,
2712
+ _docstring_owner_span(each_node, each_node.lineno),
2713
+ )
2714
+ )
2679
2715
  continue
2680
2716
  if not isinstance(each_node, (ast.FunctionDef, ast.AsyncFunctionDef)):
2681
2717
  continue
@@ -2686,34 +2722,58 @@ def _documentable_docstring_targets(
2686
2722
  function_docstring = _function_docstring_text(each_node)
2687
2723
  if function_docstring:
2688
2724
  documentable_targets.append(
2689
- (each_node.lineno, f"{each_node.name}()", function_docstring)
2725
+ (
2726
+ each_node.lineno,
2727
+ f"{each_node.name}()",
2728
+ function_docstring,
2729
+ _docstring_owner_span(each_node, each_node.lineno),
2730
+ )
2690
2731
  )
2691
2732
  return documentable_targets
2692
2733
 
2693
2734
 
2694
- def check_docstring_runon_sentence(content: str, file_path: str) -> list[str]:
2735
+ def check_docstring_runon_sentence(
2736
+ content: str,
2737
+ file_path: str,
2738
+ all_changed_lines: set[int] | None = None,
2739
+ defer_scope_to_caller: bool = False,
2740
+ ) -> list[str]:
2695
2741
  """Flag a docstring narrative sentence that reads as a dense run-on wall.
2696
2742
 
2697
2743
  A readable docstring breaks its narrative into short sentences a general
2698
2744
  developer follows on the first read. The one mechanical mark of a wall is a
2699
2745
  single sentence that runs past the word limit while chaining clauses with an
2700
2746
  em-dash, a double-hyphen, or a semicolon. This check inspects the narrative prose of module,
2701
- class, and public-function docstrings the text before the first structured
2747
+ class, and public-function docstrings - the text before the first structured
2702
2748
  section header (``Args:``, ``Arguments:``, ``Returns:``, ``Yields:``,
2703
- ``Raises:``, ``Note:``, ``Notes:``, ``Example:``, or ``Examples:``) and
2749
+ ``Raises:``, ``Note:``, ``Notes:``, ``Example:``, or ``Examples:``) - and
2704
2750
  reports a sentence that is both over the word limit and joined by one of those
2705
2751
  marks.
2706
2752
  Whether the prose paints a concrete, illustrative picture is judgment the
2707
2753
  plain-illustrative-docstrings audit lane carries; this gate catches only the
2708
2754
  run-on mark.
2709
2755
 
2756
+ The caller passes the reconstructed full file as *content* so ``ast.parse``
2757
+ sees a complete module. Findings are then scoped to *all_changed_lines* so an
2758
+ Edit blocks on a run-on it just introduced while a pre-existing far-away
2759
+ run-on on an untouched definition does not block the edit.
2760
+
2710
2761
  Args:
2711
- content: The source text to inspect.
2762
+ content: The source text to inspect - the reconstructed full file on an
2763
+ Edit so the parse succeeds.
2712
2764
  file_path: The path the source will be written to, used for exemptions.
2765
+ all_changed_lines: Post-edit line numbers the current edit touched, or
2766
+ None to treat the whole file as in scope. When provided, a finding
2767
+ blocks only when its definition-through-docstring span intersects
2768
+ the changed lines.
2769
+ defer_scope_to_caller: When True, return every violation so the
2770
+ commit/push gate's ``split_violations_by_scope`` can scope by added
2771
+ line.
2713
2772
 
2714
2773
  Returns:
2715
2774
  One issue per docstring whose narrative carries a run-on sentence, capped
2716
- at the module limit.
2775
+ at the module limit and scoped to the changed lines unless
2776
+ *defer_scope_to_caller* is True or *all_changed_lines* is None.
2717
2777
  """
2718
2778
  if is_test_file(file_path) or is_hook_infrastructure(file_path):
2719
2779
  return []
@@ -2721,25 +2781,37 @@ def check_docstring_runon_sentence(content: str, file_path: str) -> list[str]:
2721
2781
  parsed_tree = ast.parse(content)
2722
2782
  except SyntaxError:
2723
2783
  return []
2724
- issues: list[str] = []
2725
- for each_line_number, each_label, each_docstring in _documentable_docstring_targets(
2726
- parsed_tree
2727
- ):
2784
+ all_violations_in_walk_order: list[tuple[range, str]] = []
2785
+ for (
2786
+ each_line_number,
2787
+ each_label,
2788
+ each_docstring,
2789
+ each_span,
2790
+ ) in _documentable_docstring_targets(parsed_tree):
2728
2791
  flagged_sentences = _runon_sentences(_docstring_narrative_text(each_docstring))
2729
2792
  if not flagged_sentences:
2730
2793
  continue
2731
2794
  run_on_word_count = _sentence_word_count(flagged_sentences[0])
2732
- issues.append(
2795
+ message = (
2733
2796
  f"Line {each_line_number}: {each_label} docstring carries a {run_on_word_count}-word "
2734
- "run-on sentence break the narrative into short, illustrative sentences a general "
2797
+ "run-on sentence - break the narrative into short, illustrative sentences a general "
2735
2798
  "developer reads in one pass (plain-illustrative-docstrings)"
2736
2799
  )
2737
- if len(issues) >= MAX_DOCSTRING_RUNON_SENTENCE_ISSUES:
2738
- break
2739
- return issues[:MAX_DOCSTRING_RUNON_SENTENCE_ISSUES]
2800
+ all_violations_in_walk_order.append((each_span, message))
2801
+ scoped_issues = _scope_violations_to_changed_lines(
2802
+ all_violations_in_walk_order,
2803
+ all_changed_lines,
2804
+ defer_scope_to_caller,
2805
+ )
2806
+ return scoped_issues[:MAX_DOCSTRING_RUNON_SENTENCE_ISSUES]
2740
2807
 
2741
2808
 
2742
- def check_docstring_prose_wall_without_illustration(content: str, file_path: str) -> list[str]:
2809
+ def check_docstring_prose_wall_without_illustration(
2810
+ content: str,
2811
+ file_path: str,
2812
+ all_changed_lines: set[int] | None = None,
2813
+ defer_scope_to_caller: bool = False,
2814
+ ) -> list[str]:
2743
2815
  """Flag a summary that tells for many sentences and shows nothing.
2744
2816
 
2745
2817
  A reader trusts the opening to paint a scene. A run of short sentences with
@@ -2754,13 +2826,28 @@ def check_docstring_prose_wall_without_illustration(content: str, file_path: str
2754
2826
  Past the prose-line limit with no ``::`` listing and no ``>>>`` doctest, this
2755
2827
  fires. A narrative that shows a worked example, or one at the limit, passes.
2756
2828
 
2829
+ The caller passes the reconstructed full file as *content* so ``ast.parse``
2830
+ sees a complete module. Findings are then scoped to *all_changed_lines* so an
2831
+ Edit blocks on a wall it just introduced while a pre-existing far-away wall
2832
+ on an untouched definition does not block the edit.
2833
+
2757
2834
  Args:
2758
- content: The source text to inspect.
2835
+ content: The source text to inspect - the reconstructed full file on an
2836
+ Edit so the parse succeeds.
2759
2837
  file_path: The path the source will be written to, used for exemptions.
2838
+ all_changed_lines: Post-edit line numbers the current edit touched, or
2839
+ None to treat the whole file as in scope. When provided, a finding
2840
+ blocks only when its definition-through-docstring span intersects
2841
+ the changed lines.
2842
+ defer_scope_to_caller: When True, return every violation so the
2843
+ commit/push gate's ``split_violations_by_scope`` can scope by added
2844
+ line.
2760
2845
 
2761
2846
  Returns:
2762
2847
  One issue per summary that runs a wall of sentences with no worked
2763
- example, capped at the issue limit for the rule.
2848
+ example, capped at the issue limit for the rule and scoped to the
2849
+ changed lines unless *defer_scope_to_caller* is True or
2850
+ *all_changed_lines* is None.
2764
2851
  """
2765
2852
  if is_test_file(file_path) or is_hook_infrastructure(file_path):
2766
2853
  return []
@@ -2768,25 +2855,32 @@ def check_docstring_prose_wall_without_illustration(content: str, file_path: str
2768
2855
  parsed_tree = ast.parse(content)
2769
2856
  except SyntaxError:
2770
2857
  return []
2771
- issues: list[str] = []
2772
- for each_line_number, each_label, each_docstring in _documentable_docstring_targets(
2773
- parsed_tree
2774
- ):
2858
+ all_violations_in_walk_order: list[tuple[range, str]] = []
2859
+ for (
2860
+ each_line_number,
2861
+ each_label,
2862
+ each_docstring,
2863
+ each_span,
2864
+ ) in _documentable_docstring_targets(parsed_tree):
2775
2865
  prose_lines, has_illustration = _docstring_narrative_partition(each_docstring)
2776
2866
  if has_illustration:
2777
2867
  continue
2778
2868
  prose_line_count = len(prose_lines)
2779
2869
  if prose_line_count <= DOCSTRING_NARRATIVE_PROSE_LINE_LIMIT:
2780
2870
  continue
2781
- issues.append(
2871
+ message = (
2782
2872
  f"Line {each_line_number}: {each_label} summary runs {prose_line_count} "
2783
2873
  "narrative lines with no worked example - show, don't tell: swap the wall for a "
2784
2874
  "'::' listing (a sample input, an annotated outcome, ok/flag contrast rows) and "
2785
2875
  "keep the narrative to a few short lines (plain-illustrative-docstrings)"
2786
2876
  )
2787
- if len(issues) >= MAX_DOCSTRING_PROSE_WALL_ISSUES:
2788
- break
2789
- return issues[:MAX_DOCSTRING_PROSE_WALL_ISSUES]
2877
+ all_violations_in_walk_order.append((each_span, message))
2878
+ scoped_issues = _scope_violations_to_changed_lines(
2879
+ all_violations_in_walk_order,
2880
+ all_changed_lines,
2881
+ defer_scope_to_caller,
2882
+ )
2883
+ return scoped_issues[:MAX_DOCSTRING_PROSE_WALL_ISSUES]
2790
2884
 
2791
2885
 
2792
2886
  def _raises_section_text(docstring_text: str) -> str: