parallax-opencode 0.2.0 → 0.3.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 +260 -217
- package/dist/cli.js +239 -2
- package/dist/plugin.d.ts +15 -13
- package/dist/plugin.js +276 -128
- package/dist/score.d.ts +16 -0
- package/dist/score.js +60 -0
- package/dist/tests/detect.test.d.ts +1 -0
- package/dist/tests/detect.test.js +75 -0
- package/dist/tests/friction.test.d.ts +1 -0
- package/dist/tests/friction.test.js +87 -0
- package/dist/tests/protocol.test.d.ts +1 -0
- package/dist/tests/protocol.test.js +80 -0
- package/dist/tests/score.test.d.ts +1 -0
- package/dist/tests/score.test.js +106 -0
- package/dist/tests/trace.test.d.ts +1 -0
- package/dist/tests/trace.test.js +109 -0
- package/dist/types.d.ts +11 -2
- package/dist-standalone/parallax-engine.d.ts +15 -13
- package/dist-standalone/parallax-engine.js +241 -34594
- package/package.json +64 -65
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for project detection logic.
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect, vi, beforeEach } from "vitest";
|
|
5
|
+
// Mock fs functions used by detectProject
|
|
6
|
+
vi.mock("fs", () => ({
|
|
7
|
+
existsSync: vi.fn(),
|
|
8
|
+
statSync: vi.fn(),
|
|
9
|
+
readFileSync: vi.fn(),
|
|
10
|
+
}));
|
|
11
|
+
function testDetect(files, dirs) {
|
|
12
|
+
const mockExists = vi.fn((p) => {
|
|
13
|
+
const path = typeof p === "string" ? p : String(p);
|
|
14
|
+
return files[path] === true;
|
|
15
|
+
});
|
|
16
|
+
const mockStat = vi.fn((p) => {
|
|
17
|
+
const path = typeof p === "string" ? p : String(p);
|
|
18
|
+
if (dirs[path])
|
|
19
|
+
return { isDirectory: () => true };
|
|
20
|
+
return { isDirectory: () => false };
|
|
21
|
+
});
|
|
22
|
+
// Inline the detection logic from plugin.ts
|
|
23
|
+
try {
|
|
24
|
+
if (mockExists("Cargo.toml"))
|
|
25
|
+
return "cargo";
|
|
26
|
+
if (mockExists("package.json")) {
|
|
27
|
+
if (mockExists("node_modules") && mockStat("node_modules").isDirectory()) {
|
|
28
|
+
if (mockExists("tsconfig.json"))
|
|
29
|
+
return "tsc";
|
|
30
|
+
return "lint";
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
if (mockExists("pyproject.toml") || mockExists("requirements.txt"))
|
|
34
|
+
return "python";
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
catch {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
describe("Project detection", () => {
|
|
42
|
+
beforeEach(() => {
|
|
43
|
+
vi.clearAllMocks();
|
|
44
|
+
});
|
|
45
|
+
it("detects cargo project", () => {
|
|
46
|
+
const result = testDetect({ "Cargo.toml": true }, {});
|
|
47
|
+
expect(result).toBe("cargo");
|
|
48
|
+
});
|
|
49
|
+
it("detects TypeScript project (tsconfig + node_modules)", () => {
|
|
50
|
+
const result = testDetect({ "package.json": true, "node_modules": true, "tsconfig.json": true }, { "node_modules": true });
|
|
51
|
+
expect(result).toBe("tsc");
|
|
52
|
+
});
|
|
53
|
+
it("detects JS/lint project (package.json + node_modules, no tsconfig)", () => {
|
|
54
|
+
const result = testDetect({ "package.json": true, "node_modules": true }, { "node_modules": true });
|
|
55
|
+
expect(result).toBe("lint");
|
|
56
|
+
});
|
|
57
|
+
it("detects Python project (pyproject.toml)", () => {
|
|
58
|
+
const result = testDetect({ "pyproject.toml": true }, {});
|
|
59
|
+
expect(result).toBe("python");
|
|
60
|
+
});
|
|
61
|
+
it("detects Python project (requirements.txt)", () => {
|
|
62
|
+
const result = testDetect({ "requirements.txt": true }, {});
|
|
63
|
+
expect(result).toBe("python");
|
|
64
|
+
});
|
|
65
|
+
it("returns null for unknown project", () => {
|
|
66
|
+
const result = testDetect({}, {});
|
|
67
|
+
expect(result).toBeNull();
|
|
68
|
+
});
|
|
69
|
+
it("handles errors gracefully", () => {
|
|
70
|
+
// The try/catch should return null on any exception
|
|
71
|
+
const result = testDetect({ "Cargo.toml": true }, {});
|
|
72
|
+
// Should be "cargo" since we're not throwing
|
|
73
|
+
expect(result).toBe("cargo");
|
|
74
|
+
});
|
|
75
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for friction loop state machine.
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect, beforeEach, vi } from "vitest";
|
|
5
|
+
const MAX_FRICTION_RETRIES = 3;
|
|
6
|
+
function createState() {
|
|
7
|
+
return {
|
|
8
|
+
successes: 0,
|
|
9
|
+
trials: 0,
|
|
10
|
+
retriesLeft: MAX_FRICTION_RETRIES,
|
|
11
|
+
lastObservation: null,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
function recordSuccess(state) {
|
|
15
|
+
state.successes++;
|
|
16
|
+
state.trials++;
|
|
17
|
+
state.retriesLeft = MAX_FRICTION_RETRIES;
|
|
18
|
+
state.lastObservation = null;
|
|
19
|
+
}
|
|
20
|
+
function recordFailure(state, msg) {
|
|
21
|
+
state.trials++;
|
|
22
|
+
state.retriesLeft--;
|
|
23
|
+
state.lastObservation = msg;
|
|
24
|
+
}
|
|
25
|
+
// Helper for debounce-like behavior
|
|
26
|
+
function simulateTimeout() {
|
|
27
|
+
vi.advanceTimersByTime(1000);
|
|
28
|
+
}
|
|
29
|
+
describe("Friction loop state machine", () => {
|
|
30
|
+
let state;
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
state = createState();
|
|
33
|
+
vi.useFakeTimers();
|
|
34
|
+
});
|
|
35
|
+
it("starts with full retries and no observation", () => {
|
|
36
|
+
expect(state.successes).toBe(0);
|
|
37
|
+
expect(state.trials).toBe(0);
|
|
38
|
+
expect(state.retriesLeft).toBe(3);
|
|
39
|
+
expect(state.lastObservation).toBeNull();
|
|
40
|
+
});
|
|
41
|
+
it("resets retries on success", () => {
|
|
42
|
+
recordSuccess(state);
|
|
43
|
+
expect(state.successes).toBe(1);
|
|
44
|
+
expect(state.trials).toBe(1);
|
|
45
|
+
expect(state.retriesLeft).toBe(3);
|
|
46
|
+
expect(state.lastObservation).toBeNull();
|
|
47
|
+
});
|
|
48
|
+
it("decrements retries on failure", () => {
|
|
49
|
+
recordFailure(state, "error: syntax error");
|
|
50
|
+
expect(state.successes).toBe(0);
|
|
51
|
+
expect(state.trials).toBe(1);
|
|
52
|
+
expect(state.retriesLeft).toBe(2);
|
|
53
|
+
expect(state.lastObservation).toBe("error: syntax error");
|
|
54
|
+
});
|
|
55
|
+
it("blocks after 3 consecutive failures", () => {
|
|
56
|
+
recordFailure(state, "fail 1");
|
|
57
|
+
expect(state.retriesLeft).toBe(2);
|
|
58
|
+
recordFailure(state, "fail 2");
|
|
59
|
+
expect(state.retriesLeft).toBe(1);
|
|
60
|
+
recordFailure(state, "fail 3");
|
|
61
|
+
expect(state.retriesLeft).toBe(0);
|
|
62
|
+
expect(state.lastObservation).toBe("fail 3");
|
|
63
|
+
});
|
|
64
|
+
it("recovers after success following failures", () => {
|
|
65
|
+
recordFailure(state, "fail 1");
|
|
66
|
+
recordFailure(state, "fail 2");
|
|
67
|
+
expect(state.retriesLeft).toBe(1);
|
|
68
|
+
recordSuccess(state);
|
|
69
|
+
expect(state.retriesLeft).toBe(3);
|
|
70
|
+
expect(state.successes).toBe(1);
|
|
71
|
+
});
|
|
72
|
+
it("accumulates trials correctly", () => {
|
|
73
|
+
recordSuccess(state);
|
|
74
|
+
recordSuccess(state);
|
|
75
|
+
recordFailure(state, "an error");
|
|
76
|
+
recordSuccess(state);
|
|
77
|
+
expect(state.trials).toBe(4);
|
|
78
|
+
expect(state.successes).toBe(3);
|
|
79
|
+
});
|
|
80
|
+
it("isolates state per session", () => {
|
|
81
|
+
const state2 = createState();
|
|
82
|
+
recordFailure(state, "session A error");
|
|
83
|
+
expect(state.retriesLeft).toBe(2);
|
|
84
|
+
expect(state2.retriesLeft).toBe(3);
|
|
85
|
+
expect(state2.lastObservation).toBeNull();
|
|
86
|
+
});
|
|
87
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for protocol step ordering and enforcement.
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
5
|
+
const STEP_LABELS = {
|
|
6
|
+
ambiguity: "Ambiguity Check",
|
|
7
|
+
invariants: "4 Invariants",
|
|
8
|
+
gate: "Verification Gate",
|
|
9
|
+
commit: "Commit Decision",
|
|
10
|
+
summary: "Summarize",
|
|
11
|
+
};
|
|
12
|
+
function createProtocol() {
|
|
13
|
+
return {
|
|
14
|
+
ambiguityDone: false,
|
|
15
|
+
invariantsDone: false,
|
|
16
|
+
gateDone: false,
|
|
17
|
+
commitDone: false,
|
|
18
|
+
summaryDone: false,
|
|
19
|
+
writesBeforeGate: 0,
|
|
20
|
+
gateBlocked: false,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function checkin(p, step) {
|
|
24
|
+
if (step === "ambiguity" && !p.ambiguityDone) {
|
|
25
|
+
p.ambiguityDone = true;
|
|
26
|
+
return "[parallax] Step 1/6: Ambiguity Check marked complete.";
|
|
27
|
+
}
|
|
28
|
+
if (step === "invariants") {
|
|
29
|
+
if (!p.ambiguityDone)
|
|
30
|
+
return "[parallax] ERROR: Complete Ambiguity Check first (Step 1).";
|
|
31
|
+
p.invariantsDone = true;
|
|
32
|
+
return "[parallax] Step 2/6: 4 Invariants marked complete.";
|
|
33
|
+
}
|
|
34
|
+
if (step === "gate") {
|
|
35
|
+
if (!p.invariantsDone)
|
|
36
|
+
return "[parallax] ERROR: Complete 4 Invariants first (Step 2).";
|
|
37
|
+
p.gateDone = true;
|
|
38
|
+
return "[parallax] Step 3/6: Verification Gate marked complete.";
|
|
39
|
+
}
|
|
40
|
+
if (step === "commit") {
|
|
41
|
+
p.commitDone = true;
|
|
42
|
+
return "[parallax] Step 5/6: Commit Decision marked complete.";
|
|
43
|
+
}
|
|
44
|
+
if (step === "summary") {
|
|
45
|
+
p.summaryDone = true;
|
|
46
|
+
return "[parallax] Step 6/6: Summary marked complete. Protocol finished.";
|
|
47
|
+
}
|
|
48
|
+
return `[parallax] Unknown step "${step}".`;
|
|
49
|
+
}
|
|
50
|
+
describe("Protocol step enforcement", () => {
|
|
51
|
+
let p;
|
|
52
|
+
beforeEach(() => {
|
|
53
|
+
p = createProtocol();
|
|
54
|
+
});
|
|
55
|
+
it("enforces correct order: ambiguity -> invariants -> gate", () => {
|
|
56
|
+
expect(checkin(p, "ambiguity")).toContain("Step 1/6");
|
|
57
|
+
expect(checkin(p, "invariants")).toContain("Step 2/6");
|
|
58
|
+
expect(checkin(p, "gate")).toContain("Step 3/6");
|
|
59
|
+
});
|
|
60
|
+
it("blocks invariants before ambiguity", () => {
|
|
61
|
+
expect(checkin(p, "invariants")).toContain("ERROR");
|
|
62
|
+
expect(p.invariantsDone).toBe(false);
|
|
63
|
+
});
|
|
64
|
+
it("blocks gate before invariants", () => {
|
|
65
|
+
checkin(p, "ambiguity");
|
|
66
|
+
expect(checkin(p, "gate")).toContain("ERROR");
|
|
67
|
+
expect(p.gateDone).toBe(false);
|
|
68
|
+
});
|
|
69
|
+
it("allows commit and summary at any time after gate", () => {
|
|
70
|
+
checkin(p, "ambiguity");
|
|
71
|
+
checkin(p, "invariants");
|
|
72
|
+
checkin(p, "gate");
|
|
73
|
+
expect(checkin(p, "commit")).toContain("Step 5/6");
|
|
74
|
+
expect(checkin(p, "summary")).toContain("Step 6/6");
|
|
75
|
+
expect(p.summaryDone).toBe(true);
|
|
76
|
+
});
|
|
77
|
+
it("rejects unknown steps", () => {
|
|
78
|
+
expect(checkin(p, "bogus")).toContain("Unknown step");
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for coherence score computation.
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect } from "vitest";
|
|
5
|
+
function computeCoherenceScore(trace) {
|
|
6
|
+
// 1. Protocol Coverage (30 points max)
|
|
7
|
+
const required = ["ambiguity_check", "four_invariants", "verification_gate", "commit_decision", "summary"];
|
|
8
|
+
const phaseNames = new Set(trace.phases.map((p) => p.phase));
|
|
9
|
+
const completed = required.filter((r) => phaseNames.has(r)).length;
|
|
10
|
+
const protocolScore = (completed / required.length) * 30;
|
|
11
|
+
// 2. Verification Integrity (35 points max)
|
|
12
|
+
let integrityScore = 0;
|
|
13
|
+
if (trace.writes.length > 0) {
|
|
14
|
+
const firstPass = trace.writes.filter((w) => w.verification === "pass" && w.frictionRetriesLeft === 3).length;
|
|
15
|
+
integrityScore = (firstPass / trace.writes.length) * 35;
|
|
16
|
+
}
|
|
17
|
+
// 3. Edge Case Coverage (20 points max)
|
|
18
|
+
// For now, check if analyze phases recorded edge categories
|
|
19
|
+
const analyzePhases = trace.phases.filter((p) => p.phase === "mode_switch" && p.data.analysisTopic);
|
|
20
|
+
const edgeScore = Math.min(analyzePhases.length / 3, 1) * 20;
|
|
21
|
+
// 4. Timing Discipline (15 points max)
|
|
22
|
+
const order = ["ambiguity_check", "four_invariants", "verification_gate", "commit_decision", "summary"];
|
|
23
|
+
let inOrder = 0;
|
|
24
|
+
let lastIdx = -1;
|
|
25
|
+
for (const phase of trace.phases) {
|
|
26
|
+
const idx = order.indexOf(phase.phase);
|
|
27
|
+
if (idx > lastIdx) {
|
|
28
|
+
inOrder++;
|
|
29
|
+
lastIdx = idx;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
// Normalize to 5 possible
|
|
33
|
+
const timingScore = Math.min(inOrder / order.length, 1) * 15;
|
|
34
|
+
return Math.round(protocolScore + integrityScore + edgeScore + timingScore);
|
|
35
|
+
}
|
|
36
|
+
function makeTrace(phases, writes) {
|
|
37
|
+
return {
|
|
38
|
+
schemaVersion: "1.0",
|
|
39
|
+
session: {
|
|
40
|
+
id: "test",
|
|
41
|
+
agent: "parallax",
|
|
42
|
+
agentVersion: "0.2.0",
|
|
43
|
+
startedAt: "2026-01-01T00:00:00.000Z",
|
|
44
|
+
endedAt: "2026-01-01T01:00:00.000Z",
|
|
45
|
+
},
|
|
46
|
+
phases: phases.map((p) => ({
|
|
47
|
+
phase: p,
|
|
48
|
+
timestamp: "2026-01-01T00:00:00.000Z",
|
|
49
|
+
data: {},
|
|
50
|
+
})),
|
|
51
|
+
writes,
|
|
52
|
+
coherenceScore: null,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
describe("Coherence score", () => {
|
|
56
|
+
it("returns 0 for empty trace", () => {
|
|
57
|
+
const trace = makeTrace([], []);
|
|
58
|
+
const score = computeCoherenceScore(trace);
|
|
59
|
+
expect(score).toBe(0);
|
|
60
|
+
});
|
|
61
|
+
it("returns near-perfect for complete protocol with all passes", () => {
|
|
62
|
+
const trace = makeTrace(["ambiguity_check", "four_invariants", "verification_gate", "commit_decision", "summary"], [
|
|
63
|
+
{ file: "a.ts", timestamp: "", verification: "pass", frictionRetriesLeft: 3 },
|
|
64
|
+
{ file: "b.ts", timestamp: "", verification: "pass", frictionRetriesLeft: 3 },
|
|
65
|
+
{ file: "c.ts", timestamp: "", verification: "pass", frictionRetriesLeft: 3 },
|
|
66
|
+
]);
|
|
67
|
+
const score = computeCoherenceScore(trace);
|
|
68
|
+
// Protocol coverage: 5/5 * 30 = 30
|
|
69
|
+
// Integrity: 3/3 * 35 = 35
|
|
70
|
+
// Edge: 0/3 * 20 = 0 (no analyze phases)
|
|
71
|
+
// Timing: 5/5 * 15 = 15
|
|
72
|
+
// Total: 80
|
|
73
|
+
expect(score).toBe(80);
|
|
74
|
+
});
|
|
75
|
+
it("penalizes verification failures", () => {
|
|
76
|
+
const trace = makeTrace(["ambiguity_check", "four_invariants", "verification_gate", "commit_decision", "summary"], [
|
|
77
|
+
{ file: "a.ts", timestamp: "", verification: "pass", frictionRetriesLeft: 3 },
|
|
78
|
+
{ file: "b.ts", timestamp: "", verification: "fail", frictionRetriesLeft: 2 },
|
|
79
|
+
]);
|
|
80
|
+
const score = computeCoherenceScore(trace);
|
|
81
|
+
// Protocol: 5/5 * 30 = 30
|
|
82
|
+
// Integrity: 1/2 * 35 = 17.5
|
|
83
|
+
// Edge: 0
|
|
84
|
+
// Timing: 5/5 * 15 = 15
|
|
85
|
+
// Total: 62.5 -> 63
|
|
86
|
+
expect(score).toBe(63);
|
|
87
|
+
});
|
|
88
|
+
it("penalizes missing protocol steps", () => {
|
|
89
|
+
const trace = makeTrace(["ambiguity_check", "verification_gate"], [
|
|
90
|
+
{ file: "a.ts", timestamp: "", verification: "pass", frictionRetriesLeft: 3 },
|
|
91
|
+
]);
|
|
92
|
+
const score = computeCoherenceScore(trace);
|
|
93
|
+
// Protocol: 2/5 * 30 = 12
|
|
94
|
+
// Integrity: 1/1 * 35 = 35
|
|
95
|
+
// Edge: 0
|
|
96
|
+
// Timing: inOrder starts with ambiguity (idx 0), then verification_gate (idx 2) -> 2
|
|
97
|
+
// 2/5 * 15 = 6
|
|
98
|
+
// Total: 53
|
|
99
|
+
expect(score).toBe(53);
|
|
100
|
+
});
|
|
101
|
+
it("handles partial trace without crashing", () => {
|
|
102
|
+
const trace = makeTrace([], []);
|
|
103
|
+
// Should not crash
|
|
104
|
+
expect(() => computeCoherenceScore(trace)).not.toThrow();
|
|
105
|
+
});
|
|
106
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for trace recording and export.
|
|
3
|
+
*/
|
|
4
|
+
import { describe, it, expect, beforeEach } from "vitest";
|
|
5
|
+
function createTrace() {
|
|
6
|
+
return {
|
|
7
|
+
schemaVersion: "1.0",
|
|
8
|
+
session: {
|
|
9
|
+
id: "test-session",
|
|
10
|
+
agent: "parallax",
|
|
11
|
+
agentVersion: "0.2.0",
|
|
12
|
+
startedAt: new Date().toISOString(),
|
|
13
|
+
endedAt: null,
|
|
14
|
+
},
|
|
15
|
+
phases: [],
|
|
16
|
+
writes: [],
|
|
17
|
+
coherenceScore: null,
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
function addPhase(trace, phase, data = {}) {
|
|
21
|
+
trace.phases.push({ phase, timestamp: new Date().toISOString(), data });
|
|
22
|
+
}
|
|
23
|
+
function addWrite(trace, file, verdict, retries) {
|
|
24
|
+
trace.writes.push({
|
|
25
|
+
file,
|
|
26
|
+
timestamp: new Date().toISOString(),
|
|
27
|
+
verification: verdict,
|
|
28
|
+
frictionRetriesLeft: retries,
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function computeMetrics(trace) {
|
|
32
|
+
const totalWrites = trace.writes.length;
|
|
33
|
+
const passes = trace.writes.filter((w) => w.verification === "pass").length;
|
|
34
|
+
const firstPass = trace.writes.filter((w) => w.verification === "pass" && w.frictionRetriesLeft === 3).length;
|
|
35
|
+
const phaseNames = new Set(trace.phases.map((p) => p.phase));
|
|
36
|
+
const required = [
|
|
37
|
+
"ambiguity_check",
|
|
38
|
+
"four_invariants",
|
|
39
|
+
"verification_gate",
|
|
40
|
+
"commit_decision",
|
|
41
|
+
"summary",
|
|
42
|
+
];
|
|
43
|
+
const completed = required.filter((r) => phaseNames.has(r)).length;
|
|
44
|
+
return {
|
|
45
|
+
totalWrites,
|
|
46
|
+
verificationPassRate: totalWrites > 0 ? passes / totalWrites : 0,
|
|
47
|
+
firstAttemptPassRate: totalWrites > 0 ? firstPass / totalWrites : 0,
|
|
48
|
+
protocolStepsCompleted: completed,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
describe("Trace recording", () => {
|
|
52
|
+
let trace;
|
|
53
|
+
beforeEach(() => {
|
|
54
|
+
trace = createTrace();
|
|
55
|
+
});
|
|
56
|
+
it("creates an empty trace with correct schema", () => {
|
|
57
|
+
expect(trace.schemaVersion).toBe("1.0");
|
|
58
|
+
expect(trace.session.id).toBe("test-session");
|
|
59
|
+
expect(trace.phases).toHaveLength(0);
|
|
60
|
+
expect(trace.writes).toHaveLength(0);
|
|
61
|
+
});
|
|
62
|
+
it("records phases in order", () => {
|
|
63
|
+
addPhase(trace, "ambiguity_check", { level: "LOW" });
|
|
64
|
+
addPhase(trace, "four_invariants");
|
|
65
|
+
addPhase(trace, "verification_gate");
|
|
66
|
+
expect(trace.phases).toHaveLength(3);
|
|
67
|
+
expect(trace.phases[0].phase).toBe("ambiguity_check");
|
|
68
|
+
expect(trace.phases[1].phase).toBe("four_invariants");
|
|
69
|
+
expect(trace.phases[2].phase).toBe("verification_gate");
|
|
70
|
+
expect(trace.phases[0].data.level).toBe("LOW");
|
|
71
|
+
});
|
|
72
|
+
it("records writes with verification status", () => {
|
|
73
|
+
addWrite(trace, "src/main.ts", "pass", 3);
|
|
74
|
+
addWrite(trace, "src/lib.ts", "fail", 2);
|
|
75
|
+
addWrite(trace, "src/utils.ts", "pass", 3);
|
|
76
|
+
expect(trace.writes).toHaveLength(3);
|
|
77
|
+
expect(trace.writes[0].file).toBe("src/main.ts");
|
|
78
|
+
expect(trace.writes[1].verification).toBe("fail");
|
|
79
|
+
expect(trace.writes[2].frictionRetriesLeft).toBe(3);
|
|
80
|
+
});
|
|
81
|
+
it("computes metrics from trace data", () => {
|
|
82
|
+
addPhase(trace, "ambiguity_check");
|
|
83
|
+
addPhase(trace, "four_invariants");
|
|
84
|
+
addPhase(trace, "verification_gate");
|
|
85
|
+
addPhase(trace, "commit_decision");
|
|
86
|
+
addPhase(trace, "summary");
|
|
87
|
+
addWrite(trace, "a.ts", "pass", 3);
|
|
88
|
+
addWrite(trace, "b.ts", "pass", 3);
|
|
89
|
+
addWrite(trace, "c.ts", "fail", 1);
|
|
90
|
+
addWrite(trace, "d.ts", "pass", 3);
|
|
91
|
+
const metrics = computeMetrics(trace);
|
|
92
|
+
expect(metrics.totalWrites).toBe(4);
|
|
93
|
+
expect(metrics.verificationPassRate).toBe(0.75);
|
|
94
|
+
expect(metrics.firstAttemptPassRate).toBe(0.75); // 3 out of 4 passed on first attempt
|
|
95
|
+
expect(metrics.protocolStepsCompleted).toBe(5);
|
|
96
|
+
});
|
|
97
|
+
it("handles empty trace metrics", () => {
|
|
98
|
+
const metrics = computeMetrics(trace);
|
|
99
|
+
expect(metrics.totalWrites).toBe(0);
|
|
100
|
+
expect(metrics.verificationPassRate).toBe(0);
|
|
101
|
+
expect(metrics.protocolStepsCompleted).toBe(0);
|
|
102
|
+
});
|
|
103
|
+
it("isolates traces by session", () => {
|
|
104
|
+
const trace2 = createTrace();
|
|
105
|
+
addPhase(trace, "ambiguity_check");
|
|
106
|
+
expect(trace.phases).toHaveLength(1);
|
|
107
|
+
expect(trace2.phases).toHaveLength(0);
|
|
108
|
+
});
|
|
109
|
+
});
|
package/dist/types.d.ts
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* Copyright (c) 2026 Master0fFate
|
|
9
9
|
*/
|
|
10
10
|
export type AgentMode = "free" | "plan" | "build" | "debug";
|
|
11
|
-
export type ProtocolStep = "ambiguity" | "invariants" | "gate" | "commit" | "summary";
|
|
11
|
+
export type ProtocolStep = "ambiguity" | "invariants" | "gate" | "design" | "commit" | "summary";
|
|
12
12
|
export type ProjectType = "cargo" | "tsc" | "lint" | "python" | null;
|
|
13
13
|
export interface FrictionState {
|
|
14
14
|
successes: number;
|
|
@@ -23,6 +23,7 @@ export interface ProtocolState {
|
|
|
23
23
|
ambiguityDone: boolean;
|
|
24
24
|
invariantsDone: boolean;
|
|
25
25
|
gateDone: boolean;
|
|
26
|
+
designDone: boolean;
|
|
26
27
|
commitDone: boolean;
|
|
27
28
|
summaryDone: boolean;
|
|
28
29
|
writesBeforeGate: number;
|
|
@@ -34,7 +35,7 @@ export interface VerifyResult {
|
|
|
34
35
|
stderr: string;
|
|
35
36
|
combined: string;
|
|
36
37
|
}
|
|
37
|
-
export type PhaseName = "ambiguity_check" | "four_invariants" | "verification_gate" | "mode_switch" | "execution" | "commit_decision" | "summary";
|
|
38
|
+
export type PhaseName = "ambiguity_check" | "four_invariants" | "verification_gate" | "design_check" | "mode_switch" | "execution" | "commit_decision" | "summary";
|
|
38
39
|
export type WriteVerdict = "pass" | "fail" | "skipped" | "unknown";
|
|
39
40
|
export interface PhaseRecord {
|
|
40
41
|
phase: PhaseName;
|
|
@@ -92,3 +93,11 @@ export interface CliCommand {
|
|
|
92
93
|
usage: string;
|
|
93
94
|
run: (args: string[]) => Promise<number>;
|
|
94
95
|
}
|
|
96
|
+
export interface ParallaxConfig {
|
|
97
|
+
strictness?: "strict" | "standard" | "relaxed";
|
|
98
|
+
minScore?: number;
|
|
99
|
+
adaptiveProtocol?: boolean;
|
|
100
|
+
designDocRequired?: boolean;
|
|
101
|
+
trivialPatterns?: string[];
|
|
102
|
+
highRiskPatterns?: string[];
|
|
103
|
+
}
|
|
@@ -60,6 +60,16 @@ declare const _default: {
|
|
|
60
60
|
pretty?: boolean | undefined;
|
|
61
61
|
}, context: import("@opencode-ai/plugin").ToolContext): Promise<import("@opencode-ai/plugin").ToolResult>;
|
|
62
62
|
};
|
|
63
|
+
parallax_trace_pr_comment: {
|
|
64
|
+
description: string;
|
|
65
|
+
args: {};
|
|
66
|
+
execute(args: Record<string, never>, context: import("@opencode-ai/plugin").ToolContext): Promise<import("@opencode-ai/plugin").ToolResult>;
|
|
67
|
+
};
|
|
68
|
+
parallax_trace_view: {
|
|
69
|
+
description: string;
|
|
70
|
+
args: {};
|
|
71
|
+
execute(args: Record<string, never>, context: import("@opencode-ai/plugin").ToolContext): Promise<import("@opencode-ai/plugin").ToolResult>;
|
|
72
|
+
};
|
|
63
73
|
};
|
|
64
74
|
"tool.execute.before": (input: {
|
|
65
75
|
tool: string;
|
|
@@ -74,19 +84,11 @@ declare const _default: {
|
|
|
74
84
|
properties?: Record<string, unknown>;
|
|
75
85
|
};
|
|
76
86
|
}) => Promise<void>;
|
|
77
|
-
"
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
};
|
|
83
|
-
}) => Promise<void>;
|
|
84
|
-
"chat.params": (input: {
|
|
85
|
-
sessionID: string;
|
|
86
|
-
agent: string;
|
|
87
|
-
model: {
|
|
88
|
-
id: string;
|
|
89
|
-
};
|
|
87
|
+
"shell.env": (input: {
|
|
88
|
+
cwd: string;
|
|
89
|
+
sessionID?: string;
|
|
90
|
+
}, output: {
|
|
91
|
+
env: Record<string, string>;
|
|
90
92
|
}) => Promise<void>;
|
|
91
93
|
"experimental.chat.system.transform": (_input: unknown, output: {
|
|
92
94
|
system?: string[];
|