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
package/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## 4.0.27 — 2026-09-01 (auto-compact context probe fix)
4
+
5
+ **1 atomic commit from session 2026-09-01-session-fdd7aa**:
6
+
7
+ - `7f0f4ca2` perf(auto-compact): vendor-neutral context probe + token-based transcript calibration
8
+
9
+ **Highlights**:
10
+
11
+ 1. **auto-compact 根因修复(三级链)** — context 比例探针之前永远返回 `conservative-fallback`(ratio 0),auto-compact 从不触发。三处修复:(a) vendor-neutral:Claude transcript/statusline fallback 收进 claude-code-adapter 的 `IdeCompactProfile.readContextPercentFallback`,通用 reader 不再硬编码 `~/.claude`;(b) session-id:transcript 查找改用 `outerSessionId`(Claude Code 真 UUID)而非 peaks `sessionId`;(c) 校准:`ratio = contextTokens / contextWindowTokens`,contextTokens 从最新 `message.usage`(input + cache_read + cache_creation)读取,contextWindowTokens 模型感知(1M vs 200K 默认),替换错误的 `bytes/256KB`(永远 100%)。
12
+
13
+ ## 4.0.26 — 2026-09-01 (headroom removal + prompt-cache prefix alignment)
14
+
15
+ **2 atomic commits from session 2026-09-01-session-fdd7aa**:
16
+
17
+ - `3d8c911a` refactor(context): remove headroom-ai half-baked integration
18
+ - `2086074b` perf(dispatch): stabilize sub-agent prompt prefix for prompt-cache alignment
19
+
20
+ **Highlights**:
21
+
22
+ 1. **移除 headroom-ai 半成品集成** — headroom-ai 的 TS SDK 已接但 Python proxy 后端(N-7)从未集成,`--use-headroom` / `--compress-results` 是静默 no-op。删除依赖 + headroom-client/prefs + G7.7 opt-in 路径 + HeadroomPreferences;保留 G7 metadata-only + auto-compact + prompt-cache 对齐作为省 token 主杠杆。通用 DocFetcher 保留为 doc-cache-fetcher.ts。42 文件,+100/−909。
23
+
24
+ 2. **dispatch 前缀稳定化(prompt-cache 对齐)** — 把 LIFECYCLE_RULES 从 prompt 尾部挪到 L1 worktree 块之后,让所有稳定 boilerplate 连续排在最前,最大化 Anthropic prompt-cache 前缀复用(缓存读价约 90% 折扣)。变量块(context % / project memory / task)留在最后。
25
+
3
26
  ## 4.0.25 — 2026-09-01 (codegraph hint fix + post-slice re-index + frontend ACL)
4
27
 
5
28
  **1 atomic commit from session 2026-09-01-session-fdd7aa** (user feedback batch):
@@ -15,6 +15,7 @@ import { evaluateEmitHandoff, JOB_NOT_INITIALIZED, JOB_REMAINING_BLOCKED } from
15
15
  import { readJobShapeDecision, JobShapeDecisionError } from '../../services/code/job-shape-decision.js';
16
16
  import { getSkillPresence } from '../../services/skills/skill-presence-service.js';
17
17
  import { probeInFlightBatch } from '../../services/workflow/workflow-inflight-probe.js';
