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
|
@@ -12,6 +12,55 @@
|
|
|
12
12
|
// read.
|
|
13
13
|
import { existsSync, readFileSync } from 'node:fs';
|
|
14
14
|
import { join } from 'node:path';
|
|
15
|
+
/**
|
|
16
|
+
* Pair each dispatched compact with the measurement that followed it. Pure;
|
|
17
|
+
* tolerates a file written by an earlier release (no `windowTokens`, no
|
|
18
|
+
* `kind`) — those rows produce a pair with a null window rather than an
|
|
19
|
+
* error, because the history file is append-only and never rewritten.
|
|
20
|
+
*/
|
|
21
|
+
export function computeWindowCalibration(events) {
|
|
22
|
+
const pairs = [];
|
|
23
|
+
for (const event of events) {
|
|
24
|
+
const kind = event.kind ?? 'dispatch';
|
|
25
|
+
if (kind === 'observed') {
|
|
26
|
+
// Attach to the most recent dispatch still waiting for a measurement.
|
|
27
|
+
for (let i = pairs.length - 1; i >= 0; i -= 1) {
|
|
28
|
+
const candidate = pairs[i];
|
|
29
|
+
if (candidate.observedRatio !== null || typeof event.afterRatio !== 'number')
|
|
30
|
+
continue;
|
|
31
|
+
const observedTokens = candidate.windowTokens === null ? null : event.afterRatio * candidate.windowTokens;
|
|
32
|
+
pairs[i] = {
|
|
33
|
+
...candidate,
|
|
34
|
+
observedRatio: event.afterRatio,
|
|
35
|
+
observedTokens,
|
|
36
|
+
driftTokens: observedTokens === null || candidate.requestedTokens === null
|
|
37
|
+
? null
|
|
38
|
+
: observedTokens - candidate.requestedTokens,
|
|
39
|
+
measured: true
|
|
40
|
+
};
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const windowTokens = typeof event.windowTokens === 'number' ? event.windowTokens : null;
|
|
46
|
+
pairs.push({
|
|
47
|
+
dispatchedAt: event.ts,
|
|
48
|
+
windowTokens,
|
|
49
|
+
windowSource: event.windowSource ?? null,
|
|
50
|
+
requestedRatio: event.beforeRatio,
|
|
51
|
+
requestedTokens: windowTokens === null ? null : event.beforeRatio * windowTokens,
|
|
52
|
+
observedRatio: null,
|
|
53
|
+
observedTokens: null,
|
|
54
|
+
driftTokens: null,
|
|
55
|
+
measured: false
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
pairs,
|
|
60
|
+
unmeasured: pairs.filter((p) => !p.measured).length,
|
|
61
|
+
lastWindowTokens: pairs.length === 0 ? null : pairs[pairs.length - 1].windowTokens
|
|
62
|
+
};
|
|
63
|
+
}
|
|
15
64
|
export function readCompactHistory(input) {
|
|
16
65
|
const path = join(input.projectRoot, '.peaks', '_runtime', input.sessionId, 'compact-history.jsonl');
|
|
17
66
|
if (!existsSync(path)) {
|
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
export type CompactLifecycleStage = 'queued' | 'preparing' | 'compacting'
|
|
1
|
+
export type CompactLifecycleStage = 'queued' | 'preparing' | 'compacting'
|
|
2
|
+
/**
|
|
3
|
+
* Slice 2026-09-12-compact-band-policy: a compact trigger was
|
|
4
|
+
* REGISTERED but no compaction has started — e.g. claude-code's
|
|
5
|
+
* `ide-native` pathway only installs the PreToolUse hook, which
|
|
6
|
+
* compacts in-band at ratio ≥ 0.95. In the 0.80–0.95 band nothing
|
|
7
|
+
* is in flight, so claiming `compacting` was a false heartbeat.
|
|
8
|
+
*/
|
|
9
|
+
| 'armed' | 'verifying' | 'completed' | 'failed';
|
|
2
10
|
export interface CompactLifecycleRecord {
|
|
3
11
|
readonly schemaVersion: 1;
|
|
4
12
|
readonly runId: string;
|
|
@@ -49,7 +57,8 @@ export declare function writeCompactLifecycle(input: {
|
|
|
49
57
|
* Return kinds:
|
|
50
58
|
* - `missing` — no record has ever been written for this session
|
|
51
59
|
* - `valid` — record parsed and validated; stage is terminal
|
|
52
|
-
* (completed/failed) OR is active
|
|
60
|
+
* (completed/failed), resting (`armed`) OR is active
|
|
61
|
+
* but still fresh
|
|
53
62
|
* - `invalid` — file exists but is malformed / wrong schema / out
|
|
54
63
|
* of range. `reason` is a single-line English message
|
|
55
64
|
* (no CLI verbs, no stack traces). The caller MUST
|
|
@@ -19,11 +19,27 @@
|
|
|
19
19
|
// compacting / verifying) is `stalled` when (nowMs - updatedAt) >
|
|
20
20
|
// staleAfterMs. Terminal stages (`completed`, `failed`) NEVER go
|
|
21
21
|
// stalled — the record is the historical answer, not a heartbeat.
|
|
22
|
+
//
|
|
23
|
+
// Slice 2026-09-12-compact-band-policy (defect B): `armed` is a third
|
|
24
|
+
// category — a RESTING stage. It records a fact ("we registered a
|
|
25
|
+
// compact trigger") that stays true until a measurement supersedes it;
|
|
26
|
+
// no heartbeat was ever promised, so waiting in it is NORMAL and it
|
|
27
|
+
// must never be reported as `stalled`. Only `compacting` means "a
|
|
28
|
+
// compaction should be in flight right now", which is the only state
|
|
29
|
+
// where a missing heartbeat is real evidence of a stall.
|
|
22
30
|
import { existsSync, mkdirSync, readFileSync, renameSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
23
31
|
import { dirname, join } from 'node:path';
|
|
24
32
|
import { getSessionDir } from '../session/getSessionDir.js';
|
|
25
33
|
const LIFECYCLE_FILENAME = 'compact-lifecycle.json';
|
|
26
34
|
const ERROR_SUMMARY_MAX = 160;
|
|
35
|
+
/**
|
|
36
|
+
* Stages that promise a heartbeat: each one is only written while the
|
|
37
|
+
* dispatching process (or the IDE it armed) is expected to make
|
|
38
|
+
* progress, so a stale `updatedAt` is real evidence of a stall.
|
|
39
|
+
*
|
|
40
|
+
* `armed` is deliberately NOT here — see the header. It is a resting
|
|
41
|
+
* state, not a heartbeat.
|
|
42
|
+
*/
|
|
27
43
|
const ACTIVE_STAGES = [
|
|
28
44
|
'queued',
|
|
29
45
|
'preparing',
|
|
@@ -34,6 +50,7 @@ const ALL_STAGES = [
|
|
|
34
50
|
'queued',
|
|
35
51
|
'preparing',
|
|
36
52
|
'compacting',
|
|
53
|
+
'armed',
|
|
37
54
|
'verifying',
|
|
38
55
|
'completed',
|
|
39
56
|
'failed',
|
|
@@ -169,7 +186,8 @@ export function writeCompactLifecycle(input) {
|
|
|
169
186
|
* Return kinds:
|
|
170
187
|
* - `missing` — no record has ever been written for this session
|
|
171
188
|
* - `valid` — record parsed and validated; stage is terminal
|
|
172
|
-
* (completed/failed) OR is active
|
|
189
|
+
* (completed/failed), resting (`armed`) OR is active
|
|
190
|
+
* but still fresh
|
|
173
191
|
* - `invalid` — file exists but is malformed / wrong schema / out
|
|
174
192
|
* of range. `reason` is a single-line English message
|
|
175
193
|
* (no CLI verbs, no stack traces). The caller MUST
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type CompactLifecycleStage } from './compact-lifecycle-store.js';
|
|
2
|
-
export type CompactDisplayKind = 'none' | 'queued' | 'preparing' | 'compacting' | 'verifying' | 'completed' | 'failed' | 'stalled' | 'invalid';
|
|
2
|
+
export type CompactDisplayKind = 'none' | 'queued' | 'preparing' | 'compacting' | 'verifying' | 'completed' | 'failed' | 'stalled' | 'invalid' | 'armed';
|
|
3
3
|
export interface CompactStatuslineState {
|
|
4
4
|
readonly kind: CompactDisplayKind;
|
|
5
5
|
readonly filledCells: 0 | 2 | 4 | 6 | 8;
|
|
@@ -28,6 +28,9 @@
|
|
|
28
28
|
// none → 0 cells
|
|
29
29
|
// invalid → 0 cells (no false reassurance)
|
|
30
30
|
// stalled → keep the active stage's cell
|
|
31
|
+
// armed → NO bar at all (slice 2026-09-12-compact-band-policy):
|
|
32
|
+
// a registered-but-idle trigger has no progress to
|
|
33
|
+
// report, and a bar would imply one.
|
|
31
34
|
//
|
|
32
35
|
// Render contract: the rendered label is a fixed-width 8-cell bar
|
|
33
36
|
// (`[████░░░░]` filled from the left). NO `?` characters. NO guessed
|
|
@@ -36,6 +39,7 @@
|
|
|
36
39
|
// measurement" hint.
|
|
37
40
|
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
38
41
|
import { getSessionDir } from '../session/getSessionDir.js';
|
|
42
|
+
import { AUTO_COMPACT_RED_LINE_RATIO } from '../context/auto-compact-types.js';
|
|
39
43
|
import { readCompactLifecycle, } from './compact-lifecycle-store.js';
|
|
40
44
|
/**
|
|
41
45
|
* Concrete first-version stale timeout. Adjustable after real timing
|
|
@@ -70,6 +74,8 @@ const CELL_BY_STAGE = new Map([
|
|
|
70
74
|
['verifying', STAGE_CELL_VERIFYING],
|
|
71
75
|
['completed', STAGE_CELL_COMPLETED],
|
|
72
76
|
['failed', STAGE_CELL_COMPACTING],
|
|
77
|
+
// `armed` is not in the cell table: it renders WITHOUT a bar (see
|
|
78
|
+
// renderCompactStatusline). Lookups therefore miss and fall back to 0.
|
|
73
79
|
]);
|
|
74
80
|
const FILLED = '█';
|
|
75
81
|
const EMPTY = '░';
|
|
@@ -245,6 +251,12 @@ export function renderCompactStatusline(state) {
|
|
|
245
251
|
return `compact ${renderBar(2)}`;
|
|
246
252
|
case 'compacting':
|
|
247
253
|
return `compact ${renderBar(STAGE_CELL_COMPACTING)}`;
|
|
254
|
+
// Slice 2026-09-12-compact-band-policy: a registered trigger that has
|
|
255
|
+
// not fired is NOT progress. Rendering a bar would imply movement
|
|
256
|
+
// that is not happening, so `armed` deliberately renders no bar at
|
|
257
|
+
// all — the label states what is true and what would change it.
|
|
258
|
+
case 'armed':
|
|
259
|
+
return `compact armed${formatArmedRatio(state)} — fires in-band at ${arRedLinePct()}`;
|
|
248
260
|
case 'verifying':
|
|
249
261
|
return `compact ${renderBar(STAGE_CELL_VERIFYING)}`;
|
|
250
262
|
case 'completed':
|
|
@@ -257,6 +269,16 @@ export function renderCompactStatusline(state) {
|
|
|
257
269
|
return formatInvalid(state);
|
|
258
270
|
}
|
|
259
271
|
}
|
|
272
|
+
/** The red-line ratio the armed trigger waits for, as a display percentage. */
|
|
273
|
+
function arRedLinePct() {
|
|
274
|
+
return `${Math.round(AUTO_COMPACT_RED_LINE_RATIO * 100)}%`;
|
|
275
|
+
}
|
|
276
|
+
/** ` (88% now)` when the opening trigger ratio is known; empty otherwise. */
|
|
277
|
+
function formatArmedRatio(state) {
|
|
278
|
+
return typeof state.triggerRatio === 'number'
|
|
279
|
+
? ` (${Math.round(state.triggerRatio * 100)}% now)`
|
|
280
|
+
: '';
|
|
281
|
+
}
|
|
260
282
|
function formatCompleted(state) {
|
|
261
283
|
const bar = renderBar(STAGE_CELL_COMPLETED);
|
|
262
284
|
if (typeof state.afterRatio === 'number') {
|
|
@@ -51,6 +51,58 @@ export declare class InvalidProjectRootError extends Error {
|
|
|
51
51
|
constructor(reason: 'empty' | 'nul-byte' | 'non-existent' | 'non-canonical', input: string);
|
|
52
52
|
}
|
|
53
53
|
export declare function resolveCanonicalProjectRootStrict(startPath: string): string;
|
|
54
|
+
/**
|
|
55
|
+
* True when `projectRoot` IS the user's home directory itself.
|
|
56
|
+
*
|
|
57
|
+
* Exact match, NOT `isInsidePath`: `~/my-project` is an ordinary project and
|
|
58
|
+
* must keep working — the guard exists for the one directory that is never a
|
|
59
|
+
* project.
|
|
60
|
+
*
|
|
61
|
+
* `projectRootsMatch` rather than `===` because the two strings reach this
|
|
62
|
+
* function from different sources: a `--project .` argument arrives in the
|
|
63
|
+
* caller's spelling while `homedir()` yields the OS form (`C:\Users\x` vs
|
|
64
|
+
* `C:/Users/x`, and differing case on win32/darwin).
|
|
65
|
+
*
|
|
66
|
+
* This is the ONE definition of "the user's home" in the codebase; the
|
|
67
|
+
* harness-window writer's own guard delegates here so the two cannot drift.
|
|
68
|
+
*/
|
|
69
|
+
export declare function isUserHomeProjectRoot(projectRoot: string): boolean;
|
|
70
|
+
/**
|
|
71
|
+
* Thrown by `assertWritableProjectRoot` / `resolveWritableProjectRoot` when a
|
|
72
|
+
* write would land in the user's home directory. Distinct from
|
|
73
|
+
* `InvalidProjectRootError` (which is about a malformed/absent path): this path
|
|
74
|
+
* is perfectly valid — it is just not a project.
|
|
75
|
+
*/
|
|
76
|
+
export declare class UnsafeProjectRootError extends Error {
|
|
77
|
+
readonly projectRoot: string;
|
|
78
|
+
constructor(projectRoot: string);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Refuse a project root that IS the user's home directory.
|
|
82
|
+
*
|
|
83
|
+
* The hazard this closes: a fresh terminal starts in `$HOME`, and
|
|
84
|
+
* `peaks workspace init --project .` there resolved to `$HOME` (via
|
|
85
|
+
* `resolveCanonicalProjectRoot`'s fail-open fallback, which returns the start
|
|
86
|
+
* path when it can find no project marker) and then materialized a whole
|
|
87
|
+
* project tree INTO the user's home — `.claude/settings.local.json` above all,
|
|
88
|
+
* which is a live config file shared with every other project on the machine.
|
|
89
|
+
*
|
|
90
|
+
* Deliberately NOT enforced inside `resolveCanonicalProjectRoot`. That helper
|
|
91
|
+
* is called from 85 sites across 32 files, most of them READS (`peaks status`,
|
|
92
|
+
* `peaks dashboard`, `peaks memory`, the observability commands). Refusing
|
|
93
|
+
* there would break every one of them the moment a user runs a read command
|
|
94
|
+
* from their home directory, and its fail-open contract is documented and
|
|
95
|
+
* depended on. The hazard is a WRITE to `$HOME/.claude/`, so the guard belongs
|
|
96
|
+
* on write paths — this function, plus `resolveWritableProjectRoot` so a
|
|
97
|
+
* writer only has to make one call.
|
|
98
|
+
*/
|
|
99
|
+
export declare function assertWritableProjectRoot(projectRoot: string): void;
|
|
100
|
+
/**
|
|
101
|
+
* `resolveCanonicalProjectRoot` for a path that is about to be WRITTEN to:
|
|
102
|
+
* same git-root-then-heuristic promotion, plus the home-directory refusal.
|
|
103
|
+
* Read paths keep using the fail-open helper.
|
|
104
|
+
*/
|
|
105
|
+
export declare function resolveWritableProjectRoot(startPath: string): string;
|
|
54
106
|
export declare function getProjectConfigPath(projectRoot: string | null): string | null;
|
|
55
107
|
export declare function getProjectBootstrapConfigPath(projectRoot: string): string;
|
|
56
108
|
export declare function validateProjectBootstrapConfigPathForWrite(projectRoot: string, configPath: string): void;
|
|
@@ -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%.`
|