@ghyper9023/pi-dev-workflow 0.6.3 → 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.
Files changed (40) hide show
  1. package/.github/workflows/release.yml +56 -0
  2. package/.pre-commit-config.yaml +75 -0
  3. package/.version/RELEASE-v0.7.0.md +85 -0
  4. package/.version/RELEASE-v0.8.0.md +93 -0
  5. package/README.md +178 -294
  6. package/extensions/dev-prompts.ts +121 -1033
  7. package/extensions/git-commands.ts +62 -171
  8. package/extensions/grill-me-agent.ts +160 -164
  9. package/extensions/pre-check.ts +190 -0
  10. package/extensions/review-detect.ts +123 -0
  11. package/extensions/session-utils.ts +168 -0
  12. package/extensions/ui-helpers.ts +22 -780
  13. package/package.json +2 -2
  14. package/prompts/APPEND_SYSTEM.md +73 -59
  15. package/skills/review-html/SKILL.md +1 -1
  16. package/tests/test-no-subagents.mjs +264 -0
  17. package/.doc/AGENT-FRONTMATTER-REFERENCE.md +0 -198
  18. package/agents/git-agent.md +0 -44
  19. package/agents/grill/dev-doc-grill-agent.md +0 -42
  20. package/agents/grill/dev-fix-grill-agent.md +0 -44
  21. package/agents/grill/dev-grill-agent.md +0 -40
  22. package/agents/grill/dev-perf-grill-agent.md +0 -45
  23. package/agents/grill/dev-prd-agent.md +0 -60
  24. package/agents/grill/dev-refactor-grill-agent.md +0 -46
  25. package/agents/grill/dev-test-grill-agent.md +0 -45
  26. package/agents/review-agent.md +0 -53
  27. package/agents/workflow/docWriter-agent.md +0 -53
  28. package/agents/workflow/planner-agent.md +0 -131
  29. package/agents/workflow/reviewer-agent.md +0 -128
  30. package/agents/workflow/trimmer-agent.md +0 -78
  31. package/agents/workflow/worker-agent.md +0 -70
  32. package/extensions/sub-agents.ts +0 -954
  33. package/extensions/workflow-engine.ts +0 -2005
  34. package/tests/test-grill-json-fix.mjs +0 -243
  35. package/tests/test-loopcount-timeout-fix.mjs +0 -336
  36. package/tests/test-output-directory-structure.mjs +0 -177
  37. package/tests/test-save-answer-file-workflow.mjs +0 -187
  38. package/tests/test-workflow-config.mjs +0 -244
  39. package/tests/test-workflow-engine-bugs.mjs +0 -908
  40. package/tests/test-workflow-engine.mjs +0 -518
