claude-dev-env 1.93.1 → 1.95.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 (262) hide show
  1. package/_shared/advisor/CLAUDE.md +2 -2
  2. package/_shared/advisor/advisor-protocol.md +35 -27
  3. package/_shared/advisor/scripts/config/advisor_scripts_constants/model_tier_run_validator_constants.py +3 -2
  4. package/_shared/advisor/scripts/model_tier_run_validator.py +23 -15
  5. package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +81 -17
  6. package/_shared/pr-loop/CLAUDE.md +1 -0
  7. package/_shared/pr-loop/audit-contract.md +1 -1
  8. package/_shared/pr-loop/gh-payloads.md +3 -3
  9. package/_shared/pr-loop/post-audit-thread-contract.md +51 -0
  10. package/_shared/pr-loop/scripts/README.md +2 -2
  11. package/_shared/pr-loop/scripts/fix_hookspath.py +47 -12
  12. package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +0 -69
  13. package/_shared/pr-loop/scripts/tests/test_fix_hookspath.py +497 -374
  14. package/_shared/pr-loop/state-schema.md +3 -22
  15. package/bin/CLAUDE.md +11 -2
  16. package/bin/ever-shipped-skills.mjs +70 -0
  17. package/bin/install.mjs +138 -13
  18. package/bin/install.prune.test.mjs +457 -0
  19. package/bin/install.test.mjs +10 -4
  20. package/docs/CODE_RULES.md +1 -1
  21. package/hooks/blocking/CLAUDE.md +2 -0
  22. package/hooks/blocking/code_rules_enforcer.py +4 -0
  23. package/hooks/blocking/code_rules_shared.py +82 -0
  24. package/hooks/blocking/code_rules_test_layout.py +9 -3
  25. package/hooks/blocking/pii_commit_command.py +398 -0
  26. package/hooks/blocking/pii_payload_scan.py +133 -0
  27. package/hooks/blocking/pii_prevention_blocker.py +151 -449
  28. package/hooks/blocking/plain_language_blocker.py +138 -4
  29. package/hooks/blocking/sensitive_file_protector.py +114 -48
  30. package/hooks/blocking/tdd_enforcer.py +9 -2
  31. package/hooks/blocking/test_code_rules_enforcer_scratchpad.py +105 -0
  32. package/hooks/blocking/test_code_rules_shared.py +181 -0
  33. package/hooks/blocking/test_pii_prevention_blocker.py +299 -0
  34. package/hooks/blocking/test_plain_language_blocker_allowlist.py +184 -0
  35. package/hooks/blocking/test_sensitive_file_protector.py +185 -0
  36. package/hooks/blocking/test_tdd_enforcer_scratchpad.py +105 -0
  37. package/hooks/blocking/test_verdict_directory_write_blocker.py +42 -7
  38. package/hooks/hooks_constants/CLAUDE.md +4 -2
  39. package/hooks/hooks_constants/code_rules_path_utils_constants.py +0 -2
  40. package/hooks/hooks_constants/harness_scratchpad_constants.py +17 -0
  41. package/hooks/hooks_constants/local_identity.py +65 -15
  42. package/hooks/hooks_constants/pii_prevention_constants.py +16 -0
  43. package/hooks/hooks_constants/plain_language_blocker_constants.py +5 -0
  44. package/hooks/hooks_constants/pr_description_enforcer_constants.py +1 -1
  45. package/hooks/hooks_constants/sensitive_file_protector_constants.py +42 -0
  46. package/hooks/hooks_constants/test_local_identity.py +68 -0
  47. package/hooks/pyproject.toml +75 -4
  48. package/hooks/validators/CLAUDE.md +1 -1
  49. package/hooks/validators/README.md +2 -0
  50. package/hooks/validators/python_style_checks.py +114 -136
  51. package/hooks/validators/python_style_helpers.py +95 -0
  52. package/hooks/validators/test_python_style_checks.py +0 -164
  53. package/hooks/validators/test_python_style_checks_decorator_gap.py +119 -0
  54. package/hooks/validators/test_python_style_fixes.py +251 -0
  55. package/hooks/validators/test_python_style_helpers.py +125 -0
  56. package/package.json +1 -1
  57. package/rules/CLAUDE.md +1 -0
  58. package/rules/anti-corollary-tests.md +69 -0
  59. package/rules/bdd.md +1 -3
  60. package/rules/code-reviews.md +1 -1
  61. package/rules/gh-paginate.md +1 -1
  62. package/rules/plain-language.md +2 -0
  63. package/scripts/CLAUDE.md +10 -1
  64. package/scripts/Get-SessionAccount.ps1 +210 -0
  65. package/scripts/tests/CLAUDE.md +15 -1
  66. package/scripts/tests/Get-SessionAccount.Tests.ps1 +350 -0
  67. package/skills/CLAUDE.md +6 -27
  68. package/skills/anthropic-plan/SKILL.md +1 -13
  69. package/skills/autoconverge/CLAUDE.md +5 -2
  70. package/skills/autoconverge/SKILL.md +112 -387
  71. package/skills/autoconverge/reference/CLAUDE.md +4 -1
  72. package/skills/autoconverge/reference/closing-report.md +9 -2
  73. package/skills/autoconverge/reference/convergence.md +26 -22
  74. package/skills/autoconverge/reference/headless-safety.md +44 -0
  75. package/skills/autoconverge/reference/multi-pr.md +88 -0
  76. package/skills/autoconverge/reference/self-closing-loop.md +84 -0
  77. package/skills/autoconverge/reference/stop-conditions.md +21 -13
  78. package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +87 -27
  79. package/skills/autoconverge/workflow/converge.contract.test.mjs +40 -10
  80. package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +179 -47
  81. package/skills/autoconverge/workflow/converge.fix-progress.test.mjs +1 -1
  82. package/skills/autoconverge/workflow/converge.mjs +286 -96
  83. package/skills/bugteam/CLAUDE.md +1 -1
  84. package/skills/bugteam/CONSTRAINTS.md +3 -3
  85. package/skills/bugteam/PROMPTS.md +8 -19
  86. package/skills/bugteam/SKILL.md +13 -14
  87. package/skills/bugteam/reference/CLAUDE.md +1 -1
  88. package/skills/bugteam/reference/README.md +2 -3
  89. package/skills/bugteam/reference/audit-and-teammates.md +2 -2
  90. package/skills/bugteam/reference/copilot-gap-analysis.md +2 -0
  91. package/skills/bugteam/reference/github-pr-reviews.md +56 -38
  92. package/skills/bugteam/reference/team-setup.md +11 -11
  93. package/skills/bugteam/scripts/CLAUDE.md +6 -6
  94. package/skills/bugteam/scripts/README.md +20 -8
  95. package/skills/bugteam/scripts/bugteam_code_rules_gate.py +13 -1463
  96. package/skills/bugteam/scripts/bugteam_fix_hookspath.py +23 -358
  97. package/skills/bugteam/scripts/bugteam_preflight.py +13 -315
  98. package/skills/bugteam/scripts/bugteam_scripts_constants/CLAUDE.md +2 -3
  99. package/skills/bugteam/scripts/test_bugteam_code_rules_gate.py +34 -1053
  100. package/skills/bugteam/scripts/test_bugteam_fix_hookspath.py +30 -347
  101. package/skills/bugteam/scripts/test_bugteam_preflight.py +30 -305
  102. package/skills/closeout/SKILL.md +153 -0
  103. package/skills/closeout/reference/handoff-prompt-template.md +72 -0
  104. package/skills/closeout/reference/issue-body-templates.md +108 -0
  105. package/skills/closeout/reference/pii-redaction-checklist.md +36 -0
  106. package/skills/copilot-finding-triage/SKILL.md +80 -28
  107. package/skills/copilot-finding-triage/reference/tier-rubric.md +70 -11
  108. package/skills/copilot-finding-triage/templates/notification.md +8 -3
  109. package/skills/everything-search/SKILL.md +1 -2
  110. package/skills/grokify/SKILL.md +58 -0
  111. package/skills/grokify/templates/handoff-template.md +92 -0
  112. package/skills/orchestrator/SKILL.md +35 -25
  113. package/skills/orchestrator-refresh/SKILL.md +15 -11
  114. package/skills/pr-converge/CLAUDE.md +1 -1
  115. package/skills/pr-converge/SKILL.md +132 -233
  116. package/skills/pr-converge/reference/convergence-gates.md +102 -66
  117. package/skills/pr-converge/reference/examples.md +37 -25
  118. package/skills/pr-converge/reference/fix-protocol.md +7 -7
  119. package/skills/pr-converge/reference/multi-pr-orchestration.md +10 -4
  120. package/skills/pr-converge/reference/per-tick.md +64 -41
  121. package/skills/pr-converge/reference/state-schema.md +27 -2
  122. package/skills/pr-converge/scripts/CLAUDE.md +8 -0
  123. package/skills/pr-converge/scripts/README.md +4 -3
  124. package/skills/pr-converge/scripts/_pr_converge_path_setup.py +23 -0
  125. package/skills/pr-converge/scripts/check_convergence.py +189 -645
  126. package/skills/pr-converge/scripts/check_convergence_gates.py +235 -0
  127. package/skills/pr-converge/scripts/check_convergence_thread_gates.py +219 -0
  128. package/skills/pr-converge/scripts/conftest.py +8 -0
  129. package/skills/pr-converge/scripts/pr_converge_scripts_constants/CLAUDE.md +1 -0
  130. package/skills/pr-converge/scripts/pr_converge_scripts_constants/convergence_gate_constants.py +36 -0
  131. package/skills/pr-converge/scripts/test__pr_converge_path_setup.py +26 -0
  132. package/skills/pr-converge/scripts/test_check_convergence.py +73 -228
  133. package/skills/pr-converge/scripts/test_check_convergence_contract.py +321 -0
  134. package/skills/pr-converge/scripts/test_check_convergence_gates.py +68 -0
  135. package/skills/pr-converge/scripts/test_check_convergence_thread_gates.py +75 -0
  136. package/skills/pr-fix-protocol/SKILL.md +20 -20
  137. package/skills/privacy-hygiene/SKILL.md +4 -3
  138. package/skills/reviewer-gates/SKILL.md +26 -8
  139. package/skills/session-log/SKILL.md +11 -12
  140. package/skills/session-tidy/SKILL.md +40 -30
  141. package/skills/skill-builder/CLAUDE.md +13 -10
  142. package/skills/skill-builder/SKILL.md +31 -31
  143. package/skills/skill-builder/references/CLAUDE.md +10 -6
  144. package/skills/skill-builder/references/delegation-map.md +13 -4
  145. package/skills/skill-builder/references/description-field.md +113 -0
  146. package/skills/skill-builder/references/self-audit-checklist.md +21 -4
  147. package/skills/skill-builder/references/skill-modularity.md +113 -0
  148. package/skills/skill-builder/references/skill-types.md +4 -0
  149. package/skills/skill-builder/templates/CLAUDE.md +4 -4
  150. package/skills/skill-builder/templates/gap-analysis.md +25 -1
  151. package/skills/skill-builder/workflows/CLAUDE.md +6 -4
  152. package/skills/skill-builder/workflows/improve-skill.md +17 -16
  153. package/skills/skill-builder/workflows/new-skill.md +34 -10
  154. package/skills/skill-builder/workflows/polish-skill.md +59 -43
  155. package/skills/team-advisor/SKILL.md +15 -11
  156. package/skills/usage-pause/SKILL.md +10 -14
  157. package/system-prompts/software-engineer.xml +7 -6
  158. package/hooks/validators/test_verify_paths.py +0 -32
  159. package/hooks/validators/verify_paths.py +0 -57
  160. package/skills/bdd-protocol/CLAUDE.md +0 -26
  161. package/skills/bdd-protocol/SKILL.md +0 -30
  162. package/skills/bdd-protocol/references/CLAUDE.md +0 -10
  163. package/skills/bdd-protocol/references/anti-patterns.md +0 -26
  164. package/skills/bdd-protocol/references/example-mapping.md +0 -23
  165. package/skills/bg-agent/CLAUDE.md +0 -17
  166. package/skills/bg-agent/SKILL.md +0 -69
  167. package/skills/bugteam/scripts/bugteam_scripts_constants/bugteam_code_rules_gate_constants.py +0 -55
  168. package/skills/bugteam/scripts/bugteam_scripts_constants/bugteam_fix_hookspath_constants.py +0 -26
  169. package/skills/bugteam/scripts/bugteam_scripts_constants/bugteam_preflight_constants.py +0 -35
  170. package/skills/caveman/CLAUDE.md +0 -15
  171. package/skills/caveman/SKILL.md +0 -38
  172. package/skills/code/CLAUDE.md +0 -17
  173. package/skills/code/SKILL.md +0 -178
  174. package/skills/deep-research/CLAUDE.md +0 -17
  175. package/skills/deep-research/SKILL.md +0 -80
  176. package/skills/findbugs/CLAUDE.md +0 -20
  177. package/skills/findbugs/SKILL.md +0 -241
  178. package/skills/fixbugs/CLAUDE.md +0 -19
  179. package/skills/fixbugs/SKILL.md +0 -142
  180. package/skills/gh-paginate/CLAUDE.md +0 -18
  181. package/skills/gh-paginate/SKILL.md +0 -84
  182. package/skills/gotcha/CLAUDE.md +0 -33
  183. package/skills/gotcha/SKILL.md +0 -73
  184. package/skills/implement/CLAUDE.md +0 -27
  185. package/skills/implement/SKILL.md +0 -66
  186. package/skills/implement/scripts/CLAUDE.md +0 -22
  187. package/skills/implement/scripts/append_note.py +0 -133
  188. package/skills/implement/scripts/implement_scripts_constants/CLAUDE.md +0 -22
  189. package/skills/implement/scripts/implement_scripts_constants/__init__.py +0 -0
  190. package/skills/implement/scripts/implement_scripts_constants/notes_constants.py +0 -12
  191. package/skills/implement/scripts/test_append_note.py +0 -191
  192. package/skills/log-audit/CLAUDE.md +0 -20
  193. package/skills/log-audit/SKILL.md +0 -66
  194. package/skills/log-audit/reference/CLAUDE.md +0 -9
  195. package/skills/log-audit/reference/charter.md +0 -52
  196. package/skills/log-audit/scripts/CLAUDE.md +0 -27
  197. package/skills/log-audit/scripts/cluster_recurrences.py +0 -261
  198. package/skills/log-audit/scripts/collect_log_window.py +0 -199
  199. package/skills/log-audit/scripts/log_audit_constants/CLAUDE.md +0 -12
  200. package/skills/log-audit/scripts/log_audit_constants/__init__.py +0 -0
  201. package/skills/log-audit/scripts/log_audit_constants/cluster_recurrences_constants.py +0 -23
  202. package/skills/log-audit/scripts/log_audit_constants/collect_log_window_constants.py +0 -24
  203. package/skills/log-audit/scripts/log_audit_constants/mine_copilot_findings_constants.py +0 -49
  204. package/skills/log-audit/scripts/mine_copilot_findings.py +0 -302
  205. package/skills/log-audit/scripts/test_cluster_recurrences.py +0 -160
  206. package/skills/log-audit/scripts/test_collect_log_window.py +0 -111
  207. package/skills/log-audit/scripts/test_mine_copilot_findings.py +0 -126
  208. package/skills/logifix/CLAUDE.md +0 -36
  209. package/skills/logifix/SKILL.md +0 -69
  210. package/skills/logifix/scripts/CLAUDE.md +0 -16
  211. package/skills/logifix/scripts/logifix.ps1 +0 -205
  212. package/skills/monitor-open-prs/CLAUDE.md +0 -34
  213. package/skills/monitor-open-prs/SKILL.md +0 -88
  214. package/skills/monitor-open-prs/scripts/CLAUDE.md +0 -17
  215. package/skills/monitor-open-prs/scripts/discover_open_prs.py +0 -69
  216. package/skills/monitor-open-prs/scripts/test_discover_open_prs.py +0 -149
  217. package/skills/monitor-open-prs/test_skill_contract.py +0 -27
  218. package/skills/post-audit-findings/SKILL.md +0 -80
  219. package/skills/pr-consistency-audit/CLAUDE.md +0 -34
  220. package/skills/pr-consistency-audit/SKILL.md +0 -116
  221. package/skills/pr-consistency-audit/reference/CLAUDE.md +0 -16
  222. package/skills/pr-consistency-audit/reference/detection-rules.md +0 -96
  223. package/skills/pr-consistency-audit/reference/illustrations.md +0 -78
  224. package/skills/pr-review-responder/CLAUDE.md +0 -35
  225. package/skills/pr-review-responder/EXAMPLES.md +0 -590
  226. package/skills/pr-review-responder/PRINCIPLES.md +0 -533
  227. package/skills/pr-review-responder/README.md +0 -168
  228. package/skills/pr-review-responder/SKILL.md +0 -203
  229. package/skills/pr-review-responder/TESTING.md +0 -350
  230. package/skills/pr-scope-resolve/SKILL.md +0 -45
  231. package/skills/pre-compact/CLAUDE.md +0 -24
  232. package/skills/pre-compact/SKILL.md +0 -134
  233. package/skills/qbug/CLAUDE.md +0 -40
  234. package/skills/qbug/SKILL.md +0 -387
  235. package/skills/qbug/test_qbug_skill_audit_schema.py +0 -152
  236. package/skills/qbug/test_qbug_skill_post_fix_audit.py +0 -103
  237. package/skills/refine/CLAUDE.md +0 -44
  238. package/skills/refine/SKILL.md +0 -262
  239. package/skills/refine/templates/CLAUDE.md +0 -17
  240. package/skills/refine/templates/implementation-notes-template.html +0 -56
  241. package/skills/refine/templates/plan-template.md +0 -60
  242. package/skills/research-mode/CLAUDE.md +0 -35
  243. package/skills/research-mode/SKILL.md +0 -53
  244. package/skills/structure-prompt/CLAUDE.md +0 -42
  245. package/skills/structure-prompt/SKILL.md +0 -44
  246. package/skills/structure-prompt/reference/CLAUDE.md +0 -28
  247. package/skills/structure-prompt/reference/adversarial-tuning.md +0 -62
  248. package/skills/structure-prompt/reference/block-classification.md +0 -27
  249. package/skills/structure-prompt/reference/canonical-case.md +0 -48
  250. package/skills/structure-prompt/reference/citation-depth.md +0 -70
  251. package/skills/structure-prompt/reference/cleanup.md +0 -33
  252. package/skills/structure-prompt/reference/constraints.md +0 -33
  253. package/skills/structure-prompt/reference/directives.md +0 -37
  254. package/skills/structure-prompt/reference/examples.md +0 -72
  255. package/skills/structure-prompt/reference/instantiation.md +0 -51
  256. package/skills/structure-prompt/reference/output-contract.md +0 -72
  257. package/skills/structure-prompt/reference/per-category.md +0 -23
  258. package/skills/structure-prompt/reference/persona.md +0 -38
  259. package/skills/structure-prompt/reference/research.md +0 -33
  260. package/skills/structure-prompt/reference/structure.md +0 -28
  261. package/skills/verified-build/CLAUDE.md +0 -33
  262. package/skills/verified-build/SKILL.md +0 -33
