peaks-loop 4.0.13 → 4.0.14
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 +12 -0
- package/dist/cli/commands/_register.js +2 -0
- package/dist/cli/commands/outer-cache-commands.d.ts +11 -0
- package/dist/cli/commands/outer-cache-commands.js +120 -0
- package/dist/services/session/session-binding-bridge.js +58 -5
- package/dist/services/skills/hooks-settings-service.js +22 -2
- package/dist/services/skills/outer-cache-hook-constants.d.ts +24 -0
- package/dist/services/skills/outer-cache-hook-constants.js +24 -0
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 4.0.14 — 2026-08-06 (outer-session cache + ensureSession meta over-coverage)
|
|
4
|
+
|
|
5
|
+
**Fixes the "5 terminals / 5 sessions stuck on 3fe1be" bug** (slice `2026-08-06-session-outer-cache-and-meta-coverage`, commit `f02a9b45`):
|
|
6
|
+
- `src/services/session/session-binding-bridge.ts` `getCurrentOuterSessionId(projectRoot)` now resolves `PEAKS_OUTER_SESSION_ID` → `CLAUDE_CODE_SESSION_ID` → file-cache at `.peaks/_runtime/.outer-session-cache.json` → `undefined`. Any IO error or non-string `outerSessionId` is a cache-miss (no throw). The cache file lives under the existing `.peaks/_runtime/` gitignore parent rule — no `.gitignore` modification.
|
|
7
|
+
- Same file: `ensureSession` early-return path now calls `setSessionMeta(projectRoot, existing.sessionId, { outerSessionId })` BEFORE returning. The on-disk `.peaks/_runtime/<sid>/session.json` always reflects the latest outerSessionId (not a stale value captured at first-bind time). All other meta fields (title / skill / mode / gate / createdAt) preserved via read-modify-write; `lastActivity` bumped to `now`.
|
|
8
|
+
- `src/services/skills/hooks-settings-service.ts` `resolveHookEntries('claude-code')` now appends a SessionStart entry that runs `peaks outer-cache write --project ...` to keep the cache in sync with the active Claude Code session. Uninstall strips the entry alongside the gate-enforce entry. Multi-binary drift guard (slice 2026-08-05) still fires correctly because the SessionStart entry uses the same `peaks` binary.
|
|
9
|
+
- New CLI: `peaks outer-cache write` (no flags) + `peaks outer-cache read` (returns `{ missing: true }` envelope on absent / malformed / IO error). Write exits 1 with `OUTER_CACHE_NO_ENV` when neither `PEAKS_OUTER_SESSION_ID` nor `CLAUDE_CODE_SESSION_ID` is set.
|
|
10
|
+
- Tests: `tests/unit/session/get-current-outer-session-id.test.ts` (10 cases pinning env > cache > undefined ordering, cache-miss / malformed JSON / IO error tolerance) + `tests/unit/session/ensure-session-meta-coverage.test.ts` (7 cases pinning AC8-AC11 over-coverage). Regression sweep: 179/179 PASS / 1 SKIP (pre-existing Win-only conditional).
|
|
11
|
+
- A.3 / A.4 / A.5 (workflowId by callerId + `session.json` migration + cross-outer rotation cases) explicitly reserved for a future session per PRD NG1-NG4.
|
|
12
|
+
|
|
13
|
+
**Lockstep bump.** peaks-loop-shared `0.0.43 → 0.0.44` (CLI_VERSION re-stamped to 4.0.14); peaks-loop-mut `0.1.16 → 0.1.17`; peaks-loop-shared-channel `0.0.20 → 0.0.21`.
|
|
14
|
+
|
|
3
15
|
## 4.0.13 — 2026-08-06 (statusline empty-render fix + sid-only marker + multi-binary drift guard)
|
|
4
16
|
|
|
5
17
|
**Statusline callerId fallback + active `[short-sid]` suffix** (slice `2026-08-05-statusline-empty-render-and-short-sid-suffix`, commit `4be37d08`):
|
|
@@ -36,6 +36,7 @@ import { registerWorkflowEvalCommands } from './loop-eval-commands.js';
|
|
|
36
36
|
import { registerMutCommands } from './mut-commands.js';
|
|
37
37
|
import { registerObservabilityCommands } from './observability-commands.js';
|
|
38
38
|
import { registerOpenSpecCommands } from './openspec-commands.js';
|
|
39
|
+
import { registerOuterCacheCommands } from './outer-cache-commands.js';
|
|
39
40
|
import { registerPerfAuditCommands } from './perf-audit-commands.js';
|
|
40
41
|
import { registerPerfCommands } from './perf-commands.js';
|
|
41
42
|
import { registerPlaywrightCommands } from './playwright-commands.js';
|
|
@@ -127,6 +128,7 @@ const REGISTRATIONS = [
|
|
|
127
128
|
['adapter-commands-s2a', registerAdapterS2ACommands], ['polyrepo-commands', registerPolyrepoCommands],
|
|
128
129
|
['worktree-auth-commands', registerWorktreeAuthCommand],
|
|
129
130
|
['session-spill-demo', registerSpillDemoCommand],
|
|
131
|
+
['outer-cache-commands', registerOuterCacheCommands],
|
|
130
132
|
];
|
|
131
133
|
function dispatchRegister(register, program, io) {
|
|
132
134
|
if (register.length <= 1) {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
import { type ProgramIO } from '../cli-helpers.js';
|
|
3
|
+
export type OuterCacheWriteOptions = {
|
|
4
|
+
readonly project?: string;
|
|
5
|
+
readonly json?: boolean;
|
|
6
|
+
};
|
|
7
|
+
export type OuterCacheReadOptions = {
|
|
8
|
+
readonly project?: string;
|
|
9
|
+
readonly json?: boolean;
|
|
10
|
+
};
|
|
11
|
+
export declare function registerOuterCacheCommands(program: Command, io: ProgramIO): void;
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slice 2026-08-06-session-outer-cache (G1 / G2) — per-project outer-session
|
|
3
|
+
* cache CLI surface.
|
|
4
|
+
*
|
|
5
|
+
* Writes and reads `.peaks/_runtime/.outer-session-cache.json` so that
|
|
6
|
+
* peaks CLI sub-processes (which typically do NOT inherit
|
|
7
|
+
* `CLAUDE_CODE_SESSION_ID` from the parent shell) can resolve the
|
|
8
|
+
* current outer session id via `getCurrentOuterSessionId(projectRoot)`
|
|
9
|
+
* in `src/services/session/session-binding-bridge.ts`.
|
|
10
|
+
*
|
|
11
|
+
* The SessionStart hook wired by `peaks hooks install` invokes
|
|
12
|
+
* `peaks outer-cache write` to keep this file in sync with the active
|
|
13
|
+
* Claude Code / Trae / IDE session. The file is under `.peaks/_runtime/`
|
|
14
|
+
* (gitignored), so no `.gitignore` change is required.
|
|
15
|
+
*
|
|
16
|
+
* Subcommand surface:
|
|
17
|
+
* - `peaks outer-cache write` (no flags) — read PEAKS_OUTER_SESSION_ID
|
|
18
|
+
* ?? CLAUDE_CODE_SESSION_ID, write the cache file, return JSON
|
|
19
|
+
* envelope with `{ outerSessionId, capturedAt, cachePath, written }`.
|
|
20
|
+
* - `peaks outer-cache read` — return
|
|
21
|
+
* `{ outerSessionId, capturedAt, cachePath }` when present or
|
|
22
|
+
* `{ missing: true, cachePath }` when not. Never throws on
|
|
23
|
+
* malformed JSON / IO error.
|
|
24
|
+
*
|
|
25
|
+
* Both subcommands accept `--project <path>` (defaults to cwd / git
|
|
26
|
+
* root) and `--json` (default in TTY-less invocations).
|
|
27
|
+
*/
|
|
28
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
29
|
+
import { dirname, join } from 'node:path';
|
|
30
|
+
import { fail, ok } from 'peaks-loop-shared/result';
|
|
31
|
+
import { addJsonOption, printResult } from '../cli-helpers.js';
|
|
32
|
+
import { findProjectRoot } from '../../services/config/config-safety.js';
|
|
33
|
+
const OUTER_SESSION_CACHE_REL = join('.peaks', '_runtime', '.outer-session-cache.json');
|
|
34
|
+
function resolveCachePath(projectRoot) {
|
|
35
|
+
return join(projectRoot, OUTER_SESSION_CACHE_REL);
|
|
36
|
+
}
|
|
37
|
+
function readEnvOuter() {
|
|
38
|
+
const peaks = process.env.PEAKS_OUTER_SESSION_ID;
|
|
39
|
+
if (typeof peaks === 'string' && peaks.length > 0)
|
|
40
|
+
return peaks;
|
|
41
|
+
const claude = process.env.CLAUDE_CODE_SESSION_ID;
|
|
42
|
+
if (typeof claude === 'string' && claude.length > 0)
|
|
43
|
+
return claude;
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
function readCacheFile(cachePath) {
|
|
47
|
+
if (!existsSync(cachePath))
|
|
48
|
+
return { ok: false, missing: true };
|
|
49
|
+
try {
|
|
50
|
+
const raw = readFileSync(cachePath, 'utf8');
|
|
51
|
+
const parsed = JSON.parse(raw);
|
|
52
|
+
if (parsed !== null &&
|
|
53
|
+
typeof parsed === 'object' &&
|
|
54
|
+
typeof parsed.outerSessionId === 'string' &&
|
|
55
|
+
(parsed.outerSessionId).length > 0 &&
|
|
56
|
+
typeof parsed.capturedAt === 'string') {
|
|
57
|
+
return {
|
|
58
|
+
ok: true,
|
|
59
|
+
outerSessionId: parsed.outerSessionId,
|
|
60
|
+
capturedAt: parsed.capturedAt
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
return { ok: false, missing: true };
|
|
64
|
+
}
|
|
65
|
+
catch { // TODO(g2): legacy silent catch — grace: 1 minor release (v2.14.0)
|
|
66
|
+
return { ok: false, missing: true };
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
export function registerOuterCacheCommands(program, io) {
|
|
70
|
+
const outerCache = program
|
|
71
|
+
.command('outer-cache')
|
|
72
|
+
.description('Read / write the per-project outer-session cache file (.peaks/_runtime/.outer-session-cache.json). The SessionStart hook installed by `peaks hooks install` keeps this file in sync with the live Claude Code / Trae / IDE session so peaks CLI sub-processes (which typically do NOT inherit CLAUDE_CODE_SESSION_ID) can resolve the current outer session via getCurrentOuterSessionId(projectRoot).');
|
|
73
|
+
addJsonOption(outerCache
|
|
74
|
+
.command('write')
|
|
75
|
+
.description('Write the current outer-session-id (PEAKS_OUTER_SESSION_ID ?? CLAUDE_CODE_SESSION_ID) into the per-project cache file. Idempotent: re-runs overwrite. Exits 1 with OUTER_CACHE_NO_ENV when neither env var is set.')
|
|
76
|
+
.option('--project <path>', 'target project root (defaults to git root or cwd)')).action((options) => {
|
|
77
|
+
const projectRoot = options.project ?? (findProjectRoot(process.cwd()) ?? process.cwd());
|
|
78
|
+
const cachePath = resolveCachePath(projectRoot);
|
|
79
|
+
const outerSessionId = readEnvOuter();
|
|
80
|
+
if (outerSessionId === undefined) {
|
|
81
|
+
printResult(io, fail('outer-cache.write', 'OUTER_CACHE_NO_ENV', 'Neither PEAKS_OUTER_SESSION_ID nor CLAUDE_CODE_SESSION_ID is set; nothing to write', { cachePath, written: false, projectRoot }, [
|
|
82
|
+
'Set PEAKS_OUTER_SESSION_ID=<id> or run from inside Claude Code / Trae / IDE so CLAUDE_CODE_SESSION_ID is exported',
|
|
83
|
+
'Re-run `peaks hooks install` to wire the SessionStart hook that writes this cache automatically'
|
|
84
|
+
]), options.json === true);
|
|
85
|
+
process.exitCode = 1;
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
const capturedAt = new Date().toISOString();
|
|
89
|
+
const payload = { outerSessionId, capturedAt };
|
|
90
|
+
try {
|
|
91
|
+
const dir = dirname(cachePath);
|
|
92
|
+
if (!existsSync(dir))
|
|
93
|
+
mkdirSync(dir, { recursive: true });
|
|
94
|
+
writeFileSync(cachePath, JSON.stringify(payload, null, 2) + '\n', 'utf8');
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
98
|
+
printResult(io, fail('outer-cache.write', 'OUTER_CACHE_WRITE_FAILED', `Failed to write outer-session cache: ${message}`, { cachePath, written: false, projectRoot, outerSessionId, capturedAt }, [message]), options.json === true);
|
|
99
|
+
process.exitCode = 1;
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
printResult(io, ok('outer-cache.write', { cachePath, written: true, projectRoot, outerSessionId, capturedAt }, [], [`Wrote ${cachePath}; getCurrentOuterSessionId() will resolve to "${outerSessionId}" until the next SessionStart fires.`]), options.json === true);
|
|
103
|
+
});
|
|
104
|
+
addJsonOption(outerCache
|
|
105
|
+
.command('read')
|
|
106
|
+
.description('Read the current value of the per-project outer-session cache. Returns { missing: true, cachePath } when the file is absent / malformed / empty; never throws on IO error.')
|
|
107
|
+
.option('--project <path>', 'target project root (defaults to git root or cwd)')).action((options) => {
|
|
108
|
+
const projectRoot = options.project ?? (findProjectRoot(process.cwd()) ?? process.cwd());
|
|
109
|
+
const cachePath = resolveCachePath(projectRoot);
|
|
110
|
+
const result = readCacheFile(cachePath);
|
|
111
|
+
if (result.ok) {
|
|
112
|
+
printResult(io, ok('outer-cache.read', { cachePath, missing: false, projectRoot, outerSessionId: result.outerSessionId, capturedAt: result.capturedAt }, [], [`outer-session-id resolved: ${result.outerSessionId} (captured ${result.capturedAt})`]), options.json === true);
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
printResult(io, ok('outer-cache.read', { cachePath, missing: true, projectRoot }, ['No outer-session cache present — getCurrentOuterSessionId() will return undefined for this project.'], [
|
|
116
|
+
'Re-run `peaks hooks install` to wire the SessionStart hook that writes this cache automatically',
|
|
117
|
+
'Or set PEAKS_OUTER_SESSION_ID=<id> so env-first resolution wins without touching the cache'
|
|
118
|
+
]), options.json === true);
|
|
119
|
+
});
|
|
120
|
+
}
|
|
@@ -23,11 +23,18 @@ import { dirname, join } from 'node:path';
|
|
|
23
23
|
import { randomBytes } from 'node:crypto';
|
|
24
24
|
import { initWorkspace } from '../workspace/workspace-service.js';
|
|
25
25
|
import { projectRootsMatch, stableRealPath } from '../../shared/path-utils.js';
|
|
26
|
-
import { getSessionId, getSessionIdCanonical, getSessionMeta, rotateSessionBinding } from './session-manager.js';
|
|
26
|
+
import { getSessionId, getSessionIdCanonical, getSessionMeta, rotateSessionBinding, setSessionMeta } from './session-manager.js';
|
|
27
27
|
// --- Lower-level helpers the bridge needs (moved verbatim) ---
|
|
28
28
|
const SESSION_FILE = join('_runtime', 'session.json');
|
|
29
29
|
const LEGACY_SESSION_FILE = '.session.json';
|
|
30
30
|
const META_FILE = 'session.json';
|
|
31
|
+
// Slice 2026-08-06-session-outer-cache: per-project file cache for the
|
|
32
|
+
// outer (Claude Code / Trae / IDE) session id. Written by the
|
|
33
|
+
// SessionStart hook via `peaks outer-cache write` so that peaks CLI
|
|
34
|
+
// sub-processes (which do NOT inherit CLAUDE_CODE_SESSION_ID) can still
|
|
35
|
+
// resolve the current outer session. Lives under the gitignored
|
|
36
|
+
// `.peaks/_runtime/` tree, so no .gitignore change is required.
|
|
37
|
+
const OUTER_SESSION_CACHE_FILE = join('_runtime', '.outer-session-cache.json');
|
|
31
38
|
function getLegacySessionFilePath(projectRoot) {
|
|
32
39
|
return join(projectRoot, '.peaks', LEGACY_SESSION_FILE);
|
|
33
40
|
}
|
|
@@ -124,18 +131,58 @@ function writeSessionMeta(projectRoot, sessionId, meta) {
|
|
|
124
131
|
}
|
|
125
132
|
writeFileSync(metaPath, JSON.stringify(meta, null, 2), 'utf8');
|
|
126
133
|
}
|
|
127
|
-
function getCurrentOuterSessionId() {
|
|
134
|
+
function getCurrentOuterSessionId(projectRoot) {
|
|
128
135
|
const peaks = process.env.PEAKS_OUTER_SESSION_ID;
|
|
129
136
|
if (typeof peaks === 'string' && peaks.length > 0)
|
|
130
137
|
return peaks;
|
|
131
138
|
const claude = process.env.CLAUDE_CODE_SESSION_ID;
|
|
132
139
|
if (typeof claude === 'string' && claude.length > 0)
|
|
133
140
|
return claude;
|
|
141
|
+
// Slice 2026-08-06-session-outer-cache (G1): when the peaks CLI runs
|
|
142
|
+
// as a sub-process of Claude Code (or any IDE that does not export
|
|
143
|
+
// CLAUDE_CODE_SESSION_ID into the child env), the env vars above are
|
|
144
|
+
// undefined. Fall back to the per-project file cache written by the
|
|
145
|
+
// SessionStart hook via `peaks outer-cache write`. The file lives
|
|
146
|
+
// under `.peaks/_runtime/` (gitignored) so no .gitignore change is
|
|
147
|
+
// required. Any IO error or non-string `outerSessionId` field is
|
|
148
|
+
// treated as a cache miss — never throw.
|
|
149
|
+
if (projectRoot !== undefined) {
|
|
150
|
+
const cachePath = join(projectRoot, '.peaks', OUTER_SESSION_CACHE_FILE);
|
|
151
|
+
if (existsSync(cachePath)) {
|
|
152
|
+
try {
|
|
153
|
+
const raw = readFileSync(cachePath, 'utf8');
|
|
154
|
+
const parsed = JSON.parse(raw);
|
|
155
|
+
if (parsed !== null &&
|
|
156
|
+
typeof parsed === 'object' &&
|
|
157
|
+
typeof parsed.outerSessionId === 'string' &&
|
|
158
|
+
(parsed.outerSessionId).length > 0) {
|
|
159
|
+
return parsed.outerSessionId;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
catch { // TODO(g2): legacy silent catch — grace: 1 minor release (v2.14.0)
|
|
163
|
+
// fall through — file missing / malformed JSON / IO error → undefined
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
134
167
|
return undefined;
|
|
135
168
|
}
|
|
136
169
|
export async function ensureSession(projectRoot) {
|
|
137
170
|
const existing = readSessionFile(projectRoot);
|
|
138
171
|
if (existing) {
|
|
172
|
+
// Slice 2026-08-06-session-outer-cache (G3 / AC8-AC11): on every
|
|
173
|
+
// already-bound invocation, stamp the current outer-session-id
|
|
174
|
+
// onto the bound session's meta so the on-disk
|
|
175
|
+
// `.peaks/_runtime/<sid>/session.json` always reflects the latest
|
|
176
|
+
// outer signal — not a stale value captured at session creation
|
|
177
|
+
// time. `setSessionMeta` is read-modify-write: every other field
|
|
178
|
+
// (title / skill / mode / gate / createdAt / lastActivity) is
|
|
179
|
+
// preserved. `getCurrentOuterSessionId(projectRoot)` reads env
|
|
180
|
+
// → file-cache → undefined; an undefined value is NOT written,
|
|
181
|
+
// so the previously-stamped outer (if any) survives.
|
|
182
|
+
const outerSessionId = getCurrentOuterSessionId(projectRoot);
|
|
183
|
+
if (outerSessionId !== undefined) {
|
|
184
|
+
setSessionMeta(projectRoot, existing.sessionId, { outerSessionId });
|
|
185
|
+
}
|
|
139
186
|
return existing.sessionId;
|
|
140
187
|
}
|
|
141
188
|
// Slice 007 — sub-agent session sharing. When the strict-equality
|
|
@@ -165,8 +212,11 @@ export async function ensureSession(projectRoot) {
|
|
|
165
212
|
};
|
|
166
213
|
writeSessionFile(projectRoot, info);
|
|
167
214
|
await initWorkspace({ projectRoot, sessionId });
|
|
168
|
-
// Initialize session metadata inside the session directory
|
|
169
|
-
|
|
215
|
+
// Initialize session metadata inside the session directory.
|
|
216
|
+
// Slice 2026-08-06-session-outer-cache (G1): pass `projectRoot` so
|
|
217
|
+
// the env → file-cache → undefined resolution chain also reads the
|
|
218
|
+
// SessionStart-written cache on first bind.
|
|
219
|
+
const outerSessionId = getCurrentOuterSessionId(projectRoot);
|
|
170
220
|
writeSessionMeta(projectRoot, sessionId, {
|
|
171
221
|
sessionId,
|
|
172
222
|
projectRoot,
|
|
@@ -211,7 +261,10 @@ export async function ensureSession(projectRoot) {
|
|
|
211
261
|
*/
|
|
212
262
|
export async function ensureSessionWithRotation(projectRoot, options) {
|
|
213
263
|
const skipRotate = options?.skipRotateOnOuterMismatch === true;
|
|
214
|
-
|
|
264
|
+
// Slice 2026-08-06-session-outer-cache (G1): pass `projectRoot` so
|
|
265
|
+
// the rotation-decision comparison sees the SessionStart cache
|
|
266
|
+
// when the env vars are unset (sub-process / nested invocation).
|
|
267
|
+
const currentOuterSessionId = getCurrentOuterSessionId(projectRoot);
|
|
215
268
|
// Compute the rotation decision up front. We only rotate when ALL
|
|
216
269
|
// three pre-conditions hold: (a) the current outer session id is
|
|
217
270
|
// defined, (b) the bound session has a recorded outer session id,
|
|
@@ -4,6 +4,7 @@ import { resolve } from 'node:path';
|
|
|
4
4
|
import { assertSafeSettingsFile } from '../ide/shared/safe-path.js';
|
|
5
5
|
import { atomicWriteJson, readJsonObjectFile } from '../ide/shared/atomic-json.js';
|
|
6
6
|
import { getAdapter } from '../ide/ide-registry.js';
|
|
7
|
+
import { HOOK_OUTER_CACHE_COMMAND, HOOK_OUTER_CACHE_EVENT, HOOK_OUTER_CACHE_SENTINEL } from './outer-cache-hook-constants.js';
|
|
7
8
|
// --- Module-level defaults (claude-code) -----------------------------------
|
|
8
9
|
// These exports remain for backward compat — tests and downstream callers
|
|
9
10
|
// that only care about Claude Code can keep importing them. The per-IDE
|
|
@@ -181,12 +182,25 @@ export function readInstalledEntriesFromSettings(settings, ide) {
|
|
|
181
182
|
* entry's sentinel is included in the legacy sentinel set so uninstall
|
|
182
183
|
* can find + remove any stale progress-start entry that an older
|
|
183
184
|
* `peaks hooks install` may have written before this slice.
|
|
185
|
+
*
|
|
186
|
+
* Slice 2026-08-06-session-outer-cache: a SessionStart outer-cache primer
|
|
187
|
+
* entry is appended for Claude Code only (SessionStart is a Claude-Code-
|
|
188
|
+
* specific event key today).
|
|
184
189
|
*/
|
|
185
190
|
function resolveHookEntries(ide, _skipProgress = false) {
|
|
186
191
|
const spec = resolveHookSpec(ide);
|
|
187
|
-
|
|
192
|
+
const entries = [
|
|
188
193
|
{ sentinel: spec.hookEnforceSentinel, matcher: spec.hookEnforceMatcher, command: spec.hookEnforceCommand, event: spec.hookEnforceEvent }
|
|
189
194
|
];
|
|
195
|
+
if (ide === 'claude-code') {
|
|
196
|
+
entries.push({
|
|
197
|
+
sentinel: HOOK_OUTER_CACHE_SENTINEL,
|
|
198
|
+
matcher: '',
|
|
199
|
+
command: HOOK_OUTER_CACHE_COMMAND,
|
|
200
|
+
event: HOOK_OUTER_CACHE_EVENT
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
return entries;
|
|
190
204
|
}
|
|
191
205
|
/**
|
|
192
206
|
* Legacy sentinel set used by uninstall + status to find and remove stale
|
|
@@ -198,7 +212,13 @@ function resolveLegacySentinels(ide) {
|
|
|
198
212
|
if (ide === 'trae') {
|
|
199
213
|
return ['peaks hook handle', LEGACY_PROGRESS_START_SENTINEL];
|
|
200
214
|
}
|
|
201
|
-
|
|
215
|
+
// Slice 2026-08-06-session-outer-cache: include the SessionStart outer-cache
|
|
216
|
+
// sentinel so uninstall strips it alongside the gate-enforce entry.
|
|
217
|
+
const base = [HOOK_ENFORCE_SENTINEL, LEGACY_PROGRESS_START_SENTINEL];
|
|
218
|
+
if (ide === 'claude-code') {
|
|
219
|
+
return [...base, HOOK_OUTER_CACHE_SENTINEL];
|
|
220
|
+
}
|
|
221
|
+
return base;
|
|
202
222
|
}
|
|
203
223
|
// --- Slice 2026-07-29-worktree-layer3-deny -----------------------------------
|
|
204
224
|
// Layer 3 of the Worktree Governance 3-layer design (see
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slice 2026-08-06-session-outer-cache (G2): per-IDE SessionStart hook
|
|
3
|
+
* constants for the outer-session cache primer. Extracted from
|
|
4
|
+
* `hooks-settings-service.ts` to keep that file under the Karpathy
|
|
5
|
+
* 800 LOC cap.
|
|
6
|
+
*
|
|
7
|
+
* The SessionStart hook is fired by Claude Code (and any future IDE
|
|
8
|
+
* that ships a SessionStart-style event) every time a new outer
|
|
9
|
+
* session begins. It runs `peaks outer-cache write`, which reads
|
|
10
|
+
* PEAKS_OUTER_SESSION_ID ?? CLAUDE_CODE_SESSION_ID from env and
|
|
11
|
+
* persists it to `.peaks/_runtime/.outer-session-cache.json` so the
|
|
12
|
+
* next peaks CLI sub-process can resolve the current outer session
|
|
13
|
+
* via `getCurrentOuterSessionId(projectRoot)`.
|
|
14
|
+
*
|
|
15
|
+
* Only Claude Code supports SessionStart today; future adapters opt
|
|
16
|
+
* in by extending `resolveHookEntries` and adding an adapter-driven
|
|
17
|
+
* substitution for the `${...}` project-dir placeholder.
|
|
18
|
+
*/
|
|
19
|
+
/** Sentinel substring identifying a SessionStart outer-cache hook entry. */
|
|
20
|
+
export declare const HOOK_OUTER_CACHE_SENTINEL = "peaks outer-cache write";
|
|
21
|
+
/** Default (claude-code) SessionStart hook command. */
|
|
22
|
+
export declare const HOOK_OUTER_CACHE_COMMAND = "peaks outer-cache write --project \"${CLAUDE_PROJECT_DIR}\"";
|
|
23
|
+
/** SessionStart hook event key for Claude Code. */
|
|
24
|
+
export declare const HOOK_OUTER_CACHE_EVENT = "SessionStart";
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slice 2026-08-06-session-outer-cache (G2): per-IDE SessionStart hook
|
|
3
|
+
* constants for the outer-session cache primer. Extracted from
|
|
4
|
+
* `hooks-settings-service.ts` to keep that file under the Karpathy
|
|
5
|
+
* 800 LOC cap.
|
|
6
|
+
*
|
|
7
|
+
* The SessionStart hook is fired by Claude Code (and any future IDE
|
|
8
|
+
* that ships a SessionStart-style event) every time a new outer
|
|
9
|
+
* session begins. It runs `peaks outer-cache write`, which reads
|
|
10
|
+
* PEAKS_OUTER_SESSION_ID ?? CLAUDE_CODE_SESSION_ID from env and
|
|
11
|
+
* persists it to `.peaks/_runtime/.outer-session-cache.json` so the
|
|
12
|
+
* next peaks CLI sub-process can resolve the current outer session
|
|
13
|
+
* via `getCurrentOuterSessionId(projectRoot)`.
|
|
14
|
+
*
|
|
15
|
+
* Only Claude Code supports SessionStart today; future adapters opt
|
|
16
|
+
* in by extending `resolveHookEntries` and adding an adapter-driven
|
|
17
|
+
* substitution for the `${...}` project-dir placeholder.
|
|
18
|
+
*/
|
|
19
|
+
/** Sentinel substring identifying a SessionStart outer-cache hook entry. */
|
|
20
|
+
export const HOOK_OUTER_CACHE_SENTINEL = 'peaks outer-cache write';
|
|
21
|
+
/** Default (claude-code) SessionStart hook command. */
|
|
22
|
+
export const HOOK_OUTER_CACHE_COMMAND = `peaks outer-cache write --project "\${CLAUDE_PROJECT_DIR}"`;
|
|
23
|
+
/** SessionStart hook event key for Claude Code. */
|
|
24
|
+
export const HOOK_OUTER_CACHE_EVENT = 'SessionStart';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "peaks-loop",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.14",
|
|
4
4
|
"description": "Loop Engineering CLI — workflow primitive / loop guards / evaluators / slice orchestration",
|
|
5
5
|
"author": "SquabbyZ",
|
|
6
6
|
"keywords": [
|
|
@@ -100,9 +100,9 @@
|
|
|
100
100
|
"headroom-ai": "0.22.4",
|
|
101
101
|
"yaml": "^2.9.0",
|
|
102
102
|
"zod": "^3.25.76",
|
|
103
|
-
"peaks-loop-shared": "0.0.
|
|
104
|
-
"peaks-loop-
|
|
105
|
-
"peaks-loop-
|
|
103
|
+
"peaks-loop-shared": "0.0.45",
|
|
104
|
+
"peaks-loop-shared-channel": "0.0.22",
|
|
105
|
+
"peaks-loop-mut": "0.1.18"
|
|
106
106
|
},
|
|
107
107
|
"peerDependencies": {
|
|
108
108
|
"@alibaba-group/open-code-review": "1.3.1"
|