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.
Files changed (63) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/dist/cli/commands/code-runtime-commands.js +7 -1
  3. package/dist/cli/commands/core/memory-command.js +1 -3
  4. package/dist/cli/commands/dispatch-commands.js +14 -67
  5. package/dist/cli/commands/memory-commands.d.ts +0 -2
  6. package/dist/cli/commands/memory-commands.js +4 -7
  7. package/dist/cli/commands/preferences-commands.js +0 -1
  8. package/dist/cli/commands/qa-commands.js +5 -5
  9. package/dist/cli/commands/sub-agent-shared.d.ts +0 -4
  10. package/dist/cli/commands/sub-agent-shared.js +0 -14
  11. package/dist/cli/commands/workflow-commands.js +5 -5
  12. package/dist/services/code/auto-compact-orchestrator.js +3 -0
  13. package/dist/services/context/auto-compact-reader.d.ts +45 -49
  14. package/dist/services/context/auto-compact-reader.js +21 -148
  15. package/dist/services/context/auto-compact-types.d.ts +17 -2
  16. package/dist/services/context/build-dispatch-system-prompt.d.ts +4 -4
  17. package/dist/services/context/build-dispatch-system-prompt.js +6 -6
  18. package/dist/services/context/context-guard.d.ts +2 -4
  19. package/dist/services/context/context-guard.js +4 -6
  20. package/dist/services/context/{headroom-fetcher.d.ts → doc-cache-fetcher.d.ts} +2 -2
  21. package/dist/services/context/{headroom-fetcher.js → doc-cache-fetcher.js} +4 -3
  22. package/dist/services/context/memory-preflight-service.js +6 -17
  23. package/dist/services/context/threshold.d.ts +0 -3
  24. package/dist/services/context/threshold.js +0 -3
  25. package/dist/services/dispatch/batch-counter.js +1 -1
  26. package/dist/services/dispatch/leak-detector.js +1 -1
  27. package/dist/services/fuzzy-matching/fzf-pick-service.d.ts +1 -1
  28. package/dist/services/fuzzy-matching/fzf-pick-service.js +1 -1
  29. package/dist/services/ide/adapters/claude-code-adapter.d.ts +12 -12
  30. package/dist/services/ide/adapters/claude-code-adapter.js +290 -1
  31. package/dist/services/ide/ide-types.d.ts +39 -0
  32. package/dist/services/memory/llm-reranker.d.ts +4 -5
  33. package/dist/services/memory/llm-reranker.js +6 -8
  34. package/dist/services/memory/memory-search-service.d.ts +0 -32
  35. package/dist/services/memory/memory-search-service.js +0 -37
  36. package/dist/services/preferences/preferences-service.d.ts +5 -8
  37. package/dist/services/preferences/preferences-service.js +0 -30
  38. package/dist/services/preferences/preferences-types.d.ts +0 -22
  39. package/dist/services/preferences/preferences-types.js +0 -12
  40. package/dist/services/retrospective/retrospective-search-service.d.ts +0 -18
  41. package/dist/services/retrospective/retrospective-search-service.js +0 -32
  42. package/dist/services/session/binding-status-service.d.ts +11 -0
  43. package/dist/services/session/binding-status-service.js +25 -4
  44. package/dist/services/skill/skill-search-service.d.ts +1 -1
  45. package/dist/services/slice/slice-benchmark-service.d.ts +1 -1
  46. package/dist/services/slice/slice-benchmark-service.js +1 -1
  47. package/dist/services/slice/slice-pick-service.d.ts +1 -1
  48. package/dist/services/slice/slice-pick-service.js +1 -1
  49. package/package.json +5 -6
  50. package/skills/bee/peaks-qa/references/qa-context-governance.md +1 -1
  51. package/skills/bee/peaks-rd/SKILL.md +2 -2
  52. package/skills/bee/peaks-rd/references/rd-context-governance.md +2 -2
  53. package/skills/bee/peaks-txt/SKILL.md +1 -1
  54. package/skills/bee/peaks-ui/SKILL.md +1 -1
  55. package/skills/peaks-code/SKILL.md +1 -1
  56. package/skills/peaks-code/references/context-governance.md +7 -34
  57. package/skills/peaks-code/references/envelope-contract.md +2 -7
  58. package/skills/peaks-code/references/sub-agent-dispatch.md +2 -4
  59. package/dist/services/context/headroom-client.d.ts +0 -34
  60. package/dist/services/context/headroom-client.js +0 -117
  61. package/dist/services/context/headroom-prefs.d.ts +0 -46
  62. package/dist/services/context/headroom-prefs.js +0 -34
  63. 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