@@ -2,13 +2,13 @@
2
2
 
3
3
  Warm-advisor bind-and-consult protocol shared by `team-advisor`, `orchestrator`, `orchestrator-refresh`, and every executor subagent `orchestrator` routes work to. Changes here affect all of these simultaneously — treat this as a breaking-change surface.
4
4
 
5
- Host profile (Claude vs Grok) is detected first; Claude walks the multi-tier Agent spawn ladder, Grok self-binds as the advisor with a separate executor paste block.
5
+ Host profile (Claude vs Grok) is detected first; Claude walks the multi-tier Agent spawn ladder (CLI chain as fallback), Grok binds a max-tier Claude advisor through the CLI Claude-chain (fail closed when the chain cannot serve) with a separate executor paste block.
6
6
 
7
7
  ## Key documents
8
8
 
9
9
  | File | Purpose |
10
10
  |---|---|
11
- | `advisor-protocol.md` | Host profiles first, Claude-only model floor, warm-up spawn procedure and charter, consult format and cadence, lifecycle ownership (spawn on Claude / re-charter on Grok), host-matched Advisor blocks for executor spawns, and the Claude CLI fallback chain |
11
+ | `advisor-protocol.md` | Host profiles first, model floor, warm-up / CLI bind procedure and charter, consult format and cadence, lifecycle ownership (Agent spawn on Claude / CLI re-bind on Grok), host-matched Advisor blocks for executor spawns, and the shared CLI Claude-chain |
12
12
 
