peaks-loop 4.0.33 → 4.0.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/dist/cli/commands/code-commands.js +2 -0
- package/dist/cli/commands/code-mode-gate-commands.js +3 -3
- package/dist/cli/commands/code-mode-status-command.d.ts +17 -0
- package/dist/cli/commands/code-mode-status-command.js +51 -0
- package/dist/cli/commands/code-run-command.js +8 -1
- package/dist/cli/commands/code-runtime-commands.js +13 -5
- package/dist/cli/commands/core/memory-command.js +61 -3
- package/dist/cli/commands/core/skill-command.js +1 -1
- package/dist/cli/commands/dispatch-commands.js +4 -2
- package/dist/cli/commands/memory-commands.d.ts +35 -0
- package/dist/cli/commands/memory-commands.js +119 -10
- package/dist/cli/commands/request-commands.js +3 -7
- package/dist/cli/commands/session-24h-mode.js +9 -1
- package/dist/services/24h-mode/auto-engage.d.ts +39 -0
- package/dist/services/24h-mode/auto-engage.js +46 -0
- package/dist/services/24h-mode/decider.d.ts +11 -5
- package/dist/services/24h-mode/decider.js +13 -13
- package/dist/services/audit/enforcers/active-skill-resolver.d.ts +3 -1
- package/dist/services/audit/enforcers/active-skill-resolver.js +3 -5
- package/dist/services/code/auto-compact-lifecycle.d.ts +1 -1
- package/dist/services/code/auto-compact-lifecycle.js +1 -1
- package/dist/services/code/auto-compact-modes.d.ts +1 -1
- package/dist/services/code/auto-compact-modes.js +1 -1
- package/dist/services/code/auto-compact-orchestrator.js +18 -22
- package/dist/services/code/mode-gate.d.ts +6 -2
- package/dist/services/code/mode-gate.js +9 -5
- package/dist/services/code/orchestrator-can-do.js +2 -2
- package/dist/services/config/config-service.d.ts +17 -0
- package/dist/services/config/config-service.js +34 -0
- package/dist/services/context/auto-compact-dispatcher.d.ts +2 -2
- package/dist/services/context/auto-compact-dispatcher.js +4 -4
- package/dist/services/context/auto-compact-reader.js +8 -1
- package/dist/services/context/auto-compact-types.d.ts +13 -1
- package/dist/services/context/build-dispatch-system-prompt.js +2 -2
- package/dist/services/context/memory-index-reader.d.ts +26 -0
- package/dist/services/context/memory-index-reader.js +62 -30
- package/dist/services/context/memory-preflight-config.d.ts +33 -0
- package/dist/services/context/memory-preflight-config.js +32 -2
- package/dist/services/context/memory-preflight-service.d.ts +20 -1
- package/dist/services/context/memory-preflight-service.js +198 -31
- package/dist/services/doctor/doctor-service/checks/l3-memory-health.d.ts +19 -2
- package/dist/services/doctor/doctor-service/checks/l3-memory-health.js +143 -19
- package/dist/services/hooks/auto-compact-hook-install.d.ts +2 -2
- package/dist/services/hooks/auto-compact-hook-install.js +2 -2
- package/dist/services/ide/adapters/claude-code-adapter.d.ts +51 -0
- package/dist/services/ide/adapters/claude-code-adapter.js +84 -14
- package/dist/services/ide/ide-types.d.ts +10 -1
- package/dist/services/job/job-types.d.ts +3 -3
- package/dist/services/memory/memory-ingest-service.d.ts +79 -0
- package/dist/services/memory/memory-ingest-service.js +225 -0
- package/dist/services/memory/memory-rotate-service.d.ts +88 -0
- package/dist/services/memory/memory-rotate-service.js +373 -0
- package/dist/services/memory/project-memory-service/index/ranking.d.ts +9 -1
- package/dist/services/memory/project-memory-service/index/ranking.js +25 -13
- package/dist/services/memory/project-memory-service/index/reindex.d.ts +75 -0
- package/dist/services/memory/project-memory-service/index/reindex.js +207 -0
- package/dist/services/memory/project-memory-service/index/search.js +14 -24
- package/dist/services/memory/project-memory-service/index.d.ts +7 -3
- package/dist/services/memory/project-memory-service/index.js +6 -2
- package/dist/services/memory/project-memory-service/parsers/frontmatter.d.ts +75 -3
- package/dist/services/memory/project-memory-service/parsers/frontmatter.js +113 -24
- package/dist/services/memory/project-memory-service/types.d.ts +31 -1
- package/dist/services/memory/project-memory-service/types.js +76 -1
- package/dist/services/mode/mode-enforcement.d.ts +4 -1
- package/dist/services/mode/mode-enforcement.js +26 -28
- package/dist/services/mode/mode-status-service.d.ts +81 -0
- package/dist/services/mode/mode-status-service.js +108 -0
- package/dist/services/preferences/preferences-types.d.ts +14 -0
- package/dist/services/preferences/preferences-types.js +8 -0
- package/dist/services/share/run-state-contract.d.ts +1 -1
- package/dist/services/skills/presence-lease-service.d.ts +21 -0
- package/dist/services/skills/presence-lease-service.js +26 -0
- package/dist/services/skills/skill-presence-service.d.ts +10 -1
- package/dist/services/skills/skill-presence-service.js +32 -4
- package/dist/services/skills/skill-statusline-service.js +6 -3
- package/package.json +5 -5
- package/skills/peaks-code/SKILL.md +10 -10
- package/skills/peaks-code/references/mode-selection.md +7 -3
- package/skills/peaks-code/references/periodic-checkpoint.md +4 -4
- package/skills/peaks-code/references/runbook.md +16 -5
- package/skills/peaks-code/references/session-overload-signal-index.md +2 -2
- package/skills/peaks-code/references/standards-preflight.md +1 -1
- package/skills/peaks-code/references/startup-sequence.md +1 -1
- package/skills/peaks-code/references/step-0-8-gate.md +1 -1
- package/skills/peaks-code/references/step-11-memory-sediment.md +35 -0
- package/skills/peaks-code/references/swarm-dispatch-contract.md +2 -2
- package/skills/peaks-code/references/workflow-gates-and-types.md +1 -1
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slice 2026-09-09-mode-consolidation (Slice B): auto-engage may set the
|
|
3
|
+
* 24h MODE — and ONLY the 24h mode.
|
|
4
|
+
*
|
|
5
|
+
* `24h` is the single mode that may be set without an explicit user pick
|
|
6
|
+
* (the T1–T5 triggers, and `peaks code run --24h`). Every other mode
|
|
7
|
+
* still requires the Step-1 hard-pause AskUserQuestion
|
|
8
|
+
* (`mode-gate.shouldPauseAtGate` keeps `step-1-mode-select` in
|
|
9
|
+
* `HARD_PAUSE_STEPS`).
|
|
10
|
+
*
|
|
11
|
+
* Before this slice the auto-engage paths wrote only
|
|
12
|
+
* `.peaks/_runtime/<sid>/24h-state.json`; the presence lease kept
|
|
13
|
+
* whatever mode it had (or none), so `24h` was invisible to the mode
|
|
14
|
+
* gate and `strict` + `24H_ACTIVE` still paused every gate. This module
|
|
15
|
+
* is the single place that closes that gap, and the single guard that
|
|
16
|
+
* refuses to stamp any other mode.
|
|
17
|
+
*/
|
|
18
|
+
export declare const AUTO_ENGAGE_PRESENCE_MODE: "24h";
|
|
19
|
+
export type AutoEngagePresenceResult = {
|
|
20
|
+
applied: true;
|
|
21
|
+
mode: typeof AUTO_ENGAGE_PRESENCE_MODE;
|
|
22
|
+
updated: number;
|
|
23
|
+
} | {
|
|
24
|
+
applied: false;
|
|
25
|
+
mode: string;
|
|
26
|
+
reason: 'mode-not-auto-settable' | 'no-in-flight-lease' | 'stamp-failed';
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Stamp the 24h mode onto every in-flight presence lease of the bound
|
|
30
|
+
* session. Fail-soft: never throws — a missing lease / unreadable store
|
|
31
|
+
* degrades to `{ applied: false }` so the auto-engage caller can still
|
|
32
|
+
* report the 24h-state transition.
|
|
33
|
+
*/
|
|
34
|
+
export declare function applyAutoEngagePresenceMode(input: {
|
|
35
|
+
projectRoot: string;
|
|
36
|
+
sessionId: string;
|
|
37
|
+
/** Defaults to `24h`; any other value is refused. */
|
|
38
|
+
mode?: string;
|
|
39
|
+
}): AutoEngagePresenceResult;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slice 2026-09-09-mode-consolidation (Slice B): auto-engage may set the
|
|
3
|
+
* 24h MODE — and ONLY the 24h mode.
|
|
4
|
+
*
|
|
5
|
+
* `24h` is the single mode that may be set without an explicit user pick
|
|
6
|
+
* (the T1–T5 triggers, and `peaks code run --24h`). Every other mode
|
|
7
|
+
* still requires the Step-1 hard-pause AskUserQuestion
|
|
8
|
+
* (`mode-gate.shouldPauseAtGate` keeps `step-1-mode-select` in
|
|
9
|
+
* `HARD_PAUSE_STEPS`).
|
|
10
|
+
*
|
|
11
|
+
* Before this slice the auto-engage paths wrote only
|
|
12
|
+
* `.peaks/_runtime/<sid>/24h-state.json`; the presence lease kept
|
|
13
|
+
* whatever mode it had (or none), so `24h` was invisible to the mode
|
|
14
|
+
* gate and `strict` + `24H_ACTIVE` still paused every gate. This module
|
|
15
|
+
* is the single place that closes that gap, and the single guard that
|
|
16
|
+
* refuses to stamp any other mode.
|
|
17
|
+
*/
|
|
18
|
+
import { stampPresenceLeaseMode } from '../skills/presence-lease-service.js';
|
|
19
|
+
export const AUTO_ENGAGE_PRESENCE_MODE = '24h';
|
|
20
|
+
/**
|
|
21
|
+
* Stamp the 24h mode onto every in-flight presence lease of the bound
|
|
22
|
+
* session. Fail-soft: never throws — a missing lease / unreadable store
|
|
23
|
+
* degrades to `{ applied: false }` so the auto-engage caller can still
|
|
24
|
+
* report the 24h-state transition.
|
|
25
|
+
*/
|
|
26
|
+
export function applyAutoEngagePresenceMode(input) {
|
|
27
|
+
const mode = input.mode ?? AUTO_ENGAGE_PRESENCE_MODE;
|
|
28
|
+
if (mode !== AUTO_ENGAGE_PRESENCE_MODE) {
|
|
29
|
+
return { applied: false, mode, reason: 'mode-not-auto-settable' };
|
|
30
|
+
}
|
|
31
|
+
let updated;
|
|
32
|
+
try {
|
|
33
|
+
updated = stampPresenceLeaseMode({
|
|
34
|
+
projectRoot: input.projectRoot,
|
|
35
|
+
sessionId: input.sessionId,
|
|
36
|
+
mode: AUTO_ENGAGE_PRESENCE_MODE
|
|
37
|
+
}).updated;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return { applied: false, mode, reason: 'stamp-failed' };
|
|
41
|
+
}
|
|
42
|
+
if (updated === 0) {
|
|
43
|
+
return { applied: false, mode, reason: 'no-in-flight-lease' };
|
|
44
|
+
}
|
|
45
|
+
return { applied: true, mode: AUTO_ENGAGE_PRESENCE_MODE, updated };
|
|
46
|
+
}
|
|
@@ -95,9 +95,15 @@ export type HandoffExitCondition = (typeof HANDOFF_EXIT_CONDITIONS)[number];
|
|
|
95
95
|
export declare function isHandoffExitCondition(s: string): s is HandoffExitCondition;
|
|
96
96
|
export declare function isHandoffState(s: State): boolean;
|
|
97
97
|
/**
|
|
98
|
-
* Slice 2026-07-28 (rid-027)
|
|
99
|
-
*
|
|
100
|
-
*
|
|
101
|
-
* the
|
|
98
|
+
* Slice 2026-07-28 (rid-027), re-keyed by slice
|
|
99
|
+
* 2026-09-09-mode-consolidation: resolve the auto-compact mode from
|
|
100
|
+
* the PRESENCE MODE, not the 24h state machine. Returns `'partial'`
|
|
101
|
+
* when the mode is `24h`, otherwise `'standard'`. This is the CLI-side
|
|
102
|
+
* default; the explicit `--mode` flag in `peaks code auto-compact`
|
|
103
|
+
* overrides it.
|
|
104
|
+
*
|
|
105
|
+
* `sessionId` is retained in the signature for callers that still pass
|
|
106
|
+
* it; the resolution itself is project-scoped (the presence lease is
|
|
107
|
+
* per project + caller).
|
|
102
108
|
*/
|
|
103
|
-
export declare function getAutoCompactMode(projectRoot: string,
|
|
109
|
+
export declare function getAutoCompactMode(projectRoot: string, _sessionId?: string): AutoCompactMode;
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
* WAITING_USER — proposal §1 T3/T4 exception).
|
|
12
12
|
*/
|
|
13
13
|
import { B3_THRESHOLD, isDecisionKey } from './state.js';
|
|
14
|
-
import {
|
|
14
|
+
import { resolveAutoCompactProfile } from '../mode/mode-status-service.js';
|
|
15
15
|
export const B3_TRIGGER_REASONS = [
|
|
16
16
|
'prd_direction_change',
|
|
17
17
|
'blocker_3_consecutive_slices',
|
|
@@ -128,17 +128,17 @@ export function isHandoffState(s) {
|
|
|
128
128
|
return s === 'HANDOFF';
|
|
129
129
|
}
|
|
130
130
|
/**
|
|
131
|
-
* Slice 2026-07-28 (rid-027)
|
|
132
|
-
*
|
|
133
|
-
*
|
|
134
|
-
* the
|
|
131
|
+
* Slice 2026-07-28 (rid-027), re-keyed by slice
|
|
132
|
+
* 2026-09-09-mode-consolidation: resolve the auto-compact mode from
|
|
133
|
+
* the PRESENCE MODE, not the 24h state machine. Returns `'partial'`
|
|
134
|
+
* when the mode is `24h`, otherwise `'standard'`. This is the CLI-side
|
|
135
|
+
* default; the explicit `--mode` flag in `peaks code auto-compact`
|
|
136
|
+
* overrides it.
|
|
137
|
+
*
|
|
138
|
+
* `sessionId` is retained in the signature for callers that still pass
|
|
139
|
+
* it; the resolution itself is project-scoped (the presence lease is
|
|
140
|
+
* per project + caller).
|
|
135
141
|
*/
|
|
136
|
-
export function getAutoCompactMode(projectRoot,
|
|
137
|
-
|
|
138
|
-
const snap = read24hState(projectRoot, sessionId);
|
|
139
|
-
return snap.state === '24H_ACTIVE' ? 'partial' : 'standard';
|
|
140
|
-
}
|
|
141
|
-
catch {
|
|
142
|
-
return 'standard';
|
|
143
|
-
}
|
|
142
|
+
export function getAutoCompactMode(projectRoot, _sessionId) {
|
|
143
|
+
return resolveAutoCompactProfile(projectRoot);
|
|
144
144
|
}
|
|
@@ -30,7 +30,9 @@ export interface ActiveSkillResolution {
|
|
|
30
30
|
readonly sessionId: string | null;
|
|
31
31
|
/**
|
|
32
32
|
* Mode token recorded on the canonical lease (e.g. `full-auto`,
|
|
33
|
-
* `assisted`, `
|
|
33
|
+
* `assisted`, `strict`, `24h`). Normalized on read: a legacy
|
|
34
|
+
* on-disk `'swarm'` surfaces as `'full-auto'` (slice
|
|
35
|
+
* 2026-09-09-mode-consolidation). `null` when the source is not
|
|
34
36
|
* `canonical` (the legacy `active-skill-*.json` files do not
|
|
35
37
|
* surface a mode field, and the `env` / `none` cases are test
|
|
36
38
|
* overrides). Slice 2026-08-04-rid-005 surfaced this so the
|
|
@@ -29,7 +29,7 @@ import { join } from 'node:path';
|
|
|
29
29
|
import { getSessionIdCanonical } from '../../session/session-manager.js';
|
|
30
30
|
import { getSessionDir } from '../../session/getSessionDir.js';
|
|
31
31
|
import { readPresenceLease, listPresenceLeases } from '../../skills/presence-lease-service.js';
|
|
32
|
-
import { getCurrentSessionId } from '../../skills/skill-presence-service.js';
|
|
32
|
+
import { getCurrentSessionId, normalizeSkillPresenceMode } from '../../skills/skill-presence-service.js';
|
|
33
33
|
const ACTIVE_SKILL_PREFIX = 'active-skill-';
|
|
34
34
|
/**
|
|
35
35
|
* Resolve the active peak skill for the current hook invocation.
|
|
@@ -99,7 +99,7 @@ export function resolveActiveSkillForCaller(projectRoot, opts) {
|
|
|
99
99
|
skill: projection.lease.skill,
|
|
100
100
|
callerId: lease.callerId,
|
|
101
101
|
sessionId,
|
|
102
|
-
mode: typeof projection.mode === 'string'
|
|
102
|
+
mode: normalizeSkillPresenceMode(typeof projection.mode === 'string' ? projection.mode : null) ?? null,
|
|
103
103
|
source: 'canonical',
|
|
104
104
|
};
|
|
105
105
|
}
|
|
@@ -127,9 +127,7 @@ export function resolveActiveSkillForCaller(projectRoot, opts) {
|
|
|
127
127
|
const raw = readFileSync(filePath, 'utf8');
|
|
128
128
|
const parsed = JSON.parse(raw);
|
|
129
129
|
if (typeof parsed.skill === 'string' && parsed.skill.length > 0) {
|
|
130
|
-
const legacyMode = typeof parsed.mode === 'string'
|
|
131
|
-
? parsed.mode
|
|
132
|
-
: null;
|
|
130
|
+
const legacyMode = normalizeSkillPresenceMode(typeof parsed.mode === 'string' ? parsed.mode : null) ?? null;
|
|
133
131
|
return { skill: parsed.skill, callerId, sessionId, mode: legacyMode, source: 'file' };
|
|
134
132
|
}
|
|
135
133
|
}
|
|
@@ -76,7 +76,7 @@ export declare function summarizeLifecycleError(error: unknown): string;
|
|
|
76
76
|
*
|
|
77
77
|
* This is the integration with the actual post-compact detection path.
|
|
78
78
|
* The claude-code adapter's `postCompactDetectCommand` is
|
|
79
|
-
* `peaks
|
|
79
|
+
* `peaks code auto-compact --json` — i.e. the next probe through this very
|
|
80
80
|
* function. So when a probe finds a run still sitting at `compacting`
|
|
81
81
|
* and MEASURES a ratio that has dropped below the auto-fire threshold,
|
|
82
82
|
* that measurement is the proof the compact landed. Only then do we
|
|
@@ -168,7 +168,7 @@ export function summarizeLifecycleError(error) {
|
|
|
168
168
|
*
|
|
169
169
|
* This is the integration with the actual post-compact detection path.
|
|
170
170
|
* The claude-code adapter's `postCompactDetectCommand` is
|
|
171
|
-
* `peaks
|
|
171
|
+
* `peaks code auto-compact --json` — i.e. the next probe through this very
|
|
172
172
|
* function. So when a probe finds a run still sitting at `compacting`
|
|
173
173
|
* and MEASURES a ratio that has dropped below the auto-fire threshold,
|
|
174
174
|
* that measurement is the proof the compact landed. Only then do we
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Slice 2026-07-29-context-evaluation-accuracy Part 22: a new
|
|
10
10
|
* `autoFire` tier is added between soft-warn and preCompact. peaks-loop
|
|
11
|
-
* preempts and runs `peaks
|
|
11
|
+
* preempts and runs `peaks code auto-compact` itself when
|
|
12
12
|
* context crosses `autoFire`; the LLM is NOT asked to "decide"
|
|
13
13
|
* (the LLM-judged 0.85–0.95 zone was the source of false-positive
|
|
14
14
|
* "context too low" reports at 60%+ free — the LLM was guessing).
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Slice 2026-07-29-context-evaluation-accuracy Part 22: a new
|
|
10
10
|
* `autoFire` tier is added between soft-warn and preCompact. peaks-loop
|
|
11
|
-
* preempts and runs `peaks
|
|
11
|
+
* preempts and runs `peaks code auto-compact` itself when
|
|
12
12
|
* context crosses `autoFire`; the LLM is NOT asked to "decide"
|
|
13
13
|
* (the LLM-judged 0.85–0.95 zone was the source of false-positive
|
|
14
14
|
* "context too low" reports at 60%+ free — the LLM was guessing).
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-compact orchestrator (v2.13.0 AC-2 + AC-3 + AC-4).
|
|
3
3
|
*
|
|
4
|
-
* Closes the loop between `peaks
|
|
4
|
+
* Closes the loop between `peaks code auto-compact` (AC-1) and the IDE's
|
|
5
5
|
* native compact capability (AC-3). peaks-loop is project-aware: it
|
|
6
6
|
* knows the current plan, open questions, recent decisions, in-flight
|
|
7
7
|
* batches, todo state, git status, and active skills. That context is
|
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* 3. If 0.85 ≤ ratio < 0.95 (pre-compact zone): peaks-loop prepares
|
|
17
17
|
* the convergence toolkit (checkpoint + auto-decisions log +
|
|
18
18
|
* IDE-dispatch handle) and surfaces it to the LLM. The LLM
|
|
19
|
-
* DECIDES when to fire `peaks
|
|
19
|
+
* DECIDES when to fire `peaks code auto-compact`;
|
|
20
20
|
* peaks-loop does NOT auto-fire. The toolkit is ready so the
|
|
21
21
|
* LLM doesn't lose context to a last-second `/compact` panic.
|
|
22
22
|
* 4. If ratio < 0.85: skip — return a one-line info row.
|
|
@@ -34,7 +34,7 @@ import { getSessionIdCanonical } from '../session/session-manager.js';
|
|
|
34
34
|
import { resolveOuterSessionId } from '../session/binding-status-service.js';
|
|
35
35
|
import { AUTO_COMPACT_PRE_COMPACT_RATIO, AUTO_COMPACT_THRESHOLD_RATIO } from '../context/auto-compact-types.js';
|
|
36
36
|
import { describeMode, thresholdFor } from './auto-compact-modes.js';
|
|
37
|
-
import {
|
|
37
|
+
import { resolveAutoCompactProfile } from '../mode/mode-status-service.js';
|
|
38
38
|
import { CompactLifecyclePublisher, newCompactRunId, settleOpenLifecycleRun, summarizeLifecycleError } from './auto-compact-lifecycle.js';
|
|
39
39
|
const PRE_COMPACT_REASON = 'pre-compact-auto';
|
|
40
40
|
/**
|
|
@@ -72,7 +72,7 @@ export function evaluateCompactTrigger(ratio, mode = 'standard') {
|
|
|
72
72
|
}
|
|
73
73
|
if (ratio < preCompact) {
|
|
74
74
|
// Part 22: auto-fire zone (0.80 ≤ ratio < 0.85). peaks-loop
|
|
75
|
-
// preempts and runs `peaks
|
|
75
|
+
// preempts and runs `peaks code auto-compact` itself
|
|
76
76
|
// without LLM involvement. The LLM is not asked to "decide";
|
|
77
77
|
// the toolkit is applied synchronously. Closes the
|
|
78
78
|
// LLM-misjudges-context window that previously let the
|
|
@@ -255,20 +255,15 @@ function writePreCompactCheckpoint(input) {
|
|
|
255
255
|
return path;
|
|
256
256
|
}
|
|
257
257
|
/**
|
|
258
|
-
* Slice 2026-07-28 (rid-027)
|
|
259
|
-
*
|
|
260
|
-
*
|
|
261
|
-
*
|
|
262
|
-
*
|
|
258
|
+
* Slice 2026-07-28 (rid-027), re-keyed by slice
|
|
259
|
+
* 2026-09-09-mode-consolidation: resolve the auto-compact mode from
|
|
260
|
+
* the PRESENCE MODE, not the 24h state machine. Returns `'partial'`
|
|
261
|
+
* when the mode is `24h`, otherwise `'standard'`. The CLI flag
|
|
262
|
+
* `--mode` takes precedence (caller passes `input.mode` directly), so
|
|
263
|
+
* this helper is only consulted when the flag is absent.
|
|
263
264
|
*/
|
|
264
|
-
function resolveAutoCompactMode(projectRoot
|
|
265
|
-
|
|
266
|
-
const snap = read24hState(projectRoot, sessionId);
|
|
267
|
-
return snap.state === '24H_ACTIVE' ? 'partial' : 'standard';
|
|
268
|
-
}
|
|
269
|
-
catch {
|
|
270
|
-
return 'standard';
|
|
271
|
-
}
|
|
265
|
+
function resolveAutoCompactMode(projectRoot) {
|
|
266
|
+
return resolveAutoCompactProfile(projectRoot);
|
|
272
267
|
}
|
|
273
268
|
/**
|
|
274
269
|
* Execute the auto-compact flow.
|
|
@@ -300,9 +295,10 @@ export async function runAutoCompact(input) {
|
|
|
300
295
|
};
|
|
301
296
|
}
|
|
302
297
|
// Slice 2026-07-28 (rid-027): resolve mode. CLI flag `--mode` wins;
|
|
303
|
-
//
|
|
304
|
-
// 'standard' preserves
|
|
305
|
-
|
|
298
|
+
// slice 2026-09-09-mode-consolidation keys the default off the
|
|
299
|
+
// presence MODE ('24h' → 'partial'); 'standard' preserves the
|
|
300
|
+
// v2.13.0 zero-pause contract.
|
|
301
|
+
const mode = input.mode ?? resolveAutoCompactMode(input.projectRoot);
|
|
306
302
|
// Lazy import to avoid the AC-1 module depending on the orchestrator.
|
|
307
303
|
const { readContextPercent } = await import('../context/auto-compact-reader.js');
|
|
308
304
|
const outerSessionId = resolveOuterSessionId(input.projectRoot, sessionId, input.env ?? process.env);
|
|
@@ -398,12 +394,12 @@ export async function runAutoCompact(input) {
|
|
|
398
394
|
? [
|
|
399
395
|
'RED-LINE compact dispatched — further sub-agent dispatch BLOCKED until ratio < 0.85',
|
|
400
396
|
'Post-compact resume picks up the convergence plan from auto-decisions.md',
|
|
401
|
-
'Next `peaks
|
|
397
|
+
'Next `peaks code auto-compact` probe will confirm ratio dropped below 0.85'
|
|
402
398
|
]
|
|
403
399
|
: [
|
|
404
400
|
'Pre-compact dispatched — IDE compact in progress (async)',
|
|
405
401
|
'Post-compact resume picks up the convergence plan from auto-decisions.md',
|
|
406
|
-
'Next `peaks
|
|
402
|
+
'Next `peaks code auto-compact` probe will confirm ratio dropped below 0.85'
|
|
407
403
|
];
|
|
408
404
|
plan = buildConvergencePlan({
|
|
409
405
|
sessionId,
|
|
@@ -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 `
|
|
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 /
|
|
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 `
|
|
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
|
-
'
|
|
26
|
-
'
|
|
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 /
|
|
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 === '
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
194
|
+
? 'RED-LINE — call `peaks code auto-compact` immediately.'
|
|
195
195
|
: probe.ratio >= 0.85
|
|
196
|
-
? 'pre-compact zone — consider running `peaks
|
|
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.';
|
|
@@ -1,8 +1,34 @@
|
|
|
1
1
|
import type { MemoryIndexEntry } from '../memory/memory-search-service.js';
|
|
2
|
+
/**
|
|
3
|
+
* Slice 2026-09-09-memory-retrieval: the two tiers the orchestrator
|
|
4
|
+
* preflight ranks over.
|
|
5
|
+
*
|
|
6
|
+
* - `hot` — standing rules / feedback / decisions. Always eligible.
|
|
7
|
+
* - `warm` — project / reference memos. Injected only on task relevance.
|
|
8
|
+
*
|
|
9
|
+
* `cold` is deep storage and is deliberately NOT surfaced here.
|
|
10
|
+
*/
|
|
11
|
+
export interface MemoryTieredSelection {
|
|
12
|
+
hot: MemoryIndexEntry[];
|
|
13
|
+
warm: MemoryIndexEntry[];
|
|
14
|
+
}
|
|
2
15
|
export declare class MemoryIndexReader {
|
|
3
16
|
private readonly projectRoot;
|
|
4
17
|
private cache;
|
|
5
18
|
constructor(projectRoot: string);
|
|
19
|
+
/**
|
|
20
|
+
* Flatten the whole index (all tiers, all buckets). Kept for
|
|
21
|
+
* back-compat with the pre-2026-09-09 callers.
|
|
22
|
+
*/
|
|
6
23
|
loadIfStale(): MemoryIndexEntry[];
|
|
24
|
+
/**
|
|
25
|
+
* Slice 2026-09-09-memory-retrieval: read the index as tiers.
|
|
26
|
+
*
|
|
27
|
+
* Returns `null` when `.peaks/memory/index.json` is absent or
|
|
28
|
+
* unreadable — the caller distinguishes "no index" from "index with
|
|
29
|
+
* zero entries" for its fail-soft reason code. Never throws.
|
|
30
|
+
*/
|
|
31
|
+
selectTiered(): MemoryTieredSelection | null;
|
|
7
32
|
selectFeedbackLayerA(cap: number): MemoryIndexEntry[];
|
|
33
|
+
private loadSnapshot;
|
|
8
34
|
}
|