peaks-loop 4.0.45 → 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 +48 -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 +128 -34
- 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 +50 -4
- package/dist/services/code/auto-compact-lifecycle.js +52 -10
- package/dist/services/code/auto-compact-orchestrator.d.ts +53 -9
- package/dist/services/code/auto-compact-orchestrator.js +173 -43
- 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/compact-statusline/compact-lifecycle-store.d.ts +11 -2
- package/dist/services/compact-statusline/compact-lifecycle-store.js +19 -1
- package/dist/services/compact-statusline/compact-statusline-service.d.ts +1 -1
- package/dist/services/compact-statusline/compact-statusline-service.js +22 -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/skills/skill-statusline-renderer.js +30 -5
- package/dist/services/skills/statusline-palette.d.ts +6 -0
- package/dist/services/skills/statusline-palette.js +4 -1
- 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 +4 -4
- 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
|
@@ -0,0 +1,412 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Harness auto-compact window ownership
|
|
3
|
+
* (slice 2026-09-13-auto-compact-trigger-ownership, rid
|
|
4
|
+
* 2026-09-13-auto-compact-trigger-ownership).
|
|
5
|
+
*
|
|
6
|
+
* The goal (user's words): *when* to trigger auto-compact must be
|
|
7
|
+
* peaks-loop's decision; *how* to compact stays the harness's capability.
|
|
8
|
+
*
|
|
9
|
+
* That only holds if the two sides agree on ONE number. Before this slice
|
|
10
|
+
* they each resolved a window independently: Claude Code used the window it
|
|
11
|
+
* knows for the model, while peaks-loop divided by `resolveContextWindow()`
|
|
12
|
+
* (env → config → model-name heuristic → 200_000 default). A 1M-window model
|
|
13
|
+
* whose id the heuristic does not recognise made peaks-loop's "95%" land at
|
|
14
|
+
* 190K while the harness waited for ~967K — a 5× early trigger, and (before
|
|
15
|
+
* T3) a deadlock, because nothing could lower the ratio.
|
|
16
|
+
*
|
|
17
|
+
* This module owns the OTHER half of the fix: the harness itself accepts a
|
|
18
|
+
* window override (`IdeCompactProfile.autoCompactWindowEnvVar`, declared per
|
|
19
|
+
* adapter — no IDE names here), so peaks-loop writes the very number it
|
|
20
|
+
* computes the ratio against into the harness's own machine-local settings
|
|
21
|
+
* file. Reader and writer then reference one artifact instead of two
|
|
22
|
+
* resolutions that must "remember" to agree.
|
|
23
|
+
*
|
|
24
|
+
* Vendor neutrality: this module knows about a JSON file with an `env` block
|
|
25
|
+
* and nothing else. The settings path and the env-var name are BOTH passed
|
|
26
|
+
* in by the caller, resolved from the IDE adapter's declarations. There is
|
|
27
|
+
* no IDE id, no `.claude` literal, and no registry import in this file.
|
|
28
|
+
*/
|
|
29
|
+
/**
|
|
30
|
+
* Peaks-owned opt-out flag, read from the SAME machine-local `env` block the
|
|
31
|
+
* window key is written to.
|
|
32
|
+
*
|
|
33
|
+
* Why it exists: `resetHarnessWindow` must be a real rollback. Without a
|
|
34
|
+
* durable opt-out, the next context probe would simply write the key back
|
|
35
|
+
* and the user's "remove it" would last until the next tool call — a
|
|
36
|
+
* rollback in name only. Absent (or any value other than `off`) = sync is
|
|
37
|
+
* enabled, which keeps every project installed by an earlier release
|
|
38
|
+
* unchanged.
|
|
39
|
+
*/
|
|
40
|
+
export declare const HARNESS_WINDOW_SYNC_OPTOUT_KEY = "PEAKS_HARNESS_WINDOW_SYNC";
|
|
41
|
+
/** The one value that disables the sync. */
|
|
42
|
+
export declare const HARNESS_WINDOW_SYNC_OPTOUT_VALUE = "off";
|
|
43
|
+
/**
|
|
44
|
+
* Provenance marker: the window value peaks-loop ITSELF last wrote, recorded
|
|
45
|
+
* in the same `env` block as the window key.
|
|
46
|
+
*
|
|
47
|
+
* Why it must exist. The window key is a SHARED artifact — the harness reads
|
|
48
|
+
* it, the user may set it by hand (it is a documented Claude Code variable),
|
|
49
|
+
* and peaks-loop writes it. Two rules collide on it:
|
|
50
|
+
*
|
|
51
|
+
* - the ratio peaks-loop reports must divide by the key (the single-source
|
|
52
|
+
* rule), so the key outranks the model heuristic; and
|
|
53
|
+
* - a first-time mis-resolution must not become PERMANENT.
|
|
54
|
+
*
|
|
55
|
+
* The second rule needs the late 1M rescue to be able to overrule the key when
|
|
56
|
+
* the observed context proves the key too small. Applied to a value a HUMAN
|
|
57
|
+
* pinned, that same rescue would silently destroy an explicit setting — the
|
|
58
|
+
* user asks for an early compact, a long session outgrows the pin, and
|
|
59
|
+
* peaks-loop rewrites the file to 1M, permanently. Applied to peaks-loop's own
|
|
60
|
+
* earlier output it is simply self-correction.
|
|
61
|
+
*
|
|
62
|
+
* This marker is what tells the two apart: it records what peaks-loop wrote,
|
|
63
|
+
* so a mismatch means the human has taken the key over and the rescue must
|
|
64
|
+
* stand down. It travels in the same write as the value it describes, so the
|
|
65
|
+
* two can never disagree about which write they belong to.
|
|
66
|
+
*/
|
|
67
|
+
export declare const HARNESS_WINDOW_WRITTEN_KEY = "PEAKS_HARNESS_WINDOW_WRITTEN";
|
|
68
|
+
/**
|
|
69
|
+
* The harness's OWN accepted band for its auto-compact window (E1, rid
|
|
70
|
+
* 2026-09-13-defects-e).
|
|
71
|
+
*
|
|
72
|
+
* THE BAND IS A PROPERTY OF THE KEY, NOT OF peaks-loop. `autoCompactWindow` —
|
|
73
|
+
* and the `CLAUDE_CODE_AUTO_COMPACT_WINDOW` env var that shadows it — is
|
|
74
|
+
* documented as "how full the context window gets before Claude Code compacts
|
|
75
|
+
* automatically, in tokens from 100000 to 1000000", and a value outside that
|
|
76
|
+
* band is not the window the harness compacts on: below the minimum the value
|
|
77
|
+
* is ignored, above the maximum it is reduced to the model's own context size
|
|
78
|
+
* (`Math.min(native, override)`; no Claude model's window exceeds 1000000, so
|
|
79
|
+
* above the maximum the reduction is certain).
|
|
80
|
+
*
|
|
81
|
+
* Either way the outcome is the failure this whole module exists to prevent:
|
|
82
|
+
* peaks-loop divides its ratio by a number the harness is not using, so
|
|
83
|
+
* "85%" names a point the harness will never fire at. A positive integer is
|
|
84
|
+
* therefore NOT sufficient — the band is part of what the value means.
|
|
85
|
+
*
|
|
86
|
+
* Clamping instead was rejected. A clamped write would put a DIFFERENT number
|
|
87
|
+
* in the file from the one the probe just divided by, restoring the
|
|
88
|
+
* two-resolutions drift inside the single write that is supposed to delete it.
|
|
89
|
+
* Refusing keeps the disagreement in one place, reportable, and reported.
|
|
90
|
+
*/
|
|
91
|
+
export declare const HARNESS_WINDOW_MIN_TOKENS = 100000;
|
|
92
|
+
/** See `HARNESS_WINDOW_MIN_TOKENS`. */
|
|
93
|
+
export declare const HARNESS_WINDOW_MAX_TOKENS = 1000000;
|
|
94
|
+
/**
|
|
95
|
+
* True when `tokens` is a window the harness will actually compact on.
|
|
96
|
+
* `null` (absent / unparseable) is NOT in band — there is no window at all.
|
|
97
|
+
*/
|
|
98
|
+
export declare function isHarnessWindowInRange(tokens: number | null): boolean;
|
|
99
|
+
/** Where the harness key lives + which key it is. Both caller-supplied. */
|
|
100
|
+
export interface HarnessWindowLocation {
|
|
101
|
+
/** Absolute path of the machine-local settings file. */
|
|
102
|
+
readonly settingsPath: string;
|
|
103
|
+
/** Env var / settings key the harness reads its auto-compact window from. */
|
|
104
|
+
readonly envVar: string;
|
|
105
|
+
/**
|
|
106
|
+
* The project root `settingsPath` was derived from, when the caller knows it.
|
|
107
|
+
*
|
|
108
|
+
* Why the writer wants it: `--project .` with the shell sitting in the user's
|
|
109
|
+
* home directory resolves the "project root" to `$HOME` itself, which puts
|
|
110
|
+
* this file at `$HOME/.claude/settings.local.json` — the user's PERSONAL
|
|
111
|
+
* harness settings, shared by every project they own and outside any repo
|
|
112
|
+
* peaks-loop has business editing. A new terminal starts in `$HOME`, so that
|
|
113
|
+
* combination is the ordinary one, not an exotic one. The writer refuses it
|
|
114
|
+
* (reason `unsafe-project-root`) and says so instead of writing.
|
|
115
|
+
*
|
|
116
|
+
* Optional: callers that legitimately operate on a temp directory or on a
|
|
117
|
+
* path they built themselves simply omit it, and no check is made. The
|
|
118
|
+
* comparison lives in `isUserHomeProjectRoot` (`config-safety.ts`) and is
|
|
119
|
+
* never string equality — a `--project` argument may arrive as `C:/Users/x`
|
|
120
|
+
* where `homedir()` yields `C:\Users\x`.
|
|
121
|
+
*/
|
|
122
|
+
readonly projectRoot?: string;
|
|
123
|
+
}
|
|
124
|
+
/** Which artifact a value was read from. */
|
|
125
|
+
export type HarnessWindowValueSource = 'process-env' | 'settings-file';
|
|
126
|
+
export interface HarnessWindowReadResult {
|
|
127
|
+
/** Parsed window in tokens, or null when absent / not a positive integer. */
|
|
128
|
+
readonly tokens: number | null;
|
|
129
|
+
/** The raw on-disk / in-env value, for diagnostics. */
|
|
130
|
+
readonly raw: unknown;
|
|
131
|
+
/** Where the value came from; null when neither side carries it. */
|
|
132
|
+
readonly source: HarnessWindowValueSource | null;
|
|
133
|
+
/**
|
|
134
|
+
* The SETTINGS FILE's window, parsed — the value the harness will carry
|
|
135
|
+
* tomorrow, independent of the frozen process env. `null` when the file is
|
|
136
|
+
* silent or its value is unparseable.
|
|
137
|
+
*
|
|
138
|
+
* This is the T1 number, and it is deliberately separate from `tokens`:
|
|
139
|
+
* `tokens` answers "what is in force in the running session?" (env-first,
|
|
140
|
+
* for the status read), while `fileTokens` answers "what is configured for
|
|
141
|
+
* the harness?" — the one artifact peaks-loop can keep consistent with its
|
|
142
|
+
* own ratio. See `resolveHarnessRatioWindow` in `auto-compact-reader.ts`.
|
|
143
|
+
*/
|
|
144
|
+
readonly fileTokens: number | null;
|
|
145
|
+
/**
|
|
146
|
+
* The settings file's raw value, `undefined` when the file carries no such
|
|
147
|
+
* key. Presence-detection only; use `fileTokens` for the number.
|
|
148
|
+
*
|
|
149
|
+
* Needed because "the file says 150000" and "the file says something
|
|
150
|
+
* unparseable" must both be distinguishable from "the file is silent": the
|
|
151
|
+
* first two are values peaks-loop must not overwrite, the third is an empty
|
|
152
|
+
* slot it may fill.
|
|
153
|
+
*/
|
|
154
|
+
readonly fileRaw: unknown;
|
|
155
|
+
/** True when `settings.local.json` opts out of peaks-loop managing it. */
|
|
156
|
+
readonly optedOut: boolean;
|
|
157
|
+
/**
|
|
158
|
+
* True when peaks-loop is the current writer of this key — the file's window
|
|
159
|
+
* value still matches the `HARNESS_WINDOW_WRITTEN_KEY` marker peaks-loop
|
|
160
|
+
* wrote with it. False when the key is unset, or when its value differs from
|
|
161
|
+
* the marker, i.e. a human has taken the key over.
|
|
162
|
+
*
|
|
163
|
+
* Deliberately decided from the FILE's value, not from whichever copy
|
|
164
|
+
* (`process-env` / `settings-file`) supplied `tokens` this time. A running
|
|
165
|
+
* session freezes its env at start-up, so after peaks-loop raises the file
|
|
166
|
+
* the env still hands back the superseded number; judging by that copy would
|
|
167
|
+
* report "a human set this" about peaks-loop's own value and switch the late
|
|
168
|
+
* 1M rescue off on the next probe.
|
|
169
|
+
*
|
|
170
|
+
* The caller uses this to decide whether the late 1M rescue may overrule the
|
|
171
|
+
* harness layer: peaks-loop's own output may be corrected, a human's pin may
|
|
172
|
+
* not. See `resolveContextWindowTokens`.
|
|
173
|
+
*/
|
|
174
|
+
readonly peakWritten: boolean;
|
|
175
|
+
}
|
|
176
|
+
export type HarnessWindowSyncAction = 'written' | 'unchanged' | 'skipped';
|
|
177
|
+
export interface HarnessWindowSyncResult {
|
|
178
|
+
readonly settingsPath: string;
|
|
179
|
+
/** The key written / read (the adapter-declared `autoCompactWindowEnvVar`). */
|
|
180
|
+
readonly key: string;
|
|
181
|
+
readonly action: HarnessWindowSyncAction;
|
|
182
|
+
/**
|
|
183
|
+
* Why the sync did nothing. Present only when `action === 'skipped'`:
|
|
184
|
+
* - `no-window-resolved` — the caller had no token window to write
|
|
185
|
+
* (percent-only probe sources carry none)
|
|
186
|
+
* - `opted-out` — the user ran the rollback
|
|
187
|
+
* - `unreadable-settings` — the file exists but is not a JSON object we
|
|
188
|
+
* can safely edit
|
|
189
|
+
* - `not-peaks-owned` — the file already carries a value that
|
|
190
|
+
* peaks-loop did not write (a human's pin, or
|
|
191
|
+
* another tool's). peaks-loop never rewrites it
|
|
192
|
+
* and never re-arms its provenance marker for
|
|
193
|
+
* it; the value wins over peaks-loop's own
|
|
194
|
+
* resolution instead. This is the B1 guard —
|
|
195
|
+
* without it a stale process env (or any
|
|
196
|
+
* re-resolution) silently reverts a human's
|
|
197
|
+
* hand-edited window and re-claims ownership.
|
|
198
|
+
* - `unsafe-project-root` — the resolved project root IS the user's home
|
|
199
|
+
* directory, so this write would land in their
|
|
200
|
+
* personal `~/.claude/settings.local.json`
|
|
201
|
+
* (`--project .` from a fresh terminal). The H1
|
|
202
|
+
* guard; `--reset` is deliberately still allowed
|
|
203
|
+
* there, because removing a key an earlier
|
|
204
|
+
* release put in `$HOME` is the user's explicit
|
|
205
|
+
* recovery path.
|
|
206
|
+
*/
|
|
207
|
+
readonly reason?: string;
|
|
208
|
+
/** The value in force after the call. */
|
|
209
|
+
readonly tokens: number | null;
|
|
210
|
+
/**
|
|
211
|
+
* The value the SETTINGS FILE held before the call (null when absent — or
|
|
212
|
+
* when the file is unreadable). Deliberately the file's value, not the
|
|
213
|
+
* process env's: the file is what the next session reads, so it is what the
|
|
214
|
+
* caller must quote back to the user ("was 200000") and what decides whether
|
|
215
|
+
* a write is needed at all.
|
|
216
|
+
*/
|
|
217
|
+
readonly previousTokens: number | null;
|
|
218
|
+
/**
|
|
219
|
+
* The window the caller ASKED to materialize — the denominator it computed
|
|
220
|
+
* that probe's ratio against (`probe.capacityTokens`). `null` when the
|
|
221
|
+
* caller carried no token window at all.
|
|
222
|
+
*
|
|
223
|
+
* Why it is reported rather than left at the call site: the interesting
|
|
224
|
+
* outcome is a DISAGREEMENT, and a disagreement takes two numbers. A result
|
|
225
|
+
* that carried only "what is in force" left every consumer unable to name
|
|
226
|
+
* what it disagrees WITH — the notice for a refused write could say that
|
|
227
|
+
* nothing was written and never which two numbers were apart. See
|
|
228
|
+
* `describeHarnessWindowSync`.
|
|
229
|
+
*/
|
|
230
|
+
readonly requestedTokens: number | null;
|
|
231
|
+
/**
|
|
232
|
+
* The file's RAW value for the key before the call; `undefined` when the
|
|
233
|
+
* file carried no such key.
|
|
234
|
+
*
|
|
235
|
+
* The one case `previousTokens` cannot express: a hand-typed `500k` parses
|
|
236
|
+
* to `null`, exactly like an absent key, yet the two need opposite notices —
|
|
237
|
+
* an absent key is a slot peaks-loop may fill, a typo is a value it must not
|
|
238
|
+
* touch. Only the raw text can be quoted back to the user, so it travels
|
|
239
|
+
* with the result.
|
|
240
|
+
*/
|
|
241
|
+
readonly previousRawValue: unknown;
|
|
242
|
+
/**
|
|
243
|
+
* True when the window in the file after the call is peaks-loop's own write
|
|
244
|
+
* (its value matches the provenance marker).
|
|
245
|
+
*
|
|
246
|
+
* Reported because "the file already holds the number we wanted" has two very
|
|
247
|
+
* different meanings and only this flag tells them apart: peaks-loop's own
|
|
248
|
+
* earlier output, which it may raise when a session outgrows it, versus a
|
|
249
|
+
* value a human pinned, which it never will. Without it, `unchanged` reads as
|
|
250
|
+
* "all good" for a key peaks-loop has permanently stopped managing — the
|
|
251
|
+
* state a user upgrading from a pre-marker release is silently in.
|
|
252
|
+
*
|
|
253
|
+
* Always `true` for `action: 'written'`: the write that produced the result
|
|
254
|
+
* is what sets the marker.
|
|
255
|
+
*/
|
|
256
|
+
readonly peakWritten: boolean;
|
|
257
|
+
}
|
|
258
|
+
export type HarnessWindowResetResult = {
|
|
259
|
+
readonly settingsPath: string;
|
|
260
|
+
readonly action: 'removed' | 'absent';
|
|
261
|
+
readonly previousTokens: number | null;
|
|
262
|
+
};
|
|
263
|
+
/**
|
|
264
|
+
* The ONE wording of "what the harness-window sync just did", for both
|
|
265
|
+
* commands that sync (`peaks code context-now`, `peaks code auto-compact`).
|
|
266
|
+
*
|
|
267
|
+
* Why it lives here rather than inline at each call site: peaks-loop rewrites
|
|
268
|
+
* a file in the user's own harness settings on every probe, and the user
|
|
269
|
+
* accepted that write on one condition — 要告知 (tell me). Two copies of the
|
|
270
|
+
* sentence would mean a future edit tells half the users. This is the only
|
|
271
|
+
* definition; the CLI renders whatever it returns.
|
|
272
|
+
*
|
|
273
|
+
* The `written` branch is the load-bearing one: it names the key, the value,
|
|
274
|
+
* the file, what the value was before, and the exact rollback command, because
|
|
275
|
+
* the harness itself reports an override only through `/autocompact` — if
|
|
276
|
+
* peaks-loop does not say it here, nobody says it.
|
|
277
|
+
*
|
|
278
|
+
* The `skipped` branch is the one that is easy to get wrong, and did: it used
|
|
279
|
+
* to end at the reason token (`Harness window not managed (not-peaks-owned).`),
|
|
280
|
+
* which is true and tells the reader nothing. A refusal can be the exact
|
|
281
|
+
* moment the two sides came apart — the ratio divided by one number while the
|
|
282
|
+
* file pins another — so it is composed from the reason, the two numbers when
|
|
283
|
+
* they disagree, and the raw value when the file's value is not a number at
|
|
284
|
+
* all. See `harnessWindowConflictClause` / `unreadableWindowValueClause`.
|
|
285
|
+
*/
|
|
286
|
+
export declare function describeHarnessWindowSync(result: HarnessWindowSyncResult | null): string;
|
|
287
|
+
/**
|
|
288
|
+
* The same disagreement as `harnessWindowConflictClause`, reduced to one line
|
|
289
|
+
* for the `warnings` channel — the machine-readable half of 要告知, so a
|
|
290
|
+
* consumer reading the JSON envelope (or a human reading stderr) sees it even
|
|
291
|
+
* if it never renders `nextActions`. Returns `null` when there is nothing to
|
|
292
|
+
* warn about, which is the ordinary case.
|
|
293
|
+
*/
|
|
294
|
+
export declare function harnessWindowSyncWarning(result: HarnessWindowSyncResult | null): string | null;
|
|
295
|
+
/**
|
|
296
|
+
* Parse a candidate window value. Positive finite integers only — a number,
|
|
297
|
+
* or a numeric string (the env block is JSON, so the value on disk is always
|
|
298
|
+
* a string there). The harness's own documentation is explicit that the
|
|
299
|
+
* variable "accepts only the plain token count" and NOT a `850k` suffix, so
|
|
300
|
+
* anything non-integral is refused here rather than written and silently
|
|
301
|
+
* ignored downstream.
|
|
302
|
+
*/
|
|
303
|
+
export declare function parseHarnessWindowTokens(raw: unknown): number | null;
|
|
304
|
+
/**
|
|
305
|
+
* Read the window the harness is currently using for auto-compact.
|
|
306
|
+
*
|
|
307
|
+
* Order: the process env FIRST (that is the value the RUNNING session
|
|
308
|
+
* captured at start-up, so it is what the harness is actually compacting
|
|
309
|
+
* against right now), then the settings file (what the next session will
|
|
310
|
+
* read). When the two disagree, the file is what the sync updates — the
|
|
311
|
+
* process env cannot be changed from inside a running session.
|
|
312
|
+
*/
|
|
313
|
+
export declare function readHarnessWindow(input: {
|
|
314
|
+
readonly location: HarnessWindowLocation;
|
|
315
|
+
readonly env?: NodeJS.ProcessEnv | undefined;
|
|
316
|
+
}): HarnessWindowReadResult;
|
|
317
|
+
/**
|
|
318
|
+
* Write `tokens` as the harness's auto-compact window. Idempotent: a re-run
|
|
319
|
+
* with the same value performs no write at all, so repeated calls (the sync
|
|
320
|
+
* runs on every context probe) cannot churn the file or duplicate the entry.
|
|
321
|
+
*
|
|
322
|
+
* Everything else in the file is preserved verbatim — the caller's own `env`
|
|
323
|
+
* entries, every `hooks` entry, and any unknown top-level key. This is the
|
|
324
|
+
* same read-modify-write discipline `auto-compact-hook-install.ts` already
|
|
325
|
+
* applies to this file; there are now two writers, and both must leave the
|
|
326
|
+
* other's rows alone.
|
|
327
|
+
*/
|
|
328
|
+
export declare function syncHarnessWindow(input: {
|
|
329
|
+
readonly location: HarnessWindowLocation;
|
|
330
|
+
readonly tokens: number | null;
|
|
331
|
+
readonly env?: NodeJS.ProcessEnv | undefined;
|
|
332
|
+
}): HarnessWindowSyncResult;
|
|
333
|
+
/**
|
|
334
|
+
* Rollback: remove the window key and record the opt-out so the next sync
|
|
335
|
+
* does not put it straight back. Both edits land in the same
|
|
336
|
+
* read-modify-write, so there is no window in which the key is gone but the
|
|
337
|
+
* opt-out is not yet on disk.
|
|
338
|
+
*
|
|
339
|
+
* Idempotent: a second call reports `action: 'absent'` and rewrites nothing
|
|
340
|
+
* unless the opt-out row is missing.
|
|
341
|
+
*/
|
|
342
|
+
export declare function resetHarnessWindow(input: {
|
|
343
|
+
readonly location: HarnessWindowLocation;
|
|
344
|
+
readonly env?: NodeJS.ProcessEnv | undefined;
|
|
345
|
+
}): HarnessWindowResetResult;
|
|
346
|
+
/**
|
|
347
|
+
* Record the opt-out WITHOUT removing anything: "stop managing this key",
|
|
348
|
+
* expressible at any moment — including before peaks-loop has ever written it.
|
|
349
|
+
*
|
|
350
|
+
* WHY THIS IS A SEPARATE ENTRY POINT AND NOT A FALLBACK OF `resetHarnessWindow`
|
|
351
|
+
*
|
|
352
|
+
* "Remove what is there" and "never write it again" are two different
|
|
353
|
+
* intentions. `--reset` on a file holding no peaks row is deliberately a no-op
|
|
354
|
+
* (see the file-litter note in `resetHarnessWindow`), so a user whose project
|
|
355
|
+
* peaks-loop has never touched had NO command for the second intention at all:
|
|
356
|
+
* they had to wait for a probe to write the key and then remove it — the order
|
|
357
|
+
* backwards. Folding the opt-out into `--reset` would fix that by making one
|
|
358
|
+
* verb mean "delete" or "don't write", depending on whether the file happened
|
|
359
|
+
* to hold a row, which is a verb whose effect the user cannot predict from its
|
|
360
|
+
* name. Two verbs, two intentions, each predictable.
|
|
361
|
+
*
|
|
362
|
+
* WHAT IT DOES NOT DO
|
|
363
|
+
* - it does not touch the window key: a hand-set value stays exactly as it
|
|
364
|
+
* was (peaks-loop would not have overwritten it anyway — see the B1 guard);
|
|
365
|
+
* - it does not write a provenance marker, so it claims no ownership of a
|
|
366
|
+
* value it did not write.
|
|
367
|
+
* The next probe reports `skipped / opted-out` and writes nothing.
|
|
368
|
+
*
|
|
369
|
+
* THE H1 HOME GUARD APPLIES HERE TOO (E4, rid 2026-09-13-defects-e).
|
|
370
|
+
*
|
|
371
|
+
* This function used to exempt itself, on the argument that `--disable` is an
|
|
372
|
+
* explicit instruction and "the location's own file is the only place the
|
|
373
|
+
* opt-out can be recorded for it to mean anything". The first half is true and
|
|
374
|
+
* was never the problem; the second half does not hold at `$HOME`:
|
|
375
|
+
*
|
|
376
|
+
* - the location is NOT usually explicit. `--project` is optional, and a
|
|
377
|
+
* fresh terminal starts in `$HOME`, so the ordinary invocation resolves the
|
|
378
|
+
* root to the user's home directory without them naming it — the very
|
|
379
|
+
* trigger the H1 guard was written for;
|
|
380
|
+
* - the opt-out has NOTHING to mean there. `syncHarnessWindow` already
|
|
381
|
+
* refuses to write the window at that root (`unsafe-project-root`), so the
|
|
382
|
+
* only thing a recorded opt-out changes is which sentence the refusal uses.
|
|
383
|
+
* Nothing is made expressible; a visible refusal is traded for a quieter one;
|
|
384
|
+
* - what IS added is a durable peaks-loop row in `~/.claude/settings.local.json`
|
|
385
|
+
* — a file outside every repo. `--reset`'s exemption does not transfer to
|
|
386
|
+
* this verb: `--reset` REMOVES (and is a no-op when there is nothing of
|
|
387
|
+
* peaks-loop's to remove), while `--disable` only ever ADDS.
|
|
388
|
+
*
|
|
389
|
+
* So the guard fires here exactly as it does in `syncHarnessWindow`: the same
|
|
390
|
+
* exact-home comparison, before any `mkdir` and before any read-modify-write.
|
|
391
|
+
* `~/my-project` is unaffected, and a user who really does carry a stale
|
|
392
|
+
* peaks-loop window key in `$HOME` still has `--reset`, which is allowed there
|
|
393
|
+
* for the reason its own note gives.
|
|
394
|
+
*
|
|
395
|
+
* Idempotent: a second call reports `already-opted-out` and rewrites nothing.
|
|
396
|
+
*/
|
|
397
|
+
export declare function disableHarnessWindowSync(input: {
|
|
398
|
+
readonly location: HarnessWindowLocation;
|
|
399
|
+
}): {
|
|
400
|
+
readonly settingsPath: string;
|
|
401
|
+
readonly action: 'disabled' | 'already-opted-out' | 'unreadable-settings' | 'refused-unsafe-project-root';
|
|
402
|
+
};
|
|
403
|
+
/**
|
|
404
|
+
* Undo the opt-out (the companion of `resetHarnessWindow`) so peaks-loop
|
|
405
|
+
* resumes owning the harness window.
|
|
406
|
+
*/
|
|
407
|
+
export declare function reenableHarnessWindowSync(input: {
|
|
408
|
+
readonly location: HarnessWindowLocation;
|
|
409
|
+
}): {
|
|
410
|
+
readonly settingsPath: string;
|
|
411
|
+
readonly action: 'reenabled' | 'absent';
|
|
412
|
+
};
|