claude-dev-env 2.9.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 (289) hide show
  1. package/CLAUDE.md +1 -1
  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 +58 -548
  22. package/agents/code-quality-agent.md +10 -2
  23. package/agents/code-verifier.md +1 -1
  24. package/agents/test_agent_frontmatter.py +32 -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/references/CLAUDE.md +1 -0
  44. package/docs/references/advisor-tool.md +1 -1
  45. package/docs/references/prose-style-enforcement.md +25 -0
  46. package/hooks/blocking/CLAUDE.md +6 -6
  47. package/hooks/blocking/_path_setup.py +9 -5
  48. package/hooks/blocking/code_rules_docstrings.py +124 -30
  49. package/hooks/blocking/code_rules_enforcer.py +161 -16
  50. package/hooks/blocking/code_rules_shared.py +40 -23
  51. package/hooks/blocking/config/CLAUDE.md +3 -5
  52. package/hooks/blocking/config/prose_style_enforcement_constants.py +38 -0
  53. package/hooks/blocking/config/test_prose_style_enforcement_constants.py +45 -0
  54. package/hooks/blocking/eli11_reply_enforcer.py +70 -113
  55. package/hooks/blocking/hedging_language_blocker.py +103 -20
  56. package/hooks/blocking/hook_prose_detector_consistency.py +6 -0
  57. package/hooks/blocking/intent_only_ending_blocker.py +6 -0
  58. package/hooks/blocking/plain_language_blocker.py +139 -20
  59. package/hooks/blocking/pre_tool_use_dispatcher.py +102 -20
  60. package/hooks/blocking/state_description_blocker.py +7 -1
  61. package/hooks/blocking/tdd_enforcer.py +8 -0
  62. package/hooks/blocking/test__path_setup.py +28 -0
  63. package/hooks/blocking/test_code_rules_enforcer_agent_home_tooling.py +99 -0
  64. package/hooks/blocking/test_code_rules_enforcer_docstring_args_span_scope.py +232 -10
  65. package/hooks/blocking/test_code_rules_enforcer_ephemeral.py +1 -1
  66. package/hooks/blocking/test_code_rules_enforcer_join_separator_magic.py +41 -0
  67. package/hooks/blocking/test_code_rules_enforcer_string_magic.py +98 -0
  68. package/hooks/blocking/test_eli11_reply_enforcer.py +98 -165
  69. package/hooks/blocking/test_hedging_language_blocker.py +120 -1
  70. package/hooks/blocking/test_hook_prose_detector_consistency.py +28 -8
  71. package/hooks/blocking/test_intent_only_ending_blocker.py +27 -2
  72. package/hooks/blocking/test_package_inventory_stale_blocker.py +11 -4
  73. package/hooks/blocking/test_plain_language_blocker.py +129 -19
  74. package/hooks/blocking/test_plain_language_blocker_allowlist.py +70 -26
  75. package/hooks/blocking/test_pre_tool_use_dispatcher.py +99 -26
  76. package/hooks/blocking/test_pre_tool_use_dispatcher_native.py +87 -50
  77. package/hooks/blocking/test_state_description_blocker.py +45 -2
  78. package/hooks/blocking/test_stop_dispatcher.py +11 -7
  79. package/hooks/blocking/test_volatile_path_in_post_blocker.py +12 -12
  80. package/hooks/blocking/volatile_path_in_post_blocker.py +2 -2
  81. package/hooks/hooks.json +15 -0
  82. package/hooks/hooks_constants/CLAUDE.md +14 -3
  83. package/hooks/hooks_constants/ask_user_question_shape.py +281 -0
  84. package/hooks/hooks_constants/code_rules_enforcer_constants.py +2 -1
  85. package/hooks/hooks_constants/eli11_reply_enforcer_constants.py +5 -12
  86. package/hooks/hooks_constants/hedging_uncertainty_constants.py +42 -0
  87. package/hooks/hooks_constants/issue_tracker_session_starter_constants.py +23 -0
  88. package/hooks/hooks_constants/orchestrator_auto_starter_constants.py +23 -0
  89. package/hooks/hooks_constants/piped_pytest_blocker_constants.py +4 -1
  90. package/hooks/hooks_constants/plain_language_blocker_constants.py +4 -1
  91. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +6 -0
  92. package/hooks/hooks_constants/project_paths_reader.py +31 -4
  93. package/hooks/hooks_constants/prose_matcher_precision_constants.py +40 -0
  94. package/hooks/hooks_constants/pytest_invocation.py +354 -0
  95. package/hooks/hooks_constants/session_start_injector.py +163 -0
  96. package/hooks/hooks_constants/session_start_injector_constants.py +46 -0
  97. package/hooks/hooks_constants/shell_command_pipeline.py +397 -0
  98. package/hooks/hooks_constants/shell_command_segments.py +5 -0
  99. package/hooks/hooks_constants/test_ask_user_question_shape.py +167 -0
  100. package/hooks/hooks_constants/test_project_paths_reader.py +29 -0
  101. package/hooks/hooks_constants/test_prose_metrics_parity.py +8 -0
  102. package/hooks/hooks_constants/test_pytest_invocation.py +130 -0
  103. package/hooks/hooks_constants/test_session_start_injector.py +168 -0
  104. package/hooks/hooks_constants/test_shell_command_pipeline.py +135 -0
  105. package/hooks/hooks_constants/volatile_path_in_post_blocker_constants.py +1 -1
  106. package/hooks/hooks_constants/working_style_prompt_constants.py +30 -0
  107. package/hooks/observability/CLAUDE.md +2 -0
  108. package/hooks/observability/prose_matcher_advisory.py +237 -0
  109. package/hooks/observability/test_prose_matcher_advisory.py +143 -0
  110. package/hooks/session/CLAUDE.md +9 -1
  111. package/hooks/session/_path_setup.py +13 -0
  112. package/hooks/session/issue_tracker_session_starter.py +135 -0
  113. package/hooks/session/orchestrator_auto_starter.py +100 -0
  114. package/hooks/session/test__path_setup.py +28 -0
  115. package/hooks/session/test_issue_tracker_session_starter.py +104 -0
  116. package/hooks/session/test_orchestrator_auto_starter.py +99 -0
  117. package/hooks/session/test_working_style_prompt.py +47 -0
  118. package/hooks/session/untracked_repo_detector.py +1 -24
  119. package/hooks/session/working_style_prompt.py +36 -0
  120. package/hooks/validators/_path_setup.py +19 -0
  121. package/hooks/validators/run_all_validators.py +8 -13
  122. package/installable-surfaces.manifest.json +21 -0
  123. package/output-styles/CLAUDE.md +1 -3
  124. package/package.json +4 -2
  125. package/rules/CLAUDE.md +1 -0
  126. package/rules/durable-post-artifacts.md +2 -2
  127. package/rules/eli11-replies.md +6 -1
  128. package/rules/hedging-claims.md +4 -2
  129. package/rules/long-horizon-autonomy.md +3 -1
  130. package/rules/opus5-communication-contract.md +45 -0
  131. package/rules/plain-language.md +2 -2
  132. package/rules/research-mode.md +1 -1
  133. package/scripts/CLAUDE.md +11 -0
  134. package/scripts/Sync-RepoMain.ps1 +215 -0
  135. package/scripts/active_capability_references.py +218 -0
  136. package/scripts/ci/windows-installer-lifecycle.ps1 +78 -0
  137. package/scripts/claude_chain_runner.py +394 -6
  138. package/scripts/claude_chain_usage.py +1 -1
  139. package/scripts/codex_compat_materializer.py +105 -85
  140. package/scripts/dev_env_scripts_constants/CLAUDE.md +2 -0
  141. package/scripts/dev_env_scripts_constants/active_capability_constants.py +46 -0
  142. package/scripts/dev_env_scripts_constants/claude_chain_constants.py +74 -0
  143. package/scripts/dev_env_scripts_constants/verify_installable_package_constants.py +116 -0
  144. package/scripts/profile-isolation-launchers/config/mcp-bundles.json +25 -0
  145. package/scripts/profile-isolation-launchers/config/profile-isolation-constants.mjs +60 -0
  146. package/scripts/profile-isolation-launchers/config/profiles.manifest.json +54 -0
  147. package/scripts/profile-isolation-launchers/config/shared-allowlist.json +64 -0
  148. package/scripts/profile-isolation-launchers/launcher-runtime.mjs +180 -0
  149. package/scripts/profile-isolation-launchers/lib/profile-manifest.mjs +288 -0
  150. package/scripts/profile-isolation-launchers/mcp-bundles.mjs +275 -0
  151. package/scripts/profile-isolation-launchers/profile-isolation-contract.test.mjs +221 -0
  152. package/scripts/profile-isolation-launchers/tests/launcher-runtime.test.mjs +108 -0
  153. package/scripts/profile-isolation-launchers/tests/mcp-bundles.test.mjs +147 -0
  154. package/scripts/profile-isolation-launchers/tests/shortcut-contract.test.ps1 +102 -0
  155. package/scripts/profile-isolation-launchers/tests/version-compatibility.test.mjs +210 -0
  156. package/scripts/profile-isolation-launchers/version-compatibility.mjs +299 -0
  157. package/scripts/profile-isolation-launchers/windows/shortcut-inventory.ps1 +127 -0
  158. package/scripts/profile-isolation-launchers/windows/shortcut-manifest.json +51 -0
  159. package/scripts/profile-isolation-launchers/windows/shortcut-reconcile.ps1 +77 -0
  160. package/scripts/spawn_grok_batch.py +3 -0
  161. package/scripts/test_active_capability_references.py +108 -0
  162. package/scripts/test_claude_chain_runner.py +414 -82
  163. package/scripts/test_claude_chain_usage.py +12 -12
  164. package/scripts/test_resolve_worker_spawn.py +2 -2
  165. package/scripts/test_verify_installable_package.py +208 -0
  166. package/scripts/tests/test_codex_compat_materializer.py +33 -0
  167. package/scripts/verify_installable_package.py +612 -0
  168. package/settings.json +10 -0
  169. package/skills/CLAUDE.md +2 -0
  170. package/skills/_shared/pr-loop/scripts/CLAUDE.md +1 -0
  171. package/skills/_shared/pr-loop/scripts/audit_category_schema.py +355 -0
  172. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/CLAUDE.md +1 -0
  173. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/audit_category_schema_constants.py +32 -0
  174. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py +7 -19
  175. package/skills/_shared/pr-loop/scripts/test_audit_category_schema.py +94 -0
  176. package/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +21 -0
  177. package/skills/autoconverge/reference/convergence.md +2 -1
  178. package/skills/autoconverge/reference/stop-conditions.md +5 -3
  179. package/skills/beat-sheet/SKILL.md +54 -0
  180. package/skills/beat-sheet/reference/visual-beats.md +29 -0
  181. package/skills/bugteam/CONSTRAINTS.md +4 -4
  182. package/skills/bugteam/EXAMPLES.md +1 -1
  183. package/skills/bugteam/reference/README.md +1 -1
  184. package/skills/e-code-review/SKILL.md +26 -5
  185. package/skills/e-code-review/reference/effort-evaluation.md +35 -0
  186. package/skills/e-code-review/reference/medium.md +15 -4
  187. package/skills/e-code-review/scripts/config/e_code_review_effort_constants/__init__.py +41 -0
  188. package/skills/e-code-review/scripts/config/e_code_review_effort_constants/effort_constants.py +40 -0
  189. package/skills/e-code-review/scripts/e_code_review_scripts_constants/finding_pipeline_constants.py +49 -0
  190. package/skills/e-code-review/scripts/effort_defaults_evidence.json +186 -0
  191. package/skills/e-code-review/scripts/effort_evaluation.py +362 -0
  192. package/skills/e-code-review/scripts/finding_pipeline.py +140 -0
  193. package/skills/e-code-review/scripts/fixtures/demanding.json +26 -0
  194. package/skills/e-code-review/scripts/fixtures/easy.json +14 -0
  195. package/skills/e-code-review/scripts/fixtures/medium.json +20 -0
  196. package/skills/e-code-review/scripts/grok_code_review.py +16 -7
  197. package/skills/e-code-review/scripts/test_effort_evaluation.py +180 -0
  198. package/skills/e-code-review/scripts/test_finding_pipeline.py +197 -0
  199. package/skills/e-code-review/scripts/test_grok_code_review.py +77 -0
  200. package/skills/plan-to-pr/scripts/create_packet.py +4 -4
  201. package/skills/plan-to-pr/scripts/load_skill_constants.py +41 -0
  202. package/skills/plan-to-pr/scripts/validate_packet.py +4 -4
  203. package/skills/plan-to-pr/scripts/validate_protocol.py +4 -1
  204. package/skills/plan-to-pr/scripts/validate_run.py +4 -1
  205. package/skills/pr-converge/scripts/check_convergence.py +21 -19
  206. package/skills/pr-converge/scripts/check_convergence_availability.py +50 -7
  207. package/skills/pr-converge/scripts/conftest.py +35 -0
  208. package/skills/pr-converge/scripts/test_check_convergence_availability.py +65 -0
  209. package/skills/pr-converge/scripts/test_check_convergence_codex.py +11 -1
  210. package/skills/pr-converge/scripts/test_check_convergence_contract.py +9 -2
  211. package/skills/pr-loop-cloud-transport/SKILL.md +1 -1
  212. package/skills/rebase/SKILL.md +15 -3
  213. package/skills/reviewer-gates/SKILL.md +2 -2
  214. package/skills/show/SKILL.md +51 -0
  215. package/skills/show/references/accessibility.md +7 -0
  216. package/skills/show/references/art.md +3 -0
  217. package/skills/show/references/charts.md +3 -0
  218. package/skills/show/references/core-design.md +14 -0
  219. package/skills/show/references/erds.md +3 -0
  220. package/skills/show/references/flowcharts.md +3 -0
  221. package/skills/show/references/host-and-html.md +3 -0
  222. package/skills/show/references/illustrative-diagrams.md +10 -0
  223. package/skills/show/references/interaction.md +3 -0
  224. package/skills/show/references/mockups.md +3 -0
  225. package/skills/show/references/quality-gates.md +7 -0
  226. package/skills/show/references/structural-diagrams.md +3 -0
  227. package/skills/show/references/subject-inventory.md +21 -0
  228. package/skills/show/references/svg-contract.md +22 -0
  229. package/skills/show/routing.yaml +30 -0
  230. package/skills/show/samples/pr1262-v2.svg +222 -0
  231. package/skills/show/scripts/README.md +6 -0
  232. package/skills/show/scripts/validate-artifact.py +91 -0
  233. package/skills/show/scripts/validate-package.py +18 -0
  234. package/skills/show/templates/html-widget.html +4 -0
  235. package/skills/show/templates/svg-base.svg +19 -0
  236. package/skills/show/tests/fixtures/css-var.svg +6 -0
  237. package/skills/show/tests/fixtures/dead-ref.svg +7 -0
  238. package/skills/show/tests/fixtures/filled-glyph.svg +8 -0
  239. package/skills/show/tests/fixtures/inherited-fill.svg +18 -0
  240. package/skills/show/tests/fixtures/invalid.svg +1 -0
  241. package/skills/show/tests/fixtures/large-canvas.svg +21 -0
  242. package/skills/show/tests/fixtures/unfilled-connector.svg +15 -0
  243. package/skills/show/tests/fixtures/valid.html +1 -0
  244. package/skills/show/tests/test_validate-artifact.py +74 -0
  245. package/skills/show/tests/test_validators.py +59 -0
  246. package/skills/show/workflows/create-visual.md +13 -0
  247. package/skills/show/workflows/review-visual.md +20 -0
  248. package/skills/split-pr/SKILL.md +85 -0
  249. package/skills/split-pr/reference/path-layers.md +16 -0
  250. package/skills/split-pr/reference/proposal-format.md +15 -0
  251. package/skills/split-pr/reference/split-further-loop.md +10 -0
  252. package/skills/split-pr/reference/splitting-principles.md +26 -0
  253. package/skills/split-pr/scripts/analyze_pr.py +279 -0
  254. package/skills/split-pr/scripts/categorize_files.py +106 -0
  255. package/skills/split-pr/scripts/config/__init__.py +1 -0
  256. package/skills/split-pr/scripts/config/dependency_constants.py +14 -0
  257. package/skills/split-pr/scripts/config/git_operations_constants.py +36 -0
  258. package/skills/split-pr/scripts/config/packing_constants.py +61 -0
  259. package/skills/split-pr/scripts/config/plan_constants.py +49 -0
  260. package/skills/split-pr/scripts/config/split_pr_constants.py +110 -0
  261. package/skills/split-pr/scripts/execute_split_slices.py +82 -0
  262. package/skills/split-pr/scripts/pack_files_into_slices.py +212 -0
  263. package/skills/split-pr/scripts/split_pr_dependency_graph.py +70 -0
  264. package/skills/split-pr/scripts/split_pr_git_operations.py +184 -0
  265. package/skills/split-pr/scripts/split_pr_layer_order.py +58 -0
  266. package/skills/split-pr/scripts/split_pr_paginate.py +119 -0
  267. package/skills/split-pr/scripts/split_pr_process_runner.py +52 -0
  268. package/skills/split-pr/scripts/split_pr_script_types.py +126 -0
  269. package/skills/split-pr/scripts/split_pr_title.py +41 -0
  270. package/skills/split-pr/scripts/test_analyze_pr.py +228 -0
  271. package/skills/split-pr/scripts/test_categorize_files.py +55 -0
  272. package/skills/split-pr/scripts/test_categorize_files_packing.py +59 -0
  273. package/skills/split-pr/scripts/test_execute_split_slices.py +99 -0
  274. package/skills/split-pr/scripts/test_split_pr_dependency_graph.py +47 -0
  275. package/skills/split-pr/scripts/test_split_pr_git_operations.py +125 -0
  276. package/skills/split-pr/scripts/test_split_pr_layer_order.py +36 -0
  277. package/skills/split-pr/scripts/test_split_pr_paginate.py +65 -0
  278. package/skills/split-pr/scripts/test_split_pr_script_types.py +73 -0
  279. package/skills/split-pr/scripts/test_split_pr_title.py +28 -0
  280. package/skills/split-pr/scripts/test_verify_dependency_graph.py +46 -0
  281. package/skills/split-pr/scripts/test_verify_plan.py +56 -0
  282. package/skills/split-pr/scripts/test_verify_plan_contract.py +50 -0
  283. package/skills/split-pr/scripts/test_verify_plan_path_normalization.py +45 -0
  284. package/skills/split-pr/scripts/verify_dependency_graph.py +111 -0
  285. package/skills/split-pr/scripts/verify_plan.py +139 -0
  286. package/system-prompts/software-engineer.xml +11 -2
  287. package/commands/initialize.md +0 -90
  288. package/commands/stubcheck.md +0 -88
  289. package/output-styles/caveman-agent.md +0 -37
