loopforge 1.12.0 → 2.0.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 +172 -95
- package/dist/backends/interface.d.ts +3 -9
- package/dist/backends/interface.d.ts.map +1 -1
- package/dist/backends/interface.js +1 -5
- package/dist/backends/interface.js.map +1 -1
- package/dist/canonical-state.d.ts +53 -0
- package/dist/canonical-state.d.ts.map +1 -0
- package/dist/canonical-state.js +161 -0
- package/dist/canonical-state.js.map +1 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +227 -0
- package/dist/cli.js.map +1 -0
- package/dist/enforcement-gate.d.ts +44 -0
- package/dist/enforcement-gate.d.ts.map +1 -0
- package/dist/enforcement-gate.js +271 -0
- package/dist/enforcement-gate.js.map +1 -0
- package/dist/engine.d.ts +17 -7
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +158 -249
- package/dist/engine.js.map +1 -1
- package/dist/evidence-provider.d.ts +113 -0
- package/dist/evidence-provider.d.ts.map +1 -0
- package/dist/evidence-provider.js +419 -0
- package/dist/evidence-provider.js.map +1 -0
- package/dist/index.d.ts +19 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +16 -10
- package/dist/index.js.map +1 -1
- package/dist/init.d.ts +15 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +49 -0
- package/dist/init.js.map +1 -0
- package/dist/interop.d.ts +30 -0
- package/dist/interop.d.ts.map +1 -0
- package/dist/interop.js +29 -0
- package/dist/interop.js.map +1 -0
- package/dist/loop-compiler.d.ts +14 -64
- package/dist/loop-compiler.d.ts.map +1 -1
- package/dist/loop-compiler.js +307 -2001
- package/dist/loop-compiler.js.map +1 -1
- package/dist/loop-store.d.ts +82 -0
- package/dist/loop-store.d.ts.map +1 -0
- package/dist/loop-store.js +332 -0
- package/dist/loop-store.js.map +1 -0
- package/dist/mcp/server.d.ts +9 -6
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +109 -73
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/session.d.ts +84 -20
- package/dist/mcp/session.d.ts.map +1 -1
- package/dist/mcp/session.js +718 -316
- package/dist/mcp/session.js.map +1 -1
- package/dist/mcp/tools.d.ts +18 -268
- package/dist/mcp/tools.d.ts.map +1 -1
- package/dist/mcp/tools.js +225 -12
- package/dist/mcp/tools.js.map +1 -1
- package/dist/observability.d.ts +36 -16
- package/dist/observability.d.ts.map +1 -1
- package/dist/observability.js +82 -23
- package/dist/observability.js.map +1 -1
- package/dist/policy-metrics.d.ts +46 -0
- package/dist/policy-metrics.d.ts.map +1 -0
- package/dist/policy-metrics.js +146 -0
- package/dist/policy-metrics.js.map +1 -0
- package/dist/policy.d.ts +58 -75
- package/dist/policy.d.ts.map +1 -1
- package/dist/policy.js +122 -93
- package/dist/policy.js.map +1 -1
- package/dist/prompt-assembler.d.ts +30 -0
- package/dist/prompt-assembler.d.ts.map +1 -0
- package/dist/prompt-assembler.js +257 -0
- package/dist/prompt-assembler.js.map +1 -0
- package/dist/prompt-policy.d.ts +29 -0
- package/dist/prompt-policy.d.ts.map +1 -0
- package/dist/prompt-policy.js +51 -0
- package/dist/prompt-policy.js.map +1 -0
- package/dist/protocol.d.ts +117 -91
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +40 -36
- package/dist/protocol.js.map +1 -1
- package/dist/replay.d.ts.map +1 -1
- package/dist/replay.js +1 -27
- package/dist/replay.js.map +1 -1
- package/dist/round-coordinator.d.ts +90 -0
- package/dist/round-coordinator.d.ts.map +1 -0
- package/dist/round-coordinator.js +186 -0
- package/dist/round-coordinator.js.map +1 -0
- package/dist/round-driver.d.ts +53 -0
- package/dist/round-driver.d.ts.map +1 -0
- package/dist/round-driver.js +101 -0
- package/dist/round-driver.js.map +1 -0
- package/dist/round-transaction.d.ts +70 -0
- package/dist/round-transaction.d.ts.map +1 -0
- package/dist/round-transaction.js +290 -0
- package/dist/round-transaction.js.map +1 -0
- package/dist/runtime.d.ts +35 -17
- package/dist/runtime.d.ts.map +1 -1
- package/dist/runtime.js +357 -344
- package/dist/runtime.js.map +1 -1
- package/dist/storage.d.ts +38 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +201 -0
- package/dist/storage.js.map +1 -0
- package/dist/verification-gate.d.ts +39 -7
- package/dist/verification-gate.d.ts.map +1 -1
- package/dist/verification-gate.js +198 -9
- package/dist/verification-gate.js.map +1 -1
- package/loop_policy.json +26 -47
- package/package.json +13 -10
- package/skills/perception/SKILL.md +80 -249
- package/dist/backends/fs.d.ts +0 -36
- package/dist/backends/fs.d.ts.map +0 -1
- package/dist/backends/fs.js +0 -358
- package/dist/backends/fs.js.map +0 -1
- package/dist/builder.d.ts +0 -12
- package/dist/builder.d.ts.map +0 -1
- package/dist/builder.js +0 -259
- package/dist/builder.js.map +0 -1
- package/dist/mcp-server.d.ts +0 -12
- package/dist/mcp-server.d.ts.map +0 -1
- package/dist/mcp-server.js +0 -14
- package/dist/mcp-server.js.map +0 -1
- package/dist/memory-bridge.d.ts +0 -47
- package/dist/memory-bridge.d.ts.map +0 -1
- package/dist/memory-bridge.js +0 -290
- package/dist/memory-bridge.js.map +0 -1
- package/dist/tests/_helpers.d.ts +0 -25
- package/dist/tests/_helpers.d.ts.map +0 -1
- package/dist/tests/_helpers.js +0 -47
- package/dist/tests/_helpers.js.map +0 -1
- package/dist/tests/builder.test.d.ts +0 -2
- package/dist/tests/builder.test.d.ts.map +0 -1
- package/dist/tests/builder.test.js +0 -110
- package/dist/tests/builder.test.js.map +0 -1
- package/dist/tests/engine.test.d.ts +0 -2
- package/dist/tests/engine.test.d.ts.map +0 -1
- package/dist/tests/engine.test.js +0 -371
- package/dist/tests/engine.test.js.map +0 -1
- package/dist/tests/generate-schema.test.d.ts +0 -8
- package/dist/tests/generate-schema.test.d.ts.map +0 -1
- package/dist/tests/generate-schema.test.js +0 -199
- package/dist/tests/generate-schema.test.js.map +0 -1
- package/dist/tests/loop-compiler.test.d.ts +0 -2
- package/dist/tests/loop-compiler.test.d.ts.map +0 -1
- package/dist/tests/loop-compiler.test.js +0 -1051
- package/dist/tests/loop-compiler.test.js.map +0 -1
- package/dist/tests/mcp.test.d.ts +0 -7
- package/dist/tests/mcp.test.d.ts.map +0 -1
- package/dist/tests/mcp.test.js +0 -485
- package/dist/tests/mcp.test.js.map +0 -1
- package/dist/tests/memory-bridge.test.d.ts +0 -8
- package/dist/tests/memory-bridge.test.d.ts.map +0 -1
- package/dist/tests/memory-bridge.test.js +0 -383
- package/dist/tests/memory-bridge.test.js.map +0 -1
- package/dist/tests/policy.test.d.ts +0 -2
- package/dist/tests/policy.test.d.ts.map +0 -1
- package/dist/tests/policy.test.js +0 -49
- package/dist/tests/policy.test.js.map +0 -1
- package/dist/tests/protocol.test.d.ts +0 -2
- package/dist/tests/protocol.test.d.ts.map +0 -1
- package/dist/tests/protocol.test.js +0 -73
- package/dist/tests/protocol.test.js.map +0 -1
- package/dist/tests/replay.test.d.ts +0 -2
- package/dist/tests/replay.test.d.ts.map +0 -1
- package/dist/tests/replay.test.js +0 -222
- package/dist/tests/replay.test.js.map +0 -1
- package/dist/tests/runtime.test.d.ts +0 -7
- package/dist/tests/runtime.test.d.ts.map +0 -1
- package/dist/tests/runtime.test.js +0 -323
- package/dist/tests/runtime.test.js.map +0 -1
- package/dist/tests/verification-gate.test.d.ts +0 -2
- package/dist/tests/verification-gate.test.d.ts.map +0 -1
- package/dist/tests/verification-gate.test.js +0 -407
- package/dist/tests/verification-gate.test.js.map +0 -1
- package/skills/prompt-techniques/SKILL.md +0 -52
- package/skills/prompt-techniques/references/chain-of-thought.md +0 -207
- package/skills/prompt-techniques/references/few-shot.md +0 -174
- package/skills/prompt-techniques/references/least-to-most.md +0 -182
- package/skills/prompt-techniques/references/step-back.md +0 -160
- package/skills/prompt-techniques/references/tree-of-thought.md +0 -191
- package/skills/prompt-techniques/references/zero-shot.md +0 -97
|
@@ -1,1051 +0,0 @@
|
|
|
1
|
-
/** Tests for loop compiler core: gates, advisories, L0/L1/L2 compilation. */
|
|
2
|
-
import { describe, it, beforeEach } from "node:test";
|
|
3
|
-
import assert from "node:assert/strict";
|
|
4
|
-
import { decideLevel, compileLoop, computeGoalTextHash, deriveGoalId, alignTask, compileL2, buildRollingSummary, formatRollingSummaryForPrompt, buildCheckpointSummary, formatCheckpointForPrompt, filterConstraintsForSubTask, formatDelegationPrompt, buildDelegationSummary, } from "../loop-compiler.js";
|
|
5
|
-
import { makeLoopCompileRequest, makeLoopRoundResult, makeLoopObjective, makeRollingSummary, makeCheckpointSummary, makeSelfEvaluation, } from "../protocol.js";
|
|
6
|
-
import { resetPolicy } from "../policy.js";
|
|
7
|
-
describe("Loop Compiler — Goal Identity", () => {
|
|
8
|
-
it("computeGoalTextHash produces a 12-char hex string", () => {
|
|
9
|
-
const hash = computeGoalTextHash("Audit ERC20 token");
|
|
10
|
-
assert.equal(hash.length, 12);
|
|
11
|
-
assert.ok(/^[a-f0-9]{12}$/.test(hash));
|
|
12
|
-
});
|
|
13
|
-
it("computeGoalTextHash is deterministic", () => {
|
|
14
|
-
assert.equal(computeGoalTextHash("Audit ERC20 token"), computeGoalTextHash("Audit ERC20 token"));
|
|
15
|
-
});
|
|
16
|
-
it("computeGoalTextHash normalises whitespace", () => {
|
|
17
|
-
const h1 = computeGoalTextHash("Audit ERC20 token");
|
|
18
|
-
const h2 = computeGoalTextHash("Audit ERC20 token");
|
|
19
|
-
assert.equal(h1, h2);
|
|
20
|
-
});
|
|
21
|
-
it("deriveGoalId uses explicit goal_id when provided", () => {
|
|
22
|
-
assert.equal(deriveGoalId("loop-1", "Audit ERC20", "audit-erc20"), "audit-erc20");
|
|
23
|
-
});
|
|
24
|
-
it("deriveGoalId falls back to derived id", () => {
|
|
25
|
-
const id = deriveGoalId("loop-1", "Audit ERC20 token");
|
|
26
|
-
assert.ok(id.startsWith("loop-1:"));
|
|
27
|
-
});
|
|
28
|
-
});
|
|
29
|
-
describe("Loop Compiler — Decide Level (4-gate router)", () => {
|
|
30
|
-
beforeEach(() => resetPolicy());
|
|
31
|
-
it("returns l2 for round 1 (Gate 2: first call)", () => {
|
|
32
|
-
const req = makeLoopCompileRequest({ round: 1, task: "Audit ERC20" });
|
|
33
|
-
assert.equal(decideLevel(req, null), "l2");
|
|
34
|
-
});
|
|
35
|
-
it("returns l2 when plan_source is provided (Gate 2)", () => {
|
|
36
|
-
const req = makeLoopCompileRequest({
|
|
37
|
-
round: 5,
|
|
38
|
-
task: "Audit ERC20",
|
|
39
|
-
plan_source: "spec.md",
|
|
40
|
-
});
|
|
41
|
-
assert.equal(decideLevel(req, null), "l2");
|
|
42
|
-
});
|
|
43
|
-
it("returns l2 when no previous round found", () => {
|
|
44
|
-
const req = makeLoopCompileRequest({ round: 3, task: "Audit ERC20" });
|
|
45
|
-
assert.equal(decideLevel(req, null), "l2");
|
|
46
|
-
});
|
|
47
|
-
it("returns l2 when goal_id changed (Gate 3)", () => {
|
|
48
|
-
const req = makeLoopCompileRequest({
|
|
49
|
-
round: 2,
|
|
50
|
-
loop_id: "test",
|
|
51
|
-
goal_id: "new-audit",
|
|
52
|
-
task: "Audit new contract",
|
|
53
|
-
});
|
|
54
|
-
const vaultContext = {
|
|
55
|
-
results: [
|
|
56
|
-
{
|
|
57
|
-
loop_lineage: {
|
|
58
|
-
loop_id: "test",
|
|
59
|
-
round: 1,
|
|
60
|
-
goal_id: "old-audit",
|
|
61
|
-
},
|
|
62
|
-
},
|
|
63
|
-
],
|
|
64
|
-
};
|
|
65
|
-
// goal_id "new-audit" doesn't match prev "old-audit"
|
|
66
|
-
// But wait: deriveGoalId uses explicit goal_id first, and prev.goal_id is "old-audit"
|
|
67
|
-
// deriveGoalId("test", "Audit new contract", "new-audit") = "new-audit"
|
|
68
|
-
// prev.goal_id = "old-audit" → mismatch → L2
|
|
69
|
-
assert.equal(decideLevel(req, vaultContext), "l2");
|
|
70
|
-
});
|
|
71
|
-
it("returns l1 when new constraints added (Gate 4)", () => {
|
|
72
|
-
const req = makeLoopCompileRequest({
|
|
73
|
-
round: 2,
|
|
74
|
-
loop_id: "test",
|
|
75
|
-
goal_id: "audit",
|
|
76
|
-
task: "Audit ERC20",
|
|
77
|
-
constraints_from_plan: ["check flash loans"],
|
|
78
|
-
});
|
|
79
|
-
const vaultContext = {
|
|
80
|
-
results: [
|
|
81
|
-
{
|
|
82
|
-
loop_lineage: {
|
|
83
|
-
loop_id: "test",
|
|
84
|
-
round: 1,
|
|
85
|
-
goal_id: "audit",
|
|
86
|
-
},
|
|
87
|
-
},
|
|
88
|
-
],
|
|
89
|
-
};
|
|
90
|
-
assert.equal(decideLevel(req, vaultContext), "l1");
|
|
91
|
-
});
|
|
92
|
-
it("returns l1 when last round failed (Gate 4)", () => {
|
|
93
|
-
const req = makeLoopCompileRequest({
|
|
94
|
-
round: 2,
|
|
95
|
-
loop_id: "test",
|
|
96
|
-
goal_id: "audit",
|
|
97
|
-
task: "Audit ERC20",
|
|
98
|
-
last_round_result: makeLoopRoundResult({ success: false }),
|
|
99
|
-
});
|
|
100
|
-
const vaultContext = {
|
|
101
|
-
results: [
|
|
102
|
-
{
|
|
103
|
-
loop_lineage: {
|
|
104
|
-
loop_id: "test",
|
|
105
|
-
round: 1,
|
|
106
|
-
goal_id: "audit",
|
|
107
|
-
},
|
|
108
|
-
},
|
|
109
|
-
],
|
|
110
|
-
};
|
|
111
|
-
assert.equal(decideLevel(req, vaultContext), "l1");
|
|
112
|
-
});
|
|
113
|
-
it("returns l1 with repair signal (Gate 4)", () => {
|
|
114
|
-
const req = makeLoopCompileRequest({
|
|
115
|
-
round: 2,
|
|
116
|
-
loop_id: "test",
|
|
117
|
-
goal_id: "audit",
|
|
118
|
-
task: "Audit ERC20",
|
|
119
|
-
new_since_last_round: "fix the approve race condition bug",
|
|
120
|
-
});
|
|
121
|
-
const vaultContext = {
|
|
122
|
-
results: [
|
|
123
|
-
{
|
|
124
|
-
loop_lineage: {
|
|
125
|
-
loop_id: "test",
|
|
126
|
-
round: 1,
|
|
127
|
-
goal_id: "audit",
|
|
128
|
-
},
|
|
129
|
-
},
|
|
130
|
-
],
|
|
131
|
-
};
|
|
132
|
-
assert.equal(decideLevel(req, vaultContext), "l1");
|
|
133
|
-
});
|
|
134
|
-
it("returns l0 when nothing triggered (Gate 4: fast path)", () => {
|
|
135
|
-
const req = makeLoopCompileRequest({
|
|
136
|
-
round: 2,
|
|
137
|
-
loop_id: "test",
|
|
138
|
-
goal_id: "audit",
|
|
139
|
-
task: "Audit ERC20",
|
|
140
|
-
});
|
|
141
|
-
const vaultContext = {
|
|
142
|
-
results: [
|
|
143
|
-
{
|
|
144
|
-
loop_lineage: {
|
|
145
|
-
loop_id: "test",
|
|
146
|
-
round: 1,
|
|
147
|
-
goal_id: "audit",
|
|
148
|
-
},
|
|
149
|
-
},
|
|
150
|
-
],
|
|
151
|
-
};
|
|
152
|
-
assert.equal(decideLevel(req, vaultContext), "l0");
|
|
153
|
-
});
|
|
154
|
-
it("respects force_level override (Gate 1)", () => {
|
|
155
|
-
const req = makeLoopCompileRequest({
|
|
156
|
-
round: 3,
|
|
157
|
-
loop_id: "test",
|
|
158
|
-
goal_id: "audit",
|
|
159
|
-
task: "Audit ERC20",
|
|
160
|
-
force_level: "l0",
|
|
161
|
-
});
|
|
162
|
-
const vaultContext = {
|
|
163
|
-
results: [
|
|
164
|
-
{
|
|
165
|
-
loop_lineage: {
|
|
166
|
-
loop_id: "test",
|
|
167
|
-
round: 2,
|
|
168
|
-
goal_id: "audit",
|
|
169
|
-
},
|
|
170
|
-
},
|
|
171
|
-
],
|
|
172
|
-
};
|
|
173
|
-
assert.equal(decideLevel(req, vaultContext), "l0");
|
|
174
|
-
});
|
|
175
|
-
it("force_level never overrides round 1 (Gate 1 exception)", () => {
|
|
176
|
-
const req = makeLoopCompileRequest({
|
|
177
|
-
round: 1,
|
|
178
|
-
task: "Audit ERC20",
|
|
179
|
-
force_level: "l0",
|
|
180
|
-
});
|
|
181
|
-
assert.equal(decideLevel(req, null), "l2");
|
|
182
|
-
});
|
|
183
|
-
it("force_level never overrides plan_source (Gate 1 exception)", () => {
|
|
184
|
-
const req = makeLoopCompileRequest({
|
|
185
|
-
round: 3,
|
|
186
|
-
task: "Audit ERC20",
|
|
187
|
-
plan_source: "plan.md",
|
|
188
|
-
force_level: "l0",
|
|
189
|
-
});
|
|
190
|
-
assert.equal(decideLevel(req, null), "l2");
|
|
191
|
-
});
|
|
192
|
-
});
|
|
193
|
-
describe("Loop Compiler — L0 Fast Path", () => {
|
|
194
|
-
it("reuses cached prompt from previous round", () => {
|
|
195
|
-
const req = makeLoopCompileRequest({
|
|
196
|
-
round: 2,
|
|
197
|
-
loop_id: "test",
|
|
198
|
-
goal_id: "audit",
|
|
199
|
-
task: "Continue audit",
|
|
200
|
-
});
|
|
201
|
-
const vaultContext = {
|
|
202
|
-
results: [
|
|
203
|
-
{
|
|
204
|
-
loop_lineage: {
|
|
205
|
-
loop_id: "test",
|
|
206
|
-
round: 1,
|
|
207
|
-
goal_id: "audit",
|
|
208
|
-
constraints_active: ["check ownership"],
|
|
209
|
-
},
|
|
210
|
-
full_prompt: "## Round 1 Prompt\n\nAudit the contract.",
|
|
211
|
-
},
|
|
212
|
-
],
|
|
213
|
-
};
|
|
214
|
-
const response = compileLoop(req, vaultContext);
|
|
215
|
-
assert.equal(response.recompile_level, "l0");
|
|
216
|
-
assert.ok(response.prompt.includes("Round 1 Prompt"));
|
|
217
|
-
assert.equal(response.technique_used, "cached");
|
|
218
|
-
assert.deepEqual(response.constraints_active, ["check ownership"]);
|
|
219
|
-
});
|
|
220
|
-
});
|
|
221
|
-
describe("Loop Compiler — L1 Patch Path", () => {
|
|
222
|
-
it("patches prompt with new constraints", () => {
|
|
223
|
-
const req = makeLoopCompileRequest({
|
|
224
|
-
round: 2,
|
|
225
|
-
loop_id: "test",
|
|
226
|
-
goal_id: "audit",
|
|
227
|
-
task: "Check approve race condition",
|
|
228
|
-
constraints_from_plan: ["check flash loans"],
|
|
229
|
-
new_since_last_round: "fix the approve race",
|
|
230
|
-
});
|
|
231
|
-
const vaultContext = {
|
|
232
|
-
results: [
|
|
233
|
-
{
|
|
234
|
-
loop_lineage: {
|
|
235
|
-
loop_id: "test",
|
|
236
|
-
round: 1,
|
|
237
|
-
goal_id: "audit",
|
|
238
|
-
constraints_active: ["check ownership"],
|
|
239
|
-
},
|
|
240
|
-
},
|
|
241
|
-
],
|
|
242
|
-
};
|
|
243
|
-
const response = compileLoop(req, vaultContext);
|
|
244
|
-
assert.equal(response.recompile_level, "l1");
|
|
245
|
-
assert.ok(response.prompt.includes("L1 Patch"));
|
|
246
|
-
assert.ok(response.prompt.includes("check flash loans"));
|
|
247
|
-
assert.ok(response.prompt.includes("check ownership"));
|
|
248
|
-
assert.equal(response.technique_used, "patch");
|
|
249
|
-
});
|
|
250
|
-
});
|
|
251
|
-
describe("Loop Compiler — L2 Full Recompile", () => {
|
|
252
|
-
it("generates full meta-instruction prompt", () => {
|
|
253
|
-
const req = makeLoopCompileRequest({
|
|
254
|
-
round: 1,
|
|
255
|
-
loop_id: "audit-erc20",
|
|
256
|
-
goal_id: "audit-erc20",
|
|
257
|
-
task: "Audit ERC20 token for security vulnerabilities",
|
|
258
|
-
domain: "solidity-security",
|
|
259
|
-
});
|
|
260
|
-
const response = compileL2(req, null);
|
|
261
|
-
assert.equal(response.recompile_level, "l2");
|
|
262
|
-
assert.ok(response.prompt.includes("L2 Compile"));
|
|
263
|
-
assert.ok(response.prompt.includes("Loop Identity"));
|
|
264
|
-
assert.ok(response.prompt.includes("Generation Instructions"));
|
|
265
|
-
assert.notEqual(response.technique_used, "");
|
|
266
|
-
assert.notEqual(response.goal_text_hash, "");
|
|
267
|
-
assert.equal(response.round, 1);
|
|
268
|
-
assert.equal(response.loop_id, "audit-erc20");
|
|
269
|
-
});
|
|
270
|
-
it("includes loop objective at round 1", () => {
|
|
271
|
-
const req = makeLoopCompileRequest({
|
|
272
|
-
round: 1,
|
|
273
|
-
loop_id: "test",
|
|
274
|
-
task: "Audit ERC20 token",
|
|
275
|
-
loop_objective: makeLoopObjective({
|
|
276
|
-
objective: "Find all vulnerabilities",
|
|
277
|
-
success_criteria: ["All tests pass"],
|
|
278
|
-
hard_constraints: ["No external deps"],
|
|
279
|
-
loop_id: "test",
|
|
280
|
-
}),
|
|
281
|
-
});
|
|
282
|
-
const response = compileL2(req, null);
|
|
283
|
-
assert.ok(response.prompt.includes("Loop Objective (Anchor)"));
|
|
284
|
-
assert.ok(response.prompt.includes("Find all vulnerabilities"));
|
|
285
|
-
assert.ok(response.loop_objective !== null);
|
|
286
|
-
});
|
|
287
|
-
it("merges success_criteria into constraints_active alongside hard_constraints", () => {
|
|
288
|
-
const req = makeLoopCompileRequest({
|
|
289
|
-
round: 1,
|
|
290
|
-
loop_id: "test",
|
|
291
|
-
task: "Audit ERC20 token",
|
|
292
|
-
constraints_from_plan: ["check ownership"],
|
|
293
|
-
loop_objective: makeLoopObjective({
|
|
294
|
-
objective: "Find all vulnerabilities",
|
|
295
|
-
success_criteria: ["All tests pass", "No高危漏洞"],
|
|
296
|
-
hard_constraints: ["No external deps"],
|
|
297
|
-
loop_id: "test",
|
|
298
|
-
}),
|
|
299
|
-
});
|
|
300
|
-
const response = compileL2(req, null);
|
|
301
|
-
assert.ok(response.constraints_active.includes("check ownership"));
|
|
302
|
-
assert.ok(response.constraints_active.includes("No external deps"));
|
|
303
|
-
assert.ok(response.constraints_active.includes("All tests pass"));
|
|
304
|
-
assert.ok(response.constraints_active.includes("No高危漏洞"));
|
|
305
|
-
// All three sources should be merged without duplicates
|
|
306
|
-
assert.equal(response.constraints_active.length, 4, "Expected 4 unique constraints: 1 plan + 1 hard + 2 success");
|
|
307
|
-
});
|
|
308
|
-
it("auto-generates loop objective at round 1 when none provided", () => {
|
|
309
|
-
const req = makeLoopCompileRequest({
|
|
310
|
-
round: 1,
|
|
311
|
-
loop_id: "test",
|
|
312
|
-
task: "Test the API endpoints for security issues",
|
|
313
|
-
});
|
|
314
|
-
const response = compileL2(req, null);
|
|
315
|
-
assert.ok(response.loop_objective !== null);
|
|
316
|
-
assert.ok(response.loop_objective.objective.length > 0);
|
|
317
|
-
});
|
|
318
|
-
});
|
|
319
|
-
describe("Loop Compiler — Task Alignment (advisory)", () => {
|
|
320
|
-
it("returns default alignment when no loop objective", () => {
|
|
321
|
-
const req = makeLoopCompileRequest({ loop_id: "test", task: "Audit" });
|
|
322
|
-
const result = alignTask("Proposed task", req, null);
|
|
323
|
-
assert.equal(result.is_aligned, true);
|
|
324
|
-
assert.equal(result.escalation, "none");
|
|
325
|
-
});
|
|
326
|
-
it("warns when proposed task drifts from objective", () => {
|
|
327
|
-
const req = makeLoopCompileRequest({
|
|
328
|
-
loop_id: "test",
|
|
329
|
-
task: "Audit ERC20",
|
|
330
|
-
loop_objective: makeLoopObjective({
|
|
331
|
-
objective: "Audit ERC20 token for all security vulnerabilities",
|
|
332
|
-
success_criteria: ["Find all critical vulnerabilities"],
|
|
333
|
-
hard_constraints: ["No external dependencies"],
|
|
334
|
-
}),
|
|
335
|
-
});
|
|
336
|
-
const result = alignTask("Write a frontend UI for the token dashboard", req, null);
|
|
337
|
-
assert.equal(result.escalation, "block");
|
|
338
|
-
assert.equal(result.is_aligned, false);
|
|
339
|
-
});
|
|
340
|
-
});
|
|
341
|
-
describe("Loop Compiler — Compile Loop (top-level)", () => {
|
|
342
|
-
it("returns complete response with advisories", () => {
|
|
343
|
-
const req = makeLoopCompileRequest({
|
|
344
|
-
round: 1,
|
|
345
|
-
loop_id: "test",
|
|
346
|
-
task: "Test the API",
|
|
347
|
-
next_task_proposal: "Test the database",
|
|
348
|
-
loop_objective: makeLoopObjective({
|
|
349
|
-
objective: "Test all APIs",
|
|
350
|
-
success_criteria: ["All tests pass"],
|
|
351
|
-
hard_constraints: [],
|
|
352
|
-
}),
|
|
353
|
-
});
|
|
354
|
-
const response = compileLoop(req, null);
|
|
355
|
-
assert.equal(response.status, "ok");
|
|
356
|
-
assert.equal(response.recompile_level, "l2");
|
|
357
|
-
assert.notEqual(response.goal_text_hash.length, 0);
|
|
358
|
-
assert.ok(response.task_alignment !== null);
|
|
359
|
-
});
|
|
360
|
-
});
|
|
361
|
-
describe("Loop Compiler — Cross-Round Scenarios", () => {
|
|
362
|
-
it("L0→L1 escalation: goal_id stable, new constraints added", () => {
|
|
363
|
-
const vaultContext = {
|
|
364
|
-
results: [
|
|
365
|
-
{
|
|
366
|
-
loop_lineage: {
|
|
367
|
-
loop_id: "audit",
|
|
368
|
-
round: 1,
|
|
369
|
-
goal_id: "audit",
|
|
370
|
-
constraints_active: ["check ownership"],
|
|
371
|
-
},
|
|
372
|
-
},
|
|
373
|
-
],
|
|
374
|
-
};
|
|
375
|
-
// Round 2: same semantic task → deriveGoalId will match
|
|
376
|
-
const req = makeLoopCompileRequest({
|
|
377
|
-
round: 2,
|
|
378
|
-
loop_id: "audit",
|
|
379
|
-
goal_id: "audit",
|
|
380
|
-
task: "Audit ERC20 token for security vulnerabilities",
|
|
381
|
-
constraints_from_plan: ["check flash loans"],
|
|
382
|
-
});
|
|
383
|
-
const level = decideLevel(req, vaultContext);
|
|
384
|
-
assert.equal(level, "l1");
|
|
385
|
-
});
|
|
386
|
-
it("L0 stability: multiple rounds with same goal_id, no failures", () => {
|
|
387
|
-
const vaultContext = {
|
|
388
|
-
results: [
|
|
389
|
-
{
|
|
390
|
-
loop_lineage: {
|
|
391
|
-
loop_id: "audit",
|
|
392
|
-
round: 2,
|
|
393
|
-
goal_id: "audit",
|
|
394
|
-
},
|
|
395
|
-
},
|
|
396
|
-
],
|
|
397
|
-
};
|
|
398
|
-
const req = makeLoopCompileRequest({
|
|
399
|
-
round: 3,
|
|
400
|
-
loop_id: "audit",
|
|
401
|
-
goal_id: "audit",
|
|
402
|
-
task: "Audit ERC20",
|
|
403
|
-
});
|
|
404
|
-
const level = decideLevel(req, vaultContext);
|
|
405
|
-
assert.equal(level, "l0");
|
|
406
|
-
});
|
|
407
|
-
});
|
|
408
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
409
|
-
// Failure Lineage Weighting (v1.7)
|
|
410
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
411
|
-
function makeVaultResult(loopId, round, qualityScore, task, outputSummary = "", techniqueUsed = "zero-shot", constraintViolations = []) {
|
|
412
|
-
const success = qualityScore >= 3;
|
|
413
|
-
return {
|
|
414
|
-
task,
|
|
415
|
-
output_summary: outputSummary,
|
|
416
|
-
constraint_violations: constraintViolations,
|
|
417
|
-
technique_used: techniqueUsed,
|
|
418
|
-
success,
|
|
419
|
-
loop_lineage: {
|
|
420
|
-
loop_id: loopId,
|
|
421
|
-
round,
|
|
422
|
-
success,
|
|
423
|
-
task,
|
|
424
|
-
technique_used: techniqueUsed,
|
|
425
|
-
},
|
|
426
|
-
};
|
|
427
|
-
}
|
|
428
|
-
describe("Failure Lineage Weighting — buildRollingSummary", () => {
|
|
429
|
-
it("no failure patterns when all rounds score >= 4", () => {
|
|
430
|
-
const vault = {
|
|
431
|
-
results: [
|
|
432
|
-
makeVaultResult("loop-1", 1, 5, "fix auth bug", "Fixed auth by updating middleware"),
|
|
433
|
-
makeVaultResult("loop-1", 2, 4, "add tests for auth", "Added 12 test cases"),
|
|
434
|
-
makeVaultResult("loop-1", 3, 5, "refactor auth module", "Cleaned up auth module"),
|
|
435
|
-
],
|
|
436
|
-
global_entries: [],
|
|
437
|
-
};
|
|
438
|
-
const rs = buildRollingSummary("loop-1", 4, vault);
|
|
439
|
-
assert.ok(rs !== null);
|
|
440
|
-
assert.equal(rs.failed_patterns?.length ?? 0, 0);
|
|
441
|
-
// key_outcomes should not have [Consider alternatives] marker
|
|
442
|
-
for (const ko of rs.key_outcomes) {
|
|
443
|
-
assert.ok(!ko.includes("[Consider alternatives]"));
|
|
444
|
-
}
|
|
445
|
-
});
|
|
446
|
-
it("detects 2 consecutive failed rounds with same technique and similar task", () => {
|
|
447
|
-
const vault = {
|
|
448
|
-
results: [
|
|
449
|
-
makeVaultResult("loop-1", 1, 2, "fix cache bug", "Tried cache fix A", "zero-shot"),
|
|
450
|
-
makeVaultResult("loop-1", 2, 2, "fix cache problem", "Tried cache fix B", "zero-shot"),
|
|
451
|
-
makeVaultResult("loop-1", 3, 5, "found auth root cause", "Auth middleware was the issue", "step-back"),
|
|
452
|
-
],
|
|
453
|
-
global_entries: [],
|
|
454
|
-
};
|
|
455
|
-
const rs = buildRollingSummary("loop-1", 4, vault);
|
|
456
|
-
assert.ok(rs !== null);
|
|
457
|
-
assert.ok((rs.failed_patterns?.length ?? 0) >= 1);
|
|
458
|
-
const fp = rs.failed_patterns[0];
|
|
459
|
-
assert.ok(fp.includes("zero-shot"));
|
|
460
|
-
assert.ok(fp.includes("cache"));
|
|
461
|
-
assert.ok(fp.includes("2 consecutive rounds"));
|
|
462
|
-
});
|
|
463
|
-
it("detects 3 consecutive failed rounds", () => {
|
|
464
|
-
const vault = {
|
|
465
|
-
results: [
|
|
466
|
-
makeVaultResult("loop-1", 1, 2, "fix memory leak", "Attempt 1", "few-shot"),
|
|
467
|
-
makeVaultResult("loop-1", 2, 1, "fix memory leak in parser", "Attempt 2", "few-shot"),
|
|
468
|
-
makeVaultResult("loop-1", 3, 2, "fix memory leak - retry", "Attempt 3", "few-shot"),
|
|
469
|
-
],
|
|
470
|
-
global_entries: [],
|
|
471
|
-
};
|
|
472
|
-
const rs = buildRollingSummary("loop-1", 4, vault);
|
|
473
|
-
assert.ok(rs !== null);
|
|
474
|
-
assert.ok((rs.failed_patterns?.length ?? 0) >= 1);
|
|
475
|
-
assert.ok(rs.failed_patterns[0].includes("3 consecutive rounds"));
|
|
476
|
-
});
|
|
477
|
-
it("no pattern when failed rounds use different techniques", () => {
|
|
478
|
-
const vault = {
|
|
479
|
-
results: [
|
|
480
|
-
makeVaultResult("loop-1", 1, 2, "fix cache bug", "Attempt", "zero-shot"),
|
|
481
|
-
makeVaultResult("loop-1", 2, 2, "fix cache bug", "Attempt", "tree-of-thought"),
|
|
482
|
-
],
|
|
483
|
-
global_entries: [],
|
|
484
|
-
};
|
|
485
|
-
const rs = buildRollingSummary("loop-1", 3, vault);
|
|
486
|
-
assert.ok(rs !== null);
|
|
487
|
-
assert.equal(rs.failed_patterns?.length ?? 0, 0);
|
|
488
|
-
});
|
|
489
|
-
it("no pattern when failed tasks are completely different (Jaccard < 0.4)", () => {
|
|
490
|
-
const vault = {
|
|
491
|
-
results: [
|
|
492
|
-
makeVaultResult("loop-1", 1, 2, "fix cache bug in Redis layer", "Attempt", "zero-shot"),
|
|
493
|
-
makeVaultResult("loop-1", 2, 2, "rewrite authentication module entirely", "Attempt", "zero-shot"),
|
|
494
|
-
],
|
|
495
|
-
global_entries: [],
|
|
496
|
-
};
|
|
497
|
-
const rs = buildRollingSummary("loop-1", 3, vault);
|
|
498
|
-
assert.ok(rs !== null);
|
|
499
|
-
assert.equal(rs.failed_patterns?.length ?? 0, 0);
|
|
500
|
-
});
|
|
501
|
-
it("key_outcomes from failure-pattern rounds are demoted to end with [Consider alternatives]", () => {
|
|
502
|
-
const vault = {
|
|
503
|
-
results: [
|
|
504
|
-
makeVaultResult("loop-1", 1, 2, "fix cache bug", "Tried cache approach", "zero-shot"),
|
|
505
|
-
makeVaultResult("loop-1", 2, 2, "fix cache problem", "Tried another cache fix", "zero-shot"),
|
|
506
|
-
makeVaultResult("loop-1", 3, 5, "found real issue in auth", "Auth was the real problem — fixed", "step-back", ["Auth bug resolved"]),
|
|
507
|
-
],
|
|
508
|
-
global_entries: [],
|
|
509
|
-
};
|
|
510
|
-
const rs = buildRollingSummary("loop-1", 4, vault);
|
|
511
|
-
assert.ok(rs !== null);
|
|
512
|
-
// key_outcomes only include score>=4 rounds (failure-pattern rounds score<3 don't appear)
|
|
513
|
-
// Round 3 (score=5) should be present and not marked
|
|
514
|
-
for (const ko of rs.key_outcomes) {
|
|
515
|
-
if (ko.includes("cache")) {
|
|
516
|
-
assert.ok(ko.includes("[Consider alternatives]"));
|
|
517
|
-
}
|
|
518
|
-
}
|
|
519
|
-
// Round 3 (score=5) should be present with auth fix outcome
|
|
520
|
-
assert.ok(rs.key_outcomes.some((ko) => ko.includes("Auth")));
|
|
521
|
-
});
|
|
522
|
-
it("recurring_issues from failure-only rounds marked as [Possible dead end]", () => {
|
|
523
|
-
const vault = {
|
|
524
|
-
results: [
|
|
525
|
-
makeVaultResult("loop-1", 1, 2, "fix cache bug", "Attempt 1", "zero-shot", ["missed edge case"]),
|
|
526
|
-
makeVaultResult("loop-1", 2, 2, "fix cache problem", "Attempt 2", "zero-shot", ["missed edge case"]),
|
|
527
|
-
makeVaultResult("loop-1", 3, 5, "found auth root cause", "Fixed auth", "step-back", ["auth constraint missed"]),
|
|
528
|
-
makeVaultResult("loop-1", 4, 4, "add auth tests", "Added tests", "least-to-most", ["auth constraint missed"]),
|
|
529
|
-
],
|
|
530
|
-
global_entries: [],
|
|
531
|
-
};
|
|
532
|
-
const rs = buildRollingSummary("loop-1", 5, vault);
|
|
533
|
-
assert.ok(rs !== null);
|
|
534
|
-
// "missed edge case" only appears in failed rounds (1,2) → should be marked
|
|
535
|
-
const edgeCaseIssue = rs.recurring_issues.find((ri) => ri.includes("missed edge case"));
|
|
536
|
-
assert.ok(edgeCaseIssue !== undefined);
|
|
537
|
-
assert.ok(edgeCaseIssue.includes("[Possible dead end"));
|
|
538
|
-
// "auth constraint missed" appears in both failed (3) and successful (4) → NOT marked
|
|
539
|
-
const authIssue = rs.recurring_issues.find((ri) => ri.includes("auth constraint missed"));
|
|
540
|
-
assert.ok(authIssue !== undefined);
|
|
541
|
-
assert.ok(!authIssue.includes("[Possible dead end"));
|
|
542
|
-
});
|
|
543
|
-
it("empty vault returns null", () => {
|
|
544
|
-
const rs = buildRollingSummary("loop-1", 1, { results: [], global_entries: [] });
|
|
545
|
-
assert.equal(rs, null);
|
|
546
|
-
});
|
|
547
|
-
it("single round returns no failure patterns", () => {
|
|
548
|
-
const vault = {
|
|
549
|
-
results: [
|
|
550
|
-
makeVaultResult("loop-1", 1, 2, "fix cache bug", "Attempt", "zero-shot"),
|
|
551
|
-
],
|
|
552
|
-
global_entries: [],
|
|
553
|
-
};
|
|
554
|
-
const rs = buildRollingSummary("loop-1", 2, vault);
|
|
555
|
-
assert.ok(rs !== null);
|
|
556
|
-
assert.equal(rs.failed_patterns?.length ?? 0, 0);
|
|
557
|
-
});
|
|
558
|
-
});
|
|
559
|
-
describe("Failure Lineage Weighting — formatRollingSummaryForPrompt", () => {
|
|
560
|
-
it("renders failure patterns section when present", () => {
|
|
561
|
-
const rs = makeRollingSummary({
|
|
562
|
-
key_outcomes: ["[R3] ✓ (step-back): Fixed auth bug"],
|
|
563
|
-
recurring_issues: [],
|
|
564
|
-
rounds_sampled: 3,
|
|
565
|
-
generated_at_round: 4,
|
|
566
|
-
failed_patterns: [
|
|
567
|
-
"technique 'zero-shot' on task 'fix cache bug' failed 2 consecutive rounds (R1-R2) — consider strategy change",
|
|
568
|
-
],
|
|
569
|
-
});
|
|
570
|
-
const text = formatRollingSummaryForPrompt(rs);
|
|
571
|
-
assert.ok(text.includes("### ⚠️ Failure Patterns"));
|
|
572
|
-
assert.ok(text.includes("Consider Strategy Change"));
|
|
573
|
-
assert.ok(text.includes("fix cache bug"));
|
|
574
|
-
assert.ok(text.includes("🚫"));
|
|
575
|
-
});
|
|
576
|
-
it("no failure patterns section when empty", () => {
|
|
577
|
-
const rs = makeRollingSummary({
|
|
578
|
-
key_outcomes: [],
|
|
579
|
-
recurring_issues: [],
|
|
580
|
-
rounds_sampled: 2,
|
|
581
|
-
generated_at_round: 3,
|
|
582
|
-
failed_patterns: [],
|
|
583
|
-
});
|
|
584
|
-
const text = formatRollingSummaryForPrompt(rs);
|
|
585
|
-
assert.ok(!text.includes("Failure Patterns"));
|
|
586
|
-
});
|
|
587
|
-
it("returns empty string for null or empty summary", () => {
|
|
588
|
-
assert.equal(formatRollingSummaryForPrompt(null), "");
|
|
589
|
-
const empty = makeRollingSummary({ rounds_sampled: 0 });
|
|
590
|
-
assert.equal(formatRollingSummaryForPrompt(empty), "");
|
|
591
|
-
});
|
|
592
|
-
});
|
|
593
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
594
|
-
// Delegation Helpers (v1.9 — AgentTool mode)
|
|
595
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
596
|
-
describe("Delegation Helpers — filterConstraintsForSubTask", () => {
|
|
597
|
-
const constraints = [
|
|
598
|
-
"all APIs must have rate limiting",
|
|
599
|
-
"use TypeScript strict mode",
|
|
600
|
-
"all files must have license header",
|
|
601
|
-
"every function needs unit tests",
|
|
602
|
-
"禁止使用 any 类型",
|
|
603
|
-
];
|
|
604
|
-
it("returns empty array for empty constraints", () => {
|
|
605
|
-
assert.deepEqual(filterConstraintsForSubTask([], "search for deprecated API"), []);
|
|
606
|
-
});
|
|
607
|
-
it("returns empty array for empty subTask", () => {
|
|
608
|
-
assert.deepEqual(filterConstraintsForSubTask(constraints, ""), []);
|
|
609
|
-
assert.deepEqual(filterConstraintsForSubTask(constraints, " "), []);
|
|
610
|
-
});
|
|
611
|
-
it("returns only relevant constraints by Jaccard similarity", () => {
|
|
612
|
-
// "search for deprecated API usage in src/" should NOT match
|
|
613
|
-
// rate limiting, strict mode, license headers, or unit tests
|
|
614
|
-
const result = filterConstraintsForSubTask(constraints, "search for deprecated API usage in src/");
|
|
615
|
-
assert.equal(result.length, 0);
|
|
616
|
-
});
|
|
617
|
-
it("returns relevant constraint when task overlaps", () => {
|
|
618
|
-
// "add rate limiting to all API endpoints" should match
|
|
619
|
-
// "all APIs must have rate limiting"
|
|
620
|
-
const result = filterConstraintsForSubTask(constraints, "add rate limiting to all API endpoints");
|
|
621
|
-
assert.ok(result.length >= 1);
|
|
622
|
-
assert.ok(result.some((c) => c.includes("rate limiting")));
|
|
623
|
-
});
|
|
624
|
-
it("returns at least one constraint when task has TypeScript+testing overlap", () => {
|
|
625
|
-
// "add TypeScript unit tests for all API functions" overlaps with
|
|
626
|
-
// "every function needs unit tests" (Jaccard ~0.2);
|
|
627
|
-
// "use TypeScript strict mode" overlap is too low (Jaccard ~0.1)
|
|
628
|
-
const result = filterConstraintsForSubTask(constraints, "add TypeScript unit tests for all API functions");
|
|
629
|
-
assert.ok(result.length >= 1);
|
|
630
|
-
assert.ok(result.some((c) => c.includes("unit tests")));
|
|
631
|
-
});
|
|
632
|
-
it("custom threshold filters more aggressively", () => {
|
|
633
|
-
// With threshold 0.5, should return very few constraints
|
|
634
|
-
const lenient = filterConstraintsForSubTask(constraints, "add rate limiting to all API endpoints", 0.15);
|
|
635
|
-
const strict = filterConstraintsForSubTask(constraints, "add rate limiting to all API endpoints", 0.5);
|
|
636
|
-
assert.ok(strict.length <= lenient.length);
|
|
637
|
-
});
|
|
638
|
-
it("supports CJK subTask with CJK constraints", () => {
|
|
639
|
-
const cjkConstraints = [
|
|
640
|
-
"所有API必须有速率限制",
|
|
641
|
-
"使用TypeScript严格模式",
|
|
642
|
-
];
|
|
643
|
-
// "搜索废弃的API" doesn't overlap with rate limiting or strict mode
|
|
644
|
-
const result = filterConstraintsForSubTask(cjkConstraints, "搜索废弃的API使用情况");
|
|
645
|
-
assert.equal(result.length, 0);
|
|
646
|
-
});
|
|
647
|
-
it("matches CJK constraint with overlapping CJK subTask", () => {
|
|
648
|
-
const cjkConstraints = [
|
|
649
|
-
"所有API必须有速率限制",
|
|
650
|
-
"所有文件必须有许可证头",
|
|
651
|
-
];
|
|
652
|
-
const result = filterConstraintsForSubTask(cjkConstraints, "为API添加速率限制");
|
|
653
|
-
assert.ok(result.length >= 1);
|
|
654
|
-
assert.ok(result.some((c) => c.includes("速率限制")));
|
|
655
|
-
});
|
|
656
|
-
});
|
|
657
|
-
describe("Delegation Helpers — formatDelegationPrompt", () => {
|
|
658
|
-
const subTask = "search for deprecated API usage in src/";
|
|
659
|
-
const constraints = ["all APIs must have rate limiting"];
|
|
660
|
-
it("generates self-contained explore prompt", () => {
|
|
661
|
-
const prompt = formatDelegationPrompt(subTask, "explore", []);
|
|
662
|
-
assert.ok(prompt.includes("### Delegated Task"));
|
|
663
|
-
assert.ok(prompt.includes(subTask));
|
|
664
|
-
assert.ok(prompt.includes("### Output Format"));
|
|
665
|
-
// Explore should NOT get constraints
|
|
666
|
-
assert.ok(!prompt.includes("### Relevant Constraints"));
|
|
667
|
-
// Must be self-contained
|
|
668
|
-
assert.ok(!prompt.match(/based on (above|previous|your findings)/i));
|
|
669
|
-
assert.ok(!prompt.includes("continue from"));
|
|
670
|
-
});
|
|
671
|
-
it("generates general-purpose prompt with constraints", () => {
|
|
672
|
-
const prompt = formatDelegationPrompt("fix the null pointer in src/auth/validate.ts:42", "general-purpose", constraints);
|
|
673
|
-
assert.ok(prompt.includes("### Delegated Task"));
|
|
674
|
-
assert.ok(prompt.includes("### Relevant Constraints"));
|
|
675
|
-
assert.ok(prompt.includes("rate limiting"));
|
|
676
|
-
assert.ok(prompt.includes("don't leave it half-done"));
|
|
677
|
-
// Must be self-contained
|
|
678
|
-
assert.ok(!prompt.match(/based on (above|previous|your findings)/i));
|
|
679
|
-
});
|
|
680
|
-
it("generates plan prompt with instructions", () => {
|
|
681
|
-
const prompt = formatDelegationPrompt("design authentication middleware", "plan", constraints, { context: "Current auth is JWT-based in src/auth/" });
|
|
682
|
-
assert.ok(prompt.includes("### Delegated Task"));
|
|
683
|
-
assert.ok(prompt.includes("### Context"));
|
|
684
|
-
assert.ok(prompt.includes("JWT-based"));
|
|
685
|
-
assert.ok(prompt.includes("### Relevant Constraints"));
|
|
686
|
-
assert.ok(prompt.includes("Critical files for implementation"));
|
|
687
|
-
assert.ok(prompt.includes("3-5 files"));
|
|
688
|
-
});
|
|
689
|
-
it("skips constraints section when empty", () => {
|
|
690
|
-
const prompt = formatDelegationPrompt(subTask, "general-purpose", []);
|
|
691
|
-
assert.ok(!prompt.includes("### Relevant Constraints"));
|
|
692
|
-
});
|
|
693
|
-
it("custom sub-agent type gets generic template", () => {
|
|
694
|
-
const prompt = formatDelegationPrompt(subTask, "custom-auditor", constraints);
|
|
695
|
-
assert.ok(prompt.includes("### Delegated Task"));
|
|
696
|
-
assert.ok(prompt.includes("### Relevant Constraints"));
|
|
697
|
-
assert.ok(prompt.includes("Complete the task above"));
|
|
698
|
-
});
|
|
699
|
-
it("output is self-contained — no parent-context references", () => {
|
|
700
|
-
const types = ["explore", "general-purpose", "plan", "custom"];
|
|
701
|
-
for (const t of types) {
|
|
702
|
-
const prompt = formatDelegationPrompt("do something", t, constraints);
|
|
703
|
-
// No references to parent conversation
|
|
704
|
-
assert.ok(!prompt.match(/based on (above|previous|your findings)/i), `${t}: should not reference parent context`);
|
|
705
|
-
assert.ok(!prompt.includes("continue from"), `${t}: should not say "continue from"`);
|
|
706
|
-
assert.ok(!prompt.includes("as discussed"), `${t}: should not say "as discussed"`);
|
|
707
|
-
assert.ok(!prompt.includes("see above"), `${t}: should not say "see above"`);
|
|
708
|
-
}
|
|
709
|
-
});
|
|
710
|
-
});
|
|
711
|
-
describe("Delegation — buildDelegationSummary", () => {
|
|
712
|
-
it("returns empty string for null vault context", () => {
|
|
713
|
-
assert.equal(buildDelegationSummary(null), "");
|
|
714
|
-
});
|
|
715
|
-
it("returns empty string when no delegation journal entries", () => {
|
|
716
|
-
const vault = { results: [{ task_type: "loop_lineage", loop_id: "test" }] };
|
|
717
|
-
assert.equal(buildDelegationSummary(vault), "");
|
|
718
|
-
});
|
|
719
|
-
it("returns empty string when results array is empty", () => {
|
|
720
|
-
const vault = { results: [] };
|
|
721
|
-
assert.equal(buildDelegationSummary(vault), "");
|
|
722
|
-
});
|
|
723
|
-
it("builds delegation table from journal entries", () => {
|
|
724
|
-
const vault = {
|
|
725
|
-
results: [
|
|
726
|
-
{
|
|
727
|
-
task_type: "delegation_journal",
|
|
728
|
-
loop_lineage: {
|
|
729
|
-
round: 2,
|
|
730
|
-
delegations: [
|
|
731
|
-
{ index: 1, agentId: "abc-123", subAgentType: "explore", subTask: "search deprecated APIs", resultSummary: "found 12 instances", success: true, discoveredConstraints: [] },
|
|
732
|
-
{ index: 2, agentId: "def-456", subAgentType: "general-purpose", subTask: "fix auth bug", resultSummary: "fixed null pointer", success: true, discoveredConstraints: [] },
|
|
733
|
-
],
|
|
734
|
-
},
|
|
735
|
-
},
|
|
736
|
-
],
|
|
737
|
-
};
|
|
738
|
-
const text = buildDelegationSummary(vault);
|
|
739
|
-
assert.ok(text.includes("### Delegation History"));
|
|
740
|
-
assert.ok(text.includes("R2"));
|
|
741
|
-
assert.ok(text.includes("search deprecated APIs"));
|
|
742
|
-
assert.ok(text.includes("fix auth bug"));
|
|
743
|
-
assert.ok(text.includes("✓"));
|
|
744
|
-
// Agent column should display agentId
|
|
745
|
-
assert.ok(text.includes("abc-123"));
|
|
746
|
-
assert.ok(text.includes("def-456"));
|
|
747
|
-
// Table header should include Agent column
|
|
748
|
-
assert.ok(text.includes("| Round | Agent | Type | Task | Result | ✓/✗ |"));
|
|
749
|
-
});
|
|
750
|
-
it("shows ✗ for failed delegations", () => {
|
|
751
|
-
const vault = {
|
|
752
|
-
results: [
|
|
753
|
-
{
|
|
754
|
-
task_type: "delegation_journal",
|
|
755
|
-
loop_lineage: {
|
|
756
|
-
round: 3,
|
|
757
|
-
delegations: [
|
|
758
|
-
{ index: 1, agentId: "worker-xyz", subAgentType: "general-purpose", subTask: "fix complex bug", resultSummary: "failed — wrong approach", success: false, discoveredConstraints: [] },
|
|
759
|
-
],
|
|
760
|
-
},
|
|
761
|
-
},
|
|
762
|
-
],
|
|
763
|
-
};
|
|
764
|
-
const text = buildDelegationSummary(vault);
|
|
765
|
-
assert.ok(text.includes("✗"));
|
|
766
|
-
assert.ok(text.includes("fix complex bug"));
|
|
767
|
-
assert.ok(text.includes("wrong approach"));
|
|
768
|
-
});
|
|
769
|
-
it("handles multiple rounds of delegation", () => {
|
|
770
|
-
const vault = {
|
|
771
|
-
results: [
|
|
772
|
-
{
|
|
773
|
-
task_type: "delegation_journal",
|
|
774
|
-
loop_lineage: { round: 1, delegations: [{ index: 1, agentId: "agent-a", subAgentType: "explore", subTask: "research X", resultSummary: "done", success: true, discoveredConstraints: [] }] },
|
|
775
|
-
},
|
|
776
|
-
{
|
|
777
|
-
task_type: "delegation_journal",
|
|
778
|
-
loop_lineage: { round: 2, delegations: [{ index: 1, agentId: "agent-b", subAgentType: "general-purpose", subTask: "fix Y", resultSummary: "done", success: true, discoveredConstraints: [] }] },
|
|
779
|
-
},
|
|
780
|
-
],
|
|
781
|
-
};
|
|
782
|
-
const text = buildDelegationSummary(vault);
|
|
783
|
-
assert.ok(text.includes("R1"));
|
|
784
|
-
assert.ok(text.includes("R2"));
|
|
785
|
-
});
|
|
786
|
-
it("falls back to worker-N when agentId is missing (backward compat)", () => {
|
|
787
|
-
const vault = {
|
|
788
|
-
results: [
|
|
789
|
-
{
|
|
790
|
-
task_type: "delegation_journal",
|
|
791
|
-
loop_lineage: {
|
|
792
|
-
round: 1,
|
|
793
|
-
delegations: [
|
|
794
|
-
{ index: 3, subAgentType: "explore", subTask: "scan codebase", resultSummary: "done", success: true },
|
|
795
|
-
],
|
|
796
|
-
},
|
|
797
|
-
},
|
|
798
|
-
],
|
|
799
|
-
};
|
|
800
|
-
const text = buildDelegationSummary(vault);
|
|
801
|
-
assert.ok(text.includes("worker-3"), "should fall back to worker-3");
|
|
802
|
-
});
|
|
803
|
-
it("escapes pipe characters in markdown table cells", () => {
|
|
804
|
-
const vault = {
|
|
805
|
-
results: [
|
|
806
|
-
{
|
|
807
|
-
task_type: "delegation_journal",
|
|
808
|
-
loop_lineage: {
|
|
809
|
-
round: 1,
|
|
810
|
-
delegations: [
|
|
811
|
-
{ index: 1, agentId: "agent-1", subAgentType: "explore", subTask: "audit A|B testing in src/", resultSummary: "found issues in x|y module", success: true, discoveredConstraints: [] },
|
|
812
|
-
],
|
|
813
|
-
},
|
|
814
|
-
},
|
|
815
|
-
],
|
|
816
|
-
};
|
|
817
|
-
const text = buildDelegationSummary(vault);
|
|
818
|
-
// Should contain escaped pipes, not raw pipes that would break the table
|
|
819
|
-
assert.ok(text.includes("A\\|B"), "pipe in task should be escaped");
|
|
820
|
-
assert.ok(text.includes("x\\|y"), "pipe in result should be escaped");
|
|
821
|
-
// Verify the table row still has the correct number of pipe chars:
|
|
822
|
-
// 7 structural (leading + 5 separators + trailing) + 2 escaped inside cells = 9 total
|
|
823
|
-
const tableRow = text.split("\n").find((l) => l.startsWith("| R1"));
|
|
824
|
-
assert.ok(tableRow, "should have a table row for R1");
|
|
825
|
-
const pipeCount = (tableRow.match(/\|/g) ?? []).length;
|
|
826
|
-
assert.equal(pipeCount, 9, "table row should have 9 pipe chars (7 structural + 2 escaped)");
|
|
827
|
-
});
|
|
828
|
-
});
|
|
829
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
830
|
-
// Checkpoint Compression (v1.10)
|
|
831
|
-
// ═══════════════════════════════════════════════════════════════════════════
|
|
832
|
-
describe("Checkpoint — buildCheckpointSummary", () => {
|
|
833
|
-
it("builds from self-eval with checkpoint fields", () => {
|
|
834
|
-
const selfEval = makeSelfEvaluation({
|
|
835
|
-
success: true,
|
|
836
|
-
output_summary: "Completed ERC20 data model with full test coverage",
|
|
837
|
-
compression_checkpoint: true,
|
|
838
|
-
checkpoint_label: "数据模型层完成",
|
|
839
|
-
});
|
|
840
|
-
const cs = buildCheckpointSummary(selfEval, 8, ["constraint-A", "constraint-B", "constraint-C"], ["old-constraint"]);
|
|
841
|
-
assert.ok(cs !== null);
|
|
842
|
-
assert.equal(cs.label, "数据模型层完成");
|
|
843
|
-
assert.equal(cs.declared_at_round, 8);
|
|
844
|
-
assert.ok(cs.outcome.includes("ERC20 data model"));
|
|
845
|
-
assert.deepStrictEqual(cs.carried_constraints, ["constraint-A", "constraint-B", "constraint-C"]);
|
|
846
|
-
assert.deepStrictEqual(cs.resolved_constraints, ["old-constraint"]);
|
|
847
|
-
});
|
|
848
|
-
it("falls back to auto-generated label when checkpoint_label is empty", () => {
|
|
849
|
-
const selfEval = makeSelfEvaluation({
|
|
850
|
-
success: true,
|
|
851
|
-
output_summary: "Done with auth module",
|
|
852
|
-
compression_checkpoint: true,
|
|
853
|
-
checkpoint_label: "",
|
|
854
|
-
});
|
|
855
|
-
const cs = buildCheckpointSummary(selfEval, 5, ["c1"], []);
|
|
856
|
-
assert.ok(cs.label.includes("Subtask completed at round 5"));
|
|
857
|
-
});
|
|
858
|
-
it("caps carried and resolved constraints at policy limit", () => {
|
|
859
|
-
const selfEval = makeSelfEvaluation({
|
|
860
|
-
success: true,
|
|
861
|
-
output_summary: "Done",
|
|
862
|
-
compression_checkpoint: true,
|
|
863
|
-
checkpoint_label: "test",
|
|
864
|
-
});
|
|
865
|
-
const manyConstraints = Array.from({ length: 15 }, (_, i) => `c${i + 1}`);
|
|
866
|
-
const cs = buildCheckpointSummary(selfEval, 3, manyConstraints, manyConstraints);
|
|
867
|
-
// Default policy: max_carried_constraints = 10
|
|
868
|
-
assert.equal(cs.carried_constraints.length, 10);
|
|
869
|
-
assert.equal(cs.resolved_constraints.length, 10);
|
|
870
|
-
});
|
|
871
|
-
it("truncates outcome at policy limit", () => {
|
|
872
|
-
const selfEval = makeSelfEvaluation({
|
|
873
|
-
success: true,
|
|
874
|
-
output_summary: "x".repeat(300),
|
|
875
|
-
compression_checkpoint: true,
|
|
876
|
-
checkpoint_label: "test",
|
|
877
|
-
});
|
|
878
|
-
const cs = buildCheckpointSummary(selfEval, 1, [], []);
|
|
879
|
-
// Default policy: outcome_max_chars = 200
|
|
880
|
-
assert.ok(cs.outcome.length <= 200);
|
|
881
|
-
});
|
|
882
|
-
});
|
|
883
|
-
describe("Checkpoint — formatCheckpointForPrompt", () => {
|
|
884
|
-
it("renders checkpoint block with all fields", () => {
|
|
885
|
-
const cs = makeCheckpointSummary({
|
|
886
|
-
label: "Auth module complete",
|
|
887
|
-
declared_at_round: 5,
|
|
888
|
-
outcome: "Implemented JWT auth with refresh tokens",
|
|
889
|
-
carried_constraints: ["所有API需要认证", "token过期时间≤24h"],
|
|
890
|
-
resolved_constraints: ["旧密码哈希方式需更新"],
|
|
891
|
-
});
|
|
892
|
-
const text = formatCheckpointForPrompt(cs);
|
|
893
|
-
assert.ok(text.includes("### Checkpoint: Auth module complete (Round 5)"));
|
|
894
|
-
assert.ok(text.includes("Implemented JWT auth"));
|
|
895
|
-
assert.ok(text.includes("所有API需要认证"));
|
|
896
|
-
assert.ok(text.includes("旧密码哈希方式需更新"));
|
|
897
|
-
assert.ok(text.includes("Carried Constraints"));
|
|
898
|
-
assert.ok(text.includes("Resolved Constraints"));
|
|
899
|
-
});
|
|
900
|
-
it("returns empty string for null", () => {
|
|
901
|
-
assert.equal(formatCheckpointForPrompt(null), "");
|
|
902
|
-
});
|
|
903
|
-
it("returns empty string for empty checkpoint", () => {
|
|
904
|
-
const cs = makeCheckpointSummary({});
|
|
905
|
-
assert.equal(formatCheckpointForPrompt(cs), "");
|
|
906
|
-
});
|
|
907
|
-
it("omits resolved section when no resolved constraints", () => {
|
|
908
|
-
const cs = makeCheckpointSummary({
|
|
909
|
-
label: "test",
|
|
910
|
-
declared_at_round: 1,
|
|
911
|
-
outcome: "done",
|
|
912
|
-
carried_constraints: ["c1"],
|
|
913
|
-
resolved_constraints: [],
|
|
914
|
-
});
|
|
915
|
-
const text = formatCheckpointForPrompt(cs);
|
|
916
|
-
assert.ok(!text.includes("Resolved Constraints"));
|
|
917
|
-
});
|
|
918
|
-
});
|
|
919
|
-
describe("Checkpoint — buildRollingSummary with sinceRound", () => {
|
|
920
|
-
function makeVaultResult(loopId, round, qualityScore, task, outputSummary = "", techniqueUsed = "zero-shot", constraintViolations = []) {
|
|
921
|
-
const success = qualityScore >= 3;
|
|
922
|
-
return {
|
|
923
|
-
task, output_summary: outputSummary,
|
|
924
|
-
constraint_violations: constraintViolations,
|
|
925
|
-
technique_used: techniqueUsed, success,
|
|
926
|
-
loop_lineage: { loop_id: loopId, round, success, task, technique_used: techniqueUsed },
|
|
927
|
-
};
|
|
928
|
-
}
|
|
929
|
-
it("only includes rounds at or after sinceRound", () => {
|
|
930
|
-
const vault = {
|
|
931
|
-
results: [
|
|
932
|
-
makeVaultResult("loop-1", 1, 4, "task A", "did A"),
|
|
933
|
-
makeVaultResult("loop-1", 2, 5, "task B", "did B"),
|
|
934
|
-
makeVaultResult("loop-1", 3, 3, "task C", "did C"),
|
|
935
|
-
makeVaultResult("loop-1", 4, 5, "task D", "did D"),
|
|
936
|
-
makeVaultResult("loop-1", 5, 4, "task E", "did E"),
|
|
937
|
-
],
|
|
938
|
-
};
|
|
939
|
-
const rs = buildRollingSummary("loop-1", 6, vault, 3);
|
|
940
|
-
assert.ok(rs !== null);
|
|
941
|
-
// Round 1 and 2 should be excluded (before sinceRound=3)
|
|
942
|
-
assert.equal(rs.rounds_sampled, 3);
|
|
943
|
-
// Verify key_outcomes contains rounds 3-5
|
|
944
|
-
assert.equal(rs.key_outcomes.length, 3);
|
|
945
|
-
assert.ok(rs.key_outcomes.some((k) => k.includes("R3")));
|
|
946
|
-
assert.ok(rs.key_outcomes.some((k) => k.includes("R5")));
|
|
947
|
-
});
|
|
948
|
-
it("sinceRound with no matching rounds returns null", () => {
|
|
949
|
-
const vault = {
|
|
950
|
-
results: [
|
|
951
|
-
makeVaultResult("loop-1", 1, 4, "task A", "did A"),
|
|
952
|
-
makeVaultResult("loop-1", 2, 5, "task B", "did B"),
|
|
953
|
-
],
|
|
954
|
-
};
|
|
955
|
-
const rs = buildRollingSummary("loop-1", 3, vault, 5);
|
|
956
|
-
assert.equal(rs, null);
|
|
957
|
-
});
|
|
958
|
-
it("without sinceRound includes all rounds (backward compat)", () => {
|
|
959
|
-
const vault = {
|
|
960
|
-
results: [
|
|
961
|
-
makeVaultResult("loop-1", 1, 4, "task A", "did A"),
|
|
962
|
-
makeVaultResult("loop-1", 2, 5, "task B", "did B"),
|
|
963
|
-
],
|
|
964
|
-
};
|
|
965
|
-
const rs = buildRollingSummary("loop-1", 3, vault);
|
|
966
|
-
assert.ok(rs !== null);
|
|
967
|
-
assert.equal(rs.rounds_sampled, 2);
|
|
968
|
-
});
|
|
969
|
-
});
|
|
970
|
-
describe("Checkpoint — compileL2 integration", () => {
|
|
971
|
-
it("renders checkpoint in prompt when last_round_result has checkpoint", () => {
|
|
972
|
-
const lr = makeLoopRoundResult({
|
|
973
|
-
round: 4,
|
|
974
|
-
success: true,
|
|
975
|
-
output_summary: "Completed data model layer",
|
|
976
|
-
constraint_violations: [],
|
|
977
|
-
compression_checkpoint: true,
|
|
978
|
-
checkpoint_label: "Data model done",
|
|
979
|
-
});
|
|
980
|
-
const req = makeLoopCompileRequest({
|
|
981
|
-
round: 5,
|
|
982
|
-
loop_id: "test-cp",
|
|
983
|
-
task: "Build API layer on top of data model",
|
|
984
|
-
last_round_result: lr,
|
|
985
|
-
});
|
|
986
|
-
// Empty vault — no prior checkpoint to load
|
|
987
|
-
const vault = { results: [], global_entries: [] };
|
|
988
|
-
const response = compileL2(req, vault);
|
|
989
|
-
assert.ok(response.prompt.includes("### Checkpoint: Data model done (Round 4)"));
|
|
990
|
-
assert.ok(response.prompt.includes("Completed data model layer"));
|
|
991
|
-
assert.ok(response.checkpoint_summary !== null);
|
|
992
|
-
assert.equal(response.checkpoint_summary.declared_at_round, 4);
|
|
993
|
-
});
|
|
994
|
-
it("no checkpoint in prompt when last_round_result has no checkpoint", () => {
|
|
995
|
-
const lr = makeLoopRoundResult({
|
|
996
|
-
round: 2,
|
|
997
|
-
success: true,
|
|
998
|
-
output_summary: "Fixed a bug",
|
|
999
|
-
constraint_violations: [],
|
|
1000
|
-
});
|
|
1001
|
-
const req = makeLoopCompileRequest({
|
|
1002
|
-
round: 3,
|
|
1003
|
-
loop_id: "test-nocp",
|
|
1004
|
-
task: "Continue fixing bugs",
|
|
1005
|
-
last_round_result: lr,
|
|
1006
|
-
});
|
|
1007
|
-
const vault = { results: [], global_entries: [] };
|
|
1008
|
-
const response = compileL2(req, vault);
|
|
1009
|
-
assert.ok(!response.prompt.includes("### Checkpoint:"));
|
|
1010
|
-
assert.equal(response.checkpoint_summary, null);
|
|
1011
|
-
});
|
|
1012
|
-
it("loads existing checkpoint from vault on later rounds", () => {
|
|
1013
|
-
// Simulate a previous round that stored a checkpoint in vault
|
|
1014
|
-
const vault = {
|
|
1015
|
-
results: [
|
|
1016
|
-
{
|
|
1017
|
-
loop_lineage: { loop_id: "test-cp2", round: 3, success: true },
|
|
1018
|
-
checkpoint_summary: {
|
|
1019
|
-
label: "API layer done",
|
|
1020
|
-
declared_at_round: 3,
|
|
1021
|
-
outcome: "Built REST API",
|
|
1022
|
-
carried_constraints: ["auth required"],
|
|
1023
|
-
resolved_constraints: [],
|
|
1024
|
-
},
|
|
1025
|
-
task: "Build API",
|
|
1026
|
-
output_summary: "Built REST API",
|
|
1027
|
-
technique_used: "least-to-most",
|
|
1028
|
-
},
|
|
1029
|
-
],
|
|
1030
|
-
};
|
|
1031
|
-
const lr = makeLoopRoundResult({
|
|
1032
|
-
round: 4,
|
|
1033
|
-
success: true,
|
|
1034
|
-
output_summary: "Added tests",
|
|
1035
|
-
constraint_violations: [],
|
|
1036
|
-
});
|
|
1037
|
-
const req = makeLoopCompileRequest({
|
|
1038
|
-
round: 5,
|
|
1039
|
-
loop_id: "test-cp2",
|
|
1040
|
-
task: "Add integration tests",
|
|
1041
|
-
last_round_result: lr,
|
|
1042
|
-
});
|
|
1043
|
-
const response = compileL2(req, vault);
|
|
1044
|
-
// Should load checkpoint from round 3
|
|
1045
|
-
assert.ok(response.prompt.includes("### Checkpoint: API layer done (Round 3)"));
|
|
1046
|
-
assert.ok(response.prompt.includes("Built REST API"));
|
|
1047
|
-
assert.ok(response.checkpoint_summary !== null);
|
|
1048
|
-
assert.equal(response.checkpoint_summary.declared_at_round, 3);
|
|
1049
|
-
});
|
|
1050
|
-
});
|
|
1051
|
-
//# sourceMappingURL=loop-compiler.test.js.map
|