diff --git a/.codex/skills/README.md b/.codex/skills/README.md index 51edc83..add664a 100644 --- a/.codex/skills/README.md +++ b/.codex/skills/README.md @@ -33,4 +33,4 @@ 5. 모든 phase가 끝나면 `review-closeout` 6. 실패, block, follow-up이면 `repair-reopen` -새 workflow에서는 markdown spec과 JSON state를 분리한다. `spec.md`는 사람용 요구사항 초안이고, `approve`가 이를 `task.json.intake`로 잠근다. 새 spec contract에서는 clarification coverage category를 모두 채워야 승인된다. `task.json`/`phases.json`/`runs/*.json`은 자동화용 canonical source고, phase boundary kickoff proof도 여기 남긴다. +새 workflow에서는 markdown spec과 JSON state를 분리한다. `spec.md`는 사람용 요구사항 초안이고, `approve`가 이를 `task.json.intake`로 잠근다. 새 spec contract에서는 `Socratic Clarification Log`의 열린 질문이 모두 닫혀야 승인된다. `task.json`/`phases.json`/`runs/*.json`은 자동화용 canonical source고, phase boundary kickoff proof도 여기 남긴다. diff --git a/.codex/skills/repair-reopen/SKILL.md b/.codex/skills/repair-reopen/SKILL.md index 1c416e6..0030d47 100644 --- a/.codex/skills/repair-reopen/SKILL.md +++ b/.codex/skills/repair-reopen/SKILL.md @@ -21,7 +21,7 @@ description: failed, blocked, review follow-up, 추가 요구사항이 들어온 1. 왜 reopen이 필요한지 note를 한 줄로 잠근다. 2. 필요하면 target phase를 정한다. -3. `python3 scripts/workflow.py reopen --note "..." [--phase-id ...]`로 task를 `approved` 상태로 되돌리고 target phase를 `pending`으로 복구한다. +3. `python3 scripts/workflow.py reopen --note "..." [--phase-id ...]`로 task를 `approved` 상태로 되돌리고 target phase와 그 이후 downstream phase를 `pending`으로 복구한다. 4. 추가 요구사항이면 먼저 `spec.md`를 다시 잠근다. 5. spec이 바뀌었으면 `python3 scripts/workflow.py approve --note "..."`로 `task.json.intake`를 다시 잠근다. 6. 필요하면 `plan`으로 phase를 다시 적재한다. @@ -30,6 +30,6 @@ description: failed, blocked, review follow-up, 추가 요구사항이 들어온 ## 결과 - `state=approved`인 `task.json` -- rerunnable target phase (`status=pending`) +- rerunnable target/downstream phases (`status=pending`) - cleared `blocked_reason` - new reopen run evidence diff --git a/.codex/skills/socratic-spec-authoring/SKILL.md b/.codex/skills/socratic-spec-authoring/SKILL.md index ec25cd5..a5b157b 100644 --- a/.codex/skills/socratic-spec-authoring/SKILL.md +++ b/.codex/skills/socratic-spec-authoring/SKILL.md @@ -20,8 +20,8 @@ description: 소크라테스 질문으로 `spec.md`를 잠그고, 사용자 명 ## 작업 방식 1. 질문으로 request, problem, goals, non-goals, constraints, acceptance를 `spec.md`에 기록한다. -2. `Socratic Clarification Log`는 `Q:`, `A:`, `Decision:` triplet으로만 적고, 새 contract에서는 각 `Q:`에 `[scope]`, `[goal]`, `[non_goal]`, `[constraint]`, `[acceptance]` coverage category를 붙인다. -3. `status`에서 `coverage_missing`이 비고 unresolved clarification이 없어질 때까지 질문 루프를 반복한다. +2. `Socratic Clarification Log`는 clarification마다 `Q:`로 시작하고 마지막 줄에 `Status:`를 둔다. `open`은 `Q:`와 선택적 `A:`를 허용하고, `resolved`는 `Q:`/`A:`/`Decision:`/`Status: resolved`를 순서대로 가진다. +3. `status`에서 `open_clarification_count=0`이고 `validation_errors`가 비어 있을 때까지 질문 루프를 반복한다. 질문은 고정 목록이 아니며, spec 작성 중 새 애매점이 생기면 언제든 추가하거나 다시 연다. 4. placeholder가 남아 있지 않은지 확인한다. 5. 사용자가 현재 `spec.md` 초안에 명시적으로 동의했을 때만 `python3 scripts/workflow.py approve --note ...`를 실행한다. 6. `approve`가 `spec.md`를 `task.json.intake`로 잠근다는 점을 전제로, 승인 전에는 phase 생성이나 구현을 시작하지 않는다. diff --git a/AGENTS.md b/AGENTS.md index f20d58d..dab205d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -37,7 +37,7 @@ 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만 사용하고, 새 spec contract에서는 `[scope]`, `[goal]`, `[non_goal]`, `[constraint]`, `[acceptance]` coverage를 모두 채운다. +2. 소크라테스 질문으로 `spec.md`를 잠근다. `Socratic Clarification Log`는 각 clarification마다 `Q:`와 마지막 줄의 `Status:`를 가진다. `Status: open` 질문이 하나라도 남아 있으면 승인하지 않는다. `resolved` 항목만 `A:`와 `Decision:`으로 닫고, spec 작성 중 애매점이 생기면 언제든 질문을 추가하거나 다시 연다. 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`으로 적재한다. 각 phase는 `required_reads`, `starting_points`, `deliverables`, `completion_signal` bootstrap 정보를 가져야 하며, `spec.md`와 `task.json.intake`가 어긋나 있으면 plan을 시작하지 않는다. @@ -49,7 +49,7 @@ NEVER: control plane 문서를 앱 source-of-truth보다 우선시하지 않는 11. verification이 다음 pending phase를 활성화하면 `task.json.kickoff_required_for_phase`를 기록하고 task를 다시 `approved`로 전이한다. 12. 모든 phase가 완료되면 `review`가 review readiness를 기록한다. 13. `review --close --user-validation-note ...`만 최종 완료를 닫을 수 있다. -14. 실패, block, 추가 요구사항이 생기면 `reopen`으로 target phase를 `pending`으로 되돌리고 repair loop를 재개한다. reopened phase가 2번째 이상 phase면 kickoff를 다시 요구한다. +14. 실패, block, 추가 요구사항이 생기면 `reopen`으로 target phase와 그 이후 downstream phase들을 `pending`으로 되돌리고 repair loop를 재개한다. reopened phase가 2번째 이상 phase면 kickoff를 다시 요구한다. 15. `pre_push`의 branch-aware 예외는 현재 브랜치가 `main`이고 로컬 `main` tip이 로컬 `develop` tip과 같을 때의 동기화 publish에만 허용한다. ## TDD Contract @@ -89,7 +89,7 @@ python3 scripts/workflow.py hook pre_push --command-text "..." - `init`는 `.githooks/*`와 `workflows/system/hooks.json`을 source 기준으로 다시 동기화하고, `doctor`는 drift를 실패로 취급한다. - 실제 실행 엔진은 `scripts/workflow_runtime/cli.py`, `engine.py`, `models.py`, `guards.py`, `doctor.py`, `git_ops.py`가 분담한다. - `workflow.py`를 유지하는 이유는 사람과 AI가 하나의 안정적인 명령 surface만 기억하면 되기 때문이다. -- `status`는 새 spec contract에서 `coverage_missing`과 active phase bootstrap summary를 노출해 다음 세션 시작점을 복원한다. +- `status`는 새 spec contract에서 `ready_for_approval`, clarification count, open/resolved count, `open_clarifications`, active phase bootstrap summary를 노출해 다음 세션 시작점을 복원한다. ## Source Of Truth Order diff --git a/docs/artifact-model.md b/docs/artifact-model.md index ad81f23..46907f7 100644 --- a/docs/artifact-model.md +++ b/docs/artifact-model.md @@ -18,7 +18,7 @@ workflows/ ## Artifact Roles -- `workflows/tasks//spec.md`: 사람용 requirement artifact. 요청, 문제, 목표, 비목표, 제약, acceptance, 소크라테스 질문 로그, 승인 기록을 담는다. 새 spec contract에서는 clarification question마다 coverage category를 잠근다. +- `workflows/tasks//spec.md`: 사람용 requirement artifact. 요청, 문제, 목표, 비목표, 제약, acceptance, 소크라테스 질문 로그, 승인 기록을 담는다. 새 spec contract에서는 clarification마다 `Status: open|resolved`를 명시하고, 열린 질문이 없어질 때까지 질의를 계속한다. - `workflows/tasks//task.json`: mutable task state이자 승인 시점의 locked intake source다. approval, active phase, latest run, latest verified run, kickoff requirement, blocked reason, user validation과 `intake`를 담는다. - `workflows/tasks//phases.json`: 승인 이후 생성된 executable phase 집합이다. phase 목표, write scope, acceptance command, test policy, retry count와 다음 세션 kickoff용 bootstrap metadata를 담는다. - `workflows/tasks//runs/*.json`: phase completion, phase kickoff, verification, review, reopen evidence다. @@ -28,7 +28,7 @@ workflows/ ## 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만 허용하고, 새 contract에서는 `[scope]`, `[goal]`, `[non_goal]`, `[constraint]`, `[acceptance]` coverage를 모두 채운다. +2. 소크라테스 질문으로 `spec.md`를 채운다. `Socratic Clarification Log`는 clarification마다 `Q:`로 시작하고 마지막 줄에 `Status:`를 둔다. `open`은 `Q:`와 선택적 `A:`를 허용하고, `resolved`는 `Q:`/`A:`/`Decision:`/`Status: resolved`를 순서대로 가진다. 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`으로 적재한다. @@ -65,10 +65,9 @@ workflows/ `intake.clarifications`의 각 항목은 아래 필드를 가진다. - `question` -- `category` - `answer` - `decision` -- `resolved` +- `status` ## Phase Fields diff --git a/docs/runbook.md b/docs/runbook.md index 4aae60d..c61c93f 100644 --- a/docs/runbook.md +++ b/docs/runbook.md @@ -16,12 +16,20 @@ python3 -m unittest discover -s tests -v python3 scripts/workflow.py new task-001 --title "..." --primary-repo git-ranker-workflow ``` -이후 `workflows/tasks/task-001/spec.md`를 소크라테스 질문으로 잠근다. 새 spec contract에서는 `Socratic Clarification Log`가 아래처럼 coverage category를 포함한 `Q/A/Decision` triplet을 모두 채워야 한다. +이후 `workflows/tasks/task-001/spec.md`를 소크라테스 질문으로 잠근다. 새 spec contract에서는 `Socratic Clarification Log`가 질문 단위의 `Status:`를 명시하고, `Status: open` 질문이 하나라도 남아 있으면 승인되지 않는다. ```md -- Q: [scope] phase canonical source는 무엇인가? +- Q: phase canonical source는 무엇인가? - A: executable plan은 prose가 아니라 JSON이어야 한다. - Decision: `phases.json`만 canonical plan으로 쓴다. +- Status: resolved +``` + +답을 아직 못 받았거나 추가 확인이 필요한 질문은 아래처럼 열린 상태로 남긴다. + +```md +- Q: 아직 확인이 필요한 요구사항은 무엇인가? +- Status: open ``` 사용자가 현재 spec 초안에 명시적으로 동의하면 approve 한다. 이때 `approve`가 `spec.md`를 `task.json.intake`로 잠근다. @@ -57,7 +65,7 @@ python3 scripts/workflow.py review task-001 --close --user-validation-note "vali ## Repair / Reopen -실패, block, review follow-up, 추가 요구사항이 들어오면 `reopen`으로 다시 `approved` 상태로 되돌린다. `reopen`은 target phase를 `pending`으로 복구해서 바로 `run --start`로 재개할 수 있게 만든다. 추가 요구사항이면 `spec.md`를 다시 잠근 뒤 `approve`를 다시 실행하고, 그 다음 `plan`으로 phase를 갱신한다. +실패, block, review follow-up, 추가 요구사항이 들어오면 `reopen`으로 다시 `approved` 상태로 되돌린다. `reopen`은 target phase와 그 이후 downstream phase를 `pending`으로 복구해서 repair loop가 뒤 단계 실패 상태에 걸리지 않게 만든다. 추가 요구사항이면 `spec.md`를 다시 잠근 뒤 `approve`를 다시 실행하고, 그 다음 `plan`으로 phase를 갱신한다. ```bash python3 scripts/workflow.py reopen task-001 --note "추가 요구사항 반영" diff --git a/docs/runtime.md b/docs/runtime.md index c7fdb88..50b5cff 100644 --- a/docs/runtime.md +++ b/docs/runtime.md @@ -37,12 +37,13 @@ ## Runtime Rules - `approve` 전에는 `plan`, `run`, `verify`, `review`를 허용하지 않는다. -- `approve`는 `spec.md`의 필수 section, placeholder 제거 여부, `Socratic Clarification Log`의 `Q/A/Decision` triplet 형식을 검증한다. -- 새 task contract에서 `approve`는 clarification coverage도 검증한다. `scope`, `goal`, `non_goal`, `constraint`, `acceptance`가 모두 없으면 승인되지 않는다. +- 새 clarification contract에서 열린 질문은 `Status: open`으로 남기고, 모든 질문이 닫힌 뒤에만 승인을 요청한다. +- `approve`는 `spec.md`의 필수 section, placeholder 제거 여부, `Socratic Clarification Log`의 `Q:`/`A:`/`Decision:`/`Status:` 형식을 검증한다. +- `approve`는 clarification이 최소 1개 이상인지, 열린 질문이 없는지, `open` 항목에 `Decision:`이 없는지, `resolved` 항목이 `A:`와 `Decision:`으로 완결되는지 검증한다. - `approve`는 승인된 spec을 `task.json.intake`로 잠그며, 이후 `spec.md`를 수정했으면 `approve`를 다시 실행해 intake를 재잠가야 한다. - `plan`은 명시적 phase 입력 없이는 실행되지 않는다. - `plan`은 `task.json.intake`와 현재 `spec.md`가 일치할 때만 허용하며, 각 phase를 새 세션이 시작할 수 있도록 bootstrap metadata를 함께 적재한다. -- `status`는 `spec.md`를 읽어 `ready_for_approval`, `clarification_count`, `coverage_present`, `coverage_missing`, `unresolved_clarifications`를 JSON으로 노출한다. +- `status`는 `spec.md`를 읽어 `ready_for_approval`, `clarification_count`, `open_clarification_count`, `resolved_clarification_count`, `open_clarifications`, `validation_errors`를 JSON으로 노출한다. - `status`는 active phase의 `required_reads`, `starting_points`, `deliverables`, `completion_signal`도 함께 노출해 다음 세션 시작점을 복원한다. - `run --complete`는 `allowed_write_paths`와 `workflows/tasks//` 밖 변경을 차단한다. - `run --start`는 `task.json.kickoff_required_for_phase`가 active phase와 일치하면, matching `phase_kickoff` evidence 없이는 시작되지 않는다. @@ -75,6 +76,6 @@ - verification 실패도 retry에 포함한다. - 동일한 `error_fingerprint`가 짧은 시간 안에 반복되면 Circuit Breaker가 `blocked`로 전이한다. - `blocked`는 외부 입력, spec 재정의, phase 재계획이 필요한 상태다. -- `reopen`은 `failed`, `blocked`, `review_ready`, `completed` task를 다시 `approved`로 되돌리고 target phase를 `pending`으로 복구한 뒤 repair loop를 재시작한다. +- `reopen`은 `failed`, `blocked`, `review_ready`, `completed` task를 다시 `approved`로 되돌리고 target phase와 그 이후 downstream phase를 `pending`으로 복구한 뒤 repair loop를 재시작한다. - reopened target phase가 2번째 이상 phase면 kickoff proof를 다시 요구한다. -- 추가 요구사항이면 `reopen` 뒤 `spec.md`를 다시 잠그고 `approve`를 재실행한 뒤 `plan`으로 phase를 다시 적재한다. +- 추가 요구사항이면 `reopen` 뒤 `spec.md`에 새 clarification을 열고, 질문을 닫은 다음 `approve`를 재실행한 뒤 `plan`으로 phase를 다시 적재한다. diff --git a/scripts/workflow_runtime/constants.py b/scripts/workflow_runtime/constants.py index c32b47c..15bcc7c 100644 --- a/scripts/workflow_runtime/constants.py +++ b/scripts/workflow_runtime/constants.py @@ -2,8 +2,7 @@ from pathlib import Path -CURRENT_TASK_CONTRACT_VERSION = 2 -CLARIFICATION_REQUIRED_CATEGORIES = ["scope", "goal", "non_goal", "constraint", "acceptance"] +CURRENT_TASK_CONTRACT_VERSION = 3 TASK_STATES = {"draft", "approved", "in_progress", "failed", "blocked", "review_ready", "completed"} PHASE_STATES = {"pending", "in_progress", "completed", "failed", "blocked"} @@ -65,10 +64,10 @@ DOC_REQUIRED_MARKERS = { "docs/README.md": ["AGENTS.md", "docs/runtime.md", "docs/artifact-model.md"], - "docs/artifact-model.md": ["task.json", "intake", "clarifications", "kickoff_required_for_phase", "required_reads"], - "docs/runtime.md": ["소크라테스 질문", "approve", "plan", "kickoff", "review --close"], + "docs/artifact-model.md": ["task.json", "intake", "clarifications", "status", "kickoff_required_for_phase", "required_reads"], + "docs/runtime.md": ["소크라테스 질문", "Status: open", "approve", "plan", "kickoff", "review --close"], "docs/hooks.md": [".githooks/", "TDD Guard", "Dangerous Command Guard", "Circuit Breaker", "workflow.py hook"], - "docs/runbook.md": ["사용자가 현재 spec 초안에 명시적으로 동의하면 approve 한다.", "approve", "plan", "kickoff"], + "docs/runbook.md": ["Status: open", "사용자가 현재 spec 초안에 명시적으로 동의하면 approve 한다.", "approve", "plan", "kickoff"], } STALE_REFERENCE_PATTERNS = [ diff --git a/scripts/workflow_runtime/engine.py b/scripts/workflow_runtime/engine.py index 0de23a5..a54d198 100644 --- a/scripts/workflow_runtime/engine.py +++ b/scripts/workflow_runtime/engine.py @@ -284,18 +284,9 @@ def approve_task(self, task_id: str, note: str, actor: str) -> None: if not note.strip(): raise WorkflowError("approval note is required") - require_coverage = task["contract_version"] >= 2 - if not require_coverage: - coverage_probe = inspect_spec(self.spec_path(task_id), require_coverage=True) - require_coverage = coverage_probe["ready_for_approval"] - - intake = validate_spec_for_approval( - self.spec_path(task_id), - require_coverage=require_coverage, - ) + intake = validate_spec_for_approval(self.spec_path(task_id)) timestamp = now_iso() - if require_coverage: - task["contract_version"] = CURRENT_TASK_CONTRACT_VERSION + task["contract_version"] = CURRENT_TASK_CONTRACT_VERSION task["state"] = "approved" task["approved_at"] = timestamp task["approval"] = {"actor": actor, "note": note.strip(), "timestamp": timestamp} @@ -332,10 +323,10 @@ def plan_task(self, task_id: str, source_payload: Any) -> None: 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), require_coverage=task["contract_version"] >= 2) + 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"], require_coverage=task["contract_version"] >= 2) + intake = ensure_locked_intake(task["intake"], contract_version=task_contract_version(task)) if intake != spec["intake"]: raise WorkflowError("spec.md and task.intake are out of sync; rerun approve before plan") @@ -928,8 +919,13 @@ def reopen_task(self, task_id: str, note: str, phase_id: str | None) -> None: target_phase = candidate break - if target_phase and target_phase["status"] in {"failed", "blocked", "completed"}: - self.update_phase_state(target_phase, "pending") + if target_phase is not None: + target_order = target_phase["order"] + for candidate in phases_payload["phases"]: + if candidate["order"] < target_order: + continue + if candidate["status"] in {"in_progress", "failed", "blocked", "completed"}: + self.update_phase_state(candidate, "pending") task["state"] = "approved" task["blocked_reason"] = None @@ -967,7 +963,7 @@ def status_payload(self, task_id: str) -> dict[str, Any]: payload = { "task": task, "phases": phases, - "spec": inspect_spec(self.spec_path(task_id), require_coverage=task["contract_version"] >= 2), + "spec": inspect_spec(self.spec_path(task_id)), } if active_phase is not None: payload["active_phase_bootstrap"] = self.phase_bootstrap_summary(task_id, active_phase) @@ -986,7 +982,7 @@ def check_all(self) -> list[str]: raise WorkflowError(incomplete.removeprefix(f"{task_id}: ")) task = self.load_task(task_id) phases = self.load_phases(task_id) - spec = inspect_spec(self.spec_path(task_id), require_coverage=task["contract_version"] >= 2) + spec = inspect_spec(self.spec_path(task_id)) active_phase = None if task["active_phase_id"] is not None: active_phase = next((phase for phase in phases["phases"] if phase["id"] == task["active_phase_id"]), None) @@ -1009,7 +1005,7 @@ def check_all(self) -> list[str]: 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"], require_coverage=task["contract_version"] >= 2) != spec["intake"]: + if ensure_locked_intake(task["intake"], contract_version=task_contract_version(task)) != 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"]) diff --git a/scripts/workflow_runtime/models.py b/scripts/workflow_runtime/models.py index 244c698..12f4b38 100644 --- a/scripts/workflow_runtime/models.py +++ b/scripts/workflow_runtime/models.py @@ -9,7 +9,6 @@ from typing import Any from workflow_runtime.constants import ( - CLARIFICATION_REQUIRED_CATEGORIES, CURRENT_TASK_CONTRACT_VERSION, PHASE_STATES, SPEC_REQUIRED_SECTIONS, @@ -210,21 +209,7 @@ def _normalize_items(body: str, label: str) -> list[str]: return items -def _parse_clarification_question(raw: str, *, strict: bool) -> tuple[str | None, str]: - match = re.match(r"^\[([A-Za-z][A-Za-z0-9_-]*)\]\s*(.*)$", raw) - if not match: - if strict: - raise WorkflowError("Socratic Clarification Log must declare a coverage category like [scope] on each question") - return None, raw - - category = match.group(1).strip().lower().replace("-", "_") - question = match.group(2).strip() - if not question: - raise WorkflowError("Socratic Clarification Log contains an empty question") - return category, question - - -def _normalize_clarifications(body: str, *, strict: bool, require_coverage: bool) -> tuple[list[dict[str, Any]], list[str]]: +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] = {} @@ -237,24 +222,17 @@ def push_error(message: str) -> None: for line in _markdown_lines(body): if line.startswith("Q:"): if current: - push_error("must use complete Q/A/Decision triplets") + push_error("must end each clarification with a Status line") current = {} raw_question = line[2:].strip() if not raw_question: push_error("contains an empty question") continue - try: - category, question = _parse_clarification_question(raw_question, strict=require_coverage) - except WorkflowError as exc: - push_error(str(exc).replace("Socratic Clarification Log ", "")) - continue - current = {"question": question} - if category is not None: - current["category"] = category + current = {"question": raw_question} continue if line.startswith("A:"): - if "question" not in current or "answer" in current: + if "question" not in current or "answer" in current or "status" in current: push_error("must place each answer after a question") continue answer = line[2:].strip() @@ -265,7 +243,7 @@ def push_error(message: str) -> None: continue if line.startswith("Decision:"): - if "question" not in current or "answer" not in current or "decision" in current: + if "question" not in current or "answer" not in current or "decision" in current or "status" in current: push_error("must place each decision after a question and answer") continue decision = line[len("Decision:") :].strip() @@ -273,28 +251,55 @@ def push_error(message: str) -> None: push_error("contains an empty decision") continue current["decision"] = decision - current["resolved"] = True - clarifications.append(current) + continue + + if line.startswith("Status:"): + if "question" not in current or "status" in current: + push_error("must place each status after a question") + continue + status = line[len("Status:") :].strip().lower() + if status not in {"open", "resolved"}: + push_error("must declare status as open or resolved") + continue + if status == "open": + if "decision" in current: + push_error("open clarifications must not contain Decision") + continue + else: + if "answer" not in current: + push_error("resolved clarifications require an answer") + continue + if "decision" not in current: + push_error("resolved clarifications require a decision") + continue + clarifications.append( + { + "question": current["question"], + "answer": current.get("answer"), + "decision": current.get("decision"), + "status": status, + } + ) current = {} continue - push_error("must only contain Q:, A:, and Decision: entries") + push_error("must only contain Q:, A:, Decision:, and Status: entries") if current: - push_error("must end with a complete Q/A/Decision triplet") + push_error("must end each clarification with a Status line") return clarifications, errors -def inspect_spec(spec_path: Path, *, require_coverage: bool = False) -> dict[str, Any]: +def inspect_spec(spec_path: Path) -> dict[str, Any]: readiness = { "ready_for_approval": False, "missing_sections": [], "placeholder_sections": [], "validation_errors": [], "clarification_count": 0, - "coverage_present": [], - "coverage_missing": [], - "unresolved_clarifications": [], + "open_clarification_count": 0, + "resolved_clarification_count": 0, + "open_clarifications": [], "intake": empty_task_intake(), } if not spec_path.exists(): @@ -324,7 +329,6 @@ def inspect_spec(spec_path: Path, *, require_coverage: bool = False) -> dict[str clarifications, clarification_errors = _normalize_clarifications( sections["Socratic Clarification Log"], strict=False, - require_coverage=require_coverage, ) intake = { "request_summary": _normalize_summary(sections["Request"], "Request"), @@ -337,43 +341,48 @@ def inspect_spec(spec_path: Path, *, require_coverage: bool = False) -> dict[str } readiness["intake"] = intake readiness["clarification_count"] = len(clarifications) + readiness["open_clarification_count"] = sum(1 for item in clarifications if item["status"] == "open") + readiness["resolved_clarification_count"] = sum(1 for item in clarifications if item["status"] == "resolved") + readiness["open_clarifications"] = [item["question"] for item in clarifications if item["status"] == "open"] readiness["validation_errors"].extend(clarification_errors) except WorkflowError as exc: readiness["validation_errors"].append(str(exc)) - present_categories = sorted({item["category"] for item in readiness["intake"]["clarifications"] if item.get("category")}) - readiness["coverage_present"] = present_categories - if require_coverage: - missing_categories = [item for item in CLARIFICATION_REQUIRED_CATEGORIES if item not in present_categories] - readiness["coverage_missing"] = missing_categories - if missing_categories: - readiness["validation_errors"].append( - f"Socratic Clarification Log missing coverage categories: {', '.join(missing_categories)}" - ) - 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["validation_errors"].append("Socratic Clarification Log requires at least one clarification entry") readiness["ready_for_approval"] = not ( - readiness["missing_sections"] or readiness["placeholder_sections"] or readiness["unresolved_clarifications"] + readiness["missing_sections"] + or readiness["placeholder_sections"] + or readiness["validation_errors"] + or readiness["open_clarifications"] ) return readiness -def validate_spec_for_approval(spec_path: Path, *, require_coverage: bool = False) -> dict[str, Any]: - readiness = inspect_spec(spec_path, require_coverage=require_coverage) +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"])) + if readiness["validation_errors"]: + raise WorkflowError("; ".join(readiness["validation_errors"])) + if readiness["open_clarifications"]: + raise WorkflowError( + "Socratic Clarification Log still has open clarifications: " + + "; ".join(readiness["open_clarifications"]) + ) return readiness["intake"] -def validate_locked_intake(payload: dict[str, Any], label: str = "task.intake") -> dict[str, Any]: +def validate_locked_intake( + payload: dict[str, Any], + label: str = "task.intake", + *, + contract_version: int = CURRENT_TASK_CONTRACT_VERSION, +) -> dict[str, Any]: payload = _require_mapping(payload, label) _require_keys( payload, @@ -401,23 +410,37 @@ def validate_locked_intake(payload: dict[str, Any], label: str = "task.intake") 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}]") + if contract_version >= CURRENT_TASK_CONTRACT_VERSION: + _require_keys(clarification, ["question", "answer", "decision", "status"], f"{label}.clarifications[{index}]") + status = _require_string(clarification["status"], f"{label}.clarifications[{index}].status").lower() + else: + _require_keys(clarification, ["question"], f"{label}.clarifications[{index}]") + if "status" in clarification: + status = _require_string(clarification["status"], f"{label}.clarifications[{index}].status").lower() + elif "resolved" in clarification: + resolved = _require_bool(clarification["resolved"], f"{label}.clarifications[{index}].resolved") + status = "resolved" if resolved else "open" + else: + raise WorkflowError( + f"{label}.clarifications[{index}] legacy clarifications require status or resolved" + ) + if status not in {"open", "resolved"}: + raise WorkflowError(f"{label}.clarifications[{index}].status must be open or resolved") + answer = _require_optional_string(clarification.get("answer"), f"{label}.clarifications[{index}].answer") + decision = _require_optional_string(clarification.get("decision"), f"{label}.clarifications[{index}].decision") + if status == "open" and decision is not None: + raise WorkflowError(f"{label}.clarifications[{index}] open clarifications must not contain decision") + if status == "resolved": + if answer is None: + raise WorkflowError(f"{label}.clarifications[{index}] resolved clarifications require answer") + if decision is None: + raise WorkflowError(f"{label}.clarifications[{index}] resolved clarifications require decision") normalized_clarifications.append( { - **( - { - "category": _require_string( - clarification["category"], - f"{label}.clarifications[{index}].category", - ) - } - if "category" in clarification - else {} - ), "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"), + "answer": answer, + "decision": decision, + "status": status, } ) @@ -432,8 +455,13 @@ def validate_locked_intake(payload: dict[str, Any], label: str = "task.intake") } -def ensure_locked_intake(payload: dict[str, Any], label: str = "task.intake", *, require_coverage: bool = False) -> dict[str, Any]: - intake = validate_locked_intake(payload, label) +def ensure_locked_intake( + payload: dict[str, Any], + label: str = "task.intake", + *, + contract_version: int = CURRENT_TASK_CONTRACT_VERSION, +) -> dict[str, Any]: + intake = validate_locked_intake(payload, label, contract_version=contract_version) 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") @@ -442,20 +470,9 @@ def ensure_locked_intake(payload: dict[str, Any], label: str = "task.intake", *, 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"]] + unresolved = [item["question"] for item in intake["clarifications"] if item["status"] != "resolved"] if unresolved: raise WorkflowError(f"{label}.clarifications must all be resolved") - if require_coverage: - uncategorized = [item["question"] for item in intake["clarifications"] if not item.get("category")] - if uncategorized: - raise WorkflowError(f"{label}.clarifications require category for every question") - missing_categories = [ - item - for item in CLARIFICATION_REQUIRED_CATEGORIES - if item not in {entry["category"] for entry in intake["clarifications"] if entry.get("category")} - ] - if missing_categories: - raise WorkflowError(f"{label}.clarifications missing coverage categories: {', '.join(missing_categories)}") return intake @@ -497,7 +514,7 @@ def validate_task(payload: dict[str, Any]) -> None: _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"]) + intake = validate_locked_intake(payload["intake"], contract_version=contract_version) approval = payload["approval"] if approval is not None: @@ -522,7 +539,7 @@ def validate_task(payload: dict[str, Any]) -> None: 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, require_coverage=contract_version >= CURRENT_TASK_CONTRACT_VERSION) + ensure_locked_intake(intake, contract_version=contract_version) def validate_phases(payload: dict[str, Any]) -> None: diff --git a/scripts/workflow_runtime/templates.py b/scripts/workflow_runtime/templates.py index 1592c54..748149c 100644 --- a/scripts/workflow_runtime/templates.py +++ b/scripts/workflow_runtime/templates.py @@ -23,10 +23,9 @@ def default_spec(task_id: str, title: str, primary_repo: str) -> str: "## Acceptance\n\n" "- TODO: 완료를 판단할 수 있는 관찰 가능한 기준을 적는다.\n\n" "## Socratic Clarification Log\n\n" - "- TODO: `[scope]`, `[goal]`, `[non_goal]`, `[constraint]`, `[acceptance]` coverage를 모두 채운 Q/A/Decision triplet만 남긴다.\n" - "- Q: [scope] ...\n" - "- A: ...\n" - "- Decision: ...\n" + "- TODO: spec 작성 중 생기는 질문을 `Q:`, 선택적 `A:`, `Status:` 형식으로 기록한다. `Status: open` 질문이 남아 있으면 approve 할 수 없다.\n" + "- Q: 아직 확인이 필요한 요구사항은 무엇인가?\n" + "- Status: open\n" ) diff --git a/tests/test_workflow_cli.py b/tests/test_workflow_cli.py index a52e2b8..134ef13 100644 --- a/tests/test_workflow_cli.py +++ b/tests/test_workflow_cli.py @@ -145,21 +145,14 @@ def finalize_spec(self, root: Path, task_id: str, title: str = "Harness V2") -> "## Acceptance\n\n" "- approve/plan/run/verify/review/reopen 흐름이 CLI와 테스트로 검증된다.\n\n" "## Socratic Clarification Log\n\n" - "- Q: [scope] 문서 구조는 어떻게 정리할까?\n" + "- Q: 문서 구조는 어떻게 정리할까?\n" "- A: `tasks/`와 `system/`으로 역할을 나눈다.\n" "- Decision: 초안 작성은 `spec.md`, 승인 고정은 `task.json.intake`가 소유한다.\n" - "- Q: [goal] 이 workflow가 반드시 보장해야 하는 핵심 목표는 무엇일까?\n" + "- Status: resolved\n" + "- Q: 이 workflow가 반드시 보장해야 하는 핵심 목표는 무엇일까?\n" "- A: spec 승인, phase 실행, verification, review의 제어 지점을 JSON state로 잠그는 것이다.\n" "- Decision: workflow는 승인과 실행 상태를 artifact 기반으로 고정해야 한다.\n" - "- Q: [non_goal] 이번 harness가 다시 설계하지 않을 범위는 무엇일까?\n" - "- A: 앱 저장소 자체의 제품 동작이나 runtime contract는 여기서 다시 설계하지 않는다.\n" - "- Decision: control plane만 강화하고 앱 동작 재설계는 제외한다.\n" - "- Q: [constraint] 구현 시 반드시 지켜야 하는 운영 제약은 무엇일까?\n" - "- A: 기존 저장소 구조를 유지하고 workflow.py CLI를 단일 진입점으로 써야 한다.\n" - "- Decision: 공식 상태 전이와 hook enforcement는 workflow CLI를 통해서만 수행한다.\n" - "- Q: [acceptance] 완료를 무엇으로 판단할까?\n" - "- A: approve/plan/run/verify/review/reopen 흐름이 CLI와 테스트로 검증되어야 한다.\n" - "- Decision: 완료 판단 기준은 CLI 흐름이 테스트로 고정되는 것이다.\n" + "- Status: resolved\n" ), encoding="utf-8", ) @@ -214,7 +207,7 @@ def write_spec_with_clarifications( self, root: Path, task_id: str, - clarifications: list[tuple[str, str, str, str]], + clarifications: list[tuple[str, str | None, str | None, str]], *, title: str = "Harness V2", ) -> None: @@ -252,14 +245,13 @@ def write_spec_with_clarifications( "## Socratic Clarification Log", "", ] - for category, question, answer, decision in clarifications: - lines.extend( - [ - f"- Q: [{category}] {question}", - f"- A: {answer}", - f"- Decision: {decision}", - ] - ) + for question, answer, decision, status in clarifications: + lines.append(f"- Q: {question}") + if answer is not None: + lines.append(f"- A: {answer}") + if decision is not None: + lines.append(f"- Decision: {decision}") + lines.append(f"- Status: {status}") spec_path = root / "workflows" / "tasks" / task_id / "spec.md" spec_path.write_text("\n".join(lines) + "\n", encoding="utf-8") @@ -281,6 +273,7 @@ def write_repo_docs(self, root: Path) -> None: "- task.json\n" "- intake\n" "- clarifications\n" + "- status\n" "- kickoff_required_for_phase\n" "- required_reads\n" ), @@ -290,6 +283,7 @@ def write_repo_docs(self, root: Path) -> None: ( "# Runtime\n\n" "1. 소크라테스 질문\n" + "2. Status: open\n" "2. approve\n" "3. plan\n" "4. kickoff\n" @@ -311,6 +305,7 @@ def write_repo_docs(self, root: Path) -> None: (docs_dir / "runbook.md").write_text( ( "# Runbook\n\n" + "- Status: open\n" "- 사용자가 현재 spec 초안에 명시적으로 동의하면 approve 한다.\n" "- approve\n" "- plan\n" @@ -363,7 +358,7 @@ def test_init_creates_system_layout_and_doctor_passes(self) -> None: 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.assertEqual(task["intake"]["clarifications"][0]["status"], "resolved") self.assertTrue((root / "workflows" / "system" / "hooks.json").exists()) self.assertFalse((root / "workflows" / "config").exists()) self.assertFalse((root / "workflows" / "schemas").exists()) @@ -388,14 +383,15 @@ def test_approve_requires_structured_socratic_log_and_locks_intake(self) -> None self.finalize_spec(root, "task-002a") spec_path.write_text( spec_path.read_text(encoding="utf-8").replace( - "- Decision: 완료 판단 기준은 CLI 흐름이 테스트로 고정되는 것이다.\n", + "- Status: resolved\n", "", + 1, ), 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.assertIn("Status", result.stderr) self.finalize_spec(root, "task-002a") self.run_cli(root, "approve", "task-002a", "--note", "approved by user") @@ -403,9 +399,9 @@ def test_approve_requires_structured_socratic_log_and_locks_intake(self) -> None 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"]), 5) + self.assertEqual(len(task["intake"]["clarifications"]), 2) self.assertEqual(task["intake"]["clarifications"][0]["decision"], "초안 작성은 `spec.md`, 승인 고정은 `task.json.intake`가 소유한다.") - self.assertEqual(task["intake"]["clarifications"][0]["category"], "scope") + self.assertEqual(task["intake"]["clarifications"][0]["status"], "resolved") def test_plan_requires_locked_intake_even_for_approved_task(self) -> None: with tempfile.TemporaryDirectory() as tmp: @@ -416,34 +412,35 @@ def test_plan_requires_locked_intake_even_for_approved_task(self) -> None: task_path = root / "workflows" / "tasks" / "task-002b" / "task.json" task = self.read_json(task_path) - task["intake"]["clarifications"] = [] + task["intake"]["clarifications"][0]["status"] = "open" + task["intake"]["clarifications"][0]["decision"] = None 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) + self.assertIn("must all be resolved", 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: 완료 판단 기준은 CLI 흐름이 테스트로 고정되는 것이다.\n", - "", - ), - encoding="utf-8", + self.write_spec_with_clarifications( + root, + "task-002c", + [ + ("문서 구조는 어떻게 정리할까?", "`tasks/`와 `system/`으로 역할을 나눈다.", "초안 작성은 `spec.md`, 승인 고정은 `task.json.intake`가 소유한다.", "resolved"), + ("사용자 확인이 더 필요한 부분은 무엇일까?", None, None, "open"), + ], ) 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"], 4) - self.assertTrue(payload["spec"]["unresolved_clarifications"]) - self.assertIn("acceptance", payload["spec"]["coverage_missing"]) + self.assertEqual(payload["spec"]["clarification_count"], 2) + self.assertEqual(payload["spec"]["open_clarification_count"], 1) + self.assertEqual(payload["spec"]["resolved_clarification_count"], 1) + self.assertEqual(payload["spec"]["open_clarifications"], ["사용자 확인이 더 필요한 부분은 무엇일까?"]) def test_plan_keeps_legacy_empty_inputs_compatible_without_required_reads(self) -> None: with tempfile.TemporaryDirectory() as tmp: @@ -456,12 +453,6 @@ def test_plan_keeps_legacy_empty_inputs_compatible_without_required_reads(self) task_path.write_text(json.dumps(task, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") self.finalize_spec(root, "task-002cc") - spec_path = root / "workflows" / "tasks" / "task-002cc" / "spec.md" - legacy_text = spec_path.read_text(encoding="utf-8") - for category in ("scope", "goal", "non_goal", "constraint", "acceptance"): - legacy_text = legacy_text.replace(f"Q: [{category}] ", "Q: ") - spec_path.write_text(legacy_text, encoding="utf-8") - self.run_cli(root, "approve", "task-002cc", "--note", "approved by user") phase_file = self.write_phase_input( root, @@ -480,7 +471,54 @@ def test_plan_keeps_legacy_empty_inputs_compatible_without_required_reads(self) status = json.loads(self.run_cli(root, "status", "task-002cc").stdout) self.assertEqual(status["active_phase_bootstrap"]["required_reads"], []) - def test_approve_requires_coverage_categories_before_locking_intake(self) -> None: + def test_plan_accepts_legacy_locked_intake_schema_for_pre_v3_task(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + self.run_cli(root, "new", "task-002cd", "--title", "Harness V2", "--primary-repo", "git-ranker-workflow") + self.finalize_spec(root, "task-002cd") + self.run_cli(root, "approve", "task-002cd", "--note", "approved by user") + + task_path = root / "workflows" / "tasks" / "task-002cd" / "task.json" + task = self.read_json(task_path) + task["contract_version"] = 2 + for clarification in task["intake"]["clarifications"]: + clarification["resolved"] = True + clarification["category"] = "goal" + clarification.pop("status", None) + task_path.write_text(json.dumps(task, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") + + status = json.loads(self.run_cli(root, "status", "task-002cd").stdout) + self.assertEqual(status["task"]["contract_version"], 2) + self.assertEqual(status["task"]["intake"]["clarifications"][0]["resolved"], True) + + phase_file = self.write_phase_input(root, ["python3 -c \"print('ok')\""]) + self.run_cli(root, "plan", "task-002cd", "--from", str(phase_file)) + + def test_doctor_accepts_legacy_locked_intake_schema_for_pre_v3_task(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-002ce", + test_policy_mode="evidence_only", + test_policy_evidence=["legacy intake compatibility is exercised by CLI tests"], + ) + + task_path = root / "workflows" / "tasks" / "task-002ce" / "task.json" + task = self.read_json(task_path) + task["contract_version"] = 2 + for clarification in task["intake"]["clarifications"]: + clarification["resolved"] = True + clarification["category"] = "goal" + clarification.pop("status", None) + task_path.write_text(json.dumps(task, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") + + self.run_cli(root, "doctor") + self.run_cli(root, "status", "--check", "--all") + + def test_approve_rejects_open_clarifications_before_locking_intake(self) -> None: with tempfile.TemporaryDirectory() as tmp: root = Path(tmp) self.run_cli(root, "new", "task-002d", "--title", "Harness V2", "--primary-repo", "git-ranker-workflow") @@ -488,25 +526,21 @@ def test_approve_requires_coverage_categories_before_locking_intake(self) -> Non root, "task-002d", [ - ("scope", "문서 구조는 어떻게 정리할까?", "`tasks/`와 `system/`으로 역할을 나눈다.", "초안 작성은 `spec.md`, 승인 고정은 `task.json.intake`가 소유한다."), - ("goal", "이 workflow의 목표는 무엇일까?", "승인과 실행 상태를 artifact로 잠근다.", "workflow는 승인과 실행 상태를 artifact 기반으로 고정해야 한다."), + ("문서 구조는 어떻게 정리할까?", "`tasks/`와 `system/`으로 역할을 나눈다.", "초안 작성은 `spec.md`, 승인 고정은 `task.json.intake`가 소유한다.", "resolved"), + ("추가로 확인이 필요한 구현 포인트는 무엇일까?", None, None, "open"), ], ) result = self.run_cli(root, "approve", "task-002d", "--note", "approved by user", expected=1) - self.assertIn("missing coverage categories", result.stderr) - self.assertIn("non_goal", result.stderr) - self.assertIn("constraint", result.stderr) - self.assertIn("acceptance", result.stderr) + self.assertIn("open clarifications", result.stderr) self.finalize_spec(root, "task-002d") self.run_cli(root, "approve", "task-002d", "--note", "approved by user") task = self.read_json(root / "workflows" / "tasks" / "task-002d" / "task.json") - categories = {item["category"] for item in task["intake"]["clarifications"]} - self.assertEqual(categories, {"scope", "goal", "non_goal", "constraint", "acceptance"}) + self.assertTrue(all(item["status"] == "resolved" for item in task["intake"]["clarifications"])) - def test_status_reports_missing_coverage_categories_before_approval(self) -> None: + def test_status_reports_open_clarifications_before_approval(self) -> None: with tempfile.TemporaryDirectory() as tmp: root = Path(tmp) self.run_cli(root, "new", "task-002e", "--title", "Harness V2", "--primary-repo", "git-ranker-workflow") @@ -514,34 +548,35 @@ def test_status_reports_missing_coverage_categories_before_approval(self) -> Non root, "task-002e", [ - ("scope", "문서 구조는 어떻게 정리할까?", "`tasks/`와 `system/`으로 역할을 나눈다.", "초안 작성은 `spec.md`, 승인 고정은 `task.json.intake`가 소유한다."), - ("goal", "이 workflow의 목표는 무엇일까?", "승인과 실행 상태를 artifact로 잠근다.", "workflow는 승인과 실행 상태를 artifact 기반으로 고정해야 한다."), + ("문서 구조는 어떻게 정리할까?", "`tasks/`와 `system/`으로 역할을 나눈다.", "초안 작성은 `spec.md`, 승인 고정은 `task.json.intake`가 소유한다.", "resolved"), + ("이 구현에서 아직 애매한 부분은 무엇일까?", "상태 전이 규칙은 정했지만 출력 형식이 더 필요하다.", None, "open"), ], ) result = self.run_cli(root, "status", "task-002e") payload = json.loads(result.stdout) self.assertFalse(payload["spec"]["ready_for_approval"]) - self.assertEqual(payload["spec"]["coverage_present"], ["goal", "scope"]) - self.assertEqual(payload["spec"]["coverage_missing"], ["non_goal", "constraint", "acceptance"]) - self.assertTrue(any("missing coverage categories" in item for item in payload["spec"]["unresolved_clarifications"])) + self.assertEqual(payload["spec"]["clarification_count"], 2) + self.assertEqual(payload["spec"]["open_clarification_count"], 1) + self.assertEqual(payload["spec"]["resolved_clarification_count"], 1) + self.assertEqual(payload["spec"]["open_clarifications"], ["이 구현에서 아직 애매한 부분은 무엇일까?"]) - def test_legacy_task_upgrades_to_v2_when_reapproved_with_categorized_spec(self) -> None: + def test_reapprove_upgrades_task_to_v3(self) -> None: with tempfile.TemporaryDirectory() as tmp: root = Path(tmp) self.run_cli(root, "new", "task-002f", "--title", "Harness V2", "--primary-repo", "git-ranker-workflow") task_path = root / "workflows" / "tasks" / "task-002f" / "task.json" task = self.read_json(task_path) - task.pop("contract_version", None) + task["contract_version"] = 2 task_path.write_text(json.dumps(task, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") self.finalize_spec(root, "task-002f") self.run_cli(root, "approve", "task-002f", "--note", "approved by user") task = self.read_json(task_path) - self.assertEqual(task["contract_version"], 2) - self.assertEqual({item["category"] for item in task["intake"]["clarifications"]}, {"scope", "goal", "non_goal", "constraint", "acceptance"}) + self.assertEqual(task["contract_version"], 3) + self.assertTrue(all(item["status"] == "resolved" for item in task["intake"]["clarifications"])) def test_tdd_guard_blocks_completion_without_tests(self) -> None: with tempfile.TemporaryDirectory() as tmp: @@ -1198,30 +1233,26 @@ def test_kickoff_is_required_before_starting_next_phase(self) -> None: self.assertIsNone(status["task"]["kickoff_required_for_phase"]) self.assertIsNotNone(status["task"]["last_kickoff_run_id"]) - def test_legacy_multi_phase_task_still_requires_kickoff_for_phase_two(self) -> None: + def test_pre_v3_multi_phase_task_reapproves_and_requires_kickoff_for_phase_two(self) -> None: with tempfile.TemporaryDirectory() as tmp: root = Path(tmp) self.run_cli(root, "new", "task-007bb", "--title", "Harness V2", "--primary-repo", "git-ranker-workflow") task_path = root / "workflows" / "tasks" / "task-007bb" / "task.json" task = self.read_json(task_path) - task.pop("contract_version", None) + task["contract_version"] = 2 task_path.write_text(json.dumps(task, indent=2, ensure_ascii=False) + "\n", encoding="utf-8") self.finalize_spec(root, "task-007bb") - spec_path = root / "workflows" / "tasks" / "task-007bb" / "spec.md" - legacy_text = spec_path.read_text(encoding="utf-8") - for category in ("scope", "goal", "non_goal", "constraint", "acceptance"): - legacy_text = legacy_text.replace(f"Q: [{category}] ", "Q: ") - spec_path.write_text(legacy_text, encoding="utf-8") - self.run_cli(root, "approve", "task-007bb", "--note", "approved by user") + task = self.read_json(task_path) + self.assertEqual(task["contract_version"], 3) phase_file = self.write_phase_input( root, ["python3 -c \"print('ok')\""], phase_count=2, test_policy_mode="evidence_only", - test_policy_evidence=["legacy tasks should still require kickoff for later phases"], + test_policy_evidence=["reapproved pre-v3 tasks should still require kickoff for later phases"], ) self.run_cli(root, "plan", "task-007bb", "--from", str(phase_file)) self.run_cli(root, "run", "task-007bb", "--start") @@ -1308,6 +1339,58 @@ def test_reopen_resets_kickoff_requirement_for_target_phase(self) -> None: self.assertEqual(status["task"]["kickoff_required_for_phase"], "phase-2") self.assertIsNone(status["task"]["last_kickoff_run_id"]) + def test_reopening_earlier_phase_resets_downstream_phases_to_pending(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + root = Path(tmp) + self.run_cli(root, "new", "task-007e", "--title", "Harness V2", "--primary-repo", "git-ranker-workflow") + self.finalize_spec(root, "task-007e") + self.run_cli(root, "approve", "task-007e", "--note", "approved by user") + phase_file = self.write_phase_input( + root, + ["python3 -c \"print('ok')\""], + phase_count=2, + test_policy_mode="evidence_only", + test_policy_evidence=["reopening an upstream phase should invalidate downstream phase state"], + ) + self.run_cli(root, "plan", "task-007e", "--from", str(phase_file)) + self.run_cli(root, "run", "task-007e", "--start") + self.run_cli(root, "run", "task-007e", "--complete", "--changed-path", "scripts/workflow.py") + self.run_cli(root, "verify", "task-007e") + self.run_cli(root, "kickoff", "task-007e") + self.run_cli(root, "run", "task-007e", "--start") + self.run_cli( + root, + "run", + "task-007e", + "--fail", + "--phase-id", + "phase-2", + "--error-fingerprint", + "phase-2-boom", + "--note", + "boom", + expected=1, + ) + + self.run_cli(root, "reopen", "task-007e", "--note", "repair upstream contract", "--phase-id", "phase-1") + + status = json.loads(self.run_cli(root, "status", "task-007e").stdout) + phases = self.read_json(root / "workflows" / "tasks" / "task-007e" / "phases.json") + self.assertEqual(status["task"]["state"], "approved") + self.assertEqual(status["task"]["active_phase_id"], "phase-1") + self.assertIsNone(status["task"]["kickoff_required_for_phase"]) + self.assertEqual(phases["phases"][0]["status"], "pending") + self.assertEqual(phases["phases"][1]["status"], "pending") + + self.run_cli(root, "run", "task-007e", "--start") + self.run_cli(root, "run", "task-007e", "--complete", "--changed-path", "scripts/workflow.py") + self.run_cli(root, "verify", "task-007e") + + status = json.loads(self.run_cli(root, "status", "task-007e").stdout) + self.assertEqual(status["task"]["state"], "approved") + self.assertEqual(status["task"]["active_phase_id"], "phase-2") + self.assertEqual(status["task"]["kickoff_required_for_phase"], "phase-2") + def test_reopen_resets_completed_task_for_follow_up(self) -> None: with tempfile.TemporaryDirectory() as tmp: root = Path(tmp) diff --git a/workflows/tasks/task-gitranker-core-loop-unit-tests/phases.json b/workflows/tasks/task-gitranker-core-loop-unit-tests/phases.json deleted file mode 100644 index abf3307..0000000 --- a/workflows/tasks/task-gitranker-core-loop-unit-tests/phases.json +++ /dev/null @@ -1,126 +0,0 @@ -{ - "task_id": "task-gitranker-core-loop-unit-tests", - "generated_at": "2026-04-16T05:48:14+00:00", - "phases": [ - { - "id": "phase-1", - "title": "existing-tests-and-domain-core", - "goal": "Harden existing unit tests and add detailed value/domain/orchestrator coverage for the core feedback loop without expanding beyond narrow unit tests.", - "inputs": [ - "workflows/tasks/task-gitranker-core-loop-unit-tests/spec.md", - "git-ranker/AGENTS.md", - "git-ranker/src/main/java/com/gitranker/api/domain/", - "git-ranker/src/test/java/" - ], - "allowed_write_paths": [ - "git-ranker/src/test/java/" - ], - "acceptance": { - "commands": [ - "cd git-ranker && ./gradlew test --tests 'com.gitranker.api.domain.auth.service.*' --tests 'com.gitranker.api.domain.badge.*' --tests 'com.gitranker.api.domain.log.*' --tests 'com.gitranker.api.domain.ranking.*' --tests 'com.gitranker.api.domain.user.*' --tests 'com.gitranker.api.global.util.*'" - ] - }, - "test_policy": { - "mode": "require_tests", - "evidence": [] - }, - "order": 1, - "status": "completed", - "retry_count": 1, - "required_reads": [ - "workflows/tasks/task-gitranker-core-loop-unit-tests/spec.md", - "git-ranker/AGENTS.md", - "git-ranker/src/main/java/com/gitranker/api/domain/", - "git-ranker/src/test/java/" - ], - "starting_points": [ - "Read the locked spec for task-gitranker-core-loop-unit-tests.", - "Inspect the active phase goal: Harden existing unit tests and add detailed value/domain/orchestrator coverage for the core feedback loop without expanding beyond narrow unit tests.", - "Confirm allowed write paths and acceptance commands before editing." - ], - "deliverables": [ - "Harden existing unit tests and add detailed value/domain/orchestrator coverage for the core feedback loop without expanding beyond narrow unit tests." - ], - "completion_signal": "phase-1 acceptance commands pass" - }, - { - "id": "phase-2", - "title": "batch-feedback-loop-tests", - "goal": "Add fine-grained batch feedback and verification loop unit tests covering boundary cases, skip/retry/error translation, and side effects.", - "inputs": [ - "workflows/tasks/task-gitranker-core-loop-unit-tests/spec.md", - "git-ranker/src/main/java/com/gitranker/api/batch/", - "git-ranker/src/test/java/" - ], - "allowed_write_paths": [ - "git-ranker/src/test/java/" - ], - "acceptance": { - "commands": [ - "cd git-ranker && ./gradlew test --tests 'com.gitranker.api.batch.*'" - ] - }, - "test_policy": { - "mode": "require_tests", - "evidence": [] - }, - "order": 2, - "status": "completed", - "retry_count": 1, - "required_reads": [ - "workflows/tasks/task-gitranker-core-loop-unit-tests/spec.md", - "git-ranker/src/main/java/com/gitranker/api/batch/", - "git-ranker/src/test/java/" - ], - "starting_points": [ - "Read the locked spec for task-gitranker-core-loop-unit-tests.", - "Inspect the active phase goal: Add fine-grained batch feedback and verification loop unit tests covering boundary cases, skip/retry/error translation, and side effects.", - "Confirm allowed write paths and acceptance commands before editing." - ], - "deliverables": [ - "Add fine-grained batch feedback and verification loop unit tests covering boundary cases, skip/retry/error translation, and side effects." - ], - "completion_signal": "phase-2 acceptance commands pass" - }, - { - "id": "phase-3", - "title": "github-infra-and-full-suite", - "goal": "Add exhaustive GitHub collection and error translation unit tests, then confirm the full git-ranker unit-test baseline stays green.", - "inputs": [ - "workflows/tasks/task-gitranker-core-loop-unit-tests/spec.md", - "git-ranker/src/main/java/com/gitranker/api/infrastructure/github/", - "git-ranker/src/test/java/" - ], - "allowed_write_paths": [ - "git-ranker/src/test/java/" - ], - "acceptance": { - "commands": [ - "cd git-ranker && ./gradlew test --tests 'com.gitranker.api.infrastructure.github.*'", - "cd git-ranker && ./gradlew test" - ] - }, - "test_policy": { - "mode": "require_tests", - "evidence": [] - }, - "order": 3, - "status": "completed", - "retry_count": 2, - "required_reads": [ - "workflows/tasks/task-gitranker-core-loop-unit-tests/spec.md", - "git-ranker/src/main/java/com/gitranker/api/infrastructure/github/", - "git-ranker/src/test/java/" - ], - "starting_points": [ - "Read the locked spec for task-gitranker-core-loop-unit-tests.", - "Inspect the active phase goal: Add exhaustive GitHub collection and error translation unit tests, then confirm the full git-ranker unit-test baseline stays green.", - "Confirm allowed write paths and acceptance commands before editing." - ], - "deliverables": [ - "Add exhaustive GitHub collection and error translation unit tests, then confirm the full git-ranker unit-test baseline stays green." - ], - "completion_signal": "phase-3 acceptance commands pass" - } - ] -} diff --git a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T055405-c7b7c277.json b/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T055405-c7b7c277.json deleted file mode 100644 index 97ea156..0000000 --- a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T055405-c7b7c277.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "id": "20260416T055405-c7b7c277", - "task_id": "task-gitranker-core-loop-unit-tests", - "phase_id": "phase-1", - "event": "phase_completion", - "commands": [ - { - "command": "phase completion", - "status": "passed", - "output": "git-ranker/src/test/java/com/gitranker/api/domain/auth/service/AuthServiceTest.java, git-ranker/src/test/java/com/gitranker/api/domain/badge/BadgeFormatterTest.java, git-ranker/src/test/java/com/gitranker/api/domain/badge/BadgeServiceTest.java, git-ranker/src/test/java/com/gitranker/api/domain/badge/SvgBadgeRendererTest.java, git-ranker/src/test/java/com/gitranker/api/domain/log/ActivityLogOrchestratorTest.java, git-ranker/src/test/java/com/gitranker/api/domain/log/ActivityLogServiceTest.java, git-ranker/src/test/java/com/gitranker/api/domain/log/ActivityLogTest.java, git-ranker/src/test/java/com/gitranker/api/domain/ranking/RankingRecalculationServiceTest.java, git-ranker/src/test/java/com/gitranker/api/domain/ranking/RankingServiceTest.java, git-ranker/src/test/java/com/gitranker/api/domain/user/UserTest.java, git-ranker/src/test/java/com/gitranker/api/domain/user/service/UserDeletionServiceTest.java, git-ranker/src/test/java/com/gitranker/api/domain/user/service/UserPersistenceServiceTest.java, git-ranker/src/test/java/com/gitranker/api/domain/user/service/UserQueryServiceTest.java, git-ranker/src/test/java/com/gitranker/api/domain/user/service/UserRefreshServiceTest.java, git-ranker/src/test/java/com/gitranker/api/domain/user/service/UserRegistrationServiceTest.java, git-ranker/src/test/java/com/gitranker/api/domain/user/vo/ActivityStatisticsTest.java, git-ranker/src/test/java/com/gitranker/api/domain/user/vo/RankInfoTest.java, git-ranker/src/test/java/com/gitranker/api/domain/user/vo/ScoreTest.java, git-ranker/src/test/java/com/gitranker/api/global/util/TimeUtilsTest.java" - } - ], - "result": "passed", - "evidence": [ - "Added domain/value/orchestrator tests and hardened existing unit tests for error, fallback, boundary, and side-effect coverage." - ], - "error_fingerprint": null, - "next_action": "verify", - "timestamp": "2026-04-16T05:54:05+00:00" -} diff --git a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T055409-7d0a28cb.json b/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T055409-7d0a28cb.json deleted file mode 100644 index 4f6262c..0000000 --- a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T055409-7d0a28cb.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "id": "20260416T055409-7d0a28cb", - "task_id": "task-gitranker-core-loop-unit-tests", - "phase_id": "phase-1", - "event": "verification", - "commands": [ - { - "command": "cd git-ranker && ./gradlew test --tests 'com.gitranker.api.domain.auth.service.*' --tests 'com.gitranker.api.domain.badge.*' --tests 'com.gitranker.api.domain.log.*' --tests 'com.gitranker.api.domain.ranking.*' --tests 'com.gitranker.api.domain.user.*' --tests 'com.gitranker.api.global.util.*'", - "status": "failed", - "output": "Exception in thread \"main\" java.io.FileNotFoundException: /Users/hyoseok/.gradle/wrapper/dists/gradle-9.2.1-bin/2t0n5ozlw9xmuyvbp7dnzaxug/gradle-9.2.1-bin.zip.lck (Operation not permitted)\n\tat java.base/java.io.RandomAccessFile.open0(Native Method)\n\tat java.base/java.io.RandomAccessFile.open(RandomAccessFile.java:338)\n\tat java.base/java.io.RandomAccessFile.(RandomAccessFile.java:257)\n\tat java.base/java.io.RandomAccessFile.(RandomAccessFile.java:211)\n\tat org.gradle.wrapper.GradleWrapperMain.main(SourceFile:67)" - } - ], - "result": "failed", - "evidence": [], - "error_fingerprint": "verification:phase-1:cd git-ranker && ./gradlew test --tests 'com.gitranker.api.domain.auth.service.*' --tests 'com.gitranker.api.domain.badge.*' --tests 'com.gitranker.api.domain.log.*' --tests 'com.gitranker.api.domain.ranking.*' --tests 'com.gitranker.api.domain.user.*' --tests 'com.gitranker.api.global.util.*'", - "next_action": "repair", - "timestamp": "2026-04-16T05:54:09+00:00" -} diff --git a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T055443-161dc9a6.json b/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T055443-161dc9a6.json deleted file mode 100644 index f7016d5..0000000 --- a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T055443-161dc9a6.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "id": "20260416T055443-161dc9a6", - "task_id": "task-gitranker-core-loop-unit-tests", - "phase_id": "phase-1", - "event": "reopened", - "commands": [ - { - "command": "reopen task", - "status": "passed", - "output": "Verification failed due to sandbox-denied Gradle lock file access; reopen phase to rerun verification with escalated permissions." - } - ], - "result": "passed", - "evidence": [ - "Verification failed due to sandbox-denied Gradle lock file access; reopen phase to rerun verification with escalated permissions." - ], - "error_fingerprint": null, - "next_action": "plan or run", - "timestamp": "2026-04-16T05:54:43+00:00" -} diff --git a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T055503-5f5a11e6.json b/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T055503-5f5a11e6.json deleted file mode 100644 index fdd51ad..0000000 --- a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T055503-5f5a11e6.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "id": "20260416T055503-5f5a11e6", - "task_id": "task-gitranker-core-loop-unit-tests", - "phase_id": "phase-1", - "event": "phase_completion", - "commands": [ - { - "command": "phase completion", - "status": "passed", - "output": "git-ranker/src/test/java/com/gitranker/api/domain/auth/service/AuthServiceTest.java, git-ranker/src/test/java/com/gitranker/api/domain/badge/BadgeFormatterTest.java, git-ranker/src/test/java/com/gitranker/api/domain/badge/BadgeServiceTest.java, git-ranker/src/test/java/com/gitranker/api/domain/badge/SvgBadgeRendererTest.java, git-ranker/src/test/java/com/gitranker/api/domain/log/ActivityLogOrchestratorTest.java, git-ranker/src/test/java/com/gitranker/api/domain/log/ActivityLogServiceTest.java, git-ranker/src/test/java/com/gitranker/api/domain/log/ActivityLogTest.java, git-ranker/src/test/java/com/gitranker/api/domain/ranking/RankingRecalculationServiceTest.java, git-ranker/src/test/java/com/gitranker/api/domain/ranking/RankingServiceTest.java, git-ranker/src/test/java/com/gitranker/api/domain/user/UserTest.java, git-ranker/src/test/java/com/gitranker/api/domain/user/service/UserDeletionServiceTest.java, git-ranker/src/test/java/com/gitranker/api/domain/user/service/UserPersistenceServiceTest.java, git-ranker/src/test/java/com/gitranker/api/domain/user/service/UserQueryServiceTest.java, git-ranker/src/test/java/com/gitranker/api/domain/user/service/UserRefreshServiceTest.java, git-ranker/src/test/java/com/gitranker/api/domain/user/service/UserRegistrationServiceTest.java, git-ranker/src/test/java/com/gitranker/api/domain/user/vo/ActivityStatisticsTest.java, git-ranker/src/test/java/com/gitranker/api/domain/user/vo/RankInfoTest.java, git-ranker/src/test/java/com/gitranker/api/domain/user/vo/ScoreTest.java, git-ranker/src/test/java/com/gitranker/api/global/util/TimeUtilsTest.java" - } - ], - "result": "passed", - "evidence": [ - "Reclosed phase-1 after sandbox-related verification repair; code/test changes unchanged." - ], - "error_fingerprint": null, - "next_action": "verify", - "timestamp": "2026-04-16T05:55:03+00:00" -} diff --git a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T055523-6d34b7f8.json b/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T055523-6d34b7f8.json deleted file mode 100644 index 177390a..0000000 --- a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T055523-6d34b7f8.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "id": "20260416T055523-6d34b7f8", - "task_id": "task-gitranker-core-loop-unit-tests", - "phase_id": "phase-1", - "event": "verification", - "commands": [ - { - "command": "cd git-ranker && ./gradlew test --tests 'com.gitranker.api.domain.auth.service.*' --tests 'com.gitranker.api.domain.badge.*' --tests 'com.gitranker.api.domain.log.*' --tests 'com.gitranker.api.domain.ranking.*' --tests 'com.gitranker.api.domain.user.*' --tests 'com.gitranker.api.global.util.*'", - "status": "passed", - "output": "Starting a Gradle Daemon, 1 incompatible Daemon could not be reused, use --status for details\n> Task :compileJava UP-TO-DATE\n> Task :processResources UP-TO-DATE\n> Task :classes UP-TO-DATE\n> Task :compileTestJava UP-TO-DATE\n> Task :processTestResources NO-SOURCE\n> Task :testClasses UP-TO-DATE\n> Task :test UP-TO-DATE\n\nBUILD SUCCESSFUL in 4s\n4 actionable tasks: 4 up-to-date\nConsider enabling configuration cache to speed up this build: https://docs.gradle.org/9.2.1/userguide/configuration_cache_enabling.html" - } - ], - "result": "passed", - "evidence": [], - "error_fingerprint": null, - "next_action": "review", - "timestamp": "2026-04-16T05:55:23+00:00" -} diff --git a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T060406-fb8e67a9.json b/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T060406-fb8e67a9.json deleted file mode 100644 index 5f4c453..0000000 --- a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T060406-fb8e67a9.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "id": "20260416T060406-fb8e67a9", - "task_id": "task-gitranker-core-loop-unit-tests", - "phase_id": "phase-2", - "event": "phase_completion", - "commands": [ - { - "command": "phase completion", - "status": "passed", - "output": "git-ranker/src/test/java/com/gitranker/api/batch/listener/BatchProgressListenerTest.java, git-ranker/src/test/java/com/gitranker/api/batch/listener/GitHubCostListenerTest.java, git-ranker/src/test/java/com/gitranker/api/batch/listener/UserScoreCalculationSkipListenerTest.java, git-ranker/src/test/java/com/gitranker/api/batch/metrics/BatchMetricsTest.java, git-ranker/src/test/java/com/gitranker/api/batch/processor/ScoreRecalculationProcessorTest.java, git-ranker/src/test/java/com/gitranker/api/batch/reader/UserItemReaderTest.java, git-ranker/src/test/java/com/gitranker/api/batch/scheduler/BatchSchedulerTest.java, git-ranker/src/test/java/com/gitranker/api/batch/strategy/ActivityUpdateContextTest.java, git-ranker/src/test/java/com/gitranker/api/batch/strategy/FullActivityUpdateStrategyTest.java, git-ranker/src/test/java/com/gitranker/api/batch/strategy/IncrementalActivityUpdateStrategyTest.java, git-ranker/src/test/java/com/gitranker/api/batch/tasklet/RankingRecalculationTaskletTest.java, git-ranker/src/test/java/com/gitranker/api/batch/writer/UserItemWriterTest.java" - } - ], - "result": "passed", - "evidence": [], - "error_fingerprint": null, - "next_action": "verify", - "timestamp": "2026-04-16T06:04:06+00:00" -} diff --git a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T060409-cd1b0372.json b/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T060409-cd1b0372.json deleted file mode 100644 index c9ab40c..0000000 --- a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T060409-cd1b0372.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "id": "20260416T060409-cd1b0372", - "task_id": "task-gitranker-core-loop-unit-tests", - "phase_id": "phase-2", - "event": "verification", - "commands": [ - { - "command": "cd git-ranker && ./gradlew test --tests 'com.gitranker.api.batch.*'", - "status": "failed", - "output": "Exception in thread \"main\" java.io.FileNotFoundException: /Users/hyoseok/.gradle/wrapper/dists/gradle-9.2.1-bin/2t0n5ozlw9xmuyvbp7dnzaxug/gradle-9.2.1-bin.zip.lck (Operation not permitted)\n\tat java.base/java.io.RandomAccessFile.open0(Native Method)\n\tat java.base/java.io.RandomAccessFile.open(RandomAccessFile.java:338)\n\tat java.base/java.io.RandomAccessFile.(RandomAccessFile.java:257)\n\tat java.base/java.io.RandomAccessFile.(RandomAccessFile.java:211)\n\tat org.gradle.wrapper.GradleWrapperMain.main(SourceFile:67)" - } - ], - "result": "failed", - "evidence": [], - "error_fingerprint": "verification:phase-2:cd git-ranker && ./gradlew test --tests 'com.gitranker.api.batch.*'", - "next_action": "repair", - "timestamp": "2026-04-16T06:04:09+00:00" -} diff --git a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T060425-5d5d570b.json b/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T060425-5d5d570b.json deleted file mode 100644 index f8dce44..0000000 --- a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T060425-5d5d570b.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "id": "20260416T060425-5d5d570b", - "task_id": "task-gitranker-core-loop-unit-tests", - "phase_id": "phase-2", - "event": "reopened", - "commands": [ - { - "command": "reopen task", - "status": "passed", - "output": "Verification failed due to sandbox-denied Gradle wrapper lock access; reopen phase to rerun verification with escalated permissions." - } - ], - "result": "passed", - "evidence": [ - "Verification failed due to sandbox-denied Gradle wrapper lock access; reopen phase to rerun verification with escalated permissions." - ], - "error_fingerprint": null, - "next_action": "plan or run", - "timestamp": "2026-04-16T06:04:25+00:00" -} diff --git a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T060435-a977ad16.json b/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T060435-a977ad16.json deleted file mode 100644 index 4f0ba84..0000000 --- a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T060435-a977ad16.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "id": "20260416T060435-a977ad16", - "task_id": "task-gitranker-core-loop-unit-tests", - "phase_id": "phase-2", - "event": "phase_completion", - "commands": [ - { - "command": "phase completion", - "status": "passed", - "output": "git-ranker/src/test/java/com/gitranker/api/batch/listener/BatchProgressListenerTest.java, git-ranker/src/test/java/com/gitranker/api/batch/listener/GitHubCostListenerTest.java, git-ranker/src/test/java/com/gitranker/api/batch/listener/UserScoreCalculationSkipListenerTest.java, git-ranker/src/test/java/com/gitranker/api/batch/metrics/BatchMetricsTest.java, git-ranker/src/test/java/com/gitranker/api/batch/processor/ScoreRecalculationProcessorTest.java, git-ranker/src/test/java/com/gitranker/api/batch/reader/UserItemReaderTest.java, git-ranker/src/test/java/com/gitranker/api/batch/scheduler/BatchSchedulerTest.java, git-ranker/src/test/java/com/gitranker/api/batch/strategy/ActivityUpdateContextTest.java, git-ranker/src/test/java/com/gitranker/api/batch/strategy/FullActivityUpdateStrategyTest.java, git-ranker/src/test/java/com/gitranker/api/batch/strategy/IncrementalActivityUpdateStrategyTest.java, git-ranker/src/test/java/com/gitranker/api/batch/tasklet/RankingRecalculationTaskletTest.java, git-ranker/src/test/java/com/gitranker/api/batch/writer/UserItemWriterTest.java" - } - ], - "result": "passed", - "evidence": [], - "error_fingerprint": null, - "next_action": "verify", - "timestamp": "2026-04-16T06:04:35+00:00" -} diff --git a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T060618-dd774817.json b/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T060618-dd774817.json deleted file mode 100644 index ff9a165..0000000 --- a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T060618-dd774817.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "id": "20260416T060618-dd774817", - "task_id": "task-gitranker-core-loop-unit-tests", - "phase_id": "phase-2", - "event": "verification", - "commands": [ - { - "command": "cd git-ranker && ./gradlew test --tests 'com.gitranker.api.batch.*'", - "status": "passed", - "output": "> Task :compileJava UP-TO-DATE\n> Task :processResources UP-TO-DATE\n> Task :classes UP-TO-DATE\n> Task :compileTestJava UP-TO-DATE\n> Task :processTestResources NO-SOURCE\n> Task :testClasses UP-TO-DATE\n> Task :test UP-TO-DATE\n\nBUILD SUCCESSFUL in 1s\n4 actionable tasks: 4 up-to-date\nConsider enabling configuration cache to speed up this build: https://docs.gradle.org/9.2.1/userguide/configuration_cache_enabling.html" - } - ], - "result": "passed", - "evidence": [], - "error_fingerprint": null, - "next_action": "review", - "timestamp": "2026-04-16T06:06:18+00:00" -} diff --git a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T061059-edeb1748.json b/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T061059-edeb1748.json deleted file mode 100644 index e0c2ce4..0000000 --- a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T061059-edeb1748.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "id": "20260416T061059-edeb1748", - "task_id": "task-gitranker-core-loop-unit-tests", - "phase_id": "phase-3", - "event": "phase_completion", - "commands": [ - { - "command": "phase completion", - "status": "passed", - "output": "git-ranker/src/test/java/com/gitranker/api/infrastructure/github/GitHubActivityServiceTest.java, git-ranker/src/test/java/com/gitranker/api/infrastructure/github/GitHubApiErrorHandlerTest.java, git-ranker/src/test/java/com/gitranker/api/infrastructure/github/GitHubApiMetricsTest.java, git-ranker/src/test/java/com/gitranker/api/infrastructure/github/GitHubDataMapperTest.java, git-ranker/src/test/java/com/gitranker/api/infrastructure/github/GitHubGraphQLClientTest.java, git-ranker/src/test/java/com/gitranker/api/infrastructure/github/dto/GitHubActivitySummaryTest.java, git-ranker/src/test/java/com/gitranker/api/infrastructure/github/dto/GitHubAllActivitiesResponseTest.java, git-ranker/src/test/java/com/gitranker/api/infrastructure/github/dto/GitHubGraphQLRequestTest.java, git-ranker/src/test/java/com/gitranker/api/infrastructure/github/dto/GitHubNodeUserResponseTest.java, git-ranker/src/test/java/com/gitranker/api/infrastructure/github/dto/GitHubUserInfoResponseTest.java, git-ranker/src/test/java/com/gitranker/api/infrastructure/github/token/GitHubTokenPoolTest.java, git-ranker/src/test/java/com/gitranker/api/infrastructure/github/token/TokenStateTest.java, git-ranker/src/test/java/com/gitranker/api/infrastructure/github/util/GraphQLQueryBuilderTest.java" - } - ], - "result": "passed", - "evidence": [], - "error_fingerprint": null, - "next_action": "verify", - "timestamp": "2026-04-16T06:10:59+00:00" -} diff --git a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T061201-60068c66.json b/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T061201-60068c66.json deleted file mode 100644 index 3e2506d..0000000 --- a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T061201-60068c66.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "id": "20260416T061201-60068c66", - "task_id": "task-gitranker-core-loop-unit-tests", - "phase_id": "phase-3", - "event": "verification", - "commands": [ - { - "command": "cd git-ranker && ./gradlew test --tests 'com.gitranker.api.infrastructure.github.*'", - "status": "passed", - "output": "> Task :compileJava UP-TO-DATE\n> Task :processResources UP-TO-DATE\n> Task :classes UP-TO-DATE\n> Task :compileTestJava UP-TO-DATE\n> Task :processTestResources NO-SOURCE\n> Task :testClasses UP-TO-DATE\n> Task :test\n\nBUILD SUCCESSFUL in 2s\n4 actionable tasks: 1 executed, 3 up-to-date\nConsider enabling configuration cache to speed up this build: https://docs.gradle.org/9.2.1/userguide/configuration_cache_enabling.html\n\nOpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended" - }, - { - "command": "cd git-ranker && ./gradlew test", - "status": "failed", - "output": "> Task :compileJava UP-TO-DATE\n> Task :processResources UP-TO-DATE\n> Task :classes UP-TO-DATE\n> Task :compileTestJava UP-TO-DATE\n> Task :processTestResources NO-SOURCE\n> Task :testClasses UP-TO-DATE\n\n> Task :test FAILED\n\nGitHubGraphQLClientTest > getAllActivities merges merged-pr block and yearly contribution response FAILED\n org.mockito.exceptions.verification.opentest4j.ArgumentsAreDifferent at GitHubGraphQLClientTest.java:172\n4 actionable tasks: 1 executed, 3 up-to-date\n\nOpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has be..." - } - ], - "result": "failed", - "evidence": [], - "error_fingerprint": "verification:phase-3:cd git-ranker && ./gradlew test", - "next_action": "repair", - "timestamp": "2026-04-16T06:12:01+00:00" -} diff --git a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T061240-118f07df.json b/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T061240-118f07df.json deleted file mode 100644 index cd79f3e..0000000 --- a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T061240-118f07df.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "id": "20260416T061240-118f07df", - "task_id": "task-gitranker-core-loop-unit-tests", - "phase_id": "phase-3", - "event": "reopened", - "commands": [ - { - "command": "reopen task", - "status": "passed", - "output": "Verification exposed a nondeterministic full-suite assertion in GitHubGraphQLClientTest; reopen phase after fixing the test to rerun final verification." - } - ], - "result": "passed", - "evidence": [ - "Verification exposed a nondeterministic full-suite assertion in GitHubGraphQLClientTest; reopen phase after fixing the test to rerun final verification." - ], - "error_fingerprint": null, - "next_action": "plan or run", - "timestamp": "2026-04-16T06:12:40+00:00" -} diff --git a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T061249-83c3d5da.json b/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T061249-83c3d5da.json deleted file mode 100644 index a955d01..0000000 --- a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T061249-83c3d5da.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "id": "20260416T061249-83c3d5da", - "task_id": "task-gitranker-core-loop-unit-tests", - "phase_id": "phase-3", - "event": "phase_completion", - "commands": [ - { - "command": "phase completion", - "status": "passed", - "output": "git-ranker/src/test/java/com/gitranker/api/infrastructure/github/GitHubActivityServiceTest.java, git-ranker/src/test/java/com/gitranker/api/infrastructure/github/GitHubApiErrorHandlerTest.java, git-ranker/src/test/java/com/gitranker/api/infrastructure/github/GitHubApiMetricsTest.java, git-ranker/src/test/java/com/gitranker/api/infrastructure/github/GitHubDataMapperTest.java, git-ranker/src/test/java/com/gitranker/api/infrastructure/github/GitHubGraphQLClientTest.java, git-ranker/src/test/java/com/gitranker/api/infrastructure/github/dto/GitHubActivitySummaryTest.java, git-ranker/src/test/java/com/gitranker/api/infrastructure/github/dto/GitHubAllActivitiesResponseTest.java, git-ranker/src/test/java/com/gitranker/api/infrastructure/github/dto/GitHubGraphQLRequestTest.java, git-ranker/src/test/java/com/gitranker/api/infrastructure/github/dto/GitHubNodeUserResponseTest.java, git-ranker/src/test/java/com/gitranker/api/infrastructure/github/dto/GitHubUserInfoResponseTest.java, git-ranker/src/test/java/com/gitranker/api/infrastructure/github/token/GitHubTokenPoolTest.java, git-ranker/src/test/java/com/gitranker/api/infrastructure/github/token/TokenStateTest.java, git-ranker/src/test/java/com/gitranker/api/infrastructure/github/util/GraphQLQueryBuilderTest.java" - } - ], - "result": "passed", - "evidence": [], - "error_fingerprint": null, - "next_action": "verify", - "timestamp": "2026-04-16T06:12:49+00:00" -} diff --git a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T061306-e0472a6c.json b/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T061306-e0472a6c.json deleted file mode 100644 index f389c1f..0000000 --- a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T061306-e0472a6c.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "id": "20260416T061306-e0472a6c", - "task_id": "task-gitranker-core-loop-unit-tests", - "phase_id": "phase-3", - "event": "verification", - "commands": [ - { - "command": "cd git-ranker && ./gradlew test --tests 'com.gitranker.api.infrastructure.github.*'", - "status": "passed", - "output": "> Task :compileJava UP-TO-DATE\n> Task :processResources UP-TO-DATE\n> Task :classes UP-TO-DATE\n> Task :compileTestJava UP-TO-DATE\n> Task :processTestResources NO-SOURCE\n> Task :testClasses UP-TO-DATE\n> Task :test\n\nBUILD SUCCESSFUL in 2s\n4 actionable tasks: 1 executed, 3 up-to-date\nConsider enabling configuration cache to speed up this build: https://docs.gradle.org/9.2.1/userguide/configuration_cache_enabling.html\n\nOpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended" - }, - { - "command": "cd git-ranker && ./gradlew test", - "status": "passed", - "output": "> Task :compileJava UP-TO-DATE\n> Task :processResources UP-TO-DATE\n> Task :classes UP-TO-DATE\n> Task :compileTestJava UP-TO-DATE\n> Task :processTestResources NO-SOURCE\n> Task :testClasses UP-TO-DATE\n> Task :test\n\nBUILD SUCCESSFUL in 2s\n4 actionable tasks: 1 executed, 3 up-to-date\nConsider enabling configuration cache to speed up this build: https://docs.gradle.org/9.2.1/userguide/configuration_cache_enabling.html\n\nOpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended" - } - ], - "result": "passed", - "evidence": [], - "error_fingerprint": null, - "next_action": "review", - "timestamp": "2026-04-16T06:13:06+00:00" -} diff --git a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T061313-90ee609a.json b/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T061313-90ee609a.json deleted file mode 100644 index 78873ed..0000000 --- a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T061313-90ee609a.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "id": "20260416T061313-90ee609a", - "task_id": "task-gitranker-core-loop-unit-tests", - "phase_id": "phase-3", - "event": "review_ready", - "commands": [ - { - "command": "review gate", - "status": "passed", - "output": "latest passed verification: 20260416T061306-e0472a6c" - } - ], - "result": "passed", - "evidence": [ - "All planned domain, batch, and GitHub unit-test phases passed their acceptance commands including final ./gradlew test." - ], - "error_fingerprint": null, - "next_action": "user validation", - "timestamp": "2026-04-16T06:13:13+00:00" -} diff --git a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T061953-5dcd9c28.json b/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T061953-5dcd9c28.json deleted file mode 100644 index ddac44e..0000000 --- a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T061953-5dcd9c28.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "id": "20260416T061953-5dcd9c28", - "task_id": "task-gitranker-core-loop-unit-tests", - "phase_id": "phase-3", - "event": "review_closeout", - "commands": [ - { - "command": "complete task", - "status": "passed", - "output": "사용자가 테스트 보강 결과를 검증 완료로 확인하고 workflow closeout 진행을 요청함" - } - ], - "result": "passed", - "evidence": [ - "사용자가 테스트 보강 결과를 검증 완료로 확인하고 workflow closeout 진행을 요청함" - ], - "error_fingerprint": null, - "next_action": "done", - "timestamp": "2026-04-16T06:19:53+00:00" -} diff --git a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T074913-4422cb24.json b/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T074913-4422cb24.json deleted file mode 100644 index b7d17e2..0000000 --- a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T074913-4422cb24.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "id": "20260416T074913-4422cb24", - "task_id": "task-gitranker-core-loop-unit-tests", - "phase_id": "phase-3", - "event": "reopened", - "commands": [ - { - "command": "reopen task", - "status": "passed", - "output": "PR CI exposed a timezone-dependent failure in GitHubTokenPoolTest under UTC; reopen phase 3 to repair and reverify the GitHub test slice." - } - ], - "result": "passed", - "evidence": [ - "PR CI exposed a timezone-dependent failure in GitHubTokenPoolTest under UTC; reopen phase 3 to repair and reverify the GitHub test slice." - ], - "error_fingerprint": null, - "next_action": "plan or run", - "timestamp": "2026-04-16T07:49:13+00:00" -} diff --git a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T074918-e6a2ba2e.json b/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T074918-e6a2ba2e.json deleted file mode 100644 index bd9aa46..0000000 --- a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T074918-e6a2ba2e.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "id": "20260416T074918-e6a2ba2e", - "task_id": "task-gitranker-core-loop-unit-tests", - "phase_id": "phase-3", - "event": "phase_completion", - "commands": [ - { - "command": "phase completion", - "status": "passed", - "output": "git-ranker/src/test/java/com/gitranker/api/infrastructure/github/token/GitHubTokenPoolTest.java" - } - ], - "result": "passed", - "evidence": [], - "error_fingerprint": null, - "next_action": "verify", - "timestamp": "2026-04-16T07:49:18+00:00" -} diff --git a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T074928-f23cfd46.json b/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T074928-f23cfd46.json deleted file mode 100644 index de3a638..0000000 --- a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T074928-f23cfd46.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "id": "20260416T074928-f23cfd46", - "task_id": "task-gitranker-core-loop-unit-tests", - "phase_id": "phase-3", - "event": "verification", - "commands": [ - { - "command": "cd git-ranker && ./gradlew test --tests 'com.gitranker.api.infrastructure.github.*'", - "status": "passed", - "output": "> Task :compileJava UP-TO-DATE\n> Task :processResources UP-TO-DATE\n> Task :classes UP-TO-DATE\n> Task :compileTestJava UP-TO-DATE\n> Task :processTestResources NO-SOURCE\n> Task :testClasses UP-TO-DATE\n> Task :test\n\nBUILD SUCCESSFUL in 2s\n4 actionable tasks: 1 executed, 3 up-to-date\nConsider enabling configuration cache to speed up this build: https://docs.gradle.org/9.2.1/userguide/configuration_cache_enabling.html\n\nOpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended" - }, - { - "command": "cd git-ranker && ./gradlew test", - "status": "passed", - "output": "> Task :compileJava UP-TO-DATE\n> Task :processResources UP-TO-DATE\n> Task :classes UP-TO-DATE\n> Task :compileTestJava UP-TO-DATE\n> Task :processTestResources NO-SOURCE\n> Task :testClasses UP-TO-DATE\n> Task :test\n\nBUILD SUCCESSFUL in 2s\n4 actionable tasks: 1 executed, 3 up-to-date\nConsider enabling configuration cache to speed up this build: https://docs.gradle.org/9.2.1/userguide/configuration_cache_enabling.html\n\nOpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended" - } - ], - "result": "passed", - "evidence": [], - "error_fingerprint": null, - "next_action": "review", - "timestamp": "2026-04-16T07:49:28+00:00" -} diff --git a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T075010-e7076ff9.json b/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T075010-e7076ff9.json deleted file mode 100644 index bda3b45..0000000 --- a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T075010-e7076ff9.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "id": "20260416T075010-e7076ff9", - "task_id": "task-gitranker-core-loop-unit-tests", - "phase_id": "phase-3", - "event": "review_ready", - "commands": [ - { - "command": "review gate", - "status": "passed", - "output": "latest passed verification: 20260416T074928-f23cfd46" - } - ], - "result": "passed", - "evidence": [ - "PR CI follow-up fixed a UTC-dependent GitHubTokenPoolTest failure and passed local UTC clean test verification." - ], - "error_fingerprint": null, - "next_action": "user validation", - "timestamp": "2026-04-16T07:50:10+00:00" -} diff --git a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T081813-fb41cea4.json b/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T081813-fb41cea4.json deleted file mode 100644 index 75eedab..0000000 --- a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T081813-fb41cea4.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "id": "20260416T081813-fb41cea4", - "task_id": "task-gitranker-core-loop-unit-tests", - "phase_id": "phase-3", - "event": "reopened", - "commands": [ - { - "command": "reopen task", - "status": "passed", - "output": "PR review follow-up reopened phase-3 to incorporate actionable Codex and CodeRabbit test feedback." - } - ], - "result": "passed", - "evidence": [ - "PR review follow-up reopened phase-3 to incorporate actionable Codex and CodeRabbit test feedback." - ], - "error_fingerprint": null, - "next_action": "plan or run", - "timestamp": "2026-04-16T08:18:13+00:00" -} diff --git a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T081821-1c5162d8.json b/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T081821-1c5162d8.json deleted file mode 100644 index aa3d1c5..0000000 --- a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T081821-1c5162d8.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "id": "20260416T081821-1c5162d8", - "task_id": "task-gitranker-core-loop-unit-tests", - "phase_id": "phase-3", - "event": "phase_kickoff", - "commands": [ - { - "command": "phase kickoff", - "status": "passed", - "output": "kickoff recorded for phase-3" - } - ], - "result": "passed", - "evidence": [ - "required_reads: workflows/tasks/task-gitranker-core-loop-unit-tests/spec.md, git-ranker/src/main/java/com/gitranker/api/infrastructure/github/, git-ranker/src/test/java/", - "starting_points: Read the locked spec for task-gitranker-core-loop-unit-tests., Inspect the active phase goal: Add exhaustive GitHub collection and error translation unit tests, then confirm the full git-ranker unit-test baseline stays green., Confirm allowed write paths and acceptance commands before editing.", - "completion_signal: phase-3 acceptance commands pass" - ], - "error_fingerprint": null, - "next_action": "start phase", - "timestamp": "2026-04-16T08:18:21+00:00" -} diff --git a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T081830-d02f2d4f.json b/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T081830-d02f2d4f.json deleted file mode 100644 index d90036e..0000000 --- a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T081830-d02f2d4f.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "id": "20260416T081830-d02f2d4f", - "task_id": "task-gitranker-core-loop-unit-tests", - "phase_id": "phase-3", - "event": "phase_completion", - "commands": [ - { - "command": "phase completion", - "status": "passed", - "output": "git-ranker/src/test/java/com/gitranker/api/batch/listener/UserScoreCalculationSkipListenerTest.java, git-ranker/src/test/java/com/gitranker/api/batch/metrics/BatchMetricsTest.java, git-ranker/src/test/java/com/gitranker/api/batch/processor/ScoreRecalculationProcessorTest.java, git-ranker/src/test/java/com/gitranker/api/domain/auth/service/AuthServiceTest.java, git-ranker/src/test/java/com/gitranker/api/domain/log/ActivityLogOrchestratorTest.java, git-ranker/src/test/java/com/gitranker/api/domain/log/ActivityLogServiceTest.java, git-ranker/src/test/java/com/gitranker/api/domain/ranking/RankingServiceTest.java, git-ranker/src/test/java/com/gitranker/api/domain/user/UserTest.java, git-ranker/src/test/java/com/gitranker/api/infrastructure/github/GitHubApiMetricsTest.java, git-ranker/src/test/java/com/gitranker/api/infrastructure/github/GitHubGraphQLClientTest.java, git-ranker/src/test/java/com/gitranker/api/infrastructure/github/token/GitHubTokenPoolTest.java, git-ranker/src/test/java/com/gitranker/api/infrastructure/github/token/TokenStateTest.java" - } - ], - "result": "passed", - "evidence": [], - "error_fingerprint": null, - "next_action": "verify", - "timestamp": "2026-04-16T08:18:30+00:00" -} diff --git a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T081834-ecd3d697.json b/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T081834-ecd3d697.json deleted file mode 100644 index 5eeda23..0000000 --- a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T081834-ecd3d697.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "id": "20260416T081834-ecd3d697", - "task_id": "task-gitranker-core-loop-unit-tests", - "phase_id": "phase-3", - "event": "verification", - "commands": [ - { - "command": "cd git-ranker && ./gradlew test --tests 'com.gitranker.api.infrastructure.github.*'", - "status": "failed", - "output": "Exception in thread \"main\" java.io.FileNotFoundException: /Users/hyoseok/.gradle/wrapper/dists/gradle-9.2.1-bin/2t0n5ozlw9xmuyvbp7dnzaxug/gradle-9.2.1-bin.zip.lck (Operation not permitted)\n\tat java.base/java.io.RandomAccessFile.open0(Native Method)\n\tat java.base/java.io.RandomAccessFile.open(RandomAccessFile.java:338)\n\tat java.base/java.io.RandomAccessFile.(RandomAccessFile.java:257)\n\tat java.base/java.io.RandomAccessFile.(RandomAccessFile.java:211)\n\tat org.gradle.wrapper.GradleWrapperMain.main(SourceFile:67)" - } - ], - "result": "failed", - "evidence": [], - "error_fingerprint": "verification:phase-3:cd git-ranker && ./gradlew test --tests 'com.gitranker.api.infrastructure.github.*'", - "next_action": "repair", - "timestamp": "2026-04-16T08:18:34+00:00" -} diff --git a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T081905-b1928d47.json b/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T081905-b1928d47.json deleted file mode 100644 index bbe7f2c..0000000 --- a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T081905-b1928d47.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "id": "20260416T081905-b1928d47", - "task_id": "task-gitranker-core-loop-unit-tests", - "phase_id": "phase-3", - "event": "reopened", - "commands": [ - { - "command": "reopen task", - "status": "passed", - "output": "Retry phase-3 verification after sandbox blocked Gradle wrapper lock access during workflow verify." - } - ], - "result": "passed", - "evidence": [ - "Retry phase-3 verification after sandbox blocked Gradle wrapper lock access during workflow verify." - ], - "error_fingerprint": null, - "next_action": "plan or run", - "timestamp": "2026-04-16T08:19:05+00:00" -} diff --git a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T081909-3984baf2.json b/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T081909-3984baf2.json deleted file mode 100644 index b64f5be..0000000 --- a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T081909-3984baf2.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "id": "20260416T081909-3984baf2", - "task_id": "task-gitranker-core-loop-unit-tests", - "phase_id": "phase-3", - "event": "phase_kickoff", - "commands": [ - { - "command": "phase kickoff", - "status": "passed", - "output": "kickoff recorded for phase-3" - } - ], - "result": "passed", - "evidence": [ - "required_reads: workflows/tasks/task-gitranker-core-loop-unit-tests/spec.md, git-ranker/src/main/java/com/gitranker/api/infrastructure/github/, git-ranker/src/test/java/", - "starting_points: Read the locked spec for task-gitranker-core-loop-unit-tests., Inspect the active phase goal: Add exhaustive GitHub collection and error translation unit tests, then confirm the full git-ranker unit-test baseline stays green., Confirm allowed write paths and acceptance commands before editing.", - "completion_signal: phase-3 acceptance commands pass" - ], - "error_fingerprint": null, - "next_action": "start phase", - "timestamp": "2026-04-16T08:19:09+00:00" -} diff --git a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T081920-336565ba.json b/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T081920-336565ba.json deleted file mode 100644 index e7000af..0000000 --- a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T081920-336565ba.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "id": "20260416T081920-336565ba", - "task_id": "task-gitranker-core-loop-unit-tests", - "phase_id": "phase-3", - "event": "phase_completion", - "commands": [ - { - "command": "phase completion", - "status": "passed", - "output": "git-ranker/src/test/java/com/gitranker/api/batch/listener/UserScoreCalculationSkipListenerTest.java, git-ranker/src/test/java/com/gitranker/api/batch/metrics/BatchMetricsTest.java, git-ranker/src/test/java/com/gitranker/api/batch/processor/ScoreRecalculationProcessorTest.java, git-ranker/src/test/java/com/gitranker/api/domain/auth/service/AuthServiceTest.java, git-ranker/src/test/java/com/gitranker/api/domain/log/ActivityLogOrchestratorTest.java, git-ranker/src/test/java/com/gitranker/api/domain/log/ActivityLogServiceTest.java, git-ranker/src/test/java/com/gitranker/api/domain/ranking/RankingServiceTest.java, git-ranker/src/test/java/com/gitranker/api/domain/user/UserTest.java, git-ranker/src/test/java/com/gitranker/api/infrastructure/github/GitHubApiMetricsTest.java, git-ranker/src/test/java/com/gitranker/api/infrastructure/github/GitHubGraphQLClientTest.java, git-ranker/src/test/java/com/gitranker/api/infrastructure/github/token/GitHubTokenPoolTest.java, git-ranker/src/test/java/com/gitranker/api/infrastructure/github/token/TokenStateTest.java" - } - ], - "result": "passed", - "evidence": [], - "error_fingerprint": null, - "next_action": "verify", - "timestamp": "2026-04-16T08:19:20+00:00" -} diff --git a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T081946-7f67b0e4.json b/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T081946-7f67b0e4.json deleted file mode 100644 index c7e8006..0000000 --- a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T081946-7f67b0e4.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "id": "20260416T081946-7f67b0e4", - "task_id": "task-gitranker-core-loop-unit-tests", - "phase_id": "phase-3", - "event": "verification", - "commands": [ - { - "command": "cd git-ranker && ./gradlew test --tests 'com.gitranker.api.infrastructure.github.*'", - "status": "passed", - "output": "> Task :compileJava UP-TO-DATE\n> Task :processResources UP-TO-DATE\n> Task :classes UP-TO-DATE\n> Task :compileTestJava UP-TO-DATE\n> Task :processTestResources NO-SOURCE\n> Task :testClasses UP-TO-DATE\n> Task :test\n\nBUILD SUCCESSFUL in 2s\n4 actionable tasks: 1 executed, 3 up-to-date\nConsider enabling configuration cache to speed up this build: https://docs.gradle.org/9.2.1/userguide/configuration_cache_enabling.html\n\nOpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended" - }, - { - "command": "cd git-ranker && ./gradlew test", - "status": "passed", - "output": "> Task :compileJava UP-TO-DATE\n> Task :processResources UP-TO-DATE\n> Task :classes UP-TO-DATE\n> Task :compileTestJava UP-TO-DATE\n> Task :processTestResources NO-SOURCE\n> Task :testClasses UP-TO-DATE\n> Task :test\n\nBUILD SUCCESSFUL in 2s\n4 actionable tasks: 1 executed, 3 up-to-date\nConsider enabling configuration cache to speed up this build: https://docs.gradle.org/9.2.1/userguide/configuration_cache_enabling.html\n\nOpenJDK 64-Bit Server VM warning: Sharing is only supported for boot loader classes because bootstrap classpath has been appended" - } - ], - "result": "passed", - "evidence": [], - "error_fingerprint": null, - "next_action": "review", - "timestamp": "2026-04-16T08:19:46+00:00" -} diff --git a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T081951-53ffd56f.json b/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T081951-53ffd56f.json deleted file mode 100644 index 54a92b6..0000000 --- a/workflows/tasks/task-gitranker-core-loop-unit-tests/runs/20260416T081951-53ffd56f.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "id": "20260416T081951-53ffd56f", - "task_id": "task-gitranker-core-loop-unit-tests", - "phase_id": "phase-3", - "event": "review_ready", - "commands": [ - { - "command": "review gate", - "status": "passed", - "output": "latest passed verification: 20260416T081946-7f67b0e4" - } - ], - "result": "passed", - "evidence": [ - "Addressed actionable PR review feedback in git-ranker tests, reverified phase-3 with workflow evidence, and confirmed full ./gradlew test is green." - ], - "error_fingerprint": null, - "next_action": "user validation", - "timestamp": "2026-04-16T08:19:51+00:00" -} diff --git a/workflows/tasks/task-gitranker-core-loop-unit-tests/spec.md b/workflows/tasks/task-gitranker-core-loop-unit-tests/spec.md deleted file mode 100644 index 18f14b9..0000000 --- a/workflows/tasks/task-gitranker-core-loop-unit-tests/spec.md +++ /dev/null @@ -1,72 +0,0 @@ -# Strengthen git-ranker core loop unit tests - -- Task ID: `task-gitranker-core-loop-unit-tests` -- Primary Repo: `git-ranker` -- Status: `draft` - -## Request - -- `git-ranker`의 물리적 피드백 및 검증 루프에 대해 현재 구현된 로직을 기준으로 unit 테스트를 보강한다. -- 이미 존재하는 테스트 코드는 다시 검토해 개선하거나 추가해야 할 케이스를 반영한다. -- happy path만이 아니라 예외 케이스, 비즈니스 로직 검증, 경계값, fallback, side effect, non-interaction까지 놓치기 쉬운 부분을 세분화해 테스트로 고정한다. - -## Problem - -- 현재 `git-ranker`의 test baseline은 일부 service 중심으로만 구성되어 있어 핵심 피드백/검증 루프를 이루는 value object, batch, GitHub infra, orchestration 로직의 회귀 위험이 남아 있다. -- 기존 테스트도 happy path 위주인 곳이 있어 예외 번역, 경계값, null/empty fallback, metric 또는 repository side effect 누락 여부를 충분히 검증하지 못한다. -- 이 상태에서는 도메인 규칙이나 배치 루프를 수정할 때 의도하지 않은 동작 변화가 CI에서 충분히 드러나지 않을 수 있다. - -## Goals - -- 기존 unit 테스트를 재검토해 약한 assertion, 누락된 분기, 빠진 예외 케이스를 보강한다. -- 핵심 피드백/검증 루프에 해당하는 미테스트 로직에 대해 JUnit 5 + Mockito 중심의 좁은 unit 테스트를 추가한다. -- 각 대상 로직에 대해 정상 흐름, 경계값, null/empty/default, 예외 번역, side effect, non-interaction, time-dependent branch, mapping/invariant를 검증한다. -- 최종적으로 `git-ranker`에서 `./gradlew test`가 green 이어야 한다. - -## Non-goals - -- controller, security filter, `GlobalExceptionHandler`, config, repository integration, Micrometer wiring 테스트는 이번 작업에 포함하지 않는다. -- JaCoCo, coverage gate, CI workflow, Gradle dependency 변경은 이번 작업에 포함하지 않는다. -- production API, endpoint, batch schedule, 메시지 key, runtime contract 변경은 목표가 아니다. -- 테스트 고립이 막히는 경우를 제외하고 production code 리팩터링 자체를 이번 작업의 주목적으로 삼지 않는다. - -## Constraints - -- root `AGENTS.md` workflow 계약을 따른다. 승인된 spec 없이 phase 실행이나 구현을 시작하지 않는다. -- task state와 phase state는 `python3 scripts/workflow.py ...` 명령으로만 전이한다. -- 구현 변경에는 대응 테스트가 필요하며, 이번 작업은 `test_policy.mode=require_tests`를 따른다. -- canonical plan은 `workflows/tasks/task-gitranker-core-loop-unit-tests/phases.json` 하나다. -- 테스트 수준은 `JUnit 5 + Mockito` 중심의 좁은 unit로 고정하고 `@WebMvcTest`, `@SpringBootTest`는 쓰지 않는다. -- 수정은 active phase의 `allowed_write_paths` 안에서만 수행한다. - -## Acceptance - -- 기존 테스트 보강과 신규 핵심 루프 테스트가 `src/test/java` 아래에 추가되어 있다. -- 기존 테스트 보강 범위에는 최소한 `ActivityLogServiceTest`, `AuthServiceTest`, `UserPersistenceServiceTest`, `UserRegistrationServiceTest`, `UserRefreshServiceTest`, `UserQueryServiceTest`, `RankingServiceTest`, `RankingRecalculationServiceTest`, `BadgeServiceTest`, `BadgeFormatterTest`, `SvgBadgeRendererTest`, `TimeUtilsTest`, `UserDeletionServiceTest`가 포함된다. -- 신규 테스트 범위에는 최소한 `ActivityStatistics`, `Score`, `RankInfo`, `User`, `ActivityLog`, `ActivityLogOrchestrator`, batch strategy/processor/listener/tasklet/reader/writer/scheduler, GitHub service/mapper/error handler/client/token/query/dto 계열이 포함된다. -- 테스트는 happy path뿐 아니라 예외, 경계값, 비즈니스 규칙, fallback, side effect, non-interaction을 검증한다. -- `git-ranker`에서 `./gradlew test`가 통과한다. - -## Socratic Clarification Log - -- Q: 이번 task의 테스트 범위를 어디까지 잠글까요? -- A: 핵심 피드백/검증 루프를 우선 완결하고, trivial DTO/config/entity/interface 전수 테스트는 제외합니다. -- Decision: 핵심 피드백/검증 루프와 그 주변의 기존 unit test 보강만 이번 task 범위로 잠근다. - -- Q: 테스트 수준은 어디까지 허용할까요? -- A: 좁은 unit 중심으로 가고 외부 I/O와 전체 Spring context 부팅은 피합니다. -- Decision: `JUnit 5 + Mockito` 중심의 좁은 unit test로 고정한다. - -- Q: 기존 테스트 코드도 다시 검토하고 보강할까요? -- A: 네. 이미 있는 테스트도 다시 확인해 개선하거나 추가해야 할 케이스를 반영합니다. -- Decision: 신규 테스트 추가와 함께 기존 test file의 약한 assertion과 누락 분기를 보강한다. - -- Q: happy path만 작성하면 되나요? -- A: 아니요. 예외 케이스, 필요한 비즈니스 로직 검증, 놓치기 쉬운 케이스까지 모두 고려해 세분화합니다. -- Decision: 각 대상 로직은 정상 흐름, 예외, 경계값, fallback, side effect, non-interaction, invariant를 포함해 테스트한다. - -## Approval - -- Actor: `user` -- Timestamp: `2026-04-16T05:47:28+00:00` -- Note: User approved the detailed unit-test implementation plan in chat. diff --git a/workflows/tasks/task-gitranker-core-loop-unit-tests/task.json b/workflows/tasks/task-gitranker-core-loop-unit-tests/task.json deleted file mode 100644 index 5ab9407..0000000 --- a/workflows/tasks/task-gitranker-core-loop-unit-tests/task.json +++ /dev/null @@ -1,79 +0,0 @@ -{ - "id": "task-gitranker-core-loop-unit-tests", - "title": "Strengthen git-ranker core loop unit tests", - "state": "review_ready", - "primary_repo": "git-ranker", - "created_at": "2026-04-16T05:46:36+00:00", - "approved_at": "2026-04-16T05:47:28+00:00", - "approval": { - "actor": "user", - "note": "User approved the detailed unit-test implementation plan in chat.", - "timestamp": "2026-04-16T05:47:28+00:00" - }, - "active_phase_id": "phase-3", - "latest_run_id": "20260416T081951-53ffd56f", - "last_verified_run_id": "20260416T081946-7f67b0e4", - "blocked_reason": null, - "user_validated": false, - "user_validation_note": null, - "intake": { - "request_summary": "`git-ranker`의 물리적 피드백 및 검증 루프에 대해 현재 구현된 로직을 기준으로 unit 테스트를 보강한다. 이미 존재하는 테스트 코드는 다시 검토해 개선하거나 추가해야 할 케이스를 반영한다. happy path만이 아니라 예외 케이스, 비즈니스 로직 검증, 경계값, fallback, side effect, non-interaction까지 놓치기 쉬운 부분을 세분화해 테스트로 고정한다.", - "problem_summary": "현재 `git-ranker`의 test baseline은 일부 service 중심으로만 구성되어 있어 핵심 피드백/검증 루프를 이루는 value object, batch, GitHub infra, orchestration 로직의 회귀 위험이 남아 있다. 기존 테스트도 happy path 위주인 곳이 있어 예외 번역, 경계값, null/empty fallback, metric 또는 repository side effect 누락 여부를 충분히 검증하지 못한다. 이 상태에서는 도메인 규칙이나 배치 루프를 수정할 때 의도하지 않은 동작 변화가 CI에서 충분히 드러나지 않을 수 있다.", - "goals": [ - "기존 unit 테스트를 재검토해 약한 assertion, 누락된 분기, 빠진 예외 케이스를 보강한다.", - "핵심 피드백/검증 루프에 해당하는 미테스트 로직에 대해 JUnit 5 + Mockito 중심의 좁은 unit 테스트를 추가한다.", - "각 대상 로직에 대해 정상 흐름, 경계값, null/empty/default, 예외 번역, side effect, non-interaction, time-dependent branch, mapping/invariant를 검증한다.", - "최종적으로 `git-ranker`에서 `./gradlew test`가 green 이어야 한다." - ], - "non_goals": [ - "controller, security filter, `GlobalExceptionHandler`, config, repository integration, Micrometer wiring 테스트는 이번 작업에 포함하지 않는다.", - "JaCoCo, coverage gate, CI workflow, Gradle dependency 변경은 이번 작업에 포함하지 않는다.", - "production API, endpoint, batch schedule, 메시지 key, runtime contract 변경은 목표가 아니다.", - "테스트 고립이 막히는 경우를 제외하고 production code 리팩터링 자체를 이번 작업의 주목적으로 삼지 않는다." - ], - "constraints": [ - "root `AGENTS.md` workflow 계약을 따른다. 승인된 spec 없이 phase 실행이나 구현을 시작하지 않는다.", - "task state와 phase state는 `python3 scripts/workflow.py ...` 명령으로만 전이한다.", - "구현 변경에는 대응 테스트가 필요하며, 이번 작업은 `test_policy.mode=require_tests`를 따른다.", - "canonical plan은 `workflows/tasks/task-gitranker-core-loop-unit-tests/phases.json` 하나다.", - "테스트 수준은 `JUnit 5 + Mockito` 중심의 좁은 unit로 고정하고 `@WebMvcTest`, `@SpringBootTest`는 쓰지 않는다.", - "수정은 active phase의 `allowed_write_paths` 안에서만 수행한다." - ], - "acceptance": [ - "기존 테스트 보강과 신규 핵심 루프 테스트가 `src/test/java` 아래에 추가되어 있다.", - "기존 테스트 보강 범위에는 최소한 `ActivityLogServiceTest`, `AuthServiceTest`, `UserPersistenceServiceTest`, `UserRegistrationServiceTest`, `UserRefreshServiceTest`, `UserQueryServiceTest`, `RankingServiceTest`, `RankingRecalculationServiceTest`, `BadgeServiceTest`, `BadgeFormatterTest`, `SvgBadgeRendererTest`, `TimeUtilsTest`, `UserDeletionServiceTest`가 포함된다.", - "신규 테스트 범위에는 최소한 `ActivityStatistics`, `Score`, `RankInfo`, `User`, `ActivityLog`, `ActivityLogOrchestrator`, batch strategy/processor/listener/tasklet/reader/writer/scheduler, GitHub service/mapper/error handler/client/token/query/dto 계열이 포함된다.", - "테스트는 happy path뿐 아니라 예외, 경계값, 비즈니스 규칙, fallback, side effect, non-interaction을 검증한다.", - "`git-ranker`에서 `./gradlew test`가 통과한다." - ], - "clarifications": [ - { - "question": "이번 task의 테스트 범위를 어디까지 잠글까요?", - "answer": "핵심 피드백/검증 루프를 우선 완결하고, trivial DTO/config/entity/interface 전수 테스트는 제외합니다.", - "decision": "핵심 피드백/검증 루프와 그 주변의 기존 unit test 보강만 이번 task 범위로 잠근다.", - "resolved": true - }, - { - "question": "테스트 수준은 어디까지 허용할까요?", - "answer": "좁은 unit 중심으로 가고 외부 I/O와 전체 Spring context 부팅은 피합니다.", - "decision": "`JUnit 5 + Mockito` 중심의 좁은 unit test로 고정한다.", - "resolved": true - }, - { - "question": "기존 테스트 코드도 다시 검토하고 보강할까요?", - "answer": "네. 이미 있는 테스트도 다시 확인해 개선하거나 추가해야 할 케이스를 반영합니다.", - "decision": "신규 테스트 추가와 함께 기존 test file의 약한 assertion과 누락 분기를 보강한다.", - "resolved": true - }, - { - "question": "happy path만 작성하면 되나요?", - "answer": "아니요. 예외 케이스, 필요한 비즈니스 로직 검증, 놓치기 쉬운 케이스까지 모두 고려해 세분화합니다.", - "decision": "각 대상 로직은 정상 흐름, 예외, 경계값, fallback, side effect, non-interaction, invariant를 포함해 테스트한다.", - "resolved": true - } - ] - }, - "contract_version": 1, - "kickoff_required_for_phase": null, - "last_kickoff_run_id": "20260416T081909-3984baf2" -} diff --git a/workflows/tasks/task-harness-socratic-uncertainty-gate/phases.json b/workflows/tasks/task-harness-socratic-uncertainty-gate/phases.json new file mode 100644 index 0000000..fbe091a --- /dev/null +++ b/workflows/tasks/task-harness-socratic-uncertainty-gate/phases.json @@ -0,0 +1,101 @@ +{ + "task_id": "task-harness-socratic-uncertainty-gate", + "generated_at": "2026-04-17T03:01:29+00:00", + "phases": [ + { + "id": "phase-1", + "order": 1, + "title": "Implement Clarification Contract V3", + "goal": "Replace coverage-based spec readiness with status-based socratic clarification handling across runtime, docs, skills, and tests.", + "inputs": [ + "AGENTS.md", + "docs/artifact-model.md", + "docs/runtime.md", + "docs/runbook.md", + ".codex/skills/socratic-spec-authoring/SKILL.md", + "scripts/workflow_runtime/models.py", + "scripts/workflow_runtime/engine.py", + "scripts/workflow_runtime/templates.py", + "tests/test_workflow_cli.py" + ], + "required_reads": [ + "workflows/tasks/task-harness-socratic-uncertainty-gate/spec.md", + "workflows/tasks/task-harness-socratic-uncertainty-gate/task.json", + "AGENTS.md", + "docs/runtime.md", + "docs/artifact-model.md" + ], + "starting_points": [ + "Replace coverage/category parsing with Status: open|resolved parsing.", + "Update status/approve/readiness output and locked intake schema.", + "Align AGENTS/docs/skills/templates/tests with the new contract." + ], + "deliverables": [ + "Runtime and tests enforce contract_version 3 clarification behavior.", + "Docs, templates, and skills describe the new open/resolved flow." + ], + "completion_signal": "workflow CLI tests pass under the new clarification contract.", + "allowed_write_paths": [ + "AGENTS.md", + ".codex/skills/", + "docs/", + "scripts/", + "tests/" + ], + "acceptance": { + "commands": [ + "python3 -m unittest tests.test_workflow_cli -v" + ] + }, + "test_policy": { + "mode": "require_tests", + "evidence": [] + }, + "status": "completed", + "retry_count": 0 + }, + { + "id": "phase-2", + "order": 2, + "title": "Clean Task Artifacts", + "goal": "Remove old task artifacts so only the current harness task remains in workflows/tasks, then verify workspace health.", + "inputs": [ + "workflows/tasks/" + ], + "required_reads": [ + "workflows/tasks/task-harness-socratic-uncertainty-gate/spec.md", + "workflows/tasks/task-harness-socratic-uncertainty-gate/task.json", + "workflows/tasks/task-harness-socratic-uncertainty-gate/phases.json", + "docs/runtime.md" + ], + "starting_points": [ + "Keep task-harness-socratic-uncertainty-gate and workflows/tasks/.gitkeep.", + "Delete the previously accumulated task directories after runtime/tests/docs verification is green.", + "Verify doctor and status --all --check pass with the cleaned task directory." + ], + "deliverables": [ + "Only task-harness-socratic-uncertainty-gate remains under workflows/tasks.", + "Workspace health commands pass after cleanup." + ], + "completion_signal": "doctor and status --all --check succeed after task cleanup.", + "allowed_write_paths": [ + "workflows/tasks/" + ], + "acceptance": { + "commands": [ + "python3 scripts/workflow.py doctor", + "python3 scripts/workflow.py status --all --check", + "python3 -m unittest tests.test_workflow_cli -v" + ] + }, + "test_policy": { + "mode": "evidence_only", + "evidence": [ + "Task artifact cleanup does not change product/runtime behavior; workspace health and workflow CLI tests are the required evidence." + ] + }, + "status": "completed", + "retry_count": 2 + } + ] +} diff --git a/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T030141-d9dca29c.json b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T030141-d9dca29c.json new file mode 100644 index 0000000..8c93865 --- /dev/null +++ b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T030141-d9dca29c.json @@ -0,0 +1,18 @@ +{ + "id": "20260417T030141-d9dca29c", + "task_id": "task-harness-socratic-uncertainty-gate", + "phase_id": "phase-1", + "event": "phase_completion", + "commands": [ + { + "command": "phase completion", + "status": "passed", + "output": ".codex/skills/README.md, .codex/skills/socratic-spec-authoring/SKILL.md, AGENTS.md, docs/artifact-model.md, docs/runbook.md, docs/runtime.md, scripts/workflow_runtime/constants.py, scripts/workflow_runtime/engine.py, scripts/workflow_runtime/models.py, scripts/workflow_runtime/templates.py, tests/test_workflow_cli.py" + } + ], + "result": "passed", + "evidence": [], + "error_fingerprint": null, + "next_action": "verify", + "timestamp": "2026-04-17T03:01:41+00:00" +} diff --git a/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T030202-1e6a4f48.json b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T030202-1e6a4f48.json new file mode 100644 index 0000000..ea1c37e --- /dev/null +++ b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T030202-1e6a4f48.json @@ -0,0 +1,18 @@ +{ + "id": "20260417T030202-1e6a4f48", + "task_id": "task-harness-socratic-uncertainty-gate", + "phase_id": "phase-1", + "event": "verification", + "commands": [ + { + "command": "python3 -m unittest tests.test_workflow_cli -v", + "status": "passed", + "output": "test_approve_rejects_open_clarifications_before_locking_intake (tests.test_workflow_cli.WorkflowCliTest.test_approve_rejects_open_clarifications_before_locking_intake) ... ok\ntest_approve_requires_structured_socratic_log_and_locks_intake (tests.test_workflow_cli.WorkflowCliTest.test_approve_requires_structured_socratic_log_and_locks_intake) ... ok\ntest_check_fails_when_approved_task_points_to_completed_phase (tests.test_workflow_cli.WorkflowCliTest.test_check_fails_when_approved_task_points_to_completed_phase) ... ok\ntest_circuit_breaker_triggers_from_verification_failures (tests.test_workflow..." + } + ], + "result": "passed", + "evidence": [], + "error_fingerprint": null, + "next_action": "review", + "timestamp": "2026-04-17T03:02:02+00:00" +} diff --git a/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T040103-c45880eb.json b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T040103-c45880eb.json new file mode 100644 index 0000000..01fdfbb --- /dev/null +++ b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T040103-c45880eb.json @@ -0,0 +1,22 @@ +{ + "id": "20260417T040103-c45880eb", + "task_id": "task-harness-socratic-uncertainty-gate", + "phase_id": "phase-2", + "event": "phase_kickoff", + "commands": [ + { + "command": "phase kickoff", + "status": "passed", + "output": "kickoff recorded for phase-2" + } + ], + "result": "passed", + "evidence": [ + "required_reads: workflows/tasks/task-harness-socratic-uncertainty-gate/spec.md, workflows/tasks/task-harness-socratic-uncertainty-gate/task.json, workflows/tasks/task-harness-socratic-uncertainty-gate/phases.json, docs/runtime.md", + "starting_points: Keep task-harness-socratic-uncertainty-gate and workflows/tasks/.gitkeep., Delete the previously accumulated task directories after runtime/tests/docs verification is green., Verify doctor and status --all --check pass with the cleaned task directory.", + "completion_signal: doctor and status --all --check succeed after task cleanup." + ], + "error_fingerprint": null, + "next_action": "start phase", + "timestamp": "2026-04-17T04:01:03+00:00" +} diff --git a/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T040127-787baada.json b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T040127-787baada.json new file mode 100644 index 0000000..5fcf73f --- /dev/null +++ b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T040127-787baada.json @@ -0,0 +1,18 @@ +{ + "id": "20260417T040127-787baada", + "task_id": "task-harness-socratic-uncertainty-gate", + "phase_id": "phase-2", + "event": "phase_completion", + "commands": [ + { + "command": "phase completion", + "status": "passed", + "output": "workflows/tasks/task-gitranker-core-loop-unit-tests, workflows/tasks/task-gitranker-unit-test-audit-hardening, workflows/tasks/task-main-branch-hook-exemption, workflows/tasks/task-socratic-coverage-phase-kickoff" + } + ], + "result": "passed", + "evidence": [], + "error_fingerprint": null, + "next_action": "verify", + "timestamp": "2026-04-17T04:01:27+00:00" +} diff --git a/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T040127-9fd2d311.json b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T040127-9fd2d311.json new file mode 100644 index 0000000..a3623ad --- /dev/null +++ b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T040127-9fd2d311.json @@ -0,0 +1,18 @@ +{ + "id": "20260417T040127-9fd2d311", + "task_id": "task-harness-socratic-uncertainty-gate", + "phase_id": "phase-2", + "event": "verification", + "commands": [ + { + "command": "python3 scripts/workflow.py doctor", + "status": "failed", + "output": "{\n \"status\": \"failed\",\n \"checks\": [\n \"workflow system hook config is readable\",\n \"git core.hooksPath points to .githooks\",\n \"task artifacts are internally consistent\",\n \"runtime surface matches source\",\n \"AGENTS constitution is complete\",\n \"no stale legacy references remain in control-plane files\"\n ],\n \"errors\": [\n \"docs/runtime.md missing required marker: Status: open\"\n ]\n}" + } + ], + "result": "failed", + "evidence": [], + "error_fingerprint": "verification:phase-2:python3 scripts/workflow.py doctor", + "next_action": "repair", + "timestamp": "2026-04-17T04:01:27+00:00" +} diff --git a/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T040147-4ec2855b.json b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T040147-4ec2855b.json new file mode 100644 index 0000000..6454403 --- /dev/null +++ b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T040147-4ec2855b.json @@ -0,0 +1,20 @@ +{ + "id": "20260417T040147-4ec2855b", + "task_id": "task-harness-socratic-uncertainty-gate", + "phase_id": "phase-1", + "event": "reopened", + "commands": [ + { + "command": "reopen task", + "status": "passed", + "output": "doctor marker repair for runtime docs" + } + ], + "result": "passed", + "evidence": [ + "doctor marker repair for runtime docs" + ], + "error_fingerprint": null, + "next_action": "plan or run", + "timestamp": "2026-04-17T04:01:47+00:00" +} diff --git a/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T083433-4c0843c3.json b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T040312-731d1ca5.json similarity index 54% rename from workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T083433-4c0843c3.json rename to workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T040312-731d1ca5.json index 6b9bcce..8dbf8de 100644 --- a/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T083433-4c0843c3.json +++ b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T040312-731d1ca5.json @@ -1,18 +1,18 @@ { - "id": "20260416T083433-4c0843c3", - "task_id": "task-socratic-coverage-phase-kickoff", - "phase_id": "phase-3", + "id": "20260417T040312-731d1ca5", + "task_id": "task-harness-socratic-uncertainty-gate", + "phase_id": "phase-1", "event": "phase_completion", "commands": [ { "command": "phase completion", "status": "passed", - "output": "git-ranker" + "output": "docs/runtime.md" } ], "result": "passed", "evidence": [], "error_fingerprint": null, "next_action": "verify", - "timestamp": "2026-04-16T08:34:33+00:00" + "timestamp": "2026-04-17T04:03:12+00:00" } diff --git a/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T040334-758213ca.json b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T040334-758213ca.json new file mode 100644 index 0000000..8fc40fd --- /dev/null +++ b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T040334-758213ca.json @@ -0,0 +1,18 @@ +{ + "id": "20260417T040334-758213ca", + "task_id": "task-harness-socratic-uncertainty-gate", + "phase_id": "phase-1", + "event": "verification", + "commands": [ + { + "command": "python3 -m unittest tests.test_workflow_cli -v", + "status": "passed", + "output": "test_approve_rejects_open_clarifications_before_locking_intake (tests.test_workflow_cli.WorkflowCliTest.test_approve_rejects_open_clarifications_before_locking_intake) ... ok\ntest_approve_requires_structured_socratic_log_and_locks_intake (tests.test_workflow_cli.WorkflowCliTest.test_approve_requires_structured_socratic_log_and_locks_intake) ... ok\ntest_check_fails_when_approved_task_points_to_completed_phase (tests.test_workflow_cli.WorkflowCliTest.test_check_fails_when_approved_task_points_to_completed_phase) ... ok\ntest_circuit_breaker_triggers_from_verification_failures (tests.test_workflow..." + } + ], + "result": "passed", + "evidence": [], + "error_fingerprint": null, + "next_action": "review", + "timestamp": "2026-04-17T04:03:34+00:00" +} diff --git a/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T040744-867e760e.json b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T040744-867e760e.json new file mode 100644 index 0000000..08de654 --- /dev/null +++ b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T040744-867e760e.json @@ -0,0 +1,20 @@ +{ + "id": "20260417T040744-867e760e", + "task_id": "task-harness-socratic-uncertainty-gate", + "phase_id": "phase-2", + "event": "phase_failure", + "commands": [ + { + "command": "phase failure", + "status": "failed", + "output": "re-enter repair loop after upstream reopen fix" + } + ], + "result": "failed", + "evidence": [ + "re-enter repair loop after upstream reopen fix" + ], + "error_fingerprint": null, + "next_action": "repair or retry", + "timestamp": "2026-04-17T04:07:44+00:00" +} diff --git a/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T040749-d5195056.json b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T040749-d5195056.json new file mode 100644 index 0000000..6065d58 --- /dev/null +++ b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T040749-d5195056.json @@ -0,0 +1,20 @@ +{ + "id": "20260417T040749-d5195056", + "task_id": "task-harness-socratic-uncertainty-gate", + "phase_id": "phase-1", + "event": "reopened", + "commands": [ + { + "command": "reopen task", + "status": "passed", + "output": "repair upstream reopen semantics" + } + ], + "result": "passed", + "evidence": [ + "repair upstream reopen semantics" + ], + "error_fingerprint": null, + "next_action": "plan or run", + "timestamp": "2026-04-17T04:07:49+00:00" +} diff --git a/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T040755-5134e8bf.json b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T040755-5134e8bf.json new file mode 100644 index 0000000..a442fab --- /dev/null +++ b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T040755-5134e8bf.json @@ -0,0 +1,18 @@ +{ + "id": "20260417T040755-5134e8bf", + "task_id": "task-harness-socratic-uncertainty-gate", + "phase_id": "phase-1", + "event": "phase_completion", + "commands": [ + { + "command": "phase completion", + "status": "passed", + "output": ".codex/skills/repair-reopen/SKILL.md, AGENTS.md, docs/runbook.md, docs/runtime.md, scripts/workflow_runtime/engine.py, tests/test_workflow_cli.py" + } + ], + "result": "passed", + "evidence": [], + "error_fingerprint": null, + "next_action": "verify", + "timestamp": "2026-04-17T04:07:55+00:00" +} diff --git a/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T040819-45e59633.json b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T040819-45e59633.json new file mode 100644 index 0000000..2bfd603 --- /dev/null +++ b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T040819-45e59633.json @@ -0,0 +1,18 @@ +{ + "id": "20260417T040819-45e59633", + "task_id": "task-harness-socratic-uncertainty-gate", + "phase_id": "phase-1", + "event": "verification", + "commands": [ + { + "command": "python3 -m unittest tests.test_workflow_cli -v", + "status": "passed", + "output": "test_approve_rejects_open_clarifications_before_locking_intake (tests.test_workflow_cli.WorkflowCliTest.test_approve_rejects_open_clarifications_before_locking_intake) ... ok\ntest_approve_requires_structured_socratic_log_and_locks_intake (tests.test_workflow_cli.WorkflowCliTest.test_approve_requires_structured_socratic_log_and_locks_intake) ... ok\ntest_check_fails_when_approved_task_points_to_completed_phase (tests.test_workflow_cli.WorkflowCliTest.test_check_fails_when_approved_task_points_to_completed_phase) ... ok\ntest_circuit_breaker_triggers_from_verification_failures (tests.test_workflow..." + } + ], + "result": "passed", + "evidence": [], + "error_fingerprint": null, + "next_action": "review", + "timestamp": "2026-04-17T04:08:19+00:00" +} diff --git a/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T040833-c076ca07.json b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T040833-c076ca07.json new file mode 100644 index 0000000..0a3d8ee --- /dev/null +++ b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T040833-c076ca07.json @@ -0,0 +1,22 @@ +{ + "id": "20260417T040833-c076ca07", + "task_id": "task-harness-socratic-uncertainty-gate", + "phase_id": "phase-2", + "event": "phase_kickoff", + "commands": [ + { + "command": "phase kickoff", + "status": "passed", + "output": "kickoff recorded for phase-2" + } + ], + "result": "passed", + "evidence": [ + "required_reads: workflows/tasks/task-harness-socratic-uncertainty-gate/spec.md, workflows/tasks/task-harness-socratic-uncertainty-gate/task.json, workflows/tasks/task-harness-socratic-uncertainty-gate/phases.json, docs/runtime.md", + "starting_points: Keep task-harness-socratic-uncertainty-gate and workflows/tasks/.gitkeep., Delete the previously accumulated task directories after runtime/tests/docs verification is green., Verify doctor and status --all --check pass with the cleaned task directory.", + "completion_signal: doctor and status --all --check succeed after task cleanup." + ], + "error_fingerprint": null, + "next_action": "start phase", + "timestamp": "2026-04-17T04:08:33+00:00" +} diff --git a/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T040839-66a23490.json b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T040839-66a23490.json new file mode 100644 index 0000000..daaf578 --- /dev/null +++ b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T040839-66a23490.json @@ -0,0 +1,18 @@ +{ + "id": "20260417T040839-66a23490", + "task_id": "task-harness-socratic-uncertainty-gate", + "phase_id": "phase-2", + "event": "phase_completion", + "commands": [ + { + "command": "phase completion", + "status": "passed", + "output": "workflows/tasks/task-gitranker-core-loop-unit-tests, workflows/tasks/task-main-branch-hook-exemption, workflows/tasks/task-socratic-coverage-phase-kickoff" + } + ], + "result": "passed", + "evidence": [], + "error_fingerprint": null, + "next_action": "verify", + "timestamp": "2026-04-17T04:08:39+00:00" +} diff --git a/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T040904-28b92d08.json b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T040904-28b92d08.json new file mode 100644 index 0000000..738a0b2 --- /dev/null +++ b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T040904-28b92d08.json @@ -0,0 +1,28 @@ +{ + "id": "20260417T040904-28b92d08", + "task_id": "task-harness-socratic-uncertainty-gate", + "phase_id": "phase-2", + "event": "verification", + "commands": [ + { + "command": "python3 scripts/workflow.py doctor", + "status": "passed", + "output": "{\n \"status\": \"passed\",\n \"checks\": [\n \"workflow system hook config is readable\",\n \"git core.hooksPath points to .githooks\",\n \"task artifacts are internally consistent\",\n \"runtime surface matches source\",\n \"AGENTS constitution is complete\",\n \"no stale legacy references remain in control-plane files\"\n ],\n \"errors\": []\n}" + }, + { + "command": "python3 scripts/workflow.py status --all --check", + "status": "passed", + "output": "{\n \"status\": \"passed\",\n \"tasks\": [\n \"task-harness-socratic-uncertainty-gate\"\n ]\n}" + }, + { + "command": "python3 -m unittest tests.test_workflow_cli -v", + "status": "passed", + "output": "test_approve_rejects_open_clarifications_before_locking_intake (tests.test_workflow_cli.WorkflowCliTest.test_approve_rejects_open_clarifications_before_locking_intake) ... ok\ntest_approve_requires_structured_socratic_log_and_locks_intake (tests.test_workflow_cli.WorkflowCliTest.test_approve_requires_structured_socratic_log_and_locks_intake) ... ok\ntest_check_fails_when_approved_task_points_to_completed_phase (tests.test_workflow_cli.WorkflowCliTest.test_check_fails_when_approved_task_points_to_completed_phase) ... ok\ntest_circuit_breaker_triggers_from_verification_failures (tests.test_workflow..." + } + ], + "result": "passed", + "evidence": [], + "error_fingerprint": null, + "next_action": "review", + "timestamp": "2026-04-17T04:09:05+00:00" +} diff --git a/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T040912-7d91b7f6.json b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T040912-7d91b7f6.json new file mode 100644 index 0000000..dd449ae --- /dev/null +++ b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T040912-7d91b7f6.json @@ -0,0 +1,20 @@ +{ + "id": "20260417T040912-7d91b7f6", + "task_id": "task-harness-socratic-uncertainty-gate", + "phase_id": "phase-2", + "event": "review_ready", + "commands": [ + { + "command": "review gate", + "status": "passed", + "output": "latest passed verification: 20260417T040904-28b92d08" + } + ], + "result": "passed", + "evidence": [ + "clarification contract v3 and repair loop cleanup verified" + ], + "error_fingerprint": null, + "next_action": "user validation", + "timestamp": "2026-04-17T04:09:12+00:00" +} diff --git a/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T041248-ed573b82.json b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T041248-ed573b82.json new file mode 100644 index 0000000..70a2e7f --- /dev/null +++ b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T041248-ed573b82.json @@ -0,0 +1,20 @@ +{ + "id": "20260417T041248-ed573b82", + "task_id": "task-harness-socratic-uncertainty-gate", + "phase_id": "phase-2", + "event": "review_closeout", + "commands": [ + { + "command": "complete task", + "status": "passed", + "output": "사용자 validation: 네 진행해주세요." + } + ], + "result": "passed", + "evidence": [ + "사용자 validation: 네 진행해주세요." + ], + "error_fingerprint": null, + "next_action": "done", + "timestamp": "2026-04-17T04:12:48+00:00" +} diff --git a/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T042922-a99713b7.json b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T042922-a99713b7.json new file mode 100644 index 0000000..2314562 --- /dev/null +++ b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T042922-a99713b7.json @@ -0,0 +1,20 @@ +{ + "id": "20260417T042922-a99713b7", + "task_id": "task-harness-socratic-uncertainty-gate", + "phase_id": "phase-1", + "event": "reopened", + "commands": [ + { + "command": "reopen task", + "status": "passed", + "output": "PR review follow-up: restore legacy intake compatibility" + } + ], + "result": "passed", + "evidence": [ + "PR review follow-up: restore legacy intake compatibility" + ], + "error_fingerprint": null, + "next_action": "plan or run", + "timestamp": "2026-04-17T04:29:22+00:00" +} diff --git a/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T043212-ec57c7ea.json b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T043212-ec57c7ea.json new file mode 100644 index 0000000..a78de3e --- /dev/null +++ b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T043212-ec57c7ea.json @@ -0,0 +1,18 @@ +{ + "id": "20260417T043212-ec57c7ea", + "task_id": "task-harness-socratic-uncertainty-gate", + "phase_id": "phase-1", + "event": "phase_completion", + "commands": [ + { + "command": "phase completion", + "status": "passed", + "output": "scripts/workflow_runtime/engine.py, scripts/workflow_runtime/models.py, tests/test_workflow_cli.py" + } + ], + "result": "passed", + "evidence": [], + "error_fingerprint": null, + "next_action": "verify", + "timestamp": "2026-04-17T04:32:12+00:00" +} diff --git a/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T043235-106b62f0.json b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T043235-106b62f0.json new file mode 100644 index 0000000..7ffd4b1 --- /dev/null +++ b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T043235-106b62f0.json @@ -0,0 +1,18 @@ +{ + "id": "20260417T043235-106b62f0", + "task_id": "task-harness-socratic-uncertainty-gate", + "phase_id": "phase-1", + "event": "verification", + "commands": [ + { + "command": "python3 -m unittest tests.test_workflow_cli -v", + "status": "passed", + "output": "test_approve_rejects_open_clarifications_before_locking_intake (tests.test_workflow_cli.WorkflowCliTest.test_approve_rejects_open_clarifications_before_locking_intake) ... ok\ntest_approve_requires_structured_socratic_log_and_locks_intake (tests.test_workflow_cli.WorkflowCliTest.test_approve_requires_structured_socratic_log_and_locks_intake) ... ok\ntest_check_fails_when_approved_task_points_to_completed_phase (tests.test_workflow_cli.WorkflowCliTest.test_check_fails_when_approved_task_points_to_completed_phase) ... ok\ntest_circuit_breaker_triggers_from_verification_failures (tests.test_workflow..." + } + ], + "result": "passed", + "evidence": [], + "error_fingerprint": null, + "next_action": "review", + "timestamp": "2026-04-17T04:32:35+00:00" +} diff --git a/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T043247-bd03edcd.json b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T043247-bd03edcd.json new file mode 100644 index 0000000..eb74100 --- /dev/null +++ b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T043247-bd03edcd.json @@ -0,0 +1,22 @@ +{ + "id": "20260417T043247-bd03edcd", + "task_id": "task-harness-socratic-uncertainty-gate", + "phase_id": "phase-2", + "event": "phase_kickoff", + "commands": [ + { + "command": "phase kickoff", + "status": "passed", + "output": "kickoff recorded for phase-2" + } + ], + "result": "passed", + "evidence": [ + "required_reads: workflows/tasks/task-harness-socratic-uncertainty-gate/spec.md, workflows/tasks/task-harness-socratic-uncertainty-gate/task.json, workflows/tasks/task-harness-socratic-uncertainty-gate/phases.json, docs/runtime.md", + "starting_points: Keep task-harness-socratic-uncertainty-gate and workflows/tasks/.gitkeep., Delete the previously accumulated task directories after runtime/tests/docs verification is green., Verify doctor and status --all --check pass with the cleaned task directory.", + "completion_signal: doctor and status --all --check succeed after task cleanup." + ], + "error_fingerprint": null, + "next_action": "start phase", + "timestamp": "2026-04-17T04:32:47+00:00" +} diff --git a/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T043256-ccb4d344.json b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T043256-ccb4d344.json new file mode 100644 index 0000000..012a897 --- /dev/null +++ b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T043256-ccb4d344.json @@ -0,0 +1,18 @@ +{ + "id": "20260417T043256-ccb4d344", + "task_id": "task-harness-socratic-uncertainty-gate", + "phase_id": "phase-2", + "event": "phase_completion", + "commands": [ + { + "command": "phase completion", + "status": "passed", + "output": "workflows/tasks/task-harness-socratic-uncertainty-gate" + } + ], + "result": "passed", + "evidence": [], + "error_fingerprint": null, + "next_action": "verify", + "timestamp": "2026-04-17T04:32:56+00:00" +} diff --git a/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T043319-e2c0ab27.json b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T043319-e2c0ab27.json new file mode 100644 index 0000000..0ec263b --- /dev/null +++ b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T043319-e2c0ab27.json @@ -0,0 +1,28 @@ +{ + "id": "20260417T043319-e2c0ab27", + "task_id": "task-harness-socratic-uncertainty-gate", + "phase_id": "phase-2", + "event": "verification", + "commands": [ + { + "command": "python3 scripts/workflow.py doctor", + "status": "passed", + "output": "{\n \"status\": \"passed\",\n \"checks\": [\n \"workflow system hook config is readable\",\n \"git core.hooksPath points to .githooks\",\n \"task artifacts are internally consistent\",\n \"runtime surface matches source\",\n \"AGENTS constitution is complete\",\n \"no stale legacy references remain in control-plane files\"\n ],\n \"errors\": []\n}" + }, + { + "command": "python3 scripts/workflow.py status --all --check", + "status": "passed", + "output": "{\n \"status\": \"passed\",\n \"tasks\": [\n \"task-harness-socratic-uncertainty-gate\"\n ]\n}" + }, + { + "command": "python3 -m unittest tests.test_workflow_cli -v", + "status": "passed", + "output": "test_approve_rejects_open_clarifications_before_locking_intake (tests.test_workflow_cli.WorkflowCliTest.test_approve_rejects_open_clarifications_before_locking_intake) ... ok\ntest_approve_requires_structured_socratic_log_and_locks_intake (tests.test_workflow_cli.WorkflowCliTest.test_approve_requires_structured_socratic_log_and_locks_intake) ... ok\ntest_check_fails_when_approved_task_points_to_completed_phase (tests.test_workflow_cli.WorkflowCliTest.test_check_fails_when_approved_task_points_to_completed_phase) ... ok\ntest_circuit_breaker_triggers_from_verification_failures (tests.test_workflow..." + } + ], + "result": "passed", + "evidence": [], + "error_fingerprint": null, + "next_action": "review", + "timestamp": "2026-04-17T04:33:19+00:00" +} diff --git a/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T043324-82195f22.json b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T043324-82195f22.json new file mode 100644 index 0000000..a432c83 --- /dev/null +++ b/workflows/tasks/task-harness-socratic-uncertainty-gate/runs/20260417T043324-82195f22.json @@ -0,0 +1,20 @@ +{ + "id": "20260417T043324-82195f22", + "task_id": "task-harness-socratic-uncertainty-gate", + "phase_id": "phase-2", + "event": "review_ready", + "commands": [ + { + "command": "review gate", + "status": "passed", + "output": "latest passed verification: 20260417T043319-e2c0ab27" + } + ], + "result": "passed", + "evidence": [ + "legacy intake compatibility follow-up verified" + ], + "error_fingerprint": null, + "next_action": "user validation", + "timestamp": "2026-04-17T04:33:24+00:00" +} diff --git a/workflows/tasks/task-harness-socratic-uncertainty-gate/spec.md b/workflows/tasks/task-harness-socratic-uncertainty-gate/spec.md new file mode 100644 index 0000000..a51efe9 --- /dev/null +++ b/workflows/tasks/task-harness-socratic-uncertainty-gate/spec.md @@ -0,0 +1,159 @@ +# Require socratic clarification until uncertainty is resolved + +- Task ID: `task-harness-socratic-uncertainty-gate` +- Primary Repo: `git-ranker-workflow` +- Status: `draft` + +## Request + +- Harness의 소크라테스식 spec authoring을 고쳐, 단순한 5개 coverage 충족이 아니라 실제 불확실성이 없어질 때까지 질문이 계속되도록 만든다. +- Codex가 작업을 이해하고 phase를 설계하는 데 필요한 애매점, 미이해 지점, 사용자 확인이 필요한 판단 포인트를 발견할 때마다 질문을 추가할 수 있어야 한다. +- `approve`는 더 질문이 없고 열린 clarification이 없는 경우에만 가능해야 하며, 이 contract는 다음 task부터 기본 흐름으로 강제되어야 한다. + +## Problem + +- 현재 harness는 `Socratic Clarification Log`의 형식과 coverage category만 확인하고, 실제로 남은 불확실성이 있는지는 모델링하지 않는다. +- 그 결과 Codex가 구현 계획과 phase를 설계하기에 아직 애매한 상태여도 spec이 approval-ready로 보일 수 있다. +- 이 상태에서는 사용자와 Codex의 이해가 완전히 sync되지 않은 채 승인과 구현이 진행될 위험이 있다. + +## Goals + +- `Socratic Clarification Log`가 열린 질문과 해결된 질문을 명시적으로 표현할 수 있게 한다. +- Codex가 spec 작성 중 새 애매점을 발견하면 언제든 clarification을 추가하거나 기존 질문을 다시 열 수 있게 한다. +- `approve`와 `status`가 열린 clarification 존재 여부를 기준으로 readiness를 계산하게 만든다. +- 이번 task는 예외 bootstrap으로 진행하되, 구현 완료 후 다음 task부터는 새 clarification contract가 기본 규칙으로 동작하게 만든다. + +## Non-goals + +- 이번 작업은 앱 저장소의 기능 구현이나 테스트 보강 작업 자체를 수행하는 것이 아니다. +- clarification contract 변경과 직접 관련 없는 phase/runtime 전반의 대규모 재설계는 이번 범위가 아니다. +- 기존 task artifact를 보존한 채 하위 호환성을 폭넓게 유지하는 것은 목표가 아니다. + +## Constraints + +- 이번 task 자체는 새 contract를 도입하기 위한 bootstrap 예외로 진행하고, 다음 task부터 새 규칙을 기본 enforcement로 사용한다. +- `workflows/tasks/` 아래 기존 과거 task들은 정리 대상으로 보고, 현재 harness 변경 task만 남긴다. +- `Status: open` 질문이 하나라도 있으면 `approve`는 실패해야 한다. +- `open` 질문에는 `Decision:`이 있으면 안 되며, 아직 답을 못 받은 질문은 `A:` 없이 남길 수 있어야 한다. +- 기존 coverage category requirement는 제거하고 불확실성 관리 자체를 readiness 기준으로 삼는다. + +## Acceptance + +- `Socratic Clarification Log`가 `Status: open|resolved`를 이해하고, 열린 질문을 machine-readable 하게 판단할 수 있다. +- spec 작성 중 clarification을 추가하거나 기존 질문을 다시 `open`으로 되돌릴 수 있는 contract와 parser 규칙이 문서/코드/테스트에 반영된다. +- `approve`는 열린 clarification이 없을 때만 성공하고, readiness 계산도 같은 기준을 사용한다. +- 이번 task 완료 후 다음 task부터는 새 clarification contract를 기본 흐름으로 사용할 수 있다. +- `workflows/tasks/` 정리 정책이 이번 spec에 반영되고, 구현 단계에서 현재 harness task만 남기는 방향으로 수행된다. + +## Socratic Clarification Log + +- Q: Harness가 이번 변경으로 강제해야 하는 핵심 동작은 무엇인가? +- A: Codex가 spec을 작성하면서 이해하지 못했거나 애매한 부분이 생길 때마다 질문을 계속하고, 열린 질문이 남아 있으면 승인할 수 없어야 한다. +- Decision: Harness는 coverage 충족이 아니라 남은 불확실성 유무를 approval gate의 핵심 기준으로 삼는다. +- Status: resolved + +- Q: 열린 질문과 해결된 질문은 spec artifact에서 어떻게 표현해야 하는가? +- A: `Socratic Clarification Log`를 확장해 각 질문에 `Status:`를 두고, `open`과 `resolved`를 명시적으로 구분해야 한다. +- Decision: clarification contract는 `Status: open|resolved`를 기본 상태 모델로 사용한다. +- Status: resolved + +- Q: 아직 답을 못 받은 질문과 해결된 질문의 문법은 어떻게 달라야 하는가? +- A: 아직 답을 못 받은 질문은 `Q:`와 `Status: open`만 있으면 되고 `A:`는 비어 있을 수 있다. 해결된 질문은 사용자 답변과 결정이 잠겨야 한다. +- Decision: `open` 질문은 `A:` 없이 남길 수 있고, `resolved` 질문만 `A:`와 `Decision:`을 갖는다. +- Status: resolved + +- Q: 기존 질문에서 후속 확인이 필요해지면 어떻게 다뤄야 하는가? +- A: 기존 질문을 업데이트하거나 새 질문을 추가해서 다시 `open`으로 되돌릴 수 있어야 한다. +- Decision: clarification은 고정 목록이 아니며, spec authoring 중 언제든 추가/수정/재오픈될 수 있다. +- Status: resolved + +- Q: readiness와 approve 차단 조건은 무엇인가? +- A: `Status: open` 질문이 하나라도 있으면 실패해야 하고, 내가 더 이상 질문이 없다고 판단했을 때만 최종 승인을 요청해야 한다. +- Decision: approval readiness는 열린 clarification이 0개일 때만 성립한다. +- Status: resolved + +- Q: 기존 coverage category requirement는 유지해야 하는가? +- A: 아니다. 핵심은 coverage 5개를 채우는 것이 아니라 불확실성 관리다. +- Decision: `[scope]`, `[goal]`, `[non_goal]`, `[constraint]`, `[acceptance]` coverage requirement는 제거한다. +- Status: resolved + +- Q: 기존에 쌓인 과거 task artifact는 어떻게 처리해야 하는가? +- A: `workflows/tasks/`의 과거 task들은 모두 제거하고, 현재 harness 변경 task만 남긴 뒤 다시 시작한다. +- Decision: 구현 단계에서 `task-harness-socratic-uncertainty-gate`만 남기고 나머지 task 디렉터리는 삭제 대상으로 본다. +- Status: resolved + +- Q: 이번 harness 변경 task 자체는 어떤 bootstrap 규칙으로 진행해야 하는가? +- A: 지금 작업은 다음 task부터 사용할 흐름을 설계하는 예외 작업이므로, 이번 task만 예외적으로 진행하고 다음 task부터 새 contract를 강제한다. +- Decision: 현재 task는 bootstrap 예외로 수행하고, 완료 후 다음 task부터 새 clarification contract를 기본 흐름으로 적용한다. +- Status: resolved + +- Q: 새 clarification contract에서 `status` 명령은 무엇을 보여줘야 하는가? +- A: approval readiness와 함께 전체 clarification 수, 열린 질문 수, 해결된 질문 수, 그리고 현재 열린 질문 목록까지 보여주는 것이 좋다. +- Decision: `status`는 `ready_for_approval`, `clarification_count`, `open_clarification_count`, `resolved_clarification_count`, `open_clarifications`, `validation_errors`를 노출하는 방향으로 설계한다. +- Status: resolved + +- Q: `Socratic Clarification Log`가 비어 있어도 승인 가능해야 하는가? +- A: 아니다. 최소 1개 이상의 clarification 항목은 있어야 하고, 질문 필요 여부를 검토했다는 기록이 남아야 한다. +- Decision: clarification이 0개인 spec은 approval-ready가 될 수 없도록 한다. +- Status: resolved + +- Q: 옛 clarification contract와 versioning은 어떻게 처리해야 하는가? +- A: coverage 기반 옛 규칙은 history를 남기지 않고 제거하되, `contract_version` 자체는 이후 변경 가능성을 위해 메타데이터로 유지하는 편이 좋다. +- Decision: runtime은 새 `Status: open|resolved` contract만 공식 지원하고, `contract_version` 필드는 유지한다. +- Status: resolved + +- Q: `Status: open`인 clarification에서 `A:`를 허용해야 하는가? +- A: 그렇다. 사용자가 일단 답을 줬지만 추가 확인이 필요해 아직 닫지 못한 상태를 표현할 수 있어야 한다. +- Decision: `open` clarification은 `A:`를 가질 수 있지만 `Decision:`은 가질 수 없도록 한다. +- Status: resolved + +- Q: clarification 항목의 문법 순서는 어떻게 고정해야 하는가? +- A: parser와 사람이 모두 안정적으로 읽을 수 있게 항목 순서를 고정하는 편이 좋다. +- Decision: `open`은 `Q:` → 선택적 `A:` → `Status: open`, `resolved`는 `Q:` → `A:` → `Decision:` → `Status: resolved` 순서로 고정하고 `Status:`는 각 clarification의 마지막 줄로 둔다. +- Status: resolved + +- Q: 기존 과거 task artifact 삭제는 언제 수행해야 하는가? +- A: 새 clarification contract 구현과 검증을 먼저 끝낸 뒤, 같은 작업의 마지막 단계에서 `workflows/tasks/` 정리를 수행하는 편이 안전하다. +- Decision: runtime/tests/docs 변경과 검증 완료 후 마지막 단계에서 `task-harness-socratic-uncertainty-gate`만 남기고 나머지 task 디렉터리를 삭제한다. +- Status: resolved + +- Q: `approve`가 clarification을 `task.json.intake`에 잠글 때 어떤 형태로 저장해야 하는가? +- A: category는 제거하고, 새 contract의 핵심 상태를 그대로 반영하도록 `question`, `answer`, `decision`, `status` 중심으로 잠그는 편이 좋다. +- Decision: `approve`는 `status=resolved` clarification만 `task.json.intake.clarifications`에 잠그고, clarification schema는 `question`, `answer`, `decision`, `status`를 기본 필드로 사용한다. +- Status: resolved + +- Q: 새 clarification contract의 `contract_version`은 얼마로 올려야 하는가? +- A: readiness 판단 방식과 clarification schema가 실제로 바뀌므로 새 major contract로 보는 편이 명확하다. +- Decision: `Status: open|resolved` 기반 clarification contract는 `contract_version = 3`으로 올린다. +- Status: resolved + +- Q: 승인 이후 `spec.md`에 새 `Status: open` 질문이 생기면 task lifecycle은 어떻게 동작해야 하는가? +- A: 이미 승인됐더라도 더 이상 실행 가능한 spec이 아니므로, 다시 질의 루프로 돌아가 질문을 닫고 재승인해야 한다. +- Decision: 승인 이후 `spec.md`에 새 `open` clarification이 생기면 `plan`/`run`/`verify`를 막고, `reopen` 후 clarification을 닫은 다음 `approve`를 다시 수행하도록 한다. +- Status: resolved + +- Q: 이번 bootstrap 예외 task는 구현 완료 후 어떤 흐름으로 닫아야 하는가? +- A: 예외적으로 시작하더라도 runtime이 준비된 뒤에는 현재 task도 새 contract 아래로 다시 편입해 정상적인 workflow로 닫는 편이 일관적이다. +- Decision: `task-harness-socratic-uncertainty-gate`는 bootstrap 예외로 시작하되, 구현 후에는 새 clarification contract로 `approve -> plan -> verify -> review/close` 흐름을 다시 타서 정상적으로 마무리한다. +- Status: resolved + +- Q: `Q:`, `A:`, `Decision:` 값은 한 줄로 제한해야 하는가? +- A: 그렇다. clarification log는 짧고 기계적으로 읽히는 형태를 유지하고, 상세 설명은 다른 spec section에 두는 편이 parser와 검증 모두 단순하다. +- Decision: `Q:`, `A:`, `Decision:` 값은 각각 한 줄 항목으로 제한한다. +- Status: resolved + +- Q: 새 task를 만들 때 기본 `spec.md` 템플릿의 clarification 예시는 어떻게 시작해야 하는가? +- A: 템플릿 자체가 새 contract 문법을 보여줘야 하므로, `Q:`와 `Status: open` 기반 placeholder 예시로 시작하는 편이 좋다. +- Decision: `new`가 생성하는 기본 `spec.md` 템플릿의 `Socratic Clarification Log`는 새 `Status:` 기반 placeholder 예시를 사용하도록 바꾼다. +- Status: resolved + +- Q: approval block과 `approve --note` 메타데이터 구조도 함께 바꿔야 하는가? +- A: 아니다. clarification model과 독립적인 메타데이터이므로 기존 `Actor/Timestamp/Note` 구조를 유지하는 편이 안전하다. +- Decision: approval 메타데이터 구조는 유지하고, 이번 변경 범위는 clarification readiness와 intake schema에 한정한다. +- Status: resolved + +## Approval + +- Actor: `user` +- Timestamp: `2026-04-17T03:00:46+00:00` +- Note: 새 clarification contract spec 승인 diff --git a/workflows/tasks/task-harness-socratic-uncertainty-gate/task.json b/workflows/tasks/task-harness-socratic-uncertainty-gate/task.json new file mode 100644 index 0000000..b45a100 --- /dev/null +++ b/workflows/tasks/task-harness-socratic-uncertainty-gate/task.json @@ -0,0 +1,179 @@ +{ + "id": "task-harness-socratic-uncertainty-gate", + "title": "Require socratic clarification until uncertainty is resolved", + "contract_version": 3, + "state": "review_ready", + "primary_repo": "git-ranker-workflow", + "created_at": "2026-04-17T01:56:40+00:00", + "approved_at": "2026-04-17T03:00:46+00:00", + "approval": { + "actor": "user", + "note": "새 clarification contract spec 승인", + "timestamp": "2026-04-17T03:00:46+00:00" + }, + "active_phase_id": "phase-2", + "latest_run_id": "20260417T043324-82195f22", + "last_verified_run_id": "20260417T043319-e2c0ab27", + "kickoff_required_for_phase": null, + "last_kickoff_run_id": "20260417T043247-bd03edcd", + "blocked_reason": null, + "user_validated": false, + "user_validation_note": null, + "intake": { + "request_summary": "Harness의 소크라테스식 spec authoring을 고쳐, 단순한 5개 coverage 충족이 아니라 실제 불확실성이 없어질 때까지 질문이 계속되도록 만든다. Codex가 작업을 이해하고 phase를 설계하는 데 필요한 애매점, 미이해 지점, 사용자 확인이 필요한 판단 포인트를 발견할 때마다 질문을 추가할 수 있어야 한다. `approve`는 더 질문이 없고 열린 clarification이 없는 경우에만 가능해야 하며, 이 contract는 다음 task부터 기본 흐름으로 강제되어야 한다.", + "problem_summary": "현재 harness는 `Socratic Clarification Log`의 형식과 coverage category만 확인하고, 실제로 남은 불확실성이 있는지는 모델링하지 않는다. 그 결과 Codex가 구현 계획과 phase를 설계하기에 아직 애매한 상태여도 spec이 approval-ready로 보일 수 있다. 이 상태에서는 사용자와 Codex의 이해가 완전히 sync되지 않은 채 승인과 구현이 진행될 위험이 있다.", + "goals": [ + "`Socratic Clarification Log`가 열린 질문과 해결된 질문을 명시적으로 표현할 수 있게 한다.", + "Codex가 spec 작성 중 새 애매점을 발견하면 언제든 clarification을 추가하거나 기존 질문을 다시 열 수 있게 한다.", + "`approve`와 `status`가 열린 clarification 존재 여부를 기준으로 readiness를 계산하게 만든다.", + "이번 task는 예외 bootstrap으로 진행하되, 구현 완료 후 다음 task부터는 새 clarification contract가 기본 규칙으로 동작하게 만든다." + ], + "non_goals": [ + "이번 작업은 앱 저장소의 기능 구현이나 테스트 보강 작업 자체를 수행하는 것이 아니다.", + "clarification contract 변경과 직접 관련 없는 phase/runtime 전반의 대규모 재설계는 이번 범위가 아니다.", + "기존 task artifact를 보존한 채 하위 호환성을 폭넓게 유지하는 것은 목표가 아니다." + ], + "constraints": [ + "이번 task 자체는 새 contract를 도입하기 위한 bootstrap 예외로 진행하고, 다음 task부터 새 규칙을 기본 enforcement로 사용한다.", + "`workflows/tasks/` 아래 기존 과거 task들은 정리 대상으로 보고, 현재 harness 변경 task만 남긴다.", + "`Status: open` 질문이 하나라도 있으면 `approve`는 실패해야 한다.", + "`open` 질문에는 `Decision:`이 있으면 안 되며, 아직 답을 못 받은 질문은 `A:` 없이 남길 수 있어야 한다.", + "기존 coverage category requirement는 제거하고 불확실성 관리 자체를 readiness 기준으로 삼는다." + ], + "acceptance": [ + "`Socratic Clarification Log`가 `Status: open|resolved`를 이해하고, 열린 질문을 machine-readable 하게 판단할 수 있다.", + "spec 작성 중 clarification을 추가하거나 기존 질문을 다시 `open`으로 되돌릴 수 있는 contract와 parser 규칙이 문서/코드/테스트에 반영된다.", + "`approve`는 열린 clarification이 없을 때만 성공하고, readiness 계산도 같은 기준을 사용한다.", + "이번 task 완료 후 다음 task부터는 새 clarification contract를 기본 흐름으로 사용할 수 있다.", + "`workflows/tasks/` 정리 정책이 이번 spec에 반영되고, 구현 단계에서 현재 harness task만 남기는 방향으로 수행된다." + ], + "clarifications": [ + { + "question": "Harness가 이번 변경으로 강제해야 하는 핵심 동작은 무엇인가?", + "answer": "Codex가 spec을 작성하면서 이해하지 못했거나 애매한 부분이 생길 때마다 질문을 계속하고, 열린 질문이 남아 있으면 승인할 수 없어야 한다.", + "decision": "Harness는 coverage 충족이 아니라 남은 불확실성 유무를 approval gate의 핵심 기준으로 삼는다.", + "status": "resolved" + }, + { + "question": "열린 질문과 해결된 질문은 spec artifact에서 어떻게 표현해야 하는가?", + "answer": "`Socratic Clarification Log`를 확장해 각 질문에 `Status:`를 두고, `open`과 `resolved`를 명시적으로 구분해야 한다.", + "decision": "clarification contract는 `Status: open|resolved`를 기본 상태 모델로 사용한다.", + "status": "resolved" + }, + { + "question": "아직 답을 못 받은 질문과 해결된 질문의 문법은 어떻게 달라야 하는가?", + "answer": "아직 답을 못 받은 질문은 `Q:`와 `Status: open`만 있으면 되고 `A:`는 비어 있을 수 있다. 해결된 질문은 사용자 답변과 결정이 잠겨야 한다.", + "decision": "`open` 질문은 `A:` 없이 남길 수 있고, `resolved` 질문만 `A:`와 `Decision:`을 갖는다.", + "status": "resolved" + }, + { + "question": "기존 질문에서 후속 확인이 필요해지면 어떻게 다뤄야 하는가?", + "answer": "기존 질문을 업데이트하거나 새 질문을 추가해서 다시 `open`으로 되돌릴 수 있어야 한다.", + "decision": "clarification은 고정 목록이 아니며, spec authoring 중 언제든 추가/수정/재오픈될 수 있다.", + "status": "resolved" + }, + { + "question": "readiness와 approve 차단 조건은 무엇인가?", + "answer": "`Status: open` 질문이 하나라도 있으면 실패해야 하고, 내가 더 이상 질문이 없다고 판단했을 때만 최종 승인을 요청해야 한다.", + "decision": "approval readiness는 열린 clarification이 0개일 때만 성립한다.", + "status": "resolved" + }, + { + "question": "기존 coverage category requirement는 유지해야 하는가?", + "answer": "아니다. 핵심은 coverage 5개를 채우는 것이 아니라 불확실성 관리다.", + "decision": "`[scope]`, `[goal]`, `[non_goal]`, `[constraint]`, `[acceptance]` coverage requirement는 제거한다.", + "status": "resolved" + }, + { + "question": "기존에 쌓인 과거 task artifact는 어떻게 처리해야 하는가?", + "answer": "`workflows/tasks/`의 과거 task들은 모두 제거하고, 현재 harness 변경 task만 남긴 뒤 다시 시작한다.", + "decision": "구현 단계에서 `task-harness-socratic-uncertainty-gate`만 남기고 나머지 task 디렉터리는 삭제 대상으로 본다.", + "status": "resolved" + }, + { + "question": "이번 harness 변경 task 자체는 어떤 bootstrap 규칙으로 진행해야 하는가?", + "answer": "지금 작업은 다음 task부터 사용할 흐름을 설계하는 예외 작업이므로, 이번 task만 예외적으로 진행하고 다음 task부터 새 contract를 강제한다.", + "decision": "현재 task는 bootstrap 예외로 수행하고, 완료 후 다음 task부터 새 clarification contract를 기본 흐름으로 적용한다.", + "status": "resolved" + }, + { + "question": "새 clarification contract에서 `status` 명령은 무엇을 보여줘야 하는가?", + "answer": "approval readiness와 함께 전체 clarification 수, 열린 질문 수, 해결된 질문 수, 그리고 현재 열린 질문 목록까지 보여주는 것이 좋다.", + "decision": "`status`는 `ready_for_approval`, `clarification_count`, `open_clarification_count`, `resolved_clarification_count`, `open_clarifications`, `validation_errors`를 노출하는 방향으로 설계한다.", + "status": "resolved" + }, + { + "question": "`Socratic Clarification Log`가 비어 있어도 승인 가능해야 하는가?", + "answer": "아니다. 최소 1개 이상의 clarification 항목은 있어야 하고, 질문 필요 여부를 검토했다는 기록이 남아야 한다.", + "decision": "clarification이 0개인 spec은 approval-ready가 될 수 없도록 한다.", + "status": "resolved" + }, + { + "question": "옛 clarification contract와 versioning은 어떻게 처리해야 하는가?", + "answer": "coverage 기반 옛 규칙은 history를 남기지 않고 제거하되, `contract_version` 자체는 이후 변경 가능성을 위해 메타데이터로 유지하는 편이 좋다.", + "decision": "runtime은 새 `Status: open|resolved` contract만 공식 지원하고, `contract_version` 필드는 유지한다.", + "status": "resolved" + }, + { + "question": "`Status: open`인 clarification에서 `A:`를 허용해야 하는가?", + "answer": "그렇다. 사용자가 일단 답을 줬지만 추가 확인이 필요해 아직 닫지 못한 상태를 표현할 수 있어야 한다.", + "decision": "`open` clarification은 `A:`를 가질 수 있지만 `Decision:`은 가질 수 없도록 한다.", + "status": "resolved" + }, + { + "question": "clarification 항목의 문법 순서는 어떻게 고정해야 하는가?", + "answer": "parser와 사람이 모두 안정적으로 읽을 수 있게 항목 순서를 고정하는 편이 좋다.", + "decision": "`open`은 `Q:` → 선택적 `A:` → `Status: open`, `resolved`는 `Q:` → `A:` → `Decision:` → `Status: resolved` 순서로 고정하고 `Status:`는 각 clarification의 마지막 줄로 둔다.", + "status": "resolved" + }, + { + "question": "기존 과거 task artifact 삭제는 언제 수행해야 하는가?", + "answer": "새 clarification contract 구현과 검증을 먼저 끝낸 뒤, 같은 작업의 마지막 단계에서 `workflows/tasks/` 정리를 수행하는 편이 안전하다.", + "decision": "runtime/tests/docs 변경과 검증 완료 후 마지막 단계에서 `task-harness-socratic-uncertainty-gate`만 남기고 나머지 task 디렉터리를 삭제한다.", + "status": "resolved" + }, + { + "question": "`approve`가 clarification을 `task.json.intake`에 잠글 때 어떤 형태로 저장해야 하는가?", + "answer": "category는 제거하고, 새 contract의 핵심 상태를 그대로 반영하도록 `question`, `answer`, `decision`, `status` 중심으로 잠그는 편이 좋다.", + "decision": "`approve`는 `status=resolved` clarification만 `task.json.intake.clarifications`에 잠그고, clarification schema는 `question`, `answer`, `decision`, `status`를 기본 필드로 사용한다.", + "status": "resolved" + }, + { + "question": "새 clarification contract의 `contract_version`은 얼마로 올려야 하는가?", + "answer": "readiness 판단 방식과 clarification schema가 실제로 바뀌므로 새 major contract로 보는 편이 명확하다.", + "decision": "`Status: open|resolved` 기반 clarification contract는 `contract_version = 3`으로 올린다.", + "status": "resolved" + }, + { + "question": "승인 이후 `spec.md`에 새 `Status: open` 질문이 생기면 task lifecycle은 어떻게 동작해야 하는가?", + "answer": "이미 승인됐더라도 더 이상 실행 가능한 spec이 아니므로, 다시 질의 루프로 돌아가 질문을 닫고 재승인해야 한다.", + "decision": "승인 이후 `spec.md`에 새 `open` clarification이 생기면 `plan`/`run`/`verify`를 막고, `reopen` 후 clarification을 닫은 다음 `approve`를 다시 수행하도록 한다.", + "status": "resolved" + }, + { + "question": "이번 bootstrap 예외 task는 구현 완료 후 어떤 흐름으로 닫아야 하는가?", + "answer": "예외적으로 시작하더라도 runtime이 준비된 뒤에는 현재 task도 새 contract 아래로 다시 편입해 정상적인 workflow로 닫는 편이 일관적이다.", + "decision": "`task-harness-socratic-uncertainty-gate`는 bootstrap 예외로 시작하되, 구현 후에는 새 clarification contract로 `approve -> plan -> verify -> review/close` 흐름을 다시 타서 정상적으로 마무리한다.", + "status": "resolved" + }, + { + "question": "`Q:`, `A:`, `Decision:` 값은 한 줄로 제한해야 하는가?", + "answer": "그렇다. clarification log는 짧고 기계적으로 읽히는 형태를 유지하고, 상세 설명은 다른 spec section에 두는 편이 parser와 검증 모두 단순하다.", + "decision": "`Q:`, `A:`, `Decision:` 값은 각각 한 줄 항목으로 제한한다.", + "status": "resolved" + }, + { + "question": "새 task를 만들 때 기본 `spec.md` 템플릿의 clarification 예시는 어떻게 시작해야 하는가?", + "answer": "템플릿 자체가 새 contract 문법을 보여줘야 하므로, `Q:`와 `Status: open` 기반 placeholder 예시로 시작하는 편이 좋다.", + "decision": "`new`가 생성하는 기본 `spec.md` 템플릿의 `Socratic Clarification Log`는 새 `Status:` 기반 placeholder 예시를 사용하도록 바꾼다.", + "status": "resolved" + }, + { + "question": "approval block과 `approve --note` 메타데이터 구조도 함께 바꿔야 하는가?", + "answer": "아니다. clarification model과 독립적인 메타데이터이므로 기존 `Actor/Timestamp/Note` 구조를 유지하는 편이 안전하다.", + "decision": "approval 메타데이터 구조는 유지하고, 이번 변경 범위는 clarification readiness와 intake schema에 한정한다.", + "status": "resolved" + } + ] + } +} diff --git a/workflows/tasks/task-main-branch-hook-exemption/phases.json b/workflows/tasks/task-main-branch-hook-exemption/phases.json deleted file mode 100644 index 479b23f..0000000 --- a/workflows/tasks/task-main-branch-hook-exemption/phases.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "task_id": "task-main-branch-hook-exemption", - "generated_at": "2026-04-16T04:36:44+00:00", - "phases": [ - { - "id": "phase-1", - "title": "branch-aware-main-publish-hook", - "goal": "Allow main sync publishes to bypass task-scoped pre-push enforcement only when main tip matches develop tip, while keeping other hook behavior unchanged.", - "inputs": [ - "workflows/tasks/task-main-branch-hook-exemption/spec.md", - "docs/hooks.md", - "tests/test_workflow_cli.py" - ], - "allowed_write_paths": [ - ".githooks/", - "docs/", - "tests/", - ".codex/skills/", - "AGENTS.md" - ], - "acceptance": { - "commands": [ - "python3 -m unittest tests.test_workflow_cli -v", - "python3 scripts/workflow.py doctor" - ] - }, - "test_policy": { - "mode": "require_tests", - "evidence": [] - }, - "order": 1, - "status": "completed", - "retry_count": 0 - } - ] -} diff --git a/workflows/tasks/task-main-branch-hook-exemption/runs/20260416T044014-921f70aa.json b/workflows/tasks/task-main-branch-hook-exemption/runs/20260416T044014-921f70aa.json deleted file mode 100644 index ee51042..0000000 --- a/workflows/tasks/task-main-branch-hook-exemption/runs/20260416T044014-921f70aa.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "id": "20260416T044014-921f70aa", - "task_id": "task-main-branch-hook-exemption", - "phase_id": "phase-1", - "event": "phase_completion", - "commands": [ - { - "command": "phase completion", - "status": "passed", - "output": ".codex/skills/README.md, .githooks/pre-push, AGENTS.md, docs/hooks.md, docs/runbook.md, tests/test_workflow_cli.py, workflows/tasks/task-main-branch-hook-exemption/phases.json, workflows/tasks/task-main-branch-hook-exemption/spec.md, workflows/tasks/task-main-branch-hook-exemption/task.json" - } - ], - "result": "passed", - "evidence": [], - "error_fingerprint": null, - "next_action": "verify", - "timestamp": "2026-04-16T04:40:14+00:00" -} diff --git a/workflows/tasks/task-main-branch-hook-exemption/runs/20260416T044031-46f9f315.json b/workflows/tasks/task-main-branch-hook-exemption/runs/20260416T044031-46f9f315.json deleted file mode 100644 index 55b7661..0000000 --- a/workflows/tasks/task-main-branch-hook-exemption/runs/20260416T044031-46f9f315.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "id": "20260416T044031-46f9f315", - "task_id": "task-main-branch-hook-exemption", - "phase_id": "phase-1", - "event": "verification", - "commands": [ - { - "command": "python3 -m unittest tests.test_workflow_cli -v", - "status": "passed", - "output": "test_approve_requires_structured_socratic_log_and_locks_intake (tests.test_workflow_cli.WorkflowCliTest.test_approve_requires_structured_socratic_log_and_locks_intake) ... ok\ntest_check_fails_when_approved_task_points_to_completed_phase (tests.test_workflow_cli.WorkflowCliTest.test_check_fails_when_approved_task_points_to_completed_phase) ... ok\ntest_circuit_breaker_triggers_from_verification_failures (tests.test_workflow_cli.WorkflowCliTest.test_circuit_breaker_triggers_from_verification_failures) ... ok\ntest_dangerous_command_guard_blocks_short_force_push (tests.test_workflow_cli.WorkflowCli..." - }, - { - "command": "python3 scripts/workflow.py doctor", - "status": "passed", - "output": "{\n \"status\": \"passed\",\n \"checks\": [\n \"workflow system hook config is readable\",\n \"git core.hooksPath points to .githooks\",\n \"task artifacts are internally consistent\",\n \"runtime surface matches source\",\n \"AGENTS constitution is complete\",\n \"no stale legacy references remain in control-plane files\"\n ],\n \"errors\": []\n}" - } - ], - "result": "passed", - "evidence": [], - "error_fingerprint": null, - "next_action": "review", - "timestamp": "2026-04-16T04:40:31+00:00" -} diff --git a/workflows/tasks/task-main-branch-hook-exemption/runs/20260416T044034-57b90731.json b/workflows/tasks/task-main-branch-hook-exemption/runs/20260416T044034-57b90731.json deleted file mode 100644 index a26da51..0000000 --- a/workflows/tasks/task-main-branch-hook-exemption/runs/20260416T044034-57b90731.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "id": "20260416T044034-57b90731", - "task_id": "task-main-branch-hook-exemption", - "phase_id": "phase-1", - "event": "review_ready", - "commands": [ - { - "command": "review gate", - "status": "passed", - "output": "latest passed verification: 20260416T044031-46f9f315" - } - ], - "result": "passed", - "evidence": [ - "main==develop 동기화 publish 예외 검토 완료" - ], - "error_fingerprint": null, - "next_action": "user validation", - "timestamp": "2026-04-16T04:40:34+00:00" -} diff --git a/workflows/tasks/task-main-branch-hook-exemption/runs/20260416T044254-83fb50ae.json b/workflows/tasks/task-main-branch-hook-exemption/runs/20260416T044254-83fb50ae.json deleted file mode 100644 index 56f66c1..0000000 --- a/workflows/tasks/task-main-branch-hook-exemption/runs/20260416T044254-83fb50ae.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "id": "20260416T044254-83fb50ae", - "task_id": "task-main-branch-hook-exemption", - "phase_id": "phase-1", - "event": "review_closeout", - "commands": [ - { - "command": "complete task", - "status": "passed", - "output": "사용자가 main 동기화 hook 예외 변경을 커밋하고 원격에 올리도록 요청함" - } - ], - "result": "passed", - "evidence": [ - "사용자가 main 동기화 hook 예외 변경을 커밋하고 원격에 올리도록 요청함" - ], - "error_fingerprint": null, - "next_action": "done", - "timestamp": "2026-04-16T04:42:54+00:00" -} diff --git a/workflows/tasks/task-main-branch-hook-exemption/spec.md b/workflows/tasks/task-main-branch-hook-exemption/spec.md deleted file mode 100644 index 0d98bf4..0000000 --- a/workflows/tasks/task-main-branch-hook-exemption/spec.md +++ /dev/null @@ -1,59 +0,0 @@ -# main 브랜치 hook 면제 정책 도입 - -- Task ID: `task-main-branch-hook-exemption` -- Primary Repo: `git-ranker-workflow` -- Status: `draft` - -## Request - -- `develop` 브랜치에는 `pre-commit`, `pre-push`가 필요하지만, `main` 브랜치는 `develop` 내용을 merge 또는 fast-forward로 반영해 최신 상태를 publish하는 용도이므로 동일한 hook 강제를 적용하지 않도록 control plane 계약을 조정한다. -- 결과적으로 `main` publish 경로가 task artifact 부재 때문에 fail-closed 되는 문제를 제거한다. - -## Problem - -- 현재 `pre-push`는 unpushed diff를 단일 task scope로 매핑하지 못하면 fail-closed 한다. -- `main` 브랜치에는 여러 완료 이력이 누적되지만 해당 task artifact가 저장소에 남아 있지 않을 수 있어, 단순한 `develop` 동기화 push도 hook에서 차단된다. -- `main`이 배포용 동기화 브랜치라면 `develop`과 동일한 task-bound guard를 유지하는 것이 운영 흐름과 맞지 않는다. - -## Goals - -- `main` 브랜치에서 `develop` 동기화 publish를 수행할 때 `pre-commit`, `pre-push`가 불필요하게 차단하지 않도록 한다. -- 예외 판별 기준은 `main` tip이 push 시점의 `develop` tip과 동일한 경우로 제한한다. -- `develop` 및 그 외 작업 브랜치에서는 기존 hook guard를 유지한다. -- 변경된 branch-aware policy가 문서, hook wrapper, runtime 테스트에 함께 반영되도록 한다. - -## Non-goals - -- `develop` 브랜치의 guard 정책을 약화하지 않는다. -- workflow task artifact 모델 자체를 제거하거나 verification guard를 전역 비활성화하지 않는다. -- 앱 저장소(`git-ranker`, `git-ranker-client`)의 동작 계약은 변경하지 않는다. - -## Constraints - -- workflow 계약 변경이므로 `AGENTS.md`, `docs/`, `.githooks/`, `tests/`까지 같이 맞춰야 한다. -- `main` 예외가 허용되더라도 destructive command guard 같은 안전 장치는 의도치 않게 약화되면 안 된다. -- merge commit 여부, 커밋 메시지, 사람이 의도한 merge 동작 자체는 신뢰하지 않고, git graph에서 관찰 가능한 `main == develop` 상태만 허용 기준으로 사용한다. -- 승인 전에는 phase 생성이나 구현을 시작하지 않는다. - -## Acceptance - -- `main` 브랜치에서 push 대상 tip이 현재 `develop` tip과 동일하면 `git push origin main` 경로가 branch policy에 따라 허용된다. -- `main` 브랜치라도 tip이 `develop`과 다르면 기존 guard 경로를 계속 탄다. -- `develop` 브랜치에서는 기존처럼 hook guard가 동작해 task binding 및 verification freshness를 계속 검사한다. -- 관련 runtime test가 `main` 예외와 `develop` 유지 동작을 모두 검증한다. -- 문서가 branch-aware hook 정책을 명시한다. - -## Socratic Clarification Log - -- Q: `main` 브랜치에도 `develop`과 동일한 `pre-commit`/`pre-push` guard를 유지해야 하는가? -- A: `develop` 브랜치는 guard가 필요하지만, `main` 브랜치는 `develop`의 내용을 merge 후 최신화하는 역할이라 굳이 `.githook`이 필요하지 않다. -- Decision: `main` 브랜치는 작업 브랜치와 다른 publish 성격을 가진다는 전제로, branch-aware hook 정책을 검토한다. -- Q: `main` 브랜치 예외를 어떤 기준으로 판별할 것인가? -- A: merge 의도나 커밋 메시지 대신, push 시점의 `main` tip이 `develop` tip과 동일할 때만 예외를 주는 기준이 안전하다. -- Decision: `main == develop`인 동기화 publish에 한해 hook 예외를 허용하고, 그 외 `main` push는 기존 guard를 유지한다. - -## Approval - -- Actor: `user` -- Timestamp: `2026-04-16T04:34:35+00:00` -- Note: main==develop 동기화 push 예외 기준으로 spec 승인 diff --git a/workflows/tasks/task-main-branch-hook-exemption/task.json b/workflows/tasks/task-main-branch-hook-exemption/task.json deleted file mode 100644 index 200aa37..0000000 --- a/workflows/tasks/task-main-branch-hook-exemption/task.json +++ /dev/null @@ -1,61 +0,0 @@ -{ - "id": "task-main-branch-hook-exemption", - "title": "main 브랜치 hook 면제 정책 도입", - "state": "completed", - "primary_repo": "git-ranker-workflow", - "created_at": "2026-04-16T04:29:49+00:00", - "approved_at": "2026-04-16T04:34:35+00:00", - "approval": { - "actor": "user", - "note": "main==develop 동기화 push 예외 기준으로 spec 승인", - "timestamp": "2026-04-16T04:34:35+00:00" - }, - "active_phase_id": "phase-1", - "latest_run_id": "20260416T044254-83fb50ae", - "last_verified_run_id": "20260416T044031-46f9f315", - "blocked_reason": null, - "user_validated": true, - "user_validation_note": "사용자가 main 동기화 hook 예외 변경을 커밋하고 원격에 올리도록 요청함", - "intake": { - "request_summary": "`develop` 브랜치에는 `pre-commit`, `pre-push`가 필요하지만, `main` 브랜치는 `develop` 내용을 merge 또는 fast-forward로 반영해 최신 상태를 publish하는 용도이므로 동일한 hook 강제를 적용하지 않도록 control plane 계약을 조정한다. 결과적으로 `main` publish 경로가 task artifact 부재 때문에 fail-closed 되는 문제를 제거한다.", - "problem_summary": "현재 `pre-push`는 unpushed diff를 단일 task scope로 매핑하지 못하면 fail-closed 한다. `main` 브랜치에는 여러 완료 이력이 누적되지만 해당 task artifact가 저장소에 남아 있지 않을 수 있어, 단순한 `develop` 동기화 push도 hook에서 차단된다. `main`이 배포용 동기화 브랜치라면 `develop`과 동일한 task-bound guard를 유지하는 것이 운영 흐름과 맞지 않는다.", - "goals": [ - "`main` 브랜치에서 `develop` 동기화 publish를 수행할 때 `pre-commit`, `pre-push`가 불필요하게 차단하지 않도록 한다.", - "예외 판별 기준은 `main` tip이 push 시점의 `develop` tip과 동일한 경우로 제한한다.", - "`develop` 및 그 외 작업 브랜치에서는 기존 hook guard를 유지한다.", - "변경된 branch-aware policy가 문서, hook wrapper, runtime 테스트에 함께 반영되도록 한다." - ], - "non_goals": [ - "`develop` 브랜치의 guard 정책을 약화하지 않는다.", - "workflow task artifact 모델 자체를 제거하거나 verification guard를 전역 비활성화하지 않는다.", - "앱 저장소(`git-ranker`, `git-ranker-client`)의 동작 계약은 변경하지 않는다." - ], - "constraints": [ - "workflow 계약 변경이므로 `AGENTS.md`, `docs/`, `.githooks/`, `tests/`까지 같이 맞춰야 한다.", - "`main` 예외가 허용되더라도 destructive command guard 같은 안전 장치는 의도치 않게 약화되면 안 된다.", - "merge commit 여부, 커밋 메시지, 사람이 의도한 merge 동작 자체는 신뢰하지 않고, git graph에서 관찰 가능한 `main == develop` 상태만 허용 기준으로 사용한다.", - "승인 전에는 phase 생성이나 구현을 시작하지 않는다." - ], - "acceptance": [ - "`main` 브랜치에서 push 대상 tip이 현재 `develop` tip과 동일하면 `git push origin main` 경로가 branch policy에 따라 허용된다.", - "`main` 브랜치라도 tip이 `develop`과 다르면 기존 guard 경로를 계속 탄다.", - "`develop` 브랜치에서는 기존처럼 hook guard가 동작해 task binding 및 verification freshness를 계속 검사한다.", - "관련 runtime test가 `main` 예외와 `develop` 유지 동작을 모두 검증한다.", - "문서가 branch-aware hook 정책을 명시한다." - ], - "clarifications": [ - { - "question": "`main` 브랜치에도 `develop`과 동일한 `pre-commit`/`pre-push` guard를 유지해야 하는가?", - "answer": "`develop` 브랜치는 guard가 필요하지만, `main` 브랜치는 `develop`의 내용을 merge 후 최신화하는 역할이라 굳이 `.githook`이 필요하지 않다.", - "decision": "`main` 브랜치는 작업 브랜치와 다른 publish 성격을 가진다는 전제로, branch-aware hook 정책을 검토한다.", - "resolved": true - }, - { - "question": "`main` 브랜치 예외를 어떤 기준으로 판별할 것인가?", - "answer": "merge 의도나 커밋 메시지 대신, push 시점의 `main` tip이 `develop` tip과 동일할 때만 예외를 주는 기준이 안전하다.", - "decision": "`main == develop`인 동기화 publish에 한해 hook 예외를 허용하고, 그 외 `main` push는 기존 guard를 유지한다.", - "resolved": true - } - ] - } -} diff --git a/workflows/tasks/task-socratic-coverage-phase-kickoff/phases.json b/workflows/tasks/task-socratic-coverage-phase-kickoff/phases.json deleted file mode 100644 index ae6671e..0000000 --- a/workflows/tasks/task-socratic-coverage-phase-kickoff/phases.json +++ /dev/null @@ -1,50 +0,0 @@ -{ - "task_id": "task-socratic-coverage-phase-kickoff", - "generated_at": "2026-04-16T09:51:58+00:00", - "phases": [ - { - "id": "phase-4", - "title": "legacy-empty-input-bootstrap-compatibility", - "goal": "Preserve backward compatibility for legacy phase plans that omit bootstrap fields and use empty inputs while keeping the kickoff bootstrap contract intact.", - "inputs": [ - "workflows/tasks/task-socratic-coverage-phase-kickoff/spec.md", - "scripts/workflow_runtime/engine.py", - "tests/test_workflow_cli.py" - ], - "required_reads": [ - "workflows/tasks/task-socratic-coverage-phase-kickoff/spec.md", - "scripts/workflow_runtime/engine.py", - "scripts/workflow_runtime/models.py", - "tests/test_workflow_cli.py" - ], - "starting_points": [ - "Inspect the review comment about empty required_reads regression in legacy phase payloads.", - "Confirm the fix preserves bootstrap summaries without writing invalid empty required_reads into phases.json.", - "Add a regression test that plans a legacy empty-input phase without bootstrap metadata." - ], - "deliverables": [ - "Bootstrap defaults no longer force invalid empty required_reads into stored phases.", - "A regression test covers legacy empty-input phase planning." - ], - "completion_signal": "Legacy empty-input phase plans remain plannable and the full CLI test suite still passes.", - "allowed_write_paths": [ - "scripts/workflow_runtime/", - "tests/" - ], - "acceptance": { - "commands": [ - "python3 -m unittest tests.test_workflow_cli.WorkflowCliTest.test_plan_keeps_legacy_empty_inputs_compatible_without_required_reads -v", - "python3 -m unittest tests.test_workflow_cli -v", - "python3 scripts/workflow.py doctor" - ] - }, - "test_policy": { - "mode": "require_tests", - "evidence": [] - }, - "order": 1, - "status": "completed", - "retry_count": 0 - } - ] -} diff --git a/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T074937-2a5c8566.json b/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T074937-2a5c8566.json deleted file mode 100644 index b2f49a9..0000000 --- a/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T074937-2a5c8566.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "id": "20260416T074937-2a5c8566", - "task_id": "task-socratic-coverage-phase-kickoff", - "phase_id": "phase-1", - "event": "phase_completion", - "commands": [ - { - "command": "phase completion", - "status": "passed", - "output": "scripts/workflow_runtime/constants.py, scripts/workflow_runtime/engine.py, scripts/workflow_runtime/models.py, scripts/workflow_runtime/templates.py, tests/test_workflow_cli.py" - } - ], - "result": "passed", - "evidence": [ - "Added clarification coverage categories, status reporting, and v2 task defaults for spec approval gating." - ], - "error_fingerprint": null, - "next_action": "verify", - "timestamp": "2026-04-16T07:49:37+00:00" -} diff --git a/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T074940-adfc4b6b.json b/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T074940-adfc4b6b.json deleted file mode 100644 index 10fde1d..0000000 --- a/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T074940-adfc4b6b.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "id": "20260416T074940-adfc4b6b", - "task_id": "task-socratic-coverage-phase-kickoff", - "phase_id": "phase-1", - "event": "verification", - "commands": [ - { - "command": "python3 -m unittest tests.test_workflow_cli.WorkflowCliTest.test_approve_requires_coverage_categories_before_locking_intake tests.test_workflow_cli.WorkflowCliTest.test_status_reports_missing_coverage_categories_before_approval -v", - "status": "passed", - "output": "test_approve_requires_coverage_categories_before_locking_intake (tests.test_workflow_cli.WorkflowCliTest.test_approve_requires_coverage_categories_before_locking_intake) ... ok\ntest_status_reports_missing_coverage_categories_before_approval (tests.test_workflow_cli.WorkflowCliTest.test_status_reports_missing_coverage_categories_before_approval) ... ok\n\n----------------------------------------------------------------------\nRan 2 tests in 0.240s\n\nOK" - } - ], - "result": "passed", - "evidence": [], - "error_fingerprint": null, - "next_action": "review", - "timestamp": "2026-04-16T07:49:40+00:00" -} diff --git a/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T080342-aeccaa05.json b/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T080342-aeccaa05.json deleted file mode 100644 index 05d6204..0000000 --- a/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T080342-aeccaa05.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "id": "20260416T080342-aeccaa05", - "task_id": "task-socratic-coverage-phase-kickoff", - "phase_id": "phase-2", - "event": "phase_completion", - "commands": [ - { - "command": "phase completion", - "status": "passed", - "output": ".codex/skills/README.md, .codex/skills/phase-executor/SKILL.md, .codex/skills/phase-planner/SKILL.md, .codex/skills/socratic-spec-authoring/SKILL.md, .githooks/pre-commit, .githooks/pre-push, AGENTS.md, docs/artifact-model.md, docs/hooks.md, docs/runbook.md, docs/runtime.md, scripts/workflow_runtime/cli.py, scripts/workflow_runtime/constants.py, scripts/workflow_runtime/engine.py, scripts/workflow_runtime/models.py, scripts/workflow_runtime/templates.py, tests/test_workflow_cli.py" - } - ], - "result": "passed", - "evidence": [ - "Added phase kickoff gating, bootstrap metadata, explicit incomplete-task doctor errors, and contract documentation updates." - ], - "error_fingerprint": null, - "next_action": "verify", - "timestamp": "2026-04-16T08:03:42+00:00" -} diff --git a/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T080405-369c81b9.json b/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T080405-369c81b9.json deleted file mode 100644 index 3a21a0a..0000000 --- a/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T080405-369c81b9.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "id": "20260416T080405-369c81b9", - "task_id": "task-socratic-coverage-phase-kickoff", - "phase_id": "phase-2", - "event": "verification", - "commands": [ - { - "command": "python3 -m unittest tests.test_workflow_cli.WorkflowCliTest.test_kickoff_is_required_before_starting_next_phase tests.test_workflow_cli.WorkflowCliTest.test_replan_invalidates_existing_kickoff_requirement tests.test_workflow_cli.WorkflowCliTest.test_reopen_resets_kickoff_requirement_for_target_phase tests.test_workflow_cli.WorkflowCliTest.test_doctor_reports_incomplete_task_directory -v", - "status": "passed", - "output": "test_kickoff_is_required_before_starting_next_phase (tests.test_workflow_cli.WorkflowCliTest.test_kickoff_is_required_before_starting_next_phase) ... ok\ntest_replan_invalidates_existing_kickoff_requirement (tests.test_workflow_cli.WorkflowCliTest.test_replan_invalidates_existing_kickoff_requirement) ... ok\ntest_reopen_resets_kickoff_requirement_for_target_phase (tests.test_workflow_cli.WorkflowCliTest.test_reopen_resets_kickoff_requirement_for_target_phase) ... ok\ntest_doctor_reports_incomplete_task_directory (tests.test_workflow_cli.WorkflowCliTest.test_doctor_reports_incomplete_task_director..." - }, - { - "command": "python3 -m unittest tests.test_workflow_cli -v", - "status": "passed", - "output": "test_approve_requires_coverage_categories_before_locking_intake (tests.test_workflow_cli.WorkflowCliTest.test_approve_requires_coverage_categories_before_locking_intake) ... ok\ntest_approve_requires_structured_socratic_log_and_locks_intake (tests.test_workflow_cli.WorkflowCliTest.test_approve_requires_structured_socratic_log_and_locks_intake) ... ok\ntest_check_fails_when_approved_task_points_to_completed_phase (tests.test_workflow_cli.WorkflowCliTest.test_check_fails_when_approved_task_points_to_completed_phase) ... ok\ntest_circuit_breaker_triggers_from_verification_failures (tests.test_workfl..." - }, - { - "command": "python3 scripts/workflow.py doctor", - "status": "passed", - "output": "{\n \"status\": \"passed\",\n \"checks\": [\n \"workflow system hook config is readable\",\n \"git core.hooksPath points to .githooks\",\n \"task artifacts are internally consistent\",\n \"runtime surface matches source\",\n \"AGENTS constitution is complete\",\n \"no stale legacy references remain in control-plane files\"\n ],\n \"errors\": []\n}" - } - ], - "result": "passed", - "evidence": [], - "error_fingerprint": null, - "next_action": "review", - "timestamp": "2026-04-16T08:04:05+00:00" -} diff --git a/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T080412-b3c042bb.json b/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T080412-b3c042bb.json deleted file mode 100644 index 5ac83f2..0000000 --- a/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T080412-b3c042bb.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "id": "20260416T080412-b3c042bb", - "task_id": "task-socratic-coverage-phase-kickoff", - "phase_id": "phase-2", - "event": "review_ready", - "commands": [ - { - "command": "review gate", - "status": "passed", - "output": "latest passed verification: 20260416T080405-369c81b9" - } - ], - "result": "passed", - "evidence": [ - "Implementation and verification passed; ready for user validation." - ], - "error_fingerprint": null, - "next_action": "user validation", - "timestamp": "2026-04-16T08:04:12+00:00" -} diff --git a/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T081044-365456de.json b/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T081044-365456de.json deleted file mode 100644 index 7ff68c2..0000000 --- a/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T081044-365456de.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "id": "20260416T081044-365456de", - "task_id": "task-socratic-coverage-phase-kickoff", - "phase_id": "phase-2", - "event": "reopened", - "commands": [ - { - "command": "reopen task", - "status": "passed", - "output": "Follow-up hardening: legacy tasks should pick up the new kickoff contract on reopen/replan and upgrade to spec coverage enforcement when reapproved with categorized clarifications." - } - ], - "result": "passed", - "evidence": [ - "Follow-up hardening: legacy tasks should pick up the new kickoff contract on reopen/replan and upgrade to spec coverage enforcement when reapproved with categorized clarifications." - ], - "error_fingerprint": null, - "next_action": "plan or run", - "timestamp": "2026-04-16T08:10:44+00:00" -} diff --git a/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T081845-6e9f8516.json b/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T081845-6e9f8516.json deleted file mode 100644 index 0a37994..0000000 --- a/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T081845-6e9f8516.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "id": "20260416T081845-6e9f8516", - "task_id": "task-socratic-coverage-phase-kickoff", - "phase_id": "phase-2", - "event": "phase_completion", - "commands": [ - { - "command": "phase completion", - "status": "passed", - "output": ".codex/skills/README.md, .codex/skills/phase-executor/SKILL.md, .codex/skills/phase-planner/SKILL.md, .codex/skills/socratic-spec-authoring/SKILL.md, .githooks/pre-commit, .githooks/pre-push, AGENTS.md, docs/artifact-model.md, docs/hooks.md, docs/runbook.md, docs/runtime.md, scripts/workflow_runtime/cli.py, scripts/workflow_runtime/constants.py, scripts/workflow_runtime/engine.py, scripts/workflow_runtime/models.py, scripts/workflow_runtime/templates.py, tests/test_workflow_cli.py" - } - ], - "result": "passed", - "evidence": [ - "Follow-up hardening: legacy tasks now adopt kickoff gating for later phases, and legacy tasks upgrade to the v2 spec contract when reapproved with categorized clarifications." - ], - "error_fingerprint": null, - "next_action": "verify", - "timestamp": "2026-04-16T08:18:45+00:00" -} diff --git a/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T081911-d8020d25.json b/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T081911-d8020d25.json deleted file mode 100644 index 16cf625..0000000 --- a/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T081911-d8020d25.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "id": "20260416T081911-d8020d25", - "task_id": "task-socratic-coverage-phase-kickoff", - "phase_id": "phase-2", - "event": "verification", - "commands": [ - { - "command": "python3 -m unittest tests.test_workflow_cli.WorkflowCliTest.test_kickoff_is_required_before_starting_next_phase tests.test_workflow_cli.WorkflowCliTest.test_replan_invalidates_existing_kickoff_requirement tests.test_workflow_cli.WorkflowCliTest.test_reopen_resets_kickoff_requirement_for_target_phase tests.test_workflow_cli.WorkflowCliTest.test_doctor_reports_incomplete_task_directory -v", - "status": "passed", - "output": "test_kickoff_is_required_before_starting_next_phase (tests.test_workflow_cli.WorkflowCliTest.test_kickoff_is_required_before_starting_next_phase) ... ok\ntest_replan_invalidates_existing_kickoff_requirement (tests.test_workflow_cli.WorkflowCliTest.test_replan_invalidates_existing_kickoff_requirement) ... ok\ntest_reopen_resets_kickoff_requirement_for_target_phase (tests.test_workflow_cli.WorkflowCliTest.test_reopen_resets_kickoff_requirement_for_target_phase) ... ok\ntest_doctor_reports_incomplete_task_directory (tests.test_workflow_cli.WorkflowCliTest.test_doctor_reports_incomplete_task_director..." - }, - { - "command": "python3 -m unittest tests.test_workflow_cli -v", - "status": "passed", - "output": "test_approve_requires_coverage_categories_before_locking_intake (tests.test_workflow_cli.WorkflowCliTest.test_approve_requires_coverage_categories_before_locking_intake) ... ok\ntest_approve_requires_structured_socratic_log_and_locks_intake (tests.test_workflow_cli.WorkflowCliTest.test_approve_requires_structured_socratic_log_and_locks_intake) ... ok\ntest_check_fails_when_approved_task_points_to_completed_phase (tests.test_workflow_cli.WorkflowCliTest.test_check_fails_when_approved_task_points_to_completed_phase) ... ok\ntest_circuit_breaker_triggers_from_verification_failures (tests.test_workfl..." - }, - { - "command": "python3 scripts/workflow.py doctor", - "status": "passed", - "output": "{\n \"status\": \"passed\",\n \"checks\": [\n \"workflow system hook config is readable\",\n \"git core.hooksPath points to .githooks\",\n \"task artifacts are internally consistent\",\n \"runtime surface matches source\",\n \"AGENTS constitution is complete\",\n \"no stale legacy references remain in control-plane files\"\n ],\n \"errors\": []\n}" - } - ], - "result": "passed", - "evidence": [], - "error_fingerprint": null, - "next_action": "review", - "timestamp": "2026-04-16T08:19:11+00:00" -} diff --git a/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T081922-cfed0cdc.json b/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T081922-cfed0cdc.json deleted file mode 100644 index 7d08118..0000000 --- a/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T081922-cfed0cdc.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "id": "20260416T081922-cfed0cdc", - "task_id": "task-socratic-coverage-phase-kickoff", - "phase_id": "phase-2", - "event": "review_ready", - "commands": [ - { - "command": "review gate", - "status": "passed", - "output": "latest passed verification: 20260416T081911-d8020d25" - } - ], - "result": "passed", - "evidence": [ - "Follow-up legacy migration hardening also passed verification; ready for user validation." - ], - "error_fingerprint": null, - "next_action": "user validation", - "timestamp": "2026-04-16T08:19:22+00:00" -} diff --git a/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T082010-24602efb.json b/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T082010-24602efb.json deleted file mode 100644 index 1f3c7a3..0000000 --- a/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T082010-24602efb.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "id": "20260416T082010-24602efb", - "task_id": "task-socratic-coverage-phase-kickoff", - "phase_id": "phase-2", - "event": "review_closeout", - "commands": [ - { - "command": "complete task", - "status": "passed", - "output": "User reviewed the implemented socratic coverage and phase kickoff contract changes and requested to proceed with final closeout." - } - ], - "result": "passed", - "evidence": [ - "User reviewed the implemented socratic coverage and phase kickoff contract changes and requested to proceed with final closeout." - ], - "error_fingerprint": null, - "next_action": "done", - "timestamp": "2026-04-16T08:20:10+00:00" -} diff --git a/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T083218-620e6176.json b/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T083218-620e6176.json deleted file mode 100644 index a3427f2..0000000 --- a/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T083218-620e6176.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "id": "20260416T083218-620e6176", - "task_id": "task-socratic-coverage-phase-kickoff", - "phase_id": "phase-2", - "event": "reopened", - "commands": [ - { - "command": "reopen task", - "status": "passed", - "output": "Include the git-ranker submodule pointer update in this PR and re-verify the workflow branch before review." - } - ], - "result": "passed", - "evidence": [ - "Include the git-ranker submodule pointer update in this PR and re-verify the workflow branch before review." - ], - "error_fingerprint": null, - "next_action": "plan or run", - "timestamp": "2026-04-16T08:32:18+00:00" -} diff --git a/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T083440-e7381a90.json b/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T083440-e7381a90.json deleted file mode 100644 index 7217d24..0000000 --- a/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T083440-e7381a90.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "id": "20260416T083440-e7381a90", - "task_id": "task-socratic-coverage-phase-kickoff", - "phase_id": "phase-3", - "event": "verification", - "commands": [ - { - "command": "python3 -c \"import subprocess; diff=subprocess.check_output(['git','diff','--submodule=short','--','git-ranker'], text=True); assert 'fbc672c6384eb896183f838be7413e195e388dc5' in diff, diff; print(diff.strip())\"", - "status": "passed", - "output": "diff --git a/git-ranker b/git-ranker\nindex fc85948..fbc672c 160000\n--- a/git-ranker\n+++ b/git-ranker\n@@ -1 +1 @@\n-Subproject commit fc85948b3d81a07314bb5d85d68afbae9f46ffc9\n+Subproject commit fbc672c6384eb896183f838be7413e195e388dc5" - }, - { - "command": "python3 -c \"import subprocess; sha=subprocess.check_output(['git','-C','git-ranker','rev-parse','HEAD'], text=True).strip(); assert sha == 'fbc672c6384eb896183f838be7413e195e388dc5', sha; print(sha)\"", - "status": "passed", - "output": "fbc672c6384eb896183f838be7413e195e388dc5" - }, - { - "command": "python3 scripts/workflow.py doctor", - "status": "passed", - "output": "{\n \"status\": \"passed\",\n \"checks\": [\n \"workflow system hook config is readable\",\n \"git core.hooksPath points to .githooks\",\n \"task artifacts are internally consistent\",\n \"runtime surface matches source\",\n \"AGENTS constitution is complete\",\n \"no stale legacy references remain in control-plane files\"\n ],\n \"errors\": []\n}" - } - ], - "result": "passed", - "evidence": [], - "error_fingerprint": null, - "next_action": "review", - "timestamp": "2026-04-16T08:34:40+00:00" -} diff --git a/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T083445-4eabfacc.json b/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T083445-4eabfacc.json deleted file mode 100644 index d19af2b..0000000 --- a/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T083445-4eabfacc.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "id": "20260416T083445-4eabfacc", - "task_id": "task-socratic-coverage-phase-kickoff", - "phase_id": "phase-3", - "event": "review_ready", - "commands": [ - { - "command": "review gate", - "status": "passed", - "output": "latest passed verification: 20260416T083440-e7381a90" - } - ], - "result": "passed", - "evidence": [ - "Review ready with the git-ranker submodule pointer update included in the PR branch." - ], - "error_fingerprint": null, - "next_action": "user validation", - "timestamp": "2026-04-16T08:34:45+00:00" -} diff --git a/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T095113-37218ec8.json b/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T095113-37218ec8.json deleted file mode 100644 index d1005fa..0000000 --- a/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T095113-37218ec8.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "id": "20260416T095113-37218ec8", - "task_id": "task-socratic-coverage-phase-kickoff", - "phase_id": "phase-3", - "event": "reopened", - "commands": [ - { - "command": "reopen task", - "status": "passed", - "output": "Address PR review feedback about preserving legacy empty-input phase compatibility in bootstrap defaults." - } - ], - "result": "passed", - "evidence": [ - "Address PR review feedback about preserving legacy empty-input phase compatibility in bootstrap defaults." - ], - "error_fingerprint": null, - "next_action": "plan or run", - "timestamp": "2026-04-16T09:51:13+00:00" -} diff --git a/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T095243-9d26c18b.json b/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T095243-9d26c18b.json deleted file mode 100644 index c89a6a5..0000000 --- a/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T095243-9d26c18b.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "id": "20260416T095243-9d26c18b", - "task_id": "task-socratic-coverage-phase-kickoff", - "phase_id": "phase-4", - "event": "phase_completion", - "commands": [ - { - "command": "phase completion", - "status": "passed", - "output": "scripts/workflow_runtime/engine.py, tests/test_workflow_cli.py" - } - ], - "result": "passed", - "evidence": [], - "error_fingerprint": null, - "next_action": "verify", - "timestamp": "2026-04-16T09:52:43+00:00" -} diff --git a/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T095305-fef15d65.json b/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T095305-fef15d65.json deleted file mode 100644 index 2811ab3..0000000 --- a/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T095305-fef15d65.json +++ /dev/null @@ -1,28 +0,0 @@ -{ - "id": "20260416T095305-fef15d65", - "task_id": "task-socratic-coverage-phase-kickoff", - "phase_id": "phase-4", - "event": "verification", - "commands": [ - { - "command": "python3 -m unittest tests.test_workflow_cli.WorkflowCliTest.test_plan_keeps_legacy_empty_inputs_compatible_without_required_reads -v", - "status": "passed", - "output": "test_plan_keeps_legacy_empty_inputs_compatible_without_required_reads (tests.test_workflow_cli.WorkflowCliTest.test_plan_keeps_legacy_empty_inputs_compatible_without_required_reads) ... ok\n\n----------------------------------------------------------------------\nRan 1 test in 0.192s\n\nOK" - }, - { - "command": "python3 -m unittest tests.test_workflow_cli -v", - "status": "passed", - "output": "test_approve_requires_coverage_categories_before_locking_intake (tests.test_workflow_cli.WorkflowCliTest.test_approve_requires_coverage_categories_before_locking_intake) ... ok\ntest_approve_requires_structured_socratic_log_and_locks_intake (tests.test_workflow_cli.WorkflowCliTest.test_approve_requires_structured_socratic_log_and_locks_intake) ... ok\ntest_check_fails_when_approved_task_points_to_completed_phase (tests.test_workflow_cli.WorkflowCliTest.test_check_fails_when_approved_task_points_to_completed_phase) ... ok\ntest_circuit_breaker_triggers_from_verification_failures (tests.test_workfl..." - }, - { - "command": "python3 scripts/workflow.py doctor", - "status": "passed", - "output": "{\n \"status\": \"passed\",\n \"checks\": [\n \"workflow system hook config is readable\",\n \"git core.hooksPath points to .githooks\",\n \"task artifacts are internally consistent\",\n \"runtime surface matches source\",\n \"AGENTS constitution is complete\",\n \"no stale legacy references remain in control-plane files\"\n ],\n \"errors\": []\n}" - } - ], - "result": "passed", - "evidence": [], - "error_fingerprint": null, - "next_action": "review", - "timestamp": "2026-04-16T09:53:05+00:00" -} diff --git a/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T095316-852c69fa.json b/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T095316-852c69fa.json deleted file mode 100644 index 6a99325..0000000 --- a/workflows/tasks/task-socratic-coverage-phase-kickoff/runs/20260416T095316-852c69fa.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "id": "20260416T095316-852c69fa", - "task_id": "task-socratic-coverage-phase-kickoff", - "phase_id": "phase-4", - "event": "review_ready", - "commands": [ - { - "command": "review gate", - "status": "passed", - "output": "latest passed verification: 20260416T095305-fef15d65" - } - ], - "result": "passed", - "evidence": [ - "Review ready after fixing the legacy empty-input bootstrap compatibility regression raised in PR review." - ], - "error_fingerprint": null, - "next_action": "user validation", - "timestamp": "2026-04-16T09:53:16+00:00" -} diff --git a/workflows/tasks/task-socratic-coverage-phase-kickoff/spec.md b/workflows/tasks/task-socratic-coverage-phase-kickoff/spec.md deleted file mode 100644 index 16efd0e..0000000 --- a/workflows/tasks/task-socratic-coverage-phase-kickoff/spec.md +++ /dev/null @@ -1,86 +0,0 @@ -# Strengthen spec coverage gate and phase kickoff session contract - -- Task ID: `task-socratic-coverage-phase-kickoff` -- Primary Repo: `git-ranker-workflow` -- Status: `draft` - -## Request - -- 소크라테스식 질문으로 spec을 잠글 때 AI가 요구사항을 충분히 이해할 때까지 질의를 계속하고, 그 결과가 명시적 coverage gate로 강제되도록 control plane을 강화한다. -- phase는 항상 독립적인 작업 단위여야 하며, 한 phase의 구현과 검증이 끝난 뒤 다음 phase는 새 세션에서만 시작할 수 있도록 kickoff contract를 도입한다. -- 다음 phase를 시작하는 새 세션은 `task.json`, `phases.json`, `spec.md`를 읽고 어디서부터 무엇을 해야 하는지 복원할 수 있어야 한다. -- 이 PR에는 다른 작업으로 이미 완료된 `git-ranker` backend 결과를 가리키는 workflow repo의 `git-ranker` gitlink 업데이트도 함께 반영해야 한다. - -## Problem - -- 현재 `approve`는 필수 섹션과 최소 한 개의 `Q/A/Decision` triplet만 검사하므로, AI가 요구사항을 충분히 파악했는지와 추가 질문이 더 필요한지까지는 보장하지 못한다. -- 현재 phase 상태는 JSON에 기록되지만, 다음 phase를 새 세션에서 시작하기 전에 어떤 정보를 읽고 어떤 순서로 시작해야 하는지까지는 runtime contract로 고정되어 있지 않다. -- 이 상태에서는 spec 품질이 얕은 문답으로 승인될 수 있고, multi-phase 작업에서 다음 phase가 이전 채팅 맥락에 암묵적으로 의존하게 되어 control plane이 세션 독립성을 보장하지 못한다. -- 현재 PR 브랜치에는 workflow control-plane 변경만 있고, 같은 작업 흐름에서 이미 완료된 `git-ranker` backend 기준 커밋을 가리키는 submodule gitlink는 빠져 있어 reviewer가 실제 baseline을 한 번에 확인하기 어렵다. -- bootstrap metadata default를 도입한 뒤, legacy phase payload가 `inputs=[]`와 bootstrap field omission을 함께 사용하면 `plan`이 빈 `required_reads`를 저장하려다 검증에서 실패하는 회귀가 생길 수 있다. - -## Goals - -- `approve` 전에 목표, 범위, 비범위, 제약, acceptance를 모두 덮는 clarification coverage gate를 도입한다. -- `status`가 아직 덜 물어본 clarification 축을 드러내고, approval readiness를 coverage 기준으로 판단하게 한다. -- 각 phase가 다음 세션 시작에 필요한 bootstrap 정보를 `phases.json`에 담도록 확장한다. -- `kickoff`와 phase-start gating을 도입해, 검증이 끝난 다음 phase는 새 세션 kickoff 절차를 거친 뒤에만 시작되도록 한다. -- `doctor`와 consistency check가 incomplete task artifact를 더 명확하게 보고하도록 개선한다. -- 관련 문서, skills, hooks surface, 테스트를 함께 갱신한다. -- workflow control-plane 변경을 publish할 때 함께 검토되어야 하는 `git-ranker` submodule pointer를 같은 PR 브랜치에 반영한다. -- legacy phase payload가 빈 `inputs`와 omitted bootstrap field를 사용하더라도 `plan`이 회귀 없이 계속 통과하도록 bootstrap default 적용을 backward compatible하게 유지한다. - -## Non-goals - -- 실제 Codex 런타임의 메모리 초기화 여부를 외부 session ID로 증명하는 통합까지 이번 작업 범위에 넣지 않는다. -- phase 중간 지점에서 세션을 갈아타는 resume workflow는 이번 작업 목표가 아니다. -- 앱 저장소(`git-ranker`, `git-ranker-client`)의 런타임 계약이나 제품 기능을 변경하지 않는다. - -## Constraints - -- root `AGENTS.md` workflow 계약을 따른다. 승인된 spec과 canonical phase 없이 구현을 시작하지 않는다. -- task/phase/run state는 `python3 scripts/workflow.py ...` 명령으로만 전이한다. -- workflow 계약 변경이므로 `AGENTS.md`, `docs/`, `.codex/skills/`, `.githooks/`, `tests/`를 함께 갱신한다. -- 다음 phase 시작점 복원은 `task.json`, `phases.json`, `spec.md`를 기준으로 설계하고 `runs/*.json`은 보조 evidence로만 사용한다. -- fresh-session 증명은 Codex client integration이 아니라 control-plane proof 수준으로 고정한다. -- `git-ranker` 서브모듈 반영은 workflow repo의 gitlink 업데이트만 다루고, backend 저장소 내부 변경 자체를 이 저장소에서 재작성하거나 재검증 대상으로 복제하지 않는다. - -## Acceptance - -- clarification coverage가 부족한 spec은 `approve`에 실패하고, `status`는 누락 축을 JSON으로 노출한다. -- 승인된 intake의 clarification에는 coverage category가 잠기고, docs와 skill이 그 계약을 설명한다. -- `phases.json` 각 phase는 다음 세션 시작에 필요한 bootstrap 필드를 포함한다. -- `python3 scripts/workflow.py kickoff `가 active phase bootstrap summary를 기록하고, matching kickoff 없이는 `run --start`가 실패한다. -- `verify` 성공 후 다음 pending phase가 있으면 task는 다음 phase kickoff를 요구하는 상태로 전이한다. -- 새 CLI 프로세스 기준 테스트로 `phase-1 verify -> phase-2 kickoff -> phase-2 start` 흐름이 검증된다. -- incomplete task directory가 있을 때 consistency check/doctor가 원인을 명확히 보고한다. -- PR 브랜치의 `git-ranker` gitlink가 backend PR `#89`의 커밋 `fbc672c6384eb896183f838be7413e195e388dc5`를 가리키고, review summary에도 그 사실이 반영된다. -- bootstrap field가 없는 legacy phase plan에서 `inputs=[]`여도 `plan`이 실패하지 않고, status bootstrap summary는 빈 `required_reads`를 안전하게 노출한다. - -## Socratic Clarification Log - -- Q: [goal] 소크라테스식 질문 품질은 최소 질문 개수로 볼까요, 아니면 요구사항 coverage로 볼까요? -- A: 질문 수보다 요구사항 축을 모두 덮었는지가 중요합니다. 범위, 목적, 비범위, 제약, acceptance가 빠지지 않아야 합니다. -- Decision: `approve`는 최소 질문 수가 아니라 clarification coverage gate로 판단한다. - -- Q: [constraint] 다음 phase를 시작하는 새 세션은 어떤 source of truth를 읽고 시작해야 하나요? -- A: `task.json`, `phases.json`과 현재 잠긴 `spec.md`를 읽고 어디서부터 시작해야 하는지 알 수 있어야 합니다. -- Decision: phase-start bootstrap은 `task.json`/`phases.json` + `spec.md` 기준으로 복원 가능해야 한다. - -- Q: [scope] phase별 독립 세션 요구는 어떤 수준으로 강제할까요? -- A: phase 경계에서만 새 세션을 강제하고, 새 phase 시작 전 kickoff proof가 없으면 시작하지 못하게 해야 합니다. -- Decision: 각 phase는 이전 phase verification 통과 후 `kickoff` 절차를 거친 새 세션에서만 시작할 수 있도록 control-plane gate를 둔다. - -- Q: [acceptance] fresh-session 증명은 실제 외부 세션 ID가 꼭 필요한가요? -- A: 현재 저장소 단독으로는 어렵기 때문에 control-plane proof 수준에서 kickoff acknowledgement를 남기는 방식이 적절합니다. -- Decision: fresh-session 증명은 external session ID integration이 아니라 kickoff evidence 기반 control-plane proof로 고정한다. - -- Q: [non_goal] 이번 follow-up에서 `git-ranker` 저장소 내부 구현까지 이 저장소에서 다시 수정해야 하나요? -- A: 아닙니다. 이미 다른 작업에서 완료된 backend 결과를 가리키는 workflow repo의 gitlink만 맞추면 됩니다. -- Decision: 이번 follow-up은 workflow repo의 `git-ranker` submodule pointer 반영만 다루고, backend 소스 변경은 `git-ranker` 저장소의 source of truth에 맡긴다. - -## Approval - -- Actor: `user` -- Timestamp: `2026-04-16T09:51:41+00:00` -- Note: User asked to address PR review feedback about legacy empty-input phase compatibility. diff --git a/workflows/tasks/task-socratic-coverage-phase-kickoff/task.json b/workflows/tasks/task-socratic-coverage-phase-kickoff/task.json deleted file mode 100644 index 71627ac..0000000 --- a/workflows/tasks/task-socratic-coverage-phase-kickoff/task.json +++ /dev/null @@ -1,97 +0,0 @@ -{ - "id": "task-socratic-coverage-phase-kickoff", - "title": "Strengthen spec coverage gate and phase kickoff session contract", - "state": "review_ready", - "primary_repo": "git-ranker-workflow", - "created_at": "2026-04-16T07:44:01+00:00", - "approved_at": "2026-04-16T09:51:41+00:00", - "approval": { - "actor": "user", - "note": "User asked to address PR review feedback about legacy empty-input phase compatibility.", - "timestamp": "2026-04-16T09:51:41+00:00" - }, - "active_phase_id": "phase-4", - "latest_run_id": "20260416T095316-852c69fa", - "last_verified_run_id": "20260416T095305-fef15d65", - "blocked_reason": null, - "user_validated": false, - "user_validation_note": null, - "intake": { - "request_summary": "소크라테스식 질문으로 spec을 잠글 때 AI가 요구사항을 충분히 이해할 때까지 질의를 계속하고, 그 결과가 명시적 coverage gate로 강제되도록 control plane을 강화한다. phase는 항상 독립적인 작업 단위여야 하며, 한 phase의 구현과 검증이 끝난 뒤 다음 phase는 새 세션에서만 시작할 수 있도록 kickoff contract를 도입한다. 다음 phase를 시작하는 새 세션은 `task.json`, `phases.json`, `spec.md`를 읽고 어디서부터 무엇을 해야 하는지 복원할 수 있어야 한다. 이 PR에는 다른 작업으로 이미 완료된 `git-ranker` backend 결과를 가리키는 workflow repo의 `git-ranker` gitlink 업데이트도 함께 반영해야 한다.", - "problem_summary": "현재 `approve`는 필수 섹션과 최소 한 개의 `Q/A/Decision` triplet만 검사하므로, AI가 요구사항을 충분히 파악했는지와 추가 질문이 더 필요한지까지는 보장하지 못한다. 현재 phase 상태는 JSON에 기록되지만, 다음 phase를 새 세션에서 시작하기 전에 어떤 정보를 읽고 어떤 순서로 시작해야 하는지까지는 runtime contract로 고정되어 있지 않다. 이 상태에서는 spec 품질이 얕은 문답으로 승인될 수 있고, multi-phase 작업에서 다음 phase가 이전 채팅 맥락에 암묵적으로 의존하게 되어 control plane이 세션 독립성을 보장하지 못한다. 현재 PR 브랜치에는 workflow control-plane 변경만 있고, 같은 작업 흐름에서 이미 완료된 `git-ranker` backend 기준 커밋을 가리키는 submodule gitlink는 빠져 있어 reviewer가 실제 baseline을 한 번에 확인하기 어렵다. bootstrap metadata default를 도입한 뒤, legacy phase payload가 `inputs=[]`와 bootstrap field omission을 함께 사용하면 `plan`이 빈 `required_reads`를 저장하려다 검증에서 실패하는 회귀가 생길 수 있다.", - "goals": [ - "`approve` 전에 목표, 범위, 비범위, 제약, acceptance를 모두 덮는 clarification coverage gate를 도입한다.", - "`status`가 아직 덜 물어본 clarification 축을 드러내고, approval readiness를 coverage 기준으로 판단하게 한다.", - "각 phase가 다음 세션 시작에 필요한 bootstrap 정보를 `phases.json`에 담도록 확장한다.", - "`kickoff`와 phase-start gating을 도입해, 검증이 끝난 다음 phase는 새 세션 kickoff 절차를 거친 뒤에만 시작되도록 한다.", - "`doctor`와 consistency check가 incomplete task artifact를 더 명확하게 보고하도록 개선한다.", - "관련 문서, skills, hooks surface, 테스트를 함께 갱신한다.", - "workflow control-plane 변경을 publish할 때 함께 검토되어야 하는 `git-ranker` submodule pointer를 같은 PR 브랜치에 반영한다.", - "legacy phase payload가 빈 `inputs`와 omitted bootstrap field를 사용하더라도 `plan`이 회귀 없이 계속 통과하도록 bootstrap default 적용을 backward compatible하게 유지한다." - ], - "non_goals": [ - "실제 Codex 런타임의 메모리 초기화 여부를 외부 session ID로 증명하는 통합까지 이번 작업 범위에 넣지 않는다.", - "phase 중간 지점에서 세션을 갈아타는 resume workflow는 이번 작업 목표가 아니다.", - "앱 저장소(`git-ranker`, `git-ranker-client`)의 런타임 계약이나 제품 기능을 변경하지 않는다." - ], - "constraints": [ - "root `AGENTS.md` workflow 계약을 따른다. 승인된 spec과 canonical phase 없이 구현을 시작하지 않는다.", - "task/phase/run state는 `python3 scripts/workflow.py ...` 명령으로만 전이한다.", - "workflow 계약 변경이므로 `AGENTS.md`, `docs/`, `.codex/skills/`, `.githooks/`, `tests/`를 함께 갱신한다.", - "다음 phase 시작점 복원은 `task.json`, `phases.json`, `spec.md`를 기준으로 설계하고 `runs/*.json`은 보조 evidence로만 사용한다.", - "fresh-session 증명은 Codex client integration이 아니라 control-plane proof 수준으로 고정한다.", - "`git-ranker` 서브모듈 반영은 workflow repo의 gitlink 업데이트만 다루고, backend 저장소 내부 변경 자체를 이 저장소에서 재작성하거나 재검증 대상으로 복제하지 않는다." - ], - "acceptance": [ - "clarification coverage가 부족한 spec은 `approve`에 실패하고, `status`는 누락 축을 JSON으로 노출한다.", - "승인된 intake의 clarification에는 coverage category가 잠기고, docs와 skill이 그 계약을 설명한다.", - "`phases.json` 각 phase는 다음 세션 시작에 필요한 bootstrap 필드를 포함한다.", - "`python3 scripts/workflow.py kickoff `가 active phase bootstrap summary를 기록하고, matching kickoff 없이는 `run --start`가 실패한다.", - "`verify` 성공 후 다음 pending phase가 있으면 task는 다음 phase kickoff를 요구하는 상태로 전이한다.", - "새 CLI 프로세스 기준 테스트로 `phase-1 verify -> phase-2 kickoff -> phase-2 start` 흐름이 검증된다.", - "incomplete task directory가 있을 때 consistency check/doctor가 원인을 명확히 보고한다.", - "PR 브랜치의 `git-ranker` gitlink가 backend PR `#89`의 커밋 `fbc672c6384eb896183f838be7413e195e388dc5`를 가리키고, review summary에도 그 사실이 반영된다.", - "bootstrap field가 없는 legacy phase plan에서 `inputs=[]`여도 `plan`이 실패하지 않고, status bootstrap summary는 빈 `required_reads`를 안전하게 노출한다." - ], - "clarifications": [ - { - "question": "소크라테스식 질문 품질은 최소 질문 개수로 볼까요, 아니면 요구사항 coverage로 볼까요?", - "category": "goal", - "answer": "질문 수보다 요구사항 축을 모두 덮었는지가 중요합니다. 범위, 목적, 비범위, 제약, acceptance가 빠지지 않아야 합니다.", - "decision": "`approve`는 최소 질문 수가 아니라 clarification coverage gate로 판단한다.", - "resolved": true - }, - { - "question": "다음 phase를 시작하는 새 세션은 어떤 source of truth를 읽고 시작해야 하나요?", - "category": "constraint", - "answer": "`task.json`, `phases.json`과 현재 잠긴 `spec.md`를 읽고 어디서부터 시작해야 하는지 알 수 있어야 합니다.", - "decision": "phase-start bootstrap은 `task.json`/`phases.json` + `spec.md` 기준으로 복원 가능해야 한다.", - "resolved": true - }, - { - "question": "phase별 독립 세션 요구는 어떤 수준으로 강제할까요?", - "category": "scope", - "answer": "phase 경계에서만 새 세션을 강제하고, 새 phase 시작 전 kickoff proof가 없으면 시작하지 못하게 해야 합니다.", - "decision": "각 phase는 이전 phase verification 통과 후 `kickoff` 절차를 거친 새 세션에서만 시작할 수 있도록 control-plane gate를 둔다.", - "resolved": true - }, - { - "question": "fresh-session 증명은 실제 외부 세션 ID가 꼭 필요한가요?", - "category": "acceptance", - "answer": "현재 저장소 단독으로는 어렵기 때문에 control-plane proof 수준에서 kickoff acknowledgement를 남기는 방식이 적절합니다.", - "decision": "fresh-session 증명은 external session ID integration이 아니라 kickoff evidence 기반 control-plane proof로 고정한다.", - "resolved": true - }, - { - "question": "이번 follow-up에서 `git-ranker` 저장소 내부 구현까지 이 저장소에서 다시 수정해야 하나요?", - "category": "non_goal", - "answer": "아닙니다. 이미 다른 작업에서 완료된 backend 결과를 가리키는 workflow repo의 gitlink만 맞추면 됩니다.", - "decision": "이번 follow-up은 workflow repo의 `git-ranker` submodule pointer 반영만 다루고, backend 소스 변경은 `git-ranker` 저장소의 source of truth에 맡긴다.", - "resolved": true - } - ] - }, - "contract_version": 2, - "kickoff_required_for_phase": null, - "last_kickoff_run_id": null -}