@ghyper9023/pi-dev-workflow 0.3.3 → 0.4.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/README.md +171 -29
- package/agents/review-agent.md +5 -5
- package/agents/workflow/docWriter-agent.md +29 -0
- package/agents/workflow/planner-agent.md +80 -0
- package/agents/workflow/reviewer-agent.md +44 -0
- package/agents/workflow/trimmer-agent.md +34 -0
- package/agents/workflow/worker-agent.md +29 -0
- package/extensions/dev-prompts.ts +375 -75
- package/extensions/git-commands.ts +3 -13
- package/extensions/grill-me-agent.ts +138 -66
- package/extensions/sub-agents.ts +32 -11
- package/extensions/ui-helpers.ts +1030 -0
- package/extensions/workflow-engine.ts +1715 -0
- package/package.json +1 -1
- package/skills/review-html/SKILL.md +2 -2
- package/skills/to-prd/SKILL.md +1 -1
- package/tests/test-grill-json-fix.mjs +243 -0
- package/tests/test-output-directory-structure.mjs +177 -0
- package/tests/test-save-answer-file-workflow.mjs +187 -0
- package/tests/test-workflow-config.mjs +244 -0
- package/tests/test-workflow-engine.mjs +518 -0
|
@@ -0,0 +1,244 @@
|
|
|
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
|
+
}
|