@@ -1,177 +0,0 @@
1
- /**
2
- * test-output-directory-structure.mjs — 验证输出目录结构调整正确
3
- *
4
- * Bug: 原先 .pi-dev-output/pi-grill/ 直接存放 answer-*.md 和 questions-*.json,
5
- * .pi-dev-output/pi-review/ 直接存放 *.html 和 *.md,没有分类子目录。
6
- *
7
- * 预期新结构:
8
- * .pi-dev-output/pi-grill/questions/ → questions-<id>-<YYYYMMDD-HHmm>.json
9
- * .pi-dev-output/pi-grill/answers/ → answer-<id>-<YYYYMMDD-HHmm>.md
10
- * .pi-dev-output/pi-review/html/ → *.html
11
- * .pi-dev-output/pi-review/md/ → *.md
12
- *
13
- * Run: node tests/test-output-directory-structure.mjs
14
- */
15
-
16
- import * as fs from "node:fs";
17
- import * as path from "node:path";
18
- import { fileURLToPath } from "node:url";
19
-
20
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
21
- const GRILL_ME_PATH = path.resolve(__dirname, "../extensions/grill-me-agent.ts");
22
- const SUB_AGENTS_PATH = path.resolve(__dirname, "../extensions/sub-agents.ts");
23
- const WORKFLOW_PATH = path.resolve(__dirname, "../extensions/workflow-engine.ts");
24
-
25
- // ── Helpers ──────────────────────────────────────────────────
26
-
27
- let pass = 0;
28
- let fail = 0;
29
-
30
- function assert(condition, msg) {
31
- if (condition) {
32
- pass++;
33
- console.log(` ✅ ${msg}`);
34
- } else {
35
- fail++;
36
- console.error(` ❌ ${msg}`);
37
- }
38
- }
39
-
40
- function assertIncludes(source, substr, msg) {
41
- assert(source.includes(substr), msg);
42
- }
43
-
44
- function assertNotIncludes(source, substr, msg) {
45
- assert(!source.includes(substr), msg);
46
- }
47
-
48
- // ═══════════════════════════════════════════════════════════════
49
- // 1. grill-me-agent.ts — 常量与路径
50
- // ═══════════════════════════════════════════════════════════════
51
-
52
- console.log("📋 grill-me-agent.ts — 目录常量与文件路径\n");
53
-
54
- const grillMe = fs.readFileSync(GRILL_ME_PATH, "utf-8");
55
-
56
- // 1a. 新常量定义
57
- assertIncludes(grillMe, 'GRILL_ANSWERS_DIRNAME = "answers"', "定义 GRILL_ANSWERS_DIRNAME = answers");
58
- assertIncludes(grillMe, 'GRILL_QUESTIONS_DIRNAME = "questions"', "定义 GRILL_QUESTIONS_DIRNAME = questions");
59
-
60
- // 1b. grillOutputPath 写入 questions 子目录 + 新文件名格式
61
- assertIncludes(grillMe, 'path.join(GRILL_DIRNAME, GRILL_QUESTIONS_DIRNAME)', "grillOutputPath 使用 questions 子目录");
62
- assertIncludes(grillMe, "questions-${ts}-${formatTimestamp()}.json", "grillOutputPath 文件名含 formatTimestamp");
63
-
64
- // 1c. saveAnswerFile 写入 answers 子目录 + 新文件名格式
65
- assertIncludes(grillMe, 'path.join(GRILL_DIRNAME, GRILL_ANSWERS_DIRNAME)', "saveAnswerFile 使用 answers 子目录");
66
- assertIncludes(grillMe, "answer-${ts}-${formatTimestamp()}.md", "saveAnswerFile 文件名含 formatTimestamp");
67
- assertIncludes(grillMe, "DEV_OUTPUT_DIR, GRILL_DIRNAME, GRILL_ANSWERS_DIRNAME, filename", "saveAnswerFile 返回路径含 answers 子目录");
68
-
69
- // 1d. recoverFromBackup 从 answers 子目录读取
70
- assertIncludes(grillMe, "GRILL_DIRNAME, GRILL_ANSWERS_DIRNAME", "recoverFromBackup 从 answers 子目录读取");
71
-
72
- // 1e. formatTimestamp 辅助函数
73
- assertIncludes(grillMe, "function formatTimestamp", "定义 formatTimestamp 辅助函数");
74
-
75
- // 1f. 旧文件名格式不应再出现
76
- // Note: still allow "questions-" prefix in path.join for grillOutputPath
77
- const oldSavePattern = "answer-${ts}.md";
78
- assertNotIncludes(grillMe, oldSavePattern, "saveAnswerFile 不使用旧格式 answer-${ts}.md");
79
-
80
- const oldQuestionsPattern = "questions-${ts}.json";
81
- assertNotIncludes(grillMe, oldQuestionsPattern, "grillOutputPath 不使用旧格式 questions-${ts}.json");
82
-
83
- // ═══════════════════════════════════════════════════════════════
84
- // 2. sub-agents.ts — findNewestReviewHtml 新增优先路径
85
- // ═══════════════════════════════════════════════════════════════
86
-
87
- console.log("\n📋 sub-agents.ts — findNewestReviewHtml 搜索路径\n");
88
-
89
- const subAgents = fs.readFileSync(SUB_AGENTS_PATH, "utf-8");
90
- assertIncludes(subAgents, '".pi-dev-output", "pi-review", "html"', "findNewestReviewHtml 优先搜索 pi-review/html/");
91
-
92
- // ═══════════════════════════════════════════════════════════════
93
- // 3. workflow-engine.ts — reviewer 报告写入路径
94
- // ═══════════════════════════════════════════════════════════════
95
-
96
- console.log("\n📋 workflow-engine.ts — buildReviewTask 输出路径\n");
97
-
98
- const workflowEngine = fs.readFileSync(WORKFLOW_PATH, "utf-8");
99
- assertIncludes(workflowEngine, ".pi-dev-output/pi-review/md/", "buildReviewTask 告诉 reviewer 写入 pi-review/md/");
100
-
101
- // ═══════════════════════════════════════════════════════════════
102
- // 4. agent 定义文件一致性
103
- // ═══════════════════════════════════════════════════════════════
104
-
105
- console.log("\n📋 Agent 定义文件路径一致性\n");
106
-
107
- const reviewAgent = fs.readFileSync(
108
- path.resolve(__dirname, "../agents/review-agent.md"), "utf-8");
109
- assertIncludes(reviewAgent, ".pi-dev-output/pi-review/html/",
110
- "review-agent 写入 .pi-dev-output/pi-review/html/");
111
- assertNotIncludes(reviewAgent, ".pi-dev-output/pi-review/ 目录",
112
- "review-agent 不再引用旧的 .pi-dev-output/pi-review/ (无子目录)");
113
-
114
- const workflowReviewer = fs.readFileSync(
115
- path.resolve(__dirname, "../agents/workflow/reviewer-agent.md"), "utf-8");
116
- assertIncludes(workflowReviewer, ".pi-dev-output/pi-review/md/",
117
- "workflow/reviewer-agent 写入 .pi-dev-output/pi-review/md/");
118
-
119
- // ═══════════════════════════════════════════════════════════════
120
- // 5. review-html SKILL 路径一致性
121
- // ═══════════════════════════════════════════════════════════════
122
-
123
- console.log("\n📋 skills/review-html 路径一致性\n");
124
-
125
- const reviewSkill = fs.readFileSync(
126
- path.resolve(__dirname, "../skills/review-html/SKILL.md"), "utf-8");
127
- assertIncludes(reviewSkill, ".pi-dev-output/pi-review/html/",
128
- "review-html skill 写入 .pi-dev-output/pi-review/html/");
129
- assertNotIncludes(reviewSkill, ".pi-dev-output/pi-review/ 目录",
130
- "review-html skill 不再引用旧的 .pi-dev-output/pi-review/ (无子目录)");
131
-
132
- // ═══════════════════════════════════════════════════════════════
133
- // 6. 文件名格式验证:saveAnswerFile 和 grillOutputPath 生成的名称含时间戳
134
- // ═══════════════════════════════════════════════════════════════
135
-
136
- console.log("\n📋 文件名格式验证\n");
137
-
138
- // Simulate the formatTimestamp function
139
- function formatTimestamp() {
140
- const now = new Date();
141
- const Y = now.getFullYear().toString();
142
- const M = (now.getMonth() + 1).toString().padStart(2, "0");
143
- const D = now.getDate().toString().padStart(2, "0");
144
- const h = now.getHours().toString().padStart(2, "0");
145
- const m = now.getMinutes().toString().padStart(2, "0");
146
- return `${Y}${M}${D}-${h}${m}`;
147
- }
148
-
149
- // Verify the format matches YYYYMMDD-HHmm
150
- const ts = formatTimestamp();
151
- const formatRegex = /^\d{8}-\d{4}$/;
152
- assert(formatRegex.test(ts), `formatTimestamp 格式正确 (${ts} 匹配 YYYYMMDD-HHmm)`);
153
-
154
- // Simulate filename generation
155
- const id = Date.now().toString(36);
156
- const answerFilename = `answer-${id}-${ts}.md`;
157
- const questionsFilename = `questions-${id}-${ts}.json`;
158
-
159
- assert(answerFilename.startsWith("answer-"), "answer 文件名以 answer- 开头");
160
- assert(answerFilename.endsWith(".md"), "answer 文件名以 .md 结尾");
161
- assert(questionsFilename.startsWith("questions-"), "questions 文件名以 questions- 开头");
162
- assert(questionsFilename.endsWith(".json"), "questions 文件名以 .json 结尾");
163
-
164
- // ═══════════════════════════════════════════════════════════════
165
- // Summary
166
- // ═══════════════════════════════════════════════════════════════
167
-
168
- console.log(`\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
169
- console.log(`结果: ${pass} 通过, ${fail} 失败, 共 ${pass + fail} 个测试`);
170
- console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
171
-
172
- if (fail > 0) {
173
- console.error("\n⚠️ 部分测试未通过");
174
- process.exit(1);
175
- } else {
176
- console.log("\n✅ 所有测试通过 — 输出目录结构调整正确");
177
- }
@@ -1,187 +0,0 @@
1
- /**
2
- * test-save-answer-file-workflow.mjs — 验证 saveAnswerFile 在 Workflow 路径中未被跳过
3
- *
4
- * 重构后所有 workflow 入口统一经过 promptWorkflowDecision() 函数,
5
- * 该函数在调用 runWorkflow 前一定会调用 saveAnswerFile。
6
- *
7
- * 本测试验证:
8
- * 1. promptWorkflowDecision 函数体:两个路径(默认/自定义)都在 runWorkflow 前调用了 saveAnswerFile
9
- * 2. 所有 caller(runWizardWithGrill / runWizard / dev-feat handler)都调用 promptWorkflowDecision
10
- * 3. 所有非 workflow 路径的 saveAnswerFile 未被移除
11
- *
12
- * Run: node tests/test-save-answer-file-workflow.mjs
13
- */
14
-
15
- import * as fs from "node:fs";
16
- import * as path from "node:path";
17
- import { fileURLToPath } from "node:url";
18
-
19
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
20
- const SOURCE_PATH = path.resolve(__dirname, "../extensions/dev-prompts.ts");
21
-
22
- // ── Helpers ──────────────────────────────────────────────────
23
-
24
- let pass = 0;
25
- let fail = 0;
26
-
27
- function assert(condition, msg) {
28
- if (condition) {
29
- pass++;
30
- console.log(` ✅ ${msg}`);
31
- } else {
32
- fail++;
33
- console.error(` ❌ ${msg}`);
34
- }
35
- }
36
-
37
- function assertEq(actual, expected, msg) {
38
- if (actual === expected) {
39
- pass++;
40
- console.log(` ✅ ${msg}`);
41
- } else {
42
- fail++;
43
- console.error(` ❌ ${msg} — 期望 ${JSON.stringify(expected)}, 得到 ${JSON.stringify(actual)}`);
44
- }
45
- }
46
-
47
- // ═══════════════════════════════════════════════════════════════
48
- // Read source
49
- // ═══════════════════════════════════════════════════════════════
50
-
51
- let source;
52
- try {
53
- source = fs.readFileSync(SOURCE_PATH, "utf-8");
54
- } catch (e) {
55
- console.error(`Failed to read source file: ${e.message}`);
56
- process.exit(1);
57
- }
58
-
59
- console.log(`📄 源文件: ${SOURCE_PATH}`);
60
- console.log(`📏 文件大小: ${source.length} 字节\n`);
61
-
62
- // ═══════════════════════════════════════════════════════════════
63
- // Test 1: promptWorkflowDecision 在 runWorkflow 前调用 saveAnswerFile
64
- // ═══════════════════════════════════════════════════════════════
65
-
66
- console.log("📋 Test 1: promptWorkflowDecision 的两个路径都在 runWorkflow 前调用 saveAnswerFile\n");
67
-
68
- // Find the promptWorkflowDecision function body
69
- const pwdMatch = source.match(/async function promptWorkflowDecision\([\s\S]*?^\}/m);
70
- assert(pwdMatch !== null, "应找到 promptWorkflowDecision 函数");
71
-
72
- const pwdFunc = pwdMatch[0];
73
- assert(
74
- pwdFunc.includes("saveAnswerFile(ctx.cwd, finalPrompt)"),
75
- "promptWorkflowDecision 应包含 saveAnswerFile(finalPrompt) 调用",
76
- );
77
-
78
- // Verify the default path (choice "1"): saveAnswerFile before runWorkflow
79
- const defaultPath = pwdFunc.match(/if \(choice\.startsWith\("1"\)\) \{[\s\S]*?return true;\s*\}/);
80
- assert(defaultPath !== null, "默认模式路径应存在");
81
- if (defaultPath) {
82
- const saveIdx = defaultPath[0].indexOf("saveAnswerFile");
83
- const runIdx = defaultPath[0].indexOf("runWorkflow");
84
- assert(saveIdx >= 0 && saveIdx < runIdx, "默认路径应在 runWorkflow 前调用 saveAnswerFile");
85
- }
86
-
87
- // Verify the custom path: saveAnswerFile before runWorkflow
88
- const customPath = pwdFunc.match(/if \(customSteps\.length === 0\) \{[\s\S]*?return true;\s*\}/);
89
- assert(customPath !== null, "自定义模式路径应存在");
90
- if (customPath) {
91
- const saveIdx = customPath[0].indexOf("saveAnswerFile");
92
- const runIdx = customPath[0].indexOf("runWorkflow");
93
- assert(saveIdx >= 0 && runIdx >= 0, "自定义路径应包含 saveAnswerFile 和 runWorkflow");
94
- assert(saveIdx < runIdx, "自定义路径应在 runWorkflow 前调用 saveAnswerFile");
95
- }
96
-
97
- // ═══════════════════════════════════════════════════════════════
98
- // Test 2: 所有 caller 正确调用 promptWorkflowDecision
99
- // ═══════════════════════════════════════════════════════════════
100
-
101
- console.log("\n📋 Test 2: 所有 caller 正确调用 promptWorkflowDecision\n");
102
-
103
- // Count occurrences of promptWorkflowDecision calls in handlers (not definition)
104
- // We need at least: runWizardWithGrill + runWizard + dev-feat = 3 call sites
105
- const workflowDecisionCalls = source.match(/await promptWorkflowDecision\(/g);
106
- assert(workflowDecisionCalls !== null && workflowDecisionCalls.length >= 3,
107
- `应找到至少 3 个 promptWorkflowDecision 调用,实际 ${workflowDecisionCalls?.length ?? 0}`);
108
- console.log(` 共 ${workflowDecisionCalls.length} 个 promptWorkflowDecision 调用`);
109
-
110
- // ═══════════════════════════════════════════════════════════════
111
- // Test 3: runWizard 的非 workflow 路径调用 saveAnswerFile
112
- // ═══════════════════════════════════════════════════════════════
113
-
114
- console.log("\n📋 Test 3: runWizard 非 workflow 路径调用 saveAnswerFile\n");
115
-
116
- const runWizardMatch = source.match(/async function runWizard\([\s\S]*?\n\}/);
117
- assert(runWizardMatch !== null, "应找到 runWizard 函数");
118
-
119
- const runWizardFunc = runWizardMatch[0];
120
- assert(
121
- runWizardFunc.includes("saveAnswerFile(ctx.cwd, prompt);"),
122
- "runWizard 非 workflow 路径应调用 saveAnswerFile(ctx.cwd, prompt)",
123
- );
124
-
125
- // ═══════════════════════════════════════════════════════════════
126
- // Test 4: runWizardWithGrill 的非 workflow 路径保留 saveAnswerFile
127
- // ═══════════════════════════════════════════════════════════════
128
-
129
- console.log("\n📋 Test 4: runWizardWithGrill 非 workflow 路径保留 saveAnswerFile\n");
130
-
131
- const runWizardWithGrillMatch = source.match(/async function runWizardWithGrill\([\s\S]*?\n\}/);
132
- assert(runWizardWithGrillMatch !== null, "应找到 runWizardWithGrill 函数");
133
-
134
- const grillFunc = runWizardWithGrillMatch[0];
135
- assert(
136
- grillFunc.includes("const answerPath = saveAnswerFile(ctx.cwd, finalPrompt);"),
137
- "runWizardWithGrill 的非 workflow 路径应包含 answerPath = saveAnswerFile(...)",
138
- );
139
-
140
- // ═══════════════════════════════════════════════════════════════
141
- // Test 5: dev-feat handler 调用 promptWorkflowDecision
142
- // ═══════════════════════════════════════════════════════════════
143
-
144
- console.log("\n📋 Test 5: dev-feat handler 调用 promptWorkflowDecision\n");
145
-
146
- // Find the dev-feat registerCommand block
147
- const featMatch = source.match(/pi\.registerCommand\("dev-feat"[\s\S]*?\n\t\}\);/);
148
- assert(featMatch !== null, "应找到 /dev-feat handler");
149
-
150
- const featHandler = featMatch[0];
151
- assert(
152
- featHandler.includes("promptWorkflowDecision(ctx, pi, finalPrompt, FEAT_WORKFLOW_STEPS)"),
153
- "dev-feat handler 应调用 promptWorkflowDecision 并传递 FEAT_WORKFLOW_STEPS",
154
- );
155
-
156
- // ═══════════════════════════════════════════════════════════════
157
- // Test 6: 所有 saveAnswerFile 调用之前都有 finalPrompt/prompt 已赋值(空安全)
158
- // ═══════════════════════════════════════════════════════════════
159
-
160
- console.log("\n📋 Test 6: saveAnswerFile 调用时的参数非空\n");
161
-
162
- const saveCalls = source.match(/saveAnswerFile\(ctx\.cwd, \w+\)/g);
163
- assert(saveCalls !== null && saveCalls.length >= 5,
164
- `应找到至少 5 个 saveAnswerFile 调用,实际 ${saveCalls?.length ?? 0}`);
165
-
166
- console.log(` 共 ${saveCalls.length} 个 saveAnswerFile 调用`);
167
- for (const call of saveCalls) {
168
- assert(
169
- call.includes("finalPrompt") || call.includes("prompt") || call.includes("recovered"),
170
- `saveAnswerFile 参数应为 finalPrompt/prompt/recovered,实际: ${call}`,
171
- );
172
- }
173
-
174
- // ═══════════════════════════════════════════════════════════════
175
- // Summary
176
- // ═══════════════════════════════════════════════════════════════
177
-
178
- console.log(`\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
179
- console.log(`结果: ${pass} 通过, ${fail} 失败, 共 ${pass + fail} 个测试`);
180
- console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
181
-
182
- if (fail > 0) {
183
- console.error("\n⚠️ 部分测试未通过");
184
- process.exit(1);
185
- } else {
186
- console.log("\n✅ 所有测试通过 — saveAnswerFile 在所有 workflow 路径和非 workflow 路径中均被正确调用");
187
- }
@@ -1,244 +0,0 @@
1
- /**
2
- * test-workflow-config.mjs — 验证所有 dev-* 命令正确激活 Workflow 选项
3
- *
4
- * Bug: /dev-fix 的 handler 调用 runWizardWithGrill 时未传入 workflowConfig,
5
- * 导致 FIX_WORKFLOW_STEPS(已定义却未使用)永远不会被触发。工作流确认对话框不出现,
6
- * 用户无法进入自动化工作流。
7
- *
8
- * 本测试通过静态分析源码,验证:
9
- * 1. 每个定义了 *_WORKFLOW_STEPS 常量的 dev-* 命令,其 handler 必须将
10
- * 该常量作为 workflowConfig 传给 runWizardWithGrill / runWizard,
11
- * 或在内联 handler 中直接调用 runWorkflow。
12
- * 2. 没有遗漏或「定义但未使用」的 WORKFLOW_STEPS。
13
- *
14
- * Run: node tests/test-workflow-config.mjs
15
- */
16
-
17
- import * as fs from "node:fs";
18
- import * as path from "node:path";
19
- import { fileURLToPath } from "node:url";
20
-
21
- const __dirname = path.dirname(fileURLToPath(import.meta.url));
22
- const SOURCE_PATH = path.resolve(__dirname, "../extensions/dev-prompts.ts");
23
-
24
- // ── Helpers ──────────────────────────────────────────────────
25
-
26
- let pass = 0;
27
- let fail = 0;
28
-
29
- function assert(condition, msg) {
30
- if (condition) {
31
- pass++;
32
- console.log(` ✅ ${msg}`);
33
- } else {
34
- fail++;
35
- console.error(` ❌ ${msg}`);
36
- }
37
- }
38
-
39
- function assertEq(actual, expected, msg) {
40
- if (actual === expected) {
41
- pass++;
42
- console.log(` ✅ ${msg}`);
43
- } else {
44
- fail++;
45
- console.error(` ❌ ${msg} — 期望 ${JSON.stringify(expected)}, 得到 ${JSON.stringify(actual)}`);
46
- }
47
- }
48
-
49
- // ═══════════════════════════════════════════════════════════════
50
- // Read source
51
- // ═══════════════════════════════════════════════════════════════
52
-
53
- let source;
54
- try {
55
- source = fs.readFileSync(SOURCE_PATH, "utf-8");
56
- } catch (e) {
57
- console.error(`Failed to read source file: ${e.message}`);
58
- process.exit(1);
59
- }
60
-
61
- console.log(`📄 源文件: ${SOURCE_PATH}`);
62
- console.log(`📏 文件大小: ${source.length} 字节\n`);
63
-
64
- // ═══════════════════════════════════════════════════════════════
65
- // Test 1: Every *_WORKFLOW_STEPS constant is used by its handler
66
- // ═══════════════════════════════════════════════════════════════
67
-
68
- console.log("📋 Test 1: 所有 *_WORKFLOW_STEPS 常量被其 handler 使用\n");
69
-
70
- // Each entry: [constantName, expectedCommandName, expectedHandlerType]
71
- // handlerType: "runWizardWithGrill" | "runWizard" | "inline"
72
- const workflowSteps = [
73
- { const: "FEAT_WORKFLOW_STEPS", command: "dev-feat", type: "inline" }, // inline handler
74
- { const: "FIX_WORKFLOW_STEPS", command: "dev-fix", type: "runWizardWithGrill" }, // was BROKEN — missing arg
75
- { const: "DOC_WORKFLOW_STEPS", command: "dev-doc", type: "runWizardWithGrill" }, // has it
76
- { const: "REFACTOR_WORKFLOW_STEPS", command: "dev-refactor", type: "runWizardWithGrill" }, // has it
77
- { const: "TEST_WORKFLOW_STEPS", command: "dev-test", type: "runWizardWithGrill" }, // has it
78
- { const: "PERF_WORKFLOW_STEPS", command: "dev-perf", type: "runWizardWithGrill" }, // has it
79
- { const: "STYLE_WORKFLOW_STEPS", command: "dev-style", type: "runWizard" }, // has it
80
- { const: "SECURITY_WORKFLOW_STEPS", command: "dev-security", type: "runWizard" }, // has it
81
- ];
82
-
83
- for (const ws of workflowSteps) {
84
- // 1. Verify constant is defined in source
85
- const constDefined = source.includes(`const ${ws.const}: WorkflowStepDef[] = [`);
86
- assert(constDefined, `${ws.const} 应在源文件中定义`);
87
-
88
- // 2. Usage check: the constant name must appear in the source more than just its definition.
89
- // Count occurrences of the bare constant name (e.g. "FIX_WORKFLOW_STEPS").
90
- // Definition has 1 occurrence (const XXX: ...), usage adds at least 1 more.
91
- const bareName = ws.const;
92
- const allOccurrences = source.match(new RegExp(bareName, 'g'));
93
- const occurrenceCount = allOccurrences ? allOccurrences.length : 0;
94
- // Minimum: 1 for definition + 1 for usage = 2
95
- assert(occurrenceCount >= 2,
96
- `${bareName} 应至少出现 2 次 (定义 + 使用), 实际 ${occurrenceCount} 次`);
97
- }
98
-
99
- // ═══════════════════════════════════════════════════════════════
100
- // Test 2: runWizardWithGrill calls pass both grillOptions and workflowConfig
101
- // ═════════════════──────────────────────────────────────────────
102
- // All runWizardWithGrill calls that have a workflowConfig should
103
- // have it as the last argument before the closing parenthesis.
104
-
105
- console.log("\n📋 Test 2: runWizardWithGrill 调用完整性\n");
106
-
107
- // Find all runWizardWithGrill(...) calls
108
- const grillCalls = source.match(/await runWizardWithGrill\([\s\S]*?\);/g);
109
- assert(grillCalls !== null && grillCalls.length >= 4,
110
- `应找到至少 4 个 runWizardWithGrill 调用,实际 ${grillCalls?.length ?? 0}`);
111
-
112
- console.log(` 共 ${grillCalls.length} 个 runWizardWithGrill 调用`);
113
-
114
- for (let i = 0; i < grillCalls.length; i++) {
115
- const call = grillCalls[i];
116
-
117
- // Extract the command type from the call
118
- const typeMatch = call.match(/await runWizardWithGrill\(\s*ctx,\s*pi,\s*"([^"]+)"/);
119
- const type = typeMatch ? typeMatch[1] : `#${i}`;
120
-
121
- // Count arguments: split by top-level commas (ignoring those inside braces/brackets)
122
- let depth = 0;
123
- let argCount = 0;
124
- for (const ch of call) {
125
- if (ch === '(' || ch === '{' || ch === '[') depth++;
126
- else if (ch === ')' || ch === '}' || ch === ']') depth--;
127
- else if (ch === ',' && depth === 1) argCount++;
128
- }
129
- // Number of arguments = commas + 1 (inside top-level parens)
130
- // But the last comma before `)` doesn't count, so total args = number of top-level commas + 1
131
- // Actually, let me recalculate: top-level commas separate args.
132
- // For `runWizardWithGrill(a, b, c, ...)` — the paren depth at commas inside the function call is 1.
133
- // The last `)` decrements depth and we shouldn't count commas after that.
134
- // Let me just count the number of top-level commas before the last closing paren.
135
-
136
- let topLevelCommas = 0;
137
- depth = 0;
138
- for (const ch of call) {
139
- if (ch === '(') depth++;
140
- else if (ch === ')') { depth--; if (depth === 0) break; }
141
- else if (ch === ',' && depth === 1) topLevelCommas++;
142
- }
143
- const totalArgs = topLevelCommas + 1;
144
-
145
- // runWizardWithGrill has 8 parameters (ctx, pi, type, label, questions, assembler, grillOptions?, workflowConfig?)
146
- // If we see 7 args → missing workflowConfig; 8 args → has workflowConfig
147
- const hasWorkflowConfig = totalArgs >= 8;
148
-
149
- assert(hasWorkflowConfig,
150
- `runWizardWithGrill("${type}") 应有 8 个参数 (当前 ${totalArgs}) — 缺少 workflowConfig 参数`);
151
-
152
- if (hasWorkflowConfig) {
153
- console.log(` ✅ "${type}": ${totalArgs} 参数, workflowConfig 已传递`);
154
- } else {
155
- console.error(` ❌ "${type}": ${totalArgs} 参数, 缺少 workflowConfig`);
156
- }
157
- }
158
-
159
- // ═══════════════════════════════════════════════════════════════
160
- // Test 3: Inline handlers (dev-feat) call promptWorkflowDecision
161
- // ═══════════════════════════════════════════════════════════════
162
-
163
- console.log("\n📋 Test 3: 内联 handler 调用 promptWorkflowDecision\n");
164
-
165
- // dev-feat: inline handler that calls runWorkflow directly
166
- const featHandler = source.match(/pi\.registerCommand\("dev-feat"[\s\S]*?FEAT_WORKFLOW_STEPS \}\);/);
167
- assert(featHandler !== null, "应找到 /dev-feat handler");
168
-
169
- const featCallsPromptDecision = featHandler && featHandler[0].includes("promptWorkflowDecision(ctx, pi, finalPrompt, FEAT_WORKFLOW_STEPS)");
170
- assert(featCallsPromptDecision, "/dev-feat 内联 handler 应调用 promptWorkflowDecision 并传递 FEAT_WORKFLOW_STEPS");
171
-
172
- // dev-workflow-continue: also uses FEAT_WORKFLOW_STEPS
173
- const continueHandler = source.match(/pi\.registerCommand\("dev-workflow-continue"[\s\S]*?FEAT_WORKFLOW_STEPS \}\);/);
174
- assert(continueHandler !== null, "应找到 /dev-workflow-continue handler");
175
- if (continueHandler) {
176
- assert(
177
- continueHandler[0].includes("{ steps: FEAT_WORKFLOW_STEPS }"),
178
- "/dev-workflow-continue handler 应引用 FEAT_WORKFLOW_STEPS",
179
- );
180
- }
181
-
182
- // ═══════════════════════════════════════════════════════════════
183
- // Test 4: runWizard calls that have workflow steps
184
- // ═══════════════════════════════════════════════════════════════
185
-
186
- console.log("\n📋 Test 4: runWizard 调用完整性\n");
187
-
188
- // Find all runWizard(...) calls
189
- const wizardCalls = source.match(/await runWizard\([\s\S]*?\);/g);
190
- assert(wizardCalls !== null && wizardCalls.length >= 5,
191
- `应找到至少 5 个 runWizard 调用,实际 ${wizardCalls?.length ?? 0}`);
192
-
193
- console.log(` 共 ${wizardCalls.length} 个 runWizard 调用`);
194
-
195
- // runWizard has 7 parameters (ctx, pi, type, label, questions, assembler, workflowConfig?)
196
- // If the command has _WORKFLOW_STEPS defined nearby, it should pass it.
197
- const nonWorkflowCommands = ["chore", "explain", "compare"]; // intentionally no workflow
198
-
199
- for (let i = 0; i < wizardCalls.length; i++) {
200
- const call = wizardCalls[i];
201
- const typeMatch = call.match(/await runWizard\(\s*ctx,\s*pi,\s*"([^"]+)"/);
202
- const type = typeMatch ? typeMatch[1] : `#${i}`;
203
-
204
- // Count top-level arguments
205
- let topLevelCommas = 0;
206
- let depth = 0;
207
- for (const ch of call) {
208
- if (ch === '(') depth++;
209
- else if (ch === ')') { depth--; if (depth === 0) break; }
210
- else if (ch === ',' && depth === 1) topLevelCommas++;
211
- }
212
- const totalArgs = topLevelCommas + 1;
213
-
214
- const expectsWorkflow = !nonWorkflowCommands.includes(type);
215
-
216
- if (expectsWorkflow) {
217
- const hasWorkflowConfig = totalArgs >= 7;
218
- assert(hasWorkflowConfig,
219
- `runWizard("${type}") 应有 7 个参数 (当前 ${totalArgs}) — 缺少 workflowConfig`);
220
- if (hasWorkflowConfig) {
221
- console.log(` ✅ "${type}": ${totalArgs} 参数, workflowConfig 已传递`);
222
- }
223
- } else {
224
- // These commands intentionally don't pass workflowConfig
225
- const noWorkflowConfig = totalArgs <= 6;
226
- assert(noWorkflowConfig,
227
- `runWizard("${type}") 应有 6 个参数 (当前 ${totalArgs}) — 此类命令不含 workflow`);
228
- }
229
- }
230
-
231
- // ═══════════════════════════════════════════════════════════════
232
- // Summary
233
- // ═══════════════════════════════════════════════════════════════
234
-
235
- console.log(`\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
236
- console.log(`结果: ${pass} 通过, ${fail} 失败, 共 ${pass + fail} 个测试`);
237
- console.log(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`);
238
-
239
- if (fail > 0) {
240
- console.error("\n⚠️ 部分测试未通过");
241
- process.exit(1);
242
- } else {
243
- console.log("\n✅ 所有测试通过 — 所有 dev-* 命令的 Workflow 配置均完整可用");
244
- }