18
+ import { resolveOuterSessionId } from '../../services/session/binding-status-service.js';
18
19
  export function registerCodeRuntimeCommands(code, io) {
19
20
  addJsonOption(code
20
21
  .command('post-compact-detect')
@@ -175,9 +176,12 @@ export function registerCodeRuntimeCommands(code, io) {
175
176
  // missing/malformed decision file is fine — fall back to advisory.
176
177
  }
177
178
  }
179
+ const sessionId = opts.sessionId ?? readActiveSid(opts.project) ?? 'unknown';
180
+ const outerSessionId = resolveOuterSessionId(opts.project, sessionId);
178
181
  const probe = readContextPercent({
179
182
  projectRoot: opts.project,
180
- sessionId: opts.sessionId ?? readActiveSid(opts.project) ?? 'unknown',
183
+ sessionId,
184
+ outerSessionId,
181
185
  env: process.env,
182
186
  promptSizeBytes
183
187
  });
@@ -218,6 +222,8 @@ export function registerCodeRuntimeCommands(code, io) {
218
222
  ide: probe.ide,
219
223
  capacityBytes: probe.capacityBytes,
220
224
  rawBytes: probe.rawBytes ?? null,
225
+ rawTokens: probe.rawTokens ?? null,
226
+ capacityTokens: probe.capacityTokens ?? null,
221
227
  bytesPrompt: promptSizeBytes ?? null,
222
228
  capturedAt: probe.capturedAt
223
229
  }, [], [
@@ -74,10 +74,9 @@ export function registerMemoryCommand(program, io) {
74
74
  });
75
75
  addJsonOption(memory
76
76
  .command('search <query>')
77
- .description('Fuzzy-search the memory index (deterministic, local, zero-token). Default --limit 6. Pass --compress-results to also emit a headroom-compressed text view of the matches for LLM-side prompt assembly.')
77
+ .description('Fuzzy-search the memory index (deterministic, local, zero-token). Default --limit 6.')
78
78
  .option('--kind <kind>', 'filter by memory kind (one of: project, rule, decision, reference, feedback, convention, module, lesson)')
79
79
  .option('--limit <n>', 'maximum number of matches to return', (value) => Number(value))
80
- .option('--compress-results', 'compress joined match text via headroom-ai (uses preferences.headroom.perTouchpoint.memorySearch mode)')
81
80
  .option('--project <path>', 'target project root (defaults to git root or cwd)')).action((query, options) => {
82
81
  // Lazy import avoids a top-of-file import cycle (memory-commands.ts
83
82
  // imports services that the rest of this file may also touch).
@@ -86,7 +85,6 @@ export function registerMemoryCommand(program, io) {
86
85
  query,
87
86
  ...(options.kind !== undefined ? { kind: options.kind } : {}),
88
87
  ...(options.limit !== undefined ? { limit: options.limit } : {}),
89
- ...(options.compressResults === true ? { compressResults: true } : {}),
90
88
  ...(options.project !== undefined ? { project: options.project } : {}),
91
89
  ...(options.json !== undefined ? { json: options.json } : {}),
92
90
  });
@@ -27,15 +27,14 @@ import { noteDispatched, BATCH_LIMIT } from '../../services/dispatch/batch-count
27
27
  import { writeInitialDispatchRecord } from '../../services/dispatch/dispatch-record-writer.js';
28
28
  import { evaluatePromptSize } from '../../services/context/context-guard.js';
29
29
  import { getCurrentSessionId } from '../../services/skills/skill-presence-service.js';
30
+ import { resolveOuterSessionId } from '../../services/session/binding-status-service.js';
30
31
  import { buildArtifactMeta, buildContextImpact } from '../../services/context/artifact-meta.js';
31
32
  import { assertSafeArtifactPath } from 'peaks-loop-shared-channel';
32
- import { compressPrompt } from '../../services/context/headroom-client.js';
33
- import { resolveHeadroomOptions } from '../../services/context/headroom-prefs.js';
34
33
  import { playwrightProfilePaths } from '../../services/worktree/playwright-profile.js';
35
34
  import { loadPreferences } from '../../services/preferences/preferences-service.js';
36
35
  import { DEFAULT_PREFERENCES } from '../../services/preferences/preferences-types.js';
37
36
  import { writeLogEntry } from '../../services/log/logger.js';
38
- import { HEADROOM_MODES, PROMPT_LIMIT_BYTES, RECOMMENDED_ROLES, validateRole } from './sub-agent-shared.js';
37
+ import { PROMPT_LIMIT_BYTES, RECOMMENDED_ROLES, validateRole } from './sub-agent-shared.js';
39
38
  import { runDispatchFromDag } from './dispatch-from-dag.js';
40
39
  import { TEST_TOOL_DETECTION_BLOCK, formatTestToolDetection } from '../../services/dispatch/test-tool-detection.js';
41
40
  import { MemoryPreflightService } from '../../services/context/memory-preflight-service.js';
@@ -46,7 +45,7 @@ export function registerDispatchCommand(parent, io) {
46
45
  .command('dispatch')
47
46
  .description('Build an IDE-specific tool-call descriptor for a sub-agent dispatch. ' +
48
47
  'Dry-run by design; the LLM executes the returned toolCall in its own ' +
49
- 'environment. Flags: --write-artifact (G7), --use-headroom (G7.7), ' +
48
+ 'environment. Flags: --write-artifact (G7), ' +
50
49
  '--force (G9 CLI 兜底). ' +
51
50
  'See skills/peaks-code/references/sub-agent-dispatch.md for the ' +
52
51
  'orchestrator contract.')
@@ -63,8 +62,6 @@ export function registerDispatchCommand(parent, io) {
63
62
  .option('--project <path>', 'target project root (defaults to cwd)')
64
63
  .option('--batch-id <uuid>', 'batch id for the dispatch (default: auto-generated UUID)')
65
64
  .option('--write-artifact <path>', 'G7: register an artifact file at <path>; CLI computes sha256 + size + writes ArtifactMeta to the dispatch record')
66
- .option('--use-headroom', 'G7.7/G9: compress the prompt via headroom-ai before dispatch (opt-in; falls back to G7 metadata-only if headroom unavailable)')
67
- .option('--headroom-mode <mode>', `G7.7: headroom mode (${HEADROOM_MODES.join(' | ')}); default balanced`)
68
65
  .option('--force', 'G9: override the 80% hard reject threshold at CLI (NOT allowed at hook layer per RL-30 strict)')
69
66
  .option('--from-dag <file>', '2.7.0 slice-dag-dispatcher MVP: read a SliceDag JSON file, dispatch one sub-agent per node in topological order; --batch-id overrides the auto-generated batch id (mutually exclusive with <role>)')
70
67
  .option('--isolation <mode>', 'slice 2026-07-29-worktree-l2-extended Part 2.C: isolation mode for the sub-agent. Accepts "worktree" (Part 2.C + Part 12 L2 surface), "container" (Part 8 contract + Part 12 L4 docker runtime), or "vm" (Part 25 contract; the VM runtime is a follow-up rid and fail-fasts with ISOLATION_VM_NOT_YET_IMPLEMENTED). Auto-spawns a lease + injects PEAKS_<MODE>_LEASE_ID into the dispatch envelope so the sub-agent can write to the isolated surface without a separate auth grant.')
@@ -351,35 +348,14 @@ export function registerDispatchCommand(parent, io) {
351
348
  return;
352
349
  }
353
350
  }
354
- // G7.7 / G9: resolve headroom options from preferences + CLI overrides.
355
- // Preferences hard-block when headroom.enabled=false (returns HEADROOM_DISABLED_BY_PREFERENCE).
356
- // loadPreferences can throw on schema mismatch; we fall back to defaults to avoid
357
- // breaking the dispatch on a stale preferences.json file.
351
+ // loadPreferences can throw on schema mismatch; we fall back to defaults
352
+ // to avoid breaking the dispatch on a stale preferences.json file.
358
353
  let projectPrefs = DEFAULT_PREFERENCES;
359
- let headroomPrefs = DEFAULT_PREFERENCES.headroom;
360
354
  try {
361
355
  projectPrefs = loadPreferences(projectRoot);
362
- headroomPrefs = projectPrefs.headroom;
363
356
  }
364
357
  catch { // TODO(g2): legacy silent catch — grace: 1 minor release (v2.14.0)
365
- // Keep default preferences; the user can re-run with explicit --headroom-mode
366
- // if they want to override the fallback.
367
- }
368
- const headroomResolved = resolveHeadroomOptions(headroomPrefs, {
369
- useHeadroom: options.useHeadroom === true,
370
- ...(options.headroomMode !== undefined ? { headroomMode: options.headroomMode } : {})
371
- });
372
- if (headroomResolved.blocked !== null) {
373
- printResult(io, fail('sub-agent.dispatch', headroomResolved.blocked, `headroom integration is disabled in preferences (headroom.enabled=false); pass --headroom-mode and update preferences first, or run without --use-headroom`, {
374
- role,
375
- toolCall: null,
376
- dispatchRecordPath: null
377
- }, [
378
- 'Edit .peaks/preferences.json: set headroom.enabled = true (per-touchpoint mode is headroom.perTouchpoint.subAgentDispatch).',
379
- 'Or re-run without --use-headroom to dispatch without compression.'
380
- ]), asJson);
381
- process.exitCode = 1;
382
- return;
358
+ // Keep default preferences.
383
359
  }
384
360
  const ide = detectInstalledIde(projectRoot) ?? 'claude-code';
385
361
  const adapter = getAdapter(ide);
@@ -390,12 +366,9 @@ export function registerDispatchCommand(parent, io) {
390
366
  process.exitCode = 1;
391
367
  return;
392
368
  }
393
- // G7.7 headroom compress (opt-in). If headroom fails or is unavailable,
394
- // fall back to the original prompt + emit warning.
395
369
  // Slice 2026-07-22-orchestrator-memory-preflight (Task 5): prepend the
396
370
  // memory preflight block (or silently skip when unavailable) via the
397
- // pure-function builder, BEFORE the headroom-ai compress step so the
398
- // compressor sees the augmented payload.
371
+ // pure-function builder.
399
372
  const preflightService = new MemoryPreflightService(projectRoot, projectPrefs);
400
373
  const memoryBlock = await preflightService.fetchBlock(role);
401
374
  // Slice 2026-07-29-context-evaluation-accuracy: capture the
@@ -410,9 +383,11 @@ export function registerDispatchCommand(parent, io) {
410
383
  let contextProbe = null;
411
384
  try {
412
385
  const { readContextPercent } = await import('../../services/context/auto-compact-reader.js');
386
+ const outerSessionId = resolveOuterSessionId(projectRoot, sid);
413
387
  contextProbe = readContextPercent({
414
388
  projectRoot,
415
389
  sessionId: sid,
390
+ outerSessionId,
416
391
  env: process.env
417
392
  });
418
393
  }
@@ -428,9 +403,8 @@ export function registerDispatchCommand(parent, io) {
428
403
  contextProbe
429
404
  });
430
405
  // Part 2.C: when --isolation worktree, prepend an isolation envelope
431
- // block so the sub-agent sees the lease id + worktree path BEFORE
432
- // headroom-ai compress. The block is short (a few lines) and the
433
- // compressor is expected to keep it. We deliberately do NOT set
406
+ // block so the sub-agent sees the lease id + worktree path. The block
407
+ // is short (a few lines). We deliberately do NOT set
434
408
  // process.env.PEAKS_WORKTREE_LEASE_ID here — sub-agents are spawned
435
409
  // by the LLM in its own environment, not as children of this CLI;
436
410
  // the lease id travels through the dispatch record + prompt body.
@@ -465,20 +439,8 @@ export function registerDispatchCommand(parent, io) {
465
439
  `confirm the file exists. Anti-fake-green rule (sediment 2026-08-11-rid-001-redo-fake-green-recovery-closure §Lesson 1): ` +
466
440
  `if the file does not exist, your verdict MUST be \`status: "blocked"\` with reason "must_ls_files_failed". Do NOT silently skip this step.\n`;
467
441
  }
468
- let effectivePrompt = `${formatTestToolDetection()}\n\n${memoryAugmentedBody}${isolationBlock}${mustLsFilesBlock}`;
469
- let headroomCompressed = false;
470
- let headroomResult = null;
442
+ const effectivePrompt = `${formatTestToolDetection()}\n\n${memoryAugmentedBody}${isolationBlock}${mustLsFilesBlock}`;
471
443
  const warnings = [...decision.warnings];
472
- if (headroomResolved.mode !== null) {
473
- headroomResult = await compressPrompt(effectivePrompt, headroomResolved.mode);
474
- if (headroomResult.warning !== null) {
475
- warnings.push(headroomResult.warning);
476
- }
477
- if (headroomResult.compressed && headroomResult.compressedPrompt !== null) {
478
- effectivePrompt = headroomResult.compressedPrompt;
479
- headroomCompressed = true;
480
- }
481
- }
482
444
  let toolCall;
483
445
  try {
484
446
  toolCall = adapter.subAgentDispatcher.buildToolCall({ role, prompt: effectivePrompt, requestId: rid, sessionId: sid });
@@ -554,8 +516,7 @@ export function registerDispatchCommand(parent, io) {
554
516
  detail: {
555
517
  requestId: rid,
556
518
  ide: adapter.subAgentDispatcher.label,
557
- promptBytes: effectivePrompt.length,
558
- headroomCompressed
519
+ promptBytes: effectivePrompt.length
559
520
  }
560
521
  }, { projectRoot });
561
522
  }
@@ -628,9 +589,6 @@ export function registerDispatchCommand(parent, io) {
628
589
  if (counter.warning) {
629
590
  nextActions.push(`Batch is over the RL-1 limit (${BATCH_LIMIT}); consider splitting into multiple batches.`);
630
591
  }
631
- if (headroomResult && headroomResult.warning === 'HEADROOM_UNAVAILABLE') {
632
- nextActions.push('Headroom daemon unavailable; dispatched with G7 metadata-only fallback.');
633
- }
634
592
  const expectedCompletionSeconds = 45;
635
593
  const artifactsPublicPaths = typeof options.writeArtifact === 'string' && options.writeArtifact.length > 0
636
594
  ? [options.writeArtifact]
@@ -650,7 +608,7 @@ export function registerDispatchCommand(parent, io) {
650
608
  // disk (gitignored under .peaks/_sub_agents/) keeps the prompt
651
609
  // for the sub-agent to read; CLI stdout stays metadata-only.
652
610
  // Surface promptSize + originalPromptSize so the LLM-side
653
- // runner can still reason about headroom without seeing the
611
+ // runner can reason about the size delta without seeing the
654
612
  // content.
655
613
  originalPromptSize: options.prompt.length,
656
614
  promptSize: effectivePrompt.length,
@@ -658,16 +616,6 @@ export function registerDispatchCommand(parent, io) {
658
616
  dispatchRecordPath,
659
617
  batchId,
660
618
  dispatchedInBatch: counter.count,
661
- headroomCompressed,
662
- headroomResult: headroomResult
663
- ? {
664
- mode: headroomResult.mode,
665
- compressed: headroomResult.compressed,
666
- compressionRatio: headroomResult.compressionRatio,
667
- tokensSaved: headroomResult.tokensSaved,
668
- warning: headroomResult.warning
669
- }
670
- : null,
671
619
  forcedAt: decision.forcedAt,
672
620
  contextImpact,
673
621
  artifactMetas: artifactMeta ? [artifactMeta] : [],
@@ -706,7 +654,6 @@ export function registerDispatchCommand(parent, io) {
706
654
  role,
707
655
  batchId,
708
656
  dispatchedInBatch: counter.count,
709
- headroomCompressed,
710
657
  forcedAt: decision.forcedAt
711
658
  }
712
659
  });
@@ -5,8 +5,6 @@ export interface MemorySearchCommandOptions {
5
5
  limit?: number;
6
6
  project?: string;
7
7
  json?: boolean;
8
- /** When true, call headroom-ai to compress joined match text for LLM-side prompt assembly. */
9
- compressResults?: boolean;
10
8
  }
11
9
  export interface MemoryListCommandOptions {
12
10
  kind?: string;
@@ -1,6 +1,6 @@
1
1
  import { findProjectRoot } from '../../services/config/config-safety.js';
2
2
  import { resolveCanonicalProjectRoot } from '../../services/config/config-service.js';
3
- import { loadMemoryIndex, searchMemoryWithResults } from '../../services/memory/memory-search-service.js';
3
+ import { loadMemoryIndex, searchMemory } from '../../services/memory/memory-search-service.js';
4
4
  import { pickFromList } from '../../services/fuzzy-matching/fzf-pick-service.js';
5
5
  import { fail, ok } from 'peaks-loop-shared/result';
6
6
  import { getErrorMessage, printResult } from '../cli-helpers.js';
@@ -97,19 +97,16 @@ export async function runMemorySearch(io, options) {
97
97
  ? options.kind
98
98
  : undefined;
99
99
  try {
100
- const out = await searchMemoryWithResults({
100
+ const matches = searchMemory({
101
101
  query: options.query,
102
102
  projectRoot,
103
103
  ...(options.limit !== undefined ? { limit: options.limit } : {}),
104
104
  ...(kindFilter !== undefined ? { kind: kindFilter } : {}),
105
- }, {
106
- ...(options.compressResults === true ? { compressResults: true } : {})
107
105
  });
108
106
  printResult(io, ok('memory.search', {
109
107
  query: options.query,
110
- total: out.matches.length,
111
- matches: out.matches,
112
- ...(out.compressedResults !== null ? { compressedResults: out.compressedResults } : {}),
108
+ total: matches.length,
109
+ matches,
113
110
  warnings: [],
114
111
  }, []), options.json);
115
112
  }
@@ -8,7 +8,6 @@ const ALLOWED_KEYS = new Set([
8
8
  'agentShieldPrompt',
9
9
  'classifyConservatism',
10
10
  'classifyRules',
11
- 'headroom',
12
11
  'swarmSpeculative',
13
12
  'loopAutonomousEnabled',
14
13
  ]);
@@ -36,13 +36,13 @@ import { BROWSER_REUSE_HINT } from '../../services/qa/browser-reuse-hint.js';
36
36
  // Plan 1 / Task 9 — auto-build peaks-context before peaks-qa runs.
37
37
  import { buildContext } from '../../services/context/context-builder.js';
38
38
  // Plan 1 / Task 10 — production fetcher (replaces mockFetcher).
39
- import { createHeadroomFetcher } from '../../services/context/headroom-fetcher.js';
39
+ import { createDocCacheFetcher } from '../../services/context/doc-cache-fetcher.js';
40
40
  // Plan 2 / Task 8 — consume MUT.sig from peaks-mut into verdict envelope.
41
41
  import { loadMutReport, mutReportPath } from 'peaks-loop-mut';
42
- function buildHeadroomFetcher(sid) {
43
- return createHeadroomFetcher({
42
+ function buildDocFetcher(sid) {
43
+ return createDocCacheFetcher({
44
44
  cacheDir: `.peaks/_runtime/${sid}/doc-cache`,
45
- // remoteFetcher wired in a future slice (headroom-ai programmatic API).
45
+ // remoteFetcher wired in a future slice.
46
46
  });
47
47
  }
48
48
  async function ensureContextForQa(goal, project, sid) {
@@ -55,7 +55,7 @@ async function ensureContextForQa(goal, project, sid) {
55
55
  depsMode: 'locked',
56
56
  docBudgetTokens: 8000,
57
57
  out,
58
- fetcher: buildHeadroomFetcher(sid),
58
+ fetcher: buildDocFetcher(sid),
59
59
  });
60
60
  }
61
61
  catch (error) {
@@ -12,12 +12,10 @@
12
12
  * Everything else is internal to the `peaks sub-agent` group.
13
13
  */
14
14
  import type { SubAgentBatchResult } from '../../services/dispatch/sub-agent-dispatcher.js';
15
- import type { HeadroomMode } from '../../services/context/headroom-client.js';
16
15
  import type { HeartbeatStatus } from '../../services/dispatch/dispatch-record-writer.js';
17
16
  export { probeShell, type ShellProbeReport, type ShellProbeOptions } from '../../services/env/shell-probe.js';
18
17
  export declare const RECOMMENDED_ROLES = "rd | qa | ui | txt | qa-business | qa-perf | qa-security | qa-business-<*> | general-purpose";
19
18
  export declare const HEARTBEAT_STATUSES: readonly HeartbeatStatus[];
20
- export declare const HEADROOM_MODES: readonly HeadroomMode[];
21
19
  export declare const PROMPT_LIMIT_BYTES: number;
22
20
  export type DispatchOptions = {
23
21
  prompt?: string;
@@ -27,8 +25,6 @@ export type DispatchOptions = {
27
25
  project?: string;
28
26
  batchId?: string;
29
27
  writeArtifact?: string;
30
- useHeadroom?: boolean;
31
- headroomMode?: string;
32
28
  force?: boolean;
33
29
  fromDag?: string;
34
30
  /**
@@ -25,7 +25,6 @@ export const HEARTBEAT_STATUSES = [
25
25
  'never-started',
26
26
  'unreadable'
27
27
  ];
28
- export const HEADROOM_MODES = ['balanced', 'aggressive', 'conservative'];
29
28
  export const PROMPT_LIMIT_BYTES = 256 * 1024;
30
29
  /**
31
30
  * Validate a role string. Returns `null` when valid, otherwise the
@@ -75,16 +74,3 @@ export function summarizeBatchResults(results) {
75
74
  }
76
75
  return { total: results.length, done, failed, cancelled, timeout };
77
76
  }
78
- // Note: `isHeadroomMode` and `RegisterSubCommand` used to live here as
79
- // duplicate exports. Removed in slice 2026-06-23-audit-p0-cleanup:
80
- // - `isHeadroomMode` is exported by `src/services/context/headroom-prefs.ts`
81
- // and that is the canonical source — dispatch consumer imports from
82
- // there directly.
83
- // - `RegisterSubCommand` was never used as a type anywhere; the entry
84
- // point (`sub-agent-commands.ts`) calls each register function with
85
- // `(program, io)` directly.
86
- // - `deriveProjectRoot` (audit-p0-reaudit) was removed in slice
87
- // 2026-06-23-audit-3rd: it trusted the record path's `.peaks` segment,
88
- // letting a caller point `--record` at any project's record tree. The
89
- // heartbeat command now trusts `--project` (or `process.cwd()`) and
90
- // leaves the relative() backstop to the R-2 guard.
@@ -36,11 +36,11 @@ import { registerWorkflowLifecycleCommand } from './workflow-lifecycle-commands.
36
36
  // Plan 1 / Task 9 — auto-build peaks-context before peaks-rd runs.
37
37
  import { buildContext } from '../../services/context/context-builder.js';
38
38
  // Plan 1 / Task 10 — production fetcher (replaces mockFetcher).
39
- import { createHeadroomFetcher } from '../../services/context/headroom-fetcher.js';
40
- function buildHeadroomFetcher(sid) {
41
- return createHeadroomFetcher({
39
+ import { createDocCacheFetcher } from '../../services/context/doc-cache-fetcher.js';
40
+ function buildDocFetcher(sid) {
41
+ return createDocCacheFetcher({
42
42
  cacheDir: `.peaks/_runtime/${sid}/doc-cache`,
43
- // remoteFetcher wired in a future slice (headroom-ai programmatic API).
43
+ // remoteFetcher wired in a future slice.
44
44
  });
45
45
  }
46
46
  async function ensureContextForRd(goal, project, sid) {
@@ -53,7 +53,7 @@ async function ensureContextForRd(goal, project, sid) {
53
53
  depsMode: 'locked',
54
54
  docBudgetTokens: 8000,
55
55
  out,
56
- fetcher: buildHeadroomFetcher(sid),
56
+ fetcher: buildDocFetcher(sid),
57
57
  });
58
58
  }
59
59
  catch (error) {
@@ -31,6 +31,7 @@
31
31
  import { existsSync, mkdirSync, readFileSync, writeFileSync, appendFileSync } from 'node:fs';
32
32
  import { dirname, join } from 'node:path';
33
33
  import { getSessionIdCanonical } from '../session/session-manager.js';
34
+ import { resolveOuterSessionId } from '../session/binding-status-service.js';
34
35
  import { AUTO_COMPACT_PRE_COMPACT_RATIO, AUTO_COMPACT_THRESHOLD_RATIO } from '../context/auto-compact-types.js';
35
36
  import { describeMode, thresholdFor } from './auto-compact-modes.js';
36
37
  import { read24hState } from '../24h-mode/store.js';
@@ -525,9 +526,11 @@ export async function runAutoCompact(input) {
525
526
  const mode = input.mode ?? resolveAutoCompactMode(input.projectRoot, sessionId);
526
527
  // Lazy import to avoid the AC-1 module depending on the orchestrator.
527
528
  const { readContextPercent } = await import('../context/auto-compact-reader.js');
529
+ const outerSessionId = resolveOuterSessionId(input.projectRoot, sessionId, input.env ?? process.env);
528
530
  const probe = readContextPercent({
529
531
  projectRoot: input.projectRoot,
530
532
  sessionId,
533
+ outerSessionId,
531
534
  env: input.env
532
535
  });
533
536
  const decision = evaluateAutoCompactDecision({
@@ -1,14 +1,50 @@
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 and,
7
+ * when that misses, ask the adapter for a vendor-specific fallback —
8
+ * no hard-coded IDE names. Per-adapter:
9
+ *
10
+ * - claude-code: its adapter-declared env-var (MVP) + a
11
+ * `readContextPercentFallback` that polls the statusline /
12
+ * transcript (see claude-code-adapter.ts).
13
+ * - trae / codex / cursor / qoder / tongyi-lingma / hermes /
14
+ * openclaw / zcode: each adapter fills its own env-var; until
15
+ * L2-dogfood verifies each surface, adapters may omit `compact`
16
+ * and the probe returns `source: 'conservative-fallback'`.
17
+ *
18
+ * Resolution order (user-overridden → env-var → adapter fallback →
19
+ * conservative-fallback):
20
+ * 1. `promptSizeBytes` (P0 `--prompt-size <bytes>` escape hatch) →
21
+ * `source: 'user-overridden'`.
22
+ * 2. `adapter.compact.envVarForContextPercent` env-var →
23
+ * `source: '<ideId>-env'`.
24
+ * 3. `adapter.compact.readContextPercentFallback?.(input)` — the
25
+ * adapter owns any vendor-specific statusline / transcript probe.
26
+ * 4. `ratio: 0` with `source: 'conservative-fallback'` — the
27
+ * orchestrator MUST NOT auto-fire compact on this signal.
28
+ */
1
29
  import type { ContextPercentProbe } from './auto-compact-types.js';
2
30
  export interface ReadContextPercentInput {
3
31
  readonly projectRoot: string;
4
32
  readonly sessionId: string;
33
+ /**
34
+ * Outer (harness / IDE) session id — the id the IDE uses to name its
35
+ * transcript / session files. Resolved by the caller (env signal → bound
36
+ * session meta) and passed through to the adapter's
37
+ * `readContextPercentFallback`. Optional: when unresolved, the adapter
38
+ * fallback returns null → conservative-fallback.
39
+ */
40
+ readonly outerSessionId?: string | undefined;
5
41
  readonly env?: NodeJS.ProcessEnv | undefined;
6
42
  /**
7
43
  * Slice 2026-07-31-rid-002: explicit byte count from `--prompt-size <bytes>`.
8
44
  * When set to a finite non-negative number, short-circuits the entire
9
45
  * env / statusline / transcript chain with `source: 'user-overridden'`.
10
- * Mac escape hatch — Claude Code Mac does NOT inject
11
- * `CLAUDE_CONTEXT_USAGE_PERCENT` into PreToolUse sub-shells, so the
46
+ * Mac escape hatch — some IDEs (e.g. Claude Code on macOS) do NOT
47
+ * inject their context-percent env-var into PreToolUse sub-shells, so the
12
48
  * user (or a hook wrapper) can inject the bytes they observed themselves.
13
49
  * Priority P0 — above everything else.
14
50
  */
@@ -21,60 +57,20 @@ export interface ReadContextPercentInput {
21
57
  * so the function itself has no hard-coded IDE names.
22
58
  */
23
59
  declare function readEnvPercent(env: NodeJS.ProcessEnv, varName: string): number | null;
24
- /**
25
- * Read the IDE-specific statusline state. MVP path is Claude Code's
26
- * `~/.claude/statusline-state.json`; other IDEs are intentionally
27
- * left for future slices (each IDE will expose its own
28
- * `compact.postCompactDetectCommand` to drive this).
29
- */
30
- declare function readClaudeStatuslinePercent(): number | null;
31
- /**
32
- * Recursive search for `<sessionId>.jsonl` under `projectsDir`. Used by
33
- * `readClaudeTranscriptFallback` and exported via `_internal` so unit
34
- * tests can drive it without monkey-patching `os.homedir` (which is
35
- * non-configurable in ESM module namespaces).
36
- *
37
- * The Mac layout encodes the cwd as a single hash directory; on Mac
38
- * Claude Code nests the transcript under that hash with an extra level
39
- * of subdirectory we cannot predict ahead of time. A flat readdir misses
40
- * that branch and returns null — the silent-failure mode that this fix
41
- * closes.
42
- */
43
- declare function findTranscriptJsonl(projectsDir: string, sessionId: string): {
44
- path: string;
45
- bytes: number;
46
- } | null;
47
- /**
48
- * Conservative transcript-size fallback. Recursively searches
49
- * `~/.claude/projects/<hash>/<sid-or-nested>.jsonl` (Mac may nest
50
- * the jsonl under an
51
- * extra directory we cannot predict ahead of time) and estimates
52
- * `ratio = bytesUsed / 256K`. Returns the bytes seen so the
53
- * orchestrator can show "estimated from 124KB of 256KB transcript"
54
- * in the envelope. Tagged `'transcript-estimate'` (v2.14.0) so callers
55
- * know it is a real signal, NOT a hard gate.
56
- */
57
- declare function readClaudeTranscriptFallback(sessionId: string): {
58
- ratio: number;
59
- bytes: number;
60
- } | null;
61
60
  /**
62
61
  * Probe the current AI CLI's context-fill ratio. Adapter-driven:
63
62
  * looks up the registered `IdeAdapter.compact` profile via
64
- * `getIdeAdapter(detectIdeFromEnv(env))` and reads the
65
- * adapter-declared env-var. Falls back to statusline poll +
66
- * transcript estimate ONLY for adapters that opt in
67
- * (`adapter.id === 'claude-code'` for the MVP); other adapters
68
- * without an env-var hit return `source: 'conservative-fallback'`
69
- * with `ratio: 0` so the orchestrator never auto-fires on a
70
- * missing signal.
63
+ * `getAdapter(detectIdeFromEnv(env))` and reads the
64
+ * adapter-declared env-var. When that misses, delegates to the
65
+ * adapter's optional `readContextPercentFallback` (which owns any
66
+ * vendor-specific statusline / transcript probe). Adapters without a
67
+ * fallback (or a fallback that returns null) yield
68
+ * `source: 'conservative-fallback'` with `ratio: 0` so the
69
+ * orchestrator never auto-fires on a missing signal.
71
70
  */
72
71
  export declare function readContextPercent(input: ReadContextPercentInput): ContextPercentProbe;
73
72
  /** Re-export the env-var probe for unit tests. */
74
73
  export declare const _internal: {
75
74
  readEnvPercent: typeof readEnvPercent;
76
- readClaudeStatuslinePercent: typeof readClaudeStatuslinePercent;
77
- readClaudeTranscriptFallback: typeof readClaudeTranscriptFallback;
78
- findTranscriptJsonl: typeof findTranscriptJsonl;
79
75
  };
80
76
  export {};