peaks-loop 4.0.33 → 4.0.34

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 (57) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/cli/commands/code-commands.js +2 -0
  3. package/dist/cli/commands/code-mode-gate-commands.js +3 -3
  4. package/dist/cli/commands/code-mode-status-command.d.ts +17 -0
  5. package/dist/cli/commands/code-mode-status-command.js +51 -0
  6. package/dist/cli/commands/code-run-command.js +8 -1
  7. package/dist/cli/commands/code-runtime-commands.js +13 -5
  8. package/dist/cli/commands/core/skill-command.js +1 -1
  9. package/dist/cli/commands/request-commands.js +3 -7
  10. package/dist/cli/commands/session-24h-mode.js +9 -1
  11. package/dist/services/24h-mode/auto-engage.d.ts +39 -0
  12. package/dist/services/24h-mode/auto-engage.js +46 -0
  13. package/dist/services/24h-mode/decider.d.ts +11 -5
  14. package/dist/services/24h-mode/decider.js +13 -13
  15. package/dist/services/audit/enforcers/active-skill-resolver.d.ts +3 -1
  16. package/dist/services/audit/enforcers/active-skill-resolver.js +3 -5
  17. package/dist/services/code/auto-compact-lifecycle.d.ts +1 -1
  18. package/dist/services/code/auto-compact-lifecycle.js +1 -1
  19. package/dist/services/code/auto-compact-modes.d.ts +1 -1
  20. package/dist/services/code/auto-compact-modes.js +1 -1
  21. package/dist/services/code/auto-compact-orchestrator.js +18 -22
  22. package/dist/services/code/mode-gate.d.ts +6 -2
  23. package/dist/services/code/mode-gate.js +9 -5
  24. package/dist/services/code/orchestrator-can-do.js +2 -2
  25. package/dist/services/config/config-service.d.ts +17 -0
  26. package/dist/services/config/config-service.js +34 -0
  27. package/dist/services/context/auto-compact-dispatcher.d.ts +2 -2
  28. package/dist/services/context/auto-compact-dispatcher.js +4 -4
  29. package/dist/services/context/auto-compact-reader.js +8 -1
  30. package/dist/services/context/auto-compact-types.d.ts +13 -1
  31. package/dist/services/context/build-dispatch-system-prompt.js +2 -2
  32. package/dist/services/context/context-schema.d.ts +1 -1
  33. package/dist/services/hooks/auto-compact-hook-install.d.ts +2 -2
  34. package/dist/services/hooks/auto-compact-hook-install.js +2 -2
  35. package/dist/services/ide/adapters/claude-code-adapter.d.ts +51 -0
  36. package/dist/services/ide/adapters/claude-code-adapter.js +84 -14
  37. package/dist/services/ide/ide-types.d.ts +10 -1
  38. package/dist/services/mode/mode-enforcement.d.ts +4 -1
  39. package/dist/services/mode/mode-enforcement.js +26 -28
  40. package/dist/services/mode/mode-status-service.d.ts +81 -0
  41. package/dist/services/mode/mode-status-service.js +108 -0
  42. package/dist/services/skills/presence-lease-service.d.ts +21 -0
  43. package/dist/services/skills/presence-lease-service.js +26 -0
  44. package/dist/services/skills/skill-presence-service.d.ts +10 -1
  45. package/dist/services/skills/skill-presence-service.js +32 -4
  46. package/dist/services/skills/skill-statusline-service.js +6 -3
  47. package/package.json +5 -5
  48. package/skills/peaks-code/SKILL.md +9 -9
  49. package/skills/peaks-code/references/mode-selection.md +7 -3
  50. package/skills/peaks-code/references/periodic-checkpoint.md +4 -4
  51. package/skills/peaks-code/references/runbook.md +10 -5
  52. package/skills/peaks-code/references/session-overload-signal-index.md +2 -2
  53. package/skills/peaks-code/references/standards-preflight.md +1 -1
  54. package/skills/peaks-code/references/startup-sequence.md +1 -1
  55. package/skills/peaks-code/references/step-0-8-gate.md +1 -1
  56. package/skills/peaks-code/references/swarm-dispatch-contract.md +2 -2
  57. package/skills/peaks-code/references/workflow-gates-and-types.md +1 -1
@@ -5,7 +5,7 @@
5
5
  * pause for an AskUserQuestion round-trip?" The D5 design (per
6
6
  * `.peaks/memory/2026-06-26-v2-11-full-auto-self-decision.md`) requires:
7
7
  *
8
- * - `full-auto` and `swarm` modes → auto-proceed (recommended = chosen)
8
+ * - `full-auto` and `24h` modes → auto-proceed (recommended = chosen)
9
9
  * - `assisted` and `strict` modes → pause for confirmation
10
10
  * - 3 hard-floor categories ALWAYS ask, regardless of mode:
11
11
  * 1. Irreversible external side effects (git push, npm publish, …)