@@ -0,0 +1,104 @@
1
+ """Tests for the repository-gated issue-tracker SessionStart starter."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ import subprocess
8
+ import sys
9
+ from pathlib import Path
10
+
11
+ import pytest
12
+
13
+ _HOOKS_ROOT = Path(__file__).resolve().parent.parent
14
+ if str(_HOOKS_ROOT) not in sys.path:
15
+ sys.path.insert(0, str(_HOOKS_ROOT))
16
+
17
+ from hooks_constants.issue_tracker_session_starter_constants import (
18
+ ISSUE_TRACKER_SESSION_START_DIRECTIVE,
19
+ ISSUE_TRACKER_SESSION_STARTER_ENABLED_ENV_VAR,
20
+ ISSUE_TRACKER_STARTER_TIMEOUT_MILLISECONDS,
21
+ )
22
+ from session.issue_tracker_session_starter import (
23
+ issue_tracker_session_starter_enabled_in_environment,
24
+ repository_is_registered,
25
+ run_issue_tracker_session_starter,
26
+ )
27
+
28
+ STARTER_SCRIPT = Path(__file__).resolve().parent / "issue_tracker_session_starter.py"
29
+ DEFAULT_TIMEOUT = ISSUE_TRACKER_STARTER_TIMEOUT_MILLISECONDS
30
+
31
+
32
+ def test_default_environment_is_disabled(monkeypatch: pytest.MonkeyPatch) -> None:
33
+ monkeypatch.delenv(ISSUE_TRACKER_SESSION_STARTER_ENABLED_ENV_VAR, raising=False)
34
+ assert issue_tracker_session_starter_enabled_in_environment() is False
35
+
36
+
37
+ def test_enabled_env_values(monkeypatch: pytest.MonkeyPatch) -> None:
38
+ monkeypatch.setenv(ISSUE_TRACKER_SESSION_STARTER_ENABLED_ENV_VAR, "1")
39
+ assert issue_tracker_session_starter_enabled_in_environment() is True
40
+ monkeypatch.setenv(ISSUE_TRACKER_SESSION_STARTER_ENABLED_ENV_VAR, "true")
41
+ assert issue_tracker_session_starter_enabled_in_environment() is True
42
+
43
+
44
+ def test_disabled_or_ineligible_emits_nothing() -> None:
45
+ assert (
46
+ run_issue_tracker_session_starter(
47
+ {"source": "startup"}, False, True, DEFAULT_TIMEOUT
48
+ )
49
+ == {}
50
+ )
51
+ assert (
52
+ run_issue_tracker_session_starter(
53
+ {"source": "startup"}, True, False, DEFAULT_TIMEOUT
54
+ )
55
+ == {}
56
+ )
57
+
58
+
59
+ def test_enabled_eligible_startup_emits_directive() -> None:
60
+ payload = run_issue_tracker_session_starter(
61
+ {"source": "startup"}, True, True, DEFAULT_TIMEOUT
62
+ )
63
+ assert payload["additionalContext"] == ISSUE_TRACKER_SESSION_START_DIRECTIVE
64
+
65
+
66
+ def test_timeout_zero_emits_nothing() -> None:
67
+ assert (
68
+ run_issue_tracker_session_starter({"source": "startup"}, True, True, 0) == {}
69
+ )
70
+
71
+
72
+ def test_unknown_source_emits_nothing() -> None:
73
+ assert (
74
+ run_issue_tracker_session_starter({"source": "weird"}, True, True, DEFAULT_TIMEOUT)
75
+ == {}
76
+ )
77
+
78
+
79
+ def test_repository_is_registered_fail_closed_empty_registry() -> None:
80
+ assert repository_is_registered("/any/path", {}) is False # empty registry fails closed
81
+
82
+
83
+ def test_repository_is_registered_matches_git_root(tmp_path: Path) -> None:
84
+ (tmp_path / ".git").mkdir()
85
+ child = tmp_path / "pkg"
86
+ child.mkdir()
87
+ registry = {"demo": str(tmp_path)}
88
+ assert repository_is_registered(str(child), registry) is True
89
+ assert repository_is_registered(str(tmp_path / "other"), {"demo": str(tmp_path / "nope")}) is False
90
+
91
+
92
+ def test_main_disabled_prints_nothing() -> None:
93
+ environment_by_key = os.environ.copy()
94
+ environment_by_key.pop(ISSUE_TRACKER_SESSION_STARTER_ENABLED_ENV_VAR, None)
95
+ completed = subprocess.run(
96
+ [sys.executable, str(STARTER_SCRIPT)],
97
+ input=json.dumps({"source": "startup"}),
98
+ capture_output=True,
99
+ text=True,
100
+ check=False,
101
+ env=environment_by_key,
102
+ )
103
+ assert completed.returncode == 0
104
+ assert completed.stdout.strip() == ""
@@ -0,0 +1,99 @@
1
+ """Tests for the opt-in orchestrator SessionStart auto-starter."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import os
7
+ import subprocess
8
+ import sys
9
+ from pathlib import Path
10
+
11
+ import pytest
12
+
13
+ _HOOKS_ROOT = Path(__file__).resolve().parent.parent
14
+ if str(_HOOKS_ROOT) not in sys.path:
15
+ sys.path.insert(0, str(_HOOKS_ROOT))
16
+
17
+ from hooks_constants.orchestrator_auto_starter_constants import (
18
+ ORCHESTRATOR_AUTO_STARTER_ENABLED_ENV_VAR,
19
+ ORCHESTRATOR_SESSION_START_DIRECTIVE,
20
+ ORCHESTRATOR_STARTER_TIMEOUT_MILLISECONDS,
21
+ )
22
+ from session.orchestrator_auto_starter import (
23
+ orchestrator_auto_starter_enabled_in_environment,
24
+ run_orchestrator_auto_starter,
25
+ )
26
+
27
+ STARTER_SCRIPT = Path(__file__).resolve().parent / "orchestrator_auto_starter.py"
28
+ DEFAULT_TIMEOUT = ORCHESTRATOR_STARTER_TIMEOUT_MILLISECONDS
29
+
30
+
31
+ def test_default_environment_is_disabled(monkeypatch: pytest.MonkeyPatch) -> None:
32
+ monkeypatch.delenv(ORCHESTRATOR_AUTO_STARTER_ENABLED_ENV_VAR, raising=False)
33
+ assert orchestrator_auto_starter_enabled_in_environment() is False
34
+
35
+
36
+ def test_enabled_env_values(monkeypatch: pytest.MonkeyPatch) -> None:
37
+ monkeypatch.setenv(ORCHESTRATOR_AUTO_STARTER_ENABLED_ENV_VAR, "1")
38
+ assert orchestrator_auto_starter_enabled_in_environment() is True
39
+ monkeypatch.setenv(ORCHESTRATOR_AUTO_STARTER_ENABLED_ENV_VAR, "true")
40
+ assert orchestrator_auto_starter_enabled_in_environment() is True
41
+
42
+
43
+ def test_disabled_path_emits_empty_without_context() -> None:
44
+ payload = run_orchestrator_auto_starter(
45
+ {"source": "startup"}, False, DEFAULT_TIMEOUT
46
+ )
47
+ assert payload == {}
48
+
49
+
50
+ def test_enabled_startup_emits_orchestrator_directive() -> None:
51
+ payload = run_orchestrator_auto_starter(
52
+ {"source": "startup"}, True, DEFAULT_TIMEOUT
53
+ )
54
+ assert payload["additionalContext"] == ORCHESTRATOR_SESSION_START_DIRECTIVE
55
+
56
+
57
+ @pytest.mark.parametrize("source", ("startup", "resume", "clear", "compact"))
58
+ def test_enabled_known_sources_emit_directive(source: str) -> None:
59
+ payload = run_orchestrator_auto_starter({"source": source}, True, DEFAULT_TIMEOUT)
60
+ assert ORCHESTRATOR_SESSION_START_DIRECTIVE in payload.get("additionalContext", "")
61
+
62
+
63
+ def test_enabled_unknown_source_emits_nothing() -> None:
64
+ payload = run_orchestrator_auto_starter({"source": "not-real"}, True, DEFAULT_TIMEOUT)
65
+ assert payload == {}
66
+
67
+
68
+ def test_timeout_budget_zero_emits_nothing() -> None:
69
+ payload = run_orchestrator_auto_starter({"source": "startup"}, True, 0)
70
+ assert payload == {}
71
+
72
+
73
+ def test_main_stdin_enabled_prints_json() -> None:
74
+ completed = subprocess.run(
75
+ [sys.executable, str(STARTER_SCRIPT)],
76
+ input=json.dumps({"source": "startup"}),
77
+ capture_output=True,
78
+ text=True,
79
+ check=False,
80
+ env={**os.environ, ORCHESTRATOR_AUTO_STARTER_ENABLED_ENV_VAR: "1"},
81
+ )
82
+ assert completed.returncode == 0
83
+ parsed = json.loads(completed.stdout)
84
+ assert parsed["additionalContext"] == ORCHESTRATOR_SESSION_START_DIRECTIVE
85
+
86
+
87
+ def test_main_stdin_disabled_prints_nothing() -> None:
88
+ environment_by_key = os.environ.copy()
89
+ environment_by_key.pop(ORCHESTRATOR_AUTO_STARTER_ENABLED_ENV_VAR, None)
90
+ completed = subprocess.run(
91
+ [sys.executable, str(STARTER_SCRIPT)],
92
+ input=json.dumps({"source": "startup"}),
93
+ capture_output=True,
94
+ text=True,
95
+ check=False,
96
+ env=environment_by_key,
97
+ )
98
+ assert completed.returncode == 0
99
+ assert completed.stdout.strip() == ""
@@ -0,0 +1,47 @@
1
+ """Tests for working_style_prompt — SessionStart hook that injects working-style text."""
2
+
3
+ import json
4
+ import sys
5
+ from io import StringIO
6
+ from pathlib import Path
7
+ from unittest.mock import patch
8
+
9
+ _SESSION_DIR = Path(__file__).resolve().parent
10
+ _HOOKS_ROOT = _SESSION_DIR.parent
11
+ for each_sys_path_entry in (str(_SESSION_DIR), str(_HOOKS_ROOT)):
12
+ if each_sys_path_entry not in sys.path:
13
+ sys.path.insert(0, each_sys_path_entry)
14
+
15
+ import working_style_prompt as starter
16
+
17
+ from hooks_constants.working_style_prompt_constants import (
18
+ WORKING_STYLE_PROMPT,
19
+ )
20
+
21
+
22
+ def _run_main() -> str:
23
+ """Return stdout produced by running the hook's main()."""
24
+ captured_stdout = StringIO()
25
+ with patch("sys.stdout", captured_stdout):
26
+ starter.main()
27
+ return captured_stdout.getvalue()
28
+
29
+
30
+ class TestWorkingStylePrompt:
31
+ def test_main_emits_additional_context(self) -> None:
32
+ emitted = json.loads(_run_main())
33
+ assert "additionalContext" in emitted
34
+
35
+ def test_additional_context_matches_prompt_exactly(self) -> None:
36
+ emitted = json.loads(_run_main())
37
+ assert emitted["additionalContext"] == WORKING_STYLE_PROMPT
38
+
39
+ def test_emitted_prompt_carries_ledger_and_scope_lines(self) -> None:
40
+ emitted = json.loads(_run_main())
41
+ prompt_text = emitted["additionalContext"]
42
+ assert "scratch txt file you'll keep running as you go; ledger, if you will." in prompt_text
43
+ assert "Deliver what was asked, at the scope intended." in prompt_text
44
+ assert 'answer "what happened" or "what did you find,"' in prompt_text
45
+
46
+ def test_build_session_directive_returns_the_shared_constant(self) -> None:
47
+ assert starter.build_session_directive() == WORKING_STYLE_PROMPT
@@ -21,12 +21,11 @@ if _hooks_dir not in sys.path:
21
21
 
