Skip to content

Commit 3b203c1

Browse files
committed
chore(wheelhouse): cascade template@71c60ade
1 parent 9ee53db commit 3b203c1

10 files changed

Lines changed: 640 additions & 22 deletions

File tree

.claude/skills/fleet/_shared/multi-agent-backends.md

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,32 @@ Document skips inline in whatever output the skill produces (`> Skipped pass: <r
3737

3838
## Env-var conventions
3939

40-
| Var | Default | Purpose |
41-
| ----------------- | ------------- | ---------------------------------------------- |
42-
| `CODEX_MODEL` | `gpt-5.4` | Codex model when codex is the active backend |
43-
| `CODEX_REASONING` | `xhigh` | Codex reasoning effort |
44-
| `CLAUDE_MODEL` | `opus` | Claude model when claude is the active backend |
45-
| `KIMI_MODEL` | `kimi-latest` | Kimi model when kimi is the active backend |
40+
| Var | Default | Purpose |
41+
| ----------------- | ------------- | ------------------------------------------------ |
42+
| `CLAUDE_EFFORT` | `high` | Claude reasoning effort (claude `--effort`) |
43+
| `CLAUDE_MODEL` | `opus` | Claude model when claude is the active backend |
44+
| `CODEX_MODEL` | `gpt-5.4` | Codex model when codex is the active backend |
45+
| `CODEX_REASONING` | `xhigh` | Codex reasoning effort |
46+
| `KIMI_MODEL` | `kimi-latest` | Kimi model when kimi is the active backend |
47+
48+
Pair model with effort, never just model: a cheap model left on the session's default effort still burns reasoning tokens, and a premium model on `low` underthinks. Both codex (`CODEX_REASONING`) and claude (`CLAUDE_EFFORT`) carry an effort knob — set both axes when a backend supports it. Kimi has no effort flag, so it inherits its CLI default.
4649

4750
Don't invent per-skill env var names — reuse these. Skills that need a non-default model for a specific run accept a `--model` flag rather than introducing new env vars.
4851

52+
## Effort-capability matrix
53+
54+
Reasoning effort is NOT one flat vocabulary across backends — only map an effort onto a backend that actually accepts that level, or you'll pass an invalid value. The lib's `spawnAiAgent` translates the shared `AiEffort` (`@socketsecurity/lib/ai/types`) per-agent; this table is the source of truth for what each accepts.
55+
56+
| Backend | Effort flag | Accepted levels | `max` handling |
57+
| -------- | ---------------------------------- | ------------------------------------- | ----------------------- |
58+
| claude | `--effort <level>` | low / medium / high / xhigh / max | passes through |
59+
| codex | `-c model_reasoning_effort=<level>`| minimal / low / medium / high / xhigh | clamped to `xhigh` |
60+
| gemini | (none) || ignored |
61+
| kimi | (none) || ignored |
62+
| opencode | (none — provider-internal) || ignored |
63+
64+
`AiEffort` = `low | medium | high | xhigh | max`. `minimal` is codex-only and outside `AiEffort`; `max` is claude-only, so `buildArgs` clamps it to codex's `xhigh` ceiling. A backend with no effort flag silently ignores the value — never gate behavior on a backend honoring effort it doesn't support. When you hand-roll a backend runner (not via `spawnAiAgent`), pick the effort default from this table's vocab for that backend, not a flat constant.
65+
4966
## Canonical implementation
5067

5168
`.claude/skills/reviewing-code/run.mts` is the reference implementation. New skills that need multi-agent delegation should import the same registry shape and detection function (or copy the small block until extraction is worth doing) — don't roll a parallel pattern.

.claude/skills/fleet/reviewing-code/run.mts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ const BACKENDS: Readonly<Record<BackendName, BackendDescriptor>> = {
7575
name: 'claude',
7676
run(_promptFile, _outFile) {
7777
const model = process.env['CLAUDE_MODEL'] ?? 'opus'
78+
// Pair the model with a reasoning effort (claude `--effort`) — see
79+
// _shared/multi-agent-backends.md. Review is judgment-heavy, so the
80+
// default is `high`; codex's sibling knob is CODEX_REASONING.
81+
const effort = process.env['CLAUDE_EFFORT'] ?? 'high'
7882
// Programmatic-Claude lockdown — all four flags per CLAUDE.md
7983
// (tools / allowedTools / disallowedTools / permission-mode).
8084
// The official permission flow is hooks → deny → mode → allow →
@@ -89,6 +93,8 @@ const BACKENDS: Readonly<Record<BackendName, BackendDescriptor>> = {
8993
'--print',
9094
'--model',
9195
model,
96+
'--effort',
97+
effort,
9298
'--no-session-persistence',
9399
'--permission-mode',
94100
'dontAsk',

pnpm-workspace.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@ minimumReleaseAgeExclude:
9090
- '@ultrathink/*'
9191
- '@yuku-parser/*'
9292
- '@socketoverride/*'
93-
- 'shell-quote'
93+
# published: 2026-05-22 | removable: 2026-05-29
94+
- 'shell-quote@1.8.4'
9495

9596
# Refuse transitive dependencies declared via git/tarball/local-tarball
9697
# specs — an npm package shouldn't be allowed to drag in a git URL we

scripts/fleet/ai-lint-fix.mts

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,11 @@ import { getDefaultLogger } from '@socketsecurity/lib-stable/logger/default'
4242
import { hasClaudeCli, runClaudeFix } from './ai-lint-fix/claude.mts'
4343
import { runLintJson } from './ai-lint-fix/oxlint-json.mts'
4444
import { bucketFindings, buildPrompt } from './ai-lint-fix/prompt.mts'
45-
import { TIER_MODEL, escalateTier } from './ai-lint-fix/rule-guidance.mts'
45+
import {
46+
TIER_EFFORT,
47+
TIER_MODEL,
48+
escalateTier,
49+
} from './ai-lint-fix/rule-guidance.mts'
4650

4751
const logger = getDefaultLogger()
4852

@@ -113,18 +117,20 @@ async function main(): Promise<void> {
113117

114118
for (const [filePath, findings] of byFile) {
115119
const rel = path.relative(cwd, filePath)
116-
// Pick the model from the highest-tier rule in this file's batch.
117-
// Pure-Haiku files (identifier renames, null→undefined, etc.) run
118-
// cheap; any caller-chain rewrite escalates to Sonnet; a
119-
// `socket/max-file-lines` finding escalates to Opus.
120+
// Pick the model AND effort from the highest-tier rule in this file's
121+
// batch. Pure-Haiku files (identifier renames, null→undefined, etc.) run
122+
// cheap on low effort; any caller-chain rewrite escalates to Sonnet on
123+
// medium; a `socket/max-file-lines` finding escalates to Opus on high.
124+
// Effort tracks the tier per the CLAUDE.md token-spend rule.
120125
const ruleIds = findings
121126
.map(f => f.ruleId)
122127
.filter((r): r is string => typeof r === 'string')
123128
const tier = escalateTier(ruleIds)
124129
const model = TIER_MODEL[tier]
125-
logger.log(`AI-fix ${rel} (${findings.length} findings, ${tier})…`)
130+
const effort = TIER_EFFORT[tier]
131+
logger.log(`AI-fix ${rel} (${findings.length} findings, ${tier}/${effort})…`)
126132
const prompt = buildPrompt(filePath, findings)
127-
const { exitCode, stderr } = await runClaudeFix(prompt, cwd, model)
133+
const { exitCode, stderr } = await runClaudeFix(prompt, cwd, model, effort)
128134
if (exitCode === 0) {
129135
totalEdits += findings.length
130136
continue

scripts/fleet/ai-lint-fix/claude.mts

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,30 @@ import { discoverAiAgents } from '@socketsecurity/lib-stable/ai/discover'
88
import { AI_PROFILE } from '@socketsecurity/lib-stable/ai/profiles'
99
import { spawnAiAgent } from '@socketsecurity/lib-stable/ai/spawn'
1010

11+
import type { AiEffort } from '@socketsecurity/lib-stable/ai/types'
12+
1113
export async function runClaudeFix(
1214
prompt: string,
1315
cwd: string,
1416
model: string,
17+
effort: AiEffort,
1518
): Promise<{ exitCode: number; stdout: string; stderr: string }> {
1619
// AI_PROFILE.edit = in-place edits only (Edit on existing files, no
1720
// Write/MultiEdit) — exactly the lint-fix contract: the prompt forbids
1821
// creating files. spawnAiAgent owns the --no-session-persistence /
1922
// --add-dir / 529-retry the hand-rolled version used to duplicate.
20-
// The model is picked per-file by the caller via escalateTier() — see
21-
// RULE_MODEL_TIER in rule-guidance.mts. Simple regex-shaped rewrites
22-
// run on Haiku; control-flow + caller-chain rewrites run on Sonnet;
23-
// module-split refactors (`socket/max-file-lines`) run on Opus.
23+
// Model AND effort are picked per-file by the caller via escalateTier() —
24+
// see RULE_MODEL_TIER + TIER_EFFORT in rule-guidance.mts. Simple
25+
// regex-shaped rewrites run on Haiku/low; control-flow + caller-chain
26+
// rewrites run on Sonnet/medium; module-split refactors
27+
// (`socket/max-file-lines`) run on Opus/high. Pinning effort alongside the
28+
// model is the CLAUDE.md token-spend rule — a cheap model left on the
29+
// session's default (often high) still burns reasoning a mechanical
30+
// rewrite never needs.
2431
const { exitCode, stderr, stdout } = await spawnAiAgent({
2532
...AI_PROFILE.edit,
2633
cwd,
34+
effort,
2735
model,
2836
prompt,
2937
timeoutMs: 5 * 60 * 1000,

scripts/fleet/ai-lint-fix/rule-guidance.mts

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
* becomes a concern.
1717
*/
1818

19+
import type { AiEffort } from '@socketsecurity/lib-stable/ai/types'
20+
1921
// Rules below need an AI-driven fix because the right rewrite
2022
// depends on surrounding code structure that a regex / AST pass can't
2123
// safely infer. Each one IS fixable — the AI step does the work.
@@ -88,6 +90,26 @@ export const TIER_MODEL: Readonly<Record<'haiku' | 'opus' | 'sonnet', string>> =
8890
opus: 'claude-opus-4-8',
8991
} as Readonly<Record<'haiku' | 'opus' | 'sonnet', string>>
9092

93+
/**
94+
* Map a tier label to its reasoning-effort level (claude `--effort`). Effort
95+
* rides alongside the model per the CLAUDE.md token-spend rule ("match model
96+
* AND effort to the job") — a cheap model on max effort still burns reasoning
97+
* tokens a mechanical rewrite never needs. The tier ladder already encodes the
98+
* job's complexity, so effort tracks it: regex-shaped Haiku rewrites run `low`;
99+
* caller-chain Sonnet rewrites run `medium`; Opus module splits (the one tier
100+
* that genuinely reasons over the whole file) run `high`. The lib's
101+
* `spawnAiAgent` passes this through as the claude `--effort` flag; other agents
102+
* ignore it. Resolved via `AiEffort` from `@socketsecurity/lib-stable/ai/types`.
103+
*/
104+
export const TIER_EFFORT: Readonly<
105+
Record<'haiku' | 'opus' | 'sonnet', AiEffort>
106+
> = {
107+
__proto__: null,
108+
haiku: 'low',
109+
sonnet: 'medium',
110+
opus: 'high',
111+
} as unknown as Readonly<Record<'haiku' | 'opus' | 'sonnet', AiEffort>>
112+
91113
/**
92114
* Pick the highest tier present in a per-file batch's rule set. Returns a tier
93115
* label; the caller resolves it to a model via `TIER_MODEL`. Default (no

scripts/fleet/check.mts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,11 @@ const steps: Array<() => boolean> = [
5050
// Cost routing: every mutating (fix) skill must declare a model: tier so
5151
// mechanical work runs cheap. See docs/claude.md/fleet/skill-model-routing.md.
5252
() => run('node', ['scripts/fleet/check/mutating-skills-have-model.mts']),
53+
// Cost routing twin: a programmatic AI spawn that pins a model must also pin
54+
// reasoning effort (CLAUDE.md token-spend). The lib makes effort optional —
55+
// this gate is the enforcement the optional field can't provide. Vocab per
56+
// backend: .claude/skills/fleet/_shared/multi-agent-backends.md.
57+
() => run('node', ['scripts/fleet/check/ai-spawns-have-paired-effort.mts']),
5358
// Code is law: every hook + socket/* rule ships thorough tests (both arms,
5459
// every branch). A token or absent test fails the gate.
5560
() => run('node', ['scripts/fleet/check/enforcers-have-thorough-tests.mts']),
@@ -99,7 +104,7 @@ const steps: Array<() => boolean> = [
99104
// taxonomy applied — not external/, not _-prefixed) is reachable through some
100105
// exports entry (no orphaned public module). Complements files[] allowlist
101106
// hygiene and runtime require-ability; this is the map ↔ files check.
102-
() => run('node', ['scripts/fleet/check/exports-cover-public-files.mts']),
107+
() => run('node', ['scripts/fleet/check/public-files-are-exported.mts']),
103108
// Every external-tools.json / bundle-tools.json must match the shared
104109
// TypeBox schema (scripts/fleet/lib/external-tools-schema.mts). These files
105110
// pin tool versions + integrities; an unvalidated shape drift surfaces only

0 commit comments

Comments
 (0)