feat: add update script and operations docs/templates

This commit is contained in:
2026-04-14 22:02:39 +09:00
parent 974c3d0dbd
commit edf7e61a44
8 changed files with 359 additions and 55 deletions
+10 -2
View File
@@ -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_<topic-slug>.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. 테스트 실행 근거 첨부
+2 -2
View File
@@ -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 백로그 연결을 확인한다.
- 자동으로 연결하지 않으며, 사용자가 수동 등록한 것만 인계 완료로 표시한다.
+4 -3
View File
@@ -24,6 +24,7 @@ tools: [read, edit, search, agent, todo]
- 파일명: ops-<3자리문서번호>_YYYY-MM-DD_<topic-slug>.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 항목 채우기)
+1 -1
View File
@@ -30,7 +30,7 @@ tools: [read, edit, search, todo]
- 본문에 `{{DATA:` 플레이스홀더가 남아 있으면 미완성으로 표시한다.
6. 정규화 규칙:
- {{DATA:}} 플레이스홀더는 유지하면서, 사용자가 실제 데이터를 작성한 항목은 플레이스홀더 제거
- 모든 섹션이 완성되면 # 7 플레이스홀더를 정리한다
- 모든 섹션이 완성되면 # 7 사용자 결정 필요 항목 요약 정리를 안내한다
- 배포 실행 결과나 Postmortem이 일부 만료되지 않았다면, 진행 중(미완료) 상태 명시 후 진행
7. 응답 규칙:
- 검토 결과를 미충족 항목 목록으로 보고한다 (별도 수정 없음)
@@ -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}}
- (없으면 이 섹션 제거)
+97
View File
@@ -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`
+21 -47
View File
@@ -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."
}
+144
View File
@@ -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 <name> Git remote name to use when available (default: stage-pilot)
--repo-url <url> Fallback repository URL when remote is missing.
(default: https://github.com/genichin/stage-pilot.git)
--branch <name> Upstream branch to sync (default: main)
--prefix <path> Subtree prefix path (default: .vendor/stage-pilot)
--policy <name> 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."