@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.
@@ -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
+ }