@ghyper9023/pi-dev-workflow 0.7.0 → 0.8.0

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.
@@ -1,11 +1,23 @@
1
1
  /**
2
- * test-no-subagents.mjs — 验证子代理系统已移除,其余功能正常工作
2
+ * test-no-subagents.mjs — 结构性回归断言
3
3
  *
4
4
  * Run: node tests/test-no-subagents.mjs
5
+ *
6
+ * 覆盖:
7
+ * 1. 子代理基础设施已删除
8
+ * 2. dev 命令:4 个命令、参数即任务、意图识别复用 pre-check、不再绑定 Grill/PRD
9
+ * 3. 自动审查检测独立为 review-detect.ts
10
+ * 4. git 命令直接执行,不再委派给子代理
11
+ * 5. /grill 与 /prd 是独立命令
12
+ * 6. UI 组件与输出目录约定
13
+ * 7. /dev-pre-check 意图校验(confirmIntent 为共享实现)
14
+ * 8. notify 调用使用合法类型(info / warning / error)
15
+ * 9. 当前版本的 RELEASE 说明覆盖该版本区间内的全部 commit
5
16
  */
6
17
 
7
18
  import * as fs from "node:fs";
8
19
  import * as path from "node:path";
20
+ import { execFileSync } from "node:child_process";
9
21
  import { fileURLToPath } from "node:url";
10
22
 
11
23
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
@@ -57,24 +69,49 @@ assertNotExists("agents/", "agents/ 目录已删除");
57
69
  assertNotExists(".doc/AGENT-FRONTMATTER-REFERENCE.md", "AGENT-FRONTMATTER-REFERENCE.md 已删除");
58
70
 
59
71
  // ═══════════════════════════════════════════════════════════════
60
- // 2. dev-prompts 不再引用子代理,改为直接发送给当前代理
72
+ // 2. dev 命令:4 个命令,参数即任务,意图识别复用 pre-check
61
73
  // ═══════════════════════════════════════════════════════════════
62
74
 
63
75
  console.log("\n📋 dev-prompts.ts\n");
64
76
 
65
77
  assertNotIncludes("extensions/dev-prompts.ts", "./sub-agents", "不再引入 sub-agents");
66
78
  assertNotIncludes("extensions/dev-prompts.ts", "./workflow-engine", "不再引入 workflow-engine");
67
- assertNotIncludes("extensions/dev-prompts.ts", "runWorkflow", "不再调用 runWorkflow");
68
- assertNotIncludes("extensions/dev-prompts.ts", "discoverAgents", "不再自动发现 agent");
69
- assertNotIncludes("extensions/dev-prompts.ts", "WORKFLOW_STEPS", "不再定义工作流步骤链");
70
- assertIncludes("extensions/dev-prompts.ts", "event.source === \"extension\"", "过滤扩展注入消息,防止递归");
71
- assertIncludes("extensions/dev-prompts.ts", "expandPromptTemplates: true", "skill 命令展开执行");
72
- assertIncludes("extensions/dev-prompts.ts", "pi.sendUserMessage(finalPrompt", "组装后的提示词直接发送给当前代理");
73
- assertIncludes("extensions/dev-prompts.ts", "saveAnswerFile(ctx.cwd, finalPrompt)", "保留提示词持久化");
74
- assertIncludes("extensions/dev-prompts.ts", "recoverFromBackup(ctx.cwd)", "保留断点恢复");
79
+ assertNotIncludes("extensions/dev-prompts.ts", "./grill-me-agent", "不再绑定 Grill/PRD 运行时");
80
+ assertNotIncludes("extensions/dev-prompts.ts", "runGrillPhase", "不再触发 Grill 追问");
81
+ assertNotIncludes("extensions/dev-prompts.ts", "runPRDPhase", "不再触发 PRD 生成");
82
+ assertNotIncludes("extensions/dev-prompts.ts", "WizardQuestion", "向导式提问已移除");
83
+ assertIncludes("extensions/dev-prompts.ts", 'import { confirmIntent } from "./pre-check"', "意图识别复用 pre-check");
84
+ assertIncludes("extensions/dev-prompts.ts", "uiTaskArg(ctx, args", "任务原文来自命令参数,缺省才弹输入框");
85
+ assertIncludes("extensions/dev-prompts.ts", "detectProjectDefaults", "默认验收标准来自项目探测");
86
+ assertIncludes("extensions/dev-prompts.ts", "defaultAcceptanceItems", "默认验收标准按条目生成");
87
+ assertIncludes("extensions/dev-prompts.ts", "## 身份与职责", "提示词包含身份与职责段");
88
+ assertIncludes("extensions/dev-prompts.ts", "## 已确认的任务意图", "提示词携带已确认的意图");
89
+ assertIncludes("extensions/dev-prompts.ts", "## 验收标准", "提示词包含默认验收标准段");
90
+ assertIncludes("extensions/dev-prompts.ts", "pi.sendUserMessage(prompt", "组装后的提示词直接发送给当前代理");
91
+
92
+ for (const cmd of ["dev-feat", "dev-fix", "dev-refactor", "dev-test"]) {
93
+ assertIncludes("extensions/dev-prompts.ts", `registerDev(pi, "${cmd}"`, `注册 /${cmd}`);
94
+ }
95
+
96
+ for (const cmd of ["dev-doc", "dev-perf", "dev-style", "dev-security", "dev-chore", "dev-explain", "dev-compare"]) {
97
+ assertNotIncludes("extensions/dev-prompts.ts", `"${cmd}"`, `/${cmd} 已移除`);
98
+ }
99
+
100
+ // ═══════════════════════════════════════════════════════════════
101
+ // 3. 自动审查检测独立为 review-detect.ts
102
+ // ═══════════════════════════════════════════════════════════════
103
+
104
+ console.log("\n📋 review-detect.ts\n");
105
+
106
+ assertExists("extensions/review-detect.ts", "自动审查检测独立成扩展");
107
+ assertNotIncludes("extensions/review-detect.ts", "./sub-agents", "不再引入 sub-agents");
108
+ assertIncludes("extensions/review-detect.ts", 'pi.on("input"', "拦截带审查意图的输入");
109
+ assertIncludes("extensions/review-detect.ts", 'event.source === "extension"', "过滤扩展注入消息,防止递归");
110
+ assertIncludes("extensions/review-detect.ts", "expandPromptTemplates: true", "skill 命令展开执行");
111
+ assertIncludes("extensions/review-detect.ts", '"pi-review"', "自动审查仍查找 pi-review/ 输出目录");
75
112
 