@@ -77,7 +77,11 @@ export declare function isHardFloorCategory(value: string): value is HardFloorCa
77
77
  /**
78
78
  * `true` when the current mode should auto-proceed (skip the
79
79
  * AskUserQuestion round-trip). Mirrors D5.a: "recommended = chosen
80
- * in full-auto / swarm; always log, never silently skip".
80
+ * in full-auto / 24h; always log, never silently skip".
81
+ *
82
+ * Slice 2026-09-09-mode-consolidation: `swarm` was removed as a mode
83
+ * (parallel fan-out is now the default execution strategy in every
84
+ * mode); `24h` replaces it as the second auto-proceed peer.
81
85
  */
82
86
  export declare function shouldAutoProceed(mode: CodeMode): boolean;
83
87
  /**
@@ -5,7 +5,7 @@
5
5
  * pause for an AskUserQuestion round-trip?" The D5 design (per
6
6
  * `.peaks/memory/2026-06-26-v2-11-full-auto-self-decision.md`) requires:
7
7
  *
8
- * - `full-auto` and `swarm` modes → auto-proceed (recommended = chosen)
8
+ * - `full-auto` and `24h` modes → auto-proceed (recommended = chosen)
9
9
  * - `assisted` and `strict` modes → pause for confirmation
10
10
  * - 3 hard-floor categories ALWAYS ask, regardless of mode:
11
11
  * 1. Irreversible external side effects (git push, npm publish, …)
@@ -22,8 +22,8 @@
22
22
  export const CODE_MODES = [
23
23
  'full-auto',
24
24
  'assisted',
25
- 'swarm',
26
- 'strict'
25
+ 'strict',
26
+ '24h'
27
27
  ];
28
28
  export const HARD_FLOOR_CATEGORIES = [
29
29
  'irreversible-external-side-effect',
@@ -56,10 +56,14 @@ export function isHardFloorCategory(value) {
56
56
  /**
57
57
  * `true` when the current mode should auto-proceed (skip the
58
58
  * AskUserQuestion round-trip). Mirrors D5.a: "recommended = chosen
59
- * in full-auto / swarm; always log, never silently skip".
59
+ * in full-auto / 24h; always log, never silently skip".
60
+ *
61
+ * Slice 2026-09-09-mode-consolidation: `swarm` was removed as a mode
62
+ * (parallel fan-out is now the default execution strategy in every
63
+ * mode); `24h` replaces it as the second auto-proceed peer.
60
64
  */
61
65
  export function shouldAutoProceed(mode) {
62
- return mode === 'full-auto' || mode === 'swarm';
66
+ return mode === 'full-auto' || mode === '24h';
63
67
  }
