pactkit-opencode 2.22.0__tar.gz → 2.23.0__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-2.22.0 → pactkit_opencode-2.23.0}/PKG-INFO +2 -2
- {pactkit_opencode-2.22.0 → pactkit_opencode-2.23.0}/pyproject.toml +2 -2
- {pactkit_opencode-2.22.0 → pactkit_opencode-2.23.0}/src/pactkit_opencode/deployer.py +31 -41
- {pactkit_opencode-2.22.0 → pactkit_opencode-2.23.0}/src/pactkit_opencode.egg-info/PKG-INFO +2 -2
- {pactkit_opencode-2.22.0 → pactkit_opencode-2.23.0}/src/pactkit_opencode.egg-info/SOURCES.txt +2 -1
- pactkit_opencode-2.23.0/src/pactkit_opencode.egg-info/requires.txt +1 -0
- {pactkit_opencode-2.22.0 → pactkit_opencode-2.23.0}/tests/test_story069_opencode_format.py +31 -5
- {pactkit_opencode-2.22.0 → pactkit_opencode-2.23.0}/tests/test_story071_opencode_config_parity.py +7 -9
- pactkit_opencode-2.23.0/tests/test_story_work_units.py +42 -0
- pactkit_opencode-2.22.0/src/pactkit_opencode.egg-info/requires.txt +0 -1
- {pactkit_opencode-2.22.0 → pactkit_opencode-2.23.0}/README.md +0 -0
- {pactkit_opencode-2.22.0 → pactkit_opencode-2.23.0}/setup.cfg +0 -0
- {pactkit_opencode-2.22.0 → pactkit_opencode-2.23.0}/src/pactkit_opencode/__init__.py +0 -0
- {pactkit_opencode-2.22.0 → pactkit_opencode-2.23.0}/src/pactkit_opencode.egg-info/dependency_links.txt +0 -0
- {pactkit_opencode-2.22.0 → pactkit_opencode-2.23.0}/src/pactkit_opencode.egg-info/entry_points.txt +0 -0
- {pactkit_opencode-2.22.0 → pactkit_opencode-2.23.0}/src/pactkit_opencode.egg-info/top_level.txt +0 -0
- {pactkit_opencode-2.22.0 → pactkit_opencode-2.23.0}/tests/test_bug035_opencode_dual_layer.py +0 -0
- {pactkit_opencode-2.22.0 → pactkit_opencode-2.23.0}/tests/test_deploy_opencode_parity.py +0 -0
- {pactkit_opencode-2.22.0 → pactkit_opencode-2.23.0}/tests/test_story070_opencode_compliance.py +0 -0
- {pactkit_opencode-2.22.0 → pactkit_opencode-2.23.0}/tests/test_story073_command_model_routing.py +0 -0
|
@@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pactkit-opencode"
|
|
7
|
-
version = "2.
|
|
7
|
+
version = "2.23.0"
|
|
8
8
|
description = "PactKit adapter for OpenCode IDE"
|
|
9
9
|
requires-python = ">=3.10"
|
|
10
10
|
license = "MIT"
|
|
11
11
|
dependencies = [
|
|
12
|
-
"pactkit>=2.
|
|
12
|
+
"pactkit>=2.23.0,<2.24.0",
|
|
13
13
|
]
|
|
14
14
|
|
|
15
15
|
[project.entry-points."pactkit.deployers"]
|
|
@@ -7,11 +7,12 @@ import json
|
|
|
7
7
|
import re
|
|
8
8
|
from pathlib import Path
|
|
9
9
|
|
|
10
|
-
from pactkit import __version__
|
|
10
|
+
from pactkit import __version__
|
|
11
11
|
from pactkit.config import (
|
|
12
|
+
DEFAULT_RULE_IDS,
|
|
13
|
+
activate_pactkit_maintainer_overlay,
|
|
12
14
|
VALID_AGENTS,
|
|
13
15
|
VALID_COMMANDS,
|
|
14
|
-
VALID_RULES,
|
|
15
16
|
VALID_SKILLS,
|
|
16
17
|
auto_merge_config_file,
|
|
17
18
|
load_config,
|
|
@@ -19,9 +20,11 @@ from pactkit.config import (
|
|
|
19
20
|
from pactkit.generators.deploy_base import DeployerBase, register_deployer
|
|
20
21
|
from pactkit.generators.deployer import (
|
|
21
22
|
_cleanup_legacy,
|
|
23
|
+
_cleanup_legacy_portable_methods,
|
|
22
24
|
_deploy_agents,
|
|
23
25
|
_deploy_ci,
|
|
24
26
|
_deploy_commands,
|
|
27
|
+
_deploy_guides,
|
|
25
28
|
_deploy_rules,
|
|
26
29
|
_deploy_skills,
|
|
27
30
|
)
|
|
@@ -39,7 +42,7 @@ class OpenCodeDeployer(DeployerBase):
|
|
|
39
42
|
multiple LLM providers. This deployment mode generates OpenCode-native files:
|
|
40
43
|
- AGENTS.md (slim header — rules loaded via instructions)
|
|
41
44
|
- rules/ directory with modular rule files (STORY-071 R6)
|
|
42
|
-
- opencode.json (global config
|
|
45
|
+
- opencode.json (global config loads only rules/pactkit-runtime.md)
|
|
43
46
|
- agents/ directory
|
|
44
47
|
- commands/ directory (PDCA commands, invoked via /command-name)
|
|
45
48
|
- skills/ directory (embedded skills like pactkit-board, pactkit-visualize)
|
|
@@ -73,24 +76,32 @@ class OpenCodeDeployer(DeployerBase):
|
|
|
73
76
|
|
|
74
77
|
project_yaml = find_pactkit_yaml()
|
|
75
78
|
|
|
76
|
-
if
|
|
79
|
+
if config is not None:
|
|
80
|
+
cfg = config
|
|
81
|
+
elif project_yaml is not None:
|
|
77
82
|
auto_added = auto_merge_config_file(project_yaml)
|
|
78
83
|
for item in auto_added:
|
|
79
84
|
print(f" -> Auto-added: {item}")
|
|
80
85
|
cfg = load_config(project_yaml)
|
|
81
86
|
else:
|
|
82
87
|
cfg = {}
|
|
88
|
+
cfg = activate_pactkit_maintainer_overlay(cfg, Path.cwd())
|
|
83
89
|
|
|
84
90
|
enabled_agents = cfg.get("agents", sorted(VALID_AGENTS))
|
|
85
91
|
enabled_skills = cfg.get("skills", sorted(VALID_SKILLS))
|
|
86
92
|
enabled_commands = cfg.get("commands", sorted(VALID_COMMANDS))
|
|
87
|
-
enabled_rules = cfg.get("rules", sorted(
|
|
93
|
+
enabled_rules = cfg.get("rules", sorted(DEFAULT_RULE_IDS))
|
|
88
94
|
|
|
89
95
|
# Apply explicit format exclusions (currently none).
|
|
90
96
|
enabled_commands = [c for c in enabled_commands if c not in OPENCODE_EXCLUDED_COMMANDS]
|
|
91
97
|
|
|
92
98
|
# Deploy embedded skills (pactkit-board, pactkit-visualize, etc.)
|
|
93
|
-
n_skills = _deploy_skills(
|
|
99
|
+
n_skills = _deploy_skills(
|
|
100
|
+
skills_dir,
|
|
101
|
+
enabled_skills,
|
|
102
|
+
profile=self.profile,
|
|
103
|
+
)
|
|
104
|
+
_cleanup_legacy_portable_methods(skills_dir, self.profile)
|
|
94
105
|
_cleanup_legacy(skills_dir)
|
|
95
106
|
# Clean up legacy command skills from skills/ directory (migrated to commands/)
|
|
96
107
|
self._cleanup_command_skills(skills_dir)
|
|
@@ -113,6 +124,7 @@ class OpenCodeDeployer(DeployerBase):
|
|
|
113
124
|
atomic_write(cmd_md, replaced)
|
|
114
125
|
|
|
115
126
|
_deploy_rules(opencode_root, enabled_rules, profile=self.profile)
|
|
127
|
+
_deploy_guides(opencode_root, profile=self.profile)
|
|
116
128
|
self._deploy_agents_md_inline(opencode_root)
|
|
117
129
|
|
|
118
130
|
providers = self._load_opencode_providers(opencode_root)
|
|
@@ -167,45 +179,23 @@ class OpenCodeDeployer(DeployerBase):
|
|
|
167
179
|
|
|
168
180
|
@staticmethod
|
|
169
181
|
def _deploy_agents_md_inline(opencode_root):
|
|
170
|
-
"""Generate
|
|
171
|
-
|
|
172
|
-
ondemand_descriptions = {
|
|
173
|
-
"08-architecture-principles.md": "Architecture decisions, SOLID/DRY patterns",
|
|
174
|
-
"04-routing-table.md": "Agent/command routing table",
|
|
175
|
-
"06-mcp-integration.md": "MCP server integration guide",
|
|
176
|
-
"05-workflow-conventions.md": "PDCA workflow conventions",
|
|
177
|
-
"07-shared-protocols.md": "PDCA shared protocols (test mapping, visualize, context.md format)",
|
|
178
|
-
"03-file-atlas.md": "File atlas (project file locations)",
|
|
179
|
-
}
|
|
180
|
-
for filename in sorted(prompts.RULES_ONDEMAND_FILES.values()):
|
|
181
|
-
desc = ondemand_descriptions.get(filename, filename)
|
|
182
|
-
ref_lines.append(f"- {desc}: @rules/{filename}")
|
|
182
|
+
"""Generate a small runtime index without globally loading phase rules."""
|
|
183
|
+
from pactkit.prompts.rules import RULE_DEFINITIONS
|
|
183
184
|
|
|
184
|
-
|
|
185
|
-
f.replace(".md", "") for f in sorted(prompts.RULES_CORE_FILES.values())
|
|
186
|
-
)
|
|
185
|
+
runtime = RULE_DEFINITIONS["runtime"].filename
|
|
187
186
|
|
|
188
187
|
lines = [
|
|
189
|
-
f"# PactKit
|
|
190
|
-
"",
|
|
191
|
-
f"Core rules ({core_names}) are always loaded via `instructions`.",
|
|
192
|
-
"",
|
|
193
|
-
"## On-Demand Rules",
|
|
194
|
-
"",
|
|
195
|
-
"CRITICAL: When you encounter a file reference below (e.g., @rules/xxx.md), "
|
|
196
|
-
"use your Read tool to load it on a need-to-know basis. "
|
|
197
|
-
"Do NOT preemptively load all references — use lazy loading based on actual need.",
|
|
188
|
+
f"# PactKit Runtime Contract (v{__version__})",
|
|
198
189
|
"",
|
|
199
|
-
|
|
190
|
+
f"`rules/{runtime}` is the only always-loaded PactKit rule.",
|
|
200
191
|
"",
|
|
201
|
-
"##
|
|
192
|
+
"## Active Command Rules",
|
|
202
193
|
"",
|
|
203
|
-
"
|
|
204
|
-
"
|
|
205
|
-
"- **Architecture**: `docs/architecture/graphs/`",
|
|
206
|
-
"- **Commands**: Type `/` followed by command name (e.g., `/project-plan`)",
|
|
194
|
+
"PactKit command files inline only their phase contract and declared shared modules. "
|
|
195
|
+
"Do not load PDCA rules for ordinary questions or ordinary coding.",
|
|
207
196
|
"",
|
|
208
|
-
"
|
|
197
|
+
"Ordinary questions and ordinary coding do not activate PDCA.",
|
|
198
|
+
"A user-invoked PactKit command loads only its own phase contract and declared shared modules.",
|
|
209
199
|
"",
|
|
210
200
|
]
|
|
211
201
|
atomic_write(opencode_root / "AGENTS.md", "\n".join(lines))
|
|
@@ -290,9 +280,9 @@ class OpenCodeDeployer(DeployerBase):
|
|
|
290
280
|
|
|
291
281
|
config.setdefault("$schema", "https://opencode.ai/config.json")
|
|
292
282
|
|
|
293
|
-
# Always-load layer: the
|
|
294
|
-
#
|
|
295
|
-
#
|
|
283
|
+
# Always-load layer: only the self-contained Runtime Kernel is global.
|
|
284
|
+
# Phase, command, conditional, and risk rules are activated by the
|
|
285
|
+
# current command instead of through a wildcard instruction.
|
|
296
286
|
from pactkit.prompts.rules import RULES_INSTRUCTIONS_CORE
|
|
297
287
|
|
|
298
288
|
managed = set(RULES_INSTRUCTIONS_CORE)
|
{pactkit_opencode-2.22.0 → pactkit_opencode-2.23.0}/src/pactkit_opencode.egg-info/SOURCES.txt
RENAMED
|
@@ -13,4 +13,5 @@ tests/test_deploy_opencode_parity.py
|
|
|
13
13
|
tests/test_story069_opencode_format.py
|
|
14
14
|
tests/test_story070_opencode_compliance.py
|
|
15
15
|
tests/test_story071_opencode_config_parity.py
|
|
16
|
-
tests/test_story073_command_model_routing.py
|
|
16
|
+
tests/test_story073_command_model_routing.py
|
|
17
|
+
tests/test_story_work_units.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
pactkit<2.24.0,>=2.23.0
|
|
@@ -85,6 +85,24 @@ class TestR2GlobalDeployment:
|
|
|
85
85
|
for cmd in _DEPLOYABLE_COMMANDS:
|
|
86
86
|
assert cmd in deployed, f"{cmd}.md not found in commands/"
|
|
87
87
|
|
|
88
|
+
def test_sprint_uses_managed_phase_capsules_without_retired_runtime(self, tmp_path):
|
|
89
|
+
out = tmp_path / "opencode"
|
|
90
|
+
deploy(format="opencode", target=str(out))
|
|
91
|
+
|
|
92
|
+
sprint = (out / "commands" / "project-sprint.md").read_text()
|
|
93
|
+
assert "current session" in sprint.lower()
|
|
94
|
+
assert "# sprint orchestrator" in sprint.lower()
|
|
95
|
+
assert "Plan Contract" not in sprint
|
|
96
|
+
assert "Act Contract" not in sprint
|
|
97
|
+
assert "Check Contract" not in sprint
|
|
98
|
+
assert "Done Contract" not in sprint
|
|
99
|
+
for retired in ("TeamCreate", "TaskCreate", "WorkUnit", "codex runner"):
|
|
100
|
+
assert retired not in sprint
|
|
101
|
+
for phase in ("plan", "act", "check", "done"):
|
|
102
|
+
capsule = out / "skills" / "_rules" / "phases" / f"{phase}-contract.md"
|
|
103
|
+
assert capsule.is_file()
|
|
104
|
+
assert str(capsule).split(str(out), 1)[-1].lstrip("/")
|
|
105
|
+
|
|
88
106
|
def test_all_embedded_skills_deployed(self, tmp_path):
|
|
89
107
|
"""All embedded skills are in skills/."""
|
|
90
108
|
out = tmp_path / "opencode"
|
|
@@ -159,16 +177,24 @@ class TestR4AgentsMdInlineRules:
|
|
|
159
177
|
assert "PactKit" in content
|
|
160
178
|
|
|
161
179
|
def test_rules_in_separate_files(self, tmp_path):
|
|
162
|
-
"""
|
|
180
|
+
"""Only the minimal Runtime Kernel is globally deployed."""
|
|
163
181
|
out = tmp_path / "opencode"
|
|
164
182
|
deploy(format="opencode", target=str(out))
|
|
165
183
|
rules_dir = out / "rules"
|
|
166
184
|
assert rules_dir.is_dir()
|
|
167
|
-
#
|
|
185
|
+
# Scenario-driven architecture: only the Runtime Kernel is global.
|
|
168
186
|
rule_files = {f.name for f in rules_dir.glob("*.md")}
|
|
169
|
-
assert "pactkit.md"
|
|
170
|
-
content = (rules_dir / "pactkit.md").read_text()
|
|
171
|
-
assert "
|
|
187
|
+
assert rule_files == {"pactkit-runtime.md"}
|
|
188
|
+
content = (rules_dir / "pactkit-runtime.md").read_text()
|
|
189
|
+
assert "current session" in content
|
|
190
|
+
|
|
191
|
+
def test_engineering_guides_are_deployed_at_the_rendered_path(self, tmp_path):
|
|
192
|
+
out = tmp_path / "opencode"
|
|
193
|
+
deploy(format="opencode", target=str(out))
|
|
194
|
+
|
|
195
|
+
guide = out / "skills" / "_rules" / "guides" / "caching.md"
|
|
196
|
+
assert guide.is_file()
|
|
197
|
+
assert "## Trigger" in guide.read_text(encoding="utf-8")
|
|
172
198
|
|
|
173
199
|
|
|
174
200
|
# ===========================================================================
|
{pactkit_opencode-2.22.0 → pactkit_opencode-2.23.0}/tests/test_story071_opencode_config_parity.py
RENAMED
|
@@ -155,10 +155,9 @@ class TestAC6RulesModularization:
|
|
|
155
155
|
out = tmp_path / "oc"
|
|
156
156
|
deploy(format="opencode", target=str(out))
|
|
157
157
|
rule_files = list((out / "rules").glob("*.md"))
|
|
158
|
-
#
|
|
159
|
-
# pactkit.md (+ user-managed credential file when present)
|
|
158
|
+
# Scenario-driven layout: only Runtime is globally loaded.
|
|
160
159
|
assert len(rule_files) >= 1
|
|
161
|
-
assert (out / "rules" / "pactkit.md").exists()
|
|
160
|
+
assert (out / "rules" / "pactkit-runtime.md").exists()
|
|
162
161
|
|
|
163
162
|
def test_agents_md_slim(self, tmp_path):
|
|
164
163
|
"""AGENTS.md is slim (< 30 lines) after modularization."""
|
|
@@ -190,7 +189,7 @@ class TestAC6RulesModularization:
|
|
|
190
189
|
|
|
191
190
|
|
|
192
191
|
class TestAC7GlobalOpencodeJson:
|
|
193
|
-
"""AC7: Global opencode.json
|
|
192
|
+
"""AC7: Global opencode.json loads only the Runtime Kernel."""
|
|
194
193
|
|
|
195
194
|
def test_global_json_created(self, tmp_path):
|
|
196
195
|
"""opencode.json is created in the opencode root."""
|
|
@@ -198,14 +197,13 @@ class TestAC7GlobalOpencodeJson:
|
|
|
198
197
|
deploy(format="opencode", target=str(out))
|
|
199
198
|
assert (out / "opencode.json").is_file()
|
|
200
199
|
|
|
201
|
-
def
|
|
202
|
-
"""STORY-slim-011: instructions only contains 09-credential-safety (rules are per-command)."""
|
|
200
|
+
def test_instructions_contains_runtime_only(self, tmp_path):
|
|
203
201
|
out = tmp_path / "oc"
|
|
204
202
|
deploy(format="opencode", target=str(out))
|
|
205
203
|
data = json.loads((out / "opencode.json").read_text())
|
|
206
204
|
assert "instructions" in data
|
|
207
205
|
assert "rules/*.md" not in data["instructions"]
|
|
208
|
-
assert "rules/
|
|
206
|
+
assert data["instructions"] == ["rules/pactkit-runtime.md"]
|
|
209
207
|
# Core rules moved to per-command injection
|
|
210
208
|
assert "rules/01-core-protocol.md" not in data["instructions"]
|
|
211
209
|
assert "rules/02-hierarchy-of-truth.md" not in data["instructions"]
|
|
@@ -226,6 +224,6 @@ class TestAC7GlobalOpencodeJson:
|
|
|
226
224
|
# Provider must be preserved
|
|
227
225
|
assert "provider" in data
|
|
228
226
|
assert data["provider"]["anthropic"]["options"]["apiKey"] == "test-key"
|
|
229
|
-
#
|
|
227
|
+
# The one self-contained Runtime instruction must be added.
|
|
230
228
|
assert "instructions" in data
|
|
231
|
-
assert "rules/
|
|
229
|
+
assert "rules/pactkit-runtime.md" in data["instructions"]
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from pactkit.generators.deployer import _render_skill_md
|
|
4
|
+
from pactkit.portable_methods import get_portable_methods
|
|
5
|
+
from pactkit.profiles import get_profile
|
|
6
|
+
from pactkit_opencode.deployer import OpenCodeDeployer
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def test_opencode_uses_native_current_session_contract_without_portable_methods(tmp_path):
|
|
10
|
+
OpenCodeDeployer().deploy(target=tmp_path)
|
|
11
|
+
method = tmp_path / "skills/pactkit-method-spec-writing/SKILL.md"
|
|
12
|
+
assert not method.exists()
|
|
13
|
+
assert not list((tmp_path / "skills").glob("pactkit-method-*"))
|
|
14
|
+
plan = (tmp_path / "commands/project-plan.md").read_text()
|
|
15
|
+
assert "pactkit work-unit acquire" not in plan
|
|
16
|
+
assert "Codex runner" not in plan
|
|
17
|
+
assert "current session" in plan
|
|
18
|
+
manifest = json.loads((tmp_path / ".pactkit-deployed.json").read_text())
|
|
19
|
+
assert manifest["host_capabilities"]["execution_mode"] == "portable"
|
|
20
|
+
assert manifest["workflow_continuation"]["stop_hook_required"] is False
|
|
21
|
+
assert len(manifest["skills"]) == 13
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def test_opencode_upgrade_removes_only_unmodified_portable_methods(tmp_path):
|
|
25
|
+
profile = get_profile("opencode")
|
|
26
|
+
methods = get_portable_methods()
|
|
27
|
+
generated = tmp_path / "skills" / methods[0]["name"] / "SKILL.md"
|
|
28
|
+
generated.parent.mkdir(parents=True)
|
|
29
|
+
generated.write_text(
|
|
30
|
+
_render_skill_md(methods[0], profile, profile.skills_path_var),
|
|
31
|
+
encoding="utf-8",
|
|
32
|
+
)
|
|
33
|
+
customized = tmp_path / "skills" / methods[1]["name"] / "SKILL.md"
|
|
34
|
+
customized.parent.mkdir(parents=True)
|
|
35
|
+
customized.write_text("user-owned method customization\n", encoding="utf-8")
|
|
36
|
+
|
|
37
|
+
OpenCodeDeployer().deploy(config={}, target=tmp_path)
|
|
38
|
+
|
|
39
|
+
assert not generated.parent.exists()
|
|
40
|
+
assert customized.read_text(encoding="utf-8") == (
|
|
41
|
+
"user-owned method customization\n"
|
|
42
|
+
)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
pactkit>=2.20.0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pactkit_opencode-2.22.0 → pactkit_opencode-2.23.0}/src/pactkit_opencode.egg-info/entry_points.txt
RENAMED
|
File without changes
|
{pactkit_opencode-2.22.0 → pactkit_opencode-2.23.0}/src/pactkit_opencode.egg-info/top_level.txt
RENAMED
|
File without changes
|
{pactkit_opencode-2.22.0 → pactkit_opencode-2.23.0}/tests/test_bug035_opencode_dual_layer.py
RENAMED
|
File without changes
|
|
File without changes
|
{pactkit_opencode-2.22.0 → pactkit_opencode-2.23.0}/tests/test_story070_opencode_compliance.py
RENAMED
|
File without changes
|
{pactkit_opencode-2.22.0 → pactkit_opencode-2.23.0}/tests/test_story073_command_model_routing.py
RENAMED
|
File without changes
|