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,181 @@
1
+ /**
2
+ * Pure install-root resolver for the claude-dev-env installer.
3
+ *
4
+ * Precedence (highest first):
5
+ * 1. explicitTarget — CLI --target or a caller-supplied absolute path
6
+ * 2. CLAUDE_CONFIG_DIR from the environment (profile isolation root)
7
+ * 3. join(homeDirectory, '.claude') — main-profile default
8
+ *
9
+ * CLAUDE_HOME is never honored as a profile root.
10
+ */
11
+
12
+ import { homedir } from 'node:os';
13
+ import { join, normalize, resolve, sep } from 'node:path';
14
+ import { MYPY_INI_FILE_NAME } from './install-constants.mjs';
15
+
16
+ export const CLAUDE_CONFIG_DIR_ENVIRONMENT_VARIABLE = 'CLAUDE_CONFIG_DIR';
17
+ export const DEFAULT_CLAUDE_DIRECTORY_NAME = '.claude';
18
+ export const MANIFEST_FILE_NAME = '.claude-dev-env-manifest.json';
19
+
20
+ /**
21
+ * @typedef {{
22
+ * explicitTarget?: string | null,
23
+ * environment?: NodeJS.ProcessEnv | Record<string, string | undefined>,
24
+ * homeDirectory?: string,
25
+ * }} ResolveInstallRootOptions
26
+ */
27
+
28
+ /**
29
+ * @typedef {{
30
+ * managedRoot: string,
31
+ * source: 'explicit-target' | 'claude-config-dir' | 'default-home',
32
+ * homeDirectory: string,
33
+ * manifestFilePath: string,
34
+ * mypyIniInstallPath: string,
35
+ * allDeclaredExternalPaths: string[],
36
+ * }} InstallRootResolution
37
+ */
38
+
39
+ /**
40
+ * Resolve the managed install root and declared external destinations.
41
+ *
42
+ * @param {ResolveInstallRootOptions} [options]
43
+ * @returns {InstallRootResolution}
44
+ */
45
+ export function resolveInstallRoot(options = {}) {
46
+ const homeDirectory = options.homeDirectory ?? homedir();
47
+ const environment = options.environment ?? process.env;
48
+ const explicitTarget = normalizeOptionalPath(options.explicitTarget);
49
+
50
+ /** @type {InstallRootResolution['source']} */
51
+ let source;
52
+ /** @type {string} */
53
+ let managedRoot;
54
+
55
+ if (explicitTarget) {
56
+ managedRoot = explicitTarget;
57
+ source = 'explicit-target';
58
+ } else {
59
+ const configDir = normalizeOptionalPath(
60
+ environment[CLAUDE_CONFIG_DIR_ENVIRONMENT_VARIABLE],
61
+ );
62
+ if (configDir) {
63
+ managedRoot = configDir;
64
+ source = 'claude-config-dir';
65
+ } else {
66
+ managedRoot = resolve(join(homeDirectory, DEFAULT_CLAUDE_DIRECTORY_NAME));
67
+ source = 'default-home';
68
+ }
69
+ }
70
+
71
+ const mypyIniInstallPath = resolve(join(homeDirectory, MYPY_INI_FILE_NAME));
72
+ return {
73
+ managedRoot,
74
+ source,
75
+ homeDirectory: resolve(homeDirectory),
76
+ manifestFilePath: join(managedRoot, MANIFEST_FILE_NAME),
77
+ mypyIniInstallPath,
78
+ allDeclaredExternalPaths: [mypyIniInstallPath],
79
+ };
80
+ }
81
+
82
+ /**
83
+ * True when candidatePath is the managed root or a descendant of it.
84
+ * Requires a separator boundary so `.claude-extra` is not inside `.claude`.
85
+ *
86
+ * @param {string} candidatePath
87
+ * @param {string} managedRoot
88
+ * @returns {boolean}
89
+ */
90
+ export function isPathWithinManagedRoot(candidatePath, managedRoot) {
91
+ if (!candidatePath || !managedRoot) {
92
+ return false;
93
+ }
94
+ const normalizedCandidate = normalizePathForComparison(candidatePath);
95
+ const normalizedRoot = normalizePathForComparison(managedRoot);
96
+ if (normalizedCandidate === normalizedRoot) {
97
+ return true;
98
+ }
99
+ const rootWithSeparator = normalizedRoot.endsWith('/')
100
+ ? normalizedRoot
101
+ : `${normalizedRoot}/`;
102
+ return normalizedCandidate.startsWith(rootWithSeparator);
103
+ }
104
+
105
+ /**
106
+ * True when a write destination is allowed: inside the managed root or on
107
+ * the declared external allowlist (today: ~/.mypy.ini under the home dir).
108
+ *
109
+ * @param {string} candidatePath
110
+ * @param {InstallRootResolution} resolution
111
+ * @returns {boolean}
112
+ */
113
+ export function isAllowedInstallDestination(candidatePath, resolution) {
114
+ if (isPathWithinManagedRoot(candidatePath, resolution.managedRoot)) {
115
+ return true;
116
+ }
117
+ const normalizedCandidate = normalizePathForComparison(candidatePath);
118
+ return resolution.allDeclaredExternalPaths.some(
119
+ (eachExternalPath) => normalizePathForComparison(eachExternalPath) === normalizedCandidate,
120
+ );
121
+ }
122
+
123
+ /**
124
+ * Parse an explicit --target value from argv tokens.
125
+ *
126
+ * @param {string[]} argv
127
+ * @returns {string | null}
128
+ */
129
+ export function parseExplicitTargetFromArgv(argv) {
130
+ for (let index = 0; index < argv.length; index += 1) {
131
+ const token = argv[index];
132
+ if (token === '--target') {
133
+ const value = argv[index + 1];
134
+ if (!value || value.startsWith('--')) {
135
+ throw new Error('--target requires a path argument');
136
+ }
137
+ return value;
138
+ }
139
+ if (token.startsWith('--target=')) {
140
+ const value = token.slice('--target='.length);
141
+ if (!value) {
142
+ throw new Error('--target requires a path argument');
143
+ }
144
+ return value;
145
+ }
146
+ }
147
+ return null;
148
+ }
149
+
150
+ /**
151
+ * @param {string | null | undefined} maybePath
152
+ * @returns {string | null}
153
+ */
154
+ function normalizeOptionalPath(maybePath) {
155
+ if (typeof maybePath !== 'string') {
156
+ return null;
157
+ }
158
+ const trimmed = maybePath.trim();
159
+ if (!trimmed) {
160
+ return null;
161
+ }
162
+ return resolve(trimmed);
163
+ }
164
+
165
+ /**
166
+ * Compare-key for a filesystem path: resolve, forward-slash, case-fold on win32.
167
+ *
168
+ * @param {string} filesystemPath
169
+ * @returns {string}
170
+ */
171
+ export function normalizePathForComparison(filesystemPath) {
172
+ const resolved = resolve(normalize(filesystemPath));
173
+ let withForwardSlashes = resolved.split(sep).join('/');
174
+ if (process.platform === 'win32') {
175
+ withForwardSlashes = withForwardSlashes.toLowerCase();
176
+ }
177
+ if (withForwardSlashes.length > 1 && withForwardSlashes.endsWith('/')) {
178
+ withForwardSlashes = withForwardSlashes.slice(0, -1);
179
+ }
180
+ return withForwardSlashes;
181
+ }
@@ -0,0 +1,401 @@
1
+ /**
2
+ * Pure install-target selection for single-profile and selected multi-profile runs.
3
+ *
4
+ * Selection finishes before any filesystem mutation. The caller injects the
5
+ * profiles root and the profile-id → directoryName map so tests need no live
6
+ * env or disk fixtures.
7
+ */
8
+
9
+ import { join, resolve, isAbsolute, normalize } from 'node:path';
10
+ import {
11
+ resolveInstallRoot,
12
+ parseExplicitTargetFromArgv,
13
+ normalizePathForComparison,
14
+ } from './resolve-install-root.mjs';
15
+
16
+ export const MAIN_DEFAULT_TARGET_IDENTITY = 'main';
17
+ export const TARGET_IDENTITY_FLAG = '--target-identity';
18
+ export const PROFILE_FLAG = '--profile';
19
+ export const PROFILES_FLAG = '--profiles';
20
+ export const DEFAULT_PROFILES_ROOT_DIRECTORY_NAME = '.claude-profiles';
21
+ export const PROFILES_ROOT_ENVIRONMENT_VARIABLE = 'LLM_SETTINGS_PROFILES_ROOT';
22
+
23
+ /**
24
+ * @typedef {{
25
+ * mode: 'main-default' | 'explicit-path' | 'profiles' | 'child-identity',
26
+ * allProfileIds: string[],
27
+ * explicitTarget: string | null,
28
+ * targetIdentity: string | null,
29
+ * }} InstallTargetSelection
30
+ */
31
+
32
+ /**
33
+ * @typedef {{
34
+ * targetIdentity: string,
35
+ * managedRoot: string,
36
+ * source: string,
37
+ * profileId: string | null,
38
+ * }} ResolvedInstallTarget
39
+ */
40
+
41
+ /**
42
+ * Parse target-selection flags from argv. Does not resolve paths.
43
+ *
44
+ * Rejects ambiguous combinations (path target + profile), empty profile lists,
45
+ * and a child hop that still carries profile flags.
46
+ *
47
+ * @param {string[]} argv
48
+ * @returns {InstallTargetSelection}
49
+ */
50
+ export function parseInstallTargetSelectionFromArgv(argv) {
51
+ const explicitTarget = parseExplicitTargetFromArgv(argv);
52
+ const targetIdentity = parseTargetIdentityFromArgv(argv);
53
+ const allProfileIds = parseProfileIdsFromArgv(argv);
54
+ const hasProfileFlags = allProfileIds.length > 0
55
+ || argvIncludesFlag(argv, PROFILE_FLAG)
56
+ || argvIncludesFlag(argv, PROFILES_FLAG);
57
+
58
+ if (targetIdentity && hasProfileFlags) {
59
+ throw new Error(
60
+ 'child install hop rejects --profile/--profiles when --target-identity is set',
61
+ );
62
+ }
63
+ if (explicitTarget && hasProfileFlags) {
64
+ throw new Error(
65
+ 'ambiguous targets: use either --target <path> or --profile/--profiles, not both',
66
+ );
67
+ }
68
+ if (hasProfileFlags && allProfileIds.length === 0) {
69
+ throw new Error('--profile/--profiles requires at least one profile id');
70
+ }
71
+ if (allProfileIds.length > 0) {
72
+ const allDuplicates = findDuplicateStrings(allProfileIds);
73
+ if (allDuplicates.length > 0) {
74
+ throw new Error(`duplicate profile id(s): ${allDuplicates.join(', ')}`);
75
+ }
76
+ return {
77
+ mode: 'profiles',
78
+ allProfileIds,
79
+ explicitTarget: null,
80
+ targetIdentity: null,
81
+ };
82
+ }
83
+ if (targetIdentity) {
84
+ return {
85
+ mode: 'child-identity',
86
+ allProfileIds: [],
87
+ explicitTarget,
88
+ targetIdentity,
89
+ };
90
+ }
91
+ if (explicitTarget) {
92
+ return {
93
+ mode: 'explicit-path',
94
+ allProfileIds: [],
95
+ explicitTarget,
96
+ targetIdentity: null,
97
+ };
98
+ }
99
+ return {
100
+ mode: 'main-default',
101
+ allProfileIds: [],
102
+ explicitTarget: null,
103
+ targetIdentity: null,
104
+ };
105
+ }
106
+
107
+ /**
108
+ * Resolve the ordered list of install targets from a parsed selection.
109
+ *
110
+ * Dedupes on the resolved managed root path (case-insensitive on Windows) so
111
+ * `--target` and a profile that names the same directory cannot both run.
112
+ * Sanitizes each profile directoryName: absolute paths and `..` segments fail.
113
+ *
114
+ * @param {InstallTargetSelection} selection
115
+ * @param {{
116
+ * homeDirectory: string,
117
+ * environment?: NodeJS.ProcessEnv | Record<string, string | undefined>,
118
+ * profilesRoot?: string | null,
119
+ * directoryNameByProfileId?: Record<string, string>,
120
+ * }} options
121
+ * @returns {ResolvedInstallTarget[]}
122
+ */
123
+ export function resolveInstallTargets(selection, options) {
124
+ const environment = options.environment ?? {};
125
+ const homeDirectory = options.homeDirectory;
126
+ const directoryNameByProfileId = options.directoryNameByProfileId ?? {};
127
+
128
+ if (selection.mode === 'profiles') {
129
+ const profilesRoot = resolveProfilesRootDirectory({
130
+ homeDirectory,
131
+ environment,
132
+ profilesRoot: options.profilesRoot,
133
+ });
134
+ /** @type {ResolvedInstallTarget[]} */
135
+ const allTargets = [];
136
+ for (const eachProfileId of selection.allProfileIds) {
137
+ const directoryName = directoryNameByProfileId[eachProfileId];
138
+ if (typeof directoryName !== 'string' || !directoryName.trim()) {
139
+ throw new Error(`unknown profile id: ${eachProfileId}`);
140
+ }
141
+ const safeDirectoryName = assertSafeProfileDirectoryName(directoryName);
142
+ const managedRoot = resolve(join(profilesRoot, safeDirectoryName));
143
+ allTargets.push({
144
+ targetIdentity: eachProfileId,
145
+ managedRoot,
146
+ source: 'selected-profile',
147
+ profileId: eachProfileId,
148
+ });
149
+ }
150
+ return dedupeTargetsByManagedRoot(allTargets);
151
+ }
152
+
153
+ const explicitTarget = selection.mode === 'main-default' ? null : selection.explicitTarget;
154
+ const resolution = resolveInstallRoot({
155
+ homeDirectory,
156
+ environment,
157
+ explicitTarget,
158
+ });
159
+ const isChildIdentity = selection.mode === 'child-identity';
160
+ return [{
161
+ targetIdentity: isChildIdentity
162
+ ? (selection.targetIdentity ?? MAIN_DEFAULT_TARGET_IDENTITY)
163
+ : MAIN_DEFAULT_TARGET_IDENTITY,
164
+ managedRoot: resolution.managedRoot,
165
+ source: resolution.source,
166
+ profileId: isChildIdentity ? selection.targetIdentity : null,
167
+ }];
168
+ }
169
+
170
+ /**
171
+ * Build the per-target ownership manifest record.
172
+ *
173
+ * @param {{
174
+ * packageName: string,
175
+ * packageVersion: string,
176
+ * targetIdentity: string,
177
+ * managedRoot: string,
178
+ * files: string[],
179
+ * skills?: string[] | null,
180
+ * installedAt?: string,
181
+ * }} parameters
182
+ * @returns {Record<string, unknown>}
183
+ */
184
+ export function buildTargetManifestRecord(parameters) {
185
+ /** @type {Record<string, unknown>} */
186
+ const manifest = {
187
+ package: parameters.packageName,
188
+ version: parameters.packageVersion,
189
+ installedAt: parameters.installedAt ?? new Date().toISOString(),
190
+ targetIdentity: parameters.targetIdentity,
191
+ managedRoot: parameters.managedRoot,
192
+ files: parameters.files,
193
+ };
194
+ if (parameters.skills) {
195
+ manifest.skills = parameters.skills;
196
+ }
197
+ return manifest;
198
+ }
199
+
200
+ /**
201
+ * Strip target-selection flags so group parsing sees only install flags.
202
+ *
203
+ * @param {string[]} argv
204
+ * @returns {string[]}
205
+ */
206
+ export function stripTargetSelectionFlagsFromArgv(argv) {
207
+ /** @type {string[]} */
208
+ const remaining = [];
209
+ for (let index = 0; index < argv.length; index += 1) {
210
+ const token = argv[index];
211
+ if (token === PROFILE_FLAG || token === PROFILES_FLAG || token === TARGET_IDENTITY_FLAG) {
212
+ const value = argv[index + 1];
213
+ if (value && !value.startsWith('--')) {
214
+ index += 1;
215
+ }
216
+ continue;
217
+ }
218
+ if (
219
+ token.startsWith(`${PROFILE_FLAG}=`)
220
+ || token.startsWith(`${PROFILES_FLAG}=`)
221
+ || token.startsWith(`${TARGET_IDENTITY_FLAG}=`)
222
+ ) {
223
+ continue;
224
+ }
225
+ if (token === '--target') {
226
+ const value = argv[index + 1];
227
+ if (value && !value.startsWith('--')) {
228
+ index += 1;
229
+ }
230
+ continue;
231
+ }
232
+ if (token.startsWith('--target=')) {
233
+ continue;
234
+ }
235
+ remaining.push(token);
236
+ }
237
+ return remaining;
238
+ }
239
+
240
+ /**
241
+ * @param {{
242
+ * homeDirectory: string,
243
+ * environment?: NodeJS.ProcessEnv | Record<string, string | undefined>,
244
+ * profilesRoot?: string | null,
245
+ * }} options
246
+ * @returns {string}
247
+ */
248
+ export function resolveProfilesRootDirectory(options) {
249
+ if (typeof options.profilesRoot === 'string' && options.profilesRoot.trim()) {
250
+ return resolve(options.profilesRoot);
251
+ }
252
+ const environment = options.environment ?? {};
253
+ const fromEnvironment = environment[PROFILES_ROOT_ENVIRONMENT_VARIABLE];
254
+ if (typeof fromEnvironment === 'string' && fromEnvironment.trim()) {
255
+ return resolve(fromEnvironment.trim());
256
+ }
257
+ return resolve(join(options.homeDirectory, DEFAULT_PROFILES_ROOT_DIRECTORY_NAME));
258
+ }
259
+
260
+ /**
261
+ * Reject absolute and parent-hop directoryName values on every host platform.
262
+ *
263
+ * Host `isAbsolute` plus Windows drive and UNC spellings so a Linux CI host
264
+ * still rejects a profile map that smuggles an absolute Windows path.
265
+ *
266
+ * @param {string} directoryName
267
+ * @returns {string}
268
+ */
269
+ export function assertSafeProfileDirectoryName(directoryName) {
270
+ const trimmed = directoryName.trim();
271
+ if (!trimmed) {
272
+ throw new Error('profile directoryName must be a non-empty relative name');
273
+ }
274
+ const hasWindowsDriveAbsolute = /^[A-Za-z]:[\\/]/.test(trimmed);
275
+ const hasWindowsUncAbsolute = trimmed.startsWith('\\\\') || trimmed.startsWith('//');
276
+ if (isAbsolute(trimmed) || hasWindowsDriveAbsolute || hasWindowsUncAbsolute) {
277
+ throw new Error(`profile directoryName must be relative, got absolute: ${trimmed}`);
278
+ }
279
+ const allSegments = normalize(trimmed).split(/[/\\]/).filter(Boolean);
280
+ if (allSegments.some((eachSegment) => eachSegment === '..')) {
281
+ throw new Error(`profile directoryName rejects parent segments: ${trimmed}`);
282
+ }
283
+ return trimmed;
284
+ }
285
+
286
+ /**
287
+ * @param {string[]} argv
288
+ * @returns {string[]}
289
+ */
290
+ function parseProfileIdsFromArgv(argv) {
291
+ /** @type {string[]} */
292
+ const allProfileIds = [];
293
+ for (let index = 0; index < argv.length; index += 1) {
294
+ const token = argv[index];
295
+ if (token === PROFILE_FLAG || token === PROFILES_FLAG) {
296
+ const value = argv[index + 1];
297
+ if (!value || value.startsWith('--')) {
298
+ throw new Error(`${token} requires a comma-separated profile id list`);
299
+ }
300
+ allProfileIds.push(...splitProfileIdList(value));
301
+ index += 1;
302
+ continue;
303
+ }
304
+ if (token.startsWith(`${PROFILE_FLAG}=`) || token.startsWith(`${PROFILES_FLAG}=`)) {
305
+ const prefix = token.startsWith(`${PROFILE_FLAG}=`)
306
+ ? `${PROFILE_FLAG}=`
307
+ : `${PROFILES_FLAG}=`;
308
+ const value = token.slice(prefix.length);
309
+ if (!value) {
310
+ throw new Error(`${prefix.slice(0, -1)} requires a comma-separated profile id list`);
311
+ }
312
+ allProfileIds.push(...splitProfileIdList(value));
313
+ }
314
+ }
315
+ return allProfileIds;
316
+ }
317
+
318
+ /**
319
+ * @param {string[]} argv
320
+ * @returns {string | null}
321
+ */
322
+ function parseTargetIdentityFromArgv(argv) {
323
+ for (let index = 0; index < argv.length; index += 1) {
324
+ const token = argv[index];
325
+ if (token === TARGET_IDENTITY_FLAG) {
326
+ const value = argv[index + 1];
327
+ if (!value || value.startsWith('--')) {
328
+ throw new Error(`${TARGET_IDENTITY_FLAG} requires an identity argument`);
329
+ }
330
+ return value;
331
+ }
332
+ if (token.startsWith(`${TARGET_IDENTITY_FLAG}=`)) {
333
+ const value = token.slice(`${TARGET_IDENTITY_FLAG}=`.length);
334
+ if (!value) {
335
+ throw new Error(`${TARGET_IDENTITY_FLAG} requires an identity argument`);
336
+ }
337
+ return value;
338
+ }
339
+ }
340
+ return null;
341
+ }
342
+
343
+ /**
344
+ * @param {string} rawList
345
+ * @returns {string[]}
346
+ */
347
+ function splitProfileIdList(rawList) {
348
+ return rawList
349
+ .split(',')
350
+ .map((eachId) => eachId.trim())
351
+ .filter((eachId) => eachId.length > 0);
352
+ }
353
+
354
+ /**
355
+ * @param {string[]} argv
356
+ * @param {string} flagName
357
+ * @returns {boolean}
358
+ */
359
+ function argvIncludesFlag(argv, flagName) {
360
+ return argv.some(
361
+ (eachToken) => eachToken === flagName || eachToken.startsWith(`${flagName}=`),
362
+ );
363
+ }
364
+
365
+ /**
366
+ * @param {string[]} allValues
367
+ * @returns {string[]}
368
+ */
369
+ function findDuplicateStrings(allValues) {
370
+ const seen = new Set();
371
+ const allDuplicates = new Set();
372
+ for (const eachValue of allValues) {
373
+ if (seen.has(eachValue)) {
374
+ allDuplicates.add(eachValue);
375
+ }
376
+ seen.add(eachValue);
377
+ }
378
+ return [...allDuplicates];
379
+ }
380
+
381
+ /**
382
+ * @param {ResolvedInstallTarget[]} allTargets
383
+ * @returns {ResolvedInstallTarget[]}
384
+ */
385
+ function dedupeTargetsByManagedRoot(allTargets) {
386
+ const seenRootByKey = new Map();
387
+ /** @type {ResolvedInstallTarget[]} */
388
+ const uniqueTargets = [];
389
+ for (const eachTarget of allTargets) {
390
+ const rootKey = normalizePathForComparison(eachTarget.managedRoot);
391
+ if (seenRootByKey.has(rootKey)) {
392
+ const priorIdentity = seenRootByKey.get(rootKey);
393
+ throw new Error(
394
+ `duplicate managed root for targets ${priorIdentity} and ${eachTarget.targetIdentity}: ${eachTarget.managedRoot}`,
395
+ );
396
+ }
397
+ seenRootByKey.set(rootKey, eachTarget.targetIdentity);
398
+ uniqueTargets.push(eachTarget);
399
+ }
400
+ return uniqueTargets;
401
+ }
@@ -11,12 +11,10 @@ Slash-command definitions installed into `~/.claude/commands/` by `bin/install.m
11
11
  | `hook-log-extract.md` | `/hook-log-extract` | Extracts and formats hook log entries for a session |
12
12
  | `hook-log-init.md` | `/hook-log-init` | Initializes the Neon Postgres schema that backs the hook-log extractor (one-time per machine) |
13
13
  | `implement.md` | `/implement` | Provides full implementation context to a right-sized engineer in XML format |
14
- | `initialize.md` | `/initialize` | Bootstraps a new project with standard Claude Code config |
15
14
  | `plan.md` | `/plan` | Plans a feature through the `anthropic-plan` skill and workflow |
16
15
  | `pr-comments.md` | `/pr-comments` | Fetches and formats PR review comments for response |
17
16
  | `review-plan.md` | `/review-plan` | Reviews the current plan packet against code standards |
18
17
  | `right-size.md` | `/right-size` | Checks an implementation against the Right-Sized Engineering rules |
19
- | `stubcheck.md` | `/stubcheck` | Finds stub bodies (`pass`/`...`/`raise NotImplementedError`) in the diff |
20
18
  | `sum.md` | `/sum` | Generates a formatted session summary for quick pickup in a new session |
21
19
 
22
20
  ## Format
@@ -8,8 +8,10 @@ Pointer documents to external sources, standard terminology, and internal tool o
8
8
  |---|---|
9
9
  | `dead-code-elimination.md` | External sources and standard terms behind CODE_RULES §9.8 (remove code you orphan): DCE, tree shaking, reachability analysis, and the Lava Flow anti-pattern |
10
10
  | `code-review-enforcement.md` | How the code-review gates work: the two required efforts (push at low, PR creation at xhigh), the stamp bound to the branch-surface hash, the single sanctioned minter, the two-layer stamp-directory guard, and the bypass surfaces the gates leave open |
11
+ | `prose-style-enforcement.md` | How `CLAUDE_PROSE_STYLE_ENFORCEMENT` arms opinionated prose gates (default off) while AskUserQuestion lean-block stays always on |
11
12
  | `advisor-tool.md` | Canonical consult bones for any stronger reviewer: when to call, hard rule before first write, how to treat advice; maps to the Anthropic advisor tool |
12
- | `team-advisor-skill.md` | `/team-advisor` map: sole-consumer warm bind, ref index, and how it pairs with `advisor()` |
13
+ | `team-advisor-skill.md` | `/team-advisor` map: sole-consumer warm bind, ref index, and advisor selection |
14
+ | `weak-executor-advisor.md` | Consult profile a below-advisor-tier executor (Sonnet, Haiku) follows on top of `advisor-tool.md`: spawn-prompt steering, context packaging, two-timing rule, consult budget, failure branches |
13
15
 
14
16
  ## Role
15
17
 
@@ -1,14 +1,14 @@
1
1
  # Advisor Tool
2
2
 
3
- Canonical consult timing and weight for any stronger-reviewer path: the native `advisor()` tool, `/team-advisor`, and the shared warm advisor.
3
+ Canonical consult timing and weight for the repository advisor path: `/team-advisor` and the shared warm advisor. Anthropic's advisor documentation supplies the source guidance for packet shape and review timing.
4
4
 
5
5
  Source bones: [Anthropic Advisor tool](https://platform.claude.com/docs/en/agents-and-tools/tool-use/advisor-tool) (Suggested system prompt for coding tasks). API shape, model pairs, cost, and caching live there. This file carries only the call rules a session needs every time.
6
6
 
7
7
  ## What it is
8
8
 
9
- `advisor()` is a no-parameter review call. The platform forwards the full conversation (task, tool calls, results) to a stronger model. The executor continues with that guidance.
9
+ `/team-advisor` is the repository's advisor implementation. It carries the full first packet explicitly, sends later deltas, and owns the warm Agent/SendMessage or read-only Sol CLI lifecycle.
10
10
 
11
- When `advisor()` is absent, use `/team-advisor` (see `team-advisor-skill.md`).
11
+ See `team-advisor-skill.md` for the bind and lifecycle map.
12
12
 
13
13
  ## When to call
14
14
 
@@ -18,11 +18,11 @@ If the task needs orientation first (find files, fetch a source, see what exists
18
18
 
19
19
  Also call:
20
20
 
21
- - **When you believe the task is complete.** Before this call, make the deliverable durable: write the file, save the result, commit the change. The call takes time; if the session ends during it, a durable result survives and an unwritten one does not.
21
+ - **When you believe the task is complete.** Before this call, make the deliverable durable: write the file, save the result, commit the change. The call takes time; if the session ends during it, a durable result survives and an unwritten one does not. Ask the advisor to hunt for missing requirements, untested behavior, wrong assumptions, unhandled edge cases, evidence gaps, and early completion claims.
22
22
  - **When stuck** — errors recur, approach does not converge, results do not fit.
23
23
  - **When considering a change of approach.**
24
24
 
25
- On tasks longer than a few steps, call at least once before committing to an approach and once before declaring done. On short reactive tasks where the next action is dictated by tool output you just read, you do not need repeated calls most value is on the first call, before the approach hardens.
25
+ On tasks longer than a few steps, aim for an early approach consult and a completion review. Reserve a third consult for recovery or reconciliation, and add consults when material new evidence or forks arise. This cadence guides planning and leaves the task free to follow its evidence. Short reactive tasks may use the single consult that best fits the live decision.
26
26
 
27
27
  Call for design, architecture, and risk questions where you will not touch a file. If the response would be analysis or a recommendation with no other tool calls, call first. That judgment is where a second opinion is highest value. Simple factual lookups and arithmetic do not need a call.
28
28
 
@@ -36,16 +36,34 @@ Give the advice serious weight. If a step fails empirically, or primary-source e
36
36
 
37
37
  If your data points one way and the advisor points another: do not silently switch. Surface the conflict in one more call — "I found X, you suggest Y, which constraint breaks the tie?" A reconcile call is cheaper than the wrong branch.
38
38
 
39
+ Work a disagreement in this order: keep the observed evidence in the record, name the conflict plainly, ask the advisor which constraint breaks the tie, then act on the reconciled plan.
40
+
41
+ ## Escalation shapes
42
+
43
+ Four shapes cover how a harder task gets more strength behind it. Route to the one that matches the work, not by default to the advisor.
44
+
45
+ | Shape | Fits when |
46
+ |---|---|
47
+ | Advisor | The task needs intermittent strategy and review, and one executor keeps the task from start to finish. |
48
+ | Subagent | A piece of the task is a bounded subtask that benefits from its own context and its own loop. |
49
+ | Stronger-model planning phase | The plan needs the strong model's judgment; the fast model can carry it out once written. |
50
+ | Full model switch | Every step of the task needs the stronger tier, not just the hard decisions. |
51
+
52
+ Spawn a subagent when the work is a delegable bounded subtask. Switch the whole task to the stronger model when every turn needs that tier.
53
+
39
54
  ## Brevity cue
40
55
 
41
56
  When the consult path supports a free-text brief, append:
42
57
 
43
58
  `(Advisor: please keep your guidance under 80 words — I need a focused starting point, not a comprehensive plan.)`
44
59
 
60
+ Size the ask at roughly 80 percent of the true ceiling; direct address to the advisor lands more reliably than a third-person description.
61
+
45
62
  ## Related
46
63
 
47
64
  | Doc | Holds |
48
65
  |---|---|
49
66
  | [Anthropic Advisor tool](https://platform.claude.com/docs/en/agents-and-tools/tool-use/advisor-tool) | API shape, model pairs, cost, caching, full best practices |
50
- | `team-advisor-skill.md` | Standing warm advisor when `advisor()` is missing |
51
- | `~/.claude/_shared/advisor/advisor-protocol.md` | Host bind, floor walk, lifecycle, executor paste blocks |
67
+ | `team-advisor-skill.md` | Standing warm advisor for this repository |
68
+ | `~/.claude/_shared/advisor/advisor-protocol.md` | Host bind, floor walk, lifecycle a read map routes each bind or consult moment to its `reference/` detail file |
69
+ | `weak-executor-advisor.md` | Consult deltas for an executor spawned below the advisor's own tier |