@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,370 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { buildTaskGraph } from "../../src/synthesis/graph-builder.js";
|
|
3
|
+
import type { IntentIR } from "../../src/synthesis/intent-ir.js";
|
|
4
|
+
|
|
5
|
+
describe("graph-builder", () => {
|
|
6
|
+
describe("buildTaskGraph", () => {
|
|
7
|
+
describe("patch-validation family", () => {
|
|
8
|
+
it("should build graph for patch validation with branch", () => {
|
|
9
|
+
const intent: IntentIR = {
|
|
10
|
+
family: "patch-validation",
|
|
11
|
+
goal: "Validate patch against baseline",
|
|
12
|
+
inputs: {
|
|
13
|
+
repoPath: "/path/to/repo",
|
|
14
|
+
baselineRef: "v1.0.0",
|
|
15
|
+
candidateBranch: "fix-branch",
|
|
16
|
+
reproduceCommands: ["npm run fail-test"],
|
|
17
|
+
verificationCommands: ["npm test"],
|
|
18
|
+
reviewInstructions: "Check fix"
|
|
19
|
+
},
|
|
20
|
+
requiredTools: ["git"],
|
|
21
|
+
humanCheckpoints: [],
|
|
22
|
+
verificationTargets: ["npm test"]
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const graph = buildTaskGraph(intent);
|
|
26
|
+
|
|
27
|
+
expect(graph.family).toBe("patch-validation");
|
|
28
|
+
expect(graph.stages).toHaveLength(5);
|
|
29
|
+
expect(graph.stages.map(s => s.type)).toEqual([
|
|
30
|
+
"setup",
|
|
31
|
+
"reproduce",
|
|
32
|
+
"apply",
|
|
33
|
+
"verify",
|
|
34
|
+
"review"
|
|
35
|
+
]);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it("should add approval stage when approval checkpoint is present", () => {
|
|
39
|
+
const intent: IntentIR = {
|
|
40
|
+
family: "patch-validation",
|
|
41
|
+
goal: "Validate patch against baseline",
|
|
42
|
+
inputs: {
|
|
43
|
+
repoPath: "/path/to/repo",
|
|
44
|
+
baselineRef: "v1.0.0",
|
|
45
|
+
candidateBranch: "fix-branch",
|
|
46
|
+
reproduceCommands: ["npm run fail-test"],
|
|
47
|
+
verificationCommands: ["npm test"],
|
|
48
|
+
reviewInstructions: "Check fix",
|
|
49
|
+
approvalRequired: true
|
|
50
|
+
},
|
|
51
|
+
requiredTools: ["git"],
|
|
52
|
+
humanCheckpoints: ["approval-gate"],
|
|
53
|
+
verificationTargets: ["npm test"]
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
const graph = buildTaskGraph(intent);
|
|
57
|
+
|
|
58
|
+
expect(graph.stages).toHaveLength(6);
|
|
59
|
+
expect(graph.stages[5].type).toBe("approval");
|
|
60
|
+
expect(graph.stages[5].id).toBe("approval-gate");
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("should handle patch file candidate source", () => {
|
|
64
|
+
const intent: IntentIR = {
|
|
65
|
+
family: "patch-validation",
|
|
66
|
+
goal: "Validate patch against baseline",
|
|
67
|
+
inputs: {
|
|
68
|
+
repoPath: "/path/to/repo",
|
|
69
|
+
baselineRef: "main",
|
|
70
|
+
patchFilePath: "fix.patch",
|
|
71
|
+
reproduceCommands: ["npm run fail-test"],
|
|
72
|
+
verificationCommands: ["npm test"],
|
|
73
|
+
reviewInstructions: "Check fix"
|
|
74
|
+
},
|
|
75
|
+
requiredTools: ["git"],
|
|
76
|
+
humanCheckpoints: [],
|
|
77
|
+
verificationTargets: ["npm test"]
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const graph = buildTaskGraph(intent);
|
|
81
|
+
|
|
82
|
+
expect(graph.family).toBe("patch-validation");
|
|
83
|
+
expect(graph.inputs.patchFilePath).toBe("fix.patch");
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
describe("pr-review-merge family", () => {
|
|
88
|
+
it("should build graph for PR review and merge", () => {
|
|
89
|
+
const intent: IntentIR = {
|
|
90
|
+
family: "pr-review-merge",
|
|
91
|
+
goal: "Review and merge PR",
|
|
92
|
+
inputs: {
|
|
93
|
+
repoPath: "/path/to/repo",
|
|
94
|
+
sourceBranch: "feature",
|
|
95
|
+
targetBranch: "main",
|
|
96
|
+
reviewInstructions: "Check security",
|
|
97
|
+
verificationCommands: ["npm test", "npm run lint"]
|
|
98
|
+
},
|
|
99
|
+
requiredTools: ["git"],
|
|
100
|
+
humanCheckpoints: [],
|
|
101
|
+
verificationTargets: ["npm test", "npm run lint"]
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const graph = buildTaskGraph(intent);
|
|
105
|
+
|
|
106
|
+
expect(graph.family).toBe("pr-review-merge");
|
|
107
|
+
expect(graph.stages).toHaveLength(4);
|
|
108
|
+
expect(graph.stages.map(s => s.type)).toEqual([
|
|
109
|
+
"setup",
|
|
110
|
+
"review",
|
|
111
|
+
"verify",
|
|
112
|
+
"merge"
|
|
113
|
+
]);
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
it("should maintain stage dependencies correctly", () => {
|
|
117
|
+
const intent: IntentIR = {
|
|
118
|
+
family: "pr-review-merge",
|
|
119
|
+
goal: "Review and merge PR",
|
|
120
|
+
inputs: {
|
|
121
|
+
repoPath: "/path/to/repo",
|
|
122
|
+
sourceBranch: "feature",
|
|
123
|
+
targetBranch: "main",
|
|
124
|
+
reviewInstructions: "Check code",
|
|
125
|
+
verificationCommands: ["npm test"]
|
|
126
|
+
},
|
|
127
|
+
requiredTools: ["git"],
|
|
128
|
+
humanCheckpoints: [],
|
|
129
|
+
verificationTargets: ["npm test"]
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const graph = buildTaskGraph(intent);
|
|
133
|
+
|
|
134
|
+
expect(graph.stages[0].dependencies).toEqual([]);
|
|
135
|
+
expect(graph.stages[1].dependencies).toEqual(["setup-pr"]);
|
|
136
|
+
expect(graph.stages[2].dependencies).toEqual(["review-changes"]);
|
|
137
|
+
expect(graph.stages[3].dependencies).toEqual(["verify-tests"]);
|
|
138
|
+
});
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
describe("steps-based graph building", () => {
|
|
142
|
+
it("should build graph nodes from intent.steps", () => {
|
|
143
|
+
const intent: IntentIR = {
|
|
144
|
+
family: "patch-validation",
|
|
145
|
+
goal: "Validate patch",
|
|
146
|
+
inputs: {
|
|
147
|
+
repoPath: "/repo",
|
|
148
|
+
baselineRef: "main",
|
|
149
|
+
candidateBranch: "fix",
|
|
150
|
+
reproduceCommands: ["npm run fail"],
|
|
151
|
+
verificationCommands: ["npm test"],
|
|
152
|
+
reviewInstructions: "Check"
|
|
153
|
+
},
|
|
154
|
+
requiredTools: ["git"],
|
|
155
|
+
humanCheckpoints: [],
|
|
156
|
+
verificationTargets: ["npm test"],
|
|
157
|
+
steps: [
|
|
158
|
+
{ id: "step-1", label: "Install deps", kind: "tool" },
|
|
159
|
+
{ id: "step-2", label: "Run build", kind: "tool" }
|
|
160
|
+
]
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const graph = buildTaskGraph(intent);
|
|
164
|
+
|
|
165
|
+
const stepNodes = graph.stages.filter(s => s.id.startsWith("step-"));
|
|
166
|
+
expect(stepNodes).toHaveLength(2);
|
|
167
|
+
expect(stepNodes[0].id).toBe("step-1");
|
|
168
|
+
expect(stepNodes[0].description).toBe("Install deps");
|
|
169
|
+
expect(stepNodes[1].id).toBe("step-2");
|
|
170
|
+
expect(stepNodes[1].description).toBe("Run build");
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
it("should connect steps sequentially", () => {
|
|
174
|
+
const intent: IntentIR = {
|
|
175
|
+
family: "patch-validation",
|
|
176
|
+
goal: "Validate patch",
|
|
177
|
+
inputs: {
|
|
178
|
+
repoPath: "/repo",
|
|
179
|
+
baselineRef: "main",
|
|
180
|
+
candidateBranch: "fix",
|
|
181
|
+
reproduceCommands: ["npm run fail"],
|
|
182
|
+
verificationCommands: ["npm test"],
|
|
183
|
+
reviewInstructions: "Check"
|
|
184
|
+
},
|
|
185
|
+
requiredTools: ["git"],
|
|
186
|
+
humanCheckpoints: [],
|
|
187
|
+
verificationTargets: ["npm test"],
|
|
188
|
+
steps: [
|
|
189
|
+
{ id: "step-1", label: "First", kind: "tool" },
|
|
190
|
+
{ id: "step-2", label: "Second", kind: "tool" },
|
|
191
|
+
{ id: "step-3", label: "Third", kind: "tool" }
|
|
192
|
+
]
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const graph = buildTaskGraph(intent);
|
|
196
|
+
|
|
197
|
+
const step1 = graph.stages.find(s => s.id === "step-1")!;
|
|
198
|
+
const step2 = graph.stages.find(s => s.id === "step-2")!;
|
|
199
|
+
const step3 = graph.stages.find(s => s.id === "step-3")!;
|
|
200
|
+
|
|
201
|
+
expect(step1.dependencies).toEqual([]);
|
|
202
|
+
expect(step2.dependencies).toEqual(["step-1"]);
|
|
203
|
+
expect(step3.dependencies).toEqual(["step-2"]);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it("should include workflow stages alongside step nodes", () => {
|
|
207
|
+
const intent: IntentIR = {
|
|
208
|
+
family: "patch-validation",
|
|
209
|
+
goal: "Validate patch",
|
|
210
|
+
inputs: {
|
|
211
|
+
repoPath: "/repo",
|
|
212
|
+
baselineRef: "main",
|
|
213
|
+
candidateBranch: "fix",
|
|
214
|
+
reproduceCommands: ["npm run fail"],
|
|
215
|
+
verificationCommands: ["npm test"],
|
|
216
|
+
reviewInstructions: "Check"
|
|
217
|
+
},
|
|
218
|
+
requiredTools: ["git"],
|
|
219
|
+
humanCheckpoints: [],
|
|
220
|
+
verificationTargets: ["npm test"],
|
|
221
|
+
steps: [
|
|
222
|
+
{ id: "step-1", label: "Custom step", kind: "tool" }
|
|
223
|
+
]
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
const graph = buildTaskGraph(intent);
|
|
227
|
+
|
|
228
|
+
expect(graph.stages.find(s => s.id === "setup-baseline")).toBeDefined();
|
|
229
|
+
expect(graph.stages.find(s => s.id === "step-1")).toBeDefined();
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("should add verification nodes from verificationTargets", () => {
|
|
233
|
+
const intent: IntentIR = {
|
|
234
|
+
family: "patch-validation",
|
|
235
|
+
goal: "Validate patch",
|
|
236
|
+
inputs: {
|
|
237
|
+
repoPath: "/repo",
|
|
238
|
+
baselineRef: "main",
|
|
239
|
+
candidateBranch: "fix",
|
|
240
|
+
reproduceCommands: ["npm run fail"],
|
|
241
|
+
verificationCommands: ["npm test", "npm run lint"],
|
|
242
|
+
reviewInstructions: "Check"
|
|
243
|
+
},
|
|
244
|
+
requiredTools: ["git"],
|
|
245
|
+
humanCheckpoints: [],
|
|
246
|
+
verificationTargets: ["npm test", "npm run lint"],
|
|
247
|
+
steps: [
|
|
248
|
+
{ id: "step-1", label: "Custom step", kind: "tool" }
|
|
249
|
+
]
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
const graph = buildTaskGraph(intent);
|
|
253
|
+
|
|
254
|
+
const verifyNodes = graph.stages.filter(s => s.id.startsWith("verify-target-"));
|
|
255
|
+
expect(verifyNodes).toHaveLength(2);
|
|
256
|
+
expect(verifyNodes[0].type).toBe("verify");
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it("should connect verification nodes after the last step", () => {
|
|
260
|
+
const intent: IntentIR = {
|
|
261
|
+
family: "patch-validation",
|
|
262
|
+
goal: "Validate patch",
|
|
263
|
+
inputs: {
|
|
264
|
+
repoPath: "/repo",
|
|
265
|
+
baselineRef: "main",
|
|
266
|
+
candidateBranch: "fix",
|
|
267
|
+
reproduceCommands: ["npm run fail"],
|
|
268
|
+
verificationCommands: ["npm test"],
|
|
269
|
+
reviewInstructions: "Check"
|
|
270
|
+
},
|
|
271
|
+
requiredTools: ["git"],
|
|
272
|
+
humanCheckpoints: [],
|
|
273
|
+
verificationTargets: ["npm test"],
|
|
274
|
+
steps: [
|
|
275
|
+
{ id: "step-1", label: "First", kind: "tool" },
|
|
276
|
+
{ id: "step-2", label: "Second", kind: "tool" }
|
|
277
|
+
]
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const graph = buildTaskGraph(intent);
|
|
281
|
+
|
|
282
|
+
const verifyNode = graph.stages.find(s => s.id === "verify-target-0")!;
|
|
283
|
+
expect(verifyNode.dependencies).toEqual(["step-2"]);
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
it("should handle llm and human step kinds", () => {
|
|
287
|
+
const intent: IntentIR = {
|
|
288
|
+
family: "patch-validation",
|
|
289
|
+
goal: "Validate patch",
|
|
290
|
+
inputs: {
|
|
291
|
+
repoPath: "/repo",
|
|
292
|
+
baselineRef: "main",
|
|
293
|
+
candidateBranch: "fix",
|
|
294
|
+
reproduceCommands: ["npm run fail"],
|
|
295
|
+
verificationCommands: ["npm test"],
|
|
296
|
+
reviewInstructions: "Check"
|
|
297
|
+
},
|
|
298
|
+
requiredTools: ["git"],
|
|
299
|
+
humanCheckpoints: [],
|
|
300
|
+
verificationTargets: [],
|
|
301
|
+
steps: [
|
|
302
|
+
{ id: "step-1", label: "Run tool", kind: "tool" },
|
|
303
|
+
{ id: "step-2", label: "Analyze", kind: "llm" },
|
|
304
|
+
{ id: "step-3", label: "Approve", kind: "human" }
|
|
305
|
+
]
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
const graph = buildTaskGraph(intent);
|
|
309
|
+
|
|
310
|
+
expect(graph.stages.find(s => s.id === "step-1")!.type).toBe("setup");
|
|
311
|
+
expect(graph.stages.find(s => s.id === "step-2")!.type).toBe("review");
|
|
312
|
+
expect(graph.stages.find(s => s.id === "step-3")!.type).toBe("approval");
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it("should build steps-only graph for pr-review-merge family", () => {
|
|
316
|
+
const intent: IntentIR = {
|
|
317
|
+
family: "pr-review-merge",
|
|
318
|
+
goal: "Review PR",
|
|
319
|
+
inputs: {
|
|
320
|
+
repoPath: "/repo",
|
|
321
|
+
sourceBranch: "feature",
|
|
322
|
+
targetBranch: "main",
|
|
323
|
+
reviewInstructions: "Check",
|
|
324
|
+
verificationCommands: ["npm test"]
|
|
325
|
+
},
|
|
326
|
+
requiredTools: ["git"],
|
|
327
|
+
humanCheckpoints: [],
|
|
328
|
+
verificationTargets: ["npm test"],
|
|
329
|
+
steps: [
|
|
330
|
+
{ id: "step-1", label: "Custom step", kind: "tool" }
|
|
331
|
+
]
|
|
332
|
+
};
|
|
333
|
+
|
|
334
|
+
const graph = buildTaskGraph(intent);
|
|
335
|
+
|
|
336
|
+
expect(graph.stages.find(s => s.id === "step-1")).toBeDefined();
|
|
337
|
+
expect(graph.stages.find(s => s.id === "setup-pr")).toBeDefined();
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
describe("stage metadata accuracy", () => {
|
|
342
|
+
it("should list actual input field names in requiredInputs, not derived fields", () => {
|
|
343
|
+
const intent: IntentIR = {
|
|
344
|
+
family: "patch-validation",
|
|
345
|
+
goal: "Validate patch against baseline",
|
|
346
|
+
inputs: {
|
|
347
|
+
repoPath: "/path/to/repo",
|
|
348
|
+
baselineRef: "v1.0.0",
|
|
349
|
+
candidateBranch: "fix-branch",
|
|
350
|
+
reproduceCommands: ["npm run fail-test"],
|
|
351
|
+
verificationCommands: ["npm test"],
|
|
352
|
+
reviewInstructions: "Check fix"
|
|
353
|
+
},
|
|
354
|
+
requiredTools: ["git"],
|
|
355
|
+
humanCheckpoints: [],
|
|
356
|
+
verificationTargets: ["npm test"]
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
const graph = buildTaskGraph(intent);
|
|
360
|
+
|
|
361
|
+
const applyStage = graph.stages.find(s => s.id === "apply-candidate");
|
|
362
|
+
expect(applyStage).toBeDefined();
|
|
363
|
+
|
|
364
|
+
// Should list actual input names (candidateBranch, patchFilePath), not the derived "candidateSource"
|
|
365
|
+
expect(applyStage!.requiredInputs).toEqual(["candidateBranch", "patchFilePath"]);
|
|
366
|
+
expect(applyStage!.requiredInputs).not.toContain("candidateSource");
|
|
367
|
+
});
|
|
368
|
+
});
|
|
369
|
+
});
|
|
370
|
+
});
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { synthesizeHarness } from "../../src/synthesis/harness-builder.js";
|
|
3
|
+
import type { TaskGraph } from "../../src/synthesis/graph-builder.js";
|
|
4
|
+
import type { RiskModel } from "../../src/synthesis/risk-analyzer.js";
|
|
5
|
+
|
|
6
|
+
describe("harness-builder", () => {
|
|
7
|
+
describe("synthesizeHarness", () => {
|
|
8
|
+
describe("patch-validation harness synthesis", () => {
|
|
9
|
+
it("should synthesize valid HarnessSpec for patch validation", () => {
|
|
10
|
+
const graph: TaskGraph = {
|
|
11
|
+
family: "patch-validation",
|
|
12
|
+
stages: [],
|
|
13
|
+
inputs: {
|
|
14
|
+
repoPath: "/path/to/repo",
|
|
15
|
+
baselineRef: "v1.0.0",
|
|
16
|
+
candidateBranch: "fix-branch",
|
|
17
|
+
reproduceCommands: ["npm run fail-test"],
|
|
18
|
+
verificationCommands: ["npm test"],
|
|
19
|
+
reviewInstructions: "Check the fix"
|
|
20
|
+
},
|
|
21
|
+
goal: "Validate patch"
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const risks: RiskModel = {
|
|
25
|
+
overallRisk: "low",
|
|
26
|
+
approvalRequired: false,
|
|
27
|
+
candidateSourceKind: "branch",
|
|
28
|
+
verificationBreadth: "moderate",
|
|
29
|
+
stageRisks: new Map(),
|
|
30
|
+
mitigations: []
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
const spec = synthesizeHarness(graph, risks);
|
|
34
|
+
|
|
35
|
+
expect(spec).toBeDefined();
|
|
36
|
+
expect(spec.graph.nodes).toBeDefined();
|
|
37
|
+
expect(spec.graph.nodes.length).toBeGreaterThan(0);
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
it("should delegate to existing reference builder", () => {
|
|
41
|
+
const graph: TaskGraph = {
|
|
42
|
+
family: "patch-validation",
|
|
43
|
+
stages: [],
|
|
44
|
+
inputs: {
|
|
45
|
+
repoPath: "/path/to/repo",
|
|
46
|
+
baselineRef: "main",
|
|
47
|
+
candidateBranch: "fix",
|
|
48
|
+
reproduceCommands: ["npm run fail"],
|
|
49
|
+
verificationCommands: ["npm test"],
|
|
50
|
+
reviewInstructions: "Check"
|
|
51
|
+
},
|
|
52
|
+
goal: "Validate"
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const risks: RiskModel = {
|
|
56
|
+
overallRisk: "low",
|
|
57
|
+
approvalRequired: false,
|
|
58
|
+
candidateSourceKind: "branch",
|
|
59
|
+
verificationBreadth: "moderate",
|
|
60
|
+
stageRisks: new Map(),
|
|
61
|
+
mitigations: []
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
const spec = synthesizeHarness(graph, risks);
|
|
65
|
+
|
|
66
|
+
// Verify that we get a real harness spec with the expected structure
|
|
67
|
+
expect(spec.graph.entryNodeId).toBe("run-baseline");
|
|
68
|
+
expect(spec.graph.nodes.some(n => n.id === "run-baseline")).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe("pr-review-merge harness synthesis", () => {
|
|
73
|
+
it("should synthesize valid HarnessSpec for PR review and merge", () => {
|
|
74
|
+
const graph: TaskGraph = {
|
|
75
|
+
family: "pr-review-merge",
|
|
76
|
+
stages: [],
|
|
77
|
+
inputs: {
|
|
78
|
+
repoPath: "/path/to/repo",
|
|
79
|
+
sourceBranch: "feature",
|
|
80
|
+
targetBranch: "main",
|
|
81
|
+
reviewInstructions: "Check security",
|
|
82
|
+
verificationCommands: ["npm test"]
|
|
83
|
+
},
|
|
84
|
+
goal: "Review and merge PR"
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
const risks: RiskModel = {
|
|
88
|
+
overallRisk: "low",
|
|
89
|
+
approvalRequired: false,
|
|
90
|
+
candidateSourceKind: "pr",
|
|
91
|
+
verificationBreadth: "moderate",
|
|
92
|
+
stageRisks: new Map(),
|
|
93
|
+
mitigations: []
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
const spec = synthesizeHarness(graph, risks);
|
|
97
|
+
|
|
98
|
+
expect(spec).toBeDefined();
|
|
99
|
+
expect(spec.graph.nodes).toBeDefined();
|
|
100
|
+
expect(spec.graph.nodes.length).toBeGreaterThan(0);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("should throw when policy synthesis cannot produce a valid harness", () => {
|
|
104
|
+
const graph: TaskGraph = {
|
|
105
|
+
family: "pr-review-merge",
|
|
106
|
+
stages: [],
|
|
107
|
+
inputs: {
|
|
108
|
+
repoPath: "/path/to/repo",
|
|
109
|
+
sourceBranch: "feature",
|
|
110
|
+
reviewInstructions: "Check"
|
|
111
|
+
},
|
|
112
|
+
goal: "Review"
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
const risks: RiskModel = {
|
|
116
|
+
overallRisk: "medium",
|
|
117
|
+
approvalRequired: false,
|
|
118
|
+
candidateSourceKind: "pr",
|
|
119
|
+
verificationBreadth: "narrow",
|
|
120
|
+
stageRisks: new Map(),
|
|
121
|
+
mitigations: ["Consider adding more tests"]
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
expect(() => synthesizeHarness(graph, risks)).toThrow(/Policy synthesis failed/);
|
|
125
|
+
});
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
});
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { synthesizePolicy } from "../../src/synthesis/policy-builder.js";
|
|
3
|
+
import type { TaskGraph } from "../../src/synthesis/graph-builder.js";
|
|
4
|
+
import type { RiskModel } from "../../src/synthesis/risk-analyzer.js";
|
|
5
|
+
|
|
6
|
+
describe("policy-builder", () => {
|
|
7
|
+
describe("synthesizePolicy", () => {
|
|
8
|
+
const lowRisks: RiskModel = {
|
|
9
|
+
overallRisk: "low",
|
|
10
|
+
approvalRequired: false,
|
|
11
|
+
candidateSourceKind: "branch",
|
|
12
|
+
verificationBreadth: "moderate",
|
|
13
|
+
stageRisks: new Map(),
|
|
14
|
+
mitigations: []
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
describe("steps-based graph handling", () => {
|
|
18
|
+
it("should return success for graph with steps-based nodes", () => {
|
|
19
|
+
const graph: TaskGraph = {
|
|
20
|
+
family: "patch-validation",
|
|
21
|
+
stages: [
|
|
22
|
+
{
|
|
23
|
+
id: "setup-baseline",
|
|
24
|
+
type: "setup",
|
|
25
|
+
dependencies: [],
|
|
26
|
+
description: "Check out baseline",
|
|
27
|
+
requiredInputs: ["repoPath", "baselineRef"]
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
id: "step-1",
|
|
31
|
+
type: "setup",
|
|
32
|
+
dependencies: [],
|
|
33
|
+
description: "Custom step",
|
|
34
|
+
requiredInputs: []
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
inputs: {
|
|
38
|
+
repoPath: "/repo",
|
|
39
|
+
baselineRef: "main",
|
|
40
|
+
candidateBranch: "fix",
|
|
41
|
+
reproduceCommands: ["npm run fail"],
|
|
42
|
+
verificationCommands: ["npm test"],
|
|
43
|
+
reviewInstructions: "Check"
|
|
44
|
+
},
|
|
45
|
+
goal: "Validate"
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
const result = synthesizePolicy(graph, lowRisks);
|
|
49
|
+
|
|
50
|
+
expect(result.success).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("should return generic bundle for steps-based graph", () => {
|
|
54
|
+
const graph: TaskGraph = {
|
|
55
|
+
family: "patch-validation",
|
|
56
|
+
stages: [
|
|
57
|
+
{
|
|
58
|
+
id: "step-1",
|
|
59
|
+
type: "setup",
|
|
60
|
+
dependencies: [],
|
|
61
|
+
description: "Custom step",
|
|
62
|
+
requiredInputs: []
|
|
63
|
+
}
|
|
64
|
+
],
|
|
65
|
+
inputs: {
|
|
66
|
+
repoPath: "/repo",
|
|
67
|
+
baselineRef: "main",
|
|
68
|
+
candidateBranch: "fix",
|
|
69
|
+
reproduceCommands: ["npm run fail"],
|
|
70
|
+
verificationCommands: ["npm test"],
|
|
71
|
+
reviewInstructions: "Check"
|
|
72
|
+
},
|
|
73
|
+
goal: "Validate"
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
const result = synthesizePolicy(graph, lowRisks);
|
|
77
|
+
|
|
78
|
+
expect(result.success).toBe(true);
|
|
79
|
+
if (result.success) {
|
|
80
|
+
expect(result.policy.bundle).toBeDefined();
|
|
81
|
+
expect(result.policy.rationale.length).toBeGreaterThan(0);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("should not break existing patch-validation policy", () => {
|
|
86
|
+
const graph: TaskGraph = {
|
|
87
|
+
family: "patch-validation",
|
|
88
|
+
stages: [
|
|
89
|
+
{
|
|
90
|
+
id: "setup-baseline",
|
|
91
|
+
type: "setup",
|
|
92
|
+
dependencies: [],
|
|
93
|
+
description: "Check out baseline",
|
|
94
|
+
requiredInputs: ["repoPath", "baselineRef"]
|
|
95
|
+
}
|
|
96
|
+
],
|
|
97
|
+
inputs: {
|
|
98
|
+
repoPath: "/repo",
|
|
99
|
+
baselineRef: "main",
|
|
100
|
+
candidateBranch: "fix",
|
|
101
|
+
reproduceCommands: ["npm run fail"],
|
|
102
|
+
verificationCommands: ["npm test"],
|
|
103
|
+
reviewInstructions: "Check"
|
|
104
|
+
},
|
|
105
|
+
goal: "Validate"
|
|
106
|
+
};
|
|
107
|
+
|
|
108
|
+
const result = synthesizePolicy(graph, lowRisks);
|
|
109
|
+
|
|
110
|
+
expect(result.success).toBe(true);
|
|
111
|
+
if (result.success) {
|
|
112
|
+
expect(result.policy.workflow).toBe("patch-validation");
|
|
113
|
+
expect(result.policy.bundle).toBeDefined();
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("should not break existing pr-review-merge policy", () => {
|
|
118
|
+
const graph: TaskGraph = {
|
|
119
|
+
family: "pr-review-merge",
|
|
120
|
+
stages: [
|
|
121
|
+
{
|
|
122
|
+
id: "setup-pr",
|
|
123
|
+
type: "setup",
|
|
124
|
+
dependencies: [],
|
|
125
|
+
description: "Fetch PR",
|
|
126
|
+
requiredInputs: ["repoPath", "sourceBranch", "targetBranch"]
|
|
127
|
+
}
|
|
128
|
+
],
|
|
129
|
+
inputs: {
|
|
130
|
+
repoPath: "/repo",
|
|
131
|
+
sourceBranch: "feature",
|
|
132
|
+
targetBranch: "main",
|
|
133
|
+
reviewInstructions: "Check",
|
|
134
|
+
verificationCommands: ["npm test"]
|
|
135
|
+
},
|
|
136
|
+
goal: "Review"
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const result = synthesizePolicy(graph, lowRisks);
|
|
140
|
+
|
|
141
|
+
expect(result.success).toBe(true);
|
|
142
|
+
if (result.success) {
|
|
143
|
+
expect(result.policy.workflow).toBe("pr-review-merge");
|
|
144
|
+
expect(result.policy.bundle).toBeDefined();
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
});
|
|
148
|
+
});
|
|
149
|
+
});
|