peaks-cli 2.0.0 → 2.0.2
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/.claude-plugin/marketplace.json +2 -2
- package/CHANGELOG.md +73 -0
- package/README-en.md +48 -10
- package/README.md +48 -10
- package/dist/src/cli/commands/capability-commands.js +2 -1
- package/dist/src/cli/commands/workspace-commands.js +31 -2
- package/dist/src/lib/render/message-renderer.d.ts +20 -0
- package/dist/src/lib/render/message-renderer.js +80 -0
- package/dist/src/services/config/config-migration.js +21 -2
- package/dist/src/services/config/config-service.d.ts +1 -0
- package/dist/src/services/config/config-service.js +24 -0
- package/dist/src/services/config/config-types.d.ts +15 -0
- package/dist/src/services/config/config-types.js +22 -13
- package/dist/src/services/config/model-routing.js +5 -3
- package/dist/src/services/rd/rd-service.js +29 -1
- package/dist/src/services/skills/sync-service.d.ts +43 -0
- package/dist/src/services/skills/sync-service.js +179 -7
- package/dist/src/services/workflow/workflow-router-service.js +15 -4
- package/dist/src/services/workspace/claude-settings-template.d.ts +53 -0
- package/dist/src/services/workspace/claude-settings-template.js +133 -0
- package/dist/src/services/workspace/workspace-service.d.ts +24 -0
- package/dist/src/services/workspace/workspace-service.js +124 -2
- package/dist/src/shared/version.d.ts +1 -1
- package/dist/src/shared/version.js +1 -1
- package/package.json +1 -1
- package/skills/peaks-solo/SKILL.md +6 -0
- package/skills/peaks-solo/references/anchoring-and-session-info.md +9 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"metadata": {
|
|
3
3
|
"pluginRoot": ".",
|
|
4
|
-
"version": "2.0.
|
|
4
|
+
"version": "2.0.2"
|
|
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.
|
|
10
|
+
"version": "2.0.2",
|
|
11
11
|
"author": {
|
|
12
12
|
"name": "SquabbyZ"
|
|
13
13
|
},
|
package/CHANGELOG.md
CHANGED
|
@@ -231,8 +231,81 @@ is skipped (`PEAKS_SKIP_AUTO_UPGRADE=1` or `npm i --ignore-scripts`).
|
|
|
231
231
|
|
|
232
232
|
---
|
|
233
233
|
|
|
234
|
+
## [2.0.1] — 2026-06-12
|
|
235
|
+
|
|
236
|
+
### Fixed
|
|
237
|
+
|
|
238
|
+
- **Bug 1 — `~/.peaks/config.json` was bloated to 9 top-level fields.**
|
|
239
|
+
The 2.0.0 release moved per-project fields (`language`, `model`,
|
|
240
|
+
`economyMode`, `swarmMode`) to `<project>/.peaks/preferences.json`
|
|
241
|
+
per spec §10.4, but the runtime `DEFAULT_CONFIG` still shipped
|
|
242
|
+
`language` / `model` / `economyMode` / `swarmMode` / `tokens` /
|
|
243
|
+
`providers` / `proxy` / `progress` placeholders. The slim migration
|
|
244
|
+
(`executeMigration`) wrote `{ version: "2.0.0" }` only, but any
|
|
245
|
+
code path that went through `readConfig` and re-serialised
|
|
246
|
+
re-bloated the file. The 2.0.1 fix:
|
|
247
|
+
|
|
248
|
+
1. **Slim `DEFAULT_CONFIG`** to `{ version, ocr: { llm: { url, authToken, model, useAnthropic, authHeader } } }`
|
|
249
|
+
(placeholders for the OCR LLM endpoint only).
|
|
250
|
+
2. **Slim migration write** to the same 2-key form, so a fresh
|
|
251
|
+
`peaks config migrate --apply` produces a discoverable
|
|
252
|
+
`ocr.llm` block the user can paste their endpoint into.
|
|
253
|
+
3. **Tolerant loader.** Legacy 1.x files with extra fields
|
|
254
|
+
(`language`, `model`, `tokens`, `providers`, `proxy`, etc.)
|
|
255
|
+
still load without throwing; the legacy fields are exposed
|
|
256
|
+
via `getConfig` for backward compatibility, and
|
|
257
|
+
`setConfig` rejects writes to `language` / `model` /
|
|
258
|
+
`economyMode` / `swarmMode` with a pointer to
|
|
259
|
+
`<project>/.peaks/preferences.json` (do not silently migrate).
|
|
260
|
+
|
|
261
|
+
The net effect: a freshly-installed peaks-cli writes a 2-key
|
|
262
|
+
`~/.peaks/config.json`; legacy 1.x files migrate to the same
|
|
263
|
+
2-key form; the ocr second-opinion config is now the only
|
|
264
|
+
discoverable surface the user needs to populate to make
|
|
265
|
+
`peaks code-review detect-ocr` report `state: "ready"`.
|
|
266
|
+
|
|
267
|
+
### Verification
|
|
268
|
+
|
|
269
|
+
- 70 config tests pass (`tests/unit/config-*`).
|
|
270
|
+
- `pnpm tsc -p tsconfig.json --noEmit` clean (excluding pre-existing
|
|
271
|
+
sync-service test scaffold for Bug 2).
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## [2.0.2] — 2026-06-13
|
|
276
|
+
|
|
277
|
+
### Changed — README redesign (docs only)
|
|
278
|
+
|
|
279
|
+
The top of both `README.md` and `README-en.md` is rebuilt in the
|
|
280
|
+
RAG-Anything style requested from the published repo: card-grid
|
|
281
|
+
metadata (PROJECT / BASED ON / SKILLS.SH / STARS / VERSION / LICENSE
|
|
282
|
+
/ TESTS / LANG / DOWNLOADS / 中文 / QUICK START / VISITORS), a
|
|
283
|
+
multiline `readme-typing-svg` tagline animation, a
|
|
284
|
+
`github-readme-streak-stats` streak band, and a `komarev` visitor
|
|
285
|
+
counter. Both languages are structurally identical (same card grid,
|
|
286
|
+
same animations, same anchor links); only the tagline and
|
|
287
|
+
call-to-action text differ.
|
|
288
|
+
|
|
289
|
+
- `README.md` updated to the new layout (typing animation uses the
|
|
290
|
+
Chinese tagline: `peaks-cli: 跨 AI IDE 的工程门禁与编排`).
|
|
291
|
+
- `README-en.md` synced to mirror the new layout (typing animation
|
|
292
|
+
uses the English tagline: `peaks-cli: cross-AI-IDE engineering
|
|
293
|
+
gates & orchestration`).
|
|
294
|
+
- Card anchors renamed to ASCII-friendly slugs on the English file
|
|
295
|
+
(`30-seconds-to-running`, `5-minute-onboarding`, `11-skills-in-the-family`,
|
|
296
|
+
`killer-feature-un-bypassable-gates`) so the README renders
|
|
297
|
+
consistently on GitHub's auto-generated anchor list.
|
|
298
|
+
|
|
299
|
+
No code, CLI, or schema changes. The CLI still reports
|
|
300
|
+
`Peaks CLI 2.0.2` after `prepublish` regenerates
|
|
301
|
+
`src/shared/version.ts`.
|
|
302
|
+
|
|
303
|
+
---
|
|
304
|
+
|
|
234
305
|
## [1.4.2] — 2026-06-08
|
|
235
306
|
|
|
236
307
|
Last 1.x release. See git history pre-2.0.0 for details.
|
|
237
308
|
|
|
309
|
+
[2.0.2]: https://github.com/SquabbyZ/peaks-cli/releases/tag/v2.0.2
|
|
310
|
+
[2.0.1]: https://github.com/SquabbyZ/peaks-cli/releases/tag/v2.0.1
|
|
238
311
|
[2.0.0]: https://github.com/SquabbyZ/peaks-cli/releases/tag/v2.0.0
|
package/README-en.md
CHANGED
|
@@ -2,17 +2,57 @@
|
|
|
2
2
|
|
|
3
3
|
# ⛰️ Peaks
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
<img src="https://readme-typing-svg.demolab.com?font=Fira+Code&weight=700&size=22&duration=3000&pause=800&color=6366F1¢er=true&vCenter=true&multiline=true&width=720&height=110&lines=peaks-cli%3A%20cross-AI-IDE%20engineering%20gates%20%26%20orchestration;11%20%E2%9A%99%EF%B8%8F%20workflow%20skills%20%2B%20%E2%9A%96%EF%B8%8F%20executable%20gates;%E2%9C%89%EF%B8%8F%20%E2%9A%96%EF%B8%8F%20%E2%9C%89%EF%B8%8F%20%E2%9A%91%EF%B8%8F%20%E2%9A%96%EF%B8%8F%20%E2%9C%89%EF%B8%8F%20%E2%9A%91%EF%B8%8F%20gates%20%2B%20audit%20%2B%20cross-IDE%20adaptation" alt="peaks-cli tagline typing animation" />
|
|
6
6
|
|
|
7
7
|
**English** | [简体中文](./README.md)
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
<table>
|
|
10
|
+
<tr>
|
|
11
|
+
<td align="center" width="180"><b>🔥 PROJECT</b></td>
|
|
12
|
+
<td align="center" width="180"><b>⚡ BASED ON</b></td>
|
|
13
|
+
<td align="center" width="180"><b>📚 SKILLS.SH</b></td>
|
|
14
|
+
</tr>
|
|
15
|
+
<tr>
|
|
16
|
+
<td align="center"><a href="https://github.com/SquabbyZ/peaks-cli">peaks-cli / Homepage</a></td>
|
|
17
|
+
<td align="center">11 Skills + Cross-IDE</td>
|
|
18
|
+
<td align="center"><a href="https://skills.sh/SquabbyZ/peaks-cli">Listed on skills.sh</a></td>
|
|
19
|
+
</tr>
|
|
20
|
+
<tr><td colspan="3"> </td></tr>
|
|
21
|
+
<tr>
|
|
22
|
+
<td align="center" width="180"><b>⭐ STARS</b></td>
|
|
23
|
+
<td align="center" width="180"><b>📦 VERSION</b></td>
|
|
24
|
+
<td align="center" width="180"><b>📄 LICENSE</b></td>
|
|
25
|
+
</tr>
|
|
26
|
+
<tr>
|
|
27
|
+
<td align="center"><a href="https://github.com/SquabbyZ/peaks-cli/stargazers"><img src="https://img.shields.io/github/stars/SquabbyZ/peaks-cli?style=for-the-badge&logo=github&logoColor=white" alt="stars" /></a></td>
|
|
28
|
+
<td align="center"><a href="https://www.npmjs.com/package/peaks-cli"><img src="https://img.shields.io/npm/v/peaks-cli?style=for-the-badge&logo=npm&logoColor=white&color=CB3837" alt="npm version" /></a></td>
|
|
29
|
+
<td align="center"><a href="./LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue?style=for-the-badge" alt="license" /></a></td>
|
|
30
|
+
</tr>
|
|
31
|
+
<tr><td colspan="3"> </td></tr>
|
|
32
|
+
<tr>
|
|
33
|
+
<td align="center" width="180"><b>🧪 TESTS</b></td>
|
|
34
|
+
<td align="center" width="180"><b>🔧 LANG</b></td>
|
|
35
|
+
<td align="center" width="180"><b>📥 DOWNLOADS</b></td>
|
|
36
|
+
</tr>
|
|
37
|
+
<tr>
|
|
38
|
+
<td align="center"><b>2,800+</b></td>
|
|
39
|
+
<td align="center"><b>TypeScript</b></td>
|
|
40
|
+
<td align="center"><a href="https://www.npmjs.com/package/peaks-cli"><img src="https://img.shields.io/npm/dm/peaks-cli?style=for-the-badge&logo=npm&logoColor=white&color=CB3837" alt="downloads" /></a></td>
|
|
41
|
+
</tr>
|
|
42
|
+
<tr><td colspan="3"> </td></tr>
|
|
43
|
+
<tr>
|
|
44
|
+
<td align="center" width="180"><b>🌐 中文</b></td>
|
|
45
|
+
<td align="center" width="180"><b>🚀 QUICK START</b></td>
|
|
46
|
+
<td align="center" width="180"><b>👁️ VISITORS</b></td>
|
|
47
|
+
</tr>
|
|
48
|
+
<tr>
|
|
49
|
+
<td align="center"><a href="./README.md">简体中文</a></td>
|
|
50
|
+
<td align="center"><a href="#-30-seconds-to-running">30s to running →</a></td>
|
|
51
|
+
<td align="center"><img src="https://komarev.com/ghpvc/?username=SquabbyZ&repo=peaks-cli&label=views&color=blue&style=for-the-badge" alt="visitors" /></td>
|
|
52
|
+
</tr>
|
|
53
|
+
</table>
|
|
54
|
+
|
|
55
|
+
<img src="https://github-readme-streak-stats.herokuapp.com?user=SquabbyZ&repo=peaks-cli&theme=radical&hide_border=true&date_format=j%20M%5B%20Y%5D" alt="GitHub Streak Stats" />
|
|
16
56
|
|
|
17
57
|
[Install](#-30-seconds-to-running) · [5-min onboarding](#-5-minute-onboarding) · [Skill family](#-11-skills-in-the-family) · [Killer feature: un-bypassable gates](#-killer-feature-un-bypassable-gates)
|
|
18
58
|
|
|
@@ -221,6 +261,4 @@ See [`CHANGELOG.md`](./CHANGELOG.md) and [`docs/`](./docs/) for details.
|
|
|
221
261
|
|
|
222
262
|
⭐ [Star peaks-cli on GitHub](https://github.com/SquabbyZ/peaks-cli) · 🔍 [Browse on skills.sh](https://skills.sh/SquabbyZ/peaks-cli)
|
|
223
263
|
|
|
224
|
-
Make your AI IDE work like a disciplined engineering team.
|
|
225
|
-
|
|
226
264
|
</div>
|
package/README.md
CHANGED
|
@@ -2,17 +2,57 @@
|
|
|
2
2
|
|
|
3
3
|
# ⛰️ Peaks
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
<img src="https://readme-typing-svg.demolab.com?font=Fira+Code&weight=700&size=22&duration=3000&pause=800&color=6366F1¢er=true&vCenter=true&multiline=true&width=720&height=110&lines=peaks-cli%3A%20跨%20AI%20IDE%20的工程门禁与编排;11%20个%E2%9A%99%EF%B8%8F%20工作流技能%20%2B%20%E2%9A%96%EF%B8%8F%20可执行门禁;%E2%9C%89%EF%B8%8F%20%E2%9A%96%EF%B8%8F%20%E2%9C%89%EF%B8%8F%20%E2%9A%91%EF%B8%8F%20%E2%9A%96%EF%B8%8F%20%E2%9C%89%EF%B8%8F%20%E2%9A%91%EF%B8%8F%20门禁%20%2B%20审计%20%2B%20跨%20IDE%20适配" alt="peaks-cli tagline typing animation" />
|
|
6
6
|
|
|
7
7
|
[English](./README-en.md) | **简体中文**
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
9
|
+
<table>
|
|
10
|
+
<tr>
|
|
11
|
+
<td align="center" width="180"><b>🔥 PROJECT</b></td>
|
|
12
|
+
<td align="center" width="180"><b>⚡ BASED ON</b></td>
|
|
13
|
+
<td align="center" width="180"><b>📚 SKILLS.SH</b></td>
|
|
14
|
+
</tr>
|
|
15
|
+
<tr>
|
|
16
|
+
<td align="center"><a href="https://github.com/SquabbyZ/peaks-cli">peaks-cli / 首页</a></td>
|
|
17
|
+
<td align="center">11 Skills + Cross-IDE</td>
|
|
18
|
+
<td align="center"><a href="https://skills.sh/SquabbyZ/peaks-cli">在 skills.sh 收录</a></td>
|
|
19
|
+
</tr>
|
|
20
|
+
<tr><td colspan="3"> </td></tr>
|
|
21
|
+
<tr>
|
|
22
|
+
<td align="center" width="180"><b>⭐ STARS</b></td>
|
|
23
|
+
<td align="center" width="180"><b>📦 VERSION</b></td>
|
|
24
|
+
<td align="center" width="180"><b>📄 LICENSE</b></td>
|
|
25
|
+
</tr>
|
|
26
|
+
<tr>
|
|
27
|
+
<td align="center"><a href="https://github.com/SquabbyZ/peaks-cli/stargazers"><img src="https://img.shields.io/github/stars/SquabbyZ/peaks-cli?style=for-the-badge&logo=github&logoColor=white" alt="stars" /></a></td>
|
|
28
|
+
<td align="center"><a href="https://www.npmjs.com/package/peaks-cli"><img src="https://img.shields.io/npm/v/peaks-cli?style=for-the-badge&logo=npm&logoColor=white&color=CB3837" alt="npm version" /></a></td>
|
|
29
|
+
<td align="center"><a href="./LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue?style=for-the-badge" alt="license" /></a></td>
|
|
30
|
+
</tr>
|
|
31
|
+
<tr><td colspan="3"> </td></tr>
|
|
32
|
+
<tr>
|
|
33
|
+
<td align="center" width="180"><b>🧪 TESTS</b></td>
|
|
34
|
+
<td align="center" width="180"><b>🔧 LANG</b></td>
|
|
35
|
+
<td align="center" width="180"><b>📥 DOWNLOADS</b></td>
|
|
36
|
+
</tr>
|
|
37
|
+
<tr>
|
|
38
|
+
<td align="center"><b>2,800+</b></td>
|
|
39
|
+
<td align="center"><b>TypeScript</b></td>
|
|
40
|
+
<td align="center"><a href="https://www.npmjs.com/package/peaks-cli"><img src="https://img.shields.io/npm/dm/peaks-cli?style=for-the-badge&logo=npm&logoColor=white&color=CB3837" alt="downloads" /></a></td>
|
|
41
|
+
</tr>
|
|
42
|
+
<tr><td colspan="3"> </td></tr>
|
|
43
|
+
<tr>
|
|
44
|
+
<td align="center" width="180"><b>🌐 中文</b></td>
|
|
45
|
+
<td align="center" width="180"><b>🚀 QUICK START</b></td>
|
|
46
|
+
<td align="center" width="180"><b>👁️ VISITORS</b></td>
|
|
47
|
+
</tr>
|
|
48
|
+
<tr>
|
|
49
|
+
<td align="center"><a href="./README.md">简体中文</a></td>
|
|
50
|
+
<td align="center"><a href="#-30-秒跑起来">30 秒跑起来 →</a></td>
|
|
51
|
+
<td align="center"><img src="https://komarev.com/ghpvc/?username=SquabbyZ&repo=peaks-cli&label=views&color=blue&style=for-the-badge" alt="visitors" /></td>
|
|
52
|
+
</tr>
|
|
53
|
+
</table>
|
|
54
|
+
|
|
55
|
+
<img src="https://github-readme-streak-stats.herokuapp.com?user=SquabbyZ&repo=peaks-cli&theme=radical&hide_border=true&date_format=j%20M%5B%20Y%5D" alt="GitHub Streak Stats" />
|
|
16
56
|
|
|
17
57
|
[安装](#-30-秒跑起来) · [5 分钟上手](#-5-分钟上手) · [技能家族](#-11-个技能家族) · [杀手锏:不可绕过的门禁](#-杀手锏不可绕过的门禁)
|
|
18
58
|
|
|
@@ -221,6 +261,4 @@ peaks project dashboard / memories
|
|
|
221
261
|
|
|
222
262
|
⭐ [Star peaks-cli on GitHub](https://github.com/SquabbyZ/peaks-cli) · 🔍 [Browse on skills.sh](https://skills.sh/SquabbyZ/peaks-cli)
|
|
223
263
|
|
|
224
|
-
让你的 AI IDE 像一支训练有素的工程团队。
|
|
225
|
-
|
|
226
264
|
</div>
|
|
@@ -27,10 +27,11 @@ export function runCapabilityMap(io, options) {
|
|
|
27
27
|
}
|
|
28
28
|
const config = readConfig();
|
|
29
29
|
const installedCapabilityIds = getInstalledCapabilityIds(config);
|
|
30
|
+
const httpProxy = config.proxy?.httpProxy;
|
|
30
31
|
printResult(io, ok('capabilities.map', createCapabilityMapPlan({
|
|
31
32
|
source,
|
|
32
33
|
installedCapabilityIds,
|
|
33
|
-
...(
|
|
34
|
+
...(httpProxy === undefined ? {} : { httpProxy })
|
|
34
35
|
})), options.json);
|
|
35
36
|
}
|
|
36
37
|
export function getInstalledCapabilityIds(_config) {
|
|
@@ -108,7 +108,8 @@ export function registerWorkspaceCommands(program, io) {
|
|
|
108
108
|
throw new Error(`--install-hooks must be one of: ask, auto, skip (got "${value}")`);
|
|
109
109
|
}
|
|
110
110
|
return value;
|
|
111
|
-
})
|
|
111
|
+
})
|
|
112
|
+
.option('--no-claude-hooks', 'do NOT materialize .claude/settings.local.json (slice 2.0.1-bug3 fact-forcing bypass). Default: hooks installed so tool calls inside .peaks/** are not blocked by the [Fact-Forcing Gate].')).action(async (options) => {
|
|
112
113
|
try {
|
|
113
114
|
// Resolve the session id. Two paths:
|
|
114
115
|
// - explicit --session-id: use it as the requested binding target
|
|
@@ -168,7 +169,13 @@ export function registerWorkspaceCommands(program, io) {
|
|
|
168
169
|
projectRoot,
|
|
169
170
|
sessionId,
|
|
170
171
|
allowSessionRebind: options.allowSessionRebind === true,
|
|
171
|
-
...(options.changeId !== undefined ? { changeId: options.changeId } : {})
|
|
172
|
+
...(options.changeId !== undefined ? { changeId: options.changeId } : {}),
|
|
173
|
+
// Commander translates `--no-claude-hooks` into
|
|
174
|
+
// `options.claudeHooks = false`. The default (no flag) leaves
|
|
175
|
+
// `options.claudeHooks` undefined, which is not equal to
|
|
176
|
+
// `false`, so the default is "install hooks" (the bypass is
|
|
177
|
+
// on). Pass `--no-claude-hooks` to opt out.
|
|
178
|
+
noClaudeHooks: options.claudeHooks === false
|
|
172
179
|
});
|
|
173
180
|
const nextActions = [];
|
|
174
181
|
if (report.previousSessionId !== null && report.bound) {
|
|
@@ -188,6 +195,28 @@ export function registerWorkspaceCommands(program, io) {
|
|
|
188
195
|
else {
|
|
189
196
|
nextActions.push('Run `peaks scan archetype --project <path> --json` next to populate rd/project-scan.md.');
|
|
190
197
|
}
|
|
198
|
+
// Slice 2.0.1-bug3-fact-forcing-bypass: surface the consumer-
|
|
199
|
+
// project .claude/settings.local.json materialization outcome.
|
|
200
|
+
// When the bypass is in effect, the LLM knows subsequent Writes
|
|
201
|
+
// and Bash calls targeting .peaks/** will not be blocked by the
|
|
202
|
+
// [Fact-Forcing Gate]. When the user opted out, we surface a
|
|
203
|
+
// nextAction so the manual recovery is documented.
|
|
204
|
+
if (report.claudeSettings.action === 'written' || report.claudeSettings.action === 'refreshed') {
|
|
205
|
+
nextActions.push(`Materialized .claude/settings.local.json (action: ${report.claudeSettings.action}) — ` +
|
|
206
|
+
`the [Fact-Forcing Gate] is bypassed for tool calls inside .peaks/**. ` +
|
|
207
|
+
'Restart Claude Code so the hooks take effect.');
|
|
208
|
+
}
|
|
209
|
+
else if (report.claudeSettings.action === 'already-current') {
|
|
210
|
+
// No-op: the bypass is already in effect and matches the
|
|
211
|
+
// current release. Do not spam the nextAction list on every
|
|
212
|
+
// init.
|
|
213
|
+
}
|
|
214
|
+
else if (report.claudeSettings.action === 'skipped') {
|
|
215
|
+
nextActions.push('Skipped .claude/settings.local.json materialization (--no-claude-hooks). ' +
|
|
216
|
+
'If the [Fact-Forcing Gate] blocks subsequent Writes, run `peaks workspace init` ' +
|
|
217
|
+
'again without --no-claude-hooks, or drop the contents of ' +
|
|
218
|
+
'`.peaks/.claude-settings-template.json` into `.claude/settings.local.json` manually.');
|
|
219
|
+
}
|
|
191
220
|
// First-time hooks install decision. Sticky-marker at
|
|
192
221
|
// .peaks/.peaks-init-hooks-decision.json records the user's answer
|
|
193
222
|
// (or the auto-decision) so subsequent inits for new sessions in the
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export type MessageRenderMode = 'tty' | 'plain';
|
|
2
|
+
export interface MessageRenderOptions {
|
|
3
|
+
mode: MessageRenderMode;
|
|
4
|
+
/**
|
|
5
|
+
* Optional override. When `true`, the renderer returns the input unchanged
|
|
6
|
+
* regardless of `mode`. Callers should set this when `NO_COLOR` is set,
|
|
7
|
+
* `--no-color` is passed, or `--json` is requested.
|
|
8
|
+
*/
|
|
9
|
+
noColor?: boolean;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Render a human-readable message string for the terminal.
|
|
13
|
+
*
|
|
14
|
+
* Pure function. Returns the input unchanged when:
|
|
15
|
+
* - `input` is empty,
|
|
16
|
+
* - `input` is not a string (defensive: callers occasionally pass numbers/objects),
|
|
17
|
+
* - `mode === 'plain'`,
|
|
18
|
+
* - `noColor === true` (NO_COLOR / --no-color / --json opt-out).
|
|
19
|
+
*/
|
|
20
|
+
export declare function renderMessage(input: string, options: MessageRenderOptions): string;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Slice 2.0.1-ux-message-renderer — pure-function human-text renderer.
|
|
2
|
+
//
|
|
3
|
+
// PURE function contract:
|
|
4
|
+
// - No side effects (does not read process.stdout, does not call console.*).
|
|
5
|
+
// - Returns a string. The caller decides whether to write it to stdout.
|
|
6
|
+
// - Caller resolves the `mode` (TTY detection + opt-outs) and passes it in.
|
|
7
|
+
//
|
|
8
|
+
// Supported transformations (tty mode only — plain mode is a no-op pass-through):
|
|
9
|
+
// 1. OSC 8 hyperlink wrapping for http://, https://, and file:// URLs.
|
|
10
|
+
// Format: ESC]8;;URL ESC\ TEXT ESC]8;; ESC\
|
|
11
|
+
// 2. Markdown-lite: **bold** -> ANSI bold; `code` -> inverse-video.
|
|
12
|
+
// 3. Bullet markers (lines beginning with `- ` or `* `) are preserved as-is.
|
|
13
|
+
//
|
|
14
|
+
// `noColor: true` (caller's signal for `NO_COLOR` / `--no-color` / `--json`) forces
|
|
15
|
+
// the same pass-through behavior as `mode: 'plain'`, even if the caller somehow
|
|
16
|
+
// passed `mode: 'tty'`. This is a defence-in-depth opt-out: the caller should
|
|
17
|
+
// also pass `mode: 'plain'`, but we don't trust that either, because the JSON
|
|
18
|
+
// envelope contract must not leak escape sequences.
|
|
19
|
+
// OSC 8 escape sequence fragments. Format: ESC ] 8 ; ; URL ESC \ TEXT ESC ] 8 ; ; ESC \
|
|
20
|
+
// Browsers + terminals that understand OSC 8: Windows Terminal, iTerm2, WezTerm, recent GNOME Terminal.
|
|
21
|
+
const ESC = '';
|
|
22
|
+
const OSC8_OPEN = `${ESC}]8;;`;
|
|
23
|
+
const OSC8_SEP = `${ESC}\\`;
|
|
24
|
+
const OSC8_CLOSE = `${OSC8_OPEN}${OSC8_SEP}`;
|
|
25
|
+
// ANSI sequences used by the markdown-lite pass.
|
|
26
|
+
const ANSI_BOLD_OPEN = `${ESC}[1m`;
|
|
27
|
+
const ANSI_BOLD_CLOSE = `${ESC}[22m`;
|
|
28
|
+
const ANSI_INVERSE_OPEN = `${ESC}[7m`;
|
|
29
|
+
const ANSI_INVERSE_CLOSE = `${ESC}[27m`;
|
|
30
|
+
// Lightweight URL detector (deliberately not RFC-3986-perfect).
|
|
31
|
+
// Matches http(s):// and file:// tokens up to the first whitespace or
|
|
32
|
+
// common terminator. Trailing punctuation is captured as part of the URL,
|
|
33
|
+
// which is fine for display; the OSC 8 link still works because terminals
|
|
34
|
+
// re-tokenise on hover/click. To stay close to the slice spec's reference
|
|
35
|
+
// pattern we exclude <, >, ", ', and ` from URL characters.
|
|
36
|
+
const URL_PATTERN = /(https?:\/\/|file:\/\/)[^\s<>"'`]+/g;
|
|
37
|
+
// **bold** markers (non-greedy, multi-char safe). Allows ** at word boundaries.
|
|
38
|
+
const BOLD_PATTERN = /\*\*([^*\n]+?)\*\*/g;
|
|
39
|
+
// `inline-code` markers.
|
|
40
|
+
const CODE_PATTERN = /`([^`\n]+?)`/g;
|
|
41
|
+
/**
|
|
42
|
+
* Render a human-readable message string for the terminal.
|
|
43
|
+
*
|
|
44
|
+
* Pure function. Returns the input unchanged when:
|
|
45
|
+
* - `input` is empty,
|
|
46
|
+
* - `input` is not a string (defensive: callers occasionally pass numbers/objects),
|
|
47
|
+
* - `mode === 'plain'`,
|
|
48
|
+
* - `noColor === true` (NO_COLOR / --no-color / --json opt-out).
|
|
49
|
+
*/
|
|
50
|
+
export function renderMessage(input, options) {
|
|
51
|
+
if (typeof input !== 'string' || input.length === 0) {
|
|
52
|
+
return input;
|
|
53
|
+
}
|
|
54
|
+
if (options.mode === 'plain' || options.noColor === true) {
|
|
55
|
+
return input;
|
|
56
|
+
}
|
|
57
|
+
// Markdown-lite first, then URL linking. The order matters: bold/code
|
|
58
|
+
// transformations can wrap parts of a URL (e.g. `\`code\`` containing a URL),
|
|
59
|
+
// so we link URLs on the already-formatted string. Either order would be
|
|
60
|
+
// acceptable; linking on the formatted string means a URL inside a code
|
|
61
|
+
// span still gets hyperlink-wrapped, which is the modern-terminal-friendly
|
|
62
|
+
// choice.
|
|
63
|
+
let out = applyMarkdownLite(input);
|
|
64
|
+
out = applyHyperlinks(out);
|
|
65
|
+
return out;
|
|
66
|
+
}
|
|
67
|
+
function applyMarkdownLite(input) {
|
|
68
|
+
let out = input.replace(BOLD_PATTERN, (_match, inner) => {
|
|
69
|
+
return `${ANSI_BOLD_OPEN}${inner}${ANSI_BOLD_CLOSE}`;
|
|
70
|
+
});
|
|
71
|
+
out = out.replace(CODE_PATTERN, (_match, inner) => {
|
|
72
|
+
return `${ANSI_INVERSE_OPEN}${inner}${ANSI_INVERSE_CLOSE}`;
|
|
73
|
+
});
|
|
74
|
+
return out;
|
|
75
|
+
}
|
|
76
|
+
function applyHyperlinks(input) {
|
|
77
|
+
return input.replace(URL_PATTERN, (url) => {
|
|
78
|
+
return `${OSC8_OPEN}${url}${OSC8_SEP}${url}${OSC8_CLOSE}`;
|
|
79
|
+
});
|
|
80
|
+
}
|
|
@@ -85,8 +85,27 @@ export function executeMigration(opts) {
|
|
|
85
85
|
}
|
|
86
86
|
savePreferences(opts.currentProjectRoot, overrides);
|
|
87
87
|
}
|
|
88
|
-
// 3. Slim config.json —
|
|
88
|
+
// 3. Slim config.json — schema version + discoverable ocr.llm placeholders.
|
|
89
|
+
// Per the 2.0.1 slim spec, the on-disk `~/.peaks/config.json` is
|
|
90
|
+
// `{ "version": "2.0.1", "ocr": { "llm": { ... } } }`. Legacy fields
|
|
91
|
+
// (language, model, economyMode, swarmMode, tokens, providers,
|
|
92
|
+
// proxy) live in <project>/.peaks/preferences.json. peaks-cli writes
|
|
93
|
+
// the `ocr.llm.*` placeholders so the user has a discoverable spot
|
|
94
|
+
// to paste their endpoint; the placeholders are empty strings, not
|
|
95
|
+
// auto-configured values, so the post-migration file MUST contain
|
|
96
|
+
// the `ocr.llm.*` block with empty defaults.
|
|
89
97
|
mkdirSync(join(homedir(), '.peaks'), { recursive: true });
|
|
90
|
-
writeFileSync(configPath, JSON.stringify({
|
|
98
|
+
writeFileSync(configPath, JSON.stringify({
|
|
99
|
+
version: CONFIG_SCHEMA_VERSION_V2,
|
|
100
|
+
ocr: {
|
|
101
|
+
llm: {
|
|
102
|
+
url: '',
|
|
103
|
+
authToken: '',
|
|
104
|
+
model: '',
|
|
105
|
+
useAnthropic: false,
|
|
106
|
+
authHeader: 'authorization'
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}, null, 2) + '\n', 'utf8');
|
|
91
110
|
return { ...plan, applied: true, backupPath: bak, newConfigPath: configPath };
|
|
92
111
|
}
|
|
@@ -16,6 +16,7 @@ export { resolveProjectRootForConfig, resolveCanonicalProjectRoot } from './conf
|
|
|
16
16
|
export declare function loadGlobalConfig(): ConfigV2 | null;
|
|
17
17
|
export declare function isConfigLayer(value: string): value is ConfigLayer;
|
|
18
18
|
export declare function isSensitiveConfigPath(path: string): boolean;
|
|
19
|
+
export declare function isLegacyConfigKey(path: string): boolean;
|
|
19
20
|
export declare function containsSensitiveConfigValue(value: unknown): boolean;
|
|
20
21
|
export type RedactedConfigValue = string | number | boolean | null | RedactedConfigValue[] | {
|
|
21
22
|
[key: string]: RedactedConfigValue;
|
|
@@ -116,6 +116,26 @@ export function isSensitiveConfigPath(path) {
|
|
|
116
116
|
const normalized = path.toLowerCase().replace(/[^a-z0-9]/g, '');
|
|
117
117
|
return normalized.includes('apikey') || normalized.includes('accesskey') || normalized.includes('privatekey') || normalized.includes('token') || normalized.includes('secret') || normalized.includes('password') || normalized.includes('bearer') || normalized.includes('credential') || normalized.includes('auth');
|
|
118
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* 2.0.1 slim-config contract: `~/.peaks/config.json` only stores
|
|
121
|
+
* `version` + `ocr.llm.*` placeholders. The 1.x → 2.0 migration
|
|
122
|
+
* moved per-project fields (`language`, `model`, `economyMode`,
|
|
123
|
+
* `swarmMode`) to `<project>/.peaks/preferences.json` (per spec
|
|
124
|
+
* §10.4). `setConfig` rejects writes to those keys and points the
|
|
125
|
+
* user to the preferences path; tokens / providers / proxy still
|
|
126
|
+
* live in `~/.peaks/config.json` (the loader is tolerant of them
|
|
127
|
+
* but does not synthesise defaults for them anymore).
|
|
128
|
+
*/
|
|
129
|
+
const LEGACY_CONFIG_KEYS = new Set([
|
|
130
|
+
'language',
|
|
131
|
+
'model',
|
|
132
|
+
'economyMode',
|
|
133
|
+
'swarmMode'
|
|
134
|
+
]);
|
|
135
|
+
export function isLegacyConfigKey(path) {
|
|
136
|
+
const topLevel = path.split(/[.[].*/, 1)[0] ?? '';
|
|
137
|
+
return LEGACY_CONFIG_KEYS.has(topLevel);
|
|
138
|
+
}
|
|
119
139
|
function isProviderConfigPath(path) {
|
|
120
140
|
return path === 'providers' || path.startsWith('providers.');
|
|
121
141
|
}
|
|
@@ -551,6 +571,10 @@ export function setConfig(options) {
|
|
|
551
571
|
if (!isConfigLayer(layer)) {
|
|
552
572
|
throw new Error('Invalid config layer');
|
|
553
573
|
}
|
|
574
|
+
if (isLegacyConfigKey(options.key)) {
|
|
575
|
+
throw new Error(`Legacy config key "${options.key}" is no longer stored in ~/.peaks/config.json. ` +
|
|
576
|
+
'Set it under <project>/.peaks/preferences.json (e.g. `peaks preferences set --key <key> --value <value>`).');
|
|
577
|
+
}
|
|
554
578
|
if (layer === 'project' && (isProviderConfigPath(options.key) || isProxyConfigPath(options.key) || isSensitiveConfigPath(options.key) || containsSensitiveConfigValue(options.value))) {
|
|
555
579
|
throw new Error('Sensitive config keys must be stored in the user config layer');
|
|
556
580
|
}
|
|
@@ -132,6 +132,21 @@ export type ConfigSetOptions = {
|
|
|
132
132
|
value: unknown;
|
|
133
133
|
layer?: ConfigLayer;
|
|
134
134
|
};
|
|
135
|
+
/**
|
|
136
|
+
* 2.0.1 slim runtime default. The on-disk `~/.peaks/config.json`
|
|
137
|
+
* only carries `version` + `ocr.llm.*` placeholders. Legacy fields
|
|
138
|
+
* (language / model / economyMode / swarmMode / tokens / providers /
|
|
139
|
+
* proxy) live in `<project>/.peaks/preferences.json` (per spec
|
|
140
|
+
* §10.4) and are NOT synthesised here — `readConfig()` merges the
|
|
141
|
+
* user file over this default, and any legacy field that the user
|
|
142
|
+
* file still carries (1.x file) is exposed via `getConfig` for
|
|
143
|
+
* backward compatibility.
|
|
144
|
+
*
|
|
145
|
+
* Cast to `PeaksConfig` because the type still declares the legacy
|
|
146
|
+
* fields as required (they are part of the `readConfig()` contract
|
|
147
|
+
* for tolerant loading of pre-2.0.1 files); the runtime default
|
|
148
|
+
* itself does not supply them.
|
|
149
|
+
*/
|
|
135
150
|
export declare const DEFAULT_CONFIG: PeaksConfig;
|
|
136
151
|
/**
|
|
137
152
|
* Slim 2.0 schema for `~/.peaks/config.json`. After migration,
|
|
@@ -1,21 +1,30 @@
|
|
|
1
1
|
import { CLI_VERSION } from '../../shared/version.js';
|
|
2
2
|
import { CONFIG_SCHEMA_VERSION_V2 } from './config-migration.js';
|
|
3
|
+
/**
|
|
4
|
+
* 2.0.1 slim runtime default. The on-disk `~/.peaks/config.json`
|
|
5
|
+
* only carries `version` + `ocr.llm.*` placeholders. Legacy fields
|
|
6
|
+
* (language / model / economyMode / swarmMode / tokens / providers /
|
|
7
|
+
* proxy) live in `<project>/.peaks/preferences.json` (per spec
|
|
8
|
+
* §10.4) and are NOT synthesised here — `readConfig()` merges the
|
|
9
|
+
* user file over this default, and any legacy field that the user
|
|
10
|
+
* file still carries (1.x file) is exposed via `getConfig` for
|
|
11
|
+
* backward compatibility.
|
|
12
|
+
*
|
|
13
|
+
* Cast to `PeaksConfig` because the type still declares the legacy
|
|
14
|
+
* fields as required (they are part of the `readConfig()` contract
|
|
15
|
+
* for tolerant loading of pre-2.0.1 files); the runtime default
|
|
16
|
+
* itself does not supply them.
|
|
17
|
+
*/
|
|
3
18
|
export const DEFAULT_CONFIG = {
|
|
4
19
|
version: CLI_VERSION,
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
model: 'minimax-2.7'
|
|
20
|
+
ocr: {
|
|
21
|
+
llm: {
|
|
22
|
+
url: '',
|
|
23
|
+
authToken: '',
|
|
24
|
+
model: '',
|
|
25
|
+
useAnthropic: false,
|
|
26
|
+
authHeader: 'authorization'
|
|
13
27
|
}
|
|
14
|
-
},
|
|
15
|
-
proxy: {},
|
|
16
|
-
progress: {
|
|
17
|
-
enabled: true,
|
|
18
|
-
heartbeatIntervalMs: 60000
|
|
19
28
|
}
|
|
20
29
|
};
|
|
21
30
|
export function isConfigV2(raw) {
|
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import { DEFAULT_CONFIG } from './config-types.js';
|
|
2
1
|
export const STRONGEST_MODEL_ID = 'claude-opus-4-7';
|
|
3
2
|
export function getConfiguredExecutionModelId(providers) {
|
|
4
|
-
const providerConfigs = Object.values(providers ??
|
|
3
|
+
const providerConfigs = Object.values(providers ?? {});
|
|
5
4
|
const configuredModel = providerConfigs
|
|
6
5
|
.map((provider) => provider?.model?.trim())
|
|
7
6
|
.find((model) => typeof model === 'string' && model.length > 0);
|
|
@@ -11,5 +10,8 @@ export function getConfiguredExecutionModelId(providers) {
|
|
|
11
10
|
return configuredModel;
|
|
12
11
|
}
|
|
13
12
|
export function getEconomyAwareExecutionModelId(config) {
|
|
14
|
-
|
|
13
|
+
// Slice 2.0.1-bug1 round 3: economy is the project default. Treat undefined as enabled
|
|
14
|
+
// (matches the pre-slice implicit default from DEFAULT_CONFIG.economyMode = true). Only an
|
|
15
|
+
// explicit `economyMode === false` switches execution to STRONGEST_MODEL_ID.
|
|
16
|
+
return config.economyMode !== false ? getConfiguredExecutionModelId(config.providers) : STRONGEST_MODEL_ID;
|
|
15
17
|
}
|
|
@@ -5,6 +5,22 @@ import { validateChangeIdOrThrow, buildArtifactRelativePath } from '../../shared
|
|
|
5
5
|
import { WORKSPACE_UNAVAILABLE_NEXT_ACTIONS } from '../../shared/planner-response.js';
|
|
6
6
|
import { getLocalArtifactPath, hasValidArtifactWorkspace } from '../artifacts/workspace-service.js';
|
|
7
7
|
import { getConfiguredExecutionModelId, STRONGEST_MODEL_ID } from '../config/model-routing.js';
|
|
8
|
+
/**
|
|
9
|
+
* 2.0.1-bug1: the slim 2.0 `~/.peaks/config.json` no longer carries a
|
|
10
|
+
* `providers` block (legacy model config lives in
|
|
11
|
+
* `.peaks/preferences.json` per spec §10.4). `buildPlan` is a pure
|
|
12
|
+
* planner function and historically took its execution model from
|
|
13
|
+
* `DEFAULT_CONFIG.providers.minimax.model`; with the slim default
|
|
14
|
+
* that field is `undefined`, so `getConfiguredExecutionModelId`
|
|
15
|
+
* would throw. We retain the pre-2.0 default here as a literal so
|
|
16
|
+
* the planner remains usable when the caller has not passed an
|
|
17
|
+
* explicit `executionModelId` (unit tests, dry-run previews, the
|
|
18
|
+
* `peaks swarm plan` onboarding path). Production callers that
|
|
19
|
+
* have a real `ocr.llm.model` configured pass it via
|
|
20
|
+
* `request.executionModelId` (or via the legacy preferences.json
|
|
21
|
+
* bridge) and bypass this fallback.
|
|
22
|
+
*/
|
|
23
|
+
const DEFAULT_EXECUTION_MODEL_ID = 'minimax-2.7';
|
|
8
24
|
import { getTechStatus, TECH_REQUIRED_ARTIFACTS } from '../tech/tech-service.js';
|
|
9
25
|
function normalizeGoal(goal) {
|
|
10
26
|
const normalized = goal.trim();
|
|
@@ -136,6 +152,18 @@ function readArtifactFile(rootPath, artifactWorkspacePath, artifact) {
|
|
|
136
152
|
return null;
|
|
137
153
|
}
|
|
138
154
|
}
|
|
155
|
+
function resolveExecutionModelId() {
|
|
156
|
+
try {
|
|
157
|
+
return getConfiguredExecutionModelId(undefined);
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
// 2.0.1-bug1: with the slim `~/.peaks/config.json` the legacy
|
|
161
|
+
// `providers` block is gone, so the configured-model lookup is
|
|
162
|
+
// expected to throw. Fall back to the pre-2.0 default so the
|
|
163
|
+
// planner remains usable in unit tests and the dry-run path.
|
|
164
|
+
return DEFAULT_EXECUTION_MODEL_ID;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
139
167
|
function getConcreteTargetAreas(request, artifactWorkspacePath, hasApprovedTechArtifacts) {
|
|
140
168
|
if (!artifactWorkspacePath || !hasApprovedTechArtifacts || !hasPlannerArtifactWorkspace(request, artifactWorkspacePath)) {
|
|
141
169
|
return [];
|
|
@@ -154,7 +182,7 @@ function buildPlan(request) {
|
|
|
154
182
|
validateChangeIdOrThrow(request.changeId);
|
|
155
183
|
const goal = normalizeGoal(request.goal);
|
|
156
184
|
const swarmMode = request.swarmMode ?? true;
|
|
157
|
-
const executionModelId = request.executionModelId?.trim() ||
|
|
185
|
+
const executionModelId = request.executionModelId?.trim() || resolveExecutionModelId();
|
|
158
186
|
const { workerTarget, blockedReasons } = resolveWorkerTarget(request.maxWorkers);
|
|
159
187
|
const artifactWorkspacePath = resolveArtifactWorkspacePath(request);
|
|
160
188
|
const artifactRoot = buildArtifactRelativePath(request.changeId, 'rd', 'swarm');
|