@mhingston5/lasso 0.1.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 +707 -0
- package/docs/agent-wrangling.png +0 -0
- package/package.json +26 -0
- package/src/capabilities/matcher.ts +25 -0
- package/src/capabilities/registry.ts +103 -0
- package/src/capabilities/types.ts +15 -0
- package/src/cir/lower.ts +253 -0
- package/src/cir/optimize.ts +251 -0
- package/src/cir/types.ts +131 -0
- package/src/cir/validate.ts +265 -0
- package/src/compiler/compile.ts +601 -0
- package/src/compiler/feedback.ts +471 -0
- package/src/compiler/runtime-helpers.ts +455 -0
- package/src/composition/chain.ts +58 -0
- package/src/composition/conditional.ts +76 -0
- package/src/composition/parallel.ts +75 -0
- package/src/composition/types.ts +105 -0
- package/src/environment/analyzer.ts +56 -0
- package/src/environment/discovery.ts +179 -0
- package/src/environment/types.ts +68 -0
- package/src/failures/classifiers.ts +134 -0
- package/src/failures/generator.ts +421 -0
- package/src/failures/map-reference-failures.ts +23 -0
- package/src/failures/ontology.ts +210 -0
- package/src/failures/recovery.ts +214 -0
- package/src/failures/types.ts +14 -0
- package/src/index.ts +67 -0
- package/src/memory/advisor.ts +132 -0
- package/src/memory/extractor.ts +166 -0
- package/src/memory/store.ts +107 -0
- package/src/memory/types.ts +53 -0
- package/src/metaharness/engine.ts +256 -0
- package/src/metaharness/predictor.ts +168 -0
- package/src/metaharness/types.ts +40 -0
- package/src/mutation/derive.ts +308 -0
- package/src/mutation/diff.ts +52 -0
- package/src/mutation/engine.ts +256 -0
- package/src/mutation/types.ts +84 -0
- package/src/pi/command-input.ts +209 -0
- package/src/pi/commands.ts +351 -0
- package/src/pi/extension.ts +16 -0
- package/src/planner/synthesize.ts +83 -0
- package/src/planner/template-rules.ts +183 -0
- package/src/planner/types.ts +42 -0
- package/src/reference/catalog.ts +128 -0
- package/src/reference/patch-validation-strategies.ts +170 -0
- package/src/reference/patch-validation.ts +174 -0
- package/src/reference/pr-review-merge.ts +155 -0
- package/src/reference/strategies.ts +126 -0
- package/src/reference/types.ts +33 -0
- package/src/replanner/risk-rules.ts +161 -0
- package/src/replanner/runtime.ts +308 -0
- package/src/replanner/synthesize.ts +619 -0
- package/src/replanner/types.ts +73 -0
- package/src/spec/schema.ts +254 -0
- package/src/spec/types.ts +319 -0
- package/src/spec/validate.ts +296 -0
- package/src/state/snapshots.ts +43 -0
- package/src/state/types.ts +12 -0
- package/src/synthesis/graph-builder.ts +267 -0
- package/src/synthesis/harness-builder.ts +113 -0
- package/src/synthesis/intent-ir.ts +63 -0
- package/src/synthesis/policy-builder.ts +320 -0
- package/src/synthesis/risk-analyzer.ts +182 -0
- package/src/synthesis/skill-parser.ts +441 -0
- package/src/verification/engine.ts +230 -0
- package/src/versioning/file-store.ts +103 -0
- package/src/versioning/history.ts +43 -0
- package/src/versioning/store.ts +16 -0
- package/src/versioning/types.ts +31 -0
- package/test/capabilities/matcher.test.ts +67 -0
- package/test/capabilities/registry.test.ts +136 -0
- package/test/capabilities/synthesis.test.ts +264 -0
- package/test/cir/lower.test.ts +417 -0
- package/test/cir/optimize.test.ts +266 -0
- package/test/cir/validate.test.ts +368 -0
- package/test/compiler/adaptive-runtime.test.ts +157 -0
- package/test/compiler/compile.test.ts +1198 -0
- package/test/compiler/feedback.test.ts +784 -0
- package/test/compiler/guardrails.test.ts +191 -0
- package/test/compiler/trace.test.ts +404 -0
- package/test/composition/chain.test.ts +328 -0
- package/test/composition/conditional.test.ts +241 -0
- package/test/composition/parallel.test.ts +215 -0
- package/test/environment/analyzer.test.ts +204 -0
- package/test/environment/discovery.test.ts +149 -0
- package/test/failures/classifiers.test.ts +287 -0
- package/test/failures/generator.test.ts +203 -0
- package/test/failures/ontology.test.ts +439 -0
- package/test/failures/recovery.test.ts +300 -0
- package/test/helpers/createFixtureRepo.ts +84 -0
- package/test/helpers/createPatchValidationFixture.ts +144 -0
- package/test/helpers/runCompiledWorkflow.ts +208 -0
- package/test/memory/advisor.test.ts +332 -0
- package/test/memory/extractor.test.ts +295 -0
- package/test/memory/store.test.ts +244 -0
- package/test/metaharness/engine.test.ts +575 -0
- package/test/metaharness/predictor.test.ts +436 -0
- package/test/mutation/derive-failure.test.ts +209 -0
- package/test/mutation/engine.test.ts +622 -0
- package/test/package-smoke.test.ts +29 -0
- package/test/pi/command-input.test.ts +153 -0
- package/test/pi/commands.test.ts +623 -0
- package/test/planner/classify-template.test.ts +32 -0
- package/test/planner/synthesize.test.ts +901 -0
- package/test/reference/PatchValidation.failures.test.ts +137 -0
- package/test/reference/PatchValidation.test.ts +326 -0
- package/test/reference/PrReviewMerge.failures.test.ts +121 -0
- package/test/reference/PrReviewMerge.test.ts +55 -0
- package/test/reference/catalog-open.test.ts +70 -0
- package/test/replanner/runtime.test.ts +207 -0
- package/test/replanner/synthesize.test.ts +303 -0
- package/test/spec/validate.test.ts +1056 -0
- package/test/state/snapshots.test.ts +264 -0
- package/test/synthesis/custom-workflow.test.ts +264 -0
- package/test/synthesis/graph-builder.test.ts +370 -0
- package/test/synthesis/harness-builder.test.ts +128 -0
- package/test/synthesis/policy-builder.test.ts +149 -0
- package/test/synthesis/risk-analyzer.test.ts +230 -0
- package/test/synthesis/skill-parser.test.ts +796 -0
- package/test/verification/engine.test.ts +509 -0
- package/test/versioning/history.test.ts +144 -0
- package/test/versioning/store.test.ts +254 -0
- package/vitest.config.ts +9 -0
|
@@ -0,0 +1,901 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { planWorkflowRequest } from "../../src/planner/synthesize.js";
|
|
3
|
+
|
|
4
|
+
describe("planWorkflowRequest", () => {
|
|
5
|
+
describe("empty brief", () => {
|
|
6
|
+
it("rejects empty string", () => {
|
|
7
|
+
const result = planWorkflowRequest("");
|
|
8
|
+
expect(result.status).toBe("needs_clarification");
|
|
9
|
+
if (result.status === "needs_clarification") {
|
|
10
|
+
expect(result.reasons).toContain("Brief is empty");
|
|
11
|
+
expect(result.missingFields).toContain("brief");
|
|
12
|
+
expect(Array.isArray(result.guidance)).toBe(true);
|
|
13
|
+
expect(result.guidance.length).toBeGreaterThan(0);
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it("rejects whitespace-only string", () => {
|
|
18
|
+
const result = planWorkflowRequest(" \n\t ");
|
|
19
|
+
expect(result.status).toBe("needs_clarification");
|
|
20
|
+
if (result.status === "needs_clarification") {
|
|
21
|
+
expect(result.reasons).toContain("Brief is empty");
|
|
22
|
+
expect(Array.isArray(result.guidance)).toBe(true);
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe("pr-review-merge happy path", () => {
|
|
28
|
+
it("extracts all fields from complete brief", () => {
|
|
29
|
+
const brief = `
|
|
30
|
+
PR review workflow:
|
|
31
|
+
repoPath: /Users/test/repo
|
|
32
|
+
source branch: feature-xyz
|
|
33
|
+
target branch: main
|
|
34
|
+
verification commands: ["npm test", "npm run lint"]
|
|
35
|
+
reviewInstructions: "Check for breaking changes"
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
const result = planWorkflowRequest(brief);
|
|
39
|
+
expect(result.status).toBe("draft_request");
|
|
40
|
+
|
|
41
|
+
if (result.status === "draft_request") {
|
|
42
|
+
expect(result.workflow).toBe("pr-review-merge");
|
|
43
|
+
expect(result.request.workflow).toBe("pr-review-merge");
|
|
44
|
+
expect(Array.isArray(result.rationale)).toBe(true);
|
|
45
|
+
expect(result.rationale.length).toBeGreaterThan(0);
|
|
46
|
+
expect(result.rationale.some(r => r.includes("pr-review-merge"))).toBe(true);
|
|
47
|
+
expect(Array.isArray(result.warnings)).toBe(true);
|
|
48
|
+
|
|
49
|
+
if (result.request.workflow === "pr-review-merge") {
|
|
50
|
+
const input = result.request.input;
|
|
51
|
+
expect(input.repoPath).toBe("/Users/test/repo");
|
|
52
|
+
expect(input.sourceBranch).toBe("feature-xyz");
|
|
53
|
+
expect(input.targetBranch).toBe("main");
|
|
54
|
+
expect(input.verificationCommands).toEqual(["npm test", "npm run lint"]);
|
|
55
|
+
expect(input.reviewInstructions).toBe("Check for breaking changes");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("extracts a Windows repo path from a generic path label", () => {
|
|
61
|
+
const brief = `
|
|
62
|
+
PR review workflow:
|
|
63
|
+
path: "C:/Users/test/repo"
|
|
64
|
+
source branch: feature-xyz
|
|
65
|
+
target branch: main
|
|
66
|
+
verification commands: ["npm test"]
|
|
67
|
+
reviewInstructions: "Check for breaking changes"
|
|
68
|
+
`;
|
|
69
|
+
|
|
70
|
+
const result = planWorkflowRequest(brief);
|
|
71
|
+
expect(result.status).toBe("draft_request");
|
|
72
|
+
|
|
73
|
+
if (result.status === "draft_request" && result.request.workflow === "pr-review-merge") {
|
|
74
|
+
expect(result.request.input.repoPath).toBe("C:/Users/test/repo");
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
it("extracts colon-suffixed branch fields without requiring a space", () => {
|
|
79
|
+
const brief = `
|
|
80
|
+
PR review workflow:
|
|
81
|
+
repoPath: /Users/test/repo
|
|
82
|
+
source:feature-xyz
|
|
83
|
+
target:main
|
|
84
|
+
verification commands: ["npm test"]
|
|
85
|
+
reviewInstructions: "Check for breaking changes"
|
|
86
|
+
`;
|
|
87
|
+
|
|
88
|
+
const result = planWorkflowRequest(brief);
|
|
89
|
+
expect(result.status).toBe("draft_request");
|
|
90
|
+
|
|
91
|
+
if (result.status === "draft_request" && result.request.workflow === "pr-review-merge") {
|
|
92
|
+
expect(result.request.input.sourceBranch).toBe("feature-xyz");
|
|
93
|
+
expect(result.request.input.targetBranch).toBe("main");
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
it("requires explicit reviewInstructions", () => {
|
|
98
|
+
const brief = `
|
|
99
|
+
Pull request review:
|
|
100
|
+
repoPath: /opt/project
|
|
101
|
+
source branch: dev
|
|
102
|
+
target branch: staging
|
|
103
|
+
verification: ["make test"]
|
|
104
|
+
`;
|
|
105
|
+
|
|
106
|
+
const result = planWorkflowRequest(brief);
|
|
107
|
+
expect(result.status).toBe("needs_clarification");
|
|
108
|
+
|
|
109
|
+
if (result.status === "needs_clarification") {
|
|
110
|
+
expect(result.candidateWorkflow).toBe("pr-review-merge");
|
|
111
|
+
expect(result.missingFields).toContain("reviewInstructions");
|
|
112
|
+
expect(result.guidance.some(g => g.includes("reviewInstructions"))).toBe(true);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe("pr-review-merge missing fields", () => {
|
|
118
|
+
it("treats bare PR phrasing as a pr-review-merge request", () => {
|
|
119
|
+
const brief = `
|
|
120
|
+
Review the PR
|
|
121
|
+
repoPath: /repo
|
|
122
|
+
reviewInstructions: "Review carefully"
|
|
123
|
+
verification: ["test"]
|
|
124
|
+
`;
|
|
125
|
+
|
|
126
|
+
const result = planWorkflowRequest(brief);
|
|
127
|
+
expect(result.status).toBe("needs_clarification");
|
|
128
|
+
|
|
129
|
+
if (result.status === "needs_clarification") {
|
|
130
|
+
expect(result.candidateWorkflow).toBe("pr-review-merge");
|
|
131
|
+
expect(result.missingFields).toContain("sourceBranch");
|
|
132
|
+
expect(result.missingFields).toContain("targetBranch");
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it("flags missing repoPath", () => {
|
|
137
|
+
const brief = `
|
|
138
|
+
PR merge:
|
|
139
|
+
source branch: feature
|
|
140
|
+
target branch: main
|
|
141
|
+
reviewInstructions: "Review carefully"
|
|
142
|
+
verification: ["test"]
|
|
143
|
+
`;
|
|
144
|
+
|
|
145
|
+
const result = planWorkflowRequest(brief);
|
|
146
|
+
expect(result.status).toBe("needs_clarification");
|
|
147
|
+
|
|
148
|
+
if (result.status === "needs_clarification") {
|
|
149
|
+
expect(result.candidateWorkflow).toBe("pr-review-merge");
|
|
150
|
+
expect(result.missingFields).toContain("repoPath");
|
|
151
|
+
expect(Array.isArray(result.guidance)).toBe(true);
|
|
152
|
+
expect(result.guidance.some(g => g.includes("repoPath"))).toBe(true);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
it("flags missing sourceBranch", () => {
|
|
157
|
+
const brief = `
|
|
158
|
+
PR review:
|
|
159
|
+
repoPath: /repo
|
|
160
|
+
target branch: main
|
|
161
|
+
reviewInstructions: "Review carefully"
|
|
162
|
+
verification: ["test"]
|
|
163
|
+
`;
|
|
164
|
+
|
|
165
|
+
const result = planWorkflowRequest(brief);
|
|
166
|
+
expect(result.status).toBe("needs_clarification");
|
|
167
|
+
|
|
168
|
+
if (result.status === "needs_clarification") {
|
|
169
|
+
expect(result.candidateWorkflow).toBe("pr-review-merge");
|
|
170
|
+
expect(result.missingFields).toContain("sourceBranch");
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
it("flags missing targetBranch", () => {
|
|
175
|
+
const brief = `
|
|
176
|
+
PR merge:
|
|
177
|
+
repoPath: /repo
|
|
178
|
+
source branch: feature
|
|
179
|
+
reviewInstructions: "Review carefully"
|
|
180
|
+
verification: ["test"]
|
|
181
|
+
`;
|
|
182
|
+
|
|
183
|
+
const result = planWorkflowRequest(brief);
|
|
184
|
+
expect(result.status).toBe("needs_clarification");
|
|
185
|
+
|
|
186
|
+
if (result.status === "needs_clarification") {
|
|
187
|
+
expect(result.candidateWorkflow).toBe("pr-review-merge");
|
|
188
|
+
expect(result.missingFields).toContain("targetBranch");
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
it("flags missing verificationCommands", () => {
|
|
193
|
+
const brief = `
|
|
194
|
+
PR review:
|
|
195
|
+
repoPath: /repo
|
|
196
|
+
source branch: feature
|
|
197
|
+
target branch: main
|
|
198
|
+
reviewInstructions: "Review carefully"
|
|
199
|
+
`;
|
|
200
|
+
|
|
201
|
+
const result = planWorkflowRequest(brief);
|
|
202
|
+
expect(result.status).toBe("needs_clarification");
|
|
203
|
+
|
|
204
|
+
if (result.status === "needs_clarification") {
|
|
205
|
+
expect(result.candidateWorkflow).toBe("pr-review-merge");
|
|
206
|
+
expect(result.missingFields).toContain("verificationCommands");
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
describe("patch-validation happy path with branch", () => {
|
|
212
|
+
it("extracts all fields from complete brief", () => {
|
|
213
|
+
const brief = `
|
|
214
|
+
Patch validation workflow:
|
|
215
|
+
repoPath: /home/user/project
|
|
216
|
+
baseline: v1.2.3
|
|
217
|
+
candidate branch: bugfix-123
|
|
218
|
+
reproduce commands: ["npm run reproduce-bug"]
|
|
219
|
+
verification commands: ["npm test", "npm run integration"]
|
|
220
|
+
reviewInstructions: "Verify the bug is fixed"
|
|
221
|
+
approval required
|
|
222
|
+
`;
|
|
223
|
+
|
|
224
|
+
const result = planWorkflowRequest(brief);
|
|
225
|
+
expect(result.status).toBe("draft_request");
|
|
226
|
+
|
|
227
|
+
if (result.status === "draft_request") {
|
|
228
|
+
expect(result.workflow).toBe("patch-validation");
|
|
229
|
+
expect(result.request.workflow).toBe("patch-validation");
|
|
230
|
+
expect(Array.isArray(result.rationale)).toBe(true);
|
|
231
|
+
expect(result.rationale.length).toBeGreaterThan(0);
|
|
232
|
+
expect(result.rationale.some(r => r.includes("patch-validation"))).toBe(true);
|
|
233
|
+
expect(Array.isArray(result.warnings)).toBe(true);
|
|
234
|
+
|
|
235
|
+
if (result.request.workflow === "patch-validation") {
|
|
236
|
+
const input = result.request.input;
|
|
237
|
+
expect(input.repoPath).toBe("/home/user/project");
|
|
238
|
+
expect(input.baselineRef).toBe("v1.2.3");
|
|
239
|
+
expect(input.candidateSource.kind).toBe("branch");
|
|
240
|
+
expect(input.candidateSource.value).toBe("bugfix-123");
|
|
241
|
+
expect(input.reproduceCommands).toEqual(["npm run reproduce-bug"]);
|
|
242
|
+
expect(input.verificationCommands).toEqual(["npm test", "npm run integration"]);
|
|
243
|
+
expect(input.reviewInstructions).toBe("Verify the bug is fixed");
|
|
244
|
+
expect(input.approvalRequired).toBe(true);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
it("extracts colon-suffixed patch fields without requiring a space", () => {
|
|
250
|
+
const brief = `
|
|
251
|
+
Patch validation workflow:
|
|
252
|
+
repoPath: /home/user/project
|
|
253
|
+
baseline:main
|
|
254
|
+
candidate:fix-branch
|
|
255
|
+
reproduce commands: ["npm run reproduce-bug"]
|
|
256
|
+
verification commands: ["npm test"]
|
|
257
|
+
reviewInstructions: "Verify the bug is fixed"
|
|
258
|
+
`;
|
|
259
|
+
|
|
260
|
+
const result = planWorkflowRequest(brief);
|
|
261
|
+
expect(result.status).toBe("draft_request");
|
|
262
|
+
|
|
263
|
+
if (result.status === "draft_request" && result.request.workflow === "patch-validation") {
|
|
264
|
+
expect(result.request.input.baselineRef).toBe("main");
|
|
265
|
+
expect(result.request.input.candidateSource.kind).toBe("branch");
|
|
266
|
+
expect(result.request.input.candidateSource.value).toBe("fix-branch");
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
it("defaults approvalRequired to false when not specified", () => {
|
|
271
|
+
const brief = `
|
|
272
|
+
Patch validation:
|
|
273
|
+
repoPath: /project
|
|
274
|
+
baseline: main
|
|
275
|
+
candidate branch: fix-branch
|
|
276
|
+
reproduce: ["./run-bug.sh"]
|
|
277
|
+
verification: ["make test"]
|
|
278
|
+
reviewInstructions: "Validate the fix"
|
|
279
|
+
`;
|
|
280
|
+
|
|
281
|
+
const result = planWorkflowRequest(brief);
|
|
282
|
+
expect(result.status).toBe("draft_request");
|
|
283
|
+
|
|
284
|
+
if (result.status === "draft_request") {
|
|
285
|
+
expect(result.workflow).toBe("patch-validation");
|
|
286
|
+
expect(Array.isArray(result.rationale)).toBe(true);
|
|
287
|
+
expect(result.rationale.length).toBeGreaterThan(0);
|
|
288
|
+
expect(result.warnings.some(w => w.includes("approvalRequired"))).toBe(true);
|
|
289
|
+
|
|
290
|
+
if (result.request.workflow === "patch-validation") {
|
|
291
|
+
expect(result.request.input.approvalRequired).toBe(false);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
describe("patch-validation with patch file", () => {
|
|
298
|
+
it("extracts patchFile as candidate source", () => {
|
|
299
|
+
const brief = `
|
|
300
|
+
Validate this patch:
|
|
301
|
+
repoPath: /code/project
|
|
302
|
+
baseline: v2.0.0
|
|
303
|
+
candidate: /patches/fix.patch
|
|
304
|
+
reproduce commands: ["npm run fail"]
|
|
305
|
+
verification commands: ["npm test"]
|
|
306
|
+
reviewInstructions: "Validate the patch candidate"
|
|
307
|
+
`;
|
|
308
|
+
|
|
309
|
+
const result = planWorkflowRequest(brief);
|
|
310
|
+
expect(result.status).toBe("draft_request");
|
|
311
|
+
|
|
312
|
+
if (result.status === "draft_request") {
|
|
313
|
+
expect(result.workflow).toBe("patch-validation");
|
|
314
|
+
expect(Array.isArray(result.rationale)).toBe(true);
|
|
315
|
+
expect(result.rationale.length).toBeGreaterThan(0);
|
|
316
|
+
|
|
317
|
+
if (result.request.workflow === "patch-validation") {
|
|
318
|
+
const input = result.request.input;
|
|
319
|
+
expect(input.candidateSource.kind).toBe("patchFile");
|
|
320
|
+
expect(input.candidateSource.value).toBe("/patches/fix.patch");
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
it("detects .diff file extension", () => {
|
|
326
|
+
const brief = `
|
|
327
|
+
Patch validation:
|
|
328
|
+
repo: /app
|
|
329
|
+
baseline: stable
|
|
330
|
+
Apply change.diff
|
|
331
|
+
reproduce: ["test-bug"]
|
|
332
|
+
verify: ["test-suite"]
|
|
333
|
+
reviewInstructions: "Review the diff-based fix"
|
|
334
|
+
`;
|
|
335
|
+
|
|
336
|
+
const result = planWorkflowRequest(brief);
|
|
337
|
+
expect(result.status).toBe("draft_request");
|
|
338
|
+
|
|
339
|
+
if (result.status === "draft_request") {
|
|
340
|
+
expect(result.workflow).toBe("patch-validation");
|
|
341
|
+
expect(Array.isArray(result.rationale)).toBe(true);
|
|
342
|
+
expect(result.rationale.length).toBeGreaterThan(0);
|
|
343
|
+
|
|
344
|
+
if (result.request.workflow === "patch-validation") {
|
|
345
|
+
const input = result.request.input;
|
|
346
|
+
expect(input.candidateSource.kind).toBe("patchFile");
|
|
347
|
+
expect(input.candidateSource.value).toBe("change.diff");
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
describe("patch-validation missing fields", () => {
|
|
354
|
+
it("flags missing repoPath", () => {
|
|
355
|
+
const brief = `
|
|
356
|
+
Validate patch:
|
|
357
|
+
baseline: main
|
|
358
|
+
candidate branch: fix
|
|
359
|
+
reproduce: ["bug"]
|
|
360
|
+
verify: ["test"]
|
|
361
|
+
reviewInstructions: "Validate the fix"
|
|
362
|
+
`;
|
|
363
|
+
|
|
364
|
+
const result = planWorkflowRequest(brief);
|
|
365
|
+
expect(result.status).toBe("needs_clarification");
|
|
366
|
+
|
|
367
|
+
if (result.status === "needs_clarification") {
|
|
368
|
+
expect(result.candidateWorkflow).toBe("patch-validation");
|
|
369
|
+
expect(result.missingFields).toContain("repoPath");
|
|
370
|
+
}
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
it("flags missing baselineRef", () => {
|
|
374
|
+
const brief = `
|
|
375
|
+
Patch validation:
|
|
376
|
+
repoPath: /code
|
|
377
|
+
candidate branch: fix
|
|
378
|
+
reproduce: ["bug"]
|
|
379
|
+
verify: ["test"]
|
|
380
|
+
reviewInstructions: "Validate the fix"
|
|
381
|
+
`;
|
|
382
|
+
|
|
383
|
+
const result = planWorkflowRequest(brief);
|
|
384
|
+
expect(result.status).toBe("needs_clarification");
|
|
385
|
+
|
|
386
|
+
if (result.status === "needs_clarification") {
|
|
387
|
+
expect(result.candidateWorkflow).toBe("patch-validation");
|
|
388
|
+
expect(result.missingFields).toContain("baselineRef");
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
it("flags missing candidateSource", () => {
|
|
393
|
+
const brief = `
|
|
394
|
+
Validate this fix:
|
|
395
|
+
repoPath: /app
|
|
396
|
+
baseline: v1.0
|
|
397
|
+
reproduce: ["fail"]
|
|
398
|
+
verify: ["pass"]
|
|
399
|
+
reviewInstructions: "Validate the fix"
|
|
400
|
+
`;
|
|
401
|
+
|
|
402
|
+
const result = planWorkflowRequest(brief);
|
|
403
|
+
expect(result.status).toBe("needs_clarification");
|
|
404
|
+
|
|
405
|
+
if (result.status === "needs_clarification") {
|
|
406
|
+
expect(result.candidateWorkflow).toBe("patch-validation");
|
|
407
|
+
expect(result.missingFields).toContain("candidateSource (branch or patchFile)");
|
|
408
|
+
}
|
|
409
|
+
});
|
|
410
|
+
|
|
411
|
+
it("flags missing reproduceCommands", () => {
|
|
412
|
+
const brief = `
|
|
413
|
+
Patch validation:
|
|
414
|
+
repoPath: /code
|
|
415
|
+
baseline: main
|
|
416
|
+
candidate branch: fix
|
|
417
|
+
verify: ["test"]
|
|
418
|
+
reviewInstructions: "Validate the fix"
|
|
419
|
+
`;
|
|
420
|
+
|
|
421
|
+
const result = planWorkflowRequest(brief);
|
|
422
|
+
expect(result.status).toBe("needs_clarification");
|
|
423
|
+
|
|
424
|
+
if (result.status === "needs_clarification") {
|
|
425
|
+
expect(result.candidateWorkflow).toBe("patch-validation");
|
|
426
|
+
expect(result.missingFields).toContain("reproduceCommands");
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
it("flags missing verificationCommands", () => {
|
|
431
|
+
const brief = `
|
|
432
|
+
Validate patch:
|
|
433
|
+
repoPath: /code
|
|
434
|
+
baseline: main
|
|
435
|
+
candidate branch: fix
|
|
436
|
+
reproduce: ["bug"]
|
|
437
|
+
reviewInstructions: "Validate the fix"
|
|
438
|
+
`;
|
|
439
|
+
|
|
440
|
+
const result = planWorkflowRequest(brief);
|
|
441
|
+
expect(result.status).toBe("needs_clarification");
|
|
442
|
+
|
|
443
|
+
if (result.status === "needs_clarification") {
|
|
444
|
+
expect(result.candidateWorkflow).toBe("patch-validation");
|
|
445
|
+
expect(result.missingFields).toContain("verificationCommands");
|
|
446
|
+
}
|
|
447
|
+
});
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
describe("ambiguous briefs", () => {
|
|
451
|
+
it("rejects brief with both PR and patch signals", () => {
|
|
452
|
+
const brief = `
|
|
453
|
+
Review this PR and validate the patch:
|
|
454
|
+
source branch: feature
|
|
455
|
+
baseline: main
|
|
456
|
+
candidate: fix.patch
|
|
457
|
+
`;
|
|
458
|
+
|
|
459
|
+
const result = planWorkflowRequest(brief);
|
|
460
|
+
expect(result.status).toBe("needs_clarification");
|
|
461
|
+
|
|
462
|
+
if (result.status === "needs_clarification") {
|
|
463
|
+
expect(result.reasons[0]).toContain("Could not determine workflow type");
|
|
464
|
+
expect(result.missingFields).toContain("workflow type");
|
|
465
|
+
expect(result.candidateWorkflow).toBeUndefined();
|
|
466
|
+
expect(Array.isArray(result.guidance)).toBe(true);
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
|
|
470
|
+
it("accepts brief with no workflow signals as custom", () => {
|
|
471
|
+
const brief = `
|
|
472
|
+
repoPath: /code/app
|
|
473
|
+
Do some work on the codebase
|
|
474
|
+
`;
|
|
475
|
+
|
|
476
|
+
const result = planWorkflowRequest(brief);
|
|
477
|
+
expect(result.status).toBe("draft_request");
|
|
478
|
+
|
|
479
|
+
if (result.status === "draft_request") {
|
|
480
|
+
expect(result.workflow).toBe("custom");
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
it("accepts vague request as custom", () => {
|
|
485
|
+
const brief = "Check the code";
|
|
486
|
+
|
|
487
|
+
const result = planWorkflowRequest(brief);
|
|
488
|
+
expect(result.status).toBe("draft_request");
|
|
489
|
+
|
|
490
|
+
if (result.status === "draft_request") {
|
|
491
|
+
expect(result.workflow).toBe("custom");
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
describe("approval flag extraction", () => {
|
|
497
|
+
it("detects 'approval required' phrase", () => {
|
|
498
|
+
const brief = `
|
|
499
|
+
Patch validation with approval required:
|
|
500
|
+
repo: /app
|
|
501
|
+
baseline: v1
|
|
502
|
+
candidate branch: fix
|
|
503
|
+
reproduce: ["bug"]
|
|
504
|
+
verify: ["test"]
|
|
505
|
+
reviewInstructions: "Validate the fix"
|
|
506
|
+
`;
|
|
507
|
+
|
|
508
|
+
const result = planWorkflowRequest(brief);
|
|
509
|
+
if (result.status === "draft_request" && result.request.workflow === "patch-validation") {
|
|
510
|
+
expect(result.request.input.approvalRequired).toBe(true);
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
it("detects 'approvalRequired: true' format", () => {
|
|
515
|
+
const brief = `
|
|
516
|
+
Validate patch:
|
|
517
|
+
repoPath: /code
|
|
518
|
+
baseline: stable
|
|
519
|
+
candidate branch: hotfix
|
|
520
|
+
reproduce: ["fail-test"]
|
|
521
|
+
verify: ["pass-test"]
|
|
522
|
+
reviewInstructions: "Validate the hotfix"
|
|
523
|
+
approvalRequired: true
|
|
524
|
+
`;
|
|
525
|
+
|
|
526
|
+
const result = planWorkflowRequest(brief);
|
|
527
|
+
if (result.status === "draft_request" && result.request.workflow === "patch-validation") {
|
|
528
|
+
expect(result.request.input.approvalRequired).toBe(true);
|
|
529
|
+
}
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
it("detects 'no approval' phrase", () => {
|
|
533
|
+
const brief = `
|
|
534
|
+
Patch validation, no approval needed:
|
|
535
|
+
repo: /app
|
|
536
|
+
baseline: v2
|
|
537
|
+
candidate branch: auto-fix
|
|
538
|
+
reproduce: ["bug"]
|
|
539
|
+
verify: ["test"]
|
|
540
|
+
reviewInstructions: "Validate the automatic fix"
|
|
541
|
+
`;
|
|
542
|
+
|
|
543
|
+
const result = planWorkflowRequest(brief);
|
|
544
|
+
if (result.status === "draft_request" && result.request.workflow === "patch-validation") {
|
|
545
|
+
expect(result.request.input.approvalRequired).toBe(false);
|
|
546
|
+
}
|
|
547
|
+
});
|
|
548
|
+
|
|
549
|
+
it("detects 'approvalRequired: false' format", () => {
|
|
550
|
+
const brief = `
|
|
551
|
+
Validate:
|
|
552
|
+
repoPath: /src
|
|
553
|
+
baseline: main
|
|
554
|
+
candidate branch: fix
|
|
555
|
+
reproduce: ["fail"]
|
|
556
|
+
verify: ["pass"]
|
|
557
|
+
reviewInstructions: "Validate the fix"
|
|
558
|
+
approvalRequired: false
|
|
559
|
+
`;
|
|
560
|
+
|
|
561
|
+
const result = planWorkflowRequest(brief);
|
|
562
|
+
if (result.status === "draft_request" && result.request.workflow === "patch-validation") {
|
|
563
|
+
expect(result.request.input.approvalRequired).toBe(false);
|
|
564
|
+
}
|
|
565
|
+
});
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
describe("request envelope compatibility", () => {
|
|
569
|
+
it("returns ReferenceWorkflowRequest shape for pr-review-merge", () => {
|
|
570
|
+
const brief = `
|
|
571
|
+
PR review:
|
|
572
|
+
repoPath: /test
|
|
573
|
+
source branch: feat
|
|
574
|
+
target branch: main
|
|
575
|
+
reviewInstructions: "Review the feature branch"
|
|
576
|
+
verify: ["test"]
|
|
577
|
+
`;
|
|
578
|
+
|
|
579
|
+
const result = planWorkflowRequest(brief);
|
|
580
|
+
expect(result.status).toBe("draft_request");
|
|
581
|
+
|
|
582
|
+
if (result.status === "draft_request") {
|
|
583
|
+
expect(result.workflow).toBe("pr-review-merge");
|
|
584
|
+
expect(result.request).toHaveProperty("workflow");
|
|
585
|
+
expect(result.request).toHaveProperty("input");
|
|
586
|
+
expect(result.request.workflow).toBe("pr-review-merge");
|
|
587
|
+
expect(Array.isArray(result.rationale)).toBe(true);
|
|
588
|
+
expect(result.rationale.length).toBeGreaterThan(0);
|
|
589
|
+
expect(Array.isArray(result.warnings)).toBe(true);
|
|
590
|
+
|
|
591
|
+
if (result.request.workflow === "pr-review-merge") {
|
|
592
|
+
const input = result.request.input;
|
|
593
|
+
expect(input).toHaveProperty("repoPath");
|
|
594
|
+
expect(input).toHaveProperty("sourceBranch");
|
|
595
|
+
expect(input).toHaveProperty("targetBranch");
|
|
596
|
+
expect(input).toHaveProperty("reviewInstructions");
|
|
597
|
+
expect(input).toHaveProperty("verificationCommands");
|
|
598
|
+
expect(Array.isArray(input.verificationCommands)).toBe(true);
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
});
|
|
602
|
+
|
|
603
|
+
it("returns ReferenceWorkflowRequest shape for patch-validation", () => {
|
|
604
|
+
const brief = `
|
|
605
|
+
Validate patch:
|
|
606
|
+
repo: /app
|
|
607
|
+
baseline: v1
|
|
608
|
+
candidate branch: fix
|
|
609
|
+
reproduce: ["bug"]
|
|
610
|
+
verify: ["test"]
|
|
611
|
+
reviewInstructions: "Validate the candidate"
|
|
612
|
+
`;
|
|
613
|
+
|
|
614
|
+
const result = planWorkflowRequest(brief);
|
|
615
|
+
expect(result.status).toBe("draft_request");
|
|
616
|
+
|
|
617
|
+
if (result.status === "draft_request") {
|
|
618
|
+
expect(result.workflow).toBe("patch-validation");
|
|
619
|
+
expect(result.request).toHaveProperty("workflow");
|
|
620
|
+
expect(result.request).toHaveProperty("input");
|
|
621
|
+
expect(result.request.workflow).toBe("patch-validation");
|
|
622
|
+
expect(Array.isArray(result.rationale)).toBe(true);
|
|
623
|
+
expect(result.rationale.length).toBeGreaterThan(0);
|
|
624
|
+
expect(Array.isArray(result.warnings)).toBe(true);
|
|
625
|
+
|
|
626
|
+
if (result.request.workflow === "patch-validation") {
|
|
627
|
+
const input = result.request.input;
|
|
628
|
+
expect(input).toHaveProperty("repoPath");
|
|
629
|
+
expect(input).toHaveProperty("baselineRef");
|
|
630
|
+
expect(input).toHaveProperty("candidateSource");
|
|
631
|
+
expect(input.candidateSource).toHaveProperty("kind");
|
|
632
|
+
expect(input.candidateSource).toHaveProperty("value");
|
|
633
|
+
expect(input).toHaveProperty("reproduceCommands");
|
|
634
|
+
expect(input).toHaveProperty("verificationCommands");
|
|
635
|
+
expect(input).toHaveProperty("reviewInstructions");
|
|
636
|
+
expect(input).toHaveProperty("approvalRequired");
|
|
637
|
+
expect(Array.isArray(input.reproduceCommands)).toBe(true);
|
|
638
|
+
expect(Array.isArray(input.verificationCommands)).toBe(true);
|
|
639
|
+
expect(typeof input.approvalRequired).toBe("boolean");
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
});
|
|
643
|
+
});
|
|
644
|
+
|
|
645
|
+
describe("skill-markdown at planner level", () => {
|
|
646
|
+
it("should handle PR review skill markdown with normalization", () => {
|
|
647
|
+
const skillMarkdown = `
|
|
648
|
+
# PR Review Workflow
|
|
649
|
+
|
|
650
|
+
workflow: pr-review-merge
|
|
651
|
+
|
|
652
|
+
## Inputs
|
|
653
|
+
- repoPath: /Users/test/repo
|
|
654
|
+
- sourceBranch: feature
|
|
655
|
+
- targetBranch: main
|
|
656
|
+
- reviewInstructions: Check code quality
|
|
657
|
+
- verificationCommands: [npm test, npm run lint]
|
|
658
|
+
|
|
659
|
+
## Verification
|
|
660
|
+
- npm run e2e
|
|
661
|
+
`;
|
|
662
|
+
|
|
663
|
+
const result = planWorkflowRequest(skillMarkdown);
|
|
664
|
+
expect(result.status).toBe("draft_request");
|
|
665
|
+
|
|
666
|
+
if (result.status === "draft_request" && result.request.workflow === "pr-review-merge") {
|
|
667
|
+
const input = result.request.input;
|
|
668
|
+
expect(input.repoPath).toBe("/Users/test/repo");
|
|
669
|
+
expect(input.sourceBranch).toBe("feature");
|
|
670
|
+
expect(input.targetBranch).toBe("main");
|
|
671
|
+
expect(input.reviewInstructions).toBe("Check code quality");
|
|
672
|
+
// Should be normalized to array including both inputs and verification section
|
|
673
|
+
expect(Array.isArray(input.verificationCommands)).toBe(true);
|
|
674
|
+
expect(input.verificationCommands).toEqual(["npm test", "npm run lint", "npm run e2e"]);
|
|
675
|
+
}
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
it("should handle patch validation skill markdown with array and boolean normalization", () => {
|
|
679
|
+
const skillMarkdown = `
|
|
680
|
+
# Patch Validation Workflow
|
|
681
|
+
|
|
682
|
+
workflow: patch-validation
|
|
683
|
+
|
|
684
|
+
## Inputs
|
|
685
|
+
- repoPath: /home/user/project
|
|
686
|
+
- baselineRef: v1.0.0
|
|
687
|
+
- candidateBranch: fix-branch
|
|
688
|
+
- reproduceCommands: [npm run fail-test, node reproduce.js]
|
|
689
|
+
- verificationCommands: [npm test]
|
|
690
|
+
- reviewInstructions: Validate the fix
|
|
691
|
+
- approvalRequired: true
|
|
692
|
+
|
|
693
|
+
## Verification
|
|
694
|
+
- npm run integration
|
|
695
|
+
`;
|
|
696
|
+
|
|
697
|
+
const result = planWorkflowRequest(skillMarkdown);
|
|
698
|
+
expect(result.status).toBe("draft_request");
|
|
699
|
+
|
|
700
|
+
if (result.status === "draft_request" && result.request.workflow === "patch-validation") {
|
|
701
|
+
const input = result.request.input;
|
|
702
|
+
expect(input.repoPath).toBe("/home/user/project");
|
|
703
|
+
expect(input.baselineRef).toBe("v1.0.0");
|
|
704
|
+
expect(input.candidateSource.kind).toBe("branch");
|
|
705
|
+
expect(input.candidateSource.value).toBe("fix-branch");
|
|
706
|
+
// Arrays should be normalized from string representation
|
|
707
|
+
expect(Array.isArray(input.reproduceCommands)).toBe(true);
|
|
708
|
+
expect(input.reproduceCommands).toEqual(["npm run fail-test", "node reproduce.js"]);
|
|
709
|
+
expect(Array.isArray(input.verificationCommands)).toBe(true);
|
|
710
|
+
expect(input.verificationCommands).toEqual(["npm test", "npm run integration"]);
|
|
711
|
+
// Boolean should be normalized from string "true"
|
|
712
|
+
expect(typeof input.approvalRequired).toBe("boolean");
|
|
713
|
+
expect(input.approvalRequired).toBe(true);
|
|
714
|
+
}
|
|
715
|
+
});
|
|
716
|
+
|
|
717
|
+
it("should populate verificationCommands from Verification section when not in inputs", () => {
|
|
718
|
+
const skillMarkdown = `
|
|
719
|
+
# Patch Validation Workflow
|
|
720
|
+
|
|
721
|
+
workflow: patch-validation
|
|
722
|
+
|
|
723
|
+
## Inputs
|
|
724
|
+
- repoPath: /home/user/project
|
|
725
|
+
- baselineRef: main
|
|
726
|
+
- candidateBranch: fix
|
|
727
|
+
- reproduceCommands: [npm run fail]
|
|
728
|
+
- reviewInstructions: Check fix
|
|
729
|
+
|
|
730
|
+
## Verification
|
|
731
|
+
- npm test
|
|
732
|
+
- npm run integration
|
|
733
|
+
`;
|
|
734
|
+
|
|
735
|
+
const result = planWorkflowRequest(skillMarkdown);
|
|
736
|
+
expect(result.status).toBe("draft_request");
|
|
737
|
+
|
|
738
|
+
if (result.status === "draft_request" && result.request.workflow === "patch-validation") {
|
|
739
|
+
const input = result.request.input;
|
|
740
|
+
// Verification section should populate verificationCommands
|
|
741
|
+
expect(Array.isArray(input.verificationCommands)).toBe(true);
|
|
742
|
+
expect(input.verificationCommands).toEqual(["npm test", "npm run integration"]);
|
|
743
|
+
}
|
|
744
|
+
});
|
|
745
|
+
});
|
|
746
|
+
|
|
747
|
+
describe("empty command array validation", () => {
|
|
748
|
+
it("should reject PR review when verificationCommands is empty array", () => {
|
|
749
|
+
const skillMarkdown = `
|
|
750
|
+
# PR Review Workflow
|
|
751
|
+
|
|
752
|
+
workflow: pr-review-merge
|
|
753
|
+
|
|
754
|
+
## Inputs
|
|
755
|
+
- repoPath: /Users/test/repo
|
|
756
|
+
- sourceBranch: feature
|
|
757
|
+
- targetBranch: main
|
|
758
|
+
- reviewInstructions: Check code quality
|
|
759
|
+
- verificationCommands: []
|
|
760
|
+
`;
|
|
761
|
+
|
|
762
|
+
const result = planWorkflowRequest(skillMarkdown);
|
|
763
|
+
expect(result.status).toBe("needs_clarification");
|
|
764
|
+
|
|
765
|
+
if (result.status === "needs_clarification") {
|
|
766
|
+
expect(result.missingFields).toContain("verificationCommands");
|
|
767
|
+
expect(result.candidateWorkflow).toBe("pr-review-merge");
|
|
768
|
+
}
|
|
769
|
+
});
|
|
770
|
+
|
|
771
|
+
it("should reject patch validation when reproduceCommands is empty array", () => {
|
|
772
|
+
const skillMarkdown = `
|
|
773
|
+
# Patch Validation
|
|
774
|
+
|
|
775
|
+
workflow: patch-validation
|
|
776
|
+
|
|
777
|
+
## Inputs
|
|
778
|
+
- repoPath: /home/user/project
|
|
779
|
+
- baselineRef: main
|
|
780
|
+
- candidateBranch: fix-branch
|
|
781
|
+
- reproduceCommands: []
|
|
782
|
+
- verificationCommands: [npm test]
|
|
783
|
+
- reviewInstructions: Validate fix
|
|
784
|
+
`;
|
|
785
|
+
|
|
786
|
+
const result = planWorkflowRequest(skillMarkdown);
|
|
787
|
+
expect(result.status).toBe("needs_clarification");
|
|
788
|
+
|
|
789
|
+
if (result.status === "needs_clarification") {
|
|
790
|
+
expect(result.missingFields).toContain("reproduceCommands");
|
|
791
|
+
expect(result.candidateWorkflow).toBe("patch-validation");
|
|
792
|
+
}
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
it("should reject patch validation when verificationCommands is empty array", () => {
|
|
796
|
+
const skillMarkdown = `
|
|
797
|
+
# Patch Validation
|
|
798
|
+
|
|
799
|
+
workflow: patch-validation
|
|
800
|
+
|
|
801
|
+
## Inputs
|
|
802
|
+
- repoPath: /home/user/project
|
|
803
|
+
- baselineRef: main
|
|
804
|
+
- candidateBranch: fix-branch
|
|
805
|
+
- reproduceCommands: [npm run fail]
|
|
806
|
+
- verificationCommands: []
|
|
807
|
+
- reviewInstructions: Validate fix
|
|
808
|
+
`;
|
|
809
|
+
|
|
810
|
+
const result = planWorkflowRequest(skillMarkdown);
|
|
811
|
+
expect(result.status).toBe("needs_clarification");
|
|
812
|
+
|
|
813
|
+
if (result.status === "needs_clarification") {
|
|
814
|
+
expect(result.missingFields).toContain("verificationCommands");
|
|
815
|
+
expect(result.candidateWorkflow).toBe("patch-validation");
|
|
816
|
+
}
|
|
817
|
+
});
|
|
818
|
+
});
|
|
819
|
+
|
|
820
|
+
describe("commands with commas in quoted strings", () => {
|
|
821
|
+
it("should preserve commas inside quoted command strings", () => {
|
|
822
|
+
const skillMarkdown = `
|
|
823
|
+
# PR Review Workflow
|
|
824
|
+
|
|
825
|
+
workflow: pr-review-merge
|
|
826
|
+
|
|
827
|
+
## Inputs
|
|
828
|
+
- repoPath: /Users/test/repo
|
|
829
|
+
- sourceBranch: feature
|
|
830
|
+
- targetBranch: main
|
|
831
|
+
- reviewInstructions: Check code quality
|
|
832
|
+
- verificationCommands: ["echo 'hello, world'", "npm test --reporter 'json, summary'"]
|
|
833
|
+
`;
|
|
834
|
+
|
|
835
|
+
const result = planWorkflowRequest(skillMarkdown);
|
|
836
|
+
expect(result.status).toBe("draft_request");
|
|
837
|
+
|
|
838
|
+
if (result.status === "draft_request" && result.request.workflow === "pr-review-merge") {
|
|
839
|
+
const input = result.request.input;
|
|
840
|
+
expect(input.verificationCommands).toEqual([
|
|
841
|
+
"echo 'hello, world'",
|
|
842
|
+
"npm test --reporter 'json, summary'"
|
|
843
|
+
]);
|
|
844
|
+
}
|
|
845
|
+
});
|
|
846
|
+
|
|
847
|
+
it("should handle commands with double-quoted strings containing commas", () => {
|
|
848
|
+
const skillMarkdown = `
|
|
849
|
+
# Patch Validation
|
|
850
|
+
|
|
851
|
+
workflow: patch-validation
|
|
852
|
+
|
|
853
|
+
## Inputs
|
|
854
|
+
- repoPath: /home/user/project
|
|
855
|
+
- baselineRef: main
|
|
856
|
+
- candidateBranch: fix-branch
|
|
857
|
+
- reproduceCommands: ["node test.js --data \\"a, b, c\\"", "npm run fail"]
|
|
858
|
+
- verificationCommands: ["npm test"]
|
|
859
|
+
- reviewInstructions: Validate fix
|
|
860
|
+
`;
|
|
861
|
+
|
|
862
|
+
const result = planWorkflowRequest(skillMarkdown);
|
|
863
|
+
expect(result.status).toBe("draft_request");
|
|
864
|
+
|
|
865
|
+
if (result.status === "draft_request" && result.request.workflow === "patch-validation") {
|
|
866
|
+
const input = result.request.input;
|
|
867
|
+
expect(input.reproduceCommands).toEqual([
|
|
868
|
+
'node test.js --data "a, b, c"',
|
|
869
|
+
"npm run fail"
|
|
870
|
+
]);
|
|
871
|
+
}
|
|
872
|
+
});
|
|
873
|
+
});
|
|
874
|
+
|
|
875
|
+
describe("single command string normalization", () => {
|
|
876
|
+
it("should normalize quoted single command strings into arrays", () => {
|
|
877
|
+
const skillMarkdown = `
|
|
878
|
+
# Patch Validation
|
|
879
|
+
|
|
880
|
+
workflow: patch-validation
|
|
881
|
+
|
|
882
|
+
## Inputs
|
|
883
|
+
- repoPath: /home/user/project
|
|
884
|
+
- baselineRef: main
|
|
885
|
+
- candidateBranch: fix-branch
|
|
886
|
+
- reproduceCommands: "npm run fail"
|
|
887
|
+
- verificationCommands: 'npm test'
|
|
888
|
+
- reviewInstructions: "Validate fix"
|
|
889
|
+
`;
|
|
890
|
+
|
|
891
|
+
const result = planWorkflowRequest(skillMarkdown);
|
|
892
|
+
expect(result.status).toBe("draft_request");
|
|
893
|
+
|
|
894
|
+
if (result.status === "draft_request" && result.request.workflow === "patch-validation") {
|
|
895
|
+
expect(result.request.input.reproduceCommands).toEqual(["npm run fail"]);
|
|
896
|
+
expect(result.request.input.verificationCommands).toEqual(["npm test"]);
|
|
897
|
+
expect(result.request.input.reviewInstructions).toBe("Validate fix");
|
|
898
|
+
}
|
|
899
|
+
});
|
|
900
|
+
});
|
|
901
|
+
});
|