mirror of
https://github.com/wshobson/agents
synced 2026-06-21 14:13:58 +00:00
feat: generate copilot artifacts directly to ~/.copilot/ instead of .github/
- CopilotAdapter now writes to ~/.copilot/agents/ and ~/.copilot/skills/ - Updated generate.py to pass ~/.copilot/ as output_root for copilot - Added copilot stale artifact detection to doc_gardener.py - Updated validate_generated.py to check ~/.copilot/agents/ (with fallback) - Removed old copilot gitignore entries and cleaned up stale artifacts - No install step needed — make generate HARNESS=copilot writes directly
This commit is contained in:
+1
-5
@@ -28,10 +28,6 @@ node_modules
|
||||
# Generated harness artifacts (all gitignored — regenerate via `make generate HARNESS=<x>`)
|
||||
# Source-of-truth lives under plugins/. See docs/harnesses.md.
|
||||
|
||||
# Copilot
|
||||
.github/agents/
|
||||
.github/skills/
|
||||
|
||||
# Codex CLI
|
||||
.codex/
|
||||
# AGENTS.md is committed as the canonical context file (per OpenAI harness engineering).
|
||||
@@ -51,5 +47,5 @@ commands/**/*.toml
|
||||
/commands/
|
||||
/agents/
|
||||
/skills/
|
||||
# .agent.md files belong in .github/agents/ – guard Gemini output from pollution
|
||||
# Guard Gemini output from pollution (generated .agent.md files in root agents/)
|
||||
agents/*.agent.md
|
||||
|
||||
@@ -61,19 +61,21 @@ def _build_tools_list(agent_tools: list[str]) -> list[str]:
|
||||
|
||||
|
||||
class CopilotAdapter(HarnessAdapter):
|
||||
"""Emit Copilot agent profiles (.agent.md) and skills (SKILL.md) to a local output tree.
|
||||
"""Emit Copilot agent profiles (.agent.md) and skills (SKILL.md) to Copilot config dir.
|
||||
|
||||
Agents go to ``.github/agents/<plugin>__<agent>.agent.md``, skills to
|
||||
``.github/skills/<plugin>__<skill>/SKILL.md``. Tool names are rewritten from
|
||||
Claude Code CamelCase to Copilot lowercase. Model aliases are mapped to
|
||||
the GPT-5 family (same as Codex CLI).
|
||||
Agents go to ``agents/<plugin>__<agent>.agent.md``, skills to
|
||||
``skills/<plugin>__<skill>/SKILL.md`` under the Copilot config directory
|
||||
(default ``~/.copilot/``). Tool names are rewritten from Claude Code
|
||||
CamelCase to Copilot lowercase. Model aliases are mapped to the GPT-5
|
||||
family (same as Codex CLI).
|
||||
"""
|
||||
|
||||
harness_id = "copilot"
|
||||
COPILOT_CONFIG_DIR = Path.home() / ".copilot"
|
||||
|
||||
def __init__(self, output_root: Path | None = None, repo_root: Path | None = None) -> None:
|
||||
"""Set output root (defaults to WORKTREE / .github) and optional repo root."""
|
||||
super().__init__(output_root=output_root)
|
||||
"""Set output root (defaults to ~/.copilot) and optional repo root."""
|
||||
super().__init__(output_root=output_root or self.COPILOT_CONFIG_DIR)
|
||||
if repo_root is not None:
|
||||
self.repo_root = repo_root
|
||||
|
||||
@@ -97,7 +99,7 @@ class CopilotAdapter(HarnessAdapter):
|
||||
names, and resolves model aliases before writing.
|
||||
"""
|
||||
agent_id = f"{plugin.name}__{agent.name}"
|
||||
rel = Path(".github") / "agents" / f"{agent_id}.agent.md"
|
||||
rel = Path("agents") / f"{agent_id}.agent.md"
|
||||
|
||||
model, warning = resolve_model("copilot", agent.model)
|
||||
if warning:
|
||||
@@ -125,7 +127,7 @@ class CopilotAdapter(HarnessAdapter):
|
||||
phrases) and body verbatim, wrapped in YAML frontmatter.
|
||||
"""
|
||||
skill_id = f"{plugin.name}__{skill.name}"
|
||||
skill_dir = Path(".github") / "skills" / skill_id
|
||||
skill_dir = Path("skills") / skill_id
|
||||
|
||||
content = _copilot_frontmatter(skill.frontmatter) + "\n\n" + skill.body.rstrip() + "\n"
|
||||
result.written.append(self.write(skill_dir / "SKILL.md", content))
|
||||
|
||||
@@ -215,6 +215,28 @@ def check_stale_artifacts(report: Report) -> None:
|
||||
if src.is_file():
|
||||
pairs.append((src, toml_path))
|
||||
|
||||
# Copilot agents (.agent.md) and skills (SKILL.md) at ~/.copilot/
|
||||
copilot_root = Path.home() / ".copilot"
|
||||
copilot_agents = copilot_root / "agents"
|
||||
if copilot_agents.is_dir():
|
||||
for agent_md in copilot_agents.glob("*.agent.md"):
|
||||
name = agent_md.stem
|
||||
if "__" in name:
|
||||
plugin, agent = name.split("__", 1)
|
||||
src = PLUGINS_DIR / plugin / "agents" / f"{agent}.md"
|
||||
if src.is_file():
|
||||
pairs.append((src, agent_md))
|
||||
|
||||
copilot_skills = copilot_root / "skills"
|
||||
if copilot_skills.is_dir():
|
||||
for skill_md in copilot_skills.glob("*/SKILL.md"):
|
||||
name = skill_md.parent.name
|
||||
if "__" in name:
|
||||
plugin, leaf = name.split("__", 1)
|
||||
src = PLUGINS_DIR / plugin / "skills" / leaf / "SKILL.md"
|
||||
if src.is_file():
|
||||
pairs.append((src, skill_md))
|
||||
|
||||
for src, gen in pairs:
|
||||
if src.stat().st_mtime > gen.stat().st_mtime + 1: # 1s grace
|
||||
# Derive the plugin name correctly regardless of source layout.
|
||||
|
||||
+3
-9
@@ -34,7 +34,8 @@ _HARNESS_TARGETS = {
|
||||
"cursor": [".cursor", ".cursor-plugin"],
|
||||
"opencode": [".opencode", "opencode.json"],
|
||||
"gemini": ["commands", "agents", "skills"],
|
||||
"copilot": [".github/agents", ".github/skills"],
|
||||
# Copilot writes directly to ~/.copilot/ — no repo-local output to clean.
|
||||
"copilot": [],
|
||||
}
|
||||
|
||||
|
||||
@@ -59,7 +60,7 @@ def get_adapter(harness_id: str, output_root: Path) -> HarnessAdapter:
|
||||
if harness_id == "copilot":
|
||||
from tools.adapters.copilot import CopilotAdapter
|
||||
|
||||
return CopilotAdapter(output_root=output_root)
|
||||
return CopilotAdapter(output_root=output_root or Path.home() / ".copilot")
|
||||
raise ValueError(f"Unknown harness: {harness_id}. Supported: {supported_harnesses()}")
|
||||
|
||||
|
||||
@@ -160,13 +161,6 @@ def prune_orphans(harness_id: str, output_root: Path, written: set[Path]) -> lis
|
||||
d = output_root / sub
|
||||
if d.is_dir():
|
||||
candidates.extend(p for p in d.rglob("*") if p.is_file())
|
||||
elif harness_id == "copilot":
|
||||
for sub_path in (
|
||||
output_root / ".github" / "agents",
|
||||
output_root / ".github" / "skills",
|
||||
):
|
||||
if sub_path.is_dir():
|
||||
candidates.extend(p for p in sub_path.rglob("*") if p.is_file())
|
||||
elif harness_id == "cursor":
|
||||
# Both .cursor-plugin/plugins/*.json and .cursor/rules/*.mdc are adapter outputs.
|
||||
for sub_path in (
|
||||
|
||||
@@ -559,12 +559,12 @@ def validate_gemini(report: Report) -> None:
|
||||
|
||||
|
||||
def validate_copilot(report: Report) -> None:
|
||||
"""Validate Copilot agent markdown files under WORKTREE/.github/agents.
|
||||
"""Validate Copilot agent markdown files under ~/.copilot/agents.
|
||||
|
||||
Checks that every .agent.md file has valid frontmatter with required fields.
|
||||
Also checks WORKTREE/.github/ as a fallback for local dev / test compatibility.
|
||||
"""
|
||||
# Validate repository-level agents (WORKTREE/.github/agents).
|
||||
candidate_roots = [WORKTREE / ".github"]
|
||||
candidate_roots = [Path.home() / ".copilot", WORKTREE / ".github"]
|
||||
found_any = False
|
||||
for root in candidate_roots:
|
||||
agents_dir = root / "agents"
|
||||
|
||||
Reference in New Issue
Block a user