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,65 @@
1
+ """Paginated file intake flattens --slurp pages without losing paths."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ import pytest
10
+
11
+ _SCRIPTS_DIR = Path(__file__).resolve().parent
12
+ if str(_SCRIPTS_DIR) not in sys.path:
13
+ sys.path.insert(0, str(_SCRIPTS_DIR))
14
+
15
+ from split_pr_paginate import fetch_all_pr_changed_files, parse_paginated_files_payload
16
+
17
+
18
+ def test_fetch_all_pr_changed_files_builds_paginated_gh_api_command(
19
+ monkeypatch: pytest.MonkeyPatch,
20
+ ) -> None:
21
+ all_captured: list[list[str]] = []
22
+
23
+ class _Completed:
24
+ returncode = 0
25
+ stdout = (
26
+ '[[{"filename": "x.py", "additions": 1, "deletions": 0, "sha": "s"}]]'
27
+ )
28
+ stderr = ""
29
+
30
+ def _fake_run(all_command: list[str], **_kwargs: object) -> _Completed:
31
+ all_captured.append(list(all_command))
32
+ return _Completed()
33
+
34
+ monkeypatch.setattr("split_pr_paginate.subprocess.run", _fake_run)
35
+ all_files = fetch_all_pr_changed_files("jl-cmd", "claude-dev-env", 1)
36
+ assert all_files[0]["path"] == "x.py"
37
+ assert "--paginate" in all_captured[0]
38
+ assert "--slurp" in all_captured[0]
39
+
40
+
41
+ def test_slurp_pages_are_flattened_in_order() -> None:
42
+ raw = json.dumps(
43
+ [
44
+ [{"filename": "a.py", "additions": 1, "deletions": 0, "sha": "1"}],
45
+ [{"filename": "b.py", "additions": 2, "deletions": 1, "sha": "2"}],
46
+ ]
47
+ )
48
+ all_files = parse_paginated_files_payload(raw)
49
+ assert [each["path"] for each in all_files] == ["a.py", "b.py"]
50
+ assert all_files[1]["additions"] == 2
51
+
52
+
53
+ def test_single_page_array_still_parses() -> None:
54
+ raw = json.dumps([{"filename": "only.py", "additions": 3, "deletions": 0}])
55
+ all_files = parse_paginated_files_payload(raw)
56
+ assert len(all_files) == 1
57
+ assert all_files[0]["path"] == "only.py"
58
+
59
+
60
+ def test_rest_filename_preferred_over_internal_path_key() -> None:
61
+ raw = json.dumps(
62
+ [{"filename": "from-api.py", "path": "stale.py", "additions": 1, "deletions": 0}]
63
+ )
64
+ all_files = parse_paginated_files_payload(raw)
65
+ assert all_files[0]["path"] == "from-api.py"
@@ -0,0 +1,73 @@
1
+ """Split-plan schema validation rejects unassigned paths."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ import pytest
9
+
10
+ _SCRIPTS_DIR = Path(__file__).resolve().parent
11
+ if str(_SCRIPTS_DIR) not in sys.path:
12
+ sys.path.insert(0, str(_SCRIPTS_DIR))
13
+
14
+ from split_pr_script_types import build_split_plan, validate_split_plan
15
+
16
+
17
+ def test_build_and_validate_full_assignment() -> None:
18
+ plan = build_split_plan(
19
+ source_commit="abc123",
20
+ all_changed_paths=["a.py", "b.md"],
21
+ all_slices=[
22
+ {
23
+ "id": "01-config",
24
+ "title": "feat: feat: config",
25
+ "layer": "config",
26
+ "all_paths": ["a.py"],
27
+ },
28
+ {
29
+ "id": "02-docs",
30
+ "title": "docs only",
31
+ "layer": "docs",
32
+ "all_paths": ["b.md"],
33
+ },
34
+ ],
35
+ )
36
+ validate_split_plan(plan)
37
+ assert plan["schema_version"] == 1
38
+ assert plan["all_slices"][0]["title"] == "feat: config"
39
+ assert plan["all_slices"][1]["title"] == "chore: docs only"
40
+
41
+
42
+ def test_validate_rejects_unassigned_path() -> None:
43
+ plan = build_split_plan(
44
+ source_commit="abc123",
45
+ all_changed_paths=["a.py", "orphan.py"],
46
+ all_slices=[
47
+ {
48
+ "id": "01",
49
+ "title": "feat: only a",
50
+ "layer": "backend",
51
+ "all_paths": ["a.py"],
52
+ }
53
+ ],
54
+ )
55
+ with pytest.raises(ValueError, match="missing"):
56
+ validate_split_plan(plan)
57
+
58
+
59
+ def test_validate_rejects_empty_slice_id() -> None:
60
+ plan = build_split_plan(
61
+ source_commit="abc123",
62
+ all_changed_paths=["a.py"],
63
+ all_slices=[
64
+ {
65
+ "id": " ",
66
+ "title": "feat: bare",
67
+ "layer": "backend",
68
+ "all_paths": ["a.py"],
69
+ }
70
+ ],
71
+ )
72
+ with pytest.raises(ValueError, match="slice id"):
73
+ validate_split_plan(plan)
@@ -0,0 +1,28 @@
1
+ """Title normalization carries exactly one conventional prefix."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ _SCRIPTS_DIR = Path(__file__).resolve().parent
9
+ if str(_SCRIPTS_DIR) not in sys.path:
10
+ sys.path.insert(0, str(_SCRIPTS_DIR))
11
+
12
+ from split_pr_title import normalize_split_title
13
+
14
+
15
+ def test_single_prefix_unchanged() -> None:
16
+ assert normalize_split_title("feat: add gate") == "feat: add gate"
17
+
18
+
19
+ def test_stacked_prefixes_collapse_to_one() -> None:
20
+ assert normalize_split_title("feat: feat: add gate") == "feat: add gate"
21
+
22
+
23
+ def test_bare_title_gets_default_chore_prefix() -> None:
24
+ assert normalize_split_title("add gate") == "chore: add gate"
25
+
26
+
27
+ def test_scoped_prefix_normalized() -> None:
28
+ assert normalize_split_title("fix(hooks): repair gate") == "fix: repair gate"
@@ -0,0 +1,46 @@
1
+ """Dependency graph verifier CLI and rebuild checks."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ import pytest
10
+
11
+ _SCRIPTS_DIR = Path(__file__).resolve().parent
12
+ if str(_SCRIPTS_DIR) not in sys.path:
13
+ sys.path.insert(0, str(_SCRIPTS_DIR))
14
+
15
+ from split_pr_dependency_graph import build_dependency_graph
16
+ from verify_dependency_graph import main, verify_dependency_graph_document
17
+
18
+
19
+ def test_verify_accepts_rebuilt_graph() -> None:
20
+ all_slices = [
21
+ {"id": "01-config", "layer": "config"},
22
+ {"id": "02-docs", "layer": "docs"},
23
+ ]
24
+ graph = build_dependency_graph(all_slices)
25
+ graph["all_slices"] = all_slices
26
+ verify_dependency_graph_document(graph)
27
+
28
+
29
+ def test_verify_rejects_order_mismatch() -> None:
30
+ all_slices = [
31
+ {"id": "01-config", "layer": "config"},
32
+ {"id": "02-docs", "layer": "docs"},
33
+ ]
34
+ graph = build_dependency_graph(all_slices)
35
+ graph["all_slices"] = all_slices
36
+ graph["topological_order"] = ["02-docs", "01-config"]
37
+ with pytest.raises(ValueError, match="mismatch"):
38
+ verify_dependency_graph_document(graph)
39
+
40
+
41
+ def test_cli_ok(tmp_path: Path) -> None:
42
+ all_slices = [{"id": "a", "layer": "backend"}]
43
+ graph = build_dependency_graph(all_slices)
44
+ path = tmp_path / "g.json"
45
+ path.write_text(json.dumps(graph), encoding="utf-8")
46
+ assert main(["--graph-json", str(path)]) == 0
@@ -0,0 +1,56 @@
1
+ """verify_plan accepts full coverage and rejects gaps."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ import pytest
10
+
11
+ _SCRIPTS_DIR = Path(__file__).resolve().parent
12
+ if str(_SCRIPTS_DIR) not in sys.path:
13
+ sys.path.insert(0, str(_SCRIPTS_DIR))
14
+
15
+ from verify_plan import main, verify_split_plan_coverage
16
+
17
+
18
+ def _full_plan() -> dict[str, object]:
19
+ return {
20
+ "schema_version": 1,
21
+ "source_commit": "abc",
22
+ "all_changed_paths": ["src/a.py", "docs/b.md"],
23
+ "all_slices": [
24
+ {
25
+ "id": "01-backend",
26
+ "title": "feat: a",
27
+ "layer": "backend",
28
+ "all_paths": ["src/a.py"],
29
+ },
30
+ {
31
+ "id": "02-docs",
32
+ "title": "docs: b",
33
+ "layer": "docs",
34
+ "all_paths": ["docs/b.md"],
35
+ },
36
+ ],
37
+ }
38
+
39
+
40
+ def test_verify_accepts_full_unique_coverage() -> None:
41
+ verify_split_plan_coverage(_full_plan())
42
+
43
+
44
+ def test_verify_rejects_missing_path() -> None:
45
+ plan = _full_plan()
46
+ all_slices = plan["all_slices"]
47
+ assert isinstance(all_slices, list)
48
+ plan["all_slices"] = [all_slices[0]]
49
+ with pytest.raises(ValueError, match="missing"):
50
+ verify_split_plan_coverage(plan)
51
+
52
+
53
+ def test_cli_ok_on_temp_plan(tmp_path: Path) -> None:
54
+ plan_path = tmp_path / "plan.json"
55
+ plan_path.write_text(json.dumps(_full_plan()), encoding="utf-8")
56
+ assert main(["--plan-json", str(plan_path)]) == 0
@@ -0,0 +1,50 @@
1
+ """Contract: source_commit and title normalization are required."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ import pytest
9
+
10
+ _SCRIPTS_DIR = Path(__file__).resolve().parent
11
+ if str(_SCRIPTS_DIR) not in sys.path:
12
+ sys.path.insert(0, str(_SCRIPTS_DIR))
13
+
14
+ from verify_plan import verify_split_plan_coverage
15
+
16
+
17
+ def test_missing_source_commit_fails() -> None:
18
+ plan = {
19
+ "schema_version": 1,
20
+ "source_commit": "",
21
+ "all_changed_paths": ["a.py"],
22
+ "all_slices": [
23
+ {
24
+ "id": "01",
25
+ "title": "feat: a",
26
+ "layer": "backend",
27
+ "all_paths": ["a.py"],
28
+ }
29
+ ],
30
+ }
31
+ with pytest.raises(ValueError, match="source_commit"):
32
+ verify_split_plan_coverage(plan)
33
+
34
+
35
+ def test_stacked_title_fails_after_build_would_have_normalized() -> None:
36
+ plan = {
37
+ "schema_version": 1,
38
+ "source_commit": "abc",
39
+ "all_changed_paths": ["a.py"],
40
+ "all_slices": [
41
+ {
42
+ "id": "01",
43
+ "title": "feat: feat: a",
44
+ "layer": "backend",
45
+ "all_paths": ["a.py"],
46
+ }
47
+ ],
48
+ }
49
+ with pytest.raises(ValueError, match="title"):
50
+ verify_split_plan_coverage(plan)
@@ -0,0 +1,45 @@
1
+ """Path normalization fails closed on unsafe paths."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import sys
6
+ from pathlib import Path
7
+
8
+ import pytest
9
+
10
+ _SCRIPTS_DIR = Path(__file__).resolve().parent
11
+ if str(_SCRIPTS_DIR) not in sys.path:
12
+ sys.path.insert(0, str(_SCRIPTS_DIR))
13
+
14
+ from verify_plan import normalize_repo_path, verify_split_plan_coverage
15
+
16
+
17
+ def test_normalize_strips_dot_slash() -> None:
18
+ assert normalize_repo_path("./src/a.py") == "src/a.py"
19
+ assert normalize_repo_path("src\\a.py") == "src/a.py"
20
+
21
+
22
+ def test_normalize_rejects_parent_and_absolute() -> None:
23
+ with pytest.raises(ValueError):
24
+ normalize_repo_path("../secret")
25
+ with pytest.raises(ValueError):
26
+ normalize_repo_path("/etc/passwd")
27
+ with pytest.raises(ValueError):
28
+ normalize_repo_path("C:/Windows/system32")
29
+
30
+
31
+ def test_verify_normalizes_mixed_separators() -> None:
32
+ plan = {
33
+ "schema_version": 1,
34
+ "source_commit": "abc",
35
+ "all_changed_paths": ["src\\a.py"],
36
+ "all_slices": [
37
+ {
38
+ "id": "01",
39
+ "title": "feat: a",
40
+ "layer": "backend",
41
+ "all_paths": ["./src/a.py"],
42
+ }
43
+ ],
44
+ }
45
+ verify_split_plan_coverage(plan)
@@ -0,0 +1,111 @@
1
+ """CLI and checks for a split-plan dependency graph document.
2
+
3
+ ::
4
+
5
+ python verify_dependency_graph.py --graph-json graph.json
6
+ # exit 0 when shape is valid and order matches a rebuild when all_slices set
7
+ """
8
+
9
+ from __future__ import annotations
10
+
11
+ import argparse
12
+ import json
13
+ import sys
14
+ from pathlib import Path
15
+
16
+ if str(Path(__file__).resolve().parent) not in sys.path:
17
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
18
+
19
+ from config.dependency_constants import (
20
+ EDGE_KEY_FROM,
21
+ EDGE_KEY_TO,
22
+ GRAPH_KEY_EDGES,
23
+ GRAPH_KEY_NODES,
24
+ GRAPH_KEY_ORDER,
25
+ )
26
+ from config.plan_constants import (
27
+ EXIT_CODE_SUCCESS,
28
+ PLAN_KEY_ALL_SLICES,
29
+ UTF8_ENCODING,
30
+ )
31
+ from config.split_pr_constants import EXIT_CODE_FAILURE
32
+ from split_pr_dependency_graph import build_dependency_graph
33
+ from split_pr_script_types import JsonObject
34
+
35
+
36
+ def verify_dependency_graph_document(all_graph: JsonObject) -> None:
37
+ """Validate graph shape; when ``all_slices`` is set, match rebuild order.
38
+
39
+ Args:
40
+ all_graph: Graph document with nodes, edges, topological_order, and
41
+ optional ``all_slices``. Always checks: non-empty unique nodes,
42
+ order lists each node once, edges reference known nodes. When
43
+ ``all_slices`` is a non-empty list, rebuilds and requires
44
+ topological_order to match.
45
+
46
+ Raises:
47
+ ValueError: When shape checks fail or topological_order mismatches
48
+ a rebuild from ``all_slices``.
49
+ """
50
+ all_nodes = all_graph.get(GRAPH_KEY_NODES)
51
+ all_edges = all_graph.get(GRAPH_KEY_EDGES)
52
+ all_order = all_graph.get(GRAPH_KEY_ORDER)
53
+ if not isinstance(all_nodes, list) or not all_nodes:
54
+ raise ValueError("nodes must be a non-empty list")
55
+ if not isinstance(all_edges, list):
56
+ raise ValueError("edges must be a list")
57
+ if not isinstance(all_order, list):
58
+ raise ValueError("topological_order must be a list")
59
+ all_node_ids = [str(each) for each in all_nodes]
60
+ if len(all_node_ids) != len(set(all_node_ids)):
61
+ raise ValueError("nodes contains duplicates")
62
+ all_order_ids = [str(each) for each in all_order]
63
+ if sorted(all_order_ids) != sorted(all_node_ids):
64
+ raise ValueError("topological_order must list each node exactly once")
65
+ all_node_set = set(all_node_ids)
66
+ for each_edge in all_edges:
67
+ if not isinstance(each_edge, dict):
68
+ raise ValueError("each edge must be an object")
69
+ edge_from = str(each_edge.get(EDGE_KEY_FROM, ""))
70
+ edge_to = str(each_edge.get(EDGE_KEY_TO, ""))
71
+ if edge_from not in all_node_set or edge_to not in all_node_set:
72
+ raise ValueError(f"edge uses unknown node: {each_edge!r}")
73
+ all_slices = all_graph.get(PLAN_KEY_ALL_SLICES)
74
+ if isinstance(all_slices, list) and all_slices:
75
+ rebuilt = build_dependency_graph(all_slices)
76
+ if rebuilt[GRAPH_KEY_ORDER] != all_order_ids:
77
+ raise ValueError(
78
+ f"topological_order mismatch expected {rebuilt[GRAPH_KEY_ORDER]!r} "
79
+ f"got {all_order_ids!r}"
80
+ )
81
+
82
+
83
+ def main(all_argv: list[str]) -> int:
84
+ """CLI entry for graph verification.
85
+
86
+ Args:
87
+ all_argv: Args without program name.
88
+
89
+ Returns:
90
+ 0 on success, 1 on failure.
91
+
92
+ Raises:
93
+ Does not raise; writes a JSON error payload on failure.
94
+ """
95
+ parser = argparse.ArgumentParser(description=__doc__)
96
+ parser.add_argument("--graph-json", type=Path, required=True)
97
+ arguments = parser.parse_args(all_argv)
98
+ try:
99
+ payload = json.loads(arguments.graph_json.read_text(encoding=UTF8_ENCODING))
100
+ if not isinstance(payload, dict):
101
+ raise ValueError("graph root must be an object")
102
+ verify_dependency_graph_document(payload)
103
+ except (OSError, ValueError, json.JSONDecodeError) as error:
104
+ print(json.dumps({"ok": False, "error": str(error)}))
105
+ return EXIT_CODE_FAILURE
106
+ print(json.dumps({"ok": True}))
107
+ return EXIT_CODE_SUCCESS
108
+
109
+
110
+ if __name__ == "__main__":
111
+ raise SystemExit(main(sys.argv[1:]))
@@ -0,0 +1,139 @@
1
+ """Verify complete unique path coverage for a split-plan document.
2
+
3
+ ::
4
+
5
+ python verify_plan.py --plan-json plan.json
6
+ # exit 0 when every changed path is assigned once
7
+
8
+ Fails closed on unassigned paths, duplicate assignments, empty source
9
+ commit, and non-normalized titles.
10
+ """
11
+
12
+ from __future__ import annotations
13
+
14
+ import argparse
15
+ import json
16
+ import sys
17
+ from pathlib import Path
18
+
19
+ if str(Path(__file__).resolve().parent) not in sys.path:
20
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
21
+
22
+ from config.plan_constants import (
23
+ EXIT_CODE_SUCCESS,
24
+ PATH_SEPARATOR,
25
+ PLAN_KEY_ALL_CHANGED_PATHS,
26
+ PLAN_KEY_ALL_SLICES,
27
+ PLAN_KEY_SOURCE_COMMIT,
28
+ SLICE_KEY_ALL_PATHS,
29
+ UTF8_ENCODING,
30
+ VERIFY_PAYLOAD_KEY_OK,
31
+ )
32
+ from config.split_pr_constants import EXIT_CODE_FAILURE, PAYLOAD_KEY_ERROR
33
+ from split_pr_script_types import JsonObject, validate_split_plan
34
+
35
+
36
+ def _is_drive_path(path_text: str) -> bool:
37
+ return len(path_text) >= 2 and path_text[1] == ":"
38
+
39
+
40
+ def normalize_repo_path(raw_path: str) -> str:
41
+ """Normalize a changed-path string for coverage comparison.
42
+
43
+ Args:
44
+ raw_path: Path as stored in the plan or intake list.
45
+
46
+ Returns:
47
+ Forward-slash path without a leading ``./``.
48
+
49
+ Raises:
50
+ ValueError: When the path is empty, absolute, or has ``..`` segments.
51
+ """
52
+ text = raw_path.strip().replace("\\", PATH_SEPARATOR)
53
+ if not text or text.startswith(PATH_SEPARATOR) or _is_drive_path(text):
54
+ raise ValueError(f"unsafe or empty path: {raw_path!r}")
55
+ all_parts = [
56
+ each for each in text.split(PATH_SEPARATOR) if each not in ("", ".")
57
+ ]
58
+ if any(each == ".." for each in all_parts):
59
+ raise ValueError(f"ambiguous parent path segment: {raw_path!r}")
60
+ if not all_parts:
61
+ raise ValueError(f"empty path after normalization: {raw_path!r}")
62
+ return PATH_SEPARATOR.join(all_parts)
63
+
64
+
65
+ def _normalize_plan_paths(all_plan: JsonObject) -> JsonObject:
66
+ all_changed = all_plan.get(PLAN_KEY_ALL_CHANGED_PATHS, [])
67
+ if not isinstance(all_changed, list):
68
+ raise ValueError("all_changed_paths must be a list")
69
+ all_slices = all_plan.get(PLAN_KEY_ALL_SLICES, [])
70
+ if not isinstance(all_slices, list):
71
+ raise ValueError("all_slices must be a list")
72
+ all_normalized_changed = [normalize_repo_path(str(each)) for each in all_changed]
73
+ all_normalized_slices: list[JsonObject] = []
74
+ for each_slice in all_slices:
75
+ if not isinstance(each_slice, dict):
76
+ raise ValueError("slice must be an object")
77
+ all_paths = each_slice.get(SLICE_KEY_ALL_PATHS, [])
78
+ if not isinstance(all_paths, list):
79
+ raise ValueError("slice all_paths must be a list")
80
+ all_normalized_slices.append(
81
+ {
82
+ **each_slice,
83
+ SLICE_KEY_ALL_PATHS: [
84
+ normalize_repo_path(str(each_path)) for each_path in all_paths
85
+ ],
86
+ }
87
+ )
88
+ return {
89
+ **all_plan,
90
+ PLAN_KEY_ALL_CHANGED_PATHS: all_normalized_changed,
91
+ PLAN_KEY_ALL_SLICES: all_normalized_slices,
92
+ }
93
+
94
+
95
+ def verify_split_plan_coverage(all_plan: JsonObject) -> None:
96
+ """Normalize paths then run the schema assignment validator.
97
+
98
+ Args:
99
+ all_plan: Split-plan document.
100
+
101
+ Raises:
102
+ ValueError: When coverage or safety checks fail.
103
+ """
104
+ source_commit = all_plan.get(PLAN_KEY_SOURCE_COMMIT)
105
+ if not isinstance(source_commit, str) or not source_commit.strip():
106
+ raise ValueError("source_commit is required")
107
+ normalized = _normalize_plan_paths(all_plan)
108
+ validate_split_plan(normalized)
109
+
110
+
111
+ def main(all_argv: list[str]) -> int:
112
+ """CLI: verify a plan JSON file.
113
+
114
+ Args:
115
+ all_argv: Args without program name.
116
+
117
+ Returns:
118
+ Exit code 0 on success, 1 on failure.
119
+
120
+ Raises:
121
+ Does not raise; converts failures into a JSON error payload.
122
+ """
123
+ parser = argparse.ArgumentParser(description=__doc__)
124
+ parser.add_argument("--plan-json", type=Path, required=True)
125
+ arguments = parser.parse_args(all_argv)
126
+ try:
127
+ payload = json.loads(arguments.plan_json.read_text(encoding=UTF8_ENCODING))
128
+ if not isinstance(payload, dict):
129
+ raise ValueError("plan root must be an object")
130
+ verify_split_plan_coverage(payload)
131
+ except (OSError, ValueError, json.JSONDecodeError) as error:
132
+ print(json.dumps({VERIFY_PAYLOAD_KEY_OK: False, PAYLOAD_KEY_ERROR: str(error)}))
133
+ return EXIT_CODE_FAILURE
134
+ print(json.dumps({VERIFY_PAYLOAD_KEY_OK: True}))
135
+ return EXIT_CODE_SUCCESS
136
+
137
+
138
+ if __name__ == "__main__":
139
+ raise SystemExit(main(sys.argv[1:]))
@@ -16,25 +16,28 @@ One warm advisor at the strongest tier this session can reach. This session is t
16
16
  | Doc | Holds |
17
17
  |---|---|
18
18
  | [`docs/references/advisor-tool.md`](../../docs/references/advisor-tool.md) | **Consult cadence and weight** — when to call, hard rule before first write, how to treat advice. Read this for every consult. |
19
- | [`~/.claude/_shared/advisor/advisor-protocol.md`](../../_shared/advisor/advisor-protocol.md) | **Bind and lifecycle** — host detect, model floor, warm-up or CLI bind, charter, drift re-bind, CLI fallback, executor paste blocks. |
19
+ | [`~/.claude/_shared/advisor/advisor-protocol.md`](../../_shared/advisor/advisor-protocol.md) | **Bind and lifecycle** — host detect, model floor, warm-up, CLI fallback; its read map routes each moment to a `reference/` detail file. |
20
20
  | [`agents/session-advisor.md`](../../agents/session-advisor.md) | **Reply contract** — ENDORSE / CORRECTION / PLAN / STOP; SendMessage only. |
21
+ | [`reference/advisor-docs-review.md`](reference/advisor-docs-review.md) | Anthropic advisor-tool source facts: measured effects, Sonnet steering, cost levers, failure modes. Background — read it when tuning the bind, not on every consult. |
21
22
 
22
23
  ## Bind
23
24
 
24
25
  1. Detect the host profile first (protocol **Host profiles**), then walk the model floor.
25
- 2. Floor: this session's own tier on Claude; Opus floor with Fable first on a third-party host.
26
+ 2. Floor: the stronger of Opus and this session's own tier on Claude; Opus floor with Fable first on a third-party host.
26
27
  3. Name: `team-advisor-agent` on Claude (Agent spawn of `session-advisor`); one CLI `session_id` on a third-party host via the protocol Claude-chain.
27
28
  4. A Fable-tier spawn or re-spawn carries the exact token `FABLE-SPAWN-AUTHORIZED` in its prompt (protocol warm-up; `fable_spawn_gate` requires it).
28
29
  5. Skip the multi-consumer "who you are" opener — sole consumer.
29
30
  6. When the bind or reply path fails, fail closed and report to the user. On a third-party host, only the bound Claude advisor issues ENDORSE / CORRECTION / PLAN / STOP.
30
31
 
31
- Full walk, charter, consult message shape, and drift re-bind live in the protocol.
32
+ Full walk, charter, consult packet, Sol routing, and drift re-bind live in the protocol read map and its authoritative `reference/` leaves.
32
33
 
33
34
  ## Consult
34
35
 
35
36
  Follow **When to call**, **Hard rule**, and **How to treat advice** in `advisor-tool.md`.
36
37
 
37
- Each brief: delta since last consult, live decision or blocker, paths or excerpts needed. Protocol owns the full consult format.
38
+ Build every first brief with [`_shared/advisor/reference/consult-format.md`](../../_shared/advisor/reference/consult-format.md). Later briefs carry only the delta and changed evidence.
39
+
40
+ Aim for two consults on a normal task: one after orientation and one after writes and validation. Reserve a third for advisory recovery or reconciliation guidance, and add a consult when a material fork produces new evidence. This is an advisory target owned by the task, not a cap or gate.
38
41
 
39
42
  ## Constraints
40
43