feat(skill): 技能治理 v0.2 —— 分桶/调用模式/校验/安全/清单/安装矩阵
- 新增 SCOPE.md 收录门槛 + .out-of-scope/ 拒绝档案 - 生命周期分桶 promoted/beta/deprecated,marketplace 只发布 promoted - 调用模式 model-invoked/user-invoked(user-invoked 不占模型目录) - 新增脚本 validate-skills.py / generate-catalog.py / scan-skill.sh(+allowlist) - 新增 beta 技能 skill-creator、ai-proj-helper-entry、verification-before-completion(各带 evals) - install-skills.sh: --agent dsh / --link / --audit / --include-beta / --no-validate - 融合:dev-review 接收评审意见、req-design 依赖边、agent-swarm 并行编排、dev-test TDD - catalog.json 61 个技能(58 promoted / 3 beta);validate 0 错误;scan 0 高危;现有测试通过
This commit is contained in:
+94
-4
@@ -5,7 +5,11 @@
|
||||
# ./install-skills.sh [options]
|
||||
#
|
||||
# Options:
|
||||
# --agent <agent> Install target: codex (default) or claude
|
||||
# --agent <agent> Install target: codex (default), dsh, or claude
|
||||
# --link Symlink skills from the repo instead of copying (dev machines)
|
||||
# --audit Show installed vs repo versions for the selected target
|
||||
# --include-beta Also install skills whose plugin.json status=beta
|
||||
# --no-validate Skip scripts/validate-skills.py before installing
|
||||
# --dry-run Preview changes without writing anything
|
||||
# --category <cat> Only install plugins in dir_category=<cat>
|
||||
# Valid values: biz, core, dev, integration, personal, req
|
||||
@@ -30,6 +34,10 @@ FORCE=false
|
||||
CLEANUP=false
|
||||
LIST_ONLY=false
|
||||
INSTALL_ACTION=false
|
||||
LINK_MODE=false
|
||||
AUDIT_ONLY=false
|
||||
INCLUDE_BETA=false
|
||||
NO_VALIDATE=false
|
||||
|
||||
# ── Colour helpers ─────────────────────────────────────────────────────────────
|
||||
GREEN='\033[0;32m'
|
||||
@@ -48,9 +56,13 @@ dry() { echo -e "${YELLOW}[dry]${RESET} $*"; }
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--agent)
|
||||
[[ $# -ge 2 ]] || { error "--agent requires codex or claude"; exit 1; }
|
||||
[[ $# -ge 2 ]] || { error "--agent requires codex, dsh or claude"; exit 1; }
|
||||
AGENT_TARGET="$2"; shift ;;
|
||||
--dry-run) DRY_RUN=true ;;
|
||||
--link) LINK_MODE=true ;;
|
||||
--audit) AUDIT_ONLY=true ;;
|
||||
--include-beta) INCLUDE_BETA=true ;;
|
||||
--no-validate) NO_VALIDATE=true ;;
|
||||
--force) FORCE=true ;;
|
||||
--cleanup) CLEANUP=true ;;
|
||||
--list) LIST_ONLY=true ;;
|
||||
@@ -77,13 +89,21 @@ case "$AGENT_TARGET" in
|
||||
SKILLS_DIR="${AI_PROJ_HELPER_SKILLS_DIR:-${HOME}/.agents/skills}"
|
||||
STATE_FILE="${AI_PROJ_HELPER_STATE_FILE:-${HOME}/.agents/.ai-proj-helper-installed-skills.json}"
|
||||
;;
|
||||
dsh)
|
||||
if [[ -z "${DSH_HOME:-}" ]]; then
|
||||
error "--agent dsh requires DSH_HOME (or set AI_PROJ_HELPER_SKILLS_DIR explicitly)"
|
||||
exit 1
|
||||
fi
|
||||
SKILLS_DIR="${AI_PROJ_HELPER_SKILLS_DIR:-${DSH_HOME}/skills}"
|
||||
STATE_FILE="${AI_PROJ_HELPER_STATE_FILE:-${DSH_HOME}/.ai-proj-helper-installed-skills.json}"
|
||||
;;
|
||||
claude)
|
||||
SKILLS_DIR="${AI_PROJ_HELPER_SKILLS_DIR:-${HOME}/.claude/skills}"
|
||||
COMMANDS_DIR="${AI_PROJ_HELPER_COMMANDS_DIR:-${HOME}/.claude/commands}"
|
||||
STATE_FILE="${AI_PROJ_HELPER_STATE_FILE:-${HOME}/.claude/.installed-skills.json}"
|
||||
;;
|
||||
*)
|
||||
error "Unsupported agent: $AGENT_TARGET (expected codex or claude)"
|
||||
error "Unsupported agent: $AGENT_TARGET (expected codex, dsh or claude)"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
@@ -281,8 +301,19 @@ install_plugin() {
|
||||
effective_install_type="skill"
|
||||
fi
|
||||
|
||||
local status
|
||||
status="$(read_field "$json_path" status)"
|
||||
status="${status:-promoted}"
|
||||
|
||||
if [[ "$LIST_ONLY" == true ]]; then
|
||||
echo " [$dir_category] $effective_install_type:$install_name v$version"
|
||||
echo " [$dir_category] $status $effective_install_type:$install_name v$version"
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ "$status" == "deprecated" ]]; then
|
||||
return
|
||||
fi
|
||||
if [[ "$status" == "beta" && "$INCLUDE_BETA" != true ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
@@ -305,6 +336,21 @@ install_plugin() {
|
||||
return
|
||||
fi
|
||||
|
||||
# Dev machines may symlink instead of copying: repository edits apply at once.
|
||||
if [[ "$LINK_MODE" == true && "$effective_install_type" != "command" ]]; then
|
||||
if [[ "$DRY_RUN" == true ]]; then
|
||||
dry "$install_name → symlink $target_path → $src_dir"
|
||||
else
|
||||
mkdir -p "$SKILLS_DIR"
|
||||
rm -rf "$target_path"
|
||||
ln -sfn "$src_dir" "$target_path"
|
||||
state_set "$install_name" "$version" "symlink" "$(content_digest "$src_dir")"
|
||||
ok "$install_name → symlink (v$version)"
|
||||
fi
|
||||
INSTALL_ACTION=true
|
||||
return
|
||||
fi
|
||||
|
||||
# A recorded content digest distinguishes repository updates from user edits.
|
||||
# Legacy state is adopted automatically only when the target is missing or
|
||||
# already identical to the repository source.
|
||||
@@ -415,6 +461,38 @@ cleanup_removed() {
|
||||
done < <(state_all_names)
|
||||
}
|
||||
|
||||
# ── Audit ──────────────────────────────────────────────────────────────────────
|
||||
audit_target() {
|
||||
python3 - "$REPO_DIR" "$STATE_FILE" "$AGENT_TARGET" "$SKILLS_DIR" <<'PYEOF'
|
||||
import json, sys, pathlib
|
||||
repo, state_file, agent, skills_dir = sys.argv[1:5]
|
||||
root = pathlib.Path(repo)
|
||||
state = {}
|
||||
if pathlib.Path(state_file).is_file():
|
||||
try: state = json.loads(pathlib.Path(state_file).read_text())
|
||||
except Exception: state = {}
|
||||
rows = []
|
||||
for pj in sorted(root.glob('skills-*/*-plugin/.claude-plugin/plugin.json')):
|
||||
try: meta = json.loads(pj.read_text())
|
||||
except Exception: continue
|
||||
name = meta.get('install_name')
|
||||
if not name: continue
|
||||
status = meta.get('status', 'promoted')
|
||||
want = meta.get('version', '')
|
||||
got = state.get(name, {}).get('version', '')
|
||||
itype = state.get(name, {}).get('install_type', '')
|
||||
drift = '' if got == want else ('missing' if not got else 'stale')
|
||||
rows.append((name, status, want, got or '-', itype or '-', drift))
|
||||
drift_rows = [r for r in rows if r[5]]
|
||||
print(f'audit agent={agent} skills_dir={skills_dir}')
|
||||
print(f'{"name":34} {"status":10} {"repo":8} {"installed":9} {"mode":8} drift')
|
||||
for r in rows:
|
||||
print(f'{r[0]:34} {r[1]:10} {r[2]:8} {r[3]:9} {r[4]:8} {r[5]}')
|
||||
print(f'\n{len(rows)} 个技能,{len(drift_rows)} 个漂移(未安装/版本落后)')
|
||||
raise SystemExit(1 if drift_rows else 0)
|
||||
PYEOF
|
||||
}
|
||||
|
||||
# ── Main ───────────────────────────────────────────────────────────────────────
|
||||
main() {
|
||||
if [[ "$LIST_ONLY" == true ]]; then
|
||||
@@ -429,7 +507,19 @@ main() {
|
||||
return
|
||||
fi
|
||||
|
||||
if [[ "$AUDIT_ONLY" == true ]]; then
|
||||
audit_target
|
||||
return
|
||||
fi
|
||||
|
||||
info "Installing skills for $AGENT_TARGET from: $REPO_DIR"
|
||||
if [[ "$NO_VALIDATE" != true && -f "$REPO_DIR/scripts/validate-skills.py" ]]; then
|
||||
if ! python3 "$REPO_DIR/scripts/validate-skills.py" >/tmp/ai-proj-helper-validate.log 2>&1; then
|
||||
error "validate-skills.py 未通过,修复后再装(或加 --no-validate 跳过)"
|
||||
tail -20 /tmp/ai-proj-helper-validate.log >&2 || true
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
[[ "$DRY_RUN" == true ]] && warn "DRY RUN — no files will be written"
|
||||
[[ -n "$CATEGORY_FILTER" ]] && info "Category filter: $CATEGORY_FILTER"
|
||||
|
||||
|
||||
Reference in New Issue
Block a user