13
13
  ## Subdirectory
14
14
 
@@ -20,28 +20,34 @@ Use the **Model floor** ladder below (Fable → Opus → Sonnet → Haiku). Warm
20
20
 
21
21
  ### Grok host
22
22
 
23
- Grok Build / xAI has exactly one model tier. Do **not** walk the Claude ladder and do **not** require a `claude` binary.
23
+ Grok Build / xAI cannot spawn a Claude `session-advisor` through the Agent tool. Bind a **max-tier Claude advisor** through the shared CLI Claude-chain (account usage failover). Do **not** treat this Grok session as the advisor.
24
24
 
25
25
  1. Detect host profile first (this section).
26
- 2. Set `own_tier = Grok`, `candidate_tiers = ["Grok"]`, one attempt only, `selected_tier = Grok`.
27
- 3. **Self-as-advisor:** skip the Agent-tool spawn of `session-advisor`. The orchestrating Grok session *is* the advisor — the same process answers ENDORSE / CORRECTION / PLAN / STOP. Executors (when any) report blockers to the orchestrating session; that session answers with the four signals inline (or via its own same-session consult loop). There is no SendMessage-to-another-Claude-agent path on a pure Grok host.
28
- 4. Record the single attempt as `{tier: "Grok", result: "self"}` — the `self` token marks a self-bind so logs stay honest (not a separate spawn).
29
- 5. CLI Claude-chain fallback does **not** apply on a pure Grok host.
30
- 6. Paste the **Grok host** Advisor block into every executor spawn prompt — never the Claude SendMessage block.
26
+ 2. Set the advisor floor to **Opus** so the walk is `candidate_tiers = ["Fable", "Opus"]` with `own_tier = Opus`. The walk never drops to Sonnet or Haiku on a Grok host.
27
+ 3. **CLI bind (primary path):** for each candidate top-down, pipe a charter file into:
31
28
 
32
- Resolve the Grok alias with `resolve_cli_model_id("Grok")` → `grok` when a model field is required.
29
+ ```
30
+ python "$HOME/.claude/scripts/claude_chain_runner.py" -- -p --model <alias> --effort <effort> --output-format json
31
+ ```
33
32
 
