pactkit-opencode 3.0.0__tar.gz → 3.0.1__tar.gz
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.
- {pactkit_opencode-3.0.0 → pactkit_opencode-3.0.1}/PKG-INFO +2 -2
- {pactkit_opencode-3.0.0 → pactkit_opencode-3.0.1}/pyproject.toml +2 -2
- {pactkit_opencode-3.0.0 → pactkit_opencode-3.0.1}/src/pactkit_opencode/deployer.py +42 -2
- {pactkit_opencode-3.0.0 → pactkit_opencode-3.0.1}/src/pactkit_opencode.egg-info/PKG-INFO +2 -2
- {pactkit_opencode-3.0.0 → pactkit_opencode-3.0.1}/src/pactkit_opencode.egg-info/SOURCES.txt +1 -0
- pactkit_opencode-3.0.1/src/pactkit_opencode.egg-info/requires.txt +1 -0
- pactkit_opencode-3.0.1/tests/test_gate_plugin_deploy.py +69 -0
- pactkit_opencode-3.0.0/src/pactkit_opencode.egg-info/requires.txt +0 -1
- {pactkit_opencode-3.0.0 → pactkit_opencode-3.0.1}/README.md +0 -0
- {pactkit_opencode-3.0.0 → pactkit_opencode-3.0.1}/setup.cfg +0 -0
- {pactkit_opencode-3.0.0 → pactkit_opencode-3.0.1}/src/pactkit_opencode/__init__.py +0 -0
- {pactkit_opencode-3.0.0 → pactkit_opencode-3.0.1}/src/pactkit_opencode.egg-info/dependency_links.txt +0 -0
- {pactkit_opencode-3.0.0 → pactkit_opencode-3.0.1}/src/pactkit_opencode.egg-info/entry_points.txt +0 -0
- {pactkit_opencode-3.0.0 → pactkit_opencode-3.0.1}/src/pactkit_opencode.egg-info/top_level.txt +0 -0
- {pactkit_opencode-3.0.0 → pactkit_opencode-3.0.1}/tests/test_a1_config_recovery.py +0 -0
- {pactkit_opencode-3.0.0 → pactkit_opencode-3.0.1}/tests/test_a2_mcp_retired.py +0 -0
- {pactkit_opencode-3.0.0 → pactkit_opencode-3.0.1}/tests/test_bug035_opencode_dual_layer.py +0 -0
- {pactkit_opencode-3.0.0 → pactkit_opencode-3.0.1}/tests/test_deploy_opencode_parity.py +0 -0
- {pactkit_opencode-3.0.0 → pactkit_opencode-3.0.1}/tests/test_host_body_snapshot.py +0 -0
- {pactkit_opencode-3.0.0 → pactkit_opencode-3.0.1}/tests/test_story069_opencode_format.py +0 -0
- {pactkit_opencode-3.0.0 → pactkit_opencode-3.0.1}/tests/test_story070_opencode_compliance.py +0 -0
- {pactkit_opencode-3.0.0 → pactkit_opencode-3.0.1}/tests/test_story071_opencode_config_parity.py +0 -0
- {pactkit_opencode-3.0.0 → pactkit_opencode-3.0.1}/tests/test_story073_command_model_routing.py +0 -0
- {pactkit_opencode-3.0.0 → pactkit_opencode-3.0.1}/tests/test_story_work_units.py +0 -0
|
@@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pactkit-opencode"
|
|
7
|
-
version = "3.0.
|
|
7
|
+
version = "3.0.1"
|
|
8
8
|
description = "PactKit adapter for OpenCode IDE"
|
|
9
9
|
requires-python = ">=3.10"
|
|
10
10
|
license = "MIT"
|
|
11
11
|
dependencies = [
|
|
12
|
-
"pactkit>=3.0.
|
|
12
|
+
"pactkit>=3.0.1,<3.1.0",
|
|
13
13
|
]
|
|
14
14
|
|
|
15
15
|
[project.entry-points."pactkit.deployers"]
|
|
@@ -226,6 +226,7 @@ class OpenCodeDeployer(DeployerBase):
|
|
|
226
226
|
|
|
227
227
|
n_agents = _deploy_agents(agents_dir, enabled_agents, profile=self.profile)
|
|
228
228
|
|
|
229
|
+
|
|
229
230
|
ci_config = cfg.get("ci", {})
|
|
230
231
|
ci_provider = ci_config.get("provider", "none") if isinstance(ci_config, dict) else "none"
|
|
231
232
|
project_root = Path.cwd()
|
|
@@ -236,9 +237,20 @@ class OpenCodeDeployer(DeployerBase):
|
|
|
236
237
|
|
|
237
238
|
write_deploy_manifest(opencode_root, "opencode", cfg)
|
|
238
239
|
|
|
240
|
+
# STORY-slim-20260918dda35a04d01d: the pre-tool gate channel. Deployed
|
|
241
|
+
# GLOBALLY (like every other OpenCode asset here), so the plugin sees
|
|
242
|
+
# every project on the machine — it activates per project via the
|
|
243
|
+
# markers baked into its source, and is inert elsewhere.
|
|
244
|
+
#
|
|
245
|
+
# MUST run AFTER write_deploy_manifest: that function REBUILDS the
|
|
246
|
+
# manifest's `files` map from the declared components, and `plugins/`
|
|
247
|
+
# is not one — recording earlier gets silently overwritten, and then
|
|
248
|
+
# `pactkit uninstall` leaves the plugin behind as residue (R4).
|
|
249
|
+
n_plugins = self._deploy_gate_plugin(opencode_root)
|
|
250
|
+
|
|
239
251
|
print(
|
|
240
252
|
f"\n✅ OpenCode: {n_agents} Agents, {n_skills} Skills, "
|
|
241
|
-
f"{n_commands} Commands → {opencode_root}"
|
|
253
|
+
f"{n_commands} Commands, {n_plugins} Gate plugin → {opencode_root}"
|
|
242
254
|
)
|
|
243
255
|
|
|
244
256
|
if target is None:
|
|
@@ -246,6 +258,27 @@ class OpenCodeDeployer(DeployerBase):
|
|
|
246
258
|
|
|
247
259
|
# --- OpenCode-specific helpers ---
|
|
248
260
|
|
|
261
|
+
@staticmethod
|
|
262
|
+
def _deploy_gate_plugin(opencode_root: Path) -> int:
|
|
263
|
+
"""Write the pre-tool gate plugin. Returns 1 when written, else 0.
|
|
264
|
+
|
|
265
|
+
The source comes from core's `opencode_plugin.render_plugin_source()` —
|
|
266
|
+
**never hand-copied**. A local copy of the field mapping would be a
|
|
267
|
+
second mapping, and a wrong mapping is a bypass (STORY R2).
|
|
268
|
+
|
|
269
|
+
Recorded in the deployment manifest, so `pactkit uninstall` removes it
|
|
270
|
+
with the rest of the owned files — no residue (R4).
|
|
271
|
+
"""
|
|
272
|
+
from pactkit.deploy_manifest import atomic_write, record_deployed_file
|
|
273
|
+
from pactkit.generators.opencode_plugin import render_plugin_source
|
|
274
|
+
|
|
275
|
+
plugins_dir = opencode_root / "plugins"
|
|
276
|
+
plugins_dir.mkdir(parents=True, exist_ok=True)
|
|
277
|
+
plugin_path = plugins_dir / "pactkit-gate.js"
|
|
278
|
+
atomic_write(plugin_path, render_plugin_source())
|
|
279
|
+
record_deployed_file(opencode_root, plugin_path.relative_to(opencode_root).as_posix())
|
|
280
|
+
return 1
|
|
281
|
+
|
|
249
282
|
@staticmethod
|
|
250
283
|
def _generate_project_agents_md():
|
|
251
284
|
"""Generate project-level AGENTS.md if it does not exist (STORY-slim-008 R4)."""
|
|
@@ -256,7 +289,14 @@ class OpenCodeDeployer(DeployerBase):
|
|
|
256
289
|
if agents_md_path.exists():
|
|
257
290
|
return
|
|
258
291
|
project_name = project_root.name
|
|
259
|
-
|
|
292
|
+
# Never `@import` a file nothing generates (STORY-slim-148 R1): project
|
|
293
|
+
# agent instructions must point at the local context entry instead.
|
|
294
|
+
content = (
|
|
295
|
+
f"# {project_name}\n\n"
|
|
296
|
+
"Read `.pactkit/context.md` at session start for project state — "
|
|
297
|
+
"optional history, never a gate.\n"
|
|
298
|
+
"output MUST use Chinese\n"
|
|
299
|
+
)
|
|
260
300
|
atomic_write(agents_md_path, content)
|
|
261
301
|
|
|
262
302
|
@staticmethod
|
|
@@ -12,6 +12,7 @@ tests/test_a1_config_recovery.py
|
|
|
12
12
|
tests/test_a2_mcp_retired.py
|
|
13
13
|
tests/test_bug035_opencode_dual_layer.py
|
|
14
14
|
tests/test_deploy_opencode_parity.py
|
|
15
|
+
tests/test_gate_plugin_deploy.py
|
|
15
16
|
tests/test_host_body_snapshot.py
|
|
16
17
|
tests/test_story069_opencode_format.py
|
|
17
18
|
tests/test_story070_opencode_compliance.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pactkit<3.1.0,>=3.0.1
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""STORY-slim-20260918dda35a04d01d — pre-tool 门禁插件的部署与移除.
|
|
2
|
+
|
|
3
|
+
OpenCode 的插件面 `tool.execute.before` 抛异常即可否决调用(实测)。插件部署在
|
|
4
|
+
**全局** `~/.config/opencode/plugins/`,因此会经过这台机器上的每个 opencode
|
|
5
|
+
项目——它靠源码里烘焙的**激活标记**按项目生效,在别处完全不动。
|
|
6
|
+
|
|
7
|
+
本文件钉三件事(R4:可核验、可移除):
|
|
8
|
+
|
|
9
|
+
1. 部署确实写出插件,且**与 core 的生成器逐字节一致**——adapter 侧手抄一份
|
|
10
|
+
映射就是第二套映射,映射错了就是绕过。
|
|
11
|
+
2. manifest 记录了它——否则 `pactkit uninstall` 会把它留成残骸。
|
|
12
|
+
3. 移除确实删掉它。
|
|
13
|
+
"""
|
|
14
|
+
from __future__ import annotations
|
|
15
|
+
|
|
16
|
+
import json
|
|
17
|
+
|
|
18
|
+
import pytest
|
|
19
|
+
|
|
20
|
+
from pactkit_opencode.deployer import OpenCodeDeployer
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
@pytest.fixture()
|
|
24
|
+
def deployed(tmp_path):
|
|
25
|
+
target = tmp_path / "opencode"
|
|
26
|
+
OpenCodeDeployer().deploy(config={}, target=str(target))
|
|
27
|
+
return target
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class TestPluginIsDeployed:
|
|
31
|
+
def test_plugin_file_is_written(self, deployed):
|
|
32
|
+
assert (deployed / "plugins" / "pactkit-gate.js").is_file()
|
|
33
|
+
|
|
34
|
+
def test_plugin_matches_core_generator_byte_for_byte(self, deployed):
|
|
35
|
+
"""adapter 侧**不得**有第二份映射——内容必须来自 core 的生成器。"""
|
|
36
|
+
from pactkit.generators.opencode_plugin import render_plugin_source
|
|
37
|
+
|
|
38
|
+
written = (deployed / "plugins" / "pactkit-gate.js").read_text(encoding="utf-8")
|
|
39
|
+
assert written == render_plugin_source(), (
|
|
40
|
+
"部署的插件与 core 生成器不一致——adapter 侧出现了第二份映射"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
def test_plugin_carries_the_activation_markers(self, deployed):
|
|
44
|
+
"""全局部署的关键:插件必须能按项目判断自己该不该生效。"""
|
|
45
|
+
from pactkit.profiles import PACTKIT_YAML_CANDIDATES
|
|
46
|
+
|
|
47
|
+
src = (deployed / "plugins" / "pactkit-gate.js").read_text(encoding="utf-8")
|
|
48
|
+
for marker in PACTKIT_YAML_CANDIDATES:
|
|
49
|
+
assert marker in src, f"激活标记 {marker!r} 缺失——该生效的项目不会生效"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class TestPluginIsRemovable:
|
|
53
|
+
def test_manifest_records_the_plugin(self, deployed):
|
|
54
|
+
"""**必须在 write_deploy_manifest 之后记录**:那个函数会用声明的组件
|
|
55
|
+
重建 `files`,`plugins/` 不是组件——记录早了会被静默覆盖,uninstall
|
|
56
|
+
就会把它留下。"""
|
|
57
|
+
manifest = json.loads((deployed / ".pactkit-deployed.json").read_text(encoding="utf-8"))
|
|
58
|
+
assert "plugins/pactkit-gate.js" in manifest["files"], (
|
|
59
|
+
"manifest 未记录插件——pactkit uninstall 会把它留成残骸"
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
def test_uninstall_removes_the_plugin(self, deployed):
|
|
63
|
+
from pactkit.uninstall import run_uninstall
|
|
64
|
+
|
|
65
|
+
run_uninstall(deployed)
|
|
66
|
+
|
|
67
|
+
assert not (deployed / "plugins" / "pactkit-gate.js").exists(), (
|
|
68
|
+
"卸载后插件仍在——R4 要求可移除、不留残骸"
|
|
69
|
+
)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
pactkit<3.1.0,>=3.0.0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pactkit_opencode-3.0.0 → pactkit_opencode-3.0.1}/src/pactkit_opencode.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
{pactkit_opencode-3.0.0 → pactkit_opencode-3.0.1}/src/pactkit_opencode.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{pactkit_opencode-3.0.0 → pactkit_opencode-3.0.1}/src/pactkit_opencode.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pactkit_opencode-3.0.0 → pactkit_opencode-3.0.1}/tests/test_story070_opencode_compliance.py
RENAMED
|
File without changes
|
{pactkit_opencode-3.0.0 → pactkit_opencode-3.0.1}/tests/test_story071_opencode_config_parity.py
RENAMED
|
File without changes
|
{pactkit_opencode-3.0.0 → pactkit_opencode-3.0.1}/tests/test_story073_command_model_routing.py
RENAMED
|
File without changes
|
|
File without changes
|