paseo-req-kit 1.9.0-beta.1 → 1.9.0-beta.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/CHANGELOG.md +21 -0
- package/README.md +2 -0
- package/bin/req-plugin.mjs +226 -0
- package/bin/req.mjs +8 -0
- package/docs/adr/0027-paseo-plugin.md +23 -0
- package/docs/adr/README.md +1 -0
- package/docs/guide//344/275/277/347/224/250/350/200/205/346/214/207/345/215/227.md +2 -0
- package/docs/guide//345/256/211/350/243/205/344/270/216/345/215/207/347/272/247.md +24 -0
- package/docs/guide//347/273/264/346/212/244/350/200/205/346/214/207/345/215/227.md +3 -1
- package/install.mjs +3 -0
- package/package.json +2 -1
- package/paseo-plugin/OVERVIEW.md +7 -0
- package/paseo-plugin/client/data.ts +55 -0
- package/paseo-plugin/client/detail.tsx +277 -0
- package/paseo-plugin/client/format.ts +101 -0
- package/paseo-plugin/client/live.ts +161 -0
- package/paseo-plugin/client/overview.tsx +89 -0
- package/paseo-plugin/client/panel.tsx +56 -0
- package/paseo-plugin/client/row.tsx +40 -0
- package/paseo-plugin/client/settings.tsx +90 -0
- package/paseo-plugin/client/sidebar.tsx +27 -0
- package/paseo-plugin/client/stats.tsx +80 -0
- package/paseo-plugin/client/ui.tsx +111 -0
- package/paseo-plugin/index.client.tsx +52 -0
- package/paseo-plugin/index.server.ts +41 -0
- package/paseo-plugin/package.json +22 -0
- package/paseo-plugin/paseo-plugin.json +6 -0
- package/paseo-plugin/server/handlers.ts +45 -0
- package/paseo-plugin/server/req.ts +100 -0
- package/paseo-plugin/shared/contracts.ts +165 -0
- package/paseo-plugin/tsconfig.json +16 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,27 @@
|
|
|
2
2
|
|
|
3
3
|
每个版本分为"使用者会看到的变化"和"升级须知"。升级步骤见 [docs/guide/安装与升级.md](docs/guide/安装与升级.md)。
|
|
4
4
|
|
|
5
|
+
## 1.9.0-beta.2
|
|
6
|
+
|
|
7
|
+
Paseo 插件:在 Paseo 的界面里查看和管理需求。
|
|
8
|
+
|
|
9
|
+
### 使用者会看到的变化
|
|
10
|
+
|
|
11
|
+
- **req-kit 插件**(`req plugin install`):在 Paseo 的网页端、桌面版和手机上都能用。
|
|
12
|
+
- 侧边栏的"需求":本机所有项目的需求,按"需要你处理、进行中、待测试验证、最近完成"分组,带待处理的数量。
|
|
13
|
+
- 需求详情:每一步的状态、用时和结论(点开进入那个步骤的 agent)、质量门、联合需求的各仓库、用量;可以读需求说明、方案、确认预览、提测说明、完成报告等。
|
|
14
|
+
- 确认方案、停止、继续、开始测试验证:每个都要再确认一次,做的事与终端里的 `req` 命令完全相同。
|
|
15
|
+
- 每个工作区头部的"需求"按钮和面板、步骤 agent 输入框旁的"需求"按钮、使用统计页、Ctrl+K 中的"需求总览"。
|
|
16
|
+
- 插件设置中可以打开"只读",给只看不操作的人用。
|
|
17
|
+
- **安装脚本**最后询问是否安装插件(`--with-plugin` 直接安装,`--no-plugin` 不问;PowerShell 用 `$ReqKitPlugin`)。
|
|
18
|
+
- **修正**:安装脚本判断 Paseo 网页界面是否已开启时,读错了 `paseo daemon config get` 的输出("已设置"被当成"已开启")。
|
|
19
|
+
|
|
20
|
+
### 升级须知
|
|
21
|
+
|
|
22
|
+
- 插件是可选的。安装时会询问是否打开 Paseo 的插件总开关(`pluginsEnabled`):插件是不受沙箱限制的代码;req-kit 插件只运行本机的 `req api`,不连接外部服务。
|
|
23
|
+
- 装过插件后,`req setup`、`req upgrade`、`req use` 会自动把插件换成启用的版本。插件随 req-kit 一起发布,不需要单独的 npm 包。
|
|
24
|
+
- 需要 Paseo 0.11。1.9 之前运行过的需求,先在项目目录运行一次 `req index --rebuild`。设计决定见 ADR 0027。
|
|
25
|
+
|
|
5
26
|
## 1.9.0-beta.1
|
|
6
27
|
|
|
7
28
|
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
|
@@ -40,6 +40,11 @@ const HELP = `req-kit ${version}(${kitRoot})
|
|
|
40
40
|
本机记录的需求(Paseo 插件据此找到所有需求);1.9 之前运行过的用 --rebuild 补登
|
|
41
41
|
req api <命令> … 给程序用的 JSON 输出(见 contracts/cli-json.md)
|
|
42
42
|
|
|
43
|
+
Paseo 插件(在 Paseo 中查看和管理需求):
|
|
44
|
+
req plugin install [--yes] 安装或更新插件;需要时会询问是否打开 Paseo 的插件总开关
|
|
45
|
+
req plugin status 插件是否在运行
|
|
46
|
+
req plugin remove 从 Paseo 移除插件
|
|
47
|
+
|
|
43
48
|
本机维护:
|
|
44
49
|
req doctor [--no-project] 体检:安装、配置、daemon、当前项目
|
|
45
50
|
req configure 配置向导:选择 builder 和 reviewer 用的 agent
|
|
@@ -175,6 +180,9 @@ switch (command) {
|
|
|
175
180
|
case "api":
|
|
176
181
|
code = needTsx() ? node([tsxCli, path.join(kitRoot, "engine", "src", "api.ts"), ...rest]) : 1;
|
|
177
182
|
break;
|
|
183
|
+
case "plugin":
|
|
184
|
+
code = node([path.join(kitRoot, "bin", "req-plugin.mjs"), ...rest]);
|
|
185
|
+
break;
|
|
178
186
|
case "notify-test":
|
|
179
187
|
code = needTsx() ? node([tsxCli, path.join(kitRoot, "engine", "src", "notify-test.ts"), ...rest]) : 1;
|
|
180
188
|
break;
|
|
@@ -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` 相同。
|
package/docs/adr/README.md
CHANGED
|
@@ -28,3 +28,4 @@ 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)
|
|
@@ -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):
|
|
@@ -357,3 +357,27 @@ node ~/tools/req-kit/versions/<当前版本>/install.mjs --uninstall
|
|
|
357
357
|
|
|
358
358
|
事件的类型和字段见 [contracts/notifier-event.md](../../contracts/notifier-event.md)。通知失败只记日志,不影响流水线。项目配置(`.requirement-pipeline.json`)中的 `notifiers` 会被忽略。
|
|
359
359
|
|
|
360
|
+
## 13. Paseo 插件(可选)
|
|
361
|
+
|
|
362
|
+
1.9 起,req-kit 附带一个 Paseo 插件,把需求搬进 Paseo 的界面(网页端、桌面版、手机都能用):
|
|
363
|
+
|
|
364
|
+
- 侧边栏的"需求":本机所有项目的需求,按"需要你处理、进行中、待测试验证、最近完成"分组,带待处理的数量。
|
|
365
|
+
- 需求详情:每一步的状态、用时和结论(点开即进入那个步骤的 agent)、质量门、联合需求的各仓库、用量;可以读需求说明、方案、确认预览、提测说明、完成报告等文档。
|
|
366
|
+
- 管理操作:确认方案、停止、继续、开始测试验证。每个操作都要再确认一次,做的事和终端里的 `req approve`、`req stop`、`req resume`、`req verify` 完全一样,流水线本身不受插件影响。
|
|
367
|
+
- 每个工作区头部的"需求"按钮和面板、步骤 agent 输入框旁的"需求"按钮、使用统计(同 `req stats`),以及 Ctrl+K(macOS 为 ⌘K)中的"需求总览"。
|
|
368
|
+
|
|
369
|
+
**安装**:在运行 Paseo daemon 的机器上运行
|
|
370
|
+
|
|
371
|
+
```bash
|
|
372
|
+
req plugin install
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
它把当前版本的插件复制到 `~/.req-kit/paseo-plugin`,再用 Paseo 的命令行安装。Paseo 的插件总开关(`pluginsEnabled`)默认是关着的:插件是不受沙箱限制的代码,后台部分可以访问这台机器上的文件和进程,所以打开前会先问你(`--yes` 表示同意)。req-kit 插件只运行本机的 `req api`,不连接任何外部服务。一键安装脚本在最后也会问一次是否安装(`--with-plugin` 直接安装,`--no-plugin` 不问)。
|
|
376
|
+
|
|
377
|
+
没有 `paseo` 命令时(例如只用桌面版 Paseo),在 Paseo 的 设置 → Plugins 中打开 Enable plugins,再在 Plugin source 中填 `~/.req-kit/paseo-plugin` 的完整路径(运行 `req plugin install` 会准备好这个目录并显示路径)。
|
|
378
|
+
|
|
379
|
+
**更新**:不用单独做。`req setup`、`req upgrade`、`req use` 启用一个版本时,如果装过插件,会把插件文件换成这个版本的并让 Paseo 重新加载,插件总是和启用的 req-kit 版本一致。
|
|
380
|
+
|
|
381
|
+
**只读**:网页端会给只看不操作的人用时,在 Paseo 的 设置 → Plugins → req-kit 中打开"只读":不再显示管理操作,插件的后台也会拒绝它们。能连上这台 daemon 的人都能看到插件,Paseo 的插件没有按用户区分的权限。
|
|
382
|
+
|
|
383
|
+
**其他**:`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` 相同,升版本时两处一起改 |
|
package/install.mjs
CHANGED
|
@@ -911,6 +911,9 @@ if (values.check) {
|
|
|
911
911
|
if (active && path.resolve(active.root) !== path.resolve(KIT_ROOT)) ok(`已从 ${active.version} 切换到 ${VERSION}`);
|
|
912
912
|
else ok(`已启用 ${VERSION}`);
|
|
913
913
|
writeShims();
|
|
914
|
+
// The Paseo plugin, when installed, follows the active version (bin/req-plugin.mjs).
|
|
915
|
+
const { refreshPlugin } = await import("./bin/req-plugin.mjs");
|
|
916
|
+
refreshPlugin({ kitRoot: KIT_ROOT, log: (message) => ok(message) });
|
|
914
917
|
}
|
|
915
918
|
checkLegacyLeftovers(roots);
|
|
916
919
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "paseo-req-kit",
|
|
3
|
-
"version": "1.9.0-beta.
|
|
3
|
+
"version": "1.9.0-beta.2",
|
|
4
4
|
"description": "req-kit: a requirement-development workflow for Paseo. /req aligns a requirement in chat; the engine runs design, implementation, review and verification as Paseo agents.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -49,6 +49,7 @@
|
|
|
49
49
|
"bin/",
|
|
50
50
|
"engine/src/",
|
|
51
51
|
"engine/prompts/",
|
|
52
|
+
"paseo-plugin/",
|
|
52
53
|
"skills/",
|
|
53
54
|
"principles/",
|
|
54
55
|
"contracts/",
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
req-kit 的需求在 Paseo 里的样子:本机所有项目的需求一页看完,哪些在等你确认、哪些卡住了、哪些在运行;点开一个需求,看它走到哪一步、每一步用了多久、质量门的结果,读需求说明、方案、确认预览、提测说明和完成报告。
|
|
2
|
+
|
|
3
|
+
需要人处理的时候,可以直接在这里确认方案、停止、继续或开始测试验证。这些操作和在终端运行 `req approve`、`req stop`、`req resume`、`req verify` 完全一样,流水线本身不受插件影响;不想让人在网页上操作时,在插件设置中打开"只读"。
|
|
4
|
+
|
|
5
|
+
还有:侧边栏的"需求"(带待处理数)、每个工作区头部的"需求"按钮和面板、步骤 agent 输入框旁的"需求"按钮,以及使用统计(交付周期、返工、等待、用量)。
|
|
6
|
+
|
|
7
|
+
需要这台 daemon 上安装 req-kit 1.9 或更新的版本,插件通过本机的 `req api` 读取需求;它不连接任何外部服务。用 `req plugin install` 安装和更新。
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// Data hooks: every read is an RPC to the daemon side (req api), cached by React Query.
|
|
2
|
+
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
|
|
3
|
+
import { useRpc, useSettings } from "@getpaseo/plugin/client";
|
|
4
|
+
import { fileRpc, listRpc, preferences, runRpc, statsRpc, statusRpc, versionRpc, type Action } from "../shared/contracts";
|
|
5
|
+
|
|
6
|
+
export function usePreferences(): { readOnly: boolean; refreshMs: number } {
|
|
7
|
+
const settings = useSettings(preferences);
|
|
8
|
+
if (settings.status !== "ready") return { readOnly: true, refreshMs: 10_000 };
|
|
9
|
+
return { readOnly: settings.values.readOnly, refreshMs: settings.values.refreshSeconds * 1000 };
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function useRequirements() {
|
|
13
|
+
const list = useRpc(listRpc);
|
|
14
|
+
const { refreshMs } = usePreferences();
|
|
15
|
+
return useQuery({ queryKey: ["req", "list"], queryFn: () => list({}), refetchInterval: refreshMs });
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function useRequirement(repo: string, featureDir: string) {
|
|
19
|
+
const status = useRpc(statusRpc);
|
|
20
|
+
const { refreshMs } = usePreferences();
|
|
21
|
+
return useQuery({
|
|
22
|
+
queryKey: ["req", "status", repo, featureDir],
|
|
23
|
+
queryFn: () => status({ repo, featureDir }),
|
|
24
|
+
refetchInterval: refreshMs,
|
|
25
|
+
enabled: Boolean(repo && featureDir),
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function useDocument(repo: string, featureDir: string, file: string | null) {
|
|
30
|
+
const read = useRpc(fileRpc);
|
|
31
|
+
return useQuery({
|
|
32
|
+
queryKey: ["req", "file", repo, featureDir, file],
|
|
33
|
+
queryFn: () => read({ repo, featureDir, file: file ?? "" }),
|
|
34
|
+
enabled: Boolean(file),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function useStats(period: { since?: string; month?: string }) {
|
|
39
|
+
const stats = useRpc(statsRpc);
|
|
40
|
+
return useQuery({ queryKey: ["req", "stats", period.since ?? "", period.month ?? ""], queryFn: () => stats(period) });
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function useKitInfo() {
|
|
44
|
+
const version = useRpc(versionRpc);
|
|
45
|
+
return useQuery({ queryKey: ["req", "version"], queryFn: () => version({}), retry: false });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function useAction() {
|
|
49
|
+
const run = useRpc(runRpc);
|
|
50
|
+
const queries = useQueryClient();
|
|
51
|
+
return useMutation({
|
|
52
|
+
mutationFn: (input: { action: Action; repo: string; featureDir: string; verifyEnv?: "local" | "deployed" }) => run(input),
|
|
53
|
+
onSettled: () => queries.invalidateQueries({ queryKey: ["req"] }),
|
|
54
|
+
});
|
|
55
|
+
}
|