bigpowers 2.88.9 → 2.88.11
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/.gemini/extensions/bigpowers/gemini-extension.json +1 -1
- package/.pi/package.json +1 -1
- package/CHANGELOG.md +22 -0
- package/extensions/omp-hooks.ts +64 -9
- package/package.json +1 -1
- package/scripts/lib/__pycache__/link_utils.cpython-314.pyc +0 -0
- package/scripts/lib/__pycache__/srp_engine_links.cpython-314.pyc +0 -0
- package/scripts/lib/adapter-render-all.sh +74 -0
- package/scripts/lib/install-helpers.js +59 -4
- package/scripts/lib/srp-engine.py +66 -8
- package/scripts/regenerate-lockfile.sh +2 -1
- package/scripts/test-install-helpers.js +50 -8
- package/skills-lock.json +81 -81
- package/tests/test_srp_engine.py +150 -0
- package/allure-results/categories.json +0 -23
- package/allure-results/executor.json +0 -6
- package/allure-results/junit-results.xml +0 -2743
- package/scripts/lib/__pycache__/link_utils.cpython-312.pyc +0 -0
- package/scripts/lib/__pycache__/srp_engine_links.cpython-312.pyc +0 -0
package/tests/test_srp_engine.py
CHANGED
|
@@ -6,7 +6,9 @@ Run: python3 -m pytest tests/test_srp_engine.py -v
|
|
|
6
6
|
"""
|
|
7
7
|
import os
|
|
8
8
|
import sys
|
|
9
|
+
import tempfile
|
|
9
10
|
import unittest
|
|
11
|
+
from unittest import mock
|
|
10
12
|
|
|
11
13
|
# Make scripts/lib importable without installation.
|
|
12
14
|
_REPO_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
@@ -144,5 +146,153 @@ class TestRewriteLinksForPi(unittest.TestCase):
|
|
|
144
146
|
self.assertNotIn("](REFERENCE.md)", result) # real link rewritten
|
|
145
147
|
|
|
146
148
|
|
|
149
|
+
class TestResolveBash(unittest.TestCase):
|
|
150
|
+
"""Regression: dispatch must not make subprocess resolve the bare name
|
|
151
|
+
'bash'. On Windows the Win32 CreateProcess search order finds the WSL
|
|
152
|
+
stub in System32 before Git Bash on PATH, so setup aborted with
|
|
153
|
+
'Windows Subsystem for Linux has no installed distributions'."""
|
|
154
|
+
|
|
155
|
+
def test_env_override_wins(self):
|
|
156
|
+
with mock.patch.dict(os.environ, {"BIGPOWERS_BASH": "/custom/bash"}):
|
|
157
|
+
self.assertEqual(_srp.resolve_bash(), "/custom/bash")
|
|
158
|
+
|
|
159
|
+
def test_uses_path_resolved_bash(self):
|
|
160
|
+
env = {k: v for k, v in os.environ.items() if k != "BIGPOWERS_BASH"}
|
|
161
|
+
with mock.patch.dict(os.environ, env, clear=True):
|
|
162
|
+
with mock.patch.object(_srp.shutil, "which", return_value="/usr/bin/bash"):
|
|
163
|
+
self.assertEqual(_srp.resolve_bash(), "/usr/bin/bash")
|
|
164
|
+
|
|
165
|
+
def test_falls_back_to_bare_bash(self):
|
|
166
|
+
env = {k: v for k, v in os.environ.items() if k != "BIGPOWERS_BASH"}
|
|
167
|
+
with mock.patch.dict(os.environ, env, clear=True):
|
|
168
|
+
with mock.patch.object(_srp.shutil, "which", return_value=None):
|
|
169
|
+
self.assertEqual(_srp.resolve_bash(), "bash")
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
class TestDispatchUsesResolvedBash(unittest.TestCase):
|
|
173
|
+
"""The adapter subprocess must receive the PATH-resolved bash path, not
|
|
174
|
+
the bare name that Windows resolves to the WSL stub."""
|
|
175
|
+
|
|
176
|
+
def setUp(self):
|
|
177
|
+
self._tmp = tempfile.TemporaryDirectory()
|
|
178
|
+
self.repo_root = self._tmp.name
|
|
179
|
+
adapter_dir = os.path.join(self.repo_root, "scripts", "adapters")
|
|
180
|
+
os.makedirs(adapter_dir)
|
|
181
|
+
self.adapter = os.path.join(adapter_dir, "fake.sh")
|
|
182
|
+
with open(self.adapter, "w", encoding="utf-8") as f:
|
|
183
|
+
f.write("#!/usr/bin/env bash\n")
|
|
184
|
+
|
|
185
|
+
def tearDown(self):
|
|
186
|
+
self._tmp.cleanup()
|
|
187
|
+
|
|
188
|
+
def test_dispatch_passes_resolved_bash_to_popen(self):
|
|
189
|
+
captured = {}
|
|
190
|
+
|
|
191
|
+
class _FakeProc:
|
|
192
|
+
returncode = 0
|
|
193
|
+
|
|
194
|
+
def communicate(self, input=None):
|
|
195
|
+
return ("", "")
|
|
196
|
+
|
|
197
|
+
def _fake_popen(cmd, **kwargs):
|
|
198
|
+
captured["cmd"] = cmd
|
|
199
|
+
return _FakeProc()
|
|
200
|
+
|
|
201
|
+
sentinel = os.path.join(self.repo_root, "git-bash")
|
|
202
|
+
with mock.patch.object(_srp, "resolve_bash", return_value=sentinel):
|
|
203
|
+
with mock.patch.object(_srp.subprocess, "Popen", side_effect=_fake_popen):
|
|
204
|
+
_srp.dispatch_to_adapter({"name": "x"}, "fake", self.repo_root)
|
|
205
|
+
|
|
206
|
+
self.assertEqual(captured["cmd"][0], sentinel)
|
|
207
|
+
self.assertEqual(captured["cmd"][1], self.adapter)
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
class TestWriteIrStream(unittest.TestCase):
|
|
211
|
+
"""#138: the whole run's IR is serialised once."""
|
|
212
|
+
|
|
213
|
+
def test_writes_nul_delimited_fields_in_order(self):
|
|
214
|
+
records = [{
|
|
215
|
+
'name': 'demo',
|
|
216
|
+
'description': 'a "demo"',
|
|
217
|
+
'model': 'gpt',
|
|
218
|
+
'body': 'line1\nline2',
|
|
219
|
+
}]
|
|
220
|
+
with mock.patch.object(_srp, 'rewrite_links_for_pi', return_value='PI-BODY'):
|
|
221
|
+
path = _srp._write_ir_stream(records)
|
|
222
|
+
try:
|
|
223
|
+
with open(path, 'rb') as f:
|
|
224
|
+
data = f.read()
|
|
225
|
+
finally:
|
|
226
|
+
os.unlink(path)
|
|
227
|
+
self.assertEqual(data, b'demo\x00a "demo"\x00gpt\x00line1\nline2\x00PI-BODY\x00')
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
class TestRenderAll(unittest.TestCase):
|
|
231
|
+
"""#138: one bash process per adapter, not one per skill×adapter."""
|
|
232
|
+
|
|
233
|
+
@staticmethod
|
|
234
|
+
def _records(n):
|
|
235
|
+
return [{'name': f's{i}', 'description': 'd', 'model': '', 'body': 'b'} for i in range(n)]
|
|
236
|
+
|
|
237
|
+
def test_one_process_per_adapter_not_per_skill(self):
|
|
238
|
+
records = self._records(5)
|
|
239
|
+
with mock.patch.object(_srp, '_write_ir_stream', return_value='/tmp/ir.bin') as write, \
|
|
240
|
+
mock.patch.object(_srp.os, 'unlink') as unlink, \
|
|
241
|
+
mock.patch.object(_srp.subprocess, 'run') as run:
|
|
242
|
+
run.return_value = mock.Mock(returncode=0)
|
|
243
|
+
_srp.render_all(records, ['cursor', 'pi'], _REPO_ROOT)
|
|
244
|
+
self.assertEqual(write.call_count, 1)
|
|
245
|
+
self.assertEqual(run.call_count, 2)
|
|
246
|
+
unlink.assert_called_once_with('/tmp/ir.bin')
|
|
247
|
+
|
|
248
|
+
def test_nonzero_adapter_exits_nonzero(self):
|
|
249
|
+
with mock.patch.object(_srp, '_write_ir_stream', return_value='/tmp/ir.bin'), \
|
|
250
|
+
mock.patch.object(_srp.os, 'unlink'), \
|
|
251
|
+
mock.patch.object(_srp.subprocess, 'run') as run:
|
|
252
|
+
run.return_value = mock.Mock(returncode=3)
|
|
253
|
+
with self.assertRaises(SystemExit):
|
|
254
|
+
_srp.render_all(self._records(1), ['cursor'], _REPO_ROOT)
|
|
255
|
+
|
|
256
|
+
def test_empty_records_does_not_spawn(self):
|
|
257
|
+
with mock.patch.object(_srp.subprocess, 'run') as run:
|
|
258
|
+
_srp.render_all([], ['cursor'], _REPO_ROOT)
|
|
259
|
+
run.assert_not_called()
|
|
260
|
+
|
|
261
|
+
def test_missing_adapter_exits_without_spawning(self):
|
|
262
|
+
with mock.patch.object(_srp, '_write_ir_stream', return_value='/tmp/ir.bin'), \
|
|
263
|
+
mock.patch.object(_srp.os, 'unlink'), \
|
|
264
|
+
mock.patch.object(_srp.subprocess, 'run') as run:
|
|
265
|
+
with self.assertRaises(SystemExit):
|
|
266
|
+
_srp.render_all(self._records(1), ['not-a-real-adapter'], _REPO_ROOT)
|
|
267
|
+
run.assert_not_called()
|
|
268
|
+
|
|
269
|
+
|
|
270
|
+
class TestAdapterRenderAllDriver(unittest.TestCase):
|
|
271
|
+
"""#138: the bash driver sources one adapter and renders the stream."""
|
|
272
|
+
|
|
273
|
+
def test_driver_renders_stream_in_a_single_process(self):
|
|
274
|
+
import shutil
|
|
275
|
+
import subprocess
|
|
276
|
+
|
|
277
|
+
driver = os.path.join(_REPO_ROOT, 'scripts', 'lib', 'adapter-render-all.sh')
|
|
278
|
+
adapter = os.path.join(_REPO_ROOT, 'scripts', 'adapters', 'cursor.sh')
|
|
279
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
280
|
+
stream = os.path.join(tmp, 'ir.bin')
|
|
281
|
+
with open(stream, 'wb') as f:
|
|
282
|
+
for field in ('demo-skill', 'desc', '', 'body text', 'body text'):
|
|
283
|
+
f.write(field.encode())
|
|
284
|
+
f.write(b'\x00')
|
|
285
|
+
env = dict(os.environ)
|
|
286
|
+
env['CURSOR_RULES'] = tmp
|
|
287
|
+
proc = subprocess.run(
|
|
288
|
+
[shutil.which('bash') or 'bash', driver, adapter, stream],
|
|
289
|
+
env=env,
|
|
290
|
+
stdout=subprocess.PIPE,
|
|
291
|
+
stderr=subprocess.PIPE,
|
|
292
|
+
)
|
|
293
|
+
self.assertEqual(proc.returncode, 0, proc.stderr)
|
|
294
|
+
self.assertTrue(os.path.isfile(os.path.join(tmp, 'demo-skill.mdc')))
|
|
295
|
+
|
|
296
|
+
|
|
147
297
|
if __name__ == "__main__":
|
|
148
298
|
unittest.main()
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
[
|
|
2
|
-
{
|
|
3
|
-
"name": "P0 Risk",
|
|
4
|
-
"matchedStatuses": [
|
|
5
|
-
"failed"
|
|
6
|
-
],
|
|
7
|
-
"messageRegex": ".*risk.*P0.*"
|
|
8
|
-
},
|
|
9
|
-
{
|
|
10
|
-
"name": "Security Review",
|
|
11
|
-
"matchedStatuses": [
|
|
12
|
-
"failed"
|
|
13
|
-
],
|
|
14
|
-
"messageRegex": ".*security.*(?:medium|high).*"
|
|
15
|
-
},
|
|
16
|
-
{
|
|
17
|
-
"name": "Open Bugs",
|
|
18
|
-
"matchedStatuses": [
|
|
19
|
-
"failed"
|
|
20
|
-
],
|
|
21
|
-
"messageRegex": ".*Bug.*"
|
|
22
|
-
}
|
|
23
|
-
]
|