paseo-req-kit 1.9.0-beta.1 → 1.9.0-beta.3

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 (49) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +2 -0
  3. package/bin/req-plugin.mjs +226 -0
  4. package/bin/req.mjs +16 -0
  5. package/contracts/cli-json.md +2 -1
  6. package/contracts/feature-files.md +10 -0
  7. package/contracts/state.md +1 -0
  8. package/docs/adr/0027-paseo-plugin.md +23 -0
  9. package/docs/adr/0028-external-integrations.md +22 -0
  10. package/docs/adr/README.md +2 -0
  11. package/docs/guide//344/275/277/347/224/250/350/200/205/346/214/207/345/215/227.md +2 -0
  12. package/docs/guide//345/256/211/350/243/205/344/270/216/345/215/207/347/272/247.md +25 -0
  13. package/docs/guide//347/273/264/346/212/244/350/200/205/346/214/207/345/215/227.md +3 -1
  14. package/docs/guide//351/241/271/347/233/256/345/256/232/345/210/266/344/270/216/350/277/220/350/241/214/350/247/204/345/210/231.md +32 -0
  15. package/engine/src/api.ts +2 -1
  16. package/engine/src/cli-args.ts +4 -1
  17. package/engine/src/config.ts +21 -1
  18. package/engine/src/control.ts +2 -0
  19. package/engine/src/integration-cmd.ts +161 -0
  20. package/engine/src/integrations.ts +400 -0
  21. package/engine/src/launch.ts +12 -0
  22. package/engine/src/pipeline.ts +31 -1
  23. package/engine/src/preflight.ts +2 -0
  24. package/engine/src/state.ts +3 -0
  25. package/install.mjs +3 -0
  26. package/package.json +2 -1
  27. package/paseo-plugin/OVERVIEW.md +7 -0
  28. package/paseo-plugin/client/data.ts +55 -0
  29. package/paseo-plugin/client/detail.tsx +303 -0
  30. package/paseo-plugin/client/format.ts +111 -0
  31. package/paseo-plugin/client/live.ts +161 -0
  32. package/paseo-plugin/client/markdown.tsx +219 -0
  33. package/paseo-plugin/client/overview.tsx +89 -0
  34. package/paseo-plugin/client/panel.tsx +56 -0
  35. package/paseo-plugin/client/row.tsx +40 -0
  36. package/paseo-plugin/client/settings.tsx +90 -0
  37. package/paseo-plugin/client/sidebar.tsx +27 -0
  38. package/paseo-plugin/client/stats.tsx +80 -0
  39. package/paseo-plugin/client/ui.tsx +111 -0
  40. package/paseo-plugin/index.client.tsx +52 -0
  41. package/paseo-plugin/index.server.ts +41 -0
  42. package/paseo-plugin/package.json +22 -0
  43. package/paseo-plugin/paseo-plugin.json +6 -0
  44. package/paseo-plugin/server/handlers.ts +45 -0
  45. package/paseo-plugin/server/req.ts +100 -0
  46. package/paseo-plugin/shared/contracts.ts +167 -0
  47. package/paseo-plugin/tsconfig.json +16 -0
  48. package/skills/req/SKILL.md +1 -0
  49. package/skills/req/playbooks/new.md +3 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,46 @@
2
2
 
3
3
  每个版本分为"使用者会看到的变化"和"升级须知"。升级步骤见 [docs/guide/安装与升级.md](docs/guide/安装与升级.md)。
4
4
 
5
+ ## 1.9.0-beta.3
6
+
7
+ 外部系统对接:需求在团队的需求管理系统中自动建卡、跟着阶段改状态;插件中的文档按 Markdown 显示。
8
+
9
+ ### 使用者会看到的变化
10
+
11
+ - **外部系统对接**:配置 `integrations` 后,流水线开始时在需求管理系统中建一张卡,之后每到一个阶段改卡片的状态:开始开发、等待确认、研发完成、开始测试验证、完成、中断,对应哪个状态可以配置。卡片编号和链接显示在进度、完成报告和 Paseo 插件里。
12
+ - 对接的是本机已有的命令行工具,命令的写法写在团队配置里,换系统或系统改参数都不用升级 req-kit。
13
+ - 卡片的业务字段(优先级、需求类型等)由 AI 在确认需求说明时从需求说明中整理,和需求方一起确认后写进 `card.json`。
14
+ - 外部系统出错不影响流水线:失败的调用会自动补发,也可以运行 `req sync <需求>`,插件中有"同步到外部系统"。
15
+ - `req integration test [需求]` 显示每个事件会执行的完整命令,但不真正调用,配置时先用它检查。
16
+ - **插件**:文档按 Markdown 显示(标题、列表、表格、代码、链接),`decisions.tsv` 等显示为表格;需求详情中显示外部系统的卡片和链接。
17
+
18
+ ### 升级须知
19
+
20
+ - 对接是可选的,写法见《项目定制与运行规则》的"外部系统对接"。`integrations` 只能写在本机或团队配置中;项目可以在自己的配置中用 `integrationDefaults` 提供项目编号等取值。
21
+ - 状态文件新增可选字段 `external`,功能目录新增 `card.json`,格式版本仍为 1。
22
+ - 自定义了 `prompts.local/` 的不受影响;`/req` 的新需求手册在确认前多一步"卡片字段",没有配置对接时自动跳过。设计决定见 ADR 0028。
23
+
24
+ ## 1.9.0-beta.2
25
+
26
+ Paseo 插件:在 Paseo 的界面里查看和管理需求。
27
+
28
+ ### 使用者会看到的变化
29
+
30
+ - **req-kit 插件**(`req plugin install`):在 Paseo 的网页端、桌面版和手机上都能用。
31
+ - 侧边栏的"需求":本机所有项目的需求,按"需要你处理、进行中、待测试验证、最近完成"分组,带待处理的数量。
32
+ - 需求详情:每一步的状态、用时和结论(点开进入那个步骤的 agent)、质量门、联合需求的各仓库、用量;可以读需求说明、方案、确认预览、提测说明、完成报告等。
33
+ - 确认方案、停止、继续、开始测试验证:每个都要再确认一次,做的事与终端里的 `req` 命令完全相同。
34
+ - 每个工作区头部的"需求"按钮和面板、步骤 agent 输入框旁的"需求"按钮、使用统计页、Ctrl+K 中的"需求总览"。
35
+ - 插件设置中可以打开"只读",给只看不操作的人用。
36
+ - **安装脚本**最后询问是否安装插件(`--with-plugin` 直接安装,`--no-plugin` 不问;PowerShell 用 `$ReqKitPlugin`)。
37
+ - **修正**:安装脚本判断 Paseo 网页界面是否已开启时,读错了 `paseo daemon config get` 的输出("已设置"被当成"已开启")。
38
+
39
+ ### 升级须知
40
+
41
+ - 插件是可选的。安装时会询问是否打开 Paseo 的插件总开关(`pluginsEnabled`):插件是不受沙箱限制的代码;req-kit 插件只运行本机的 `req api`,不连接外部服务。
42
+ - 装过插件后,`req setup`、`req upgrade`、`req use` 会自动把插件换成启用的版本。插件随 req-kit 一起发布,不需要单独的 npm 包。
43
+ - 需要 Paseo 0.11。1.9 之前运行过的需求,先在项目目录运行一次 `req index --rebuild`。设计决定见 ADR 0027。
44
+
5
45
  ## 1.9.0-beta.1
