diff --git a/.github/START-HERE.md b/.github/START-HERE.md index 8f44c52..118f035 100644 --- a/.github/START-HERE.md +++ b/.github/START-HERE.md @@ -38,13 +38,21 @@ bash .github/tools/calc_priority_scores.sh .github/templates/planning-backlog-sc - .github/runbooks/services/web.rollback-runbook.md 3. 배포 전 각 서비스 인스턴스의 Trigger/Procedure/Validation 최신화 -## 4. Verification Exception Quick Start +## 4. Operations Quick Start + +1. 기본 템플릿: .github/templates/operations-note.template.md +2. 생성 프롬프트: /operation-draft rel-001 +3. 검토 프롬프트: /operation-review rel-001 +4. 승인 프롬프트: /operation-confirm rel-001 +5. 산출 경로: docs/sdlc/operations/ops-<3자리문서번호>_YYYY-MM-DD_.operations.md + +## 5. Verification Exception Quick Start 1. Major 결함 예외는 승인자 정책을 따름 2. Valid Until 설정은 만료 전 재검증 필수 3. 만료/해소 상태를 검증 리포트에 반영 -## 5. Minimum Go-live Checklist +## 6. Minimum Go-live Checklist 1. 우선순위 점수표 완료 2. 테스트 실행 근거 첨부 diff --git a/.github/prompts/operation-confirm.prompt.md b/.github/prompts/operation-confirm.prompt.md index 6b65afc..13ebeac 100644 --- a/.github/prompts/operation-confirm.prompt.md +++ b/.github/prompts/operation-confirm.prompt.md @@ -33,8 +33,8 @@ tools: [read, edit, search, todo] 7. 승인 가능 시 아래 순서로 문서를 수정한다: a. `# 0. 문서 상태`의 상태를 `confirmed`로 변경한다. b. `# 0. 문서 상태`의 승인 시각을 현재 날짜(YYYY-MM-DD)로 기재한다. `{{CONFIRM:` 형식이 남아 있으면 현재 날짜로 대체한다. - c. `# 7. 파일 처리 결과` 항목에 Operations 완료 시각과 마감 상태를 추가한다. - d. `# 8. 사용자 결정 필요 항목 요약`의 내용을 `현재 미해결 플레이스홀더 없음`으로 대체한다. 기존 `##` 하위 항목은 모두 제거한다. + c. `# 6. 파일 처리 결과` 항목에 Operations 완료 시각과 마감 상태를 추가한다. + d. `# 7. 사용자 결정 필요 항목 요약`의 내용을 `현재 미해결 플레이스홀더 없음`으로 대체한다. 기존 `##` 하위 항목은 모두 제거한다. 8. 환류 항목 처리 규칙: - `# 5. 다음 반복 환류 항목`에 명시된 각 개선 항목에 대해 다음 Discovery/Planning 백로그 연결을 확인한다. - 자동으로 연결하지 않으며, 사용자가 수동 등록한 것만 인계 완료로 표시한다. diff --git a/.github/prompts/operation-draft.prompt.md b/.github/prompts/operation-draft.prompt.md index 2a1f48c..4df4543 100644 --- a/.github/prompts/operation-draft.prompt.md +++ b/.github/prompts/operation-draft.prompt.md @@ -24,6 +24,7 @@ tools: [read, edit, search, agent, todo] - 파일명: ops-<3자리문서번호>_YYYY-MM-DD_.operations.md - 문서번호: 기존 ops 번호 최대값 + 1, 없으면 001부터 시작 - 날짜: 생성일 기준, 이후 수정 시 파일명 날짜 유지 + - 본문 템플릿: .github/templates/operations-note.template.md - append 금지, 생성/갱신 시 overwrite 6. Operations 작성 원칙: - Release의 배포 체크리스트, 롤백 계획, 관찰 계획을 근거로 운영 추적 문맥을 작성한다. @@ -42,7 +43,7 @@ tools: [read, edit, search, agent, todo] - 상태: 초안 생성 시 `draft` - 승인자: Release 문서에서 인계받음 - 승인 시각: `{{CONFIRM: 배포 완료 후 Operations 확인}}` -9. # 6 사용자 결정 필요 항목 요약 규칙: +9. # 7 사용자 결정 필요 항목 요약 규칙: - 본문의 {{DECIDE}}/{{CONFIRM}}/{{DATA}}를 유형별로 집계한다. - 미해결 플레이스홀더가 없으면 `현재 미해결 플레이스홀더 없음`으로 작성한다. @@ -57,7 +58,7 @@ tools: [read, edit, search, agent, todo] 8. Postmortem 섹션 생성 (8개 항목 모두 {{DATA:}} 또는 등급별 예시 포함) 9. 환류 항목 섹션 생성 (발견된 개선 사항 추출 또는 {{DATA:}}) 10. Operations 문서 저장 -11. 본문 플레이스홀더 집계 후 # 6 작성 +11. 본문 플레이스홀더 집계 후 # 7 작성 12. 최종 응답에 처리 요약 제공 Operations 문서 출력 섹션 순서(고정): @@ -75,7 +76,7 @@ Operations 문서 출력 섹션 순서(고정): 2. 입력 Release 문서 경로 3. Operations 문서 생성/갱신 결과 4. 배포 실행 기록 초안 요약 -5. 모니터링 관찰 항목 복사 상여 +5. 모니터링 관찰 항목 복사 상세 6. Postmortem 템플릿 완성도 7. 업데이트된 파일 경로 목록 8. 다음 액션 (배포 실행자가 DATA 항목 채우기) diff --git a/.github/prompts/operation-review.prompt.md b/.github/prompts/operation-review.prompt.md index 331713d..1741fd5 100644 --- a/.github/prompts/operation-review.prompt.md +++ b/.github/prompts/operation-review.prompt.md @@ -30,7 +30,7 @@ tools: [read, edit, search, todo] - 본문에 `{{DATA:` 플레이스홀더가 남아 있으면 미완성으로 표시한다. 6. 정규화 규칙: - {{DATA:}} 플레이스홀더는 유지하면서, 사용자가 실제 데이터를 작성한 항목은 플레이스홀더 제거 - - 모든 섹션이 완성되면 # 7 플레이스홀더를 정리한다 + - 모든 섹션이 완성되면 # 7 사용자 결정 필요 항목 요약 정리를 안내한다 - 배포 실행 결과나 Postmortem이 일부 만료되지 않았다면, 진행 중(미완료) 상태 명시 후 진행 7. 응답 규칙: - 검토 결과를 미충족 항목 목록으로 보고한다 (별도 수정 없음) diff --git a/.github/templates/operations-note.template.md b/.github/templates/operations-note.template.md new file mode 100644 index 0000000..bace579 --- /dev/null +++ b/.github/templates/operations-note.template.md @@ -0,0 +1,80 @@ +# 0. 문서 상태 +- 상태: draft +- 승인자: {{DECIDE: 이 Operations의 최종 승인자는 누구인가?}} +- 승인 시각: {{CONFIRM: 배포 완료 후 Operations 확인}} + +# 1. 배포 실행 기록 +- 입력 Release: {{DATA: docs/sdlc/release/rel-XXX_YYYY-MM-DD_topic.release.md}} +- 배포 방식: {{DATA: 점진 배포/일괄 배포}} +- 배포 환경: {{DATA: production/staging 등}} +- 배포 창(UTC): {{DATA: 시작/종료 시각}} + +## 사전 검증 +- 체크리스트 점검 결과: {{DATA: 완료/미완료 항목}} +- 롤백 준비 상태: {{DATA: 준비됨/미준비 + 근거}} + +## 실행 결과 +- 실행 명령/절차 요약: {{DATA: 실행 로그 요약}} +- 결과: {{DATA: 성공/부분성공/실패}} +- 영향 범위: {{DATA: 사용자/시스템 영향}} + +## 배포 직후 검증 결과 +- 핵심 시나리오 검증: {{DATA: 통과/실패 + 근거}} +- 예외/알림 발생 여부: {{DATA: 있음/없음 + 내용}} + +# 2. 모니터링 관찰 결과 +| 모니터링 항목 | 측정 지표 | 정상 범위 | 이상 감지 기준 | 관찰 결과 | 비고 | +|---|---|---|---|---|---| +| {{DATA: 항목명}} | {{DATA: 지표}} | {{DATA: 정상 범위}} | {{DATA: 임계치}} | {{DATA: 결과}} | | + +## 관찰 기간 +- 집중 관찰 기간: {{DATA: 시작~종료}} +- 일반 관찰 기간: {{DATA: 시작~종료}} + +# 3. 인시던트 대응 기록 +- 인시던트 발생 여부: {{DATA: 발생/해당없음}} + +## 발생한 경우 기록 +- 영향도 분류: {{DATA: 사용자/기능/데이터 영향}} +- 타임라인: + - 탐지 시각(UTC): {{DATA: 시각}} + - 대응 시작(UTC): {{DATA: 시각}} + - 완화 시각(UTC): {{DATA: 시각}} + - 복구 완료(UTC): {{DATA: 시각}} +- 커뮤니케이션: + - 채널: {{DATA: 채널}} + - 공지 시각(UTC): {{DATA: 시각}} + - 업데이트 주기: {{DATA: 주기}} +- 우회책 실행 여부: {{DATA: 실행/미실행 + 내용}} +- 복구 절차 요약: {{DATA: 복구 단계}} + +# 4. Postmortem +- What Happened: {{DATA: 사건 개요와 사용자 관찰 증상}} +- Impact: {{DATA: 서비스/사용자/비즈니스 영향}} +- Root Cause: {{DATA: 기술적 원인 + 시스템적 원인}} +- Detection: {{DATA: 탐지 경로/지연 요인/알림 품질}} +- Response: {{DATA: 대응 의사결정 및 실행 평가}} +- Recovery: {{DATA: 복구 절차와 검증 결과}} +- Preventive Actions: {{DATA: 재발 방지 액션(담당자/기한 포함)}} +- Follow-up Tracking: {{DATA: Discovery/Planning 백로그 연결 상태}} + +# 5. 다음 반복 환류 항목 +| 환류 ID | 개선 항목 | 연결 대상(Discovery/Planning) | 담당자 | 목표일 | 상태 | +|---|---|---|---|---|---| +| FB-001 | {{DATA: 개선 항목}} | {{DATA: dcy/pln 문서 또는 티켓}} | {{DATA: 담당자}} | {{DATA: YYYY-MM-DD}} | {{DATA: 등록됨/대기}} | + +# 6. 파일 처리 결과 +- 생성/갱신 경로: {{DATA: docs/sdlc/operations/ops-XXX_YYYY-MM-DD_topic.operations.md}} +- 입력 Release 경로: {{DATA: docs/sdlc/release/rel-XXX_YYYY-MM-DD_topic.release.md}} +- 처리 결과: {{DATA: 생성/갱신/검토}} +- 비고: {{DATA: 특이사항}} + +# 7. 사용자 결정 필요 항목 요약 +## {{DECIDE}} +- (없으면 이 섹션 제거) + +## {{CONFIRM}} +- (없으면 이 섹션 제거) + +## {{DATA}} +- (없으면 이 섹션 제거) diff --git a/README.md b/README.md new file mode 100644 index 0000000..bddfc3d --- /dev/null +++ b/README.md @@ -0,0 +1,97 @@ + +# 사용방법 + +## 최초 등록 + +```bash +# 리모트 등록 +git remote add stage-pilot https://github.com/genichin/stage-pilot.git +# subtree 추가 +git fetch stage-pilot main +git subtree add --prefix=.vendor/stage-pilot stage-pilot main --squash +# 설치 +bash .vendor/stage-pilot/bootstrap/install.sh . +``` + +## 업데이트 + +```bash +# 원격이 등록된 경우(stage-pilot remote 우선 사용) +bash .vendor/stage-pilot/bootstrap/update.sh . + +# 원격 없이 URL로 직접 업데이트 +bash .vendor/stage-pilot/bootstrap/update.sh --repo-url https://github.com/genichin/stage-pilot.git . + +# 설치 재적용 없이 subtree만 갱신 +bash .vendor/stage-pilot/bootstrap/update.sh --skip-install . +``` + +## SDLC 프롬프트 사용법 + +`.github/prompts/`에 단계별 프롬프트가 정의되어 있으며, Copilot Chat에서 `/프롬프트이름 인자` 형태로 실행한다. + +### 기본 실행 순서 + +1. Discovery +2. Planning +3. Design +4. Implementation +5. Verification +6. Release +7. Operations + +### 단계별 명령 예시 + +```text +# Discovery +/discovery-draft 결제 실패 알림 정책을 도입한다 +/discovery-review dcy-001 +/discovery-confirm dcy-001 + +# Planning +/planning-draft dcy-001 +/planning-review dcy-001 +/planning-confirm pln-001 + +# Design +/design-draft pln-001 +/design-review dsn-001 +/design-confirm dsn-001 + +# Implementation +/implementation-draft dsn-001 +/implementation imp-001 I-001, I-002 +/implementation-confirm imp-001 + +# Verification +/verification-draft imp-001 +/verification-review ver-001 +/verification-confirm ver-001 + +# Release +/release-draft ver-001 +/release-review ver-001 +/release-confirm ver-001 + +# Operations +/operation-draft rel-001 +/operation-review rel-001 +/operation-confirm rel-001 +``` + +### 문서 ID 규칙 + +1. Discovery: `dcy-001` +2. Planning: `pln-001` +3. Design: `dsn-001` +4. Implementation: `imp-001` +5. Verification: `ver-001` +6. Release: `rel-001` + +### 참고 파일 + +1. 시작 가이드: `.github/START-HERE.md` +2. 단계 규칙: `.github/instructions/` +3. 프롬프트 정의: `.github/prompts/` +4. 템플릿: `.github/templates/` +5. Operations 템플릿: `.github/templates/operations-note.template.md` diff --git a/bootstrap/bootstrap.sh b/bootstrap/bootstrap.sh index c63f251..c42b43f 100644 --- a/bootstrap/bootstrap.sh +++ b/bootstrap/bootstrap.sh @@ -11,10 +11,8 @@ fail() { exit 1 } -require_linux() { - if [ "$(uname -s)" != "Linux" ]; then - fail "This bootstrap script supports Linux only." - fi +current_os() { + uname -s } HOST_ROOT="${1:-$(pwd)}" @@ -51,55 +49,31 @@ report_github_structure() { fi } -has_python_311_or_higher() { - command -v python3 >/dev/null 2>&1 \ - && python3 -c 'import sys; raise SystemExit(0 if sys.version_info >= (3, 11) else 1)' -} - -install_python_311() { - if has_python_311_or_higher; then - return 0 - fi - if command -v apt-get >/dev/null 2>&1 && command -v sudo >/dev/null 2>&1; then - log "Installing Python 3.11 via apt-get (minimum required: 3.11)." - sudo apt-get update - sudo apt-get install -y python3.11 - else - fail "Python 3.11+ is required. Install it manually or provide apt-get + sudo." - fi - has_python_311_or_higher || fail "Python 3.11+ installation check failed." -} - -has_node_20() { - command -v node >/dev/null 2>&1 \ - && node -e 'const [major] = process.versions.node.split(".").map(Number); process.exit(major >= 20 ? 0 : 1);' -} - -install_node_20() { - if has_node_20; then - return 0 - fi - if command -v apt-get >/dev/null 2>&1 && command -v sudo >/dev/null 2>&1 && command -v curl >/dev/null 2>&1; then - log "Installing Node.js 20 LTS via NodeSource (minimum required: v20)." - curl -fsSL https://deb.nodesource.com/setup_20.x | sudo -E bash - - sudo apt-get install -y nodejs - else - fail "Node.js v20+ is required. Install it manually or provide apt-get + sudo + curl." - fi - has_node_20 || fail "Node.js v20+ installation check failed." -} - main() { - require_linux + os_name="$(current_os)" [ -d "${HOST_ROOT}" ] || fail "Host root not found: ${HOST_ROOT}" log "Host root: ${HOST_ROOT}" ensure_host_structure - install_python_311 - install_node_20 + + # Intentionally do not install dependencies in bootstrap. + # This script only prepares the host directory layout. + log "${os_name} detected. Skipping Python/Node installation by design." + report_github_structure - log "python3: $(python3 --version 2>&1)" - log "node: $(node --version 2>&1)" + + if command -v python3 >/dev/null 2>&1; then + log "python3: $(python3 --version 2>&1)" + else + log "python3: not found" + fi + + if command -v node >/dev/null 2>&1; then + log "node: $(node --version 2>&1)" + else + log "node: not found" + fi + log "Bootstrap completed successfully." } diff --git a/bootstrap/update.sh b/bootstrap/update.sh new file mode 100755 index 0000000..43c0549 --- /dev/null +++ b/bootstrap/update.sh @@ -0,0 +1,144 @@ +#!/usr/bin/env bash + +set -euo pipefail + +usage() { + cat <<'EOF' +Usage: update.sh [options] [HOST_ROOT] + +Options: + --dry-run Show planned commands without executing them. + --remote Git remote name to use when available (default: stage-pilot) + --repo-url Fallback repository URL when remote is missing. + (default: https://github.com/genichin/stage-pilot.git) + --branch Upstream branch to sync (default: main) + --prefix Subtree prefix path (default: .vendor/stage-pilot) + --policy install.sh conflict policy: replace|preserve|fail + (default: preserve) + --skip-install Only update subtree. Do not run install.sh. + -h, --help Show this help message. + +Arguments: + HOST_ROOT Host repository root (default: current directory) +EOF +} + +log() { + printf '[update] %s\n' "$*" +} + +fail() { + printf '[update] ERROR: %s\n' "$*" >&2 + exit 1 +} + +run_cmd() { + if [ "${DRY_RUN}" -eq 1 ]; then + log "[dry-run] $*" + else + "$@" + fi +} + +DRY_RUN=0 +REMOTE_NAME="stage-pilot" +REPO_URL="https://github.com/genichin/stage-pilot.git" +BRANCH="main" +PREFIX=".vendor/stage-pilot" +POLICY="preserve" +SKIP_INSTALL=0 +ARGS=() + +while [ "$#" -gt 0 ]; do + case "$1" in + --dry-run) + DRY_RUN=1 + ;; + --remote) + [ "$#" -ge 2 ] || fail "--remote requires a value" + REMOTE_NAME="$2" + shift + ;; + --repo-url) + [ "$#" -ge 2 ] || fail "--repo-url requires a value" + REPO_URL="$2" + shift + ;; + --branch) + [ "$#" -ge 2 ] || fail "--branch requires a value" + BRANCH="$2" + shift + ;; + --prefix) + [ "$#" -ge 2 ] || fail "--prefix requires a value" + PREFIX="$2" + shift + ;; + --policy) + [ "$#" -ge 2 ] || fail "--policy requires a value" + POLICY="$2" + shift + ;; + --skip-install) + SKIP_INSTALL=1 + ;; + -h|--help) + usage + exit 0 + ;; + --*) + fail "Unknown option: $1" + ;; + *) + ARGS+=("$1") + ;; + esac + shift +done + +HOST_ROOT="${ARGS[0]:-$(pwd)}" + +case "${POLICY}" in + replace|preserve|fail) + ;; + *) + fail "Invalid --policy '${POLICY}'. Allowed: replace|preserve|fail" + ;; +esac + +[ -d "${HOST_ROOT}" ] || fail "Host root not found: ${HOST_ROOT}" + +if ! git -C "${HOST_ROOT}" rev-parse --is-inside-work-tree >/dev/null 2>&1; then + fail "Host root is not a git repository: ${HOST_ROOT}" +fi + +if git -C "${HOST_ROOT}" remote get-url "${REMOTE_NAME}" >/dev/null 2>&1; then + SOURCE_REF="${REMOTE_NAME}" + log "Using git remote '${REMOTE_NAME}'" +else + SOURCE_REF="${REPO_URL}" + log "Remote '${REMOTE_NAME}' not found; using URL '${REPO_URL}'" +fi + +if [ -d "${HOST_ROOT}/${PREFIX}" ]; then + run_cmd git -C "${HOST_ROOT}" subtree pull --prefix="${PREFIX}" "${SOURCE_REF}" "${BRANCH}" --squash +else + log "Subtree prefix not found; adding subtree at ${PREFIX}" + run_cmd git -C "${HOST_ROOT}" subtree add --prefix="${PREFIX}" "${SOURCE_REF}" "${BRANCH}" --squash +fi + +if [ "${SKIP_INSTALL}" -eq 1 ]; then + log "Skip install requested. Update completed." + exit 0 +fi + +INSTALL_SCRIPT="${HOST_ROOT}/${PREFIX}/bootstrap/install.sh" +[ -f "${INSTALL_SCRIPT}" ] || fail "Install script not found after update: ${INSTALL_SCRIPT}" + +if [ "${DRY_RUN}" -eq 1 ]; then + run_cmd /bin/bash "${INSTALL_SCRIPT}" --dry-run "${HOST_ROOT}" "${POLICY}" +else + /bin/bash "${INSTALL_SCRIPT}" "${HOST_ROOT}" "${POLICY}" +fi + +log "Update completed successfully."