pan-wizard 3.31.0 → 3.33.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 (87) hide show
  1. package/README.md +28 -24
  2. package/agents/pan-conductor.md +2 -2
  3. package/agents/pan-design-checker.md +18 -0
  4. package/agents/pan-document_code.md +2 -2
  5. package/agents/pan-executor.md +3 -3
  6. package/agents/pan-experiment-runner.md +7 -10
  7. package/agents/pan-hardener.md +15 -15
  8. package/agents/pan-knowledge.md +1 -1
  9. package/agents/pan-meta-reviewer.md +2 -2
  10. package/agents/pan-optimizer.md +7 -3
  11. package/agents/pan-phase-researcher.md +2 -2
  12. package/agents/pan-plan-checker.md +29 -2
  13. package/agents/pan-planner.md +23 -10
  14. package/agents/pan-project-researcher.md +10 -67
  15. package/agents/pan-reviewer.md +19 -0
  16. package/agents/pan-roadmapper.md +5 -48
  17. package/agents/pan-verifier.md +49 -11
  18. package/bin/install-lib.cjs +170 -15
  19. package/bin/install.js +39 -14
  20. package/commands/pan/army.md +2 -2
  21. package/commands/pan/design-phase.md +8 -0
  22. package/commands/pan/exec-phase.md +1 -1
  23. package/commands/pan/experiment.md +2 -2
  24. package/commands/pan/focus-auto.md +12 -8
  25. package/commands/pan/focus-design.md +2 -0
  26. package/commands/pan/focus-exec.md +1 -1
  27. package/commands/pan/focus-scan.md +2 -7
  28. package/commands/pan/git.md +2 -2
  29. package/commands/pan/learn.md +2 -0
  30. package/commands/pan/map-codebase.md +4 -4
  31. package/commands/pan/mcp-bridge.md +1 -1
  32. package/commands/pan/milestone-done.md +1 -2
  33. package/commands/pan/optimize.md +14 -2
  34. package/commands/pan/phase-tests.md +2 -2
  35. package/commands/pan/profile.md +2 -2
  36. package/commands/pan/research-phase.md +2 -4
  37. package/commands/pan/review-deep.md +8 -8
  38. package/commands/pan/todo-add.md +2 -2
  39. package/commands/pan/todo-check.md +2 -2
  40. package/hooks/dist/pan-context-monitor.js +1 -1
  41. package/hooks/dist/pan-cost-logger.js +63 -11
  42. package/hooks/dist/pan-state-reinject.js +142 -17
  43. package/hooks/dist/pan-stop-guard.js +2 -2
  44. package/hooks/dist/pan-trace-logger.js +283 -18
  45. package/package.json +1 -1
  46. package/pan-wizard-core/bin/lib/agents-md.cjs +4 -1
  47. package/pan-wizard-core/bin/lib/commands.cjs +95 -2
  48. package/pan-wizard-core/bin/lib/config.cjs +110 -0
  49. package/pan-wizard-core/bin/lib/constants.cjs +39 -9
  50. package/pan-wizard-core/bin/lib/core.cjs +27 -7
  51. package/pan-wizard-core/bin/lib/cost.cjs +16 -6
  52. package/pan-wizard-core/bin/lib/findings.cjs +385 -0
  53. package/pan-wizard-core/bin/lib/focus.cjs +72 -19
  54. package/pan-wizard-core/bin/lib/foreign-planning.cjs +24 -6
  55. package/pan-wizard-core/bin/lib/hygiene.cjs +12 -2
  56. package/pan-wizard-core/bin/lib/init.cjs +9 -2
  57. package/pan-wizard-core/bin/lib/optimize.cjs +349 -21
  58. package/pan-wizard-core/bin/lib/phase.cjs +52 -2
  59. package/pan-wizard-core/bin/lib/review-deep.cjs +38 -2
  60. package/pan-wizard-core/bin/lib/state.cjs +10 -4
  61. package/pan-wizard-core/bin/lib/verdict.cjs +481 -0
  62. package/pan-wizard-core/bin/lib/verify-drift.cjs +14 -5
  63. package/pan-wizard-core/bin/lib/verify-preflight.cjs +4 -2
  64. package/pan-wizard-core/bin/lib/verify-scope.cjs +223 -0
  65. package/pan-wizard-core/bin/lib/verify.cjs +111 -8
  66. package/pan-wizard-core/bin/pan-tools.cjs +68 -6
  67. package/pan-wizard-core/learnings/README.md +10 -7
  68. package/pan-wizard-core/mcp/server.cjs +18 -9
  69. package/pan-wizard-core/mcp/tool-registry.cjs +2 -0
  70. package/pan-wizard-core/references/checkpoints.md +4 -2
  71. package/pan-wizard-core/references/verdict-contract.md +102 -0
  72. package/pan-wizard-core/templates/discovery.md +1 -1
  73. package/pan-wizard-core/templates/phase-prompt.md +2 -1
  74. package/pan-wizard-core/templates/verification-report.md +21 -0
  75. package/pan-wizard-core/workflows/exec-phase.md +73 -50
  76. package/pan-wizard-core/workflows/health.md +3 -0
  77. package/pan-wizard-core/workflows/learn.md +4 -2
  78. package/pan-wizard-core/workflows/milestone-audit.md +15 -3
  79. package/pan-wizard-core/workflows/new-project.md +1 -1
  80. package/pan-wizard-core/workflows/optimize.md +14 -1
  81. package/pan-wizard-core/workflows/plan-phase.md +20 -15
  82. package/pan-wizard-core/workflows/quick.md +6 -6
  83. package/pan-wizard-core/workflows/verify-phase.md +10 -15
  84. package/pan-zcode/README.md +3 -3
  85. package/scripts/build-agent-plugin.js +19 -5
  86. package/scripts/git-hooks/pre-commit +2 -1
  87. package/scripts/test-surface.cjs +4 -0
