with one click
(hook)
npx skills add https://github.com/aniboy2k-gif/skill-advisor --skill skill-advisorCopy and paste this command into Claude Code to install the skill
(hook)
npx skills add https://github.com/aniboy2k-gif/skill-advisor --skill skill-advisorCopy and paste this command into Claude Code to install the skill
| name | skill-advisor |
| description | (hook) |
| argument-hint | [--scan] | --scan --json | --enrich <skill-name> | --session-review [--confirm | --json | --jsonl <path>] | --update [<skill>] [--apply] [--yes] [--dry-run] [--json] [--keep-backup] [--no-listing-audit] | --listing-audit [--apply] [--json] |
์ค์น๋ ์คํฌ ์ปค๋ฒ๋ฆฌ์ง ์ง๋จ + ํ์ฉ๋ฒ ์๋ด ๋๊ตฌ. read-only ์์น โ SKILL.md๋ฅผ ์ง์ ์์ ํ์ง ์๋๋ค.
ARGUMENTS๊ฐ ๋น์ด ์์ผ๋ฉด ์๋ ๋ฉ์์ง๋ฅผ ์ถ๋ ฅํ ํ ๊ธฐ๋ณธ ๋์(--scan)์ ์คํํ๋ค.
/skill-advisor [--scan] | --scan --json | --enrich <skill-name> | --session-review [--confirm | --json | --jsonl <path>] | --update [<skill>] [--apply] [--yes] [--json]
Options:
(์์ / --scan) ์ค์น๋ ์ ์ฒด ์คํฌ ์ปค๋ฒ๋ฆฌ์ง ์ง๋จ (๊ธฐ๋ณธ)
--scan --json JSON ํ์์ผ๋ก ์ง๋จ ๊ฒฐ๊ณผ ์ถ๋ ฅ (CI/์๋ํ์ฉ)
--enrich <skill-name> ํน์ ์คํฌ ์ฌ์ธต ๋ถ์ + SkillPatchProposal JSON ์ถ๋ ฅ
--session-review ์์
ํ ํธ์ง ํ์ผ ๊ธฐ๋ฐ ๊ด๋ จ ์คํฌ ํ๋ณด ์ถ์ฒ
--session-review --confirm JSONL ์ธ์
์ง์ ์ ํ (โ ๋ํํ ํฐ๋ฏธ๋ ์ ์ฉ)
--session-review --json JSON ํ์์ผ๋ก session-review ๊ฒฐ๊ณผ ์ถ๋ ฅ
--session-review --jsonl <path> ํน์ JSONL ํ์ผ ๋ถ์ (์ ๋ ์ฐ์ ์์ โ Track1 ์๋ detection skip)
--session-scan alias for --session-review (CSR #829 #2, 2026-05-24)
--update [<skill>] ์
์คํธ๋ฆผ URL ๊ธฐ๋ฐ ์
๋ฐ์ดํธ ์ฒดํฌ (์ ์ฒด ๋๋ ์ง์ ์คํฌ)
--update --apply [<skill>] ์
๋ฐ์ดํธ ์ค์ ์ ์ฉ (trigger_conditions ์๋ ๋ณด์กด)
--update --apply --yes ์๋ ์งํ ๋ชจ๋ (y/N ํ์ธ ์๋ต, CI ์ ์ฉ)
--update --json JSON ์ถ๋ ฅ (CI/์๋ํ์ฉ)
--update --keep-backup apply ํ .bak ํ์ผ ๋ณด์กด
--update --refresh-sources skill-sources.json ์ฌ์์ฑ ์๋ด
--update --no-listing-audit apply ํ ์๋ listing-audit ์คํ ์ต์
--listing-audit skillListing ์์ฐ ์ง๋จ (shortened ์์ธก + hook ์ต์ ํ ํ๋ณด)
--listing-audit --apply hook ์คํฌ description "(hook)"์ผ๋ก ์์์ ์์
--listing-audit --json JSON ํ์์ผ๋ก ์ง๋จ ๊ฒฐ๊ณผ ์ถ๋ ฅ
ARGUMENTS๊ฐ ์์ผ๋ฉด ํด๋น ๋ชจ๋์ ๋ง๋ ํค๋๋ฅผ ํ ์ค ์ถ๋ ฅํ ๋ค ์คํํ๋ค:
| ARGUMENTS | ํค๋ ์ถ๋ ฅ |
|---|---|
์์ / --scan | โถ /skill-advisor --scan (์ ์ฒด ์คํฌ ์ปค๋ฒ๋ฆฌ์ง ์ง๋จ) |
--scan --json | โถ /skill-advisor --scan --json (JSON ์ถ๋ ฅ) |
--enrich <name> | โถ /skill-advisor --enrich <name> (์ฌ์ธต ๋ถ์ + SkillPatchProposal) |
--session-review | โถ /skill-advisor --session-review (ํธ์ง ํ์ผ ๊ธฐ๋ฐ ์คํฌ ํ๋ณด ์ถ์ฒ) |
--session-review --confirm | โถ /skill-advisor --session-review --confirm (์ธ์
์ ํ ํ ๋ถ์) |
--session-review --json | โถ /skill-advisor --session-review --json (JSON ์ถ๋ ฅ) |
--session-review --jsonl <path> | โถ /skill-advisor --session-review --jsonl <path> (์ง์ ์ธ์
๋ถ์) |
--update [<skill>] | โถ /skill-advisor --update (์
์คํธ๋ฆผ ์
๋ฐ์ดํธ ์ฒดํฌ ๋ฐ ์ ์ฉ) |
--update --apply | โถ /skill-advisor --update --apply (์
๋ฐ์ดํธ ์ ์ฉ โ y/N ํ์ธ, ์๋ฃ ํ listing-audit --apply ์๋ ์คํ) |
--update --apply --yes | โถ /skill-advisor --update --apply --yes (์๋ ์งํ ๋ชจ๋, ์๋ฃ ํ listing-audit --apply ์๋ ์คํ) |
--listing-audit | โถ /skill-advisor --listing-audit (skillListing ์์ฐ ์ง๋จ) |
--listing-audit --apply | โถ /skill-advisor --listing-audit --apply (hook ์คํฌ description ์ต์ ํ โ y/N ํ์ธ) |
/skill-advisor โ ์ ์ฒด ์คํฌ ํํฉ ๋ณด๊ณ (--scan ๊ธฐ๋ณธ)
/skill-advisor --scan โ ์ปค๋ฒ๋ฆฌ์ง ์ง๋จ (severity ๋ถ๋ฅ)
/skill-advisor --scan --json โ JSON ํ์์ผ๋ก ์ถ๋ ฅ
/skill-advisor --enrich [์คํฌ๋ช
] โ ํน์ ์คํฌ ๋ถ์ + SkillPatchProposal dry-run ์ถ๋ ฅ
/skill-advisor --session-review โ ์์
ํ ํธ์ง ํ์ผ ๊ธฐ๋ฐ ๊ด๋ จ ์คํฌ ํ๋ณด ์ถ์ฒ
| ์ญํ | ์คํฌ | ์ค๋ช |
|---|---|---|
| ์์ฑยทํธ์ง | skill-creator | SKILL.md ์ ๊ท ์์ฑยท์์ |
| ์ผ๊ด ์์ฑ | skill-factory | ํ ํ๋ฆฟ ๊ธฐ๋ฐ ์คํฌ ์์ฑ |
| ๋ชฉ๋กยทCRUD | manage-skills | ์คํฌ ์ค์นยท์ญ์ ยท๊ด๋ฆฌ |
| ์ง๋จยท์๋ด | skill-advisor | ์ปค๋ฒ๋ฆฌ์ง ๋ถ์ + ํ์ฉ๋ฒ ์ ์ (read-only) |
| ํญ๋ชฉ | ์ ์ |
|---|---|
| stdin | ์์ |
| stdout | ํ ์คํธ ๋ณด๊ณ ์ (--json ์ JSON array) |
| stderr | ์คํ ์ค๋ฅ ๋ฉ์์ง |
| exit 0 | ์ฑ๊ณต (์ด์ ์์) |
| exit 1 | ์ด์/๊ฒฝ๊ณ ๋ฐ๊ฒฌ (CRITICALยทHIGHยทMEDIUM ํฌํจ) |
| exit 2 | ์คํ ์คํจ (ํ์ผ ์ ๊ทผ ์ค๋ฅ, ํ์ฑ ์ค๋ฅ ๋ฑ) |
์ค์น๋ ์ ์ฒด ์คํฌ์ ํธ๋ฆฌ๊ฑฐ ์ปค๋ฒ๋ฆฌ์ง๋ฅผ ์ง๋จํ๋ค.
๊ฒ์ถ ๊ฐ๋ฅ:
hard-gates.json์ ์ ์ธ๋ Hard Gate์ file_path๊ฐ ๋ฏธ์ค์น ์ํ (SSOT โ ์ค์น ๋ถ์ผ์น)source_kind=yaml_frontmatter๋ก๋ง ์กด์ฌ โ ์๋ ๋ก๋ ํธ๋ฆฌ๊ฑฐ ์์source_kind=yaml_frontmatter โ YAML frontmatter๋ง ์๋ ์๋ ์ ์ฉ ์คํฌ (INFO)trigger_block + file_path_globs>0 + description>100์ โ hook ์คํฌ description ๋จ์ถ ๊ถ์ฅ ("(hook)" ์ต์ ํ ํ๋ณด)๊ฒ์ถ ๋ถ๊ฐ (Phase 2 ์์ ):
*.ts vs src/**/*.ts)โ M1 ์ฃผ์: ๋์ผ ๋ฌธ์์ด ์์ ์ผ์น๋ง ๊ฐ์งํ๋ฉฐ, fnmatch ๊ฒฝ๋ก ์ค์ฒฉ ์ถฉ๋์ ๊ฐ์งํ์ง ์์ต๋๋ค. "M1 ์์ = ์ถฉ๋ ์์"์ด ์๋๋๋ค.
1๋จ๊ณ โ ์คํฌ ์ธ๋ฑ์ค ๋ก๋
SKILL_INDEX="/tmp/skill-index.json"
FALLBACK_MODE=false
if [ ! -f "$SKILL_INDEX" ]; then
echo "โ skill-index.json ์์ โ skill-index.sh ์ฌ์คํ ์๋..."
if ! zsh ~/.claude/hooks/skill-index.sh 2>/dev/null; then
echo "โ skill-index.sh ์คํจ โ ~/.claude/skills/** ์ง์ ์ค์บ์ผ๋ก fallback"
FALLBACK_MODE=true
fi
fi
fallback ๋ชจ๋: ~/.claude/skills/*/SKILL.md ์ง์ glob ์ค์บ์ผ๋ก ์ธ๋ฑ์ค ์์ด ๋ถ์ ์งํ. ์ด ๊ฒฝ์ฐ ๋ณด๊ณ ์ ์๋จ์ [์ธ๋ฑ์ค ์์ โ ์ง์ ์ค์บ] ๊ฒฝ๊ณ ํ์.
2๋จ๊ณ โ ์คํฌ ๋ถ๋ฅ + ์ฒดํฌ๋ฆฌ์คํธ ์คํ
import json, os
from pathlib import Path
data = json.load(open("/tmp/skill-index.json"))
# v1/v2 ํ์ ํธํ
if isinstance(data, list):
skills = data
elif isinstance(data, dict):
skills = data.get("skills", [])
else:
skills = []
# hard-gates.json ๋ก๋ (C2ยทC1-b ํ์ ์ฉ SSOT). ์๊ฑฐ๋ ์คํค๋ง ๋ฏธ์ง์์ด๋ฉด ๋น ์
fallback.
hard_gate_map = {} # id โ gate dict
hard_gate_ids_critical = set() # Tier A/B/C id set
hg_path = Path(os.path.expanduser("~/.claude/hard-gates.json"))
if hg_path.exists():
try:
hg = json.loads(hg_path.read_text(encoding="utf-8"))
for g in hg.get("gates", []):
gid = g.get("id")
if gid:
hard_gate_map[gid] = g
if g.get("tier") in ("A", "B", "C"):
hard_gate_ids_critical.add(gid)
except (json.JSONDecodeError, OSError):
pass
results = []
for item in skills:
skill_name = item.get("skill", "?")
globs = item.get("file_path_globs", [])
events = item.get("tool_events", [])
desc = item.get("description", "")
utterances = item.get("utterance_patterns", {})
source_kind = item.get("source_kind", "trigger_block")
has_utterances = bool(
utterances.get("ko") or utterances.get("en")
)
issues = []
# C1: ๊นจ์ง ์ ๋ ๊ฒฝ๋ก ์ฐธ์กฐ
# glob ๋ฉํ๋ฌธ์(* ? [)๊ฐ ์์ผ๋ฉด ๋ฆฌํฐ๋ด ๊ฒฝ๋ก๊ฐ ์๋๋ผ ํจํด โ glob-expand ํ
# "๋งค์นญ 0๊ฑด์ผ ๋๋ง" ๊ฒฝ๊ณ ํ๋ค. transient ํจํด(/tmp/csr-*.md ๋ฑ)์ ํ์ฌ ๋งค์นญ์ด
# ์์ด๋ ์ ์ ํธ๋ฆฌ๊ฑฐ์ด๋ฏ๋ก ๊ฒฝ๊ณ ์ ์ธ (CSR #969: C1 glob false-positive ์์ ).
import glob as _glob
for g in globs:
if not g.startswith("/"):
continue
if any(ch in g for ch in "*?["):
# ํจํด: ๋๋ ํ ๋ฆฌ(๋ถ๋ชจ)๊ฐ ์ค์ฌํ๊ณ transient(/tmp, /var/folders ๋ฑ)๋ฉด ์ ์
parent = os.path.dirname(g.split("*")[0].rstrip("/")) or "/"
is_transient = g.startswith("/tmp/") or "/T/" in g or g.startswith("/var/folders/")
if not is_transient and not _glob.glob(g) and not Path(parent).exists():
issues.append({"severity": "critical", "code": "C1",
"msg": f"๊นจ์ง glob (๋ถ๋ชจ ๊ฒฝ๋ก ๋ถ์ฌ, ๋งค์นญ 0): {g}"})
elif not Path(g).exists():
issues.append({"severity": "critical", "code": "C1",
"msg": f"๊นจ์ง ์ฐธ์กฐ: {g}"})
# C2: Hard Gate ์คํฌ์ด๋ฉด์ source_kind=yaml_frontmatter โ ์๋ ๋ก๋ ๋ถ๊ฐ
if skill_name in hard_gate_ids_critical and source_kind == "yaml_frontmatter":
gate = hard_gate_map.get(skill_name, {})
tier = gate.get("tier", "?")
issues.append({"severity": "critical", "code": "C2",
"msg": f"Hard Gate(Tier {tier}) ์คํฌ์ด yaml_frontmatter ์ ์ฉ โ ์๋ ๋ก๋ ํธ๋ฆฌ๊ฑฐ ์์, ์ ๋ช
๋ฌด์ค"})
# H1-Y: yaml_frontmatter ์คํฌ โ ์๋ ์ ์ฉ (์๋ ๋ก๋ ๋ถ๊ฐ)
if source_kind == "yaml_frontmatter":
issues.append({"severity": "info", "code": "H1-Y",
"msg": "[YAML ์๋ ์ ์ฉ] trigger_conditions ์์ โ /skill-creator๋ก ์ถ๊ฐ ๊ถ์ฅ"})
else:
# ํธ๋ฆฌ๊ฑฐ ๋ถ๋ฅ (trigger_block ์คํฌ๋ง ํด๋น)
if len(globs) == 0 and len(events) == 0:
if not has_utterances:
issues.append({"severity": "high", "code": "H0",
"msg": "๋ชจ๋ ํธ๋ฆฌ๊ฑฐ ์์ (์ ๋ น ์คํฌ) โ ํธ์ถ ๋ฐฉ๋ฒ์ด ์์ต๋๋ค"})
else:
issues.append({"severity": "info", "code": "H1-M",
"msg": "[์๋ ์ ์ฉ] utterance ๊ธฐ๋ฐ ํธ๋ฆฌ๊ฑฐ๋ง ์กด์ฌ โ ํ์ผ ๊ธฐ๋ฐ ์๋ ๋ก๋ ์์"})
elif len(globs) == 0 and len(events) > 0:
issues.append({"severity": "info", "code": "H1-E",
"msg": "[์ด๋ฒคํธ ์ ์ฉ] tool_events ๊ธฐ๋ฐ ํธ๋ฆฌ๊ฑฐ๋ง ์กด์ฌ โ ํ์ผ ๊ฒฝ๋ก ๊ธฐ๋ฐ ์๋ ๋ก๋ ์์"})
# I1: description ๋น์ด์์ ("(hook)"์ valid placeholder โ ์ ์ธ)
if not desc.strip() and desc != "(hook)":
issues.append({"severity": "info", "code": "I1",
"msg": "description ๋น์ด์์ โ ์์ธ ํ์ง ์ ํ"})
# OPT-1: trigger_block + file_path_globs>0 + description>100์ โ "(hook)" ๋จ์ถ ๊ถ์ฅ
# opt-out: SKILL.md YAML์ hook_no_opt: true ํ๋๊ทธ ์ ์ต์
# CSR #969: utterance ๋ณด์ OR critical-tier(A/B/C) Hard Gate ๋ ์ ์ธ โ
# ์ด๋ฐ ์คํฌ์ description ์ invocation ๋ผ์ฐํ
์ ํธ(ํ์ผ glob ์๋ ํธ๋ฆฌ๊ฑฐ๊ฐ
# ์๋๋ผ utterance/์๋ ํธ์ถ ์์กด)๋ผ "(hook)" ๋จ์ถ ์ discoverability ์์.
# (auto-loader ๋ critical ๋ฏธ์ฃผ์
โ glob ์๋ ํธ๋ฆฌ๊ฑฐ๋ ๋ถํ์ค, #966 ์ฐธ์กฐ)
skill_meta = item # item์ด ๊ณง meta dict
if (source_kind == "trigger_block"
and len(globs) > 0
and len(desc) > 100
and desc != "(hook)"
and not has_utterances
and skill_name not in hard_gate_ids_critical
and not skill_meta.get("hook_no_opt", False)):
issues.append({"severity": "info", "code": "OPT-1",
"msg": f"[listing ์ต์ ํ] hook ์คํฌ์ด์ง๋ง description {len(desc)}์ โ "
f"'(hook)' ๋จ์ถ ์ ์์ฐ ์ ์ฝ. ์ต์ : YAML์ hook_no_opt: true ์ถ๊ฐ"})
results.append({
"skill": skill_name,
"source_kind": source_kind,
"globs": len(globs),
"events": len(events),
"has_utterances": has_utterances,
"issues": issues
})
# C1-b: SSOT โ ์ค์น ๋ถ์ผ์น (์คํฌ๋ณ์ด ์๋ ์ ์ญ ๋จ๊ณ)
# hook ๊ตฌํ ๊ฒ์ดํธ ์ ์ธ (CSR #969: C1-b hook-gate false-positive ์์ ).
# tool_events_bash_if ์์(=hook ๊ฐ์ ) ๋๋ tier=D advisory๋ฉด SKILL.md file_path๋
# ์ธ๋ฒคํ ๋ฆฌ placeholder์ผ ์ ์์ โ ๋ฏธ์ค์น๋ฅผ critical๋ก ์คํํ์ง ์๋๋ค.
installed_skill_ids = {item.get("skill") for item in skills}
for gate_id, gate in hard_gate_map.items():
fp_raw = gate.get("file_path", "")
fp = Path(os.path.expanduser(fp_raw)) if fp_raw else None
is_hook_impl = bool(gate.get("tool_events_bash_if"))
is_advisory = gate.get("tier") == "D"
if fp and not fp.exists():
if is_hook_impl or is_advisory:
# hook/advisory ๊ฒ์ดํธ โ file_path๋ placeholder, INFO๋ก ๊ฐ๋ฑ
results.append({
"skill": f"<SSOT:{gate_id}>", "source_kind": "ssot_only",
"globs": 0, "events": 0, "has_utterances": False,
"issues": [{"severity": "info", "code": "C1-b-hook",
"msg": f"SSOT file_path placeholder (hook/advisory ๊ตฌํ): {gate_id} โ {fp_raw}"}]
})
else:
# ์ง์ง critical: A/B/C tier + ๋น-hook ๊ฒ์ดํธ๊ฐ ๋ฏธ์ค์น
results.append({
"skill": f"<SSOT:{gate_id}>", "source_kind": "ssot_only",
"globs": 0, "events": 0, "has_utterances": False,
"issues": [{"severity": "critical", "code": "C1-b",
"msg": f"SSOT ์ ์ธ Hard Gate ๋ฏธ์ค์น: {gate_id} โ {fp_raw}"}]
})
3๋จ๊ณ โ M1 ์ค๋ณต glob ๊ฐ์ง
๋ชจ๋ ์คํฌ์ glob์ ๋น๊ตํ์ฌ ๋์ผ ๋ฌธ์์ด์ 2๊ฐ ์ด์ ์คํฌ์ด ์ ์ธํ๋ฉด MEDIUM ๊ฒฝ๊ณ .
4๋จ๊ณ โ ๊ฒฐ๊ณผ ์ถ๋ ฅ
severity ์ (critical โ high โ medium โ info):
## /skill-advisor --scan ๊ฒฐ๊ณผ (2026-04-23)
โ M1 ์ฃผ์: ๋์ผ ๋ฌธ์์ด ์์ ์ผ์น๋ง ๊ฐ์ง (fnmatch ์ค์ฒฉ ๊ฐ์ง๋ Phase 2)
### ์คํฌ๋ณ ํํฉ
| ์คํฌ | globs | events | ๋ถ๋ฅ | ์ด์ |
|------|-------|--------|------|------|
| doc-coauthoring | 8 | 10 | ์ ์ | โ |
| skill-creator | 7 | 12 | ์ ์ | โ |
| skill-advisor | 0 | 0 | ์๋ ์ ์ฉ | โน H1-M |
...
### ์ด์ ๋ชฉ๋ก
[CRITICAL] skill-name: C1 ๊นจ์ง ์ฐธ์กฐ: /path/to/file
[HIGH] skill-name: H0 ์ ๋ น ์คํฌ โ ๋ชจ๋ ํธ๋ฆฌ๊ฑฐ ์์
[HIGH] skill-name: H1 file_path_globs ์์
[MEDIUM] skill-a, skill-b: M1 ๋์ผ glob "**/SKILL.md" โ 2๊ฐ ์คํฌ ์ ์ธ
[INFO] skill-name: I1 description ๋น์ด์์
| ์ฝ๋ | severity | ์๋ฏธ | ์๋ ๋ถ๋ฅ ์์ธ |
|---|---|---|---|
| C1 | critical | ์กด์ฌํ์ง ์๋ ์ ๋ ๊ฒฝ๋ก ์ฐธ์กฐ | โ |
| C1-b | critical | SSOT(hard-gates.json) ์ ์ธ Hard Gate๊ฐ ํ์ผ ์์คํ ์ ๋ฏธ์ค์น | โ |
| C2 | critical | Hard Gate(Tier A/B/C) ์คํฌ์ด source_kind=yaml_frontmatter ์ ์ฉ โ ์๋ ๋ก๋ ํธ๋ฆฌ๊ฑฐ ์์ | โ |
| H0 | high | ๋ชจ๋ ํธ๋ฆฌ๊ฑฐ ์์ (์ ๋ น ์คํฌ) | โ |
| H1-E | info | ์ด๋ฒคํธ ์ ์ฉ (tool_events>0, globs=0) | ์๋์ ์ค๊ณ๋ก ๊ฐ์ฃผ, H1-M๊ณผ ๋์ผ ์ฒ๋ฆฌ |
| H1-M | info | ์๋ ์ ์ฉ (utterance๋ง ์๊ณ globs=events=0) | skill-advisor ์์ ํฌํจ |
| H1-Y | info | YAML frontmatter ์๋ ์ ์ฉ (source_kind=yaml_frontmatter) | ์ปค๋ฎค๋ํฐ ์คํฌ ๊ธฐ๋ณธ๊ฐ โ /skill-creator๋ก trigger_conditions ์ถ๊ฐ ๊ถ์ฅ |
| M1 | medium | ๋์ผ glob ๋ฌธ์์ด ์ค๋ณต ์ ์ธ | โ |
| I1 | info | description ๋น์ด์์ | "(hook)" placeholder๋ ์ ์ธ |
| OPT-1 | info | hook ์คํฌ(trigger_block+globs) description > 100์ โ "(hook)" ๋จ์ถ ๊ถ์ฅ | YAML hook_no_opt: true ์ ์ต์ |
skillListingMaxDescCharsยทskillListingBudgetFraction ์ค์ ์ํ์ ์ค์ ์คํฌ description ๋ถํฌ๋ฅผ ์ง๋จํ์ฌ "N skill descriptions shortened" ๋ฌธ์ ๋ฅผ ์ฌ์ ํ์งํ๊ณ ์ต์ ํ ํ๋ณด๋ฅผ ์ ์ํ๋ค.
Claude Code์ ์คํฌ ๋ชฉ๋ก ์์ฐ ๊ณต์ (v2.1.142 ๋ฐ์ด๋๋ฆฌ ํ์ธ):
char_budget = (contextTokens ?? 200_000) ร 4 ร skillListingBudgetFraction
skillListingMaxDescChars=1536, skillListingBudgetFraction=0.01"N descriptions shortened": description์ด skillListingMaxDescChars๋ฅผ ์ด๊ณผ ์ ๋ฐ์"N descriptions dropped": ์ด description ํฉ์ด char_budget์ ์ด๊ณผ ์ ๋ฐ์ (๋ ์ฌ๊ฐ)hook ์คํฌ description ๊ท์ฝ ("(hook)" ํจํด):
trigger_block + file_path_globs > 0 ์คํฌ์ ํ์ผ ๊ฒฝ๋ก ๊ธฐ๋ฐ์ผ๋ก ์๋ ํธ๋ฆฌ๊ฑฐ"(hook)" (6์) ์ฌ์ฉ ๊ถ์ฅdescription: "" (๋น ๋ฌธ์์ด)์ ์ฌ์ฉ ๊ธ์ง โ Claude Code๊ฐ SKILL.md body ์ฒซ ์ค์ description์ผ๋ก fallbackํ์ฌ <!--trigger_conditions ๋
ธ์ถ ๋ฒ๊ทธ ๋ฐ์1๋จ๊ณ โ settings.json ์ฝ๊ธฐ
import json, os, math
from pathlib import Path
settings_path = Path(os.path.expanduser("~/.claude/settings.json"))
settings = {}
if settings_path.exists():
settings = json.loads(settings_path.read_text(encoding="utf-8"))
max_desc_chars = settings.get("skillListingMaxDescChars", 1536)
budget_fraction = settings.get("skillListingBudgetFraction", 0.01)
# char_budget ๊ณต์ (200K ๊ณ ์ ๊ธฐ์ค, ร 4 multiplier)
char_budget = 200_000 * 4 * budget_fraction
2๋จ๊ณ โ ์คํฌ ์ธ๋ฑ์ค ๋ก๋ + ๋ถ์
data = json.load(open("/tmp/skill-index.json"))
skills = data.get("skills", data) if isinstance(data, dict) else data
desc_map = [(s.get("skill","?"), s.get("description",""), s.get("source_kind",""),
s.get("file_path_globs",[]), s.get("skill_path",""),
s.get("hook_no_opt", False))
for s in skills]
total_chars = sum(len(d) for _,d,*_ in desc_map)
shortened_count = sum(1 for _,d,*_ in desc_map if len(d) > max_desc_chars)
# OPT-1 ํ๋ณด: trigger_block + globs>0 + desc>100 + desc!="(hook)" + not hook_no_opt
opt1_candidates = [
(name, len(desc), path)
for name, desc, src, globs, path, no_opt in desc_map
if src == "trigger_block" and len(globs) > 0
and len(desc) > 100 and desc != "(hook)" and not no_opt
]
# ์์ฐ ์ํ
budget_used_pct = (total_chars / char_budget * 100) if char_budget > 0 else 999
3๋จ๊ณ โ ๊ฒฐ๊ณผ ์ถ๋ ฅ
โถ /skill-advisor --listing-audit (skillListing ์์ฐ ์ง๋จ)
## skillListing ์์ฐ ์ง๋จ ๊ฒฐ๊ณผ
### ํ์ฌ ์ค์
| ํญ๋ชฉ | ํ์ฌ๊ฐ | ๊ธฐ๋ณธ๊ฐ | ๊ถ์ฅ ๋ฒ์ | ์ํ |
|------|--------|--------|-----------|------|
| skillListingMaxDescChars | 2000 | 1536 | 1536~2000 | โ
|
| skillListingBudgetFraction | 0.05 | 0.01 | 0.03~0.05 | โ
|
| char_budget | 40,000์ | โ | โ | โ |
### description ๋ถํฌ
| ํญ๋ชฉ | ๊ฐ |
|------|-----|
| ์ ์ฒด ์คํฌ ์ | 68๊ฐ |
| ์ ์ฒด description ํฉ๊ณ | 16,137์ |
| ์์ฐ ์ฌ์ฉ๋ฅ | 40% (16,137 / 40,000์) |
| shortened ์์ | 0๊ฐ (max_desc_chars=2000 ๊ธฐ์ค) |
| dropped ์์ | ์์ (์์ฐ ์ถฉ๋ถ) |
### OPT-1 ์ต์ ํ ํ๋ณด (hook ์คํฌ description ๋จ์ถ ๊ถ์ฅ)
| ์คํฌ | description ๊ธธ์ด | ์ ์ฝ ๊ฐ๋ฅ |
|------|----------------|---------|
| (์์) | โ | โ |
โ
ํ์ฌ ์ค์ ์ต์ ์ํ โ ์กฐ์น ๋ถํ์
---
๐ก ์ ์ฉํ๋ ค๋ฉด: /skill-advisor --listing-audit --apply
4๋จ๊ณ โ settings ๊ถ์ฅ๊ฐ ์ ์ ์ถ๋ ฅ
### settings.json ๊ถ์ฅ๊ฐ (์ฐธ๊ณ ์ฉ โ ์ ์ฉ์ /update-config ์ฌ์ฉ)
{
"skillListingMaxDescChars": 2000, // ํ์ฌ: 2000 โ
(๋ณ๊ฒฝ ๋ถํ์)
"skillListingBudgetFraction": 0.05 // ํ์ฌ: 0.05 โ
(๋ณ๊ฒฝ ๋ถํ์)
}
settings.json ์ง์ ์์ ์
/update-config์คํฌ์ ์ฌ์ฉํ๋ค. skill-advisor๋ ์ ์๋ง ์ถ๋ ฅ.
OPT-1 ํ๋ณด๊ฐ ์์ ๋๋ง ํ์ฑํ. ์์ผ๋ฉด "์ต์ ํ ๋์ ์์" ์ถ๋ ฅ ํ ์ข ๋ฃ.
# ํ๋ณด ๋ชฉ๋ก ํ์ + y/N ํ์ธ
print("๋ค์ ์คํฌ์ description์ '(hook)'์ผ๋ก ์์ ํฉ๋๋ค:")
for name, dlen, path in opt1_candidates:
print(f" {name}: {dlen}์ โ '(hook)' (6์, {dlen-6}์ ์ ์ฝ)")
print("\n๊ณ์ํ์๊ฒ ์ต๋๊น? (y/N): ", end="")
# ์ฌ์ฉ์ ํ์ธ ํ ์์์ ์์ (write-temp โ fsync โ atomic rename)
import shutil, tempfile
for name, dlen, path in opt1_candidates:
skill_path = Path(path)
content = skill_path.read_text(encoding="utf-8")
# description ํ๋๋ง ๊ต์ฒด (์ ๊ท์์ผ๋ก YAML frontmatter ๋ด description ๋ผ์ธ๋ง ์์ )
import re
new_content = re.sub(
r'^(description:\s*)".{101,}"',
'description: "(hook)"',
content, flags=re.MULTILINE
)
# atomic write
tmp = skill_path.with_suffix(".tmp")
tmp.write_text(new_content, encoding="utf-8")
tmp.replace(skill_path) # atomic rename (POSIX)
print(f"โ
{name}: description โ '(hook)'")
์์์ฑ ๋ณด์ฅ: write-temp โ replace (atomic rename) ํจํด. ์ค๊ฐ ์คํจ ์ .tmp ํ์ผ๋ง ๋จ๊ณ ์๋ณธ ์ ์ง.
JSON ํ์ ์ถ๋ ฅ:
{
"settings": {
"skillListingMaxDescChars": 2000,
"skillListingBudgetFraction": 0.05,
"char_budget": 40000
},
"stats": {
"total_skills": 68,
"total_desc_chars": 16137,
"budget_used_pct": 40.3,
"shortened_count": 0,
"dropped_count": 0
},
"opt1_candidates": [],
"settings_recommendations": {
"skillListingMaxDescChars": {"current": 2000, "recommended": 2000, "action": "ok"},
"skillListingBudgetFraction": {"current": 0.05, "recommended": 0.05, "action": "ok"}
}
}
ํน์ ์คํฌ์ ๋ถ์ํ์ฌ ํธ๋ฆฌ๊ฑฐ ๊ฐ์ ์ ์์ SkillPatchProposal JSON์ผ๋ก ์ถ๋ ฅํ๋ค.
1๋จ๊ณ โ ์คํฌ ์กฐํ
--enrich [์คํฌ๋ช
]: ์ธ๋ฑ์ค์์ skill_path ์กฐํ.
์ค๋ณต์ด๋ฉด ๋ชฉ๋ก ์ถ๋ ฅ + ์ ํ ์์ฒญ. absolute skill_path๋ก ์ง์ ์ง์ ๊ฐ๋ฅ.
2๋จ๊ณ โ SKILL.md ์๋
์ธ๋ฑ์ค์์ skill_path ํ๋ ํ Read ํด๋ก ์ ์ฒด ์ฝ๊ธฐ.
3๋จ๊ณ โ source_type ํ๋จ
def get_source_type(skill_path: str, skill_meta: dict) -> str:
# 1์์: frontmatter source ํ๋
if skill_meta.get("source") == "anthropic":
return "official"
# 2์์: ๊ฒฝ๋ก ํด๋ฆฌ์คํฑ (fallback)
if "anthropics/skills" in skill_path:
return "official"
elif ".claude/skills" in skill_path or "claude-forge/skills" in skill_path:
return "local"
return "third_party"
โ Phase 1 ํ๊ณ: source_type ํ๋ณ์ ๊ฒฝ๋ก ํจํด ๊ธฐ๋ฐ์ด๋ฉฐ, official ์ฌ์นญ์ ๊ฐ์งํ์ง ๋ชปํฉ๋๋ค.
| source_type | ์น ๊ฒ์ | ์ค๋ช |
|---|---|---|
| official | ํ์ฉ | anthropics/skills GitHub README ๊ฒ์ |
| local | ๋ถํ | ๋ก์ปฌ SKILL.md ๋ถ์๋ง |
| third_party | ๋ถํ | ๋ถ์ ์ ์ธ (์ ๋ขฐ์ฑ ๋ถ๋ช ) |
4๋จ๊ณ โ ์น ๊ฒ์ (official ํ์ )
WebSearch: "anthropics/skills {์คํฌ๋ช
} github trigger conditions"
WebSearch: "anthropics/skills {์คํฌ๋ช
} site:github.com"
5๋จ๊ณ โ ๊ฐ์ ์ ์ ์์ฑ + confidence ์ฐ์ถ
confidence ๊ธฐ์ค:
high: official README์์ ์ง์ ์ธ์ฉํ ํจํดmedium: ์น ๊ฒ์ ๊ฒฐ๊ณผ ์ถ๋ก / ๋ก์ปฌ SKILL.md ๋ด์ฉ ๊ธฐ๋ฐlow: ํ์ผ ๊ตฌ์กฐยท๊ฒฝ๋ก๋ช
์์ ์ถ๋ก Phase 1: ์บ์ ์์ด ๋งค๋ฒ ๊ฒ์. fetched_at์ ํ์ฌ ์คํ ์๊ฐ์ผ๋ก ์ฑ์.
6๋จ๊ณ โ SkillPatchProposal JSON ์ถ๋ ฅ
[
{
"schema_version": "1.0",
"skill": "doc-coauthoring",
"target_field": "file_path_globs",
"proposal_type": "add_glob",
"value": "**/*.mdx",
"current_value": ["**/*.md", "**/*-spec.md"],
"source": "official_readme",
"source_url": "https://github.com/anthropics/skills/...",
"fetched_at": "2026-04-23T10:00:00+0900",
"confidence": "high",
"reason": "GitHub README์์ MDX ํ์ผ ์ฒ๋ฆฌ ๋ช
์"
}
]
Phase 1 ์ง์ proposal_type (4๊ฐ):
add_glob: file_path_globs์ ํจํด ์ถ๊ฐupdate_description: description ํ
์คํธ ๊ฐ์ fix_path: ๊นจ์ง ๊ฒฝ๋ก ์์ update_utterance: utterance_patterns ko/en ์
๋ฐ์ดํธPhase 2 ์์ (3๊ฐ): remove_glob, add_event, update_risk_level
๋ฏธ์ง์ ์ ์ ํ์: "status": "unsupported_in_phase1"
์คํค๋ง ๋ฒ์ ์ ์ฑ (v1.0):
์น์ธ ๊ฒ์ดํธ: SkillPatchProposal ์ถ๋ ฅ ํ ์ฌ์ฉ์์๊ฒ "์ด ์ ์์ skill-creator๋ฅผ ํตํด ์ ์ฉํ ๊น์?"๋ฅผ ๋ฌป๋๋ค. ์ง์ SKILL.md๋ฅผ ์์ ํ์ง ์๋๋ค.
์์ ์๋ฃ ํ ํ์ฌ Claude Code ์ธ์ ์ ํ๋์ ๋ถ์ํ์ฌ ์คํฌ ํ์ฉ ํํฉ์ ์ง๋จํ๋ค. Hard Gate ์ค์ ์ฌ๋ถ(์ถ์ ), ํ์ผ ํธ์ง ๊ธฐ๋ฐ ์คํฌ ํ๋ณด, ๋์์ด ๋์ ์คํฌ์ ํจ๊ป ๋ณด๊ณ ํ๋ค.
/skill-advisor --session-review โ ์ฆ์ ์คํ (๊ธฐ๋ณธ)
/skill-advisor --session-review --confirm โ JSONL ์ธ์
์ ํ ํ ์คํ
/skill-advisor --session-review --max-edits 500 โ ํ์ผ ๋ณ๊ฒฝ ์ด๋ฒคํธ ์ต๋ 500๊ฐ ๋ถ์
/skill-advisor --session-review --json โ JSON ์ถ๋ ฅ (Layer 1 ์์ ๋ฐ์ดํฐ)
/skill-advisor --session-review --jsonl [๊ฒฝ๋ก] โ ํน์ JSONL ํ์ผ ๋ถ์
ํต์ฌ ์์น: ํ์ ์ง๋จ ๊ธ์ง โ ์ถ์ ๊ธฐ๋ฐ ํ๋ณด ํ์ง๋ง ํ์ฉ. ๋ก๋ ์ด๋ ฅ์ ์ง์ ํ์ธ ๋ถ๊ฐ (Phase 1.5 ๋ก๋ ๋ก๊ทธ ์๋ฃ ์ ).
| Track 1 | Track 2 | |
|---|---|---|
| ์กฐ๊ฑด | session-retrospective ์คํฌ ์ค์น + session_id ํ๋ ๊ฐ๋ฅ (hook stdin JSON 1์์ / env var fallback) | ์๋ fallback |
| JSONL ์์ค | get-session.sh (์ธ์
ID ๊ธฐ๋ฐ, ์ ํ) | cwd ํด์ ๊ธฐ๋ฐ ํ์ |
| ๋ถ์ ๋ด์ฉ | ๋์ผ | ๋์ผ |
| ์ถ๋ ฅ | [Track 1: session-retrospective] | [Track 2: built-in] |
Track 1์ session-retrospective ์ค์น ํ session_id ํ๋ ๊ฐ๋ฅํ ์ธ์
์์ ์๋ ํ์ฑํ๋๋ค. CLAUDE_SESSION_ID ํ๊ฒฝ๋ณ์๋ 2026-05-01๊น์ง deprecation fallback์ผ๋ก๋ง ์ ํจ.
Bash ํด๋ก ์๋๋ฅผ ์คํํ๋ค:
python3 ~/.claude/skills/skill-advisor/scripts/session-review.py
--confirm ํ๋๊ทธ๊ฐ ์์ผ๋ฉด ๋ถ์ ์ JSONL ํ๋ณด ๋ชฉ๋ก์ ํ์ํ๊ณ ์ฌ์ฉ์ ์ ํ์ ๋ฐ๋๋ค.
session-review.py๊ฐ ์ถ๋ ฅํ JSON์ ๋ฐ์ ๋ค์ ์น์
์ ์์ฑํ๋ค:
์น์ 3 โ ํธ๋ฆฌ๊ฑฐ๋์์ด์ผ ํ ์คํฌ:
hard_gate_candidates ์ค detected=false์ด๊ณ triggered_by๊ฐ ๋น์ด ์์ง ์์ ํญ๋ชฉedited_files + slash_commands_found + triggered_by๋ก ๋งฅ๋ฝ ์์ฑโ Hard Gate (CLAUDE.md ๊ธฐ์ค, ํธ๋ฆฌ๊ฑฐ ๊ฐ์ง๋จ โ ์คํ ๋ฏธํ์ธ [ESTIMATED])
| ์คํฌ | ํธ๋ฆฌ๊ฑฐ ์กฐ๊ฑด | ์ด๋ฒ ์์
์ํฉ | ๊ฒฐ๊ณผ |
|------|-----------|--------------|------|
| verification-before-completion | โ
์๋ฃ ์ ์ธ ์ ํ์ | SKILL.md ํธ์ง, /plan ์คํ | ๋ฏธํ์ธ |
์น์ 4 โ ๋์์ด ๋์ ์คํฌ:
skill_candidates (file-glob ๋งค์นญ ๊ธฐ๋ฐ)hard_gate_candidates ์ค triggered_by ์๋ ํญ๋ชฉsignal_recommendations (์๋ฌ ์ ํธ ๊ธฐ๋ฐ)โ
hard_gate_candidates๋ JSONL ์ฌ๋์ ์ปค๋งจ๋ ํ์ง + ํ์ผ ์ ํธ ๊ธฐ๋ฐ ์ถ์ [ESTIMATED]. Phase 1.5 ๋ก๋ ๋ก๊ทธ ์๋ฃ ์ ๊น์ง ์ ๋ขฐ๋๊ฐ ์ ํ์ ์ด๋ค.
[ESTIMATED]--jsonl ๊ฒฝ๋ก๋ ~/.claude/projects/ ๋๋ /tmp ํ์๋ง ํ์ฉ (๋ณด์ ์ ํ)--session-review์ "๋ก๋ ์ด๋ ฅ ์ง์ ๋์กฐ" ๊ธฐ๋ฅ์ skill-auto-loader.sh์ ๋ก๋ ๋ก๊ทธ ๊ธฐ๋ก์ด
์ถ๊ฐ๋์ด์ผ ํ๋ค. Phase 1.5 ๊ตฌํ ์ ์๋ ๋ ํ์ผ์ ๋์์ ์
๋ฐ์ดํธํด์ผ ํ๋ค:
~/.claude/hooks/skill-auto-loader.sh (๋ก๋ ์ด๋ฒคํธ ๊ธฐ๋ก ์ถ๊ฐ)ํ์ฌ --apply๋ ๋นํ์ฑํ ์ํ. ์๋ ์ฒดํฌ๋ฆฌ์คํธ๊ฐ ๋ชจ๋ ์๋ฃ๋๋ฉด Phase 2 ์ง์ :
who_last_modified ๋ฉํ๋ฐ์ดํฐ ์ถ๊ฐ--apply-proposal [JSONํ์ผ] ์๋ธ์ปค๋งจ๋ ๊ตฌํpytest ~/.claude/skills/skill-advisor/tests/)| ์ํฉ | ์ฒ๋ฆฌ | exit code |
|---|---|---|
| skill-index.json ์์ + skill-index.sh ์ฑ๊ณต | ๊ณ์ ์งํ | 0 ๋๋ 1 |
| skill-index.json ์์ + skill-index.sh ์คํจ | ์ง์ ์ค์บ fallback + ๊ฒฝ๊ณ | 1 |
| SKILL.md ์ฝ๊ธฐ ์คํจ | ํด๋น ์คํฌ skip + [์ฝ๊ธฐ ์คํจ] | 1 |
| ์น ๊ฒ์ ์คํจ | ๋ก์ปฌ ๋ถ์๋ง + [์น ๊ฒ์ ์คํจ] ํ์ | 1 |
| JSON ํ์ฑ ์ค๋ฅ | ์ค๋ฅ ๋ฉ์์ง stderr + ์ฆ์ ์ข ๋ฃ | 2 |
| ํ์ผ ์ ๊ทผ ๊ถํ ์ค๋ฅ | ์ค๋ฅ ๋ฉ์์ง stderr + ์ฆ์ ์ข ๋ฃ | 2 |
--scan/--enrich/--session-review๋ read-only ์ง๋จ ๋๊ตฌ โ SKILL.md ์์ ์ ๋ ๊ธ์ง.--update --apply๋ ์ ๋ฐ์ดํธ ๋๊ตฌ โ ์ฌ์ฉ์ ํ์ธ ํ upstream ๋ด์ฉ ์ ์ฉ ํ์ฉ. skill-advisor ์์ ์ SKILL.md ์ ๋ฐ์ดํธ๋ ์๊ธฐ ์ฐธ์กฐ ๋ฐฉ์ง๋ฅผ ์ํด ํญ์ ๊ธ์ง.
์ฑ๋ (upstream_type) | check (๊ธฐ๋ณธ) | apply |
|---|---|---|
github_raw / anthropic_official / microsoft / community | SHA256 ๋น๊ต + diff ์์ฝ | backupโapplyโverifyโatomic rename |
npm | npm outdated + npm audit | v1.0: ๋ช ๋ น์ด ์ถ๋ ฅ๋ง (์ง์ ์ค์น ์ ํจ) |
pip | pip list --outdated | v1.0: ๋ช ๋ น์ด ์ถ๋ ฅ๋ง (์ง์ ์ค์น ์ ํจ) |
local_custom | ์ฒดํฌ ์์ (ํญ์ local-custom) | ํด๋น ์์ |
SKILL_DIR="<skill-advisor scripts ๊ฒฝ๋ก>"
# 1. check (๊ธฐ๋ณธ)
python3 "$SKILL_DIR/update.py" [<skill>] [--json]
# 2. apply (github_raw ์ฑ๋๋ง ์๋ ์ ์ฉ; npm/pip๋ ๋ช
๋ น์ด ์ถ๋ ฅ)
python3 "$SKILL_DIR/update.py" [<skill>] --apply [--yes] [--keep-backup]
์คํ ์ ๊ณํ ์ถ๋ ฅ: --apply ์ ์ฑ๋๋ณ ๋์ยท๋ถ์์ฉ ๋ฑ๊ธ์ ํญ์ ํ๋ก ์ถ๋ ฅํ ํ y/N ํ์ธ. --yes ํ๋๊ทธ ์ ํ์ธ ์๋ต (CI ๋ชจ๋).
--apply ์๋ฃ ํ github_raw ์ฑ๋์์ 1๊ฐ ์ด์ ์ ์ฉ๋์ผ๋ฉด ์๋์ผ๋ก --listing-audit --apply๋ฅผ ์คํํ๋ค.
์ด์ : ์
์คํธ๋ฆผ ์
๋ฐ์ดํธ๋ ๋ก์ปฌ์์ "(hook)"์ผ๋ก ์ต์ ํํ description์ ์๋ ๊ฐ์ผ๋ก ๋ณต๊ตฌํ ์ ์๋ค. ์๋ ์คํ์ผ๋ก OPT-1 ํ๋ณด๋ฅผ ์ฆ์ ์ฌ์ ์ฉํ๋ค.
# --apply ์๋ฃ ํ ์ ์ฉ ์ ํ์ธ
APPLIED_COUNT=$(๊ฒฐ๊ณผ์์ "โ ์ ์ฉ๋จ" ๋ผ์ธ ์)
NO_LISTING_AUDIT_FLAG=false # --no-listing-audit ํ๋๊ทธ ์ฌ๋ถ
if [ "$APPLIED_COUNT" -ge 1 ] && [ "$NO_LISTING_AUDIT_FLAG" = "false" ]; then
echo ""
echo "โโโ ์๋ ์คํ: --listing-audit --apply โโโ"
# --listing-audit --apply ๋ชจ๋ ์คํ (OPT-1 ํ๋ณด๊ฐ ์์ผ๋ฉด "์ต์ ํ ๋์ ์์" ์ถ๋ ฅ ํ ์ข
๋ฃ)
fi
์ต์ : --no-listing-audit ํ๋๊ทธ ๋ช
์ ์ ์๋ ์คํ ๊ฑด๋๋.
| ์ฝ๋ | ์๋ฏธ |
|---|---|
| 0 | ์ฑ๊ณต (์ต์ ์ํ) |
| 1 | ์ ๋ฐ์ดํธ ๊ฐ๋ฅ ํญ๋ชฉ ์์ |
| 2 | ์คํ ์คํจ (fetch ์ค๋ฅ ๋ฑ) |
| 3 | apply ์คํจ, rollback ์๋ฃ |
| 4 | apply ์คํจ + rollback ์คํจ (๋ฐ์ดํฐ ์์ค ์ํ โ .bak ๊ฒฝ๋ก ์ถ๋ ฅ) |
<!--trigger_conditions...--> ๋ธ๋ก ์ถ์ถnpm ์ฑ๋ ์ฃผ์: node_modules ๋ด SKILL.md์ trigger_conditions๊ฐ ์์ผ๋ฉด --apply ์ฐจ๋จ.
skill-creator๋ก trigger_conditions๋ฅผ ๊ธฐ๋ณธ SKILL.md๋ก ์ด์ ํ ์ฌ์คํ ํ์.
~/.claude/skill-sources.json โ 65๊ฐ+ ์คํฌ์ upstream URLยท์ฑ๋ยท์ํ ์ ๋ณด.
scripts/update.py ์ค์ผ์คํธ๋ ์ดํฐ, scripts/channels/{github_raw,npm,pip}.py ์ฑ๋๋ณ ํธ๋ค๋ฌ.
/skill-advisor โ DA 2ํ Finalized (2026-04-23)"" ์ฌ์ฉ ๊ธ์ง (body fallback ๋ฒ๊ทธ). hook_no_opt: true๋ก ์ต์ ๊ฐ๋ฅ (2026-05 ์ธ์
๊ฒ์ฆ)--update --apply ์๋ฃ ํ github_raw 1๊ฐ+ ์ ์ฉ ์ --listing-audit --apply ์๋ ์คํ (๊ธฐ๋ณธ ON). ์ต์ : --no-listing-audit ํ๋๊ทธ. ์ด์ : ์
์คํธ๋ฆผ ์
๋ฐ์ดํธ๊ฐ ๋ก์ปฌ "(hook)" ์ต์ ํ๋ฅผ ๋ฎ์ด์ธ ์ ์์ (2026-05 ์ค์ฆ)๋ณธ SKILL.md + scripts/session-review.py ์ฒซ ์ฌ์ฉ์ ์ค์ฆ (session 33cb50b0) ๋ฐ๊ฒฌ 6 ๋ฌธ์ ๋ณด๊ฐ. CSR #784ยท#808ยท#809ยท#827ยท#828 G-13 carry-forward์์ ๋ฐ๊ฒฌ.
--session-scan alias ์ถ๊ฐ (#2): ์ฌ์ฉ์ typo ๋ณดํธ โ --session-review ๋์ผ ํจ๊ณผ. argparse dest="session_scan_alias". SKILL.md ์ฌ์ฉ๋ฒ ์น์
๋ช
์.--jsonl <path> ์ ๋ ์ฐ์ ์์ ๋ณด์ฅ (#3): Track1 ์๋ detection skip ๋ช
์. stderr [INFO] --jsonl ๋ช
์ ์ฌ์ฉ ๊ฐ์์ฑ ๋ก๊ทธ.~/.claude/hooks/skill-auto-loader.sh ๋ก๋ ๋ก๊ทธ ์ถ๊ฐ โ ~/.claude/da-tools/skill-load.jsonl JSONL (3-SID schema ํฌํจ, CSR #831 ํตํฉ).~/.claude/hooks/skill-keyword-injector.sh ์ ์ค โ csr-task utterance miss ํํผ (paddo.dev Controllability Problem). Issue #38 opencode-skills ๋ต์ต.~/.claude/hooks/guide-board-detector.sh ์ ์ค (PreToolUse:Bash) + hard-gates.json tool_events_bash_if schema ํ์ฅ. Bash:matches ์ ๊ท์ ๋ฏธ์ง์ ๋ฐ๊ฒฌ โ Plan #11 ์ ์ (Permission rule syntax ์์ผ๋์นด๋).~/.claude/hooks/plan-skip-audit.sh ์ ์ค โ plan ๋ฉด์ ํค์๋ + transcript edit count cross-check โ ~/.claude/da-tools/plan-skip.jsonl event plan_skip_invalid|justified.if: Bash(...) Permission rule syntax ์์ผ๋์นด๋ (Plan #11 ์ ์ ๊ทผ๊ฑฐ)๋ณธ SKILL.md ๋ฐ scripts/session-review.py ์ G ํตํฉ 6 ๋ณ๊ฒฝ ์ค 3 ๋ณ๊ฒฝ๋ง ์ฑํ (AยทEยทF ์ถ์). DA chain Tier 1 (4-AI ์์ฐจ) ๊ฒฐ๊ณผ N + ์ต์
B (Layer 4 ํ๊ธฐ) ์ ์ฉ.
build_hard_gate_candidates() ์ถ๋ ฅ detected ํ๋๋ฅผ 4 string status ๋ก ๋ถ๋ฆฌ:
| Status | ์ ์ | ๊ธฐ์กด (v1) |
|---|---|---|
"executed" | slash_command_found ๋งค์นญ (์คํ evidence) | true |
"triggered" | triggered_by ๋น์ด์์ง ์์ (์กฐ๊ฑด ๋งค์นญ, ์คํ ๋ฏธํ์ธ) | false (false negative) |
"artifact_confirmed" | gate-artifacts/.done ๋ง์ปค ํ์ธ | "artifact" |
"miss" | ๋ชจ๋ ์กฐ๊ฑด ๋ฏธ๋งค์นญ | false |
Backward compat: legacy_detected ํ๋ ๋ณด์กด (60์ผ deprecation, drop 2026-07-23). schema_version: 2 ํ๋ ์ ๊ท.
ํต์ฌ ํํผ: CSR #823 ๋ณธ ์ธ์ csr-task false negative ์ฌ๋ก (triggered_by ์๋๋ฐ detected=false) โ CSR #825 C-A1 ํด์.
build_unified_skill_view() ์ ๊ท ํจ์ โ Hard Gate + slash_command + file-glob + signal ํตํฉ view.
Ordering rule:
Top-N cap: ๊ธฐ๋ณธ 10 (CW-HIGH-2 visual complexity ํํผ).
Thresholds ์ธ๋ถํ: ~/.claude/skill-advisor/thresholds.json โ signal_thresholds (large_change / refactor / tdd / da_chain) + default_mode: conservative.
User override: --threshold-config <path> flag.
๋ณธ SKILL.md ๋ณ๊ฒฝ ์ DA chain Tier ๋ถ๋ฆฌ:
| ๋ถ๋ฅ | ๊ธฐ์ค | DA chain Tier |
|---|---|---|
| major | frontmatter ๋ณ๊ฒฝ / โ Hard Gate line ์ถ๊ฐ/์ญ์ /์์ / tool ๋ชฉ๋ก ๋ณ๊ฒฝ / trigger pattern ๋ณ๊ฒฝ | Tier 1 (4-AI ์์ฐจ) ์๋ฌด |
| minor | ๋ณธ๋ฌธ prose only / typo / formatting / CHANGELOG entry ์ถ๊ฐ | Tier 3 skip (Claude ๋จ๋ ) |
Semantic prose evasion warning (DS-HIGH-3 carry-forward): ๋ณธ๋ฌธ prose ๊ฐ implicit trigger pattern ์ถ๊ฐ ์ minor ๋ถ๋ฅ ์ํ โ reviewer ์๋ ๊ฒฉ์ (Tier 2+) ๊ถ์ฅ.
Composition reference (frontmatter metadata): CLAUDE.md anchor ํํผ (CW-MED-3) โ Composition ๊ท์น reference ๋ SKILL.md frontmatter ๋๋ ๋ณ๋ metadata ๋ฐ์ . CLAUDE.md ์ง์ anchor ์ฌ์ฉ ๊ธ์ง.
DA chain ๊ฒฐ๊ณผ ๋ณธ ๋ณ๊ฒฝ์์ ์ ์ธ๋ ํญ๋ชฉ:
| ์ | REJECT ์ด์ | ๋ณ๊ฑด carry-forward |
|---|---|---|
| B Phase 1.5 load log | r2 ChatGPT โ hook side effect catastrophic risk (skill-auto-loader.sh ๋ณ๊ฒฝ) | --analyze-recent-jsonl subcommand ๋ณ๊ฑด (read-only post-hoc) |
| C read-only redefine | r2 ChatGPT โ semantic drift, ๊ธฐ์กด ์ฌ์ฉ์ mental model ์นจํด | ๋ณธ ์์น ์ ์ง (ํธ๋ฆฌ๊ฑฐ immutable ์๋ฏธ) |
| D Tool path validation | r2 ChatGPT โ scope creep (god-object risk) | ~/.claude/scripts/validate-skill-tools.sh ๋ณ๋ ๋๊ตฌ |
| Layer 4 skill-creator hard-block | r4 DeepSeek CRIT-1ยท2 โ frontmatter flag self-referential guard violation ("guard inside the guarded system") | External integrity store (๋ณ๊ฑด CSR โ signed manifest ๋๋ OS permission ๋ณดํธ) |
๋ณดํธ mechanism (CSR #823 ๋ต์ต procedural enforcement):
--session-review self-application ์ฌ์ฉ์ ๊ฒํ โ ๋ณ๊ฒฝ ํ ์๋ฌด์ญ์ (DA chain ๊ฒฐ๊ณผ): self-reference guard frontmatter flag ยท 4-layer hard enforcement ยท skill-creator hard-block ยท skill_name == "skill-advisor" ํ๋์ฝ๋.
tests/)fixture-A-detection-3-status.json โ 4 status ๊ฒ์ฆfixture-E-unified-view.json โ ordering + top-10 capfixture-F-tier-classification.json โ major/minor ๋ช
์ ๊ธฐ์ค์๋ ๊ฒ์ฆ: bash tests/g-integrated-test.sh (13/13 PASS Green)
| ์ผ์ | ๋ณ๊ฒฝ |
|---|---|
| 2026-05-24 | CSR #825 G3' ์ถ์ โ DA chain Tier 1 Conditional Y (์ต์
B ์ฑํ). detection 4 status (executed/triggered/artifact_confirmed/miss) + schema_version: 2 + legacy_detected 60์ผ deprecation (drop 2026-07-23). unified_skill_view ์ ๊ท (ordering + top-10 cap). thresholds.json ์ธ๋ถํ (signal_thresholds + conservative mode). major/minor Tier ๋ถ๋ฆฌ ๋ช
์ ๊ธฐ์ค. REJECT 4๊ฑด: B Phase 1.5 load log (hook side effect) / C read-only redefine (semantic drift) / D tool path validation (scope creep) / Layer 4 skill-creator hard-block (DeepSeek r4 self-referential guard violation). ์ญ์ : self-reference guard frontmatter flag ยท 4-layer hard enforcement. ๋ณดํธ mechanism = CSR #823 ๋ต์ต procedural (์ฌ์ฉ์ ๊ฒํ + DA chain + first self-application ๊ฒํ ). ์ ๊ท ๋๊ตฌ: tests/g-integrated-test.sh (13/13 PASS Green). ๋ณ๊ฑด carry-forward 6๊ฑด (External integrity store / DS-HIGH-1ยท2ยท3 / --analyze-recent-jsonl / validate-skill-tools.sh). claude_learn 3 ๊ธ ๋ฐ์ (self-referential guard violation / Monotonic MODIFY meta-pattern / Procedural vs Hard enforcement). DA session: /tmp/da-chain-csr825-1779578551/ |