34
- ## Model floor
33
+ Use `--model fable --effort high` on Fable; use `--model opus --effort max` on Opus. The chain runner walks `~/.claude/claude-chain.json` (binaries such as `claude`, `claude-ev`, `claude-editor`, `claude-mel`) and fails over only on a usage-limit signature.
34
+ 4. Stop at the first successful bind. Record `{tier, result: "cli"}` and set `selected_tier` to that tier. Persist `session_id` from the JSON events (any event carries it; reply text is the `type == "result"` event's `.result` field). Run every bind and every later consult with cwd set to the repo root the work is for — Claude sessions are project-scoped by working directory.
35
+ 5. **Fail closed:** when every candidate fails (chain exhausted or model unavailable), set `selected_tier = null` and a `fallback_reason`, report that the advisor is unreachable, and **stop**. Do **not** answer ENDORSE / CORRECTION / PLAN / STOP as this Grok session. Do **not** self-endorse.
36
+ 6. Paste the **Grok host** Advisor block into every executor spawn prompt — never the Claude SendMessage block. Executors report to the orchestrating session; that session consults the bound Claude CLI advisor and relays the four-signal reply.
37
+
38
+ Resolve a Grok session's own model field with `resolve_cli_model_id("Grok")` → `grok` when a host model alias is required. The **advisor** bind uses Fable/Opus aliases only.
35
39
 
36
- **Claude host only.** On a Grok host, skip this section entirely — follow **Host profiles → Grok host**.
40
+ ## Model floor
37
41
 
38
- The advisor's model tier must be at or above the highest tier of any consumer that will reach it. Each consuming skill supplies its own consumer set when computing the floor:
42
+ **Claude host:** the advisor's model tier must be at or above the highest tier of any consumer that will reach it. Each consuming skill supplies its own consumer set when computing the floor:
39
43
  - `team-advisor`: the sole consumer is the calling session itself, so the floor is just that session's own tier.
40
44
  - `orchestrator`: the consumer set is the orchestrating session plus every tier named in its routing table, so the floor is the max of those.
41
45
 
42
- Ladder, strongest first (canonical Title Case names: `Fable`, `Opus`, `Sonnet`, `Haiku`; the validator accepts any letter case and normalizes to Title Case): Fable, Opus, Sonnet, Haiku. Read the floor tier — the lower bound only — then try the warm-agent spawn top-down from Fable, stopping at the floor tier — never spawn below it. Each walk attempt sets the Agent tool `model:` field to the short alias for that attempt's candidate tier (`resolve_cli_model_id(candidate_tier)` — for example `opus`, not Title Case `Opus`). The warm agent is created at `selected_tier` (the first ladder tier that actually spawned), which may sit above the floor. If even the floor tier's spawn fails, move to the CLI fallback below rather than spawning below the floor.
46
+ **Grok host:** the CLI advisor floor is fixed at **Opus** (walk Fable Opus only). The Grok session's own tier is not the advisor floor see **Host profiles Grok host**.
43
47
 
44
- Emit a structured spawn-walk log so it can be checked mechanically rather than inferred from a transcript. Record: `own_tier` (the floor tier read at the top of this section), `candidate_tiers` (the ladder slice down to that floor), `attempts` (one `{tier, result}` entry appended as each spawn try happens, `result` one of `spawned` for a Claude Agent spawn, `self` for a Grok host self-bind, or a failure reason such as `unavailable`), and `selected_tier` (the tier of the first successful bind first `spawned` or `self` entry or `null` paired with a `fallback_reason` string when none bound and the CLI fallback took over). Write the log as JSON with those field names to a path the session controls typically `<job-temp-dir>/model-tier-run.json` (or the OS temp directory when no job directory exists). Check it with:
48
+ Ladder, strongest first (canonical Title Case names: `Fable`, `Opus`, `Sonnet`, `Haiku`; the validator accepts any letter case and normalizes to Title Case): Fable, Opus, Sonnet, Haiku. Read the floor tier the lower bound only then try binds top-down from Fable, stopping at the floor tier never bind below it. On a Claude host each walk attempt sets the Agent tool `model:` field to the short alias for that attempt's candidate tier (`resolve_cli_model_id(candidate_tier)` for example `opus`, not Title Case `Opus`). On a Grok host each walk attempt uses the CLI chain with that alias and the effort flags in **Host profiles Grok host**. The advisor is created at `selected_tier` (the first ladder tier that actually bound), which may sit above the floor. If even the floor tier fails on a Claude host, move to the CLI fallback below; on a Grok host the CLI chain **is** the primary path, so floor failure is fail-closed (report unreachable).
49
+
50
+ Emit a structured spawn-walk log so it can be checked mechanically rather than inferred from a transcript. Record: `own_tier` (the floor tier), `candidate_tiers` (the ladder slice down to that floor), `attempts` (one `{tier, result}` entry appended as each bind try happens, `result` one of `spawned` for a Claude Agent spawn, `cli` for a CLI Claude-chain bind, or a failure reason such as `unavailable`), and `selected_tier` (the tier of the first successful bind — first `spawned` or `cli` entry — or `null` paired with a `fallback_reason` string when none bound). Write the log as JSON with those field names to a path the session controls — typically `<job-temp-dir>/model-tier-run.json` (or the OS temp directory when no job directory exists). Check it with:
45
51
 
46
52
  ```
47
53
  python "$HOME/.claude/_shared/advisor/scripts/model_tier_run_validator.py" <path-to-model-tier-run.json>
@@ -49,15 +55,15 @@ python "$HOME/.claude/_shared/advisor/scripts/model_tier_run_validator.py" <path
49
55
 
50
56
  Exit code `0` means every invariant holds; `1` means a ladder invariant failed; `2` means the path or JSON was unusable. The same checks are available in-process via `validate_model_tier_run(run)`.
51
57
 
52
- The validator checks ladder shape only (candidate slice, attempt order, success-token rules per tier). On a pure Grok host the protocol forbids CLI fallback after a failed self-bind; a log with `own_tier=Grok`, exhausted attempts, and `selected_tier=null` may still pass the structural validator treat that as a host-policy violation outside the ladder checker.
58
+ The validator checks ladder shape only (candidate slice, attempt order, success-token rules per tier). Host policy on top: a Grok host with `selected_tier=null` after an exhausted Fable→Opus walk must fail closed (report unreachable; never self-endorse).
53
59
 
54
60
  ## Warm-up (once per session)
55
61
 
56
- On a Grok host, skip this section — follow **Host profiles → Grok host** (self-as-advisor; no Agent spawn walk).
62
+ On a **Grok host**, follow **Host profiles → Grok host** (CLI Claude-chain bind at Fable then Opus; no Agent-tool `session-advisor` spawn). Charter the CLI session as a standing reviewer that only answers with ENDORSE / CORRECTION / PLAN / STOP — same consult contract as the Agent path, without SendMessage.
57
63
 
58
- On a Claude host, the consuming skill's session walks the candidate tiers top-down. For each attempt, spawn with:
64
+ On a **Claude host**, the consuming skill's session walks the candidate tiers top-down. For each attempt, spawn with:
59
65
  - `subagent_type: session-advisor` (see [`agents/session-advisor.md`](../../agents/session-advisor.md) for the full signal contract).
60
- - `model`: the short alias for that attempt's candidate tier via `resolve_cli_model_id` (or the alias table under Fallback) — for example `opus`, not Title Case `Opus`. The floor is only the lower bound of the walk; the walk still tries stronger tiers first.
66
+ - `model`: the short alias for that attempt's candidate tier via `resolve_cli_model_id` (or the alias table under CLI chain) — for example `opus`, not Title Case `Opus`. The floor is only the lower bound of the walk; the walk still tries stronger tiers first.
61
67
  - `name`: a name the session and every consumer will use to reach it (e.g. `team-advisor-agent`).
62
68
  - `run_in_background: true`.
63
69
 
@@ -86,7 +92,7 @@ Each consult carries, in order: who you are and your assignment (only needed on
86
92
 
87
93
  **Report-back rule.** After a CORRECTION or PLAN, your next consult on that topic opens with what happened when you followed it.
88
94
 
89
- Treat the reply as a serious second opinion: a CORRECTION — whether it names a wrong step or a risk worth closing — is something to address before treating the plan or the work as done. A STOP, or a consult that finds the advisor unreachable, is reported up rather than retried — team-advisor's sole consumer is the session itself, so it reports to the user; orchestrator's executors report to the orchestrating session, which decides. When the advisor becomes unreachable, report that to the session that owns its lifecycle (see below); that session alone decides whether to respawn (Claude) or re-charter itself (Grok).
95
+ Treat the reply as a serious second opinion: a CORRECTION — whether it names a wrong step or a risk worth closing — is something to address before treating the plan or the work as done. A STOP, or a consult that finds the advisor unreachable, is reported up rather than retried — team-advisor's sole consumer is the session itself, so it reports to the user; orchestrator's executors report to the orchestrating session, which decides. When the advisor becomes unreachable, report that to the session that owns its lifecycle (see below); that session alone decides whether to respawn (Claude Agent or Grok CLI re-bind). A Grok host that cannot re-bind fails closed and reports to the user — it does not answer the four signals as itself.
90
96
 
91
97
  ## Advisor block — paste the host-matched block into every executor spawn prompt
92
98
 
@@ -96,9 +102,9 @@ Each paragraph is self-contained — the executor receives only this text, not t
96
102
 
97
103
  > A shared session advisor named `<name>` is reachable via SendMessage. Consult it before locking in a nontrivial approach, once you believe your assignment is done, before any hard-to-reverse action, when the same failure repeats or progress has stalled, and when the chosen approach is being reconsidered. Open each consult with who you are and your assignment, then: what you tried, the exact decision or blocker, and relevant paths or excerpts. Re-raise something it already answered only when you have new evidence to attach — the result of trying its advice, fresh output, or a changed constraint; otherwise act on its standing answer. After a CORRECTION or PLAN, your next consult on that topic opens with what happened when you followed it. Its replies open with one of ENDORSE, CORRECTION, PLAN, or STOP — treat CORRECTION and PLAN as actions to take. On STOP, or if the advisor is unreachable, report that back to whoever assigned you and leave lifecycle decisions to the session that owns the advisor.
98
104
 
99
- ### Grok host (self-as-advisor; report to orchestrating session)
105
+ ### Grok host (Claude CLI advisor; report to orchestrating session)
100
106
 
101
- > The orchestrating session is the shared advisor for this run there is no separate `session-advisor` agent and no SendMessage path to one. Report blockers and hard decisions to the **orchestrating session** (the session that assigned you) before locking in a nontrivial approach, once you believe your assignment is done, before any hard-to-reverse action, when the same failure repeats or progress has stalled, and when the chosen approach is being reconsidered. Open each report with who you are and your assignment, then: what you tried, the exact decision or blocker, and relevant paths or excerpts. Re-raise something already answered only when you have new evidence to attach — the result of trying prior advice, fresh output, or a changed constraint; otherwise act on the standing answer. After a CORRECTION or PLAN, your next report on that topic opens with what happened when you followed it. The orchestrating session answers with one of ENDORSE, CORRECTION, PLAN, or STOP — treat CORRECTION and PLAN as actions to take. On STOP, or if the orchestrating session is unreachable, stop work and surface that upward; do not spawn a `session-advisor` agent yourself.
107
+ > The orchestrating session owns a standing **Claude** advisor bound through the CLI Claude-chain (max tier: Fable high, then Opus max). There is no Agent-tool `session-advisor` and no SendMessage path to one. Report blockers and hard decisions to the **orchestrating session** (the session that assigned you) before locking in a nontrivial approach, once you believe your assignment is done, before any hard-to-reverse action, when the same failure repeats or progress has stalled, and when the chosen approach is being reconsidered. Open each report with who you are and your assignment, then: what you tried, the exact decision or blocker, and relevant paths or excerpts. Re-raise something already answered only when you have new evidence to attach — the result of trying prior advice, fresh output, or a changed constraint; otherwise act on the standing answer. After a CORRECTION or PLAN, your next report on that topic opens with what happened when you followed it. The orchestrating session consults the Claude CLI advisor and relays one of ENDORSE, CORRECTION, PLAN, or STOP — treat CORRECTION and PLAN as actions to take. On STOP, or if the orchestrating session reports the advisor unreachable, stop work and surface that upward; do not spawn a `session-advisor` agent yourself, and do not treat the Grok orchestrator's own judgment as an advisor signal.
102
108
 
103
109
  ## Lifecycle ownership
104
110
 
@@ -110,15 +116,17 @@ The session that spawns the shared advisor owns its whole lifecycle — spawn, d
110
116
 
111
117
  ### Grok host
112
118
 
113
- The orchestrating session owns the self-as-advisor role for the whole run. There is no Agent spawn to end or respawn.
119
+ The orchestrating session owns the Claude CLI advisor bind for the whole run first bind, re-bind on drift or lost `session_id`, and fail-closed report when the chain cannot serve.
120
+
121
+ **Re-bind on drift.** If a reply shows a stale picture, the task pivots, or `--resume` fails after a usage-limit failover (session stores are per binary/account), re-bind through `claude_chain_runner.py` with the charter plus a compact recap of consults so far, capture the new `session_id`, and log a fresh Fable→Opus walk with `result: "cli"` on success. Executors keep reporting to the orchestrating session; they never bind a replacement advisor themselves.
114
122
 
115
- **Re-charter on drift.** If the session's own answers show a stale picture, or the task pivots, re-state the self-as-advisor charter in-session (goal, four-signal contract, executor report path) and log another `{tier: "Grok", result: "self"}` bind only when a fresh structural log is needed. Executors keep reporting to the orchestrating session; they never spawn a replacement advisor.
123
+ ## CLI chain
116
124
 
117
- ## Fallback: the CLI chain
125
+ The shared runner is `python "$HOME/.claude/scripts/claude_chain_runner.py" -- <claude args...>`. It walks `~/.claude/claude-chain.json` and fails over to the next binary only on a usage-limit signature, so a usage-limited primary account still gets served.
118
126
 
119
- This section applies on a **Claude host** only. On a pure Grok host, skip it there is no Claude binary chain and the session is already the advisor (see **Host profiles**).
127
+ **Grok host:** this runner is the **primary** advisor bind and consult path (see **Host profiles → Grok host**). Map each walk attempt to `--model <alias>` and the effort flags there. When the walk exhausts, fail closed.
120
128
 
121
- Fall back to the CLI when any of these holds, rather than on judgment call:
129
+ **Claude host:** fall back to this runner when any of these holds, rather than on judgment call:
122
130
  - The Agent-tool spawn errors at every candidate tier down to the floor — the tool itself, not just the top tier, is unavailable.
123
131
  - `SendMessage` to the shared advisor errors, or draws no reply within the bound in `ADVISOR_SENDMESSAGE_REPLY_WAIT_SECONDS` (120) in `$HOME/.claude/_shared/advisor/scripts/config/advisor_scripts_constants/model_tier_run_validator_constants.py`, and a re-spawn also fails.
124
132
  - The running session is itself a subagent barred from spawning further agents.
@@ -131,11 +139,11 @@ Map `selected_tier` when one exists (the warm agent already bound above the floo
131
139
  | Opus | `opus` |
132
140
  | Sonnet | `sonnet` |
133
141
  | Haiku | `haiku` |
134
- | Grok (Grok host only; not part of the Claude walk) | `grok` |
142
+ | Grok (Grok session model field only; not an advisor walk tier) | `grok` |
135
143
 
136
- Resolve in code with `python -c "from tier_model_ids import resolve_cli_model_id; print(resolve_cli_model_id('Opus'))"` from `$HOME/.claude/_shared/advisor/scripts/` (any letter case accepted; unknown tiers raise `ValueError`). Use `python "$HOME/.claude/scripts/claude_chain_runner.py" -- -p --model <model alias> --output-format json` in place of the Agent-tool spawn. The chain runner walks the fallback chain configured at `~/.claude/claude-chain.json` (typically `claude` then `claude-ev`), so a usage-limited primary account still gets served. Write the charter or the consult brief to a temporary file under the job's own temporary directory (or the OS temp directory when no job directory exists) and pipe it in, rather than passing either as an inline argument, and drop that file once the consult completes.
144
+ Resolve in code with `python -c "from tier_model_ids import resolve_cli_model_id; print(resolve_cli_model_id('Opus'))"` from `$HOME/.claude/_shared/advisor/scripts/` (any letter case accepted; unknown tiers raise `ValueError`). Write the charter or the consult brief to a temporary file under the job's own temporary directory (or the OS temp directory when no job directory exists) and pipe it in, rather than passing either as an inline argument, and drop that file once the consult completes.
137
145
 
138
- Read the `session_id` out of the first call's JSON response and pass it to `-p --resume <session_id> --output-format json` on every later consult — `-p` stays on the resume call too, since it is still a non-interactive invocation. A usage-limit failover to the next binary in the chain does not carry the `session_id` forward: a session store belongs to the binary and account that minted it, so a `--resume` against the new binary can fail. Treat that failure as starting over, not as an error to retry — resend the charter plus a compact recap of the consults since the last one, capture the new `session_id` the fresh call returns, and continue the fallback path from there.
146
+ Read the `session_id` out of the first call's JSON events and pass it to `-p --resume <session_id> --output-format json` on every later consult — `-p` stays on the resume call too, since it is still a non-interactive invocation. A usage-limit failover to the next binary in the chain does not carry the `session_id` forward: a session store belongs to the binary and account that minted it, so a `--resume` against the new binary can fail. Treat that failure as starting over, not as an error to retry — resend the charter plus a compact recap of the consults since the last one, capture the new `session_id` the fresh call returns, and continue from there.
139
147
 
140
148
  ## Mechanism, not a measured saving
141
149
 
@@ -9,7 +9,7 @@ that mark a bind.
9
9
  attempt = {"tier": "Opus", "result": "spawned"}
10
10
  ^^^^^^ ^^^^^^^^^
11
11
  a ladder tier SPAWN_SUCCESS_TOKEN
12
- ok: "self" marks a Grok host self-bind (SELF_BIND_SUCCESS_TOKEN)
12
+ ok: "cli" marks a CLI Claude-chain bind (CLI_BIND_SUCCESS_TOKEN)
13
13
  flag: any other result token counts as no bind
14
14
 
15
15
  The alias map turns each tier into its short CLI / Agent name (``opus``,
@@ -31,6 +31,7 @@ ALL_HOST_PROFILES: tuple[str, ...] = (HOST_PROFILE_CLAUDE, HOST_PROFILE_GROK)
31
31
  ALL_MODEL_TIERS: tuple[str, ...] = ("Fable", "Opus", "Sonnet", "Haiku")
32
32
  GROK_MODEL_TIER: str = "Grok"
33
33
  ALL_KNOWN_TIER_NAMES: tuple[str, ...] = (*ALL_MODEL_TIERS, GROK_MODEL_TIER)
34
+ GROK_CLI_ADVISOR_FLOOR_TIER: str = "Opus"
34
35
 
35
36
  ADVISOR_SENDMESSAGE_REPLY_WAIT_SECONDS: int = 120
36
37
 
@@ -51,7 +52,7 @@ ALL_GROK_BUILD_TRUTHY_VALUES: frozenset[str] = frozenset(
51
52
  TIER_KEY: str = "tier"
52
53
  SPAWN_OUTCOME_KEY: str = "result"
53
54
  SPAWN_SUCCESS_TOKEN: str = "spawned"
54
- SELF_BIND_SUCCESS_TOKEN: str = "self"
55
+ CLI_BIND_SUCCESS_TOKEN: str = "cli"
55
56
 
56
57
  UNKNOWN_OWN_TIER_MESSAGE: str = "own_tier is not a known model tier"
57
58
  UNKNOWN_LADDER_NAME_ERROR: str = "ladder name is not a known model tier: {!r}"
@@ -17,13 +17,13 @@ from data, not inferred from a transcript.
17
17
  )
18
18
  validate_model_tier_run(ladder_walk) # ok: returns None, raises nothing
19
19
 
20
- self_bind = ModelTierRun(
21
- own_tier="Grok",
22
- candidate_tiers=["Grok"],
23
- attempts=[{"tier": "Grok", "result": "self"}],
24
- selected_tier="Grok",
20
+ cli_bind = ModelTierRun(
21
+ own_tier="Opus",
22
+ candidate_tiers=["Fable", "Opus"],
23
+ attempts=[{"tier": "Fable", "result": "cli"}],
24
+ selected_tier="Fable",
25
25
  )
26
- validate_model_tier_run(self_bind) # ok: host self-as-advisor bind
26
+ validate_model_tier_run(cli_bind) # ok: Grok-host CLI Claude-chain bind
27
27
 
28
28
  A run whose selected_tier is not the first successful bind fails.
29
29
  On any broken invariant, validate_model_tier_run raises ModelTierRunError.
@@ -52,17 +52,18 @@ from advisor_scripts_constants.model_tier_run_validator_constants import ( # no
52
52
  ATTEMPT_ORDER_MISMATCH_MESSAGE,
53
53
  ATTEMPT_TIER_OUT_OF_SLICE_MESSAGE,
54
54
  CANDIDATE_TIERS_MISMATCH_MESSAGE,
55
+ CLI_BIND_SUCCESS_TOKEN,
55
56
  CLI_INVALID_JSON_EXIT_CODE,
56
57
  CLI_MISSING_PATH_EXIT_CODE,
57
58
  CLI_SUCCESS_EXIT_CODE,
58
59
  CLI_USAGE_MESSAGE,
59
60
  CLI_VALIDATION_FAILURE_EXIT_CODE,
61
+ GROK_CLI_ADVISOR_FLOOR_TIER,
60
62
  GROK_MODEL_TIER,
61
63
  INCOMPLETE_FALLBACK_WALK_MESSAGE,
62
64
  MISSING_FALLBACK_REASON_MESSAGE,
63
65
  SELECTED_TIER_MISMATCH_MESSAGE,
64
66
  SELECTED_TIER_NOT_NULL_MESSAGE,
65
- SELF_BIND_SUCCESS_TOKEN,
66
67
  SPAWN_OUTCOME_KEY,
67
68
  SPAWN_SUCCESS_TOKEN,
68
69
  TIER_KEY,
@@ -99,7 +100,8 @@ def _expected_candidate_tiers(own_tier: str) -> list[str]:
99
100
  if maybe_canonical_own_tier is None:
100
101
  raise ModelTierRunError(f"{UNKNOWN_OWN_TIER_MESSAGE}: {own_tier!r}")
101
102
  if maybe_canonical_own_tier == GROK_MODEL_TIER:
102
- return [GROK_MODEL_TIER]
103
+ floor_index = ALL_MODEL_TIERS.index(GROK_CLI_ADVISOR_FLOOR_TIER)
104
+ return list(ALL_MODEL_TIERS[: floor_index + 1])
103
105
  floor_index = ALL_MODEL_TIERS.index(maybe_canonical_own_tier)
104
106
  return list(ALL_MODEL_TIERS[: floor_index + 1])
105
107
 
@@ -109,8 +111,12 @@ def _is_successful_attempt_outcome(
109
111
  outcome_token: str,
110
112
  ) -> bool:
111
113
  if canonical_tier == GROK_MODEL_TIER:
112
- return outcome_token == SELF_BIND_SUCCESS_TOKEN
113
- return outcome_token == SPAWN_SUCCESS_TOKEN
114
+ return False
115
+ if outcome_token == SPAWN_SUCCESS_TOKEN:
116
+ return True
117
+ if outcome_token == CLI_BIND_SUCCESS_TOKEN:
118
+ return True
119
+ return False
114
120
 
115
121
 
116
122
  def validate_model_tier_run(run: ModelTierRun) -> None:
@@ -118,13 +124,15 @@ def validate_model_tier_run(run: ModelTierRun) -> None:
118
124
 
119
125
  ::
120
126
 
121
- validate_model_tier_run(ladder_walk) # ok: multi-tier ladder walk
122
- validate_model_tier_run(self_bind) # ok: single-tier self-bind
127
+ validate_model_tier_run(ladder_walk) # ok: multi-tier Agent walk
128
+ validate_model_tier_run(cli_bind) # ok: CLI Claude-chain bind
123
129
  validate_model_tier_run(broken_log) # flag: ModelTierRunError
124
130
 
125
- Candidate tiers must match the floor slice (or ``["Grok"]`` alone). Tries
126
- walk that slice in order; early stop only after ``spawned`` or ``self``.
127
- A null selected_tier requires a full walk plus fallback_reason.
131
+ Candidate tiers must match the floor slice. ``own_tier=Grok`` maps to the
132
+ Grok-host CLI advisor floor (Fable Opus). Tries walk that slice in order;
133
+ early stop only after ``spawned`` or ``cli``. A null selected_tier requires
134
+ a full walk plus fallback_reason (fail-closed on Grok when the chain
135
+ cannot serve).
128
136
 
129
137
  Args:
130
138
  run: The structured spawn-walk log to check.
@@ -238,53 +238,117 @@ def test_cli_missing_path_returns_usage_exit_code() -> None:
238
238
  assert main([]) == 2
239
239
 
240
240
 
241
- def test_grok_host_single_tier_self_bind_passes() -> None:
241
+ def test_cli_bind_at_fable_passes() -> None:
242
+ run = ModelTierRun(
243
+ own_tier="Opus",
244
+ candidate_tiers=["Fable", "Opus"],
245
+ attempts=[{"tier": "Fable", "result": "cli"}],
246
+ selected_tier="Fable",
247
+ )
248
+ assert validate_model_tier_run(run) is None
249
+
250
+
251
+ def test_cli_bind_fallthrough_to_opus_passes() -> None:
252
+ run = ModelTierRun(
253
+ own_tier="Opus",
254
+ candidate_tiers=["Fable", "Opus"],
255
+ attempts=[
256
+ {"tier": "Fable", "result": "unavailable"},
257
+ {"tier": "Opus", "result": "cli"},
258
+ ],
259
+ selected_tier="Opus",
260
+ )
261
+ assert validate_model_tier_run(run) is None
262
+
263
+
264
+ def test_grok_own_tier_maps_to_fable_opus_cli_bind_passes() -> None:
242
265
  run = ModelTierRun(
243
266
  own_tier="Grok",
244
- candidate_tiers=["Grok"],
245
- attempts=[{"tier": "Grok", "result": "self"}],
246
- selected_tier="Grok",
267
+ candidate_tiers=["Fable", "Opus"],
268
+ attempts=[{"tier": "Fable", "result": "cli"}],
269
+ selected_tier="Fable",
247
270
  )
248
271
  assert validate_model_tier_run(run) is None
249
272
 
250
273
 
251
- def test_grok_host_lowercase_self_bind_passes() -> None:
274
+ def test_grok_own_tier_lowercase_cli_bind_passes() -> None:
252
275
  run = ModelTierRun(
253
276
  own_tier="grok",
254
- candidate_tiers=["grok"],
255
- attempts=[{"tier": "grok", "result": "self"}],
256
- selected_tier="grok",
277
+ candidate_tiers=["fable", "opus"],
278
+ attempts=[{"tier": "fable", "result": "cli"}],
279
+ selected_tier="fable",
257
280
  )
258
281
  assert validate_model_tier_run(run) is None
259
282
 
260
283
 
261
- def test_grok_host_multi_tier_candidate_list_raises() -> None:
284
+ def test_self_token_is_not_bind_success_raises() -> None:
285
+ run = ModelTierRun(
286
+ own_tier="Opus",
287
+ candidate_tiers=["Fable", "Opus"],
288
+ attempts=[{"tier": "Fable", "result": "self"}],
289
+ selected_tier="Fable",
290
+ )
291
+ with pytest.raises(ModelTierRunError):
292
+ validate_model_tier_run(run)
293
+
294
+
295
+ def test_grok_self_token_is_not_bind_success_raises() -> None:
262
296
  run = ModelTierRun(
263
297
  own_tier="Grok",
264
- candidate_tiers=["Fable", "Opus", "Grok"],
265
- attempts=[{"tier": "Grok", "result": "self"}],
266
- selected_tier="Grok",
298
+ candidate_tiers=["Fable", "Opus"],
299
+ attempts=[{"tier": "Fable", "result": "self"}],
300
+ selected_tier="Fable",
267
301
  )
268
302
  with pytest.raises(ModelTierRunError):
269
303
  validate_model_tier_run(run)
270
304
 
271
305
 
272
- def test_grok_host_spawned_token_is_not_self_bind_raises() -> None:
306
+ def test_grok_host_legacy_single_tier_self_bind_raises() -> None:
273
307
  run = ModelTierRun(
274
308
  own_tier="Grok",
275
309
  candidate_tiers=["Grok"],
276
- attempts=[{"tier": "Grok", "result": "spawned"}],
310
+ attempts=[{"tier": "Grok", "result": "self"}],
277
311
  selected_tier="Grok",
278
312
  )
279
313
  with pytest.raises(ModelTierRunError):
280
314
  validate_model_tier_run(run)
281
315
 
282
316
 
283
- def test_grok_host_selected_tier_mismatch_raises() -> None:
317
+ def test_grok_cli_exhausted_fail_closed_passes() -> None:
284
318
  run = ModelTierRun(
285
319
  own_tier="Grok",
286
- candidate_tiers=["Grok"],
287
- attempts=[{"tier": "Grok", "result": "self"}],
320
+ candidate_tiers=["Fable", "Opus"],
321
+ attempts=[
322
+ {"tier": "Fable", "result": "unavailable"},
323
+ {"tier": "Opus", "result": "unavailable"},
324
+ ],
325
+ selected_tier=None,
326
+ fallback_reason=(
327
+ "Grok host CLI Claude-chain exhausted; fail closed"
328
+ ),
329
+ )
330
+ assert validate_model_tier_run(run) is None
331
+
332
+
333
+ def test_grok_cli_exhausted_without_fallback_reason_raises() -> None:
334
+ run = ModelTierRun(
335
+ own_tier="Grok",
336
+ candidate_tiers=["Fable", "Opus"],
337
+ attempts=[
338
+ {"tier": "Fable", "result": "unavailable"},
339
+ {"tier": "Opus", "result": "unavailable"},
340
+ ],
341
+ selected_tier=None,
342
+ )
343
+ with pytest.raises(ModelTierRunError):
344
+ validate_model_tier_run(run)
345
+
346
+
347
+ def test_grok_cli_selected_tier_mismatch_raises() -> None:
348
+ run = ModelTierRun(
349
+ own_tier="Grok",
350
+ candidate_tiers=["Fable", "Opus"],
351
+ attempts=[{"tier": "Fable", "result": "cli"}],
288
352
  selected_tier="Opus",
289
353
  )
290
354
  with pytest.raises(ModelTierRunError):
@@ -8,6 +8,7 @@ Runtime documents and scripts shared by every PR-loop skill. Changes here affect
8
8
  |---|---|
9
9
  | `audit-contract.md` | Canonical finding schema (Shape A / Shape B) and loop contract; defines the JSON shapes every audit skill must emit |
10
10
  | `audit-reply-template.md` | Canonical reply skeleton Claude posts to each unresolved review thread; single source of truth for reply structure |
11
+ | `post-audit-thread-contract.md` | Single source of truth for the `post_audit_thread.py` invocation string, exit-code table, and per-caller policy (bugteam exit 2 = hard blocker; autoconverge clean-audit failed post = recorded bypass) |
11
12
  | `fix-protocol.md` | Ordered sequence a fix lens follows: read, capture SHA, TDD, apply, validate, self-audit, commit, push, reply + resolve |
12
13
  | `gh-payloads.md` | How to build GitHub review and reply payloads via MCP tools; describes the one-review-per-loop pattern |
13
14
  | `state-schema.md` | Fields each PR-loop workflow tracks across iterations; documents common fields and per-skill extensions |
@@ -120,7 +120,7 @@ Sequence:
120
120
  2. Apply edits.
121
121
  3. Run `py_compile` (or language-equivalent) on each modified file.
122
122
  4. Compute `fix_diff` against pre-fix contents for the modified set.
123
- 5. Run `bugteam_code_rules_gate.py` with explicit paths for every modified file.
123
+ 5. Run `_shared/pr-loop/scripts/code_rules_gate.py` with explicit paths for every modified file.
124
124
  6. Spawn a scoped audit of `fix_diff` with full A–P rigor, Shape A/B contract, adversarial pass, AND Haiku secondary in parallel (paranoid mode on post-fix).
125
125
  7. Read the previous loop's outcome XML (`<worktree_path>/.bugteam-pr<N>-loop<L-1>.outcomes.xml`) and obtain its total finding count. If this is the first loop (L <= 1) or the file does not exist, skip this comparison. Compute the post-fix total: previous total minus bugs fixed in this round plus new violations found in the post-fix audit (step 6). If the post-fix total exceeds the previous total, flag all new findings as same-loop fix-targets and revise. An increase in total findings across loop transitions is a regression.
126
126
  8. Any new findings become same-loop fix-targets. Internal iteration count increments by one.
@@ -55,9 +55,9 @@ user-facing output (chat reply to the user) rather than in the PR review body.
55
55
 
56
56
  ## Review POST failure
57
57
 
58
- There is no fallback path. `post_audit_thread.py` exits `2` on retry
59
- exhaustion (four non-2xx responses across the built-in `1s / 4s / 16s` backoff)
60
- and the orchestrator halts with `error: post_audit_thread retry exhausted`.
58
+ There is no fallback payload to build. On retry exhaustion
59
+ `post_audit_thread.py` exits `2` and the orchestrator halts see
60
+ [`./post-audit-thread-contract.md`](./post-audit-thread-contract.md) § Exit codes.
61
61
  A hard blocker on the audit-posting path is a halt condition, not a degraded
62
62
  flat-issue-comment fall-through.
63
63
 
@@ -0,0 +1,51 @@
1
+ # post_audit_thread.py invocation contract
2
+
3
+ Single source of truth for how every PR-loop caller runs
4
+ [`scripts/post_audit_thread.py`](scripts/post_audit_thread.py) to post one
5
+ audit review per pass. The script posts an `APPROVE` review on `CLEAN` (an
6
+ empty findings list, zero inline comments) and a `REQUEST_CHANGES` review on
7
+ `DIRTY` (one inline anchored comment per finding). It reads its body skeleton
8
+ from [`audit-reply-template.md`](audit-reply-template.md) at runtime.
9
+
10
+ ## Invocation
11
+
12
+ ```
13
+ python "<pr-loop-scripts>/post_audit_thread.py" \
14
+ --skill <caller> \
15
+ --owner <owner> \
16
+ --repo <repo> \
17
+ --pr-number <N> \
18
+ --commit <head_sha> \
19
+ --state <CLEAN|DIRTY> \
20
+ --findings-json <path>
21
+ ```
22
+
23
+ Capture `<head_sha>` with `git rev-parse HEAD` in the caller's cwd right before
24
+ the call, so the review attaches to the commit the audit scoped. `--findings-json`
25
+ points to a JSON file whose root is a list; on `CLEAN` the list is empty (`[]`).
26
+
27
+ ## Exit codes
28
+
29
+ The script retries on its own with 1s / 4s / 16s backoff across four tries (one
30
+ plus three retries). It then exits:
31
+
32
+ | Exit | Meaning |
33
+ |---|---|
34
+ | `0` | Review posted. The new review's `html_url` is on stdout. |
35
+ | `1` | User input error: bad arguments, malformed findings JSON, or a missing template. |
36
+ | `2` | Retry exhaustion: every try failed. |
37
+
38
+ ## Per-caller policy
39
+
40
+ The same exit codes carry different weight per caller:
41
+
42
+ - **bugteam** — an exit `2` is a hard blocker. The lead exits with
43
+ `error: post_audit_thread retry exhausted`, and does not retry or fall back to
44
+ a flat issue comment.
45
+ - **autoconverge clean-audit** — any post that does not land (a denied post, an
46
+ error, or an agent that never ran) is a recorded bypass, not a blocker. Every
47
+ review lens already cleared the HEAD, so the CLEAN post is a record-keeping
48
+ artifact. The run records a bypass note, sets the convergence check's
49
+ `--bugteam-post-blocked` flag, and proceeds to the terminal Bugbot gate. Full
50
+ policy: [`../../skills/autoconverge/reference/stop-conditions.md`](../../skills/autoconverge/reference/stop-conditions.md)
51
+ § "Clean-audit post bypassed".
@@ -9,11 +9,11 @@ Runnable helpers used by **bugteam**, **qbug**, **pr-converge**, and related ski
9
9
  | `preflight.py` | Local checks before a PR-loop run (pytest discovery, optional pre-commit, hooksPath sanity). |
10
10
  | `reviewer_availability.py` | Unified availability check for Copilot and Bugbot; reuses `copilot_quota.py` and `reviews_disabled.py` and exits 0 when the named `--reviewer` may be spawned. |
11
11
  | `code_rules_gate.py` | CODE_RULES gate over PR-scoped diffs (`--base`, staged-only, path filters). |
12
- | `fix_hookspath.py` | Repair `core.hooksPath` when it does not point at the packaged git-hooks tree. |
12
+ | `fix_hookspath.py` | Repair `core.hooksPath` when it does not point at the packaged git-hooks tree. Single home for unexpected non-empty git stderr (hard-fail `RuntimeError`); skill wrappers may call it later. |
13
13
  | `grant_project_claude_permissions.py` / `revoke_project_claude_permissions.py` | Claude Code permission JSON helpers used during publish-style flows. |
14
14
  | `_claude_permissions_common.py` | Shared implementation for the permission scripts. |
15
15
 
16
- Configuration lives under `config/` next to these scripts (for example `preflight_constants.py`, `code_rules_gate_constants.py`).
16
+ Configuration lives under `pr_loop_shared_constants/` next to these scripts (for example `preflight_constants.py`, `code_rules_gate_constants.py`, `fix_hookspath_constants.py`).
17
17
 
18
18
  ## Gate semantics
19
19
 
@@ -53,6 +53,9 @@ def list_local_core_hooks_path_values(
53
53
  Returns:
54
54
  Non-empty stripped values from ``git config --local --get-all``, or
55
55
  an empty list when no values are configured.
56
+
57
+ Raises:
58
+ RuntimeError: When git exits non-zero with a non-empty stderr diagnostic.
56
59
  """
57
60
  git_command = [
58
61
  "git",
@@ -75,11 +78,9 @@ def list_local_core_hooks_path_values(
75
78
  if completed_process.returncode != 0:
76
79
  diagnostic_stderr = completed_process.stderr.strip()
77
80
  if diagnostic_stderr:
78
- print(
79
- "fix_hookspath: git read of local core.hooksPath on "
80
- f"{repository_root} exited {completed_process.returncode}: "
81
- f"{diagnostic_stderr}",
82
- file=sys.stderr,
81
+ raise RuntimeError(
82
+ f"git read of local core.hooksPath on {repository_root} "
83
+ f"exited {completed_process.returncode}: {diagnostic_stderr}"
83
84
  )
84
85
  return []
85
86
  return [
@@ -99,8 +100,11 @@ def read_global_core_hooks_path(
99
100
  forwarded to ``subprocess.run``.
100
101
 
101
102
  Returns:
102
- The stripped global value, or an empty string when unset or when git
103
- returns non-zero.
103
+ The stripped global value, or an empty string when the key is unset
104
+ (git exits non-zero with empty stderr).
105
+
106
+ Raises:
107
+ RuntimeError: When git exits non-zero with a non-empty stderr diagnostic.
104
108
  """
105
109
  git_command = list(ALL_GIT_GLOBAL_GET_CORE_HOOKS_PATH_COMMAND)
106
110
  completed_process = subprocess.run(
@@ -115,10 +119,9 @@ def read_global_core_hooks_path(
115
119
  if completed_process.returncode != 0:
116
120
  diagnostic_stderr = completed_process.stderr.strip()
117
121
  if diagnostic_stderr:
118
- print(
119
- "fix_hookspath: git read of global core.hooksPath exited "
120
- f"{completed_process.returncode}: {diagnostic_stderr}",
121
- file=sys.stderr,
122
+ raise RuntimeError(
123
+ f"git read of global core.hooksPath exited "
124
+ f"{completed_process.returncode}: {diagnostic_stderr}"
122
125
  )
123
126
  return ""
124
127
  return completed_process.stdout.strip()
@@ -280,7 +283,8 @@ def main(
280
283
  forwarded to every git invocation and to the preflight rerun.
281
284
 
282
285
  Returns:
283
- Zero on success. Non-zero on the first failing git command or on a
286
+ Zero on success. Non-zero on the first failing git command, on a
287
+ hard-fail git stderr diagnostic (caught ``RuntimeError``), or on a
284
288
  non-zero preflight rerun exit code.
285
289
  """
286
290
  arguments = parse_arguments(all_arguments)
@@ -302,6 +306,37 @@ def main(
302
306
  file=sys.stderr,
303
307
  )
304
308
  return 1
309
+ try:
310
+ return _repair_hooks_path_then_rerun_preflight(
311
+ repository_root=repository_root,
312
+ canonical_hooks_directory=canonical_hooks_directory,
313
+ all_environment_overrides=all_environment_overrides,
314
+ )
315
+ except RuntimeError as runtime_error:
316
+ print(f"fix_hookspath: {runtime_error}", file=sys.stderr)
317
+ return 1
318
+
319
+
320
+ def _repair_hooks_path_then_rerun_preflight(
321
+ repository_root: Path,
322
+ canonical_hooks_directory: Path,
323
+ all_environment_overrides: dict[str, str] | None,
324
+ ) -> int:
325
+ """Apply local/global hooksPath repairs, then re-run preflight.
326
+
327
+ Args:
328
+ repository_root: Repository root used for local git config and preflight.
329
+ canonical_hooks_directory: Absolute path to the packaged git-hooks tree.
330
+ all_environment_overrides: Optional environment variable mapping
331
+ forwarded to every git invocation and to the preflight rerun.
332
+
333
+ Returns:
334
+ Zero on success. Non-zero on the first failing git write or on a
335
+ non-zero preflight rerun exit code.
336
+
337
+ Raises:
338
+ RuntimeError: When a git read exits non-zero with non-empty stderr.
339
+ """
305
340
  local_hooks_path_values = list_local_core_hooks_path_values(
306
341
  repository_root,
307
342
  all_environment_overrides,