From b1610f14c594fcf3c0ddfb4efb73494134d0a2b7 Mon Sep 17 00:00:00 2001 From: agentson Date: Fri, 27 Feb 2026 23:08:29 +0900 Subject: [PATCH] process: enforce session handover gate across sessions (#308) --- .gitea/PULL_REQUEST_TEMPLATE.md | 6 + .gitea/workflows/ci.yml | 38 +++++ .github/workflows/ci.yml | 3 + docs/agent-constraints.md | 6 + docs/commands.md | 13 ++ .../60_repo_enforcement_checklist.md | 3 +- docs/workflow.md | 13 ++ scripts/session_handover_check.py | 138 ++++++++++++++++++ scripts/validate_governance_assets.py | 36 ++++- workflow/session-handover.md | 26 ++++ 10 files changed, 280 insertions(+), 2 deletions(-) create mode 100644 .gitea/workflows/ci.yml create mode 100755 scripts/session_handover_check.py create mode 100644 workflow/session-handover.md diff --git a/.gitea/PULL_REQUEST_TEMPLATE.md b/.gitea/PULL_REQUEST_TEMPLATE.md index 4bda55f..90edcf4 100644 --- a/.gitea/PULL_REQUEST_TEMPLATE.md +++ b/.gitea/PULL_REQUEST_TEMPLATE.md @@ -35,6 +35,12 @@ - [ ] `docs/commands.md`와 `docs/workflow.md` 트러블슈팅 선확인 - [ ] `tea` 사용 (`gh` 미사용) +## Session Handover Gate + +- [ ] `python3 scripts/session_handover_check.py --strict` 통과 +- [ ] `workflow/session-handover.md` 최신 엔트리가 현재 브랜치/당일(UTC) 기준으로 갱신됨 +- 최신 handover 엔트리 heading: + ## Runtime Evidence - 시스템 실제 구동 커맨드: diff --git a/.gitea/workflows/ci.yml b/.gitea/workflows/ci.yml new file mode 100644 index 0000000..f73be37 --- /dev/null +++ b/.gitea/workflows/ci.yml @@ -0,0 +1,38 @@ +name: Gitea CI + +on: + pull_request: + push: + branches: + - main + - feature/** + +jobs: + test: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install dependencies + run: pip install ".[dev]" + + - name: Session handover gate + run: python3 scripts/session_handover_check.py --strict + + - name: Validate governance assets + run: python3 scripts/validate_governance_assets.py + + - name: Validate Ouroboros docs + run: python3 scripts/validate_ouroboros_docs.py + + - name: Lint + run: ruff check src/ tests/ + + - name: Run tests with coverage + run: pytest -v --cov=src --cov-report=term-missing --cov-fail-under=80 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 756de37..67cf621 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,6 +21,9 @@ jobs: - name: Install dependencies run: pip install ".[dev]" + - name: Session handover gate + run: python3 scripts/session_handover_check.py --strict + - name: Validate governance assets run: python3 scripts/validate_governance_assets.py diff --git a/docs/agent-constraints.md b/docs/agent-constraints.md index 0e955fb..1e6754b 100644 --- a/docs/agent-constraints.md +++ b/docs/agent-constraints.md @@ -32,6 +32,11 @@ It is distinct from `docs/requirements-log.md`, which records **project/product (or in a dedicated policy doc) and reference it when working. - Keep entries short and concrete, with dates. +5. **Session start handover gate** + - Before implementation/verification work, run `python3 scripts/session_handover_check.py --strict`. + - Keep `workflow/session-handover.md` updated with a same-day entry for the active branch. + - If the check fails, stop and fix handover artifacts first. + ## Change Control - Changes to this file follow the same workflow as code changes. @@ -50,3 +55,4 @@ It is distinct from `docs/requirements-log.md`, which records **project/product - All agents must pre-read `docs/commands.md` and `docs/workflow.md` troubleshooting before running Gitea issue/PR/comment commands. - `gh` CLI is prohibited for repository ticket/PR operations; use `tea` (or documented Gitea API fallback only). +- Session start must pass `python3 scripts/session_handover_check.py --strict`, with branch-matched entry in `workflow/session-handover.md`. diff --git a/docs/commands.md b/docs/commands.md index 666c0b3..aeb0210 100644 --- a/docs/commands.md +++ b/docs/commands.md @@ -11,6 +11,16 @@ - 기본 도구는 `tea`이며, `tea` 미지원 케이스만 Gitea API를 fallback으로 사용한다. - 실행 전 `docs/workflow.md`의 `Gitea CLI Formatting Troubleshooting`을 반드시 확인한다. +## Session Handover Preflight (Mandatory) + +- 세션 시작 직후(코드 변경 전) 아래 명령을 먼저 실행한다. + +```bash +python3 scripts/session_handover_check.py --strict +``` + +- 실패 시 `workflow/session-handover.md` 최신 엔트리를 보강한 뒤 재실행한다. + ### tea CLI (Gitea Command Line Tool) #### ❌ TTY Error - Interactive Confirmation Fails @@ -150,6 +160,9 @@ python -m src.main --mode=paper --dashboard # Runtime verification monitor (NOT_OBSERVED detection) bash scripts/runtime_verify_monitor.sh +# Session handover gate (must pass before implementation) +python3 scripts/session_handover_check.py --strict + # Follow runtime verification log tail -f data/overnight/runtime_verify_*.log diff --git a/docs/ouroboros/60_repo_enforcement_checklist.md b/docs/ouroboros/60_repo_enforcement_checklist.md index 0b5f809..989248c 100644 --- a/docs/ouroboros/60_repo_enforcement_checklist.md +++ b/docs/ouroboros/60_repo_enforcement_checklist.md @@ -3,7 +3,7 @@ Doc-ID: DOC-OPS-002 Version: 1.0.0 Status: active Owner: tpm -Updated: 2026-02-26 +Updated: 2026-02-27 --> # 저장소 강제 설정 체크리스트 @@ -58,6 +58,7 @@ Updated: 2026-02-26 자동 점검: - 문서 검증 스크립트 통과 - 테스트 통과 +- `python3 scripts/session_handover_check.py --strict` 통과 - 개발 완료 시 시스템 구동/모니터링 증적 코멘트 존재 - 이슈/PR 조작 전에 `docs/commands.md` 및 `docs/workflow.md` 트러블슈팅 확인 코멘트 존재 - `gh` CLI 미사용, `tea` 사용 증적 존재 diff --git a/docs/workflow.md b/docs/workflow.md index 7e39d72..d8ba052 100644 --- a/docs/workflow.md +++ b/docs/workflow.md @@ -30,6 +30,19 @@ Gitea 이슈/PR/코멘트 작업 전에 모든 에이전트는 아래를 먼저 - `tea` 실패 시 동일 명령 재시도 전에 원인/수정사항을 PR 코멘트에 남긴다. - 필요한 경우에만 Gitea API(`localhost:3000`)를 fallback으로 사용한다. +## Session Handover Gate (Mandatory) + +새 세션에서 구현/검증을 시작하기 전에 아래를 선행해야 한다. + +1. `docs/workflow.md`, `docs/commands.md`, `docs/agent-constraints.md` 재확인 +2. `workflow/session-handover.md`에 최신 세션 엔트리 추가 +3. `python3 scripts/session_handover_check.py --strict` 통과 확인 + +강제 규칙: +- handover check 실패 상태에서 코드 수정/이슈 상태 전이/PR 생성 금지 +- 최신 handover 엔트리는 현재 작업 브랜치를 명시해야 한다 +- 최신 handover 엔트리는 당일(UTC) 날짜를 포함해야 한다 + ## Branch Strategy (Mandatory) - Team operation default branch is the **program feature branch**, not `main`. diff --git a/scripts/session_handover_check.py b/scripts/session_handover_check.py new file mode 100755 index 0000000..229301e --- /dev/null +++ b/scripts/session_handover_check.py @@ -0,0 +1,138 @@ +#!/usr/bin/env python3 +"""Session handover preflight gate. + +This script enforces a minimal handover record per working branch so that +new sessions cannot start implementation without reading the required docs +and recording current intent. +""" + +from __future__ import annotations + +import argparse +import subprocess +import sys +from datetime import UTC, datetime +from pathlib import Path + +REQUIRED_DOCS = ( + Path("docs/workflow.md"), + Path("docs/commands.md"), + Path("docs/agent-constraints.md"), +) +HANDOVER_LOG = Path("workflow/session-handover.md") + + +def _run_git(*args: str) -> str: + try: + return ( + subprocess.check_output(["git", *args], stderr=subprocess.DEVNULL) + .decode("utf-8") + .strip() + ) + except Exception: + return "" + + +def _current_branch() -> str: + branch = _run_git("branch", "--show-current") + if branch: + return branch + return _run_git("rev-parse", "--abbrev-ref", "HEAD") + + +def _latest_entry(text: str) -> str: + chunks = text.split("\n### ") + if not chunks: + return "" + if chunks[0].startswith("### "): + chunks[0] = chunks[0][4:] + latest = chunks[-1].strip() + if not latest: + return "" + if not latest.startswith("### "): + latest = f"### {latest}" + return latest + + +def _check_required_files(errors: list[str]) -> None: + for path in REQUIRED_DOCS: + if not path.exists(): + errors.append(f"missing required document: {path}") + if not HANDOVER_LOG.exists(): + errors.append(f"missing handover log: {HANDOVER_LOG}") + + +def _check_handover_entry( + *, + branch: str, + strict: bool, + errors: list[str], +) -> None: + if not HANDOVER_LOG.exists(): + return + text = HANDOVER_LOG.read_text(encoding="utf-8") + latest = _latest_entry(text) + if not latest: + errors.append("handover log has no session entry") + return + + required_tokens = ( + "- branch:", + "- docs_checked:", + "- open_issues_reviewed:", + "- next_ticket:", + ) + for token in required_tokens: + if token not in latest: + errors.append(f"latest handover entry missing token: {token}") + + if strict: + today_utc = datetime.now(UTC).date().isoformat() + if today_utc not in latest: + errors.append( + f"latest handover entry must contain today's UTC date ({today_utc})" + ) + branch_token = f"- branch: {branch}" + if branch_token not in latest: + errors.append( + "latest handover entry must target current branch " + f"({branch_token})" + ) + + +def main() -> int: + parser = argparse.ArgumentParser( + description="Validate session handover gate requirements." + ) + parser.add_argument( + "--strict", + action="store_true", + help="Enforce today-date and current-branch match on latest handover entry.", + ) + args = parser.parse_args() + + errors: list[str] = [] + _check_required_files(errors) + + branch = _current_branch() + if not branch: + errors.append("cannot resolve current git branch") + elif branch in {"main", "master"}: + errors.append(f"working branch must not be {branch}") + + _check_handover_entry(branch=branch, strict=args.strict, errors=errors) + + if errors: + print("[FAIL] session handover check failed") + for err in errors: + print(f"- {err}") + return 1 + + print("[OK] session handover check passed") + print(f"[OK] branch={branch}") + print(f"[OK] handover_log={HANDOVER_LOG}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/scripts/validate_governance_assets.py b/scripts/validate_governance_assets.py index e7058a2..80680d7 100644 --- a/scripts/validate_governance_assets.py +++ b/scripts/validate_governance_assets.py @@ -22,6 +22,10 @@ def main() -> int: pr_template = Path(".gitea/PULL_REQUEST_TEMPLATE.md") issue_template = Path(".gitea/ISSUE_TEMPLATE/runtime_verification.md") + workflow_doc = Path("docs/workflow.md") + commands_doc = Path("docs/commands.md") + handover_script = Path("scripts/session_handover_check.py") + handover_log = Path("workflow/session-handover.md") must_contain( pr_template, @@ -32,6 +36,8 @@ def main() -> int: "NOT_OBSERVED", "tea", "gh", + "Session Handover Gate", + "session_handover_check.py --strict", ], errors, ) @@ -45,6 +51,35 @@ def main() -> int: ], errors, ) + must_contain( + workflow_doc, + [ + "Session Handover Gate (Mandatory)", + "session_handover_check.py --strict", + ], + errors, + ) + must_contain( + commands_doc, + [ + "Session Handover Preflight (Mandatory)", + "session_handover_check.py --strict", + ], + errors, + ) + must_contain( + handover_log, + [ + "Session Handover Log", + "- branch:", + "- docs_checked:", + "- open_issues_reviewed:", + "- next_ticket:", + ], + errors, + ) + if not handover_script.exists(): + errors.append(f"missing file: {handover_script}") if errors: print("[FAIL] governance asset validation failed") @@ -58,4 +93,3 @@ def main() -> int: if __name__ == "__main__": sys.exit(main()) - diff --git a/workflow/session-handover.md b/workflow/session-handover.md new file mode 100644 index 0000000..4233291 --- /dev/null +++ b/workflow/session-handover.md @@ -0,0 +1,26 @@ +# Session Handover Log + +목적: 세션 시작 시 인수인계 확인을 기록하고, 구현/검증 작업 시작 전에 공통 컨텍스트를 강제한다. + +작성 규칙: +- 세션 시작마다 최신 엔트리를 맨 아래에 추가한다. +- `docs/workflow.md`, `docs/commands.md`, `docs/agent-constraints.md`를 먼저 확인한 뒤 기록한다. +- 각 엔트리는 현재 작업 브랜치 기준으로 작성한다. + +템플릿: + +```md +### YYYY-MM-DD | session= +- branch: +- docs_checked: docs/workflow.md, docs/commands.md, docs/agent-constraints.md +- open_issues_reviewed: #... +- next_ticket: #... +- risks_or_notes: ... +``` + +### 2026-02-27 | session=handover-gate-bootstrap +- branch: feature/v3-session-policy-stream +- docs_checked: docs/workflow.md, docs/commands.md, docs/agent-constraints.md +- open_issues_reviewed: #304, #305, #306 +- next_ticket: #304 +- risks_or_notes: 세션 시작 게이트를 문서/스크립트/CI로 강제 적용 -- 2.49.1