Commit b27a9bc
authored
refactor(plugin): independent per-load registration with marker-based bootstrap idempotency (#352)
* refactor(plugin): independent per-load registration with marker-based bootstrap idempotency
Each call to SystematicPlugin now runs initializePlugin independently and
returns its own hooks surface. With multiple OpenCode plugin sources
configured (project + user config), each source gets its own systematic_skill
tool, its own config handler, and its own chat.system.transform closure.
hasLoggedInit moves into per-init closure state, so the process emits N init
log lines for N registrations — honest signal that init ran N times.
Bootstrap content idempotency is now enforced at injection time, not init
time. applyBootstrapContent walks output.system for any prior
<SYSTEMATIC_WORKFLOWS>...</SYSTEMATIC_WORKFLOWS> block (non-greedy regex) and
replaces it in-place; missing-block falls through to append. Under OpenCode's
verified FIFO hook iteration, the last transform to run owns the final block
— most-recently-registered plugin wins, which matches the project-after-user
load order developers expect.
This reverts the plugInOnce singleton from PR #335, which turned out to be
over-correction. OpenCode registers tools per-source even with a shared hooks
reference, so the singleton's init-dedup had no visible effect on the TUI
tool catalog — what it did do in dev setups was silently collapse all loads
onto whichever ran first, shadowing later sources.
Also removes _resetPluginSingleton from the integration test setup; the
singleton itself is deleted in a follow-up commit.
12 new behavior tests cover marker-replacement in every slot, non-greedy
boundary, multi-block edge cases, per-invocation distinct references, and
the cross-registration integration scenario.
* refactor(plugin): delete plugin-singleton module after factory revert
The plugInOnce abstraction and its test file are no longer reachable —
src/index.ts stopped importing them in the prior commit, and the consumer
test files (plugin.test.ts and opencode.test.ts) dropped the
_resetPluginSingleton reset calls.
Deletes 227 lines of now-dead infrastructure: the singleton helper, its
five test cases, and the module-level exports. The architectural rationale
lives in docs/brainstorms/2026-05-10-multi-load-plugin-registration-requirements.md
and the implementation plan at docs/plans/2026-05-10-002-refactor-multi-load-plugin-registration-plan.md.
* docs: sync AGENTS hierarchy and PR #335 solution after singleton removal
Updates module count in AGENTS.md and src/lib/AGENTS.md from 16/14 to 15
(post-deletion of plugin-singleton.ts). Appends a 2026-05-10 follow-up
section to the PR #335 solution doc noting the singleton was removed and
documenting marker-based idempotency as the current correctness contract.
Also commits the implementation plan with all four units marked complete.
The plan was untracked locally during execution per project convention;
landing it here gives the architectural inversion a visible paper trail.
* fix(plugin): replace bootstrap marker regex with linear scan to close ReDoS
The non-greedy regex /<SYSTEMATIC_WORKFLOWS>[\s\S]*?<\/SYSTEMATIC_WORKFLOWS>/
was flagged by CodeQL as polynomial-time on uncontrolled input — when the
opening tag repeats and the closing tag is absent, the engine backtracks
through every prefix. With per-load registration now letting any plugin
source contribute system prompt fragments, this regex sees content the
plugin itself didn't author.
Replaces the regex with a small indexOf/slice helper. Fixed literal
delimiters never needed regex; the linear scan is provably immune to ReDoS
and unchanged in behavior for the existing seven marker-replacement tests.
Adds a regression test that runs the helper against 10000 repeated opening
markers with no closing tag and asserts completion in well under 1s.
Closes CodeQL alerts #42 and #43.1 parent 39c8ecf commit b27a9bc
9 files changed
Lines changed: 560 additions & 258 deletions
File tree
- docs
- plans
- solutions/integration-issues
- src
- lib
- tests
- integration
- unit
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
46 | 46 | | |
47 | 47 | | |
48 | 48 | | |
49 | | - | |
| 49 | + | |
50 | 50 | | |
51 | 51 | | |
52 | 52 | | |
| |||
Lines changed: 250 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 6 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
142 | 142 | | |
143 | 143 | | |
144 | 144 | | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
8 | 8 | | |
9 | 9 | | |
10 | 10 | | |
11 | | - | |
12 | 11 | | |
13 | 12 | | |
14 | 13 | | |
| |||
18 | 17 | | |
19 | 18 | | |
20 | 19 | | |
21 | | - | |
22 | 20 | | |
23 | | - | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
24 | 38 | | |
25 | 39 | | |
26 | 40 | | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
27 | 50 | | |
28 | 51 | | |
29 | 52 | | |
| |||
42 | 65 | | |
43 | 66 | | |
44 | 67 | | |
45 | | - | |
46 | | - | |
47 | | - | |
48 | | - | |
49 | | - | |
50 | | - | |
51 | | - | |
52 | | - | |
53 | 68 | | |
| 69 | + | |
54 | 70 | | |
55 | 71 | | |
56 | 72 | | |
| |||
129 | 145 | | |
130 | 146 | | |
131 | 147 | | |
132 | | - | |
133 | | - | |
134 | | - | |
135 | | - | |
136 | | - | |
137 | | - | |
138 | | - | |
| 148 | + | |
139 | 149 | | |
140 | 150 | | |
141 | 151 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | | - | |
| 3 | + | |
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| |||
This file was deleted.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
7 | | - | |
8 | 7 | | |
9 | 8 | | |
10 | 9 | | |
| |||
109 | 108 | | |
110 | 109 | | |
111 | 110 | | |
112 | | - | |
113 | 111 | | |
114 | 112 | | |
115 | 113 | | |
| |||
126 | 124 | | |
127 | 125 | | |
128 | 126 | | |
129 | | - | |
130 | 127 | | |
131 | 128 | | |
132 | 129 | | |
| |||
This file was deleted.
0 commit comments