peaks-cli 2.0.2 → 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.
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "metadata": {
3
3
  "pluginRoot": ".",
4
- "version": "2.0.2"
4
+ "version": "2.0.3"
5
5
  },
6
6
  "plugins": [
7
7
  {
8
8
  "name": "peaks-cli",
9
9
  "description": "Cross-AI-IDE workflow-gating CLI + 11-skill family. Turns LLM improvisation into auditable engineering process. Skills cover PRD / R&D / UI / QA / change-control / context / SOP definition / orchestration. Soft-fail gates block irreversible actions mid-conversation (even under --dangerously-skip-permissions).",
10
- "version": "2.0.2",
10
+ "version": "2.0.3",
11
11
  "author": {
12
12
  "name": "SquabbyZ"
13
13
  },
package/CHANGELOG.md CHANGED
@@ -7,6 +7,73 @@ 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
+
57
+ ## [2.0.3] — 2026-06-13
58
+
59
+ ### Fixed
60
+
61
+ - **`@alibaba-group/open-code-review` reverted to `optionalDependency`**
62
+ (was promoted to a hard `dependency` in 2.0.1 and carried through
63
+ 2.0.2). The ocr npm package's `postinstall` downloads a Go binary
64
+ via HTTPS, which fails in restricted/proxied environments and was
65
+ aborting the whole `npm i -g peaks-cli` flow. The 5-state detector
66
+ (`ready` / `package-missing` / `binary-missing` / `config-missing` /
67
+ `detection-failed`) and the soft-fail policy are unchanged — peaks-cli
68
+ never blocks on ocr being installed; it just no longer forces the
69
+ install. Users who want the second-opinion review run
70
+ `npm i -g @alibaba-group/open-code-review` explicitly. Under pnpm
71
+ they also need `pnpm approve-builds @alibaba-group/open-code-review`
72
+ for the binary download to run. Source-of-truth refactor (ocr config
73
+ under `peaksConfig.ocr.llm`) from 2.0.1 is unchanged.
74
+
75
+ ---
76
+
10
77
  ## [2.0.0] — 2026-06-12
11
78
 
12
79
  ### 🎯 Headline
@@ -29,6 +96,15 @@ alongside the LLM-only review. Soft-fails so missing ocr never blocks
29
96
  a slice. New CLI: `peaks code-review detect-ocr` / `run-ocr`. See
30
97
  `skills/peaks-rd/references/ocr-integration.md` for the contract.
31
98
 
99
+ > **Note:** This `optionalDependency` classification was briefly
100
+ > promoted to a hard `dependency` in 2.0.1 (alongside the source-of-truth
101
+ > refactor) because the user feedback was "peaks-cli should not leave
102
+ > install to the user". 2.0.3 reverts just the classification — the
103
+ > source-of-truth refactor stays — because the ocr postinstall
104
+ > downloads a Go binary via HTTPS, which fails in restricted/proxied
105
+ > environments and was aborting `npm i -g peaks-cli`. See the 2.0.3
106
+ > entry above for the full rationale.
107
+
32
108
  ### Breaking Changes
33
109
 
34
110
  - **`.claude/rules/` is no longer the source of truth for project standards.**
@@ -71,6 +147,14 @@ config surface.
71
147
  to download the platform binary still soft-fail at runtime
72
148
  (`binary-missing` state) — the install-time failure risk is the
73
149
  trade-off.
150
+
151
+ > **Reverted in 2.0.3.** The install-time failure risk turned out
152
+ > to bite too many real-world installs (corporate proxies, region
153
+ > firewalls, sandboxed dev environments all abort the whole
154
+ > `npm i -g peaks-cli`). 2.0.3 puts ocr back under
155
+ > `optionalDependencies`; everything else in this section
156
+ > (env-var injection, `config-template` CLI, `missingKeys`,
157
+ > source-of-truth under `peaksConfig.ocr.llm`) is unchanged.
74
158
  - **`detectOcr` / `runOcrReview` no longer read `~/.opencodereview/config.json`.**
75
159
  The source of truth is `peaksConfig.ocr.llm` (parsed by
76
160
  `getOcrLlmConfig()` in `config-service.ts`). Missing fields surface
@@ -5,7 +5,7 @@ import { fail, ok } from '../../shared/result.js';
5
5
  export function registerCodeReviewCommands(program, io) {
6
6
  const codeReview = program
7
7
  .command('code-review')
8
- .description('Code-review primitives for peaks-rd Gate B3. Wraps the soft-optional `@alibaba-group/open-code-review` (ocr) tool when it is installed + configured; peaks-rd uses the structured JSON output as a second-opinion review alongside its own LLM review. ocr ships as a peaks-cli dependency (not optional). LLM endpoint config lives under `peaksConfig.ocr.llm` in the user config — run `peaks code-review config-template` to see the JSON snippet to paste.');
8
+ .description('Code-review primitives for peaks-rd Gate B3. Wraps the soft-optional `@alibaba-group/open-code-review` (ocr) tool when it is installed + configured; peaks-rd uses the structured JSON output as a second-opinion review alongside its own LLM review. ocr is an optional dependency of peaks-cli 2.0.3+ (was briefly promoted to a hard dependency in 2.0.1/2.0.2 — reverted because its postinstall downloads a Go binary via HTTPS and would otherwise abort `npm i -g peaks-cli` in restricted environments). LLM endpoint config lives under `peaksConfig.ocr.llm` in the user config — run `peaks code-review config-template` to see the JSON snippet to paste.');
9
9
  addJsonOption(codeReview
10
10
  .command('detect-ocr')
11
11
  .description('Read-only probe: returns the ocr install + config state as a JSON envelope (5 reasons: ready / package-missing / binary-missing / config-missing / detection-failed). peaks-rd calls this first to decide whether to invoke `run-ocr`. Reads the LLM endpoint from `peaksConfig.ocr.llm` (not from ~/.opencodereview/config.json).')
@@ -36,12 +36,17 @@
36
36
  * To see the JSON template to paste, run:
37
37
  * `peaks code-review config-template`
38
38
  *
39
- * The ocr package is declared in package.json:dependencies (was
40
- * previously optionalDependencies) so `npm i -g peaks-cli@2.0.x`
41
- * pulls it automatically (npm runs the ocr postinstall by default,
42
- * which downloads the Go binary). pnpm-based installs need
43
- * `pnpm approve-builds @alibaba-group/open-code-review`. Either
44
- * way, peaks-cli detects the install state and reports it.
39
+ * The ocr package is declared in package.json:optionalDependencies
40
+ * (was promoted to `dependencies` in 2.0.1 and reverted in 2.0.3 —
41
+ * the ocr postinstall downloads a Go binary via HTTPS, which fails
42
+ * in restricted/proxied environments and would otherwise abort the
43
+ * whole `npm i -g peaks-cli` flow). Peaks-cli ships with ocr *not*
44
+ * installed; if the user wants it, they run
45
+ * `npm i -g @alibaba-group/open-code-review`
46
+ * and peaks-cli's 5-state detector (below) reports whether the
47
+ * binary is actually usable. pnpm-based installs additionally need
48
+ * `pnpm approve-builds @alibaba-group/open-code-review` for the
49
+ * binary download to run. Either way, peaks-cli never blocks on it.
45
50
  */
46
51
  import { spawnSync } from 'node:child_process';
47
52
  import { existsSync } from 'node:fs';
@@ -50,7 +55,7 @@ import { fileURLToPath } from 'node:url';
50
55
  import { dirname } from 'node:path';
51
56
  const OCR_DETECT_TIMEOUT_MS = 5000;
52
57
  const OCR_REVIEW_TIMEOUT_MS = 180000;
53
- const OCR_INSTALL_HINT = 'Install: `npm i -g @alibaba-group/open-code-review` (it is a hard dependency of peaks-cli 2.0.x and ships in the regular `npm install` flow). Then add your LLM endpoint to ~/.peaks/config.json — run `peaks code-review config-template` for the JSON snippet to paste.';
58
+ const OCR_INSTALL_HINT = 'Install: `npm i -g @alibaba-group/open-code-review` (peaks-cli 2.0.3 ships with ocr as an optional dependency — its postinstall downloads a Go binary via HTTPS, which fails in some restricted/proxied environments; that\'s why peaks-cli does not auto-install it). Then add your LLM endpoint to ~/.peaks/config.json — run `peaks code-review config-template` for the JSON snippet to paste. Under pnpm you also need `pnpm approve-builds @alibaba-group/open-code-review` to allow the binary download.';
54
59
  const OCR_CONFIG_TEMPLATE = JSON.stringify({
55
60
  ocr: {
56
61
  llm: {
@@ -52,10 +52,43 @@ const PEAKS_SUBCOMMAND_ALLOWLIST = [
52
52
  'upgrade'
53
53
  ];
54
54
  /**
55
- * Build the Bash matcher command. The command is a node -e one-liner
56
- * that reads its candidate command string from argv[2] and exits 0
57
- * iff the command starts with `peaks <whitelisted-subcommand> ` (or
58
- * is exactly `peaks <whitelisted-subcommand>` with no trailing args).
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[2] (the tool-call command string),
67
- // checks it starts with `peaks `, splits on whitespace, and looks
68
- // up the second token in the allowlist. Exit 0 = allow, exit 1 =
69
- // deny (so the gate fires for non-peaks commands).
70
- return ('const c=process.argv[1]||"";' +
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
- return ('const p=process.argv[1]||"";' +
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.2";
1
+ export declare const CLI_VERSION = "2.0.4";
@@ -1 +1 @@
1
- export const CLI_VERSION = "2.0.2";
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.2",
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",
@@ -59,12 +59,14 @@
59
59
  "node": ">=20.0.0"
60
60
  },
61
61
  "dependencies": {
62
- "@alibaba-group/open-code-review": "1.3.1",
63
62
  "@colbymchenry/codegraph": "0.7.10",
64
63
  "commander": "^12.1.0",
65
64
  "fzf": "^0.5.2",
66
65
  "headroom-ai": "0.22.4"
67
66
  },
67
+ "optionalDependencies": {
68
+ "@alibaba-group/open-code-review": "1.3.1"
69
+ },
68
70
  "devDependencies": {
69
71
  "@types/node": "^22.10.2",
70
72
  "@vitest/coverage-v8": "^2.1.8",
@@ -2,11 +2,16 @@
2
2
 
3
3
  > Soft-optional second-opinion code review for peaks-rd Gate B3.
4
4
  > Mirrors the ECC 64-agents pattern (spec §7.2): peaks-cli ships
5
- > `@alibaba-group/open-code-review` as a **required dependency** and
6
- > reads the LLM endpoint config from `peaksConfig.ocr.llm` in the
7
- > user's `~/.peaks/config.json` (single source of truth, user-managed).
8
- > When present + configured, the wrapper turns the output into
9
- > structured `code-review.md` evidence.
5
+ > `@alibaba-group/open-code-review` as an **`optionalDependency`**
6
+ > (was promoted to `dependencies` in 2.0.1 and reverted in 2.0.3
7
+ > because its postinstall downloads a Go binary via HTTPS and would
8
+ > otherwise abort `npm i -g peaks-cli` in restricted/proxied
9
+ > environments). The LLM endpoint config still lives under
10
+ > `peaksConfig.ocr.llm` in the user's `~/.peaks/config.json` (single
11
+ > source of truth, user-managed). When the user installs + configures
12
+ > ocr, the wrapper turns its output into structured `code-review.md`
13
+ > evidence; when missing, peaks-rd proceeds LLM-only and the slice
14
+ > ships without the second opinion.
10
15
 
11
16
  ## What ocr is
12
17
 
@@ -34,9 +39,19 @@ ships without the second opinion.
34
39
 
35
40
  ## Install
36
41
 
37
- `@alibaba-group/open-code-review` is a **required `dependency`** of
38
- peaks-cli 2.0.1+. `npm i -g peaks-cli` pulls it automatically and
39
- downloads the platform binary in the postinstall step. Verify with:
42
+ `@alibaba-group/open-code-review` is an **`optionalDependency`** of
43
+ peaks-cli 2.0.3+ (was a required `dependency` in 2.0.1/2.0.2; reverted
44
+ because the postinstall downloads a Go binary via HTTPS, which fails in
45
+ restricted/proxied environments and would otherwise abort
46
+ `npm i -g peaks-cli`). peaks-cli does NOT auto-install it. To enable
47
+ the second-opinion review:
48
+
49
+ ```bash
50
+ npm i -g @alibaba-group/open-code-review
51
+ ```
52
+
53
+ (Under pnpm you also need `pnpm approve-builds @alibaba-group/open-code-review`
54
+ so the binary download script can run.) Verify with:
40
55
 
41
56
  ```bash
42
57
  peaks code-review detect-ocr --json
@@ -47,7 +62,7 @@ Five possible states:
47
62
  | state | Meaning | Recovery |
48
63
  |---|---|---|
49
64
  | `ready` | Installed + binary downloaded + peaks-cli's `peaksConfig.ocr.llm` valid | Nothing — `run-ocr` will work. |
50
- | `package-missing` | npm dep not installed (corrupt node_modules, or user removed it) | `npm i -g @alibaba-group/open-code-review` (peaks-cli 2.0.1+ does this automatically; this state is rare) |
65
+ | `package-missing` | npm dep not installed (peaks-cli 2.0.3+ ships with ocr as an `optionalDependency`, so the common cause is the user has not installed it yet, or it was removed from node_modules) | `npm i -g @alibaba-group/open-code-review` (peaks-cli no longer auto-installs it; under pnpm also run `pnpm approve-builds @alibaba-group/open-code-review`) |
51
66
  | `binary-missing` | npm dep present but Go binary did not download | `pnpm approve-builds @alibaba-group/open-code-review`, OR run `node node_modules/@alibaba-group/open-code-review/scripts/install.js`, OR manually fetch from https://github.com/alibaba/open-code-review/releases and place the binary at the path shown in `nextActions[2]`. |
52
67
  | `config-missing` | binary present but `peaksConfig.ocr.llm` is empty or partial | See "Configure" below. |
53
68
  | `detection-failed` | Unexpected error during detection | Inspect stderr; re-run probe. |