peaks-cli 1.3.0 → 1.3.1
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/README.md +62 -46
- package/dist/src/cli/commands/hooks-commands.js +24 -9
- package/dist/src/cli/commands/progress-commands.js +26 -2
- package/dist/src/cli/commands/request-commands.js +5 -0
- package/dist/src/cli/commands/slice-commands.d.ts +3 -0
- package/dist/src/cli/commands/slice-commands.js +42 -0
- package/dist/src/cli/commands/workflow-commands.js +3 -3
- package/dist/src/cli/commands/workspace-commands.d.ts +63 -0
- package/dist/src/cli/commands/workspace-commands.js +288 -4
- package/dist/src/cli/program.js +4 -0
- package/dist/src/services/artifacts/artifact-prerequisites.d.ts +17 -1
- package/dist/src/services/artifacts/artifact-prerequisites.js +38 -5
- package/dist/src/services/artifacts/request-artifact-service.d.ts +22 -0
- package/dist/src/services/artifacts/request-artifact-service.js +172 -54
- package/dist/src/services/doctor/doctor-service.d.ts +7 -0
- package/dist/src/services/doctor/doctor-service.js +20 -2
- package/dist/src/services/progress/progress-service.d.ts +26 -0
- package/dist/src/services/progress/progress-service.js +25 -0
- package/dist/src/services/sc/sc-service.js +71 -13
- package/dist/src/services/scan/acceptance-coverage-service.js +6 -2
- package/dist/src/services/session/session-manager.js +12 -2
- package/dist/src/services/skills/hooks-settings-service.d.ts +25 -3
- package/dist/src/services/skills/hooks-settings-service.js +57 -13
- package/dist/src/services/slice/slice-check-service.d.ts +2 -0
- package/dist/src/services/slice/slice-check-service.js +248 -0
- package/dist/src/services/slice/slice-check-types.d.ts +61 -0
- package/dist/src/services/slice/slice-check-types.js +18 -0
- package/dist/src/services/workflow/pipeline-verify-service.d.ts +5 -2
- package/dist/src/services/workflow/pipeline-verify-service.js +35 -35
- package/dist/src/services/workspace/migrate-service.d.ts +2 -0
- package/dist/src/services/workspace/migrate-service.js +484 -0
- package/dist/src/services/workspace/migrate-types.d.ts +84 -0
- package/dist/src/services/workspace/migrate-types.js +21 -0
- package/dist/src/services/workspace/workspace-service.d.ts +11 -0
- package/dist/src/services/workspace/workspace-service.js +87 -7
- package/dist/src/shared/change-id.d.ts +59 -0
- package/dist/src/shared/change-id.js +194 -16
- package/dist/src/shared/version.d.ts +1 -1
- package/dist/src/shared/version.js +1 -1
- package/package.json +10 -2
- package/skills/peaks-solo/SKILL.md +11 -1
- package/skills/peaks-solo/references/micro-cycle.md +155 -0
package/README.md
CHANGED
|
@@ -1,36 +1,35 @@
|
|
|
1
1
|
# Peaks
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
CLI 是这些技能在背后调用的引擎,负责「门禁 + JSON 契约 + 不可逆动作」。
|
|
3
|
+
[English](./README-en.md) | **简体中文**
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
[](https://www.npmjs.com/package/peaks-cli)
|
|
6
|
+
[](https://github.com/SquabbyZ/peaks-cli)
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
```bash
|
|
11
|
-
npm install -g peaks-cli
|
|
12
|
-
```
|
|
8
|
+
Peaks 是一个**跨 AI IDE 的工作流门禁 CLI + 技能家族**——把项目治理、工作流规划、受控执行、QA 验证、变更追踪组织成可复用的工程流程。CLI 是跨 IDE 稳定的核心(门禁 + JSON 契约 + 不可逆动作),技能 / 钩子 / 配置按各 IDE 的原生格式承载。
|
|
13
9
|
|
|
14
|
-
|
|
10
|
+
> **支持的 IDE**:
|
|
11
|
+
> - ✅ **Claude Code**(当前实现):11 个 `peaks-*` 技能 + `.claude/settings.json` PreToolUse hook
|
|
12
|
+
> - 🚧 **Trae**(适配中):用 Trae 的原生配置承载相同的角色 / 状态机 / 门禁
|
|
13
|
+
> - 📋 **Codex / Cursor / Qoder / 通义灵码 等**(路线图)
|
|
15
14
|
|
|
16
|
-
|
|
15
|
+
> **产品定位**:你**用技能工作**,CLI 是跨 IDE 的质量保障层。
|
|
16
|
+
>
|
|
17
|
+
> 我们的目标:让 LLM 在每个环节里自由判断与决策;peaks 在流程边界上提供可审计的 SOP 护栏,把项目级记忆和使用经验沉淀下来——AI IDE 和 LLM 在你的项目上用得越久就越懂它。
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
## 安装
|
|
19
20
|
|
|
20
21
|
```bash
|
|
21
|
-
|
|
22
|
-
pnpm exec tsx src/cli/index.ts --version # 应打印 1.2.9
|
|
23
|
-
pnpm exec tsx src/cli/index.ts <cmd> # 与全局 `peaks <cmd>` 行为一致
|
|
22
|
+
npm install -g peaks-cli
|
|
24
23
|
```
|
|
25
24
|
|
|
26
|
-
|
|
25
|
+
安装后,Peaks 会把内置的 11 个 `peaks-*` 技能注册到已适配的 AI IDE(当前:Claude Code),会话里直接通过技能名调用即可。
|
|
27
26
|
|
|
28
27
|
## 5 分钟上手
|
|
29
28
|
|
|
30
|
-
|
|
29
|
+
在已适配的 AI IDE 对话里,**直接对 AI 说「用 X 技能做 Y」** 即可,技能会接管剩下的所有流程:
|
|
31
30
|
|
|
32
31
|
```text
|
|
33
|
-
peaks-solo
|
|
32
|
+
peaks-solo 帮我给登录页加 OAuth 回调 # 第一次显性用 peaks-solo;项目根 = IDE 当前 cwd
|
|
34
33
|
peaks-prd 为会员邀请功能整理产品目标、非目标和验收标准
|
|
35
34
|
peaks-rd 分析这次重构的最小实现切片和风险
|
|
36
35
|
peaks-qa 为这次改动设计测试和回归验证清单
|
|
@@ -40,14 +39,22 @@ peaks-txt 为当前模块生成上下文胶囊,保留关键决策
|
|
|
40
39
|
peaks-sop 帮我把"内容发布"流程变成带门禁的 SOP
|
|
41
40
|
```
|
|
42
41
|
|
|
43
|
-
|
|
42
|
+
第一次使用?分两层:**你做 2 步,peaks 接管剩下**。
|
|
43
|
+
|
|
44
|
+
**你需要做的(2 步)**:
|
|
45
|
+
|
|
46
|
+
1. 在项目目录里打开已适配的 AI IDE:`cd /path/to/your-project && <你的 IDE 命令,如 claude>`——让 IDE 知道项目根在哪
|
|
47
|
+
2. 在 IDE 里对 AI 说:**`peaks-solo 帮我做 X`**(X = 需求描述,如"给登录页加 OAuth 回调")
|
|
48
|
+
- LLM 会按任务和项目推荐模式;想直接定可以写 `peaks-solo 全自动做 X` / `peaks-solo swarm X` / `peaks-solo strict X`
|
|
44
49
|
|
|
45
|
-
|
|
46
|
-
2. 技能会自动跑:`peaks workspace init` → `peaks scan archetype` → 生成 `.peaks/<session-id>/rd/project-scan.md`
|
|
47
|
-
3. 接着说你要做的需求,技能会按 PRD → RD → UI → QA → SC → TXT 的顺序把流程走完
|
|
48
|
-
4. 工作流结束时,技能会把所有中间产物留在 `.peaks/<session-id>/`,并把"该记住的事实"写进 `.peaks/memory/`
|
|
50
|
+
**之后 peaks 会自动**:
|
|
49
51
|
|
|
50
|
-
|
|
52
|
+
- 跑 `peaks workspace init`(首次会创建 `.peaks/`)→ `peaks scan archetype` → 生成 `.peaks/<session-id>/rd/project-scan.md`
|
|
53
|
+
- 复杂任务按 PRD → RD → UI → QA → SC → TXT 协调;简单任务直接走 solo 全自动,无需分阶段
|
|
54
|
+
- 工作流进行中可用 `peaks-solo-status` 看看当前到哪了;中断后用 `peaks-solo-resume` 继续
|
|
55
|
+
- 工作流结束时把所有中间产物留在 `.peaks/<session-id>/`,并把"该记住的事实"写进 `.peaks/memory/`
|
|
56
|
+
|
|
57
|
+
想要随时确认状态?让 AI 跑一下:
|
|
51
58
|
|
|
52
59
|
```bash
|
|
53
60
|
peaks -V # 版本号
|
|
@@ -69,28 +76,44 @@ peaks project dashboard --project . --json # 当前项目 dashboard
|
|
|
69
76
|
| `peaks-sc` | 变更追踪、commit 边界、artifact 留存、回滚证据 | 影响范围记录、回滚证据、变更控制 |
|
|
70
77
|
| `peaks-txt` | 上下文胶囊、决策记录、知识压缩 | 模块理解、关键决策留存、复盘 |
|
|
71
78
|
| `peaks-sop` | **把你的工作流变成带门禁的 SOP**(不是研发专属) | 内容发布、合规清单、数据 pipeline、运维 runbook、个人流程 |
|
|
79
|
+
| `peaks-solo-resume` | **继续刚才没做完的切片**——一键检测 in-flight slice 的最深已完成 gate,省 3-5k token | 「继续完成刚才为完成的」「resume the unfinished slice」 |
|
|
80
|
+
| `peaks-solo-status` | **看一眼现在到哪了**——5-CLI snapshot 表格(presence + session + dashboard + request + memory) | 「现在到哪了」「show me the dashboard」 |
|
|
81
|
+
| `peaks-solo-test` | **跑项目测试**——从 `package.json` 探测测试工具(vitest / jest / mocha / pytest / ...),用项目原生命令跑并汇总 pass/fail | 「跑测试」「run the tests」 |
|
|
82
|
+
|
|
83
|
+
### 两种基本用法
|
|
72
84
|
|
|
73
|
-
|
|
85
|
+
**1. 让 `peaks-solo` 编排(最常用)**
|
|
74
86
|
|
|
75
|
-
|
|
87
|
+
`peaks-solo` 是产品入口,**绝大多数场景都用它**——告诉它做什么,剩下 PRD / UI / RD / QA / SC / TXT 的链路它自己协调。**默认模式不写死**:LLM 根据任务复杂度和项目状态主动推荐 assisted / full-auto / swarm / strict 中的一种;想自己指定时再显式标注:
|
|
76
88
|
|
|
77
89
|
```text
|
|
78
|
-
peaks-
|
|
90
|
+
peaks-solo 帮我做 X # 默认(不写死,LLM 按任务和项目推荐);X = 需求描述;项目路径走 IDE 当前 cwd
|
|
91
|
+
peaks-solo 全自动做 X # 显式 full-auto:端到端跑完
|
|
92
|
+
peaks-solo swarm 模式做 X # 显式 swarm:最大化子代理并行(适合大任务)
|
|
93
|
+
peaks-solo strict 模式做 X # 显式 strict:最严格门禁
|
|
79
94
|
```
|
|
80
95
|
|
|
81
|
-
|
|
96
|
+
3 个 `peaks-solo-*` 包装技能是 solo 的轻量变体(不算单独角色):
|
|
82
97
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
peaks-solo(编排执行) → peaks-sc(变更证据)
|
|
87
|
-
```
|
|
98
|
+
- `peaks-solo-resume` —— 继续刚才没做完的切片
|
|
99
|
+
- `peaks-solo-status` —— 看看现在到哪了
|
|
100
|
+
- `peaks-solo-test` —— 跑项目测试
|
|
88
101
|
|
|
89
|
-
|
|
102
|
+
**2. 直接调用单个角色技能(进阶)**
|
|
90
103
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
104
|
+
只有当你只想做工作流的**一个阶段**(比如只写 PRD、只做架构分析、只跑回归),不想走 full pipeline 时,才单独调这些:
|
|
105
|
+
|
|
106
|
+
| 技能 | 你用它做什么 | 什么时候才需要 |
|
|
107
|
+
|---|---|---|
|
|
108
|
+
| `peaks-prd` | 写 / 改 PRD(目标、非目标、行为保留、验收标准) | 想自己定义需求,不走 solo 整流程 |
|
|
109
|
+
| `peaks-rd` | 架构分析 + 最小切片规划 + 风险 | 只想拿一份技术分析,不要写代码 |
|
|
110
|
+
| `peaks-qa` | 测试用例 + 回归矩阵 + 验收证据 | 只想补测试,不走完整 solo |
|
|
111
|
+
| `peaks-ui` | 视觉方向 + 交互方案 + 设计系统约束 | 只做 UI 设计,不带实现 |
|
|
112
|
+
| `peaks-sc` | 影响范围 + commit 边界 + 留存策略 | 只想记录变更,不触发整流程 |
|
|
113
|
+
| `peaks-txt` | 上下文胶囊 + 决策记录 | 只想压缩知识,不走整流程 |
|
|
114
|
+
| `peaks-sop` | 把任意工作流(不只是开发)变成带门禁的 SOP | 想定义 / 注册自己的 SOP |
|
|
115
|
+
|
|
116
|
+
**3 个 solo 包装 + 7 个角色技能 + 1 个 solo 编排 = 11 个技能家族。** 日常使用中,1 个(`peaks-solo`)覆盖 ≥90% 的需求。
|
|
94
117
|
|
|
95
118
|
## 怎么用:技能优先,CLI 是门禁
|
|
96
119
|
|
|
@@ -108,8 +131,11 @@ Peaks 里的 `peaks <cmd>` CLI **不是日常使用的主要入口**。它的存
|
|
|
108
131
|
|
|
109
132
|
```bash
|
|
110
133
|
peaks workspace init --project <repo> --json # 创建 .peaks/ 工作区(每个 session 一次)
|
|
134
|
+
peaks workspace reconcile --project <repo> --json # 4-tier heuristic 重新指向 canonical session,干掉孤儿 dir(默认 dry-run,--apply 才删)
|
|
111
135
|
peaks scan archetype --project <repo> --json # 探测项目原型(greenfield/legacy-frontend/...)
|
|
136
|
+
peaks scan libraries --project <repo> --json # 枚举依赖 + 解析 major,支持 monorepo
|
|
112
137
|
peaks request init/show/transition # PRD/RD/QA/SC 的请求状态机
|
|
138
|
+
peaks session list/info/title/rotate # session 元数据;rotate 丢弃绑定让下次 peaks 自动重生成
|
|
113
139
|
peaks sop init/lint/check/advance/register # 你的自定义 SOP 生命周期
|
|
114
140
|
peaks hooks install --project <repo> # 装门禁的 PreToolUse hook
|
|
115
141
|
peaks project dashboard --project <repo> --json # 整个项目一眼看完
|
|
@@ -161,16 +187,6 @@ peaks hooks install --project <repo> # 显式 opt-in:装一条 PreToolUse
|
|
|
161
187
|
|
|
162
188
|
> **两层定义、执行按项目**:SOP 定义(`sop.json` + `SKILL.md`)可以放在**全局** `~/.peaks/sops/`(个人跨项目复用)或**仓库** `<repo>/.peaks/sops/`(随仓库提交、团队共享;`peaks sop init/register --project <repo>`)。**仓库层优先**于全局层。运行态(当前阶段、历史)按项目落在 `<project>/.peaks/sop-state/<sop-id>/`。
|
|
163
189
|
|
|
164
|
-
## 工程结构(了解 peaks-cli 本身)
|
|
165
|
-
|
|
166
|
-
```text
|
|
167
|
-
skills/ # 8 个 SKILL.md(peaks-solo / -prd / -rd / -qa / -ui / -sc / -txt / -sop)
|
|
168
|
-
src/cli/ # CLI 引擎(commands/、services/、hooks/、memory/、sop/、scan/、...)
|
|
169
|
-
bin/peaks.js # 入口
|
|
170
|
-
docs/ # 设计文档
|
|
171
|
-
openspec/ # 内部 OpenSpec 变更提案
|
|
172
|
-
```
|
|
173
|
-
|
|
174
190
|
## 许可
|
|
175
191
|
|
|
176
192
|
MIT License,详见 [LICENSE](LICENSE)。
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { fail, ok } from '../../shared/result.js';
|
|
2
2
|
import { addJsonOption, printResult, getErrorMessage } from '../cli-helpers.js';
|
|
3
3
|
import { findProjectRoot } from '../../services/config/config-safety.js';
|
|
4
|
-
import { applyHookInstall, planHookInstall, readHookStatus, removeHookInstall } from '../../services/skills/hooks-settings-service.js';
|
|
4
|
+
import { applyHookInstall, PEAKS_HOOK_ENTRIES, planHookInstall, readHookStatus, removeHookInstall } from '../../services/skills/hooks-settings-service.js';
|
|
5
5
|
function resolveScope(options) {
|
|
6
6
|
return options.global ? 'global' : 'project';
|
|
7
7
|
}
|
|
@@ -11,10 +11,10 @@ function resolveProjectRoot(scope, project) {
|
|
|
11
11
|
export function registerHooksCommands(program, io) {
|
|
12
12
|
const hooks = program
|
|
13
13
|
.command('hooks')
|
|
14
|
-
.description('Manage the Peaks
|
|
14
|
+
.description('Manage the Peaks PreToolUse hooks in .claude/settings.json: (1) Bash→peaks gate enforce (SOP gate), (2) Task→peaks progress start (auto-spawn sub-agent progress terminal). Both are installed / removed together.');
|
|
15
15
|
addJsonOption(hooks
|
|
16
16
|
.command('install')
|
|
17
|
-
.description(
|
|
17
|
+
.description(`Install all peaks-managed PreToolUse hooks (${PEAKS_HOOK_ENTRIES.map((e) => e.matcher).join(', ')}) into the target .claude/settings.json. Idempotent: re-runs are no-ops. Project scope by default.`)
|
|
18
18
|
.option('--global', 'install into the user-level ~/.claude/settings.json instead of the project')
|
|
19
19
|
.option('--project <path>', 'project root path (auto-detected from cwd when omitted)')
|
|
20
20
|
.option('--dry-run', 'show what would change without writing')).action((options) => {
|
|
@@ -23,14 +23,26 @@ export function registerHooksCommands(program, io) {
|
|
|
23
23
|
try {
|
|
24
24
|
if (options.dryRun === true) {
|
|
25
25
|
const plan = planHookInstall(scope, projectRoot);
|
|
26
|
-
printResult(io, ok('hooks.install', {
|
|
26
|
+
printResult(io, ok('hooks.install', {
|
|
27
|
+
...plan,
|
|
28
|
+
applied: false,
|
|
29
|
+
dryRun: true,
|
|
30
|
+
entries: PEAKS_HOOK_ENTRIES.map((e) => ({ matcher: e.matcher, sentinel: e.sentinel }))
|
|
31
|
+
}, [], [`would install ${PEAKS_HOOK_ENTRIES.length} peaks-managed hook entries`]), options.json);
|
|
27
32
|
return;
|
|
28
33
|
}
|
|
29
34
|
const result = applyHookInstall(scope, projectRoot);
|
|
30
35
|
const nextActions = result.applied
|
|
31
|
-
? [
|
|
36
|
+
? [
|
|
37
|
+
'Restart Claude Code (or reload the window) so the PreToolUse hooks take effect',
|
|
38
|
+
`Installed: ${PEAKS_HOOK_ENTRIES.map((e) => `${e.matcher}→${e.sentinel}`).join(', ')}`
|
|
39
|
+
]
|
|
32
40
|
: [];
|
|
33
|
-
printResult(io, ok('hooks.install', {
|
|
41
|
+
printResult(io, ok('hooks.install', {
|
|
42
|
+
...result,
|
|
43
|
+
dryRun: false,
|
|
44
|
+
entries: PEAKS_HOOK_ENTRIES.map((e) => ({ matcher: e.matcher, sentinel: e.sentinel }))
|
|
45
|
+
}, [], nextActions), options.json);
|
|
34
46
|
}
|
|
35
47
|
catch (error) {
|
|
36
48
|
const message = getErrorMessage(error);
|
|
@@ -40,7 +52,7 @@ export function registerHooksCommands(program, io) {
|
|
|
40
52
|
});
|
|
41
53
|
addJsonOption(hooks
|
|
42
54
|
.command('uninstall')
|
|
43
|
-
.description('Remove
|
|
55
|
+
.description('Remove all peaks-managed PreToolUse hooks (gate-enforce + progress-start) from the target .claude/settings.json. Third-party hooks are preserved.')
|
|
44
56
|
.option('--global', 'remove from the user-level ~/.claude/settings.json instead of the project')
|
|
45
57
|
.option('--project <path>', 'project root path (auto-detected from cwd when omitted)')).action((options) => {
|
|
46
58
|
const scope = resolveScope(options);
|
|
@@ -57,14 +69,17 @@ export function registerHooksCommands(program, io) {
|
|
|
57
69
|
});
|
|
58
70
|
addJsonOption(hooks
|
|
59
71
|
.command('status')
|
|
60
|
-
.description('Report
|
|
72
|
+
.description('Report which peaks-managed PreToolUse hooks are installed (gate-enforce + progress-start).')
|
|
61
73
|
.option('--global', 'inspect the user-level ~/.claude/settings.json instead of the project')
|
|
62
74
|
.option('--project <path>', 'project root path (auto-detected from cwd when omitted)')).action((options) => {
|
|
63
75
|
const scope = resolveScope(options);
|
|
64
76
|
const projectRoot = resolveProjectRoot(scope, options.project);
|
|
65
77
|
try {
|
|
66
78
|
const status = readHookStatus(scope, projectRoot);
|
|
67
|
-
printResult(io, ok('hooks.status',
|
|
79
|
+
printResult(io, ok('hooks.status', {
|
|
80
|
+
...status,
|
|
81
|
+
entries: PEAKS_HOOK_ENTRIES.map((e) => ({ matcher: e.matcher, sentinel: e.sentinel }))
|
|
82
|
+
}), options.json);
|
|
68
83
|
}
|
|
69
84
|
catch (error) {
|
|
70
85
|
const message = getErrorMessage(error);
|
|
@@ -2,7 +2,7 @@ import { spawn } from 'node:child_process';
|
|
|
2
2
|
import { platform } from 'node:os';
|
|
3
3
|
import chalk from 'chalk';
|
|
4
4
|
import ora from 'ora';
|
|
5
|
-
import { clearSpawnRecord, phaseAutoClosesSpawn, readSpawnRecord, readSubAgentProgress, resolveProgressProjectRoot, subAgentProgressPath, subAgentSpawnPath, writeSpawnRecord, writeSubAgentProgress } from '../../services/progress/progress-service.js';
|
|
5
|
+
import { clearSpawnRecord, isRecentSpawn, phaseAutoClosesSpawn, readSpawnRecord, readSubAgentProgress, resolveProgressProjectRoot, subAgentProgressPath, subAgentSpawnPath, writeSpawnRecord, writeSubAgentProgress } from '../../services/progress/progress-service.js';
|
|
6
6
|
import { resolveCanonicalProjectRoot } from '../../services/config/config-service.js';
|
|
7
7
|
import { fail, ok } from '../../shared/result.js';
|
|
8
8
|
import { addJsonOption, getErrorMessage, printResult } from '../cli-helpers.js';
|
|
@@ -156,12 +156,36 @@ export function registerProgressCommands(program, io) {
|
|
|
156
156
|
.command('start')
|
|
157
157
|
.description('Auto-spawn a new terminal running `peaks progress watch` for this project. Called by the LLM at the first phase transition; the user can close the new terminal at any time.')
|
|
158
158
|
.option('--project <path>', 'target project root (defaults to git root or cwd)')
|
|
159
|
-
.option('--reason <text>', 'human-readable reason for the auto-spawn, recorded in the response data')
|
|
159
|
+
.option('--reason <text>', 'human-readable reason for the auto-spawn, recorded in the response data')
|
|
160
|
+
.option('--quiet', 'suppress human-readable output (the Task-tool PreToolUse hook uses this to keep the LLM context clean)')).action(async (options) => {
|
|
160
161
|
try {
|
|
161
162
|
const projectRoot = options.project !== undefined
|
|
162
163
|
? options.project
|
|
163
164
|
: resolveProgressProjectRoot(undefined, process.cwd());
|
|
164
165
|
const canonical = resolveCanonicalProjectRoot(projectRoot);
|
|
166
|
+
// Idempotency check: when the Task-tool PreToolUse hook fires
|
|
167
|
+
// `peaks progress start` on every Task call, a fresh terminal
|
|
168
|
+
// should NOT be spawned if a watch window was already opened for
|
|
169
|
+
// this session within the last 5 minutes. The user closes the
|
|
170
|
+
// window deliberately; we honor that until the record ages out.
|
|
171
|
+
const recent = isRecentSpawn(canonical);
|
|
172
|
+
if (recent.recent) {
|
|
173
|
+
if (options.quiet !== true) {
|
|
174
|
+
// Non-hook path: keep the human feedback (so an LLM running
|
|
175
|
+
// peaks progress start manually understands the no-op).
|
|
176
|
+
}
|
|
177
|
+
printResult(io, ok('progress.start', {
|
|
178
|
+
projectRoot: canonical,
|
|
179
|
+
spawned: false,
|
|
180
|
+
idempotent: true,
|
|
181
|
+
reason: recent.reason,
|
|
182
|
+
ageMs: recent.ageMs,
|
|
183
|
+
note: 'a recent spawn record exists; the watch window is presumed open. Re-run after 5 min (TTL) or `peaks progress close` to force a fresh spawn.'
|
|
184
|
+
}, [], recent.reason === 'recent-spawn'
|
|
185
|
+
? []
|
|
186
|
+
: ['run `peaks progress close` to clear the stale record and force a fresh spawn']), options.json);
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
165
189
|
const currentPlatform = platform();
|
|
166
190
|
const peaksBin = process.argv[1] ?? 'peaks';
|
|
167
191
|
const reasonSuffix = options.reason !== undefined ? ` — ${options.reason}` : '';
|
|
@@ -45,6 +45,11 @@ export function registerRequestCommands(program, io) {
|
|
|
45
45
|
};
|
|
46
46
|
if (options.sessionId !== undefined) {
|
|
47
47
|
serviceOptions.sessionId = options.sessionId;
|
|
48
|
+
// Back-compat: pre-1.3.0 the `--session-id <scope>` flag also
|
|
49
|
+
// set the on-disk dir name. Preserve that by passing the same
|
|
50
|
+
// value as the explicit change-id; the service still records
|
|
51
|
+
// the session binding separately in the artifact body.
|
|
52
|
+
serviceOptions.changeId = options.sessionId;
|
|
48
53
|
}
|
|
49
54
|
if (options.apply === true) {
|
|
50
55
|
serviceOptions.apply = true;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { resolveCanonicalProjectRoot } from '../../services/config/config-service.js';
|
|
2
|
+
import { sliceCheck } from '../../services/slice/slice-check-service.js';
|
|
3
|
+
import { fail, ok } from '../../shared/result.js';
|
|
4
|
+
import { addJsonOption, getErrorMessage, printResult } from '../cli-helpers.js';
|
|
5
|
+
export function registerSliceCommands(program, io) {
|
|
6
|
+
const slice = program.command('slice').description('Run slice-level checks (TDD micro-cycle boundary, see ' +
|
|
7
|
+
'skills/peaks-solo/references/micro-cycle.md). `peaks slice check` bundles ' +
|
|
8
|
+
'tsc + vitest + 3-way review fan-out + gate verify-pipeline. ' +
|
|
9
|
+
'Boundaries only; do NOT run inside a micro-cycle.');
|
|
10
|
+
addJsonOption(slice
|
|
11
|
+
.command('check')
|
|
12
|
+
.description('Boundary check for a slice (post-micro-cycle, pre-peaks-qa). ' +
|
|
13
|
+
'Runs 4 stages in order: typecheck → unit-tests → review-fanout → ' +
|
|
14
|
+
'gate-verify-pipeline. Each stage reports pass / fail / skipped. ' +
|
|
15
|
+
'Exit 0 only if every stage passes or is skipped.')
|
|
16
|
+
.option('--project <path>', 'target project root', '.')
|
|
17
|
+
.option('--rid <rid>', 'request id; defaults to the active current-change binding')
|
|
18
|
+
.option('--refresh-fanout', 're-run the 3-way review fan-out (peaks-rd) even if the review files already exist', false)
|
|
19
|
+
.option('--skip-tests', 'skip the unit-test stage (e.g. docs-only slices)', false)).action(async (options) => {
|
|
20
|
+
try {
|
|
21
|
+
const projectRoot = resolveCanonicalProjectRoot(options.project);
|
|
22
|
+
const result = await sliceCheck({
|
|
23
|
+
projectRoot,
|
|
24
|
+
...(options.rid ? { rid: options.rid } : {}),
|
|
25
|
+
refreshFanout: options.refreshFanout === true,
|
|
26
|
+
skipTests: options.skipTests === true
|
|
27
|
+
});
|
|
28
|
+
const warnings = [];
|
|
29
|
+
if (result.stages.some((s) => s.status === 'fail')) {
|
|
30
|
+
warnings.push(`${result.stages.filter((s) => s.status === 'fail').length} of ${result.stages.length} stages failed. 边界 NOT ready — fix the failures and re-run, or proceed at your own risk.`);
|
|
31
|
+
}
|
|
32
|
+
printResult(io, ok('slice.check', result, warnings, result.nextActions), options.json ?? false);
|
|
33
|
+
if (!result.boundaryReady) {
|
|
34
|
+
process.exitCode = 1;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
catch (error) {
|
|
38
|
+
printResult(io, fail('slice.check', 'SLICE_CHECK_FAILED', getErrorMessage(error), { projectRoot: options.project }, ['Verify the project path is a peaks repo, --rid is correct, and .peaks/_runtime/current-change is valid']), options.json ?? false);
|
|
39
|
+
process.exitCode = 1;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
@@ -312,13 +312,13 @@ export function registerWorkflowCommands(program, io) {
|
|
|
312
312
|
.description('Verify the complete rd→qa pipeline was followed for a request')
|
|
313
313
|
.requiredOption('--rid <rid>', 'request identifier')
|
|
314
314
|
.requiredOption('--project <path>', 'project root path')
|
|
315
|
-
.
|
|
315
|
+
.option('--change-id <id>', 'change-id hint (when omitted, the on-disk change-id is resolved from the RD/QA artifact itself)')
|
|
316
316
|
.option('--type <type>', 'request type: feature, bugfix, refactor, docs, config, chore', 'feature')).action(async (options) => {
|
|
317
317
|
try {
|
|
318
318
|
const result = await verifyPipeline({
|
|
319
319
|
projectRoot: options.project,
|
|
320
320
|
rid: options.rid,
|
|
321
|
-
|
|
321
|
+
...(options.changeId ? { changeId: options.changeId } : {}),
|
|
322
322
|
...(options.type ? { requestType: options.type } : {})
|
|
323
323
|
});
|
|
324
324
|
const exitOk = result.complete ? 0 : 1;
|
|
@@ -328,7 +328,7 @@ export function registerWorkflowCommands(program, io) {
|
|
|
328
328
|
process.exitCode = exitOk;
|
|
329
329
|
}
|
|
330
330
|
catch (error) {
|
|
331
|
-
printResult(io, fail('workflow.verify-pipeline', 'VERIFY_FAILED', getErrorMessage(error), {}, ['Check that --project
|
|
331
|
+
printResult(io, fail('workflow.verify-pipeline', 'VERIFY_FAILED', getErrorMessage(error), {}, ['Check that --project and --rid are correct; --change-id is optional (resolved from the artifact otherwise)']), options.json);
|
|
332
332
|
process.exitCode = 1;
|
|
333
333
|
}
|
|
334
334
|
});
|
|
@@ -1,3 +1,66 @@
|
|
|
1
1
|
import { Command } from 'commander';
|
|
2
2
|
import { type ProgramIO } from '../cli-helpers.js';
|
|
3
|
+
type HooksDecision = 'installed' | 'skipped';
|
|
3
4
|
export declare function registerWorkspaceCommands(program: Command, io: ProgramIO): void;
|
|
5
|
+
/**
|
|
6
|
+
* Outcome of the first-time "install peaks hooks" decision attached to
|
|
7
|
+
* `peaks workspace init`. Reported in the response data so the LLM and
|
|
8
|
+
* the human both see what happened.
|
|
9
|
+
*/
|
|
10
|
+
export type FirstTimeHooksInstallOutcome = {
|
|
11
|
+
/** The decision recorded (or already on file) in the sticky marker. */
|
|
12
|
+
decision: HooksDecision;
|
|
13
|
+
/**
|
|
14
|
+
* What the install path actually did this call.
|
|
15
|
+
* - first-decision: we recorded a brand-new sticky marker (and may have installed)
|
|
16
|
+
* - reinstalled: the marker said installed but the hooks were missing — we re-applied
|
|
17
|
+
* - marker-honored: the marker already existed; we did not touch the hooks
|
|
18
|
+
* - already-installed: the hooks were already present and the marker does not exist yet
|
|
19
|
+
* (we write a fresh marker to lock in the answer for next time)
|
|
20
|
+
*/
|
|
21
|
+
action: 'first-decision' | 'reinstalled' | 'marker-honored' | 'already-installed';
|
|
22
|
+
scope: 'project' | 'global';
|
|
23
|
+
/**
|
|
24
|
+
* Why the action was the way it was (e.g. "stdin-not-tty", "user-answered-no",
|
|
25
|
+
* "marker-installed-hooks-missing"). Surfaced for forensics.
|
|
26
|
+
*/
|
|
27
|
+
reason?: string;
|
|
28
|
+
};
|
|
29
|
+
export type ResolveFirstTimeHooksInstallOptions = {
|
|
30
|
+
projectRoot: string;
|
|
31
|
+
/**
|
|
32
|
+
* Explicit mode from the --install-hooks flag. When omitted, the default
|
|
33
|
+
* is "ask" in TTY mode and "auto" otherwise (see `defaultMode` logic).
|
|
34
|
+
*/
|
|
35
|
+
explicitMode?: 'ask' | 'auto' | 'skip' | undefined;
|
|
36
|
+
/**
|
|
37
|
+
* Whether the caller is in --json mode. In --json mode we never prompt
|
|
38
|
+
* (the LLM cannot answer an interactive question) — we silently treat
|
|
39
|
+
* "ask" as "auto" and proceed.
|
|
40
|
+
*/
|
|
41
|
+
jsonMode: boolean;
|
|
42
|
+
};
|
|
43
|
+
/**
|
|
44
|
+
* Resolve the first-time "install peaks hooks" decision for this project.
|
|
45
|
+
* Decision tree:
|
|
46
|
+
*
|
|
47
|
+
* 1. Read the sticky marker.
|
|
48
|
+
* - Marker present:
|
|
49
|
+
* - marker.decision === 'installed' AND hooks are present → action: marker-honored, no side effects
|
|
50
|
+
* - marker.decision === 'installed' AND hooks are MISSING → re-install, action: reinstalled
|
|
51
|
+
* - marker.decision === 'skipped' → action: marker-honored, no install
|
|
52
|
+
* - Marker absent:
|
|
53
|
+
* - hooks already present → write a fresh 'installed' marker, action: already-installed
|
|
54
|
+
* - otherwise:
|
|
55
|
+
* - explicit --install-hooks=auto → install + marker, action: first-decision
|
|
56
|
+
* - explicit --install-hooks=skip → marker only, action: first-decision
|
|
57
|
+
* - explicit --install-hooks=ask OR default in TTY:
|
|
58
|
+
* - jsonMode → silently auto-install (LLM cannot answer), action: first-decision
|
|
59
|
+
* - TTY → prompt; on yes install + marker, on no marker-only
|
|
60
|
+
* - default in non-TTY → auto-install, action: first-decision
|
|
61
|
+
*
|
|
62
|
+
* Project scope is the only supported scope here; global scope is reserved
|
|
63
|
+
* for explicit `peaks hooks install --global` invocations.
|
|
64
|
+
*/
|
|
65
|
+
export declare function resolveFirstTimeHooksInstall(options: ResolveFirstTimeHooksInstallOptions): Promise<FirstTimeHooksInstallOutcome>;
|
|
66
|
+
export {};
|