安装项目 AGENTS 指南

This commit is contained in:
wxm
2026-05-18 19:21:55 -07:00
parent 8cd173889c
commit 0c4227cba6
7 changed files with 240 additions and 23 deletions

View File

@@ -4,9 +4,11 @@
Current runtime scope:
- Copy bundled Codex custom agents from this plugin's `runtime/agents/`
directory into the target project's `.codex/agents/` directory.
- Copy the Codex project guide template from `project-template/AGENTS.md`
into the target project's root `AGENTS.md`.
This installer intentionally does not install hooks, rules, MCP servers, apps,
or AGENTS.md templates yet.
This installer intentionally does not install hooks, rules, MCP servers, or apps
yet.
"""
from __future__ import annotations
@@ -31,6 +33,7 @@ def find_plugin_root(start: Path) -> Path:
PLUGIN_ROOT = find_plugin_root(Path(__file__).resolve())
SOURCE_AGENTS_DIR = PLUGIN_ROOT / "runtime" / "agents"
PROJECT_AGENTS_TEMPLATE = PLUGIN_ROOT / "project-template" / "AGENTS.md"
@dataclass(frozen=True)
@@ -96,6 +99,26 @@ def install_agents(target: Path, *, force: bool, dry_run: bool) -> InstallResult
return InstallResult(installed=installed, unchanged=unchanged, conflicts=conflicts)
def install_project_guide(
target: Path, *, force: bool, dry_run: bool
) -> InstallResult:
if not PROJECT_AGENTS_TEMPLATE.is_file():
raise FileNotFoundError(f"Missing project guide template: {PROJECT_AGENTS_TEMPLATE}")
destination = target / "AGENTS.md"
if destination.exists():
if filecmp.cmp(PROJECT_AGENTS_TEMPLATE, destination, shallow=False):
return InstallResult(installed=[], unchanged=[destination], conflicts=[])
if not force:
return InstallResult(installed=[], unchanged=[], conflicts=[destination])
if not dry_run:
shutil.copy2(PROJECT_AGENTS_TEMPLATE, destination)
return InstallResult(installed=[destination], unchanged=[], conflicts=[])
def print_path_list(label: str, paths: list[Path], target: Path) -> None:
if not paths:
return
@@ -117,6 +140,11 @@ def parse_args() -> argparse.Namespace:
action="store_true",
help="Overwrite existing agent files when their content differs.",
)
parser.add_argument(
"--force-agents-md",
action="store_true",
help="Overwrite an existing target AGENTS.md when its content differs.",
)
parser.add_argument(
"--dry-run",
action="store_true",
@@ -132,25 +160,44 @@ def main() -> int:
print("Codex Game Studios runtime installer")
print(f"Plugin root: {PLUGIN_ROOT}")
print(f"Target project: {target}")
print("Runtime scope: custom agents only")
print("Runtime scope: custom agents plus project AGENTS.md")
try:
result = install_agents(target, force=args.force, dry_run=args.dry_run)
agent_result = install_agents(target, force=args.force, dry_run=True)
guide_result = install_project_guide(
target, force=args.force_agents_md, dry_run=True
)
except FileNotFoundError as error:
print(f"ERROR: {error}", file=sys.stderr)
return 1
print_path_list("Installed" if not args.dry_run else "Would install", result.installed, target)
print_path_list("Unchanged", result.unchanged, target)
print_path_list("Conflicts", result.conflicts, target)
print_path_list("Agent conflicts", agent_result.conflicts, target)
print_path_list("Project guide conflicts", guide_result.conflicts, target)
if result.conflicts:
print(
"ERROR: Existing agent files differ. Re-run with --force to overwrite them.",
file=sys.stderr,
)
if agent_result.conflicts or guide_result.conflicts:
if agent_result.conflicts:
print(
"ERROR: Existing agent files differ. Re-run with --force to overwrite them.",
file=sys.stderr,
)
if guide_result.conflicts:
print(
"ERROR: Target AGENTS.md differs. Re-run with --force-agents-md to overwrite it.",
file=sys.stderr,
)
return 2
if not args.dry_run:
agent_result = install_agents(target, force=args.force, dry_run=False)
guide_result = install_project_guide(
target, force=args.force_agents_md, dry_run=False
)
print_path_list("Installed agents" if not args.dry_run else "Would install agents", agent_result.installed, target)
print_path_list("Unchanged agents", agent_result.unchanged, target)
print_path_list("Installed project guide" if not args.dry_run else "Would install project guide", guide_result.installed, target)
print_path_list("Unchanged project guide", guide_result.unchanged, target)
print("Done.")
return 0

View File

@@ -101,6 +101,41 @@ def assert_runtime_agents() -> None:
fail(f"Missing runtime agents: {', '.join(missing)}")
def assert_project_template() -> None:
template_path = ROOT / "project-template" / "AGENTS.md"
if not template_path.is_file():
fail("Missing project-template/AGENTS.md")
text = template_path.read_text(encoding="utf-8")
required_fragments = [
"Codex Game Studios Project Guide",
"production/review-mode.txt",
".codex/agents/",
"design/gdd/game-concept.md",
"design/gdd/game-pillars.md",
]
for fragment in required_fragments:
if fragment not in text:
fail(f"project-template/AGENTS.md missing required content: {fragment}")
def assert_runtime_installer() -> None:
installer_path = ROOT / "scripts" / "install_codex_runtime.py"
if not installer_path.is_file():
fail("Missing scripts/install_codex_runtime.py")
text = installer_path.read_text(encoding="utf-8")
required_fragments = [
"PROJECT_AGENTS_TEMPLATE",
"project-template",
"AGENTS.md",
"--force-agents-md",
]
for fragment in required_fragments:
if fragment not in text:
fail(f"install_codex_runtime.py missing required content: {fragment}")
def assert_package_hygiene() -> None:
try:
tracked = run_git(["ls-files"])
@@ -129,6 +164,8 @@ def main() -> int:
for skill_name in sorted(EXPECTED_SKILLS):
assert_skill(skill_name)
assert_runtime_agents()
assert_project_template()
assert_runtime_installer()
assert_package_hygiene()
print("Codex Game Studios plugin validation passed.")
return 0