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.
Files changed (42) hide show
  1. package/README.md +62 -46
  2. package/dist/src/cli/commands/hooks-commands.js +24 -9
  3. package/dist/src/cli/commands/progress-commands.js +26 -2
  4. package/dist/src/cli/commands/request-commands.js +5 -0
  5. package/dist/src/cli/commands/slice-commands.d.ts +3 -0
  6. package/dist/src/cli/commands/slice-commands.js +42 -0
  7. package/dist/src/cli/commands/workflow-commands.js +3 -3
  8. package/dist/src/cli/commands/workspace-commands.d.ts +63 -0
  9. package/dist/src/cli/commands/workspace-commands.js +288 -4
  10. package/dist/src/cli/program.js +4 -0
  11. package/dist/src/services/artifacts/artifact-prerequisites.d.ts +17 -1
  12. package/dist/src/services/artifacts/artifact-prerequisites.js +38 -5
  13. package/dist/src/services/artifacts/request-artifact-service.d.ts +22 -0
  14. package/dist/src/services/artifacts/request-artifact-service.js +172 -54
  15. package/dist/src/services/doctor/doctor-service.d.ts +7 -0
  16. package/dist/src/services/doctor/doctor-service.js +20 -2
  17. package/dist/src/services/progress/progress-service.d.ts +26 -0
  18. package/dist/src/services/progress/progress-service.js +25 -0
  19. package/dist/src/services/sc/sc-service.js +71 -13
  20. package/dist/src/services/scan/acceptance-coverage-service.js +6 -2
  21. package/dist/src/services/session/session-manager.js +12 -2
  22. package/dist/src/services/skills/hooks-settings-service.d.ts +25 -3
  23. package/dist/src/services/skills/hooks-settings-service.js +57 -13
  24. package/dist/src/services/slice/slice-check-service.d.ts +2 -0
  25. package/dist/src/services/slice/slice-check-service.js +248 -0
  26. package/dist/src/services/slice/slice-check-types.d.ts +61 -0
  27. package/dist/src/services/slice/slice-check-types.js +18 -0
  28. package/dist/src/services/workflow/pipeline-verify-service.d.ts +5 -2
  29. package/dist/src/services/workflow/pipeline-verify-service.js +35 -35
  30. package/dist/src/services/workspace/migrate-service.d.ts +2 -0
  31. package/dist/src/services/workspace/migrate-service.js +484 -0
  32. package/dist/src/services/workspace/migrate-types.d.ts +84 -0
  33. package/dist/src/services/workspace/migrate-types.js +21 -0
  34. package/dist/src/services/workspace/workspace-service.d.ts +11 -0
  35. package/dist/src/services/workspace/workspace-service.js +87 -7
  36. package/dist/src/shared/change-id.d.ts +59 -0
  37. package/dist/src/shared/change-id.js +194 -16
  38. package/dist/src/shared/version.d.ts +1 -1
  39. package/dist/src/shared/version.js +1 -1
  40. package/package.json +10 -2
  41. package/skills/peaks-solo/SKILL.md +11 -1
  42. package/skills/peaks-solo/references/micro-cycle.md +155 -0
package/README.md CHANGED
@@ -1,36 +1,35 @@
1
1
  # Peaks
2
2
 
3
- Peaks 是一组跑在 Claude Code 里的 **技能(SKILL)家族** ——把项目治理、工作流规划、受控执行、QA 验证、变更追踪组织成可复用的工程流程。
4
- CLI 是这些技能在背后调用的引擎,负责「门禁 + JSON 契约 + 不可逆动作」。
3
+ [English](./README-en.md) | **简体中文**
5
4
 
