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
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
|
|
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.
|
|
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 {
|
|
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),
|
|
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
|
-
//
|
|
355
|
-
//
|
|
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
|
|
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
|
|
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
|
|
432
|
-
//
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
|
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:
|
|
111
|
-
matches
|
|
112
|
-
...(out.compressedResults !== null ? { compressedResults: out.compressedResults } : {}),
|
|
108
|
+
total: matches.length,
|
|
109
|
+
matches,
|
|
113
110
|
warnings: [],
|
|
114
111
|
}, []), options.json);
|
|
115
112
|
}
|
|
@@ -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 {
|
|
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
|
|
43
|
-
return
|
|
42
|
+
function buildDocFetcher(sid) {
|
|
43
|
+
return createDocCacheFetcher({
|
|
44
44
|
cacheDir: `.peaks/_runtime/${sid}/doc-cache`,
|
|
45
|
-
// remoteFetcher wired in a future slice
|
|
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:
|
|
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 {
|
|
40
|
-
function
|
|
41
|
-
return
|
|
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
|
|
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:
|
|
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
|
|
11
|
-
*
|
|
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
|
-
* `
|
|
65
|
-
* adapter-declared env-var.
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
69
|
-
* with `ratio: 0` so the
|
|
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 {};
|