@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.
- package/.github/workflows/release.yml +56 -0
- package/.pre-commit-config.yaml +75 -0
- package/.version/RELEASE-v0.7.0.md +85 -0
- package/.version/RELEASE-v0.8.0.md +93 -0
- package/README.md +178 -294
- package/extensions/dev-prompts.ts +121 -1033
- package/extensions/git-commands.ts +62 -171
- package/extensions/grill-me-agent.ts +160 -164
- package/extensions/pre-check.ts +190 -0
- package/extensions/review-detect.ts +123 -0
- package/extensions/session-utils.ts +168 -0
- package/extensions/ui-helpers.ts +22 -780
- package/package.json +2 -2
- package/prompts/APPEND_SYSTEM.md +73 -59
- package/skills/review-html/SKILL.md +1 -1
- package/tests/test-no-subagents.mjs +264 -0
- package/.doc/AGENT-FRONTMATTER-REFERENCE.md +0 -198
- package/agents/git-agent.md +0 -44
- package/agents/grill/dev-doc-grill-agent.md +0 -42
- package/agents/grill/dev-fix-grill-agent.md +0 -44
- package/agents/grill/dev-grill-agent.md +0 -40
- package/agents/grill/dev-perf-grill-agent.md +0 -45
- package/agents/grill/dev-prd-agent.md +0 -60
- package/agents/grill/dev-refactor-grill-agent.md +0 -46
- package/agents/grill/dev-test-grill-agent.md +0 -45
- package/agents/review-agent.md +0 -53
- package/agents/workflow/docWriter-agent.md +0 -53
- package/agents/workflow/planner-agent.md +0 -131
- package/agents/workflow/reviewer-agent.md +0 -128
- package/agents/workflow/trimmer-agent.md +0 -78
- package/agents/workflow/worker-agent.md +0 -70
- package/extensions/sub-agents.ts +0 -954
- package/extensions/workflow-engine.ts +0 -2005
- package/tests/test-grill-json-fix.mjs +0 -243
- package/tests/test-loopcount-timeout-fix.mjs +0 -336
- package/tests/test-output-directory-structure.mjs +0 -177
- package/tests/test-save-answer-file-workflow.mjs +0 -187
- package/tests/test-workflow-config.mjs +0 -244
- package/tests/test-workflow-engine-bugs.mjs +0 -908
- 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
|
-
}
|