feat(build.py): --no-purge flag and opencode-archive install target

Conflict fix: --install opencode wipes ~/.config/opencode/skills/ on every
build, which clobbers the work of external managers like opc-skills. New flag
--no-purge skips that wipe so opc-skills' enabled set survives a rebuild.

New install target opencode-archive: instead of writing to ~/.config/opencode/,
it regenerates personas/skills-archive/ + personas/agents-opencode-archive/
(the source-of-truth that opc-skills + opc-agents consume). This lets the
build pipeline stay decoupled from runtime install dirs.

install_opencode() now accepts agents_dest / skills_dest overrides for the
archive target. Live target behavior unchanged.
This commit is contained in:
salva
2026-04-30 23:47:07 +03:00
parent 00dc88bf5f
commit 9cc04606c0

View File

@@ -1513,6 +1513,9 @@ def install_opencode(
output_dir: Path,
shared_dir: Path | None = None,
topics: set[str] | None = None,
purge_skills: bool = True,
agents_dest: Path | None = None,
skills_dest: Path | None = None,
):
"""Install personas to OpenCode as agents + skills.
@@ -1529,11 +1532,26 @@ def install_opencode(
Args:
topics: set of topics to install (see OPENCODE_TOPICS). Defaults to
OPENCODE_DEFAULT_TOPICS which drops marketing/biz skills.
purge_skills: when True (default), wipe ~/.config/opencode/skills/
before installing. Set False (via --no-purge) when this dir is also
managed by an external tool (e.g. opc-skills) — purge would otherwise
delete its work on every build.
"""
if topics is None:
topics = OPENCODE_DEFAULT_TOPICS
agents_dir = Path.home() / ".config" / "opencode" / "agents"
skills_dir = Path.home() / ".config" / "opencode" / "skills"
# Archive mode: agents_dest / skills_dest override the live opencode dirs.
# Used by --install opencode-archive to regenerate the personas archive
# that opc-skills and opc-agents consume as source-of-truth.
if agents_dest is not None:
agents_dir = agents_dest
agents_dir.mkdir(parents=True, exist_ok=True)
else:
agents_dir = Path.home() / ".config" / "opencode" / "agents"
if skills_dest is not None:
skills_dir = skills_dest
skills_dir.mkdir(parents=True, exist_ok=True)
else:
skills_dir = Path.home() / ".config" / "opencode" / "skills"
agents_dir.mkdir(parents=True, exist_ok=True)
skills_dir.mkdir(parents=True, exist_ok=True)
@@ -1719,12 +1737,16 @@ def install_opencode(
skipped_topic = 0
# Purge existing skills dir so stale filtered-out skills are removed.
if skills_dir.exists():
# Skipped when --no-purge is passed (so external tools like opc-skills
# that manage this directory don't get clobbered on every build).
if purge_skills and skills_dir.exists():
import shutil as _shutil
for existing in skills_dir.iterdir():
if existing.is_dir():
_shutil.rmtree(existing)
elif not purge_skills:
print(f" OpenCode: --no-purge — preserving existing {skills_dir}/")
if shared_dir:
for skills_subdir in [
@@ -2107,12 +2129,16 @@ def main():
"gemini",
"openclaw",
"opencode",
"opencode-archive",
"paperclip",
"all",
],
help="Install generated personas to a target platform. "
"'claude' installs persona agents+commands; 'claude-skills' installs "
"shared skills to ~/.claude/skills/ with category filters.",
"shared skills to ~/.claude/skills/ with category filters; "
"'opencode-archive' writes opencode-format agents+skills to "
"personas/agents-opencode-archive/ + personas/skills-archive/ "
"(consumed by opc-skills/opc-agents) without touching ~/.config/opencode/.",
)
# --- claude-skills filters --------------------------------------------
parser.add_argument(
@@ -2171,6 +2197,13 @@ def main():
"business-pm, uncategorized. "
"Default drops marketing/biz. Use 'all' for no filter.",
)
parser.add_argument(
"--no-purge",
action="store_true",
help="For --install opencode: do NOT wipe ~/.config/opencode/skills/ "
"before installing. Use when the directory is also managed by an "
"external tool (e.g. opc-skills) so its enabled skills are preserved.",
)
parser.add_argument(
"--search",
type=str,
@@ -2325,7 +2358,7 @@ def main():
install_gemini(output_dir)
elif target == "openclaw":
install_openclaw(output_dir)
elif target == "opencode":
elif target in ("opencode", "opencode-archive"):
if args.opencode_topics:
if args.opencode_topics.strip().lower() == "all":
oc_topics = OPENCODE_TOPICS
@@ -2337,7 +2370,29 @@ def main():
}
else:
oc_topics = None # use default
install_opencode(output_dir, shared_dir, topics=oc_topics)
if target == "opencode-archive":
# Source-of-truth layout consumed by opc-skills + opc-agents:
# personas/skills-archive/<name>/SKILL.md ← opc-skills reads
# personas/agents-opencode-archive/<n>.md ← opc-agents reads
install_opencode(
output_dir,
shared_dir,
topics=oc_topics,
purge_skills=False,
agents_dest=root / "agents-opencode-archive",
skills_dest=root / "skills-archive",
)
print(
" opcode-archive: regenerated source-of-truth used by "
"opc-skills + opc-agents. Did NOT touch ~/.config/opencode/."
)
else:
install_opencode(
output_dir,
shared_dir,
topics=oc_topics,
purge_skills=not args.no_purge,
)
elif target == "paperclip":
install_paperclip(output_dir, personas_dir, shared_dir)