peaks-loop 4.0.36 → 4.0.37
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 +24 -0
- package/README-en.md +1 -1
- package/README.md +1 -1
- package/bin/peaks.js +71 -1
- package/dist/cli/cli-helpers.js +7 -0
- package/dist/cli/commands/_register.js +2 -0
- package/dist/cli/commands/best-practice-scan-command.d.ts +14 -1
- package/dist/cli/commands/best-practice-scan-command.js +67 -9
- package/dist/cli/commands/code-runtime-commands.js +21 -5
- package/dist/cli/commands/hooks-commands.js +10 -1
- package/dist/cli/commands/job-commands.js +107 -25
- package/dist/cli/commands/scan-commands.js +1 -1
- package/dist/cli/commands/web-commands.d.ts +28 -0
- package/dist/cli/commands/web-commands.js +327 -0
- package/dist/cli/commands/web-lifecycle-commands.d.ts +49 -0
- package/dist/cli/commands/web-lifecycle-commands.js +321 -0
- package/dist/services/best-practice/scan-orchestrator.d.ts +22 -0
- package/dist/services/best-practice/scan-orchestrator.js +14 -5
- package/dist/services/code/orchestrator-can-do.js +27 -4
- package/dist/services/context/context-audit-hint.d.ts +79 -0
- package/dist/services/context/context-audit-hint.js +150 -0
- package/dist/services/hooks/auto-compact-hook-install.js +10 -1
- package/dist/services/hooks/write-gate.js +88 -0
- package/dist/services/lint/detect-eslint.d.ts +2 -0
- package/dist/services/lint/detect-eslint.js +23 -9
- package/dist/services/lint/npx-resolver.d.ts +6 -0
- package/dist/services/lint/npx-resolver.js +38 -14
- package/dist/services/release/version-precheck-service.js +9 -2
- package/dist/services/scan/file-size-scan.d.ts +29 -0
- package/dist/services/scan/file-size-scan.js +63 -0
- package/dist/services/session/caller-binding-service.d.ts +24 -0
- package/dist/services/session/caller-binding-service.js +34 -0
- package/dist/services/session/getSessionDir.js +15 -10
- package/dist/services/skills/hooks-codegate-superpowers.d.ts +33 -0
- package/dist/services/skills/hooks-codegate-superpowers.js +34 -3
- package/dist/services/skills/hooks-settings-service.d.ts +10 -0
- package/dist/services/skills/hooks-settings-service.js +152 -61
- package/dist/services/slice/slice-check-service.d.ts +14 -0
- package/dist/services/slice/slice-check-service.js +110 -50
- package/dist/services/slice/slice-check-types.d.ts +12 -7
- package/dist/services/slice/slice-check-types.js +8 -3
- package/dist/services/slice/slice-decompose-runners.js +24 -21
- package/dist/services/sop/sop-check-service.js +12 -1
- package/dist/services/web/bounded-output.d.ts +34 -0
- package/dist/services/web/bounded-output.js +68 -0
- package/dist/services/web/browser-acquire.d.ts +14 -0
- package/dist/services/web/browser-acquire.js +84 -0
- package/dist/services/web/browser-session-manager.d.ts +111 -0
- package/dist/services/web/browser-session-manager.js +413 -0
- package/dist/services/web/daemon-entry.d.ts +1 -0
- package/dist/services/web/daemon-entry.js +65 -0
- package/dist/services/web/daemon-registry.d.ts +42 -0
- package/dist/services/web/daemon-registry.js +164 -0
- package/dist/services/web/daemon-supervisor.d.ts +144 -0
- package/dist/services/web/daemon-supervisor.js +455 -0
- package/dist/services/web/playwright-loader.d.ts +89 -0
- package/dist/services/web/playwright-loader.js +253 -0
- package/dist/services/web/snapshot-pruner.d.ts +48 -0
- package/dist/services/web/snapshot-pruner.js +241 -0
- package/dist/services/web/untrusted-envelope.d.ts +27 -0
- package/dist/services/web/untrusted-envelope.js +44 -0
- package/dist/services/web/web-artifact-paths.d.ts +79 -0
- package/dist/services/web/web-artifact-paths.js +163 -0
- package/dist/services/web/web-client.d.ts +19 -0
- package/dist/services/web/web-client.js +55 -0
- package/dist/services/web/web-daemon-service.d.ts +38 -0
- package/dist/services/web/web-daemon-service.js +416 -0
- package/dist/services/web/web-fallback.d.ts +70 -0
- package/dist/services/web/web-fallback.js +121 -0
- package/dist/services/web/web-install-service.d.ts +91 -0
- package/dist/services/web/web-install-service.js +346 -0
- package/dist/services/web/web-login-profile.d.ts +89 -0
- package/dist/services/web/web-login-profile.js +612 -0
- package/dist/services/web/web-login-staging.d.ts +27 -0
- package/dist/services/web/web-login-staging.js +173 -0
- package/dist/services/web/web-protocol.d.ts +58 -0
- package/dist/services/web/web-protocol.js +58 -0
- package/dist/services/web/web-status-report.d.ts +33 -0
- package/dist/services/web/web-status-report.js +47 -0
- package/dist/services/workspace/claude-settings-template.d.ts +41 -5
- package/dist/services/workspace/claude-settings-template.js +116 -64
- package/dist/services/workspace/workspace-claude-settings-materializer.js +5 -1
- package/dist/services/workspace/workspace-service.js +33 -0
- package/package.json +5 -5
- package/scripts/copy-templates.mjs +12 -0
- package/scripts/sync-version.mjs +20 -0
- package/skills/peaks-code/SKILL.md +10 -0
- package/skills/peaks-code/references/browser-workflow.md +10 -1
|
@@ -14,10 +14,12 @@
|
|
|
14
14
|
* the in-memory template byte-for-byte.
|
|
15
15
|
*
|
|
16
16
|
* One matcher is emitted:
|
|
17
|
-
* 1. `Write|Edit|MultiEdit` — a node
|
|
18
|
-
*
|
|
19
|
-
* for
|
|
20
|
-
* everything else.
|
|
17
|
+
* 1. `Write|Edit|MultiEdit` — a `node <script>` handler that runs the
|
|
18
|
+
* path gate shipped at `src/services/hooks/write-gate.js`. Exits 0
|
|
19
|
+
* (allow) for the paths the gate skips, exit 1 (deny → fall through to
|
|
20
|
+
* gate) for everything else. TEMPLATE_VERSION 1.6.0 moved the decision
|
|
21
|
+
* out of an inlined `node -e "<js>"` one-liner, whose escaping was
|
|
22
|
+
* bash-specific and therefore could not take a platform `shell` pin.
|
|
21
23
|
*
|
|
22
24
|
* The previous `Bash` matcher (which whitelisted a fixed `peaks
|
|
23
25
|
* <subcommand>` prefix) was removed in TEMPLATE_VERSION 1.2.0. The
|
|
@@ -31,6 +33,9 @@
|
|
|
31
33
|
* consumer project's `.claude/settings.json` and which exits 0
|
|
32
34
|
* silently for any command not guarded by a registered SOP gate.
|
|
33
35
|
*/
|
|
36
|
+
import { dirname, resolve } from 'node:path';
|
|
37
|
+
import { fileURLToPath } from 'node:url';
|
|
38
|
+
import { resolveHookShell, resolveHookSpec } from '../skills/hooks-codegate-superpowers.js';
|
|
34
39
|
export const CLAUDE_SETTINGS_LOCAL_FILENAME = '.claude/settings.local.json';
|
|
35
40
|
/**
|
|
36
41
|
* Informational version of the offline template shape. Bumped when the
|
|
@@ -55,8 +60,26 @@ export const CLAUDE_SETTINGS_LOCAL_FILENAME = '.claude/settings.local.json';
|
|
|
55
60
|
* gate). The existing Write|Edit|MultiEdit matcher is
|
|
56
61
|
* preserved. The new matcher's exit code is the load-bearing
|
|
57
62
|
* signal: 0 = allow, 2 = block (with stderr BLOCKED reason).
|
|
63
|
+
* 1.4.0 — added the `peaks gate enforce` `Bash` PreToolUse entry. It
|
|
64
|
+
* lives here (machine-local, gitignored file) rather than in
|
|
65
|
+
* the committed `.claude/settings.json` because its `shell`
|
|
66
|
+
* is machine-specific: on Windows the default Git-Bash shell
|
|
67
|
+
* force-allocates a console window on every Bash tool call.
|
|
68
|
+
* This template is the second writer of that file, so it must
|
|
69
|
+
* emit the entry too — otherwise `peaks workspace init` would
|
|
70
|
+
* overwrite whatever `peaks hooks install` put there.
|
|
71
|
+
* 1.5.0 — pinned the same platform `shell` on the `peaks code
|
|
72
|
+
* gate-step-08` handler. It runs on the same `Bash` matcher,
|
|
73
|
+
* so leaving it un-pinned left the console-window defect in
|
|
74
|
+
* place for half of every Bash tool call.
|
|
75
|
+
* 1.6.0 — the `Write|Edit|MultiEdit` handler no longer inlines its
|
|
76
|
+
* JavaScript as `node -e "<js>"`. It invokes the shipped script
|
|
77
|
+
* `src/services/hooks/write-gate.js` instead, so the command
|
|
78
|
+
* string carries no shell-escaped payload at all and the handler
|
|
79
|
+
* can take the same platform `shell` pin as its siblings. The
|
|
80
|
+
* decision itself is a verbatim relocation — see that file.
|
|
58
81
|
*/
|
|
59
|
-
export const TEMPLATE_VERSION = '1.
|
|
82
|
+
export const TEMPLATE_VERSION = '1.6.0';
|
|
60
83
|
/**
|
|
61
84
|
* Compare two serialized template strings for semantic equivalence.
|
|
62
85
|
*
|
|
@@ -124,70 +147,70 @@ function sameHooksArray(a, b) {
|
|
|
124
147
|
for (let i = 0; i < a.length; i += 1) {
|
|
125
148
|
const ha = a[i];
|
|
126
149
|
const hb = b[i];
|
|
127
|
-
|
|
150
|
+
// `shell` participates in the comparison: it is machine-specific (see
|
|
151
|
+
// `resolveHookShell`), so a file written on one platform must be
|
|
152
|
+
// recognized as drifted on the other instead of silently kept.
|
|
153
|
+
if (ha.type !== hb.type || ha.command !== hb.command || ha.shell !== hb.shell) {
|
|
128
154
|
return false;
|
|
129
155
|
}
|
|
130
156
|
}
|
|
131
157
|
return true;
|
|
132
158
|
}
|
|
133
159
|
/**
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
* into `.claude/settings.local.json` under the `command` field. Per
|
|
137
|
-
* Node.js docs (https://nodejs.org/api/process.html#processargv), when
|
|
138
|
-
* using `-e` there is no script-file slot, so `process.argv[1]` is the
|
|
139
|
-
* first user-passed extra argument. This is consistent across Windows,
|
|
140
|
-
* macOS, and Linux.
|
|
160
|
+
* This module's own directory — `<root>/src/services/workspace` in the
|
|
161
|
+
* source tree, `<root>/dist/services/workspace` in a build.
|
|
141
162
|
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
*
|
|
163
|
+
* Anchored on the running module rather than on `process.argv[1]`: the
|
|
164
|
+
* same reason `daemon-supervisor.ts` documents — `argv[1]` is a different
|
|
165
|
+
* file in each way the CLI is entered (`bin/peaks.js`, `src/cli/index.ts`
|
|
166
|
+
* under tsx, `dist/cli/index.js` when invoked directly), whereas the
|
|
167
|
+
* module's own location is the one fact that is always true.
|
|
168
|
+
*/
|
|
169
|
+
const MODULE_DIR = dirname(fileURLToPath(import.meta.url));
|
|
170
|
+
/**
|
|
171
|
+
* Absolute path of the shipped Write|Edit|MultiEdit gate script.
|
|
172
|
+
*
|
|
173
|
+
* `write-gate.js` is a plain `.js` (not a compiled `.ts`) precisely so this
|
|
174
|
+
* single relative filename resolves in BOTH trees: `src/services/hooks/` for
|
|
175
|
+
* `tsx` / vitest, `dist/services/hooks/` for an installed consumer (copied by
|
|
176
|
+
* `scripts/copy-templates.mjs`; the `dist/**` `*.js` glob in
|
|
177
|
+
* `package.json#files` already ships it).
|
|
147
178
|
*
|
|
148
|
-
*
|
|
149
|
-
*
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
* already terminated by the payload itself.
|
|
179
|
+
* Separators are normalized to `/` so the emitted command contains no
|
|
180
|
+
* backslash at all. That is what makes the handler shell-agnostic: bash
|
|
181
|
+
* reduces `\X` inside `"..."` and PowerShell escapes with a backtick, so any
|
|
182
|
+
* backslash in the string is one dialect's problem waiting to happen.
|
|
153
183
|
*/
|
|
154
|
-
function
|
|
155
|
-
|
|
156
|
-
// unescaped inner `"` would close the wrapper prematurely. Backslashes
|
|
157
|
-
// do NOT need escaping here — bash inside a `"..."` wrapper reduces
|
|
158
|
-
// `\\` to `\`, so any `\X` in the inner JS reaches Node as `\X`,
|
|
159
|
-
// which is what regex literals like `/\.peaks\//` need. Adding a
|
|
160
|
-
// second `\\` → `\\` pass would double-escape backslashes and break
|
|
161
|
-
// every regex literal the inner JS contains.
|
|
162
|
-
const escaped = js.replace(/"/g, '\\"');
|
|
163
|
-
return `node -e "${escaped}"`;
|
|
184
|
+
export function writeGateScriptPath() {
|
|
185
|
+
return resolve(MODULE_DIR, '..', 'hooks', 'write-gate.js').replaceAll('\\', '/');
|
|
164
186
|
}
|
|
165
187
|
/**
|
|
166
|
-
* Build the Write|Edit|MultiEdit matcher command.
|
|
167
|
-
* the candidate file path from argv[2] and exits 0 iff the path
|
|
168
|
-
* contains `.peaks/_runtime/` or `.peaks/_runtime/<sessionId>/` (the change-id
|
|
169
|
-
* segment is the next path component after `.peaks/`). All other
|
|
170
|
-
* paths exit 1 so the gate fires normally.
|
|
188
|
+
* Build the Write|Edit|MultiEdit matcher command.
|
|
171
189
|
*
|
|
172
|
-
*
|
|
173
|
-
*
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
* peaks-code workflow needs more paths.
|
|
190
|
+
* TEMPLATE_VERSION 1.6.0: `node "<script>"` with NO inline payload. The
|
|
191
|
+
* decision lives in `src/services/hooks/write-gate.js` and was relocated
|
|
192
|
+
* there verbatim. Because there is nothing left to escape, this handler is
|
|
193
|
+
* shell-dialect-independent and can carry the same platform `shell` pin as
|
|
194
|
+
* its Bash siblings (see `resolveHookShell`).
|
|
178
195
|
*/
|
|
179
196
|
function buildWriteHookCommand() {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
197
|
+
return `node "${writeGateScriptPath()}"`;
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* TEMPLATE_VERSION 1.4.0 — the SOP gate-enforce handler, read from the same
|
|
201
|
+
* canonical hook spec `peaks hooks install` uses. Deriving it here (rather
|
|
202
|
+
* than re-typing the literal) is what keeps the two writers of this file
|
|
203
|
+
* byte-compatible: `templateContentMatches` compares the `command` string,
|
|
204
|
+
* so any drift would make every `peaks workspace init` rewrite the file and
|
|
205
|
+
* drop whatever `peaks hooks install` had merged in.
|
|
206
|
+
*/
|
|
207
|
+
function buildGateEnforceHandler() {
|
|
208
|
+
const spec = resolveHookSpec('claude-code');
|
|
209
|
+
return {
|
|
210
|
+
type: 'command',
|
|
211
|
+
command: spec.hookEnforceCommand,
|
|
212
|
+
...(spec.hookEnforceShell !== undefined ? { shell: spec.hookEnforceShell } : {})
|
|
213
|
+
};
|
|
191
214
|
}
|
|
192
215
|
/**
|
|
193
216
|
* Build the full template object. The shape is the subset of Claude
|
|
@@ -202,6 +225,14 @@ function buildWriteHookCommand() {
|
|
|
202
225
|
* `.claude/settings.json` (not `.claude/settings.local.json`).
|
|
203
226
|
*/
|
|
204
227
|
export function buildClaudeSettingsLocalJson() {
|
|
228
|
+
// TEMPLATE_VERSION 1.6.0 — the write handler can now be shell-pinned for the
|
|
229
|
+
// same Windows reason as the two Bash handlers below: a shell-form command is
|
|
230
|
+
// executed by Git Bash / MSYS2, which force-allocates a console window on
|
|
231
|
+
// every matching tool call. It could NOT take the pin while its payload was
|
|
232
|
+
// inlined JavaScript, because PowerShell does not perform bash's backslash
|
|
233
|
+
// reduction and would have corrupted the payload. `undefined` on POSIX omits
|
|
234
|
+
// the key entirely.
|
|
235
|
+
const writeShell = resolveHookShell();
|
|
205
236
|
return {
|
|
206
237
|
hooks: {
|
|
207
238
|
PreToolUse: [
|
|
@@ -210,7 +241,8 @@ export function buildClaudeSettingsLocalJson() {
|
|
|
210
241
|
hooks: [
|
|
211
242
|
{
|
|
212
243
|
type: 'command',
|
|
213
|
-
command: buildWriteHookCommand()
|
|
244
|
+
command: buildWriteHookCommand(),
|
|
245
|
+
...(writeShell !== undefined ? { shell: writeShell } : {})
|
|
214
246
|
}
|
|
215
247
|
]
|
|
216
248
|
},
|
|
@@ -223,12 +255,22 @@ export function buildClaudeSettingsLocalJson() {
|
|
|
223
255
|
// 2 = block (stderr contains the BLOCKED: ... reason).
|
|
224
256
|
// The existing Write|Edit|MultiEdit matcher is preserved.
|
|
225
257
|
matcher: 'Bash',
|
|
226
|
-
hooks: [
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
258
|
+
hooks: [buildBashGateStep08Handler()]
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
// TEMPLATE_VERSION 1.4.0 — SOP gate enforcement. Lives in this
|
|
262
|
+
// machine-local file (not the committed settings.json) because
|
|
263
|
+
// `shell` is machine-specific; see the version history above and
|
|
264
|
+
// `resolveHookShell`.
|
|
265
|
+
//
|
|
266
|
+
// It sits in its own matcher group on purpose: a group counts as
|
|
267
|
+
// peaks-managed only when EVERY handler in it carries a peaks
|
|
268
|
+
// sentinel, and the `peaks code gate-step-08` handler above does
|
|
269
|
+
// not. Sharing a group with it would make the whole group
|
|
270
|
+
// unmanaged, so `peaks hooks install` would append a second Bash
|
|
271
|
+
// group and the gate would run twice per Bash call.
|
|
272
|
+
matcher: 'Bash',
|
|
273
|
+
hooks: [buildGateEnforceHandler()]
|
|
232
274
|
}
|
|
233
275
|
]
|
|
234
276
|
}
|
|
@@ -244,11 +286,21 @@ export function buildClaudeSettingsLocalJson() {
|
|
|
244
286
|
* re-running `peaks workspace init` on a project that already has the
|
|
245
287
|
* Bash hook is a no-op (already-current).
|
|
246
288
|
*/
|
|
247
|
-
function
|
|
289
|
+
function buildBashGateStep08Handler() {
|
|
248
290
|
// The hook receives the tool call on stdin. We ignore stdin and
|
|
249
291
|
// delegate entirely to `peaks code gate-step-08`, which reads
|
|
250
292
|
// .peaks/_runtime/<sessionId>/job-shape.json and last-prompt.txt.
|
|
251
293
|
// `${CLAUDE_PROJECT_DIR}` resolves to the consumer project's root
|
|
252
294
|
// (Claude Code's standard convention).
|
|
253
|
-
|
|
295
|
+
//
|
|
296
|
+
// TEMPLATE_VERSION 1.5.0 — shell-pinned on Windows for the same reason
|
|
297
|
+
// as the gate-enforce handler below: this runs on the same `Bash`
|
|
298
|
+
// matcher, so a shell-form command is executed by Git Bash / MSYS2,
|
|
299
|
+
// which force-allocates a console window on every Bash tool call.
|
|
300
|
+
const shell = resolveHookShell();
|
|
301
|
+
return {
|
|
302
|
+
type: 'command',
|
|
303
|
+
command: 'peaks code gate-step-08 --project "${CLAUDE_PROJECT_DIR}"',
|
|
304
|
+
...(shell !== undefined ? { shell } : {})
|
|
305
|
+
};
|
|
254
306
|
}
|
|
@@ -89,7 +89,11 @@ export async function materializeClaudeSettingsLocal(projectRoot, noClaudeHooks)
|
|
|
89
89
|
try {
|
|
90
90
|
const { readFile } = await import('node:fs/promises');
|
|
91
91
|
const existing = await readFile(settingsPath, 'utf8');
|
|
92
|
-
|
|
92
|
+
// Structural comparison (not a byte comparison): `peaks hooks
|
|
93
|
+
// install` also writes this file, through a different serializer, so
|
|
94
|
+
// an equal hooks tree must be recognized as current or every init
|
|
95
|
+
// would rewrite the file and drop the installer's entries.
|
|
96
|
+
if (templateContentMatches(serialized, existing)) {
|
|
93
97
|
action = 'already-current';
|
|
94
98
|
}
|
|
95
99
|
else {
|
|
@@ -3,7 +3,31 @@ import { existsSync, lstatSync, readdirSync } from 'node:fs';
|
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { isDirectory } from 'peaks-loop-shared/fs';
|
|
5
5
|
import { getSessionIdCanonical, setCurrentSessionBinding, setSessionMeta } from '../session/session-manager.js';
|
|
6
|
+
import { updateCallerBindingSessionId } from '../session/caller-binding-service.js';
|
|
7
|
+
import { resolveCallerProjection } from '../session/resolve-caller-id.js';
|
|
6
8
|
import { normalizePath } from '../../shared/path-utils.js';
|
|
9
|
+
/**
|
|
10
|
+
* Slice 2026-09-10 (rid=rebind-must-update-caller-binding): repoint the
|
|
11
|
+
* calling process's own per-caller binding at the session id this init just
|
|
12
|
+
* bound. `getSessionIdCanonical` reads `callers/<callerId>.json` FIRST, so a
|
|
13
|
+
* binding left behind by an earlier session shadows every explicit rebind.
|
|
14
|
+
*
|
|
15
|
+
* Best-effort: an unresolvable callerId (`PEAKS_CALLER_NOT_RESOLVED`, e.g. a
|
|
16
|
+
* stock shell with no IDE adapter) means there is no binding file to
|
|
17
|
+
* repoint, and `session.json` alone answers both resolvers.
|
|
18
|
+
*
|
|
19
|
+
* @returns `true` when a binding file existed and was repointed.
|
|
20
|
+
*/
|
|
21
|
+
function rebindCurrentCallerBinding(projectRoot, sessionId) {
|
|
22
|
+
let callerId;
|
|
23
|
+
try {
|
|
24
|
+
callerId = resolveCallerProjection({ projectRoot, env: process.env }).callerId;
|
|
25
|
+
}
|
|
26
|
+
catch { // TODO(g2): legacy silent catch — grace: 1 minor release (v2.14.0)
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
return updateCallerBindingSessionId(projectRoot, callerId, sessionId);
|
|
30
|
+
}
|
|
7
31
|
/**
|
|
8
32
|
* Slice 2026-06-29-change-id-root-removal: list the immediate children of
|
|
9
33
|
* `.peaks/` so the legacy sibling-dir guard can enumerate date-stamped
|
|
@@ -299,6 +323,15 @@ export async function initWorkspace(options) {
|
|
|
299
323
|
// Either: existing session dir is empty (true leftover, no user data),
|
|
300
324
|
// or the caller explicitly authorised a rebind. Overwrite.
|
|
301
325
|
setCurrentSessionBinding(options.projectRoot, options.sessionId);
|
|
326
|
+
// Slice 2026-09-10 (rid=rebind-must-update-caller-binding): an
|
|
327
|
+
// explicit rebind must ALSO repoint this caller's per-caller binding.
|
|
328
|
+
// `getSessionIdCanonical` prefers `callers/<callerId>.json`, so leaving
|
|
329
|
+
// it untouched shadowed the rebind for every command resolving through
|
|
330
|
+
// it (session checkpoint / 24h-mode wrote into the stale session dir)
|
|
331
|
+
// while `getCurrentSessionId` (session.json) reported the new one.
|
|
332
|
+
// Only THIS caller's binding is repointed — a second caller keeps its
|
|
333
|
+
// own session by design.
|
|
334
|
+
rebindCurrentCallerBinding(options.projectRoot, options.sessionId);
|
|
302
335
|
bound = true;
|
|
303
336
|
}
|
|
304
337
|
// Slice 2026-06-16-peaks-code-auto-scaffold (RD#7):
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "peaks-loop",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.37",
|
|
4
4
|
"description": "Loop Engineering CLI — workflow primitive / loop guards / evaluators / slice orchestration",
|
|
5
5
|
"author": "SquabbyZ",
|
|
6
6
|
"keywords": [
|
|
@@ -101,10 +101,10 @@
|
|
|
101
101
|
"fzf": "^0.5.2",
|
|
102
102
|
"yaml": "^2.9.0",
|
|
103
103
|
"zod": "^4.4.3",
|
|
104
|
-
"peaks-loop-
|
|
105
|
-
"peaks-loop-
|
|
106
|
-
"peaks-loop-shared": "0.0.
|
|
107
|
-
"peaks-loop-
|
|
104
|
+
"peaks-loop-mut": "0.1.35",
|
|
105
|
+
"peaks-loop-internal-runtime": "0.0.22",
|
|
106
|
+
"peaks-loop-shared-channel": "0.0.39",
|
|
107
|
+
"peaks-loop-shared": "0.0.71"
|
|
108
108
|
},
|
|
109
109
|
"devDependencies": {
|
|
110
110
|
"@changesets/cli": "2.31.1",
|
|
@@ -60,6 +60,18 @@ const targets = [
|
|
|
60
60
|
src: join(packageRoot, 'src/services/skillhub/migrations'),
|
|
61
61
|
dest: join(packageRoot, 'dist/services/skillhub/migrations'),
|
|
62
62
|
extensions: ['.sql']
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
// Slice c5-write-hook-exec-form: the Write|Edit|MultiEdit PreToolUse gate
|
|
66
|
+
// emitted by `peaks workspace init` is invoked as `node <path>`, and the
|
|
67
|
+
// path is resolved relative to this module — `dist/services/hooks/` in an
|
|
68
|
+
// installed consumer, `src/services/hooks/` under tsx. A plain `.js` asset
|
|
69
|
+
// (not a `.ts` compiled by tsc) is what lets ONE relative filename be valid
|
|
70
|
+
// in both trees. Without this copy the hook would be a broken path in every
|
|
71
|
+
// installed consumer while every test in this repo still passed.
|
|
72
|
+
src: join(packageRoot, 'src/services/hooks'),
|
|
73
|
+
dest: join(packageRoot, 'dist/services/hooks'),
|
|
74
|
+
extensions: ['.js']
|
|
63
75
|
}
|
|
64
76
|
];
|
|
65
77
|
|
package/scripts/sync-version.mjs
CHANGED
|
@@ -18,6 +18,26 @@ writeFileSync(
|
|
|
18
18
|
`export const CLI_VERSION = ${JSON.stringify(version)};\n`,
|
|
19
19
|
);
|
|
20
20
|
|
|
21
|
+
// Slice 2026-09-11 (runtime-version-lockstep) — sync RUNTIME_VERSION.
|
|
22
|
+
// `packages/peaks-loop-internal-runtime/src/index.ts` declares
|
|
23
|
+
// `RUNTIME_VERSION` under a comment stating it tracks the peaks-loop root
|
|
24
|
+
// version, but nothing wrote it: v4.0.37 was tagged with the constant still
|
|
25
|
+
// at 4.0.36 and publish.yml's gate-cli-version step aborted before npm
|
|
26
|
+
// publish. The literal is replaced in place, single-quoted — the exact
|
|
27
|
+
// shape the gate greps — because the file also holds the package's public
|
|
28
|
+
// exports and must never be regenerated wholesale. A literal we cannot
|
|
29
|
+
// find throws instead of no-op'ing: that silence is how the drift reached CI.
|
|
30
|
+
const runtimeIndexPath = resolve('packages/peaks-loop-internal-runtime/src/index.ts');
|
|
31
|
+
const runtimeIndex = readFileSync(runtimeIndexPath, 'utf8');
|
|
32
|
+
const runtimeDecl = /(export const RUNTIME_VERSION = ')[^']*(';)/;
|
|
33
|
+
if (!runtimeDecl.test(runtimeIndex)) {
|
|
34
|
+
throw new Error(`${runtimeIndexPath}: could not find "export const RUNTIME_VERSION = '...';"`);
|
|
35
|
+
}
|
|
36
|
+
const syncedRuntimeIndex = runtimeIndex.replace(runtimeDecl, `$1${version}$2`);
|
|
37
|
+
if (syncedRuntimeIndex !== runtimeIndex) {
|
|
38
|
+
writeFileSync(runtimeIndexPath, syncedRuntimeIndex);
|
|
39
|
+
}
|
|
40
|
+
|
|
21
41
|
// 2026-07-23 follow-up (peaks-publish-stale fix, AC6): the shared
|
|
22
42
|
// bump used to live here, gated on `PEAKS_AUTO_BUMP_SHARED === '1'`.
|
|
23
43
|
// That gate was the Layer 2 root cause: publish.yml set the env on
|
|
@@ -235,6 +235,16 @@ triggered for this intent.
|
|
|
235
235
|
5. LLM picks one recommendation (★ marker) with reasoning block.
|
|
236
236
|
6. **Mandatory ⚠️ catch gate** — user explicitly acks / picks alt / rejects + reason.
|
|
237
237
|
|
|
238
|
+
**When the scan cannot be performed, the sub-step is SKIPPED — never fabricated.** The lookups behind
|
|
239
|
+
this scan are stubs in the current build: they return synthetic fragments marked internally as
|
|
240
|
+
synthetic. When the scan runs on a synthetic result it **refuses** — `ok:false`,
|
|
241
|
+
`BEST_PRACTICE_SCAN_SYNTHETIC_LOOKUP`, exit 1 — and prints no recommendation, no comparison table and
|
|
242
|
+
no ⚠️ catch gate, because there is nothing real for the user to judge. Record the sub-step as
|
|
243
|
+
**skipped with reason `synthetic-lookup`** and carry on; do not synthesise a recommendation to satisfy
|
|
244
|
+
the gate. `--intent <text>` is required (the business goal, not the project path) — omitting it is
|
|
245
|
+
`BEST_PRACTICE_SCAN_INTENT_REQUIRED`, also exit 1. A real scan renders the full table and the gate
|
|
246
|
+
normally; wiring the real Context7 / WebSearch lookup is a future slice.
|
|
247
|
+
|
|
238
248
|
**Out of scope for this sub-step:**
|
|
239
249
|
- ❌ No changes to RD dispatch Karpathy prose (Step 3 unchanged)
|
|
240
250
|
- ❌ No changes to QA / SC / TXT skills
|
|
@@ -105,7 +105,7 @@ If the page redirects to a login challenge:
|
|
|
105
105
|
|
|
106
106
|
## Sensitive data sanitization
|
|
107
107
|
|
|
108
|
-
Never persist any of the following in `.peaks/_runtime/<session-id>/**` artifacts:
|
|
108
|
+
**Default: never persist.** Never persist any of the following in `.peaks/_runtime/<session-id>/**` artifacts:
|
|
109
109
|
|
|
110
110
|
- Login URLs, redirect URLs, OAuth callback URLs containing tokens or state.
|
|
111
111
|
- Cookies, request or response headers, session tokens, storage state, QR payloads.
|
|
@@ -113,6 +113,15 @@ Never persist any of the following in `.peaks/_runtime/<session-id>/**` artifact
|
|
|
113
113
|
- Raw browser state, browser traces.
|
|
114
114
|
- Screenshots or logs containing PII, SSO challenge content, or MFA material.
|
|
115
115
|
|
|
116
|
+
**The one exception, and it is narrow.** `peaks web login --profile <name>` persists a Playwright `storageState.json` to `~/.peaks/web-profiles/<name>/`. It is allowed only when the user explicitly asks for a persistent login. An LLM must never choose it on its own initiative, and it is never part of a default workflow. That name at that path is the whole of the exception: **no other artifact may hold these values, anywhere** — not under `.peaks/_runtime/`, not elsewhere in the project tree, not elsewhere under the user's home. This file is written outside the project tree and outside git; that is a property of this one path, not a licence to persist these values wherever the project tree happens to end.
|
|
117
|
+
|
|
118
|
+
**Two named carve-outs, so the rule above can be read literally.**
|
|
119
|
+
|
|
120
|
+
1. **The staging file.** The state is published atomically, which means writing it to a staging file **in the same profile directory** (`0600`) and renaming it into place. The staging name is **per-run** (`storageState.json.<pid>.staging`) so two concurrent logins cannot overwrite each other's in-flight bytes — the rule sanctions *a* staging file, and that is the only form it takes. It is sanctioned **only** while a publish is in flight: it must be deleted on every failure path, must never outlive the command that created it, and nothing may ever read it as a profile. A single non-atomic write is **not** an acceptable alternative — it truncates the previous session before writing the new one, so a failed write destroys a working login and the caller is told nothing happened.
|
|
121
|
+
2. **The browser's own profile directory.** A headed Chromium writes its live session cookies to an **ephemeral profile directory the browser manages itself** (under the OS temp directory for the duration of the session, normally removed on close). This is not something a login flow can prevent and it is not what this rule is about — but it is a place those values are held, so it is named here rather than left implied. Launching a headed browser to log in is therefore **never zero-exposure**, and anyone weighing the exception above should weigh that too.
|
|
122
|
+
|
|
123
|
+
**The risk, stated plainly.** That file holds live session cookies and tokens for the sites that were logged into. Any process running as this user can read it. It does not expire with the browser session, and it survives until it is deleted. Deleting `~/.peaks/web-profiles/<name>/` removes what the tool stored — it does not revoke the session at the site, so log out there as well if the account matters. Only persist a login for an account that may safely stay logged in on this machine.
|
|
124
|
+
|
|
116
125
|
Redact sensitive values before retention. Store evidence as sanitized observations (e.g., "user reached settings page; first 3 list items had a missing-image regression") rather than raw captures.
|
|
117
126
|
|
|
118
127
|
## Fallback when Playwright MCP is not installed
|