claude-dev-env 1.94.0 → 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.
- package/_shared/advisor/CLAUDE.md +2 -2
- package/_shared/advisor/advisor-protocol.md +35 -27
- package/_shared/advisor/scripts/config/advisor_scripts_constants/model_tier_run_validator_constants.py +3 -2
- package/_shared/advisor/scripts/model_tier_run_validator.py +23 -15
- package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +81 -17
- package/bin/CLAUDE.md +10 -1
- package/bin/ever-shipped-skills.mjs +70 -0
- package/bin/install.mjs +136 -6
- package/bin/install.prune.test.mjs +457 -0
- package/docs/CODE_RULES.md +1 -1
- package/hooks/blocking/code_rules_enforcer.py +4 -0
- package/hooks/blocking/code_rules_shared.py +82 -0
- package/hooks/blocking/code_rules_test_layout.py +9 -3
- package/hooks/blocking/plain_language_blocker.py +138 -4
- package/hooks/blocking/sensitive_file_protector.py +114 -48
- package/hooks/blocking/tdd_enforcer.py +9 -2
- package/hooks/blocking/test_code_rules_enforcer_scratchpad.py +105 -0
- package/hooks/blocking/test_code_rules_shared.py +181 -0
- package/hooks/blocking/test_plain_language_blocker_allowlist.py +184 -0
- package/hooks/blocking/test_sensitive_file_protector.py +185 -0
- package/hooks/blocking/test_tdd_enforcer_scratchpad.py +105 -0
- package/hooks/hooks_constants/CLAUDE.md +2 -0
- package/hooks/hooks_constants/harness_scratchpad_constants.py +17 -0
- package/hooks/hooks_constants/plain_language_blocker_constants.py +5 -0
- package/hooks/hooks_constants/sensitive_file_protector_constants.py +42 -0
- package/hooks/pyproject.toml +75 -4
- package/hooks/validators/CLAUDE.md +1 -1
- package/hooks/validators/README.md +2 -0
- package/hooks/validators/python_style_checks.py +114 -136
- package/hooks/validators/python_style_helpers.py +95 -0
- package/hooks/validators/test_python_style_checks.py +0 -164
- package/hooks/validators/test_python_style_checks_decorator_gap.py +119 -0
- package/hooks/validators/test_python_style_fixes.py +251 -0
- package/hooks/validators/test_python_style_helpers.py +125 -0
- package/package.json +1 -1
- package/rules/CLAUDE.md +1 -0
- package/rules/anti-corollary-tests.md +69 -0
- package/rules/bdd.md +1 -3
- package/rules/code-reviews.md +1 -1
- package/rules/gh-paginate.md +1 -1
- package/rules/plain-language.md +2 -0
- package/skills/CLAUDE.md +4 -3
- package/skills/autoconverge/workflow/converge.mjs +2 -2
- package/skills/bugteam/reference/README.md +2 -3
- package/skills/closeout/SKILL.md +153 -0
- package/skills/closeout/reference/handoff-prompt-template.md +72 -0
- package/skills/closeout/reference/issue-body-templates.md +108 -0
- package/skills/closeout/reference/pii-redaction-checklist.md +36 -0
- package/skills/orchestrator/SKILL.md +27 -21
- package/skills/orchestrator-refresh/SKILL.md +12 -8
- package/skills/pr-converge/CLAUDE.md +1 -1
- package/skills/pr-fix-protocol/SKILL.md +65 -0
- package/skills/skill-builder/references/skill-modularity.md +1 -1
- package/skills/team-advisor/SKILL.md +15 -11
- package/system-prompts/software-engineer.xml +7 -6
- package/hooks/validators/test_verify_paths.py +0 -32
- package/hooks/validators/verify_paths.py +0 -57
|
@@ -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
|
|
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,
|
|
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
|
|
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
|
|
27
|
-
3. **
|
|
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
|
-
|
|
29
|
+
```
|
|
30
|
+
python "$HOME/.claude/scripts/claude_chain_runner.py" -- -p --model <alias> --effort <effort> --output-format json
|
|
31
|
+
```
|
|
33
32
|
|
|
34
|
-
|
|
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
|
-
|
|
40
|
+
## Model floor
|
|
37
41
|
|
|
38
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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).
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 (
|
|
105
|
+
### Grok host (Claude CLI advisor; report to orchestrating session)
|
|
100
106
|
|
|
101
|
-
> The orchestrating session
|
|
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
|
|
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
|
-
|
|
123
|
+
## CLI chain
|
|
116
124
|
|
|
117
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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`).
|
|
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
|
|
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: "
|
|
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
|
-
|
|
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
|
-
|
|
21
|
-
own_tier="
|
|
22
|
-
candidate_tiers=["
|
|
23
|
-
attempts=[{"tier": "
|
|
24
|
-
selected_tier="
|
|
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(
|
|
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
|
-
|
|
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
|
|
113
|
-
|
|
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
|
|
122
|
-
validate_model_tier_run(
|
|
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
|
|
126
|
-
|
|
127
|
-
|
|
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
|
|
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=["
|
|
245
|
-
attempts=[{"tier": "
|
|
246
|
-
selected_tier="
|
|
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
|
|
274
|
+
def test_grok_own_tier_lowercase_cli_bind_passes() -> None:
|
|
252
275
|
run = ModelTierRun(
|
|
253
276
|
own_tier="grok",
|
|
254
|
-
candidate_tiers=["
|
|
255
|
-
attempts=[{"tier": "
|
|
256
|
-
selected_tier="
|
|
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
|
|
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"
|
|
265
|
-
attempts=[{"tier": "
|
|
266
|
-
selected_tier="
|
|
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
|
|
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": "
|
|
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
|
|
317
|
+
def test_grok_cli_exhausted_fail_closed_passes() -> None:
|
|
284
318
|
run = ModelTierRun(
|
|
285
319
|
own_tier="Grok",
|
|
286
|
-
candidate_tiers=["
|
|
287
|
-
attempts=[
|
|
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):
|
package/bin/CLAUDE.md
CHANGED
|
@@ -6,7 +6,8 @@ The installer and its companion modules. Running `npx claude-dev-env` (or `node
|
|
|
6
6
|
|
|
7
7
|
| File | Purpose |
|
|
8
8
|
|---|---|
|
|
9
|
-
| `install.mjs` | Main installer: discovers install groups, copies content directories (`rules`, `docs`, `commands`, `agents`, `system-prompts`, `scripts`, `_shared`, `audit-rubrics`), merges hooks into `settings.json`, installs skills, runs `git_hooks_installer.mjs` and `install_mypy_ini.mjs` |
|
|
9
|
+
| `install.mjs` | Main installer: discovers install groups, copies content directories (`rules`, `docs`, `commands`, `agents`, `system-prompts`, `scripts`, `_shared`, `audit-rubrics`), merges hooks into `settings.json`, installs skills, prunes retired skills on a full install, runs `git_hooks_installer.mjs` and `install_mypy_ini.mjs` |
|
|
10
|
+
| `ever-shipped-skills.mjs` | Static `EVER_SHIPPED_SKILL_NAMES` set of every top-level skill directory name the package has shipped; the installer subtracts the current skill set from it to prune retired skills left under `~/.claude/skills` |
|
|
10
11
|
| `expand_home_directory_tokens.mjs` | Expands residual `$HOME` / `${HOME}` / `~/` tokens in settings.json hook and statusLine commands to absolute home paths at install time (literal-safe for homes that contain `$`) |
|
|
11
12
|
| `git_hooks_installer.mjs` | Installs or updates the `pre-commit`, `pre-push`, and `post-commit` Git hooks in the user's git config; writes hook scripts that delegate to the installed Python hooks |
|
|
12
13
|
| `install_mypy_ini.mjs` | Writes `~/.mypy.ini` with settings that make mypy find the hooks package and enforce strict type checking |
|
|
@@ -14,6 +15,14 @@ The installer and its companion modules. Running `npx claude-dev-env` (or `node
|
|
|
14
15
|
| `git_hooks_installer.test.mjs` | Tests for `git_hooks_installer.mjs` |
|
|
15
16
|
| `install_mypy_ini.test.mjs` | Tests for `install_mypy_ini.mjs` |
|
|
16
17
|
|
|
18
|
+
## Retired-skill prune
|
|
19
|
+
|
|
20
|
+
The full-install prune renames a retired skill directory into a timestamped backup rather than deleting it. Each pruned directory is renamed to `~/.claude/.claude-dev-env-pruned/<timestamp>/<skill-name>/`, a backup root outside `~/.claude/skills` so a backed-up directory is never re-discovered as a skill. Backups accumulate — nothing cleans them — so a user can recover a directory. A rename that fails leaves the directory in place with a logged warning and never falls back to deletion, so a prune failure costs at most a cosmetic leftover.
|
|
21
|
+
|
|
22
|
+
Matching is by directory name alone, so a user-authored directory whose name collides with a retired skill is backed up as if it were that skill. A directory is pruned when the prior install's manifest recorded it or the ever-shipped set names it, and the current install did not just write it. A name absent from all three of those sets, together with `~/.claude/skills/_shared`, is left in place.
|
|
23
|
+
|
|
24
|
+
The prune is skipped for the whole run — with a logged notice naming the unresolved group — when any declared dependency group fails to resolve. An unresolved dependency contributes no skills to the installed set, so a live skill that a dependency package supplies would look retired; holding the prune until every dependency resolves keeps such a skill from being backed up.
|
|
25
|
+
|
|
17
26
|
## Key exports from install.mjs
|
|
18
27
|
|
|
19
28
|
| Export | Description |
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The top-level skill directory names this package has ever shipped.
|
|
3
|
+
*
|
|
4
|
+
* The installer subtracts the skills the current package installs from this set
|
|
5
|
+
* to learn which shipped skills have retired, so a full reinstall can remove a
|
|
6
|
+
* retired skill left behind under ~/.claude/skills. Because the current set is
|
|
7
|
+
* subtracted at runtime, restoring a retired skill to the package protects it
|
|
8
|
+
* automatically — it re-enters the installed set and drops out of the retired
|
|
9
|
+
* set on the next install.
|
|
10
|
+
*
|
|
11
|
+
* The list is a committed literal rather than a git query, because a user
|
|
12
|
+
* installs from the npm tarball, which carries no source-repo history. Refresh
|
|
13
|
+
* it by running, from the repository root:
|
|
14
|
+
*
|
|
15
|
+
* git log --all --pretty=format: --name-only -- 'packages/claude-dev-env/skills/*\/SKILL.md' | sort -u
|
|
16
|
+
*
|
|
17
|
+
* and pasting each distinct top-level skill directory name below.
|
|
18
|
+
*/
|
|
19
|
+
export const EVER_SHIPPED_SKILL_NAMES = new Set([
|
|
20
|
+
'anthropic-plan',
|
|
21
|
+
'auditing-claude-config',
|
|
22
|
+
'autoconverge',
|
|
23
|
+
'bdd-protocol',
|
|
24
|
+
'bg-agent',
|
|
25
|
+
'bugteam',
|
|
26
|
+
'caveman',
|
|
27
|
+
'code',
|
|
28
|
+
'copilot-finding-triage',
|
|
29
|
+
'copilot-review',
|
|
30
|
+
'deep-research',
|
|
31
|
+
'everything-search',
|
|
32
|
+
'findbugs',
|
|
33
|
+
'fixbugs',
|
|
34
|
+
'fresh-branch',
|
|
35
|
+
'gh-paginate',
|
|
36
|
+
'gotcha',
|
|
37
|
+
'grokify',
|
|
38
|
+
'implement',
|
|
39
|
+
'log-audit',
|
|
40
|
+
'logifix',
|
|
41
|
+
'monitor-open-prs',
|
|
42
|
+
'orchestrator',
|
|
43
|
+
'orchestrator-refresh',
|
|
44
|
+
'post-audit-findings',
|
|
45
|
+
'pr-consistency-audit',
|
|
46
|
+
'pr-converge',
|
|
47
|
+
'pr-fix-protocol',
|
|
48
|
+
'pr-loop-cloud-transport',
|
|
49
|
+
'pr-loop-lifecycle',
|
|
50
|
+
'pr-review-responder',
|
|
51
|
+
'pr-scope-resolve',
|
|
52
|
+
'pre-compact',
|
|
53
|
+
'privacy-hygiene',
|
|
54
|
+
'qbug',
|
|
55
|
+
'rebase',
|
|
56
|
+
'recall',
|
|
57
|
+
'refine',
|
|
58
|
+
'remember',
|
|
59
|
+
'research-mode',
|
|
60
|
+
'reviewer-gates',
|
|
61
|
+
'session-log',
|
|
62
|
+
'session-tidy',
|
|
63
|
+
'skill-builder',
|
|
64
|
+
'structure-prompt',
|
|
65
|
+
'task-build',
|
|
66
|
+
'team-advisor',
|
|
67
|
+
'update',
|
|
68
|
+
'usage-pause',
|
|
69
|
+
'verified-build',
|
|
70
|
+
]);
|