22
22
  from hooks_constants.dynamic_stderr_handler import DynamicStderrHandler # noqa: E402
23
23
  from hooks_constants.project_paths_reader import ( # noqa: E402
24
+ find_git_root,
24
25
  load_registry,
25
26
  registry_contains_path,
26
27
  registry_file_path,
27
28
  )
28
- from hooks_constants.setup_project_paths_constants import GIT_DIRECTORY_SEGMENT_NAME # noqa: E402
29
-
30
29
 
31
30
  _logger = logging.getLogger("untracked_repo_detector")
32
31
  if not _logger.handlers:
@@ -42,28 +41,6 @@ def current_working_directory() -> str:
42
41
  return os.getcwd()
43
42
 
44
43
 
45
- def find_git_root(start_path: str) -> str | None:
46
- """Walk upward from start_path looking for a .git directory.
47
-
48
- The walk is bounded by the user's home directory: once the candidate
49
- reaches the home directory without finding ``.git``, the search stops.
50
- This prevents a stray ``.git`` above the user's home (for example a
51
- parent dotfiles repo) from being falsely reported as the session's repo.
52
-
53
- Returns the absolute path of the repo root, or None if not found.
54
- """
55
- home_directory = Path.home().resolve()
56
- candidate = Path(start_path).resolve()
57
- while True:
58
- if (candidate / GIT_DIRECTORY_SEGMENT_NAME).exists():
59
- return str(candidate)
60
- if candidate == home_directory:
61
- return None
62
- parent = candidate.parent
63
- if parent == candidate:
64
- return None
65
- candidate = parent
66
-
67
44
 
