peaks-loop 4.0.25 → 4.0.27
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 +23 -0
- package/dist/cli/commands/code-runtime-commands.js +7 -1
- package/dist/cli/commands/core/memory-command.js +1 -3
- package/dist/cli/commands/dispatch-commands.js +14 -67
- package/dist/cli/commands/memory-commands.d.ts +0 -2
- package/dist/cli/commands/memory-commands.js +4 -7
- package/dist/cli/commands/preferences-commands.js +0 -1
- package/dist/cli/commands/qa-commands.js +5 -5
- package/dist/cli/commands/sub-agent-shared.d.ts +0 -4
- package/dist/cli/commands/sub-agent-shared.js +0 -14
- package/dist/cli/commands/workflow-commands.js +5 -5
- package/dist/services/code/auto-compact-orchestrator.js +3 -0
- package/dist/services/context/auto-compact-reader.d.ts +45 -49
- package/dist/services/context/auto-compact-reader.js +21 -148
- package/dist/services/context/auto-compact-types.d.ts +17 -2
- package/dist/services/context/build-dispatch-system-prompt.d.ts +4 -4
- package/dist/services/context/build-dispatch-system-prompt.js +6 -6
- package/dist/services/context/context-guard.d.ts +2 -4
- package/dist/services/context/context-guard.js +4 -6
- package/dist/services/context/{headroom-fetcher.d.ts → doc-cache-fetcher.d.ts} +2 -2
- package/dist/services/context/{headroom-fetcher.js → doc-cache-fetcher.js} +4 -3
- package/dist/services/context/memory-preflight-service.js +6 -17
- package/dist/services/context/threshold.d.ts +0 -3
- package/dist/services/context/threshold.js +0 -3
- package/dist/services/dispatch/batch-counter.js +1 -1
- package/dist/services/dispatch/leak-detector.js +1 -1
- package/dist/services/fuzzy-matching/fzf-pick-service.d.ts +1 -1
- package/dist/services/fuzzy-matching/fzf-pick-service.js +1 -1
- package/dist/services/ide/adapters/claude-code-adapter.d.ts +12 -12
- package/dist/services/ide/adapters/claude-code-adapter.js +290 -1
- package/dist/services/ide/ide-types.d.ts +39 -0
- package/dist/services/memory/llm-reranker.d.ts +4 -5
- package/dist/services/memory/llm-reranker.js +6 -8
- package/dist/services/memory/memory-search-service.d.ts +0 -32
- package/dist/services/memory/memory-search-service.js +0 -37
- package/dist/services/preferences/preferences-service.d.ts +5 -8
- package/dist/services/preferences/preferences-service.js +0 -30
- package/dist/services/preferences/preferences-types.d.ts +0 -22
- package/dist/services/preferences/preferences-types.js +0 -12
- package/dist/services/retrospective/retrospective-search-service.d.ts +0 -18
- package/dist/services/retrospective/retrospective-search-service.js +0 -32
- package/dist/services/session/binding-status-service.d.ts +11 -0
- package/dist/services/session/binding-status-service.js +25 -4
- package/dist/services/skill/skill-search-service.d.ts +1 -1
- package/dist/services/slice/slice-benchmark-service.d.ts +1 -1
- package/dist/services/slice/slice-benchmark-service.js +1 -1
- package/dist/services/slice/slice-pick-service.d.ts +1 -1
- package/dist/services/slice/slice-pick-service.js +1 -1
- package/package.json +5 -6
- package/skills/bee/peaks-qa/references/qa-context-governance.md +1 -1
- package/skills/bee/peaks-rd/SKILL.md +2 -2
- package/skills/bee/peaks-rd/references/rd-context-governance.md +2 -2
- package/skills/bee/peaks-txt/SKILL.md +1 -1
- package/skills/bee/peaks-ui/SKILL.md +1 -1
- package/skills/peaks-code/SKILL.md +1 -1
- package/skills/peaks-code/references/context-governance.md +7 -34
- package/skills/peaks-code/references/envelope-contract.md +2 -7
- package/skills/peaks-code/references/sub-agent-dispatch.md +2 -4
- package/dist/services/context/headroom-client.d.ts +0 -34
- package/dist/services/context/headroom-client.js +0 -117
- package/dist/services/context/headroom-prefs.d.ts +0 -46
- package/dist/services/context/headroom-prefs.js +0 -34
- package/skills/peaks-code/references/headroom-integration.md +0 -107
|
@@ -1,30 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AC-1 — auto context-percent probe.
|
|
3
|
-
*
|
|
4
|
-
* Reads the current AI CLI context-fill ratio without requiring the
|
|
5
|
-
* LLM to pass `--prompt-size <bytes>` manually. Strategy: ask the
|
|
6
|
-
* registered `IdeAdapter.compact` profile which env-var to read;
|
|
7
|
-
* no hard-coded IDE names. Per-adapter:
|
|
8
|
-
*
|
|
9
|
-
* - claude-code: `CLAUDE_CONTEXT_USAGE_PERCENT` (MVP)
|
|
10
|
-
* - trae / codex / cursor / qoder / tongyi-lingma / hermes /
|
|
11
|
-
* openclaw: each adapter fills its own env-var; until L2-dogfood
|
|
12
|
-
* verifies each surface, adapters may omit `compact` and the
|
|
13
|
-
* probe returns `source: 'conservative-fallback'`.
|
|
14
|
-
*
|
|
15
|
-
* Fallback chain (when adapter.compact is undefined OR the env-var
|
|
16
|
-
* is missing):
|
|
17
|
-
* 1. statusline poll (`~/.claude/statusline-state.json` for
|
|
18
|
-
* Claude Code MVP; other IDEs register their own poll path
|
|
19
|
-
* by exposing `compact.postCompactDetectCommand`).
|
|
20
|
-
* 2. Conservative transcript-size estimate
|
|
21
|
-
* (`~/.claude/projects/<hash>/<sid>.jsonl` for Claude Code).
|
|
22
|
-
* 3. `ratio: 0` with `source: 'conservative-fallback'` — the
|
|
23
|
-
* orchestrator MUST NOT auto-fire compact on this signal.
|
|
24
|
-
*/
|
|
25
|
-
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
26
|
-
import { homedir } from 'node:os';
|
|
27
|
-
import { join } from 'node:path';
|
|
28
1
|
import { detectIdeFromEnv } from './ide-detect.js';
|
|
29
2
|
import { getAdapter } from '../ide/ide-registry.js';
|
|
30
3
|
/**
|
|
@@ -44,106 +17,16 @@ function readEnvPercent(env, varName) {
|
|
|
44
17
|
return null;
|
|
45
18
|
return Math.max(0, Math.min(1, parsed));
|
|
46
19
|
}
|
|
47
|
-
/**
|
|
48
|
-
* Read the IDE-specific statusline state. MVP path is Claude Code's
|
|
49
|
-
* `~/.claude/statusline-state.json`; other IDEs are intentionally
|
|
50
|
-
* left for future slices (each IDE will expose its own
|
|
51
|
-
* `compact.postCompactDetectCommand` to drive this).
|
|
52
|
-
*/
|
|
53
|
-
function readClaudeStatuslinePercent() {
|
|
54
|
-
const path = join(homedir(), '.claude', 'statusline-state.json');
|
|
55
|
-
if (!existsSync(path))
|
|
56
|
-
return null;
|
|
57
|
-
try {
|
|
58
|
-
const json = JSON.parse(readFileSync(path, 'utf8'));
|
|
59
|
-
const candidates = ['contextPercent', 'context_usage_percent', 'contextPercentUsed'];
|
|
60
|
-
for (const key of candidates) {
|
|
61
|
-
const raw = json[key];
|
|
62
|
-
if (typeof raw === 'number' && Number.isFinite(raw)) {
|
|
63
|
-
return raw > 1.5 ? raw / 100 : Math.max(0, Math.min(1, raw));
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
catch (err) { // TODO(g2): legacy silent catch — now narrows to IO errors only (grace: 1 minor release, v2.14.0)
|
|
68
|
-
if (err instanceof ReferenceError)
|
|
69
|
-
throw err; // surface module-load bugs
|
|
70
|
-
if (err instanceof SyntaxError)
|
|
71
|
-
throw err; // surface parse bugs (e.g. broken statusline JSON)
|
|
72
|
-
return null; // only swallow IO errors
|
|
73
|
-
}
|
|
74
|
-
return null;
|
|
75
|
-
}
|
|
76
|
-
/**
|
|
77
|
-
* Recursive search for `<sessionId>.jsonl` under `projectsDir`. Used by
|
|
78
|
-
* `readClaudeTranscriptFallback` and exported via `_internal` so unit
|
|
79
|
-
* tests can drive it without monkey-patching `os.homedir` (which is
|
|
80
|
-
* non-configurable in ESM module namespaces).
|
|
81
|
-
*
|
|
82
|
-
* The Mac layout encodes the cwd as a single hash directory; on Mac
|
|
83
|
-
* Claude Code nests the transcript under that hash with an extra level
|
|
84
|
-
* of subdirectory we cannot predict ahead of time. A flat readdir misses
|
|
85
|
-
* that branch and returns null — the silent-failure mode that this fix
|
|
86
|
-
* closes.
|
|
87
|
-
*/
|
|
88
|
-
function findTranscriptJsonl(projectsDir, sessionId) {
|
|
89
|
-
if (!existsSync(projectsDir))
|
|
90
|
-
return null;
|
|
91
|
-
try {
|
|
92
|
-
const stack = [projectsDir];
|
|
93
|
-
while (stack.length > 0) {
|
|
94
|
-
const dir = stack.pop();
|
|
95
|
-
if (dir === undefined)
|
|
96
|
-
break;
|
|
97
|
-
const entries = readdirSync(dir, { withFileTypes: true });
|
|
98
|
-
for (const entry of entries) {
|
|
99
|
-
const full = join(dir, entry.name);
|
|
100
|
-
if (entry.isDirectory()) {
|
|
101
|
-
stack.push(full);
|
|
102
|
-
}
|
|
103
|
-
else if (entry.isFile() && entry.name === `${sessionId}.jsonl`) {
|
|
104
|
-
const bytes = statSync(full).size;
|
|
105
|
-
return { path: full, bytes };
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
catch (err) { // TODO(g2): legacy silent catch — now narrows to IO errors only (grace: 1 minor release, v2.14.0)
|
|
111
|
-
if (err instanceof ReferenceError)
|
|
112
|
-
throw err; // surface module-load bugs
|
|
113
|
-
if (err instanceof SyntaxError)
|
|
114
|
-
throw err; // surface parse bugs
|
|
115
|
-
return null; // only swallow IO errors
|
|
116
|
-
}
|
|
117
|
-
return null;
|
|
118
|
-
}
|
|
119
|
-
/**
|
|
120
|
-
* Conservative transcript-size fallback. Recursively searches
|
|
121
|
-
* `~/.claude/projects/<hash>/<sid-or-nested>.jsonl` (Mac may nest
|
|
122
|
-
* the jsonl under an
|
|
123
|
-
* extra directory we cannot predict ahead of time) and estimates
|
|
124
|
-
* `ratio = bytesUsed / 256K`. Returns the bytes seen so the
|
|
125
|
-
* orchestrator can show "estimated from 124KB of 256KB transcript"
|
|
126
|
-
* in the envelope. Tagged `'transcript-estimate'` (v2.14.0) so callers
|
|
127
|
-
* know it is a real signal, NOT a hard gate.
|
|
128
|
-
*/
|
|
129
|
-
function readClaudeTranscriptFallback(sessionId) {
|
|
130
|
-
const projectsDir = join(homedir(), '.claude', 'projects');
|
|
131
|
-
const hit = findTranscriptJsonl(projectsDir, sessionId);
|
|
132
|
-
if (hit === null)
|
|
133
|
-
return null;
|
|
134
|
-
const ratio = Math.min(1, hit.bytes / (256 * 1024));
|
|
135
|
-
return { ratio, bytes: hit.bytes };
|
|
136
|
-
}
|
|
137
20
|
/**
|
|
138
21
|
* Probe the current AI CLI's context-fill ratio. Adapter-driven:
|
|
139
22
|
* looks up the registered `IdeAdapter.compact` profile via
|
|
140
|
-
* `
|
|
141
|
-
* adapter-declared env-var.
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
* with `ratio: 0` so the
|
|
146
|
-
* missing signal.
|
|
23
|
+
* `getAdapter(detectIdeFromEnv(env))` and reads the
|
|
24
|
+
* adapter-declared env-var. When that misses, delegates to the
|
|
25
|
+
* adapter's optional `readContextPercentFallback` (which owns any
|
|
26
|
+
* vendor-specific statusline / transcript probe). Adapters without a
|
|
27
|
+
* fallback (or a fallback that returns null) yield
|
|
28
|
+
* `source: 'conservative-fallback'` with `ratio: 0` so the
|
|
29
|
+
* orchestrator never auto-fires on a missing signal.
|
|
147
30
|
*/
|
|
148
31
|
export function readContextPercent(input) {
|
|
149
32
|
const env = input.env ?? process.env;
|
|
@@ -158,7 +41,7 @@ export function readContextPercent(input) {
|
|
|
158
41
|
// through `unknown` to IdeId's wider 8-element set.
|
|
159
42
|
const ideId = (detected === 'unknown' ? 'claude-code' : detected);
|
|
160
43
|
const adapter = getAdapter(ideId);
|
|
161
|
-
// P0 user-overridden takes priority over env /
|
|
44
|
+
// P0 user-overridden takes priority over env / fallback / transcript.
|
|
162
45
|
// Mac escape hatch: when the CLI/helper passes `--prompt-size <bytes>`,
|
|
163
46
|
// honor that number directly. Do NOT read env, statusline, or transcript
|
|
164
47
|
// — user intent always wins. Negative / non-finite values are ignored
|
|
@@ -176,8 +59,8 @@ export function readContextPercent(input) {
|
|
|
176
59
|
capturedAt
|
|
177
60
|
};
|
|
178
61
|
}
|
|
179
|
-
// Primary: read the adapter-declared env-var (no hard-coded IDE names).
|
|
180
62
|
if (adapter.compact) {
|
|
63
|
+
// Primary: read the adapter-declared env-var (no hard-coded IDE names).
|
|
181
64
|
const primary = readEnvPercent(env, adapter.compact.envVarForContextPercent);
|
|
182
65
|
if (primary !== null) {
|
|
183
66
|
return {
|
|
@@ -188,27 +71,17 @@ export function readContextPercent(input) {
|
|
|
188
71
|
capturedAt
|
|
189
72
|
};
|
|
190
73
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
if (fallback !== null) {
|
|
203
|
-
return {
|
|
204
|
-
ratio: fallback.ratio,
|
|
205
|
-
source: 'transcript-estimate',
|
|
206
|
-
rawBytes: fallback.bytes,
|
|
207
|
-
capacityBytes,
|
|
208
|
-
ide: ideId,
|
|
209
|
-
capturedAt
|
|
210
|
-
};
|
|
211
|
-
}
|
|
74
|
+
// Fallback: the adapter owns any vendor-specific statusline /
|
|
75
|
+
// transcript probe. When it returns a probe, honor it; otherwise
|
|
76
|
+
// fall through to conservative-fallback.
|
|
77
|
+
const fallback = adapter.compact.readContextPercentFallback?.({
|
|
78
|
+
projectRoot: input.projectRoot,
|
|
79
|
+
sessionId: input.sessionId,
|
|
80
|
+
outerSessionId: input.outerSessionId,
|
|
81
|
+
env
|
|
82
|
+
});
|
|
83
|
+
if (fallback)
|
|
84
|
+
return fallback;
|
|
212
85
|
}
|
|
213
86
|
// No signal available — return `ratio: 0` so the orchestrator
|
|
214
87
|
// stays in `none` zone and the LLM can still pass `--prompt-size`
|
|
@@ -216,4 +89,4 @@ export function readContextPercent(input) {
|
|
|
216
89
|
return { ratio: 0, source: 'conservative-fallback', capacityBytes, ide: ideId, capturedAt };
|
|
217
90
|
}
|
|
218
91
|
/** Re-export the env-var probe for unit tests. */
|
|
219
|
-
export const _internal = { readEnvPercent
|
|
92
|
+
export const _internal = { readEnvPercent };
|
|
@@ -161,8 +161,10 @@ export interface ContextPercentProbe {
|
|
|
161
161
|
* - `transcript-estimate` (v2.14.0 Mac-aware: recursive
|
|
162
162
|
* readdir under ~/.claude/projects/
|
|
163
163
|
* finds <sid>.jsonl and reports
|
|
164
|
-
*
|
|
165
|
-
*
|
|
164
|
+
* contextTokens / contextWindowTokens
|
|
165
|
+
* from the LATEST message.usage entry —
|
|
166
|
+
* token-based + model-aware; a real
|
|
167
|
+
* signal, NOT a hard gate)
|
|
166
168
|
* - `conservative-fallback` (no signal available — caller
|
|
167
169
|
* MUST NOT treat as hard gate)
|
|
168
170
|
* Future IDEs may add per-ide sources; the type accepts any
|
|
@@ -171,6 +173,19 @@ export interface ContextPercentProbe {
|
|
|
171
173
|
readonly source: string;
|
|
172
174
|
readonly rawBytes?: number;
|
|
173
175
|
readonly capacityBytes?: number;
|
|
176
|
+
/**
|
|
177
|
+
* Token-based numerator for `transcript-estimate`: the observed
|
|
178
|
+
* `input_tokens + cache_read_input_tokens + cache_creation_input_tokens`
|
|
179
|
+
* from the LATEST transcript `message.usage` entry. Undefined for byte /
|
|
180
|
+
* percent sources (`user-overridden`, `${ideId}-env`, `statusline-poll`).
|
|
181
|
+
*/
|
|
182
|
+
readonly rawTokens?: number;
|
|
183
|
+
/**
|
|
184
|
+
* Token-based denominator for `transcript-estimate`: the model's context
|
|
185
|
+
* window in tokens (`modelContextWindowTokens`). Undefined for byte /
|
|
186
|
+
* percent sources.
|
|
187
|
+
*/
|
|
188
|
+
readonly capacityTokens?: number;
|
|
174
189
|
readonly ide: string;
|
|
175
190
|
readonly capturedAt: string;
|
|
176
191
|
}
|
|
@@ -72,10 +72,10 @@ export declare const L1_WORKTREE_GOVERNANCE_BLOCK = "## Superpowers chain refusa
|
|
|
72
72
|
* index, race the worktree release, and can corrupt the
|
|
73
73
|
* caller's working branch.
|
|
74
74
|
*
|
|
75
|
-
* These rules are
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
75
|
+
* These rules are placed immediately after the L1 worktree governance
|
|
76
|
+
* block so every stable boilerplate block is contiguous at the prompt
|
|
77
|
+
* start, maximizing Anthropic prompt-cache prefix reuse (stable-first
|
|
78
|
+
* ordering).
|
|
79
79
|
*/
|
|
80
80
|
export declare const LIFECYCLE_RULES = "## Sub-agent lifecycle rules (locked 2026-08-01)\n\n- If you start a long-lived local service (vite dev, mock API, docker container, etc.), register it with `peaks sub-agent shutdown register --pid <pid> --name <label>` before you exit. The parent session will best-effort-kill it before merge-back.\n- Do NOT run E2E. The parent session runs Playwright verification once after merge-back (Task 10). Your E2E work is duplicate effort.\n- Do NOT call `git merge`, `git pull`, `git rebase`, or `peaks worktree release`. The parent session owns the merge-back step.\n";
|
|
81
81
|
/**
|
|
@@ -62,10 +62,10 @@ If the upstream superpowers chain suggests raw \`git worktree add\`:
|
|
|
62
62
|
* index, race the worktree release, and can corrupt the
|
|
63
63
|
* caller's working branch.
|
|
64
64
|
*
|
|
65
|
-
* These rules are
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
65
|
+
* These rules are placed immediately after the L1 worktree governance
|
|
66
|
+
* block so every stable boilerplate block is contiguous at the prompt
|
|
67
|
+
* start, maximizing Anthropic prompt-cache prefix reuse (stable-first
|
|
68
|
+
* ordering).
|
|
69
69
|
*/
|
|
70
70
|
export const LIFECYCLE_RULES = `## Sub-agent lifecycle rules (locked 2026-08-01)
|
|
71
71
|
|
|
@@ -97,9 +97,9 @@ export function buildDispatchSystemPrompt(input) {
|
|
|
97
97
|
const { taskBody, memoryBlock, contextProbe } = input;
|
|
98
98
|
const contextBlock = renderContextBlock(contextProbe ?? null);
|
|
99
99
|
if (memoryBlock.available === true && typeof memoryBlock.block === 'string') {
|
|
100
|
-
return `${L1_WORKTREE_GOVERNANCE_BLOCK}\n${contextBlock}${memoryBlock.block}\n## Task\n${taskBody}
|
|
100
|
+
return `${L1_WORKTREE_GOVERNANCE_BLOCK}\n${LIFECYCLE_RULES}\n${contextBlock}${memoryBlock.block}\n## Task\n${taskBody}`;
|
|
101
101
|
}
|
|
102
|
-
return `${L1_WORKTREE_GOVERNANCE_BLOCK}\n${
|
|
102
|
+
return `${L1_WORKTREE_GOVERNANCE_BLOCK}\n${LIFECYCLE_RULES}\n${contextBlock}${taskBody}`;
|
|
103
103
|
}
|
|
104
104
|
/**
|
|
105
105
|
* Slice 2026-07-29-context-evaluation-accuracy: emit a
|
|
@@ -8,13 +8,11 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Decision codes:
|
|
10
10
|
* - `OK` — under 50%
|
|
11
|
-
* - `CONTEXT_SOFT_WARN` — 50-75%,
|
|
12
|
-
* - `CONTEXT_NEAR_LIMIT` — 75-80%,
|
|
11
|
+
* - `CONTEXT_SOFT_WARN` — 50-75%, prompt is large
|
|
12
|
+
* - `CONTEXT_NEAR_LIMIT` — 75-80%, prompt is near the limit
|
|
13
13
|
* - `PROMPT_TOO_LARGE` — 80-90%, hard reject (allow = false)
|
|
14
14
|
* - `PROMPT_EMERGENCY` — ≥ 90%, hard reject + emergency
|
|
15
15
|
* - `FORCED_OVER_THRESHOLD` — user passed --force at CLI; allow = true
|
|
16
|
-
*
|
|
17
|
-
* See: `.peaks/memory/sub-agent-headroom-forced-compression-gate.md`.
|
|
18
16
|
*/
|
|
19
17
|
import { tierToCode, type ThresholdEvaluation } from './threshold.js';
|
|
20
18
|
export type ContextGuardCode = 'OK' | 'CONTEXT_SOFT_WARN' | 'CONTEXT_NEAR_LIMIT' | 'PROMPT_TOO_LARGE' | 'PROMPT_EMERGENCY' | 'FORCED_OVER_THRESHOLD';
|
|
@@ -8,17 +8,15 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Decision codes:
|
|
10
10
|
* - `OK` — under 50%
|
|
11
|
-
* - `CONTEXT_SOFT_WARN` — 50-75%,
|
|
12
|
-
* - `CONTEXT_NEAR_LIMIT` — 75-80%,
|
|
11
|
+
* - `CONTEXT_SOFT_WARN` — 50-75%, prompt is large
|
|
12
|
+
* - `CONTEXT_NEAR_LIMIT` — 75-80%, prompt is near the limit
|
|
13
13
|
* - `PROMPT_TOO_LARGE` — 80-90%, hard reject (allow = false)
|
|
14
14
|
* - `PROMPT_EMERGENCY` — ≥ 90%, hard reject + emergency
|
|
15
15
|
* - `FORCED_OVER_THRESHOLD` — user passed --force at CLI; allow = true
|
|
16
|
-
*
|
|
17
|
-
* See: `.peaks/memory/sub-agent-headroom-forced-compression-gate.md`.
|
|
18
16
|
*/
|
|
19
17
|
import { CONTEXT_CAPACITY_DEFAULT_BYTES, evaluateThresholdTier, tierToCode } from './threshold.js';
|
|
20
|
-
const NEAR_LIMIT_SUGGEST = '
|
|
21
|
-
const SOFT_WARN_SUGGEST = '
|
|
18
|
+
const NEAR_LIMIT_SUGGEST = 'Prompt is near the context limit; trim the prompt or split into multiple dispatches.';
|
|
19
|
+
const SOFT_WARN_SUGGEST = 'Prompt is large; trim or split into multiple dispatches to stay within context budget.';
|
|
22
20
|
const HARD_REJECT_SUGGEST = 'Trim prompt to < 80% of context capacity. Pass --force at CLI to override (NOT allowed at hook layer).';
|
|
23
21
|
const EMERGENCY_SUGGEST = 'Prompt exceeds 90% of context. Trim aggressively or split into multiple dispatches.';
|
|
24
22
|
/**
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { DocFetcher } from './doc-retriever.js';
|
|
2
|
-
export interface
|
|
2
|
+
export interface DocCacheFetcherOptions {
|
|
3
3
|
readonly cacheDir: string;
|
|
4
4
|
readonly remoteFetcher?: DocFetcher;
|
|
5
5
|
}
|
|
6
|
-
export declare function
|
|
6
|
+
export declare function createDocCacheFetcher(opts: DocCacheFetcherOptions): DocFetcher;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Production DocFetcher.
|
|
3
|
-
*
|
|
2
|
+
* Production DocFetcher. Local cache (per-session, per-dep markdown)
|
|
3
|
+
* preferred when version matches; otherwise delegates to an injected
|
|
4
|
+
* remote fetcher.
|
|
4
5
|
*
|
|
5
6
|
* Hard constraint H2 (locked version): never returns a doc whose version
|
|
6
7
|
* differs from the requested locked version.
|
|
@@ -12,7 +13,7 @@
|
|
|
12
13
|
*/
|
|
13
14
|
import { readFile } from 'node:fs/promises';
|
|
14
15
|
import { join } from 'node:path';
|
|
15
|
-
export function
|
|
16
|
+
export function createDocCacheFetcher(opts) {
|
|
16
17
|
return async (dep, version) => {
|
|
17
18
|
const cachePath = join(opts.cacheDir, `${dep}@${version}.md`);
|
|
18
19
|
try {
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* - MemoryIndexReader (Task 3) — reads .peaks/memory/index.json + layer=A filter
|
|
9
9
|
* - MemoryLruCache (Task 2) — constructed for capacity parity, but NOT used
|
|
10
10
|
* for memo content (see deviation note below)
|
|
11
|
-
* -
|
|
11
|
+
* - truncateToCap — byte-cap truncation of the composed block
|
|
12
12
|
*
|
|
13
13
|
* Deviation from brief — controller-accepted (pre-task 4):
|
|
14
14
|
* The brief mandated use of `MemoryLruCache` for memo content caching. The
|
|
@@ -20,25 +20,14 @@
|
|
|
20
20
|
* `MemoryLruCache` class remains as a separate, reusable LRU primitive
|
|
21
21
|
* (Task 2) and is not consumed by this service.
|
|
22
22
|
*/
|
|
23
|
-
import { compressPrompt } from './headroom-client.js';
|
|
24
23
|
import { MemoryIndexReader } from './memory-index-reader.js';
|
|
25
24
|
import { resolveMemoryPreflightConfig, } from './memory-preflight-config.js';
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
const result = await compressPrompt(text, mode);
|
|
29
|
-
if (result.warning !== null || result.compressedPrompt === null) {
|
|
30
|
-
return { text, truncated: false };
|
|
31
|
-
}
|
|
32
|
-
const compressed = result.compressedPrompt;
|
|
33
|
-
if (Buffer.byteLength(compressed, 'utf8') > capBytes) {
|
|
34
|
-
const sliced = compressed.slice(0, Math.max(0, capBytes - 64)) + '\n…[truncated]';
|
|
35
|
-
return { text: sliced, truncated: true };
|
|
36
|
-
}
|
|
37
|
-
return { text: compressed, truncated: false };
|
|
38
|
-
}
|
|
39
|
-
catch {
|
|
25
|
+
function truncateToCap(text, capBytes) {
|
|
26
|
+
if (Buffer.byteLength(text, 'utf8') <= capBytes) {
|
|
40
27
|
return { text, truncated: false };
|
|
41
28
|
}
|
|
29
|
+
const sliced = text.slice(0, Math.max(0, capBytes - 64)) + '\n…[truncated]';
|
|
30
|
+
return { text: sliced, truncated: true };
|
|
42
31
|
}
|
|
43
32
|
export class MemoryPreflightService {
|
|
44
33
|
reader;
|
|
@@ -85,7 +74,7 @@ export class MemoryPreflightService {
|
|
|
85
74
|
const header = '## Project memory relevant to this task\n';
|
|
86
75
|
const composed = `${header}${listLines}${tail}`;
|
|
87
76
|
const capBytes = Math.max(64, this.config.maxTokens * 4);
|
|
88
|
-
const { text, truncated } =
|
|
77
|
+
const { text, truncated } = truncateToCap(composed, capBytes);
|
|
89
78
|
const droppedCount = truncated ? selected.length - countItemsInBlock(text) : 0;
|
|
90
79
|
return {
|
|
91
80
|
available: true,
|
|
@@ -4,9 +4,6 @@
|
|
|
4
4
|
* 256K default context capacity is a conservative proxy. The LLM's real
|
|
5
5
|
* capacity is its private business (R-1 / R-8 / R-10 / R-13 boundary
|
|
6
6
|
* inherited from slice #009); we use prompt size as the gate signal.
|
|
7
|
-
*
|
|
8
|
-
* See: `.peaks/memory/sub-agent-headroom-forced-compression-gate.md`
|
|
9
|
-
* for the full G9 rule (RL-27..RL-32, AC-50..AC-65).
|
|
10
7
|
*/
|
|
11
8
|
export declare const CONTEXT_CAPACITY_DEFAULT_BYTES: number;
|
|
12
9
|
export declare const THRESHOLD_SOFT_WARN_RATIO = 0.5;
|
|
@@ -4,9 +4,6 @@
|
|
|
4
4
|
* 256K default context capacity is a conservative proxy. The LLM's real
|
|
5
5
|
* capacity is its private business (R-1 / R-8 / R-10 / R-13 boundary
|
|
6
6
|
* inherited from slice #009); we use prompt size as the gate signal.
|
|
7
|
-
*
|
|
8
|
-
* See: `.peaks/memory/sub-agent-headroom-forced-compression-gate.md`
|
|
9
|
-
* for the full G9 rule (RL-27..RL-32, AC-50..AC-65).
|
|
10
7
|
*/
|
|
11
8
|
export const CONTEXT_CAPACITY_DEFAULT_BYTES = 256 * 1024; // 256K
|
|
12
9
|
export const THRESHOLD_SOFT_WARN_RATIO = 0.5; // 50%
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Above 6, the LLM / human is encouraged to split into multiple
|
|
6
6
|
* batches with an explicit reducer step in between. peaks-code's
|
|
7
7
|
* swarm phase dispatches 3; peaks-rd's 4-way fan-out dispatches 4;
|
|
8
|
-
* peaks-qa's 3-way fan-out dispatches 3. The 6 limit leaves
|
|
8
|
+
* peaks-qa's 3-way fan-out dispatches 3. The 6 limit leaves margin
|
|
9
9
|
* for "qa-business-api" / "qa-business-frontend" / "qa-business-regression"
|
|
10
10
|
* subdivisions (3-way + 3-way = 6) without crossing the line.
|
|
11
11
|
*
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Threshold: 1h (configurable). Rationale: the longest empirical
|
|
12
12
|
* peaks-rd / peaks-qa fan-out + reducer cycle is < 60s; the threshold
|
|
13
|
-
* gives slow slices
|
|
13
|
+
* gives slow slices margin without hiding leaks for the next session.
|
|
14
14
|
*/
|
|
15
15
|
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
16
16
|
import { join } from 'node:path';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Generic fzf binary picker (slice 2026-06-14-fzf-
|
|
2
|
+
* Generic fzf binary picker (slice 2026-06-14-fzf-rollout).
|
|
3
3
|
*
|
|
4
4
|
* Promoted from `src/services/slice/slice-pick-service.ts` (which is
|
|
5
5
|
* the only prior caller). Encapsulates the canonical fzf integration
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Generic fzf binary picker (slice 2026-06-14-fzf-
|
|
2
|
+
* Generic fzf binary picker (slice 2026-06-14-fzf-rollout).
|
|
3
3
|
*
|
|
4
4
|
* Promoted from `src/services/slice/slice-pick-service.ts` (which is
|
|
5
5
|
* the only prior caller). Encapsulates the canonical fzf integration
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
import type { IdeAdapter } from '../ide-types.js';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* Model-aware context-window size in tokens.
|
|
4
4
|
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Detection rule (documented):
|
|
6
|
+
* 1. Empty / unknown model → DEFAULT_CONTEXT_WINDOW_TOKENS (200_000).
|
|
7
|
+
* 2. Suffix heuristic — a model id containing `1m` (case-insensitive) is
|
|
8
|
+
* treated as 1M-context.
|
|
9
|
+
* 3. Explicit allowlist — known 1M Claude model ids
|
|
10
|
+
* (ONE_MILLION_CONTEXT_MODELS).
|
|
11
|
+
* 4. Everything else → 200_000 (the safe default).
|
|
8
12
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* - envVar = 'CLAUDE_PROJECT_DIR' : Claude Code 注入的 env 变量,用于 ${...} 占位
|
|
13
|
-
* - hookEvent = 'PreToolUse' : Claude Code hook 数组 key
|
|
14
|
-
* - toolMatcher = 'Bash' | 'Task' : PreToolUse 数组元素的 matcher 字段
|
|
15
|
-
*
|
|
16
|
-
* 不可消除的 per-IDE 字段(见 tech-doc.md §1.3)。
|
|
13
|
+
* Callers MAY additionally infer ≥1M from the observed token count: if
|
|
14
|
+
* `contextTokens > DEFAULT_CONTEXT_WINDOW_TOKENS`, the model cannot be a
|
|
15
|
+
* 200K model and must be ≥1M (see `readClaudeTranscriptEstimate`).
|
|
17
16
|
*/
|
|
17
|
+
export declare function modelContextWindowTokens(model: string): number;
|
|
18
18
|
export declare const CLAUDE_CODE_ADAPTER: IdeAdapter;
|