pactkit-copilot 2.9.4__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.

Potentially problematic release.


This version of pactkit-copilot might be problematic. Click here for more details.

@@ -0,0 +1,63 @@
1
+ Metadata-Version: 2.4
2
+ Name: pactkit-copilot
3
+ Version: 2.9.4
4
+ Summary: PactKit adapter for GitHub Copilot
5
+ Author: Slim
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/pactkit/pactkit-copilot
8
+ Project-URL: Repository, https://github.com/pactkit/pactkit-copilot
9
+ Project-URL: Documentation, https://github.com/pactkit/pactkit-copilot#readme
10
+ Project-URL: Issues, https://github.com/pactkit/pactkit-copilot/issues
11
+ Keywords: ai,agent,copilot,github,github-copilot,code-assistant,code-quality,developer-tools,spec-driven,devops,scaffold,tdd,pdca
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Software Development :: Build Tools
20
+ Classifier: Topic :: Software Development :: Quality Assurance
21
+ Requires-Python: >=3.10
22
+ Description-Content-Type: text/markdown
23
+ Requires-Dist: pactkit>=2.9.4
24
+
25
+ # pactkit-copilot
26
+
27
+ PactKit adapter for GitHub Copilot — project-level deployment to `.github/`.
28
+
29
+ ## Install
30
+
31
+ ```bash
32
+ pip install pactkit pactkit-copilot
33
+ ```
34
+
35
+ ## Usage
36
+
37
+ ```bash
38
+ # Deploy PactKit to a project for GitHub Copilot
39
+ cd your-project
40
+ pactkit init --format copilot
41
+
42
+ # Then open the project in your IDE and use /project-init
43
+ ```
44
+
45
+ ## What it deploys
46
+
47
+ | Asset | Location |
48
+ |-------|----------|
49
+ | Skills | `.github/skills/{name}/SKILL.md` |
50
+ | Agents | `.github/agents/{name}.md` |
51
+ | Commands | `.github/prompts/{name}.prompt.md` |
52
+ | Instructions | `.github/copilot-instructions.md` |
53
+
54
+ ## Key differences from other adapters
55
+
56
+ - **Project-level only**: Everything goes into `.github/` (no user-level `~/` directory)
57
+ - **Per-project init**: Each new project requires `pactkit init --format copilot`
58
+ - **Rules inlined**: Copilot doesn't support `@include`, so rules are inlined into command prompts
59
+ - **Two-step bootstrap**: `pactkit init` (terminal) → `/project-init` (IDE)
60
+
61
+ ## License
62
+
63
+ MIT
@@ -0,0 +1,39 @@
1
+ # pactkit-copilot
2
+
3
+ PactKit adapter for GitHub Copilot — project-level deployment to `.github/`.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ pip install pactkit pactkit-copilot
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ ```bash
14
+ # Deploy PactKit to a project for GitHub Copilot
15
+ cd your-project
16
+ pactkit init --format copilot
17
+
18
+ # Then open the project in your IDE and use /project-init
19
+ ```
20
+
21
+ ## What it deploys
22
+
23
+ | Asset | Location |
24
+ |-------|----------|
25
+ | Skills | `.github/skills/{name}/SKILL.md` |
26
+ | Agents | `.github/agents/{name}.md` |
27
+ | Commands | `.github/prompts/{name}.prompt.md` |
28
+ | Instructions | `.github/copilot-instructions.md` |
29
+
30
+ ## Key differences from other adapters
31
+
32
+ - **Project-level only**: Everything goes into `.github/` (no user-level `~/` directory)
33
+ - **Per-project init**: Each new project requires `pactkit init --format copilot`
34
+ - **Rules inlined**: Copilot doesn't support `@include`, so rules are inlined into command prompts
35
+ - **Two-step bootstrap**: `pactkit init` (terminal) → `/project-init` (IDE)
36
+
37
+ ## License
38
+
39
+ MIT
@@ -0,0 +1,75 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "pactkit-copilot"
7
+ version = "2.9.4"
8
+ description = "PactKit adapter for GitHub Copilot"
9
+ readme = "README.md"
10
+ license = {text = "MIT"}
11
+ requires-python = ">=3.10"
12
+ dependencies = [
13
+ "pactkit>=2.9.4",
14
+ ]
15
+
16
+ authors = [
17
+ { name = "Slim" },
18
+ ]
19
+ keywords = [
20
+ "ai",
21
+ "agent",
22
+ "copilot",
23
+ "github",
24
+ "github-copilot",
25
+ "code-assistant",
26
+ "code-quality",
27
+ "developer-tools",
28
+ "spec-driven",
29
+ "devops",
30
+ "scaffold",
31
+ "tdd",
32
+ "pdca",
33
+ ]
34
+ classifiers = [
35
+ "Development Status :: 3 - Alpha",
36
+ "Intended Audience :: Developers",
37
+ "Programming Language :: Python :: 3",
38
+ "Programming Language :: Python :: 3.10",
39
+ "Programming Language :: Python :: 3.11",
40
+ "Programming Language :: Python :: 3.12",
41
+ "Programming Language :: Python :: 3.13",
42
+ "Topic :: Software Development :: Build Tools",
43
+ "Topic :: Software Development :: Quality Assurance",
44
+ ]
45
+
46
+ [project.urls]
47
+ Homepage = "https://github.com/pactkit/pactkit-copilot"
48
+ Repository = "https://github.com/pactkit/pactkit-copilot"
49
+ Documentation = "https://github.com/pactkit/pactkit-copilot#readme"
50
+ Issues = "https://github.com/pactkit/pactkit-copilot/issues"
51
+
52
+ [project.entry-points."pactkit.deployers"]
53
+ copilot = "pactkit_copilot:CopilotDeployer"
54
+
55
+ [tool.setuptools.packages.find]
56
+ where = ["src"]
57
+
58
+ [tool.pytest.ini_options]
59
+ testpaths = ["tests"]
60
+ pythonpath = ["src", "../pactkit/src"]
61
+ markers = [
62
+ "e2e: End-to-end CLI tests (subprocess-based)",
63
+ "integration: Integration tests that call deploy() with filesystem I/O",
64
+ ]
65
+
66
+ [tool.ruff]
67
+ target-version = "py310"
68
+ line-length = 120
69
+
70
+ [tool.ruff.lint]
71
+ select = ["E", "F", "I", "W"]
72
+
73
+ [tool.ruff.lint.per-file-ignores]
74
+ "src/pactkit_copilot/deployer.py" = ["E501"]
75
+ "tests/**/*.py" = ["E402", "E501", "E741", "F841", "I001"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,5 @@
1
+ """PactKit Copilot — adapter for GitHub Copilot."""
2
+
3
+ from pactkit import __version__ # noqa: F401
4
+
5
+ from pactkit_copilot.deployer import CopilotDeployer # noqa: F401
@@ -0,0 +1,590 @@
1
+ """Copilot deployer — project-level adapter for GitHub Copilot.
2
+
3
+ STORY-013: Converts PactKit assets for GitHub Copilot deployment.
4
+
5
+ Copilot-specific features:
6
+ - **Project-level only**: Everything deploys to `.github/` inside the project
7
+ - Skills → `.github/skills/{name}/SKILL.md`
8
+ - Agents → `.github/agents/{name}.md` (individual files, not single AGENTS.md)
9
+ - Commands → `.github/prompts/{name}.prompt.md` (Copilot slash commands)
10
+ - Instructions → `.github/copilot-instructions.md` (project-level, no @include)
11
+ - Rules → inlined into agents/skills (Copilot has no rules/ directory)
12
+ - No user-level deployment (unlike Claude Code, OpenCode, Codex)
13
+ - Each project requires `pactkit init --format copilot` before IDE use
14
+ """
15
+
16
+ import re
17
+ from pathlib import Path
18
+
19
+ from pactkit import __version__, prompts
20
+ from pactkit.config import (
21
+ VALID_SKILLS,
22
+ auto_merge_config_file,
23
+ load_config,
24
+ )
25
+ from pactkit.generators.deploy_base import DeployerBase, register_deployer
26
+ from pactkit.generators.deployer import (
27
+ _cleanup_legacy,
28
+ _render_prompt,
29
+ )
30
+ from pactkit.profiles import get_profile
31
+ from pactkit.prompts.rules import (
32
+ COMMAND_RULES_MAP,
33
+ )
34
+ from pactkit.skills import load_script
35
+ from pactkit.utils import atomic_write
36
+
37
+ # Commands excluded from Copilot deployment (require multi-agent orchestration)
38
+ COPILOT_EXCLUDED_COMMANDS = frozenset({"project-sprint.md"})
39
+
40
+ # Version marker filename
41
+ VERSION_MARKER_FILE = ".pactkit-version"
42
+
43
+ # --- Templates ---
44
+
45
+ _COPILOT_INSTRUCTIONS_MD = """\
46
+ # {project_name} — Copilot Instructions
47
+
48
+ > Auto-generated by PactKit v{version}. Do not edit the [pactkit:managed] sections.
49
+ > Read `docs/product/context.md` at session start for project state.
50
+ > Read `.github/copilot-instructions.local.md` for project-specific overrides.
51
+
52
+ ## Dev Commands
53
+
54
+ ```bash
55
+ {dev_commands}
56
+ ```
57
+
58
+ ## PDCA Routing Table
59
+
60
+ | Phase | Command | Role |
61
+ |-------|---------|------|
62
+ | Plan | `/project-plan` | system-architect |
63
+ | Plan | `/project-design` | product-designer |
64
+ | Plan | `/project-clarify` | system-architect |
65
+ | Act | `/project-act` | senior-developer |
66
+ | Act | `/project-hotfix` | senior-developer |
67
+ | Check | `/project-check` | qa-engineer |
68
+ | Done | `/project-done` | repo-maintainer |
69
+ | Done | `/project-release` | repo-maintainer |
70
+ | Done | `/project-pr` | repo-maintainer |
71
+ | Bootstrap | `/project-init` | system-architect |
72
+
73
+ > **TIP**: Use `/project-init` to set up project governance.
74
+
75
+ {rules_section}
76
+ """
77
+
78
+ _COPILOT_LOCAL_INSTRUCTIONS = """\
79
+ # Project Local Instructions
80
+ # Add your custom Copilot instructions below.
81
+ # PactKit will never overwrite this file.
82
+ """
83
+
84
+ _STACK_DEV_COMMANDS = {
85
+ "python": "# Run tests\npython3 -m pytest tests/ -v\n\n# Lint\nruff check src/ tests/",
86
+ "node": "# Run tests\nnpm test\n\n# Lint\nnpm run lint",
87
+ "go": "# Run tests\ngo test ./...\n\n# Lint\ngolangci-lint run",
88
+ "java": "# Run tests\nmvn test\n\n# Lint\nmvn checkstyle:check",
89
+ "unknown": "# TODO: Add your test and lint commands here",
90
+ }
91
+
92
+ # Core rules to inline in copilot-instructions.md (minimal-core strategy)
93
+ _MINIMAL_CORE_RULES = ["core", "hierarchy", "atlas", "workflow", "shared"]
94
+
95
+
96
+ class CopilotDeployer(DeployerBase):
97
+ """GitHub Copilot deployment — project-level `.github/` structure.
98
+
99
+ Unlike other deployers, Copilot deploys everything into the project
100
+ directory (`.github/`) rather than a user-level config directory.
101
+ Each new project requires `pactkit init --format copilot`.
102
+ """
103
+
104
+ profile = get_profile("copilot")
105
+
106
+ def deploy(self, config=None, target=None):
107
+ """Deploy PactKit configuration for GitHub Copilot.
108
+
109
+ Target is the project root (not .github/). Everything goes into
110
+ {project_root}/.github/ subdirectories.
111
+ """
112
+ project_root = Path(target) if target else Path.cwd()
113
+ github_dir = project_root / ".github"
114
+
115
+ print("🚀 PactKit GitHub Copilot Deployment")
116
+
117
+ # Load pactkit.yaml from project if available
118
+ from pactkit.config import find_pactkit_yaml
119
+
120
+ project_yaml = find_pactkit_yaml()
121
+ if project_yaml is not None:
122
+ auto_added = auto_merge_config_file(project_yaml)
123
+ for item in auto_added:
124
+ print(f" -> Auto-added: {item}")
125
+ cfg = load_config(project_yaml)
126
+ else:
127
+ cfg = {}
128
+
129
+ enabled_skills = cfg.get("skills", sorted(VALID_SKILLS))
130
+ rules_strategy = cfg.get("copilot_rules_strategy", "minimal-core")
131
+
132
+ # Deploy skills
133
+ skills_dir = github_dir / "skills"
134
+ skills_dir.mkdir(parents=True, exist_ok=True)
135
+ n_skills = self.deploy_copilot_skills(skills_dir, enabled_skills, self.profile)
136
+
137
+ # Deploy command prompts
138
+ prompts_dir = github_dir / "prompts"
139
+ prompts_dir.mkdir(parents=True, exist_ok=True)
140
+ n_commands = self.deploy_copilot_command_prompts(prompts_dir, self.profile)
141
+
142
+ # Deploy agent definitions
143
+ agents_dir = github_dir / "agents"
144
+ agents_dir.mkdir(parents=True, exist_ok=True)
145
+ self.deploy_copilot_agents(agents_dir, self.profile)
146
+
147
+ # Generate copilot-instructions.md
148
+ stack = _detect_stack(project_root)
149
+ self.generate_copilot_instructions(github_dir, project_root.name, stack, rules_strategy)
150
+
151
+ # Generate pactkit.yaml if missing
152
+ yaml_path = github_dir / "pactkit.yaml"
153
+ if yaml_path.exists():
154
+ print(" ⚠️ .github/pactkit.yaml already exists — skipping")
155
+ else:
156
+ yaml_content = (
157
+ f"stack: {stack}\n"
158
+ f"version: 0.0.1\n"
159
+ f"root: .\n"
160
+ f'developer: ""\n'
161
+ )
162
+ atomic_write(yaml_path, yaml_content)
163
+
164
+ # Write local instructions if missing
165
+ local_path = github_dir / "copilot-instructions.local.md"
166
+ if not local_path.exists():
167
+ atomic_write(local_path, _COPILOT_LOCAL_INSTRUCTIONS)
168
+
169
+ # Legacy cleanup
170
+ _cleanup_legacy(skills_dir)
171
+
172
+ self.write_version_marker(github_dir)
173
+
174
+ print(
175
+ f"\n✅ Copilot: {n_skills} Skills, {n_commands} Commands → {github_dir}"
176
+ )
177
+ print(
178
+ "\n📌 Next step: Open the project in your IDE and use /project-init to complete PDCA setup."
179
+ )
180
+
181
+ # --- Version tracking ---
182
+
183
+ @staticmethod
184
+ def write_version_marker(github_dir, version=None):
185
+ """Write version marker to .github/.pactkit-version."""
186
+ v = version if version is not None else __version__
187
+ atomic_write(github_dir / VERSION_MARKER_FILE, v + "\n")
188
+
189
+ @staticmethod
190
+ def read_deployed_version(github_dir):
191
+ """Read deployed version from marker file. Returns None if not found."""
192
+ marker = github_dir / VERSION_MARKER_FILE
193
+ if marker.exists():
194
+ return marker.read_text().strip()
195
+ return None
196
+
197
+ # --- Copilot-specific deployment methods ---
198
+
199
+ @staticmethod
200
+ def deploy_copilot_skills(skills_dir, enabled_skills, profile):
201
+ """Deploy skills to .github/skills/{name}/SKILL.md with embedded scripts."""
202
+ _prefix = profile.skills_path_var
203
+
204
+ scripted_skill_defs = [
205
+ {
206
+ "name": "pactkit-visualize",
207
+ "skill_md": prompts.SKILL_VISUALIZE_MD,
208
+ "script_name": "visualize.py",
209
+ "script_source": load_script("visualize.py"),
210
+ },
211
+ {
212
+ "name": "pactkit-board",
213
+ "skill_md": prompts.SKILL_BOARD_MD,
214
+ "script_name": "board.py",
215
+ "script_source": load_script("board.py"),
216
+ },
217
+ {
218
+ "name": "pactkit-scaffold",
219
+ "skill_md": prompts.SKILL_SCAFFOLD_MD,
220
+ "script_name": "scaffold.py",
221
+ "script_source": load_script("scaffold.py"),
222
+ },
223
+ ]
224
+
225
+ prompt_only_skill_defs = [
226
+ {"name": "pactkit-trace", "skill_md": prompts.SKILL_TRACE_MD},
227
+ {"name": "pactkit-draw", "skill_md": prompts.SKILL_DRAW_MD},
228
+ {"name": "pactkit-status", "skill_md": prompts.SKILL_STATUS_MD},
229
+ {"name": "pactkit-doctor", "skill_md": prompts.SKILL_DOCTOR_MD},
230
+ {"name": "pactkit-review", "skill_md": prompts.SKILL_REVIEW_MD},
231
+ {"name": "pactkit-release", "skill_md": prompts.SKILL_RELEASE_MD},
232
+ {"name": "pactkit-analyze", "skill_md": prompts.SKILL_ANALYZE_MD},
233
+ ]
234
+
235
+ enabled_set = set(enabled_skills)
236
+ deployed = 0
237
+
238
+ for sd in scripted_skill_defs:
239
+ if sd["name"] not in enabled_set:
240
+ continue
241
+ skill_dir = skills_dir / sd["name"]
242
+ scripts_dir = skill_dir / "scripts"
243
+ scripts_dir.mkdir(parents=True, exist_ok=True)
244
+
245
+ from pactkit.generators.deployer import _render_skill_md
246
+ skill_md = _render_skill_md(sd, profile, _prefix)
247
+ skill_md = _replace_paths_for_copilot(skill_md)
248
+ skill_md = CopilotDeployer.strip_model_references(skill_md)
249
+ atomic_write(skill_dir / "SKILL.md", skill_md)
250
+
251
+ script_content = sd["script_source"]
252
+ script_content = script_content.replace("~/.claude/", f"{profile.global_config_dir}/")
253
+ script_content = script_content.replace("~/.config/opencode/", f"{profile.global_config_dir}/")
254
+ script_content = script_content.replace("~/.codex/", f"{profile.global_config_dir}/")
255
+ atomic_write(scripts_dir / sd["script_name"], script_content)
256
+ deployed += 1
257
+
258
+ for sd in prompt_only_skill_defs:
259
+ if sd["name"] not in enabled_set:
260
+ continue
261
+ skill_dir = skills_dir / sd["name"]
262
+ skill_dir.mkdir(parents=True, exist_ok=True)
263
+
264
+ from pactkit.generators.deployer import _render_skill_md
265
+ skill_md = _render_skill_md(sd, profile, _prefix)
266
+ skill_md = _replace_paths_for_copilot(skill_md)
267
+ skill_md = CopilotDeployer.strip_model_references(skill_md)
268
+ atomic_write(skill_dir / "SKILL.md", skill_md)
269
+ deployed += 1
270
+
271
+ return deployed
272
+
273
+ @staticmethod
274
+ def deploy_copilot_command_prompts(prompts_dir, profile):
275
+ """Deploy PDCA commands as .github/prompts/{name}.prompt.md.
276
+
277
+ Copilot uses .prompt.md files in .github/prompts/ for slash commands.
278
+ Each file becomes a /command-name in the Copilot chat.
279
+ """
280
+ deployed = 0
281
+ for filename, raw_content in prompts.COMMANDS_CONTENT.items():
282
+ if filename in COPILOT_EXCLUDED_COMMANDS:
283
+ continue
284
+ cmd_name = filename.removesuffix(".md")
285
+
286
+ # Extract description from original frontmatter, then strip it
287
+ description = cmd_name
288
+ content = raw_content
289
+ if content.startswith("---"):
290
+ parts = content.split("---", 2)
291
+ if len(parts) >= 3:
292
+ for line in parts[1].strip().split("\n"):
293
+ if line.startswith("description:"):
294
+ description = line.split(":", 1)[1].strip().strip('"')
295
+ content = parts[2].lstrip("\n")
296
+
297
+ content = _render_prompt(content, profile)
298
+ content = _replace_paths_for_copilot(content)
299
+ content = CopilotDeployer.strip_model_references(content)
300
+ content = _replace_slash_commands(content)
301
+
302
+ # Inline rules for this command (Copilot doesn't support @include)
303
+ rules_block = CopilotDeployer.inline_rules_for_command(cmd_name, profile)
304
+
305
+ # Build .prompt.md with Copilot frontmatter
306
+ frontmatter = (
307
+ f"---\n"
308
+ f"mode: agent\n"
309
+ f"description: \"{description}\"\n"
310
+ f"---\n\n"
311
+ )
312
+
313
+ prompt_content = frontmatter + content
314
+ if rules_block:
315
+ prompt_content += "\n\n---\n\n## Rules Reference\n\n" + rules_block
316
+
317
+ atomic_write(prompts_dir / f"{cmd_name}.prompt.md", prompt_content)
318
+ deployed += 1
319
+
320
+ return deployed
321
+
322
+ @staticmethod
323
+ def deploy_copilot_agents(agents_dir, profile):
324
+ """Deploy agent definitions as individual .github/agents/{name}.md files.
325
+
326
+ Copilot supports multi-agent via individual .md files in .github/agents/.
327
+ Each file defines one agent with frontmatter (name, description, tools).
328
+ """
329
+ excluded_fields = profile.excluded_agent_fields
330
+
331
+ for name, cfg in sorted(prompts.AGENTS_EXPERT.items()):
332
+ # Build Copilot agent frontmatter
333
+ fm_lines = ["---"]
334
+ fm_lines.append(f"name: {name}")
335
+ fm_lines.append(f'description: "{cfg["desc"]}"')
336
+
337
+ # Map allowed-tools → tools
338
+ if "tools" not in excluded_fields and cfg.get("tools"):
339
+ tools_list = ", ".join(cfg["tools"])
340
+ fm_lines.append(f"tools: [{tools_list}]")
341
+
342
+ fm_lines.append("---")
343
+ frontmatter = "\n".join(fm_lines) + "\n\n"
344
+
345
+ # Agent prompt body
346
+ prompt = cfg.get("prompt", "")
347
+ prompt = _render_prompt(prompt, profile)
348
+ prompt = _replace_paths_for_copilot(prompt)
349
+ prompt = CopilotDeployer.strip_model_references(prompt)
350
+
351
+ # Remove excluded fields from prompt body
352
+ for field in excluded_fields:
353
+ prompt = re.sub(
354
+ rf'^[-*]\s*\**{field}\**:.*$\n?',
355
+ '',
356
+ prompt,
357
+ flags=re.MULTILINE,
358
+ )
359
+
360
+ content = frontmatter + prompt.strip() + "\n"
361
+ atomic_write(agents_dir / f"{name}.md", content)
362
+
363
+ print(f" 📋 {len(prompts.AGENTS_EXPERT)} agent definitions deployed")
364
+
365
+ @staticmethod
366
+ def generate_copilot_instructions(github_dir, project_name, stack, rules_strategy="minimal-core"):
367
+ """Generate .github/copilot-instructions.md with optional rules inlining.
368
+
369
+ Strategies:
370
+ - minimal-core: Only core rules in instructions, detailed rules inlined per-command
371
+ - full-inline: All rules merged into copilot-instructions.md
372
+ """
373
+ dev_commands = _STACK_DEV_COMMANDS.get(stack, _STACK_DEV_COMMANDS["unknown"])
374
+
375
+ # Build rules section based on strategy
376
+ if rules_strategy == "full-inline":
377
+ rules_lines = ["## Rules\n"]
378
+ for key in _MINIMAL_CORE_RULES:
379
+ content = prompts.RULES_MODULES.get(key, "")
380
+ if not content:
381
+ continue
382
+ content = content.strip()
383
+ content = CopilotDeployer.strip_model_selection_table(content)
384
+ content = CopilotDeployer.strip_model_references(content)
385
+ content = _replace_paths_for_copilot(content)
386
+ rules_lines.append(content)
387
+ rules_lines.append("")
388
+ rules_section = "\n".join(rules_lines)
389
+ else:
390
+ # minimal-core: just a pointer
391
+ rules_section = (
392
+ "## Rules\n\n"
393
+ "Rules are embedded in each command prompt (`.github/prompts/`).\n"
394
+ "See individual command files for applicable governance rules.\n"
395
+ )
396
+
397
+ content = _COPILOT_INSTRUCTIONS_MD.format(
398
+ project_name=project_name,
399
+ version=__version__,
400
+ dev_commands=dev_commands,
401
+ rules_section=rules_section,
402
+ )
403
+
404
+ atomic_write(github_dir / "copilot-instructions.md", content)
405
+ print(f" 📄 copilot-instructions.md ({rules_strategy})")
406
+
407
+ # --- Brand replacement helpers ---
408
+
409
+ @staticmethod
410
+ def strip_model_references(content):
411
+ """Strip Claude/Anthropic model references from Copilot content."""
412
+ content = re.sub(r'claude-sonnet[\w-]*', 'capable-model', content)
413
+ content = re.sub(r'claude-haiku[\w-]*', 'fast-model', content)
414
+ content = re.sub(r'claude-opus[\w-]*', 'reasoning-model', content)
415
+ content = content.replace("[Claude Code](https://claude.com/claude-code)", "[GitHub Copilot](https://github.com/features/copilot)")
416
+ content = content.replace("Claude Code", "GitHub Copilot")
417
+ content = content.replace("Codex CLI", "GitHub Copilot")
418
+ content = content.replace("claude.com", "github.com/features/copilot")
419
+ content = re.sub(r'\bAnthropic\b', 'GitHub', content)
420
+ content = re.sub(r'\bOpenAI\b', 'GitHub', content)
421
+ return content
422
+
423
+ @staticmethod
424
+ def strip_model_selection_table(content):
425
+ """Remove the Subagent Model Selection section from inlined rules."""
426
+ lines = content.split("\n")
427
+ result = []
428
+ skip = False
429
+ for line in lines:
430
+ if "Subagent Model Selection" in line and line.strip().startswith("#"):
431
+ skip = True
432
+ continue
433
+ if skip:
434
+ if line.startswith("## ") or line.startswith("# "):
435
+ skip = False
436
+ result.append(line)
437
+ else:
438
+ result.append(line)
439
+ return "\n".join(result)
440
+
441
+ @staticmethod
442
+ def inline_rules_for_command(cmd_name, profile):
443
+ """Build inlined rules block for a command prompt (no @include support).
444
+
445
+ Returns the rules text to append, or empty string if no rules.
446
+ """
447
+ rule_keys = COMMAND_RULES_MAP.get(cmd_name, ["core", "credential"])
448
+ blocks = []
449
+
450
+ for key in rule_keys:
451
+ if key == "credential":
452
+ blocks.append(
453
+ "### Credential Safety\n\n"
454
+ "NEVER print passwords, keys, or tokens to stdout.\n"
455
+ "NEVER commit secrets to version control.\n"
456
+ )
457
+ continue
458
+
459
+ content = prompts.RULES_MODULES.get(key, "")
460
+ if not content:
461
+ continue
462
+ content = content.strip()
463
+ content = CopilotDeployer.strip_model_selection_table(content)
464
+ content = CopilotDeployer.strip_model_references(content)
465
+ content = _replace_paths_for_copilot(content)
466
+ blocks.append(content)
467
+
468
+ return "\n\n".join(blocks)
469
+
470
+
471
+ # --- Module-level functions ---
472
+
473
+
474
+ def update(target=None, force=False, if_needed=False, dry_run=False):
475
+ """Update deployed PactKit files incrementally.
476
+
477
+ Args:
478
+ target: Project root directory (default: cwd)
479
+ force: Bypass version check, always redeploy
480
+ if_needed: Silent no-op if already current
481
+ dry_run: Show plan without making changes
482
+
483
+ Returns:
484
+ dict with 'action' key: 'skip', 'updated', or 'dry_run'
485
+ """
486
+ project_root = Path(target) if target else Path.cwd()
487
+ github_dir = project_root / ".github"
488
+
489
+ deployed_version = CopilotDeployer.read_deployed_version(github_dir)
490
+ needs_update = deployed_version != __version__
491
+
492
+ if not needs_update and not force:
493
+ if not if_needed:
494
+ print(f"✅ Already up to date (v{__version__})")
495
+ return {"action": "skip"}
496
+
497
+ if dry_run:
498
+ print(f"Would update ({deployed_version or 'none'} → {__version__}):")
499
+ print(f" {github_dir}/copilot-instructions.md")
500
+ print(f" {github_dir}/skills/*/")
501
+ print(f" {github_dir}/prompts/*/")
502
+ print(f" {github_dir}/agents/*/")
503
+ print("Preserved (user-owned):")
504
+ print(f" {github_dir}/copilot-instructions.local.md")
505
+ print(f" {github_dir}/pactkit.yaml")
506
+ return {"action": "dry_run"}
507
+
508
+ print(f"🔄 Updating PactKit ({deployed_version or 'none'} → {__version__})")
509
+ CopilotDeployer().deploy(target=str(project_root))
510
+
511
+ return {"action": "updated"}
512
+
513
+
514
+ # --- Helper functions ---
515
+
516
+
517
+ def _detect_stack(project_root):
518
+ """Detect project stack from filesystem markers."""
519
+ project_root = Path(project_root)
520
+ if (project_root / "pyproject.toml").exists() or (project_root / "requirements.txt").exists():
521
+ return "python"
522
+ if (project_root / "package.json").exists():
523
+ return "node"
524
+ if (project_root / "go.mod").exists():
525
+ return "go"
526
+ if (project_root / "pom.xml").exists() or (project_root / "build.gradle").exists():
527
+ return "java"
528
+ return "unknown"
529
+
530
+
531
+ def _replace_paths_for_copilot(content):
532
+ """Replace Claude/OpenCode/Codex paths with Copilot .github/ paths."""
533
+ # Order matters: longer patterns first
534
+ content = content.replace("~/.claude/skills/", ".github/skills/")
535
+ content = content.replace("~/.claude/rules/", ".github/rules/")
536
+ content = content.replace("~/.claude/commands/", ".github/prompts/")
537
+ content = content.replace("~/.claude/agents/", ".github/agents/")
538
+ content = content.replace("~/.claude/", ".github/")
539
+ content = content.replace("~/.config/opencode/skills/", ".github/skills/")
540
+ content = content.replace("~/.config/opencode/", ".github/")
541
+ content = content.replace("~/.codex/skills/", ".github/skills/")
542
+ content = content.replace("~/.codex/rules/", ".github/rules/")
543
+ content = content.replace("~/.codex/", ".github/")
544
+ content = content.replace(".claude/settings.json", ".github/copilot-instructions.md")
545
+ content = content.replace(".claude/", ".github/")
546
+ content = content.replace(".codex/", ".github/")
547
+ content = content.replace(".opencode/", ".github/")
548
+ # Replace AGENTS.md → copilot-instructions.md
549
+ content = content.replace("AGENTS.md", "copilot-instructions.md")
550
+ content = content.replace("CLAUDE.md", "copilot-instructions.md")
551
+ return content
552
+
553
+
554
+ def _replace_slash_commands(content):
555
+ """Adapt slash command references for Copilot format.
556
+
557
+ Copilot uses / prefix natively for slash commands (same as Claude Code).
558
+ Replace $ prefix (Codex style) back to / if present.
559
+ Also replace CLI-style pactkit commands with script paths.
560
+ """
561
+ # Codex uses $ prefix; Copilot uses / prefix (same as Claude Code)
562
+ content = re.sub(r'`\$project-', '`/project-', content)
563
+ content = re.sub(r'"\$project-', '"/project-', content)
564
+ content = re.sub(r"'\$project-", "'/project-", content)
565
+
566
+ # Replace pactkit CLI references with script paths for project-level deployment
567
+ _skills_base = ".github/skills"
568
+ viz_script = f"python3 {_skills_base}/pactkit-visualize/scripts/visualize.py"
569
+
570
+ content = content.replace("Run `pactkit visualize --lazy`",
571
+ f"Run `{viz_script}` (file, `--mode class`, `--mode call` if source changed)")
572
+ content = content.replace("Run `pactkit visualize", f"Run `{viz_script}")
573
+ content = content.replace("Run `visualize --focus", f"Run `{viz_script} --focus")
574
+ content = content.replace("Run `visualize --mode", f"Run `{viz_script} --mode")
575
+ content = content.replace("Run `visualize`", f"Run `{viz_script}`")
576
+ content = content.replace("Run `pactkit clean`",
577
+ "Run language-specific cleanup (e.g., `find . -name '__pycache__' -exec rm -rf {} +` for Python)")
578
+
579
+ # Handle backtick-wrapped bare visualize references
580
+ content = re.sub(
581
+ r'`visualize (--(?:focus|mode|entry))',
582
+ rf'`{viz_script} \1',
583
+ content,
584
+ )
585
+
586
+ return content
587
+
588
+
589
+ # Auto-register when this module is imported
590
+ register_deployer("copilot", CopilotDeployer, force=True)
@@ -0,0 +1,63 @@
1
+ Metadata-Version: 2.4
2
+ Name: pactkit-copilot
3
+ Version: 2.9.4
4
+ Summary: PactKit adapter for GitHub Copilot
5
+ Author: Slim
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/pactkit/pactkit-copilot
8
+ Project-URL: Repository, https://github.com/pactkit/pactkit-copilot
9
+ Project-URL: Documentation, https://github.com/pactkit/pactkit-copilot#readme
10
+ Project-URL: Issues, https://github.com/pactkit/pactkit-copilot/issues
11
+ Keywords: ai,agent,copilot,github,github-copilot,code-assistant,code-quality,developer-tools,spec-driven,devops,scaffold,tdd,pdca
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Topic :: Software Development :: Build Tools
20
+ Classifier: Topic :: Software Development :: Quality Assurance
21
+ Requires-Python: >=3.10
22
+ Description-Content-Type: text/markdown
23
+ Requires-Dist: pactkit>=2.9.4
24
+
25
+ # pactkit-copilot
26
+
27
+ PactKit adapter for GitHub Copilot — project-level deployment to `.github/`.
28
+
29
+ ## Install
30
+
31
+ ```bash
32
+ pip install pactkit pactkit-copilot
33
+ ```
34
+
35
+ ## Usage
36
+
37
+ ```bash
38
+ # Deploy PactKit to a project for GitHub Copilot
39
+ cd your-project
40
+ pactkit init --format copilot
41
+
42
+ # Then open the project in your IDE and use /project-init
43
+ ```
44
+
45
+ ## What it deploys
46
+
47
+ | Asset | Location |
48
+ |-------|----------|
49
+ | Skills | `.github/skills/{name}/SKILL.md` |
50
+ | Agents | `.github/agents/{name}.md` |
51
+ | Commands | `.github/prompts/{name}.prompt.md` |
52
+ | Instructions | `.github/copilot-instructions.md` |
53
+
54
+ ## Key differences from other adapters
55
+
56
+ - **Project-level only**: Everything goes into `.github/` (no user-level `~/` directory)
57
+ - **Per-project init**: Each new project requires `pactkit init --format copilot`
58
+ - **Rules inlined**: Copilot doesn't support `@include`, so rules are inlined into command prompts
59
+ - **Two-step bootstrap**: `pactkit init` (terminal) → `/project-init` (IDE)
60
+
61
+ ## License
62
+
63
+ MIT
@@ -0,0 +1,10 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/pactkit_copilot/__init__.py
4
+ src/pactkit_copilot/deployer.py
5
+ src/pactkit_copilot.egg-info/PKG-INFO
6
+ src/pactkit_copilot.egg-info/SOURCES.txt
7
+ src/pactkit_copilot.egg-info/dependency_links.txt
8
+ src/pactkit_copilot.egg-info/entry_points.txt
9
+ src/pactkit_copilot.egg-info/requires.txt
10
+ src/pactkit_copilot.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ [pactkit.deployers]
2
+ copilot = pactkit_copilot:CopilotDeployer
@@ -0,0 +1 @@
1
+ pactkit>=2.9.4
@@ -0,0 +1 @@
1
+ pactkit_copilot