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 CHANGED
@@ -1,260 +1,264 @@
1
- [![Parallax Banner](https://capsule-render.vercel.app/api?type=waving&height=200&color=6c63ff&desc=PARALLAX%20ENGINE&descAlignY=65&fontColor=ffffff&section=header&reversal=true&textBg=false&animation=fadeIn)](https://github.com/Master0fFate/parallax-opencode)
2
-
3
- # PARALLAX ENGINE
4
-
5
- **The first AI coding assistant that shows its work.**
6
-
7
- [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
8
- [![OpenCode](https://img.shields.io/badge/OpenCode-plugin-6c63ff)](https://opencode.ai)
9
- [![npm](https://img.shields.io/npm/v/parallax-opencode)](https://www.npmjs.com/package/parallax-opencode)
10
- [![Tests](https://img.shields.io/badge/tests-30%20passing-brightgreen)]()
11
- [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen)]()
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
- | .parallax/state.json written on every transition (debounced)
120
- | .parallax/traces/ per-session JSON trace files
121
- | .parallax/scores.jsonl append-only score history
122
- | .parallax/config.json per-project config (optional)
123
- |
124
- +-- CLI (parallax) -- 11 commands
125
- init | trace list/show/score/export/trend/report/compare/compliance
126
- gate --min-score <n> | pre-commit
127
- ```
128
-
129
- ### Three Modes
130
-
131
- | Mode | Tool | When |
132
- |---|---|---|
133
- | **PLAN** | `parallax_plan` | Vague requirements, before writing code |
134
- | **BUILD** | `parallax_build` | Executing, writing code (default) |
135
- | **DEBUG** | `parallax_debug` | Post-build quality and security audit |
136
-
137
- ### Six Protocol Steps
138
-
139
- | Step | Checkin | Enforced |
140
- |---|---|---|
141
- | 1. Ambiguity Check | `parallax_checkin("ambiguity")` | Blocks all writes until done |
142
- | 2. 4 Invariants | `parallax_checkin("invariants")` | Warns after 3 writes without checkin |
143
- | 3. Verification Gate | `parallax_checkin("gate")` | Requires invariants first |
144
- | 4. Design Doc *(opt-in)* | `parallax_checkin("design")` | Per project via config |
145
- | 5. Commit Decision | `parallax_checkin("commit")` | Any time after gate |
146
- | 6. Summary | `parallax_checkin("summary")` | Generates retrospective |
147
-
148
- ---
149
-
150
- ## Project Config (.parallax/config.json)
151
-
152
- ```json
153
- {
154
- "strictness": "standard",
155
- "minScore": 70,
156
- "adaptiveProtocol": false,
157
- "designDocRequired": false,
158
- "trivialPatterns": ["*.md", "*.json"],
159
- "highRiskPatterns": ["**/auth/**", "**/*.env*"]
160
- }
161
- ```
162
-
163
- | Key | Default | Description |
164
- |---|---|---|
165
- | `strictness` | `"standard"` | `"strict"` / `"standard"` / `"relaxed"` |
166
- | `minScore` | `70` | Gate threshold for CI |
167
- | `adaptiveProtocol` | `false` | Auto-skip gate steps for trivial changes |
168
- | `designDocRequired` | `false` | Block writes until design doc produced |
169
- | `trivialPatterns` | `[]` | File patterns considered low-risk |
170
- | `highRiskPatterns` | `[]` | Patterns always requiring full protocol |
171
-
172
- ---
173
-
174
- ## Manual Install
175
-
176
- ```bash
177
- cp dist-standalone/parallax-engine.js ~/.config/opencode/plugins/
178
- cp agents/parallax.md ~/.config/opencode/agents/
179
- cp -r skills/parallax ~/.config/opencode/skills/
180
- cp -r skills/parallax-plan ~/.config/opencode/skills/
181
- cp -r skills/parallax-debug ~/.config/opencode/skills/
182
- cd ~/.config/opencode && npm init -y && npm install @opencode-ai/plugin
183
- ```
184
-
185
- ---
186
-
187
- ## Project Status
188
-
189
- **v0.3.0** -- Trace Protocol + Protocol Intelligence + Analytics
190
-
191
- | Phase | Status | What |
192
- |---|---|---|
193
- | 1: Trace Artifact | Complete | PR comments, inline viewer, CI gate, pre-commit |
194
- | 2: Protocol Intelligence | Complete | State persistence, retrospective, multi-agent sharing, shell env, design doc |
195
- | 3: Developer Experience | Partial | Config system, design doc gate. Adaptive protocol deferred |
196
- | 4: TUI Integration | Planned | Sidebar protocol status widget |
197
- | 5: Analytics | Complete | Weekly reports, trace comparison, compliance reports |
198
-
199
- See [ROADMAP.md](ROADMAP.md) for the full strategic plan.
200
-
201
- ---
202
-
203
- ## Commands
204
-
205
- ### Plugin Tools (AI calls in OpenCode chat)
206
-
207
- ```
208
- parallax_verify Run project verification
209
- parallax_analyze {topic} Multi-perspective analysis
210
- parallax_checkin {step} Protocol step (ambiguity/invariants/gate/design/commit/summary)
211
- parallax_plan / _build / _debug Mode switching
212
- parallax_trace_export Export session trace to JSON
213
- parallax_trace_pr_comment Generate trace as PR-ready markdown
214
- parallax_trace_view Show reasoning trace inline in chat
215
- ```
216
-
217
- ### CLI (for CI and analytics)
218
-
219
- ```
220
- parallax init Create .parallax/ directory
221
- parallax trace list List all traces
222
- parallax trace show <id> Show trace details
223
- parallax trace score <id> Show coherence score breakdown
224
- parallax trace export <id> Export trace as pretty JSON
225
- parallax trace trend Show score trend over time
226
- parallax trace report --week Weekly analytics report
227
- parallax trace compare <a> <b> Side-by-side trace comparison
228
- parallax trace compliance <id> Protocol compliance report
229
- parallax gate --min-score <n> CI coherence gate
230
- parallax pre-commit Git pre-commit hook wrapper
231
- ```
232
-
233
- ---
234
-
235
- ## Files
236
-
237
- ```
238
- parallax_plugin/
239
- agents/parallax.md # Primary agent definition
240
- src/plugin.ts # Canonical plugin (TypeScript, 900+ lines)
241
- src/types.ts # Shared type definitions
242
- src/detect.ts # Project detection
243
- src/trace.ts # Trace recording and export
244
- src/score.ts # Coherence score + analytics
245
- src/cli.ts # CLI entry point (11 commands)
246
- src/tests/ # 30 tests across 5 files
247
- dist/ # Compiled output
248
- dist-standalone/ # Standalone bundled plugin (34KB)
249
- skills/ # Protocol skills
250
- scripts/ # Install and publish scripts
251
- .parallax/ # Runtime state + traces + scores
252
- ROADMAP.md # Strategic roadmap
253
- CHANGELOG.md # Version history
254
- ```
255
-
256
- ---
257
-
258
- ## License
259
-
260
- MIT (c) [@Master0fFate](https://github.com/Master0fFate)
1
+ [![Parallax Banner](https://capsule-render.vercel.app/api?type=waving&height=200&color=6c63ff&desc=PARALLAX%20ENGINE&descAlignY=65&fontColor=ffffff&section=header&reversal=true&textBg=false&animation=fadeIn)](https://github.com/Master0fFate/parallax-opencode)
2
+
3
+ # PARALLAX ENGINE
4
+
5
+ **The first AI coding assistant that shows its work.**
6
+
7
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg)](LICENSE)
8
+ [![OpenCode](https://img.shields.io/badge/OpenCode-plugin-6c63ff)](https://opencode.ai)
9
+ [![npm](https://img.shields.io/npm/v/parallax-opencode)](https://www.npmjs.com/package/parallax-opencode)
10
+ [![Tests](https://img.shields.io/badge/tests-30%20passing-brightgreen)]()
11
+ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen)]()
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: currentSessionId,
106
- sessionStart: getTrace(sessionId()).session.startedAt,
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
- writeFileSync(STATE_FILE, JSON.stringify(state, null, 2), "utf8");
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: currentSessionId,
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
- const p = getProtocol();
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: currentSessionId,
297
- sessionStart: getTrace(sessionId()).session.startedAt,
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
- writeFileSync2(STATE_FILE, JSON.stringify(state, null, 2), "utf8");
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: currentSessionId,
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.8",
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 {};
@@ -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 {};
@@ -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
- });