@skyramp/mcp 0.1.8 → 0.2.0-rc.2
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/build/index.js +4 -2
- package/build/playwright/registerPlaywrightTools.js +12 -0
- package/build/playwright/traceRecordingPrompt.js +15 -0
- package/build/prompts/code-reuse.js +106 -7
- package/build/prompts/pom-aware-code-reuse.js +106 -7
- package/build/prompts/startTraceCollectionPrompts.js +37 -15
- package/build/prompts/test-maintenance/drift-analysis-prompt.js +26 -31
- package/build/prompts/test-maintenance/drift-analysis-prompt.test.js +40 -1
- package/build/prompts/test-maintenance/driftAnalysisSections.js +90 -86
- package/build/prompts/test-recommendation/analysisOutputPrompt.js +286 -163
- package/build/prompts/test-recommendation/analysisOutputPrompt.test.js +154 -45
- package/build/prompts/test-recommendation/diffExecutionPlan.js +246 -117
- package/build/prompts/test-recommendation/promptPlan.js +290 -0
- package/build/prompts/test-recommendation/promptPlan.test.js +336 -0
- package/build/prompts/test-recommendation/recommendationSections.js +4 -3
- package/build/prompts/test-recommendation/recommendationShared.js +23 -1
- package/build/prompts/test-recommendation/scopeAssessment.js +65 -14
- package/build/prompts/test-recommendation/scopeAssessment.test.js +93 -2
- package/build/prompts/test-recommendation/test-recommendation-prompt.js +36 -12
- package/build/prompts/test-recommendation/test-recommendation-prompt.test.js +316 -1
- package/build/prompts/testbot/testbot-prompts.js +73 -13
- package/build/prompts/testbot/testbot-prompts.test.js +114 -1
- package/build/resources/testbotResource.js +1 -1
- package/build/services/ScenarioGenerationService.integration.test.js +158 -0
- package/build/services/ScenarioGenerationService.js +47 -4
- package/build/services/ScenarioGenerationService.test.js +158 -22
- package/build/services/TestExecutionService.js +73 -15
- package/build/services/TestExecutionService.test.js +105 -0
- package/build/services/TestGenerationService.js +11 -1
- package/build/tools/executeSkyrampTestTool.js +1 -10
- package/build/tools/generate-tests/generateBatchScenarioRestTool.js +16 -4
- package/build/tools/generate-tests/generateIntegrationRestTool.js +2 -0
- package/build/tools/generate-tests/generateUIRestTool.js +2 -0
- package/build/tools/test-management/actionsTool.js +152 -63
- package/build/tools/test-management/analyzeChangesTool.js +178 -64
- package/build/tools/test-management/analyzeChangesTool.test.js +103 -16
- package/build/tools/test-management/analyzeTestHealthTool.js +30 -81
- package/build/tools/test-management/index.js +1 -0
- package/build/tools/test-management/uiAnalyzeChangesTool.js +149 -0
- package/build/tools/test-management/uiAnalyzeChangesTool.test.js +100 -0
- package/build/tools/trace/resolveSaveStoragePath.js +16 -0
- package/build/tools/trace/resolveSaveStoragePath.test.js +17 -0
- package/build/tools/trace/resolveSessionPaths.js +39 -0
- package/build/tools/trace/resolveSessionPaths.test.js +103 -0
- package/build/tools/trace/sessionState.js +14 -0
- package/build/tools/trace/sessionState.test.js +17 -0
- package/build/tools/trace/startTraceCollectionTool.js +84 -14
- package/build/tools/trace/stopTraceCollectionTool.js +9 -2
- package/build/types/TestAnalysis.js +50 -0
- package/build/types/TestRecommendation.js +6 -58
- package/build/types/TestTypes.js +1 -1
- package/build/utils/AnalysisStateManager.js +22 -11
- package/build/utils/branchDiff.js +11 -2
- package/build/utils/docker.test.js +1 -1
- package/build/utils/gitStaging.js +52 -3
- package/build/utils/gitStaging.test.js +19 -1
- package/build/utils/repoScanner.js +18 -10
- package/build/utils/repoScanner.test.js +92 -0
- package/build/utils/routeParsers.js +180 -25
- package/build/utils/routeParsers.test.js +180 -1
- package/build/utils/scenarioDrafting.js +220 -17
- package/build/utils/scenarioDrafting.test.js +182 -9
- package/build/utils/sourceRouteExtractor.js +806 -0
- package/build/utils/sourceRouteExtractor.test.js +565 -0
- package/build/utils/uiPageEnumerator.js +319 -0
- package/build/utils/uiPageEnumerator.test.js +422 -0
- package/build/utils/utils.js +27 -0
- package/build/utils/versions.js +1 -1
- package/build/utils/workspaceAuth.js +33 -4
- package/node_modules/playwright/ThirdPartyNotices.txt +6 -6
- package/node_modules/playwright/lib/dom-analyzer/analyze.js +111 -0
- package/node_modules/playwright/lib/dom-analyzer/blueprint.js +1210 -0
- package/node_modules/playwright/lib/dom-analyzer/blueprint.test.js +396 -0
- package/node_modules/playwright/lib/dom-analyzer/blueprintCache.js +57 -0
- package/node_modules/playwright/lib/dom-analyzer/blueprintCache.test.js +57 -0
- package/node_modules/playwright/lib/dom-analyzer/blueprintDiff.js +254 -0
- package/node_modules/playwright/lib/dom-analyzer/blueprintDiff.test.js +304 -0
- package/node_modules/playwright/lib/dom-analyzer/crawler.js +384 -0
- package/node_modules/playwright/lib/dom-analyzer/curatedWidgets.js +73 -0
- package/node_modules/playwright/lib/dom-analyzer/dynamicId.js +43 -0
- package/node_modules/playwright/lib/dom-analyzer/dynamicId.test.js +85 -0
- package/node_modules/playwright/lib/dom-analyzer/fingerprint.js +90 -0
- package/node_modules/playwright/lib/dom-analyzer/fingerprint.test.js +231 -0
- package/node_modules/playwright/lib/dom-analyzer/fingerprintAblation.fixtures.js +145 -0
- package/node_modules/playwright/lib/dom-analyzer/fingerprintAblation.test.js +41 -0
- package/node_modules/playwright/lib/dom-analyzer/graph.js +36 -0
- package/node_modules/playwright/lib/dom-analyzer/liveFingerprints.js +43 -0
- package/node_modules/playwright/lib/dom-analyzer/logicalNameResolver.js +72 -0
- package/node_modules/playwright/lib/dom-analyzer/logicalNameResolver.test.js +182 -0
- package/node_modules/playwright/lib/dom-analyzer/possibleAssertions.js +150 -0
- package/node_modules/playwright/lib/dom-analyzer/possibleAssertions.test.js +470 -0
- package/node_modules/playwright/lib/dom-analyzer/sectionGrouper.js +169 -0
- package/node_modules/playwright/lib/dom-analyzer/sectionGrouper.test.js +269 -0
- package/node_modules/playwright/lib/dom-analyzer/serialization.js +75 -0
- package/node_modules/playwright/lib/dom-analyzer/slug.js +30 -0
- package/node_modules/playwright/lib/dom-analyzer/slug.test.js +84 -0
- package/node_modules/playwright/lib/dom-analyzer/widgetContract.js +127 -0
- package/node_modules/playwright/lib/dom-analyzer/widgetContract.test.js +212 -0
- package/node_modules/playwright/lib/mcp/browser/browserContextFactory.js +3 -1
- package/node_modules/playwright/lib/mcp/browser/config.js +1 -1
- package/node_modules/playwright/lib/mcp/browser/context.js +17 -1
- package/node_modules/playwright/lib/mcp/browser/tab.js +38 -0
- package/node_modules/playwright/lib/mcp/browser/tools/domAnalyzer.js +261 -0
- package/node_modules/playwright/lib/mcp/browser/tools/keyboard.js +3 -3
- package/node_modules/playwright/lib/mcp/browser/tools/pageBlueprint.js +146 -0
- package/node_modules/playwright/lib/mcp/browser/tools/pageBlueprint.test.js +140 -0
- package/node_modules/playwright/lib/mcp/browser/tools/sitemap.js +226 -0
- package/node_modules/playwright/lib/mcp/browser/tools/snapshot.js +2 -2
- package/node_modules/playwright/lib/mcp/browser/tools/widgetContract.js +168 -0
- package/node_modules/playwright/lib/mcp/browser/tools.js +6 -0
- package/node_modules/playwright/lib/mcp/skyramp/traceRecordingBackend.js +52 -12
- package/node_modules/playwright/lib/mcp/test/skyRampExport.js +64 -13
- package/node_modules/playwright/package.json +1 -1
- package/node_modules/playwright/skyramp-playwright-1.58.2-skyramp.8.9.3.tgz +0 -0
- package/node_modules/playwright/skyramp-playwright-1.58.2-skyramp.8.9.4.tgz +0 -0
- package/node_modules/playwright/skyramp-playwright-1.58.2-skyramp.8.9.5.tgz +0 -0
- package/node_modules/playwright/skyramp-playwright-1.58.2-skyramp.8.9.6.tgz +0 -0
- package/package.json +3 -3
- package/build/services/TestHealthService.js +0 -694
- package/build/services/TestHealthService.test.js +0 -241
- package/build/types/TestDriftAnalysis.js +0 -1
- package/build/types/TestHealth.js +0 -4
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PromptPlan — structured prompt composition with phases, steps, and sub-steps.
|
|
3
|
+
*
|
|
4
|
+
* Design goals:
|
|
5
|
+
* - Main steps are always non-conditional; their labels (1, 2, 3, …) are
|
|
6
|
+
* static string constants usable in tests without calling any function.
|
|
7
|
+
* - Sub-steps are sequentially numbered within their parent (1.1, 1.2, 2.1, …).
|
|
8
|
+
* No manually chosen suffixes — insertion or removal automatically renumbers.
|
|
9
|
+
* - Conditional sub-steps: when absent, remaining siblings re-number so the
|
|
10
|
+
* LLM never sees a gap in the step sequence.
|
|
11
|
+
* - Phases group steps under an optional named heading with a configurable
|
|
12
|
+
* rendering style.
|
|
13
|
+
* - preview(params) returns a structured snapshot for developer inspection:
|
|
14
|
+
* which steps are active, what their labels are, and why conditional ones
|
|
15
|
+
* are absent.
|
|
16
|
+
*
|
|
17
|
+
* Usage:
|
|
18
|
+
* const _plan = new PromptPlan<MyParams>()
|
|
19
|
+
* .addPhase("main", "Task Instructions", { headerLevel: "##", stepFormat: "hash" })
|
|
20
|
+
* .step("READ", "Read the files", (p) => buildReadBody(p))
|
|
21
|
+
* .subStep("PATHS", "Resolve paths", (p) => buildPathsBody(p))
|
|
22
|
+
* .step("EXTRACT", "Extract items", (p) => buildExtractBody(p))
|
|
23
|
+
* .subStep("TRACE", "Trace callers", (p) => buildTraceBody(p),
|
|
24
|
+
* { when: (p) => p.hasUnmatched, whenDesc: "unmatched backend files present" })
|
|
25
|
+
* .step("CALL", "Call the tool", (p) => buildCallBody(p))
|
|
26
|
+
* .done();
|
|
27
|
+
*
|
|
28
|
+
* // Static constants for non-conditional steps (safe to export at module level)
|
|
29
|
+
* export const STEP_READ = _plan.labels.READ; // "1"
|
|
30
|
+
* export const STEP_PATHS = _plan.labels.PATHS; // "1.1"
|
|
31
|
+
* export const STEP_EXTRACT = _plan.labels.EXTRACT; // "2"
|
|
32
|
+
* export const STEP_CALL = _plan.labels.CALL; // "3"
|
|
33
|
+
*
|
|
34
|
+
* // Dynamic accessor for the conditional sub-step
|
|
35
|
+
* export const getStepTrace = (p: MyParams) => _plan.labelFor("TRACE", p); // "2.1" | null
|
|
36
|
+
*
|
|
37
|
+
* // Render for the LLM
|
|
38
|
+
* const promptText = _plan.render(params);
|
|
39
|
+
*
|
|
40
|
+
* // Developer review
|
|
41
|
+
* console.log(_plan.preview(params));
|
|
42
|
+
*/
|
|
43
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
44
|
+
export class PhaseBuilder {
|
|
45
|
+
_plan;
|
|
46
|
+
_phase;
|
|
47
|
+
_lastStep = null;
|
|
48
|
+
constructor(plan, phase) {
|
|
49
|
+
this._plan = plan;
|
|
50
|
+
this._phase = phase;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Add a non-conditional main step.
|
|
54
|
+
* Main steps always appear in the rendered output and always carry a stable
|
|
55
|
+
* label (1, 2, 3, …) regardless of which sub-steps are active.
|
|
56
|
+
*/
|
|
57
|
+
step(key, title, body) {
|
|
58
|
+
this._plan._registerKey(key);
|
|
59
|
+
const def = { key, title, body, subSteps: [] };
|
|
60
|
+
this._phase.steps.push(def);
|
|
61
|
+
this._lastStep = def;
|
|
62
|
+
return this;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Add a sub-step under the most recently declared main step.
|
|
66
|
+
* Sub-steps are sequentially numbered within their parent (1.1, 1.2, …).
|
|
67
|
+
*
|
|
68
|
+
* When opts.when is provided the sub-step is conditional: if the condition
|
|
69
|
+
* returns false for the given params, the step is omitted and remaining
|
|
70
|
+
* siblings re-number so the LLM never sees a gap.
|
|
71
|
+
*
|
|
72
|
+
* @throws when called before any step() in this phase.
|
|
73
|
+
*/
|
|
74
|
+
subStep(key, title, body, opts) {
|
|
75
|
+
if (!this._lastStep) {
|
|
76
|
+
throw new Error(`subStep("${key}") must follow a step() call in the same phase`);
|
|
77
|
+
}
|
|
78
|
+
this._plan._registerKey(key);
|
|
79
|
+
this._lastStep.subSteps.push({
|
|
80
|
+
key,
|
|
81
|
+
title,
|
|
82
|
+
body,
|
|
83
|
+
condition: opts?.when,
|
|
84
|
+
conditionDesc: opts?.whenDesc,
|
|
85
|
+
});
|
|
86
|
+
return this;
|
|
87
|
+
}
|
|
88
|
+
/** Finish building this phase and return the parent PromptPlan. */
|
|
89
|
+
done() {
|
|
90
|
+
return this._plan;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
// ─────────────────────────────────────────────────────────────────────────────
|
|
94
|
+
export class PromptPlan {
|
|
95
|
+
_phases = [];
|
|
96
|
+
_startFrom;
|
|
97
|
+
/** Track registered step/sub-step keys for duplicate detection. */
|
|
98
|
+
_registeredKeys = new Set();
|
|
99
|
+
/**
|
|
100
|
+
* @param opts.startFrom - Label of the first main step. Defaults to 1.
|
|
101
|
+
* Use 0 for execution-plan style (Step 0, Step 1, Step 2, …).
|
|
102
|
+
*/
|
|
103
|
+
constructor(opts) {
|
|
104
|
+
this._startFrom = opts?.startFrom ?? 1;
|
|
105
|
+
}
|
|
106
|
+
/** @internal Register a key and throw if already taken. */
|
|
107
|
+
_registerKey(key) {
|
|
108
|
+
if (this._registeredKeys.has(key)) {
|
|
109
|
+
throw new Error(`PromptPlan: duplicate step key "${key}"`);
|
|
110
|
+
}
|
|
111
|
+
this._registeredKeys.add(key);
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Add a phase and return a PhaseBuilder to register its steps.
|
|
115
|
+
* Multiple phases share a single global main-step counter, so step labels
|
|
116
|
+
* increment continuously across phase boundaries.
|
|
117
|
+
*/
|
|
118
|
+
addPhase(key, title, opts) {
|
|
119
|
+
const phase = {
|
|
120
|
+
key,
|
|
121
|
+
title,
|
|
122
|
+
headerLevel: opts?.headerLevel ?? "hidden",
|
|
123
|
+
stepFormat: opts?.stepFormat ?? "hash",
|
|
124
|
+
steps: [],
|
|
125
|
+
};
|
|
126
|
+
this._phases.push(phase);
|
|
127
|
+
return new PhaseBuilder(this, phase);
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Static label map for all non-conditional steps (main steps and
|
|
131
|
+
* unconditional sub-steps). Conditional sub-steps are not present here —
|
|
132
|
+
* use labelFor() for those.
|
|
133
|
+
*
|
|
134
|
+
* Safe to export as module-level constants for use in tests.
|
|
135
|
+
*
|
|
136
|
+
* **Ordering constraint**: unconditional sub-steps that appear *after* a
|
|
137
|
+
* conditional sibling will have a stable label here only if no conditional
|
|
138
|
+
* siblings precede them. When a conditional sibling is active at runtime,
|
|
139
|
+
* labelFor() will assign a higher number than the static label here. To
|
|
140
|
+
* avoid this mismatch, place conditional sub-steps AFTER all unconditional
|
|
141
|
+
* siblings, or use labelFor() for any sub-step with a conditional predecessor.
|
|
142
|
+
*
|
|
143
|
+
* O(steps) per call — intentionally avoids a stale-cache window during plan
|
|
144
|
+
* construction. Plans have ~6 steps so the cost is negligible; exported
|
|
145
|
+
* constants are evaluated once at module-load time.
|
|
146
|
+
*/
|
|
147
|
+
get labels() {
|
|
148
|
+
const map = {};
|
|
149
|
+
let mainIdx = this._startFrom - 1;
|
|
150
|
+
for (const phase of this._phases) {
|
|
151
|
+
for (const step of phase.steps) {
|
|
152
|
+
mainIdx++;
|
|
153
|
+
map[step.key] = String(mainIdx);
|
|
154
|
+
let subIdx = 0;
|
|
155
|
+
for (const sub of step.subSteps) {
|
|
156
|
+
if (!sub.condition) {
|
|
157
|
+
subIdx++;
|
|
158
|
+
map[sub.key] = `${mainIdx}.${subIdx}`;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return map;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Dynamic label for any registered step given runtime params.
|
|
167
|
+
*
|
|
168
|
+
* Returns null when the step has a condition that evaluates to false.
|
|
169
|
+
* Throws when key is not registered in this plan.
|
|
170
|
+
*
|
|
171
|
+
* For non-conditional steps the result equals labels[key], but using
|
|
172
|
+
* labels is preferred for those since it avoids passing params.
|
|
173
|
+
*/
|
|
174
|
+
labelFor(key, params) {
|
|
175
|
+
let mainIdx = this._startFrom - 1;
|
|
176
|
+
for (const phase of this._phases) {
|
|
177
|
+
for (const step of phase.steps) {
|
|
178
|
+
mainIdx++;
|
|
179
|
+
if (step.key === key)
|
|
180
|
+
return String(mainIdx);
|
|
181
|
+
let subIdx = 0;
|
|
182
|
+
for (const sub of step.subSteps) {
|
|
183
|
+
const active = !sub.condition || sub.condition(params);
|
|
184
|
+
if (active)
|
|
185
|
+
subIdx++;
|
|
186
|
+
if (sub.key === key) {
|
|
187
|
+
return active ? `${mainIdx}.${subIdx}` : null;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
throw new Error(`PromptPlan: unknown step key "${key}"`);
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Render the full prompt for the given params.
|
|
196
|
+
*
|
|
197
|
+
* - Phase headers are emitted when headerLevel is not "hidden".
|
|
198
|
+
* - Steps are formatted per the phase's stepFormat.
|
|
199
|
+
* - Conditional sub-steps absent for these params are skipped; remaining
|
|
200
|
+
* siblings re-number with no gaps.
|
|
201
|
+
* - Each section (header + body) is separated by a single blank line.
|
|
202
|
+
*/
|
|
203
|
+
render(params) {
|
|
204
|
+
const sections = [];
|
|
205
|
+
let mainIdx = this._startFrom - 1;
|
|
206
|
+
for (const phase of this._phases) {
|
|
207
|
+
if (phase.headerLevel !== "hidden") {
|
|
208
|
+
sections.push(`${phase.headerLevel} ${phase.title}`);
|
|
209
|
+
}
|
|
210
|
+
for (const step of phase.steps) {
|
|
211
|
+
mainIdx++;
|
|
212
|
+
const label = String(mainIdx);
|
|
213
|
+
const title = resolveTitle(step.title, params);
|
|
214
|
+
const body = step.body(params);
|
|
215
|
+
sections.push(`${renderStepHeader(phase.stepFormat, label, title)}\n${body.trimEnd()}`);
|
|
216
|
+
let subIdx = 0;
|
|
217
|
+
for (const sub of step.subSteps) {
|
|
218
|
+
if (sub.condition && !sub.condition(params))
|
|
219
|
+
continue;
|
|
220
|
+
subIdx++;
|
|
221
|
+
const subLabel = `${label}.${subIdx}`;
|
|
222
|
+
const subTitle = resolveTitle(sub.title, params);
|
|
223
|
+
const subBody = sub.body(params);
|
|
224
|
+
sections.push(`${renderStepHeader(phase.stepFormat, subLabel, subTitle)}\n${subBody.trimEnd()}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return sections.join("\n\n");
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Return a structured preview of all steps and their active state for the
|
|
232
|
+
* given params. Useful in development, tests, and debugging to verify exactly
|
|
233
|
+
* which steps would be emitted and what labels they carry.
|
|
234
|
+
*
|
|
235
|
+
* @example
|
|
236
|
+
* const pv = _plan.preview({ ...params, unmatchedFiles: [] });
|
|
237
|
+
* // pv.phases[0].steps[1].subSteps[0] =>
|
|
238
|
+
* // { key: "TRACE_CALLERS", label: "2.?", active: false,
|
|
239
|
+
* // conditionDesc: "non-frontend unmatched backend files present" }
|
|
240
|
+
*/
|
|
241
|
+
preview(params) {
|
|
242
|
+
let mainIdx = this._startFrom - 1;
|
|
243
|
+
let activeCount = 0;
|
|
244
|
+
let totalCount = 0;
|
|
245
|
+
const phases = this._phases.map((phase) => ({
|
|
246
|
+
key: phase.key,
|
|
247
|
+
title: phase.title,
|
|
248
|
+
steps: phase.steps.map((step) => {
|
|
249
|
+
mainIdx++;
|
|
250
|
+
activeCount++;
|
|
251
|
+
totalCount++;
|
|
252
|
+
const label = String(mainIdx);
|
|
253
|
+
let subIdx = 0;
|
|
254
|
+
const subSteps = step.subSteps.map((sub) => {
|
|
255
|
+
const active = !sub.condition || sub.condition(params);
|
|
256
|
+
totalCount++;
|
|
257
|
+
if (active) {
|
|
258
|
+
subIdx++;
|
|
259
|
+
activeCount++;
|
|
260
|
+
}
|
|
261
|
+
return {
|
|
262
|
+
key: sub.key,
|
|
263
|
+
label: active ? `${label}.${subIdx}` : `${label}.?`,
|
|
264
|
+
title: resolveTitle(sub.title, params),
|
|
265
|
+
active,
|
|
266
|
+
...(sub.conditionDesc !== undefined
|
|
267
|
+
? { conditionDesc: sub.conditionDesc }
|
|
268
|
+
: {}),
|
|
269
|
+
};
|
|
270
|
+
});
|
|
271
|
+
return {
|
|
272
|
+
key: step.key,
|
|
273
|
+
label,
|
|
274
|
+
title: resolveTitle(step.title, params),
|
|
275
|
+
subSteps,
|
|
276
|
+
};
|
|
277
|
+
}),
|
|
278
|
+
}));
|
|
279
|
+
return { phases, activeStepCount: activeCount, totalStepCount: totalCount };
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
// ─── Internal helpers ────────────────────────────────────────────────────────
|
|
283
|
+
function resolveTitle(title, params) {
|
|
284
|
+
return typeof title === "function" ? title(params) : title;
|
|
285
|
+
}
|
|
286
|
+
function renderStepHeader(format, label, title) {
|
|
287
|
+
return format === "bold"
|
|
288
|
+
? `**Step ${label} — ${title}**`
|
|
289
|
+
: `### Step ${label}: ${title}`;
|
|
290
|
+
}
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
import { PromptPlan } from "./promptPlan.js";
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// Core numbering invariants
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
describe("PromptPlan — main step numbering", () => {
|
|
6
|
+
it("generates sequential main-step labels starting at 1", () => {
|
|
7
|
+
const plan = new PromptPlan()
|
|
8
|
+
.addPhase("p", "Phase")
|
|
9
|
+
.step("A", "Step A", () => "body-a")
|
|
10
|
+
.step("B", "Step B", () => "body-b")
|
|
11
|
+
.step("C", "Step C", () => "body-c")
|
|
12
|
+
.done();
|
|
13
|
+
expect(plan.labels.A).toBe("1");
|
|
14
|
+
expect(plan.labels.B).toBe("2");
|
|
15
|
+
expect(plan.labels.C).toBe("3");
|
|
16
|
+
});
|
|
17
|
+
it("supports startFrom: 0 for execution-plan style (Step 0, Step 1, …)", () => {
|
|
18
|
+
const plan = new PromptPlan({ startFrom: 0 })
|
|
19
|
+
.addPhase("p", "Phase")
|
|
20
|
+
.step("COVERAGE", "Coverage", () => "")
|
|
21
|
+
.step("ENRICH", "Enrich", () => "")
|
|
22
|
+
.step("DIVERSITY", "Diversity", () => "")
|
|
23
|
+
.step("EXECUTE", "Execute", () => "")
|
|
24
|
+
.done();
|
|
25
|
+
expect(plan.labels.COVERAGE).toBe("0");
|
|
26
|
+
expect(plan.labels.ENRICH).toBe("1");
|
|
27
|
+
expect(plan.labels.DIVERSITY).toBe("2");
|
|
28
|
+
expect(plan.labels.EXECUTE).toBe("3");
|
|
29
|
+
});
|
|
30
|
+
it("main step labels are compile-time stable — identical to labelFor() with any params", () => {
|
|
31
|
+
const plan = new PromptPlan()
|
|
32
|
+
.addPhase("p", "Phase")
|
|
33
|
+
.step("X", "X", () => "")
|
|
34
|
+
.step("Y", "Y", () => "")
|
|
35
|
+
.done();
|
|
36
|
+
const params = { flag: true };
|
|
37
|
+
expect(plan.labels.X).toBe(plan.labelFor("X", params));
|
|
38
|
+
expect(plan.labels.Y).toBe(plan.labelFor("Y", params));
|
|
39
|
+
});
|
|
40
|
+
it("auto-renumbers when a step is inserted — key contract of the abstraction", () => {
|
|
41
|
+
const plan = new PromptPlan()
|
|
42
|
+
.addPhase("p", "Phase")
|
|
43
|
+
.step("FIRST", "First", () => "")
|
|
44
|
+
.step("NEW", "Newly inserted", () => "") // inserted before what was "2"
|
|
45
|
+
.step("SECOND", "Second", () => "")
|
|
46
|
+
.done();
|
|
47
|
+
expect(plan.labels.FIRST).toBe("1");
|
|
48
|
+
expect(plan.labels.NEW).toBe("2");
|
|
49
|
+
expect(plan.labels.SECOND).toBe("3"); // auto-renumbered from "2" to "3"
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Sub-step numbering
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
describe("PromptPlan — sub-step numbering", () => {
|
|
56
|
+
it("assigns sequential sub-step labels within their parent (1.1, 1.2, 2.1, …)", () => {
|
|
57
|
+
const plan = new PromptPlan()
|
|
58
|
+
.addPhase("p", "Phase")
|
|
59
|
+
.step("READ", "Read", () => "")
|
|
60
|
+
.subStep("PATHS", "Paths", () => "")
|
|
61
|
+
.step("EXTRACT", "Extract", () => "")
|
|
62
|
+
.subStep("TRACE", "Trace", () => "", { when: (p) => p.flag })
|
|
63
|
+
.done();
|
|
64
|
+
expect(plan.labels.READ).toBe("1");
|
|
65
|
+
expect(plan.labels.PATHS).toBe("1.1");
|
|
66
|
+
expect(plan.labels.EXTRACT).toBe("2");
|
|
67
|
+
// TRACE is conditional — NOT in labels map
|
|
68
|
+
expect(plan.labels.TRACE).toBeUndefined();
|
|
69
|
+
});
|
|
70
|
+
it("sub() does not advance the main counter", () => {
|
|
71
|
+
const plan = new PromptPlan()
|
|
72
|
+
.addPhase("p", "Phase")
|
|
73
|
+
.step("A", "A", () => "")
|
|
74
|
+
.subStep("A1", "A1", () => "")
|
|
75
|
+
.subStep("A2", "A2", () => "")
|
|
76
|
+
.step("B", "B", () => "")
|
|
77
|
+
.done();
|
|
78
|
+
expect(plan.labels.A).toBe("1");
|
|
79
|
+
expect(plan.labels.A1).toBe("1.1");
|
|
80
|
+
expect(plan.labels.A2).toBe("1.2");
|
|
81
|
+
expect(plan.labels.B).toBe("2");
|
|
82
|
+
});
|
|
83
|
+
it("conditional sub-step is absent from static labels map", () => {
|
|
84
|
+
const plan = new PromptPlan()
|
|
85
|
+
.addPhase("p", "Phase")
|
|
86
|
+
.step("A", "A", () => "")
|
|
87
|
+
.subStep("COND", "Conditional", () => "", { when: (p) => p.flag })
|
|
88
|
+
.done();
|
|
89
|
+
expect(plan.labels.A).toBe("1");
|
|
90
|
+
expect(plan.labels.COND).toBeUndefined();
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
// ---------------------------------------------------------------------------
|
|
94
|
+
// labelFor() — dynamic labels
|
|
95
|
+
// ---------------------------------------------------------------------------
|
|
96
|
+
describe("PromptPlan — labelFor()", () => {
|
|
97
|
+
const plan = new PromptPlan()
|
|
98
|
+
.addPhase("p", "Phase")
|
|
99
|
+
.step("READ", "Read", () => "")
|
|
100
|
+
.subStep("PATHS", "Paths", () => "")
|
|
101
|
+
.step("EXTRACT", "Extract", () => "")
|
|
102
|
+
.subStep("TRACE", "Trace", () => "", { when: (p) => p.flag })
|
|
103
|
+
.step("DRAFT", "Draft", () => "")
|
|
104
|
+
.done();
|
|
105
|
+
it("returns the static label for always-present main steps", () => {
|
|
106
|
+
const p = { flag: true };
|
|
107
|
+
expect(plan.labelFor("READ", p)).toBe("1");
|
|
108
|
+
expect(plan.labelFor("EXTRACT", p)).toBe("2");
|
|
109
|
+
expect(plan.labelFor("DRAFT", p)).toBe("3");
|
|
110
|
+
});
|
|
111
|
+
it("returns the static label for always-present sub-steps", () => {
|
|
112
|
+
expect(plan.labelFor("PATHS", { flag: false })).toBe("1.1");
|
|
113
|
+
});
|
|
114
|
+
it("returns '2.1' for the conditional sub-step when condition is true", () => {
|
|
115
|
+
expect(plan.labelFor("TRACE", { flag: true })).toBe("2.1");
|
|
116
|
+
});
|
|
117
|
+
it("returns null for the conditional sub-step when condition is false", () => {
|
|
118
|
+
expect(plan.labelFor("TRACE", { flag: false })).toBeNull();
|
|
119
|
+
});
|
|
120
|
+
it("throws for an unknown key", () => {
|
|
121
|
+
expect(() => plan.labelFor("UNKNOWN", { flag: true })).toThrow('PromptPlan: unknown step key "UNKNOWN"');
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
// ---------------------------------------------------------------------------
|
|
125
|
+
// Conditional sub-step: re-numbering when sibling is absent
|
|
126
|
+
// ---------------------------------------------------------------------------
|
|
127
|
+
describe("PromptPlan — conditional sub-step re-numbering", () => {
|
|
128
|
+
const plan = new PromptPlan()
|
|
129
|
+
.addPhase("p", "Phase")
|
|
130
|
+
.step("A", "A", () => "")
|
|
131
|
+
.subStep("A_COND", "Conditional A", () => "", { when: (p) => p.flag })
|
|
132
|
+
.subStep("A_ALWAYS", "Always present", () => "always")
|
|
133
|
+
.done();
|
|
134
|
+
it("when condition is true, A_COND=1.1 and A_ALWAYS=1.2", () => {
|
|
135
|
+
expect(plan.labelFor("A_COND", { flag: true })).toBe("1.1");
|
|
136
|
+
expect(plan.labelFor("A_ALWAYS", { flag: true })).toBe("1.2");
|
|
137
|
+
});
|
|
138
|
+
it("when condition is false, A_COND=null and A_ALWAYS re-numbers to 1.1", () => {
|
|
139
|
+
expect(plan.labelFor("A_COND", { flag: false })).toBeNull();
|
|
140
|
+
expect(plan.labelFor("A_ALWAYS", { flag: false })).toBe("1.1");
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
// ---------------------------------------------------------------------------
|
|
144
|
+
// render() — output format
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
146
|
+
describe("PromptPlan — render()", () => {
|
|
147
|
+
it("emits ### Step N: Title headers for hash format", () => {
|
|
148
|
+
const plan = new PromptPlan()
|
|
149
|
+
.addPhase("p", "Phase", { stepFormat: "hash" })
|
|
150
|
+
.step("A", "Do something", () => "body text")
|
|
151
|
+
.done();
|
|
152
|
+
const output = plan.render({ flag: true });
|
|
153
|
+
expect(output).toContain("### Step 1: Do something");
|
|
154
|
+
expect(output).toContain("body text");
|
|
155
|
+
});
|
|
156
|
+
it("emits **Step N — Title** headers for bold format", () => {
|
|
157
|
+
const plan = new PromptPlan({ startFrom: 0 })
|
|
158
|
+
.addPhase("p", "Phase", { stepFormat: "bold" })
|
|
159
|
+
.step("A", "Coverage", () => "coverage body")
|
|
160
|
+
.done();
|
|
161
|
+
const output = plan.render({ flag: true });
|
|
162
|
+
expect(output).toContain("**Step 0 — Coverage**");
|
|
163
|
+
expect(output).toContain("coverage body");
|
|
164
|
+
});
|
|
165
|
+
it("emits the phase header when headerLevel is not hidden", () => {
|
|
166
|
+
const plan = new PromptPlan()
|
|
167
|
+
.addPhase("p", "My Phase Title", { headerLevel: "##" })
|
|
168
|
+
.step("A", "Step A", () => "")
|
|
169
|
+
.done();
|
|
170
|
+
expect(plan.render({ flag: true })).toContain("## My Phase Title");
|
|
171
|
+
});
|
|
172
|
+
it("does NOT emit a phase header when headerLevel is hidden", () => {
|
|
173
|
+
const plan = new PromptPlan()
|
|
174
|
+
.addPhase("p", "Hidden Phase", { headerLevel: "hidden" })
|
|
175
|
+
.step("A", "Step A", () => "")
|
|
176
|
+
.done();
|
|
177
|
+
expect(plan.render({ flag: true })).not.toContain("Hidden Phase");
|
|
178
|
+
});
|
|
179
|
+
it("omits a conditional sub-step when condition is false", () => {
|
|
180
|
+
const plan = new PromptPlan()
|
|
181
|
+
.addPhase("p", "Phase")
|
|
182
|
+
.step("A", "A", () => "step-a-body")
|
|
183
|
+
.subStep("COND", "Conditional", () => "cond-body", { when: (p) => p.flag })
|
|
184
|
+
.done();
|
|
185
|
+
const withFlag = plan.render({ flag: true });
|
|
186
|
+
const withoutFlag = plan.render({ flag: false });
|
|
187
|
+
expect(withFlag).toContain("cond-body");
|
|
188
|
+
expect(withoutFlag).not.toContain("cond-body");
|
|
189
|
+
expect(withoutFlag).not.toContain("Conditional");
|
|
190
|
+
});
|
|
191
|
+
it("re-numbers remaining sub-steps when a conditional one is absent", () => {
|
|
192
|
+
const plan = new PromptPlan()
|
|
193
|
+
.addPhase("p", "Phase")
|
|
194
|
+
.step("A", "A", () => "")
|
|
195
|
+
.subStep("COND", "Conditional", () => "cond", { when: (p) => p.flag })
|
|
196
|
+
.subStep("ALWAYS", "Always", () => "always")
|
|
197
|
+
.done();
|
|
198
|
+
// With flag: 1.1 = Conditional, 1.2 = Always
|
|
199
|
+
expect(plan.render({ flag: true })).toContain("### Step 1.1: Conditional");
|
|
200
|
+
expect(plan.render({ flag: true })).toContain("### Step 1.2: Always");
|
|
201
|
+
// Without flag: conditional absent, Always re-numbers to 1.1
|
|
202
|
+
expect(plan.render({ flag: false })).not.toContain("Conditional");
|
|
203
|
+
expect(plan.render({ flag: false })).toContain("### Step 1.1: Always");
|
|
204
|
+
});
|
|
205
|
+
it("supports dynamic step titles via title functions", () => {
|
|
206
|
+
const plan = new PromptPlan()
|
|
207
|
+
.addPhase("p", "Phase")
|
|
208
|
+
.step("A", (p) => p.flag ? "Flag is on" : "Flag is off", () => "body")
|
|
209
|
+
.done();
|
|
210
|
+
expect(plan.render({ flag: true })).toContain("### Step 1: Flag is on");
|
|
211
|
+
expect(plan.render({ flag: false })).toContain("### Step 1: Flag is off");
|
|
212
|
+
});
|
|
213
|
+
it("sections are separated by exactly one blank line", () => {
|
|
214
|
+
const plan = new PromptPlan()
|
|
215
|
+
.addPhase("p", "Phase", { headerLevel: "##" })
|
|
216
|
+
.step("A", "Step A", () => "body-a")
|
|
217
|
+
.step("B", "Step B", () => "body-b")
|
|
218
|
+
.done();
|
|
219
|
+
const output = plan.render({ flag: true });
|
|
220
|
+
// Each pair of sections should have \n\n (exactly one blank line) between them
|
|
221
|
+
expect(output).toContain("## Phase\n\n### Step 1");
|
|
222
|
+
expect(output).toContain("body-a\n\n### Step 2");
|
|
223
|
+
});
|
|
224
|
+
it("reproduces the analysis-output step sequence", () => {
|
|
225
|
+
const plan = new PromptPlan()
|
|
226
|
+
.addPhase("analysis", "Your Task", { headerLevel: "##", stepFormat: "hash" })
|
|
227
|
+
.step("READ", "Read the changed files", () => "read body")
|
|
228
|
+
.subStep("PATHS", "Resolve endpoint paths", () => "paths body")
|
|
229
|
+
.step("EXTRACT", "Extract endpoints", () => "extract body")
|
|
230
|
+
.subStep("TRACE", "Trace callers", () => "trace body", { when: (p) => p.flag })
|
|
231
|
+
.step("DRAFT", "Draft scenarios", () => "draft body")
|
|
232
|
+
.step("CALL", "Call the tool", () => "call body")
|
|
233
|
+
.done();
|
|
234
|
+
expect(plan.labels.READ).toBe("1");
|
|
235
|
+
expect(plan.labels.PATHS).toBe("1.1");
|
|
236
|
+
expect(plan.labels.EXTRACT).toBe("2");
|
|
237
|
+
expect(plan.labels.DRAFT).toBe("3");
|
|
238
|
+
expect(plan.labels.CALL).toBe("4");
|
|
239
|
+
// With flag: trace appears as 2.1
|
|
240
|
+
const withTrace = plan.render({ flag: true });
|
|
241
|
+
expect(withTrace).toContain("### Step 1: Read the changed files");
|
|
242
|
+
expect(withTrace).toContain("### Step 1.1: Resolve endpoint paths");
|
|
243
|
+
expect(withTrace).toContain("### Step 2: Extract endpoints");
|
|
244
|
+
expect(withTrace).toContain("### Step 2.1: Trace callers");
|
|
245
|
+
expect(withTrace).toContain("### Step 3: Draft scenarios");
|
|
246
|
+
expect(withTrace).toContain("### Step 4: Call the tool");
|
|
247
|
+
// Without flag: no trace step, sequence has no gap
|
|
248
|
+
const noTrace = plan.render({ flag: false });
|
|
249
|
+
expect(noTrace).not.toContain("trace body");
|
|
250
|
+
expect(noTrace).not.toContain("Step 2.1");
|
|
251
|
+
expect(noTrace).toContain("### Step 3: Draft scenarios");
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
// ---------------------------------------------------------------------------
|
|
255
|
+
// preview() — developer review
|
|
256
|
+
// ---------------------------------------------------------------------------
|
|
257
|
+
describe("PromptPlan — preview()", () => {
|
|
258
|
+
const plan = new PromptPlan()
|
|
259
|
+
.addPhase("analysis", "Analysis Phase", { headerLevel: "##" })
|
|
260
|
+
.step("READ", "Read", () => "")
|
|
261
|
+
.subStep("PATHS", "Paths", () => "")
|
|
262
|
+
.step("EXTRACT", "Extract", () => "")
|
|
263
|
+
.subStep("TRACE", "Trace", () => "", {
|
|
264
|
+
when: (p) => p.flag,
|
|
265
|
+
whenDesc: "backend unmatched files present",
|
|
266
|
+
})
|
|
267
|
+
.step("DRAFT", "Draft", () => "")
|
|
268
|
+
.done();
|
|
269
|
+
it("reports correct activeStepCount and totalStepCount", () => {
|
|
270
|
+
const withFlag = plan.preview({ flag: true });
|
|
271
|
+
const withoutFlag = plan.preview({ flag: false });
|
|
272
|
+
expect(withFlag.totalStepCount).toBe(5); // 3 main + 2 subs = 5
|
|
273
|
+
expect(withFlag.activeStepCount).toBe(5); // all active
|
|
274
|
+
expect(withoutFlag.totalStepCount).toBe(5);
|
|
275
|
+
expect(withoutFlag.activeStepCount).toBe(4); // TRACE absent
|
|
276
|
+
});
|
|
277
|
+
it("marks the conditional sub-step inactive when condition is false", () => {
|
|
278
|
+
const pv = plan.preview({ flag: false });
|
|
279
|
+
const extractStep = pv.phases[0].steps.find(s => s.key === "EXTRACT");
|
|
280
|
+
const traceSub = extractStep.subSteps.find(s => s.key === "TRACE");
|
|
281
|
+
expect(traceSub.active).toBe(false);
|
|
282
|
+
expect(traceSub.label).toBe("2.?");
|
|
283
|
+
expect(traceSub.conditionDesc).toBe("backend unmatched files present");
|
|
284
|
+
});
|
|
285
|
+
it("marks the conditional sub-step active with correct label when condition is true", () => {
|
|
286
|
+
const pv = plan.preview({ flag: true });
|
|
287
|
+
const extractStep = pv.phases[0].steps.find(s => s.key === "EXTRACT");
|
|
288
|
+
const traceSub = extractStep.subSteps.find(s => s.key === "TRACE");
|
|
289
|
+
expect(traceSub.active).toBe(true);
|
|
290
|
+
expect(traceSub.label).toBe("2.1");
|
|
291
|
+
});
|
|
292
|
+
it("includes all phase / step keys in the preview output", () => {
|
|
293
|
+
const pv = plan.preview({ flag: true });
|
|
294
|
+
expect(pv.phases).toHaveLength(1);
|
|
295
|
+
expect(pv.phases[0].key).toBe("analysis");
|
|
296
|
+
const stepKeys = pv.phases[0].steps.map(s => s.key);
|
|
297
|
+
expect(stepKeys).toEqual(["READ", "EXTRACT", "DRAFT"]);
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
// ---------------------------------------------------------------------------
|
|
301
|
+
// PhaseBuilder guard
|
|
302
|
+
// ---------------------------------------------------------------------------
|
|
303
|
+
describe("PhaseBuilder — error handling", () => {
|
|
304
|
+
it("throws when subStep() is called before any step()", () => {
|
|
305
|
+
expect(() => {
|
|
306
|
+
new PromptPlan()
|
|
307
|
+
.addPhase("p", "Phase")
|
|
308
|
+
.subStep("ORPHAN", "Orphan", () => "");
|
|
309
|
+
}).toThrow('subStep("ORPHAN") must follow a step() call in the same phase');
|
|
310
|
+
});
|
|
311
|
+
it("throws on duplicate step keys", () => {
|
|
312
|
+
expect(() => {
|
|
313
|
+
new PromptPlan()
|
|
314
|
+
.addPhase("p", "Phase")
|
|
315
|
+
.step("DUP", "First", () => "")
|
|
316
|
+
.step("DUP", "Second", () => "");
|
|
317
|
+
}).toThrow('PromptPlan: duplicate step key "DUP"');
|
|
318
|
+
});
|
|
319
|
+
it("throws on duplicate sub-step keys", () => {
|
|
320
|
+
expect(() => {
|
|
321
|
+
new PromptPlan()
|
|
322
|
+
.addPhase("p", "Phase")
|
|
323
|
+
.step("A", "A", () => "")
|
|
324
|
+
.subStep("DUP", "First sub", () => "")
|
|
325
|
+
.subStep("DUP", "Second sub", () => "");
|
|
326
|
+
}).toThrow('PromptPlan: duplicate step key "DUP"');
|
|
327
|
+
});
|
|
328
|
+
it("throws when a sub-step key conflicts with a main step key", () => {
|
|
329
|
+
expect(() => {
|
|
330
|
+
new PromptPlan()
|
|
331
|
+
.addPhase("p", "Phase")
|
|
332
|
+
.step("SHARED", "Main", () => "")
|
|
333
|
+
.subStep("SHARED", "Sub", () => "");
|
|
334
|
+
}).toThrow('PromptPlan: duplicate step key "SHARED"');
|
|
335
|
+
});
|
|
336
|
+
});
|
|
@@ -172,7 +172,7 @@ When no Playwright trace exists, use the Playwright browser tools (\`browser_nav
|
|
|
172
172
|
`}
|
|
173
173
|
`;
|
|
174
174
|
}
|
|
175
|
-
export function buildVerificationChecklist(topN, maxGen) {
|
|
175
|
+
export function buildVerificationChecklist(topN, maxGen, codeReviewStepLabel = "0") {
|
|
176
176
|
const minTotal = Math.min(maxGen + 1, topN);
|
|
177
177
|
return `<verification>
|
|
178
178
|
Before finalizing your output, verify:
|
|
@@ -186,6 +186,8 @@ Before finalizing your output, verify:
|
|
|
186
186
|
8. **bugCatchingTarget**: Every GENERATE integration test that targets a business rule, formula, or constraint has a non-empty \`bugCatchingTarget\`.
|
|
187
187
|
9. **FK chaining**: In multi-step integration tests, path params sourced from a prior step's response (e.g. \`order_id\` from step 1) use \`chainsFrom\` — not hardcoded IDs.
|
|
188
188
|
10. **Concrete scenario names**: No GENERATE item uses a placeholder name ending in a numeric suffix (e.g. \`ui-test-for-changed-component-1\`, \`ui-test-from-trace-2\`). Derive the name from the actual changed component or flow: if the diff touches \`LinkCard.tsx\`, the scenario name should be \`link-card-pin-toggle\` or \`link-card-edit-description\`, not \`ui-test-for-changed-component-1\`. The changed file list is available above — use it.
|
|
189
|
+
11. **Issue coverage**: If \`<bug_found>\` blocks exist from Step ${codeReviewStepLabel} (Code Review), verify that the highest-severity flaw (HIGH or CRITICAL) has at least one GENERATE item directly targeting it (its pass/fail outcome depends on whether that bug exists). At most one promotion per run. If the promoted flaw lacks a dedicated GENERATE item, promote or create one before proceeding. Additional HIGH/CRITICAL flaws beyond the first should appear in ADDITIONAL at highest priority.
|
|
190
|
+
12. **Code Review completeness**: Did you produce a \`<function_review>\` block for EVERY changed function/handler in Step ${codeReviewStepLabel}? If any function is missing a review, you skipped the correctness analysis for it. Go back and complete it before finalizing.
|
|
189
191
|
</verification>`;
|
|
190
192
|
}
|
|
191
193
|
export function buildFewShotExamples() {
|
|
@@ -317,8 +319,7 @@ ${authGuidance}
|
|
|
317
319
|
**OpenAPI spec is NOT required.** \`apiSchema\` is OPTIONAL — omit it if no spec exists.
|
|
318
320
|
**CRITICAL — Query params vs request body:**
|
|
319
321
|
- For **POST/PUT/PATCH**: use \`requestBody\` with realistic field values from source code schemas.
|
|
320
|
-
- For **GET/DELETE with search/filter/pagination**: use \`queryParams
|
|
321
|
-
Do not put query parameters in \`requestBody\` for GET requests — GET request bodies are non-standard and may be ignored or rejected.
|
|
322
|
+
- For **GET/DELETE with search/filter/pagination**: use \`queryParams\`. Do not put query parameters in \`requestBody\` for GET requests — GET request bodies are non-standard and may be ignored or rejected.
|
|
322
323
|
- For **GET by ID**: no \`requestBody\` or \`queryParams\` needed — the ID is in the path.
|
|
323
324
|
\`responseBody\` should match the actual API response shape from source code (including all fields
|
|
324
325
|
returned by the controller — e.g., \`id\`, \`ownerId\`, \`createdAt\`, included relations like \`collection\`, \`tags\`).
|