diff --git a/.codex/skills/README.md b/.codex/skills/README.md index 1c537e8..47e6351 100644 --- a/.codex/skills/README.md +++ b/.codex/skills/README.md @@ -1,66 +1,36 @@ # Skills -이 디렉터리는 `git-ranker-workflow`에서만 쓰는 project-local skill을 관리한다. - -## 목적 - -- 병렬 에이전트가 같은 품질 기준으로 작업하게 한다. -- 반복 작업을 매번 자연어로 다시 설명하지 않게 한다. -- 문서, spec, evidence 규칙을 실행 절차와 연결한다. +이 디렉터리는 `git-ranker-workflow`에서만 쓰는 최소 workflow skill만 유지한다. ## Canonical Ownership -- 정책, vocabulary, 상태 전이, 종료 조건, evidence minimum은 `docs/operations/`, `docs/architecture/`, `docs/specs/`가 관리한다. -- route 결정은 `docs/operations/request-routing-policy.md`, Socratic 질문 contract와 approval gate는 `docs/operations/sdd-spec-policy.md`, runtime order와 artifact rule은 `docs/operations/workflow-governance.md`가 각각 canonical owner다. -- skill은 그 정책을 대체하지 않고, 한 단계의 반복 실행 절차와 handoff만 다룬다. -- policy와 skill이 충돌하면 skill에 새 규칙을 더하지 말고 canonical policy를 먼저 수정한 뒤 skill을 맞춘다. - -## Thin-Layer Rule - -- workflow-local skill은 하나의 harness stage, 필요한 handoff, 다음에 읽을 canonical source만 설명한다. -- workflow-local skill은 route 정의, approval gate, state machine, question catalog를 policy 문서처럼 길게 다시 쓰지 않는다. -- workflow-local skill은 backend/frontend file map, repo-local command inventory, bootstrap guide, implementation heuristic의 stable home이 되면 안 된다. -- thin-layer migration은 workflow skill에서 repo-specific detail을 걷어내도 target repo entry doc과 코드/테스트만으로 onboarding이 유지될 때 완료로 본다. - -## Registry Status +- 프로젝트 헌법은 `AGENTS.md`가 가진다. +- artifact model, runtime, hook rule, runbook은 `docs/`가 가진다. +- current task state와 evidence는 `workflows/tasks//`가 가진다. +- 전역 guard 정책은 `workflows/system/hooks.json`이 가진다. +- 내부 런타임 구현은 `scripts/workflow_runtime/`이 가진다. +- skill은 위 규칙을 복제하지 않고, 한 단계의 handoff와 CLI 진입점만 안내한다. -- `skill-creator`: 새 skill 생성, 기존 skill 리팩터링 -- `request-intake`: 새 요청을 `대화`, `모호한 요청`, `즉시 실행 가능한 작업`으로 분류하고 spec authoring stage로 handoff -- `ambiguity-interview`: `모호한 요청`을 single spec 후보, `Blocked`, `Rejected`로 수렴 -- `socratic-spec-authoring`: 소크라테스 질문으로 spec을 세부화하고 질문 루프 종료 뒤 사용자 명시적 승인을 거쳐 승인 상태까지 고정 -- `context-pack-selection`: approved spec 뒤에 primary context pack과 required docs를 고정 -- `boundary-check`: 구현 전에 read/write/network/escalation 경계를 다시 확인 -- `parallel-work-split`: 여러 agent를 투입하기 전에 ownership과 disjoint write set을 고정 -- `api-contract-sync`: backend API 계약 변화가 client consumer와 workflow evidence에 미치는 영향을 맞춤 -- `verification-contract-runner`: selected verification contract profile을 실행하고 latest verification evidence를 남김 -- `repair-loop-triage`: verification 실패나 review 수리 요청 뒤 rerun, `Blocked`, split 중 하나를 정함 -- `reviewer-handoff`: review가 실제로 필요할 때 reviewer minimum context를 넘김 -- `publish-after-review`: legacy 이름이지만 current policy에서는 latest verification 뒤 open PR을 publish -- `guardrail-ledger-update`: feedback close-out에서 guardrail ledger entry 작성 -- `failure-to-policy`: normalized failure를 가장 작은 guardrail asset으로 연결 -- `quality-sweep-triage`: quality sweep signal을 cleanup candidate, guardrail follow-up, repair-now, no-action으로 분류 +## Active Skill Set -## Recommended Use +- `request-intake` +- `socratic-spec-authoring` +- `phase-planner` +- `boundary-check` +- `phase-executor` +- `verification-runner` +- `review-closeout` +- `repair-reopen` -새 요청을 처음 받을 때의 기본 순서는 아래와 같다. +## Recommended Order 1. `request-intake` -2. `ambiguity-interview` if route is `모호한 요청` -3. `socratic-spec-authoring` -4. `context-pack-selection` -5. `boundary-check` -6. `parallel-work-split` if more than one agent will work on the same spec - -`request-intake`는 route와 handoff까지만 담당한다. 현재 spec 초안에 대한 승인 요청과 `Approved` 고정은 `socratic-spec-authoring` 단계에서, 질문 루프가 닫힌 뒤 사용자 명시적 동의를 받아야만 가능하다. - -구현 뒤 close-out 순서는 아래 hook을 따른다. - -1. `verification-contract-runner` -2. `publish-after-review` once latest verification evidence is `passed` -3. `reviewer-handoff` if independent review is required after publish -4. `repair-loop-triage` if verification failed or review requested changes -5. `guardrail-ledger-update` once feedback close-out is triggered -6. `failure-to-policy` when feedback close-out must choose a guardrail promotion target -7. `quality-sweep-triage` when periodic or targeted quality scan is in scope - -`api-contract-sync`의 canonical backend contract는 `git-ranker/docs/openapi/openapi.json`이다. workflow는 canonical spec을 복제해 소유하지 않고, sync 절차와 evidence를 관리한다. +2. `socratic-spec-authoring` +3. `phase-planner` +4. `boundary-check` +5. `phase-executor` +6. `verification-runner` +7. `review-closeout` +8. `repair-reopen` + +새 workflow에서는 markdown spec과 JSON state를 분리한다. `spec.md`는 사람용 요구사항 초안이고, `approve`가 이를 `task.json.intake`로 잠근다. `task.json`/`phases.json`/`runs/*.json`은 자동화용 canonical source다. diff --git a/.codex/skills/ambiguity-interview/SKILL.md b/.codex/skills/ambiguity-interview/SKILL.md deleted file mode 100644 index 19c35dc..0000000 --- a/.codex/skills/ambiguity-interview/SKILL.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -name: ambiguity-interview -description: "`request-intake` 결과가 `모호한 요청`일 때, 남아 있는 blocker를 줄여 single executable spec 후보, `Blocked`, 또는 `Rejected`로 수렴시킨다." ---- - -# Ambiguity Interview - -이 skill은 질문을 늘리는 용도가 아니라 질문을 줄이는 용도다. 목표는 `모호한 요청`을 single executable spec 후보로 줄이는 것이다. - -## 언제 사용하나 - -- `request-intake`가 `모호한 요청`으로 끝났다. -- source of truth를 읽고도 single requirement나 primary repo가 잠기지 않는다. - -## 먼저 확인할 것 - -- 최신 사용자 요청 -- `docs/operations/request-routing-policy.md` -- 관련 product 문서와 저장소 entry 문서 - -## 작업 방식 - -1. 남아 있는 ambiguity signal을 짧게 적는다. -2. 그중 하나를 가장 많이 줄일 수 있는 blocker 질문 한 개를 고른다. -3. 질문에는 왜 지금 이 답이 필요한지 짧게 붙인다. -4. 질문을 고를 때는 single requirement, primary repo, expected outcome, 숨은 다중 목표 여부 중 무엇을 잠그는지 먼저 분명히 한다. -5. spec authoring 단계에서 닫아도 되는 세부사항, 예를 들면 write scope, verification, acceptance criteria는 여기서 잠그지 않는다. -6. 답을 받은 뒤에는 무엇이 잠겼고 무엇이 아직 안 잠겼는지 짧게 요약한다. -7. single requirement와 primary repo가 잠기면 route summary만 남기고 `socratic-spec-authoring`으로 넘긴다. -8. 더 줄일 수 없으면 `Blocked` 또는 `Rejected` reason을 정리한다. - -## 결과 - -- unlocked single requirement와 primary repo, 또는 `Blocked`/`Rejected` reason -- 무엇이 잠겼는지와 남은 spec-level 질문 후보 -- next stage 또는 stop signal - -## 피해야 할 것 - -- 한 번에 여러 blocker 질문을 쏟아내는 것 -- spec authoring 단계에서 닫아야 할 write scope, verification, approval gate를 ambiguity 단계에서 미리 확정하려는 것 -- 답을 받은 뒤에도 무엇이 잠겼는지 요약 없이 바로 다음 질문으로 넘어가는 것 diff --git a/.codex/skills/api-contract-sync/SKILL.md b/.codex/skills/api-contract-sync/SKILL.md deleted file mode 100644 index 6bc491f..0000000 --- a/.codex/skills/api-contract-sync/SKILL.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -name: api-contract-sync -description: backend API 계약 변경을 canonical spec, client consumer, workflow 문서와 같은 기준으로 맞춘다. `git-ranker/docs/openapi/openapi.json` 변화가 client 타입, validation, workflow evidence에 영향을 주는지 함께 정리해야 할 때 이 skill을 사용한다. ---- - -# API Contract Sync - -목표는 drift를 같은 순서로 줄이는 것이다. - -## 언제 사용하나 - -- approved spec 또는 명시적 contract drift review 범위가 이미 잠겨 있다. -- `git-ranker/docs/openapi/openapi.json`이 바뀌었다. -- `/api/v1/**` request, response, enum, auth, error envelope가 바뀌었다. -- client 타입이나 validation에서 backend와 drift가 의심된다. -- workflow 문서나 evidence가 현재 계약과 맞지 않을 가능성이 있다. - -## 먼저 확인할 것 - -- canonical backend contract: `git-ranker/docs/openapi/openapi.json` -- 관련 client consumer -- 관련 workflow 문서와 approved spec - -## 작업 방식 - -1. canonical spec에서 바뀐 endpoint, schema, enum을 확인한다. -2. 영향을 받는 client consumer를 찾는다. -3. workflow 문서나 evidence surface가 영향을 받는지 확인한다. -4. 필요한 변경만 반영한다. -5. contract sync evidence를 spec이나 PR에 남긴다. - -## 피해야 할 것 - -- request route를 분류하거나 spec approval을 대신하려는 것 -- approved spec 없이 contract drift만 보고 구현 범위를 새로 발명하는 것 -- backend contract를 workflow 문서가 canonical source인 것처럼 다루는 것 diff --git a/.codex/skills/boundary-check/SKILL.md b/.codex/skills/boundary-check/SKILL.md index afefb13..a80d4f6 100644 --- a/.codex/skills/boundary-check/SKILL.md +++ b/.codex/skills/boundary-check/SKILL.md @@ -1,37 +1,31 @@ --- name: boundary-check -description: context pack selection 뒤에 task type별 read/write/network/escalation 경계와 approved spec의 write scope completeness를 다시 잠가야 할 때 사용한다. +description: active phase의 allowed_write_paths 경계를 먼저 확인하고 범위를 벗어나면 phase를 수정하거나 재계획한다. --- # Boundary Check -이 skill의 목적은 더 넓은 접근을 정당화하는 것이 아니라, 현재 subtask가 이미 허용한 경계 안에서만 움직이도록 다시 확인하는 것이다. - ## 언제 사용하나 -- primary context pack이 정해졌고 구현 직전이다. -- approved spec에 write scope와 verification contract profile이 적혀 있다. +- 구현 전에 수정 대상 파일이 현재 phase 경계 안에 들어가는지 확인해야 한다. ## 먼저 확인할 것 -- approved spec -- `docs/operations/tool-boundary-matrix.md` -- `docs/operations/workflow-governance.md` -- 필요하면 `docs/operations/verification-contract-registry.md` +- `AGENTS.md` +- `docs/artifact-model.md` +- `docs/hooks.md` +- `workflows/tasks//task.json` +- `workflows/tasks//phases.json` +- `workflows/system/hooks.json` ## 작업 방식 -1. primary repo와 active subtask가 여전히 하나인지 확인한다. -2. 현재 읽으려는 문서와 파일이 selected context pack과 spec named input 안에 있는지 점검한다. -3. allowed write paths, control-plane artifacts, explicitly forbidden path가 spec에 모두 적혀 있는지 확인한다. -4. network와 escalation trigger를 최소 범위로 좁힌다. +1. active phase의 `allowed_write_paths`를 읽는다. +2. 수정하려는 파일 목록이 범위 안에 있는지 먼저 대조한다. +3. 범위를 벗어나면 임의로 수정하지 말고 `phases.json`을 다시 계획하거나 승인 범위를 갱신한다. +4. phase를 닫을 때는 `python3 scripts/workflow.py run --complete --changed-path ...`로 write scope를 다시 검증한다. ## 결과 -- task type -- read boundary -- write boundary -- control-plane artifact -- explicitly forbidden path -- network 필요 여부 -- escalation trigger +- 범위 안이면 구현 진행 +- 범위 밖이면 phase 재계획 또는 승인 범위 수정 diff --git a/.codex/skills/context-pack-selection/SKILL.md b/.codex/skills/context-pack-selection/SKILL.md deleted file mode 100644 index 9cabd04..0000000 --- a/.codex/skills/context-pack-selection/SKILL.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -name: context-pack-selection -description: approved spec 뒤에 task type별 primary context pack을 고르고, required docs와 optional trigger와 forbidden context를 잠가야 할 때 사용한다. ---- - -# Context Pack Selection - -이 skill의 목적은 더 많은 문서를 읽는 것이 아니라, 지금 subtask에 필요한 최소 컨텍스트만 고정하는 것이다. - -## 언제 사용하나 - -- approved spec이 준비됐고 구현 전에 primary context pack을 정해야 한다. -- task type은 이미 `workflow 문서 수정`, `backend 수정`, `frontend 수정`, `cross-repo planning` 중 하나로 좁혀졌다. - -## 먼저 확인할 것 - -- approved spec -- `AGENTS.md` -- `docs/README.md` -- `SPECS.md` -- `docs/operations/workflow-governance.md` -- `docs/architecture/context-pack-registry.md` - -## 작업 방식 - -1. spec에서 primary repo와 active subtask가 하나로 잠겼는지 확인한다. -2. registry의 mapping으로 primary context pack 하나를 고른다. -3. common base context와 선택한 pack의 required docs만 먼저 연다. -4. optional docs는 trigger와 함께 적고, forbidden context는 eager load 금지 surface로 분리한다. - -## 결과 - -- primary repo -- active subtask -- primary context pack -- required docs -- optional docs trigger -- forbidden context diff --git a/.codex/skills/failure-to-policy/SKILL.md b/.codex/skills/failure-to-policy/SKILL.md deleted file mode 100644 index 9a7ea5c..0000000 --- a/.codex/skills/failure-to-policy/SKILL.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -name: failure-to-policy -description: normalized root cause를 가장 작은 guardrail asset으로 연결한다. `docs-rule`, `skill`, `test`, `ci`, `template`, `no-new-guardrail` 중 무엇으로 승격할지와 그 이유를 결정해야 할 때 이 skill을 사용한다. ---- - -# Failure To Policy - -이 skill은 같은 실패를 가장 이른 차단 지점에서 끊는 가장 작은 자산을 고르는 데 목적이 있다. - -## 언제 사용하나 - -- guardrail ledger entry 초안은 있는데 `Promotion decision`과 `Guardrail status`가 비어 있다. -- 같은 root cause를 어떤 자산으로 승격할지 판단해야 한다. - -## 먼저 확인할 것 - -- normalized root cause와 trigger signal -- `docs/operations/failure-to-guardrail-feedback-loop.md` -- current issue disposition -- recurrence 근거 -- existing guardrail 또는 `없음` - -## 결과 - -- failure class -- existing guardrail -- recurrence -- selected promotion decision -- selected guardrail status -- decision rationale -- guardrail change or follow-up asset -- owner / next action diff --git a/.codex/skills/guardrail-ledger-update/SKILL.md b/.codex/skills/guardrail-ledger-update/SKILL.md deleted file mode 100644 index e4833bb..0000000 --- a/.codex/skills/guardrail-ledger-update/SKILL.md +++ /dev/null @@ -1,40 +0,0 @@ ---- -name: guardrail-ledger-update -description: verification과 review 결과가 고정된 뒤 guardrail ledger entry를 작성한다. normalized root cause, evidence, promotion decision 입력을 한 entry로 정리하고 feedback close-out을 닫아야 할 때 이 skill을 사용한다. ---- - -# Guardrail Ledger Update - -목표는 feedback section을 길게 쓰는 것이 아니라, root cause 하나당 하나의 canonical entry를 정확히 남기는 것이다. - -## 언제 사용하나 - -- latest verification 결과가 `passed`이거나 `blocked` 이유가 고정됐다. -- latest review verdict가 고정됐거나 review 불가 사유가 정리됐다. -- spec close-out이나 follow-up artifact에 guardrail ledger entry를 남겨야 한다. - -## 먼저 확인할 것 - -- approved spec 또는 close-out artifact 경로 -- `docs/operations/failure-to-guardrail-feedback-loop.md` -- `docs/operations/guardrail-ledger-template.md` -- latest verification evidence 또는 `Blocked` 이유 -- latest review verdict 또는 review 불가 사유 - -## 작업 방식 - -1. trigger signal과 root cause를 구분한다. -2. root cause 하나당 entry 하나만 잡는다. -3. template field를 빠짐없이 채운다. -4. promotion decision이 비어 있으면 `failure-to-policy`로 먼저 넘긴다. - -## 결과 - -- date -- task / spec -- stage -- failure class -- trigger signal -- root cause -- promotion decision -- owner / next action diff --git a/.codex/skills/guardrail-ledger-update/references/feedback-closeout-minimum.md b/.codex/skills/guardrail-ledger-update/references/feedback-closeout-minimum.md deleted file mode 100644 index a9f71f4..0000000 --- a/.codex/skills/guardrail-ledger-update/references/feedback-closeout-minimum.md +++ /dev/null @@ -1,23 +0,0 @@ -# Feedback Close-Out Minimum - -feedback close-out 전에 아래를 확인한다. - -## Input Ready - -- [ ] approved spec 또는 close-out artifact 경로가 고정돼 있다. -- [ ] latest verification evidence 또는 `Blocked` 이유가 있다. -- [ ] latest review verdict 또는 review 불가 사유가 있다. -- [ ] root cause를 한 문장으로 적을 수 있다. - -## Entry Quality - -- [ ] `Date`, `Task / Spec`, `Existing guardrail` 또는 `없음`을 먼저 적었다. -- [ ] root cause 하나당 entry 하나만 쓴다. -- [ ] `Trigger signal`과 `Root cause`를 구분했다. -- [ ] `Promotion decision`과 `Guardrail status`를 둘 다 채웠다. -- [ ] `Owner / next action`과 `Evidence`를 비우지 않았다. - -## Close-Out Hook - -- [ ] follow-up asset, issue, 또는 없음 사유를 적었다. -- [ ] spec close-out 또는 review artifact에 feedback 판단 요약을 남길 위치를 정했다. diff --git a/.codex/skills/parallel-work-split/SKILL.md b/.codex/skills/parallel-work-split/SKILL.md deleted file mode 100644 index e910f5b..0000000 --- a/.codex/skills/parallel-work-split/SKILL.md +++ /dev/null @@ -1,35 +0,0 @@ ---- -name: parallel-work-split -description: 같은 spec에 둘 이상의 agent를 투입하기 전에 ownership, write set, merge 순서를 고정한다. shared file 충돌이나 cross-repo drift 위험이 있어 병렬 작업을 안전하게 나눠야 할 때 이 skill을 사용한다. ---- - -# Parallel Work Split - -병렬화의 목적은 속도보다 충돌 제거다. - -## 언제 사용하나 - -- 한 spec에 둘 이상의 agent를 넣으려 한다. -- 작업이 여러 저장소, 여러 디렉터리, 여러 책임 영역으로 갈라진다. - -## 먼저 확인할 것 - -- approved spec -- active subtask와 acceptance criteria -- 대략적인 파일 집합 - -## 작업 방식 - -1. critical path와 side task를 먼저 나눈다. -2. agent별 disjoint write set을 만든다. -3. shared file이 꼭 필요하면 owner를 한 명만 둔다. -4. 저장소가 다르면 저장소별 branch/worktree를 분리한다. - -## 결과 - -- owner -- 대상 저장소 -- 목표 -- write set -- read-only dependency -- handoff 조건 diff --git a/.codex/skills/phase-executor/SKILL.md b/.codex/skills/phase-executor/SKILL.md new file mode 100644 index 0000000..0338118 --- /dev/null +++ b/.codex/skills/phase-executor/SKILL.md @@ -0,0 +1,31 @@ +--- +name: phase-executor +description: 현재 active phase 하나만 실행하고 hook을 통과시켜 phase state를 전이한다. +--- + +# Phase Executor + +## 언제 사용하나 + +- `phases.json`이 준비됐고 현재 active phase를 실행해야 한다. + +## 먼저 확인할 것 + +- `AGENTS.md` +- `docs/runtime.md` +- `docs/hooks.md` +- `workflows/tasks//task.json` +- `workflows/tasks//phases.json` + +## 작업 방식 + +1. `python3 scripts/workflow.py run --start`로 phase를 시작한다. +2. phase의 허용 write path 안에서만 구현한다. +3. phase를 닫을 때는 `python3 scripts/workflow.py run --complete --changed-path ...`를 사용한다. +4. TDD Guard가 막히면 테스트를 추가하거나 `test_policy.mode=evidence_only`와 `test_policy.evidence`를 먼저 보강한다. + +## 결과 + +- phase run evidence +- updated `task.json` / `phases.json` +- 다음 stage: `verification-runner` diff --git a/.codex/skills/phase-planner/SKILL.md b/.codex/skills/phase-planner/SKILL.md new file mode 100644 index 0000000..943a715 --- /dev/null +++ b/.codex/skills/phase-planner/SKILL.md @@ -0,0 +1,31 @@ +--- +name: phase-planner +description: 승인된 task를 executable phase 집합으로 쪼개고 `phases.json`을 canonical 계획으로 고정한다. +--- + +# Phase Planner + +## 언제 사용하나 + +- `task.json`이 `approved` 상태고 `task.json.intake`가 현재 `spec.md`와 동기화되어 있다. + +## 먼저 확인할 것 + +- `AGENTS.md` +- `docs/artifact-model.md` +- `docs/runtime.md` +- `workflows/tasks//spec.md` +- `workflows/tasks//task.json` + +## 작업 방식 + +1. task를 작은 phase들로 쪼갠다. +2. 각 phase에 `allowed_write_paths`, `acceptance.commands`, `test_policy`를 채운 JSON을 작성한다. +3. 추가 요구사항으로 spec을 바꿨다면 `approve`를 다시 실행해 intake를 재잠근 뒤 `python3 scripts/workflow.py plan --from ` 또는 `--stdin`을 실행한다. +4. `phases.json`은 오직 `plan` 명령만 canonical write owner다. + +## 결과 + +- `workflows/tasks//phases.json` +- `task.json.active_phase_id` +- 다음 stage: `phase-executor` diff --git a/.codex/skills/publish-after-review/SKILL.md b/.codex/skills/publish-after-review/SKILL.md deleted file mode 100644 index 5865e88..0000000 --- a/.codex/skills/publish-after-review/SKILL.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -name: publish-after-review -description: legacy 이름이지만 current policy에서는 latest verification evidence 뒤 open PR을 publish하는 단계에 사용한다. review는 open PR 이후 필요할 때 수행한다. ---- - -# Publish After Review - -이 skill의 목적은 latest verification evidence를 끝낸 결과를 open PR로 빠르게 공개하는 것이다. - -## 언제 사용하나 - -- latest verification report 또는 summary가 최신이다. -- 이제 PR body를 만들고 publish해야 한다. - -## 먼저 확인할 것 - -- approved spec -- latest verification evidence -- `.github/PULL_REQUEST_TEMPLATE.md` -- `docs/operations/workflow-governance.md` - -## 작업 방식 - -1. latest verification evidence가 current diff와 일치하는지 다시 확인한다. -2. PR body는 reader-first로 작성하고 detailed evidence는 spec이나 linked artifact에 둔다. -3. 사용자가 draft를 명시적으로 요청하지 않았다면 open PR을 생성한다. -4. 생성 직후 body render를 확인한다. - -## 결과 - -- latest verification status -- open PR 또는 draft PR -- PR link diff --git a/.codex/skills/quality-sweep-triage/SKILL.md b/.codex/skills/quality-sweep-triage/SKILL.md deleted file mode 100644 index 8ceb780..0000000 --- a/.codex/skills/quality-sweep-triage/SKILL.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -name: quality-sweep-triage -description: periodic 또는 targeted quality sweep에서 나온 signal을 `repair-now`, `cleanup-pr-candidate`, `guardrail-follow-up`, `no-action` 중 하나로 분류한다. ---- - -# Quality Sweep Triage - -이 skill은 bounded quality drift를 evidence와 next action까지 붙은 후보로 바꾼다. - -## 언제 사용하나 - -- `post-closeout` quality sweep을 해야 한다. -- `scheduled` sweep 결과가 들어왔다. -- reviewer note나 반복 finding 때문에 `targeted` sweep을 해야 한다. - -## 먼저 확인할 것 - -- `docs/operations/continuous-quality-feedback-loop.md` -- `docs/operations/quality-sweep-report-template.md` -- source task, PR, baseline, reviewer note, detector output -- 대상 저장소와 bounded scan scope - -## 결과 - -- trigger mode -- source repo와 source task/PR/baseline -- scan scope -- detection surface -- signal class -- selected disposition -- follow-up asset / issue / PR -- owner / next action -- 핵심 evidence diff --git a/.codex/skills/repair-loop-triage/SKILL.md b/.codex/skills/repair-loop-triage/SKILL.md deleted file mode 100644 index e44e813..0000000 --- a/.codex/skills/repair-loop-triage/SKILL.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -name: repair-loop-triage -description: verification 실패나 review `changes-requested` 이후, 현재 spec 안에서 수리할지 `Blocked`로 전환할지, follow-up으로 쪼갤지를 정한다. ---- - -# Repair Loop Triage - -이 skill은 실패를 오래 끄는 대신, root cause 하나에 맞는 다음 행동만 좁히는 데 초점을 둔다. - -## 언제 사용하나 - -- latest verification report에 `failed` command가 있다. -- required evidence가 빠졌거나 stale report가 지적됐다. -- reviewer가 `changes-requested`와 rerun을 요구했다. - -## 먼저 확인할 것 - -- approved spec -- latest verification report -- `docs/operations/verification-contract-registry.md` -- review finding이 있다면 `docs/operations/dual-agent-review-policy.md` - -## 결과 - -- trigger -- 현재 결정: repair, `Blocked`, follow-up split -- root cause 요약 -- rerun 대상 command 또는 stop signal -- next owner diff --git a/.codex/skills/repair-reopen/SKILL.md b/.codex/skills/repair-reopen/SKILL.md new file mode 100644 index 0000000..9e3f39d --- /dev/null +++ b/.codex/skills/repair-reopen/SKILL.md @@ -0,0 +1,34 @@ +--- +name: repair-reopen +description: failed, blocked, review follow-up, 추가 요구사항이 들어온 task를 reopen해서 repair loop를 재개한다. +--- + +# Repair Reopen + +## 언제 사용하나 + +- task가 `failed`, `blocked`, `review_ready`, `completed` 상태인데 다시 작업을 이어가야 한다. + +## 먼저 확인할 것 + +- `AGENTS.md` +- `docs/runtime.md` +- `workflows/tasks//task.json` +- `workflows/tasks//phases.json` +- 가장 최근 `runs/*.json` + +## 작업 방식 + +1. 왜 reopen이 필요한지 note를 한 줄로 잠근다. +2. 필요하면 target phase를 정한다. +3. `python3 scripts/workflow.py reopen --note "..." [--phase-id ...]`로 task를 `approved` 상태로 되돌린다. +4. 추가 요구사항이면 먼저 `spec.md`를 다시 잠근다. +5. spec이 바뀌었으면 `python3 scripts/workflow.py approve --note "..."`로 `task.json.intake`를 다시 잠근다. +6. 필요하면 `plan`으로 phase를 다시 적재한다. +7. 이후 구현 재시작 중 필요한 흐름으로 이어간다. + +## 결과 + +- `state=approved`인 `task.json` +- cleared `blocked_reason` +- new reopen run evidence diff --git a/.codex/skills/request-intake/SKILL.md b/.codex/skills/request-intake/SKILL.md index b03de67..72f942a 100644 --- a/.codex/skills/request-intake/SKILL.md +++ b/.codex/skills/request-intake/SKILL.md @@ -1,42 +1,30 @@ --- name: request-intake -description: 새 요청을 `대화`, `모호한 요청`, `즉시 실행 가능한 작업` 중 하나로 분류한다. 실행 의도가 보이면 구현 전에 spec authoring으로 갈 수 있는지 먼저 가려야 할 때 사용한다. +description: 새 요청을 실행 가능한 single task로 줄이고 `workflows/tasks//` 생성으로 handoff한다. --- # Request Intake -새 요청을 받으면 가장 먼저 route를 잠근다. 이 skill은 route를 분류하고 다음 stage로 handoff하는 데만 쓴다. - ## 언제 사용하나 -- 새 사용자 요청이 들어왔다. -- 아직 spec, issue, PR, 파일 편집을 시작하지 않았다. +- 새 작업 요청을 받았고 아직 task artifact가 없다. ## 먼저 확인할 것 -- 최신 사용자 요청 -- `docs/operations/request-routing-policy.md` -- `docs/operations/workflow-governance.md` +- `AGENTS.md` +- `docs/README.md` +- `docs/runtime.md` ## 작업 방식 -1. `request-routing-policy.md` 기준으로 route만 결정한다. -2. `즉시 실행 가능한 작업`이면 problem, primary repo candidate, expected outcome을 한 줄씩 요약해 `socratic-spec-authoring`으로 넘긴다. -3. 이 단계는 spec approval을 받는 단계가 아니다. 사용자의 초기 작업 요청을 spec 승인으로 해석하지 않는다. -4. `모호한 요청`이면 남아 있는 ambiguity signal과 첫 blocker 하나만 적어 `ambiguity-interview`로 넘긴다. -5. `대화`면 사용자 질문에 대한 답변을 제공하고 종료한다. -6. `Rejected`면 close-out reason만 남기고 종료한다. +1. 요청을 하나의 task로 줄인다. +2. primary repo와 완료 조건을 한 줄씩 잠근다. +3. `python3 scripts/workflow.py new --title ... --primary-repo ...`로 task skeleton을 만든다. +4. 생성 위치는 항상 `workflows/tasks//` 하나다. draft 단계에서는 `spec.md`만 채우고 `task.json.intake`는 `approve`가 잠근다. +5. 구현으로 바로 가지 말고 `socratic-spec-authoring`으로 넘긴다. ## 결과 -- route -- route reason -- next stage 또는 close-out reason - -`대화` route에서는 close-out reason 대신 실제 답변이 결과여야 한다. - -## 피해야 할 것 - -- spec-level 질문, approval gate, write scope를 여기서 잠그려는 것 -- 사용자의 작업 요청을 spec approval로 오인하는 것 -- route를 정하지 않은 채 바로 구현이나 spec 편집으로 들어가는 것 +- `workflows/tasks//spec.md` +- `workflows/tasks//task.json` +- 다음 stage: `socratic-spec-authoring` diff --git a/.codex/skills/review-closeout/SKILL.md b/.codex/skills/review-closeout/SKILL.md new file mode 100644 index 0000000..f14ed51 --- /dev/null +++ b/.codex/skills/review-closeout/SKILL.md @@ -0,0 +1,30 @@ +--- +name: review-closeout +description: latest passed verification을 기준으로 review readiness와 user validation closeout을 남긴다. +--- + +# Review Closeout + +## 언제 사용하나 + +- verification이 통과했고 review/user validation을 닫아야 한다. + +## 먼저 확인할 것 + +- `docs/runtime.md` +- `workflows/tasks//task.json` +- `workflows/tasks//phases.json` +- `workflows/tasks//runs/*.json` + +## 작업 방식 + +1. 모든 phase가 `completed`인지 확인한다. +2. `python3 scripts/workflow.py review --note ...`로 review readiness를 기록한다. +3. 사용자 검증이 끝나면 `python3 scripts/workflow.py review --close --user-validation-note ...`로 완료를 닫는다. +4. explicit user validation 없이 `completed`로 전이하지 않는다. + +## 결과 + +- review run evidence +- final completion evidence +- `state=completed`인 `task.json` diff --git a/.codex/skills/reviewer-handoff/SKILL.md b/.codex/skills/reviewer-handoff/SKILL.md deleted file mode 100644 index 4fd90b3..0000000 --- a/.codex/skills/reviewer-handoff/SKILL.md +++ /dev/null @@ -1,37 +0,0 @@ ---- -name: reviewer-handoff -description: review가 실제로 필요할 때 latest verification evidence를 바탕으로 reviewer minimum context를 묶고, open PR 기준 review evidence를 남긴다. ---- - -# Reviewer Handoff - -reviewer가 더 많은 문서를 읽게 만드는 것이 목적이 아니다. - -## 언제 사용하나 - -- latest verification report 또는 summary의 overall status가 `passed`다. -- independent review를 시작해야 한다. -- open PR이 이미 생성됐다. - -## 먼저 확인할 것 - -- approved spec -- latest verification evidence -- touched diff summary -- source-of-truth update 목록 또는 업데이트 불필요 사유 -- remaining risk, skipped checks, follow-up 필요 사항 -- `docs/operations/dual-agent-review-policy.md` - -## 작업 방식 - -1. reviewer minimum context를 한 번에 묶는다. -2. reviewer가 한 명이면 그대로 넘기고, high-risk surface일 때만 role별 focus를 추가한다. -3. implementer와 reviewer 세션을 분리한 채 review를 시작한다. -4. finding과 verdict를 모은다. - -## 결과 - -- reviewer 이름 -- reviewer input -- review verdict -- finding 또는 no-blocking note diff --git a/.codex/skills/skill-creator/SKILL.md b/.codex/skills/skill-creator/SKILL.md deleted file mode 100644 index abd3d9b..0000000 --- a/.codex/skills/skill-creator/SKILL.md +++ /dev/null @@ -1,242 +0,0 @@ ---- -name: skill-creator -description: 이 저장소의 project-local skill을 새로 만들거나 기존 skill을 Claude/Anthropic 모범 사례에 맞게 다시 쓸 때 사용한다. 새 skill의 trigger description, instruction 구조, bundled resource 구성을 설계해야 하거나 기존 skill을 전면 리팩터링해야 할 때 이 skill을 먼저 사용한다. ---- - -# Skill Creator - -새 skill을 만들고 기존 skill을 개선하는 방법을 정리한다. - -이 skill의 목적은 문서를 하나 더 추가하는 것이 아니다. 모델이 필요한 순간에 skill을 잘 발견하고, SKILL.md를 과하게 읽지 않고, 실제 사용자 프롬프트에서 더 일관되게 동작하도록 skill bundle을 설계하는 것이다. - -## 언제 사용하나 - -다음 상황에서 이 skill을 사용한다. - -- 새 skill을 처음 만들 때 -- 기존 skill을 Claude/Anthropic 모범 사례 기준으로 다시 쓸 때 -- `description`이 약해서 skill trigger 품질이 낮을 때 -- 메모 수준 규칙을 reusable skill로 올릴 때 -- bundled resource가 필요한지, 필요 없다면 왜 없는지가 설계 이슈일 때 - -사소한 오탈자 수정처럼 구조와 trigger 판단이 전혀 바뀌지 않는 경우에는 이 skill이 과하다. - -## 좋은 skill의 기준 - -좋은 skill은 아래 조건을 만족한다. - -- 무엇을 하는지와 언제 써야 하는지가 `description`에서 바로 드러난다. -- SKILL.md 본문은 핵심 workflow와 판단 기준만 남기고 불필요하게 길지 않다. -- 상세 문서, 스크립트, 템플릿은 필요할 때만 따로 둔다. -- 실제 사용자 프롬프트와 비슷한 예시로 빠르게 검토할 수 있다. - -## 핵심 원칙 - -### 1. 간결함이 핵심이다 - -모델은 이미 많은 일반론을 알고 있다. SKILL.md에는 이 skill에서만 필요한 판단 기준과 반복 workflow만 남긴다. - -줄여야 하는 것: - -- 교과서적인 설명 -- canonical doc를 길게 다시 적은 문단 -- 결과를 바꾸지 않는 긴 예시 - -남겨야 하는 것: - -- trigger에 직접 영향을 주는 표현 -- 실패를 줄이는 workflow -- resource를 언제 읽고 언제 안 읽는지에 대한 기준 - -### 2. `description`은 trigger surface다 - -`description`은 단순 요약이 아니라 "이 skill을 언제 써야 하는지"를 모델에게 알려주는 첫 번째 표면이다. - -약한 예: - -```yaml -description: 문서 작업을 도와준다. -``` - -강한 예: - -```yaml -description: 이 저장소의 project-local skill을 새로 만들거나 기존 skill을 Claude/Anthropic 모범 사례에 맞게 다시 쓸 때 사용한다. 새 skill의 trigger description, instruction 구조, bundled resource 구성을 설계해야 하거나 기존 skill을 전면 리팩터링해야 할 때 이 skill을 먼저 사용한다. -``` - -좋은 `description`은 아래를 함께 담는다. - -1. 무엇을 하는지 -2. 언제 트리거되어야 하는지 -3. 사용자가 실제로 말할 법한 인접 표현 - -이 저장소처럼 본문이 한국어인 pack에서는 `description`도 한국어로 맞추는 편이 낫다. 다만 말투는 1인칭 도움말보다 중립적인 동작 설명에 가깝게 쓴다. - -### 3. 자유도를 맞게 둔다 - -모든 skill을 같은 강도로 통제할 필요는 없다. - -- 높은 자유도: 여러 접근이 가능하고 heuristic이 중요한 작업 -- 중간 자유도: 선호하는 패턴은 있지만 출력이 상황에 따라 달라지는 작업 -- 낮은 자유도: 순서가 중요하거나 반복 구현 비용이 큰 작업 - -절벽 사이의 좁은 다리라면 가드레일을 강하게 두고, 열린 들판이라면 모델이 판단할 여지를 남긴다. - -### 4. Progressive disclosure를 사용한다 - -SKILL.md는 entrypoint다. 모든 세부사항을 한 파일에 몰아넣지 않는다. - -- `SKILL.md`: trigger, 핵심 workflow, 판단 기준 -- `references/`: 길고 상세한 참고 문서 -- `scripts/`: 반복되는 결정론적 작업 -- `assets/`: 템플릿, boilerplate, 재사용 파일 - -추가 자산은 "있으면 좋아 보인다"가 아니라 "반복 사용에서 실제로 이득이 있다"는 근거가 있을 때만 만든다. - -### 5. 실제 프롬프트로 검토한다 - -초안만으로 끝내지 않는다. 실제 사용자 요청과 비슷한 2~3개의 프롬프트를 떠올려 아래를 확인한다. - -- 이 `description`이 실제로 trigger될 것 같은가 -- 본문이 너무 막연하거나 너무 빡빡하지 않은가 -- bundled resource가 진짜 필요한가 - -### 6. 레거시 resource를 재분류한다 - -기존 skill pack을 리팩터링할 때는 예전 디렉터리 이름을 그대로 유지하지 않는다. 먼저 "이 파일이 실제로 무엇을 위해 존재하는가"를 보고 다시 분류한다. - -- 최종 출력에 복사하거나 수정해서 쓰는 템플릿이면 `assets/` -- 작업 중 읽어야 하는 checklist, schema, 긴 참고 문서면 `references/` -- 반복해서 실행하는 deterministic helper면 `scripts/` -- 실제 반복 가치가 없다면 별도 파일로 두지 말고 SKILL.md에 흡수한다 - -## 작성 순서 - -명확한 이유가 없다면 아래 순서를 따른다. - -### 1. 의도와 사용 예시를 고정한다 - -먼저 아래 질문에 답한다. - -1. 이 skill이 모델에게 무엇을 가능하게 해야 하는가 -2. 사용자는 어떤 말로 이 skill을 필요로 할까 -3. 기대 결과물은 무엇인가 -4. 성공 기준은 무엇인가 -5. 무엇을 비범위로 둘 것인가 - -가능하면 현재 대화와 기존 문서에서 먼저 답을 추출하고, 정말 구조가 달라질 질문만 추가로 묻는다. - -### 2. 관련 자료를 읽는다 - -초안을 쓰기 전에 아래를 확인한다. - -- 이 공간의 canonical source -- 비슷한 로컬 skill 1~2개 -- 사용자가 준 외부 reference - -어떤 정보가 skill 안에 들어가야 하고, 어떤 정보가 바깥 canonical doc에 남아야 하는지 이 단계에서 먼저 정리한다. - -이 저장소 안 문서와 skill 파일에서 링크를 남길 때는 저장소 기준 상대경로를 사용한다. 로컬 도구 응답에서 쓰는 절대경로를 본문에 그대로 넣지 않는다. - -### 3. skill shape를 결정한다 - -가장 작은 유효 bundle을 선택한다. - -질문은 단순하다. - -- SKILL.md 하나로 충분한가 -- 길고 상세한 참고 문서가 필요해서 `references/`가 필요한가 -- 같은 코드를 반복해서 쓸 가능성이 높아 `scripts/`가 필요한가 -- 템플릿이나 boilerplate가 핵심이라 `assets/`가 필요한가 - -지원 자산은 반복 사용에서 비용을 줄일 때만 추가한다. - -### 4. frontmatter를 먼저 쓴다 - -`name`과 `description`부터 쓴다. - -- `name`: 짧고 구체적인 `kebab-case` -- `description`: 무엇을 하는지 + 언제 쓰는지 - -이 단계에서 애매하면 skill 전체가 흐려진다. clever한 이름보다 잘 발견되는 이름을 고른다. - -### 5. SKILL.md 본문을 쓴다 - -좋은 SKILL.md는 보통 아래를 설명한다. - -- 이 skill이 해결하는 일 -- 언제 사용해야 하는지 -- 어떻게 생각해야 하는지 -- 핵심 workflow 또는 iteration loop -- 더 읽을 자료가 있다면 어디를 볼지 -- 어떻게 빠르게 검토할지 - -모든 skill에 동일한 section template를 강제할 필요는 없다. skill의 workflow를 가장 잘 드러내는 구조를 선택한다. - -### 6. 기존 skill을 리팩터링한다 - -기존 skill을 고칠 때는 형식만 다듬지 말고 아래를 본다. - -- `description`이 너무 모호해서 under-trigger되는가 -- canonical doc를 본문에서 과하게 복제하고 있는가 -- 한 파일이 너무 커서 reference로 쪼개는 편이 나은가 -- 이유 설명 없이 MUST만 반복하는가 -- 반복되는 예시나 boilerplate가 asset이나 script로 분리되는 편이 나은가 - -리팩터링의 목표는 더 잘 발견되고, 더 lean하며, 실제 프롬프트에서 더 잘 쓰이게 만드는 것이다. - -pack 전체를 리팩터링할 때는 아래 순서를 권장한다. - -1. 모든 skill의 현재 `description`과 줄 수를 빠르게 훑는다 -2. weak trigger, 과도한 boilerplate, 레거시 resource 경로를 먼저 표시한다 -3. 공통 문제가 보이면 `skill-creator`를 먼저 보완한다 -4. 그 다음 개별 skill을 다시 쓴다 -5. refactor 중 새 패턴이 반복되면 `skill-creator`에 다시 반영한다 - -### 7. 가볍게 검토한다 - -초안 뒤에는 최소한 아래를 다시 본다. - -1. frontmatter를 다시 읽고 `description`을 다듬는다 -2. SKILL.md가 여전히 lean한지 확인한다 -3. representative prompt 2~3개를 기준으로 시뮬레이션한다 -4. 추가한 resource가 정말 필요한지 확인한다 -5. registry가 바뀌었다면 `.codex/skills/README.md`를 갱신한다 - -초안이 아직 raw note처럼 보인다면 끝난 것이 아니다. - -## 언제 SKILL.md 하나로 충분한가 - -아래 조건이 맞으면 SKILL.md 하나로 시작해도 된다. - -- 핵심 가치가 workflow와 판단 기준 자체에 있다 -- 상세 reference 없이도 skill을 실행할 수 있다 -- 반복 실행마다 같은 코드를 다시 쓰게 될 가능성이 낮다 -- 템플릿 파일 없이도 결과 품질이 크게 흔들리지 않는다 - -반대로 아래가 보이면 지원 자산 추가를 검토한다. - -- 긴 참고 문서가 없으면 본문이 비대해진다 -- 같은 스크립트를 반복해서 작성하게 된다 -- 템플릿이나 boilerplate가 결과 품질에 직접 영향을 준다 - -## 안티패턴 - -아래는 피한다. - -- 모호한 `description`을 두고 본문이 보완해주길 기대하는 것 -- 외부 문서를 길게 복사해 넣는 것 -- 모델이 이미 아는 일반론을 장황하게 적는 것 -- 근거 없이 `references/`, `scripts/`, `assets/`를 미리 만드는 것 -- `templates/`, `checklists/`, `queries/` 같은 레거시 디렉터리를 목적 재검토 없이 그대로 유지하는 것 -- 모든 skill을 같은 section 구조에 억지로 맞추는 것 -- 기존 skill을 리팩터링하면서 trigger 품질은 건드리지 않는 것 - -## 산출물 - -이 skill을 제대로 사용했다면 결과는 보통 아래를 포함한다. - -- 새로 만든 또는 개정한 `.codex/skills//SKILL.md` -- 정말 필요한 경우에만 추가한 bundled resource -- registry나 진입점이 바뀌었다면 갱신된 `.codex/skills/README.md` -- 어떤 프롬프트나 점검으로 검토했는지에 대한 짧은 메모 diff --git a/.codex/skills/socratic-spec-authoring/SKILL.md b/.codex/skills/socratic-spec-authoring/SKILL.md index 2820565..37c5279 100644 --- a/.codex/skills/socratic-spec-authoring/SKILL.md +++ b/.codex/skills/socratic-spec-authoring/SKILL.md @@ -1,50 +1,34 @@ --- name: socratic-spec-authoring -description: 모든 즉시 실행 가능한 작업에서 소크라테스 질문으로 spec을 세부화하고, 더 이상 blocker 질문이 없을 때만 승인 요청을 거쳐 승인 상태까지 고정할 때 사용한다. 답보다 질문이 먼저여야 하고, spec 안에 하위 작업, write scope, verification, tracking 결정을 함께 넣어야 할 때 이 skill을 사용한다. +description: 소크라테스 질문으로 `spec.md`를 잠그고, 사용자 명시적 승인 뒤 `task.json`을 `approved`로 고정한다. --- # Socratic Spec Authoring -이 skill의 목적은 spec을 예쁘게 쓰는 것이 아니라, 구현 전에 요구사항을 충분히 명확하게 고정하는 것이다. - ## 언제 사용하나 -- `request-intake`가 `즉시 실행 가능한 작업`으로 끝났다. -- `ambiguity-interview`가 single spec 후보를 만든 뒤다. -- 구현 전에 승인된 spec이 필요하다. +- `request-intake`로 task skeleton이 만들어졌고 구현 전 승인된 requirement가 필요하다. ## 먼저 확인할 것 -- 최신 사용자 요청 -- relevant source of truth -- `SPECS.md` -- `docs/operations/sdd-spec-policy.md` -- `docs/operations/workflow-governance.md` -- `assets/spec-template.md` +- `AGENTS.md` +- `docs/artifact-model.md` +- `docs/runtime.md` +- `workflows/tasks//spec.md` +- `workflows/tasks//task.json` ## 작업 방식 -1. 사실 확인과 question contract는 `docs/operations/sdd-spec-policy.md`를 그대로 따른다. -2. source of truth로 먼저 닫고도 남는 blocker만 질문한다. -3. 질문 루프와 답변 요약은 active spec의 clarification log에 직접 남긴다. -4. Harness가 approval gate를 채우기에 더 이상 blocker 질문이 없다고 판단하기 전에는 승인 요청으로 넘어가지 않는다. -5. approval gate, write scope, verification, tracking decision이 비어 있으면 spec을 `Approved`로 올리지 않는다. -6. 충분히 잠기면 spec 초안을 만들고, 그 초안에 대해 사용자에게 명시적 승인 요청을 한다. -7. 사용자가 현재 spec 초안에 명시적으로 동의했을 때만 `Approved`로 상태를 고정한다. -8. verification, review, user validation에서 spec defect가 드러나면 별도 planning 메모를 만들지 말고 spec을 다시 `Draft`로 내려 재질의응답을 연다. +1. 질문으로 request, problem, goals, non-goals, constraints, acceptance를 `spec.md`에 기록한다. +2. `Socratic Clarification Log`는 `Q:`, `A:`, `Decision:` triplet으로만 적는다. +3. blocker가 없어질 때까지 질문 루프를 반복한다. +4. placeholder가 남아 있지 않은지 확인한다. +5. 사용자가 현재 `spec.md` 초안에 명시적으로 동의했을 때만 `python3 scripts/workflow.py approve --note ...`를 실행한다. +6. `approve`가 `spec.md`를 `task.json.intake`로 잠근다는 점을 전제로, 승인 전에는 phase 생성이나 구현을 시작하지 않는다. ## 결과 -- `docs/specs/active/YYYY-MM-DD-.md` spec 문서 -- clarification log와 approval gate가 채워진 canonical requirement artifact -- 다음 stage가 implementation이면 approved spec, 아니라면 reopened draft 또는 stop signal - -## 피해야 할 것 - -- 질문 없이 바로 설계안이나 코드를 내놓지 않는다. -- 더 이상 blocker 질문이 남아 있는데 승인 요청부터 하지 않는다. -- 사용자의 작업 요청 자체를 spec approval로 간주하지 않는다. -- 승인 전 구현, issue 생성, PR 생성을 시작하지 않는다. -- 하위 작업이 필요한데 spec 밖 TODO로만 남기지 않는다. -- write scope와 verification을 비운 spec을 `Approved`로 올리지 않는다. -- late-discovered spec defect를 code repair만으로 흡수하지 않는다. +- 승인된 `spec.md` +- locked `task.json.intake` +- `state=approved`인 `task.json` +- 다음 stage: `phase-planner` diff --git a/.codex/skills/socratic-spec-authoring/assets/spec-template.md b/.codex/skills/socratic-spec-authoring/assets/spec-template.md deleted file mode 100644 index 9fca4a6..0000000 --- a/.codex/skills/socratic-spec-authoring/assets/spec-template.md +++ /dev/null @@ -1,62 +0,0 @@ -# - -- Status: `Draft` -- Primary Repo: -- Related Issue: -- Related PR: - -## Request Summary - -## Problem - -## Goals - -## Non-goals - -## Socratic Clarification Log - -### Round 1 -- Prompted gap: -- Why it mattered: -- User answer / evidence: -- Closed gap: -- Remaining blocker: - -## Assumptions And Constraints - -## Approval Gate -- Problem and goal locked: -- Non-goals explicit: -- Primary repo and write scope locked: -- Verification method locked: -- Subtask split decided: -- Tracking decision locked: -- Remaining blockers: - -## Write Scope -- Primary repo: -- Allowed write paths: -- Control-plane artifacts: -- Explicitly forbidden: -- Network / external systems: -- Escalation triggers: - -## Acceptance Criteria - -## Verification -- Contract profile: -- Commands: - -## Delivery And Tracking Plan -- Lane: -- Parent issue needed: -- PR needed: - -## Detailed Subtasks -- 없음 - -## Risks Or Open Questions - -## Approval -- Harness judgment: -- User approval: diff --git a/.codex/skills/verification-contract-runner/SKILL.md b/.codex/skills/verification-contract-runner/SKILL.md deleted file mode 100644 index 483d0d7..0000000 --- a/.codex/skills/verification-contract-runner/SKILL.md +++ /dev/null @@ -1,36 +0,0 @@ ---- -name: verification-contract-runner -description: 구현이 끝난 뒤 선택된 verification contract profile에 따라 exact command set을 실행하고 latest verification evidence를 남긴다. ---- - -# Verification Contract Runner - -이 skill의 핵심은 새 명령을 발명하지 않는 것이다. - -## 언제 사용하나 - -- 구현이나 문서 변경이 끝나서 검증이 필요하다. -- repair 후 failed command를 다시 실행해야 한다. - -## 먼저 확인할 것 - -- approved spec -- spec에 적힌 verification contract profile -- `docs/operations/verification-contract-registry.md` - -## 작업 방식 - -1. selected profile의 required command와 conditional command를 확인한다. -2. required command를 모두 실행한다. -3. conditional command는 trigger 또는 skip reason과 함께 남긴다. -4. 결과를 compact `Verification Summary` 또는 detailed `Verification Report`로 정리한다. - -## 결과 - -- `Contract profile` -- `Overall status` -- `Preconditions` 필요 시 -- `Ran` 또는 command별 `Status` -- 핵심 `Evidence` -- `Failure` 또는 `Failure or skipped summary` -- `Next action` diff --git a/.codex/skills/verification-runner/SKILL.md b/.codex/skills/verification-runner/SKILL.md new file mode 100644 index 0000000..79a48b6 --- /dev/null +++ b/.codex/skills/verification-runner/SKILL.md @@ -0,0 +1,30 @@ +--- +name: verification-runner +description: active phase의 acceptance command를 실행하고 `runs/*.json`에 verification evidence를 남긴다. +--- + +# Verification Runner + +## 언제 사용하나 + +- phase 구현이 끝났고 검증을 실행해야 한다. + +## 먼저 확인할 것 + +- `docs/runtime.md` +- `docs/hooks.md` +- `workflows/tasks//phases.json` +- `workflows/tasks//task.json` + +## 작업 방식 + +1. `acceptance.commands`를 확인한다. +2. `python3 scripts/workflow.py verify `를 실행한다. +3. `task.json.last_verified_run_id`가 현재 phase의 passed verification으로 갱신됐는지 확인한다. + +## 결과 + +- `runs/.json` +- `task.json.latest_run_id` +- `task.json.last_verified_run_id` +- 다음 stage: `review-closeout` diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..1b64787 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,17 @@ +#!/bin/sh +set -eu + +task_id="${WORKFLOW_TASK_ID:-}" +if [ -z "$task_id" ]; then + task_id="$(python3 scripts/workflow.py status --infer-active-task 2>/dev/null || true)" +fi + +if [ -n "$task_id" ]; then + phase_id="${WORKFLOW_PHASE_ID:-}" + if [ -z "$phase_id" ]; then + phase_id="$(python3 scripts/workflow.py status "$task_id" --field active_phase_id 2>/dev/null || true)" + fi + python3 scripts/workflow.py hook pre_commit --task-id "$task_id" --phase-id "$phase_id" --staged +else + python3 scripts/workflow.py hook pre_commit --staged +fi diff --git a/.githooks/pre-push b/.githooks/pre-push new file mode 100755 index 0000000..4b907c2 --- /dev/null +++ b/.githooks/pre-push @@ -0,0 +1,18 @@ +#!/bin/sh +set -eu + +python3 scripts/workflow.py doctor +python3 scripts/workflow.py hook pre_command --command-text "git push $*" + +task_id="${WORKFLOW_TASK_ID:-}" +if [ -z "$task_id" ]; then + task_id="$(python3 scripts/workflow.py status --infer-active-task 2>/dev/null || true)" +fi + +if [ -n "$task_id" ]; then + phase_id="${WORKFLOW_PHASE_ID:-}" + if [ -z "$phase_id" ]; then + phase_id="$(python3 scripts/workflow.py status "$task_id" --field active_phase_id 2>/dev/null || true)" + fi + python3 scripts/workflow.py hook pre_push --task-id "$task_id" --phase-id "$phase_id" --command-text "git push $*" +fi diff --git a/.github/ISSUE_TEMPLATE/engineering_task.yml b/.github/ISSUE_TEMPLATE/engineering_task.yml index a8987e0..aaa0ab2 100644 --- a/.github/ISSUE_TEMPLATE/engineering_task.yml +++ b/.github/ISSUE_TEMPLATE/engineering_task.yml @@ -10,7 +10,7 @@ body: 각 항목은 짧은 문단 하나나 1~3개 bullet이면 충분합니다. 아래에는 문제, 배경, 결과, 범위, 리스크처럼 판단에 필요한 내용만 적어 주세요. - 파일 목록, 브랜치 이름, spec 파일명, raw 검증 명령, 문서 inventory 같은 운영 상세는 approved spec이나 별도 증거에 둡니다. + 파일 목록, 브랜치 이름, raw 검증 명령, 세부 상태 로그 같은 운영 상세는 `workflows/tasks//` artifact에 둡니다. - type: dropdown id: target_repo diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index d5613a9..15e92aa 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,8 +1,8 @@ diff --git a/.github/workflows/workflow-control-plane.yml b/.github/workflows/workflow-control-plane.yml new file mode 100644 index 0000000..22a8759 --- /dev/null +++ b/.github/workflows/workflow-control-plane.yml @@ -0,0 +1,48 @@ +name: Workflow Control Plane + +on: + pull_request: + paths: + - "AGENTS.md" + - "docs/**" + - "workflows/**" + - "scripts/**" + - ".githooks/**" + - ".codex/skills/**" + - "tests/**" + push: + branches: + - main + paths: + - "AGENTS.md" + - "docs/**" + - "workflows/**" + - "scripts/**" + - ".githooks/**" + - ".codex/skills/**" + - "tests/**" + +jobs: + workflow-control-plane: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Initialize workflow runtime + run: python3 scripts/workflow.py init + + - name: Run unit tests + run: python3 -m unittest discover -s tests -v + + - name: Run doctor + run: python3 scripts/workflow.py doctor + + - name: Validate tracked workflow tasks + run: python3 scripts/workflow.py status --all --check + + - name: Check diff formatting + run: git diff --check diff --git a/.gitignore b/.gitignore index 6776abc..cbee7fa 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ .gitignore .idea/ +__pycache__/ +workflows/system/circuit-breaker.json diff --git a/AGENTS.md b/AGENTS.md index c38fe8e..d94ce58 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,29 +1,103 @@ # AGENTS.md -`git-ranker-workflow`는 `git-ranker-workflow`, `git-ranker`, `git-ranker-client`를 묶는 하네스 컨트롤 플레인 저장소다. +`git-ranker-workflow`는 `git-ranker-workflow`, `git-ranker`, `git-ranker-client`를 묶는 Codex용 workflow control plane 저장소다. -## 시작 순서 +## Mission -1. [docs/README.md](docs/README.md)를 읽고 문서 구조를 확인한다. -2. [SPECS.md](SPECS.md)에서 spec 파일명과 상태 규칙을 확인한다. -3. [docs/operations/sdd-spec-policy.md](docs/operations/sdd-spec-policy.md)에서 SDD와 소크라테스 기반 spec 규칙을 확인한다. -4. [docs/operations/workflow-governance.md](docs/operations/workflow-governance.md)에서 공통 작업 운영 규칙과 증거 규칙을 확인한다. -5. 해당 작업과 관련된 디렉터리의 `README.md`와 본문 문서를 읽는다. -6. 현재 작업 문서는 `docs/specs/active/`, 완료된 작업 문서는 `docs/specs/completed/`에서 찾는다. +- 이 저장소는 하위 저장소 작업을 위한 spec, phase, verification, review control plane만 소유한다. +- 앱 동작, API, UI, 런타임 계약의 진짜 source of truth는 각 앱 저장소의 `AGENTS.md`, `README.md`, 코드, 테스트다. +- 이 저장소는 작업을 어떻게 정의하고, 승인하고, 실행하고, 검증하고, 닫는지만 책임진다. -## source of truth 위치 +## Start Here -- [docs/architecture](docs/architecture/README.md): 컨트롤 플레인 구조와 cross-repo 경계 -- [docs/operations](docs/operations/README.md): 작업 운영 규칙, evidence, runbook -- [docs/specs](docs/specs/README.md): SDD 기반 작업 spec 문서와 남은 요구사항 queue +1. [docs/README.md](docs/README.md)로 문서 맵을 확인한다. +2. 현재 작업이 있으면 `workflows/tasks//spec.md`, `task.json`, `phases.json`, `runs/*.json`을 읽는다. +3. 전역 guard와 breaker 설정은 `workflows/system/hooks.json`에서 확인한다. +4. 실제 앱 동작을 바꾸는 작업이면 대상 저장소의 문서와 테스트를 먼저 읽는다. -앱 동작의 canonical source는 workflow 복제 문서가 아니라 각 앱 저장소의 엔트리 문서와 코드/테스트에 둔다. +## CRITICAL Rules -## 운영 원칙 +- CRITICAL: 승인되지 않은 spec으로 구현이나 phase 실행을 시작하지 않는다. +- CRITICAL: task state는 `python3 scripts/workflow.py ...` 명령으로만 전이한다. `task.json`, `phases.json`, `runs/*.json`을 수동 편집하지 않는다. +- CRITICAL: 구현 계획의 canonical source는 `workflows/tasks//phases.json` 하나다. +- CRITICAL: `allowed_write_paths` 밖 변경은 phase 위반이다. 범위가 바뀌면 spec/phase를 다시 잠근다. +- CRITICAL: 구현 코드 변경에는 대응 테스트가 필요하다. 예외는 `test_policy.mode=evidence_only`와 non-empty `test_policy.evidence`뿐이다. +- CRITICAL: user validation 기록 없이 `completed`로 전이하지 않는다. -- 루트 문서는 인덱스만 맡는다. 상세 규칙은 `docs/` 아래에 둔다. -- 새 작업은 원칙적으로 stable source of truth 문서를 먼저 읽고, 남은 작업은 `docs/specs/active/`의 draft/approved spec으로 확인한다. -- 모든 즉시 실행 가능한 작업은 구현 전에 소크라테스 방식으로 spec을 먼저 만든다. -- spec은 요구사항, 하위 작업, write scope, verification, tracking 결정을 함께 소유한다. -- 변경이 생기면 코드만이 아니라 관련 source of truth 문서도 함께 갱신한다. -- 커밋 메시지는 항상 루트의 `.gitmessage.ko.txt` 형식을 따른다. +## Architecture Boundaries + +- `git-ranker-workflow`: harness 헌법, task artifact, phase orchestration, guard/hook, review closeout +- `git-ranker`: backend/source-of-truth for server behavior, domain logic, backend tests +- `git-ranker-client`: frontend/source-of-truth for UI behavior, client tests + +ALWAYS: 앱 계약을 바꾸는 변경은 해당 앱 저장소의 문서와 테스트를 함께 갱신한다. +ALWAYS: 이 저장소에는 앱 동작을 복제한 prose 문서를 두지 않는다. +NEVER: control plane 문서를 앱 source-of-truth보다 우선시하지 않는다. + +## Workflow Contract + +1. `new`가 `workflows/tasks//spec.md`와 `task.json` skeleton을 만든다. +2. 소크라테스 질문으로 `spec.md`를 잠근다. `Socratic Clarification Log`는 `Q:`, `A:`, `Decision:` triplet만 사용한다. +3. 사용자가 동의하면 `approve`가 같은 task 디렉터리의 `spec.md`에 Approval block을 추가하고, `spec.md` 내용을 `task.json.intake`로 잠근 뒤 `task.json`을 `approved`로 전이한다. +4. 추가 요구사항으로 spec을 다시 잠그면 `approve`를 다시 실행해 `task.json.intake`를 재잠근다. +5. `plan --from ` 또는 `plan --stdin`이 phase 초안을 읽어 `phases.json`으로 적재한다. `spec.md`와 `task.json.intake`가 어긋나 있으면 plan을 시작하지 않는다. +6. `run --start`가 active phase를 시작한다. +7. 구현 후 `run --complete --changed-path ...`가 write scope와 TDD guard를 통과해야 한다. +8. `verify`가 acceptance command를 실행하고 `runs/*.json`에 evidence를 남긴다. +9. 모든 phase가 완료되면 `review`가 review readiness를 기록한다. +10. `review --close --user-validation-note ...`만 최종 완료를 닫을 수 있다. +11. 실패, block, 추가 요구사항이 생기면 `reopen`으로 repair loop를 재개한다. + +## TDD Contract + +ALWAYS: 구현 변경은 같은 phase 안에서 테스트 변경과 함께 제출한다. +ALWAYS: 테스트를 생략하는 경우에는 `test_policy.mode=evidence_only`와 명시적 근거를 phase에 적는다. +ALWAYS: TDD Guard가 막으면 테스트 또는 phase policy를 먼저 보강한다. +NEVER: guard를 우회하기 위해 훅 설정을 임시로 낮추거나 artifact를 직접 수정하지 않는다. +NEVER: verification 통과 전 review 단계로 건너뛰지 않는다. + +## Command Contract + +표준 명령은 아래만 사용한다. + +```bash +python3 scripts/workflow.py init +python3 scripts/workflow.py doctor +python3 scripts/workflow.py new --title "..." --primary-repo +python3 scripts/workflow.py approve --note "..." +python3 scripts/workflow.py plan --from /path/to/phases.json +python3 scripts/workflow.py plan --stdin +python3 scripts/workflow.py run --start +python3 scripts/workflow.py run --complete --changed-path ... +python3 scripts/workflow.py verify +python3 scripts/workflow.py review --note "..." +python3 scripts/workflow.py review --close --user-validation-note "..." +python3 scripts/workflow.py reopen --note "..." +python3 scripts/workflow.py status +``` + +- `scripts/workflow.py`는 영구적인 CLI entrypoint다. +- 실제 실행 엔진은 `scripts/workflow_runtime/cli.py`, `engine.py`, `models.py`, `guards.py`, `doctor.py`, `git_ops.py`가 분담한다. +- `workflow.py`를 유지하는 이유는 사람과 AI가 하나의 안정적인 명령 surface만 기억하면 되기 때문이다. + +## Source Of Truth Order + +1. `AGENTS.md`: 헌법과 강제 규칙 +2. `docs/`: artifact, runtime, hooks, runbook 설명 +3. `workflows/tasks//`: 현재 작업의 spec, state, phase, evidence +4. `workflows/system/hooks.json`: 전역 guard/breaker 정책 +5. 대상 앱 저장소의 문서, 코드, 테스트: 실제 앱 동작 + +## Forbidden Actions + +NEVER: `task.json`, `phases.json`, `runs/*.json`을 수동 편집해서 상태를 맞춘다. +NEVER: `phases.json` 대신 prose TODO, PR 본문, 채팅 로그를 canonical 계획으로 쓴다. +NEVER: `allowed_write_paths` 밖 파일을 조용히 수정한다. +NEVER: destructive command를 guard 없이 실행한다. + +## Change Discipline + +ALWAYS: workflow 계약이 바뀌면 `AGENTS.md`, 관련 `docs/`, `.codex/skills/`, `.githooks/`, `tests/`를 함께 갱신한다. +ALWAYS: 전역 정책은 `workflows/system/hooks.json` 하나에 잠근다. +ALWAYS: commit 메시지는 루트의 `.gitmessage.ko.txt` 형식을 따른다. +ALWAYS: doctor와 테스트가 통과하는 상태로 변경을 마무리한다. diff --git a/SPECS.md b/SPECS.md deleted file mode 100644 index 8a97da2..0000000 --- a/SPECS.md +++ /dev/null @@ -1,34 +0,0 @@ -# SPECS.md - -이 문서는 spec 문서의 파일명 규칙과 상태 전이 규칙만 다룬다. - -## 파일 위치 - -- 진행 중이거나 아직 끝나지 않은 spec 문서: [docs/specs/active](docs/specs/active/README.md) -- 완료되었거나 실행 없이 종료된 spec 문서: [docs/specs/completed](docs/specs/completed/README.md) - -## 파일명 규칙 - -- 기본 형식: `YYYY-MM-DD-.md` -- 예시: `2026-04-13-grw-sdd-socratic-harness-workflow.md` -- slug는 가능하면 primary repo나 work item을 드러내도록 짧고 구체적으로 쓴다. -- GitHub issue가 있더라도 spec 파일명에 issue 번호를 강제하지 않는다. issue/PR 연결 정보는 spec metadata에 적는다. - -## 상태 규칙 - -spec 문서 본문 상단에는 최소한 아래 상태 중 하나를 명시한다. - -- `Draft`: 소크라테스 질문과 초안 정리가 진행 중인 상태 -- `Approved`: Harness 판단과 사용자 승인이 끝나 구현 또는 추적 단계로 넘어갈 수 있는 상태 -- `In Progress`: 구현, 검증, tracking artifact 생성 같은 실행이 진행 중인 상태 -- `Blocked`: 선행조건 미충족이나 외부 이슈로 진행이 막힌 상태 -- `Completed`: 구현, 검증, 최종 사용자 확인까지 끝난 상태 -- `Rejected`: 대화 전환, 취소, 범위 밖 요청 등으로 실행 없이 종료한 상태 - -## 전이 규칙 - -- `Draft`, `Approved`, `In Progress`, `Blocked` 상태 문서는 `docs/specs/active/`에 둔다. -- `Completed`, `Rejected` 상태 문서는 `docs/specs/completed/`에 둔다. -- 한 작업의 canonical working artifact는 항상 spec 하나다. -- 추가 planning 문서를 따로 만들지 않는다. 하위 작업 분해, write scope, verification, tracking 결정은 spec 안에 함께 남긴다. -- `Approved` 또는 `In Progress` 상태에서 요구사항 lock이 깨지거나 late-discovered spec defect가 드러나면 상태를 다시 `Draft`로 내려 재질의응답과 재승인을 거친다. diff --git a/docs/README.md b/docs/README.md index 4d65b80..ede2dd6 100644 --- a/docs/README.md +++ b/docs/README.md @@ -1,27 +1,25 @@ # Docs Index -이 저장소의 공식 문서는 목적별 디렉터리로 나뉜다. 새 작업은 이 문서와 각 디렉터리 `README.md`를 진입점으로 삼는다. +이 저장소의 공식 문서는 `AGENTS.md`를 헌법으로 두고, `docs/`는 그 헌법을 설명하는 보조 문서만 유지한다. ## 먼저 읽을 문서 1. [AGENTS.md](../AGENTS.md) -2. [SPECS.md](../SPECS.md) -3. [docs/architecture/control-plane-map.md](architecture/control-plane-map.md) -4. [docs/architecture/harness-system-map.md](architecture/harness-system-map.md) -5. [docs/operations/sdd-spec-policy.md](operations/sdd-spec-policy.md) -6. [docs/operations/workflow-governance.md](operations/workflow-governance.md) -7. [docs/specs/README.md](specs/README.md) -8. [docs/specs/active/README.md](specs/active/README.md) +2. [docs/artifact-model.md](artifact-model.md) +3. [docs/runtime.md](runtime.md) +4. [docs/hooks.md](hooks.md) +5. [docs/runbook.md](runbook.md) -## 디렉터리 맵 +## 문서 맵 -- [docs/architecture](architecture/README.md): 저장소 구조, cross-repo 경계, 문서 배치 규칙 -- [docs/operations](operations/README.md): 작업 운영 규칙, evidence, 실행 runbook -- [docs/specs](specs/README.md): SDD 기반 작업 spec 문서, draft requirement, completed history +- [artifact-model.md](artifact-model.md): `tasks/`와 `system/` artifact 구조, spec과 phase 적재 위치 +- [runtime.md](runtime.md): 승인, phase 실행, verification, review, reopen 순서와 내부 런타임 구조 +- [hooks.md](hooks.md): TDD, write scope, dangerous command, verification freshness, circuit breaker +- [runbook.md](runbook.md): 초기화, 새 task 생성, 점검 명령 ## 우선순위 -- 현재 workflow는 Harness control plane 문서만 유지한다. -- 모든 즉시 실행 가능한 작업은 구현 전에 승인된 spec을 먼저 만든다. -- 별도 roadmap/catalog source는 유지하지 않고, 남은 작업은 `docs/specs/active/`의 draft/approved spec으로 관리한다. -- 앱 동작과 계약의 canonical source는 각 앱 저장소의 `AGENTS.md`, `README.md`, 코드, 테스트를 우선한다. +- 헌법과 강제 규칙은 `AGENTS.md`가 소유한다. +- 현재 작업의 상태와 증거는 `workflows/tasks//`가 소유한다. +- 전역 guard 정책은 `workflows/system/hooks.json`이 소유한다. +- 앱 동작의 source of truth는 각 앱 저장소의 문서, 코드, 테스트다. diff --git a/docs/architecture/README.md b/docs/architecture/README.md deleted file mode 100644 index d93a495..0000000 --- a/docs/architecture/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# Architecture Docs - -이 디렉터리는 workflow 저장소가 컨트롤 플레인으로서 어떤 책임을 가지는지 정리한다. - -현재 기준 문서: - -- [control-plane-map.md](control-plane-map.md) -- [context-pack-registry.md](context-pack-registry.md) -- [harness-system-map.md](harness-system-map.md) -- [repo-handoff-contract.md](repo-handoff-contract.md) diff --git a/docs/architecture/context-pack-registry.md b/docs/architecture/context-pack-registry.md deleted file mode 100644 index b0ea732..0000000 --- a/docs/architecture/context-pack-registry.md +++ /dev/null @@ -1,123 +0,0 @@ -# Context Pack Registry - -이 문서는 승인된 spec에서 구현 컨텍스트로 넘어갈 때 무엇을 읽고 무엇을 읽지 말아야 하는지 task type별로 고정한다. - -관련 intake 기준은 [../operations/request-routing-policy.md](../operations/request-routing-policy.md), spec 기준은 [../operations/sdd-spec-policy.md](../operations/sdd-spec-policy.md), 공통 운영 규칙은 [../operations/workflow-governance.md](../operations/workflow-governance.md)를 따른다. - -## Registry Invariants - -- 실행 가능한 작업은 항상 하나의 primary context pack만 가진다. -- 모든 pack은 공통 base context를 먼저 읽고, 그 다음 pack별 required docs를 연다. -- optional docs는 approved spec이나 hot file 탐색이 명시한 trigger가 있을 때만 연다. -- 다른 pack의 required docs까지 필요해지면 임의로 pack을 합치지 않고 spec의 subtask split 또는 tracking split로 되돌린다. -- repo entrypoint와 local skill discovery는 [repo-handoff-contract.md](repo-handoff-contract.md)의 순서를 따른다. -- target repo entrypoint 문서나 worktree가 없으면 `Context Ready`를 선언하지 않는다. - -## Common Base Context - -모든 pack은 아래 입력을 기본으로 공유한다. - -| 입력 | 목적 | -| --- | --- | -| `AGENTS.md` | 시작 순서와 source of truth 확인 | -| `docs/README.md` | 현재 문서 구조 확인 | -| `SPECS.md` | spec 상태와 파일명 규칙 확인 | -| [../operations/sdd-spec-policy.md](../operations/sdd-spec-policy.md) | spec 작성과 승인 규칙 확인 | -| [../operations/workflow-governance.md](../operations/workflow-governance.md) | 운영 규칙 확인 | -| request record 또는 GitHub issue | 요청 배경 확인 | -| approved spec | 문제, 하위 작업, write scope, verification, tracking 결정 확인 | - -## Task-To-Context Mapping - -| Task type | Primary repo | Primary pack | 기본 목표 | 기본 금지 | -| --- | --- | --- | --- | --- | -| `workflow 문서 수정` | `git-ranker-workflow` | `workflow-docs` | harness 문서, template, skill, spec을 같은 용어로 수정 | sibling app repo code tree eager load | -| `backend 수정` | `git-ranker` | `backend-change` | backend 코드, 테스트, 설정을 한 저장소 범위에서 수정 | frontend 전체 pack eager load | -| `frontend 수정` | `git-ranker-client` | `frontend-change` | client route, UI, state, config를 한 저장소 범위에서 수정 | backend 내부 구현 eager load | -| `cross-repo planning` | `git-ranker-workflow` | `cross-repo-planning` | 여러 저장소 작업을 spec과 tracking 단위로 분해 | multi-repo code diff 동시 작성 | - -## Pack Definitions - -### `workflow-docs` - -대상: - -- `docs/`, `.github/`, `.codex/skills/`, `docs/specs/` 수정 - -Required docs: - -- touched directory의 `README.md` -- approved spec에서 named input으로 적은 현재 source of truth -- stage semantics를 바꾸는 경우 [harness-system-map.md](harness-system-map.md) - -Optional docs: - -- directly linked adjacent docs -- 같은 surface를 다루는 현재 active spec 또는 가까운 completed spec - -Forbidden context: - -- sibling app repo code tree -- workflow에 앱 canonical source를 새로 복제하는 것 - -### `backend-change` - -Required docs: - -- target repo entrypoint 문서 -- approved spec에 적힌 backend write scope -- current subtask가 직접 건드리는 build/test/contract entrypoint - -Optional docs: - -- verification contract나 tool boundary 확인이 필요할 때 관련 operations 문서 -- directly impacted public contract doc - -Forbidden context: - -- frontend route/component/state 전체 pack -- workflow stable docs eager load - -### `frontend-change` - -Required docs: - -- target repo entrypoint 문서 -- approved spec에 적힌 frontend write scope -- current subtask가 직접 건드리는 `package.json`, `.env.example`, route entrypoint - -Optional docs: - -- verification contract나 tool boundary 확인이 필요할 때 관련 operations 문서 -- directly impacted backend contract surface - -Forbidden context: - -- backend 내부 service/repository 구현 eager load -- workflow stable docs eager load - -### `cross-repo-planning` - -Required docs: - -- [control-plane-map.md](control-plane-map.md) -- [harness-system-map.md](harness-system-map.md) -- approved spec -- 영향받는 각 저장소의 root entry 문서 - -Optional docs: - -- repo별 verification entrypoint -- repo manifest 또는 contract surface - -Forbidden context: - -- 여러 저장소 code tree를 동시에 깊게 읽는 것 -- app 구현 착수 - -## Hot File Exploration Rules - -1. 먼저 approved spec의 active subtask와 write scope를 기준으로 `rg -n` 한다. -2. first ring은 entry handler, nearest config, nearest test만 연다. -3. import chain은 한 hop씩만 확장한다. -4. 다른 pack이 필요해지면 spec split 또는 task split로 되돌린다. diff --git a/docs/architecture/control-plane-map.md b/docs/architecture/control-plane-map.md deleted file mode 100644 index dc91815..0000000 --- a/docs/architecture/control-plane-map.md +++ /dev/null @@ -1,50 +0,0 @@ -# Workflow Control Plane Map - -`git-ranker-workflow`는 앱 저장소의 시스템 오브 레코드를 대체하지 않는다. 대신 cross-repo 작업을 시작하고 검증하고 증거를 정리하는 컨트롤 플레인 역할을 맡는다. - -## 문서 배치 원칙 - -| 항목 | 공식 위치 | 설명 | -| --- | --- | --- | -| 컨트롤 플레인 구조 | `docs/architecture/` | 하네스 구성요소, 상태 전이, context pack 정의 | -| 작업 운영 규칙 | `docs/operations/` | Issue/PR 규칙, evidence, close-out 규칙 | -| 작업 spec 문서와 requirement queue | `docs/specs/` | 현재 작업의 draft/approved requirement, 하위 작업, 완료 기록 | -| 앱별 canonical source | 각 앱 저장소 | `AGENTS.md`, `README.md`, 코드, 테스트, 저장소별 계약 문서 | - -## Federated Ownership Matrix - -| Surface | Workflow control plane owns | Repo-local app owns | Workflow must not own | -| --- | --- | --- | --- | -| planning and sequencing | cross-repo request routing, spec, context pack, verification semantics, evidence, publish choreography | repo 안에서의 concrete implementation sequencing | repo-local implementation playbook를 stable workflow policy로 다시 쓰는 것 | -| runtime entrypoint and knowledge | [repo-handoff-contract.md](repo-handoff-contract.md)에 정의한 target repo discovery order, handoff minimum, 다음 source를 고르는 기준 | target repo `AGENTS.md`, `README.md`, 코드, 테스트, repo-local contract docs | app 동작 설명, troubleshooting, config 해석을 workflow 문서로 복제하는 것 | -| verification and guardrail baseline | contract profile vocabulary, evidence minimum, publish/review stage rule | concrete command surface, CI wiring, bootstrap, env preconditions, static analysis config | app verification command/config를 workflow-owned canonical text로 유지하는 것 | -| skill layer | workflow stage procedure, orchestration handoff wording | repo-local bootstrap and implementation skill | backend/frontend implementation knowledge를 workflow-local skill에 축적하는 것 | - -## 읽기 우선순위 - -1. 루트 인덱스: `AGENTS.md`, `SPECS.md` -2. 아키텍처 기준: `docs/architecture/control-plane-map.md`, `docs/architecture/harness-system-map.md`, `docs/architecture/context-pack-registry.md`, `docs/architecture/repo-handoff-contract.md` -3. 공통 운영 규칙: `docs/operations/sdd-spec-policy.md`, `docs/operations/workflow-governance.md` -4. 해당 작업의 draft/approved spec: `docs/specs/active/*.md` -5. 완료 기록이 필요할 때의 historical spec: `docs/specs/completed/*.md` -6. 앱 저장소 엔트리 문서와 해당 코드/테스트 - -## 이관 원칙 - -- 현재 control plane source of truth는 `docs/architecture/`, `docs/operations/`, `docs/specs/`로 한정한다. -- readiness/history/generated/runtime 전용 자산은 현재 Harness 범위에 포함하지 않는다. -- 별도 roadmap/catalog 문서는 유지하지 않고, remaining work는 active draft spec과 그 prerequisite로 표현한다. -- 앱 동작 설명이 필요하면 workflow 복제 문서 대신 대상 저장소의 문서와 코드를 직접 읽는다. -- 새 문서가 생기면 이 구조를 깨지 않도록 가장 가까운 목적 디렉터리에 추가한다. - -## No-Duplication Rule - -- sibling app repo에 이미 canonical source가 있으면 workflow는 link, handoff boundary, remaining-work spec만 남긴다. -- workflow stable docs는 "언제 어디로 넘기는지"까지만 설명하고, command, config path, feature behavior 같은 repo-local detail을 canonical text로 다시 적지 않는다. -- runtime handoff minimum과 target repo entrypoint requirement는 [repo-handoff-contract.md](repo-handoff-contract.md)에서만 고정한다. - -## Migration Exit Criteria - -- workflow stable docs가 app knowledge를 복제하지 않고 repo-local canonical source를 가리킨다. -- workflow-local skill이 thin-layer로 남아 stage procedure와 handoff만 다룬다. -- repo-specific verification, bootstrap, implementation detail은 sibling repo 또는 follow-up spec이 소유한다. diff --git a/docs/architecture/harness-system-map.md b/docs/architecture/harness-system-map.md deleted file mode 100644 index 77362df..0000000 --- a/docs/architecture/harness-system-map.md +++ /dev/null @@ -1,176 +0,0 @@ -# Harness System Map - -이 문서는 `git-ranker-workflow` 하네스의 architecture-level source of truth다. task별 요구사항과 sequencing은 `docs/specs/`의 draft/approved spec이 맡고, 이 문서는 각 단계의 책임과 입력/출력과 상태 전이를 정의한다. - -## Control Objectives - -하네스는 아래 축을 순서대로 고정하는 것을 목표로 한다. - -1. 요청 라우팅을 빠르게 끝낸다 -2. 즉시 실행 가능한 작업은 모두 소크라테스 기반 spec으로 먼저 정의한다 -3. spec 안에서 하위 작업, write scope, tracking 결정을 잠근다 -4. 한 번에 한 하위 작업만 구현하고 결정론적 검증을 끝낸다 -5. publish가 필요한 결과는 open PR로 공개한다 -6. review와 feedback은 위험 신호가 있을 때만 올린다 -7. 마지막 완료 판정은 사용자 최종 검증까지 포함한다 - -## Planning Boundary - -- 현재 planning source of truth는 [docs/specs/](../specs/README.md)다. -- remaining work와 sequencing은 `docs/specs/active/`의 draft/approved spec과 prerequisite note로 표현한다. -- 별도 roadmap/catalog 문서는 유지하지 않는다. -- 현재 control plane은 별도 history/reference 트리를 유지하지 않는다. 앱 동작의 canonical source는 각 앱 저장소 문서와 코드/테스트다. - -## Federated Thin-Layer Invariants - -- workflow-local docs, skills, handoff artifact는 thin layer다. route, stage responsibility, boundary, minimum handoff만 정의한다. -- state transition은 repo entrypoint에서 멈춘다. backend/frontend implementation procedure, file-level bootstrap, repo-local verification command는 sibling repo canonical source가 소유한다. -- control plane 단계가 더 많은 app-specific detail을 필요로 하면 workflow stable doc을 두껍게 늘리지 말고 target repo entrypoint 또는 repo-local follow-up spec으로 handoff한다. - -## System Components - -| Component | Responsibility | Primary Input | Primary Output | -| --- | --- | --- | --- | -| Router | 사용자 요청을 `대화`, `모호한 요청`, `즉시 실행 가능한 작업`으로 분류한다. | 사용자 요청, 운영 규칙 | route decision | -| Spec Writer | 소크라테스 질문으로 문제, 목표, 제약, 전제, framing, 하위 작업, 검증 기준을 정리한다. | routed request, source docs, user answers | draft spec, approved spec | -| Tracker | spec에 따라 parent issue, subtask issue, pre-implementation tracking artifact가 필요한지 결정하고 만든다. | approved spec | optional issue set, tracking decision | -| Context Pack | approved spec 기준으로 최소 문서와 읽기 경계를 고정한다. | approved spec, task type, source docs | bounded context pack | -| Implementer | 승인된 spec의 현재 하위 작업을 허용 범위 안에서 구현한다. | approved spec, selected subtask, context | diff, change notes | -| Verification | 명시된 verification contract를 실행하고 pass/fail을 기록한다. | diff, verification contract | verification report or summary | -| Publisher | latest verification을 반영한 open PR을 만든다. | diff, verification evidence, PR body | open PR | -| Reviewer | 필요할 때만 open PR 또는 latest diff를 검토하고 verdict를 남긴다. | open PR, verification evidence, approved spec | optional review verdict | -| Feedback | blocker, 반복 실패, quality drift가 있을 때만 guardrail 후보 또는 cleanup candidate를 남긴다. | failure notes, review verdict, quality sweep signal | optional feedback entry, next follow-up | -| User Validator | 최종 결과가 승인된 spec과 맞는지 사용자 관점에서 닫는다. | latest diff or PR, verification summary, approved spec | final approval or follow-up request | - -## Canonical Artifacts - -| Artifact | Description | Canonical Location | -| --- | --- | --- | -| Request record | 사용자 요청과 초기 분류 결과 | Issue 본문 또는 작업 대화 | -| Spec | 승인된 요구사항, 하위 작업, write scope, verification, tracking 결정을 모은 단일 문서 | `docs/specs/` | -| Context pack | approved spec에서 허용된 최소 문서와 읽기 경계 | spec과 후속 policy | -| Verification evidence | compact verification summary 또는 detailed report로 latest verification 상태를 남긴다 | spec, verification artifact, PR summary | -| Open PR | 검증된 결과를 공개하는 기본 협업 surface | GitHub PR | -| Review verdict | optional independent review 결과 | review comment, PR comment, spec summary | -| Feedback entry | optional guardrail 승격 또는 후속 분류 결과 | spec, follow-up policy, ledger | -| Quality sweep report | non-blocking quality signal과 cleanup handoff | spec, sweep artifact, 후속 issue/PR | -| User validation note | 사용자가 승인된 spec과 최종 결과를 대조해 남긴 완료 판정 | spec, PR comment, 작업 대화 | - -## Canonical State Machine - -### Default Happy Path - -`Received -> Routed -> Spec Drafting -> Spec Approved -> Context Ready -> Implementing -> Verifying -> User Validating -> Completed` - -### Tracked Path - -`Received -> Routed -> Spec Drafting -> Spec Approved -> Tracking -> Context Ready -> Implementing -> Verifying -> Publishing -> Reviewing -> User Validating -> Completed` - -### Ambiguity Path - -`Received -> Routed -> Interviewing -> Spec Drafting` - -### Repair Path - -`Verifying -> Repairing -> Implementing` - -`Reviewing -> Repairing -> Verifying -> Publishing` - -### Spec Reopen Path - -`Verifying -> Spec Drafting` - -`Reviewing -> Spec Drafting` - -`Repairing -> Spec Drafting` - -`User Validating -> Spec Drafting` - -### Optional Feedback Path - -`Reviewing -> Feedback Pending -> Completed` - -`Verifying -> Feedback Pending -> Completed` - -### Terminal Non-execution Path - -`Routed -> Rejected` - -### Canonical States - -| State | Entry Criteria | Exit Condition | Next State | -| --- | --- | --- | --- | -| `Received` | 사용자 요청이 들어왔다. | Router가 요청 유형을 분류했다. | `Routed` | -| `Routed` | 요청이 `대화`, `모호한 요청`, `즉시 실행 가능한 작업` 중 하나로 분류됐다. | 대화/거절이면 종료, 모호하면 인터뷰 시작, 실행 가능하면 spec drafting을 시작한다. | `Rejected`, `Interviewing`, `Spec Drafting` | -| `Interviewing` | 요청은 실행 후보지만 single requirement, primary repo, 기본 완료 조건이 아직 모호하다. | ambiguity가 줄어 spec drafting으로 넘어가거나, 더 이상 줄일 수 없다고 판단한다. | `Spec Drafting`, `Blocked`, `Rejected` | -| `Spec Drafting` | 소크라테스 질문과 초안 정리로 spec을 작성 중이다. | Harness가 approval gate를 통과했다고 판단하고 사용자가 승인했거나, 더 진행할 수 없다고 판단한다. | `Spec Approved`, `Blocked`, `Rejected` | -| `Spec Approved` | 승인된 spec이 존재하고 목표, 하위 작업, write scope, verification, tracking 결정이 잠겼다. | tracking이 필요하면 tracking을 시작하고, 아니면 context pack을 준비한다. | `Tracking`, `Context Ready`, `Blocked` | -| `Tracking` | approved spec이 issue/PR 또는 subtask issue로 투영돼야 한다. | 필요한 tracking artifact 생성이 끝나거나, tracking 불가 사유가 정리됐다. | `Context Ready`, `Blocked` | -| `Context Ready` | 현재 하위 작업에 필요한 최소 문서와 write scope가 승인된 spec 기준으로 고정됐다. | Implementer가 변경을 시작하거나, 필요한 source of truth가 부족하다고 판단한다. | `Implementing`, `Interviewing`, `Blocked` | -| `Implementing` | Implementer가 허용된 범위 안에서 변경을 만든다. | 검증 가능한 diff가 준비되거나, scope drift 또는 외부 blocker가 드러난다. | `Verifying`, `Blocked`, `Spec Drafting` | -| `Verifying` | 결정론적 검증 명령을 실행한다. | 모든 필수 명령이 통과하거나, 수리 가능한 diff defect, spec defect, 환경 blocker가 분류된다. | `Publishing`, `Repairing`, `Spec Drafting`, `Blocked`, `Feedback Pending` | -| `Publishing` | latest verification이 current diff 기준으로 준비됐고 PR body가 채워졌다. | open PR이 publish되거나, publish를 못 할 이유가 확인된다. | `Reviewing`, `User Validating`, `Blocked` | -| `Reviewing` | open PR 또는 latest diff에 대해 independent review가 실제로 시작됐다. | 승인하거나, 수정 요청을 남기거나, spec defect 또는 검토 불가능한 blocker를 선언한다. | `User Validating`, `Repairing`, `Spec Drafting`, `Blocked`, `Feedback Pending` | -| `Repairing` | verification 실패나 review 요청으로 재작업이 필요하다. | 재시도 가능한 수정이 준비되거나, root cause가 spec defect라고 확인되거나, 계속 진행할 근거가 사라진다. | `Implementing`, `Spec Drafting`, `Blocked`, `Rejected` | -| `Feedback Pending` | blocker, 반복 실패, systemic gap, quality handoff를 정리해야 한다. | feedback이 기록되고 다음 follow-up 여부가 결정됐다. | `User Validating`, `Completed` | -| `User Validating` | latest verification과 결과 공유가 끝났고, 최종 수용 여부를 사용자 기준으로 닫아야 한다. | 사용자가 spec 대비 결과를 승인하거나, 구현 follow-up 또는 spec defect reopen을 요청한다. | `Completed`, `Repairing`, `Spec Drafting`, `Blocked` | -| `Completed` | 검증된 결과가 승인된 spec과 맞게 닫혔고, trigger된 review나 feedback이 있다면 그것도 정리됐다. | 없음 | terminal | -| `Blocked` | 선행조건 미충족, 외부 의존성, write scope 충돌, source 부족으로 더 진행할 수 없다. | blocker 해소 후 다시 진입하거나 작업을 종료한다. | `Interviewing`, `Spec Drafting`, `Spec Approved`, `Context Ready`, terminal | -| `Rejected` | 대화성 요청, 작업 취소, 범위 밖 요청처럼 실행을 계속하지 않기로 했다. | 없음 | terminal | - -## Pass / Fail Semantics - -| Stage | Pass | Fail | Required Consequence | -| --- | --- | --- | --- | -| Router | 정확히 하나의 route가 선택됐고 다음 소유자가 명확하다. | route가 중복되거나 비어 있다. | 편집 작업을 시작하지 않고 intake로 되돌린다. | -| Spec Writer | 질문, why, 전제 점검, framing 점검, 요약으로 approval gate를 닫는다. | 즉답, 숨은 가정 추가, approval gate 누락, 승인 전 구현 착수가 발생한다. | `Interviewing`, `Spec Drafting`, 또는 `Blocked`로 되돌린다. | -| Context Pack | 최소 문서와 금지 범위가 함께 고정됐다. | source of truth가 부족하거나 불필요한 컨텍스트를 과다 공개했다. | `Interviewing` 또는 `Blocked`로 되돌린다. | -| Implementer | 승인된 spec의 현재 하위 작업을 허용 write scope 안에서 구현한다. | scope drift, 무단 cross-repo 변경, spec 밖 요구사항 추가가 발생한다. | `Blocked` 또는 spec 수정 승인 단계로 되돌린다. | -| Verification | 모든 필수 검증 명령이 통과하고 증거가 남는다. | 명령 실패, 환경 부족, 증거 누락, spec defect가 있다. | `Repairing`, `Spec Drafting`, 또는 `Blocked`로 전환한다. | -| Publishing | latest verification을 반영한 open PR이 생성됐다. | 검증 전 publish를 시도하거나, publish 이유가 불명확하다. | `Blocked` 또는 `Verifying`로 되돌린다. | -| Reviewer | reviewer가 diff, verification evidence, approved spec을 기준으로 판단한다. | blocking finding, self-approval 시도, 검증과 diff 불일치, late-discovered spec defect가 있다. | `Repairing`, `Spec Drafting`, 또는 `Blocked`로 되돌린다. | -| Feedback | 정말 필요한 failure나 follow-up만 기록됐다. | close-out과 후속 guardrail/cleanup 판단이 필요한데 비어 있다. | `Feedback Pending`에 머문다. | -| User Validator | 사용자가 spec 대비 결과를 승인한다. | spec과 결과가 어긋나거나, spec lock 자체가 잘못됐거나, follow-up이 필요하다고 판단한다. | `Repairing`, `Spec Drafting`, 또는 `Blocked`로 되돌린다. | - -## Stop Conditions - -- `Rejected`: 대화성 요청, 범위 밖 요청, 사용자 취소처럼 작업 자체를 진행하지 않는 경우다. -- `Blocked`: 선행조건 부족, 외부 의존성, 권한 문제, canonical source 부재로 현재 작업 안에서 더 진행할 수 없는 경우다. -- `Completed`: latest verification을 반영한 결과가 승인된 spec과 맞게 닫혔고, trigger된 review/feedback/user validation까지 정리된 경우다. -- `Completed`는 terminal state지만, post-closeout quality sweep이 필요하면 새 `Received` work item을 시작할 수 있다. -- `Repairing`은 terminal state가 아니다. 반복 실패가 누적되면 verification contract registry와 feedback policy의 retry budget 기준에 따라 `Blocked` 또는 후속 planning으로 넘긴다. - -## Role Separation Invariants - -- Implementer는 자기 결과를 independent review evidence로 승인할 수 없다. -- review를 수행할 때만 reviewer separation invariant가 적용된다. -- reviewer는 diff만이 아니라 verification evidence와 approved spec을 함께 읽어야 한다. -- 구현 전 승인되지 않은 spec은 canonical source로 간주하지 않는다. -- 늦게 드러난 spec defect는 current diff patch만으로 흡수하지 않고 spec reopen과 재승인으로 되돌린다. -- verification이 끝나기 전에는 publish하지 않는다. -- publish는 review보다 앞설 수 있다. open PR은 검증된 결과를 공유하는 기본 surface다. -- feedback 단계는 optional이다. blocker, 반복 실패, systemic gap이 있을 때만 들어간다. -- non-blocking quality drift는 original task의 completion verdict를 뒤집지 않고, separate cleanup issue/PR candidate로 분리한다. - -## Issue / PR Projection - -- approved spec은 모든 즉시 실행 가능한 작업의 공식 기록이다. -- issue는 approved spec이 추적 surface를 요구할 때만 만든다. -- 하위 작업이 생기면 spec 안에서 먼저 분해하고, tracking 필요 여부를 각 하위 작업에 대해 별도로 판단한다. -- PR은 기본적으로 latest verification을 반영한 결과를 공유하는 컨테이너다. review는 그 다음 optional stage다. -- review/repair loop가 열리면 open PR의 current diff와 latest verification evidence를 기준으로 닫는다. -- feedback이나 quality sweep이 필요하면 PR 완료 여부와 분리해 별도 artifact나 follow-up으로 남긴다. -- 완료된 spec은 `docs/specs/completed/`로 옮기고, 후속 작업이 있으면 이 문서를 전제조건으로 참조한다. -- completed spec은 historical close-out record이며 full transcript는 아니다. 현재 canonical runtime과 정책은 stable source of truth 문서에서 해석한다. - -## Detailed Policy Surfaces - -- [../operations/request-routing-policy.md](../operations/request-routing-policy.md)는 `Router`, `Interviewing`, `Spec Drafting`, `Rejected` semantics를 세분화한다. -- [../operations/sdd-spec-policy.md](../operations/sdd-spec-policy.md)는 소크라테스 질문 루프와 spec 승인 기준을 고정한다. -- [repo-handoff-contract.md](repo-handoff-contract.md)는 workflow가 target repo에 넘기는 minimum handoff, discovery order, bootstrap gap handling을 고정한다. -- [context-pack-registry.md](context-pack-registry.md)는 승인된 spec에서 `Context Ready`로 가는 규칙을 registry 형태로 고정한다. -- [../operations/tool-boundary-matrix.md](../operations/tool-boundary-matrix.md)는 `Context Ready`와 `Implementing` 단계의 허용 도구와 write scope 경계를 상세화한다. -- [../operations/verification-contract-registry.md](../operations/verification-contract-registry.md)는 `Verifying`, `Repairing`, `Blocked` semantics와 retry budget을 명시한다. -- [../operations/dual-agent-review-policy.md](../operations/dual-agent-review-policy.md)는 optional `Reviewing` 단계의 reviewer input, verdict, repair loop, evidence rule을 구체화한다. -- [../operations/failure-to-guardrail-feedback-loop.md](../operations/failure-to-guardrail-feedback-loop.md)는 optional `Feedback Pending` close-out과 guardrail 승격 규칙을 고정한다. -- [../operations/continuous-quality-feedback-loop.md](../operations/continuous-quality-feedback-loop.md)는 recurring quality sweep, cleanup candidate, quality sweep report handoff를 고정한다. diff --git a/docs/architecture/repo-handoff-contract.md b/docs/architecture/repo-handoff-contract.md deleted file mode 100644 index e119f18..0000000 --- a/docs/architecture/repo-handoff-contract.md +++ /dev/null @@ -1,72 +0,0 @@ -# Federated Repo Handoff Contract - -이 문서는 workflow control plane이 target repo에 handoff할 때 넘겨야 하는 최소 contract를 고정한다. 목적은 repo-local implementation knowledge를 workflow가 다시 소유하지 않으면서도, 다음 owner가 어디서부터 읽어야 하는지 흔들리지 않게 만드는 것이다. - -## Contract Invariants - -- workflow handoff는 target repo root entrypoint에서 멈춘다. -- workflow는 route, active spec, active subtask, write boundary, verification hook까지만 넘긴다. -- target repo가 구현 절차, command inventory, bootstrap, verification surface의 canonical source를 소유한다. -- target repo entrypoint가 비어 있으면 workflow는 stable doc으로 대신 메우지 않고 repo-local bootstrap follow-up으로 넘긴다. - -## Handoff Summary Minimum - -workflow가 target repo를 다음 owner로 지정할 때 handoff summary에는 최소 아래가 보여야 한다. - -| Field | Why it is required | -| --- | --- | -| target repo | 다음 owner가 어느 저장소로 이동해야 하는지 잠근다. | -| approved spec path | 현재 결정의 canonical source를 가리킨다. | -| active subtask 또는 current goal | 이번 handoff가 무엇을 닫으려는지 한 줄로 고정한다. | -| allowed write scope와 explicit non-goal | repo-local owner가 범위를 넓히지 않게 한다. | -| verification contract profile 또는 named verification entrypoint | 어떤 검증 surface를 먼저 확인해야 하는지 고정한다. | -| repo entrypoint status | `AGENTS.md`가 로컬/remote source에서 확인됐는지, 아니면 bootstrap gap인지 드러낸다. | -| next source order | handoff 이후 읽을 다음 canonical source를 순서대로 고정한다. | -| open question 또는 blocker | workflow가 닫지 못한 공백을 repo-local follow-up과 분리한다. | - -## Target Repo Discovery Order - -workflow handoff 뒤 target repo는 아래 순서로 읽는다. - -1. root `AGENTS.md` -2. root `README.md` for repo overview only -3. `AGENTS.md`가 지목한 repo-local contract, verification, environment entry docs -4. repo-local `.codex/skills/` 또는 equivalent skill surface -5. current subtask가 직접 건드리는 code, test, config - -root `README.md`는 프로젝트 설명과 high-level orientation까지만 기대한다. concrete entrypoint, command inventory, verification sequence는 `AGENTS.md` 또는 named entry docs가 소유한다. - -## Local Skill Discovery Order - -- local skill은 canonical docs보다 앞서 읽지 않는다. -- `AGENTS.md` 또는 repo-local entry doc이 특정 skill을 지목했을 때만 연다. -- skill은 repo-local canonical source를 대체하지 않고, bootstrap 또는 implementation handoff를 압축하는 절차 layer만 소유한다. -- skill이 없으면 workflow 문서로 되돌아가지 않고 repo-local docs와 code/test를 canonical source로 읽는다. - -## Target Repo `AGENTS.md` Requirement - -target repo root `AGENTS.md`는 최소 아래를 만족해야 한다. - -- repo의 canonical source가 workflow stable docs가 아니라 repo-local 문서와 code/test임을 명시한다. -- 시작 순서에서 `README.md`는 개요 확인용으로만 두고, concrete entrypoint는 named entry docs 기준으로 적는다. -- verification entrypoint 또는 repo manifest surface를 어디서 찾는지 적는다. -- local skill discovery rule과 skill이 canonical docs 뒤에 온다는 점을 적는다. -- workflow handoff가 repo-local command inventory나 troubleshooting playbook를 carry하지 않는다고 적는다. - -## Repo Bootstrap Checklist - -target repo가 workflow handoff를 안전하게 받을 최소 bootstrap은 아래다. - -| Checklist item | Minimum expectation | -| --- | --- | -| root `AGENTS.md` | repo-local start order, source of truth, verification entrypoint, skill discovery rule을 노출한다. | -| root `README.md` | repo 목적과 high-level project description만 제공해도 된다. concrete entrypoint 책임은 지지 않는다. | -| verification entry surface | `build.gradle`, `package.json`, `.env.example`, contract doc처럼 현재 slice가 먼저 읽어야 할 검증/환경 entrypoint가 named source로 존재한다. | -| repo-local contract doc | public API, auth, schema, runtime precondition처럼 repo가 직접 소유해야 하는 surface가 있으면 workflow가 아니라 repo-local 문서가 소유한다. | -| local skill surface | 있으면 repo-local bootstrap 또는 implementation 절차를 thin layer로 제공하고, 없으면 canonical docs와 code/test만으로 onboarding 가능해야 한다. | - -## Missing Bootstrap Handling - -- `AGENTS.md`가 없으면 workflow handoff는 `bootstrap gap`으로 표시하고 repo-local bootstrap spec을 prerequisite로 건다. -- `README.md`나 verification entry surface가 비어 있으면 workflow는 missing item만 기록하고, 상세 구현 절차를 stable doc으로 추가하지 않는다. -- remote source로만 entrypoint를 확인한 경우에도 같은 minimum을 적용하며, local worktree onboarding은 repo-local follow-up이 닫는다. diff --git a/docs/artifact-model.md b/docs/artifact-model.md new file mode 100644 index 0000000..d6b99ff --- /dev/null +++ b/docs/artifact-model.md @@ -0,0 +1,94 @@ +# Artifact Model + +## Core Layout + +```text +workflows/ +├── system/ +│ ├── hooks.json +│ └── circuit-breaker.json +└── tasks/ + └── / + ├── spec.md + ├── task.json + ├── phases.json + └── runs/ + └── .json +``` + +## Artifact Roles + +- `workflows/tasks//spec.md`: 사람용 requirement artifact. 요청, 문제, 목표, 비목표, 제약, acceptance, 소크라테스 질문 로그, 승인 기록을 담는다. +- `workflows/tasks//task.json`: mutable task state이자 승인 시점의 locked intake source다. approval, active phase, latest run, latest verified run, blocked reason, user validation과 `intake`를 담는다. +- `workflows/tasks//phases.json`: 승인 이후 생성된 executable phase 집합이다. phase 목표, write scope, acceptance command, test policy, retry count를 담는다. +- `workflows/tasks//runs/*.json`: phase completion, verification, review, reopen evidence다. +- `workflows/system/hooks.json`: 전역 hook/guard/breaker 정책이다. +- `workflows/system/circuit-breaker.json`: 반복 실패 fingerprint 메모리다. + +## Spec And Phase Loading Flow + +1. `new`가 task 디렉터리와 `spec.md`, `task.json`, `phases.json` skeleton을 만든다. +2. 소크라테스 질문으로 `spec.md`를 채운다. `Socratic Clarification Log`는 `Q:`, `A:`, `Decision:` triplet만 허용한다. +3. 사용자가 현재 spec 초안에 명시적으로 동의하면 `approve`가 같은 `spec.md`에 Approval block을 추가하고, 내용을 `task.json.intake`로 잠근 뒤 `task.json`을 `approved`로 전이한다. +4. 추가 요구사항으로 spec을 다시 잠그면 `approve`를 다시 실행해 `task.json.intake`를 갱신한다. +5. `plan --from` 또는 `plan --stdin`이 외부 phase 초안을 읽어 같은 task 디렉터리의 `phases.json`으로 적재한다. +6. 이후 실행, verification, review, reopen은 같은 task 디렉터리 artifact만 읽고 쓴다. + +## Canonical State Fields + +`task.json`은 최소한 아래 필드를 가진다. + +- `id` +- `title` +- `state` +- `primary_repo` +- `created_at` +- `approved_at` +- `approval` +- `active_phase_id` +- `latest_run_id` +- `last_verified_run_id` +- `blocked_reason` +- `user_validated` +- `user_validation_note` +- `intake.request_summary` +- `intake.problem_summary` +- `intake.goals` +- `intake.non_goals` +- `intake.constraints` +- `intake.acceptance` +- `intake.clarifications` + +`intake.clarifications`의 각 항목은 아래 필드를 가진다. + +- `question` +- `answer` +- `decision` +- `resolved` + +## Phase Fields + +`phases.json`의 각 phase는 최소한 아래 필드를 가진다. + +- `id` +- `order` +- `title` +- `goal` +- `inputs` +- `allowed_write_paths` +- `acceptance.commands` +- `test_policy.mode` +- `test_policy.evidence` +- `status` +- `retry_count` + +`test_policy.mode`는 아래 둘 중 하나다. + +- `require_tests`: 구현 변경이 있으면 대응 테스트 변경이 필요하다. +- `evidence_only`: 테스트 delta 대신 명시적 evidence 배열을 근거로 허용한다. + +## Validation Ownership + +- JSON schema 파일은 사용하지 않는다. +- artifact validation과 상태 전이는 `scripts/workflow_runtime/models.py`와 `engine.py`가 소유한다. +- 문서는 구조를 설명하지만 enforcement owner는 코드와 테스트다. diff --git a/docs/hooks.md b/docs/hooks.md new file mode 100644 index 0000000..8391bed --- /dev/null +++ b/docs/hooks.md @@ -0,0 +1,57 @@ +# Hooks + +## Canonical Hook Config + +- 설정 파일: [workflows/system/hooks.json](../workflows/system/hooks.json) +- 구현 파일: [scripts/hooks](../scripts/hooks/) +- trigger surface: workflow CLI, `.githooks/`, CI + +## Hook Events + +- `pre_command` +- `pre_commit` +- `pre_phase_start` +- `post_change` +- `pre_phase_complete` +- `pre_review` +- `pre_complete` +- `pre_push` + +## Guards + +### TDD Guard + +- 구현 파일이 바뀌면 대응 테스트 변경이 있어야 한다. +- 예외는 phase의 `test_policy.mode=evidence_only`와 non-empty `test_policy.evidence`로만 허용한다. +- docs, workflow JSON, markdown, template-only 변경은 guard 대상에서 제외한다. + +### Write Scope Guard + +- staged 변경과 phase completion 변경이 `allowed_write_paths` 밖이면 차단한다. +- task artifact 디렉터리 `workflows/tasks//`는 항상 허용 범위에 포함된다. + +### Dangerous Command Guard + +- `rm -rf`, `git reset --hard`, `git checkout --`, `git clean -fd`, force push, destructive DB command를 차단한다. +- workflow CLI가 acceptance command를 실행하기 전에도 같은 guard를 적용한다. + +### Latest Verification Guard + +- `pre_review`에서는 active phase에 대한 latest passed verification을 요구한다. +- `pre_push`에서는 unpushed diff가 active phase scope와 겹칠 때 latest passed verification을 요구한다. + +### Circuit Breaker + +- 동일한 `error_fingerprint`가 60초 안에 5회 반복되면 task와 phase를 `blocked`로 전이한다. +- `run --fail`뿐 아니라 verification 실패 누적도 동일하게 breaker 입력으로 취급한다. +- 상태는 `workflows/system/circuit-breaker.json`에 저장한다. + +## Trigger Surfaces + +- `python3 scripts/workflow.py hook ...` +- `.githooks/pre-commit` +- `.githooks/pre-push` +- `.github/workflows/workflow-control-plane.yml` + +git hook만으로는 충분하지 않다. 로컬 CLI와 phase runner가 같은 hook 설정을 읽어야 guard가 유지된다. +`python3 scripts/workflow.py init`는 로컬 git 저장소의 `core.hooksPath`를 `.githooks`로 맞추고, `doctor`는 이 설정이 빠지면 실패한다. diff --git a/docs/operations/README.md b/docs/operations/README.md deleted file mode 100644 index 7114c35..0000000 --- a/docs/operations/README.md +++ /dev/null @@ -1,16 +0,0 @@ -# Operations Docs - -이 디렉터리는 작업 운영 규칙, evidence 규칙, runbook 같은 실행 문서를 둔다. - -현재 기준 문서: - -- [sdd-spec-policy.md](sdd-spec-policy.md) -- [workflow-governance.md](workflow-governance.md) -- [request-routing-policy.md](request-routing-policy.md) -- [tool-boundary-matrix.md](tool-boundary-matrix.md) -- [verification-contract-registry.md](verification-contract-registry.md) -- [dual-agent-review-policy.md](dual-agent-review-policy.md) -- [failure-to-guardrail-feedback-loop.md](failure-to-guardrail-feedback-loop.md) -- [continuous-quality-feedback-loop.md](continuous-quality-feedback-loop.md) -- [guardrail-ledger-template.md](guardrail-ledger-template.md) -- [quality-sweep-report-template.md](quality-sweep-report-template.md) diff --git a/docs/operations/continuous-quality-feedback-loop.md b/docs/operations/continuous-quality-feedback-loop.md deleted file mode 100644 index 3239262..0000000 --- a/docs/operations/continuous-quality-feedback-loop.md +++ /dev/null @@ -1,60 +0,0 @@ -# Continuous Quality Feedback Loop - -이 문서는 recurring quality sweep 규칙을 고정한다. - -## Policy Invariants - -- quality sweep은 verification, review, feedback close-out을 대체하지 않는다. -- quality sweep은 `Completed` 뒤의 recurring surface다. -- quality signal entry는 signal class 하나당 하나씩 남긴다. -- correctness, security, reliability에 직접 영향을 주는 finding은 cleanup candidate로 축소하지 않는다. -- workflow control plane은 trigger, taxonomy, evidence, handoff만 소유한다. - -## Trigger Modes - -| Trigger mode | When to use | Expected output | -| --- | --- | --- | -| `post-closeout` | 특정 task가 닫힌 직후 quality drift를 다시 점검할 때 | quality sweep report | -| `scheduled` | 저장소별 cadence에 따라 주기적으로 점검할 때 | quality sweep report | -| `targeted` | 특정 경로나 reviewer note를 근거로 한정 스캔이 필요할 때 | bounded sweep result | - -## Quality Signal Taxonomy - -| Signal class | Typical signal | Preferred follow-up | -| --- | --- | --- | -| `coding-rule-drift` | lint warning 증가, rule suppression 증가, formatter drift | `cleanup-pr-candidate` 또는 `guardrail-follow-up` | -| `duplication-drift` | copy-paste된 로직, 유사 patch 반복 | `cleanup-pr-candidate` | -| `unused-code-drift` | unused export/helper/dependency, stale flag, dead path | `cleanup-pr-candidate` 또는 `repair-now` | - -## Detection Surface Rule - -quality sweep report에는 어떤 detector 또는 inspection surface를 썼는지 반드시 남긴다. 가능한 source는 아래 우선순위를 따른다. - -1. impacted repo의 entry doc, package/build script, verification contract에 이미 정의된 deterministic command -2. repo-specific static analysis surface -3. reviewer note, completed spec, grep, manual code reading 같은 bounded inspection - -## Disposition Vocabulary - -| Disposition | Choose when | Typical output | -| --- | --- | --- | -| `repair-now` | 사실상 correctness, contract, reliability, security bug를 다시 발견한 경우 | 새 bug/repair issue 또는 즉시 repair handoff | -| `cleanup-pr-candidate` | bounded write scope와 deterministic evidence가 있어 별도 cleanup issue/PR로 안전하게 분리 가능한 경우 | quality sweep report, cleanup handoff | -| `guardrail-follow-up` | root cause가 missing detector, missing policy, weak CI gate 같은 system asset 부족인 경우 | feedback ledger entry, follow-up asset | -| `no-action` | false positive, duplicate report, 이미 해소됨, 지금 자산화할 가치가 낮은 경우 | explicit rationale only | - -## Cleanup Handoff Rule - -`cleanup-pr-candidate`를 선택했으면 최소한 아래를 next work item에 넘긴다. - -- 대상 저장소 -- bounded scan scope -- signal class -- detector 또는 inspection surface -- 핵심 evidence -- non-scope -- follow-up owner 또는 next action - -## Quality Sweep Report Minimum - -quality sweep 결과는 spec close-out, review note, 또는 follow-up artifact 중 최소 한 곳에 [quality-sweep-report-template.md](quality-sweep-report-template.md) 형식으로 남긴다. diff --git a/docs/operations/dual-agent-review-policy.md b/docs/operations/dual-agent-review-policy.md deleted file mode 100644 index dcc818e..0000000 --- a/docs/operations/dual-agent-review-policy.md +++ /dev/null @@ -1,69 +0,0 @@ -# Dual-Agent Review Policy - -이 문서는 open PR 이후 independent review를 언제 수행하고, 어떤 입력으로 verdict를 남기며, 어떤 경우에 repair 또는 `Blocked`로 되돌리는지를 고정한다. - -## Policy Invariants - -- independent review는 중요한 통제지만, 모든 작업의 publish 선행조건은 아니다. -- canonical 기본 흐름은 `implement -> verify -> open PR publish`다. -- independent review를 수행할 때만 implementer와 reviewer는 반드시 서로 다른 agent 또는 사람이어야 한다. -- review는 latest verification evidence가 최신이고 reviewer minimum context가 채워진 뒤에만 시작할 수 있다. -- reviewer는 diff만 보지 않고 approved spec, verification 결과, 남은 리스크를 함께 읽어야 한다. - -## Review Trigger Rules - -아래 중 하나라도 해당하면 independent review를 수행한다. - -- 작업이 `guarded lane`이다. -- public API/schema, auth/permission, security, CI, migration, destructive change를 포함한다. -- 사용자가 reviewer 또는 sub-agent 검토를 명시적으로 요청했다. -- verification은 통과했지만 residual risk가 높다. - -## Reviewer Minimum Context - -- open PR link 또는 latest diff reference -- latest verification report 또는 summary -- approved spec 요약 또는 경로 -- touched diff summary -- source-of-truth update 목록 또는 업데이트 불필요 사유 -- 남은 리스크, skipped conditional command, follow-up 필요 사항 - -아래 중 하나라도 있으면 verdict는 `blocked`다. - -- latest verification evidence가 없거나 latest가 아니다 -- approved spec이 없어 scope를 고정할 수 없다 -- diff summary와 실제 변경 범위가 맞지 않는다 -- canonical source가 없어 reviewer가 새 정책을 발명해야 한다 - -## Verdict Vocabulary - -| Verdict | Meaning | Required Consequence | -| --- | --- | --- | -| `approved` | latest verification과 current diff를 기준으로 blocking finding이 없다 | merge 또는 후속 handoff로 진행할 수 있다 | -| `changes-requested` | 현재 PR 범위 안에서 수리 가능한 blocking finding이 있다 | implementer가 repair 후 affected verification을 다시 실행한다 | -| `blocked` | missing reviewer input, missing canonical source, boundary conflict처럼 현재 PR 안에서 review를 닫을 수 없다 | `Blocked` 또는 follow-up planning으로 전환한다 | - -## Review Evidence Rule - -canonical review evidence는 review comment, PR comment, spec close-out 중 최소 한 곳에 아래 필드를 남긴다. - -```md -## Independent Review -- Implementer: -- Reviewer: -- Reviewer Input: - - PR or diff: - - Latest verification report: - - Approved spec: - - Source-of-truth update: - - Remaining risks / skipped checks: -- Review Verdict: -- Findings / Change Requests: -- Evidence: -``` - -## Review Repair Loop - -- `changes-requested` verdict에는 blocking finding, 기대 next action, 다시 확인할 command 또는 문서가 들어가야 한다. -- implementer는 current diff에서 수정을 남기고 영향을 받은 command를 다시 실행한 뒤 verification evidence를 갱신한다. -- 같은 root cause가 두 번의 repair 뒤에도 남으면 `blocked` 또는 follow-up split로 전환한다. diff --git a/docs/operations/failure-to-guardrail-feedback-loop.md b/docs/operations/failure-to-guardrail-feedback-loop.md deleted file mode 100644 index af685fd..0000000 --- a/docs/operations/failure-to-guardrail-feedback-loop.md +++ /dev/null @@ -1,62 +0,0 @@ -# Failure-To-Guardrail Feedback Loop - -이 문서는 언제 feedback close-out이 필요한가와 이 failure를 어떤 guardrail로 승격할 것인가를 고정한다. - -## Policy Invariants - -- feedback close-out은 모든 성공 경로의 공통 의무가 아니다. -- latest verification이 통과했고 blocker, 반복 실패, systemic gap이 없으면 feedback 단계 없이 완료할 수 있다. -- feedback close-out은 verification과 review를 대체하지 않는다. -- ledger entry는 root cause 하나당 하나씩 남긴다. -- 승격 대상은 `docs-rule`, `skill`, `test`, `ci`, `template`, `no-new-guardrail` 여섯 가지로 고정한다. - -## Feedback Trigger Rules - -아래 중 하나라도 해당하면 feedback close-out을 남긴다. - -- verification이 `failed` 또는 `blocked`로 끝났다 -- independent review가 `changes-requested` 또는 `blocked`를 남겼다 -- 같은 root cause가 한 작업 안에서 반복됐다 -- 이번 작업 중 guardrail promotion, cleanup candidate, follow-up policy asset이 필요하다고 판단했다 -- 사용자가 회고, postmortem, guardrail 정리를 명시적으로 요청했다 - -## Feedback Entry Preconditions - -- approved spec -- latest verification evidence 또는 `Blocked` 이유 -- latest review verdict 또는 review skipped reason -- repair attempt 요약 필요 시 -- 남은 리스크, skipped check, failure note - -## Failure Taxonomy - -| Failure class | Typical signal | Primary stage | -| --- | --- | --- | -| `intake-scope` | request가 single spec으로 고정되지 않음, non-goal 누락, subtask split 미정 | `Routed`, `Interviewing`, `Spec Drafting` | -| `context-boundary` | 잘못된 context pack, sibling repo eager load, boundary 위반 | `Context Ready`, `Implementing` | -| `verification-contract` | latest report 누락, command 불일치, precondition 미기록 | `Verifying`, `Repairing` | -| `review-handoff` | reviewer input 누락, diff/report mismatch, finding 분류 drift | `Reviewing` | -| `behavior-regression` | correctness, contract, reliability regression이 늦게 발견됨 | `Implementing`, `Verifying`, `Reviewing` | -| `evidence-closeout` | source-of-truth update 누락, artifact 위치 불명확 | `Publishing`, `User Validating`, `Completed` | -| `external-dependency` | Docker, port, credential, remote outage, missing worktree | 어느 단계든 | - -## Guardrail Promotion Decision - -| Decision | Choose when | -| --- | --- | -| `docs-rule` | canonical rule, vocabulary, evidence minimum이 비어 있거나 충돌한다 | -| `skill` | canonical rule은 있지만 같은 실행 순서나 handoff가 반복적으로 흔들린다 | -| `test` | regression을 결정론적으로 재현하고 막을 수 있다 | -| `ci` | 로컬 deterministic check는 있는데 PR/runtime에서 일관되게 실행되지 않는다 | -| `template` | spec, issue, PR, review body처럼 structured input 자체가 비어 있어 정보 누락이 반복된다 | -| `no-new-guardrail` | 기존 guardrail이 이미 충분하거나 one-off 외부 이슈다 | - -## Feedback Close-Out Minimum - -`Feedback Pending`을 닫을 때는 spec close-out, review note, follow-up artifact 중 최소 한 곳에 아래가 있어야 한다. - -- latest verification 상태 또는 `Blocked` 이유 -- latest review verdict 또는 review skipped reason -- ledger entry 하나 이상 또는 `no new guardrail` entry -- promotion decision 근거 -- follow-up asset, issue, 또는 `없음` 사유 diff --git a/docs/operations/guardrail-ledger-template.md b/docs/operations/guardrail-ledger-template.md deleted file mode 100644 index 955d0b6..0000000 --- a/docs/operations/guardrail-ledger-template.md +++ /dev/null @@ -1,67 +0,0 @@ -# Guardrail Ledger Template - -이 문서는 [failure-to-guardrail-feedback-loop.md](failure-to-guardrail-feedback-loop.md)의 canonical ledger entry 형식을 제공한다. PR 본문에는 요약만 둘 수 있고, canonical entry는 spec close-out, review note, follow-up artifact 중 하나에 아래 최소 필드로 남긴다. - -## Entry Template - -```md -## Guardrail Ledger Entry -- Date: -- Task / Spec: -- Stage: - - `Interviewing | Spec Drafting | Spec Approved | Context Ready | Implementing | Verifying | Reviewing | Feedback Pending | User Validating | Blocked` -- Failure class: - - `intake-scope | context-boundary | verification-contract | review-handoff | behavior-regression | evidence-closeout | external-dependency` -- Trigger signal: -- Root cause: -- Existing guardrail: -- Recurrence: - - `first-seen | repeated | systemic` -- Current issue disposition: - - `repaired-in-scope | approved-with-note | blocked | follow-up-needed` -- Promotion decision: - - `docs-rule | skill | test | ci | template | no-new-guardrail` -- Guardrail status: - - `applied-now | follow-up-created | deferred | no-new-guardrail` -- Decision rationale: -- Guardrail change or follow-up asset: -- Owner / next action: -- Evidence: -- Notes: -``` - -## Writing Rules - -- 한 entry에는 root cause 하나만 적는다. -- `Trigger signal`은 symptom이고, `Root cause`는 반복될 이유다. -- `Existing guardrail`에는 이미 있던 policy, template, test, CI, skill이 있으면 적고, 없으면 `없음`이라고 적는다. -- `Promotion decision`과 `Guardrail status`를 함께 적어 무엇으로 승격할지와 이번 작업에서 적용됐는지를 분리한다. -- `no-new-guardrail`을 고르면 `Decision rationale`에 왜 추가 guardrail이 필요 없는지 반드시 적는다. - -## Minimal Example - -```md -## Guardrail Ledger Entry -- Date: `2026-04-07` -- Task / Spec: `docs/specs/completed/2026-04-07-sample.md` -- Stage: - - `Reviewing` -- Failure class: - - `review-handoff` -- Trigger signal: reviewer input에 latest verification evidence 요약이 비어 있었다. -- Root cause: review handoff minimum이 close-out 형식에서 강제되지 않았다. -- Existing guardrail: `docs/operations/dual-agent-review-policy.md` -- Recurrence: - - `repeated` -- Current issue disposition: - - `follow-up-needed` -- Promotion decision: - - `template` -- Guardrail status: - - `follow-up-created` -- Decision rationale: structured field가 비어도 PR close-out이 진행돼 handoff 누락이 반복됐다. -- Guardrail change or follow-up asset: `.github/PULL_REQUEST_TEMPLATE.md` follow-up -- Owner / next action: template 정렬 작업에서 reviewer input block을 강화한다. -- Evidence: PR close-out draft와 review note -- Notes: policy는 이미 있었고, 입력 강제가 부족했다. -``` diff --git a/docs/operations/quality-sweep-report-template.md b/docs/operations/quality-sweep-report-template.md deleted file mode 100644 index d4ecacc..0000000 --- a/docs/operations/quality-sweep-report-template.md +++ /dev/null @@ -1,64 +0,0 @@ -# Quality Sweep Report Template - -이 문서는 [continuous-quality-feedback-loop.md](continuous-quality-feedback-loop.md)의 canonical quality sweep report 형식을 제공한다. cleanup candidate, guardrail follow-up, repair-now handoff는 최소한 아래 필드를 포함한 report 하나로 남긴다. - -## Report Template - -```md -## Quality Sweep Report -- Date: -- Trigger mode: - - `post-closeout | scheduled | targeted` -- Source repo: -- Source task / PR / baseline: -- Scan scope: -- Detection surface: -- Signal class: - - `coding-rule-drift | duplication-drift | unused-code-drift` -- Trigger signal: -- Root cause hypothesis: -- Severity: - - `blocking | non-blocking` -- Existing guardrail: -- Disposition: - - `repair-now | cleanup-pr-candidate | guardrail-follow-up | no-action` -- Follow-up asset / issue / PR: -- Owner / next action: -- Evidence: -- Notes: -``` - -## Writing Rules - -- report 하나에는 signal class 하나만 적는다. -- `Trigger signal`은 symptom이고, `Root cause hypothesis`는 왜 이 quality drift가 다시 생길 수 있는지에 대한 가설이다. -- `Detection surface`에는 command, grep, review note, repo-specific detector 중 실제로 쓴 경로를 적는다. -- `Existing guardrail`에는 이미 있던 lint rule, docs rule, CI gate, detector가 있으면 적고, 없으면 `없음`이라고 적는다. -- `cleanup-pr-candidate`를 고르면 follow-up asset에 대상 issue, PR candidate, spec path 중 최소 하나를 적는다. -- `no-action`을 고르면 `Notes`에 왜 지금은 follow-up을 만들지 않는지 적는다. - -## Minimal Example - -```md -## Quality Sweep Report -- Date: `2026-04-08` -- Trigger mode: - - `post-closeout` -- Source repo: `git-ranker-client` -- Source task / PR / baseline: `docs/specs/completed/2026-04-08-sample.md` -- Scan scope: `src/features/ranking` -- Detection surface: `npm run lint` warning summary와 reviewer note -- Signal class: - - `unused-code-drift` -- Trigger signal: unused ranking prefetch helper가 current route tree에서 더 이상 호출되지 않는다. -- Root cause hypothesis: route refactor 이후 stale helper cleanup이 follow-up 없이 남았다. -- Severity: - - `non-blocking` -- Existing guardrail: `없음` -- Disposition: - - `cleanup-pr-candidate` -- Follow-up asset / issue / PR: cleanup issue 초안 -- Owner / next action: request-intake로 새 cleanup issue를 시작한다. -- Evidence: lint warning, grep path inventory, reviewer note -- Notes: current issue completion verdict는 유지한다. -``` diff --git a/docs/operations/request-routing-policy.md b/docs/operations/request-routing-policy.md deleted file mode 100644 index bece78b..0000000 --- a/docs/operations/request-routing-policy.md +++ /dev/null @@ -1,103 +0,0 @@ -# Request Routing Policy - -이 문서는 사용자 요청을 `대화`, `모호한 요청`, `즉시 실행 가능한 작업`으로 분류하는 intake 규칙을 고정한다. - -이 문서의 범위는 route 결정과 handoff 조건까지다. spec-level 소크라테스 질문 규칙, approval gate, write scope, verification lock은 `sdd-spec-policy.md`가 canonical owner다. - -## Routing Decision Order - -1. 요청이 응답만으로 끝나는지 판단한다. -2. 실행 의도가 있으면 single executable requirement로 줄일 수 있는지 본다. -3. 줄일 수 있으면 `즉시 실행 가능한 작업`으로 분류하고 spec authoring으로 넘긴다. -4. 아직 줄일 수 없으면 `모호한 요청`으로 분류하고 blocker 질문을 시작한다. -5. 대화 전환, 취소, 범위 밖 요청, canonical source 부재는 `Rejected`로 닫는다. - -## Route Categories - -| Route | 선택 기준 | 즉시 해야 할 일 | 상태 전이 | -| --- | --- | --- | --- | -| `대화` | 설명, 요약, 번역, 코드 리뷰 의견처럼 응답만으로 끝난다 | 답변만 제공한다 | `Received -> Routed -> Rejected` | -| `모호한 요청` | 실행 의도는 있지만 single requirement, primary repo, 기본 완료 조건이 아직 하나로 잠기지 않는다 | source of truth를 먼저 확인하고 blocker 질문으로 줄인다 | `Received -> Routed -> Interviewing` | -| `즉시 실행 가능한 작업` | 하나의 spec으로 정의할 수 있고 source of truth로 첫 spec drafting을 시작할 수 있다 | 소크라테스 spec loop를 시작한다 | `Received -> Routed -> Spec Drafting` | - -`Rejected`는 route가 아니라 terminal close-out이다. - -## Ambiguity Signals - -아래 중 하나라도 남아 있으면 `모호한 요청`이다. - -- 대상 저장소가 둘 이상으로 해석된다. -- 하나의 요청 안에 둘 이상의 목표가 섞여 있다. -- 완료 조건이나 산출물 형태가 없다. -- 어떤 요구사항을 하나의 spec으로 고정할지 판단할 수 없다. -- canonical source 없이 새 정책을 발명해야 한다. - -아래는 ambiguity signal로 보지 않는다. - -- spec 안에서 소크라테스 질문으로 더 구체화해야 하는 일반적인 세부사항 -- tracking issue가 필요한지 여부처럼 spec authoring 단계에서 결정할 수 있는 사항 -- tool approval이나 네트워크 승인만 남아 있는 경우 - -## Immediate Execution Criteria - -아래를 만족하면 `즉시 실행 가능한 작업`이다. - -1. 지금 턴에서 답변이 아니라 실행을 원하는 것이 분명하다. -2. 하나의 spec으로 묶을 primary requirement가 있다. -3. primary repo 또는 planning repo를 하나로 고를 수 있다. -4. spec authoring을 시작할 최소한의 source of truth가 있다. -5. 추가 질문이 필요하더라도, 그 질문은 spec 안을 채우는 질문이지 "무슨 작업인지"를 새로 고르는 질문이 아니다. - -## Ambiguity Interview Policy - -### Interview Objectives - -interview 목적은 아래 다섯 가지를 잠그는 것이다. - -- single requirement -- primary repo -- expected outcome -- 실행 가능한 범위 -- spec authoring을 시작할 수 있는 최소 source - -### Interview Rules - -- 질문 전에 source of truth를 먼저 읽는다. -- 기본은 한 번에 하나의 blocker 질문이다. -- 여러 목표가 섞여 있으면 무엇을 먼저 한 spec으로 고정할지 묻는다. -- 여러 저장소가 섞여 있으면 이번 턴의 primary repo를 먼저 고정한다. -- ambiguity가 줄어들면 바로 spec authoring 단계로 넘긴다. - -### Interview Exit Conditions - -| 종료 상태 | 기준 | 다음 행동 | -| --- | --- | --- | -| `Spec Drafting` | single requirement와 primary repo가 잠겨 spec을 쓸 수 있다 | `socratic-spec-authoring`으로 넘긴다 | -| `Blocked` | 실행 의도는 유지되지만 외부 입력이나 canonical source가 없어 더 줄일 수 없다 | blocker를 남기고 멈춘다 | -| `Rejected` | 대화 전환, 취소, 범위 밖 요청, 새 정책 발명이 필요하다 | 실행을 종료한다 | - -## Rejected Semantics - -canonical close-out reason은 아래를 사용한다. - -- `conversation-only` -- `cancelled` -- `out-of-scope` -- `missing-canonical-source` -- `non-executable-after-interview` - -## Example Classification - -| 요청 | 판정 | 이유 | 다음 행동 | -| --- | --- | --- | --- | -| "`docs/README.md` 구조를 설명해줘" | `대화` | 응답만으로 끝난다 | 답변만 제공 | -| "하네스에 SDD workflow를 도입해줘" | `즉시 실행 가능한 작업` | 하나의 workflow policy 변경 spec으로 정의할 수 있다 | 소크라테스 spec loop 시작 | -| "랭킹 화면 좀 개선해줘" | `모호한 요청` | 어떤 화면과 어떤 완료 조건인지 없다 | blocker 질문 | -| "backend랑 client 인증 흐름 다 정리하고 필요한 것들 한 번에 고쳐줘" | `모호한 요청` | 여러 저장소와 여러 목표가 섞여 있다 | primary spec 후보를 먼저 고르도록 질문 | - -## Governance Hooks - -- `즉시 실행 가능한 작업`은 구현 전에 항상 spec authoring으로 들어간다. -- `모호한 요청` 단계에서는 spec, issue, PR, 파일 편집을 시작하지 않는다. -- `모호한 요청` 단계에서는 single requirement와 primary repo를 잠그는 질문만 하고, spec-level write scope나 verification detail은 미리 잠그지 않는다. -- `대화`와 `Rejected` close-out은 문서 변경 없이 종료한다. diff --git a/docs/operations/sdd-spec-policy.md b/docs/operations/sdd-spec-policy.md deleted file mode 100644 index 9010e3d..0000000 --- a/docs/operations/sdd-spec-policy.md +++ /dev/null @@ -1,141 +0,0 @@ -# SDD Spec Policy - -이 문서는 Harness가 모든 즉시 실행 가능한 작업을 Spec-Driven Development 방식으로 수행할 때 따라야 하는 공통 규칙을 고정한다. - -## Policy Invariants - -- 모든 즉시 실행 가능한 작업은 구현 전에 spec을 먼저 만든다. -- spec은 요구사항, 하위 작업, write scope, verification, tracking 결정을 함께 소유하는 단일 canonical artifact다. -- 추가 planning 문서를 따로 만들지 않는다. 더 자세한 planning이 필요하면 spec을 더 구체화한다. -- 소크라테스 모드의 기본 규칙은 `답 내놓기 금지, 먼저 캐물어라`다. -- 첫 실행 응답은 구현이나 해답이 아니라 질문, 요약, 가정 점검 중 하나여야 한다. -- Harness 판단과 사용자 승인이 모두 있어야 spec을 `Approved`로 고정한다. -- 사용자의 작업 요청 자체는 spec 초안에 대한 승인으로 간주하지 않는다. -- Harness가 approval gate를 채우기에 더 이상 blocker 질문이 없다고 판단한 뒤에만 사용자에게 spec 승인 요청을 할 수 있다. -- approval exit conditions를 통과하기 전까지 spec은 `Draft`로 본다. -- 승인 전에는 구현, issue 분해, 하위 작업 실행, publish를 시작하지 않는다. -- issue 생성은 추적이 필요할 때만 한다. spec 자체가 항상 먼저다. -- 늦게 드러난 spec defect는 repair note로 흡수하지 말고 `Spec Drafting` 또는 `Draft` 상태로 되돌린다. - -## Socratic Question Rules - -- 질문은 추측을 줄이고 결정이 필요한 공백을 닫는 용도로만 한다. -- source of truth로 닫을 수 있는 사실은 먼저 직접 확인하고, 남는 공백만 묻는다. -- 기본은 한 번에 하나의 blocker 질문이다. -- 같은 답으로 함께 닫히는 공백이 명확할 때만 한 라운드에 최대 세 개까지 묶어 묻는다. -- 각 질문에는 지금 왜 이 정보를 알아야 하는지 짧게 덧붙인다. -- 각 라운드는 숨은 전제, 용어 정의, 또는 범위 framing 중 최소 하나를 점검한다. -- framing 자체가 틀렸을 가능성이 보이면 대안 framing이나 spec split 가능성을 먼저 제기한다. -- 경계, 위험, 검증에 영향이 있는 작업은 예외, 반례, 실패 조건을 질문으로 드러낸다. -- 답을 받은 뒤에는 내부 논리 충돌이나 미잠금 결정을 짧게 요약해 다음 blocker로 넘긴다. -- 용어 정의, 성공 기준, 범위/비범위, 제약, 예외, 검증, publish 필요 여부를 우선 묻는다. -- 질문 없이 바로 설계안이나 코드를 내놓지 않는다. - -## Spec Authoring Loop - -1. 사용자 요청과 source of truth를 읽는다. -2. 실행 가능한 단일 요구사항인지 먼저 확인한다. -3. blocker 질문을 던질 때는 닫으려는 공백과 왜 지금 필요한지 함께 말한다. -4. 각 라운드마다 확정된 점, 뒤집힌 가정, 남은 공백, 논리 충돌 여부를 짧게 요약한다. -5. approval gate가 비어 있으면 즉답하지 말고 질문 루프를 계속한다. -6. Harness가 approval gate를 채우기에 충분하고 더 이상 blocker 질문이 없다고 판단하면 clarification log와 approval gate를 포함한 spec 초안을 작성한다. -7. 작업 요청 자체를 승인으로 취급하지 말고, 현재 spec 초안에 대한 사용자 승인 요청을 별도로 한다. -8. 사용자가 그 spec 초안에 명시적으로 승인했을 때만 spec 상태를 `Approved`로 올린다. -9. 구현 중 scope가 바뀌거나 verification, review, user validation에서 spec defect가 드러나면 별도 planning 문서를 만들지 말고 spec을 다시 `Draft`로 내려 재승인받아 갱신한다. - -## Approval Exit Conditions - -아래 조건이 모두 충족되어야 spec을 `Approved`로 본다. - -- problem과 goal이 한 spec으로 묶일 만큼 명확하다. -- non-goal이 적혀 있어 scope drift를 막을 수 있다. -- clarification log 또는 summary로 핵심 결정이 왜 필요한지 설명할 수 있다. -- primary repo와 write scope가 잠겨 있다. -- verification 방법을 적을 수 있다. -- 하위 작업이 필요한지와 필요하면 어떻게 쪼갤지 정해졌다. -- tracking issue/PR이 필요한지 결정됐다. -- 남은 blocker가 없거나, 남아 있는 것은 explicit risk/open question으로 승격됐다. -- Harness가 "이제 구현 가능한 spec"이라고 판단했다. -- Harness가 현재 spec 초안에 대해 사용자 승인 요청을 했다. -- 사용자가 작업 요청이 아니라 현재 spec 초안 자체를 명시적으로 승인했다. - -## Spec Minimum Shape - -모든 spec은 최소한 아래 정보를 가져야 한다. - -- task metadata -- request summary -- problem -- goals -- non-goals -- socratic clarification log -- approval gate -- assumptions and constraints -- write scope -- acceptance criteria -- verification -- delivery and tracking plan -- risks or open questions -- approval status - -## Detailed Subtask Rules - -하위 작업은 아래 중 하나라도 해당하면 spec 안에 명시한다. - -- 한 번의 구현 루프로 닫기 어렵다. -- 저장소가 둘 이상 얽힌다. -- verification이나 publish 단위가 나뉜다. -- 위험도나 ownership 분리가 필요하다. - -각 하위 작업에는 최소한 아래가 보여야 한다. - -- subtask name -- target repo -- goal -- in-scope -- done when -- verification hook -- tracking needed 여부 -- dependency 또는 execution order 필요 시 - -하위 작업을 정의했다고 해서 issue를 자동 생성하지 않는다. issue는 tracking이 필요한 경우에만 만든다. - -## Tracking Projection Rules - -- spec은 항상 만든다. -- parent issue는 cross-repo planning, 장기 추적, 외부 협업, review focus가 필요한 경우에만 만든다. -- subtask issue는 하위 작업이 독립된 owner, review, publish 단위를 가질 때만 만든다. -- publish가 필요한 구현 결과는 latest verification 뒤 open PR로 공개한다. -- spec만 정의하고 끝난 작업은 issue/PR 없이 종료할 수 있다. - -## Write Scope Rules - -spec의 `Write Scope` 섹션에는 최소한 아래가 있어야 한다. - -- primary repo -- allowed write paths -- control-plane artifact 필요 시 -- explicitly forbidden path -- network / external systems -- escalation triggers - -이 정보가 없으면 구현으로 가지 않는다. - -## Verification and Final Validation - -- verification contract profile은 spec 안에 적는다. -- verification evidence는 spec이나 별도 verification artifact에 남긴다. -- review와 feedback은 optional이지만, trigger되면 evidence를 남긴다. -- verification, review, user validation에서 spec defect가 드러나면 repair만 하지 말고 spec을 다시 `Draft`로 내려 clarification과 approval를 반복한다. -- 마지막 완료 판정은 사용자 최종 검증을 포함한다. - -## Anti-Patterns - -- spec 없이 구현부터 시작하는 것 -- 질문 대신 가정으로 빈칸을 채우는 것 -- 사용자의 작업 요청을 spec 승인으로 오인하는 것 -- blocker 질문이 남아 있는데 승인 요청부터 하는 것 -- 하위 작업이 필요한데 spec 밖 TODO나 메모로만 남기는 것 -- 추적 issue가 필요하지 않은데 관리용 artifact만 늘리는 것 -- 구현 도중 scope가 바뀌었는데 spec을 다시 승인받지 않는 것 -- late-discovered spec defect를 code repair만으로 덮고 clarification loop를 다시 열지 않는 것 diff --git a/docs/operations/tool-boundary-matrix.md b/docs/operations/tool-boundary-matrix.md deleted file mode 100644 index 23ac1f6..0000000 --- a/docs/operations/tool-boundary-matrix.md +++ /dev/null @@ -1,69 +0,0 @@ -# Tool Boundary Matrix - -이 문서는 `Context Ready -> Implementing` 구간에서 어떤 도구와 접근 범위를 허용할지 고정한다. - -## Boundary Invariants - -- 도구 경계는 프롬프트의 편의 지시보다 우선한다. -- 더 넓은 접근이 필요하면 즉흥적으로 진행하지 말고 spec을 갱신하고 다시 승인받는다. -- 모든 실행 작업은 하나의 primary repo와 하나의 active subtask를 가진다. -- read boundary는 primary context pack과 approved spec의 named input에서 시작한다. -- write boundary는 approved spec의 `Write Scope`가 canonical source다. -- network는 default allow가 아니다. -- escalation은 마지막 수단이다. -- cross-repo 작업은 기본적으로 workflow 문서 PR과 앱 코드 PR로 분리한다. - -## Task Matrix - -| Task type | Read boundary | Write boundary | Network | Escalation | Default denied | -| --- | --- | --- | --- | --- | --- | -| `workflow 문서 수정` | `git-ranker-workflow`의 `docs/`, `.github/`, `.codex/skills/`, `docs/specs/`, approved spec named input까지만 읽는다 | `git-ranker-workflow`의 문서/템플릿/skill/spec 경로만 쓴다 | GitHub issue/PR 동기화와 공식 문서 확인만 허용한다 | `gh issue/pr` 발행이나 허용 경로 안 검증 명령이 막힐 때만 | sibling app code tree eager load, app repo 수정 | -| `backend 수정` | target backend repo entry docs, approved spec에 적힌 모듈/테스트/설정까지만 읽는다 | backend repo의 허용 코드/테스트/설정 경로와 control-plane artifact만 쓴다 | GitHub/공식 문서 조회, package registry, declared verification endpoint만 허용한다 | backend build/test가 sandbox에서 막힐 때만 | frontend repo 수정, backend+frontend 동시 구현 | -| `frontend 수정` | target frontend repo entry docs, approved spec에 적힌 route/component/hook/test까지만 읽는다 | frontend repo의 허용 route/UI/state/config/test 경로와 control-plane artifact만 쓴다 | GitHub/공식 문서 조회, package registry, declared verification endpoint만 허용한다 | frontend build/typecheck/lint/dev server가 sandbox에서 막힐 때만 | backend repo 수정, unrelated UI sweep | -| `cross-repo planning` | workflow planning docs와 영향받는 각 저장소의 entry docs, 필요한 contract 문서까지만 읽는다 | workflow repo의 planning 문서, spec, issue/PR body file만 쓴다 | GitHub metadata와 공식 문서 조회만 허용한다 | planning을 닫는 데 필요한 read-only inspection만 | multi-repo code diff 동시 작성, app 구현 착수 | - -## Dangerous Command Rules - -아래는 기본적으로 dangerous command다. - -- `git reset --hard`, `git checkout --`, `git clean -fd`, branch 삭제, history rewrite, blanket `rm -rf`, force push -- 허용 경로를 넘는 repo-wide formatter, mass replace, bulk delete -- 목적과 출처가 적히지 않은 remote script 실행 - -사용자가 직접 요청한 경우가 아니면 실행하지 않는다. - -## Escalation Decision Gate - -escalation 전 아래가 모두 `yes`여야 한다. - -1. 이 명령이 현재 subtask의 완료 조건이나 verification contract에 직접 필요하다. -2. 명령 대상이 approved spec의 허용 범위 안에 있다. -3. 더 좁은 non-escalated 대안이 없다. -4. 승인 요청 문장에 목적과 범위가 한 줄로 설명된다. - -## Write Scope Template - -approved spec에는 최소한 아래 형식의 write scope가 있어야 한다. - -```md -## Write Scope -- Primary repo: `git-ranker-workflow` -- Allowed write paths: - - `docs/operations/` - - `docs/architecture/` -- Control-plane artifacts: - - `docs/specs/active/.md` -- Explicitly forbidden: - - sibling app repo code - - named scope 밖 stable source of truth -- Network / external systems: - - GitHub issue/PR metadata 확인 -- Escalation triggers: - - sandbox가 declared verification command를 막을 때 -``` - -## Boundary Failure Consequences - -- read boundary를 넘겼으면 추가 탐색을 멈추고 context pack이나 spec split이 맞는지 다시 확인한다. -- write boundary를 넘겼으면 임의로 계속 수정하지 말고 범위 축소 또는 task 분리를 먼저 한다. -- network 또는 escalation이 문서화된 목적 없이 필요해지면 spec을 갱신하기 전에는 실행하지 않는다. diff --git a/docs/operations/verification-contract-registry.md b/docs/operations/verification-contract-registry.md deleted file mode 100644 index e88b1a4..0000000 --- a/docs/operations/verification-contract-registry.md +++ /dev/null @@ -1,158 +0,0 @@ -# Verification Contract Registry - -이 문서는 `Verifying`, `Repairing`, `Blocked` semantics를 실제 실행 규약으로 고정한다. - -관련 운영 규칙은 [workflow-governance.md](workflow-governance.md), spec 기준은 [sdd-spec-policy.md](sdd-spec-policy.md), review verdict 규칙은 [dual-agent-review-policy.md](dual-agent-review-policy.md)를 따른다. - -## Registry Invariants - -- 모든 실행 작업은 `Verifying`에 들어가기 전에 정확히 하나의 primary contract profile을 approved spec에 적어야 한다. -- contract profile은 repo-specific 세부 contract를 대체하지 않는다. 대신 각 저장소가 최소한 지켜야 할 기본 entrypoint, evidence, retry, `Blocked` semantics를 제공한다. -- `passed`는 모든 required command가 성공하고 required evidence가 남았을 때만 성립한다. -- required command가 실패하면 overall status는 `failed`다. -- required command를 실행할 수 없거나 canonical source, 환경 선행조건, access boundary가 비어 있으면 overall status는 `blocked`다. -- optional 또는 conditional command는 생략 가능하지만, trigger 부재 또는 제외 이유를 evidence에 남겨야 한다. - -## Status Vocabulary - -| Level | Status | Meaning | Next state | -| --- | --- | --- | --- | -| command | `passed` | 명령이 기대한 종료 상태와 evidence를 남겼다 | 계속 진행 | -| command | `failed` | 명령이 실행됐고 현재 write scope 안에서 수리 가능하다 | `Repairing` | -| command | `blocked` | 명령을 실행할 수 없거나 현재 subtask 안에서 수리할 수 없는 선행조건이 비어 있다 | `Blocked` | -| command | `skipped` | conditional command를 trigger 부재 또는 범위 제외 사유와 함께 생략했다 | 계속 진행 | -| overall | `passed` | 모든 required command가 `passed`이고 evidence 필드가 완전하다 | `Publishing` 또는 `User Validating` | -| overall | `failed` | required command 중 하나 이상이 `failed`다 | `Repairing` | -| overall | `blocked` | required command 중 하나 이상이 `blocked`이거나 retry budget을 초과했다 | `Blocked` | - -## Contract Selection Rule - -- `workflow 문서 수정`은 `workflow-docs`를 기본으로 쓴다. -- `cross-repo planning`은 `cross-repo-planning`을 기본으로 쓴다. -- `git-ranker` 코드/테스트/설정 변경은 `backend-change`를 쓴다. -- `git-ranker-client` route/UI/state/config 변경은 `frontend-change`를 쓴다. -- 하나의 subtask에서 둘 이상의 profile이 동시에 필요해지면 contract를 합치지 말고 subtask split 또는 spec 갱신으로 되돌린다. - -## Contract Profiles - -### `workflow-docs` - -대상: - -- `git-ranker-workflow`의 `docs/`, `.github/`, `.codex/skills/`, `docs/specs/` 변경 - -Required commands: - -- `sed -n '1,p' ` -- `rg -n "<핵심 용어 또는 hook>" ` -- `git diff --check` - -Conditional commands: - -- template 또는 GitHub 본문이 바뀌면 render 확인 명령을 추가한다. - -Evidence minimum: - -- 읽은 문서 경로 -- 핵심 grep 또는 수동 검토 결과 -- `git diff --check` 결과 - -### `cross-repo-planning` - -Required commands: - -- `sed -n '1,p' ` -- impacted repo entry doc 또는 공식 remote source 확인 -- `rg -n "" ` -- `git diff --check` - -Evidence minimum: - -- impacted repo별로 읽은 entry doc 또는 remote source -- planning hook 검토 결과 -- `git diff --check` 결과 - -### `backend-change` - -Required commands: - -- `./gradlew test` -- `./gradlew build` - -Conditional commands: - -- DB, Testcontainers, runtime wiring, public API end-to-end 영향을 건드리면 `./gradlew integrationTest` -- public contract regeneration이 spec에 적혀 있으면 관련 command를 추가한다 - -Evidence minimum: - -- baseline `test`, `build` 결과 -- conditional command 실행 여부와 trigger 근거 -- 실패 시 preflight 또는 환경 상태 요약 - -### `frontend-change` - -Required commands: - -- `npm run lint` -- `npx tsc --noEmit` -- `NEXT_PUBLIC_BASE_URL=http://localhost:3000 NEXT_PUBLIC_API_URL=http://localhost:8080 npm run build` - -Conditional commands: - -- route runtime, locale redirect, browser-only behavior를 직접 바꾸면 local dev 또는 preview 확인 명령을 spec에 추가한다 - -Evidence minimum: - -- `lint`, `tsc`, `build` 결과 -- 사용한 `NEXT_PUBLIC_BASE_URL`, `NEXT_PUBLIC_API_URL` 값 -- conditional runtime 확인 여부와 사유 - -## Verification Evidence Shape - -canonical verification evidence는 spec이나 별도 verification artifact 중 최소 한 곳에 남긴다. - -### Compact Verification Summary - -```md -## Verification Summary -- Contract profile: `workflow-docs` -- Overall status: `passed` -- Ran: - - `sed -n '1,260p' docs/operations/workflow-governance.md` - - `rg -n "spec|verification evidence" docs/operations docs/architecture .codex/skills` - - `git diff --check` -- Evidence: - - 관련 source of truth hook이 함께 정렬됐다. -- Failure or skipped summary: 없음 -- Next action: user validation -``` - -### Detailed Verification Report - -```md -## Verification Report -- Contract profile: `frontend-change` -- Overall status: `passed` -- Preconditions: - - `NEXT_PUBLIC_BASE_URL=http://localhost:3000` - - `NEXT_PUBLIC_API_URL=http://localhost:8080` -- Command: `npm run lint` - - Status: `passed` - - Evidence: 성공 -- Command: `npx tsc --noEmit` - - Status: `passed` - - Evidence: 성공 -- Command: `NEXT_PUBLIC_BASE_URL=http://localhost:3000 NEXT_PUBLIC_API_URL=http://localhost:8080 npm run build` - - Status: `passed` - - Evidence: 성공 -- Failure summary: 없음 -- Next action: publish or user validation -``` - -## Repair Loop Policy - -- required command가 `failed`면 `Repairing`으로 들어간다. -- required command가 `blocked`면 `Blocked`다. -- required evidence가 비어 있거나 stale이면 verification은 끝난 것으로 보지 않는다. -- 같은 root cause가 두 번의 repair 뒤에도 남으면 더 밀어붙이지 말고 `Blocked` 또는 subtask split으로 전환한다. diff --git a/docs/operations/workflow-governance.md b/docs/operations/workflow-governance.md deleted file mode 100644 index 6496958..0000000 --- a/docs/operations/workflow-governance.md +++ /dev/null @@ -1,201 +0,0 @@ -# Workflow Governance - -이 문서는 workflow 저장소를 기준으로 모든 작업이 공유해야 하는 운영 규칙을 정의한다. - -## Reader-First Body 규칙 - -- Issue와 PR 본문은 사람이 빠르게 맥락과 판단 포인트를 이해하기 위한 reader-first 문서로 쓴다. -- 본문에는 문제, 배경, 접근, 영향, 검토 포인트, 남은 리스크처럼 사람이 바로 판단할 정보를 우선 적는다. -- 한 섹션은 가능하면 짧은 문단 하나 또는 1~3개 bullet로 끝낸다. -- raw verification command, spec 경로, 브랜치 이름, reviewer runtime dump, ledger detail은 본문 기본값으로 넣지 않는다. -- 이런 운영 상세는 spec, verification artifact, review note, feedback artifact에 남긴다. - -## 문서 링크 규칙 - -- 저장소 문서 본문에는 저장소 기준 상대경로를 사용한다. -- 로컬 도구 응답에서만 절대경로를 사용한다. - -## Stable Source Of Truth의 Task ID 규칙 - -- `docs/specs/`, GitHub Issue/PR 본문처럼 tracking이 본질인 문서는 work item ID를 직접 써도 된다. -- `docs/architecture/`, `docs/operations/` 같은 stable source of truth 문서에는 future work 설명을 위해 직접적인 work item ID를 남기지 않는다. -- stable 문서에서 후속 확장을 가리킬 때는 task ID 대신 정책, registry, skill, guardrail 같은 자산 이름을 쓴다. - -## Policy / Skill Boundary - -- `request-routing-policy.md`는 route 결정과 ambiguity exit condition만 다룬다. -- `sdd-spec-policy.md`는 Socratic question contract, clarification loop, approval gate를 다룬다. -- 이 문서는 runtime 순서, artifact/lane 운영, evidence minimum을 다룬다. -- project-local skill은 위 policy를 대체하지 않고 한 stage의 반복 workflow와 handoff만 다룬다. - -## Runtime Principles - -- 모든 즉시 실행 가능한 작업은 `request -> Socratic spec loop -> Harness no-more-questions judgment -> user approval request -> approved spec -> implementation` 순서를 따른다. -- 추가 planning 문서를 따로 만들지 않는다. 작업을 더 자세히 정의해야 하면 spec을 갱신한다. -- spec은 요구사항, 하위 작업, write scope, verification, tracking 결정을 함께 소유한다. -- verification, review, user validation에서 spec defect가 드러나면 patch로 밀어붙이지 말고 spec을 다시 `Draft`로 내려 clarification과 재승인을 반복한다. -- issue는 추적이 필요할 때만 만든다. -- PR은 publish가 필요한 결과를 latest verification 뒤 공개하는 기본 surface다. -- independent review와 feedback close-out은 중요한 통제지만 기본 happy path의 공통 선행조건은 아니다. -- 마지막 완료 판정은 사용자 최종 검증까지 포함한다. - -## Request And Spec 규칙 - -- 작업 시작 전 [request-routing-policy.md](request-routing-policy.md)로 요청을 `대화`, `모호한 요청`, `즉시 실행 가능한 작업`으로 분류한다. -- `모호한 요청`은 먼저 single executable requirement로 줄인다. -- `즉시 실행 가능한 작업`은 곧바로 구현하지 않고 [sdd-spec-policy.md](sdd-spec-policy.md)에 따라 spec을 먼저 만든다. -- 작업 요청 자체를 spec 승인으로 취급하지 않는다. -- Harness가 더 이상 blocker 질문이 없다고 판단한 뒤에만 사용자에게 현재 spec 초안의 승인 요청을 할 수 있다. -- remaining planned work도 별도 roadmap/catalog이 아니라 `docs/specs/active/`의 `Draft` spec으로 유지한다. -- 승인되지 않은 spec은 canonical source가 아니다. -- 구현 중 범위가 바뀌면 별도 planning 문서를 만들지 말고 spec을 다시 승인받는다. -- review나 user validation에서 current spec이 요구사항을 잘못 잠근 것으로 드러나면 구현 수리보다 먼저 spec을 재초안하고 다시 승인받는다. - -## Lane 규칙 - -lane은 approved spec이 만들어진 뒤에 고른다. - -| Lane | 언제 쓰나 | 기본 산출물 | 생략 가능한 것 | -| --- | --- | --- | --- | -| `default lane` | 단일 저장소, bounded scope, rollback cost 낮음, tracking 필요 없음 | approved spec, verification evidence, 필요 시 open PR | parent issue, subtask issue, independent review | -| `guarded lane` | tracked backlog, cross-repo planning, public contract/auth/schema/CI/migration/destructive change, 또는 formal tracking이 필요한 작업 | approved spec, tracking issue 필요 시, verification evidence, 필요 시 review/feedback, 필요 시 open PR | 불필요한 multi-reviewer pool | - -`guarded lane`이라도 spec이 canonical source다. issue는 spec을 대체하지 않는다. - -## Subtask 규칙 - -- 한 spec 안에서 여러 하위 작업이 필요하면 spec 안에 먼저 분해한다. -- 하위 작업 하나는 하나의 목표를 가진다. -- 저장소가 둘 이상이면 하위 작업도 저장소별로 나눈다. -- 하위 작업별 issue/PR은 tracking 필요가 있을 때만 만든다. -- current 구현 루프는 항상 하나의 active subtask만 가진다. - -## Context / Boundary 규칙 - -- 구현 전 [../architecture/context-pack-registry.md](../architecture/context-pack-registry.md)에서 primary context pack 하나를 고른다. -- [tool-boundary-matrix.md](tool-boundary-matrix.md)로 read boundary, write boundary, network, escalation을 spec 기준으로 잠근다. -- approved spec에 없는 저장소 쓰기나 broad network access가 필요해지면 spec을 먼저 갱신하고 다시 승인받는다. -- cross-repo planning은 여러 저장소를 읽을 수 있어도 app 구현은 시작하지 않는다. - -## Verification / Review / Feedback 규칙 - -- verification contract profile은 spec에 적는다. -- latest verification evidence는 spec이나 별도 verification artifact에 남긴다. -- independent review는 guarded lane, high-risk change, 또는 사용자 요청이 있을 때 수행한다. -- feedback close-out은 blocker, 반복 실패, guardrail promotion 필요성이 있을 때만 남긴다. -- quality sweep signal은 original task completion과 분리해 follow-up으로 보낸다. - -## Issue / PR 단위 규칙 - -- tracked backlog 또는 guarded lane에서는 `Issue 1개 = 목표 1개`, `PR 1개 = 목표 1개`를 유지한다. -- default lane의 작은 직접 요청은 issue 없이 진행할 수 있다. -- 하나의 PR은 하나의 목표만 해결한다. -- 한 PR에서 여러 저장소를 동시에 건드리는 경우는 피한다. -- cross-repo 작업은 workflow 문서 PR과 앱 코드 PR로 나눈다. - -## 각 Spec에 반드시 들어가야 할 내용 - -- 대상 저장소 -- 문제 또는 배경 -- 목표와 비목표 -- 소크라테스 질문으로 확정한 핵심 결정 -- approval gate 또는 그에 준하는 readiness 판단 -- write scope -- 수용 기준 -- verification 방법 -- 하위 작업 필요 시 그 분해 -- issue/PR tracking 필요 여부 -- 남아 있는 리스크나 open question - -## 각 Issue에 반드시 들어가야 할 내용 - -- target repo -- problem / background -- why now -- expected outcome -- scope / non-scope -- approach note -- impact or dependency -- risk or open question - -## 각 PR에 반드시 들어가야 할 내용 - -- linked issue 또는 no-issue reason -- 무엇이 바뀌었는지와 왜 필요한지 -- 어떤 spec 또는 subtask를 구현한 것인지 -- verification 결과 요약과 남은 공백 -- reviewer focus 필요 시 -- impact / risks - -## 증거 규칙 - -- human-facing Issue/PR 본문과 운영용 close-out artifact를 분리한다. -- verification evidence 최소 필드: - - contract profile - - overall status - - 무엇을 실행했는지 - - 핵심 evidence - - failure or skipped summary - - next action -- review evidence 최소 필드: - - implementer - - reviewer - - reviewer input - - verdict - - finding 또는 no-blocking note -- feedback evidence 최소 필드: - - stage - - failure class - - promotion decision - - follow-up asset 또는 `no-new-guardrail` 이유 -- quality sweep evidence 최소 필드: - - trigger mode - - scan scope - - signal class - - disposition - - evidence - -## GitHub Issue / PR 운영 규칙 - -- issue는 approved spec이 추적 surface를 요구할 때만 만든다. -- GitHub 본문은 먼저 파일로 작성한 뒤 `gh issue create --body-file ...` 또는 `gh pr create --body-file ...`로 보낸다. -- issue와 PR 본문은 대상 저장소의 template 형식을 따른다. -- PR은 latest verification이 끝난 뒤 open 상태로 생성한다. -- draft PR은 사용자가 명시적으로 요청했을 때만 사용한다. -- Issue/PR 본문에는 사람이 확인할 요약만 적고, raw command literal이나 detailed artifact dump는 넣지 않는다. - -## 현재 기준 실행 순서 - -1. `request-routing-policy.md`로 요청을 분류한다. -2. 즉시 실행 가능한 작업이면 소크라테스 질문으로 spec 초안을 세부화한다. -3. Harness가 approval gate를 채우기에 더 이상 blocker 질문이 없다고 판단하면 현재 spec 초안에 대한 승인 요청을 사용자에게 한다. -4. 사용자가 그 spec 초안에 명시적으로 동의하면 spec을 `Approved`로 고정한다. -5. spec이 요구하면 parent issue와 subtask issue를 만든다. -6. 현재 active subtask를 구현한다. -7. verification evidence를 남긴다. -8. publish가 필요한 결과면 open PR을 만든다. -9. review, feedback, user validation 중 spec defect가 드러나면 spec을 `Draft`로 내려 clarification loop와 재승인을 다시 연다. -10. review 또는 feedback이 trigger되면 그 evidence를 남긴다. -11. 사용자 최종 검증으로 완료를 닫는다. - -## 공통 실행 지시 - -- 이 spec 또는 active subtask의 목표만 수행한다. 범위를 넓히지 않는다. -- spec이 승인되기 전에는 구현에 들어가지 않는다. -- draft spec이 있더라도 사용자 명시적 approval이 없으면 `Approved`로 취급하지 않는다. -- 허용된 write scope 밖의 파일은 수정하지 않는다. -- 늦게 드러난 spec defect를 code repair나 reviewer note로만 덮지 않는다. -- network나 escalation이 필요하면 spec 또는 close-out artifact에 남긴다. -- verification failure가 나면 retry budget 안에서만 repair loop를 돌린다. -- source of truth 문서를 함께 업데이트하거나, 업데이트가 불필요한 이유를 남긴다. -- 동일 작업이 반복될 가능성이 높다면 skill화 여부를 검토한다. - -## 공통 Definition Of Done - -- 왜 이 작업이 필요한지 approved spec에 남아 있다. -- 변경 범위가 한 가지 목표 또는 active subtask에 집중되어 있다. -- verification 결과가 spec, verification artifact, PR 중 적절한 위치에 남아 있다. -- publish가 필요했다면 latest verification을 반영한 open PR이 생성됐거나, publish하지 않은 이유가 명시되어 있다. -- review나 feedback이 실제로 필요했다면 그 evidence가 남아 있다. -- 사용자 최종 검증이 완료되었거나 follow-up 필요성이 명시되어 있다. -- source of truth 문서가 함께 업데이트되었거나 불필요 사유가 명시되어 있다. diff --git a/docs/runbook.md b/docs/runbook.md new file mode 100644 index 0000000..500631e --- /dev/null +++ b/docs/runbook.md @@ -0,0 +1,70 @@ +# Runbook + +## 초기 설정 + +```bash +python3 scripts/workflow.py init +python3 scripts/workflow.py doctor +python3 -m unittest discover -s tests -v +``` + +`init`가 로컬 git 저장소의 `core.hooksPath`를 `.githooks`로 맞춘다. `doctor`는 이 값이 어긋나면 실패한다. + +## 새 작업 시작 + +```bash +python3 scripts/workflow.py new task-001 --title "..." --primary-repo git-ranker-workflow +``` + +이후 `workflows/tasks/task-001/spec.md`를 소크라테스 질문으로 잠근다. `Socratic Clarification Log`는 아래처럼 `Q/A/Decision` triplet만 사용한다. + +```md +- Q: phase canonical source는 무엇인가? +- A: executable plan은 prose가 아니라 JSON이어야 한다. +- Decision: `phases.json`만 canonical plan으로 쓴다. +``` + +사용자가 현재 spec 초안에 명시적으로 동의하면 approve 한다. 이때 `approve`가 `spec.md`를 `task.json.intake`로 잠근다. + +```bash +python3 scripts/workflow.py approve task-001 --note "사용자 승인 요약" +``` + +## Phase 고정 + +phase 정의는 명시적 JSON 입력으로만 적재한다. + +```bash +python3 scripts/workflow.py plan task-001 --from /tmp/task-001-phases.json +cat /tmp/task-001-phases.json | python3 scripts/workflow.py plan task-001 --stdin +``` + +## 실행과 검증 + +```bash +python3 scripts/workflow.py run task-001 --start +python3 scripts/workflow.py run task-001 --complete --changed-path scripts/workflow.py --changed-path tests/test_workflow_cli.py +python3 scripts/workflow.py verify task-001 +python3 scripts/workflow.py review task-001 --note "review ready" +python3 scripts/workflow.py review task-001 --close --user-validation-note "validated" +``` + +## Repair / Reopen + +실패, block, review follow-up, 추가 요구사항이 들어오면 `reopen`으로 다시 `approved` 상태로 되돌린다. 추가 요구사항이면 `spec.md`를 다시 잠근 뒤 `approve`를 다시 실행하고, 그 다음 `plan`으로 phase를 갱신한다. + +```bash +python3 scripts/workflow.py reopen task-001 --note "추가 요구사항 반영" +python3 scripts/workflow.py approve task-001 --note "변경된 spec 재승인" +``` + +## 점검 명령 + +```bash +python3 scripts/workflow.py status task-001 +python3 scripts/workflow.py status --all --check +python3 scripts/workflow.py doctor +python3 scripts/workflow.py hook pre_command --command-text "git push origin feature" +python3 scripts/workflow.py hook pre_commit --staged +python3 scripts/workflow.py hook pre_push --command-text "git push origin feature" +``` diff --git a/docs/runtime.md b/docs/runtime.md new file mode 100644 index 0000000..47d527a --- /dev/null +++ b/docs/runtime.md @@ -0,0 +1,70 @@ +# Runtime + +## Canonical Order + +1. 요청 수신 +2. 소크라테스 질문으로 `spec.md` 정의 +3. 사용자가 현재 spec 초안에 명시적으로 동의하면 `approve`로 승인 고정 +4. `approve`가 `spec.md`를 `task.json.intake`로 잠금 +5. `plan`으로 `phases.json` 적재 +6. active phase 실행 +7. `run --complete`로 phase closure +8. `verify`로 acceptance command 실행 +9. 모든 phase 완료 후 `review` +10. `review --close`로 user validation과 완료 고정 +11. 필요하면 `reopen`으로 repair loop 재개 + +## Task States + +- `draft` +- `approved` +- `in_progress` +- `failed` +- `blocked` +- `review_ready` +- `completed` + +## Phase States + +- `pending` +- `in_progress` +- `completed` +- `failed` +- `blocked` + +## Runtime Rules + +- `approve` 전에는 `plan`, `run`, `verify`, `review`를 허용하지 않는다. +- `approve`는 `spec.md`의 필수 section, placeholder 제거 여부, `Socratic Clarification Log`의 `Q/A/Decision` triplet 형식을 검증한다. +- `approve`는 승인된 spec을 `task.json.intake`로 잠그며, 이후 `spec.md`를 수정했으면 `approve`를 다시 실행해 intake를 재잠가야 한다. +- `plan`은 명시적 phase 입력 없이는 실행되지 않는다. +- `plan`은 `task.json.intake`와 현재 `spec.md`가 일치할 때만 허용한다. +- `status`는 `spec.md`를 읽어 `ready_for_approval`, `clarification_count`, `unresolved_clarifications`를 JSON으로 노출한다. +- `run --complete`는 `allowed_write_paths`와 `workflows/tasks//` 밖 변경을 차단한다. +- `verify`는 active phase 또는 지정 phase의 `acceptance.commands`를 실행해 run evidence를 남긴다. +- phase completion 뒤 verification이 통과하기 전까지 `last_verified_run_id`는 비어 있어야 한다. +- `review`는 `last_verified_run_id`가 active phase와 일치하는 passed verification일 때만 허용한다. +- task-level review는 모든 phase가 `completed`일 때만 연다. +- `review --close --user-validation-note ...`만 최종 완료를 닫을 수 있다. + +## Internal Runtime Structure + +- `scripts/workflow.py`: 안정적인 CLI entrypoint +- `scripts/workflow_runtime/cli.py`: command parsing과 dispatch +- `scripts/workflow_runtime/engine.py`: 상태 전이와 실행 엔진 +- `scripts/workflow_runtime/models.py`: artifact validation과 공통 model helper +- `scripts/workflow_runtime/guards.py`: TDD, write scope, dangerous command, verification freshness guard +- `scripts/workflow_runtime/doctor.py`: repository health와 stale reference 점검 +- `scripts/workflow_runtime/git_ops.py`: staged/unpushed path 조회 + +`workflow.py`를 유지하는 이유는 사람과 AI가 단일 고정 명령 surface를 기억하는 편이 가장 효율적이기 때문이다. +`init`는 로컬 git 저장소의 `core.hooksPath`를 `.githooks`로 맞추고, `doctor`는 이 설정이 어긋나면 실패한다. + +## Retry / Blocked Semantics + +- phase 실패 시 `retry_count`를 증가시킨다. +- verification 실패도 retry에 포함한다. +- 동일한 `error_fingerprint`가 짧은 시간 안에 반복되면 Circuit Breaker가 `blocked`로 전이한다. +- `blocked`는 외부 입력, spec 재정의, phase 재계획이 필요한 상태다. +- `reopen`은 `failed`, `blocked`, `review_ready`, `completed` task를 다시 `approved`로 되돌리고 repair loop를 재시작한다. +- 추가 요구사항이면 `reopen` 뒤 `spec.md`를 다시 잠그고 `approve`를 재실행한 뒤 `plan`으로 phase를 다시 적재한다. diff --git a/docs/specs/README.md b/docs/specs/README.md deleted file mode 100644 index 4056a92..0000000 --- a/docs/specs/README.md +++ /dev/null @@ -1,54 +0,0 @@ -# Specs - -이 디렉터리는 SDD 기반 작업 spec 문서를 관리한다. - -## 목적 - -- 사용자 요구사항을 소크라테스 방식으로 명확히 정의한다. -- 별도 roadmap/catalog 없이 remaining work도 `Draft` spec으로 같은 surface에 남긴다. -- 각 clarification round에서 왜 물었는지와 어떤 공백이 닫혔는지 남긴다. -- 하위 작업이 필요하면 같은 spec 안에서 분해한다. -- write scope, verification, tracking 결정을 한 문서에 모은다. -- 완료 후에는 completed spec을 historical record로 남긴다. - -## 디렉터리 규칙 - -- [active](active/README.md): 아직 끝나지 않은 spec 문서 -- [completed](completed/README.md): 완료되었거나 실행 없이 종료된 spec 문서 -- pending requirement seed는 `active/` 아래 `Draft` spec으로 둔다. - -## Active Spec 권장 섹션 - -- Task metadata -- Request -- Problem -- Goals -- Non-goals -- Socratic clarification log -- Approval gate -- Assumptions and constraints -- Write scope -- Acceptance criteria -- Verification -- Delivery and tracking plan -- Detailed subtasks 필요 시 -- Risks or open questions - -## Completed Spec 권장 섹션 - -- Task metadata -- Approved requirement summary -- Final change summary -- Final subtasks or delivery summary -- Verification Summary 또는 Verification Report -- Review / feedback 필요 시 -- Final user validation -- Docs updated - -## 피해야 할 것 - -- spec 밖에 별도 planning 문서를 만들어 같은 정보를 중복하는 것 -- roadmap, backlog, work-item catalog를 spec 밖 stable source로 다시 만드는 것 -- 사용자가 승인하지 않은 요구사항을 구현 단계에서 임의로 추가하는 것 -- 하위 작업이 필요한데 spec에 분해 없이 구현부터 시작하는 것 -- late-discovered spec defect를 clarification log나 재승인 없이 repair 메모로만 덮는 것 diff --git a/docs/specs/active/2026-04-14-git-ranker-tdd-skills.md b/docs/specs/active/2026-04-14-git-ranker-tdd-skills.md deleted file mode 100644 index 3493038..0000000 --- a/docs/specs/active/2026-04-14-git-ranker-tdd-skills.md +++ /dev/null @@ -1,146 +0,0 @@ -# git-ranker-tdd-skills - -- Status: `In Progress` -- Primary Repo: `git-ranker` -- Related Issue: `alexization/git-ranker#84` -- Related PR: `alexization/git-ranker#85` - -## Request Summary - -`git-ranker` 백엔드에서 테스트 코드 작성과 TDD 기반 기능 구현 요청에 사용할 repo-local `red`, `green`, `refactor` skill 3개를 정의하고 구현한다. 각 skill은 역할, 수행 목표, 작업 방식, handoff를 명확히 가져야 하며 `red -> green -> refactor` 순서를 기본 루프로 고정해야 한다. - -## Problem - -`git-ranker`는 현재 repo-local `.codex/skills/`가 없고, `src/test/` tree도 baseline reset 상태다. 이 상태에서 테스트 작성이나 기능 구현을 TDD로 진행하면 turn 역할, Spring Boot test level 선택, build-only baseline 제약 처리 방식이 매번 달라질 수 있다. - -## Goals - -- `git-ranker` repo-local skill registry와 `red`, `green`, `refactor` skill 3개를 추가한다. -- 각 skill에 TDD 단계별 목표, 입력/출력, anti-pattern, 다음 handoff를 고정한다. -- Java/Spring Boot 문맥에 맞는 test level 선택 기준과 현재 repo baseline 제약을 skill에 반영한다. -- `git-ranker/AGENTS.md`에서 테스트 작성/TDD 구현 요청 시 `red -> green -> refactor` 순서를 기본 루프로 명시한다. - -## Non-goals - -- 실제 backend 테스트 코드나 production 기능을 이번 작업에서 추가하지 않는다. -- `git-ranker/build.gradle`, `.github/workflows/`, `src/main/`, `src/test/`를 변경하지 않는다. -- workflow repo 루트에 삭제 상태로 남아 있는 기존 `red`, `green`, `refactor` skill을 복구하거나 재정렬하지 않는다. - -## Socratic Clarification Log - -### Round 1 -- Prompted gap: 이번 요청이 단일 spec으로 실행 가능한지, 그리고 primary repo가 `git-ranker`로 잠기는지 확인해야 했다. -- Why it mattered: spec, write scope, repo-local skill 위치를 잘못 잡으면 workflow repo 규칙과 backend canonical source 경계가 흐려진다. -- User answer / evidence: 사용자 요청이 `Java/Spring Boot 구조의 백엔드 레포지토리인 git-ranker`용 `red`, `green`, `refactor` skill 정의/구현을 명시했다. `git-ranker/AGENTS.md`도 현재 repo-local `.codex/skills/`가 없다고 고정한다. -- Closed gap: single requirement와 primary repo가 `git-ranker`로 잠겼다. -- Remaining blocker: 없음 - -### Round 2 -- Prompted gap: spec approval은 Harness 내부 판단만으로 성립하지 않고 사용자 명시적 동의가 별도로 필요하다는 점을 현재 artifact에 정확히 반영해야 했다. -- Why it mattered: 작업 요청과 spec 승인 의사를 같은 것으로 취급하면 SDD approval gate를 잘못 통과시킨다. -- User answer / evidence: 사용자가 "스펙 문서를 정의하는 과정은 Harness의 내부 판단만 통과되었다고 확정짓는게 아니라, 사용자의 동의도 있어야" 한다고 명시적으로 수정했다. -- Closed gap: 현재 spec은 사용자 승인 대기 상태이며 `Approved` 또는 `In Progress`로 볼 수 없다는 점이 잠겼다. -- Remaining blocker: 사용자 명시적 승인 - -### Round 3 -- Prompted gap: 현재 draft spec과 구현 결과를 tracked artifact로 publish할 수 있는지, 즉 현재 spec 초안에 대한 사용자 명시적 approval과 issue/PR tracking 요구가 확보됐는지 반영해야 했다. -- Why it mattered: approval과 tracking decision이 잠기지 않으면 issue/commit/PR 단계로 진행할 수 없다. -- User answer / evidence: 사용자가 작업한 내용들에 대해 모두 `Issue/Commit/PR`를 진행하라고 명시했다. -- Closed gap: 현재 spec 초안과 구현 결과를 사용자가 받아들였고, tracking artifact와 publish가 필요하다는 점이 잠겼다. -- Remaining blocker: 없음 - -## Assumptions And Constraints - -- 현재 `git-ranker` baseline verification command는 `./gradlew build`이며 dedicated test/integration lane이 없다. -- 새 skill은 repo-local TDD workflow를 정의하는 thin layer여야 하며 build/test baseline 자체를 바꾸지 않는다. -- skill은 future test-writing request에서 test bootstrap 필요 여부를 드러내야 하지만, 이번 작업에서 test infra를 몰래 추가하지 않는다. - -## Approval Gate - -- Problem and goal locked: Yes -- Non-goals explicit: Yes -- Primary repo and write scope locked: Yes -- Verification method locked: Yes -- Subtask split decided: Yes -- Tracking decision locked: Yes -- Remaining blockers: None - -## Write Scope - -- Primary repo: `git-ranker` -- Allowed write paths: - - `git-ranker/.codex/skills/**` - - `git-ranker/AGENTS.md` -- Control-plane artifacts: - - `docs/specs/active/2026-04-14-git-ranker-tdd-skills.md` -- Explicitly forbidden: - - `git-ranker/build.gradle` - - `git-ranker/.github/workflows/**` - - `git-ranker/src/**` - - workflow repo 루트 `.codex/skills/red|green|refactor` -- Network / external systems: 없음 -- Escalation triggers: 없음 - -## Acceptance Criteria - -- `git-ranker/.codex/skills/README.md`가 repo-local skill registry와 recommended TDD order를 설명한다. -- `git-ranker/.codex/skills/red/SKILL.md`, `green/SKILL.md`, `refactor/SKILL.md`가 각각 distinct trigger description과 단계별 workflow를 가진다. -- 세 skill 모두 `git-ranker`의 Spring Boot/JUnit 문맥과 current baseline 제약을 반영한다. -- `git-ranker/AGENTS.md`가 repo-local skill 존재와 `red -> green -> refactor` 적용 규칙을 source of truth로 설명한다. - -## Verification - -- Contract profile: `doc-and-skill-structure` -- Commands: - - `git diff --check -- docs/specs/active/2026-04-14-git-ranker-tdd-skills.md` - - `git -C git-ranker diff --check -- AGENTS.md .codex/skills/README.md .codex/skills/red/SKILL.md .codex/skills/green/SKILL.md .codex/skills/refactor/SKILL.md` - - `find git-ranker/.codex/skills -maxdepth 2 -name 'SKILL.md' | sort` - - `sed -n '1,220p' git-ranker/AGENTS.md` -- Latest evidence: - - `git diff --check -- docs/specs/active/2026-04-14-git-ranker-tdd-skills.md` -> passed - - `git -C git-ranker diff --check -- AGENTS.md .codex/skills/README.md .codex/skills/red/SKILL.md .codex/skills/green/SKILL.md .codex/skills/refactor/SKILL.md` -> passed - - `find git-ranker/.codex/skills -maxdepth 2 -name 'SKILL.md' | sort` -> `red`, `green`, `refactor` skill 파일 존재 확인 - - `sed -n '1,220p' git-ranker/AGENTS.md` -> repo-local TDD workflow와 usage order 반영 확인 - -## Delivery And Tracking Plan - -- Lane: `default lane` -- Parent issue needed: Yes -- PR needed: Yes - -## Detailed Subtasks - -1. Control-plane spec 작성 - - Target repo: `git-ranker-workflow` - - Goal: 이 작업의 requirement, write scope, verification을 잠근다. - - In-scope: active spec 1개 작성 - - Done when: implementation 전에 canonical spec이 존재한다. - - Verification hook: spec file 렌더와 diff check - - Tracking needed: No - -2. `git-ranker` repo-local skill pack 추가 - - Target repo: `git-ranker` - - Goal: `red`, `green`, `refactor` skill과 registry를 추가한다. - - In-scope: `.codex/skills/README.md`, skill 3개 - - Done when: 각 skill에 trigger, workflow, handoff, anti-pattern이 있다. - - Verification hook: file existence + diff check - - Tracking needed: No - -3. Backend entry doc 갱신 - - Target repo: `git-ranker` - - Goal: `AGENTS.md`에 repo-local skill 존재와 TDD loop 사용 규칙을 반영한다. - - In-scope: `AGENTS.md` - - Done when: repo entrypoint만 읽어도 새 skill의 존재와 사용 순서를 알 수 있다. - - Verification hook: `sed -n '1,220p' git-ranker/AGENTS.md` - - Tracking needed: No - -## Risks Or Open Questions - -- 현재 `git-ranker`는 test dependency와 dedicated CI test lane이 없으므로, 실제 첫 TDD 구현 작업은 spec 안에서 test harness bootstrap 또는 verification baseline rebuild 여부를 별도로 잠가야 할 수 있다. -- workflow repo 루트에는 삭제 상태의 generic `red`, `green`, `refactor` skill 흔적이 남아 있다. 이번 작업은 그 상태를 건드리지 않고 `git-ranker` repo-local skill만 추가한다. -- 사용자 승인 전에 구현이 먼저 진행된 절차상 오류가 있었다. 현재 변경은 draft spec 아래에 있으며, 사용자가 spec을 승인하지 않으면 완료나 publish로 진행하지 않는다. - -## Approval - -- Harness judgment: source of truth review와 latest evidence 기준으로 구현과 publish 단계로 진행 가능하다. -- User approval: 2026-04-14 대화에서 현재 작업한 내용들에 대해 issue/commit/PR 진행을 명시적으로 요청함 diff --git a/docs/specs/active/2026-04-14-harness-spec-approval-gate.md b/docs/specs/active/2026-04-14-harness-spec-approval-gate.md deleted file mode 100644 index 8a86133..0000000 --- a/docs/specs/active/2026-04-14-harness-spec-approval-gate.md +++ /dev/null @@ -1,232 +0,0 @@ -# harness-spec-approval-gate - -- Status: `In Progress` -- Primary Repo: `git-ranker-workflow` -- Related Issue: `alexization/git-ranker-workflow#91` -- Related PR: `alexization/git-ranker-workflow#92` - -## Request Summary - -Harness workflow 문서와 관련 skill에 spec 작성과 승인 순서를 더 명시적으로 고정한다. 특히, 소크라테스식 질의응답 루프를 돌며 spec을 세부화하고, Harness가 더 이상 질문이 없다고 판단한 뒤에만 사용자에게 승인 요청을 할 수 있으며, 그때 사용자가 spec에 명시적으로 동의했을 때만 `Approved`로 고정된다는 점을 stable source of truth와 relevant skill handoff에 함께 반영한다. - -## Problem - -현재 `docs/operations/sdd-spec-policy.md`, `docs/operations/workflow-governance.md`, `.codex/skills/request-intake/SKILL.md`, `.codex/skills/socratic-spec-authoring/SKILL.md`에는 pre-implementation 흐름의 일부가 적혀 있지만, 아래 순서가 policy와 skill 모두에 충분히 직접적으로 반영되어 있지 않다. - -- 소크라테스식 질의응답 루프로 spec을 세부화한다. -- Harness가 더 이상 blocker 질문이 없다고 판단할 때까지 loop를 계속한다. -- 그 다음에만 사용자에게 spec approval을 요청한다. -- 사용자가 spec 초안에 명시적으로 동의했을 때만 `Approved`로 고정한다. - -이 공백 때문에 작업 요청, spec drafting loop, Harness readiness 판단, approval request, 사용자 명시적 승인 사이의 단계가 실제 실행에서 흐려질 수 있다. - -## Goals - -- 소크라테스식 질문 루프가 approval request보다 먼저 온다는 순서를 policy에 명시한다. -- Harness가 질문이 더 없다고 판단한 뒤에만 approval request를 할 수 있다는 규칙을 추가한다. -- spec approval은 사용자의 명시적 동의가 있어야만 성립한다는 규칙을 Harness workflow 문서에 직접 추가한다. -- 관련 skill이 같은 순서를 handoff와 anti-pattern에 맞춰 따르도록 갱신한다. -- workflow-local `red`, `green`, `refactor` skill을 제거하는 현재 사용자 의도를 publish scope에 반영한다. -- workflow repo의 `git-ranker` submodule 포인터를 backend PR merge commit에 맞춰 반영한다. -- 작업 요청, spec 초안 작성, Harness readiness 판단, 사용자 approval의 차이를 문서상으로 구분한다. -- 승인 전에는 구현이나 publish로 진행할 수 없다는 runtime rule을 더 분명히 적는다. - -## Non-goals - -- request routing category를 새로 정의하지 않는다. -- repo-local AGENTS나 backend/frontend app-specific skill은 이번 작업에서 바꾸지 않는다. -- 승인 방식에 새로운 UI, tooling, automation requirement를 추가하지 않는다. - -## Socratic Clarification Log - -### Round 1 -- Prompted gap: 이번 요청이 stable source of truth 문서 수정 작업으로 바로 좁혀지는지 확인해야 했다. -- Why it mattered: session-level 메모로 끝낼지, Harness workflow 문서까지 바꿀지에 따라 primary repo와 write scope가 달라진다. -- User answer / evidence: 사용자가 "이 기준을 이번 세션에서만 유지하는게 아니라, Harness 워크플로우에 명시해줘"라고 명시했다. -- Closed gap: primary repo는 `git-ranker-workflow`이고, 목표는 Harness workflow policy 문서 수정이다. -- Remaining blocker: 없음 - -### Round 2 -- Prompted gap: 질문이 없어도 spec drafting을 시작할 수 있는지, 아니면 요구사항 ambiguity가 남아 있는지 확인해야 했다. -- Why it mattered: blocker가 없으면 draft spec을 쓸 수 있지만, 사용자 approval 전 구현은 금지되어야 한다. -- User answer / evidence: 관련 source of truth인 `docs/operations/sdd-spec-policy.md`와 `docs/operations/workflow-governance.md`를 읽은 결과, 보강할 규칙의 위치와 대상 문서가 이미 보인다. -- Closed gap: draft spec 작성까지는 추가 질문 없이 진행 가능하다. -- Remaining blocker: 사용자 명시적 spec 승인 - -### Round 3 -- Prompted gap: approval gate만이 아니라, 소크라테스식 질의응답 루프를 끝내고 approval request로 넘어가는 정확한 순서를 문서에 넣어야 했다. -- Why it mattered: "사용자 승인 필요"만 적으면 approval request를 언제 해도 되는지 모호해져, 질문 루프가 충분히 닫히기 전에 승인부터 받으려는 잘못된 흐름이 남을 수 있다. -- User answer / evidence: 사용자가 "소크라테스식 질문법을 통해 질의응답을 계속해서 루프를 돌면서 스펙문서를 세부화해나가고 Harness가 판단했을 때 더 이상 질문이 없을 때 승인 요청을 사용자에게 하고, 그때 사용자가 스펙 문서를 동의했을 때 고정"해야 한다고 명시했다. -- Closed gap: policy change는 approval semantics뿐 아니라 `question loop -> Harness no-more-questions judgment -> approval request -> user explicit approval -> Approved` 순서까지 고정해야 한다. -- Remaining blocker: 사용자 명시적 spec 승인 - -### Round 4 -- Prompted gap: 이 핵심 순서를 stable policy 문서에만 둘지, 관련 skill에도 함께 반영할지 범위를 잠가야 했다. -- Why it mattered: policy만 고치고 skill handoff가 예전 표현을 유지하면 실제 실행 단계에서 다시 드리프트가 생길 수 있다. -- User answer / evidence: 사용자가 "이 핵심 순서가 Harness 정책문서, 스킬에 모두 반영되어야합니다."라고 명시했다. -- Closed gap: 이번 작업의 범위는 policy 문서와 관련 workflow-local skill까지 포함한다. -- Remaining blocker: 사용자 명시적 spec 승인 - -### Round 5 -- Prompted gap: draft spec을 실제 문서/skill 수정 단계로 올릴 수 있는지, 즉 사용자 명시적 approval이 확보됐는지 반영해야 했다. -- Why it mattered: approval 전에는 stable source of truth와 relevant skill 수정에 들어가면 안 된다. -- User answer / evidence: 사용자가 "진행해주세요."라고 명시적으로 승인했다. -- Closed gap: 현재 draft spec에 대한 사용자 명시적 approval이 확보됐다. -- Remaining blocker: 없음 - -### Round 6 -- Prompted gap: workflow repo의 `red`, `green`, `refactor` skill을 유지할지 제거할지 current publish scope를 다시 잠가야 했다. -- Why it mattered: 이미 만든 workflow commit에는 이 세 파일을 복원한 정리 커밋이 있고, 사용자의 최신 의도와 다르면 publish 범위를 다시 맞춰야 한다. -- User answer / evidence: 사용자가 "workflow의 red/green/refactor는 제거할거야."라고 명시했다. -- Closed gap: workflow repo publish scope는 이 세 skill의 제거를 포함해야 하며, 기존 approved scope는 바뀌었으므로 spec을 다시 `Draft`로 내려야 한다. -- Remaining blocker: workflow skill 제거를 포함한 갱신 spec에 대한 사용자 명시적 승인 - -### Round 7 -- Prompted gap: workflow skill 제거를 포함한 갱신 spec으로 다시 implementation과 publish를 진행할 수 있는지 반영해야 했다. -- Why it mattered: Round 6에서 scope가 바뀌면서 spec이 다시 `Draft`가 되었기 때문에, 최신 범위에 대한 사용자 명시적 승인 없이는 진행할 수 없었다. -- User answer / evidence: 사용자가 "제거를 포함해서 진행해줘"라고 명시했다. -- Closed gap: workflow-local `red`, `green`, `refactor` 제거를 포함한 최신 spec 범위에 대한 사용자 명시적 approval이 확보됐다. -- Remaining blocker: 없음 - -### Round 8 -- Prompted gap: workflow PR에 `git-ranker` 서브모듈 포인터까지 반영할지, 반영한다면 어떤 commit을 기준으로 잠글지 current publish scope를 다시 정해야 했다. -- Why it mattered: workflow repo에서는 app repo 변경과 submodule gitlink 반영이 별도 artifact이므로, gitlink update를 추가하면 write scope와 acceptance criteria가 달라진다. -- User answer / evidence: 사용자가 "workflow의 서브 모듈인 git-ranker에 대해서도 반영해줘"라고 명시했다. GitHub metadata 기준 backend PR `alexization/git-ranker#85`는 merge되었고 merge commit은 `3552ddf7a417d24b1693fabd38313532993ed179`다. -- Closed gap: workflow publish scope는 `git-ranker` submodule 포인터를 backend merge commit `3552ddf7`로 갱신하는 작업까지 포함해야 한다. -- Remaining blocker: submodule 포인터 반영을 포함한 갱신 spec에 대한 사용자 명시적 승인 - -### Round 9 -- Prompted gap: submodule 포인터 반영을 포함한 갱신 spec으로 다시 implementation과 publish를 진행할 수 있는지 반영해야 했다. -- Why it mattered: Round 8에서 scope가 다시 넓어졌으므로 최신 범위에 대한 사용자 명시적 승인 없이는 gitlink update를 진행할 수 없었다. -- User answer / evidence: 사용자가 "진행해주세요"라고 명시적으로 승인했다. -- Closed gap: `git-ranker` submodule 포인터 반영을 포함한 최신 spec 범위에 대한 사용자 명시적 approval이 확보됐다. -- Remaining blocker: 없음 - -## Assumptions And Constraints - -- stable source of truth 수정은 `docs/operations/` 문서와 relevant workflow-local skill로 닫는다. -- spec approval에 필요한 사용자 동의는 작업 요청 자체와 구분해서 명시해야 한다. -- 구현 전 approval gate를 지키는 runtime rule과 approval exit condition을 함께 보강하는 것이 필요하다. -- 질문 루프는 blocker 질문이 없어질 때까지 반복될 수 있으며, approval request는 그 이후 단계여야 한다. -- skill은 policy를 대체하지 않고, 동일한 순서를 trigger/handoff/anti-pattern에 맞춰 얇게 반영해야 한다. -- workflow-local `red`, `green`, `refactor` 제거가 현재 publish 범위에 들어오면 skills registry와 commit scope도 함께 다시 맞춰야 한다. -- `git-ranker` submodule 포인터 반영은 backend PR head가 아니라 merge된 canonical commit `3552ddf7a417d24b1693fabd38313532993ed179`를 기준으로 한다. - -## Approval Gate - -- Problem and goal locked: Yes -- Non-goals explicit: Yes -- Primary repo and write scope locked: Yes -- Verification method locked: Yes -- Subtask split decided: Yes -- Tracking decision locked: Yes -- Remaining blockers: None - -## Write Scope - -- Primary repo: `git-ranker-workflow` -- Allowed write paths: - - `docs/operations/sdd-spec-policy.md` - - `docs/operations/workflow-governance.md` - - `git-ranker` - - `.codex/skills/red/SKILL.md` - - `.codex/skills/green/SKILL.md` - - `.codex/skills/refactor/SKILL.md` - - `.codex/skills/request-intake/SKILL.md` - - `.codex/skills/socratic-spec-authoring/SKILL.md` - - `.codex/skills/README.md` - - `docs/specs/active/2026-04-14-harness-spec-approval-gate.md` -- Control-plane artifacts: - - `docs/specs/active/2026-04-14-harness-spec-approval-gate.md` -- Explicitly forbidden: - - `docs/architecture/**` - - `git-ranker/**` - - `git-ranker-client/**` -- Network / external systems: 없음 -- Escalation triggers: 없음 - -## Acceptance Criteria - -- `docs/operations/sdd-spec-policy.md`가 소크라테스식 질의응답 루프를 통해 spec을 세부화하고, Harness가 더 이상 질문이 없다고 판단한 뒤에만 approval request를 한다는 순서를 직접 설명한다. -- `docs/operations/sdd-spec-policy.md`가 작업 요청과 사용자 명시적 spec 승인 사이의 차이를 직접적으로 설명한다. -- `docs/operations/workflow-governance.md`가 구현 시작 전에 필요한 approval gate를 runtime rule로 더 분명히 적는다. -- `.codex/skills/socratic-spec-authoring/SKILL.md`가 question loop, Harness no-more-questions judgment, approval request, user explicit approval의 순서를 handoff에 반영한다. -- `.codex/skills/request-intake/SKILL.md`가 작업 요청을 approval로 오해하지 않고 spec authoring stage handoff까지만 담당한다는 경계를 분명히 한다. -- 필요하면 `.codex/skills/README.md`가 pre-implementation 흐름에서 policy와 skill의 역할 경계를 더 분명히 설명한다. -- workflow repo의 `.codex/skills/red/SKILL.md`, `green/SKILL.md`, `refactor/SKILL.md`가 제거되고 registry에서도 제거 상태가 반영된다. -- workflow repo의 `git-ranker` gitlink가 `3552ddf7a417d24b1693fabd38313532993ed179`를 가리키도록 갱신된다. -- 문서 표현은 기존 policy/skill boundary를 깨지 않고 approval semantics만 강화한다. - -## Verification - -- Contract profile: `docs-policy-diff` -- Commands: - - `git diff --check -- docs/operations/sdd-spec-policy.md docs/operations/workflow-governance.md .codex/skills/request-intake/SKILL.md .codex/skills/socratic-spec-authoring/SKILL.md .codex/skills/README.md .codex/skills/red/SKILL.md .codex/skills/green/SKILL.md .codex/skills/refactor/SKILL.md docs/specs/active/2026-04-14-harness-spec-approval-gate.md` - - `git diff --submodule=short -- git-ranker` - - `sed -n '1,260p' docs/operations/sdd-spec-policy.md` - - `sed -n '1,260p' docs/operations/workflow-governance.md` - - `sed -n '1,240p' .codex/skills/request-intake/SKILL.md` - - `sed -n '1,260p' .codex/skills/socratic-spec-authoring/SKILL.md` - - `sed -n '1,220p' .codex/skills/README.md` - -## Delivery And Tracking Plan - -- Lane: `default lane` -- Parent issue needed: Yes -- PR needed: Yes - -## Detailed Subtasks - -1. Draft spec 고정 - - Target repo: `git-ranker-workflow` - - Goal: 요구사항, write scope, verification을 잠근다. - - In-scope: active spec 1개 - - Done when: 사용자 approval 대기 상태의 draft spec이 존재한다. - - Verification hook: spec file 존재와 내용 확인 - - Tracking needed: No - -2. Approval semantics 문서 보강 - - Target repo: `git-ranker-workflow` - - Goal: question loop에서 approval request를 거쳐 `Approved`로 고정되는 순서를 stable source of truth에 더 직접적으로 추가한다. - - In-scope: `docs/operations/sdd-spec-policy.md`, `docs/operations/workflow-governance.md` - - Done when: 사용자 승인 후에 문서 수정과 verification이 완료된다. - - Verification hook: policy diff check - - Tracking needed: No - -3. Relevant skill handoff 보강 - - Target repo: `git-ranker-workflow` - - Goal: pre-implementation 단계의 workflow-local skill이 같은 순서를 얇게 반영하게 한다. - - In-scope: `.codex/skills/request-intake/SKILL.md`, `.codex/skills/socratic-spec-authoring/SKILL.md`, 필요 시 `.codex/skills/README.md` - - Done when: skill 설명이 policy와 충돌하지 않고 같은 gate 순서를 따른다. - - Verification hook: skill diff check - - Tracking needed: No - -4. Legacy workflow TDD skill 제거 - - Target repo: `git-ranker-workflow` - - Goal: workflow-local `red`, `green`, `refactor` skill을 제거하고 registry를 정리한다. - - In-scope: `.codex/skills/red/SKILL.md`, `.codex/skills/green/SKILL.md`, `.codex/skills/refactor/SKILL.md`, `.codex/skills/README.md` - - Done when: 세 skill 파일이 제거되고 workflow registry가 현재 유지 대상만 설명한다. - - Verification hook: file deletion + registry diff - - Tracking needed: Yes - -5. Backend submodule pointer 반영 - - Target repo: `git-ranker-workflow` - - Goal: workflow repo의 `git-ranker` gitlink를 backend merge commit으로 올린다. - - In-scope: root gitlink path `git-ranker` - - Done when: workflow repo gitlink가 `3552ddf7a417d24b1693fabd38313532993ed179`를 가리킨다. - - Verification hook: `git diff --submodule=short -- git-ranker` - - Tracking needed: Yes - -## Risks Or Open Questions - -- 사용자 approval 표현을 얼마나 엄격하게 정의할지 문구가 과도하면 작은 직접 요청에서도 운영 마찰이 커질 수 있다. -- request와 approval를 분리하는 표현을 강화하되, blocker 질문이 없는 경우 draft spec 작성까지는 허용된다는 현재 흐름은 유지해야 한다. -- "더 이상 질문이 없다"는 판단이 질문을 영구히 금지하는 뜻이 아니라, 현재 approval gate를 채우기에 충분하다는 뜻임을 문서에서 과도하게 복잡하게 만들지 않고 표현해야 한다. -- skill에 policy 내용을 과하게 복제하면 thin-layer rule을 깨므로, skill은 handoff와 anti-pattern 수준까지만 반영해야 한다. -- workflow skill 제거를 현재 branch에 포함하면, 직전 복원 커밋과 publish artifact 구성을 다시 정리해야 한다. -- submodule pointer를 반영하면 workflow PR이 backend merge commit에 의존하게 되므로, target SHA를 잘못 잡으면 control-plane과 backend reference가 어긋날 수 있다. - -## Approval - -- Harness judgment: `git-ranker` submodule 포인터 반영을 포함한 최신 write scope와 acceptance criteria가 잠겼고 implementation 및 publish 단계로 다시 진행 가능하다. -- User approval: 2026-04-14 대화에서 `git-ranker` submodule 포인터 반영을 포함해 진행하라고 명시적으로 승인함 diff --git a/docs/specs/active/README.md b/docs/specs/active/README.md deleted file mode 100644 index 26ae63e..0000000 --- a/docs/specs/active/README.md +++ /dev/null @@ -1,10 +0,0 @@ -# Active Specs - -이 디렉터리는 현재 진행 중인 spec 문서를 둔다. - -- 상태는 `Draft`, `Approved`, `In Progress`, `Blocked`만 허용한다. -- `Draft` spec은 future work를 위한 seeded requirement queue 역할도 맡는다. -- 구현 전에 요구사항, 하위 작업, write scope, verification이 spec 안에 잠겨 있어야 한다. -- 진행 중이라도 spec defect가 드러나면 `Draft`로 되돌려 clarification loop와 재승인을 다시 거친다. -- 별도 roadmap/catalog 대신 이 디렉터리의 `Draft`/`Approved` spec 집합이 남은 작업을 설명한다. -- 작업이 끝나면 `Completed` 또는 `Rejected`로 갱신하고 `../completed/`로 옮긴다. diff --git a/docs/specs/completed/2026-04-09-grb-04-backend-verification-contract-reset.md b/docs/specs/completed/2026-04-09-grb-04-backend-verification-contract-reset.md deleted file mode 100644 index f847eb1..0000000 --- a/docs/specs/completed/2026-04-09-grb-04-backend-verification-contract-reset.md +++ /dev/null @@ -1,145 +0,0 @@ -# 2026-04-09-grb-04-backend-verification-contract-reset - -- Status: `Completed` -- Primary Repo: `git-ranker` -- Related Issue: `alexization/git-ranker#77` -- Related PR: `alexization/git-ranker#78` - -## Request Summary - -legacy verification surface를 backend repo 기준의 repo-local verification contract로 다시 정렬한다. - -## Problem - -`git-ranker`에는 harness 기준으로 재사용하기 어려운 legacy verification surface가 남아 있고, cleanup 이후 review에서 orphan integration lane과 pre-deploy verification gap이 드러났다. - -## Goals - -- backend verification surface를 repo-local canonical source 기준으로 정리한다. -- minimal `integrationTest` lane과 pre-deploy verification gate를 복구한다. -- legacy verification surface 제거와 current repair 범위를 함께 기록한다. - -## Non-goals - -- 새 기능 구현 -- backend production behavior 변경 -- legacy coverage/OpenAPI/Docker preflight surface 복구 - -## Socratic Clarification Summary - -- 현재 active work item을 새 spec 체계로 이관하는 작업이므로, 요구사항은 기존 issue와 active work 범위를 그대로 유지한다. -- 이번 subtask의 핵심은 legacy surface reset 자체보다 review에서 드러난 minimum verification gap repair다. - -## Assumptions And Constraints - -- primary repo와 task type은 `git-ranker` / `backend 수정`으로 고정한다. -- local Docker daemon이 없을 수 있으므로 integration lane 최종 확인은 remote CI evidence를 사용할 수 있다. - -## Approval Gate -- Problem and goal locked: `yes` -- Non-goals explicit: `yes` -- Primary repo and write scope locked: `yes` -- Verification method locked: `yes` -- Subtask split decided: `yes` -- Tracking decision locked: `yes` -- Remaining blockers: `none` - -## Write Scope -- Primary repo: `git-ranker` -- Allowed write paths: - - `README.md` - - `build.gradle` - - `.github/workflows/` - - `docs/openapi/` - - `src/test/java/com/gitranker/api/architecture/` - - `src/test/java/com/gitranker/api/docs/` - - `src/test/java/com/gitranker/api/testsupport/` -- Control-plane artifacts: - - `docs/specs/completed/2026-04-09-grb-04-backend-verification-contract-reset.md` -- Explicitly forbidden: - - `git-ranker-client` code tree - - `src/main/` production feature change - - workflow stable source of truth mass update -- Network / external systems: - - GitHub issue/PR metadata for `alexization/git-ranker` -- Escalation triggers: - - sandbox가 declared verification command를 막을 때만 - -## Acceptance Criteria - -- minimal `integrationTest` lane이 다시 executable하다. -- deploy workflow 앞단에 verification gate가 복구된다. -- legacy verification surface 제거 범위와 remaining risk가 기록된다. - -## Verification -- Contract profile: `backend-change` -- Commands: - - `./gradlew test` - - `./gradlew integrationTest` - - `./gradlew build` - - `git diff --check` - -## Delivery And Tracking Plan -- Lane: `guarded lane` -- Parent issue needed: `yes` -- PR needed: `yes` - -## Detailed Subtasks -- `legacy-verification-surface-reset` - - Target repo: `git-ranker` - - Goal: obsolete verification task, workflow, doc surface를 제거하거나 neutralize한다. - - Done when: cleanup 범위가 고정되고 repo-local baseline이 남는다. - - Verification hook: `./gradlew test`, `./gradlew build` - - Tracking needed: `yes` -- `minimum-verification-gap-repair` - - Target repo: `git-ranker` - - Goal: orphan integration lane과 pre-deploy verification gap을 복구한다. - - Done when: `integrationTest` lane과 deploy gate가 current diff 기준으로 설명 가능하다. - - Verification hook: `./gradlew integrationTest`, CI evidence 필요 시 - - Tracking needed: `no` - -## Risks Or Open Questions - -- local Docker 부재 때문에 integration lane은 remote CI evidence 의존이 남을 수 있다. -- coverage/OpenAPI/Docker preflight surface는 follow-up으로 남는다. - -## Approval -- Harness judgment: 기존 active work item을 새 spec 체계로 이관해 계속 실행 가능하다고 판단한다. -- User approval: pre-existing active tracked work를 migration 대상으로 유지한다. - -## Verification Summary - -- Contract profile: `backend-change` -- Overall status: `passed` -- Ran: - - `./gradlew test` - - `./gradlew integrationTest` - - `./gradlew build` - - `gh pr checks 78 --repo alexization/git-ranker` - - GitHub combined status for `b973b36f191d9bd9f8696a30329995bfec5da493` - - `git diff --check` -- Evidence: - - writable backend worktree에서 `./gradlew test`와 `./gradlew build`가 성공했다. - - local `./gradlew integrationTest`는 Docker daemon 부재로 Testcontainers 초기화에 실패했지만, spec의 환경 가정대로 remote CI evidence를 사용해 보완했다. - - PR #78의 `Unit and Integration Verification` check가 성공했고, pre-deploy gate 복구 commit `b973b36f191d9bd9f8696a30329995bfec5da493`의 combined status에도 success가 남아 있다. - - feature branch의 `.github/workflows/deploy.yml`은 `verify` job 뒤에 `docker` job이 이어지도록 바뀌어 deploy 앞단 gate가 복구됐다. - - `git diff --check`는 통과했다. -- Failure or skipped summary: - - local integration lane은 Docker 없는 환경이라 direct pass 증거를 만들지 못했고, remote GitHub Actions evidence로 대체했다. -- Next action: completed - -## Final Change Summary - -- 레거시 verification surface 제거 뒤 남은 gap을 메우기 위해 backend repo feature branch에 minimal `integrationTest` lane을 되살렸다. -- `.github/workflows/ci.yml`에 unit + integration verification job을 복구했다. -- `.github/workflows/deploy.yml`에 pre-deploy `verify` job을 추가해 deploy 전 검증 gate를 다시 걸었다. -- `build.gradle`에 `integrationTest` task와 필요한 test dependency surface를 되살려 `*IT` 테스트가 orphan lane으로 남지 않게 했다. - -## Final User Validation - -- 사용자는 `이어서 작업을 진행해주세요.`라고 요청해 sequenced backend follow-up continuation을 직접 승인했다. -- 이번 slice는 tracked PR/branch와 active spec close-out 정리까지 포함하며, next backend follow-up은 `GRB-07` readiness 판단으로 이어진다. - -## Docs Updated - -- `docs/specs/completed/2026-04-09-grb-04-backend-verification-contract-reset.md` diff --git a/docs/specs/completed/2026-04-09-grb-06-backend-test-ci-removal.md b/docs/specs/completed/2026-04-09-grb-06-backend-test-ci-removal.md deleted file mode 100644 index dc85e71..0000000 --- a/docs/specs/completed/2026-04-09-grb-06-backend-test-ci-removal.md +++ /dev/null @@ -1,140 +0,0 @@ -# 2026-04-09-grb-06-backend-test-ci-removal - -- Status: `Completed` -- Primary Repo: `git-ranker` -- Related Issue: `alexization/git-ranker#82` -- Related PR: `alexization/git-ranker#83` - -## Request Summary - -현 Harness 기준과 맞지 않는 backend legacy test/CI surface를 baseline reset 대상으로 제거한다. - -## Problem - -`git-ranker`에는 과거 방식의 테스트 코드와 CI 검증 레인이 남아 있다. 이 surface를 유지하면 legacy 검증 의미와 후속 재구성 방향이 동시에 존재하게 된다. - -## Goals - -- `src/test/` 아래 기존 테스트 코드와 테스트 리소스를 제거한다. -- `build.gradle`의 current test/integration surface를 baseline reset 수준으로 제거한다. -- workflow 파일은 남기되 test execution code path만 제거한다. - -## Non-goals - -- 새 테스트 코드 작성 -- 새 CI 검증 로직 도입 -- `src/main/` production 동작 변경 - -## Socratic Clarification Summary - -- 사용자는 workflow 파일 자체 삭제가 아니라 test execution lane 제거를 원한다. -- replacement test/CI는 같은 작업에 넣지 않고 follow-up으로 남긴다. - -## Assumptions And Constraints - -- baseline reset이 목적이다. -- current active work를 새 spec 체계로 이관한다. -- current `src/test/` tree는 이미 비어 있으므로, 이번 slice의 실질 변경은 residual Gradle dependency/task surface와 workflow test execution code path 제거에 집중된다. - -## Approval Gate -- Problem and goal locked: `yes` -- Non-goals explicit: `yes` -- Primary repo and write scope locked: `yes` -- Verification method locked: `yes` -- Subtask split decided: `yes` -- Tracking decision locked: `yes` -- Remaining blockers: `none` - -## Write Scope -- Primary repo: `git-ranker` -- Allowed write paths: - - `build.gradle` - - `.github/workflows/ci.yml` - - `.github/workflows/deploy.yml` - - `src/test/` -- Control-plane artifacts: - - `docs/specs/completed/2026-04-09-grb-06-backend-test-ci-removal.md` -- Explicitly forbidden: - - `git-ranker/src/main/` - - `git-ranker-client/` - - workflow stable docs mass update -- Network / external systems: - - 없음 -- Escalation triggers: - - sandbox가 declared verification command를 막을 때만 - -## Acceptance Criteria - -- backend test source/resource tree가 제거된다. -- workflow 파일은 남지만 test execution code는 제거된다. -- 후속 test/CI rebuild 전제조건이 기록된다. - -## Verification -- Contract profile: `backend-change` -- Commands: - - `./gradlew test` - - `./gradlew build` - - `git diff --check` - -## Delivery And Tracking Plan -- Lane: `guarded lane` -- Parent issue needed: `optional` -- PR needed: `yes when published` - -## Detailed Subtasks -- `test-tree-removal` - - Target repo: `git-ranker` - - Goal: legacy test source/resource tree를 제거한다. - - Done when: `src/test/` baseline이 비워진다. - - Verification hook: `./gradlew test` - - Tracking needed: `no` -- `workflow-lane-reset` - - Target repo: `git-ranker` - - Goal: CI/deploy workflow는 유지하되 test execution code path만 제거한다. - - Done when: retained workflow와 removed test lane이 함께 설명 가능하다. - - Verification hook: `./gradlew build`, `git diff --check` - - Tracking needed: `no` - -## Risks Or Open Questions - -- automated regression surface가 follow-up rebuild 전까지 거의 비게 된다. - -## Approval -- Harness judgment: 기존 active work item을 새 spec 체계로 이관해 계속 실행 가능하다고 판단한다. -- User approval: pre-existing active tracked work를 migration 대상으로 유지한다. - -## Verification Summary - -- Contract profile: `backend-change` -- Overall status: `passed` -- Ran: - - `./gradlew test` - - `./gradlew build` - - `git diff --check` in `git-ranker` - - `git diff --check` in `git-ranker-workflow` -- Evidence: - - current `src/test/` tree는 계속 비어 있고, legacy test source/resource tree가 다시 도입되지 않았다. - - `git-ranker/build.gradle`에서 legacy test dependency와 custom `integrationTest` task를 제거해 residual test/integration surface를 baseline reset 수준으로 걷어냈다. - - `git-ranker/.github/workflows/ci.yml`과 `git-ranker/.github/workflows/deploy.yml`은 workflow 파일을 유지한 채 test execution code path를 제거하고 `./gradlew build`만 실행하도록 줄였다. - - reset 뒤에도 `./gradlew test`와 `./gradlew build`가 모두 성공했고, 두 저장소에서 `git diff --check`가 통과했다. -- Failure or skipped summary: - - `./gradlew test`는 current baseline 특성상 `NO-SOURCE`로 종료된다. -- Next action: completed - -## Final Change Summary - -- backend repo의 legacy test lane은 이미 비어 있는 `src/test/` tree 위에 남아 있던 Gradle dependency/task 선언과 workflow execution path까지 함께 제거하며 닫았다. -- workflow 파일 자체는 유지해 deploy/build orchestration은 남기되, reset 대상이었던 unit/integration execution lane만 없앴다. -- 후속 test/CI rebuild는 repo-local baseline을 다시 정의하는 별도 follow-up으로 남긴다. - -## Final User Validation - -- 사용자는 `GRB-05, GRB-06을 진행해주세요.`라고 요청해 legacy test/CI surface reset continuation을 직접 승인했다. -- 이번 close-out은 existing tracked work item의 reset 목적을 유지하면서 residual build/workflow cleanup까지 포함해 마무리했다. - -## Docs Updated - -- `git-ranker/build.gradle` -- `git-ranker/.github/workflows/ci.yml` -- `git-ranker/.github/workflows/deploy.yml` -- `docs/specs/completed/2026-04-09-grb-06-backend-test-ci-removal.md` diff --git a/docs/specs/completed/2026-04-13-grb-05-backend-gc-baseline-alignment.md b/docs/specs/completed/2026-04-13-grb-05-backend-gc-baseline-alignment.md deleted file mode 100644 index 3276785..0000000 --- a/docs/specs/completed/2026-04-13-grb-05-backend-gc-baseline-alignment.md +++ /dev/null @@ -1,159 +0,0 @@ -# 2026-04-13-grb-05-backend-gc-baseline-alignment - -- Status: `Completed` -- Primary Repo: `git-ranker` -- Related Issue: `alexization/git-ranker#82` -- Related PR: `alexization/git-ranker#83` - -## Request Summary - -backend GC baseline을 workflow repo가 아니라 backend repo의 Gradle task와 GitHub Actions ownership으로 정렬한다. - -## Problem - -backend guardrail and GC baseline이 repo-local Gradle/CI ownership으로 충분히 정렬되지 않으면 workflow control plane이 quality gate까지 소유하는 것처럼 보일 수 있다. 이는 federated ownership model과 충돌한다. - -## Goals - -- backend GC baseline ownership을 repo-local Gradle task와 GitHub Actions로 고정한다. -- static analysis config와 verification entry docs의 연결을 정리한다. -- workflow repo가 상위 semantics만 소유한다는 경계를 재확인한다. - -## Non-goals - -- backend feature 변경 -- frontend repo 수정 -- workflow repo에서 backend GC를 대신 실행하는 새 surface 추가 - -## Socratic Clarification Log - -### Round 1 -- Prompted gap: former product catalog의 `GRB-05`를 product 제거 이후에도 canonical requirement로 남길 방법 -- Why it mattered: backend quality baseline alignment가 사라지면 repo-local guardrail ownership migration이 미완으로 남는다 -- User answer / evidence: - - current user request는 product task를 새로운 요구사항으로 전환하라고 명시한다 - - former catalog entry는 `build.gradle`, backend static analysis config, `.github/workflows/`, backend README 또는 verification entry docs`를 권장 write scope로 제시했다 -- Closed gap: `GRB-05`는 backend-change draft spec으로 승격한다 -- Remaining blocker: approval 시점에 exact GC surface와 retained workflow scope를 더 좁혀야 한다 - -### Round 2 -- Prompted gap: exact GC surface, retained CI scope, tracking 여부 -- Why it mattered: repo-local baseline을 어떤 Gradle/workflow/doc surface가 소유하는지 잠기지 않으면 `GRB-06`의 legacy lane reset 뒤에도 backend guardrail ownership이 다시 흐려진다 -- User answer / evidence: - - current `git-ranker/src/test/` tree는 이미 비어 있고, `./gradlew test`, `./gradlew integrationTest`, `./gradlew build`는 모두 성공하지만 test lane은 `NO-SOURCE` baseline이다 - - `git-ranker/build.gradle`은 여전히 `testImplementation`, `testRuntimeOnly`, `tasks.named('test')`, `integrationTest`를 선언하고 `.github/workflows/ci.yml`, `.github/workflows/deploy.yml`도 같은 legacy trio를 실행한다 - - `git-ranker/AGENTS.md`는 repo-local verification baseline을 여전히 `test -> integrationTest -> build`로 설명하고, 별도 backend static analysis config 파일은 현재 repo에 없다 -- Closed gap: 이번 slice의 exact GC surface는 `build.gradle`, `.github/workflows/ci.yml`, `.github/workflows/deploy.yml`, `AGENTS.md`로 잠그고, retained CI scope는 `./gradlew build` 단일 baseline으로 고정한다 -- Remaining blocker: `none` - -## Assumptions And Constraints - -- Prerequisites before approval: `GRB-04`, `GRB-07` -- backend GC baseline ownership은 repo-local이다. -- workflow repo는 상위 semantics와 evidence 규칙만 유지한다. -- current repo에는 standalone backend static analysis config가 없으므로, 이번 slice의 baseline semantics는 Gradle build surface와 GitHub Actions, `AGENTS.md`가 함께 소유한다. -- root `README.md`는 overview-only surface로 유지하고 GC baseline detail은 repo entry docs와 workflow가 소유한다. - -## Approval Gate -- Problem and goal locked: `yes` -- Non-goals explicit: `yes` -- Primary repo and write scope locked: `yes` -- Verification method locked: `yes` -- Subtask split decided: `yes` -- Tracking decision locked: `yes` -- Remaining blockers: `none` - -## Write Scope -- Primary repo: `git-ranker` -- Allowed write paths: - - `build.gradle` - - `.github/workflows/ci.yml` - - `.github/workflows/deploy.yml` - - `AGENTS.md` -- Control-plane artifacts: - - `docs/specs/completed/2026-04-13-grb-05-backend-gc-baseline-alignment.md` -- Explicitly forbidden: - - frontend repo edits - - unrelated backend production code changes -- Network / external systems: - - GitHub metadata only if future tracking requires it -- Escalation triggers: - - sandbox가 declared verification command를 막을 때만 - -## Acceptance Criteria - -- backend GC baseline ownership이 repo-local Gradle/CI surface로 설명된다. -- `build.gradle`, workflow, `AGENTS.md`가 같은 baseline semantics를 따른다. -- standalone backend static analysis config가 없는 current state를 새 surface 없이 설명한다. -- workflow repo가 backend GC implementation detail을 소유하지 않는다. - -## Verification -- Contract profile: `backend-change` -- Commands: - - `./gradlew build` - - `git diff --check` - -## Delivery And Tracking Plan -- Lane: `guarded lane` -- Parent issue needed: `no` -- PR needed: `conditional` - -## Detailed Subtasks -- `backend-gc-surface-inventory` - - Target repo: `git-ranker` - - Goal: current GC/static analysis/CI surface를 inventory하고 repo-local ownership 범위를 잠근다. - - In-scope: build task, static analysis config, workflow references - - Done when: retained baseline surface가 설명 가능하다. - - Verification hook: config and workflow review - - Tracking needed: `no` -- `repo-local-baseline-alignment` - - Target repo: `git-ranker` - - Goal: backend GC baseline을 repo-local entry docs와 함께 정렬한다. - - In-scope: verification docs, CI wording, retained tasks - - Done when: workflow repo 없이도 backend GC baseline을 해석할 수 있다. - - Verification hook: declared commands + doc review - - Tracking needed: `conditional` - -## Risks Or Open Questions - -- current backend repo의 GC surface가 legacy lane과 섞여 있으면 baseline reset 또는 split spec이 더 필요할 수 있다. - -## Approval -- Harness judgment: actual Gradle/workflow/doc surface, retained CI scope, tracking decision이 잠겨 repo-local baseline alignment slice로 바로 실행 가능하다. -- User approval: current user request가 `GRB-05`, `GRB-06` sequenced backend continuation을 직접 요청했다. - -## Verification Summary - -- Contract profile: `backend-change` -- Overall status: `passed` -- Ran: - - `./gradlew build` - - `git diff --check` in `git-ranker` - - `git diff --check` in `git-ranker-workflow` -- Evidence: - - `git-ranker/build.gradle`은 legacy test dependency와 custom `integrationTest` surface를 제거하고 current build-only baseline만 남겼다. - - `git-ranker/.github/workflows/ci.yml`과 `git-ranker/.github/workflows/deploy.yml`은 같은 baseline을 `./gradlew build` 단일 step으로 실행하도록 정렬됐다. - - `git-ranker/AGENTS.md`는 current repo-local baseline이 dedicated test/integration lane 없이 build-only라는 점과 follow-up rebuild contract를 직접 설명한다. - - backend repo와 workflow repo 모두 `git diff --check`를 통과했다. -- Failure or skipped summary: - - 없음 -- Next action: completed - -## Final Change Summary - -- backend GC baseline ownership을 `build.gradle`, GitHub Actions workflow, `AGENTS.md`가 공유하는 build-only semantics로 다시 잠갔다. -- standalone backend static analysis config가 없는 current state를 새 surface 없이 설명하고, workflow repo가 backend GC implementation detail을 대신 소유하지 않게 정리했다. -- `GRB-06`의 legacy lane reset 이후 남는 repo-local guardrail baseline을 같은 change set에서 바로 문서화해 downstream handoff ambiguity를 없앴다. - -## Final User Validation - -- 사용자는 `GRB-05, GRB-06을 진행해주세요.`라고 요청해 backend baseline reset과 repo-local baseline alignment를 같은 continuation 범위로 직접 승인했다. -- 이번 close-out으로 backend repo는 current contract 기준에서 build/workflow/entry-doc surface만으로 baseline을 해석할 수 있게 됐다. - -## Docs Updated - -- `git-ranker/build.gradle` -- `git-ranker/.github/workflows/ci.yml` -- `git-ranker/.github/workflows/deploy.yml` -- `git-ranker/AGENTS.md` -- `docs/specs/completed/2026-04-13-grb-05-backend-gc-baseline-alignment.md` diff --git a/docs/specs/completed/2026-04-13-grb-07-backend-agents-entrypoint-bootstrap.md b/docs/specs/completed/2026-04-13-grb-07-backend-agents-entrypoint-bootstrap.md deleted file mode 100644 index bc881b3..0000000 --- a/docs/specs/completed/2026-04-13-grb-07-backend-agents-entrypoint-bootstrap.md +++ /dev/null @@ -1,157 +0,0 @@ -# 2026-04-13-grb-07-backend-agents-entrypoint-bootstrap - -- Status: `Completed` -- Primary Repo: `git-ranker` -- Related Issue: `not created` -- Related PR: `not created` - -## Request Summary - -backend repo가 `AGENTS.md` entrypoint와 backend-only knowledge bootstrap surface를 repo-local canonical source로 노출하도록 정리한다. - -## Problem - -workflow가 federated handoff를 하더라도 backend repo 자체의 entrypoint와 backend-only skill/bootstrap surface가 부족하면 구현 단계의 first source가 여전히 workflow로 역류할 수 있다. - -## Goals - -- backend repo의 `AGENTS.md` entrypoint requirement를 충족시킨다. -- backend-only knowledge bootstrap surface를 repo-local로 내린다. -- verification entry docs와의 연결을 정리한다. - -## Non-goals - -- workflow repo의 control-plane 정책 재설계 -- backend production feature 변경 -- frontend repo 수정 - -## Socratic Clarification Log - -### Round 1 -- Prompted gap: former product catalog의 `GRB-07`을 spec-first requirement로 남길 방법 -- Why it mattered: backend repo-local ownership bootstrap이 사라지면 `GRW-27` 이후 실제 handoff destination이 비게 된다 -- User answer / evidence: - - current user request는 product의 task를 새 요구사항으로 남기라고 한다 - - former catalog entry는 backend entry docs, `.codex/skills/`, verification entry docs를 권장 write scope로 제시했다 -- Closed gap: `GRB-07`은 backend-change draft spec으로 승격한다 -- Remaining blocker: approval 시점에 actual backend entry docs 범위와 bootstrap minimum을 더 좁혀야 한다 - -### Round 2 -- Prompted gap: actual backend entry docs, bootstrap minimum, tracking 여부 -- Why it mattered: entry docs 범위와 bootstrap minimum이 잠기지 않으면 `AGENTS.md`가 다시 workflow 문서를 복제하거나, 반대로 handoff destination이 비어 있게 된다 -- User answer / evidence: - - `GRW-27` completed contract는 root `README.md`를 overview-only로 두고, concrete entrypoint responsibility를 `AGENTS.md`와 named entry docs에 둔다 - - `GRB-04` completed close-out으로 backend verification entry surface는 `build.gradle`, `.github/workflows/ci.yml`, `.github/workflows/deploy.yml`, `src/test/java/**/*IT.java` 기준으로 다시 설명 가능해졌다 - - current backend repo root에는 `README.md`, `build.gradle`, `settings.gradle`만 있고 `.codex/skills/`와 `AGENTS.md`는 아직 없다 -- Closed gap: 이번 slice의 exact entry docs는 root `AGENTS.md`, overview-only `README.md`, `build.gradle`, `.github/workflows/ci.yml`, `.github/workflows/deploy.yml`로 잠그고, local skill surface는 optional/no로 둔다 -- Remaining blocker: `none` - -## Assumptions And Constraints - -- Prerequisites before approval: `GRB-04`, `GRW-27` -- backend-only implementation knowledge는 workflow가 아니라 `git-ranker`가 소유한다. -- repo-local bootstrap은 verification contract와 충돌하지 않아야 한다. -- root `README.md`는 프로젝트 설명만 유지할 수 있고, concrete bootstrap은 `AGENTS.md`가 맡는다. - -## Approval Gate -- Problem and goal locked: `yes` -- Non-goals explicit: `yes` -- Primary repo and write scope locked: `yes` -- Verification method locked: `yes` -- Subtask split decided: `yes` -- Tracking decision locked: `yes` -- Remaining blockers: `none` - -## Write Scope -- Primary repo: `git-ranker` -- Allowed write paths: - - `AGENTS.md` - - `README.md` - - `build.gradle` - - `.github/workflows/ci.yml` - - `.github/workflows/deploy.yml` -- Control-plane artifacts: - - `docs/specs/completed/2026-04-13-grb-07-backend-agents-entrypoint-bootstrap.md` -- Explicitly forbidden: - - frontend repo edits - - unrelated backend production code changes -- Network / external systems: - - GitHub metadata only if future tracking requires it -- Escalation triggers: - - sandbox가 declared verification command를 막을 때만 - -## Acceptance Criteria - -- backend repo가 `AGENTS.md` entrypoint를 canonical source로 노출한다. -- backend-only knowledge bootstrap surface가 workflow 밖으로 이동한다. -- verification entry docs와 bootstrap linkage가 문서화된다. - -## Verification -- Contract profile: `backend-change` -- Commands: - - `./gradlew test` - - `./gradlew build` - - `git diff --check` - -## Delivery And Tracking Plan -- Lane: `guarded lane` -- Parent issue needed: `no` -- PR needed: `conditional` - -## Detailed Subtasks -- `backend-entrypoint-definition` - - Target repo: `git-ranker` - - Goal: backend repo의 `AGENTS.md`와 entry docs requirement를 정의하고 적용한다. - - In-scope: entrypoint wording, backend-only source list, verification/deploy entry doc linkage - - Done when: workflow handoff가 backend repo entrypoint를 first source로 가리킨다. - - Verification hook: entry doc review - - Tracking needed: `no` -- `backend-bootstrap-surface-alignment` - - Target repo: `git-ranker` - - Goal: backend-only skill/bootstrap surface를 repo-local ownership에 맞춰 정리한다. - - In-scope: no-skill fallback 결정, verification entry docs alignment - - Done when: workflow repo가 backend implementation knowledge를 다시 들고 있지 않다. - - Verification hook: doc/skill review + declared verification commands - - Tracking needed: `no` - -## Risks Or Open Questions - -- repo-local bootstrap을 너무 넓게 잡으면 thin-layer handoff contract와 경계가 다시 흐려질 수 있다. - -## Approval -- Harness judgment: exact backend entry docs, bootstrap minimum, tracking decision이 잠겨 repo-local docs slice로 바로 실행 가능하다. -- User approval: current user request가 sequenced backend follow-up continuation을 직접 요청했다. - -## Verification Summary - -- Contract profile: `backend-change` -- Overall status: `passed` -- Ran: - - `./gradlew test` - - `./gradlew build` - - `git diff --check` -- Evidence: - - backend writable worktree에 root `AGENTS.md`를 추가해 overview-only `README.md`와 분리된 concrete entrypoint를 repo-local로 노출했다. - - 새 `AGENTS.md`는 `build.gradle`, `settings.gradle`, `.github/workflows/ci.yml`, `.github/workflows/deploy.yml`, `src/test/java/**/*IT.java`, `src/main/java`, `src/main/resources`를 backend bootstrap surface로 고정했다. - - local `.codex/skills/` 부재를 명시하고, fallback first source를 `AGENTS.md`와 nearest code/test surface로 정리했다. - - backend worktree에서 `./gradlew test`와 `./gradlew build`가 모두 성공했다. - - `git diff --check`가 통과했고, backend worktree 기준 변경 파일은 새 `AGENTS.md`만 남았다. -- Failure or skipped summary: - - 없음 -- Next action: completed - -## Final Change Summary - -- backend repo root에 `AGENTS.md`를 추가해 workflow handoff 이후의 concrete entrypoint를 repo-local canonical source로 고정했다. -- root `README.md`는 overview-only로 유지하고, verification/bootstrap 상세는 `AGENTS.md`, Gradle build surface, CI/deploy workflow, test tree가 소유하도록 정리했다. -- repo-local skill surface가 없는 현재 상태를 명시하고, 구현자가 workflow 문서로 역류하지 않도록 fallback discovery order를 backend repo 안에서 닫았다. - -## Final User Validation - -- 사용자는 `이어서 작업을 진행햊수에ㅛ.`라고 요청해 `GRW-27` 후속인 backend bootstrap continuation을 직접 승인했다. -- 앞선 clarification에서 target repo root `README.md`는 프로젝트 설명만 둔다고 잠겨 있었고, 이번 close-out은 그 contract를 backend repo에 그대로 적용했다. - -## Docs Updated - -- `git-ranker/AGENTS.md` -- `docs/specs/completed/2026-04-13-grb-07-backend-agents-entrypoint-bootstrap.md` diff --git a/docs/specs/completed/2026-04-13-grc-05-frontend-verification-contract-normalization.md b/docs/specs/completed/2026-04-13-grc-05-frontend-verification-contract-normalization.md deleted file mode 100644 index 2363bb8..0000000 --- a/docs/specs/completed/2026-04-13-grc-05-frontend-verification-contract-normalization.md +++ /dev/null @@ -1,162 +0,0 @@ -# 2026-04-13-grc-05-frontend-verification-contract-normalization - -- Status: `Completed` -- Primary Repo: `git-ranker-client` -- Related Issue: `not created` -- Related PR: `not created` - -## Request Summary - -frontend repo의 verification surface를 repo-local canonical source 기준으로 정규화한다. - -## Problem - -workflow의 상위 verification semantics는 고정돼 있지만, frontend repo는 아직 자신의 build/lint/typecheck surface와 환경 전제를 repo-local verification contract로 충분히 잠그지 못했다. 이 상태에서는 workflow와 repo-local validation의 경계가 흐려진다. - -## Goals - -- frontend verification surface를 repo-local canonical source 기준으로 정리한다. -- build/lint/typecheck entrypoint와 환경 전제를 명시한다. -- 결과 해석 기준과 remaining risk를 기록한다. - -## Non-goals - -- 새 기능 구현 -- frontend route or UI behavior 변경 -- frontend CI 전면 재설계 - -## Socratic Clarification Log - -### Round 1 -- Prompted gap: former product catalog의 `GRC-05`를 spec-first workflow 안에 보존할 방법 -- Why it mattered: frontend track의 prerequisite가 사라지면 후속 `GRC-07`, `GRC-06`의 출발점이 없어진다 -- User answer / evidence: - - current user request는 product의 task를 새로운 요구사항으로 전환하라고 명시한다 - - former catalog entry는 frontend 검증 문서, build/lint/typecheck entrypoint 정리를 핵심 작업으로 남겼다 -- Closed gap: `GRC-05`는 frontend-change draft spec으로 승격한다 -- Remaining blocker: approval 시점에 exact frontend entrypoint와 environment precondition을 더 좁혀야 한다 - -### Round 2 -- Prompted gap: exact frontend entrypoints, environment assumptions, tracking 여부 -- Why it mattered: verification contract surface와 build precondition이 잠기지 않으면 후속 `GRC-07`, `GRC-06`이 다른 기준을 참조하게 된다 -- User answer / evidence: - - local `git-ranker-client/README.md`는 Node.js 20+, npm, `NEXT_PUBLIC_BASE_URL`, `NEXT_PUBLIC_API_URL`, `.env.example`, Docker Compose usage를 current entry doc으로 이미 드러낸다 - - `git-ranker-client/package.json`은 `dev`, `build`, `start`, `lint`만 노출하고 있어 `typecheck` contract surface가 아직 script로 정규화되지 않았다 - - `git-ranker-client/tsconfig.json`은 `strict: true`, `noEmit: true`라서 repo-local typecheck baseline을 `tsc --noEmit`로 고정할 수 있다 - - `git-ranker-client/.github/workflows/ci.yml`, `Dockerfile`, `docker-compose.yml`, `src/shared/lib/public-env.ts`는 `NEXT_PUBLIC_BASE_URL`, `NEXT_PUBLIC_API_URL`가 build/runtime 전제라는 점을 동일하게 가리킨다 -- Closed gap: 이번 slice의 exact verification contract surface는 `package.json`, `README.md`, `docs/verification-contract.md`, `.env.example`로 잠그고, environment precondition은 Node.js 20+, npm, required public env 2개로 고정한다 -- Remaining blocker: `none` - -## Assumptions And Constraints - -- Prerequisites before approval: 없음 -- workflow는 상위 verification semantics만 고정하고, repo-local command surface는 `git-ranker-client`가 소유한다. -- repo-local canonical source 확인 없이는 `Approved`로 올리지 않는다. -- `NEXT_PUBLIC_BASE_URL`, `NEXT_PUBLIC_API_URL`가 비어 있거나 absolute URL이 아니면 build/runtime verification은 실패해야 한다. - -## Approval Gate -- Problem and goal locked: `yes` -- Non-goals explicit: `yes` -- Primary repo and write scope locked: `yes` -- Verification method locked: `yes` -- Subtask split decided: `yes` -- Tracking decision locked: `yes` -- Remaining blockers: `none` - -## Write Scope -- Primary repo: `git-ranker-client` -- Allowed write paths: - - `README.md` - - `package.json` - - `docs/verification-contract.md` - - `.env.example` -- Control-plane artifacts: - - `docs/specs/completed/2026-04-13-grc-05-frontend-verification-contract-normalization.md` -- Explicitly forbidden: - - backend repo changes - - unrelated frontend feature work - - `.github/workflows/` 변경 -- Network / external systems: - - package registry or declared verification endpoint only when future verification requires it -- Escalation triggers: - - sandbox가 frontend verification command를 막을 때만 - -## Acceptance Criteria - -- frontend verification contract가 repo-local canonical source에서 읽힌다. -- build/lint/typecheck command와 환경 전제가 문서화된다. -- verification 결과 해석 기준과 remaining risk가 기록된다. - -## Verification -- Contract profile: `frontend-change` -- Commands: - - `npm run lint` - - `npm run typecheck` - - `npm run build` - - `git diff --check` - -## Delivery And Tracking Plan -- Lane: `guarded lane` -- Parent issue needed: `no` -- PR needed: `conditional` - -## Detailed Subtasks -- `frontend-verification-surface-inventory` - - Target repo: `git-ranker-client` - - Goal: current build/lint/typecheck surface와 환경 전제를 inventory한다. - - In-scope: repo-local entry docs, scripts, config surface 확인 - - Done when: verification contract 초안에 필요한 command set이 잠긴다. - - Verification hook: entry doc and script review - - Tracking needed: `no` -- `repo-local-contract-normalization` - - Target repo: `git-ranker-client` - - Goal: workflow semantics와 충돌하지 않는 frontend verification contract를 repo-local 문서에 정리한다. - - In-scope: verification doc wording, remaining risk 기록 - - Done when: 후속 frontend spec이 같은 contract를 재사용할 수 있다. - - Verification hook: declared commands + doc review - - Tracking needed: `conditional` - -## Risks Or Open Questions - -- 실제 frontend repo의 current script surface를 읽으면 command set이나 config path가 더 좁아질 수 있다. - -## Approval -- Harness judgment: actual script surface, env precondition, write scope, tracking decision이 잠겨 repo-local verification contract slice로 바로 실행 가능하다. -- User approval: current user request가 sequenced frontend follow-up continuation까지 포함해 실행을 직접 요청했다. - -## Verification Summary - -- Contract profile: `frontend-change` -- Overall status: `passed` -- Ran: - - `npm run lint` - - `npm run typecheck` - - `NEXT_PUBLIC_BASE_URL=http://localhost:3000 NEXT_PUBLIC_API_URL=http://localhost:8080 npm run build` - - `git diff --check` -- Evidence: - - `package.json`에 `typecheck` script를 추가해 repo-local command surface를 `npm run` contract로 정규화했다. - - `docs/verification-contract.md`를 새로 추가해 required env, command semantics, result interpretation, remaining risk를 repo-local canonical source로 분리했다. - - `README.md`는 `npm run typecheck`를 entry command로 노출하고 새 verification contract 문서를 가리키도록 정리했다. - - `npm run lint`와 `npm run typecheck`는 로컬 worktree에서 성공했다. - - `npm run build`는 sandbox 안에서 Turbopack의 local port bind 제한으로 실패했지만, 같은 command를 sandbox 밖에서 재실행해 성공했다. - - `git diff --check`가 통과했다. -- Failure or skipped summary: - - sandbox 내부 `npm run build`는 Turbopack runtime 제약 때문에 verification evidence로 채택하지 않았다. -- Next action: completed - -## Final Change Summary - -- frontend repo verification contract를 `package.json` script surface와 `docs/verification-contract.md` 기준으로 정규화했다. -- required public env 2개와 local/CI build precondition을 repo-local 문서로 분리해 후속 `GRC-07`, `GRC-06`이 재사용할 수 있게 했다. -- root `README.md`는 concrete verification contract를 직접 들고 있지 않고 repo-local verification 문서를 가리키는 얇은 entry 역할로 줄였다. - -## Final User Validation - -- 사용자는 `이어서 작업을 진행햊수에ㅛ.`라고 요청해 backend follow-up 뒤 frontend prerequisite까지 이어서 실행하는 것을 직접 승인했다. -- 이번 slice는 frontend verification contract normalizing만 다루고, CI baseline 정렬과 `AGENTS.md` entrypoint는 각각 후속 `GRC-06`, `GRC-07`에 남긴다. - -## Docs Updated - -- `git-ranker-client/README.md` -- `git-ranker-client/docs/verification-contract.md` -- `docs/specs/completed/2026-04-13-grc-05-frontend-verification-contract-normalization.md` diff --git a/docs/specs/completed/2026-04-13-grc-06-frontend-gc-baseline-alignment.md b/docs/specs/completed/2026-04-13-grc-06-frontend-gc-baseline-alignment.md deleted file mode 100644 index 223e05d..0000000 --- a/docs/specs/completed/2026-04-13-grc-06-frontend-gc-baseline-alignment.md +++ /dev/null @@ -1,161 +0,0 @@ -# 2026-04-13-grc-06-frontend-gc-baseline-alignment - -- Status: `Completed` -- Primary Repo: `git-ranker-client` -- Related Issue: `not created` -- Related PR: `not created` - -## Request Summary - -frontend GC baseline을 repo-local npm script와 GitHub Actions ownership으로 정렬한다. - -## Problem - -frontend quality baseline이 workflow repo가 아니라 frontend repo의 script/CI surface로 고정되지 않으면 federated ownership model 아래에서 guardrail 위치가 계속 흔들린다. - -## Goals - -- frontend GC baseline ownership을 repo-local npm script와 GitHub Actions로 고정한다. -- static analysis config와 verification entry docs의 연결을 정리한다. -- workflow repo는 상위 semantics만 남긴다. - -## Non-goals - -- frontend feature 구현 -- backend repo 수정 -- workflow repo에 frontend GC implementation guide를 추가하는 것 - -## Socratic Clarification Log - -### Round 1 -- Prompted gap: former product catalog의 `GRC-06`을 product 제거 후에도 남길 canonical surface -- Why it mattered: frontend track의 repo-local guardrail baseline alignment가 future work로 계속 남아 있어야 한다 -- User answer / evidence: - - current user request는 product task를 새로운 요구사항으로 전환하라고 한다 - - former catalog entry는 `package.json`, frontend static analysis config, `.github/workflows/`, frontend README 또는 repo entry docs`를 권장 write scope로 제시했다 -- Closed gap: `GRC-06`은 frontend-change draft spec으로 승격한다 -- Remaining blocker: approval 시점에 exact frontend GC surface와 retained CI scope를 더 좁혀야 한다 - -### Round 2 -- Prompted gap: exact frontend GC surface, retained CI scope, tracking 여부 -- Why it mattered: script와 CI baseline이 다른 command set을 가리키면 repo-local ownership이 다시 흔들리고 verification evidence 해석도 갈라진다 -- User answer / evidence: - - `GRC-05` completed close-out으로 `package.json`은 `lint`, `typecheck`, `build` script surface를 노출하고 `docs/verification-contract.md`가 result interpretation을 canonical source로 가진다 - - `GRC-07` completed close-out으로 `AGENTS.md`가 repo entrypoint가 됐고 root `README.md`는 overview-only로 줄었다 - - current `eslint.config.mjs`와 `tsconfig.json`은 각각 lint/typecheck baseline config surface고, `.github/workflows/ci.yml`는 아직 `lint`와 `build`만 실행해 explicit `typecheck` lane이 빠져 있다 -- Closed gap: 이번 slice의 exact GC surface는 `package.json`, `docs/verification-contract.md`, `AGENTS.md`, `eslint.config.mjs`, `tsconfig.json`, `.github/workflows/ci.yml`로 잠그고, retained CI scope는 `lint -> typecheck -> build`로 고정한다 -- Remaining blocker: `none` - -## Assumptions And Constraints - -- Prerequisites before approval: `GRC-05`, `GRC-07` -- frontend GC baseline ownership은 repo-local이다. -- workflow repo는 상위 semantics와 evidence rule만 유지한다. -- root `README.md`는 overview-only surface로 유지하고 GC baseline detail은 entry docs와 workflow가 소유한다. - -## Approval Gate -- Problem and goal locked: `yes` -- Non-goals explicit: `yes` -- Primary repo and write scope locked: `yes` -- Verification method locked: `yes` -- Subtask split decided: `yes` -- Tracking decision locked: `yes` -- Remaining blockers: `none` - -## Write Scope -- Primary repo: `git-ranker-client` -- Allowed write paths: - - `package.json` - - `eslint.config.mjs` - - `tsconfig.json` - - `.github/workflows/ci.yml` - - `docs/verification-contract.md` - - `AGENTS.md` -- Control-plane artifacts: - - `docs/specs/completed/2026-04-13-grc-06-frontend-gc-baseline-alignment.md` -- Explicitly forbidden: - - backend repo edits - - unrelated frontend route or UI changes -- Network / external systems: - - GitHub metadata only if future tracking requires it -- Escalation triggers: - - sandbox가 declared verification command를 막을 때만 - -## Acceptance Criteria - -- frontend GC baseline ownership이 repo-local npm script/CI surface로 설명된다. -- static analysis config, workflow, verification docs가 같은 baseline semantics를 따른다. -- workflow repo가 frontend GC implementation detail을 소유하지 않는다. - -## Verification -- Contract profile: `frontend-change` -- Commands: - - `npm run lint` - - `npm run typecheck` - - `npm run build` - - `git diff --check` - -## Delivery And Tracking Plan -- Lane: `guarded lane` -- Parent issue needed: `no` -- PR needed: `conditional` - -## Detailed Subtasks -- `frontend-gc-surface-inventory` - - Target repo: `git-ranker-client` - - Goal: current GC/static analysis/CI surface를 inventory하고 repo-local ownership 범위를 잠근다. - - In-scope: script, config, workflow references - - Done when: retained baseline surface가 설명 가능하다. - - Verification hook: config and workflow review - - Tracking needed: `no` -- `repo-local-baseline-alignment` - - Target repo: `git-ranker-client` - - Goal: frontend GC baseline을 repo-local entry docs와 함께 정렬한다. - - In-scope: verification docs, CI wording, retained scripts - - Done when: workflow repo 없이도 frontend GC baseline을 해석할 수 있다. - - Verification hook: declared commands + doc review - - Tracking needed: `conditional` - -## Risks Or Open Questions - -- current frontend repo의 GC surface가 실제 script/config 상태와 다르면 approval 단계에서 더 좁은 split이 필요할 수 있다. - -## Approval -- Harness judgment: actual script/config/workflow surface, retained CI scope, tracking decision이 잠겨 repo-local baseline alignment slice로 바로 실행 가능하다. -- User approval: current user request가 sequenced frontend follow-up continuation을 직접 요청했다. - -## Verification Summary - -- Contract profile: `frontend-change` -- Overall status: `passed` -- Ran: - - `npm run lint` - - `npm run typecheck` - - `NEXT_PUBLIC_BASE_URL=http://localhost:3000 NEXT_PUBLIC_API_URL=http://localhost:8080 npm run build` - - `git diff --check` -- Evidence: - - `.github/workflows/ci.yml`가 `lint -> typecheck -> build` baseline order를 explicit job chain으로 실행하도록 정렬됐다. - - `docs/verification-contract.md`는 CI가 same command trio를 실행한다고 반영했고, follow-up remaining risk를 제거했다. - - `AGENTS.md`는 repo entrypoint에서 same baseline order를 직접 가리키도록 갱신됐다. - - `npm run lint`, `npm run typecheck`, sandbox 밖 `npm run build`가 모두 성공했다. - - `git diff --check`가 통과했다. -- Failure or skipped summary: - - build는 Turbopack sandbox 제한 때문에 latest verification을 sandbox 밖에서 실행했다. -- Next action: completed - -## Final Change Summary - -- frontend GC baseline ownership을 `package.json` script surface와 `.github/workflows/ci.yml` job chain으로 고정했다. -- verification contract 문서와 `AGENTS.md`가 CI의 same baseline order를 가리키도록 맞춰 repo-local 해석 기준을 하나로 합쳤다. -- workflow repo가 frontend GC implementation detail을 들고 있지 않아도 repo-local script, config, workflow만으로 baseline을 설명할 수 있게 정리했다. - -## Final User Validation - -- 사용자는 `이어서 작업을 진행햊수에ㅛ.`라고 요청해 frontend baseline alignment까지 포함한 sequenced continuation을 직접 승인했다. -- 이번 slice로 `GRC-05`, `GRC-07`, `GRC-06`이 연속해서 닫혀 frontend repo-local verification/bootstrap baseline이 현재 contract 기준으로 정렬됐다. - -## Docs Updated - -- `git-ranker-client/AGENTS.md` -- `git-ranker-client/docs/verification-contract.md` -- `docs/specs/completed/2026-04-13-grc-06-frontend-gc-baseline-alignment.md` diff --git a/docs/specs/completed/2026-04-13-grc-07-frontend-agents-entrypoint-bootstrap.md b/docs/specs/completed/2026-04-13-grc-07-frontend-agents-entrypoint-bootstrap.md deleted file mode 100644 index 21a1cb2..0000000 --- a/docs/specs/completed/2026-04-13-grc-07-frontend-agents-entrypoint-bootstrap.md +++ /dev/null @@ -1,163 +0,0 @@ -# 2026-04-13-grc-07-frontend-agents-entrypoint-bootstrap - -- Status: `Completed` -- Primary Repo: `git-ranker-client` -- Related Issue: `not created` -- Related PR: `not created` - -## Request Summary - -frontend repo가 `AGENTS.md` entrypoint와 frontend-only knowledge bootstrap surface를 repo-local canonical source로 노출하도록 정리한다. - -## Problem - -frontend repo가 자체 entrypoint와 bootstrap surface를 충분히 갖추지 못하면 workflow에서 내려보낸 handoff가 다시 workflow 문서 의존으로 되돌아간다. 이러면 federated ownership model이 frontend track에서 실제로 작동하지 않는다. - -## Goals - -- frontend repo의 `AGENTS.md` entrypoint requirement를 충족시킨다. -- frontend-only knowledge surface를 repo-local로 내린다. -- verification entry docs와 bootstrap linkage를 정리한다. - -## Non-goals - -- backend repo 수정 -- frontend feature 구현 -- workflow control-plane 정책 재설계 - -## Socratic Clarification Log - -### Round 1 -- Prompted gap: former product catalog의 `GRC-07`을 spec-first requirement로 보존할 방법 -- Why it mattered: `GRC-05`와 `GRW-27` 뒤에 이어질 frontend bootstrap task가 product 제거와 함께 사라지면 안 된다 -- User answer / evidence: - - current user request는 product의 task를 새 요구사항으로 남기라고 요구한다 - - former catalog entry는 frontend entry docs, `.codex/skills/`, verification entry docs를 권장 write scope로 제시했다 -- Closed gap: `GRC-07`은 frontend-change draft spec으로 승격한다 -- Remaining blocker: approval 시점에 actual frontend entry docs와 bootstrap minimum을 더 좁혀야 한다 - -### Round 2 -- Prompted gap: actual frontend entry docs, bootstrap minimum, tracking 여부 -- Why it mattered: root `README.md`가 concrete entrypoint를 계속 들고 있거나 repo-local bootstrap minimum이 잠기지 않으면 workflow handoff 이후 first source가 다시 흔들린다 -- User answer / evidence: - - `GRW-27` completed contract는 target repo root `README.md`를 overview-only로 두고, concrete entrypoint responsibility를 `AGENTS.md`와 named entry docs에 둔다 - - `GRC-05` completed close-out으로 frontend verification entry surface는 `docs/verification-contract.md`, `package.json`, `.env.example`, `.github/workflows/ci.yml`, `.github/workflows/deploy.yml` 기준으로 정리됐다 - - current frontend repo에는 `.codex/skills/`가 없고, repo 구조는 `src/app`, `src/features`, `src/shared`, `src/fonts`, `docs`, `.github`가 실제 bootstrap surface다 -- Closed gap: 이번 slice의 exact entry docs는 root `AGENTS.md`, overview-only `README.md`, `docs/verification-contract.md`, `package.json`, `.env.example`, `.github/workflows/ci.yml`, `.github/workflows/deploy.yml`로 잠그고, local skill surface는 optional/no로 둔다 -- Remaining blocker: `none` - -## Assumptions And Constraints - -- Prerequisites before approval: `GRC-05`, `GRW-27` -- frontend-only knowledge surface는 `git-ranker-client`가 소유한다. -- repo-local bootstrap은 frontend verification contract와 일관돼야 한다. -- root `README.md`는 프로젝트 overview만 맡고, concrete bootstrap은 `AGENTS.md`와 named entry docs가 소유한다. - -## Approval Gate -- Problem and goal locked: `yes` -- Non-goals explicit: `yes` -- Primary repo and write scope locked: `yes` -- Verification method locked: `yes` -- Subtask split decided: `yes` -- Tracking decision locked: `yes` -- Remaining blockers: `none` - -## Write Scope -- Primary repo: `git-ranker-client` -- Allowed write paths: - - `AGENTS.md` - - `README.md` - - `docs/verification-contract.md` - - `package.json` - - `.env.example` - - `.github/workflows/ci.yml` - - `.github/workflows/deploy.yml` -- Control-plane artifacts: - - `docs/specs/completed/2026-04-13-grc-07-frontend-agents-entrypoint-bootstrap.md` -- Explicitly forbidden: - - backend repo edits - - unrelated frontend route or UI changes -- Network / external systems: - - GitHub metadata only if future tracking requires it -- Escalation triggers: - - sandbox가 declared verification command를 막을 때만 - -## Acceptance Criteria - -- frontend repo가 `AGENTS.md` entrypoint를 canonical source로 노출한다. -- frontend-only knowledge bootstrap surface가 workflow 밖으로 이동한다. -- verification entry docs와 bootstrap linkage가 문서화된다. - -## Verification -- Contract profile: `frontend-change` -- Commands: - - `npm run lint` - - `npm run typecheck` - - `npm run build` - - `git diff --check` - -## Delivery And Tracking Plan -- Lane: `guarded lane` -- Parent issue needed: `no` -- PR needed: `conditional` - -## Detailed Subtasks -- `frontend-entrypoint-definition` - - Target repo: `git-ranker-client` - - Goal: frontend repo의 `AGENTS.md`와 entry docs requirement를 정의하고 적용한다. - - In-scope: entrypoint wording, frontend-only source list - - Done when: workflow handoff가 frontend repo entrypoint를 first source로 가리킨다. - - Verification hook: entry doc review - - Tracking needed: `conditional` -- `frontend-bootstrap-surface-alignment` - - Target repo: `git-ranker-client` - - Goal: frontend-only bootstrap surface를 repo-local ownership에 맞춰 정리한다. - - In-scope: `.codex/skills/`, verification entry docs alignment - - Done when: workflow repo가 frontend implementation knowledge를 다시 들고 있지 않다. - - Verification hook: doc/skill review + declared verification commands - - Tracking needed: `no` - -## Risks Or Open Questions - -- frontend repo entrypoint가 실제 codebase 구조와 맞지 않으면 approval 단계에서 subtask split이 더 필요할 수 있다. - -## Approval -- Harness judgment: actual frontend entry docs, bootstrap minimum, no-skill fallback, tracking decision이 잠겨 repo-local docs slice로 바로 실행 가능하다. -- User approval: current user request가 sequenced frontend follow-up continuation을 직접 요청했다. - -## Verification Summary - -- Contract profile: `frontend-change` -- Overall status: `passed` -- Ran: - - `npm run lint` - - `npm run typecheck` - - `NEXT_PUBLIC_BASE_URL=http://localhost:3000 NEXT_PUBLIC_API_URL=http://localhost:8080 npm run build` - - `git diff --check` -- Evidence: - - frontend repo root에 `AGENTS.md`를 추가해 overview-only `README.md`와 분리된 concrete entrypoint를 repo-local로 노출했다. - - 새 `AGENTS.md`는 `docs/verification-contract.md`, `package.json`, `.env.example`, workflow, config, `src/app`, `src/features`, `src/shared`, `src/proxy.ts`를 bootstrap discovery order로 고정했다. - - local `.codex/skills/` 부재와 fallback first source를 `AGENTS.md` + nearest docs/config/code surface로 명시했다. - - `README.md`는 프로젝트 overview만 남기고 concrete entrypoint responsibility를 `AGENTS.md`와 named entry docs로 넘겼다. - - `npm run lint`, `npm run typecheck`, sandbox 밖 `npm run build`가 모두 성공했다. - - `git diff --check`가 통과했다. -- Failure or skipped summary: - - build는 Turbopack sandbox 제한 때문에 latest verification을 sandbox 밖에서 실행했다. -- Next action: completed - -## Final Change Summary - -- frontend repo root에 `AGENTS.md`를 추가해 workflow handoff 이후 first source를 repo-local canonical entrypoint로 고정했다. -- root `README.md`는 overview-only surface로 줄이고, verification/bootstrap 상세는 `AGENTS.md`와 named entry docs가 소유하도록 정리했다. -- local skill surface가 없는 현재 상태를 명시하고, 구현자가 workflow 문서로 역류하지 않도록 frontend repo 안에서 fallback discovery order를 닫았다. - -## Final User Validation - -- 사용자는 `이어서 작업을 진행햊수에ㅛ.`라고 요청해 frontend bootstrap continuation까지 직접 승인했다. -- 앞선 clarification에서 target repo root `README.md`는 프로젝트 설명만 둔다고 잠겨 있었고, 이번 close-out은 그 contract를 frontend repo에도 그대로 적용했다. - -## Docs Updated - -- `git-ranker-client/AGENTS.md` -- `git-ranker-client/README.md` -- `docs/specs/completed/2026-04-13-grc-07-frontend-agents-entrypoint-bootstrap.md` diff --git a/docs/specs/completed/2026-04-13-grw-26-federated-ownership-model-alignment.md b/docs/specs/completed/2026-04-13-grw-26-federated-ownership-model-alignment.md deleted file mode 100644 index 3a3e4bc..0000000 --- a/docs/specs/completed/2026-04-13-grw-26-federated-ownership-model-alignment.md +++ /dev/null @@ -1,161 +0,0 @@ -# 2026-04-13-grw-26-federated-ownership-model-alignment - -- Status: `Completed` -- Primary Repo: `git-ranker-workflow` -- Related Issue: `alexization/git-ranker-workflow#85` -- Related PR: `alexization/git-ranker-workflow#86` - -## Request Summary - -workflow control plane과 sibling app repo 사이의 federated ownership model을 stable source of truth로 정렬한다. - -## Problem - -workflow repo가 orchestration만 소유해야 한다는 원칙은 이미 여러 문서에 흩어져 있지만, ownership matrix와 thin-layer 기준이 완전히 잠겨 있지 않다. 이 간극이 남으면 workflow가 repo-local implementation knowledge를 다시 흡수할 위험이 있다. - -## Goals - -- workflow vs repo-local ownership 경계를 stable source of truth에 명시한다. -- workflow-local skill이 thin layer만 소유해야 한다는 기준을 잠근다. -- migration exit criteria와 no-duplication rule을 정리한다. - -## Non-goals - -- backend/frontend repo 실제 파일 생성 -- repo-local CI나 guardrail 구현 -- app source code 수정 - -## Socratic Clarification Log - -### Round 1 -- Prompted gap: removed product catalog의 `GRW-26`을 어떤 spec shape로 보존할지 -- Why it mattered: product 제거 후에도 ownership alignment requirement가 canonical artifact로 남아야 한다 -- User answer / evidence: - - current user request는 product의 work item을 새 요구사항으로 전환하라고 명시한다 - - former catalog entry는 `ownership matrix`, `workflow-vs-repo 책임 경계`, `thin-layer 기준`, `migration exit criteria`를 핵심 작업으로 제시했다 -- Closed gap: `GRW-26`은 workflow-docs draft spec으로 승격한다 -- Remaining blocker: approval 시점에 exact touched doc set과 skill 변경 필요 여부를 더 좁혀야 한다 - -### Round 2 -- Prompted gap: workflow-only slice로 바로 실행 가능한지, 그리고 exact touched doc set과 tracking 필요 여부가 무엇인지 -- Why it mattered: sibling repo draft spec을 기다리지 않고도 control-plane ownership vocabulary를 먼저 잠글 수 있어야 했다 -- User answer / evidence: - - current user request는 이 spec 작업을 지금 진행하라고 명시한다 - - `docs/architecture/control-plane-map.md`, `docs/architecture/harness-system-map.md`, `.codex/skills/README.md`가 이미 control-plane boundary와 skill ownership의 stable surface다 - - `docs/specs/active/2026-04-13-grw-27-federated-repo-handoff-contract.md`, `docs/specs/active/2026-04-13-grb-05-backend-gc-baseline-alignment.md`, `docs/specs/active/2026-04-13-grc-06-frontend-gc-baseline-alignment.md`는 federated ownership model을 downstream input으로 참조한다 -- Closed gap: exact touched doc set은 두 architecture 문서와 skill README, 그리고 current spec이며, 이 작업은 `workflow 문서 수정`으로 self-contained하게 실행한다 -- Remaining blocker: `none` - -## Assumptions And Constraints - -- Prerequisites before approval: 없음 -- 이 spec은 downstream repo-local spec이 재사용할 stable ownership vocabulary를 먼저 고정한다. -- 이 spec은 control-plane 문서와 workflow-local skill만 다룬다. -- app repo 구현은 follow-up spec/PR에서만 다룬다. - -## Approval Gate -- Problem and goal locked: `yes` -- Non-goals explicit: `yes` -- Primary repo and write scope locked: `yes` -- Verification method locked: `yes` -- Subtask split decided: `yes` -- Tracking decision locked: `yes` -- Remaining blockers: `none` - -## Write Scope -- Primary repo: `git-ranker-workflow` -- Allowed write paths: - - `docs/architecture/control-plane-map.md` - - `docs/architecture/harness-system-map.md` - - `.codex/skills/README.md` -- Control-plane artifacts: - - `docs/specs/completed/2026-04-13-grw-26-federated-ownership-model-alignment.md` -- Explicitly forbidden: - - sibling app repo code or docs edits - - workflow가 app canonical source를 복제하는 새 문서 추가 -- Network / external systems: - - 없음 -- Escalation triggers: - - 없음 - -## Acceptance Criteria - -- ownership matrix가 workflow orchestration과 repo-local implementation knowledge를 분리해 설명한다. -- workflow-local skill의 thin-layer 기준이 문서화된다. -- migration exit criteria와 duplication 금지 원칙이 source of truth에 반영된다. - -## Verification -- Contract profile: `workflow-docs` -- Commands: - - `sed -n '1,260p' docs/architecture/control-plane-map.md` - - `sed -n '1,260p' docs/architecture/harness-system-map.md` - - `sed -n '1,240p' .codex/skills/README.md` - - `rg -ni "federated ownership|ownership matrix|thin layer|thin-layer|no-duplication|canonical source" docs/architecture .codex/skills` - - `git diff --check` - -## Delivery And Tracking Plan -- Lane: `guarded lane` -- Parent issue needed: `no` -- PR needed: `conditional` - -## Detailed Subtasks -- `ownership-boundary-definition` - - Target repo: `git-ranker-workflow` - - Goal: workflow와 app repo 책임 경계를 stable source of truth로 잠근다. - - In-scope: ownership matrix, no-duplication rule, migration exit criteria - - Done when: control-plane 문서가 federated ownership model을 일관되게 설명한다. - - Verification hook: doc review + `rg` - - Tracking needed: `no` -- `workflow-skill-thin-layer-alignment` - - Target repo: `git-ranker-workflow` - - Goal: workflow-local skill이 orchestration handoff만 남기도록 기준을 정렬한다. - - In-scope: affected skill registry or wording updates - - Done when: skill layer가 repo-local implementation knowledge를 소유하지 않는다. - - Verification hook: `.codex/skills/README.md` and affected skill review - - Tracking needed: `no` - -## Risks Or Open Questions - -- ownership 문구를 과하게 일반화하면 후속 `GRW-27` handoff contract의 concrete requirement가 흐려질 수 있다. - -## Approval -- Harness judgment: exact touched doc set, task type, verification contract, tracking decision이 모두 잠겨 workflow-only docs slice로 실행 가능하다. -- User approval: current user request가 이 spec의 실행을 직접 요청했다. - -## Verification Summary - -- Contract profile: `workflow-docs` -- Overall status: `passed` -- Ran: - - `sed -n '1,260p' docs/architecture/control-plane-map.md` - - `sed -n '1,260p' docs/architecture/harness-system-map.md` - - `sed -n '1,240p' .codex/skills/README.md` - - `rg -ni "federated ownership|ownership matrix|thin layer|thin-layer|no-duplication|canonical source" docs/architecture .codex/skills` - - `git diff --check` -- Evidence: - - `docs/architecture/control-plane-map.md`가 federated ownership matrix, no-duplication rule, migration exit criteria를 명시한다. - - `docs/architecture/harness-system-map.md`가 workflow thin-layer invariant와 repo entrypoint handoff boundary를 명시한다. - - `.codex/skills/README.md`가 workflow-local skill을 stage procedure와 handoff 중심 thin layer로 제한한다. - - diff scope는 spec write scope 안에 머물렀고 formatting issue는 없었다. -- Failure or skipped summary: 없음 -- Next action: completed - -## Final Change Summary - -- `docs/architecture/control-plane-map.md`에 federated ownership matrix를 추가해 workflow control plane ownership, repo-local ownership, 금지 ownership을 한 표로 잠갔다. -- 같은 문서에 no-duplication rule과 migration exit criteria를 추가해 workflow가 sibling repo canonical source를 다시 복제하지 않도록 명시했다. -- `docs/architecture/harness-system-map.md`에 federated thin-layer invariant를 추가해 workflow 문서와 skill이 handoff boundary까지만 소유한다고 고정했다. -- `.codex/skills/README.md`에 thin-layer rule을 추가해 workflow-local skill이 repo-specific implementation knowledge의 stable home이 되지 않도록 정렬했다. -- current spec의 approval gate, write scope, tracking decision, verification evidence를 현재 실행 결과에 맞춰 닫았다. - -## Final User Validation - -- 사용자는 구현 요약과 verification 결과를 공유받은 뒤 `진행해주세요.`라고 답해 close-out continuation을 요청했다. -- 이번 작업은 workflow 문서 정렬 작업이므로, 추가 수정 요청 없이 close-out을 진행해도 된다는 user validation로 해석한다. - -## Docs Updated - -- `docs/architecture/control-plane-map.md` -- `docs/architecture/harness-system-map.md` -- `.codex/skills/README.md` -- `docs/specs/completed/2026-04-13-grw-26-federated-ownership-model-alignment.md` diff --git a/docs/specs/completed/2026-04-13-grw-27-federated-repo-handoff-contract.md b/docs/specs/completed/2026-04-13-grw-27-federated-repo-handoff-contract.md deleted file mode 100644 index 9d78296..0000000 --- a/docs/specs/completed/2026-04-13-grw-27-federated-repo-handoff-contract.md +++ /dev/null @@ -1,187 +0,0 @@ -# 2026-04-13-grw-27-federated-repo-handoff-contract - -- Status: `Completed` -- Primary Repo: `git-ranker-workflow` -- Related Issue: `not created` -- Related PR: `not created` - -## Request Summary - -workflow가 target repo에 넘겨야 하는 최소 handoff contract를 정의하고, 각 repo의 `AGENTS.md` entrypoint requirement를 고정한다. - -## Problem - -federated ownership model을 실제 runtime handoff로 연결하려면 workflow가 무엇을 넘기고 무엇을 넘기지 않는지, 그리고 각 repo가 어떤 entrypoint를 반드시 노출해야 하는지가 필요하다. 이 contract가 없으면 cross-repo planning 뒤 구현 단계의 first source가 흔들린다. - -## Goals - -- target repo `AGENTS.md` requirement를 정의한다. -- handoff summary shape와 local skill discovery order를 고정한다. -- repo bootstrap checklist를 정리한다. - -## Non-goals - -- backend/frontend repo에서 실제 bootstrap 수행 -- app repo 코드 변경 -- workflow 밖 새로운 orchestration surface 추가 - -## Socratic Clarification Log - -### Round 1 -- Prompted gap: removed product catalog의 `GRW-27`을 실행 가능한 spec seed로 보존할 방법 -- Why it mattered: `GRW-26` 뒤에 이어질 concrete handoff requirement가 사라지면 federation transition이 중간에서 끊긴다 -- User answer / evidence: - - current user request는 product의 남은 작업을 spec 정의부터 시작할 수 있게 남기라고 요구한다 - - former catalog entry는 `AGENTS.md` requirement, handoff summary shape, local skill discovery order, repo bootstrap checklist를 기본 결정으로 담고 있었다 -- Closed gap: `GRW-27`을 workflow-docs draft spec으로 승격한다 -- Remaining blocker: approval 시점에 handoff artifact shape와 repo bootstrap checklist의 exact minimum을 더 좁혀야 한다 - -### Round 2 -- Prompted gap: exact minimum handoff artifact, repo bootstrap checklist depth, tracking 여부 -- Why it mattered: 이 셋이 잠기지 않으면 workflow가 repo-local knowledge를 다시 복제하거나, 반대로 handoff가 너무 얇아서 다음 source가 비게 된다 -- User answer / evidence: - - `docs/architecture/control-plane-map.md`는 runtime entrypoint and knowledge를 workflow ownership surface로 정의하고, target repo discovery order와 handoff minimum을 workflow가 소유한다고 적는다 - - `docs/architecture/harness-system-map.md`는 state transition이 repo entrypoint에서 멈추고, repo-local verification/bootstrap은 sibling repo가 소유한다고 고정한다 - - `docs/architecture/context-pack-registry.md`와 `docs/operations/verification-contract-registry.md`는 impacted repo entry doc 또는 공식 remote source 확인이 planning verification의 일부임을 고정한다 - - local `../git-ranker/README.md`와 remote `alexization/git-ranker-client` source를 확인한 결과, 두 repo 모두 `AGENTS.md`는 아직 없고 frontend `README.md`는 기본 템플릿 상태라서 contract는 current implementation guide가 아니라 repo-local bootstrap minimum이어야 한다 -- Closed gap: handoff summary minimum, discovery order, target repo `AGENTS.md` requirement, bootstrap gap handling, tracking 불필요 결정이 architecture source로 잠겼다 -- Remaining blocker: `none` - -### Round 3 -- Prompted gap: target repo root `README.md`가 bootstrap checklist에서 concrete entry surface까지 책임져야 하는지 여부 -- Why it mattered: root `README.md`에 entrypoint responsibility를 실으면 repo-local ownership 경계가 다시 흐려지고, 실제 repo bootstrap 방향과도 어긋난다 -- User answer / evidence: - - target repo의 root `README.md`에는 단순한 프로젝트 설명만 둔다 -- Closed gap: root `README.md`는 overview-only surface로 두고, concrete entrypoint responsibility는 `AGENTS.md`와 named entry docs가 소유한다고 contract를 수정한다 -- Remaining blocker: `none` - -## Assumptions And Constraints - -- Prerequisites before approval: `GRW-26` -- workflow는 target repo에 필요한 최소 handoff만 남긴다. -- target repo bootstrap 실제 적용은 repo-local spec에서 닫는다. -- local worktree가 없는 impacted repo는 official remote source로 entrypoint 상태를 확인할 수 있다. -- target repo root `README.md`는 overview-only surface일 수 있고, entrypoint detail은 `AGENTS.md` 또는 named entry docs가 소유한다. - -## Approval Gate -- Problem and goal locked: `yes` -- Non-goals explicit: `yes` -- Primary repo and write scope locked: `yes` -- Verification method locked: `yes` -- Subtask split decided: `yes` -- Tracking decision locked: `yes` -- Remaining blockers: `none` - -## Write Scope -- Primary repo: `git-ranker-workflow` -- Allowed write paths: - - `docs/architecture/README.md` - - `docs/architecture/control-plane-map.md` - - `docs/architecture/context-pack-registry.md` - - `docs/architecture/harness-system-map.md` - - `docs/architecture/repo-handoff-contract.md` - - `docs/specs/` -- Control-plane artifacts: - - `docs/specs/completed/2026-04-13-grw-27-federated-repo-handoff-contract.md` -- Explicitly forbidden: - - sibling app repo bootstrap execution - - sibling app repo code or docs edits - - repo-local implementation guide나 command inventory를 workflow에서 복제하는 것 -- Network / external systems: - - impacted repo entrypoint 상태 확인을 위한 GitHub remote source read-only inspection -- Escalation triggers: - - 없음 - -## Acceptance Criteria - -- target repo `AGENTS.md` requirement와 handoff summary shape가 문서화된다. -- local skill discovery order가 control-plane 기준으로 정렬된다. -- repo bootstrap checklist가 follow-up repo-local work와 연결되고, root `README.md`는 overview-only requirement로 유지된다. - -## Verification -- Contract profile: `cross-repo-planning` -- Commands: - - `sed -n '1,260p' docs/architecture/repo-handoff-contract.md` - - `sed -n '1,260p' docs/architecture/control-plane-map.md` - - `sed -n '1,260p' docs/architecture/harness-system-map.md` - - `sed -n '1,260p' docs/architecture/context-pack-registry.md` - - impacted repo entry doc 또는 official remote source 확인 - - `rg -n "handoff|AGENTS.md|bootstrap|entrypoint|local skill|repo-handoff-contract" docs/architecture docs/specs` - - `git diff --check` - -## Delivery And Tracking Plan -- Lane: `guarded lane` -- Parent issue needed: `no` -- PR needed: `no` - -## Detailed Subtasks -- `handoff-contract-definition` - - Target repo: `git-ranker-workflow` - - Goal: workflow-to-repo handoff summary의 minimum shape를 고정한다. - - In-scope: handoff summary fields, local skill discovery order - - Done when: Implementer가 target repo entrypoint로 안전하게 넘겨질 수 있다. - - Verification hook: architecture/operations doc review - - Tracking needed: `no` -- `repo-bootstrap-requirement-definition` - - Target repo: `git-ranker-workflow` - - Goal: repo가 노출해야 하는 `AGENTS.md`와 bootstrap checklist requirement를 정리한다. - - In-scope: checklist wording, repo-local follow-up linkage - - Done when: backend/frontend follow-up spec이 같은 contract를 참조할 수 있다. - - Verification hook: `AGENTS.md` + related doc review - - Tracking needed: `no` - -## Risks Or Open Questions - -- handoff contract를 너무 두껍게 정의하면 `GRW-26`의 thin-layer 원칙과 충돌할 수 있다. - -## Approval -- Harness judgment: handoff summary minimum, bootstrap checklist depth, contract profile, tracking decision이 모두 잠겨 cross-repo planning slice로 바로 실행 가능하다. -- User approval: current user request가 이 spec의 실행을 직접 요청했다. - -## Verification Summary - -- Contract profile: `cross-repo-planning` -- Overall status: `passed` -- Ran: - - `sed -n '1,260p' docs/architecture/repo-handoff-contract.md` - - `sed -n '1,220p' docs/architecture/control-plane-map.md` - - `sed -n '1,220p' docs/architecture/harness-system-map.md` - - `sed -n '1,220p' docs/architecture/context-pack-registry.md` - - `test -f ../git-ranker/AGENTS.md && echo present || echo missing` - - `sed -n '1,80p' ../git-ranker/README.md` - - GitHub fetch `alexization/git-ranker-client:README.md` - - GitHub fetch `alexization/git-ranker-client:AGENTS.md` - - GitHub fetch `alexization/git-ranker-client:package.json` - - `rg -n "handoff|AGENTS.md|bootstrap|entrypoint|local skill|repo-handoff-contract" docs/architecture docs/specs` - - `git diff --check` -- Evidence: - - `docs/architecture/repo-handoff-contract.md`가 handoff summary minimum, discovery order, local skill order, target repo `AGENTS.md` requirement, bootstrap gap handling을 stable source로 고정했다. - - root `README.md`는 overview-only이고 concrete entrypoint는 `AGENTS.md`와 named entry docs가 소유한다는 correction을 반영했다. - - `docs/architecture/control-plane-map.md`, `docs/architecture/harness-system-map.md`, `docs/architecture/context-pack-registry.md`, `docs/architecture/README.md`가 새 contract 문서를 existing architecture surface에서 발견 가능하게 연결했다. - - local backend worktree 기준 `../git-ranker/AGENTS.md`는 아직 없고 `README.md`만 존재해 `GRB-07`이 bootstrap gap close-out을 계속 소유한다는 점이 확인됐다. - - remote `alexization/git-ranker-client` 기준 `AGENTS.md`는 404이고 `README.md`는 기본 Next.js 템플릿, `package.json`은 verification entry surface로 존재해 `GRC-07`이 entrypoint/bootstrap 정렬을 계속 소유한다는 점이 확인됐다. - - `git diff --check`가 통과해 formatting issue는 없었다. -- Failure or skipped summary: 없음 -- Next action: completed - -## Final Change Summary - -- `docs/architecture/repo-handoff-contract.md`를 추가해 workflow-to-repo handoff minimum을 전용 stable source로 분리했다. -- 같은 문서에 handoff summary field, target repo discovery order, local skill discovery rule, target repo `AGENTS.md` requirement, bootstrap gap handling을 고정했고 root `README.md`를 overview-only로 낮췄다. -- `docs/architecture/control-plane-map.md`, `docs/architecture/harness-system-map.md`, `docs/architecture/context-pack-registry.md`, `docs/architecture/README.md`에서 새 contract 문서를 canonical hook으로 연결했다. -- current spec의 blocker를 source evidence로 닫고 `cross-repo-planning` verification contract, tracking `no`, network boundary를 명시했다. - -## Final User Validation - -- 사용자는 `GRW-27를 진행해주세요.`라고 요청해 현재 docs-only planning slice의 실행과 close-out continuation을 직접 승인했다. -- 이후 `target repo에 root README.md 에는 단순히 프로젝트 설명밖에 없을거야.`라고 clarification을 남겨 README expectation correction을 직접 닫았다. -- 이번 결과는 workflow stable source 정렬 작업이라 추가 runtime validation surface가 없고, repo-local follow-up은 `GRB-07`, `GRC-07`에서 이어서 닫는다. - -## Docs Updated - -- `docs/architecture/README.md` -- `docs/architecture/control-plane-map.md` -- `docs/architecture/context-pack-registry.md` -- `docs/architecture/harness-system-map.md` -- `docs/architecture/repo-handoff-contract.md` -- `docs/specs/completed/2026-04-13-grw-27-federated-repo-handoff-contract.md` diff --git a/docs/specs/completed/2026-04-13-grw-pr82-review-feedback.md b/docs/specs/completed/2026-04-13-grw-pr82-review-feedback.md deleted file mode 100644 index e026c04..0000000 --- a/docs/specs/completed/2026-04-13-grw-pr82-review-feedback.md +++ /dev/null @@ -1,142 +0,0 @@ -# 2026-04-13-grw-pr82-review-feedback - -- Status: `Completed` -- Primary Repo: `git-ranker-workflow` -- Related Issue: `없음` -- Related PR: `alexization/git-ranker-workflow#82` - -## Request Summary - -PR `#82`의 코드리뷰 코멘트를 검토하고, 반영할 내용이 있으면 workflow 문서와 active spec을 수정한다. - -## Problem - -PR `#82` 리뷰에서 두 가지 문서 일관성 문제가 지적됐다. 첫째, `approval gate` requirement가 기존 active spec을 즉시 비호환처럼 보이게 만들 수 있다. 둘째, workflow governance의 실행 순서가 verification 단계의 spec reopen trigger를 누락해 같은 문서 안에서 운영 path가 갈릴 수 있다. - -## Goals - -- 현재 active spec 두 건이 새 minimum shape와 충돌하지 않도록 호환성을 맞춘다. -- `현재 기준 실행 순서`에 verification-based reopen trigger를 명시한다. -- PR `#82`에 올린 변경이 review feedback 기준으로 설명 가능하게 만든다. - -## Non-goals - -- PR review thread reply 또는 resolve -- app repo 코드/테스트 변경 -- PR `#82`의 나머지 본문/제목 재작성 - -## Socratic Clarification Log - -### Round 1 -- Prompted gap: review comment가 실제로 code change가 필요한 actionable feedback인지 여부 -- Why it mattered: informational comment까지 섞어 scope를 넓히면 PR repair 범위가 흔들린다 -- User answer / evidence: - - 사용자는 PR `#82` 코드리뷰를 검토하고 반영할 내용이 있으면 반영해 달라고 요청했다 - - connector review comment 두 개는 모두 specific file/line을 가진 actionable doc feedback이었다 -- Closed gap: 이번 작업은 두 review comment 반영으로 좁힐 수 있다 - -## Assumptions And Constraints - -- review reply나 thread resolve는 사용자 요청이 없어 수행하지 않는다. -- current PR branch `codex/socratic-loop-hardening` 위에서 docs-only repair commit 하나로 닫는 것이 가장 자연스럽다. - -## Approval Gate -- Problem and goal locked: `yes` -- Non-goals explicit: `yes` -- Primary repo and write scope locked: `yes` -- Verification method locked: `yes` -- Subtask split decided: `yes` -- Tracking decision locked: `yes` -- Remaining blockers: `none` - -## Write Scope -- Primary repo: `git-ranker-workflow` -- Allowed write paths: - - `docs/operations/workflow-governance.md` - - `docs/specs/active/2026-04-09-grb-04-backend-verification-contract-reset.md` - - `docs/specs/active/2026-04-09-grb-06-backend-test-ci-removal.md` - - `docs/specs/completed/2026-04-13-grw-pr82-review-feedback.md` -- Control-plane artifacts: - - `docs/specs/completed/2026-04-13-grw-pr82-review-feedback.md` -- Explicitly forbidden: - - sibling app repo changes - - unrelated policy rewrites outside the two review findings -- Network / external systems: - - GitHub PR review context for `alexization/git-ranker-workflow#82` -- Escalation triggers: - - git commit / git push only - -## Acceptance Criteria - -- active spec 두 건이 `Approval Gate`를 포함해 새 shape와 충돌하지 않는다. -- `docs/operations/workflow-governance.md`의 실행 순서가 verification-based reopen trigger를 포함한다. -- workflow-docs verification evidence가 남는다. - -## Verification -- Contract profile: `workflow-docs` -- Commands: - - `sed -n '1,220p' docs/specs/active/2026-04-09-grb-04-backend-verification-contract-reset.md` - - `sed -n '1,220p' docs/specs/active/2026-04-09-grb-06-backend-test-ci-removal.md` - - `sed -n '150,170p' docs/operations/workflow-governance.md` - - `rg -n "Approval Gate|verification, review, feedback, user validation" docs/specs/active docs/operations/workflow-governance.md` - - `git diff --check` - -## Delivery And Tracking Plan -- Lane: `default lane` -- Parent issue needed: `no` -- PR needed: `already open` - -## Detailed Subtasks -- `active-spec-compatibility` - - Target repo: `git-ranker-workflow` - - Goal: 기존 active spec 두 건에 `Approval Gate`를 추가해 minimum shape와 맞춘다. - - In-scope: review comment 1 관련 active spec 보강 - - Done when: 두 active spec 모두 approval gate를 가진다. - - Verification hook: active spec review - - Tracking needed: `no` -- `execution-order-alignment` - - Target repo: `git-ranker-workflow` - - Goal: workflow governance 실행 순서가 verification-based reopen trigger를 포함한다. - - In-scope: review comment 2 관련 wording 정렬 - - Done when: 실행 순서 문장이 runtime principle과 state machine에 맞는다. - - Verification hook: governance review - - Tracking needed: `no` - -## Risks Or Open Questions - -- active spec에 approval gate를 추가해도 older clarification summary wording은 남아 있을 수 있다. 이번 repair는 review comment 범위에 한정한다. - -## Approval -- Harness judgment: 두 review comment 모두 명확하고 bounded docs repair로 닫을 수 있다. -- User approval: 사용자가 PR `#82`의 코드리뷰를 검토하고 반영할 내용이 있으면 반영해 달라고 요청했다. - -## Verification Summary - -- Contract profile: `workflow-docs` -- Overall status: `passed` -- Review evidence: - - `gh api graphql`로 PR `#82`의 unresolved review thread를 확인했고, actionable thread는 2건이었다. - - thread 1은 `docs/operations/sdd-spec-policy.md`의 `Approval Gate` minimum shape가 기존 active spec과 충돌한다는 지적이었다. - - thread 2는 `docs/operations/workflow-governance.md` 실행 순서가 verification-based reopen trigger를 누락한다는 지적이었다. -- Ran: - - `sed -n '1,220p' docs/specs/active/2026-04-09-grb-04-backend-verification-contract-reset.md` - - `sed -n '1,220p' docs/specs/active/2026-04-09-grb-06-backend-test-ci-removal.md` - - `sed -n '150,170p' docs/operations/workflow-governance.md` - - `rg -n "Approval Gate|verification, review, feedback, user validation" docs/specs/active docs/operations/workflow-governance.md` - - `git diff --check` -- Evidence: - - 기존 active spec 두 건이 모두 `Approval Gate` section을 가져 current minimum shape와 충돌하지 않는다. - - workflow governance 실행 순서가 `verification, review, feedback, user validation`을 모두 reopen trigger로 명시한다. - - 문서 diff에 whitespace 또는 patch formatting 문제는 없다. -- Failure or skipped summary: review reply / thread resolve는 사용자 요청이 없어 수행하지 않았다. -- Next action: merged in PR `#82` - -## Final Change Summary - -- active spec 두 건에 `Approval Gate`를 보강했다. -- workflow governance 실행 순서에 verification-based reopen trigger를 반영했다. -- review feedback artifact를 completed history로 보존했다. - -## Final User Validation - -- PR `#82`는 2026-04-13에 merged 되었고, 이 spec은 해당 review repair의 historical close-out record로 보존된다. diff --git a/docs/specs/completed/2026-04-13-grw-pr84-review-feedback.md b/docs/specs/completed/2026-04-13-grw-pr84-review-feedback.md deleted file mode 100644 index 435a01b..0000000 --- a/docs/specs/completed/2026-04-13-grw-pr84-review-feedback.md +++ /dev/null @@ -1,141 +0,0 @@ -# 2026-04-13-grw-pr84-review-feedback - -- Status: `Completed` -- Primary Repo: `git-ranker-workflow` -- Related Issue: `없음` -- Related PR: `alexization/git-ranker-workflow#84` - -## Request Summary - -PR `#84`의 코드리뷰 피드백을 검토하고, actionable unresolved thread가 있으면 workflow 문서와 spec을 수정한다. - -## Problem - -PR `#84` 리뷰에서 두 가지 문서 일관성 문제가 지적됐다. 첫째, `2026-04-13-grw-pr82-review-feedback.md`를 active queue에서 삭제하면서 completed history로 보존하지 않아 canonical artifact가 사라졌다. 둘째, product migration completed spec이 그 spec을 여전히 active로 retained했다고 적고 있어 현재 diff와 충돌한다. - -## Goals - -- `2026-04-13-grw-pr82-review-feedback.md`를 completed history로 복원한다. -- product migration completed spec의 retained-active 설명을 실제 상태와 맞춘다. -- PR `#84` 리뷰 thread 두 건이 같은 docs repair로 설명 가능해지게 만든다. - -## Non-goals - -- PR review thread reply 또는 resolve -- workflow 외 저장소 변경 -- current PR의 본문/제목 전면 재작성 - -## Socratic Clarification Log - -### Round 1 -- Prompted gap: PR `#84`의 어떤 리뷰 피드백이 실제 수정이 필요한 actionable unresolved thread인지 -- Why it mattered: informational review까지 섞으면 repair 범위가 불필요하게 넓어진다 -- User answer / evidence: - - 사용자는 `코드 리뷰를 검토해주고 피드백을 반영할 내용이 있으면, 반영해줘.`라고 요청했다 - - thread-aware GraphQL 조회 결과 unresolved actionable thread는 2건뿐이었다 - - 두 thread 모두 `PR82 review-feedback` spec archival과 migration spec consistency라는 같은 behavior area를 가리켰다 -- Closed gap: 이번 작업은 `completed spec archival + migration spec consistency` repair 하나로 닫을 수 있다 -- Remaining blocker: 없음 - -## Assumptions And Constraints - -- PR `#82`는 이미 merged 상태이므로 `2026-04-13-grw-pr82-review-feedback.md`는 active가 아니라 completed history로 보존하는 것이 맞다. -- review reply나 thread resolve는 사용자 요청이 없어 수행하지 않는다. -- current PR branch `codex/product-to-spec-migration` 위에서 docs-only repair commit 하나로 닫는다. - -## Approval Gate -- Problem and goal locked: `yes` -- Non-goals explicit: `yes` -- Primary repo and write scope locked: `yes` -- Verification method locked: `yes` -- Subtask split decided: `yes` -- Tracking decision locked: `yes` -- Remaining blockers: `none` - -## Write Scope -- Primary repo: `git-ranker-workflow` -- Allowed write paths: - - `docs/specs/completed/2026-04-13-grw-pr84-review-feedback.md` - - `docs/specs/completed/2026-04-13-grw-pr82-review-feedback.md` - - `docs/specs/completed/2026-04-13-grw-product-to-spec-migration.md` -- Control-plane artifacts: - - `docs/specs/completed/2026-04-13-grw-pr84-review-feedback.md` -- Explicitly forbidden: - - sibling app repo changes - - unrelated workflow policy rewrites outside the two review findings -- Network / external systems: - - GitHub PR review context for `alexization/git-ranker-workflow#84` -- Escalation triggers: - - git push only - -## Acceptance Criteria - -- `2026-04-13-grw-pr82-review-feedback.md`가 completed history에 보존된다. -- product migration completed spec이 current active/completed split과 모순되지 않는다. -- workflow-docs verification evidence가 남는다. - -## Verification -- Contract profile: `workflow-docs` -- Commands: - - `sed -n '1,260p' docs/specs/completed/2026-04-13-grw-pr82-review-feedback.md` - - `sed -n '60,110p' docs/specs/completed/2026-04-13-grw-product-to-spec-migration.md` - - `find docs/specs -maxdepth 2 -type f | sort` - - `git diff --check` - -## Delivery And Tracking Plan -- Lane: `default lane` -- Parent issue needed: `no` -- PR needed: `already open` - -## Detailed Subtasks -- `archival-repair` - - Target repo: `git-ranker-workflow` - - Goal: removed `PR82 review-feedback` spec을 completed history로 복원한다. - - In-scope: file relocation, status/close-out wording alignment - - Done when: review-feedback spec이 completed 위치에서 historical artifact로 읽힌다. - - Verification hook: completed spec review - - Tracking needed: `no` -- `migration-consistency-repair` - - Target repo: `git-ranker-workflow` - - Goal: product migration spec의 retained-active 설명을 실제 queue 상태에 맞춘다. - - In-scope: assumptions/docs-updated wording alignment - - Done when: migration record가 current diff와 모순되지 않는다. - - Verification hook: migration spec review - - Tracking needed: `no` - -## Risks Or Open Questions - -- archived spec의 completed close-out wording은 PR `#82` 당시 남긴 verification summary를 기반으로 재구성되므로, historical narration은 최소 수정 원칙을 지켜야 한다. - -## Approval -- Harness judgment: 두 unresolved review thread는 같은 docs-only repair로 안전하게 닫을 수 있다. -- User approval: 사용자가 actionable review feedback은 반영해 달라고 요청했다. - -## Verification Summary - -- Contract profile: `workflow-docs` -- Overall status: `passed` -- Review evidence: - - thread-aware GraphQL 조회에서 unresolved actionable review thread는 2건이었다. - - thread 1은 deleted `PR82 review-feedback` spec을 completed history로 보존하라고 요구했다. - - thread 2는 migration completed spec이 deleted active spec을 retained했다고 적은 모순을 지적했다. -- Ran: - - `sed -n '1,260p' docs/specs/completed/2026-04-13-grw-pr82-review-feedback.md` - - `sed -n '60,110p' docs/specs/completed/2026-04-13-grw-product-to-spec-migration.md` - - `find docs/specs -maxdepth 2 -type f | sort` - - `git diff --check` -- Evidence: - - `PR82 review-feedback` spec이 completed history로 복원됐다. - - product migration spec의 assumptions/docs-updated wording이 현재 active/completed split과 일치한다. - - current diff에는 patch formatting 문제가 없다. -- Failure or skipped summary: review reply / thread resolve는 사용자 요청이 없어 수행하지 않았다. -- Next action: push repair commit to PR `#84` - -## Final Change Summary - -- deleted `PR82 review-feedback` spec을 completed history로 복원했다. -- product migration completed spec의 retained-active 설명을 completed archival과 일치하게 고쳤다. - -## Final User Validation - -- 사용자는 actionable review feedback을 반영해 달라고 요청했고, 이번 변경은 unresolved review thread 두 건을 모두 문서 수정으로 반영했다. diff --git a/docs/specs/completed/2026-04-13-grw-product-migration-publish.md b/docs/specs/completed/2026-04-13-grw-product-migration-publish.md deleted file mode 100644 index 6debecb..0000000 --- a/docs/specs/completed/2026-04-13-grw-product-migration-publish.md +++ /dev/null @@ -1,149 +0,0 @@ -# 2026-04-13-grw-product-migration-publish - -- Status: `Completed` -- Primary Repo: `git-ranker-workflow` -- Related Issue: `alexization/git-ranker-workflow#83` -- Related PR: `alexization/git-ranker-workflow#84` - -## Request Summary - -현재 workflow 문서 변경과 seeded draft spec 추가 내용을 기준으로 umbrella issue와 open PR을 생성한다. - -## Problem - -`docs/product` 제거와 spec-first queue 전환은 로컬 diff로만 남아 있고, 현재 이를 추적하고 리뷰할 GitHub surface가 없다. - -## Goals - -- 현재 docs-only 변경을 설명하는 umbrella issue를 만든다. -- 같은 변경을 publish하는 open PR을 만든다. -- issue/PR 본문이 reader-first 규칙과 repo template을 따른다. - -## Non-goals - -- sibling app repo 변경 publish -- seeded draft spec 자체의 구현 시작 -- current docs diff와 무관한 cleanup 포함 - -## Socratic Clarification Log - -### Round 1 -- Prompted gap: current worktree에 섞여 있는 변경 중 무엇을 이번 umbrella issue/PR 범위에 포함할지 -- Why it mattered: PR은 목표 1개만 가져야 하므로 publish scope를 먼저 잠가야 한다 -- User answer / evidence: - - 사용자는 `일단 이 작업들에 대해서 크게 이슈와 PR을 작성해줘.`라고 요청했다 - - 추가 질문에 `포함해줘.`라고 답해 함께 잡혀 있던 spec 삭제 signal도 같은 publish 범위에 포함해도 된다고 승인했다 -- Closed gap: 이번 publish는 현재 workflow docs diff 전체를 하나의 umbrella artifact로 묶는다 -- Remaining blocker: 없음 - -## Assumptions And Constraints - -- PR은 repo policy에 따라 draft가 아니라 open PR로 생성한다. -- publish 대상은 `git-ranker-workflow`의 docs/spec/skill 변경만 포함한다. -- issue/PR 본문에는 raw command dump 대신 문제, 접근, 영향, 리스크만 요약한다. - -## Approval Gate -- Problem and goal locked: `yes` -- Non-goals explicit: `yes` -- Primary repo and write scope locked: `yes` -- Verification method locked: `yes` -- Subtask split decided: `yes` -- Tracking decision locked: `yes` -- Remaining blockers: `none` - -## Write Scope -- Primary repo: `git-ranker-workflow` -- Allowed write paths: - - `docs/specs/completed/2026-04-13-grw-product-migration-publish.md` - - `docs/specs/completed/2026-04-13-grw-product-to-spec-migration.md` - - temporary issue/PR body file under `/tmp` - - git metadata for branch/commit on current repo -- Control-plane artifacts: - - `docs/specs/completed/2026-04-13-grw-product-migration-publish.md` -- Explicitly forbidden: - - sibling app repo changes - - current diff와 무관한 추가 문서 수정 -- Network / external systems: - - GitHub issue/PR publish for `alexization/git-ranker-workflow` -- Escalation triggers: - - `git push` - - GitHub issue/PR creation if sandbox/network blocks - -## Acceptance Criteria - -- umbrella issue가 현재 docs migration과 seeded draft queue를 설명한다. -- open PR이 현재 diff를 publish하고 linked issue를 포함한다. -- branch, commit, issue, PR metadata가 current spec과 모순되지 않는다. - -## Verification -- Contract profile: `workflow-docs` -- Commands: - - `git status --short` - - `git diff --stat` - - `git diff --check` - - `gh issue view 83 --repo alexization/git-ranker-workflow` - - `gh pr view 84 --repo alexization/git-ranker-workflow` - -## Delivery And Tracking Plan -- Lane: `guarded lane` -- Parent issue needed: `yes` -- PR needed: `yes` - -## Detailed Subtasks -- `tracking-body-authoring` - - Target repo: `git-ranker-workflow` - - Goal: template에 맞는 umbrella issue/PR body를 준비한다. - - In-scope: title, summary, scope, risk, linkage - - Done when: issue/PR body file이 reader-first 규칙을 만족한다. - - Verification hook: body file review - - Tracking needed: `yes` -- `publish-current-diff` - - Target repo: `git-ranker-workflow` - - Goal: current docs diff를 branch/commit/issue/PR로 publish한다. - - In-scope: branch creation, commit, push, GitHub issue/PR creation - - Done when: open PR과 linked issue가 존재한다. - - Verification hook: `gh issue view`, `gh pr view` - - Tracking needed: `yes` - -## Risks Or Open Questions - -- untracked draft spec 파일을 빠뜨리면 issue/PR 설명과 actual diff가 어긋날 수 있다. - -## Approval -- Harness judgment: 현재 publish 범위는 workflow docs migration과 seeded draft spec queue를 설명하는 하나의 umbrella artifact로 닫을 수 있다. -- User approval: 사용자가 umbrella issue/PR 생성을 요청했고, publish scope에 current mixed diff를 포함하는 데 동의했다. - -## Verification Summary - -- Contract profile: `workflow-docs` -- Overall status: `passed` -- Ran: - - `git status --short` - - `git diff --stat` - - `git diff --check` - - `gh issue create --repo alexization/git-ranker-workflow --title "[Task] docs/product 제거와 spec-first queue 전환" --label task --label triage --body-file /tmp/grw-product-migration-issue-body.md` - - `git checkout -b codex/product-to-spec-migration` - - `git add AGENTS.md .codex/skills/README.md docs` - - `git commit -m "docs: product 문서를 제거하고 spec queue를 정렬" ...` - - `git push -u origin codex/product-to-spec-migration` - - `gh pr create --repo alexization/git-ranker-workflow --base develop --head codex/product-to-spec-migration --title "docs: product 문서를 제거하고 spec queue를 정렬" --body-file /tmp/grw-product-migration-pr-body.md` - - `gh issue view 83 --repo alexization/git-ranker-workflow` - - `gh pr view 84 --repo alexization/git-ranker-workflow` -- Evidence: - - umbrella issue `#83`가 생성됐고 `task`, `triage` label과 reader-first body를 가진다. - - open PR `#84`가 `develop <- codex/product-to-spec-migration`로 생성됐다. - - PR 본문이 issue `#83`를 닫도록 연결한다. - - current branch가 원격에 push됐고 current docs diff가 PR로 publish됐다. -- Failure or skipped summary: 없음 -- Next action: reviewer follow-up on PR `#84` - -## Final Change Summary - -- publish용 issue `#83`과 PR `#84`를 생성했다. -- current docs migration diff를 branch `codex/product-to-spec-migration`에 커밋/푸시했다. -- publish task spec을 close-out 하고 related issue/PR metadata를 고정했다. - -## Final User Validation - -- 사용자는 umbrella issue/PR 생성을 요청했고, publish scope에 mixed docs diff를 포함하는 데 동의했다. -- 그 승인에 맞춰 issue와 open PR을 생성했다. diff --git a/docs/specs/completed/2026-04-13-grw-product-to-spec-migration.md b/docs/specs/completed/2026-04-13-grw-product-to-spec-migration.md deleted file mode 100644 index ec9cf18..0000000 --- a/docs/specs/completed/2026-04-13-grw-product-to-spec-migration.md +++ /dev/null @@ -1,213 +0,0 @@ -# 2026-04-13-grw-product-to-spec-migration - -- Status: `Completed` -- Primary Repo: `git-ranker-workflow` -- Related Issue: `alexization/git-ranker-workflow#83` -- Related PR: `alexization/git-ranker-workflow#84` - -## Request Summary - -`docs/product`에 남아 있는 로드맵과 작업 카탈로그를 제거하고, 그 안의 남은 work item을 `docs/specs/active/`의 draft spec으로 승격해 SDD 기준의 단일 요구사항 surface로 정렬한다. - -## Problem - -현재 Harness에는 `docs/product/`와 `docs/specs/`가 동시에 남아 있어 pending work의 source of truth가 둘로 갈라져 있다. 이 상태에서는 작업이 spec authoring이 아니라 roadmap/catalog에서 출발하는 것처럼 읽힐 수 있다. - -## Goals - -- `docs/product`의 pending work를 개별 draft spec으로 투영한다. -- 상위 workflow 목표와 문서 경계를 architecture/operations/spec docs에 흡수한다. -- `docs/product`를 제거하고 spec-first 구조만 남긴다. -- 기존 active spec과 새 draft spec이 함께 remaining work를 설명하도록 정렬한다. - -## Non-goals - -- 새로 생성하는 draft spec을 이번 turn에서 바로 `Approved`로 올리거나 구현까지 진행하는 것 -- sibling app repo 코드나 문서를 실제로 수정하는 것 -- `docs/specs/` 밖에 별도 backlog/roadmap/catalog surface를 새로 만드는 것 - -## Socratic Clarification Log - -### Round 1 -- Prompted gap: `docs/product`를 없앤 뒤 남은 work item을 어떤 canonical surface로 보존할지 -- Why it mattered: 단순 삭제만 하면 remaining requirement와 dependency 정보가 함께 사라진다 -- User answer / evidence: - - 사용자는 `product에 명시되어있는 작업들을 새로운 요구사항으로 두고 스펙 정의부터 시작해서 작업을 수행할 수 있도록` 정리해 달라고 요청했다 - - existing policy는 spec 하나를 canonical working artifact로 두고 별도 planning 문서 중복을 금지한다 -- Closed gap: 남은 work item은 개별 draft spec으로 승격하고, 별도 roadmap/catalog은 남기지 않는다 - -### Round 2 -- Prompted gap: roadmap에 있던 상위 목표와 순서를 어디에 남길지 -- Why it mattered: product 제거 후에도 control-plane 목적과 sequencing semantics는 stable source of truth에서 읽을 수 있어야 한다 -- User answer / evidence: - - `docs/architecture/`와 `docs/operations/`는 이미 control-plane 책임과 runtime rule을 소유한다 - - sequencing은 각 draft spec의 prerequisite와 상태로 표현할 수 있다 -- Closed gap: 상위 목표는 architecture/operations/spec docs로 흡수하고, 남은 순서는 draft spec dependency로 남긴다 -- Remaining blocker: seeded draft spec의 초기 상태와 approval semantics를 explicit하게 잠가야 한다 - -### Round 3 -- Prompted gap: actual Socratic question loop 없이 이 spec을 `Approved`/`Completed`로 닫아도 되는지 -- Why it mattered: user validation에서 드러난 process defect는 current policy상 `Draft` 재오픈과 재승인을 요구한다 -- User answer / evidence: - - 사용자는 `이 작업을 하면서 단 한번도 소크라테스 프롬프트를 돌리지 않은것 같은데 맞아 ?`라고 지적했다 - - 이어서 `그러면 진행해줘.`라고 답해 reopen loop를 실제로 진행하라고 요청했다 -- Closed gap: 이 spec은 user validation에서 process defect가 확인돼 `Draft`로 재오픈한다 -- Remaining blocker: pending item을 seed draft로 두는 semantics를 explicit하게 승인받아야 한다 - -### Round 4 -- Prompted gap: `docs/product`에서 옮긴 7개 남은 항목을 모두 future execution용 seeded `Draft` spec으로만 유지해도 되는지 -- Why it mattered: 이 결정이 잠겨야 `docs/product` 제거 뒤 남는 canonical planning surface와 approval semantics를 닫을 수 있다 -- User answer / evidence: - - 사용자는 `네`라고 답해 7개 pending item을 seeded `Draft` spec queue로 두는 방향을 승인했다 -- Closed gap: remaining item은 모두 seeded `Draft` spec으로 유지하고, 실제 착수 시 각 spec을 다시 소크라테스 loop로 `Approved`까지 올린다 -- Remaining blocker: 없음 - -## Assumptions And Constraints - -- 기존 active spec `GRB-04`, `GRB-06`는 그대로 유지하고, `GRW-PR82-review-feedback`는 completed history로 보존한다. -- 새로 추가하는 spec은 requirement seed이므로 `Draft` 상태로 남기고, future execution 시 별도의 Socratic clarification과 사용자 승인으로 `Approved`로 올린다. -- 이번 작업의 primary context pack은 `workflow-docs`다. - -## Approval Gate -- Problem and goal locked: `yes` -- Non-goals explicit: `yes` -- Primary repo and write scope locked: `yes` -- Verification method locked: `yes` -- Subtask split decided: `yes` -- Tracking decision locked: `yes` -- Remaining blockers: `none` - -## Write Scope -- Primary repo: `git-ranker-workflow` -- Allowed write paths: - - `AGENTS.md` - - `docs/README.md` - - `docs/architecture/` - - `docs/operations/` - - `docs/specs/` - - `.codex/skills/README.md` -- Control-plane artifacts: - - `docs/specs/completed/2026-04-13-grw-product-to-spec-migration.md` -- Explicitly forbidden: - - sibling app repo code or docs edits - - `docs/specs/` 밖 새 planning/backlog surface 추가 -- Network / external systems: - - 없음 -- Escalation triggers: - - 없음 - -## Acceptance Criteria - -- `docs/product`의 pending work가 existing active spec 또는 new draft spec으로 모두 투영된다. -- stable source of truth가 `docs/product` 없이 spec-first planning surface를 설명한다. -- `docs/specs/README.md`와 `docs/specs/active/README.md`가 draft spec이 remaining requirement를 대체한다는 점을 설명한다. -- `docs/product/` 디렉터리가 제거된다. - -## Verification -- Contract profile: `workflow-docs` -- Commands: - - `test ! -d docs/product && echo removed` - - `find docs/specs -maxdepth 2 -type f | sort` - - `sed -n '1,220p' AGENTS.md` - - `sed -n '1,220p' docs/README.md` - - `sed -n '1,260p' docs/architecture/control-plane-map.md` - - `sed -n '1,260p' docs/architecture/harness-system-map.md` - - `sed -n '1,260p' docs/operations/workflow-governance.md` - - `sed -n '1,220p' docs/specs/README.md` - - `sed -n '1,220p' docs/specs/active/README.md` - - `sed -n '1,220p' .codex/skills/README.md` - - `rg -n "docs/product|harness-roadmap|work-item-catalog" AGENTS.md docs/README.md docs/architecture docs/operations docs/specs/README.md docs/specs/active/README.md .codex/skills/README.md` - - `git diff --check` - -## Delivery And Tracking Plan -- Lane: `guarded lane` -- Parent issue needed: `no` -- PR needed: `conditional` - -## Detailed Subtasks -- `seed-draft-specs` - - Target repo: `git-ranker-workflow` - - Goal: former `docs/product/work-item-catalog.md`의 pending item을 개별 draft spec으로 승격한다. - - In-scope: draft spec 생성, prerequisite와 provisional write scope 이관 - - Done when: remaining pending item마다 active draft spec이 존재한다. - - Verification hook: `find docs/specs -maxdepth 2 -type f | sort` - - Tracking needed: `no` -- `source-of-truth-realignment` - - Target repo: `git-ranker-workflow` - - Goal: root/architecture/operations/spec 문서에서 planning surface를 spec-first로 재정렬한다. - - In-scope: `docs/product` 참조 제거, spec registry semantics 추가 - - Done when: stable source of truth가 `docs/product` 없이도 same intent를 설명한다. - - Verification hook: `sed` review, negative `rg` - - Tracking needed: `no` -- `product-surface-removal` - - Target repo: `git-ranker-workflow` - - Goal: obsolete `docs/product/` tree를 제거한다. - - In-scope: directory file deletion - - Done when: `docs/product/`가 더 이상 존재하지 않는다. - - Verification hook: negative `rg`, `find docs -maxdepth 2 -type f | sort` - - Tracking needed: `no` - -## Risks Or Open Questions - -- draft spec을 seed로만 남기기 때문에 각 작업의 final write scope와 verification contract는 future approval loop에서 더 좁혀질 수 있다. - -## Approval -- Harness judgment: `docs/product` 제거는 remaining work를 seeded `Draft` spec으로 먼저 투영할 때 안전하게 닫을 수 있다. -- User approval: 사용자가 `네`라고 답해 7개 pending item을 seeded `Draft` spec queue로 유지하는 semantics를 승인했다. - -## Verification Summary - -- Contract profile: `workflow-docs` -- Overall status: `passed` -- Ran: - - `test ! -d docs/product && echo removed` - - `find docs/specs -maxdepth 2 -type f | sort` - - `sed -n '1,220p' AGENTS.md` - - `sed -n '1,220p' docs/README.md` - - `sed -n '1,260p' docs/architecture/control-plane-map.md` - - `sed -n '1,260p' docs/architecture/harness-system-map.md` - - `sed -n '1,260p' docs/operations/workflow-governance.md` - - `sed -n '1,220p' docs/specs/README.md` - - `sed -n '1,220p' docs/specs/active/README.md` - - `sed -n '1,220p' .codex/skills/README.md` - - `rg -n "docs/product|harness-roadmap|work-item-catalog" AGENTS.md docs/README.md docs/architecture docs/operations docs/specs/README.md docs/specs/active/README.md .codex/skills/README.md` - - `git diff --check` -- Evidence: - - `docs/product/` directory가 제거됐다. - - `docs/specs/active/`에 pending work를 대체하는 seeded draft spec 7건이 추가됐다. - - root/architecture/operations/spec/skill docs가 `docs/specs/active/`의 draft/approved spec을 remaining work surface로 설명한다. - - stable source of truth 경로에서는 `docs/product`, `harness-roadmap`, `work-item-catalog` 참조가 남지 않았다. -- Failure or skipped summary: 없음 -- Next action: completed - -## Final Change Summary - -- `docs/product`의 roadmap/catalog 내용을 개별 draft spec으로 승격했다. -- `AGENTS.md`, `docs/README.md`, architecture/governance/spec/skill docs를 spec-first planning surface 기준으로 재정렬했다. -- `docs/product/` 디렉터리를 제거했다. -- user validation에서 드러난 process defect를 반영해 spec을 재오픈했고, seeded draft semantics에 대한 explicit approval 뒤 다시 close-out 했다. - -## Final User Validation - -- 사용자는 process defect 지적 뒤 reopen loop를 요청했고, 마지막 blocker 질문에 `네`라고 답해 seeded draft semantics를 승인했다. -- 이번 결과는 그 승인에 맞춰 remaining work를 `docs/specs/active/`의 draft spec queue로 유지하고 별도 product surface를 제거한 상태로 닫혔다. - -## Docs Updated - -- `AGENTS.md` -- `docs/README.md` -- `docs/architecture/control-plane-map.md` -- `docs/architecture/harness-system-map.md` -- `docs/operations/workflow-governance.md` -- `docs/specs/README.md` -- `docs/specs/active/README.md` -- `.codex/skills/README.md` -- `docs/specs/active/2026-04-13-grw-26-federated-ownership-model-alignment.md` -- `docs/specs/active/2026-04-13-grw-27-federated-repo-handoff-contract.md` -- `docs/specs/active/2026-04-13-grc-05-frontend-verification-contract-normalization.md` -- `docs/specs/active/2026-04-13-grb-07-backend-agents-entrypoint-bootstrap.md` -- `docs/specs/active/2026-04-13-grc-07-frontend-agents-entrypoint-bootstrap.md` -- `docs/specs/active/2026-04-13-grb-05-backend-gc-baseline-alignment.md` -- `docs/specs/active/2026-04-13-grc-06-frontend-gc-baseline-alignment.md` -- `docs/specs/completed/2026-04-13-grw-pr82-review-feedback.md` -- `docs/specs/completed/2026-04-13-grw-product-to-spec-migration.md` diff --git a/docs/specs/completed/2026-04-13-grw-sdd-socratic-harness-workflow.md b/docs/specs/completed/2026-04-13-grw-sdd-socratic-harness-workflow.md deleted file mode 100644 index 82ea1f3..0000000 --- a/docs/specs/completed/2026-04-13-grw-sdd-socratic-harness-workflow.md +++ /dev/null @@ -1,155 +0,0 @@ -# 2026-04-13-grw-sdd-socratic-harness-workflow - -- Status: `Completed` -- Primary Repo: `git-ranker-workflow` -- Related Issue: `없음` -- Related PR: `not created in this session` - -## Request Summary - -Harness 시스템에서 `exec plan` 개념을 완전히 제거하고, SDD와 소크라테스 프롬프트를 적용한 새로운 workflow를 구축한다. - -## Problem - -기존 Harness는 request routing, exec plan, context pack, verification artifact가 분리되어 있었고, 사용자가 원하는 `답 내놓기 금지, 먼저 캐물어라` 기반 spec-first workflow와 맞지 않았다. - -## Goals - -- `exec plan` 개념과 `docs/exec-plans/` 트리를 제거한다. -- 모든 즉시 실행 가능한 작업이 소크라테스 기반 approved spec을 먼저 갖도록 바꾼다. -- spec 하나가 요구사항, 하위 작업, write scope, verification, tracking 결정을 함께 소유하도록 재설계한다. -- issue 생성은 conditional로 두고, publish가 필요한 결과만 PR로 공개하는 흐름을 유지한다. -- current active work는 새 `docs/specs/active/`로 이관한다. - -## Non-goals - -- backend/frontend repo 구현 작업 자체 수행 -- old completed exec plan historical record 보존 -- repo-local app source of truth 재설계 - -## Socratic Clarification Summary - -- 모든 즉시 실행 가능한 작업에 소크라테스 기반 spec을 필수 적용한다. -- spec 고정 조건은 `Harness 판단 + 사용자 승인`이다. -- issue 생성은 추적이 필요할 때만 한다. -- `exec plan`은 완전히 제거하고, 하위 작업 정의도 spec 안에서 처리한다. -- 기존 `docs/exec-plans/`는 archive 없이 제거한다. - -## Assumptions And Constraints - -- workflow는 control plane만 소유한다. -- tracking issue와 implementation PR은 spec이 요구할 때만 만든다. -- 기존 active backend 작업 2건은 새 active spec으로 이관해 canonical working artifact 공백을 막는다. - -## Write Scope -- Primary repo: `git-ranker-workflow` -- Allowed write paths: - - `AGENTS.md` - - `SPECS.md` - - `docs/` - - `.github/` - - `.codex/skills/` -- Control-plane artifacts: - - `docs/specs/completed/2026-04-13-grw-sdd-socratic-harness-workflow.md` - - `docs/specs/active/2026-04-09-grb-04-backend-verification-contract-reset.md` - - `docs/specs/active/2026-04-09-grb-06-backend-test-ci-removal.md` -- Explicitly forbidden: - - sibling app repo code change - - app repo implementation start -- Network / external systems: - - 없음 -- Escalation triggers: - - 없음 - -## Acceptance Criteria - -- source of truth 문서가 `exec plan` 없이 spec-first workflow를 설명한다. -- `docs/exec-plans/`가 제거된다. -- project-local skill registry가 `socratic-spec-authoring` 중심 순서로 재정렬된다. -- active backend work 2건이 `docs/specs/active/`로 이관된다. -- old `exec plan` 용어가 stable source of truth와 skill layer에서 제거된다. - -## Verification -- Contract profile: `workflow-docs` -- Commands: - - `sed -n '1,220p' AGENTS.md` - - `sed -n '1,220p' SPECS.md` - - `sed -n '1,260p' docs/operations/sdd-spec-policy.md` - - `sed -n '1,260p' docs/operations/workflow-governance.md` - - `sed -n '1,260p' docs/architecture/harness-system-map.md` - - `sed -n '1,240p' .codex/skills/README.md` - - `find docs/specs -maxdepth 2 -type f | sort` - - `rg -n "Socratic|소크라테스|approved spec|docs/specs|user final|user validation|Detailed Subtasks|verification contract profile" AGENTS.md SPECS.md docs .codex .github` - - `rg -n "exec plan|docs/exec-plans|issue-to-exec-plan|PLANS.md|task brief" AGENTS.md SPECS.md docs/architecture docs/operations docs/product .codex .github` - - `git diff --check` - -## Delivery And Tracking Plan -- Lane: `guarded lane` -- Parent issue needed: `no` -- PR needed: `conditional` - -## Detailed Subtasks -- `source-of-truth-migration` - - Target repo: `git-ranker-workflow` - - Goal: architecture, operations, product, root entry docs를 spec-first workflow로 재작성한다. - - Done when: stable source of truth가 approved spec 중심 흐름을 설명한다. - - Verification hook: workflow-docs grep and `sed` review - - Tracking needed: `no` -- `skill-layer-migration` - - Target repo: `git-ranker-workflow` - - Goal: `issue-to-exec-plan`을 제거하고 `socratic-spec-authoring`을 새 기본 진입점으로 추가한다. - - Done when: skill registry와 핵심 skill이 새 순서를 따른다. - - Verification hook: skill README and skill file review - - Tracking needed: `no` -- `active-work-migration` - - Target repo: `git-ranker-workflow` - - Goal: 기존 active backend work 2건을 새 active spec으로 이관한다. - - Done when: `docs/specs/active/`에 대응 spec이 존재한다. - - Verification hook: `find docs/specs -maxdepth 2 -type f | sort` - - Tracking needed: `no` -- `exec-plan-removal` - - Target repo: `git-ranker-workflow` - - Goal: old `docs/exec-plans/` tree와 obsolete skill asset을 제거한다. - - Done when: old terminology grep이 비어 있다. - - Verification hook: negative `rg` - - Tracking needed: `no` - -## Verification Summary - -- Contract profile: `workflow-docs` -- Overall status: `passed` -- Ran: - - `sed -n '1,220p' AGENTS.md` - - `sed -n '1,220p' SPECS.md` - - `sed -n '1,260p' docs/operations/sdd-spec-policy.md` - - `sed -n '1,260p' docs/operations/workflow-governance.md` - - `sed -n '1,260p' docs/architecture/harness-system-map.md` - - `sed -n '1,240p' .codex/skills/README.md` - - `find docs/specs -maxdepth 2 -type f | sort` - - `rg -n "Socratic|소크라테스|approved spec|docs/specs|user final|user validation|Detailed Subtasks|verification contract profile" AGENTS.md SPECS.md docs .codex .github` - - `rg -n "exec plan|docs/exec-plans|issue-to-exec-plan|PLANS.md|task brief" AGENTS.md SPECS.md docs/architecture docs/operations docs/product .codex .github` - - `git diff --check` -- Evidence: - - root entrypoint가 `SPECS.md`와 `docs/specs/` 기준으로 바뀌었다. - - workflow governance, state machine, verification/review/feedback policy가 모두 approved spec vocabulary를 사용한다. - - skill registry가 `request-intake -> ambiguity-interview -> socratic-spec-authoring` 순서로 바뀌었다. - - `docs/specs/active/`에 현재 active backend 작업 2건이 이관됐다. - - stable source of truth와 skill/template layer에서 old `exec plan` terminology grep은 결과가 없었고, completed migration spec만 제거 대상을 설명하기 위해 예외적으로 해당 용어를 포함한다. -- Failure or skipped summary: 없음 -- Next action: user validation - -## Final Change Summary - -- `SPECS.md`와 `docs/specs/` 구조를 추가했다. -- `docs/operations/sdd-spec-policy.md`를 추가하고 runtime governance를 spec-first로 재작성했다. -- `docs/architecture/`, `docs/product/`, `.github/`, `.codex/skills/`를 approved spec 기준으로 정렬했다. -- `issue-to-exec-plan` skill과 `docs/exec-plans/` tree를 제거했다. - -## Final User Validation - -- 사용자는 소크라테스 loop에서 아래 결정을 직접 승인했다. - - 모든 즉시 실행 가능한 작업에 spec 필수 - - spec 고정 조건은 Harness 판단 + 사용자 승인 - - issue 생성은 추적이 필요할 때만 - - `exec plan` 완전 제거 -- 이번 turn의 구현 결과는 그 승인된 방향에 맞게 반영되었다. diff --git a/docs/specs/completed/2026-04-13-grw-socratic-loop-hardening.md b/docs/specs/completed/2026-04-13-grw-socratic-loop-hardening.md deleted file mode 100644 index 51cfe19..0000000 --- a/docs/specs/completed/2026-04-13-grw-socratic-loop-hardening.md +++ /dev/null @@ -1,190 +0,0 @@ -# 2026-04-13-grw-socratic-loop-hardening - -- Status: `Completed` -- Primary Repo: `git-ranker-workflow` -- Related Issue: `없음` -- Related PR: `not created in this session` - -## Request Summary - -Harness의 소크라테스 기반 spec workflow를 강화해, spec 결함이 늦게 드러나면 재질의응답과 재승인 루프로 되돌아가고, spec 문서 자체가 그 루프의 증거를 더 명확히 남기도록 정렬한다. - -## Problem - -현재 workflow는 spec-first 원칙 자체는 갖고 있지만, review나 user validation 단계에서 spec 결함이 드러났을 때 canonical state machine이 재승인 loop를 강하게 표현하지 못한다. 또한 Socratic skill/policy/template가 질문 이유, 전제 점검, 프레이밍 재검토, clarification log를 충분히 강제하지 않아 "통과할 때까지 캐묻고 spec을 고정한다"는 운영 의도가 약하게 남아 있다. - -## Goals - -- review, repair, user validation에서 spec 결함이 드러나면 `Spec Drafting`으로 되돌아가 재승인받는 semantics를 source of truth에 추가한다. -- Socratic policy와 skill에 질문 이유, 전제 점검, 프레이밍 재검토, 예외 탐색, approval gate를 명시한다. -- spec template과 spec docs에 clarification log와 approval gate checklist를 반영한다. - -## Non-goals - -- backend/frontend app repo 구현 변경 -- issue/PR 운영 모델 전면 재설계 -- 새로운 tracking surface 추가 - -## Socratic Clarification Log - -### Round 1 -- Prompted gap: 소크라테스 프롬프트 강도와 Harness 반영 수준을 검토해 달라는 요청 -- Why it mattered: skill 단위 검토에 그칠지, state machine과 policy까지 바꿔야 할지 scope를 잠가야 했다 -- Answer / evidence: - - 사용자 설명과 existing docs를 대조한 결과, 문제는 skill 단독이 아니라 architecture/policy/template 전반에 걸쳐 있었다 - - review 결과로 `spec reopen semantics`, `stronger socratic contract`, `spec artifact hardening` 세 축이 도출됐다 -- Closed gap: 이번 작업이 workflow stable source of truth + project-local skill + spec template hardening이라는 점을 고정했다 - -### Round 2 -- Prompted gap: 리뷰 결과를 실제로 수정까지 진행할지 여부 -- Why it mattered: stable source of truth 편집은 승인된 spec 없이는 시작하면 안 된다 -- Answer / evidence: - - 사용자 응답: `진행해주세요.` - - 직전 리뷰에서 제안한 우선순위는 `state machine -> skill/policy -> template` -- Closed gap: 사용자가 위 수정축을 그대로 진행하는 데 동의했다고 본다 - -### Round 3 -- Prompted gap: `.codex/skills/ambiguity-interview/SKILL.md`도 같은 작업 범위로 묶을지 여부 -- Why it mattered: ambiguity 단계가 그대로 남으면 stronger Socratic contract가 intake 직후 첫 질문 루프에 일관되게 적용되지 않는다 -- Answer / evidence: - - 사용자 응답: `.codex/skills/ambiguity-interview/SKILL.md:1 까지 같은 작업 범위로 묶어서 이어서 진행해주세요.` - - 현재 파일 diff는 YAML description quoting만 있고, behavior hardening은 아직 반영되지 않았다 -- Closed gap: `ambiguity-interview` skill을 같은 hardening scope로 편입해도 된다는 사용자 승인으로 본다 - -## Assumptions And Constraints - -- 이번 작업은 `git-ranker-workflow` control-plane 문서와 project-local skill만 수정한다. -- blanket rule로 "항상 3개 질문"을 강제하기보다, 기존 one-blocker default를 유지하면서 stronger Socratic checks와 reopen loop를 추가한다. -- approval evidence는 이 spec의 clarification log와 approval section으로 남긴다. -- ambiguity 단계는 spec authoring 전 단계이므로, 여기서는 multiple-goal/primary-repo ambiguity를 줄이는 질문 contract만 강화하고 spec reopen semantics 자체를 넣지는 않는다. - -## Approval Gate -- Problem and goal locked: `yes` -- Non-goals explicit: `yes` -- Primary repo and write scope locked: `yes` -- Verification method locked: `yes` -- Subtask split decided: `yes` -- Tracking decision locked: `yes` -- Remaining blockers: `none` - -## Write Scope -- Primary repo: `git-ranker-workflow` -- Allowed write paths: - - `docs/architecture/` - - `docs/operations/` - - `docs/specs/` - - `.codex/skills/ambiguity-interview/` - - `.codex/skills/socratic-spec-authoring/` -- Control-plane artifacts: - - `docs/specs/completed/2026-04-13-grw-socratic-loop-hardening.md` -- Explicitly forbidden: - - sibling app repo code or docs changes - - unrelated workflow policy rewrites outside Socratic/spec loop hardening -- Network / external systems: - - 없음 -- Escalation triggers: - - 없음 - -## Acceptance Criteria - -- architecture/governance 문서가 review, repair, user validation에서 spec 결함 발견 시 `Spec Drafting` 재진입과 재승인을 명시한다. -- Socratic policy와 관련 skill이 질문 이유, 전제 점검, 프레이밍 재검토, 예외 탐색, approval gate 또는 handoff 조건을 명시한다. -- spec template 또는 spec docs가 round-based clarification log와 approval gate checklist를 남길 수 있다. - -## Verification -- Contract profile: `workflow-docs` -- Commands: - - `sed -n '1,260p' docs/architecture/harness-system-map.md` - - `sed -n '1,260p' docs/operations/sdd-spec-policy.md` - - `sed -n '1,260p' docs/operations/workflow-governance.md` - - `sed -n '1,220p' docs/specs/README.md` - - `sed -n '1,220p' .codex/skills/ambiguity-interview/SKILL.md` - - `sed -n '1,220p' .codex/skills/socratic-spec-authoring/SKILL.md` - - `sed -n '1,220p' .codex/skills/socratic-spec-authoring/assets/spec-template.md` - - `rg -n "Spec Drafting|clarification log|Approval Gate|framing|전제|왜 지금 필요한지|spec defect|재승인|ambiguity|single requirement|primary repo" docs .codex/skills SPECS.md` - - `git diff --check` - -## Delivery And Tracking Plan -- Lane: `guarded lane` -- Parent issue needed: `no` -- PR needed: `conditional` - -## Detailed Subtasks -- `state-machine-reopen-hardening` - - Target repo: `git-ranker-workflow` - - Goal: review/user validation/repair 단계에서 spec defect를 canonical하게 `Spec Drafting`으로 되돌린다. - - In-scope: architecture and workflow governance source of truth - - Done when: reopen path와 재승인 규칙이 문서에 명시된다. - - Verification hook: state-machine and governance doc review - - Tracking needed: `no` -- `socratic-contract-hardening` - - Target repo: `git-ranker-workflow` - - Goal: stronger Socratic questioning contract를 policy와 intake/spec-authoring skill에 반영한다. - - In-scope: Socratic rules, approval gate, anti-pattern, ambiguity-to-spec handoff - - Done when: 질문 이유, 전제 점검, 프레이밍 재검토, 예외 탐색, ambiguity handoff 조건이 문서상 명시된다. - - Verification hook: policy and skill grep/review - - Tracking needed: `no` -- `spec-artifact-hardening` - - Target repo: `git-ranker-workflow` - - Goal: spec artifact가 clarification loop evidence를 더 구조적으로 남긴다. - - In-scope: spec docs guidance and template - - Done when: clarification log와 approval gate checklist가 template/docs에 나타난다. - - Verification hook: spec template and docs review - - Tracking needed: `no` - -## Risks Or Open Questions - -- stronger Socratic contract를 너무 공격적으로 쓰면 one-blocker rule과 충돌할 수 있으므로, one-blocker default는 유지해야 한다. -- state machine에 reopen path를 추가해도 실제 수행 품질은 implementer discipline에 계속 의존한다. - -## Approval -- Harness judgment: review에서 드러난 gap은 stable source of truth와 skill/template을 함께 고쳐야 닫힌다. -- User approval: 사용자가 직전 리뷰 제안에 대해 `진행해주세요.`라고 답해 이 수정축을 그대로 진행하는 데 동의했다. - -## Verification Summary - -- Contract profile: `workflow-docs` -- Overall status: `passed` -- Ran: - - `sed -n '1,260p' docs/architecture/harness-system-map.md` - - `sed -n '1,260p' docs/operations/sdd-spec-policy.md` - - `sed -n '1,260p' docs/operations/workflow-governance.md` - - `sed -n '1,220p' docs/specs/README.md` - - `sed -n '1,220p' .codex/skills/ambiguity-interview/SKILL.md` - - `sed -n '1,220p' .codex/skills/socratic-spec-authoring/SKILL.md` - - `sed -n '1,220p' .codex/skills/socratic-spec-authoring/assets/spec-template.md` - - `rg -n "Spec Drafting|clarification log|Approval Gate|framing|전제|왜 지금 필요한지|spec defect|재승인|ambiguity|single requirement|primary repo" docs .codex/skills SPECS.md` - - `git diff --check` -- Evidence: - - state machine과 pass/fail semantics에 `Spec Drafting` reopen path가 추가됐다. - - governance와 spec policy가 late-discovered spec defect를 `Draft` 재승인 루프로 되돌리도록 정렬됐다. - - `ambiguity-interview`가 one-blocker + why-needed + handoff summary contract를 갖추게 됐다. - - Socratic skill과 spec template이 `clarification log` 및 `approval gate` 구조를 갖추게 됐다. - - spec docs와 active-spec rules가 reopened draft semantics를 설명한다. -- Failure or skipped summary: 없음 -- Next action: completed - -## Final Change Summary - -- `SPECS.md`, `docs/specs/README.md`, `docs/specs/active/README.md`가 reopened draft semantics와 clarification evidence expectations를 설명하도록 정렬됐다. -- `docs/architecture/harness-system-map.md`가 `Spec Reopen Path`와 late-discovered spec defect의 canonical consequence를 state machine에 추가했다. -- `docs/operations/sdd-spec-policy.md`, `docs/operations/workflow-governance.md`가 재질의응답, approval gate, 재승인 루프를 runtime rule로 고정했다. -- `.codex/skills/ambiguity-interview/SKILL.md`, `.codex/skills/socratic-spec-authoring/SKILL.md`, `.codex/skills/socratic-spec-authoring/assets/spec-template.md`가 stronger Socratic questioning contract와 handoff evidence 구조를 반영했다. - -## Final User Validation - -- 사용자 응답 `이어서 진행해주세요.`를 close-out continuation이자 결과 확정 신호로 해석했다. -- 사용자가 같은 작업 범위로 `ambiguity-interview`까지 편입하는 데 동의했고, 그 범위를 포함한 verification이 다시 통과했다. -- 이번 결과는 요청한 방향대로 Socratic loop hardening을 source of truth, skill, template에 반영했다. - -## Docs Updated - -- `SPECS.md` -- `docs/architecture/harness-system-map.md` -- `docs/operations/sdd-spec-policy.md` -- `docs/operations/workflow-governance.md` -- `docs/specs/README.md` -- `docs/specs/active/README.md` -- `.codex/skills/ambiguity-interview/SKILL.md` -- `.codex/skills/socratic-spec-authoring/SKILL.md` -- `.codex/skills/socratic-spec-authoring/assets/spec-template.md` diff --git a/docs/specs/completed/2026-04-14-grw-pr88-review-feedback.md b/docs/specs/completed/2026-04-14-grw-pr88-review-feedback.md deleted file mode 100644 index d1e8998..0000000 --- a/docs/specs/completed/2026-04-14-grw-pr88-review-feedback.md +++ /dev/null @@ -1,148 +0,0 @@ -# 2026-04-14-grw-pr88-review-feedback - -- Status: `Completed` -- Primary Repo: `git-ranker-workflow` -- Related Issue: `없음` -- Related PR: `alexization/git-ranker-workflow#88` - -## Request Summary - -PR `#88`의 코드리뷰를 검토하고, backend/frontend merge 이후 workflow submodule pointer와 review feedback을 실제 publish 상태에 맞춘다. - -## Problem - -PR `#88`은 `GRB-07`, `GRC-07` close-out을 completed로 옮겼지만 workflow gitlink는 backend를 pre-merge commit에, frontend를 feature branch tip에 두고 있었다. 이 상태로는 workflow revision 하나만 checkout해도 두 sibling repo의 delivered bootstrap artifact를 merged baseline으로 재현할 수 없어 federated handoff traceability가 흔들린다. - -## Goals - -- `git-ranker` gitlink를 backend PR `#81` merge commit으로 맞춘다. -- `git-ranker-client` gitlink를 frontend PR `#9` merge commit으로 맞춘다. -- PR `#88` review feedback과 repair evidence를 workflow completed history에 남긴다. - -## Non-goals - -- PR review thread reply 또는 resolve -- sibling repo에 추가 코드 변경 -- workflow PR 본문/제목 전면 재작성 - -## Socratic Clarification Log - -### Round 1 -- Prompted gap: workflow PR `#88`에서 실제 반영해야 할 actionable review feedback과 final publish target이 무엇인지 -- Why it mattered: informational comment까지 섞거나 pre-merge gitlink를 그대로 두면 repair 범위가 불필요하게 넓어지거나 traceability gap이 남는다 -- User answer / evidence: - - 사용자는 backend와 frontend를 모두 merge했다고 알리고, workflow 재정렬과 workflow PR review 검토 및 반영을 요청했다 - - PR `#88` thread-aware review 조회 결과 unresolved actionable thread는 backend submodule sync 한 건이었다 - - backend PR `#81` merge commit은 `2fc52c3d196bc6814c2f615a45162af4eafb4751`, frontend PR `#9` merge commit은 `16d5d03a29ea8bfc9016b5f1ccc2508b1f282ec6`였다 -- Closed gap: 이번 repair는 두 submodule gitlink를 merged result로 재정렬하고 그 evidence를 기록하는 workflow-only slice로 잠근다 -- Remaining blocker: `none` - -## Assumptions And Constraints - -- backend/frontend PR은 모두 merged 상태이며 workflow는 merged result를 가리켜야 한다. -- review reply나 thread resolve는 사용자 요청이 없어 수행하지 않는다. -- workflow repo는 sibling repo 구현 세부를 복제하지 않고 publish된 commit pointer만 정렬한다. - -## Approval Gate - -- Problem and goal locked: `yes` -- Non-goals explicit: `yes` -- Primary repo and write scope locked: `yes` -- Verification method locked: `yes` -- Subtask split decided: `yes` -- Tracking decision locked: `yes` -- Remaining blockers: `none` - -## Write Scope - -- Primary repo: `git-ranker-workflow` -- Allowed write paths: - - `git-ranker` - - `git-ranker-client` - - `docs/specs/completed/2026-04-14-grw-pr88-review-feedback.md` -- Control-plane artifacts: - - `docs/specs/completed/2026-04-14-grw-pr88-review-feedback.md` -- Explicitly forbidden: - - sibling repo 추가 코드 편집 - - unrelated workflow policy/docs rewrite -- Network / external systems: - - GitHub PR metadata and review context for `alexization/git-ranker-workflow#88` - - GitHub PR metadata for backend/frontend merged PR lookup -- Escalation triggers: - - submodule checkout, git add, git commit, git push - -## Acceptance Criteria - -- workflow repo의 `git-ranker` gitlink가 backend PR `#81` merge commit을 가리킨다. -- workflow repo의 `git-ranker-client` gitlink가 frontend PR `#9` merge commit을 가리킨다. -- PR `#88`의 actionable review feedback이 current diff 기준으로 설명 가능해진다. - -## Verification - -- Contract profile: `workflow-docs` -- Commands: - - `gh api graphql -f query='...' -F owner=alexization -F repo=git-ranker-workflow -F number=88` - - `git diff --submodule=log -- git-ranker git-ranker-client` - - `git submodule status` - - `git diff --check` - -## Delivery And Tracking Plan - -- Lane: `default lane` -- Parent issue needed: `no` -- PR needed: `already open` - -## Detailed Subtasks - -- `review-thread-triage` - - Target repo: `git-ranker-workflow` - - Goal: PR `#88` review feedback 중 실제 수정이 필요한 thread만 추린다. - - In-scope: reviewThreads 조회, actionable 여부 판단 - - Done when: unresolved actionable feedback가 gitlink repair로 수렴한다. - - Verification hook: review context readback - - Tracking needed: `no` -- `submodule-pointer-realignment` - - Target repo: `git-ranker-workflow` - - Goal: backend/frontend submodule pointer를 merged baseline으로 맞춘다. - - In-scope: `git-ranker`, `git-ranker-client` gitlink update - - Done when: workflow checkout 하나로 merged sibling repo artifact를 재현할 수 있다. - - Verification hook: submodule diff + status - - Tracking needed: `no` - -## Risks Or Open Questions - -- submodule pointer만 갱신하므로 sibling repo follow-up change가 이후 추가되면 workflow는 다시 재정렬이 필요할 수 있다. - -## Approval - -- Harness judgment: review feedback이 bounded gitlink repair 하나로 닫히고, frontend도 이미 merged 되었으므로 두 pointer를 함께 final merged baseline으로 맞추는 것이 일관적이다. -- User approval: 사용자가 workflow 재정렬과 workflow PR review feedback 반영을 직접 요청했다. - -## Verification Summary - -- Contract profile: `workflow-docs` -- Overall status: `passed` -- Review evidence: - - thread-aware GraphQL 조회에서 unresolved actionable review thread는 backend submodule sync 한 건이었다. - - backend PR `#81`와 frontend PR `#9`는 모두 merged 상태였고 각 merge commit은 `2fc52c3`, `16d5d03`였다. -- Ran: - - `gh api graphql -f query='...' -F owner=alexization -F repo=git-ranker-workflow -F number=88` - - `git diff --submodule=log -- git-ranker git-ranker-client` - - `git submodule status` - - `git diff --check` -- Evidence: - - `git-ranker` gitlink가 backend PR `#81` merge commit `2fc52c3`로 이동했다. - - `git-ranker-client` gitlink가 frontend PR `#9` merge commit `16d5d03`로 이동했다. - - current diff는 workflow PR `#88`의 actionable review feedback을 direct fix로 설명할 수 있다. -- Failure or skipped summary: review reply / thread resolve는 사용자 요청이 없어 수행하지 않았다. -- Next action: push repair commit to PR `#88` - -## Final Change Summary - -- backend/frontend submodule pointer를 모두 merged baseline으로 재정렬했다. -- workflow PR `#88` review feedback을 thread-aware 기준으로 재검토하고, 필요한 repair만 반영했다. - -## Final User Validation - -- 사용자는 backend와 frontend merge 완료를 알린 뒤 workflow 재정렬과 workflow PR review feedback 반영을 요청했다. -- 이번 변경은 sibling repo 추가 수정 없이 workflow repo에서 final publish pointer와 review evidence를 정리하는 close-out repair다. diff --git a/docs/specs/completed/2026-04-14-grw-pr90-review-feedback.md b/docs/specs/completed/2026-04-14-grw-pr90-review-feedback.md deleted file mode 100644 index 932916e..0000000 --- a/docs/specs/completed/2026-04-14-grw-pr90-review-feedback.md +++ /dev/null @@ -1,126 +0,0 @@ -# 2026-04-14-grw-pr90-review-feedback - -- Status: `Completed` -- Primary Repo: `git-ranker-workflow` -- Related Issue: `없음` -- Related PR: `alexization/git-ranker-workflow#90` - -## Request Summary - -PR `#90`의 코드리뷰를 검토하고, actionable feedback이 있으면 workflow docs/skill에 반영한다. - -## Problem - -PR `#90` review에서 `.codex/skills/request-intake/SKILL.md`가 `대화` route와 `Rejected` route를 같은 종료 처리로 묶어 canonical routing policy와 충돌한다는 피드백이 들어왔다. 이 상태면 conversation-only 요청에서 실제 답변 대신 close-out만 남기고 종료하는 잘못된 handoff를 유도할 수 있다. - -## Goals - -- `request-intake` skill이 `대화` route에서는 실제 답변을 제공한다고 명시한다. -- `Rejected` route만 close-out reason으로 종료되도록 구분한다. -- review feedback과 repair evidence를 spec으로 남긴다. - -## Non-goals - -- review thread reply 또는 resolve -- 다른 skill이나 policy 문구 전면 재정리 -- PR 본문 재작성 - -## Socratic Clarification Log - -### Round 1 -- Prompted gap: review feedback 중 실제 수정이 필요한 actionable item이 몇 건인지 먼저 잠가야 했다. -- Why it mattered: informational review까지 포함하면 범위가 불필요하게 커지고, unrelated cleanup이 다시 섞일 수 있다. -- User answer / evidence: - - 사용자는 PR review를 검토하고 반영할 피드백이 있으면 적용해 달라고 요청했다. - - PR `#90` flat review comments에는 actionable feedback이 `request-intake`의 `대화` route 처리 충돌 1건만 보였다. -- Closed gap: 이번 repair는 `request-intake` skill의 `대화` route handling 문구 수정으로 한정한다. -- Remaining blocker: `none` - -## Assumptions And Constraints - -- 현재 visible review comment 하나가 repair 대상의 전부라고 본다. -- canonical source는 `docs/operations/request-routing-policy.md`이고, skill은 그 policy를 따라야 한다. -- review reply나 thread resolve는 사용자 요청이 없어 수행하지 않는다. - -## Approval Gate -- Problem and goal locked: `yes` -- Non-goals explicit: `yes` -- Primary repo and write scope locked: `yes` -- Verification method locked: `yes` -- Subtask split decided: `no` -- Tracking decision locked: `yes` -- Remaining blockers: `none` - -## Write Scope -- Primary repo: `git-ranker-workflow` -- Allowed write paths: - - `.codex/skills/request-intake/` - - `docs/specs/active/2026-04-14-grw-pr90-review-feedback.md` - - `docs/specs/completed/2026-04-14-grw-pr90-review-feedback.md` -- Control-plane artifacts: - - `docs/specs/completed/2026-04-14-grw-pr90-review-feedback.md` -- Explicitly forbidden: - - other policy/skill broad rewrites - - review reply / resolve writes to GitHub - - sibling repo changes -- Network / external systems: - - GitHub PR `alexization/git-ranker-workflow#90` review comment read -- Escalation triggers: - - git add / commit / push - -## Acceptance Criteria - -- `.codex/skills/request-intake/SKILL.md`가 `대화` route에서 답변 제공을 분명히 적는다. -- `Rejected` route만 close-out reason으로 종료되도록 구분된다. -- review feedback과 verification evidence가 spec에 남는다. - -## Verification -- Contract profile: `workflow-docs` -- Commands: - - `sed -n '1,120p' .codex/skills/request-intake/SKILL.md` - - `sed -n '1,120p' docs/operations/request-routing-policy.md` - - `git diff --check` - -## Delivery And Tracking Plan -- Lane: `default lane` -- Parent issue needed: `no` -- PR needed: `already open` - -## Detailed Subtasks -- 없음 - -## Risks Or Open Questions - -- thread-aware unresolved state는 flat comment surface만으로 완전히 보이지 않을 수 있지만, 현재 visible actionable comment는 이 한 건이다. - -## Approval -- Harness judgment: current repair는 bounded skill wording fix 하나로 닫힌다. -- User approval: 사용자가 PR review를 검토하고 반영 가능한 feedback을 적용해 달라고 직접 요청했다. - -## Verification Summary - -- Contract profile: `workflow-docs` -- Overall status: `passed` -- Review evidence: - - actionable review comment는 `.codex/skills/request-intake/SKILL.md`에서 `대화` route가 close-out reason만 남기고 종료되는 것으로 읽힐 수 있다는 지적 1건이었다. -- Ran: - - `sed -n '1,120p' .codex/skills/request-intake/SKILL.md` - - `sed -n '1,120p' docs/operations/request-routing-policy.md` - - `git diff --check` -- Evidence: - - `request-intake` skill이 `대화` route에서는 사용자 질문에 대한 답변을 제공하고 종료한다고 명시한다. - - `Rejected` route만 close-out reason으로 종료된다고 분리됐다. - - skill 문구가 canonical routing policy의 `대화` route semantics와 다시 일치한다. -- Failure or skipped summary: - - review reply / thread resolve는 사용자 요청이 없어 수행하지 않았다. -- Next action: push repair commit to PR `#90` - -## Final Change Summary - -- `request-intake` skill에서 `대화` route와 `Rejected` route의 종료 semantics를 분리했다. -- `대화` route는 실제 답변을 제공한다는 점을 명시적으로 적었다. - -## Final User Validation - -- 사용자는 PR review를 검토하고 반영할 수 있는 feedback이 있으면 적용해 달라고 요청했다. -- 이번 repair는 visible actionable feedback 1건을 bounded skill wording fix로 반영한 결과다. diff --git a/docs/specs/completed/2026-04-14-grw-skill-policy-alignment-publish.md b/docs/specs/completed/2026-04-14-grw-skill-policy-alignment-publish.md deleted file mode 100644 index 63e6b2e..0000000 --- a/docs/specs/completed/2026-04-14-grw-skill-policy-alignment-publish.md +++ /dev/null @@ -1,147 +0,0 @@ -# 2026-04-14-grw-skill-policy-alignment-publish - -- Status: `Completed` -- Primary Repo: `git-ranker-workflow` -- Related Issue: `없음` -- Related PR: `#90` - -## Request Summary - -현재 worktree에 쌓여 있는 workflow policy/skill 정리와 stale reference cleanup 변경을 의도적으로 커밋하고 GitHub에 publish한다. - -## Problem - -workflow policy/skill alignment와 stale reference cleanup 변경은 로컬 worktree에만 있고 아직 commit, push, PR로 공개되지 않았다. 이 저장소 운영 규칙상 publish가 필요한 결과는 latest verification 뒤 open PR surface로 공개해야 한다. - -## Goals - -- 현재 worktree 변경 범위를 하나의 intentional commit으로 고정한다. -- feature branch를 만들어 remote에 push한다. -- latest verification evidence를 확인한 뒤 open PR을 생성한다. - -## Non-goals - -- 현재 diff의 요구사항 재설계 -- unrelated local change 포함 -- sibling repo 변경 - -## Socratic Clarification Log - -### Round 1 -- Prompted gap: 현재 worktree 전체를 이번 publish 범위로 봐도 되는지 먼저 잠가야 했다. -- Why it mattered: mixed worktree면 staging 범위를 따로 분리해야 하고, 잘못 stage하면 unrelated change를 같이 publish하게 된다. -- User answer / evidence: - - 사용자 요청은 `commit/publish 작업을 수행해주세요.`였다. - - 현재 `git status --short`에는 workflow docs/skill 정리와 그 close-out spec만 있어 같은 변경 묶음으로 볼 수 있다. -- Closed gap: 현재 worktree 전체를 같은 publish 범위로 본다. -- Remaining blocker: 없음 - -## Assumptions And Constraints - -- base branch는 현재 `develop`이다. -- 사용자는 draft가 아니라 일반 open PR publish를 요청한 것으로 본다. -- commit message는 `.gitmessage.ko.txt` 형식을 따른다. - -## Approval Gate -- Problem and goal locked: `yes` -- Non-goals explicit: `yes` -- Primary repo and write scope locked: `yes` -- Verification method locked: `yes` -- Subtask split decided: `no` -- Tracking decision locked: `yes` -- Remaining blockers: `none` - -## Write Scope -- Primary repo: `git-ranker-workflow` -- Allowed write paths: - - `.git/` - - `docs/specs/active/2026-04-14-grw-skill-policy-alignment-publish.md` - - `docs/specs/completed/2026-04-14-grw-skill-policy-alignment-publish.md` - - `/tmp/` -- Control-plane artifacts: - - `docs/specs/completed/2026-04-14-grw-skill-policy-alignment-publish.md` -- Explicitly forbidden: - - sibling repo changes - - current diff requirement 변경 -- Network / external systems: - - GitHub remote push - - GitHub PR creation -- Escalation triggers: - - `git push`가 sandbox/network 제한에 막히는 경우 - -## Acceptance Criteria - -- current worktree가 intended commit 하나로 기록된다. -- remote branch가 생성되거나 갱신된다. -- current diff와 맞는 latest verification evidence가 남아 있다. -- open PR link가 생성된다. - -## Verification -- Contract profile: `workflow-publish` -- Commands: - - `git status --short` - - `git diff --check` - - `sed -n '1,220p' docs/operations/request-routing-policy.md` - - `sed -n '1,220p' docs/operations/workflow-governance.md` - - `sed -n '1,220p' .codex/skills/README.md` - - `sed -n '1,180p' .codex/skills/request-intake/SKILL.md` - - `sed -n '1,180p' .codex/skills/socratic-spec-authoring/SKILL.md` - - `sed -n '1,240p' docs/specs/completed/2026-04-14-grw-socratic-skill-policy-alignment.md` - - `sed -n '1,240p' docs/specs/completed/2026-04-14-grw-stale-skill-reference-cleanup.md` - -## Delivery And Tracking Plan -- Lane: `default lane` -- Parent issue needed: `no` -- PR needed: `yes` - -## Detailed Subtasks -- 없음 - -## Risks Or Open Questions - -- remote push는 sandbox/network 정책에 따라 escalation이 필요할 수 있다. - -## Approval -- Harness judgment: 현재 worktree는 하나의 publish unit으로 충분히 묶인다. -- User approval: 사용자가 commit/publish 실행을 직접 요청했다. - -## Verification Summary - -- Contract profile: `workflow-publish` -- Overall status: `passed` -- Ran: - - `git status --short` - - `git diff --check` - - `sed -n '1,220p' docs/operations/request-routing-policy.md` - - `sed -n '1,220p' docs/operations/workflow-governance.md` - - `sed -n '1,220p' .codex/skills/README.md` - - `sed -n '1,180p' .codex/skills/request-intake/SKILL.md` - - `sed -n '1,180p' .codex/skills/socratic-spec-authoring/SKILL.md` - - `sed -n '1,220p' .codex/skills/ambiguity-interview/SKILL.md` - - `sed -n '1,220p' .codex/skills/api-contract-sync/SKILL.md` - - `sed -n '1,240p' docs/specs/completed/2026-04-14-grw-socratic-skill-policy-alignment.md` - - `sed -n '1,240p' docs/specs/completed/2026-04-14-grw-stale-skill-reference-cleanup.md` -- Evidence: - - current worktree 전체가 같은 workflow docs/skill 정리 범위로 확인됐다. - - branch `codex/skill-policy-alignment-publish`에서 initial publish commit `04c729b`와 close-out commit `e1e4cf2`가 생성됐다. - - remote push가 성공했고 open PR `#90`이 `develop` 대상으로 생성됐다. -- Failure or skipped summary: - - `gh auth status`는 invalid token 상태였지만 GitHub app으로 PR 생성이 가능했다. -- Next action: completed - -## Final Change Summary - -- workflow docs/skill 정리와 stale reference cleanup 변경을 intentional commit으로 고정했다. -- branch `codex/skill-policy-alignment-publish`를 remote에 push했다. -- open PR `#90`으로 publish를 완료했다. - -## Final User Validation - -- 사용자는 현재 변경분의 commit/publish를 직접 요청했다. -- 이번 결과는 그 요청에 맞게 branch, commit, push, open PR까지 닫았다. - -## Publish Result - -- Branch: `codex/skill-policy-alignment-publish` -- Commit: `e1e4cf2` -- PR: `https://github.com/alexization/git-ranker-workflow/pull/90` diff --git a/docs/specs/completed/2026-04-14-grw-socratic-skill-policy-alignment.md b/docs/specs/completed/2026-04-14-grw-socratic-skill-policy-alignment.md deleted file mode 100644 index b348eb7..0000000 --- a/docs/specs/completed/2026-04-14-grw-socratic-skill-policy-alignment.md +++ /dev/null @@ -1,171 +0,0 @@ -# 2026-04-14-grw-socratic-skill-policy-alignment - -- Status: `Completed` -- Primary Repo: `git-ranker-workflow` -- Related Issue: `없음` -- Related PR: `not created in this session` - -## Request Summary - -Harness workflow가 SDD와 소크라테스식 spec 정의를 중심으로 동작하도록, 기존 policy 문서와 project-local skill 사이의 역할 중복과 충돌을 검토하고 Socratic spec authoring을 약화시키는 내용을 제거한다. - -## Problem - -현재 workflow는 큰 방향에서 `request-intake -> ambiguity-interview -> socratic-spec-authoring` 흐름을 갖고 있지만, policy와 skill이 같은 규칙을 여러 번 재서술하고 있어 drift 여지가 있다. - -## Goals - -- workflow policy에서 route 결정, ambiguity 축소, Socratic spec authoring의 canonical owner를 더 분명히 적는다. -- project-local skill을 thin-layer 원칙에 맞게 줄여 policy 재서술을 최소화한다. -- `api-contract-sync`를 spec authoring 단계와 분리된 implementation/contract-sync stage로 명확히 둔다. - -## Non-goals - -- `request-intake`, `ambiguity-interview`, `socratic-spec-authoring` 세 stage 자체를 제거하거나 합치기 -- backend/frontend sibling repo 코드나 문서 수정 -- global `~/.codex/skills/` 자산 자체 수정 -- verification/review/publish lane 전체를 재설계 - -## Socratic Clarification Log - -### Round 1 -- Prompted gap: 중복을 줄일 때 skill stage를 합칠지, thin-layer로 남길지를 먼저 잠가야 했다. -- Why it mattered: stage 삭제까지 들어가면 request routing과 spec authoring state machine 자체를 다시 설계해야 해서 작업 범위가 달라진다. -- User answer / evidence: - - 사용자 요청은 `ambiguity-interview`, `api-contract-sync`, `request-intake` 등 기존 skill과 policy의 겹침이나 방해 요소를 검토하고 제거해 달라는 것이었다. - - 현재 canonical docs와 completed spec을 읽은 결과, stage 분리 자체보다 policy/skill의 중복 서술이 더 직접적인 문제였다. -- Closed gap: 이번 작업은 stage 유지, canonical ownership 명확화, thin-layer skill 정리, Socratic 방해 문구 제거로 한정한다. -- Remaining blocker: 없음 - -## Assumptions And Constraints - -- user request 자체를 범위 승인 신호로 보고, 별도 parent issue 없이 docs/skills 정리까지 한 번의 workflow-docs loop로 닫는다. -- project-local skill은 policy를 대체하지 않고 stage handoff와 최소 workflow만 설명해야 한다. -- global skill conflict가 보이더라도 이번 변경은 workflow repo 안의 source of truth와 project-local skill에서 먼저 방어선을 세운다. - -## Approval Gate -- Problem and goal locked: `yes` -- Non-goals explicit: `yes` -- Primary repo and write scope locked: `yes` -- Verification method locked: `yes` -- Subtask split decided: `yes` -- Tracking decision locked: `yes` -- Remaining blockers: `none` - -## Write Scope -- Primary repo: `git-ranker-workflow` -- Allowed write paths: - - `docs/operations/` - - `docs/specs/active/2026-04-14-grw-socratic-skill-policy-alignment.md` - - `docs/specs/completed/2026-04-14-grw-socratic-skill-policy-alignment.md` - - `.codex/skills/README.md` - - `.codex/skills/request-intake/` - - `.codex/skills/ambiguity-interview/` - - `.codex/skills/socratic-spec-authoring/` - - `.codex/skills/api-contract-sync/` -- Control-plane artifacts: - - `docs/specs/completed/2026-04-14-grw-socratic-skill-policy-alignment.md` -- Explicitly forbidden: - - sibling app repo code or docs changes - - global `~/.codex/skills/` edits - - unrelated workflow/runtime rewrites outside skill-policy alignment -- Network / external systems: - - 없음 -- Escalation triggers: - - 없음 - -## Acceptance Criteria - -- workflow policy가 route 결정, ambiguity interview, Socratic spec authoring의 경계를 분명히 설명한다. -- `request-intake`, `ambiguity-interview`, `socratic-spec-authoring`가 thin-layer 원칙에 맞게 policy 재서술을 줄인다. -- `api-contract-sync`가 intake/spec authoring 단계와 겹치지 않는 implementation-side contract sync skill로 설명된다. -- skill registry 또는 related README가 위 경계와 ownership을 반영한다. - -## Verification -- Contract profile: `workflow-docs` -- Commands: - - `sed -n '1,260p' docs/operations/request-routing-policy.md` - - `sed -n '1,280p' docs/operations/sdd-spec-policy.md` - - `sed -n '1,320p' docs/operations/workflow-governance.md` - - `sed -n '1,240p' .codex/skills/README.md` - - `sed -n '1,220p' .codex/skills/request-intake/SKILL.md` - - `sed -n '1,220p' .codex/skills/ambiguity-interview/SKILL.md` - - `sed -n '1,260p' .codex/skills/socratic-spec-authoring/SKILL.md` - - `sed -n '1,220p' .codex/skills/api-contract-sync/SKILL.md` - - `rg -n "thin-layer|canonical owner|canonical source|request-intake|ambiguity-interview|socratic-spec-authoring|api-contract-sync" docs/operations .codex/skills -g '*.md'` - - `git diff --check` - -## Delivery And Tracking Plan -- Lane: `default lane` -- Parent issue needed: `no` -- PR needed: `conditional` - -## Detailed Subtasks -- `policy-boundary-alignment` - - Target repo: `git-ranker-workflow` - - Goal: route, ambiguity, Socratic spec authoring 경계를 canonical policy에 명시한다. - - In-scope: `docs/operations/` - - Done when: workflow policy가 각 stage의 canonical owner와 forbidden overlap을 설명한다. - - Verification hook: policy `sed` review and grep - - Tracking needed: `no` -- `skill-thin-layer-refactor` - - Target repo: `git-ranker-workflow` - - Goal: intake-related skill과 contract sync skill 설명을 thin-layer 원칙에 맞게 정리한다. - - In-scope: `.codex/skills/README.md`, selected SKILL.md files - - Done when: skill이 policy를 길게 복제하지 않고 stage goal, inputs, outputs, handoff만 남긴다. - - Verification hook: skill `sed` review and grep - - Tracking needed: `no` - -## Risks Or Open Questions - -- skill을 너무 짧게 줄이면 trigger 품질이 떨어질 수 있으므로 description은 유지하고 본문만 lean하게 줄여야 한다. - -## Approval -- Harness judgment: current problem은 stage model 자체보다 canonical policy와 skill 간 중복에 가깝다. -- User approval: 사용자가 overlap과 Socratic 방해 요소를 검토하고 제거해 달라고 직접 요청했다. - -## Verification Summary - -- Contract profile: `workflow-docs` -- Overall status: `passed` -- Ran: - - `sed -n '1,260p' docs/operations/request-routing-policy.md` - - `sed -n '1,280p' docs/operations/sdd-spec-policy.md` - - `sed -n '1,320p' docs/operations/workflow-governance.md` - - `sed -n '1,240p' .codex/skills/README.md` - - `sed -n '1,220p' .codex/skills/request-intake/SKILL.md` - - `sed -n '1,220p' .codex/skills/ambiguity-interview/SKILL.md` - - `sed -n '1,260p' .codex/skills/socratic-spec-authoring/SKILL.md` - - `sed -n '1,220p' .codex/skills/api-contract-sync/SKILL.md` - - `rg -n "thin-layer|canonical owner|canonical source|request-intake|ambiguity-interview|socratic-spec-authoring|api-contract-sync" docs/operations .codex/skills -g '*.md'` - - `git diff --check` -- Evidence: - - `request-routing-policy.md`가 route/handoff 전용 canonical owner 범위를 명시하고, ambiguity 단계에서 spec-level detail을 미리 잠그지 못하게 했다. - - `.codex/skills/README.md`가 policy ownership과 thin-layer rule을 더 구체적으로 적었다. - - `request-intake`, `ambiguity-interview`, `socratic-spec-authoring`는 policy 재서술을 줄이고 stage goal, handoff, anti-pattern 중심으로 정리됐다. - - `api-contract-sync`는 approved spec 이후의 contract sync skill로 경계가 고정됐다. -- Failure or skipped summary: - - global `~/.codex/skills/`는 non-goal이라 직접 수정하지 않았다. -- Next action: user validation - -## Final Change Summary - -- workflow policy에 route 결정, Socratic spec contract, runtime governance의 canonical owner를 분리해서 적었다. -- repo-local skill registry와 intake/spec-authoring skill을 thin-layer 원칙에 맞게 줄였다. -- `api-contract-sync`를 intake/spec authoring과 겹치지 않는 후행 contract-sync skill로 재정의했다. - -## Final User Validation - -- 사용자 요청은 role overlap과 Socratic 방해 요소를 검토하고 제거하라는 직접적인 실행 요청이었다. -- 이번 결과는 stage 자체를 없애지 않고, policy를 canonical source로 두고 skill을 thin-layer handoff로 줄이는 방향으로 그 요청을 반영했다. - -## Docs Updated - -- `docs/operations/request-routing-policy.md` -- `docs/operations/sdd-spec-policy.md` -- `docs/operations/workflow-governance.md` -- `.codex/skills/README.md` -- `.codex/skills/request-intake/SKILL.md` -- `.codex/skills/ambiguity-interview/SKILL.md` -- `.codex/skills/socratic-spec-authoring/SKILL.md` -- `.codex/skills/api-contract-sync/SKILL.md` diff --git a/docs/specs/completed/2026-04-14-grw-stale-skill-reference-cleanup.md b/docs/specs/completed/2026-04-14-grw-stale-skill-reference-cleanup.md deleted file mode 100644 index 5820281..0000000 --- a/docs/specs/completed/2026-04-14-grw-stale-skill-reference-cleanup.md +++ /dev/null @@ -1,151 +0,0 @@ -# 2026-04-14-grw-stale-skill-reference-cleanup - -- Status: `Completed` -- Primary Repo: `git-ranker-workflow` -- Related Issue: `없음` -- Related PR: `not created in this session` - -## Request Summary - -최근 제거된 local skill을 전제로 남아 있던 stale skill-specific reference를 workflow policy, project-local skill, recent spec artifact에서 제거한다. - -## Problem - -직전 docs/skill 정리에서 특정 local skill 존재를 전제로 한 explicit reference가 여러 문서에 남아 있다. 현재 repo-local skill registry에는 해당 skill이 없으므로, 그 문구가 source of truth와 맞지 않는 stale reference가 됐다. - -## Goals - -- workflow policy와 local skill에서 제거된 skill을 전제로 쓴 direct reference를 제거한다. -- spec-first, thin-layer, canonical ownership 같은 일반 규칙은 유지한다. -- 직전 completed spec artifact에서도 stale reference를 걷어낸다. - -## Non-goals - -- request-routing / Socratic SDD stage 구조 자체 재설계 -- repo 밖 global skill 또는 다른 저장소 수정 -- 일반적인 planning/spec-first 원칙 삭제 - -## Socratic Clarification Log - -### Round 1 -- Prompted gap: 사용자가 지우라고 한 범위가 전체 alignment 변경인지, stale skill reference cleanup인지 먼저 잠겨야 했다. -- Why it mattered: 전체 alignment를 되돌리면 canonical owner, thin-layer 정리까지 같이 사라져 범위가 과도하게 커진다. -- User answer / evidence: - - 사용자 요청은 제거된 local skill을 전제로 남아 있는 명시 문구를 지워 달라는 것이었다. - - 현재 grep 결과는 skill-specific direct reference가 policy, skill, recent spec artifact에만 남아 있고, thin-layer / canonical owner 정리는 별도 축으로 남아 있다. -- Closed gap: 이번 작업은 stale skill-specific reference cleanup으로 한정한다. -- Remaining blocker: 없음 - -## Assumptions And Constraints - -- 해당 local skill removal은 이미 완료된 사실로 본다. -- skill 삭제와 무관한 `spec-first`, `Approved spec`, `thin-layer` 규칙은 유지한다. -- stale reference 제거 대상에는 직전 untracked completed spec도 포함한다. - -## Approval Gate -- Problem and goal locked: `yes` -- Non-goals explicit: `yes` -- Primary repo and write scope locked: `yes` -- Verification method locked: `yes` -- Subtask split decided: `no` -- Tracking decision locked: `yes` -- Remaining blockers: `none` - -## Write Scope -- Primary repo: `git-ranker-workflow` -- Allowed write paths: - - `docs/operations/` - - `.codex/skills/README.md` - - `.codex/skills/request-intake/` - - `.codex/skills/socratic-spec-authoring/` - - `docs/specs/active/2026-04-14-grw-stale-skill-reference-cleanup.md` - - `docs/specs/completed/2026-04-14-grw-socratic-skill-policy-alignment.md` - - `docs/specs/completed/2026-04-14-grw-stale-skill-reference-cleanup.md` -- Control-plane artifacts: - - `docs/specs/completed/2026-04-14-grw-stale-skill-reference-cleanup.md` -- Explicitly forbidden: - - sibling app repo changes - - repo 밖 global skill edits - - removed skill cleanup과 무관한 workflow rule rollback -- Network / external systems: - - 없음 -- Escalation triggers: - - 없음 - -## Acceptance Criteria - -- repo-local docs/skills에서 제거된 skill을 직접 지칭한 stale reference가 제거된다. -- thin-layer, canonical owner, spec-first 같은 일반 규칙은 유지된다. -- recent completed spec artifact가 현재 cleanup 결과와 모순되지 않게 정리된다. - -## Verification -- Contract profile: `workflow-docs` -- Commands: - - targeted stale-reference negative grep over workflow policy, selected local skills, and the recent completed spec - - `sed -n '1,220p' docs/operations/request-routing-policy.md` - - `sed -n '1,220p' docs/operations/sdd-spec-policy.md` - - `sed -n '1,220p' docs/operations/workflow-governance.md` - - `sed -n '1,220p' .codex/skills/README.md` - - `sed -n '1,220p' .codex/skills/request-intake/SKILL.md` - - `sed -n '1,220p' .codex/skills/socratic-spec-authoring/SKILL.md` - - `sed -n '1,240p' docs/specs/completed/2026-04-14-grw-socratic-skill-policy-alignment.md` - - `git diff --check` - -## Delivery And Tracking Plan -- Lane: `default lane` -- Parent issue needed: `no` -- PR needed: `conditional` - -## Detailed Subtasks -- 없음 - -## Risks Or Open Questions - -- 다른 historical artifact에 남아 있는 제거된 skill reference까지 전부 정리할지 여부는 이번 범위 밖이다. - -## Approval -- Harness judgment: stale reference cleanup으로 범위를 좁혀도 사용자 요청을 충분히 충족한다. -- User approval: 사용자가 제거된 skill을 근거로 명시 문구 제거를 직접 요청했다. - -## Verification Summary - -- Contract profile: `workflow-docs` -- Overall status: `passed` -- Ran: - - targeted stale-reference negative grep over workflow policy, selected local skills, and the recent completed spec - - `sed -n '1,220p' docs/operations/request-routing-policy.md` - - `sed -n '1,220p' docs/operations/sdd-spec-policy.md` - - `sed -n '1,220p' docs/operations/workflow-governance.md` - - `sed -n '1,220p' .codex/skills/README.md` - - `sed -n '1,220p' .codex/skills/request-intake/SKILL.md` - - `sed -n '1,220p' .codex/skills/socratic-spec-authoring/SKILL.md` - - `sed -n '1,240p' docs/specs/completed/2026-04-14-grw-socratic-skill-policy-alignment.md` - - `git diff --check` -- Evidence: - - targeted workflow policy와 local skill에서 removed skill을 직접 지칭하던 stale 문구가 제거됐다. - - `spec-first`, `Approved spec`, `thin-layer`, canonical owner 정리는 유지됐다. - - 직전 completed spec도 cleanup 결과와 모순되지 않도록 같이 정리됐다. -- Failure or skipped summary: - - unrelated historical artifact와 `tool-boundary-matrix.md`의 일반 planning vocabulary는 이번 범위 밖이라 건드리지 않았다. -- Next action: completed - -## Final Change Summary - -- workflow policy와 selected local skill에서 removed skill-specific direct reference를 제거했다. -- spec-first와 thin-layer 정리는 유지하고, stale direct mention만 걷어냈다. -- recent completed spec artifact도 현재 상태와 맞게 업데이트했다. - -## Final User Validation - -- 사용자가 제거된 skill을 전제로 명시 문구 삭제를 직접 요청했고, 이번 결과는 그 cleanup 범위만 반영했다. -- 결과적으로 repo-local source of truth와 skill layer에서는 해당 removed skill을 전제로 한 direct reference가 남지 않게 정리됐다. - -## Docs Updated - -- `docs/operations/request-routing-policy.md` -- `docs/operations/sdd-spec-policy.md` -- `docs/operations/workflow-governance.md` -- `.codex/skills/README.md` -- `.codex/skills/request-intake/SKILL.md` -- `.codex/skills/socratic-spec-authoring/SKILL.md` -- `docs/specs/completed/2026-04-14-grw-socratic-skill-policy-alignment.md` diff --git a/docs/specs/completed/README.md b/docs/specs/completed/README.md deleted file mode 100644 index a6a6704..0000000 --- a/docs/specs/completed/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# Completed Specs - -이 디렉터리는 완료되었거나 실행 없이 종료된 spec 문서를 둔다. - -- 상태는 `Completed` 또는 `Rejected`만 허용한다. -- completed spec은 historical record이며, 현재 canonical runtime과 정책은 stable source of truth 문서에서 읽는다. diff --git a/git-ranker b/git-ranker index 3552ddf..fc85948 160000 --- a/git-ranker +++ b/git-ranker @@ -1 +1 @@ -Subproject commit 3552ddf7a417d24b1693fabd38313532993ed179 +Subproject commit fc85948b3d81a07314bb5d85d68afbae9f46ffc9 diff --git a/scripts/hooks/circuit_breaker.py b/scripts/hooks/circuit_breaker.py new file mode 100755 index 0000000..772f323 --- /dev/null +++ b/scripts/hooks/circuit_breaker.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 + +from __future__ import annotations + +import argparse +import json +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from workflow_runtime.engine import WorkflowService # noqa: E402 + + +def main() -> int: + parser = argparse.ArgumentParser(description="Record a failure fingerprint") + parser.add_argument("--error-fingerprint", required=True) + args = parser.parse_args() + + service = WorkflowService() + result = service.record_circuit_breaker(args.error_fingerprint) + print(json.dumps(result.as_payload(), indent=2, ensure_ascii=False)) + return 0 if result.status != "blocked" else 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/hooks/dangerous_cmd_guard.py b/scripts/hooks/dangerous_cmd_guard.py new file mode 100755 index 0000000..1b26f41 --- /dev/null +++ b/scripts/hooks/dangerous_cmd_guard.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python3 + +from __future__ import annotations + +import argparse +import json +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from workflow_runtime.engine import WorkflowService # noqa: E402 + + +def main() -> int: + parser = argparse.ArgumentParser(description="Run Dangerous Command Guard") + parser.add_argument("--command", required=True) + args = parser.parse_args() + + service = WorkflowService() + result = service.dangerous_cmd_guard(args.command) + print(json.dumps(result.as_payload(), indent=2, ensure_ascii=False)) + return 0 if result.status == "passed" else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/hooks/tdd_guard.py b/scripts/hooks/tdd_guard.py new file mode 100755 index 0000000..74d9c8b --- /dev/null +++ b/scripts/hooks/tdd_guard.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python3 + +from __future__ import annotations + +import argparse +import json +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parents[1])) + +from workflow_runtime.engine import WorkflowService # noqa: E402 + + +def main() -> int: + parser = argparse.ArgumentParser(description="Run TDD Guard") + parser.add_argument("--task-id") + parser.add_argument("--phase-id") + parser.add_argument("--changed-path", action="append", default=[]) + parser.add_argument("--staged", action="store_true") + args = parser.parse_args() + + service = WorkflowService() + task = service.load_task(args.task_id) if args.task_id else None + phase = None + if args.task_id and args.phase_id: + _, _, phase = service.current_phase(args.task_id, args.phase_id) + elif task and task["active_phase_id"]: + _, _, phase = service.current_phase(args.task_id, task["active_phase_id"]) + + changed_paths = list(args.changed_path) + if args.staged: + changed_paths.extend(service.staged_or_worktree_paths(staged=True)) + result = service.tdd_guard(sorted(set(changed_paths)), phase) + print(json.dumps(result.as_payload(), indent=2, ensure_ascii=False)) + return 0 if result.status == "passed" else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/workflow.py b/scripts/workflow.py new file mode 100755 index 0000000..467d04a --- /dev/null +++ b/scripts/workflow.py @@ -0,0 +1,7 @@ +#!/usr/bin/env python3 + +from workflow_runtime.cli import main + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/workflow_runtime/__init__.py b/scripts/workflow_runtime/__init__.py new file mode 100644 index 0000000..1694706 --- /dev/null +++ b/scripts/workflow_runtime/__init__.py @@ -0,0 +1 @@ +"""Workflow runtime package.""" diff --git a/scripts/workflow_runtime/cli.py b/scripts/workflow_runtime/cli.py new file mode 100644 index 0000000..81060ec --- /dev/null +++ b/scripts/workflow_runtime/cli.py @@ -0,0 +1,246 @@ +from __future__ import annotations + +import argparse +import json +import sys +from pathlib import Path +from typing import Any + +from workflow_runtime.engine import WorkflowService +from workflow_runtime.models import WorkflowError, emit, read_json + + +def read_phase_source(source_path: str | None, use_stdin: bool) -> Any: + if source_path and use_stdin: + raise WorkflowError("plan accepts only one input source") + if source_path: + path = Path(source_path) + if not path.exists(): + raise WorkflowError(f"phase input file not found: {source_path}") + try: + return read_json(path) + except json.JSONDecodeError as exc: + raise WorkflowError(f"invalid JSON file: {source_path}") from exc + if use_stdin: + text = sys.stdin.read().strip() + if not text: + raise WorkflowError("stdin phase input is empty") + try: + return json.loads(text) + except json.JSONDecodeError as exc: + raise WorkflowError(f"invalid JSON from stdin: {exc.msg}") from exc + return None + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description="Workflow control plane CLI") + subparsers = parser.add_subparsers(dest="command", required=True) + + subparsers.add_parser("init") + subparsers.add_parser("doctor") + + new_parser = subparsers.add_parser("new") + new_parser.add_argument("task_id") + new_parser.add_argument("--title", required=True) + new_parser.add_argument("--primary-repo", required=True) + + approve_parser = subparsers.add_parser("approve") + approve_parser.add_argument("task_id") + approve_parser.add_argument("--note", required=True) + approve_parser.add_argument("--actor", default="user") + + plan_parser = subparsers.add_parser("plan") + plan_parser.add_argument("task_id") + plan_parser.add_argument("--from", dest="source_path") + plan_parser.add_argument("--stdin", action="store_true") + + run_parser = subparsers.add_parser("run") + run_parser.add_argument("task_id") + run_parser.add_argument("--phase-id") + run_parser.add_argument("--start", action="store_true") + run_parser.add_argument("--complete", action="store_true") + run_parser.add_argument("--fail", action="store_true") + run_parser.add_argument("--block", action="store_true") + run_parser.add_argument("--changed-path", action="append", default=[]) + run_parser.add_argument("--note") + run_parser.add_argument("--evidence", action="append", default=[]) + run_parser.add_argument("--error-fingerprint") + + verify_parser = subparsers.add_parser("verify") + verify_parser.add_argument("task_id") + verify_parser.add_argument("--phase-id") + verify_parser.add_argument("--verify-command", action="append", default=[]) + verify_parser.add_argument("--evidence", action="append", default=[]) + + review_parser = subparsers.add_parser("review") + review_parser.add_argument("task_id") + review_parser.add_argument("--note") + review_parser.add_argument("--close", action="store_true") + review_parser.add_argument("--user-validation-note") + + reopen_parser = subparsers.add_parser("reopen") + reopen_parser.add_argument("task_id") + reopen_parser.add_argument("--note", required=True) + reopen_parser.add_argument("--phase-id") + + status_parser = subparsers.add_parser("status") + status_parser.add_argument("task_id", nargs="?") + status_parser.add_argument("--all", action="store_true") + status_parser.add_argument("--check", action="store_true") + status_parser.add_argument("--field") + status_parser.add_argument("--infer-active-task", action="store_true") + + hook_parser = subparsers.add_parser("hook") + hook_parser.add_argument("event") + hook_parser.add_argument("--task-id") + hook_parser.add_argument("--phase-id") + hook_parser.add_argument("--command-text") + hook_parser.add_argument("--changed-path", action="append", default=[]) + hook_parser.add_argument("--staged", action="store_true") + hook_parser.add_argument("--worktree", action="store_true") + hook_parser.add_argument("--user-validation-note") + + return parser + + +def main(argv: list[str] | None = None) -> int: + parser = build_parser() + args = parser.parse_args(argv) + service = WorkflowService() + service.ensure_layout() + + try: + if args.command == "init": + emit(service.init_workspace()) + return 0 + + if args.command == "doctor": + report = service.doctor_report() + emit(report) + return 0 if report["status"] == "passed" else 1 + + if args.command == "new": + service.new_task(args.task_id, args.title, args.primary_repo) + emit({"status": "created", "task_id": args.task_id}) + return 0 + + if args.command == "approve": + service.approve_task(args.task_id, args.note, args.actor) + emit({"status": "approved", "task_id": args.task_id}) + return 0 + + if args.command == "plan": + service.plan_task(args.task_id, read_phase_source(args.source_path, args.stdin)) + emit({"status": "planned", "task_id": args.task_id}) + return 0 + + if args.command == "run": + if args.complete: + service.complete_phase(args.task_id, args.phase_id, args.changed_path, args.note, args.evidence) + emit({"status": "completed", "task_id": args.task_id}) + return 0 + if args.fail: + code = service.fail_phase(args.task_id, args.phase_id, args.error_fingerprint, args.note) + emit({"status": "failed" if code == 1 else "blocked", "task_id": args.task_id}) + return code + if args.block: + service.block_phase(args.task_id, args.phase_id, args.note) + emit({"status": "blocked", "task_id": args.task_id}) + return 2 + service.start_phase(args.task_id, args.phase_id) + emit({"status": "started", "task_id": args.task_id}) + return 0 + + if args.command == "verify": + code = service.verify_task(args.task_id, args.phase_id, args.verify_command or None, args.evidence) + emit({"status": "passed" if code == 0 else ("blocked" if code == 2 else "failed"), "task_id": args.task_id}) + return code + + if args.command == "review": + if args.close: + if not args.user_validation_note: + raise WorkflowError("--close requires --user-validation-note") + service.close_review(args.task_id, args.user_validation_note) + emit({"status": "completed", "task_id": args.task_id}) + return 0 + service.review_task(args.task_id, args.note) + emit({"status": "review_ready", "task_id": args.task_id}) + return 0 + + if args.command == "reopen": + service.reopen_task(args.task_id, args.note, args.phase_id) + emit({"status": "approved", "task_id": args.task_id}) + return 0 + + if args.command == "status": + if args.infer_active_task: + task_id = service.infer_active_task() or "" + if args.field: + raise WorkflowError("--field is not supported with --infer-active-task") + print(task_id) + return 0 + if args.check: + errors = service.check_all() + if errors: + emit({"status": "failed", "errors": errors}) + return 1 + emit({"status": "passed", "tasks": sorted([p.name for p in service.tasks_dir.iterdir() if p.is_dir()])}) + return 0 + if args.all: + payload = { + "tasks": [ + service.load_task(path.name) + for path in sorted(service.tasks_dir.iterdir()) + if path.is_dir() and (path / "task.json").exists() + ] + } + emit(payload) + return 0 + if not args.task_id: + raise WorkflowError("status requires task_id unless --all or --check is used") + payload = service.status_payload(args.task_id) + if args.field: + task = payload["task"] + if args.field not in task: + raise WorkflowError(f"unknown task field: {args.field}") + value = task[args.field] + if isinstance(value, (dict, list)): + print(json.dumps(value, ensure_ascii=False)) + else: + print("" if value is None else value) + return 0 + emit(payload) + return 0 + + if args.command == "hook": + task = service.load_task(args.task_id) if args.task_id else None + phase = None + if args.task_id and args.phase_id: + _, _, phase = service.current_phase(args.task_id, args.phase_id) + elif args.task_id and task and task["active_phase_id"]: + _, _, phase = service.current_phase(args.task_id, task["active_phase_id"]) + + changed_paths = list(args.changed_path) + if args.staged: + changed_paths.extend(service.staged_or_worktree_paths(staged=True)) + if args.worktree: + changed_paths.extend(service.staged_or_worktree_paths(staged=False)) + if args.event == "pre_push" and not changed_paths: + changed_paths.extend(service.unpushed_paths()) + + result = service.run_hooks( + args.event, + task=task, + phase=phase, + changed_paths=service.normalize_changed_paths(changed_paths), + command=args.command_text, + user_validation_note=args.user_validation_note, + ) + emit(result.as_payload()) + return 0 if result.status == "passed" else 1 + + except WorkflowError as exc: + print(str(exc), file=sys.stderr) + return 1 + + return 0 diff --git a/scripts/workflow_runtime/constants.py b/scripts/workflow_runtime/constants.py new file mode 100644 index 0000000..6c560a0 --- /dev/null +++ b/scripts/workflow_runtime/constants.py @@ -0,0 +1,87 @@ +from __future__ import annotations + +from pathlib import Path + +TASK_STATES = {"draft", "approved", "in_progress", "failed", "blocked", "review_ready", "completed"} +PHASE_STATES = {"pending", "in_progress", "completed", "failed", "blocked"} + +TASK_TRANSITIONS = { + "draft": {"approved"}, + "approved": {"in_progress", "blocked"}, + "in_progress": {"approved", "failed", "blocked", "review_ready", "completed"}, + "failed": {"approved", "in_progress", "blocked"}, + "blocked": {"approved", "in_progress"}, + "review_ready": {"completed", "blocked"}, + "completed": set(), +} + +PHASE_TRANSITIONS = { + "pending": {"in_progress", "blocked"}, + "in_progress": {"completed", "failed", "blocked"}, + "failed": {"pending", "in_progress", "blocked"}, + "blocked": {"pending", "in_progress"}, + "completed": {"failed", "blocked"}, +} + +SPEC_REQUIRED_SECTIONS = [ + "Request", + "Problem", + "Goals", + "Non-goals", + "Constraints", + "Acceptance", + "Socratic Clarification Log", +] + +AGENTS_REQUIRED_HEADINGS = [ + "## Mission", + "## Start Here", + "## CRITICAL Rules", + "## Architecture Boundaries", + "## Workflow Contract", + "## TDD Contract", + "## Command Contract", + "## Source Of Truth Order", + "## Forbidden Actions", + "## Change Discipline", +] + +AGENTS_REQUIRED_KEYWORDS = [ + "CRITICAL:", + "ALWAYS:", + "NEVER:", +] + +DOC_FILES = [ + "docs/README.md", + "docs/artifact-model.md", + "docs/runtime.md", + "docs/hooks.md", + "docs/runbook.md", +] + +DOC_REQUIRED_MARKERS = { + "docs/README.md": ["AGENTS.md", "docs/runtime.md", "docs/artifact-model.md"], + "docs/artifact-model.md": ["task.json", "intake", "clarifications"], + "docs/runtime.md": ["소크라테스 질문", "approve", "plan", "review --close"], + "docs/hooks.md": [".githooks/", "TDD Guard", "Dangerous Command Guard", "Circuit Breaker"], + "docs/runbook.md": ["사용자가 현재 spec 초안에 명시적으로 동의하면 approve 한다.", "approve", "plan"], +} + +STALE_REFERENCE_PATTERNS = [ + "docs/workflow/", + "SPECS.md", + "docs/specs/", + "docs/operations/", + "docs/architecture/", + "workflows/config/", + "workflows/runtime/", + "workflows/schemas/", + "workflow_lib.py", +] + +STALE_REFERENCE_SCAN_EXCLUDES = { + Path("scripts/workflow_runtime/constants.py"), + Path("scripts/workflow_runtime/doctor.py"), + Path(".github/workflows/workflow-control-plane.yml"), +} diff --git a/scripts/workflow_runtime/doctor.py b/scripts/workflow_runtime/doctor.py new file mode 100644 index 0000000..2e397f6 --- /dev/null +++ b/scripts/workflow_runtime/doctor.py @@ -0,0 +1,141 @@ +from __future__ import annotations + +import subprocess +from pathlib import Path +from typing import Any, Callable, Iterable + +from workflow_runtime.constants import ( + AGENTS_REQUIRED_HEADINGS, + AGENTS_REQUIRED_KEYWORDS, + DOC_FILES, + DOC_REQUIRED_MARKERS, + STALE_REFERENCE_PATTERNS, + STALE_REFERENCE_SCAN_EXCLUDES, +) + + +def validate_agents_constitution(agents_path: Path) -> list[str]: + text = agents_path.read_text(encoding="utf-8", errors="ignore") + errors: list[str] = [] + for heading in AGENTS_REQUIRED_HEADINGS: + if heading not in text: + errors.append(f"AGENTS.md missing heading: {heading}") + for keyword in AGENTS_REQUIRED_KEYWORDS: + if keyword not in text: + errors.append(f"AGENTS.md missing constitution marker: {keyword}") + return errors + + +def validate_doc_markers(doc_path: Path, relative: str, markers: list[str]) -> list[str]: + text = doc_path.read_text(encoding="utf-8", errors="ignore") + return [f"{relative} missing required marker: {marker}" for marker in markers if marker not in text] + + +def current_hooks_path(root: Path) -> str | None: + result = subprocess.run( + ["git", "config", "--local", "--get", "core.hooksPath"], + cwd=root, + capture_output=True, + text=True, + ) + if result.returncode != 0: + return None + return result.stdout.strip() or None + + +def iter_control_plane_files(root: Path) -> Iterable[Path]: + roots = [ + Path("AGENTS.md"), + Path("docs"), + Path(".codex/skills"), + Path(".github"), + Path("scripts"), + Path("tests"), + Path("workflows/system"), + Path(".githooks"), + ] + for relative in roots: + target = root / relative + if not target.exists(): + continue + if target.is_file(): + yield target + continue + for candidate in target.rglob("*"): + if candidate.is_file() and "__pycache__" not in candidate.parts and candidate.suffix != ".pyc": + yield candidate + + +def stale_reference_errors(root: Path) -> list[str]: + errors: list[str] = [] + for path in iter_control_plane_files(root): + relative = path.relative_to(root) + if relative in STALE_REFERENCE_SCAN_EXCLUDES: + continue + text = path.read_text(encoding="utf-8", errors="ignore") + for pattern in STALE_REFERENCE_PATTERNS: + if pattern in text: + errors.append(f"{relative} still references {pattern}") + return errors + + +def build_doctor_report( + root: Path, + *, + load_hooks_config: Callable[[], dict[str, Any]], + check_all: Callable[[], list[str]], +) -> dict[str, Any]: + checks: list[str] = [] + errors: list[str] = [] + + load_hooks_config() + checks.append("workflow system hook config is readable") + + hooks_path = current_hooks_path(root) + if hooks_path is None: + errors.append("git core.hooksPath is not configured; run `python3 scripts/workflow.py init`") + elif hooks_path != ".githooks": + errors.append(f"git core.hooksPath must be .githooks, got: {hooks_path}") + else: + checks.append("git core.hooksPath points to .githooks") + + task_errors = check_all() + if task_errors: + errors.extend(task_errors) + else: + checks.append("task artifacts are internally consistent") + + repo_surface = any((root / name).exists() for name in ("AGENTS.md", "docs", ".github", ".codex")) + if repo_surface: + agents_path = root / "AGENTS.md" + if not agents_path.exists(): + errors.append("missing canonical document: AGENTS.md") + else: + agents_errors = validate_agents_constitution(agents_path) + if agents_errors: + errors.extend(agents_errors) + else: + checks.append("AGENTS constitution is complete") + + for relative in DOC_FILES: + doc_path = root / relative + if not doc_path.exists(): + errors.append(f"missing canonical document: {relative}") + continue + markers = DOC_REQUIRED_MARKERS.get(relative) + if markers: + errors.extend(validate_doc_markers(doc_path, relative, markers)) + for relative in (".githooks/pre-commit", ".githooks/pre-push", "workflows/system/hooks.json"): + if not (root / relative).exists(): + errors.append(f"missing canonical artifact: {relative}") + for legacy_dir in ("workflows/config", "workflows/runtime", "workflows/schemas"): + if (root / legacy_dir).exists(): + errors.append(f"legacy directory must not exist: {legacy_dir}") + + stale_errors = stale_reference_errors(root) + if stale_errors: + errors.extend(stale_errors) + else: + checks.append("no stale legacy references remain in control-plane files") + + return {"status": "failed" if errors else "passed", "checks": checks, "errors": errors} diff --git a/scripts/workflow_runtime/engine.py b/scripts/workflow_runtime/engine.py new file mode 100644 index 0000000..ac840f7 --- /dev/null +++ b/scripts/workflow_runtime/engine.py @@ -0,0 +1,808 @@ +from __future__ import annotations + +import copy +import os +import re +import shutil +import subprocess +from datetime import datetime, timedelta, timezone +from pathlib import Path +from typing import Any +from uuid import uuid4 + +from workflow_runtime.constants import PHASE_TRANSITIONS, TASK_TRANSITIONS +from workflow_runtime.doctor import build_doctor_report +from workflow_runtime.git_ops import git_paths, unpushed_git_paths +from workflow_runtime.guards import ( + dangerous_cmd_guard, + latest_verified_run_guard, + out_of_scope_paths, + tdd_guard, + user_validation_guard, + write_scope_guard, +) +from workflow_runtime.models import ( + ensure_locked_intake, + HookResult, + WorkflowError, + compact_output, + inspect_spec, + normalize_repo_path, + now_iso, + read_json, + validate_phases, + validate_relative_repo_path, + validate_run, + validate_spec_for_approval, + validate_task_id, + validate_task, + write_json, +) +from workflow_runtime.templates import approval_block, default_spec, empty_phases_payload, empty_task_payload + +SOURCE_ROOT = Path(__file__).resolve().parents[2] +DEFAULT_ROOT = Path(os.environ.get("WORKFLOW_ROOT", SOURCE_ROOT)) + + +class WorkflowService: + def __init__(self, root: Path | None = None): + self.root = Path(root or DEFAULT_ROOT).resolve() + self.workflows_dir = self.root / "workflows" + self.system_dir = self.workflows_dir / "system" + self.tasks_dir = self.workflows_dir / "tasks" + self.hooks_config_path = self.system_dir / "hooks.json" + + def ensure_layout(self) -> None: + self.system_dir.mkdir(parents=True, exist_ok=True) + self.tasks_dir.mkdir(parents=True, exist_ok=True) + + source_hook = SOURCE_ROOT / "workflows" / "system" / "hooks.json" + if not self.hooks_config_path.exists() and source_hook.exists(): + shutil.copy2(source_hook, self.hooks_config_path) + + source_githooks_dir = SOURCE_ROOT / ".githooks" + target_githooks_dir = self.root / ".githooks" + if source_githooks_dir.exists(): + target_githooks_dir.mkdir(parents=True, exist_ok=True) + for hook_name in ("pre-commit", "pre-push"): + source_hook_script = source_githooks_dir / hook_name + target_hook_script = target_githooks_dir / hook_name + if not target_hook_script.exists() and source_hook_script.exists(): + shutil.copy2(source_hook_script, target_hook_script) + + def task_dir(self, task_id: str) -> Path: + return self.tasks_dir / validate_task_id(task_id) + + def task_path(self, task_id: str) -> Path: + return self.task_dir(task_id) / "task.json" + + def spec_path(self, task_id: str) -> Path: + return self.task_dir(task_id) / "spec.md" + + def phases_path(self, task_id: str) -> Path: + return self.task_dir(task_id) / "phases.json" + + def runs_dir(self, task_id: str) -> Path: + return self.task_dir(task_id) / "runs" + + def run_path(self, task_id: str, run_id: str) -> Path: + return self.runs_dir(task_id) / f"{run_id}.json" + + def load_hooks_config(self) -> dict[str, Any]: + self.ensure_layout() + return read_json(self.hooks_config_path) + + def load_task(self, task_id: str) -> dict[str, Any]: + path = self.task_path(task_id) + if not path.exists(): + raise WorkflowError(f"task not found: {task_id}") + payload = read_json(path) + validate_task(payload) + return payload + + def save_task(self, task_id: str, payload: dict[str, Any]) -> None: + validate_task(payload) + write_json(self.task_path(task_id), payload) + + def load_phases(self, task_id: str) -> dict[str, Any]: + path = self.phases_path(task_id) + if not path.exists(): + raise WorkflowError(f"phases not found: {task_id}") + payload = read_json(path) + validate_phases(payload) + return payload + + def save_phases(self, task_id: str, payload: dict[str, Any]) -> None: + validate_phases(payload) + write_json(self.phases_path(task_id), payload) + + def load_run(self, task_id: str, run_id: str) -> dict[str, Any]: + path = self.run_path(task_id, run_id) + if not path.exists(): + raise WorkflowError(f"run not found: {run_id}") + payload = read_json(path) + validate_run(payload) + return payload + + def write_run(self, task_id: str, payload: dict[str, Any]) -> None: + validate_run(payload) + write_json(self.run_path(task_id, payload["id"]), payload) + + def configure_git_hooks(self) -> str | None: + result = subprocess.run( + ["git", "rev-parse", "--show-toplevel"], + cwd=self.root, + capture_output=True, + text=True, + ) + if result.returncode != 0: + return None + config = subprocess.run( + ["git", "config", "--local", "core.hooksPath", ".githooks"], + cwd=self.root, + capture_output=True, + text=True, + ) + if config.returncode != 0: + raise WorkflowError(config.stderr.strip() or "failed to configure core.hooksPath") + return ".githooks" + + def init_workspace(self) -> dict[str, Any]: + self.ensure_layout() + hooks_path = self.configure_git_hooks() + return { + "status": "ready", + "root": str(self.root), + "paths": { + "tasks": str(self.tasks_dir), + "system": str(self.system_dir), + }, + "git_hooks_path": hooks_path, + } + + def new_task(self, task_id: str, title: str, primary_repo: str) -> None: + self.ensure_layout() + task_dir = self.task_dir(task_id) + if task_dir.exists(): + raise WorkflowError(f"task already exists: {task_id}") + task_dir.mkdir(parents=True, exist_ok=False) + self.runs_dir(task_id).mkdir(parents=True, exist_ok=True) + + self.spec_path(task_id).write_text(default_spec(task_id, title, primary_repo), encoding="utf-8") + self.save_task(task_id, empty_task_payload(task_id, title, primary_repo)) + self.save_phases(task_id, empty_phases_payload(task_id)) + + def update_task_state(self, task: dict[str, Any], next_state: str) -> None: + current = task["state"] + allowed = TASK_TRANSITIONS[current] + if next_state not in allowed: + raise WorkflowError(f"invalid task transition: {current} -> {next_state}") + task["state"] = next_state + + def update_phase_state(self, phase: dict[str, Any], next_state: str) -> None: + current = phase["status"] + allowed = PHASE_TRANSITIONS[current] + if next_state not in allowed: + raise WorkflowError(f"invalid phase transition: {current} -> {next_state}") + phase["status"] = next_state + + def append_approval_to_spec(self, task_id: str, note: str, actor: str, timestamp: str) -> None: + spec_path = self.spec_path(task_id) + spec = spec_path.read_text(encoding="utf-8") + block = approval_block(actor, note, timestamp) + if "\n## Approval\n" in spec: + spec = re.sub(r"\n## Approval\n.*", block, spec, flags=re.S) + else: + spec += block + spec_path.write_text(spec, encoding="utf-8") + + def approve_task(self, task_id: str, note: str, actor: str) -> None: + task = self.load_task(task_id) + if task["state"] not in {"draft", "approved"}: + raise WorkflowError("approve requires draft or approved task") + if not note.strip(): + raise WorkflowError("approval note is required") + + intake = validate_spec_for_approval(self.spec_path(task_id)) + timestamp = now_iso() + task["state"] = "approved" + task["approved_at"] = timestamp + task["approval"] = {"actor": actor, "note": note.strip(), "timestamp": timestamp} + task["intake"] = intake + self.append_approval_to_spec(task_id, note.strip(), actor, timestamp) + self.save_task(task_id, task) + + def normalize_phase(self, payload: dict[str, Any], fallback_order: int) -> dict[str, Any]: + try: + phase = copy.deepcopy(payload) + phase["id"] = phase["id"].strip() + phase["title"] = phase["title"].strip() + phase["goal"] = phase["goal"].strip() + phase["order"] = phase.get("order", fallback_order) + phase["status"] = "pending" + phase["retry_count"] = 0 + phase["inputs"] = [item.strip() for item in phase.get("inputs", [])] + phase["allowed_write_paths"] = [validate_relative_repo_path(item) for item in phase["allowed_write_paths"]] + phase["acceptance"] = { + "commands": [command.strip() for command in phase["acceptance"]["commands"]], + } + policy = phase.get("test_policy") or {"mode": "require_tests", "evidence": []} + phase["test_policy"] = { + "mode": policy["mode"], + "evidence": [item.strip() for item in policy.get("evidence", []) if item.strip()], + } + return phase + except KeyError as exc: + raise WorkflowError(f"phase input missing key: {exc.args[0]}") from exc + + def plan_task(self, task_id: str, source_payload: Any) -> None: + task = self.load_task(task_id) + if task["state"] != "approved": + raise WorkflowError("plan requires approved task") + if source_payload is None: + raise WorkflowError("plan requires explicit phase input via --from or --stdin") + spec = inspect_spec(self.spec_path(task_id)) + if not spec["ready_for_approval"]: + raise WorkflowError("plan requires approval-ready spec.md") + intake = ensure_locked_intake(task["intake"]) + if intake != spec["intake"]: + raise WorkflowError("spec.md and task.intake are out of sync; rerun approve before plan") + + phases_input = source_payload["phases"] if isinstance(source_payload, dict) and "phases" in source_payload else source_payload + if not isinstance(phases_input, list) or not phases_input: + raise WorkflowError("phases input must be a non-empty array") + + normalized = [self.normalize_phase(phase, index) for index, phase in enumerate(phases_input, start=1)] + normalized = sorted(normalized, key=lambda item: item["order"]) + phases_payload = { + "task_id": task_id, + "generated_at": now_iso(), + "phases": normalized, + } + self.save_phases(task_id, phases_payload) + + task["active_phase_id"] = normalized[0]["id"] + task["last_verified_run_id"] = None + task["blocked_reason"] = None + task["user_validated"] = False + task["user_validation_note"] = None + self.save_task(task_id, task) + + def current_phase(self, task_id: str, phase_id: str | None = None) -> tuple[dict[str, Any], dict[str, Any], dict[str, Any]]: + task = self.load_task(task_id) + phases_payload = self.load_phases(task_id) + if not phases_payload["phases"]: + raise WorkflowError("task has no phases") + target_id = phase_id or task["active_phase_id"] + if target_id is None: + raise WorkflowError("task has no active phase") + for phase in phases_payload["phases"]: + if phase["id"] == target_id: + return task, phases_payload, phase + raise WorkflowError(f"phase not found: {target_id}") + + def next_pending_phase_id(self, phases_payload: dict[str, Any]) -> str | None: + pending = sorted( + (phase for phase in phases_payload["phases"] if phase["status"] == "pending"), + key=lambda item: item["order"], + ) + return pending[0]["id"] if pending else None + + def infer_active_task(self) -> str | None: + if not self.tasks_dir.exists(): + return None + active = [] + for candidate in sorted(self.tasks_dir.iterdir()): + if not candidate.is_dir(): + continue + task_file = candidate / "task.json" + if not task_file.exists(): + continue + task = read_json(task_file) + if task.get("state") in {"approved", "in_progress", "failed", "blocked", "review_ready"}: + active.append(task["id"]) + if len(active) == 1: + return active[0] + return None + + def build_run( + self, + task_id: str, + phase_id: str | None, + event: str, + commands: list[dict[str, str]], + result: str, + evidence: list[str], + error_fingerprint: str | None, + next_action: str, + ) -> dict[str, Any]: + return { + "id": f"{datetime.now(timezone.utc).strftime('%Y%m%dT%H%M%S')}-{uuid4().hex[:8]}", + "task_id": task_id, + "phase_id": phase_id, + "event": event, + "commands": commands, + "result": result, + "evidence": evidence, + "error_fingerprint": error_fingerprint, + "next_action": next_action, + "timestamp": now_iso(), + } + + def staged_or_worktree_paths(self, staged: bool = False) -> list[str]: + try: + return git_paths(self.root, staged=staged) + except Exception: + return [] + + def unpushed_paths(self) -> list[str]: + try: + return unpushed_git_paths(self.root) + except Exception: + return [] + + def circuit_breaker_path(self) -> Path: + config = self.load_hooks_config()["guards"]["circuit_breaker"] + return self.root / config["state_file"] + + def record_circuit_breaker(self, fingerprint: str) -> HookResult: + config = self.load_hooks_config()["guards"]["circuit_breaker"] + window = timedelta(seconds=int(config["window_seconds"])) + threshold = int(config["threshold"]) + path = self.circuit_breaker_path() + state = {"fingerprints": {}} + if path.exists(): + state = read_json(path) + + now = datetime.now(timezone.utc) + entries = state["fingerprints"].get(fingerprint, []) + fresh_entries = [] + for entry in entries: + try: + entry_time = datetime.fromisoformat(entry) + except ValueError: + continue + if now - entry_time <= window: + fresh_entries.append(entry) + fresh_entries.append(now_iso()) + state["fingerprints"][fingerprint] = fresh_entries + write_json(path, state) + + if len(fresh_entries) >= threshold: + return HookResult("circuit_breaker", "blocked", [f"circuit breaker open for {fingerprint}"]) + return HookResult("circuit_breaker", "passed", [f"failure count for {fingerprint}: {len(fresh_entries)}"]) + + def run_hooks( + self, + event: str, + task: dict[str, Any] | None = None, + phase: dict[str, Any] | None = None, + changed_paths: list[str] | None = None, + command: str | None = None, + user_validation_note: str | None = None, + ) -> HookResult: + config = self.load_hooks_config() + guards = config["events"].get(event) + if guards is None: + raise WorkflowError(f"unknown hook event: {event}") + + messages: list[str] = [] + for guard_name in guards: + if guard_name == "tdd_guard": + result = tdd_guard(config["guards"]["tdd_guard"], changed_paths or [], phase) + elif guard_name == "dangerous_cmd_guard": + result = dangerous_cmd_guard(config["guards"]["dangerous_cmd_guard"], command or "") + elif guard_name == "write_scope_guard": + result = write_scope_guard(task["id"] if task else None, phase, changed_paths or []) + elif guard_name == "latest_verified_run_guard": + if task is None: + raise WorkflowError(f"{event} requires task context") + result = latest_verified_run_guard( + task, + phase, + changed_paths or [], + load_run=self.load_run, + scope_sensitive=(event == "pre_push"), + ) + elif guard_name == "user_validation_guard": + if task is None: + raise WorkflowError("pre_complete requires task context") + result = user_validation_guard(user_validation_note, task) + else: + raise WorkflowError(f"unknown guard: {guard_name}") + + messages.extend(result.messages) + if result.status != "passed": + return HookResult(event, result.status, messages) + return HookResult(event, "passed", messages) + + def open_circuit_if_needed( + self, + task: dict[str, Any], + phases_payload: dict[str, Any], + phase: dict[str, Any], + fingerprint: str, + note: str, + ) -> int: + breaker = self.record_circuit_breaker(fingerprint) + if breaker.status != "blocked": + return 1 + + if phase["status"] != "blocked" and phase["status"] in {"failed", "completed", "in_progress", "pending"}: + self.update_phase_state(phase, "blocked") + task["state"] = "blocked" + task["blocked_reason"] = note + self.save_phases(task["id"], phases_payload) + self.save_task(task["id"], task) + return 2 + + def start_phase(self, task_id: str, phase_id: str | None) -> None: + task, phases_payload, phase = self.current_phase(task_id, phase_id) + if task["state"] not in {"approved", "in_progress", "failed", "blocked"}: + raise WorkflowError(f"task state does not allow start: {task['state']}") + if phase["status"] not in {"pending", "failed", "blocked"}: + raise WorkflowError(f"phase state does not allow start: {phase['status']}") + + hook_result = self.run_hooks("pre_phase_start", task=task, phase=phase) + if hook_result.status != "passed": + raise WorkflowError("; ".join(hook_result.messages)) + + self.update_phase_state(phase, "in_progress") + task["state"] = "in_progress" + task["active_phase_id"] = phase["id"] + task["blocked_reason"] = None + task["last_verified_run_id"] = None + self.save_phases(task_id, phases_payload) + self.save_task(task_id, task) + + def complete_phase( + self, + task_id: str, + phase_id: str | None, + changed_paths: list[str], + note: str | None, + evidence: list[str], + ) -> None: + task, phases_payload, phase = self.current_phase(task_id, phase_id) + if phase["status"] != "in_progress": + raise WorkflowError("phase must be in_progress before completion") + + final_paths = sorted(set(changed_paths or self.staged_or_worktree_paths(staged=False))) + violations = out_of_scope_paths(task_id, phase, final_paths) + if violations: + raise WorkflowError(f"changed paths outside allowed_write_paths: {', '.join(violations)}") + + for event in ("post_change", "pre_phase_complete"): + hook_result = self.run_hooks(event, task=task, phase=phase, changed_paths=final_paths) + if hook_result.status != "passed": + raise WorkflowError("; ".join(hook_result.messages)) + + self.update_phase_state(phase, "completed") + run = self.build_run( + task_id, + phase["id"], + "phase_completion", + [{"command": "phase completion", "status": "passed", "output": ", ".join(final_paths)}], + "passed", + evidence + ([note] if note else []), + None, + "verify", + ) + self.write_run(task_id, run) + task["latest_run_id"] = run["id"] + task["last_verified_run_id"] = None + task["blocked_reason"] = None + task["active_phase_id"] = phase["id"] + task["state"] = "in_progress" + self.save_phases(task_id, phases_payload) + self.save_task(task_id, task) + + def fail_phase(self, task_id: str, phase_id: str | None, fingerprint: str | None, note: str | None) -> int: + task, phases_payload, phase = self.current_phase(task_id, phase_id) + if phase["status"] not in {"pending", "in_progress", "failed"}: + raise WorkflowError("phase cannot be failed from current state") + + if phase["status"] == "pending": + self.update_phase_state(phase, "in_progress") + if phase["status"] != "failed": + self.update_phase_state(phase, "failed") + phase["retry_count"] += 1 + + run = self.build_run( + task_id, + phase["id"], + "phase_failure", + [{"command": "phase failure", "status": "failed", "output": note or ""}], + "failed", + [note] if note else [], + fingerprint, + "repair or retry", + ) + self.write_run(task_id, run) + task["latest_run_id"] = run["id"] + task["last_verified_run_id"] = None + task["active_phase_id"] = phase["id"] + + if fingerprint: + code = self.open_circuit_if_needed( + task, + phases_payload, + phase, + fingerprint, + note or f"repeated failure in {phase['id']}", + ) + if code == 2: + return 2 + + task["state"] = "failed" + task["blocked_reason"] = None + self.save_phases(task_id, phases_payload) + self.save_task(task_id, task) + return 1 + + def block_phase(self, task_id: str, phase_id: str | None, note: str | None) -> None: + task, phases_payload, phase = self.current_phase(task_id, phase_id) + reason = note or f"manual block on {phase['id']}" + if phase["status"] not in {"pending", "in_progress", "failed", "completed"}: + raise WorkflowError("phase cannot be blocked from current state") + if phase["status"] != "blocked": + self.update_phase_state(phase, "blocked") + + run = self.build_run( + task_id, + phase["id"], + "phase_blocked", + [{"command": "phase blocked", "status": "blocked", "output": reason}], + "blocked", + [reason], + None, + "manual intervention", + ) + self.write_run(task_id, run) + task["latest_run_id"] = run["id"] + task["last_verified_run_id"] = None + task["state"] = "blocked" + task["blocked_reason"] = reason + task["active_phase_id"] = phase["id"] + self.save_phases(task_id, phases_payload) + self.save_task(task_id, task) + + def verification_fingerprint(self, phase: dict[str, Any], command: str) -> str: + return f"verification:{phase['id']}:{command.strip()}" + + def verify_task(self, task_id: str, phase_id: str | None, commands: list[str] | None, evidence: list[str]) -> int: + task, phases_payload, phase = self.current_phase(task_id, phase_id) + if phase["status"] not in {"completed", "in_progress"}: + raise WorkflowError("verification requires phase status completed or in_progress") + + command_list = commands or phase["acceptance"]["commands"] + if not command_list: + raise WorkflowError("verification commands are required") + + command_results: list[dict[str, str]] = [] + overall = "passed" + failure_fingerprint: str | None = None + block_reason: str | None = None + for command in command_list: + guard = self.run_hooks("pre_command", task=task, phase=phase, command=command) + if guard.status != "passed": + overall = "blocked" + block_reason = "; ".join(guard.messages) + command_results.append({"command": command, "status": "blocked", "output": block_reason}) + break + + result = subprocess.run(command, cwd=self.root, shell=True, capture_output=True, text=True) + status = "passed" if result.returncode == 0 else "failed" + output = compact_output(result.stdout, result.stderr) + command_results.append({"command": command, "status": status, "output": output}) + if status == "failed": + overall = "failed" + failure_fingerprint = self.verification_fingerprint(phase, command) + break + + next_action = "review" if overall == "passed" else ("manual intervention" if overall == "blocked" else "repair") + run = self.build_run( + task_id, + phase["id"], + "verification", + command_results, + overall, + evidence, + failure_fingerprint, + next_action, + ) + self.write_run(task_id, run) + task["latest_run_id"] = run["id"] + task["active_phase_id"] = phase["id"] + + if overall == "passed": + task["last_verified_run_id"] = run["id"] + task["blocked_reason"] = None + next_phase = self.next_pending_phase_id(phases_payload) + if phase["status"] == "completed" and next_phase: + task["active_phase_id"] = next_phase + task["state"] = "approved" + else: + task["state"] = "in_progress" + self.save_phases(task_id, phases_payload) + self.save_task(task_id, task) + return 0 + + task["last_verified_run_id"] = None + if phase["status"] != "failed": + self.update_phase_state(phase, "failed" if overall == "failed" else "blocked") + phase["retry_count"] += 1 + + if overall == "blocked": + task["state"] = "blocked" + task["blocked_reason"] = block_reason or f"verification blocked for {phase['id']}" + self.save_phases(task_id, phases_payload) + self.save_task(task_id, task) + return 2 + + code = self.open_circuit_if_needed( + task, + phases_payload, + phase, + failure_fingerprint or f"verification:{phase['id']}", + f"repeated verification failure in {phase['id']}", + ) + if code == 2: + return 2 + + task["state"] = "failed" + task["blocked_reason"] = None + self.save_phases(task_id, phases_payload) + self.save_task(task_id, task) + return 1 + + def review_task(self, task_id: str, note: str | None) -> None: + task = self.load_task(task_id) + phases_payload = self.load_phases(task_id) + if not phases_payload["phases"]: + raise WorkflowError("review requires planned phases") + if any(phase["status"] != "completed" for phase in phases_payload["phases"]): + raise WorkflowError("review requires all phases to be completed") + + _, _, phase = self.current_phase(task_id, task["active_phase_id"]) + hook = self.run_hooks("pre_review", task=task, phase=phase) + if hook.status != "passed": + raise WorkflowError("; ".join(hook.messages)) + + if task["state"] != "review_ready": + self.update_task_state(task, "review_ready") + run = self.build_run( + task_id, + phase["id"], + "review_ready", + [{"command": "review gate", "status": "passed", "output": "; ".join(hook.messages)}], + "passed", + [note] if note else [], + None, + "user validation", + ) + self.write_run(task_id, run) + task["latest_run_id"] = run["id"] + self.save_task(task_id, task) + + def close_review(self, task_id: str, note: str) -> None: + task = self.load_task(task_id) + if task["state"] != "review_ready": + raise WorkflowError("review closeout requires state=review_ready") + + task["user_validated"] = True + task["user_validation_note"] = note + hook = self.run_hooks("pre_complete", task=task, user_validation_note=note) + if hook.status != "passed": + raise WorkflowError("; ".join(hook.messages)) + + self.update_task_state(task, "completed") + task["blocked_reason"] = None + run = self.build_run( + task_id, + task["active_phase_id"], + "review_closeout", + [{"command": "complete task", "status": "passed", "output": note}], + "passed", + [note], + None, + "done", + ) + self.write_run(task_id, run) + task["latest_run_id"] = run["id"] + self.save_task(task_id, task) + + def reopen_task(self, task_id: str, note: str, phase_id: str | None) -> None: + task = self.load_task(task_id) + phases_payload = self.load_phases(task_id) + if task["state"] not in {"failed", "blocked", "review_ready", "completed"}: + raise WorkflowError("reopen requires failed, blocked, review_ready, or completed task") + + target_phase: dict[str, Any] | None = None + if phase_id: + for candidate in phases_payload["phases"]: + if candidate["id"] == phase_id: + target_phase = candidate + break + if target_phase is None: + raise WorkflowError(f"phase not found: {phase_id}") + else: + ordered = sorted(phases_payload["phases"], key=lambda item: item["order"]) + target_phase = next((phase for phase in ordered if phase["status"] in {"failed", "blocked", "pending"}), None) + if target_phase is None and task["active_phase_id"]: + for candidate in ordered: + if candidate["id"] == task["active_phase_id"]: + target_phase = candidate + break + + if target_phase and target_phase["status"] in {"failed", "blocked"}: + self.update_phase_state(target_phase, "pending") + + task["state"] = "approved" + task["blocked_reason"] = None + task["last_verified_run_id"] = None + task["user_validated"] = False + task["user_validation_note"] = None + if target_phase is not None: + task["active_phase_id"] = target_phase["id"] + + run = self.build_run( + task_id, + target_phase["id"] if target_phase else task["active_phase_id"], + "reopened", + [{"command": "reopen task", "status": "passed", "output": note}], + "passed", + [note], + None, + "plan or run", + ) + self.write_run(task_id, run) + task["latest_run_id"] = run["id"] + self.save_phases(task_id, phases_payload) + self.save_task(task_id, task) + + def status_payload(self, task_id: str) -> dict[str, Any]: + return {"task": self.load_task(task_id), "phases": self.load_phases(task_id), "spec": inspect_spec(self.spec_path(task_id))} + + def check_all(self) -> list[str]: + self.ensure_layout() + errors = [] + for task_dir in sorted(self.tasks_dir.iterdir()): + if not task_dir.is_dir(): + continue + task_id = task_dir.name + try: + task = self.load_task(task_id) + phases = self.load_phases(task_id) + spec = inspect_spec(self.spec_path(task_id)) + if task["active_phase_id"] is not None and not any( + phase["id"] == task["active_phase_id"] for phase in phases["phases"] + ): + raise WorkflowError("active phase does not exist") + if task["state"] != "draft": + if not spec["ready_for_approval"]: + raise WorkflowError("approved-or-later task requires approval-ready spec.md") + if ensure_locked_intake(task["intake"]) != spec["intake"]: + raise WorkflowError("task.intake is out of sync with spec.md") + if task["latest_run_id"] is not None: + self.load_run(task_id, task["latest_run_id"]) + if task["last_verified_run_id"] is not None: + run = self.load_run(task_id, task["last_verified_run_id"]) + if run["event"] != "verification" or run["result"] != "passed": + raise WorkflowError("last_verified_run_id must point to passed verification") + if task["state"] in {"review_ready", "completed"} and any( + phase["status"] != "completed" for phase in phases["phases"] + ): + raise WorkflowError("review_ready/completed task requires every phase to be completed") + for run_file in sorted(self.runs_dir(task_id).glob("*.json")): + validate_run(read_json(run_file)) + except WorkflowError as exc: + errors.append(f"{task_id}: {exc}") + return errors + + def doctor_report(self) -> dict[str, Any]: + self.ensure_layout() + return build_doctor_report(self.root, load_hooks_config=self.load_hooks_config, check_all=self.check_all) + + def normalize_changed_paths(self, paths: list[str]) -> list[str]: + return sorted(set(normalize_repo_path(path) for path in paths)) diff --git a/scripts/workflow_runtime/git_ops.py b/scripts/workflow_runtime/git_ops.py new file mode 100644 index 0000000..0f86e9f --- /dev/null +++ b/scripts/workflow_runtime/git_ops.py @@ -0,0 +1,62 @@ +from __future__ import annotations + +import subprocess +from pathlib import Path + +from workflow_runtime.models import normalize_repo_path + + +def git_command(root: Path, args: list[str]) -> subprocess.CompletedProcess[str]: + return subprocess.run(["git", *args], cwd=root, capture_output=True, text=True) + + +def git_paths(root: Path, staged: bool = False) -> list[str]: + result = git_command( + root, + ["diff", "--name-only", "--diff-filter=ACMRTUXB", *(["--cached"] if staged else [])], + ) + if result.returncode != 0: + return [] + + tracked = [normalize_repo_path(line) for line in result.stdout.splitlines() if line.strip()] + if staged: + return sorted(set(tracked)) + + untracked_result = git_command(root, ["ls-files", "--others", "--exclude-standard"]) + if untracked_result.returncode != 0: + return sorted(set(tracked)) + untracked = [normalize_repo_path(line) for line in untracked_result.stdout.splitlines() if line.strip()] + return sorted(set(tracked + untracked)) + + +def unpushed_git_paths(root: Path) -> list[str]: + upstream = git_command(root, ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{upstream}"]) + if upstream.returncode == 0: + ref = upstream.stdout.strip() + if ref: + diff_result = git_command(root, ["diff", "--name-only", "--diff-filter=ACMRTUXB", f"{ref}...HEAD"]) + if diff_result.returncode == 0: + return sorted( + { + normalize_repo_path(line) + for line in diff_result.stdout.splitlines() + if line.strip() + } + ) + + parent = git_command(root, ["rev-parse", "HEAD~1"]) + if parent.returncode == 0: + diff_result = git_command(root, ["diff", "--name-only", "--diff-filter=ACMRTUXB", "HEAD~1..HEAD"]) + if diff_result.returncode == 0: + return sorted( + { + normalize_repo_path(line) + for line in diff_result.stdout.splitlines() + if line.strip() + } + ) + + show_result = git_command(root, ["show", "--name-only", "--pretty=format:", "HEAD"]) + if show_result.returncode != 0: + return [] + return sorted({normalize_repo_path(line) for line in show_result.stdout.splitlines() if line.strip()}) diff --git a/scripts/workflow_runtime/guards.py b/scripts/workflow_runtime/guards.py new file mode 100644 index 0000000..faa3639 --- /dev/null +++ b/scripts/workflow_runtime/guards.py @@ -0,0 +1,140 @@ +from __future__ import annotations + +import re +import shlex +from typing import Any, Callable + +from workflow_runtime.models import HookResult, path_matches, scope_matches + + +def phase_scope(task_id: str, phase: dict[str, Any]) -> list[str]: + return [*phase["allowed_write_paths"], f"workflows/tasks/{task_id}/"] + + +def out_of_scope_paths(task_id: str, phase: dict[str, Any], changed_paths: list[str]) -> list[str]: + scopes = phase_scope(task_id, phase) + violations = [] + for path in changed_paths: + if not any(scope_matches(path, scope) for scope in scopes): + violations.append(path) + return sorted(set(violations)) + + +def write_scope_guard(task_id: str | None, phase: dict[str, Any] | None, changed_paths: list[str]) -> HookResult: + if not changed_paths: + return HookResult("write_scope_guard", "passed", ["no changed paths"]) + if task_id is None or phase is None: + return HookResult("write_scope_guard", "passed", ["no phase context"]) + + violations = out_of_scope_paths(task_id, phase, changed_paths) + if violations: + return HookResult( + "write_scope_guard", + "failed", + [f"changed paths outside allowed_write_paths: {', '.join(violations)}"], + ) + return HookResult("write_scope_guard", "passed", ["all changed paths are inside allowed_write_paths"]) + + +def tdd_guard(config: dict[str, Any], changed_paths: list[str], phase: dict[str, Any] | None) -> HookResult: + if not changed_paths: + return HookResult("tdd_guard", "passed", ["no changed paths"]) + + relevant = [path for path in changed_paths if not path_matches(path, config["ignore_patterns"])] + implementation_changes = [path for path in relevant if path_matches(path, config["implementation_patterns"])] + test_changes = [path for path in relevant if path_matches(path, config["test_patterns"])] + + if not implementation_changes: + return HookResult("tdd_guard", "passed", ["no implementation changes"]) + if test_changes: + return HookResult("tdd_guard", "passed", [f"test changes detected: {', '.join(sorted(set(test_changes)))}"]) + + if phase is None: + return HookResult( + "tdd_guard", + "failed", + [f"implementation changes require tests: {', '.join(sorted(set(implementation_changes)))}"], + ) + + policy = phase["test_policy"] + if policy["mode"] == "evidence_only" and policy["evidence"]: + return HookResult( + "tdd_guard", + "passed", + [f"test evidence accepted: {', '.join(policy['evidence'])}"], + ) + + return HookResult( + "tdd_guard", + "failed", + [f"implementation changes require tests: {', '.join(sorted(set(implementation_changes)))}"], + ) + + +def dangerous_cmd_guard(config: dict[str, Any], command: str) -> HookResult: + try: + tokens = shlex.split(command) + except ValueError: + tokens = command.split() + + if tokens and tokens[0].lower() == "git": + try: + push_index = next(index for index, token in enumerate(tokens[1:], start=1) if token.lower() == "push") + except StopIteration: + push_index = None + if push_index is not None: + for token in tokens[push_index + 1 :]: + normalized = token.lower() + if normalized.startswith("--force"): + return HookResult("dangerous_cmd_guard", "failed", [f"blocked command: {command}"]) + if re.fullmatch(r"-[A-Za-z]*f[A-Za-z]*", token): + return HookResult("dangerous_cmd_guard", "failed", [f"blocked command: {command}"]) + + for pattern in config["blocked_patterns"]: + if re.search(pattern, command, flags=re.I): + return HookResult("dangerous_cmd_guard", "failed", [f"blocked command: {command}"]) + return HookResult("dangerous_cmd_guard", "passed", [f"command allowed: {command}"]) + + +def latest_verified_run_guard( + task: dict[str, Any], + phase: dict[str, Any] | None, + changed_paths: list[str] | None, + *, + load_run: Callable[[str, str], dict[str, Any]], + scope_sensitive: bool, +) -> HookResult: + if phase is None: + if scope_sensitive: + return HookResult("latest_verified_run_guard", "passed", ["no phase context"]) + return HookResult("latest_verified_run_guard", "failed", ["active phase context is required"]) + + if scope_sensitive: + if not changed_paths: + return HookResult("latest_verified_run_guard", "passed", ["no changed paths detected"]) + if not any( + any(scope_matches(path, scope) for scope in phase_scope(task["id"], phase)) + for path in changed_paths + ): + return HookResult("latest_verified_run_guard", "passed", ["no active phase scope changes detected"]) + + run_id = task["last_verified_run_id"] + if not run_id: + return HookResult("latest_verified_run_guard", "failed", ["no passed verification recorded for active phase"]) + + run = load_run(task["id"], run_id) + if run["event"] != "verification" or run["result"] != "passed": + return HookResult("latest_verified_run_guard", "failed", [f"run {run_id} is not a passed verification"]) + if run["phase_id"] != phase["id"]: + return HookResult( + "latest_verified_run_guard", + "failed", + [f"latest passed verification is for phase {run['phase_id']}, not active phase {phase['id']}"], + ) + return HookResult("latest_verified_run_guard", "passed", [f"latest passed verification: {run_id}"]) + + +def user_validation_guard(note: str | None, task: dict[str, Any]) -> HookResult: + if note or task.get("user_validated"): + return HookResult("user_validation_guard", "passed", ["user validation provided"]) + return HookResult("user_validation_guard", "failed", ["user validation note is required"]) diff --git a/scripts/workflow_runtime/models.py b/scripts/workflow_runtime/models.py new file mode 100644 index 0000000..2dcfcba --- /dev/null +++ b/scripts/workflow_runtime/models.py @@ -0,0 +1,573 @@ +from __future__ import annotations + +import fnmatch +import json +import re +from dataclasses import dataclass +from datetime import datetime, timezone +from pathlib import Path, PurePosixPath +from typing import Any + +from workflow_runtime.constants import PHASE_STATES, SPEC_REQUIRED_SECTIONS, TASK_STATES + + +class WorkflowError(Exception): + pass + + +@dataclass +class HookResult: + event: str + status: str + messages: list[str] + + def as_payload(self) -> dict[str, Any]: + return {"event": self.event, "status": self.status, "messages": self.messages} + + +def now_iso() -> str: + return datetime.now(timezone.utc).replace(microsecond=0).isoformat() + + +def read_json(path: Path) -> dict[str, Any]: + return json.loads(path.read_text(encoding="utf-8")) + + +def write_json(path: Path, payload: dict[str, Any]) -> None: + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text(json.dumps(payload, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") + + +def emit(payload: dict[str, Any]) -> None: + print(json.dumps(payload, indent=2, ensure_ascii=False)) + + +def compact_output(stdout: str, stderr: str, limit: int = 600) -> str: + text = (stdout + "\n" + stderr).strip() + if len(text) <= limit: + return text + return text[:limit] + "..." + + +def normalize_repo_path(value: str) -> str: + normalized = value.strip().replace("\\", "/") + if normalized.startswith("./"): + normalized = normalized[2:] + while "//" in normalized: + normalized = normalized.replace("//", "/") + return normalized + + +def validate_task_id(value: str) -> str: + task_id = _require_string(value, "task_id").strip() + if task_id in {".", ".."}: + raise WorkflowError(f"task_id must not be {task_id!r}") + if "/" in task_id or "\\" in task_id: + raise WorkflowError(f"task_id must not contain path separators: {value}") + if not re.fullmatch(r"[A-Za-z0-9][A-Za-z0-9._-]*", task_id): + raise WorkflowError( + "task_id may only contain letters, numbers, dot, underscore, and hyphen, and must start with an alphanumeric character" + ) + return task_id + + +def path_matches(path: str, patterns: list[str]) -> bool: + normalized = path.replace("\\", "/") + pure = PurePosixPath(normalized) + return any(pure.match(pattern) or fnmatch.fnmatch(normalized, pattern) for pattern in patterns) + + +def validate_relative_repo_path(value: str, *, allow_glob: bool = True) -> str: + normalized = normalize_repo_path(value) + if not normalized: + raise WorkflowError("path must not be empty") + if normalized == ".": + raise WorkflowError("path must not be repo root") + if normalized.startswith("/") or normalized.startswith("../") or "/../" in normalized or normalized == "..": + raise WorkflowError(f"path must be repo-relative: {value}") + if not allow_glob and any(token in normalized for token in "*?["): + raise WorkflowError(f"path must not contain glob tokens: {value}") + return normalized + + +def has_glob(pattern: str) -> bool: + return any(token in pattern for token in "*?[") + + +def scope_matches(path: str, scope: str) -> bool: + normalized_path = normalize_repo_path(path) + normalized_scope = validate_relative_repo_path(scope) + if normalized_scope.endswith("/"): + scope_root = normalized_scope.rstrip("/") + return normalized_path == scope_root or normalized_path.startswith(f"{scope_root}/") + if has_glob(normalized_scope): + return path_matches(normalized_path, [normalized_scope]) + return normalized_path == normalized_scope + + +def _require_mapping(payload: Any, label: str) -> dict[str, Any]: + if not isinstance(payload, dict): + raise WorkflowError(f"{label} must be an object") + return payload + + +def _require_keys(payload: dict[str, Any], required: list[str], label: str) -> None: + missing = [key for key in required if key not in payload] + if missing: + raise WorkflowError(f"{label} missing required keys: {', '.join(missing)}") + + +def _require_string(value: Any, label: str) -> str: + if not isinstance(value, str) or not value.strip(): + raise WorkflowError(f"{label} must be a non-empty string") + return value + + +def _require_optional_string(value: Any, label: str) -> str | None: + if value is None: + return None + if not isinstance(value, str) or not value.strip(): + raise WorkflowError(f"{label} must be null or a non-empty string") + return value + + +def _require_bool(value: Any, label: str) -> bool: + if not isinstance(value, bool): + raise WorkflowError(f"{label} must be a boolean") + return value + + +def _require_string_list(value: Any, label: str, *, allow_empty: bool = True) -> list[str]: + if not isinstance(value, list): + raise WorkflowError(f"{label} must be an array") + result: list[str] = [] + for index, item in enumerate(value): + if not isinstance(item, str) or not item.strip(): + raise WorkflowError(f"{label}[{index}] must be a non-empty string") + result.append(item) + if not allow_empty and not result: + raise WorkflowError(f"{label} must not be empty") + return result + + +def _require_int(value: Any, label: str, *, minimum: int = 0) -> int: + if not isinstance(value, int) or isinstance(value, bool): + raise WorkflowError(f"{label} must be an integer") + if value < minimum: + raise WorkflowError(f"{label} must be >= {minimum}") + return value + + +def empty_task_intake() -> dict[str, Any]: + return { + "request_summary": None, + "problem_summary": None, + "goals": [], + "non_goals": [], + "constraints": [], + "acceptance": [], + "clarifications": [], + } + + +def _strip_markdown_prefix(line: str) -> str: + stripped = line.strip() + stripped = re.sub(r"^[-*+]\s+", "", stripped) + stripped = re.sub(r"^\d+\.\s+", "", stripped) + return stripped.strip() + + +def _markdown_lines(body: str) -> list[str]: + return [_strip_markdown_prefix(line) for line in body.splitlines() if line.strip()] + + +def _normalize_summary(body: str, label: str) -> str: + lines = _markdown_lines(body) + if not lines: + raise WorkflowError(f"{label} must not be empty") + return " ".join(lines) + + +def _normalize_items(body: str, label: str) -> list[str]: + items = _markdown_lines(body) + if not items: + raise WorkflowError(f"{label} must not be empty") + return items + + +def _normalize_clarifications(body: str, *, strict: bool) -> tuple[list[dict[str, Any]], list[str]]: + clarifications: list[dict[str, Any]] = [] + errors: list[str] = [] + current: dict[str, Any] = {} + + def push_error(message: str) -> None: + if strict: + raise WorkflowError(f"Socratic Clarification Log {message}") + errors.append(f"Socratic Clarification Log {message}") + + for line in _markdown_lines(body): + if line.startswith("Q:"): + if current: + push_error("must use complete Q/A/Decision triplets") + current = {} + question = line[2:].strip() + if not question: + push_error("contains an empty question") + continue + current = {"question": question} + continue + + if line.startswith("A:"): + if "question" not in current or "answer" in current: + push_error("must place each answer after a question") + continue + answer = line[2:].strip() + if not answer: + push_error("contains an empty answer") + continue + current["answer"] = answer + continue + + if line.startswith("Decision:"): + if "question" not in current or "answer" not in current or "decision" in current: + push_error("must place each decision after a question and answer") + continue + decision = line[len("Decision:") :].strip() + if not decision: + push_error("contains an empty decision") + continue + current["decision"] = decision + current["resolved"] = True + clarifications.append(current) + current = {} + continue + + push_error("must only contain Q:, A:, and Decision: entries") + + if current: + push_error("must end with a complete Q/A/Decision triplet") + return clarifications, errors + + +def inspect_spec(spec_path: Path) -> dict[str, Any]: + readiness = { + "ready_for_approval": False, + "missing_sections": [], + "placeholder_sections": [], + "validation_errors": [], + "clarification_count": 0, + "unresolved_clarifications": [], + "intake": empty_task_intake(), + } + if not spec_path.exists(): + readiness["validation_errors"].append("spec.md is missing") + return readiness + + spec = spec_path.read_text(encoding="utf-8") + sections = markdown_sections(spec) + readiness["missing_sections"] = [name for name in SPEC_REQUIRED_SECTIONS if name not in sections] + if readiness["missing_sections"]: + return readiness + + placeholder_sections: list[str] = [] + for name in SPEC_REQUIRED_SECTIONS: + body = sections[name] + content_lines = [line.strip() for line in body.splitlines() if line.strip()] + if not content_lines: + placeholder_sections.append(name) + continue + if any("TODO:" in line for line in content_lines): + placeholder_sections.append(name) + readiness["placeholder_sections"] = placeholder_sections + if placeholder_sections: + return readiness + + try: + clarifications, clarification_errors = _normalize_clarifications(sections["Socratic Clarification Log"], strict=False) + intake = { + "request_summary": _normalize_summary(sections["Request"], "Request"), + "problem_summary": _normalize_summary(sections["Problem"], "Problem"), + "goals": _normalize_items(sections["Goals"], "Goals"), + "non_goals": _normalize_items(sections["Non-goals"], "Non-goals"), + "constraints": _normalize_items(sections["Constraints"], "Constraints"), + "acceptance": _normalize_items(sections["Acceptance"], "Acceptance"), + "clarifications": clarifications, + } + readiness["intake"] = intake + readiness["clarification_count"] = len(clarifications) + readiness["validation_errors"].extend(clarification_errors) + except WorkflowError as exc: + readiness["validation_errors"].append(str(exc)) + + if readiness["clarification_count"] == 0: + readiness["unresolved_clarifications"].append("Socratic Clarification Log requires at least one complete Q/A/Decision triplet") + readiness["unresolved_clarifications"].extend(readiness["validation_errors"]) + readiness["ready_for_approval"] = not ( + readiness["missing_sections"] or readiness["placeholder_sections"] or readiness["unresolved_clarifications"] + ) + return readiness + + +def validate_spec_for_approval(spec_path: Path) -> dict[str, Any]: + readiness = inspect_spec(spec_path) + if readiness["missing_sections"]: + raise WorkflowError(f"spec.md missing required sections: {', '.join(readiness['missing_sections'])}") + if readiness["placeholder_sections"]: + raise WorkflowError( + f"spec.md still contains placeholder content in: {', '.join(readiness['placeholder_sections'])}" + ) + if readiness["unresolved_clarifications"]: + raise WorkflowError("; ".join(readiness["unresolved_clarifications"])) + return readiness["intake"] + + +def validate_locked_intake(payload: dict[str, Any], label: str = "task.intake") -> dict[str, Any]: + payload = _require_mapping(payload, label) + _require_keys( + payload, + [ + "request_summary", + "problem_summary", + "goals", + "non_goals", + "constraints", + "acceptance", + "clarifications", + ], + label, + ) + request_summary = _require_optional_string(payload["request_summary"], f"{label}.request_summary") + problem_summary = _require_optional_string(payload["problem_summary"], f"{label}.problem_summary") + goals = _require_string_list(payload["goals"], f"{label}.goals") + non_goals = _require_string_list(payload["non_goals"], f"{label}.non_goals") + constraints = _require_string_list(payload["constraints"], f"{label}.constraints") + acceptance = _require_string_list(payload["acceptance"], f"{label}.acceptance") + clarifications = payload["clarifications"] + if not isinstance(clarifications, list): + raise WorkflowError(f"{label}.clarifications must be an array") + + normalized_clarifications: list[dict[str, Any]] = [] + for index, clarification in enumerate(clarifications): + clarification = _require_mapping(clarification, f"{label}.clarifications[{index}]") + _require_keys(clarification, ["question", "answer", "decision", "resolved"], f"{label}.clarifications[{index}]") + normalized_clarifications.append( + { + "question": _require_string(clarification["question"], f"{label}.clarifications[{index}].question"), + "answer": _require_string(clarification["answer"], f"{label}.clarifications[{index}].answer"), + "decision": _require_string(clarification["decision"], f"{label}.clarifications[{index}].decision"), + "resolved": _require_bool(clarification["resolved"], f"{label}.clarifications[{index}].resolved"), + } + ) + + return { + "request_summary": request_summary, + "problem_summary": problem_summary, + "goals": goals, + "non_goals": non_goals, + "constraints": constraints, + "acceptance": acceptance, + "clarifications": normalized_clarifications, + } + + +def ensure_locked_intake(payload: dict[str, Any], label: str = "task.intake") -> dict[str, Any]: + intake = validate_locked_intake(payload, label) + required_list_fields = ["goals", "non_goals", "constraints", "acceptance", "clarifications"] + if intake["request_summary"] is None: + raise WorkflowError(f"{label}.request_summary must be locked before approval") + if intake["problem_summary"] is None: + raise WorkflowError(f"{label}.problem_summary must be locked before approval") + for name in required_list_fields: + if not intake[name]: + raise WorkflowError(f"{label}.{name} must not be empty") + unresolved = [item["question"] for item in intake["clarifications"] if not item["resolved"]] + if unresolved: + raise WorkflowError(f"{label}.clarifications must all be resolved") + return intake + + +def validate_task(payload: dict[str, Any]) -> None: + payload = _require_mapping(payload, "task") + _require_keys( + payload, + [ + "id", + "title", + "state", + "primary_repo", + "created_at", + "approved_at", + "approval", + "active_phase_id", + "latest_run_id", + "last_verified_run_id", + "blocked_reason", + "user_validated", + "user_validation_note", + "intake", + ], + "task", + ) + _require_string(payload["id"], "task.id") + _require_string(payload["title"], "task.title") + _require_string(payload["primary_repo"], "task.primary_repo") + if payload["state"] not in TASK_STATES: + raise WorkflowError(f"task.state must be one of {sorted(TASK_STATES)}") + _require_string(payload["created_at"], "task.created_at") + _require_optional_string(payload["approved_at"], "task.approved_at") + _require_optional_string(payload["active_phase_id"], "task.active_phase_id") + _require_optional_string(payload["latest_run_id"], "task.latest_run_id") + _require_optional_string(payload["last_verified_run_id"], "task.last_verified_run_id") + _require_optional_string(payload["blocked_reason"], "task.blocked_reason") + _require_bool(payload["user_validated"], "task.user_validated") + _require_optional_string(payload["user_validation_note"], "task.user_validation_note") + intake = validate_locked_intake(payload["intake"]) + + approval = payload["approval"] + if approval is not None: + approval = _require_mapping(approval, "task.approval") + _require_keys(approval, ["actor", "note", "timestamp"], "task.approval") + _require_string(approval["actor"], "task.approval.actor") + _require_string(approval["note"], "task.approval.note") + _require_string(approval["timestamp"], "task.approval.timestamp") + + if (payload["approved_at"] is None) != (payload["approval"] is None): + raise WorkflowError("task approval metadata must be fully set or fully null") + if payload["state"] == "draft" and payload["approval"] is not None: + raise WorkflowError("draft task cannot carry approval metadata") + if payload["state"] == "blocked" and not payload["blocked_reason"]: + raise WorkflowError("blocked task requires blocked_reason") + if payload["state"] != "blocked" and payload["blocked_reason"] is not None: + raise WorkflowError("blocked_reason is only valid when state=blocked") + if payload["user_validated"] and not payload["user_validation_note"]: + raise WorkflowError("user validation note is required when user_validated=true") + if payload["user_validation_note"] and not payload["user_validated"]: + raise WorkflowError("user_validation_note requires user_validated=true") + if payload["state"] in {"review_ready", "completed"} and not payload["last_verified_run_id"]: + raise WorkflowError("review_ready/completed task requires last_verified_run_id") + if payload["state"] != "draft": + ensure_locked_intake(intake) + + +def validate_phases(payload: dict[str, Any]) -> None: + payload = _require_mapping(payload, "phases") + _require_keys(payload, ["task_id", "generated_at", "phases"], "phases") + _require_string(payload["task_id"], "phases.task_id") + _require_string(payload["generated_at"], "phases.generated_at") + phase_list = payload["phases"] + if not isinstance(phase_list, list): + raise WorkflowError("phases.phases must be an array") + + phase_ids: list[str] = [] + orders: list[int] = [] + for index, phase in enumerate(phase_list, start=1): + phase = _require_mapping(phase, f"phase[{index}]") + _require_keys( + phase, + [ + "id", + "order", + "title", + "goal", + "inputs", + "allowed_write_paths", + "acceptance", + "status", + "retry_count", + "test_policy", + ], + f"phase[{index}]", + ) + phase_id = _require_string(phase["id"], f"phase[{index}].id").strip() + order = _require_int(phase["order"], f"phase[{index}].order", minimum=1) + _require_string(phase["title"], f"phase[{index}].title") + _require_string(phase["goal"], f"phase[{index}].goal") + _require_string_list(phase["inputs"], f"phase[{index}].inputs") + + write_paths = _require_string_list(phase["allowed_write_paths"], f"phase[{index}].allowed_write_paths", allow_empty=False) + normalized_paths = [validate_relative_repo_path(path) for path in write_paths] + if len(set(normalized_paths)) != len(normalized_paths): + raise WorkflowError(f"phase[{index}] allowed_write_paths must be unique") + + acceptance = _require_mapping(phase["acceptance"], f"phase[{index}].acceptance") + _require_keys(acceptance, ["commands"], f"phase[{index}].acceptance") + _require_string_list(acceptance["commands"], f"phase[{index}].acceptance.commands", allow_empty=False) + + if phase["status"] not in PHASE_STATES: + raise WorkflowError(f"phase[{index}].status must be one of {sorted(PHASE_STATES)}") + _require_int(phase["retry_count"], f"phase[{index}].retry_count", minimum=0) + + policy = _require_mapping(phase["test_policy"], f"phase[{index}].test_policy") + _require_keys(policy, ["mode", "evidence"], f"phase[{index}].test_policy") + mode = _require_string(policy["mode"], f"phase[{index}].test_policy.mode") + evidence = _require_string_list(policy["evidence"], f"phase[{index}].test_policy.evidence") + if mode not in {"require_tests", "evidence_only"}: + raise WorkflowError(f"phase[{index}].test_policy.mode is invalid: {mode}") + if mode == "evidence_only" and not evidence: + raise WorkflowError(f"phase[{index}] evidence_only requires non-empty evidence") + + phase_ids.append(phase_id) + orders.append(order) + + if len(set(phase_ids)) != len(phase_ids): + raise WorkflowError("phase ids must be unique") + expected_orders = list(range(1, len(orders) + 1)) + if sorted(orders) != expected_orders: + raise WorkflowError( + f"phase orders must be contiguous starting at 1: expected {expected_orders}, got {sorted(orders)}" + ) + + +def validate_run(payload: dict[str, Any]) -> None: + payload = _require_mapping(payload, "run") + _require_keys( + payload, + [ + "id", + "task_id", + "phase_id", + "event", + "commands", + "result", + "evidence", + "error_fingerprint", + "next_action", + "timestamp", + ], + "run", + ) + _require_string(payload["id"], "run.id") + _require_string(payload["task_id"], "run.task_id") + _require_optional_string(payload["phase_id"], "run.phase_id") + _require_string(payload["event"], "run.event") + commands = payload["commands"] + if not isinstance(commands, list): + raise WorkflowError("run.commands must be an array") + for index, command in enumerate(commands): + command = _require_mapping(command, f"run.commands[{index}]") + _require_keys(command, ["command", "status", "output"], f"run.commands[{index}]") + _require_string(command["command"], f"run.commands[{index}].command") + status = _require_string(command["status"], f"run.commands[{index}].status") + if status not in {"passed", "failed", "blocked", "skipped"}: + raise WorkflowError(f"run.commands[{index}].status is invalid: {status}") + if not isinstance(command["output"], str): + raise WorkflowError(f"run.commands[{index}].output must be a string") + + result = _require_string(payload["result"], "run.result") + if result not in {"passed", "failed", "blocked"}: + raise WorkflowError(f"run.result is invalid: {result}") + _require_string_list(payload["evidence"], "run.evidence") + _require_optional_string(payload["error_fingerprint"], "run.error_fingerprint") + _require_string(payload["next_action"], "run.next_action") + _require_string(payload["timestamp"], "run.timestamp") + + +def markdown_sections(text: str) -> dict[str, str]: + matches = list(re.finditer(r"^##\s+(.+?)\s*$", text, flags=re.M)) + sections: dict[str, str] = {} + for index, match in enumerate(matches): + name = match.group(1).strip() + start = match.end() + end = matches[index + 1].start() if index + 1 < len(matches) else len(text) + sections[name] = text[start:end].strip() + return sections diff --git a/scripts/workflow_runtime/templates.py b/scripts/workflow_runtime/templates.py new file mode 100644 index 0000000..77d6a61 --- /dev/null +++ b/scripts/workflow_runtime/templates.py @@ -0,0 +1,65 @@ +from __future__ import annotations + +from workflow_runtime.models import empty_task_intake, now_iso + + +def default_spec(task_id: str, title: str, primary_repo: str) -> str: + return ( + f"# {title}\n\n" + f"- Task ID: `{task_id}`\n" + f"- Primary Repo: `{primary_repo}`\n" + f"- Status: `draft`\n\n" + "## Request\n\n" + "- TODO: 요청 배경과 기대 결과를 한 문단 또는 bullet로 정리한다.\n\n" + "## Problem\n\n" + "- TODO: 지금 무엇이 문제인지, 왜 바꿔야 하는지 적는다.\n\n" + "## Goals\n\n" + "- TODO: 이번 작업에서 반드시 만족해야 하는 결과를 적는다.\n\n" + "## Non-goals\n\n" + "- TODO: 이번 작업에 포함하지 않을 범위를 적는다.\n\n" + "## Constraints\n\n" + "- TODO: 기술, 일정, 운영, 정책 제약을 적는다.\n\n" + "## Acceptance\n\n" + "- TODO: 완료를 판단할 수 있는 관찰 가능한 기준을 적는다.\n\n" + "## Socratic Clarification Log\n\n" + "- TODO: Q/A/Decision triplet으로 잠근 결정만 남긴다.\n" + "- Q: ...\n" + "- A: ...\n" + "- Decision: ...\n" + ) + + +def empty_task_payload(task_id: str, title: str, primary_repo: str) -> dict[str, object]: + return { + "id": task_id, + "title": title, + "state": "draft", + "primary_repo": primary_repo, + "created_at": now_iso(), + "approved_at": None, + "approval": None, + "active_phase_id": None, + "latest_run_id": None, + "last_verified_run_id": None, + "blocked_reason": None, + "user_validated": False, + "user_validation_note": None, + "intake": empty_task_intake(), + } + + +def empty_phases_payload(task_id: str) -> dict[str, object]: + return { + "task_id": task_id, + "generated_at": now_iso(), + "phases": [], + } + + +def approval_block(actor: str, note: str, timestamp: str) -> str: + return ( + "\n## Approval\n\n" + f"- Actor: `{actor}`\n" + f"- Timestamp: `{timestamp}`\n" + f"- Note: {note}\n" + ) diff --git a/tests/test_workflow_cli.py b/tests/test_workflow_cli.py new file mode 100644 index 0000000..a2e496d --- /dev/null +++ b/tests/test_workflow_cli.py @@ -0,0 +1,563 @@ +from __future__ import annotations + +import json +import os +import subprocess +import tempfile +import unittest +from pathlib import Path + + +REPO_ROOT = Path(__file__).resolve().parents[1] +SCRIPT = REPO_ROOT / "scripts" / "workflow.py" + + +class WorkflowCliTest(unittest.TestCase): + def run_cli(self, root: Path, *args: str, expected: int = 0, stdin: str | None = None) -> subprocess.CompletedProcess[str]: + env = os.environ.copy() + env["WORKFLOW_ROOT"] = str(root) + result = subprocess.run( + ["python3", str(SCRIPT), *args], + cwd=REPO_ROOT, + env=env, + input=stdin, + capture_output=True, + text=True, + ) + self.assertEqual( + result.returncode, + expected, + msg=f"stdout={result.stdout}\nstderr={result.stderr}", + ) + return result + + def read_json(self, path: Path) -> dict: + return json.loads(path.read_text(encoding="utf-8")) + + def init_git_repo(self, root: Path) -> None: + subprocess.run(["git", "init"], cwd=root, capture_output=True, text=True, check=True) + subprocess.run(["git", "config", "user.email", "test@example.com"], cwd=root, capture_output=True, text=True, check=True) + subprocess.run(["git", "config", "user.name", "Workflow Test"], cwd=root, capture_output=True, text=True, check=True) + + def git_config(self, root: Path, key: str) -> str: + result = subprocess.run( + ["git", "config", "--local", "--get", key], + cwd=root, + capture_output=True, + text=True, + check=True, + ) + return result.stdout.strip() + + def write_valid_agents(self, root: Path) -> None: + (root / "AGENTS.md").write_text( + ( + "# AGENTS.md\n\n" + "## Mission\n\n" + "- control plane only\n\n" + "## Start Here\n\n" + "1. read docs\n\n" + "## CRITICAL Rules\n\n" + "- CRITICAL: approved spec first\n\n" + "## Architecture Boundaries\n\n" + "ALWAYS: update app source of truth together.\n" + "NEVER: bypass app source of truth.\n\n" + "## Workflow Contract\n\n" + "1. new\n" + "2. approve\n" + "3. plan\n\n" + "## TDD Contract\n\n" + "ALWAYS: tests with code.\n\n" + "## Command Contract\n\n" + "ALWAYS: use workflow.py.\n\n" + "## Source Of Truth Order\n\n" + "1. AGENTS.md\n\n" + "## Forbidden Actions\n\n" + "NEVER: edit canonical state manually.\n\n" + "## Change Discipline\n\n" + "ALWAYS: keep docs and tests aligned.\n" + ), + encoding="utf-8", + ) + + def finalize_spec(self, root: Path, task_id: str, title: str = "Harness V2") -> None: + spec_path = root / "workflows" / "tasks" / task_id / "spec.md" + spec_path.write_text( + ( + f"# {title}\n\n" + f"- Task ID: `{task_id}`\n" + "- Primary Repo: `git-ranker-workflow`\n" + "- Status: `draft`\n\n" + "## Request\n\n" + "- Codex 기준 workflow control plane을 강화한다.\n\n" + "## Problem\n\n" + "- 승인, phase, verification, hook contract가 느슨해서 자동화 신뢰도가 떨어진다.\n\n" + "## Goals\n\n" + "- spec 승인, phase 실행, verification, review를 JSON state로 강제한다.\n\n" + "## Non-goals\n\n" + "- 앱 저장소 동작 자체를 다시 설계하지 않는다.\n\n" + "## Constraints\n\n" + "- 기존 저장소 위에서 추가 요구사항을 계속 수용해야 한다.\n\n" + "## Acceptance\n\n" + "- approve/plan/run/verify/review/reopen 흐름이 CLI와 테스트로 검증된다.\n\n" + "## Socratic Clarification Log\n\n" + "- Q: 문서 구조는 어떻게 정리할까?\n" + "- A: `tasks/`와 `system/`으로 역할을 나눈다.\n" + "- Decision: 초안 작성은 `spec.md`, 승인 고정은 `task.json.intake`가 소유한다.\n" + ), + encoding="utf-8", + ) + + def write_phase_input( + self, + root: Path, + commands: list[str], + *, + allowed_write_paths: list[str] | None = None, + test_policy_mode: str = "require_tests", + test_policy_evidence: list[str] | None = None, + ) -> Path: + phase_file = root / "phase-input.json" + phase_file.write_text( + json.dumps( + { + "phases": [ + { + "id": "phase-1", + "title": "build-runtime", + "goal": "create workflow runtime", + "inputs": ["spec.md"], + "allowed_write_paths": allowed_write_paths or ["scripts/", "tests/"], + "acceptance": {"commands": commands}, + "test_policy": { + "mode": test_policy_mode, + "evidence": test_policy_evidence or [], + }, + } + ] + }, + ensure_ascii=False, + ), + encoding="utf-8", + ) + return phase_file + + def write_repo_docs(self, root: Path) -> None: + docs_dir = root / "docs" + docs_dir.mkdir(parents=True, exist_ok=True) + (docs_dir / "README.md").write_text( + ( + "# Docs Index\n\n" + "- AGENTS.md\n" + "- docs/runtime.md\n" + "- docs/artifact-model.md\n" + ), + encoding="utf-8", + ) + (docs_dir / "artifact-model.md").write_text( + ( + "# Artifact Model\n\n" + "- task.json\n" + "- intake\n" + "- clarifications\n" + ), + encoding="utf-8", + ) + (docs_dir / "runtime.md").write_text( + ( + "# Runtime\n\n" + "1. 소크라테스 질문\n" + "2. approve\n" + "3. plan\n" + "4. review --close\n" + ), + encoding="utf-8", + ) + (docs_dir / "hooks.md").write_text( + ( + "# Hooks\n\n" + "- .githooks/\n" + "- TDD Guard\n" + "- Dangerous Command Guard\n" + "- Circuit Breaker\n" + ), + encoding="utf-8", + ) + (docs_dir / "runbook.md").write_text( + ( + "# Runbook\n\n" + "- 사용자가 현재 spec 초안에 명시적으로 동의하면 approve 한다.\n" + "- approve\n" + "- plan\n" + ), + encoding="utf-8", + ) + + def set_breaker_threshold(self, root: Path, threshold: int) -> None: + hooks_path = root / "workflows" / "system" / "hooks.json" + payload = self.read_json(hooks_path) + payload["guards"]["circuit_breaker"]["threshold"] = threshold + hooks_path.write_text(json.dumps(payload, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") + + def bootstrap_task( + self, + root: Path, + task_id: str, + *, + commands: list[str] | None = None, + allowed_write_paths: list[str] | None = None, + test_policy_mode: str = "require_tests", + test_policy_evidence: list[str] | None = None, + ) -> None: + self.run_cli(root, "new", task_id, "--title", "Harness V2", "--primary-repo", "git-ranker-workflow") + self.finalize_spec(root, task_id) + self.run_cli(root, "approve", task_id, "--note", "approved by user") + phase_file = self.write_phase_input( + root, + commands or ["python3 -c \"print('ok')\""], + allowed_write_paths=allowed_write_paths, + test_policy_mode=test_policy_mode, + test_policy_evidence=test_policy_evidence, + ) + self.run_cli(root, "plan", task_id, "--from", str(phase_file)) + + def test_init_creates_system_layout_and_doctor_passes(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + self.init_git_repo(root) + self.run_cli(root, "init") + self.bootstrap_task( + root, + "task-001", + test_policy_mode="evidence_only", + test_policy_evidence=["workflow CLI exercised by unit tests"], + ) + self.run_cli(root, "doctor") + self.run_cli(root, "status", "--check", "--all") + + task = self.read_json(root / "workflows" / "tasks" / "task-001" / "task.json") + self.assertEqual(task["state"], "approved") + self.assertEqual(task["intake"]["clarifications"][0]["resolved"], True) + self.assertTrue((root / "workflows" / "system" / "hooks.json").exists()) + self.assertFalse((root / "workflows" / "config").exists()) + self.assertFalse((root / "workflows" / "schemas").exists()) + self.assertEqual(self.git_config(root, "core.hooksPath"), ".githooks") + + spec_text = (root / "workflows" / "tasks" / "task-001" / "spec.md").read_text(encoding="utf-8") + self.assertIn("## Approval", spec_text) + + def test_plan_requires_explicit_input(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + self.run_cli(root, "new", "task-002", "--title", "Harness V2", "--primary-repo", "git-ranker-workflow") + self.finalize_spec(root, "task-002") + self.run_cli(root, "approve", "task-002", "--note", "approved by user") + self.run_cli(root, "plan", "task-002", expected=1) + + def test_approve_requires_structured_socratic_log_and_locks_intake(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + self.run_cli(root, "new", "task-002a", "--title", "Harness V2", "--primary-repo", "git-ranker-workflow") + spec_path = root / "workflows" / "tasks" / "task-002a" / "spec.md" + self.finalize_spec(root, "task-002a") + spec_path.write_text(spec_path.read_text(encoding="utf-8").replace("- Decision: 초안 작성은 `spec.md`, 승인 고정은 `task.json.intake`가 소유한다.\n", ""), encoding="utf-8") + + result = self.run_cli(root, "approve", "task-002a", "--note", "approved by user", expected=1) + self.assertIn("Socratic Clarification Log", result.stderr) + + self.finalize_spec(root, "task-002a") + self.run_cli(root, "approve", "task-002a", "--note", "approved by user") + + task = self.read_json(root / "workflows" / "tasks" / "task-002a" / "task.json") + self.assertEqual(task["intake"]["request_summary"], "Codex 기준 workflow control plane을 강화한다.") + self.assertEqual(task["intake"]["goals"], ["spec 승인, phase 실행, verification, review를 JSON state로 강제한다."]) + self.assertEqual(len(task["intake"]["clarifications"]), 1) + self.assertEqual(task["intake"]["clarifications"][0]["decision"], "초안 작성은 `spec.md`, 승인 고정은 `task.json.intake`가 소유한다.") + + def test_plan_requires_locked_intake_even_for_approved_task(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + self.run_cli(root, "new", "task-002b", "--title", "Harness V2", "--primary-repo", "git-ranker-workflow") + self.finalize_spec(root, "task-002b") + self.run_cli(root, "approve", "task-002b", "--note", "approved by user") + + task_path = root / "workflows" / "tasks" / "task-002b" / "task.json" + task = self.read_json(task_path) + task["intake"]["clarifications"] = [] + task_path.write_text(json.dumps(task, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") + + phase_file = self.write_phase_input(root, ["python3 -c \"print('ok')\""]) + result = self.run_cli(root, "plan", "task-002b", "--from", str(phase_file), expected=1) + self.assertIn("task.intake.clarifications", result.stderr) + + def test_status_reports_spec_readiness_for_draft_task(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + self.run_cli(root, "new", "task-002c", "--title", "Harness V2", "--primary-repo", "git-ranker-workflow") + self.finalize_spec(root, "task-002c") + spec_path = root / "workflows" / "tasks" / "task-002c" / "spec.md" + spec_path.write_text(spec_path.read_text(encoding="utf-8").replace("- Decision: 초안 작성은 `spec.md`, 승인 고정은 `task.json.intake`가 소유한다.\n", ""), encoding="utf-8") + + result = self.run_cli(root, "status", "task-002c") + payload = json.loads(result.stdout) + self.assertEqual(payload["task"]["state"], "draft") + self.assertFalse(payload["spec"]["ready_for_approval"]) + self.assertEqual(payload["spec"]["clarification_count"], 0) + self.assertTrue(payload["spec"]["unresolved_clarifications"]) + + def test_tdd_guard_blocks_completion_without_tests(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + self.bootstrap_task(root, "task-003") + self.run_cli(root, "run", "task-003", "--start") + self.run_cli( + root, + "run", + "task-003", + "--complete", + "--changed-path", + "scripts/workflow.py", + expected=1, + ) + + def test_write_scope_blocks_out_of_phase_changes(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + self.bootstrap_task( + root, + "task-004", + test_policy_mode="evidence_only", + test_policy_evidence=["generated code validated through existing test harness"], + ) + self.run_cli(root, "run", "task-004", "--start") + self.run_cli( + root, + "run", + "task-004", + "--complete", + "--changed-path", + "docs/README.md", + expected=1, + ) + + def test_review_requires_last_verified_run_and_closeout(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + self.bootstrap_task(root, "task-005") + self.run_cli(root, "run", "task-005", "--start") + self.run_cli( + root, + "run", + "task-005", + "--complete", + "--changed-path", + "scripts/workflow.py", + "--changed-path", + "tests/test_workflow_cli.py", + ) + self.run_cli(root, "review", "task-005", "--note", "review ready", expected=1) + self.run_cli(root, "verify", "task-005") + self.run_cli(root, "review", "task-005", "--note", "review ready") + self.run_cli(root, "review", "task-005", "--close", "--user-validation-note", "validated") + + task = self.read_json(root / "workflows" / "tasks" / "task-005" / "task.json") + self.assertEqual(task["state"], "completed") + self.assertTrue(task["user_validated"]) + self.assertIsNotNone(task["last_verified_run_id"]) + self.assertNotEqual(task["latest_run_id"], task["last_verified_run_id"]) + + def test_pre_push_requires_verification_for_active_phase_scope(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + self.bootstrap_task( + root, + "task-006", + test_policy_mode="evidence_only", + test_policy_evidence=["runtime guard is exercised by CLI tests"], + ) + self.run_cli( + root, + "hook", + "pre_push", + "--task-id", + "task-006", + "--phase-id", + "phase-1", + "--changed-path", + "scripts/workflow.py", + "--command-text", + "git push origin feature", + expected=1, + ) + + self.run_cli(root, "run", "task-006", "--start") + self.run_cli( + root, + "run", + "task-006", + "--complete", + "--changed-path", + "scripts/workflow.py", + ) + self.run_cli(root, "verify", "task-006") + self.run_cli( + root, + "hook", + "pre_push", + "--task-id", + "task-006", + "--phase-id", + "phase-1", + "--changed-path", + "scripts/workflow.py", + "--command-text", + "git push origin feature", + ) + + def test_reopen_resets_failed_task_for_repair_loop(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + self.bootstrap_task( + root, + "task-007", + test_policy_mode="evidence_only", + test_policy_evidence=["repair loop uses explicit reopen flow"], + ) + self.run_cli(root, "run", "task-007", "--start") + self.run_cli( + root, + "run", + "task-007", + "--fail", + "--error-fingerprint", + "same-error", + "--note", + "boom", + expected=1, + ) + self.run_cli(root, "reopen", "task-007", "--note", "retry after repair", "--phase-id", "phase-1") + + task = self.read_json(root / "workflows" / "tasks" / "task-007" / "task.json") + phases = self.read_json(root / "workflows" / "tasks" / "task-007" / "phases.json") + self.assertEqual(task["state"], "approved") + self.assertIsNone(task["blocked_reason"]) + self.assertIsNone(task["last_verified_run_id"]) + self.assertEqual(task["active_phase_id"], "phase-1") + self.assertEqual(phases["phases"][0]["status"], "pending") + + def test_circuit_breaker_triggers_from_verification_failures(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + self.bootstrap_task( + root, + "task-008", + commands=["python3 -c \"import sys; sys.exit(1)\""], + test_policy_mode="evidence_only", + test_policy_evidence=["verification failure path is intentional in this test"], + ) + self.set_breaker_threshold(root, 2) + + self.run_cli(root, "run", "task-008", "--start") + self.run_cli( + root, + "run", + "task-008", + "--complete", + "--changed-path", + "scripts/workflow.py", + ) + self.run_cli(root, "verify", "task-008", expected=1) + + self.run_cli(root, "reopen", "task-008", "--note", "retry after failed verification", "--phase-id", "phase-1") + self.run_cli(root, "run", "task-008", "--start") + self.run_cli( + root, + "run", + "task-008", + "--complete", + "--changed-path", + "scripts/workflow.py", + ) + self.run_cli(root, "verify", "task-008", expected=2) + + task = self.read_json(root / "workflows" / "tasks" / "task-008" / "task.json") + self.assertEqual(task["state"], "blocked") + self.assertEqual(task["blocked_reason"], "repeated verification failure in phase-1") + + def test_dangerous_command_guard_blocks_short_force_push(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + self.run_cli(root, "init") + + result = self.run_cli( + root, + "hook", + "pre_command", + "--command-text", + "git push -f origin feature", + expected=1, + ) + payload = json.loads(result.stdout) + self.assertEqual(payload["status"], "failed") + self.assertTrue(any("blocked command" in message for message in payload["messages"])) + + def test_new_rejects_task_id_path_traversal(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + self.run_cli(root, "init") + + result = self.run_cli( + root, + "new", + "../../../escape-task", + "--title", + "Harness V2", + "--primary-repo", + "git-ranker-workflow", + expected=1, + ) + self.assertIn("task_id", result.stderr) + self.assertFalse((root / "escape-task").exists()) + self.assertFalse((root / "workflows" / "escape-task").exists()) + + def test_doctor_fails_when_agents_constitution_is_incomplete(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + self.init_git_repo(root) + self.run_cli(root, "init") + self.write_repo_docs(root) + (root / "AGENTS.md").write_text("# AGENTS.md\n", encoding="utf-8") + + result = self.run_cli(root, "doctor", expected=1) + report = json.loads(result.stdout) + self.assertTrue(any("AGENTS.md missing heading" in error for error in report["errors"])) + self.assertTrue(any("AGENTS.md missing constitution marker" in error for error in report["errors"])) + + def test_init_and_doctor_enforce_git_hooks_path(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + self.init_git_repo(root) + self.run_cli(root, "init") + self.assertEqual(self.git_config(root, "core.hooksPath"), ".githooks") + + subprocess.run(["git", "config", "--local", "core.hooksPath", ".git/hooks"], cwd=root, capture_output=True, text=True, check=True) + result = self.run_cli(root, "doctor", expected=1) + report = json.loads(result.stdout) + self.assertTrue(any("core.hooksPath" in error for error in report["errors"])) + + def test_doctor_fails_when_runbook_loses_socratic_contract(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + self.init_git_repo(root) + self.run_cli(root, "init") + self.write_repo_docs(root) + self.write_valid_agents(root) + (root / "docs" / "runbook.md").write_text("# Runbook\n\n- approve\n", encoding="utf-8") + + result = self.run_cli(root, "doctor", expected=1) + report = json.loads(result.stdout) + self.assertTrue(any("docs/runbook.md missing required marker" in error for error in report["errors"])) + + +if __name__ == "__main__": + unittest.main() diff --git a/workflows/system/hooks.json b/workflows/system/hooks.json new file mode 100644 index 0000000..0613bbf --- /dev/null +++ b/workflows/system/hooks.json @@ -0,0 +1,88 @@ +{ + "version": 1, + "events": { + "pre_command": [ + "dangerous_cmd_guard" + ], + "pre_commit": [ + "write_scope_guard", + "tdd_guard" + ], + "pre_phase_start": [], + "post_change": [ + "tdd_guard" + ], + "pre_phase_complete": [ + "write_scope_guard", + "tdd_guard" + ], + "pre_review": [ + "latest_verified_run_guard" + ], + "pre_complete": [ + "user_validation_guard" + ], + "pre_push": [ + "dangerous_cmd_guard", + "latest_verified_run_guard" + ] + }, + "guards": { + "tdd_guard": { + "strict": true, + "implementation_patterns": [ + "*.py", + "*.sh", + "*.js", + "*.jsx", + "*.ts", + "*.tsx", + "*.java", + "*.kt" + ], + "test_patterns": [ + "tests/**", + "**/*_test.py", + "**/*test*.py", + "**/*.spec.ts", + "**/*.test.ts", + "**/*.spec.tsx", + "**/*.test.tsx", + "**/src/test/**" + ], + "ignore_patterns": [ + "docs/**", + "workflows/**", + ".github/**", + "*.md", + "*.json", + "*.yml", + "*.yaml" + ] + }, + "dangerous_cmd_guard": { + "blocked_patterns": [ + "rm\\s+-rf", + "git\\s+reset\\s+--hard", + "git\\s+checkout\\s+--", + "git\\s+clean\\s+-fd", + "git\\s+push(?:\\s+\\S+)*\\s+--force(?:\\S+)?", + "git\\s+push(?:\\s+\\S+)*\\s+-[A-Za-z]*f[A-Za-z]*", + "DROP\\s+TABLE", + "TRUNCATE\\s+TABLE" + ], + "allowlist": [] + }, + "latest_verified_run_guard": { + "require_phase_match": true, + "scope_sensitive_events": [ + "pre_push" + ] + }, + "circuit_breaker": { + "window_seconds": 60, + "threshold": 5, + "state_file": "workflows/system/circuit-breaker.json" + } + } +} diff --git a/workflows/tasks/.gitkeep b/workflows/tasks/.gitkeep new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/workflows/tasks/.gitkeep @@ -0,0 +1 @@ +