6
46
 
7
47
  1.9 的基础:本机知道自己有哪些需求,记下每次运行的用时和用量,并给程序一个稳定的读取接口。插件(beta.2)和外部系统集成(beta.3)都建在它上面。
package/README.md CHANGED
@@ -74,6 +74,8 @@ irm https://paseo-req-kit.vercel.app/install.ps1 | iex
74
74
  2. 在项目的 Paseo 对话中运行 `/req-setup` —— 接入项目(每个项目一次);
75
75
  3. 在 Paseo 对话中输入 `/req 我想……` —— 开始第一个需求。
76
76
 
77
+ 想在 Paseo 的界面(网页端、桌面版、手机)里看需求总览和进度、确认方案或停止继续,运行 `req plugin install` 安装 req-kit 的 Paseo 插件。
78
+
77
79
  其他安装方式(npm、离线安装包、git)、各 agent 的配置写法和升级回滚,见 [安装与升级](docs/guide/安装与升级.md)。
78
80
 
79
81
  ## 工作原理
@@ -0,0 +1,226 @@
1
+ // `req plugin`: the req-kit plugin for Paseo (1.9, ADR 0027).
2
+ //
3
+ // req plugin install [--yes] copy the plugin to <kit home>/paseo-plugin and install it in Paseo
4
+ // req plugin status whether it is installed and running
5
+ // req plugin remove remove it from Paseo
6
+ //
7
+ // The plugin ships inside this package (paseo-plugin/). It is installed from a fixed copy under
8
+ // the kit home, so upgrading or switching req-kit versions only refreshes that copy and reloads
9
+ // the plugin (refreshPlugin, called by install.mjs); the plugin always matches the active version.
10
+ // Turning on Paseo's plugin switch (pluginsEnabled) needs the person's consent: plugins run
11
+ // unsandboxed on this machine.
12
+ import { spawnSync } from "node:child_process";
13
+ import fs from "node:fs";
14
+ import os from "node:os";
15
+ import path from "node:path";
16
+ import readline from "node:readline";
17
+ import { fileURLToPath } from "node:url";
18
+
19
+ export const PLUGIN_ID = "req-kit";
20
+ const KIT_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
21
+
22
+ export function kitHome(env = process.env) {
23
+ const override = env.REQ_KIT_HOME?.trim();
24
+ return override ? path.resolve(override) : path.join(os.homedir(), ".req-kit");
25
+ }
26
+
27
+ export function stagedDir(home = kitHome()) {
28
+ return path.join(home, "paseo-plugin");
29
+ }
30
+
31
+ /** Copies this version's plugin to the fixed directory Paseo installs from. */
32
+ export function stagePlugin(kitRoot = KIT_ROOT, home = kitHome()) {
33
+ const from = path.join(kitRoot, "paseo-plugin");
34
+ if (!fs.existsSync(path.join(from, "paseo-plugin.json"))) throw new Error(`这个版本的 req-kit 没有附带 Paseo 插件(${from})`);
35
+ const to = stagedDir(home);
36
+ fs.rmSync(to, { recursive: true, force: true });
37
+ fs.cpSync(from, to, {
38
+ recursive: true,
39
+ filter: (src) => !path.relative(from, src).split(path.sep).includes("node_modules"),
40
+ });
41
+ return to;
42
+ }
43
+
44
+ /** The paseo command: REQ_KIT_PASEO_BIN (tests; empty means none), else paseo on PATH. */
45
+ function paseoCommand(env = process.env) {
46
+ if (env.REQ_KIT_PASEO_BIN !== undefined) return env.REQ_KIT_PASEO_BIN || null;
47
+ const cmd = process.platform === "win32" ? "paseo.cmd" : "paseo";
48
+ return run(cmd, ["--version"], 30_000, env).status === 0 ? cmd : null;
49
+ }
50
+
51
+ // A .cmd shim on Windows only runs through the shell; quote what the shell would split.
52
+ const WINDOWS = process.platform === "win32";
53
+ const quote = (a) => (WINDOWS && /[\s"&|<>^]/.test(a) ? `"${a.replace(/"/g, '""')}"` : a);
54
+
55
+ function run(cmd, args, timeout, env = process.env) {
56
+ return spawnSync(WINDOWS ? quote(cmd) : cmd, WINDOWS ? args.map(quote) : args, { encoding: "utf8", windowsHide: true, timeout, shell: WINDOWS, env });
57
+ }
58
+
59
+ function paseo(cmd, args, env = process.env) {
60
+ const r = run(cmd, args, 180_000, env);
61
+ let json = null;
62
+ try {
63
+ json = JSON.parse((r.stdout ?? "").trim());
64
+ } catch {
65
+ json = null;
66
+ }
67
+ return { ok: r.status === 0, json, text: `${r.stdout ?? ""}${r.stderr ?? ""}`.trim() };
68
+ }
69
+
70
+ function pluginEntry(cmd, env = process.env) {
71
+ const r = paseo(cmd, ["plugin", "ls", "--json"], env);
72
+ if (!r.ok) return { error: r.text };
73
+ const list = Array.isArray(r.json) ? r.json : Array.isArray(r.json?.data) ? r.json.data : [];
74
+ return { entry: list.find((p) => p.id === PLUGIN_ID) ?? null };
75
+ }
76
+
77
+ function pluginsEnabled(cmd, env = process.env) {
78
+ const r = paseo(cmd, ["daemon", "config", "get", "pluginsEnabled", "--json"], env);
79
+ const value = r.json?.value ?? r.json?.data?.value;
80
+ return value === true;
81
+ }
82
+
83
+ function ask(question) {
84
+ if (!process.stdin.isTTY) return Promise.resolve(false);
85
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
86
+ return new Promise((resolve) =>
87
+ rl.question(question, (answer) => {
88
+ rl.close();
89
+ resolve(/^(y|yes|是|好|可以)$/i.test(answer.trim()));
90
+ }),
91
+ );
92
+ }
93
+
94
+ const say = (s) => console.log(s);
95
+
96
+ function manualSteps(dir) {
97
+ say("也可以在 Paseo 中手动安装:");
98
+ say(" 1. 设置 → Plugins → 打开 Enable plugins(插件是不受限制的代码,可以访问这台机器上的文件和进程)");
99
+ say(` 2. 在 Plugin source 中填:${dir}`);
100
+ }
101
+
102
+ export async function installPlugin({ yes = false, env = process.env, kitRoot = KIT_ROOT } = {}) {
103
+ const dir = stagePlugin(kitRoot, kitHome(env));
104
+ say(`插件文件:${dir}`);
105
+ const cmd = paseoCommand(env);
106
+ if (!cmd) {
107
+ say("没有找到 paseo 命令(桌面版 Paseo 管理的 daemon 通常没有)。");
108
+ manualSteps(dir);
109
+ return 1;
110
+ }
111
+ if (!pluginsEnabled(cmd, env)) {
112
+ say("");
113
+ say("Paseo 的插件总开关(pluginsEnabled)是关着的。");
114
+ say("插件是受信任、不受沙箱限制的代码:它的后台部分可以访问这台机器上的文件、进程和网络。");
115
+ say("req-kit 插件只运行本机的 req 命令,不连接任何外部服务。");
116
+ const agreed = yes || (await ask("打开 Paseo 的插件总开关并安装 req-kit 插件?[y/N] "));
117
+ if (!agreed) {
118
+ say("没有安装。之后可以运行 req plugin install(或加 --yes)。");
119
+ manualSteps(dir);
120
+ return 1;
121
+ }
122
+ const set = paseo(cmd, ["daemon", "config", "set", "pluginsEnabled", "true", "--json"], env);
123
+ if (!set.ok) {
124
+ say(`没能打开插件总开关:${set.text}`);
125
+ manualSteps(dir);
126
+ return 1;
127
+ }
128
+ say("✓ 已打开 Paseo 的插件总开关");
129
+ }
130
+ const found = pluginEntry(cmd, env);
131
+ if (found.error) {
132
+ say(`读取不了 Paseo 的插件列表:${found.error}`);
133
+ say("请确认 Paseo daemon 在运行(paseo daemon status)。");
134
+ return 1;
135
+ }
136
+ const r = found.entry ? paseo(cmd, ["plugin", "reload", PLUGIN_ID, "--json"], env) : paseo(cmd, ["plugin", "install", dir, "--json"], env);
137
+ if (!r.ok) {
138
+ say(`${found.entry ? "重新加载" : "安装"}失败:${r.text}`);
139
+ say(`查看插件输出:paseo plugin logs ${PLUGIN_ID}`);
140
+ return 1;
141
+ }
142
+ const after = pluginEntry(cmd, env).entry;
143
+ if (after?.status === "running") {
144
+ say(`✓ req-kit 插件${found.entry ? "已更新" : "已安装"},正在运行。在 Paseo 侧边栏中点"需求"打开。`);
145
+ return 0;
146
+ }
147
+ say(`插件状态:${after?.status ?? "未知"}${after?.error ? `(${after.error})` : ""}。查看输出:paseo plugin logs ${PLUGIN_ID}`);
148
+ return 1;
149
+ }
150
+
151
+ export function pluginStatus({ env = process.env } = {}) {
152
+ const dir = stagedDir(kitHome(env));
153
+ const staged = fs.existsSync(path.join(dir, "package.json")) ? JSON.parse(fs.readFileSync(path.join(dir, "package.json"), "utf8")).version : null;
154
+ say(staged ? `插件文件:${dir}(版本 ${staged})` : "还没有准备插件文件:运行 req plugin install");
155
+ const cmd = paseoCommand(env);
156
+ if (!cmd) {
157
+ say("没有找到 paseo 命令,无法查看 Paseo 中的状态(在 Paseo 的 设置 → Plugins 中查看)。");
158
+ return staged ? 0 : 1;
159
+ }
160
+ const found = pluginEntry(cmd, env);
161
+ if (found.error) {
162
+ say(`读取不了 Paseo 的插件列表:${found.error}`);
163
+ return 1;
164
+ }
165
+ if (!found.entry) {
166
+ say("Paseo 中没有安装 req-kit 插件:运行 req plugin install");
167
+ return 1;
168
+ }
169
+ say(`Paseo 中的 req-kit 插件:${found.entry.status}${found.entry.error ? `(${found.entry.error})` : ""}`);
170
+ if (!pluginsEnabled(cmd, env)) say("注意:Paseo 的插件总开关是关着的(设置 → Plugins → Enable plugins)。");
171
+ return found.entry.status === "running" ? 0 : 1;
172
+ }
173
+
174
+ export function removePlugin({ env = process.env } = {}) {
175
+ const cmd = paseoCommand(env);
176
+ if (cmd) {
177
+ const found = pluginEntry(cmd, env);
178
+ if (found.entry) {
179
+ const r = paseo(cmd, ["plugin", "remove", PLUGIN_ID, "--json"], env);
180
+ if (!r.ok) {
181
+ say(`移除失败:${r.text}`);
182
+ return 1;
183
+ }
184
+ say("✓ 已从 Paseo 移除 req-kit 插件(Paseo 的插件总开关保持原样)");
185
+ } else say("Paseo 中没有安装 req-kit 插件");
186
+ } else say("没有找到 paseo 命令:请在 Paseo 的 设置 → Plugins 中移除 req-kit。");
187
+ fs.rmSync(stagedDir(kitHome(env)), { recursive: true, force: true });
188
+ return 0;
189
+ }
190
+
191
+ /**
192
+ * After a version becomes active (req setup, upgrade, use): if the plugin was installed,
193
+ * refresh its files from that version and reload it. Quiet and best effort.
194
+ */
195
+ export function refreshPlugin({ kitRoot = KIT_ROOT, env = process.env, log = () => {} } = {}) {
196
+ const home = kitHome(env);
197
+ if (!fs.existsSync(path.join(stagedDir(home), "paseo-plugin.json"))) return false;
198
+ try {
199
+ stagePlugin(kitRoot, home);
200
+ } catch (error) {
201
+ log(`没能更新 Paseo 插件的文件:${error.message}`);
202
+ return false;
203
+ }
204
+ const cmd = paseoCommand(env);
205
+ const r = cmd ? paseo(cmd, ["plugin", "reload", PLUGIN_ID, "--json"], env) : null;
206
+ log(r?.ok ? "Paseo 插件已更新到这个版本" : `Paseo 插件的文件已更新;在 Paseo 中重新加载它:paseo plugin reload ${PLUGIN_ID}(或 设置 → Plugins)`);
207
+ return true;
208
+ }
209
+
210
+ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
211
+ const [sub = "status", ...rest] = process.argv.slice(2);
212
+ let code;
213
+ try {
214
+ if (sub === "install") code = await installPlugin({ yes: rest.includes("--yes") || rest.includes("-y") });
215
+ else if (sub === "status") code = pluginStatus();
216
+ else if (sub === "remove" || sub === "uninstall") code = removePlugin();
217
+ else {
218
+ console.error(`没有 "${sub}" 这个子命令。可用:req plugin install [--yes]、req plugin status、req plugin remove`);
219
+ code = 2;
220
+ }
221
+ } catch (error) {
222
+ console.error(error.message);
223
+ code = 1;
224
+ }
225
+ process.exit(code);
226
+ }
package/bin/req.mjs CHANGED
@@ -29,6 +29,8 @@ const HELP = `req-kit ${version}(${kitRoot})
29
29
  req digest [--notify] 项目的需求总览;--notify 同时发到外部通知(如企业 IM)
30
30
  req digest --schedule "0 9 * * 1"
31
31
  让 Paseo 按时间表发送需求总览(例子为每周一 9:00);取消:req digest --cancel <ID>
32
+ req sync <需求> 把需求同步到对接的外部系统(补发失败的调用、补建卡片、更新状态)
33
+ req integration test [需求] 只显示对接外部系统时会执行的命令,不真正调用
32
34
 
33
35
  使用情况(可以在任何目录运行):
34
36
  req stats [--since 30d] [--month 2026-10]
@@ -40,6 +42,11 @@ const HELP = `req-kit ${version}(${kitRoot})
40
42
  本机记录的需求(Paseo 插件据此找到所有需求);1.9 之前运行过的用 --rebuild 补登
41
43
  req api <命令> … 给程序用的 JSON 输出(见 contracts/cli-json.md)
42
44
 
45
+ Paseo 插件(在 Paseo 中查看和管理需求):
46
+ req plugin install [--yes] 安装或更新插件;需要时会询问是否打开 Paseo 的插件总开关
47
+ req plugin status 插件是否在运行
48
+ req plugin remove 从 Paseo 移除插件
49
+
43
50
  本机维护:
44
51
  req doctor [--no-project] 体检:安装、配置、daemon、当前项目
45
52
  req configure 配置向导:选择 builder 和 reviewer 用的 agent
@@ -175,6 +182,15 @@ switch (command) {
175
182
  case "api":
176
183
  code = needTsx() ? node([tsxCli, path.join(kitRoot, "engine", "src", "api.ts"), ...rest]) : 1;
177
184
  break;
185
+ case "sync":
186
+ code = needTsx() ? node([tsxCli, path.join(kitRoot, "engine", "src", "integration-cmd.ts"), "sync", ...rest]) : 1;
187
+ break;
188
+ case "integration":
189
+ code = needTsx() ? node([tsxCli, path.join(kitRoot, "engine", "src", "integration-cmd.ts"), ...rest]) : 1;
190
+ break;
191
+ case "plugin":
192
+ code = node([path.join(kitRoot, "bin", "req-plugin.mjs"), ...rest]);
193
+ break;
178
194
  case "notify-test":
179
195
  code = needTsx() ? node([tsxCli, path.join(kitRoot, "engine", "src", "notify-test.ts"), ...rest]) : 1;
180
196
  break;
@@ -27,7 +27,7 @@
27
27
  | `req api file <需求> <文件> [--repo <目录>]` | `{ featureDir, file, size, truncated, content }`;只能读取功能目录中的文档(见下),最多 512 KB |
28
28
  | `req api stats [--since 30d] [--month 2026-10]` | `req stats --json` 的内容,见 [使用记录](usage-record.md) |
29
29
  | `req api index [--rebuild <目录>…] [--prune]` | 不带选项:索引的全部条目;`--rebuild`:`{ scanned, registered, pruned, total }` |
30
- | `req api run <approve\|stop\|resume\|verify> <需求> [--repo <目录>] [选项]` | `{ command, featureDir, ok, exitCode, message }`。在 `--repo`(默认当前目录)中执行与 `req <命令> <需求>` 完全相同的操作;`ok` 为 false 时退出码为 1 |
30
+ | `req api run <approve\|stop\|resume\|verify\|sync> <需求> [--repo <目录>] [选项]` | `{ command, featureDir, ok, exitCode, message }`。在 `--repo`(默认当前目录)中执行与 `req <命令> <需求>` 完全相同的操作;`ok` 为 false 时退出码为 1 |
31
31
 
32
32
  `<需求>` 的写法与 `req status` 相同:功能目录、目录名或名称中能唯一确定的一部分。需求在为它新建的工作区中开发时,`--repo` 写对话所在的目录或那个工作区都可以。
33
33
 
@@ -64,3 +64,4 @@
64
64
  | `steps` | 每个步骤一项:`{ key, stepId, title, phase, attempt, round, agentId, startedAt, endedAt, minutes, outcome, result, error, usage }`;`title` 是给人看的名称(如"代码评审(第 2 轮)"),`result` 是结论的文字(如"需要修改") |
65
65
  | `gate` | 质量门:状态文件中的 `gate` |
66
66
  | `joint` | 联合需求的其他仓库:`[{ name, slug, repo, sourceRepo, branch, workspaceId, gate }]` |
67
+ | `external` | 外部系统中的卡片(1.9.0-beta.3 起):`[{ name, id, url, status, syncedAt, pending, lastError }]`;`pending` 是等待补发的调用数 |
@@ -67,6 +67,16 @@ spec.md 头部的"版本"与最新一节一致。
67
67
 
68
68
  1.8.0 起。缺陷类型的"复现与定位"写 `diagnosis.md`:现象与复现(复现测试或脚本的位置、命令、修复前的失败输出)、根因(带代码位置)、排除的其他原因、影响面、修复思路。评估类型的"调研评估"写 `assessment.md`(代替 `design.md`):结论摘要、现状、可选方案(含工作量的大小和主要工作项)、推荐、风险与未知、如果要做(需求草稿)。
69
69
 
70
+ ## card.json(外部系统的卡片字段)
71
+
72
+ 1.9.0 起。团队配置了外部系统对接(`integrations`)、且其中写了 `cardFields` 时,由 `/req` 在需求说明确认时和需求方一起确认后写入,与 `spec.md` 一起提交:
73
+
74
+ ```json
75
+ { "title": "<卡片标题,可省略,默认用需求说明的标题>", "body": "<卡片描述,可省略,默认用 spec.md 全文>", "fields": { "<字段>": "<值>" } }
76
+ ```
77
+
78
+ `fields` 的名称来自 `cardFields`,在命令参数中写作 `{card.<字段>}`。没有写的字段用 `defaults` / `integrationDefaults` 中同名的值,都没有时这个参数和它前面的选项一起省略。
79
+
70
80
  ## contract.md(联合需求的接口约定)
71
81
 
72
82
  1.8.0 起,联合需求(spec 的 `- 联合开发:`)的方案设计在主仓库写入:每个接口一节,路径和方法、请求字段(类型、是否必填、取值范围)、响应字段、错误码和提示、一两组真实的请求和响应示例。方案审核通过后冻结,各仓库按它同时开发:提供接口的一边用示例写接口测试,调用的一边用示例做测试替身。主仓库的需求说明、访谈记录、方案、接口约定和预览在实现前同步到其他仓库工作区的同名功能目录并提交;其他仓库的实现、评审和缺陷处理记录写在它们自己的功能目录中。
@@ -34,6 +34,7 @@
34
34
  | `codeBase` | object \| null | 可选,1.8.0 起。`attempt`、`commit`:这次尝试在改动任何代码之前的提交,质量门的基线和范围检查从这里比较。尝试开始时就已经写过代码的旧记录为 `commit: null` |
35
35
  | `joint` | array \| null | 可选,1.8.0 起。联合需求(spec 的 `- 联合开发:`)中其他仓库的工作区:`name`(`relatedRepos` 中的名称)、`slug`(步骤键的后缀,如 `implement@backend`)、`sourceRepo`(切出工作区的那份检出)、`repo`(工作区目录)、`workspaceId`、`branch`、`specHash`(最近一次同步过去的 spec),以及这个仓库自己的 `codeBase` 和 `gate`(格式同顶层)。见 ADR 0025 |
36
36
  | `approval` | object \| null | 可选,1.8.0 起。方案确认点:`attempt`(哪一次尝试的方案)、`status`(`waiting` / `approved`)、`at`。需求变更后的新尝试需要重新确认 |
37
+ | `external` | object \| null | 可选,1.9.0 起。外部系统中的卡片,按集成名称:`id`(卡片编号)、`url`、`status`(最近设置的状态)、`syncedAt`、`pending`(没有成功、等待补发的调用:`action` 为 `create` / `status`、`event`、`status`、`at`、`attempts`、`error`)。见 ADR 0028 |
37
38
  | `gate` | object \| null | 可选,1.8.0 起。质量门:`baseline`(`attempt`、`commit`、`at`、`results`:每项检查改动前 `pass` / `fail`),`runs`(每次运行:`key` 步骤键、`attempt`、`at`、`checks`(`name`、`result`:`pass` / `fail` / `skipped`、`failedBefore`)、`newFailures`、`fixRounds`、`passed`)。各次运行的完整输出在 `.pipeline/checks/<步骤键>/` |
38
39
 
39
40
  `schedules[].kind` 在 1.7.0 起还可能是 `verify-start`(`--verify-at` 预约的测试验证开始),1.8.0 起还可能是 `approval-continue`(方案确认超时后按方案继续,`designApproval.autoContinueHours`)。`history` 中每条步骤记录在 1.7.0 起有可选的 `phase`(`develop` / `verify`),用于按阶段统计用时与用量;没有时按研发阶段统计。
@@ -0,0 +1,23 @@
1
+ # 0027 Paseo 插件:随 req-kit 一起发布,只通过 req api 工作
2
+
3
+ - 状态:已采纳(建立在 [0026](0026-machine-records-and-api.md) 的需求索引和 `req api` 之上;不改变 [0003](0003-script-engine-not-hub.md):流程仍由脚本引擎驱动)
4
+
5
+ ## 背景
6
+
7
+ 需求方和项目经理想在 Paseo 的网页端或手机上看到所有需求在哪一步、卡在哪里,并做确认方案、停止、继续这类操作,而不是在对话里问或在终端敲 `req`。Paseo 0.11 有完整的插件机制:界面(侧边栏、页面、工作区面板、按钮、命令中心)、daemon 侧的 RPC 和生命周期事件,同一份界面代码在桌面版、网页端和手机上运行。
8
+
9
+ ## 决定
10
+
11
+ - **插件只做展示和管理,不参与流程**:每个读取是一次 `req api`,每个管理操作是 `req api run <approve|stop|resume|verify>`,与在终端执行 `req <命令>` 完全相同。插件不读写状态文件,引擎仍是唯一修改需求状态的一方。不提供发起需求(需要在对话中对齐)、编辑需求说明或方案、修改配置、删除。
12
+ - **随 req-kit 一起发布,从固定目录安装**:插件源码在仓库的 `paseo-plugin/`,打进 `paseo-req-kit` 包。`req plugin install` 把当前版本的插件复制到 `~/.req-kit/paseo-plugin`,用 `paseo plugin install <目录>` 安装;之后每次启用版本(setup、upgrade、use)都刷新这个目录并 `paseo plugin reload`。这样插件版本总是和启用的 req-kit 一致,也不需要单独的 npm 包、网络或第二套发布配置。设计时曾考虑第二个 npm 包,Paseo 的目录安装让它变得不必要。
13
+ - **插件总开关要人同意**:Paseo 的 `pluginsEnabled` 默认关闭,插件是不受沙箱限制的代码。`req plugin install` 和安装脚本都先说明再询问,`--yes` / `--with-plugin` 表示同意;移除插件不改动总开关。
14
+ - **只读模式**:Paseo 的插件没有按用户区分的权限,能连上 daemon 的人都能操作。插件设置中的"只读"隐藏管理操作,daemon 侧也拒绝它们。
15
+ - **只依赖 `req api` 的格式**:插件通过本机配置目录的 `active.json` 找到启用的版本并运行它的 `req api`,不引用 req-kit 的内部代码;req-kit 太旧时插件给出升级提示。
16
+ - **界面按 Paseo 的跨平台规则写**:只用 React Native 组件和主题颜色,不用 DOM。插件的界面部分由 CI 用 Paseo 的 SDK 做类型检查(`paseo-plugin/` 的开发依赖),daemon 侧和 `req plugin` 有单元测试;界面效果需要在真实的 Paseo 中检查。
17
+
18
+ ## 影响
19
+
20
+ - 新增 `req plugin install|status|remove`,安装脚本最后可选安装插件。
21
+ - 列表靠轮询刷新(默认 10 秒,可设置);daemon 侧把短时间内的多个列表请求合并成一次 `req api list`。
22
+ - 步骤 agent 的"需求"按钮靠 req-kit 给步骤 agent 打的标签(`requirement-pipeline`)找到需求;工作区的"需求"按钮按目录匹配需求。
23
+ - `paseo-plugin/package.json` 的版本必须与 req-kit 相同(测试检查),`paseo-plugin.json` 的 Paseo 范围与 `reqKit.paseo` 相同。
@@ -0,0 +1,22 @@
1
+ # 0028 外部系统对接:命令写法在配置里,内容由 AI 整理,调用是确定的
2
+
3
+ - 状态:已采纳(与 [0014](0014-notifier-slot.md) 的通知槽并存:通知只发消息,集成会回写)
4
+
5
+ ## 背景
6
+
7
+ 团队希望需求同时出现在自己的需求管理系统中:需求开始时建卡,阶段变化时改卡片状态,卡片编号能在需求、报告和插件里看到。对接的系统各不相同,通常提供本机命令行工具(例如需求管理系统的 CLI),参数会随系统升级变化。
8
+
9
+ ## 决定
10
+
11
+ - **命令写法写在本机或团队配置里,不写进 req-kit 的代码**:`integrations` 中每项写明工具、建卡和改状态的参数(带占位符)、从输出取卡片编号和链接的正则、各个流水线事件对应的卡片状态。换系统或系统改参数只改配置。项目配置不能写 `integrations`(它会运行命令),只能用 `integrationDefaults` 提供自己的取值,例如项目编号。
12
+ - **AI 整理内容,不决定怎么调用**:卡片的业务字段(`cardFields`,如优先级、需求类型、子系统)由 `/req` 在需求说明确认时从需求说明中整理,和需求方一起确认后写进 `card.json`。运行时引擎按配置把这些值填进参数并执行,每次行为一致、可以补发、不会重复建卡;不在每次调用时让 AI 读帮助、拼命令,避免结果不确定、在真实系统上做错操作和难以复现。配置本身可以请 AI 读工具的帮助起草,由人确认。
13
+ - **不经过 shell**:参数数组直接传给进程,标题和 Markdown 描述中的空格、换行、引号都原样传递。Windows 上 npm 安装的工具是 `.cmd` 包装,只有 cmd.exe 能运行,而 cmd.exe 会破坏这类参数,所以改用 node 直接运行包装里指向的脚本。
14
+ - **外部系统出错不影响流水线**:失败或超时的调用记在状态文件的 `external.<名称>.pending`,下一个事件时先按顺序补发;卡片没建好时状态排在后面;等待中的状态只留最新的一个。流水线没在运行时可以 `req sync`。
15
+ - **先试再用**:`req integration test` 打印每个事件会执行的完整命令而不调用。
16
+ - 录入测试用例暂不做,等对接系统提供能力后,作为新的动作和事件加入(例如功能验证结束时把验收标准和结果录入)。
17
+
18
+ ## 影响
19
+
20
+ - 新的状态文件字段 `external`(可选,格式版本仍为 1)、功能目录文件 `card.json`、`req api status` 的 `external`。
21
+ - `/req` 在确认需求说明前运行"卡片字段"命令;没有配置对接时它输出"跳过"。
22
+ - 事件:started、approval、blocked、phase_done、verify、completed(可按结论细分)、failed。
@@ -28,3 +28,5 @@ req-kit 自身的设计决定。修改其中任何一条,请新增一条 ADR
28
28
  - [0024 让分歧更早暴露:验收例子、方案确认点、两个方向的评审、反复的问题变成规则](0024-confirm-early-review-axes.md)
29
29
  - [0025 联合需求:一个需求同时改几个仓库](0025-joint-requirements.md)
30
30
  - [0026 本机的需求索引、使用记录和 req api](0026-machine-records-and-api.md)
31
+ - [0027 Paseo 插件:随 req-kit 一起发布,只通过 req api 工作](0027-paseo-plugin.md)
32
+ - [0028 外部系统对接:命令写法在配置里,内容由 AI 整理,调用是确定的](0028-external-integrations.md)
@@ -60,6 +60,8 @@ req-kit 帮你把一个需求从"我想要……"做到"可以验收":你在 P
60
60
 
61
61
  也可以不进对话,在终端里用 `req` 命令(需要开发同事装好并加入 PATH,见 [安装与升级](安装与升级.md)):在项目目录中 `req list` 看所有需求,`req status` 看进度(多个需求时写名称的一部分,如 `req status sms-login`),`req stop`、`req resume` 暂停和继续。
62
62
 
63
+ 装了 req-kit 插件的话(见 [安装与升级](安装与升级.md) 第 13 节),在 Paseo 侧边栏点"需求":本机所有项目的需求一页看完,"需要你处理"的排在最前;点开一个需求能看到每一步的进度和用时、读需求说明、方案、确认预览和完成报告,也可以直接确认方案、停止、继续或开始测试验证。网页端、桌面版和手机上都能用。
64
+
63
65
  每个步骤都会出现在这个对话的子 agent 列表里(名字形如"[20261012-sms-login] 6-代码评审 第2轮"),点开可以看到它在做什么。功能目录下的 `progress.md` 按时间记录了每一步的结果。
64
66
 
65
67
  可能收到的通知(团队接入了企业 IM 的话,这些通知也会发到 IM):
@@ -126,6 +126,7 @@ node install.mjs --configure
126
126
  | `req notify-test` | 给外部通知发测试消息 |
127
127
  | `req digest` | 项目的需求总览;`--notify` 发到外部通知,`--schedule "0 9 * * 1"` 每周一 9 点自动发送 |
128
128
  | `req stats` | 本机的使用统计;`--report --month 2026-10` 生成汇报用的文件,`--export` / `--merge` 汇总多台机器 |
129
+ | `req sync <需求>` / `req integration test [需求]` | 外部系统对接:补发和试运行(见 [项目定制与运行规则](项目定制与运行规则.md) 的"外部系统对接") |
129
130
  | `req index --rebuild` | 把 1.9 之前运行过的需求登记进本机的需求索引(Paseo 插件据此列出所有需求) |
130
131
 
131
132
  `<需求>` 可以只写名称中能唯一确定的一部分,如 `sms-login`。`req help` 查看全部。不加 PATH 也可以用完整路径 `~/.req-kit/bin/req`(Windows 为 `%USERPROFILE%\.req-kit\bin\req.cmd`)。
@@ -357,3 +358,27 @@ node ~/tools/req-kit/versions/<当前版本>/install.mjs --uninstall
357
358
 
358
359
  事件的类型和字段见 [contracts/notifier-event.md](../../contracts/notifier-event.md)。通知失败只记日志,不影响流水线。项目配置(`.requirement-pipeline.json`)中的 `notifiers` 会被忽略。
359
360
 
361
+ ## 13. Paseo 插件(可选)
362
+
363
+ 1.9 起,req-kit 附带一个 Paseo 插件,把需求搬进 Paseo 的界面(网页端、桌面版、手机都能用):
364
+
365
+ - 侧边栏的"需求":本机所有项目的需求,按"需要你处理、进行中、待测试验证、最近完成"分组,带待处理的数量。
366
+ - 需求详情:每一步的状态、用时和结论(点开即进入那个步骤的 agent)、质量门、联合需求的各仓库、用量;可以读需求说明、方案、确认预览、提测说明、完成报告等文档。
367
+ - 管理操作:确认方案、停止、继续、开始测试验证。每个操作都要再确认一次,做的事和终端里的 `req approve`、`req stop`、`req resume`、`req verify` 完全一样,流水线本身不受插件影响。
368
+ - 每个工作区头部的"需求"按钮和面板、步骤 agent 输入框旁的"需求"按钮、使用统计(同 `req stats`),以及 Ctrl+K(macOS 为 ⌘K)中的"需求总览"。
369
+
370
+ **安装**:在运行 Paseo daemon 的机器上运行
371
+
372
+ ```bash
373
+ req plugin install
374
+ ```
375
+
376
+ 它把当前版本的插件复制到 `~/.req-kit/paseo-plugin`,再用 Paseo 的命令行安装。Paseo 的插件总开关(`pluginsEnabled`)默认是关着的:插件是不受沙箱限制的代码,后台部分可以访问这台机器上的文件和进程,所以打开前会先问你(`--yes` 表示同意)。req-kit 插件只运行本机的 `req api`,不连接任何外部服务。一键安装脚本在最后也会问一次是否安装(`--with-plugin` 直接安装,`--no-plugin` 不问)。
377
+
378
+ 没有 `paseo` 命令时(例如只用桌面版 Paseo),在 Paseo 的 设置 → Plugins 中打开 Enable plugins,再在 Plugin source 中填 `~/.req-kit/paseo-plugin` 的完整路径(运行 `req plugin install` 会准备好这个目录并显示路径)。
379
+
380
+ **更新**:不用单独做。`req setup`、`req upgrade`、`req use` 启用一个版本时,如果装过插件,会把插件文件换成这个版本的并让 Paseo 重新加载,插件总是和启用的 req-kit 版本一致。
381
+
382
+ **只读**:网页端会给只看不操作的人用时,在 Paseo 的 设置 → Plugins → req-kit 中打开"只读":不再显示管理操作,插件的后台也会拒绝它们。能连上这台 daemon 的人都能看到插件,Paseo 的插件没有按用户区分的权限。
383
+
384
+ **其他**:`req plugin status` 看插件是否在运行;`req plugin remove` 从 Paseo 移除(不改插件总开关)。插件需要 req-kit 1.9 和 Paseo 0.11。1.9 之前运行过的需求要先在项目目录运行一次 `req index --rebuild` 才会出现在插件里。插件出问题时:`paseo plugin logs req-kit`。
@@ -26,6 +26,7 @@
26
26
  | 入口、手册、方法 skill、原则 | `npm test` + 对话评估中受影响的剧本(`evals/conversation/`) |
27
27
  | 安装器 | `npm test`(在临时 HOME 中测试),并在 Windows 上实际装一次 |
28
28
  | 评估用例 | `npm run eval:self-check`(参考实现必须通过隐藏验收) |
29
+ | Paseo 插件(`paseo-plugin/`) | `npm test`(插件后台和 `req plugin`);在 `paseo-plugin/` 中 `npm install && npm run typecheck`(CI 的 plugin 任务也会做);用 `req plugin install` 装到一台 Paseo 上,在网页端和手机宽度下看一遍,切换一次深色主题 |
29
30
 
30
31
  提示词改动以评估结果为准:如实率(不夸大)不能下降;隐藏验收通过率、用时、轮次与上一次对比,变差时说明原因。
31
32
 
@@ -60,7 +61,7 @@
60
61
 
61
62
  没有使用 changesets 等发布工具(见 ADR 0013):发布频率低,CHANGELOG 由人写更贴近使用者。
62
63
 
63
- **对外约定**:通知事件(`contracts/notifier-event.md`)由各团队自己的 IM 模块读取,同一 `schema` 内只增加字段;`req` 的子命令和参数会被写进各团队的使用说明,改名时保留旧名一个主版本。
64
+ **对外约定**:通知事件(`contracts/notifier-event.md`)由各团队自己的 IM 模块读取,同一 `schema` 内只增加字段;`req` 的子命令和参数会被写进各团队的使用说明,改名时保留旧名一个主版本。`req api` 的输出(`contracts/cli-json.md`)是插件和脚本读取的格式,同一 `schema` 内只增加字段。
64
65
 
65
66
  ## 5. 处理复盘提交的问题
66
67
 
@@ -83,3 +84,4 @@
83
84
  | `evals/` | 评估:样例仓库、用例、对话剧本 |
84
85
  | `contracts/` | 文件格式约定 |
85
86
  | `docs/adr/` | 设计决定 |
87
+ | `paseo-plugin/` | Paseo 插件:`client/`(界面,React Native)、`server/`(daemon 侧,运行 `req api`)、`shared/`(RPC 约定)。版本号与根目录的 `package.json` 相同,升版本时两处一起改 |
@@ -285,6 +285,38 @@ Paseo 自带的 `/paseo-advisor`、`/paseo-committee` 等编排 skill 也可以
285
285
 
286
286
  本机的需求索引 `~/.req-kit/index/` 记录每个运行过的需求在哪个目录,Paseo 插件据此在一个页面列出本机所有项目的需求。1.9 之前运行过的需求不在索引里,在项目目录运行一次 `req index --rebuild` 补登;目录删掉后用 `req index --prune` 清理。
287
287
 
288
+ ## 外部系统对接(需求管理等)
289
+
290
+ 1.9.0 起,流水线可以把需求同步到团队的需求管理系统:需求开始时建一张卡片,之后每到一个阶段改卡片的状态,卡片编号和链接写回需求,显示在进度、完成报告和 Paseo 插件里。对接的是本机已有的命令行工具(例如需求管理系统的 CLI),写在本机或团队配置中(它会运行命令,项目配置中的会被忽略):
291
+
292
+ ```json
293
+ "integrations": [
294
+ {
295
+ "name": "ALM",
296
+ "command": "alm",
297
+ "create": {
298
+ "args": ["card", "create", "--title", "{title}", "--type", "{field.issueType}", "--project", "{field.projectId}", "--priority", "{card.priority}", "--body", "{body}"],
299
+ "id": "(?<id>[A-Z][A-Z0-9-]*#\\d+)",
300
+ "url": "(https?://\\S+)"
301
+ },
302
+ "status": { "args": ["card", "edit", "{id}", "--state", "{status}"] },
303
+ "statusMap": { "started": "开发中", "phase_done": "待测试", "verify": "测试中", "completed": "已完成", "not_completed": "已挂起" },
304
+ "cardFields": { "priority": "优先级:高 / 中 / 低" },
305
+ "defaults": { "issueType": "用户故事" }
306
+ }
307
+ ],
308
+ "integrationDefaults": { "ALM": { "projectId": "<团队的项目编号>" } }
309
+ ```
310
+
311
+ - **命令**:`args` 是参数数组,引擎不经过 shell 运行,标题或描述里有空格、换行、引号都不会出问题。Windows 上 npm 安装的命令(`.cmd`)会改用 node 直接运行它的脚本。
312
+ - **占位符**:`{title}`(`card.json` 的标题,否则需求说明的标题)、`{body}`(`card.json` 的描述,否则 `spec.md` 全文)、`{card.<字段>}`(`card.json` 中需求方确认过的字段)、`{field.<名称>}`(`defaults` 和项目配置的 `integrationDefaults`,项目可以只写自己的项目编号)、`{id}`、`{url}`、`{status}`、`{feature}`、`{branch}`、`{type}`、`{outcome}`。一个参数只是占位符且没有值时,它和前面的选项一起省略。
313
+ - **卡片编号**:`create.id` 是从建卡命令输出中取编号的正则表达式(取名为 `id` 的组或第一个组),`url` 同理,可以不写。
314
+ - **状态**:`statusMap` 的键是流水线事件:`started`(开始开发)、`approval`(等待确认方案)、`blocked`(等待需求方决定)、`phase_done`(研发完成,待测试)、`verify`(开始测试验证)、`completed`(完成;可以用 `completed_with_issues`、`not_completed` 细分)、`failed`(中断)。没写的事件不改状态。
315
+ - **卡片字段由 AI 整理、需求方确认**:写了 `cardFields` 时,`/req` 在请需求方确认需求说明时,从需求说明中整理出这些字段的取值一起确认,写进功能目录的 `card.json`。命令的写法固定在配置里,运行时不让 AI 决定怎么调用。
316
+ - **失败不影响流水线**:调用失败或超时(`timeoutSeconds`,默认 60)记在状态文件里,下一个事件时先按顺序补发;等待中的状态只留最新的一个,不会重复建卡。流水线没在运行时,`req sync <需求>` 立即补发(插件中也有"同步到外部系统")。
317
+ - **先试再用**:`req integration test [需求]` 显示每个事件会执行的完整命令(用这个需求的需求说明和 `card.json` 填好),不真正调用;`req integration fields` 显示需求方要确认的字段。
318
+ - 认证由工具自己负责(例如工具已登录本机账号)。不要把口令写进配置;工具需要时放在环境变量里。
319
+
288
320
  ## 团队配置
289
321
 
290
322
  几台机器、多个项目共用的设置(统一的模型、评审小组、超时、要求的 req-kit 版本)可以放在一份团队配置里。把它放进一个团队共用的 git 仓库,各台机器克隆下来,在本机配置 `~/.req-kit/config.json` 中写上它的路径(绝对路径,或相对 `~/.req-kit/` 的路径):
package/engine/src/api.ts CHANGED
@@ -256,6 +256,7 @@ export function apiStatus(repoArg: string, feature: string) {
256
256
  : null,
257
257
  steps: state ? state.history.map((h) => stepJson(h, state)) : [],
258
258
  gate: state?.gate ?? null,
259
+ external: Object.entries(state?.external ?? {}).map(([name, r]) => ({ name, id: r.id, url: r.url, status: r.status, syncedAt: r.syncedAt, pending: r.pending.length, lastError: r.pending.at(-1)?.error ?? null })),
259
260
  joint: (state?.joint ?? []).map((j) => ({ name: j.name, slug: j.slug, repo: j.repo, sourceRepo: j.sourceRepo, branch: j.branch, workspaceId: j.workspaceId, gate: j.gate ?? null })),
260
261
  };
261
262
  }
@@ -290,7 +291,7 @@ export function apiIndex(input: { rebuild: string[]; prune: boolean }) {
290
291
  return { scanned: [...dirs], registered, pruned, total: readIndex().length };
291
292
  }
292
293
 
293
- const RUNNABLE = ["approve", "stop", "resume", "verify"] as const;
294
+ const RUNNABLE = ["approve", "stop", "resume", "verify", "sync"] as const;
294
295
 
295
296
  /** Runs `req <command> <需求>` where people would, and reports what it said. */
296
297
  export function apiRun(command: string, repoArg: string, feature: string, extra: string[]) {
@@ -11,6 +11,7 @@ export const USAGE = `用法:
11
11
  start-pipeline [--feature <功能目录>] --status 查看进度(不写 --feature 时:工作区里只有一个需求运行过就看它)
12
12
  start-pipeline --feature <功能目录> --stop 停止(之后可用 --resume 继续)
13
13
  start-pipeline --list 列出这个项目所有工作区中的需求
14
+ start-pipeline --card-fields 团队对接了外部系统时,确认需求说明时要一并确认的卡片字段
14
15
  start-pipeline --feature <功能目录> --spec-review 确认前请另一个模型独立审阅需求说明草稿(后台进行,结果发回对话)
15
16
  start-pipeline --feature <功能目录> --worktree 为这个需求新建 Paseo worktree 工作区并在那里开发(对话目录不变)
16
17
  start-pipeline --feature <功能目录> --at 22:00 预约在指定时间开始(也可以写"明天 8:30""2026-10-14 08:30""+2h")
@@ -52,6 +53,7 @@ export function parseLaunchArgs(argv: string[]): LaunchArgs {
52
53
  status: { type: "boolean", default: false },
53
54
  stop: { type: "boolean", default: false },
54
55
  list: { type: "boolean", default: false },
56
+ "card-fields": { type: "boolean", default: false },
55
57
  "spec-review": { type: "boolean", default: false },
56
58
  worktree: { type: "boolean", default: false },
57
59
  "source-repo": { type: "string" },
@@ -71,7 +73,7 @@ export function parseLaunchArgs(argv: string[]): LaunchArgs {
71
73
  console.log(USAGE);
72
74
  process.exit(0);
73
75
  }
74
- if (!values.feature && !values.list && !values.status) {
76
+ if (!values.feature && !values.list && !values.status && !values["card-fields"]) {
75
77
  throw new Error(`缺少 --feature\n\n${USAGE}`);
76
78
  }
77
79
  const profile = values.profile?.trim().toLowerCase();
@@ -121,6 +123,7 @@ export function parseLaunchArgs(argv: string[]): LaunchArgs {
121
123
  status: values.status ?? false,
122
124
  stop: values.stop ?? false,
123
125
  list: values.list ?? false,
126
+ cardFields: values["card-fields"] ?? false,
124
127
  specReview: values["spec-review"] ?? false,
125
128
  worktree: values.worktree ?? false,
126
129
  sourceRepo: values["source-repo"] ? path.resolve(values["source-repo"]) : null,