parallax-opencode 0.3.8 → 0.3.10
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 +264 -260
- package/dist/plugin.js +12 -6
- package/dist-standalone/parallax-engine.js +7 -5
- package/package.json +1 -1
- package/dist/tests/detect.test.d.ts +0 -1
- package/dist/tests/detect.test.js +0 -75
- package/dist/tests/friction.test.d.ts +0 -1
- package/dist/tests/friction.test.js +0 -87
- package/dist/tests/protocol.test.d.ts +0 -1
- package/dist/tests/protocol.test.js +0 -80
- package/dist/tests/score.test.d.ts +0 -1
- package/dist/tests/score.test.js +0 -106
- package/dist/tests/trace.test.d.ts +0 -1
- package/dist/tests/trace.test.js +0 -109
package/README.md
CHANGED
|
@@ -1,260 +1,264 @@
|
|
|
1
|
-
[](https://github.com/Master0fFate/parallax-opencode)
|
|
2
|
-
|
|
3
|
-
# PARALLAX ENGINE
|
|
4
|
-
|
|
5
|
-
**The first AI coding assistant that shows its work.**
|
|
6
|
-
|
|
7
|
-
[](LICENSE)
|
|
8
|
-
[](https://opencode.ai)
|
|
9
|
-
[](https://www.npmjs.com/package/parallax-opencode)
|
|
10
|
-
[]()
|
|
11
|
-
[]()
|
|
12
|
-
|
|
13
|
-
---
|
|
14
|
-
|
|
15
|
-
## Quick Install
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
npx parallax-opencode
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
After install, restart OpenCode and press **Tab** to cycle to the Parallax agent.
|
|
22
|
-
|
|
23
|
-
---
|
|
24
|
-
|
|
25
|
-
## What Makes Parallax Different
|
|
26
|
-
|
|
27
|
-
Every AI coding tool gives you code. None of them show you **how they got there**.
|
|
28
|
-
|
|
29
|
-
Parallax captures the complete reasoning trace of every session -- the ambiguity assessment, the invariants analysis, every verification result, every decision -- and makes it visible, exportable, and replayable.
|
|
30
|
-
|
|
31
|
-
> "Here is my complete reasoning trace. Review it. Score it. Compare it. See for yourself."
|
|
32
|
-
|
|
33
|
-
### The 4 Invariants
|
|
34
|
-
|
|
35
|
-
| Question | Why It Matters |
|
|
36
|
-
|---|---|
|
|
37
|
-
| Where does state live? | Ownership & truth, consistency, blast radius |
|
|
38
|
-
| Where does feedback live? | Observability, debugging, monitoring |
|
|
39
|
-
| What breaks if I delete this? | Coupling & fragility, safe refactoring |
|
|
40
|
-
| When does timing matter? | Async & ordering, race conditions, correctness |
|
|
41
|
-
|
|
42
|
-
Based on [@acidgreenservers AGENTS.md](https://gist.github.com/acidgreenservers/001185d63e5cd65f9fbe6f7a1c70a200)
|
|
43
|
-
|
|
44
|
-
### The Coherence Score
|
|
45
|
-
|
|
46
|
-
After every session, Parallax computes an evidence-based quality score (0-100):
|
|
47
|
-
|
|
48
|
-
- **Protocol Coverage (30%)** -- Were all 6 protocol phases completed?
|
|
49
|
-
- **Verification Integrity (35%)** -- Pass rate on first attempt?
|
|
50
|
-
- **Edge Case Coverage (20%)** -- How many edge categories were analyzed?
|
|
51
|
-
- **Timing Discipline (15%)** -- Were phases in correct order?
|
|
52
|
-
|
|
53
|
-
Track scores over time:
|
|
54
|
-
|
|
55
|
-
```bash
|
|
56
|
-
parallax trace trend
|
|
57
|
-
parallax trace report --week
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
### Trace in Your PR
|
|
61
|
-
|
|
62
|
-
The AI calls `parallax_trace_pr_comment` at session end and outputs a formatted markdown block directly in the chat. Copy it into your PR. No terminal needed.
|
|
63
|
-
|
|
64
|
-
### CI/CD Gate
|
|
65
|
-
|
|
66
|
-
```yaml
|
|
67
|
-
- name: Parallax Gate
|
|
68
|
-
run: parallax gate --min-score 70
|
|
69
|
-
```
|
|
70
|
-
|
|
71
|
-
Pre-commit hook:
|
|
72
|
-
```bash
|
|
73
|
-
echo 'parallax pre-commit' >> .git/hooks/pre-commit
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
---
|
|
77
|
-
|
|
78
|
-
## Comparison
|
|
79
|
-
|
|
80
|
-
| Feature | Plain OpenCode | Cursor | Copilot | Parallax |
|
|
81
|
-
|---|---|---|---|---|
|
|
82
|
-
| Structured reasoning trace | No | No | No | **Yes** |
|
|
83
|
-
| Trace as PR comment (inline) | No | No | No | **Yes** |
|
|
84
|
-
| Evidence-based quality score | No | No | No | **Yes** |
|
|
85
|
-
| Protocol enforcement (6 steps) | No | No | No | **Yes** |
|
|
86
|
-
| Mode switching (plan/build/debug) | No | No | No | **Yes** |
|
|
87
|
-
| CI coherence gate | No | No | No | **Yes** |
|
|
88
|
-
| Session retrospective | No | No | No | **Yes** |
|
|
89
|
-
| Design doc enforcement | No | No | No | **Yes** |
|
|
90
|
-
| Multi-agent state sharing | No | No | No | **Yes** |
|
|
91
|
-
| Shell env injection | No | No | No | **Yes** |
|
|
92
|
-
| Trace analytics | No | No | No | **Yes** |
|
|
93
|
-
|
|
94
|
-
---
|
|
95
|
-
|
|
96
|
-
## Architecture
|
|
97
|
-
|
|
98
|
-
```
|
|
99
|
-
Parallax Agent (system prompt)
|
|
100
|
-
|
|
|
101
|
-
+-- Plugin hooks (8)
|
|
102
|
-
| event --> session ID + agent switches
|
|
103
|
-
| tool.execute.before --> protocol enforcement + friction + design doc gate
|
|
104
|
-
| tool.execute.after --> auto-verify + trace recording + state persistence
|
|
105
|
-
| experimental.chat.system.transform --> protocol status + mode skill + agent context
|
|
106
|
-
| experimental.session.compacting --> state preservation + trace export
|
|
107
|
-
| shell.env --> PARALLAX_MODE, PARALLAX_SESSION_ID in shell
|
|
108
|
-
|
|
|
109
|
-
+-- Custom tools (9)
|
|
110
|
-
| parallax_verify auto-verify after writes
|
|
111
|
-
| parallax_analyze multi-perspective analysis
|
|
112
|
-
| parallax_checkin protocol step checkin (6 steps incl. design)
|
|
113
|
-
| parallax_plan / _build / _debug mode switching
|
|
114
|
-
| parallax_trace_export export session trace to JSON file
|
|
115
|
-
| parallax_trace_pr_comment generate trace as PR-ready markdown
|
|
116
|
-
| parallax_trace_view show full reasoning trace inline in chat
|
|
117
|
-
|
|
|
118
|
-
+-- State files
|
|
119
|
-
|
|
|
120
|
-
|
|
|
121
|
-
|
|
|
122
|
-
|
|
|
123
|
-
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
|
144
|
-
|
|
145
|
-
|
|
|
146
|
-
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
"
|
|
159
|
-
"
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
|
168
|
-
|
|
169
|
-
| `
|
|
170
|
-
| `
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
cp -
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
|
196
|
-
|
|
197
|
-
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
```
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
parallax
|
|
225
|
-
parallax trace
|
|
226
|
-
parallax trace
|
|
227
|
-
parallax trace
|
|
228
|
-
parallax trace
|
|
229
|
-
parallax
|
|
230
|
-
parallax
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
src/
|
|
245
|
-
src/
|
|
246
|
-
src/
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
1
|
+
[](https://github.com/Master0fFate/parallax-opencode)
|
|
2
|
+
|
|
3
|
+
# PARALLAX ENGINE
|
|
4
|
+
|
|
5
|
+
**The first AI coding assistant that shows its work.**
|
|
6
|
+
|
|
7
|
+
[](LICENSE)
|
|
8
|
+
[](https://opencode.ai)
|
|
9
|
+
[](https://www.npmjs.com/package/parallax-opencode)
|
|
10
|
+
[]()
|
|
11
|
+
[]()
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Quick Install
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npx parallax-opencode
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
After install, restart OpenCode and press **Tab** to cycle to the Parallax agent.
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
25
|
+
## What Makes Parallax Different
|
|
26
|
+
|
|
27
|
+
Every AI coding tool gives you code. None of them show you **how they got there**.
|
|
28
|
+
|
|
29
|
+
Parallax captures the complete reasoning trace of every session -- the ambiguity assessment, the invariants analysis, every verification result, every decision -- and makes it visible, exportable, and replayable.
|
|
30
|
+
|
|
31
|
+
> "Here is my complete reasoning trace. Review it. Score it. Compare it. See for yourself."
|
|
32
|
+
|
|
33
|
+
### The 4 Invariants
|
|
34
|
+
|
|
35
|
+
| Question | Why It Matters |
|
|
36
|
+
|---|---|
|
|
37
|
+
| Where does state live? | Ownership & truth, consistency, blast radius |
|
|
38
|
+
| Where does feedback live? | Observability, debugging, monitoring |
|
|
39
|
+
| What breaks if I delete this? | Coupling & fragility, safe refactoring |
|
|
40
|
+
| When does timing matter? | Async & ordering, race conditions, correctness |
|
|
41
|
+
|
|
42
|
+
Based on [@acidgreenservers AGENTS.md](https://gist.github.com/acidgreenservers/001185d63e5cd65f9fbe6f7a1c70a200)
|
|
43
|
+
|
|
44
|
+
### The Coherence Score
|
|
45
|
+
|
|
46
|
+
After every session, Parallax computes an evidence-based quality score (0-100):
|
|
47
|
+
|
|
48
|
+
- **Protocol Coverage (30%)** -- Were all 6 protocol phases completed?
|
|
49
|
+
- **Verification Integrity (35%)** -- Pass rate on first attempt?
|
|
50
|
+
- **Edge Case Coverage (20%)** -- How many edge categories were analyzed?
|
|
51
|
+
- **Timing Discipline (15%)** -- Were phases in correct order?
|
|
52
|
+
|
|
53
|
+
Track scores over time:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
parallax trace trend
|
|
57
|
+
parallax trace report --week
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Trace in Your PR
|
|
61
|
+
|
|
62
|
+
The AI calls `parallax_trace_pr_comment` at session end and outputs a formatted markdown block directly in the chat. Copy it into your PR. No terminal needed.
|
|
63
|
+
|
|
64
|
+
### CI/CD Gate
|
|
65
|
+
|
|
66
|
+
```yaml
|
|
67
|
+
- name: Parallax Gate
|
|
68
|
+
run: parallax gate --min-score 70
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Pre-commit hook:
|
|
72
|
+
```bash
|
|
73
|
+
echo 'parallax pre-commit' >> .git/hooks/pre-commit
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Comparison
|
|
79
|
+
|
|
80
|
+
| Feature | Plain OpenCode | Cursor | Copilot | Parallax |
|
|
81
|
+
|---|---|---|---|---|
|
|
82
|
+
| Structured reasoning trace | No | No | No | **Yes** |
|
|
83
|
+
| Trace as PR comment (inline) | No | No | No | **Yes** |
|
|
84
|
+
| Evidence-based quality score | No | No | No | **Yes** |
|
|
85
|
+
| Protocol enforcement (6 steps) | No | No | No | **Yes** |
|
|
86
|
+
| Mode switching (plan/build/debug) | No | No | No | **Yes** |
|
|
87
|
+
| CI coherence gate | No | No | No | **Yes** |
|
|
88
|
+
| Session retrospective | No | No | No | **Yes** |
|
|
89
|
+
| Design doc enforcement | No | No | No | **Yes** |
|
|
90
|
+
| Multi-agent state sharing | No | No | No | **Yes** |
|
|
91
|
+
| Shell env injection | No | No | No | **Yes** |
|
|
92
|
+
| Trace analytics | No | No | No | **Yes** |
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## Architecture
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
Parallax Agent (system prompt)
|
|
100
|
+
|
|
|
101
|
+
+-- Plugin hooks (8)
|
|
102
|
+
| event --> session ID + agent switches
|
|
103
|
+
| tool.execute.before --> protocol enforcement + friction + design doc gate
|
|
104
|
+
| tool.execute.after --> auto-verify + trace recording + state persistence
|
|
105
|
+
| experimental.chat.system.transform --> protocol status + mode skill + agent context
|
|
106
|
+
| experimental.session.compacting --> state preservation + trace export
|
|
107
|
+
| shell.env --> PARALLAX_MODE, PARALLAX_SESSION_ID in shell
|
|
108
|
+
|
|
|
109
|
+
+-- Custom tools (9)
|
|
110
|
+
| parallax_verify auto-verify after writes
|
|
111
|
+
| parallax_analyze multi-perspective analysis
|
|
112
|
+
| parallax_checkin protocol step checkin (6 steps incl. design)
|
|
113
|
+
| parallax_plan / _build / _debug mode switching
|
|
114
|
+
| parallax_trace_export export session trace to JSON file
|
|
115
|
+
| parallax_trace_pr_comment generate trace as PR-ready markdown
|
|
116
|
+
| parallax_trace_view show full reasoning trace inline in chat
|
|
117
|
+
|
|
|
118
|
+
+-- State files (at ~/.parallax/ in user home)
|
|
119
|
+
| state.json written on every transition (immediate on checkins)
|
|
120
|
+
| traces/ per-session JSON trace files
|
|
121
|
+
| scores.jsonl append-only score history
|
|
122
|
+
| config.json per-project config (optional)
|
|
123
|
+
|
|
|
124
|
+
| Note: Plugin process cwd is the user home directory (~), so all
|
|
125
|
+
| state persists at ~/.parallax/. The project .parallax/ dir is
|
|
126
|
+
| used by the CLI only.
|
|
127
|
+
|
|
|
128
|
+
+-- CLI (parallax) -- 11 commands
|
|
129
|
+
init | trace list/show/score/export/trend/report/compare/compliance
|
|
130
|
+
gate --min-score <n> | pre-commit
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Three Modes
|
|
134
|
+
|
|
135
|
+
| Mode | Tool | When |
|
|
136
|
+
|---|---|---|
|
|
137
|
+
| **PLAN** | `parallax_plan` | Vague requirements, before writing code |
|
|
138
|
+
| **BUILD** | `parallax_build` | Executing, writing code (default) |
|
|
139
|
+
| **DEBUG** | `parallax_debug` | Post-build quality and security audit |
|
|
140
|
+
|
|
141
|
+
### Six Protocol Steps
|
|
142
|
+
|
|
143
|
+
| Step | Checkin | Enforced |
|
|
144
|
+
|---|---|---|
|
|
145
|
+
| 1. Ambiguity Check | `parallax_checkin("ambiguity")` | Blocks all writes until done |
|
|
146
|
+
| 2. 4 Invariants | `parallax_checkin("invariants")` | Warns after 3 writes without checkin |
|
|
147
|
+
| 3. Verification Gate | `parallax_checkin("gate")` | Requires invariants first |
|
|
148
|
+
| 4. Design Doc *(opt-in)* | `parallax_checkin("design")` | Per project via config |
|
|
149
|
+
| 5. Commit Decision | `parallax_checkin("commit")` | Any time after gate |
|
|
150
|
+
| 6. Summary | `parallax_checkin("summary")` | Generates retrospective |
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Project Config (.parallax/config.json)
|
|
155
|
+
|
|
156
|
+
```json
|
|
157
|
+
{
|
|
158
|
+
"strictness": "standard",
|
|
159
|
+
"minScore": 70,
|
|
160
|
+
"adaptiveProtocol": false,
|
|
161
|
+
"designDocRequired": false,
|
|
162
|
+
"trivialPatterns": ["*.md", "*.json"],
|
|
163
|
+
"highRiskPatterns": ["**/auth/**", "**/*.env*"]
|
|
164
|
+
}
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
| Key | Default | Description |
|
|
168
|
+
|---|---|---|
|
|
169
|
+
| `strictness` | `"standard"` | `"strict"` / `"standard"` / `"relaxed"` |
|
|
170
|
+
| `minScore` | `70` | Gate threshold for CI |
|
|
171
|
+
| `adaptiveProtocol` | `false` | Auto-skip gate steps for trivial changes |
|
|
172
|
+
| `designDocRequired` | `false` | Block writes until design doc produced |
|
|
173
|
+
| `trivialPatterns` | `[]` | File patterns considered low-risk |
|
|
174
|
+
| `highRiskPatterns` | `[]` | Patterns always requiring full protocol |
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Manual Install
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
cp dist-standalone/parallax-engine.js ~/.config/opencode/plugins/
|
|
182
|
+
cp agents/parallax.md ~/.config/opencode/agents/
|
|
183
|
+
cp -r skills/parallax ~/.config/opencode/skills/
|
|
184
|
+
cp -r skills/parallax-plan ~/.config/opencode/skills/
|
|
185
|
+
cp -r skills/parallax-debug ~/.config/opencode/skills/
|
|
186
|
+
cd ~/.config/opencode && npm init -y && npm install @opencode-ai/plugin
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Project Status
|
|
192
|
+
|
|
193
|
+
**v0.3.9** -- Cross-context protocol enforcement + full plugin integration
|
|
194
|
+
|
|
195
|
+
| Phase | Status | What |
|
|
196
|
+
|---|---|---|
|
|
197
|
+
| 1: Trace Artifact | Complete | PR comments, inline viewer, CI gate, pre-commit |
|
|
198
|
+
| 2: Protocol Intelligence | Complete | State persistence, retrospective, multi-agent sharing, shell env, design doc |
|
|
199
|
+
| 3: Developer Experience | Partial | Config system, design doc gate. Adaptive protocol deferred |
|
|
200
|
+
| 4: TUI Integration | Planned | Sidebar protocol status widget |
|
|
201
|
+
| 5: Analytics | Complete | Weekly reports, trace comparison, compliance reports |
|
|
202
|
+
|
|
203
|
+
See [ROADMAP.md](ROADMAP.md) for the full strategic plan.
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## Commands
|
|
208
|
+
|
|
209
|
+
### Plugin Tools (AI calls in OpenCode chat)
|
|
210
|
+
|
|
211
|
+
```
|
|
212
|
+
parallax_verify Run project verification
|
|
213
|
+
parallax_analyze {topic} Multi-perspective analysis
|
|
214
|
+
parallax_checkin {step} Protocol step (ambiguity/invariants/gate/design/commit/summary)
|
|
215
|
+
parallax_plan / _build / _debug Mode switching
|
|
216
|
+
parallax_trace_export Export session trace to JSON
|
|
217
|
+
parallax_trace_pr_comment Generate trace as PR-ready markdown
|
|
218
|
+
parallax_trace_view Show reasoning trace inline in chat
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### CLI (for CI and analytics)
|
|
222
|
+
|
|
223
|
+
```
|
|
224
|
+
parallax init Create .parallax/ directory
|
|
225
|
+
parallax trace list List all traces
|
|
226
|
+
parallax trace show <id> Show trace details
|
|
227
|
+
parallax trace score <id> Show coherence score breakdown
|
|
228
|
+
parallax trace export <id> Export trace as pretty JSON
|
|
229
|
+
parallax trace trend Show score trend over time
|
|
230
|
+
parallax trace report --week Weekly analytics report
|
|
231
|
+
parallax trace compare <a> <b> Side-by-side trace comparison
|
|
232
|
+
parallax trace compliance <id> Protocol compliance report
|
|
233
|
+
parallax gate --min-score <n> CI coherence gate
|
|
234
|
+
parallax pre-commit Git pre-commit hook wrapper
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## Files
|
|
240
|
+
|
|
241
|
+
```
|
|
242
|
+
parallax_plugin/
|
|
243
|
+
agents/parallax.md # Primary agent definition
|
|
244
|
+
src/plugin.ts # Canonical plugin (TypeScript, 1000+ lines)
|
|
245
|
+
src/types.ts # Shared type definitions
|
|
246
|
+
src/detect.ts # Project detection
|
|
247
|
+
src/trace.ts # Trace recording and export
|
|
248
|
+
src/score.ts # Coherence score + analytics
|
|
249
|
+
src/cli.ts # CLI entry point (11 commands)
|
|
250
|
+
src/tests/ # 30 tests across 5 files
|
|
251
|
+
dist/ # Compiled output
|
|
252
|
+
dist-standalone/ # Standalone bundled plugin (34KB)
|
|
253
|
+
skills/ # Protocol skills
|
|
254
|
+
scripts/ # Install and publish scripts
|
|
255
|
+
.parallax/ # Runtime state + traces + scores
|
|
256
|
+
ROADMAP.md # Strategic roadmap
|
|
257
|
+
CHANGELOG.md # Version history
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## License
|
|
263
|
+
|
|
264
|
+
MIT (c) [@Master0fFate](https://github.com/Master0fFate)
|
package/dist/plugin.js
CHANGED
|
@@ -101,9 +101,10 @@ function flushState() {
|
|
|
101
101
|
const s = getFriction();
|
|
102
102
|
const m = getMode();
|
|
103
103
|
const p = getProtocol();
|
|
104
|
+
const trace = getTrace(sessionId());
|
|
104
105
|
const state = {
|
|
105
|
-
sessionId:
|
|
106
|
-
sessionStart:
|
|
106
|
+
sessionId: "current",
|
|
107
|
+
sessionStart: trace.session.startedAt,
|
|
107
108
|
mode: m.mode,
|
|
108
109
|
friction: {
|
|
109
110
|
successes: s.successes,
|
|
@@ -122,9 +123,12 @@ function flushState() {
|
|
|
122
123
|
gateBlocked: p.gateBlocked,
|
|
123
124
|
},
|
|
124
125
|
};
|
|
125
|
-
|
|
126
|
+
// Debug: write BEFORE state file to isolate error
|
|
127
|
+
const json = JSON.stringify(state, null, 2);
|
|
128
|
+
writeFileSync(STATE_FILE, json, "utf8");
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
126
131
|
}
|
|
127
|
-
catch { }
|
|
128
132
|
}
|
|
129
133
|
function writeState(immediate = false) {
|
|
130
134
|
if (immediate) {
|
|
@@ -143,7 +147,7 @@ function writeState(immediate = false) {
|
|
|
143
147
|
const diskState = readProtocolFromDisk();
|
|
144
148
|
const p = diskState || getProtocol();
|
|
145
149
|
const state = {
|
|
146
|
-
sessionId:
|
|
150
|
+
sessionId: "current",
|
|
147
151
|
sessionStart: getTrace(sessionId()).session.startedAt,
|
|
148
152
|
mode: m.mode,
|
|
149
153
|
friction: {
|
|
@@ -597,7 +601,9 @@ export default {
|
|
|
597
601
|
"tool.execute.before": async (input) => {
|
|
598
602
|
if (!["write", "edit", "apply_patch"].includes(input.tool))
|
|
599
603
|
return;
|
|
600
|
-
|
|
604
|
+
// Read from disk: OpenCode loads plugin in separate execution contexts
|
|
605
|
+
// for tools vs hooks. In-memory Maps are NOT shared across contexts.
|
|
606
|
+
const p = readProtocolFromDisk() || getProtocol();
|
|
601
607
|
const cfg = loadConfig();
|
|
602
608
|
// Enforce ambiguity check before any write
|
|
603
609
|
if (!p.ambiguityDone) {
|
|
@@ -292,9 +292,10 @@ function flushState() {
|
|
|
292
292
|
const s = getFriction();
|
|
293
293
|
const m = getMode();
|
|
294
294
|
const p = getProtocol();
|
|
295
|
+
const trace = getTrace(sessionId());
|
|
295
296
|
const state = {
|
|
296
|
-
sessionId:
|
|
297
|
-
sessionStart:
|
|
297
|
+
sessionId: "current",
|
|
298
|
+
sessionStart: trace.session.startedAt,
|
|
298
299
|
mode: m.mode,
|
|
299
300
|
friction: {
|
|
300
301
|
successes: s.successes,
|
|
@@ -313,7 +314,8 @@ function flushState() {
|
|
|
313
314
|
gateBlocked: p.gateBlocked
|
|
314
315
|
}
|
|
315
316
|
};
|
|
316
|
-
|
|
317
|
+
const json = JSON.stringify(state, null, 2);
|
|
318
|
+
writeFileSync2(STATE_FILE, json, "utf8");
|
|
317
319
|
} catch {
|
|
318
320
|
}
|
|
319
321
|
}
|
|
@@ -331,7 +333,7 @@ function writeState(immediate = false) {
|
|
|
331
333
|
const diskState = readProtocolFromDisk();
|
|
332
334
|
const p = diskState || getProtocol();
|
|
333
335
|
const state = {
|
|
334
|
-
sessionId:
|
|
336
|
+
sessionId: "current",
|
|
335
337
|
sessionStart: getTrace(sessionId()).session.startedAt,
|
|
336
338
|
mode: m.mode,
|
|
337
339
|
friction: {
|
|
@@ -729,7 +731,7 @@ Coherence Score: ${breakdown.total}/100`;
|
|
|
729
731
|
// -----------------------------------------------------------------------
|
|
730
732
|
"tool.execute.before": async (input) => {
|
|
731
733
|
if (!["write", "edit", "apply_patch"].includes(input.tool)) return;
|
|
732
|
-
const p = getProtocol();
|
|
734
|
+
const p = readProtocolFromDisk() || getProtocol();
|
|
733
735
|
const cfg = loadConfig();
|
|
734
736
|
if (!p.ambiguityDone) {
|
|
735
737
|
throw new Error(
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "parallax-opencode",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.10",
|
|
4
4
|
"description": "PARALLAX ENGINE plugin for OpenCode -- protocol enforcement, friction-loop verification, mode switching (plan/build/debug), trace recording, coherence scoring, CI gate, and PR-ready trace artifacts",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/plugin.js",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,75 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,87 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,80 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/tests/score.test.js
DELETED
|
@@ -1,106 +0,0 @@
|
|
|
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
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
package/dist/tests/trace.test.js
DELETED
|
@@ -1,109 +0,0 @@
|
|
|
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
|
-
});
|