76
113
  // ═══════════════════════════════════════════════════════════════
77
- // 3. Git 命令直接执行,不再委派给子代理
114
+ // 4. Git 命令直接执行,不再委派给子代理
78
115
  // ═══════════════════════════════════════════════════════════════
79
116
 
80
117
  console.log("\n📋 git-commands.ts\n");
@@ -88,47 +125,128 @@ assertIncludes("extensions/git-commands.ts", "git-push", "保留 /git-push 命
88
125
  assertIncludes("extensions/git-commands.ts", "git-commit-push", "保留 /git-commit-push 命令");
89
126
 
90
127
  // ═══════════════════════════════════════════════════════════════
91
- // 4. Grill / PRD 运行在当前代理中
128
+ // 5. Grill / PRD 是独立命令,运行在当前代理中
92
129
  // ═══════════════════════════════════════════════════════════════
93
130
 
94
131
  console.log("\n📋 grill-me-agent.ts\n");
95
132
 
96
133
  assertNotIncludes("extensions/grill-me-agent.ts", "./sub-agents", "不再引入 sub-agents");
97
134
  assertNotIncludes("extensions/grill-me-agent.ts", "spawnSubagent", "不再 spawn 子进程");
135
+ assertNotIncludes("extensions/grill-me-agent.ts", "recoverFromBackup", "断点恢复随向导一并移除");
136
+ assertIncludes("extensions/grill-me-agent.ts", 'registerCommand("grill"', "注册独立 /grill 命令");
137
+ assertIncludes("extensions/grill-me-agent.ts", 'registerCommand("prd"', "注册独立 /prd 命令");
98
138
  assertIncludes("extensions/grill-me-agent.ts", "pollFor", "轮询等待当前代理产物后读取结果");
99
139
  assertIncludes("extensions/grill-me-agent.ts", "GRILL_ANSWERS_DIRNAME = \"answers\"", "保留 answers 子目录");
100
140
  assertIncludes("extensions/grill-me-agent.ts", "GRILL_QUESTIONS_DIRNAME = \"questions\"", "保留 questions 子目录");
101
141
 
102
142
  // ═══════════════════════════════════════════════════════════════
103
- // 5. UI 组件保留 select/confirm/input,移除工作流面板
143
+ // 6. UI 组件与输出目录约定
104
144
  // ═══════════════════════════════════════════════════════════════
105
145
 
106
- console.log("\n📋 ui-helpers.ts\n");
146
+ console.log("\n📋 输出目录与 UI 组件\n");
107
147
 
108
- assertNotIncludes("extensions/ui-helpers.ts", "updateWorkflowWidget", "移除工作流 widget");
109
- assertNotIncludes("extensions/ui-helpers.ts", "sendWorkflowResult", "移除工作流结果消息");
110
- assertNotIncludes("extensions/ui-helpers.ts", "WorkflowStepWidgetState", "移除工作流状态类型");
148
+ const reviewSkill = fs.readFileSync(path.resolve(ROOT, "skills/review-html/SKILL.md"), "utf-8");
149
+ assert(reviewSkill.includes(".pi-dev-output/pi-review/html/"), "review-html 仍写入 pi-review/html/");
150
+
151
+ assertIncludes("extensions/session-utils.ts", "detectProjectDefaults", "项目探测(语言/测试/lint/pre-commit/CI)");
152
+ assertIncludes("extensions/session-utils.ts", "defaultAcceptanceItems", "生成默认验收标准条目");
111
153
  assertIncludes("extensions/ui-helpers.ts", "export function uiSelect", "保留 uiSelect");
112
154
  assertIncludes("extensions/ui-helpers.ts", "export function uiConfirm", "保留 uiConfirm");
113
155
  assertIncludes("extensions/ui-helpers.ts", "export function uiInput", "保留 uiInput");
156
+ assertIncludes("extensions/ui-helpers.ts", "export async function uiTaskArg", "命令参数与输入框二选一取任务文本");
157
+ assertNotIncludes("extensions/ui-helpers.ts", "updateWorkflowWidget", "移除工作流 widget");
158
+ assertNotIncludes("extensions/ui-helpers.ts", "sendWorkflowResult", "移除工作流结果消息");
159
+ assertNotIncludes("extensions/ui-helpers.ts", "WorkflowStepWidgetState", "移除工作流状态类型");
114
160
 
115
161
  // ═══════════════════════════════════════════════════════════════
116
- // 6. 审查技能与输出目录结构保持不变
162
+ // 7. /dev-pre-check 独立意图校验扩展
117
163
  // ═══════════════════════════════════════════════════════════════
118
164
 
119
- console.log("\n📋 输出目录与技能\n");
165
+ console.log("\n📋 pre-check.ts\n");
120
166
 
121
- const reviewSkill = fs.readFileSync(path.resolve(ROOT, "skills/review-html/SKILL.md"), "utf-8");
122
- assertIncludes("skills/review-html/SKILL.md", ".pi-dev-output/pi-review/html/", "review-html 仍写入 pi-review/html/");
167
+ assertExists("extensions/pre-check.ts", "提供独立的 pre-check 扩展");
168
+ assertIncludes("extensions/pre-check.ts", 'registerCommand("dev-pre-check"', "注册 /dev-pre-check 命令");
169
+ assertIncludes("extensions/pre-check.ts", "export async function confirmIntent", "导出共享的意图确认循环");
170
+ assertIncludes("extensions/pre-check.ts", "用自己的话重述你认为用户的目标是什么,以及用户试图解决的问题是什么", "固定指令:用自己的话重述目标与问题");
171
+ assertIncludes("extensions/pre-check.ts", "[pre-check] 任务意图校验:只复述,不执行", "意图校验提示词声明只复述不执行");
172
+ assertIncludes("extensions/pre-check.ts", "禁止修改、创建、删除任何文件", "约束禁止任何实质改动");
173
+ assertIncludes("extensions/pre-check.ts", "pi.sendUserMessage(buildExecutionPrompt", "确认通过后才发送执行提示词");
174
+ assertIncludes("extensions/pre-check.ts", "buildExecutionPrompt(originalPrompt: string, intent: string)", "执行提示词携带已确认的意图复述");
175
+ assertIncludes("extensions/pre-check.ts", "ctx.isIdle()", "以代理空闲作为复述完成信号之一");
176
+ assertIncludes("extensions/pre-check.ts", "pollFor", "轮询等待复述产物");
177
+ assertNotIncludes("extensions/pre-check.ts", "./dev-prompts", "不依赖 /dev-* 命令实现");
178
+ assertNotIncludes("extensions/pre-check.ts", "./grill-me-agent", "不依赖 Grill/PRD 运行时");
179
+ assertNotIncludes("extensions/dev-prompts.ts", "dev-pre-check", "dev-prompts 不注册覆盖 pre-check 的命令");
123
180
 
124
- const devPrompts = fs.readFileSync(path.resolve(ROOT, "extensions/dev-prompts.ts"), "utf-8");
125
- assertIncludes("extensions/dev-prompts.ts", "\"pi-review\"", "自动审查仍查找 pi-review/ 输出目录");
126
- assertIncludes("extensions/session-utils.ts", "detectProjectDefaults", "项目探测(语言/测试/lint/pre-commit/CI)");
127
- assertIncludes("extensions/session-utils.ts", "defaultAcceptance", "生成默认验收标准");
128
- assertIncludes("extensions/dev-prompts.ts", "applyDefaults", "未填字段注入默认值");
129
- assertIncludes("extensions/dev-prompts.ts", "**验收标准**", "四段式:验收标准段");
130
- assertIncludes("extensions/dev-prompts.ts", "WizardQuestion", "提问结构支持字段合并");
131
- assertIncludes("extensions/dev-prompts.ts", "assignAnswers", "提问支持单值/多字段填写");
181
+ // ═══════════════════════════════════════════════════════════════
182
+ // 8. notify 调用使用合法类型
183
+ // ═══════════════════════════════════════════════════════════════
184
+
185
+ console.log("\n📋 notify 类型\n");
186
+
187
+ const NOTIFY_TYPES = ["info", "warning", "error"];
188
+ const badNotify = [];
189
+ for (const file of fs.readdirSync(path.resolve(ROOT, "extensions"))) {
190
+ if (!file.endsWith(".ts")) continue;
191
+ const lines = fs.readFileSync(path.resolve(ROOT, "extensions", file), "utf-8").split("\n");
192
+ for (const line of lines) {
193
+ const matched = line.match(/\.notify\(.*,\s*"([^"]+)"\s*\)/);
194
+ if (matched && !NOTIFY_TYPES.includes(matched[1])) badNotify.push(`${file} → ${matched[1]}`);
195
+ }
196
+ }
197
+ assert(
198
+ badNotify.length === 0,
199
+ badNotify.length === 0 ? "所有 notify 调用使用合法类型(info / warning / error)" : `非法 notify 类型: ${badNotify.join(", ")}`,
200
+ );
201
+
202
+ // ═══════════════════════════════════════════════════════════════
203
+ // 9. 版本说明覆盖该版本全部 commit
204
+ // ═══════════════════════════════════════════════════════════════
205
+
206
+ console.log("\n📋 版本说明\n");
207
+
208
+ function git(args) {
209
+ return execFileSync("git", args, { cwd: ROOT, encoding: "utf-8" }).trim();
210
+ }
211
+
212
+ const version = JSON.parse(fs.readFileSync(path.resolve(ROOT, "package.json"), "utf-8")).version;
213
+ const releaseDoc = `.version/RELEASE-v${version}.md`;
214
+ assertExists(releaseDoc, `存在 ${releaseDoc}`);
215
+
216
+ const docText = fs.readFileSync(path.resolve(ROOT, releaseDoc), "utf-8");
217
+ const listed = docText
218
+ .split("\n")
219
+ .map((line) => line.match(/^[0-9a-f]{7,40}\b/)?.[0])
220
+ .filter(Boolean);
221
+ assert(listed.length > 0, "Commit History 至少列出一条 commit");
222
+
223
+ const unknown = listed.filter((h) => {
224
+ try {
225
+ git(["rev-parse", "--verify", "--quiet", `${h}^{commit}`]);
226
+ return false;
227
+ } catch {
228
+ return true;
229
+ }
230
+ });
231
+ assert(unknown.length === 0, `列出的 commit 都真实存在(${unknown.join(", ") || "无缺失"})`);
232
+
233
+ // 以文档中最新的一条 commit 作为覆盖终点,避免把后续版本的提交算进本版本
234
+ const tip = listed.reduce((a, b) =>
235
+ Number(git(["rev-list", "--count", a])) >= Number(git(["rev-list", "--count", b])) ? a : b,
236
+ );
237
+ const prevTag = git(["tag", "--merged", tip, "--sort=-v:refname"])
238
+ .split("\n")
239
+ .find((tag) => tag && tag !== `v${version}`);
240
+ assert(Boolean(prevTag), `找到上一版本 tag(${prevTag})`);
241
+
242
+ const range = git(["log", "--format=%h", `${prevTag}..${tip}`]).split("\n").filter(Boolean);
243
+ const missing = range.filter((h) => !listed.some((l) => h.startsWith(l) || l.startsWith(h)));
244
+ assert(
245
+ missing.length === 0,
246
+ missing.length === 0
247
+ ? `${releaseDoc} 覆盖 ${prevTag}..${tip} 全部 ${range.length} 个 commit`
248
+ : `${releaseDoc} 缺少 ${missing.length} 个 commit: ${missing.join(", ")}`,
249
+ );
132
250
 
133
251
  // ═══════════════════════════════════════════════════════════════
134
252
  // Summary
@@ -142,5 +260,5 @@ if (fail > 0) {
142
260
  console.error("\n⚠️ 部分测试未通过");
143
261
  process.exit(1);
144
262
  } else {
145
- console.log("\n✅ 所有测试通过 — 子代理已移除,其余功能正常运行");
146
- }
263
+ console.log("\n✅ 所有测试通过");
264
+ }