pan-wizard 3.29.0 → 3.30.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/README.md +39 -39
- package/bin/install-lib.cjs +65 -58
- package/bin/install.js +199 -188
- package/commands/pan/army.md +2 -2
- package/commands/pan/audit-deployment.md +2 -2
- package/commands/pan/cost.md +5 -5
- package/commands/pan/exec-phase.md +2 -0
- package/commands/pan/focus-auto.md +5 -5
- package/hooks/dist/pan-cost-logger.js +1 -1
- package/hooks/dist/pan-stop-guard.js +81 -2
- package/hooks/dist/pan-trace-logger.js +1 -1
- package/package.json +1 -1
- package/pan-wizard-core/bin/lib/agents-md.cjs +3 -2
- package/pan-wizard-core/bin/lib/core.cjs +17 -4
- package/pan-wizard-core/bin/lib/cost.cjs +104 -47
- package/pan-wizard-core/bin/lib/memory-rebuild.cjs +3 -3
- package/pan-wizard-core/bin/lib/verify-deploy.cjs +1 -1
- package/pan-wizard-core/references/model-profiles.md +4 -4
- package/pan-wizard-core/references/planning-config.md +19 -23
- package/pan-wizard-core/workflows/settings.md +2 -4
- package/scripts/test-surface.cjs +10 -9
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: focus-auto
|
|
3
3
|
group: Focus
|
|
4
|
-
description: Continuous scan-plan-exec loop with purpose-driven categories and
|
|
4
|
+
description: Continuous scan-plan-exec loop with purpose-driven categories and a layered safety harness
|
|
5
5
|
allowed-tools:
|
|
6
6
|
- Read
|
|
7
7
|
- Write
|
|
@@ -107,7 +107,7 @@ Wait for the user's reply before proceeding. Do not guess or pick a default cate
|
|
|
107
107
|
| `--stop` | — | Gracefully stop active run |
|
|
108
108
|
| `--status` | — | Show current campaign progress |
|
|
109
109
|
| `--dry-run` | — | Show plan without executing |
|
|
110
|
-
| `--deep-review` | off | After every exec cycle, run inline OWASP security check on changed files. Verdict `block` or `review_required` stops the campaign (
|
|
110
|
+
| `--deep-review` | off | After every exec cycle, run inline OWASP security check on changed files. Verdict `block` or `review_required` stops the campaign (the security-gate layer of the safety harness). Works with all categories. |
|
|
111
111
|
| `--parallel-research` | off | Fan out the per-item *research* stage via the Workflow tool (read-only agents). No-op fallback to sequential where the host has no Workflow tool. (ADR-0031) |
|
|
112
112
|
| `--parallel-verify` | off | Fan out the per-item *verify* stage via the Workflow tool (read-only). The implement/exec stage always stays a single agent. (ADR-0031) |
|
|
113
113
|
| `--clean-seal` | off | After the loop's last item, run one clean build + full verification (commands from `config.json → build`/`verification`) to catch cross-item orphans. (ADR-0031) |
|
|
@@ -185,7 +185,7 @@ Phase 2 (each cycle): Scan → Plan → Exec → Commit is strictly sequential w
|
|
|
185
185
|
HARD STOP conditions:
|
|
186
186
|
- Phase 1 fails (tests broken): Do not enter main loop — report and exit
|
|
187
187
|
- Any cycle: test count drops below baseline after revert → stop campaign, preserve state
|
|
188
|
-
- Context drops below 25%: stop campaign cleanly (safety harness
|
|
188
|
+
- Context drops below 25%: stop campaign cleanly (safety harness)
|
|
189
189
|
</phase_dependencies>
|
|
190
190
|
|
|
191
191
|
### Phase 2: Main Loop
|
|
@@ -413,7 +413,7 @@ Then continue immediately to the next cycle (back to Step 2.1).
|
|
|
413
413
|
|
|
414
414
|
4. Remove safety tag: `git tag -d focus-auto-baseline 2>/dev/null`
|
|
415
415
|
|
|
416
|
-
##
|
|
416
|
+
## Layered Safety Harness
|
|
417
417
|
|
|
418
418
|
| Layer | Mechanism | Action |
|
|
419
419
|
|-------|-----------|--------|
|
|
@@ -425,7 +425,7 @@ Then continue immediately to the next cycle (back to Step 2.1).
|
|
|
425
425
|
| Zero-completed guard | 0 items done in a cycle | Stop — further cycles won't help |
|
|
426
426
|
| Security gate (`--deep-review`) | Critical/high OWASP pattern in changed files | Revert last commit (critical) or flag for manual review (high), stop campaign |
|
|
427
427
|
|
|
428
|
-
##
|
|
428
|
+
## Behavioral Rules
|
|
429
429
|
|
|
430
430
|
1. **Read Before Write** — Read every file before editing. Understand context, callers, invariants.
|
|
431
431
|
2. **Root Cause** — Fix the actual defect, not symptoms. Trace the code path.
|
|
@@ -182,7 +182,7 @@ function readActiveSessionMeta(cwd) {
|
|
|
182
182
|
const COMMAND_TAIL_BYTES = 262144;
|
|
183
183
|
|
|
184
184
|
// PAN's own command namespace, as a runtime writes it: `/pan:exec-phase` (Claude Code,
|
|
185
|
-
// Gemini) or `/pan-exec-phase` (Codex, OpenCode, Copilot). Only these spawn PAN agents,
|
|
185
|
+
// Gemini) or `/pan-exec-phase` (Codex, OpenCode, Copilot, and Claude Code after --unified-skills). Only these spawn PAN agents,
|
|
186
186
|
// so only these are attributed — a host UI command (`/model`, `/compact`) and a plain
|
|
187
187
|
// typed prompt leave the row honestly unattributed instead of borrowing a name.
|
|
188
188
|
const PAN_COMMAND_RE = /<command-name>\s*\/?pan[:-]([a-z0-9][a-z0-9-]*)\s*<\/command-name>/i;
|
|
@@ -39,6 +39,19 @@
|
|
|
39
39
|
// per stop chain, so a user who genuinely wants to stop is delayed by exactly
|
|
40
40
|
// one continuation, never trapped.
|
|
41
41
|
//
|
|
42
|
+
// Gemini CLI (R29, 2026-09-23): the guard is registered there on AfterAgent,
|
|
43
|
+
// Gemini's end-of-turn event — until this date PAN registered it under Claude's
|
|
44
|
+
// `Stop` key, which Gemini skips with an "Invalid hook event name" warning, so it
|
|
45
|
+
// had never run. AfterAgent honours the same {decision: 'block', reason} output
|
|
46
|
+
// (a block re-prompts the agent with the reason), but its stop_hook_active is only
|
|
47
|
+
// true for the AfterAgent that evaluates a continuation the block started
|
|
48
|
+
// directly: a continuation that used tools reports false again (gemini-cli
|
|
49
|
+
// client.ts / useGeminiStream.ts, read 2026-09-23). The flag alone would let the
|
|
50
|
+
// guard block every such turn. So on AfterAgent the one-shot promise is kept with
|
|
51
|
+
// a marker per session, project and target phase in the per-user 0700 hook
|
|
52
|
+
// directory: a second stop aimed at the same phase is allowed. No safe marker
|
|
53
|
+
// directory, or no session id, means no block (fail open, as everywhere else).
|
|
54
|
+
//
|
|
42
55
|
// Escape hatch: set workflow.stop_guard to false in .planning/config.json to
|
|
43
56
|
// disable the guard entirely without turning off auto_advance.
|
|
44
57
|
//
|
|
@@ -51,7 +64,9 @@
|
|
|
51
64
|
// rather than only reachable via stdin (same pattern as the other PAN hooks).
|
|
52
65
|
|
|
53
66
|
const fs = require('fs');
|
|
67
|
+
const os = require('os');
|
|
54
68
|
const path = require('path');
|
|
69
|
+
const crypto = require('crypto');
|
|
55
70
|
/**
|
|
56
71
|
* Which planning tree this hook acts on.
|
|
57
72
|
*
|
|
@@ -155,6 +170,63 @@ function readIfExists(p) {
|
|
|
155
170
|
try { return fs.readFileSync(p, 'utf8'); } catch { return null; }
|
|
156
171
|
}
|
|
157
172
|
|
|
173
|
+
// Per-user hook state directory inside tmpdir, created 0700 — the same directory
|
|
174
|
+
// and the same checks as bridgeDir() in pan-context-monitor.js (hooks are
|
|
175
|
+
// standalone files and cannot require one another). Fail CLOSED (null) when the
|
|
176
|
+
// directory is not provably ours: a shared host must not be able to pre-plant a
|
|
177
|
+
// marker that silences the guard, or a symlink it writes through (M60).
|
|
178
|
+
function hookStateDir() {
|
|
179
|
+
const uid = (typeof process.getuid === 'function' ? process.getuid() : process.env.USERNAME || 'win');
|
|
180
|
+
const dir = path.join(os.tmpdir(), `pan-hooks-${uid}`);
|
|
181
|
+
try {
|
|
182
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
183
|
+
const st = fs.lstatSync(dir);
|
|
184
|
+
if (st.isSymbolicLink()) return null;
|
|
185
|
+
// POSIX-only ownership and mode checks: Windows fakes mode bits (N15).
|
|
186
|
+
if (typeof process.getuid === 'function') {
|
|
187
|
+
if (st.uid !== process.getuid()) return null;
|
|
188
|
+
if ((st.mode & 0o077) !== 0) return null;
|
|
189
|
+
}
|
|
190
|
+
return dir;
|
|
191
|
+
} catch { return null; }
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* The file name of the one-shot marker for an AfterAgent block (Gemini CLI).
|
|
196
|
+
* Keyed by session, project and the block's reason — the reason names the target
|
|
197
|
+
* phase, so a later drop at a DIFFERENT phase in the same session is still caught.
|
|
198
|
+
* Pure; null when there is no session id to key on.
|
|
199
|
+
*/
|
|
200
|
+
function onceMarkerName(sessionId, projectDir, reason) {
|
|
201
|
+
if (typeof sessionId !== 'string' || !sessionId) return null;
|
|
202
|
+
const key = crypto.createHash('sha256').update(`${sessionId}\0${projectDir}\0${reason}`).digest('hex').slice(0, 32);
|
|
203
|
+
return `stop-guard-${key}.json`;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const MARKER_MAX_AGE_MS = 7 * 24 * 3600 * 1000;
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Apply the AfterAgent one-shot rule: returns true when this block may be issued
|
|
210
|
+
* (and records it), false when the same session already got it or no safe place
|
|
211
|
+
* to remember it exists. Best-effort pruning keeps the directory from growing.
|
|
212
|
+
*/
|
|
213
|
+
function claimOnceMarker(dir, name, now = Date.now()) {
|
|
214
|
+
if (!dir || !name) return false;
|
|
215
|
+
const marker = path.join(dir, name);
|
|
216
|
+
try {
|
|
217
|
+
for (const f of fs.readdirSync(dir)) {
|
|
218
|
+
if (!/^stop-guard-[0-9a-f]{32}\.json$/.test(f)) continue;
|
|
219
|
+
try { if (now - fs.statSync(path.join(dir, f)).mtimeMs > MARKER_MAX_AGE_MS) fs.unlinkSync(path.join(dir, f)); } catch { /* keep */ }
|
|
220
|
+
}
|
|
221
|
+
} catch { /* unreadable dir — the exclusive create below still decides */ }
|
|
222
|
+
try {
|
|
223
|
+
// 'wx' fails when the marker exists: the create IS the check, so two hook
|
|
224
|
+
// processes racing on the same stop cannot both block.
|
|
225
|
+
fs.writeFileSync(marker, JSON.stringify({ at: new Date(now).toISOString() }), { flag: 'wx', mode: 0o600 });
|
|
226
|
+
return true;
|
|
227
|
+
} catch { return false; }
|
|
228
|
+
}
|
|
229
|
+
|
|
158
230
|
function main() {
|
|
159
231
|
let input = '';
|
|
160
232
|
process.stdin.setEncoding('utf8');
|
|
@@ -171,13 +243,20 @@ function main() {
|
|
|
171
243
|
let config = null;
|
|
172
244
|
try { config = JSON.parse(fs.readFileSync(path.join(planningDir, 'config.json'), 'utf8')); } catch { /* no project / bad config -> allow */ }
|
|
173
245
|
|
|
174
|
-
|
|
246
|
+
let decision = buildStopDecision({
|
|
175
247
|
stopHookActive: payload.stop_hook_active === true,
|
|
176
248
|
config,
|
|
177
249
|
stateContent: readIfExists(path.join(planningDir, 'state.md')),
|
|
178
250
|
roadmapContent: readIfExists(path.join(planningDir, 'roadmap.md')),
|
|
179
251
|
});
|
|
180
252
|
|
|
253
|
+
// Gemini CLI's end-of-turn event: its stop_hook_active cannot carry the
|
|
254
|
+
// one-shot promise on its own (see the header), so a marker does.
|
|
255
|
+
if (decision && payload.hook_event_name === 'AfterAgent') {
|
|
256
|
+
const name = onceMarkerName(payload.session_id, projectDir, decision.reason);
|
|
257
|
+
if (!claimOnceMarker(hookStateDir(), name)) decision = null;
|
|
258
|
+
}
|
|
259
|
+
|
|
181
260
|
if (decision) process.stdout.write(JSON.stringify(decision));
|
|
182
261
|
} catch { /* fail open — never break a stop */ }
|
|
183
262
|
process.exit(0);
|
|
@@ -188,4 +267,4 @@ if (require.main === module) {
|
|
|
188
267
|
main();
|
|
189
268
|
}
|
|
190
269
|
|
|
191
|
-
module.exports = { buildStopDecision, UNTICKED_PHASE_RE };
|
|
270
|
+
module.exports = { buildStopDecision, UNTICKED_PHASE_RE, onceMarkerName, claimOnceMarker };
|
|
@@ -175,7 +175,7 @@ function finalizeSession(cwd, sid) {
|
|
|
175
175
|
const COMMAND_TAIL_BYTES = 262144;
|
|
176
176
|
|
|
177
177
|
// PAN's own command namespace, as a runtime writes it: `/pan:exec-phase` (Claude Code,
|
|
178
|
-
// Gemini) or `/pan-exec-phase` (Codex, OpenCode, Copilot). Only these spawn PAN agents,
|
|
178
|
+
// Gemini) or `/pan-exec-phase` (Codex, OpenCode, Copilot, and Claude Code after --unified-skills). Only these spawn PAN agents,
|
|
179
179
|
// so only these are attributed — a host UI command (`/model`, `/compact`) and a plain
|
|
180
180
|
// typed prompt leave the row honestly unattributed instead of borrowing a name.
|
|
181
181
|
const PAN_COMMAND_RE = /<command-name>\s*\/?pan[:-]([a-z0-9][a-z0-9-]*)\s*<\/command-name>/i;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pan-wizard",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.30.0",
|
|
4
4
|
"description": "Command a bot army for your codebase: a reasoning-tier Mission Control delegates whole-project goals to specialist squads and ships behind a human merge gate. Five AI CLIs, zero context rot.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"pan-wizard": "bin/install.js"
|
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
// ─── AGENTS.md universal rules layer (ADR-0028 Phase 3) ─────────────────────
|
|
4
4
|
//
|
|
5
|
-
// AGENTS.md is the cross-runtime project-instructions standard;
|
|
6
|
-
//
|
|
5
|
+
// AGENTS.md is the cross-runtime project-instructions standard; Codex,
|
|
6
|
+
// OpenCode and Copilot CLI read it natively, Claude Code only through the @AGENTS.md
|
|
7
|
+
// import in CLAUDE.md; Gemini CLI reads GEMINI.md by default, not this file. PAN contributes one
|
|
7
8
|
// marker-fenced section so agents in any runtime understand the PAN context
|
|
8
9
|
// when reading the repo. User content outside the markers is never touched.
|
|
9
10
|
//
|
|
@@ -31,10 +31,23 @@ const { planningPath, planningRel } = require('./utils.cjs');
|
|
|
31
31
|
* Each provider maps reasoning/mid/fast to its native model identifiers.
|
|
32
32
|
* "inherit" means the host runtime uses its own top-tier model selection.
|
|
33
33
|
*/
|
|
34
|
+
//
|
|
35
|
+
// Every non-inherit value must be a model id the host accepts: a Claude Code alias
|
|
36
|
+
// (sonnet, haiku) for Anthropic, otherwise a concrete API id that DEFAULT_RATES in
|
|
37
|
+
// cost.cjs prices exactly (tests/documented-default-models.test.cjs pins both).
|
|
38
|
+
// Until 2026-09-22 the OpenAI row held the literal strings 'mid' and 'fast', so a
|
|
39
|
+
// budget-profile project on Codex or OpenCode was told to spawn a model called
|
|
40
|
+
// "mid" (reality check R28).
|
|
41
|
+
// - openai: Codex's subagent docs (learn.chatgpt.com/docs/agent-configuration/
|
|
42
|
+
// subagents, read 2026-09-23): "start with gpt-6-sol. Use gpt-6-luna when you
|
|
43
|
+
// want a faster, lower-cost option". gpt-6-astra is the flagship above both.
|
|
44
|
+
// - google: the newest stable Flash and Flash-Lite on ai.google.dev's models page
|
|
45
|
+
// (read 2026-09-23). The 2.5 family these rows used is "not deprecated" but
|
|
46
|
+
// limited to users who have used it before, so a new project may not reach it.
|
|
34
47
|
const PROVIDER_MODELS = {
|
|
35
48
|
anthropic: { reasoning: 'inherit', mid: 'sonnet', fast: 'haiku' },
|
|
36
|
-
openai: { reasoning: 'inherit', mid: '
|
|
37
|
-
google: { reasoning: 'inherit', mid: 'gemini-
|
|
49
|
+
openai: { reasoning: 'inherit', mid: 'gpt-6-sol', fast: 'gpt-6-luna' },
|
|
50
|
+
google: { reasoning: 'inherit', mid: 'gemini-3.8-flash', fast: 'gemini-3.5-flash-lite' },
|
|
38
51
|
default: { reasoning: 'inherit', mid: 'sonnet', fast: 'haiku' },
|
|
39
52
|
};
|
|
40
53
|
|
|
@@ -52,7 +65,7 @@ const COST_MULTIPLIERS = { reasoning: 15, mid: 3, fast: 1 };
|
|
|
52
65
|
// isolation (each subagent runs in its own window), not a cheaper model, is what
|
|
53
66
|
// keeps the main conversation clean. Cheapness is now OPT-IN: choose the `budget`
|
|
54
67
|
// profile (the only column that still down-tiers) or pin a specific agent via
|
|
55
|
-
// config `model_overrides`. The
|
|
68
|
+
// config `model_overrides`. The reviewer-class agents additionally pin `model: opus`
|
|
56
69
|
// in their own frontmatter (a native, deliberate exception). resolve-model /
|
|
57
70
|
// MODEL_PROFILES is advisory + cost-estimation; native Claude Code delegation
|
|
58
71
|
// reads each agent file's static `model:` (unset → inherit).
|
|
@@ -781,7 +794,7 @@ function adjustTierForCapabilities(tier, opts) {
|
|
|
781
794
|
* @param {string} agentType - Agent name (e.g., "pan-planner", "pan-executor")
|
|
782
795
|
* @param {Object} [taskMetadata] - Optional metadata. Supports complexity fields and
|
|
783
796
|
* capability hints: {context_estimate, needs_thinking, cache_warm}.
|
|
784
|
-
* @returns {string} Model identifier: "inherit", "sonnet", "haiku",
|
|
797
|
+
* @returns {string} Model identifier: "inherit", a Claude Code alias ("sonnet", "haiku"), or a provider model id from PROVIDER_MODELS
|
|
785
798
|
*/
|
|
786
799
|
function resolveModelInternal(cwd, agentType, taskMetadata) {
|
|
787
800
|
const config = loadConfig(cwd);
|
|
@@ -30,8 +30,9 @@
|
|
|
30
30
|
* - hit rate: cache_read / (cache_read + input - cache_write) if any cache activity
|
|
31
31
|
*
|
|
32
32
|
* Rate table is approximate — real pricing comes from the provider's API.
|
|
33
|
-
* Rates are US dollars per million tokens,
|
|
34
|
-
*
|
|
33
|
+
* Rates are US dollars per million tokens, verified against each provider's
|
|
34
|
+
* pricing page on the date in RATES_VERIFIED_AT (below). Users can override with
|
|
35
|
+
* `.planning/config.json` → `cost.rates`.
|
|
35
36
|
*/
|
|
36
37
|
|
|
37
38
|
const fs = require('fs');
|
|
@@ -47,63 +48,109 @@ const TOKENS_FILE = 'tokens.jsonl';
|
|
|
47
48
|
* Override per-model in config.json → cost.rates.
|
|
48
49
|
*/
|
|
49
50
|
const DEFAULT_RATES = {
|
|
50
|
-
// Anthropic —
|
|
51
|
-
// $5/$25 (the old $15/$75 Opus pricing ended with
|
|
52
|
-
//
|
|
53
|
-
//
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
//
|
|
51
|
+
// Anthropic — platform.claude.com/docs/en/about-claude/pricing, every row read
|
|
52
|
+
// 2026-09-23. Opus 4.6+/Opus 5 are $5/$25 (the old $15/$75 Opus pricing ended with
|
|
53
|
+
// the 4.5 generation). cache_write is the 5-minute write rate (1.25× input; the
|
|
54
|
+
// 1-hour write bills 2×, which the ledger does not split yet). Cache reads follow
|
|
55
|
+
// the 0.1× convention EXCEPT on three models the page footnotes: Fable 5.1 and
|
|
56
|
+
// Mythos 5.1 at 0.025× input, Opus 5.5 at 0.05×. Each needs its own row, because
|
|
57
|
+
// the family-prefix fallback would otherwise price it at its predecessor's reads.
|
|
58
|
+
// Fable 5.1 is what the `fable`/`best` aliases resolve to (model-config: neither
|
|
59
|
+
// Fable model is any plan's default); cached re-reads are the bulk of PAN's
|
|
60
|
+
// traffic (ADR-0044).
|
|
58
61
|
'claude-fable-5-1': { input: 10.0, output: 50.0, cache_read: 0.25, cache_write: 12.5 },
|
|
59
62
|
'claude-fable-5': { input: 10.0, output: 50.0, cache_read: 1.0, cache_write: 12.5 },
|
|
60
|
-
// Mythos 5.1 / Mythos 5 (limited availability)
|
|
61
|
-
//
|
|
62
|
-
// the two models whose cache reads bill at 0.025× input; Mythos 5 follows the 0.1× rule.
|
|
63
|
-
// Added for reality check R7: resolveRate returned null for both ids.
|
|
63
|
+
// Mythos 5.1 / Mythos 5 (limited availability): $10/$50; Mythos 5.1 reads at
|
|
64
|
+
// 0.025× input like Fable 5.1, Mythos 5 follows the 0.1× rule (R7).
|
|
64
65
|
'claude-mythos-5-1': { input: 10.0, output: 50.0, cache_read: 0.25, cache_write: 12.5 },
|
|
65
66
|
'claude-mythos-5': { input: 10.0, output: 50.0, cache_read: 1.0, cache_write: 12.5 },
|
|
67
|
+
// Opus 5.5 (claude-opus-5-5, released 2026-09-22): $4 input / $20 output, 5-minute
|
|
68
|
+
// writes $5, cache hits $0.20 — the page prices its hits at 0.05× input. Claude
|
|
69
|
+
// Code 2.1.280 made it the default model on every plan (model-config, read
|
|
70
|
+
// 2026-09-23), so PAN's inherit tier now runs on it. Without this row the family
|
|
71
|
+
// prefix priced it as Opus 5: cache reads 2.5× high, the rest a quarter high
|
|
72
|
+
// (reality check 2026-09-22, R25). The models overview lists no dated snapshot —
|
|
73
|
+
// from the 4.6 generation on, the dateless id is canonical.
|
|
74
|
+
'claude-opus-5-5': { input: 4.0, output: 20.0, cache_read: 0.20, cache_write: 5.0 },
|
|
66
75
|
'claude-opus-5': { input: 5.0, output: 25.0, cache_read: 0.5, cache_write: 6.25 },
|
|
67
76
|
'claude-opus-4-8': { input: 5.0, output: 25.0, cache_read: 0.5, cache_write: 6.25 },
|
|
68
77
|
'claude-opus-4-7': { input: 5.0, output: 25.0, cache_read: 0.5, cache_write: 6.25 },
|
|
69
78
|
'claude-opus-4-6': { input: 5.0, output: 25.0, cache_read: 0.5, cache_write: 6.25 },
|
|
70
|
-
// Opus 4.5 (dated id claude-opus-4-5-20251101)
|
|
71
|
-
//
|
|
72
|
-
// and priced as null (R7).
|
|
79
|
+
// Opus 4.5 (dated id claude-opus-4-5-20251101): without this row the dated id had
|
|
80
|
+
// no family prefix to land on and priced as null (R7).
|
|
73
81
|
'claude-opus-4-5': { input: 5.0, output: 25.0, cache_read: 0.5, cache_write: 6.25 },
|
|
74
82
|
// Sonnet 5 is $2/$10: the launch price announced as introductory through
|
|
75
|
-
// 2026-08-31 was made permanent and the scheduled rise to $3/$15 cancelled
|
|
76
|
-
//
|
|
77
|
-
//
|
|
83
|
+
// 2026-08-31 was made permanent and the scheduled rise to $3/$15 cancelled.
|
|
84
|
+
// Lesson: never write down a pre-announced price — this row carried the future
|
|
85
|
+
// rate for a month and over-billed by half.
|
|
78
86
|
'claude-sonnet-5': { input: 2.0, output: 10.0, cache_read: 0.20, cache_write: 2.50 },
|
|
79
87
|
'claude-sonnet-4-6': { input: 3.0, output: 15.0, cache_read: 0.3, cache_write: 3.75 },
|
|
80
|
-
// Sonnet 4.5 (dated id claude-sonnet-4-5-20250929)
|
|
81
|
-
// $3/$15/$0.30/$3.75 (the pre-Sonnet-5 rate; Sonnet 5 is $2/$10). R7.
|
|
88
|
+
// Sonnet 4.5 (dated id claude-sonnet-4-5-20250929): $3/$15/$0.30/$3.75 (R7).
|
|
82
89
|
'claude-sonnet-4-5': { input: 3.0, output: 15.0, cache_read: 0.3, cache_write: 3.75 },
|
|
90
|
+
// Haiku 4.5: API id claude-haiku-4-5-20251001, alias claude-haiku-4-5.
|
|
83
91
|
'claude-haiku-4-5': { input: 1.0, output: 5.0, cache_read: 0.1, cache_write: 1.25 },
|
|
84
92
|
|
|
85
|
-
// OpenAI —
|
|
86
|
-
// input
|
|
87
|
-
//
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
+
// OpenAI — developers.openai.com/api/docs/pricing, Standard tier, short context
|
|
94
|
+
// (≤272K input tokens), read 2026-09-23. On the GPT-5.6 and GPT-6 families a cache
|
|
95
|
+
// write bills at 1.25× input IN PLACE of the input rate ("writes are not an
|
|
96
|
+
// additive fee"); GPT-5.5 charges no cache write ("-"), so its write tokens bill
|
|
97
|
+
// as plain input. Until 2026-09-22 this block said "no separate write charge" for
|
|
98
|
+
// every row, which had stopped being true — Terra and Luna writes were a fifth
|
|
99
|
+
// low (R27). Longest-prefix match lets each tier key win over a shorter one.
|
|
100
|
+
// GPT-6: Astra is the flagship; Sol and Luna were released 2026-09-22. Codex's
|
|
101
|
+
// config docs use gpt-6-sol as the default model and gpt-6-luna for lower-cost
|
|
102
|
+
// subagents (learn.chatgpt.com/docs/config-file/config-basic, /agent-configuration/subagents).
|
|
103
|
+
'gpt-6-astra': { input: 10.0, output: 50.0, cache_read: 1.0, cache_write: 12.5 },
|
|
104
|
+
'gpt-6-sol': { input: 2.0, output: 10.0, cache_read: 0.2, cache_write: 2.5 },
|
|
105
|
+
'gpt-6-luna': { input: 0.10, output: 0.50, cache_read: 0.01, cache_write: 0.125 },
|
|
106
|
+
// GPT-5.6. The bare `gpt-5.6` id is an alias that "routes requests to GPT-5.6 Sol"
|
|
107
|
+
// (its model page, read 2026-09-23), so it carries Sol's row — not the $5/$30 it
|
|
108
|
+
// carried before, which no page listed. Sol's price is promotional "at least
|
|
109
|
+
// through November 21, 2026": re-read it after that date.
|
|
110
|
+
'gpt-5.6': { input: 4.0, output: 20.0, cache_read: 0.4, cache_write: 5.0 },
|
|
111
|
+
'gpt-5.6-sol': { input: 4.0, output: 20.0, cache_read: 0.4, cache_write: 5.0 },
|
|
112
|
+
'gpt-5.6-terra': { input: 2.0, output: 12.0, cache_read: 0.2, cache_write: 2.5 },
|
|
113
|
+
'gpt-5.6-luna': { input: 0.20, output: 1.20, cache_read: 0.02, cache_write: 0.25 },
|
|
114
|
+
// Priced only in the page's separate "Cyber models" table, not in the Standard tiers.
|
|
115
|
+
'gpt-5.6-cyber': { input: 12.5, output: 75.0, cache_read: 1.25, cache_write: 15.625 },
|
|
93
116
|
'gpt-5.5': { input: 5.0, output: 30.0, cache_read: 0.5, cache_write: 5.0 },
|
|
94
117
|
|
|
95
|
-
// Google Gemini —
|
|
96
|
-
//
|
|
118
|
+
// Google Gemini — ai.google.dev/gemini-api/docs/pricing, paid tier, text input,
|
|
119
|
+
// the ≤200K-token prompt tier where the page splits by length, read 2026-09-23.
|
|
120
|
+
// cache_read is the page's "context caching" price: 0.1× input on every row, not
|
|
121
|
+
// the 0.25× this block carried until 2026-09-22 (cache reads 2.5× high, R26). The
|
|
122
|
+
// tokens that CREATE a cache bill at the standard input price (Google Cloud's
|
|
123
|
+
// context-cache overview; the Gemini API pages list only cached-token and storage
|
|
124
|
+
// charges), so cache_write = input. Cache STORAGE bills per million tokens per
|
|
125
|
+
// HOUR, which a per-token table cannot carry: PAN's Gemini totals exclude it.
|
|
126
|
+
// The 3.6/3.7/3.8 Flash prices run "through December 31, 2026" and double from
|
|
127
|
+
// January 1, 2027 (the page states both) — re-read them after that date.
|
|
128
|
+
'gemini-3.8-flash': { input: 0.75, output: 3.75, cache_read: 0.075, cache_write: 0.75 },
|
|
129
|
+
'gemini-3.7-flash': { input: 0.75, output: 3.75, cache_read: 0.075, cache_write: 0.75 },
|
|
130
|
+
'gemini-3.6-flash': { input: 0.75, output: 3.75, cache_read: 0.075, cache_write: 0.75 },
|
|
131
|
+
'gemini-3.5-flash': { input: 1.50, output: 9.00, cache_read: 0.15, cache_write: 1.50 },
|
|
132
|
+
'gemini-3.5-flash-lite': { input: 0.30, output: 2.50, cache_read: 0.03, cache_write: 0.30 },
|
|
133
|
+
// Shutdown announced for 2027-05-07 (deprecations page); replacement gemini-3.5-flash-lite.
|
|
134
|
+
'gemini-3.1-flash-lite': { input: 0.25, output: 1.50, cache_read: 0.025, cache_write: 0.25 },
|
|
135
|
+
'gemini-3-flash-preview': { input: 0.50, output: 3.00, cache_read: 0.05, cache_write: 0.50 },
|
|
136
|
+
// The page lists 3.1 Pro only as gemini-3.1-pro-preview; the bare id keeps the
|
|
137
|
+
// preview's row so records written under it stay priced.
|
|
138
|
+
'gemini-3.1-pro': { input: 2.00, output: 12.0, cache_read: 0.20, cache_write: 2.00 },
|
|
139
|
+
'gemini-3.1-pro-preview': { input: 2.00, output: 12.0, cache_read: 0.20, cache_write: 2.00 },
|
|
140
|
+
// The 2.5 family is "not deprecated", but the models page limits access to users
|
|
141
|
+
// who have used it before — new projects cannot count on it (PAN's Google routing
|
|
142
|
+
// tiers moved off it, R28). Rows kept for the ledgers that recorded it.
|
|
143
|
+
'gemini-2.5-pro': { input: 1.25, output: 10.0, cache_read: 0.125, cache_write: 1.25 },
|
|
144
|
+
'gemini-2.5-flash': { input: 0.30, output: 2.50, cache_read: 0.03, cache_write: 0.30 },
|
|
145
|
+
'gemini-2.5-flash-lite': { input: 0.10, output: 0.40, cache_read: 0.01, cache_write: 0.10 },
|
|
97
146
|
// (gemini-1.5-pro removed 2026-06: retired model; records for it fall back to tier rates.)
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
'reasoning': { input: 5.0, output: 25.0, cache_read: 0.5, cache_write: 6.25 },
|
|
106
|
-
'mid': { input: 3.0, output: 15.0, cache_read: 0.3, cache_write: 3.75 },
|
|
147
|
+
|
|
148
|
+
// Tier fallbacks when the model id is unknown: reasoning tracks the default Opus
|
|
149
|
+
// (Opus 5.5 since 2026-09-22 — the model the inherit tier runs on for most users),
|
|
150
|
+
// mid the Sonnet the `sonnet` alias resolves to on the Anthropic API (Sonnet 5),
|
|
151
|
+
// fast Haiku 4.5.
|
|
152
|
+
'reasoning': { input: 4.0, output: 20.0, cache_read: 0.20, cache_write: 5.0 },
|
|
153
|
+
'mid': { input: 2.0, output: 10.0, cache_read: 0.20, cache_write: 2.50 },
|
|
107
154
|
'fast': { input: 1.0, output: 5.0, cache_read: 0.1, cache_write: 1.25 },
|
|
108
155
|
};
|
|
109
156
|
|
|
@@ -587,12 +634,22 @@ function cmdCostClear(cwd, raw) {
|
|
|
587
634
|
|
|
588
635
|
// ─── Rate-table staleness ───────────────────────────────────────────────────
|
|
589
636
|
|
|
590
|
-
// Date DEFAULT_RATES was last verified against published provider pricing
|
|
591
|
-
//
|
|
592
|
-
//
|
|
593
|
-
//
|
|
594
|
-
|
|
595
|
-
|
|
637
|
+
// Date DEFAULT_RATES was last verified against published provider pricing — ALL
|
|
638
|
+
// providers' pricing pages, not only Anthropic's (the 2026-09-10 bump covered the
|
|
639
|
+
// Anthropic rows alone, and the Gemini and OpenAI rows it vouched for had never
|
|
640
|
+
// matched their pages: reality check 2026-09-22, RC29). Bump it only when every
|
|
641
|
+
// row has been read against its page that day.
|
|
642
|
+
//
|
|
643
|
+
// `models check` flags the table once it is older than RATES_STALE_AFTER_DAYS.
|
|
644
|
+
// That window was half a year until 2026-09-22 (R38), which is longer than the
|
|
645
|
+
// interval at which the lineup now moves: the default Claude model changed on
|
|
646
|
+
// every plan inside twelve days of the last verification. Sixty days is the
|
|
647
|
+
// review cadence the ecosystem reviews settled on. The calendar still cannot see
|
|
648
|
+
// a default-model change inside the window — that is the job of the
|
|
649
|
+
// documented-default-ids fixture test (tests/fixtures/documented-default-models.json,
|
|
650
|
+
// R31), which fails the suite the day a documented default id has no exact row.
|
|
651
|
+
const RATES_VERIFIED_AT = '2026-09-23';
|
|
652
|
+
const RATES_STALE_AFTER_DAYS = 60;
|
|
596
653
|
const RATE_TIERS = ['reasoning', 'mid', 'fast'];
|
|
597
654
|
|
|
598
655
|
function checkRatesStaleness(now = new Date()) {
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Three derived targets:
|
|
12
12
|
* 1. AGENTS.md — the universal, cross-runtime tools memory. PAN owns exactly
|
|
13
|
-
* the marker-fenced `<!-- BEGIN/END PAN WIZARD -->` section (
|
|
14
|
-
*
|
|
13
|
+
* the marker-fenced `<!-- BEGIN/END PAN WIZARD -->` section (Codex, Copilot and
|
|
14
|
+
* OpenCode read AGENTS.md natively; Claude via the CLAUDE.md bridge; Gemini reads GEMINI.md by default). User content
|
|
15
15
|
* outside the markers is preserved byte-for-byte.
|
|
16
16
|
* 2. CLAUDE.md — the Claude bridge (`@AGENTS.md` import), regenerated only
|
|
17
17
|
* when the Claude runtime is installed here.
|
|
@@ -126,7 +126,7 @@ function cmdMemoryRebuild(cwd, opts = {}, raw) {
|
|
|
126
126
|
const targets = [];
|
|
127
127
|
const warnings = [];
|
|
128
128
|
|
|
129
|
-
// 1. AGENTS.md — universal PAN section (
|
|
129
|
+
// 1. AGENTS.md — universal PAN section (Claude Code reads it through the bridge in step 2).
|
|
130
130
|
{
|
|
131
131
|
const p = path.join(cwd, 'AGENTS.md');
|
|
132
132
|
const existing = safeReadFile(p);
|
|
@@ -107,7 +107,7 @@ function validateRuntimeInstall(cwd, configDir, runtime) {
|
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
|
-
// Copilot
|
|
110
|
+
// statusLine (Claude, Copilot; PAN writes none for Gemini since 2026-09-23)
|
|
111
111
|
if (settings.statusLine && settings.statusLine.command) {
|
|
112
112
|
hookCommands.push(settings.statusLine.command);
|
|
113
113
|
}
|
|
@@ -11,8 +11,8 @@ PAN uses three abstract tiers instead of hardcoded model names:
|
|
|
11
11
|
| Tier | Purpose | Anthropic | OpenAI | Google |
|
|
12
12
|
|------|---------|-----------|--------|--------|
|
|
13
13
|
| `reasoning` | Architecture, planning, complex decisions | inherit (your session's top-tier model) | inherit | inherit |
|
|
14
|
-
| `mid` | Execution, research, verification | Sonnet |
|
|
15
|
-
| `fast` | Read-only extraction, budget tasks | Haiku |
|
|
14
|
+
| `mid` | Execution, research, verification | Sonnet | gpt-6-sol | gemini-3.8-flash |
|
|
15
|
+
| `fast` | Read-only extraction, budget tasks | Haiku | gpt-6-luna | gemini-3.5-flash-lite |
|
|
16
16
|
|
|
17
17
|
**Why `inherit` for reasoning?** Host runtimes map "opus" to a specific model version. PAN returns `inherit` for reasoning-tier agents, so they use whatever top-tier model the user has configured. This avoids version conflicts and silent fallbacks.
|
|
18
18
|
|
|
@@ -39,7 +39,7 @@ At install time PAN also runs a **best-effort, advisory** capability check on th
|
|
|
39
39
|
| Class | Example model IDs | Role in PAN | Context | Relative cost | Notes |
|
|
40
40
|
|-------|-------------------|-------------|---------|---------------|-------|
|
|
41
41
|
| Fable / Mythos | `claude-fable-5` | **Recommended flagship** — deepest long-horizon reasoning; best for the bot army's Mission Control + planning | 1M | ~2× Opus | Runs input safety classifiers (see caveat below); requires 30-day data retention |
|
|
42
|
-
| Opus | `claude-opus-5`, `claude-opus-4-8` | **Cost-conscious pick** — same 1M context + thinking, about half the cost, no cyber classifier | 1M | 1× | The safe pick when you want Opus behavior without Fable's refusal surface |
|
|
42
|
+
| Opus | `claude-opus-5-5`, `claude-opus-5`, `claude-opus-4-8` | **Cost-conscious pick** — same 1M context + thinking, about half the cost, no cyber classifier | 1M | 1× | The safe pick when you want Opus behavior without Fable's refusal surface |
|
|
43
43
|
|
|
44
44
|
**Why the Fable class is the recommended flagship.** It is Anthropic's deepest class for demanding, long-horizon agentic work — exactly what PAN's hierarchical bot army (Mission Control → squads → workers) asks of its reasoning tier. Select the current release in that class in your host runtime and `inherit` routes the reasoning-tier agents to it automatically.
|
|
45
45
|
|
|
@@ -117,7 +117,7 @@ PAN auto-detects the LLM provider to map tiers to the right model names:
|
|
|
117
117
|
|
|
118
118
|
1. **Explicit config** — `routing.provider` in config.json (if not `"auto"`)
|
|
119
119
|
2. **Environment variable** — `PAN_PROVIDER` env var
|
|
120
|
-
3. **Runtime directory** — `.claude/` → Anthropic, `.codex/` → OpenAI, `.gemini/` → Google
|
|
120
|
+
3. **Runtime directory** — `.claude/` → Anthropic, `.codex/` → OpenAI, `.gemini/` → Google, `.opencode/` → OpenAI, `.github/` → default (first match wins)
|
|
121
121
|
4. **Fallback** — Default provider map (Anthropic-style names)
|
|
122
122
|
|
|
123
123
|
---
|
|
@@ -4,24 +4,22 @@ Configuration options for `.planning/` directory behavior.
|
|
|
4
4
|
|
|
5
5
|
<config_schema>
|
|
6
6
|
```json
|
|
7
|
-
"
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
},
|
|
11
|
-
"
|
|
12
|
-
"branching_strategy": "none",
|
|
13
|
-
"phase_branch_template": "pan/phase-{phase}-{slug}",
|
|
14
|
-
"milestone_branch_template": "pan/{milestone}-{slug}"
|
|
15
|
-
}
|
|
7
|
+
"commit_docs": true,
|
|
8
|
+
"search_gitignored": false,
|
|
9
|
+
"branching_strategy": "none",
|
|
10
|
+
"phase_branch_template": "pan/phase-{phase}-{slug}",
|
|
11
|
+
"milestone_branch_template": "pan/{milestone}-{slug}"
|
|
16
12
|
```
|
|
17
13
|
|
|
18
14
|
| Option | Default | Description |
|
|
19
15
|
|--------|---------|-------------|
|
|
20
16
|
| `commit_docs` | `true` | Whether to commit planning artifacts to git |
|
|
21
17
|
| `search_gitignored` | `false` | Add `--no-ignore` to broad rg searches |
|
|
22
|
-
| `
|
|
23
|
-
| `
|
|
24
|
-
| `
|
|
18
|
+
| `branching_strategy` | `"none"` | Git branching approach: `"none"`, `"phase"`, or `"milestone"` |
|
|
19
|
+
| `phase_branch_template` | `"pan/phase-{phase}-{slug}"` | Branch template for phase strategy |
|
|
20
|
+
| `milestone_branch_template` | `"pan/{milestone}-{slug}"` | Branch template for milestone strategy |
|
|
21
|
+
|
|
22
|
+
These keys are top-level. The nested `planning.*` / `git.*` forms older versions wrote are read only when the top-level key is absent. A config `config-ensure-section` creates carries every top-level key, and one `/pan:new-project` writes carries a top-level `commit_docs` — so write the top-level key; a nested one is ignored whenever the top-level key exists.
|
|
25
23
|
</config_schema>
|
|
26
24
|
|
|
27
25
|
<commit_docs_behavior>
|
|
@@ -84,10 +82,8 @@ To use uncommitted mode:
|
|
|
84
82
|
|
|
85
83
|
1. **Set config:**
|
|
86
84
|
```json
|
|
87
|
-
"
|
|
88
|
-
|
|
89
|
-
"search_gitignored": true
|
|
90
|
-
}
|
|
85
|
+
"commit_docs": false,
|
|
86
|
+
"search_gitignored": true
|
|
91
87
|
```
|
|
92
88
|
|
|
93
89
|
2. **Add to .gitignore:**
|
|
@@ -101,7 +97,7 @@ To use uncommitted mode:
|
|
|
101
97
|
git commit -m "chore: stop tracking planning docs"
|
|
102
98
|
```
|
|
103
99
|
|
|
104
|
-
4. **Branch merges:**
|
|
100
|
+
4. **Branch merges:** PAN never merges branches; when you merge a phase or milestone branch yourself with `commit_docs: false`, keep `.planning/` out of the merge commit.
|
|
105
101
|
|
|
106
102
|
</setup_uncommitted_mode>
|
|
107
103
|
|
|
@@ -113,24 +109,24 @@ To use uncommitted mode:
|
|
|
113
109
|
|----------|---------------------|--------------|-------------|
|
|
114
110
|
| `none` | Never | N/A | N/A |
|
|
115
111
|
| `phase` | At `execute-phase` start | Single phase | User merges after phase |
|
|
116
|
-
| `milestone` | At first `execute-phase` of milestone | Entire milestone |
|
|
112
|
+
| `milestone` | At first `execute-phase` of milestone | Entire milestone | User merges after milestone |
|
|
117
113
|
|
|
118
|
-
**When `
|
|
114
|
+
**When `branching_strategy: "none"` (default):**
|
|
119
115
|
- All work commits to current branch
|
|
120
116
|
- Standard PAN behavior
|
|
121
117
|
|
|
122
|
-
**When `
|
|
118
|
+
**When `branching_strategy: "phase"`:**
|
|
123
119
|
- `execute-phase` creates/switches to a branch before execution
|
|
124
120
|
- Branch name from `phase_branch_template` (e.g., `pan/phase-03-authentication`)
|
|
125
121
|
- All plan commits go to that branch
|
|
126
122
|
- User merges branches manually after phase completion
|
|
127
|
-
- `milestone-done`
|
|
123
|
+
- `milestone-done` does not merge — merge the phase branches yourself
|
|
128
124
|
|
|
129
|
-
**When `
|
|
125
|
+
**When `branching_strategy: "milestone"`:**
|
|
130
126
|
- First `execute-phase` of milestone creates the milestone branch
|
|
131
127
|
- Branch name from `milestone_branch_template` (e.g., `pan/v1.0-mvp`)
|
|
132
128
|
- All phases in milestone commit to same branch
|
|
133
|
-
- `milestone-done`
|
|
129
|
+
- `milestone-done` archives and tags but does not merge — merge the milestone branch yourself
|
|
134
130
|
|
|
135
131
|
**Template variables:**
|
|
136
132
|
|
|
@@ -31,7 +31,7 @@ Parse current values (default to `true` if not present):
|
|
|
31
31
|
- `workflow.nyquist_validation` — validation architecture research during plan-phase
|
|
32
32
|
- `model_profile` — which model each agent uses (default: `balanced`)
|
|
33
33
|
- `routing.strategy` — how model tiers are adjusted at runtime (default: `static`)
|
|
34
|
-
- `
|
|
34
|
+
- `branching_strategy` — branching approach (default: `"none"`; a top-level key — a nested `git.branching_strategy` is ignored when the top-level key exists, as it does in every config `config-ensure-section` creates — write the top-level key)
|
|
35
35
|
</step>
|
|
36
36
|
|
|
37
37
|
<step name="present_settings">
|
|
@@ -131,9 +131,7 @@ Merge new settings into existing config.json:
|
|
|
131
131
|
"auto_advance": true/false,
|
|
132
132
|
"nyquist_validation": true/false
|
|
133
133
|
},
|
|
134
|
-
"
|
|
135
|
-
"branching_strategy": "none" | "phase" | "milestone"
|
|
136
|
-
},
|
|
134
|
+
"branching_strategy": "none" | "phase" | "milestone",
|
|
137
135
|
"routing": {
|
|
138
136
|
"strategy": "static" | "complexity"
|
|
139
137
|
}
|