peaks-loop 4.0.46 → 4.0.48
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 +54 -0
- package/README-en.md +1 -1
- package/README.md +1 -1
- package/agents/karpathy-reviewer.md +11 -10
- package/dist/cli/cli-helpers.d.ts +34 -0
- package/dist/cli/cli-helpers.js +57 -0
- package/dist/cli/commands/code-job-shape-commands.js +8 -0
- package/dist/cli/commands/code-runtime-commands.d.ts +22 -0
- package/dist/cli/commands/code-runtime-commands.js +139 -16
- package/dist/cli/commands/compact-command.js +241 -1
- package/dist/cli/commands/config-commands.js +15 -9
- package/dist/cli/commands/container-commands.js +3 -3
- package/dist/cli/commands/core/skill-command.js +45 -10
- package/dist/cli/commands/cron-commands.js +2 -1
- package/dist/cli/commands/dashboard-long-run.js +6 -0
- package/dist/cli/commands/dispatch-commands.js +11 -1
- package/dist/cli/commands/doctor/invoke-from-code.js +6 -0
- package/dist/cli/commands/e2e-verify.js +3 -3
- package/dist/cli/commands/governance-classify-contract-commands.js +1 -0
- package/dist/cli/commands/hooks-commands.js +14 -5
- package/dist/cli/commands/job-commands.js +8 -0
- package/dist/cli/commands/loop-commands.js +1 -0
- package/dist/cli/commands/loop-eval-commands.js +15 -0
- package/dist/cli/commands/perf-audit-commands.js +2 -0
- package/dist/cli/commands/playwright-commands.js +14 -1
- package/dist/cli/commands/prd-commands.js +1 -1
- package/dist/cli/commands/qa-commands.js +22 -0
- package/dist/cli/commands/reinject-command.d.ts +72 -0
- package/dist/cli/commands/reinject-command.js +174 -0
- package/dist/cli/commands/request-commands.js +14 -3
- package/dist/cli/commands/scan-commands.js +1 -1
- package/dist/cli/commands/security-audit-commands.js +2 -0
- package/dist/cli/commands/shadcn-commands.js +1 -0
- package/dist/cli/commands/slice-integrate-commands.js +5 -0
- package/dist/cli/commands/statusline-commands.js +44 -4
- package/dist/cli/commands/sub-agent/detached.d.ts +14 -1
- package/dist/cli/commands/sub-agent/detached.js +47 -22
- package/dist/cli/commands/sub-agent-shutdown-commands.js +11 -0
- package/dist/cli/commands/test-commands.js +2 -1
- package/dist/cli/commands/verdict-aggregate-command.js +95 -13
- package/dist/cli/commands/vm-commands.js +7 -7
- package/dist/cli/commands/workflow-commands.js +1 -1
- package/dist/cli/commands/workspace/init-command.js +24 -2
- package/dist/cli/commands/worktree-lease-commands.js +4 -4
- package/dist/cli/index.js +10 -3
- package/dist/cli/program.js +5 -0
- package/dist/hooks/pre-tool-use-sub-agent.js +1 -1
- package/dist/services/adapter/adapter-registry.js +1 -1
- package/dist/services/artifacts/artifact-prerequisites.d.ts +38 -7
- package/dist/services/artifacts/artifact-prerequisites.js +130 -65
- package/dist/services/artifacts/artifact-service.js +1 -1
- package/dist/services/artifacts/request-artifact-service.d.ts +8 -0
- package/dist/services/artifacts/request-artifact-service.js +18 -8
- package/dist/services/artifacts/request-artifact-state-helpers.d.ts +57 -0
- package/dist/services/artifacts/request-artifact-state-helpers.js +91 -10
- package/dist/services/audit-independent/perf-audit-service.d.ts +9 -0
- package/dist/services/audit-independent/perf-audit-service.js +27 -5
- package/dist/services/audit-independent/security-audit-service.d.ts +12 -2
- package/dist/services/audit-independent/security-audit-service.js +28 -6
- package/dist/services/capability-guard-runner/contracts/J01.js +2 -1
- package/dist/services/capability-guard-runner/contracts/J02.js +3 -3
- package/dist/services/capability-guard-runner/contracts/J04.js +4 -2
- package/dist/services/capability-guard-runner/contracts/J07.js +2 -1
- package/dist/services/code/auto-compact-lifecycle.d.ts +130 -1
- package/dist/services/code/auto-compact-lifecycle.js +180 -4
- package/dist/services/code/auto-compact-orchestrator.d.ts +53 -9
- package/dist/services/code/auto-compact-orchestrator.js +166 -34
- package/dist/services/code/compact-event-settle.d.ts +122 -0
- package/dist/services/code/compact-event-settle.js +219 -0
- package/dist/services/code/orchestrator-can-do.d.ts +4 -2
- package/dist/services/code/orchestrator-can-do.js +37 -5
- package/dist/services/codegraph/codegraph-exclude-reconciler.js +2 -1
- package/dist/services/codegraph/codegraph-process-runner.js +3 -2
- package/dist/services/compact/request-transition-hook.js +5 -2
- package/dist/services/compact-history/compact-history-service.d.ts +75 -0
- package/dist/services/compact-history/compact-history-service.js +49 -0
- package/dist/services/config/config-restore.d.ts +12 -1
- package/dist/services/config/config-restore.js +35 -4
- package/dist/services/config/config-rollback.js +6 -1
- package/dist/services/config/config-safety.d.ts +52 -0
- package/dist/services/config/config-safety.js +75 -1
- package/dist/services/context/auto-compact-dispatcher.d.ts +7 -37
- package/dist/services/context/auto-compact-dispatcher.js +113 -40
- package/dist/services/context/auto-compact-reader.d.ts +68 -28
- package/dist/services/context/auto-compact-reader.js +155 -1
- package/dist/services/context/auto-compact-types.d.ts +89 -12
- package/dist/services/context/auto-compact-types.js +16 -32
- package/dist/services/context/harness-context-witness.d.ts +310 -0
- package/dist/services/context/harness-context-witness.js +606 -0
- package/dist/services/context/harness-window-config.d.ts +412 -0
- package/dist/services/context/harness-window-config.js +607 -0
- package/dist/services/context/main-session-monitor.d.ts +27 -0
- package/dist/services/context/main-session-monitor.js +32 -1
- package/dist/services/context/post-compact-reinjection.d.ts +221 -0
- package/dist/services/context/post-compact-reinjection.js +491 -0
- package/dist/services/dispatch/merge-back-runner.js +5 -5
- package/dist/services/dispatch/service-shutdown.js +3 -3
- package/dist/services/doc/doc-generator.js +2 -1
- package/dist/services/env/shell-probe.js +1 -1
- package/dist/services/evidence/evidence-generator.js +86 -49
- package/dist/services/final-review/final-review-service.d.ts +9 -0
- package/dist/services/final-review/final-review-service.js +36 -12
- package/dist/services/fuzzy-matching/fzf-pick-service.js +2 -0
- package/dist/services/hooks/auto-compact-hook-install.d.ts +10 -2
- package/dist/services/hooks/auto-compact-hook-install.js +8 -0
- package/dist/services/ide/adapters/claude-code-adapter.d.ts +107 -3
- package/dist/services/ide/adapters/claude-code-adapter.js +154 -7
- package/dist/services/ide/ide-registry.d.ts +31 -0
- package/dist/services/ide/ide-registry.js +35 -0
- package/dist/services/ide/ide-types.d.ts +59 -0
- package/dist/services/job/job-state-store.js +7 -0
- package/dist/services/lint/detect-eslint.js +2 -2
- package/dist/services/lint/eslint-runner.js +3 -1
- package/dist/services/loop/evaluator-dispatcher.js +2 -1
- package/dist/services/memory/project-memory-service/index/kind-dispatch.js +1 -1
- package/dist/services/memory/project-memory-service/store/paths.d.ts +9 -1
- package/dist/services/memory/project-memory-service/store/paths.js +15 -6
- package/dist/services/polyrepo/polyrepo-dispatcher.js +11 -0
- package/dist/services/prd/best-practice-auto-trigger.js +1 -0
- package/dist/services/prd/handoff-auto-regen.js +31 -27
- package/dist/services/prd/handoff-frontmatter.d.ts +44 -0
- package/dist/services/prd/handoff-frontmatter.js +75 -0
- package/dist/services/prd/handoff-service.d.ts +41 -2
- package/dist/services/prd/handoff-service.js +81 -8
- package/dist/services/prd/handoff-types.d.ts +3 -2
- package/dist/services/prd/handoff-types.js +3 -2
- package/dist/services/qa/qa-business-review-state.js +9 -0
- package/dist/services/release/version-precheck-service.d.ts +2 -1
- package/dist/services/release/version-precheck-service.js +82 -12
- package/dist/services/runtime/vendor-adapter.d.ts +29 -4
- package/dist/services/runtime/vendors/claude-code.js +1 -1
- package/dist/services/runtime/vendors/codex.js +1 -1
- package/dist/services/runtime/vendors/copilot.js +1 -1
- package/dist/services/sc/sc-service.js +1 -1
- package/dist/services/scan/diff-scope-service.js +2 -2
- package/dist/services/scan/file-size-scan.js +2 -2
- package/dist/services/scan/karpathy-service.js +2 -2
- package/dist/services/scan/orphan-service.js +2 -1
- package/dist/services/scan/type-sanity-service.js +2 -2
- package/dist/services/session/session-checkpoint-service.js +8 -0
- package/dist/services/skill/resume-detector.js +29 -11
- package/dist/services/skillhub/tar-runtime.js +1 -0
- package/dist/services/skills/hooks-codegate-superpowers.d.ts +14 -0
- package/dist/services/skills/hooks-codegate-superpowers.js +99 -3
- package/dist/services/skills/hooks-settings-service.d.ts +12 -0
- package/dist/services/skills/hooks-settings-service.js +91 -14
- package/dist/services/skills/session-start-hook-constants.d.ts +86 -0
- package/dist/services/skills/session-start-hook-constants.js +86 -0
- package/dist/services/skills/skill-presence-service.js +9 -0
- package/dist/services/skills/skill-statusline-service.d.ts +14 -0
- package/dist/services/slice/slice-check-service.js +31 -12
- package/dist/services/slice/slice-decompose-runners.js +2 -1
- package/dist/services/slice/slice-review-state.js +8 -0
- package/dist/services/upgrade/upgrade-service.js +1 -0
- package/dist/services/workflow/pipeline-verify-gate-support.d.ts +47 -10
- package/dist/services/workflow/pipeline-verify-gate-support.js +212 -93
- package/dist/services/workflow/pipeline-verify-service.js +24 -23
- package/dist/services/workflow/pipeline-verify-types.d.ts +10 -3
- package/dist/services/workflow/workflow-skip-service.js +2 -1
- package/dist/services/workspace/claude-settings-template.d.ts +56 -8
- package/dist/services/workspace/claude-settings-template.js +98 -20
- package/dist/services/workspace/migrate-service.js +1 -1
- package/dist/services/workspace/workspace-claude-settings-materializer.js +124 -9
- package/dist/services/workspace/workspace-service.js +8 -0
- package/dist/services/worktree/host-worktree-reconciler.js +1 -0
- package/dist/services/worktree/long-path-cleanup.js +3 -2
- package/dist/shared/process.js +1 -1
- package/package.json +6 -6
- package/scripts/install-skills.mjs +1 -0
- package/scripts/watch.mjs +3 -1
- package/skills/bee/peaks-perf-audit/SKILL.md +1 -1
- package/skills/bee/peaks-prd/SKILL.md +8 -6
- package/skills/bee/peaks-qa/SKILL.md +7 -7
- package/skills/bee/peaks-qa/references/qa-runbook.md +2 -2
- package/skills/bee/peaks-qa/references/qa-transition-gates.md +7 -7
- package/skills/bee/peaks-rd/SKILL.md +10 -8
- package/skills/bee/peaks-rd/references/artifact-per-request.md +2 -2
- package/skills/bee/peaks-rd/references/parallel-review-fanout.md +7 -5
- package/skills/bee/peaks-rd/references/rd-fanout-contracts.md +13 -13
- package/skills/bee/peaks-rd/references/rd-runbook.md +9 -5
- package/skills/bee/peaks-rd/references/rd-transition-gates.md +9 -7
- package/skills/bee/peaks-rd/references/writing-handoff-frontmatter.md +6 -6
- package/skills/bee/peaks-reviewer/SKILL.md +1 -1
- package/skills/bee/peaks-sc/SKILL.md +1 -1
- package/skills/bee/peaks-security-audit/SKILL.md +1 -1
- package/skills/bee/peaks-txt/SKILL.md +1 -1
- package/skills/bee/peaks-ui/SKILL.md +1 -1
- package/skills/peaks-audit/SKILL.md +1 -1
- package/skills/peaks-code/SKILL.md +3 -3
- package/skills/peaks-code/references/a2a-artifact-mapping.md +3 -3
- package/skills/peaks-code/references/local-artifact-workspace.md +1 -1
- package/skills/peaks-code/references/resume-detection.md +13 -7
- package/skills/peaks-code/references/runbook.md +3 -2
- package/skills/peaks-code/references/session-overload-signal-index.md +2 -1
- package/skills/peaks-code/references/sub-agent-dispatch.md +1 -1
- package/skills/peaks-code/references/workflow-gates-and-types.md +8 -6
- package/skills/peaks-content/SKILL.md +1 -1
- package/skills/peaks-doctor/SKILL.md +1 -1
- package/skills/peaks-final-review/SKILL.md +1 -1
- package/skills/peaks-ide/SKILL.md +1 -1
- package/skills/peaks-issue-fix-orchestrator/SKILL.md +1 -1
- package/skills/peaks-resume/SKILL.md +1 -1
- package/skills/peaks-slice-decompose/SKILL.md +1 -1
- package/skills/peaks-solo/SKILL.md +1 -1
- package/skills/peaks-sop/SKILL.md +1 -1
- package/skills/peaks-status/SKILL.md +1 -1
- package/skills/peaks-test/SKILL.md +1 -1
|
@@ -9,10 +9,15 @@
|
|
|
9
9
|
* conversation history — so peaks-loop drives the entire compaction:
|
|
10
10
|
*
|
|
11
11
|
* 1. Read current context % (via IDE adapter's `readContextPercent`).
|
|
12
|
-
* 2. If ratio ≥ 0.95 (RED LINE):
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
12
|
+
* 2. If ratio ≥ 0.95 (RED LINE): peaks-loop ASKS the harness to
|
|
13
|
+
* compact and reports that it is waiting. It does NOT block
|
|
14
|
+
* sub-agent dispatch (slice
|
|
15
|
+
* 2026-09-13-auto-compact-trigger-ownership, T3): peaks-loop owns
|
|
16
|
+
* the *decision*, the harness owns the *capability*, and peaks-loop
|
|
17
|
+
* has no way to compact a running session itself — so a "gate"
|
|
18
|
+
* here gated nothing and deadlocked the runner at the worst
|
|
19
|
+
* moment. If the ratio keeps rising and the harness has not
|
|
20
|
+
* compacted, the honest move is to say so and hand control back.
|
|
16
21
|
* 3. If 0.85 ≤ ratio < 0.95 (pre-compact zone): peaks-loop prepares
|
|
17
22
|
* the convergence toolkit (checkpoint + auto-decisions log +
|
|
18
23
|
* IDE-dispatch handle) and surfaces it to the LLM. The LLM
|
|
@@ -24,18 +29,20 @@
|
|
|
24
29
|
* Why two tiers (vs. one): the LLM uses the 0.85–0.95 zone for
|
|
25
30
|
* intelligent convergence — wait for in-flight sub-agents, finish
|
|
26
31
|
* the current todo row, persist a checkpoint, then compact. At 0.95
|
|
27
|
-
*
|
|
28
|
-
* the LLM-runner keeps working
|
|
29
|
-
* intervention.
|
|
32
|
+
* peaks-loop stops negotiating and requests the compact outright. Net
|
|
33
|
+
* effect: the LLM-runner keeps working at any ratio without human
|
|
34
|
+
* intervention, and without a gate it cannot satisfy.
|
|
30
35
|
*/
|
|
31
36
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, appendFileSync } from 'node:fs';
|
|
32
37
|
import { dirname, join } from 'node:path';
|
|
33
38
|
import { getSessionIdCanonical } from '../session/session-manager.js';
|
|
34
39
|
import { resolveOuterSessionId } from '../session/binding-status-service.js';
|
|
35
|
-
import {
|
|
40
|
+
import { resolveCanonicalProjectRoot } from '../config/config-service.js';
|
|
41
|
+
import { describeHarnessWindowSync, harnessWindowSyncWarning } from '../context/harness-window-config.js';
|
|
42
|
+
import { AUTO_COMPACT_PRE_COMPACT_RATIO, DEPRECATED_ENVELOPE_FIELDS } from '../context/auto-compact-types.js';
|
|
36
43
|
import { describeMode, thresholdFor } from './auto-compact-modes.js';
|
|
37
44
|
import { resolveAutoCompactProfile } from '../mode/mode-status-service.js';
|
|
38
|
-
import { CompactLifecyclePublisher, newCompactRunId, resolveDispatchedStage, settleOpenLifecycleRun, summarizeLifecycleError } from './auto-compact-lifecycle.js';
|
|
45
|
+
import { CompactLifecyclePublisher, fillEventSettledMeasurement, newCompactRunId, resolveDispatchedStage, settleOpenLifecycleRun, summarizeLifecycleError } from './auto-compact-lifecycle.js';
|
|
39
46
|
const PRE_COMPACT_REASON = 'pre-compact-auto';
|
|
40
47
|
/**
|
|
41
48
|
* Map a context ratio to a `CompactTrigger` action. Pure; the side
|
|
@@ -44,7 +51,7 @@ const PRE_COMPACT_REASON = 'pre-compact-auto';
|
|
|
44
51
|
*
|
|
45
52
|
* - ratio < preCompact → 'none' or 'soft-warn'
|
|
46
53
|
* - ratio ≥ preCompact → 'pre-compact' (async-friendly path)
|
|
47
|
-
* - ratio ≥ redLine → 'red-line' (
|
|
54
|
+
* - ratio ≥ redLine → 'red-line' (ask the harness; dispatch continues)
|
|
48
55
|
*
|
|
49
56
|
* Slice 2026-07-28 (rid-027): `mode` selects the threshold table.
|
|
50
57
|
* Default `'standard'` (0.85/0.95). `'partial'` (0.70/0.85) is used
|
|
@@ -64,10 +71,17 @@ export function evaluateCompactTrigger(ratio, mode = 'standard') {
|
|
|
64
71
|
};
|
|
65
72
|
}
|
|
66
73
|
if (ratio >= redLine) {
|
|
74
|
+
// Slice 2026-09-13-auto-compact-trigger-ownership (T3): the red line no
|
|
75
|
+
// longer claims it blocks anything. It cannot: peaks-loop has no
|
|
76
|
+
// executor for a running session (no `/compact` the model may invoke, no
|
|
77
|
+
// hook-initiated compact, no `--compact` flag), so "refuse dispatch until
|
|
78
|
+
// ratio < 0.85" was a gate with no key — a constructive deadlock at the
|
|
79
|
+
// exact moment the runner most needed to keep working. What peaks-loop
|
|
80
|
+
// CAN do is ask the harness (whose own trigger is armed) and say so.
|
|
67
81
|
return {
|
|
68
82
|
kind: 'red-line',
|
|
69
83
|
ratio,
|
|
70
|
-
message: `Context at ${(ratio * 100).toFixed(1)}% ≥ ${(redLine * 100).toFixed(0)}% red line (mode=${mode}).
|
|
84
|
+
message: `Context at ${(ratio * 100).toFixed(1)}% ≥ ${(redLine * 100).toFixed(0)}% red line (mode=${mode}). peaks-loop has asked the harness to compact and is WAITING for it — sub-agent dispatch is NOT blocked; carry on and re-probe with \`peaks code context-now\`.`
|
|
71
85
|
};
|
|
72
86
|
}
|
|
73
87
|
if (ratio < preCompact) {
|
|
@@ -104,8 +118,13 @@ export function evaluateCompactTrigger(ratio, mode = 'standard') {
|
|
|
104
118
|
* - 0.85 ≤ ratio < 0.95 → pre-compact; if in-flight batch
|
|
105
119
|
* present, defer (D6.e); else dispatch
|
|
106
120
|
* IDE compact asynchronously.
|
|
107
|
-
* - ratio ≥ 0.95 → red-line;
|
|
108
|
-
* regardless of in-flight batch.
|
|
121
|
+
* - ratio ≥ 0.95 → red-line; ask the harness to compact
|
|
122
|
+
* regardless of in-flight batch. Nothing is
|
|
123
|
+
* gated — dispatch is NOT blocked (slice
|
|
124
|
+
* 2026-09-13-auto-compact-trigger-ownership:
|
|
125
|
+
* peaks-loop cannot compact a running session,
|
|
126
|
+
* so a "block" gated nothing and deadlocked the
|
|
127
|
+
* runner).
|
|
109
128
|
*/
|
|
110
129
|
export function evaluateAutoCompactDecision(input) {
|
|
111
130
|
const trigger = evaluateCompactTrigger(input.ratio, input.mode ?? 'standard');
|
|
@@ -123,7 +142,10 @@ export function evaluateAutoCompactDecision(input) {
|
|
|
123
142
|
return { shouldCompact: false, reason: 'below-threshold', trigger, action: 'soft-warn' };
|
|
124
143
|
}
|
|
125
144
|
if (trigger.kind === 'red-line') {
|
|
126
|
-
// Red line: ignore in-flight batch —
|
|
145
|
+
// Red line: ignore in-flight batch — the harness is asked NOW rather than
|
|
146
|
+
// waiting for the batch to drain. Not "synchronous dispatch": peaks-loop
|
|
147
|
+
// has no synchronous compact to run, it can only request one and report
|
|
148
|
+
// that it is waiting.
|
|
127
149
|
return { shouldCompact: true, reason: 'red-line', trigger, action: 'red-line' };
|
|
128
150
|
}
|
|
129
151
|
// pre-compact zone (0.85 ≤ ratio < 0.95): honor D6.e in-flight deferral.
|
|
@@ -162,7 +184,7 @@ export function buildConvergencePlan(input) {
|
|
|
162
184
|
checkpointPath: input.checkpointPath,
|
|
163
185
|
nextActions: [...input.nextActions],
|
|
164
186
|
resumeHint: input.redLine === true
|
|
165
|
-
? 'RED-LINE compact
|
|
187
|
+
? 'RED-LINE compact requested from the harness; work CONTINUES (nothing is blocked). Re-probe with `peaks code context-now`; if the ratio is still ≥ 0.95 and the harness has not compacted, report it and hand control back to the user.'
|
|
166
188
|
: 'post-compact-detect shouldAutoResume → resume pre-compact plan from checkpoint'
|
|
167
189
|
};
|
|
168
190
|
}
|
|
@@ -204,6 +226,13 @@ function appendAutoDecisionLog(input) {
|
|
|
204
226
|
* The file is gitignored under `.peaks/_runtime/<sessionId>/txt/` and
|
|
205
227
|
* is one-shot: the LLM should `mv` it to `.consumed` after firing
|
|
206
228
|
* `/compact`. A re-run will overwrite.
|
|
229
|
+
*
|
|
230
|
+
* ZERO READERS as of slice 2026-09-13-auto-compact-trigger-ownership: the
|
|
231
|
+
* write survives, but nothing consumes the file, and its `nextAction` asks
|
|
232
|
+
* for a `/compact` the model cannot invoke (the Skill tool exposes only
|
|
233
|
+
* `/init` and `/security-review`; hooks can observe or veto, never initiate).
|
|
234
|
+
* Retained deliberately for the sibling A2 slice, which owns harness-side
|
|
235
|
+
* state re-injection. Not a capability peaks-loop has today.
|
|
207
236
|
*/
|
|
208
237
|
function writeMainSessionCompactIntent(input) {
|
|
209
238
|
const dir = join(input.projectRoot, '.peaks', '_runtime', input.sessionId, 'txt');
|
|
@@ -242,7 +271,7 @@ function writePreCompactCheckpoint(input) {
|
|
|
242
271
|
// rehydrates from the auto-decisions log + open question list.
|
|
243
272
|
mode: 'full-auto',
|
|
244
273
|
currentPlan: input.redLine === true
|
|
245
|
-
? 'RED-LINE compact
|
|
274
|
+
? 'RED-LINE compact REQUESTED from the harness (not executed by peaks-loop); work continues'
|
|
246
275
|
: 'auto-compact in progress; resume from auto-decisions.md',
|
|
247
276
|
openQuestions: [],
|
|
248
277
|
recentDecisions: [],
|
|
@@ -276,13 +305,14 @@ function resolveAutoCompactMode(projectRoot) {
|
|
|
276
305
|
* 5. If trigger.kind === 'pre-compact' AND in-flight batch → wait.
|
|
277
306
|
* 6. If trigger.kind === 'pre-compact' → async dispatch (write
|
|
278
307
|
* checkpoint + IDE compact; orchestrator returns immediately).
|
|
279
|
-
* 7. If trigger.kind === 'red-line' →
|
|
280
|
-
*
|
|
308
|
+
* 7. If trigger.kind === 'red-line' → dispatch the IDE compact, report
|
|
309
|
+
* that the harness has been asked and that dispatch continues.
|
|
281
310
|
*
|
|
282
311
|
* The caller (CLI or skill body) handles the actual return — D7's
|
|
283
|
-
* post-compact-detect will pick up the checkpoint on the next turn.
|
|
284
|
-
*
|
|
285
|
-
*
|
|
312
|
+
* post-compact-detect will pick up the checkpoint on the next turn. For
|
|
313
|
+
* red-line, the caller keeps working; it re-probes and, if the ratio is
|
|
314
|
+
* still rising with no compact from the harness, reports that instead of
|
|
315
|
+
* stalling.
|
|
286
316
|
*/
|
|
287
317
|
export async function runAutoCompact(input) {
|
|
288
318
|
const sessionId = input.sessionId ?? getSessionIdCanonical(input.projectRoot);
|
|
@@ -300,7 +330,7 @@ export async function runAutoCompact(input) {
|
|
|
300
330
|
// v2.13.0 zero-pause contract.
|
|
301
331
|
const mode = input.mode ?? resolveAutoCompactMode(input.projectRoot);
|
|
302
332
|
// Lazy import to avoid the AC-1 module depending on the orchestrator.
|
|
303
|
-
const { readContextPercent } = await import('../context/auto-compact-reader.js');
|
|
333
|
+
const { readContextPercent, syncHarnessWindowForProject } = await import('../context/auto-compact-reader.js');
|
|
304
334
|
const outerSessionId = resolveOuterSessionId(input.projectRoot, sessionId, input.env ?? process.env);
|
|
305
335
|
const probe = readContextPercent({
|
|
306
336
|
projectRoot: input.projectRoot,
|
|
@@ -308,6 +338,21 @@ export async function runAutoCompact(input) {
|
|
|
308
338
|
outerSessionId,
|
|
309
339
|
env: input.env
|
|
310
340
|
});
|
|
341
|
+
// Slice 2026-09-13-auto-compact-trigger-ownership (T1 + T2): write the very
|
|
342
|
+
// denominator this probe divided by into the harness's own machine-local
|
|
343
|
+
// settings, so "peaks-loop's 85%" and "the harness's trigger" are the same
|
|
344
|
+
// point on one scale. Idempotent — an unchanged value performs no write, so
|
|
345
|
+
// a hook firing this on every Bash call cannot churn the file.
|
|
346
|
+
const harnessWindow = syncHarnessWindowForProject({
|
|
347
|
+
// Promoted to the git root first (`--project .` is what the PreToolUse
|
|
348
|
+
// hook passes): the harness's settings live at the project root, and the
|
|
349
|
+
// envelope must report an absolute path for the write it claims to have
|
|
350
|
+
// made. Fail-open — `resolveCanonicalProjectRoot` returns its input when
|
|
351
|
+
// nothing resolves.
|
|
352
|
+
projectRoot: resolveCanonicalProjectRoot(input.projectRoot),
|
|
353
|
+
env: input.env,
|
|
354
|
+
tokens: probe.capacityTokens ?? null
|
|
355
|
+
});
|
|
311
356
|
const decision = evaluateAutoCompactDecision({
|
|
312
357
|
ratio: probe.ratio,
|
|
313
358
|
// Slice 4.0.8 (D4d): production `inFlightBatch` MUST come from
|
|
@@ -337,27 +382,79 @@ export async function runAutoCompact(input) {
|
|
|
337
382
|
// still open at `compacting`. Nothing is written when there is no
|
|
338
383
|
// open run, when the ratio is still high, or when the probe could
|
|
339
384
|
// not measure at all.
|
|
340
|
-
settleOpenLifecycleRun({
|
|
385
|
+
const settled = settleOpenLifecycleRun({
|
|
341
386
|
projectRoot: input.projectRoot,
|
|
342
387
|
sessionId,
|
|
343
388
|
measuredRatio: probe.ratio,
|
|
344
389
|
source: probe.source,
|
|
345
390
|
autoFireThreshold: thresholdFor(mode, 'autoFire'),
|
|
346
391
|
onLifecycleStage: input.onLifecycleStage
|
|
347
|
-
})
|
|
392
|
+
}) ??
|
|
393
|
+
// Repair R1 (`2026-09-13-compact-event-settle`): the HARNESS event may
|
|
394
|
+
// already have closed this run WITHOUT an honest post-compact number —
|
|
395
|
+
// in which case the call above finds nothing open, and without this the
|
|
396
|
+
// calibration pair stays blank for exactly the compactions the event path
|
|
397
|
+
// exists to witness. Fills the number the event owed.
|
|
398
|
+
fillEventSettledMeasurement({
|
|
399
|
+
projectRoot: input.projectRoot,
|
|
400
|
+
sessionId,
|
|
401
|
+
measuredRatio: probe.ratio,
|
|
402
|
+
source: probe.source
|
|
403
|
+
});
|
|
404
|
+
// Slice 2026-09-13-auto-compact-trigger-ownership (T4): a settle means a
|
|
405
|
+
// dispatched compact demonstrably landed. Append an `observed` row
|
|
406
|
+
// carrying the measured ratio, so `peaks compact history` can show
|
|
407
|
+
// "asked at R (intent) / landed by R' (observed)" once a real session has
|
|
408
|
+
// run. Without this row the intent has nothing to be compared against.
|
|
409
|
+
if (settled !== null) {
|
|
410
|
+
appendObservedCompactEvent({
|
|
411
|
+
projectRoot: input.projectRoot,
|
|
412
|
+
sessionId,
|
|
413
|
+
event: {
|
|
414
|
+
schemaVersion: 1,
|
|
415
|
+
kind: 'observed',
|
|
416
|
+
ts: new Date().toISOString(),
|
|
417
|
+
target: input.target ?? 'main',
|
|
418
|
+
mode,
|
|
419
|
+
ide: probe.ide,
|
|
420
|
+
pathway: 'post-compact-probe',
|
|
421
|
+
beforeRatio: settled.triggerRatio,
|
|
422
|
+
afterRatio: probe.ratio,
|
|
423
|
+
redLine: false,
|
|
424
|
+
ok: true,
|
|
425
|
+
checkpointPath: '',
|
|
426
|
+
dispatchMessage: `post-compact probe measured ratio ${(probe.ratio * 100).toFixed(1)}% (source=${probe.source}) after the compact dispatched at ${(settled.triggerRatio * 100).toFixed(1)}%`,
|
|
427
|
+
windowTokens: probe.capacityTokens ?? null,
|
|
428
|
+
windowSource: probe.capacitySource ?? null
|
|
429
|
+
}
|
|
430
|
+
});
|
|
431
|
+
}
|
|
348
432
|
return {
|
|
349
433
|
ok: true,
|
|
350
434
|
code: decision.reason === 'in-flight-batch' ? 'AUTO_COMPACT_WAIT' : 'AUTO_COMPACT_SKIP',
|
|
351
|
-
message: decision.trigger.kind === 'soft-warn'
|
|
435
|
+
message: `${decision.trigger.kind === 'soft-warn'
|
|
352
436
|
? decision.trigger.message
|
|
353
437
|
: decision.reason === 'in-flight-batch'
|
|
354
438
|
? `In-flight batch detected; deferring pre-compact (ratio=${(probe.ratio * 100).toFixed(1)}%); next probe will re-evaluate.`
|
|
355
|
-
: `Context at ${(probe.ratio * 100).toFixed(1)}%; below the ${(thresholdFor(mode, 'autoFire') * 100).toFixed(0)}% auto-fire threshold (mode=${mode})
|
|
439
|
+
: `Context at ${(probe.ratio * 100).toFixed(1)}%; below the ${(thresholdFor(mode, 'autoFire') * 100).toFixed(0)}% auto-fire threshold (mode=${mode}).`}${
|
|
440
|
+
// No compact was needed, but the sync may still have rewritten the
|
|
441
|
+
// harness's settings (the first probe of a project always does). The
|
|
442
|
+
// notice is appended ONLY for an actual write — the other actions'
|
|
443
|
+
// sentences would be noise on every quiet probe — PLUS the one refusal
|
|
444
|
+
// that is not quiet: a refused write that leaves peaks-loop's number and
|
|
445
|
+
// the harness's pinned window disagreeing. That is the moment the ratio
|
|
446
|
+
// stops describing the harness's trigger, so staying silent is exactly
|
|
447
|
+
// the failure 要告知 exists to prevent.
|
|
448
|
+
harnessWindow !== null && harnessWindow !== undefined &&
|
|
449
|
+
(harnessWindow.action === 'written' || harnessWindowSyncWarning(harnessWindow) !== null)
|
|
450
|
+
? ` ${describeHarnessWindowSync(harnessWindow)}`
|
|
451
|
+
: ''}`,
|
|
356
452
|
data: {
|
|
357
453
|
sessionId,
|
|
358
454
|
ratio: probe.ratio,
|
|
359
455
|
source: probe.source,
|
|
360
|
-
decision: decision.reason === 'in-flight-batch' ? 'in-flight-batch' : 'below-threshold'
|
|
456
|
+
decision: decision.reason === 'in-flight-batch' ? 'in-flight-batch' : 'below-threshold',
|
|
457
|
+
harnessWindow
|
|
361
458
|
}
|
|
362
459
|
};
|
|
363
460
|
}
|
|
@@ -392,14 +489,20 @@ export async function runAutoCompact(input) {
|
|
|
392
489
|
});
|
|
393
490
|
nextActions = isRedLine
|
|
394
491
|
? [
|
|
395
|
-
'RED-LINE compact
|
|
492
|
+
'RED-LINE: harness compact requested — sub-agent dispatch is NOT blocked; keep working',
|
|
396
493
|
'Post-compact resume picks up the convergence plan from auto-decisions.md',
|
|
397
|
-
'Next `peaks code
|
|
494
|
+
'Next `peaks code context-now` probe will confirm the ratio dropped; if it keeps climbing and the harness has not compacted, report that to the user and hand control back',
|
|
495
|
+
// Slice 2026-09-13-auto-compact-trigger-ownership: this command
|
|
496
|
+
// also rewrites the harness's own settings, so it says so too. The
|
|
497
|
+
// user accepted that write on the condition 要告知 — a write only
|
|
498
|
+
// one of the two syncing commands reports is not a notice.
|
|
499
|
+
describeHarnessWindowSync(harnessWindow)
|
|
398
500
|
]
|
|
399
501
|
: [
|
|
400
502
|
'Pre-compact dispatched — IDE compact in progress (async)',
|
|
401
503
|
'Post-compact resume picks up the convergence plan from auto-decisions.md',
|
|
402
|
-
'Next `peaks code auto-compact` probe will confirm ratio dropped below 0.85'
|
|
504
|
+
'Next `peaks code auto-compact` probe will confirm ratio dropped below 0.85',
|
|
505
|
+
describeHarnessWindowSync(harnessWindow)
|
|
403
506
|
];
|
|
404
507
|
plan = buildConvergencePlan({
|
|
405
508
|
sessionId,
|
|
@@ -426,7 +529,14 @@ export async function runAutoCompact(input) {
|
|
|
426
529
|
source: probe.source,
|
|
427
530
|
target: input.target ?? 'main',
|
|
428
531
|
mode,
|
|
429
|
-
|
|
532
|
+
redLineRequested: isRedLine,
|
|
533
|
+
// Compatibility alias + its record. `redLineGated` shipped from
|
|
534
|
+
// 2.13.0 to 4.0.46, so a consumer outside this repo reads it and must
|
|
535
|
+
// not start receiving `undefined`; the record is what makes the
|
|
536
|
+
// deprecation visible to that consumer. See the type.
|
|
537
|
+
redLineGated: isRedLine,
|
|
538
|
+
deprecatedFields: DEPRECATED_ENVELOPE_FIELDS,
|
|
539
|
+
harnessWindow
|
|
430
540
|
}
|
|
431
541
|
};
|
|
432
542
|
}
|
|
@@ -474,7 +584,10 @@ export async function runAutoCompact(input) {
|
|
|
474
584
|
convergencePlan: plan,
|
|
475
585
|
target,
|
|
476
586
|
mode,
|
|
477
|
-
|
|
587
|
+
redLineRequested: isRedLine,
|
|
588
|
+
redLineGated: isRedLine,
|
|
589
|
+
deprecatedFields: DEPRECATED_ENVELOPE_FIELDS,
|
|
590
|
+
harnessWindow
|
|
478
591
|
}
|
|
479
592
|
};
|
|
480
593
|
}
|
|
@@ -506,6 +619,7 @@ export async function runAutoCompact(input) {
|
|
|
506
619
|
sessionId,
|
|
507
620
|
event: {
|
|
508
621
|
schemaVersion: 1,
|
|
622
|
+
kind: 'dispatch',
|
|
509
623
|
ts: now.toISOString(),
|
|
510
624
|
target,
|
|
511
625
|
mode,
|
|
@@ -516,6 +630,10 @@ export async function runAutoCompact(input) {
|
|
|
516
630
|
ok: dispatch.ok,
|
|
517
631
|
checkpointPath,
|
|
518
632
|
dispatchMessage: dispatch.message,
|
|
633
|
+
// T4 calibration: the exact denominator this dispatch divided by, so
|
|
634
|
+
// `beforeRatio * windowTokens` is the token point we asked for.
|
|
635
|
+
windowTokens: probe.capacityTokens ?? null,
|
|
636
|
+
windowSource: probe.capacitySource ?? null,
|
|
519
637
|
},
|
|
520
638
|
});
|
|
521
639
|
}
|
|
@@ -527,7 +645,7 @@ export async function runAutoCompact(input) {
|
|
|
527
645
|
: 'AUTO_COMPACT_DISPATCH_FAILED',
|
|
528
646
|
message: dispatch.ok
|
|
529
647
|
? isRedLine
|
|
530
|
-
? `RED-LINE compact
|
|
648
|
+
? `RED-LINE: harness compact REQUESTED (${dispatch.ide} / ${dispatch.pathway} / target=${target} / mode=${mode} — ${describeMode(mode)}); checkpoint at ${checkpointPath}. Sub-agent dispatch is NOT blocked — keep working and re-probe with \`peaks code context-now\`.`
|
|
531
649
|
: `Auto-compact dispatched (${dispatch.ide} / ${dispatch.pathway} / target=${target} / mode=${mode} — ${describeMode(mode)}); checkpoint at ${checkpointPath}.`
|
|
532
650
|
: `Auto-compact checkpoint written but IDE dispatch failed: ${dispatch.message}`,
|
|
533
651
|
data: {
|
|
@@ -539,7 +657,10 @@ export async function runAutoCompact(input) {
|
|
|
539
657
|
dispatch,
|
|
540
658
|
target,
|
|
541
659
|
mode,
|
|
542
|
-
|
|
660
|
+
redLineRequested: isRedLine,
|
|
661
|
+
redLineGated: isRedLine,
|
|
662
|
+
deprecatedFields: DEPRECATED_ENVELOPE_FIELDS,
|
|
663
|
+
harnessWindow
|
|
543
664
|
}
|
|
544
665
|
};
|
|
545
666
|
}
|
|
@@ -550,6 +671,17 @@ export function appendCompactHistoryEvent(input) {
|
|
|
550
671
|
const path = join(dir, 'compact-history.jsonl');
|
|
551
672
|
appendFileSync(path, JSON.stringify(input.event) + '\n', 'utf8');
|
|
552
673
|
}
|
|
674
|
+
/**
|
|
675
|
+
* Best-effort variant for the post-compact measurement row. Telemetry must
|
|
676
|
+
* never change the probe's return envelope, so an append failure here is
|
|
677
|
+
* swallowed — same discipline as the dispatch-path append.
|
|
678
|
+
*/
|
|
679
|
+
function appendObservedCompactEvent(input) {
|
|
680
|
+
try {
|
|
681
|
+
appendCompactHistoryEvent(input);
|
|
682
|
+
}
|
|
683
|
+
catch { /* best-effort; the probe result is already settled */ }
|
|
684
|
+
}
|
|
553
685
|
// Keep dirname import live for symmetry with sibling services that
|
|
554
686
|
// use it for path joins; tree-shaking removes it in builds.
|
|
555
687
|
void dirname;
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/** The harness's own report of what caused a compaction. */
|
|
2
|
+
export type CompactTrigger = 'manual' | 'auto';
|
|
3
|
+
/**
|
|
4
|
+
* Which layer settled the run. `post-compact-hook` = the harness's event;
|
|
5
|
+
* `post-compact-probe` (written by the orchestrator) = a later probe's
|
|
6
|
+
* measurement. The two are deliberately distinct strings so a reader of
|
|
7
|
+
* `compact-history.jsonl` can tell a NOTIFICATION from an INFERENCE — which is
|
|
8
|
+
* the entire instrument this slice exists to build.
|
|
9
|
+
*/
|
|
10
|
+
export declare const COMPACT_EVENT_PATHWAY = "post-compact-hook";
|
|
11
|
+
/**
|
|
12
|
+
* One post-compact reading: the ratio, the window it was divided by, and the
|
|
13
|
+
* adapter that produced it — all from a SINGLE probe call, so the three cannot
|
|
14
|
+
* disagree with each other.
|
|
15
|
+
*/
|
|
16
|
+
export type PostCompactMeasurement = {
|
|
17
|
+
/** `null` = nothing could be measured. Never `0`, which means "unknown". */
|
|
18
|
+
readonly ratio: number | null;
|
|
19
|
+
readonly ide: string;
|
|
20
|
+
readonly windowTokens: number | null;
|
|
21
|
+
readonly windowSource: string | null;
|
|
22
|
+
};
|
|
23
|
+
export type CompactSettleResult = {
|
|
24
|
+
readonly settled: true;
|
|
25
|
+
readonly runId: string;
|
|
26
|
+
readonly beforeRatio: number;
|
|
27
|
+
/** The measured post-compact ratio, or `null` when none was trustworthy. */
|
|
28
|
+
readonly afterRatio: number | null;
|
|
29
|
+
/** Present only when the harness reported one. */
|
|
30
|
+
readonly trigger?: CompactTrigger;
|
|
31
|
+
/**
|
|
32
|
+
* False when the run settled but the `observed` row could not be
|
|
33
|
+
* appended. The settlement is the load-bearing half and is never undone
|
|
34
|
+
* for this, but the two outcomes are not the same fact.
|
|
35
|
+
*/
|
|
36
|
+
readonly historyWritten: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* False when the lifecycle record could not be written. The run is then
|
|
39
|
+
* still open, so a later probe will settle it from its own measurement —
|
|
40
|
+
* but the `observed` row is appended anyway, because the harness's
|
|
41
|
+
* statement is evidence independent of that write. The two failures are
|
|
42
|
+
* reported separately rather than collapsed: a caller that sees only one
|
|
43
|
+
* of them cannot say which half of the settlement landed.
|
|
44
|
+
*/
|
|
45
|
+
readonly lifecycleWritten: boolean;
|
|
46
|
+
} | {
|
|
47
|
+
readonly settled: false;
|
|
48
|
+
/**
|
|
49
|
+
* `nothing-to-settle` — there was no open run. `different-session` — the
|
|
50
|
+
* payload named a harness session that is not this project's, so the open
|
|
51
|
+
* run was left alone. The second is a refusal of ATTRIBUTION, not an
|
|
52
|
+
* absence of it, and a reader told "nothing was open" would be told
|
|
53
|
+
* something false.
|
|
54
|
+
*/
|
|
55
|
+
readonly reason: 'nothing-to-settle' | 'different-session';
|
|
56
|
+
};
|
|
57
|
+
/**
|
|
58
|
+
* Read `trigger` out of an already-parsed `PostCompact` payload.
|
|
59
|
+
*
|
|
60
|
+
* Anything that is not exactly `'manual'` or `'auto'` yields `undefined` — a
|
|
61
|
+
* missing field, a `null`, a number, another string, a payload that is not an
|
|
62
|
+
* object at all. This function never throws and never guesses (see this file's
|
|
63
|
+
* header, refusal 1).
|
|
64
|
+
*/
|
|
65
|
+
export declare function readTriggerFromHookPayload(payload: unknown): CompactTrigger | undefined;
|
|
66
|
+
/**
|
|
67
|
+
* Read the harness session id out of an already-parsed `PostCompact` payload.
|
|
68
|
+
*
|
|
69
|
+
* Same contract as `readTriggerFromHookPayload`: a missing field, an empty
|
|
70
|
+
* string, a number, a non-object payload — every one of those is `undefined`,
|
|
71
|
+
* and "absent" is an expected input rather than an error (see this file's
|
|
72
|
+
* header, refusal 4, for what the caller does with it and what it does with
|
|
73
|
+
* `undefined`).
|
|
74
|
+
*/
|
|
75
|
+
export declare function readSessionIdFromHookPayload(payload: unknown): string | undefined;
|
|
76
|
+
/**
|
|
77
|
+
* The post-compact ruler: the same probe the orchestrator uses, so this row
|
|
78
|
+
* divides by the same denominator the dispatch did.
|
|
79
|
+
*
|
|
80
|
+
* `ratio` is `null` when nothing could be measured — a `conservative-fallback`
|
|
81
|
+
* probe reports `0`, but that `0` means "unknown", and recording it here would
|
|
82
|
+
* publish a real, infinitely-deep drop. The rest of the reading (adapter, the
|
|
83
|
+
* window the ratio divides by) is still carried, because knowing WHICH adapter
|
|
84
|
+
* could not measure is diagnostic.
|
|
85
|
+
*/
|
|
86
|
+
export declare function measurePostCompact(input: {
|
|
87
|
+
readonly projectRoot: string;
|
|
88
|
+
readonly sessionId: string;
|
|
89
|
+
readonly env: NodeJS.ProcessEnv;
|
|
90
|
+
}): PostCompactMeasurement;
|
|
91
|
+
/**
|
|
92
|
+
* Settle whatever compact run is open in `sessionId`, because the harness just
|
|
93
|
+
* reported one.
|
|
94
|
+
*
|
|
95
|
+
* `measure` is the ruler, injected rather than called directly for one concrete
|
|
96
|
+
* reason: the production ruler prefers `~/.claude/statusline-state.json`, a real
|
|
97
|
+
* file whose contents differ on every machine that runs the test suite. A test
|
|
98
|
+
* asserting this row's `afterRatio` against the developer's own statusline would
|
|
99
|
+
* pass or fail by accident. It is called at most once — the IDE tag and the
|
|
100
|
+
* window come from the same reading, so they cannot disagree with each other.
|
|
101
|
+
*
|
|
102
|
+
* Returns `{ settled: false }` when there is no open run — the honest answer,
|
|
103
|
+
* not an error — and when the payload names another harness session. The two
|
|
104
|
+
* say different things in `reason`, because they are different facts.
|
|
105
|
+
*/
|
|
106
|
+
export declare function settleCompactFromHarnessEvent(input: {
|
|
107
|
+
readonly projectRoot: string;
|
|
108
|
+
readonly sessionId: string;
|
|
109
|
+
readonly trigger?: CompactTrigger | undefined;
|
|
110
|
+
/**
|
|
111
|
+
* The harness session id the payload named, when it named one (see
|
|
112
|
+
* `readSessionIdFromHookPayload`). Refused when it is present AND this
|
|
113
|
+
* project's own harness session id resolves AND the two differ. Absent, or
|
|
114
|
+
* unresolvable on this side, is accepted: a guard that cannot see what it is
|
|
115
|
+
* checking against must not become a hook that never settles anything.
|
|
116
|
+
*/
|
|
117
|
+
readonly hookSessionId?: string | undefined;
|
|
118
|
+
readonly env?: NodeJS.ProcessEnv | undefined;
|
|
119
|
+
readonly measure?: (() => PostCompactMeasurement | null) | undefined;
|
|
120
|
+
/** Failure injection for the lifecycle write — the seam `CompactLifecyclePublisher` already takes. */
|
|
121
|
+
readonly failLifecycleWrite?: boolean | undefined;
|
|
122
|
+
}): CompactSettleResult;
|