6
- > **一句话定位**:你**用技能(SKILL)工作**,CLI 只是技能用来在 hook、CI、结构化判断等场景下提供机器层保障的底层。
5
+ [![npm version](https://img.shields.io/npm/v/peaks-cli.svg)](https://www.npmjs.com/package/peaks-cli)
6
+ [![GitHub repo](https://img.shields.io/badge/GitHub-SquabbyZ%2Fpeaks--cli-181717?logo=github)](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
- 安装后,Peaks 会把内置的 8 个 `peaks-*` 技能注册到 Claude Code,会话里直接通过技能名调用即可。
10
+ > **支持的 IDE**:
11
+ > - ✅ **Claude Code**(当前实现):11 个 `peaks-*` 技能 + `.claude/settings.json` PreToolUse hook
12
+ > - 🚧 **Trae**(适配中):用 Trae 的原生配置承载相同的角色 / 状态机 / 门禁
13
+ > - 📋 **Codex / Cursor / Qoder / 通义灵码 等**(路线图)
15
14
 
16
- ## 本地开发(从源码跑 CLI)
15
+ > **产品定位**:你**用技能工作**,CLI 是跨 IDE 的质量保障层。
16
+ >
17
+ > 我们的目标:让 LLM 在每个环节里自由判断与决策;peaks 在流程边界上提供可审计的 SOP 护栏,把项目级记忆和使用经验沉淀下来——AI IDE 和 LLM 在你的项目上用得越久就越懂它。
17
18
 
18
- 仓库自带 `peaks` CLI 源码。开发模式用 `tsx` 直接跑 `src/cli/index.ts`,所以**首次克隆后 `node_modules/` 里不会有 `chalk` / `ora` / `terminal-kit` 等运行时依赖**——直接 `tsx src/cli/index.ts` 会报 `ERR_MODULE_NOT_FOUND: chalk`。先执行一次 `pnpm install` 把依赖补齐,再验证:
19
+ ## 安装
19
20
 
20
21
  ```bash
21
- pnpm install
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
- 热重载开发循环可用 `pnpm dev:watch`。
25
+ 安装后,Peaks 会把内置的 11 个 `peaks-*` 技能注册到已适配的 AI IDE(当前:Claude Code),会话里直接通过技能名调用即可。
27
26
 
28
27
  ## 5 分钟上手
29
28
 
30
- Claude Code 对话里,**直接对 Claude 说「用 X 技能做 Y」** 即可,技能会接管剩下的所有流程:
29
+ 在已适配的 AI IDE 对话里,**直接对 AI 说「用 X 技能做 Y」** 即可,技能会接管剩下的所有流程:
31
30
 
32
31
  ```text
33
- peaks-solo 用全自动模式治理 /path/to/your-project
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
- 第一次使用?照这 4 步走:
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
- 1. 在 Claude Code 里对 Claude 说:**`peaks-solo 分析 /path/to/your-project`**
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
- 想要随时确认状态?让 Claude 跑一下:
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-prd → peaks-ui(如果涉及 UI) → peaks-rd → peaks-qa → peaks-sc
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
- ```text
84
- peaks-txt(先压缩现状) → peaks-prd(明确目标) →
85
- peaks-rd(拆最小切片) → peaks-qa(回归矩阵) →
86
- peaks-solo(编排执行) → peaks-sc(变更证据)
87
- ```
98
+ - `peaks-solo-resume` —— 继续刚才没做完的切片
99
+ - `peaks-solo-status` —— 看看现在到哪了
100
+ - `peaks-solo-test` —— 跑项目测试
88
101
 
89
- **修 bug**
102
+ **2. 直接调用单个角色技能(进阶)**
90
103
 
91
- ```text
92
- peaks-rd(复现 + 根因) → peaks-qa(失败用例 + 验收) → 改代码(先补失败测试) → peaks-sc
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 gate-enforcement PreToolUse hook in .claude/settings.json');
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('Install the un-bypassable SOP gate hook (project scope by default)')
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', { ...plan, applied: false, dryRun: true }), options.json);
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
- ? ['Restart Claude Code (or reload the window) so the gate hook takes effect']
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', { ...result, dryRun: false }, [], nextActions), options.json);
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 the Peaks gate-enforcement hook from .claude/settings.json')
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 whether the Peaks gate hook is installed')
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', status), options.json);
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')).action(async (options) => {
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,3 @@
1
+ import { Command } from 'commander';
2
+ import { type ProgramIO } from '../cli-helpers.js';
3
+ export declare function registerSliceCommands(program: Command, io: ProgramIO): void;
@@ -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
- .requiredOption('--session-id <id>', 'session identifier')
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
- sessionId: options.sessionId,
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, --rid, and --session-id are correct']), options.json);
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 {};