- * `getIdeAdapter(detectIdeFromEnv(env))` and reads the
141
- * adapter-declared env-var. Falls back to statusline poll +
142
- * transcript estimate ONLY for adapters that opt in
143
- * (`adapter.id === 'claude-code'` for the MVP); other adapters
144
- * without an env-var hit return `source: 'conservative-fallback'`
145
- * with `ratio: 0` so the orchestrator never auto-fires on a
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 / statusline / transcript.
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
- // MVP-only fallback chain: statusline poll + transcript estimate.
193
- // Non-MVP adapters that don't fill `compact` skip this and return
194
- // a conservative-zero probe (the orchestrator will stay in
195
- // 'none' / 'soft-warn' zone and never auto-fire compact).
196
- if (ideId === 'claude-code') {
197
- const statusline = readClaudeStatuslinePercent();
198
- if (statusline !== null) {
199
- return { ratio: statusline, source: 'statusline-poll', capacityBytes, ide: ideId, capturedAt };
200
- }
201
- const fallback = readClaudeTranscriptFallback(input.sessionId);
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, readClaudeStatuslinePercent, readClaudeTranscriptFallback, findTranscriptJsonl };
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
- * bytes/256K; a real signal, NOT
165
- * a hard gate)
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 appended to the system prompt on top of the L1
76
- * worktree governance block so the sub-agent sees them last (i.e.
77
- * most-recently-read), which is the strongest prompt position in
78
- * transformer attention.
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 appended to the system prompt on top of the L1
66
- * worktree governance block so the sub-agent sees them last (i.e.
67
- * most-recently-read), which is the strongest prompt position in
68
- * transformer attention.
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}\n${LIFECYCLE_RULES}`;
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${contextBlock}${taskBody}\n${LIFECYCLE_RULES}`;
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%, suggest --use-headroom
12
- * - `CONTEXT_NEAR_LIMIT` — 75-80%, mandatory --use-headroom suggestion
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%, suggest --use-headroom
12
- * - `CONTEXT_NEAR_LIMIT` — 75-80%, mandatory --use-headroom suggestion
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 = 'Consider --use-headroom to compress prompt.';
21
- const SOFT_WARN_SUGGEST = 'Use --use-headroom to compress prompt proactively.';
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 HeadroomFetcherOptions {
2
+ export interface DocCacheFetcherOptions {
3
3
  readonly cacheDir: string;
4
4
  readonly remoteFetcher?: DocFetcher;
5
5
  }
6
- export declare function createHeadroomFetcher(opts: HeadroomFetcherOptions): DocFetcher;
6
+ export declare function createDocCacheFetcher(opts: DocCacheFetcherOptions): DocFetcher;
@@ -1,6 +1,7 @@
1
1
  /**
2
- * Production DocFetcher. Uses existing headroom-ai dependency for remote
3
- * fetch; local cache (per-session, per-dep markdown) preferred when version matches.
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 createHeadroomFetcher(opts) {
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
- * - compressPrompt (existing) — headroom-ai wrapper for hard-cap compression
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
- async function compressToCap(text, capBytes, mode) {
27
- try {
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 } = await compressToCap(composed, capBytes, 'balanced');
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 headroom
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 headroom without hiding leaks for the next session.
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-headroom-rollout).
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-headroom-rollout).
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
- * Claude Code adapter —— peaks-loop 的"起源 IDE"。
3
+ * Model-aware context-window size in tokens.
4
4
  *
5
- * 该 adapter 从原 `src/services/skills/hooks-settings-service.ts` 提取,保持
6
- * 字节级兼容:用户在 Claude Code 环境下跑 `peaks hooks install` 产出的
7
- * `.claude/settings.json` 与 refactor 前逐字节相同。
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
- * 字段解释(见 PRD AC-1):
10
- * - dirName = '.claude' : Claude Code 项目根下的 settings 目录
11
- * - settingsFileName = 'settings.json'
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;