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
|
@@ -3,6 +3,7 @@ import { execFileSync } from 'node:child_process';
|
|
|
3
3
|
import { randomUUID } from 'node:crypto';
|
|
4
4
|
import { dirname, isAbsolute, relative, resolve } from 'node:path';
|
|
5
5
|
import { homedir } from 'node:os';
|
|
6
|
+
import { projectRootsMatch } from '../../shared/path-utils.js';
|
|
6
7
|
export function getUserConfigPath() {
|
|
7
8
|
return resolve(homedir(), '.peaks', 'config.json');
|
|
8
9
|
}
|
|
@@ -202,7 +203,8 @@ function resolveProjectRootFromGit(startPath) {
|
|
|
202
203
|
const stdout = execFileSync('git', ['rev-parse', '--show-toplevel'], {
|
|
203
204
|
cwd: startPath,
|
|
204
205
|
encoding: 'utf8',
|
|
205
|
-
stdio: ['ignore', 'pipe', 'ignore']
|
|
206
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
207
|
+
windowsHide: true
|
|
206
208
|
});
|
|
207
209
|
const trimmed = stdout.trim();
|
|
208
210
|
if (trimmed.length === 0)
|
|
@@ -227,6 +229,78 @@ function resolveProjectRootFromGit(startPath) {
|
|
|
227
229
|
return rawRoot;
|
|
228
230
|
}
|
|
229
231
|
}
|
|
232
|
+
/**
|
|
233
|
+
* True when `projectRoot` IS the user's home directory itself.
|
|
234
|
+
*
|
|
235
|
+
* Exact match, NOT `isInsidePath`: `~/my-project` is an ordinary project and
|
|
236
|
+
* must keep working — the guard exists for the one directory that is never a
|
|
237
|
+
* project.
|
|
238
|
+
*
|
|
239
|
+
* `projectRootsMatch` rather than `===` because the two strings reach this
|
|
240
|
+
* function from different sources: a `--project .` argument arrives in the
|
|
241
|
+
* caller's spelling while `homedir()` yields the OS form (`C:\Users\x` vs
|
|
242
|
+
* `C:/Users/x`, and differing case on win32/darwin).
|
|
243
|
+
*
|
|
244
|
+
* This is the ONE definition of "the user's home" in the codebase; the
|
|
245
|
+
* harness-window writer's own guard delegates here so the two cannot drift.
|
|
246
|
+
*/
|
|
247
|
+
export function isUserHomeProjectRoot(projectRoot) {
|
|
248
|
+
try {
|
|
249
|
+
return projectRootsMatch(projectRoot, homedir());
|
|
250
|
+
}
|
|
251
|
+
catch {
|
|
252
|
+
// A path that cannot be resolved is not a claim about the home directory.
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Thrown by `assertWritableProjectRoot` / `resolveWritableProjectRoot` when a
|
|
258
|
+
* write would land in the user's home directory. Distinct from
|
|
259
|
+
* `InvalidProjectRootError` (which is about a malformed/absent path): this path
|
|
260
|
+
* is perfectly valid — it is just not a project.
|
|
261
|
+
*/
|
|
262
|
+
export class UnsafeProjectRootError extends Error {
|
|
263
|
+
projectRoot;
|
|
264
|
+
constructor(projectRoot) {
|
|
265
|
+
super(`Refusing to write into the user's home directory: "${projectRoot}" is not a project root.`);
|
|
266
|
+
this.projectRoot = projectRoot;
|
|
267
|
+
this.name = 'UnsafeProjectRootError';
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
/**
|
|
271
|
+
* Refuse a project root that IS the user's home directory.
|
|
272
|
+
*
|
|
273
|
+
* The hazard this closes: a fresh terminal starts in `$HOME`, and
|
|
274
|
+
* `peaks workspace init --project .` there resolved to `$HOME` (via
|
|
275
|
+
* `resolveCanonicalProjectRoot`'s fail-open fallback, which returns the start
|
|
276
|
+
* path when it can find no project marker) and then materialized a whole
|
|
277
|
+
* project tree INTO the user's home — `.claude/settings.local.json` above all,
|
|
278
|
+
* which is a live config file shared with every other project on the machine.
|
|
279
|
+
*
|
|
280
|
+
* Deliberately NOT enforced inside `resolveCanonicalProjectRoot`. That helper
|
|
281
|
+
* is called from 85 sites across 32 files, most of them READS (`peaks status`,
|
|
282
|
+
* `peaks dashboard`, `peaks memory`, the observability commands). Refusing
|
|
283
|
+
* there would break every one of them the moment a user runs a read command
|
|
284
|
+
* from their home directory, and its fail-open contract is documented and
|
|
285
|
+
* depended on. The hazard is a WRITE to `$HOME/.claude/`, so the guard belongs
|
|
286
|
+
* on write paths — this function, plus `resolveWritableProjectRoot` so a
|
|
287
|
+
* writer only has to make one call.
|
|
288
|
+
*/
|
|
289
|
+
export function assertWritableProjectRoot(projectRoot) {
|
|
290
|
+
if (isUserHomeProjectRoot(projectRoot)) {
|
|
291
|
+
throw new UnsafeProjectRootError(projectRoot);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
/**
|
|
295
|
+
* `resolveCanonicalProjectRoot` for a path that is about to be WRITTEN to:
|
|
296
|
+
* same git-root-then-heuristic promotion, plus the home-directory refusal.
|
|
297
|
+
* Read paths keep using the fail-open helper.
|
|
298
|
+
*/
|
|
299
|
+
export function resolveWritableProjectRoot(startPath) {
|
|
300
|
+
const root = resolveCanonicalProjectRoot(startPath);
|
|
301
|
+
assertWritableProjectRoot(root);
|
|
302
|
+
return root;
|
|
303
|
+
}
|
|
230
304
|
export function getProjectConfigPath(projectRoot) {
|
|
231
305
|
if (!projectRoot)
|
|
232
306
|
return null;
|
|
@@ -1,37 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AC-3 — IDE-aware compact dispatcher.
|
|
3
|
-
*
|
|
4
|
-
* Reads the active IDE's `IdeAdapter.compact` profile and dispatches
|
|
5
|
-
* compact via the adapter-declared pathway. No hard-coded IDE names
|
|
6
|
-
* — Claude Code is the MVP that fills `compact`; other adapters
|
|
7
|
-
* register their own `compact` profile as L2-dogfood verifies
|
|
8
|
-
* each IDE's actual compact surface.
|
|
9
|
-
*
|
|
10
|
-
* Pathway dispatch:
|
|
11
|
-
*
|
|
12
|
-
* - `ide-native` — peaks-loop writes the compact intent to
|
|
13
|
-
* the IDE's hook file (per
|
|
14
|
-
* `IdeSettingsLocation`). Used when the IDE
|
|
15
|
-
* requires a registered hook rather than a
|
|
16
|
-
* runtime command.
|
|
17
|
-
* - `llm-self-compress` — peaks-loop records the intent and
|
|
18
|
-
* returns `ok: true` with `pathway` echoed.
|
|
19
|
-
* The LLM does its own context summarization
|
|
20
|
-
* on the next turn. Always available; least
|
|
21
|
-
* precise fallback.
|
|
22
|
-
* - `noop` — adapter explicitly opted out. peaks-loop
|
|
23
|
-
* returns `ok: false` with `message: 'noop'`.
|
|
24
|
-
* Used by legacy / unverified adapters.
|
|
25
|
-
*
|
|
26
|
-
* rid-031 (2026-07-28): `shell-exec` pathway is DEPRECATED. The
|
|
27
|
-
* `case 'shell-exec':` branch remains reachable only to keep the
|
|
28
|
-
* 2 currently-passing tests in `tests/unit/context/auto-compact-main-target.test.ts:58`
|
|
29
|
-
* + `tests/unit/services/context/auto-compact-dispatcher-ide-native.test.ts:102`
|
|
30
|
-
* passing (they assert `pathway: 'shell-exec'`). Real callers must
|
|
31
|
-
* use `ide-native` (main session) or `llm-self-compress`. No host
|
|
32
|
-
* CLI spawn occurs; the case logs a deprecation warning and returns
|
|
33
|
-
* the same envelope shape.
|
|
34
|
-
*/
|
|
35
1
|
import type { CompactDispatchResult } from './auto-compact-types.js';
|
|
36
2
|
export type CompactTarget = 'main' | 'sub-agent';
|
|
37
3
|
export interface DispatchIdeCompactInput {
|
|
@@ -48,14 +14,18 @@ export interface DispatchIdeCompactInput {
|
|
|
48
14
|
* shells that spawn their own `peaks code auto-compact` flow pass
|
|
49
15
|
* `'sub-agent'` to preserve the legacy shell-spawn behaviour.
|
|
50
16
|
*
|
|
51
|
-
* Behaviour matrix
|
|
17
|
+
* Behaviour matrix, keyed on the ADAPTER'S DECLARED pathway only
|
|
18
|
+
* — never on the adapter's name (slice
|
|
19
|
+
* 2026-09-12-auto-compact-vendor-neutrality):
|
|
52
20
|
* - target='main' → llm-self-compress (write intent; main LLM
|
|
53
|
-
* fires
|
|
21
|
+
* fires its compact command on its next turn).
|
|
54
22
|
* - target='sub-agent'→ shell-exec stub (DEPRECATED — no host
|
|
55
23
|
* CLI spawn; returns envelope with
|
|
56
24
|
* `pathway: 'shell-exec'` for legacy
|
|
57
25
|
* contract only — see rid-031).
|
|
58
|
-
*
|
|
26
|
+
* An adapter with no registered `compact` profile returns noop for
|
|
27
|
+
* BOTH targets; an adapter whose profile serves the main session is
|
|
28
|
+
* dispatched regardless of which IDE it is.
|
|
59
29
|
*/
|
|
60
30
|
readonly target?: CompactTarget | undefined;
|
|
61
31
|
}
|
|
@@ -1,3 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AC-3 — IDE-aware compact dispatcher.
|
|
3
|
+
*
|
|
4
|
+
* Reads the active IDE's `IdeAdapter.compact` profile and dispatches
|
|
5
|
+
* compact via the adapter-declared pathway. No hard-coded IDE names
|
|
6
|
+
* — Claude Code is the MVP that fills `compact`; other adapters
|
|
7
|
+
* register their own `compact` profile as L2-dogfood verifies
|
|
8
|
+
* each IDE's actual compact surface.
|
|
9
|
+
*
|
|
10
|
+
* Pathway dispatch:
|
|
11
|
+
*
|
|
12
|
+
* - `ide-native` — peaks-loop writes the compact intent to
|
|
13
|
+
* the IDE's hook file (per
|
|
14
|
+
* `IdeSettingsLocation`). Used when the IDE
|
|
15
|
+
* requires a registered hook rather than a
|
|
16
|
+
* runtime command.
|
|
17
|
+
* - `llm-self-compress` — peaks-loop records the intent and
|
|
18
|
+
* returns `ok: true` with `pathway` echoed.
|
|
19
|
+
* The LLM does its own context summarization
|
|
20
|
+
* on the next turn. Always available; least
|
|
21
|
+
* precise fallback.
|
|
22
|
+
* - `noop` — adapter explicitly opted out. peaks-loop
|
|
23
|
+
* returns `ok: false` with `message: 'noop'`.
|
|
24
|
+
* Used by legacy / unverified adapters.
|
|
25
|
+
*
|
|
26
|
+
* rid-031 (2026-07-28): `shell-exec` pathway is DEPRECATED. Real
|
|
27
|
+
* callers must use `ide-native` (main session) or
|
|
28
|
+
* `llm-self-compress`. No host CLI spawn occurs; the `case
|
|
29
|
+
* 'shell-exec':` branch logs a deprecation warning and returns the
|
|
30
|
+
* same envelope shape.
|
|
31
|
+
*
|
|
32
|
+
* Slice 2026-09-12-auto-compact-vendor-neutrality (defect #2 of the QA
|
|
33
|
+
* pass): the paragraph above used to justify keeping that branch with
|
|
34
|
+
* "the 2 currently-passing tests" it named by path and line (`:58` of
|
|
35
|
+
* one file, `:102` of another, asserting `pathway: 'shell-exec'`).
|
|
36
|
+
* That justification ROTTED and is false as of today: both files were
|
|
37
|
+
* deleted by `f17aa377 test(rebuild): delete 559 legacy unit tests and
|
|
38
|
+
* reset vitest config`, and `grep -rn "shell-exec" tests/` now returns
|
|
39
|
+
* nothing — no test in this repo asserts `pathway: 'shell-exec'`. The
|
|
40
|
+
* branch is retained for the rid-031 ENVELOPE CONTRACT itself
|
|
41
|
+
* (`pathway` is part of `CompactDispatchResult` and is echoed to
|
|
42
|
+
* `peaks code auto-compact` callers), not for a test. Deleting it is a
|
|
43
|
+
* contract change, deliberately out of this slice.
|
|
44
|
+
*/
|
|
45
|
+
import { join } from 'node:path';
|
|
1
46
|
import { detectIdeFromEnv } from './ide-detect.js';
|
|
2
47
|
import { getAdapter } from '../ide/ide-registry.js';
|
|
3
48
|
/**
|
|
@@ -21,21 +66,28 @@ export async function dispatchIdeCompact(input) {
|
|
|
21
66
|
// the main-session context. The orchestrator passes `'sub-agent'`
|
|
22
67
|
// explicitly when a sub-agent shell dispatches the call.
|
|
23
68
|
const target = input.target ?? 'main';
|
|
24
|
-
// Slice 2026-
|
|
25
|
-
//
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
69
|
+
// Slice 2026-09-12-auto-compact-vendor-neutrality — the up-front
|
|
70
|
+
// `target === 'main' && ideId !== 'claude-code'` refusal is GONE.
|
|
71
|
+
//
|
|
72
|
+
// It decided by adapter NAME what the adapter's own profile already
|
|
73
|
+
// declares, contradicting this file's contract ("No hard-coded IDE
|
|
74
|
+
// names", :5-8): any adapter that filled in `compact` was still
|
|
75
|
+
// rejected for not being called `claude-code`.
|
|
76
|
+
//
|
|
77
|
+
// It was also redundant. Every pathway below owns its own
|
|
78
|
+
// main-session semantics — `ide-native` installs the hook against the
|
|
79
|
+
// caller's own window; `shell-exec` is explicitly degraded to
|
|
80
|
+
// `llm-self-compress` for a main target; `llm-self-compress` and
|
|
81
|
+
// `noop` behave identically for both targets (so `noop` still returns
|
|
82
|
+
// `ok: false` on main); an UNKNOWN pathway falls through to the
|
|
83
|
+
// default noop. An adapter with no `compact` profile keeps its own
|
|
84
|
+
// explicit refusal immediately below. No remaining capability test
|
|
85
|
+
// would have been anything but a duplicate of one of those branches,
|
|
86
|
+
// so the check was deleted rather than re-keyed. The two candidate
|
|
87
|
+
// re-keyings were both rejected: `pathway !== 'ide-native'` would
|
|
88
|
+
// refuse `shell-exec` and `llm-self-compress` adapters that the
|
|
89
|
+
// switch already serves, and `pathway === 'noop'` IS the branch
|
|
90
|
+
// immediately below.
|
|
39
91
|
// Adapters that don't declare `compact` (legacy / unverified) →
|
|
40
92
|
// explicit noop so the caller can distinguish "IDE doesn't support
|
|
41
93
|
// compact" from "IDE supports but dispatch failed".
|
|
@@ -53,12 +105,13 @@ export async function dispatchIdeCompact(input) {
|
|
|
53
105
|
switch (pathway) {
|
|
54
106
|
case 'shell-exec':
|
|
55
107
|
// rid-031 (2026-07-28): `shell-exec` pathway is DEPRECATED.
|
|
56
|
-
// No host CLI spawn occurs. The case marker is preserved
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
// use `ide-native` (main session) or
|
|
108
|
+
// No host CLI spawn occurs. The case marker is preserved for the
|
|
109
|
+
// rid-031 envelope contract (callers read `pathway`), NOT for a
|
|
110
|
+
// test — see this file's header, defect #2 of the 2026-09-12 QA
|
|
111
|
+
// pass: the tests the previous comment named were deleted by
|
|
112
|
+
// f17aa377 and no test asserts `pathway: 'shell-exec'` now.
|
|
113
|
+
// Real callers must use `ide-native` (main session) or
|
|
114
|
+
// `llm-self-compress`.
|
|
62
115
|
console.warn(`compact: shell-exec pathway is deprecated on adapter '${ideId}' (target='${target}'); no host CLI spawn — use ide-native instead.`);
|
|
63
116
|
if (target === 'main') {
|
|
64
117
|
return {
|
|
@@ -76,12 +129,14 @@ export async function dispatchIdeCompact(input) {
|
|
|
76
129
|
};
|
|
77
130
|
case 'ide-native':
|
|
78
131
|
// Slice 2026-07-02-auto-compact-zero-pause: write the auto-compact
|
|
79
|
-
// PreToolUse hook into
|
|
80
|
-
//
|
|
81
|
-
// `
|
|
82
|
-
//
|
|
83
|
-
//
|
|
84
|
-
//
|
|
132
|
+
// PreToolUse hook into the IDE's MACHINE-LOCAL settings layer,
|
|
133
|
+
// resolved from the adapter (`settings.dirName` +
|
|
134
|
+
// `settings.localSettingsFileName`) — never from a path literal
|
|
135
|
+
// here. The hook command (`peaks code auto-compact`) reads the
|
|
136
|
+
// adapter-declared `envVarForContextPercent` on every subsequent
|
|
137
|
+
// Bash/Task tool call from the runner and, at ratio ≥ 0.95,
|
|
138
|
+
// in-band spawns the adapter-declared `compactCommand` against the
|
|
139
|
+
// CURRENT runner (not a child process — the bug documented in
|
|
85
140
|
// `.peaks/memory/2026-06-27-auto-compact-design.md:139-152`).
|
|
86
141
|
//
|
|
87
142
|
// `ide-native` is ONLY for the main-session runner. When the
|
|
@@ -93,10 +148,11 @@ export async function dispatchIdeCompact(input) {
|
|
|
93
148
|
if (target === 'sub-agent') {
|
|
94
149
|
// rid-031 (2026-07-28): legacy shell-spawn fallback is
|
|
95
150
|
// DEPRECATED. No host CLI spawn occurs. The envelope is
|
|
96
|
-
// returned with `pathway: 'shell-exec'` to preserve the
|
|
97
|
-
// contract
|
|
98
|
-
//
|
|
99
|
-
//
|
|
151
|
+
// returned with `pathway: 'shell-exec'` to preserve the rid-031
|
|
152
|
+
// contract for callers that read `pathway` (sub-agent shells
|
|
153
|
+
// historically relied on this path). The test the previous
|
|
154
|
+
// comment named was deleted by f17aa377 — see this file's
|
|
155
|
+
// header, defect #2 of the 2026-09-12 QA pass.
|
|
100
156
|
console.warn(`compact: sub-agent shell-exec fallback is deprecated on adapter '${ideId}'; no host CLI spawn for '${profile.compactCommand}'.`);
|
|
101
157
|
return {
|
|
102
158
|
ok: true,
|
|
@@ -111,7 +167,9 @@ export async function dispatchIdeCompact(input) {
|
|
|
111
167
|
return await dispatchIdeNativeHook({
|
|
112
168
|
projectRoot: input.projectRoot,
|
|
113
169
|
sessionId: input.sessionId,
|
|
114
|
-
target
|
|
170
|
+
target,
|
|
171
|
+
adapter,
|
|
172
|
+
profile
|
|
115
173
|
});
|
|
116
174
|
case 'llm-self-compress':
|
|
117
175
|
return {
|
|
@@ -141,11 +199,18 @@ export async function dispatchIdeCompact(input) {
|
|
|
141
199
|
/**
|
|
142
200
|
* Slice 2026-07-02-auto-compact-zero-pause: implement the
|
|
143
201
|
* `ide-native` pathway. Writes the auto-compact PreToolUse hook
|
|
144
|
-
* into
|
|
145
|
-
* service is a no-op if the hook is already present). On
|
|
146
|
-
* Bash/Task tool call from the runner, the hook fires
|
|
147
|
-
* `peaks code auto-compact` which in-band spawns
|
|
148
|
-
*
|
|
202
|
+
* into the IDE's MACHINE-LOCAL settings file (idempotent; the
|
|
203
|
+
* install service is a no-op if the hook is already present). On
|
|
204
|
+
* the next Bash/Task tool call from the runner, the hook fires
|
|
205
|
+
* `peaks code auto-compact` which in-band spawns the adapter's
|
|
206
|
+
* declared compact command against the CURRENT runner session.
|
|
207
|
+
*
|
|
208
|
+
* Slice 2026-09-12-auto-compact-vendor-neutrality: the settings path
|
|
209
|
+
* AND the echoed `ide` are read off the adapter, so this function no
|
|
210
|
+
* longer names a vendor. An adapter that declares
|
|
211
|
+
* `compactPathway: 'ide-native'` and its own `settings` location gets
|
|
212
|
+
* its own hook file — the `.claude/settings.local.json` value survives
|
|
213
|
+
* only as the installer's default for callers with no adapter in hand.
|
|
149
214
|
*
|
|
150
215
|
* Returns `ok: true, pathway: 'ide-native'` regardless of install
|
|
151
216
|
* action (`installed` vs `already-installed`) — both states
|
|
@@ -158,13 +223,21 @@ async function dispatchIdeNativeHook(input) {
|
|
|
158
223
|
// avoids a static cycle if future slices add cross-imports
|
|
159
224
|
// between dispatcher and hook-install.
|
|
160
225
|
const { installAutoCompactHook } = await import('../hooks/auto-compact-hook-install.js');
|
|
161
|
-
|
|
226
|
+
// Resolve the local settings file from the adapter's declared
|
|
227
|
+
// location. `undefined` means "this IDE declares no machine-local
|
|
228
|
+
// layer" → the installer's documented default applies.
|
|
229
|
+
const localFileName = input.adapter.settings.localSettingsFileName;
|
|
230
|
+
const settingsPath = localFileName === undefined
|
|
231
|
+
? undefined
|
|
232
|
+
: join(input.projectRoot, input.adapter.settings.dirName, localFileName);
|
|
233
|
+
const result = installAutoCompactHook({ projectRoot: input.projectRoot, settingsPath });
|
|
234
|
+
const envVar = input.profile.envVarForContextPercent;
|
|
162
235
|
return {
|
|
163
236
|
ok: true,
|
|
164
|
-
ide:
|
|
237
|
+
ide: input.adapter.id,
|
|
165
238
|
pathway: 'ide-native',
|
|
166
239
|
message: result.action === 'installed'
|
|
167
|
-
? `Auto-compact PreToolUse hook installed at ${result.settingsPath}. Next Bash/Task tool call will read
|
|
240
|
+
? `Auto-compact PreToolUse hook installed at ${result.settingsPath}. Next Bash/Task tool call will read ${envVar} and compact in-band at ratio ≥ 95%.`
|
|
168
241
|
: result.action === 'updated'
|
|
169
242
|
? `Auto-compact PreToolUse hook REPAIRED at ${result.settingsPath} — the installed entry carried a stale command and was rewritten.`
|
|
170
243
|
: `Auto-compact PreToolUse hook already installed at ${result.settingsPath}; next Bash/Task tool call will trigger compact in-band at ratio ≥ 95%.`
|
|
@@ -1,32 +1,5 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AC-1 — auto context-percent probe.
|
|
3
|
-
*
|
|
4
|
-
* Reads the current AI CLI context-fill ratio without requiring the
|
|
5
|
-
* LLM to pass `--prompt-size <bytes>` manually. Strategy: ask the
|
|
6
|
-
* registered `IdeAdapter.compact` profile which env-var to read and,
|
|
7
|
-
* when that misses, ask the adapter for a vendor-specific fallback —
|
|
8
|
-
* no hard-coded IDE names. Per-adapter:
|
|
9
|
-
*
|
|
10
|
-
* - claude-code: its adapter-declared env-var (MVP) + a
|
|
11
|
-
* `readContextPercentFallback` that polls the statusline /
|
|
12
|
-
* transcript (see claude-code-adapter.ts).
|
|
13
|
-
* - trae / codex / cursor / qoder / tongyi-lingma / hermes /
|
|
14
|
-
* openclaw / zcode: each adapter fills its own env-var; until
|
|
15
|
-
* L2-dogfood verifies each surface, adapters may omit `compact`
|
|
16
|
-
* and the probe returns `source: 'conservative-fallback'`.
|
|
17
|
-
*
|
|
18
|
-
* Resolution order (user-overridden → env-var → adapter fallback →
|
|
19
|
-
* conservative-fallback):
|
|
20
|
-
* 1. `promptSizeBytes` (P0 `--prompt-size <bytes>` escape hatch) →
|
|
21
|
-
* `source: 'user-overridden'`.
|
|
22
|
-
* 2. `adapter.compact.envVarForContextPercent` env-var →
|
|
23
|
-
* `source: '<ideId>-env'`.
|
|
24
|
-
* 3. `adapter.compact.readContextPercentFallback?.(input)` — the
|
|
25
|
-
* adapter owns any vendor-specific statusline / transcript probe.
|
|
26
|
-
* 4. `ratio: 0` with `source: 'conservative-fallback'` — the
|
|
27
|
-
* orchestrator MUST NOT auto-fire compact on this signal.
|
|
28
|
-
*/
|
|
29
1
|
import type { ContextPercentProbe } from './auto-compact-types.js';
|
|
2
|
+
import { type HarnessWindowLocation, type HarnessWindowReadResult, type HarnessWindowSyncResult } from './harness-window-config.js';
|
|
30
3
|
export interface ReadContextPercentInput {
|
|
31
4
|
readonly projectRoot: string;
|
|
32
5
|
readonly sessionId: string;
|
|
@@ -50,6 +23,73 @@ export interface ReadContextPercentInput {
|
|
|
50
23
|
*/
|
|
51
24
|
readonly promptSizeBytes?: number | undefined;
|
|
52
25
|
}
|
|
26
|
+
/**
|
|
27
|
+
* Resolve WHERE the harness auto-compact window lives, for the IDE the
|
|
28
|
+
* current env selects. Returns `null` when either half is undeclared:
|
|
29
|
+
* - the adapter exposes no `compact` profile, or
|
|
30
|
+
* - the adapter's `compact` profile declares no
|
|
31
|
+
* `autoCompactWindowEnvVar` (the IDE has no such knob), or
|
|
32
|
+
* - the adapter declares no machine-local settings layer.
|
|
33
|
+
*
|
|
34
|
+
* Both the path and the key name come from the adapter's OWN declarations,
|
|
35
|
+
* so this module names no IDE and no settings literal — the vendor-neutrality
|
|
36
|
+
* guard's shape-3 rule applies to every registry consumer, and this is one.
|
|
37
|
+
*
|
|
38
|
+
* Slice 2026-09-13-auto-compact-trigger-ownership.
|
|
39
|
+
*/
|
|
40
|
+
export declare function resolveHarnessWindowLocation(input: {
|
|
41
|
+
readonly projectRoot: string;
|
|
42
|
+
readonly env?: NodeJS.ProcessEnv | undefined;
|
|
43
|
+
}): HarnessWindowLocation | null;
|
|
44
|
+
/**
|
|
45
|
+
* Read the window peaks-loop last configured for the harness, plus whether
|
|
46
|
+
* the user has opted out of peaks-loop managing it. Returns `null` when the
|
|
47
|
+
* active adapter exposes no window knob at all.
|
|
48
|
+
*/
|
|
49
|
+
export declare function readHarnessWindowState(input: {
|
|
50
|
+
readonly projectRoot: string;
|
|
51
|
+
readonly env?: NodeJS.ProcessEnv | undefined;
|
|
52
|
+
}): HarnessWindowReadResult | null;
|
|
53
|
+
/**
|
|
54
|
+
* The RAW window peaks-loop's context ratio must divide by — T1's number.
|
|
55
|
+
*
|
|
56
|
+
* The SETTINGS FILE is the source of truth, so its value wins whenever it has
|
|
57
|
+
* one; the env-first read is consulted only when the file is silent (no key
|
|
58
|
+
* yet, or the IDE injects the value from an outer layer).
|
|
59
|
+
*
|
|
60
|
+
* Why not "whatever the read returned" (the round-2 shape): the process env is
|
|
61
|
+
* a snapshot the harness froze at session start, so it is stale by exactly one
|
|
62
|
+
* write. Trusting it over the file is what let a stale env revert a human's
|
|
63
|
+
* hand-edited window (B1) and downgrade a 1M file to a 200K one (a second
|
|
64
|
+
* session's frozen env), and it is also what made T1's consistency depend on
|
|
65
|
+
* *rewriting the file* — the same unconditional write that destroyed the human's
|
|
66
|
+
* value. Dividing by the file instead makes peaks-loop's ratio and the harness's
|
|
67
|
+
* configured window one number BY CONSTRUCTION, with no write required. The
|
|
68
|
+
* write is then free to be gated on provenance (see `syncHarnessWindow`).
|
|
69
|
+
*
|
|
70
|
+
* Falls back to `undefined` (no harness layer at all) when the adapter
|
|
71
|
+
* declares no knob or neither side carries the key — the adapter then resolves
|
|
72
|
+
* its own window exactly as it did before this slice.
|
|
73
|
+
*/
|
|
74
|
+
export declare function resolveHarnessRatioWindow(state: HarnessWindowReadResult | null): unknown;
|
|
75
|
+
/**
|
|
76
|
+
* Materialize a window into the harness's own settings, so the number
|
|
77
|
+
* peaks-loop divides by and the number the harness compacts against are one
|
|
78
|
+
* value rather than two resolutions (slice
|
|
79
|
+
* 2026-09-13-auto-compact-trigger-ownership).
|
|
80
|
+
*
|
|
81
|
+
* The caller passes the denominator it JUST USED (`probe.capacityTokens`), not
|
|
82
|
+
* a re-derived one — that is what makes the two sides structurally identical.
|
|
83
|
+
* `tokens: null` (a percent-only probe carries no token window) is a
|
|
84
|
+
* deliberate no-op: peaks-loop never invents a window it did not measure.
|
|
85
|
+
*
|
|
86
|
+
* Returns `null` when the active adapter declares no window knob at all.
|
|
87
|
+
*/
|
|
88
|
+
export declare function syncHarnessWindowForProject(input: {
|
|
89
|
+
readonly projectRoot: string;
|
|
90
|
+
readonly env?: NodeJS.ProcessEnv | undefined;
|
|
91
|
+
readonly tokens: number | null;
|
|
92
|
+
}): HarnessWindowSyncResult | null;
|
|
53
93
|
/**
|
|
54
94
|
* Read env-var-based context percentage. Returns `null` when the env
|
|
55
95
|
* is absent or unparseable. Adapter-driven: the caller passes the
|
|
@@ -1,6 +1,141 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AC-1 — auto context-percent probe.
|
|
3
|
+
*
|
|
4
|
+
* Reads the current AI CLI context-fill ratio without requiring the
|
|
5
|
+
* LLM to pass `--prompt-size <bytes>` manually. Strategy: ask the
|
|
6
|
+
* registered `IdeAdapter.compact` profile which env-var to read and,
|
|
7
|
+
* when that misses, ask the adapter for a vendor-specific fallback —
|
|
8
|
+
* no hard-coded IDE names. Per-adapter:
|
|
9
|
+
*
|
|
10
|
+
* - claude-code: its adapter-declared env-var (MVP) + a
|
|
11
|
+
* `readContextPercentFallback` that polls the statusline /
|
|
12
|
+
* transcript (see claude-code-adapter.ts).
|
|
13
|
+
* - trae / codex / cursor / qoder / tongyi-lingma / hermes /
|
|
14
|
+
* openclaw / zcode: each adapter fills its own env-var; until
|
|
15
|
+
* L2-dogfood verifies each surface, adapters may omit `compact`
|
|
16
|
+
* and the probe returns `source: 'conservative-fallback'`.
|
|
17
|
+
*
|
|
18
|
+
* Resolution order (user-overridden → env-var → adapter fallback →
|
|
19
|
+
* conservative-fallback):
|
|
20
|
+
* 1. `promptSizeBytes` (P0 `--prompt-size <bytes>` escape hatch) →
|
|
21
|
+
* `source: 'user-overridden'`.
|
|
22
|
+
* 2. `adapter.compact.envVarForContextPercent` env-var →
|
|
23
|
+
* `source: '<ideId>-env'`.
|
|
24
|
+
* 3. `adapter.compact.readContextPercentFallback?.(input)` — the
|
|
25
|
+
* adapter owns any vendor-specific statusline / transcript probe.
|
|
26
|
+
* 4. `ratio: 0` with `source: 'conservative-fallback'` — the
|
|
27
|
+
* orchestrator MUST NOT auto-fire compact on this signal.
|
|
28
|
+
*/
|
|
29
|
+
import { join } from 'node:path';
|
|
1
30
|
import { detectIdeFromEnv } from './ide-detect.js';
|
|
2
31
|
import { getAdapter } from '../ide/ide-registry.js';
|
|
3
32
|
import { readContextWindowTokensOverride } from '../config/config-service.js';
|
|
33
|
+
import { isHarnessWindowInRange, parseHarnessWindowTokens, readHarnessWindow, syncHarnessWindow } from './harness-window-config.js';
|
|
34
|
+
/**
|
|
35
|
+
* Resolve WHERE the harness auto-compact window lives, for the IDE the
|
|
36
|
+
* current env selects. Returns `null` when either half is undeclared:
|
|
37
|
+
* - the adapter exposes no `compact` profile, or
|
|
38
|
+
* - the adapter's `compact` profile declares no
|
|
39
|
+
* `autoCompactWindowEnvVar` (the IDE has no such knob), or
|
|
40
|
+
* - the adapter declares no machine-local settings layer.
|
|
41
|
+
*
|
|
42
|
+
* Both the path and the key name come from the adapter's OWN declarations,
|
|
43
|
+
* so this module names no IDE and no settings literal — the vendor-neutrality
|
|
44
|
+
* guard's shape-3 rule applies to every registry consumer, and this is one.
|
|
45
|
+
*
|
|
46
|
+
* Slice 2026-09-13-auto-compact-trigger-ownership.
|
|
47
|
+
*/
|
|
48
|
+
export function resolveHarnessWindowLocation(input) {
|
|
49
|
+
const env = input.env ?? process.env;
|
|
50
|
+
const detected = detectIdeFromEnv(env);
|
|
51
|
+
const ideId = (detected === 'unknown' ? 'claude-code' : detected);
|
|
52
|
+
const adapter = getAdapter(ideId);
|
|
53
|
+
const envVar = adapter.compact?.autoCompactWindowEnvVar;
|
|
54
|
+
const fileName = adapter.settings.localSettingsFileName ?? adapter.settings.settingsFileName;
|
|
55
|
+
if (envVar === undefined || fileName === undefined)
|
|
56
|
+
return null;
|
|
57
|
+
return {
|
|
58
|
+
settingsPath: join(input.projectRoot, adapter.settings.dirName, fileName),
|
|
59
|
+
envVar,
|
|
60
|
+
// Carried so the writer can refuse to target the user's own home directory
|
|
61
|
+
// (`--project .` from a fresh terminal): the path alone cannot say which
|
|
62
|
+
// root it came from, and this is the only place a location is built.
|
|
63
|
+
projectRoot: input.projectRoot
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Read the window peaks-loop last configured for the harness, plus whether
|
|
68
|
+
* the user has opted out of peaks-loop managing it. Returns `null` when the
|
|
69
|
+
* active adapter exposes no window knob at all.
|
|
70
|
+
*/
|
|
71
|
+
export function readHarnessWindowState(input) {
|
|
72
|
+
const location = resolveHarnessWindowLocation(input);
|
|
73
|
+
if (location === null)
|
|
74
|
+
return null;
|
|
75
|
+
return readHarnessWindow({ location, env: input.env ?? process.env });
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* The RAW window peaks-loop's context ratio must divide by — T1's number.
|
|
79
|
+
*
|
|
80
|
+
* The SETTINGS FILE is the source of truth, so its value wins whenever it has
|
|
81
|
+
* one; the env-first read is consulted only when the file is silent (no key
|
|
82
|
+
* yet, or the IDE injects the value from an outer layer).
|
|
83
|
+
*
|
|
84
|
+
* Why not "whatever the read returned" (the round-2 shape): the process env is
|
|
85
|
+
* a snapshot the harness froze at session start, so it is stale by exactly one
|
|
86
|
+
* write. Trusting it over the file is what let a stale env revert a human's
|
|
87
|
+
* hand-edited window (B1) and downgrade a 1M file to a 200K one (a second
|
|
88
|
+
* session's frozen env), and it is also what made T1's consistency depend on
|
|
89
|
+
* *rewriting the file* — the same unconditional write that destroyed the human's
|
|
90
|
+
* value. Dividing by the file instead makes peaks-loop's ratio and the harness's
|
|
91
|
+
* configured window one number BY CONSTRUCTION, with no write required. The
|
|
92
|
+
* write is then free to be gated on provenance (see `syncHarnessWindow`).
|
|
93
|
+
*
|
|
94
|
+
* Falls back to `undefined` (no harness layer at all) when the adapter
|
|
95
|
+
* declares no knob or neither side carries the key — the adapter then resolves
|
|
96
|
+
* its own window exactly as it did before this slice.
|
|
97
|
+
*/
|
|
98
|
+
export function resolveHarnessRatioWindow(state) {
|
|
99
|
+
if (state === null)
|
|
100
|
+
return undefined;
|
|
101
|
+
// E1 (rid 2026-09-13-defects-e): a value outside the band the harness accepts
|
|
102
|
+
// is not a window, however well-formed an integer it is — the harness ignores
|
|
103
|
+
// or caps it, so handing it over here would make the ratio's denominator a
|
|
104
|
+
// number the harness is not compacting on. This is the read half of the same
|
|
105
|
+
// guard the writer applies before planting one; without it, a value already on
|
|
106
|
+
// disk (hand-planted, or written by a release that predates the band) would
|
|
107
|
+
// keep poisoning the ratio on every probe.
|
|
108
|
+
//
|
|
109
|
+
// The two NULLs are deliberate and different: an out-of-band value is refused
|
|
110
|
+
// here (the adapter falls through to its own resolution and the writer's
|
|
111
|
+
// notice names the skipped number), while an UNPARSEABLE one is still handed
|
|
112
|
+
// over — the adapter validates it and warns about a hand-edited `500k`, which
|
|
113
|
+
// is a closer reading of the user's intent than silently ignoring it.
|
|
114
|
+
const raw = state.fileRaw !== undefined ? state.fileRaw : state.raw;
|
|
115
|
+
const tokens = parseHarnessWindowTokens(raw);
|
|
116
|
+
if (tokens !== null && !isHarnessWindowInRange(tokens))
|
|
117
|
+
return undefined;
|
|
118
|
+
return raw;
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Materialize a window into the harness's own settings, so the number
|
|
122
|
+
* peaks-loop divides by and the number the harness compacts against are one
|
|
123
|
+
* value rather than two resolutions (slice
|
|
124
|
+
* 2026-09-13-auto-compact-trigger-ownership).
|
|
125
|
+
*
|
|
126
|
+
* The caller passes the denominator it JUST USED (`probe.capacityTokens`), not
|
|
127
|
+
* a re-derived one — that is what makes the two sides structurally identical.
|
|
128
|
+
* `tokens: null` (a percent-only probe carries no token window) is a
|
|
129
|
+
* deliberate no-op: peaks-loop never invents a window it did not measure.
|
|
130
|
+
*
|
|
131
|
+
* Returns `null` when the active adapter declares no window knob at all.
|
|
132
|
+
*/
|
|
133
|
+
export function syncHarnessWindowForProject(input) {
|
|
134
|
+
const location = resolveHarnessWindowLocation(input);
|
|
135
|
+
if (location === null)
|
|
136
|
+
return null;
|
|
137
|
+
return syncHarnessWindow({ location, tokens: input.tokens, env: input.env ?? process.env });
|
|
138
|
+
}
|
|
4
139
|
/**
|
|
5
140
|
* Read env-var-based context percentage. Returns `null` when the env
|
|
6
141
|
* is absent or unparseable. Adapter-driven: the caller passes the
|
|
@@ -61,6 +196,17 @@ export function readContextPercent(input) {
|
|
|
61
196
|
};
|
|
62
197
|
}
|
|
63
198
|
if (adapter.compact) {
|
|
199
|
+
// Slice 2026-09-13-auto-compact-trigger-ownership: the window peaks-loop
|
|
200
|
+
// configured for the harness, read from the adapter's declared settings
|
|
201
|
+
// path + key. `null` (no knob declared, or the key absent) leaves the
|
|
202
|
+
// adapter's chain byte-identical to before this slice.
|
|
203
|
+
//
|
|
204
|
+
// The RAW value is handed over, not the parsed one: the adapter validates
|
|
205
|
+
// it (`parseContextWindowOverride`) and warns on a bad hand-edited value,
|
|
206
|
+
// so parsing here too would be a second, silently-disagreeing parser —
|
|
207
|
+
// exactly the duplicated-resolution shape this slice exists to delete.
|
|
208
|
+
const harnessWindowState = readHarnessWindowState({ projectRoot: input.projectRoot, env });
|
|
209
|
+
const harnessWindowTokens = resolveHarnessRatioWindow(harnessWindowState);
|
|
64
210
|
// Primary: read the adapter-declared env-var (no hard-coded IDE names).
|
|
65
211
|
const primary = readEnvPercent(env, adapter.compact.envVarForContextPercent);
|
|
66
212
|
if (primary !== null) {
|
|
@@ -85,7 +231,15 @@ export function readContextPercent(input) {
|
|
|
85
231
|
sessionId: input.sessionId,
|
|
86
232
|
outerSessionId: input.outerSessionId,
|
|
87
233
|
env,
|
|
88
|
-
configWindowTokens: readContextWindowTokensOverride(input.projectRoot)
|
|
234
|
+
configWindowTokens: readContextWindowTokensOverride(input.projectRoot),
|
|
235
|
+
// Slice 2026-09-13-auto-compact-trigger-ownership: hand the adapter the
|
|
236
|
+
// window peaks-loop configured for the harness, so the ratio it reports
|
|
237
|
+
// divides by the very number the harness compacts against — plus whether
|
|
238
|
+
// peaks-loop is the one that wrote it, so the adapter may self-correct
|
|
239
|
+
// its own earlier resolution without ever overruling a human's pin.
|
|
240
|
+
...(harnessWindowTokens !== undefined
|
|
241
|
+
? { harnessWindowTokens, harnessWindowPeakWritten: harnessWindowState?.peakWritten === true }
|
|
242
|
+
: {})
|
|
89
243
|
});
|
|
90
244
|
if (fallback)
|
|
91
245
|
return fallback;
|