64
68
  export const COMMIT_BOUNDARY_ACTIONS = [
65
69
  'git-push',
@@ -185,11 +185,11 @@ export function buildOrchestratorCanDoResult(input, signals) {
185
185
  // Q4 — context ratio gate. ≥0.95 → red-line; ≥0.85 → pre-compact.
186
186
  if (signals.q4ContextRatio >= ORCHESTRATOR_REDLINE_RATIO) {
187
187
  blockers.push(`context red-line (ratio=${signals.q4ContextRatio.toFixed(2)} ≥ ${ORCHESTRATOR_REDLINE_RATIO}); auto-compact now or push to next session`);
188
- suggestions.push('peaks compact auto --execute');
188
+ suggestions.push('peaks code auto-compact');
189
189
  }
190
190
  else if (signals.q4ContextRatio >= ORCHESTRATOR_PRECOMPACT_RATIO) {
191
191
  blockers.push(`context near limit (ratio=${signals.q4ContextRatio.toFixed(2)} ≥ ${ORCHESTRATOR_PRECOMPACT_RATIO}); auto-compact or push to next session`);
192
- suggestions.push('peaks compact auto --execute');
192
+ suggestions.push('peaks code auto-compact');
193
193
  }
194
194
  // Q3 — user-decision keywords → soft warning, NOT a blocker. The
195
195
  // LLM should AskUserQuestion, which is cheap.
@@ -15,6 +15,23 @@ export { resolveProjectRootForConfig, resolveCanonicalProjectRoot } from './conf
15
15
  */
16
16
  export declare function loadGlobalConfig(): ConfigV2 | null;
17
17
  export declare function isConfigLayer(value: string): value is ConfigLayer;
18
+ /**
19
+ * Machine-scoped context-window override key, the config twin of the
20
+ * `PEAKS_CONTEXT_WINDOW_TOKENS` env var (slice
21
+ * 2026-09-09-context-window-override). Written with
22
+ * `peaks config set --key context.windowTokens --value <positive-int>`
23
+ * (user layer) so the user pins it once instead of exporting an env var
24
+ * in every shell.
25
+ */
26
+ export declare const CONTEXT_WINDOW_TOKENS_CONFIG_KEY = "context.windowTokens";
27
+ /**
28
+ * Read the raw `context.windowTokens` value from the merged config, project
29
+ * layer over user layer (most specific wins). Returns the RAW value —
30
+ * validation lives in the context-window resolver so exactly one warning is
31
+ * emitted per bad value. Returns `undefined` when absent or unreadable;
32
+ * never throws (a corrupt config must not crash a context probe).
33
+ */
34
+ export declare function readContextWindowTokensOverride(projectRoot?: string | null): unknown;
18
35
  export declare function isSensitiveConfigPath(path: string): boolean;
19
36
  export declare function isLegacyConfigKey(path: string): boolean;
20
37
  export declare function containsSensitiveConfigValue(value: unknown): boolean;
@@ -176,6 +176,40 @@ function removeProjectSensitiveConfig(config) {
176
176
  export function isConfigLayer(value) {
177
177
  return value === 'user' || value === 'project';
178
178
  }
179
+ /**
180
+ * Machine-scoped context-window override key, the config twin of the
181
+ * `PEAKS_CONTEXT_WINDOW_TOKENS` env var (slice
182
+ * 2026-09-09-context-window-override). Written with
183
+ * `peaks config set --key context.windowTokens --value <positive-int>`
184
+ * (user layer) so the user pins it once instead of exporting an env var
185
+ * in every shell.
186
+ */
187
+ export const CONTEXT_WINDOW_TOKENS_CONFIG_KEY = 'context.windowTokens';
188
+ /**
189
+ * Read the raw `context.windowTokens` value from the merged config, project
190
+ * layer over user layer (most specific wins). Returns the RAW value —
191
+ * validation lives in the context-window resolver so exactly one warning is
192
+ * emitted per bad value. Returns `undefined` when absent or unreadable;
193
+ * never throws (a corrupt config must not crash a context probe).
194
+ */
195
+ export function readContextWindowTokensOverride(projectRoot) {
196
+ try {
197
+ const projectRaw = readProjectJsonFile(projectRoot ?? null);
198
+ if (isRecord(projectRaw)) {
199
+ const fromProject = getNestedValue(projectRaw, CONTEXT_WINDOW_TOKENS_CONFIG_KEY);
200
+ if (fromProject !== undefined)
201
+ return fromProject;
202
+ }
203
+ const userRaw = readUserJsonFile();
204
+ if (isRecord(userRaw)) {
205
+ return getNestedValue(userRaw, CONTEXT_WINDOW_TOKENS_CONFIG_KEY);
206
+ }
207
+ return undefined;
208
+ }
209
+ catch { // TODO(g2): legacy silent catch — never let config IO break a probe (grace: 1 minor release, v2.14.0)
210
+ return undefined;
211
+ }
212
+ }
179
213
  export function isSensitiveConfigPath(path) {
180
214
  const normalized = path.toLowerCase().replace(/[^a-z0-9]/g, '');
181
215
  return normalized.includes('apikey') || normalized.includes('accesskey') || normalized.includes('privatekey') || normalized.includes('token') || normalized.includes('secret') || normalized.includes('password') || normalized.includes('bearer') || normalized.includes('credential') || normalized.includes('auth');
@@ -45,7 +45,7 @@ export interface DispatchIdeCompactInput {
45
45
  * the compact should target. Default `'main'` — the orchestrator
46
46
  * (peaks-code body) runs in the main-session Claude Code window and
47
47
  * wants to compress *its* context, not a sub-agent's. Sub-agent
48
- * shells that spawn their own `peaks compact auto` flow pass
48
+ * shells that spawn their own `peaks code auto-compact` flow pass
49
49
  * `'sub-agent'` to preserve the legacy shell-spawn behaviour.
50
50
  *
51
51
  * Behaviour matrix (claude-code MVP):
@@ -66,7 +66,7 @@ export interface DispatchIdeCompactInput {
66
66
  * when the dispatch path completed without error — for
67
67
  * `llm-self-compress` the LLM still has to do the actual summary,
68
68
  * so the orchestrator MUST NOT treat `ok: true` as proof that the
69
- * context actually shrunk; the next `peaks compact auto` probe
69
+ * context actually shrunk; the next `peaks code auto-compact` probe
70
70
  * confirms.
71
71
  */
72
72
  export declare function dispatchIdeCompact(input: DispatchIdeCompactInput): Promise<CompactDispatchResult>;
@@ -7,7 +7,7 @@ import { getAdapter } from '../ide/ide-registry.js';
7
7
  * when the dispatch path completed without error — for
8
8
  * `llm-self-compress` the LLM still has to do the actual summary,
9
9
  * so the orchestrator MUST NOT treat `ok: true` as proof that the
10
- * context actually shrunk; the next `peaks compact auto` probe
10
+ * context actually shrunk; the next `peaks code auto-compact` probe
11
11
  * confirms.
12
12
  */
13
13
  export async function dispatchIdeCompact(input) {
@@ -77,7 +77,7 @@ export async function dispatchIdeCompact(input) {
77
77
  case 'ide-native':
78
78
  // Slice 2026-07-02-auto-compact-zero-pause: write the auto-compact
79
79
  // PreToolUse hook into `.claude/settings.local.json`. The hook
80
- // command (`peaks compact auto`) reads
80
+ // command (`peaks code auto-compact`) reads
81
81
  // `CLAUDE_CONTEXT_USAGE_PERCENT` on every subsequent Bash/Task
82
82
  // tool call from the runner and, at ratio ≥ 0.95, in-band spawns
83
83
  // `claude --compact` against the CURRENT runner (not a child
@@ -106,7 +106,7 @@ export async function dispatchIdeCompact(input) {
106
106
  };
107
107
  }
108
108
  // Lazy install: we only get here when the caller explicitly
109
- // invokes `peaks compact auto --execute`, so the user has
109
+ // invokes `peaks code auto-compact`, so the user has
110
110
  // already opted in. No zero-touch surprise on workspace init.
111
111
  return await dispatchIdeNativeHook({
112
112
  projectRoot: input.projectRoot,
@@ -144,7 +144,7 @@ export async function dispatchIdeCompact(input) {
144
144
  * into `.claude/settings.local.json` (idempotent; the install
145
145
  * service is a no-op if the hook is already present). On the next
146
146
  * Bash/Task tool call from the runner, the hook fires
147
- * `peaks compact auto` which in-band spawns
147
+ * `peaks code auto-compact` which in-band spawns
148
148
  * `claude --compact` against the CURRENT runner session.
149
149
  *
150
150
  * Returns `ok: true, pathway: 'ide-native'` regardless of install
@@ -1,5 +1,6 @@
1
1
  import { detectIdeFromEnv } from './ide-detect.js';
2
2
  import { getAdapter } from '../ide/ide-registry.js';
3
+ import { readContextWindowTokensOverride } from '../config/config-service.js';
3
4
  /**
4
5
  * Read env-var-based context percentage. Returns `null` when the env
5
6
  * is absent or unparseable. Adapter-driven: the caller passes the
@@ -74,11 +75,17 @@ export function readContextPercent(input) {
74
75
  // Fallback: the adapter owns any vendor-specific statusline /
75
76
  // transcript probe. When it returns a probe, honor it; otherwise
76
77
  // fall through to conservative-fallback.
78
+ //
79
+ // Slice 2026-09-09-context-window-override: the generic reader also
80
+ // hands the adapter the raw `context.windowTokens` config override, so
81
+ // the adapter's window resolver can prefer an explicit user value over
82
+ // its model-name heuristics (the env override arrives via `env`).
77
83
  const fallback = adapter.compact.readContextPercentFallback?.({
78
84
  projectRoot: input.projectRoot,
79
85
  sessionId: input.sessionId,
80
86
  outerSessionId: input.outerSessionId,
81
- env
87
+ env,
88
+ configWindowTokens: readContextWindowTokensOverride(input.projectRoot)
82
89
  });
83
90
  if (fallback)
84
91
  return fallback;
@@ -45,7 +45,7 @@ export type CompactTrigger = {
45
45
  }
46
46
  /**
47
47
  * Part 22: auto-fire zone (0.80 ≤ ratio < 0.85). peaks-loop
48
- * preempts and runs `peaks compact auto --execute` itself
48
+ * preempts and runs `peaks code auto-compact` itself
49
49
  * without LLM involvement. The LLM is not asked to "decide";
50
50
  * the toolkit is applied synchronously.
51
51
  */
@@ -186,6 +186,18 @@ export interface ContextPercentProbe {
186
186
  * percent sources.
187
187
  */
188
188
  readonly capacityTokens?: number;
189
+ /**
190
+ * Which layer produced `capacityTokens` (token-based sources only):
191
+ * - `env-override` — `PEAKS_CONTEXT_WINDOW_TOKENS`
192
+ * - `config` — `context.windowTokens` (`peaks config set`)
193
+ * - `model-heuristic` — `[1M]` suffix / known-1M model allowlist
194
+ * - `default` — 200K safe default
195
+ * Undefined for byte / percent sources (`user-overridden`, `${ideId}-env`,
196
+ * `statusline-poll`), which have no token window. Slice
197
+ * 2026-09-09-context-window-override: lets a wrong window be diagnosed in
198
+ * one read instead of guessing which heuristic fired.
199
+ */
200
+ readonly capacitySource?: string;
189
201
  readonly ide: string;
190
202
  readonly capturedAt: string;
191
203
  }
@@ -191,9 +191,9 @@ function renderContextBlock(probe) {
191
191
  const usedPct = (probe.ratio * 100).toFixed(1);
192
192
  const freePct = ((1 - probe.ratio) * 100).toFixed(1);
193
193
  const action = probe.ratio >= 0.95
194
- ? 'RED-LINE — call `peaks compact auto --execute` immediately.'
194
+ ? 'RED-LINE — call `peaks code auto-compact` immediately.'
195
195
  : probe.ratio >= 0.85
196
- ? 'pre-compact zone — consider running `peaks compact auto --execute` proactively.'
196
+ ? 'pre-compact zone — consider running `peaks code auto-compact` proactively.'
197
197
  : probe.ratio >= 0.5
198
198
  ? 'soft-warn zone — continue working; the next dispatch will re-check.'
199
199
  : 'plenty of room — continue without compacting.';
@@ -13,8 +13,8 @@ export declare const ContextJsonSchema: z.ZodObject<{
13
13
  path: z.ZodString;
14
14
  kind: z.ZodEnum<{
15
15
  doc: "doc";
16
- source: "source";
17
16
  config: "config";
17
+ source: "source";
18
18
  test: "test";
19
19
  }>;
20
20
  lines: z.ZodNumber;
@@ -37,7 +37,7 @@
37
37
  */
38
38
  export declare const AUTO_COMPACT_HOOK_MATCHER = "Bash|Task";
39
39
  /**
40
- * Stable command the hook fires. Wraps `peaks compact auto` so the
40
+ * Stable command the hook fires. Wraps `peaks code auto-compact` so the
41
41
  * heavy lifting (ratio probe + in-band `claude --compact` spawn)
42
42
  * lives in the CLI surface, not inlined into the hook.
43
43
  *
@@ -46,7 +46,7 @@ export declare const AUTO_COMPACT_HOOK_MATCHER = "Bash|Task";
46
46
  * so the unqualified command works in the same shell the runner
47
47
  * is using.
48
48
  */
49
- export declare const AUTO_COMPACT_HOOK_COMMAND = "peaks compact auto";
49
+ export declare const AUTO_COMPACT_HOOK_COMMAND = "peaks code auto-compact";
50
50
  /**
51
51
  * Magic comment marker that fences the auto-compatchook block.
52
52
  * Both install (writes the marker) and remove (matches on it) use
@@ -39,7 +39,7 @@ import { dirname, join } from 'node:path';
39
39
  */
40
40
  export const AUTO_COMPACT_HOOK_MATCHER = 'Bash|Task';
41
41
  /**
42
- * Stable command the hook fires. Wraps `peaks compact auto` so the
42
+ * Stable command the hook fires. Wraps `peaks code auto-compact` so the
43
43
  * heavy lifting (ratio probe + in-band `claude --compact` spawn)
44
44
  * lives in the CLI surface, not inlined into the hook.
45
45
  *
@@ -48,7 +48,7 @@ export const AUTO_COMPACT_HOOK_MATCHER = 'Bash|Task';
48
48
  * so the unqualified command works in the same shell the runner
49
49
  * is using.
50
50
  */
51
- export const AUTO_COMPACT_HOOK_COMMAND = 'peaks compact auto';
51
+ export const AUTO_COMPACT_HOOK_COMMAND = 'peaks code auto-compact';
52
52
  /**
53
53
  * Magic comment marker that fences the auto-compatchook block.
54
54
  * Both install (writes the marker) and remove (matches on it) use
@@ -20,6 +20,57 @@ export declare function resolveClaudeModelFromEnv(env: NodeJS.ProcessEnv | undef
20
20
  * Callers MAY additionally infer ≥1M from the observed token count: if
21
21
  * `contextTokens > DEFAULT_CONTEXT_WINDOW_TOKENS`, the model cannot be a
22
22
  * 200K model and must be ≥1M (see `readClaudeTranscriptEstimate`).
23
+ *
24
+ * This is the HEURISTIC layer only. Explicit user overrides sit ABOVE it —
25
+ * see `resolveContextWindow`, which is what the probe actually calls.
23
26
  */
24
27
  export declare function modelContextWindowTokens(model: string): number;
28
+ /**
29
+ * Slice 2026-09-09-context-window-override: vendor-neutral escape hatch for
30
+ * the context-window size in tokens. Third-party / proxied models whose id
31
+ * carries no `[1M]` suffix (and is absent from the hardcoded allowlist) are
32
+ * otherwise stuck at the 200K default, which inflates `ratio` up to 5×.
33
+ * Same spirit as `PEAKS_CALLER_ID`: an env var the user can export, plus a
34
+ * machine-wide `peaks config set --key context.windowTokens` twin.
35
+ */
36
+ export declare const CONTEXT_WINDOW_TOKENS_ENV_VAR = "PEAKS_CONTEXT_WINDOW_TOKENS";
37
+ /**
38
+ * Which layer produced a resolved context window:
39
+ * - `env-override` — `PEAKS_CONTEXT_WINDOW_TOKENS`
40
+ * - `config` — `context.windowTokens` (`peaks config set`)
41
+ * - `model-heuristic` — `[1M]` suffix / `ONE_MILLION_CONTEXT_MODELS`
42
+ * - `default` — 200K safe default
43
+ */
44
+ export type ContextWindowSource = 'env-override' | 'config' | 'model-heuristic' | 'default';
45
+ export interface ContextWindowResolution {
46
+ readonly tokens: number;
47
+ readonly source: ContextWindowSource;
48
+ }
49
+ /** Explicit override inputs (both optional; env wins over config). */
50
+ export interface ContextWindowOverrides {
51
+ readonly env?: NodeJS.ProcessEnv | undefined;
52
+ /** Raw `context.windowTokens` value (unvalidated — validated here). */
53
+ readonly configWindowTokens?: unknown;
54
+ /** Warning sink for an invalid override (defaults to `console.warn`). */
55
+ readonly onInvalidOverride?: ((message: string) => void) | undefined;
56
+ }
57
+ /**
58
+ * Parse an explicit context-window override. Accepts a positive finite
59
+ * integer only (number, or a numeric string so an env var works); anything
60
+ * else — `0`, negative, `NaN`, `Infinity`, `1.5`, `'abc'`, `''` — returns
61
+ * null so the caller can ignore it and fall through to the next layer.
62
+ */
63
+ export declare function parseContextWindowOverride(raw: unknown): number | null;
64
+ /**
65
+ * Resolve the context window in tokens AND report which layer won
66
+ * (first hit wins):
67
+ * 1. env `PEAKS_CONTEXT_WINDOW_TOKENS`
68
+ * 2. config `context.windowTokens`
69
+ * 3. model-name heuristic (`modelContextWindowTokens`)
70
+ * 4. `DEFAULT_CONTEXT_WINDOW_TOKENS` (200_000)
71
+ *
72
+ * An invalid explicit override is ignored with a warning and falls through
73
+ * to the next layer — a typo must never crash or silently win the probe.
74
+ */
75
+ export declare function resolveContextWindow(model: string, overrides?: ContextWindowOverrides): ContextWindowResolution;
25
76
  export declare const CLAUDE_CODE_ADAPTER: IdeAdapter;
@@ -161,6 +161,9 @@ export function resolveClaudeModelFromEnv(env) {
161
161
  * Callers MAY additionally infer ≥1M from the observed token count: if
162
162
  * `contextTokens > DEFAULT_CONTEXT_WINDOW_TOKENS`, the model cannot be a
163
163
  * 200K model and must be ≥1M (see `readClaudeTranscriptEstimate`).
164
+ *
165
+ * This is the HEURISTIC layer only. Explicit user overrides sit ABOVE it —
166
+ * see `resolveContextWindow`, which is what the probe actually calls.
164
167
  */
165
168
  export function modelContextWindowTokens(model) {
166
169
  const m = model.trim().toLowerCase();
@@ -174,6 +177,60 @@ export function modelContextWindowTokens(model) {
174
177
  }
175
178
  return DEFAULT_CONTEXT_WINDOW_TOKENS;
176
179
  }
180
+ /**
181
+ * Slice 2026-09-09-context-window-override: vendor-neutral escape hatch for
182
+ * the context-window size in tokens. Third-party / proxied models whose id
183
+ * carries no `[1M]` suffix (and is absent from the hardcoded allowlist) are
184
+ * otherwise stuck at the 200K default, which inflates `ratio` up to 5×.
185
+ * Same spirit as `PEAKS_CALLER_ID`: an env var the user can export, plus a
186
+ * machine-wide `peaks config set --key context.windowTokens` twin.
187
+ */
188
+ export const CONTEXT_WINDOW_TOKENS_ENV_VAR = 'PEAKS_CONTEXT_WINDOW_TOKENS';
189
+ /**
190
+ * Parse an explicit context-window override. Accepts a positive finite
191
+ * integer only (number, or a numeric string so an env var works); anything
192
+ * else — `0`, negative, `NaN`, `Infinity`, `1.5`, `'abc'`, `''` — returns
193
+ * null so the caller can ignore it and fall through to the next layer.
194
+ */
195
+ export function parseContextWindowOverride(raw) {
196
+ if (typeof raw !== 'number' && typeof raw !== 'string')
197
+ return null;
198
+ if (typeof raw === 'string' && raw.trim().length === 0)
199
+ return null;
200
+ const parsed = typeof raw === 'number' ? raw : Number(raw.trim());
201
+ return Number.isFinite(parsed) && Number.isInteger(parsed) && parsed > 0 ? parsed : null;
202
+ }
203
+ /**
204
+ * Resolve the context window in tokens AND report which layer won
205
+ * (first hit wins):
206
+ * 1. env `PEAKS_CONTEXT_WINDOW_TOKENS`
207
+ * 2. config `context.windowTokens`
208
+ * 3. model-name heuristic (`modelContextWindowTokens`)
209
+ * 4. `DEFAULT_CONTEXT_WINDOW_TOKENS` (200_000)
210
+ *
211
+ * An invalid explicit override is ignored with a warning and falls through
212
+ * to the next layer — a typo must never crash or silently win the probe.
213
+ */
214
+ export function resolveContextWindow(model, overrides = {}) {
215
+ const warn = overrides.onInvalidOverride ?? ((message) => console.warn(message));
216
+ const envRaw = overrides.env?.[CONTEXT_WINDOW_TOKENS_ENV_VAR];
217
+ if (envRaw !== undefined) {
218
+ const parsed = parseContextWindowOverride(envRaw);
219
+ if (parsed !== null)
220
+ return { tokens: parsed, source: 'env-override' };
221
+ warn(`[peaks] ${CONTEXT_WINDOW_TOKENS_ENV_VAR}="${String(envRaw)}" is not a positive integer — ignoring the override`);
222
+ }
223
+ if (overrides.configWindowTokens !== undefined) {
224
+ const parsed = parseContextWindowOverride(overrides.configWindowTokens);
225
+ if (parsed !== null)
226
+ return { tokens: parsed, source: 'config' };
227
+ warn(`[peaks] config context.windowTokens=${JSON.stringify(overrides.configWindowTokens)} is not a positive integer — ignoring the override`);
228
+ }
229
+ const heuristic = modelContextWindowTokens(model);
230
+ return heuristic === DEFAULT_CONTEXT_WINDOW_TOKENS
231
+ ? { tokens: heuristic, source: 'default' }
232
+ : { tokens: heuristic, source: 'model-heuristic' };
233
+ }
177
234
  /** A non-negative finite number, or null when the value is not numeric. */
178
235
  function numericTokenCount(value) {
179
236
  if (typeof value === 'number' && Number.isFinite(value) && value >= 0)
@@ -277,14 +334,20 @@ function findLatestTranscriptUsage(filePath) {
277
334
  }
278
335
  }
279
336
  /**
280
- * Resolve the context window for a transcript usage entry. Prefers the
281
- * explicit `modelContextWindowTokens(model)` mapping; when the observed token
282
- * count contradicts it (tokens exceed the mapped window), the model must be
283
- * ≥1M, so bump to the 1M window.
337
+ * Resolve the context window for a transcript usage entry. Explicit user
338
+ * overrides (`resolveContextWindow`) win outright — the user pinned the
339
+ * number, so nothing else may contradict it. Without an explicit override,
340
+ * when the observed token count contradicts the heuristic window (tokens
341
+ * exceed it), the model must be ≥1M, so bump to the 1M window (the late
342
+ * rescue; it keeps the heuristic source tag, only the tokens change).
284
343
  */
285
- function resolveContextWindowTokens(model, contextTokens) {
286
- const window = modelContextWindowTokens(model);
287
- return contextTokens > window ? ONE_MILLION_CONTEXT_TOKENS : window;
344
+ function resolveContextWindowTokens(model, contextTokens, overrides = {}) {
345
+ const resolved = resolveContextWindow(model, overrides);
346
+ if (resolved.source === 'env-override' || resolved.source === 'config')
347
+ return resolved;
348
+ return contextTokens > resolved.tokens
349
+ ? { tokens: ONE_MILLION_CONTEXT_TOKENS, source: resolved.source }
350
+ : resolved;
288
351
  }
289
352
  /**
290
353
  * Conservative transcript-estimate fallback. Recursively searches
@@ -298,9 +361,11 @@ function resolveContextWindowTokens(model, contextTokens) {
298
361
  *
299
362
  * Window model resolution is env-first: when `envModel` is present, its id
300
363
  * (which Claude Code stamps with the `[1M]` / `[200K]` suffix) drives the
301
- * window; otherwise the transcript `message.model` is used.
364
+ * window; otherwise the transcript `message.model` is used. Explicit
365
+ * overrides (env `PEAKS_CONTEXT_WINDOW_TOKENS` / config `context.windowTokens`)
366
+ * sit above both and are reported via `capacitySource`.
302
367
  */
303
- function readClaudeTranscriptEstimate(outerSessionId, envModel) {
368
+ function readClaudeTranscriptEstimate(outerSessionId, envModel, overrides = {}) {
304
369
  const projectsDir = join(homedir(), '.claude', 'projects');
305
370
  const path = findTranscriptJsonl(projectsDir, outerSessionId);
306
371
  if (path === null)
@@ -309,9 +374,10 @@ function readClaudeTranscriptEstimate(outerSessionId, envModel) {
309
374
  if (latest === null)
310
375
  return null;
311
376
  const model = envModel !== undefined && envModel.trim().length > 0 ? envModel : latest.model;
312
- const contextWindowTokens = resolveContextWindowTokens(model, latest.contextTokens);
377
+ const resolved = resolveContextWindowTokens(model, latest.contextTokens, overrides);
378
+ const contextWindowTokens = resolved.tokens;
313
379
  const ratio = Math.min(1, latest.contextTokens / contextWindowTokens);
314
- return { ratio, contextTokens: latest.contextTokens, contextWindowTokens };
380
+ return { ratio, contextTokens: latest.contextTokens, contextWindowTokens, capacitySource: resolved.source };
315
381
  }
316
382
  /**
317
383
  * Claude Code's vendor-specific context-percent fallback, exposed as
@@ -344,13 +410,17 @@ function readContextPercentFallback(input) {
344
410
  }
345
411
  if (typeof input.outerSessionId === 'string' && input.outerSessionId.length > 0) {
346
412
  const envModel = resolveClaudeModelFromEnv(input.env);
347
- const estimate = readClaudeTranscriptEstimate(input.outerSessionId, envModel);
413
+ const estimate = readClaudeTranscriptEstimate(input.outerSessionId, envModel, {
414
+ env: input.env,
415
+ configWindowTokens: input.configWindowTokens
416
+ });
348
417
  if (estimate !== null) {
349
418
  return {
350
419
  ratio: estimate.ratio,
351
420
  source: 'transcript-estimate',
352
421
  rawTokens: estimate.contextTokens,
353
422
  capacityTokens: estimate.contextWindowTokens,
423
+ capacitySource: estimate.capacitySource,
354
424
  ide,
355
425
  capturedAt
356
426
  };
@@ -401,7 +471,7 @@ export const CLAUDE_CODE_ADAPTER = {
401
471
  // Pathway = 'ide-native' (not 'shell-exec') so the dispatcher
402
472
  // routes main-session compacts through the PreToolUse hook in
403
473
  // `.claude/settings.local.json`. The hook fires
404
- // `peaks compact auto` on the NEXT Bash/Task tool
474
+ // `peaks code auto-compact` on the NEXT Bash/Task tool
405
475
  // call from the runner, which in-band spawns `claude --compact`
406
476
  // against the CURRENT runner — not a child process (the
407
477
  // shell-exec spawn-new-claude bug documented in
@@ -412,7 +482,7 @@ export const CLAUDE_CODE_ADAPTER = {
412
482
  envVarForContextPercent: 'CLAUDE_CONTEXT_USAGE_PERCENT',
413
483
  compactCommand: 'claude --compact',
414
484
  compactPathway: 'ide-native',
415
- postCompactDetectCommand: 'peaks compact auto --json',
485
+ postCompactDetectCommand: 'peaks code auto-compact --json',
416
486
  readContextPercentFallback
417
487
  },
418
488
  // Slice #011: standards profile. Claude Code reads its constitution at
@@ -197,7 +197,7 @@ export interface IdeCompactProfile {
197
197
  readonly compactPathway: 'shell-exec' | 'ide-native' | 'llm-self-compress' | 'noop';
198
198
  /**
199
199
  * Optional command the runner invokes post-compact to confirm
200
- * ratio dropped (e.g. `peaks compact auto --json`). When omitted,
200
+ * ratio dropped (e.g. `peaks code auto-compact --json`). When omitted,
201
201
  * the orchestrator polls `envVarForContextPercent` directly.
202
202
  */
203
203
  readonly postCompactDetectCommand?: string;
@@ -239,6 +239,15 @@ export interface ContextPercentFallbackInput {
239
239
  readonly outerSessionId?: string | undefined;
240
240
  /** Injectable env (defaults to process.env in the reader). */
241
241
  readonly env?: NodeJS.ProcessEnv | undefined;
242
+ /**
243
+ * Raw `context.windowTokens` value from the merged config (project layer
244
+ * over user layer), read by the generic reader so the adapter stays free
245
+ * of config-path knowledge. UNVALIDATED — the adapter validates it via
246
+ * `parseContextWindowOverride` and warns on a bad value.
247
+ *
248
+ * Slice 2026-09-09-context-window-override.
249
+ */
250
+ readonly configWindowTokens?: unknown;
242
251
  }
243
252
  /**
244
253
  * Per-IDE standards-file location + format profile. Used by the
@@ -8,7 +8,10 @@ export type ConfirmationOptions = {
8
8
  forceConfirm?: boolean | undefined;
9
9
  };
10
10
  export declare class ConfirmationRequiredError extends Error {
11
- constructor(transitionKey: TransitionKey);
11
+ readonly transitionKey: TransitionKey;
12
+ readonly mode: SkillPresenceMode;
13
+ readonly nextActions: readonly string[];
14
+ constructor(transitionKey: TransitionKey, mode: SkillPresenceMode);
12
15
  }
13
16
  export declare function requireUserConfirmation(options: ConfirmationOptions): Promise<void>;
14
17
  export {};