peaks-cli 2.0.3 → 2.0.4
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
CHANGED
|
@@ -7,6 +7,53 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [2.0.4] — 2026-06-13 (hotfix)
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **PreToolUse hook `command` field was bare JavaScript source, not a
|
|
15
|
+
`node -e "..."` one-liner.** `peaks workspace init` writes
|
|
16
|
+
`.claude/settings.local.json` containing two PreToolUse hooks (one
|
|
17
|
+
for `Bash`, one for `Write|Edit|MultiEdit`) whose `command` field
|
|
18
|
+
was the inner JS payload without the `node -e "..."` wrapper.
|
|
19
|
+
Claude Code executes the `command` field as a shell string, so
|
|
20
|
+
bash saw literal `const c=process.argv[1]...` and tripped
|
|
21
|
+
`syntax error near unexpected token`. Net effect on every 2.0.3
|
|
22
|
+
install on Windows + macOS + Linux:
|
|
23
|
+
- Every Bash tool call (peaks CLI or otherwise) was rejected.
|
|
24
|
+
- Every Write / Edit / MultiEdit call was rejected.
|
|
25
|
+
- The [Fact-Forcing Gate] bypass that `peaks workspace init` was
|
|
26
|
+
supposed to install was therefore self-defeating — the bypass
|
|
27
|
+
broke the gate itself, and the gate could not be reached to fix
|
|
28
|
+
it.
|
|
29
|
+
Recovery required the user to delete `.claude/settings.local.json`
|
|
30
|
+
manually (losing the bypass permanently) or hand-patch the
|
|
31
|
+
`command` field (drift vs the template).
|
|
32
|
+
The fix wraps both builders' JS payloads in a real shell-evaluable
|
|
33
|
+
`node -e "<js>"` form via a new `wrapAsNodeOneLiner` helper in
|
|
34
|
+
`src/services/workspace/claude-settings-template.ts`. Inner `"`
|
|
35
|
+
are escaped to `\"`; backslashes pass through unchanged so regex
|
|
36
|
+
literals like `/\.peaks\//` still match correctly. `process.argv[1]`
|
|
37
|
+
is the correct slot under `-e` per Node.js docs
|
|
38
|
+
(https://nodejs.org/api/process.html#processargv) — consistent
|
|
39
|
+
across Windows, macOS, and Linux. The docstring is reconciled
|
|
40
|
+
with the implementation (the previous docstring incorrectly said
|
|
41
|
+
`argv[2]`).
|
|
42
|
+
|
|
43
|
+
Regression tests cover:
|
|
44
|
+
- `buildBashHookCommand()` and `buildWriteHookCommand()` return
|
|
45
|
+
`node -e "..."` form.
|
|
46
|
+
- Inner `"` are escaped to `\"`.
|
|
47
|
+
- Spawning the wrapped command with `peaks workspace init --project . --json`
|
|
48
|
+
exits 0; with `npm install foo` exits non-zero.
|
|
49
|
+
- Spawning the Write hook with `.peaks/_runtime/...` and
|
|
50
|
+
`.peaks/<changeId>/...` paths exits 0; with `src/...`,
|
|
51
|
+
`package.json`, `.peaks/_archive/...` exits non-zero.
|
|
52
|
+
- The existing workspace-init round-trip test (case A/B/C) still
|
|
53
|
+
passes with the wrapper.
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
10
57
|
## [2.0.3] — 2026-06-13
|
|
11
58
|
|
|
12
59
|
### Fixed
|
|
@@ -52,10 +52,43 @@ const PEAKS_SUBCOMMAND_ALLOWLIST = [
|
|
|
52
52
|
'upgrade'
|
|
53
53
|
];
|
|
54
54
|
/**
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
55
|
+
* Wrap an inner JavaScript payload as a shell-evaluable `node -e "..."`
|
|
56
|
+
* one-liner. The returned string is what Claude Code writes verbatim
|
|
57
|
+
* into `.claude/settings.local.json` under the `command` field. Per
|
|
58
|
+
* Node.js docs (https://nodejs.org/api/process.html#processargv), when
|
|
59
|
+
* using `-e` there is no script-file slot, so `process.argv[1]` is the
|
|
60
|
+
* first user-passed extra argument. This is consistent across Windows,
|
|
61
|
+
* macOS, and Linux.
|
|
62
|
+
*
|
|
63
|
+
* Every `"` character in the inner JS must be JSON-escaped as `\\"`
|
|
64
|
+
* so that the surrounding wrapper `node -e "..."` parses correctly:
|
|
65
|
+
* the shell sees the escape and passes a literal `"` to Node. A
|
|
66
|
+
* single missed escape closes the wrapper early and the entire hook
|
|
67
|
+
* regresses to the bash-syntax-error class of bug.
|
|
68
|
+
*
|
|
69
|
+
* @param js Inner JavaScript payload. Must be a single statement or a
|
|
70
|
+
* sequence of statements joined with `;`. The wrapper does
|
|
71
|
+
* not insert any `;` between the payload and the closing
|
|
72
|
+
* `"` because Node accepts a trailing expression with `;`
|
|
73
|
+
* already terminated by the payload itself.
|
|
74
|
+
*/
|
|
75
|
+
function wrapAsNodeOneLiner(js) {
|
|
76
|
+
// Only `"` needs JSON-escaping: the wrapper uses double quotes, so an
|
|
77
|
+
// unescaped inner `"` would close the wrapper prematurely. Backslashes
|
|
78
|
+
// do NOT need escaping here — bash inside a `"..."` wrapper reduces
|
|
79
|
+
// `\\` to `\`, so any `\X` in the inner JS reaches Node as `\X`,
|
|
80
|
+
// which is what regex literals like `/\.peaks\//` need. Adding a
|
|
81
|
+
// second `\\` → `\\` pass would double-escape backslashes and break
|
|
82
|
+
// every regex literal the inner JS contains.
|
|
83
|
+
const escaped = js.replace(/"/g, '\\"');
|
|
84
|
+
return `node -e "${escaped}"`;
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Build the Bash matcher command. The command is a `node -e "..."`
|
|
88
|
+
* one-liner that reads its candidate command string from `argv[1]`
|
|
89
|
+
* and exits 0 iff the command starts with
|
|
90
|
+
* `peaks <whitelisted-subcommand> ` (or is exactly
|
|
91
|
+
* `peaks <whitelisted-subcommand>` with no trailing args).
|
|
59
92
|
*
|
|
60
93
|
* The list is serialised as a JSON array literal embedded in the
|
|
61
94
|
* command string so we avoid regex special-character pitfalls and
|
|
@@ -63,15 +96,17 @@ const PEAKS_SUBCOMMAND_ALLOWLIST = [
|
|
|
63
96
|
*/
|
|
64
97
|
function buildBashHookCommand() {
|
|
65
98
|
const allowlistLiteral = JSON.stringify(PEAKS_SUBCOMMAND_ALLOWLIST);
|
|
66
|
-
// The command reads process.argv[
|
|
67
|
-
// checks it starts with `peaks `, splits on
|
|
68
|
-
// up the second token in the allowlist. Exit
|
|
69
|
-
// deny (so the gate fires for non-peaks
|
|
70
|
-
|
|
99
|
+
// The command reads process.argv[1] (the tool-call command string
|
|
100
|
+
// passed by Claude Code), checks it starts with `peaks `, splits on
|
|
101
|
+
// whitespace, and looks up the second token in the allowlist. Exit
|
|
102
|
+
// 0 = allow, exit 1 = deny (so the gate fires for non-peaks
|
|
103
|
+
// commands).
|
|
104
|
+
const js = 'const c=process.argv[1]||"";' +
|
|
71
105
|
'if(!c.startsWith("peaks "))process.exit(1);' +
|
|
72
106
|
'const sub=c.slice(6).trim().split(/\\s+/)[0];' +
|
|
73
107
|
`if(${allowlistLiteral}.indexOf(sub)===-1)process.exit(1);` +
|
|
74
|
-
'process.exit(0)'
|
|
108
|
+
'process.exit(0)';
|
|
109
|
+
return wrapAsNodeOneLiner(js);
|
|
75
110
|
}
|
|
76
111
|
/**
|
|
77
112
|
* Build the Write|Edit|MultiEdit matcher command. The command reads
|
|
@@ -91,12 +126,14 @@ function buildWriteHookCommand() {
|
|
|
91
126
|
// Path-matching: allow when the path contains `.peaks/_runtime/`
|
|
92
127
|
// OR when the second `.peaks/` segment starts with anything that
|
|
93
128
|
// looks like a change-id (kebab-case slug). Exit 0 for allow, exit
|
|
94
|
-
// 1 for deny.
|
|
95
|
-
|
|
129
|
+
// 1 for deny. The candidate path arrives on `process.argv[1]` per
|
|
130
|
+
// Node.js argv layout under `-e` (cross-platform consistent).
|
|
131
|
+
const js = 'const p=process.argv[1]||"";' +
|
|
96
132
|
'if(p.includes(".peaks/_runtime/"))process.exit(0);' +
|
|
97
133
|
'const m=p.match(/\\.peaks\\/([a-z0-9][a-z0-9.-]*)\\//);' +
|
|
98
134
|
'if(m&&m[1]&&m[1]!=="_runtime"&&m[1]!=="_dogfood"&&m[1]!=="_sub_agents"&&m[1]!=="_archive"&&m[1]!=="memory"&&m[1]!=="issues"&&m[1]!=="sops"&&m[1]!=="retrospective"&&m[1]!=="project-scan"&&m[1]!=="perf-baseline")process.exit(0);' +
|
|
99
|
-
'process.exit(1)'
|
|
135
|
+
'process.exit(1)';
|
|
136
|
+
return wrapAsNodeOneLiner(js);
|
|
100
137
|
}
|
|
101
138
|
/**
|
|
102
139
|
* Build the full template object. The shape is the subset of Claude
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const CLI_VERSION = "2.0.
|
|
1
|
+
export declare const CLI_VERSION = "2.0.4";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export const CLI_VERSION = "2.0.
|
|
1
|
+
export const CLI_VERSION = "2.0.4";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "peaks-cli",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.4",
|
|
4
4
|
"description": "Cross-AI-IDE workflow-gating CLI + skill family (Claude Code shipped, Trae in progress; Codex / Cursor / Qoder / Tongyi Lingma on the roadmap).",
|
|
5
5
|
"author": "SquabbyZ",
|
|
6
6
|
"license": "MIT",
|