projectops
CLAUDE.md6 starsChanged 32 days agoMITView on GitHub
완전 자동화된 GitHub 프로젝트 관리 템플릿 이 프로젝트는 develop 브랜치에서 직접 작업하는 것을 기본값으로 한다. main은 프로덕션(default) — 직접 커밋·push 금지. 여러 Claude Code 세션이 같은 develop에서 동시에 작업한다. 각 세션은 서로의 존재나 작업 내용을 알지 못한다. 내가 만들지 않은 변경이 언제든 작업트리에 나타날 수 있다는 전제로 움직인다. 의도를 모르는 변경이 작업트리에 있으면 건드리지 말고 그대로 둔다. 내 변경만 커밋하고 나머지는 그 세션이 처리하도록 남겨두는 것이 안전하다. 혼동 주의: 프로젝트에는 브랜치가 *3개념*이 있고, version.yml 필드명이 직관과 어긋난다. ⚠️ 키 이름은…
- Pipes a download into a shell
- Reads credentials
- Deletes or force-pushes
- Installs packages
- Commits and pushes
In this repository17 other files it gives its agents.
- AGENTS.mdAGENTS.md
- llms.txtllms.txt
- pro-agent-testSkill
- pro-changelog-deploySkill
- pro-commitSkill
- pro-desktop-controlSkill
- pro-figmaSkill
- pro-githubSkill
- pro-init-worktreeSkill
- pro-launchSkill
- pro-oss-consultSkill
- pro-reportSkill
- pro-reviewSkill
- pro-skill-creatorSkill
- pro-sshSkill
- pro-testcaseSkill
- pro-ui-designSkill
The file
Files
Projectops
완전 자동화된 GitHub 프로젝트 관리 템플릿
⚠️ 작업 브랜치 규칙 (agent 필독)
이 프로젝트는 develop 브랜치에서 직접 작업하는 것을 기본값으로 한다. main은 프로덕션(default) — 직접 커밋·push 금지.
- 별도 지시가 없으면 feature 브랜치를 만들지 말고
develop에서 작업·커밋·푸시한다. developpush 전에는 항상git pull --rebase origin develop으로 먼저 동기화한다 (릴리스 시 버전 확정 커밋이 develop에 추가되므로 로컬이 뒤처지기 쉽다).- 릴리스(배포)는 develop→main PR로만 진행한다 (
/pro-changelog-deploy). main 직접 push는 안전망(VERSION-CONTROL 가드)이 버전만 보전할 뿐 지원 경로가 아니다. git push는 사용자가 명시적으로 요청한 경우에만 실행한다.- 사용자가 명시적으로 브랜치 작업을 요청한 경우에만 feature 브랜치를 사용한다.
- 커밋은
/pro-commit을 사용한다. 브랜치명에서 이슈 번호를 뽑아 컨벤션에 맞는 메시지를 만들고, 스테이징 대상도 함께 확인시켜 준다.
여러 에이전트가 동시에 작업한다 (agent 필독)
여러 Claude Code 세션이 같은 develop에서 동시에 작업한다. 각 세션은 서로의 존재나
작업 내용을 알지 못한다. 내가 만들지 않은 변경이 언제든 작업트리에 나타날 수 있다는
전제로 움직인다.
| 상황 | 대응 |
| 작업 시작 전 | git pull --rebase origin develop로 최신 상태를 먼저 받는다 |
| 커밋 전 | git status로 내가 만들지 않은 변경이 섞였는지 확인한다 |
| 스테이징 | git add -A보다 내가 건드린 경로만 명시해 담는다 |
| 브랜치가 바뀌어 있음 | 다른 세션이 옮긴 것일 수 있다. 임의로 되돌리지 말고 현재 브랜치를 존중한다 |
| 푸시 거부(non-fast-forward) | 강제 푸시하지 않는다. rebase로 통합한 뒤 다시 푸시한다 |
금지
git push --force/--force-with-lease— 다른 세션의 커밋이 사라진다git checkout .·git reset --hard— 다른 세션의 미커밋 작업을 날린다- 내 작업과 무관한 파일을 함께 커밋하는 것 — 원인 추적이 불가능해진다
의도를 모르는 변경이 작업트리에 있으면 건드리지 말고 그대로 둔다. 내 변경만 커밋하고 나머지는 그 세션이 처리하도록 남겨두는 것이 안전하다.
브랜치 3개념 (agent·마법사 필독 — #482)
혼동 주의: 프로젝트에는 브랜치가 3개념이 있고, version.yml 필드명이 직관과 어긋난다.
| 개념 | 뜻 | version.yml 필드 | 표준값 |
| 기본(배포) 브랜치 | 레포 default·프로덕션. push되면 배포가 도는 곳 | default_branch | main |
| 개발(릴리스 소스) 브랜치 | 개발을 모아 기본 브랜치로 올리는 릴리스 PR의 head | development_branch (구 deploy_branch) | develop |
| 이슈 브랜치 | 작업 단위 (YYYYMMDD_#번호_제목) | — | — |
⚠️ 키 이름은
development_branch가 정본이다 (#915). 구 이름deploy_branch는 "배포 브랜치"로 오독되는 이름이라 정본에서 내렸고, 하위호환용으로만 남는다.
- 읽기: 모든 소비자가
development_branch→ 없으면deploy_branch→ 없으면develop순으로 읽는다 (src/core/version-yml.jsreadDevelopmentBranch,changelog_cli.py_parse_release_branches).- 쓰기: 생성기는 두 키를 같은 값으로 함께 쓴다 — 구 키만 읽는 구버전 스킬(플러그인은 레포와 따로 업데이트된다)이 조용히
develop로 폴백하지 않게 하기 위해서다. 업데이트(마법사 재실행)가 구 키만 있는 파일을 두 키로 다시 쓴다.- 워크플로우는 런타임에 이 키를 읽지 않는다 (설치 시 브랜치명을 치환해 넣는다).
- 사용자 노출 문구에서 이 브랜치를 "배포 브랜치"라 부르지 말고 "개발(릴리스 소스) 브랜치"로 칭한다. CLI 플래그
--deploy-branch와 내부 변수명deployBranch는 그대로다.
⚠️ 이슈 처리 규칙 (agent 필독)
이 프로젝트는 이슈가 완료되면 완료 라벨을 붙이고 이슈를 닫는다(close). (2026-10-10 소유자 결정으로 변경 — 이전엔 닫지 않고 라벨만 바꿨다.)
- 작업이 끝나면 이슈 상태 라벨을
status: done(기존 한글 레포는작업완료)으로 바꾼 뒤 이슈를 닫는다.finish-issue {owner} {repo} {번호} [--outcome done|cancelled|in-progress]로 한다 — 상태 라벨만 바꾸고(type:·area:등 다른 라벨 유지) 닫는다.set-labels는 라벨 전체를 교체하므로set-labels ... "status: done"만 넘기면 다른 라벨이 전부 지워진다.add-labels만 쓰면 이전 상태 라벨이 남아 상태가 두 개가 된다. - 완료 기준을 지킨다 — 구현이 main(또는 개발 브랜치)에 반영되고, 검증과 보고서(
/pro-report)까지 끝난 것만 닫는다. 사용자가 특정 이슈를 명시적으로 완료 처리하라고 하면 이 기준을 다시 따지지 않고 처리한다. 남은 하위 작업이 있으면 닫지 않고status: in progress로 둔다. 취소된 작업은status: cancelled와 함께 닫는다. - 라벨 변경과 close는
/pro-github(github_cli.py finish-issue)로 처리한다. 일부 단계만 실패하면failed_steps의 단계만set-labels(기존 라벨에서 상태만 바꾼 전체 목록) /close-issue로 다시 한다. - 릴리스 워크플로우도 닫는다 (#771,
close_on_release). 완료 라벨이 붙은 이슈 중 릴리스 구간 커밋이 URL로 참조한 것을 닫는다. 에이전트가 먼저 닫아도 충돌하지 않는다(이미 닫힌 이슈는 건너뛴다). 이 저장소는version.yml에서 켜져 있다. - 업데이트는 사용자가 지운·제외한 워크플로우를 되살리지 않는다 (#810). baseline에 있는데 없는 파일,
options.excluded_workflows에 적힌 파일, 개발 브랜치가 원격에 없는데develop에서만 도는 파일은 건너뛰고incoming/에 사본을 남긴다. 새 조건부 설치 경로를 추가하면copy/workflows.js의skipNew게이트를 거치게 한다. - 상태 라벨은 영문 표준(
status: todo등)과 기존 한글(작업전등)을 둘 다 쓴다 (#776).label_style이 표기를 정하고(신규en, 기존ko유지), 스킬 공통 라벨 함수와 Projects 동기화는 두 표기를 서로 바꿔 인식한다.
프로젝트 개요
지원 프로젝트 타입
| 타입 | 설명 | 버전 동기화 파일 |
spring | Spring Boot | build.gradle |
flutter | Flutter | pubspec.yaml |
react | React.js / Next.js | package.json |
node | Node.js | package.json |
python | FastAPI/Django | pyproject.toml |
react-native | React Native CLI | Info.plist + build.gradle |
react-native-expo | Expo | app.json |
basic | 범용 | version.yml만 |
멀티타입: 단일 레포에 여러 타입 공존 시
--type spring,react,pythoncsv로 지정.version.yml의project_types배열에 저장되며, 첫 항목이 primary 타입이다 (단수project_type키는 v4.1.0에서 제거됨 — SSOT).모노레포 경로: 타입별 프로젝트가 서브폴더에 있으면(예:
app/,client/,ai/)version.yml의project_paths맵(타입 → 레포 루트 기준 상대경로)으로 지정한다. integrator가 통합 시 마커 파일(pubspec.yaml·package.json·pyproject.toml·build.gradle등)을 자동 감지·확인하며, 키가 없으면 루트 기준(기존 동작 100% 유지). 비대화형은--paths "flutter=app,react=client"(.ps1은-Paths).version_manager.sh가 이 경로를 따라 서브폴더 버전 파일을 동기화하므로,PROJECT-COMMON-VERSION-CONTROL워크플로우는 무수정으로 모노레포를 커버한다.
폴더 구조
projectops/
├── .github/
│ ├── workflows/
│ │ ├── PROJECT-TEMPLATE-INITIALIZER.yaml
│ │ ├── PROJECT-COMMON-*.yaml
│ │ └── project-types/
│ │ ├── common/ # 공통 원본 (+ secret-backup/ opt-in, deploy/vercel/ 배포타겟)
│ │ ├── flutter/ # Flutter 전용 (스토어 배포 워크플로우 루트 포함)
│ │ ├── spring/ # Spring 전용 (server-deploy/ + publish/{nexus,github-packages}/)
│ │ ├── python/ # Python 전용 (CI 루트 + server-deploy/)
│ │ ├── react/ # React/Next.js 공용 (next 타입은 v4.1.0에서 흡수됨)
│ │ └── node/ # Node 전용 (publish/npm/)
│ ├── scripts/
│ │ ├── version_manager.sh # + version_manager.py (실 로직, #448)
│ │ ├── changelog_manager.py
│ │ ├── truncate_release_notes.sh # + .py (실 로직, #448)
│ │ └── template_initializer.sh
│ ├── util/
│ │ ├── flutter/
│ │ │ ├── playstore-wizard/
│ │ │ ├── testflight-wizard/
│ │ │ ├── firebase-wizard/
│ │ │ └── oauth-wizard/ # Kakao·Google 로그인 (#156)
│ │ └── common/
│ │ ├── projects-sync-wizard/
│ │ ├── github-projects-sync-worker/
│ │ └── github-secrets-converter/
│ ├── ISSUE_TEMPLATE/
│ └── PULL_REQUEST_TEMPLATE.md
├── .claude-plugin/ # 플러그인 매니페스트
├── skills/ # 플러그인 Skills (마켓플레이스 전용)
├── scripts/ # 플러그인 Scripts (마켓플레이스 전용)
├── .cursor/skills/
├── docs/ # 상세 문서
├── version.yml
└── CHANGELOG.md / CHANGELOG.json네이밍 컨벤션
워크플로우 파일
PROJECT-[TYPE]-[FEATURE]-[DETAIL].yaml
TYPE: TEMPLATE | COMMON | FLUTTER | SPRING | REACT스크립트 파일
snake_case.sh / snake_case.py핵심 워크플로우
공통 워크플로우
| 파일명 | 트리거 | 기능 |
PROJECT-TEMPLATE-INITIALIZER | 저장소 생성 | 템플릿 초기화 (일회성) |
PROJECT-TEMPLATE-PLUGIN-VERSION-SYNC | main 푸시 | 플러그인 매니페스트 버전 동기화 (이 레포 전용) |
PROJECT-TEMPLATE-CI | develop·main 푸시/PR | 테스트 — 빠른 길/넓은 길 2갈래 (이 레포 전용, 아래 절) |
PROJECT-TEMPLATE-NPM-PUBLISH | main 푸시 | 같은 커밋의 CI job이 전부 통과한 뒤 projectops npm 패키지 배포 (이 레포 전용) |
PROJECT-COMMON-VERSION-CONTROL | main 직접 푸시(안전망) | 릴리스 머지 외 push 시 patch 증가 |
PROJECT-COMMON-RELEASE-CHANGELOG | main PR (develop→main) | 버전 확정(semver 승격 — #546) + AI 체인지로그 + automerge |
PROJECT-COMMON-README-VERSION-UPDATE | main 푸시 | README 버전 동기화 |
PROJECT-COMMON-SUH-ISSUE-HELPER | 이슈 생성 | 브랜치명/커밋 제안 (내부 py — issue_helper.py, #478 내재화) |
PROJECT-COMMON-QA-ISSUE-CREATION-BOT | @projectops 멘션 | QA 이슈 자동 생성 |
PROJECT-COMMON-SYNC-ISSUE-LABELS | 라벨 파일 변경 | GitHub 라벨 동기화 |
PROJECT-COMMON-TEMPLATE-UTIL-VERSION-SYNC | version.json 변경 | Util HTML 버전 동기화 (util 모듈 보유 레포에만 복사 — #491) |
PROJECT-COMMON-PROJECTS-SYNC-MANAGER | 이슈 라벨 변경 | Issue Label → Projects Status 동기화 (선택 — common/projects-sync/, --projects-sync, #716) |
PROJECT-COMMON-AI-PR-SUMMARY | 작업 PR 생성·갱신 | 변경 요약 댓글 (선택 — common/pr-summary/, #566) |
⚠️ Windows는 cp949와 비ASCII 경로까지 봐야 검증이다 (agent 필독)
영문 경로와 UTF-8 모드로 통과한 것은 Windows 검증이 아니다. 일반 사용자 환경은 시스템 코드페이지가 cp949이고 폴더 이름에 한글과 공백이 있다.
| 함정 | 증상 | 지키는 방법 |
fs.cpSync + 비ASCII 경로 (Node 22 이상) | 메시지 없이 프로세스 종료, 임시 폴더 잔존 | fsutil.copyPathSync. 규칙 no-fs-cpsync |
subprocess.run(text=True)만 쓰기 | 한글 출력에서 stdout이 None, 다음 줄에서 크래시 | git은 encoding="utf-8", errors="replace", 그 외 errors="replace". 규칙 no-subprocess-text-without-errors |
Windows용 안내 명령에 && | PowerShell 5.1 파싱 오류 | ;로 잇는다 |
PYTHONUTF8=1이 켜진 개발 PC에서는 위 결함이 전부 숨는다. 확인할 때unset PYTHONUTF8 PYTHONIOENCODING으로 재현한다.- 전역 git
core.longpaths도 마찬가지다. 새 사용자처럼 빈HOME으로 돌려 본다. - 스모크(
matrix-smoke)는 3 OS, Node 20과 24에서 비ASCII 공백 경로 설치까지 돈다.
⚠️ 테스트는 감시용 5개만 둔다 (agent 필독)
AI가 작업마다 쌓은 테스트(약 250개 파일, 3만 줄)를 걷어 내고, 여러 파일에 흩어진 약속이 서로 맞는지 보는 감시 검사만 남겼다. 사람이 눈으로 놓치는 것만 잡고, 작고 빠르다.
| 파일 | 지키는 것 |
test/rename-consistency.test.js | 스킬 개수·목록이 폴더 ↔ 매니페스트 ↔ README·CLAUDE.md에서 같다 |
test/options-schema.test.js | CLI 플래그·version.yml 옵션이 정본(src/core/options-schema.js)과 같다 |
scripts/tests/test_skill_docs.py | 스킬 description이 짧고(300자) 영어다 |
scripts/tests/test_korean_line_budget.py | 영문 표면의 한글 줄이 늘지 않는다 (아래 절) |
.github/scripts/test/test_workflow_permissions.py | 모든 워크플로우가 permissions:를 선언한다 |
- 새 테스트 파일을 커밋하지 않는다. 구현 확인은 스크래치 폴더의 임시 테스트나 직접 실행으로 하고 지운다.
- 감시 검사를 더 두려면 "여러 파일의 약속이 어긋나는 것을 사람이 놓친 실제 사고"가 있을 때만 사용자와 상의한다.
PROJECT-TEMPLATE-CI: develop은 ubuntu 1대에서 위 5개 + 마법사 정합성(check-consistency.py) + CLI 스모크, main은 3 OS에서 CLI 스모크(macOS bash 3.2·Windows.cmd차이 #415·#418).
⚠️ 테스트 코드는 사용자에게 나가지 않는다 (#591)
skills/ 는 Cursor·GitHub Copilot 설치 소스라 통째로 ~/.cursor/skills/·~/.copilot/skills/ 로 복사된다 (#200).
스킬 CLI 가 import 하는 scripts/(common 등)도 옆 ~/.cursor/scripts/·~/.copilot/scripts/ 로 함께 복사된다 — 빠지면 CLI 가 import 에서 죽는다.
그래서 skills/*/tests/·scripts/tests/·conftest.py를 거르지 않으면 우리 테스트가 남의 컴퓨터에 쌓인다.
두 어댑터가 함께 쓰는 src/core/ide/skills-copy.js의 SKIP_IN_COPY·TEST_PATH가 막는다.
scripts/ 의 관리자 전용 파일(README.md·build_troubleshooting.py·sync_extras.py)은 SHARED_SCRIPTS_DEV_ONLY 가 뺀다 (#988).
스킬을 추가하거나 이름을 바꾸면 scripts/projectops_cli.py의 SKILL_CLIS와 RESOLVER_REVISION(+1)을 함께 고친다 (#1080 실사고: 번호를 안 올려 기존 사용자의 고정 진입점이 새 스킬을 bad_args로 거부).
scripts/ 최상위에 관리자 전용 파일을 새로 두면 이 목록에 넣는다. 런타임이 쓰는 항목을 넣으면 설치본 CLI가 import 에서 죽는다.
스킬에 새 보조 파일을 추가할 때 "이게 스킬을 쓰는 사람에게 필요한가"를 자문한다.
⚠️ Flutter 마법사 4종은 공통 자산을 공유한다 (#521 — agent 필독)
testflight / playstore / firebase / oauth(#156) 마법사는 .github/util/flutter/_shared/의 정본을 함께 쓴다.
과거 각자 복사본을 들고 있다가 CSS 377줄 중복·같은 클래스명 반대 의미·iOS에만 개선 축적 같은 드리프트가 생겨 통합했다.
| 파일 | 역할 |
_shared/wizard.css | 공통 컴포넌트 스타일 (툴팁·모달·토스트·코드블록·커스텀 Secret 등) |
_shared/wizard-common.js | 공통 유틸 (이스케이프·클립보드·파일 변환·OS 감지·상태 저장·변경 이력 모달) |
_shared/wizard-i18n.js | 언어 전환 엔진 (영문 기본·한국어 전환, #769). common 마법사도 로드한다. 한국어는 <name>-wizard-i18n.js 사전에 |
_shared/check-consistency.py | 4종 정합성 검증 — CI(PROJECT-TEMPLATE-CI)가 실행한다 (check_i18n.py로 사전 키 누락·한글 잔존도 검사) |
마법사를 건드렸으면 반드시 통과시킬 것:
python3 .github/util/flutter/_shared/check-consistency.py- 공통 유틸을 마법사 JS에서 재정의하지 않는다 (정본이 무력화된다 — 검증기가 잡는다).
- 로드 순서 고정:
wizard.css→wizard-common.js→<name>-wizard.js. 공통이 뒤로 가면 마법사 구현을 덮어쓴다. .step-indicator는 "개별 단계 항목"이다 (전체 컨테이너 아님). firebase가 반대로 써서 충돌했던 자리다.- oauth 마법사는 단계가 동적이다 — 고른 제공자(Kakao·Google)의 단계만
data-step순번을 받아, 공통showStep·prevStep을 덮어쓰지 않고 쓴다..py는 위치인자 이력이 없어 argparse이고, 공통 블록은쓰기 게이트만 testflight·playstore와 글자 단위로 같게 둔다. - Python CLI 정본은 명명 플래그(
--project-path등)이고, 구 위치인자 호출도 계속 받는다(마법사 HTML이 배포한 명령어가 그 형식). --dry-run은 선언만 하면 안 된다. 쓰기·복사·이동·삭제·디렉터리 생성·keytool이 전부 게이트 함수를 거쳐야 하고, "쓰고 다시 읽어 검증"하는 단계 때문에 메모리 오버레이로 실제 실행과 같은 경로를 타야 한다.version.json을 고쳤으면 해당 마법사의./version-sync.sh를 실행해야 화면 변경 이력에 반영된다._로 시작하는 폴더는 모듈이 아니라 공유 자산이다 (src/core/copy/util.js의 모듈 카운트에서 제외됨).
상세: .github/util/flutter/_shared/README.md
⚠️ 워크플로가 부르는 fastlane lane 은 실재해야 한다 (#601 — agent 필독)
Fastfile 은 마법사가 덮어쓴다. lane 을 손으로 더하면 다시 깔 때 사라지므로, 워크플로를 있는 lane 에 맞춘다. 반대로 하면 안 된다.
| 템플릿 | 정의하는 lane |
testflight-wizard/templates/Fastfile.ios.template | deploy |
playstore-wizard/templates/Fastfile.playstore.template | build_aab · deploy_internal · promote_internal_to_production · validate |
실사고: iOS 테스트 빌드가 없는 upload_testflight 를 불러 한 번도 TestFlight 에 올라간
적이 없었다. IPA 는 정상 생성되고 마지막 업로드 한 스텝에서만 죽어서 오래 안 보였다.
배포 경로는 맞는 lane 을 불러 멀쩡했고 테스트 경로만 틀렸는데, 그 경로를 끝까지
밟아본 사람이 없었다. 같은 종류를 찾아보니 Android 쪽에도 둘 더 있었다(fastlane build).
직접 만든 Fastfile 에만 있는 lane 을 쓰려면 있는지 확인하고 부른다.
if [ -f "android/fastlane/Fastfile" ] \
&& grep -qE '^[[:space:]]*lane[[:space:]]*:build([[:space:]]|$)' android/fastlane/Fastfile; then
( cd android && bundle exec fastlane build --verbose ) # 서브셸 — cd 가 새면 뒤가 엉킨다
else
flutter build apk --release
fi🔴 테스트 빌드는
DEPLOY_MODE를 반드시 못 박는다. iOSdeploylane 은 이 값에 따라 심사 자동 제출(submit_for_review)까지 간다. 기본값에 기대고 있으면 Fastfile 기본값이 바뀌는 순간 테스트 빌드가 앱 심사에 올라간다 — 되돌릴 수 없는 종류의 사고다. 테스트 경로는export DEPLOY_MODE="store_only"를 명시한다.
⚠️ 빌드 플래그는 두 층이 함께 가야 한다 (#603 — agent 필독)
앱의 개발 스위치는 두 층에 있고, 한 층만 맞아도 빌드는 통과한다.
| 층 | 어디에 | 위조 |
| 런타임 | .env (dotenv) | 가능 (Secret 주입) |
| 컴파일타임 | --dart-define | 불가 (바이너리에 박힌다) |
실사고: .env 에 개발 플래그가 찍히고 로그는 ✅ 적용 완료 를 냈는데, 설치해 보면
디버깅 도구가 없었다. 릴리스 빌드라 컴파일타임 게이트가 닫혀 있었고, 레포 전체에
dart-define 이 한 군데도 없었다 — 즉 한 번도 켜진 적이 없었다. 로그만 봐서는 알 수 없다.
.github/scripts/apply_build_profile.py 가 한 곳에서 두 층을 모두 정한다.
- name: Apply build profile (test)
id: build_profile
run: python3 .github/scripts/apply_build_profile.py test .env
- name: Build
# 🔴 여기에 dart-define 을 직접 적지 않는다. 적는 순간 다시 두 곳이 된다.
run: flutter build apk --release ${{ steps.build_profile.outputs.build_flags }}- 설정(
.github/config/build-profile.json)이 없으면 아무 일도 하지 않는다. 남의 저장소에 설치되는 템플릿이므로 이것이 기본값이다. 예시는.example파일. release라는 이름의 프로파일에는 검증이 걸린다 — 개발 키가 켜진 채로, QA 토큰이 남은 채로, 테스트 전용 dart-define 이 섞인 채로 배포 빌드가 나가면 빌드를 세운다..env생성과 빌드가 다른 job 이면 프로파일 결과를outputs.build_flags로 넘긴다 (IOS-TEST-TESTFLIGHT·IOS-TESTFLIGHT가 그렇다). 같은 job 이면steps.로 읽는다.- fastlane lane 으로 빌드하는 경로에는 플래그를 넘길 자리가 없다. 플래그가 있는데 그 경로를 타면 빌드를 세운다 — 조용히 빠뜨리면 이 스텝이 고치려던 사고가 다시 난다.
- 플레이버 이름은
APP_FLAVOR처럼 앱 이름이 안 들어가는 중립적인 것을 쓴다. Flutter 표준FLUTTER_APP_FLAVOR는 예약어라--dart-define으로 못 넣는다 (--flavor전용이며, 그건 Gradle productFlavors·Xcode scheme 을 요구한다).
적용된 워크플로우: ANDROID-TEST-APK·IOS-TEST-TESTFLIGHT(test) ·
ANDROID-PLAYSTORE-CICD·ANDROID-FIREBASE-CICD·ANDROID-SELFHOSTED-CICD·IOS-TESTFLIGHT(release)
⚠️ 워크플로우는 permissions: 를 반드시 선언한다 (#595 — agent 필독)
선언하지 않으면 레포 기본값을 따르는데, 그 기본값은 레포마다 다르고 나중에 바뀐다.
| 기본값 | 선언 안 했을 때 |
read (GitHub 신규 레포 기본값) | 댓글을 다는 워크플로우가 그 스텝에서 죽는다 |
write | 읽기만 하면 되는 배포 워크플로우가 과한 권한을 갖는다 |
실사고: @projectops apk build 테스트 빌드가 항상 실패했다. 트리거 워크플로우에는 블록이
있어 트리거까지는 성공했고, 정작 빌드가 진행 상황 댓글을 다는 순간 막혔다.
겉보기에는 "빌드가 도는데 결과가 없다"로 보여 원인을 찾는 데 오래 걸렸다.
RequestError [HttpError]: Resource not accessible by integration| 하는 일 | 적을 것 |
| 이슈·PR에 댓글·라벨 | contents: read + issues: write + pull-requests: write |
| PR에만 댓글 | contents: read + pull-requests: write |
| 체크아웃·빌드·배포만 | contents: read |
| 커밋을 푸시 | contents: write |
| 별도 PAT로만 API 호출 | contents: read (GITHUB_TOKEN 은 안 쓴다) |
위치는 최상위 on: 블록 바로 뒤다 (job 단위로 적어도 된다).
회귀 방지: .github/scripts/test/test_workflow_permissions.py 가 모든 워크플로우를 검사한다.
⚠️
.yaml만 훑지 말 것. 이 저장소에는.yml확장자 워크플로우도 5개 있다 (NEXUS-*·GITHUB-PACKAGES-PUBLISH·TEMPLATE-UTIL-VERSION-SYNC). 실제로*.yaml만 본 첫 점검에서 한 건을 놓쳤고 테스트가 잡았다.
⚠️
repository_dispatch는 기본 브랜치의 워크플로 정의로 돈다. 이 트리거를 쓰는 워크플로우(ANDROID-TEST-APK·IOS-TEST-TESTFLIGHT·APP-BUILD-TRIGGER)를 develop 에서 고치고 테스트 빌드를 다시 걸면 여전히 옛 정의가 실행된다. 수정이 안 먹은 것처럼 보여 원인을 엉뚱한 데서 찾게 되므로, 반드시 기본 브랜치에 반영된 뒤에 확인한다.
옵션 정본은 한 곳이다 (agent 필독)
CLI 플래그와 version.yml 옵션의 이름·값·기본값은 src/core/options-schema.js 한 곳에 있고,
npx projectops --mode options --json이 그대로 출력한다. AI agent는 문서를 뒤지지 않고 이 명령으로 확인한다.
- 새 플래그(
src/cli/args.js)나version.yml옵션(parseTemplateOptions)을 추가하면 정본에도 반드시 추가한다.test/options-schema.test.js가 코드와 정본을 대조해 어긋나면 실패한다. - 문서에 옵션 표를 새로 복제하지 않는다 — 복제본은 낡는다. 문서는 정본을 가리킨다.
- 옵션 값(플래그 > 저장값 > 기본값)은
src/core/resolve-options.js한 곳에서 정한다 (#851).index.js(비대화형)는 결과를 그대로 쓰고,interactive.js는 초기값으로 쓴 뒤 사용자가 답한 것만 덮어쓴다. 진입점에 기본값을 다시 적지 않는다 — 두 경로가 갈라져 대화형에서 PR 요약 선택이 버려진 적이 있다. - 사용자 레포에는
.github/.projectops/AGENT-GUIDE.md가 설치된다(src/core/agent-guide.js). 옵션 값은 박지 않고 명령만 안내한다. - 이 레포의
AGENTS.md·GEMINI.md·llms.txt는 템플릿 전용이라exclusions.js와template_initializer.py양쪽에서 제외된다.
문서·데모 GIF는 읽는 언어를 따른다 — 기본은 영어 (#976)
| 대상 | 영어 (기본) | 한국어 |
| 문서 사이트 URL | /projectops/<slug> ← docs/en/<slug>.md | /projectops/ko/<slug> ← docs/<SLUG>.md |
| 데모 GIF | docs/images/<이름>.gif | docs/images/<이름>.ko.gif |
| 쓰는 곳 | README.md, ja·zh-CN README, docs/en/ | docs/i18n/README.ko.md, docs/*.md |
- 파일은 옮기지 않고 URL만 바꾼다. 규칙은
site/.vitepress/locale-paths.mts한 곳(rewrites와 옛 URL 리다이렉트가 같이 쓴다). 두 언어 slug가 같아야 언어 전환이 같은 쪽으로 간다 — 한국어 문서를 추가하면 같은 slug의 영어 문서도 만든다. - GIF는 손으로 찍지 않는다.
docs/recording/의 스크립트로 언어별로 다시 찍는다 (README.md참고). 시스템 언어가 새지 않게PROJECTOPS_LANG/--language를 명시하고, npm 배포본이 아니라 작업 트리 CLI·템플릿으로 찍는다. - 마법사 배포 환경값 문구(
.github/config/wizard-prompts.yml)는label=영어,label_ko=한국어다. 새 키는 두 언어를 함께 넣는다.
영문 표면의 한글 줄은 줄기만 한다 (#769)
scripts/tests/test_korean_line_budget.py가 skills·src·워크플로·스크립트·마법사·README 등에서 주석·docstring을 뺀 한글 줄 수를 파일별로 세어 동결 장부scripts/tests/korean_line_budget.json과 대조한다. 장부에 없는 파일은 0이어야 하고, 있는 파일은 장부 숫자를 넘으면 실패한다.- 한글을 걷어 내 숫자가 줄면 테스트가 "장부를 낮춰라"로 실패한다 →
python3 scripts/tests/test_korean_line_budget.py --update(줄어든 숫자만 반영, 절대 올리지 않음). - 사용자에게 보이는 문구는 영어로 쓰고 한국어는 i18n ko 사전에 넣는다. 세는 파일에 한글을 새로 넣으려면 코드가 대조하는 데이터(한글 라벨
작업전같은 것)처럼 강한 이유가 있어야 하고, 그때만 장부 숫자를 손으로 올리고 리뷰에서 이유를 밝힌다. - 제외 범위(ko 사전·
docs/·테스트·pro-launch troubleshooting 등)와 사유는 테스트 파일의EXCLUDED_PATTERNS가 정본이다.
실행 기록 3계층 (#493·#494·#561 — agent 필독)
마법사(full/workflows)가 끝나면 대상 레포의 .github/.projectops/logs/에 실행 기록을 남긴다.
docs/ 아래였던 위치를 #561에서 옮겼다 — 사람이 읽는 문서가 아니라 Agent가 원인을 짚는 진단 자료라
저장소 이력에 남을 이유가 없다.
| 계층 | 파일 | 내용 | 소스 모듈 |
| 1 | PROJECTOPS-MIGRATION-GUIDE.md | 고정 헤더(AI 해석 가이드라인) + 실행 엔트리 append-only: 동적 체크리스트·통과한 breaking 전문·yaml 메타(schema 버저닝) | src/core/migration-guide.js |
| 2 | {stamp}_v{from}_to_v{to}.jsonl | 구조화 이벤트 (파일명 grep으로 인과 추적) | src/core/run-trace.js |
| 3 | {stamp}_..._.log | 서버 로그 형식 — 내부 이벤트 + 터미널 출력이 시간순 병합 | 〃 |
건너뛴 워크플로의 새 템플릿은 incoming/에 남긴다 (#654). 사용자가 수정한 워크플로는 덮어쓰지 않고
skipped-conflict로 유지하는데, 무엇이 다른지 알 방법이 없었다. 그래서 새 템플릿(env·브랜치 치환 적용본)을
.github/.projectops/incoming/<파일명>에 저장하고, 종료 화면·skipped-conflict 이벤트 detail(incoming,
added, removed)·MIGRATION-GUIDE 체크리스트에 비교 명령(diff -u, git diff --no-index)을 남긴다.
logs/와 같은 패턴: 폴더가 자체.gitignore(*+!.gitignore)를 들고 다니므로 루트.gitignore는 건드리지 않는다..github/workflows/에는 절대 쓰지 않는다 — 거기 생긴 파일은 GitHub이 실제 워크플로로 실행한다.- 실패해도 마법사를 죽이지 않는다 — 저장·diff 실패는
incoming-failed이벤트와 경고 한 줄로 흡수한다. 종료 코드는 그대로다. - 코드:
src/core/incoming.js, 호출은copy/workflows.js의applyDecision.
추적 제외는 폴더가 스스로 들고 다닌다. logs/.gitignore(* + !.gitignore)를 매 실행마다 보장하므로
루트 .gitignore를 건드리지 않는다. 형제인 .github/.projectops/baseline.json(#557)은 팀원 공유
자산이라 계속 추적된다 — 폴더를 나눈 이유가 이것이다.
로그 레벨 (levelOf): ERROR(실패) / WARN(두면 실패하거나 판단 필요) / INFO(단계 경계·확정된 판단) /
DEBUG(건별 상세). DEBUG도 파일에는 전부 남긴다 — 문제가 터진 뒤에 "그때 켰더라면"은 소용없다.
- 엔진(copy/env/legacy/orphan)은
hooks.trace/ctx.trace로 null-safe emit — trace 미주입 경로(기존 테스트)는 무영향. - 단계는
trace.step(name, fn)/stepAsync로 감싼다 — 진입·종료·소요 ms·실패가 자동 기록된다. 개별 호출부에 start/done을 흩뿌리면 반드시 빠뜨리는 자리가 생긴다. - 가이드의 워크플로우 목록·env 값은 trace events에서 파생한다 (단일 소스). 새 엔진 동작을 추가하면 이벤트 emit도 함께 추가할 것.
- 호출은
trace.event(phase, action, target, detail)이다.emit은 존재하지 않는다 — optional chaining(?.) 때문에 오타가 예외 없이 조용히 무시되어 기록이 통째로 사라진 적이 있다(#561). - 민감값(PAT·token·secret·password·credential 키)은
scrubDetail이 이벤트에서 자동 제거 — 우회 금지. 값이 비밀이 아닌데 키 이름이 걸리면(token→placeholder처럼) 키 이름을 바꾼다. - 어떤 경로로 끝나도 기록이 남아야 한다: 정상 완주·중간 취소·예외는
finally의trace.finalize()(멱등), 강제 종료(Ctrl+C)는trace.armSignals()가 처리한다. 새 종료 경로를 만들면 이 계약을 확인할 것. - 완료 화면 출력은
try안에,finalize()는finally에만 둔다 (#561).finally는 try를 빠져나온 직후 돌기 때문에, 완료 화면을 try 밖에 두면 기록이 먼저 닫혀 그 화면이.log에서 통째로 빠진다. 실제로 사용자 실행 로그가step/done install-full에서 끊겨 "로그에 아무 안내가 없다"는 신고로 돌아왔다.finalize()는 멱등이라 뒤늦게 다시 불러도 복구되지 않는다 — 순서가 곧 계약이다.
타입별 워크플로우
Flutter
| 파일명 | 용도 | 위치 |
PROJECT-FLUTTER-CI | 코드 분석 + 빌드 검증 | 기본 |
PROJECT-FLUTTER-ANDROID-PLAYSTORE-CICD | Play Store 배포 | 기본 |
PROJECT-FLUTTER-ANDROID-FIREBASE-CICD | Firebase App Distribution | 기본 |
PROJECT-FLUTTER-ANDROID-TEST-APK | 테스트 APK 빌드 | 기본 |
PROJECT-FLUTTER-IOS-TESTFLIGHT | TestFlight 배포 | 기본 |
PROJECT-FLUTTER-IOS-TEST-TESTFLIGHT | 테스트 빌드 | 기본 |
PROJECT-FLUTTER-IOS-ASC-STATUS | App Store Connect 버전 상태, 최근 빌드 번호 조회 (수동 실행, 조회 전용, #651) | 기본 |
PROJECT-FLUTTER-PROJECTOPS-APP-BUILD-TRIGGER | 댓글 트리거 빌드 | 기본 |
PROJECT-FLUTTER-ANDROID-SELFHOSTED-CICD | 자체 서버(SMB) APK 배포 | 기본 |
⚠️ 배포/publish 타겟 축 (#439, v4.2.0 — agent 필독)
배포는 두 개의 독립 축으로 표현된다. 마법사가 타입 확정 직후 물어본다 (단 basic 단독은 스킵 — 아래).
| 축 | 의미 | 다중성 | 값 | version.yml 키 |
| deploy | 실행물(서버/앱)을 어디에 올리나 | 택1 | docker-ssh(기본)·vercel·none | options.deploy |
| publish | 라이브러리/패키지를 어느 레지스트리에 내나 | 0..n 공존 | nexus·npm·github-packages | options.publish 배열 |
🧭 intent(프로젝트 성격) 우선 분기 (#485, v4.2.14 — agent 필독)
두 축을 낱개로 묻던 구조가 사용자에게 "이게 나한테 해당되나?" 부담을 줬다. 이제 먼저 프로젝트 성격(intent)을 한 번 묻고, 그 답이 위 두 축을 유도한다.
| intent | deploy 질문 | publish 질문 | version.yml 키 |
app | 물음 | 스킵(publish=[]) | options.intent |
library | 스킵(deploy=none) | 물음 | |
both | 물음 | 물음 | |
none | 스킵(none) | 스킵([]) | |
manual | 물음 | 물음(기존 순차 질문 그대로 — 탈출구) |
metadata.template.options.intent에 저장. 구 version.yml(intent 없음)은deploy/publish값에서 역추론(inferIntent,src/core/version-yml.js)해 하위호환. deploy≠none&publish=[]→app, deploy=none&publish≠[]→library, 둘 다→both, 둘 다 없음→none.- 비대화형
--intent app|library|both|none|manual. 미지정 시--deploy/--publish에서 역추론(기존 CLI 무수정 동작). intent 명시 + 해당 축 플래그 없으면 intent가 그 축을 유도(library→deploy=none 등,src/index.js). - 수정 메뉴에 "프로젝트 성격" 항목(#485) + 개별 축 항목(#483) 병존. 성격을 바꾸면 축이 재유도되고, 개별 축 항목은 그 축만 세밀 조정(
scope). basic단독 타입은 intent 질문도 스킵 —none으로 확정.
🧩 타입별 축 적용성 (#498 — agent 필독)
deploy/publish 축은 타입에 따라 적용 자체가 안 될 수 있다. src/core/options-ask.js의 TYPE_DEPLOY_TARGETS/TYPE_PUBLISH_TARGETS 선언이 타입별 적용 가능 타겟을 정의하고, applicableTargets(types)가 선택 타입 집합의 합집합을 산출한다.
- 모바일 앱 타입(flutter/react-native/react-native-expo)은 두 축 모두 빈 배열 — 스토어 배포(Play Store/TestFlight/Firebase 등)는 타입 워크플로우가 항상 포함되므로 서버 deploy 축과 무관하고, 레지스트리 publish 개념도 없다. basic처럼 intent 질문까지 통째로 스킵하고
none/[]로 조용히 확정한다 (안내 문구도 안 띄움 — 보여줄 이유가 없다). - 저장값/CLI값이 적용 불가면 경고 없이 정리 — 구버전에서 flutter 단독에
deploy: docker-ssh가 저장된 레포도 업데이트 시 조용히none/교집합으로 정리된다 (복사 결과 동일 — SSOT). 비대화형(src/index.js)도 동일 규칙. - 수정 메뉴(
editMenu)도 적용 불가 축 항목("프로젝트 성격"·"배포 방식"·"publish 타겟")을 숨긴다. 타입을 바꾸면 다음 진입부터 다시 노출. - 서버형 타입(spring/react/node/python)은 현행 선택지 전체 유지 (동작 무변경 — 타입별 세분화는 후속 과제). 질문 선택지는
applicable.*로 필터링되므로 이후 선언만 좁히면 선택지가 자동 반영된다. - 새 타입을 추가하면 두 선언에 항목을 반드시 넣는다 —
project-types폴더(server-deploy/·publish/<target>/)와 어긋나지 않게 맞춘다. 미선언 타입은 보수적으로 전체 허용(질문 유지). basic단독 타입은 배포/publish 질문을 건너뛴다 — 서버·라이브러리 개념이 없는 범용 타입이라 물어보면 사용자가 당황한다.deploy=none·publish=[]로 조용히 확정하고 분석 카드에만 표시. 타입을 바꾸면(수정 메뉴 → 프로젝트 타입) 그때 질문이 등장한다. (구 jsisBasicOnly특례는 #498의 두-축-빈-타입 일반 판정noAxes로 흡수됨 —.sh/.ps1은 #458 EOF)- 실타입 질문 앞에는 "왜 묻는지" 맥락 한 줄을 붙인다 (서버 올릴 계획 있으면 고르고 없으면 넘기라는 안내).
- 비대화형:
--deploy docker-ssh|vercel|none+--publish nexus,npm(csv)..ps1은-Deploy/-Publish. - 구 플래그
--nexus/--npm-publish는 deprecated(1 minor 유지) —--publish nexus/--publish npm으로 해석되며 경고를 출력한다.--nexus는 추가로--deploy none을 함의(구 동작 보존). - version.yml의 구 키(
nexus/npm_publish)는 통합/업데이트 시 자동으로 신 축으로 변환·기록된다 (SSOT — 이중 표기 금지).
Spring
| 파일명 | 용도 | 위치 |
PROJECT-SPRING-SIMPLE-CICD | SSH+Docker 배포 (기본, 단일 컨테이너) | spring/server-deploy/ |
PROJECT-SPRING-NONSTOP-TRAEFIK-CICD | 무중단 배포 (Traefik Blue-Green) | spring/server-deploy/ |
PROJECT-SPRING-NONSTOP-NGINX-CICD | 무중단 배포 (Nginx Blue-Green) | spring/server-deploy/ |
PROJECT-SPRING-PR-PREVIEW | PR 프리뷰 배포 | spring/server-deploy/ |
PROJECT-SPRING-NEXUS-CI / -NEXUS-PUBLISH | Nexus 라이브러리 배포 | spring/publish/nexus/ |
PROJECT-SPRING-GITHUB-PACKAGES-PUBLISH | GitHub Packages 라이브러리 배포 | spring/publish/github-packages/ |
서버 배포 워크플로우(SIMPLE/NONSTOP-*/PR-PREVIEW)는
server-deploy/로 묶여deploy=docker-ssh일 때만 포함된다(vercel/none이면 폴더째 제외). publish 워크플로우는<type>/publish/<target>/에 있고 선택된 publish 타겟 집합으로 복사가 결정된다(타입은 파일 위치일 뿐 게이트 아님).확장 규칙(agent 필독): 새 "서버 배포" 워크플로우는 그 타입의
<type>/server-deploy/에 파일만 넣는다(deploy≠docker-ssh면 자동 제외). 현재server-deploy/를 가진 타입은spring·python두 개다. 타입 폴더 직하위에 두면--deploy none에서도 복사되므로 절대 그렇게 두지 않는다 (실제로 python이 이 함정에 빠져 #521에서 교정됨). 새 publish 타겟은<type>/publish/<target>/에 넣고 마법사 질문 목록에 값을 추가한다. 타입 비종속 배포 타겟(Vercel 등)은common/deploy/<target>/에 넣는다.⚠️ 워크플로우를 리네임/삭제할 때 (agent 필독, #470): 구 이름을
src/core/migrations/registry.js에 반드시 추가한다 — 마법사 업데이트가 기존 통합 레포의 구 파일을 자동 무해화(.bak)하는 유일한 경로다(레거시 마이그레이션은 전부 이 레지스트리 한 곳에서 관리). tier는safe(순수 리네임 — 공존 시 중복 실행 실해) /confirm(배포 파이프라인일 수 있음 — 자동 조치 없이 안내만) 중 실해 기준으로 고른다. 레지스트리에 현행 배포 세트의 이름을 넣으면 살아 있는 워크플로우를 무해화하므로, 넣기 전에 현행 템플릿에 같은 이름이 없는지 확인한다. 구 파일에 사용자 커스텀 설정이 들어있을 수 있으면 registry 항목에settingsExtractor를 지정해 무해화 직전 version.yml로 자동 이관한다 (rules/settings-extractors.js, #478 이슈 헬퍼가 모범 사례). 참고: 타입 선택 해제로 남는 고아 워크플로우는 registry가 아니라src/core/orphan-workflows.js가 동적 감지한다(#487) — registry는 리네임·폐기 전용, 고아 정리는 템플릿 인벤토리 대조(정확한 파일명 일치) 방식이다. 대화형은 확인 후 .bak 무해화, 비대화형은 안내만 출력한다.⚠️
common/아래에 조건부 폴더를 새로 만들 때 (agent 필독, #567·#851):src/core/workflow-groups.js의commonOptionalGroups에 한 줄을 넣는다. 복사 게이트(copy/workflows.js)와 고아 감지(orphan-workflows.js)가 같은 표를 읽는다. 예전에는 두 곳을 따로 고쳐야 했고, 켜는 쪽만 만들어 끈 뒤에도 파일이 남아 계속 실행되는 일이secret-backup·deploy/<target>에서 실제로 있었다.
.github/util/모듈 안의 파일을 리네임/폐기할 때도 동일하다 (#500): registry에category: "util-file"(tier safe, 정확 경로)로 구 파일을 등록한다 — util 복사(src/core/copy/util.js)는 overlay라 구 파일을 지우지 않으므로 registry가 유일한 정리 경로다. 현행 템플릿 파일과 같은 경로를 넣지 않는다.⚠️ 브랜치 규칙(
YYYYMMDD_#번호_제목)에 의존하는 워크플로우를 추가할 때 (agent 필독, #478):.github/scripts/issue_helper.py의GUIDE_LINES에 (파일명, 안내 문구)를 추가하고, 워크플로우 헤더에 "⚠️ 브랜치 규칙 의존" 주석 블록을 넣는다. 상세:docs/BRANCH-CONVENTION.md
공통 — 배포 타겟 / Secret 백업
| 파일명 | 기능 | 위치 | 조건 |
PROJECT-COMMON-VERCEL-DEPLOY | Vercel 프로덕션 배포 (React/Next 등) | common/deploy/vercel/ | --deploy vercel |
PROJECT-COMMON-SECRET-FILE-UPLOAD | GitHub Secret → 서버(SSH) 업로드 | common/secret-backup/ | --secret-backup |
PROJECT-COMMON-AI-PR-SUMMARY | 작업 PR에 변경 요약 댓글 | common/pr-summary/ | PR 댓글 질문에서 선택 |
Vercel은
VERCEL_TOKEN·VERCEL_ORG_ID·VERCEL_PROJECT_IDsecret이 필요하다.
Python (FastAPI/Django)
| 파일명 | 용도 | 위치 |
PROJECT-PYTHON-CI | 빌드/검증 | 기본 |
PROJECT-PYTHON-SIMPLE-CICD | SSH+Docker 배포 (단일 컨테이너) | python/server-deploy/ |
PROJECT-PYTHON-PR-PREVIEW | PR 프리뷰 배포 | python/server-deploy/ |
Spring과 동일한 SSH+Docker 엔진을 쓴다. 배포 2종은
server-deploy/에 있어deploy=docker-ssh일 때만 포함된다 (#521에서 타입 루트 →server-deploy/로 이동, 그전엔--deploy none에도 복사되던 버그가 있었다).
React (Next.js 포함)
| 파일명 | 용도 |
PROJECT-REACT-CI | 빌드 검증 (.next/cache 캐싱 포함) |
PROJECT-REACT-CICD | Docker 빌드 및 배포 (Next.js SSR 옵션 포함) |
next타입은 v4.1.0에서react로 흡수되었습니다 (PROJECT-NEXT-* 워크플로우 삭제).
Node — npm publish (opt-in)
| 파일명 | 용도 | 위치 |
PROJECT-NODE-NPM-PUBLISH | main push 시 version.yml 버전으로 공개 npmjs 배포 (멱등, NPM_TOKEN secret) | node/publish/npm/ |
--publish npm으로 포함한다(구--npm-publish는 deprecated alias). 선택 값은version.yml의metadata.template.options.publish배열에 저장된다.
핵심 스크립트
version_manager (.py가 실 로직, .sh는 위임 shim — #448)
.github/scripts/version_manager.sh get # 또는: python .github/scripts/version_manager.py get
.github/scripts/version_manager.sh increment # patch +1 (기본)
.github/scripts/version_manager.sh increment --bump minor # 4.2.45 → 4.3.0
.github/scripts/version_manager.sh increment --bump major # 4.2.45 → 5.0.0
.github/scripts/version_manager.sh set 2.0.0
.github/scripts/version_manager.sh sync
.github/scripts/version_manager.sh get-code
.github/scripts/version_manager.sh increment-codev4.2부터 로직은
version_manager.py(stdlib 전용 — yq/jq 불필요)에 있고.sh는 Python 위임 shim이다. Windows에서는python .github/scripts/version_manager.py get을 직접 실행한다. integrator 복사 목록에.sh+.py한 쌍이 모두 있어야 한다 (truncate_release_notes.*도 동일).
⚠️ semver 자동 승격 축 (#546 — agent 필독)
릴리스 시 승격 폭은 version.yml의 metadata.template.options.semver_auto가 정한다.
| 값 | 동작 |
| 키 없음 (기존 통합 레포 전부) | 항상 patch +1 — 구 동작 그대로 |
true | 릴리스 구간 커밋 제목으로 major/minor/patch 결정 |
false | 항상 patch +1 |
- 키가 없으면 신규·기존 모두
true로 기록된다. (구 정책: 기존 레포는false로 남겼으나 소유자 결정으로 바꿨다.) 대신 예고 없이 버전이 튀지 않게 업데이트가 처음 켠 때 완료 화면이 경고한다 (semverAutoNewlyOn). 저장된 값은 명시적false까지 그대로 존중한다. - 마법사는 이 축을 묻지 않는다 (#485 질문 부담 축소 방향 유지). 켜려면
version.yml을 직접 고친다. - 판정 규칙과 커밋 타입 대응은 "커밋 컨벤션 필수 규칙" 절 참조.
version_code는 이 옵션과 무관하게 매 릴리스 +1이다. PROJECT-COMMON-VERSION-CONTROL(main 직접 push)도semver_auto를 따른다 (#805, #815). 켜져 있으면 개발 브랜치 유무와 상관없이 push 구간 커밋 제목으로 major/minor/patch를 정하고, 명시적false일 때만 patch다. (예전엔 이 경로가 항상 patch 고정이라 옵션이 조용히 무시됐다. "커밋 컨벤션을 신뢰할 수 없다"는 이전 이유는 소유자 결정으로 폐기.)- VERSION-CONTROL 가드는
version.yml파일 이름이 아니라version:값 변경으로 릴리스 머지를 판정한다 (#806). 설정만 바꾼 push는 릴리스가 아니므로 버전이 올라간다.
빌드 번호와 iOS 업로드 판단 (#643, agent 필독)
앱 빌드 번호와 Apple 업로드 오류 해석은 각각 한 곳에서만 정한다. 워크플로 yaml에 번호 계산이나 오류 문구 grep을 다시 적지 않는다.
| 무엇 | 어디 | 규칙 |
| 빌드 번호 | .github/scripts/build_number.py | max(2024-01-01 UTC부터 지난 초, ASC 최근 번호+1, 거부 메시지 요구값+1). iOS 테스트, iOS 릴리스, Android 테스트 공통 |
| Apple 업로드 오류 해석 | .github/scripts/ios_release.py classify-error | duplicate / too_low / train_closed / other로 분류. 재시도 여부도 이 분류로 정한다 |
version_code는 Android 릴리스 전용이다 (Play Store, Firebase CICD). iOS와 테스트 빌드 번호에 쓰지 않는다.- 새 Apple 오류 코드(ITMS-...)를 다루려면
classify_upload_error에 실제 오류 문구를 근거로 패턴을 넣는다. 문구를 지어내지 않는다. - iOS 릴리스는 닫힌 버전이면 버전 이름을 바꾸지 않고 실패한다. 테스트 빌드만 다음 패치로 전환한다.
changelog_manager.py
python3 .github/scripts/changelog_manager.py update-from-summary
python3 .github/scripts/changelog_manager.py generate-md
python3 .github/scripts/changelog_manager.py export --version 1.2.3 --output release_notes.txt
python3 .github/scripts/changelog_manager.py classify-bump --commits-file commits.txt # major|minor|patchchangelog_providers/ (릴리스 노트 생성 사다리 — 전부 .py, #455·#566)
RELEASE-CHANGELOG의 fallback-summary job이 ladder.py를 호출한다. 마법사는 provider를 묻지 않는다 (#566) — 사용자의 목적은 "릴리스 노트가 잘 나오는 것"이지 생성기 선택이 아니다.
생성 사다리 (위에서부터 시도, 실패하면 아래로)
| 순서 | 경로 | 조건 | 비용 |
| ① | PR 본문에 이미 있으면 그대로 사용 | 배포 스킬·CodeRabbit·사람이 작성 | — |
| ② | openai_compatible.py | AI 키가 등록됐을 때만 | 무료 등급 가능 |
| ③ | commit.py — 커밋 분석 | 위가 안 될 때 | 무료·무제한 |
| (별도) | copilot.py — Copilot CLI | provider: copilot을 명시했을 때만 | Premium Request 소모 |
⚠️ 기본 경로에 과금 요소를 두지 말 것 (#569). Copilot은 요청 수로 과금되므로 기본 사다리에서 제외했다. 남의 저장소에 설치되는 템플릿이라 설치한 사람이 모르는 사이에 비용이 발생해선 안 된다. 기본값은 언제나 "돈이 들지 않는 것"이다.
AI 키는 서비스별 전용 이름으로 등록한다 — MODEL_API_KEY 하나로는 무슨 서비스인지
사람도 코드도 알 수 없었다.
| Secret 이름 | 서비스 | 무료 |
GEMINI_API_KEY | Google Gemini | ✅ 일 500회 |
GROQ_API_KEY | Groq | ✅ |
MISTRAL_API_KEY | Mistral | ✅ |
OPENAI_API_KEY / ANTHROPIC_API_KEY | OpenAI / Anthropic | ❌ |
MODEL_API_KEY | 구 이름 — 키 접두사로 서비스 추정 | 하위호환 |
④는 AI·네트워크 무의존이라 항상 완주한다. 어느 경로든 마지막이 commit이므로 "릴리스 노트가 비는" 상황은 생기지 않는다.
| provider 저장값 | 동작 |
| 미설정 | 레포 루트에 .coderabbit.yaml이 있으면 coderabbit, 없으면 commit. 어느 쪽이든 키가 있으면 사다리가 외부 AI를 자동으로 집는다 (#821) |
copilot | Copilot → (키 있으면 Gemini) → commit |
openai/gemini/claude/groq/mistral/ollama | 해당 provider → commit (MODEL_API_KEY 필요, ollama는 changelog.base_url) |
github-ai | 서비스 종료(2026-07-30) — 호출하지 않고 조용히 흡수, 업데이트 시 commit으로 이전 |
coderabbit | 기다리지 않는다. 본문에 요약이 있으면 ①에서 존중될 뿐 |
미설정 판정(명시값 →
.coderabbit.yaml→commit)은 워크플로우 "Read version info" 스텝과changelog_cli.py_read_release_branches두 곳이 같은 규칙이어야 한다.
⚠️ 모델명에 버전을 박지 말 것 (#566).
gemini-1.5-flash가 404로 죽어 있었다.openai_compatible.py의 preset은-latest별칭을 쓴다. gemini는gemini-flash-lite-latest가 기본인데, 무료 등급에서 상위 Flash는 RPD 20인 반면 Lite는 500이라 자동화에 쓸 수 있는 쪽이 Lite뿐이기 때문이다(실측).⚠️ 외부 서비스를 기본값으로 두지 말 것. 기본 경로(①·②·④)에는 외부 API 키가 필요 없다. 남의 레포에 설치되는 템플릿이므로, 설치한 사람이 모르는 사이에 키를 요구하거나 과금이 발생해선 안 된다.
생성 결과 안내 (changelog_notice.py): 어떤 경로로 만들어졌는지 Job Summary에 매번 남기고, AI를 하나도 쓰지 못했을 때만 PR에 댓글로 대안을 안내한다. 댓글은 마커로 갱신하므로 이메일 알림은 처음 한 번만 간다 — 매 릴리스 알림은 소음이 되고, 소음이 되면 아무도 읽지 않는다.
npx 복사 엔진(src/core/copy/simple.js)이 provider .py들을 사용자 프로젝트에 복사한다 — 새 provider를 추가하면 복사 목록에도 넣는다 (빠지면 사용자 레포에서 import 실패).
issue_helper.py (이슈 브랜치/커밋 댓글 — #478에서 내재화)
이슈 생성/제목 수정 시 PROJECT-COMMON-SUH-ISSUE-HELPER.yaml이 실행. 외부 액션 의존 없음 (stdlib 전용).
설정: version.yml metadata.template.options.issue_helper (branch_prefix/commit_template/commit_type_map/timezone/show_guide 등 — 없으면 기본값).
브랜치 코어(YYYYMMDD_#번호_제목)와 댓글의 ### 브랜치 코드블록은 불변 계약 — 소비자 목록은 docs/BRANCH-CONVENTION.md.
서명 문구는 guide_signature 설정(기본 Guide by ProjectOps)이고, 구버전 소비자가 찾는 옛 문구 Guide by SUH-LAB는 숨김 주석으로 항상 남긴다 (#749).
구 MODULE 워크플로우의 커스텀 설정은 마이그레이션이 자동 이관한다 (rules/settings-extractors.js).
template_integrator.sh / .ps1 — 제거됨 (EOF #458 → 삭제 #800)
두 스크립트는 v4.3.0에서 안내용 shim으로 바뀌었고, #800에서 파일 자체를 지웠다. 옛 curl .../template_integrator.sh | bash는 이제 404다. src/ 주석에 지운 스크립트의 줄번호·함수명(.sh L1234 등가)을 적지 않는다 — 가리키는 파일이 없다. 이유는 현재형으로 적는다 (#916).
통합/업데이트/스킬 설치는 전부 npx projectops 한 경로다. 배포/publish 축·secret 백업 등 모든 옵션은 npx 마법사가 질문하며, 선택 값은 version.yml의 metadata.template.options.*에 동일하게 저장된다.
초기화/통합 시 복사되지 않는 템플릿 전용 파일:
CLAUDE.md, CONTRIBUTING.md, LICENSE
CHANGELOG.md, CHANGELOG.json
docs/, .github/scripts/test/
.claude-plugin/, .codex-plugin/, .agents/, .cursor/, skills/, scripts/
package.json, harness/ # pi 패키지 매니페스트 + Persona Harness
plugins/ # extras 선택 플러그인 (projectops-extras)
hooks/ # Claude Code 플러그인 훅 (세션 종료 시 pro-launch 기기 반납 #953 · raw 기기 명령 차단 #954)⚠️ 레포 루트에 "마켓플레이스/템플릿 전용" 파일·폴더를 추가할 때 (agent 필독)
이 레포는 두 정체성을 동시에 가진다 — 이 점이 일반 레포와 다른 핵심이다.
- 템플릿 레포: GitHub "Use this template"로 새 프로젝트를 만들면
.github/scripts/template_initializer.py(.sh는 위임 shim)가 마켓플레이스/템플릿 전용 파일을 삭제한다. - 마법사 배포원:
npx projectops(src/)가 기존 프로젝트에 템플릿을 통합할 때 그 파일들을 복사 대상에서 제외한다 (src/core/exclusions.js).
따라서 레포 루트에 새 파일/폴더를 추가했는데 그게 이 레포에서만 의미 있고 사용자 프로젝트로 흘러가면 안 되는 것(플러그인 매니페스트, skill, pi 패키지 파일, 내부 문서 등)이라면, 아래 2곳(+필요 시 3번째)을 반드시 함께 수정한다. 한 곳만 고치면 새 프로젝트가 오염되거나 마법사가 불필요한 파일을 복사한다 — 실제로 자주 빠뜨리는 함정이다.
| # | 파일 | 수정할 위치 | 동작 |
| 1 | .github/scripts/template_initializer.py | 삭제 목록 CLEANUP_TARGETS (CHANGELOG.json 항목 근처) | ("파일명", "설명") 항목 추가 (삭제) |
| 2 | src/core/exclusions.js | DOCS_TO_REMOVE 등 제외 배열 | 파일/폴더명 추가 (복사 제외) |
| 3 | .github/workflows/PROJECT-TEMPLATE-PLUGIN-VERSION-SYNC.yaml | 버전 동기화 step + git add | 버전 필드가 있는 매니페스트(package.json 등)면 동기화 step 추가 |
(구
template_integrator.sh/.ps1의plugin_items_to_remove배열은 #458 EOF로 사라졌다 — 이제 npxexclusions.js가 유일한 복사 제외 지점이다.)체크 순서: 새 루트 파일 추가 → "이게 사용자 프로젝트에도 필요한가?" 자문 → 아니오면 위 1·2를 모두 수정. 버전을
version.yml과 맞춰야 하는 매니페스트면 3번도 추가. 수정 후python -m py_compile .github/scripts/template_initializer.py와npm test(감시 검사)로 확인한다.반대로, 추가한 게 사용자 프로젝트에도 같이 가야 하는 공통 자산(워크플로우·스크립트·설정)이라면 위 목록에 넣지 않는다 — 제외하면 통합 대상 프로젝트가 그 기능을 못 받는다.
⚠️ macOS는 bash 3.2 + BSD 도구다 — .sh 작성·검증 시 절대 잊지 말 것 (agent 필독, 실측)
핵심: 윈도우(
.ps1·Git Bash)에서 잘 돌아도 macOS에서 깨질 수 있다. macOS 기본/bin/bash는 라이선스 문제로 3.2.57(2007년)에 박제돼 있고, 기본 grep/sed도 BSD 계열이다..sh는 항상/bin/bash(3.2) + BSD 도구로 검증한다. "윈도우에서만 테스트했다"가 실제 사용자(macOS) 버그를 통째로 놓치게 한 주원인이다. (실측: 이슈 #415·#418 — 윈도우 정상, macOS만 깨짐.)
bash 3.2에서 못 쓰는 것 (4+ 전용 → macOS에서 조용히 오작동)
- 연관배열
declare -A금지. bash 3.2는 미지원 → 모든 문자열 키가 인덱스 0으로 뭉개져 "키가 1개만 남는" 버그가 된다(실측 #418: @wizard env 키가 6개인데 1개만 수집). 동적 key-value가 필요하면 eval 동적변수 + 16진 키 인코딩 헬퍼(구 integrator의_kv_*패턴, 3.2/4 공용)를 쓴다. declare -g금지. bash 3.2에서invalid option. 최상위 레벨이면 어차피 전역이니-g없이 선언한다.mapfile/readarray,${var,,}/${var^^}(대소문자),&>>도 4+ 전용 → 사용 금지.
BSD 도구 함정 (macOS 기본 grep/sed ≠ GNU)
grep -P/-oP/\K금지 (PCRE는 GNU 전용).grep -E로 라인 잡고sed -E로 추출한다. (실측 #415:grep: invalid option -- P로 버전 감지 실패.)grep이 매치 0건이면exit 1→set -e에서var=$(grep ...)단독 대입이 스크립트를 죽인다.|| true로 흡수하거나| head/| sed파이프로 끝낸다(pipefail 없으면 파이프 마지막 명령 코드만 봄). (실측 #415.)sed -i는 BSD가sed -i ''/sed -i.bak형태로 인자가 다르다.readlink -f·date -d·xargs -r도 BSD 미지원.
set -e + 함수 끝 종료코드 (메뉴 아닌 일반 함수도 해당)
- 위 "var=$(menu_fn)" 함정의 일반화: 함수의 마지막 명령이 비-0이면, 그 함수가 호출부의 마지막 명령일 때
set -e가 스크립트를 통째로 죽인다.[ -d x ] || return(폴더 없으면 1 전파),[ cond ] && cmd(조건 거짓이면 1),command -v foo && ...(미설치면 1),cp ... && ...(실패면 1) 모두 위험. → early return은return 0명시, 끝줄조건 && 명령은{ ...; } || true로 감싼다. (실측 #415: Nexus/Secret 메뉴·interactive_mode·codex 제거·config cp 등 5곳.)
검증 방법: bash -n(문법)만으론 부족하다. 반드시 /bin/bash(3.2)로 실제 실행한다. 함수만 떼어 source 후 호출해 실제 입력으로 종료코드 0 완주를 확인한다. which bash가 brew bash(/opt/homebrew/bin/bash, 4+)를 가리키면 /bin/bash로 명시 실행해 3.2를 강제한다.
⚠️ 워크플로우 YAML 검증 — 로컬 파서를 GitHub 실제 동작으로 착각하지 말 것 (agent 필독)
핵심 원칙: 로컬 YAML 검증 도구(
actionlint·Rubypsych·Pythonpyyaml)가 빨갛게 떠도, 그 워크플로우가 GitHub에서 실제로 깨진다는 뜻이 아니다. 도구가 못 읽는 것과 GitHub이 못 돌리는 것은 다르다. 멀쩡히 돌던 워크플로우를 "검증 도구가 오류라고 했으니" 멋대로 고치지 마라 — 이건 실측으로 확인된 함정이다.
왜 이런 일이 생기나 (실측 사례)
run: | 블록 안에서 heredoc을 들여쓰기 0칸 본문으로 쓰는 패턴이 대표적이다:
run: |
cat > android/key.properties << EOF
storeFile=keystore/key.jks # ← 들여쓰기 0칸
EOFactionlint·psych·pyyaml은 이걸could not find expected ':'로 파싱 실패 처리한다 (블록 스칼라 경계를 들여쓰기로만 판단하는 엄격한 go-yaml/libyaml 계열).- 하지만 GitHub Actions의 실제 YAML 파서는 이 heredoc을 정상 처리해서 워크플로우가 success한다. 즉 도구의 한계지 진짜 버그가 아니다.
진짜 깨졌는지 확인하는 올바른 순서 (추측 금지, 실측만)
- GitHub 실행 이력을 먼저 본다 (가장 강력한 증거). 같은 파일/패턴이 실제
success한 run이 있으면 → 멀쩡한 코드 확정. 절대 손대지 마라.PAT=$(python3 -c "import json;print(json.load(open('$HOME/.projectops/config/config.json'))['github']['global_pat'])") curl -s -H "Authorization: token $PAT" \ "https://api.github.com/repos/<owner>/<repo>/actions/workflows/<file>.yaml/runs?per_page=20" \ | python3 -c "import json,sys;from collections import Counter;d=json.load(sys.stdin);print(Counter(r['conclusion'] for r in d.get('workflow_runs',[])))" - "잘 작동하는 기준 레포"와 대조한다. 이 템플릿의 검증 기준 레포는
TEAM-ROMROM/RomRom-FE(Flutter)·TEAM-ROMROM/RomRom-BE(Spring) 다 — 실제 운영 중이고 빌드가 success한다.passQL은 이 템플릿을 테스트하는 실험 프로젝트라 (Flutter init도 미완) 신뢰 기준이 아니다. passQL의 failure를 근거로 템플릿이 깨졌다고 결론짓지 마라. - YAML 파싱 자체가 깨졌는지는 run annotations로 확인한다. GitHub이 파싱에 실패하면
syntax errorannotation을 남긴다. annotation이 없고 job이 빌드 중간 step에서 실패했으면 → YAML은 정상, 원인은 빌드 로직(secret 누락 등)이다.curl -s -H "Authorization: token $PAT" "https://api.github.com/repos/<owner>/<repo>/check-runs/<job_id>/annotations"
검증 도구를 쓸 때의 자세
actionlint/psych/pyyaml은 참고용 신호다. 빨간불 = "확인해봐라"지 "고쳐라"가 아니다.- 특히 이미 운영 중인 워크플로우(GitHub에 success 이력 있음)는 도구가 뭐라 하든 건드리지 않는 것이 기본값이다.
- 정 고쳐야 한다면, "잘 작동하는 RomRom이 같은 자리를 어떻게 쓰는지" 먼저 받아 대조하라. (예: key.properties는 RomRom-FE가
echo "k=v" >> file방식으로 쓰며 success — 0칸 heredoc을 안 쓴다.) - 내가 토큰화·치환 같은 env 값만 바꾸는 작업을 할 때,
run:/uses:/with:/steps:등 실행 로직은 한 줄도 건드리지 않았는지git diff로 자가검증하라:git diff <files> | grep "^+" | grep -v "^+++" | grep -vE "내가_의도한_변경_패턴" # 결과 비면 실행로직 무손상
트리거 키워드
댓글 기반
| 키워드 | 워크플로우 | 기능 |
@projectops create qa | QA-ISSUE-CREATION-BOT | QA 이슈 자동 생성 |
@projectops build app | PROJECTOPS-APP-BUILD-TRIGGER | Android + iOS 빌드 |
@projectops apk build | PROJECTOPS-APP-BUILD-TRIGGER | Android만 빌드 |
@projectops ios build | PROJECTOPS-APP-BUILD-TRIGGER | iOS만 빌드 |
브랜치 기반
| 브랜치 | 트리거 | 워크플로우 |
develop | push | CI (버전 증가 없음) |
main | PR (develop→main) | CHANGELOG-CONTROL (버전 확정 + automerge) |
main | push (릴리스 머지) | README-UPDATE, PLUGIN-SYNC, NPM-PUBLISH, CICD |
main | push (직접) | VERSION-CONTROL 안전망 (+CICD — 비권장 경로) |
이슈/PR 템플릿
이슈 템플릿: bug_report.md / feature_request.md / design_request.md / qa_request.md
이슈 라벨: 영문 표준 priority: urgent, documentation, status: todo, status: in progress, status: needs review, status: feedback, status: done, status: on hold, status: cancelled (기존 한글: 긴급, 문서, 작업전, 작업중, 담당자확인, 피드백, 작업완료, 보류, 취소)
Skills (Claude Code 플러그인)
플러그인명: projectops
claude plugin marketplace add Cassiiopeia/projectops
claude plugin install projectops@projectops-marketplace --scope user설계·계획·구현은 이 플러그인이 아니라
superpowers(brainstorming → writing-plans → executing-plans)가 맡는다. 아래 표(코어 15종 + extras 4종)에는 분석·계획·구현 스킬이 없다.
| 명령어 | 용도 |
review | 코드 리뷰 |
testcase | QA 테스트케이스 |
init-worktree | Git worktree 생성 |
commit | 이슈 기반 커밋 자동화 |
github | GitHub 전반: 이슈 생성/조회/수정/댓글/라벨/담당자, PR 생성/머지/조회, 레포 탐색, Actions 로그, Secret 관리, 이미지 첨부(이슈 본문·댓글·PR) |
report | 구현 보고서 생성 |
changelog-deploy | develop push → main으로 릴리스 PR(deploy PR) → 버전 확정 + automerge / automerge 실패 시 재트리거 |
synology-expose | 시놀로지 서비스 외부 노출 가이드 (extras 선택 플러그인 projectops-extras — plugins/projectops-extras/, 공유 문서 사본은 scripts/sync_extras.py로 동기화) |
code-consult | AI가 짠 코드 관리 (extras) — 코딩 규칙 기준 점검·한 항목씩 리팩터링(전후 실행 대조)·변경분 검사(낡은 주석·중복)·위반 동결 목록(docs/architecture/rules/, 줄을 지울 수만 있음). CLI arch_cli.py는 extras가 자기 폴더만 설치되므로 scripts/common을 쓰지 않는다 (#867) |
skill-bug | projectops 스킬 버그 보고 (extras) — 어느 레포 세션에서든 의심되면 불러, 세션 기록에서 명령·결과·흐름·로그·스크린샷을 모아 개인정보를 지우고 Cassiiopeia/projectops에 이슈(같은 이슈 있으면 댓글)로 묻지 않고 올린 뒤 하던 일로 돌아간다. 판정·수정은 하지 않는다 (CLI bug_cli.py). Claude Code에서는 extras 훅(plugins/projectops-extras/hooks/skill_bug_watch.py, 규칙 정본 skill_bug_rules.py)이 모든 pro- CLI의 실패 후 우회(스킬별 우회 표)·도구가 답한 기능 없음·같은 사용법 오류 2회·같은 실패 3회·CLI 내부 예외를 잡아 백그라운드 서브에이전트로 자동 보고시키고(세션당 3건, skill-bug.auto_report: false로 끔), skill_bug_notice.ts가 토스트를 띄운다 (#997). 자동 보고 본문은 이슈만 받은 에이전트가 재현·수정할 수 있게 bug_cli.py alarm·excerpt --tool-use-id·context가 모은 사실(원본 응답·앞뒤 흐름·환경)로 채운다 (#1000) |
disk-cleanup | 디스크 용량 정리 (extras, macOS·Windows·Linux, 스택 무관 — 알려진 위치표 + 홈 아래 큰 폴더를 크기로 찾는 largest) — 읽기 전용 scan 먼저, safe·decide·keep 3단계로 나눠 safe만 한 번 확인 후 정리, decide(열린 이슈 worktree·DerivedData·Gradle 캐시·에뮬레이터)는 사용자가 이름을 댈 때만. worktree 제거는 GitHub에서 닫힘 확인 > 사용 중·최근 활동 거절 > 백업(패치·무시된 .env 포함) > git worktree remove, 브랜치 유지. 모든 삭제는 disk_cli.py 게이트를 거치고 --yes 없으면 계획만 (#1091) |
ssh | 원격 서버 SSH 접속 및 명령 실행 (AWS EC2, 시놀로지 NAS, Linux 서버 등) |
skill-creator | skill 생성/리뷰/개선 (CREATE·REVIEW·IMPROVE 3모드) |
agent-test | 앱·웹·서버를 실제로 실행해 밟는다 (E2E·증적) — 문서만 쓰는 testcase와 다르다 |
figma | 시안 → 코드 → 대조 한 묶음. 덤프 항목을 세어 작업 목록으로 삼고, 옮긴 뒤 픽셀로 맞댄다 (#619에서 옮기기·대조를 figma-verify로 합쳤고, #854에서 이름을 figma로 되돌렸다) |
launch | 능력 스킬 — 앱·웹·서버를 띄우고 조작하고 찍는다. 다른 스킬이 launch_cli.py 를 스크립트로 부른다 (#629) |
desktop-control | 이 컴퓨터 화면의 프로그램 조작 (웹 페이지·에뮬레이터가 아닌 설치 마법사·OS 대화상자·데스크톱 앱). 요소를 읽고, 마우스를 움직이지 않고 누르고, 설치 전에는 묻고, 보안 확인 창은 사용자에게 넘긴다. launch의 desktop 명령 묶음 위에 얹은 절차 스킬이라 자체 CLI가 없다. 지금은 Windows만 (macOS는 #985) |
ui-design | 시안을 로컬 주소에 띄워 비교하고 고른다 (#1080) — 레포 토큰·글꼴 반영, 애니메이션 비교, 여러 화면 연결 프로토타입, PNG·GIF 이슈 공유, 안 되면 디자이너 요청서(#634). 판단과 시안 제작은 에이전트, ui_design_cli.py는 서버·사실 수집·캡처만. 설정 ui-design 섹션 + 옵션 표(pro_ui_design/options.py), 프로젝트 지식은 레포 범위 하네스 ui-design, 스킬 연결은 decision.json |
oss-consult | 오픈소스 컨설팅 — 레포 성격을 판별하고 6축(첫인상·가치 증명·커뮤니티·코드 확장성·법적 안전·장기 운영)으로 깐깐하게 채점, 성숙도 단계와 선행 조건을 짚고 승인받은 것을 고친다. 단일 레포·전체 일괄 (#644) |
Skills 개발 가이드
폴더 구조
skills/
├── {skill-name}/SKILL.md
├── config.json.example # 전체 config 구조 예시 (모든 skill_id 섹션 포함)
└── references/
├── common-rules.md # 절대 규칙, 커밋 컨벤션, 작업 시작 프로토콜
├── personas.md # 5 전문가 페르소나 + 6 마인드셋 (harness/PERSONA.md single source) — 로드하는 스킬 없음 (harness 사본)
├── approval-and-questions.md # 산출물 skill 8종의 승인 게이트 + 시작 전 핵심 질문 표준 (#526)
├── config-rules.md # config 경로·스키마·읽기/쓰기 표준
├── mcp-subcommand-rules.md # skill CLI(<scope>_cli.py) 서브커맨드 MCP-style 설계 표준 (JSON+next, 코드 템플릿)
├── doc-output-path.md
├── project-detection.md
├── code-style-detection.md
├── tech-flutter.md
├── tech-react.md
└── tech-spring.md핵심 원칙
- config는 agent가 Read/Write tool로 직접 처리 —
config-getCLI 호출 금지 - config 경로·스키마는
references/config-rules.md참조 — skill 내 직접 기술 금지 - GitHub API는 각 skill의
<scope>_cli.py서브커맨드로 호출 (scripts/common/gh_client경유) —ghCLI 금지 - OS 호환성: Python 실행 시
PYTHON=$(command -v python3 2>/dev/null || command -v python 2>/dev/null)패턴 사용 - skill 시작 시 필독:
references/common-rules.md→ (config 필요 시)references/config-rules.md→ (기술별)tech-*.md5.5. 산출물(md)을 만드는 skill이면references/approval-and-questions.md를 따른다 — 시작 전 핵심 질문, 저장 전 승인 게이트, 첫 실행 1회 자동화 제안. 저장 경로는 각 skill CLI의get-output-path로 받고 직접 조립하지 않는다 (#525·#526) - Python 행동 로직은 재사용 스크립트 파일 + MCP-style 표준 — 아래 "Python 행동 스크립트 표준" 절을 따른다. SKILL.md에 긴 Python heredoc 인라인 금지.
Python 행동 스크립트 표준 (필수)
스킬이 Python으로 외부 시스템(GitHub API, SSH 등)을 호출할 때 반드시 이 패턴을 따른다. 이 표준은 Windows Git Bash + macOS 양쪽에서 깨지지 않도록 실측 검증된 것이다.
skill CLI(
skills/pro-*/scripts/<scope>_cli.py)에 새 서브커맨드를 추가할 때는skills/references/mcp-subcommand-rules.md를 먼저 읽는다. 입력 계약·JSON 스키마(ok/verdict/summary/next)·gh_client와 command 레이어 분리·테스트 패턴을 코드 템플릿과 체크리스트로 정리해 둔 구체적 구현 레퍼런스다. 모범 사례는actions·deploy-status서브커맨드.
1. 로직은 재사용 스크립트 파일에 둔다
- 진입 파일은 얇게, 서브커맨드는 패키지로, 파일당 450줄. 진입
<scope>_cli.py는 파서·등록만 두고 서브커맨드 구현은 옆 패키지에 둔다 (모범:skills/pro-launch/scripts/pro_launch/). skills/{skill-name}/scripts/{name}.py에 행동 로직을 고정 파일로 저장한다.- SKILL.md는 호출법만 기술한다 (서브커맨드·인자·환경변수). 긴 Python 코드를 SKILL.md에 인라인하지 않는다.
- 이유: SKILL.md는 LLM이 매번 재입력하는 문서다. redirect strip 같은 핵심 로직을 인라인하면 재입력 시 누락·오타 위험.
2. MCP-style 서브커맨드 — 입력 해석은 agent, 실행은 .py
- .py는
argparse서브커맨드로 명확한 입력 계약을 갖는다 (예:show-run RUN_ID,joblog JOB_ID,resolve-pr PR_NUM). - .py는 URL 파싱·PR→run 추적 같은 해석을 하지 않는다. agent가 사용자 입력(URL/PR/브랜치/빈입력)을 해석해 정확한 서브커맨드·인자를 넘긴다.
- SKILL.md에 "이런 입력 → 이런 서브커맨드" 라우팅 규칙을 명시해 agent가 제대로 판단하게 한다.
- 효과: .py는 단독 실행·테스트 가능(MCP tool처럼 예측 가능), agent는 유연하게 입력 해석.
3. 인자는 환경변수로 전달 — heredoc·/tmp·stdin pipe 금지
- 민감값(PAT 등)과 인자는 환경변수로 넘긴다. heredoc 본문 보간·
/tmp임시파일·curl | pythonstdin pipe 전부 금지. - 이유 (실측):
/tmp경로 → Windows Git Bash에서 깨짐.curl | python3→ Windows에서 Exit code 49.- heredoc
{변수}보간 → 한글·특수문자 이스케이프 깨짐.
PYTHON=$(for _py in python3 python; do _path=$(command -v "$_py" 2>/dev/null) || continue; "$_path" -c "import sys; sys.exit(0)" 2>/dev/null && echo "$_path" && break; done)
GH_PAT="..." GH_OWNER="..." GH_REPO="..." \
PYTHONIOENCODING=utf-8 "$PYTHON" "$PROJECT_ROOT/skills/{skill}/scripts/{name}.py" show-run 123454. 출력은 언제나 JSON — 내부에 next 힌트 필드
- 모든 서브커맨드는 항상 JSON을 stdout으로 출력한다 (plain text 모드 없음,
--json옵션도 두지 않는다). - JSON에
ok(성공 여부), 데이터 필드, 그리고next(agent가 이어서 호출할 다음 서브커맨드 힌트)를 담는다. - agent는 단일 JSON 형식만 파싱 → 다음 행동을 정확히 판단.
{"ok": true, "run_id": 12345, "conclusion": "failure",
"failed_jobs": [{"job_id": 678, "name": "build", "failed_steps": ["Flutter build"]}],
"next": "joblog 678"}5. 표준 라이브러리 우선 — 진짜 목표는 mac/Windows 양쪽 동작 + 내부망 대응
- "의존성 0"이 목표가 아니다. 진짜 목표는 mac·Windows 어디서든 깨지지 않고, 내부망(폐쇄망)에서
pip install불가해도 돌아가는 것이다. - 따라서 가능하면 표준 라이브러리(
urllib.request/json/argparse)로 해결한다 — 추가 설치 없이 양쪽 OS·내부망에서 바로 동작하기 때문. - 표준 라이브러리로 안 되는 일이면 외부 패키지를 당연히 쓴다. 다만 스크립트 내에서 설치 시도(
pip install ... -q) + 실패 시 수동 설치 안내를 둬서 내부망에서도 우아하게 처리한다. (예: secret 암호화 PyNaCl)
6. redirect 시 Authorization 헤더 strip (필수 보안·동작)
- GitHub job logs 등 일부 엔드포인트는 Azure Blob(SAS URL)로 302 redirect된다.
- urllib 기본 동작은
Authorization헤더를 redirect 대상까지 전달 → Azure가403 AuthenticationFailed. - redirect 시
Authorization헤더를 제거하는 핸들러를 반드시 둔다:
class StripAuthRedirect(urllib.request.HTTPRedirectHandler):
def redirect_request(self, req, fp, code, msg, headers, newurl):
new = super().redirect_request(req, fp, code, msg, headers, newurl)
if new is not None:
new.headers.pop("Authorization", None)
new.unredirected_hdrs.pop("Authorization", None)
return new
opener = urllib.request.build_opener(StripAuthRedirect)config 구조
모든 스킬의 config는 단일 파일 ~/.projectops/config/config.json 하나로 관리한다.
skill_id를 키로 각 스킬의 설정을 네임스페이스로 분리한다.
| 스킬 | config 섹션 키 | 비고 |
commit, github, changelog-deploy, report | github | PAT + repos 공유 (이슈 생성 승인/담당자 설정 issue.* 키 포함) |
synology-expose | synology-expose | NAS 인스턴스 정보 |
ssh | ssh | SSH 서버 접속 정보 |
새 스킬에 config 추가하는 방법
skill_id(스킬 폴더명)를 키로config.json에 섹션 추가skills/references/config-rules.md§7에 스키마 문서화skills/config.json.example에 예시 추가- SKILL.md에
references/config-rules.md §2~3참조 명시
별도 config 파일(skill-name.config.json 등)을 새로 만들지 않는다.
skill별 CLI 커맨드 (3-layer 아키텍처)
각 skill이 자체 scripts/<scope>_cli.py를 보유한다. 서브커맨드가 많은 CLI는 진입 파일 + 같은 폴더의 패키지로 나뉜다 — 진입 파일은 파서와 등록만, 서브커맨드 구현은 패키지(pro_github/·pro_agent_test/·pro_ui_design/·figma_tools/·oss_consult/·pro_launch/, 워크트리는 scripts/git_worktree/). 호출 패턴 = self-contained 5줄 (skills/references/common-rules.md §"skill별 py 분산 호출" 참조).
| skill | cli 파일 | 주요 서브커맨드 |
| github | skills/pro-github/scripts/github_cli.py | create-issue, get-issue, get-issues, list-issues, update-issue, close-issue, reopen-issue, search-issues, add-comment, list-comments, edit-comment, delete-comment, list-labels, add-labels, remove-label, set-labels, add-assignees, remove-assignees, create-pr, list-prs, update-pr, get-pr, add-pr-comment, close-pr, reopen-pr, merge-pr, normalize-title, create-branch-name, get-commit-template, explore, create-repo, secrets, actions, upload-image, delete-image, get-output-path |
| commit | skills/pro-commit/scripts/commit_cli.py | get-issue-number, get-issue, normalize-title, sanitize-message, get-commit-template, resolve-style |
| report | skills/pro-report/scripts/report_cli.py | get-output-path, add-comment |
| review | skills/pro-review/scripts/review_cli.py | get-output-path |
| changelog-deploy | skills/pro-changelog-deploy/scripts/changelog_cli.py | actions, deploy-status, list-prs, update-pr, create-pr, detect-release-context (release_language: 노트 언어는 version.yml language — 워크플로우와 같은 규칙, 스토어 언어·고정 문구 — #977) |
| testcase | skills/pro-testcase/scripts/testcase_cli.py | get-output-path (#525·#623) |
| init-worktree | skills/pro-init-worktree/scripts/worktree_cli.py | recall, record (지난번 복사한 로컬 파일 세트를 기억 — #839) |
| figma | skills/pro-figma/scripts/figma_cli.py | get-output-path, coverage, assets, conform, diff, screens, fetch, ingest, check-fresh, token (링크 종류 판별·화면 목록 · 저장된 토큰으로 REST 직접 가져오기(MCP 불필요, 과거 버전 --version) · diff --mask-data·--mask-node 로 mock 데이터 영역 제외, --solids(한쪽에만 있는 색면)·--rows(글줄 높이), conform 자리 검사·시스템 노드 자동 제외, svg-check(렌더러가 버리는 SVG 요소) · MCP 결과 파일 저장 · 시안 변경 확인 · Figma 토큰 등록 — 토큰은 launch.credentials의 provider: figma 항목을 쓴다, #1017), store-specs, store-check (스토어 이미지 규격 정본 figma_tools/store_specs.json · 언어별 판정, #884), store-compose (앱 캡처+문구 > 언어별 스토어 이미지 HTML·렌더 스크립트, 산출물은 get-output-path의 run_dir/store 아래에만, #1021), store-upload-plan, store-upload-mark, store-upload-status (콘솔 업로드 체크리스트 · 단계별 기록, 업로드는 pro-launch web, 심사 제출 금지, #1021), store-pages (언어별 랜딩 페이지 + hreflang + sitemap, run_dir/store/site, #1021) |
| agent-test | skills/pro-agent-test/scripts/e2e_cli.py | detect, scenario, note, api, other (실행·캡처 명령은 pro-launch 로 넘겨준다 — #631) |
| launch | skills/pro-launch/scripts/launch_cli.py | doctor, detect, devices, device, app, web, desktop (PC 화면의 창, #985), render, http, access, db, logs, cred, ssh, local (이 맥 sudo, #784), shrink, mask, redact (올리기 전 이미지 덮기·글자 가리기, #1082), recall, learn, forget, kb (공식 트러블슈팅 search·show·list·propose, #889), journal, harness (사이트·작업별 안내서 폴더 list·show·match·new — 맞는 주소·호스트·앱 식별자·창 이름 응답에 자동 표시, #1023·#1024), get-output-path |
| desktop-control | (자체 CLI 없음) skills/pro-launch/scripts/launch_cli.py | desktop 그룹을 그대로 쓴다: doctor, setup, session, windows, tree, find, click, type, press, focus, wait, shot |
| ui-design | skills/pro-ui-design/scripts/ui_design_cli.py | start, status, stop, choices, tokens, check, capture, decide, decision (#1080), config (show·set·options), get-output-path, board, copy-lint, ascii, rewrite-images (업로드 결과로 본문 이미지 경로 치환 — #974) |
| oss-consult | skills/pro-oss-consult/scripts/oss_cli.py | collect, local-facts, list-repos, get-output-path (사실만 모은다 — 판단은 에이전트, #644), repo-update, label (레포를 바꾸는 유일한 경로 — --dry-run·before 기록, 삭제·가시성 변경은 구현하지 않는다, #660) |
공유 도메인 로직은 scripts/common/에 있다 (gh_client, config, paths, title, issue_number, gh_branch, manifest, emit, bootstrap).
⚠️ 산출물 경로를 SKILL.md에 박아 쓰지 않는다 (#623 — agent 필독). 산출물 루트는 설정(
output.root)으로 바뀐다.docs/projectops/<스킬>/...를 직접 조립하면 루트를 옮긴 팀에서 조용히 틀린다 — 실제로testcase는 엉뚱한 곳에 쓰고, 읽는 스킬은 빈 폴더를 뒤져 "산출물 없음"으로 판단했다. 산출물 경로는 항상 CLI의get-output-path로 받는다. 문서에는 산문 언급이 아니라 실제 호출 줄을 적고, 그 서브커맨드가 CLI에 실재하는지 확인한다.증거물(스크린샷·덤프·렌더)과 문서(보고서·계획)는 다르다 (#621). 어느 쪽인지는
common/paths.py의EVIDENCE_SKILLS/DOCUMENT_SKILLS가 단독으로 알고, 증거 스킬이면resolve_output_path가 폴더에.gitignore를 자동으로 심는다 (루트.gitignore는 건드리지 않는다). 새 산출물 스킬은 두 집합 중 하나에 반드시 넣는다 — 안 넣으면 테스트가 막는다.
⚠️ 선택 위치 인자를 여러 개 줄줄이 두지 않는다 (#622 — agent 필독). argparse는 값을 왼쪽부터 채우므로,
run_id/job_id/pr_number/branch처럼 서브커맨드마다 다른 값을 각각의 칸으로 두면 첫 칸에만 들어간다.github_cli.py의actions가 그랬고joblog·resolve-pr·resolve-branch셋이 문서에 적힌 그대로 불러도 깨졌다 (show-run·list-failed만 우연히 맞아서 오래 안 보였다). 위치 인자는 하나로 두고 서브커맨드가 해석한다 (changelog_cli.py가 처음부터 그 방식이다).
GitHub API 호출은 각 skill의
<scope>_cli.py서브커맨드 우선. 새 동작이 필요하면skills/references/mcp-subcommand-rules.md기준으로common/gh_client헬퍼 + cli 서브커맨드 + 테스트를 추가한다. 신규 skill에 py 필요하면skills/pro-skill-creator/templates/python_cli_script.py골격을 복사.
이슈·완료 댓글의 증거 첨부 (#1082)
이미지·캡처·E2E 결과·로그는 묻지 않고 첨부하고, 올리기 전에 반드시 직접 본다. 규칙 정본은 skills/references/evidence-attach.md. 가리기 규칙 정본은 scripts/common/redact.py이고 extras 사본은 scripts/sync_extras.py가 만든다.
Agent 주의사항
| 상황 | 처리 |
| config 없음 | 대화형 수집 — 억지 추론 금지 |
| repo owner/repo 불명확 | git remote get-url origin 추출 → 실패 시 config github_repos 참조 |
| GitHub API 401 | PAT 만료 안내 + /pro-github에서 재등록 유도 (references/config-rules.md §2~5) |
gh CLI 사용 시도 | 금지 — curl로 대체 |
| 공통 워크플로우 수정 | project-types/common/과 .github/workflows/ 루트 두 곳 동일하게 유지 |
| GitHub 댓글에 마크다운 표 | array.join('\n') 패턴 사용 (template literal 들여쓰기 시 표 깨짐) |
기여 가이드라인 핵심
상세 내용:
CONTRIBUTING.md,docs/WORKFLOW-COMMENT-GUIDELINES.md
워크플로우 추가 시
- 공통 워크플로우:
project-types/common/(원본) +.github/workflows/루트 (복사본) 동일 유지 - 타입별 워크플로우:
project-types/[type]/만 - 필수 요소:
workflow_dispatch,concurrency,[skip ci]
Breaking Changes
호환성 문제 변경 시 .github/config/breaking-changes.json에 등록:
{
"버전": {
"severity": "critical | warning",
"title": "English title",
"message": "English details and action steps",
"title_ko": "제목 (선택)",
"message_ko": "상세 설명 및 조치 방법 (선택)"
}
}
title/message는 영어가 정본이고 필수다 (#988). 구버전 CLI는main의 이 파일을 받아 이 두 키만 읽으므로 키 이름·형식을 바꾸지 않는다. 한국어는 선택 키title_ko/message_ko에 넣고, CLI 화면 언어(--lang·resolveLang)가ko일 때만 보인다. 없으면 영어로 폴백한다 (src/core/breaking.jslocalizeBreakingEntry). 마이그레이션 가이드(실행 기록)에는 영어 원문이 남는다. (모든 항목에 영어title/message), 한글 줄 감시는*_ko키 줄만 세지 않는다.
Skill routing
이 프로젝트에서 사용 가능한 스킬 호출 규칙:
| 요청 유형 | 호출 스킬 |
| 이슈 만들어줘, 이슈 등록, 버그 리포트, PR 생성, PR 올려줘, PR 머지, 이슈 댓글, 댓글 달아줘/수정/삭제, 이슈 확인, 이슈 닫기, 라벨 추가/제거, 담당자 추가, PR 조회, GitHub API | pro-github ← 최우선 트리거 |
| 뭔가 안 됨, 버그, 장애, 원인 파악 | superpowers:systematic-debugging |
| 코드 리뷰 | pro-review |
| QA 테스트케이스 | pro-testcase |
| 이슈 작성 / 이슈 생성 | pro-github (이슈 생성 워크플로우 흡수) |
| 커밋 | pro-commit |
| 배포 / automerge 실패 재트리거 | pro-changelog-deploy |
| 보고서 | pro-report |
| 원격 서버 SSH 접속, 로그/상태 확인 | pro-ssh |
| 실제로 테스트해줘, 에뮬레이터·시뮬레이터·브라우저로 확인, E2E, 끝까지 밟아줘 | pro-agent-test |
| 에뮬레이터 띄워서 찍어줘, 시뮬레이터 스크린샷, 브라우저로 캡처, 모바일 폭으로·빈 목록·500 화면 찍어줘 | pro-launch |
| 시안 몇 개 보여줘, 애니메이션 시안 비교, 이 흐름 눌러 보게 해줘, 시안 이슈에 올려줘, 디자인 요청해줘, 디자이너한테 보낼 거 만들어줘, 빈 화면·에러 화면도 그려 달라고 해줘 | pro-ui-design |
| 오픈소스 점검, 레포 컨설팅, trending 가려면, 오픈소스답게 만들어줘, 내 레포 전부 점검, README·라벨·라이선스 괜찮아? | pro-oss-consult |
| figma 디자인 구현, 시안대로 만들어줘, 시안이랑 같은지 확인, 디자인대로 됐는지, 그림자 빠진 거 없나 | pro-figma |
| 설계·기획 (무엇을 왜) | superpowers:brainstorming |
| 구현 계획 (어떻게) | superpowers:writing-plans |
| 계획 실행 | superpowers:executing-plans |
설계·계획·구현 흐름은 위 superpowers 3종이 담당한다. (옛 분석·계획·구현 스킬 3종은 #849에서 삭제되어 superpowers로 이관됐다.)
커밋 컨벤션 필수 규칙
커밋 메시지 앞에 이모지·태그(🚀[기능개선], ⚙️[기능추가] 등) 절대 포함 금지.
이슈 제목에서 이모지+태그를 제거한 순수 내용만 사용한다.
- 올바른 예:
RELEASE-CHANGELOG PR 본문 초기화 보호 로직 추가 : feat : ... https://... - 잘못된 예:
🚀[기능개선][ChangeLog] RELEASE-CHANGELOG : feat : ...
커밋은 pro-commit이 만든다. pro-report는 커밋을 직접 실행하지 않는다(SKILL.md에서 금지) — 커밋 메시지를 제안하는 스킬도 이 규칙을 따른다.
커밋 타입이 릴리스 버전을 결정한다 (#546 — agent 필독)
semver_auto가 켜진 레포(이 레포 포함)는 릴리스 구간 커밋 제목으로 버전 승격 폭이 정해진다.
커밋 타입을 아무거나 고르면 버전이 잘못 나간다.
| 커밋 타입 | 릴리스 결과 |
제목 : feat! : 내용 (또는 feat!:) | major — 4.2.45 → 5.0.0 |
제목 : feat : 내용 | minor — 4.2.45 → 4.3.0 |
fix / docs / chore / refactor / test | patch — 4.2.45 → 4.2.46 |
!는 호환성이 깨질 때만 붙인다 — 기존 사용자의 설정·API·CLI 인자가 더 이상 동작하지 않게 되는 변경. 확신이 없으면 붙이지 말고 사용자에게 묻는다.- 한 릴리스 구간에 섞이면 가장 높은 것이 이긴다 (major > minor > patch).
[skip ci]포함 커밋과Merge로 시작하는 커밋은 판정에서 제외된다.- 판정 로직:
.github/scripts/changelog_manager.pyclassify_bump_level().
기능 구현 워크플로우
새 기능 구현 시 순서:
superpowers:brainstorming— 설계 및 스펙 확정superpowers:writing-plans— 상세 구현 계획superpowers:executing-plans— 실제 구현superpowers:requesting-code-review— 코드 리뷰 요청
알려진 스킬 동작 문제 및 해결 가이드
1. pro-github 스킬 자동 트리거 실패
문제: 사용자가 "PR 올려줘", "댓글 달아줘" 등을 요청해도 pro-github 스킬이 자동으로 트리거되지 않고, 다른 스킬(brainstorming 등)이 먼저 실행됨.
원인: superpowers:using-superpowers 규칙에서 어떤 스킬이든 1% 가능성이면 먼저 호출하도록 강제되는데, brainstorming 등 범용 스킬이 더 넓은 설명을 가지고 있어 우선 매칭됨. pro-github description의 트리거 키워드("PR 만들어줘", "댓글 달아줘")가 있어도 다른 스킬보다 낮은 우선순위로 처리됨.
해결 방법 (사용자 관점):
- GitHub 작업 시 명시적으로
/pro-github슬래시 커맨드를 입력 - 또는 메시지 앞에 "github:" 접두어 사용 ("github: PR 올려줘")
해결 방법 (스킬 개발 관점):
skills/pro-github/SKILL.md의 description에 더 구체적인 트리거 키워드 추가 필요- 또는
pro-github를 Skill routing 표에 더 명확한 패턴으로 등록 - PR 생성, 이슈 댓글, GitHub API 작업은 반드시
/pro-github명시 호출 원칙을 CLAUDE.md에 명시
2. pro-github 스킬 실행 시 Repo 자동 감지 실패 (워크트리 환경)
문제: 스킬이 git remote get-url origin으로 현재 디렉토리의 repo를 감지하는데, Claude Code가 projectops 레포 컨텍스트에서 실행 중이면 TEAM-ROMROM/RomRom-BE가 아닌 Cassiiopeia/projectops를 origin으로 잡음.
원인: 작업 대상 레포(RomRom-BE)가 별도 워크트리에 있거나, 현재 Claude Code 세션의 primary working directory가 다른 레포인 경우 발생.
해결 방법:
- 스킬 호출 시 대상 레포를 명시:
/pro-github TEAM-ROMROM/RomRom-BE 이슈 #653 PR 생성 - config의
repos목록에 등록된 레포는 이름으로 지정 가능: "RomRom-BE PR 올려줘"
3. Windows 환경 Python urllib 에러 (Exit code 49)
문제: curl ... | python3 -c "..." 파이프라인에서 Exit code 49 오류 발생. Python이 정상 설치되어 있어도 bash 파이프에서 python3 경로 인식 실패.
원인: Windows Git Bash 환경에서 python3 명령이 Windows Store python stub을 가리키거나, 파이프 stdin 처리 방식 차이.
해결 방법 (스킬 개선 필요):
- Windows 환경에서 GitHub API JSON 파싱은
curl | python3 -c대신 PowerShellInvoke-RestMethod사용 - 또는 curl 응답을 파일로 저장 후 파싱:
curl ... -o /tmp/out.json && python3 /tmp/out.json skills/pro-github/SKILL.md에 Windows 대응 PowerShell 코드블록 추가 필요
임시 해결: Claude가 PowerShell tool을 직접 사용하여 Invoke-RestMethod로 GitHub API 호출
4. PR head 브랜치명 422 오류 (한글 브랜치명)
문제: 브랜치명에 한글이 포함된 경우(20260420_#653_FCM_푸시_페이로드에_라우팅용_데이터_포함_필요) GitHub API PR 생성 시 422 Validation Failed: head invalid 오류.
원인: PowerShell ConvertTo-Json이 한글 포함 문자열을 올바르게 인코딩하지 못하거나, GitHub API가 URL-encoded 브랜치명을 다르게 처리.
해결 방법:
- PR 생성 전 실제 remote 브랜치 존재 여부를 먼저 확인:
git ls-remote origin "브랜치명" - 브랜치명이 push되어 있는지 확인 후 API 호출
head에owner:branch형식 사용:"TEAM-ROMROM:브랜치명"
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

