peaks-loop 4.0.32 → 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 (111) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README-en.md +3 -4
  3. package/README.md +20 -28
  4. package/dist/cli/commands/_register.js +4 -3
  5. package/dist/cli/commands/audit-commands.js +12 -39
  6. package/dist/cli/commands/capability-commands.d.ts +6 -0
  7. package/dist/cli/commands/capability-commands.js +20 -2
  8. package/dist/cli/commands/code-commands.js +2 -0
  9. package/dist/cli/commands/code-mode-gate-commands.js +3 -3
  10. package/dist/cli/commands/code-mode-status-command.d.ts +17 -0
  11. package/dist/cli/commands/code-mode-status-command.js +51 -0
  12. package/dist/cli/commands/code-run-command.js +8 -1
  13. package/dist/cli/commands/code-runtime-commands.js +13 -5
  14. package/dist/cli/commands/core/skill-command.js +1 -1
  15. package/dist/cli/commands/ecc-commands.js +3 -2
  16. package/dist/cli/commands/project-commands.js +1 -1
  17. package/dist/cli/commands/request-commands.js +3 -7
  18. package/dist/cli/commands/session-24h-mode.js +9 -1
  19. package/dist/cli/commands/shadcn-commands.d.ts +32 -0
  20. package/dist/cli/commands/shadcn-commands.js +120 -0
  21. package/dist/cli/commands/slice-commands.js +1 -4
  22. package/dist/services/24h-mode/auto-engage.d.ts +39 -0
  23. package/dist/services/24h-mode/auto-engage.js +46 -0
  24. package/dist/services/24h-mode/decider.d.ts +11 -5
  25. package/dist/services/24h-mode/decider.js +13 -13
  26. package/dist/services/audit/enforcers/active-skill-resolver.d.ts +3 -1
  27. package/dist/services/audit/enforcers/active-skill-resolver.js +3 -5
  28. package/dist/services/audit/static-service.d.ts +1 -1
  29. package/dist/services/audit/static-service.js +1 -1
  30. package/dist/services/code/auto-compact-lifecycle.d.ts +1 -1
  31. package/dist/services/code/auto-compact-lifecycle.js +1 -1
  32. package/dist/services/code/auto-compact-modes.d.ts +1 -1
  33. package/dist/services/code/auto-compact-modes.js +1 -1
  34. package/dist/services/code/auto-compact-orchestrator.js +18 -22
  35. package/dist/services/code/mode-gate.d.ts +6 -2
  36. package/dist/services/code/mode-gate.js +9 -5
  37. package/dist/services/code/orchestrator-can-do.js +2 -2
  38. package/dist/services/code-review/ecc-bridge.d.ts +94 -15
  39. package/dist/services/code-review/ecc-bridge.js +133 -13
  40. package/dist/services/config/config-service.d.ts +17 -0
  41. package/dist/services/config/config-service.js +34 -0
  42. package/dist/services/context/auto-compact-dispatcher.d.ts +2 -2
  43. package/dist/services/context/auto-compact-dispatcher.js +4 -4
  44. package/dist/services/context/auto-compact-reader.js +8 -1
  45. package/dist/services/context/auto-compact-types.d.ts +13 -1
  46. package/dist/services/context/build-dispatch-system-prompt.js +2 -2
  47. package/dist/services/context/context-schema.d.ts +1 -1
  48. package/dist/services/dashboard/project-dashboard-service.d.ts +0 -7
  49. package/dist/services/dashboard/project-dashboard-service.js +1 -9
  50. package/dist/services/hooks/auto-compact-hook-install.d.ts +2 -2
  51. package/dist/services/hooks/auto-compact-hook-install.js +2 -2
  52. package/dist/services/ide/adapters/claude-code-adapter.d.ts +51 -0
  53. package/dist/services/ide/adapters/claude-code-adapter.js +84 -14
  54. package/dist/services/ide/ide-types.d.ts +10 -1
  55. package/dist/services/mode/mode-enforcement.d.ts +4 -1
  56. package/dist/services/mode/mode-enforcement.js +26 -28
  57. package/dist/services/mode/mode-status-service.d.ts +81 -0
  58. package/dist/services/mode/mode-status-service.js +108 -0
  59. package/dist/services/preferences/preferences-types.d.ts +16 -7
  60. package/dist/services/recommendations/capability-seed-items.js +0 -1
  61. package/dist/services/recommendations/installed-capability-detector.d.ts +33 -0
  62. package/dist/services/recommendations/installed-capability-detector.js +76 -0
  63. package/dist/services/scan/orphan-service.js +1 -1
  64. package/dist/services/skills/presence-lease-service.d.ts +21 -0
  65. package/dist/services/skills/presence-lease-service.js +26 -0
  66. package/dist/services/skills/skill-presence-service.d.ts +10 -1
  67. package/dist/services/skills/skill-presence-service.js +32 -4
  68. package/dist/services/skills/skill-statusline-service.js +6 -3
  69. package/dist/services/slice/multi-pass-orchestrator.js +0 -13
  70. package/dist/services/slice/slice-decompose-import-edges.d.ts +2 -2
  71. package/dist/services/slice/slice-decompose-import-edges.js +1 -29
  72. package/dist/services/slice/slice-decompose-runners.d.ts +2 -4
  73. package/dist/services/slice/slice-decompose-runners.js +1 -23
  74. package/dist/services/slice/slice-decompose-service.d.ts +3 -4
  75. package/dist/services/slice/slice-decompose-service.js +10 -29
  76. package/dist/services/slice/slice-decompose-types.d.ts +6 -47
  77. package/dist/services/slice/slice-decompose-types.js +3 -2
  78. package/dist/services/slice/slice-topology-types.d.ts +2 -3
  79. package/package.json +5 -5
  80. package/schemas/decomposition-v1.json +0 -12
  81. package/schemas/decomposition-v2.json +1 -15
  82. package/skills/bee/peaks-prd/SKILL.md +0 -1
  83. package/skills/bee/peaks-rd/SKILL.md +1 -1
  84. package/skills/bee/peaks-rd/references/external-references.md +1 -3
  85. package/skills/bee/peaks-rd/references/parallel-review-fanout.md +7 -2
  86. package/skills/bee/peaks-rd/references/rd-fanout-contracts.md +22 -8
  87. package/skills/bee/peaks-rd/references/rd-runbook.md +0 -2
  88. package/skills/bee/peaks-txt/SKILL.md +0 -12
  89. package/skills/peaks-code/SKILL.md +9 -9
  90. package/skills/peaks-code/references/mode-selection.md +7 -3
  91. package/skills/peaks-code/references/periodic-checkpoint.md +4 -4
  92. package/skills/peaks-code/references/runbook.md +10 -5
  93. package/skills/peaks-code/references/session-overload-signal-index.md +2 -2
  94. package/skills/peaks-code/references/standards-preflight.md +1 -1
  95. package/skills/peaks-code/references/startup-sequence.md +1 -1
  96. package/skills/peaks-code/references/step-0-8-gate.md +1 -1
  97. package/skills/peaks-code/references/swarm-dispatch-contract.md +2 -2
  98. package/skills/peaks-code/references/workflow-gates-and-types.md +1 -1
  99. package/skills/peaks-doctor/SKILL.md +1 -1
  100. package/skills/peaks-slice-decompose/SKILL.md +0 -1
  101. package/skills/peaks-slice-decompose/references/v2-schema.md +1 -12
  102. package/dist/cli/commands/understand-commands.d.ts +0 -3
  103. package/dist/cli/commands/understand-commands.js +0 -148
  104. package/dist/services/understand/hybrid-types.d.ts +0 -32
  105. package/dist/services/understand/hybrid-types.js +0 -1
  106. package/dist/services/understand/understand-hybrid-service.d.ts +0 -10
  107. package/dist/services/understand/understand-hybrid-service.js +0 -100
  108. package/dist/services/understand/understand-scan-service.d.ts +0 -28
  109. package/dist/services/understand/understand-scan-service.js +0 -170
  110. package/dist/services/understand/understand-types.d.ts +0 -50
  111. package/dist/services/understand/understand-types.js +0 -1
