diff --git a/.claude/skills/mr/SKILL.md b/.claude/skills/mr/SKILL.md index 7b3b317..cb218b1 100644 --- a/.claude/skills/mr/SKILL.md +++ b/.claude/skills/mr/SKILL.md @@ -30,6 +30,43 @@ CAN_PUSH=$(echo "$PERMISSIONS" | python3 -c "import sys,json; print(json.load(sy - `CAN_PUSH`가 `False`이면: "MR 생성 권한이 필요합니다. 프로젝트 관리자에게 요청하세요." 안내 후 종료 +### 0.5. 팀 워크플로우 최신화 확인 + +`.claude/workflow-version.json`이 존재하지 않으면 이 단계를 건너뛴다 (팀 프로젝트가 아닌 경우). + +```bash +# 로컬 설정 읽기 +GITEA_URL=$(python3 -c "import json; print(json.load(open('.claude/workflow-version.json')).get('gitea_url', 'https://gitea.gc-si.dev'))" 2>/dev/null) +PROJECT_TYPE=$(python3 -c "import json; print(json.load(open('.claude/workflow-version.json')).get('project_type', ''))" 2>/dev/null) +CUSTOM_PRECOMMIT=$(python3 -c "import json; print(json.load(open('.claude/workflow-version.json')).get('custom_pre_commit', False))" 2>/dev/null) + +# 서버 해시 조회 (custom_pre_commit이면 pre-commit 제외 해시 사용) +SERVER_VER=$(curl -sf --max-time 5 "${GITEA_URL}/gc/template-common/raw/branch/develop/workflow-version.json") +if [ "$CUSTOM_PRECOMMIT" = "True" ]; then + SERVER_HASH=$(echo "$SERVER_VER" | python3 -c "import sys,json; print(json.load(sys.stdin).get('content_hashes_custom_precommit',{}).get('${PROJECT_TYPE}',''))" 2>/dev/null) +else + SERVER_HASH=$(echo "$SERVER_VER" | python3 -c "import sys,json; print(json.load(sys.stdin).get('content_hashes',{}).get('${PROJECT_TYPE}',''))" 2>/dev/null) +fi + +# 로컬 해시 계산 (custom_pre_commit이면 .githooks/pre-commit 제외) +if [ "$CUSTOM_PRECOMMIT" = "True" ]; then + LOCAL_HASH=$(find .claude/rules .claude/agents .claude/scripts .githooks \ + .claude/skills/push .claude/skills/mr .claude/skills/create-mr \ + .claude/skills/release .claude/skills/version .claude/skills/fix-issue \ + -type f ! -path '.githooks/pre-commit' 2>/dev/null | sort | xargs cat 2>/dev/null | shasum -a 256 | cut -d' ' -f1) +else + LOCAL_HASH=$(find .claude/rules .claude/agents .claude/scripts .githooks \ + .claude/skills/push .claude/skills/mr .claude/skills/create-mr \ + .claude/skills/release .claude/skills/version .claude/skills/fix-issue \ + -type f 2>/dev/null | sort | xargs cat 2>/dev/null | shasum -a 256 | cut -d' ' -f1) +fi +``` + +**비교 결과 처리**: +- **서버 조회 실패** (`SERVER_HASH` 비어있음): "⚠️ 서버 연결 불가, 워크플로우 체크를 건너뜁니다" 경고 후 다음 단계 진행 +- **일치** (`LOCAL_HASH == SERVER_HASH`): 다음 단계 진행 +- **불일치**: "⚠️ 팀 워크플로우가 최신이 아닙니다. 동기화를 실행합니다..." 출력 → **sync-team-workflow 절차를 자동 실행** → 완료 후 원래 작업 계속 + ### 1. 사전 검증 ```bash @@ -174,8 +211,52 @@ curl -X POST "https://{host}/api/v1/repos/{owner}/{repo}/pulls" \ }' ``` -### 7. 결과 출력 +### 7. PR 승인 + 머지 (선택) +**사용자 확인** (AskUserQuestion): +- **질문**: "MR을 어떻게 처리하시겠습니까?" +- 옵션 1: 리뷰 대기 (추천) — MR만 생성, 리뷰어 지정 후 수동 머지 +- 옵션 2: 승인 + 머지 — claude-bot으로 즉시 승인하고 머지 + +**승인 + 머지 선택 시**: + +```bash +# CLAUDE_BOT_TOKEN 확인 +if [ -z "$CLAUDE_BOT_TOKEN" ]; then + echo "CLAUDE_BOT_TOKEN이 설정되지 않아 자동 승인이 불가합니다. MR만 생성합니다." + # MR URL 출력 후 종료 +fi + +# 1. claude-bot이 PR 리뷰 승인 +curl -X POST "https://{host}/api/v1/repos/{owner}/{repo}/pulls/{pr_number}/reviews" \ + -H "Authorization: token ${CLAUDE_BOT_TOKEN}" \ + -H "Content-Type: application/json" \ + -d '{"event": "APPROVED", "body": "MR 승인 (via /mr skill)"}' + +# 2. 머지 실행 +curl -X POST "https://{host}/api/v1/repos/{owner}/{repo}/pulls/{pr_number}/merge" \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Content-Type: application/json" \ + -d '{"Do": "merge", "merge_message_field": "MR 제목", "delete_branch_after_merge": true}' +``` + +⚠️ `delete_branch_after_merge: true` — feature 브랜치는 머지 후 삭제한다 (develop/main과 달리). + +### 8. 결과 출력 + +**승인 + 머지한 경우**: +``` +✅ MR 머지 완료 + 브랜치: feature/my-branch → develop + MR: https://gitea.gc-si.dev/gc/my-project/pulls/42 + 커밋: 3건, 파일: 5개 변경 + 릴리즈 노트: docs/RELEASE-NOTES.md [Unreleased] 갱신됨 + PR 승인: claude-bot ✅ + 머지: 완료 ✅ + 브랜치 삭제: feature/my-branch ✅ +``` + +**리뷰 대기 선택 시**: ``` ✅ MR 생성 완료 브랜치: feature/my-branch → develop @@ -190,6 +271,7 @@ curl -X POST "https://{host}/api/v1/repos/{owner}/{repo}/pulls" \ - `GITEA_TOKEN`: Gitea API 접근 토큰 - 없으면: "Gitea 토큰이 필요합니다. Settings → Applications에서 생성하세요" 안내 +- `CLAUDE_BOT_TOKEN`: claude-bot PR 승인용 토큰 (선택, 없으면 자동 승인 건너뜀) ## 기존 /create-mr과의 차이 diff --git a/.claude/skills/push/SKILL.md b/.claude/skills/push/SKILL.md index 7cbdceb..b9b7203 100644 --- a/.claude/skills/push/SKILL.md +++ b/.claude/skills/push/SKILL.md @@ -30,6 +30,43 @@ CAN_PUSH=$(echo "$PERMISSIONS" | python3 -c "import sys,json; print(json.load(sy - `CAN_PUSH`가 `False`이면: "push 권한이 필요합니다. 프로젝트 관리자에게 요청하세요." 안내 후 종료 +### 0.5. 팀 워크플로우 최신화 확인 + +`.claude/workflow-version.json`이 존재하지 않으면 이 단계를 건너뛴다 (팀 프로젝트가 아닌 경우). + +```bash +# 로컬 설정 읽기 +GITEA_URL=$(python3 -c "import json; print(json.load(open('.claude/workflow-version.json')).get('gitea_url', 'https://gitea.gc-si.dev'))" 2>/dev/null) +PROJECT_TYPE=$(python3 -c "import json; print(json.load(open('.claude/workflow-version.json')).get('project_type', ''))" 2>/dev/null) +CUSTOM_PRECOMMIT=$(python3 -c "import json; print(json.load(open('.claude/workflow-version.json')).get('custom_pre_commit', False))" 2>/dev/null) + +# 서버 해시 조회 (custom_pre_commit이면 pre-commit 제외 해시 사용) +SERVER_VER=$(curl -sf --max-time 5 "${GITEA_URL}/gc/template-common/raw/branch/develop/workflow-version.json") +if [ "$CUSTOM_PRECOMMIT" = "True" ]; then + SERVER_HASH=$(echo "$SERVER_VER" | python3 -c "import sys,json; print(json.load(sys.stdin).get('content_hashes_custom_precommit',{}).get('${PROJECT_TYPE}',''))" 2>/dev/null) +else + SERVER_HASH=$(echo "$SERVER_VER" | python3 -c "import sys,json; print(json.load(sys.stdin).get('content_hashes',{}).get('${PROJECT_TYPE}',''))" 2>/dev/null) +fi + +# 로컬 해시 계산 (custom_pre_commit이면 .githooks/pre-commit 제외) +if [ "$CUSTOM_PRECOMMIT" = "True" ]; then + LOCAL_HASH=$(find .claude/rules .claude/agents .claude/scripts .githooks \ + .claude/skills/push .claude/skills/mr .claude/skills/create-mr \ + .claude/skills/release .claude/skills/version .claude/skills/fix-issue \ + -type f ! -path '.githooks/pre-commit' 2>/dev/null | sort | xargs cat 2>/dev/null | shasum -a 256 | cut -d' ' -f1) +else + LOCAL_HASH=$(find .claude/rules .claude/agents .claude/scripts .githooks \ + .claude/skills/push .claude/skills/mr .claude/skills/create-mr \ + .claude/skills/release .claude/skills/version .claude/skills/fix-issue \ + -type f 2>/dev/null | sort | xargs cat 2>/dev/null | shasum -a 256 | cut -d' ' -f1) +fi +``` + +**비교 결과 처리**: +- **서버 조회 실패** (`SERVER_HASH` 비어있음): "⚠️ 서버 연결 불가, 워크플로우 체크를 건너뜁니다" 경고 후 다음 단계 진행 +- **일치** (`LOCAL_HASH == SERVER_HASH`): 다음 단계 진행 +- **불일치**: "⚠️ 팀 워크플로우가 최신이 아닙니다. 동기화를 실행합니다..." 출력 → **sync-team-workflow 절차를 자동 실행** → 완료 후 원래 작업 계속 + ### 1. 현재 상태 수집 ```bash diff --git a/.claude/skills/release/SKILL.md b/.claude/skills/release/SKILL.md index 8369a99..4d99a21 100644 --- a/.claude/skills/release/SKILL.md +++ b/.claude/skills/release/SKILL.md @@ -29,6 +29,43 @@ IS_ADMIN=$(echo "$PERMISSIONS" | python3 -c "import sys,json; print(json.load(sy - `IS_ADMIN`이 `False`이면: "릴리즈는 프로젝트 관리자만 실행할 수 있습니다." 안내 후 종료 +### 0.5. 팀 워크플로우 최신화 확인 + +`.claude/workflow-version.json`이 존재하지 않으면 이 단계를 건너뛴다 (팀 프로젝트가 아닌 경우). + +```bash +# 로컬 설정 읽기 +GITEA_URL=$(python3 -c "import json; print(json.load(open('.claude/workflow-version.json')).get('gitea_url', 'https://gitea.gc-si.dev'))" 2>/dev/null) +PROJECT_TYPE=$(python3 -c "import json; print(json.load(open('.claude/workflow-version.json')).get('project_type', ''))" 2>/dev/null) +CUSTOM_PRECOMMIT=$(python3 -c "import json; print(json.load(open('.claude/workflow-version.json')).get('custom_pre_commit', False))" 2>/dev/null) + +# 서버 해시 조회 (custom_pre_commit이면 pre-commit 제외 해시 사용) +SERVER_VER=$(curl -sf --max-time 5 "${GITEA_URL}/gc/template-common/raw/branch/develop/workflow-version.json") +if [ "$CUSTOM_PRECOMMIT" = "True" ]; then + SERVER_HASH=$(echo "$SERVER_VER" | python3 -c "import sys,json; print(json.load(sys.stdin).get('content_hashes_custom_precommit',{}).get('${PROJECT_TYPE}',''))" 2>/dev/null) +else + SERVER_HASH=$(echo "$SERVER_VER" | python3 -c "import sys,json; print(json.load(sys.stdin).get('content_hashes',{}).get('${PROJECT_TYPE}',''))" 2>/dev/null) +fi + +# 로컬 해시 계산 (custom_pre_commit이면 .githooks/pre-commit 제외) +if [ "$CUSTOM_PRECOMMIT" = "True" ]; then + LOCAL_HASH=$(find .claude/rules .claude/agents .claude/scripts .githooks \ + .claude/skills/push .claude/skills/mr .claude/skills/create-mr \ + .claude/skills/release .claude/skills/version .claude/skills/fix-issue \ + -type f ! -path '.githooks/pre-commit' 2>/dev/null | sort | xargs cat 2>/dev/null | shasum -a 256 | cut -d' ' -f1) +else + LOCAL_HASH=$(find .claude/rules .claude/agents .claude/scripts .githooks \ + .claude/skills/push .claude/skills/mr .claude/skills/create-mr \ + .claude/skills/release .claude/skills/version .claude/skills/fix-issue \ + -type f 2>/dev/null | sort | xargs cat 2>/dev/null | shasum -a 256 | cut -d' ' -f1) +fi +``` + +**비교 결과 처리**: +- **서버 조회 실패** (`SERVER_HASH` 비어있음): "⚠️ 서버 연결 불가, 워크플로우 체크를 건너뜁니다" 경고 후 다음 단계 진행 +- **일치** (`LOCAL_HASH == SERVER_HASH`): 다음 단계 진행 +- **불일치**: "⚠️ 팀 워크플로우가 최신이 아닙니다. 동기화를 실행합니다..." 출력 → **sync-team-workflow 절차를 자동 실행** → 완료 후 원래 작업 계속 + ### 1. 사전 검증 - 커밋되지 않은 변경 사항이 있으면 경고 ("먼저 /push로 커밋하세요") diff --git a/.githooks/commit-msg b/.githooks/commit-msg index 93bb350..67be0a9 100755 --- a/.githooks/commit-msg +++ b/.githooks/commit-msg @@ -20,9 +20,10 @@ fi # Conventional Commits 정규식 # type(scope): subject # - type: feat|fix|docs|style|refactor|test|chore|ci|perf (필수) -# - scope: 영문, 숫자, 한글, 점, 밑줄, 하이픈 허용 (선택) -# - subject: 1~72자, 한/영 혼용 허용 (필수) -PATTERN='^(feat|fix|docs|style|refactor|test|chore|ci|perf)(\([a-zA-Z0-9가-힣._-]+\))?: .{1,72}$' +# - scope: 괄호 제외 모든 문자 허용 — 한/영/숫자/특수문자 (선택) +# - subject: 1자 이상 (길이는 바이트 기반 별도 검증) +PATTERN='^(feat|fix|docs|style|refactor|test|chore|ci|perf)(\([^)]+\))?: .+$' +MAX_SUBJECT_BYTES=200 # UTF-8 한글(3byte) 허용: 72문자 ≈ 최대 216byte FIRST_LINE=$(head -1 "$COMMIT_MSG_FILE") @@ -58,3 +59,13 @@ if ! echo "$FIRST_LINE" | grep -qE "$PATTERN"; then echo "" exit 1 fi + +# 길이 검증 (바이트 기반 — UTF-8 한글 허용) +MSG_LEN=$(echo -n "$FIRST_LINE" | wc -c | tr -d ' ') +if [ "$MSG_LEN" -gt "$MAX_SUBJECT_BYTES" ]; then + echo "" + echo " ✗ 커밋 메시지가 너무 깁니다 (${MSG_LEN}바이트, 최대 ${MAX_SUBJECT_BYTES})" + echo " 현재 메시지: $FIRST_LINE" + echo "" + exit 1 +fi diff --git a/.test-env b/.test-env new file mode 100644 index 0000000..bcbf938 --- /dev/null +++ b/.test-env @@ -0,0 +1 @@ +test 2026. 02. 16. (월) 07:26:28 KST diff --git a/update-hash.sh b/update-hash.sh new file mode 100644 index 0000000..43438e1 --- /dev/null +++ b/update-hash.sh @@ -0,0 +1,153 @@ +#!/bin/bash +#============================================================================== +# update-hash.sh — 팀 워크플로우 content_hashes 갱신 스크립트 +# +# template-common 리포 루트에서 실행한다. +# common 파일 + 각 타입별 파일을 결합하여 SHA256 해시를 계산하고 +# workflow-version.json의 content_hashes 필드를 갱신한다. +# +# 사용법: +# cd template-common && bash update-hash.sh +# +# 의존성: curl, python3, shasum (macOS) 또는 sha256sum (Linux) +#============================================================================== +set -e + +# shasum 호환 (macOS: shasum, Linux: sha256sum) +if command -v shasum &>/dev/null; then + SHA_CMD="shasum -a 256" +elif command -v sha256sum &>/dev/null; then + SHA_CMD="sha256sum" +else + echo "ERROR: shasum 또는 sha256sum이 필요합니다." + exit 1 +fi + +# Gitea URL 읽기 +GITEA_URL=$(python3 -c "import json; print(json.load(open('workflow-version.json')).get('gitea_url', 'https://gitea.gc-si.dev'))" 2>/dev/null) +if [ -z "$GITEA_URL" ]; then + echo "ERROR: workflow-version.json에서 gitea_url을 읽을 수 없습니다." + exit 1 +fi + +TYPES="java-maven java-gradle react-ts" +TYPE_SPECIFIC_FILES=".claude/rules/code-style.md .claude/rules/naming.md .claude/rules/testing.md .githooks/pre-commit" + +# 임시 디렉토리 +WORK_TMPDIR=$(mktemp -d) +trap "rm -rf $WORK_TMPDIR" EXIT + +echo "=== 팀 워크플로우 해시 갱신 ===" +echo "Gitea URL: $GITEA_URL" +echo "" + +# 1. common 관리 파일을 임시 디렉토리에 복사 +MANAGED_DIRS=".claude/rules .claude/agents .claude/scripts .githooks" +MANAGED_SKILLS=".claude/skills/push .claude/skills/mr .claude/skills/create-mr .claude/skills/release .claude/skills/version .claude/skills/fix-issue" + +mkdir -p "$WORK_TMPDIR/common" +for dir in $MANAGED_DIRS $MANAGED_SKILLS; do + if [ -d "$dir" ]; then + mkdir -p "$WORK_TMPDIR/common/$dir" + find "$dir" -type f -exec sh -c 'cp "$1" "'"$WORK_TMPDIR/common"'/$1"' _ {} \; + fi +done + +COMMON_COUNT=$(cd "$WORK_TMPDIR/common" && find . -type f | wc -l | tr -d ' ') +echo "Common 파일: ${COMMON_COUNT}개" + +# 2. 각 타입별 해시 계산 → 파일로 저장 +HASH_FILE="$WORK_TMPDIR/hashes.json" +HASH_NO_PC_FILE="$WORK_TMPDIR/hashes_no_pc.json" +echo "{" > "$HASH_FILE" +echo "{" > "$HASH_NO_PC_FILE" + +FIRST=true +for TYPE in $TYPES; do + echo "" + echo "--- $TYPE ---" + + # common 파일 복사 + rm -rf "$WORK_TMPDIR/combined" + cp -r "$WORK_TMPDIR/common" "$WORK_TMPDIR/combined" + + # 타입별 파일 다운로드 + TYPE_COUNT=0 + for FILE in $TYPE_SPECIFIC_FILES; do + URL="${GITEA_URL}/gc/template-${TYPE}/raw/branch/develop/${FILE}" + TARGET="$WORK_TMPDIR/combined/$FILE" + mkdir -p "$(dirname "$TARGET")" + + if curl -sf --max-time 10 "$URL" -o "$TARGET" 2>/dev/null; then + echo " ✓ $FILE" + TYPE_COUNT=$((TYPE_COUNT + 1)) + else + echo " ✗ $FILE (다운로드 실패, 건너뜀)" + fi + done + + # 해시 계산 — 전체 (find | sort | xargs cat | shasum) + HASH=$(cd "$WORK_TMPDIR/combined" && find .claude/rules .claude/agents .claude/scripts .githooks \ + .claude/skills/push .claude/skills/mr .claude/skills/create-mr \ + .claude/skills/release .claude/skills/version .claude/skills/fix-issue \ + -type f 2>/dev/null | sort | xargs cat 2>/dev/null | $SHA_CMD | cut -d' ' -f1) + + # 해시 계산 — pre-commit 제외 (custom_pre_commit 프로젝트용) + HASH_NO_PC=$(cd "$WORK_TMPDIR/combined" && find .claude/rules .claude/agents .claude/scripts .githooks \ + .claude/skills/push .claude/skills/mr .claude/skills/create-mr \ + .claude/skills/release .claude/skills/version .claude/skills/fix-issue \ + -type f ! -path '.githooks/pre-commit' 2>/dev/null | sort | xargs cat 2>/dev/null | $SHA_CMD | cut -d' ' -f1) + + if [ "$FIRST" = true ]; then + FIRST=false + else + echo "," >> "$HASH_FILE" + echo "," >> "$HASH_NO_PC_FILE" + fi + printf ' "%s": "%s"' "$TYPE" "$HASH" >> "$HASH_FILE" + printf ' "%s": "%s"' "$TYPE" "$HASH_NO_PC" >> "$HASH_NO_PC_FILE" + + echo " 타입별 파일: ${TYPE_COUNT}개 추가" + echo " 해시(전체): $HASH" + echo " 해시(pre-commit 제외): $HASH_NO_PC" +done + +echo "" >> "$HASH_FILE" +echo "}" >> "$HASH_FILE" +echo "" >> "$HASH_NO_PC_FILE" +echo "}" >> "$HASH_NO_PC_FILE" + +# 3. workflow-version.json 갱신 +echo "" +echo "=== workflow-version.json 갱신 ===" + +python3 -c " +import json + +with open('$HASH_FILE', 'r') as f: + hashes = json.load(f) + +with open('$HASH_NO_PC_FILE', 'r') as f: + hashes_no_pc = json.load(f) + +with open('workflow-version.json', 'r') as f: + data = json.load(f) + +data['content_hashes'] = hashes +data['content_hashes_custom_precommit'] = hashes_no_pc + +with open('workflow-version.json', 'w') as f: + json.dump(data, f, indent=2, ensure_ascii=False) + f.write('\n') + +print('content_hashes 갱신 완료:') +for t, h in hashes.items(): + print(f' {t}: {h}') +print() +print('content_hashes_custom_precommit 갱신 완료:') +for t, h in hashes_no_pc.items(): + print(f' {t}: {h}') +" + +echo "" +echo "✅ 완료. workflow-version.json을 커밋하세요." diff --git a/workflow-version.json b/workflow-version.json index 56a8f81..34a4d36 100644 --- a/workflow-version.json +++ b/workflow-version.json @@ -1,15 +1,67 @@ { - "version": "1.5.0", - "updated": "2026-03-01", + "version": "1.6.1", + "updated": "2026-03-06", "gitea_url": "https://gitea.gc-si.dev", "nexus_url": "https://nexus.gc-si.dev", + "content_hashes": { + "java-maven": "", + "java-gradle": "", + "react-ts": "" + }, + "content_hashes_custom_precommit": { + "java-maven": "", + "java-gradle": "", + "react-ts": "" + }, "changes": [ + { + "version": "1.6.1", + "date": "2026-03-06", + "description": "custom_pre_commit 프로젝트 해시 불일치 해결", + "items": [ + "content_hashes_custom_precommit: pre-commit 제외 해시 필드 추가", + "push/mr/release Step 0.5: custom_pre_commit 플래그에 따라 해시 비교 분기", + "update-hash.sh: 두 세트 해시 동시 계산 (전체 + pre-commit 제외)", + "적용: 자동 (기존 v1.6.0 프로젝트는 /push 시 해시 차이로 자동 동기화)" + ], + "affected_files": [ + ".claude/skills/push/SKILL.md", + ".claude/skills/mr/SKILL.md", + ".claude/skills/release/SKILL.md", + "update-hash.sh", + "workflow-version.json" + ] + }, + { + "version": "1.6.0", + "date": "2026-03-06", + "description": "해시 기반 동기화 + 자동 최신화 강제", + "items": [ + "push/mr/release: SHA256 해시 체크 → 불일치 시 자동 동기화", + "sync-team-workflow: 해시 기반 전면 재작성 (버전 비교 제거, 항상 서버 기준 동기화)", + "init-project: 마지막에 sync 자동 호출, .gitignore에 팀 관리 경로 추가", + "팀 관리 파일 .gitignore 처리 (리포 커밋 방지, rollback 위험 제거)", + ".claude/scripts/ 파일을 template-common에 분리 저장", + "update-hash.sh: 관리자용 해시 갱신 스크립트 추가", + "적용: 프로젝트에서 /sync-team-workflow 실행" + ], + "affected_files": [ + ".claude/skills/sync-team-workflow/SKILL.md", + ".claude/skills/push/SKILL.md", + ".claude/skills/mr/SKILL.md", + ".claude/skills/release/SKILL.md", + ".claude/skills/init-project/SKILL.md", + ".claude/scripts/*", + "workflow-version.json", + "update-hash.sh" + ] + }, { "version": "1.5.0", "date": "2026-03-01", "description": "2계층 릴리즈 노트 + 권한 기반 스킬 체계 + Claude 봇 PR 승인", "items": [ - "/mr: 권한 확인 + docs/RELEASE-NOTES.md [Unreleased] 자동 갱신 + 초안 첨삭", + "/mr: 권한 확인 + docs/RELEASE-NOTES.md [Unreleased] 자동 갱신 + 초안 첨삭 + 봇 승인/머지 옵션", "/release: 권한 확인(admin) + 날짜 압축 + 봇 PR 승인/머지 + 미기록 커밋 감지", "/version: RELEASE-NOTES.md 기반 docs/VERSION-HISTORY.md 생성 (신규 스킬)", "/push: Gitea API 권한 확인 추가",