@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,796 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { parsePromptOrSkill, parseSkillMarkdown } from "../../src/synthesis/skill-parser.js";
|
|
3
|
+
import { buildTaskGraph } from "../../src/synthesis/graph-builder.js";
|
|
4
|
+
import { analyzeRisks } from "../../src/synthesis/risk-analyzer.js";
|
|
5
|
+
import { synthesizePolicy } from "../../src/synthesis/policy-builder.js";
|
|
6
|
+
import { synthesizeHarness } from "../../src/synthesis/harness-builder.js";
|
|
7
|
+
|
|
8
|
+
describe("skill-parser", () => {
|
|
9
|
+
describe("parsePromptOrSkill", () => {
|
|
10
|
+
describe("freeform PR review brief", () => {
|
|
11
|
+
it("should parse a complete PR review brief", () => {
|
|
12
|
+
const brief = `
|
|
13
|
+
Review the PR from feature-branch to main
|
|
14
|
+
repoPath: /path/to/repo
|
|
15
|
+
sourceBranch: feature-branch
|
|
16
|
+
targetBranch: main
|
|
17
|
+
reviewInstructions: "Check for security issues"
|
|
18
|
+
verification commands: [npm test, npm run lint]
|
|
19
|
+
`;
|
|
20
|
+
|
|
21
|
+
const result = parsePromptOrSkill(brief);
|
|
22
|
+
|
|
23
|
+
expect(result).toHaveProperty("success", true);
|
|
24
|
+
if ("success" in result && result.success) {
|
|
25
|
+
expect(result.intent.family).toBe("pr-review-merge");
|
|
26
|
+
expect(result.intent.inputs.repoPath).toBe("/path/to/repo");
|
|
27
|
+
expect(result.intent.inputs.sourceBranch).toBe("feature-branch");
|
|
28
|
+
expect(result.intent.inputs.targetBranch).toBe("main");
|
|
29
|
+
expect(result.intent.verificationTargets).toHaveLength(2);
|
|
30
|
+
}
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
describe("freeform patch-validation brief", () => {
|
|
35
|
+
it("should parse a patch validation brief with branch", () => {
|
|
36
|
+
const brief = `
|
|
37
|
+
Validate the patch for baseline v1.0.0
|
|
38
|
+
repoPath: /path/to/repo
|
|
39
|
+
baseline: v1.0.0
|
|
40
|
+
candidate branch: fix-branch
|
|
41
|
+
reproduce commands: [npm run fail-test]
|
|
42
|
+
verification commands: [npm test]
|
|
43
|
+
reviewInstructions: "Ensure the fix works"
|
|
44
|
+
`;
|
|
45
|
+
|
|
46
|
+
const result = parsePromptOrSkill(brief);
|
|
47
|
+
|
|
48
|
+
expect(result).toHaveProperty("success", true);
|
|
49
|
+
if ("success" in result && result.success) {
|
|
50
|
+
expect(result.intent.family).toBe("patch-validation");
|
|
51
|
+
expect(result.intent.inputs.baselineRef).toBe("v1.0.0");
|
|
52
|
+
expect(result.intent.inputs.candidateBranch).toBe("fix-branch");
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
it("should parse a patch validation brief with patch file", () => {
|
|
57
|
+
const brief = `
|
|
58
|
+
Validate the patch file fix.patch against baseline main
|
|
59
|
+
repoPath: /path/to/repo
|
|
60
|
+
baseline: main
|
|
61
|
+
patchFile: fix.patch
|
|
62
|
+
reproduce: [node test-fail.js]
|
|
63
|
+
verify: [npm test]
|
|
64
|
+
reviewInstructions: "Check if bug is fixed"
|
|
65
|
+
`;
|
|
66
|
+
|
|
67
|
+
const result = parsePromptOrSkill(brief);
|
|
68
|
+
|
|
69
|
+
expect(result).toHaveProperty("success", true);
|
|
70
|
+
if ("success" in result && result.success) {
|
|
71
|
+
expect(result.intent.family).toBe("patch-validation");
|
|
72
|
+
expect(result.intent.inputs.patchFilePath).toBe("fix.patch");
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("should include approval checkpoint when approvalRequired is true", () => {
|
|
77
|
+
const brief = `
|
|
78
|
+
patch validation with approval required
|
|
79
|
+
repoPath: /path/to/repo
|
|
80
|
+
baseline: main
|
|
81
|
+
candidate branch: fix-branch
|
|
82
|
+
reproduce: [npm run fail]
|
|
83
|
+
verify: [npm test]
|
|
84
|
+
reviewInstructions: "Validate fix"
|
|
85
|
+
approvalRequired: true
|
|
86
|
+
`;
|
|
87
|
+
|
|
88
|
+
const result = parsePromptOrSkill(brief);
|
|
89
|
+
|
|
90
|
+
expect(result).toHaveProperty("success", true);
|
|
91
|
+
if ("success" in result && result.success) {
|
|
92
|
+
expect(result.intent.humanCheckpoints).toContain("approval-gate");
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
describe("skill-markdown input", () => {
|
|
98
|
+
it("should parse skill markdown for PR review", () => {
|
|
99
|
+
const skillMd = `
|
|
100
|
+
# PR Review Workflow
|
|
101
|
+
|
|
102
|
+
workflow: pr-review-merge
|
|
103
|
+
|
|
104
|
+
## Inputs
|
|
105
|
+
- repoPath: /path/to/repo
|
|
106
|
+
- sourceBranch: feature
|
|
107
|
+
- targetBranch: main
|
|
108
|
+
- reviewInstructions: Check code quality
|
|
109
|
+
|
|
110
|
+
## Verification
|
|
111
|
+
- npm test
|
|
112
|
+
- npm run lint
|
|
113
|
+
`;
|
|
114
|
+
|
|
115
|
+
const result = parsePromptOrSkill(skillMd);
|
|
116
|
+
|
|
117
|
+
expect(result).toHaveProperty("success", true);
|
|
118
|
+
if ("success" in result && result.success) {
|
|
119
|
+
expect(result.intent.family).toBe("pr-review-merge");
|
|
120
|
+
expect(result.intent.inputs.repoPath).toBe("/path/to/repo");
|
|
121
|
+
expect(result.intent.inputs.sourceBranch).toBe("feature");
|
|
122
|
+
expect(result.intent.verificationTargets).toHaveLength(2);
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("should parse skill markdown for patch validation", () => {
|
|
127
|
+
const skillMd = `
|
|
128
|
+
# Patch Validation Workflow
|
|
129
|
+
|
|
130
|
+
workflow: patch-validation
|
|
131
|
+
|
|
132
|
+
## Inputs
|
|
133
|
+
- repoPath: /path/to/repo
|
|
134
|
+
- baselineRef: v1.0.0
|
|
135
|
+
- candidateBranch: fix-branch
|
|
136
|
+
- reproduceCommands: [npm run fail-test]
|
|
137
|
+
- reviewInstructions: Validate the fix
|
|
138
|
+
|
|
139
|
+
## Verification
|
|
140
|
+
- npm test
|
|
141
|
+
- npm run integration
|
|
142
|
+
`;
|
|
143
|
+
|
|
144
|
+
const result = parsePromptOrSkill(skillMd);
|
|
145
|
+
|
|
146
|
+
expect(result).toHaveProperty("success", true);
|
|
147
|
+
if ("success" in result && result.success) {
|
|
148
|
+
expect(result.intent.family).toBe("patch-validation");
|
|
149
|
+
expect(result.intent.inputs.baselineRef).toBe("v1.0.0");
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
it("should normalize array-like inputs in skill markdown for PR review", () => {
|
|
154
|
+
const skillMd = `
|
|
155
|
+
# PR Review Workflow
|
|
156
|
+
|
|
157
|
+
workflow: pr-review-merge
|
|
158
|
+
|
|
159
|
+
## Inputs
|
|
160
|
+
- repoPath: /path/to/repo
|
|
161
|
+
- sourceBranch: feature
|
|
162
|
+
- targetBranch: main
|
|
163
|
+
- reviewInstructions: Check code quality
|
|
164
|
+
- verificationCommands: [npm test, npm run lint]
|
|
165
|
+
|
|
166
|
+
## Verification
|
|
167
|
+
- npm run e2e
|
|
168
|
+
`;
|
|
169
|
+
|
|
170
|
+
const result = parsePromptOrSkill(skillMd);
|
|
171
|
+
|
|
172
|
+
expect(result).toHaveProperty("success", true);
|
|
173
|
+
if ("success" in result && result.success) {
|
|
174
|
+
expect(result.intent.family).toBe("pr-review-merge");
|
|
175
|
+
// verificationCommands should be normalized from string "[npm test, npm run lint]" to array
|
|
176
|
+
expect(result.intent.inputs.verificationCommands).toEqual(["npm test", "npm run lint", "npm run e2e"]);
|
|
177
|
+
// Verification section should merge/append to verificationCommands
|
|
178
|
+
expect(result.intent.verificationTargets).toContain("npm test");
|
|
179
|
+
expect(result.intent.verificationTargets).toContain("npm run lint");
|
|
180
|
+
expect(result.intent.verificationTargets).toContain("npm run e2e");
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("should normalize array-like and boolean inputs in skill markdown for patch validation", () => {
|
|
185
|
+
const skillMd = `
|
|
186
|
+
# Patch Validation Workflow
|
|
187
|
+
|
|
188
|
+
workflow: patch-validation
|
|
189
|
+
|
|
190
|
+
## Inputs
|
|
191
|
+
- repoPath: /path/to/repo
|
|
192
|
+
- baselineRef: v1.0.0
|
|
193
|
+
- candidateBranch: fix-branch
|
|
194
|
+
- reproduceCommands: [npm run fail-test, node reproduce.js]
|
|
195
|
+
- verificationCommands: [npm test]
|
|
196
|
+
- reviewInstructions: Validate the fix
|
|
197
|
+
- approvalRequired: true
|
|
198
|
+
|
|
199
|
+
## Verification
|
|
200
|
+
- npm run integration
|
|
201
|
+
`;
|
|
202
|
+
|
|
203
|
+
const result = parsePromptOrSkill(skillMd);
|
|
204
|
+
|
|
205
|
+
expect(result).toHaveProperty("success", true);
|
|
206
|
+
if ("success" in result && result.success) {
|
|
207
|
+
expect(result.intent.family).toBe("patch-validation");
|
|
208
|
+
// reproduceCommands should be normalized from string to array
|
|
209
|
+
expect(result.intent.inputs.reproduceCommands).toEqual(["npm run fail-test", "node reproduce.js"]);
|
|
210
|
+
// verificationCommands should include both inputs section and Verification section
|
|
211
|
+
expect(result.intent.inputs.verificationCommands).toEqual(["npm test", "npm run integration"]);
|
|
212
|
+
// approvalRequired should be normalized from string "true" to boolean true
|
|
213
|
+
expect(result.intent.inputs.approvalRequired).toBe(true);
|
|
214
|
+
expect(result.intent.humanCheckpoints).toContain("approval-gate");
|
|
215
|
+
}
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
it("should populate verificationCommands from Verification section when not in inputs", () => {
|
|
219
|
+
const skillMd = `
|
|
220
|
+
# PR Review Workflow
|
|
221
|
+
|
|
222
|
+
workflow: pr-review-merge
|
|
223
|
+
|
|
224
|
+
## Inputs
|
|
225
|
+
- repoPath: /path/to/repo
|
|
226
|
+
- sourceBranch: feature
|
|
227
|
+
- targetBranch: main
|
|
228
|
+
- reviewInstructions: Check code quality
|
|
229
|
+
|
|
230
|
+
## Verification
|
|
231
|
+
- npm test
|
|
232
|
+
- npm run lint
|
|
233
|
+
`;
|
|
234
|
+
|
|
235
|
+
const result = parsePromptOrSkill(skillMd);
|
|
236
|
+
|
|
237
|
+
expect(result).toHaveProperty("success", true);
|
|
238
|
+
if ("success" in result && result.success) {
|
|
239
|
+
expect(result.intent.family).toBe("pr-review-merge");
|
|
240
|
+
// Verification section should populate inputs.verificationCommands
|
|
241
|
+
expect(result.intent.inputs.verificationCommands).toEqual(["npm test", "npm run lint"]);
|
|
242
|
+
expect(result.intent.verificationTargets).toEqual(["npm test", "npm run lint"]);
|
|
243
|
+
}
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
describe("unsupported/underspecified cases", () => {
|
|
248
|
+
it("should reject empty input", () => {
|
|
249
|
+
const result = parsePromptOrSkill("");
|
|
250
|
+
|
|
251
|
+
expect(result).toHaveProperty("rejected", true);
|
|
252
|
+
if ("rejected" in result) {
|
|
253
|
+
expect(result.reasons).toContain("Input is empty");
|
|
254
|
+
}
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it("should accept ambiguous input as custom workflow", () => {
|
|
258
|
+
const brief = "Do something with a repository at /path/to/repo";
|
|
259
|
+
|
|
260
|
+
const result = parsePromptOrSkill(brief);
|
|
261
|
+
|
|
262
|
+
expect(result).toHaveProperty("success", true);
|
|
263
|
+
if ("success" in result && result.success) {
|
|
264
|
+
expect(result.intent.family).toBe("custom");
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it("should accept unsupported workflow as custom workflow", () => {
|
|
269
|
+
const brief = `
|
|
270
|
+
workflow: deploy-to-production
|
|
271
|
+
repoPath: /path/to/repo
|
|
272
|
+
`;
|
|
273
|
+
|
|
274
|
+
const result = parsePromptOrSkill(brief);
|
|
275
|
+
|
|
276
|
+
// Now accepted as a custom workflow family
|
|
277
|
+
expect(result).toHaveProperty("success", true);
|
|
278
|
+
if ("success" in result && result.success) {
|
|
279
|
+
expect(result.intent.family).toBe("deploy-to-production");
|
|
280
|
+
}
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
describe("parseSkillMarkdown", () => {
|
|
286
|
+
it("should extract title from markdown", () => {
|
|
287
|
+
const md = `# My Workflow Title`;
|
|
288
|
+
const skill = parseSkillMarkdown(md);
|
|
289
|
+
expect(skill.title).toBe("My Workflow Title");
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it("should extract workflow type", () => {
|
|
293
|
+
const md = `workflow: patch-validation`;
|
|
294
|
+
const skill = parseSkillMarkdown(md);
|
|
295
|
+
expect(skill.workflow).toBe("patch-validation");
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it("should extract inputs section", () => {
|
|
299
|
+
const md = `
|
|
300
|
+
## Inputs
|
|
301
|
+
- repoPath: /path/to/repo
|
|
302
|
+
- branch: main
|
|
303
|
+
`;
|
|
304
|
+
const skill = parseSkillMarkdown(md);
|
|
305
|
+
expect(skill.inputs).toEqual({
|
|
306
|
+
repoPath: "/path/to/repo",
|
|
307
|
+
branch: "main"
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
describe("normalizeInputValue edge cases", () => {
|
|
313
|
+
it("should handle commands with commas inside quoted strings", () => {
|
|
314
|
+
const skillMd = `
|
|
315
|
+
# PR Review Workflow
|
|
316
|
+
|
|
317
|
+
workflow: pr-review-merge
|
|
318
|
+
|
|
319
|
+
## Inputs
|
|
320
|
+
- repoPath: /path/to/repo
|
|
321
|
+
- sourceBranch: feature
|
|
322
|
+
- targetBranch: main
|
|
323
|
+
- reviewInstructions: Check code quality
|
|
324
|
+
- verificationCommands: ["echo 'hello, world'", "npm test"]
|
|
325
|
+
`;
|
|
326
|
+
|
|
327
|
+
const result = parsePromptOrSkill(skillMd);
|
|
328
|
+
|
|
329
|
+
expect(result).toHaveProperty("success", true);
|
|
330
|
+
if ("success" in result && result.success) {
|
|
331
|
+
expect(result.intent.inputs.verificationCommands).toEqual([
|
|
332
|
+
"echo 'hello, world'",
|
|
333
|
+
"npm test"
|
|
334
|
+
]);
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it("should handle commands with commas in double quotes", () => {
|
|
339
|
+
const skillMd = `
|
|
340
|
+
# Patch Validation
|
|
341
|
+
|
|
342
|
+
workflow: patch-validation
|
|
343
|
+
|
|
344
|
+
## Inputs
|
|
345
|
+
- repoPath: /path/to/repo
|
|
346
|
+
- baselineRef: main
|
|
347
|
+
- candidateBranch: fix
|
|
348
|
+
- reproduceCommands: ["node test.js --data 'a, b, c'", "npm run fail"]
|
|
349
|
+
- verificationCommands: ["npm test"]
|
|
350
|
+
- reviewInstructions: Validate fix
|
|
351
|
+
`;
|
|
352
|
+
|
|
353
|
+
const result = parsePromptOrSkill(skillMd);
|
|
354
|
+
|
|
355
|
+
expect(result).toHaveProperty("success", true);
|
|
356
|
+
if ("success" in result && result.success) {
|
|
357
|
+
expect(result.intent.inputs.reproduceCommands).toEqual([
|
|
358
|
+
"node test.js --data 'a, b, c'",
|
|
359
|
+
"npm run fail"
|
|
360
|
+
]);
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it("should normalize empty array string [] to empty array", () => {
|
|
365
|
+
const skillMd = `
|
|
366
|
+
# PR Review Workflow
|
|
367
|
+
|
|
368
|
+
workflow: pr-review-merge
|
|
369
|
+
|
|
370
|
+
## Inputs
|
|
371
|
+
- repoPath: /path/to/repo
|
|
372
|
+
- sourceBranch: feature
|
|
373
|
+
- targetBranch: main
|
|
374
|
+
- reviewInstructions: Check code quality
|
|
375
|
+
- verificationCommands: []
|
|
376
|
+
`;
|
|
377
|
+
|
|
378
|
+
const result = parsePromptOrSkill(skillMd);
|
|
379
|
+
|
|
380
|
+
expect(result).toHaveProperty("success", true);
|
|
381
|
+
if ("success" in result && result.success) {
|
|
382
|
+
// Should be normalized to an empty array, not remain as string "[]"
|
|
383
|
+
expect(Array.isArray(result.intent.inputs.verificationCommands)).toBe(true);
|
|
384
|
+
expect((result.intent.inputs.verificationCommands as string[]).length).toBe(0);
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it("should handle arrays with quoted strings containing brackets", () => {
|
|
389
|
+
const skillMd = `
|
|
390
|
+
# Test Workflow
|
|
391
|
+
|
|
392
|
+
workflow: pr-review-merge
|
|
393
|
+
|
|
394
|
+
## Inputs
|
|
395
|
+
- repoPath: /path/to/repo
|
|
396
|
+
- sourceBranch: feature
|
|
397
|
+
- targetBranch: main
|
|
398
|
+
- reviewInstructions: Check
|
|
399
|
+
- verificationCommands: ["echo '[test]'", "npm test"]
|
|
400
|
+
`;
|
|
401
|
+
|
|
402
|
+
const result = parsePromptOrSkill(skillMd);
|
|
403
|
+
|
|
404
|
+
expect(result).toHaveProperty("success", true);
|
|
405
|
+
if ("success" in result && result.success) {
|
|
406
|
+
expect(result.intent.inputs.verificationCommands).toEqual([
|
|
407
|
+
"echo '[test]'",
|
|
408
|
+
"npm test"
|
|
409
|
+
]);
|
|
410
|
+
}
|
|
411
|
+
});
|
|
412
|
+
|
|
413
|
+
describe("steps field mapping to IntentIR", () => {
|
|
414
|
+
it("should populate intent.steps from skill markdown steps section", () => {
|
|
415
|
+
const skillMd = `
|
|
416
|
+
# Custom Workflow
|
|
417
|
+
|
|
418
|
+
workflow: patch-validation
|
|
419
|
+
|
|
420
|
+
## Inputs
|
|
421
|
+
- repoPath: /path/to/repo
|
|
422
|
+
- baselineRef: main
|
|
423
|
+
- candidateBranch: fix
|
|
424
|
+
- reproduceCommands: [npm run fail]
|
|
425
|
+
- verificationCommands: [npm test]
|
|
426
|
+
- reviewInstructions: Validate fix
|
|
427
|
+
|
|
428
|
+
## Steps
|
|
429
|
+
- [tool] Run npm install
|
|
430
|
+
- [tool] Apply the patch
|
|
431
|
+
- [llm] Summarize changes
|
|
432
|
+
- [human] Review the output
|
|
433
|
+
`;
|
|
434
|
+
|
|
435
|
+
const result = parsePromptOrSkill(skillMd);
|
|
436
|
+
|
|
437
|
+
expect(result).toHaveProperty("success", true);
|
|
438
|
+
if ("success" in result && result.success) {
|
|
439
|
+
expect(result.intent.steps).toBeDefined();
|
|
440
|
+
expect(result.intent.steps).toHaveLength(4);
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
it("should infer step kind from [tool] prefix", () => {
|
|
445
|
+
const skillMd = `
|
|
446
|
+
# Test
|
|
447
|
+
|
|
448
|
+
workflow: patch-validation
|
|
449
|
+
|
|
450
|
+
## Inputs
|
|
451
|
+
- repoPath: /repo
|
|
452
|
+
- baselineRef: main
|
|
453
|
+
- candidateBranch: fix
|
|
454
|
+
- reproduceCommands: [npm run fail]
|
|
455
|
+
- verificationCommands: [npm test]
|
|
456
|
+
- reviewInstructions: Check
|
|
457
|
+
|
|
458
|
+
## Steps
|
|
459
|
+
- [tool] npm install
|
|
460
|
+
`;
|
|
461
|
+
|
|
462
|
+
const result = parsePromptOrSkill(skillMd);
|
|
463
|
+
|
|
464
|
+
expect(result).toHaveProperty("success", true);
|
|
465
|
+
if ("success" in result && result.success) {
|
|
466
|
+
expect(result.intent.steps![0].kind).toBe("tool");
|
|
467
|
+
expect(result.intent.steps![0].label).toBe("npm install");
|
|
468
|
+
}
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
it("should infer step kind from [llm] prefix", () => {
|
|
472
|
+
const skillMd = `
|
|
473
|
+
# Test
|
|
474
|
+
|
|
475
|
+
workflow: patch-validation
|
|
476
|
+
|
|
477
|
+
## Inputs
|
|
478
|
+
- repoPath: /repo
|
|
479
|
+
- baselineRef: main
|
|
480
|
+
- candidateBranch: fix
|
|
481
|
+
- reproduceCommands: [npm run fail]
|
|
482
|
+
- verificationCommands: [npm test]
|
|
483
|
+
- reviewInstructions: Check
|
|
484
|
+
|
|
485
|
+
## Steps
|
|
486
|
+
- [llm] Analyze code quality
|
|
487
|
+
`;
|
|
488
|
+
|
|
489
|
+
const result = parsePromptOrSkill(skillMd);
|
|
490
|
+
|
|
491
|
+
expect(result).toHaveProperty("success", true);
|
|
492
|
+
if ("success" in result && result.success) {
|
|
493
|
+
expect(result.intent.steps![0].kind).toBe("llm");
|
|
494
|
+
expect(result.intent.steps![0].label).toBe("Analyze code quality");
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
|
|
498
|
+
it("should infer step kind from [human] prefix", () => {
|
|
499
|
+
const skillMd = `
|
|
500
|
+
# Test
|
|
501
|
+
|
|
502
|
+
workflow: patch-validation
|
|
503
|
+
|
|
504
|
+
## Inputs
|
|
505
|
+
- repoPath: /repo
|
|
506
|
+
- baselineRef: main
|
|
507
|
+
- candidateBranch: fix
|
|
508
|
+
- reproduceCommands: [npm run fail]
|
|
509
|
+
- verificationCommands: [npm test]
|
|
510
|
+
- reviewInstructions: Check
|
|
511
|
+
|
|
512
|
+
## Steps
|
|
513
|
+
- [human] Approve changes
|
|
514
|
+
`;
|
|
515
|
+
|
|
516
|
+
const result = parsePromptOrSkill(skillMd);
|
|
517
|
+
|
|
518
|
+
expect(result).toHaveProperty("success", true);
|
|
519
|
+
if ("success" in result && result.success) {
|
|
520
|
+
expect(result.intent.steps![0].kind).toBe("human");
|
|
521
|
+
expect(result.intent.steps![0].label).toBe("Approve changes");
|
|
522
|
+
}
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
it("should infer step kind from [condition] prefix", () => {
|
|
526
|
+
const skillMd = `
|
|
527
|
+
# Test
|
|
528
|
+
|
|
529
|
+
workflow: patch-validation
|
|
530
|
+
|
|
531
|
+
## Inputs
|
|
532
|
+
- repoPath: /repo
|
|
533
|
+
- baselineRef: main
|
|
534
|
+
- candidateBranch: fix
|
|
535
|
+
- reproduceCommands: [npm run fail]
|
|
536
|
+
- verificationCommands: [npm test]
|
|
537
|
+
- reviewInstructions: Check
|
|
538
|
+
|
|
539
|
+
## Steps
|
|
540
|
+
- [condition] Tests passed?
|
|
541
|
+
`;
|
|
542
|
+
|
|
543
|
+
const result = parsePromptOrSkill(skillMd);
|
|
544
|
+
|
|
545
|
+
expect(result).toHaveProperty("success", true);
|
|
546
|
+
if ("success" in result && result.success) {
|
|
547
|
+
expect(result.intent.steps![0].kind).toBe("condition");
|
|
548
|
+
}
|
|
549
|
+
});
|
|
550
|
+
|
|
551
|
+
it("should default to tool kind when no prefix is present", () => {
|
|
552
|
+
const skillMd = `
|
|
553
|
+
# Test
|
|
554
|
+
|
|
555
|
+
workflow: patch-validation
|
|
556
|
+
|
|
557
|
+
## Inputs
|
|
558
|
+
- repoPath: /repo
|
|
559
|
+
- baselineRef: main
|
|
560
|
+
- candidateBranch: fix
|
|
561
|
+
- reproduceCommands: [npm run fail]
|
|
562
|
+
- verificationCommands: [npm test]
|
|
563
|
+
- reviewInstructions: Check
|
|
564
|
+
|
|
565
|
+
## Steps
|
|
566
|
+
- npm install
|
|
567
|
+
`;
|
|
568
|
+
|
|
569
|
+
const result = parsePromptOrSkill(skillMd);
|
|
570
|
+
|
|
571
|
+
expect(result).toHaveProperty("success", true);
|
|
572
|
+
if ("success" in result && result.success) {
|
|
573
|
+
expect(result.intent.steps![0].kind).toBe("tool");
|
|
574
|
+
expect(result.intent.steps![0].label).toBe("npm install");
|
|
575
|
+
}
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
it("should preserve step order", () => {
|
|
579
|
+
const skillMd = `
|
|
580
|
+
# Test
|
|
581
|
+
|
|
582
|
+
workflow: patch-validation
|
|
583
|
+
|
|
584
|
+
## Inputs
|
|
585
|
+
- repoPath: /repo
|
|
586
|
+
- baselineRef: main
|
|
587
|
+
- candidateBranch: fix
|
|
588
|
+
- reproduceCommands: [npm run fail]
|
|
589
|
+
- verificationCommands: [npm test]
|
|
590
|
+
- reviewInstructions: Check
|
|
591
|
+
|
|
592
|
+
## Steps
|
|
593
|
+
- [tool] First step
|
|
594
|
+
- [llm] Second step
|
|
595
|
+
- [human] Third step
|
|
596
|
+
`;
|
|
597
|
+
|
|
598
|
+
const result = parsePromptOrSkill(skillMd);
|
|
599
|
+
|
|
600
|
+
expect(result).toHaveProperty("success", true);
|
|
601
|
+
if ("success" in result && result.success) {
|
|
602
|
+
expect(result.intent.steps![0].label).toBe("First step");
|
|
603
|
+
expect(result.intent.steps![1].label).toBe("Second step");
|
|
604
|
+
expect(result.intent.steps![2].label).toBe("Third step");
|
|
605
|
+
}
|
|
606
|
+
});
|
|
607
|
+
|
|
608
|
+
it("should generate unique step ids from label text", () => {
|
|
609
|
+
const skillMd = `
|
|
610
|
+
# Test
|
|
611
|
+
|
|
612
|
+
workflow: patch-validation
|
|
613
|
+
|
|
614
|
+
## Inputs
|
|
615
|
+
- repoPath: /repo
|
|
616
|
+
- baselineRef: main
|
|
617
|
+
- candidateBranch: fix
|
|
618
|
+
- reproduceCommands: [npm run fail]
|
|
619
|
+
- verificationCommands: [npm test]
|
|
620
|
+
- reviewInstructions: Check
|
|
621
|
+
|
|
622
|
+
## Steps
|
|
623
|
+
- [tool] Run tests
|
|
624
|
+
- [tool] Run linter
|
|
625
|
+
`;
|
|
626
|
+
|
|
627
|
+
const result = parsePromptOrSkill(skillMd);
|
|
628
|
+
|
|
629
|
+
expect(result).toHaveProperty("success", true);
|
|
630
|
+
if ("success" in result && result.success) {
|
|
631
|
+
expect(result.intent.steps![0].id).not.toBe(result.intent.steps![1].id);
|
|
632
|
+
}
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
it("should not populate steps when no steps section exists", () => {
|
|
636
|
+
const skillMd = `
|
|
637
|
+
# Test
|
|
638
|
+
|
|
639
|
+
workflow: patch-validation
|
|
640
|
+
|
|
641
|
+
## Inputs
|
|
642
|
+
- repoPath: /repo
|
|
643
|
+
- baselineRef: main
|
|
644
|
+
- candidateBranch: fix
|
|
645
|
+
- reproduceCommands: [npm run fail]
|
|
646
|
+
- verificationCommands: [npm test]
|
|
647
|
+
- reviewInstructions: Check
|
|
648
|
+
`;
|
|
649
|
+
|
|
650
|
+
const result = parsePromptOrSkill(skillMd);
|
|
651
|
+
|
|
652
|
+
expect(result).toHaveProperty("success", true);
|
|
653
|
+
if ("success" in result && result.success) {
|
|
654
|
+
expect(result.intent.steps).toBeUndefined();
|
|
655
|
+
}
|
|
656
|
+
});
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
it("should normalize single command strings into command arrays", () => {
|
|
660
|
+
const skillMd = `
|
|
661
|
+
# Patch Validation
|
|
662
|
+
|
|
663
|
+
workflow: patch-validation
|
|
664
|
+
|
|
665
|
+
## Inputs
|
|
666
|
+
- repoPath: /path/to/repo
|
|
667
|
+
- baselineRef: main
|
|
668
|
+
- candidateBranch: fix
|
|
669
|
+
- reproduceCommands: "npm run fail"
|
|
670
|
+
- verificationCommands: 'npm test'
|
|
671
|
+
- reviewInstructions: "Validate fix"
|
|
672
|
+
`;
|
|
673
|
+
|
|
674
|
+
const result = parsePromptOrSkill(skillMd);
|
|
675
|
+
|
|
676
|
+
expect(result).toHaveProperty("success", true);
|
|
677
|
+
if ("success" in result && result.success) {
|
|
678
|
+
expect(result.intent.inputs.reproduceCommands).toEqual(["npm run fail"]);
|
|
679
|
+
expect(result.intent.inputs.verificationCommands).toEqual(["npm test"]);
|
|
680
|
+
expect(result.intent.inputs.reviewInstructions).toBe("Validate fix");
|
|
681
|
+
}
|
|
682
|
+
});
|
|
683
|
+
});
|
|
684
|
+
|
|
685
|
+
describe("full pipeline: skill markdown with steps to harness", () => {
|
|
686
|
+
it("should compile skill markdown with custom steps all the way to harness spec", () => {
|
|
687
|
+
const skillMd = `
|
|
688
|
+
# Custom Build Pipeline
|
|
689
|
+
|
|
690
|
+
workflow: patch-validation
|
|
691
|
+
|
|
692
|
+
## Inputs
|
|
693
|
+
- repoPath: /path/to/repo
|
|
694
|
+
- baselineRef: v1.0.0
|
|
695
|
+
- candidateBranch: fix-branch
|
|
696
|
+
- reproduceCommands: [npm run fail-test]
|
|
697
|
+
- verificationCommands: [npm test]
|
|
698
|
+
- reviewInstructions: Validate the fix
|
|
699
|
+
|
|
700
|
+
## Steps
|
|
701
|
+
- [tool] npm install
|
|
702
|
+
- [tool] npm run build
|
|
703
|
+
- [llm] Summarize build output
|
|
704
|
+
- [human] Review summary
|
|
705
|
+
|
|
706
|
+
## Verification
|
|
707
|
+
- npm test
|
|
708
|
+
`;
|
|
709
|
+
|
|
710
|
+
const intentResult = parsePromptOrSkill(skillMd);
|
|
711
|
+
expect(intentResult).toHaveProperty("success", true);
|
|
712
|
+
|
|
713
|
+
if (!("success" in intentResult) || !intentResult.success) return;
|
|
714
|
+
|
|
715
|
+
const intent = intentResult.intent;
|
|
716
|
+
expect(intent.steps).toHaveLength(4);
|
|
717
|
+
|
|
718
|
+
const graph = buildTaskGraph(intent);
|
|
719
|
+
expect(graph.stages.find(s => s.id === "step-1")).toBeDefined();
|
|
720
|
+
expect(graph.stages.find(s => s.id === "step-4")).toBeDefined();
|
|
721
|
+
|
|
722
|
+
const verifyNodes = graph.stages.filter(s => s.id.startsWith("verify-target-"));
|
|
723
|
+
expect(verifyNodes.length).toBeGreaterThan(0);
|
|
724
|
+
|
|
725
|
+
const risks = analyzeRisks(graph);
|
|
726
|
+
const policyResult = synthesizePolicy(graph, risks);
|
|
727
|
+
expect(policyResult.success).toBe(true);
|
|
728
|
+
|
|
729
|
+
const spec = synthesizeHarness(graph, risks);
|
|
730
|
+
expect(spec).toBeDefined();
|
|
731
|
+
expect(spec.graph.nodes.length).toBeGreaterThan(0);
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
it("should still compile bundled patch-validation workflow without steps", () => {
|
|
735
|
+
const skillMd = `
|
|
736
|
+
# Standard Patch Validation
|
|
737
|
+
|
|
738
|
+
workflow: patch-validation
|
|
739
|
+
|
|
740
|
+
## Inputs
|
|
741
|
+
- repoPath: /path/to/repo
|
|
742
|
+
- baselineRef: v1.0.0
|
|
743
|
+
- candidateBranch: fix-branch
|
|
744
|
+
- reproduceCommands: [npm run fail-test]
|
|
745
|
+
- verificationCommands: [npm test]
|
|
746
|
+
- reviewInstructions: Validate the fix
|
|
747
|
+
`;
|
|
748
|
+
|
|
749
|
+
const intentResult = parsePromptOrSkill(skillMd);
|
|
750
|
+
expect(intentResult).toHaveProperty("success", true);
|
|
751
|
+
|
|
752
|
+
if (!("success" in intentResult) || !intentResult.success) return;
|
|
753
|
+
|
|
754
|
+
const graph = buildTaskGraph(intentResult.intent);
|
|
755
|
+
expect(graph.stages.map(s => s.type)).toEqual([
|
|
756
|
+
"setup", "reproduce", "apply", "verify", "review"
|
|
757
|
+
]);
|
|
758
|
+
|
|
759
|
+
const risks = analyzeRisks(graph);
|
|
760
|
+
const spec = synthesizeHarness(graph, risks);
|
|
761
|
+
expect(spec.graph.entryNodeId).toBe("run-baseline");
|
|
762
|
+
});
|
|
763
|
+
|
|
764
|
+
it("should still compile bundled pr-review-merge workflow without steps", () => {
|
|
765
|
+
const skillMd = `
|
|
766
|
+
# Standard PR Review
|
|
767
|
+
|
|
768
|
+
workflow: pr-review-merge
|
|
769
|
+
|
|
770
|
+
## Inputs
|
|
771
|
+
- repoPath: /path/to/repo
|
|
772
|
+
- sourceBranch: feature
|
|
773
|
+
- targetBranch: main
|
|
774
|
+
- reviewInstructions: Check code quality
|
|
775
|
+
|
|
776
|
+
## Verification
|
|
777
|
+
- npm test
|
|
778
|
+
- npm run lint
|
|
779
|
+
`;
|
|
780
|
+
|
|
781
|
+
const intentResult = parsePromptOrSkill(skillMd);
|
|
782
|
+
expect(intentResult).toHaveProperty("success", true);
|
|
783
|
+
|
|
784
|
+
if (!("success" in intentResult) || !intentResult.success) return;
|
|
785
|
+
|
|
786
|
+
const graph = buildTaskGraph(intentResult.intent);
|
|
787
|
+
expect(graph.stages.map(s => s.type)).toEqual([
|
|
788
|
+
"setup", "review", "verify", "merge"
|
|
789
|
+
]);
|
|
790
|
+
|
|
791
|
+
const risks = analyzeRisks(graph);
|
|
792
|
+
const spec = synthesizeHarness(graph, risks);
|
|
793
|
+
expect(spec.graph.entryNodeId).toBe("load-pr");
|
|
794
|
+
});
|
|
795
|
+
});
|
|
796
|
+
});
|