68
45
  def _build_confirm_instruction(repo_root: str) -> str:
69
46
  config_file_path = str(registry_file_path())
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env python3
2
+ """SessionStart hook — inject working-style guidance into the session.
3
+
4
+ At session start this hook emits an ``additionalContext`` block carrying a fixed
5
+ working-style prompt: keep a running ledger, write in plain English, narrate
6
+ before the first tool call, lead with the outcome, and stay at the asked scope.
7
+ The hook writes nothing and runs no tools itself.
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import json
13
+ import sys
14
+ from pathlib import Path
15
+
16
+ _hooks_dir = str(Path(__file__).resolve().parent.parent)
17
+ if _hooks_dir not in sys.path:
18
+ sys.path.insert(0, _hooks_dir)
19
+
20
+ from hooks_constants.working_style_prompt_constants import ( # noqa: E402
21
+ WORKING_STYLE_PROMPT,
22
+ )
23
+
24
+
25
+ def build_session_directive() -> str:
26
+ """Return the working-style prompt emitted at session start."""
27
+ return WORKING_STYLE_PROMPT
28
+
29
+
30
+ def main() -> None:
31
+ """Emit the working-style prompt as SessionStart additionalContext."""
32
+ print(json.dumps({"additionalContext": build_session_directive()}))
33
+
34
+
35
+ if __name__ == "__main__":
36
+ main()
@@ -0,0 +1,19 @@
1
+ """Add the hooks directory to sys.path for cross-package validator imports.
2
+
3
+ Importing this module inserts the hooks directory (this file's parent's parent)
4
+ at the front of sys.path, so a validator module can import the ``blocking`` and
5
+ ``hooks_constants`` packages while every import stays at module top.
6
+ """
7
+
8
+ import sys
9
+ from pathlib import Path
10
+
11
+ hooks_directory_on_path = str(Path(__file__).resolve().parent.parent)
12
+ """The hooks directory this module places on ``sys.path``.
13
+
14
+ An importer binds this name so the bootstrap import reads as used rather than as
15
+ a stray side-effect import.
16
+ """
17
+
18
+ if hooks_directory_on_path not in sys.path:
19
+ sys.path.insert(0, hooks_directory_on_path)
@@ -19,6 +19,7 @@ from datetime import datetime
19
19
  from pathlib import Path
20
20
  from typing import Callable, Dict, List, Optional, Tuple
21
21
 
22
+ from ._path_setup import hooks_directory_on_path # noqa: F401
22
23
  from .config.directory_exemption_constants import (
23
24
  ALL_DIRECTORY_EXEMPTION_SEGMENT_NAMES,
24
25
  ALL_DIRECTORY_EXEMPTION_SUBSTRING_PATTERNS,
@@ -29,23 +30,17 @@ from .mypy_integration import check_mypy_available, run_mypy_check
29
30
  from .output_formatter import OutputFormatter, OutputMode, ValidatorResultDict
30
31
  from .python_style_checks import fix_file
31
32
  from .ruff_integration import check_ruff_available, run_ruff_check
32
-
33
+ from blocking.code_rules_shared import is_ephemeral_path
34
+ from hooks_constants.hook_block_logger import log_hook_block
35
+ from hooks_constants.multi_edit_reconstruction import (
36
+ apply_edits,
37
+ edits_for_tool,
38
+ )
33
39
 
34
40
  VALIDATORS_DIR = Path(__file__).parent
35
41
  hooks_dir = VALIDATORS_DIR.parent
36
42
  package_name = VALIDATORS_DIR.name
37
43
 
38
- _hooks_directory_on_path = str(hooks_dir.resolve())
39
- if _hooks_directory_on_path not in sys.path:
40
- sys.path.insert(0, _hooks_directory_on_path)
41
-
42
- from blocking.code_rules_shared import is_ephemeral_path # noqa: E402
43
- from hooks_constants.hook_block_logger import log_hook_block # noqa: E402
44
- from hooks_constants.multi_edit_reconstruction import ( # noqa: E402
45
- apply_edits,
46
- edits_for_tool,
47
- )
48
-
49
44
 
50
45
  def _windows_non_unc_working_directory_string(
51
46
  candidate_directory_strings: list[str | None],
@@ -824,7 +819,7 @@ def _is_absolute_path_under_system_temporary_directory(file_path: str) -> bool:
824
819
 
825
820
  ::
826
821
 
827
- ok: "C:/Users/.../Temp/pytest-of-x/test_foo0/a.py" -> True
822
+ ok: "<windows-temp-root>/pytest-of-x/test_foo0/a.py" -> True
828
823
  ok: "/home/runner/work/_temp/pytest-basetemp/.../a.py" when RUNNER_TEMP
829
824
  flag: "packages/demo/test_helpers/worker.py" -> False (relative)
830
825
  flag: "C:/repo/pkg/test_helpers/worker.py" -> False (project)
@@ -0,0 +1,21 @@
1
+ {
2
+ "directories": [
3
+ "bin",
4
+ "rules",
5
+ "docs",
6
+ "commands",
7
+ "agents",
8
+ "output-styles",
9
+ "skills",
10
+ "hooks",
11
+ "system-prompts",
12
+ "scripts",
13
+ "_shared",
14
+ "audit-rubrics"
15
+ ],
16
+ "root_files": [
17
+ "CLAUDE.md",
18
+ "codex-capability-map.json",
19
+ "installable-surfaces.manifest.json"
20
+ ]
21
+ }
@@ -4,9 +4,7 @@ Output-style instruction files installed into `~/.claude/output-styles/` by `bin
4
4
 
5
5
  ## Files
6
6
 
7
- | File | Style | Effect |
8
- |---|---|---|
9
- | `caveman-agent.md` | Caveman Agent | Terse fragments, lead with answer, smallest artifact that solves the stated problem, question premise before building |
7
+ No output-style instruction files ship in this directory. The active caveman behavior lives in `agents/caveman.md`.
10
8
 
11
9
  ## Format
12
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-dev-env",
3
- "version": "2.9.0",
3
+ "version": "2.10.0",
4
4
  "description": "Claude Code development standards — rules, hooks, agents, commands, and skills",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,7 +8,7 @@
8
8
  "codex-compat": "bin/codex-compat.mjs"
9
9
  },
10
10
  "scripts": {
11
- "test": "node --test \"bin/*.test.mjs\" \"skills/**/*.test.mjs\""
11
+ "test": "node --test \"bin/*.test.mjs\" \"skills/**/*.test.mjs\" \"scripts/profile-isolation-launchers/**/*.test.mjs\" \"tests/fresh-session/**/*.test.mjs\""
12
12
  },
13
13
  "files": [
14
14
  "bin/",
@@ -22,6 +22,8 @@
22
22
  "system-prompts/",
23
23
  "scripts/",
24
24
  "codex-capability-map.json",
25
+ "settings.json",
26
+ "installable-surfaces.manifest.json",
25
27
  "_shared/",
26
28
  "audit-rubrics/",
27
29
  "CLAUDE.md",
package/rules/CLAUDE.md CHANGED
@@ -39,6 +39,7 @@ Rule files installed into `~/.claude/rules/` by `bin/install.mjs`. A rule withou
39
39
  | `nas-ssh-invocation.md` | Reach the NAS through the paramiko-backed `nas_ssh_key.py` runner, which signs in-process; every ssh-family client reads the key through file permissions, refuses it, and stalls an unattended run on a password prompt |
40
40
  | `no-cross-skill-duplicate-helpers.md` | Within one skill a duplicated helper is blocked; across two skill folders a small self-contained copy is a sanctioned isolation tradeoff that draws a non-blocking advisory naming the source skill |
41
41
  | `orphan-css-class.md` | Every `class="..."` attribute in Python-generated markup has a matching selector in the `<style>` block |
42
+ | `opus5-communication-contract.md` | Visible concision, one-sentence first progress, important-change-only updates, outcome-first finals, thinking-disabled tool narration (`opus5-communication-contract-v1`) |
42
43
  | `paired-test-coverage.md` | A public function omitted by a module's established paired test suite must get a behavioral test |
43
44
  | `parallel-tools.md` | Make all independent tool calls in a single response |
44
45
  | `plain-illustrative-docstrings.md` | Public docstring narrative reads plainly and shows behavior with a diagram block (a `::` example or a doctest), painting a concrete scene a general developer follows on first read; a run-on backstop hook, a prose-wall backstop hook, and Category O9 audit enforce it |
@@ -19,7 +19,7 @@ Handle the two kinds of content differently:
19
19
 
20
20
  ## Volatile paths that must not appear in a post body
21
21
 
22
- - A job scratch directory (`.claude-editor/jobs/`)
22
+ - A job scratch directory (`.claude-profile-a/jobs/`)
23
23
  - A worktree (`.claude/worktrees/`)
24
24
  - A system temp location (`AppData\Local\Temp`, `%TEMP%`, `$env:TEMP`, `/tmp/`)
25
25
  - The job scratch environment variable (`$CLAUDE_JOB_DIR`)
@@ -28,7 +28,7 @@ Both slash directions count.
28
28
 
29
29
  The worktree and job-scratch entries count as a path when either:
30
30
 
31
- - A `/` or `\` sits right before them — a drive-letter path (`C:\Users\me\.claude\worktrees\wt\f.py`), a home path (`~/.claude/worktrees/wt`), or a POSIX absolute path (`/home/me/.claude-editor/jobs/j/log.txt`).
31
+ - A `/` or `\` sits right before them — a drive-letter path (`C:\Users\me\.claude\worktrees\wt\f.py`), a home path (`~/.claude/worktrees/wt`), or a POSIX absolute path (`/home/me/.claude-profile-a/jobs/j/log.txt`).
32
32
  - A path segment follows them — a relative path that names a child under that directory (`see .claude/worktrees/wt-1/notes.md`, a markdown link target, or `cd .claude/worktrees/wt-199`).
33
33
 
34
34
  With neither anchor the text names the directory rather than something inside it, and it posts — a quoted config constant, a backticked directory name, or a placeholder form such as `.claude/worktrees/<name>`. Un-backticked prose that puts a word immediately after the marker reads the same as a relative path and is blocked; the placeholder form is the documented escape.
@@ -7,7 +7,7 @@
7
7
  1. **Bold keyword first** — open every line and bullet with its load-bearing words in bold. Eyes land on the first words of a line, then fall down the left edge — the [F-pattern](https://www.nngroup.com/articles/f-shaped-pattern-reading-web-content-discovered/).
8
8
  2. **One idea per line** — a second idea in the same line is invisible to a skimmer.
9
9
  3. **Conclusion first** — the outcome goes in sentence one. Detail sits below it, or gets cut.
10
- 4. **Half the words** — write it, halve it, halve it again. Cap a reply at about 120 words.
10
+ 4. **Half the words** — write it, halve it, halve it again. Stay brief by default; a requested full report may run long.
11
11
 
12
12
  **Measured gains** ([NN/g eyetracking](https://www.nngroup.com/articles/how-users-read-on-the-web/)): concise +58%, scannable layout +47%, both together +124%.
13
13
 
@@ -25,7 +25,12 @@
25
25
  - **Cut findings first** — when a reply runs long, drop findings and keep the action steps.
26
26
  - **Skim test** — reading only the bold words tells the whole story.
27
27
 
28
+ ## Progress and finals
29
+
30
+ Mid-run and closing narration follow [`opus5-communication-contract.md`](opus5-communication-contract.md) (`opus5-communication-contract-v1`): first progress update is one sentence; later updates only for important discoveries or direction changes; the final starts with the outcome.
31
+
28
32
  ## Relationship to other rules
29
33
 
30
34
  - **[`plain-language`](plain-language.md)** owns word choice; this rule owns reply length and shape.
35
+ - **[`opus5-communication-contract`](opus5-communication-contract.md)** owns progress updates, outcome-first finals, and thinking-disabled tool narration.
31
36
  - **`AskUserQuestion`** carries every question to the user, in the same short style.
@@ -2,8 +2,10 @@
2
2
 
3
3
  State a claim with the evidence that backs it, or name the claim unverified. A hedge word stands in for evidence you did not gather.
4
4
 
5
- `hedging_language_blocker` (Stop hook, hosted by `stop_dispatcher`) blocks a response carrying one — `likely`, `probably`, `presumably`, `perhaps`, `possibly`, `seemingly`, `apparently`, `arguably`, `supposedly`, `ostensibly`, `conceivably`, `plausibly`, `unlikely`, `probable`, and the phrases `might be`, `could be`, `seems to be`, `appears to be`, `in all likelihood`, `more likely than not`, `it's possible that`.
5
+ `hedging_language_blocker` (Stop hook, hosted by `stop_dispatcher`) runs when `CLAUDE_PROSE_STYLE_ENFORCEMENT` is on (see [`docs/references/prose-style-enforcement.md`](../docs/references/prose-style-enforcement.md)). It blocks a response that carries a bare hedge word — `likely`, `probably`, `presumably`, `perhaps`, `possibly`, `seemingly`, `apparently`, `arguably`, `supposedly`, `ostensibly`, `conceivably`, `plausibly`, `unlikely`, `probable`, and the phrases `might be`, `could be`, `seems to be`, `appears to be`, `in all likelihood`, `more likely than not`, `it's possible that` — **unless the same sentence** labels the claim with explicit uncertainty (`unverified`, `I don't know`, `I do not know`, `no source for this claim`, `without a source`).
6
6
 
7
- Deleting the hedge word and keeping the claim does not clear the block. Gather the source, run the probe, or ask the user through `AskUserQuestion` — then re-output the whole revised response.
7
+ A label in another sentence does not clear a bare hedge. Deleting the hedge word and keeping the claim does not clear the block. Gather the source, run the probe, label the claim unverified in that sentence, or ask the user through `AskUserQuestion` — then re-output the whole revised response.
8
+
9
+ When enforcement is off, bare hedges still emit privacy-safe advisory candidates for precision measurement (OP-07B).
8
10
 
9
11
  Sibling rules: [`research-mode.md`](research-mode.md) names what counts as a citation; [`verify-runtime-state.md`](verify-runtime-state.md) names the live probe a runtime verdict rests on.
@@ -30,9 +30,11 @@ On a long build, set a checkpoint cadence and hold to it. At each interval, chec
30
30
 
31
31
  Before you report progress, check each claim against a tool result from this session. State only what the evidence backs; name anything unverified as unverified. If tests fail, say so with the output; if a step was skipped, say that.
32
32
 
33
+ Visible progress follows [`opus5-communication-contract.md`](opus5-communication-contract.md) (`opus5-communication-contract-v1`): the **first progress update is one sentence**; later updates mark only **important discoveries or a direction change**.
34
+
33
35
  ## Re-ground the final message
34
36
 
35
- Terse shorthand between tool calls is fine — that is you thinking. The final message is for a reader who saw none of it. After a long or unwatched run, write it as a fresh briefing: the outcome in one sentence, then the one or two things you need from the reader, each explained as if new. Drop the working vocabulary, arrow chains, and stacked-hyphen compounds; give each file, commit, or flag its own plain clause. When short and clear pull apart, choose clear.
37
+ Terse shorthand between tool calls is fine — that is you thinking. The final message is for a reader who saw none of it. After a long or unwatched run, write it as a fresh briefing: the **outcome in one sentence first**, then the one or two things you need from the reader, each explained as if new. Drop the working vocabulary, arrow chains, and stacked-hyphen compounds; give each file, commit, or flag its own plain clause. When short and clear pull apart, choose clear.
36
38
 
37
39
  ## Keep going on context
38
40
 
@@ -0,0 +1,45 @@
1
+ # Opus 5 communication contract
2
+
3
+ **Marker:** `opus5-communication-contract-v1`
4
+
5
+ Positive contract for visible replies. Complements [`eli11-replies.md`](eli11-replies.md) (shape) and [`plain-language.md`](plain-language.md) (word choice).
6
+
7
+ ## Visible output
8
+
9
+ 1. **Concise by default.** A simple answer stays brief. When the user asks for a full audit or deep dump, the reply may run long enough to cover the substance.
10
+ 2. **First progress update is one sentence.** After the first tool batch on a multi-step task, one short status line is enough.
11
+ 3. **Later updates only for important change.** Further mid-run narration marks only important discoveries or a direction change — not every tool call.
12
+ 4. **Final starts with the outcome.** The closing message leads with what happened; artifacts scale to their substance.
13
+ 5. **Material corrections only.** Narrate a correction only when it changes code, a conclusion, or a decision.
14
+
15
+ ## Thinking-disabled tool narration
16
+
17
+ When thinking is disabled in the harness:
18
+
19
+ 1. Speak one brief natural-language sentence before a tool call that needs user-visible context.
20
+ 2. When no fitting tool exists, say so in plain language.
21
+ 3. Never put internal-system XML in visible output.
22
+
23
+ ## Positive examples
24
+
25
+ ### Short answer
26
+
27
+ User: "What is the default branch?"
28
+ Reply: "**Default branch** is `main`."
29
+
30
+ ### Requested full audit
31
+
32
+ User: "Audit every hook registration and list gaps."
33
+ Reply: Outcome first, then a structured audit long enough to cover each gap — ELI11 caps still guide skim shape, not a hard character ceiling when depth was asked.
34
+
35
+ ### First progress update
36
+
37
+ After the first tool batch: "**Checked** `hooks.json` registration paths."
38
+
39
+ ### Important discovery mid-run
40
+
41
+ "**Blocker:** the package suite shadows `config` — running as two sessions."
42
+
43
+ ### Outcome-first final
44
+
45
+ "**Shipped** draft PR #123. CI green on head `abc1234`. Merge still needs your OK."
@@ -2,8 +2,8 @@
2
2
 
3
3
  All prose a person reads (chat, `AskUserQuestion`, docs, PR/issue bodies, commits): everyday words, short active sentences, lead with the answer, define jargon on first use, and give only the detail the reader needs to act (progressive disclosure). Aim for first-pass readability by a non-specialist. Exact identifiers, file paths, and API names stay exact; code is out of scope.
4
4
 
5
- The `plain_language_blocker` PreToolUse hook (AskUserQuestion + `.md` Write/Edit/MultiEdit) blocks a heavy word and names the everyday swap; code fences, inline code, blockquotes, URLs, and file paths are skipped.
5
+ The `plain_language_blocker` PreToolUse hook (AskUserQuestion + `.md` Write/Edit/MultiEdit) names an everyday swap for a heavy word when `CLAUDE_PROSE_STYLE_ENFORCEMENT` is on (default off). That path is **allow with a systemMessage advisory** — it does not deny the write. AskUserQuestion lean-block structure stays always on and still denies chat detail. When the flag is off, heavy-word hits only emit privacy-safe advisory candidates (see [`docs/references/prose-style-enforcement.md`](../docs/references/prose-style-enforcement.md)). Code fences, inline code, blockquotes, URLs, and file paths are skipped.
6
6
 
7
- [`eli11-replies`](eli11-replies.md) governs reply length and shape; this rule governs word choice.
7
+ [`eli11-replies`](eli11-replies.md) governs reply length and shape; [`opus5-communication-contract`](opus5-communication-contract.md) governs progress and finals; this rule governs word choice.
8
8
 
9
9
  A project can keep its own domain words out of the check with a `.claude/plain-language-allow.json` file: a JSON array of terms. An exact, case-insensitive, whole-word match on any term passes. The hook reads this file only from inside the project tree, up to the repository root, so each project's allowlist stays with its own code.
@@ -30,4 +30,4 @@ Creative thinking, brainstorming, and novel ideas don't require citation. You ca
30
30
 
31
31
  - [`explore-thoroughly.md`](explore-thoroughly.md) — grounds an implementation plan in the codebase, as this rule grounds a factual claim in a source.
32
32
  - [`verify-runtime-state.md`](verify-runtime-state.md) — a runtime verdict needs a live probe from this session, not a citation.
33
- - [`hedging-claims.md`](hedging-claims.md) — the Stop hook that catches an unverified claim wearing a hedge word.
33
+ - [`hedging-claims.md`](hedging-claims.md) — the Stop hook that catches a bare hedge word; an explicit uncertainty label in the same sentence is the allowed alternative to a citation.
package/scripts/CLAUDE.md CHANGED
@@ -16,6 +16,8 @@ Utility scripts installed into `~/.claude/scripts/` by `bin/install.mjs`. Each s
16
16
  | `sweep_empty_dirs.py` | Deletes empty directories older than a configurable age under a given root; runs once (`--once`) or in continuous-watch mode |
17
17
  | `sync_to_cursor.py` | Entry point for syncing Claude rules to Cursor `.mdc` files; delegates to the `sync_to_cursor/` package |
18
18
  | `resolve_worker_spawn.py` | Dispatches a worker role through grok then claude fallback tiers (preflight, headless grok, `claude_agent_required` handoff, optional claude headless); applies `require_timeout_within_bounds` before the preflight, so an out-of-bounds `--timeout-seconds` prints a `timeout_out_of_bounds` outcome and exits 3 on every tier; protocol: [`../_shared/pr-loop/worker-spawn.md`](../_shared/pr-loop/worker-spawn.md) |
19
+ | `active_capability_references.py` | Inventories committed skill, agent, and command names and scans package markdown for active slash or backticked capability references; fails ban-listed names outside inert historical/example fences |
20
+ | `verify_installable_package.py` | Verifies the published package: runs real `npm pack`, checks every surface in `installable-surfaces.manifest.json` appears in the tarball, requires each `hooks.json` `.py` command to be git-tracked, and smoke-compiles those scripts (`node --check` on `bin/install.mjs`) |
19
21
 
20
22
  ## PowerShell scripts
21
23
 
@@ -24,6 +26,7 @@ Utility scripts installed into `~/.claude/scripts/` by `bin/install.mjs`. Each s
24
26
  | `Audit-ShellPolicy.ps1` | Audits Bash tool calls in session transcripts against the `pwsh`-only shell policy |
25
27
  | `Migrate-ShellPolicy.ps1` | Applies automated fixes for common shell-policy violations found by the audit script |
26
28
  | `Install-SweepEmptyDirs.ps1` | Registers `sweep_empty_dirs.py` as a scheduled task on Windows |
29
+ | `Sync-RepoMain.ps1` | Fast-forwards a main-tracking mirror (`-RepoPath`, required) to its remote main using `merge --ff-only`; refuses a different checkout or a diverged branch, logs each run to `-LogPath`, and exits non-zero so a scheduled-task history row surfaces the problem. `-StashDirty` stashes tracked changes right before the fast-forward under a dated `pre-sync stash` message and leaves the stash in the list |
27
30
  | `check.ps1` | Runs the full code-quality check suite |
28
31
  | `Show-Asset.ps1` | Opens files on screen, sizing each image window to the image's pixel dimensions (scaled to fit the screen); non-image files open in their default application |
29
32
  | `Get-SessionAccount.ps1` | Reports which Claude account the current session is actually logged into by comparing `~/.claude.json`'s CLI login against a `CLAUDE_USER_DATA_DIR` desktop profile's `lastKnownAccountUuid`, recovering the desktop account's email from profile storage when the two accounts differ |
@@ -33,7 +36,9 @@ Utility scripts installed into `~/.claude/scripts/` by `bin/install.mjs`. Each s
33
36
 
34
37
  | Entry | Description |
35
38
  |---|---|
39
+ | `ci/` | CI-only adapters; `windows-installer-lifecycle.ps1` runs the Node installer 16-check driver under isolated HOME/USERPROFILE/GIT_CONFIG_GLOBAL and writes bounded evidence |
36
40
  | `dev_env_scripts_constants/` | Named constants (`timing.py`, `grok_worker_constants.py`, …) for scripts in this directory, including worker-advisor placeholder launcher/model/effort, four verdict signals, correction cap, and advisor timeout |
41
+ | `profile-isolation-launchers/` | Profile-launcher contract plus MCP activation, Windows shortcut source semantics, and version preflight: profiles manifest, shared-path allowlist, profile resolver, `CLAUDE_CONFIG_DIR` authority, lean/full MCP bundle materialization into profile `mcp.json`, pure CLI/Desktop version-compatibility classifier consumed by launcher preflight, read-only shortcut inventory and preview reconcile under `windows/`. Live launcher execution, provenance pin, live shortcut mutation, and L1 deploy stay residual |
37
42
  | `sync_to_cursor/` | Package that builds Cursor `.mdc` files from Claude rules and docs |
38
43
  | `tests/` | pytest suite for the Python scripts and Pester (`*.Tests.ps1`) suite for the PowerShell scripts in this directory |
39
44
 
@@ -45,6 +50,12 @@ Python scripts (pytest):
45
50
  python -m pytest packages/claude-dev-env/scripts/tests/
46
51
  ```
47
52
 
53
+ Profile-isolation launcher contract (node:test):
54
+
55
+ ```bash
56
+ node --test packages/claude-dev-env/scripts/profile-isolation-launchers/**/*.test.mjs
57
+ ```
58
+
48
59
  PowerShell scripts (Pester 5+, `*.Tests.ps1`):
49
60
 
50
61
  ```powershell