@@ -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 {};
@@ -1,4 +1,3 @@
1
- import * as readline from 'node:readline';
2
1
  import { getSkillPresence } from '../skills/skill-presence-service.js';
3
2
  const ASSISTED_CONFIRM_TRANSITIONS = new Set([
4
3
  'prd:confirmed-by-user',
@@ -6,7 +5,9 @@ const ASSISTED_CONFIRM_TRANSITIONS = new Set([
6
5
  'qa:verdict-issued'
7
6
  ]);
8
7
  export function requiresConfirmation(mode, transitionKey) {
9
- if (mode === 'full-auto' || mode === 'swarm') {
8
+ // Slice 2026-09-09-mode-consolidation: `swarm` removed as a mode; the
9
+ // two auto-proceed peers are `full-auto` and `24h`.
10
+ if (mode === 'full-auto' || mode === '24h') {
10
11
  return false;
11
12
  }
12
13
  if (mode === 'strict') {
@@ -22,12 +23,23 @@ function describeTransition(transitionKey) {
22
23
  return `Transition ${role.toUpperCase()} → ${state}`;
23
24
  }
24
25
  export class ConfirmationRequiredError extends Error {
25
- constructor(transitionKey) {
26
+ transitionKey;
27
+ mode;
28
+ nextActions;
29
+ constructor(transitionKey, mode) {
26
30
  const description = describeTransition(transitionKey);
27
- super(`Confirmation required for: ${description}\n` +
28
- 'Add --confirm to proceed non-interactively, or run in an interactive terminal.\n' +
29
- 'In assisted/strict mode, major workflow boundaries require explicit user approval.');
31
+ const nextActions = [
32
+ `Ask the user via AskUserQuestion whether to proceed with ${description} in ${mode} mode.`,
33
+ 'If the user approves, re-run the same command with --confirm.'
34
+ ];
35
+ super(`Confirmation required for: ${description} (mode: ${mode})\n` +
36
+ `${nextActions[0]}\n` +
37
+ `${nextActions[1]}\n` +
38
+ 'No terminal prompt is available: this gate never reads stdin.');
30
39
  this.name = 'ConfirmationRequiredError';
40
+ this.transitionKey = transitionKey;
41
+ this.mode = mode;
42
+ this.nextActions = nextActions;
31
43
  }
32
44
  }
33
45
  export async function requireUserConfirmation(options) {
@@ -40,11 +52,11 @@ export async function requireUserConfirmation(options) {
40
52
  if (!requiresConfirmation(mode, options.transitionKey)) {
41
53
  return;
42
54
  }
43
- // --confirm flag bypasses interactive prompt
55
+ // --confirm flag bypasses the gate
44
56
  if (options.confirmed) {
45
57
  return;
46
58
  }
47
- // PEAKS_AUTO_CONFIRM=1 only works for full-auto/swarm (already returned above)
59
+ // PEAKS_AUTO_CONFIRM=1 only works for full-auto/24h (already returned above)
48
60
  // For assisted/strict, env var is ignored unless --force-confirm is also set
49
61
  if (process.env.PEAKS_AUTO_CONFIRM === '1') {
50
62
  if (options.forceConfirm) {
@@ -53,7 +65,7 @@ export async function requireUserConfirmation(options) {
53
65
  'This bypasses user confirmation. Use with caution.');
54
66
  return;
55
67
  }
56
- throw new ConfirmationRequiredError(options.transitionKey);
68
+ throw new ConfirmationRequiredError(options.transitionKey, mode);
57
69
  }
58
70
  // --force-confirm without env var
59
71
  if (options.forceConfirm) {
@@ -62,23 +74,9 @@ export async function requireUserConfirmation(options) {
62
74
  'This bypasses user confirmation. Use with caution.');
63
75
  return;
64
76
  }
65
- // Interactive prompt
66
- const rl = readline.createInterface({
67
- input: process.stdin,
68
- output: process.stderr
69
- });
70
- return new Promise((resolve, reject) => {
71
- const description = describeTransition(options.transitionKey);
72
- const prompt = `\n[CONFIRM] ${description}\nProceed? (y/N) `;
73
- rl.question(prompt, (answer) => {
74
- rl.close();
75
- const normalized = answer.trim().toLowerCase();
76
- if (normalized === 'y' || normalized === 'yes') {
77
- resolve();
78
- }
79
- else {
80
- reject(new ConfirmationRequiredError(options.transitionKey));
81
- }
82
- });
83
- });
77
+ // No bypass flag: refuse immediately. Never read stdin — in an LLM-driven
78
+ // session there is no TTY, so a prompt would hang forever, and a `y/N`
79
+ // terminal prompt would also violate the Human-NL-Choice-Only rule (the
80
+ // user answers via AskUserQuestion, never by typing into a shell).
81
+ throw new ConfirmationRequiredError(options.transitionKey, mode);
84
82
  }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Slice 2026-09-09-mode-consolidation (Slice D): one read for the whole
3
+ * stacked mode picture.
4
+ *
5
+ * The autonomy model has two layers that used to be read by nothing in
6
+ * common:
7
+ *
8
+ * - MODE — `SkillPresenceMode` on the presence lease: the autonomy
9
+ * level (`full-auto | assisted | strict | 24h`).
10
+ * - STATE — `.peaks/_runtime/<sid>/24h-state.json`, the 6-state
11
+ * machine (`IDLE / BRAINSTORM / USER_CONFIRM / 24H_ACTIVE /
12
+ * WAITING_USER / HANDOFF`): the long-run PHASE *inside* the `24h`
13
+ * mode.
14
+ *
15
+ * This module is the single place that reads both, plus the Job-mode
16
+ * decision and the resolved auto-compact profile, so `peaks code mode
17
+ * status` (and any future consumer) does not re-derive the stack.
18
+ *
19
+ * Read-only + fail-soft: every sub-read is wrapped so a missing or
20
+ * malformed artifact degrades to `null` / `false` rather than throwing.
21
+ */
22
+ import { type SkillPresenceMode } from '../skills/skill-presence-service.js';
23
+ import type { State24hSnapshot } from '../24h-mode/state.js';
24
+ import { type AutoCompactMode } from '../code/auto-compact-modes.js';
25
+ export interface ResolvedPresenceMode {
26
+ readonly mode: SkillPresenceMode | null;
27
+ readonly skill: string | null;
28
+ readonly source: 'canonical' | 'file' | 'env' | 'none';
29
+ }
30
+ export interface ModeStatus {
31
+ /** Autonomy level from the presence lease; `null` when no lease / no mode recorded. */
32
+ readonly mode: SkillPresenceMode | null;
33
+ /** Active skill name from the same lease read (display aid). */
34
+ readonly skill: string | null;
35
+ /** Where the mode came from. */
36
+ readonly modeSource: 'canonical' | 'file' | 'env' | 'none';
37
+ /** `true` when the resolved mode is `24h`. */
38
+ readonly is24h: boolean;
39
+ /**
40
+ * The 24h internal state machine snapshot. Populated only when
41
+ * `is24h` is true (the phase is meaningless outside the 24h mode);
42
+ * `null` when the mode is not `24h` or the snapshot is unreadable.
43
+ */
44
+ readonly h24State: State24hSnapshot | null;
45
+ /** Job-mode flag from `job-shape.json` (`false` when undecided / unreadable). */
46
+ readonly jobMode: boolean;
47
+ /** Resolved auto-compact profile: `partial` iff the mode is `24h`. */
48
+ readonly autoCompactProfile: AutoCompactMode;
49
+ /** Thresholds backing `autoCompactProfile` (display aid). */
50
+ readonly autoCompactThresholds: {
51
+ readonly autoFire: number;
52
+ readonly preCompact: number;
53
+ readonly redLine: number;
54
+ };
55
+ }
56
+ /**
57
+ * Read the active presence mode via the canonical lease projection.
58
+ * Never throws.
59
+ */
60
+ export declare function resolvePresenceMode(projectRoot: string): ResolvedPresenceMode;
61
+ /**
62
+ * Resolve the auto-compact profile from the MODE, not from the 24h
63
+ * state machine. `24h` mode → `partial` (earlier thresholds for
64
+ * long-run sessions); every other mode → `standard`.
65
+ *
66
+ * Slice 2026-09-09-mode-consolidation: this replaced the previous
67
+ * `24h-state.state === '24H_ACTIVE'` mapping. The state machine is the
68
+ * long-run *phase*; the mode is the autonomy level the user (or the
69
+ * T3/T4 auto-engage) selected. Keying off the mode means a `24h`
70
+ * session keeps the partial profile even while it is in
71
+ * `WAITING_USER`, which is the correct long-run behaviour.
72
+ */
73
+ export declare function resolveAutoCompactProfile(projectRoot: string): AutoCompactMode;
74
+ /**
75
+ * Assemble the full stacked status. `sessionId` defaults to the
76
+ * canonical binding when omitted.
77
+ */
78
+ export declare function resolveModeStatus(input: {
79
+ projectRoot: string;
80
+ sessionId?: string | null | undefined;
81
+ }): ModeStatus;
@@ -0,0 +1,108 @@
1
+ /**
2
+ * Slice 2026-09-09-mode-consolidation (Slice D): one read for the whole
3
+ * stacked mode picture.
4
+ *
5
+ * The autonomy model has two layers that used to be read by nothing in
6
+ * common:
7
+ *
8
+ * - MODE — `SkillPresenceMode` on the presence lease: the autonomy
9
+ * level (`full-auto | assisted | strict | 24h`).
10
+ * - STATE — `.peaks/_runtime/<sid>/24h-state.json`, the 6-state
11
+ * machine (`IDLE / BRAINSTORM / USER_CONFIRM / 24H_ACTIVE /
12
+ * WAITING_USER / HANDOFF`): the long-run PHASE *inside* the `24h`
13
+ * mode.
14
+ *
15
+ * This module is the single place that reads both, plus the Job-mode
16
+ * decision and the resolved auto-compact profile, so `peaks code mode
17
+ * status` (and any future consumer) does not re-derive the stack.
18
+ *
19
+ * Read-only + fail-soft: every sub-read is wrapped so a missing or
20
+ * malformed artifact degrades to `null` / `false` rather than throwing.
21
+ */
22
+ import { resolveActiveSkillForCaller } from '../audit/enforcers/active-skill-resolver.js';
23
+ import { getCurrentSessionId } from '../skills/skill-presence-service.js';
24
+ // Import the 24h store directly (NOT the `24h-mode/index.js` barrel) —
25
+ // the barrel re-exports `decider.ts`, which imports this module, and
26
+ // that would form an import cycle.
27
+ import { read24hState } from '../24h-mode/store.js';
28
+ import { readJobShapeDecision } from '../code/job-shape-decision.js';
29
+ import { AUTO_COMPACT_THRESHOLDS } from '../code/auto-compact-modes.js';
30
+ /**
31
+ * Read the active presence mode via the canonical lease projection.
32
+ * Never throws.
33
+ */
34
+ export function resolvePresenceMode(projectRoot) {
35
+ try {
36
+ const resolved = resolveActiveSkillForCaller(projectRoot);
37
+ const mode = resolved.mode;
38
+ return {
39
+ mode: mode === null ? null : mode,
40
+ skill: resolved.skill,
41
+ source: resolved.source
42
+ };
43
+ }
44
+ catch {
45
+ return { mode: null, skill: null, source: 'none' };
46
+ }
47
+ }
48
+ /**
49
+ * Resolve the auto-compact profile from the MODE, not from the 24h
50
+ * state machine. `24h` mode → `partial` (earlier thresholds for
51
+ * long-run sessions); every other mode → `standard`.
52
+ *
53
+ * Slice 2026-09-09-mode-consolidation: this replaced the previous
54
+ * `24h-state.state === '24H_ACTIVE'` mapping. The state machine is the
55
+ * long-run *phase*; the mode is the autonomy level the user (or the
56
+ * T3/T4 auto-engage) selected. Keying off the mode means a `24h`
57
+ * session keeps the partial profile even while it is in
58
+ * `WAITING_USER`, which is the correct long-run behaviour.
59
+ */
60
+ export function resolveAutoCompactProfile(projectRoot) {
61
+ return resolvePresenceMode(projectRoot).mode === '24h' ? 'partial' : 'standard';
62
+ }
63
+ /**
64
+ * Assemble the full stacked status. `sessionId` defaults to the
65
+ * canonical binding when omitted.
66
+ */
67
+ export function resolveModeStatus(input) {
68
+ const { projectRoot } = input;
69
+ const presence = resolvePresenceMode(projectRoot);
70
+ const is24h = presence.mode === '24h';
71
+ let sessionId = input.sessionId ?? null;
72
+ if (sessionId === null) {
73
+ try {
74
+ sessionId = getCurrentSessionId(projectRoot);
75
+ }
76
+ catch {
77
+ sessionId = null;
78
+ }
79
+ }
80
+ let h24State = null;
81
+ if (is24h && sessionId !== null) {
82
+ try {
83
+ h24State = read24hState(projectRoot, sessionId);
84
+ }
85
+ catch {
86
+ h24State = null;
87
+ }
88
+ }
89
+ let jobMode = false;
90
+ if (sessionId !== null) {
91
+ try {
92
+ jobMode = readJobShapeDecision(projectRoot, sessionId).decision.isJob;
93
+ }
94
+ catch {
95
+ jobMode = false;
96
+ }
97
+ }
98
+ return {
99
+ mode: presence.mode,
100
+ skill: presence.skill,
101
+ modeSource: presence.source,
102
+ is24h,
103
+ h24State,
104
+ jobMode,
105
+ autoCompactProfile: is24h ? 'partial' : 'standard',
106
+ autoCompactThresholds: AUTO_COMPACT_THRESHOLDS[is24h ? 'partial' : 'standard']
107
+ };
108
+ }
@@ -76,7 +76,16 @@ export interface ProjectPreferences {
76
76
  * dispatch *shape* (worker graph vs flat dispatch).
77
77
  */
78
78
  readonly swarmMode: boolean;
79
+ /**
80
+ * Legacy UA opt-in prompt decision. The UA knowledge-graph surface was
81
+ * removed in 2026-09-09 (project analysis is codegraph-first); the field
82
+ * is retained only so legacy `.peaks/preferences.json` files keep parsing.
83
+ */
79
84
  readonly uaPrompt: UaPromptDecision;
85
+ /**
86
+ * Legacy AgentShield opt-in prompt decision. The ECC AgentShield
87
+ * subprocess was removed in 4.0.0-beta.11; retained for legacy parse.
88
+ */
80
89
  readonly agentShieldPrompt: UaPromptDecision;
81
90
  readonly classifyConservatism: ClassifyConservatism;
82
91
  readonly classifyRules: ClassifyRuleOverrides;
@@ -84,15 +93,15 @@ export interface ProjectPreferences {
84
93
  /** Loop Autonomous (L4 14.5) toggle. Default: false — never auto-enable. */
85
94
  readonly loopAutonomousEnabled: boolean;
86
95
  /**
87
- * L2.3 P2-a: ECC AgentShield subprocess toggle. Default: false.
96
+ * L2.3 P2-a: ECC AgentShield subprocess toggle — **frozen, no-op**.
88
97
  *
89
- * When true, `peaks audit static` spawns `npx ecc-agentshield scan --json`
90
- * and merges its findings into the audit report. When false (default),
91
- * the audit runs peaks-loop-only and the subprocess is never spawned.
98
+ * Slice 3 of 4.0.0-beta.11 removed the ECC AgentShield subprocess
99
+ * entirely (upstream ships no `ecc-agentshield` binary). The field is
100
+ * retained only so legacy `.peaks/preferences.json` files still parse
101
+ * (`static-service.ts` calls `loadPreferences` for back-compat and
102
+ * ignores the value). Setting it to `true` has no effect.
92
103
  *
93
- * The preference is independent of whether ECC is installed — i.e.
94
- * `agentShieldEnabled: true` with ECC missing surfaces a soft
95
- * "ECC not installed" warning and the audit still completes.
104
+ * Default: false.
96
105
  */
97
106
  readonly agentShieldEnabled: boolean;
98
107
  /**
@@ -98,7 +98,6 @@ export const seedCapabilityItems = [
98
98
  capability('gitnexus.repo-intelligence', 'gitnexus', 'GitNexus Repository Intelligence', 'cli', 'repo-intelligence', ['engineer'], 'medium', 'local-repo-scan', 'Use local project scanning through Peaks RD.', 'Repository Intelligence', '仓库智能分析', 'Repository intelligence should be proxied through Peaks before use.', '仓库智能分析应先通过 Peaks 代理边界再使用。'),
99
99
  capability('claude-code-best-practice.workflow-guidance', 'claude-code-best-practice', 'Claude Code Best Practice', 'doc', 'workflow-guidance', ['engineer'], 'low', 'peaks-built-in-rules', 'Use Peaks built-in workflow and review rules.', 'Claude Code Best Practice', 'Claude Code 最佳实践', 'Guidance for Claude Code engineering workflows.', 'Claude Code 工程工作流指导。'),
100
100
  capability('superpowers.workflow-methodology', 'superpowers', 'Superpowers Methodology', 'workflow', 'workflow-methodology', ['product', 'engineer'], 'low', 'peaks-workflow-route', 'Use Peaks route/autonomous plans.', 'Superpowers Methodology', 'Superpowers 方法论', 'Workflow and artifact methodology reference.', '工作流与 artifact 方法论参考。'),
101
- capability('understand-anything.knowledge-graph', 'understand-anything', 'Understand Anything Knowledge Graph', 'skill', 'project-analysis', ['engineer', 'product', 'qa'], 'low', 'peaks-codegraph-or-rd-scan', 'Use peaks codegraph or peaks RD project scan when Understand Anything is not installed. Optionally suggest installing it via Claude Code: /plugin marketplace add Lum1104/Understand-Anything then /plugin install understand-anything.', 'Understand Anything Knowledge Graph', 'Understand Anything 知识图谱', 'Multi-agent project knowledge graph that Peaks reads from .understand-anything/knowledge-graph.json when it has been generated by the Understand Anything plugin in Claude Code.', '通过 Claude Code 的 Understand Anything 插件生成的多 agent 知识图谱,Peaks 在 .understand-anything/knowledge-graph.json 存在时读取它作为项目分析证据。'),
102
101
  capability('penpot.design-source', 'penpot', 'Penpot Design Source', 'doc', 'design-source', ['designer', 'engineer'], 'medium', 'manual-design-input', 'Ask for exported design notes or screenshots.', 'Penpot Design Source', 'Penpot 设计来源', 'Design-source reference for UI planning.', '用于 UI 规划的设计来源参考。'),
103
102
  capability('gstack.product-stack-guidance', 'gstack', 'Product Stack Guidance', 'doc', 'product-guidance', ['product'], 'low', 'peaks-prd', 'Use peaks-prd for product goals and non-goals.', 'Product Stack Guidance', '产品栈指导', 'Product and startup stack guidance reference.', '产品与创业栈指导参考。'),
104
103
  capability('awesome-design-md.design-reference', 'awesome-design-md', 'Design Markdown Reference', 'doc', 'design-reference', ['designer', 'engineer'], 'low', 'peaks-ui-visual-direction', 'Use peaks-ui to define visual direction and interaction constraints.', 'Design Reference', '设计参考', 'Design markdown examples and inspiration.', '设计 markdown 示例与灵感。'),