pactkit-opencode 2.22.0__tar.gz → 2.24.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.
Files changed (20) hide show
  1. {pactkit_opencode-2.22.0 → pactkit_opencode-2.24.0}/PKG-INFO +2 -2
  2. {pactkit_opencode-2.22.0 → pactkit_opencode-2.24.0}/pyproject.toml +2 -2
  3. {pactkit_opencode-2.22.0 → pactkit_opencode-2.24.0}/src/pactkit_opencode/deployer.py +31 -41
  4. {pactkit_opencode-2.22.0 → pactkit_opencode-2.24.0}/src/pactkit_opencode.egg-info/PKG-INFO +2 -2
  5. {pactkit_opencode-2.22.0 → pactkit_opencode-2.24.0}/src/pactkit_opencode.egg-info/SOURCES.txt +2 -1
  6. pactkit_opencode-2.24.0/src/pactkit_opencode.egg-info/requires.txt +1 -0
  7. {pactkit_opencode-2.22.0 → pactkit_opencode-2.24.0}/tests/test_story069_opencode_format.py +31 -5
  8. {pactkit_opencode-2.22.0 → pactkit_opencode-2.24.0}/tests/test_story071_opencode_config_parity.py +7 -9
  9. pactkit_opencode-2.24.0/tests/test_story_work_units.py +42 -0
  10. pactkit_opencode-2.22.0/src/pactkit_opencode.egg-info/requires.txt +0 -1
  11. {pactkit_opencode-2.22.0 → pactkit_opencode-2.24.0}/README.md +0 -0
  12. {pactkit_opencode-2.22.0 → pactkit_opencode-2.24.0}/setup.cfg +0 -0
  13. {pactkit_opencode-2.22.0 → pactkit_opencode-2.24.0}/src/pactkit_opencode/__init__.py +0 -0
  14. {pactkit_opencode-2.22.0 → pactkit_opencode-2.24.0}/src/pactkit_opencode.egg-info/dependency_links.txt +0 -0
  15. {pactkit_opencode-2.22.0 → pactkit_opencode-2.24.0}/src/pactkit_opencode.egg-info/entry_points.txt +0 -0
  16. {pactkit_opencode-2.22.0 → pactkit_opencode-2.24.0}/src/pactkit_opencode.egg-info/top_level.txt +0 -0
  17. {pactkit_opencode-2.22.0 → pactkit_opencode-2.24.0}/tests/test_bug035_opencode_dual_layer.py +0 -0
  18. {pactkit_opencode-2.22.0 → pactkit_opencode-2.24.0}/tests/test_deploy_opencode_parity.py +0 -0
  19. {pactkit_opencode-2.22.0 → pactkit_opencode-2.24.0}/tests/test_story070_opencode_compliance.py +0 -0
  20. {pactkit_opencode-2.22.0 → pactkit_opencode-2.24.0}/tests/test_story073_command_model_routing.py +0 -0
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pactkit-opencode
3
- Version: 2.22.0
3
+ Version: 2.24.0
4
4
  Summary: PactKit adapter for OpenCode IDE
5
5
  License-Expression: MIT
6
6
  Requires-Python: >=3.10
7
- Requires-Dist: pactkit>=2.20.0
7
+ Requires-Dist: pactkit<2.25.0,>=2.24.0
@@ -4,12 +4,12 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pactkit-opencode"
7
- version = "2.22.0"
7
+ version = "2.24.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.20.0",
12
+ "pactkit>=2.24.0,<2.25.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__, prompts
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 with instructions: ["rules/*.md"])
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 project_yaml is not None:
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(VALID_RULES))
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(skills_dir, enabled_skills, profile=self.profile)
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 AGENTS.md with on-demand @reference index (STORY-slim-009 R3)."""
171
- ref_lines = []
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
- core_names = ", ".join(
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 Global Constitution (v{__version__} Modular)",
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
- *ref_lines,
190
+ f"`rules/{runtime}` is the only always-loaded PactKit rule.",
200
191
  "",
201
- "## Quick Reference",
192
+ "## Active Command Rules",
202
193
  "",
203
- "- **Specs** (`docs/specs/`) are the source of truth",
204
- "- **Sprint Board**: `docs/product/sprint_board.md`",
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
- "> **TIP**: Run `/project-init` to set up project governance and enable cross-session context.",
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 merged constitution + credential safety must be
294
- # globally referenced (RULES_INSTRUCTIONS_CORE). Older logic stripped
295
- # rules/* entries that silently dropped pactkit.md (drift 2026-08-13).
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)
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pactkit-opencode
3
- Version: 2.22.0
3
+ Version: 2.24.0
4
4
  Summary: PactKit adapter for OpenCode IDE
5
5
  License-Expression: MIT
6
6
  Requires-Python: >=3.10
7
- Requires-Dist: pactkit>=2.20.0
7
+ Requires-Dist: pactkit<2.25.0,>=2.24.0
@@ -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.25.0,>=2.24.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
- """Merged constitution is deployed to rules/ (post-merge layout)."""
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
- # Post-merge (slim-126+): the constitution ships as a single pactkit.md
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" in rule_files
170
- content = (rules_dir / "pactkit.md").read_text()
171
- assert "Hierarchy of Truth" in content
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
  # ===========================================================================
@@ -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
- # Post-merge layout: the 8 VALID_RULES components deploy as one merged
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 has instructions with rules/*.md."""
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 test_instructions_contains_credential_only(self, tmp_path):
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/09-credential-safety.md" in data["instructions"]
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
- # instructions must be added STORY-slim-011: only credential safety
227
+ # The one self-contained Runtime instruction must be added.
230
228
  assert "instructions" in data
231
- assert "rules/09-credential-safety.md" in data["instructions"]
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