---
name: skill-to-kg
description: >
  (Who) Claude Code or any user.
  (What) Archive all non-core skills from {project_root}/.claude/skills/ to .gem-squared/external-skills/.
  (When) 1. At init-session (automatically), 2. After user creates/extracts a project-specific skill, 3. When user wants to restore a specific skill.
  (Where) {project_root}/.claude/skills/ → {project_root}/.gem-squared/external-skills/ (archive). Reverse for restore.
  (Why) In TPMN mode, only the 12 core lifecycle skills and the project identity skill belong in .claude/skills/. Other skills become CONTRACTs — searchable via /search-kg, not active triggers.
argument-hint: "[archive (default)|restore <skill-name>|list]"
metadata:
  author: David Seo of GEM².AI
  version: 2.2.0
allowed-tools:
  - Read
  - Glob
  - Bash(mv *)
  - Bash(mkdir *)
  - Bash(ls *)
  - Bash(date *)
---

(* TPMN SKILL — skill-to-kg *)

(* === Protected — never archived === *)
CORE_SKILLS ≜ {
  "archive-work", "check-session", "end-session", "extract-skill",
  "init-session", "plan-work", "proceed-work", "search-kg",
  "search-skill", "skill-to-kg", "update-work-plan", "verify-work"
}  (* 12 official TPMN lifecycle skills *)

PROJECT_SKILL ≜ {project_slug}
(* The project identity skill bound by CLAUDE.md — always protected alongside CORE_SKILLS *)

PROTECTED ≜ CORE_SKILLS ∪ {PROJECT_SKILL}

(* === Input === *)
A ≜ [
  operation: {archive, restore, list}?,  (* ⊥ = archive (default). archive = batch move ALL non-core out *)
  skill_name: 𝕊?,                     (* required for restore only. ⊥ for archive/list *)
  project_slug: 𝕊
]

(* === Output === *)
B ≜ [
  project_slug: 𝕊,
  operation: {archive, restore, list},
  moved_skills: Seq(𝕊)?,              (* names of skills/dirs moved out — archive only *)
  moved_count: ℕ?,                     (* |moved_skills| — archive only. 0 = nothing to archive *)
  restored_skill: 𝕊?,                 (* name restored — restore only *)
  source_path: Path?,                  (* restore: moved FROM *)
  dest_path: Path?,                    (* restore: moved TO *)
  archived_skills: Seq([               (* list: contents of external-skills/ *)
    name: 𝕊,
    path: Path,
    has_skill_md: 𝔹
  ])?,
  active_non_core: Seq(𝕊)?,           (* list: non-core items still in .claude/skills/ *)
  completed_at: 𝕊                     (* ISO8601 *)
]

(* === Precondition === *)
P ≜ project_slug ≠ ⊥
    ∧ (operation = restore ⟹ skill_name ≠ ⊥)
    ∧ (operation = restore ⟹ ".gem-squared/external-skills/{skill_name}/" exists)

(* === Transform === *)
F ≜ <<
  0. Resolve default operation:
       IF operation = ⊥ → operation ≜ archive.
       (* No args = archive. User must explicitly say "restore <name>" or "list". *)

  1. Ensure archive directory exists:
       mkdir -p {project_root}/.gem-squared/external-skills/

  2. Execute operation:
       IF operation = archive:
         Glob {project_root}/.claude/skills/*/ → all_dirs.
         (* Globs ALL subdirectories, not just SKILL.md holders — catches agents/, etc. *)
         candidates ≜ [d | d ∈ all_dirs, d.name ∉ PROTECTED]
         IF |candidates| = 0 → skip (nothing to archive), output B with moved_count=0.
         ELSE →
           (* MANDATORY USER NOTIFICATION — print BEFORE moving anything *)
           Print to user:
             "⚠ TPMN Skill Hygiene — /skill-to-kg archive
              Moving {|candidates|} non-core item(s) from .claude/skills/ to .gem-squared/external-skills/:
                {list each candidate name}
              ✓ NOT deleted — moved to .gem-squared/external-skills/
              ✓ Still searchable via /search-skill and /search-kg
              ✓ Restore anytime: /skill-to-kg restore <name>"
           moved_skills ≜ []
           FOR each dir in candidates:
             IF .gem-squared/external-skills/{dir.name}/ exists → skip, log conflict.
             ELSE → mv .claude/skills/{dir.name}/ → .gem-squared/external-skills/{dir.name}/
             Append dir.name to moved_skills.
           moved_count ≜ |moved_skills|.

       IF operation = restore:
         Verify .gem-squared/external-skills/{skill_name}/ exists.
         IF .claude/skills/{skill_name}/ already exists → STOP, report conflict.
         mv .gem-squared/external-skills/{skill_name}/ → .claude/skills/{skill_name}/
         Record restored_skill, source_path, dest_path.

       IF operation = list:
         Glob .gem-squared/external-skills/*/ → archived_skills (check has_skill_md per dir).
         Glob .claude/skills/*/ → all_active.
         active_non_core ≜ [d.name | d ∈ all_active, d.name ∉ PROTECTED].

  3. Record completed_at timestamp. Output B.
>>

(* === Constraint === *)
CONSTRAINT ≜ [
  ⊢ NEVER move PROTECTED (CORE_SKILLS ∪ {PROJECT_SKILL}) — permanently active,
  ⊢ NEVER delete skills — move only (bidirectional, reversible),
  ⊢ NEVER modify skill content — move directory as-is,
  ⊢ NEVER overwrite existing directory at destination — skip with conflict log,
  ⊢ NEVER touch ~/.claude/skills/ (global) — project-local {project_root}/.claude/skills/ only,
  ⊢ archive is BATCH — all non-protected directories moved in one invocation, not one by one,
  ⊢ Default operation is archive — no args means archive, not list or restore,
  ⊢ ALWAYS notify user before archive — list skill names, explain they are NOT deleted, show restore command
]

(* === Invariant === *)
INV ≜ [
  ⊢ After archive: {project_root}/.claude/skills/ contains ONLY PROTECTED items — zero non-protected,
  ⊢ A skill/dir exists in EITHER .claude/skills/ OR .gem-squared/external-skills/, never both,
  ⊢ Archived items retain full directory structure (SKILL.md, references/, scripts/, etc.),
  ⊢ /search-skill finds both active AND archived skills (with location field),
  ⊢ Archived skills are CONTRACTs — searchable via /search-kg, not active triggers,
  ⊢ CORE_SKILLS (12) are never archivable — hard-coded protection,
  ⊢ PROJECT_SKILL ({project_slug}) is never archivable — project identity protection,
  ⊢ Non-SKILL.md directories (e.g., agents/) are archived like any other non-protected item,
  ⊢ MANDATE BOUNDARY: skill location management only — never skill content modification
]

(* === Post-Execution Routing === *)
Routing ≜ [
  operation = archive → report: "{moved_count} non-protected items archived. PROTECTED ({|PROTECTED|}) remain.",
  operation = restore → report: "{skill_name} restored to .claude/skills/.",
  operation = list    → display: protected | non-protected active | archived
]
