peaks-loop 4.0.46 → 4.0.47
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 +30 -0
- package/README-en.md +1 -1
- package/README.md +1 -1
- package/dist/cli/commands/code-runtime-commands.d.ts +22 -0
- package/dist/cli/commands/code-runtime-commands.js +99 -16
- package/dist/cli/commands/compact-command.js +129 -1
- 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/e2e-verify.js +3 -3
- package/dist/cli/commands/governance-classify-contract-commands.js +1 -0
- package/dist/cli/commands/hooks-commands.js +10 -1
- package/dist/cli/commands/loop-commands.js +1 -0
- package/dist/cli/commands/playwright-commands.js +2 -1
- 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 +6 -3
- package/dist/cli/commands/shadcn-commands.js +1 -0
- package/dist/cli/commands/test-commands.js +2 -1
- package/dist/cli/commands/vm-commands.js +7 -7
- 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 +49 -2
- 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-service.js +1 -1
- 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 +11 -1
- package/dist/services/code/auto-compact-lifecycle.js +11 -4
- package/dist/services/code/auto-compact-orchestrator.d.ts +53 -9
- package/dist/services/code/auto-compact-orchestrator.js +153 -32
- 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 +61 -0
- package/dist/services/compact-history/compact-history-service.js +49 -0
- 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-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/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 +12 -0
- package/dist/services/ide/ide-registry.js +14 -0
- package/dist/services/ide/ide-types.d.ts +59 -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/prd/best-practice-auto-trigger.js +1 -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/orphan-service.js +2 -1
- package/dist/services/scan/type-sanity-service.js +2 -2
- package/dist/services/skillhub/tar-runtime.js +1 -0
- package/dist/services/skills/hooks-codegate-superpowers.d.ts +8 -0
- package/dist/services/skills/hooks-codegate-superpowers.js +40 -3
- package/dist/services/skills/hooks-settings-service.d.ts +12 -0
- package/dist/services/skills/hooks-settings-service.js +77 -10
- package/dist/services/skills/session-start-hook-constants.d.ts +41 -0
- package/dist/services/skills/session-start-hook-constants.js +41 -0
- package/dist/services/skills/skill-presence-service.js +9 -0
- package/dist/services/slice/slice-check-service.js +2 -1
- package/dist/services/slice/slice-decompose-runners.js +2 -1
- package/dist/services/upgrade/upgrade-service.js +1 -0
- package/dist/services/workflow/workflow-skip-service.js +2 -1
- package/dist/services/workspace/migrate-service.js +1 -1
- package/dist/services/workspace/workspace-claude-settings-materializer.js +51 -7
- 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 +5 -5
- 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 +1 -1
- package/skills/bee/peaks-qa/SKILL.md +2 -2
- package/skills/bee/peaks-rd/SKILL.md +2 -2
- 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 +2 -2
- package/skills/peaks-code/references/sub-agent-dispatch.md +1 -1
- 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
|
@@ -214,28 +214,34 @@ export function summarizeLifecycleError(error) {
|
|
|
214
214
|
* `afterRatio: 0` there would publish a fabricated number;
|
|
215
215
|
* - the ratio is still at or above the auto-fire threshold — the
|
|
216
216
|
* compact has not landed, so the run stays open.
|
|
217
|
+
*
|
|
218
|
+
* Returns the settled record, or `null` when nothing settled. Slice
|
|
219
|
+
* 2026-09-13-auto-compact-trigger-ownership: the caller needs the settled
|
|
220
|
+
* `afterRatio` to append the "observed compaction point" row that makes the
|
|
221
|
+
* intent-vs-observed gap readable after a real session. The return value is
|
|
222
|
+
* telemetry only — callers that ignore it are unaffected.
|
|
217
223
|
*/
|
|
218
224
|
export function settleOpenLifecycleRun(input) {
|
|
219
225
|
// A `conservative-fallback` probe means no signal was available at
|
|
220
226
|
// all. Its `ratio: 0` is the absence of a measurement, so it can
|
|
221
227
|
// never be evidence that the context shrank.
|
|
222
228
|
if (input.source === 'conservative-fallback')
|
|
223
|
-
return;
|
|
229
|
+
return null;
|
|
224
230
|
if (input.measuredRatio >= input.autoFireThreshold)
|
|
225
|
-
return;
|
|
231
|
+
return null;
|
|
226
232
|
const prior = readOpenCompactLifecycle({
|
|
227
233
|
projectRoot: input.projectRoot,
|
|
228
234
|
sessionId: input.sessionId
|
|
229
235
|
});
|
|
230
236
|
if (prior === null)
|
|
231
|
-
return;
|
|
237
|
+
return null;
|
|
232
238
|
// Only a run that was actually dispatched can be completed by a
|
|
233
239
|
// post-compact measurement. Both `compacting` (the in-band trigger is
|
|
234
240
|
// satisfied) and `armed` (a trigger was registered and could fire at
|
|
235
241
|
// any time) qualify: the measured drop is proof that SOME compact
|
|
236
242
|
// landed, and this is the only open run to attribute it to.
|
|
237
243
|
if (prior.stage !== 'compacting' && prior.stage !== 'armed')
|
|
238
|
-
return;
|
|
244
|
+
return null;
|
|
239
245
|
const emit = (stage, withAfterRatio) => {
|
|
240
246
|
const record = {
|
|
241
247
|
schemaVersion: 1,
|
|
@@ -267,4 +273,5 @@ export function settleOpenLifecycleRun(input) {
|
|
|
267
273
|
emit('verifying', false);
|
|
268
274
|
// `completed` = the measurement confirms the drop; publish it.
|
|
269
275
|
emit('completed', true);
|
|
276
|
+
return { runId: prior.runId, triggerRatio: prior.triggerRatio, afterRatio: input.measuredRatio };
|
|
270
277
|
}
|
|
@@ -31,7 +31,21 @@ export interface AutoCompactInput {
|
|
|
31
31
|
* autonomously at 0.85+ with zero human / zero LLM intervention.
|
|
32
32
|
*/
|
|
33
33
|
readonly force?: boolean | undefined;
|
|
34
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* INERT — nothing reads this. It is threaded from the CLI's
|
|
36
|
+
* `--bypass-red-line` (a published flag, kept for backward compatibility;
|
|
37
|
+
* see the option's own note) down to `evaluateAutoCompactDecision`, where the
|
|
38
|
+
* red-line branch ignores it.
|
|
39
|
+
*
|
|
40
|
+
* Why nothing reads it any more: it used to skip a gate that refused to
|
|
41
|
+
* dispatch below 0.85. That gate was removed (slice
|
|
42
|
+
* 2026-09-13-auto-compact-trigger-ownership, T3/A1) — peaks-loop cannot
|
|
43
|
+
* compact a running session, so the refusal could not shorten the wait it
|
|
44
|
+
* was waiting for, and it deadlocked the runner at the worst moment. With the
|
|
45
|
+
* gate gone, the red line still ASKS the harness to compact (when it always
|
|
46
|
+
* did) and no longer needs a bypass. Kept as a field so the published
|
|
47
|
+
* signature does not change under callers that pass it.
|
|
48
|
+
*/
|
|
35
49
|
readonly bypassRedLine?: boolean | undefined;
|
|
36
50
|
/** Current session id (default = resolve via session-id-service). */
|
|
37
51
|
readonly sessionId?: string | undefined;
|
|
@@ -93,7 +107,7 @@ export interface AutoCompactTestHooks {
|
|
|
93
107
|
*
|
|
94
108
|
* - ratio < preCompact → 'none' or 'soft-warn'
|
|
95
109
|
* - ratio ≥ preCompact → 'pre-compact' (async-friendly path)
|
|
96
|
-
* - ratio ≥ redLine → 'red-line' (
|
|
110
|
+
* - ratio ≥ redLine → 'red-line' (ask the harness; dispatch continues)
|
|
97
111
|
*
|
|
98
112
|
* Slice 2026-07-28 (rid-027): `mode` selects the threshold table.
|
|
99
113
|
* Default `'standard'` (0.85/0.95). `'partial'` (0.70/0.85) is used
|
|
@@ -109,8 +123,13 @@ export declare function evaluateCompactTrigger(ratio: number, mode?: AutoCompact
|
|
|
109
123
|
* - 0.85 ≤ ratio < 0.95 → pre-compact; if in-flight batch
|
|
110
124
|
* present, defer (D6.e); else dispatch
|
|
111
125
|
* IDE compact asynchronously.
|
|
112
|
-
* - ratio ≥ 0.95 → red-line;
|
|
113
|
-
* regardless of in-flight batch.
|
|
126
|
+
* - ratio ≥ 0.95 → red-line; ask the harness to compact
|
|
127
|
+
* regardless of in-flight batch. Nothing is
|
|
128
|
+
* gated — dispatch is NOT blocked (slice
|
|
129
|
+
* 2026-09-13-auto-compact-trigger-ownership:
|
|
130
|
+
* peaks-loop cannot compact a running session,
|
|
131
|
+
* so a "block" gated nothing and deadlocked the
|
|
132
|
+
* runner).
|
|
114
133
|
*/
|
|
115
134
|
export declare function evaluateAutoCompactDecision(input: {
|
|
116
135
|
ratio: number;
|
|
@@ -129,6 +148,7 @@ export declare function evaluateAutoCompactDecision(input: {
|
|
|
129
148
|
*/
|
|
130
149
|
inflightBatch?: boolean | InFlightBatchProbe | undefined;
|
|
131
150
|
force?: boolean | undefined;
|
|
151
|
+
/** Accepted and ignored — see `AutoCompactInput.bypassRedLine`. */
|
|
132
152
|
bypassRedLine?: boolean | undefined;
|
|
133
153
|
mode?: AutoCompactMode | undefined;
|
|
134
154
|
/**
|
|
@@ -170,13 +190,14 @@ export declare function buildConvergencePlan(input: {
|
|
|
170
190
|
* 5. If trigger.kind === 'pre-compact' AND in-flight batch → wait.
|
|
171
191
|
* 6. If trigger.kind === 'pre-compact' → async dispatch (write
|
|
172
192
|
* checkpoint + IDE compact; orchestrator returns immediately).
|
|
173
|
-
* 7. If trigger.kind === 'red-line' →
|
|
174
|
-
*
|
|
193
|
+
* 7. If trigger.kind === 'red-line' → dispatch the IDE compact, report
|
|
194
|
+
* that the harness has been asked and that dispatch continues.
|
|
175
195
|
*
|
|
176
196
|
* The caller (CLI or skill body) handles the actual return — D7's
|
|
177
|
-
* post-compact-detect will pick up the checkpoint on the next turn.
|
|
178
|
-
*
|
|
179
|
-
*
|
|
197
|
+
* post-compact-detect will pick up the checkpoint on the next turn. For
|
|
198
|
+
* red-line, the caller keeps working; it re-probes and, if the ratio is
|
|
199
|
+
* still rising with no compact from the harness, reports that instead of
|
|
200
|
+
* stalling.
|
|
180
201
|
*/
|
|
181
202
|
export declare function runAutoCompact(input: AutoCompactInput): Promise<AutoCompactResult>;
|
|
182
203
|
/** Re-export for callers that need to surface the trigger shape. */
|
|
@@ -200,6 +221,29 @@ export interface CompactHistoryEvent {
|
|
|
200
221
|
readonly ok: boolean;
|
|
201
222
|
readonly checkpointPath: string;
|
|
202
223
|
readonly dispatchMessage: string;
|
|
224
|
+
/**
|
|
225
|
+
* Slice 2026-09-13-auto-compact-trigger-ownership (T4): the calibration
|
|
226
|
+
* instrument. `windowTokens` is the denominator peaks-loop divided by
|
|
227
|
+
* (`probe.capacityTokens`), so `beforeRatio * windowTokens` is the exact
|
|
228
|
+
* TOKEN POINT peaks-loop asked the harness to compact at. That number is
|
|
229
|
+
* the deliverable — NOT a hand-picked threshold: this slice could not run a
|
|
230
|
+
* real Claude Code session, so the real trigger point of
|
|
231
|
+
* `CLAUDE_CODE_AUTO_COMPACT_WINDOW` (documented as a window, observed to
|
|
232
|
+
* fire near the window's end) has no measured answer yet. Recording the
|
|
233
|
+
* intent lets the first real session produce one.
|
|
234
|
+
*/
|
|
235
|
+
readonly windowTokens?: number | null;
|
|
236
|
+
/** Which layer produced `windowTokens` (see `ContextWindowSource`). */
|
|
237
|
+
readonly windowSource?: string | null;
|
|
238
|
+
/**
|
|
239
|
+
* `dispatch` (default, and the only kind earlier releases wrote) or
|
|
240
|
+
* `observed` — a row appended when a later probe MEASURED the ratio after
|
|
241
|
+
* a dispatched compact, proving one landed. The pair is what yields the
|
|
242
|
+
* intent-vs-observed delta.
|
|
243
|
+
*/
|
|
244
|
+
readonly kind?: 'dispatch' | 'observed';
|
|
245
|
+
/** `observed` rows only: the measured post-compact ratio. */
|
|
246
|
+
readonly afterRatio?: number;
|
|
203
247
|
}
|
|
204
248
|
export declare function appendCompactHistoryEvent(input: {
|
|
205
249
|
readonly projectRoot: string;
|
|
@@ -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,15 +29,17 @@
|
|
|
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
45
|
import { CompactLifecyclePublisher, newCompactRunId, resolveDispatchedStage, settleOpenLifecycleRun, summarizeLifecycleError } from './auto-compact-lifecycle.js';
|
|
@@ -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,7 +382,7 @@ 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,
|
|
@@ -345,19 +390,60 @@ export async function runAutoCompact(input) {
|
|
|
345
390
|
autoFireThreshold: thresholdFor(mode, 'autoFire'),
|
|
346
391
|
onLifecycleStage: input.onLifecycleStage
|
|
347
392
|
});
|
|
393
|
+
// Slice 2026-09-13-auto-compact-trigger-ownership (T4): a settle means a
|
|
394
|
+
// dispatched compact demonstrably landed. Append an `observed` row
|
|
395
|
+
// carrying the measured ratio, so `peaks compact history` can show
|
|
396
|
+
// "asked at R (intent) / landed by R' (observed)" once a real session has
|
|
397
|
+
// run. Without this row the intent has nothing to be compared against.
|
|
398
|
+
if (settled !== null) {
|
|
399
|
+
appendObservedCompactEvent({
|
|
400
|
+
projectRoot: input.projectRoot,
|
|
401
|
+
sessionId,
|
|
402
|
+
event: {
|
|
403
|
+
schemaVersion: 1,
|
|
404
|
+
kind: 'observed',
|
|
405
|
+
ts: new Date().toISOString(),
|
|
406
|
+
target: input.target ?? 'main',
|
|
407
|
+
mode,
|
|
408
|
+
ide: probe.ide,
|
|
409
|
+
pathway: 'post-compact-probe',
|
|
410
|
+
beforeRatio: settled.triggerRatio,
|
|
411
|
+
afterRatio: probe.ratio,
|
|
412
|
+
redLine: false,
|
|
413
|
+
ok: true,
|
|
414
|
+
checkpointPath: '',
|
|
415
|
+
dispatchMessage: `post-compact probe measured ratio ${(probe.ratio * 100).toFixed(1)}% (source=${probe.source}) after the compact dispatched at ${(settled.triggerRatio * 100).toFixed(1)}%`,
|
|
416
|
+
windowTokens: probe.capacityTokens ?? null,
|
|
417
|
+
windowSource: probe.capacitySource ?? null
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
}
|
|
348
421
|
return {
|
|
349
422
|
ok: true,
|
|
350
423
|
code: decision.reason === 'in-flight-batch' ? 'AUTO_COMPACT_WAIT' : 'AUTO_COMPACT_SKIP',
|
|
351
|
-
message: decision.trigger.kind === 'soft-warn'
|
|
424
|
+
message: `${decision.trigger.kind === 'soft-warn'
|
|
352
425
|
? decision.trigger.message
|
|
353
426
|
: decision.reason === 'in-flight-batch'
|
|
354
427
|
? `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})
|
|
428
|
+
: `Context at ${(probe.ratio * 100).toFixed(1)}%; below the ${(thresholdFor(mode, 'autoFire') * 100).toFixed(0)}% auto-fire threshold (mode=${mode}).`}${
|
|
429
|
+
// No compact was needed, but the sync may still have rewritten the
|
|
430
|
+
// harness's settings (the first probe of a project always does). The
|
|
431
|
+
// notice is appended ONLY for an actual write — the other actions'
|
|
432
|
+
// sentences would be noise on every quiet probe — PLUS the one refusal
|
|
433
|
+
// that is not quiet: a refused write that leaves peaks-loop's number and
|
|
434
|
+
// the harness's pinned window disagreeing. That is the moment the ratio
|
|
435
|
+
// stops describing the harness's trigger, so staying silent is exactly
|
|
436
|
+
// the failure 要告知 exists to prevent.
|
|
437
|
+
harnessWindow !== null && harnessWindow !== undefined &&
|
|
438
|
+
(harnessWindow.action === 'written' || harnessWindowSyncWarning(harnessWindow) !== null)
|
|
439
|
+
? ` ${describeHarnessWindowSync(harnessWindow)}`
|
|
440
|
+
: ''}`,
|
|
356
441
|
data: {
|
|
357
442
|
sessionId,
|
|
358
443
|
ratio: probe.ratio,
|
|
359
444
|
source: probe.source,
|
|
360
|
-
decision: decision.reason === 'in-flight-batch' ? 'in-flight-batch' : 'below-threshold'
|
|
445
|
+
decision: decision.reason === 'in-flight-batch' ? 'in-flight-batch' : 'below-threshold',
|
|
446
|
+
harnessWindow
|
|
361
447
|
}
|
|
362
448
|
};
|
|
363
449
|
}
|
|
@@ -392,14 +478,20 @@ export async function runAutoCompact(input) {
|
|
|
392
478
|
});
|
|
393
479
|
nextActions = isRedLine
|
|
394
480
|
? [
|
|
395
|
-
'RED-LINE compact
|
|
481
|
+
'RED-LINE: harness compact requested — sub-agent dispatch is NOT blocked; keep working',
|
|
396
482
|
'Post-compact resume picks up the convergence plan from auto-decisions.md',
|
|
397
|
-
'Next `peaks code
|
|
483
|
+
'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',
|
|
484
|
+
// Slice 2026-09-13-auto-compact-trigger-ownership: this command
|
|
485
|
+
// also rewrites the harness's own settings, so it says so too. The
|
|
486
|
+
// user accepted that write on the condition 要告知 — a write only
|
|
487
|
+
// one of the two syncing commands reports is not a notice.
|
|
488
|
+
describeHarnessWindowSync(harnessWindow)
|
|
398
489
|
]
|
|
399
490
|
: [
|
|
400
491
|
'Pre-compact dispatched — IDE compact in progress (async)',
|
|
401
492
|
'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'
|
|
493
|
+
'Next `peaks code auto-compact` probe will confirm ratio dropped below 0.85',
|
|
494
|
+
describeHarnessWindowSync(harnessWindow)
|
|
403
495
|
];
|
|
404
496
|
plan = buildConvergencePlan({
|
|
405
497
|
sessionId,
|
|
@@ -426,7 +518,14 @@ export async function runAutoCompact(input) {
|
|
|
426
518
|
source: probe.source,
|
|
427
519
|
target: input.target ?? 'main',
|
|
428
520
|
mode,
|
|
429
|
-
|
|
521
|
+
redLineRequested: isRedLine,
|
|
522
|
+
// Compatibility alias + its record. `redLineGated` shipped from
|
|
523
|
+
// 2.13.0 to 4.0.46, so a consumer outside this repo reads it and must
|
|
524
|
+
// not start receiving `undefined`; the record is what makes the
|
|
525
|
+
// deprecation visible to that consumer. See the type.
|
|
526
|
+
redLineGated: isRedLine,
|
|
527
|
+
deprecatedFields: DEPRECATED_ENVELOPE_FIELDS,
|
|
528
|
+
harnessWindow
|
|
430
529
|
}
|
|
431
530
|
};
|
|
432
531
|
}
|
|
@@ -474,7 +573,10 @@ export async function runAutoCompact(input) {
|
|
|
474
573
|
convergencePlan: plan,
|
|
475
574
|
target,
|
|
476
575
|
mode,
|
|
477
|
-
|
|
576
|
+
redLineRequested: isRedLine,
|
|
577
|
+
redLineGated: isRedLine,
|
|
578
|
+
deprecatedFields: DEPRECATED_ENVELOPE_FIELDS,
|
|
579
|
+
harnessWindow
|
|
478
580
|
}
|
|
479
581
|
};
|
|
480
582
|
}
|
|
@@ -506,6 +608,7 @@ export async function runAutoCompact(input) {
|
|
|
506
608
|
sessionId,
|
|
507
609
|
event: {
|
|
508
610
|
schemaVersion: 1,
|
|
611
|
+
kind: 'dispatch',
|
|
509
612
|
ts: now.toISOString(),
|
|
510
613
|
target,
|
|
511
614
|
mode,
|
|
@@ -516,6 +619,10 @@ export async function runAutoCompact(input) {
|
|
|
516
619
|
ok: dispatch.ok,
|
|
517
620
|
checkpointPath,
|
|
518
621
|
dispatchMessage: dispatch.message,
|
|
622
|
+
// T4 calibration: the exact denominator this dispatch divided by, so
|
|
623
|
+
// `beforeRatio * windowTokens` is the token point we asked for.
|
|
624
|
+
windowTokens: probe.capacityTokens ?? null,
|
|
625
|
+
windowSource: probe.capacitySource ?? null,
|
|
519
626
|
},
|
|
520
627
|
});
|
|
521
628
|
}
|
|
@@ -527,7 +634,7 @@ export async function runAutoCompact(input) {
|
|
|
527
634
|
: 'AUTO_COMPACT_DISPATCH_FAILED',
|
|
528
635
|
message: dispatch.ok
|
|
529
636
|
? isRedLine
|
|
530
|
-
? `RED-LINE compact
|
|
637
|
+
? `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
638
|
: `Auto-compact dispatched (${dispatch.ide} / ${dispatch.pathway} / target=${target} / mode=${mode} — ${describeMode(mode)}); checkpoint at ${checkpointPath}.`
|
|
532
639
|
: `Auto-compact checkpoint written but IDE dispatch failed: ${dispatch.message}`,
|
|
533
640
|
data: {
|
|
@@ -539,7 +646,10 @@ export async function runAutoCompact(input) {
|
|
|
539
646
|
dispatch,
|
|
540
647
|
target,
|
|
541
648
|
mode,
|
|
542
|
-
|
|
649
|
+
redLineRequested: isRedLine,
|
|
650
|
+
redLineGated: isRedLine,
|
|
651
|
+
deprecatedFields: DEPRECATED_ENVELOPE_FIELDS,
|
|
652
|
+
harnessWindow
|
|
543
653
|
}
|
|
544
654
|
};
|
|
545
655
|
}
|
|
@@ -550,6 +660,17 @@ export function appendCompactHistoryEvent(input) {
|
|
|
550
660
|
const path = join(dir, 'compact-history.jsonl');
|
|
551
661
|
appendFileSync(path, JSON.stringify(input.event) + '\n', 'utf8');
|
|
552
662
|
}
|
|
663
|
+
/**
|
|
664
|
+
* Best-effort variant for the post-compact measurement row. Telemetry must
|
|
665
|
+
* never change the probe's return envelope, so an append failure here is
|
|
666
|
+
* swallowed — same discipline as the dispatch-path append.
|
|
667
|
+
*/
|
|
668
|
+
function appendObservedCompactEvent(input) {
|
|
669
|
+
try {
|
|
670
|
+
appendCompactHistoryEvent(input);
|
|
671
|
+
}
|
|
672
|
+
catch { /* best-effort; the probe result is already settled */ }
|
|
673
|
+
}
|
|
553
674
|
// Keep dirname import live for symmetry with sibling services that
|
|
554
675
|
// use it for path joins; tree-shaking removes it in builds.
|
|
555
676
|
void dirname;
|
|
@@ -18,8 +18,10 @@
|
|
|
18
18
|
* decide, ?, 选择, 决定). If yes → soft warning, NOT a blocker
|
|
19
19
|
* (the LLM should AskUserQuestion, which is cheap).
|
|
20
20
|
* Q4 — Is context usage sustainable? (probe `peaks code context-now
|
|
21
|
-
* --json`). ratio ≥ 0.95 →
|
|
22
|
-
*
|
|
21
|
+
* --json`). ratio ≥ 0.95 → WARNING (red-line, ask-and-wait);
|
|
22
|
+
* ≥ 0.85 → WARNING (pre-compact band). Never a blocker — see the
|
|
23
|
+
* comment on the Q4 branch in `buildOrchestratorCanDoResult`, and
|
|
24
|
+
* `evaluateCompactTrigger`, which is the face this one must agree with.
|
|
23
25
|
*
|
|
24
26
|
* Decision rule:
|
|
25
27
|
* canDoInSession === (blockers.length === 0)
|
|
@@ -18,8 +18,10 @@
|
|
|
18
18
|
* decide, ?, 选择, 决定). If yes → soft warning, NOT a blocker
|
|
19
19
|
* (the LLM should AskUserQuestion, which is cheap).
|
|
20
20
|
* Q4 — Is context usage sustainable? (probe `peaks code context-now
|
|
21
|
-
* --json`). ratio ≥ 0.95 →
|
|
22
|
-
*
|
|
21
|
+
* --json`). ratio ≥ 0.95 → WARNING (red-line, ask-and-wait);
|
|
22
|
+
* ≥ 0.85 → WARNING (pre-compact band). Never a blocker — see the
|
|
23
|
+
* comment on the Q4 branch in `buildOrchestratorCanDoResult`, and
|
|
24
|
+
* `evaluateCompactTrigger`, which is the face this one must agree with.
|
|
23
25
|
*
|
|
24
26
|
* Decision rule:
|
|
25
27
|
* canDoInSession === (blockers.length === 0)
|
|
@@ -153,6 +155,7 @@ export async function probeSubAgentAvailable(projectRoot, peaksBin = DEFAULT_PEA
|
|
|
153
155
|
await execFileAsync(command, [...args, 'sub-agent', 'dispatch', '--role', 'rd', '--help'], {
|
|
154
156
|
cwd: projectRoot,
|
|
155
157
|
timeout: 5000,
|
|
158
|
+
windowsHide: true,
|
|
156
159
|
});
|
|
157
160
|
return true;
|
|
158
161
|
}
|
|
@@ -171,6 +174,7 @@ export async function probeContextRatio(projectRoot, peaksBin = DEFAULT_PEAKS_BI
|
|
|
171
174
|
const { stdout } = await execFileAsync(command, [...args, 'code', 'context-now', '--project', projectRoot, '--json'], {
|
|
172
175
|
cwd: projectRoot,
|
|
173
176
|
timeout: 10000,
|
|
177
|
+
windowsHide: true,
|
|
174
178
|
});
|
|
175
179
|
const parsed = JSON.parse(stdout);
|
|
176
180
|
const ratio = typeof parsed.data?.ratio === 'number' ? parsed.data.ratio : 0;
|
|
@@ -205,13 +209,41 @@ export function buildOrchestratorCanDoResult(input, signals) {
|
|
|
205
209
|
blockers.push('sub-agent dispatch unavailable (peaks sub-agent dispatch --help failed)');
|
|
206
210
|
suggestions.push('verify peaks CLI is on PATH; check `peaks --version`');
|
|
207
211
|
}
|
|
208
|
-
// Q4 — context ratio
|
|
212
|
+
// Q4 — context ratio. ≥0.95 → red-line; ≥0.85 → pre-compact. A WARNING, not
|
|
213
|
+
// a blocker, and the difference is deliberate (E3, rid 2026-09-13-defects-e).
|
|
214
|
+
//
|
|
215
|
+
// Why this is not a blocker any more, and why it is not an oversight:
|
|
216
|
+
//
|
|
217
|
+
// The peak this answers is "can this slice run in the current session".
|
|
218
|
+
// Context ratio cannot answer "no" to it. The reason is the one the
|
|
219
|
+
// T3 slice (2026-09-13-auto-compact-trigger-ownership) landed on the
|
|
220
|
+
// OTHER face of this same threshold: peaks-loop has no executor for a
|
|
221
|
+
// running session, so it cannot compact its way out of a high ratio —
|
|
222
|
+
// `evaluateCompactTrigger` therefore says of the red line "peaks-loop has
|
|
223
|
+
// asked the harness to compact and is WAITING for it — sub-agent dispatch
|
|
224
|
+
// is NOT blocked; carry on and re-probe". A probe that returned
|
|
225
|
+
// `canDoInSession: false` here would contradict that sentence for the same
|
|
226
|
+
// 0.95 on the same number, and its only programmatic consumer
|
|
227
|
+
// (`code-orchestrator-can-do.ts`) turns the false into exit code 1 — i.e.
|
|
228
|
+
// it would re-open, at the CLI layer, exactly the deadlock T3 deleted.
|
|
229
|
+
//
|
|
230
|
+
// Nor does the pre-compact zone (0.85–0.95) earn a blocker: the trigger's
|
|
231
|
+
// message there is "peaks-loop already fired the auto-compact pathway; the
|
|
232
|
+
// LLM does not need to act", which is the opposite of "you may not proceed".
|
|
233
|
+
//
|
|
234
|
+
// What a blocker would still need to be true: THE ORCHESTRATOR itself
|
|
235
|
+
// cannot continue. It can — the slice's edits are delegated to a sub-agent
|
|
236
|
+
// (Q1), and the orchestrator's own context is only spent co-ordinating.
|
|
237
|
+
//
|
|
238
|
+
// So the ratio is reported, warned about, and given a next action; the
|
|
239
|
+
// verdict is left to the blockers that really are un-survivable (a
|
|
240
|
+
// hard-blocked path family, an unreachable sub-agent dispatcher).
|
|
209
241
|
if (signals.q4ContextRatio >= ORCHESTRATOR_REDLINE_RATIO) {
|
|
210
|
-
|
|
242
|
+
warnings.push(`context red-line (ratio=${signals.q4ContextRatio.toFixed(2)} ≥ ${ORCHESTRATOR_REDLINE_RATIO}): peaks-loop has asked the harness to compact and is waiting for it; dispatch is NOT blocked — carry on and re-probe with \`peaks code context-now\``);
|
|
211
243
|
suggestions.push('peaks code auto-compact');
|
|
212
244
|
}
|
|
213
245
|
else if (signals.q4ContextRatio >= ORCHESTRATOR_PRECOMPACT_RATIO) {
|
|
214
|
-
|
|
246
|
+
warnings.push(`context near limit (ratio=${signals.q4ContextRatio.toFixed(2)} ≥ ${ORCHESTRATOR_PRECOMPACT_RATIO}): in the pre-compact band; peaks-loop fires the auto-compact pathway itself and dispatch is NOT blocked`);
|
|
215
247
|
suggestions.push('peaks code auto-compact');
|
|
216
248
|
}
|
|
217
249
|
// Q3 — user-decision keywords → soft warning, NOT a blocker. The
|
|
@@ -176,7 +176,8 @@ export const CODEGRAPH_CONFIG_FILENAME = 'config.json';
|
|
|
176
176
|
export function readTrackedFiles(projectRoot) {
|
|
177
177
|
const stdout = execFileSync('git', ['-C', projectRoot, 'ls-files'], {
|
|
178
178
|
encoding: 'utf8',
|
|
179
|
-
maxBuffer: 64 * 1024 * 1024
|
|
179
|
+
maxBuffer: 64 * 1024 * 1024,
|
|
180
|
+
windowsHide: true
|
|
180
181
|
});
|
|
181
182
|
return stdout
|
|
182
183
|
.split('\n')
|
|
@@ -27,7 +27,7 @@ function terminateCodegraphProcess(childProcess) {
|
|
|
27
27
|
}
|
|
28
28
|
if (process.platform === 'win32') {
|
|
29
29
|
if (process.env.SystemRoot) {
|
|
30
|
-
spawn(join(process.env.SystemRoot, 'System32', 'taskkill.exe'), ['/pid', String(childProcess.pid), '/T', '/F'], { shell: false, stdio: 'ignore' });
|
|
30
|
+
spawn(join(process.env.SystemRoot, 'System32', 'taskkill.exe'), ['/pid', String(childProcess.pid), '/T', '/F'], { shell: false, stdio: 'ignore', windowsHide: true });
|
|
31
31
|
}
|
|
32
32
|
else {
|
|
33
33
|
childProcess.kill();
|
|
@@ -47,7 +47,8 @@ export function defaultCodegraphProcessRunner(invocation) {
|
|
|
47
47
|
cwd: invocation.cwd,
|
|
48
48
|
detached: process.platform !== 'win32',
|
|
49
49
|
env: createCodegraphEnvironment(),
|
|
50
|
-
shell: false
|
|
50
|
+
shell: false,
|
|
51
|
+
windowsHide: true
|
|
51
52
|
});
|
|
52
53
|
const timeout = setTimeout(() => {
|
|
53
54
|
terminateCodegraphProcess(childProcess);
|
|
@@ -15,8 +15,11 @@
|
|
|
15
15
|
*
|
|
16
16
|
* The hook is INVOKED from `src/cli/commands/request-commands.ts`
|
|
17
17
|
* (RD→QA boundary) and from any future QA→final-review boundary. The
|
|
18
|
-
* 0.95 red line is
|
|
19
|
-
* orchestrator
|
|
18
|
+
* 0.95 red line is unchanged in THRESHOLD but not in behaviour: the
|
|
19
|
+
* auto-compact orchestrator asks the harness to compact and reports that
|
|
20
|
+
* it is waiting. It does not refuse sub-agent dispatch — peaks-loop has no
|
|
21
|
+
* executor for a running session, so a refusal gated nothing and deadlocked
|
|
22
|
+
* the runner (slice 2026-09-13-auto-compact-trigger-ownership).
|
|
20
23
|
*/
|
|
21
24
|
import { existsSync, readFileSync } from 'node:fs';
|
|
22
25
|
import { join } from 'node:path';
|