@@ -103,6 +103,102 @@ function buildConfigDefaults(hasBraveSearch, userDefaults) {
103
103
  };
104
104
  }
105
105
 
106
+ // What PAN reads beyond the keys buildConfigDefaults writes (market-ideas M26). The
107
+ // rest of the known set is derived from the defaults, so a new default is known the
108
+ // day it is added. Each entry has a reader: loadConfig's flat aliases and alias
109
+ // sections (`planning.*`, `git.*`), the stop guard (`mode`, `workflow.stop_guard`),
110
+ // the roadmapper and new-project (`depth`), memory-optimize (`memory`), and the
111
+ // workflows that config-get `workflow.auto_advance`. tests/config-keys in
112
+ // tests/config.test.cjs pins this list against the keys shipped code and prose use.
113
+ const CONFIG_KEYS_BEYOND_DEFAULTS = Object.freeze({
114
+ top: Object.freeze(['research', 'plan_checker', 'verifier', 'nyquist_validation', 'mode', 'depth', 'planning', 'git',
115
+ 'focus', 'model_overrides', 'effort_overrides', 'cost', 'cache', 'build', 'verification', 'concurrency', 'memory']),
116
+ sections: Object.freeze({
117
+ workflow: Object.freeze(['auto_advance', 'stop_guard']),
118
+ planning: Object.freeze(['commit_docs', 'search_gitignored']),
119
+ git: Object.freeze(['branching_strategy', 'phase_branch_template', 'milestone_branch_template']),
120
+ }),
121
+ });
122
+ // Sections whose keys are the user's own (model ids, agent names, file lists,
123
+ // commands), so nothing below them is checked.
124
+ const OPEN_CONFIG_SECTIONS = Object.freeze(['focus', 'model_overrides', 'effort_overrides', 'cost', 'cache', 'build', 'verification', 'concurrency', 'memory']);
125
+
126
+ /** The known top-level keys, and the known keys one level inside each closed section. */
127
+ function knownConfigKeys() {
128
+ const defaults = buildConfigDefaults(false, {});
129
+ const top = new Set([...Object.keys(defaults), ...CONFIG_KEYS_BEYOND_DEFAULTS.top]);
130
+ const sections = new Map();
131
+ for (const [k, v] of Object.entries(defaults)) {
132
+ if (v && typeof v === 'object' && !Array.isArray(v)) sections.set(k, new Set(Object.keys(v)));
133
+ }
134
+ for (const [k, list] of Object.entries(CONFIG_KEYS_BEYOND_DEFAULTS.sections)) {
135
+ sections.set(k, new Set([...(sections.get(k) || []), ...list]));
136
+ }
137
+ for (const k of OPEN_CONFIG_SECTIONS) sections.delete(k);
138
+ return { top, sections };
139
+ }
140
+
141
+ /**
142
+ * Whether PAN reads a dot-notation key path: the top-level key is known, and inside a
143
+ * closed section so is the second segment. Deeper segments are not checked.
144
+ * @param {string} keyPath
145
+ * @returns {boolean}
146
+ */
147
+ function isKnownConfigPath(keyPath) {
148
+ const [head, sub] = String(keyPath).split('.');
149
+ const { top, sections } = knownConfigKeys();
150
+ if (!top.has(head)) return false;
151
+ return sub === undefined || !sections.has(head) || sections.get(head).has(sub);
152
+ }
153
+
154
+ /**
155
+ * The key paths in a parsed config.json that PAN does not read (market-ideas M26):
156
+ * unknown top-level keys, and unknown keys one level inside a closed section.
157
+ * @param {Object} config
158
+ * @returns {string[]}
159
+ */
160
+ function unknownConfigKeys(config) {
161
+ if (!config || typeof config !== 'object' || Array.isArray(config)) return [];
162
+ const { top, sections } = knownConfigKeys();
163
+ const unknown = [];
164
+ for (const [k, v] of Object.entries(config)) {
165
+ if (!top.has(k)) { unknown.push(k); continue; }
166
+ const known = sections.get(k);
167
+ if (!known || !v || typeof v !== 'object' || Array.isArray(v)) continue;
168
+ for (const sub of Object.keys(v)) if (!known.has(sub)) unknown.push(`${k}.${sub}`);
169
+ }
170
+ return unknown;
171
+ }
172
+
173
+ /** The known key path nearest an unknown one (edit distance 2 or less), or null. */
174
+ function nearestConfigKey(keyPath) {
175
+ const { top, sections } = knownConfigKeys();
176
+ const candidates = [...top];
177
+ for (const [k, subs] of sections) for (const s of subs) candidates.push(`${k}.${s}`);
178
+ const want = String(keyPath).split('.').slice(0, 2).join('.');
179
+ let best = null;
180
+ let bestD = 3;
181
+ for (const c of candidates) {
182
+ const d = editDistance(want, c);
183
+ if (d < bestD) { best = c; bestD = d; }
184
+ }
185
+ return best;
186
+ }
187
+
188
+ function editDistance(a, b) {
189
+ const row = Array.from({ length: b.length + 1 }, (_, j) => j);
190
+ for (let i = 1; i <= a.length; i++) {
191
+ let prev = row[0];
192
+ row[0] = i;
193
+ for (let j = 1; j <= b.length; j++) {
194
+ const tmp = row[j];
195
+ row[j] = Math.min(row[j] + 1, row[j - 1] + 1, prev + (a[i - 1] === b[j - 1] ? 0 : 1));
196
+ prev = tmp;
197
+ }
198
+ }
199
+ return row[b.length];
200
+ }
201
+
106
202
  function cmdConfigEnsureSection(cwd, raw) {
107
203
  const configPath = path.join(planningPath(cwd), CONFIG_FILE);
108
204
 
@@ -216,6 +312,14 @@ function cmdConfigSet(cwd, keyPath, value, raw) {
216
312
  try {
217
313
  fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
218
314
  const result = { updated: true, key: keyPath, value: parsedValue };
315
+ // A key PAN does not read is still written (a newer PAN, or the user's own
316
+ // note, may want it), but never silently: a typo used to look like a setting
317
+ // that took effect (market-ideas M26).
318
+ if (!isKnownConfigPath(keyPath)) {
319
+ const near = nearestConfigKey(keyPath);
320
+ result.warning = `unknown config key "${keyPath}": PAN does not read it` + (near ? `; did you mean "${near}"?` : '');
321
+ process.stderr.write(`pan-tools: ${result.warning}\n`);
322
+ }
219
323
  output(result, raw, `${keyPath}=${parsedValue}`);
220
324
  } catch (err) {
221
325
  // Config file could not be written — disk full, permissions, etc.
@@ -664,6 +768,12 @@ function cmdStandardsTools(cwd, standardId, raw) {
664
768
 
665
769
  module.exports = {
666
770
  buildConfigDefaults,
771
+ CONFIG_KEYS_BEYOND_DEFAULTS,
772
+ OPEN_CONFIG_SECTIONS,
773
+ knownConfigKeys,
774
+ isKnownConfigPath,
775
+ unknownConfigKeys,
776
+ nearestConfigKey,
667
777
  cmdConfigEnsureSection,
668
778
  cmdConfigSet,
669
779
  cmdConfigGet,
@@ -207,16 +207,18 @@ const STANDARDS_CATALOG = {
207
207
  level: 'foundational',
208
208
  url: 'https://owasp.org/www-project-top-ten/',
209
209
  checklist: [
210
- 'A01: Broken Access Control — verify authorization checks on all endpoints',
211
- 'A02: Cryptographic Failures — verify sensitive data encryption at rest and in transit',
212
- 'A03: Injection — verify input validation and parameterized queries',
213
- 'A04: Insecure Design — verify threat modeling and secure design patterns',
214
- 'A05: Security Misconfiguration — verify default credentials removed, headers set',
215
- 'A06: Vulnerable Components — verify dependency scanning, no known CVEs',
210
+ // The 2025 list (top10.owasp.org): until 2026-10-03 this held the 2021 list
211
+ // under the 2025 name (prompt audit, market-ideas M34). SSRF is now part of A01.
212
+ 'A01: Broken Access Control — verify authorization checks on all endpoints, and allowlists for server-side requests (SSRF)',
213
+ 'A02: Security Misconfiguration — verify default credentials removed, headers set',
214
+ 'A03: Software Supply Chain Failures — verify dependency scanning, no known CVEs, pinned and verified packages and build tooling',
215
+ 'A04: Cryptographic Failures — verify sensitive data encryption at rest and in transit',
216
+ 'A05: Injection — verify input validation and parameterized queries',
217
+ 'A06: Insecure Design — verify threat modeling and secure design patterns',
216
218
  'A07: Authentication Failures — verify MFA support, session management, credential storage',
217
- 'A08: Software and Data Integrity — verify CI/CD pipeline integrity, signed updates',
218
- 'A09: Logging and Monitoring — verify security events logged, alerts configured',
219
- 'A10: SSRF — verify server-side request validation, allowlists',
219
+ 'A08: Software or Data Integrity Failures — verify CI/CD pipeline integrity, signed updates',
220
+ 'A09: Security Logging and Alerting Failures — verify security events logged, alerts configured',
221
+ 'A10: Mishandling of Exceptional Conditions — verify errors fail closed, are handled, and release resources',
220
222
  ],
221
223
  },
222
224
  'owasp-asvs-l1': {
@@ -541,6 +543,15 @@ const HEALTH_STATUS = { HEALTHY: 'healthy', DEGRADED: 'degraded', BROKEN: 'broke
541
543
  /** Max JSON payload size before writing to tmpfile (bytes) */
542
544
  const MAX_JSON_SIZE = 50000;
543
545
 
546
+ /**
547
+ * Shape version of the machine-readable state JSON: `state` (load), `state json`,
548
+ * `progress` (json) and the MCP resources that wrap them (market item M17).
549
+ * Additive rule: fields may be added within a major version and are never renamed
550
+ * or removed; readers accept any 1.x and ignore fields they do not know. A rename
551
+ * or removal is a major bump ("2.0").
552
+ */
553
+ const STATE_CONTRACT = '1.0';
554
+
544
555
  /** Width of progress bar in status display (character count) */
545
556
  const PROGRESS_BAR_WIDTH = 10;
546
557
 
@@ -724,6 +735,24 @@ const FOREIGN_PLANNING_MARKERS = Object.freeze({
724
735
  'graphify.enabled', 'intel.enabled', 'hooks.workflow_guard', 'dynamic_routing', 'resolve_model_ids',
725
736
  ]),
726
737
  }),
738
+ // planning-with-files (OthmanAdi/planning-with-files README, read 2026-09-28 at
739
+ // v3.21.0; market item M3 / MI-028). It writes INTO .planning/ beside whatever else
740
+ // lives there: `.active_plan`, `.attestation`, `sessions/`, `ledger-<agent>.jsonl`, and
741
+ // one `YYYY-MM-DD-slug/` directory per task holding task_plan.md / findings.md /
742
+ // progress.md. PAN writes none of those names. `coexists`: unlike gsd-core, a match
743
+ // means the tree is SHARED, not owned. PAN keeps working in it and says so, rather
744
+ // than refusing. planning-with-files gitignores .planning/ by default, which is what
745
+ // the I005 health check exists for.
746
+ planning_with_files: Object.freeze({
747
+ tool: 'planning-with-files',
748
+ coexists: true,
749
+ files: Object.freeze(['.active_plan', '.attestation']),
750
+ filePatterns: Object.freeze([/^ledger-[A-Za-z0-9._-]+\.jsonl$/]),
751
+ dirs: Object.freeze(['sessions']),
752
+ // A dated task directory is strong evidence only with the tool's plan file in it.
753
+ dirPatterns: Object.freeze([{ re: /^\d{4}-\d{2}-\d{2}-[A-Za-z0-9][A-Za-z0-9._-]*$/, requires: 'task_plan.md' }]),
754
+ configKeys: Object.freeze([]),
755
+ }),
727
756
  });
728
757
 
729
758
  module.exports = {
@@ -806,6 +835,7 @@ module.exports = {
806
835
  HEALTH_STATUS,
807
836
  FOREIGN_PLANNING_MARKERS,
808
837
  MAX_JSON_SIZE,
838
+ STATE_CONTRACT,
809
839
  PROGRESS_BAR_WIDTH,
810
840
  MAX_SLUG_LENGTH,
811
841
  FILLED_BLOCK,
@@ -39,8 +39,13 @@ const { planningPath, planningRel } = require('./utils.cjs');
39
39
  // budget-profile project on Codex or OpenCode was told to spawn a model called
40
40
  // "mid" (reality check R28).
41
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.
42
+ // subagents, re-read 2026-10-03) now start demanding agents on gpt-6.1-sol "when
43
+ // your signed-in account or workspace has access", and keep gpt-6-luna as the
44
+ // "faster, lower-cost option". The mid tier stays gpt-6-sol: GPT-6.1 Sol's launch
45
+ // keeps it off on Enterprise and Edu until an administrator enables it
46
+ // (learn.chatgpt.com/docs/models, read 2026-10-03), so pinning it would hand
47
+ // those users a model they cannot run. The inherit tier runs Codex's own
48
+ // default, which is gpt-6.1-sol where available. gpt-6-astra is the flagship.
44
49
  // - google: the newest stable Flash and Flash-Lite on ai.google.dev's models page
45
50
  // (read 2026-09-23). The 2.5 family these rows used is "not deprecated" but
46
51
  // limited to users who have used it before, so a new project may not reach it.
@@ -58,12 +63,14 @@ const PROVIDER_MODELS = {
58
63
  // packages/web/src/content/docs/models.mdx and models.dev/api.json, both read
59
64
  // 2026-09-26; every id below exists there under that provider). Same tiers as
60
65
  // PROVIDER_MODELS, provider-qualified; `default` is Anthropic as it is there.
61
- // Reality check R41.
66
+ // Reality check R41. The Anthropic mid tier follows the `sonnet` alias, which
67
+ // resolves to Sonnet 5.5 on the Anthropic API from Claude Code 2.1.284;
68
+ // models.dev lists `anthropic/claude-sonnet-5-5` (api.json, read 2026-10-03).
62
69
  const OPENCODE_MODELS = {
63
- anthropic: { reasoning: 'inherit', mid: 'anthropic/claude-sonnet-5', fast: 'anthropic/claude-haiku-4-5' },
64
- openai: { reasoning: 'inherit', mid: 'openai/gpt-6-sol', fast: 'openai/gpt-6-luna' },
65
- google: { reasoning: 'inherit', mid: 'google/gemini-3.8-flash', fast: 'google/gemini-3.5-flash-lite' },
66
- default: { reasoning: 'inherit', mid: 'anthropic/claude-sonnet-5', fast: 'anthropic/claude-haiku-4-5' },
70
+ anthropic: { reasoning: 'inherit', mid: 'anthropic/claude-sonnet-5-5', fast: 'anthropic/claude-haiku-4-5' },
71
+ openai: { reasoning: 'inherit', mid: 'openai/gpt-6-sol', fast: 'openai/gpt-6-luna' },
72
+ google: { reasoning: 'inherit', mid: 'google/gemini-3.8-flash', fast: 'google/gemini-3.5-flash-lite' },
73
+ default: { reasoning: 'inherit', mid: 'anthropic/claude-sonnet-5-5', fast: 'anthropic/claude-haiku-4-5' },
67
74
  };
68
75
 
69
76
  /**
@@ -552,6 +559,18 @@ function escapeRegex(value) {
552
559
  return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
553
560
  }
554
561
 
562
+ /**
563
+ * Make free text safe for one markdown table cell: escape backslashes and pipes,
564
+ * and fold every run of line breaks into one space. A raw `|` splits the row into
565
+ * an extra column, and state.md's tables are appended to for the life of a
566
+ * project, so one bad row breaks every later read of the table (market-ideas M21).
567
+ * @param {*} value - Cell text; null and undefined become ''
568
+ * @returns {string}
569
+ */
570
+ function escapeTableCell(value) {
571
+ return String(value ?? '').replace(/\\/g, '\\\\').replace(/\|/g, '\\|').replace(/[\r\n]+/g, ' ');
572
+ }
573
+
555
574
  /**
556
575
  * Normalize a phase identifier to zero-padded format.
557
576
  * Examples: "1" → "01", "3A" → "03A", "12.1" → "12.1"
@@ -1454,6 +1473,7 @@ module.exports = {
1454
1473
  isGitRepo,
1455
1474
  execGit,
1456
1475
  escapeRegex,
1476
+ escapeTableCell,
1457
1477
  normalizePhaseName,
1458
1478
  comparePhaseNum,
1459
1479
  searchPhaseInDir,
@@ -59,7 +59,7 @@ const DEFAULT_RATES = {
59
59
  // Anthropic — platform.claude.com/docs/en/about-claude/pricing, every row read
60
60
  // 2026-09-23. Opus 4.6+/Opus 5 are $5/$25 (the old $15/$75 Opus pricing ended with
61
61
  // the 4.5 generation). cache_write is the 5-minute write rate (1.25× input; the
62
- // 1-hour write bills 2×, which the ledger does not split yet). Cache reads follow
62
+ // 1-hour write bills 2× and is priced from cache_write_1h, see above). Cache reads follow
63
63
  // the 0.1× convention EXCEPT on three models the page footnotes: Fable 5.1 and
64
64
  // Mythos 5.1 at 0.025× input, Opus 5.5 at 0.05×. Each needs its own row, because
65
65
  // the family-prefix fallback would otherwise price it at its predecessor's reads.
@@ -91,6 +91,12 @@ const DEFAULT_RATES = {
91
91
  // 2026-08-31 was made permanent and the scheduled rise to $3/$15 cancelled.
92
92
  // Lesson: never write down a pre-announced price — this row carried the future
93
93
  // rate for a month and over-billed by half.
94
+ // Sonnet 5.5 (claude-sonnet-5-5): the same prices as Sonnet 5 — $2/$10, 5-minute
95
+ // writes $2.50, 1-hour $4, hits $0.20 (pricing page, read 2026-10-03). Claude Code
96
+ // 2.1.284 points the `sonnet` alias at it on the Anthropic API (model-config, read
97
+ // 2026-10-03). The family prefix already priced it at Sonnet 5's identical row; its
98
+ // own row means a later price change to either model cannot misprice the other.
99
+ 'claude-sonnet-5-5': { input: 2.0, output: 10.0, cache_read: 0.20, cache_write: 2.50, cache_write_1h: 4.0 },
94
100
  'claude-sonnet-5': { input: 2.0, output: 10.0, cache_read: 0.20, cache_write: 2.50, cache_write_1h: 4.0 },
95
101
  'claude-sonnet-4-6': { input: 3.0, output: 15.0, cache_read: 0.3, cache_write: 3.75, cache_write_1h: 6.0 },
96
102
  // Sonnet 4.5 (dated id claude-sonnet-4-5-20250929): $3/$15/$0.30/$3.75 (R7).
@@ -105,10 +111,14 @@ const DEFAULT_RATES = {
105
111
  // as plain input. Until 2026-09-22 this block said "no separate write charge" for
106
112
  // every row, which had stopped being true — Terra and Luna writes were a fifth
107
113
  // low (R27). Longest-prefix match lets each tier key win over a shorter one.
108
- // GPT-6: Astra is the flagship; Sol and Luna were released 2026-09-22. Codex's
109
- // config docs use gpt-6-sol as the default model and gpt-6-luna for lower-cost
110
- // subagents (learn.chatgpt.com/docs/config-file/config-basic, /agent-configuration/subagents).
114
+ // GPT-6: Astra is the flagship; Sol and Luna were released 2026-09-22. GPT-6.1 Sol
115
+ // is Codex's default model from 0.159.1 (2026-09-29): `model = "gpt-6.1-sol"` in
116
+ // config-basic, and the subagent docs' starting model for demanding agents, with
117
+ // gpt-6-luna for lower-cost ones (learn.chatgpt.com/docs/config-file/config-basic,
118
+ // /agent-configuration/subagents, both read 2026-10-03). Its row was read from the
119
+ // pricing page on 2026-10-03: GPT-6 Sol's prices, except cached input at 0.05×.
111
120
  'gpt-6-astra': { input: 10.0, output: 50.0, cache_read: 1.0, cache_write: 12.5 },
121
+ 'gpt-6.1-sol': { input: 2.0, output: 10.0, cache_read: 0.1, cache_write: 2.5 },
112
122
  'gpt-6-sol': { input: 2.0, output: 10.0, cache_read: 0.2, cache_write: 2.5 },
113
123
  'gpt-6-luna': { input: 0.10, output: 0.50, cache_read: 0.01, cache_write: 0.125 },
114
124
  // GPT-5.6. The bare `gpt-5.6` id is an alias that "routes requests to GPT-5.6 Sol"
@@ -469,8 +479,8 @@ function readRecords(cwd) {
469
479
  */
470
480
  // No single subagent runs for six hours — the longest native-workflow phase runs
471
481
  // measured in the harness finish inside an hour — while a parent-transcript slice
472
- // that spans a working day, or the idle night between two stops, does. Mirrored by
473
- // the hooks' SLICE_MAX_DURATION_MS, which nulls the span on write. Calibrated on
482
+ // that spans a working day, or the idle night between two stops, does. The hooks
483
+ // write the span as measured; only this reader applies the rule. Calibrated on
474
484
  // eleven field ledgers (2026-09): of the timed rows the old ratio rule flagged,
475
485
  // 55 spanned under three hours and 12 spanned six to twenty-four — the latter all
476
486
  // parent slices booked to a `general-purpose` or workflow subagent.