commit 9339d3d503a273316eafa8877032fe978eefd8a1 Author: Justin Ko Date: Wed Jun 3 08:56:02 2026 +0000 Squashed '.stage-pilot/' content from commit 2c0510c git-subtree-dir: .stage-pilot git-subtree-split: 2c0510c68add43c50adcdedb30c174e68e730395 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..08958da --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +# 루트 전용 무시 (source/ 아래의 .claude·.github 템플릿은 추적해야 하므로 / 앵커 필수) +/.vscode/ +/.claude/ +/.github/ +/.stage-pilot/ +/.docs/ +/temp/ +/CLAUDE.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..dba94b3 --- /dev/null +++ b/README.md @@ -0,0 +1,424 @@ + +# 사용방법 + +## 최초 등록 + +```bash +# 리모트 등록 +git remote add stage-pilot https://github.com/genichin/stage-pilot.git +# subtree 추가 +git fetch stage-pilot main +git subtree add --prefix=.stage-pilot stage-pilot main --squash +# 설치 +bash .stage-pilot/tools/install.sh . +``` + +설치 직후에는 Copilot Chat에서 `/bootstrap-baseline`을 먼저 실행해 `docs/project-structure.md`, `docs/runtime-flows.md`, `docs/interface-contract.md`, `docs/data-model.md`, 각 active index를 초기화한다. 이 단계는 Discovery/REQ/Batch/Release 중 어느 것도 아니며, 첫 real Discovery 전에 수행하는 bootstrap 단계다. + +greenfield 저장소처럼 읽을 코드나 설정이 없으면 `bootstrap-baseline`은 사용자에게 최소 질문 세트와 필요한 선택 follow-up(`interface-contracts`, `interface-inputs`, `interface-outputs`, `interface-errors`, `core-entities`, `persistence-backend`, `compatibility-rules`)를 묻고, 답변을 `.stage-pilot/bootstrap/baseline.yaml`에 저장한 뒤 baseline 문서를 렌더링한다. + +seed 파일만 먼저 만들고 싶으면 helper를 사용할 수 있다. + +```bash +# vendor wrapper 사용 +bash .stage-pilot/tools/stagepilot.sh bootstrap-seed . + +# 설치된 script 직접 사용 +python3 .stage-pilot/tools/stagepilot-bootstrap-seed.py . +``` + +bootstrap-baseline 실행 결과 예시는 `examples/bootstrap-baseline/`에 포함돼 있다. + +## 업데이트 + +```bash +# 원격이 등록된 경우(stage-pilot remote 우선 사용) +bash .stage-pilot/tools/update.sh . + +# 충돌 파일을 보존하고 싶을 때(덮어쓰기 비활성화) +bash .stage-pilot/tools/update.sh --no-overwrite . + +# 원격 없이 URL로 직접 업데이트 +bash .stage-pilot/tools/update.sh --repo-url https://github.com/genichin/stage-pilot.git . + +# 설치 재적용 없이 subtree만 갱신 +bash .stage-pilot/tools/update.sh --skip-install . +``` + +## 검증 + +```bash +# .stage-pilot subtree에서 doctor 실행 +bash .stage-pilot/tools/stagepilot.sh doctor . + +# subtree의 scripts를 직접 실행 +python3 .stage-pilot/tools/stagepilot-doctor.py . + +# active docs가 반드시 있어야 하는 workspace라면 엄격 모드 사용 +python3 .stage-pilot/tools/stagepilot-doctor.py --strict-missing-docs . + +# Markdown 리포트를 파일로 저장 +python3 .stage-pilot/tools/stagepilot-doctor.py --report artifacts/stagepilot-doctor.md . +``` + +`stagepilot-doctor`는 아래 항목을 검사한다. + +- active 문서 루트 존재 여부 +- index와 본문 문서의 상태 동기화 +- REQ traceability matrix (`Discovery -> REQ -> Batch -> Release`) +- feedback loop summary (`Release -> Discovery/REQ/change-req input`) +- change-req 이후 상태 되돌림 누락 (`Status Recommendation`, 재검증 필요, 구현 무효화와 현재 REQ 상태의 불일치) +- orphan REQ (`Approved`인데 batch 미포함, release-candidate batch 미포함 등) +- feedback 미연결 release (`released`인데 `feedback-captured` 미전환 등) +- 남아 있는 placeholder +- 상대 markdown 링크 경로 +- REQ 타입 분류 일관성 +- `Next Requirement ID` 규칙 +- template-skill contract 불일치 +- bootstrap 완료 저장소에서 선택 cross-cutting baseline 문서(`docs/interface-contract.md`, `docs/data-model.md`) 누락 + +active 문서가 아직 없는 패키지 저장소나 초기 host 저장소에서는 package contract 검사만 수행하고, active docs 부재는 기본적으로 warning으로 보고한다. + +fresh host 저장소에서 baseline 초기화가 아직 끝나지 않았으면 doctor는 `INFO [bootstrap-required]`로 `/bootstrap-baseline` 실행과 누락된 bootstrap 파일 목록을 함께 안내한다. + +`--report`를 주면 findings와 traceability matrix를 별도 Markdown 리포트로 저장한다. 기본 동작은 stdout 출력만 유지한다. + +## 예제 + +- `examples/bootstrap-baseline/`: fresh host 저장소에서 `/bootstrap-baseline`을 실행한 직후의 baseline 문서와 sample output +- `examples/p3-change-management/`: change-req와 suggest-next-discovery가 연결되는 P3 sample workspace + +## Active 문서 루트와 Source of Truth + +### 필수 루트 + +| 경로 | 역할 | +| --- | --- | +| `docs/discovery/index.md` | Discovery 전역 인덱스 | +| `docs/discovery/*.md` | Discovery 본문 | +| `docs/srs/index.md` | REQ 전역 인덱스와 상태 정의 | +| `docs/srs//req-XXX_.md` | 개별 REQ 문서 | +| `docs/batches/index.md` | batch 전역 인덱스 | +| `docs/batches//planning.md` | batch 계획 | +| `docs/batches//design.md` | batch 설계 | +| `docs/batches//implementation.md` | batch 구현 로그 | +| `docs/batches//verification.md` | batch 검증 근거 | +| `docs/releases/index.md` | release 전역 인덱스 | +| `docs/releases/rel-XXX_*.md` | 개별 release 문서 | + +### 선택 cross-cutting 문서 + +| 경로 | 역할 | +| --- | --- | +| `docs/project-structure.md` | 저장소 구조, 모듈 경계, 의존 규칙 baseline | +| `docs/runtime-flows.md` | 대표 entry point와 실행 흐름 baseline | +| `docs/interface-contract.md` | 외부 API, CLI, 이벤트, 파일 I/O 계약 baseline | +| `docs/data-model.md` | 핵심 엔티티, 관계, 상태, persistence 규칙 baseline | + +이 문서들은 active unit 자체는 아니지만 여러 REQ와 batch에서 반복 참조하는 공통 기술 기준선이다. + +### 4.2 명령, 템플릿, 가이드 경로 + +| 경로 | 역할 | +| --- | --- | +| `.stage-pilot/skills/bootstrap-baseline/SKILL.md` | 프로젝트 시작 시 baseline 문서와 active index를 초기화하는 bootstrap entrypoint | +| `.stage-pilot/skills/` | active skill entrypoint 집합 | +| `.stage-pilot/skills/run-sdlc/SKILL.md` | 현재 상태를 읽고 다음 skill을 안내하는 orchestrator | +| `.stage-pilot/templates/bootstrap/baseline-seed.yaml` | bootstrap 질문 결과를 저장하는 seed 템플릿 | +| `.stage-pilot/templates/discovery/discovery.md` | Discovery 생성 템플릿 | +| `.stage-pilot/templates/project-structure.md` | baseline 구조 문서 템플릿 | +| `.stage-pilot/templates/runtime-flows.md` | baseline 실행 흐름 문서 템플릿 | +| `.stage-pilot/templates/interface-contract.md` | 선택형 인터페이스 계약 문서 템플릿 | +| `.stage-pilot/templates/data-model.md` | 선택형 데이터 모델 문서 템플릿 | +| `.stage-pilot/instructions/placeholder-guide.md` | placeholder 치환 규칙과 active 경로 가이드 | + +### 4.3 비활성 경로 정책 + +- prompt 기반 stage command는 active flow에 포함하지 않는다. +- legacy stage template는 active source of truth가 아니다. +- active 작업은 `docs/discovery/`, `docs/srs/`, `docs/batches/`, `docs/releases/` 기준으로만 수행한다. + +## 5. 단위별 상태 모델 + +### 5.1 Discovery 상태 + +| 상태 | 의미 | +| --- | --- | +| `draft` | 초안 상태이며 REQ drafting 전 | +| `confirmed` | REQ drafting으로 넘길 수 있도록 확인 완료 | + +Discovery는 구현 완료 상태를 표현하는 단위가 아니다. Discovery의 역할은 문제 정의와 handoff 준비다. + +### 5.2 REQ 상태 + +| 상태 | 의미 | +| --- | --- | +| `Proposed` | 초안 상태이며 구현 기준으로 확정되지 않음 | +| `Approved` | 구현 기준으로 승인됨 | +| `Implemented` | 구현과 검증이 완료됨 | +| `Deprecated` | 더 이상 유지하지 않음 | + +### 5.3 Batch 상태 + +| 상태 | 의미 | +| --- | --- | +| `draft` | batch 초안만 생성된 상태 | +| `in-delivery` | planning, design, implementation, verification 중 일부가 진행 중 | +| `release-candidate` | verification 확인이 끝나 release 입력으로 사용 가능 | +| `released` | 하나 이상의 release에 포함됨 | +| `archived` | 더 이상 active delivery 단위로 사용하지 않음 | + +### 5.4 Release 상태 + +| 상태 | 의미 | +| --- | --- | +| `draft` | release 초안 상태 | +| `confirmed` | rollout, rollback, verification 준비가 승인됨 | +| `released` | 배포 실행이 완료됨 | +| `feedback-captured` | 운영 환류까지 기록 완료 | + +## 6. 기본 운영 흐름 + +### 6.0 프로젝트 시작 시 baseline 초기화 + +새 저장소에 StagePilot을 처음 적용할 때는 Discovery를 만들기 전에 `bootstrap-baseline`을 먼저 실행한다. + +1. `bootstrap-baseline` + 필요하면 최소 질문 세트(`project-summary`, `primary-domain`, `tech-stack`, `primary-runtime`, `primary-entrypoints`)와 선택 follow-up(`interface-contracts`, `core-entities`)를 묻고, 답을 `.stage-pilot/bootstrap/baseline.yaml`에 저장한 뒤 `docs/project-structure.md`, `docs/runtime-flows.md`, `docs/interface-contract.md`, `docs/data-model.md`, `docs/discovery/index.md`, `docs/srs/index.md`, `docs/batches/index.md`, `docs/releases/index.md`를 렌더링한다. + 이 단계는 bootstrap 단계이며, Discovery/REQ/Batch/Release 단위로 계산하지 않는다. + 샘플 결과는 `examples/bootstrap-baseline/bootstrap-baseline-output.md`를 참고한다. +2. `new-discovery` + baseline 초기화가 끝난 뒤 첫 real Discovery를 실제 제품/서비스 변경 주제로 생성한다. + +이미 코드와 실행 경로가 있는 기존 저장소에 StagePilot을 처음 적용할 때도 시작점은 동일하게 `bootstrap-baseline`이다. 차이는 bootstrap이 질문만으로 baseline을 만들지 않고, 현재 저장소 구조와 실행 흔적을 먼저 읽어 `observed` 또는 `mixed` baseline을 만든다는 점이다. + +1. 설치 직후 `python3 .stage-pilot/tools/stagepilot-doctor.py .` 또는 `bash .stage-pilot/tools/stagepilot.sh doctor .`로 누락된 bootstrap 파일과 active docs 상태를 확인한다. +2. active SDLC 문서가 아직 없다면 `/bootstrap-baseline`을 실행해 기존 저장소 기준의 `docs/project-structure.md`, `docs/runtime-flows.md`, `docs/interface-contract.md`, `docs/data-model.md`, active index를 만든다. +3. 저장소만으로 프로젝트 정체성이나 계획 runtime을 충분히 설명할 수 없으면 `python3 .stage-pilot/tools/stagepilot-bootstrap-seed.py .` 또는 `bash .stage-pilot/tools/stagepilot.sh bootstrap-seed .`로 `.stage-pilot/bootstrap/baseline.yaml`을 먼저 만들고, 그 다음 `/bootstrap-baseline`을 실행해 baseline을 `mixed`로 보강한다. +4. bootstrap이 끝난 뒤 첫 real Discovery는 baseline 생성 자체가 아니라 현재 진행하려는 기능 변경, 운영 이슈, 기술 결정 중 하나를 주제로 `new-discovery`에서 시작한다. +5. 이미 `docs/discovery/`, `docs/srs/`, `docs/batches/`, `docs/releases/` 아래 active unit가 운영 중인 저장소라면 bootstrap을 광범위하게 다시 수행하지 말고 `run-sdlc ` 또는 해당 active unit의 skill로 이어서 처리한다. 이 경우 `bootstrap-baseline`은 단순 누락 복구에만 사용한다. + +### 6.1 Requirements Phase + +1. `new-discovery` + baseline 초기화 이후 사용자 요청, 운영 피드백, issue 입력을 실제 변경 주제의 Discovery 초안으로 만든다. +2. `confirm-discovery` + Discovery를 저장소 현실과 대조하고 REQ drafting 가능 상태로 `confirmed` 전환한다. + 선행 품질 점검이 필요하면 보조 helper인 `review-discovery`로 누락, OQ 후보, 중복, 불필요 서술을 먼저 정리할 수 있다. 이 helper는 상태 전환 없이 review만 수행한다. +3. `draft-req` + confirmed Discovery에서 승인 가능한 REQ 문서를 만든다. +4. `confirm-req` + REQ를 `Approved`로 전환한다. +5. `change-req` + 기존 REQ 수정이 필요할 때 Change Log 기반 변경 요청을 먼저 기록하고 상태 되돌림/재검증 범위를 판정한다. +6. `suggest-batch-reqs` + Approved REQ를 어떤 batch로 묶을지 후보를 제시한다. + `change-req` 이후에도 delivery로 이어질 REQ만 batch 후보로 본다. +7. `draft-batch` + 선택된 REQ 묶음으로 `standard` 또는 `batch-lite` profile의 batch 폴더와 기본 문서를 생성한다. + +### 6.2 Delivery Phase + +1. `draft-batch-planning` + batch 범위, 제외 범위, 의존성, 마일스톤, 리스크를 정리한다. +2. `draft-batch-design` + batch 수준의 architecture summary, changed areas, key decisions를 정리한다. `batch-lite`에서 구조 영향이 없으면 생략할 수 있다. +3. `run-batch-implementation` + 실제 코드 변경을 수행하고 implementation 문서에 changed files, execution log, validation을 기록한다. +4. `draft-batch-verification` + REQ acceptance criteria를 evidence에 연결한다. `batch-lite`는 planning과 implementation만으로도 검증할 수 있지만, 구조 영향이 생기면 design을 추가해야 한다. +5. `confirm-batch-verification` + evidence가 충분하면 batch를 `release-candidate`로 전환한다. +6. `confirm-req-implemented` + `release-candidate` 또는 `released` batch의 verification evidence를 기준으로 포함 REQ를 `Implemented`로 동기화한다. + +### 6.3 Release & Feedback Phase + +1. `draft-release` + 하나 이상의 `release-candidate` batch로 release 문서를 생성하고 `docs-only`, `tooling`, `app-service` 중 하나의 release profile을 선택한다. +2. `confirm-release` + rollout, rollback, verification checklist와 profile별 검증 항목을 점검하고 release를 `confirmed`로 전환한다. +3. `capture-release-feedback` + 배포 후 운영 관찰 결과와 후속 Discovery/REQ/change-req 입력을 release 문서에 남긴다. +4. `suggest-next-discovery` + release feedback, baseline gap, 미구현/변경 대기 REQ를 읽고 다음 반복 후보를 추천한다. + +### 6.4 경량 변경 경로 + +아래 조건을 모두 만족하면 `minor-change` fast path를 사용할 수 있다. + +- `Approved` REQ가 정확히 1개다. +- 범위가 문서, 템플릿, 스크립트, bootstrap, 설정처럼 국소적이다. +- 구조, 인터페이스, runtime flow 변경이 없다. +- 독립 release가 가능하고 사람 선택이 필요한 묶음 판단이 남아 있지 않다. + +이 경로에서는 `suggest-batch-reqs`를 생략하고 바로 `draft-batch`를 실행해 `Profile: batch-lite` batch를 만든다. `batch-lite`는 최초에 `index.md`와 `planning.md`만 만들고, implementation/verification 문서는 해당 단계 진입 시 추가한다. design은 planning의 `Design Gate`가 `yes`가 될 때만 생성한다. + +### 6.5 Orchestrator 사용 기준 + +`run-sdlc`는 현재 문서 상태를 읽고 다음에 실행할 skill을 1단계씩 안내하는 entrypoint다. + +- active unit가 아직 없고 baseline 문서나 active index가 비어 있으면 `bootstrap-baseline`을 먼저 제안한다. +- Discovery 입력이면 `confirm-discovery`, `draft-req`, `suggest-batch-reqs` 중 다음 단계를 제안한다. +- REQ 입력이면 `confirm-req`, `change-req`, `suggest-batch-reqs`, `draft-batch`, `confirm-req-implemented` 중 다음 단계를 제안한다. 단일 저위험 REQ면 `minor-change -> batch-lite` 경로를 우선 제안할 수 있다. +- Batch 입력이면 `draft-batch-planning`, `draft-batch-design`, `run-batch-implementation`, `draft-batch-verification`, `confirm-batch-verification`, `confirm-req-implemented`, `draft-release` 중 다음 단계를 제안한다. +- Release 입력이면 선택된 release profile과 feedback handoff 상태를 기준으로 `confirm-release`, `capture-release-feedback`, `suggest-next-discovery`, `change-req`를 제안한다. + +### 6.6 변경 관리와 개선 루프 + +- 기존 REQ의 `Requirement`, `Acceptance Criteria`, `Impacted Area`를 바꿀 때는 먼저 `change-req`로 새 Change Log 항목을 만든다. +- 변경이 기존 구현/검증을 무효화하면 REQ 상태를 `Implemented -> Approved` 또는 필요 시 `Proposed`로 되돌린다. +- release feedback는 `Discovery Input`, `REQ Input`, `Change Request Input`으로 나눠 기록한다. +- 다음 개선 루프의 우선순위만 먼저 정하고 싶으면 `suggest-next-discovery`를 사용한다. + +## 7. Phase Gate 기준 + +### 7.1 Requirements 완료 기준 + +아래 조건을 만족해야 Delivery로 넘어간다. + +- Discovery가 `confirmed` 상태다. +- 구현 대상으로 채택할 REQ가 `Approved` 상태다. +- REQ의 Acceptance Criteria가 구현 판단 가능한 수준이다. +- 사람 결정 필요 항목과 후속 backlog 항목이 분리되어 있다. +- 기존 REQ 변경이 있었다면 `change-req` 기록과 상태 동기화가 끝났다. + +### 7.2 Delivery 완료 기준 + +아래 조건을 만족해야 Release & Feedback으로 넘어간다. + +- batch planning, implementation, verification 문서가 존재한다. +- `standard` batch이거나 `batch-lite`에서 구조 영향이 생긴 경우 design 문서가 존재한다. +- batch에 포함된 REQ acceptance criteria가 evidence와 연결된다. +- verification blocker가 해소되었다. +- batch 상태가 `release-candidate`다. +- 구현과 검증이 완료된 포함 REQ는 필요 시 `Implemented` 상태로 동기화되었다. + +### 7.3 Release & Feedback 완료 기준 + +아래 조건을 만족해야 release 단위를 닫는다. + +- release 계획이 `confirmed` 상태를 통과했다. +- 배포 결과가 기록되었다. +- 운영 관찰 결과가 release 문서에 남았다. +- 후속 Discovery, REQ, change-req 입력이 정리되었다. + +## 8. 사람 판단이 필요한 경계 + +다음 항목은 AI가 임의로 확정하지 않는다. + +- 승인자 지정 +- Owner 지정 +- 정책 선택 +- 우선순위 승인 +- 외부 시스템 또는 외부 데이터 확인 결과 +- `suggest-batch-reqs` 결과 중 어떤 후보를 실제 batch로 채택할지에 대한 선택 + +특히 `suggest-batch-reqs`는 추천안을 만드는 단계이며, 최종 batch 묶음은 사람 선택이 필요하다. 따라서 orchestrator는 이 경계에서 자동 진행하지 않고 멈춘다. 단, 단일 저위험 REQ는 `minor-change` fast path로 바로 `batch-lite`를 만들 수 있다. + +## 9. 자동화 규칙 + +### 9.1 Discovery 자동 생성 확장 규칙 + +현재 StagePilot 기본 패키지에는 GitHub issue label 기반 Discovery 자동 생성 workflow/script가 포함되어 있지 않다. + +호스트 저장소가 별도 workflow 또는 script를 추가해 Discovery 자동 생성을 확장할 수 있으며, 그런 자동화는 아래 규칙을 따라야 한다. + +- Discovery ID는 `dcy-<3자리>__` 형식을 사용한다. +- 동일 issue에 대해 반복 실행해도 중복 Discovery를 만들지 않는다. +- discovery index에 문서 링크와 상태를 등록한다. +- 결과는 issue comment로 다시 보고한다. + +자동화를 추가하지 않는 경우에도 Discovery 생성과 index 갱신의 source of truth는 여전히 `new-discovery` skill과 `docs/discovery/` 문서다. + +### 9.2 Index 관리 규칙 + +- `docs/discovery/index.md`는 자동 생성 및 자동 갱신 결과를 포함할 수 있다. +- `docs/srs/index.md`, `docs/batches/index.md`, `docs/releases/index.md`는 각 confirm 또는 draft 단계에서 상태와 register를 함께 맞춘다. +- 상태 변경은 본문 문서와 index가 항상 함께 갱신되어야 한다. + +## 10. 표준 작업 절차 + +### 10.1 신규 기능 또는 운영 이슈가 들어온 경우 + +1. issue 또는 요청 내용을 기준으로 `new-discovery`를 실행한다. +2. Discovery를 저장소 현실과 맞춰 보강한 뒤 `confirm-discovery`를 실행한다. +3. Discovery를 REQ로 쪼개고 `draft-req`를 수행한다. +4. 각 REQ를 `confirm-req`로 승인한다. +5. REQ가 여러 개이거나 묶음 판단이 필요하면 `suggest-batch-reqs`로 batch 후보를 받고, 사람이 묶음을 선택한다. +6. 단일 저위험 REQ면 `draft-batch`를 `minor-change -> batch-lite` 경로로 바로 실행한다. +7. 그 외 경우 선택된 묶음으로 `draft-batch`를 실행한다. +8. planning, design(필요한 경우), implementation, verification을 순서대로 수행한다. +9. `confirm-batch-verification`으로 release-candidate를 만든다. +10. `confirm-req-implemented`로 구현/검증 완료 REQ를 `Implemented`로 동기화한다. +11. `draft-release`, `confirm-release`로 배포를 준비하고 실행한다. 이때 release profile은 `docs-only`, `tooling`, `app-service` 중 하나를 선택한다. +12. `capture-release-feedback`으로 운영 환류를 남긴다. +13. 필요하면 `suggest-next-discovery`로 다음 반복 후보를 정리하거나, 기존 REQ 수정이면 `change-req`로 바로 연결한다. + +### 10.2 이미 존재하는 문서를 이어서 처리하는 경우 + +1. 가장 가까운 식별자 하나를 선택한다. +2. `run-sdlc `로 현재 단위의 다음 skill을 확인한다. +3. 제안된 다음 skill을 실행한다. +4. 사람이 결정해야 하는 경계에 도달하면 그 결정만 먼저 확정한다. + +## 11. Hotfix 예외 경로 + +긴급 핫픽스는 Requirements와 Delivery 설계를 경량화할 수 있다. 단, 아래 4가지는 생략하지 않는다. + +1. 최소 요구사항 정의 + 증상, 영향, 성공 조건을 기록한다. +2. 최소 검증 + 재현 테스트 또는 핵심 시나리오 확인을 남긴다. +3. 배포 전 롤백 계획 + 실패 시 복구 방법을 명시한다. +4. 배포 후 Postmortem + 운영 결과를 기록하고 다음 sprint 입력으로 환류한다. + +## 12. 유지보수 규칙 + +이 메뉴얼을 수정해야 하는 경우는 아래와 같다. + +- skill 이름이 바뀌는 경우 +- active 문서 루트가 바뀌는 경우 +- 상태 모델이 바뀌는 경우 +- workflow 또는 자동 생성 경로가 바뀌는 경우 +- phase gate 기준이 바뀌는 경우 + +위 항목이 바뀌면 아래 파일들도 함께 점검한다. + +- `source/instruction.md` (설치 시 `.github/copilot-instructions.md`와 `CLAUDE.md`로 fan-out) +- `skills/run-sdlc/SKILL.md` +- 관련 `confirm-*`, `draft-*`, `run-*`, `capture-*` skill +- `docs/discovery/index.md` +- `docs/srs/index.md` +- `docs/batches/index.md` +- `docs/releases/index.md` + +### 12.1 새 skill 추가 시 체크리스트 + +skill을 새로 추가할 때는 아래 두 파일을 반드시 함께 추가한다. + +| 파일 | 역할 | +| --- | --- | +| `skills//SKILL.md` | skill 본문 (subtree로 `.stage-pilot/skills/`에 노출된다) | +| `source/.claude/commands/.md` | Claude Code용 wrapper (내용: `Follow the instructions in \`.stage-pilot/skills//SKILL.md\` exactly.`) | + +wrapper는 설치 시 `source/.claude/commands/`에서 호스트 `.claude/commands/`로 복사된다. skill을 삭제하거나 이름을 변경할 때도 두 경로를 함께 처리한다. + +## 13. 빠른 점검표 + +### 13.1 Requirements 진입 전 + +- 요청 또는 운영 이슈가 명확한가 +- 기존 Discovery를 갱신할지 새로 만들지 판단했는가 + +### 13.2 Delivery 진입 전 + +- 관련 REQ가 모두 `Approved`인가 +- batch 범위와 제외 범위가 정리되었는가 + +### 13.3 Release 진입 전 + +- batch가 `release-candidate`인가 +- rollout plan, rollback plan, verification checklist가 준비되었는가 \ No newline at end of file diff --git a/examples/bootstrap-baseline/.stagepilot/bootstrap/baseline.yaml b/examples/bootstrap-baseline/.stagepilot/bootstrap/baseline.yaml new file mode 100644 index 0000000..ef31bc4 --- /dev/null +++ b/examples/bootstrap-baseline/.stagepilot/bootstrap/baseline.yaml @@ -0,0 +1,115 @@ +schema_version: 1 +capture_mode: declared + +project: + repository_name: sample-host-project + display_name: Sample Host Project + summary: Python-based API service for curated market data analysis. + primary_domain: financial analytics + +stack: + tech_stack: + - Python + - FastAPI + - PostgreSQL + +runtime: + primary_runtime: api-service + primary_entrypoints: + - name: uvicorn src.main:app + purpose: serve the HTTP API + - name: python -m src.jobs.refresh_market_data + purpose: refresh curated market data snapshots + +interfaces: + contracts: + - name: HTTP market data API + type: http-api + actors: internal dashboards and downstream service clients + purpose: serve curated market data and derived analytics to clients + stability: experimental + inputs: + - client requests for curated market data and analytics + outputs: + - JSON market snapshot payloads and derived analytics payloads + errors: + - 404 not_found :: symbol is not available in the curated dataset + - 503 upstream_unavailable :: refreshed data is temporarily unavailable + compatibility_rules: + - response payload changes should remain backward compatible within the same API version + - name: Refresh job command + type: cli + actors: operators and scheduled automation + purpose: update stored market data snapshots from upstream sources + stability: internal + inputs: + - python -m src.jobs.refresh_market_data :: trigger a refresh of curated market data snapshots + outputs: + - refreshed local snapshot artifacts persisted for later API reads + errors: + - non-zero exit code :: refresh failed and data should be treated as stale + compatibility_rules: + - command arguments and exit-code semantics should remain stable for automation callers + shared_constraints: + - all interfaces assume symbol identifiers are validated before persistence or response rendering + - refresh failures must not silently overwrite previously known-good snapshots + current_gaps: + - authentication and authorization rules are not defined yet + - API versioning strategy is still undecided + +structure: + planned_top_level_areas: + - path: src/ + responsibility: application code and background jobs + - path: scripts/ + responsibility: developer and operator helper commands + - path: tests/ + responsibility: automated validation and regression coverage + +data_model: + persistence_backend: PostgreSQL + entities: + - name: Market Snapshot + purpose: represent the latest curated market data for a tracked symbol + source_of_truth: PostgreSQL market_snapshots table + lifecycle: collected -> normalized -> published -> superseded + key_fields: + - symbol :: string :: tracked market identifier + - captured_at :: timestamp :: time the upstream data snapshot was collected + - payload :: json :: normalized market data stored for API responses and analytics inputs + relationships: + - Analytics Snapshot :: derived from one published Market Snapshot + state_rules: + - only one published snapshot per symbol should be treated as current + - superseded snapshots remain queryable for audit or replay use cases + - name: Analytics Snapshot + purpose: represent derived metrics computed from a market snapshot + source_of_truth: PostgreSQL analytics_snapshots table + lifecycle: queued -> computed -> published -> superseded + key_fields: + - symbol :: string :: tracked market identifier + - derived_at :: timestamp :: time analytics were computed + - metrics :: json :: derived indicators and summary values + relationships: + - Market Snapshot :: source snapshot used to compute analytics + state_rules: + - published analytics must reference an existing published or superseded market snapshot + - analytics should be recomputed whenever a new published market snapshot replaces the current one + consistency_rules: + - symbol identifiers must remain consistent across snapshot and analytics entities + - analytics records must not exist without a corresponding source market snapshot + persistence_notes: + - refresh jobs write Market Snapshot rows before recomputing Analytics Snapshot rows + - future retention and archival policies are not defined yet + current_gaps: + - exact table schema and indexing strategy are still undecided + - historical retention policy is still undecided + +notes: + known_unknowns: + - deployment target is undecided + +metadata: + captured_at_kst: 2026-04-30 16:20 + source: bootstrap-baseline-questionnaire + updated_from: user-answer \ No newline at end of file diff --git a/examples/bootstrap-baseline/README.md b/examples/bootstrap-baseline/README.md new file mode 100644 index 0000000..4097e59 --- /dev/null +++ b/examples/bootstrap-baseline/README.md @@ -0,0 +1,38 @@ +# Bootstrap Baseline Example + +이 예시는 fresh host 저장소에 StagePilot을 설치한 뒤 `/bootstrap-baseline`을 실행했을 때 남아야 하는 최소 active baseline 상태를 보여 준다. + +## Scenario + +1. host 저장소에 StagePilot을 설치했다. +2. 아직 Discovery, REQ, Batch, Release 문서는 하나도 없었다. +3. doctor를 실행하면 `INFO [bootstrap-required]`로 `/bootstrap-baseline`이 필요하다는 안내가 나온다. +4. `/bootstrap-baseline`이 최소 질문 세트를 받고 `.stage-pilot/bootstrap/baseline.yaml`을 만든 뒤 baseline 문서와 active index를 초기화했다. +5. 첫 real Discovery는 아직 시작하지 않았고, 이 예시는 그 직후 상태를 보여 준다. + +## Files to inspect + +- `doctor-before-bootstrap-output.md` + - bootstrap 이전 fresh host 저장소에서 기대하는 doctor 출력 예시다. +- `bootstrap-baseline-output.md` + - `/bootstrap-baseline` 실행 결과 예시다. +- `.stage-pilot/bootstrap/baseline.yaml` + - bootstrap 질문 답변을 저장한 seed file 예시다. +- `docs/project-structure.md` + - bootstrap으로 생성된 구조 baseline 초안 예시다. +- `docs/runtime-flows.md` + - bootstrap으로 생성된 runtime flow baseline 초안 예시다. +- `docs/interface-contract.md` + - bootstrap으로 생성된 인터페이스 계약 baseline 초안 예시다. +- `docs/data-model.md` + - bootstrap으로 생성된 데이터 모델 baseline 초안 예시다. +- `docs/discovery/index.md`, `docs/srs/index.md`, `docs/batches/index.md`, `docs/releases/index.md` + - 첫 real Discovery 전까지 비어 있어도 되는 register 골격이다. + +## Validation + +이 예시 루트는 doctor로 독립 검증할 수 있다. + +```bash +python3 .stage-pilot/tools/stagepilot-doctor.py examples/bootstrap-baseline +``` \ No newline at end of file diff --git a/examples/bootstrap-baseline/bootstrap-baseline-output.md b/examples/bootstrap-baseline/bootstrap-baseline-output.md new file mode 100644 index 0000000..9cb4972 --- /dev/null +++ b/examples/bootstrap-baseline/bootstrap-baseline-output.md @@ -0,0 +1,45 @@ +# Sample Output: bootstrap-baseline + +- Input: + - fresh host repository after StagePilot install +- Asked Questions: + - `project-summary` -> `Python-based API service for curated market data analysis` + - `primary-domain` -> `financial analytics` + - `tech-stack` -> `Python, FastAPI, PostgreSQL` + - `primary-runtime` -> `api-service` + - `primary-entrypoints` -> `uvicorn src.main:app :: serve the HTTP API`, `python -m src.jobs.refresh_market_data :: refresh curated market data snapshots` + - `interface-contracts` -> `HTTP market data API :: http-api :: internal dashboards and downstream service clients :: serve curated market data and derived analytics to clients`, `Refresh job command :: cli :: operators and scheduled automation :: update stored market data snapshots from upstream sources` + - `interface-inputs` -> `HTTP market data API :: GET /markets/{symbol} :: request the current curated snapshot for a symbol`, `HTTP market data API :: GET /analytics/{symbol} :: request derived analytics for a symbol`, `Refresh job command :: python -m src.jobs.refresh_market_data :: trigger a refresh of curated market data snapshots` + - `interface-outputs` -> `HTTP market data API :: JSON market snapshot payload :: current normalized market data for the requested symbol`, `HTTP market data API :: JSON analytics payload :: derived analytics and summary metrics for the requested symbol`, `Refresh job command :: refreshed local snapshot artifacts :: normalized data persisted for later API reads` + - `interface-errors` -> `HTTP market data API :: 404 not_found :: symbol is not available in the curated dataset`, `HTTP market data API :: 503 upstream_unavailable :: refreshed data is temporarily unavailable`, `Refresh job command :: non-zero exit code :: refresh failed and data should be treated as stale` + - `core-entities` -> `Market Snapshot :: represent the latest curated market data for a tracked symbol`, `Analytics Snapshot :: represent derived metrics computed from a market snapshot` + - `persistence-backend` -> `PostgreSQL` + - `compatibility-rules` -> `HTTP market data API :: response payload changes should remain backward compatible within the same API version`, `Refresh job command :: command arguments and exit-code semantics should remain stable for automation callers` +- Created Files: + - `.stage-pilot/bootstrap/baseline.yaml` + - `docs/discovery/index.md` + - `docs/srs/index.md` + - `docs/batches/index.md` + - `docs/releases/index.md` + - `docs/project-structure.md` + - `docs/runtime-flows.md` + - `docs/interface-contract.md` + - `docs/data-model.md` +- Seed Status: + - `.stage-pilot/bootstrap/baseline.yaml`: created +- Baseline Status: + - `docs/project-structure.md`: created + - `docs/runtime-flows.md`: created + - `docs/interface-contract.md`: created + - `docs/data-model.md`: created +- Index Status: + - `docs/discovery/index.md`: created + - `docs/srs/index.md`: created + - `docs/batches/index.md`: created + - `docs/releases/index.md`: created +- Notes: + - baseline docs were rendered from `.stage-pilot/bootstrap/baseline.yaml` + - no Discovery document was created + - no REQ, Batch, or Release unit was opened +- Next Action: + - `/new-discovery ` \ No newline at end of file diff --git a/examples/bootstrap-baseline/docs/batches/index.md b/examples/bootstrap-baseline/docs/batches/index.md new file mode 100644 index 0000000..8df4c90 --- /dev/null +++ b/examples/bootstrap-baseline/docs/batches/index.md @@ -0,0 +1,6 @@ +# Batch Index + +## Batch Register + +| BAT ID | Profile | Status | Included REQ | Discovery | Folder | +| --- | --- | --- | --- | --- | --- | \ No newline at end of file diff --git a/examples/bootstrap-baseline/docs/data-model.md b/examples/bootstrap-baseline/docs/data-model.md new file mode 100644 index 0000000..ce77eb4 --- /dev/null +++ b/examples/bootstrap-baseline/docs/data-model.md @@ -0,0 +1,97 @@ +# Sample Host Project Data Model + +- Baseline Mode: declared +- Baseline Seed: .stage-pilot/bootstrap/baseline.yaml +- Status: confirmed +- Owner: Platform Team +- Last Updated (KST): 2026-04-30 16:30 +- Source Discovery / Batch: bootstrap-baseline + +## Project Summary +- Python-based API service for curated market data analysis. + +## Primary Domain +- financial analytics + +## Primary Persistence Backend +- PostgreSQL + +## Purpose +- This document captures the initial data model baseline after StagePilot bootstrap. +- It provides a shared reference for later REQ, delivery, migration, and verification work. + +## Scope +- Covers core entities, relationships, lifecycle rules, and persistence assumptions for the sample host project. +- Does not replace ORM code, migrations, or detailed acceptance criteria. + +## Model Summary + +- Market Snapshot :: normalized raw and curated market data for a tracked symbol +- Analytics Snapshot :: derived metrics computed from a Market Snapshot + +## Entity: `Market Snapshot` + +- Purpose: represent the latest curated market data for a tracked symbol +- Source of Truth: PostgreSQL `market_snapshots` table +- Lifecycle: collected -> normalized -> published -> superseded + +### Key Fields + +- `symbol` :: string :: tracked market identifier +- `captured_at` :: timestamp :: time the upstream data snapshot was collected +- `payload` :: json :: normalized market data stored for API responses and analytics inputs + +### Relationships + +- `Analytics Snapshot` :: derived from one published Market Snapshot + +### State Rules + +- only one published snapshot per symbol should be treated as current +- superseded snapshots remain queryable for audit or replay use cases + +## Entity: `Analytics Snapshot` + +- Purpose: represent derived metrics calculated from a market snapshot +- Source of Truth: PostgreSQL `analytics_snapshots` table +- Lifecycle: queued -> computed -> published -> superseded + +### Key Fields + +- `symbol` :: string :: tracked market identifier +- `derived_at` :: timestamp :: time analytics were computed +- `metrics` :: json :: derived indicators and summary values + +### Relationships + +- `Market Snapshot` :: source snapshot used to compute analytics + +### State Rules + +- published analytics must reference an existing published or superseded market snapshot +- analytics should be recomputed whenever a new published market snapshot replaces the current one + +## Shared Consistency Rules + +- `symbol` identifiers must remain consistent across snapshot and analytics entities +- analytics records must not exist without a corresponding source market snapshot + +## Persistence / Integration Notes + +- refresh jobs write Market Snapshot rows before recomputing Analytics Snapshot rows +- future retention and archival policies are not defined yet + +## Current Gaps / Planned Changes + +- exact table schema and indexing strategy are still undecided +- historical retention policy is still undecided + +## Update Triggers + +- Update this document when a core entity, key field, or relationship changes. +- Update this document when lifecycle or persistence behavior changes. +- Update this document when a batch introduces a migration, backfill, or breaking data assumption. + +## Change Log + +- 2026-04-30: bootstrap-baseline created the initial data model baseline from declared project inputs. \ No newline at end of file diff --git a/examples/bootstrap-baseline/docs/discovery/index.md b/examples/bootstrap-baseline/docs/discovery/index.md new file mode 100644 index 0000000..8c9f028 --- /dev/null +++ b/examples/bootstrap-baseline/docs/discovery/index.md @@ -0,0 +1,8 @@ +# Discovery 전체 인덱스 + + + +## Discovery 목록 + +| ID | 날짜 | 이슈명 | 문서 | 상태 | +|---|---|---|---|---| \ No newline at end of file diff --git a/examples/bootstrap-baseline/docs/interface-contract.md b/examples/bootstrap-baseline/docs/interface-contract.md new file mode 100644 index 0000000..fb816ec --- /dev/null +++ b/examples/bootstrap-baseline/docs/interface-contract.md @@ -0,0 +1,97 @@ +# Sample Host Project Interface Contract + +- Baseline Mode: declared +- Baseline Seed: .stage-pilot/bootstrap/baseline.yaml +- Status: confirmed +- Owner: Platform Team +- Last Updated (KST): 2026-04-30 16:30 +- Source Discovery / Batch: bootstrap-baseline +- Primary Runtime: api-service + +## Project Summary +- Python-based API service for curated market data analysis. + +## Primary Domain +- financial analytics + +## Purpose +- This document captures the initial interface contract baseline after StagePilot bootstrap. +- It provides a shared reference for future REQ, design, implementation, and verification work. + +## Scope +- Covers the planned API and job interfaces exposed by the sample host project. +- Does not duplicate detailed acceptance criteria or generated OpenAPI/schema artifacts. + +## Interface Summary + +- HTTP market data API :: serve curated market data and derived analytics to clients +- Refresh job command :: update stored market data snapshots from upstream sources + +## Interface: `HTTP market data API` + +- Type: http-api +- Consumers / Producers: internal dashboards and downstream service clients +- Purpose: serve curated market data and derived analytics to clients +- Stability: experimental + +### Inputs + +- `GET /markets/{symbol}` :: request the current curated snapshot for a symbol +- `GET /analytics/{symbol}` :: request derived analytics for a symbol + +### Outputs + +- JSON market snapshot payload :: current normalized market data for the requested symbol +- JSON analytics payload :: derived analytics and summary metrics for the requested symbol + +### Error Contract + +- `404 not_found` :: symbol is not available in the curated dataset +- `503 upstream_unavailable` :: refreshed data is temporarily unavailable + +### Compatibility Rules + +- response payload changes should remain backward compatible within the same API version + +## Interface: `Refresh job command` + +- Type: cli +- Consumers / Producers: operators and scheduled automation +- Purpose: update stored market data snapshots from upstream sources +- Stability: internal + +### Inputs + +- `python -m src.jobs.refresh_market_data` :: trigger a refresh of curated market data snapshots + +### Outputs + +- refreshed local snapshot artifacts :: normalized data persisted for later API reads + +### Error Contract + +- non-zero exit code :: refresh failed and data should be treated as stale + +### Compatibility Rules + +- command arguments and exit-code semantics should remain stable for automation callers + +## Shared Constraints + +- all interfaces assume symbol identifiers are validated before persistence or response rendering +- refresh failures must not silently overwrite previously known-good snapshots + +## Current Gaps / Planned Changes + +- authentication and authorization rules are not defined yet +- API versioning strategy is still undecided + +## Update Triggers + +- Update this document when an externally visible API, event, CLI, or file contract changes. +- Update this document when a batch changes compatibility, validation, or error behavior. +- Update this document when a new external consumer or producer is introduced. + +## Change Log + +- 2026-04-30: bootstrap-baseline created the initial interface contract baseline from declared project inputs. \ No newline at end of file diff --git a/examples/bootstrap-baseline/docs/project-structure.md b/examples/bootstrap-baseline/docs/project-structure.md new file mode 100644 index 0000000..70bc4c9 --- /dev/null +++ b/examples/bootstrap-baseline/docs/project-structure.md @@ -0,0 +1,91 @@ +# Sample Host Project Structure + +- Baseline Mode: declared +- Baseline Seed: .stage-pilot/bootstrap/baseline.yaml +- Status: confirmed +- Owner: Platform Team +- Last Updated (KST): 2026-04-30 16:30 +- Source Discovery / Batch: bootstrap-baseline + +## Project Summary +- Python-based API service for curated market data analysis. + +## Primary Domain +- financial analytics + +## Tech Stack +- Python +- FastAPI +- PostgreSQL + +## Primary Runtime +- Type: api-service +- Planned Entry Points: +- uvicorn src.main:app :: serve the HTTP API +- python -m src.jobs.refresh_market_data :: refresh curated market data snapshots + +## Purpose +- This document captures the initial repository structure baseline after StagePilot bootstrap. +- It becomes a shared reference before the first real Discovery starts. + +## Scope +- Covers top-level repository layout and broad module boundaries. +- Does not define feature-specific acceptance criteria or delivery units. + +## Top-Level Structure + +```text +. +├── .github/ +├── .stage-pilot/ +├── docs/ +├── src/ +├── scripts/ +├── tests/ +└── README.md +``` + +## Package / Module Responsibilities + +- `.github/` + - StagePilot skills, templates, and host instructions installed into the repository. +- `.stage-pilot/` + - bootstrap declaration seed used to render the baseline documents. +- `docs/` + - Active SDLC docs, index files, and cross-cutting baseline references. +- `src/` + - Product and service implementation code. +- `scripts/` + - developer and operator helper commands. +- `tests/` + - Automated checks and regression coverage. + +## Dependency Rules + +- Delivery documents under `docs/` must not be treated as implementation modules. +- `src/` can evolve independently, but structure changes should update this baseline. +- `.stage-pilot/bootstrap/baseline.yaml` should be updated when the declared project identity changes before code exists. +- Tooling automation under `.stage-pilot/` must not overwrite host source code. + +## Shared Boundaries + +- `.stage-pilot/` provides SDLC workflow assets. +- `.stage-pilot/` stores bootstrap declarations outside active SDLC units. +- `docs/` is the source of truth for active SDLC state. +- `src/`, `scripts/`, and `tests/` remain host-project owned implementation surfaces. + +## Current Gaps / Planned Changes + +- The first real Discovery has not been created yet. +- Deployment target is still undecided. +- Actual domain module boundaries will be refined once the first delivery topic is defined. + +## Update Triggers + +- Update this document when top-level directories change. +- Update this document when module ownership boundaries change. +- Update this document when a batch changes the approved structure baseline. + +## Change Log + +- 2026-04-30: bootstrap-baseline created the initial repository structure baseline from `.stage-pilot/bootstrap/baseline.yaml`. \ No newline at end of file diff --git a/examples/bootstrap-baseline/docs/releases/index.md b/examples/bootstrap-baseline/docs/releases/index.md new file mode 100644 index 0000000..a7717ce --- /dev/null +++ b/examples/bootstrap-baseline/docs/releases/index.md @@ -0,0 +1,6 @@ +# Release Index + +## Release Register + +| REL ID | Profile | Status | Included Batch | Discovery/REQ Input | Link | +| --- | --- | --- | --- | --- | --- | \ No newline at end of file diff --git a/examples/bootstrap-baseline/docs/runtime-flows.md b/examples/bootstrap-baseline/docs/runtime-flows.md new file mode 100644 index 0000000..0e3780e --- /dev/null +++ b/examples/bootstrap-baseline/docs/runtime-flows.md @@ -0,0 +1,68 @@ +# Sample Host Project Runtime Flows + +- Baseline Mode: declared +- Baseline Seed: .stage-pilot/bootstrap/baseline.yaml +- Status: confirmed +- Owner: Platform Team +- Last Updated (KST): 2026-04-30 16:30 +- Source Discovery / Batch: bootstrap-baseline +- Primary Runtime: api-service + +## Project Summary +- Python-based API service for curated market data analysis. + +## Purpose +- This document captures the initial command and runtime flow baseline after StagePilot bootstrap. +- It gives later Discovery and Batch work a stable reference before product-specific flows exist. + +## Scope +- Covers bootstrap, discovery start, and validation entry points. +- Does not define detailed feature behavior. + +## Covered Entry Points + +- uvicorn src.main:app :: serve the HTTP API +- python -m src.jobs.refresh_market_data :: refresh curated market data snapshots +- Copilot Chat /bootstrap-baseline :: initialize declared baseline docs + +## Shared Components + +- .stage-pilot/bootstrap/baseline.yaml +- `.stage-pilot/skills/` +- `docs/project-structure.md` +- `docs/*/index.md` + +## Flow: `bootstrap baseline initialization` + +1. StagePilot assets are installed into the host repository. +2. `/bootstrap-baseline` collects the minimum declaration set and writes `.stage-pilot/bootstrap/baseline.yaml`. +3. baseline docs and active index skeletons are rendered from that seed. +4. The repository becomes ready for the first real Discovery. + +## Flow: `planned host runtime` + +1. Operator starts `uvicorn src.main:app` to serve the planned HTTP API. +2. Background refresh can run with `python -m src.jobs.refresh_market_data`. +3. Detailed production flows remain subject to later Discovery and delivery work. + +## Flow Constraints + +- Fresh host repositories should complete baseline initialization before the first real Discovery. +- Declared runtime assumptions must stay in sync with `.stage-pilot/bootstrap/baseline.yaml` until observed implementation evidence exists. +- Structure or orchestration changes must update this document or `docs/project-structure.md`. + +## Current Gaps / Planned Changes + +- Product-specific runtime orchestration is not defined yet. +- Deployment target is undecided. +- Delivery and release flows will be added by later Discovery and Batch work. + +## Update Triggers + +- Update this document when command entry points change. +- Update this document when runtime orchestration changes. +- Update this document when a batch changes the approved flow baseline. + +## Change Log + +- 2026-04-30: bootstrap-baseline created the initial runtime flow baseline from `.stage-pilot/bootstrap/baseline.yaml`. \ No newline at end of file diff --git a/examples/bootstrap-baseline/docs/srs/index.md b/examples/bootstrap-baseline/docs/srs/index.md new file mode 100644 index 0000000..be0d471 --- /dev/null +++ b/examples/bootstrap-baseline/docs/srs/index.md @@ -0,0 +1,19 @@ +# Software Requirement Specification Index + +## 목적 +- 이 문서는 bootstrap-baseline 직후의 requirement register 골격 예시다. +- 첫 real Discovery 이후 `draft-req`가 실제 requirement를 추가한다. + +## Next Requirement ID + +- Current: `REQ-001` +- Rule: `Requirement Register`에 있는 가장 큰 REQ 번호보다 정확히 1 커야 한다. + +## Requirement Register + +| ID | Title | Type | Status | Priority | Owner | Link | +| --- | --- | --- | --- | --- | --- | --- | + +## Recent Change Log Summary + +- none \ No newline at end of file diff --git a/examples/bootstrap-baseline/doctor-before-bootstrap-output.md b/examples/bootstrap-baseline/doctor-before-bootstrap-output.md new file mode 100644 index 0000000..4181d8d --- /dev/null +++ b/examples/bootstrap-baseline/doctor-before-bootstrap-output.md @@ -0,0 +1,9 @@ +# Sample Output: doctor before bootstrap-baseline + +fresh host 저장소에서 아직 baseline이 초기화되지 않은 경우 doctor는 아래처럼 bootstrap 안내를 함께 출력한다. + +```text +WARN [missing-active-docs] .: No active SDLC docs were found under docs/. Workspace-specific checks were skipped. +INFO [bootstrap-required] .: Fresh host repos should run /bootstrap-baseline in Copilot Chat before the first real Discovery. Missing bootstrap files: docs/discovery/index.md, docs/srs/index.md, docs/batches/index.md, docs/releases/index.md, docs/project-structure.md, docs/runtime-flows.md +stagepilot-doctor: 0 error(s), 1 warning(s), 1 info message(s) +``` \ No newline at end of file diff --git a/examples/p3-change-management/README.md b/examples/p3-change-management/README.md new file mode 100644 index 0000000..326255a --- /dev/null +++ b/examples/p3-change-management/README.md @@ -0,0 +1,32 @@ +# P3 Example: change-req and suggest-next-discovery + +이 예시는 release feedback 이후 개선 루프를 어떻게 닫는지 보여 주는 self-contained sample workspace다. + +## Scenario + +1. `rel-001` 배포가 `feedback-captured` 상태로 닫혔다. +2. 운영 피드백에서 두 종류의 후속 작업이 나왔다. + - 기존 REQ 강화: 문서 release checklist의 acceptance criteria를 더 엄격하게 바꿔야 한다. + - 신규 반복 후보: release feedback를 기반으로 follow-up triage를 자동 추천하는 개선이 필요하다. +3. `suggest-next-discovery rel-001`는 위 두 항목을 각각 `change-req`와 `new-discovery`로 분류한다. +4. 이 예시의 active docs는 그 결과 `change-req req-001`를 이미 한 번 적용한 뒤의 상태를 보여 준다. + +## Files to inspect + +- `docs/releases/rel-001_20260428_docs_release_hardening.md` + - `Feedback Handoff`에 `Discovery Input`, `REQ Input`, `Change Request Input`이 모두 있다. +- `docs/srs/Documentation/req-001_docs_release_checklist.md` + - 두 번째 `Change Log` 항목이 `change-req` 실행 결과를 나타낸다. + - 구현 근거가 무효화되어 상태가 `Approved`로 되돌아간 상태다. +- `suggest-next-discovery-output.md` + - `suggest-next-discovery rel-001`의 예시 결과다. +- `change-req-output.md` + - `change-req req-001`의 예시 결과다. + +## Validation + +이 예시 루트는 doctor로 독립 검증할 수 있다. + +```bash +python3 .stage-pilot/tools/stagepilot-doctor.py examples/p3-change-management +``` \ No newline at end of file diff --git a/examples/p3-change-management/change-req-output.md b/examples/p3-change-management/change-req-output.md new file mode 100644 index 0000000..0bea906 --- /dev/null +++ b/examples/p3-change-management/change-req-output.md @@ -0,0 +1,22 @@ +# Sample Output: change-req req-001 + +- Target REQ: `docs/srs/Documentation/req-001_docs_release_checklist.md` +- New Change Entry: `CHG-2026-04-30-02` represented as `CHG-20260430-02` +- Change Summary: + - docs-only release profile에서도 broken-link 검증을 필수 acceptance criteria로 승격 + - delivery trace에 기존 batch/release 영향과 재검증 필요 여부를 추가 +- Impacted Delivery Trace: + - Batches: `bat-001` + - Releases: `rel-001` +- Revalidation Impact: + - Reverification Needed: `yes` + - Existing Implementation Invalidated: `yes` + - Status Recommendation: `revert-to-approved` +- Result: + - REQ status changed from `Implemented` to `Approved` + - `docs/srs/index.md` register status changed to `Approved` + - `Recent Change Log Summary` updated with the new change entry + +## Rationale + +이 변경은 기존 release evidence만으로는 새 acceptance criteria를 충족했다고 볼 수 없으므로 `Implemented` 상태를 유지할 수 없다. 따라서 REQ는 `Approved`로 되돌아가고, 후속 batch verification을 다시 거쳐야 한다. \ No newline at end of file diff --git a/examples/p3-change-management/docs/batches/bat-001_20260427_docs_release_hardening/design.md b/examples/p3-change-management/docs/batches/bat-001_20260427_docs_release_hardening/design.md new file mode 100644 index 0000000..22684fe --- /dev/null +++ b/examples/p3-change-management/docs/batches/bat-001_20260427_docs_release_hardening/design.md @@ -0,0 +1,23 @@ +# bat-001 Design + +- Status: confirmed + +## Architecture Summary +- docs-only release는 app-service health signal 대신 문서 품질 증거를 사용한다. + +## Changed Areas +- release template +- feedback handoff structure + +## Key Decisions +- docs-only release는 broken-link와 release note update를 핵심 evidence로 남긴다. + +## Edge Cases +- release 후 feedback로 acceptance criteria가 강화되면 REQ 상태를 되돌릴 수 있다. + +## Architecture Impact +- runtime-flows + +## Reference Doc Update Plan +- project-structure.md: none +- runtime-flows.md: docs-only release feedback flow 반영 \ No newline at end of file diff --git a/examples/p3-change-management/docs/batches/bat-001_20260427_docs_release_hardening/implementation.md b/examples/p3-change-management/docs/batches/bat-001_20260427_docs_release_hardening/implementation.md new file mode 100644 index 0000000..29e2d4e --- /dev/null +++ b/examples/p3-change-management/docs/batches/bat-001_20260427_docs_release_hardening/implementation.md @@ -0,0 +1,22 @@ +# bat-001 Implementation + +- Status: completed +- Batch: bat-001 + +## Plan Summary +- release template와 docs-only verification structure를 구현했다. + +## Changed Files +- .stage-pilot/templates/releases/release.md +- README.md + +## Execution Log +1. docs-only release profile 섹션 추가 +2. feedback handoff를 Discovery/REQ로 나누어 기록 + +## Validation +- manual release review +- doctor sample workspace check + +## Remaining Risks +- 후속 feedback가 REQ 변경으로 이어질 수 있다. \ No newline at end of file diff --git a/examples/p3-change-management/docs/batches/bat-001_20260427_docs_release_hardening/index.md b/examples/p3-change-management/docs/batches/bat-001_20260427_docs_release_hardening/index.md new file mode 100644 index 0000000..9899f86 --- /dev/null +++ b/examples/p3-change-management/docs/batches/bat-001_20260427_docs_release_hardening/index.md @@ -0,0 +1,26 @@ +# bat-001: docs release hardening + +- Status: released +- Profile: standard +- Source Discovery: dcy-001 +- Included REQ: req-001 +- Excluded REQ: 없음 + +## Summary +- docs-only release checklist를 requirement와 release evidence로 연결하는 batch다. + +## Scope +- In Scope: + - release template update + - docs-only verification structure +- Out of Scope: + - runtime health checks + +## Documents +- Planning: [planning.md](./planning.md) +- Design: [design.md](./design.md) +- Implementation: [implementation.md](./implementation.md) +- Verification: [verification.md](./verification.md) + +## Notes +- release feedback 이후 req-001이 다시 `Approved`로 되돌아갔다. \ No newline at end of file diff --git a/examples/p3-change-management/docs/batches/bat-001_20260427_docs_release_hardening/planning.md b/examples/p3-change-management/docs/batches/bat-001_20260427_docs_release_hardening/planning.md new file mode 100644 index 0000000..0fd10cd --- /dev/null +++ b/examples/p3-change-management/docs/batches/bat-001_20260427_docs_release_hardening/planning.md @@ -0,0 +1,30 @@ +# bat-001 Planning + +- Status: confirmed +- Batch: bat-001 +- Profile: standard + +## Included REQ +- req-001 + +## Delivery Plan +- release template와 docs-only release evidence 구조를 정리한다. + +## Dependencies +- Internal: + - release template + - README validation guide +- External: + - none + +## Design Gate +- Design Required Before Implementation: yes +- Reason: docs-only release profile과 evidence structure를 새로 정의해야 한다. + +## Milestones +1. release template 정리 +2. release evidence 반영 +3. feedback handoff 확인 + +## Risks +- docs-only validation 기준이 release 후 강화될 수 있다. \ No newline at end of file diff --git a/examples/p3-change-management/docs/batches/bat-001_20260427_docs_release_hardening/verification.md b/examples/p3-change-management/docs/batches/bat-001_20260427_docs_release_hardening/verification.md new file mode 100644 index 0000000..0132a0f --- /dev/null +++ b/examples/p3-change-management/docs/batches/bat-001_20260427_docs_release_hardening/verification.md @@ -0,0 +1,22 @@ +# bat-001 Verification + +- Status: confirmed +- Batch: bat-001 + +## Acceptance Mapping +- req-001: + - AC1 -> docs-only release checklist evidence + - AC2 -> feedback handoff structure + +## Evidence +- Test: + - doctor sample validation passed before release +- Manual: + - docs-only release note and checklist reviewed +- Logs: + - release review notes + +## Result +- Ready for Release: true +- Blocking Issues: + - 없음 \ No newline at end of file diff --git a/examples/p3-change-management/docs/batches/index.md b/examples/p3-change-management/docs/batches/index.md new file mode 100644 index 0000000..4930146 --- /dev/null +++ b/examples/p3-change-management/docs/batches/index.md @@ -0,0 +1,7 @@ +# Batch Index + +## Batch Register + +| BAT ID | Profile | Status | Included REQ | Discovery | Folder | +| --- | --- | --- | --- | --- | --- | +| bat-001 | standard | released | req-001 | dcy-001 | [batch](./bat-001_20260427_docs_release_hardening) | \ No newline at end of file diff --git a/examples/p3-change-management/docs/discovery/dcy-001_20260425_docs_release_quality_loop.md b/examples/p3-change-management/docs/discovery/dcy-001_20260425_docs_release_quality_loop.md new file mode 100644 index 0000000..085d8ba --- /dev/null +++ b/examples/p3-change-management/docs/discovery/dcy-001_20260425_docs_release_quality_loop.md @@ -0,0 +1,149 @@ +# Discovery + +- 상태: confirmed +- 문서 ID: dcy-001_20260425_docs_release_quality_loop +- 이슈명: Docs release quality loop +- 작성 시각(KST): 2026-04-25 10:00 +- 마지막 갱신 시각(KST): 2026-04-30 13:00 +- 대체됨: 없음 +- 후속 Discovery 참조: 없음 +- 생성된 REQ 참조: req-001 + +# 1. 계획 상태 요약 + +- 연계 문서 현황: + - 관련 점수 파일: 미생성 + - 관련 TODO/메모: 없음 +- 해석: + - docs-only release에서도 문서 검증 품질이 release 판단 근거에 직접 연결돼야 한다. + - 운영 피드백으로 checklist 강화를 반복적으로 흡수할 수 있어야 한다. + +# 2. 요구사항 판정 결과 + +- 판정: 신규 +- 근거 + - 기존 release checklist는 docs-only profile 세분화 기준이 부족했다. + - follow-up feedback를 기존 REQ와 연결할 필요가 있었다. + +# 3. 문제점의 요약 + +- 현재 상태 + - docs release는 수동 검토 메모에 의존한다. + - broken-link 점검과 release note 점검이 acceptance criteria로 명확히 드러나지 않는다. +- 기대 상태 + - release checklist가 명시적 acceptance criteria와 evidence로 연결된다. + - 운영 피드백이 후속 REQ 또는 Discovery로 자연스럽게 이어진다. +- 이해관계자 + - maintainer + - reviewer + - docs consumer +- 제약사항 + - 문서 패키지는 app-service 운영 지표를 사용하지 않는다. + - doctor 검증과 release 문서만으로도 추적 가능해야 한다. + +## 주요 사용자 시나리오 + +- maintainer가 docs-only release 전 broken-link와 release note 상태를 확인한다. +- reviewer가 release feedback를 보고 기존 REQ 변경인지 신규 반복인지 판단한다. + +# 4. 이번에 정의할 변경 + +- docs release checklist를 REQ와 release evidence에 연결한다. +- feedback handoff에서 후속 작업 분류 기준을 남긴다. + +## 영향 범위 + +- 코드 영향: + - 없음 +- 문서 영향: + - docs/srs/Documentation/req-001_docs_release_checklist.md + - docs/releases/rel-001_20260428_docs_release_hardening.md +- 운영 영향: + - docs-only release review 절차 강화 + +# 5. 요구사항 목록 + +## 기능 요구사항 +- FR-1: docs-only release는 broken-link 검증을 acceptance criteria로 포함해야 한다. +- FR-2: release feedback는 후속 Discovery/REQ/change-req 입력으로 분리되어야 한다. + +## 비기능 요구사항 +- NFR-1: 변경 이력은 Change Log와 release feedback 양쪽에서 추적 가능해야 한다. + +## 범위 경계 +- In Scope + - docs release checklist + - feedback handoff structure +- Out of Scope + - app-service runtime health check + - 외부 배포 자동화 + +# 6. 리스크/가정 목록 + +## 리스크 +- R-1 (Medium): checklist 강화 시 기존 구현 증거가 부족해질 수 있다. + - 완화 방안: change-req로 상태 되돌림과 재검증 계획을 남긴다. +- R-2 (Low): feedback 분류가 모호하면 follow-up 경로가 섞일 수 있다. + - 완화 방안: suggest-next-discovery에서 분류 근거를 명시한다. + +## 가정 +- A-1: docs-only release는 doctor와 수동 리뷰 결과로 검증 가능하다. +- A-2: feedback handoff는 release 문서가 source of truth다. + +# 7. 초기 성공 기준 + +- S-1: docs checklist REQ가 release feedback까지 추적된다. +- S-2: feedback에서 change-req와 new-discovery가 구분된다. + +## 측정 방식 및 데이터 출처 + +- S-1 측정 방식: doctor traceability 확인 / 데이터 출처: docs/srs, docs/releases +- S-2 측정 방식: feedback handoff review / 데이터 출처: release 문서 + +# 8. REQ로 넘기기 전 확인 체크 + +- [x] 범위(In Scope/Out of Scope) 확정 + - 기준: docs checklist와 feedback 구조만 포함 +- [x] 성공 지표의 측정 방식/데이터 출처 확정 + - 기준: doctor와 release 문서로 추적 가능 +- [x] High 영향 리스크 우선순위 및 해소 책임자 지정 + - 기준: maintainer가 후속 변경 관리 수행 +- [x] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 + - 기준: open question 없음 +- [x] Freeze 확정 담당자(CONFIRMED_BY) 지정 + - 기준: maintainer confirmed + +# 9. 파일 처리 결과 + +- 처리 결과: 생성 +- 생성 경로: docs/discovery/dcy-001_20260425_docs_release_quality_loop.md +- 참조 문서: + - docs/srs/Documentation/req-001_docs_release_checklist.md + - docs/releases/rel-001_20260428_docs_release_hardening.md + +# 10. 사용자 결정 필요 항목 요약 + +## DECIDE +- 없음 + +## CONFIRM +- 없음 + +## DATA +- 없음 + +# 11. Discovery Freeze + +## 섹션 참조 +- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) +- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) +- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) +- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) +- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) + +## Freeze 플래그 +- Handoff Decision: REQ Drafting 진행 가능 +- Handoff Rationale: docs release quality loop를 REQ와 release feedback에 연결할 준비가 됨 +- Ready for REQ Drafting: true +- Confirmed By: maintainer +- Confirmed At (KST): 2026-04-25 11:00 \ No newline at end of file diff --git a/examples/p3-change-management/docs/discovery/index.md b/examples/p3-change-management/docs/discovery/index.md new file mode 100644 index 0000000..3dde566 --- /dev/null +++ b/examples/p3-change-management/docs/discovery/index.md @@ -0,0 +1,7 @@ +# Discovery 전체 인덱스 + +## Discovery 목록 + +| ID | 날짜 | 이슈명 | 문서 | 상태 | +|---|---|---|---|---| +| dcy-001 | 20260425 | Docs release quality loop | [Discovery](./dcy-001_20260425_docs_release_quality_loop.md) | confirmed | \ No newline at end of file diff --git a/examples/p3-change-management/docs/releases/index.md b/examples/p3-change-management/docs/releases/index.md new file mode 100644 index 0000000..f454a80 --- /dev/null +++ b/examples/p3-change-management/docs/releases/index.md @@ -0,0 +1,7 @@ +# Release Index + +## Release Register + +| REL ID | Profile | Status | Included Batch | Discovery/REQ Input | Link | +| --- | --- | --- | --- | --- | --- | +| rel-001 | docs-only | feedback-captured | bat-001 | dcy-001, req-001 | [release](./rel-001_20260428_docs_release_hardening.md) | \ No newline at end of file diff --git a/examples/p3-change-management/docs/releases/rel-001_20260428_docs_release_hardening.md b/examples/p3-change-management/docs/releases/rel-001_20260428_docs_release_hardening.md new file mode 100644 index 0000000..f630951 --- /dev/null +++ b/examples/p3-change-management/docs/releases/rel-001_20260428_docs_release_hardening.md @@ -0,0 +1,94 @@ +# rel-001: docs release hardening + +- Status: feedback-captured +- Profile: docs-only +- Included Batch: bat-001 + +## Scope +- Included: + - docs-only release profile evidence + - feedback handoff structure +- Excluded: + - app-service rollout automation + +## Rollout Plan +1. merge release template updates +2. publish package update + +## Rollback Plan +- revert package update and release note changes + +## Verification Checklist +- docs-only release note updated +- broken-link verification reviewed +- feedback handoff recorded + +## Release Execution Evidence +- Release Status Transition Target: released + +### Deployment Facts +- Executed At: + - 2026-04-28T09:00:00Z +- Completed At: + - 2026-04-28T09:12:00Z +- Environment: + - repository package +- Operator: + - docs maintainer +- Revision or Artifact: + - main@HEAD +- Commands or Procedure: + - merge release prep and publish package update + +### Profile-Specific Checks +- docs-only: + - Published or Packaged Targets: + - release template and README guidance + - Link or Rendering Check: + - doctor broken-link validation reviewed during release + - Navigation or Consumer Update: + - release note updated for docs-only profile +- tooling: + - Install or Upgrade Smoke: + - none + - CLI or Script Execution: + - none + - Compatibility Note: + - none +- app-service: + - Health Endpoint: + - n/a + - Startup Log Fields: + - n/a + - Request Log Fields: + - n/a + - Smoke or Manual Verification: + - n/a + +### Rollback Outcome +- Rollback Required: + - no +- Rollback Trigger: + - none +- Rollback Action: + - none + +### Release Decision Evidence +- Released Decision: + - yes +- Rationale: + - docs-only release evidence was sufficient at release time +- Recorded By: + - docs maintainer +- Recorded At: + - 2026-04-28T09:12:00Z + +## Feedback Handoff +- Observation Summary: + - docs consumers requested stricter broken-link evidence and clearer follow-up triage +- Discovery Input: + - release feedback triage automation for maintainers +- REQ Input: + - release note checklist for docs-only profile +- Change Request Input: + - req-001 docs release checklist needs stricter broken-link acceptance criteria \ No newline at end of file diff --git a/examples/p3-change-management/docs/srs/Documentation/req-001_docs_release_checklist.md b/examples/p3-change-management/docs/srs/Documentation/req-001_docs_release_checklist.md new file mode 100644 index 0000000..da1d307 --- /dev/null +++ b/examples/p3-change-management/docs/srs/Documentation/req-001_docs_release_checklist.md @@ -0,0 +1,110 @@ +# req-001: Docs release checklist + +- Status: Approved +- Type: Documentation +- Priority: Medium +- Owner: Docs Maintainer + +## Intent +- docs-only release에서도 소비자 관점의 품질 게이트를 문서화하고 검증 근거로 남긴다. + +## Requirement +- docs-only release는 release note 반영과 broken-link 검증 결과를 release evidence에 포함해야 한다. + +## Acceptance Criteria +1. Given docs-only release가 준비되었을 때, When release evidence를 검토하면, Then broken-link 검증 결과가 명시되어야 한다. +2. Given release feedback가 수집되었을 때, When follow-up를 분류하면, Then Discovery/REQ/change-req 입력이 구분되어야 한다. + +## Impacted Area +- Docs: docs/releases/rel-001_20260428_docs_release_hardening.md, README.md +- Modules: tools/stagepilot.sh, .stage-pilot/tools/stagepilot-doctor.py +- Tests: doctor release feedback summary validation + +## Notes +- `CHG-20260430-02`로 acceptance criteria가 강화되어 기존 구현 증거는 재검증이 필요하다. + +## Change Log + +### CHG-20260426-01 +- Date: 2026-04-26 +- Author: Docs Maintainer + +#### Change Summary +- initial requirement created + +#### Intent +- docs-only release checklist를 requirement로 관리한다. + +#### Acceptance Criteria Delta +- Added: + - release evidence must include release note update result +- Updated: + - none +- Removed: + - none + +#### Impacted Area Delta +- Docs: + - release template +- Modules: + - none +- Tests: + - manual release review + +#### Delivery Trace Delta +- Batches: + - bat-001 +- Releases: + - rel-001 + +#### Revalidation Impact +- Reverification Needed: no +- Existing Implementation Invalidated: no +- Status Recommendation: keep-current +- Follow-up Action: implement through bat-001 + +#### Validation Plan +- verify release checklist before approval + +### CHG-20260430-02 +- Date: 2026-04-30 +- Author: Docs Maintainer + +#### Change Summary +- strengthen docs-only release criteria to require broken-link evidence and explicit feedback triage + +#### Intent +- release feedback에서 드러난 품질 gap을 기존 REQ에 반영한다. + +#### Acceptance Criteria Delta +- Added: + - broken-link 검증 결과가 release evidence에 포함되어야 한다. + - feedback handoff는 Discovery/REQ/change-req 입력을 구분해야 한다. +- Updated: + - release note update alone is no longer sufficient for docs-only release verification. +- Removed: + - none + +#### Impacted Area Delta +- Docs: + - docs/releases/rel-001_20260428_docs_release_hardening.md + - README.md +- Modules: + - .stage-pilot/tools/stagepilot-doctor.py +- Tests: + - doctor feedback summary sample + +#### Delivery Trace Delta +- Batches: + - bat-001 needs reverification or successor batch is required +- Releases: + - rel-001 remains historical evidence only + +#### Revalidation Impact +- Reverification Needed: yes +- Existing Implementation Invalidated: yes +- Status Recommendation: revert-to-approved +- Follow-up Action: prepare a follow-up batch to reverify docs-only release criteria + +#### Validation Plan +- rerun doctor on a follow-up batch and verify release feedback summary plus broken-link evidence \ No newline at end of file diff --git a/examples/p3-change-management/docs/srs/index.md b/examples/p3-change-management/docs/srs/index.md new file mode 100644 index 0000000..dc68960 --- /dev/null +++ b/examples/p3-change-management/docs/srs/index.md @@ -0,0 +1,26 @@ +# Software Requirement Specification Index + +## 목적 +- 이 문서는 프로젝트의 SRS 운영 기준과 requirement 목록을 관리하는 entry point이다. +- 개별 requirement의 상세 내용은 타입별 폴더 하위 `req-XXX_*.md` 파일에서 관리한다. + +## 사용 방법 +1. 새로운 requirement가 필요하면 `Next Requirement ID`를 확인하고 `docs/srs/req-template.md`를 복사해 `docs/srs//req-XXX_.md` 파일을 생성한다. +2. 초안 작성은 `draft-req`, 승인 전환은 `confirm-req`, 변경 대응은 `change-req`, 구현 완료 전환은 `confirm-req-implemented` skill을 기준으로 수행한다. +3. 작성 또는 수정 후 누락과 모호성을 점검한다. +4. requirement 추가 또는 변경 후 `Next Requirement ID`, `Requirement Register`, `Recent Change Log Summary`를 함께 갱신한다. + +## Next Requirement ID + +- Current: `REQ-002` +- Rule: `Requirement Register`에 있는 가장 큰 REQ 번호보다 정확히 1 커야 한다. + +## Requirement Register + +| ID | Title | Type | Status | Priority | Owner | Link | +| --- | --- | --- | --- | --- | --- | --- | +| REQ-001 | Docs release checklist | Documentation | Approved | Medium | Docs Maintainer | [req-001](./Documentation/req-001_docs_release_checklist.md) | + +## Recent Change Log Summary + +- 2026-04-30 `REQ-001` `CHG-20260430-02`: broken-link acceptance criteria 강화, 상태 `Implemented -> Approved` \ No newline at end of file diff --git a/examples/p3-change-management/suggest-next-discovery-output.md b/examples/p3-change-management/suggest-next-discovery-output.md new file mode 100644 index 0000000..cd722ae --- /dev/null +++ b/examples/p3-change-management/suggest-next-discovery-output.md @@ -0,0 +1,44 @@ +# Sample Output: suggest-next-discovery rel-001 + +- Input Release: `rel-001` +- Release Status: `feedback-captured` +- Basis: + - `Discovery Input`: `release feedback triage automation for maintainers` + - `REQ Input`: `release note checklist for docs-only profile` + - `Change Request Input`: `req-001 docs release checklist needs stricter broken-link acceptance criteria` + +## CANDIDATE-1 + +- Classification: `change-req` +- Target: `req-001` +- Value: High +- Risk: Medium +- Effort: Low +- Reason: + - 피드백이 기존 REQ acceptance criteria 강화로 닫힌다. + - 기존 batch/release trace를 유지한 채 REQ 상태를 재검증 대상으로 되돌릴 수 있다. +- Next Command: + - `/change-req req-001` + +## CANDIDATE-2 + +- Classification: `new-discovery` +- Target: `release feedback triage automation for maintainers` +- Value: Medium +- Risk: Low +- Effort: Medium +- Reason: + - release feedback를 다음 반복 후보로 자동 분류하는 기능은 기존 REQ 수정만으로 닫히지 않는다. + - 독립 Discovery로 추적하는 편이 범위와 성공 기준을 명확히 유지한다. +- Next Command: + - `/new-discovery release feedback triage automation for maintainers` + +## CANDIDATE-3 + +- Classification: `no-action` +- Target: `release note checklist for docs-only profile` +- Value: Low +- Risk: Low +- Effort: Low +- Reason: + - 현재 `req-001` 변경에 흡수 가능하므로 별도 반복으로 분리하지 않는다. \ No newline at end of file diff --git a/skills/CHANGELOG.md b/skills/CHANGELOG.md new file mode 100644 index 0000000..6048c0d --- /dev/null +++ b/skills/CHANGELOG.md @@ -0,0 +1,155 @@ +# Changelog + +All notable changes to the StagePilot skill pack should be recorded in this file. + +## 2026-05-22 + +### run-batch-delivery `0.7.0` + +- added new orchestration skill `run-batch-delivery` +- defined a 6-step delivery chain for an existing batch: `draft-batch-planning` -> `draft-batch-design` -> `run-batch-implementation` -> `draft-batch-verification` -> `confirm-batch-verification` -> `confirm-req-implemented` +- clarified that the skill resumes from the first incomplete delivery stage and stops at approval blockers instead of forcing progress +- clarified that release drafting remains a separate next step and is recommended, not auto-executed + +### draft-req `1.0.3` + +- bumped `draft-req` from `1.0.2` to `1.0.3` +- normalized the template path section to the same logical `stage-pilot/templates/...` + common resolution format used across the pack +- clarified source vs target path distinction for SRS index and REQ document generation + +### bootstrap-baseline `1.0.1` + +- bumped `bootstrap-baseline` from `1.0.0` to `1.0.1` +- changed template references from fixed `.stage-pilot/...` paths to logical `stage-pilot/templates/...` paths +- added the common template path resolution section (`.stage-pilot` -> `.vendor/stage-pilot` -> `~/.stage-pilot`) +- clarified source vs target path distinction for bootstrap seed, index, and baseline document generation + +### new-discovery `1.0.1` + +- bumped `new-discovery` from `1.0.0` to `1.0.1` +- changed template references from fixed `.stage-pilot/...` paths to logical `stage-pilot/templates/...` paths +- added the common template path resolution section (`.stage-pilot` -> `.vendor/stage-pilot` -> `~/.stage-pilot`) +- clarified source vs target path distinction for discovery document, discovery index, and baseline reference templates + +### draft-release `0.8.1` + +- bumped `draft-release` from `0.8.0` to `0.8.1` +- changed template references from fixed `.stage-pilot/...` paths to logical `stage-pilot/templates/...` paths +- added the common template path resolution section (`.stage-pilot` -> `.vendor/stage-pilot` -> `~/.stage-pilot`) +- clarified source vs target path distinction for release document and release index generation + +### draft-batch `0.9.1` + +- bumped `draft-batch` from `0.9.0` to `0.9.1` +- added `When to use` and `Do not use when` guidance to separate batch creation from recommendation, approval, downstream drafting, and SDLC routing +- clarified that `draft-batch` operates only after people have selected the REQ set to include +- replaced the fixed batch template path wording with logical `stage-pilot/templates/batches/...` paths plus explicit resolution order +- clarified that `minor-change` is an input fast path while the persisted batch profile remains `batch-lite` +- added explicit source-to-target template mapping, source discovery recording rules, `Common Pitfalls`, and a `Verification Checklist` + +### suggest-batch-reqs `0.8.2` + +- bumped `suggest-batch-reqs` from `0.8.0` to `0.8.2` +- added `Do not use when` guidance to separate recommendation from REQ approval, direct batch drafting, minor-change fast paths, and top-level SDLC routing +- added a dedicated `사람 선택 규칙` section so batch candidate adoption stays explicitly human-decided +- strengthened Discovery input handling so linked REQs must be explicit and approved before recommendation proceeds +- added explicit candidate-level batch profile hints for `standard` vs `batch-lite`, including required reasoning +- added `Common Pitfalls` and a `Verification Checklist` + +### confirm-req `0.9.1` + +- bumped `confirm-req` from `0.9.0` to `0.9.1` +- added `Do not use when` guidance to separate approval from drafting, change management, implementation confirmation, and batch planning +- made approval prerequisites explicit, including blocker vs non-blocker handling for `Notes` and open questions +- clarified that approval `Change Log` entries are appended and not written for blocked REQs +- added a local `docs/srs/index.md` preservation rule so existing repository wording/format is updated in place rather than overwritten from template wording +- added `Common Pitfalls` and a `Verification Checklist` + +### draft-req `1.0.2` + +- bumped `draft-req` from `1.0.1` to `1.0.2` +- added `Do not use when` guidance to separate `draft-req` from discovery review, confirmation, change-req, and batch planning stages +- made the confirmed Discovery prerequisite explicit and documented the limited draft-only exception case +- added a `Common Pitfalls` section covering confirmation, NFR splitting, baseline gaps, path confusion, index updates, and source Discovery back-references +- replaced the old validation bullets with a `Verification Checklist` for operational verification after drafting + +### draft-req `1.0.1` + +- bumped `draft-req` from `1.0.0` to `1.0.1` +- replaced fixed template path references with logical template source paths under `stage-pilot/templates/...` +- added explicit template path resolution rules for workspace-local, vendor/subtree, and Hermes external installs +- clarified resolution priority: workspace-local -> vendor/subtree -> Hermes external +- clarified the distinction between template source paths and generated target paths under `docs/srs/` +- added validation checks for logical-path-to-physical-path resolution and source/target path confusion + +### confirm-discovery `0.9.1` + +- bumped `confirm-discovery` from `0.9.0` to `0.9.1` +- applied wording cleanup (`Skill` -> `skill`) for local style consistency +- clarified that `Confirmed By` is never auto-generated or inferred by AI +- clarified when to use `review-discovery` or `new-discovery` instead of `confirm-discovery` +- added baseline cross-check guidance for `docs/project-structure.md` and `docs/runtime-flows.md` +- added explicit pitfalls and a verification checklist +- clarified how to add a missing `docs/discovery/index.md` row while preserving register format + +### Metadata normalization + +Applied long-term management metadata across all StagePilot skills under `~/.stage-pilot/skills`. + +Changed for every skill: + +- added or normalized `author: Justin Ko` +- added `license: private` +- added `metadata.hermes.tags` +- added `metadata.hermes.related_skills` +- normalized frontmatter layout for long-term reuse + +### Version reclassification + +Assigned workflow-maturity-based semantic versions instead of a uniform initial version. + +#### Stable core (`1.0.0`) + +- `bootstrap-baseline` +- `new-discovery` +- `draft-req` + +#### Near-stable (initial classification) + +- `confirm-discovery` → later bumped to `0.9.1` +- `confirm-req` → `0.9.0` +- `draft-batch` → `0.9.0` + +#### Maturing workflow (`0.8.0`) + +- `change-req` +- `confirm-batch-verification` +- `confirm-release` +- `draft-batch-design` +- `draft-batch-planning` +- `draft-batch-verification` +- `draft-release` +- `review-discovery` +- `suggest-batch-reqs` + +#### Active evolution / orchestration (`0.7.0`) + +- `confirm-req-implemented` +- `run-batch-delivery` +- `run-batch-implementation` +- `run-sdlc` + +#### Early-support (`0.6.0`) + +- `capture-release-feedback` +- `suggest-next-discovery` + +### Notes + +Current intent of the version scale: + +- `0.6.x` — useful early-support skill, likely to evolve significantly +- `0.7.x` — execution/orchestration behavior still evolving +- `0.8.x` — maturing drafting/decision workflow +- `0.9.x` — near-stable, only moderate refinement expected +- `1.0.x` — stable core workflow skill diff --git a/skills/README.md b/skills/README.md new file mode 100644 index 0000000..78cdaca --- /dev/null +++ b/skills/README.md @@ -0,0 +1,123 @@ +# StagePilot Skills + +Personal StagePilot skill pack for Hermes external skills. + +This directory is configured as an external skill root and contains the reusable skills, shared conventions, and cross-skill metadata for the StagePilot SDLC workflow. + +## Purpose + +StagePilot organizes work into a document-driven flow around: + +- baseline bootstrap +- discovery +- requirements +- batch planning / design / implementation / verification +- release +- release feedback and next-discovery planning + +These skills are intended to be reusable across repositories and machines, with versioned frontmatter so changes can be tracked over time. + +## Installation / usage on another machine + +1. Copy or clone this directory to the target machine. +2. Point Hermes `config.yaml` external skills setting at `~/.stage-pilot/skills`. +3. Reload skills: + - `/reload-skills` in-session, or + - restart Hermes / start a new session. + +## Skill inventory + +### Stable core skills + +- `bootstrap-baseline` — `1.0.1` +- `new-discovery` — `1.0.1` +- `draft-req` — `1.0.3` + +These are treated as the most reusable, foundational StagePilot workflow skills. + +### Near-stable skills + +- `confirm-discovery` — `0.9.1` +- `confirm-req` — `0.9.1` +- `draft-batch` — `0.9.1` + +These are close to stable but may still get light structural refinement. + +### Maturing workflow skills + +- `change-req` — `0.8.0` +- `confirm-batch-verification` — `0.8.0` +- `confirm-release` — `0.8.0` +- `draft-batch-design` — `0.8.0` +- `draft-batch-planning` — `0.8.0` +- `draft-batch-verification` — `0.8.0` +- `draft-release` — `0.8.1` +- `review-discovery` — `0.8.0` +- `suggest-batch-reqs` — `0.8.2` + +These are structured and usable now, but are expected to evolve as StagePilot usage expands. + +### Active evolution / orchestration skills + +- `confirm-req-implemented` — `0.7.0` +- `run-batch-delivery` — `0.7.0` +- `run-batch-implementation` — `0.7.0` +- `run-sdlc` — `0.7.0` + +These involve orchestration or execution-heavy behavior and are expected to change more than drafting skills. + +### Early-support skills + +- `capture-release-feedback` — `0.6.0` +- `suggest-next-discovery` — `0.6.0` + +These are useful but still relatively early in the overall workflow maturity. + +## Versioning policy + +Semantic versioning is used loosely to describe workflow maturity: + +- `0.6.x` — useful early-support skill, likely to evolve significantly +- `0.7.x` — active execution/orchestration logic still evolving +- `0.8.x` — maturing drafting/decision workflow +- `0.9.x` — near-stable and expected to change lightly +- `1.0.x` — stable core skill suitable for broad reuse + +Practical interpretation: + +- patch bump (`x.y.Z`) → wording fixes, metadata tweaks, examples, minor clarifications +- minor bump (`x.Y.z`) → meaningful behavior/structure expansion without redefining the role of the skill +- major bump (`X.y.z`) → significant workflow or contract change + +## Metadata conventions + +Each StagePilot skill should include these frontmatter fields: + +- `name` +- `description` +- `version` +- `author: Justin Ko` +- `license: private` +- `metadata.hermes.tags` +- `metadata.hermes.related_skills` + +Optional but commonly retained: + +- `argument-hint` +- `user-invocable` + +## Recommended repository hygiene + +If this directory is tracked in git, recommended companions are: + +- `README.md` — this file +- `CHANGELOG.md` — top-level release notes for the skill pack +- commits for every meaningful skill change +- optional tags when you want to snapshot a known-good pack revision + +## Last metadata normalization + +- Date: 2026-05-22 +- Author normalized to: `Justin Ko` +- Version fields reclassified by workflow maturity +- Cross-skill tags and related-skill metadata added to all StagePilot skills diff --git a/skills/bootstrap-baseline/SKILL.md b/skills/bootstrap-baseline/SKILL.md new file mode 100644 index 0000000..8841863 --- /dev/null +++ b/skills/bootstrap-baseline/SKILL.md @@ -0,0 +1,200 @@ +--- +name: bootstrap-baseline +description: "Use when: initializing StagePilot in a project before the first real Discovery, running /bootstrap-baseline to create baseline documents and active indexes, or backfilling missing docs/project-structure.md, docs/runtime-flows.md, docs/interface-contract.md, and docs/data-model.md without creating a Discovery unit." +version: 1.0.1 +author: Justin Ko +license: private +argument-hint: "예: 신규 저장소 baseline 초기화, StagePilot init, baseline 문서/인덱스 생성" +user-invocable: true +metadata: + hermes: + tags: [stage-pilot, bootstrap, baseline, documentation, sdlc] + related_skills: [new-discovery, run-sdlc] +--- + +# Purpose + +This skill initializes the StagePilot baseline outside the normal Discovery -> REQ -> Batch -> Release flow. + +`docs/project-structure.md`, `docs/runtime-flows.md`, `docs/interface-contract.md`, `docs/data-model.md`, 그리고 active index 문서는 첫 real Discovery 전에 준비하는 bootstrap 산출물이다. 이 skill은 baseline과 index를 먼저 만들고, 첫 Discovery가 실제 제품/서비스 변경 주제로 시작되도록 만든다. + +greenfield 저장소처럼 읽을 코드나 설정이 아직 없으면, 이 skill은 사용자에게 최소 질문 세트를 묻고 `.stage-pilot/bootstrap/baseline.yaml` seed를 만든 뒤 baseline 문서를 렌더링한다. + +# When to use + +다음 상황에서 사용한다. + +- StagePilot을 새 프로젝트에 처음 적용한 직후 baseline 문서와 index를 만들 때 +- `/bootstrap-baseline` 또는 유사한 init 요청으로 baseline 초기화를 수행할 때 +- `docs/project-structure.md`, `docs/runtime-flows.md`, `docs/interface-contract.md`, `docs/data-model.md`, `docs/discovery/index.md`, `docs/srs/index.md`, `docs/batches/index.md`, `docs/releases/index.md` 중 일부가 없어서 SDLC를 시작하기 전에 뼈대를 복구해야 할 때 +- 첫 real Discovery를 기능, 운영 문제, 기술 결정 같은 실제 변경 주제로 시작하고 싶을 때 + +다음 상황에는 이 skill을 기본 경로로 사용하지 않는다. + +- 이미 진행 중인 Discovery/REQ/Batch/Release가 있고, baseline 변경 책임이 특정 active unit에 속하는 경우 +- 실제 제품/서비스 변경 주제가 이미 명확하여 Discovery 초안 생성이 우선인 경우 + +# Inputs + +이 skill은 아래 입력을 사용한다. + +- 현재 저장소 루트와 디렉터리 구조 +- `docs/discovery/`, `docs/srs/`, `docs/batches/`, `docs/releases/` 존재 여부 +- `.stage-pilot/bootstrap/baseline.yaml` 존재 여부 +- `docs/project-structure.md`, `docs/runtime-flows.md`, `docs/interface-contract.md`, `docs/data-model.md` 존재 여부 +- 템플릿의 논리 경로 + - `stage-pilot/templates/bootstrap/baseline-seed.yaml` + - `stage-pilot/templates/discovery/index.md` + - `stage-pilot/templates/srs/index.md` + - `stage-pilot/templates/batches/index.md` + - `stage-pilot/templates/releases/index.md` + - `stage-pilot/templates/project-structure.md` + - `stage-pilot/templates/runtime-flows.md` + - `stage-pilot/templates/interface-contract.md` + - `stage-pilot/templates/data-model.md` +- 현재 시각 (KST) + +## Template path resolution + +- 템플릿은 하나의 물리 경로만 고정하지 말고 `stage-pilot/templates/...`를 논리 경로로 취급한다. +- 물리 경로는 다음 순서로 해석한다. + 1. `.stage-pilot/templates/...` + 2. `~/.stage-pilot/templates/...` +- 여러 후보가 동시에 존재하면 가장 우선순위가 높은 한 곳만 사용하고, 서로 다른 설치 위치의 템플릿을 섞지 않는다. +- 템플릿 source path와 생성 target path를 혼동하지 않는다. + - source: `stage-pilot/templates/...` + - target: `.stage-pilot/bootstrap/baseline.yaml`, `docs/discovery/index.md`, `docs/srs/index.md`, `docs/batches/index.md`, `docs/releases/index.md`, `docs/project-structure.md`, `docs/runtime-flows.md`, `docs/interface-contract.md`, `docs/data-model.md` + +# Core Rules + +## 0. 단위 경계 + +- `bootstrap-baseline`은 Discovery, REQ, Batch, Release 중 어느 것도 아니다. +- 이 skill은 초기 운영 기준선을 만드는 bootstrap 단계다. +- 이 skill은 Discovery 문서를 생성하지 않는다. +- baseline 초기화가 끝난 뒤 첫 real Discovery는 실제 제품/서비스 변경, 운영 이슈, 기술 결정 같은 주제로 시작한다. + +## 1. 생성 대상 + +- 아래 경로가 없으면 생성한다. + - `.stage-pilot/bootstrap/baseline.yaml` + - `docs/discovery/index.md` + - `docs/srs/index.md` + - `docs/batches/index.md` + - `docs/releases/index.md` + - `docs/project-structure.md` + - `docs/runtime-flows.md` + - `docs/interface-contract.md` + - `docs/data-model.md` +- 이미 존재하는 파일은 임의로 덮어쓰지 않는다. +- 일부만 없는 경우에는 누락된 파일만 생성한다. + +## 1.5 최소 질문 세트 + +- 저장소 관찰만으로 baseline 핵심 정보가 부족하면 먼저 질문을 수행한다. +- 질문은 아래 다섯 개를 기본 세트로 사용한다. + - `project-summary` + - 질문: `이 프로젝트를 한 문장으로 설명하면 무엇인가?` + - 답변 형식: 한 문장 자유 텍스트 + - `primary-domain` + - 질문: `이 프로젝트의 주요 도메인은 무엇인가?` + - 답변 형식: 짧은 명사구 또는 분야명 + - `tech-stack` + - 질문: `계획 중인 주 언어, 프레임워크, 핵심 인프라는 무엇인가?` + - 답변 형식: 쉼표로 구분한 목록 + - `primary-runtime` + - 질문: `계획 중인 주 실행 형태는 무엇인가?` + - 답변 형식: `cli` | `api-service` | `web-app` | `worker` | `library` | `mixed` | `other` + - `primary-entrypoints` + - 질문: `대표 진입점 1~3개를 적어 달라.` + - 답변 형식: 줄마다 `name :: purpose` +- 아래 질문은 선택 입력이다. + - `planned-top-level-areas` + - 질문: `이미 알고 있는 top-level 경로가 있으면 적어 달라.` + - 답변 형식: 줄마다 `path :: responsibility` + - `interface-contracts` + - 질문: `대표 인터페이스 1~2개가 있으면 적어 달라.` + - 답변 형식: 줄마다 `name :: type :: actors :: purpose` + - `interface-inputs` + - 질문: `각 인터페이스의 대표 input을 적어 달라.` + - 답변 형식: 줄마다 `interface-name :: input contract or invocation note` + - `interface-outputs` + - 질문: `각 인터페이스의 대표 output을 적어 달라.` + - 답변 형식: 줄마다 `interface-name :: output contract or artifact` + - `interface-errors` + - 질문: `각 인터페이스의 대표 error를 적어 달라.` + - 답변 형식: 줄마다 `interface-name :: error code or failure behavior` + - `core-entities` + - 질문: `핵심 엔티티 1~2개가 있으면 적어 달라.` + - 답변 형식: 줄마다 `name :: purpose` + - `persistence-backend` + - 질문: `주 persistence backend가 있으면 적어 달라.` + - 답변 형식: 짧은 backend 또는 storage 이름 + - `compatibility-rules` + - 질문: `인터페이스 호환성 규칙이 있으면 적어 달라.` + - 답변 형식: 줄마다 `interface-name :: rule` 또는 `* :: rule` + - `known-unknowns` + - 질문: `아직 미정인 핵심 항목이 있으면 적어 달라.` + - 답변 형식: 줄마다 항목 하나 +- 이미 읽을 수 있는 저장소 증거가 있으면 질문 수를 줄이고, seed의 비어 있는 값만 보충한다. +- `interface-contracts`, `interface-inputs`, `interface-outputs`, `interface-errors`, `core-entities`, `persistence-backend`, `compatibility-rules`가 비어 있더라도 bootstrap은 `runtime`, `primary-entrypoints`, `primary-domain`, `tech-stack`, `planned-top-level-areas`, `known-unknowns`를 바탕으로 기본 초안을 유도해 seed를 보강할 수 있다. + +## 1.6 seed 파일 source-of-truth 규칙 + +- bootstrap 질문 결과와 저장소 관찰 결과의 합성 source of truth는 `.stage-pilot/bootstrap/baseline.yaml`이다. +- seed 파일은 active SDLC unit가 아니라 bootstrap 선언 파일이다. +- seed 파일이 있으면 baseline 문서 생성과 갱신 시 이를 우선 읽는다. +- seed 파일이 없고 저장소 증거가 충분하지 않으면 질문 없이 baseline 문서를 추정 생성하지 않는다. +- 저장소 관찰값과 사용자 답변이 함께 있으면 `capture_mode`는 `mixed`다. +- 저장소 관찰값만으로 충분하면 `capture_mode`는 `observed`다. +- 사용자 선언이 중심이고 저장소 관찰값은 보조면 `capture_mode`는 `declared`다. + +## 2. baseline 문서 작성 원칙 + +- `docs/project-structure.md`는 seed의 `project`, `stack`, `runtime`, `structure` 값을 먼저 반영하고, 저장소 관찰 결과가 있으면 보강한다. +- `docs/runtime-flows.md`는 seed의 `runtime`과 `notes` 값을 먼저 반영하고, 저장소 관찰 결과가 있으면 보강한다. +- `docs/interface-contract.md`는 seed의 `interfaces` 섹션을 먼저 반영하고, 값이 부족하면 `runtime`, `primary-entrypoints`, `known-unknowns`를 바탕으로 대표 외부 API, CLI, 이벤트, 파일 I/O 계약의 기본 초안을 구체화한다. +- `docs/data-model.md`는 seed의 `data_model` 섹션을 먼저 반영하고, 값이 부족하면 `primary-domain`, `tech-stack`, `planned-top-level-areas`, `known-unknowns`를 바탕으로 핵심 엔티티, 관계, 상태, persistence 규칙의 기본 초안을 구체화한다. +- 입력만으로 알 수 없는 항목은 의미 있는 플레이스홀더로 남기되, 추론 가능한 구조 정보는 실제 문장으로 채운다. +- baseline 문서의 `Source Discovery / Batch`는 bootstrap 산출물임을 드러내는 값으로 채운다. +- baseline 문서는 `declared`, `observed`, `mixed` 중 어느 모드로 렌더링되었는지와 seed 경로를 드러내야 한다. + +## 3. index 작성 원칙 + +- index 문서는 템플릿을 기반으로 생성한다. +- 초기화 시점에는 register 본문만 만들고 실제 Discovery/REQ/Batch/Release 항목은 비워 둔다. +- index 생성은 active unit 생성으로 간주하지 않는다. + +## 4. 진행 중 unit가 있는 경우 + +- 진행 중인 Discovery/REQ/Batch/Release가 이미 있고 baseline 문서만 일부 비어 있다면, 먼저 현재 active unit가 그 변경을 소유해야 하는지 판정한다. +- 특정 active unit의 요구사항이나 설계/검증 근거로 baseline 변경이 필요한 경우에는 그 unit 안에서 처리한다. +- 단순 bootstrap 누락 복구이고 active unit 책임이 아니라면 `bootstrap-baseline`으로 누락 파일만 보강할 수 있다. + +# Execution Procedure + +1. 저장소에서 active docs 루트와 baseline 파일 존재 여부를 확인한다. +2. `.stage-pilot/bootstrap/baseline.yaml`이 없거나 핵심 필드가 비어 있으면 저장소 관찰로 채울 수 있는 값과 질문이 필요한 값을 분리한다. +3. 저장소 관찰만으로 부족하면 최소 질문 세트를 사용해 사용자 선언 입력을 수집한다. +4. `stage-pilot/templates/bootstrap/baseline-seed.yaml`에 해당하는 실제 template source를 해석한 뒤 `.stage-pilot/bootstrap/baseline.yaml`을 생성 또는 보강한다. +5. 누락된 디렉터리가 있으면 `docs/discovery`, `docs/srs`, `docs/batches`, `docs/releases`를 먼저 준비한다. +6. 필요한 논리 템플릿 경로를 현재 환경의 물리 경로로 해석한 뒤, 누락된 index 파일을 각 템플릿으로 생성한다. +7. seed 파일과 저장소 상태를 반영해 `docs/project-structure.md`, `docs/runtime-flows.md`, `docs/interface-contract.md`, `docs/data-model.md`를 생성 또는 보강한다. +8. 이미 존재하는 파일은 보존하고, 필요한 경우에만 누락 복구 또는 seed 반영 사실을 결과에 기록한다. +9. 완료 후 다음 단계로 `new-discovery`를 사용해 첫 real Discovery를 시작하도록 안내한다. + +# Output Expectations + +- 생성 또는 복구한 파일 목록 +- seed 파일 상태 (`created` | `preserved` | `updated`) +- baseline 문서 상태 (`created` | `preserved` | `missing-information`) +- index 문서 상태 (`created` | `preserved`) +- 첫 real Discovery에서 다뤄야 할 주제 후보 또는 다음 action + +# Validation + +- 생성한 파일 경로가 active docs 구조와 일치하는지 확인한다. +- `.stage-pilot/bootstrap/baseline.yaml`의 필수 필드가 비어 있지 않은지 확인한다. +- baseline 문서와 index에 남아 있는 플레이스홀더가 정말 사람 결정이 필요한 값인지 점검한다. +- baseline 문서의 `Project Summary`, `Primary Domain`, `Tech Stack`, `Primary Runtime`와 공통 계약 정보가 seed 및 저장소 관찰과 모순되지 않는지 확인한다. +- Discovery 문서를 생성하지 않았는지 확인한다. \ No newline at end of file diff --git a/skills/capture-release-feedback/SKILL.md b/skills/capture-release-feedback/SKILL.md new file mode 100644 index 0000000..9be543f --- /dev/null +++ b/skills/capture-release-feedback/SKILL.md @@ -0,0 +1,45 @@ +--- +name: capture-release-feedback +description: "Use when: recording post-release observations, running /capture-release-feedback with a REL ID, appending feedback and follow-up inputs to docs/releases/rel-XXX_*.md, or marking included batches as released after deployment feedback is captured." +version: 0.6.0 +author: Justin Ko +license: private +argument-hint: "예: rel-001 또는 docs/releases/rel-001_20260424_scaffold.md" +user-invocable: true +metadata: + hermes: + tags: [stage-pilot, release, feedback, retrospective, sdlc] + related_skills: [draft-release, confirm-release, suggest-next-discovery] +--- + +# Purpose + +This skill records post-release observations, converts them into follow-up Discovery or REQ inputs, and closes the release with captured feedback. + +# Inputs + +- `REL-ID` 또는 release 경로 +- 운영 관찰 결과 +- 대상 release 문서 +- 포함 batch 목록 +- `docs/releases/index.md` +- `docs/batches/index.md` + +# Core Rules + +- 운영 관찰 결과는 사실과 후속 입력으로 분리해 기록한다. +- 후속 입력은 `Discovery Input`, `REQ Input`, `Change Request Input`으로 구분한다. +- release feedback을 남긴 뒤 release 상태는 `feedback-captured`로 전환할 수 있다. +- 포함 batch 상태는 필요하면 `released`로 갱신한다. + +# Execution Procedure + +1. release 문서와 포함 batch를 읽는다. +2. 운영 관찰 결과를 release 문서의 Feedback Handoff 섹션에 반영한다. +3. 후속 Discovery 입력, 신규 REQ 입력, 기존 REQ 변경 후보를 분리해 기록한다. +4. release index 상태를 `feedback-captured`로 갱신하고, 포함 batch 상태를 `released`로 조정한다. + +# Validation + +- release 문서에 운영 관찰 결과와 `Discovery Input`, `REQ Input`, `Change Request Input`이 모두 남았는지 확인한다. +- release index 상태와 batch index 상태가 문서 본문과 일치하는지 확인한다. \ No newline at end of file diff --git a/skills/change-req/SKILL.md b/skills/change-req/SKILL.md new file mode 100644 index 0000000..8b2036a --- /dev/null +++ b/skills/change-req/SKILL.md @@ -0,0 +1,110 @@ +--- +name: change-req +description: "Use when: updating an existing requirement after approval or implementation, running /change-req with a REQ ID or file path, recording a change-request entry before overwriting a REQ, analyzing impacted batches/releases, or deciding whether an Implemented REQ must be downgraded and reverified." +version: 0.8.1 +author: Justin Ko +license: private +argument-hint: "예: req-001 또는 docs/srs/Documentation/req-001_minor-docs.md" +user-invocable: true +metadata: + hermes: + tags: [stage-pilot, requirements, change-management, documentation, sdlc] + related_skills: [draft-req, confirm-req, draft-batch] +--- + +# Purpose + +This skill applies a controlled change to an existing REQ by recording a new change-request entry before editing the requirement body, analyzing downstream delivery impact, and updating status/index state when the prior implementation is no longer valid. + +# When to use + +- `/change-req req-001`처럼 기존 REQ의 Requirement, Acceptance Criteria, Impacted Area를 수정해야 할 때 +- 운영 피드백이나 release feedback에서 기존 REQ 수정 필요가 확인됐을 때 +- `Implemented` 또는 `Approved` REQ가 더 이상 현재 구현/검증 증거와 맞지 않는지 판단해야 할 때 +- `docs/srs/index.md`의 REQ 상태와 실제 변경 영향 범위를 다시 동기화해야 할 때 + +# Inputs + +- 선택 입력 + - `REQ-ID` prefix + - REQ 파일 경로 +- 변경 요청 근거 + - 사용자 요청 + - release feedback + - batch verification 또는 implementation 결과 +- 대상 REQ 문서 +- `docs/srs/index.md` +- 관련 `docs/batches/index.md` +- 관련 `docs/releases/index.md` +- 영향이 의심되는 batch/release 문서들 + +# Core Rules + +## 1. 입력 해석 + +- prefix 입력이면 `docs/srs/**/req-001_*.md` 형식으로 찾는다. +- 동일 prefix에 여러 문서가 매칭되면 임의 선택하지 않는다. +- REQ가 `Deprecated`면 기본적으로 새 변경 요청 대상으로 삼지 않는다. 복구가 필요하면 근거를 먼저 명시한다. + +## 2. 변경 요청 기록 규칙 + +- 기존 `Requirement`, `Acceptance Criteria`, `Impacted Area`를 덮어쓰기 전에 새 `Change Log` 항목을 추가한다. +- 새 변경 항목 ID는 `CHG-YYYYMMDD-NN` 형식을 사용하고 같은 날짜 내에서 순차 증가시킨다. +- 새 항목에는 최소한 아래를 모두 기록한다. + - `Change Summary` + - `Intent` + - `Acceptance Criteria Delta` + - `Impacted Area Delta` + - `Delivery Trace Delta` + - `Revalidation Impact` + - `Validation Plan` +- 과거 `Change Log` 항목은 삭제하거나 덮어쓰지 않는다. + +## 3. 상태 영향 규칙 + +- 변경이 기존 구현 또는 verification evidence를 무효화하지 않으면 현재 상태를 유지할 수 있다. +- `Implemented` REQ의 기존 구현/검증 근거가 깨지면 상태를 `Approved`로 되돌린다. +- 변경이 승인 전제 자체를 다시 검토하게 만들면 상태를 `Proposed`로 되돌릴 수 있다. +- 상태를 되돌릴 때는 이유를 `Revalidation Impact`와 `Change Log`에 함께 남긴다. +- 상태가 바뀌면 `docs/srs/index.md`의 Register와 `Recent Change Log Summary`도 함께 갱신한다. + +## 4. 영향 분석 규칙 + +- 관련 batch가 있으면 어떤 batch planning/design/verification이 다시 검토되어야 하는지 기록한다. +- 관련 release가 있으면 release 문서를 직접 덮어쓰지 말고 영향 범위와 후속 action을 REQ의 `Change Log`에 남긴다. +- 사람 판단이 필요한 범위 재정의, 우선순위 변경, 정책 변경은 임의 확정하지 않는다. + +# Execution Procedure + +1. 입력에서 대상 REQ 경로를 확정한다. +2. REQ 현재 상태와 최근 `Change Log`를 읽는다. +3. `docs/batches/index.md`, `docs/releases/index.md`, 관련 batch/release 문서를 읽어 현재 delivery trace를 요약한다. +4. 변경 요청 근거를 기존 구현/검증 상태와 비교한다. +5. 새 `Change Log` 항목을 먼저 추가하고 delta, trace, revalidation impact를 채운다. +6. 그 다음 REQ 본문(`Requirement`, `Acceptance Criteria`, `Impacted Area`, `Notes`)을 필요한 만큼 수정한다. +7. 상태 유지 또는 상태 되돌림 필요 여부를 결정해 REQ 본문과 `docs/srs/index.md`를 갱신한다. +8. `Recent Change Log Summary`에 최신 변경 항목을 반영한다. + +# Output Expectations + +- 대상 REQ 경로 +- 새 `Change Log` 항목 ID +- 변경 요약 +- 영향 받는 batch/release 목록 +- 재검증 필요 여부 +- 상태 유지 또는 상태 되돌림 결과 +- `docs/srs/index.md` 갱신 결과 + +# Common Pitfalls + +1. 활성 batch 안에서 계약/필드 순서 같은 외부 계약을 바꾸면서 REQ만 수정하고 delivery 산출물과 baseline 문서를 같이 재정렬하지 않는 실수 + - 예: persistence flat slot 순서, CTSP read/write 슬롯 순서, revision migration 시 기본값 채움 순서 변경. + - 이런 변경은 `change-req` 후에 source Discovery, 연결된 Approved REQ들, 현재 batch의 `planning/design/implementation/verification`, 그리고 `CTSP_PROTOCOL`/`CONFIGURATION`/`runtime-flows` 같은 기준 문서를 같은 변경 묶음으로 갱신해야 한다. + - verification evidence가 다시 필요해질 수 있으므로, 검증 전에는 batch를 `release-candidate`로 올리거나 REQ를 `Implemented`로 확정하지 않는다. + +# Validation + +- `Change Log` 새 항목이 REQ 본문 수정보다 앞선 시간순 근거로 남았는지 확인한다. +- `Acceptance Criteria Delta`, `Delivery Trace Delta`, `Revalidation Impact`가 모두 채워졌는지 확인한다. +- 상태 되돌림이 필요한 경우 REQ 문서와 `docs/srs/index.md` 상태가 함께 바뀌었는지 확인한다. +- 관련 batch/release 영향이 `Change Log`에 누락되지 않았는지 확인한다. \ No newline at end of file diff --git a/skills/confirm-batch-verification/SKILL.md b/skills/confirm-batch-verification/SKILL.md new file mode 100644 index 0000000..6906e6d --- /dev/null +++ b/skills/confirm-batch-verification/SKILL.md @@ -0,0 +1,48 @@ +--- +name: confirm-batch-verification +description: "Use when: approving a batch verification result, running /confirm-batch-verification with a BAT ID, promoting docs/batches/ to release-candidate, or updating docs/batches/index.md after verification passes." +version: 0.8.0 +author: Justin Ko +license: private +argument-hint: "예: bat-001 또는 docs/batches/bat-001_20260424_scaffold" +user-invocable: true +metadata: + hermes: + tags: [stage-pilot, batch, verification, approval, sdlc] + related_skills: [draft-batch-verification, confirm-req-implemented, draft-release] +--- + +# Purpose + +This skill checks whether a batch verification document has enough evidence to release the batch and, if so, promotes the batch to `release-candidate`. + +# Inputs + +- `BAT-ID` 또는 batch 경로 +- `docs/batches//verification.md` +- 포함된 REQ 문서들 +- `docs/batches/index.md` + +# Core Rules + +- verification에 미해결 blocker가 있으면 승인하지 않는다. +- verification에 미해결 blocker가 있으면 승인하지 않는다. +- 단, verification 문서에 명시적 `Human Approval Memo`가 있고 승인자, 승인 시각, 스킵/수용 범위, 잔여 리스크, 승인 근거가 모두 적혀 있으면 그 메모가 명시적으로 수용한 blocker는 residual risk로 보고 승인할 수 있다. 이 경우 승인 결과에는 사람이 수용한 예외 범위를 함께 요약한다. +- 포함된 REQ의 acceptance criteria가 evidence와 연결돼야 한다. +- `batch-lite`는 design 문서 없이도 승인할 수 있지만, planning의 `Design Gate`가 design 불필요를 명시하고 verification이 구조 영향 없음 또는 baseline 영향 없음을 확인해야 한다. +- 승인 성공 시 batch status는 `release-candidate`가 된다. + +# Execution Procedure + +1. batch와 verification 문서를 읽는다. +2. 필요하면 planning과 design을 함께 읽어 profile과 구조 영향 여부를 확인한다. +3. Acceptance Mapping, Evidence, Blocking Issues를 점검한다. +4. 승인 가능하면 verification 상태와 batch index 상태를 갱신한다. +5. 승인 불가면 상태는 유지하고 blocker를 보고한다. +6. verification에 `Human Approval Memo`가 있으면, 메모가 수용한 blocker와 여전히 승인 불가한 blocker를 분리해서 판단한다. 사람이 수용한 항목만 남아 있다면 verification을 `approved`로 올리고 batch를 `release-candidate`로 승격할 수 있다. + +# Validation + +- 승인 성공인 경우 batch index 상태가 `release-candidate`인지 확인한다. +- 승인 보류인 경우 blocker가 명시됐는지 확인한다. +- `batch-lite` 승인 성공인 경우 design 부재가 검증 근거와 모순되지 않는지 확인한다. \ No newline at end of file diff --git a/skills/confirm-discovery/SKILL.md b/skills/confirm-discovery/SKILL.md new file mode 100644 index 0000000..d322109 --- /dev/null +++ b/skills/confirm-discovery/SKILL.md @@ -0,0 +1,273 @@ +--- +name: confirm-discovery +description: "Use when: running /confirm-discovery with dcy-xyz or a Discovery path, re-checking the current repository state against docs/discovery/*.md, filling AI-resolvable placeholders or inconsistencies, and promoting the Discovery to confirmed when it is ready for REQ drafting in the Requirements phase." +version: 0.9.1 +author: Justin Ko +license: private +argument-hint: "예: dcy-001 또는 docs/discovery/dcy-001_20260424_krx-stock-picker-python-scaffold.md" +user-invocable: true +metadata: + hermes: + tags: [stage-pilot, discovery, approval, quality, sdlc] + related_skills: [new-discovery, review-discovery, draft-req] +--- + +# Purpose + +This skill revalidates an existing Discovery document against the current repository state, fills anything the AI can resolve from local evidence, and promotes the document to `confirmed` only when REQ drafting handoff has no remaining blockers. + +이 skill의 결과는 아래 두 경우로 나뉜다. + +- 확인 결과 승인 가능이면 대상 Discovery 문서를 `confirmed`로 전환하고 `docs/discovery/index.md`의 해당 행 상태를 함께 갱신한다. +- 승인까지는 불가하더라도 AI가 근거 있게 채울 수 있는 항목이 있으면 문서를 먼저 보강한다. 단, 이 경우 `confirmed` 전환과 REQ drafting 준비 플래그 변경은 하지 않는다. + +# When to use + +다음 상황에서 사용한다. + +- `/confirm-discovery dcy-xyz` 형태로 기존 Discovery를 최종 확인해야 할 때 +- `docs/discovery/` 아래 Discovery 초안 문서를 REQ 초안 작성 가능 상태로 승격해야 할 때 +- 현재 저장소 구현 상태를 다시 확인해 Discovery 본문의 사실 관계를 정정하거나 보강해야 할 때 +- 문서 안의 플레이스홀더 중 AI가 근거 있게 채울 수 있는 항목을 먼저 정리한 뒤 승인 가능 여부를 판정해야 할 때 +- `docs/discovery/index.md`의 상태가 실제 Discovery 상태와 일치하는지 함께 맞춰야 할 때 + +다음 상황에는 이 skill을 기본 경로로 사용하지 않는다. + +- Discovery 품질 점검, 누락 점검, OQ 후보 정리만 먼저 하고 싶을 때 + - 이 경우 `review-discovery`를 사용한다. +- 현재 문서가 사실상 새 Discovery 생성 또는 대규모 범위 재정의를 필요로 할 때 + - 이 경우 `new-discovery`로 새 주기를 시작하거나 기존 Discovery를 재작성해야 한다. +- 승인자 지정, 정책 확정, 외부 확인 결과처럼 사람 판단 또는 외부 근거가 먼저 필요한 상태일 때 + - 이 경우 confirm 없이 보류하고 필요한 결정을 먼저 해소한다. + +# Inputs + +이 skill은 아래 입력을 사용한다. + +- Discovery 식별자 + - `dcy-001` 같은 축약 ID + - `dcy-001_20260424_topic-slug` 같은 전체 Discovery ID + - `docs/discovery/.md` 같은 직접 경로 +- Discovery 문서 본문 +- `docs/discovery/index.md` +- `docs/project-structure.md` (존재하는 경우) +- `docs/runtime-flows.md` (존재하는 경우) +- 현재 저장소 구현 상태 + - 관련 소스 파일 + - 설정 파일 + - 관련 문서 +- 현재 시각 (KST) + +# Core Rules + +## 1. 입력 해석 규칙 + +- 입력이 `dcy-001`처럼 prefix만 주어지면 `docs/discovery/` 아래에서 해당 prefix로 시작하는 문서를 찾는다. +- 입력이 전체 Discovery ID면 `docs/discovery/.md`를 직접 찾는다. +- 입력이 파일 경로면 해당 경로를 그대로 사용한다. +- 매칭 결과가 0개면 자동 진행하지 않고 사용자에게 입력을 재확인하도록 알린다. +- 같은 prefix에 대해 여러 문서가 매칭되면 임의 선택하지 않고 후보 목록을 보여 준 뒤 사용자 확인을 요청한다. +- `.stage-pilot/` 경로는 탐색 대상에서 제외한다. + +## 2. 현재 구현 상태 재확인 규칙 + +- 현재 구현 상태 확인은 요구사항이 이미 모두 구현되었는지 검사하는 절차가 아니다. +- 이 확인의 목적은 Discovery의 `현재 상태`, `영향 범위`, `성공 기준`, `체크 항목`, `사용자 결정 필요 항목`이 저장소 현실과 맞는지 검증하는 것이다. +- `docs/project-structure.md`, `docs/runtime-flows.md`가 존재하면 Discovery의 구조 설명, 영향 범위, runtime flow 설명이 baseline과 모순되지 않는지도 함께 확인한다. +- 구현 부재 자체는 차단 사유가 아니다. Discovery가 애초에 신규 작업을 정의하는 문서라면 코드가 없는 상태도 정상일 수 있다. +- 차단이 되는 경우는 아래와 같다. + - 문서가 이미 존재한다고 적은 구현이 실제 저장소에는 없어서 본문 사실 관계가 틀린 경우 + - 저장소 현실이 문서의 핵심 범위, 사용자 시나리오, 성공 기준을 다시 써야 할 정도로 바뀐 경우 + - 문서의 남은 결정 항목이 여전히 사람 판단을 필요로 하는데 승인 상태로 올리려는 경우 + +## 3. 승인 전 보강 규칙 + +승인 판정 전에 먼저 아래 보강 절차를 수행한다. + +### 3-1. 일관성 검토 + +- 용어 혼용, 범위 충돌, 중복 서술, 번호 누락을 확인한다. +- `# 5. 요구사항 목록`, `# 6. 리스크/가정 목록`, `# 7. 초기 성공 기준`, `# 8. REQ로 넘기기 전 확인 체크`, `# 10. 사용자 결정 필요 항목 요약`, `# 11. Discovery Freeze`의 연결 관계를 확인한다. +- 문장만 다르고 같은 의미를 반복하는 부분은 간결하게 정리한다. +- 문법, 오타, 링크 형식, 체크리스트 표현을 함께 정리한다. + +### 3-2. 증거 기반 보강 + +- Discovery 본문에서 이슈명, 핵심 변경 항목, FR/NFR, 범위 경계를 읽고 키워드를 추출한다. +- 키워드를 바탕으로 저장소에서 관련 파일, 설정, 문서를 탐색한다. +- 탐색 결과는 최소한 아래 판단으로 정리한다. + - 이미 존재함 + - 일부 존재함 + - 아직 없음 + - 문서와 모순됨 +- AI가 아래 근거로 확정 가능한 항목은 실제 문장으로 치환한다. + - 같은 Discovery 문서 안 다른 섹션에 이미 답이 있는 항목 + - 현재 저장소 파일 구조, 설정, 문서에서 직접 확인 가능한 항목 + - 승인 행위 시각처럼 현재 실행 시점에 자동 확정 가능한 항목 +- 아래 항목은 추론만으로 확정하지 않는다. + - 정책 선택 + - 우선순위 승인 + - 외부 시스템 또는 외부 데이터 확인 결과 + - 승인자 지명처럼 사람 책임을 수반하는 결정 + - `Confirmed By`처럼 승인 책임 주체를 명시하는 값 + +### 3-3. 플레이스홀더 분류 기준 + +- A. 승인 시점 자동 채움 + - 예: `{{DATA_CONFIRMED_AT_KST:...}}`, `{{CONFIRMED_AT_KST:...}}` + - 승인 성공 시 현재 시각으로 채운다. +- B. 증거 기반 해소 가능 + - 문서 다른 섹션, 저장소 구조, 이미 확정된 값으로 해소 가능한 플레이스홀더다. + - 승인 성공 여부와 무관하게 먼저 채운다. +- C. 사람 결정 또는 외부 확인 필요 + - `{{DECIDE_*}}`, `{{CONFIRM_*}}`, `{{DATA_*}}` 중 아직 문서와 저장소 근거만으로 해소되지 않는 항목이다. + - 승인 차단 대상이다. + +### 3-4. 책임자 지정과 저장소 범위 DATA 처리 기준 + +- `REQ/검토 책임자 지정`, `구현/검토 책임자 지정`, `해소 책임자 지정`처럼 사람 책임 배정이 필요한 항목은 AI가 자동으로 채우지 않는다. +- 이런 항목이 Discovery의 범위/정책/성공 기준 자체를 결정하는 질문이 아니라 이후 REQ 또는 batch planning 단계의 담당자 할당 문제라면, `# 10. 사용자 결정 필요 항목 요약`에 남겨 Discovery confirm을 가로막는 질문으로 유지하지 말고 `# 8. REQ로 넘기기 전 확인 체크` 또는 `# 11. Discovery Freeze`의 보류 사유로 정리한다. +- 다만 사람이 이미 대화에서 승인자/책임자/범위 결정을 명시해 주었고, 그 값이 문서에 직접 반영 가능한 형태라면 AI는 그 사람 결정을 문서에 옮길 수 있다. 이 경우에도 AI가 새 값을 추정하면 안 되고, 사용자가 제공한 사람 이름/결정만 그대로 기록해야 한다. +- 저장소 내부에서 확인 가능한 DATA 항목은 repo-wide consumer 탐색으로 먼저 해소를 시도한다. 예를 들어 특정 명령의 응답 길이에 의존하는 호스트 도구/스크립트 목록이 필요한 경우, 관련 토큰(`VISIONCFG`, `visioncfg` 등)으로 코드/문서/QA 스크립트 전체를 검색해 in-repo 소비처를 먼저 열거한다. +- 측정 경로와 검증 방법은 저장소 근거로 이미 확정됐지만 실제 수치가 구현/운영 단계에서만 수집 가능한 경우, 그 DATA 항목이 REQ 초안 작성 자체를 막는지 분리해서 판단한다. 대표 검증셋, 범위, 성공 기준, 사람 승인자가 확정돼 있으면 runtime baseline 수치 같은 운영 데이터는 REQ 단계 계측 항목으로 넘기고 confirm을 진행할 수 있다. +- 이 과정에서 저장소 안에서 길이 의존 구현이 확인되지 않으면 `현 저장소 기준 확인된 소비처는 없음`처럼 범위를 한정해 정리할 수 있다. 단, 저장소 밖 배포 도구나 외부 시스템까지 없다고 확대 해석하지 않는다. + +- 승인 판정 직전에는 A와 B만 남아 있어야 한다. +- B로 분류한 항목을 채운 뒤에도 `# 1`부터 `# 10` 사이에 C 유형 플레이스홀더가 남아 있으면 승인하지 않는다. +- 단, `DATA_*` 성격의 항목이 "REQ 또는 delivery 단계에서 계측·확정할 운영 데이터"임이 문서 안에 명시되고, Discovery 본문만으로도 FR/NFR, 범위, 성공 기준, 검증 시나리오를 REQ 초안으로 내리기에 충분하다면 confirm 차단 사유로 유지하지 않는다. 이런 경우에는 `# 8. REQ로 넘기기 전 확인 체크`와 `# 10. 사용자 결정 필요 항목 요약`에 "REQ 단계 계측/확정" 또는 동등한 표현으로 넘기고, Discovery Freeze rationale에도 비차단 운영 데이터라는 점을 적는다. + +## 4. 최종 승인 게이트 + +아래 조건을 모두 만족해야 `confirmed`로 전환한다. + +- 문서 상태가 아직 `confirmed`가 아니어야 한다. +- B 유형 보강 이후 C 유형 플레이스홀더가 0개여야 한다. +- `# 6. 리스크/가정 목록`의 오픈 질문에 `상태: Open` 항목이 없어야 한다. +- `# 8. REQ로 넘기기 전 확인 체크`에 남아 있는 미완료 항목이 있다면, 그 항목이 REQ 초안 작성을 막지 않는다고 문서에 명시되어 있어야 한다. 그렇지 않으면 차단한다. +- `# 10. 사용자 결정 필요 항목 요약`에 남은 항목이 모두 `없음`이거나, 문서 안에서 이미 답이 반영되어 제거 가능해야 한다. +- `# 11. Discovery Freeze`에 `Confirmed By`가 비어 있지 않아야 한다. +- 현재 저장소 상태가 Discovery의 핵심 범위나 요구사항을 다시 정의해야 할 정도로 모순되지 않아야 한다. +- runtime budget baseline, 허용 상한, 운영 계측값처럼 구현 전에는 원천적으로 확정되지 않는 수치가 남아 있더라도, 그 값이 REQ의 acceptance/verification에서 수집할 evidence로 이미 위치가 정리돼 있으면 confirm 차단 사유로 과도하게 남기지 않는다. + +`Confirmed By` 관련 규칙: + +- `Confirmed By`는 사람 승인자 또는 사람 책임 주체를 의미한다. +- AI는 `Confirmed By`를 새로 생성하거나 추정해서 채우지 않는다. +- `Confirmed By`가 비어 있으면 승인 차단 사유로 유지한다. + +다음 경우는 `confirmed` 전환 대신 보류한다. + +- Discovery 초안이 아니라 사실상 새 Discovery 생성 또는 대규모 범위 재정의가 필요한 경우 +- 확인 과정에서 FR/NFR, In Scope/Out of Scope, 성공 기준을 실질적으로 다시 써야 하는 경우 +- 승인자 또는 필수 결정 사항이 여전히 문서 밖 사람 판단에 의존하는 경우 + +## 5. 허용되는 수정 범위 + +이 skill은 아래 수정만 수행한다. + +- AI가 근거 있게 채울 수 있는 플레이스홀더 치환 +- 현재 저장소 상태에 맞춘 사실 관계 보정 +- 표현, 번호, 체크리스트, 링크의 일관성 정리 +- 실제 문서 변경이 있었다면 `마지막 갱신 시각(KST)` 갱신 +- 승인 가능 시 아래 상태 전환 수행 + - `# 0. 문서 상태`의 `상태`를 `confirmed`로 변경 + - `# 11. Discovery Freeze`의 `Handoff Decision`을 `승인`으로 변경 + - `# 11. Discovery Freeze`의 `Ready for REQ Drafting`을 `true`로 변경 + - `Confirmed At (KST)`를 현재 시각으로 채움 + - `docs/discovery/index.md`의 해당 Discovery 행 상태를 `confirmed`로 갱신 + +단, 아래 항목은 자동으로 채우지 않는다. + +- `Confirmed By` + - 사람 승인자 또는 사람 책임 주체가 이미 문서에 명시되어 있어야 한다. + - 비어 있으면 승인 전환 없이 보류한다. + +아래 상황이면 상태 전환 없이 보강만 하고 종료한다. + +- C 유형 플레이스홀더가 남아 있는 경우 +- 오픈 질문 또는 사용자 결정 항목이 남아 있는 경우 +- 승인 자체보다 먼저 Discovery 내용 재정비가 필요한 경우 + +# Repository Exploration + +현재 구현 상태를 다시 확인할 때는 아래 순서로 저장소를 탐색한다. + +1. Discovery 문서에서 이슈명, 핵심 변경 항목, FR/NFR, 범위 경계 키워드를 뽑는다. +2. 키워드 기준으로 관련 소스, 설정, 문서를 찾는다. + - `docs/project-structure.md`, `docs/runtime-flows.md`가 있으면 baseline 참조 문서로 함께 읽는다. +3. 아래 항목은 기본적으로 제외한다. + - 테스트 파일 + - 의존성 잠금 파일 + - `.stage-pilot/` 하위 파일 +4. 탐색 결과를 아래 형태로 정리한다. + - 관련 파일 경로 목록 + - 이미 구현된 내용 + - 일부만 구현된 내용 + - 아직 없는 내용 + - 문서와 모순되는 내용 + - baseline 문서와 일치하는 내용 / 충돌하는 내용 +5. 탐색 결과는 Discovery 보강과 승인 게이트 판단에만 사용한다. + +# Execution Procedure + +다음 순서대로 작업한다. + +1. 입력에서 Discovery 문서 경로를 확정한다. +2. 대상 Discovery 문서와 `docs/discovery/index.md`를 읽는다. +3. Discovery의 핵심 범위, FR/NFR, 오픈 질문, 체크리스트, 사용자 결정 필요 항목을 추출한다. +4. 위 `Repository Exploration` 규칙에 따라 현재 저장소 상태를 확인한다. +5. 문서 전체를 검토해 일관성 오류와 플레이스홀더를 나열한다. +6. B 유형 플레이스홀더와 명백한 사실 관계 오류를 먼저 수정한다. +7. 문서 내용이 바뀌었다면 `마지막 갱신 시각(KST)`를 현재 시각으로 갱신한다. +8. 다시 한 번 `최종 승인 게이트`를 평가한다. +9. 게이트를 통과하면 A 유형 플레이스홀더를 채우고 문서를 `confirmed`로 전환한다. +10. 게이트를 통과하면 `docs/discovery/index.md`의 해당 행 상태도 `confirmed`로 갱신한다. +11. `docs/discovery/index.md`에 해당 Discovery 행이 없으면 현재 문서 메타데이터를 기준으로 기존 register/header 형식과 열 순서를 유지한 채 행을 추가한다. + - 최소한 Discovery ID, 제목/이슈명, 상태, 링크, 마지막 갱신 시각에 해당하는 값을 채운다. + - 표가 아닌 다른 register 형식이면 그 형식을 그대로 따른다. +12. 게이트를 통과하지 못하면 상태는 유지하고, 보강된 내용과 남은 차단 항목만 보고한다. + +# Output Expectations + +작업이 끝나면 아래 내용을 보고할 수 있어야 한다. + +- 대상 `DISCOVERY_ID`와 문서 경로 +- 현재 저장소 상태 재확인 결과 요약 +- AI가 자동으로 채운 항목 목록 +- 남겨 둔 항목 목록과 남긴 이유 +- 최종 판정 + - `confirmed 전환 완료` + - `보강만 수행, 승인 보류` + - `문서 변경 없이 승인 보류` +- `docs/discovery/index.md` 갱신 결과 +- REQ 초안 작성으로 넘기기 전에 남은 작업 요약 + +# Common Pitfalls + +1. 구현이 아직 없다는 이유만으로 승인 차단하기 + - 신규 작업을 정의하는 Discovery라면 구현 부재는 정상일 수 있다. 차단 사유는 문서와 저장소 사실 관계 모순 또는 사람 결정 미해소여야 한다. + +2. `Confirmed By`를 AI가 추정해서 채우기 + - `Confirmed By`는 사람 승인자 또는 사람 책임 주체를 뜻한다. AI가 새로 만들거나 추정하면 안 된다. + +3. 사실상 새 Discovery가 필요한 문서를 confirm으로 밀어 올리기 + - 확인 과정에서 FR/NFR, 범위 경계, 성공 기준을 다시 써야 한다면 confirm보다 재작성 또는 새 Discovery 판단이 우선이다. + +4. `review-discovery`에서 할 품질 점검과 `confirm-discovery`의 승인 게이트를 혼동하기 + - 단순 누락 점검과 구조 품질 확인이 목적이면 먼저 `review-discovery`를 사용한다. + +5. Discovery 본문만 바꾸고 index 또는 freeze 필드를 함께 확인하지 않기 + - 승인 완료 시 본문 상태, Freeze 필드, `docs/discovery/index.md` 상태가 함께 일치해야 한다. + +# Verification Checklist + +완료 전에 아래를 확인한다. + +- [ ] 대상 경로가 `docs/discovery/.md` 형식을 만족하는지 +- [ ] 대상 Discovery 문서가 실제로 존재하는지 +- [ ] `docs/project-structure.md`, `docs/runtime-flows.md`가 있다면 필요한 baseline cross-check가 반영되었는지 +- [ ] 보강 후에도 C 유형 플레이스홀더와 `상태: Open` 오픈 질문이 정확히 식별되었는지 +- [ ] 승인 완료인 경우 `Confirmed By`가 사람에 의해 채워져 있고, 문서 상태와 `docs/discovery/index.md` 상태가 모두 `confirmed`인지 +- [ ] 승인 보류인 경우 `confirmed`, `승인`, `true` 같은 승인 전환 값이 잘못 들어가지 않았는지 +- [ ] 문서 본문 보강이 새로운 요구사항 생성이나 범위 재정의로 번지지 않았는지 +- [ ] 결과 보고에 자동 처리 항목과 차단 항목이 모두 포함되는지 \ No newline at end of file diff --git a/skills/confirm-release/SKILL.md b/skills/confirm-release/SKILL.md new file mode 100644 index 0000000..c062437 --- /dev/null +++ b/skills/confirm-release/SKILL.md @@ -0,0 +1,47 @@ +--- +name: confirm-release +description: "Use when: approving a release plan, running /confirm-release with a REL ID or release path, promoting docs/releases/rel-XXX_*.md from draft to confirmed, or validating rollout and rollback readiness before deployment." +version: 0.8.0 +author: Justin Ko +license: private +argument-hint: "예: rel-001 또는 docs/releases/rel-001_20260424_scaffold.md" +user-invocable: true +metadata: + hermes: + tags: [stage-pilot, release, approval, quality, sdlc] + related_skills: [draft-release, capture-release-feedback, suggest-next-discovery] +--- + +# Purpose + +This skill validates a release document and promotes it to `confirmed` only when rollout, rollback, and verification checks are ready. + +# Inputs + +- `REL-ID` 또는 release 경로 +- 대상 release 문서 +- `docs/releases/index.md` +- 포함 batch 문서들 + +# Core Rules + +- Included Batch가 모두 release-candidate여야 한다. +- Rollout Plan, Rollback Plan, Verification Checklist가 비어 있으면 승인하지 않는다. +- release profile에 맞는 검증 항목이 비어 있으면 승인하지 않는다. +- `docs-only`는 링크/렌더링/패키징 검증으로 충분하며 health endpoint를 요구하지 않는다. +- `tooling`은 설치/업데이트/CLI 또는 스크립트 smoke 결과가 있어야 한다. +- `app-service`는 health, 로그, smoke 또는 동등한 운영 확인 근거가 있어야 한다. +- 승인 성공 시 release 상태를 `confirmed`로 바꾸고 index를 갱신한다. + +# Execution Procedure + +1. release 문서와 포함 batch 상태를 확인한다. +2. rollout, rollback, verification 준비도와 release profile의 필수 검증 항목을 점검한다. +3. 게이트 통과 시 release 상태와 index를 `confirmed`로 갱신한다. +4. 미통과 시 상태는 유지하고 blocker를 보고한다. + +# Validation + +- 승인 성공인 경우 release 문서와 index 상태가 모두 `confirmed`인지 확인한다. +- 승인 보류인 경우 누락 항목이 명시됐는지 확인한다. +- profile별 검증 항목이 release 문서의 선택된 profile과 모순되지 않는지 확인한다. \ No newline at end of file diff --git a/skills/confirm-req-implemented/SKILL.md b/skills/confirm-req-implemented/SKILL.md new file mode 100644 index 0000000..93ad197 --- /dev/null +++ b/skills/confirm-req-implemented/SKILL.md @@ -0,0 +1,107 @@ +--- +name: confirm-req-implemented +description: "Use when: confirming that approved requirements have implementation and verification evidence, running /confirm-req-implemented with a REQ ID, BAT ID, or file path, promoting docs/srs//req-XXX_.md from Approved to Implemented, or updating docs/srs/index.md after batch verification passes." +version: 0.7.0 +author: Justin Ko +license: private +argument-hint: "예: req-001 또는 bat-001 또는 docs/srs/Interface/req-001_picker-cli-run.md" +user-invocable: true +metadata: + hermes: + tags: [stage-pilot, requirements, implementation, validation, sdlc] + related_skills: [draft-batch-verification, confirm-batch-verification, draft-release] +--- + +# Purpose + +This skill validates that an approved requirement has implementation and verification evidence, then promotes it to `Implemented` only when every acceptance criterion is traceable to delivery evidence. + +`Implemented`는 배포 완료 자체를 뜻하지 않는다. 기본 기준은 구현과 검증 증거가 확보된 상태이며, 일반적으로 `release-candidate` 또는 `released` batch의 verification 결과를 근거로 삼는다. + +# When to use + +- `/confirm-req-implemented req-001`처럼 특정 REQ를 `Implemented`로 전환해야 할 때 +- `/confirm-req-implemented bat-001`처럼 batch verification 이후 포함 REQ 상태를 동기화해야 할 때 +- `/confirm-req-implemented`처럼 입력 없이 현재 저장소의 `Approved` REQ 중 구현 완료로 승격 가능한 대상을 일괄 점검해야 할 때 +- `docs/srs/index.md`의 REQ 상태가 실제 batch verification evidence와 어긋나 있을 때 + +# Inputs + +- 선택 입력 + - `REQ-ID` prefix + - REQ 파일 경로 + - `BAT-ID` prefix + - batch 경로 +- 입력이 없으면 `docs/srs/**/req-*.md` 전체에서 `Status: Approved` REQ를 후보로 찾는다. +- 대상 REQ 문서 또는 대상 batch 문서 +- `docs/srs/index.md` +- `docs/batches/index.md` +- 관련 `docs/batches//index.md` +- 관련 `docs/batches//verification.md` +- 필요하면 관련 `docs/batches//implementation.md` + +# Core Rules + +## 1. 입력 해석 + +- `req-001` 같은 prefix가 주어지면 `docs/srs/**/req-001_*.md` 형식으로 찾는다. +- `bat-001` 같은 prefix가 주어지면 `docs/batches/` 아래 matching batch를 찾고, 포함 REQ 중 `Approved` 상태만 후보로 삼는다. +- 입력이 없으면 `docs/srs/` 아래에서 `req-template.md`를 제외한 모든 `req-*.md`를 읽고, 그중 `Status: Approved` 문서를 후보로 삼는다. +- 같은 prefix에 여러 문서가 매칭되면 임의 선택하지 않는다. +- 입력이 없는 일괄 모드에서는 여러 후보가 정상이며, 각 후보를 개별 게이트로 평가한다. + +## 2. 구현 완료 전환 게이트 + +- 아래 조건을 모두 만족해야 `Implemented`로 전환한다. + - 대상 REQ의 현재 `Status`가 `Approved`다. + - 연결된 batch가 `release-candidate` 또는 `released` 상태다. + - batch `verification.md`에 해당 REQ의 `Acceptance Mapping`이 존재한다. + - REQ의 모든 `Acceptance Criteria`가 verification evidence와 연결된다. + - batch verification의 `Blocking Issues`가 이 REQ를 미통과 상태로 남기지 않는다. +- release 배포 완료는 필수 조건이 아니다. 구현과 검증 evidence가 충분하면 `release-candidate` 상태에서도 `Implemented` 전환이 가능하다. +- 연결된 batch를 찾지 못하거나 evidence가 부족하면 상태를 바꾸지 않는다. +- 하나의 REQ가 여러 batch에 걸쳐 있고 어떤 batch를 근거로 삼아야 할지 명확하지 않으면 임의로 전환하지 않는다. + +## 3. 허용되는 수정 + +- AI가 근거 있게 정리할 수 있는 오타, 링크, 표현 불일치는 보강할 수 있다. +- 전환 성공 시 아래 변경을 수행한다. + - REQ 문서의 `Status`를 `Implemented`로 변경 + - `Change Log`에 구현 완료 전환 기록 추가 + - `docs/srs/index.md`의 해당 Register 행 상태를 `Implemented`로 갱신 + - 필요하면 `Recent Change Log Summary`를 갱신 +- evidence 자체를 새로 만들어 내거나, batch verification의 blocker를 임의 해소하지 않는다. +- 사람 판단이 필요한 범위 변경, acceptance criteria 재정의, batch 재매핑은 임의로 확정하지 않는다. + +# Execution Procedure + +1. 입력에서 대상 REQ 하나, REQ 목록, 또는 batch 경로를 확정한다. +2. batch 입력인 경우 batch `index.md`에서 Included REQ를 읽고 `Approved` REQ만 후보로 만든다. +3. 입력이 없는 경우 `docs/srs/**/req-*.md`를 스캔해 `Status: Approved` 문서를 후보 목록으로 만든다. +4. 각 후보 REQ에 대해 `docs/batches/index.md`와 batch 문서를 읽어 연결된 batch를 찾는다. +5. 연결된 batch의 `verification.md`에서 REQ acceptance mapping, evidence, blocking issue를 점검한다. +6. 게이트를 통과한 REQ만 `Implemented`로 전환하고 `Change Log`, `docs/srs/index.md`를 갱신한다. +7. 게이트를 통과하지 못한 REQ는 상태를 유지하고 남은 blocker를 수집한다. +8. 입력이 batch 또는 무인자 일괄 모드인 경우, 성공 목록과 보류 목록을 분리해 보고한다. + +# Output Expectations + +- 단일 입력 모드 + - 대상 REQ 또는 batch 경로 + - 구현 완료 전환 성공 여부 + - 사용한 근거 batch와 verification evidence 요약 + - 자동 보강한 항목 목록 + - 남은 blocker 또는 미해결 항목 + - `docs/srs/index.md` 갱신 결과 +- batch/무인자 일괄 모드 + - 검토한 REQ 전체 목록 + - `Implemented` 전환 성공한 REQ 목록 + - 전환 보류한 REQ 목록과 각 blocker + - `docs/srs/index.md` 갱신 결과 + +# Validation + +- 전환 성공인 경우 REQ 문서와 `docs/srs/index.md` 상태가 모두 `Implemented`인지 확인한다. +- 전환 보류인 경우 상태가 잘못 바뀌지 않았는지 확인한다. +- 사용한 verification evidence가 실제 REQ acceptance criteria와 연결되는지 확인한다. +- batch 입력 모드에서는 batch에 포함된 `Approved` REQ만 처리되었는지 확인한다. \ No newline at end of file diff --git a/skills/confirm-req/SKILL.md b/skills/confirm-req/SKILL.md new file mode 100644 index 0000000..38b0486 --- /dev/null +++ b/skills/confirm-req/SKILL.md @@ -0,0 +1,154 @@ +--- +name: confirm-req +description: "Use when: reviewing a proposed REQ for approval, running /confirm-req with a REQ ID or file path, running /confirm-req without arguments to review all unapproved REQs, promoting docs/srs//req-XXX_.md from Proposed to Approved, updating docs/srs/index.md after approval, or when the user asks in Korean to '요구 사항 명세를 검토하고 승인해줘', '요구사항 명세를 검토하고 승인해줘', 'REQ를 검토하고 승인해줘', or '승인되지 않은 전체 req를 검토하고 승인해줘'." +version: 0.9.1 +author: Justin Ko +license: private +argument-hint: "예: req-001 또는 docs/srs/Interface/req-001_picker-cli-run.md 또는 인자 없이 /confirm-req" +user-invocable: true +metadata: + hermes: + tags: [stage-pilot, requirements, approval, quality, sdlc] + related_skills: [draft-req, change-req, draft-batch] +--- + +# Purpose + +This skill validates a proposed requirement document and promotes it to `Approved` only when it is ready to be used as delivery input. + +# When to use + +- `/confirm-req req-001`처럼 REQ 승인 게이트를 통과시켜야 할 때 +- `/confirm-req`처럼 입력 없이 승인되지 않은 전체 REQ를 일괄 검토하고 승인 가능한 항목만 승격해야 할 때 +- REQ 문서의 필수 필드와 Acceptance Criteria 충족 여부를 다시 확인해야 할 때 +- `docs/srs/index.md` 상태를 REQ 문서와 맞춰야 할 때 + +## Do not use when + +- REQ가 아직 실질적으로 초안 수준이라 먼저 새로 정리하거나 다시 써야 할 때 (`draft-req`가 더 적절함) +- 기존 `Approved` 또는 `Implemented` REQ의 Requirement, Acceptance Criteria, 범위를 변경해야 할 때 (`change-req`가 더 적절함) +- 승인된 REQ를 구현/검증 evidence 기준으로 `Implemented`로 전환하려는 단계일 때 (`confirm-req-implemented`가 더 적절함) +- 여러 `Approved` REQ를 delivery batch로 묶고 planning/design 단계로 넘기려는 시점일 때 (`suggest-batch-reqs`, `draft-batch`가 더 적절함) + +# Inputs + +- 선택 입력: REQ 식별자 + - `req-001` 같은 prefix + - 전체 REQ 파일 경로 +- 입력이 없으면 `docs/srs/**/req-*.md` 전체를 스캔해 승인되지 않은 REQ를 대상으로 삼는다. +- 대상 REQ 문서 또는 대상 REQ 문서 목록 +- `docs/srs/index.md` + +# Core Rules + +## 0. 승인 전제 + +- 대상 REQ의 현재 `Status`는 기본적으로 `Proposed`여야 한다. +- REQ는 delivery input으로 넘길 수 있을 만큼 구체적이어야 하며, 단순 질문 목록이나 TODO 모음 수준이면 승인하지 않는다. +- source Discovery 또는 상위 변경 맥락과 명백히 모순되는 REQ는 승인하지 않는다. +- `Notes`에 남은 항목은 승인 blocker인지, 승인 후 후속 작업으로 남길 수 있는 메모인지 구분해서 판단한다. +- `Priority`, `Owner`, 핵심 범위, 승인 판단에 영향을 주는 open question이 비어 있거나 사람 결정을 필요로 하면 승인하지 않는다. +- 다만 승인 이후 batch planning에서 더 구체화 가능한 비핵심 운영 메모는 `Notes`에 남아 있어도 승인 blocker가 아닐 수 있다. + +## 1. 입력 해석 + +- prefix 입력이면 `docs/srs/**/req-001_*.md` 형식으로 찾는다. +- 입력이 없으면 `docs/srs/` 아래에서 `req-template.md`를 제외한 모든 `req-*.md`를 찾고, 그중 `Status: Proposed`인 문서를 승인 후보로 삼는다. +- 후보가 0개면 진행하지 않는다. +- 후보가 여러 개면 임의 선택하지 않는다. +- 입력이 없는 일괄 모드에서는 여러 후보가 정상이며, 각 후보를 개별 승인 게이트로 평가한다. + +## 2. 승인 게이트 + +- 아래 항목이 모두 있어야 승인 가능하다. + - `Status`, `Type`, `Priority`, `Owner` + - `Intent` + - `Requirement` + - 구체적인 `Acceptance Criteria` + - `Impacted Area` + - `Change Log` +- 미해결 placeholder, 사람 결정 필요 메모, blocker가 남아 있으면 승인하지 않는다. +- `Status`가 이미 `Approved`, `Implemented`, `Deprecated`이면 중복 승인하지 않는다. +- 일괄 모드에서는 승인 불가한 문서가 있더라도 전체 실행을 중단하지 않는다. 승인 가능한 문서만 승격하고, 나머지는 blocker와 함께 별도 보고한다. + +## 3. 허용되는 수정 + +- AI가 근거 있게 바로 고칠 수 있는 표현, 오타, 누락 제목 정리는 보강할 수 있다. +- 승인 성공 시 아래 변경을 수행한다. + - REQ 문서의 `Status`를 `Approved`로 변경 + - `Change Log`에 승인 기록을 새 항목으로 추가 + - `docs/srs/index.md`의 해당 Register 행 상태 갱신 + - 필요하면 `Recent Change Log Summary` 갱신 +- `Change Log` 승인 기록은 기존 항목을 덮어쓰지 않고 append한다. +- 승인 보류인 REQ에는 승인 기록을 남기지 않는다. +- `docs/srs/index.md`가 이미 존재하면, template wording으로 통째로 덮어쓰지 말고 현재 저장소의 로컬 형식을 source of truth로 취급한다. + - 기존 섹션 구조, 설명 문구, 표/리스트 형식, 열 순서는 가능한 한 유지한다. + - 필요한 상태값과 요약 항목만 in-place로 갱신한다. +- 사람 판단이 필요한 우선순위, 범위, Owner는 임의로 확정하지 않는다. +- 일괄 모드에서도 허용되는 수정 범위는 동일하며, 승인 불가 문서의 상태는 바꾸지 않는다. + +# Execution Procedure + +1. 입력에서 대상 REQ 경로 하나 또는 대상 REQ 문서 목록을 확정한다. +2. 입력이 없는 경우 `docs/srs/**/req-*.md`를 스캔해 `Status: Proposed` 문서를 승인 후보 목록으로 만든다. +3. 대상 REQ들과 `docs/srs/index.md`를 읽는다. +4. 각 REQ마다 필수 필드, Acceptance Criteria, Notes의 blocker 여부를 확인한다. +5. AI가 근거 있게 정리할 수 있는 사소한 일관성 문제를 먼저 고친다. + - 승인 기록을 append 하거나 문서 말미를 patch할 때는 대상 REQ 파일을 전체 기준으로 다시 읽었는지 확인한다. offset/limit 기반 부분 읽기나 도구 축약 출력만 본 상태에서 Change Log를 덧붙이면 `Change Summary` 같은 제목/본문이 잘린 채 저장될 수 있다. + - 승인 패치 직후에는 최소한 (a) 대상 REQ의 새 Change Log tail, (b) `Status: Approved`, (c) `docs/srs/index.md`의 해당 Register 행을 다시 읽어 실제 저장 결과를 검증한다. +6. source Discovery가 명시돼 있거나 `생성된 REQ 참조`로 추적 가능한 경우, 승인 전에 Discovery의 범위/결정/오픈 질문과 REQ가 모순되지 않는지 확인한다. + - 특히 Discovery의 `사용자 결정 필요 항목 요약`, `REQ로 넘기기 전 확인 체크`, `범위 경계`, `리스크/가정 목록`에서 승인 blocker가 남아 있지 않은지 본다. + - Discovery에 `Open` 상태 질문, 미확정 DECIDE/CONFIRM/DATA 항목, handoff 미승인 상태가 남아 있으면 승인 보류를 우선 검토한다. +7. 각 REQ에 대해 승인 게이트를 다시 평가한다. +8. 게이트 통과한 REQ만 `Approved`로 전환하고 인덱스를 갱신한다. +9. 게이트 미통과한 REQ는 상태를 유지하고 남은 blocker를 수집한다. +10. 입력이 없는 경우 승인 성공 목록과 승인 불가 목록을 분리해 함께 보고한다. + +# Output Expectations + +- 단일 입력 모드: + - 대상 REQ 경로 + - 승인 성공 여부 + - 자동 보강한 항목 목록 + - 남은 blocker 또는 미해결 항목 + - `docs/srs/index.md` 갱신 결과 +- 무인자 일괄 모드: + - 검토한 REQ 전체 목록 + - 승인 성공한 REQ 목록 + - 승인 불가한 REQ 목록과 각 blocker + - 자동 보강한 항목 목록 + - `docs/srs/index.md` 갱신 결과 + +# Common Pitfalls + +1. 필수 섹션은 있지만 내용이 너무 비어 있는 REQ를 승인하는 실수 + - 필드 존재 여부만 보지 말고, 실제로 구현/검증 판단에 쓸 수 있을 정도로 구체적인지 확인한다. + +2. `Notes`의 open question이나 사람 결정 필요 항목을 blocker로 처리하지 않고 승인하는 실수 + - Priority, Owner, 핵심 범위, 승인 판단에 영향을 주는 질문은 해소 전까지 승인하지 않는다. + +3. 이미 `Approved`, `Implemented`, `Deprecated`인 REQ를 다시 confirm 대상으로 다루는 실수 + - 이 경우 중복 승인 대신 현재 상태 유지 또는 `change-req`, `confirm-req-implemented` 경로를 검토한다. + +4. `docs/srs/index.md` 상태만 바꾸고 REQ 본문 상태를 같이 안 바꾸는 실수 + - 승인 성공 시 REQ 본문과 index register 상태를 함께 `Approved`로 맞춰야 한다. + +5. 일괄 모드에서 일부 blocker가 있다고 전체를 중단하는 실수 + - 일괄 모드에서는 각 REQ를 독립 게이트로 평가하고, 승인 가능한 문서만 승격한다. + +6. 승인 단계에서 사람이 정해야 할 Priority, Owner, 범위를 AI가 임의로 확정하는 실수 + - 사람이 결정해야 하는 항목은 자동으로 메우지 말고 blocker로 남긴다. + +# Verification Checklist + +- [ ] 대상 REQ 경로가 유일하게 확정되었거나, 일괄 모드 후보 집합이 올바르게 수집되었다. +- [ ] 대상 REQ의 현재 상태가 `Proposed`인지 확인했다. +- [ ] 필수 필드(`Status`, `Type`, `Priority`, `Owner`, `Intent`, `Requirement`, `Acceptance Criteria`, `Impacted Area`, `Change Log`)가 모두 존재한다. +- [ ] `Acceptance Criteria`가 구현/검증에 사용할 수 있을 만큼 구체적이다. +- [ ] 승인 blocker인 placeholder, open question, 사람 결정 필요 항목이 남아 있지 않다. +- [ ] source Discovery가 있으면 handoff 상태와 open question 정리가 승인 전제와 모순되지 않는다. +- [ ] 사람이 판단해야 하는 `Priority`, `Owner`, 범위를 AI가 임의 확정하지 않았다. +- [ ] 승인 성공 시 REQ 본문과 `docs/srs/index.md` 상태가 둘 다 `Approved`로 갱신되었다. +- [ ] 승인 보류 시 상태가 잘못 바뀌지 않았다. +- [ ] 일괄 모드에서는 승인 가능한 REQ만 승격되었다. +- [ ] `docs/srs/index.md`는 로컬 형식을 유지한 채 필요한 상태값만 in-place로 갱신되었다. \ No newline at end of file diff --git a/skills/draft-batch-design/SKILL.md b/skills/draft-batch-design/SKILL.md new file mode 100644 index 0000000..16cbc19 --- /dev/null +++ b/skills/draft-batch-design/SKILL.md @@ -0,0 +1,61 @@ +--- +name: draft-batch-design +description: "Use when: drafting the design document for an existing batch, running /draft-batch-design with a BAT ID, filling docs/batches//design.md, recording architecture and interface decisions before implementation, or planning updates to docs/project-structure.md and docs/runtime-flows.md." +version: 0.8.0 +author: Justin Ko +license: private +argument-hint: "예: bat-001 또는 docs/batches/bat-001_20260424_scaffold" +user-invocable: true +metadata: + hermes: + tags: [stage-pilot, batch, design, architecture, sdlc] + related_skills: [draft-batch, draft-batch-planning, run-batch-implementation] +--- + +# Purpose + +This skill writes the design document for a batch by summarizing architecture, changed areas, key decisions, and edge cases from the batch plan and included REQs. + +`docs/project-structure.md`와 `docs/runtime-flows.md`가 존재하면, 이 skill은 해당 baseline 문서를 읽고 이번 batch의 구조 영향과 흐름 영향을 비교해야 한다. + +# Inputs + +- `BAT-ID` 또는 batch 경로 +- `docs/batches//planning.md` +- 포함된 REQ 문서들 +- `docs/batches//design.md` +- `docs/project-structure.md` (존재하는 경우) +- `docs/runtime-flows.md` (존재하는 경우) + +# Core Rules + +- planning이 없거나 범위가 비어 있으면 설계를 진행하지 않는다. +- 설계는 구현 전에 결정해야 할 구조와 인터페이스를 중심으로 적는다. +- `batch-lite`이고 planning의 `Design Gate`가 `no`이며 구조/인터페이스/런타임 영향이 없다면 design 단계는 생략할 수 있다. +- REQ 간 공통 설계 전제가 있다면 batch design에 끌어올린다. +- baseline 문서가 있으면 이번 batch 설계는 baseline 대비 변경점과 유지점이 무엇인지 명시해야 한다. +- `design.md`에는 최소한 아래 내용을 포함해야 한다. + - Architecture Summary + - Changed Areas + - Key Decisions + - Edge Cases + - Architecture Impact (`none` | `project-structure` | `runtime-flows` | `both`) + - Reference Doc Update Plan +- 기존 템플릿에 위 섹션이 없으면 설계 문서에 추가해 채운다. + +# Execution Procedure + +1. batch 경로를 확정한다. +2. planning과 포함 REQ를 읽는다. +3. planning의 `Design Gate`가 `no`이고 batch profile이 `batch-lite`면 design 없이 진행 가능한지 다시 확인하고, 그대로면 design 생략 결정을 유지한다. +4. `docs/project-structure.md`와 `docs/runtime-flows.md`가 있으면 읽고 현재 baseline과 batch 범위를 비교한다. +5. `design.md`에 Architecture Summary, Changed Areas, Key Decisions, Edge Cases를 채운다. +6. Architecture Impact와 Reference Doc Update Plan을 명시한다. +7. 설계상 blocker가 있으면 문서에 명시하고 구현으로 넘기지 않는다. + +# Validation + +- design 문서에 Architecture Summary, Changed Areas, Key Decisions, Edge Cases가 모두 있는지 확인한다. +- planning에서 정의한 핵심 범위가 design에서 누락되지 않았는지 확인한다. +- 구조 또는 흐름 영향이 있는 batch라면 Architecture Impact와 Reference Doc Update Plan이 채워졌는지 확인한다. +- `batch-lite`에서 design을 생략한 경우 planning의 `Design Gate`와 batch index profile이 그 결정을 설명하는지 확인한다. \ No newline at end of file diff --git a/skills/draft-batch-planning/SKILL.md b/skills/draft-batch-planning/SKILL.md new file mode 100644 index 0000000..1217014 --- /dev/null +++ b/skills/draft-batch-planning/SKILL.md @@ -0,0 +1,45 @@ +--- +name: draft-batch-planning +description: "Use when: drafting the planning document for an existing batch, running /draft-batch-planning with a BAT ID, populating docs/batches//planning.md, or setting batch scope and dependency notes before design." +version: 0.8.0 +author: Justin Ko +license: private +argument-hint: "예: bat-001 또는 docs/batches/bat-001_20260424_scaffold" +user-invocable: true +metadata: + hermes: + tags: [stage-pilot, batch, planning, implementation, sdlc] + related_skills: [draft-batch, draft-batch-design, run-batch-implementation] +--- + +# Purpose + +This skill writes the planning document for a batch by consolidating included REQs, scope, dependencies, milestones, and key risks. + +# Inputs + +- `BAT-ID` 또는 batch 경로 +- `docs/batches//index.md` +- 포함된 REQ 문서들 +- `docs/batches//planning.md` + +# Core Rules + +- planning은 batch 수준 범위, 의존성, 순서를 명확히 해야 한다. +- REQ 문서를 다시 쓰지 말고 delivery 관점의 묶음 계획으로 요약한다. +- planning은 design이 구현 전에 필요한지 여부를 명시해야 한다. +- `batch-lite`라면 planning에 왜 design을 생략할 수 있는지 또는 언제 design이 필요해지는지 남긴다. +- planning 작성 후 batch 상태는 `in-delivery`로 올릴 수 있다. + +# Execution Procedure + +1. 입력에서 batch 경로를 확정한다. +2. batch index와 포함 REQ를 읽는다. +3. `planning.md`에 scope, out-of-scope, dependency, milestone, risk를 채운다. +4. `planning.md`의 `Design Gate`에 design 필요 여부와 근거를 적는다. +4. 필요하면 batch `index.md`의 Status를 `in-delivery`로 갱신한다. + +# Validation + +- planning 문서에 Included REQ, Delivery Plan, Dependencies, Design Gate, Milestones, Risks가 모두 존재하는지 확인한다. +- batch index와 planning의 포함 REQ 목록이 일치하는지 확인한다. \ No newline at end of file diff --git a/skills/draft-batch-verification/SKILL.md b/skills/draft-batch-verification/SKILL.md new file mode 100644 index 0000000..66f5348 --- /dev/null +++ b/skills/draft-batch-verification/SKILL.md @@ -0,0 +1,67 @@ +--- +name: draft-batch-verification +description: "Use when: drafting verification evidence for a batch, running /draft-batch-verification with a BAT ID, filling docs/batches//verification.md, mapping REQ acceptance criteria to evidence before release approval, or verifying sync with docs/project-structure.md and docs/runtime-flows.md." +version: 0.8.1 +author: Justin Ko +license: private +argument-hint: "예: bat-001 또는 docs/batches/bat-001_20260424_scaffold" +user-invocable: true +metadata: + hermes: + tags: [stage-pilot, batch, verification, testing, sdlc] + related_skills: [draft-batch, run-batch-implementation, confirm-batch-verification] +--- + +# Purpose + +This skill drafts the verification document for a batch by mapping included REQ acceptance criteria to available evidence and identifying remaining blockers. + +batch design 또는 REQ가 baseline 문서 생성/갱신 또는 구조/흐름 영향이 있음을 나타내면, verification은 `docs/project-structure.md`와 `docs/runtime-flows.md`가 코드와 설계에 맞게 동기화되었는지도 확인해야 한다. + +# Inputs + +- `BAT-ID` 또는 batch 경로 +- 포함된 REQ 문서들 +- `docs/batches//design.md` (존재하는 경우) +- `docs/batches//planning.md` +- `docs/batches//implementation.md` +- `docs/batches//verification.md` +- `docs/project-structure.md` (존재하는 경우) +- `docs/runtime-flows.md` (존재하는 경우) + +# Core Rules + +- verification은 Discovery 성공 기준이 아니라 REQ acceptance criteria에 직접 연결한다. +- evidence가 없는 항목은 통과로 간주하지 않는다. +- stateful runtime/PYDEBUG binding 검증에서는 같은 입력을 연속 호출해도 내부 상태가 누적될 수 있음을 먼저 확인한다. REQ가 deterministic candidate/order evidence를 요구하면 '같은 입력 + 같은 초기 상태'를 보장하는 reset hook, 재초기화 step, 또는 동등한 통제 절차를 verification evidence에 포함한다. +- 확인되지 않은 항목은 `Blocking Issues`에 명시한다. +- `batch-lite`에서 design 문서가 없다면 planning의 `Design Gate`와 implementation 결과를 기준으로 구조 영향이 실제로 없었는지 확인해야 한다. +- design의 Architecture Impact가 `none`이 아니거나 baseline 문서 생성/갱신이 batch 범위에 있으면, baseline 문서 동기화 evidence를 verification에 포함해야 한다. +- baseline 문서 동기화가 필요한데 evidence가 없으면 release-ready로 간주하지 않는다. +- hardware/manual 검증이 남아 있는 상태에서 코드/문서 구현만 끝났다면 `Ready for Release: false`를 유지하고, 누락된 실장치 증거를 `Missing evidence`와 `Blocking Issues`에 명시해 confirm 단계로 성급히 올리지 않는다. +- 설정 persistence를 실장치로 검증할 때는 장치가 노출하는 실제 제어 경로를 먼저 확인한다. CTSP 설정의 재부팅 persistence를 확인하는 목적이면 가능할 때 CTSP `REBOOT`/`RESET` 명령으로 재부팅하고, 재부팅 전후 동일 CTSP readback(`DATE`, 대상 설정 명령 등)으로 복귀와 설정 유지 여부를 캡처한다. 테스트 중 설정을 바꿨다면 verification evidence를 수집한 뒤 baseline 값을 다시 써서 원복 여부도 함께 남긴다. +- CTSP 기반 실장치 검증에서는 먼저 비파괴 명령(예: `DATE`, 읽기 전용 `VISIONCFG`)으로 연결과 응답 형식을 확인한 뒤 쓰기/재부팅 테스트로 넘어간다. `VISIONCFG`처럼 값 목록이 단일 payload 안에 `/`로 직렬화되는 명령은 raw response, 분해 후 값 개수, slot별 해석(예: ratio 27번째, trailing reserved 28~32번째)을 함께 기록해야 한다. +- 업그레이드/migration 검증에서는 업그레이드 직전 legacy readback 원문과 기대 26개 값을 먼저 고정하고, 업그레이드 직후 첫 current 32값 readback의 1~26 슬롯을 그 legacy 캡처와 직접 대조해야 한다. ratio=`10`, trailing reserved=`0xFFFFFFFF`만 맞고 1~26 슬롯이 업그레이드 전 값과 다르면 migration 성공으로 간주하지 말고, 설정 영역 보존 실패/플래시 경로 문제/fixture 주입 실패 가능성을 blocker로 기록한다. +- 업그레이드 직후 boot log에 `current revision size mismatch (...)` 뒤 `vision config default`가 보이면, 이는 rev0 migration 성공 증거가 아니라 "current revision으로 해석된 저장본의 binary schema/길이가 현 코드와 다름"을 뜻한다. 이런 경우에는 downgrade 후 legacy `VISIONCFG` readback을 다시 캡처해 기존 26개 값이 되살아나는지 확인하고, 값이 복원되면 storage erase보다 revision-schema drift 또는 current-loader incompatibility를 우선 원인 후보로 기록한다. +- 업그레이드 경로 진단에서는 image slot과 `storage_partition`을 분리해서 해석한다. firmware loader가 image slot만 erase/write하는 구조라면, migration 실패 후 default current 값이 보였더라도 곧바로 "설정이 지워졌다"고 결론내리지 말고, downgrade/legacy readback 또는 raw property dump로 storage 보존 여부를 먼저 구분한다. +- verification에 사람이 명시적으로 잔여 리스크를 수용하기로 했다면 `Human Approval Memo` 섹션을 추가해 승인 결정, 승인자, 승인 시각, 스킵/수용 범위, 잔여 리스크, 승인 근거를 구조적으로 남긴다. 이후 confirm 단계에서는 이 메모가 수용한 blocker와 미수용 blocker를 분리해서 판단할 수 있어야 한다. +- 재부팅 persistence를 자동 검증할 때는 가능하면 ext-proto reboot보다 CTSP `REBOOT`/`RESET`처럼 실제 설정 검증 경로와 같은 제어면을 우선 사용하고, 재부팅 전후 `DATE`와 대상 설정 readback을 같이 캡처한다. + +# Execution Procedure + +1. batch와 포함 REQ를 확정한다. +2. REQ acceptance criteria를 목록화한다. +3. design 문서가 있으면 Architecture Impact와 Reference Doc Update Plan을 읽고, 없으면 planning의 `Design Gate`를 읽는다. +4. implementation 결과와 테스트 로그를 근거로 evidence를 연결한다. +5. baseline 문서 동기화가 필요하면 `docs/project-structure.md`와 `docs/runtime-flows.md`의 생성/갱신 여부를 evidence에 포함한다. +6. `batch-lite`에서 design이 없었다면 verification에 `no architecture impact confirmed` 여부를 남긴다. +7. `verification.md`의 Acceptance Mapping, Evidence, Result를 채운다. +8. 재부팅 persistence나 CTSP contract 계열 실장치 검증을 수행했다면 `references/opspro-visioncfg-hardware-verification.md` 같은 session note를 남겨, 실제로 성공한 명령/응답/원복 순서를 future batch verification에 재사용할 수 있게 한다. OpsPro `VISIONCFG` 작업에서는 current 32값 round-trip, legacy 26값 호환, invalid ratio reject, reboot persistence, migration failure signature(`current revision size mismatch`)뿐 아니라, 순수 legacy device에서의 성공한 first-boot migration / post-reboot current direct-load evidence와 downgrade/re-upgrade schema-drift exclusion rule까지 그 reference에 누적한다. + +# Validation + +- 포함 REQ마다 acceptance mapping이 존재하는지 확인한다. +- evidence 없는 항목이 있으면 Result에 blocker로 반영했는지 확인한다. +- Architecture Impact가 있는 batch라면 baseline 문서 동기화 evidence 또는 blocker가 명시됐는지 확인한다. +- `batch-lite`에서 design이 없는 경우 planning과 implementation을 기준으로 구조 영향 없음 또는 design 누락 blocker 중 하나가 명시됐는지 확인한다. +- 실장치/manual evidence가 필요한 REQ인데 코드 diff나 빌드 결과만 있는 경우, `Ready for Release`가 false로 유지되고 confirm 단계 보류 사유가 적혀 있는지 확인한다. \ No newline at end of file diff --git a/skills/draft-batch-verification/references/opspro-visioncfg-hardware-verification.md b/skills/draft-batch-verification/references/opspro-visioncfg-hardware-verification.md new file mode 100644 index 0000000..694142a --- /dev/null +++ b/skills/draft-batch-verification/references/opspro-visioncfg-hardware-verification.md @@ -0,0 +1,58 @@ +# OpsPro VISIONCFG hardware verification reference + +Use this reference when drafting or updating verification evidence for OpsPro `VISIONCFG` CTSP and migration work. + +## CTSP hardware verification patterns + +1. Start with non-destructive probes. + - `DATE` confirms port/CTSP responsiveness. + - `VISIONCFG` read confirms payload shape before any write. +2. For `VISIONCFG`, always capture: + - raw request + - raw response + - parsed value count + - slot interpretation (1..26 legacy/public, 27 ratio, 28..32 reserved for current 32-value contract) +3. When write tests are allowed, use this order: + - current 32-value round-trip + - legacy 26-value compatibility write + - invalid ratio reject / no partial write + - reboot persistence + - migration from pure legacy device/state +4. After write-based evidence capture, restore baseline values and confirm readback. + +## Migration verification lessons + +### Exclude downgrade/re-upgrade drift from pure legacy migration evidence +If a device has already booted a newer firmware that can write a `current` `visioncfg`, then later downgrade/re-upgrade behavior is not clean REQ-005 legacy migration evidence. Treat that as a separate compatibility/schema-drift investigation, not as the primary legacy-to-current migration acceptance path. + +### Pure legacy migration acceptance path +For REQ-005 migration evidence, use a device that has never been updated to the newer firmware revision being tested. + +Required sequence: +1. Confirm legacy firmware returns 26-value `VISIONCFG`. +2. Write distinctive 26-value legacy test data. +3. Capture legacy readback before upgrade. +4. Upgrade to target firmware and reboot. +5. On first boot, read `VISIONCFG` and confirm: + - 32 values total + - slots 1..26 exactly match pre-upgrade legacy values + - slot 27 is `10` + - slots 28..32 are `-1/-1/-1/-1/-1` +6. Reboot via CTSP `REBOOT` and confirm current readback is unchanged. +7. Restore baseline current values after evidence capture. + +### Diagnostic logging for schema issues +When investigating migration failures, instrument `_load_vision_config()` to log: +- `sizeof(struct prop_header)` +- `sizeof(struct ocv_rect)` +- `sizeof(struct _vision_config_rev0)` +- `sizeof(struct _vision_config)` +- header probe `rc`, `type`, `revision` +- current read expected size vs `rc` +- revision-0 read expected size vs `rc` + +Interpretation rule: +- `current revision size mismatch (...)` followed by `vision config default` means the stored blob was interpreted as current revision but its binary size/layout did not match the current firmware's expected current schema. Do not misclassify that as successful legacy migration. + +## ABI / struct-layout caution +Do not infer target firmware ABI from `PYDEBUG`-only fallback definitions or host-side binding builds. For firmware storage compatibility analysis, trust target-firmware definitions and on-device logs first. diff --git a/skills/draft-batch/SKILL.md b/skills/draft-batch/SKILL.md new file mode 100644 index 0000000..5789833 --- /dev/null +++ b/skills/draft-batch/SKILL.md @@ -0,0 +1,142 @@ +--- +name: draft-batch +description: "Use when: creating a new batch from approved REQ IDs, running /draft-batch with one or more req IDs, scaffolding docs/batches// files, or updating docs/batches/index.md for a new delivery unit." +version: 0.9.2 +author: Justin Ko +license: private +argument-hint: "예: req-001 req-002 req-003" +user-invocable: true +metadata: + hermes: + tags: [stage-pilot, batch, planning, documentation, sdlc] + related_skills: [suggest-batch-reqs, draft-batch-planning, draft-batch-design, draft-batch-verification, run-batch-delivery] +--- + +# Purpose + +This skill creates a new batch delivery unit from already-selected approved requirements, chooses the right delivery profile, scaffolds its folder and documents, and updates the batch register. + +# When to use + +- 포함할 `Approved` REQ 집합이 이미 확정되어 새 batch를 실제로 생성해야 할 때 +- `/draft-batch req-001 req-002`처럼 batch 문서 scaffold와 register entry가 필요할 때 +- 단일 저위험 `Approved` REQ를 `minor-change` fast path로 바로 batch-lite batch로 시작할 때 +- `suggest-batch-reqs` 결과에서 사람이 후보를 선택한 뒤 실제 batch 문서를 만들 차례일 때 + +## Do not use when + +- 아직 REQ가 `Approved`되지 않아 먼저 승인 절차가 필요할 때 (`confirm-req`가 더 적절함) +- 어떤 REQ를 함께 묶을지 아직 사람 선택이 끝나지 않았을 때 (`suggest-batch-reqs`가 더 적절함) +- 이미 존재하는 batch의 planning/design/verification 내용을 채우는 단계일 때 (`draft-batch-planning`, `draft-batch-design`, `draft-batch-verification`이 더 적절함) +- 현재 저장소의 전체 다음 SDLC 단계를 알고 싶은 상황일 때 (`run-sdlc`가 더 적절함) + +# Inputs + +- `Approved` `REQ-ID` 목록 +- 선택 입력 + - batch profile 힌트 (`standard` | `batch-lite` | `minor-change`) +- `docs/srs/index.md` +- 대상 REQ 문서들 +- `docs/batches/index.md` +- 배치 템플릿의 논리 경로 + - `stage-pilot/templates/batches/batch-index.md` + - `stage-pilot/templates/batches/planning.md` + - `stage-pilot/templates/batches/design.md` + - `stage-pilot/templates/batches/implementation.md` + - `stage-pilot/templates/batches/verification.md` + - `stage-pilot/templates/batches/index.md` (`docs/batches/index.md` register 템플릿/기준) + +## Template path resolution + +- 배치 템플릿은 하나의 물리 경로만 고정하지 말고 `stage-pilot/templates/batches/...`를 논리 경로로 취급한다. +- 물리 경로는 다음 순서로 해석한다. + 1. `.stage-pilot/templates/batches/...` + 2. `~/.stage-pilot/templates/batches/...` +- 여러 후보가 동시에 존재하면 가장 우선순위가 높은 한 곳만 사용하고, 서로 다른 설치 위치의 템플릿을 섞지 않는다. +- 이 resolution rule은 SKILL.md 본문에 그대로 적어 두는 편이 좋다. 배치 템플릿이 실제로 존재하는 현재 머신의 경로만 적어 두면 다른 PC나 다른 agent install shape에서 portability가 떨어진다. +- 템플릿 source path와 생성 target path를 혼동하지 않는다. + - source: `stage-pilot/templates/batches/...` + - target: `docs/batches//...`, `docs/batches/index.md` + +# Core Rules + +- 이 skill은 사람이 선택을 마친 REQ 집합을 batch 문서로 구체화하는 단계다. +- `Approved` 상태가 아닌 REQ는 batch에 포함하지 않는다. +- 새 batch 폴더는 `docs/batches/bat-XXX_YYYYMMDD_scope/` 형식을 사용한다. +- 새 batch의 persisted profile은 `standard` 또는 `batch-lite` 중 하나다. +- `minor-change`는 profile 자체가 아니라 단일 저위험 REQ를 빠르게 `batch-lite`로 판단하는 입력 fast path다. +- 입력이 정확히 1개의 `Approved` REQ이고 범위가 국소적이며 구조/인터페이스/런타임 흐름 변경이 없으면 `minor-change` fast path를 사용할 수 있다. 이 경우 최종 batch profile은 `batch-lite`로 기록한다. +- `standard` batch는 생성 시 아래 파일을 함께 만든다. + - `index.md` ← `stage-pilot/templates/batches/batch-index.md` + - `planning.md` ← `stage-pilot/templates/batches/planning.md` + - `design.md` ← `stage-pilot/templates/batches/design.md` + - `implementation.md` ← `stage-pilot/templates/batches/implementation.md` + - `verification.md` ← `stage-pilot/templates/batches/verification.md` +- `batch-lite`는 생성 시 아래 파일만 먼저 만든다. + - `index.md` ← `stage-pilot/templates/batches/batch-index.md` + - `planning.md` ← `stage-pilot/templates/batches/planning.md` +- `batch-lite`의 `implementation.md`와 `verification.md`는 해당 단계 진입 시 생성한다. +- `batch-lite`라도 구조/인터페이스/흐름 영향이 생기면 `design.md`를 추가하고 사실상 standard depth로 다룬다. +- REQ 묶음 선택이 아직 불안정하거나 대안 비교가 필요하면 batch를 생성하지 말고 먼저 `suggest-batch-reqs`로 되돌린다. +- `docs/batches/index.md`의 register는 새 batch 생성과 함께 반드시 갱신한다. +- register 행에는 최소한 `BAT ID`, `Profile`, `Status`, `Included REQ`, `Discovery`, `Folder`를 채운다. +- source discovery는 포함 REQ에서 역추적해 채운다. source가 하나면 그대로 기록하고, 여러 source discovery가 섞이면 축약 표기와 함께 batch 문서 Notes에 설명한다. + +# Execution Procedure + +1. 입력된 REQ 경로와 상태를 확인하고 모두 `Approved`인지 검증한다. +2. 입력된 REQ 묶음이 이미 사람에 의해 선택된 집합인지 확인한다. 아직 후보 선택 단계면 batch를 만들지 않는다. +3. 필요한 템플릿의 논리 경로를 현재 환경의 물리 경로로 해석하고, 어떤 위치를 사용할지 확정한다. +4. 현재 저장소의 기존 batch 문서(`docs/batches/index.md`, 가능하면 가장 최근 batch의 `index.md`/`planning.md`)를 먼저 읽어 로컬 heading, register 표기, 문체를 확인한다. 템플릿은 scaffold source이고, target 문서는 저장소 로컬 형식을 우선한다. +5. REQ 수, 영향 범위, 구조/인터페이스/런타임 영향 여부를 기준으로 `standard` 또는 `batch-lite` profile을 결정한다. + - 특히 같은 Discovery에서 나온 REQ 묶음이 (a) runtime 또는 interface 구조 변경, (b) 성능/운영 budget 같은 Non-Functional 기준, (c) baseline 문서 정렬을 함께 포함하면 기본값은 `standard`로 본다. 이런 조합은 planning만으로 끝나지 않고 design, implementation, verification 문서를 같은 턴에 scaffold하는 편이 drift와 재작업을 줄인다. +6. 단일 저위험 REQ면 `minor-change` fast path를 검토하되 최종 기록 profile은 `batch-lite`로 남긴다. +7. 배치 제목과 slug를 결정하고 다음 `BAT-ID`를 계산한다. +8. profile에 맞는 템플릿으로 batch 폴더와 문서를 생성하되, placeholder를 그대로 두지 말고 이번 REQ 집합 기준의 구체적 초안 내용을 즉시 채운다. +9. `index.md`에 `Profile`, Included REQ, Excluded REQ, Source Discovery, Scope, Documents, Notes를 채운다. +10. 포함 REQ의 source discovery를 수집해 `index.md`와 register에 반영한다. source가 복수면 Notes에 혼합 배경을 설명한다. +11. `planning.md`에 `Design Gate`를 기록해 design이 즉시 필요한지 여부와 근거를 남긴다. +12. `docs/batches/index.md`에 새 register 행을 추가하고 `BAT ID`, `Profile`, `Status`, `Included REQ`, `Discovery`, `Folder`를 실제 생성 결과와 일치하게 채운다. 이때 register의 헤더/구분선/행 순서는 현재 저장소 로컬 형식을 유지한다. +13. 생성된 문서 집합과 register가 profile 판단 및 입력 REQ 집합과 일치하는지 검증한다. + +# Common Pitfalls + +1. `Approved`가 아닌 REQ를 섞는 실수 + - batch 생성은 delivery 입력이 확정된 REQ만 대상으로 해야 하므로 상태를 먼저 검증한다. + +2. 아직 사람 선택이 끝나지 않았는데 곧바로 batch를 만드는 실수 + - 여러 후보안 중 선택이 남아 있으면 먼저 `suggest-batch-reqs`에서 멈춘다. + +3. `minor-change`를 최종 profile 값처럼 기록하는 실수 + - `minor-change`는 fast path 이름일 뿐이고, persisted profile은 `batch-lite`다. + +4. `stage-pilot/templates/batches/index.md`와 `docs/batches/index.md`를 혼동하는 실수 + - 전자는 register template/source이고 후자는 실제 저장소 target 파일이다. + +5. `batch-lite`인데 구조 영향이 생겼는데도 design 문서를 만들지 않는 실수 + - planning의 `Design Gate`를 다시 보고 필요하면 `design.md`를 추가한다. + +6. 템플릿 경로를 한 설치 형태에만 고정하는 실수 + - `.stage-pilot`, `~/.stage-pilot` 순서의 resolution rule을 따른다. + +7. pack-level 문서를 빼먹는 실수 + - 외부 skill pack의 SKILL.md를 실제 수정해 버전이 바뀌면 `README.md` inventory와 `CHANGELOG.md`도 같은 턴에 함께 갱신한다. + +8. 템플릿 문구를 저장소 로컬 형식보다 우선해 그대로 덮어쓰는 실수 + - 템플릿은 scaffold source일 뿐이다. 실제 생성 시에는 현재 저장소의 기존 batch 문서 형식과 register 배치를 먼저 보고, target 문서는 그 로컬 형식에 맞춰 채운다. + +9. 템플릿 placeholder만 복사하고 batch 초안 내용을 비워 두는 실수 + - `draft-batch` 단계에서는 최소한 포함/제외 REQ, source discovery, scope, Design Gate, 핵심 리스크까지 즉시 채워 넣어 다음 stage가 바로 이어질 수 있게 한다. + +# Verification Checklist + +- [ ] 포함 REQ가 모두 `Approved` 상태다. +- [ ] 새 `BAT-ID`와 folder명이 기존 register와 충돌하지 않는다. +- [ ] 사용한 템플릿 물리 경로가 resolution rule과 일치한다. +- [ ] template source path와 generated target path를 혼동하지 않았다. +- [ ] `standard`면 5개 문서가 모두 생성되었다. +- [ ] `batch-lite`면 최소 `index.md`, `planning.md`가 생성되었고 profile이 `batch-lite`로 기록되었다. +- [ ] `planning.md`의 `Design Gate`가 현재 profile과 구조 영향 판단을 설명한다. +- [ ] `docs/batches/index.md` register 행이 실제 batch folder, profile, included REQ, discovery와 일치한다. +- [ ] source discovery가 단일/복수 여부에 맞게 기록되었다. +- [ ] 아직 사람 선택이 필요한 상태를 batch 생성으로 건너뛰지 않았다. diff --git a/skills/draft-release/SKILL.md b/skills/draft-release/SKILL.md new file mode 100644 index 0000000..e9350ca --- /dev/null +++ b/skills/draft-release/SKILL.md @@ -0,0 +1,63 @@ +--- +name: draft-release +description: "Use when: creating a release document from one or more release-candidate batches, running /draft-release with BAT IDs, generating docs/releases/rel-XXX_YYYYMMDD_.md, or updating docs/releases/index.md before release approval." +version: 0.8.1 +author: Justin Ko +license: private +argument-hint: "예: bat-001 또는 bat-001 bat-002" +user-invocable: true +metadata: + hermes: + tags: [stage-pilot, release, drafting, documentation, sdlc] + related_skills: [confirm-batch-verification, confirm-release, capture-release-feedback] +--- + +# Purpose + +This skill creates a profile-aware release document from one or more verified batches and registers it in the release index. + +# Inputs + +- `BAT-ID` 목록 +- `docs/batches/index.md` +- 각 batch의 `verification.md` +- `docs/releases/index.md` +- 템플릿의 논리 경로 + - `stage-pilot/templates/releases/release.md` + - `stage-pilot/templates/releases/index.md` + +## Template path resolution + +- 템플릿은 하나의 물리 경로만 고정하지 말고 `stage-pilot/templates/...`를 논리 경로로 취급한다. +- 물리 경로는 다음 순서로 해석한다. + 1. `.stage-pilot/templates/...` + 2. `~/.stage-pilot/templates/...` +- 여러 후보가 동시에 존재하면 가장 우선순위가 높은 한 곳만 사용하고, 서로 다른 설치 위치의 템플릿을 섞지 않는다. +- 템플릿 source path와 생성 target path를 혼동하지 않는다. + - source: `stage-pilot/templates/...` + - target: `docs/releases/rel-XXX_YYYYMMDD_.md`, `docs/releases/index.md` + +# Core Rules + +- `release-candidate`가 아닌 batch는 release에 포함하지 않는다. +- 새 release 경로는 `docs/releases/rel-XXX_YYYYMMDD_.md` 형식을 사용한다. +- release는 `docs-only`, `tooling`, `app-service` profile 중 하나를 가진다. +- release profile은 포함 batch의 운영 부담 중 가장 무거운 것을 따른다. 기본 우선순위는 `app-service` > `tooling` > `docs-only`다. +- `docs-only`는 문서, 가이드, 템플릿, 정적 산출물 변경에 사용한다. +- `tooling`은 bootstrap, 스크립트, CLI, 자동화, 패키지 동작 검증이 필요한 변경에 사용한다. +- `app-service`는 실제 서비스 배포, runtime health, 로그, 운영 확인이 필요한 변경에 사용한다. +- release 문서 생성 시 Included Batch, Profile, Scope, Rollout Plan, Rollback Plan, Verification Checklist를 채운다. + +# Execution Procedure + +1. 입력 batch들의 상태와 verification 결과를 확인한다. +2. 포함 batch의 변경 성격과 검증 evidence를 기준으로 release profile을 결정한다. +3. 다음 `REL-ID`를 계산하고 제목/slug를 정한다. +4. 필요한 release 논리 템플릿 경로를 현재 환경의 물리 경로로 해석한 뒤 release 문서를 생성한다. +5. `docs/releases/index.md`가 없으면 `stage-pilot/templates/releases/index.md`에 해당하는 실제 template source를 해석한 뒤 생성하고, 이미 있으면 register 행을 추가한다. + +# Validation + +- 포함 batch가 모두 `release-candidate`인지 확인한다. +- release 문서에 `Profile`이 있고 profile별 검증 항목이 비어 있지 않은지 확인한다. +- release 문서와 index register 행이 함께 생성됐는지 확인한다. \ No newline at end of file diff --git a/skills/draft-req/SKILL.md b/skills/draft-req/SKILL.md new file mode 100644 index 0000000..14af9b2 --- /dev/null +++ b/skills/draft-req/SKILL.md @@ -0,0 +1,211 @@ +--- +name: draft-req +description: "Use when: turning a confirmed Discovery into one or more requirement documents under `docs/srs/`, running /draft-req with a dcy-id or Discovery path, creating docs/srs//req-XXX_.md files, updating docs/srs/index.md from FR/NFR input, or carrying forward constraints from docs/project-structure.md and docs/runtime-flows.md." +version: 1.0.3 +author: Justin Ko +license: private +argument-hint: "예: dcy-001 또는 docs/discovery/dcy-001_20260424_krx-stock-picker-python-scaffold.md" +user-invocable: true +metadata: + hermes: + tags: [stage-pilot, requirements, drafting, documentation, sdlc] + related_skills: [confirm-discovery, change-req, confirm-req, suggest-batch-reqs] +--- + +# Purpose + +This skill converts a Discovery document into one or more requirement documents under `docs/srs/`, updates the SRS register, and writes the generated REQ references back to the source Discovery document. + +Reference: template path portability guidance lives in `references/template-path-resolution.md`. + +`docs/project-structure.md`와 `docs/runtime-flows.md`가 존재하면, REQ 작성 시 이 문서들을 공통 baseline 참조 문서로 읽고 필요한 구조 제약만 REQ에 반영한다. + +# When to use + +- `/draft-req dcy-001`처럼 confirmed Discovery를 REQ backlog로 정규화해야 할 때 +- Discovery의 FR/NFR을 구현 가능한 requirement 문서로 분리해야 할 때 +- `docs/srs/index.md`의 register와 Next Requirement ID를 함께 갱신해야 할 때 +- Discovery가 구조 또는 runtime baseline 갱신을 요구할 때 이를 REQ backlog에 반영해야 할 때 + +## Do not use when + +- source Discovery가 아직 `confirmed` 상태가 아니고, 아직 문제 정의나 범위 정리가 끝나지 않았을 때 +- 기존 Discovery 문서를 검토/보강/승인해야 하는 단계일 때 (`review-discovery`, `confirm-discovery`가 더 적절함) +- 새 REQ를 만드는 것이 아니라 기존 REQ의 상태, acceptance criteria, 범위를 변경해야 할 때 (`change-req`가 더 적절함) +- 이미 승인된 REQ를 구현 batch로 묶는 단계일 때 (`suggest-batch-reqs`, `draft-batch`가 더 적절함) + +# Inputs + +- Discovery 식별자 + - `dcy-001` 같은 prefix + - 전체 Discovery ID + - `docs/discovery/.md` 경로 +- 대상 Discovery 문서 본문 +- `docs/project-structure.md` (존재하는 경우) +- `docs/runtime-flows.md` (존재하는 경우) +- 템플릿의 논리 경로 + - `stage-pilot/templates/srs/index.md` + - `stage-pilot/templates/srs/req-template.md` + +## Template path resolution + +- 템플릿은 하나의 물리 경로만 고정하지 말고 `stage-pilot/templates/...`를 논리 경로로 취급한다. +- 물리 경로는 다음 순서로 해석한다. + 1. `.stage-pilot/templates/...` + 2. `~/.stage-pilot/templates/...` +- 여러 후보가 동시에 존재하면 가장 우선순위가 높은 한 곳만 사용하고, 서로 다른 설치 위치의 템플릿을 섞지 않는다. +- 템플릿 source path와 생성 target path를 혼동하지 않는다. + - source: `stage-pilot/templates/...` + - target: `docs/srs/index.md`, `docs/srs//req-XXX_.md` +- 어떤 physical path를 선택했는지 생성 전에 내부적으로 확정하고, 경로 해석이 애매하면 이를 보고한다. + +- Repository targets + - `docs/srs/index.md` + - `docs/srs//req-XXX_.md` + +# Core Rules + +## 0. Entry gating and confirmed prerequisite + +- source Discovery는 기본적으로 `confirmed` 상태여야 한다. +- source Discovery가 아직 `draft`이거나 confirmed 여부가 불명확하면, REQ drafting을 계속 진행하지 말고 그 사실을 먼저 보고한다. +- confirmed 전제가 충족되지 않으면 `review-discovery` 또는 `confirm-discovery`가 선행되어야 한다. +- 다만 사용자가 명시적으로 "초안 REQ를 미리 뽑아 달라"고 요청한 예외 상황에서는, 승인 전 초안임을 분명히 표시하고 `Proposed` 초안으로만 제한적으로 작성할 수 있다. + +## 1. Baseline architecture reference rules + +- `docs/project-structure.md`와 `docs/runtime-flows.md`는 REQ의 상세 본문을 대체하지 않지만, 구조 제약과 흐름 제약을 읽기 위한 공통 baseline 참조 문서로 사용한다. +- REQ에는 baseline 문서의 전체 구조나 전체 흐름을 복제하지 않는다. 구현에 필요한 제약과 acceptance 기준만 남긴다. +- source Discovery가 구조 또는 runtime baseline 갱신을 요구하면, REQ 후보에 baseline 문서 갱신 작업을 포함해야 한다. +- baseline 문서가 없고 source Discovery가 그 갭 보완을 직접 범위에 포함한다면, baseline 문서를 다루는 REQ를 누락하면 안 된다. +- baseline 문서가 없는데 source Discovery가 그 갭을 다루지 않는다면, `bootstrap-baseline` 선행 누락 가능성을 보고한다. + +## 2. 입력 해석 + +- 입력이 prefix면 `docs/discovery/` 아래에서 일치하는 문서를 찾는다. +- 여러 후보가 나오면 임의 선택하지 않고 사용자 확인이 필요하다고 보고한다. +- `.stage-pilot/` 경로는 Discovery 문서 탐색 대상에서 제외한다. +- template source는 단일 고정 경로로 가정하지 않는다. +- `stage-pilot/templates/...`는 logical path로 취급하고, 실제 읽기 경로는 현재 workspace의 StagePilot 설치 형태에 맞게 해석한다. +- template source를 읽을 때는 아래 순서로 후보를 확인한다. + 1. `.stage-pilot/templates/...` + 2. `~/.stage-pilot/templates/...` +- 어떤 physical path를 선택했는지 생성 전에 내부적으로 확정하고, 경로 해석이 애매하면 이를 보고한다. + +## 3. REQ 분해 원칙 + +- FR은 기본적으로 독립 구현과 독립 검증 가능 단위로 REQ 후보를 만든다. +- 하나의 FR이 과도하게 크면 복수 REQ로 분해한다. +- NFR은 독립 측정 가능성, 독립 backlog 가치, 운영 영향도가 충분할 때만 별도 REQ로 승격한다. +- 특정 FR 또는 batch 검증 기준에 흡수하는 편이 적절한 NFR은 별도 REQ를 만들지 말고 Notes에 연결 근거를 남긴다. +- baseline 문서 갱신 요구는 보통 `Documentation`, `Interface`, `Configuration` 중 가장 적절한 Type으로 분류한다. +- Discovery가 "기능 계약 + 성능/운영 기준 + baseline 문서 정렬"을 함께 요구하는 경우에는 다음 우선 분해 패턴을 먼저 검토한다. + 1. 기능 계약(상태 전이, 인터페이스, 실행 흐름) -> `Interface` 또는 가장 가까운 기능 Type + 2. 상태별 budget, latency, 처리 시간, 계측 의무 -> `Non-Functional` + 3. `docs/runtime-flows.md`/`docs/project-structure.md` 같은 baseline 정렬 -> `Documentation` +- 성능 수치가 구현 전 운영 계측값에 의존하더라도, 측정 경로와 목표 기준이 Discovery에 이미 정리돼 있으면 별도 `Non-Functional` REQ로 승격해 REQ 단계에서 evidence 수집을 요구하는 편을 우선한다. + +## 4. Type 및 경로 규칙 + +- 아래 폴더 중 정확히 하나를 선택한다. + - `Configuration` + - `Data` + - `Deployment` + - `Documentation` + - `Exception` + - `Installation` + - `Integration` + - `Interface` + - `Migration` + - `Non-Functional` + - `Testing` +- 새 REQ 경로는 `docs/srs//req-XXX_.md` 형식을 사용한다. +- `REQ-XXX` 번호는 `docs/srs/index.md`의 `Next Requirement ID`를 source of truth로 사용한다. + +## 5. 문서 작성 규칙 + +- `stage-pilot/templates/srs/req-template.md`에 해당하는 실제 template source를 해석한 뒤 문서를 만든다. +- 생성 대상 경로는 `docs/srs//req-XXX_.md`다. +- `Status` 기본값은 `Proposed`다. +- `Intent`, `Requirement`, `Acceptance Criteria`, `Impacted Area`, `Notes`를 실제 문장으로 채운다. +- baseline 문서와 충돌하거나 baseline 문서를 갱신해야 하는 구조 제약이 있으면 `Impacted Area` 또는 `Notes`에 명시한다. +- 사람 승인 없이는 확정할 수 없는 항목은 비워 두지 말고 `Notes`에 결정 필요 또는 가정으로 명시한다. +- Change Log는 최초 생성 기록을 남긴다. + +## 6. 인덱스 갱신 규칙 + +- REQ를 하나 이상 생성하면 `docs/srs/index.md`의 아래 항목을 함께 갱신한다. + - `Next Requirement ID` + - `Requirement Register` + - `Recent Change Log Summary` +- 기존 `docs/srs/index.md`가 이미 존재하면, 먼저 현재 register/header 형식과 열 순서를 읽고 그 로컬 형식을 우선 보존한다. +- template의 예시 열(`ID, Title, Type, Status, Priority, Owner, Link`)은 기본 기준이지만, 기존 index가 다른 열 이름이나 순서를 사용 중이면 사용자나 저장소가 이미 채택한 형식을 임의로 템플릿 형식으로 갈아엎지 않는다. +- 기존 index에 없는 새 필드가 필요하면 가능한 한 기존 형식에 자연스럽게 확장하고, 대규모 형식 정규화는 별도 변경으로 다룬다. +- Register에는 현재 index 형식이 요구하는 최소 메타데이터를 누락 없이 추가한다. +- 기존 `docs/srs/index.md`가 이미 존재하면, template wording으로 통째로 덮어쓰지 말고 현재 저장소의 로컬 형식을 source of truth로 취급한다. + - 기존 섹션 구조, 설명 문구, 표/리스트 형식, 열 순서는 가능한 한 유지한다. + - `Next Requirement ID` 표현이 `- REQ-002` 형태인지, `- Current: \`REQ-002\`` 형태인지 등은 현재 파일 형식을 따른다. + - 필요한 값만 in-place로 갱신하고, template source는 새 index bootstrap이 필요할 때만 사용한다. + +## 7. Source Discovery 역참조 갱신 규칙 + +- REQ를 하나 이상 생성하면 source Discovery 문서의 `# 0. 문서 상태`에도 어떤 REQ가 생성됐는지 기록해야 한다. +- Discovery 문서에 `생성된 REQ 참조` 항목이 이미 있으면 생성된 REQ 목록으로 갱신하고, 없으면 `후속 Discovery 참조` 바로 아래에 추가한다. +- `생성된 REQ 참조`는 생성된 REQ 번호 순서대로 유지하고 중복을 허용하지 않는다. +- source Discovery 문서를 갱신했다면 `마지막 갱신 시각(KST)`도 함께 갱신한다. +- 생성된 REQ가 아직 없으면 `생성된 REQ 참조: 없음`을 유지한다. + +# Execution Procedure + +1. 입력에서 Discovery 경로를 확정한다. +2. Discovery 문서에서 FR/NFR, 범위 경계, 리스크, 성공 기준을 읽는다. +3. `docs/project-structure.md`와 `docs/runtime-flows.md`가 있으면 읽고, REQ에 남겨야 할 구조 제약과 흐름 제약을 추린다. +4. FR/NFR별 REQ 후보 목록을 만든다. +5. baseline 문서 갱신 또는 baseline 갭 보완 요구가 있으면 이를 반영한 REQ 후보를 포함한다. +6. 각 후보에 대해 Type, 제목, slug, Acceptance Criteria를 결정한다. +7. `docs/srs/index.md`의 다음 번호부터 순서대로 REQ 파일을 생성한다. +8. `docs/srs/index.md`를 갱신한다. +9. source Discovery 문서의 `생성된 REQ 참조`와 `마지막 갱신 시각(KST)`를 갱신한다. +10. 생성 결과와 제외한 NFR이 있으면 그 이유를 함께 보고한다. + +# Output Expectations + +- 생성된 REQ 파일 목록 +- 각 REQ의 Type과 핵심 의도 +- baseline 문서 참조 또는 생성/갱신 요구를 어떤 REQ에 반영했는지 +- `docs/srs/index.md` 갱신 결과 +- source Discovery 문서의 `생성된 REQ 참조` 갱신 결과 +- 별도 REQ로 승격하지 않은 NFR 목록과 처리 이유 + +# Common Pitfalls + +1. source Discovery가 아직 `confirmed`가 아닌데 REQ부터 만드는 실수 + - 이 경우 문제 정의와 범위가 흔들릴 수 있으므로 `review-discovery` 또는 `confirm-discovery`를 먼저 거친다. + +2. NFR을 전부 별도 REQ로 승격하는 실수 + - 독립 측정 가능성, backlog 가치, 운영 영향이 약하면 FR 또는 batch 검증 기준에 흡수하는 편이 낫다. + +3. baseline gap을 읽고도 관련 REQ를 만들지 않는 실수 + - source Discovery 범위에 baseline 보완이 포함되어 있으면, 그 갭을 다루는 REQ를 반드시 backlog에 포함한다. + +4. template source와 generated target path를 혼동하는 실수 + - template source는 `stage-pilot/templates/...` logical path를 해석한 결과이고, 생성 대상은 `docs/srs/...`다. + +5. `docs/srs/index.md`를 갱신하지 않고 REQ 파일만 만드는 실수 + - `Next Requirement ID`, `Requirement Register`, `Recent Change Log Summary`를 함께 갱신해야 register가 깨지지 않는다. + +6. source Discovery 역참조를 빼먹는 실수 + - 생성된 REQ 참조와 `마지막 갱신 시각(KST)`를 source Discovery에도 반영해야 추적성이 유지된다. + +# Verification Checklist + +- [ ] source Discovery가 `confirmed` 상태인지 확인했다. +- [ ] Discovery 경로가 유일하게 확정되었고 prefix 충돌이 없다. +- [ ] `docs/project-structure.md`와 `docs/runtime-flows.md` 존재 여부를 확인했다. +- [ ] logical template path를 실제 physical path로 올바르게 해석했다. +- [ ] template source와 generated target path를 혼동하지 않았다. +- [ ] 모든 REQ가 `docs/srs//req-XXX_.md` 형식을 따른다. +- [ ] `docs/srs/index.md`의 `Next Requirement ID`, `Requirement Register`, `Recent Change Log Summary`를 함께 갱신했다. +- [ ] source Discovery의 `생성된 REQ 참조`와 `마지막 갱신 시각(KST)`를 갱신했다. +- [ ] baseline 갭 보완이 source Discovery 범위에 포함되면 해당 REQ를 실제로 생성했다. +- [ ] 별도 REQ로 승격하지 않은 NFR은 이유를 함께 보고했다. \ No newline at end of file diff --git a/skills/draft-req/references/template-path-resolution.md b/skills/draft-req/references/template-path-resolution.md new file mode 100644 index 0000000..1ea6f88 --- /dev/null +++ b/skills/draft-req/references/template-path-resolution.md @@ -0,0 +1,61 @@ +# Template Path Resolution for `draft-req` + +Use this reference when StagePilot is installed in different physical locations across agents or repositories. + +## Core rule + +Do **not** treat one physical path as the canonical template location for every agent. + +Instead: + +- treat `stage-pilot/templates/...` as the stable logical template path +- resolve that logical path against the active workspace or agent installation +- keep template source paths separate from generated target paths under `docs/srs/` + +## Logical template paths + +For `draft-req`, the logical template sources are: + +- `stage-pilot/templates/srs/index.md` +- `stage-pilot/templates/srs/req-template.md` + +These names are portable across Hermes Agent, GitHub Copilot, Claude, and similar environments. The physical path may differ. + +## Physical path resolution order + +When resolving a logical template path, check candidates in this order: + +1. Workspace-local / subtree install: `.stage-pilot/templates/...` +2. Hermes external install: `~/.stage-pilot/templates/...` + +Prefer the current workspace's installed copy over a user-level fallback. + +## Source vs target distinction + +Keep these separate: + +- Template source: `stage-pilot/templates/...` resolved to one physical path above +- Generated targets: `docs/srs/index.md` and `docs/srs//req-XXX_.md` + +Do not describe a generated `docs/srs/...` file as if it were the template source. + +## Ambiguity rule + +If multiple physical candidates exist: + +- prefer the highest-priority path in the resolution order above +- do not silently mix sources from different installs +- report which physical path was used when ambiguity could matter + +## Durable pitfall + +A common portability mistake is to hardcode only one of these: + +- `.stage-pilot/templates/...` +- `~/.stage-pilot/templates/...` + +That works for one install shape but breaks on another machine or agent. The durable pattern is: + +- logical path in the skill +- resolution rule in the workflow +- generated target path documented separately diff --git a/skills/new-discovery/SKILL.md b/skills/new-discovery/SKILL.md new file mode 100644 index 0000000..394120b --- /dev/null +++ b/skills/new-discovery/SKILL.md @@ -0,0 +1,372 @@ +--- +name: new-discovery +description: "Use when: deciding whether to update an existing Discovery document or start a new one, running /new-discovery with or without parameters, drafting docs/discovery/dcy-__.md for a real product/service change, generating Discovery documents, or updating docs/discovery/index.md after baseline initialization is ready." +version: 1.0.1 +author: Justin Ko +license: private +argument-hint: "예: 사용자 로그인 기능 추가 - OAuth2 소셜 로그인, 세션 관리, 로그아웃 처리" +user-invocable: true +metadata: + hermes: + tags: [stage-pilot, discovery, documentation, sdlc, planning] + related_skills: [bootstrap-baseline, review-discovery, confirm-discovery, run-sdlc] +--- + +# Purpose + +This skill decides whether an incoming request should update an existing Discovery document or start a new one, then drafts or updates Discovery documentation based on issues, user requests, meeting notes, or repository state. + +입력이 없으면 현재 Discovery/SRS/batch/release 상태와 baseline 문서 상태를 읽어 새 Discovery가 필요한지 판정한다. baseline 초기화가 아직 끝나지 않은 저장소에서는 먼저 `bootstrap-baseline`으로 `docs/project-structure.md`, `docs/runtime-flows.md`, active index를 준비한 뒤 첫 real Discovery를 생성한다. + +새 Discovery를 생성하기로 판정된 경우 generated output must follow this structure: + +- `docs/discovery/dcy-__.md` +- Update `docs/discovery/index.md` + +# When to use + +다음 상황에서 사용한다. + +- 새로운 SDLC 주기를 시작해야 할 때 +- 기존 SDLC의 요구사항, 범위, 성공 기준을 조정해야 하는데 새 주기 생성이 필요한지 먼저 판정해야 할 때 +- `/new-discovery ...` 또는 파라미터 없는 `/new-discovery`로 Discovery 초안을 만들어야 할 때 +- baseline 초기화가 완료된 뒤 첫 real Discovery를 시작해야 할 때 +- 이슈/요청사항을 `docs/discovery/` 아래 표준 Discovery 문서로 전환해야 할 때 +- Discovery 문서를 템플릿 기반으로 작성하되, 추론 가능한 내용을 실제 문장으로 채워야 할 때 +- `docs/discovery/index.md`에 새 Discovery 링크를 추가해야 할 때 + +# Inputs + +이 skill은 아래 입력을 사용한다. + +- 이슈 식별 정보 + - GitHub issue link 또는 issue 번호 +- 이슈 내용 + - 사용자 입력 원문 또는 이슈 본문 전체 + - 이슈 제목 또는 요청 요약 +- SDLC 문맥 + - 기존 `docs/discovery/` 폴더 구조 및 파일 목록 + - 기존 `docs/srs/`, `docs/batches/`, `docs/releases/` 상태 + - 기존 `docs/discovery/index.md` 내용 + - `docs/project-structure.md`, `docs/runtime-flows.md` 존재 여부 + - 현재 시각 (KST) +- 저장소 문맥 + - 현재 저장소 상태 + - 저장소 현황 탐색 결과 +- 템플릿의 논리 경로 + - `stage-pilot/templates/discovery/discovery.md` + - `stage-pilot/templates/discovery/index.md` + - `stage-pilot/templates/project-structure.md` + - `stage-pilot/templates/runtime-flows.md` + +## Template path resolution + +- 템플릿은 하나의 물리 경로만 고정하지 말고 `stage-pilot/templates/...`를 논리 경로로 취급한다. +- 물리 경로는 다음 순서로 해석한다. + 1. `.stage-pilot/templates/...` + 2. `~/.stage-pilot/templates/...` +- 여러 후보가 동시에 존재하면 가장 우선순위가 높은 한 곳만 사용하고, 서로 다른 설치 위치의 템플릿을 섞지 않는다. +- 템플릿 source path와 생성 target path를 혼동하지 않는다. + - source: `stage-pilot/templates/...` + - target: `docs/discovery/.md`, `docs/discovery/index.md`, 이후 필요 시 참조하는 `docs/project-structure.md`, `docs/runtime-flows.md` + +입력 해석 규칙: +- GitHub issue link 또는 issue 번호가 있으면 해당 이슈의 제목, 본문, 링크를 기준 입력으로 사용한다. +- 사용자 입력 원문과 요청 요약은 이슈 링크가 없거나 보조 설명이 필요할 때 함께 사용한다. +- 파라미터가 없으면 저장소 상태를 먼저 분류한 뒤 추천 Discovery 또는 진행 중인 반복 유지 여부를 판단한다. +- 저장소 현황 탐색 결과는 `# 3. 문제점의 요약` 중 `현재 상태` 작성에 사용한다. + +# Core Rules + +## 0. Baseline 문서 정책 + +- `docs/project-structure.md`와 `docs/runtime-flows.md`는 SDLC의 별도 governance unit가 아니라, 이후 Discovery/REQ/Batch/Release에서 공통으로 참조하는 cross-cutting baseline 문서다. +- baseline 초기화는 첫 Discovery와 분리된 `bootstrap-baseline` 경로를 기본값으로 사용한다. +- baseline 문서와 active index가 모두 없는 저장소에서는 `new-discovery`가 Discovery를 생성하기 전에 `bootstrap-baseline`을 우선 추천하고 중단한다. +- baseline 문서가 이미 있고, Discovery가 구조/런타임 변경을 다룰 때만 해당 Discovery 안에 baseline 문서 갱신 요구를 포함한다. + +## 1. 파라미터 없는 실행 판정 규칙 + +- `/new-discovery`가 파라미터 없이 실행되면 먼저 아래 경로를 점검한다. + - `docs/discovery/` + - `docs/srs/` + - `docs/batches/` + - `docs/releases/` + - `docs/project-structure.md` + - `docs/runtime-flows.md` +- 파라미터 없는 실행은 아래 3가지 저장소 상태 중 하나로 판정한다. + - `no-history`: Discovery와 후속 SDLC 문서가 사실상 없고 첫 반복을 시작해야 하는 상태 + - `active-cycle`: draft/review Discovery, Proposed REQ, 진행 중 batch, 미정리 release처럼 활성 unit가 존재해 파라미터 없는 실행만으로는 다음 반복 추천이 모호한 상태 + - `ready-for-next-iteration`: active unit는 없고 새 반복을 시작할 준비가 된 상태 +- `no-history`이고 baseline 문서 또는 active index가 없으면 `bootstrap-baseline`을 우선 추천하고 새 Discovery를 생성하지 않는다. +- `no-history`이지만 baseline 초기화가 끝나 있으면 첫 real Discovery를 생성한다. +- `active-cycle`이면 새 Discovery를 생성하지 않고 중단한다. 대신 현재 반복을 계속 진행하도록 `run-sdlc` 또는 해당 후속 skill을 추천한다. +- `ready-for-next-iteration`이면 이전 Discovery, SRS, 구현 상태, baseline 문서 차이를 읽고 추천 follow-up Discovery를 생성한다. +- `ready-for-next-iteration`에서 후보 ranking만 먼저 필요하면 `suggest-next-discovery`를 사용하고, 실제 문서 생성이 확정되면 `new-discovery`로 이어 간다. +- baseline 문서가 없고 active unit도 없다면, follow-up Discovery보다 `bootstrap-baseline`을 우선한다. + +## 2. 기존 Discovery 갱신 vs 새 Discovery 생성 판정 규칙 + +- 기존 주기 후보 탐색과 비교는 아래 `확인 절차`를 따른다. +- 후보 문서가 있고 아래 조건을 모두 만족하면 기존 Discovery를 갱신한다. + - 변경이 기존 문제 정의와 핵심 사용자 시나리오를 유지한다. + - FR/NFR 변경이 문구 명확화, 측정 가능성 보강, 범위 경계의 미세 조정, 우선순위 재정렬 수준이다. + - 요구사항 삭제 또는 수정이 기존 REQ, batch planning, batch design, implementation 산출물을 사실상 무효화하지 않는다. + - 대상 Discovery가 아직 초기 정리 단계이거나, 후속 단계 문서가 있어도 변경 영향이 국소적이라 동일 문서 안에서 추적 가능하다. +- 아래 조건 중 하나라도 만족하면 새 Discovery를 생성한다. + - FR/NFR 삭제 또는 수정이 범위, 이해관계자, 핵심 사용자 시나리오, 성공 기준을 실질적으로 바꾼다. + - 기존 하위 단계 문서나 구현 결과를 다시 써야 할 정도로 설계/구현 전제가 깨진다. + - 기존 Discovery가 사실상 Freeze 이후 단계로 진행되어 변경 이력을 같은 문서에 계속 누적하면 책임 경계가 흐려진다. + - 운영 환류, 새로운 이슈, 정책 전환 등으로 후속 반복을 독립 주기로 추적하는 편이 명확하다. +- 기존 Discovery를 더 이상 기준으로 쓰지 않더라도 기존 문서를 삭제하지 않는다. 새 문서를 생성하는 경우 기존 문서는 참조 대상으로 유지하고, 대체 또는 후속 관계를 문서에 남긴다. +- 판정 결과는 반드시 아래 둘 중 하나로 명시한다. + - `기존 Discovery 갱신` + - `새 Discovery 생성` + +### 확인 절차 + +- 입력 원문 또는 추천 Discovery 주제에서 아래 식별 정보를 먼저 추출한다. + - GitHub issue 번호 또는 링크 + - 이슈 제목의 핵심 명사 + - 기능명, 모듈명, 동사, 영향 범위 키워드 +- `docs/discovery/` 아래 기존 문서를 탐색할 때는 `.stage-pilot/` 경로를 제외하고 아래 순서로 후보를 찾는다. + - issue 번호 또는 링크의 정확 일치 + - 이슈 제목과 파일명 slug의 핵심 키워드 일치 + - 기존 Discovery 문서 안의 문제 정의, FR/NFR, 범위 경계 키워드 일치 +- 후보 문서가 하나 이상 발견되면 각 후보마다 아래 항목을 비교한다. + - 같은 문제 정의 또는 같은 기능 묶음을 다루는지 + - 현재 단계가 Discovery, REQ 정제, batch delivery, release 이후 어디까지 진행됐는지 + - 이번 변경이 기존 FR/NFR의 미세 조정인지, 핵심 요구사항 폐기 또는 재정의인지 + - 기존 하위 단계 문서와 구현 산출물을 유지 가능한지, 다시 작성해야 하는지 +- 후보가 없으면 기본 판정은 `새 Discovery 생성`이다. +- 후보가 하나이고 판정 규칙에 명확히 부합하면 해당 후보를 기준으로 자동 판정한다. +- 후보가 여러 개이거나, 하나의 후보라도 `기존 Discovery 갱신`과 `새 Discovery 생성` 사이에서 명확히 갈리지 않으면 임의로 진행하지 않는다. + - 후보 문서 경로 목록과 각 후보의 비교 근거를 사용자에게 보고한다. + - 사용자 확인 전에는 새 폴더 생성이나 기존 문서 갱신을 하지 않는다. +- 최종 판정 결과에는 선택된 대상 문서 경로 또는 새로 만들 Discovery 생성 사유를 반드시 함께 기록한다. + +## 3. Discovery ID 규칙 + +- `docs/discovery/` 아래 기존 문서 중 `dcy-<3자리>_` 패턴의 최대 번호를 찾는다. +- 다음 번호를 3자리 zero-pad로 계산한다. +- 오늘 날짜를 `YYYYMMDD` 형식으로 구한다. +- 사용자 입력 원문, 추천 Discovery 제목, 이슈 본문 전체, GitHub issue link에서 `topic-slug`를 만든다. +- `topic-slug`는 소문자 kebab-case의 형태로 만든다. +- slug 규칙: + - 영문/숫자 외 문자는 `-`로 치환 + - 연속 `-`는 하나로 축약 + - 시작/끝 `-` 제거 + - 결과가 비면 `untitled` 사용 +- 최종 `DISCOVERY_ID`는 `dcy-__` 형식이어야 한다. + +## 4. 문서 생성/갱신 규칙 + +- 판정 결과가 `새 Discovery 생성`이면 새 파일을 `docs/discovery/.md` 경로로 생성한다. +- `새 Discovery 생성`인 경우 `stage-pilot/templates/discovery/discovery.md`에 해당하는 실제 template source를 해석한 뒤 Discovery 문서를 생성한다. +- `docs/discovery/index.md`가 없으면 `stage-pilot/templates/discovery/index.md`에 해당하는 실제 template source를 해석한 뒤 생성한다. +- Discovery가 구조/런타임 baseline 갭을 직접 다루는 경우, `stage-pilot/templates/project-structure.md`와 `stage-pilot/templates/runtime-flows.md`를 이후 산출물의 논리 템플릿 참조로 취급한다. +- `기존 Discovery 갱신`이면 대상 Discovery 문서를 갱신한다. +- 동일 경로가 이미 있으면 덮어쓰지 말고 다음 ID를 재계산한다. +- 불필요한 파일은 만들지 않는다. + +## 5. Discovery 초안 작성 원칙 + +- 입력 문장이 짧더라도 기능 목적, 예상 사용자, 산출물, 성공 조건을 합리적으로 추론해 초안을 작성한다. +- 입력만으로 추론 가능한 내용은 플레이스홀더로 남기지 말고 실제 문장으로 채운다. +- 사용자 승인, 외부 데이터, 정책 결정, 확정 범위 경계처럼 추론만으로 확정하면 안 되는 항목만 플레이스홀더로 남긴다. +- 공란은 허용하지 않는다. +- 합리적 기본안을 제시할 수 있으면 초안 문장으로 채운다. +- 사용자 확인이 반드시 필요하면 관련 본문에는 최소 플레이스홀더를 남기고 `# 10. 사용자 결정 필요 항목 요약`에 정리한다. +- `기존 Discovery 갱신`인 경우 FR/NFR 변경 또는 삭제 이유, 영향 받는 하위 단계, 기존 문서와의 차이를 문서 안에 명시해 변경 근거를 남긴다. +- Discovery가 구조/런타임 baseline 변경을 직접 다루는 경우 `# 4. 이번에 정의할 변경`, `# 5. 요구사항 목록`, `# 9. 파일 처리 결과`에 `docs/project-structure.md`와 `docs/runtime-flows.md` 갱신 요구를 명시한다. + +## 6. 플레이스홀더 유지 규칙 + +- 사용자 승인 전 확정되면 안 되는 정책/범위/산출물 위치/대상 독자 결정은 본문에도 플레이스홀더를 남긴다. +- `# 10. 사용자 결정 필요 항목 요약`의 항목은 반드시 아래 형식 중 하나를 사용한다. + - `{{DECIDE_*}}` + - `{{CONFIRM_*}}` + - `{{DATA_*}}` +- `TBD` 같은 일반 문자열보다 의미가 드러나는 플레이스홀더를 우선 사용한다. + +# Discovery Sections To Fill + +다음 섹션은 자동으로 채워야 한다. + +## `# 1. 계획 상태 요약` + +- `해석` 2줄은 입력 요청 또는 파라미터 없는 실행의 저장소 상태 해석을 바탕으로 직접 작성한다. + +## `# 0. 문서 상태` + +- `새 Discovery 생성`이면 `대체됨`, `후속 주기 참조` 기본값은 `없음`으로 둔다. +- 기존 Discovery를 대체하거나 이어받는 새 Discovery를 만드는 경우, 이전 Discovery 문서에도 해당 참조 필드를 갱신한다. +- `기존 Discovery 갱신`이면 기존 참조 값을 보존하고, 새 관계가 확정된 경우에만 관련 필드를 갱신한다. + +플레이스홀더 치환 규칙: +- `{{DISCOVERY_ID:dcy-<3자리>__}}` -> 계산한 `DISCOVERY_ID` +- `{{SUPERSEDED_BY_DISCOVERY:없음 또는 docs/discovery/.md}}` -> `없음` 또는 대체 Discovery 경로 +- `{{FOLLOW_UP_DISCOVERY_REF:없음 또는 docs/discovery/.md}}` -> `없음` 또는 후속 Discovery 경로 + +## `# 2. 요구사항 판정 결과` + +- 기본값은 `신규`로 둔다. +- 입력만으로 명백한 중복/유사라고 판단 가능한 경우에만 다르게 쓴다. +- 근거 2줄도 직접 작성한다. + +## `# 3. 문제점의 요약` + +- 현재 상태, 기대 상태, 이해관계자, 주요 사용자 시나리오를 입력 기반으로 채운다. +- 특히 `현재 상태`는 저장소 탐색 결과와 baseline 문서 존재 여부를 반영해야 한다. + +## `# 4. 이번에 정의할 변경` + +- 핵심 변경 항목 1~3개를 직접 작성한다. +- Discovery가 구조 또는 runtime baseline 갭을 다루면 baseline 문서 갱신을 핵심 변경 항목에 포함한다. +- `영향 범위`의 코드/문서/운영 영향도 추론해 채운다. + +## `# 5. 요구사항 목록` + +- FR 2개 이상, NFR 1개 이상을 직접 작성한다. +- Discovery가 구조 또는 runtime baseline 갭을 다루는 경우, `docs/project-structure.md`와 `docs/runtime-flows.md` 갱신 요구를 FR에 포함한다. +- In Scope/Out of Scope는 초안 수준으로 제안한다. +- 사용자 승인 없이는 확정할 수 없는 범위 경계는 플레이스홀더로 남긴다. + +## `# 6. 리스크/가정 목록` + +- 리스크 2개 이상, 가정 2개 이상을 직접 작성한다. +- 오픈 질문은 정말 결정이 필요한 경우에만 남긴다. + +## `# 7. 초기 성공 기준` + +- 성공 기준 2개 이상을 작성한다. +- Discovery가 구조 또는 runtime baseline 변경을 다루는 경우 baseline 문서와 이후 산출물 참조 가능성을 성공 기준에 연결한다. +- 각 성공 기준에 대해 측정 방식과 데이터 출처 초안을 작성한다. + +## `# 9. 파일 처리 결과` + +- 생성 결과와 최소 참조 문서를 채운다. +- Discovery가 구조 또는 runtime baseline 갭을 다루면 `stage-pilot/templates/project-structure.md`와 `stage-pilot/templates/runtime-flows.md`를 참조 문서에 포함한다. +- 참조 문서가 없으면 `없음`이라고 쓴다. + +## `# 10. 사용자 결정 필요 항목 요약` + +- 자동 추론으로 확정할 수 없는 항목만 `DECIDE`, `CONFIRM`, `DATA`에 넣는다. +- 이 섹션 항목은 설명문이 아니라 플레이스홀더 형식으로 남긴다. + +## `# 11. Discovery Freeze` + +- `Handoff Decision` 기본값은 `보류`다. +- `Handoff Rationale`에는 현재 상태를 짧게 적는다. + - 예: `초기 Discovery 초안 생성 완료, REQ 초안 작성 전 사용자 확인 필요` + +# Repository Exploration + +Discovery 문서의 `# 3. 문제점의 요약` 중 `현재 상태`를 채우기 위해 저장소 현황 탐색을 수행한다. + +1. 이슈 원문 또는 추천 주제에서 핵심 키워드(기능명, 모듈명, 동사)를 추출한다. +2. 키워드를 기반으로 저장소에서 관련 파일, 디렉터리, 함수, 기존 문서를 탐색한다. +3. 탐색 범위는 아래와 같다. + - 소스 파일: `.ts`, `.js`, `.py`, `.go`, `.java` 등 + - 설정 파일 + - 기존 `docs/` 문서 +4. 아래 항목은 제외한다. + - 테스트 파일 + - 의존성 잠금 파일 (`package-lock.json`, `.lock`) +5. 탐색 결과에서 아래 내용을 추출한다. + - 관련 파일 경로 목록 (최대 10개) + - 현재 구현 방식 요약 (함수명, 클래스명, 주요 로직 1~3줄) + - 현재 상태에서 이슈 원문이 지적하는 문제가 실제로 존재하는지 여부 + - `docs/project-structure.md`, `docs/runtime-flows.md` 존재 여부와 최신성 판단에 필요한 단서 +6. 관련 파일을 찾지 못하면 `관련 파일 없음 — 신규 기능으로 추정`으로 기록한다. +7. 탐색 결과는 `# 3. 문제점의 요약`의 `현재 상태` 기술에만 반영한다. + +# Execution Procedure + +다음 순서대로 작업한다. + +1. 입력 원문이 있으면 이슈명을 한 문장으로 정리하고, 없으면 저장소 상태를 먼저 분류한다. +2. 파라미터 없는 실행이면 discovery/srs/batches/releases/baseline 문서 상태를 읽어 `no-history`, `active-cycle`, `ready-for-next-iteration` 중 하나로 판정한다. +3. 입력 원문이 없고 `active-cycle`이면 새 Discovery 자동 생성을 중단하고 현재 반복을 계속 진행해야 하는 이유와 추천 skill을 보고한다. +4. `no-history`이고 baseline 문서 또는 active index가 비어 있으면 `bootstrap-baseline`을 우선 추천하고 Discovery 생성을 중단한다. +5. `no-history`이지만 baseline 초기화가 끝나 있으면 첫 real Discovery 생성 대상으로 확정한다. +6. 입력 원문이 없고 `ready-for-next-iteration`이면 이전 Discovery, SRS, 구현 상태, baseline 문서 상태를 바탕으로 추천 follow-up Discovery 주제를 정한다. +7. 입력 원문, 이슈 링크, 신규 요구 요약처럼 명시적 신규 입력이 있으면 `active-cycle` 여부와 무관하게 위 `확인 절차`에 따라 issue 번호/링크, 제목 키워드, 기능명, 범위 키워드를 추출하고 기존 후보 주기를 찾는다. +8. 입력 원문이 없고 추천 Discovery 주제가 정해졌으면 위 `확인 절차`에 따라 issue 번호/링크, 제목 키워드, 기능명, 범위 키워드를 추출하고 기존 후보 주기를 찾는다. +9. 후보 비교 결과를 바탕으로 `기존 Discovery 갱신` 또는 `새 Discovery 생성`으로 판정한다. +10. `active-cycle` 상태에서 명시적 신규 입력으로 새 Discovery를 진행하는 경우, 기존 active unit와의 범위 충돌 여부와 병렬 진행 사유를 함께 기록한다. +11. 판정이 애매하면 사용자 확인 전까지 자동 생성 또는 자동 갱신을 중단하고 후보와 근거를 보고한다. +12. 저장소 현황 탐색을 수행한다. +13. 판정 결과가 `새 Discovery 생성`이면 기존 `docs/discovery/` 경로를 다시 스캔해 다음 번호를 계산한다. +14. `새 Discovery 생성`이면 날짜와 slug를 조합해 `DISCOVERY_ID`를 만든다. +15. `새 Discovery 생성`이면 `docs/discovery/.md` 파일을 생성한다. +16. `기존 Discovery 갱신`이면 대상 Discovery 문서를 갱신한다. +17. 아래 플레이스홀더를 우선 치환한다. + - `{{DOC_STATUS:draft|review|confirmed}}` -> `draft` + - `{{DISCOVERY_ID:dcy-<3자리>__}}` -> 계산한 `DISCOVERY_ID` + - `{{ISSUE_NAME:짧은 한글 또는 영문 이슈명}}` -> 입력 원문 또는 요약 이슈명 + - `{{ISSUE_NAME}}` -> 입력 원문 또는 요약 이슈명 + - `{{CREATED_AT_KST:TBD}}` -> 현재 시각(KST) + - `{{CREATED_AT_KST}}` -> 현재 시각(KST) + - `{{UPDATED_AT_KST:TBD}}` -> 현재 시각(KST) + - `{{UPDATED_AT_KST}}` -> 현재 시각(KST) + - `{{SUPERSEDED_BY_DISCOVERY:없음 또는 docs/discovery/.md}}` -> `없음` 또는 대체 Discovery 경로 + - `{{FOLLOW_UP_DISCOVERY_REF:없음 또는 docs/discovery/.md}}` -> `없음` 또는 후속 Discovery 경로 + - `{{OUTPUT_PATH}}` -> 생성 파일 경로 + - `{{FILE_RESULT:생성|갱신|미생성}}` -> `생성` +18. Discovery가 구조 또는 runtime baseline 갭을 직접 다루면 `docs/project-structure.md`와 `docs/runtime-flows.md` 갱신 요구를 핵심 변경과 FR에 반영한다. +19. `기존 Discovery 갱신`인 경우 `{{OUTPUT_PATH}}`와 `{{FILE_RESULT}}`는 갱신 대상 파일과 `갱신`으로 해석한다. +20. `새 Discovery 생성`이면서 기존 Discovery를 대체하거나 이어받는 경우, 기존 Discovery 문서에 `대체됨` 또는 `후속 주기 참조` 필드를 새 문서 경로로 갱신한다. +21. Discovery 문서에서는 추론 가능한 플레이스홀더를 적극적으로 실제 내용으로 치환한다. +22. 추론 불가능하거나 사용자 확인이 필요한 항목만 플레이스홀더로 남긴다. +23. `새 Discovery 생성`인 경우에만 `docs/discovery/index.md` 전역 인덱스에 새 행을 추가한다. +24. `기존 Discovery 갱신`인 경우 전역 인덱스에 새 행을 추가하지 않는다. + +# Global Index Rules + +- `docs/discovery/index.md`가 없으면 아래 골격으로 새로 만든다. +- `## Discovery 목록`에는 새 행을 추가한다. +- 형식은 아래와 같다. + +`| | | | [Discovery](./.md) | draft |` + +- 테이블 구조 자체는 바꾸지 않는다. +- `기존 Discovery 갱신`으로 판정된 경우 `docs/discovery/index.md`에 새 행을 추가하지 않는다. +- 같은 `DISCOVERY_ID`가 이미 있으면 중복 추가하지 않는다. + +# Output Expectations + +작업이 끝나면 아래 내용을 보고할 수 있어야 한다. + +- 판정 결과 (`기존 Discovery 갱신` 또는 `새 Discovery 생성`) +- 파라미터 없는 실행이면 저장소 상태 분류 (`no-history` | `active-cycle` | `ready-for-next-iteration`) +- `active-cycle` 상태였다면 자동 중단 사유 또는 병렬 Discovery 허용 사유 +- 대상 `DISCOVERY_ID` +- 확인에 사용한 후보 Discovery 목록과 각 후보의 비교 근거 +- 새 Discovery인 경우 생성한 파일 경로 +- 기존 Discovery 갱신인 경우 갱신한 파일 경로 +- 기존 Discovery와의 대체 또는 후속 참조 갱신 결과 +- `docs/discovery/index.md` 갱신 결과 또는 미갱신 사유 +- `docs/project-structure.md`, `docs/runtime-flows.md` baseline 상태와 Discovery 반영 결과 +- 저장소 현황 탐색 결과 +- 자동으로 채운 Discovery 핵심 섹션 요약 +- 사용자 확인이 필요해 플레이스홀더로 남긴 항목 요약 + +# Validation + +완료 전에 아래를 확인한다. + +- `새 Discovery 생성`이면 생성 경로가 `docs/discovery/.md` 형식을 만족하는지 +- `새 Discovery 생성`이면 Discovery 문서가 생성되었는지 +- `새 Discovery 생성`이면 `docs/discovery/index.md`가 신규 생성 또는 올바르게 갱신되었는지 +- Discovery가 구조 또는 runtime baseline 갭을 다루면 `docs/project-structure.md`, `docs/runtime-flows.md` 갱신 요구가 문서 본문에 포함됐는지 +- `기존 Discovery 갱신`이면 대상 Discovery 문서만 갱신되었는지 +- `기존 Discovery 갱신`이면 새 `DISCOVERY_ID`와 불필요한 파일이 생성되지 않았는지 +- 기존 Discovery 후보 탐색 결과와 판정 근거가 보고 가능한 형태로 정리되었는지 +- 파라미터 없는 실행에서 `active-cycle` 상태라면 사용자 확인이나 명시적 신규 입력 없이 새 Discovery를 자동 생성하지 않았는지 +- `active-cycle` 상태에서 명시적 신규 입력으로 새 Discovery를 생성한 경우, 기존 active unit와의 범위 충돌 여부와 판정 근거가 함께 정리됐는지 +- 판정이 애매한 경우 사용자 확인 없이 새 폴더 생성이나 기존 문서 갱신을 진행하지 않았는지 +- `대체됨`, `후속 주기 참조` 필드가 `없음` 또는 유효한 Discovery 경로로 채워졌는지 +- 새 Discovery가 기존 Discovery의 대체 또는 후속 반복이라면 기존 Discovery의 참조 필드가 함께 갱신되었는지 +- 추론 가능한 섹션은 실제 문장으로 채워졌는지 +- 사용자 확인이 필요한 항목만 플레이스홀더로 남았는지 \ No newline at end of file diff --git a/skills/review-discovery/SKILL.md b/skills/review-discovery/SKILL.md new file mode 100644 index 0000000..5b6b4e0 --- /dev/null +++ b/skills/review-discovery/SKILL.md @@ -0,0 +1,218 @@ +--- +name: review-discovery +description: "Use when: reviewing an existing Discovery document for missing content, additional OQ candidates, internal consistency, duplication, unnecessary content, placeholder residue, and REQ handoff readiness without changing lifecycle state." +version: 0.8.0 +author: Justin Ko +license: private +argument-hint: "예: dcy-001 또는 docs/discovery/dcy-001_20260424_topic.md" +user-invocable: true +metadata: + hermes: + tags: [stage-pilot, discovery, review, quality, sdlc] + related_skills: [new-discovery, confirm-discovery, draft-req] +--- + +# Purpose + +This skill reviews an existing Discovery document as a non-gating quality check before `confirm-discovery` or before human review. It looks for missing content, candidate open questions, internal inconsistencies, duplicated statements, unnecessary text, placeholder residue, and weak handoff readiness. + +이 skill은 별도 lifecycle stage를 만들지 않는다. 기본 목적은 Discovery 품질을 점검하고 수정 우선순위를 정리하는 것이며, 문서 상태 전환이나 index 갱신은 수행하지 않는다. + +# When to use + +- `/review-discovery dcy-xyz` 형태로 Discovery 문서를 먼저 점검하고 싶을 때 +- `confirm-discovery` 전에 누락, 중복, OQ 후보, 표현 불일치를 빠르게 정리하고 싶을 때 +- Discovery 문서가 길어지면서 같은 내용이 여러 섹션에 흩어졌는지 확인해야 할 때 +- 템플릿 잔여 문구, 불필요한 상세 구현 설명, 근거 없는 확정 표현이 남아 있는지 보고 싶을 때 +- 사람이 수정할 항목과 AI가 바로 정리할 수 있는 항목을 분리해서 보고 싶을 때 + +# Inputs + +- Discovery 식별자 + - `dcy-001` 같은 prefix + - `dcy-001_20260424_topic-slug` 같은 전체 ID + - `docs/discovery/.md` 같은 직접 경로 +- 대상 Discovery 문서 본문 +- 필요 시 관련 저장소 문맥 + - 현재 저장소의 관련 파일, 문서, 설정 + - `docs/discovery/index.md` + - `docs/project-structure.md`, `docs/runtime-flows.md` +- 현재 시각 (KST) + +# Core Rules + +## 1. 역할 경계 + +- 이 skill은 review helper이며, active SDLC 단계로 취급하지 않는다. +- 아래 항목은 수정하지 않는다. + - `# 0. 문서 상태`의 `상태` + - `# 11. Discovery Freeze`의 `Handoff Decision`, `Ready for REQ Drafting`, `Confirmed By`, `Confirmed At (KST)` + - `docs/discovery/index.md` +- 이 skill은 REQ 생성, 승인, 상태 승격을 하지 않는다. +- 사실 관계를 바꾸는 추정성 수정은 하지 않는다. 근거 없는 항목은 finding 또는 OQ 후보로 남긴다. +- 사용자가 단순 검토만 요청한 경우 결과는 findings-first로 보고한다. +- 사용자가 `정리`, `수정`, `보강`, `반영`까지 명시한 경우에만 의미 변화가 없는 경미한 편집을 수행할 수 있다. + +## 2. 기본 검토 축 + +반드시 아래 항목을 검토한다. + +### 2-1. 누락 항목 + +- 템플릿 기준으로 필요한 섹션과 핵심 내용이 빠지지 않았는지 확인한다. +- 빈 bullet, `TBD`, `{{...}}`, `필요 없으면 삭제` 잔여 문구를 누락 또는 미정리 항목으로 본다. +- 아래 핵심 정보가 약하거나 비어 있으면 finding으로 기록한다. + - 문제의 현재 상태와 기대 상태 + - 핵심 변경 항목과 영향 범위 + - FR/NFR와 범위 경계 + - 리스크, 가정, 성공 기준 + - REQ handoff 전 확인 기준 +- 단, `# 2. 요구사항 판정 결과`가 `완전 중복`이면 템플릿 가이드에 따라 이후 섹션 축약을 허용한다. 이 경우 `# 9. 파일 처리 결과`까지의 중복 판정 근거와 참조만 충분한지 본다. + +### 2-2. 추가 OQ 후보 + +- 아래 조건이면 새 OQ 후보를 제안한다. + - 범위 경계가 모호해 In Scope/Out of Scope가 흔들리는 경우 + - 성공 기준은 있는데 측정 방식이나 데이터 출처가 없는 경우 + - 리스크 완화 책임자나 판단 주체가 비어 있는 경우 + - 외부 정책, 외부 시스템, 운영 절차, 승인자 결정이 필요한데 본문에 근거가 없는 경우 + - 서로 다른 섹션이 충돌해서 사람 판단 없이는 확정할 수 없는 경우 +- 이미 다른 섹션에서 답이 있는 질문은 OQ로 늘리지 말고, answered cleanup 대상으로 분류한다. +- 사소한 문장 다듬기나 서식 문제는 OQ로 만들지 않는다. + +### 2-3. 문서 일관성 + +- 이슈명, 핵심 변경, FR/NFR, 범위 경계, 성공 기준, Freeze rationale 사이의 용어와 범위가 일치하는지 확인한다. +- `# 4. 이번에 정의할 변경`, `# 5. 요구사항 목록`, `# 6. 리스크/가정 목록`, `# 7. 초기 성공 기준`, `# 8. REQ로 넘기기 전 확인 체크`, `# 10. 사용자 결정 필요 항목 요약`, `# 11. Discovery Freeze`의 연결 관계를 본다. +- `Open` OQ가 남아 있는데 Freeze가 사실상 진행 가능처럼 읽히는지 확인한다. +- 문서 안에서 같은 개념을 서로 다른 이름으로 부르는 경우 정규화 필요 finding으로 기록한다. + +### 2-4. 중복 제거 대상 + +- 같은 사실이나 요구를 여러 섹션에서 반복하는지 찾는다. +- 중복은 아래 세 종류로 분류한다. + - 완전 중복: 문장만 다르지만 의미가 같은 경우 + - 부분 중복: 핵심은 같고 세부만 다른 경우 + - 잘못된 재기술: 상위 요약과 하위 상세가 서로 다른 말을 하는 경우 +- 중복이 있으면 어떤 섹션을 source of truth로 둘지 함께 제안한다. + +### 2-5. 불필요한 내용 + +- Discovery 단계에 필요 없는 구현 디테일, 과도한 알고리즘 설계, 배포 절차 상세, 이미 결정된 이력 서술을 찾는다. +- 현재 반복의 문제 정의와 성공 기준에 기여하지 않는 배경 설명은 삭제 또는 압축 후보로 본다. +- 템플릿 가이드 문구, 예시 텍스트, 삭제 예정 placeholder 설명이 본문에 남아 있으면 제거 대상으로 본다. + +## 3. 추가 검토 항목 + +사용자 요청 외에도 아래를 함께 본다. + +### 3-1. 근거와 사실 일치성 + +- 문서가 현재 저장소 상태를 서술하는 경우, 그 주장에 직접 관련된 파일이나 문서와 모순이 없는지 확인한다. +- 저장소 확인은 문서의 핵심 주장과 직접 연결된 범위만 탐색한다. 구현 전체를 감사하지는 않는다. +- 저장소 근거와 충돌하는 서술은 높은 우선순위 finding으로 기록한다. + +### 3-2. 실행 가능성과 측정 가능성 + +- FR/NFR, 성공 기준, 체크 항목이 다음 단계에서 실제 REQ/검증 항목으로 이어질 정도로 구체적인지 본다. +- `개선한다`, `지원한다`, `정리한다`처럼 모호한 동사만 있고 판정 기준이 없으면 구체화 필요 finding으로 기록한다. +- 성공 기준은 가능한 한 측정 방식과 데이터 출처에 연결되어야 한다. + +### 3-3. 플레이스홀더 위생 + +- 사용자 결정이 필요한 placeholder와 단순 미정리 placeholder를 구분한다. +- 같은 의미의 placeholder가 본문과 `# 10. 사용자 결정 필요 항목 요약`에 중복으로 남아 있으면 정리 대상으로 본다. +- 이미 문서 근거로 해소 가능한 placeholder는 unresolved로 방치하지 않는다. + +## 4. 허용되는 편집 범위 + +사용자가 수정까지 요청한 경우에만 아래 경미한 편집을 허용한다. + +- 중복 문장 삭제 또는 압축 +- 템플릿 잔여 문구 제거 +- 번호, 체크리스트, 링크, 용어 표기 일관성 정리 +- 답이 이미 문서 안에 있는 OQ를 본문 반영 후 resolved cleanup으로 정리 +- 의미 변화 없는 섹션 재배치 +- 실제 내용이 바뀌었을 때 `마지막 갱신 시각(KST)` 갱신 + +아래는 이 skill로 수정하지 않는다. + +- 새 요구사항 생성 +- 범위 재정의 +- 정책 확정 +- 승인자 지정 +- 상태 전환과 index 갱신 + +# Review Heuristics By Section + +섹션별로 최소한 아래를 점검한다. + +- `# 0. 문서 상태` + - 문서 ID, 이슈명, 시각 정보, 참조 필드가 자기모순 없는지 확인한다. +- `# 1. 계획 상태 요약` + - 현재 작성 배경과 이번 Discovery의 핵심 판단 포인트가 2줄 요약으로 드러나는지 본다. +- `# 2. 요구사항 판정 결과` + - 신규/유사/중복 판정과 근거가 실제 본문 범위와 맞는지 본다. +- `# 3. 문제점의 요약` + - 현재 상태, 기대 상태, 이해관계자, 시나리오가 왜 이 Discovery가 필요한지를 설명하는지 본다. +- `# 4. 이번에 정의할 변경` + - 변경 항목과 영향 범위가 `# 5` 요구사항과 연결되는지 본다. +- `# 5. 요구사항 목록` + - FR/NFR가 구분되어 있고 범위 경계가 충돌하지 않는지 본다. +- `# 6. 리스크/가정 목록` + - 리스크, 가정, OQ가 각각 역할에 맞게 쓰였는지 본다. +- `# 7. 초기 성공 기준` + - 측정 대상, 방식, 데이터 출처가 요구사항과 연결되는지 본다. +- `# 8. REQ로 넘기기 전 확인 체크` + - 체크 항목이 선언만 있고 판단 기준이 없는지 본다. +- `# 9. 파일 처리 결과` + - 참조 문서가 실제 검토 근거를 반영하는지 본다. +- `# 10. 사용자 결정 필요 항목 요약` + - DECIDE, CONFIRM, DATA가 실제 unresolved 항목과 맞는지 본다. +- `# 11. Discovery Freeze` + - review 결과와 무관하게 상태 전환 없이, 본문과 논리적 충돌만 점검한다. + +# Execution Procedure + +1. 입력에서 Discovery 문서 경로를 확정한다. +2. 대상 Discovery 문서를 읽고, 필요 시 `docs/discovery/index.md`와 직접 관련된 저장소 문맥을 읽는다. +3. 문서를 `중복 판정으로 축약 가능한 문서`인지 `정상 Discovery 검토 대상`인지 먼저 분류한다. +4. 위 `기본 검토 축`과 `추가 검토 항목` 기준으로 findings를 수집한다. +5. findings를 아래 우선순위로 분류한다. + - `blocking`: REQ handoff 또는 사실 관계에 직접 문제를 만드는 항목 + - `major`: 지금 고치는 편이 좋은 구조/일관성/측정 가능성 문제 + - `minor`: 표현, 중복, 서식, 경미한 cleanup 문제 +6. 추가 OQ 후보가 있으면 기존 OQ와 중복 여부를 제거한 뒤 후보 목록으로 정리한다. +7. 사용자가 수정까지 요청한 경우에만 `허용되는 편집 범위` 안에서 경미한 cleanup을 수행한다. +8. 편집을 수행한 경우 `마지막 갱신 시각(KST)`만 갱신하고, 상태 전환 없이 종료한다. +9. 최종적으로 findings, OQ 후보, auto-fix 결과, 다음 추천 행동을 보고한다. + +# Output Expectations + +작업이 끝나면 아래 내용을 보고할 수 있어야 한다. + +- 대상 `DISCOVERY_ID`와 문서 경로 +- 전체 판정 + - `양호, 바로 confirm-discovery 검토 가능` + - `경미한 정리 필요` + - `추가 보강 필요` + - `문서 구조 또는 사실 관계 재정비 필요` +- severity별 findings 목록 +- 새 OQ 후보 목록 +- 중복/불필요 내용 정리 제안 +- 자동 정리한 항목 목록(수정 요청이 있었을 때만) +- 다음 추천 행동 + - `confirm-discovery` 진행 + - Discovery 문서 추가 수정 후 재검토 + - 범위가 바뀌어 `new-discovery` 또는 `change-req` 판단 필요 + +# Validation + +완료 전에 아래를 확인한다. + +- 대상 경로가 실제 Discovery 문서를 가리키는지 확인한다. +- `완전 중복` 예외를 제외하고, 누락 판단이 템플릿 구조와 충돌하지 않는지 확인한다. +- 제안한 OQ가 이미 본문이나 다른 섹션에서 답을 가진 항목이 아닌지 확인한다. +- 같은 finding을 누락/중복/불필요 항목에 중복 보고하지 않았는지 확인한다. +- 수정이 있었다면 상태 전환 값과 `docs/discovery/index.md`가 바뀌지 않았는지 확인한다. +- 저장소 사실 관계와의 충돌을 지적했다면 최소 한 개 이상의 직접 근거가 있는지 확인한다. \ No newline at end of file diff --git a/skills/run-batch-delivery/SKILL.md b/skills/run-batch-delivery/SKILL.md new file mode 100644 index 0000000..db06b0e --- /dev/null +++ b/skills/run-batch-delivery/SKILL.md @@ -0,0 +1,162 @@ +--- +name: run-batch-delivery +description: "Use when: orchestrating the full delivery chain for an existing batch, running /run-batch-delivery with a BAT ID, resuming an in-progress batch from the first incomplete delivery stage, or chaining planning, design, implementation, verification drafting, verification approval, and REQ implementation sync for one selected batch." +version: 0.7.0 +author: Justin Ko +license: private +argument-hint: "예: bat-002 또는 docs/batches/bat-002_20260522_visioncfg-32-contract-migration" +user-invocable: true +metadata: + hermes: + tags: [stage-pilot, batch, delivery, orchestration, execution, sdlc] + related_skills: [draft-batch-planning, draft-batch-design, run-batch-implementation, draft-batch-verification, confirm-batch-verification, confirm-req-implemented, draft-release] +--- + +# Purpose + +This skill orchestrates the Delivery phase for one already-created batch by chaining the batch-level skills in order until the batch reaches `release-candidate` and its included REQs are synchronized to `Implemented` where evidence exists. + +이 skill은 새 batch를 만들지 않는다. 이미 존재하는 `docs/batches//`를 기준으로 현재 delivery 진행 상태를 읽고, 아래 6단계를 순서대로 이어 붙이는 orchestration entrypoint다. + +1. `draft-batch-planning` +2. `draft-batch-design` +3. `run-batch-implementation` +4. `draft-batch-verification` +5. `confirm-batch-verification` +6. `confirm-req-implemented` + +# When to use + +- `/run-batch-delivery bat-002`처럼 특정 batch를 delivery phase 끝까지 밀고 싶을 때 +- 이미 `draft-batch`까지 끝난 batch를 planning/design/implementation/verification/REQ 상태 동기화까지 이어서 처리할 때 +- 기존 batch가 어느 delivery 단계에서 멈췄는지 확인하고, 첫 incomplete stage부터 재개하고 싶을 때 +- 사람이 REQ 묶음을 이미 선택했고 batch도 생성했으므로, 이제 batch 내부 delivery chain만 집중 실행해야 할 때 + +## Do not use when + +- 아직 batch가 생성되지 않았고 포함 REQ 집합 선택도 끝나지 않았을 때 (`suggest-batch-reqs`, `draft-batch`가 더 적절함) +- 단순히 batch의 planning만 채우거나 design만 보강하려는 단일 단계 작업일 때 (해당 개별 skill이 더 적절함) +- verification 승인 이후 release 문서까지 자동으로 이어 가고 싶을 때 (`draft-release`는 별도 단계로 남겨 둔다) +- batch가 아니라 저장소 전체의 다음 SDLC 단계를 판단하려는 상황일 때 (`run-sdlc`가 더 적절함) + +# Inputs + +- `BAT-ID` prefix 또는 batch 경로 +- `docs/batches//index.md` +- `docs/batches//planning.md` +- `docs/batches//design.md` (존재하는 경우) +- `docs/batches//implementation.md` (존재하는 경우) +- `docs/batches//verification.md` (존재하는 경우) +- 포함된 REQ 문서들 +- `docs/batches/index.md` +- `docs/srs/index.md` + +# Core Rules + +## 1. 배치 단위 orchestration + +- 이 skill은 정확히 하나의 batch를 대상으로 한다. +- 입력 batch가 유일하게 확정되지 않으면 진행하지 않는다. +- batch 밖의 다른 REQ나 다른 batch를 임의로 끌어오지 않는다. + +## 2. 6단계 delivery chain 유지 + +- 기본 chain은 아래 순서를 따른다. + 1. `draft-batch-planning` + 2. `draft-batch-design` + 3. `run-batch-implementation` + 4. `draft-batch-verification` + 5. `confirm-batch-verification` + 6. `confirm-req-implemented` +- 앞 단계 결과가 다음 단계 입력 전제를 만족하지 않으면 그 지점에서 멈추고 blocker를 보고한다. +- `confirm-batch-verification`과 `confirm-req-implemented`는 승인 게이트이므로 evidence 부족 상태를 억지로 통과시키지 않는다. + +## 3. design 생략 규칙 + +- batch profile이 `batch-lite`이고 planning의 `Design Gate`가 `no`이며 구조/인터페이스/런타임 영향이 실제로 없을 때만 `draft-batch-design`을 생략할 수 있다. +- 그 외에는 design 단계를 delivery chain에 포함한다. +- 생략 여부는 planning 문서와 batch index 근거로 설명되어야 한다. + +## 4. 중복 실행보다 불완전 단계 우선 + +- 이미 충분히 채워진 단계는 다시 덮어쓰는 것이 목적이 아니다. +- 각 단계는 먼저 현재 batch 문서를 읽고, 실제로 비어 있거나 draft 수준인 stage부터 시작한다. +- 다만 앞 단계 산출물이 뒤 단계 전제를 만족하지 못하면 필요한 범위 내에서 보강할 수 있다. + +## 5. 사람 결정과 승인 게이트 존중 + +- verification evidence가 부족하거나 blocker가 있으면 `confirm-batch-verification`에서 멈춘다. +- REQ acceptance criteria와 evidence 연결이 불충분하면 `confirm-req-implemented`에서 멈춘다. +- release 문서 생성과 release 승인까지 자동으로 넘기지 않는다. 이 skill의 종료점은 delivery phase 완료 직전 또는 완료 직후 상태 보고다. + +## 6. child skill 규칙 상속 + +- 이 skill은 하위 skill들을 대체하지 않는다. +- 각 단계에서는 해당 skill의 prerequisite, validation, blocker 규칙을 그대로 따른다. +- 특히 baseline 문서(`docs/project-structure.md`, `docs/runtime-flows.md`) 영향 여부는 design/verification 단계 child skill 규칙을 따라 점검한다. + +# Execution Procedure + +1. 입력에서 `BAT-ID` 또는 batch 경로를 유일하게 확정한다. +2. batch `index.md`, `planning.md`, `design.md`, `implementation.md`, `verification.md`, 포함 REQ, `docs/batches/index.md`, `docs/srs/index.md`를 읽어 현재 delivery 상태를 요약한다. +3. 아래 기준으로 첫 incomplete stage를 판정한다. + - `planning.md`가 비어 있거나 draft 수준이면 `draft-batch-planning` + - design이 필요하지만 `design.md`가 비어 있거나 부족하면 `draft-batch-design` + - 구현 로그/변경/검증 기록이 부족하면 `run-batch-implementation` + - verification mapping/evidence 초안이 부족하면 `draft-batch-verification` + - verification evidence는 있으나 batch가 아직 `release-candidate`가 아니면 `confirm-batch-verification` + - batch가 `release-candidate` 또는 `released`인데 포함 REQ 중 `Approved`가 남아 있으면 `confirm-req-implemented` +4. 첫 incomplete stage부터 시작해, blocker가 생길 때까지 다음 stage로 순차 진행한다. +5. `draft-batch-planning`이 실행되면 범위, dependency, milestone, Design Gate가 채워졌는지 확인한다. +6. `draft-batch-design`이 필요하면 architecture summary, changed areas, key decisions, architecture impact가 채워졌는지 확인한다. +7. `run-batch-implementation` 후에는 구현 변경, execution log, validation, remaining risks가 기록됐는지 확인한다. +8. `draft-batch-verification` 후에는 REQ acceptance criteria와 evidence mapping, blocking issues, baseline 동기화 evidence가 필요한 경우 그 항목이 채워졌는지 확인한다. +9. `confirm-batch-verification` 성공 시 batch가 `release-candidate`로 전환됐는지 확인한다. 실패 시 blocker를 보고하고 멈춘다. +10. `confirm-req-implemented`를 실행해 포함 REQ 중 evidence가 충분한 항목만 `Implemented`로 동기화한다. 부족한 항목은 blocker와 함께 남긴다. +11. delivery chain 종료 후 결과를 아래 중 하나로 보고한다. + - `Stopped at ` + blocker + - `Delivery chain complete through REQ sync` +12. delivery chain이 성공적으로 끝났으면 다음 단계로 `draft-release`를 추천하되, 자동 실행하지는 않는다. + +# Output Expectations + +- 대상 batch 경로 +- 시작 시점 batch 상태 요약 +- 실제로 실행한 stage 목록 +- 생략한 stage와 생략 근거 (`batch-lite` design 생략 등) +- 각 stage 결과 요약 +- 중단 지점과 blocker 또는 완료 결과 +- batch 최종 상태 (`draft` | `in-delivery` | `release-candidate` 등) +- 포함 REQ 중 `Implemented` 전환 성공 목록 / 보류 목록 +- 다음 권장 단계 (`draft-release` 또는 blocker 해소) + +# Common Pitfalls + +1. `draft-batch` 전 batch를 바로 delivery chain에 넣는 실수 + - 이 skill은 이미 생성된 batch가 전제다. REQ 묶음 선택이나 batch 생성은 먼저 끝나 있어야 한다. + +2. `draft-batch-verification`을 건너뛰고 바로 `confirm-batch-verification`으로 가는 실수 + - verification 초안과 evidence mapping이 먼저 있어야 approval gate를 제대로 통과시킬 수 있다. + +3. `batch-lite`라는 이유만으로 design을 자동 생략하는 실수 + - planning의 `Design Gate`가 `no`여야 하고 실제 구조 영향도 없어야 한다. + +4. verification blocker가 있는데도 REQ를 `Implemented`로 올리는 실수 + - `confirm-req-implemented`는 batch verification evidence를 근거로 해야 하므로 blocker가 남아 있으면 보류해야 한다. + +5. child skill이 이미 만든 문서를 읽지 않고 매번 처음부터 다시 쓰는 실수 + - 현재 batch 문서를 먼저 읽고, 첫 incomplete stage부터 이어 가야 한다. + +6. release까지 자동으로 계속 진행하는 실수 + - 이 skill의 역할은 delivery chain orchestration이다. release 문서 생성/승인은 별도 stage다. + +# Verification Checklist + +- [ ] 대상 batch가 유일하게 확정되었다. +- [ ] batch 생성 단계가 이미 완료된 상태다. +- [ ] planning -> design -> implementation -> verification drafting -> verification approval -> REQ sync 순서가 유지되었다. +- [ ] `batch-lite` design 생략은 `Design Gate`와 실제 영향 근거로 설명된다. +- [ ] `confirm-batch-verification`은 blocker 없는 evidence 상태에서만 통과했다. +- [ ] `confirm-req-implemented`는 evidence가 연결된 REQ만 전환했다. +- [ ] stage별 실행 결과와 중단/완료 상태가 보고되었다. +- [ ] release 단계는 자동으로 진행하지 않고 별도 추천으로 남겼다. diff --git a/skills/run-batch-implementation/SKILL.md b/skills/run-batch-implementation/SKILL.md new file mode 100644 index 0000000..fd76db0 --- /dev/null +++ b/skills/run-batch-implementation/SKILL.md @@ -0,0 +1,51 @@ +--- +name: run-batch-implementation +description: "Use when: implementing a confirmed batch design, running /run-batch-implementation with a BAT ID, applying code changes for docs/batches/, or updating implementation logs and validation evidence for a batch." +version: 0.7.1 +author: Justin Ko +license: private +argument-hint: "예: bat-001 또는 docs/batches/bat-001_20260424_scaffold" +user-invocable: true +metadata: + hermes: + tags: [stage-pilot, batch, implementation, execution, sdlc] + related_skills: [draft-batch-planning, draft-batch-design, draft-batch-verification, confirm-req-implemented] +--- + +# Purpose + +This skill executes the implementation work for a batch, updates code, and records the changed files, execution log, validation, and remaining risks in the batch implementation document. + +# Inputs + +- `BAT-ID` 또는 batch 경로 +- `docs/batches//planning.md` +- `docs/batches//design.md` +- `docs/batches//implementation.md` +- 관련 REQ 문서와 실제 코드 경로 + +# Core Rules + +- planning과 design이 없는 batch는 구현하지 않는다. +- 코드 변경 전 `implementation.md`의 Plan Summary와 Changed Files 초안을 먼저 맞춘다. +- 구현 직후 가장 좁은 테스트, lint, typecheck, 또는 동작 검증을 수행한다. +- stateful runtime을 host/PYDEBUG binding으로 검증할 때는 동일 입력 반복 호출이 내부 scan/cache/state를 누적할 수 있음을 먼저 점검한다. deterministic evidence가 필요하면 테스트 전에 reset hook을 추가하거나 초기 상태를 명시적으로 복원한 뒤 비교한다. +- blocker가 생기면 문서에 남기고 범위를 임의 확장하지 않는다. +- 구현을 시작한 batch는 `implementation.md`만 갱신하고 끝내지 말고, batch index와 batch index 문서의 Status도 최소 `in-delivery`로 맞춰 stage 상태가 어긋나지 않게 한다. +- 배치 문서 경로가 아직 없거나 추적 문서가 비어 있으면, 구현 단계에서 `index.md`, `implementation.md`, `verification.md` 기본 뼈대를 함께 정리해 이후 stage가 바로 이어질 수 있게 한다. + +# Execution Procedure + +1. batch 경로, plan, design, 관련 REQ를 읽는다. +2. 구현 범위와 변경 파일 후보를 요약한다. +3. 실제 코드 변경을 수행한다. +4. `implementation.md`의 Changed Files, Execution Log, Validation, Remaining Risks를 갱신한다. +5. batch index 상태를 필요하면 `in-delivery`로 유지 또는 보정한다. +6. persistence schema, flash migration, 또는 저장 포맷 호환성 이슈를 디버깅할 때는 호스트/PYDEBUG 빌드의 `sizeof(...)` 추정으로 결론내리지 말고, 타깃 펌웨어 쪽에 구조체 크기(`sizeof(struct prop_header)`, 관련 persisted struct), header probe 결과, 첫/둘째 `property_read(...)` 반환값을 직접 로그로 남겨 원인을 분리한다. + +# Validation + +- `implementation.md`에 실제 변경 파일과 검증 기록이 반영됐는지 확인한다. +- 구현 직후 수행한 가장 좁은 검증 명령 또는 결과가 문서에 남았는지 확인한다. +- persistence/migration 디버깅 변경이라면, 타깃 로그로 ABI 크기와 read rc를 캡처하는 계측 또는 동등한 증거가 남았는지 확인한다. +- batch 문서 `index.md`와 `docs/batches/index.md`의 Status가 구현 진행 상태(`in-delivery` 등)와 일치하는지 확인한다. \ No newline at end of file diff --git a/skills/run-sdlc/SKILL.md b/skills/run-sdlc/SKILL.md new file mode 100644 index 0000000..d5dd2d7 --- /dev/null +++ b/skills/run-sdlc/SKILL.md @@ -0,0 +1,105 @@ +--- +name: run-sdlc +description: "Use when: orchestrating the next SDLC action from the current 3-phase state, running /run-sdlc with a Discovery, REQ, Batch, or Release ID, resuming from the first incomplete unit in docs/discovery, docs/srs, docs/batches, or docs/releases, or routing a no-history repository to bootstrap-baseline before the first real Discovery." +version: 0.7.0 +author: Justin Ko +license: private +argument-hint: "예: dcy-001, bat-001, rel-001, req-001 req-002" +user-invocable: true +metadata: + hermes: + tags: [stage-pilot, orchestration, workflow, execution, sdlc] + related_skills: [bootstrap-baseline, new-discovery, draft-req, draft-batch, run-batch-delivery, draft-release] +--- + +# Purpose + +This skill inspects the current state of Discovery, REQ, Batch, and Release documents and routes work to the next skill-only step in the 3-phase SDLC model. + +`docs/project-structure.md`와 `docs/runtime-flows.md`는 별도 governance unit는 아니지만, 이 skill은 다음 단계를 추천할 때 해당 baseline 문서의 존재 여부와 갱신 필요 여부를 함께 확인한다. + +# Inputs + +- `DISCOVERY_ID` +- `REQ-ID` 목록 +- `BAT-ID` +- `REL-ID` +- 관련 문서 경로 +- `docs/project-structure.md` (존재하는 경우) +- `docs/runtime-flows.md` (존재하는 경우) + +# Core Rules + +- 이 skill은 `docs/discovery/`, `docs/srs/`, `docs/batches/`, `docs/releases/`만 active 경로로 사용한다. +- `docs/project-structure.md`와 `docs/runtime-flows.md`는 active routing 대상은 아니지만, 모든 단계에서 참고할 cross-cutting baseline 문서로 취급한다. +- prompt 기반 stage command는 사용하지 않는다. +- `review-*` 단계는 별도 명령으로 두지 않고 대응 `confirm-*` 절차에 흡수된 상태를 전제로 한다. +- 사람 선택이 필요한 경계에서는 자동으로 넘기지 않고 중단한다. 대표적으로 `suggest-batch-reqs` 결과에서 어떤 후보를 채택할지 결정하는 단계가 그렇다. +- 단, 정확히 1개의 `Approved` REQ가 국소적이고 저위험 변경으로 판단되면 `minor-change` fast path로 `draft-batch`를 바로 제안할 수 있다. 이 경로의 batch profile은 `batch-lite`다. +- active unit가 아직 없고 baseline 문서 또는 active index가 비어 있으면, 첫 real Discovery 전에 `bootstrap-baseline`을 우선 추천한다. + +# Execution Procedure + +## 1. 입력 해석 + +- 입력이 `dcy-`로 시작하면 Discovery를 기준으로 현재 상태를 확인한다. +- 입력이 `req-` 목록이면 REQ 상태를 읽고 batch 생성 또는 추천 단계로 보낸다. +- 입력이 `bat-`로 시작하면 batch delivery 상태를 확인한다. +- 입력이 `rel-`로 시작하면 release 상태를 확인한다. +- 입력이 없으면 현재 저장소에서 첫 incomplete step을 판정한다. +- 어떤 입력이든 가능하면 `docs/project-structure.md`와 `docs/runtime-flows.md` 존재 여부를 함께 확인한다. + +## 1.5 bootstrap baseline routing + +- active unit가 없고 `docs/project-structure.md`, `docs/runtime-flows.md`, active index 중 일부라도 비어 있으면 `bootstrap-baseline`을 다음 skill로 제안한다. +- 이 경우 Discovery를 자동 생성하지 않는다. +- baseline 초기화가 끝난 뒤 `new-discovery`를 다음 real Discovery entrypoint로 안내한다. + +## 2. Discovery 기준 routing + +- Discovery가 아직 `confirmed`가 아니면 `confirm-discovery`를 우선 실행한다. +- Discovery는 confirmed지만 연결된 REQ가 없으면 `draft-req`를 다음 단계로 사용한다. +- `Approved` REQ는 있으나 batch가 없으면 `suggest-batch-reqs`를 실행해 후보를 제시하고, 사용자가 후보를 선택한 뒤 `draft-batch`로 이어 가도록 멈춘다. +- batch가 이미 생성되었고 delivery chain을 한 번에 이어서 진행하려면 `run-batch-delivery`를 직접 entrypoint로 사용할 수 있다. +- baseline 문서 갭이 있으면 상태 요약에 `docs/project-structure.md`와 `docs/runtime-flows.md` 갱신 필요 여부를 함께 기록한다. + +## 3. REQ 기준 routing + +- `Proposed` REQ면 `confirm-req`를 다음 단계로 사용한다. +- 기존 REQ의 `Requirement`, `Acceptance Criteria`, 구현 전제가 바뀌어 Change Log 기반 변경 관리가 필요하면 `change-req`를 다음 단계로 사용한다. +- `Approved` REQ가 `release-candidate` 또는 `released` batch의 verification evidence와 연결돼 있으면 `confirm-req-implemented`를 다음 단계로 사용한다. +- 단일 `Approved` REQ이고 구조/인터페이스/런타임 영향이 없는 소규모 변경이면 `draft-batch`를 `minor-change -> batch-lite` 경로로 바로 제안한다. +- Approved REQ 묶음이 2개 이상이거나 사람 선택이 필요한 경우 `suggest-batch-reqs` 또는 `draft-batch`로 이어진다. +- baseline 문서 갱신이 REQ에 포함돼 있으면 상태 요약에 이를 반영한다. + +## 4. Batch 기준 routing + +- `planning.md`가 비어 있거나 draft 수준이면 `draft-batch-planning` +- batch profile이 `standard`이거나 `batch-lite`라도 planning의 `Design Gate`가 `yes`면 `draft-batch-design` +- batch profile이 `batch-lite`이고 `Design Gate`가 `no`면 design 없이 `run-batch-implementation` +- 구현 로그와 코드 변경이 아직 없으면 `run-batch-implementation` +- verification 문서가 없거나 불완전하면 `draft-batch-verification` +- verification evidence가 있지만 release-candidate가 아니면 `confirm-batch-verification` +- batch가 `release-candidate` 또는 `released`이고 포함 REQ 중 `Approved` 상태가 남아 있으면 `confirm-req-implemented` +- batch가 `release-candidate`이고 포함 REQ 구현 상태 동기화까지 끝났으면 `draft-release` +- batch design 또는 verification에 structure/flow 영향이 보이면 baseline 문서 갱신 필요 여부를 상태 요약에 포함한다. + +## 5. Release 기준 routing + +- release 문서가 draft면 profile(`docs-only` | `tooling` | `app-service`)에 맞는 근거가 채워졌는지 확인한 뒤 `confirm-release` +- `feedback-captured` release에 follow-up 입력이 있으면 `suggest-next-discovery` 또는 `change-req`를 다음 단계 후보로 제안한다. +- release가 confirmed 이후 운영 관찰이 남아 있지 않으면 `capture-release-feedback` + +# Output Expectations + +- 입력으로 해석한 단위 종류 +- 현재 상태 요약 +- baseline 문서 상태 (`present` | `missing` | `update-likely-required` | `bootstrap-required`) +- 바로 실행할 다음 skill 1개 또는 추천 후보 목록 +- 자동으로 진행하지 않고 멈춘 이유가 있으면 그 사유 + +# Validation + +- 다음 단계가 현재 문서 상태와 모순되지 않는지 확인한다. +- 제안한 다음 skill이 실제 `.stage-pilot/skills/` 아래 존재하는지 확인한다. +- baseline 문서가 필요한 상황에서 상태 요약 또는 추천 사유에 그 정보가 누락되지 않았는지 확인한다. \ No newline at end of file diff --git a/skills/suggest-batch-reqs/SKILL.md b/skills/suggest-batch-reqs/SKILL.md new file mode 100644 index 0000000..d513969 --- /dev/null +++ b/skills/suggest-batch-reqs/SKILL.md @@ -0,0 +1,160 @@ +--- +name: suggest-batch-reqs +description: "Use when: recommending delivery batch groupings from approved requirements, running /suggest-batch-reqs with a Discovery ID or REQ list, assessing cohesion and delivery risk, or preparing input for /draft-batch without changing repository state." +version: 0.8.2 +author: Justin Ko +license: private +argument-hint: "예: dcy-001 또는 req-001 req-002 req-003" +user-invocable: true +metadata: + hermes: + tags: [stage-pilot, batch, requirements, planning, sdlc] + related_skills: [draft-req, draft-batch, run-sdlc] +--- + +# Purpose + +This skill recommends one or more candidate batch groupings from approved requirements and reports rationale, exclusions, risk, and confidence without modifying any files. 입력이 없으면 현재 저장소에서 아직 delivery 입력으로 소모되지 않은 Approved REQ를 찾아 이번 batch 후보를 추천한다. + +# When to use + +- Approved REQ가 여러 개 쌓여 어떤 묶음으로 delivery를 시작할지 추천이 필요할 때 +- `/suggest-batch-reqs dcy-001` 또는 `/suggest-batch-reqs req-001 req-002`처럼 batch 후보안을 받아야 할 때 +- `/suggest-batch-reqs`처럼 인자 없이 현재 저장소의 Approved REQ 중 아직 구현 완료 또는 기존 batch 편성이 끝나지 않은 후보만 추려 이번 batch 권장안을 보고 싶을 때 +- `draft-batch` 실행 전에 포함/제외 REQ 근거를 미리 보고 싶을 때 + +## Do not use when + +- 아직 `Approved`되지 않은 REQ를 먼저 승인해야 할 때 (`confirm-req`가 더 적절함) +- 포함할 REQ 집합이 이미 확정되어 바로 batch 문서를 생성하면 될 때 (`draft-batch`가 더 적절함) +- 단일 저위험 `Approved` REQ라 minor-change 또는 `batch-lite` 경로가 명확할 때 (`draft-batch`를 바로 고려할 수 있음) +- batch 추천이 아니라 현재 저장소의 전체 다음 SDLC 단계를 알고 싶을 때 (`run-sdlc`가 더 적절함) + +# Inputs + +- 선택 입력 + - `DISCOVERY_ID` 또는 Approved `REQ-ID` 목록 + - 입력이 없으면 `docs/srs/**/req-*.md`와 `docs/srs/index.md`를 읽어 Approved REQ를 찾는다. + - 입력이 없으면 `docs/batches/index.md`도 함께 읽어 이미 batch에 편성된 REQ를 제외한다. +- 선택 입력 + - batch 최대 크기 + - Type, Priority, Owner 필터 + - 제외할 REQ 목록 + - 강제로 함께 고려할 REQ 목록 + +# Core Rules + +- 이 skill은 추천만 수행하고 저장소 상태를 바꾸지 않는다. +- `Approved`가 아닌 REQ는 후보에서 제외한다. +- `Deprecated` REQ는 후보에서 제외한다. +- 입력이 없는 경우 `Implemented` 상태 REQ는 후보에서 제외한다. +- 입력이 없는 경우 `docs/batches/index.md` 기준으로 `draft`, `in-delivery`, `release-candidate`, `released` batch에 이미 포함된 REQ는 중복 추천하지 않는다. +- 상태가 REQ 본문과 index 사이에서 불일치하면, 추천 전에 그 불일치를 보고하고 기본적으로 후보에서 제외하거나 보류한다. +- 아래 조건을 많이 만족할수록 같은 batch 후보로 묶는다. + - 같은 사용자 가치 또는 기능 흐름 + - 같은 모듈, 인터페이스, 런타임 경로 + - 같은 설계 전제와 환경 가정 + - 같은 iteration 안에서 구현과 검증 가능 + - acceptance criteria를 함께 검증하는 것이 효율적임 + - release 시 함께 배포해도 위험이 과도하지 않음 +- 아래 조건이 있으면 같은 batch로 묶지 않는다. + - 핵심 설계 전제 충돌 + - 완전히 다른 verification 흐름 필요 + - 독립 배포가 필요한 고위험 변경 + - 미해결 정책 결정이나 외부 의존성으로 planning이 불안정함 + +## 사람 선택 규칙 + +- 추천 결과 중 어떤 후보를 채택할지는 사용자 또는 사람 책임자가 결정한다. +- 이 skill은 후보를 자동 채택하거나 `draft-batch`를 자동 실행하지 않는다. +- 후보 간 우열이 애매하면 `기본안` 1개와 `보수적 대안` 1개 이상을 함께 제시하고 trade-off를 설명한다. +- 사람이 선택해야 하는 기준은 보통 다음 순서로 설명한다. + 1. 이번 iteration 안에 끝낼 수 있는가 + 2. 함께 검증할 때 verification cost가 실제로 줄어드는가 + 3. release risk가 한 번에 묶어도 감당 가능한가 + 4. 나중에 분리하면 오히려 재작업이 커지는가 + +## 후보별 batch profile 힌트 + +- `batch-lite` + - 단일 REQ 또는 아주 작은 저위험 묶음 + - 구조/인터페이스/런타임 영향이 좁음 + - planning/design/verification 서술이 짧고 명확함 +- `standard` + - 다수 REQ 또는 상호작용이 있는 묶음 + - 구조/인터페이스/런타임 영향이 중간 이상임 + - planning/design/verification 근거를 분리해서 적는 편이 안전함 +- 각 후보 출력에는 profile 이름만 적지 말고, 왜 그 profile이 맞는지 REQ 수, 변경 폭, verification 복잡도 기준으로 한 줄 이상 근거를 붙인다. + +# Execution Procedure + +1. 입력이 Discovery면 source Discovery의 `생성된 REQ 참조` 또는 명시된 연결 REQ를 우선 읽고, 입력이 REQ 목록이면 각 REQ 문서를 읽는다. +2. Discovery 입력인데 연결 REQ가 없거나 연결 REQ가 모두 `Approved`가 아니면 추천을 만들지 않고 그 사실을 보고한다. +3. 입력이 없으면 `docs/srs/**/req-*.md`, `docs/srs/index.md`, `docs/batches/index.md`를 읽어 Approved REQ 전체를 찾고, `Implemented` 상태 또는 기존 batch에 이미 포함된 REQ를 제외해 현재 후보군을 만든다. +4. 각 REQ의 상태, Type, Impacted Area, Acceptance Criteria를 요약한다. +5. 후보군에서 Approved REQ만 추린 뒤 응집도와 delivery risk 기준으로 후보안을 1개 이상 만든다. +6. 입력이 없는 스캔 결과 후보군이 비어 있으면 억지로 batch 후보를 만들지 않는다. + - `Approved` REQ가 전혀 없거나 + - Approved REQ가 모두 기존 batch에 이미 포함돼 있거나 + - 남은 REQ가 전부 `Proposed`/`Implemented`/`Deprecated`라면 + `추천 가능한 신규 batch 없음`으로 보고한다. + - 이 경우 스캔한 REQ와 제외 사유를 함께 정리하고, 필요하면 먼저 `confirm-req` 또는 기존 batch 진행 상태 점검을 권장한다. + - 후보가 없는데도 형식상 `BAT-CANDIDATE-1`을 억지로 만들거나, 실제로 구성할 수 없는 `/draft-batch ...` 예시를 만들지 않는다. +7. 가능하면 최소 2개 후보를 제시한다. + - 응집도 중심 기본안 + - 더 작은 묶음 또는 보수적 대안 +8. 적절한 묶음이 없지만 단일 Approved REQ는 존재하면 `단일 REQ batch 권장`을 명시한다. +9. 각 후보에 대해 포함 REQ, 제외 REQ와 제외 이유, 추천 근거, 위험도, 신뢰도, 예상 batch profile(`standard` | `batch-lite`)을 정리한다. +10. 각 후보에 대해 profile 판단 근거(REQ 수, 구조/인터페이스/런타임 영향, verification 흐름 복잡도)를 함께 적는다. +11. 바로 사용할 수 있는 `/draft-batch ...` 입력 예시를 제시하되, 이는 추천 초안이며 최종 채택은 사람 선택임을 명시한다. 단, 추천 가능한 후보가 없으면 `/draft-batch` 예시는 생략하고 그 이유를 쓴다. + +# Output Expectations + +- 후보 `BAT-CANDIDATE-N` 목록 +- 입력이 없는 경우 스캔한 Approved REQ 목록 +- 후보별 포함 REQ +- 후보별 제외 REQ와 제외 이유 +- 후보별 추천 근거 +- 후보별 위험도 (`Low|Medium|High`) +- 후보별 신뢰도 (`High|Medium|Low`) +- 후보별 예상 batch profile (`standard` | `batch-lite`)과 판단 근거 +- `draft-batch` 추천 명령 예시 +- 최종 채택은 사람 선택이라는 안내 +- 단, 추천 가능한 신규 batch가 없으면 아래 형식으로 대체 보고할 수 있다. + - 스캔한 REQ 목록 + - 승인됨/제외됨/기존 batch 포함 상태 요약 + - `추천 가능한 신규 batch 없음` 결론 + - 먼저 필요한 선행 단계(`confirm-req`, 기존 batch 진행, release 정리 등) + +# Common Pitfalls + +1. `Approved`가 아닌 REQ를 후보에 섞는 실수 + - batch 추천은 delivery input이 확정된 REQ만 대상으로 해야 하므로 `Proposed`, `Implemented`, `Deprecated`를 구분해 제외한다. + +2. 이미 다른 batch에 포함된 REQ를 다시 추천하는 실수 + - `docs/batches/index.md`를 읽고 중복 편성 여부를 먼저 확인한다. + +3. 서로 다른 verification 흐름이 필요한 REQ를 단순 개수 맞추기로 한 batch에 묶는 실수 + - 응집도보다 검증 흐름 충돌이 크면 더 작은 보수적 대안을 함께 제시한다. + +4. Discovery 입력을 받았지만 실제 연결 REQ 근거 없이 임의로 묶는 실수 + - source Discovery의 `생성된 REQ 참조` 또는 명시된 연결 REQ를 우선 근거로 삼는다. + +5. 추천 후보를 정답처럼 확정해 버리는 실수 + - 어떤 후보를 채택할지는 사용자 또는 사람 책임자가 결정하며, 이 skill은 추천만 수행한다. + +6. 단일 저위험 REQ인데도 불필요하게 큰 batch 후보만 제시하는 실수 + - 필요하면 `단일 REQ batch 권장` 또는 `batch-lite` 대안을 함께 제시한다. + +# Verification Checklist + +- [ ] 후보에 `Approved`가 아닌 REQ가 포함되지 않았다. +- [ ] 입력이 없는 경우 `Implemented` 상태 REQ와 기존 batch 포함 REQ가 제외되었다. +- [ ] Discovery 입력이면 연결 REQ 근거가 명확하다. +- [ ] 상태 불일치 REQ는 보고되었고 무리하게 후보에 포함되지 않았다. +- [ ] 후보별 포함/제외 이유가 설명되어 있다. +- [ ] 가능하면 최소 1개의 보수적 대안 또는 `단일 REQ batch 권장`이 포함되어 있다. +- [ ] 후보별 예상 batch profile(`standard` | `batch-lite`)과 그 근거가 제시되었다. +- [ ] `/draft-batch` 예시 입력이 실제 후보 구성과 일치한다. +- [ ] 저장소 상태를 변경하지 않았다. +- [ ] 최종 채택이 사람 선택이라는 점이 명시되어 있다. \ No newline at end of file diff --git a/skills/suggest-next-discovery/SKILL.md b/skills/suggest-next-discovery/SKILL.md new file mode 100644 index 0000000..8c5b7a4 --- /dev/null +++ b/skills/suggest-next-discovery/SKILL.md @@ -0,0 +1,70 @@ +--- +name: suggest-next-discovery +description: "Use when: recommending the next Discovery candidate from release feedback, pending requirements, baseline gaps, or recent doctor reports, running /suggest-next-discovery with or without a REL ID, or deciding whether feedback should become a new Discovery or a change-req instead." +version: 0.6.0 +author: Justin Ko +license: private +argument-hint: "예: rel-001 또는 인자 없이 /suggest-next-discovery" +user-invocable: true +metadata: + hermes: + tags: [stage-pilot, discovery, prioritization, planning, sdlc] + related_skills: [capture-release-feedback, new-discovery, run-sdlc] +--- + +# Purpose + +This skill analyzes release feedback, pending REQs, baseline gaps, and recent delivery signals to recommend the next improvement loop. It reports ranked candidates and tells the user whether to start a new Discovery or route work into `change-req` instead. + +# When to use + +- `/suggest-next-discovery`처럼 다음 반복 주제를 추천받고 싶을 때 +- `feedback-captured` release 이후 어떤 후속 Discovery를 열지 정해야 할 때 +- release feedback에 기존 REQ 수정과 신규 Discovery 후보가 섞여 있어 triage가 필요할 때 +- `stagepilot-doctor --report ...` 결과와 release feedback를 함께 보고 다음 개선 루프를 정하고 싶을 때 + +# Inputs + +- 선택 입력 + - `REL-ID` prefix 또는 release 경로 + - 입력이 없으면 `docs/releases/*.md`, `docs/srs/index.md`, `docs/project-structure.md`, `docs/runtime-flows.md`를 읽어 후보를 찾는다. +- 선택 입력 + - `stagepilot-doctor` Markdown report 경로 +- 관련 release 문서의 `Feedback Handoff` +- `docs/srs/index.md`와 미구현/변경 대기 REQ 상태 +- `docs/project-structure.md`, `docs/runtime-flows.md` (존재하는 경우) + +# Core Rules + +- 이 skill은 추천만 수행하고 저장소 상태를 바꾸지 않는다. +- release feedback의 후보는 아래 셋 중 하나로 분류한다. + - `new-discovery` + - `change-req` + - `no-action` +- 기존 REQ의 수정으로 닫히는 항목은 새 Discovery보다 `change-req`를 우선 추천한다. +- 서로 다른 가치 흐름이나 이해관계자를 가지는 후보는 별도 Discovery로 분리한다. +- 추천에는 가치, 위험, 노력의 상대 평가를 포함한다. + +# Execution Procedure + +1. 입력 release가 있으면 해당 문서를, 없으면 최근 release 문서들을 읽는다. +2. `Feedback Handoff`에서 Discovery Input, REQ Input, Change Request Input을 추린다. +3. `docs/srs/index.md`와 관련 REQ 상태를 읽어 기존 REQ 수정으로 해결 가능한지 판정한다. +4. baseline 문서 gap, 미구현 REQ, doctor report의 경고/오류가 후속 반복 후보인지 함께 판단한다. +5. 후보를 `new-discovery` 또는 `change-req`로 분류하고 가치/위험/노력 점수를 매긴다. +6. 우선순위 순으로 추천 목록과 바로 실행할 명령 예시를 제시한다. + +# Output Expectations + +- 후보 목록 (`CANDIDATE-1`, `CANDIDATE-2`, ...) +- 각 후보의 분류 (`new-discovery` | `change-req` | `no-action`) +- 근거 release 또는 REQ +- 가치/위험/노력 (`High|Medium|Low`) +- 추천 이유 +- 다음 명령 예시 (`/new-discovery ...` 또는 `/change-req ...`) + +# Validation + +- 동일 후보를 `new-discovery`와 `change-req`에 중복 배치하지 않았는지 확인한다. +- release feedback 근거가 없는 임의 후보를 만들지 않았는지 확인한다. +- 기존 REQ 수정으로 닫히는 항목을 새 Discovery로 과도하게 승격하지 않았는지 확인한다. \ No newline at end of file diff --git a/source/.claude/commands/bootstrap-baseline.md b/source/.claude/commands/bootstrap-baseline.md new file mode 100644 index 0000000..03afeef --- /dev/null +++ b/source/.claude/commands/bootstrap-baseline.md @@ -0,0 +1 @@ +Follow the instructions in `.stage-pilot/skills/bootstrap-baseline/SKILL.md` exactly. diff --git a/source/.claude/commands/capture-release-feedback.md b/source/.claude/commands/capture-release-feedback.md new file mode 100644 index 0000000..03266f9 --- /dev/null +++ b/source/.claude/commands/capture-release-feedback.md @@ -0,0 +1 @@ +Follow the instructions in `.stage-pilot/skills/capture-release-feedback/SKILL.md` exactly. diff --git a/source/.claude/commands/change-req.md b/source/.claude/commands/change-req.md new file mode 100644 index 0000000..20fa644 --- /dev/null +++ b/source/.claude/commands/change-req.md @@ -0,0 +1 @@ +Follow the instructions in `.stage-pilot/skills/change-req/SKILL.md` exactly. diff --git a/source/.claude/commands/confirm-batch-verification.md b/source/.claude/commands/confirm-batch-verification.md new file mode 100644 index 0000000..6e72a0b --- /dev/null +++ b/source/.claude/commands/confirm-batch-verification.md @@ -0,0 +1 @@ +Follow the instructions in `.stage-pilot/skills/confirm-batch-verification/SKILL.md` exactly. diff --git a/source/.claude/commands/confirm-discovery.md b/source/.claude/commands/confirm-discovery.md new file mode 100644 index 0000000..4b36987 --- /dev/null +++ b/source/.claude/commands/confirm-discovery.md @@ -0,0 +1 @@ +Follow the instructions in `.stage-pilot/skills/confirm-discovery/SKILL.md` exactly. diff --git a/source/.claude/commands/confirm-release.md b/source/.claude/commands/confirm-release.md new file mode 100644 index 0000000..fea9962 --- /dev/null +++ b/source/.claude/commands/confirm-release.md @@ -0,0 +1 @@ +Follow the instructions in `.stage-pilot/skills/confirm-release/SKILL.md` exactly. diff --git a/source/.claude/commands/confirm-req-implemented.md b/source/.claude/commands/confirm-req-implemented.md new file mode 100644 index 0000000..d9e90b5 --- /dev/null +++ b/source/.claude/commands/confirm-req-implemented.md @@ -0,0 +1 @@ +Follow the instructions in `.stage-pilot/skills/confirm-req-implemented/SKILL.md` exactly. diff --git a/source/.claude/commands/confirm-req.md b/source/.claude/commands/confirm-req.md new file mode 100644 index 0000000..558f868 --- /dev/null +++ b/source/.claude/commands/confirm-req.md @@ -0,0 +1 @@ +Follow the instructions in `.stage-pilot/skills/confirm-req/SKILL.md` exactly. diff --git a/source/.claude/commands/draft-batch-design.md b/source/.claude/commands/draft-batch-design.md new file mode 100644 index 0000000..2674265 --- /dev/null +++ b/source/.claude/commands/draft-batch-design.md @@ -0,0 +1 @@ +Follow the instructions in `.stage-pilot/skills/draft-batch-design/SKILL.md` exactly. diff --git a/source/.claude/commands/draft-batch-planning.md b/source/.claude/commands/draft-batch-planning.md new file mode 100644 index 0000000..cd5261b --- /dev/null +++ b/source/.claude/commands/draft-batch-planning.md @@ -0,0 +1 @@ +Follow the instructions in `.stage-pilot/skills/draft-batch-planning/SKILL.md` exactly. diff --git a/source/.claude/commands/draft-batch-verification.md b/source/.claude/commands/draft-batch-verification.md new file mode 100644 index 0000000..7758a67 --- /dev/null +++ b/source/.claude/commands/draft-batch-verification.md @@ -0,0 +1 @@ +Follow the instructions in `.stage-pilot/skills/draft-batch-verification/SKILL.md` exactly. diff --git a/source/.claude/commands/draft-batch.md b/source/.claude/commands/draft-batch.md new file mode 100644 index 0000000..66d1a06 --- /dev/null +++ b/source/.claude/commands/draft-batch.md @@ -0,0 +1 @@ +Follow the instructions in `.stage-pilot/skills/draft-batch/SKILL.md` exactly. diff --git a/source/.claude/commands/draft-release.md b/source/.claude/commands/draft-release.md new file mode 100644 index 0000000..40c6826 --- /dev/null +++ b/source/.claude/commands/draft-release.md @@ -0,0 +1 @@ +Follow the instructions in `.stage-pilot/skills/draft-release/SKILL.md` exactly. diff --git a/source/.claude/commands/draft-req.md b/source/.claude/commands/draft-req.md new file mode 100644 index 0000000..29b91bb --- /dev/null +++ b/source/.claude/commands/draft-req.md @@ -0,0 +1 @@ +Follow the instructions in `.stage-pilot/skills/draft-req/SKILL.md` exactly. diff --git a/source/.claude/commands/new-discovery.md b/source/.claude/commands/new-discovery.md new file mode 100644 index 0000000..970e28a --- /dev/null +++ b/source/.claude/commands/new-discovery.md @@ -0,0 +1 @@ +Follow the instructions in `.stage-pilot/skills/new-discovery/SKILL.md` exactly. diff --git a/source/.claude/commands/review-discovery.md b/source/.claude/commands/review-discovery.md new file mode 100644 index 0000000..255c040 --- /dev/null +++ b/source/.claude/commands/review-discovery.md @@ -0,0 +1 @@ +Follow the instructions in `.stage-pilot/skills/review-discovery/SKILL.md` exactly. diff --git a/source/.claude/commands/run-batch-delivery.md b/source/.claude/commands/run-batch-delivery.md new file mode 100644 index 0000000..4c1c9ce --- /dev/null +++ b/source/.claude/commands/run-batch-delivery.md @@ -0,0 +1 @@ +Follow the instructions in `.stage-pilot/skills/run-batch-delivery/SKILL.md` exactly. diff --git a/source/.claude/commands/run-batch-implementation.md b/source/.claude/commands/run-batch-implementation.md new file mode 100644 index 0000000..9014da3 --- /dev/null +++ b/source/.claude/commands/run-batch-implementation.md @@ -0,0 +1 @@ +Follow the instructions in `.stage-pilot/skills/run-batch-implementation/SKILL.md` exactly. diff --git a/source/.claude/commands/run-sdlc.md b/source/.claude/commands/run-sdlc.md new file mode 100644 index 0000000..e8cdb83 --- /dev/null +++ b/source/.claude/commands/run-sdlc.md @@ -0,0 +1 @@ +Follow the instructions in `.stage-pilot/skills/run-sdlc/SKILL.md` exactly. diff --git a/source/.claude/commands/suggest-batch-reqs.md b/source/.claude/commands/suggest-batch-reqs.md new file mode 100644 index 0000000..b4a7b1d --- /dev/null +++ b/source/.claude/commands/suggest-batch-reqs.md @@ -0,0 +1 @@ +Follow the instructions in `.stage-pilot/skills/suggest-batch-reqs/SKILL.md` exactly. diff --git a/source/.claude/commands/suggest-next-discovery.md b/source/.claude/commands/suggest-next-discovery.md new file mode 100644 index 0000000..16198ca --- /dev/null +++ b/source/.claude/commands/suggest-next-discovery.md @@ -0,0 +1 @@ +Follow the instructions in `.stage-pilot/skills/suggest-next-discovery/SKILL.md` exactly. diff --git a/source/.github/instructions/placeholder-guide.md b/source/.github/instructions/placeholder-guide.md new file mode 100644 index 0000000..30b97f5 --- /dev/null +++ b/source/.github/instructions/placeholder-guide.md @@ -0,0 +1,239 @@ +# 플레이스홀더 가이드 + +이 파일은 active workspace 기준으로 `.github/templates/`와 `.github/skills/`에서 사용하는 플레이스홀더의 문법과 처리 원칙을 정의한다. + +기본 전제는 아래와 같다. + +- active SDLC 진입점은 skill-only 체계다. +- baseline 문서와 active index 초기화는 `bootstrap-baseline`이 담당하며, 이 단계는 Discovery unit를 만들지 않는다. +- `.github/prompts/`는 active 기준 문서가 아니다. +- active 산출물 경로는 `docs/discovery/`, `docs/srs/`, `docs/batches/`, `docs/releases/`다. + +## 1. 문법 규칙 + +| 형식 | 의미 | 예시 | +| --- | --- | --- | +| `{{KEY}}` | 단순 치환 대상 | `{{DISCOVERY_ID}}` | +| `{{KEY:hint}}` | 힌트 포함 치환 대상 | `{{CONFIRMED_AT_KST:TBD}}` | +| `{{KEY:opt1\|opt2}}` | 열거형 선택 값 | `{{DOC_STATUS:draft\|confirmed}}` | + +규칙: + +- 최종 문서에는 `{{...}}` 문법이 남지 않아야 한다. +- 힌트 텍스트는 치환 시 함께 제거한다. +- 사람 승인 없이는 확정할 수 없는 값은 본문에 무리하게 확정하지 말고 Notes 또는 결정 필요 항목으로 남긴다. + +## 2. 처리 주체 + +플레이스홀더는 아래 세 가지 처리 주체 중 하나에 속한다. + +### A. 자동 치환 + +- ID, 생성 시각, 갱신 시각, 상태 기본값처럼 실행 시점에 자동으로 정해지는 값 +- 예: + - `{{DISCOVERY_ID}}` + - `{{CONFIRMED_AT_KST}}` + - `{{BAT_ID}}` + - `{{REL_ID}}` + +### B. AI 근거 기반 치환 + +- 현재 저장소 상태, 상위 문서, 이미 확정된 값으로 채울 수 있는 항목 +- 예: + - Discovery의 현재 상태 요약 + - REQ의 Impacted Area 초안 + - Batch planning의 dependency 정리 + - Release rollout checklist 초안 + +### C. 사람 결정 필요 + +- 정책 선택, 승인자 지정, 우선순위 확정, 외부 환경 정보처럼 사람 판단이 필요한 값 +- 이런 항목은 무리하게 확정하지 않는다. + +## 3. 경로별 플레이스홀더 원칙 + +### 3.0 Bootstrap baseline 템플릿 + +대상 경로: + +- `.github/templates/bootstrap/baseline-seed.yaml` +- `.github/templates/project-structure.md` +- `.github/templates/runtime-flows.md` +- `.github/templates/discovery/index.md` +- `.github/templates/srs/index.md` +- `.github/templates/batches/index.md` +- `.github/templates/releases/index.md` + +처리 원칙: + +- `bootstrap-baseline`은 먼저 `.stage-pilot/bootstrap/baseline.yaml` seed를 만들거나 보강한다. +- greenfield 저장소처럼 관찰 가능한 정보가 부족하면 최소 질문 세트로 seed를 채운다. +- `bootstrap-baseline`은 baseline 문서와 index를 초기화하지만 Discovery 문서를 만들지 않는다. +- baseline 문서는 seed를 source of truth로 렌더링하고, 필요하면 저장소 관찰값으로 보강한다. +- index 문서는 빈 register 골격만 준비하고, 실제 unit 행은 이후 skill이 추가한다. + +주요 키: + +- `{{BASELINE_MODE}}` +- `{{BASELINE_SEED_PATH}}` +- `{{PROJECT_SUMMARY}}` +- `{{PRIMARY_DOMAIN}}` +- `{{TECH_STACK_BULLETS}}` +- `{{PRIMARY_RUNTIME}}` +- `{{PRIMARY_ENTRYPOINT_BULLETS}}` +- `interfaces.contracts[*]` +- `data_model.entities[*]` + +### 3.0.1 선택 cross-cutting 템플릿 + +대상 경로: + +- `.github/templates/interface-contract.md` +- `.github/templates/data-model.md` + +처리 원칙: + +- 이 문서들은 optional cross-cutting reference다. +- active unit의 source of truth를 대체하지 않고, 여러 REQ와 batch에 공통으로 반복되는 계약 정보를 모아 둔다. +- `bootstrap-baseline`은 seed의 `interfaces`와 `data_model` 섹션을 우선 읽고, 값이 없으면 `runtime`, `structure`, `notes`를 바탕으로 기본 초안을 구체적으로 유도한다. +- baseline 수준의 변경이 있을 때만 갱신하고, REQ별 acceptance criteria를 그대로 복사하지 않는다. + +주요 키: + +- `{{BASELINE_MODE}}` +- `{{BASELINE_SEED_PATH}}` +- `{{PROJECT_SUMMARY}}` +- `{{PRIMARY_DOMAIN}}` +- `{{PRIMARY_RUNTIME}}` +- `{{INTERFACE_NAME_1}}` +- `{{INTERFACE_PURPOSE_1}}` +- `{{INTERFACE_SUMMARY_BULLETS}}` +- `{{INTERFACE_INPUTS_1}}` +- `{{INTERFACE_OUTPUTS_1}}` +- `{{INTERFACE_ERRORS_1}}` +- `{{INTERFACE_COMPATIBILITY_1}}` +- `{{INTERFACE_SHARED_CONSTRAINTS_BULLETS}}` +- `{{INTERFACE_CURRENT_GAPS_BULLETS}}` +- `{{PERSISTENCE_BACKEND}}` +- `{{ENTITY_NAME_1}}` +- `{{ENTITY_PURPOSE_1}}` +- `{{ENTITY_SOURCE_1}}` +- `{{ENTITY_LIFECYCLE_1}}` +- `{{MODEL_SUMMARY_BULLETS}}` +- `{{ENTITY_FIELDS_1}}` +- `{{ENTITY_RELATIONSHIPS_1}}` +- `{{CONSISTENCY_RULE_BULLETS}}` +- `{{PERSISTENCE_NOTES_BULLETS}}` +- `{{MODEL_CURRENT_GAPS_BULLETS}}` + +### 3.1 Discovery 템플릿 + +대상 경로: + +- `.github/templates/discovery/discovery.md` +- `docs/discovery/*.md` + +주요 키: + +- `{{DISCOVERY_ID}}` +- `{{ISSUE_NAME}}` +- `{{CREATED_AT_KST}}` +- `{{UPDATED_AT_KST}}` +- `{{FREEZE_HANDOFF_DECISION}}` +- `{{READY_FOR_REQ_DRAFTING}}` +- `{{CONFIRMED_BY}}` +- `{{CONFIRMED_AT_KST}}` + +처리 원칙: + +- Discovery는 REQ 초안 작성으로 넘길 수 있을 때만 freeze handoff를 완료한다. +- Discovery 문서의 용어는 `Planning` 직결이 아니라 `REQ Drafting` 기준으로 유지한다. + +### 3.2 SRS 템플릿 + +대상 경로: + +- `docs/srs/req-template.md` +- `docs/srs/index.md` + +주요 키: + +- `REQ-XXX` +- `Status` +- `Type` +- `Priority` +- `Owner` + +처리 원칙: + +- REQ는 `Proposed -> Approved -> Implemented -> Deprecated` 상태를 따른다. +- `draft-req`는 초안을 만들고, `confirm-req`는 `Proposed -> Approved`, `confirm-req-implemented`는 `Approved -> Implemented` 전환을 담당한다. + +### 3.3 Batch 템플릿 + +대상 경로: + +- `.github/templates/batches/*` +- `docs/batches/*` + +주요 키: + +- `{{BAT_ID}}` +- `Profile` +- Included REQ 목록 +- Source Discovery + +처리 원칙: + +- batch는 Approved REQ만 포함한다. +- batch는 `standard` 또는 `batch-lite` profile을 가진다. +- `batch-lite`는 `minor-change` fast path의 기본 delivery profile이며, 처음에는 `index.md`와 `planning.md`만 만들 수 있다. +- planning, design, implementation, verification 문서는 같은 batch 폴더 아래에서 관리한다. 단, `batch-lite`는 design/implementation/verification을 단계 진입 시점에 생성할 수 있다. +- `confirm-batch-verification` 성공 전에는 `release-candidate`로 올리지 않는다. + +### 3.4 Release 템플릿 + +대상 경로: + +- `.github/templates/releases/*` +- `docs/releases/*` + +주요 키: + +- `{{REL_ID}}` +- `Profile` +- `Change Request Input` +- Included Batch +- Rollout Plan +- Rollback Plan + +처리 원칙: + +- release는 `release-candidate` batch만 포함한다. +- release는 `docs-only`, `tooling`, `app-service` profile 중 하나를 가진다. +- feedback handoff에는 `Discovery Input`, `REQ Input`, `Change Request Input`을 구분해 기록한다. +- `confirm-release`는 release 승인, `capture-release-feedback`는 운영 환류 기록을 담당한다. + +## 4. Active Skill Summary + +| 목적 | Skill | 주요 입력 | 주요 출력 | +| --- | --- | --- | --- | +| Bootstrap baseline 초기화 | `.github/skills/bootstrap-baseline/SKILL.md` | 저장소 루트, baseline/index 존재 여부 | baseline 문서와 active index | +| Discovery 초안 생성 | `.github/skills/new-discovery/SKILL.md` | 이슈/요청 요약 | `docs/discovery/*.md` | +| Discovery 확정 | `.github/skills/confirm-discovery/SKILL.md` | `DISCOVERY_ID` | confirmed Discovery | +| REQ 초안 생성 | `.github/skills/draft-req/SKILL.md` | `DISCOVERY_ID` | `docs/srs//req-*.md` | +| REQ 확정 | `.github/skills/confirm-req/SKILL.md` | `REQ-ID` | Approved REQ | +| REQ 변경 관리 | `.github/skills/change-req/SKILL.md` | `REQ-ID` | Change Log 기반 REQ 갱신 | +| REQ 구현 완료 전환 | `.github/skills/confirm-req-implemented/SKILL.md` | `REQ-ID` 또는 `BAT-ID` | Implemented REQ | +| Batch 추천 | `.github/skills/suggest-batch-reqs/SKILL.md` | `DISCOVERY_ID` 또는 REQ 목록 | batch 후보안 | +| Batch 생성 | `.github/skills/draft-batch/SKILL.md` | Approved REQ 목록 | `docs/batches//` | +| Delivery 진행 | `.github/skills/draft-batch-planning/SKILL.md`, `.github/skills/draft-batch-design/SKILL.md`, `.github/skills/run-batch-implementation/SKILL.md`, `.github/skills/draft-batch-verification/SKILL.md`, `.github/skills/confirm-batch-verification/SKILL.md` | `BAT-ID` | batch 문서와 상태 | +| Release 진행 | `.github/skills/draft-release/SKILL.md`, `.github/skills/confirm-release/SKILL.md`, `.github/skills/capture-release-feedback/SKILL.md` | `BAT-ID` 또는 `REL-ID` | release 문서와 운영 환류 | +| 다음 Discovery 추천 | `.github/skills/suggest-next-discovery/SKILL.md` | `REL-ID` 또는 없음 | follow-up 후보안 | +| 상위 orchestration | `.github/skills/run-sdlc/SKILL.md` | Discovery/REQ/Batch/Release 식별자 | 다음 skill 라우팅 | + +## 5. 금지 사항 + +- active 설명 문서에서 `.github/prompts/`를 기준 경로로 안내하지 않는다. +- `review-*`를 별도 active 단계로 서술하지 않는다. +- `docs/sdlc/`를 신규 active 산출물 경로로 사용하지 않는다. \ No newline at end of file diff --git a/source/instruction.md b/source/instruction.md new file mode 100644 index 0000000..623cf73 --- /dev/null +++ b/source/instruction.md @@ -0,0 +1,80 @@ +# Copilot Instructions + +이 파일은 호스트 프로젝트 고유 규칙과 StagePilot 규칙을 함께 담는다. + +## Host Project Rules + +호스트 프로젝트 전용 규칙은 이 섹션에 작성한다. + +## StagePilot + +# SDLC Governance Instructions + +이 워크스페이스의 AI 에이전트는 Agile 중심 반복형 SDLC를 따른다. +모든 작업은 3-phase 구조와 그 안의 단위별 게이트를 기준으로 수행하며, 임의 단계 스킵은 허용되지 않는다(긴급 핫픽스 예외는 별도 규칙 적용). + +## Lifecycle Phases + +1. Requirements +2. Delivery +3. Release & Feedback + +## Governing Units + +1. Discovery: 문제 정의 단위 +2. REQ: 승인 가능한 요구사항 단위 +3. Batch: 함께 계획, 설계, 구현, 검증하는 delivery 단위 +4. Release: 함께 배포하고 운영 환류를 기록하는 단위 + +## Bootstrap Baseline Path + +- 프로젝트 시작 시 baseline 문서와 active index는 `bootstrap-baseline`으로 먼저 초기화한다. +- `bootstrap-baseline`은 3-phase lifecycle 밖의 bootstrap 단계이며 별도 governance unit가 아니다. +- 첫 real Discovery는 baseline 초기화 이후 실제 제품/서비스 변경 주제로 시작한다. + +## Default Flow + +1. Bootstrap: `bootstrap-baseline` (baseline 문서와 active index가 비어 있을 때만) +2. Requirements: `new-discovery` -> `confirm-discovery` -> `draft-req` -> `confirm-req` -> `suggest-batch-reqs` -> `draft-batch` +3. Delivery: `draft-batch-planning` -> `draft-batch-design` -> `run-batch-implementation` -> `draft-batch-verification` -> `confirm-batch-verification` -> `confirm-req-implemented` +4. Release & Feedback: `draft-release` -> `confirm-release` -> `capture-release-feedback` + +## Lightweight Change Path + +- 단일 저위험 `Approved` REQ이고 구조/인터페이스/런타임 흐름 영향이 없으면 `minor-change` fast path를 사용할 수 있다. +- 이 경로는 `suggest-batch-reqs`를 생략하고 `draft-batch`로 바로 `batch-lite` batch를 만든다. +- `batch-lite`는 planning부터 시작하고, design은 필요할 때만 생성한다. +- release는 `docs-only`, `tooling`, `app-service` profile 중 하나를 선택해 검증 강도를 조정한다. + +## Change Management Extension + +- 기존 REQ 본문을 수정할 때는 `change-req`를 사용해 Change Log 근거를 먼저 남긴다. +- release feedback의 후속 입력은 `Discovery Input`, `REQ Input`, `Change Request Input`으로 분리해 기록한다. +- 다음 반복 후보만 먼저 정리하려면 `suggest-next-discovery`를 사용하고, 실제 문서 생성은 `new-discovery`로 이어 간다. + +## Stage Mapping + +- Discovery는 Requirements phase 안에서 유지한다. +- Planning, Design, Implementation, Verification은 Batch 내부 stage로 다룬다. +- Release와 Operations는 Release & Feedback phase 내부에서 다룬다. +- `review-*` stage command는 더 이상 active flow에 포함하지 않고 대응 `confirm-*` 절차에 흡수한다. +- prompt 기반 stage command는 active 경로에서 사용하지 않고 `.stage-pilot/skills/` 아래 skill entrypoint만 사용한다. + +## Global Rules + +1. 증거 기반: 요구사항, 가정, 리스크, 결정 근거를 문서에 남긴다. +2. 게이트 준수: 각 phase와 단위의 DoR(진입 조건)과 DoD(완료 조건)을 충족해야 다음 단계로 이동한다. +3. 테스트 우선 검증: 구현 결과는 테스트 또는 검증 근거 없이 완료로 간주하지 않는다. +4. 변경 영향도 기록: 코드/문서/운영 영향 범위를 명시한다. +5. 불확실성 우선 해소: 차단 이슈가 있으면 임의 추정 대신 질문 또는 추가 탐색을 수행한다. +6. 운영 환류: 운영 단계 인사이트는 다음 Discovery의 입력으로 반드시 반영한다. + +## Hotfix Exception Path + +긴급 핫픽스는 Requirements/Delivery 설계를 경량화할 수 있다. 단, 아래는 필수다. + +1. 최소 요구사항 정의(증상, 영향, 성공 조건) +2. 최소 검증(재현 테스트 또는 핵심 시나리오 확인) +3. 배포 전 롤백 계획 +4. 배포 후 Postmortem 작성 및 다음 스프린트 환류 + diff --git a/templates/batches/batch-index.md b/templates/batches/batch-index.md new file mode 100644 index 0000000..8aca9a2 --- /dev/null +++ b/templates/batches/batch-index.md @@ -0,0 +1,25 @@ +# {{BAT_ID}}: <배치 제목> + +- Status: draft | in-delivery | release-candidate | released | archived +- Profile: standard | batch-lite +- Source Discovery: +- Included REQ: +- Excluded REQ: + +## Summary +- <이 batch를 왜 묶었는지> + +## Scope +- In Scope: + - <포함 범위> +- Out of Scope: + - <제외 범위> + +## Documents +- Planning: [planning.md](./planning.md) +- Design: <`[design.md](./design.md)` 또는 `none (batch-lite with no architecture impact)`> +- Implementation: <`[implementation.md](./implementation.md)` 또는 `create at implementation stage`> +- Verification: <`[verification.md](./verification.md)` 또는 `create at verification stage`> + +## Notes +- <공통 리스크, 공통 결정 사항> diff --git a/templates/batches/design.md b/templates/batches/design.md new file mode 100644 index 0000000..87d718b --- /dev/null +++ b/templates/batches/design.md @@ -0,0 +1,26 @@ +# {{BAT_ID}} Design + +- Status: draft | confirmed +- Batch: {{BAT_ID}} + +## Architecture Summary +- <배치 수준 설계 요약> + +## Changed Areas +- Docs: <문서 경로> +- Modules: <모듈/컴포넌트> +- Interfaces: <인터페이스> + +## Key Decisions +1. <결정 1> +2. <결정 2> + +## Edge Cases +- <예외 처리 및 설계 경계> + +## Architecture Impact +- + +## Reference Doc Update Plan +- project-structure.md: <갱신 계획 또는 none> +- runtime-flows.md: <갱신 계획 또는 none> diff --git a/templates/batches/implementation.md b/templates/batches/implementation.md new file mode 100644 index 0000000..d69112e --- /dev/null +++ b/templates/batches/implementation.md @@ -0,0 +1,20 @@ +# {{BAT_ID}} Implementation + +- Status: draft | in-progress | completed +- Batch: {{BAT_ID}} + +## Plan Summary +- <이번 구현에서 실제 변경할 범위> + +## Changed Files +- <파일 목록> + +## Execution Log +1. <실행 항목> +2. <실행 항목> + +## Validation +- <실행한 테스트, lint, typecheck, 수동 점검> + +## Remaining Risks +- <잔여 이슈> diff --git a/templates/batches/index.md b/templates/batches/index.md new file mode 100644 index 0000000..158750c --- /dev/null +++ b/templates/batches/index.md @@ -0,0 +1,10 @@ +# Batch Index + +## 목적 +- 이 문서는 batch delivery 단위의 전역 인덱스다. +- 각 batch는 `docs/batches//` 아래에서 profile(`standard` | `batch-lite`)에 맞는 산출물을 관리한다. + +## Batch Register + +| BAT ID | Profile | Status | Included REQ | Discovery | Folder | +| --- | --- | --- | --- | --- | --- | diff --git a/templates/batches/planning.md b/templates/batches/planning.md new file mode 100644 index 0000000..324adbd --- /dev/null +++ b/templates/batches/planning.md @@ -0,0 +1,28 @@ +# {{BAT_ID}} Planning + +- Status: draft | confirmed +- Batch: {{BAT_ID}} +- Profile: standard | batch-lite + +## Included REQ +- + +## Delivery Plan +- <구현 범위, 순서, 선행 조건> + +## Dependencies +- Internal: + - <내부 의존성> +- External: + - <외부 의존성> + +## Design Gate +- Design Required Before Implementation: yes | no +- Reason: <왜 즉시 design이 필요한지 또는 왜 생략 가능한지> + +## Milestones +1. <마일스톤> +2. <마일스톤> + +## Risks +- <리스크와 대응> diff --git a/templates/batches/verification.md b/templates/batches/verification.md new file mode 100644 index 0000000..0a7971e --- /dev/null +++ b/templates/batches/verification.md @@ -0,0 +1,21 @@ +# {{BAT_ID}} Verification + +- Status: draft | confirmed +- Batch: {{BAT_ID}} + +## Acceptance Mapping +- : + - + +## Evidence +- Test: + - <테스트 결과> +- Manual: + - <수동 검증 결과> +- Logs: + - <로그 또는 스크린샷 경로> + +## Result +- Ready for Release: false +- Blocking Issues: + - <차단 이슈 또는 없음> diff --git a/templates/bootstrap/baseline-seed.yaml b/templates/bootstrap/baseline-seed.yaml new file mode 100644 index 0000000..41c3cbf --- /dev/null +++ b/templates/bootstrap/baseline-seed.yaml @@ -0,0 +1,95 @@ +schema_version: 1 +capture_mode: declared # declared | observed | mixed + +project: + repository_name: {{REPOSITORY_NAME:repo-folder-name}} + display_name: {{PROJECT_NAME:display name or repo name}} + summary: {{PROJECT_SUMMARY:one-sentence project summary}} + primary_domain: {{PRIMARY_DOMAIN:short domain phrase or undecided}} + +stack: + tech_stack: + - {{TECH_STACK_ITEM_1:primary language or framework}} + +runtime: + primary_runtime: {{PRIMARY_RUNTIME:cli|api-service|web-app|worker|library|mixed|other}} + primary_entrypoints: + - name: {{ENTRYPOINT_NAME_1:command or interface name}} + purpose: {{ENTRYPOINT_PURPOSE_1:brief purpose}} + +interfaces: + contracts: + - name: {{INTERFACE_NAME_1:primary interface name}} + type: {{INTERFACE_TYPE_1:http-api|cli|event|file|internal-service|other}} + actors: {{INTERFACE_ACTORS_1:who calls or receives it}} + purpose: {{INTERFACE_PURPOSE_1:what this interface exists to do}} + stability: {{INTERFACE_STABILITY_1:experimental|internal|stable}} + inputs: + - {{INTERFACE_INPUT_1_1:input contract or invocation note}} + outputs: + - {{INTERFACE_OUTPUT_1_1:output contract or artifact}} + errors: + - {{INTERFACE_ERROR_1_1:error code or failure behavior}} + compatibility_rules: + - {{INTERFACE_COMPATIBILITY_1_1:compatibility or versioning rule}} + - name: {{INTERFACE_NAME_2:secondary interface name}} + type: {{INTERFACE_TYPE_2:http-api|cli|event|file|internal-service|other}} + actors: {{INTERFACE_ACTORS_2:who calls or receives it}} + purpose: {{INTERFACE_PURPOSE_2:what this interface exists to do}} + stability: {{INTERFACE_STABILITY_2:experimental|internal|stable}} + inputs: + - {{INTERFACE_INPUT_2_1:input contract or invocation note}} + outputs: + - {{INTERFACE_OUTPUT_2_1:output contract or artifact}} + errors: + - {{INTERFACE_ERROR_2_1:error code or failure behavior}} + compatibility_rules: + - {{INTERFACE_COMPATIBILITY_2_1:compatibility or versioning rule}} + shared_constraints: + - {{INTERFACE_SHARED_CONSTRAINT_1:cross-cutting interface constraint}} + current_gaps: + - {{INTERFACE_GAP_1:known contract gap or planned change}} + +structure: + planned_top_level_areas: + - path: {{TOP_LEVEL_PATH_1:path such as src/ or app/}} + responsibility: {{TOP_LEVEL_RESPONSIBILITY_1:brief responsibility}} + +data_model: + persistence_backend: {{PERSISTENCE_BACKEND:primary persistence backend or storage system}} + entities: + - name: {{ENTITY_NAME_1:primary entity}} + purpose: {{ENTITY_PURPOSE_1:what this entity represents}} + source_of_truth: {{ENTITY_SOURCE_1:table, collection, file, or service}} + lifecycle: {{ENTITY_LIFECYCLE_1:created -> updated -> archived}} + key_fields: + - {{ENTITY_FIELD_1_1:field :: type :: meaning}} + relationships: + - {{ENTITY_RELATIONSHIP_1_1:related entity :: relationship meaning}} + state_rules: + - {{ENTITY_STATE_RULE_1_1:state or invariant rule}} + - name: {{ENTITY_NAME_2:secondary entity}} + purpose: {{ENTITY_PURPOSE_2:what this entity represents}} + source_of_truth: {{ENTITY_SOURCE_2:table, collection, file, or service}} + lifecycle: {{ENTITY_LIFECYCLE_2:created -> updated -> archived}} + key_fields: + - {{ENTITY_FIELD_2_1:field :: type :: meaning}} + relationships: + - {{ENTITY_RELATIONSHIP_2_1:related entity :: relationship meaning}} + state_rules: + - {{ENTITY_STATE_RULE_2_1:state or invariant rule}} + consistency_rules: + - {{CONSISTENCY_RULE_1:consistency or referential rule}} + persistence_notes: + - {{PERSISTENCE_NOTE_1:mapping, storage, or migration note}} + current_gaps: + - {{MODEL_GAP_1:known model gap or planned schema change}} + +notes: + known_unknowns: + - {{KNOWN_UNKNOWN_1:optional unresolved bootstrap item or none}} + +metadata: + captured_at_kst: {{CAPTURED_AT_KST:YYYY-MM-DD HH:MM}} + source: bootstrap-baseline-questionnaire + updated_from: {{UPDATED_FROM:user-answer|repo-observation|mixed}} \ No newline at end of file diff --git a/templates/data-model.md b/templates/data-model.md new file mode 100644 index 0000000..2709090 --- /dev/null +++ b/templates/data-model.md @@ -0,0 +1,88 @@ +# {{PROJECT_NAME}} Data Model + +- Baseline Mode: {{BASELINE_MODE:declared|observed|mixed}} +- Baseline Seed: {{BASELINE_SEED_PATH:.stage-pilot/bootstrap/baseline.yaml}} +- Status: draft | confirmed +- Owner: {{OWNER_OR_TEAM}} +- Last Updated (KST): {{UPDATED_AT_KST}} +- Source Discovery / Batch: {{SOURCE_REF}} + +## Project Summary +- {{PROJECT_SUMMARY:one-sentence summary of the project}} + +## Primary Domain +- {{PRIMARY_DOMAIN:short domain phrase or undecided}} + +## Primary Persistence Backend +- {{PERSISTENCE_BACKEND:primary persistence backend or storage system}} + +## Purpose +- This document captures the approved cross-cutting data model baseline for the project. +- It is a shared reference for REQ drafting, batch design, implementation, verification, migration planning, and handoff. + +## Scope +- Covers core entities, relationships, states, persistence mappings, and shared consistency rules. +- Does not duplicate full functional acceptance criteria already owned by REQ documents. +- Does not replace ORM definitions, migration files, or generated schema artifacts. + +## Model Summary + +{{MODEL_SUMMARY_BULLETS:- - :: }} + +## Entity: `{{ENTITY_NAME_1:primary entity}}` + +- Purpose: {{ENTITY_PURPOSE_1:what this entity represents}} +- Source of Truth: {{ENTITY_SOURCE_1:table, collection, file, or service}} +- Lifecycle: {{ENTITY_LIFECYCLE_1:created -> updated -> archived}} + +### Key Fields + +{{ENTITY_FIELDS_1:- :: :: }} + +### Relationships + +{{ENTITY_RELATIONSHIPS_1:- :: }} + +### State Rules + +{{ENTITY_STATE_RULES_1:- }} + +## Entity: `{{ENTITY_NAME_2:secondary entity}}` + +- Purpose: {{ENTITY_PURPOSE_2:what this entity represents}} +- Source of Truth: {{ENTITY_SOURCE_2:table, collection, file, or service}} +- Lifecycle: {{ENTITY_LIFECYCLE_2:created -> updated -> archived}} + +### Key Fields + +{{ENTITY_FIELDS_2:- :: :: }} + +### Relationships + +{{ENTITY_RELATIONSHIPS_2:- :: }} + +### State Rules + +{{ENTITY_STATE_RULES_2:- }} + +## Shared Consistency Rules + +{{CONSISTENCY_RULE_BULLETS:- - }} + +## Persistence / Integration Notes + +{{PERSISTENCE_NOTES_BULLETS:- - }} + +## Current Gaps / Planned Changes + +{{MODEL_CURRENT_GAPS_BULLETS:- - }} + +## Update Triggers + +- Update this document when a core entity, key field, or relationship changes. +- Update this document when lifecycle or persistence behavior changes. +- Update this document when a batch introduces a migration, backfill, or breaking data assumption. + +## Change Log + +{{CHANGE_LOG_BULLETS:- - }} \ No newline at end of file diff --git a/templates/discovery/discovery.md b/templates/discovery/discovery.md new file mode 100644 index 0000000..dc3c13d --- /dev/null +++ b/templates/discovery/discovery.md @@ -0,0 +1,224 @@ +# 0. 문서 상태 +이 문서의 현재 상태와 식별 정보를 기록한다. 문서 추적과 승인 흐름에 필요한 최소 메타데이터를 적는다. + +- 상태: {{DOC_STATUS:draft|review|confirmed}} +- 문서 ID: {{DISCOVERY_ID:dcy-<3자리>__}} +- 이슈명: {{ISSUE_NAME:짧은 한글 또는 영문 이슈명}} +- 작성 시각(KST): {{CREATED_AT_KST:TBD}} +- 마지막 갱신 시각(KST): {{UPDATED_AT_KST:TBD}} +- 대체됨: {{SUPERSEDED_BY_DISCOVERY:없음 또는 docs/discovery/.md}} +- 후속 Discovery 참조: {{FOLLOW_UP_DISCOVERY_REF:없음 또는 docs/discovery/.md}} +- 생성된 REQ 참조: 없음 + +# 1. 계획 상태 요약 +현재 Discovery가 어떤 배경에서 작성되고 있는지, 연계 문서가 있는지, 지금 시점의 해석이 무엇인지 짧게 요약한다. + +- 연계 문서 현황: + - 관련 점수 파일: {{RELATED_SCORE_FILE:미생성 또는 경로}} + - 관련 TODO/메모: {{TODO_FILE:경로 또는 없음}} +- 해석: + - {{SUMMARY_LINE_1:현재 검토/작성 배경 요약}} + - {{SUMMARY_LINE_2:이번 Discovery에서 판단해야 할 핵심 요약}} + +# 2. 요구사항 판정 결과 +이번 요청이 기존 요구사항과 비교해 신규인지, 유사한지, 중복인지 판단하고 그 근거를 남긴다. + +- 판정: {{REQUIREMENT_CLASSIFICATION:완전 중복|유사|신규}} +- 근거 + - {{CLASSIFICATION_REASON_1}} + - {{CLASSIFICATION_REASON_2}} + +> **판정이 `완전 중복`인 경우**: 이하 섹션 작성을 중단하고 `# 9. 파일 처리 결과`에 중복 판정 사유와 참조 문서 경로를 기록한 뒤 문서를 닫는다. REQ 초안 작성으로 넘기지 않는다. + +# 3. 문제점의 요약 +문제의 현재 상태, 기대 상태, 관련 이해관계자, 제약사항을 정리한다. Discovery의 핵심 맥락을 가장 먼저 이해할 수 있어야 한다. + +- 현재 상태 + - {{CURRENT_STATE_1}} + - {{CURRENT_STATE_2}} +- 기대 상태 + - {{TARGET_STATE_1}} + - {{TARGET_STATE_2}} +- 이해관계자 + - {{STAKEHOLDER_1:예. 문서 작성자(AI agent)}} + - {{STAKEHOLDER_2:예. 리뷰어/승인자}} + - {{STAKEHOLDER_3:예. 운영 담당자}} +- 제약사항 + - {{CONSTRAINT_1}} + - {{CONSTRAINT_2}} + +## 주요 사용자 시나리오 +사용자 또는 운영자가 실제로 어떤 상황에서 이 변경을 필요로 하는지 대표 시나리오를 적는다. + +- {{USER_SCENARIO_1}} +- {{USER_SCENARIO_2}} +- {{USER_SCENARIO_3}} + +# 4. 이번에 정의할 변경 +이번 Discovery에서 다루는 핵심 변경 항목을 목록으로 적는다. 기능, 구조, 규칙 변경을 함께 포함해도 된다. + +- {{CHANGE_ITEM_1}} +- {{CHANGE_ITEM_2:필요 없으면 삭제}} +- {{CHANGE_ITEM_3:필요 없으면 삭제}} + +## 영향 범위 +변경이 미칠 코드/문서/운영 범위를 명시해 REQ 정제와 batch planning에서 추정 누락을 줄인다. + +- 코드 영향: + - {{CODE_IMPACT_1:예. docs/discovery/index.md 갱신 로직}} + - {{CODE_IMPACT_2:필요 없으면 삭제}} +- 문서 영향: + - {{DOC_IMPACT_1}} + - {{DOC_IMPACT_2:필요 없으면 삭제}} +- 운영 영향: + - {{OPS_IMPACT_1:예. 배포/운영 절차 변경 없음}} + - {{OPS_IMPACT_2:필요 없으면 삭제}} + +# 5. 요구사항 목록 +실제로 구현과 검증으로 이어질 요구사항을 기능/비기능으로 나누어 정리하고, 범위 경계를 명확히 적는다. + +## 기능 요구사항 +- FR-1: {{FR_1}} +- FR-2: {{FR_2}} +- FR-3: {{FR_3:필요 없으면 삭제}} +- FR-4: {{FR_4:필요 없으면 삭제}} + +## 비기능 요구사항 +성능, 운영성, 일관성, 자동화 안정성 등 기능 외 요구사항을 적는다. + +- NFR-1: {{NFR_1}} +- NFR-2: {{NFR_2}} +- NFR-3: {{NFR_3:필요 없으면 삭제}} + +## 범위 경계 +이번 반복에서 반드시 포함할 범위와 명시적으로 제외할 범위를 구분한다. + +- In Scope + - {{IN_SCOPE_1}} + - {{IN_SCOPE_2}} +- Out of Scope + - {{OUT_OF_SCOPE_1}} + - {{OUT_OF_SCOPE_2}} + +# 6. 리스크/가정 목록 +불확실성과 전제 조건을 분리해서 적는다. 리스크는 영향과 완화 방안을, 가정은 현재 판단의 전제를 기록한다. + +## 리스크 +- R-1 ({{RISK_LEVEL_1:High|Medium|Low}}): {{RISK_1}} + - 완화 방안: {{MITIGATION_1}} +- R-2 ({{RISK_LEVEL_2:High|Medium|Low}}): {{RISK_2}} + - 완화 방안: {{MITIGATION_2}} +- R-3 ({{RISK_LEVEL_3:High|Medium|Low}}): {{RISK_3:필요 없으면 삭제}} + - 완화 방안: {{MITIGATION_3:필요 없으면 삭제}} + +## 가정 +아직 확정되지 않았지만 현재 문서 작성의 기반이 되는 전제를 적는다. + +- A-1: {{ASSUMPTION_1}} +- A-2: {{ASSUMPTION_2}} +- A-3: {{ASSUMPTION_3:필요 없으면 삭제}} + +## 오픈 질문 +답을 아직 모르는 항목을 적는다. 추정으로 덮지 말고 REQ 초안 작성 이전에 확인할 질문으로 남긴다. + +- OQ-1: {{OPEN_QUESTION_1}} + - 상태: {{OPEN_QUESTION_STATUS_1:Open|Answered|Deferred|Dropped}} + - 처리 방안: {{OPEN_QUESTION_ACTION_1:누가 어떤 방식으로 결정/확인할지}} + - 종료 조건: {{OPEN_QUESTION_EXIT_1:언제 이 질문을 Answered/Deferred/Dropped로 처리할지}} +- OQ-2: {{OPEN_QUESTION_2}} + - 상태: {{OPEN_QUESTION_STATUS_2:Open|Answered|Deferred|Dropped}} + - 처리 방안: {{OPEN_QUESTION_ACTION_2:누가 어떤 방식으로 결정/확인할지}} + - 종료 조건: {{OPEN_QUESTION_EXIT_2:언제 이 질문을 Answered/Deferred/Dropped로 처리할지}} +- OQ-3: {{OPEN_QUESTION_3:필요 없으면 삭제}} + - 상태: {{OPEN_QUESTION_STATUS_3:Open|Answered|Deferred|Dropped}} + - 처리 방안: {{OPEN_QUESTION_ACTION_3:누가 어떤 방식으로 결정/확인할지}} + - 종료 조건: {{OPEN_QUESTION_EXIT_3:언제 이 질문을 Answered/Deferred/Dropped로 처리할지}} + +### 오픈 질문 처리 기준 +- `Open`: 아직 답이 없고 다음 단계 진행에 영향이 있는 상태다. 문서에 유지한다. +- `Answered`: 답이 확정된 상태다. 관련 본문/요구사항에 반영한 뒤 오픈 질문 목록에서는 제거하거나 resolved 기록으로 이동한다. +- `Deferred`: 지금 단계에서 결정하지 않고 다음 단계 또는 후속 이슈로 넘기는 상태다. 넘길 대상과 이유를 반드시 적는다. +- `Dropped`: 더 이상 유효하지 않은 질문이다. 삭제 사유를 짧게 남기고 목록에서 제거한다. +- 처리 원칙 1: 질문만 남기지 말고 반드시 `상태`, `처리 방안`, `종료 조건`을 함께 적는다. +- 처리 원칙 2: 답이 나온 질문은 본문 반영 없이 단순 삭제하지 않는다. 반영 위치를 먼저 갱신한다. +- 처리 원칙 3: High 영향 질문은 `Deferred` 처리 후 처리 방안에 REQ 인계 추적 방식을 명시한다. + +# 7. 초기 성공 기준 +이 변경이 성공했다고 판단할 기준을 측정 가능하게 정의한다. 가능하면 데이터 출처까지 연결한다. + +- S-1: {{SUCCESS_METRIC_1}} +- S-2: {{SUCCESS_METRIC_2}} +- S-3: {{SUCCESS_METRIC_3:필요 없으면 삭제}} + +## 측정 방식 및 데이터 출처 +각 성공 기준을 어떻게 측정할지와 어떤 데이터나 문서를 근거로 쓸지 적는다. + +- S-1 측정 방식: {{MEASURE_METHOD_1}} / 데이터 출처: {{MEASURE_SOURCE_1}} +- S-2 측정 방식: {{MEASURE_METHOD_2}} / 데이터 출처: {{MEASURE_SOURCE_2}} +- S-3 측정 방식: {{MEASURE_METHOD_3:필요 없으면 삭제}} / 데이터 출처: {{MEASURE_SOURCE_3:필요 없으면 삭제}} + +# 8. REQ로 넘기기 전 확인 체크 +REQ 초안 작성 단계로 넘기기 전에 무엇이 확정되어야 하는지 확인한다. 미완료 항목은 후속 결정이 필요함을 뜻한다. + +- [ ] 범위(In Scope/Out of Scope) 확정 + - 기준: {{CHECK_SCOPE_CRITERIA}} +- [ ] 성공 지표의 측정 방식/데이터 출처 확정 + - 기준: {{CHECK_METRICS_CRITERIA}} +- [ ] High 영향 리스크 우선순위 및 해소 책임자 지정 + - 기준: {{CHECK_RISK_OWNER_CRITERIA}} +- [ ] 모든 오픈 질문(OQ) `Open` 상태 없음 확인 + - 기준: OQ 목록에 `Open` 상태 항목이 0개이거나, 잔여 항목 전부 `Deferred` 처리 완료 +- [ ] Freeze 확정 담당자(CONFIRMED_BY) 지정 + - 기준: {{CHECK_CONFIRMEDBY_CRITERIA}} + +# 9. 파일 처리 결과 +이 Discovery 문서 생성/갱신 결과와 참조 문서를 기록한다. 이후 추적과 리뷰 시 근거로 사용한다. + +- 처리 결과: {{FILE_RESULT:생성|갱신|미생성}} +- 생성 경로: {{OUTPUT_PATH}} +- 참조 문서: + - {{REFERENCE_1}} + - {{REFERENCE_2}} + - {{REFERENCE_3:필요 없으면 삭제}} + +# 10. 사용자 결정 필요 항목 요약 +사용자나 승인자가 결정해야 하는 항목만 모아 정리한다. 결정, 확인, 데이터 요청을 구분해서 적는다. + +## DECIDE +- {{DECIDE_ITEM_1}} +- {{DECIDE_ITEM_2:필요 없으면 삭제}} + +## CONFIRM +- {{CONFIRM_ITEM_1}} +- {{CONFIRM_ITEM_2:필요 없으면 삭제}} + +## DATA +- {{DATA_ITEM_1}} +- {{DATA_ITEM_2:필요 없으면 삭제}} + +# 11. Discovery Freeze +REQ 초안 작성 인계 시점의 확정 플래그와 섹션 참조만 기록한다. 내용 재작성 없이 원본 섹션을 링크로 대신한다. + +## 섹션 참조 +- 범위: [# 5. 요구사항 목록 > 범위 경계](#5-요구사항-목록) +- 리스크/오픈 질문: [# 6. 리스크/가정 목록](#6-리스크가정-목록) +- 성공 기준: [# 7. 초기 성공 기준](#7-초기-성공-기준) +- 인계 전 확인: [# 8. REQ로 넘기기 전 확인 체크](#8-req로-넘기기-전-확인-체크) +- 사용자 결정 필요: [# 10. 사용자 결정 필요 항목 요약](#10-사용자-결정-필요-항목-요약) + +## Freeze 플래그 +- Handoff Decision: {{FREEZE_HANDOFF_DECISION:REQ Drafting 진행 가능|보류}} +- Handoff Rationale: {{FREEZE_HANDOFF_RATIONALE:진행 가능 또는 보류 판단 근거 1-2줄}} +- Ready for REQ Drafting: {{READY_FOR_REQ_DRAFTING:false}} +- Confirmed By: {{CONFIRMED_BY:TBD}} +- Confirmed At (KST): {{CONFIRMED_AT_KST:TBD}} + +# 12. 작성 가이드 +템플릿 사용 시의 기본 규칙을 적는다. 실제 문서를 만들 때는 이 가이드를 기준으로 플레이스홀더를 치환한다. + +- 이 파일은 템플릿이다. 실제 문서 작성 시 `{{...}}` 플레이스홀더를 구체 값으로 교체한다. +- 필요 없는 항목은 삭제하고, 부족한 항목은 같은 형식으로 추가한다. +- Open Questions는 가정으로 대체하지 말고 그대로 유지한다. +- `대체됨`은 현재 Discovery가 더 이상 기준 문서가 아닐 때 대체 Discovery 경로를 기록하고, 해당 사항이 없으면 `없음`으로 둔다. +- `후속 Discovery 참조`는 범위를 이어받는 별도 Discovery가 생성된 경우 그 Discovery 경로를 기록하고, 없으면 `없음`으로 둔다. +- Discovery Freeze는 문서 하단에 반드시 유지한다. \ No newline at end of file diff --git a/templates/discovery/index.md b/templates/discovery/index.md new file mode 100644 index 0000000..8c9f028 --- /dev/null +++ b/templates/discovery/index.md @@ -0,0 +1,8 @@ +# Discovery 전체 인덱스 + + + +## Discovery 목록 + +| ID | 날짜 | 이슈명 | 문서 | 상태 | +|---|---|---|---|---| \ No newline at end of file diff --git a/templates/interface-contract.md b/templates/interface-contract.md new file mode 100644 index 0000000..48bdbb8 --- /dev/null +++ b/templates/interface-contract.md @@ -0,0 +1,92 @@ +# {{PROJECT_NAME}} Interface Contract + +- Baseline Mode: {{BASELINE_MODE:declared|observed|mixed}} +- Baseline Seed: {{BASELINE_SEED_PATH:.stage-pilot/bootstrap/baseline.yaml}} +- Status: draft | confirmed +- Owner: {{OWNER_OR_TEAM}} +- Last Updated (KST): {{UPDATED_AT_KST}} +- Source Discovery / Batch: {{SOURCE_REF}} +- Primary Runtime: {{PRIMARY_RUNTIME:cli|api-service|web-app|worker|library|mixed|other}} + +## Project Summary +- {{PROJECT_SUMMARY:one-sentence summary of the project}} + +## Primary Domain +- {{PRIMARY_DOMAIN:short domain phrase or undecided}} + +## Purpose +- This document captures the approved external and cross-boundary interface contracts for the project. +- It is a cross-cutting reference for REQ drafting, batch design, implementation, verification, and handoff. + +## Scope +- Covers externally visible APIs, CLI surfaces, events, files, or batch I/O contracts. +- Does not duplicate full business requirements already owned by REQ documents. +- Does not replace low-level code references or generated API artifacts. + +## Interface Summary + +{{INTERFACE_SUMMARY_BULLETS:- - :: }} + +## Interface: `{{INTERFACE_NAME_1:primary interface name}}` + +- Type: {{INTERFACE_TYPE_1:http-api|cli|event|file|internal-service|other}} +- Consumers / Producers: {{INTERFACE_ACTORS_1:who calls or receives it}} +- Purpose: {{INTERFACE_PURPOSE_1:what this interface exists to do}} +- Stability: {{INTERFACE_STABILITY_1:experimental|internal|stable}} + +### Inputs + +{{INTERFACE_INPUTS_1:- :: }} + +### Outputs + +{{INTERFACE_OUTPUTS_1:- :: }} + +### Error Contract + +{{INTERFACE_ERRORS_1:- :: }} + +### Compatibility Rules + +{{INTERFACE_COMPATIBILITY_1:- }} + +## Interface: `{{INTERFACE_NAME_2:secondary interface name}}` + +- Type: {{INTERFACE_TYPE_2:http-api|cli|event|file|internal-service|other}} +- Consumers / Producers: {{INTERFACE_ACTORS_2:who calls or receives it}} +- Purpose: {{INTERFACE_PURPOSE_2:what this interface exists to do}} +- Stability: {{INTERFACE_STABILITY_2:experimental|internal|stable}} + +### Inputs + +{{INTERFACE_INPUTS_2:- :: }} + +### Outputs + +{{INTERFACE_OUTPUTS_2:- :: }} + +### Error Contract + +{{INTERFACE_ERRORS_2:- :: }} + +### Compatibility Rules + +{{INTERFACE_COMPATIBILITY_2:- }} + +## Shared Constraints + +{{INTERFACE_SHARED_CONSTRAINTS_BULLETS:- - }} + +## Current Gaps / Planned Changes + +{{INTERFACE_CURRENT_GAPS_BULLETS:- - }} + +## Update Triggers + +- Update this document when an externally visible API, event, CLI, or file contract changes. +- Update this document when a batch changes compatibility, validation, or error behavior. +- Update this document when a new external consumer or producer is introduced. + +## Change Log + +{{CHANGE_LOG_BULLETS:- - }} \ No newline at end of file diff --git a/templates/project-structure.md b/templates/project-structure.md new file mode 100644 index 0000000..844d3ae --- /dev/null +++ b/templates/project-structure.md @@ -0,0 +1,64 @@ +# {{PROJECT_NAME}} Project Structure + +- Baseline Mode: {{BASELINE_MODE:declared|observed|mixed}} +- Baseline Seed: {{BASELINE_SEED_PATH:.stage-pilot/bootstrap/baseline.yaml}} +- Status: draft | confirmed +- Owner: {{OWNER_OR_TEAM}} +- Last Updated (KST): {{UPDATED_AT_KST}} +- Source Discovery / Batch: {{SOURCE_REF}} + +## Project Summary +- {{PROJECT_SUMMARY:one-sentence summary of the project}} + +## Primary Domain +- {{PRIMARY_DOMAIN:short domain phrase or undecided}} + +## Tech Stack +{{TECH_STACK_BULLETS:- +- }} + +## Primary Runtime +- Type: {{PRIMARY_RUNTIME:cli|api-service|web-app|worker|library|mixed|other}} +- Planned Entry Points: +{{PRIMARY_ENTRYPOINT_BULLETS:- :: }} + +## Purpose +- This document is the current approved repository and package structure baseline. +- It is a cross-cutting reference document for Discovery, REQ, Batch Design, Batch Verification, and implementation updates. + +## Scope +- Covers the top-level repository layout and package/module boundaries. +- Does not duplicate detailed business rules, acceptance criteria, or implementation-only function signatures. + +## Top-Level Structure + +```text +{{TOP_LEVEL_STRUCTURE_TREE:}} +``` + +## Package / Module Responsibilities + +{{MODULE_RESPONSIBILITY_BULLETS:- `` + - }} + +## Dependency Rules + +{{DEPENDENCY_RULE_BULLETS:- }} + +## Shared Boundaries + +{{SHARED_BOUNDARY_BULLETS:- }} + +## Current Gaps / Planned Changes + +{{CURRENT_GAPS_BULLETS:- }} + +## Update Triggers + +- Update this document when package boundaries change. +- Update this document when new top-level runtime or domain modules are added. +- Update this document when a batch changes the approved structure baseline. + +## Change Log + +{{CHANGE_LOG_BULLETS:- - }} diff --git a/templates/releases/index.md b/templates/releases/index.md new file mode 100644 index 0000000..1b66789 --- /dev/null +++ b/templates/releases/index.md @@ -0,0 +1,10 @@ +# Release Index + +## 목적 +- 이 문서는 release 단위의 전역 인덱스다. +- 각 release는 하나 이상의 release-candidate batch를 묶고, profile(`docs-only` | `tooling` | `app-service`)에 맞는 배포와 후속 환류를 추적한다. + +## Release Register + +| REL ID | Profile | Status | Included Batch | Discovery/REQ Input | Link | +| --- | --- | --- | --- | --- | --- | diff --git a/templates/releases/release.md b/templates/releases/release.md new file mode 100644 index 0000000..ce1e43c --- /dev/null +++ b/templates/releases/release.md @@ -0,0 +1,92 @@ +# {{REL_ID}}: <릴리즈 제목> + +- Status: draft | confirmed | released | feedback-captured +- Profile: docs-only | tooling | app-service +- Included Batch: + +## Scope +- Included: + - <포함 batch 또는 REQ> +- Excluded: + - <제외 범위> + +## Rollout Plan +1. <배포 순서> +2. <배포 순서> + +## Rollback Plan +- <롤백 조건과 절차> + +## Verification Checklist +- <배포 전 확인> +- <배포 후 확인> + +## Release Execution Evidence +- Release Status Transition Target: released + +### Deployment Facts +- Executed At: + - <배포 시작 시각> +- Completed At: + - <배포 완료 시각> +- Environment: + - <배포 대상 환경> +- Operator: + - <배포 수행자> +- Revision or Artifact: + - <커밋 SHA 또는 배포 artifact 식별자> +- Commands or Procedure: + - <실행 명령 또는 배포 절차 요약> + +### Profile-Specific Checks +- docs-only: + - Published or Packaged Targets: + - <문서 경로, 배포 산출물 또는 none> + - Link or Rendering Check: + - <링크 점검, 렌더링 확인 또는 none> + - Navigation or Consumer Update: + - <목차/가이드/릴리즈 노트 반영 결과 또는 none> +- tooling: + - Install or Upgrade Smoke: + - <설치/업데이트/부트스트랩 결과 또는 none> + - CLI or Script Execution: + - <명령 실행 결과 또는 none> + - Compatibility Note: + - <하위 호환성/운영자 영향 또는 none> +- app-service: + - Health Endpoint: + - <호출 결과와 확인 시각> + - Startup Log Fields: + - + - Request Log Fields: + - + - Smoke or Manual Verification: + - <배포 후 재실행한 검증과 결과> + +### Rollback Outcome +- Rollback Required: + - +- Rollback Trigger: + - <발생 시 원인, 없으면 none> +- Rollback Action: + - <수행한 조치, 없으면 none> + +### Release Decision Evidence +- Released Decision: + - +- Rationale: + - +- Recorded By: + - <기록자> +- Recorded At: + - <기록 시각> + +## Feedback Handoff +- Observation Summary: + - <운영 관찰 사실 요약 또는 없음> +- Discovery Input: + - <후속 discovery 후보> +- REQ Input: + - <후속 req 후보> +- Change Request Input: + - <기존 req 변경 후보> \ No newline at end of file diff --git a/templates/runtime-flows.md b/templates/runtime-flows.md new file mode 100644 index 0000000..0720694 --- /dev/null +++ b/templates/runtime-flows.md @@ -0,0 +1,58 @@ +# {{PROJECT_NAME}} Runtime Flows + +- Baseline Mode: {{BASELINE_MODE:declared|observed|mixed}} +- Baseline Seed: {{BASELINE_SEED_PATH:.stage-pilot/bootstrap/baseline.yaml}} +- Status: draft | confirmed +- Owner: {{OWNER_OR_TEAM}} +- Last Updated (KST): {{UPDATED_AT_KST}} +- Source Discovery / Batch: {{SOURCE_REF}} +- Primary Runtime: {{PRIMARY_RUNTIME:cli|api-service|web-app|worker|library|mixed|other}} + +## Project Summary +- {{PROJECT_SUMMARY:one-sentence summary of the project}} + +## Purpose +- This document is the current approved execution-flow baseline for the project. +- It is a cross-cutting reference document for Discovery, REQ, Batch Design, Batch Verification, and implementation updates. + +## Scope +- Covers representative command and runtime flows. +- Does not duplicate full acceptance criteria or low-level implementation details. + +## Covered Entry Points + +{{PRIMARY_ENTRYPOINT_BULLETS:- :: }} + +## Shared Components + +{{SHARED_COMPONENT_BULLETS:- }} + +## Flow: `{{FLOW_NAME_1:bootstrap or first runtime flow}}` + +{{FLOW_STEPS_1:1. +2. +3. }} + +## Flow: `{{FLOW_NAME_2:secondary runtime flow or operation}}` + +{{FLOW_STEPS_2:1. +2. +3. }} + +## Flow Constraints + +{{FLOW_CONSTRAINT_BULLETS:- }} + +## Current Gaps / Planned Changes + +{{CURRENT_GAPS_BULLETS:- }} + +## Update Triggers + +- Update this document when a command entry point changes. +- Update this document when a runtime orchestration path changes. +- Update this document when a batch changes the approved flow baseline. + +## Change Log + +{{CHANGE_LOG_BULLETS:- - }} diff --git a/templates/srs/index.md b/templates/srs/index.md new file mode 100644 index 0000000..67c789c --- /dev/null +++ b/templates/srs/index.md @@ -0,0 +1,87 @@ +# Software Requirement Specification Index + +## 목적 +- 이 문서는 프로젝트의 SRS 운영 기준과 requirement 목록을 관리하는 entry point이다. +- 개별 requirement의 상세 내용은 타입별 폴더 하위 `req-XXX_*.md` 파일에서 관리한다. + +## 사용 방법 +1. 새로운 requirement가 필요하면 `Next Requirement ID`를 확인하고 `docs/srs/req-template.md`를 복사해 `docs/srs//req-XXX_.md` 파일을 생성한다. +2. 초안 작성은 `draft-req`, 승인 전환은 `confirm-req`, 변경 대응은 `change-req`, 구현 완료 전환은 `confirm-req-implemented` skill을 기준으로 수행한다. +3. 작성 또는 수정 후 누락과 모호성을 점검한다. +4. requirement 추가 또는 변경 후 `Next Requirement ID`, `Requirement Register`, `Recent Change Log Summary`를 함께 갱신한다. +5. `Proposed` requirement를 승인하려면 승인 준비 상태와 blocker를 점검한다. +6. `Approved` requirement는 구현 이슈나 작업 항목으로 연결한다. +7. 구현과 검증이 끝난 requirement는 `confirm-req-implemented` skill로 완료 전환 가능 여부를 점검한다. +8. requirement 변경 시 `change-req` skill을 기준으로 해당 REQ 파일의 `Change Log`를 먼저 갱신한 뒤 본문을 수정한다. +9. 구현 전에는 관련 REQ 파일과 최근 `Change Log`를 기준으로 변경 범위를 판단한다. + +## 상태 정의 +- Proposed: 초안 상태이며 구현 기준으로 확정되지 않음 +- Approved: 구현 기준으로 승인됨 +- Implemented: 구현과 검증이 완료됨 +- Deprecated: 더 이상 유지하지 않음 + +## Status Transition Evidence + +### Proposed -> Approved +- `Status`, `Type`, `Priority`, `Owner`, `Intent`, `Requirement`, `Acceptance Criteria`, `Impacted Area`, `Change Log`가 모두 존재해야 한다. +- `Type`은 아래 분류 도표에서 정확하게 하나로 지정되어야 한다. +- `Acceptance Criteria`는 구현 판단에 사용할 수 있을 정도로 구체적이어야 한다. +- 미해결 assumption 또는 open question이 있으면 `Notes`에 명시하고, 승인 blocker 여부를 판단해야 한다. +- 승인 전환 시 해당 REQ 파일의 `Change Log`에 status 변경 사유를 기록해야 한다. + +### Approved -> Implemented +- 각 `Acceptance Criteria`에 대해 test, manual verification, 또는 구현 결과 중 최소 1개의 evidence가 있어야 한다. +- evidence는 requirement 문서 자체 또는 연결된 구현/검증 결과에서 추적 가능해야 한다. +- 미충족 `Acceptance Criteria`가 있으면 `Implemented`로 전환하지 않는다. +- 구현 완료 전환 시 잔여 리스크와 미검증 영역이 있으면 `Change Log`에 기록해야 한다. +- 상태 전환은 `confirm-req-implemented` skill을 기준으로 수행한다. + +### Requirement Change Handling +- 기존 REQ 본문(`Requirement`, `Acceptance Criteria`, `Impacted Area`)을 수정하기 전에는 새 `Change Log` 항목을 만든다. +- `Change Log`에는 최소한 `Acceptance Criteria Delta`, `Impacted Area Delta`, `Delivery Trace Delta`, `Revalidation Impact`, `Validation Plan`이 포함되어야 한다. +- 변경이 기존 구현 또는 verification evidence를 무효화하면 `Implemented` requirement는 `Approved`로 되돌린다. +- 변경이 승인 전제 자체를 다시 검토하게 만들면 `Approved` 또는 `Implemented` requirement를 `Proposed`로 되돌릴 수 있다. +- 상태 되돌림이 발생하면 `Requirement Register`와 `Recent Change Log Summary`도 함께 갱신한다. + +## Requirement 타입 분류 도표 + +| 타입 | 정의 | 예시 | +|------|------|------| +| **Configuration** | 설정 옵션, 커스터마이징 포인트 | 설정 파라미터, 환경 변수, 옵션 항목 | +| **Data** | 데이터 구조, 저장/로드, 파일 형식, 스키마 | 설정 파일 형식, DB 스키마, API 구조 | +| **Deployment** | 배포 방식, 운영 절차, 배포 환경 | 컨테이너 배포, 스택 관리, 운영 가이드 | +| **Documentation** | 문서 작성, 가이드, 매뉴얼, 안내서 | 사용자 가이드, API 문서, 튜토리얼 | +| **Exception** | 오류 처리, 예외 복구, 재시도 전략 | 오류 복구, 검증 실패 처리, 폴백 전략 | +| **Installation** | 시스템 설치, 환경 구성, 초기화 절차 | Docker 이미지 빌드, DB 마이그레이션, 사전 조건 설정 | +| **Integration** | 외부 시스템 연동, 플러그인, 확장 | 3rd party 연동, 플러그인 인터페이스 | +| **Interface** | 사용자 상호작용 방식 (UI, API, CLI, 제어방식) | CLI 명령어, API 엔드포인트, 웹 UI 기능 | +| **Migration** | 데이터 이전, 버전 업그레이드 | 데이터 마이그레이션, 버전 호환성 | +| **Non-Functional** | 성능, 보안, 확장성, 호환성, 신뢰성 | 성능 목표, 보안 정책, 확장성 | +| **Testing** | 테스트 전략, QA 요구사항, 검증 방식 | 테스트 커버리지, 회귀 테스트, 수락 기준 | + +## Requirement 작성 규칙 +- 각 requirement는 고유 ID를 가진다. 예: `REQ-001` +- 다음 신규 requirement 번호의 source of truth는 이 문서의 `Next Requirement ID` 섹션이다. +- 새 requirement를 추가하거나 ID 충돌이 발생할 수 있는 변경을 했으면 `Next Requirement ID`를 함께 갱신한다. +- 각 requirement는 `Type`을 명시해야 한다. 위 분류 도표 중 정확하게 하나로 선택하며, `docs/srs//` 폴더명도 같은 문자열을 사용한다. +- 각 requirement는 `Intent`, `Requirement`, `Acceptance Criteria`, `Impacted Area`를 반드시 포함한다. +- 모호한 표현("개선한다", "적절히 처리한다") 대신 관찰 가능하고 test 가능한 문장을 사용한다. +- requirement 변경 사항은 해당 REQ 파일의 `Change Log`에 기록한다. +- requirement 변경 시 영향 받는 batch/release와 재검증 필요 여부를 `Change Log`에 남긴다. +- `Recent Change Log Summary`는 최신 5건만 유지한다. +- status 전환은 `Status Transition Evidence` 기준을 따른다. + +## Next Requirement ID + +- Current: `REQ-001` +- Rule: `Requirement Register`에 있는 가장 큰 REQ 번호보다 정확히 1 커야 한다. + +## Requirement Register + +| ID | Title | Type | Status | Priority | Owner | Link | +| --- | --- | --- | --- | --- | --- | --- | + +## Recent Change Log Summary + +- 없음 \ No newline at end of file diff --git a/templates/srs/req-template.md b/templates/srs/req-template.md new file mode 100644 index 0000000..2fd39c7 --- /dev/null +++ b/templates/srs/req-template.md @@ -0,0 +1,67 @@ +# req-XXX: <요구사항 제목> + +- Status: Proposed | Approved | Implemented | Deprecated +- Type: Configuration | Data | Deployment | Documentation | Exception | Installation | Integration | Interface | Migration | Non-Functional | Testing +- Priority: High | Medium | Low +- Owner: <담당자 또는 팀> + +## Intent +- <이 요구사항이 필요한 이유와 달성하려는 목표를 1~3문장으로 작성> + +## Requirement +- <시스템이 반드시 수행해야 하는 동작을 단정형 문장으로 작성> + +## Acceptance Criteria +1. Given <선행 조건>, When <행동>, Then <기대 결과> +2. Given <선행 조건>, When <행동>, Then <기대 결과> + +## Impacted Area +- Docs: <영향 받는 문서 경로> +- Modules: <영향 받는 모듈/컴포넌트> +- Tests: <추가/수정해야 하는 테스트> + +## Notes +- <가정, 제약, 미해결 이슈> + +## Change Log + +### CHG-YYYYMMDD-01 +- Date: YYYY-MM-DD +- Author: <작성자> + +#### Change Summary +- <무엇이 변경되었는지> + +#### Intent +- <왜 변경했는지> + +#### Acceptance Criteria Delta +- Added: + - <추가된 기준> +- Updated: + - <변경된 기준> +- Removed: + - <삭제된 기준> + +#### Impacted Area Delta +- Docs: + - <문서 영향> +- Modules: + - <코드 영향> +- Tests: + - <테스트 영향> + +#### Delivery Trace Delta +- Batches: + - <영향 받는 batch 또는 none> +- Releases: + - <영향 받는 release 또는 none> + +#### Revalidation Impact +- Reverification Needed: yes | no +- Existing Implementation Invalidated: yes | no +- Status Recommendation: keep-current | revert-to-approved | revert-to-proposed +- Follow-up Action: <필요한 재검증, 재배치, 후속 작업> + +#### Validation Plan +- <검증 방법과 완료 조건> \ No newline at end of file diff --git a/tools/bootstrap.sh b/tools/bootstrap.sh new file mode 100644 index 0000000..c90b3e8 --- /dev/null +++ b/tools/bootstrap.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash + +set -euo pipefail + +log() { + printf '[bootstrap] %s\n' "$*" +} + +fail() { + printf '[bootstrap] ERROR: %s\n' "$*" >&2 + exit 1 +} + +current_os() { + uname -s +} + +HOST_ROOT="${1:-$(pwd)}" + +ensure_host_structure() { + # active SDLC 문서 루트만 생성한다. + # .github/.claude 하위 파일은 install.sh가 source/에서 복사하며 필요한 디렉터리를 함께 만든다. + local dirs=( + "docs/discovery" + "docs/srs" + "docs/batches" + "docs/releases" + ) + + for dir in "${dirs[@]}"; do + mkdir -p "${HOST_ROOT}/${dir}" + done +} + +report_github_structure() { + if [ ! -d "${HOST_ROOT}/.github" ]; then + return 0 + fi + log "Host .github structure:" + if command -v find >/dev/null 2>&1; then + find "${HOST_ROOT}/.github" -maxdepth 2 -mindepth 1 | sed "s#${HOST_ROOT}/##" | sort + else + log "find command not available; skipping structure scan." + fi +} + +main() { + os_name="$(current_os)" + [ -d "${HOST_ROOT}" ] || fail "Host root not found: ${HOST_ROOT}" + + log "Host root: ${HOST_ROOT}" + ensure_host_structure + + # 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 + + 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." +} + +main "$@" diff --git a/tools/install.sh b/tools/install.sh new file mode 100644 index 0000000..f480c80 --- /dev/null +++ b/tools/install.sh @@ -0,0 +1,244 @@ +#!/usr/bin/env bash + +set -euo pipefail + +usage() { + cat <<'EOF' +Usage: install.sh [--dry-run] [HOST_ROOT] [POLICY] + +source/ 아래의 모든 파일을 호스트 저장소 루트로 복사한다. + source/.github/... -> HOST_ROOT/.github/... + source/.claude/... -> HOST_ROOT/.claude/... + +단, source/instruction.md는 도구 중립 단일 원본으로 다음 두 복사본으로 fan-out 한다. + -> HOST_ROOT/.github/copilot-instructions.md (Copilot용, verbatim 복사) + -> HOST_ROOT/CLAUDE.md 의 StagePilot 마커 블록 (Claude용) +(instruction.md 자체는 루트로 그대로 복사하지 않는다.) + +또한 .stage-pilot/skills를 .github/skills로 동기화한다. + .stage-pilot/skills/... -> HOST_ROOT/.github/skills/... + +Options: + --dry-run 변경 없이 복사/생성 계획만 출력한다. + -h, --help 도움말을 출력한다. + +Arguments: + HOST_ROOT 대상 호스트 저장소 루트 (default: 현재 디렉터리) + POLICY 기존 파일 충돌 정책: replace|preserve|fail + (default: STAGEPILOT_CONFLICT_POLICY 또는 preserve) +EOF +} + +log() { + printf '[install] %s\n' "$*" +} + +fail() { + printf '[install] ERROR: %s\n' "$*" >&2 + exit 1 +} + +# pwd -P로 물리 경로를 잡아 심링크(.stage-pilot/* 등)를 실제 위치로 해석한다. +# 그렇지 않으면 SOURCE_DIR가 심링크가 되어 `find`가 내려가지 못한다. +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +PACKAGE_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd -P)" +SOURCE_DIR="${PACKAGE_ROOT}/source" +SKILLS_SOURCE_DIR="${PACKAGE_ROOT}/skills" + +DRY_RUN=0 +ARGS=() +while [ "$#" -gt 0 ]; do + case "$1" in + --dry-run) + DRY_RUN=1 + ;; + -h|--help) + usage + exit 0 + ;; + --*) + fail "Unknown option: $1" + ;; + *) + ARGS+=("$1") + ;; + esac + shift +done + +HOST_ROOT="${ARGS[0]:-$(pwd)}" +POLICY="${ARGS[1]:-${STAGEPILOT_CONFLICT_POLICY:-preserve}}" +TARGET_CLAUDE="${HOST_ROOT}/CLAUDE.md" +INSTRUCTION_SOURCE="${SOURCE_DIR}/instruction.md" +STAGEPILOT_BEGIN="" +STAGEPILOT_END="" + +case "${POLICY}" in + replace|preserve|fail) + ;; + *) + fail "Invalid conflict policy '${POLICY}'. Allowed: replace|preserve|fail" + ;; +esac + +[ -d "${SOURCE_DIR}" ] || fail "Source directory not found: ${SOURCE_DIR}" +[ -d "${HOST_ROOT}" ] || fail "Host root not found: ${HOST_ROOT}" + +copied=0 +skipped=0 +conflicted=0 +failed=0 + +# source/ 파일 하나를 HOST_ROOT/로 정책에 맞춰 복사한다. +copy_one() { + local src_file="$1" + local rel_path="$2" + local dst_file="${HOST_ROOT}/${rel_path}" + local dst_dir + dst_dir="$(dirname "${dst_file}")" + + if [ ! -e "${dst_file}" ]; then + if [ "${DRY_RUN}" -eq 1 ]; then + log "[dry-run] copy: ${rel_path}" + else + mkdir -p "${dst_dir}" + cp -a "${src_file}" "${dst_file}" + fi + copied=$((copied + 1)) + return + fi + + if cmp -s "${src_file}" "${dst_file}"; then + skipped=$((skipped + 1)) + return + fi + + conflicted=$((conflicted + 1)) + case "${POLICY}" in + replace) + if [ "${DRY_RUN}" -eq 1 ]; then + log "[dry-run] replace: ${rel_path}" + else + mkdir -p "${dst_dir}" + cp -a "${src_file}" "${dst_file}" + fi + copied=$((copied + 1)) + ;; + preserve) + log "preserve existing: ${rel_path}" + skipped=$((skipped + 1)) + ;; + fail) + printf '[install] CONFLICT: %s\n' "${rel_path}" >&2 + failed=$((failed + 1)) + ;; + esac +} + +# 1) source/ 전체를 호스트 루트로 복사 (.github, .claude 등 구조 보존) +# 단, instruction.md는 단일 원본이므로 루트로 직접 복사하지 않고 아래에서 fan-out 한다. +while IFS= read -r -d '' src_file; do + rel_path="${src_file#${SOURCE_DIR}/}" + if [ "${rel_path}" = "instruction.md" ]; then + continue + fi + copy_one "${src_file}" "${rel_path}" +done < <(find "${SOURCE_DIR}" -type f -print0 | sort -z) + +# 1-b) instruction.md -> .github/copilot-instructions.md (Copilot 매직 파일명)로 fan-out +if [ -f "${INSTRUCTION_SOURCE}" ]; then + copy_one "${INSTRUCTION_SOURCE}" ".github/copilot-instructions.md" +fi + +# 1-c) .stage-pilot/skills -> .github/skills 동기화 (심볼릭 링크 미사용) +if [ -d "${SKILLS_SOURCE_DIR}" ]; then + while IFS= read -r -d '' src_file; do + rel_path="${src_file#${SKILLS_SOURCE_DIR}/}" + copy_one "${src_file}" ".github/skills/${rel_path}" + done < <(find "${SKILLS_SOURCE_DIR}" -type f -print0 | sort -z) +else + log "skills source not found: ${SKILLS_SOURCE_DIR}" +fi + +# 2) CLAUDE.md: instruction.md를 단일 원본으로 StagePilot 블록 생성/갱신. +# 호스트가 직접 추가한 CLAUDE.md 내용은 마커 밖에 그대로 보존한다. +generate_claude_md() { + [ -f "${INSTRUCTION_SOURCE}" ] || { log "instruction.md 없음; CLAUDE.md 생략"; return 0; } + + local payload output + payload="$(mktemp)" + output="$(mktemp)" + + # 원본에 이미 마커가 있으면 그 블록만, 없으면 파일 전체를 payload로 사용한다. + if grep -Fq "${STAGEPILOT_BEGIN}" "${INSTRUCTION_SOURCE}" && grep -Fq "${STAGEPILOT_END}" "${INSTRUCTION_SOURCE}"; then + awk -v b="${STAGEPILOT_BEGIN}" -v e="${STAGEPILOT_END}" ' + $0 == b { f=1; next } + $0 == e { f=0; exit } + f { print } + ' "${INSTRUCTION_SOURCE}" > "${payload}" + else + cat "${INSTRUCTION_SOURCE}" > "${payload}" + fi + + if [ ! -e "${TARGET_CLAUDE}" ]; then + { + printf '%s\n' "${STAGEPILOT_BEGIN}" + cat "${payload}" + printf '%s\n' "${STAGEPILOT_END}" + } > "${output}" + elif grep -Fq "${STAGEPILOT_BEGIN}" "${TARGET_CLAUDE}" && grep -Fq "${STAGEPILOT_END}" "${TARGET_CLAUDE}"; then + awk -v b="${STAGEPILOT_BEGIN}" -v e="${STAGEPILOT_END}" -v p="${payload}" ' + $0 == b { print; while ((getline line < p) > 0) print line; f=1; next } + $0 == e { f=0; print; next } + !f { print } + ' "${TARGET_CLAUDE}" > "${output}" + else + cat "${TARGET_CLAUDE}" > "${output}" + { + printf '\n## StagePilot\n' + printf '%s\n' "${STAGEPILOT_BEGIN}" + cat "${payload}" + printf '%s\n' "${STAGEPILOT_END}" + } >> "${output}" + fi + + if [ "${DRY_RUN}" -eq 1 ]; then + if command -v diff >/dev/null 2>&1 && [ -e "${TARGET_CLAUDE}" ]; then + log "[dry-run] CLAUDE.md diff:" + diff -u "${TARGET_CLAUDE}" "${output}" || true + else + log "[dry-run] create/update: CLAUDE.md" + fi + else + cp -a "${output}" "${TARGET_CLAUDE}" + log "CLAUDE.md StagePilot 블록 갱신" + fi + + rm -f "${payload}" "${output}" +} + +generate_claude_md + +log "host_root=${HOST_ROOT}" +log "source=${SOURCE_DIR}" +log "policy=${POLICY}" +log "copied=${copied} skipped=${skipped} conflicted=${conflicted}" + +if [ "${failed}" -ne 0 ]; then + if [ "${DRY_RUN}" -eq 1 ]; then + fail "[dry-run] policy=fail에서 충돌이 발생합니다." + fi + fail "policy=fail 충돌로 설치를 중단합니다." +fi + +if [ "${DRY_RUN}" -eq 1 ]; then + log "[dry-run] 디렉터리 골격 생성 단계 생략. 변경된 파일 없음." + exit 0 +fi + +# 3) 디렉터리 골격 생성 (docs/discovery|srs|batches|releases 등) +BOOTSTRAP_SCRIPT="${PACKAGE_ROOT}/tools/bootstrap.sh" +[ -f "${BOOTSTRAP_SCRIPT}" ] || fail "Bootstrap script not found: ${BOOTSTRAP_SCRIPT}" + +/bin/bash "${BOOTSTRAP_SCRIPT}" "${HOST_ROOT}" +log "Install completed successfully." diff --git a/tools/stagepilot-bootstrap-seed.py b/tools/stagepilot-bootstrap-seed.py new file mode 100644 index 0000000..10bc293 --- /dev/null +++ b/tools/stagepilot-bootstrap-seed.py @@ -0,0 +1,913 @@ +#!/usr/bin/env python3 + +from __future__ import annotations + +import argparse +from dataclasses import dataclass +from datetime import datetime, timedelta, timezone +from pathlib import Path +import re +import sys + +try: + from zoneinfo import ZoneInfo +except ImportError: # pragma: no cover + ZoneInfo = None + + +RUNTIME_CHOICES = ( + "cli", + "api-service", + "web-app", + "worker", + "library", + "mixed", + "other", +) + +INTERFACE_TYPE_CHOICES = ( + "http-api", + "cli", + "event", + "file", + "internal-service", + "other", +) + +UPPERCASE_TOKENS = { + "api": "API", + "http": "HTTP", + "cli": "CLI", + "id": "ID", + "io": "I/O", + "json": "JSON", + "sql": "SQL", + "ui": "UI", + "ux": "UX", + "url": "URL", +} + +STORAGE_BACKEND_HINTS = ( + (("postgresql", "postgres", "psql"), "PostgreSQL"), + (("mysql", "mariadb"), "MySQL"), + (("sqlite",), "SQLite"), + (("mongodb", "mongo"), "MongoDB"), + (("dynamodb",), "DynamoDB"), + (("redis",), "Redis"), + (("s3", "blob storage", "object storage"), "Object Storage"), +) + + +@dataclass(frozen=True) +class Entrypoint: + name: str + purpose: str + + +@dataclass(frozen=True) +class TopLevelArea: + path: str + responsibility: str + + +@dataclass(frozen=True) +class InterfaceContractHint: + name: str + interface_type: str + actors: str + purpose: str + + +@dataclass(frozen=True) +class EntityHint: + name: str + purpose: str + + +@dataclass(frozen=True) +class CompatibilityRuleHint: + interface_name: str + rule: str + + +@dataclass(frozen=True) +class InterfaceDetailHint: + interface_name: str + value: str + + +@dataclass(frozen=True) +class SeedAnswers: + repository_name: str + display_name: str + summary: str + primary_domain: str + tech_stack: list[str] + primary_runtime: str + primary_entrypoints: list[Entrypoint] + planned_top_level_areas: list[TopLevelArea] + interface_contracts: list[InterfaceContractHint] + interface_inputs: list[InterfaceDetailHint] + interface_outputs: list[InterfaceDetailHint] + interface_errors: list[InterfaceDetailHint] + core_entities: list[EntityHint] + persistence_backend: str + compatibility_rules: list[CompatibilityRuleHint] + known_unknowns: list[str] + capture_mode: str + + +def fail(message: str) -> int: + print(f"[bootstrap-seed] ERROR: {message}", file=sys.stderr) + return 1 + + +def log(message: str) -> None: + print(f"[bootstrap-seed] {message}") + + +def default_display_name(repository_name: str) -> str: + words = re.sub(r"[-_]+", " ", repository_name.strip()).split() + if not words: + return "Project" + return " ".join(word.capitalize() for word in words) + + +def kst_timestamp() -> str: + if ZoneInfo is not None: + current = datetime.now(ZoneInfo("Asia/Seoul")) + else: # pragma: no cover + current = datetime.now(timezone(timedelta(hours=9))) + return current.strftime("%Y-%m-%d %H:%M") + + +def yaml_scalar(value: str) -> str: + escaped = value.replace("'", "''") + return f"'{escaped}'" + + +def parse_csv_list(raw_value: str) -> list[str]: + return [item.strip() for item in raw_value.split(",") if item.strip()] + + +def parse_pair(raw_value: str, label: str) -> tuple[str, str]: + if "::" not in raw_value: + raise ValueError(f"{label} entries must use 'name :: purpose' format: {raw_value}") + + left, right = raw_value.split("::", 1) + left = left.strip() + right = right.strip() + if not left or not right: + raise ValueError(f"{label} entries must include values on both sides of '::': {raw_value}") + return left, right + + +def parse_parts(raw_value: str, label: str, expected_parts: int) -> tuple[str, ...]: + parts = tuple(part.strip() for part in raw_value.split("::")) + if len(parts) != expected_parts or any(not part for part in parts): + raise ValueError( + f"{label} entries must use exactly {expected_parts} parts separated by '::': {raw_value}" + ) + return parts + + +def prompt_value(question: str, *, default: str | None = None, choices: tuple[str, ...] | None = None) -> str: + while True: + choice_hint = f" ({', '.join(choices)})" if choices else "" + default_hint = f" [{default}]" if default else "" + raw_value = input(f"{question}{choice_hint}{default_hint}: ").strip() + + if not raw_value and default is not None: + return default + if not raw_value: + print("Value is required.") + continue + if choices and raw_value not in choices: + print(f"Choose one of: {', '.join(choices)}") + continue + return raw_value + + +def prompt_lines(question: str, *, example: str, required: bool = False, max_items: int | None = None) -> list[str]: + print(question) + print(f"Format: {example}") + print("Press Enter on an empty line to finish.") + + values: list[str] = [] + while True: + raw_value = input("> ").strip() + if not raw_value: + if required and not values: + print("At least one line is required.") + continue + break + + values.append(raw_value) + if max_items is not None and len(values) >= max_items: + break + + return values + + +def title_case_phrase(text: str) -> str: + tokens = [token for token in re.split(r"[\s/_-]+", text.strip()) if token] + formatted: list[str] = [] + for token in tokens: + lowered = token.lower() + if lowered in UPPERCASE_TOKENS: + formatted.append(UPPERCASE_TOKENS[lowered]) + else: + formatted.append(token.capitalize()) + return " ".join(formatted) + + +def compact_label(text: str, *, max_words: int = 5) -> str: + cleaned = re.sub(r"[^A-Za-z0-9\s/_-]+", " ", text).strip() + label = title_case_phrase(cleaned) + words = label.split() + if len(words) > max_words: + return " ".join(words[:max_words]) + return label + + +def infer_interface_type(primary_runtime: str, entrypoint_name: str, index: int) -> str: + lowered = entrypoint_name.lower() + if primary_runtime in {"api-service", "web-app"} and index == 0: + return "http-api" + if primary_runtime == "library": + return "internal-service" + if primary_runtime in {"cli", "worker"}: + return "cli" + if any(token in lowered for token in ("uvicorn", "gunicorn", ":app")): + return "http-api" + if lowered.startswith(("python ", "python3 ", "node ")) or " -m " in lowered: + return "cli" + return "other" + + +def default_interface_actors(interface_type: str) -> str: + actor_map = { + "http-api": "clients and downstream services", + "cli": "operators and automation", + "event": "publishers and subscribers", + "file": "file producers and consumers", + "internal-service": "internal services", + "other": "project operators and integrators", + } + return actor_map[interface_type] + + +def default_interface_stability(interface_type: str, capture_mode: str) -> str: + if interface_type == "cli": + return "internal" + if capture_mode == "declared": + return "experimental" + return "internal" + + +def derive_interface_name(entrypoint: Entrypoint, interface_type: str, index: int) -> str: + purpose_label = compact_label(entrypoint.purpose) + if interface_type == "http-api": + return "Primary HTTP API" if index == 0 else f"{purpose_label} API" + if interface_type == "cli": + return f"{purpose_label} Command" + if interface_type == "event": + return f"{purpose_label} Event" + if interface_type == "file": + return f"{purpose_label} File Contract" + if interface_type == "internal-service": + return f"{purpose_label} Service" + return f"{purpose_label} Interface" + + +def build_interface_hints(answers: SeedAnswers) -> list[InterfaceContractHint]: + hints = list(answers.interface_contracts[:2]) + for index, entrypoint in enumerate(answers.primary_entrypoints): + if len(hints) >= 2: + break + interface_type = infer_interface_type(answers.primary_runtime, entrypoint.name, index) + hints.append( + InterfaceContractHint( + name=derive_interface_name(entrypoint, interface_type, index), + interface_type=interface_type, + actors=default_interface_actors(interface_type), + purpose=entrypoint.purpose, + ) + ) + + while len(hints) < 2: + index = len(hints) + fallback_type = "http-api" if index == 0 and answers.primary_runtime in {"api-service", "web-app"} else "internal-service" + hints.append( + InterfaceContractHint( + name="Primary HTTP API" if fallback_type == "http-api" else "Internal Service Interface", + interface_type=fallback_type, + actors=default_interface_actors(fallback_type), + purpose=f"support the primary {answers.primary_domain} workflow", + ) + ) + return hints[:2] + + +def default_entity_name(primary_domain: str, index: int) -> str: + domain_label = compact_label(primary_domain, max_words=4) or "Domain" + if index == 0: + return f"{domain_label} Record" + return f"{domain_label} Snapshot" + + +def default_entity_purpose(primary_domain: str, index: int) -> str: + if index == 0: + return f"represent a core business record in the {primary_domain} domain" + return f"represent a derived or supporting artifact in the {primary_domain} domain" + + +def build_entity_hints(answers: SeedAnswers) -> list[EntityHint]: + hints = list(answers.core_entities[:2]) + while len(hints) < 2: + index = len(hints) + hints.append( + EntityHint( + name=default_entity_name(answers.primary_domain, index), + purpose=default_entity_purpose(answers.primary_domain, index), + ) + ) + return hints[:2] + + +def detect_storage_backend(tech_stack: list[str]) -> str: + lowered_stack = [item.lower() for item in tech_stack] + for keywords, label in STORAGE_BACKEND_HINTS: + if any(keyword in item for item in lowered_stack for keyword in keywords): + return label + return "application data store" + + +def normalize_storage_backend(raw_value: str) -> str: + lowered_value = raw_value.strip().lower() + if not lowered_value: + return "application data store" + for keywords, label in STORAGE_BACKEND_HINTS: + if any(keyword in lowered_value for keyword in keywords): + return label + return raw_value.strip() + + +def derive_interface_inputs(contract: InterfaceContractHint) -> list[str]: + if contract.interface_type == "http-api": + return [f"service request handled by {contract.name} :: {contract.purpose}"] + if contract.interface_type == "cli": + return [f"command invocation :: {contract.purpose}"] + if contract.interface_type == "event": + return [f"event publication or subscription :: {contract.purpose}"] + if contract.interface_type == "file": + return [f"file input or output trigger :: {contract.purpose}"] + return [f"interface invocation :: {contract.purpose}"] + + +def derive_interface_outputs(contract: InterfaceContractHint) -> list[str]: + if contract.interface_type == "http-api": + return [f"structured service response :: result of {contract.purpose}"] + if contract.interface_type == "cli": + return [f"console output or generated artifact :: result of {contract.purpose}"] + if contract.interface_type == "event": + return [f"published event payload :: result of {contract.purpose}"] + if contract.interface_type == "file": + return [f"generated or updated file artifact :: result of {contract.purpose}"] + return [f"observable interface result :: outcome of {contract.purpose}"] + + +def derive_interface_errors(contract: InterfaceContractHint) -> list[str]: + if contract.interface_type == "http-api": + return ["4xx/5xx response :: the request could not be completed successfully"] + if contract.interface_type == "cli": + return ["non-zero exit code :: the command failed for operators or automation"] + return ["reported interface failure :: the contract could not be completed successfully"] + + +def derive_interface_compatibility(contract: InterfaceContractHint) -> list[str]: + if contract.interface_type == "cli": + return ["arguments and exit-code semantics should remain stable for automation callers"] + return ["payload shape and externally visible behavior should remain backward compatible for stable consumers"] + + +def resolve_interface_compatibility_rules( + answers: SeedAnswers, + contract: InterfaceContractHint, +) -> list[str]: + shared_rules = [ + rule.rule for rule in answers.compatibility_rules if rule.interface_name.strip() == "*" + ] + named_rules = [ + rule.rule + for rule in answers.compatibility_rules + if rule.interface_name.strip().casefold() == contract.name.casefold() + ] + if shared_rules or named_rules: + return shared_rules + named_rules + return derive_interface_compatibility(contract) + + +def resolve_interface_detail_hints( + detail_hints: list[InterfaceDetailHint], + contract: InterfaceContractHint, +) -> list[str]: + return [ + hint.value + for hint in detail_hints + if hint.interface_name.strip().casefold() == contract.name.casefold() + ] + + +def derive_interface_shared_constraints(answers: SeedAnswers, contracts: list[InterfaceContractHint]) -> list[str]: + constraints: list[str] = [] + if any(contract.interface_type == "http-api" for contract in contracts): + constraints.append("request validation should happen before persistence or response rendering") + if any(contract.interface_type == "cli" for contract in contracts): + constraints.append("automation-facing commands should communicate failure through stable exit semantics") + if not constraints: + constraints.append("externally visible interfaces should document compatibility expectations before release") + return constraints[:2] + + +def derive_interface_current_gaps(answers: SeedAnswers, contracts: list[InterfaceContractHint]) -> list[str]: + if answers.known_unknowns: + return answers.known_unknowns[:2] + gaps: list[str] = [] + if any(contract.interface_type == "http-api" for contract in contracts): + gaps.append("authentication and API versioning strategy are not defined yet") + if any(contract.interface_type == "cli" for contract in contracts): + gaps.append("automation caller expectations are not fully documented yet") + return gaps[:2] or ["consumer-specific compatibility expectations are not defined yet"] + + +def derive_entity_source(storage_backend: str, index: int) -> str: + if storage_backend == "PostgreSQL": + return "PostgreSQL primary tables" if index == 0 else "PostgreSQL derived tables" + if storage_backend == "MySQL": + return "MySQL primary tables" if index == 0 else "MySQL derived tables" + if storage_backend == "SQLite": + return "SQLite primary tables" if index == 0 else "SQLite derived tables" + if storage_backend == "MongoDB": + return "MongoDB primary collections" if index == 0 else "MongoDB derived collections" + if storage_backend == "Object Storage": + return "object storage manifests" if index == 0 else "object storage derived artifacts" + return "primary application records" if index == 0 else "derived application records" + + +def derive_entity_lifecycle(index: int) -> str: + if index == 0: + return "created -> updated -> archived" + return "derived -> published -> superseded" + + +def derive_entity_fields(index: int, entity: EntityHint, source_entity: EntityHint | None = None) -> list[str]: + if index == 0: + return [ + "id :: string or uuid :: stable identifier for the entity", + "status :: enum :: current lifecycle state for the entity", + "created_at :: timestamp :: when the entity was first recorded", + ] + source_name = source_entity.name if source_entity is not None else "the primary entity" + return [ + "id :: string or uuid :: stable identifier for the derived entity", + f"source_id :: string or uuid :: reference to {source_name}", + "updated_at :: timestamp :: when the derived state was last recomputed or refreshed", + ] + + +def derive_entity_relationships(index: int, entity: EntityHint, other_entity: EntityHint) -> list[str]: + if index == 0: + return [f"{other_entity.name} :: derived from or associated with {entity.name}"] + return [f"{other_entity.name} :: source record or upstream entity for {entity.name}"] + + +def derive_entity_state_rules(index: int, entity: EntityHint, other_entity: EntityHint) -> list[str]: + if index == 0: + return [ + "only one current active record should be treated as authoritative per business key", + "archived records should remain available for audit or replay needs when retained", + ] + return [ + f"{entity.name} should not exist without a corresponding {other_entity.name}", + f"{entity.name} should be refreshed whenever the authoritative {other_entity.name} changes", + ] + + +def derive_consistency_rules(entities: list[EntityHint]) -> list[str]: + return [ + "stable identifiers must remain consistent across related entities", + f"derived records must not outlive their source relationship to {entities[0].name}", + ] + + +def derive_persistence_notes(storage_backend: str) -> list[str]: + return [ + f"{storage_backend} is expected to store the current cross-cutting records and derived artifacts", + "migration, indexing, and retention strategy should be updated when schema assumptions change", + ] + + +def derive_model_current_gaps(answers: SeedAnswers) -> list[str]: + if answers.known_unknowns: + return answers.known_unknowns[:2] + return [ + "exact schema and indexing strategy are not defined yet", + "retention and archival policy are not defined yet", + ] + + +def collect_required_args(args: argparse.Namespace) -> tuple[str, str, list[str], str, list[str]]: + missing: list[str] = [] + if not args.project_summary: + missing.append("--project-summary") + if not args.primary_domain: + missing.append("--primary-domain") + if not args.tech_stack: + missing.append("--tech-stack") + if not args.primary_runtime: + missing.append("--primary-runtime") + if not args.primary_entrypoint: + missing.append("--primary-entrypoint") + if missing: + raise ValueError("Missing required arguments for --non-interactive: " + ", ".join(missing)) + + tech_stack = parse_csv_list(args.tech_stack) + if not tech_stack: + raise ValueError("--tech-stack must include at least one comma-separated item") + + return ( + args.project_summary, + args.primary_domain, + tech_stack, + args.primary_runtime, + args.primary_entrypoint, + ) + + +def collect_answers(args: argparse.Namespace, host_root: Path) -> SeedAnswers: + repository_name = args.repository_name or host_root.resolve().name or "project" + display_name = args.display_name or default_display_name(repository_name) + + if args.non_interactive: + summary, primary_domain, tech_stack, primary_runtime, primary_entrypoints = collect_required_args(args) + top_level_area_values = args.top_level_area + interface_contract_values = args.interface_contract + interface_input_values = args.interface_input + interface_output_values = args.interface_output + interface_error_values = args.interface_error + core_entity_values = args.core_entity + persistence_backend = normalize_storage_backend(args.persistence_backend) if args.persistence_backend else detect_storage_backend(tech_stack) + compatibility_rule_values = args.compatibility_rule + known_unknowns = args.known_unknown + else: + print("[bootstrap-seed] Answer the minimum bootstrap questions for baseline.yaml.") + print(f"[bootstrap-seed] Host root: {host_root}") + display_name = prompt_value("Display name", default=display_name) + summary = args.project_summary or prompt_value("project-summary | 이 프로젝트를 한 문장으로 설명하면 무엇인가?") + primary_domain = args.primary_domain or prompt_value("primary-domain | 이 프로젝트의 주요 도메인은 무엇인가?") + tech_stack = parse_csv_list(args.tech_stack) if args.tech_stack else parse_csv_list( + prompt_value("tech-stack | 계획 중인 주 언어, 프레임워크, 핵심 인프라는 무엇인가? (comma-separated)") + ) + primary_runtime = args.primary_runtime or prompt_value( + "primary-runtime | 계획 중인 주 실행 형태는 무엇인가?", + choices=RUNTIME_CHOICES, + ) + primary_entrypoints = args.primary_entrypoint or prompt_lines( + "primary-entrypoints | 대표 진입점 1~3개를 적어 달라.", + example="name :: purpose", + required=True, + max_items=3, + ) + top_level_area_values = args.top_level_area or prompt_lines( + "planned-top-level-areas | 이미 알고 있는 top-level 경로가 있으면 적어 달라.", + example="path :: responsibility", + ) + interface_contract_values = args.interface_contract or prompt_lines( + "interface-contracts | 대표 인터페이스 1~2개가 있으면 적어 달라.", + example="name :: type :: actors :: purpose", + max_items=2, + ) + interface_input_values = args.interface_input or prompt_lines( + "interface-inputs | 각 인터페이스의 대표 input을 적어 달라.", + example="interface-name :: input contract or invocation note", + ) + interface_output_values = args.interface_output or prompt_lines( + "interface-outputs | 각 인터페이스의 대표 output을 적어 달라.", + example="interface-name :: output contract or artifact", + ) + interface_error_values = args.interface_error or prompt_lines( + "interface-errors | 각 인터페이스의 대표 error를 적어 달라.", + example="interface-name :: error code or failure behavior", + ) + core_entity_values = args.core_entity or prompt_lines( + "core-entities | 핵심 엔티티 1~2개가 있으면 적어 달라.", + example="name :: purpose", + max_items=2, + ) + persistence_backend = normalize_storage_backend(args.persistence_backend) if args.persistence_backend else prompt_value( + "persistence-backend | 주 persistence backend가 있으면 적어 달라.", + default=detect_storage_backend(tech_stack), + ) + compatibility_rule_values = args.compatibility_rule or prompt_lines( + "compatibility-rules | 인터페이스 호환성 규칙이 있으면 적어 달라.", + example="interface-name :: rule 또는 * :: rule", + ) + known_unknowns = args.known_unknown or prompt_lines( + "known-unknowns | 아직 미정인 핵심 항목이 있으면 적어 달라.", + example="one unresolved item per line", + ) + + if primary_runtime not in RUNTIME_CHOICES: + raise ValueError(f"primary runtime must be one of: {', '.join(RUNTIME_CHOICES)}") + if not tech_stack: + raise ValueError("tech-stack must include at least one item") + if not primary_entrypoints: + raise ValueError("at least one primary-entrypoint is required") + if len(primary_entrypoints) > 3: + raise ValueError("primary-entrypoint supports at most 3 values") + if len(interface_contract_values) > 2: + raise ValueError("interface-contract supports at most 2 values") + if len(core_entity_values) > 2: + raise ValueError("core-entity supports at most 2 values") + + entrypoints = [Entrypoint(*parse_pair(value, "primary-entrypoint")) for value in primary_entrypoints] + top_level_areas = [TopLevelArea(*parse_pair(value, "top-level-area")) for value in top_level_area_values] + interface_contracts = [ + InterfaceContractHint(*parse_parts(value, "interface-contract", 4)) for value in interface_contract_values + ] + for contract in interface_contracts: + if contract.interface_type not in INTERFACE_TYPE_CHOICES: + raise ValueError( + "interface-contract type must be one of: " + ", ".join(INTERFACE_TYPE_CHOICES) + ) + interface_inputs = [InterfaceDetailHint(*parse_pair(value, "interface-input")) for value in interface_input_values] + interface_outputs = [InterfaceDetailHint(*parse_pair(value, "interface-output")) for value in interface_output_values] + interface_errors = [InterfaceDetailHint(*parse_pair(value, "interface-error")) for value in interface_error_values] + core_entities = [EntityHint(*parse_parts(value, "core-entity", 2)) for value in core_entity_values] + compatibility_rules = [ + CompatibilityRuleHint(*parse_pair(value, "compatibility-rule")) for value in compatibility_rule_values + ] + + return SeedAnswers( + repository_name=repository_name, + display_name=display_name, + summary=summary, + primary_domain=primary_domain, + tech_stack=tech_stack, + primary_runtime=primary_runtime, + primary_entrypoints=entrypoints, + planned_top_level_areas=top_level_areas, + interface_contracts=interface_contracts, + interface_inputs=interface_inputs, + interface_outputs=interface_outputs, + interface_errors=interface_errors, + core_entities=core_entities, + persistence_backend=persistence_backend, + compatibility_rules=compatibility_rules, + known_unknowns=known_unknowns, + capture_mode=args.capture_mode or "declared", + ) + + +def render_seed(answers: SeedAnswers) -> str: + updated_from = { + "declared": "user-answer", + "observed": "repo-observation", + "mixed": "mixed", + }[answers.capture_mode] + interface_contracts = build_interface_hints(answers) + entities = build_entity_hints(answers) + storage_backend = answers.persistence_backend + interface_shared_constraints = derive_interface_shared_constraints(answers, interface_contracts) + interface_current_gaps = derive_interface_current_gaps(answers, interface_contracts) + consistency_rules = derive_consistency_rules(entities) + persistence_notes = derive_persistence_notes(storage_backend) + model_current_gaps = derive_model_current_gaps(answers) + + lines = [ + "schema_version: 1", + f"capture_mode: {answers.capture_mode}", + "", + "project:", + f" repository_name: {yaml_scalar(answers.repository_name)}", + f" display_name: {yaml_scalar(answers.display_name)}", + f" summary: {yaml_scalar(answers.summary)}", + f" primary_domain: {yaml_scalar(answers.primary_domain)}", + "", + "stack:", + " tech_stack:", + ] + + for item in answers.tech_stack: + lines.append(f" - {yaml_scalar(item)}") + + lines.extend( + [ + "", + "runtime:", + f" primary_runtime: {answers.primary_runtime}", + " primary_entrypoints:", + ] + ) + for entrypoint in answers.primary_entrypoints: + lines.append(f" - name: {yaml_scalar(entrypoint.name)}") + lines.append(f" purpose: {yaml_scalar(entrypoint.purpose)}") + + lines.extend(["", "interfaces:", " contracts:"]) + for contract in interface_contracts: + resolved_inputs = resolve_interface_detail_hints(answers.interface_inputs, contract) + resolved_outputs = resolve_interface_detail_hints(answers.interface_outputs, contract) + resolved_errors = resolve_interface_detail_hints(answers.interface_errors, contract) + lines.append(f" - name: {yaml_scalar(contract.name)}") + lines.append(f" type: {yaml_scalar(contract.interface_type)}") + lines.append(f" actors: {yaml_scalar(contract.actors)}") + lines.append(f" purpose: {yaml_scalar(contract.purpose)}") + lines.append( + f" stability: {yaml_scalar(default_interface_stability(contract.interface_type, answers.capture_mode))}" + ) + lines.append(" inputs:") + for item in resolved_inputs or derive_interface_inputs(contract): + lines.append(f" - {yaml_scalar(item)}") + lines.append(" outputs:") + for item in resolved_outputs or derive_interface_outputs(contract): + lines.append(f" - {yaml_scalar(item)}") + lines.append(" errors:") + for item in resolved_errors or derive_interface_errors(contract): + lines.append(f" - {yaml_scalar(item)}") + lines.append(" compatibility_rules:") + for item in resolve_interface_compatibility_rules(answers, contract): + lines.append(f" - {yaml_scalar(item)}") + lines.append(" shared_constraints:") + for item in interface_shared_constraints: + lines.append(f" - {yaml_scalar(item)}") + lines.append(" current_gaps:") + for item in interface_current_gaps: + lines.append(f" - {yaml_scalar(item)}") + + lines.extend(["", "structure:"]) + if answers.planned_top_level_areas: + lines.append(" planned_top_level_areas:") + for area in answers.planned_top_level_areas: + lines.append(f" - path: {yaml_scalar(area.path)}") + lines.append(f" responsibility: {yaml_scalar(area.responsibility)}") + else: + lines.append(" planned_top_level_areas: []") + + lines.extend(["", "data_model:", f" persistence_backend: {yaml_scalar(storage_backend)}", " entities:"]) + for index, entity in enumerate(entities): + other_entity = entities[1 - index] + source_entity = entities[0] if index == 1 else None + lines.append(f" - name: {yaml_scalar(entity.name)}") + lines.append(f" purpose: {yaml_scalar(entity.purpose)}") + lines.append(f" source_of_truth: {yaml_scalar(derive_entity_source(storage_backend, index))}") + lines.append(f" lifecycle: {yaml_scalar(derive_entity_lifecycle(index))}") + lines.append(" key_fields:") + for item in derive_entity_fields(index, entity, source_entity): + lines.append(f" - {yaml_scalar(item)}") + lines.append(" relationships:") + for item in derive_entity_relationships(index, entity, other_entity): + lines.append(f" - {yaml_scalar(item)}") + lines.append(" state_rules:") + for item in derive_entity_state_rules(index, entity, other_entity): + lines.append(f" - {yaml_scalar(item)}") + lines.append(" consistency_rules:") + for item in consistency_rules: + lines.append(f" - {yaml_scalar(item)}") + lines.append(" persistence_notes:") + for item in persistence_notes: + lines.append(f" - {yaml_scalar(item)}") + lines.append(" current_gaps:") + for item in model_current_gaps: + lines.append(f" - {yaml_scalar(item)}") + + lines.extend(["", "notes:"]) + if answers.known_unknowns: + lines.append(" known_unknowns:") + for item in answers.known_unknowns: + lines.append(f" - {yaml_scalar(item)}") + else: + lines.append(" known_unknowns: []") + + lines.extend( + [ + "", + "metadata:", + f" captured_at_kst: {yaml_scalar(kst_timestamp())}", + " source: stagepilot-bootstrap-seed.py", + f" updated_from: {updated_from}", + "", + ] + ) + return "\n".join(lines) + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser( + description="Create .stage-pilot/bootstrap/baseline.yaml from StagePilot bootstrap answers.", + ) + parser.add_argument("host_root", nargs="?", default=".", help="Host repository root. Defaults to the current directory.") + parser.add_argument("--output", help="Explicit output path. Defaults to HOST_ROOT/.stage-pilot/bootstrap/baseline.yaml.") + parser.add_argument("--force", action="store_true", help="Overwrite an existing seed file.") + parser.add_argument("--dry-run", action="store_true", help="Print the generated seed to stdout without writing a file.") + parser.add_argument("--non-interactive", action="store_true", help="Require explicit CLI answers instead of interactive prompts.") + parser.add_argument("--capture-mode", choices=("declared", "observed", "mixed"), help="Seed capture mode.") + parser.add_argument("--repository-name", help="Repository name stored in the seed. Defaults to the host root folder name.") + parser.add_argument("--display-name", help="Display name stored in the seed. Defaults to a title-cased repository name.") + parser.add_argument("--project-summary", help="One-sentence project summary.") + parser.add_argument("--primary-domain", help="Primary domain noun phrase.") + parser.add_argument("--tech-stack", help="Comma-separated technology list.") + parser.add_argument("--primary-runtime", choices=RUNTIME_CHOICES, help="Primary runtime type.") + parser.add_argument( + "--primary-entrypoint", + action="append", + default=[], + help="Primary entrypoint line in 'name :: purpose' format. Repeat up to 3 times.", + ) + parser.add_argument( + "--top-level-area", + action="append", + default=[], + help="Optional top-level area in 'path :: responsibility' format.", + ) + parser.add_argument( + "--interface-contract", + action="append", + default=[], + help="Optional interface contract in 'name :: type :: actors :: purpose' format. Repeat up to 2 times.", + ) + parser.add_argument( + "--interface-input", + action="append", + default=[], + help="Optional interface input in 'interface-name :: input contract or invocation note' format.", + ) + parser.add_argument( + "--interface-output", + action="append", + default=[], + help="Optional interface output in 'interface-name :: output contract or artifact' format.", + ) + parser.add_argument( + "--interface-error", + action="append", + default=[], + help="Optional interface error in 'interface-name :: error code or failure behavior' format.", + ) + parser.add_argument( + "--core-entity", + action="append", + default=[], + help="Optional core entity in 'name :: purpose' format. Repeat up to 2 times.", + ) + parser.add_argument("--persistence-backend", help="Optional primary persistence backend or storage system.") + parser.add_argument( + "--compatibility-rule", + action="append", + default=[], + help="Optional compatibility rule in 'interface-name :: rule' format. Use '* :: rule' for a shared rule.", + ) + parser.add_argument( + "--known-unknown", + action="append", + default=[], + help="Optional unresolved bootstrap note. Repeat for multiple items.", + ) + return parser + + +def main() -> int: + parser = build_parser() + args = parser.parse_args() + + host_root = Path(args.host_root).resolve() + if not host_root.exists() or not host_root.is_dir(): + return fail(f"host root not found: {host_root}") + + output_path = Path(args.output).resolve() if args.output else host_root / ".stage-pilot/bootstrap/baseline.yaml" + + try: + answers = collect_answers(args, host_root) + content = render_seed(answers) + except ValueError as error: + return fail(str(error)) + except EOFError: + return fail("input ended before the questionnaire was completed") + + if args.dry_run: + print(content, end="") + return 0 + + if output_path.exists() and not args.force: + return fail(f"seed file already exists: {output_path}. Use --force to overwrite.") + + output_path.parent.mkdir(parents=True, exist_ok=True) + output_path.write_text(content, encoding="utf-8") + + log(f"wrote {output_path}") + log("Next: run /bootstrap-baseline in Copilot Chat to render baseline docs from the seed.") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/tools/stagepilot-doctor.py b/tools/stagepilot-doctor.py new file mode 100644 index 0000000..4729d34 --- /dev/null +++ b/tools/stagepilot-doctor.py @@ -0,0 +1,1271 @@ +#!/usr/bin/env python3 + +from __future__ import annotations + +import argparse +from datetime import datetime +import re +import sys +from dataclasses import dataclass +from pathlib import Path +from typing import Iterable + + +PLACEHOLDER_PATTERN = re.compile(r"\{\{[^{}]+\}\}") +LINK_PATTERN = re.compile(r"\[[^\]]+\]\(([^)]+)\)") +REQ_ID_PATTERN = re.compile(r"req-(\d+)", re.IGNORECASE) +BATCH_ID_PATTERN = re.compile(r"bat-(\d+)", re.IGNORECASE) +DISCOVERY_ID_PATTERN = re.compile(r"dcy-(\d+)", re.IGNORECASE) +RELEASE_ID_PATTERN = re.compile(r"rel-(\d+)", re.IGNORECASE) +STATUS_PATTERN = re.compile(r"^-\s*(?:Status|상태):\s*(.+?)\s*$", re.MULTILINE) +BATCH_PROFILE_PATTERN = re.compile(r"^-\s*Profile:\s*(.+?)\s*$", re.MULTILINE) +RELEASE_PROFILE_PATTERN = re.compile(r"^-\s*Profile:\s*(.+?)\s*$", re.MULTILINE) +REQ_TYPE_LINE_PATTERN = re.compile(r"^-\s*Type:\s*(.+)$", re.MULTILINE) +INLINE_CODE_PATTERN = re.compile(r"`([^`]+)`") + +BATCH_PROFILES = {"standard", "batch-lite"} +RELEASE_PROFILES = {"docs-only", "tooling", "app-service"} + +FINALIZED_STATUSES = { + "approved", + "archived", + "completed", + "confirmed", + "deprecated", + "feedback-captured", + "implemented", + "in-delivery", + "release-candidate", + "released", +} + +ACTIVE_INDEX_FILES = [ + Path("docs/discovery/index.md"), + Path("docs/srs/index.md"), + Path("docs/batches/index.md"), + Path("docs/releases/index.md"), +] + +BASELINE_DOC_FILES = [ + Path("docs/project-structure.md"), + Path("docs/runtime-flows.md"), +] + +OPTIONAL_CROSS_CUTTING_DOC_FILES = [ + Path("docs/interface-contract.md"), + Path("docs/data-model.md"), +] + +BOOTSTRAP_REQUIRED_FILES = ACTIVE_INDEX_FILES + BASELINE_DOC_FILES + + +@dataclass(frozen=True) +class Finding: + level: str + code: str + path: Path + message: str + + +@dataclass(frozen=True) +class TraceabilityRow: + req_id: str + status: str + discovery_ids: tuple[str, ...] + batch_ids: tuple[str, ...] + release_ready_batch_ids: tuple[str, ...] + release_ids: tuple[str, ...] + flags: tuple[str, ...] + + +@dataclass(frozen=True) +class FeedbackHandoffRow: + release_id: str + status: str + discovery_inputs: tuple[str, ...] + req_inputs: tuple[str, ...] + change_req_inputs: tuple[str, ...] + flags: tuple[str, ...] + + +@dataclass(frozen=True) +class ChangeReqImpact: + change_id: str + status_recommendation: str + reverification_needed: str + implementation_invalidated: str + + +class Doctor: + def __init__(self, workspace_root: Path, package_root: Path, strict_missing_docs: bool, report_path: Path | None = None) -> None: + self.workspace_root = workspace_root + self.package_root = package_root + self.strict_missing_docs = strict_missing_docs + self.report_path = report_path + self.report_written = False + self.findings: list[Finding] = [] + self.traceability_rows: list[TraceabilityRow] = [] + self.feedback_handoff_rows: list[FeedbackHandoffRow] = [] + + def add(self, level: str, code: str, path: Path, message: str) -> None: + self.findings.append(Finding(level=level, code=code, path=path, message=message)) + + def run(self) -> int: + self.check_req_type_contract() + self.check_design_template_contract() + self.check_markdown_links(self.package_markdown_files(), relative_to=self.package_root) + + workspace_mode = self.check_active_workspace_roots() + if workspace_mode: + self.check_discovery_index() + self.check_srs_index() + self.check_batch_index() + self.check_release_index() + self.build_traceability_matrix() + self.build_feedback_handoff_summary() + self.check_placeholders(self.workspace_markdown_files()) + self.check_markdown_links(self.workspace_markdown_files(), relative_to=self.workspace_root) + + if self.report_path is not None: + self.write_report() + self.print_report() + return 1 if any(f.level == "ERROR" for f in self.findings) else 0 + + def package_markdown_files(self) -> list[Path]: + patterns = [ + Path("README.md"), + Path("source/instruction.md"), + Path("source/.github/instructions"), + Path("skills"), + Path("templates"), + ] + files: list[Path] = [] + for pattern in patterns: + full_path = self.package_root / pattern + if full_path.is_file(): + files.append(full_path) + continue + if full_path.is_dir(): + files.extend(sorted(full_path.rglob("*.md"))) + return files + + def workspace_markdown_files(self) -> list[Path]: + docs_root = self.workspace_root / "docs" + if not docs_root.exists(): + return [] + return sorted(docs_root.rglob("*.md")) + + def check_active_workspace_roots(self) -> bool: + existing = [path for path in ACTIVE_INDEX_FILES if (self.workspace_root / path).exists()] + missing_bootstrap_files = [path for path in BOOTSTRAP_REQUIRED_FILES if not (self.workspace_root / path).exists()] + if not existing: + level = "ERROR" if self.strict_missing_docs else "WARN" + self.add( + level, + "missing-active-docs", + self.workspace_root, + "No active SDLC docs were found under docs/. Workspace-specific checks were skipped.", + ) + self.add_bootstrap_required_hint(missing_bootstrap_files) + return False + + for path in ACTIVE_INDEX_FILES: + if not (self.workspace_root / path).exists(): + self.add( + "ERROR", + "missing-root-index", + self.workspace_root / path, + "Missing active index file.", + ) + + if missing_bootstrap_files: + self.add_bootstrap_required_hint(missing_bootstrap_files) + else: + self.check_optional_cross_cutting_docs() + return True + + def check_optional_cross_cutting_docs(self) -> None: + if self.is_package_repo_self_check(): + return + + for path in OPTIONAL_CROSS_CUTTING_DOC_FILES: + if (self.workspace_root / path).exists(): + continue + self.add( + "WARN", + "missing-cross-cutting-baseline-doc", + self.workspace_root / path, + "Missing optional cross-cutting baseline doc. Run /bootstrap-baseline to backfill it or add the document manually if the project needs this shared contract.", + ) + + def add_bootstrap_required_hint(self, missing_files: list[Path]) -> None: + if not missing_files or self.is_package_repo_self_check(): + return + + missing_list = ", ".join(str(path) for path in missing_files[:6]) + if len(missing_files) > 6: + missing_list += ", ..." + + self.add( + "INFO", + "bootstrap-required", + self.workspace_root, + "Fresh host repos should run /bootstrap-baseline in Copilot Chat before the first real Discovery. " + f"Missing bootstrap files: {missing_list}", + ) + + def is_package_repo_self_check(self) -> bool: + if self.workspace_root.resolve() != self.package_root.resolve(): + return False + + required_paths = [ + Path("tools/install.sh"), + Path("examples/p3-change-management"), + ] + return all((self.workspace_root / path).exists() for path in required_paths) + + def check_req_type_contract(self) -> None: + skill_path = self.package_root / "skills/draft-req/SKILL.md" + template_path = self.package_root / "templates/srs/req-template.md" + index_path = self.package_root / "templates/srs/index.md" + + if not skill_path.exists() or not template_path.exists() or not index_path.exists(): + self.add("ERROR", "missing-req-contract-files", self.package_root, "REQ type contract files are missing.") + return + + skill_types = self.parse_req_types_from_skill(skill_path) + template_types = self.parse_req_types_from_template(template_path) + index_types = self.parse_req_types_from_index(index_path) + + if not skill_types or not template_types or not index_types: + self.add("ERROR", "unparseable-req-types", skill_path, "Could not parse REQ types from skill/template/index files.") + return + + normalized_sets = { + tuple(sorted(skill_types)), + tuple(sorted(template_types)), + tuple(sorted(index_types)), + } + if len(normalized_sets) != 1: + self.add( + "ERROR", + "req-type-mismatch", + skill_path, + "REQ type taxonomy differs across draft-req skill, req-template, and srs index.", + ) + + def check_design_template_contract(self) -> None: + skill_path = self.package_root / "skills/draft-batch-design/SKILL.md" + template_path = self.package_root / "templates/batches/design.md" + if not skill_path.exists() or not template_path.exists(): + self.add("ERROR", "missing-design-contract-files", self.package_root, "Batch design contract files are missing.") + return + + required_sections = self.parse_design_required_sections(skill_path) + template_sections = self.parse_markdown_headings(template_path) + missing = [section for section in required_sections if section not in template_sections] + for section in missing: + self.add( + "ERROR", + "design-template-missing-section", + template_path, + f"Missing required section '{section}' declared by draft-batch-design.", + ) + + def check_markdown_links(self, files: Iterable[Path], relative_to: Path) -> None: + for file_path in files: + text = file_path.read_text(encoding="utf-8") + for target in LINK_PATTERN.findall(text): + target = target.strip() + if not target or target.startswith(("http://", "https://", "mailto:")): + continue + if target.startswith("#"): + continue + if any(token in target for token in ("<", ">", "{{", "}}")): + continue + link_target = target.split("#", 1)[0] + resolved = (file_path.parent / link_target).resolve() + if not resolved.exists(): + self.add( + "ERROR", + "broken-link", + file_path, + f"Link target '{target}' does not exist.", + ) + + def check_placeholders(self, files: Iterable[Path]) -> None: + for file_path in files: + text = file_path.read_text(encoding="utf-8") + matches = sorted(set(PLACEHOLDER_PATTERN.findall(text))) + if not matches: + continue + status = self.parse_status(text) + level = "ERROR" if status in FINALIZED_STATUSES else "WARN" + preview = ", ".join(matches[:3]) + if len(matches) > 3: + preview += ", ..." + self.add(level, "placeholder-found", file_path, f"Found unresolved placeholders: {preview}") + + def check_discovery_index(self) -> None: + index_path = self.workspace_root / "docs/discovery/index.md" + if not index_path.exists(): + return + + for row in self.parse_markdown_table(index_path, "## Discovery 목록"): + doc_target = self.extract_link_target(row.get("문서", "")) + doc_path = index_path.parent / doc_target if doc_target else None + if not doc_target or not doc_path or not doc_path.exists(): + self.add("ERROR", "discovery-index-link", index_path, f"Discovery row has a missing document target: {row}") + continue + doc_text = doc_path.read_text(encoding="utf-8") + doc_status = self.parse_status(doc_text) + row_status = row.get("상태", "").strip().lower() + if doc_status and row_status and doc_status != row_status: + self.add("ERROR", "discovery-status-mismatch", doc_path, f"Index status '{row_status}' does not match document status '{doc_status}'.") + + def check_srs_index(self) -> None: + index_path = self.workspace_root / "docs/srs/index.md" + if not index_path.exists(): + return + + index_text = index_path.read_text(encoding="utf-8") + req_files = sorted((self.workspace_root / "docs/srs").glob("*/req-*.md")) + register = self.parse_markdown_table(index_path, "## Requirement Register") + register_map = {self.normalize_req_id(row.get("ID", "")): row for row in register if row.get("ID")} + + max_req_number = 0 + for req_path in req_files: + req_id = self.normalize_req_id(req_path.stem.split("_", 1)[0]) + max_req_number = max(max_req_number, self.req_id_number(req_id)) + text = req_path.read_text(encoding="utf-8") + req_status = self.parse_status(text) + req_type = self.parse_req_type(text) + latest_change_req_impact = self.parse_latest_change_req_impact(text) + expected_type = req_path.parent.name + if req_type and expected_type and req_type != expected_type: + self.add("ERROR", "req-folder-type-mismatch", req_path, f"REQ type '{req_type}' does not match folder '{expected_type}'.") + self.check_req_change_rollback(req_path, req_status, latest_change_req_impact) + if req_id not in register_map: + self.add("ERROR", "req-missing-register-row", req_path, "REQ file is missing from Requirement Register.") + continue + row = register_map[req_id] + row_status = row.get("Status", "").strip().lower() + row_type = row.get("Type", "").strip() + row_link = self.extract_link_target(row.get("Link", "")) + if req_status and row_status and req_status != row_status: + self.add("ERROR", "req-status-mismatch", req_path, f"Register status '{row_status}' does not match REQ status '{req_status}'.") + if req_type and row_type and req_type != row_type: + self.add("ERROR", "req-type-register-mismatch", req_path, f"Register type '{row_type}' does not match REQ type '{req_type}'.") + if row_link: + resolved = (index_path.parent / row_link).resolve() + if resolved != req_path.resolve(): + self.add("ERROR", "req-link-mismatch", req_path, f"Register link points to '{row_link}', expected '{req_path.relative_to(index_path.parent)}'.") + + for req_id, row in register_map.items(): + link_target = self.extract_link_target(row.get("Link", "")) + if not link_target: + self.add("ERROR", "req-register-link-missing", index_path, f"Requirement Register row for '{req_id}' is missing a link target.") + continue + linked_path = (index_path.parent / link_target).resolve() + if not linked_path.exists(): + self.add("ERROR", "req-register-link-broken", index_path, f"Requirement Register link '{link_target}' does not exist.") + + next_req = self.parse_next_requirement_id(index_text) + if next_req is not None: + expected_next = max_req_number + 1 if max_req_number else 1 + if next_req != expected_next: + self.add( + "ERROR", + "next-req-id-mismatch", + index_path, + f"Next Requirement ID is REQ-{next_req:03d}, expected REQ-{expected_next:03d}.", + ) + + def check_batch_index(self) -> None: + index_path = self.workspace_root / "docs/batches/index.md" + if not index_path.exists(): + return + + rows = self.parse_markdown_table(index_path, "## Batch Register") + row_map = {row.get("BAT ID", "").strip().lower(): row for row in rows if row.get("BAT ID")} + batch_dirs = sorted(path for path in (self.workspace_root / "docs/batches").glob("bat-*") if path.is_dir()) + for batch_dir in batch_dirs: + batch_id = batch_dir.name.split("_", 1)[0].lower() + batch_index = batch_dir / "index.md" + if not batch_index.exists(): + self.add("ERROR", "batch-index-missing", batch_dir, "Batch folder is missing index.md.") + continue + batch_index_text = batch_index.read_text(encoding="utf-8") + batch_status = self.parse_status(batch_index_text) + batch_profile = self.parse_batch_profile(batch_index_text) + if batch_profile and batch_profile not in BATCH_PROFILES: + self.add( + "ERROR", + "batch-profile-invalid", + batch_index, + f"Unsupported batch profile '{batch_profile}'. Expected 'standard' or 'batch-lite'.", + ) + for doc_name in self.required_batch_docs(batch_profile, batch_status): + if not (batch_dir / doc_name).exists(): + self.add("ERROR", "batch-doc-missing", batch_dir / doc_name, "Batch folder is missing a required document.") + if batch_id not in row_map: + self.add("ERROR", "batch-missing-register-row", batch_index, "Batch folder is missing from Batch Register.") + continue + row = row_map[batch_id] + row_profile = row.get("Profile", "").strip().lower() + row_status = row.get("Status", "").strip().lower() + if row_profile and batch_profile and row_profile != batch_profile: + self.add("ERROR", "batch-profile-mismatch", batch_index, f"Register profile '{row_profile}' does not match batch profile '{batch_profile}'.") + if batch_status and row_status and batch_status != row_status: + self.add("ERROR", "batch-status-mismatch", batch_index, f"Register status '{row_status}' does not match batch status '{batch_status}'.") + + for batch_id, row in row_map.items(): + folder_target = self.extract_link_target(row.get("Folder", "")) or row.get("Folder", "").strip() + if not folder_target: + self.add("ERROR", "batch-register-folder-missing", index_path, f"Batch Register row for '{batch_id}' is missing a folder path.") + continue + linked_path = (index_path.parent / folder_target).resolve() + if not linked_path.exists(): + self.add("ERROR", "batch-register-folder-broken", index_path, f"Batch Register folder '{folder_target}' does not exist.") + + def check_release_index(self) -> None: + index_path = self.workspace_root / "docs/releases/index.md" + if not index_path.exists(): + return + + rows = self.parse_markdown_table(index_path, "## Release Register") + row_map = {row.get("REL ID", "").strip().lower(): row for row in rows if row.get("REL ID")} + release_files = sorted((self.workspace_root / "docs/releases").glob("rel-*.md")) + for release_path in release_files: + rel_id = release_path.stem.split("_", 1)[0].lower() + if rel_id not in row_map: + self.add("ERROR", "release-missing-register-row", release_path, "Release document is missing from Release Register.") + continue + row = row_map[rel_id] + release_text = release_path.read_text(encoding="utf-8") + release_status = self.parse_status(release_text) + release_profile = self.parse_release_profile(release_text) + if release_profile and release_profile not in RELEASE_PROFILES: + self.add( + "ERROR", + "release-profile-invalid", + release_path, + f"Unsupported release profile '{release_profile}'. Expected 'docs-only', 'tooling', or 'app-service'.", + ) + row_profile = row.get("Profile", "").strip().lower() + row_status = row.get("Status", "").strip().lower() + if row_profile and release_profile and row_profile != release_profile: + self.add("ERROR", "release-profile-mismatch", release_path, f"Register profile '{row_profile}' does not match release profile '{release_profile}'.") + if release_status and row_status and release_status != row_status: + self.add("ERROR", "release-status-mismatch", release_path, f"Register status '{row_status}' does not match release status '{release_status}'.") + + for rel_id, row in row_map.items(): + link_target = self.extract_link_target(row.get("Link", "")) + if not link_target: + self.add("ERROR", "release-register-link-missing", index_path, f"Release Register row for '{rel_id}' is missing a link target.") + continue + linked_path = (index_path.parent / link_target).resolve() + if not linked_path.exists(): + self.add("ERROR", "release-register-link-broken", index_path, f"Release Register link '{link_target}' does not exist.") + + def build_traceability_matrix(self) -> None: + req_files = sorted((self.workspace_root / "docs/srs").glob("*/req-*.md")) + if not req_files: + return + + discovery_refs = self.parse_discovery_traceability() + batch_refs, batch_sources, batch_statuses = self.parse_batch_traceability() + release_refs, release_statuses, release_feedback_states = self.parse_release_traceability() + + for release_id, release_status in release_statuses.items(): + feedback_state = release_feedback_states.get(release_id, "missing") + release_path = self.resolve_release_path(release_id) + if release_status == "released": + if feedback_state != "resolved": + self.add( + "WARN", + "release-feedback-unlinked", + release_path, + "Released release has no resolved Feedback Handoff content.", + ) + else: + self.add( + "WARN", + "release-feedback-pending", + release_path, + "Released release has Feedback Handoff content but is not yet closed with feedback-captured status.", + ) + elif release_status == "feedback-captured" and feedback_state != "resolved": + self.add( + "ERROR", + "release-feedback-unresolved", + release_path, + "Release is marked feedback-captured but Feedback Handoff is still unresolved.", + ) + + rows: list[TraceabilityRow] = [] + for req_path in req_files: + req_id = self.normalize_req_id(req_path.stem.split("_", 1)[0]) + if not req_id: + continue + + req_text = req_path.read_text(encoding="utf-8") + req_status = self.parse_status(req_text) + req_batches = set(batch_refs.get(req_id, set())) + req_discoveries = set(discovery_refs.get(req_id, set())) + req_releases: set[str] = set() + + for batch_id in req_batches: + req_discoveries.update(batch_sources.get(batch_id, set())) + req_releases.update(release_refs.get(batch_id, set())) + + releasable_batches = { + batch_id + for batch_id in req_batches + if batch_statuses.get(batch_id) in {"release-candidate", "released"} + } + + flags: list[str] = [] + has_discovery = bool(req_discoveries) + has_batch = bool(req_batches) + + if req_status == "implemented" and not has_batch: + flags.append("implemented-without-batch") + self.add( + "ERROR", + "implemented-req-no-batch", + req_path, + "Implemented REQ has no batch traceability.", + ) + elif req_status in {"proposed", "approved", "implemented"} and not has_discovery and not has_batch: + flags.append("orphan") + self.add( + "WARN", + "orphan-req", + req_path, + "REQ has no Discovery or Batch linkage.", + ) + elif req_status == "approved" and not has_batch: + flags.append("approved-without-batch") + self.add( + "WARN", + "orphan-approved-req", + req_path, + "Approved REQ is not included in any batch.", + ) + elif req_status == "approved" and not releasable_batches: + flags.append("approved-without-release-candidate-batch") + self.add( + "WARN", + "approved-req-no-release-candidate-batch", + req_path, + "Approved REQ is not included in any release-candidate or released batch.", + ) + + rows.append( + TraceabilityRow( + req_id=req_id, + status=req_status or "unknown", + discovery_ids=tuple(sorted(req_discoveries)), + batch_ids=tuple(sorted(req_batches)), + release_ready_batch_ids=tuple(sorted(releasable_batches)), + release_ids=tuple(sorted(req_releases)), + flags=tuple(flags), + ) + ) + + self.traceability_rows = sorted(rows, key=lambda row: self.req_id_number(row.req_id)) + + def parse_discovery_traceability(self) -> dict[str, set[str]]: + refs: dict[str, set[str]] = {} + discovery_dir = self.workspace_root / "docs/discovery" + if not discovery_dir.exists(): + return refs + + for discovery_path in sorted(discovery_dir.glob("dcy-*.md")): + discovery_id = self.normalize_discovery_id(discovery_path.stem) + if not discovery_id: + continue + for line in discovery_path.read_text(encoding="utf-8").splitlines(): + if "생성된 REQ 참조" not in line: + continue + for req_id in self.extract_req_ids(line): + refs.setdefault(req_id, set()).add(discovery_id) + return refs + + def parse_batch_traceability(self) -> tuple[dict[str, set[str]], dict[str, set[str]], dict[str, str]]: + req_to_batches: dict[str, set[str]] = {} + batch_to_discoveries: dict[str, set[str]] = {} + batch_statuses: dict[str, str] = {} + batches_dir = self.workspace_root / "docs/batches" + if not batches_dir.exists(): + return req_to_batches, batch_to_discoveries, batch_statuses + + for batch_dir in sorted(path for path in batches_dir.glob("bat-*") if path.is_dir()): + batch_index = batch_dir / "index.md" + if not batch_index.exists(): + continue + batch_id = self.normalize_batch_id(batch_dir.name) + if not batch_id: + continue + + batch_text = batch_index.read_text(encoding="utf-8") + batch_statuses[batch_id] = self.parse_status(batch_text) + + for line in batch_text.splitlines(): + if "Included REQ" in line: + for req_id in self.extract_req_ids(line): + req_to_batches.setdefault(req_id, set()).add(batch_id) + if "Source Discovery" in line: + for discovery_id in self.extract_discovery_ids(line): + batch_to_discoveries.setdefault(batch_id, set()).add(discovery_id) + + return req_to_batches, batch_to_discoveries, batch_statuses + + def parse_release_traceability(self) -> tuple[dict[str, set[str]], dict[str, str], dict[str, str]]: + batch_to_releases: dict[str, set[str]] = {} + release_statuses: dict[str, str] = {} + release_feedback_states: dict[str, str] = {} + releases_dir = self.workspace_root / "docs/releases" + if not releases_dir.exists(): + return batch_to_releases, release_statuses, release_feedback_states + + for release_path in sorted(releases_dir.glob("rel-*.md")): + release_id = self.normalize_release_id(release_path.stem) + if not release_id: + continue + release_text = release_path.read_text(encoding="utf-8") + release_statuses[release_id] = self.parse_status(release_text) + release_feedback_states[release_id] = self.parse_release_feedback_state(release_text) + for line in release_text.splitlines(): + if "Included Batch" not in line: + continue + for batch_id in self.extract_batch_ids(line): + batch_to_releases.setdefault(batch_id, set()).add(release_id) + + return batch_to_releases, release_statuses, release_feedback_states + + def write_report(self) -> None: + if self.report_path is None: + return + + try: + self.report_path.parent.mkdir(parents=True, exist_ok=True) + self.report_path.write_text(self.render_markdown_report(), encoding="utf-8") + self.report_written = True + except OSError as exc: + self.add( + "ERROR", + "report-write-failed", + self.report_path, + f"Failed to write Markdown report: {exc}", + ) + + def render_markdown_report(self) -> str: + error_count, warn_count, info_count = self.summarize_findings() + lines = [ + "# StagePilot Doctor Report", + "", + "## Metadata", + "", + f"- Generated At: `{datetime.now().astimezone().isoformat(timespec='seconds')}`", + f"- Workspace Root: `{self.workspace_root}`", + f"- Package Root: `{self.package_root}`", + f"- Strict Missing Docs: `{str(self.strict_missing_docs).lower()}`", + ] + if self.report_path is not None: + lines.append(f"- Requested Report Path: `{self.report_path}`") + + lines.extend( + [ + "", + "## Summary", + "", + f"- Errors: {error_count}", + f"- Warnings: {warn_count}", + f"- Info: {info_count}", + "", + "## Findings", + "", + ] + ) + + sorted_findings = self.sorted_findings() + if not sorted_findings: + lines.append("- None") + else: + for level in ("ERROR", "WARN", "INFO"): + level_findings = [finding for finding in sorted_findings if finding.level == level] + if not level_findings: + continue + lines.append(f"### {level}") + lines.append("") + for finding in level_findings: + lines.append( + f"- [{finding.code}] `{self.display_path(finding.path)}`: {finding.message}" + ) + lines.append("") + + lines.extend(["## Traceability Matrix", ""]) + if not self.traceability_rows: + lines.append("- No traceability rows generated.") + else: + lines.extend( + [ + "| REQ ID | Status | Discovery | Batch | Release-Ready Batch | Release | Flags |", + "| --- | --- | --- | --- | --- | --- | --- |", + ] + ) + for row in self.traceability_rows: + discovery = ", ".join(row.discovery_ids) if row.discovery_ids else "-" + batch = ", ".join(row.batch_ids) if row.batch_ids else "-" + release_ready_batch = ", ".join(row.release_ready_batch_ids) if row.release_ready_batch_ids else "-" + release = ", ".join(row.release_ids) if row.release_ids else "-" + flags = ", ".join(row.flags) if row.flags else "-" + lines.append( + f"| {row.req_id} | {row.status} | {discovery} | {batch} | {release_ready_batch} | {release} | {flags} |" + ) + + lines.extend(["", "## Feedback Loop Summary", ""]) + if not self.feedback_handoff_rows: + lines.append("- No feedback handoff rows generated.") + else: + lines.extend( + [ + "| Release | Status | Discovery Input | REQ Input | Change Request Input | Flags |", + "| --- | --- | --- | --- | --- | --- |", + ] + ) + for row in self.feedback_handoff_rows: + discovery_inputs = ", ".join(row.discovery_inputs) if row.discovery_inputs else "-" + req_inputs = ", ".join(row.req_inputs) if row.req_inputs else "-" + change_req_inputs = ", ".join(row.change_req_inputs) if row.change_req_inputs else "-" + flags = ", ".join(row.flags) if row.flags else "-" + lines.append( + f"| {row.release_id} | {row.status} | {discovery_inputs} | {req_inputs} | {change_req_inputs} | {flags} |" + ) + + return "\n".join(lines) + "\n" + + def sorted_findings(self) -> list[Finding]: + severity_order = {"ERROR": 0, "WARN": 1, "INFO": 2} + return sorted( + self.findings, + key=lambda item: (severity_order.get(item.level, 99), str(item.path), item.code), + ) + + def summarize_findings(self) -> tuple[int, int, int]: + error_count = sum(1 for finding in self.findings if finding.level == "ERROR") + warn_count = sum(1 for finding in self.findings if finding.level == "WARN") + info_count = sum(1 for finding in self.findings if finding.level == "INFO") + return error_count, warn_count, info_count + + def print_report(self) -> None: + if not self.findings: + print("stagepilot-doctor: no issues found") + else: + for finding in self.sorted_findings(): + display_path = self.display_path(finding.path) + print(f"{finding.level} [{finding.code}] {display_path}: {finding.message}") + + if self.traceability_rows: + print() + print("Traceability Matrix") + print("REQ ID | Status | Discovery | Batch | Release-Ready Batch | Release | Flags") + print("--- | --- | --- | --- | --- | --- | ---") + for row in self.traceability_rows: + discovery = ", ".join(row.discovery_ids) if row.discovery_ids else "-" + batch = ", ".join(row.batch_ids) if row.batch_ids else "-" + release_ready_batch = ", ".join(row.release_ready_batch_ids) if row.release_ready_batch_ids else "-" + release = ", ".join(row.release_ids) if row.release_ids else "-" + flags = ", ".join(row.flags) if row.flags else "-" + print(f"{row.req_id} | {row.status} | {discovery} | {batch} | {release_ready_batch} | {release} | {flags}") + + if self.feedback_handoff_rows: + print() + print("Feedback Loop Summary") + print("Release | Status | Discovery Input | REQ Input | Change Request Input | Flags") + print("--- | --- | --- | --- | --- | ---") + for row in self.feedback_handoff_rows: + discovery_inputs = ", ".join(row.discovery_inputs) if row.discovery_inputs else "-" + req_inputs = ", ".join(row.req_inputs) if row.req_inputs else "-" + change_req_inputs = ", ".join(row.change_req_inputs) if row.change_req_inputs else "-" + flags = ", ".join(row.flags) if row.flags else "-" + print(f"{row.release_id} | {row.status} | {discovery_inputs} | {req_inputs} | {change_req_inputs} | {flags}") + + error_count, warn_count, info_count = self.summarize_findings() + print(f"stagepilot-doctor: {error_count} error(s), {warn_count} warning(s), {info_count} info message(s)") + if self.report_written and self.report_path is not None: + print(f"stagepilot-doctor: wrote Markdown report to {self.report_path}") + + def display_path(self, path: Path) -> str: + for base in (self.workspace_root, self.package_root): + try: + return str(path.resolve().relative_to(base.resolve())) or "." + except ValueError: + continue + return str(path) + + def resolve_release_path(self, release_id: str) -> Path: + releases_dir = self.workspace_root / "docs/releases" + matches = sorted(releases_dir.glob(f"{release_id}_*.md")) + if matches: + return matches[0] + return releases_dir / f"{release_id}.md" + + def check_req_change_rollback(self, req_path: Path, req_status: str, latest_change_req_impact: ChangeReqImpact | None) -> None: + if latest_change_req_impact is None: + return + + recommendation = latest_change_req_impact.status_recommendation + implementation_invalidated = latest_change_req_impact.implementation_invalidated == "yes" + reverification_needed = latest_change_req_impact.reverification_needed == "yes" + + if recommendation == "revert-to-approved" and req_status == "implemented": + self.add( + "ERROR", + "req-change-rollback-missing", + req_path, + f"Latest change '{latest_change_req_impact.change_id}' recommends reverting to Approved, but REQ status is still Implemented.", + ) + elif recommendation == "revert-to-proposed" and req_status in {"approved", "implemented"}: + self.add( + "ERROR", + "req-change-rollback-missing", + req_path, + f"Latest change '{latest_change_req_impact.change_id}' recommends reverting to Proposed, but REQ status is still '{req_status}'.", + ) + elif implementation_invalidated and req_status == "implemented": + self.add( + "ERROR", + "req-change-rollback-missing", + req_path, + f"Latest change '{latest_change_req_impact.change_id}' invalidates existing implementation, but REQ status is still Implemented.", + ) + elif reverification_needed and req_status == "implemented": + self.add( + "WARN", + "req-change-reverification-pending", + req_path, + f"Latest change '{latest_change_req_impact.change_id}' requires reverification while REQ remains Implemented.", + ) + + def build_feedback_handoff_summary(self) -> None: + releases_dir = self.workspace_root / "docs/releases" + if not releases_dir.exists(): + self.feedback_handoff_rows = [] + return + + existing_req_ids = { + self.normalize_req_id(path.stem.split("_", 1)[0]) + for path in sorted((self.workspace_root / "docs/srs").glob("*/req-*.md")) + } + rows: list[FeedbackHandoffRow] = [] + + for release_path in sorted(releases_dir.glob("rel-*.md")): + release_id = self.normalize_release_id(release_path.stem) + if not release_id: + continue + + release_text = release_path.read_text(encoding="utf-8") + feedback_inputs = self.parse_release_feedback_inputs(release_text) + discovery_inputs = self.normalize_feedback_values(feedback_inputs.get("discovery", [])) + req_inputs = self.normalize_feedback_values(feedback_inputs.get("req", [])) + change_req_inputs = self.normalize_feedback_values(feedback_inputs.get("change-req", [])) + + flags: list[str] = [] + if not discovery_inputs and not req_inputs and not change_req_inputs: + flags.append("no-follow-up-input") + + referenced_change_req_ids = { + req_id + for value in change_req_inputs + for req_id in self.extract_req_ids(value) + } + missing_change_req_ids = sorted(req_id for req_id in referenced_change_req_ids if req_id not in existing_req_ids) + if missing_change_req_ids: + flags.append("unknown-change-req-target") + self.add( + "WARN", + "feedback-change-req-target-missing", + release_path, + f"Change Request Input references missing REQ IDs: {', '.join(missing_change_req_ids)}", + ) + + rows.append( + FeedbackHandoffRow( + release_id=release_id, + status=self.parse_status(release_text) or "unknown", + discovery_inputs=discovery_inputs, + req_inputs=req_inputs, + change_req_inputs=change_req_inputs, + flags=tuple(flags), + ) + ) + + self.feedback_handoff_rows = rows + + @staticmethod + def parse_req_types_from_skill(path: Path) -> list[str]: + lines = path.read_text(encoding="utf-8").splitlines() + collecting = False + types: list[str] = [] + for line in lines: + if "아래 폴더 중 정확히 하나를 선택한다." in line: + collecting = True + continue + if collecting and line.startswith("- 새 REQ 경로"): + break + if collecting: + match = INLINE_CODE_PATTERN.search(line) + if match: + types.append(match.group(1)) + return types + + @staticmethod + def parse_req_types_from_template(path: Path) -> list[str]: + text = path.read_text(encoding="utf-8") + match = REQ_TYPE_LINE_PATTERN.search(text) + if not match: + return [] + return [part.strip() for part in match.group(1).split("|") if part.strip()] + + @staticmethod + def parse_req_types_from_index(path: Path) -> list[str]: + rows = Doctor.parse_markdown_table(path, "## Requirement 타입 분류 도표") + types: list[str] = [] + for row in rows: + type_name = row.get("타입", "").strip().strip("*") + if type_name: + types.append(type_name) + return types + + @staticmethod + def parse_design_required_sections(path: Path) -> list[str]: + lines = path.read_text(encoding="utf-8").splitlines() + collecting = False + sections: list[str] = [] + for line in lines: + if "design.md에는 최소한 아래 내용을 포함해야 한다." in line: + collecting = True + continue + if collecting and not line.startswith(("\t-", " -", "- ")): + if line.strip(): + break + if collecting: + entry = line.strip().lstrip("-").strip() + if not entry: + continue + cleaned = entry.strip("`") + if "(" in cleaned: + cleaned = cleaned.split("(", 1)[0].strip() + sections.append(cleaned) + return sections + + @staticmethod + def parse_markdown_headings(path: Path) -> set[str]: + headings = set() + for line in path.read_text(encoding="utf-8").splitlines(): + if line.startswith("## "): + headings.add(line[3:].strip()) + return headings + + @staticmethod + def parse_markdown_table(path: Path, heading: str) -> list[dict[str, str]]: + lines = path.read_text(encoding="utf-8").splitlines() + start_index = None + for index, line in enumerate(lines): + if line.strip() == heading: + start_index = index + 1 + break + if start_index is None: + return [] + + table_lines: list[str] = [] + for line in lines[start_index:]: + if line.strip().startswith("|"): + table_lines.append(line) + continue + if table_lines: + break + + if len(table_lines) < 2: + return [] + + header = [cell.strip() for cell in table_lines[0].strip().strip("|").split("|")] + rows: list[dict[str, str]] = [] + for line in table_lines[2:]: + cells = [cell.strip() for cell in line.strip().strip("|").split("|")] + if len(cells) != len(header): + continue + rows.append(dict(zip(header, cells))) + return rows + + @staticmethod + def extract_link_target(cell: str) -> str | None: + match = LINK_PATTERN.search(cell) + if match: + return match.group(1).split("#", 1)[0] + return None + + @staticmethod + def parse_status(text: str) -> str: + match = STATUS_PATTERN.search(text) + if not match: + return "" + value = match.group(1).strip().strip("`") + return value.split("|")[0].strip().lower() + + @staticmethod + def parse_batch_profile(text: str) -> str: + match = BATCH_PROFILE_PATTERN.search(text) + if not match: + return "standard" + value = match.group(1).strip().strip("`") + return value.split("|")[0].strip().lower() + + @staticmethod + def parse_release_profile(text: str) -> str: + match = RELEASE_PROFILE_PATTERN.search(text) + if not match: + return "app-service" + value = match.group(1).strip().strip("`") + return value.split("|")[0].strip().lower() + + @staticmethod + def required_batch_docs(profile: str, status: str) -> tuple[str, ...]: + normalized_profile = profile or "standard" + normalized_status = status or "draft" + if normalized_profile == "batch-lite": + required_docs = ["planning.md"] + if normalized_status in {"in-delivery", "release-candidate", "released", "archived"}: + required_docs.append("implementation.md") + if normalized_status in {"release-candidate", "released", "archived"}: + required_docs.append("verification.md") + return tuple(required_docs) + return ("planning.md", "design.md", "implementation.md", "verification.md") + + @staticmethod + def parse_req_type(text: str) -> str: + match = REQ_TYPE_LINE_PATTERN.search(text) + if not match: + return "" + value = match.group(1).strip() + return value.split("|")[0].strip() + + @staticmethod + def parse_next_requirement_id(text: str) -> int | None: + match = re.search(r"Current:\s*`REQ-(\d+)`", text) + if not match: + return None + return int(match.group(1)) + + @staticmethod + def parse_release_feedback_state(text: str) -> str: + lines = text.splitlines() + in_section = False + values: list[str] = [] + for line in lines: + stripped = line.strip() + if stripped == "## Feedback Handoff": + in_section = True + continue + if in_section and stripped.startswith("## "): + break + if not in_section: + continue + if stripped.startswith("- "): + value = stripped[2:].strip() + if value.endswith(":"): + continue + values.append(value) + + if not in_section: + return "missing" + if not values: + return "missing" + + normalized_values = {value.strip().lower() for value in values} + unresolved_tokens = ( + "<", + ">", + "{{", + "}}", + "후속 discovery 후보", + "후속 req 후보", + "tbd", + "미정", + ) + if any(any(token in value for token in unresolved_tokens) for value in normalized_values): + return "unresolved" + if normalized_values.issubset({"없음", "none", "n/a", "na"}): + return "resolved" + return "resolved" + + @staticmethod + def parse_latest_change_req_impact(text: str) -> ChangeReqImpact | None: + sections = re.split(r"^###\s+(CHG-\d{8}-\d+)\s*$", text, flags=re.MULTILINE) + if len(sections) < 3: + return None + + latest_change_id = sections[-2].strip() + latest_change_body = sections[-1] + status_recommendation = Doctor.parse_change_log_field(latest_change_body, "Status Recommendation") + reverification_needed = Doctor.parse_change_log_field(latest_change_body, "Reverification Needed") + implementation_invalidated = Doctor.parse_change_log_field(latest_change_body, "Existing Implementation Invalidated") + + if not status_recommendation and not reverification_needed and not implementation_invalidated: + return None + + return ChangeReqImpact( + change_id=latest_change_id, + status_recommendation=status_recommendation, + reverification_needed=reverification_needed, + implementation_invalidated=implementation_invalidated, + ) + + @staticmethod + def parse_change_log_field(text: str, field_name: str) -> str: + pattern = rf"^-\s*{re.escape(field_name)}:\s*(.+?)\s*$" + match = re.search(pattern, text, flags=re.MULTILINE) + if not match: + return "" + return match.group(1).strip().strip("`").lower() + + @staticmethod + def parse_release_feedback_inputs(text: str) -> dict[str, list[str]]: + lines = text.splitlines() + in_section = False + current_group: str | None = None + groups: dict[str, list[str]] = { + "observation": [], + "discovery": [], + "req": [], + "change-req": [], + } + group_labels = { + "Observation Summary": "observation", + "Discovery Input": "discovery", + "REQ Input": "req", + "Change Request Input": "change-req", + } + + for line in lines: + stripped = line.strip() + if stripped == "## Feedback Handoff": + in_section = True + current_group = None + continue + if in_section and stripped.startswith("## "): + break + if not in_section or not stripped.startswith("- "): + continue + + value = stripped[2:].strip() + if value.endswith(":"): + current_group = group_labels.get(value[:-1].strip()) + continue + if current_group is not None: + groups[current_group].append(value) + + return groups + + @staticmethod + def normalize_feedback_values(values: Iterable[str]) -> tuple[str, ...]: + normalized: list[str] = [] + for value in values: + cleaned = value.strip() + if not cleaned or cleaned.lower() in {"없음", "none", "n/a", "na"}: + continue + normalized.append(cleaned) + return tuple(normalized) + + @staticmethod + def extract_req_ids(text: str) -> set[str]: + return {f"req-{int(match.group(1)):03d}" for match in REQ_ID_PATTERN.finditer(text)} + + @staticmethod + def extract_batch_ids(text: str) -> set[str]: + return {f"bat-{int(match.group(1)):03d}" for match in BATCH_ID_PATTERN.finditer(text)} + + @staticmethod + def extract_discovery_ids(text: str) -> set[str]: + return {f"dcy-{int(match.group(1)):03d}" for match in DISCOVERY_ID_PATTERN.finditer(text)} + + @staticmethod + def extract_release_ids(text: str) -> set[str]: + return {f"rel-{int(match.group(1)):03d}" for match in RELEASE_ID_PATTERN.finditer(text)} + + @staticmethod + def normalize_req_id(raw_value: str) -> str: + match = REQ_ID_PATTERN.search(raw_value or "") + if not match: + return "" + return f"req-{int(match.group(1)):03d}" + + @staticmethod + def normalize_batch_id(raw_value: str) -> str: + match = BATCH_ID_PATTERN.search(raw_value or "") + if not match: + return "" + return f"bat-{int(match.group(1)):03d}" + + @staticmethod + def normalize_discovery_id(raw_value: str) -> str: + match = DISCOVERY_ID_PATTERN.search(raw_value or "") + if not match: + return "" + return f"dcy-{int(match.group(1)):03d}" + + @staticmethod + def normalize_release_id(raw_value: str) -> str: + match = RELEASE_ID_PATTERN.search(raw_value or "") + if not match: + return "" + return f"rel-{int(match.group(1)):03d}" + + @staticmethod + def req_id_number(req_id: str) -> int: + match = REQ_ID_PATTERN.search(req_id) + return int(match.group(1)) if match else 0 + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description="Validate StagePilot package contracts and active SDLC workspace documents.") + parser.add_argument("root", nargs="?", default=".", help="Workspace root to validate (default: current directory)") + parser.add_argument( + "--strict-missing-docs", + action="store_true", + help="Treat a missing active docs workspace as an error instead of a warning.", + ) + parser.add_argument( + "--report", + help="Write a Markdown report to the given path.", + ) + return parser + + +def main() -> int: + parser = build_parser() + args = parser.parse_args() + + workspace_root = Path(args.root).resolve() + # tools/stagepilot-doctor.py 기준 한 단계 위가 패키지 루트. + # (호스트에서는 .stage-pilot subtree 루트, 개발 레포에서는 저장소 루트) + package_root = Path(__file__).resolve().parents[1] + report_path = Path(args.report).resolve() if args.report else None + + if not workspace_root.exists(): + parser.error(f"Workspace root does not exist: {workspace_root}") + + doctor = Doctor( + workspace_root=workspace_root, + package_root=package_root, + strict_missing_docs=args.strict_missing_docs, + report_path=report_path, + ) + return doctor.run() + + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/tools/stagepilot.sh b/tools/stagepilot.sh new file mode 100644 index 0000000..841ae06 --- /dev/null +++ b/tools/stagepilot.sh @@ -0,0 +1,72 @@ +#!/usr/bin/env bash + +set -euo pipefail + +usage() { + cat <<'EOF' +Usage: stagepilot.sh [args] + +Commands: + bootstrap-seed [HOST_ROOT] [options] + Create .stage-pilot/bootstrap/baseline.yaml from interactive answers + or explicit CLI arguments. + doctor [HOST_ROOT] [--strict-missing-docs] [--report ] + Run the StagePilot validation script against a workspace. +EOF +} + +fail() { + printf '[stagepilot] ERROR: %s\n' "$*" >&2 + exit 1 +} + +run_doctor() { + local script_dir package_root doctor_script + script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" + package_root="$(cd "${script_dir}/.." && pwd -P)" + doctor_script="${package_root}/tools/stagepilot-doctor.py" + + [ -f "${doctor_script}" ] || fail "Doctor script not found: ${doctor_script}" + command -v python3 >/dev/null 2>&1 || fail "python3 is required to run doctor" + + python3 "${doctor_script}" "$@" +} + +run_bootstrap_seed() { + local script_dir package_root seed_script + script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" + package_root="$(cd "${script_dir}/.." && pwd -P)" + seed_script="${package_root}/tools/stagepilot-bootstrap-seed.py" + + [ -f "${seed_script}" ] || fail "Bootstrap seed helper not found: ${seed_script}" + command -v python3 >/dev/null 2>&1 || fail "python3 is required to generate a bootstrap seed" + + python3 "${seed_script}" "$@" +} + +main() { + local command="${1:-}" + if [ -z "${command}" ]; then + usage + exit 1 + fi + shift || true + + case "${command}" in + bootstrap-seed) + run_bootstrap_seed "$@" + ;; + doctor) + run_doctor "$@" + ;; + -h|--help|help) + usage + ;; + *) + usage + fail "Unknown command: ${command}" + ;; + esac +} + +main "$@" \ No newline at end of file diff --git a/tools/update.sh b/tools/update.sh new file mode 100755 index 0000000..d9b2e58 --- /dev/null +++ b/tools/update.sh @@ -0,0 +1,153 @@ +#!/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: .stage-pilot) + --overwrite Overwrite conflicts during install (same as --policy replace). + (default behavior) + --no-overwrite Preserve host files on conflicts (same as --policy preserve). + --policy install.sh conflict policy: replace|preserve|fail + (default: replace) + --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=".stage-pilot" +POLICY="replace" +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 + ;; + --overwrite) + POLICY="replace" + ;; + --no-overwrite) + POLICY="preserve" + ;; + --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}/tools/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."